Conversation
- CLAUDE.mdをシンプルなインデックス型に刷新(Day表記を全削除) - ドキュメントをカテゴリ別に整理: - docs/design/: 設計ドキュメント(機能仕様、キーボードタイプ、セキュリティ) - docs/operations/: 運用ドキュメント(デプロイ、バックアップ、監視、メンテナンス) - docs/development/: 開発ドキュメント(テスト、コード品質、国際化) - docs/future/: 将来実装予定の設計(Vial連携、指配置カスタマイズ) - docs/archive/: 日付付きドキュメント(記録として保持) - 古いCLAUDE_*.mdファイルを削除(FEATURES、KEYBOARD_TYPE_DESIGN、STABILITY_AND_OPERATIONS、TEST_STRATEGY) - 既存の運用ドキュメントを適切なディレクトリに移動 oobunプロジェクトの構成を参考に、アプリの仕様が明確にわかるシンプルな構成を実現。 🤖 Generated with Claude Code Co-Authored-By: Claude <noreply@anthropic.com>
- Articleモデル作成(タイトル、スラッグ、コンテンツ、カテゴリー、公開日時) - カテゴリーenum実装(typing_tips, keyboard_basics, keyboard_reviews, customization, typnix_guides, community) - スラッグ自動生成機能(重複チェック、一意性保証) - スコープ実装(published, recent, by_category, popular) - インスタンスメソッド実装(published?, to_param, increment_view_count!, author_name) - バリデーション実装(タイトル・コンテンツ必須、スラッグ形式チェック、一意性) - RSpecテスト26例(全てパス) - FactoryBot trait実装(draft, scheduled, popular, カテゴリー別) - マイグレーション実装(NOT NULL制約、インデックス、外部キー) - 記事アイデア45+件をdocs/future/articles.mdに文書化 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
- Admin::ArticlesControllerの実装(index, new, create, edit, update, destroy) - slug-basedルーティング(/admin/articles/:slug) - フィルター機能(全て・公開中・下書き・予約投稿) - Kaminariによるページネーション(1ページ20件) - フォームビュー実装(_form.html.slim) - タイトル・スラッグ・カテゴリー・概要・本文(Markdown)・公開日時 - バリデーションエラー表示 - 下書き保存・公開ボタン - 一覧ビュー実装(index.html.slim) - ステータスバッジ(公開中・予約投稿・下書き) - 閲覧数表示 - 編集・削除アクション - ja.ymlに日本語翻訳追加(Article属性・カテゴリー名) - RuboCop 0違反 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
【公開ページ実装】 - ArticlesControllerの実装(index, show) - slug-basedルーティング(/articles, /articles/:slug) - カテゴリーフィルター機能(6カテゴリー) - 一覧ページ実装(index.html.slim) - カードグリッドレイアウト(1列・2列・3列レスポンシブ) - カテゴリーバッジ、著者名、公開日、閲覧数表示 - Kaminariページネーション(1ページ12件) - 詳細ページ実装(show.html.slim) - パンくずリスト - Markdown本文レンダリング - X(Twitter)シェアボタン - 関連記事表示(同カテゴリー3件) - 閲覧数カウント自動インクリメント 【Markdownレンダリング実装】 - redcarpet gem導入 - ArticlesHelperにmarkdownメソッド実装 - リンクは新しいタブで開く(target="_blank", rel="noopener noreferrer") - 自動リンク、テーブル、コードブロック、取り消し線、脚注対応 - proseクラス用CSS実装(application.css) - 見出し・段落・リンク・コードブロック・リスト・引用・テーブルスタイル - ダークモード対応 - 読みやすい行間・最大幅設定 【SEO/OGP実装】 - Article#meta_descriptionメソッド追加(excerpt優先、なければcontent切り詰め160文字) - content_for によるページごとのメタタグカスタマイズ対応 - OGPタグ実装(title, description, type, url, image, site_name, locale) - Twitter Card実装(summary_large_image) - 記事固有のOGPタグ(article:published_time, article:author) - レイアウトファイル修正(application.html.slim, _head.html.slim) 【その他】 - RuboCop 0違反 - 全てのタスク完了(Article機能実装完了) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
- activerecord.attributes.article.category/* を category/ プレフィックス形式に変更 - I18n::InvalidPluralizationData エラーを解消 - Article model specs 全26例パス 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
- to_s(:delimited) を number_with_delimiter() に変更 - 管理者画面・一覧ページ・詳細ページの3箇所を修正 - TypeError: no implicit conversion of Symbol into Integer を解消 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
- flexboxで横並び配置に変更 - gap-3で適切な間隔を確保 - button_toによるform要素のずれを解消 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
- .prose.max-w-none で最大幅制限を無効化 - 記事本文とシェアエリアの幅を統一 - 読みやすさを向上 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
- .prose ul に list-style-type: disc を追加(箇条書き「•」を表示) - .prose ol に list-style-type: decimal を追加(番号付きリスト「1.」を表示) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
デスクトップ表示時に画面を覆いすぎる問題を解消するため、 よりスマートな横長の2:1アスペクト比に変更。 - 管理者フォーム: ラベル・説明文・プレビュー画像を2:1に変更 - 記事一覧ページ: カードのサムネイルを2:1に変更 (600×300px) - 記事詳細ページ: サムネイルを2:1に変更 (1200×600px) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
Google AdSense審査対応として、記事にサムネイルと本文用画像を
アップロード・管理できる機能を実装。
## 実装内容
### ActiveStorage導入
- rails active_storage:install 実行
- image_processing gem (既存)
- VPS local storage 使用 (Kamal volumes設定済み)
### Articleモデル
- has_one_attached :thumbnail (サムネイル画像)
- has_many_attached :images (本文用画像、複数)
- thumbnail_validation (5MB制限、JPEG/PNG/GIF/WebP)
### 画像アップロード機能
- サムネイル: 1枚、2:1アスペクト比でプレビュー表示
- 本文用画像: 複数枚、クリックでMarkdownパスをコピー
- url_for() で絶対URL生成 (Markdown表示用)
- 既存画像の保持ロジック (空配列時にimagesパラメータを削除)
### 画像削除機能
- DELETE /admin/articles/:slug/images/:signed_id
- purge_imageアクション (ActiveStorage#purge)
- フォームネスト回避のためlink_to使用 (button_toから変更)
### UI改善
- 管理者ダッシュボードに「📝 記事管理」ボタン追加
- 記事詳細ページに管理者専用「編集」ボタン追加
- 編集ページの「記事を表示」リンク修正
- 画像削除ボタン (×アイコン、確認ダイアログ付き)
- 2列グリッド表示
## 技術的対応
### フォーム問題の解決
- direct_upload無効化 (JavaScriptエラー回避)
- button_to → link_to 変更 (フォームネスト禁止のため)
- data: { turbo_method: :delete } でDELETEリクエスト送信
### 画像パス問題の解決
- rails_blob_path → url_for に変更
- 絶対URL生成でMarkdown内の画像が正しく表示
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Google AdSense審査対応として、分割キーボードに関する 有益なコンテンツを作成。 ## 記事内容 - タイトル: 分割キーボードとは?メリット・デメリット完全ガイド - カテゴリー: 分割キーボード入門 - 対象読者: 初心者向け ## 構成 1. 分割キーボードの基本(種類、特徴) 2. メリット(肩こり軽減、手首負担軽減、カスタマイズ性) 3. デメリット(価格、慣れるまでの期間、持ち運び) 4. 選び方のポイント 5. よくある質問(FAQ) docs/articles/ ディレクトリに下書きとして保存。 今後、管理画面から公開予定。 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
Summary of ChangesHello @sugiwe, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed! このプルリクエストは、Google AdSenseの審査基準である「低品質コンテンツ」のフィードバックに対応するため、記事/ブログ機能を追加するものです。これにより、アプリケーションはよりリッチなコンテンツを提供できるようになり、SEOの強化とユーザーエンゲージメントの向上が期待されます。記事の作成、編集、公開、画像管理、そして公開記事の閲覧といった一連の機能が実装され、管理者は記事コンテンツを柔軟に運用できるようになります。 Highlights
Changelog
Activity
Using Gemini Code AssistThe full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips. Invoking Gemini You can request assistance from Gemini at any point by creating a comment using either
Customization To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a Limitations & Feedback Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here. You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension. Footnotes
|
There was a problem hiding this comment.
Code Review
このプルリクエストでは、Google AdSenseの審査対応として、包括的な記事・ブログ機能が実装されました。全体的に優れた実装ですが、Redcarpet レンダラーの不適切な設定により、Markdownレンダリングヘルパーに保存型XSSの脆弱性が確認されました。この脆弱性に対処することで、悪意のあるコンテンツが記事データベースに挿入された場合でもアプリケーションのセキュリティが確保されます。また、コントローラーの可読性向上とモデルのバリデーションロジックの簡素化について、改善提案があります。
| renderer = Redcarpet::Render::HTML.new( | ||
| filter_html: false, | ||
| hard_wrap: true, | ||
| link_attributes: { target: "_blank", rel: "noopener noreferrer" } | ||
| ) |
There was a problem hiding this comment.
The markdown helper is vulnerable to Stored Cross-Site Scripting (XSS). It initializes the Redcarpet HTML renderer with filter_html: false (line 6) and then marks the rendered output as html_safe (line 24). This allows raw HTML tags and malicious scripts embedded in the article content to be rendered and executed in the browser of any user viewing the article. While article content is currently provided by administrators, this still poses a significant risk if an admin account is compromised or if a malicious internal actor is involved. Additionally, the renderer does not use safe_links_only: true, which could allow javascript: URIs in Markdown links.
To remediate this, set filter_html: true and safe_links_only: true in the renderer options. This will ensure that any HTML tags in the input are escaped or stripped, and only safe protocols are allowed for links.
renderer = Redcarpet::Render::HTML.new(
filter_html: true,
safe_links_only: true,
hard_wrap: true,
link_attributes: { target: "_blank", rel: "noopener noreferrer" }
)| @articles = case @filter | ||
| when "published" | ||
| Article.published.recent | ||
| when "draft" | ||
| Article.where(published_at: nil).recent | ||
| when "scheduled" | ||
| Article.where("published_at > ?", Time.current).recent | ||
| else | ||
| Article.recent | ||
| end |
There was a problem hiding this comment.
フィルターロジックが各case節で完結していますが、より宣言的でDRYな書き方にリファクタリングできます。case文でスコープを決定し、その後に共通のメソッドチェーン(.recent.page)を適用することで、コードの重複を減らし、可読性を向上させることができます。
さらに、Articleモデルにdraftとscheduledのスコープを定義すると、コントローラーはより責務に集中でき、コードが綺麗になります。
# app/models/article.rb に追加
scope :draft, -> { where(published_at: nil) }
scope :scheduled, -> { where("published_at > ?", Time.current) } scope = case @filter
when "published"
Article.published
when "draft"
Article.where(published_at: nil)
when "scheduled"
Article.where("published_at > ?", Time.current)
else
Article.all
end
@articles = scope.recent
app/models/article.rb
Outdated
| validates :slug, uniqueness: true, | ||
| format: { with: /\A[a-z0-9-]+\z/ }, length: { maximum: 100 }, allow_blank: true | ||
| validates :content, presence: true | ||
| validates :category, presence: true | ||
| validate :slug_must_exist_after_generation | ||
| validate :thumbnail_validation |
There was a problem hiding this comment.
スラッグのバリデーションロジックが少し複雑になっているようです。before_validationコールバックでスラッグが必ず生成されることを考慮すると、allow_blank: trueオプションとカスタムバリデーションメソッドslug_must_exist_after_generationは不要になります。
validates :slug, presence: true を使用することで、Railsの標準的な方法でよりシンプルにバリデーションを記述でき、コードの意図が明確になります。この変更に合わせて、63行目から67行目のslug_must_exist_after_generationメソッドも削除してください。
validates :slug, presence: true, uniqueness: true,
format: { with: /\A[a-z0-9-]+\z/ }, length: { maximum: 100 }
validates :content, presence: true
validates :category, presence: true
validate :thumbnail_validation1. XSS脆弱性の修正(articles_helper.rb) - filter_html: true に変更(HTMLタグをエスケープ) - safe_links_only: true を追加(安全なプロトコルのみ許可) 2. DRY原則の適用(articles_controller.rb、article.rb) - Article モデルに draft、scheduled スコープを追加 - コントローラーの重複コードを削減 3. バリデーションロジックの簡略化(article.rb) - slug に presence: true を追加 - 不要な allow_blank: true と custom validation を削除 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
|
/gemini review |
There was a problem hiding this comment.
Code Review
Google AdSense審査対応のための記事機能の実装、お疲れ様です。ActiveStorageを利用した画像管理やMarkdownレンダリング、SEO/OGP対応など、必要な機能がしっかりと網羅されています。特に、複数画像アップロード時の既存画像保持ロジックや、管理者向けのUI改善など、実用的な配慮がなされている点が高評価です。一方で、画像の削除機能におけるロジックの不備(実行時エラーの可能性)や、翻訳ファイルのキー設定の誤り、日本語タイトル時のスラッグ自動生成に関する挙動など、いくつか修正・改善が必要な点が見受けられました。これらを修正することで、より堅牢で使いやすい機能になるでしょう。
| image = @article.images.find_by(signed_id: params[:signed_id]) | ||
| if image | ||
| image.purge | ||
| redirect_to edit_admin_article_path(@article), notice: "画像を削除しました" | ||
| else | ||
| redirect_to edit_admin_article_path(@article), alert: "画像が見つかりませんでした" | ||
| end |
There was a problem hiding this comment.
signed_id はデータベースのカラムではないため、find_by(signed_id: ...) では検索できず、実行時に ActiveRecord::UnknownAttributeError が発生します。signed_id を使用してレコードを特定するには、ActiveStorage::Attachment.find_signed メソッドを使用する必要があります。また、セキュリティのために、取得した画像が本当に対象の記事(@article)に属しているかを確認するロジックも含めるべきです。
def purge_image
image = ActiveStorage::Attachment.find_signed(params[:signed_id])
if image && image.record == @article
image.purge
redirect_to edit_admin_article_path(@article), notice: "画像を削除しました"
else
redirect_to edit_admin_article_path(@article), alert: "画像が見つかりませんでした"
end
end
config/locales/ja.yml
Outdated
| "category/typing_tips": "タイピングのコツ" | ||
| "category/keyboard_basics": "キーボード基礎知識" | ||
| "category/keyboard_reviews": "キーボードレビュー" | ||
| "category/customization": "カスタマイズガイド" | ||
| "category/typnix_guides": "Typnix使い方" | ||
| "category/community": "コミュニティ" |
There was a problem hiding this comment.
Railsの human_attribute_name や enum の翻訳機能は、ドット区切りによるネスト構造を期待します。現在の "category/typing_tips" のようなスラッシュを含んだフラットなキー文字列では、Article.human_attribute_name("category.#{k}") で正しく翻訳が取得されません。category: の下に各キーをネストさせる構造に修正してください。
category:
typing_tips: "タイピングのコツ"
keyboard_basics: "キーボード基礎知識"
keyboard_reviews: "キーボードレビュー"
customization: "カスタマイズガイド"
typnix_guides: "Typnix使い方"
community: "コミュニティ"
app/models/article.rb
Outdated
| end | ||
|
|
||
| def generate_slug | ||
| base_slug = title.parameterize |
1. 画像削除のセキュリティ修正(articles_controller.rb) - ActiveStorage::Attachment.find_signed を使用 - image.record == @Article で所有権を確認 - 他の記事の画像削除を防止 2. enum翻訳のi18n構造改善(ja.yml、各種ビュー) - activerecord.enums.article.category にネスト構造化 - human_attribute_name から I18n.t に変更 - Rails i18n規約に準拠 3. 日本語タイトル対応(article.rb) - title.parameterize が空の場合、SecureRandom.hex(8) をフォールバック - 日本語タイトルの記事も確実に保存可能 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
|
/gemini review |
There was a problem hiding this comment.
Code Review
Google AdSense審査対応のための記事/ブログ機能の実装、お疲れ様です。ActiveStorageを利用した画像管理やMarkdownレンダリング、SEO対応など、必要な機能が網羅的に実装されています。特に、has_many_attachedにおける既存画像の保持ロジックや、フォームのネスト回避など、Rails特有のハマりどころが適切に処理されている点が高評価です。一方で、Markdownレンダリングの効率化や、画像バリデーションの適用範囲、ビューにおける不要なCSSクラスの整理など、いくつか改善の余地が見受けられました。
| def markdown(text) | ||
| return "" if text.blank? | ||
|
|
||
| renderer = Redcarpet::Render::HTML.new( | ||
| filter_html: true, # HTMLタグをエスケープ(XSS対策) | ||
| safe_links_only: true, # 安全なプロトコル(http/https)のみ許可 | ||
| hard_wrap: true, | ||
| link_attributes: { target: "_blank", rel: "noopener noreferrer" } | ||
| ) | ||
|
|
||
| markdown = Redcarpet::Markdown.new( | ||
| renderer, | ||
| autolink: true, | ||
| tables: true, | ||
| fenced_code_blocks: true, | ||
| strikethrough: true, | ||
| space_after_headers: true, | ||
| superscript: true, | ||
| underline: true, | ||
| highlight: true, | ||
| footnotes: true | ||
| ) | ||
|
|
||
| markdown.render(text).html_safe |
There was a problem hiding this comment.
markdownヘルパーメソッド内で、呼び出しのたびにRedcarpetのインスタンスを生成しています。記事の表示回数やコンテンツ量が増えた際にパフォーマンスに影響を与える可能性があるため、定数として定義して再利用することをお勧めします。
RENDERER = Redcarpet::Render::HTML.new(
filter_html: true,
safe_links_only: true,
hard_wrap: true,
link_attributes: { target: "_blank", rel: "noopener noreferrer" }
)
MARKDOWN = Redcarpet::Markdown.new(
RENDERER,
autolink: true,
tables: true,
fenced_code_blocks: true,
strikethrough: true,
space_after_headers: true,
superscript: true,
underline: true,
highlight: true,
footnotes: true
)
def markdown(text)
return "" if text.blank?
MARKDOWN.render(text).html_safe| format: { with: /\A[a-z0-9-]+\z/ }, length: { maximum: 100 } | ||
| validates :content, presence: true | ||
| validates :category, presence: true | ||
| validate :thumbnail_validation |
| unless thumbnail.content_type.in?(%w[image/jpeg image/png image/gif image/webp]) | ||
| errors.add(:thumbnail, "はJPEG、PNG、GIF、WebP形式のみ対応しています") | ||
| end | ||
| end |
There was a problem hiding this comment.
images用のバリデーションメソッドの実装例です。各添付ファイルに対してサイズと形式のチェックを行います。
end
def images_validation
return unless images.attached?
images.each do |image|
if image.blob.byte_size > 5.megabytes
errors.add(:images, "の各サイズは5MB以下にしてください")
end
unless image.content_type.in?(%w[image/jpeg image/png image/gif image/webp])
errors.add(:images, "はJPEG、PNG、GIF、WebP形式のみ対応しています")
end
end
end| span = number_with_delimiter(@article.view_count) + " 回閲覧" | ||
|
|
||
| / 記事本文(Markdown) | ||
| article.prose.prose-lg.dark:prose-invert.max-w-none.bg-white.dark:bg-gray-800.rounded-lg.shadow-md.p-8.mb-8 |
There was a problem hiding this comment.
概要
Google AdSense審査の「低品質コンテンツ」フィードバックに対応するため、記事/ブログ機能を実装しました。
実装内容
1. Article/Blog基本機能(前回セッション)
/admin/articles)/articles、/articles/:slug)2. UI/UX改善
3. 画像管理機能(ActiveStorage)
4. コンテンツ準備
技術的ハイライト
ActiveStorage画像管理
has_one_attached :thumbnail- サムネイル画像has_many_attached :images- 本文用画像(複数)url_for()で絶対URL生成(Markdown表示用)フォーム問題の解決
button_to→link_to)data: { turbo_method: :delete }でDELETEリクエスト送信direct_upload無効化(JavaScriptエラー回避)画像削除機能
DELETE /admin/articles/:slug/images/:signed_idpurge_imageアクション(ActiveStorage#purge)主要な変更ファイル
モデル
app/models/article.rb- ActiveStorage attachments、画像バリデーションコントローラー
app/controllers/admin/articles_controller.rb- 画像削除アクション、画像パラメータ処理ビュー
app/views/admin/articles/_form.html.slim- 画像アップロードUI、削除ボタンapp/views/articles/index.html.slim- サムネイル表示(2:1)app/views/articles/show.html.slim- サムネイル表示、管理者編集ボタンマイグレーション
db/migrate/20260215062859_create_active_storage_tables.active_storage.rb- ActiveStorage導入コンテンツ
docs/articles/0_分割キーボードとは?メリット・デメリット完全ガイド.md- 記事下書きセキュリティ
テスト
次のステップ
🤖 Generated with Claude Code