2026-04-24 12:30:06 +02:00
|
|
|
import { pgTable, text, boolean, timestamp, jsonb, index, uniqueIndex } from 'drizzle-orm/pg-core';
|
Initial commit: Port Nimara CRM (Layers 0-4)
Full CRM rebuild with Next.js 15, TypeScript, Tailwind, Drizzle ORM,
PostgreSQL, Redis, BullMQ, MinIO, and Socket.io. Includes 461 source
files covering clients, berths, interests/pipeline, documents/EOI,
expenses/invoices, email, notifications, dashboard, admin, and
client portal. CI/CD via Gitea Actions with Docker builds.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 11:52:51 +01:00
|
|
|
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;
|
feat(interests): manual stage override + Residential Partner system role
Manual stage override
Sales reps need to skip canTransitionStage rules when the data was
entered out of order — e.g. recording a contract_signed deal whose
earlier stages were never tracked in the system.
- New permission flag interests.override_stage in RolePermissions.
Plumbed through the schema TS type, the role-editor UI, the seed
file's pre-built roles (super_admin/director/sales_manager get it,
sales_agent + viewer don't), and the test factories.
- changeStageSchema gains an optional `override` boolean and the
service checks it before evaluating canTransitionStage. When
override=true the reason field becomes required (min 5 chars) and
is recorded in the audit log.
- The route handler gates `override` on the new permission so a
sales_agent without it can't pass override=true and bypass.
- InterestStagePicker auto-detects when the requested transition is
blocked by the table and switches into "override mode" — shows an
amber warning, requires the reason, button label flips to
"Override stage". When the operator lacks the permission, the
warning is red and the button is disabled.
Residential Partner role
Per the smart-archive scoping conversation: external partners who
handle residential inquiries shouldn't see marina clients, yachts,
berths, or financials. The two residential_* permission groups
already exist; this commit just seeds a pre-built system role
("residential_partner") with those flags + minimal own-reminders, so
admins can invite a partner today via /admin/users without manually
building the permission set.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-06 18:32:57 +02:00
|
|
|
/** Bypass the canTransitionStage table (e.g. mark a contract_signed
|
|
|
|
|
* deal as completed without going through deposit_10pct first when
|
|
|
|
|
* the data was entered out of order). Audit-logged with the reason
|
|
|
|
|
* the rep gives. Sales-team-restricted. */
|
|
|
|
|
override_stage: boolean;
|
Initial commit: Port Nimara CRM (Layers 0-4)
Full CRM rebuild with Next.js 15, TypeScript, Tailwind, Drizzle ORM,
PostgreSQL, Redis, BullMQ, MinIO, and Socket.io. Includes 461 source
files covering clients, berths, interests/pipeline, documents/EOI,
expenses/invoices, email, notifications, dashboard, admin, and
client portal. CI/CD via Gitea Actions with Docker builds.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 11:52:51 +01:00
|
|
|
generate_eoi: boolean;
|
|
|
|
|
export: boolean;
|
|
|
|
|
};
|
|
|
|
|
berths: {
|
|
|
|
|
view: boolean;
|
|
|
|
|
edit: boolean;
|
|
|
|
|
import: boolean;
|
|
|
|
|
manage_waiting_list: boolean;
|
|
|
|
|
};
|
|
|
|
|
documents: {
|
|
|
|
|
view: boolean;
|
|
|
|
|
create: boolean;
|
2026-05-05 18:33:13 +02:00
|
|
|
edit: boolean;
|
Initial commit: Port Nimara CRM (Layers 0-4)
Full CRM rebuild with Next.js 15, TypeScript, Tailwind, Drizzle ORM,
PostgreSQL, Redis, BullMQ, MinIO, and Socket.io. Includes 461 source
files covering clients, berths, interests/pipeline, documents/EOI,
expenses/invoices, email, notifications, dashboard, admin, and
client portal. CI/CD via Gitea Actions with Docker builds.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 11:52:51 +01:00
|
|
|
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;
|
2026-05-05 18:33:13 +02:00
|
|
|
edit: boolean;
|
Initial commit: Port Nimara CRM (Layers 0-4)
Full CRM rebuild with Next.js 15, TypeScript, Tailwind, Drizzle ORM,
PostgreSQL, Redis, BullMQ, MinIO, and Socket.io. Includes 461 source
files covering clients, berths, interests/pipeline, documents/EOI,
expenses/invoices, email, notifications, dashboard, admin, and
client portal. CI/CD via Gitea Actions with Docker builds.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 11:52:51 +01:00
|
|
|
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;
|
|
|
|
|
};
|
2026-04-24 12:30:06 +02:00
|
|
|
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;
|
|
|
|
|
};
|
Initial commit: Port Nimara CRM (Layers 0-4)
Full CRM rebuild with Next.js 15, TypeScript, Tailwind, Drizzle ORM,
PostgreSQL, Redis, BullMQ, MinIO, and Socket.io. Includes 461 source
files covering clients, berths, interests/pipeline, documents/EOI,
expenses/invoices, email, notifications, dashboard, admin, and
client portal. CI/CD via Gitea Actions with Docker builds.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 11:52:51 +01:00
|
|
|
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;
|
feat(clients): hard-delete with email-code confirmation (single + bulk)
Permanent client deletion is now reachable from:
- archived single-client detail page (icon button, gated by new
admin.permanently_delete_clients perm)
- archived clients list bulk action
Both flows are 2-stage: request a 4-digit code (sent to operator's
account email, 10min Redis TTL), then enter both code AND a typed
confirmation (client name single, "DELETE N CLIENTS" bulk). Cascade
strategy preserves audit trails: signed documents, email threads,
files and reminders are detached but retained; addresses, contacts,
notes, portal user, GDPR records, interests and reservations are
deleted via FK cascade or explicit tx delete.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-06 19:26:42 +02:00
|
|
|
// Permanent client deletion is gated separately from admin.manage_users
|
|
|
|
|
// because it bypasses archive/restore. Requires email-code confirmation.
|
|
|
|
|
permanently_delete_clients: boolean;
|
Initial commit: Port Nimara CRM (Layers 0-4)
Full CRM rebuild with Next.js 15, TypeScript, Tailwind, Drizzle ORM,
PostgreSQL, Redis, BullMQ, MinIO, and Socket.io. Includes 461 source
files covering clients, berths, interests/pipeline, documents/EOI,
expenses/invoices, email, notifications, dashboard, admin, and
client portal. CI/CD via Gitea Actions with Docker builds.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 11:52:51 +01:00
|
|
|
};
|
feat(platform): residential module + admin UI + reliability fixes
Residential platform
- New schema: residentialClients, residentialInterests (separate from
marina/yacht clients) with migration 0010
- Service layer with CRUD + audit + sockets + per-port portal toggle
- v1 + public API routes (/api/v1/residential/*, /api/public/residential-inquiries)
- List + detail pages with inline editing for clients and interests
- Per-user residentialAccess toggle on userPortRoles (migration 0011)
- Permission keys: residential_clients, residential_interests
- Sidebar nav + role form integration
- Smoke spec covering page loads, UI create flow, public endpoint
Admin & shared UI
- Admin → Forms (form templates CRUD) with validators + service
- Notification preferences page (in-app + email per type)
- Email composition + accounts list + threads view
- Branded auth shell shared across CRM + portal auth surfaces
- Inline editing extended to yacht/company/interest detail pages
- InlineTagEditor + per-entity tags endpoints (yachts, companies)
- Notes service polymorphic across clients/interests/yachts/companies
- Client list columns: yachtCount + companyCount badges
- Reservation file-download via presigned URL (replaces stale <a href>)
Route handler refactor
- Extracted yachts/companies/berths reservation handlers to sibling
handlers.ts files (Next.js 15 route.ts only allows specific exports)
Reliability fixes
- apiFetch double-stringify bug fixed across 13 components
(apiFetch already JSON.stringifies its body; passing a stringified
body produced double-encoded JSON which failed zod validation)
- SocketProvider gated behind useSyncExternalStore-based mount check
to avoid useSession() SSR crashes under React 19 + Next 15
- apiFetch falls back to URL-pathname → port-id resolution when the
Zustand store hasn't hydrated yet (fresh contexts, e2e tests)
- CRM invite flow (schema, service, route, email, dev script)
- Dashboard route → [portSlug]/dashboard/page.tsx + redirect
- Document the dev-server restart-after-migration gotcha in CLAUDE.md
Tests
- 5-case residential smoke spec
- Integration test updates for new service signatures
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-27 21:54:32 +02:00
|
|
|
residential_clients: {
|
|
|
|
|
view: boolean;
|
|
|
|
|
create: boolean;
|
|
|
|
|
edit: boolean;
|
|
|
|
|
delete: boolean;
|
|
|
|
|
};
|
|
|
|
|
residential_interests: {
|
|
|
|
|
view: boolean;
|
|
|
|
|
create: boolean;
|
|
|
|
|
edit: boolean;
|
|
|
|
|
delete: boolean;
|
|
|
|
|
change_stage: boolean;
|
|
|
|
|
};
|
Initial commit: Port Nimara CRM (Layers 0-4)
Full CRM rebuild with Next.js 15, TypeScript, Tailwind, Drizzle ORM,
PostgreSQL, Redis, BullMQ, MinIO, and Socket.io. Includes 461 source
files covering clients, berths, interests/pipeline, documents/EOI,
expenses/invoices, email, notifications, dashboard, admin, and
client portal. CI/CD via Gitea Actions with Docker builds.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 11:52:51 +01:00
|
|
|
};
|
|
|
|
|
|
feat(interests): EOI/contract/reservation tabs + contact log + berth interest milestone + interest list overhaul
Major interest workflow expansion driven by the rapid-fire UX session.
EOI / Contract / Reservation tabs replace the generic Documents tab when
the deal is at the relevant stage — workspace pattern with active-doc
hero, signing progress, paper-signed upload, and history strip. Stage-
conditional visibility wired through interest-tabs.tsx so the tab set
shrinks/expands as the deal moves through the pipeline.
Contact log: per-interaction structured log (channel/direction/summary/
optional follow-up reminder). New `interest_contact_log` table + service
+ tab UI (timeline with channel-coded icons + compose dialog).
auto-creates a reminder when followUpAt is set.
Berth Interest milestone: first milestone in the OverviewTab's pipeline
strip, completes the moment any berth is linked via the junction. Drives
the "have we captured what they want?" sanity check for general_interest
leads before they move to EOI.
Stage-conditional milestones: past phases collapse into a one-liner
strip, current phase expands, future phases hide behind a "Show
upcoming" toggle. Inline stage picker now defers reason capture to an
override-confirm view (only required for illegal transitions, not the
default flow).
Notes blob → threaded: dropped `interests.notes` column entirely; the
threaded `interest_notes` table is the single source of truth. Latest-
note teaser on Overview links into the dedicated Notes tab. Polymorphic
notes service gains aggregated client view (unions client + interest +
yacht notes with source chips and group-by-source toggle).
Berth interest list overhaul:
- Configurable columns via ColumnPicker (18 toggleable, 5 default-on)
- Natural-sort SQL ORDER BY on mooring number (A1, A2, A10 not A10, A2)
- Per-letter row tinting via colored left-border accent + dot in cell
- Documents tab merged Files (single attachments section)
Topbar improvements:
- Always-visible back arrow on detail pages (path depth > 2)
- Breadcrumb-hint store + useBreadcrumbHint hook so detail pages can
push their entity hierarchy (Clients › Mary Smith › Interest › B17)
- Tighter spacing, softer separators, 160px crumb truncation
DataTable upgrades:
- Page-size selector with All option (validator cap raised to 1000)
- getRowClassName slot for per-row styling (used by berth tinting)
- Fixed Radix SelectItem crash on empty-string values via __any__
sentinel (was crashing every list page that opened a select filter)
Interest list:
- Configurable columns picker
- Stage cell clickable into detail
- TagPicker + SavedViewsDropdown sized h-8 to match adjacent buttons
- Save view moved into ColumnPicker menu; Views button hidden when
no views are saved
- Pipeline kanban board endpoint at /api/v1/interests/board with
minimal projection, 5000-row cap + truncated banner, filter
pass-through
Mobile chrome + sidebar collapse removed (always-expanded design choice).
User management lists super-admins (was inner-joined on user_port_roles
which excluded global super-admins).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-07 20:59:28 +02:00
|
|
|
/**
|
|
|
|
|
* Per-table column visibility — drives the `<ColumnPicker>` and the
|
|
|
|
|
* DataTable `columnVisibility` state. `hiddenColumns` is the source of
|
|
|
|
|
* truth; an entry's absence means "show this column" (so newly-added
|
|
|
|
|
* columns show by default for existing users without us having to
|
|
|
|
|
* migrate stored preferences).
|
|
|
|
|
*/
|
|
|
|
|
export type TablePreferences = {
|
|
|
|
|
hiddenColumns?: string[];
|
|
|
|
|
};
|
|
|
|
|
|
Initial commit: Port Nimara CRM (Layers 0-4)
Full CRM rebuild with Next.js 15, TypeScript, Tailwind, Drizzle ORM,
PostgreSQL, Redis, BullMQ, MinIO, and Socket.io. Includes 461 source
files covering clients, berths, interests/pipeline, documents/EOI,
expenses/invoices, email, notifications, dashboard, admin, and
client portal. CI/CD via Gitea Actions with Docker builds.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 11:52:51 +01:00
|
|
|
export type UserPreferences = {
|
|
|
|
|
dark_mode?: boolean;
|
|
|
|
|
locale?: string;
|
|
|
|
|
timezone?: string;
|
feat(interests): EOI/contract/reservation tabs + contact log + berth interest milestone + interest list overhaul
Major interest workflow expansion driven by the rapid-fire UX session.
EOI / Contract / Reservation tabs replace the generic Documents tab when
the deal is at the relevant stage — workspace pattern with active-doc
hero, signing progress, paper-signed upload, and history strip. Stage-
conditional visibility wired through interest-tabs.tsx so the tab set
shrinks/expands as the deal moves through the pipeline.
Contact log: per-interaction structured log (channel/direction/summary/
optional follow-up reminder). New `interest_contact_log` table + service
+ tab UI (timeline with channel-coded icons + compose dialog).
auto-creates a reminder when followUpAt is set.
Berth Interest milestone: first milestone in the OverviewTab's pipeline
strip, completes the moment any berth is linked via the junction. Drives
the "have we captured what they want?" sanity check for general_interest
leads before they move to EOI.
Stage-conditional milestones: past phases collapse into a one-liner
strip, current phase expands, future phases hide behind a "Show
upcoming" toggle. Inline stage picker now defers reason capture to an
override-confirm view (only required for illegal transitions, not the
default flow).
Notes blob → threaded: dropped `interests.notes` column entirely; the
threaded `interest_notes` table is the single source of truth. Latest-
note teaser on Overview links into the dedicated Notes tab. Polymorphic
notes service gains aggregated client view (unions client + interest +
yacht notes with source chips and group-by-source toggle).
Berth interest list overhaul:
- Configurable columns via ColumnPicker (18 toggleable, 5 default-on)
- Natural-sort SQL ORDER BY on mooring number (A1, A2, A10 not A10, A2)
- Per-letter row tinting via colored left-border accent + dot in cell
- Documents tab merged Files (single attachments section)
Topbar improvements:
- Always-visible back arrow on detail pages (path depth > 2)
- Breadcrumb-hint store + useBreadcrumbHint hook so detail pages can
push their entity hierarchy (Clients › Mary Smith › Interest › B17)
- Tighter spacing, softer separators, 160px crumb truncation
DataTable upgrades:
- Page-size selector with All option (validator cap raised to 1000)
- getRowClassName slot for per-row styling (used by berth tinting)
- Fixed Radix SelectItem crash on empty-string values via __any__
sentinel (was crashing every list page that opened a select filter)
Interest list:
- Configurable columns picker
- Stage cell clickable into detail
- TagPicker + SavedViewsDropdown sized h-8 to match adjacent buttons
- Save view moved into ColumnPicker menu; Views button hidden when
no views are saved
- Pipeline kanban board endpoint at /api/v1/interests/board with
minimal projection, 5000-row cap + truncated banner, filter
pass-through
Mobile chrome + sidebar collapse removed (always-expanded design choice).
User management lists super-admins (was inner-joined on user_port_roles
which excluded global super-admins).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-07 20:59:28 +02:00
|
|
|
/** ISO-3166-1 alpha-2. Drives the default timezone when the rep
|
|
|
|
|
* hasn't picked one explicitly, and lets the auto-detect banner
|
|
|
|
|
* spot a mismatch when they're travelling. */
|
|
|
|
|
country?: string;
|
|
|
|
|
/** Keyed by entity type: `clients`, `yachts`, `interests`, etc. */
|
|
|
|
|
tablePreferences?: Record<string, TablePreferences>;
|
Initial commit: Port Nimara CRM (Layers 0-4)
Full CRM rebuild with Next.js 15, TypeScript, Tailwind, Drizzle ORM,
PostgreSQL, Redis, BullMQ, MinIO, and Socket.io. Includes 461 source
files covering clients, berths, interests/pipeline, documents/EOI,
expenses/invoices, email, notifications, dashboard, admin, and
client portal. CI/CD via Gitea Actions with Docker builds.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 11:52:51 +01:00
|
|
|
[key: string]: unknown;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// ─── Better Auth Core Tables ─────────────────────────────────────────────────
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Core user table managed by Better Auth.
|
2026-05-04 22:57:01 +02:00
|
|
|
* Do NOT modify directly - Better Auth handles CRUD via its adapter.
|
Initial commit: Port Nimara CRM (Layers 0-4)
Full CRM rebuild with Next.js 15, TypeScript, Tailwind, Drizzle ORM,
PostgreSQL, Redis, BullMQ, MinIO, and Socket.io. Includes 461 source
files covering clients, berths, interests/pipeline, documents/EOI,
expenses/invoices, email, notifications, dashboard, admin, and
client portal. CI/CD via Gitea Actions with Docker builds.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 11:52:51 +01:00
|
|
|
*/
|
|
|
|
|
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(),
|
2026-04-24 12:30:06 +02:00
|
|
|
userId: text('user_id')
|
|
|
|
|
.notNull()
|
|
|
|
|
.references(() => user.id),
|
Initial commit: Port Nimara CRM (Layers 0-4)
Full CRM rebuild with Next.js 15, TypeScript, Tailwind, Drizzle ORM,
PostgreSQL, Redis, BullMQ, MinIO, and Socket.io. Includes 461 source
files covering clients, berths, interests/pipeline, documents/EOI,
expenses/invoices, email, notifications, dashboard, admin, and
client portal. CI/CD via Gitea Actions with Docker builds.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 11:52:51 +01:00
|
|
|
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',
|
|
|
|
|
{
|
2026-04-24 12:30:06 +02:00
|
|
|
id: text('id')
|
|
|
|
|
.primaryKey()
|
|
|
|
|
.$defaultFn(() => crypto.randomUUID()),
|
Initial commit: Port Nimara CRM (Layers 0-4)
Full CRM rebuild with Next.js 15, TypeScript, Tailwind, Drizzle ORM,
PostgreSQL, Redis, BullMQ, MinIO, and Socket.io. Includes 461 source
files covering clients, berths, interests/pipeline, documents/EOI,
expenses/invoices, email, notifications, dashboard, admin, and
client portal. CI/CD via Gitea Actions with Docker builds.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 11:52:51 +01:00
|
|
|
userId: text('user_id').notNull().unique(), // references Better Auth user ID
|
2026-05-09 18:36:31 +02:00
|
|
|
/**
|
|
|
|
|
* Canonical first/last name pair. Added 2026-05-09 as the primary
|
|
|
|
|
* source for greetings, invoicing, and DocSign field-merging — the
|
|
|
|
|
* older `displayName` is now kept around as a derived/optional
|
|
|
|
|
* override (e.g. for nicknames or vanity formatting). When migrating
|
|
|
|
|
* production, backfill these columns from displayName by splitting
|
|
|
|
|
* on the first space and zero-pad the trailing column with NULL so
|
|
|
|
|
* single-token names don't fail the not-null assumption.
|
|
|
|
|
*/
|
|
|
|
|
firstName: text('first_name'),
|
|
|
|
|
lastName: text('last_name'),
|
Initial commit: Port Nimara CRM (Layers 0-4)
Full CRM rebuild with Next.js 15, TypeScript, Tailwind, Drizzle ORM,
PostgreSQL, Redis, BullMQ, MinIO, and Socket.io. Includes 461 source
files covering clients, berths, interests/pipeline, documents/EOI,
expenses/invoices, email, notifications, dashboard, admin, and
client portal. CI/CD via Gitea Actions with Docker builds.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 11:52:51 +01:00
|
|
|
displayName: text('display_name').notNull(),
|
|
|
|
|
avatarUrl: text('avatar_url'),
|
feat(interests): EOI/contract/reservation tabs + contact log + berth interest milestone + interest list overhaul
Major interest workflow expansion driven by the rapid-fire UX session.
EOI / Contract / Reservation tabs replace the generic Documents tab when
the deal is at the relevant stage — workspace pattern with active-doc
hero, signing progress, paper-signed upload, and history strip. Stage-
conditional visibility wired through interest-tabs.tsx so the tab set
shrinks/expands as the deal moves through the pipeline.
Contact log: per-interaction structured log (channel/direction/summary/
optional follow-up reminder). New `interest_contact_log` table + service
+ tab UI (timeline with channel-coded icons + compose dialog).
auto-creates a reminder when followUpAt is set.
Berth Interest milestone: first milestone in the OverviewTab's pipeline
strip, completes the moment any berth is linked via the junction. Drives
the "have we captured what they want?" sanity check for general_interest
leads before they move to EOI.
Stage-conditional milestones: past phases collapse into a one-liner
strip, current phase expands, future phases hide behind a "Show
upcoming" toggle. Inline stage picker now defers reason capture to an
override-confirm view (only required for illegal transitions, not the
default flow).
Notes blob → threaded: dropped `interests.notes` column entirely; the
threaded `interest_notes` table is the single source of truth. Latest-
note teaser on Overview links into the dedicated Notes tab. Polymorphic
notes service gains aggregated client view (unions client + interest +
yacht notes with source chips and group-by-source toggle).
Berth interest list overhaul:
- Configurable columns via ColumnPicker (18 toggleable, 5 default-on)
- Natural-sort SQL ORDER BY on mooring number (A1, A2, A10 not A10, A2)
- Per-letter row tinting via colored left-border accent + dot in cell
- Documents tab merged Files (single attachments section)
Topbar improvements:
- Always-visible back arrow on detail pages (path depth > 2)
- Breadcrumb-hint store + useBreadcrumbHint hook so detail pages can
push their entity hierarchy (Clients › Mary Smith › Interest › B17)
- Tighter spacing, softer separators, 160px crumb truncation
DataTable upgrades:
- Page-size selector with All option (validator cap raised to 1000)
- getRowClassName slot for per-row styling (used by berth tinting)
- Fixed Radix SelectItem crash on empty-string values via __any__
sentinel (was crashing every list page that opened a select filter)
Interest list:
- Configurable columns picker
- Stage cell clickable into detail
- TagPicker + SavedViewsDropdown sized h-8 to match adjacent buttons
- Save view moved into ColumnPicker menu; Views button hidden when
no views are saved
- Pipeline kanban board endpoint at /api/v1/interests/board with
minimal projection, 5000-row cap + truncated banner, filter
pass-through
Mobile chrome + sidebar collapse removed (always-expanded design choice).
User management lists super-admins (was inner-joined on user_port_roles
which excluded global super-admins).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-07 20:59:28 +02:00
|
|
|
/** FK into the polymorphic `files` table — the avatar is stored
|
|
|
|
|
* via getStorageBackend() so an S3↔filesystem swap carries it
|
|
|
|
|
* without breaking the URL. The legacy `avatarUrl` column is
|
|
|
|
|
* kept for any external photo sources but the file pointer wins
|
|
|
|
|
* when both are set. */
|
|
|
|
|
avatarFileId: text('avatar_file_id'),
|
Initial commit: Port Nimara CRM (Layers 0-4)
Full CRM rebuild with Next.js 15, TypeScript, Tailwind, Drizzle ORM,
PostgreSQL, Redis, BullMQ, MinIO, and Socket.io. Includes 461 source
files covering clients, berths, interests/pipeline, documents/EOI,
expenses/invoices, email, notifications, dashboard, admin, and
client portal. CI/CD via Gitea Actions with Docker builds.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 11:52:51 +01:00
|
|
|
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<UserPreferences>().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', {
|
2026-04-24 12:30:06 +02:00
|
|
|
id: text('id')
|
|
|
|
|
.primaryKey()
|
|
|
|
|
.$defaultFn(() => crypto.randomUUID()),
|
Initial commit: Port Nimara CRM (Layers 0-4)
Full CRM rebuild with Next.js 15, TypeScript, Tailwind, Drizzle ORM,
PostgreSQL, Redis, BullMQ, MinIO, and Socket.io. Includes 461 source
files covering clients, berths, interests/pipeline, documents/EOI,
expenses/invoices, email, notifications, dashboard, admin, and
client portal. CI/CD via Gitea Actions with Docker builds.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 11:52:51 +01:00
|
|
|
name: text('name').notNull(),
|
|
|
|
|
description: text('description'),
|
2026-04-24 12:30:06 +02:00
|
|
|
permissions: jsonb('permissions')
|
|
|
|
|
.$type<RolePermissions>()
|
|
|
|
|
.notNull()
|
|
|
|
|
.default({} as RolePermissions),
|
Initial commit: Port Nimara CRM (Layers 0-4)
Full CRM rebuild with Next.js 15, TypeScript, Tailwind, Drizzle ORM,
PostgreSQL, Redis, BullMQ, MinIO, and Socket.io. Includes 461 source
files covering clients, berths, interests/pipeline, documents/EOI,
expenses/invoices, email, notifications, dashboard, admin, and
client portal. CI/CD via Gitea Actions with Docker builds.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 11:52:51 +01:00
|
|
|
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',
|
|
|
|
|
{
|
2026-04-24 12:30:06 +02:00
|
|
|
id: text('id')
|
|
|
|
|
.primaryKey()
|
|
|
|
|
.$defaultFn(() => crypto.randomUUID()),
|
Initial commit: Port Nimara CRM (Layers 0-4)
Full CRM rebuild with Next.js 15, TypeScript, Tailwind, Drizzle ORM,
PostgreSQL, Redis, BullMQ, MinIO, and Socket.io. Includes 461 source
files covering clients, berths, interests/pipeline, documents/EOI,
expenses/invoices, email, notifications, dashboard, admin, and
client portal. CI/CD via Gitea Actions with Docker builds.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 11:52:51 +01:00
|
|
|
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<Partial<RolePermissions>>()
|
|
|
|
|
.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),
|
|
|
|
|
],
|
|
|
|
|
);
|
|
|
|
|
|
feat(interests): EOI/contract/reservation tabs + contact log + berth interest milestone + interest list overhaul
Major interest workflow expansion driven by the rapid-fire UX session.
EOI / Contract / Reservation tabs replace the generic Documents tab when
the deal is at the relevant stage — workspace pattern with active-doc
hero, signing progress, paper-signed upload, and history strip. Stage-
conditional visibility wired through interest-tabs.tsx so the tab set
shrinks/expands as the deal moves through the pipeline.
Contact log: per-interaction structured log (channel/direction/summary/
optional follow-up reminder). New `interest_contact_log` table + service
+ tab UI (timeline with channel-coded icons + compose dialog).
auto-creates a reminder when followUpAt is set.
Berth Interest milestone: first milestone in the OverviewTab's pipeline
strip, completes the moment any berth is linked via the junction. Drives
the "have we captured what they want?" sanity check for general_interest
leads before they move to EOI.
Stage-conditional milestones: past phases collapse into a one-liner
strip, current phase expands, future phases hide behind a "Show
upcoming" toggle. Inline stage picker now defers reason capture to an
override-confirm view (only required for illegal transitions, not the
default flow).
Notes blob → threaded: dropped `interests.notes` column entirely; the
threaded `interest_notes` table is the single source of truth. Latest-
note teaser on Overview links into the dedicated Notes tab. Polymorphic
notes service gains aggregated client view (unions client + interest +
yacht notes with source chips and group-by-source toggle).
Berth interest list overhaul:
- Configurable columns via ColumnPicker (18 toggleable, 5 default-on)
- Natural-sort SQL ORDER BY on mooring number (A1, A2, A10 not A10, A2)
- Per-letter row tinting via colored left-border accent + dot in cell
- Documents tab merged Files (single attachments section)
Topbar improvements:
- Always-visible back arrow on detail pages (path depth > 2)
- Breadcrumb-hint store + useBreadcrumbHint hook so detail pages can
push their entity hierarchy (Clients › Mary Smith › Interest › B17)
- Tighter spacing, softer separators, 160px crumb truncation
DataTable upgrades:
- Page-size selector with All option (validator cap raised to 1000)
- getRowClassName slot for per-row styling (used by berth tinting)
- Fixed Radix SelectItem crash on empty-string values via __any__
sentinel (was crashing every list page that opened a select filter)
Interest list:
- Configurable columns picker
- Stage cell clickable into detail
- TagPicker + SavedViewsDropdown sized h-8 to match adjacent buttons
- Save view moved into ColumnPicker menu; Views button hidden when
no views are saved
- Pipeline kanban board endpoint at /api/v1/interests/board with
minimal projection, 5000-row cap + truncated banner, filter
pass-through
Mobile chrome + sidebar collapse removed (always-expanded design choice).
User management lists super-admins (was inner-joined on user_port_roles
which excluded global super-admins).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-07 20:59:28 +02:00
|
|
|
/**
|
|
|
|
|
* Pending email-change records for the verify-old-and-new flow.
|
|
|
|
|
* The CRM's `/api/v1/me/email` endpoint creates a row here, emails
|
|
|
|
|
* the OLD address with a cancel link and the NEW address with a
|
|
|
|
|
* confirm link, and applies the change only when the new address
|
|
|
|
|
* confirms (or auto-cancels at `expiresAt`).
|
|
|
|
|
*
|
|
|
|
|
* `confirmTokenHash` stores a sha256 of the random confirmation
|
|
|
|
|
* token; the raw token is only present in the email body.
|
|
|
|
|
*/
|
|
|
|
|
export const userEmailChanges = pgTable(
|
|
|
|
|
'user_email_changes',
|
|
|
|
|
{
|
|
|
|
|
id: text('id')
|
|
|
|
|
.primaryKey()
|
|
|
|
|
.$defaultFn(() => crypto.randomUUID()),
|
|
|
|
|
userId: text('user_id').notNull(),
|
|
|
|
|
oldEmail: text('old_email').notNull(),
|
|
|
|
|
newEmail: text('new_email').notNull(),
|
|
|
|
|
confirmTokenHash: text('confirm_token_hash').notNull(),
|
|
|
|
|
expiresAt: timestamp('expires_at', { withTimezone: true }).notNull(),
|
|
|
|
|
appliedAt: timestamp('applied_at', { withTimezone: true }),
|
|
|
|
|
cancelledAt: timestamp('cancelled_at', { withTimezone: true }),
|
|
|
|
|
createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(),
|
|
|
|
|
},
|
|
|
|
|
(table) => [
|
|
|
|
|
index('idx_uec_user').on(table.userId),
|
|
|
|
|
index('idx_uec_token').on(table.confirmTokenHash),
|
|
|
|
|
],
|
|
|
|
|
);
|
|
|
|
|
|
Initial commit: Port Nimara CRM (Layers 0-4)
Full CRM rebuild with Next.js 15, TypeScript, Tailwind, Drizzle ORM,
PostgreSQL, Redis, BullMQ, MinIO, and Socket.io. Includes 461 source
files covering clients, berths, interests/pipeline, documents/EOI,
expenses/invoices, email, notifications, dashboard, admin, and
client portal. CI/CD via Gitea Actions with Docker builds.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 11:52:51 +01:00
|
|
|
export const userPortRoles = pgTable(
|
|
|
|
|
'user_port_roles',
|
|
|
|
|
{
|
2026-04-24 12:30:06 +02:00
|
|
|
id: text('id')
|
|
|
|
|
.primaryKey()
|
|
|
|
|
.$defaultFn(() => crypto.randomUUID()),
|
Initial commit: Port Nimara CRM (Layers 0-4)
Full CRM rebuild with Next.js 15, TypeScript, Tailwind, Drizzle ORM,
PostgreSQL, Redis, BullMQ, MinIO, and Socket.io. Includes 461 source
files covering clients, berths, interests/pipeline, documents/EOI,
expenses/invoices, email, notifications, dashboard, admin, and
client portal. CI/CD via Gitea Actions with Docker builds.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 11:52:51 +01:00
|
|
|
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' }),
|
feat(platform): residential module + admin UI + reliability fixes
Residential platform
- New schema: residentialClients, residentialInterests (separate from
marina/yacht clients) with migration 0010
- Service layer with CRUD + audit + sockets + per-port portal toggle
- v1 + public API routes (/api/v1/residential/*, /api/public/residential-inquiries)
- List + detail pages with inline editing for clients and interests
- Per-user residentialAccess toggle on userPortRoles (migration 0011)
- Permission keys: residential_clients, residential_interests
- Sidebar nav + role form integration
- Smoke spec covering page loads, UI create flow, public endpoint
Admin & shared UI
- Admin → Forms (form templates CRUD) with validators + service
- Notification preferences page (in-app + email per type)
- Email composition + accounts list + threads view
- Branded auth shell shared across CRM + portal auth surfaces
- Inline editing extended to yacht/company/interest detail pages
- InlineTagEditor + per-entity tags endpoints (yachts, companies)
- Notes service polymorphic across clients/interests/yachts/companies
- Client list columns: yachtCount + companyCount badges
- Reservation file-download via presigned URL (replaces stale <a href>)
Route handler refactor
- Extracted yachts/companies/berths reservation handlers to sibling
handlers.ts files (Next.js 15 route.ts only allows specific exports)
Reliability fixes
- apiFetch double-stringify bug fixed across 13 components
(apiFetch already JSON.stringifies its body; passing a stringified
body produced double-encoded JSON which failed zod validation)
- SocketProvider gated behind useSyncExternalStore-based mount check
to avoid useSession() SSR crashes under React 19 + Next 15
- apiFetch falls back to URL-pathname → port-id resolution when the
Zustand store hasn't hydrated yet (fresh contexts, e2e tests)
- CRM invite flow (schema, service, route, email, dev script)
- Dashboard route → [portSlug]/dashboard/page.tsx + redirect
- Document the dev-server restart-after-migration gotcha in CLAUDE.md
Tests
- 5-case residential smoke spec
- Integration test updates for new service signatures
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-27 21:54:32 +02:00
|
|
|
/**
|
|
|
|
|
* 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),
|
Initial commit: Port Nimara CRM (Layers 0-4)
Full CRM rebuild with Next.js 15, TypeScript, Tailwind, Drizzle ORM,
PostgreSQL, Redis, BullMQ, MinIO, and Socket.io. Includes 461 source
files covering clients, berths, interests/pipeline, documents/EOI,
expenses/invoices, email, notifications, dashboard, admin, and
client portal. CI/CD via Gitea Actions with Docker builds.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 11:52:51 +01:00
|
|
|
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),
|
|
|
|
|
],
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
/**
|
2026-05-04 22:57:01 +02:00
|
|
|
* Sessions table - Better Auth compatibility.
|
Initial commit: Port Nimara CRM (Layers 0-4)
Full CRM rebuild with Next.js 15, TypeScript, Tailwind, Drizzle ORM,
PostgreSQL, Redis, BullMQ, MinIO, and Socket.io. Includes 461 source
files covering clients, berths, interests/pipeline, documents/EOI,
expenses/invoices, email, notifications, dashboard, admin, and
client portal. CI/CD via Gitea Actions with Docker builds.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 11:52:51 +01:00
|
|
|
* 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;
|