168 lines
5.0 KiB
TypeScript
168 lines
5.0 KiB
TypeScript
|
|
/**
|
||
|
|
* 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;
|
||
|
|
}
|