import { pgTable, text, boolean, timestamp, jsonb, index, uniqueIndex } from 'drizzle-orm/pg-core'; import { ports } from './ports'; // ─── Permission Types ───────────────────────────────────────────────────────── export type RolePermissions = { clients: { view: boolean; create: boolean; edit: boolean; delete: boolean; merge: boolean; export: boolean; }; interests: { view: boolean; create: boolean; edit: boolean; delete: boolean; change_stage: boolean; generate_eoi: boolean; export: boolean; }; berths: { view: boolean; edit: boolean; import: boolean; manage_waiting_list: boolean; }; documents: { view: boolean; create: boolean; edit: boolean; send_for_signing: boolean; upload_signed: boolean; delete: boolean; }; expenses: { view: boolean; create: boolean; edit: boolean; delete: boolean; export: boolean; scan_receipt: boolean; }; invoices: { view: boolean; create: boolean; edit: boolean; delete: boolean; send: boolean; record_payment: boolean; export: boolean; }; files: { view: boolean; upload: boolean; edit: boolean; delete: boolean; manage_folders: boolean; }; email: { view: boolean; send: boolean; configure_account: boolean; }; reminders: { view_own: boolean; view_all: boolean; create: boolean; edit_own: boolean; edit_all: boolean; assign_others: boolean; }; calendar: { connect: boolean; view_events: boolean; }; reports: { view_dashboard: boolean; view_analytics: boolean; export: boolean; }; document_templates: { view: boolean; generate: boolean; manage: boolean; }; yachts: { view: boolean; create: boolean; edit: boolean; delete: boolean; transfer: boolean; }; companies: { view: boolean; create: boolean; edit: boolean; delete: boolean; }; memberships: { view: boolean; manage: boolean; }; reservations: { view: boolean; create: boolean; activate: boolean; cancel: boolean; }; admin: { manage_users: boolean; view_audit_log: boolean; manage_settings: boolean; manage_webhooks: boolean; manage_reports: boolean; manage_custom_fields: boolean; manage_forms: boolean; manage_tags: boolean; system_backup: boolean; }; residential_clients: { view: boolean; create: boolean; edit: boolean; delete: boolean; }; residential_interests: { view: boolean; create: boolean; edit: boolean; delete: boolean; change_stage: boolean; }; }; export type UserPreferences = { dark_mode?: boolean; locale?: string; timezone?: string; [key: string]: unknown; }; // ─── Better Auth Core Tables ───────────────────────────────────────────────── /** * Core user table managed by Better Auth. * Do NOT modify directly - Better Auth handles CRUD via its adapter. */ export const user = pgTable('user', { id: text('id').primaryKey(), name: text('name').notNull(), email: text('email').notNull().unique(), emailVerified: boolean('email_verified').notNull().default(false), image: text('image'), createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(), updatedAt: timestamp('updated_at', { withTimezone: true }).notNull().defaultNow(), }); export const account = pgTable('account', { id: text('id').primaryKey(), accountId: text('account_id').notNull(), providerId: text('provider_id').notNull(), userId: text('user_id') .notNull() .references(() => user.id), accessToken: text('access_token'), refreshToken: text('refresh_token'), idToken: text('id_token'), accessTokenExpiresAt: timestamp('access_token_expires_at', { withTimezone: true }), refreshTokenExpiresAt: timestamp('refresh_token_expires_at', { withTimezone: true }), scope: text('scope'), password: text('password'), createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(), updatedAt: timestamp('updated_at', { withTimezone: true }).notNull().defaultNow(), }); export const verification = pgTable('verification', { id: text('id').primaryKey(), identifier: text('identifier').notNull(), value: text('value').notNull(), expiresAt: timestamp('expires_at', { withTimezone: true }).notNull(), createdAt: timestamp('created_at', { withTimezone: true }), updatedAt: timestamp('updated_at', { withTimezone: true }), }); // ─── CRM Extension Tables ─────────────────────────────────────────────────── /** * Extension table for Better Auth users. * Better Auth manages the core `user` table. * We extend with CRM-specific fields here. */ export const userProfiles = pgTable( 'user_profiles', { id: text('id') .primaryKey() .$defaultFn(() => crypto.randomUUID()), userId: text('user_id').notNull().unique(), // references Better Auth user ID displayName: text('display_name').notNull(), avatarUrl: text('avatar_url'), phone: text('phone'), isSuperAdmin: boolean('is_super_admin').notNull().default(false), isActive: boolean('is_active').notNull().default(true), lastLoginAt: timestamp('last_login_at', { withTimezone: true }), preferences: jsonb('preferences').$type().notNull().default({}), createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(), updatedAt: timestamp('updated_at', { withTimezone: true }).notNull().defaultNow(), }, (table) => [uniqueIndex('user_profiles_user_id_idx').on(table.userId)], ); export const roles = pgTable('roles', { id: text('id') .primaryKey() .$defaultFn(() => crypto.randomUUID()), name: text('name').notNull(), description: text('description'), permissions: jsonb('permissions') .$type() .notNull() .default({} as RolePermissions), isGlobal: boolean('is_global').notNull().default(true), isSystem: boolean('is_system').notNull().default(false), createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(), updatedAt: timestamp('updated_at', { withTimezone: true }).notNull().defaultNow(), }); export const portRoleOverrides = pgTable( 'port_role_overrides', { id: text('id') .primaryKey() .$defaultFn(() => crypto.randomUUID()), portId: text('port_id') .notNull() .references(() => ports.id, { onDelete: 'cascade' }), roleId: text('role_id') .notNull() .references(() => roles.id, { onDelete: 'cascade' }), permissionOverrides: jsonb('permission_overrides') .$type>() .notNull() .default({}), createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(), updatedAt: timestamp('updated_at', { withTimezone: true }).notNull().defaultNow(), }, (table) => [ uniqueIndex('port_role_overrides_port_role_idx').on(table.portId, table.roleId), index('port_role_overrides_port_idx').on(table.portId), ], ); export const userPortRoles = pgTable( 'user_port_roles', { id: text('id') .primaryKey() .$defaultFn(() => crypto.randomUUID()), userId: text('user_id').notNull(), // references Better Auth user ID portId: text('port_id') .notNull() .references(() => ports.id, { onDelete: 'cascade' }), roleId: text('role_id') .notNull() .references(() => roles.id, { onDelete: 'cascade' }), /** * Per-user per-port toggle that grants full residential domain access * (residential_clients.* and residential_interests.*) on top of the * user's primary role. Lets admins flip residential access for sales * staff individually without minting a second role. */ residentialAccess: boolean('residential_access').notNull().default(false), assignedBy: text('assigned_by'), // user ID of who assigned this createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(), }, (table) => [ uniqueIndex('user_port_roles_user_port_role_idx').on(table.userId, table.portId, table.roleId), index('idx_upr_user').on(table.userId), index('idx_upr_port').on(table.portId), ], ); /** * Sessions table - Better Auth compatibility. * Better Auth manages session creation/validation. */ export const session = pgTable( 'session', { id: text('id').primaryKey(), userId: text('user_id').notNull(), token: text('token').notNull().unique(), expiresAt: timestamp('expires_at', { withTimezone: true }).notNull(), ipAddress: text('ip_address'), userAgent: text('user_agent'), createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(), updatedAt: timestamp('updated_at', { withTimezone: true }).notNull().defaultNow(), }, (table) => [ uniqueIndex('sessions_token_idx').on(table.token), index('sessions_user_id_idx').on(table.userId), ], ); export type UserProfile = typeof userProfiles.$inferSelect; export type NewUserProfile = typeof userProfiles.$inferInsert; export type Role = typeof roles.$inferSelect; export type NewRole = typeof roles.$inferInsert; export type PortRoleOverride = typeof portRoleOverrides.$inferSelect; export type NewPortRoleOverride = typeof portRoleOverrides.$inferInsert; export type UserPortRole = typeof userPortRoles.$inferSelect; export type NewUserPortRole = typeof userPortRoles.$inferInsert;