feat(seed): synthetic fixture covering every pipeline stage + db:reset
Splits seed bootstrap (ports/roles/profile) into a shared module so
two seed entry points can share it:
- pnpm db:seed realistic NocoDB-shaped fixture (existing)
- pnpm db:seed:synthetic 12 clients, one per pipeline stage + archive
variants (rich metadata for restore wizard)
scripts/db-reset.ts truncates all data tables (preserves migrations);
guarded by --confirm and a localhost host check. Companion npm scripts:
- pnpm db:reset
- pnpm db:reseed:realistic
- pnpm db:reseed:synthetic
scripts/dev-open-browser.ts launches a headed Chromium with no viewport
override (uses the host monitor's natural size), pre-fills the login
form for the requested role.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
167
src/lib/db/seed-bootstrap.ts
Normal file
167
src/lib/db/seed-bootstrap.ts
Normal file
@@ -0,0 +1,167 @@
|
||||
/**
|
||||
* Shared seed bootstrap: ports + system roles + super admin profile.
|
||||
*
|
||||
* Both the realistic seed (`seed.ts`) and the synthetic seed
|
||||
* (`seed-synthetic.ts`) call into this so we don't drift on the
|
||||
* permission maps or the operator account ids.
|
||||
*
|
||||
* Idempotent. Returns the resolved port ids so callers can chain
|
||||
* per-port fixture builders.
|
||||
*/
|
||||
|
||||
import { eq } from 'drizzle-orm';
|
||||
import { db } from './index';
|
||||
import { ports } from './schema/ports';
|
||||
import { roles, userProfiles } from './schema/users';
|
||||
import {
|
||||
ALL_PERMISSIONS,
|
||||
DIRECTOR_PERMISSIONS,
|
||||
SALES_MANAGER_PERMISSIONS,
|
||||
SALES_AGENT_PERMISSIONS,
|
||||
VIEWER_PERMISSIONS,
|
||||
RESIDENTIAL_PARTNER_PERMISSIONS,
|
||||
} from './seed-permissions';
|
||||
|
||||
export interface BootstrappedPort {
|
||||
id: string;
|
||||
name: string;
|
||||
slug: string;
|
||||
}
|
||||
|
||||
export const PORT_DEFINITIONS: Array<{
|
||||
name: string;
|
||||
slug: string;
|
||||
primaryColor: string;
|
||||
defaultCurrency: string;
|
||||
timezone: string;
|
||||
}> = [
|
||||
{
|
||||
name: 'Port Nimara',
|
||||
slug: 'port-nimara',
|
||||
primaryColor: '#0F4C81',
|
||||
defaultCurrency: 'USD',
|
||||
timezone: 'America/Anguilla',
|
||||
},
|
||||
{
|
||||
name: 'Port Amador',
|
||||
slug: 'port-amador',
|
||||
primaryColor: '#D97706',
|
||||
defaultCurrency: 'USD',
|
||||
timezone: 'America/Panama',
|
||||
},
|
||||
];
|
||||
|
||||
export const SUPER_ADMIN_USER_ID = 'super-admin-matt-portnimara';
|
||||
|
||||
export async function seedBootstrap(): Promise<BootstrappedPort[]> {
|
||||
console.log('Bootstrap: ports + roles + super admin profile');
|
||||
|
||||
// ── Ports ──────────────────────────────────────────────────────────────────
|
||||
const portIds: BootstrappedPort[] = [];
|
||||
for (const def of PORT_DEFINITIONS) {
|
||||
const [inserted] = await db
|
||||
.insert(ports)
|
||||
.values({
|
||||
id: crypto.randomUUID(),
|
||||
name: def.name,
|
||||
slug: def.slug,
|
||||
logoUrl: null,
|
||||
primaryColor: def.primaryColor,
|
||||
defaultCurrency: def.defaultCurrency,
|
||||
timezone: def.timezone,
|
||||
settings: {},
|
||||
isActive: true,
|
||||
})
|
||||
.onConflictDoNothing()
|
||||
.returning();
|
||||
|
||||
if (inserted) {
|
||||
console.log(` Port created: ${def.name} (${inserted.id})`);
|
||||
portIds.push({ id: inserted.id, name: def.name, slug: def.slug });
|
||||
} else {
|
||||
const [existing] = await db.select().from(ports).where(eq(ports.slug, def.slug)).limit(1);
|
||||
if (existing) {
|
||||
console.log(` Port exists: ${def.name} (${existing.id})`);
|
||||
portIds.push({ id: existing.id, name: def.name, slug: def.slug });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ── System roles ──────────────────────────────────────────────────────────
|
||||
const systemRoles = [
|
||||
{
|
||||
id: crypto.randomUUID(),
|
||||
name: 'super_admin',
|
||||
description: 'Full system access. Bypasses all permission checks.',
|
||||
permissions: ALL_PERMISSIONS,
|
||||
isGlobal: true,
|
||||
isSystem: true,
|
||||
},
|
||||
{
|
||||
id: crypto.randomUUID(),
|
||||
name: 'director',
|
||||
description: 'Operational admin within assigned port(s). Can manage users and settings.',
|
||||
permissions: DIRECTOR_PERMISSIONS,
|
||||
isGlobal: true,
|
||||
isSystem: true,
|
||||
},
|
||||
{
|
||||
id: crypto.randomUUID(),
|
||||
name: 'sales_manager',
|
||||
description: 'Full sales access. Can view all reminders, assign tasks, and export reports.',
|
||||
permissions: SALES_MANAGER_PERMISSIONS,
|
||||
isGlobal: true,
|
||||
isSystem: true,
|
||||
},
|
||||
{
|
||||
id: crypto.randomUUID(),
|
||||
name: 'sales_agent',
|
||||
description:
|
||||
'Standard sales role. View/create/edit clients and interests, manage own reminders.',
|
||||
permissions: SALES_AGENT_PERMISSIONS,
|
||||
isGlobal: true,
|
||||
isSystem: true,
|
||||
},
|
||||
{
|
||||
id: crypto.randomUUID(),
|
||||
name: 'viewer',
|
||||
description: 'Read-only access to all records.',
|
||||
permissions: VIEWER_PERMISSIONS,
|
||||
isGlobal: true,
|
||||
isSystem: true,
|
||||
},
|
||||
{
|
||||
id: crypto.randomUUID(),
|
||||
name: 'residential_partner',
|
||||
description:
|
||||
'External partner who handles residential inquiries. Sees only the residential pages — no marina clients, yachts, berths, or financial data.',
|
||||
permissions: RESIDENTIAL_PARTNER_PERMISSIONS,
|
||||
isGlobal: true,
|
||||
isSystem: true,
|
||||
},
|
||||
];
|
||||
|
||||
for (const role of systemRoles) {
|
||||
await db.insert(roles).values(role).onConflictDoNothing();
|
||||
}
|
||||
console.log(` Roles ensured: ${systemRoles.map((r) => r.name).join(', ')}`);
|
||||
|
||||
// ── Super admin profile placeholder ───────────────────────────────────────
|
||||
await db
|
||||
.insert(userProfiles)
|
||||
.values({
|
||||
id: crypto.randomUUID(),
|
||||
userId: SUPER_ADMIN_USER_ID,
|
||||
displayName: 'Matt',
|
||||
avatarUrl: null,
|
||||
phone: null,
|
||||
isSuperAdmin: true,
|
||||
isActive: true,
|
||||
lastLoginAt: null,
|
||||
preferences: {},
|
||||
})
|
||||
.onConflictDoNothing();
|
||||
console.log(` Super admin profile ensured (user_id: ${SUPER_ADMIN_USER_ID})`);
|
||||
|
||||
return portIds;
|
||||
}
|
||||
Reference in New Issue
Block a user