【第2回】Wikiテーブルの追加とダッシュボードの改修

こんにちは。前回は、Next.js で開発したパスワード管理アプリに Codex を活用して Wiki 機能を追加した内容をご紹介しました。今回は、Wiki のテーブル構成とその実装コード、ダッシュボードの改修点について詳しくご紹介します。
パスワード管理の記事は以下を参照してください。

【第1回】の記事は以下を参照してください。

目次
SQLiteデータベースのテーブル追加
以下の SQL を使って、既存の password_manager
テーブルに加え、新たに wiki
テーブルを作成しました(Codex によって生成されたコードです)。
import db from '../src/lib/db';
db.exec(`
CREATE TABLE IF NOT EXISTS password_manager (
id INTEGER PRIMARY KEY AUTOINCREMENT,
site_name TEXT NOT NULL,
site_url TEXT NOT NULL,
login_id TEXT,
password TEXT NOT NULL,
email TEXT,
memo TEXT,
category TEXT,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
);
CREATE TABLE IF NOT EXISTS wiki (
id INTEGER PRIMARY KEY AUTOINCREMENT,
title TEXT NOT NULL,
content TEXT NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
`);
console.log('パスワード管理テーブルとwikiテーブルを作成しました');
ダッシュボードの改修
Top ページのダッシュボードには以下のような改修を加えました:
- 最新の Wiki 項目を上部に表示
- 「Wiki 登録」ボタンを追加
/wikis
への一覧リンクを追加
これにより、パスワード情報と Wiki 情報を一元的に確認できるようになり、使いやすさが大きく向上しました。
/src/app/(main)/page.tsx
'use client';
import { useCallback, useEffect, useState } from 'react';
import Link from 'next/link';
import PasswordList from '../components/PasswordList';
import WikiCards from '../components/WikiCards';
import type { Password } from '@/types/password';
import type { Wiki } from '@/types/wiki';
const MainPage = () => {
const [passwords, setPasswords] = useState<Password[]>([]);
const [wikis, setWikis] = useState<Wiki[]>([]);
const [loading, setLoading] = useState(true);
const [errors, setErrors] = useState<{ diaries?: string; wikis?: string; passwords?: string }>({});
const fetchData = useCallback(async <T,>(
url: string,
setter: (data: T) => void,
key: keyof typeof errors
) => {
try {
const response = await fetch(url);
if (!response.ok) throw new Error(`${key}の取得に失敗しました。`);
const data: T = await response.json();
setter(data);
} catch (err) {
console.error(`Error fetching ${key}:`, err);
setErrors((prev) => ({ ...prev, [key]: (err as Error).message }));
}
}, []);
useEffect(() => {
const loadData = async () => {
await Promise.all([
fetchData<Password[]>('/api/passwords', setPasswords, 'passwords'),
fetchData<Wiki[]>('/api/wiki?limit=3', setWikis, 'wikis'),
]);
setLoading(false);
};
loadData();
}, [fetchData]);
if (loading) {
return <div>読み込み中...</div>;
}
return (
<div className="p-3">
<header className="flex justify-between items-center">
<h1 className="text-2xl font-bold">パスワード管理</h1>
<div className="flex space-x-4">
<Link
href="/wikis/new"
className="bg-blue-500 text-white font-bold py-2 px-4 rounded-lg shadow-md hover:bg-blue-600 active:scale-95 transition-transform"
>
Wiki登録
</Link>
<Link
href="/passwords/new"
className="bg-green-500 text-white font-bold py-2 px-4 rounded-lg shadow-md hover:bg-green-600 active:scale-95 transition-transform"
>
パスワード登録
</Link>
</div>
</header>
<section className="my-6">
<h2 className="text-xl font-semibold">最新Wiki</h2>
{errors.wikis ? (
<p className="text-red-500">{errors.wikis}</p>
) : wikis.length > 0 ? (
<WikiCards wikis={wikis} />
) : (
<p className="text-gray-500">登録されたWikiがありません。</p>
)}
<div className="mt-2">
<Link href="/wikis" className="text-blue-600 hover:underline">
一覧を見る
</Link>
</div>
</section>
<section className="my-6">
<h2 className="text-xl font-semibold">パスワード一覧</h2>
{errors.passwords ? (
<p className="text-red-500">{errors.passwords}</p>
) : passwords.length > 0 ? (
<PasswordList passwords={passwords} />
) : (
<p className="text-gray-500">登録されたパスワードがありません。</p>
)}
</section>
</div>
);
};
export default MainPage;
WikiCards コンポーネントの作成
Wiki 記事をカード型で表示するため、Tailwind CSS を用いて視認性の高い UI を構築しました。
/src/app/components/WikiCards.tsx
'use client';
import type { Wiki } from '@/types/wiki';
import Link from 'next/link';
type Props = { wikis: Wiki[] };
const WikiCards: React.FC<Props> = ({ wikis }) => (
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
{wikis.map((wiki) => (
<div key={wiki.id} className="border rounded p-4 bg-white shadow">
<h3 className="font-bold mb-2 truncate">{wiki.title}</h3>
<p className="line-clamp-3 text-sm whitespace-pre-wrap mb-2">{wiki.content}</p>
<Link href={`/wikis/${wiki.id}`} className="text-blue-600 hover:underline">詳細</Link>
</div>
))}
</div>
);
export default WikiCards;
TypeScriptの型定義
Wiki データは以下のように型定義しました。データの整合性を保ちつつ、型推論によって開発効率が向上します。
/src/types/wiki.d.ts
export type Wiki = {
id: number;
title: string;
content: string;
created_at: string;
};
APIエンドポイントの実装
Codex により自動生成された、Wiki API の GET / POST 処理は以下のとおりです。Codex の支援により、迅速かつ安全な実装が可能になりました。
/src/app/api/wiki/route.ts
import { NextResponse } from 'next/server';
import { runSelect, runExecute } from '@/lib/db';
import type { Wiki } from '@/types/wiki';
export async function GET(request: Request) {
const { searchParams } = new URL(request.url);
const limit = searchParams.get('limit');
try {
const sql = limit
? `SELECT * FROM wiki ORDER BY id DESC LIMIT ?`
: 'SELECT * FROM wiki ORDER BY id DESC';
const results = limit
? runSelect<Wiki>(sql, [Number(limit)])
: runSelect<Wiki>(sql);
return NextResponse.json(results);
} catch (error) {
return NextResponse.json({ error: 'DB取得失敗' }, { status: 500 });
}
}
export async function POST(req: Request) {
try {
const body = await req.json();
const { title, content } = body;
if (!title || !content) {
return NextResponse.json({ error: '必須項目不足' }, { status: 400 });
}
runExecute('INSERT INTO wiki (title, content) VALUES (?, ?)', [title, content]);
return NextResponse.json({ message: '登録成功' });
} catch (error) {
return NextResponse.json({ error: '登録失敗' }, { status: 500 });
}
}
次回予告:「Wiki記事の登録・更新機能を実装」
次回は、Wiki 記事の新規登録フォームと既存記事の更新処理の実装について紹介します。今回と同様、ゆるっとしたペースで進めていきますので、ぜひお楽しみに。
まとめ
今回は、Wiki テーブルの追加と Top ページのダッシュボード改修についてご紹介しました。OpenAI Codex の活用により、コード生成の速度と精度が向上し、より多くのリソースをアイデアの実装に割けるようになりました。
今後も Codex を活かした開発事例を共有していきますので、ご期待ください。
0 件のコメント:
コメントを投稿
コメントをお待ちしています。