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)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy