feat(yachts): add yachts, ownership history, notes, tags schema

This commit is contained in:
Matt Ciaccio
2026-04-23 17:51:19 +02:00
parent 11969c0d8a
commit 51523e6768
5 changed files with 7781 additions and 0 deletions

View File

@@ -0,0 +1,67 @@
CREATE TABLE "yacht_notes" (
"id" text PRIMARY KEY NOT NULL,
"yacht_id" text NOT NULL,
"author_id" text NOT NULL,
"content" text NOT NULL,
"mentions" text[],
"is_locked" boolean DEFAULT false NOT NULL,
"created_at" timestamp with time zone DEFAULT now() NOT NULL,
"updated_at" timestamp with time zone DEFAULT now() NOT NULL
);
--> statement-breakpoint
CREATE TABLE "yacht_ownership_history" (
"id" text PRIMARY KEY NOT NULL,
"yacht_id" text NOT NULL,
"owner_type" text NOT NULL,
"owner_id" text NOT NULL,
"start_date" timestamp with time zone NOT NULL,
"end_date" timestamp with time zone,
"transfer_reason" text,
"transfer_notes" text,
"created_by" text NOT NULL,
"created_at" timestamp with time zone DEFAULT now() NOT NULL
);
--> statement-breakpoint
CREATE TABLE "yacht_tags" (
"yacht_id" text NOT NULL,
"tag_id" text NOT NULL,
CONSTRAINT "yacht_tags_yacht_id_tag_id_pk" PRIMARY KEY("yacht_id","tag_id")
);
--> statement-breakpoint
CREATE TABLE "yachts" (
"id" text PRIMARY KEY NOT NULL,
"port_id" text NOT NULL,
"name" text NOT NULL,
"hull_number" text,
"registration" text,
"flag" text,
"year_built" integer,
"builder" text,
"model" text,
"hull_material" text,
"length_ft" numeric,
"width_ft" numeric,
"draft_ft" numeric,
"length_m" numeric,
"width_m" numeric,
"draft_m" numeric,
"current_owner_type" text NOT NULL,
"current_owner_id" text NOT NULL,
"status" text DEFAULT 'active' NOT NULL,
"notes" text,
"archived_at" timestamp with time zone,
"created_at" timestamp with time zone DEFAULT now() NOT NULL,
"updated_at" timestamp with time zone DEFAULT now() NOT NULL
);
--> statement-breakpoint
ALTER TABLE "yacht_notes" ADD CONSTRAINT "yacht_notes_yacht_id_yachts_id_fk" FOREIGN KEY ("yacht_id") REFERENCES "public"."yachts"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "yacht_ownership_history" ADD CONSTRAINT "yacht_ownership_history_yacht_id_yachts_id_fk" FOREIGN KEY ("yacht_id") REFERENCES "public"."yachts"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "yacht_tags" ADD CONSTRAINT "yacht_tags_yacht_id_yachts_id_fk" FOREIGN KEY ("yacht_id") REFERENCES "public"."yachts"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "yachts" ADD CONSTRAINT "yachts_port_id_ports_id_fk" FOREIGN KEY ("port_id") REFERENCES "public"."ports"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint
CREATE INDEX "idx_yn_yacht" ON "yacht_notes" USING btree ("yacht_id");--> statement-breakpoint
CREATE INDEX "idx_yoh_yacht" ON "yacht_ownership_history" USING btree ("yacht_id");--> statement-breakpoint
CREATE UNIQUE INDEX "idx_yoh_active" ON "yacht_ownership_history" USING btree ("yacht_id") WHERE "yacht_ownership_history"."end_date" IS NULL;--> statement-breakpoint
CREATE INDEX "idx_yachts_port" ON "yachts" USING btree ("port_id");--> statement-breakpoint
CREATE INDEX "idx_yachts_current_owner" ON "yachts" USING btree ("port_id","current_owner_type","current_owner_id");--> statement-breakpoint
CREATE INDEX "idx_yachts_name" ON "yachts" USING btree ("port_id","name");--> statement-breakpoint
CREATE INDEX "idx_yachts_archived" ON "yachts" USING btree ("port_id","archived_at");

File diff suppressed because it is too large Load Diff

View File

@@ -15,6 +15,13 @@
"when": 1776185487775,
"tag": "0001_soft_ender_wiggin",
"breakpoints": true
},
{
"idx": 2,
"version": "7",
"when": 1776958500747,
"tag": "0002_groovy_excalibur",
"breakpoints": true
}
]
}

View File

@@ -7,6 +7,9 @@ export * from './users';
// Clients
export * from './clients';
// Yachts
export * from './yachts';
// Interests
export * from './interests';

119
src/lib/db/schema/yachts.ts Normal file
View File

@@ -0,0 +1,119 @@
import {
pgTable,
text,
integer,
numeric,
timestamp,
boolean,
index,
uniqueIndex,
primaryKey,
} from 'drizzle-orm/pg-core';
import { sql } from 'drizzle-orm';
import { ports } from './ports';
export const yachts = pgTable(
'yachts',
{
id: text('id')
.primaryKey()
.$defaultFn(() => crypto.randomUUID()),
portId: text('port_id')
.notNull()
.references(() => ports.id),
name: text('name').notNull(),
hullNumber: text('hull_number'),
registration: text('registration'),
flag: text('flag'),
yearBuilt: integer('year_built'),
builder: text('builder'),
model: text('model'),
hullMaterial: text('hull_material'),
lengthFt: numeric('length_ft'),
widthFt: numeric('width_ft'),
draftFt: numeric('draft_ft'),
lengthM: numeric('length_m'),
widthM: numeric('width_m'),
draftM: numeric('draft_m'),
currentOwnerType: text('current_owner_type').notNull(), // 'client' | 'company'
currentOwnerId: text('current_owner_id').notNull(),
status: text('status').notNull().default('active'), // 'active' | 'retired' | 'sold_away'
notes: text('notes'),
archivedAt: timestamp('archived_at', { withTimezone: true }),
createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(),
updatedAt: timestamp('updated_at', { withTimezone: true }).notNull().defaultNow(),
},
(table) => [
index('idx_yachts_port').on(table.portId),
index('idx_yachts_current_owner').on(
table.portId,
table.currentOwnerType,
table.currentOwnerId,
),
index('idx_yachts_name').on(table.portId, table.name),
index('idx_yachts_archived').on(table.portId, table.archivedAt),
],
);
export const yachtOwnershipHistory = pgTable(
'yacht_ownership_history',
{
id: text('id')
.primaryKey()
.$defaultFn(() => crypto.randomUUID()),
yachtId: text('yacht_id')
.notNull()
.references(() => yachts.id, { onDelete: 'cascade' }),
ownerType: text('owner_type').notNull(),
ownerId: text('owner_id').notNull(),
startDate: timestamp('start_date', { withTimezone: true, mode: 'date' }).notNull(),
endDate: timestamp('end_date', { withTimezone: true, mode: 'date' }),
transferReason: text('transfer_reason'),
transferNotes: text('transfer_notes'),
createdBy: text('created_by').notNull(),
createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(),
},
(table) => [
index('idx_yoh_yacht').on(table.yachtId),
uniqueIndex('idx_yoh_active')
.on(table.yachtId)
.where(sql`${table.endDate} IS NULL`),
],
);
export const yachtNotes = pgTable(
'yacht_notes',
{
id: text('id')
.primaryKey()
.$defaultFn(() => crypto.randomUUID()),
yachtId: text('yacht_id')
.notNull()
.references(() => yachts.id, { onDelete: 'cascade' }),
authorId: text('author_id').notNull(),
content: text('content').notNull(),
mentions: text('mentions').array(),
isLocked: boolean('is_locked').notNull().default(false),
createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(),
updatedAt: timestamp('updated_at', { withTimezone: true }).notNull().defaultNow(),
},
(table) => [index('idx_yn_yacht').on(table.yachtId)],
);
export const yachtTags = pgTable(
'yacht_tags',
{
yachtId: text('yacht_id')
.notNull()
.references(() => yachts.id, { onDelete: 'cascade' }),
tagId: text('tag_id').notNull(),
},
(table) => [primaryKey({ columns: [table.yachtId, table.tagId] })],
);
export type Yacht = typeof yachts.$inferSelect;
export type NewYacht = typeof yachts.$inferInsert;
export type YachtOwnershipHistoryRow = typeof yachtOwnershipHistory.$inferSelect;
export type NewYachtOwnershipHistoryRow = typeof yachtOwnershipHistory.$inferInsert;
export type YachtNote = typeof yachtNotes.$inferSelect;
export type NewYachtNote = typeof yachtNotes.$inferInsert;