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 ? '管理员' : '用户'}