Browse Source
Transition the database from PostgreSQL to SQLite, updating migration files, database connection settings, and schema definitions accordingly. Adjust package dependencies to reflect the new database type and ensure compatibility. Remove obsolete migration files and snapshots related to PostgreSQL. Enhance error handling in authentication and configuration services to accommodate the new database structure.feat/auth-access-control
25 changed files with 436 additions and 737 deletions
@ -1,10 +1,22 @@ |
|||
import { drizzle } from 'drizzle-orm/libsql'; |
|||
import path from 'path'; |
|||
|
|||
if (process.env.NODE_ENV === 'production') { |
|||
// 打包时需要保证migrator被引入
|
|||
import('drizzle-orm/better-sqlite3/migrator') |
|||
} |
|||
|
|||
const tempCwd = path.resolve(process.cwd(), 'packages/drizzle-pkg'); |
|||
|
|||
let dbUrl = process.env.DATABASE_URL; |
|||
if (dbUrl && dbUrl.startsWith('file:')) { |
|||
let filePath = dbUrl.slice(5); |
|||
if (!path.isAbsolute(filePath)) { |
|||
filePath = path.resolve(tempCwd, filePath); |
|||
process.env.DATABASE_URL = 'file:' + filePath; |
|||
} |
|||
} |
|||
|
|||
const _db = drizzle(process.env.DATABASE_URL!); |
|||
|
|||
export { _db as dbGlobal } |
|||
@ -0,0 +1,28 @@ |
|||
import { index, integer, sqliteTable, text } from "drizzle-orm/sqlite-core"; |
|||
|
|||
export const users = sqliteTable("users", { |
|||
id: integer().primaryKey(), |
|||
username: text().notNull().unique(), |
|||
email: text(), |
|||
nickname: text(), |
|||
password: text().notNull(), |
|||
avatar: text(), |
|||
createdAt: integer("created_at", { mode: "timestamp_ms" }).defaultNow().notNull(), |
|||
updatedAt: integer("updated_at", { mode: "timestamp_ms" }) |
|||
.defaultNow() |
|||
.$onUpdate(() => new Date()) |
|||
.notNull(), |
|||
}); |
|||
|
|||
export const sessions = sqliteTable( |
|||
"sessions", |
|||
{ |
|||
id: text().primaryKey(), |
|||
userId: integer("user_id") |
|||
.notNull() |
|||
.references(() => users.id, { onDelete: "cascade" }), |
|||
expiresAt: integer("expires_at", { mode: "timestamp_ms" }).notNull(), |
|||
createdAt: integer("created_at", { mode: "timestamp_ms" }).defaultNow().notNull(), |
|||
}, |
|||
(table) => [index("sessions_user_id_idx").on(table.userId)], |
|||
); |
|||
@ -0,0 +1,35 @@ |
|||
import { index, integer, primaryKey, sqliteTable, text } from "drizzle-orm/sqlite-core"; |
|||
import { users } from "./auth"; |
|||
|
|||
export const appConfigs = sqliteTable("app_configs", { |
|||
key: text().primaryKey(), |
|||
value: text().notNull(), |
|||
valueType: text("value_type").notNull(), |
|||
updatedAt: integer("updated_at", { mode: "timestamp_ms" }) |
|||
.defaultNow() |
|||
.$onUpdate(() => new Date()) |
|||
.notNull(), |
|||
}); |
|||
|
|||
export const userConfigs = sqliteTable( |
|||
"user_configs", |
|||
{ |
|||
userId: integer("user_id") |
|||
.notNull() |
|||
.references(() => users.id, { onDelete: "cascade" }), |
|||
key: text().notNull(), |
|||
value: text().notNull(), |
|||
valueType: text("value_type").notNull(), |
|||
updatedAt: integer("updated_at", { mode: "timestamp_ms" }) |
|||
.defaultNow() |
|||
.$onUpdate(() => new Date()) |
|||
.notNull(), |
|||
}, |
|||
(table) => [ |
|||
primaryKey({ |
|||
name: "user_configs_user_id_key_pk", |
|||
columns: [table.userId, table.key], |
|||
}), |
|||
index("user_configs_user_id_idx").on(table.userId), |
|||
], |
|||
); |
|||
Binary file not shown.
@ -1 +1 @@ |
|||
export { dbGlobal } from '../database/pg/db' |
|||
export { dbGlobal } from '../database/sqlite/db' |
|||
@ -1 +1 @@ |
|||
export { users, sessions } from '../../database/pg/schema/auth' |
|||
export { users, sessions } from '../../database/sqlite/schema/auth' |
|||
@ -1 +1 @@ |
|||
export { appConfigs, userConfigs } from "../../database/pg/schema/config"; |
|||
export { appConfigs, userConfigs } from "../../database/sqlite/schema/config"; |
|||
|
|||
@ -1,11 +1,39 @@ |
|||
CREATE TABLE "users" ( |
|||
"id" integer PRIMARY KEY NOT NULL, |
|||
"username" varchar NOT NULL, |
|||
"email" varchar, |
|||
"nickname" varchar, |
|||
"password" varchar NOT NULL, |
|||
"avatar" varchar, |
|||
"created_at" timestamp DEFAULT now() NOT NULL, |
|||
"updated_at" timestamp DEFAULT now() NOT NULL, |
|||
CONSTRAINT "users_username_unique" UNIQUE("username") |
|||
CREATE TABLE `sessions` ( |
|||
`id` text PRIMARY KEY NOT NULL, |
|||
`user_id` integer NOT NULL, |
|||
`expires_at` integer NOT NULL, |
|||
`created_at` integer DEFAULT (cast((julianday('now') - 2440587.5)*86400000 as integer)) NOT NULL, |
|||
FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON UPDATE no action ON DELETE cascade |
|||
); |
|||
--> statement-breakpoint |
|||
CREATE INDEX `sessions_user_id_idx` ON `sessions` (`user_id`);--> statement-breakpoint |
|||
CREATE TABLE `users` ( |
|||
`id` integer PRIMARY KEY NOT NULL, |
|||
`username` text NOT NULL, |
|||
`email` text, |
|||
`nickname` text, |
|||
`password` text NOT NULL, |
|||
`avatar` text, |
|||
`created_at` integer DEFAULT (cast((julianday('now') - 2440587.5)*86400000 as integer)) NOT NULL, |
|||
`updated_at` integer DEFAULT (cast((julianday('now') - 2440587.5)*86400000 as integer)) NOT NULL |
|||
); |
|||
--> statement-breakpoint |
|||
CREATE UNIQUE INDEX `users_username_unique` ON `users` (`username`);--> statement-breakpoint |
|||
CREATE TABLE `app_configs` ( |
|||
`key` text PRIMARY KEY NOT NULL, |
|||
`value` text NOT NULL, |
|||
`value_type` text NOT NULL, |
|||
`updated_at` integer DEFAULT (cast((julianday('now') - 2440587.5)*86400000 as integer)) NOT NULL |
|||
); |
|||
--> statement-breakpoint |
|||
CREATE TABLE `user_configs` ( |
|||
`user_id` integer NOT NULL, |
|||
`key` text NOT NULL, |
|||
`value` text NOT NULL, |
|||
`value_type` text NOT NULL, |
|||
`updated_at` integer DEFAULT (cast((julianday('now') - 2440587.5)*86400000 as integer)) NOT NULL, |
|||
PRIMARY KEY(`user_id`, `key`), |
|||
FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON UPDATE no action ON DELETE cascade |
|||
); |
|||
--> statement-breakpoint |
|||
CREATE INDEX `user_configs_user_id_idx` ON `user_configs` (`user_id`); |
|||
@ -1,8 +0,0 @@ |
|||
CREATE TABLE "sessions" ( |
|||
"id" varchar PRIMARY KEY NOT NULL, |
|||
"user_id" integer NOT NULL, |
|||
"expires_at" timestamp NOT NULL, |
|||
"created_at" timestamp DEFAULT now() NOT NULL |
|||
); |
|||
--> statement-breakpoint |
|||
ALTER TABLE "sessions" ADD CONSTRAINT "sessions_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE cascade ON UPDATE no action; |
|||
@ -1,2 +0,0 @@ |
|||
ALTER TABLE "sessions" ALTER COLUMN "expires_at" SET DATA TYPE timestamp with time zone;--> statement-breakpoint |
|||
CREATE INDEX "sessions_user_id_idx" ON "sessions" USING btree ("user_id"); |
|||
@ -1,18 +0,0 @@ |
|||
CREATE TABLE "app_configs" ( |
|||
"key" varchar PRIMARY KEY NOT NULL, |
|||
"value" varchar NOT NULL, |
|||
"value_type" varchar NOT NULL, |
|||
"updated_at" timestamp DEFAULT now() NOT NULL |
|||
); |
|||
--> statement-breakpoint |
|||
CREATE TABLE "user_configs" ( |
|||
"user_id" integer NOT NULL, |
|||
"key" varchar NOT NULL, |
|||
"value" varchar NOT NULL, |
|||
"value_type" varchar NOT NULL, |
|||
"updated_at" timestamp DEFAULT now() NOT NULL, |
|||
CONSTRAINT "user_configs_user_id_key_pk" PRIMARY KEY("user_id","key") |
|||
); |
|||
--> statement-breakpoint |
|||
ALTER TABLE "user_configs" ADD CONSTRAINT "user_configs_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint |
|||
CREATE INDEX "user_configs_user_id_idx" ON "user_configs" USING btree ("user_id"); |
|||
@ -1,90 +1,270 @@ |
|||
{ |
|||
"id": "25840823-aa2a-4e32-a6b6-70bb1e27348e", |
|||
"version": "6", |
|||
"dialect": "sqlite", |
|||
"id": "0c82e00e-7d76-4479-a196-ceb6eb02ad3f", |
|||
"prevId": "00000000-0000-0000-0000-000000000000", |
|||
"version": "7", |
|||
"dialect": "postgresql", |
|||
"tables": { |
|||
"public.users": { |
|||
"sessions": { |
|||
"name": "sessions", |
|||
"columns": { |
|||
"id": { |
|||
"name": "id", |
|||
"type": "text", |
|||
"primaryKey": true, |
|||
"notNull": true, |
|||
"autoincrement": false |
|||
}, |
|||
"user_id": { |
|||
"name": "user_id", |
|||
"type": "integer", |
|||
"primaryKey": false, |
|||
"notNull": true, |
|||
"autoincrement": false |
|||
}, |
|||
"expires_at": { |
|||
"name": "expires_at", |
|||
"type": "integer", |
|||
"primaryKey": false, |
|||
"notNull": true, |
|||
"autoincrement": false |
|||
}, |
|||
"created_at": { |
|||
"name": "created_at", |
|||
"type": "integer", |
|||
"primaryKey": false, |
|||
"notNull": true, |
|||
"autoincrement": false, |
|||
"default": "(cast((julianday('now') - 2440587.5)*86400000 as integer))" |
|||
} |
|||
}, |
|||
"indexes": { |
|||
"sessions_user_id_idx": { |
|||
"name": "sessions_user_id_idx", |
|||
"columns": [ |
|||
"user_id" |
|||
], |
|||
"isUnique": false |
|||
} |
|||
}, |
|||
"foreignKeys": { |
|||
"sessions_user_id_users_id_fk": { |
|||
"name": "sessions_user_id_users_id_fk", |
|||
"tableFrom": "sessions", |
|||
"tableTo": "users", |
|||
"columnsFrom": [ |
|||
"user_id" |
|||
], |
|||
"columnsTo": [ |
|||
"id" |
|||
], |
|||
"onDelete": "cascade", |
|||
"onUpdate": "no action" |
|||
} |
|||
}, |
|||
"compositePrimaryKeys": {}, |
|||
"uniqueConstraints": {}, |
|||
"checkConstraints": {} |
|||
}, |
|||
"users": { |
|||
"name": "users", |
|||
"schema": "", |
|||
"columns": { |
|||
"id": { |
|||
"name": "id", |
|||
"type": "integer", |
|||
"primaryKey": true, |
|||
"notNull": true |
|||
"notNull": true, |
|||
"autoincrement": false |
|||
}, |
|||
"username": { |
|||
"name": "username", |
|||
"type": "varchar", |
|||
"type": "text", |
|||
"primaryKey": false, |
|||
"notNull": true |
|||
"notNull": true, |
|||
"autoincrement": false |
|||
}, |
|||
"email": { |
|||
"name": "email", |
|||
"type": "varchar", |
|||
"type": "text", |
|||
"primaryKey": false, |
|||
"notNull": false |
|||
"notNull": false, |
|||
"autoincrement": false |
|||
}, |
|||
"nickname": { |
|||
"name": "nickname", |
|||
"type": "varchar", |
|||
"type": "text", |
|||
"primaryKey": false, |
|||
"notNull": false |
|||
"notNull": false, |
|||
"autoincrement": false |
|||
}, |
|||
"password": { |
|||
"name": "password", |
|||
"type": "varchar", |
|||
"type": "text", |
|||
"primaryKey": false, |
|||
"notNull": true |
|||
"notNull": true, |
|||
"autoincrement": false |
|||
}, |
|||
"avatar": { |
|||
"name": "avatar", |
|||
"type": "varchar", |
|||
"type": "text", |
|||
"primaryKey": false, |
|||
"notNull": false |
|||
"notNull": false, |
|||
"autoincrement": false |
|||
}, |
|||
"created_at": { |
|||
"name": "created_at", |
|||
"type": "timestamp", |
|||
"type": "integer", |
|||
"primaryKey": false, |
|||
"notNull": true, |
|||
"default": "now()" |
|||
"autoincrement": false, |
|||
"default": "(cast((julianday('now') - 2440587.5)*86400000 as integer))" |
|||
}, |
|||
"updated_at": { |
|||
"name": "updated_at", |
|||
"type": "timestamp", |
|||
"type": "integer", |
|||
"primaryKey": false, |
|||
"notNull": true, |
|||
"default": "now()" |
|||
"autoincrement": false, |
|||
"default": "(cast((julianday('now') - 2440587.5)*86400000 as integer))" |
|||
} |
|||
}, |
|||
"indexes": {}, |
|||
"foreignKeys": {}, |
|||
"compositePrimaryKeys": {}, |
|||
"uniqueConstraints": { |
|||
"indexes": { |
|||
"users_username_unique": { |
|||
"name": "users_username_unique", |
|||
"nullsNotDistinct": false, |
|||
"columns": [ |
|||
"username" |
|||
] |
|||
], |
|||
"isUnique": true |
|||
} |
|||
}, |
|||
"policies": {}, |
|||
"checkConstraints": {}, |
|||
"isRLSEnabled": false |
|||
"foreignKeys": {}, |
|||
"compositePrimaryKeys": {}, |
|||
"uniqueConstraints": {}, |
|||
"checkConstraints": {} |
|||
}, |
|||
"app_configs": { |
|||
"name": "app_configs", |
|||
"columns": { |
|||
"key": { |
|||
"name": "key", |
|||
"type": "text", |
|||
"primaryKey": true, |
|||
"notNull": true, |
|||
"autoincrement": false |
|||
}, |
|||
"value": { |
|||
"name": "value", |
|||
"type": "text", |
|||
"primaryKey": false, |
|||
"notNull": true, |
|||
"autoincrement": false |
|||
}, |
|||
"value_type": { |
|||
"name": "value_type", |
|||
"type": "text", |
|||
"primaryKey": false, |
|||
"notNull": true, |
|||
"autoincrement": false |
|||
}, |
|||
"updated_at": { |
|||
"name": "updated_at", |
|||
"type": "integer", |
|||
"primaryKey": false, |
|||
"notNull": true, |
|||
"autoincrement": false, |
|||
"default": "(cast((julianday('now') - 2440587.5)*86400000 as integer))" |
|||
} |
|||
}, |
|||
"indexes": {}, |
|||
"foreignKeys": {}, |
|||
"compositePrimaryKeys": {}, |
|||
"uniqueConstraints": {}, |
|||
"checkConstraints": {} |
|||
}, |
|||
"user_configs": { |
|||
"name": "user_configs", |
|||
"columns": { |
|||
"user_id": { |
|||
"name": "user_id", |
|||
"type": "integer", |
|||
"primaryKey": false, |
|||
"notNull": true, |
|||
"autoincrement": false |
|||
}, |
|||
"key": { |
|||
"name": "key", |
|||
"type": "text", |
|||
"primaryKey": false, |
|||
"notNull": true, |
|||
"autoincrement": false |
|||
}, |
|||
"value": { |
|||
"name": "value", |
|||
"type": "text", |
|||
"primaryKey": false, |
|||
"notNull": true, |
|||
"autoincrement": false |
|||
}, |
|||
"value_type": { |
|||
"name": "value_type", |
|||
"type": "text", |
|||
"primaryKey": false, |
|||
"notNull": true, |
|||
"autoincrement": false |
|||
}, |
|||
"updated_at": { |
|||
"name": "updated_at", |
|||
"type": "integer", |
|||
"primaryKey": false, |
|||
"notNull": true, |
|||
"autoincrement": false, |
|||
"default": "(cast((julianday('now') - 2440587.5)*86400000 as integer))" |
|||
} |
|||
}, |
|||
"indexes": { |
|||
"user_configs_user_id_idx": { |
|||
"name": "user_configs_user_id_idx", |
|||
"columns": [ |
|||
"user_id" |
|||
], |
|||
"isUnique": false |
|||
} |
|||
}, |
|||
"foreignKeys": { |
|||
"user_configs_user_id_users_id_fk": { |
|||
"name": "user_configs_user_id_users_id_fk", |
|||
"tableFrom": "user_configs", |
|||
"tableTo": "users", |
|||
"columnsFrom": [ |
|||
"user_id" |
|||
], |
|||
"columnsTo": [ |
|||
"id" |
|||
], |
|||
"onDelete": "cascade", |
|||
"onUpdate": "no action" |
|||
} |
|||
}, |
|||
"compositePrimaryKeys": { |
|||
"user_configs_user_id_key_pk": { |
|||
"columns": [ |
|||
"user_id", |
|||
"key" |
|||
], |
|||
"name": "user_configs_user_id_key_pk" |
|||
} |
|||
}, |
|||
"uniqueConstraints": {}, |
|||
"checkConstraints": {} |
|||
} |
|||
}, |
|||
"enums": {}, |
|||
"schemas": {}, |
|||
"sequences": {}, |
|||
"roles": {}, |
|||
"policies": {}, |
|||
"views": {}, |
|||
"enums": {}, |
|||
"_meta": { |
|||
"columns": {}, |
|||
"schemas": {}, |
|||
"tables": {} |
|||
"tables": {}, |
|||
"columns": {} |
|||
}, |
|||
"internal": { |
|||
"indexes": {} |
|||
} |
|||
} |
|||
@ -1,142 +0,0 @@ |
|||
{ |
|||
"id": "b2828e1e-04fb-495c-9a25-233d062ee460", |
|||
"prevId": "25840823-aa2a-4e32-a6b6-70bb1e27348e", |
|||
"version": "7", |
|||
"dialect": "postgresql", |
|||
"tables": { |
|||
"public.sessions": { |
|||
"name": "sessions", |
|||
"schema": "", |
|||
"columns": { |
|||
"id": { |
|||
"name": "id", |
|||
"type": "varchar", |
|||
"primaryKey": true, |
|||
"notNull": true |
|||
}, |
|||
"user_id": { |
|||
"name": "user_id", |
|||
"type": "integer", |
|||
"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()" |
|||
} |
|||
}, |
|||
"indexes": {}, |
|||
"foreignKeys": { |
|||
"sessions_user_id_users_id_fk": { |
|||
"name": "sessions_user_id_users_id_fk", |
|||
"tableFrom": "sessions", |
|||
"tableTo": "users", |
|||
"columnsFrom": [ |
|||
"user_id" |
|||
], |
|||
"columnsTo": [ |
|||
"id" |
|||
], |
|||
"onDelete": "cascade", |
|||
"onUpdate": "no action" |
|||
} |
|||
}, |
|||
"compositePrimaryKeys": {}, |
|||
"uniqueConstraints": {}, |
|||
"policies": {}, |
|||
"checkConstraints": {}, |
|||
"isRLSEnabled": false |
|||
}, |
|||
"public.users": { |
|||
"name": "users", |
|||
"schema": "", |
|||
"columns": { |
|||
"id": { |
|||
"name": "id", |
|||
"type": "integer", |
|||
"primaryKey": true, |
|||
"notNull": true |
|||
}, |
|||
"username": { |
|||
"name": "username", |
|||
"type": "varchar", |
|||
"primaryKey": false, |
|||
"notNull": true |
|||
}, |
|||
"email": { |
|||
"name": "email", |
|||
"type": "varchar", |
|||
"primaryKey": false, |
|||
"notNull": false |
|||
}, |
|||
"nickname": { |
|||
"name": "nickname", |
|||
"type": "varchar", |
|||
"primaryKey": false, |
|||
"notNull": false |
|||
}, |
|||
"password": { |
|||
"name": "password", |
|||
"type": "varchar", |
|||
"primaryKey": false, |
|||
"notNull": true |
|||
}, |
|||
"avatar": { |
|||
"name": "avatar", |
|||
"type": "varchar", |
|||
"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": { |
|||
"users_username_unique": { |
|||
"name": "users_username_unique", |
|||
"nullsNotDistinct": false, |
|||
"columns": [ |
|||
"username" |
|||
] |
|||
} |
|||
}, |
|||
"policies": {}, |
|||
"checkConstraints": {}, |
|||
"isRLSEnabled": false |
|||
} |
|||
}, |
|||
"enums": {}, |
|||
"schemas": {}, |
|||
"sequences": {}, |
|||
"roles": {}, |
|||
"policies": {}, |
|||
"views": {}, |
|||
"_meta": { |
|||
"columns": {}, |
|||
"schemas": {}, |
|||
"tables": {} |
|||
} |
|||
} |
|||
@ -1,158 +0,0 @@ |
|||
{ |
|||
"id": "3caf12bf-ffef-4a49-b1ed-9af7f01551f4", |
|||
"prevId": "b2828e1e-04fb-495c-9a25-233d062ee460", |
|||
"version": "7", |
|||
"dialect": "postgresql", |
|||
"tables": { |
|||
"public.sessions": { |
|||
"name": "sessions", |
|||
"schema": "", |
|||
"columns": { |
|||
"id": { |
|||
"name": "id", |
|||
"type": "varchar", |
|||
"primaryKey": true, |
|||
"notNull": true |
|||
}, |
|||
"user_id": { |
|||
"name": "user_id", |
|||
"type": "integer", |
|||
"primaryKey": false, |
|||
"notNull": true |
|||
}, |
|||
"expires_at": { |
|||
"name": "expires_at", |
|||
"type": "timestamp with time zone", |
|||
"primaryKey": false, |
|||
"notNull": true |
|||
}, |
|||
"created_at": { |
|||
"name": "created_at", |
|||
"type": "timestamp", |
|||
"primaryKey": false, |
|||
"notNull": true, |
|||
"default": "now()" |
|||
} |
|||
}, |
|||
"indexes": { |
|||
"sessions_user_id_idx": { |
|||
"name": "sessions_user_id_idx", |
|||
"columns": [ |
|||
{ |
|||
"expression": "user_id", |
|||
"isExpression": false, |
|||
"asc": true, |
|||
"nulls": "last" |
|||
} |
|||
], |
|||
"isUnique": false, |
|||
"concurrently": false, |
|||
"method": "btree", |
|||
"with": {} |
|||
} |
|||
}, |
|||
"foreignKeys": { |
|||
"sessions_user_id_users_id_fk": { |
|||
"name": "sessions_user_id_users_id_fk", |
|||
"tableFrom": "sessions", |
|||
"tableTo": "users", |
|||
"columnsFrom": [ |
|||
"user_id" |
|||
], |
|||
"columnsTo": [ |
|||
"id" |
|||
], |
|||
"onDelete": "cascade", |
|||
"onUpdate": "no action" |
|||
} |
|||
}, |
|||
"compositePrimaryKeys": {}, |
|||
"uniqueConstraints": {}, |
|||
"policies": {}, |
|||
"checkConstraints": {}, |
|||
"isRLSEnabled": false |
|||
}, |
|||
"public.users": { |
|||
"name": "users", |
|||
"schema": "", |
|||
"columns": { |
|||
"id": { |
|||
"name": "id", |
|||
"type": "integer", |
|||
"primaryKey": true, |
|||
"notNull": true |
|||
}, |
|||
"username": { |
|||
"name": "username", |
|||
"type": "varchar", |
|||
"primaryKey": false, |
|||
"notNull": true |
|||
}, |
|||
"email": { |
|||
"name": "email", |
|||
"type": "varchar", |
|||
"primaryKey": false, |
|||
"notNull": false |
|||
}, |
|||
"nickname": { |
|||
"name": "nickname", |
|||
"type": "varchar", |
|||
"primaryKey": false, |
|||
"notNull": false |
|||
}, |
|||
"password": { |
|||
"name": "password", |
|||
"type": "varchar", |
|||
"primaryKey": false, |
|||
"notNull": true |
|||
}, |
|||
"avatar": { |
|||
"name": "avatar", |
|||
"type": "varchar", |
|||
"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": { |
|||
"users_username_unique": { |
|||
"name": "users_username_unique", |
|||
"nullsNotDistinct": false, |
|||
"columns": [ |
|||
"username" |
|||
] |
|||
} |
|||
}, |
|||
"policies": {}, |
|||
"checkConstraints": {}, |
|||
"isRLSEnabled": false |
|||
} |
|||
}, |
|||
"enums": {}, |
|||
"schemas": {}, |
|||
"sequences": {}, |
|||
"roles": {}, |
|||
"policies": {}, |
|||
"views": {}, |
|||
"_meta": { |
|||
"columns": {}, |
|||
"schemas": {}, |
|||
"tables": {} |
|||
} |
|||
} |
|||
@ -1,278 +0,0 @@ |
|||
{ |
|||
"id": "5ad5cea4-52fb-4e68-813a-f1b125bcba51", |
|||
"prevId": "3caf12bf-ffef-4a49-b1ed-9af7f01551f4", |
|||
"version": "7", |
|||
"dialect": "postgresql", |
|||
"tables": { |
|||
"public.sessions": { |
|||
"name": "sessions", |
|||
"schema": "", |
|||
"columns": { |
|||
"id": { |
|||
"name": "id", |
|||
"type": "varchar", |
|||
"primaryKey": true, |
|||
"notNull": true |
|||
}, |
|||
"user_id": { |
|||
"name": "user_id", |
|||
"type": "integer", |
|||
"primaryKey": false, |
|||
"notNull": true |
|||
}, |
|||
"expires_at": { |
|||
"name": "expires_at", |
|||
"type": "timestamp with time zone", |
|||
"primaryKey": false, |
|||
"notNull": true |
|||
}, |
|||
"created_at": { |
|||
"name": "created_at", |
|||
"type": "timestamp", |
|||
"primaryKey": false, |
|||
"notNull": true, |
|||
"default": "now()" |
|||
} |
|||
}, |
|||
"indexes": { |
|||
"sessions_user_id_idx": { |
|||
"name": "sessions_user_id_idx", |
|||
"columns": [ |
|||
{ |
|||
"expression": "user_id", |
|||
"isExpression": false, |
|||
"asc": true, |
|||
"nulls": "last" |
|||
} |
|||
], |
|||
"isUnique": false, |
|||
"concurrently": false, |
|||
"method": "btree", |
|||
"with": {} |
|||
} |
|||
}, |
|||
"foreignKeys": { |
|||
"sessions_user_id_users_id_fk": { |
|||
"name": "sessions_user_id_users_id_fk", |
|||
"tableFrom": "sessions", |
|||
"tableTo": "users", |
|||
"columnsFrom": [ |
|||
"user_id" |
|||
], |
|||
"columnsTo": [ |
|||
"id" |
|||
], |
|||
"onDelete": "cascade", |
|||
"onUpdate": "no action" |
|||
} |
|||
}, |
|||
"compositePrimaryKeys": {}, |
|||
"uniqueConstraints": {}, |
|||
"policies": {}, |
|||
"checkConstraints": {}, |
|||
"isRLSEnabled": false |
|||
}, |
|||
"public.users": { |
|||
"name": "users", |
|||
"schema": "", |
|||
"columns": { |
|||
"id": { |
|||
"name": "id", |
|||
"type": "integer", |
|||
"primaryKey": true, |
|||
"notNull": true |
|||
}, |
|||
"username": { |
|||
"name": "username", |
|||
"type": "varchar", |
|||
"primaryKey": false, |
|||
"notNull": true |
|||
}, |
|||
"email": { |
|||
"name": "email", |
|||
"type": "varchar", |
|||
"primaryKey": false, |
|||
"notNull": false |
|||
}, |
|||
"nickname": { |
|||
"name": "nickname", |
|||
"type": "varchar", |
|||
"primaryKey": false, |
|||
"notNull": false |
|||
}, |
|||
"password": { |
|||
"name": "password", |
|||
"type": "varchar", |
|||
"primaryKey": false, |
|||
"notNull": true |
|||
}, |
|||
"avatar": { |
|||
"name": "avatar", |
|||
"type": "varchar", |
|||
"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": { |
|||
"users_username_unique": { |
|||
"name": "users_username_unique", |
|||
"nullsNotDistinct": false, |
|||
"columns": [ |
|||
"username" |
|||
] |
|||
} |
|||
}, |
|||
"policies": {}, |
|||
"checkConstraints": {}, |
|||
"isRLSEnabled": false |
|||
}, |
|||
"public.app_configs": { |
|||
"name": "app_configs", |
|||
"schema": "", |
|||
"columns": { |
|||
"key": { |
|||
"name": "key", |
|||
"type": "varchar", |
|||
"primaryKey": true, |
|||
"notNull": true |
|||
}, |
|||
"value": { |
|||
"name": "value", |
|||
"type": "varchar", |
|||
"primaryKey": false, |
|||
"notNull": true |
|||
}, |
|||
"value_type": { |
|||
"name": "value_type", |
|||
"type": "varchar", |
|||
"primaryKey": false, |
|||
"notNull": true |
|||
}, |
|||
"updated_at": { |
|||
"name": "updated_at", |
|||
"type": "timestamp", |
|||
"primaryKey": false, |
|||
"notNull": true, |
|||
"default": "now()" |
|||
} |
|||
}, |
|||
"indexes": {}, |
|||
"foreignKeys": {}, |
|||
"compositePrimaryKeys": {}, |
|||
"uniqueConstraints": {}, |
|||
"policies": {}, |
|||
"checkConstraints": {}, |
|||
"isRLSEnabled": false |
|||
}, |
|||
"public.user_configs": { |
|||
"name": "user_configs", |
|||
"schema": "", |
|||
"columns": { |
|||
"user_id": { |
|||
"name": "user_id", |
|||
"type": "integer", |
|||
"primaryKey": false, |
|||
"notNull": true |
|||
}, |
|||
"key": { |
|||
"name": "key", |
|||
"type": "varchar", |
|||
"primaryKey": false, |
|||
"notNull": true |
|||
}, |
|||
"value": { |
|||
"name": "value", |
|||
"type": "varchar", |
|||
"primaryKey": false, |
|||
"notNull": true |
|||
}, |
|||
"value_type": { |
|||
"name": "value_type", |
|||
"type": "varchar", |
|||
"primaryKey": false, |
|||
"notNull": true |
|||
}, |
|||
"updated_at": { |
|||
"name": "updated_at", |
|||
"type": "timestamp", |
|||
"primaryKey": false, |
|||
"notNull": true, |
|||
"default": "now()" |
|||
} |
|||
}, |
|||
"indexes": { |
|||
"user_configs_user_id_idx": { |
|||
"name": "user_configs_user_id_idx", |
|||
"columns": [ |
|||
{ |
|||
"expression": "user_id", |
|||
"isExpression": false, |
|||
"asc": true, |
|||
"nulls": "last" |
|||
} |
|||
], |
|||
"isUnique": false, |
|||
"concurrently": false, |
|||
"method": "btree", |
|||
"with": {} |
|||
} |
|||
}, |
|||
"foreignKeys": { |
|||
"user_configs_user_id_users_id_fk": { |
|||
"name": "user_configs_user_id_users_id_fk", |
|||
"tableFrom": "user_configs", |
|||
"tableTo": "users", |
|||
"columnsFrom": [ |
|||
"user_id" |
|||
], |
|||
"columnsTo": [ |
|||
"id" |
|||
], |
|||
"onDelete": "cascade", |
|||
"onUpdate": "no action" |
|||
} |
|||
}, |
|||
"compositePrimaryKeys": { |
|||
"user_configs_user_id_key_pk": { |
|||
"name": "user_configs_user_id_key_pk", |
|||
"columns": [ |
|||
"user_id", |
|||
"key" |
|||
] |
|||
} |
|||
}, |
|||
"uniqueConstraints": {}, |
|||
"policies": {}, |
|||
"checkConstraints": {}, |
|||
"isRLSEnabled": false |
|||
} |
|||
}, |
|||
"enums": {}, |
|||
"schemas": {}, |
|||
"sequences": {}, |
|||
"roles": {}, |
|||
"policies": {}, |
|||
"views": {}, |
|||
"_meta": { |
|||
"columns": {}, |
|||
"schemas": {}, |
|||
"tables": {} |
|||
} |
|||
} |
|||
@ -1,34 +1,13 @@ |
|||
{ |
|||
"version": "7", |
|||
"dialect": "postgresql", |
|||
"dialect": "sqlite", |
|||
"entries": [ |
|||
{ |
|||
"idx": 0, |
|||
"version": "7", |
|||
"when": 1776329125490, |
|||
"version": "6", |
|||
"when": 1776386788654, |
|||
"tag": "0000_init", |
|||
"breakpoints": true |
|||
}, |
|||
{ |
|||
"idx": 1, |
|||
"version": "7", |
|||
"when": 1776336996454, |
|||
"tag": "0001_auth-sessions", |
|||
"breakpoints": true |
|||
}, |
|||
{ |
|||
"idx": 2, |
|||
"version": "7", |
|||
"when": 1776337212081, |
|||
"tag": "0002_auth-sessions-quality-fixes", |
|||
"breakpoints": true |
|||
}, |
|||
{ |
|||
"idx": 3, |
|||
"version": "7", |
|||
"when": 1776348953237, |
|||
"tag": "0003_config-module", |
|||
"breakpoints": true |
|||
} |
|||
] |
|||
} |
|||
@ -0,0 +1,82 @@ |
|||
/** |
|||
* 识别各驱动常见的「唯一 / 重复键」错误,供任意 service 复用。 |
|||
* 具体业务(如用户名冲突 vs 主键重试)用 {@link uniqueConstraintTouchesField} 再区分即可。 |
|||
*/ |
|||
|
|||
/** 遍历包装错误(如 Drizzle)与底层驱动的整条 cause 链 */ |
|||
export function* eachErrorInChain(err: unknown): Generator<Error> { |
|||
let current: unknown = err; |
|||
const seen = new Set<unknown>(); |
|||
while (current instanceof Error && !seen.has(current)) { |
|||
seen.add(current); |
|||
yield current; |
|||
current = "cause" in current ? (current as { cause?: unknown }).cause : undefined; |
|||
} |
|||
} |
|||
|
|||
function escapeRegExp(s: string) { |
|||
return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); |
|||
} |
|||
|
|||
/** |
|||
* 报错文案或 constraint 名是否涉及该字段。 |
|||
* message 用词边界,避免 `guid` 误匹配 `id`;constraint 多为 `users_username_key` 式拼接,用子串匹配。 |
|||
*/ |
|||
export function uniqueConstraintTouchesField(err: unknown, field: string): boolean { |
|||
const fl = field.toLowerCase(); |
|||
const re = new RegExp(`\\b${escapeRegExp(field)}\\b`, "i"); |
|||
for (const e of eachErrorInChain(err)) { |
|||
if (re.test(e.message)) { |
|||
return true; |
|||
} |
|||
if ("constraint" in e) { |
|||
const c = String((e as { constraint?: unknown }).constraint ?? "").toLowerCase(); |
|||
if (c.includes(fl)) { |
|||
return true; |
|||
} |
|||
} |
|||
} |
|||
return false; |
|||
} |
|||
|
|||
export function isUniqueConstraintViolation(err: unknown): boolean { |
|||
for (const e of eachErrorInChain(err)) { |
|||
const code = "code" in e ? String((e as { code?: unknown }).code ?? "") : ""; |
|||
const errno = "errno" in e ? (e as { errno?: unknown }).errno : undefined; |
|||
|
|||
if (code === "23505") { |
|||
return true; |
|||
} |
|||
if (code === "ER_DUP_ENTRY") { |
|||
return true; |
|||
} |
|||
if (typeof errno === "number" && errno === 1062) { |
|||
return true; |
|||
} |
|||
if (code.startsWith("SQLITE_CONSTRAINT")) { |
|||
return true; |
|||
} |
|||
|
|||
const m = e.message.toLowerCase(); |
|||
if (m.includes("unique constraint failed")) { |
|||
return true; |
|||
} |
|||
if (m.includes("duplicate entry")) { |
|||
return true; |
|||
} |
|||
if (m.includes("duplicate key") && m.includes("unique")) { |
|||
return true; |
|||
} |
|||
} |
|||
return false; |
|||
} |
|||
|
|||
/** 唯一冲突且与指定字段相关(例如用户名占用) */ |
|||
export function isUniqueConflictOnField(err: unknown, field: string): boolean { |
|||
return isUniqueConstraintViolation(err) && uniqueConstraintTouchesField(err, field); |
|||
} |
|||
|
|||
/** 唯一冲突且与指定字段无关(例如除 username 外仅有主键时的 id 撞车重试) */ |
|||
export function isUniqueConflictExceptField(err: unknown, field: string): boolean { |
|||
return isUniqueConstraintViolation(err) && !uniqueConstraintTouchesField(err, field); |
|||
} |
|||
Loading…
Reference in new issue