feat(16-01): migrate schema to pgTable and add users table with userId columns
- Rewrite schema.ts from sqlite-core to pg-core (pgTable, serial, timestamp, doublePrecision) - Add users table with id, logtoSub (unique), createdAt - Add userId FK column to items, categories, threads, setups, apiKeys, oauthTokens - Add composite unique constraint on categories(userId, name) - Change settings PK to composite (userId, key) - Remove global Uncategorized seed from seed.ts (now per-user lazy) - Generate Drizzle pg migration
This commit is contained in:
7
drizzle-pg.config.ts
Normal file
7
drizzle-pg.config.ts
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
import { defineConfig } from "drizzle-kit";
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
out: "./drizzle-pg",
|
||||||
|
schema: "./src/db/schema.ts",
|
||||||
|
dialect: "postgresql",
|
||||||
|
});
|
||||||
140
drizzle-pg/0000_thankful_loners.sql
Normal file
140
drizzle-pg/0000_thankful_loners.sql
Normal file
@@ -0,0 +1,140 @@
|
|||||||
|
CREATE TABLE "api_keys" (
|
||||||
|
"id" serial PRIMARY KEY NOT NULL,
|
||||||
|
"name" text NOT NULL,
|
||||||
|
"key_hash" text NOT NULL,
|
||||||
|
"key_prefix" text NOT NULL,
|
||||||
|
"user_id" integer NOT NULL,
|
||||||
|
"created_at" timestamp DEFAULT now() NOT NULL
|
||||||
|
);
|
||||||
|
--> statement-breakpoint
|
||||||
|
CREATE TABLE "categories" (
|
||||||
|
"id" serial PRIMARY KEY NOT NULL,
|
||||||
|
"name" text NOT NULL,
|
||||||
|
"icon" text DEFAULT 'package' NOT NULL,
|
||||||
|
"user_id" integer NOT NULL,
|
||||||
|
"created_at" timestamp DEFAULT now() NOT NULL,
|
||||||
|
CONSTRAINT "categories_user_id_name_unique" UNIQUE("user_id","name")
|
||||||
|
);
|
||||||
|
--> statement-breakpoint
|
||||||
|
CREATE TABLE "items" (
|
||||||
|
"id" serial PRIMARY KEY NOT NULL,
|
||||||
|
"name" text NOT NULL,
|
||||||
|
"weight_grams" double precision,
|
||||||
|
"price_cents" integer,
|
||||||
|
"category_id" integer NOT NULL,
|
||||||
|
"user_id" integer NOT NULL,
|
||||||
|
"notes" text,
|
||||||
|
"product_url" text,
|
||||||
|
"image_filename" text,
|
||||||
|
"image_source_url" text,
|
||||||
|
"quantity" integer DEFAULT 1 NOT NULL,
|
||||||
|
"created_at" timestamp DEFAULT now() NOT NULL,
|
||||||
|
"updated_at" timestamp DEFAULT now() NOT NULL
|
||||||
|
);
|
||||||
|
--> statement-breakpoint
|
||||||
|
CREATE TABLE "oauth_clients" (
|
||||||
|
"id" serial PRIMARY KEY NOT NULL,
|
||||||
|
"client_id" text NOT NULL,
|
||||||
|
"client_name" text,
|
||||||
|
"redirect_uris" text NOT NULL,
|
||||||
|
"created_at" timestamp DEFAULT now() NOT NULL,
|
||||||
|
CONSTRAINT "oauth_clients_client_id_unique" UNIQUE("client_id")
|
||||||
|
);
|
||||||
|
--> statement-breakpoint
|
||||||
|
CREATE TABLE "oauth_codes" (
|
||||||
|
"id" serial PRIMARY KEY NOT NULL,
|
||||||
|
"code" text NOT NULL,
|
||||||
|
"client_id" text NOT NULL,
|
||||||
|
"code_challenge" text NOT NULL,
|
||||||
|
"code_challenge_method" text DEFAULT 'S256' NOT NULL,
|
||||||
|
"redirect_uri" text NOT NULL,
|
||||||
|
"expires_at" timestamp NOT NULL,
|
||||||
|
"used" integer DEFAULT 0 NOT NULL,
|
||||||
|
CONSTRAINT "oauth_codes_code_unique" UNIQUE("code")
|
||||||
|
);
|
||||||
|
--> statement-breakpoint
|
||||||
|
CREATE TABLE "oauth_tokens" (
|
||||||
|
"id" serial PRIMARY KEY NOT NULL,
|
||||||
|
"access_token_hash" text NOT NULL,
|
||||||
|
"refresh_token_hash" text NOT NULL,
|
||||||
|
"client_id" text NOT NULL,
|
||||||
|
"user_id" integer NOT NULL,
|
||||||
|
"expires_at" timestamp NOT NULL,
|
||||||
|
"refresh_expires_at" timestamp NOT NULL,
|
||||||
|
"created_at" timestamp DEFAULT now() NOT NULL,
|
||||||
|
CONSTRAINT "oauth_tokens_access_token_hash_unique" UNIQUE("access_token_hash"),
|
||||||
|
CONSTRAINT "oauth_tokens_refresh_token_hash_unique" UNIQUE("refresh_token_hash")
|
||||||
|
);
|
||||||
|
--> statement-breakpoint
|
||||||
|
CREATE TABLE "settings" (
|
||||||
|
"user_id" integer NOT NULL,
|
||||||
|
"key" text NOT NULL,
|
||||||
|
"value" text NOT NULL,
|
||||||
|
CONSTRAINT "settings_user_id_key_pk" PRIMARY KEY("user_id","key")
|
||||||
|
);
|
||||||
|
--> statement-breakpoint
|
||||||
|
CREATE TABLE "setup_items" (
|
||||||
|
"id" serial PRIMARY KEY NOT NULL,
|
||||||
|
"setup_id" integer NOT NULL,
|
||||||
|
"item_id" integer NOT NULL,
|
||||||
|
"classification" text DEFAULT 'base' NOT NULL
|
||||||
|
);
|
||||||
|
--> statement-breakpoint
|
||||||
|
CREATE TABLE "setups" (
|
||||||
|
"id" serial PRIMARY KEY NOT NULL,
|
||||||
|
"name" text NOT NULL,
|
||||||
|
"user_id" integer NOT NULL,
|
||||||
|
"created_at" timestamp DEFAULT now() NOT NULL,
|
||||||
|
"updated_at" timestamp DEFAULT now() NOT NULL
|
||||||
|
);
|
||||||
|
--> statement-breakpoint
|
||||||
|
CREATE TABLE "thread_candidates" (
|
||||||
|
"id" serial PRIMARY KEY NOT NULL,
|
||||||
|
"thread_id" integer NOT NULL,
|
||||||
|
"name" text NOT NULL,
|
||||||
|
"weight_grams" double precision,
|
||||||
|
"price_cents" integer,
|
||||||
|
"category_id" integer NOT NULL,
|
||||||
|
"notes" text,
|
||||||
|
"product_url" text,
|
||||||
|
"image_filename" text,
|
||||||
|
"image_source_url" text,
|
||||||
|
"status" text DEFAULT 'researching' NOT NULL,
|
||||||
|
"pros" text,
|
||||||
|
"cons" text,
|
||||||
|
"sort_order" double precision DEFAULT 0 NOT NULL,
|
||||||
|
"created_at" timestamp DEFAULT now() NOT NULL,
|
||||||
|
"updated_at" timestamp DEFAULT now() NOT NULL
|
||||||
|
);
|
||||||
|
--> statement-breakpoint
|
||||||
|
CREATE TABLE "threads" (
|
||||||
|
"id" serial PRIMARY KEY NOT NULL,
|
||||||
|
"name" text NOT NULL,
|
||||||
|
"status" text DEFAULT 'active' NOT NULL,
|
||||||
|
"resolved_candidate_id" integer,
|
||||||
|
"category_id" integer NOT NULL,
|
||||||
|
"user_id" integer NOT NULL,
|
||||||
|
"created_at" timestamp DEFAULT now() NOT NULL,
|
||||||
|
"updated_at" timestamp DEFAULT now() NOT NULL
|
||||||
|
);
|
||||||
|
--> statement-breakpoint
|
||||||
|
CREATE TABLE "users" (
|
||||||
|
"id" serial PRIMARY KEY NOT NULL,
|
||||||
|
"logto_sub" text NOT NULL,
|
||||||
|
"created_at" timestamp DEFAULT now() NOT NULL,
|
||||||
|
CONSTRAINT "users_logto_sub_unique" UNIQUE("logto_sub")
|
||||||
|
);
|
||||||
|
--> statement-breakpoint
|
||||||
|
ALTER TABLE "api_keys" ADD CONSTRAINT "api_keys_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint
|
||||||
|
ALTER TABLE "categories" ADD CONSTRAINT "categories_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint
|
||||||
|
ALTER TABLE "items" ADD CONSTRAINT "items_category_id_categories_id_fk" FOREIGN KEY ("category_id") REFERENCES "public"."categories"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint
|
||||||
|
ALTER TABLE "items" ADD CONSTRAINT "items_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint
|
||||||
|
ALTER TABLE "oauth_tokens" ADD CONSTRAINT "oauth_tokens_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint
|
||||||
|
ALTER TABLE "settings" ADD CONSTRAINT "settings_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint
|
||||||
|
ALTER TABLE "setup_items" ADD CONSTRAINT "setup_items_setup_id_setups_id_fk" FOREIGN KEY ("setup_id") REFERENCES "public"."setups"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||||
|
ALTER TABLE "setup_items" ADD CONSTRAINT "setup_items_item_id_items_id_fk" FOREIGN KEY ("item_id") REFERENCES "public"."items"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||||
|
ALTER TABLE "setups" ADD CONSTRAINT "setups_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint
|
||||||
|
ALTER TABLE "thread_candidates" ADD CONSTRAINT "thread_candidates_thread_id_threads_id_fk" FOREIGN KEY ("thread_id") REFERENCES "public"."threads"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||||
|
ALTER TABLE "thread_candidates" ADD CONSTRAINT "thread_candidates_category_id_categories_id_fk" FOREIGN KEY ("category_id") REFERENCES "public"."categories"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint
|
||||||
|
ALTER TABLE "threads" ADD CONSTRAINT "threads_category_id_categories_id_fk" FOREIGN KEY ("category_id") REFERENCES "public"."categories"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint
|
||||||
|
ALTER TABLE "threads" ADD CONSTRAINT "threads_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE no action ON UPDATE no action;
|
||||||
934
drizzle-pg/meta/0000_snapshot.json
Normal file
934
drizzle-pg/meta/0000_snapshot.json
Normal file
@@ -0,0 +1,934 @@
|
|||||||
|
{
|
||||||
|
"id": "2f3f44c0-0fd3-4ac5-b1fb-51bc709342df",
|
||||||
|
"prevId": "00000000-0000-0000-0000-000000000000",
|
||||||
|
"version": "7",
|
||||||
|
"dialect": "postgresql",
|
||||||
|
"tables": {
|
||||||
|
"public.api_keys": {
|
||||||
|
"name": "api_keys",
|
||||||
|
"schema": "",
|
||||||
|
"columns": {
|
||||||
|
"id": {
|
||||||
|
"name": "id",
|
||||||
|
"type": "serial",
|
||||||
|
"primaryKey": true,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"name": {
|
||||||
|
"name": "name",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"key_hash": {
|
||||||
|
"name": "key_hash",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"key_prefix": {
|
||||||
|
"name": "key_prefix",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"user_id": {
|
||||||
|
"name": "user_id",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"created_at": {
|
||||||
|
"name": "created_at",
|
||||||
|
"type": "timestamp",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"default": "now()"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {},
|
||||||
|
"foreignKeys": {
|
||||||
|
"api_keys_user_id_users_id_fk": {
|
||||||
|
"name": "api_keys_user_id_users_id_fk",
|
||||||
|
"tableFrom": "api_keys",
|
||||||
|
"tableTo": "users",
|
||||||
|
"columnsFrom": [
|
||||||
|
"user_id"
|
||||||
|
],
|
||||||
|
"columnsTo": [
|
||||||
|
"id"
|
||||||
|
],
|
||||||
|
"onDelete": "no action",
|
||||||
|
"onUpdate": "no action"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"compositePrimaryKeys": {},
|
||||||
|
"uniqueConstraints": {},
|
||||||
|
"policies": {},
|
||||||
|
"checkConstraints": {},
|
||||||
|
"isRLSEnabled": false
|
||||||
|
},
|
||||||
|
"public.categories": {
|
||||||
|
"name": "categories",
|
||||||
|
"schema": "",
|
||||||
|
"columns": {
|
||||||
|
"id": {
|
||||||
|
"name": "id",
|
||||||
|
"type": "serial",
|
||||||
|
"primaryKey": true,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"name": {
|
||||||
|
"name": "name",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"icon": {
|
||||||
|
"name": "icon",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"default": "'package'"
|
||||||
|
},
|
||||||
|
"user_id": {
|
||||||
|
"name": "user_id",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"created_at": {
|
||||||
|
"name": "created_at",
|
||||||
|
"type": "timestamp",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"default": "now()"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {},
|
||||||
|
"foreignKeys": {
|
||||||
|
"categories_user_id_users_id_fk": {
|
||||||
|
"name": "categories_user_id_users_id_fk",
|
||||||
|
"tableFrom": "categories",
|
||||||
|
"tableTo": "users",
|
||||||
|
"columnsFrom": [
|
||||||
|
"user_id"
|
||||||
|
],
|
||||||
|
"columnsTo": [
|
||||||
|
"id"
|
||||||
|
],
|
||||||
|
"onDelete": "no action",
|
||||||
|
"onUpdate": "no action"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"compositePrimaryKeys": {},
|
||||||
|
"uniqueConstraints": {
|
||||||
|
"categories_user_id_name_unique": {
|
||||||
|
"name": "categories_user_id_name_unique",
|
||||||
|
"nullsNotDistinct": false,
|
||||||
|
"columns": [
|
||||||
|
"user_id",
|
||||||
|
"name"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"policies": {},
|
||||||
|
"checkConstraints": {},
|
||||||
|
"isRLSEnabled": false
|
||||||
|
},
|
||||||
|
"public.items": {
|
||||||
|
"name": "items",
|
||||||
|
"schema": "",
|
||||||
|
"columns": {
|
||||||
|
"id": {
|
||||||
|
"name": "id",
|
||||||
|
"type": "serial",
|
||||||
|
"primaryKey": true,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"name": {
|
||||||
|
"name": "name",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"weight_grams": {
|
||||||
|
"name": "weight_grams",
|
||||||
|
"type": "double precision",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"price_cents": {
|
||||||
|
"name": "price_cents",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"category_id": {
|
||||||
|
"name": "category_id",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"user_id": {
|
||||||
|
"name": "user_id",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"notes": {
|
||||||
|
"name": "notes",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"product_url": {
|
||||||
|
"name": "product_url",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"image_filename": {
|
||||||
|
"name": "image_filename",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"image_source_url": {
|
||||||
|
"name": "image_source_url",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"quantity": {
|
||||||
|
"name": "quantity",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"default": 1
|
||||||
|
},
|
||||||
|
"created_at": {
|
||||||
|
"name": "created_at",
|
||||||
|
"type": "timestamp",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"default": "now()"
|
||||||
|
},
|
||||||
|
"updated_at": {
|
||||||
|
"name": "updated_at",
|
||||||
|
"type": "timestamp",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"default": "now()"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {},
|
||||||
|
"foreignKeys": {
|
||||||
|
"items_category_id_categories_id_fk": {
|
||||||
|
"name": "items_category_id_categories_id_fk",
|
||||||
|
"tableFrom": "items",
|
||||||
|
"tableTo": "categories",
|
||||||
|
"columnsFrom": [
|
||||||
|
"category_id"
|
||||||
|
],
|
||||||
|
"columnsTo": [
|
||||||
|
"id"
|
||||||
|
],
|
||||||
|
"onDelete": "no action",
|
||||||
|
"onUpdate": "no action"
|
||||||
|
},
|
||||||
|
"items_user_id_users_id_fk": {
|
||||||
|
"name": "items_user_id_users_id_fk",
|
||||||
|
"tableFrom": "items",
|
||||||
|
"tableTo": "users",
|
||||||
|
"columnsFrom": [
|
||||||
|
"user_id"
|
||||||
|
],
|
||||||
|
"columnsTo": [
|
||||||
|
"id"
|
||||||
|
],
|
||||||
|
"onDelete": "no action",
|
||||||
|
"onUpdate": "no action"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"compositePrimaryKeys": {},
|
||||||
|
"uniqueConstraints": {},
|
||||||
|
"policies": {},
|
||||||
|
"checkConstraints": {},
|
||||||
|
"isRLSEnabled": false
|
||||||
|
},
|
||||||
|
"public.oauth_clients": {
|
||||||
|
"name": "oauth_clients",
|
||||||
|
"schema": "",
|
||||||
|
"columns": {
|
||||||
|
"id": {
|
||||||
|
"name": "id",
|
||||||
|
"type": "serial",
|
||||||
|
"primaryKey": true,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"client_id": {
|
||||||
|
"name": "client_id",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"client_name": {
|
||||||
|
"name": "client_name",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"redirect_uris": {
|
||||||
|
"name": "redirect_uris",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"created_at": {
|
||||||
|
"name": "created_at",
|
||||||
|
"type": "timestamp",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"default": "now()"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {},
|
||||||
|
"foreignKeys": {},
|
||||||
|
"compositePrimaryKeys": {},
|
||||||
|
"uniqueConstraints": {
|
||||||
|
"oauth_clients_client_id_unique": {
|
||||||
|
"name": "oauth_clients_client_id_unique",
|
||||||
|
"nullsNotDistinct": false,
|
||||||
|
"columns": [
|
||||||
|
"client_id"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"policies": {},
|
||||||
|
"checkConstraints": {},
|
||||||
|
"isRLSEnabled": false
|
||||||
|
},
|
||||||
|
"public.oauth_codes": {
|
||||||
|
"name": "oauth_codes",
|
||||||
|
"schema": "",
|
||||||
|
"columns": {
|
||||||
|
"id": {
|
||||||
|
"name": "id",
|
||||||
|
"type": "serial",
|
||||||
|
"primaryKey": true,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"code": {
|
||||||
|
"name": "code",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"client_id": {
|
||||||
|
"name": "client_id",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"code_challenge": {
|
||||||
|
"name": "code_challenge",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"code_challenge_method": {
|
||||||
|
"name": "code_challenge_method",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"default": "'S256'"
|
||||||
|
},
|
||||||
|
"redirect_uri": {
|
||||||
|
"name": "redirect_uri",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"expires_at": {
|
||||||
|
"name": "expires_at",
|
||||||
|
"type": "timestamp",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"used": {
|
||||||
|
"name": "used",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"default": 0
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {},
|
||||||
|
"foreignKeys": {},
|
||||||
|
"compositePrimaryKeys": {},
|
||||||
|
"uniqueConstraints": {
|
||||||
|
"oauth_codes_code_unique": {
|
||||||
|
"name": "oauth_codes_code_unique",
|
||||||
|
"nullsNotDistinct": false,
|
||||||
|
"columns": [
|
||||||
|
"code"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"policies": {},
|
||||||
|
"checkConstraints": {},
|
||||||
|
"isRLSEnabled": false
|
||||||
|
},
|
||||||
|
"public.oauth_tokens": {
|
||||||
|
"name": "oauth_tokens",
|
||||||
|
"schema": "",
|
||||||
|
"columns": {
|
||||||
|
"id": {
|
||||||
|
"name": "id",
|
||||||
|
"type": "serial",
|
||||||
|
"primaryKey": true,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"access_token_hash": {
|
||||||
|
"name": "access_token_hash",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"refresh_token_hash": {
|
||||||
|
"name": "refresh_token_hash",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"client_id": {
|
||||||
|
"name": "client_id",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"user_id": {
|
||||||
|
"name": "user_id",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"expires_at": {
|
||||||
|
"name": "expires_at",
|
||||||
|
"type": "timestamp",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"refresh_expires_at": {
|
||||||
|
"name": "refresh_expires_at",
|
||||||
|
"type": "timestamp",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"created_at": {
|
||||||
|
"name": "created_at",
|
||||||
|
"type": "timestamp",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"default": "now()"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {},
|
||||||
|
"foreignKeys": {
|
||||||
|
"oauth_tokens_user_id_users_id_fk": {
|
||||||
|
"name": "oauth_tokens_user_id_users_id_fk",
|
||||||
|
"tableFrom": "oauth_tokens",
|
||||||
|
"tableTo": "users",
|
||||||
|
"columnsFrom": [
|
||||||
|
"user_id"
|
||||||
|
],
|
||||||
|
"columnsTo": [
|
||||||
|
"id"
|
||||||
|
],
|
||||||
|
"onDelete": "no action",
|
||||||
|
"onUpdate": "no action"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"compositePrimaryKeys": {},
|
||||||
|
"uniqueConstraints": {
|
||||||
|
"oauth_tokens_access_token_hash_unique": {
|
||||||
|
"name": "oauth_tokens_access_token_hash_unique",
|
||||||
|
"nullsNotDistinct": false,
|
||||||
|
"columns": [
|
||||||
|
"access_token_hash"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"oauth_tokens_refresh_token_hash_unique": {
|
||||||
|
"name": "oauth_tokens_refresh_token_hash_unique",
|
||||||
|
"nullsNotDistinct": false,
|
||||||
|
"columns": [
|
||||||
|
"refresh_token_hash"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"policies": {},
|
||||||
|
"checkConstraints": {},
|
||||||
|
"isRLSEnabled": false
|
||||||
|
},
|
||||||
|
"public.settings": {
|
||||||
|
"name": "settings",
|
||||||
|
"schema": "",
|
||||||
|
"columns": {
|
||||||
|
"user_id": {
|
||||||
|
"name": "user_id",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"key": {
|
||||||
|
"name": "key",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"value": {
|
||||||
|
"name": "value",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {},
|
||||||
|
"foreignKeys": {
|
||||||
|
"settings_user_id_users_id_fk": {
|
||||||
|
"name": "settings_user_id_users_id_fk",
|
||||||
|
"tableFrom": "settings",
|
||||||
|
"tableTo": "users",
|
||||||
|
"columnsFrom": [
|
||||||
|
"user_id"
|
||||||
|
],
|
||||||
|
"columnsTo": [
|
||||||
|
"id"
|
||||||
|
],
|
||||||
|
"onDelete": "no action",
|
||||||
|
"onUpdate": "no action"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"compositePrimaryKeys": {
|
||||||
|
"settings_user_id_key_pk": {
|
||||||
|
"name": "settings_user_id_key_pk",
|
||||||
|
"columns": [
|
||||||
|
"user_id",
|
||||||
|
"key"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"uniqueConstraints": {},
|
||||||
|
"policies": {},
|
||||||
|
"checkConstraints": {},
|
||||||
|
"isRLSEnabled": false
|
||||||
|
},
|
||||||
|
"public.setup_items": {
|
||||||
|
"name": "setup_items",
|
||||||
|
"schema": "",
|
||||||
|
"columns": {
|
||||||
|
"id": {
|
||||||
|
"name": "id",
|
||||||
|
"type": "serial",
|
||||||
|
"primaryKey": true,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"setup_id": {
|
||||||
|
"name": "setup_id",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"item_id": {
|
||||||
|
"name": "item_id",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"classification": {
|
||||||
|
"name": "classification",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"default": "'base'"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {},
|
||||||
|
"foreignKeys": {
|
||||||
|
"setup_items_setup_id_setups_id_fk": {
|
||||||
|
"name": "setup_items_setup_id_setups_id_fk",
|
||||||
|
"tableFrom": "setup_items",
|
||||||
|
"tableTo": "setups",
|
||||||
|
"columnsFrom": [
|
||||||
|
"setup_id"
|
||||||
|
],
|
||||||
|
"columnsTo": [
|
||||||
|
"id"
|
||||||
|
],
|
||||||
|
"onDelete": "cascade",
|
||||||
|
"onUpdate": "no action"
|
||||||
|
},
|
||||||
|
"setup_items_item_id_items_id_fk": {
|
||||||
|
"name": "setup_items_item_id_items_id_fk",
|
||||||
|
"tableFrom": "setup_items",
|
||||||
|
"tableTo": "items",
|
||||||
|
"columnsFrom": [
|
||||||
|
"item_id"
|
||||||
|
],
|
||||||
|
"columnsTo": [
|
||||||
|
"id"
|
||||||
|
],
|
||||||
|
"onDelete": "cascade",
|
||||||
|
"onUpdate": "no action"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"compositePrimaryKeys": {},
|
||||||
|
"uniqueConstraints": {},
|
||||||
|
"policies": {},
|
||||||
|
"checkConstraints": {},
|
||||||
|
"isRLSEnabled": false
|
||||||
|
},
|
||||||
|
"public.setups": {
|
||||||
|
"name": "setups",
|
||||||
|
"schema": "",
|
||||||
|
"columns": {
|
||||||
|
"id": {
|
||||||
|
"name": "id",
|
||||||
|
"type": "serial",
|
||||||
|
"primaryKey": true,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"name": {
|
||||||
|
"name": "name",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"user_id": {
|
||||||
|
"name": "user_id",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"created_at": {
|
||||||
|
"name": "created_at",
|
||||||
|
"type": "timestamp",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"default": "now()"
|
||||||
|
},
|
||||||
|
"updated_at": {
|
||||||
|
"name": "updated_at",
|
||||||
|
"type": "timestamp",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"default": "now()"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {},
|
||||||
|
"foreignKeys": {
|
||||||
|
"setups_user_id_users_id_fk": {
|
||||||
|
"name": "setups_user_id_users_id_fk",
|
||||||
|
"tableFrom": "setups",
|
||||||
|
"tableTo": "users",
|
||||||
|
"columnsFrom": [
|
||||||
|
"user_id"
|
||||||
|
],
|
||||||
|
"columnsTo": [
|
||||||
|
"id"
|
||||||
|
],
|
||||||
|
"onDelete": "no action",
|
||||||
|
"onUpdate": "no action"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"compositePrimaryKeys": {},
|
||||||
|
"uniqueConstraints": {},
|
||||||
|
"policies": {},
|
||||||
|
"checkConstraints": {},
|
||||||
|
"isRLSEnabled": false
|
||||||
|
},
|
||||||
|
"public.thread_candidates": {
|
||||||
|
"name": "thread_candidates",
|
||||||
|
"schema": "",
|
||||||
|
"columns": {
|
||||||
|
"id": {
|
||||||
|
"name": "id",
|
||||||
|
"type": "serial",
|
||||||
|
"primaryKey": true,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"thread_id": {
|
||||||
|
"name": "thread_id",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"name": {
|
||||||
|
"name": "name",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"weight_grams": {
|
||||||
|
"name": "weight_grams",
|
||||||
|
"type": "double precision",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"price_cents": {
|
||||||
|
"name": "price_cents",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"category_id": {
|
||||||
|
"name": "category_id",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"notes": {
|
||||||
|
"name": "notes",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"product_url": {
|
||||||
|
"name": "product_url",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"image_filename": {
|
||||||
|
"name": "image_filename",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"image_source_url": {
|
||||||
|
"name": "image_source_url",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"status": {
|
||||||
|
"name": "status",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"default": "'researching'"
|
||||||
|
},
|
||||||
|
"pros": {
|
||||||
|
"name": "pros",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"cons": {
|
||||||
|
"name": "cons",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"sort_order": {
|
||||||
|
"name": "sort_order",
|
||||||
|
"type": "double precision",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"default": 0
|
||||||
|
},
|
||||||
|
"created_at": {
|
||||||
|
"name": "created_at",
|
||||||
|
"type": "timestamp",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"default": "now()"
|
||||||
|
},
|
||||||
|
"updated_at": {
|
||||||
|
"name": "updated_at",
|
||||||
|
"type": "timestamp",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"default": "now()"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {},
|
||||||
|
"foreignKeys": {
|
||||||
|
"thread_candidates_thread_id_threads_id_fk": {
|
||||||
|
"name": "thread_candidates_thread_id_threads_id_fk",
|
||||||
|
"tableFrom": "thread_candidates",
|
||||||
|
"tableTo": "threads",
|
||||||
|
"columnsFrom": [
|
||||||
|
"thread_id"
|
||||||
|
],
|
||||||
|
"columnsTo": [
|
||||||
|
"id"
|
||||||
|
],
|
||||||
|
"onDelete": "cascade",
|
||||||
|
"onUpdate": "no action"
|
||||||
|
},
|
||||||
|
"thread_candidates_category_id_categories_id_fk": {
|
||||||
|
"name": "thread_candidates_category_id_categories_id_fk",
|
||||||
|
"tableFrom": "thread_candidates",
|
||||||
|
"tableTo": "categories",
|
||||||
|
"columnsFrom": [
|
||||||
|
"category_id"
|
||||||
|
],
|
||||||
|
"columnsTo": [
|
||||||
|
"id"
|
||||||
|
],
|
||||||
|
"onDelete": "no action",
|
||||||
|
"onUpdate": "no action"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"compositePrimaryKeys": {},
|
||||||
|
"uniqueConstraints": {},
|
||||||
|
"policies": {},
|
||||||
|
"checkConstraints": {},
|
||||||
|
"isRLSEnabled": false
|
||||||
|
},
|
||||||
|
"public.threads": {
|
||||||
|
"name": "threads",
|
||||||
|
"schema": "",
|
||||||
|
"columns": {
|
||||||
|
"id": {
|
||||||
|
"name": "id",
|
||||||
|
"type": "serial",
|
||||||
|
"primaryKey": true,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"name": {
|
||||||
|
"name": "name",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"status": {
|
||||||
|
"name": "status",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"default": "'active'"
|
||||||
|
},
|
||||||
|
"resolved_candidate_id": {
|
||||||
|
"name": "resolved_candidate_id",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"category_id": {
|
||||||
|
"name": "category_id",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"user_id": {
|
||||||
|
"name": "user_id",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"created_at": {
|
||||||
|
"name": "created_at",
|
||||||
|
"type": "timestamp",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"default": "now()"
|
||||||
|
},
|
||||||
|
"updated_at": {
|
||||||
|
"name": "updated_at",
|
||||||
|
"type": "timestamp",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"default": "now()"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {},
|
||||||
|
"foreignKeys": {
|
||||||
|
"threads_category_id_categories_id_fk": {
|
||||||
|
"name": "threads_category_id_categories_id_fk",
|
||||||
|
"tableFrom": "threads",
|
||||||
|
"tableTo": "categories",
|
||||||
|
"columnsFrom": [
|
||||||
|
"category_id"
|
||||||
|
],
|
||||||
|
"columnsTo": [
|
||||||
|
"id"
|
||||||
|
],
|
||||||
|
"onDelete": "no action",
|
||||||
|
"onUpdate": "no action"
|
||||||
|
},
|
||||||
|
"threads_user_id_users_id_fk": {
|
||||||
|
"name": "threads_user_id_users_id_fk",
|
||||||
|
"tableFrom": "threads",
|
||||||
|
"tableTo": "users",
|
||||||
|
"columnsFrom": [
|
||||||
|
"user_id"
|
||||||
|
],
|
||||||
|
"columnsTo": [
|
||||||
|
"id"
|
||||||
|
],
|
||||||
|
"onDelete": "no action",
|
||||||
|
"onUpdate": "no action"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"compositePrimaryKeys": {},
|
||||||
|
"uniqueConstraints": {},
|
||||||
|
"policies": {},
|
||||||
|
"checkConstraints": {},
|
||||||
|
"isRLSEnabled": false
|
||||||
|
},
|
||||||
|
"public.users": {
|
||||||
|
"name": "users",
|
||||||
|
"schema": "",
|
||||||
|
"columns": {
|
||||||
|
"id": {
|
||||||
|
"name": "id",
|
||||||
|
"type": "serial",
|
||||||
|
"primaryKey": true,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"logto_sub": {
|
||||||
|
"name": "logto_sub",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"created_at": {
|
||||||
|
"name": "created_at",
|
||||||
|
"type": "timestamp",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"default": "now()"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {},
|
||||||
|
"foreignKeys": {},
|
||||||
|
"compositePrimaryKeys": {},
|
||||||
|
"uniqueConstraints": {
|
||||||
|
"users_logto_sub_unique": {
|
||||||
|
"name": "users_logto_sub_unique",
|
||||||
|
"nullsNotDistinct": false,
|
||||||
|
"columns": [
|
||||||
|
"logto_sub"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"policies": {},
|
||||||
|
"checkConstraints": {},
|
||||||
|
"isRLSEnabled": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"enums": {},
|
||||||
|
"schemas": {},
|
||||||
|
"sequences": {},
|
||||||
|
"roles": {},
|
||||||
|
"policies": {},
|
||||||
|
"views": {},
|
||||||
|
"_meta": {
|
||||||
|
"columns": {},
|
||||||
|
"schemas": {},
|
||||||
|
"tables": {}
|
||||||
|
}
|
||||||
|
}
|
||||||
13
drizzle-pg/meta/_journal.json
Normal file
13
drizzle-pg/meta/_journal.json
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
{
|
||||||
|
"version": "7",
|
||||||
|
"dialect": "postgresql",
|
||||||
|
"entries": [
|
||||||
|
{
|
||||||
|
"idx": 0,
|
||||||
|
"version": "7",
|
||||||
|
"when": 1775377947759,
|
||||||
|
"tag": "0000_thankful_loners",
|
||||||
|
"breakpoints": true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
193
src/db/schema.ts
193
src/db/schema.ts
@@ -1,58 +1,86 @@
|
|||||||
import { integer, real, sqliteTable, text } from "drizzle-orm/sqlite-core";
|
import {
|
||||||
|
doublePrecision,
|
||||||
|
integer,
|
||||||
|
pgTable,
|
||||||
|
primaryKey,
|
||||||
|
serial,
|
||||||
|
text,
|
||||||
|
timestamp,
|
||||||
|
unique,
|
||||||
|
} from "drizzle-orm/pg-core";
|
||||||
|
|
||||||
export const categories = sqliteTable("categories", {
|
// ── Users ───────────────────────────────────────────────────────────
|
||||||
id: integer("id").primaryKey({ autoIncrement: true }),
|
|
||||||
name: text("name").notNull().unique(),
|
export const users = pgTable("users", {
|
||||||
icon: text("icon").notNull().default("package"),
|
id: serial("id").primaryKey(),
|
||||||
createdAt: integer("created_at", { mode: "timestamp" })
|
logtoSub: text("logto_sub").notNull().unique(),
|
||||||
.notNull()
|
createdAt: timestamp("created_at").defaultNow().notNull(),
|
||||||
.$defaultFn(() => new Date()),
|
|
||||||
});
|
});
|
||||||
|
|
||||||
export const items = sqliteTable("items", {
|
// ── Categories ──────────────────────────────────────────────────────
|
||||||
id: integer("id").primaryKey({ autoIncrement: true }),
|
|
||||||
|
export const categories = pgTable(
|
||||||
|
"categories",
|
||||||
|
{
|
||||||
|
id: serial("id").primaryKey(),
|
||||||
|
name: text("name").notNull(),
|
||||||
|
icon: text("icon").notNull().default("package"),
|
||||||
|
userId: integer("user_id")
|
||||||
|
.notNull()
|
||||||
|
.references(() => users.id),
|
||||||
|
createdAt: timestamp("created_at").defaultNow().notNull(),
|
||||||
|
},
|
||||||
|
(table) => [unique().on(table.userId, table.name)],
|
||||||
|
);
|
||||||
|
|
||||||
|
// ── Items ───────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
export const items = pgTable("items", {
|
||||||
|
id: serial("id").primaryKey(),
|
||||||
name: text("name").notNull(),
|
name: text("name").notNull(),
|
||||||
weightGrams: real("weight_grams"),
|
weightGrams: doublePrecision("weight_grams"),
|
||||||
priceCents: integer("price_cents"),
|
priceCents: integer("price_cents"),
|
||||||
categoryId: integer("category_id")
|
categoryId: integer("category_id")
|
||||||
.notNull()
|
.notNull()
|
||||||
.references(() => categories.id),
|
.references(() => categories.id),
|
||||||
|
userId: integer("user_id")
|
||||||
|
.notNull()
|
||||||
|
.references(() => users.id),
|
||||||
notes: text("notes"),
|
notes: text("notes"),
|
||||||
productUrl: text("product_url"),
|
productUrl: text("product_url"),
|
||||||
imageFilename: text("image_filename"),
|
imageFilename: text("image_filename"),
|
||||||
imageSourceUrl: text("image_source_url"),
|
imageSourceUrl: text("image_source_url"),
|
||||||
quantity: integer("quantity").notNull().default(1),
|
quantity: integer("quantity").notNull().default(1),
|
||||||
createdAt: integer("created_at", { mode: "timestamp" })
|
createdAt: timestamp("created_at").defaultNow().notNull(),
|
||||||
.notNull()
|
updatedAt: timestamp("updated_at").defaultNow().notNull(),
|
||||||
.$defaultFn(() => new Date()),
|
|
||||||
updatedAt: integer("updated_at", { mode: "timestamp" })
|
|
||||||
.notNull()
|
|
||||||
.$defaultFn(() => new Date()),
|
|
||||||
});
|
});
|
||||||
|
|
||||||
export const threads = sqliteTable("threads", {
|
// ── Threads ─────────────────────────────────────────────────────────
|
||||||
id: integer("id").primaryKey({ autoIncrement: true }),
|
|
||||||
|
export const threads = pgTable("threads", {
|
||||||
|
id: serial("id").primaryKey(),
|
||||||
name: text("name").notNull(),
|
name: text("name").notNull(),
|
||||||
status: text("status").notNull().default("active"),
|
status: text("status").notNull().default("active"),
|
||||||
resolvedCandidateId: integer("resolved_candidate_id"),
|
resolvedCandidateId: integer("resolved_candidate_id"),
|
||||||
categoryId: integer("category_id")
|
categoryId: integer("category_id")
|
||||||
.notNull()
|
.notNull()
|
||||||
.references(() => categories.id),
|
.references(() => categories.id),
|
||||||
createdAt: integer("created_at", { mode: "timestamp" })
|
userId: integer("user_id")
|
||||||
.notNull()
|
.notNull()
|
||||||
.$defaultFn(() => new Date()),
|
.references(() => users.id),
|
||||||
updatedAt: integer("updated_at", { mode: "timestamp" })
|
createdAt: timestamp("created_at").defaultNow().notNull(),
|
||||||
.notNull()
|
updatedAt: timestamp("updated_at").defaultNow().notNull(),
|
||||||
.$defaultFn(() => new Date()),
|
|
||||||
});
|
});
|
||||||
|
|
||||||
export const threadCandidates = sqliteTable("thread_candidates", {
|
// ── Thread Candidates ───────────────────────────────────────────────
|
||||||
id: integer("id").primaryKey({ autoIncrement: true }),
|
|
||||||
|
export const threadCandidates = pgTable("thread_candidates", {
|
||||||
|
id: serial("id").primaryKey(),
|
||||||
threadId: integer("thread_id")
|
threadId: integer("thread_id")
|
||||||
.notNull()
|
.notNull()
|
||||||
.references(() => threads.id, { onDelete: "cascade" }),
|
.references(() => threads.id, { onDelete: "cascade" }),
|
||||||
name: text("name").notNull(),
|
name: text("name").notNull(),
|
||||||
weightGrams: real("weight_grams"),
|
weightGrams: doublePrecision("weight_grams"),
|
||||||
priceCents: integer("price_cents"),
|
priceCents: integer("price_cents"),
|
||||||
categoryId: integer("category_id")
|
categoryId: integer("category_id")
|
||||||
.notNull()
|
.notNull()
|
||||||
@@ -64,28 +92,27 @@ export const threadCandidates = sqliteTable("thread_candidates", {
|
|||||||
status: text("status").notNull().default("researching"),
|
status: text("status").notNull().default("researching"),
|
||||||
pros: text("pros"),
|
pros: text("pros"),
|
||||||
cons: text("cons"),
|
cons: text("cons"),
|
||||||
sortOrder: real("sort_order").notNull().default(0),
|
sortOrder: doublePrecision("sort_order").notNull().default(0),
|
||||||
createdAt: integer("created_at", { mode: "timestamp" })
|
createdAt: timestamp("created_at").defaultNow().notNull(),
|
||||||
.notNull()
|
updatedAt: timestamp("updated_at").defaultNow().notNull(),
|
||||||
.$defaultFn(() => new Date()),
|
|
||||||
updatedAt: integer("updated_at", { mode: "timestamp" })
|
|
||||||
.notNull()
|
|
||||||
.$defaultFn(() => new Date()),
|
|
||||||
});
|
});
|
||||||
|
|
||||||
export const setups = sqliteTable("setups", {
|
// ── Setups ──────────────────────────────────────────────────────────
|
||||||
id: integer("id").primaryKey({ autoIncrement: true }),
|
|
||||||
|
export const setups = pgTable("setups", {
|
||||||
|
id: serial("id").primaryKey(),
|
||||||
name: text("name").notNull(),
|
name: text("name").notNull(),
|
||||||
createdAt: integer("created_at", { mode: "timestamp" })
|
userId: integer("user_id")
|
||||||
.notNull()
|
.notNull()
|
||||||
.$defaultFn(() => new Date()),
|
.references(() => users.id),
|
||||||
updatedAt: integer("updated_at", { mode: "timestamp" })
|
createdAt: timestamp("created_at").defaultNow().notNull(),
|
||||||
.notNull()
|
updatedAt: timestamp("updated_at").defaultNow().notNull(),
|
||||||
.$defaultFn(() => new Date()),
|
|
||||||
});
|
});
|
||||||
|
|
||||||
export const setupItems = sqliteTable("setup_items", {
|
// ── Setup Items ─────────────────────────────────────────────────────
|
||||||
id: integer("id").primaryKey({ autoIncrement: true }),
|
|
||||||
|
export const setupItems = pgTable("setup_items", {
|
||||||
|
id: serial("id").primaryKey(),
|
||||||
setupId: integer("setup_id")
|
setupId: integer("setup_id")
|
||||||
.notNull()
|
.notNull()
|
||||||
.references(() => setups.id, { onDelete: "cascade" }),
|
.references(() => setups.id, { onDelete: "cascade" }),
|
||||||
@@ -95,69 +122,69 @@ export const setupItems = sqliteTable("setup_items", {
|
|||||||
classification: text("classification").notNull().default("base"),
|
classification: text("classification").notNull().default("base"),
|
||||||
});
|
});
|
||||||
|
|
||||||
export const settings = sqliteTable("settings", {
|
// ── Settings ────────────────────────────────────────────────────────
|
||||||
key: text("key").primaryKey(),
|
|
||||||
value: text("value").notNull(),
|
|
||||||
});
|
|
||||||
|
|
||||||
export const users = sqliteTable("users", {
|
export const settings = pgTable(
|
||||||
id: integer("id").primaryKey({ autoIncrement: true }),
|
"settings",
|
||||||
username: text("username").notNull().unique(),
|
{
|
||||||
passwordHash: text("password_hash").notNull(),
|
userId: integer("user_id")
|
||||||
createdAt: integer("created_at", { mode: "timestamp" })
|
.notNull()
|
||||||
.notNull()
|
.references(() => users.id),
|
||||||
.$defaultFn(() => new Date()),
|
key: text("key").notNull(),
|
||||||
});
|
value: text("value").notNull(),
|
||||||
|
},
|
||||||
|
(table) => [primaryKey({ columns: [table.userId, table.key] })],
|
||||||
|
);
|
||||||
|
|
||||||
export const sessions = sqliteTable("sessions", {
|
// ── API Keys ────────────────────────────────────────────────────────
|
||||||
id: text("id").primaryKey(),
|
|
||||||
userId: integer("user_id")
|
|
||||||
.notNull()
|
|
||||||
.references(() => users.id, { onDelete: "cascade" }),
|
|
||||||
expiresAt: integer("expires_at", { mode: "timestamp" }).notNull(),
|
|
||||||
});
|
|
||||||
|
|
||||||
export const apiKeys = sqliteTable("api_keys", {
|
export const apiKeys = pgTable("api_keys", {
|
||||||
id: integer("id").primaryKey({ autoIncrement: true }),
|
id: serial("id").primaryKey(),
|
||||||
name: text("name").notNull(),
|
name: text("name").notNull(),
|
||||||
keyHash: text("key_hash").notNull(),
|
keyHash: text("key_hash").notNull(),
|
||||||
keyPrefix: text("key_prefix").notNull(),
|
keyPrefix: text("key_prefix").notNull(),
|
||||||
createdAt: integer("created_at", { mode: "timestamp" })
|
userId: integer("user_id")
|
||||||
.notNull()
|
.notNull()
|
||||||
.$defaultFn(() => new Date()),
|
.references(() => users.id),
|
||||||
|
createdAt: timestamp("created_at").defaultNow().notNull(),
|
||||||
});
|
});
|
||||||
|
|
||||||
export const oauthClients = sqliteTable("oauth_clients", {
|
// ── OAuth Clients ───────────────────────────────────────────────────
|
||||||
id: integer("id").primaryKey({ autoIncrement: true }),
|
|
||||||
|
export const oauthClients = pgTable("oauth_clients", {
|
||||||
|
id: serial("id").primaryKey(),
|
||||||
clientId: text("client_id").notNull().unique(),
|
clientId: text("client_id").notNull().unique(),
|
||||||
clientName: text("client_name"),
|
clientName: text("client_name"),
|
||||||
redirectUris: text("redirect_uris").notNull(), // JSON array
|
redirectUris: text("redirect_uris").notNull(), // JSON array
|
||||||
createdAt: integer("created_at", { mode: "timestamp" })
|
createdAt: timestamp("created_at").defaultNow().notNull(),
|
||||||
.notNull()
|
|
||||||
.$defaultFn(() => new Date()),
|
|
||||||
});
|
});
|
||||||
|
|
||||||
export const oauthCodes = sqliteTable("oauth_codes", {
|
// ── OAuth Authorization Codes ───────────────────────────────────────
|
||||||
id: integer("id").primaryKey({ autoIncrement: true }),
|
|
||||||
|
export const oauthCodes = pgTable("oauth_codes", {
|
||||||
|
id: serial("id").primaryKey(),
|
||||||
code: text("code").notNull().unique(),
|
code: text("code").notNull().unique(),
|
||||||
clientId: text("client_id").notNull(),
|
clientId: text("client_id").notNull(),
|
||||||
codeChallenge: text("code_challenge").notNull(),
|
codeChallenge: text("code_challenge").notNull(),
|
||||||
codeChallengeMethod: text("code_challenge_method").notNull().default("S256"),
|
codeChallengeMethod: text("code_challenge_method")
|
||||||
|
.notNull()
|
||||||
|
.default("S256"),
|
||||||
redirectUri: text("redirect_uri").notNull(),
|
redirectUri: text("redirect_uri").notNull(),
|
||||||
expiresAt: integer("expires_at", { mode: "timestamp" }).notNull(),
|
expiresAt: timestamp("expires_at").notNull(),
|
||||||
used: integer("used").notNull().default(0),
|
used: integer("used").notNull().default(0),
|
||||||
});
|
});
|
||||||
|
|
||||||
export const oauthTokens = sqliteTable("oauth_tokens", {
|
// ── OAuth Tokens ────────────────────────────────────────────────────
|
||||||
id: integer("id").primaryKey({ autoIncrement: true }),
|
|
||||||
|
export const oauthTokens = pgTable("oauth_tokens", {
|
||||||
|
id: serial("id").primaryKey(),
|
||||||
accessTokenHash: text("access_token_hash").notNull().unique(),
|
accessTokenHash: text("access_token_hash").notNull().unique(),
|
||||||
refreshTokenHash: text("refresh_token_hash").notNull().unique(),
|
refreshTokenHash: text("refresh_token_hash").notNull().unique(),
|
||||||
clientId: text("client_id").notNull(),
|
clientId: text("client_id").notNull(),
|
||||||
expiresAt: integer("expires_at", { mode: "timestamp" }).notNull(), // access token expiry
|
userId: integer("user_id")
|
||||||
refreshExpiresAt: integer("refresh_expires_at", {
|
|
||||||
mode: "timestamp",
|
|
||||||
}).notNull(), // refresh token expiry
|
|
||||||
createdAt: integer("created_at", { mode: "timestamp" })
|
|
||||||
.notNull()
|
.notNull()
|
||||||
.$defaultFn(() => new Date()),
|
.references(() => users.id),
|
||||||
|
expiresAt: timestamp("expires_at").notNull(),
|
||||||
|
refreshExpiresAt: timestamp("refresh_expires_at").notNull(),
|
||||||
|
createdAt: timestamp("created_at").defaultNow().notNull(),
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,14 +1,4 @@
|
|||||||
import { db } from "./index.ts";
|
|
||||||
import { categories } from "./schema.ts";
|
|
||||||
|
|
||||||
export function seedDefaults() {
|
export function seedDefaults() {
|
||||||
const existing = db.select().from(categories).all();
|
// Per-user default categories are created on first login (Phase 16)
|
||||||
if (existing.length === 0) {
|
// The getOrCreateUncategorized helper in category.service.ts handles this lazily.
|
||||||
db.insert(categories)
|
|
||||||
.values({
|
|
||||||
name: "Uncategorized",
|
|
||||||
icon: "package",
|
|
||||||
})
|
|
||||||
.run();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user