diff --git a/packages/drizzle-pkg/lib/schema/auth.ts b/packages/drizzle-pkg/lib/schema/auth.ts index 4eb2bb2..053fbb3 100644 --- a/packages/drizzle-pkg/lib/schema/auth.ts +++ b/packages/drizzle-pkg/lib/schema/auth.ts @@ -3,7 +3,7 @@ import { index, integer, sqliteTable, text } from "drizzle-orm/sqlite-core"; export const users = sqliteTable("users", { id: integer().primaryKey(), username: text().notNull().unique(), - email: text(), + email: text(), // unique index added via migration nickname: text(), password: text().notNull(), avatar: text(), @@ -14,4 +14,22 @@ export const users = sqliteTable("users", { .defaultNow() .$onUpdate(() => new Date()) .notNull(), + // --- auth fields --- + emailVerified: integer("email_verified", { mode: "boolean" }).default(false).notNull(), + passwordHistory: text("password_history").default("[]").notNull(), + failedLoginAttempts: integer("failed_login_attempts").default(0).notNull(), + lockoutUntil: integer("lockout_until", { mode: "timestamp_ms" }), + lastLoginAt: integer("last_login_at", { mode: "timestamp_ms" }), + lastLoginIp: text("last_login_ip"), }); + +export const userSessions = sqliteTable("user_sessions", { + id: text().primaryKey(), + userId: integer("user_id").notNull().references(() => users.id, { onDelete: "cascade" }), + refreshTokenHash: text("refresh_token_hash").notNull(), + userAgent: text("user_agent"), + ip: text(), + createdAt: integer("created_at", { mode: "timestamp_ms" }).defaultNow().notNull(), + expiresAt: integer("expires_at", { mode: "timestamp_ms" }).notNull(), + revokedAt: integer("revoked_at", { mode: "timestamp_ms" }), +}); \ No newline at end of file diff --git a/packages/drizzle-pkg/migrations/0002_add_auth_tables.sql b/packages/drizzle-pkg/migrations/0002_add_auth_tables.sql new file mode 100644 index 0000000..0780c7f --- /dev/null +++ b/packages/drizzle-pkg/migrations/0002_add_auth_tables.sql @@ -0,0 +1,52 @@ +-- 1. 添加 auth 相关字段到 users 表 +CREATE TABLE `users_new` ( + `id` integer PRIMARY KEY NOT NULL, + `username` text NOT NULL, + `email` text, + `nickname` text, + `password` text NOT NULL, + `avatar` text, + `role` text DEFAULT 'user' NOT NULL, + `status` text DEFAULT 'active' NOT NULL, + `public_slug` text, + `bio_markdown` text, + `bio_visibility` text DEFAULT 'private' NOT NULL, + `social_links_json` text DEFAULT '[]' NOT NULL, + `avatar_visibility` text DEFAULT 'private' NOT NULL, + `discover_visible` integer DEFAULT true NOT NULL, + `discover_location` text, + `discover_show_location` integer DEFAULT false NOT NULL, + `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, + `email_verified` integer DEFAULT false NOT NULL, + `password_history` text DEFAULT '[]' NOT NULL, + `failed_login_attempts` integer DEFAULT 0 NOT NULL, + `lockout_until` integer, + `last_login_at` integer, + `last_login_ip` text +); +--> statement-breakpoint +INSERT INTO `users_new` (`id`, `username`, `email`, `nickname`, `password`, `avatar`, `role`, `status`, `public_slug`, `bio_markdown`, `bio_visibility`, `social_links_json`, `avatar_visibility`, `discover_visible`, `discover_location`, `discover_show_location`, `created_at`, `updated_at`) SELECT `id`, `username`, `email`, `nickname`, `password`, `avatar`, `role`, `status`, `public_slug`, `bio_markdown`, `bio_visibility`, `social_links_json`, `avatar_visibility`, `discover_visible`, `discover_location`, `discover_show_location`, `created_at`, `updated_at` FROM `users`; +--> statement-breakpoint +DROP TABLE `users`; +--> statement-breakpoint +ALTER TABLE `users_new` RENAME TO `users`; +--> statement-breakpoint +CREATE UNIQUE INDEX `users_email_unique` ON `users` (`email`); +--> statement-breakpoint +CREATE INDEX `users_email_index` ON `users` (`email`); + +-- 2. 创建 user_sessions 表 +CREATE TABLE `user_sessions` ( + `id` text PRIMARY KEY NOT NULL, + `user_id` integer NOT NULL, + `refresh_token_hash` text NOT NULL, + `user_agent` text, + `ip` text, + `created_at` integer NOT NULL, + `expires_at` integer NOT NULL, + `revoked_at` integer, + FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON DELETE CASCADE +); +--> statement-breakpoint +CREATE INDEX `user_sessions_user_id_index` ON `user_sessions` (`user_id`); \ No newline at end of file diff --git a/packages/drizzle-pkg/migrations/meta/0002_snapshot.json b/packages/drizzle-pkg/migrations/meta/0002_snapshot.json new file mode 100644 index 0000000..2dcba67 --- /dev/null +++ b/packages/drizzle-pkg/migrations/meta/0002_snapshot.json @@ -0,0 +1,446 @@ +{ + "version": "6", + "dialect": "sqlite", + "id": "376a01af-bb2d-4390-85da-eafe842f6857", + "prevId": "3ec4e66c-a1be-4d18-82bb-9f967de5ea72", + "tables": { + "user_sessions": { + "name": "user_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 + }, + "refresh_token_hash": { + "name": "refresh_token_hash", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "user_agent": { + "name": "user_agent", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "ip": { + "name": "ip", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(cast((julianday('now') - 2440587.5)*86400000 as integer))" + }, + "expires_at": { + "name": "expires_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "revoked_at": { + "name": "revoked_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "user_sessions_user_id_users_id_fk": { + "name": "user_sessions_user_id_users_id_fk", + "tableFrom": "user_sessions", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "users": { + "name": "users", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "username": { + "name": "username", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "nickname": { + "name": "nickname", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "password": { + "name": "password", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "avatar": { + "name": "avatar", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'user'" + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'active'" + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(cast((julianday('now') - 2440587.5)*86400000 as integer))" + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(cast((julianday('now') - 2440587.5)*86400000 as integer))" + }, + "email_verified": { + "name": "email_verified", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "password_history": { + "name": "password_history", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'[]'" + }, + "failed_login_attempts": { + "name": "failed_login_attempts", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "lockout_until": { + "name": "lockout_until", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "last_login_at": { + "name": "last_login_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "last_login_ip": { + "name": "last_login_ip", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "users_username_unique": { + "name": "users_username_unique", + "columns": [ + "username" + ], + "isUnique": true + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "scheduled_tasks": { + "name": "scheduled_tasks", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "cron_expression": { + "name": "cron_expression", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "function_name": { + "name": "function_name", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "function_payload": { + "name": "function_payload", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "http_method": { + "name": "http_method", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "http_url": { + "name": "http_url", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "http_headers": { + "name": "http_headers", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "http_body": { + "name": "http_body", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "catch_up": { + "name": "catch_up", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "enabled": { + "name": "enabled", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 1 + }, + "max_retries": { + "name": "max_retries", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "retry_delay_seconds": { + "name": "retry_delay_seconds", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 60 + }, + "timeout_seconds": { + "name": "timeout_seconds", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 300 + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(cast((julianday('now') - 2440587.5)*86400000 as integer))" + }, + "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": {} + }, + "task_execution_logs": { + "name": "task_execution_logs", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "task_id": { + "name": "task_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "started_at": { + "name": "started_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(cast((julianday('now') - 2440587.5)*86400000 as integer))" + }, + "finished_at": { + "name": "finished_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "error_message": { + "name": "error_message", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "result_summary": { + "name": "result_summary", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "task_execution_logs_task_id_scheduled_tasks_id_fk": { + "name": "task_execution_logs_task_id_scheduled_tasks_id_fk", + "tableFrom": "task_execution_logs", + "tableTo": "scheduled_tasks", + "columnsFrom": [ + "task_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + } + }, + "views": {}, + "enums": {}, + "_meta": { + "schemas": {}, + "tables": {}, + "columns": {} + }, + "internal": { + "indexes": {} + } +} \ No newline at end of file