Skip to content

Commit b62734c

Browse files
committed
Merge remote-tracking branch 'origin/feat/1577-auto-news-from-rss'
2 parents 0baae65 + 0694996 commit b62734c

File tree

16 files changed

+343
-28
lines changed

16 files changed

+343
-28
lines changed

.github/workflows/fetch_news.yml

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
name: Fetch News
2+
3+
on:
4+
schedule:
5+
# 毎朝 9:00 JST
6+
- cron: '0 0 * * *'
7+
workflow_dispatch:
8+
9+
jobs:
10+
fetch:
11+
runs-on: ubuntu-latest
12+
13+
steps:
14+
- name: Checkout repository
15+
uses: actions/checkout@v3
16+
17+
- name: Set up Ruby
18+
uses: ruby/setup-ruby@v1
19+
with:
20+
ruby-version: .ruby-version
21+
bundler-cache: true
22+
23+
- name: Install dependencies
24+
run: bundle install --jobs 4 --retry 3
25+
26+
- name: Run news:fetch task
27+
run: bin/rails news:fetch
28+
29+
- name: Commit updated news.yml
30+
run: |
31+
git config user.name "github-actions[bot]"
32+
git config user.email "github-actions[bot]@users.noreply.github.com"
33+
git add db/news.yml
34+
if ! git diff --cached --quiet; then
35+
git commit -m "chore: update news.yml via GitHub Actions"
36+
git push
37+
else
38+
echo "No changes in db/news.yml"
39+
fi

app/controllers/home_controller.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,5 +3,6 @@ def show
33
@dojo_count = Dojo.active_dojos_count
44
@regions_and_dojos = Dojo.group_by_region_on_active
55
@prefectures_and_dojos = Dojo.group_by_prefecture_on_active
6+
@news_items = News.recent.limit(7)
67
end
78
end

app/helpers/application_helper.rb

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -203,4 +203,8 @@ def translate_dojo_tag(tag_name)
203203
tag_translations[tag_name] || tag_name
204204
end
205205

206+
def format_news_title(news)
207+
has_emoji = news.title[0]&.match?(/[\p{Emoji}&&[^0-9#*]]/)
208+
has_emoji ? news.title : "📰 #{news.title}"
209+
end
206210
end

app/models/news.rb

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
class News < ApplicationRecord
2+
scope :recent, -> { order(published_at: :desc) }
3+
4+
validates :title, presence: true
5+
validates :url, presence: true,
6+
uniqueness: true,
7+
format: { with: /\Ahttps?:\/\/.*\z/i }
8+
validates :published_at, presence: true
9+
end

app/views/home/show.html.erb

Lines changed: 5 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -177,27 +177,11 @@
177177
</h2>
178178
<br>
179179
<ul class="list" style="list-style: none;">
180-
<li>💻
181-
<%= link_to '米国系IT企業からCoderDojoへ、55台のノートPC寄贈', news_url('/2025/04/04/55-laptops-to-coderdojo/') %>
182-
</li>
183-
<li>📰
184-
<%= link_to 'DojoLetter Vol.81 2025年01月号', news_url('/2025/03/10/dojoletter-vol-82-2025年01月号/') %>
185-
</li>
186-
<li>🎲
187-
<%= link_to 'ダイス×プログラミング『ニャイス!コード』 を75台寄贈', news_url('/2025/02/14/coderdojo-de-nyaicecode/') %>
188-
</li>
189-
<li>📰
190-
<%= link_to 'DojoLetter Vol.80 2024年12月号', news_url('/2025/02/10/dojoletter-vol-80-2024年12月号/') %>
191-
</li>
192-
<li>📰
193-
<%= link_to 'DojoLetter Vol.79 2024年11月号', news_url('/2025/01/14/dojoletter-vol-79-2024年11月号/') %>
194-
</li>
195-
<li>🎄
196-
<%= link_to '128台のノートPC寄贈、Box JapanからCoderDojoへ', news_url('/2024/12/25/box-japan-to-coderdojo/') %>
197-
</li>
198-
<li>📰
199-
<%= link_to 'DojoLetter Vol.78 2024年10月号', news_url('/2024/12/10/dojoletter-vol-78-2024年10月号/') %>
200-
</li>
180+
<% @news_items.each do |news| %>
181+
<li>
182+
<%= link_to format_news_title(news), news.url, target: '_blank', rel: 'noopener noreferrer' %>
183+
</li>
184+
<% end %>
201185
</ul>
202186
<p style="margin-top: 30px;">
203187
最新情報はメールで受け取れます。
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
class CreateNews < ActiveRecord::Migration[8.0]
2+
def change
3+
create_table :news do |t|
4+
t.string :title
5+
t.string :url
6+
t.datetime :published_at
7+
8+
t.timestamps
9+
end
10+
11+
add_index :news, :url, unique: true
12+
end
13+
end

db/news.yml

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
---
2+
news:
3+
- id: 10
4+
url: https://news.coderdojo.jp/2025/07/14/233-laptops-to-coderdojo/
5+
title: 米国系 IT 企業から CoderDojo へ、233 台のノート PC 寄贈
6+
published_at: Mon, 14 Jul 2025 05:50:31 +0000
7+
- id: 9
8+
url: https://news.coderdojo.jp/2025/07/10/dojoletter-vol-86-2025%e5%b9%b405%e6%9c%88%e5%8f%b7/
9+
title: DojoLetter Vol.86 2025年05月号
10+
published_at: Thu, 10 Jul 2025 04:00:07 +0000
11+
- id: 8
12+
url: https://news.coderdojo.jp/2025/06/10/dojoletter-vol-85-2025%e5%b9%b404%e6%9c%88%e5%8f%b7/
13+
title: DojoLetter Vol.85 2025年04月号
14+
published_at: Tue, 10 Jun 2025 03:30:18 +0000
15+
- id: 7
16+
url: https://news.coderdojo.jp/2025/05/12/dojoletter-vol-84-2025%e5%b9%b403%e6%9c%88%e5%8f%b7/
17+
title: DojoLetter Vol.84 2025年03月号
18+
published_at: Mon, 12 May 2025 04:00:33 +0000
19+
- id: 6
20+
url: https://news.coderdojo.jp/2025/04/10/dojoletter-vol-83-2025%e5%b9%b402%e6%9c%88%e5%8f%b7/
21+
title: DojoLetter Vol.83 2025年02月号
22+
published_at: Thu, 10 Apr 2025 03:45:27 +0000
23+
- id: 5
24+
url: https://news.coderdojo.jp/2025/04/04/55-laptops-to-coderdojo/
25+
title: 米国系 IT 企業から CoderDojo へ、55 台のノート PC 寄贈
26+
published_at: Fri, 04 Apr 2025 10:00:32 +0000
27+
- id: 4
28+
url: https://news.coderdojo.jp/2025/03/10/dojoletter-vol-82-2025%e5%b9%b401%e6%9c%88%e5%8f%b7/
29+
title: DojoLetter Vol.82 2025年01月号
30+
published_at: Mon, 10 Mar 2025 04:00:33 +0000
31+
- id: 3
32+
url: https://news.coderdojo.jp/2025/02/14/coderdojo-de-nyaicecode/
33+
title: "\U0001F3B2 ダイス×プログラミング『ニャイス!コード』を、CoderDojo に75台寄贈"
34+
published_at: Fri, 14 Feb 2025 08:24:07 +0000
35+
- id: 2
36+
url: https://news.coderdojo.jp/2025/02/10/dojoletter-vol-80-2024%e5%b9%b412%e6%9c%88%e5%8f%b7/
37+
title: DojoLetter Vol.80 2024年12月号
38+
published_at: Mon, 10 Feb 2025 04:00:55 +0000
39+
- id: 1
40+
url: https://news.coderdojo.jp/2025/01/14/dojoletter-vol-79-2024%e5%b9%b411%e6%9c%88%e5%8f%b7/
41+
title: DojoLetter Vol.79 2024年11月号
42+
published_at: Tue, 14 Jan 2025 03:30:45 +0000

db/schema.rb

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,10 @@
1010
#
1111
# It's strongly recommended that you check this file into your version control system.
1212

13-
ActiveRecord::Schema[7.1].define(version: 2025_05_20_091834) do
13+
ActiveRecord::Schema[8.0].define(version: 2025_06_30_040611) do
1414
# These are extensions that must be enabled in order to support this database
15+
enable_extension "pg_catalog.plpgsql"
1516
enable_extension "pg_stat_statements"
16-
enable_extension "plpgsql"
1717

1818
create_table "dojo_event_services", id: :serial, force: :cascade do |t|
1919
t.integer "dojo_id", null: false
@@ -58,6 +58,15 @@
5858
t.index ["service_name", "event_id"], name: "index_event_histories_on_service_name_and_event_id", unique: true
5959
end
6060

61+
create_table "news", force: :cascade do |t|
62+
t.string "title"
63+
t.string "url"
64+
t.datetime "published_at"
65+
t.datetime "created_at", null: false
66+
t.datetime "updated_at", null: false
67+
t.index ["url"], name: "index_news_on_url", unique: true
68+
end
69+
6170
create_table "podcasts", force: :cascade do |t|
6271
t.string "enclosure_url", null: false
6372
t.string "title", null: false

lib/tasks/fetch_news.rake

Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
require 'rss'
2+
require 'net/http'
3+
require 'uri'
4+
require 'yaml'
5+
require 'time'
6+
require 'active_support/broadcast_logger'
7+
8+
def safe_open(url)
9+
uri = URI.parse(url)
10+
raise "不正なURLです: #{url}" unless uri.is_a?(URI::HTTP) || uri.is_a?(URI::HTTPS)
11+
12+
Net::HTTP.start(uri.host, uri.port, use_ssl: uri.scheme == 'https') do |http|
13+
request = Net::HTTP::Get.new(uri)
14+
response = http.request(request)
15+
response.body
16+
end
17+
end
18+
19+
namespace :news do
20+
desc 'RSS フィードから最新ニュースを取得し、db/news.yml に書き出す'
21+
task fetch: :environment do
22+
# ロガー設定(ファイル+コンソール出力)
23+
file_logger = ActiveSupport::Logger.new('log/news.log')
24+
console = ActiveSupport::Logger.new(STDOUT)
25+
logger = ActiveSupport::BroadcastLogger.new(file_logger, console)
26+
27+
logger.info('==== START news:fetch ====')
28+
29+
# 既存の news.yml を読み込み
30+
yaml_path = Rails.root.join('db', 'news.yml')
31+
existing_news = if File.exist?(yaml_path)
32+
YAML.safe_load(File.read(yaml_path), permitted_classes: [Time], aliases: true)['news'] || []
33+
else
34+
[]
35+
end
36+
37+
# テスト/ステージング環境ではサンプルファイル、本番は実サイトのフィード
38+
feed_urls = if Rails.env.test? || Rails.env.staging?
39+
[Rails.root.join('spec', 'fixtures', 'sample_news.rss').to_s]
40+
else
41+
[
42+
'https://news.coderdojo.jp/feed/'
43+
# 必要に応じて他 Dojo の RSS もここに追加可能
44+
# 'https://coderdojotokyo.org/feed',
45+
]
46+
end
47+
48+
# RSS 取得&パース
49+
new_items = feed_urls.flat_map do |url|
50+
logger.info("Fetching RSS → #{url}")
51+
begin
52+
rss = safe_open(url)
53+
feed = RSS::Parser.parse(rss, false)
54+
feed.items.map do |item|
55+
{
56+
'url' => item.link,
57+
'title' => item.title,
58+
'published_at' => item.pubDate.to_s
59+
}
60+
end
61+
rescue => e
62+
logger.warn("⚠️ Failed to fetch #{url}: #{e.message}")
63+
[]
64+
end
65+
end
66+
67+
# 既存データをハッシュに変換(URL をキーに)
68+
existing_items_hash = existing_news.index_by { |item| item['url'] }
69+
70+
# 新しいアイテムと既存アイテムを分離
71+
truly_new_items = []
72+
updated_items = []
73+
74+
new_items.each do |new_item|
75+
if existing_items_hash.key?(new_item['url'])
76+
# 既存アイテムの更新
77+
existing_item = existing_items_hash[new_item['url']]
78+
updated_item = existing_item.merge(new_item) # 新しい情報で更新
79+
updated_items << updated_item
80+
else
81+
# 完全に新しいアイテム
82+
truly_new_items << new_item
83+
end
84+
end
85+
86+
# 既存の最大IDを取得
87+
max_existing_id = existing_news.map { |item| item['id'].to_i }.max || 0
88+
89+
# 新しいアイテムのみに ID を割り当て(古い順)
90+
truly_new_items_sorted = truly_new_items.sort_by { |item|
91+
Time.parse(item['published_at'])
92+
}
93+
94+
truly_new_items_sorted.each_with_index do |item, index|
95+
item['id'] = max_existing_id + index + 1
96+
end
97+
98+
# 更新されなかった既存アイテムを取得
99+
updated_urls = updated_items.map { |item| item['url'] }
100+
unchanged_items = existing_news.reject { |item| updated_urls.include?(item['url']) }
101+
102+
# 全アイテムをマージ
103+
all_items = unchanged_items + updated_items + truly_new_items_sorted
104+
105+
# 日付降順ソート
106+
sorted_items = all_items.sort_by { |item|
107+
Time.parse(item['published_at'])
108+
}.reverse
109+
110+
File.open('db/news.yml', 'w') do |f|
111+
formatted_items = sorted_items.map do |item|
112+
{
113+
'id' => item['id'],
114+
'url' => item['url'],
115+
'title' => item['title'],
116+
'published_at' => item['published_at']
117+
}
118+
end
119+
120+
f.write({ 'news' => formatted_items }.to_yaml)
121+
end
122+
123+
logger.info("✅ Wrote #{sorted_items.size} items to db/news.yml (#{truly_new_items_sorted.size} new, #{updated_items.size} updated)")
124+
logger.info('==== END news:fetch ====')
125+
end
126+
end

lib/tasks/import_news.rake

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
require 'yaml'
2+
3+
namespace :news do
4+
desc 'db/news.yml を読み込んで News テーブルを upsert する'
5+
task import_from_yaml: :environment do
6+
yaml_path = Rails.root.join('db', 'news.yml')
7+
raw = YAML.safe_load(File.read(yaml_path), permitted_classes: [Time], aliases: true)
8+
9+
# entries を計算
10+
entries = raw['news'] || []
11+
12+
entries.each do |attrs|
13+
news = News.find_or_initialize_by(url: attrs['url'])
14+
news.assign_attributes(
15+
title: attrs['title'],
16+
published_at: attrs['published_at']
17+
)
18+
news.save!
19+
puts "[news] #{news.published_at.to_date} #{news.title}"
20+
end
21+
22+
puts "Imported #{entries.size} items."
23+
end
24+
end

0 commit comments

Comments
 (0)