refactor: 降级 Drizzle ORM 至 0.45.x 稳定版,对齐 Better Auth 兼容性

- drizzle-orm 1.0.0-beta.15 → 0.45.2, drizzle-kit → 0.31.10
- RQBv2 defineRelations() → 旧版 relations() 回调语法
- drizzle-orm/zod → drizzle-zod 独立包
- auth/schema.ts 改由 Better Auth CLI 生成(bun run db:auth)
- db/schema/index.ts 选择性导出表(不导出生成文件中的旧版 relations)
- 删除 db:push script,强制 db:generate → db:migrate 工作流
- 重建迁移基线(删除旧迁移目录,全新生成初始迁移)
This commit is contained in:
2026-03-31 20:18:15 +08:00
parent 5e65c37a26
commit 6bedc1d60d
19 changed files with 879 additions and 1141 deletions
+20 -17
View File
@@ -8,7 +8,7 @@ TanStack Start fullstack web app with ORPC (contract-first RPC) and shadcn/ui.
- **Framework**: TanStack Start (React 19 SSR, file-based routing)
- **Styling**: Tailwind CSS v4 + shadcn/ui (base-nova style, `@base-ui/react`)
- **Database**: PostgreSQL + Drizzle ORM v1 beta (`drizzle-orm/postgres-js`, RQBv2)
- **Database**: PostgreSQL + Drizzle ORM 0.45.x (`drizzle-orm/postgres-js`)
- **State**: TanStack Query v5 (with MutationCache auto-invalidation)
- **RPC**: ORPC (contract-first, type-safe)
- **Auth**: Better Auth (email+password, single-owner, self-hosted)
@@ -99,7 +99,7 @@ src/
### 1. Define Contract (in module: `src/modules/feature/contract.ts`)
```typescript
import { oc } from '@orpc/contract'
import { createInsertSchema, createSelectSchema, createUpdateSchema } from 'drizzle-orm/zod'
import { createInsertSchema, createSelectSchema, createUpdateSchema } from 'drizzle-zod'
import { z } from 'zod'
import * as schema from '@/modules/feature/schema'
import { generatedFieldKeys } from '@/server/db/fields'
@@ -128,8 +128,8 @@ export const myResource = {
.use(dbMiddleware).use(authMiddleware)
.handler(async ({ context }) => {
return await context.db.query.myTable.findMany({
where: { userId: context.user.id },
orderBy: { createdAt: 'desc' },
where: (t, { eq }) => eq(t.userId, context.user.id),
orderBy: (t, { desc }) => desc(t.createdAt),
})
}),
@@ -244,14 +244,16 @@ toast.success('操作成功')
toast.error('操作失败')
```
## Database (Drizzle ORM v1 beta)
## Database (Drizzle ORM 0.45.x)
- **Driver**: `drizzle-orm/postgres-js` (NOT `bun-sql`)
- **Validation**: `drizzle-orm/zod` (built-in, NOT separate `drizzle-zod` package)
- **Relations**: `defineRelations()` in `src/server/db/relations.ts`RQBv2 object syntax
- **Validation**: `drizzle-zod` (separate package, NOT `drizzle-orm/zod`)
- **Relations**: `relations()` from `drizzle-orm` in `src/server/db/relations.ts`callback syntax
- **Table naming**: No `Table` suffix — `user`, `category`, `bookmark`
- **DB instance**: Module-level singleton `export const db = drizzle(...)` (NOT factory pattern)
- **DB instance**: Module-level singleton `export const db = drizzle(client, { schema })` (NOT factory pattern)
- **Shared fields**: Use `...generatedFields` spread for id/createdAt/updatedAt
- **Auth schema**: Generated by Better Auth CLI (`bun run db:auth`), **never hand-edit**
- **Schema re-export**: `db/schema/index.ts` selectively exports tables only (not relations) from auth schema
- **Migration workflow**: Always `db:generate``db:migrate`. **Never** use `db:push`.
- **Path alias exception**: Files in the Drizzle schema chain (`db/schema/index.ts`, module `schema.ts`) MUST use relative imports — `drizzle-kit` does not resolve `@/*` aliases.
@@ -263,11 +265,12 @@ export const myTable = pgTable('my_table', {
userId: text('user_id').notNull().references(() => user.id, { onDelete: 'cascade' }),
})
// Relations — RQBv2 defineRelations
export const relations = defineRelations(schema, (r) => ({
myTable: {
user: r.one.user({ from: r.myTable.userId, to: r.user.id }),
},
// Relations — callback syntax
export const myTableRelations = relations(myTable, ({ one }) => ({
user: one(user, {
fields: [myTable.userId],
references: [user.id],
}),
}))
```
@@ -293,8 +296,8 @@ Kairos is a self-hosted single-user app. There is NO public registration. The fi
- Use `@/*` path aliases (not relative imports)
- Use `render` prop (NOT `asChild`) for base-ui component delegation
- Use `ORPCError` with proper codes
- Use `drizzle-orm/zod` (NOT `drizzle-zod`)
- Use RQBv2 object syntax for `orderBy` and `where`
- Use `drizzle-zod` for schema validation (NOT `drizzle-orm/zod`)
- Use callback syntax for `orderBy` and `where` in relational queries
- Use `move()` from `@dnd-kit/helpers` for DnD reordering
- Use `useState` callback ref for virtualizer scroll elements inside Dialogs
@@ -302,9 +305,9 @@ Kairos is a self-hosted single-user app. There is NO public registration. The fi
- Add new `src/components/ui/*.tsx` without CLI (use `bunx shadcn@latest add` to scaffold, then freely customize)
- Edit `src/routeTree.gen.ts` (auto-generated)
- Use `asChild` prop (base-ui uses `render`, NOT Radix)
- Import from `drizzle-zod` (use `drizzle-orm/zod`)
- Import from `drizzle-orm/zod` (use `drizzle-zod`)
- Use `drizzle-orm/bun-sql` driver
- Pass `schema` to `drizzle()` constructor (only `relations` needed in RQBv2)
- Hand-edit `src/server/auth/schema.ts` (generated by Better Auth CLI, use `bun run db:auth`)
- Add `Table` suffix to Drizzle table exports
- Use `useRef` for scroll elements inside Dialog/conditional rendering
- Use `db:push` — always use `db:generate``db:migrate`
+80
View File
@@ -0,0 +1,80 @@
CREATE TABLE "account" (
"id" text PRIMARY KEY NOT NULL,
"account_id" text NOT NULL,
"provider_id" text NOT NULL,
"user_id" text NOT NULL,
"access_token" text,
"refresh_token" text,
"id_token" text,
"access_token_expires_at" timestamp,
"refresh_token_expires_at" timestamp,
"scope" text,
"password" text,
"created_at" timestamp DEFAULT now() NOT NULL,
"updated_at" timestamp NOT NULL
);
--> statement-breakpoint
CREATE TABLE "session" (
"id" text PRIMARY KEY NOT NULL,
"expires_at" timestamp NOT NULL,
"token" text NOT NULL,
"created_at" timestamp DEFAULT now() NOT NULL,
"updated_at" timestamp NOT NULL,
"ip_address" text,
"user_agent" text,
"user_id" text NOT NULL,
CONSTRAINT "session_token_unique" UNIQUE("token")
);
--> statement-breakpoint
CREATE TABLE "user" (
"id" text PRIMARY KEY NOT NULL,
"name" text NOT NULL,
"email" text NOT NULL,
"email_verified" boolean DEFAULT false NOT NULL,
"image" text,
"created_at" timestamp DEFAULT now() NOT NULL,
"updated_at" timestamp DEFAULT now() NOT NULL,
CONSTRAINT "user_email_unique" UNIQUE("email")
);
--> statement-breakpoint
CREATE TABLE "verification" (
"id" text PRIMARY KEY NOT NULL,
"identifier" text NOT NULL,
"value" text NOT NULL,
"expires_at" timestamp NOT NULL,
"created_at" timestamp DEFAULT now() NOT NULL,
"updated_at" timestamp DEFAULT now() NOT NULL
);
--> statement-breakpoint
CREATE TABLE "bookmark" (
"id" uuid PRIMARY KEY NOT NULL,
"created_at" timestamp with time zone DEFAULT now() NOT NULL,
"updated_at" timestamp with time zone DEFAULT now() NOT NULL,
"name" text NOT NULL,
"url" text NOT NULL,
"icon" text,
"category_id" uuid NOT NULL,
"is_public" boolean DEFAULT true NOT NULL,
"order_id" integer DEFAULT 0 NOT NULL,
"user_id" text NOT NULL
);
--> statement-breakpoint
CREATE TABLE "category" (
"id" uuid PRIMARY KEY NOT NULL,
"created_at" timestamp with time zone DEFAULT now() NOT NULL,
"updated_at" timestamp with time zone DEFAULT now() NOT NULL,
"name" text NOT NULL,
"is_pinned" boolean DEFAULT false NOT NULL,
"is_public" boolean DEFAULT true NOT NULL,
"order_id" integer DEFAULT 0 NOT NULL,
"user_id" text NOT NULL
);
--> statement-breakpoint
ALTER TABLE "account" ADD CONSTRAINT "account_user_id_user_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."user"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "session" ADD CONSTRAINT "session_user_id_user_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."user"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "bookmark" ADD CONSTRAINT "bookmark_category_id_category_id_fk" FOREIGN KEY ("category_id") REFERENCES "public"."category"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "bookmark" ADD CONSTRAINT "bookmark_user_id_user_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."user"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "category" ADD CONSTRAINT "category_user_id_user_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."user"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
CREATE INDEX "account_userId_idx" ON "account" USING btree ("user_id");--> statement-breakpoint
CREATE INDEX "session_userId_idx" ON "session" USING btree ("user_id");--> statement-breakpoint
CREATE INDEX "verification_identifier_idx" ON "verification" USING btree ("identifier");
@@ -1,75 +0,0 @@
CREATE TABLE "bookmark" (
"id" uuid PRIMARY KEY,
"created_at" timestamp with time zone DEFAULT now() NOT NULL,
"updated_at" timestamp with time zone DEFAULT now() NOT NULL,
"name" text NOT NULL,
"url" text NOT NULL,
"icon" text,
"category_id" uuid NOT NULL,
"is_public" boolean DEFAULT true NOT NULL,
"order_id" integer DEFAULT 0 NOT NULL,
"user_id" text NOT NULL
);
--> statement-breakpoint
CREATE TABLE "category" (
"id" uuid PRIMARY KEY,
"created_at" timestamp with time zone DEFAULT now() NOT NULL,
"updated_at" timestamp with time zone DEFAULT now() NOT NULL,
"name" text NOT NULL,
"is_pinned" boolean DEFAULT false NOT NULL,
"is_public" boolean DEFAULT true NOT NULL,
"order_id" integer DEFAULT 0 NOT NULL,
"user_id" text NOT NULL
);
--> statement-breakpoint
CREATE TABLE "account" (
"id" text PRIMARY KEY,
"account_id" text NOT NULL,
"provider_id" text NOT NULL,
"user_id" text NOT NULL,
"access_token" text,
"refresh_token" text,
"id_token" text,
"access_token_expires_at" timestamp with time zone,
"refresh_token_expires_at" timestamp with time zone,
"scope" text,
"password" text,
"created_at" timestamp with time zone DEFAULT now() NOT NULL,
"updated_at" timestamp with time zone DEFAULT now() NOT NULL
);
--> statement-breakpoint
CREATE TABLE "session" (
"id" text PRIMARY KEY,
"expires_at" timestamp with time zone NOT NULL,
"token" text NOT NULL UNIQUE,
"created_at" timestamp with time zone DEFAULT now() NOT NULL,
"updated_at" timestamp with time zone DEFAULT now() NOT NULL,
"ip_address" text,
"user_agent" text,
"user_id" text NOT NULL
);
--> statement-breakpoint
CREATE TABLE "user" (
"id" text PRIMARY KEY,
"name" text NOT NULL,
"email" text NOT NULL UNIQUE,
"email_verified" boolean DEFAULT false NOT NULL,
"image" text,
"created_at" timestamp with time zone DEFAULT now() NOT NULL,
"updated_at" timestamp with time zone DEFAULT now() NOT NULL
);
--> statement-breakpoint
CREATE TABLE "verification" (
"id" text PRIMARY KEY,
"identifier" text NOT NULL,
"value" text NOT NULL,
"expires_at" timestamp with time zone NOT NULL,
"created_at" timestamp with time zone DEFAULT now() NOT NULL,
"updated_at" timestamp with time zone DEFAULT now() NOT NULL
);
--> statement-breakpoint
ALTER TABLE "bookmark" ADD CONSTRAINT "bookmark_category_id_category_id_fkey" FOREIGN KEY ("category_id") REFERENCES "category"("id") ON DELETE CASCADE;--> statement-breakpoint
ALTER TABLE "bookmark" ADD CONSTRAINT "bookmark_user_id_user_id_fkey" FOREIGN KEY ("user_id") REFERENCES "user"("id") ON DELETE CASCADE;--> statement-breakpoint
ALTER TABLE "category" ADD CONSTRAINT "category_user_id_user_id_fkey" FOREIGN KEY ("user_id") REFERENCES "user"("id") ON DELETE CASCADE;--> statement-breakpoint
ALTER TABLE "account" ADD CONSTRAINT "account_user_id_user_id_fkey" FOREIGN KEY ("user_id") REFERENCES "user"("id") ON DELETE CASCADE;--> statement-breakpoint
ALTER TABLE "session" ADD CONSTRAINT "session_user_id_user_id_fkey" FOREIGN KEY ("user_id") REFERENCES "user"("id") ON DELETE CASCADE;
@@ -1,852 +0,0 @@
{
"version": "8",
"dialect": "postgres",
"id": "1521f139-4e9c-4d3f-965b-c9cbe21f80ff",
"prevIds": ["00000000-0000-0000-0000-000000000000"],
"ddl": [
{
"isRlsEnabled": false,
"name": "bookmark",
"entityType": "tables",
"schema": "public"
},
{
"isRlsEnabled": false,
"name": "category",
"entityType": "tables",
"schema": "public"
},
{
"isRlsEnabled": false,
"name": "account",
"entityType": "tables",
"schema": "public"
},
{
"isRlsEnabled": false,
"name": "session",
"entityType": "tables",
"schema": "public"
},
{
"isRlsEnabled": false,
"name": "user",
"entityType": "tables",
"schema": "public"
},
{
"isRlsEnabled": false,
"name": "verification",
"entityType": "tables",
"schema": "public"
},
{
"type": "uuid",
"typeSchema": null,
"notNull": true,
"dimensions": 0,
"default": null,
"generated": null,
"identity": null,
"name": "id",
"entityType": "columns",
"schema": "public",
"table": "bookmark"
},
{
"type": "timestamp with time zone",
"typeSchema": null,
"notNull": true,
"dimensions": 0,
"default": "now()",
"generated": null,
"identity": null,
"name": "created_at",
"entityType": "columns",
"schema": "public",
"table": "bookmark"
},
{
"type": "timestamp with time zone",
"typeSchema": null,
"notNull": true,
"dimensions": 0,
"default": "now()",
"generated": null,
"identity": null,
"name": "updated_at",
"entityType": "columns",
"schema": "public",
"table": "bookmark"
},
{
"type": "text",
"typeSchema": null,
"notNull": true,
"dimensions": 0,
"default": null,
"generated": null,
"identity": null,
"name": "name",
"entityType": "columns",
"schema": "public",
"table": "bookmark"
},
{
"type": "text",
"typeSchema": null,
"notNull": true,
"dimensions": 0,
"default": null,
"generated": null,
"identity": null,
"name": "url",
"entityType": "columns",
"schema": "public",
"table": "bookmark"
},
{
"type": "text",
"typeSchema": null,
"notNull": false,
"dimensions": 0,
"default": null,
"generated": null,
"identity": null,
"name": "icon",
"entityType": "columns",
"schema": "public",
"table": "bookmark"
},
{
"type": "uuid",
"typeSchema": null,
"notNull": true,
"dimensions": 0,
"default": null,
"generated": null,
"identity": null,
"name": "category_id",
"entityType": "columns",
"schema": "public",
"table": "bookmark"
},
{
"type": "boolean",
"typeSchema": null,
"notNull": true,
"dimensions": 0,
"default": "true",
"generated": null,
"identity": null,
"name": "is_public",
"entityType": "columns",
"schema": "public",
"table": "bookmark"
},
{
"type": "integer",
"typeSchema": null,
"notNull": true,
"dimensions": 0,
"default": "0",
"generated": null,
"identity": null,
"name": "order_id",
"entityType": "columns",
"schema": "public",
"table": "bookmark"
},
{
"type": "text",
"typeSchema": null,
"notNull": true,
"dimensions": 0,
"default": null,
"generated": null,
"identity": null,
"name": "user_id",
"entityType": "columns",
"schema": "public",
"table": "bookmark"
},
{
"type": "uuid",
"typeSchema": null,
"notNull": true,
"dimensions": 0,
"default": null,
"generated": null,
"identity": null,
"name": "id",
"entityType": "columns",
"schema": "public",
"table": "category"
},
{
"type": "timestamp with time zone",
"typeSchema": null,
"notNull": true,
"dimensions": 0,
"default": "now()",
"generated": null,
"identity": null,
"name": "created_at",
"entityType": "columns",
"schema": "public",
"table": "category"
},
{
"type": "timestamp with time zone",
"typeSchema": null,
"notNull": true,
"dimensions": 0,
"default": "now()",
"generated": null,
"identity": null,
"name": "updated_at",
"entityType": "columns",
"schema": "public",
"table": "category"
},
{
"type": "text",
"typeSchema": null,
"notNull": true,
"dimensions": 0,
"default": null,
"generated": null,
"identity": null,
"name": "name",
"entityType": "columns",
"schema": "public",
"table": "category"
},
{
"type": "boolean",
"typeSchema": null,
"notNull": true,
"dimensions": 0,
"default": "false",
"generated": null,
"identity": null,
"name": "is_pinned",
"entityType": "columns",
"schema": "public",
"table": "category"
},
{
"type": "boolean",
"typeSchema": null,
"notNull": true,
"dimensions": 0,
"default": "true",
"generated": null,
"identity": null,
"name": "is_public",
"entityType": "columns",
"schema": "public",
"table": "category"
},
{
"type": "integer",
"typeSchema": null,
"notNull": true,
"dimensions": 0,
"default": "0",
"generated": null,
"identity": null,
"name": "order_id",
"entityType": "columns",
"schema": "public",
"table": "category"
},
{
"type": "text",
"typeSchema": null,
"notNull": true,
"dimensions": 0,
"default": null,
"generated": null,
"identity": null,
"name": "user_id",
"entityType": "columns",
"schema": "public",
"table": "category"
},
{
"type": "text",
"typeSchema": null,
"notNull": true,
"dimensions": 0,
"default": null,
"generated": null,
"identity": null,
"name": "id",
"entityType": "columns",
"schema": "public",
"table": "account"
},
{
"type": "text",
"typeSchema": null,
"notNull": true,
"dimensions": 0,
"default": null,
"generated": null,
"identity": null,
"name": "account_id",
"entityType": "columns",
"schema": "public",
"table": "account"
},
{
"type": "text",
"typeSchema": null,
"notNull": true,
"dimensions": 0,
"default": null,
"generated": null,
"identity": null,
"name": "provider_id",
"entityType": "columns",
"schema": "public",
"table": "account"
},
{
"type": "text",
"typeSchema": null,
"notNull": true,
"dimensions": 0,
"default": null,
"generated": null,
"identity": null,
"name": "user_id",
"entityType": "columns",
"schema": "public",
"table": "account"
},
{
"type": "text",
"typeSchema": null,
"notNull": false,
"dimensions": 0,
"default": null,
"generated": null,
"identity": null,
"name": "access_token",
"entityType": "columns",
"schema": "public",
"table": "account"
},
{
"type": "text",
"typeSchema": null,
"notNull": false,
"dimensions": 0,
"default": null,
"generated": null,
"identity": null,
"name": "refresh_token",
"entityType": "columns",
"schema": "public",
"table": "account"
},
{
"type": "text",
"typeSchema": null,
"notNull": false,
"dimensions": 0,
"default": null,
"generated": null,
"identity": null,
"name": "id_token",
"entityType": "columns",
"schema": "public",
"table": "account"
},
{
"type": "timestamp with time zone",
"typeSchema": null,
"notNull": false,
"dimensions": 0,
"default": null,
"generated": null,
"identity": null,
"name": "access_token_expires_at",
"entityType": "columns",
"schema": "public",
"table": "account"
},
{
"type": "timestamp with time zone",
"typeSchema": null,
"notNull": false,
"dimensions": 0,
"default": null,
"generated": null,
"identity": null,
"name": "refresh_token_expires_at",
"entityType": "columns",
"schema": "public",
"table": "account"
},
{
"type": "text",
"typeSchema": null,
"notNull": false,
"dimensions": 0,
"default": null,
"generated": null,
"identity": null,
"name": "scope",
"entityType": "columns",
"schema": "public",
"table": "account"
},
{
"type": "text",
"typeSchema": null,
"notNull": false,
"dimensions": 0,
"default": null,
"generated": null,
"identity": null,
"name": "password",
"entityType": "columns",
"schema": "public",
"table": "account"
},
{
"type": "timestamp with time zone",
"typeSchema": null,
"notNull": true,
"dimensions": 0,
"default": "now()",
"generated": null,
"identity": null,
"name": "created_at",
"entityType": "columns",
"schema": "public",
"table": "account"
},
{
"type": "timestamp with time zone",
"typeSchema": null,
"notNull": true,
"dimensions": 0,
"default": "now()",
"generated": null,
"identity": null,
"name": "updated_at",
"entityType": "columns",
"schema": "public",
"table": "account"
},
{
"type": "text",
"typeSchema": null,
"notNull": true,
"dimensions": 0,
"default": null,
"generated": null,
"identity": null,
"name": "id",
"entityType": "columns",
"schema": "public",
"table": "session"
},
{
"type": "timestamp with time zone",
"typeSchema": null,
"notNull": true,
"dimensions": 0,
"default": null,
"generated": null,
"identity": null,
"name": "expires_at",
"entityType": "columns",
"schema": "public",
"table": "session"
},
{
"type": "text",
"typeSchema": null,
"notNull": true,
"dimensions": 0,
"default": null,
"generated": null,
"identity": null,
"name": "token",
"entityType": "columns",
"schema": "public",
"table": "session"
},
{
"type": "timestamp with time zone",
"typeSchema": null,
"notNull": true,
"dimensions": 0,
"default": "now()",
"generated": null,
"identity": null,
"name": "created_at",
"entityType": "columns",
"schema": "public",
"table": "session"
},
{
"type": "timestamp with time zone",
"typeSchema": null,
"notNull": true,
"dimensions": 0,
"default": "now()",
"generated": null,
"identity": null,
"name": "updated_at",
"entityType": "columns",
"schema": "public",
"table": "session"
},
{
"type": "text",
"typeSchema": null,
"notNull": false,
"dimensions": 0,
"default": null,
"generated": null,
"identity": null,
"name": "ip_address",
"entityType": "columns",
"schema": "public",
"table": "session"
},
{
"type": "text",
"typeSchema": null,
"notNull": false,
"dimensions": 0,
"default": null,
"generated": null,
"identity": null,
"name": "user_agent",
"entityType": "columns",
"schema": "public",
"table": "session"
},
{
"type": "text",
"typeSchema": null,
"notNull": true,
"dimensions": 0,
"default": null,
"generated": null,
"identity": null,
"name": "user_id",
"entityType": "columns",
"schema": "public",
"table": "session"
},
{
"type": "text",
"typeSchema": null,
"notNull": true,
"dimensions": 0,
"default": null,
"generated": null,
"identity": null,
"name": "id",
"entityType": "columns",
"schema": "public",
"table": "user"
},
{
"type": "text",
"typeSchema": null,
"notNull": true,
"dimensions": 0,
"default": null,
"generated": null,
"identity": null,
"name": "name",
"entityType": "columns",
"schema": "public",
"table": "user"
},
{
"type": "text",
"typeSchema": null,
"notNull": true,
"dimensions": 0,
"default": null,
"generated": null,
"identity": null,
"name": "email",
"entityType": "columns",
"schema": "public",
"table": "user"
},
{
"type": "boolean",
"typeSchema": null,
"notNull": true,
"dimensions": 0,
"default": "false",
"generated": null,
"identity": null,
"name": "email_verified",
"entityType": "columns",
"schema": "public",
"table": "user"
},
{
"type": "text",
"typeSchema": null,
"notNull": false,
"dimensions": 0,
"default": null,
"generated": null,
"identity": null,
"name": "image",
"entityType": "columns",
"schema": "public",
"table": "user"
},
{
"type": "timestamp with time zone",
"typeSchema": null,
"notNull": true,
"dimensions": 0,
"default": "now()",
"generated": null,
"identity": null,
"name": "created_at",
"entityType": "columns",
"schema": "public",
"table": "user"
},
{
"type": "timestamp with time zone",
"typeSchema": null,
"notNull": true,
"dimensions": 0,
"default": "now()",
"generated": null,
"identity": null,
"name": "updated_at",
"entityType": "columns",
"schema": "public",
"table": "user"
},
{
"type": "text",
"typeSchema": null,
"notNull": true,
"dimensions": 0,
"default": null,
"generated": null,
"identity": null,
"name": "id",
"entityType": "columns",
"schema": "public",
"table": "verification"
},
{
"type": "text",
"typeSchema": null,
"notNull": true,
"dimensions": 0,
"default": null,
"generated": null,
"identity": null,
"name": "identifier",
"entityType": "columns",
"schema": "public",
"table": "verification"
},
{
"type": "text",
"typeSchema": null,
"notNull": true,
"dimensions": 0,
"default": null,
"generated": null,
"identity": null,
"name": "value",
"entityType": "columns",
"schema": "public",
"table": "verification"
},
{
"type": "timestamp with time zone",
"typeSchema": null,
"notNull": true,
"dimensions": 0,
"default": null,
"generated": null,
"identity": null,
"name": "expires_at",
"entityType": "columns",
"schema": "public",
"table": "verification"
},
{
"type": "timestamp with time zone",
"typeSchema": null,
"notNull": true,
"dimensions": 0,
"default": "now()",
"generated": null,
"identity": null,
"name": "created_at",
"entityType": "columns",
"schema": "public",
"table": "verification"
},
{
"type": "timestamp with time zone",
"typeSchema": null,
"notNull": true,
"dimensions": 0,
"default": "now()",
"generated": null,
"identity": null,
"name": "updated_at",
"entityType": "columns",
"schema": "public",
"table": "verification"
},
{
"nameExplicit": false,
"columns": ["category_id"],
"schemaTo": "public",
"tableTo": "category",
"columnsTo": ["id"],
"onUpdate": "NO ACTION",
"onDelete": "CASCADE",
"name": "bookmark_category_id_category_id_fkey",
"entityType": "fks",
"schema": "public",
"table": "bookmark"
},
{
"nameExplicit": false,
"columns": ["user_id"],
"schemaTo": "public",
"tableTo": "user",
"columnsTo": ["id"],
"onUpdate": "NO ACTION",
"onDelete": "CASCADE",
"name": "bookmark_user_id_user_id_fkey",
"entityType": "fks",
"schema": "public",
"table": "bookmark"
},
{
"nameExplicit": false,
"columns": ["user_id"],
"schemaTo": "public",
"tableTo": "user",
"columnsTo": ["id"],
"onUpdate": "NO ACTION",
"onDelete": "CASCADE",
"name": "category_user_id_user_id_fkey",
"entityType": "fks",
"schema": "public",
"table": "category"
},
{
"nameExplicit": false,
"columns": ["user_id"],
"schemaTo": "public",
"tableTo": "user",
"columnsTo": ["id"],
"onUpdate": "NO ACTION",
"onDelete": "CASCADE",
"name": "account_user_id_user_id_fkey",
"entityType": "fks",
"schema": "public",
"table": "account"
},
{
"nameExplicit": false,
"columns": ["user_id"],
"schemaTo": "public",
"tableTo": "user",
"columnsTo": ["id"],
"onUpdate": "NO ACTION",
"onDelete": "CASCADE",
"name": "session_user_id_user_id_fkey",
"entityType": "fks",
"schema": "public",
"table": "session"
},
{
"columns": ["id"],
"nameExplicit": false,
"name": "bookmark_pkey",
"schema": "public",
"table": "bookmark",
"entityType": "pks"
},
{
"columns": ["id"],
"nameExplicit": false,
"name": "category_pkey",
"schema": "public",
"table": "category",
"entityType": "pks"
},
{
"columns": ["id"],
"nameExplicit": false,
"name": "account_pkey",
"schema": "public",
"table": "account",
"entityType": "pks"
},
{
"columns": ["id"],
"nameExplicit": false,
"name": "session_pkey",
"schema": "public",
"table": "session",
"entityType": "pks"
},
{
"columns": ["id"],
"nameExplicit": false,
"name": "user_pkey",
"schema": "public",
"table": "user",
"entityType": "pks"
},
{
"columns": ["id"],
"nameExplicit": false,
"name": "verification_pkey",
"schema": "public",
"table": "verification",
"entityType": "pks"
},
{
"nameExplicit": false,
"columns": ["token"],
"nullsNotDistinct": false,
"name": "session_token_key",
"schema": "public",
"table": "session",
"entityType": "uniques"
},
{
"nameExplicit": false,
"columns": ["email"],
"nullsNotDistinct": false,
"name": "user_email_key",
"schema": "public",
"table": "user",
"entityType": "uniques"
}
],
"renames": []
}
+558
View File
@@ -0,0 +1,558 @@
{
"id": "e52e0416-6f56-4223-9016-44087dd17d11",
"prevId": "00000000-0000-0000-0000-000000000000",
"version": "7",
"dialect": "postgresql",
"tables": {
"public.account": {
"name": "account",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "text",
"primaryKey": true,
"notNull": true
},
"account_id": {
"name": "account_id",
"type": "text",
"primaryKey": false,
"notNull": true
},
"provider_id": {
"name": "provider_id",
"type": "text",
"primaryKey": false,
"notNull": true
},
"user_id": {
"name": "user_id",
"type": "text",
"primaryKey": false,
"notNull": true
},
"access_token": {
"name": "access_token",
"type": "text",
"primaryKey": false,
"notNull": false
},
"refresh_token": {
"name": "refresh_token",
"type": "text",
"primaryKey": false,
"notNull": false
},
"id_token": {
"name": "id_token",
"type": "text",
"primaryKey": false,
"notNull": false
},
"access_token_expires_at": {
"name": "access_token_expires_at",
"type": "timestamp",
"primaryKey": false,
"notNull": false
},
"refresh_token_expires_at": {
"name": "refresh_token_expires_at",
"type": "timestamp",
"primaryKey": false,
"notNull": false
},
"scope": {
"name": "scope",
"type": "text",
"primaryKey": false,
"notNull": false
},
"password": {
"name": "password",
"type": "text",
"primaryKey": false,
"notNull": false
},
"created_at": {
"name": "created_at",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"updated_at": {
"name": "updated_at",
"type": "timestamp",
"primaryKey": false,
"notNull": true
}
},
"indexes": {
"account_userId_idx": {
"name": "account_userId_idx",
"columns": [
{
"expression": "user_id",
"isExpression": false,
"asc": true,
"nulls": "last"
}
],
"isUnique": false,
"concurrently": false,
"method": "btree",
"with": {}
}
},
"foreignKeys": {
"account_user_id_user_id_fk": {
"name": "account_user_id_user_id_fk",
"tableFrom": "account",
"tableTo": "user",
"columnsFrom": [
"user_id"
],
"columnsTo": [
"id"
],
"onDelete": "cascade",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {},
"uniqueConstraints": {},
"policies": {},
"checkConstraints": {},
"isRLSEnabled": false
},
"public.session": {
"name": "session",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "text",
"primaryKey": true,
"notNull": true
},
"expires_at": {
"name": "expires_at",
"type": "timestamp",
"primaryKey": false,
"notNull": true
},
"token": {
"name": "token",
"type": "text",
"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
},
"ip_address": {
"name": "ip_address",
"type": "text",
"primaryKey": false,
"notNull": false
},
"user_agent": {
"name": "user_agent",
"type": "text",
"primaryKey": false,
"notNull": false
},
"user_id": {
"name": "user_id",
"type": "text",
"primaryKey": false,
"notNull": true
}
},
"indexes": {
"session_userId_idx": {
"name": "session_userId_idx",
"columns": [
{
"expression": "user_id",
"isExpression": false,
"asc": true,
"nulls": "last"
}
],
"isUnique": false,
"concurrently": false,
"method": "btree",
"with": {}
}
},
"foreignKeys": {
"session_user_id_user_id_fk": {
"name": "session_user_id_user_id_fk",
"tableFrom": "session",
"tableTo": "user",
"columnsFrom": [
"user_id"
],
"columnsTo": [
"id"
],
"onDelete": "cascade",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {},
"uniqueConstraints": {
"session_token_unique": {
"name": "session_token_unique",
"nullsNotDistinct": false,
"columns": [
"token"
]
}
},
"policies": {},
"checkConstraints": {},
"isRLSEnabled": false
},
"public.user": {
"name": "user",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "text",
"primaryKey": true,
"notNull": true
},
"name": {
"name": "name",
"type": "text",
"primaryKey": false,
"notNull": true
},
"email": {
"name": "email",
"type": "text",
"primaryKey": false,
"notNull": true
},
"email_verified": {
"name": "email_verified",
"type": "boolean",
"primaryKey": false,
"notNull": true,
"default": false
},
"image": {
"name": "image",
"type": "text",
"primaryKey": false,
"notNull": false
},
"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": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {
"user_email_unique": {
"name": "user_email_unique",
"nullsNotDistinct": false,
"columns": [
"email"
]
}
},
"policies": {},
"checkConstraints": {},
"isRLSEnabled": false
},
"public.verification": {
"name": "verification",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "text",
"primaryKey": true,
"notNull": true
},
"identifier": {
"name": "identifier",
"type": "text",
"primaryKey": false,
"notNull": true
},
"value": {
"name": "value",
"type": "text",
"primaryKey": false,
"notNull": true
},
"expires_at": {
"name": "expires_at",
"type": "timestamp",
"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": {
"verification_identifier_idx": {
"name": "verification_identifier_idx",
"columns": [
{
"expression": "identifier",
"isExpression": false,
"asc": true,
"nulls": "last"
}
],
"isUnique": false,
"concurrently": false,
"method": "btree",
"with": {}
}
},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {},
"policies": {},
"checkConstraints": {},
"isRLSEnabled": false
},
"public.bookmark": {
"name": "bookmark",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "uuid",
"primaryKey": true,
"notNull": true
},
"created_at": {
"name": "created_at",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"updated_at": {
"name": "updated_at",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"name": {
"name": "name",
"type": "text",
"primaryKey": false,
"notNull": true
},
"url": {
"name": "url",
"type": "text",
"primaryKey": false,
"notNull": true
},
"icon": {
"name": "icon",
"type": "text",
"primaryKey": false,
"notNull": false
},
"category_id": {
"name": "category_id",
"type": "uuid",
"primaryKey": false,
"notNull": true
},
"is_public": {
"name": "is_public",
"type": "boolean",
"primaryKey": false,
"notNull": true,
"default": true
},
"order_id": {
"name": "order_id",
"type": "integer",
"primaryKey": false,
"notNull": true,
"default": 0
},
"user_id": {
"name": "user_id",
"type": "text",
"primaryKey": false,
"notNull": true
}
},
"indexes": {},
"foreignKeys": {
"bookmark_category_id_category_id_fk": {
"name": "bookmark_category_id_category_id_fk",
"tableFrom": "bookmark",
"tableTo": "category",
"columnsFrom": [
"category_id"
],
"columnsTo": [
"id"
],
"onDelete": "cascade",
"onUpdate": "no action"
},
"bookmark_user_id_user_id_fk": {
"name": "bookmark_user_id_user_id_fk",
"tableFrom": "bookmark",
"tableTo": "user",
"columnsFrom": [
"user_id"
],
"columnsTo": [
"id"
],
"onDelete": "cascade",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {},
"uniqueConstraints": {},
"policies": {},
"checkConstraints": {},
"isRLSEnabled": false
},
"public.category": {
"name": "category",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "uuid",
"primaryKey": true,
"notNull": true
},
"created_at": {
"name": "created_at",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"updated_at": {
"name": "updated_at",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"name": {
"name": "name",
"type": "text",
"primaryKey": false,
"notNull": true
},
"is_pinned": {
"name": "is_pinned",
"type": "boolean",
"primaryKey": false,
"notNull": true,
"default": false
},
"is_public": {
"name": "is_public",
"type": "boolean",
"primaryKey": false,
"notNull": true,
"default": true
},
"order_id": {
"name": "order_id",
"type": "integer",
"primaryKey": false,
"notNull": true,
"default": 0
},
"user_id": {
"name": "user_id",
"type": "text",
"primaryKey": false,
"notNull": true
}
},
"indexes": {},
"foreignKeys": {
"category_user_id_user_id_fk": {
"name": "category_user_id_user_id_fk",
"tableFrom": "category",
"tableTo": "user",
"columnsFrom": [
"user_id"
],
"columnsTo": [
"id"
],
"onDelete": "cascade",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {},
"uniqueConstraints": {},
"policies": {},
"checkConstraints": {},
"isRLSEnabled": false
}
},
"enums": {},
"schemas": {},
"sequences": {},
"roles": {},
"policies": {},
"views": {},
"_meta": {
"columns": {},
"schemas": {},
"tables": {}
}
}
+13
View File
@@ -0,0 +1,13 @@
{
"version": "7",
"dialect": "postgresql",
"entries": [
{
"idx": 0,
"version": "7",
"when": 1774958187626,
"tag": "0000_tricky_stick",
"breakpoints": true
}
]
}
+2 -1
View File
@@ -15,9 +15,9 @@
"compile:linux:x64": "bun compile.ts --target bun-linux-x64",
"compile:windows": "bun run compile:windows:x64",
"compile:windows:x64": "bun compile.ts --target bun-windows-x64",
"db:auth": "bunx auth@latest generate --output src/server/auth/schema.ts --yes",
"db:generate": "drizzle-kit generate",
"db:migrate": "drizzle-kit migrate",
"db:push": "drizzle-kit push",
"db:studio": "drizzle-kit studio",
"dev": "bunx --bun vite dev",
"fix": "biome check --write",
@@ -46,6 +46,7 @@
"class-variance-authority": "catalog:",
"clsx": "catalog:",
"drizzle-orm": "catalog:",
"drizzle-zod": "catalog:",
"lucide-react": "catalog:",
"next-themes": "catalog:",
"postgres": "catalog:",
+4 -1
View File
@@ -2,6 +2,7 @@ import { hashPassword } from 'better-auth/crypto'
import { defineCommand } from 'citty'
import { eq } from 'drizzle-orm'
import { drizzle } from 'drizzle-orm/postgres-js'
import postgres from 'postgres'
import * as authSchema from '@/server/auth/schema'
export const resetPassword = defineCommand({
@@ -23,7 +24,8 @@ export const resetPassword = defineCommand({
process.exit(1)
}
const db = drizzle({ connection: databaseUrl })
const client = postgres(databaseUrl)
const db = drizzle(client)
const owner = await db
.select({ id: authSchema.user.id, email: authSchema.user.email })
@@ -63,6 +65,7 @@ export const resetPassword = defineCommand({
await db.delete(authSchema.session).where(eq(authSchema.session.userId, owner[0].id))
console.log(`✓ 已重置 ${owner[0].email} 的密码,所有会话已失效`)
await client.end()
process.exit(0)
},
})
@@ -1,5 +1,5 @@
import { oc } from '@orpc/contract'
import { createInsertSchema, createSelectSchema, createUpdateSchema } from 'drizzle-orm/zod'
import { createInsertSchema, createSelectSchema, createUpdateSchema } from 'drizzle-zod'
import { z } from 'zod'
import * as schema from '@/modules/bookmarks/schema'
import { generatedFieldKeys } from '@/server/db/fields'
+3 -3
View File
@@ -10,11 +10,11 @@ export const category = {
.use(authMiddleware)
.handler(async ({ context }) => {
return await context.db.query.category.findMany({
where: { userId: context.user.id },
orderBy: { orderId: 'asc' },
where: (category, { eq }) => eq(category.userId, context.user.id),
orderBy: (category, { asc }) => asc(category.orderId),
with: {
bookmarks: {
orderBy: { orderId: 'asc' },
orderBy: (bookmark, { asc }) => asc(bookmark.orderId),
},
},
})
-21
View File
@@ -10,7 +10,6 @@
import { Route as rootRouteImport } from './routes/__root'
import { Route as SetupRouteImport } from './routes/setup'
import { Route as RecoverRouteImport } from './routes/recover'
import { Route as LoginRouteImport } from './routes/login'
import { Route as ProtectedRouteImport } from './routes/_protected'
import { Route as ProtectedIndexRouteImport } from './routes/_protected/index'
@@ -27,11 +26,6 @@ const SetupRoute = SetupRouteImport.update({
path: '/setup',
getParentRoute: () => rootRouteImport,
} as any)
const RecoverRoute = RecoverRouteImport.update({
id: '/recover',
path: '/recover',
getParentRoute: () => rootRouteImport,
} as any)
const LoginRoute = LoginRouteImport.update({
id: '/login',
path: '/login',
@@ -85,7 +79,6 @@ const ProtectedAdminBookmarksRoute = ProtectedAdminBookmarksRouteImport.update({
export interface FileRoutesByFullPath {
'/': typeof ProtectedIndexRoute
'/login': typeof LoginRoute
'/recover': typeof RecoverRoute
'/setup': typeof SetupRoute
'/admin': typeof ProtectedAdminRouteWithChildren
'/api/$': typeof ApiSplatRoute
@@ -97,7 +90,6 @@ export interface FileRoutesByFullPath {
}
export interface FileRoutesByTo {
'/login': typeof LoginRoute
'/recover': typeof RecoverRoute
'/setup': typeof SetupRoute
'/api/$': typeof ApiSplatRoute
'/api/health': typeof ApiHealthRoute
@@ -111,7 +103,6 @@ export interface FileRoutesById {
__root__: typeof rootRouteImport
'/_protected': typeof ProtectedRouteWithChildren
'/login': typeof LoginRoute
'/recover': typeof RecoverRoute
'/setup': typeof SetupRoute
'/_protected/admin': typeof ProtectedAdminRouteWithChildren
'/api/$': typeof ApiSplatRoute
@@ -127,7 +118,6 @@ export interface FileRouteTypes {
fullPaths:
| '/'
| '/login'
| '/recover'
| '/setup'
| '/admin'
| '/api/$'
@@ -139,7 +129,6 @@ export interface FileRouteTypes {
fileRoutesByTo: FileRoutesByTo
to:
| '/login'
| '/recover'
| '/setup'
| '/api/$'
| '/api/health'
@@ -152,7 +141,6 @@ export interface FileRouteTypes {
| '__root__'
| '/_protected'
| '/login'
| '/recover'
| '/setup'
| '/_protected/admin'
| '/api/$'
@@ -167,7 +155,6 @@ export interface FileRouteTypes {
export interface RootRouteChildren {
ProtectedRoute: typeof ProtectedRouteWithChildren
LoginRoute: typeof LoginRoute
RecoverRoute: typeof RecoverRoute
SetupRoute: typeof SetupRoute
ApiSplatRoute: typeof ApiSplatRoute
ApiHealthRoute: typeof ApiHealthRoute
@@ -184,13 +171,6 @@ declare module '@tanstack/react-router' {
preLoaderRoute: typeof SetupRouteImport
parentRoute: typeof rootRouteImport
}
'/recover': {
id: '/recover'
path: '/recover'
fullPath: '/recover'
preLoaderRoute: typeof RecoverRouteImport
parentRoute: typeof rootRouteImport
}
'/login': {
id: '/login'
path: '/login'
@@ -295,7 +275,6 @@ const ProtectedRouteWithChildren = ProtectedRoute._addFileChildren(
const rootRouteChildren: RootRouteChildren = {
ProtectedRoute: ProtectedRouteWithChildren,
LoginRoute: LoginRoute,
RecoverRoute: RecoverRoute,
SetupRoute: SetupRoute,
ApiSplatRoute: ApiSplatRoute,
ApiHealthRoute: ApiHealthRoute,
+88 -65
View File
@@ -1,70 +1,93 @@
import { boolean, pgTable, text, timestamp } from 'drizzle-orm/pg-core'
import { relations } from "drizzle-orm";
import { pgTable, text, timestamp, boolean, index } from "drizzle-orm/pg-core";
/**
* Better Auth 认证表
*
* 注意:所有 ID 使用 text 类型(Better Auth 自管 ID 生成),
* 不使用项目的 generatedFieldsUUID v7)。
*/
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()
export const user = pgTable("user", {
id: text("id").primaryKey(),
name: text("name").notNull(),
email: text("email").notNull().unique(),
emailVerified: boolean("email_verified").default(false).notNull(),
image: text("image"),
createdAt: timestamp("created_at").defaultNow().notNull(),
updatedAt: timestamp("updated_at")
.defaultNow()
.$onUpdateFn(() => new Date()),
})
.$onUpdate(() => /* @__PURE__ */ new Date())
.notNull(),
});
export const session = pgTable('session', {
id: text('id').primaryKey(),
expiresAt: timestamp('expires_at', { withTimezone: true }).notNull(),
token: text('token').notNull().unique(),
createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(),
updatedAt: timestamp('updated_at', { withTimezone: true })
.notNull()
.defaultNow()
.$onUpdateFn(() => new Date()),
ipAddress: text('ip_address'),
userAgent: text('user_agent'),
userId: text('user_id')
.notNull()
.references(() => user.id, { onDelete: 'cascade' }),
})
export const session = pgTable(
"session",
{
id: text("id").primaryKey(),
expiresAt: timestamp("expires_at").notNull(),
token: text("token").notNull().unique(),
createdAt: timestamp("created_at").defaultNow().notNull(),
updatedAt: timestamp("updated_at")
.$onUpdate(() => /* @__PURE__ */ new Date())
.notNull(),
ipAddress: text("ip_address"),
userAgent: text("user_agent"),
userId: text("user_id")
.notNull()
.references(() => user.id, { onDelete: "cascade" }),
},
(table) => [index("session_userId_idx").on(table.userId)],
);
export const account = pgTable('account', {
id: text('id').primaryKey(),
accountId: text('account_id').notNull(),
providerId: text('provider_id').notNull(),
userId: text('user_id')
.notNull()
.references(() => user.id, { onDelete: 'cascade' }),
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()
.$onUpdateFn(() => new Date()),
})
export const account = pgTable(
"account",
{
id: text("id").primaryKey(),
accountId: text("account_id").notNull(),
providerId: text("provider_id").notNull(),
userId: text("user_id")
.notNull()
.references(() => user.id, { onDelete: "cascade" }),
accessToken: text("access_token"),
refreshToken: text("refresh_token"),
idToken: text("id_token"),
accessTokenExpiresAt: timestamp("access_token_expires_at"),
refreshTokenExpiresAt: timestamp("refresh_token_expires_at"),
scope: text("scope"),
password: text("password"),
createdAt: timestamp("created_at").defaultNow().notNull(),
updatedAt: timestamp("updated_at")
.$onUpdate(() => /* @__PURE__ */ new Date())
.notNull(),
},
(table) => [index("account_userId_idx").on(table.userId)],
);
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 }).notNull().defaultNow(),
updatedAt: timestamp('updated_at', { withTimezone: true })
.notNull()
.defaultNow()
.$onUpdateFn(() => new Date()),
})
export const verification = pgTable(
"verification",
{
id: text("id").primaryKey(),
identifier: text("identifier").notNull(),
value: text("value").notNull(),
expiresAt: timestamp("expires_at").notNull(),
createdAt: timestamp("created_at").defaultNow().notNull(),
updatedAt: timestamp("updated_at")
.defaultNow()
.$onUpdate(() => /* @__PURE__ */ new Date())
.notNull(),
},
(table) => [index("verification_identifier_idx").on(table.identifier)],
);
export const userRelations = relations(user, ({ many }) => ({
sessions: many(session),
accounts: many(account),
}));
export const sessionRelations = relations(session, ({ one }) => ({
user: one(user, {
fields: [session.userId],
references: [user.id],
}),
}));
export const accountRelations = relations(account, ({ one }) => ({
user: one(user, {
fields: [account.userId],
references: [user.id],
}),
}));
+6 -5
View File
@@ -1,10 +1,11 @@
import { drizzle } from 'drizzle-orm/postgres-js'
import postgres from 'postgres'
import { env } from '@/env'
import { relations } from '@/server/db/relations'
import * as relations from '@/server/db/relations'
import * as schema from '@/server/db/schema'
export const db = drizzle({
connection: env.DATABASE_URL,
relations,
})
const client = postgres(env.DATABASE_URL)
export const db = drizzle(client, { schema: { ...schema, ...relations } })
export type DB = typeof db
+25 -24
View File
@@ -1,26 +1,27 @@
import { defineRelations } from 'drizzle-orm'
import * as schema from './schema'
import { relations } from 'drizzle-orm'
import { bookmark, category } from '../../modules/bookmarks/schema'
import { user } from '../auth/schema'
export const relations = defineRelations(schema, (r) => ({
user: {
categories: r.many.category(),
bookmarks: r.many.bookmark(),
},
category: {
user: r.one.user({
from: r.category.userId,
to: r.user.id,
}),
bookmarks: r.many.bookmark(),
},
bookmark: {
user: r.one.user({
from: r.bookmark.userId,
to: r.user.id,
}),
category: r.one.category({
from: r.bookmark.categoryId,
to: r.category.id,
}),
},
export const userRelations = relations(user, ({ many }) => ({
categories: many(category),
bookmarks: many(bookmark),
}))
export const categoryRelations = relations(category, ({ one, many }) => ({
user: one(user, {
fields: [category.userId],
references: [user.id],
}),
bookmarks: many(bookmark),
}))
export const bookmarkRelations = relations(bookmark, ({ one }) => ({
user: one(user, {
fields: [bookmark.userId],
references: [user.id],
}),
category: one(category, {
fields: [bookmark.categoryId],
references: [category.id],
}),
}))
+1 -1
View File
@@ -1,2 +1,2 @@
export * from '../../../modules/bookmarks/schema'
export * from '../../auth/schema'
export { account, session, user, verification } from '../../auth/schema'