スナップショット API
:LiTarget: 用途
全データのスナップショットを定期保存・復元する API。世代管理つき。
:LiSparkle: 特徴
- 定期スナップショット
- 世代管理
- 復元 API
- JSON ダンプ
:LiCode: 実コード(SCALE Base より自動抽出)
:LiInfo:
scale-crm:functions/api/snapshot.tsの中身そのもの。コピペ即可。
// POST /api/snapshot → 手動スナップショット作成
// GET /api/snapshot → 一覧
// GET /api/snapshot?id=X → 特定スナップショット取得
import { corsResponse, corsError, handleOptions } from '../_lib/cors';
interface Env { DB: D1Database }
export const onRequestOptions = () => handleOptions();
export const onRequestPost: PagesFunction<Env> = async ({ request, env }) => {
try {
const body = await request.json().catch(() => ({})) as any;
const reason = body.reason || 'manual';
const user = (() => {
const raw = request.headers.get('X-User') || 'unknown';
try { return decodeURIComponent(raw); } catch { return raw; }
})();
const now = new Date().toISOString();
// 全 app_data をJSON化
const { results } = await env.DB.prepare('SELECT key, value FROM app_data').all();
const payload = JSON.stringify({
created_at: now,
reason,
user,
data: results,
});
const id = 'snap_' + Date.now().toString(36) + '_' + Math.random().toString(36).slice(2, 8);
await env.DB.prepare(
'INSERT INTO snapshots (id, taken_at, taken_by, reason, payload) VALUES (?, ?, ?, ?, ?)'
).bind(id, now, user, reason, payload).run();
// 古いスナップショット削除(最新100個保持)
await env.DB.prepare(
"DELETE FROM snapshots WHERE id NOT IN (SELECT id FROM snapshots ORDER BY taken_at DESC LIMIT 100)"
).run();
return corsResponse({ ok: true, id, taken_at: now, count: results.length });
} catch (e: any) {
return corsError(e.message);
}
};
export const onRequestGet: PagesFunction<Env> = async ({ request, env }) => {
try {
const url = new URL(request.url);
const id = url.searchParams.get('id');
if (id) {
const row = await env.DB.prepare(
'SELECT id, taken_at, taken_by, reason, payload FROM snapshots WHERE id = ?'
).bind(id).first();
if (!row) return corsResponse({ ok: false, error: 'not found' }, 404);
return corsResponse({ ok: true, snapshot: row });
}
const { results } = await env.DB.prepare(
'SELECT id, taken_at, taken_by, reason FROM snapshots ORDER BY taken_at DESC LIMIT 100'
).all();
return corsResponse({ ok: true, count: results.length, snapshots: results });
} catch (e: any) {
return corsError(e.message);
}
};
:LiFolder: ソースファイルのパス
/Users/oogushiyuuki/株式会社SCALE/scale-lead/functions/api/snapshot.ts
:LiHandPointer: 使い方
対象プロジェクトに該当ファイルをコピーして、props を流し込むだけ。
:LiAlertCircle: 注意事項
- 依存パッケージを忘れず追加