概要
開発ブログに「著者プロフィール」機能を追加しました。
これまで、各記事が誰によって書かれたものか分からない状態でした。今後、AIが自動生成した記事と開発者が手動で書いた記事が混在するため、読者が一目で判断できる仕組みが必要と判断しました。
実装した機能:
- 著者プロフィールの管理(名前・アイコン・紹介文)
- ブログ記事との紐づけ(管理画面のドロップダウンで選択)
- 記事ページへの著者カード表示
- デフォルト著者「arismmn」と「AI」の2種類
データベース設計
著者テーブルの作成
著者情報を管理する専用テーブルを追加しました。
| カラム | 型 | 説明 |
|---|---|---|
| id | INTEGER | 主キー(自動採番) |
| slug | TEXT | 識別子(例: arismmn, ai) |
| name | TEXT | 表示名 |
| icon_url | TEXT | アイコン画像URL |
| bio | TEXT | プロフィール文 |
| created_at | TEXT | 作成日時 |
| updated_at | TEXT | 更新日時 |
記事テーブルへの関連付け
既存の記事テーブルに author_id カラムを追加し、著者テーブルの id を参照します。
ALTER TABLE posts ADD COLUMN author_id INTEGER DEFAULT NULL;
既存レコードは NULL になるため、後方互換性を保ちながら段階的に著者を設定できます。
管理画面の実装
著者管理ページ
/admin/blog/authors/ に著者の一覧・新規作成・編集ページを追加しました。
著者の登録情報は以下の3項目です:
- 表示名 - ブログに表示する名前
- アイコン画像URL -
https://またはhttp://で始まるURL - プロフィール文 - 記事ページに表示する紹介文(500文字以内)
アイコンURLの入力フォームには、リアルタイムのプレビュー機能を追加しています。URLを入力すると即座に表示確認できます。
セキュリティ対応
アイコンURLの入力値には2つのバリデーションを適用しています。
// 🔒 アイコンURLはhttpまたはhttpsのみ許可する
if (iconUrl && !iconUrl.startsWith('https://') && !iconUrl.startsWith('http://')) {
errorMsg = 'アイコンURLはhttps://またはhttp://で始まるURLを入力してください。';
}
javascript: などの危険なスキームを含むURLを拒否することで、クロスサイトスクリプティング(XSS)攻撃を防止します。
著者IDの入力値についても、数値以外の入力を拒否するバリデーションを追加しています。
記事作成・編集フォームへの統合
新規作成フォームと既存記事の編集フォームに、著者を選択するドロップダウンを追加しました。
AIが生成した記事は必ず「AI」著者を選択するよう、フォーム上に注記を表示しています。
公開ブログの実装
著者プロフィールカード
記事ページの本文下部に著者プロフィールカードを表示します。
表示される情報:
- アイコン画像(未設定時は名前の頭文字をグラデーション背景で表示)
- 著者名
- プロフィール文
デザインは既存のブログカードと統一感を持たせつつ、「AI」著者は紫系のグラデーションで区別しています。また、カードには光が流れるシマーアニメーションを追加し、視覚的なアクセントを加えています。
マイグレーション前の互換性
author_id カラムが存在しない状態(マイグレーション実行前)でもページが動作するよう、エラーハンドリングを実装しています。
// author_idカラムなしでリトライする(マイグレーション前の互換性対応)
try {
const row = await db.prepare(
"SELECT slug, title, content FROM posts WHERE slug = ?"
).bind(slug).first();
// ...
} catch { /* フォールバックも失敗した場合は無視 */ }
カラムの追加も、各管理ページのロード時に自動的に試みます。既に追加済みの場合はエラーを無視します。
// blog_postsにauthor_idカラムが存在しない場合は追加する
try {
await db.prepare("ALTER TABLE posts ADD COLUMN author_id INTEGER DEFAULT NULL").run();
} catch { /* カラムが既に存在する場合は何もしない */ }
静的Markdownファイルへの対応
既存のMarkdownファイル記事も著者機能に対応しています。フロントマターに author フィールドを追加することで著者を指定できます。
---
title: "記事タイトル"
date: 2026-03-06 08:00:00
author: arismmn
---
author フィールドに著者テーブルの slug 値を指定すると、記事ページに著者カードが表示されます。
実装時の学び
ALTER TABLEの冪等性
既存テーブルへのカラム追加は ALTER TABLE で行いますが、すでにカラムが存在する場合はエラーになります。本実装では try/catch で囲むことで冪等な処理にしています。
SQLiteでは IF NOT EXISTS 構文が ALTER TABLE に対応していないため、この方法が現実的な選択です。
ページ設計の一貫性
著者管理ページは既存の管理ページと同じ設計パターンに揃えました。
- セッション検証ロジック
- テーブルの自動作成(マイグレーション代替)
- フォームのバリデーション
- POST後のリダイレクトパターン
同じパターンで書くことで、後からコードを読む際の理解コストを下げられます。
まとめ
| 追加・変更したもの | 内容 |
|---|---|
| 著者テーブル | 著者プロフィールを管理するテーブルを追加 |
| 著者管理ページ | 著者の一覧・作成・編集機能を追加 |
| 記事フォーム | 著者選択ドロップダウンを追加 |
| 記事詳細ページ | 著者プロフィールカードを表示 |
| 静的記事スキーマ | author フィールドを追加 |
デフォルト著者として「arismmn」と「AI」の2つを登録しています。AIが自動生成した記事には必ず「AI」著者を選択することで、読者が記事の出自を正確に把握できます。