feat(seed): ~5x richer demo data via deterministic generators
Rewrite scripts/seed-demo.ts as a thin orchestrator over a new scripts/seed/ module (rng, pools, helpers, content, schedule, logs, generators). Scales demo data from 6 users/3 projects to ~30 users/15 projects with ~5-8x per-table content (~6.8x total rows) via a seeded mulberry32 PRNG for full reproducibility (incl. bcrypt salts and file/URL identifiers). FK-safe ordering, real captured IDs for activity logs, enum pools typed against lib/types. Preserves canonical login credentials and projects.
This commit is contained in:
138
scripts/seed/schedule.ts
Normal file
138
scripts/seed/schedule.ts
Normal file
@ -0,0 +1,138 @@
|
||||
/**
|
||||
* スケジュール系エンティティの生成器(マイルストーン/ミーティング/カレンダー)。
|
||||
*/
|
||||
import type { SqliteDatabase } from '@/lib/db/sqlite';
|
||||
import type { MeetingMemberStatus, MilestoneStatus } from '@/lib/types';
|
||||
import { chance, pick, pickN, randInt, seededUuid, type Rng } from './rng';
|
||||
import { CALENDAR_TITLES, CALENDAR_TYPES, MEETING_TITLES } from './pools';
|
||||
import { dayStr, daysFromNow, insert, now } from './helpers';
|
||||
|
||||
const MILESTONE_STATUS_OPEN: MilestoneStatus = 'open';
|
||||
const MEETING_MEMBER_INVITED: MeetingMemberStatus = 'invited';
|
||||
|
||||
export function seedMilestones(
|
||||
db: SqliteDatabase,
|
||||
rng: Rng,
|
||||
projectId: number
|
||||
): number[] {
|
||||
const ids: number[] = [];
|
||||
const phases = ['計画', '設計', '実装', 'テスト', 'リリース', '振り返り'];
|
||||
const count = randInt(rng, 2, 4);
|
||||
for (let i = 0; i < count; i++) {
|
||||
const id = insert(
|
||||
db,
|
||||
`INSERT INTO milestones (project_id, title, description, due_date, status, created_at, updated_at, deleted_at)
|
||||
VALUES (@projectId, @title, @description, @dueDate, @status, @createdAt, @updatedAt, NULL)`,
|
||||
{
|
||||
projectId,
|
||||
title: `M${i + 1}: ${phases[i % phases.length]}完了`,
|
||||
description: 'マイルストーンです。',
|
||||
dueDate: dayStr(randInt(rng, 3, 60)),
|
||||
status: MILESTONE_STATUS_OPEN,
|
||||
createdAt: now(),
|
||||
updatedAt: now(),
|
||||
}
|
||||
);
|
||||
ids.push(id);
|
||||
}
|
||||
return ids;
|
||||
}
|
||||
|
||||
export function seedMeetings(
|
||||
db: SqliteDatabase,
|
||||
rng: Rng,
|
||||
projectId: number,
|
||||
creatorId: number,
|
||||
memberIds: number[]
|
||||
): number[] {
|
||||
const ids: number[] = [];
|
||||
const count = randInt(rng, 1, 2);
|
||||
for (let i = 0; i < count; i++) {
|
||||
const offset = randInt(rng, -5, 30);
|
||||
const id = insert(
|
||||
db,
|
||||
`INSERT INTO meetings (project_id, title, description, location, meeting_url, start_at, end_at, agenda_md, minutes_md, created_by_id, created_at, updated_at, deleted_at)
|
||||
VALUES (@projectId, @title, @description, @location, @meetingUrl, @startAt, @endAt, @agendaMd, @minutesMd, @createdById, @createdAt, @updatedAt, NULL)`,
|
||||
{
|
||||
projectId,
|
||||
title: pick(rng, MEETING_TITLES),
|
||||
description: '進捗確認と課題共有',
|
||||
location: pick(rng, [
|
||||
'会議室A',
|
||||
'会議室B',
|
||||
'オンライン',
|
||||
'会議室A / オンライン',
|
||||
]),
|
||||
meetingUrl: chance(rng, 0.7)
|
||||
? `https://meet.example.com/${seededUuid(rng).slice(0, 8)}`
|
||||
: null,
|
||||
startAt: daysFromNow(offset),
|
||||
endAt: daysFromNow(offset),
|
||||
agendaMd:
|
||||
'# アジェンダ\n\n1. 進捗共有\n2. ブロッカー確認\n3. 次週の計画',
|
||||
minutesMd: chance(rng, 0.6)
|
||||
? '# 議事録\n\n- 進捗は順調\n- 課題を共有\n- 次週も継続'
|
||||
: null,
|
||||
createdById: creatorId,
|
||||
createdAt: now(),
|
||||
updatedAt: now(),
|
||||
}
|
||||
);
|
||||
const attendeeCount = randInt(rng, 2, Math.max(2, memberIds.length));
|
||||
for (const uid of pickN(rng, memberIds, attendeeCount)) {
|
||||
insert(
|
||||
db,
|
||||
`INSERT INTO meeting_members (meeting_id, user_id, status)
|
||||
VALUES (@meetingId, @userId, @status)`,
|
||||
{ meetingId: id, userId: uid, status: MEETING_MEMBER_INVITED }
|
||||
);
|
||||
}
|
||||
ids.push(id);
|
||||
}
|
||||
return ids;
|
||||
}
|
||||
|
||||
export function seedCalendar(
|
||||
db: SqliteDatabase,
|
||||
rng: Rng,
|
||||
projectId: number,
|
||||
creatorId: number,
|
||||
todoIds: number[],
|
||||
milestoneIds: number[],
|
||||
meetingIds: number[]
|
||||
): number[] {
|
||||
const ids: number[] = [];
|
||||
const count = randInt(rng, 3, 5);
|
||||
for (let i = 0; i < count; i++) {
|
||||
const type = pick(rng, CALENDAR_TYPES);
|
||||
const offset = randInt(rng, -7, 45);
|
||||
const id = insert(
|
||||
db,
|
||||
`INSERT INTO calendar_events (project_id, title, description, type, start_at, end_at, created_by_id, related_todo_id, related_milestone_id, related_meeting_id, created_at, updated_at, deleted_at)
|
||||
VALUES (@projectId, @title, @description, @type, @startAt, @endAt, @createdById, @relatedTodoId, @relatedMilestoneId, @relatedMeetingId, @createdAt, @updatedAt, NULL)`,
|
||||
{
|
||||
projectId,
|
||||
title: pick(rng, CALENDAR_TITLES),
|
||||
description: 'カレンダーイベントです。',
|
||||
type,
|
||||
startAt: type === 'deadline' ? dayStr(offset) : daysFromNow(offset),
|
||||
endAt: chance(rng, 0.4) ? daysFromNow(offset) : null,
|
||||
createdById: creatorId,
|
||||
relatedTodoId:
|
||||
chance(rng, 0.2) && todoIds.length > 0 ? pick(rng, todoIds) : null,
|
||||
relatedMilestoneId:
|
||||
chance(rng, 0.15) && milestoneIds.length > 0
|
||||
? pick(rng, milestoneIds)
|
||||
: null,
|
||||
relatedMeetingId:
|
||||
chance(rng, 0.15) && meetingIds.length > 0
|
||||
? pick(rng, meetingIds)
|
||||
: null,
|
||||
createdAt: now(),
|
||||
updatedAt: now(),
|
||||
}
|
||||
);
|
||||
ids.push(id);
|
||||
}
|
||||
return ids;
|
||||
}
|
||||
Reference in New Issue
Block a user