import argon2 from 'argon2'; import { PrismaClient, AccessMode, ToolStatus, ArtifactStatus, DownloadRecordStatus, AdminUserStatus, } from '@prisma/client'; const prisma = new PrismaClient(); type ArtifactSeed = { id: string; version: string; fileName: string; fileSizeBytes: number; sha256: string; mimeType: string; gitlabProjectId: number; gitlabPackageName: string; gitlabPackageVersion: string; gitlabFilePath: string; status: ArtifactStatus; releaseNotes: string; uploadedBy?: string; createdAt: Date; }; type ToolSeed = { id: string; name: string; slug: string; categoryId: string; description: string; rating: number; downloadCount: number; openCount: number; accessMode: AccessMode; openUrl?: string; versionOverride?: string; openInNewTab?: boolean; status: ToolStatus; createdAt: Date; updatedAt: string; features: Array<{ id: string; featureText: string; sortOrder: number; }>; tagIds: string[]; artifacts?: ArtifactSeed[]; latestArtifactId?: string; }; const USER_AGENTS = [ 'Mozilla/5.0 (Macintosh; Intel Mac OS X 14_4)', 'Mozilla/5.0 (Windows NT 10.0; Win64; x64)', 'Mozilla/5.0 (X11; Linux x86_64)', 'ToolsShowDesktop/1.1.0', 'curl/8.7.1', ]; function dateOnly(date: Date): string { return date.toISOString().slice(0, 10); } function daysAgo(days: number, hour = 10, minute = 0): Date { const date = new Date(); date.setHours(hour, minute, 0, 0); date.setDate(date.getDate() - days); return date; } function makeIp(seed: number): string { return `10.24.${Math.floor(seed / 200) + 10}.${(seed % 200) + 20}`; } function buildOpenRecords( toolId: string, dailyCounts: number[], channel: string, referer: string, ) { return dailyCounts.flatMap((count, index) => { const dayOffset = dailyCounts.length - index - 1; return Array.from({ length: count }, (_, slot) => ({ toolId, openedAt: daysAgo(dayOffset, 9 + (slot % 8), (slot * 11) % 60), clientIp: makeIp(index * 40 + slot), userAgent: USER_AGENTS[(index + slot) % USER_AGENTS.length], channel, clientVersion: channel === 'desktop' ? '1.1.0' : 'web', referer, })); }); } function buildDownloadRecords( toolId: string, artifactId: string, dailyCounts: number[], channel: string, clientVersion: string, ) { return dailyCounts.flatMap((count, index) => { const dayOffset = dailyCounts.length - index - 1; return Array.from({ length: count }, (_, slot) => ({ toolId, artifactId, ticket: `ticket_${toolId}_${dayOffset}_${slot}`, downloadedAt: daysAgo(dayOffset, 8 + (slot % 10), (slot * 7) % 60), clientIp: makeIp(index * 60 + slot + 300), userAgent: USER_AGENTS[(index + slot + 1) % USER_AGENTS.length], channel, clientVersion, status: DownloadRecordStatus.success, errorMessage: null, })); }); } async function createToolWithRelations(tool: ToolSeed) { await prisma.tool.create({ data: { id: tool.id, name: tool.name, slug: tool.slug, categoryId: tool.categoryId, description: tool.description, rating: tool.rating, downloadCount: tool.downloadCount, openCount: tool.openCount, accessMode: tool.accessMode, openUrl: tool.openUrl, versionOverride: tool.versionOverride, openInNewTab: tool.openInNewTab ?? true, status: tool.status, createdAt: tool.createdAt, updatedAt: tool.updatedAt, features: { createMany: { data: tool.features, }, }, tags: { create: tool.tagIds.map((tagId) => ({ tagId })), }, }, }); if (tool.artifacts?.length) { await prisma.toolArtifact.createMany({ data: tool.artifacts.map((artifact) => ({ ...artifact, toolId: tool.id, })), }); } if (tool.latestArtifactId) { await prisma.tool.update({ where: { id: tool.id }, data: { latestArtifactId: tool.latestArtifactId, updatedAt: tool.updatedAt, }, }); } } async function main() { await prisma.$transaction([ prisma.toolTag.deleteMany(), prisma.toolFeature.deleteMany(), prisma.downloadRecord.deleteMany(), prisma.downloadTicket.deleteMany(), prisma.openRecord.deleteMany(), prisma.adminAuditLog.deleteMany(), prisma.toolArtifact.deleteMany(), prisma.tool.deleteMany(), prisma.tag.deleteMany(), prisma.category.deleteMany(), prisma.hotKeyword.deleteMany(), prisma.adminUser.deleteMany(), ]); await prisma.category.createMany({ data: [ { id: 'cat_ai', name: 'AI', sortOrder: 10 }, { id: 'cat_dev', name: 'Developer', sortOrder: 20 }, { id: 'cat_ops', name: 'Operations', sortOrder: 30 }, { id: 'cat_design', name: 'Design', sortOrder: 40 }, { id: 'cat_productivity', name: 'Productivity', sortOrder: 50 }, ], }); await prisma.tag.createMany({ data: [ { id: 'tag_hot', name: 'Hot' }, { id: 'tag_free', name: 'Free' }, { id: 'tag_official', name: 'Official' }, { id: 'tag_new', name: 'New' }, { id: 'tag_team', name: 'Team' }, { id: 'tag_self_hosted', name: 'Self-hosted' }, { id: 'tag_enterprise', name: 'Enterprise' }, { id: 'tag_recommended', name: 'Recommended' }, ], }); const passwordHash = await argon2.hash('admin123456', { type: argon2.argon2id, }); await prisma.adminUser.createMany({ data: [ { id: 'u_admin_001', username: 'admin', passwordHash, displayName: 'System Admin', status: AdminUserStatus.active, lastLoginAt: daysAgo(0, 9, 25), }, { id: 'u_admin_002', username: 'ops-admin', passwordHash, displayName: 'Operations Admin', status: AdminUserStatus.active, lastLoginAt: daysAgo(1, 18, 5), }, { id: 'u_admin_003', username: 'auditor', passwordHash, displayName: 'Read Only Auditor', status: AdminUserStatus.disabled, lastLoginAt: daysAgo(11, 13, 10), }, ], }); const tools: ToolSeed[] = [ { id: 'tool_web_001', name: 'OpenAI Playground', slug: 'openai-playground', categoryId: 'cat_ai', description: 'OpenAI web playground for prompt iteration, parameter tuning, and quick eval loops.', rating: 4.9, downloadCount: 0, openCount: 642, accessMode: AccessMode.web, openUrl: 'https://platform.openai.com/playground', versionOverride: '2026.04', openInNewTab: true, status: ToolStatus.published, createdAt: daysAgo(45, 10, 0), updatedAt: dateOnly(daysAgo(0)), features: [ { id: 'feat_web_001', featureText: 'Prompt debugging', sortOrder: 10 }, { id: 'feat_web_002', featureText: 'Model parameter tuning', sortOrder: 20, }, { id: 'feat_web_003', featureText: 'Quick compare workflow', sortOrder: 30, }, ], tagIds: ['tag_hot', 'tag_official', 'tag_recommended'], }, { id: 'tool_web_002', name: 'Claude Artifacts', slug: 'claude-artifacts', categoryId: 'cat_ai', description: 'Web workspace for iterating on prompts, generated UI fragments, and artifact previews.', rating: 4.7, downloadCount: 0, openCount: 438, accessMode: AccessMode.web, openUrl: 'https://claude.ai', versionOverride: '2026.03', openInNewTab: true, status: ToolStatus.published, createdAt: daysAgo(31, 11, 0), updatedAt: dateOnly(daysAgo(1)), features: [ { id: 'feat_web_101', featureText: 'Artifact preview', sortOrder: 10 }, { id: 'feat_web_102', featureText: 'Long context drafting', sortOrder: 20, }, { id: 'feat_web_103', featureText: 'Fast iteration loop', sortOrder: 30, }, ], tagIds: ['tag_hot', 'tag_new'], }, { id: 'tool_web_003', name: 'Figma AI Board', slug: 'figma-ai-board', categoryId: 'cat_design', description: 'Design board for collecting references, generating layout ideas, and collaborating on concepts.', rating: 4.5, downloadCount: 0, openCount: 271, accessMode: AccessMode.web, openUrl: 'https://www.figma.com', openInNewTab: true, status: ToolStatus.published, createdAt: daysAgo(22, 14, 0), updatedAt: dateOnly(daysAgo(2)), features: [ { id: 'feat_web_201', featureText: 'Shared whiteboard', sortOrder: 10 }, { id: 'feat_web_202', featureText: 'Prompt-based explorations', sortOrder: 20, }, { id: 'feat_web_203', featureText: 'Reference clustering', sortOrder: 30, }, ], tagIds: ['tag_official', 'tag_team'], }, { id: 'tool_web_004', name: 'Runbook Atlas', slug: 'runbook-atlas', categoryId: 'cat_ops', description: 'Operational runbook viewer with incident links, deployment guides, and on-call context.', rating: 4.4, downloadCount: 0, openCount: 196, accessMode: AccessMode.web, openUrl: 'https://atlas.example.internal/runbooks', versionOverride: 'atlas-3.2', openInNewTab: false, status: ToolStatus.published, createdAt: daysAgo(19, 9, 0), updatedAt: dateOnly(daysAgo(3)), features: [ { id: 'feat_web_301', featureText: 'Incident handoff docs', sortOrder: 10, }, { id: 'feat_web_302', featureText: 'Change checklist templates', sortOrder: 20, }, { id: 'feat_web_303', featureText: 'Service ownership map', sortOrder: 30, }, ], tagIds: ['tag_team', 'tag_self_hosted'], }, { id: 'tool_web_005', name: 'Notion Workflow Hub', slug: 'notion-workflow-hub', categoryId: 'cat_productivity', description: 'Documentation and request-tracking workspace for internal workflow handoffs.', rating: 4.2, downloadCount: 0, openCount: 153, accessMode: AccessMode.web, openUrl: 'https://www.notion.so', versionOverride: 'workflow-1.8', openInNewTab: true, status: ToolStatus.published, createdAt: daysAgo(12, 13, 30), updatedAt: dateOnly(daysAgo(1)), features: [ { id: 'feat_web_401', featureText: 'Workflow templates', sortOrder: 10, }, { id: 'feat_web_402', featureText: 'Knowledge base linking', sortOrder: 20, }, { id: 'feat_web_403', featureText: 'Review queue boards', sortOrder: 30, }, ], tagIds: ['tag_team', 'tag_recommended'], }, { id: 'tool_dl_001', name: 'ToolsShow Desktop', slug: 'toolsshow-desktop', categoryId: 'cat_dev', description: 'Desktop bundle for local workflows, offline browsing, and curated plugin execution.', rating: 4.8, downloadCount: 182, openCount: 0, accessMode: AccessMode.download, versionOverride: 'stable-1.1', status: ToolStatus.published, createdAt: daysAgo(40, 10, 15), updatedAt: dateOnly(daysAgo(0)), features: [ { id: 'feat_dl_001', featureText: 'Offline usage', sortOrder: 10 }, { id: 'feat_dl_002', featureText: 'Bundled plugins', sortOrder: 20 }, { id: 'feat_dl_003', featureText: 'Local workspace sync', sortOrder: 30, }, ], tagIds: ['tag_hot', 'tag_free', 'tag_recommended'], artifacts: [ { id: 'art_001', version: '1.0.0', fileName: 'toolsshow-desktop-1.0.0.zip', fileSizeBytes: 12345678, sha256: 'sha256-toolsshow-100', mimeType: 'application/zip', gitlabProjectId: 1001, gitlabPackageName: 'toolsshow/toolsshow-desktop', gitlabPackageVersion: '1.0.0', gitlabFilePath: 'storage/toolsshow-desktop-1.0.0.zip', status: ArtifactStatus.deprecated, releaseNotes: 'Initial public release with bundled runtime.', uploadedBy: 'u_admin_001', createdAt: daysAgo(35, 15, 30), }, { id: 'art_002', version: '1.1.0', fileName: 'toolsshow-desktop-1.1.0.zip', fileSizeBytes: 14567890, sha256: 'sha256-toolsshow-110', mimeType: 'application/zip', gitlabProjectId: 1001, gitlabPackageName: 'toolsshow/toolsshow-desktop', gitlabPackageVersion: '1.1.0', gitlabFilePath: 'storage/toolsshow-desktop-1.1.0.zip', status: ArtifactStatus.active, releaseNotes: 'Adds plugin auto-update and workspace pinning.', uploadedBy: 'u_admin_002', createdAt: daysAgo(8, 16, 45), }, ], latestArtifactId: 'art_002', }, { id: 'tool_dl_002', name: 'MCP Inspector', slug: 'mcp-inspector', categoryId: 'cat_dev', description: 'Desktop inspector for local MCP servers, request payloads, and connector metadata.', rating: 4.6, downloadCount: 134, openCount: 0, accessMode: AccessMode.download, openUrl: 'https://github.com/modelcontextprotocol/inspector/releases', status: ToolStatus.published, createdAt: daysAgo(28, 12, 0), updatedAt: dateOnly(daysAgo(2)), features: [ { id: 'feat_dl_101', featureText: 'Connection tracing', sortOrder: 10 }, { id: 'feat_dl_102', featureText: 'Schema inspection', sortOrder: 20 }, { id: 'feat_dl_103', featureText: 'Replay requests', sortOrder: 30 }, ], tagIds: ['tag_new', 'tag_recommended'], artifacts: [ { id: 'art_101', version: '0.9.2', fileName: 'mcp-inspector-0.9.2.dmg', fileSizeBytes: 67452311, sha256: 'sha256-mcp-092', mimeType: 'application/x-apple-diskimage', gitlabProjectId: 1002, gitlabPackageName: 'toolsshow/mcp-inspector', gitlabPackageVersion: '0.9.2', gitlabFilePath: 'storage/mcp-inspector-0.9.2.dmg', status: ArtifactStatus.active, releaseNotes: 'Adds server capability panel and request timeline export.', uploadedBy: 'u_admin_001', createdAt: daysAgo(9, 17, 20), }, ], latestArtifactId: 'art_101', }, { id: 'tool_dl_003', name: 'LogScope Agent', slug: 'logscope-agent', categoryId: 'cat_ops', description: 'Lightweight collector for shipping local logs, traces, and health snapshots to a central view.', rating: 4.3, downloadCount: 89, openCount: 0, accessMode: AccessMode.download, versionOverride: 'stable-2.1', status: ToolStatus.published, createdAt: daysAgo(26, 9, 40), updatedAt: dateOnly(daysAgo(4)), features: [ { id: 'feat_dl_201', featureText: 'Log tail capture', sortOrder: 10 }, { id: 'feat_dl_202', featureText: 'Service heartbeat sync', sortOrder: 20, }, { id: 'feat_dl_203', featureText: 'Remote support bundle', sortOrder: 30, }, ], tagIds: ['tag_enterprise', 'tag_self_hosted'], artifacts: [ { id: 'art_201', version: '2.0.0', fileName: 'logscope-agent-2.0.0.tar.gz', fileSizeBytes: 28765432, sha256: 'sha256-logscope-200', mimeType: 'application/gzip', gitlabProjectId: 1003, gitlabPackageName: 'toolsshow/logscope-agent', gitlabPackageVersion: '2.0.0', gitlabFilePath: 'storage/logscope-agent-2.0.0.tar.gz', status: ArtifactStatus.deleted, releaseNotes: 'Pulled due to startup regression on Linux hosts.', uploadedBy: 'u_admin_002', createdAt: daysAgo(21, 14, 0), }, { id: 'art_202', version: '2.1.0', fileName: 'logscope-agent-2.1.0.tar.gz', fileSizeBytes: 29876543, sha256: 'sha256-logscope-210', mimeType: 'application/gzip', gitlabProjectId: 1003, gitlabPackageName: 'toolsshow/logscope-agent', gitlabPackageVersion: '2.1.0', gitlabFilePath: 'storage/logscope-agent-2.1.0.tar.gz', status: ArtifactStatus.active, releaseNotes: 'Fixes Linux startup regression and improves compression.', uploadedBy: 'u_admin_002', createdAt: daysAgo(6, 11, 45), }, ], latestArtifactId: 'art_202', }, { id: 'tool_none_001', name: 'Prompt Catalog Preview', slug: 'prompt-catalog-preview', categoryId: 'cat_ai', description: 'Preview-only catalog for upcoming internal prompt packs, curation notes, and launch plans.', rating: 4.1, downloadCount: 0, openCount: 0, accessMode: AccessMode.none, versionOverride: 'preview-2026.04', status: ToolStatus.published, createdAt: daysAgo(7, 15, 20), updatedAt: dateOnly(daysAgo(0)), features: [ { id: 'feat_none_001', featureText: 'Upcoming prompt pack roadmap', sortOrder: 10, }, { id: 'feat_none_002', featureText: 'Preview-only release notes', sortOrder: 20, }, { id: 'feat_none_003', featureText: 'Owner and rollout visibility', sortOrder: 30, }, ], tagIds: ['tag_new', 'tag_team'], }, { id: 'tool_draft_001', name: 'Local Agent Kit', slug: 'local-agent-kit', categoryId: 'cat_dev', description: 'Work-in-progress desktop kit for packaging internal prompts, tools, and local runtime configs.', rating: 0, downloadCount: 0, openCount: 0, accessMode: AccessMode.download, status: ToolStatus.draft, createdAt: daysAgo(5, 10, 10), updatedAt: dateOnly(daysAgo(0)), features: [ { id: 'feat_draft_001', featureText: 'Local prompt packs', sortOrder: 10, }, { id: 'feat_draft_002', featureText: 'Workspace policies', sortOrder: 20, }, ], tagIds: ['tag_new'], }, { id: 'tool_archived_001', name: 'Canvas Pilot', slug: 'canvas-pilot', categoryId: 'cat_design', description: 'Legacy internal design helper kept for historical reference and migration support.', rating: 3.9, downloadCount: 14, openCount: 25, accessMode: AccessMode.web, openUrl: 'https://legacy.example.internal/canvas-pilot', openInNewTab: false, status: ToolStatus.archived, createdAt: daysAgo(90, 9, 0), updatedAt: dateOnly(daysAgo(14)), features: [ { id: 'feat_arc_001', featureText: 'Legacy board import', sortOrder: 10, }, { id: 'feat_arc_002', featureText: 'Snapshot export', sortOrder: 20 }, ], tagIds: ['tag_team'], }, ]; for (const tool of tools) { await createToolWithRelations(tool); } await prisma.hotKeyword.createMany({ data: [ { id: 'kw_001', keyword: 'agent', sortOrder: 10, isActive: true }, { id: 'kw_002', keyword: 'automation', sortOrder: 20, isActive: true }, { id: 'kw_003', keyword: 'mcp', sortOrder: 30, isActive: true }, { id: 'kw_004', keyword: 'design', sortOrder: 40, isActive: true }, { id: 'kw_005', keyword: 'workflow', sortOrder: 50, isActive: true }, { id: 'kw_006', keyword: 'self-hosted', sortOrder: 60, isActive: false }, ], }); await prisma.openRecord.createMany({ data: [ ...buildOpenRecords( 'tool_web_001', [14, 17, 19, 21, 24, 26, 28], 'web', '/', ), ...buildOpenRecords( 'tool_web_002', [8, 10, 12, 14, 16, 18, 20], 'web', '/tools', ), ...buildOpenRecords( 'tool_web_003', [4, 6, 7, 8, 8, 9, 10], 'web', '/design', ), ...buildOpenRecords('tool_web_004', [3, 4, 5, 6, 5, 7, 6], 'web', '/ops'), ...buildOpenRecords( 'tool_web_005', [2, 3, 3, 4, 4, 5, 6], 'web', '/workspace', ), ...buildOpenRecords( 'tool_dl_002', [1, 1, 2, 2, 2, 3, 3], 'desktop', '/admin/tools', ), ], }); await prisma.downloadRecord.createMany({ data: [ ...buildDownloadRecords( 'tool_dl_001', 'art_002', [3, 4, 5, 6, 8, 9, 10], 'desktop', '1.1.0', ), ...buildDownloadRecords( 'tool_dl_002', 'art_101', [2, 2, 3, 4, 4, 5, 6], 'cli', '0.9.2', ), ...buildDownloadRecords( 'tool_dl_003', 'art_202', [1, 2, 2, 3, 3, 4, 5], 'agent', '2.1.0', ), { toolId: 'tool_dl_003', artifactId: 'art_202', ticket: 'ticket_failed_001', downloadedAt: daysAgo(2, 23, 15), clientIp: '10.24.99.40', userAgent: 'curl/8.7.1', channel: 'agent', clientVersion: '2.1.0', status: DownloadRecordStatus.failed, errorMessage: 'checksum mismatch', }, { toolId: 'tool_dl_001', artifactId: 'art_002', ticket: 'ticket_failed_002', downloadedAt: daysAgo(5, 7, 50), clientIp: '10.24.99.41', userAgent: 'ToolsShowDesktop/1.1.0', channel: 'desktop', clientVersion: '1.1.0', status: DownloadRecordStatus.failed, errorMessage: 'network timeout', }, ], }); await prisma.downloadTicket.createMany({ data: [ { ticket: 'seed_ticket_001', toolId: 'tool_dl_001', artifactId: 'art_002', channel: 'desktop', clientVersion: '1.1.0', requestIp: '10.24.88.11', expiresAt: daysAgo(-1, 12, 0), consumedAt: null, createdAt: daysAgo(0, 11, 45), }, { ticket: 'seed_ticket_002', toolId: 'tool_dl_002', artifactId: 'art_101', channel: 'cli', clientVersion: '0.9.2', requestIp: '10.24.88.12', expiresAt: daysAgo(0, 13, 0), consumedAt: daysAgo(0, 12, 10), createdAt: daysAgo(0, 12, 0), }, { ticket: 'seed_ticket_003', toolId: 'tool_dl_003', artifactId: 'art_202', channel: 'agent', clientVersion: '2.1.0', requestIp: '10.24.88.13', expiresAt: daysAgo(1, 14, 0), consumedAt: null, createdAt: daysAgo(1, 13, 20), }, { ticket: 'seed_ticket_004', toolId: 'tool_dl_001', artifactId: 'art_002', channel: 'desktop', clientVersion: '1.1.0', requestIp: '10.24.88.14', expiresAt: daysAgo(3, 10, 0), consumedAt: daysAgo(3, 9, 12), createdAt: daysAgo(3, 9, 0), }, { ticket: 'seed_ticket_005', toolId: 'tool_dl_003', artifactId: 'art_202', channel: 'agent', clientVersion: '2.1.0', requestIp: '10.24.88.15', expiresAt: daysAgo(6, 17, 0), consumedAt: null, createdAt: daysAgo(6, 16, 30), }, ], }); await prisma.adminAuditLog.createMany({ data: [ { adminUserId: 'u_admin_001', action: 'LOGIN', resourceType: 'auth', resourceId: 'u_admin_001', requestMethod: 'POST', requestPath: '/admin/auth/login', requestBody: JSON.stringify({ username: 'admin' }), ip: '10.24.77.10', userAgent: USER_AGENTS[0], createdAt: daysAgo(0, 9, 25), }, { adminUserId: 'u_admin_001', action: 'CREATE_TOOL', resourceType: 'tool', resourceId: 'tool_draft_001', requestMethod: 'POST', requestPath: '/admin/tools', requestBody: JSON.stringify({ name: 'Local Agent Kit', accessMode: 'download', }), ip: '10.24.77.10', userAgent: USER_AGENTS[0], createdAt: daysAgo(0, 10, 5), }, { adminUserId: 'u_admin_002', action: 'UPLOAD_ARTIFACT', resourceType: 'artifact', resourceId: 'art_202', requestMethod: 'POST', requestPath: '/admin/tools/tool_dl_003/artifacts', requestBody: JSON.stringify({ version: '2.1.0' }), ip: '10.24.77.21', userAgent: USER_AGENTS[1], createdAt: daysAgo(1, 14, 12), }, { adminUserId: 'u_admin_002', action: 'SET_LATEST_ARTIFACT', resourceType: 'tool', resourceId: 'tool_dl_003', requestMethod: 'PATCH', requestPath: '/admin/tools/tool_dl_003/artifacts/art_202/latest', requestBody: null, ip: '10.24.77.21', userAgent: USER_AGENTS[1], createdAt: daysAgo(1, 14, 18), }, { adminUserId: 'u_admin_001', action: 'UPDATE_TOOL_STATUS', resourceType: 'tool', resourceId: 'tool_dl_001', requestMethod: 'PATCH', requestPath: '/admin/tools/tool_dl_001/status', requestBody: JSON.stringify({ status: 'published' }), ip: '10.24.77.10', userAgent: USER_AGENTS[0], createdAt: daysAgo(2, 11, 40), }, { adminUserId: 'u_admin_002', action: 'UPDATE_TOOL', resourceType: 'tool', resourceId: 'tool_web_004', requestMethod: 'PUT', requestPath: '/admin/tools/tool_web_004', requestBody: JSON.stringify({ openInNewTab: false }), ip: '10.24.77.21', userAgent: USER_AGENTS[2], createdAt: daysAgo(2, 17, 5), }, { adminUserId: 'u_admin_001', action: 'CREATE_CATEGORY', resourceType: 'category', resourceId: 'cat_productivity', requestMethod: 'POST', requestPath: '/admin/categories', requestBody: JSON.stringify({ name: 'Productivity' }), ip: '10.24.77.10', userAgent: USER_AGENTS[0], createdAt: daysAgo(3, 9, 45), }, { adminUserId: 'u_admin_001', action: 'CREATE_TAG', resourceType: 'tag', resourceId: 'tag_recommended', requestMethod: 'POST', requestPath: '/admin/tags', requestBody: JSON.stringify({ name: 'Recommended' }), ip: '10.24.77.10', userAgent: USER_AGENTS[0], createdAt: daysAgo(3, 10, 10), }, { adminUserId: 'u_admin_002', action: 'UPDATE_ARTIFACT_STATUS', resourceType: 'artifact', resourceId: 'art_001', requestMethod: 'PATCH', requestPath: '/admin/tools/tool_dl_001/artifacts/art_001/status', requestBody: JSON.stringify({ status: 'deprecated' }), ip: '10.24.77.21', userAgent: USER_AGENTS[3], createdAt: daysAgo(4, 15, 30), }, { adminUserId: 'u_admin_002', action: 'LOGIN', resourceType: 'auth', resourceId: 'u_admin_002', requestMethod: 'POST', requestPath: '/admin/auth/login', requestBody: JSON.stringify({ username: 'ops-admin' }), ip: '10.24.77.21', userAgent: USER_AGENTS[1], createdAt: daysAgo(5, 8, 55), }, { adminUserId: 'u_admin_001', action: 'DELETE_ARTIFACT', resourceType: 'artifact', resourceId: 'art_201', requestMethod: 'DELETE', requestPath: '/admin/tools/tool_dl_003/artifacts/art_201', requestBody: null, ip: '10.24.77.10', userAgent: USER_AGENTS[0], createdAt: daysAgo(6, 16, 40), }, { adminUserId: null, action: 'SYSTEM_BOOTSTRAP', resourceType: 'system', resourceId: null, requestMethod: 'POST', requestPath: '/system/bootstrap', requestBody: JSON.stringify({ source: 'seed' }), ip: '127.0.0.1', userAgent: 'seed-script', createdAt: daysAgo(6, 7, 0), }, ], }); } main() .catch(async (error) => { console.error(error); process.exitCode = 1; }) .finally(async () => { await prisma.$disconnect(); });