Vollstaendiges Datenmodell: Katalog (manufacturer, theme, set, tag, instruction), Besitz (collection_item, location, missing_part, photo), Wunschlisten (wishlist, wishlist_item) und die Auth-Tabellen von better-auth inklusive passkey und invite. Kernregel: ein set ist ein Katalogeintrag, ein collection_item die Verknuepfung mit dem tatsaechlichen Besitz. Preise liegen als Cent in INTEGER-Spalten, damit Summen exakt bleiben. SQLite laeuft mit WAL, foreign_keys=ON und busy_timeout. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
271 lines
10 KiB
SQL
271 lines
10 KiB
SQL
CREATE TABLE `account` (
|
|
`id` text PRIMARY KEY NOT NULL,
|
|
`accountId` text NOT NULL,
|
|
`providerId` text NOT NULL,
|
|
`userId` text NOT NULL,
|
|
`accessToken` text,
|
|
`refreshToken` text,
|
|
`idToken` text,
|
|
`accessTokenExpiresAt` integer,
|
|
`refreshTokenExpiresAt` integer,
|
|
`scope` text,
|
|
`password` text,
|
|
`createdAt` integer NOT NULL,
|
|
`updatedAt` integer NOT NULL,
|
|
FOREIGN KEY (`userId`) REFERENCES `user`(`id`) ON UPDATE no action ON DELETE cascade
|
|
);
|
|
--> statement-breakpoint
|
|
CREATE INDEX `account_user_idx` ON `account` (`userId`);--> statement-breakpoint
|
|
CREATE TABLE `invite` (
|
|
`id` text PRIMARY KEY NOT NULL,
|
|
`token_hash` text NOT NULL,
|
|
`email` text,
|
|
`role` text DEFAULT 'member' NOT NULL,
|
|
`created_by_user_id` text NOT NULL,
|
|
`expires_at` integer NOT NULL,
|
|
`used_at` integer,
|
|
`used_by_user_id` text,
|
|
`created_at` integer NOT NULL,
|
|
FOREIGN KEY (`created_by_user_id`) REFERENCES `user`(`id`) ON UPDATE no action ON DELETE cascade,
|
|
FOREIGN KEY (`used_by_user_id`) REFERENCES `user`(`id`) ON UPDATE no action ON DELETE set null
|
|
);
|
|
--> statement-breakpoint
|
|
CREATE UNIQUE INDEX `invite_token_hash_unique` ON `invite` (`token_hash`);--> statement-breakpoint
|
|
CREATE TABLE `passkey` (
|
|
`id` text PRIMARY KEY NOT NULL,
|
|
`name` text,
|
|
`publicKey` text NOT NULL,
|
|
`userId` text NOT NULL,
|
|
`credentialID` text NOT NULL,
|
|
`counter` integer NOT NULL,
|
|
`deviceType` text NOT NULL,
|
|
`backedUp` integer NOT NULL,
|
|
`transports` text,
|
|
`aaguid` text,
|
|
`createdAt` integer,
|
|
FOREIGN KEY (`userId`) REFERENCES `user`(`id`) ON UPDATE no action ON DELETE cascade
|
|
);
|
|
--> statement-breakpoint
|
|
CREATE UNIQUE INDEX `passkey_credentialID_unique` ON `passkey` (`credentialID`);--> statement-breakpoint
|
|
CREATE INDEX `passkey_user_idx` ON `passkey` (`userId`);--> statement-breakpoint
|
|
CREATE TABLE `rateLimit` (
|
|
`id` text PRIMARY KEY NOT NULL,
|
|
`key` text,
|
|
`count` integer,
|
|
`lastRequest` integer
|
|
);
|
|
--> statement-breakpoint
|
|
CREATE TABLE `session` (
|
|
`id` text PRIMARY KEY NOT NULL,
|
|
`token` text NOT NULL,
|
|
`userId` text NOT NULL,
|
|
`expiresAt` integer NOT NULL,
|
|
`ipAddress` text,
|
|
`userAgent` text,
|
|
`createdAt` integer NOT NULL,
|
|
`updatedAt` integer NOT NULL,
|
|
FOREIGN KEY (`userId`) REFERENCES `user`(`id`) ON UPDATE no action ON DELETE cascade
|
|
);
|
|
--> statement-breakpoint
|
|
CREATE UNIQUE INDEX `session_token_unique` ON `session` (`token`);--> statement-breakpoint
|
|
CREATE INDEX `session_user_idx` ON `session` (`userId`);--> statement-breakpoint
|
|
CREATE TABLE `user` (
|
|
`id` text PRIMARY KEY NOT NULL,
|
|
`name` text NOT NULL,
|
|
`email` text NOT NULL,
|
|
`emailVerified` integer DEFAULT false NOT NULL,
|
|
`image` text,
|
|
`role` text DEFAULT 'member' NOT NULL,
|
|
`createdAt` integer NOT NULL,
|
|
`updatedAt` integer NOT NULL
|
|
);
|
|
--> statement-breakpoint
|
|
CREATE UNIQUE INDEX `user_email_unique` ON `user` (`email`);--> statement-breakpoint
|
|
CREATE TABLE `verification` (
|
|
`id` text PRIMARY KEY NOT NULL,
|
|
`identifier` text NOT NULL,
|
|
`value` text NOT NULL,
|
|
`expiresAt` integer NOT NULL,
|
|
`createdAt` integer NOT NULL,
|
|
`updatedAt` integer NOT NULL
|
|
);
|
|
--> statement-breakpoint
|
|
CREATE INDEX `verification_identifier_idx` ON `verification` (`identifier`);--> statement-breakpoint
|
|
CREATE TABLE `instruction` (
|
|
`id` text PRIMARY KEY NOT NULL,
|
|
`set_id` text NOT NULL,
|
|
`kind` text NOT NULL,
|
|
`file_path` text,
|
|
`url` text,
|
|
`label` text,
|
|
`page_count` integer,
|
|
`file_size_bytes` integer,
|
|
`sort_order` integer DEFAULT 0 NOT NULL,
|
|
`created_at` integer NOT NULL,
|
|
FOREIGN KEY (`set_id`) REFERENCES `set`(`id`) ON UPDATE no action ON DELETE cascade,
|
|
CONSTRAINT "instruction_source" CHECK(("instruction"."kind" = 'pdf_upload' AND "instruction"."file_path" IS NOT NULL AND "instruction"."url" IS NULL)
|
|
OR ("instruction"."kind" = 'external_url' AND "instruction"."url" IS NOT NULL AND "instruction"."file_path" IS NULL))
|
|
);
|
|
--> statement-breakpoint
|
|
CREATE INDEX `instruction_set_idx` ON `instruction` (`set_id`);--> statement-breakpoint
|
|
CREATE TABLE `manufacturer` (
|
|
`id` text PRIMARY KEY NOT NULL,
|
|
`name` text NOT NULL,
|
|
`slug` text NOT NULL,
|
|
`website` text,
|
|
`created_at` integer NOT NULL,
|
|
`updated_at` integer NOT NULL
|
|
);
|
|
--> statement-breakpoint
|
|
CREATE UNIQUE INDEX `manufacturer_slug_unique` ON `manufacturer` (`slug`);--> statement-breakpoint
|
|
CREATE TABLE `set` (
|
|
`id` text PRIMARY KEY NOT NULL,
|
|
`manufacturer_id` text NOT NULL,
|
|
`theme_id` text,
|
|
`set_number` text NOT NULL,
|
|
`name` text NOT NULL,
|
|
`year` integer,
|
|
`piece_count` integer,
|
|
`minifig_count` integer,
|
|
`ean` text,
|
|
`age_range` text,
|
|
`image_url` text,
|
|
`image_path` text,
|
|
`retail_price_cents` integer,
|
|
`external_ids` text,
|
|
`notes` text,
|
|
`created_at` integer NOT NULL,
|
|
`updated_at` integer NOT NULL,
|
|
FOREIGN KEY (`manufacturer_id`) REFERENCES `manufacturer`(`id`) ON UPDATE no action ON DELETE restrict,
|
|
FOREIGN KEY (`theme_id`) REFERENCES `theme`(`id`) ON UPDATE no action ON DELETE set null
|
|
);
|
|
--> statement-breakpoint
|
|
CREATE UNIQUE INDEX `set_manufacturer_number_unique` ON `set` (`manufacturer_id`,`set_number`);--> statement-breakpoint
|
|
CREATE INDEX `set_name_idx` ON `set` (`name`);--> statement-breakpoint
|
|
CREATE INDEX `set_ean_idx` ON `set` (`ean`);--> statement-breakpoint
|
|
CREATE INDEX `set_theme_idx` ON `set` (`theme_id`);--> statement-breakpoint
|
|
CREATE INDEX `set_year_idx` ON `set` (`year`);--> statement-breakpoint
|
|
CREATE TABLE `set_tag` (
|
|
`set_id` text NOT NULL,
|
|
`tag_id` text NOT NULL,
|
|
PRIMARY KEY(`set_id`, `tag_id`),
|
|
FOREIGN KEY (`set_id`) REFERENCES `set`(`id`) ON UPDATE no action ON DELETE cascade,
|
|
FOREIGN KEY (`tag_id`) REFERENCES `tag`(`id`) ON UPDATE no action ON DELETE cascade
|
|
);
|
|
--> statement-breakpoint
|
|
CREATE TABLE `tag` (
|
|
`id` text PRIMARY KEY NOT NULL,
|
|
`name` text NOT NULL,
|
|
`slug` text NOT NULL
|
|
);
|
|
--> statement-breakpoint
|
|
CREATE UNIQUE INDEX `tag_slug_unique` ON `tag` (`slug`);--> statement-breakpoint
|
|
CREATE TABLE `theme` (
|
|
`id` text PRIMARY KEY NOT NULL,
|
|
`manufacturer_id` text NOT NULL,
|
|
`parent_theme_id` text,
|
|
`name` text NOT NULL,
|
|
`created_at` integer NOT NULL,
|
|
`updated_at` integer NOT NULL,
|
|
FOREIGN KEY (`manufacturer_id`) REFERENCES `manufacturer`(`id`) ON UPDATE no action ON DELETE cascade,
|
|
FOREIGN KEY (`parent_theme_id`) REFERENCES `theme`(`id`) ON UPDATE no action ON DELETE set null
|
|
);
|
|
--> statement-breakpoint
|
|
CREATE INDEX `theme_manufacturer_idx` ON `theme` (`manufacturer_id`);--> statement-breakpoint
|
|
CREATE UNIQUE INDEX `theme_unique_name` ON `theme` (`manufacturer_id`,`name`);--> statement-breakpoint
|
|
CREATE TABLE `collection_item` (
|
|
`id` text PRIMARY KEY NOT NULL,
|
|
`set_id` text NOT NULL,
|
|
`status` text DEFAULT 'owned' NOT NULL,
|
|
`completeness` text DEFAULT 'unknown' NOT NULL,
|
|
`condition` text,
|
|
`location_id` text,
|
|
`acquired_at` integer,
|
|
`acquired_price_cents` integer,
|
|
`acquired_from` text,
|
|
`gifted_by` text,
|
|
`lent_out_to` text,
|
|
`notes` text,
|
|
`created_at` integer NOT NULL,
|
|
`updated_at` integer NOT NULL,
|
|
FOREIGN KEY (`set_id`) REFERENCES `set`(`id`) ON UPDATE no action ON DELETE cascade,
|
|
FOREIGN KEY (`location_id`) REFERENCES `location`(`id`) ON UPDATE no action ON DELETE set null
|
|
);
|
|
--> statement-breakpoint
|
|
CREATE INDEX `collection_item_set_idx` ON `collection_item` (`set_id`);--> statement-breakpoint
|
|
CREATE INDEX `collection_item_status_idx` ON `collection_item` (`status`);--> statement-breakpoint
|
|
CREATE INDEX `collection_item_location_idx` ON `collection_item` (`location_id`);--> statement-breakpoint
|
|
CREATE TABLE `location` (
|
|
`id` text PRIMARY KEY NOT NULL,
|
|
`name` text NOT NULL,
|
|
`parent_location_id` text,
|
|
`created_at` integer NOT NULL,
|
|
`updated_at` integer NOT NULL,
|
|
FOREIGN KEY (`parent_location_id`) REFERENCES `location`(`id`) ON UPDATE no action ON DELETE set null
|
|
);
|
|
--> statement-breakpoint
|
|
CREATE TABLE `missing_part` (
|
|
`id` text PRIMARY KEY NOT NULL,
|
|
`collection_item_id` text NOT NULL,
|
|
`part_number` text,
|
|
`color_name` text,
|
|
`quantity` integer DEFAULT 1 NOT NULL,
|
|
`resolved` integer DEFAULT false NOT NULL,
|
|
`notes` text,
|
|
`created_at` integer NOT NULL,
|
|
FOREIGN KEY (`collection_item_id`) REFERENCES `collection_item`(`id`) ON UPDATE no action ON DELETE cascade
|
|
);
|
|
--> statement-breakpoint
|
|
CREATE INDEX `missing_part_item_idx` ON `missing_part` (`collection_item_id`);--> statement-breakpoint
|
|
CREATE TABLE `photo` (
|
|
`id` text PRIMARY KEY NOT NULL,
|
|
`set_id` text,
|
|
`collection_item_id` text,
|
|
`file_path` text NOT NULL,
|
|
`thumbnail_path` text,
|
|
`caption` text,
|
|
`sort_order` integer DEFAULT 0 NOT NULL,
|
|
`created_at` integer NOT NULL,
|
|
FOREIGN KEY (`set_id`) REFERENCES `set`(`id`) ON UPDATE no action ON DELETE cascade,
|
|
FOREIGN KEY (`collection_item_id`) REFERENCES `collection_item`(`id`) ON UPDATE no action ON DELETE cascade,
|
|
CONSTRAINT "photo_single_owner" CHECK(("photo"."set_id" IS NOT NULL) <> ("photo"."collection_item_id" IS NOT NULL))
|
|
);
|
|
--> statement-breakpoint
|
|
CREATE INDEX `photo_set_idx` ON `photo` (`set_id`);--> statement-breakpoint
|
|
CREATE INDEX `photo_item_idx` ON `photo` (`collection_item_id`);--> statement-breakpoint
|
|
CREATE TABLE `wishlist` (
|
|
`id` text PRIMARY KEY NOT NULL,
|
|
`owner_user_id` text NOT NULL,
|
|
`title` text NOT NULL,
|
|
`occasion` text DEFAULT 'other' NOT NULL,
|
|
`event_date` integer,
|
|
`share_token` text NOT NULL,
|
|
`is_public` integer DEFAULT false NOT NULL,
|
|
`surprise_mode` integer DEFAULT false NOT NULL,
|
|
`note` text,
|
|
`created_at` integer NOT NULL,
|
|
`updated_at` integer NOT NULL,
|
|
FOREIGN KEY (`owner_user_id`) REFERENCES `user`(`id`) ON UPDATE no action ON DELETE cascade
|
|
);
|
|
--> statement-breakpoint
|
|
CREATE UNIQUE INDEX `wishlist_share_token_unique` ON `wishlist` (`share_token`);--> statement-breakpoint
|
|
CREATE INDEX `wishlist_owner_idx` ON `wishlist` (`owner_user_id`);--> statement-breakpoint
|
|
CREATE TABLE `wishlist_item` (
|
|
`id` text PRIMARY KEY NOT NULL,
|
|
`wishlist_id` text NOT NULL,
|
|
`set_id` text NOT NULL,
|
|
`priority` integer DEFAULT 2 NOT NULL,
|
|
`note` text,
|
|
`sort_order` integer DEFAULT 0 NOT NULL,
|
|
`reserved_by_name` text,
|
|
`reserved_at` integer,
|
|
`reservation_token_hash` text,
|
|
`fulfilled_at` integer,
|
|
`created_at` integer NOT NULL,
|
|
FOREIGN KEY (`wishlist_id`) REFERENCES `wishlist`(`id`) ON UPDATE no action ON DELETE cascade,
|
|
FOREIGN KEY (`set_id`) REFERENCES `set`(`id`) ON UPDATE no action ON DELETE cascade,
|
|
CONSTRAINT "wishlist_item_priority" CHECK("wishlist_item"."priority" BETWEEN 1 AND 3)
|
|
);
|
|
--> statement-breakpoint
|
|
CREATE UNIQUE INDEX `wishlist_item_unique` ON `wishlist_item` (`wishlist_id`,`set_id`);--> statement-breakpoint
|
|
CREATE INDEX `wishlist_item_list_idx` ON `wishlist_item` (`wishlist_id`,`sort_order`); |