From f357330ba840c1a87b118cce76de297a11725121 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=A0=E7=BF=94?= Date: Thu, 12 Mar 2026 20:45:43 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E9=87=8D=E6=9E=84=E7=94=A8=E6=88=B7?= =?UTF-8?q?=E8=A7=92=E8=89=B2=E7=B3=BB=E7=BB=9F=E4=B8=BA=E7=AE=A1=E7=90=86?= =?UTF-8?q?=E5=91=98=E6=A0=87=E8=AF=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 将用户角色字段从role改为is_admin布尔值 - 更新相关API权限检查逻辑 - 修改数据库schema和迁移文件 - 调整前端用户显示逻辑 - 添加API响应工具函数 - 优化权限检查中间件 - 重构英雄组件为原子组件 --- drizzle/0001_clammy_toro.sql | 17 + drizzle/meta/0001_snapshot.json | 502 ++++++++++++++++++ drizzle/meta/_journal.json | 9 +- jest.setup.js | 2 + src/app/admin/layout.tsx | 3 +- src/app/api/admin/config/route.ts | 112 ++-- src/app/api/admin/content/[id]/route.ts | 71 +-- src/app/api/admin/content/route.ts | 49 +- src/app/api/admin/upload/route.ts | 56 +- src/app/api/admin/users/[id]/route.ts | 100 ++-- src/app/api/admin/users/route.ts | 91 +--- src/components/effects/index.ts | 20 + .../sections/hero-section-atoms.tsx | 220 ++++++++ src/components/sections/hero-section.tsx | 196 +------ src/db/schema.ts | 2 +- src/db/seed.ts | 2 +- src/lib/api-response.ts | 89 ++++ src/lib/auth.ts | 6 +- src/lib/auth/check-permission.ts | 33 +- src/lib/auth/permissions.ts | 39 +- src/types/jest-dom.d.ts | 5 + src/types/next-auth.d.ts | 6 +- 22 files changed, 1078 insertions(+), 552 deletions(-) create mode 100644 drizzle/0001_clammy_toro.sql create mode 100644 drizzle/meta/0001_snapshot.json create mode 100644 src/components/effects/index.ts create mode 100644 src/components/sections/hero-section-atoms.tsx create mode 100644 src/lib/api-response.ts create mode 100644 src/types/jest-dom.d.ts diff --git a/drizzle/0001_clammy_toro.sql b/drizzle/0001_clammy_toro.sql new file mode 100644 index 0000000..df08fca --- /dev/null +++ b/drizzle/0001_clammy_toro.sql @@ -0,0 +1,17 @@ +PRAGMA foreign_keys=OFF;--> statement-breakpoint +CREATE TABLE `__new_users` ( + `id` text PRIMARY KEY NOT NULL, + `email` text NOT NULL, + `password_hash` text, + `name` text NOT NULL, + `is_admin` integer DEFAULT false NOT NULL, + `avatar` text, + `created_at` integer NOT NULL, + `updated_at` integer NOT NULL +); +--> statement-breakpoint +INSERT INTO `__new_users`("id", "email", "password_hash", "name", "is_admin", "avatar", "created_at", "updated_at") SELECT "id", "email", "password_hash", "name", "is_admin", "avatar", "created_at", "updated_at" FROM `users`;--> statement-breakpoint +DROP TABLE `users`;--> statement-breakpoint +ALTER TABLE `__new_users` RENAME TO `users`;--> statement-breakpoint +PRAGMA foreign_keys=ON;--> statement-breakpoint +CREATE UNIQUE INDEX `users_email_unique` ON `users` (`email`); \ No newline at end of file diff --git a/drizzle/meta/0001_snapshot.json b/drizzle/meta/0001_snapshot.json new file mode 100644 index 0000000..471c818 --- /dev/null +++ b/drizzle/meta/0001_snapshot.json @@ -0,0 +1,502 @@ +{ + "version": "6", + "dialect": "sqlite", + "id": "923c66d2-b19b-4d84-b88c-dc75d07fefd6", + "prevId": "98ef90e0-460c-4b25-9197-bf2f4900d3f9", + "tables": { + "audit_logs": { + "name": "audit_logs", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "action": { + "name": "action", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "resource_type": { + "name": "resource_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "resource_id": { + "name": "resource_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "details": { + "name": "details", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "ip_address": { + "name": "ip_address", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "user_agent": { + "name": "user_agent", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "timestamp": { + "name": "timestamp", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "audit_logs_user_id_users_id_fk": { + "name": "audit_logs_user_id_users_id_fk", + "tableFrom": "audit_logs", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "content": { + "name": "content", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "slug": { + "name": "slug", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "excerpt": { + "name": "excerpt", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "content": { + "name": "content", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "cover_image": { + "name": "cover_image", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "category": { + "name": "category", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "tags": { + "name": "tags", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'draft'" + }, + "published_at": { + "name": "published_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "author_id": { + "name": "author_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "sort_order": { + "name": "sort_order", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": 0 + }, + "metadata": { + "name": "metadata", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "content_slug_unique": { + "name": "content_slug_unique", + "columns": [ + "slug" + ], + "isUnique": true + } + }, + "foreignKeys": { + "content_author_id_users_id_fk": { + "name": "content_author_id_users_id_fk", + "tableFrom": "content", + "tableTo": "users", + "columnsFrom": [ + "author_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "content_versions": { + "name": "content_versions", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "content_id": { + "name": "content_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "version": { + "name": "version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "content": { + "name": "content", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "changes": { + "name": "changes", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "changed_by": { + "name": "changed_by", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "changed_at": { + "name": "changed_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "content_versions_content_id_content_id_fk": { + "name": "content_versions_content_id_content_id_fk", + "tableFrom": "content_versions", + "tableTo": "content", + "columnsFrom": [ + "content_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "content_versions_changed_by_users_id_fk": { + "name": "content_versions_changed_by_users_id_fk", + "tableFrom": "content_versions", + "tableTo": "users", + "columnsFrom": [ + "changed_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "site_config": { + "name": "site_config", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "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 + }, + "category": { + "name": "category", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_by": { + "name": "updated_by", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "site_config_key_unique": { + "name": "site_config_key_unique", + "columns": [ + "key" + ], + "isUnique": true + } + }, + "foreignKeys": { + "site_config_updated_by_users_id_fk": { + "name": "site_config_updated_by_users_id_fk", + "tableFrom": "site_config", + "tableTo": "users", + "columnsFrom": [ + "updated_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "users": { + "name": "users", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "password_hash": { + "name": "password_hash", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "is_admin": { + "name": "is_admin", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "avatar": { + "name": "avatar", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "users_email_unique": { + "name": "users_email_unique", + "columns": [ + "email" + ], + "isUnique": true + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + } + }, + "views": {}, + "enums": {}, + "_meta": { + "schemas": {}, + "tables": {}, + "columns": { + "\"users\".\"role\"": "\"users\".\"is_admin\"" + } + }, + "internal": { + "indexes": {} + } +} \ No newline at end of file diff --git a/drizzle/meta/_journal.json b/drizzle/meta/_journal.json index 2017e1c..a74c5f8 100644 --- a/drizzle/meta/_journal.json +++ b/drizzle/meta/_journal.json @@ -8,6 +8,13 @@ "when": 1772974841798, "tag": "0000_white_justice", "breakpoints": true + }, + { + "idx": 1, + "version": "6", + "when": 1773202935722, + "tag": "0001_clammy_toro", + "breakpoints": true } ] -} \ No newline at end of file +} diff --git a/jest.setup.js b/jest.setup.js index dbe7948..063214a 100644 --- a/jest.setup.js +++ b/jest.setup.js @@ -1,3 +1,5 @@ +import '@testing-library/jest-dom'; + jest.mock('next-auth', () => { return { __esModule: true, diff --git a/src/app/admin/layout.tsx b/src/app/admin/layout.tsx index b785b69..bd992ce 100644 --- a/src/app/admin/layout.tsx +++ b/src/app/admin/layout.tsx @@ -129,8 +129,7 @@ export default function AdminLayout({ {session?.user?.name}

- {session?.user?.role === 'admin' ? '管理员' : - session?.user?.role === 'editor' ? '编辑' : '查看者'} + {session?.user?.isAdmin ? '管理员' : '用户'}