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 { drizzle } from 'drizzle-orm/libsql'; |
||||
|
import path from 'path'; |
||||
|
|
||||
if (process.env.NODE_ENV === 'production') { |
if (process.env.NODE_ENV === 'production') { |
||||
// 打包时需要保证migrator被引入
|
// 打包时需要保证migrator被引入
|
||||
import('drizzle-orm/better-sqlite3/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!); |
const _db = drizzle(process.env.DATABASE_URL!); |
||||
|
|
||||
export { _db as dbGlobal } |
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" ( |
CREATE TABLE `sessions` ( |
||||
"id" integer PRIMARY KEY NOT NULL, |
`id` text PRIMARY KEY NOT NULL, |
||||
"username" varchar NOT NULL, |
`user_id` integer NOT NULL, |
||||
"email" varchar, |
`expires_at` integer NOT NULL, |
||||
"nickname" varchar, |
`created_at` integer DEFAULT (cast((julianday('now') - 2440587.5)*86400000 as integer)) NOT NULL, |
||||
"password" varchar NOT NULL, |
FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON UPDATE no action ON DELETE cascade |
||||
"avatar" varchar, |
|
||||
"created_at" timestamp DEFAULT now() NOT NULL, |
|
||||
"updated_at" timestamp DEFAULT now() NOT NULL, |
|
||||
CONSTRAINT "users_username_unique" UNIQUE("username") |
|
||||
); |
); |
||||
|
--> 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", |
"prevId": "00000000-0000-0000-0000-000000000000", |
||||
"version": "7", |
|
||||
"dialect": "postgresql", |
|
||||
"tables": { |
"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", |
"name": "users", |
||||
"schema": "", |
|
||||
"columns": { |
"columns": { |
||||
"id": { |
"id": { |
||||
"name": "id", |
"name": "id", |
||||
"type": "integer", |
"type": "integer", |
||||
"primaryKey": true, |
"primaryKey": true, |
||||
"notNull": true |
"notNull": true, |
||||
|
"autoincrement": false |
||||
}, |
}, |
||||
"username": { |
"username": { |
||||
"name": "username", |
"name": "username", |
||||
"type": "varchar", |
"type": "text", |
||||
"primaryKey": false, |
"primaryKey": false, |
||||
"notNull": true |
"notNull": true, |
||||
|
"autoincrement": false |
||||
}, |
}, |
||||
"email": { |
"email": { |
||||
"name": "email", |
"name": "email", |
||||
"type": "varchar", |
"type": "text", |
||||
"primaryKey": false, |
"primaryKey": false, |
||||
"notNull": false |
"notNull": false, |
||||
|
"autoincrement": false |
||||
}, |
}, |
||||
"nickname": { |
"nickname": { |
||||
"name": "nickname", |
"name": "nickname", |
||||
"type": "varchar", |
"type": "text", |
||||
"primaryKey": false, |
"primaryKey": false, |
||||
"notNull": false |
"notNull": false, |
||||
|
"autoincrement": false |
||||
}, |
}, |
||||
"password": { |
"password": { |
||||
"name": "password", |
"name": "password", |
||||
"type": "varchar", |
"type": "text", |
||||
"primaryKey": false, |
"primaryKey": false, |
||||
"notNull": true |
"notNull": true, |
||||
|
"autoincrement": false |
||||
}, |
}, |
||||
"avatar": { |
"avatar": { |
||||
"name": "avatar", |
"name": "avatar", |
||||
"type": "varchar", |
"type": "text", |
||||
"primaryKey": false, |
"primaryKey": false, |
||||
"notNull": false |
"notNull": false, |
||||
|
"autoincrement": false |
||||
}, |
}, |
||||
"created_at": { |
"created_at": { |
||||
"name": "created_at", |
"name": "created_at", |
||||
"type": "timestamp", |
"type": "integer", |
||||
"primaryKey": false, |
"primaryKey": false, |
||||
"notNull": true, |
"notNull": true, |
||||
"default": "now()" |
"autoincrement": false, |
||||
|
"default": "(cast((julianday('now') - 2440587.5)*86400000 as integer))" |
||||
}, |
}, |
||||
"updated_at": { |
"updated_at": { |
||||
"name": "updated_at", |
"name": "updated_at", |
||||
"type": "timestamp", |
"type": "integer", |
||||
"primaryKey": false, |
"primaryKey": false, |
||||
"notNull": true, |
"notNull": true, |
||||
"default": "now()" |
"autoincrement": false, |
||||
|
"default": "(cast((julianday('now') - 2440587.5)*86400000 as integer))" |
||||
} |
} |
||||
}, |
}, |
||||
"indexes": {}, |
"indexes": { |
||||
"foreignKeys": {}, |
|
||||
"compositePrimaryKeys": {}, |
|
||||
"uniqueConstraints": { |
|
||||
"users_username_unique": { |
"users_username_unique": { |
||||
"name": "users_username_unique", |
"name": "users_username_unique", |
||||
"nullsNotDistinct": false, |
|
||||
"columns": [ |
"columns": [ |
||||
"username" |
"username" |
||||
] |
], |
||||
|
"isUnique": true |
||||
} |
} |
||||
}, |
}, |
||||
"policies": {}, |
"foreignKeys": {}, |
||||
"checkConstraints": {}, |
"compositePrimaryKeys": {}, |
||||
"isRLSEnabled": false |
"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": {}, |
"views": {}, |
||||
|
"enums": {}, |
||||
"_meta": { |
"_meta": { |
||||
"columns": {}, |
|
||||
"schemas": {}, |
"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", |
"version": "7", |
||||
"dialect": "postgresql", |
"dialect": "sqlite", |
||||
"entries": [ |
"entries": [ |
||||
{ |
{ |
||||
"idx": 0, |
"idx": 0, |
||||
"version": "7", |
"version": "6", |
||||
"when": 1776329125490, |
"when": 1776386788654, |
||||
"tag": "0000_init", |
"tag": "0000_init", |
||||
"breakpoints": true |
"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