Files
opengroupware/scripts/seed/schedule.ts
Ken Yasue 571b8ae0c4 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.
2026-06-25 14:35:56 +02:00

139 lines
4.8 KiB
TypeScript

/**
* スケジュール系エンティティの生成器(マイルストーン/ミーティング/カレンダー)。
*/
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;
}