Browse Source

feat(auth): add auth schema - users ext + user_sessions table

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
npmrun 1 week ago
parent
commit
d5fdbe3bd5
  1. 20
      packages/drizzle-pkg/lib/schema/auth.ts
  2. 52
      packages/drizzle-pkg/migrations/0002_add_auth_tables.sql
  3. 446
      packages/drizzle-pkg/migrations/meta/0002_snapshot.json

20
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", { export const users = sqliteTable("users", {
id: integer().primaryKey(), id: integer().primaryKey(),
username: text().notNull().unique(), username: text().notNull().unique(),
email: text(), email: text(), // unique index added via migration
nickname: text(), nickname: text(),
password: text().notNull(), password: text().notNull(),
avatar: text(), avatar: text(),
@ -14,4 +14,22 @@ export const users = sqliteTable("users", {
.defaultNow() .defaultNow()
.$onUpdate(() => new Date()) .$onUpdate(() => new Date())
.notNull(), .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" }),
}); });

52
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`);

446
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": {}
}
}
Loading…
Cancel
Save