From 04c12ad340a3e4c4dd5d6f6d685383d2eff8dc42 Mon Sep 17 00:00:00 2001 From: kongkx Date: Mon, 10 Aug 2026 23:04:41 +0800 Subject: [PATCH] feat(users): add user admin section and delegate login authorization to API --- src/api/modules/users.ts | 16 + src/api/types.gen.ts | 8357 +++++++++++++++------- src/api/types.ts | 33 + src/auth/permissions.ts | 2 + src/components/layout/nav.ts | 1 + src/features/users/user-detail-sheet.tsx | 220 + src/routeTree.gen.ts | 21 + src/routes/_authed/users/index.tsx | 475 ++ src/routes/login.tsx | 5 - 9 files changed, 6630 insertions(+), 2500 deletions(-) create mode 100644 src/api/modules/users.ts create mode 100644 src/features/users/user-detail-sheet.tsx create mode 100644 src/routes/_authed/users/index.tsx diff --git a/src/api/modules/users.ts b/src/api/modules/users.ts new file mode 100644 index 0000000..51e989a --- /dev/null +++ b/src/api/modules/users.ts @@ -0,0 +1,16 @@ +import { api } from '@/api/client' +import type { ApiResponse, PaginatedResponse, PlatformUser, UserListParams } from '@/api/types' + +const BASE = '/api/admin/v1/users' + +export function fetchUsers(params?: UserListParams) { + return api.get>(BASE, params) +} + +export function fetchUserDetail(uid: string) { + return api.get>(`${BASE}/${uid}`) +} + +export function updateUserStatus(uid: string, status: boolean) { + return api.put>(`${BASE}/${uid}/status`, { status }) +} diff --git a/src/api/types.gen.ts b/src/api/types.gen.ts index f893308..5d62a69 100644 --- a/src/api/types.gen.ts +++ b/src/api/types.gen.ts @@ -5,1112 +5,6 @@ */ export interface paths { - "/api/admin/v1/auth/login/otp": { - parameters: { - query?: never; - header?: never; - path?: never; - cookie?: never; - }; - get?: never; - put?: never; - /** - * [管理后台] 请求OTP验证码 - * @description Request OTP code for admin login via email or SMS - */ - post: operations["OTP"]; - delete?: never; - options?: never; - head?: never; - patch?: never; - trace?: never; - }; - "/api/admin/v1/auth/login/verify-otp": { - parameters: { - query?: never; - header?: never; - path?: never; - cookie?: never; - }; - get?: never; - put?: never; - /** - * [管理后台] 验证OTP验证码 - * @description Verify OTP code for admin login and return access token - */ - post: operations["OTP"]; - delete?: never; - options?: never; - head?: never; - patch?: never; - trace?: never; - }; - "/api/admin/v1/auth/login/password": { - parameters: { - query?: never; - header?: never; - path?: never; - cookie?: never; - }; - get?: never; - put?: never; - /** - * [管理后台] 密码登录 - * @description Login admin user with identity and password - */ - post: { - parameters: { - query?: never; - header?: never; - path?: never; - cookie?: never; - }; - requestBody: { - content: { - "application/json": { - /** - * @description Admin identity (email). - * @example admin@example.com - */ - identity: string; - /** - * @description Admin password. - * @example admin123 - */ - password: string; - }; - }; - }; - responses: { - /** @description success */ - 200: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": { - /** @example true */ - success?: boolean; - data?: { - /** @example eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImp0aSI6... */ - access_token?: string; - /** @example Bearer */ - token_type?: string; - /** @example 900 */ - expires_in?: number; - /** @example refresh-token-string */ - refresh_token?: string; - }; - /** @example Admin login successful */ - message?: string; - }; - }; - }; - /** @description invalid_credentials */ - 401: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": { - /** @example false */ - success?: boolean; - /** @example INVALID_CREDENTIALS */ - code?: string; - /** @example Invalid admin credentials */ - message?: string; - }; - }; - }; - /** @description validation_error */ - 422: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": { - /** @example false */ - success?: boolean; - /** @example VALIDATION_ERROR */ - code?: string; - /** @example Validation failed */ - message?: string; - data?: { - errors?: { - /** - * @example [ - * "Identity is required" - * ] - */ - identity?: string[]; - /** - * @example [ - * "Password is required" - * ] - */ - password?: string[]; - }; - }; - }; - }; - }; - }; - }; - delete?: never; - options?: never; - head?: never; - patch?: never; - trace?: never; - }; - "/api/admin/v1/auth/robots/{uid}/token": { - parameters: { - query?: never; - header?: never; - path: { - /** @example architecto */ - uid: string; - }; - cookie?: never; - }; - get?: never; - put?: never; - /** - * [管理后台] 为机器人账号签发会话令牌 - * @description 为指定机器人账号签发一组 OAuth 访问令牌和刷新令牌。 - */ - post: { - parameters: { - query?: never; - header?: never; - path: { - /** @example architecto */ - uid: string; - }; - cookie?: never; - }; - requestBody?: never; - responses: never; - }; - delete?: never; - options?: never; - head?: never; - patch?: never; - trace?: never; - }; - "/api/admin/v1/auth/me": { - parameters: { - query?: never; - header?: never; - path?: never; - cookie?: never; - }; - /** - * Get the current admin user with roles and permissions - * @description Returns the authenticated user plus their roles and effective permissions - * (role-derived + direct) so the admin frontend can render permission-aware UI. - */ - get: operations["getTheCurrentAdminUserWithRolesAndPermissions"]; - put?: never; - post?: never; - delete?: never; - options?: never; - head?: never; - patch?: never; - trace?: never; - }; - "/api/admin/v1/auth/roles": { - parameters: { - query?: never; - header?: never; - path?: never; - cookie?: never; - }; - /** [角色管理] 角色列表 */ - get: { - parameters: { - query?: never; - header?: never; - path?: never; - cookie?: never; - }; - requestBody?: never; - responses: { - 200: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": { - /** @example true */ - success?: boolean; - /** - * @example [ - * { - * "kind": "role", - * "name": "moderator", - * "permissions": [ - * "admin:comment:read" - * ], - * "users_count": 1, - * "builtin": false - * } - * ] - */ - data?: { - /** @example role */ - kind?: string; - /** @example moderator */ - name?: string; - /** - * @example [ - * "admin:comment:read" - * ] - */ - permissions?: string[]; - /** @example 1 */ - users_count?: number; - /** @example false */ - builtin?: boolean; - }[]; - }; - }; - }; - 403: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": { - /** @example false */ - success?: boolean; - /** @example FORBIDDEN */ - code?: string; - /** @example 无权限访问 */ - message?: string; - }; - }; - }; - }; - }; - put?: never; - /** [角色管理] 新建角色 */ - post: { - parameters: { - query?: never; - header?: never; - path?: never; - cookie?: never; - }; - requestBody: { - content: { - "application/json": { - /** - * @description 角色名(唯一)。 - * @example reviewer - */ - name: string; - }; - }; - }; - responses: { - 201: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": { - /** @example true */ - success?: boolean; - data?: { - /** @example role */ - kind?: string; - /** @example reviewer */ - name?: string; - /** @example [] */ - permissions?: unknown[]; - /** @example 0 */ - users_count?: number; - /** @example false */ - builtin?: boolean; - }; - }; - }; - }; - }; - }; - delete?: never; - options?: never; - head?: never; - patch?: never; - trace?: never; - }; - "/api/admin/v1/auth/roles/{role}/permissions": { - parameters: { - query?: never; - header?: never; - path: { - /** - * @description 角色名。 - * @example moderator - */ - role: string; - }; - cookie?: never; - }; - get?: never; - /** - * [角色管理] 同步角色权限 - * @description 全量替换该角色的权限集合。super-admin 角色不可修改。 - */ - put: { - parameters: { - query?: never; - header?: never; - path: { - /** - * @description 角色名。 - * @example moderator - */ - role: string; - }; - cookie?: never; - }; - requestBody: { - content: { - "application/json": { - /** - * @description 权限键列表。 - * @example [ - * "admin:comment:read" - * ] - */ - permissions: string[]; - }; - }; - }; - responses: { - 200: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": { - /** @example true */ - success?: boolean; - data?: { - /** @example role */ - kind?: string; - /** @example moderator */ - name?: string; - /** - * @example [ - * "admin:comment:read" - * ] - */ - permissions?: string[]; - /** @example 1 */ - users_count?: number; - /** @example false */ - builtin?: boolean; - }; - }; - }; - }; - 422: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": { - /** @example false */ - success?: boolean; - /** @example VALIDATION_ERROR */ - code?: string; - /** @example The given data was invalid. */ - message?: string; - data?: { - errors?: { - /** - * @example [ - * "super-admin role cannot be modified." - * ] - */ - role?: string[]; - }; - }; - }; - }; - }; - }; - }; - post?: never; - delete?: never; - options?: never; - head?: never; - patch?: never; - trace?: never; - }; - "/api/admin/v1/auth/roles/{id}": { - parameters: { - query?: never; - header?: never; - path: { - /** - * @description The ID of the role. - * @example architecto - */ - id: string; - /** - * @description 角色名。 - * @example reviewer - */ - role: string; - }; - cookie?: never; - }; - get?: never; - put?: never; - post?: never; - /** - * [角色管理] 删除角色 - * @description 内建角色与仍有用户的角色不可删除。 - */ - delete: { - parameters: { - query?: never; - header?: never; - path: { - /** - * @description The ID of the role. - * @example architecto - */ - id: string; - /** - * @description 角色名。 - * @example reviewer - */ - role: string; - }; - cookie?: never; - }; - requestBody?: never; - responses: { - 200: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": { - /** @example true */ - success?: boolean; - /** @example Role deleted */ - message?: string; - }; - }; - }; - }; - }; - options?: never; - head?: never; - patch?: never; - trace?: never; - }; - "/api/admin/v1/auth/permissions": { - parameters: { - query?: never; - header?: never; - path?: never; - cookie?: never; - }; - /** - * [角色管理] 权限目录 - * @description 按模块/分组返回全部已注册权限键及描述,用于权限矩阵渲染。 - */ - get: { - parameters: { - query?: never; - header?: never; - path?: never; - cookie?: never; - }; - requestBody?: never; - responses: { - 200: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": { - /** @example true */ - success?: boolean; - data?: { - comment?: { - /** @example comment */ - module?: string; - groups?: { - admin?: { - /** @example View reported comments */ - "admin:comment:read"?: string; - }; - }; - }; - }; - }; - }; - }; - }; - }; - put?: never; - post?: never; - delete?: never; - options?: never; - head?: never; - patch?: never; - trace?: never; - }; - "/api/admin/v1/auth/users": { - parameters: { - query?: never; - header?: never; - path?: never; - cookie?: never; - }; - /** [角色管理] 搜索用户(角色分配用) */ - get: { - parameters: { - query: { - /** - * @description 按邮箱/用户名/UID 模糊搜索。 示例: moderator - * @example architecto - */ - q: string; - }; - header?: never; - path?: never; - cookie?: never; - }; - requestBody: { - content: { - "application/json": { - /** - * @description Must not be greater than 100 characters. - * @example b - */ - q: string; - }; - }; - }; - responses: { - 200: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": { - /** @example true */ - success?: boolean; - /** - * @example [ - * { - * "kind": "role_user", - * "uid": "01HXXXX", - * "display_name": "内容审核员", - * "email": "moderator@gig.local", - * "roles": [ - * "moderator" - * ] - * } - * ] - */ - data?: { - /** @example role_user */ - kind?: string; - /** @example 01HXXXX */ - uid?: string; - /** @example 内容审核员 */ - display_name?: string; - /** @example moderator@gig.local */ - email?: string; - /** - * @example [ - * "moderator" - * ] - */ - roles?: string[]; - }[]; - }; - }; - }; - }; - }; - put?: never; - post?: never; - delete?: never; - options?: never; - head?: never; - patch?: never; - trace?: never; - }; - "/api/admin/v1/auth/users/{uid}/roles": { - parameters: { - query?: never; - header?: never; - path: { - /** - * @description 用户 UID。 - * @example 01HXXXX - */ - uid: string; - }; - cookie?: never; - }; - get?: never; - /** - * [角色管理] 分配用户角色 - * @description 全量替换用户的角色集合。不能修改自己的角色。 - */ - put: { - parameters: { - query?: never; - header?: never; - path: { - /** - * @description 用户 UID。 - * @example 01HXXXX - */ - uid: string; - }; - cookie?: never; - }; - requestBody: { - content: { - "application/json": { - /** - * @description 角色名列表。 - * @example [ - * "moderator" - * ] - */ - roles: string[]; - }; - }; - }; - responses: { - 200: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": { - /** @example true */ - success?: boolean; - data?: { - /** @example role_user */ - kind?: string; - /** @example 01HXXXX */ - uid?: string; - /** - * @example [ - * "moderator" - * ] - */ - roles?: string[]; - }; - }; - }; - }; - 422: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": { - /** @example false */ - success?: boolean; - /** @example VALIDATION_ERROR */ - code?: string; - /** @example The given data was invalid. */ - message?: string; - data?: { - errors?: { - /** - * @example [ - * "You cannot modify your own roles." - * ] - */ - uid?: string[]; - }; - }; - }; - }; - }; - }; - }; - post?: never; - delete?: never; - options?: never; - head?: never; - patch?: never; - trace?: never; - }; - "/api/admin/v1/comments/reported": { - parameters: { - query?: never; - header?: never; - path?: never; - cookie?: never; - }; - /** - * [评论审核] 被举报评论列表 - * @description 按被举报的评论分组列出举报,附带举报明细与屏蔽状态。 - */ - get: { - parameters: { - query?: { - /** - * @description 可选。按状态过滤: pending(有待处理举报), blocked(已屏蔽), all(默认)。 示例: pending - * @example architecto - */ - status?: string; - /** - * @description 可选。页码。 示例: 1 - * @example 16 - */ - page?: number; - /** - * @description 可选。每页数量,最大100。 示例: 20 - * @example 16 - */ - page_size?: number; - }; - header?: never; - path?: never; - cookie?: never; - }; - requestBody?: { - content: { - "application/json": { - /** - * @example pending - * @enum {string|null} - */ - status?: "pending" | "blocked" | "all" | null; - /** - * @description Must be at least 1. - * @example 16 - */ - page?: number | null; - /** - * @description Must be at least 1. Must not be greater than 100. - * @example 22 - */ - page_size?: number | null; - }; - }; - }; - responses: { - 200: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": { - /** @example true */ - success?: boolean; - /** - * @example [ - * { - * "kind": "reported_comment", - * "id": 10, - * "body": "这条评论包含垃圾推广链接", - * "body_format": "markdown", - * "status": "active", - * "is_blocked": false, - * "blocked_at": null, - * "blocked_by_uid": null, - * "author": { - * "uid": "01HYYYY", - * "display_name": "Bob" - * }, - * "open_reports_count": 2, - * "latest_report_at": "2026-07-11T10:10:00Z", - * "reports": [ - * { - * "id": 1, - * "reason": "spam", - * "message": "含有垃圾推广链接", - * "status": "open", - * "reporter": { - * "uid": "01HXXXX", - * "display_name": "Alice" - * }, - * "created_at": "2026-07-11T10:10:00Z", - * "handled_at": null - * } - * ], - * "created_at": "2026-07-10T09:00:00Z" - * } - * ] - */ - data?: { - /** @example reported_comment */ - kind?: string; - /** @example 10 */ - id?: number; - /** @example 这条评论包含垃圾推广链接 */ - body?: string; - /** @example markdown */ - body_format?: string; - /** @example active */ - status?: string; - /** @example false */ - is_blocked?: boolean; - /** @example null */ - blocked_at?: string; - /** @example null */ - blocked_by_uid?: string; - author?: { - /** @example 01HYYYY */ - uid?: string; - /** @example Bob */ - display_name?: string; - }; - /** @example 2 */ - open_reports_count?: number; - /** @example 2026-07-11T10:10:00Z */ - latest_report_at?: string; - /** - * @example [ - * { - * "id": 1, - * "reason": "spam", - * "message": "含有垃圾推广链接", - * "status": "open", - * "reporter": { - * "uid": "01HXXXX", - * "display_name": "Alice" - * }, - * "created_at": "2026-07-11T10:10:00Z", - * "handled_at": null - * } - * ] - */ - reports?: { - /** @example 1 */ - id?: number; - /** @example spam */ - reason?: string; - /** @example 含有垃圾推广链接 */ - message?: string; - /** @example open */ - status?: string; - reporter?: { - /** @example 01HXXXX */ - uid?: string; - /** @example Alice */ - display_name?: string; - }; - /** @example 2026-07-11T10:10:00Z */ - created_at?: string; - /** @example null */ - handled_at?: string; - }[]; - /** @example 2026-07-10T09:00:00Z */ - created_at?: string; - }[]; - pagination?: { - /** @example 1 */ - current_page?: number; - /** @example null */ - next_page?: string; - /** @example 20 */ - page_size?: number; - /** @example null */ - prev_page?: string; - /** @example 1 */ - total_count?: number; - /** @example 1 */ - total_pages?: number; - }; - }; - }; - }; - 401: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": { - /** @example false */ - success?: boolean; - /** @example UNAUTHENTICATED */ - code?: string; - /** @example 未认证 */ - message?: string; - }; - }; - }; - 403: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": { - /** @example false */ - success?: boolean; - /** @example FORBIDDEN */ - code?: string; - /** @example 无权限访问 */ - message?: string; - }; - }; - }; - }; - }; - put?: never; - post?: never; - delete?: never; - options?: never; - head?: never; - patch?: never; - trace?: never; - }; - "/api/admin/v1/comments/reported/{comment}": { - parameters: { - query?: never; - header?: never; - path: { - /** - * @description 评论 ID。 - * @example 10 - */ - comment: number; - }; - cookie?: never; - }; - get?: never; - put?: never; - post?: never; - delete?: never; - options?: never; - head?: never; - /** - * [评论审核] 处理被举报评论 - * @description approve: 举报成立,屏蔽评论并将其全部待处理举报标记已处理; - * reject: 举报不成立,驳回全部待处理举报; - * revert: 撤销屏蔽(仅对已屏蔽评论有效),举报记录保留。 - */ - patch: { - parameters: { - query?: never; - header?: never; - path: { - /** - * @description 评论 ID。 - * @example 10 - */ - comment: number; - }; - cookie?: never; - }; - requestBody: { - content: { - "application/json": { - /** - * @description 处理动作: approve, reject, revert。 - * @example approve - */ - action: string; - }; - }; - }; - responses: { - 200: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": { - /** @example true */ - success?: boolean; - data?: { - /** @example reported_comment */ - kind?: string; - /** @example 10 */ - id?: number; - /** @example true */ - is_blocked?: boolean; - /** @example 2026-07-11T10:20:00Z */ - blocked_at?: string; - /** @example 01HXXXX */ - blocked_by_uid?: string; - /** @example 0 */ - open_reports_count?: number; - }; - /** @example Moderation applied */ - message?: string; - }; - }; - }; - 401: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": { - /** @example false */ - success?: boolean; - /** @example UNAUTHENTICATED */ - code?: string; - /** @example 未认证 */ - message?: string; - }; - }; - }; - 403: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": { - /** @example false */ - success?: boolean; - /** @example FORBIDDEN */ - code?: string; - /** @example 无权限访问 */ - message?: string; - }; - }; - }; - 422: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": { - /** @example false */ - success?: boolean; - /** @example VALIDATION_ERROR */ - code?: string; - /** @example The given data was invalid. */ - message?: string; - data?: { - errors?: { - /** - * @example [ - * "Comment is not blocked." - * ] - */ - action?: string[]; - }; - }; - }; - }; - }; - }; - }; - trace?: never; - }; "/api/admin/v1/dimzou/documents": { parameters: { query?: never; @@ -1156,7 +50,7 @@ export interface paths { content: { "application/json": { /** - * @example archived + * @example all * @enum {string|null} */ state?: "active" | "archived" | "trashed" | "all" | null; @@ -1645,7 +539,7 @@ export interface paths { */ language_code?: string; /** - * @example published + * @example draft * @enum {string} */ status?: "draft" | "awaiting_publish" | "published"; @@ -1865,6 +759,3566 @@ export interface paths { patch?: never; trace?: never; }; + "/api/admin/v1/studio/category-briefs": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * [内容分类简报] 列表 + * @description 分页返回分类内容简报(每个分类一份,含子话题/作者/文章聚合计数)。 + * 可按 category_id 过滤。需要 admin:studio:read 权限。 + */ + get: { + parameters: { + query?: { + /** + * @description 按所属分类过滤(UUID)。 + * @example 9f1c2d3e-4b5a-6789-0abc-def012345678 + */ + category_id?: string; + /** + * @description 每页数量,1-100,默认 20。 + * @example 20 + */ + page_size?: number; + /** + * @description 页码,默认 1。 + * @example 1 + */ + page?: number; + }; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + /** @example true */ + success?: boolean; + /** + * @example [ + * { + * "kind": "studio_category_brief", + * "id": "9f1c2d3e-4b5a-6789-0abc-def012345678", + * "category_id": "1a2b...", + * "description": "家电清洗与维修相关内容", + * "target_audience": "有家电保养需求的家庭用户", + * "subtopic_count": 12, + * "author_count": 3, + * "article_count": 40 + * } + * ] + */ + data?: { + /** @example studio_category_brief */ + kind?: string; + /** @example 9f1c2d3e-4b5a-6789-0abc-def012345678 */ + id?: string; + /** @example 1a2b... */ + category_id?: string; + /** @example 家电清洗与维修相关内容 */ + description?: string; + /** @example 有家电保养需求的家庭用户 */ + target_audience?: string; + /** @example 12 */ + subtopic_count?: number; + /** @example 3 */ + author_count?: number; + /** @example 40 */ + article_count?: number; + }[]; + pagination?: { + /** @example 1 */ + current_page?: number; + /** @example null */ + next_page?: string; + /** @example 20 */ + page_size?: number; + /** @example null */ + prev_page?: string; + /** @example 1 */ + total_count?: number; + /** @example 1 */ + total_pages?: number; + }; + }; + }; + }; + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + /** @example false */ + success?: boolean; + /** @example UNAUTHENTICATED */ + code?: string; + /** @example 未认证 */ + message?: string; + }; + }; + }; + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + /** @example false */ + success?: boolean; + /** @example FORBIDDEN */ + code?: string; + /** @example 无权限访问 */ + message?: string; + }; + }; + }; + }; + }; + put?: never; + /** + * [内容分类简报] 创建 + * @description 为某个分类新建内容简报。聚合计数(subtopic_count 等)由系统维护,不可写入。 + * 需要 admin:studio:manage 权限。 + */ + post: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": { + /** + * @description 所属分类 UUID。 + * @example 9f1c2d3e-4b5a-6789-0abc-def012345678 + */ + category_id: string; + /** + * @description 分类内容定位描述。 + * @example 家电清洗与维修相关内容 + */ + description?: string; + /** + * @description 目标读者。 + * @example 有家电保养需求的家庭用户 + */ + target_audience?: string; + /** + * @description 内容切入角度列表。 + * @example [ + * "清洗周期", + * "自检清单", + * "故障排查" + * ] + */ + content_angles?: string[]; + /** + * @description 内容风险/合规备注。 + * @example 涉及电器安全,需提示断电操作 + */ + risk_notes?: string; + }; + }; + }; + responses: { + 201: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + /** @example true */ + success?: boolean; + data?: { + /** @example studio_category_brief */ + kind?: string; + /** @example 9f1c2d3e-4b5a-6789-0abc-def012345678 */ + id?: string; + /** @example 1a2b... */ + category_id?: string; + /** @example 家电清洗与维修相关内容 */ + description?: string; + /** @example 0 */ + subtopic_count?: number; + /** @example 0 */ + author_count?: number; + /** @example 0 */ + article_count?: number; + }; + }; + }; + }; + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + /** @example false */ + success?: boolean; + /** @example VALIDATION_ERROR */ + code?: string; + /** @example The given data was invalid. */ + message?: string; + data?: { + errors?: { + /** + * @example [ + * "The category id field is required." + * ] + */ + category_id?: string[]; + }; + }; + }; + }; + }; + }; + }; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/admin/v1/studio/category-briefs/{id}": { + parameters: { + query?: never; + header?: never; + path: { + /** + * @description The ID of the category brief. + * @example architecto + */ + id: string; + }; + cookie?: never; + }; + /** + * 获取详情 + * @description 按 UUID 获取单个 Studio 实体,返回其全部属性(含 kind 判别字段)。 + * 需要 admin:studio:read 权限。 + */ + get: { + parameters: { + query?: never; + header?: never; + path: { + /** + * @description The ID of the category brief. + * @example architecto + */ + id: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + /** @example true */ + success?: boolean; + data?: { + /** @example studio_subtopic */ + kind?: string; + /** @example 9f1c2d3e-4b5a-6789-0abc-def012345678 */ + id?: string; + /** @example active */ + status?: string; + /** @example 2026-07-18T09:00:00.000000Z */ + created_at?: string; + /** @example 2026-07-18T09:00:00.000000Z */ + updated_at?: string; + }; + }; + }; + }; + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + /** @example false */ + success?: boolean; + /** @example UNAUTHENTICATED */ + code?: string; + /** @example 未认证 */ + message?: string; + }; + }; + }; + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + /** @example false */ + success?: boolean; + /** @example FORBIDDEN */ + code?: string; + /** @example 无权限访问 */ + message?: string; + }; + }; + }; + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + /** @example false */ + success?: boolean; + /** @example NOT_FOUND */ + code?: string; + /** @example 请求的资源未找到。 */ + message?: string; + }; + }; + }; + }; + }; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/admin/v1/studio/subtopics": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * [子话题] 列表 + * @description 分页返回子话题,可按 category_id、status 过滤。需要 admin:studio:read 权限。 + */ + get: { + parameters: { + query?: { + /** + * @description 按所属分类过滤(UUID)。 + * @example 9f1c2d3e-4b5a-6789-0abc-def012345678 + */ + category_id?: string; + /** + * @description 按状态过滤:active, archived。 + * @example active + */ + status?: string; + /** + * @description 每页数量,1-100,默认 20。 + * @example 20 + */ + page_size?: number; + /** + * @description 页码,默认 1。 + * @example 1 + */ + page?: number; + }; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + /** @example true */ + success?: boolean; + /** + * @example [ + * { + * "kind": "studio_subtopic", + * "id": "9f1c2d3e-4b5a-6789-0abc-def012345678", + * "category_id": "1a2b...", + * "title": "空调清洗保养指南", + * "target_audience": "家庭用户", + * "expandability_score": 80, + * "status": "active" + * } + * ] + */ + data?: { + /** @example studio_subtopic */ + kind?: string; + /** @example 9f1c2d3e-4b5a-6789-0abc-def012345678 */ + id?: string; + /** @example 1a2b... */ + category_id?: string; + /** @example 空调清洗保养指南 */ + title?: string; + /** @example 家庭用户 */ + target_audience?: string; + /** @example 80 */ + expandability_score?: number; + /** @example active */ + status?: string; + }[]; + pagination?: { + /** @example 1 */ + current_page?: number; + /** @example null */ + next_page?: string; + /** @example 20 */ + page_size?: number; + /** @example null */ + prev_page?: string; + /** @example 1 */ + total_count?: number; + /** @example 1 */ + total_pages?: number; + }; + }; + }; + }; + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + /** @example false */ + success?: boolean; + /** @example UNAUTHENTICATED */ + code?: string; + /** @example 未认证 */ + message?: string; + }; + }; + }; + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + /** @example false */ + success?: boolean; + /** @example FORBIDDEN */ + code?: string; + /** @example 无权限访问 */ + message?: string; + }; + }; + }; + }; + }; + put?: never; + /** + * [子话题] 创建 + * @description 在某分类下新建子话题。需要 admin:studio:manage 权限。 + */ + post: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": { + /** + * @description 所属分类 UUID。 + * @example 9f1c2d3e-4b5a-6789-0abc-def012345678 + */ + category_id: string; + /** + * @description 子话题标题,最长 255。 + * @example 空调清洗保养指南 + */ + title: string; + /** + * @description 目标读者。 + * @example 家庭用户 + */ + target_audience?: string; + /** + * @description 读者痛点。 + * @example 不清楚多久该洗一次空调 + */ + pain_points?: string; + /** + * @description 内容价值主张。 + * @example 给出清洗周期与自检清单 + */ + content_value?: string; + /** + * @description 可扩展性评分,0-255。 + * @example 80 + */ + expandability_score?: number; + /** + * @description 状态:active, archived。默认 active。 + * @example active + */ + status?: string; + }; + }; + }; + responses: { + 201: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + /** @example true */ + success?: boolean; + data?: { + /** @example studio_subtopic */ + kind?: string; + /** @example 9f1c2d3e-4b5a-6789-0abc-def012345678 */ + id?: string; + /** @example 空调清洗保养指南 */ + title?: string; + /** @example active */ + status?: string; + }; + }; + }; + }; + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + /** @example false */ + success?: boolean; + /** @example VALIDATION_ERROR */ + code?: string; + /** @example The given data was invalid. */ + message?: string; + data?: { + errors?: { + /** + * @example [ + * "The title field is required." + * ] + */ + title?: string[]; + }; + }; + }; + }; + }; + }; + }; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/admin/v1/studio/subtopics/{id}": { + parameters: { + query?: never; + header?: never; + path: { + /** + * @description The ID of the subtopic. + * @example architecto + */ + id: string; + }; + cookie?: never; + }; + /** + * 获取详情 + * @description 按 UUID 获取单个 Studio 实体,返回其全部属性(含 kind 判别字段)。 + * 需要 admin:studio:read 权限。 + */ + get: { + parameters: { + query?: never; + header?: never; + path: { + /** + * @description The ID of the subtopic. + * @example architecto + */ + id: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + /** @example true */ + success?: boolean; + data?: { + /** @example studio_subtopic */ + kind?: string; + /** @example 9f1c2d3e-4b5a-6789-0abc-def012345678 */ + id?: string; + /** @example active */ + status?: string; + /** @example 2026-07-18T09:00:00.000000Z */ + created_at?: string; + /** @example 2026-07-18T09:00:00.000000Z */ + updated_at?: string; + }; + }; + }; + }; + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + /** @example false */ + success?: boolean; + /** @example UNAUTHENTICATED */ + code?: string; + /** @example 未认证 */ + message?: string; + }; + }; + }; + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + /** @example false */ + success?: boolean; + /** @example FORBIDDEN */ + code?: string; + /** @example 无权限访问 */ + message?: string; + }; + }; + }; + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + /** @example false */ + success?: boolean; + /** @example NOT_FOUND */ + code?: string; + /** @example 请求的资源未找到。 */ + message?: string; + }; + }; + }; + }; + }; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/admin/v1/studio/robot-authors": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * [机器人作者] 列表 + * @description 分页返回机器人作者,附带绑定账号的公开身份(display_name / avatar)与账号级人设 + * (persona / 风格 / 发布规则)。可按 user_uid、category_id、subtopic_id、status、source 过滤。 + * 需要 admin:studio:read 权限。 + */ + get: { + parameters: { + query?: { + /** + * @description 按绑定的机器人账号 uid 过滤。 + * @example 01HXXXXAUTHORBOT + */ + user_uid?: string; + /** + * @description 按所属分类过滤(UUID)。 + * @example 1a2b3c4d-5e6f-7081-9234-abcdef012345 + */ + category_id?: string; + /** + * @description 按所属子话题过滤(UUID)。 + * @example 2b3c4d5e-6f70-8912-3abc-def012345678 + */ + subtopic_id?: string; + /** + * @description 按状态过滤:pending, profiling, ready, paused。 + * @example ready + */ + status?: string; + /** + * @description 按来源过滤:user, category。 + * @example category + */ + source?: string; + /** + * @description 每页数量,1-100,默认 20。 + * @example 20 + */ + page_size?: number; + /** + * @description 页码,默认 1。 + * @example 1 + */ + page?: number; + }; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + /** @example true */ + success?: boolean; + /** + * @example [ + * { + * "kind": "studio_robot_author", + * "id": "3c4d5e6f-7081-9234-5abc-def012345678", + * "user_uid": "01HXXXXAUTHORBOT", + * "category_id": "1a2b...", + * "status": "ready", + * "source": "category", + * "display_name": "家电保养小助手", + * "avatar": "https://…/avatar.png", + * "persona": "务实、耐心的家电保养老师傅", + * "article_style": { + * "tone": "亲切" + * }, + * "image_style": { + * "style": "写实" + * }, + * "publishing_rules": { + * "cadence": "每周 2 篇" + * } + * } + * ] + */ + data?: { + /** @example studio_robot_author */ + kind?: string; + /** @example 3c4d5e6f-7081-9234-5abc-def012345678 */ + id?: string; + /** @example 01HXXXXAUTHORBOT */ + user_uid?: string; + /** @example 1a2b... */ + category_id?: string; + /** @example ready */ + status?: string; + /** @example category */ + source?: string; + /** @example 家电保养小助手 */ + display_name?: string; + /** @example https://…/avatar.png */ + avatar?: string; + /** @example 务实、耐心的家电保养老师傅 */ + persona?: string; + article_style?: { + /** @example 亲切 */ + tone?: string; + }; + image_style?: { + /** @example 写实 */ + style?: string; + }; + publishing_rules?: { + /** @example 每周 2 篇 */ + cadence?: string; + }; + }[]; + pagination?: { + /** @example 1 */ + current_page?: number; + /** @example null */ + next_page?: string; + /** @example 20 */ + page_size?: number; + /** @example null */ + prev_page?: string; + /** @example 1 */ + total_count?: number; + /** @example 1 */ + total_pages?: number; + }; + }; + }; + }; + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + /** @example false */ + success?: boolean; + /** @example UNAUTHENTICATED */ + code?: string; + /** @example 未认证 */ + message?: string; + }; + }; + }; + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + /** @example false */ + success?: boolean; + /** @example FORBIDDEN */ + code?: string; + /** @example 无权限访问 */ + message?: string; + }; + }; + }; + }; + }; + put?: never; + /** + * [机器人作者] 创建 + * @description 绑定一个机器人账号(必须是 is_robot=true 的用户)作为某分类的内容作者。 + * 人设字段(persona / article_style / image_style / publishing_rules)写入账号级 + * profile,供该账号的所有作者身份与系列共享。需要 admin:studio:manage 权限。 + */ + post: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": { + /** + * @description 机器人账号 uid(须 is_robot=true)。 + * @example 01HXXXXAUTHORBOT + */ + user_uid: string; + /** + * @description 所属分类 UUID。 + * @example 1a2b3c4d-5e6f-7081-9234-abcdef012345 + */ + category_id: string; + /** + * @description 关联子话题 UUID。 + * @example 2b3c4d5e-6f70-8912-3abc-def012345678 + */ + subtopic_id?: string; + /** + * @description 账号人设描述(写入 profile)。 + * @example 务实、耐心的家电保养老师傅 + */ + persona?: string; + /** + * @description 文章风格配置(写入 profile)。 + * @example { + * "tone": "亲切", + * "length": "1200-1800字" + * } + */ + article_style?: Record; + /** + * @description 配图风格配置(写入 profile)。 + * @example { + * "style": "写实", + * "palette": "明亮" + * } + */ + image_style?: Record; + /** + * @description 发布规则(写入 profile)。 + * @example { + * "cadence": "每周 2 篇" + * } + */ + publishing_rules?: Record; + /** + * @description 状态:pending, profiling, ready, paused。默认 pending。 + * @example ready + */ + status?: string; + /** + * @description 来源:user, category。 + * @example category + */ + source?: string; + }; + }; + }; + responses: { + 201: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + /** @example true */ + success?: boolean; + data?: { + /** @example studio_robot_author */ + kind?: string; + /** @example 3c4d5e6f-7081-9234-5abc-def012345678 */ + id?: string; + /** @example 01HXXXXAUTHORBOT */ + user_uid?: string; + /** @example ready */ + status?: string; + /** @example 家电保养小助手 */ + display_name?: string; + /** @example 务实、耐心的家电保养老师傅 */ + persona?: string; + }; + }; + }; + }; + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + /** @example false */ + success?: boolean; + /** @example VALIDATION_ERROR */ + code?: string; + /** @example The given data was invalid. */ + message?: string; + data?: { + errors?: { + /** + * @example [ + * "The selected user uid is invalid." + * ] + */ + user_uid?: string[]; + }; + }; + }; + }; + }; + }; + }; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/admin/v1/studio/robot-authors/{id}": { + parameters: { + query?: never; + header?: never; + path: { + /** + * @description The ID of the robot author. + * @example architecto + */ + id: string; + }; + cookie?: never; + }; + /** + * 获取详情 + * @description 按 UUID 获取单个 Studio 实体,返回其全部属性(含 kind 判别字段)。 + * 需要 admin:studio:read 权限。 + */ + get: { + parameters: { + query?: never; + header?: never; + path: { + /** + * @description The ID of the robot author. + * @example architecto + */ + id: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + /** @example true */ + success?: boolean; + data?: { + /** @example studio_subtopic */ + kind?: string; + /** @example 9f1c2d3e-4b5a-6789-0abc-def012345678 */ + id?: string; + /** @example active */ + status?: string; + /** @example 2026-07-18T09:00:00.000000Z */ + created_at?: string; + /** @example 2026-07-18T09:00:00.000000Z */ + updated_at?: string; + }; + }; + }; + }; + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + /** @example false */ + success?: boolean; + /** @example UNAUTHENTICATED */ + code?: string; + /** @example 未认证 */ + message?: string; + }; + }; + }; + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + /** @example false */ + success?: boolean; + /** @example FORBIDDEN */ + code?: string; + /** @example 无权限访问 */ + message?: string; + }; + }; + }; + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + /** @example false */ + success?: boolean; + /** @example NOT_FOUND */ + code?: string; + /** @example 请求的资源未找到。 */ + message?: string; + }; + }; + }; + }; + }; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/admin/v1/studio/series": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * [文章系列] 列表 + * @description 分页返回文章系列,可按 category_id、subtopic_id、robot_author_id、status 过滤。 + * 需要 admin:studio:read 权限。 + */ + get: { + parameters: { + query?: { + /** + * @description 按所属分类过滤(UUID)。 + * @example 9f1c2d3e-4b5a-6789-0abc-def012345678 + */ + category_id?: string; + /** + * @description 按所属子话题过滤(UUID)。 + * @example 2b3c4d5e-6f70-8912-3abc-def012345678 + */ + subtopic_id?: string; + /** + * @description 按机器人作者过滤(UUID)。 + * @example 3c4d5e6f-7081-9234-5abc-def012345678 + */ + robot_author_id?: string; + /** + * @description 按状态过滤:planned, in_progress, complete。 + * @example in_progress + */ + status?: string; + /** + * @description 每页数量,1-100,默认 20。 + * @example 20 + */ + page_size?: number; + /** + * @description 页码,默认 1。 + * @example 1 + */ + page?: number; + }; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + /** @example true */ + success?: boolean; + /** + * @example [ + * { + * "kind": "studio_series", + * "id": "9f1c2d3e-4b5a-6789-0abc-def012345678", + * "category_id": "1a2b...", + * "robot_author_id": "3c4d...", + * "goal": "系统讲透家用空调清洗保养", + * "target_article_count": 8, + * "status": "in_progress" + * } + * ] + */ + data?: { + /** @example studio_series */ + kind?: string; + /** @example 9f1c2d3e-4b5a-6789-0abc-def012345678 */ + id?: string; + /** @example 1a2b... */ + category_id?: string; + /** @example 3c4d... */ + robot_author_id?: string; + /** @example 系统讲透家用空调清洗保养 */ + goal?: string; + /** @example 8 */ + target_article_count?: number; + /** @example in_progress */ + status?: string; + }[]; + pagination?: { + /** @example 1 */ + current_page?: number; + /** @example null */ + next_page?: string; + /** @example 20 */ + page_size?: number; + /** @example null */ + prev_page?: string; + /** @example 1 */ + total_count?: number; + /** @example 1 */ + total_pages?: number; + }; + }; + }; + }; + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + /** @example false */ + success?: boolean; + /** @example UNAUTHENTICATED */ + code?: string; + /** @example 未认证 */ + message?: string; + }; + }; + }; + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + /** @example false */ + success?: boolean; + /** @example FORBIDDEN */ + code?: string; + /** @example 无权限访问 */ + message?: string; + }; + }; + }; + }; + }; + put?: never; + /** + * [文章系列] 创建 + * @description 为某机器人作者在某分类下规划一个文章系列。需要 admin:studio:manage 权限。 + */ + post: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": { + /** + * @description 所属分类 UUID。 + * @example 9f1c2d3e-4b5a-6789-0abc-def012345678 + */ + category_id: string; + /** + * @description 机器人作者 UUID。 + * @example 3c4d5e6f-7081-9234-5abc-def012345678 + */ + robot_author_id: string; + /** + * @description 关联子话题 UUID。 + * @example 2b3c4d5e-6f70-8912-3abc-def012345678 + */ + subtopic_id?: string; + /** + * @description 系列目标。 + * @example 系统讲透家用空调清洗保养 + */ + goal?: string; + /** + * @description 系列定位。 + * @example 面向新手的保姆级实操 + */ + positioning?: string; + /** + * @description 目标读者。 + * @example 第一次自己清洗空调的家庭用户 + */ + target_readers?: string; + /** + * @description 读者阅读旅程。 + * @example 从认知清洗必要性到能独立完成 + */ + reader_journey?: string; + /** + * @description 计划文章数,>=0。 + * @example 8 + */ + target_article_count?: number; + /** + * @description 计划中的文章大纲列表。 + * @example [ + * [] + * ] + */ + planned_articles?: Record[]; + /** + * @description 状态:planned, in_progress, complete。默认 planned。 + * @example planned + */ + status?: string; + }; + }; + }; + responses: { + 201: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + /** @example true */ + success?: boolean; + data?: { + /** @example studio_series */ + kind?: string; + /** @example 9f1c2d3e-4b5a-6789-0abc-def012345678 */ + id?: string; + /** @example 系统讲透家用空调清洗保养 */ + goal?: string; + /** @example planned */ + status?: string; + }; + }; + }; + }; + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + /** @example false */ + success?: boolean; + /** @example VALIDATION_ERROR */ + code?: string; + /** @example The given data was invalid. */ + message?: string; + data?: { + errors?: { + /** + * @example [ + * "The robot author id field is required." + * ] + */ + robot_author_id?: string[]; + }; + }; + }; + }; + }; + }; + }; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/admin/v1/studio/series/{id}": { + parameters: { + query?: never; + header?: never; + path: { + /** + * @description The ID of the series. + * @example architecto + */ + id: string; + }; + cookie?: never; + }; + /** + * 获取详情 + * @description 按 UUID 获取单个 Studio 实体,返回其全部属性(含 kind 判别字段)。 + * 需要 admin:studio:read 权限。 + */ + get: { + parameters: { + query?: never; + header?: never; + path: { + /** + * @description The ID of the series. + * @example architecto + */ + id: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + /** @example true */ + success?: boolean; + data?: { + /** @example studio_subtopic */ + kind?: string; + /** @example 9f1c2d3e-4b5a-6789-0abc-def012345678 */ + id?: string; + /** @example active */ + status?: string; + /** @example 2026-07-18T09:00:00.000000Z */ + created_at?: string; + /** @example 2026-07-18T09:00:00.000000Z */ + updated_at?: string; + }; + }; + }; + }; + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + /** @example false */ + success?: boolean; + /** @example UNAUTHENTICATED */ + code?: string; + /** @example 未认证 */ + message?: string; + }; + }; + }; + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + /** @example false */ + success?: boolean; + /** @example FORBIDDEN */ + code?: string; + /** @example 无权限访问 */ + message?: string; + }; + }; + }; + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + /** @example false */ + success?: boolean; + /** @example NOT_FOUND */ + code?: string; + /** @example 请求的资源未找到。 */ + message?: string; + }; + }; + }; + }; + }; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/admin/v1/studio/articles/{article}/preview": { + parameters: { + query?: never; + header?: never; + path: { + /** + * @description 文章 UUID。 + * @example 9f1c2d3e-4b5a-6789-0abc-def012345678 + */ + article: string; + }; + cookie?: never; + }; + /** + * [文章] Dimzou 草稿预览 + * @description 渲染文章当前 Dimzou 草稿(无草稿时回退到最新已发布版本)的正文为 HTML, + * 连同标题/摘要/封面返回,供后台预览。需要 admin:studio:read 权限,绕过 Dimzou 逐用户读权限。 + */ + get: operations["Dimzou"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/admin/v1/studio/articles": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * [文章] 列表 + * @description 分页返回文章,可按 series_id、robot_author_id、category_id、subtopic_id、status 过滤。 + * 需要 admin:studio:read 权限。 + */ + get: { + parameters: { + query?: { + /** + * @description 按所属系列过滤(UUID)。 + * @example 9f1c2d3e-4b5a-6789-0abc-def012345678 + */ + series_id?: string; + /** + * @description 按机器人作者过滤(UUID)。 + * @example 3c4d5e6f-7081-9234-5abc-def012345678 + */ + robot_author_id?: string; + /** + * @description 按所属分类过滤(UUID)。 + * @example 1a2b3c4d-5e6f-7081-9234-abcdef012345 + */ + category_id?: string; + /** + * @description 按所属子话题过滤(UUID)。 + * @example 2b3c4d5e-6f70-8912-3abc-def012345678 + */ + subtopic_id?: string; + /** + * @description 按状态过滤:planned, drafting, drafted, published, scheduled。 + * @example drafted + */ + status?: string; + /** + * @description 每页数量,1-100,默认 20。 + * @example 20 + */ + page_size?: number; + /** + * @description 页码,默认 1。 + * @example 1 + */ + page?: number; + }; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + /** @example true */ + success?: boolean; + /** + * @example [ + * { + * "kind": "studio_article", + * "id": "9f1c2d3e-4b5a-6789-0abc-def012345678", + * "series_id": "…", + * "article_number": 1, + * "title": "空调多久洗一次?一篇讲清楚", + * "status": "drafted", + * "dimzou_document_id": null + * } + * ] + */ + data?: { + /** @example studio_article */ + kind?: string; + /** @example 9f1c2d3e-4b5a-6789-0abc-def012345678 */ + id?: string; + /** @example … */ + series_id?: string; + /** @example 1 */ + article_number?: number; + /** @example 空调多久洗一次?一篇讲清楚 */ + title?: string; + /** @example drafted */ + status?: string; + /** @example null */ + dimzou_document_id?: string; + }[]; + pagination?: { + /** @example 1 */ + current_page?: number; + /** @example null */ + next_page?: string; + /** @example 20 */ + page_size?: number; + /** @example null */ + prev_page?: string; + /** @example 1 */ + total_count?: number; + /** @example 1 */ + total_pages?: number; + }; + }; + }; + }; + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + /** @example false */ + success?: boolean; + /** @example UNAUTHENTICATED */ + code?: string; + /** @example 未认证 */ + message?: string; + }; + }; + }; + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + /** @example false */ + success?: boolean; + /** @example FORBIDDEN */ + code?: string; + /** @example 无权限访问 */ + message?: string; + }; + }; + }; + }; + }; + put?: never; + /** + * [文章] 创建 + * @description 在某系列下规划一篇文章。正文(大纲/草稿)由外部 Agent 通过 MCP 工具补全, + * 封面/内文图由 Agent 上传。需要 admin:studio:manage 权限。 + */ + post: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": { + /** + * @description 所属系列 UUID。 + * @example 9f1c2d3e-4b5a-6789-0abc-def012345678 + */ + series_id: string; + /** + * @description 机器人作者 UUID。 + * @example 3c4d5e6f-7081-9234-5abc-def012345678 + */ + robot_author_id: string; + /** + * @description 所属分类 UUID。 + * @example 1a2b3c4d-5e6f-7081-9234-abcdef012345 + */ + category_id: string; + /** + * @description 关联子话题 UUID。 + * @example 2b3c4d5e-6f70-8912-3abc-def012345678 + */ + subtopic_id?: string; + /** + * @description 系列内序号,>=1。 + * @example 1 + */ + article_number?: number; + /** + * @description 选题/写作计划。 + * @example [] + */ + plan?: Record; + /** + * @description 文章大纲。 + * @example [] + */ + outline?: Record; + /** + * @description 标题,最长 255。 + * @example 空调多久洗一次?一篇讲清楚 + */ + title?: string; + /** + * @description 摘要。 + * @example 从健康与能耗角度给出清洗周期建议 + */ + summary?: string; + /** + * @description 核心要点列表。 + * @example [ + * "建议每年清洗 1-2 次", + * "自检 3 个信号" + * ] + */ + key_takeaways?: string[]; + /** + * @description 行动号召文案。 + * @example 收藏本文,换季前照着做 + */ + cta?: string; + /** + * @description 标签。 + * @example [ + * "空调", + * "清洗", + * "家电保养" + * ] + */ + tags?: string[]; + /** + * @description SEO 关键词。 + * @example [ + * "空调清洗周期", + * "空调多久洗一次" + * ] + */ + seo_keywords?: string[]; + /** + * @description 封面图生成提示词。 + * @example 明亮客厅中清洗空调的写实场景 + */ + cover_image_prompt?: string; + /** + * @description 内文配图提示词列表。 + * @example [ + * "architecto" + * ] + */ + inline_image_prompts?: string[]; + /** + * @description 状态:planned, drafting, drafted, published, scheduled。默认 planned。 + * @example planned + */ + status?: string; + /** + * @description 生成所用模型的元信息。 + * @example [] + */ + drafting_model?: Record; + /** + * @description 发布排期配置。 + * @example [] + */ + publish_schedule?: Record; + }; + }; + }; + responses: { + 201: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + /** @example true */ + success?: boolean; + data?: { + /** @example studio_article */ + kind?: string; + /** @example 9f1c2d3e-4b5a-6789-0abc-def012345678 */ + id?: string; + /** @example … */ + series_id?: string; + /** @example 1 */ + article_number?: number; + /** @example 空调多久洗一次?一篇讲清楚 */ + title?: string; + /** @example planned */ + status?: string; + }; + }; + }; + }; + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + /** @example false */ + success?: boolean; + /** @example VALIDATION_ERROR */ + code?: string; + /** @example The given data was invalid. */ + message?: string; + data?: { + errors?: { + /** + * @example [ + * "The series id field is required." + * ] + */ + series_id?: string[]; + }; + }; + }; + }; + }; + }; + }; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/admin/v1/studio/articles/{id}": { + parameters: { + query?: never; + header?: never; + path: { + /** + * @description The ID of the article. + * @example architecto + */ + id: string; + }; + cookie?: never; + }; + /** + * 获取详情 + * @description 按 UUID 获取单个 Studio 实体,返回其全部属性(含 kind 判别字段)。 + * 需要 admin:studio:read 权限。 + */ + get: { + parameters: { + query?: never; + header?: never; + path: { + /** + * @description The ID of the article. + * @example architecto + */ + id: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + /** @example true */ + success?: boolean; + data?: { + /** @example studio_subtopic */ + kind?: string; + /** @example 9f1c2d3e-4b5a-6789-0abc-def012345678 */ + id?: string; + /** @example active */ + status?: string; + /** @example 2026-07-18T09:00:00.000000Z */ + created_at?: string; + /** @example 2026-07-18T09:00:00.000000Z */ + updated_at?: string; + }; + }; + }; + }; + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + /** @example false */ + success?: boolean; + /** @example UNAUTHENTICATED */ + code?: string; + /** @example 未认证 */ + message?: string; + }; + }; + }; + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + /** @example false */ + success?: boolean; + /** @example FORBIDDEN */ + code?: string; + /** @example 无权限访问 */ + message?: string; + }; + }; + }; + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + /** @example false */ + success?: boolean; + /** @example NOT_FOUND */ + code?: string; + /** @example 请求的资源未找到。 */ + message?: string; + }; + }; + }; + }; + }; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/admin/v1/studio/style-presets": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * [样式预设] 列表 + * @description 分页返回样式预设(文章 / 图片 / 组合),可按 type 过滤。需要 admin:studio:read 权限。 + */ + get: { + parameters: { + query?: { + /** + * @description 按类型过滤:article, image, bundle。 + * @example article + */ + type?: string; + /** + * @description 每页数量,1-100,默认 20。 + * @example 20 + */ + page_size?: number; + /** + * @description 页码,默认 1。 + * @example 1 + */ + page?: number; + }; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + /** @example true */ + success?: boolean; + /** + * @example [ + * { + * "kind": "studio_style_preset", + * "id": "9f1c2d3e-4b5a-6789-0abc-def012345678", + * "type": "article", + * "name": "口语化科普风", + * "payload": { + * "tone": "亲切", + * "length": "1200-1800字" + * } + * } + * ] + */ + data?: { + /** @example studio_style_preset */ + kind?: string; + /** @example 9f1c2d3e-4b5a-6789-0abc-def012345678 */ + id?: string; + /** @example article */ + type?: string; + /** @example 口语化科普风 */ + name?: string; + payload?: { + /** @example 亲切 */ + tone?: string; + /** @example 1200-1800字 */ + length?: string; + }; + }[]; + pagination?: { + /** @example 1 */ + current_page?: number; + /** @example null */ + next_page?: string; + /** @example 20 */ + page_size?: number; + /** @example null */ + prev_page?: string; + /** @example 1 */ + total_count?: number; + /** @example 1 */ + total_pages?: number; + }; + }; + }; + }; + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + /** @example false */ + success?: boolean; + /** @example UNAUTHENTICATED */ + code?: string; + /** @example 未认证 */ + message?: string; + }; + }; + }; + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + /** @example false */ + success?: boolean; + /** @example FORBIDDEN */ + code?: string; + /** @example 无权限访问 */ + message?: string; + }; + }; + }; + }; + }; + put?: never; + /** + * [样式预设] 创建 + * @description 新建一个可复用的写作/绘图样式预设,供机器人作者与文章生成引用。 + * 需要 admin:studio:manage 权限。 + */ + post: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": { + /** + * @description 预设类型:article, image, bundle。 + * @example article + */ + type: string; + /** + * @description 预设名称,最长 255。 + * @example 口语化科普风 + */ + name: string; + /** + * @description 预设内容(结构随 type 而定)。 + * @example { + * "tone": "亲切", + * "length": "1200-1800字" + * } + */ + payload?: Record; + }; + }; + }; + responses: { + 201: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + /** @example true */ + success?: boolean; + data?: { + /** @example studio_style_preset */ + kind?: string; + /** @example 9f1c2d3e-4b5a-6789-0abc-def012345678 */ + id?: string; + /** @example article */ + type?: string; + /** @example 口语化科普风 */ + name?: string; + }; + }; + }; + }; + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + /** @example false */ + success?: boolean; + /** @example VALIDATION_ERROR */ + code?: string; + /** @example The given data was invalid. */ + message?: string; + data?: { + errors?: { + /** + * @example [ + * "The selected type is invalid." + * ] + */ + type?: string[]; + }; + }; + }; + }; + }; + }; + }; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/admin/v1/studio/style-presets/{id}": { + parameters: { + query?: never; + header?: never; + path: { + /** + * @description The ID of the style preset. + * @example architecto + */ + id: string; + }; + cookie?: never; + }; + /** + * 获取详情 + * @description 按 UUID 获取单个 Studio 实体,返回其全部属性(含 kind 判别字段)。 + * 需要 admin:studio:read 权限。 + */ + get: { + parameters: { + query?: never; + header?: never; + path: { + /** + * @description The ID of the style preset. + * @example architecto + */ + id: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + /** @example true */ + success?: boolean; + data?: { + /** @example studio_subtopic */ + kind?: string; + /** @example 9f1c2d3e-4b5a-6789-0abc-def012345678 */ + id?: string; + /** @example active */ + status?: string; + /** @example 2026-07-18T09:00:00.000000Z */ + created_at?: string; + /** @example 2026-07-18T09:00:00.000000Z */ + updated_at?: string; + }; + }; + }; + }; + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + /** @example false */ + success?: boolean; + /** @example UNAUTHENTICATED */ + code?: string; + /** @example 未认证 */ + message?: string; + }; + }; + }; + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + /** @example false */ + success?: boolean; + /** @example FORBIDDEN */ + code?: string; + /** @example 无权限访问 */ + message?: string; + }; + }; + }; + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + /** @example false */ + success?: boolean; + /** @example NOT_FOUND */ + code?: string; + /** @example 请求的资源未找到。 */ + message?: string; + }; + }; + }; + }; + }; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/admin/v1/studio/category-briefs/{category_brief_id}": { + parameters: { + query?: never; + header?: never; + path: { + /** + * @description The ID of the category brief. + * @example architecto + */ + category_brief_id: string; + }; + cookie?: never; + }; + get?: never; + /** + * [内容分类简报] 更新 + * @description 部分更新,所有字段均可选。需要 admin:studio:manage 权限。 + */ + put: { + parameters: { + query?: never; + header?: never; + path: { + /** + * @description The ID of the category brief. + * @example architecto + */ + category_brief_id: string; + }; + cookie?: never; + }; + requestBody?: { + content: { + "application/json": { + /** + * @description 分类内容定位描述。 + * @example 家电清洗、保养与维修 + */ + description?: string; + /** + * @description 目标读者。 + * @example architecto + */ + target_audience?: string; + /** + * @description 内容切入角度列表。 + * @example [ + * "architecto" + * ] + */ + content_angles?: string[]; + /** + * @description 内容风险/合规备注。 + * @example architecto + */ + risk_notes?: string; + }; + }; + }; + responses: { + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + /** @example true */ + success?: boolean; + data?: { + /** @example studio_category_brief */ + kind?: string; + /** @example 9f1c2d3e-4b5a-6789-0abc-def012345678 */ + id?: string; + /** @example 家电清洗、保养与维修 */ + description?: string; + }; + }; + }; + }; + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + /** @example false */ + success?: boolean; + /** @example NOT_FOUND */ + code?: string; + /** @example 请求的资源未找到。 */ + message?: string; + }; + }; + }; + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + /** @example false */ + success?: boolean; + /** @example VALIDATION_ERROR */ + code?: string; + /** @example The given data was invalid. */ + message?: string; + }; + }; + }; + }; + }; + post?: never; + /** + * 删除 + * @description 按 UUID 删除单个 Studio 实体。需要 admin:studio:manage 权限。 + */ + delete: { + parameters: { + query?: never; + header?: never; + path: { + /** + * @description The ID of the category brief. + * @example architecto + */ + category_brief_id: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + /** @example true */ + success?: boolean; + /** @example null */ + data?: string; + }; + }; + }; + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + /** @example false */ + success?: boolean; + /** @example UNAUTHENTICATED */ + code?: string; + /** @example 未认证 */ + message?: string; + }; + }; + }; + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + /** @example false */ + success?: boolean; + /** @example FORBIDDEN */ + code?: string; + /** @example 无权限访问 */ + message?: string; + }; + }; + }; + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + /** @example false */ + success?: boolean; + /** @example NOT_FOUND */ + code?: string; + /** @example 请求的资源未找到。 */ + message?: string; + }; + }; + }; + }; + }; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/admin/v1/studio/subtopics/{subtopic_id}": { + parameters: { + query?: never; + header?: never; + path: { + /** + * @description The ID of the subtopic. + * @example architecto + */ + subtopic_id: string; + }; + cookie?: never; + }; + get?: never; + /** + * [子话题] 更新 + * @description 部分更新,所有字段均可选。需要 admin:studio:manage 权限。 + */ + put: { + parameters: { + query?: never; + header?: never; + path: { + /** + * @description The ID of the subtopic. + * @example architecto + */ + subtopic_id: string; + }; + cookie?: never; + }; + requestBody?: { + content: { + "application/json": { + /** + * @description 子话题标题,最长 255。 + * @example 空调清洗保养指南(2026 版) + */ + title?: string; + /** + * @description 目标读者。 + * @example architecto + */ + target_audience?: string; + /** + * @description 读者痛点。 + * @example architecto + */ + pain_points?: string; + /** + * @description 内容价值主张。 + * @example architecto + */ + content_value?: string; + /** + * @description 可扩展性评分,0-255。 + * @example 90 + */ + expandability_score?: number; + /** + * @description 状态:active, archived。 + * @example archived + */ + status?: string; + }; + }; + }; + responses: { + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + /** @example true */ + success?: boolean; + data?: { + /** @example studio_subtopic */ + kind?: string; + /** @example 9f1c2d3e-4b5a-6789-0abc-def012345678 */ + id?: string; + /** @example archived */ + status?: string; + }; + }; + }; + }; + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + /** @example false */ + success?: boolean; + /** @example NOT_FOUND */ + code?: string; + /** @example 请求的资源未找到。 */ + message?: string; + }; + }; + }; + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + /** @example false */ + success?: boolean; + /** @example VALIDATION_ERROR */ + code?: string; + /** @example The given data was invalid. */ + message?: string; + data?: { + errors?: { + /** + * @example [ + * "The selected status is invalid." + * ] + */ + status?: string[]; + }; + }; + }; + }; + }; + }; + }; + post?: never; + /** + * 删除 + * @description 按 UUID 删除单个 Studio 实体。需要 admin:studio:manage 权限。 + */ + delete: { + parameters: { + query?: never; + header?: never; + path: { + /** + * @description The ID of the subtopic. + * @example architecto + */ + subtopic_id: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + /** @example true */ + success?: boolean; + /** @example null */ + data?: string; + }; + }; + }; + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + /** @example false */ + success?: boolean; + /** @example UNAUTHENTICATED */ + code?: string; + /** @example 未认证 */ + message?: string; + }; + }; + }; + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + /** @example false */ + success?: boolean; + /** @example FORBIDDEN */ + code?: string; + /** @example 无权限访问 */ + message?: string; + }; + }; + }; + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + /** @example false */ + success?: boolean; + /** @example NOT_FOUND */ + code?: string; + /** @example 请求的资源未找到。 */ + message?: string; + }; + }; + }; + }; + }; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/admin/v1/studio/robot-authors/{robot_author_id}": { + parameters: { + query?: never; + header?: never; + path: { + /** + * @description The ID of the robot author. + * @example architecto + */ + robot_author_id: string; + }; + cookie?: never; + }; + get?: never; + /** + * [机器人作者] 更新 + * @description 部分更新,所有字段均可选。人设字段(persona / article_style / image_style / + * publishing_rules)会同步到账号级 profile。需要 admin:studio:manage 权限。 + */ + put: { + parameters: { + query?: never; + header?: never; + path: { + /** + * @description The ID of the robot author. + * @example architecto + */ + robot_author_id: string; + }; + cookie?: never; + }; + requestBody?: { + content: { + "application/json": { + /** + * @description 关联子话题 UUID。 + * @example 2b3c4d5e-6f70-8912-3abc-def012345678 + */ + subtopic_id?: string; + /** + * @description 账号人设描述(写入 profile)。 + * @example 务实、耐心的家电保养老师傅 + */ + persona?: string; + /** + * @description 文章风格配置(写入 profile)。 + * @example [] + */ + article_style?: Record; + /** + * @description 配图风格配置(写入 profile)。 + * @example [] + */ + image_style?: Record; + /** + * @description 发布规则(写入 profile)。 + * @example [] + */ + publishing_rules?: Record; + /** + * @description 状态:pending, profiling, ready, paused。 + * @example paused + */ + status?: string; + /** + * @description 来源:user, category。 + * @example category + */ + source?: string; + }; + }; + }; + responses: { + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + /** @example true */ + success?: boolean; + data?: { + /** @example studio_robot_author */ + kind?: string; + /** @example 3c4d5e6f-7081-9234-5abc-def012345678 */ + id?: string; + /** @example paused */ + status?: string; + }; + }; + }; + }; + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + /** @example false */ + success?: boolean; + /** @example NOT_FOUND */ + code?: string; + /** @example 请求的资源未找到。 */ + message?: string; + }; + }; + }; + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + /** @example false */ + success?: boolean; + /** @example VALIDATION_ERROR */ + code?: string; + /** @example The given data was invalid. */ + message?: string; + }; + }; + }; + }; + }; + post?: never; + /** + * 删除 + * @description 按 UUID 删除单个 Studio 实体。需要 admin:studio:manage 权限。 + */ + delete: { + parameters: { + query?: never; + header?: never; + path: { + /** + * @description The ID of the robot author. + * @example architecto + */ + robot_author_id: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + /** @example true */ + success?: boolean; + /** @example null */ + data?: string; + }; + }; + }; + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + /** @example false */ + success?: boolean; + /** @example UNAUTHENTICATED */ + code?: string; + /** @example 未认证 */ + message?: string; + }; + }; + }; + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + /** @example false */ + success?: boolean; + /** @example FORBIDDEN */ + code?: string; + /** @example 无权限访问 */ + message?: string; + }; + }; + }; + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + /** @example false */ + success?: boolean; + /** @example NOT_FOUND */ + code?: string; + /** @example 请求的资源未找到。 */ + message?: string; + }; + }; + }; + }; + }; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/admin/v1/studio/series/{series_id}": { + parameters: { + query?: never; + header?: never; + path: { + /** + * @description The ID of the series. + * @example architecto + */ + series_id: string; + }; + cookie?: never; + }; + get?: never; + /** + * [文章系列] 更新 + * @description 部分更新,所有字段均可选。需要 admin:studio:manage 权限。 + */ + put: { + parameters: { + query?: never; + header?: never; + path: { + /** + * @description The ID of the series. + * @example architecto + */ + series_id: string; + }; + cookie?: never; + }; + requestBody?: { + content: { + "application/json": { + /** + * @description 系列目标。 + * @example 系统讲透家用空调清洗保养与选购 + */ + goal?: string; + /** + * @description 系列定位。 + * @example architecto + */ + positioning?: string; + /** + * @description 目标读者。 + * @example architecto + */ + target_readers?: string; + /** + * @description 读者阅读旅程。 + * @example architecto + */ + reader_journey?: string; + /** + * @description 计划文章数,>=0。 + * @example 10 + */ + target_article_count?: number; + /** + * @description 计划中的文章大纲列表。 + * @example [ + * [] + * ] + */ + planned_articles?: Record[]; + /** + * @description 状态:planned, in_progress, complete。 + * @example complete + */ + status?: string; + }; + }; + }; + responses: { + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + /** @example true */ + success?: boolean; + data?: { + /** @example studio_series */ + kind?: string; + /** @example 9f1c2d3e-4b5a-6789-0abc-def012345678 */ + id?: string; + /** @example complete */ + status?: string; + }; + }; + }; + }; + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + /** @example false */ + success?: boolean; + /** @example NOT_FOUND */ + code?: string; + /** @example 请求的资源未找到。 */ + message?: string; + }; + }; + }; + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + /** @example false */ + success?: boolean; + /** @example VALIDATION_ERROR */ + code?: string; + /** @example The given data was invalid. */ + message?: string; + }; + }; + }; + }; + }; + post?: never; + /** + * 删除 + * @description 按 UUID 删除单个 Studio 实体。需要 admin:studio:manage 权限。 + */ + delete: { + parameters: { + query?: never; + header?: never; + path: { + /** + * @description The ID of the series. + * @example architecto + */ + series_id: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + /** @example true */ + success?: boolean; + /** @example null */ + data?: string; + }; + }; + }; + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + /** @example false */ + success?: boolean; + /** @example UNAUTHENTICATED */ + code?: string; + /** @example 未认证 */ + message?: string; + }; + }; + }; + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + /** @example false */ + success?: boolean; + /** @example FORBIDDEN */ + code?: string; + /** @example 无权限访问 */ + message?: string; + }; + }; + }; + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + /** @example false */ + success?: boolean; + /** @example NOT_FOUND */ + code?: string; + /** @example 请求的资源未找到。 */ + message?: string; + }; + }; + }; + }; + }; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/admin/v1/studio/articles/{article_id}": { + parameters: { + query?: never; + header?: never; + path: { + /** + * @description The ID of the article. + * @example architecto + */ + article_id: string; + }; + cookie?: never; + }; + get?: never; + /** + * [文章] 更新 + * @description 部分更新,所有字段均可选。发布态字段(dimzou_document_id 等)由发布流程写入,不可直接更新。 + * 需要 admin:studio:manage 权限。 + */ + put: { + parameters: { + query?: never; + header?: never; + path: { + /** + * @description The ID of the article. + * @example architecto + */ + article_id: string; + }; + cookie?: never; + }; + requestBody?: { + content: { + "application/json": { + /** + * @description 标题,最长 255。 + * @example 空调多久洗一次?看完这篇就懂 + */ + title?: string; + /** + * @description 摘要。 + * @example architecto + */ + summary?: string; + /** + * @description 文章大纲。 + * @example [] + */ + outline?: Record; + /** + * @description 核心要点列表。 + * @example [ + * "architecto" + * ] + */ + key_takeaways?: string[]; + /** + * @description 标签。 + * @example [ + * "architecto" + * ] + */ + tags?: string[]; + /** + * @description SEO 关键词。 + * @example [ + * "architecto" + * ] + */ + seo_keywords?: string[]; + /** + * @description 状态:planned, drafting, drafted, published, scheduled。 + * @example drafted + */ + status?: string; + /** + * @description 发布排期配置。 + * @example [] + */ + publish_schedule?: Record; + }; + }; + }; + responses: { + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + /** @example true */ + success?: boolean; + data?: { + /** @example studio_article */ + kind?: string; + /** @example 9f1c2d3e-4b5a-6789-0abc-def012345678 */ + id?: string; + /** @example drafted */ + status?: string; + }; + }; + }; + }; + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + /** @example false */ + success?: boolean; + /** @example NOT_FOUND */ + code?: string; + /** @example 请求的资源未找到。 */ + message?: string; + }; + }; + }; + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + /** @example false */ + success?: boolean; + /** @example VALIDATION_ERROR */ + code?: string; + /** @example The given data was invalid. */ + message?: string; + data?: { + errors?: { + /** + * @example [ + * "The selected status is invalid." + * ] + */ + status?: string[]; + }; + }; + }; + }; + }; + }; + }; + post?: never; + /** + * 删除 + * @description 按 UUID 删除单个 Studio 实体。需要 admin:studio:manage 权限。 + */ + delete: { + parameters: { + query?: never; + header?: never; + path: { + /** + * @description The ID of the article. + * @example architecto + */ + article_id: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + /** @example true */ + success?: boolean; + /** @example null */ + data?: string; + }; + }; + }; + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + /** @example false */ + success?: boolean; + /** @example UNAUTHENTICATED */ + code?: string; + /** @example 未认证 */ + message?: string; + }; + }; + }; + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + /** @example false */ + success?: boolean; + /** @example FORBIDDEN */ + code?: string; + /** @example 无权限访问 */ + message?: string; + }; + }; + }; + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + /** @example false */ + success?: boolean; + /** @example NOT_FOUND */ + code?: string; + /** @example 请求的资源未找到。 */ + message?: string; + }; + }; + }; + }; + }; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/admin/v1/studio/style-presets/{style_preset_id}": { + parameters: { + query?: never; + header?: never; + path: { + /** + * @description The ID of the style preset. + * @example architecto + */ + style_preset_id: string; + }; + cookie?: never; + }; + get?: never; + /** + * [样式预设] 更新 + * @description 部分更新,所有字段均可选。需要 admin:studio:manage 权限。 + */ + put: { + parameters: { + query?: never; + header?: never; + path: { + /** + * @description The ID of the style preset. + * @example architecto + */ + style_preset_id: string; + }; + cookie?: never; + }; + requestBody?: { + content: { + "application/json": { + /** + * @description 预设类型:article, image, bundle。 + * @example article + */ + type?: string; + /** + * @description 预设名称,最长 255。 + * @example 口语化科普风(精简版) + */ + name?: string; + /** + * @description 预设内容(结构随 type 而定)。 + * @example [] + */ + payload?: Record; + }; + }; + }; + responses: { + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + /** @example true */ + success?: boolean; + data?: { + /** @example studio_style_preset */ + kind?: string; + /** @example 9f1c2d3e-4b5a-6789-0abc-def012345678 */ + id?: string; + /** @example 口语化科普风(精简版) */ + name?: string; + }; + }; + }; + }; + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + /** @example false */ + success?: boolean; + /** @example NOT_FOUND */ + code?: string; + /** @example 请求的资源未找到。 */ + message?: string; + }; + }; + }; + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + /** @example false */ + success?: boolean; + /** @example VALIDATION_ERROR */ + code?: string; + /** @example The given data was invalid. */ + message?: string; + }; + }; + }; + }; + }; + post?: never; + /** + * 删除 + * @description 按 UUID 删除单个 Studio 实体。需要 admin:studio:manage 权限。 + */ + delete: { + parameters: { + query?: never; + header?: never; + path: { + /** + * @description The ID of the style preset. + * @example architecto + */ + style_preset_id: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + /** @example true */ + success?: boolean; + /** @example null */ + data?: string; + }; + }; + }; + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + /** @example false */ + success?: boolean; + /** @example UNAUTHENTICATED */ + code?: string; + /** @example 未认证 */ + message?: string; + }; + }; + }; + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + /** @example false */ + success?: boolean; + /** @example FORBIDDEN */ + code?: string; + /** @example 无权限访问 */ + message?: string; + }; + }; + }; + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + /** @example false */ + success?: boolean; + /** @example NOT_FOUND */ + code?: string; + /** @example 请求的资源未找到。 */ + message?: string; + }; + }; + }; + }; + }; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/admin/v1/studio/agent/tokens": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** 查看当前有效的 Agent token 列表 */ + get: operations["AgentToken"]; + put?: never; + /** + * 签发新的 Agent token + * @description 返回的 access_token 仅此一次完整展示;配置到 Agent 的 MCP client + * 后即可访问 POST /mcp/studio。 + */ + post: operations["AgentToken"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/admin/v1/studio/agent/tokens/{tokenId}": { + parameters: { + query?: never; + header?: never; + path: { + /** @example architecto */ + tokenId: string; + }; + cookie?: never; + }; + get?: never; + put?: never; + post?: never; + /** 吊销一个 Agent token */ + delete: operations["AgentToken"]; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; "/api/admin/v1/exc/demands": { parameters: { query?: never; @@ -1917,7 +4371,7 @@ export interface paths { /** @example architecto */ status?: string | null; /** - * @example instant_accept + * @example scheduled_booking * @enum {string|null} */ dispatch_mode?: "instant_accept" | "quote" | "scheduled_booking" | "manual_selection" | null; @@ -2231,7 +4685,7 @@ export interface paths { content: { "application/json": { /** - * @example sent + * @example expired * @enum {string|null} */ status?: "sent" | "delivered" | "viewed" | "accepted" | "rejected" | "ignored" | "expired" | "taken_by_other" | null; @@ -2425,12 +4879,12 @@ export interface paths { content: { "application/json": { /** - * @example paid + * @example consumed * @enum {string|null} */ status?: "created" | "confirmed" | "started" | "consumed" | "paid" | "fulfilled" | "canceled" | null; /** - * @example PAID + * @example UNPAID * @enum {string|null} */ payment_status?: "UNPAID" | "PAID" | null; @@ -2709,13 +5163,13 @@ export interface paths { content: { "application/json": { /** - * @example offline + * @example busy * @enum {string|null} */ status?: "offline" | "online" | "inactive" | "busy" | null; - /** @example true */ + /** @example false */ is_disabled?: boolean | null; - /** @example true */ + /** @example false */ is_verified?: boolean | null; /** * @description Must be at least 1. @@ -2936,7 +5390,7 @@ export interface paths { patch?: never; trace?: never; }; - "/api/admin/v1/notification/devices": { + "/api/admin/v1/file-x/events": { parameters: { query?: never; header?: never; @@ -2944,29 +5398,44 @@ export interface paths { cookie?: never; }; /** - * [管理] 获取设备列表 - * @description 获取所有设备的分页列表,支持按平台、在线状态等筛选。 + * [事件监控] 事件列表 + * @description 只读列出全站事件动态,包含被屏蔽(is_blocked)的记录,支持按可见性、作者、屏蔽状态、话题及标题模糊搜索过滤。 */ get: { parameters: { query?: { /** - * @description 可选。按平台筛选 (ios, android, web, desktop) + * @description 可选。按可见性过滤: public, friends, private。 示例: public * @example architecto */ - platform?: string; + visibility?: string; /** - * @description 可选。按在线状态筛选 - * @example false + * @description 可选。按屏蔽状态过滤: blocked(已屏蔽), visible(未屏蔽), all(默认)。 示例: blocked + * @example architecto */ - is_online?: boolean; + status?: string; /** - * @description 可选。页码 (默认: 1) + * @description 可选。按作者过滤。 示例: 01HXXXX + * @example architecto + */ + user_uid?: string; + /** + * @description 可选。按主话题过滤。 示例: 家政保洁 + * @example architecto + */ + primary_topic?: string; + /** + * @description 可选。按标题模糊搜索。 示例: 空调 + * @example architecto + */ + q?: string; + /** + * @description 可选。页码。 示例: 1 * @example 16 */ page?: number; /** - * @description 可选。每页条数 (默认: 20, 最大: 100) + * @description 可选。每页数量,最大100。 示例: 20 * @example 16 */ page_size?: number; @@ -2979,20 +5448,35 @@ export interface paths { content: { "application/json": { /** - * @example android + * @example public * @enum {string|null} */ - platform?: "ios" | "android" | "web" | "desktop" | null; - /** @example false */ - is_online?: boolean | null; + visibility?: "public" | "friends" | "private" | null; + /** + * @example blocked + * @enum {string|null} + */ + status?: "blocked" | "visible" | "all" | null; + /** @example architecto */ + user_uid?: string | null; + /** + * @description Must not be greater than 100 characters. + * @example n + */ + primary_topic?: string | null; + /** + * @description Must not be greater than 100 characters. + * @example g + */ + q?: string | null; /** * @description Must be at least 1. - * @example 16 + * @example 66 */ page?: number | null; /** * @description Must be at least 1. Must not be greater than 100. - * @example 22 + * @example 17 */ page_size?: number | null; }; @@ -3010,70 +5494,78 @@ export interface paths { /** * @example [ * { - * "id": "01HN1234567890ABCDEF", - * "device_client_id": "device_123", - * "platform": "ios", - * "app_key": "app_v1", - * "device_model": "iPhone 12", - * "push_token": "push_token_here", - * "websocket_session_id": "ws_session_123", - * "last_seen_at": "2023-11-15T07:30:00.000000Z", - * "is_online": true, - * "kind": "device", - * "active_sessions_count": 2, - * "created_at": "2023-11-01T00:00:00.000000Z", - * "updated_at": "2023-11-15T07:30:00.000000Z" + * "kind": "filex_event", + * "id": 25, + * "title": "空调清洗上门服务记录", + * "visibility": "public", + * "primary_topic": "家政保洁", + * "is_blocked": false, + * "blocked_at": null, + * "blocked_by_uid": null, + * "author": { + * "uid": "01HXXXX", + * "display_name": "Alice" + * }, + * "likes_count": 5, + * "comments_count": 3, + * "open_reports_count": 0, + * "event_day": "2026-07-10", + * "created_at": "2026-07-10T09:00:00Z" * } * ] */ data?: { - /** @example 01HN1234567890ABCDEF */ - id?: string; - /** @example device_123 */ - device_client_id?: string; - /** @example ios */ - platform?: string; - /** @example app_v1 */ - app_key?: string; - /** @example iPhone 12 */ - device_model?: string; - /** @example push_token_here */ - push_token?: string; - /** @example ws_session_123 */ - websocket_session_id?: string; - /** @example 2023-11-15T07:30:00.000000Z */ - last_seen_at?: string; - /** @example true */ - is_online?: boolean; - /** @example device */ + /** @example filex_event */ kind?: string; - /** @example 2 */ - active_sessions_count?: number; - /** @example 2023-11-01T00:00:00.000000Z */ + /** @example 25 */ + id?: number; + /** @example 空调清洗上门服务记录 */ + title?: string; + /** @example public */ + visibility?: string; + /** @example 家政保洁 */ + primary_topic?: string; + /** @example false */ + is_blocked?: boolean; + /** @example null */ + blocked_at?: string; + /** @example null */ + blocked_by_uid?: string; + author?: { + /** @example 01HXXXX */ + uid?: string; + /** @example Alice */ + display_name?: string; + }; + /** @example 5 */ + likes_count?: number; + /** @example 3 */ + comments_count?: number; + /** @example 0 */ + open_reports_count?: number; + /** @example 2026-07-10 */ + event_day?: string; + /** @example 2026-07-10T09:00:00Z */ created_at?: string; - /** @example 2023-11-15T07:30:00.000000Z */ - updated_at?: string; }[]; pagination?: { /** @example 1 */ current_page?: number; - /** @example 2 */ - next_page?: number; + /** @example null */ + next_page?: string; /** @example 20 */ page_size?: number; /** @example null */ prev_page?: string; - /** @example 150 */ + /** @example 1 */ total_count?: number; - /** @example 8 */ + /** @example 1 */ total_pages?: number; }; - /** @example Devices retrieved successfully */ - message?: string; }; }; }; - 422: { + 401: { headers: { [name: string]: unknown; }; @@ -3081,24 +5573,25 @@ export interface paths { "application/json": { /** @example false */ success?: boolean; - /** @example VALIDATION_ERROR */ + /** @example UNAUTHENTICATED */ code?: string; - /** @example Validation failed */ + /** @example 未认证 */ + message?: string; + }; + }; + }; + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + /** @example false */ + success?: boolean; + /** @example FORBIDDEN */ + code?: string; + /** @example 无权限访问 */ message?: string; - data?: { - /** - * @example [ - * "The selected platform is invalid." - * ] - */ - platform?: string[]; - /** - * @example [ - * "The is_online field must be true or false." - * ] - */ - is_online?: string[]; - }; }; }; }; @@ -3112,225 +5605,134 @@ export interface paths { patch?: never; trace?: never; }; - "/api/admin/v1/studio/agent/tokens": { - parameters: { - query?: never; - header?: never; - path?: never; - cookie?: never; - }; - /** 查看当前有效的 Agent token 列表 */ - get: operations["AgentToken"]; - put?: never; - /** - * 签发新的 Agent token - * @description 返回的 access_token 仅此一次完整展示;配置到 Agent 的 MCP client - * 后即可访问 POST /mcp/studio。 - */ - post: operations["AgentToken"]; - delete?: never; - options?: never; - head?: never; - patch?: never; - trace?: never; - }; - "/api/admin/v1/studio/agent/tokens/{tokenId}": { - parameters: { - query?: never; - header?: never; - path: { - /** @example architecto */ - tokenId: string; - }; - cookie?: never; - }; - get?: never; - put?: never; - post?: never; - /** 吊销一个 Agent token */ - delete: operations["AgentToken"]; - options?: never; - head?: never; - patch?: never; - trace?: never; - }; - "/api/admin/v1/studio/category-briefs": { - parameters: { - query?: never; - header?: never; - path?: never; - cookie?: never; - }; - get: operations["getApiAdminV1StudioCategoryBriefs"]; - put?: never; - post: operations["postApiAdminV1StudioCategoryBriefs"]; - delete?: never; - options?: never; - head?: never; - patch?: never; - trace?: never; - }; - "/api/admin/v1/studio/category-briefs/{id}": { + "/api/admin/v1/file-x/events/{id}": { parameters: { query?: never; header?: never; path: { /** - * @description The ID of the category brief. - * @example architecto + * @description 事件 ID。 + * @example 25 */ - id: string; - }; - cookie?: never; - }; - get: operations["getApiAdminV1StudioCategoryBriefsId"]; - put?: never; - post?: never; - delete?: never; - options?: never; - head?: never; - patch?: never; - trace?: never; - }; - "/api/admin/v1/studio/category-briefs/{category_brief_id}": { - parameters: { - query?: never; - header?: never; - path: { - /** - * @description The ID of the category brief. - * @example architecto - */ - category_brief_id: string; - }; - cookie?: never; - }; - get?: never; - put: operations["putApiAdminV1StudioCategoryBriefsCategory_brief_id"]; - post?: never; - delete: operations["deleteApiAdminV1StudioCategoryBriefsCategory_brief_id"]; - options?: never; - head?: never; - patch?: never; - trace?: never; - }; - "/api/admin/v1/studio/subtopics": { - parameters: { - query?: never; - header?: never; - path?: never; - cookie?: never; - }; - get: operations["getApiAdminV1StudioSubtopics"]; - put?: never; - post: operations["postApiAdminV1StudioSubtopics"]; - delete?: never; - options?: never; - head?: never; - patch?: never; - trace?: never; - }; - "/api/admin/v1/studio/subtopics/{id}": { - parameters: { - query?: never; - header?: never; - path: { - /** - * @description The ID of the subtopic. - * @example architecto - */ - id: string; - }; - cookie?: never; - }; - get: operations["getApiAdminV1StudioSubtopicsId"]; - put?: never; - post?: never; - delete?: never; - options?: never; - head?: never; - patch?: never; - trace?: never; - }; - "/api/admin/v1/studio/subtopics/{subtopic_id}": { - parameters: { - query?: never; - header?: never; - path: { - /** - * @description The ID of the subtopic. - * @example architecto - */ - subtopic_id: string; - }; - cookie?: never; - }; - get?: never; - put: operations["putApiAdminV1StudioSubtopicsSubtopic_id"]; - post?: never; - delete: operations["deleteApiAdminV1StudioSubtopicsSubtopic_id"]; - options?: never; - head?: never; - patch?: never; - trace?: never; - }; - "/api/admin/v1/studio/articles/{article}/preview": { - parameters: { - query?: never; - header?: never; - path: { - /** - * @description The article. - * @example architecto - */ - article: string; + id: number; }; cookie?: never; }; /** - * [文章] Dimzou 草稿预览 - * @description 渲染文章当前 Dimzou 草稿(无草稿时回退到最新已发布版本)的正文为 HTML, - * 连同标题/摘要/封面返回,供后台预览。管理员权限,绕过 Dimzou 逐用户读权限。 + * [事件监控] 事件详情 + * @description 含正文、附件、地理位置、话题提取信息及互动/举报统计。可查看被屏蔽或已软删除的事件。 */ - get: operations["Dimzou"]; - put?: never; - post?: never; - delete?: never; - options?: never; - head?: never; - patch?: never; - trace?: never; - }; - "/api/admin/v1/studio/articles": { - parameters: { - query?: never; - header?: never; - path?: never; - cookie?: never; - }; - get: operations["getApiAdminV1StudioArticles"]; - put?: never; - post: operations["postApiAdminV1StudioArticles"]; - delete?: never; - options?: never; - head?: never; - patch?: never; - trace?: never; - }; - "/api/admin/v1/studio/articles/{id}": { - parameters: { - query?: never; - header?: never; - path: { - /** - * @description The ID of the article. - * @example architecto - */ - id: string; + get: { + parameters: { + query?: never; + header?: never; + path: { + /** + * @description 事件 ID。 + * @example 25 + */ + id: number; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + /** @example true */ + success?: boolean; + data?: { + /** @example filex_event */ + kind?: string; + /** @example 25 */ + id?: number; + /** @example 空调清洗上门服务记录 */ + title?: string; + /** @example public */ + visibility?: string; + /** @example 家政保洁 */ + primary_topic?: string; + /** @example false */ + is_blocked?: boolean; + author?: { + /** @example 01HXXXX */ + uid?: string; + /** @example Alice */ + display_name?: string; + }; + /** @example 两台挂机深度清洗 */ + content?: string; + /** + * @example [ + * "家政保洁" + * ] + */ + topics?: string[]; + /** @example 广州 */ + location_name?: string; + /** @example 23.13 */ + latitude?: number; + /** @example 113.26 */ + longitude?: number; + /** @example [] */ + assets?: unknown[]; + /** @example null */ + deleted_at?: string; + }; + }; + }; + }; + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + /** @example false */ + success?: boolean; + /** @example UNAUTHENTICATED */ + code?: string; + /** @example 未认证 */ + message?: string; + }; + }; + }; + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + /** @example false */ + success?: boolean; + /** @example FORBIDDEN */ + code?: string; + /** @example 无权限访问 */ + message?: string; + }; + }; + }; + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + /** @example false */ + success?: boolean; + /** @example NOT_FOUND */ + code?: string; + /** @example 请求的资源未找到。 */ + message?: string; + }; + }; + }; }; - cookie?: never; }; - get: operations["getApiAdminV1StudioArticlesId"]; put?: never; post?: never; delete?: never; @@ -3339,208 +5741,6 @@ export interface paths { patch?: never; trace?: never; }; - "/api/admin/v1/studio/articles/{article_id}": { - parameters: { - query?: never; - header?: never; - path: { - /** - * @description The ID of the article. - * @example architecto - */ - article_id: string; - }; - cookie?: never; - }; - get?: never; - put: operations["putApiAdminV1StudioArticlesArticle_id"]; - post?: never; - delete: operations["deleteApiAdminV1StudioArticlesArticle_id"]; - options?: never; - head?: never; - patch?: never; - trace?: never; - }; - "/api/admin/v1/studio/series": { - parameters: { - query?: never; - header?: never; - path?: never; - cookie?: never; - }; - get: operations["getApiAdminV1StudioSeries"]; - put?: never; - post: operations["postApiAdminV1StudioSeries"]; - delete?: never; - options?: never; - head?: never; - patch?: never; - trace?: never; - }; - "/api/admin/v1/studio/series/{id}": { - parameters: { - query?: never; - header?: never; - path: { - /** - * @description The ID of the series. - * @example architecto - */ - id: string; - }; - cookie?: never; - }; - get: operations["getApiAdminV1StudioSeriesId"]; - put?: never; - post?: never; - delete?: never; - options?: never; - head?: never; - patch?: never; - trace?: never; - }; - "/api/admin/v1/studio/series/{series_id}": { - parameters: { - query?: never; - header?: never; - path: { - /** - * @description The ID of the series. - * @example architecto - */ - series_id: string; - }; - cookie?: never; - }; - get?: never; - put: operations["putApiAdminV1StudioSeriesSeries_id"]; - post?: never; - delete: operations["deleteApiAdminV1StudioSeriesSeries_id"]; - options?: never; - head?: never; - patch?: never; - trace?: never; - }; - "/api/admin/v1/studio/robot-authors": { - parameters: { - query?: never; - header?: never; - path?: never; - cookie?: never; - }; - get: operations["getApiAdminV1StudioRobotAuthors"]; - put?: never; - post: operations["postApiAdminV1StudioRobotAuthors"]; - delete?: never; - options?: never; - head?: never; - patch?: never; - trace?: never; - }; - "/api/admin/v1/studio/robot-authors/{id}": { - parameters: { - query?: never; - header?: never; - path: { - /** - * @description The ID of the robot author. - * @example architecto - */ - id: string; - }; - cookie?: never; - }; - get: operations["getApiAdminV1StudioRobotAuthorsId"]; - put?: never; - post?: never; - delete?: never; - options?: never; - head?: never; - patch?: never; - trace?: never; - }; - "/api/admin/v1/studio/robot-authors/{robot_author_id}": { - parameters: { - query?: never; - header?: never; - path: { - /** - * @description The ID of the robot author. - * @example architecto - */ - robot_author_id: string; - }; - cookie?: never; - }; - get?: never; - put: operations["putApiAdminV1StudioRobotAuthorsRobot_author_id"]; - post?: never; - delete: operations["deleteApiAdminV1StudioRobotAuthorsRobot_author_id"]; - options?: never; - head?: never; - patch?: never; - trace?: never; - }; - "/api/admin/v1/studio/style-presets": { - parameters: { - query?: never; - header?: never; - path?: never; - cookie?: never; - }; - get: operations["getApiAdminV1StudioStylePresets"]; - put?: never; - post: operations["postApiAdminV1StudioStylePresets"]; - delete?: never; - options?: never; - head?: never; - patch?: never; - trace?: never; - }; - "/api/admin/v1/studio/style-presets/{id}": { - parameters: { - query?: never; - header?: never; - path: { - /** - * @description The ID of the style preset. - * @example architecto - */ - id: string; - }; - cookie?: never; - }; - get: operations["getApiAdminV1StudioStylePresetsId"]; - put?: never; - post?: never; - delete?: never; - options?: never; - head?: never; - patch?: never; - trace?: never; - }; - "/api/admin/v1/studio/style-presets/{style_preset_id}": { - parameters: { - query?: never; - header?: never; - path: { - /** - * @description The ID of the style preset. - * @example architecto - */ - style_preset_id: string; - }; - cookie?: never; - }; - get?: never; - put: operations["putApiAdminV1StudioStylePresetsStyle_preset_id"]; - post?: never; - delete: operations["deleteApiAdminV1StudioStylePresetsStyle_preset_id"]; - options?: never; - head?: never; - patch?: never; - trace?: never; - }; "/api/admin/v1/file-x/reported-events": { parameters: { query?: never; @@ -3579,7 +5779,7 @@ export interface paths { content: { "application/json": { /** - * @example all + * @example pending * @enum {string|null} */ status?: "pending" | "blocked" | "all" | null; @@ -4478,26 +6678,6 @@ export interface paths { patch?: never; trace?: never; }; - "/api/admin/v1/sid/assign": { - parameters: { - query?: never; - header?: never; - path?: never; - cookie?: never; - }; - get?: never; - put?: never; - /** - * 分配用户 SID - * @description 管理员为用户分配或覆盖 SID。 - */ - post: operations["SID"]; - delete?: never; - options?: never; - head?: never; - patch?: never; - trace?: never; - }; "/api/admin/v1/sid/rules": { parameters: { query?: never; @@ -4884,6 +7064,1740 @@ export interface paths { patch?: never; trace?: never; }; + "/api/admin/v1/sid/assign": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** + * 分配用户 SID + * @description 管理员为用户分配或覆盖 SID。 + */ + post: operations["SID"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/admin/v1/users": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * [用户管理] 用户列表 + * @description 分页返回平台用户列表,支持关键字、状态、机器人账号与角色过滤。 + */ + get: { + parameters: { + query?: { + /** + * @description 可选。按邮箱/用户名/昵称/手机号/UID/SID 模糊搜索。 示例: admin + * @example architecto + */ + q?: string; + /** + * @description 可选。按启用状态过滤(1/0)。 示例: 1 + * @example false + */ + status?: boolean; + /** + * @description 可选。按是否机器人账号过滤(1/0)。 示例: 1 + * @example false + */ + is_robot?: boolean; + /** + * @description 可选。按角色名精确过滤。 示例: service-provider + * @example architecto + */ + role?: string; + /** + * @description 可选。页码。 示例: 1 + * @example 16 + */ + page?: number; + /** + * @description 可选。每页数量,最大100。 示例: 20 + * @example 16 + */ + page_size?: number; + }; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: { + content: { + "application/json": { + /** + * @description Must not be greater than 100 characters. + * @example b + */ + q?: string | null; + /** @example true */ + status?: boolean | null; + /** @example false */ + is_robot?: boolean | null; + /** + * @description Must not be greater than 100 characters. + * @example n + */ + role?: string | null; + /** + * @description Must be at least 1. + * @example 67 + */ + page?: number | null; + /** + * @description Must be at least 1. Must not be greater than 100. + * @example 16 + */ + page_size?: number | null; + }; + }; + }; + responses: { + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + /** @example true */ + success?: boolean; + /** + * @example [ + * { + * "kind": "user", + * "uid": "01HQ8M4Z2A3B5C6D7E8F9G0H1I2J3K4L", + * "sid": "ABC168888", + * "sid_display": "ABC-168888", + * "username": "admin", + * "display_name": "管理员", + * "email": "admin@example.com", + * "phone": "+1234567890", + * "country_code": "USA", + * "avatar": null, + * "expertise": "平台管理", + * "city": "旧金山", + * "timezone": "America/Los_Angeles", + * "timezone_offset": -480, + * "locale": null, + * "status": true, + * "is_robot": false, + * "email_verified_at": "2026-07-11T09:00:00.000000Z", + * "phone_verified_at": null, + * "roles": [ + * "admin" + * ], + * "created_at": "2026-07-11T08:00:00.000000Z", + * "updated_at": "2026-07-11T08:00:00.000000Z" + * } + * ] + */ + data?: { + /** @example user */ + kind?: string; + /** @example 01HQ8M4Z2A3B5C6D7E8F9G0H1I2J3K4L */ + uid?: string; + /** @example ABC168888 */ + sid?: string; + /** @example ABC-168888 */ + sid_display?: string; + /** @example admin */ + username?: string; + /** @example 管理员 */ + display_name?: string; + /** @example admin@example.com */ + email?: string; + /** @example +1234567890 */ + phone?: string; + /** @example USA */ + country_code?: string; + /** @example null */ + avatar?: string; + /** @example 平台管理 */ + expertise?: string; + /** @example 旧金山 */ + city?: string; + /** @example America/Los_Angeles */ + timezone?: string; + /** @example -480 */ + timezone_offset?: number; + /** @example null */ + locale?: string; + /** @example true */ + status?: boolean; + /** @example false */ + is_robot?: boolean; + /** @example 2026-07-11T09:00:00.000000Z */ + email_verified_at?: string; + /** @example null */ + phone_verified_at?: string; + /** + * @example [ + * "admin" + * ] + */ + roles?: string[]; + /** @example 2026-07-11T08:00:00.000000Z */ + created_at?: string; + /** @example 2026-07-11T08:00:00.000000Z */ + updated_at?: string; + }[]; + pagination?: { + /** @example 1 */ + current_page?: number; + /** @example null */ + next_page?: string; + /** @example 20 */ + page_size?: number; + /** @example null */ + prev_page?: string; + /** @example 1 */ + total_count?: number; + /** @example 1 */ + total_pages?: number; + }; + }; + }; + }; + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + /** @example false */ + success?: boolean; + /** @example FORBIDDEN */ + code?: string; + /** @example 您无权访问此资源。 */ + message?: string; + }; + }; + }; + }; + }; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/admin/v1/users/{uid}": { + parameters: { + query?: never; + header?: never; + path: { + /** @example architecto */ + uid: string; + }; + cookie?: never; + }; + /** + * [用户管理] 用户详情 + * @description 返回指定用户的完整信息,包括邮箱、手机号、验证状态与角色。 + */ + get: { + parameters: { + query?: never; + header?: never; + path: { + /** @example architecto */ + uid: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + /** @example true */ + success?: boolean; + data?: { + /** @example user */ + kind?: string; + /** @example 01HQ8M4Z2A3B5C6D7E8F9G0H1I2J3K4L */ + uid?: string; + /** @example ABC168888 */ + sid?: string; + /** @example ABC-168888 */ + sid_display?: string; + /** @example admin */ + username?: string; + /** @example 管理员 */ + display_name?: string; + /** @example admin@example.com */ + email?: string; + /** @example +1234567890 */ + phone?: string; + /** @example USA */ + country_code?: string; + /** @example null */ + avatar?: string; + /** @example 平台管理 */ + expertise?: string; + /** @example 旧金山 */ + city?: string; + /** @example America/Los_Angeles */ + timezone?: string; + /** @example -480 */ + timezone_offset?: number; + /** @example null */ + locale?: string; + /** @example true */ + status?: boolean; + /** @example false */ + is_robot?: boolean; + /** @example 2026-07-11T09:00:00.000000Z */ + email_verified_at?: string; + /** @example null */ + phone_verified_at?: string; + /** + * @example [ + * "admin" + * ] + */ + roles?: string[]; + /** @example 2026-07-11T08:00:00.000000Z */ + created_at?: string; + /** @example 2026-07-11T08:00:00.000000Z */ + updated_at?: string; + }; + }; + }; + }; + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + /** @example false */ + success?: boolean; + /** @example NOT_FOUND */ + code?: string; + /** @example 用户不存在 */ + message?: string; + }; + }; + }; + }; + }; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/admin/v1/users/{uid}/status": { + parameters: { + query?: never; + header?: never; + path: { + /** @example architecto */ + uid: string; + }; + cookie?: never; + }; + get?: never; + /** + * [用户管理] 启用/禁用用户 + * @description 更新用户启用状态。禁用用户时,其全部有效访问令牌将被吊销, + * 且无法再通过密码或验证码登录;重新启用后恢复登录能力。 + */ + put: { + parameters: { + query?: never; + header?: never; + path: { + /** @example architecto */ + uid: string; + }; + cookie?: never; + }; + requestBody: { + content: { + "application/json": { + /** + * @description 是否启用。 + * @example false + */ + status: boolean; + }; + }; + }; + responses: { + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + /** @example true */ + success?: boolean; + data?: { + /** @example user */ + kind?: string; + /** @example 01HQ8M4Z2A3B5C6D7E8F9G0H1I2J3K4L */ + uid?: string; + /** @example false */ + status?: boolean; + /** @example [] */ + roles?: unknown[]; + }; + /** @example 用户状态更新成功 */ + message?: string; + }; + }; + }; + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + /** @example false */ + success?: boolean; + /** @example NOT_FOUND */ + code?: string; + /** @example 用户不存在 */ + message?: string; + }; + }; + }; + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + /** @example false */ + success?: boolean; + /** @example VALIDATION_ERROR */ + code?: string; + /** @example 数据验证失败 */ + message?: string; + data?: { + errors?: { + /** + * @example [ + * "不能禁用当前登录的管理员账号" + * ] + */ + uid?: string[]; + }; + }; + }; + }; + }; + }; + }; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/admin/v1/auth/login/otp": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** + * [管理后台] 请求OTP验证码 + * @description Request OTP code for admin login via email or SMS + */ + post: operations["OTP"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/admin/v1/auth/login/verify-otp": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** + * [管理后台] 验证OTP验证码 + * @description Verify OTP code for admin login and return access token + */ + post: operations["OTP"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/admin/v1/auth/login/password": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** + * [管理后台] 密码登录 + * @description Login admin user with identity and password + */ + post: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": { + /** + * @description Admin identity (email). + * @example admin@example.com + */ + identity: string; + /** + * @description Admin password. + * @example admin123 + */ + password: string; + }; + }; + }; + responses: { + /** @description success */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + /** @example true */ + success?: boolean; + data?: { + /** @example eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImp0aSI6... */ + access_token?: string; + /** @example Bearer */ + token_type?: string; + /** @example 900 */ + expires_in?: number; + /** @example refresh-token-string */ + refresh_token?: string; + }; + /** @example Admin login successful */ + message?: string; + }; + }; + }; + /** @description invalid_credentials */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + /** @example false */ + success?: boolean; + /** @example INVALID_CREDENTIALS */ + code?: string; + /** @example Invalid admin credentials */ + message?: string; + }; + }; + }; + /** @description validation_error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + /** @example false */ + success?: boolean; + /** @example VALIDATION_ERROR */ + code?: string; + /** @example Validation failed */ + message?: string; + data?: { + errors?: { + /** + * @example [ + * "Identity is required" + * ] + */ + identity?: string[]; + /** + * @example [ + * "Password is required" + * ] + */ + password?: string[]; + }; + }; + }; + }; + }; + }; + }; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/admin/v1/auth/robots/{uid}/token": { + parameters: { + query?: never; + header?: never; + path: { + /** @example architecto */ + uid: string; + }; + cookie?: never; + }; + get?: never; + put?: never; + /** + * [管理后台] 为机器人账号签发会话令牌 + * @description 为指定机器人账号签发一组 OAuth 访问令牌和刷新令牌。 + */ + post: { + parameters: { + query?: never; + header?: never; + path: { + /** @example architecto */ + uid: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: never; + }; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/admin/v1/auth/me": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Get the current admin user with roles and permissions + * @description Returns the authenticated user plus their roles and effective permissions + * (role-derived + direct) so the admin frontend can render permission-aware UI. + */ + get: operations["getTheCurrentAdminUserWithRolesAndPermissions"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/admin/v1/auth/roles": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** [角色管理] 角色列表 */ + get: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + /** @example true */ + success?: boolean; + /** + * @example [ + * { + * "kind": "role", + * "name": "moderator", + * "permissions": [ + * "admin:comment:read" + * ], + * "users_count": 1, + * "builtin": false + * } + * ] + */ + data?: { + /** @example role */ + kind?: string; + /** @example moderator */ + name?: string; + /** + * @example [ + * "admin:comment:read" + * ] + */ + permissions?: string[]; + /** @example 1 */ + users_count?: number; + /** @example false */ + builtin?: boolean; + }[]; + }; + }; + }; + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + /** @example false */ + success?: boolean; + /** @example FORBIDDEN */ + code?: string; + /** @example 无权限访问 */ + message?: string; + }; + }; + }; + }; + }; + put?: never; + /** [角色管理] 新建角色 */ + post: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": { + /** + * @description 角色名(唯一)。 + * @example reviewer + */ + name: string; + }; + }; + }; + responses: { + 201: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + /** @example true */ + success?: boolean; + data?: { + /** @example role */ + kind?: string; + /** @example reviewer */ + name?: string; + /** @example [] */ + permissions?: unknown[]; + /** @example 0 */ + users_count?: number; + /** @example false */ + builtin?: boolean; + }; + }; + }; + }; + }; + }; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/admin/v1/auth/roles/{role}/permissions": { + parameters: { + query?: never; + header?: never; + path: { + /** + * @description 角色名。 + * @example moderator + */ + role: string; + }; + cookie?: never; + }; + get?: never; + /** + * [角色管理] 同步角色权限 + * @description 全量替换该角色的权限集合。super-admin 角色不可修改。 + */ + put: { + parameters: { + query?: never; + header?: never; + path: { + /** + * @description 角色名。 + * @example moderator + */ + role: string; + }; + cookie?: never; + }; + requestBody: { + content: { + "application/json": { + /** + * @description 权限键列表。 + * @example [ + * "admin:comment:read" + * ] + */ + permissions: string[]; + }; + }; + }; + responses: { + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + /** @example true */ + success?: boolean; + data?: { + /** @example role */ + kind?: string; + /** @example moderator */ + name?: string; + /** + * @example [ + * "admin:comment:read" + * ] + */ + permissions?: string[]; + /** @example 1 */ + users_count?: number; + /** @example false */ + builtin?: boolean; + }; + }; + }; + }; + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + /** @example false */ + success?: boolean; + /** @example VALIDATION_ERROR */ + code?: string; + /** @example The given data was invalid. */ + message?: string; + data?: { + errors?: { + /** + * @example [ + * "super-admin role cannot be modified." + * ] + */ + role?: string[]; + }; + }; + }; + }; + }; + }; + }; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/admin/v1/auth/roles/{id}": { + parameters: { + query?: never; + header?: never; + path: { + /** + * @description The ID of the role. + * @example architecto + */ + id: string; + /** + * @description 角色名。 + * @example reviewer + */ + role: string; + }; + cookie?: never; + }; + get?: never; + put?: never; + post?: never; + /** + * [角色管理] 删除角色 + * @description 内建角色与仍有用户的角色不可删除。 + */ + delete: { + parameters: { + query?: never; + header?: never; + path: { + /** + * @description The ID of the role. + * @example architecto + */ + id: string; + /** + * @description 角色名。 + * @example reviewer + */ + role: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + /** @example true */ + success?: boolean; + /** @example Role deleted */ + message?: string; + }; + }; + }; + }; + }; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/admin/v1/auth/permissions": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * [角色管理] 权限目录 + * @description 按模块/分组返回全部已注册权限键及描述,用于权限矩阵渲染。 + */ + get: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + /** @example true */ + success?: boolean; + data?: { + comment?: { + /** @example comment */ + module?: string; + groups?: { + admin?: { + /** @example View reported comments */ + "admin:comment:read"?: string; + }; + }; + }; + }; + }; + }; + }; + }; + }; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/admin/v1/auth/users": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** [角色管理] 搜索用户(角色分配用) */ + get: { + parameters: { + query: { + /** + * @description 按邮箱/用户名/UID 模糊搜索。 示例: moderator + * @example architecto + */ + q: string; + }; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": { + /** + * @description Must not be greater than 100 characters. + * @example b + */ + q: string; + }; + }; + }; + responses: { + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + /** @example true */ + success?: boolean; + /** + * @example [ + * { + * "kind": "role_user", + * "uid": "01HXXXX", + * "display_name": "内容审核员", + * "email": "moderator@gig.local", + * "roles": [ + * "moderator" + * ] + * } + * ] + */ + data?: { + /** @example role_user */ + kind?: string; + /** @example 01HXXXX */ + uid?: string; + /** @example 内容审核员 */ + display_name?: string; + /** @example moderator@gig.local */ + email?: string; + /** + * @example [ + * "moderator" + * ] + */ + roles?: string[]; + }[]; + }; + }; + }; + }; + }; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/admin/v1/auth/users/{uid}/roles": { + parameters: { + query?: never; + header?: never; + path: { + /** + * @description 用户 UID。 + * @example 01HXXXX + */ + uid: string; + }; + cookie?: never; + }; + get?: never; + /** + * [角色管理] 分配用户角色 + * @description 全量替换用户的角色集合。不能修改自己的角色。 + */ + put: { + parameters: { + query?: never; + header?: never; + path: { + /** + * @description 用户 UID。 + * @example 01HXXXX + */ + uid: string; + }; + cookie?: never; + }; + requestBody: { + content: { + "application/json": { + /** + * @description 角色名列表。 + * @example [ + * "moderator" + * ] + */ + roles: string[]; + }; + }; + }; + responses: { + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + /** @example true */ + success?: boolean; + data?: { + /** @example role_user */ + kind?: string; + /** @example 01HXXXX */ + uid?: string; + /** + * @example [ + * "moderator" + * ] + */ + roles?: string[]; + }; + }; + }; + }; + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + /** @example false */ + success?: boolean; + /** @example VALIDATION_ERROR */ + code?: string; + /** @example The given data was invalid. */ + message?: string; + data?: { + errors?: { + /** + * @example [ + * "You cannot modify your own roles." + * ] + */ + uid?: string[]; + }; + }; + }; + }; + }; + }; + }; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/admin/v1/comments/reported": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * [评论审核] 被举报评论列表 + * @description 按被举报的评论分组列出举报,附带举报明细与屏蔽状态。 + */ + get: { + parameters: { + query?: { + /** + * @description 可选。按状态过滤: pending(有待处理举报), blocked(已屏蔽), all(默认)。 示例: pending + * @example architecto + */ + status?: string; + /** + * @description 可选。页码。 示例: 1 + * @example 16 + */ + page?: number; + /** + * @description 可选。每页数量,最大100。 示例: 20 + * @example 16 + */ + page_size?: number; + }; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: { + content: { + "application/json": { + /** + * @example all + * @enum {string|null} + */ + status?: "pending" | "blocked" | "all" | null; + /** + * @description Must be at least 1. + * @example 16 + */ + page?: number | null; + /** + * @description Must be at least 1. Must not be greater than 100. + * @example 22 + */ + page_size?: number | null; + }; + }; + }; + responses: { + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + /** @example true */ + success?: boolean; + /** + * @example [ + * { + * "kind": "reported_comment", + * "id": 10, + * "body": "这条评论包含垃圾推广链接", + * "body_format": "markdown", + * "status": "active", + * "is_blocked": false, + * "blocked_at": null, + * "blocked_by_uid": null, + * "author": { + * "uid": "01HYYYY", + * "display_name": "Bob" + * }, + * "open_reports_count": 2, + * "latest_report_at": "2026-07-11T10:10:00Z", + * "reports": [ + * { + * "id": 1, + * "reason": "spam", + * "message": "含有垃圾推广链接", + * "status": "open", + * "reporter": { + * "uid": "01HXXXX", + * "display_name": "Alice" + * }, + * "created_at": "2026-07-11T10:10:00Z", + * "handled_at": null + * } + * ], + * "created_at": "2026-07-10T09:00:00Z" + * } + * ] + */ + data?: { + /** @example reported_comment */ + kind?: string; + /** @example 10 */ + id?: number; + /** @example 这条评论包含垃圾推广链接 */ + body?: string; + /** @example markdown */ + body_format?: string; + /** @example active */ + status?: string; + /** @example false */ + is_blocked?: boolean; + /** @example null */ + blocked_at?: string; + /** @example null */ + blocked_by_uid?: string; + author?: { + /** @example 01HYYYY */ + uid?: string; + /** @example Bob */ + display_name?: string; + }; + /** @example 2 */ + open_reports_count?: number; + /** @example 2026-07-11T10:10:00Z */ + latest_report_at?: string; + /** + * @example [ + * { + * "id": 1, + * "reason": "spam", + * "message": "含有垃圾推广链接", + * "status": "open", + * "reporter": { + * "uid": "01HXXXX", + * "display_name": "Alice" + * }, + * "created_at": "2026-07-11T10:10:00Z", + * "handled_at": null + * } + * ] + */ + reports?: { + /** @example 1 */ + id?: number; + /** @example spam */ + reason?: string; + /** @example 含有垃圾推广链接 */ + message?: string; + /** @example open */ + status?: string; + reporter?: { + /** @example 01HXXXX */ + uid?: string; + /** @example Alice */ + display_name?: string; + }; + /** @example 2026-07-11T10:10:00Z */ + created_at?: string; + /** @example null */ + handled_at?: string; + }[]; + /** @example 2026-07-10T09:00:00Z */ + created_at?: string; + }[]; + pagination?: { + /** @example 1 */ + current_page?: number; + /** @example null */ + next_page?: string; + /** @example 20 */ + page_size?: number; + /** @example null */ + prev_page?: string; + /** @example 1 */ + total_count?: number; + /** @example 1 */ + total_pages?: number; + }; + }; + }; + }; + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + /** @example false */ + success?: boolean; + /** @example UNAUTHENTICATED */ + code?: string; + /** @example 未认证 */ + message?: string; + }; + }; + }; + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + /** @example false */ + success?: boolean; + /** @example FORBIDDEN */ + code?: string; + /** @example 无权限访问 */ + message?: string; + }; + }; + }; + }; + }; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/admin/v1/comments/reported/{comment}": { + parameters: { + query?: never; + header?: never; + path: { + /** + * @description 评论 ID。 + * @example 10 + */ + comment: number; + }; + cookie?: never; + }; + get?: never; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + /** + * [评论审核] 处理被举报评论 + * @description approve: 举报成立,屏蔽评论并将其全部待处理举报标记已处理; + * reject: 举报不成立,驳回全部待处理举报; + * revert: 撤销屏蔽(仅对已屏蔽评论有效),举报记录保留。 + */ + patch: { + parameters: { + query?: never; + header?: never; + path: { + /** + * @description 评论 ID。 + * @example 10 + */ + comment: number; + }; + cookie?: never; + }; + requestBody: { + content: { + "application/json": { + /** + * @description 处理动作: approve, reject, revert。 + * @example approve + */ + action: string; + }; + }; + }; + responses: { + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + /** @example true */ + success?: boolean; + data?: { + /** @example reported_comment */ + kind?: string; + /** @example 10 */ + id?: number; + /** @example true */ + is_blocked?: boolean; + /** @example 2026-07-11T10:20:00Z */ + blocked_at?: string; + /** @example 01HXXXX */ + blocked_by_uid?: string; + /** @example 0 */ + open_reports_count?: number; + }; + /** @example Moderation applied */ + message?: string; + }; + }; + }; + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + /** @example false */ + success?: boolean; + /** @example UNAUTHENTICATED */ + code?: string; + /** @example 未认证 */ + message?: string; + }; + }; + }; + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + /** @example false */ + success?: boolean; + /** @example FORBIDDEN */ + code?: string; + /** @example 无权限访问 */ + message?: string; + }; + }; + }; + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + /** @example false */ + success?: boolean; + /** @example VALIDATION_ERROR */ + code?: string; + /** @example The given data was invalid. */ + message?: string; + data?: { + errors?: { + /** + * @example [ + * "Comment is not blocked." + * ] + */ + action?: string[]; + }; + }; + }; + }; + }; + }; + }; + trace?: never; + }; + "/api/admin/v1/notification/devices": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * [管理] 获取设备列表 + * @description 获取所有设备的分页列表,支持按平台、在线状态等筛选。 + */ + get: { + parameters: { + query?: { + /** + * @description 可选。按平台筛选 (ios, android, web, desktop) + * @example architecto + */ + platform?: string; + /** + * @description 可选。按在线状态筛选 + * @example false + */ + is_online?: boolean; + /** + * @description 可选。页码 (默认: 1) + * @example 16 + */ + page?: number; + /** + * @description 可选。每页条数 (默认: 20, 最大: 100) + * @example 16 + */ + page_size?: number; + }; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: { + content: { + "application/json": { + /** + * @example desktop + * @enum {string|null} + */ + platform?: "ios" | "android" | "web" | "desktop" | null; + /** @example false */ + is_online?: boolean | null; + /** + * @description Must be at least 1. + * @example 16 + */ + page?: number | null; + /** + * @description Must be at least 1. Must not be greater than 100. + * @example 22 + */ + page_size?: number | null; + }; + }; + }; + responses: { + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + /** @example true */ + success?: boolean; + /** + * @example [ + * { + * "id": "01HN1234567890ABCDEF", + * "device_client_id": "device_123", + * "platform": "ios", + * "app_key": "app_v1", + * "device_model": "iPhone 12", + * "push_token": "push_token_here", + * "websocket_session_id": "ws_session_123", + * "last_seen_at": "2023-11-15T07:30:00.000000Z", + * "is_online": true, + * "kind": "device", + * "active_sessions_count": 2, + * "created_at": "2023-11-01T00:00:00.000000Z", + * "updated_at": "2023-11-15T07:30:00.000000Z" + * } + * ] + */ + data?: { + /** @example 01HN1234567890ABCDEF */ + id?: string; + /** @example device_123 */ + device_client_id?: string; + /** @example ios */ + platform?: string; + /** @example app_v1 */ + app_key?: string; + /** @example iPhone 12 */ + device_model?: string; + /** @example push_token_here */ + push_token?: string; + /** @example ws_session_123 */ + websocket_session_id?: string; + /** @example 2023-11-15T07:30:00.000000Z */ + last_seen_at?: string; + /** @example true */ + is_online?: boolean; + /** @example device */ + kind?: string; + /** @example 2 */ + active_sessions_count?: number; + /** @example 2023-11-01T00:00:00.000000Z */ + created_at?: string; + /** @example 2023-11-15T07:30:00.000000Z */ + updated_at?: string; + }[]; + pagination?: { + /** @example 1 */ + current_page?: number; + /** @example 2 */ + next_page?: number; + /** @example 20 */ + page_size?: number; + /** @example null */ + prev_page?: string; + /** @example 150 */ + total_count?: number; + /** @example 8 */ + total_pages?: number; + }; + /** @example Devices retrieved successfully */ + message?: string; + }; + }; + }; + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + /** @example false */ + success?: boolean; + /** @example VALIDATION_ERROR */ + code?: string; + /** @example Validation failed */ + message?: string; + data?: { + /** + * @example [ + * "The selected platform is invalid." + * ] + */ + platform?: string[]; + /** + * @example [ + * "The is_online field must be true or false." + * ] + */ + is_online?: string[]; + }; + }; + }; + }; + }; + }; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; } export type webhooks = Record; export interface components { @@ -4896,223 +8810,17 @@ export interface components { } export type $defs = Record; export interface operations { - OTP: { + Dimzou: { parameters: { query?: never; header?: never; - path?: never; - cookie?: never; - }; - requestBody: { - content: { - "application/json": { - /** - * @description Admin email or phone. - * @example admin@example.com - */ - identity: string; - /** - * @description Identity type (email/phone). - * @example email - */ - type: string; - /** - * @description required_if:type,phone ISO 3166-1 alpha-3 country code. - * @example CHN - */ - country_code?: string; - }; + path: { + /** + * @description 文章 UUID。 + * @example 9f1c2d3e-4b5a-6789-0abc-def012345678 + */ + article: string; }; - }; - responses: { - /** @description success */ - 200: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": { - /** @example true */ - success?: boolean; - /** @example null */ - data?: string; - meta?: { - /** @example 4 */ - code_length?: number; - }; - /** @example OTP sent successfully */ - message?: string; - }; - }; - }; - /** @description validation_error */ - 422: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": { - /** @example false */ - success?: boolean; - /** @example VALIDATION_ERROR */ - code?: string; - /** @example Validation failed */ - message?: string; - data?: { - errors?: { - /** - * @example [ - * "Identity field is required" - * ] - */ - identity?: string[]; - /** - * @example [ - * "Type must be email or phone" - * ] - */ - type?: string[]; - /** - * @example [ - * "Country code is required for phone type" - * ] - */ - country_code?: string[]; - }; - }; - }; - }; - }; - /** @description rate_limit */ - 429: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": { - /** @example false */ - success?: boolean; - /** @example TOO_MANY_REQUESTS */ - code?: string; - /** @example Too many requests */ - message?: string; - data?: { - /** @example 60 */ - retry_after?: number; - }; - }; - }; - }; - }; - }; - OTP: { - parameters: { - query?: never; - header?: never; - path?: never; - cookie?: never; - }; - requestBody: { - content: { - "application/json": { - /** - * @description Admin email or phone. - * @example admin@example.com - */ - identity: string; - /** - * @description Identity type (email/phone). - * @example email - */ - type: string; - /** - * @description OTP code. - * @example 1234 - */ - code: string; - }; - }; - }; - responses: { - /** @description success */ - 200: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": { - /** @example true */ - success?: boolean; - data?: { - /** @example eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImp0aSI6... */ - access_token?: string; - /** @example Bearer */ - token_type?: string; - /** @example 900 */ - expires_in?: number; - /** @example refresh-token-string */ - refresh_token?: string; - }; - /** @example Admin login successful */ - message?: string; - }; - }; - }; - /** @description invalid_otp */ - 400: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": { - /** @example false */ - success?: boolean; - /** @example INVALID_OTP */ - code?: string; - /** @example Invalid or expired OTP code */ - message?: string; - }; - }; - }; - /** @description validation_error */ - 422: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": { - /** @example false */ - success?: boolean; - /** @example VALIDATION_ERROR */ - code?: string; - /** @example Validation failed */ - message?: string; - data?: { - errors?: { - /** - * @example [ - * "OTP code is required" - * ] - */ - code?: string[]; - /** - * @example [ - * "Type must be email or phone" - * ] - */ - type?: string[]; - }; - }; - }; - }; - }; - }; - }; - getTheCurrentAdminUserWithRolesAndPermissions: { - parameters: { - query?: never; - header?: never; - path?: never; cookie?: never; }; requestBody?: never; @@ -5126,33 +8834,65 @@ export interface operations { /** @example true */ success?: boolean; data?: { - /** @example user */ + /** @example studio_article_preview */ kind?: string; - /** @example 01J... */ - uid?: string; - /** @example Admin */ - display_name?: string; - /** @example active */ - status?: string; - }; - meta?: { - /** - * @example [ - * "admin" - * ] - */ - roles?: string[]; - /** - * @example [ - * "admin:category:read", - * "admin:category:approve" - * ] - */ - permissions?: string[]; + /** @example 空调多久洗一次?一篇讲清楚 */ + title?: string; + /** @example 从健康与能耗角度给出清洗周期建议 */ + summary?: string; + /** @example null */ + cover_image?: string; + /** @example

为什么要定期清洗

……

*/ + html?: string; }; }; }; }; + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + /** @example false */ + success?: boolean; + /** @example UNAUTHENTICATED */ + code?: string; + /** @example 未认证 */ + message?: string; + }; + }; + }; + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + /** @example false */ + success?: boolean; + /** @example FORBIDDEN */ + code?: string; + /** @example 无权限访问 */ + message?: string; + }; + }; + }; + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + /** @example false */ + success?: boolean; + /** @example NOT_FOUND */ + code?: string; + /** @example 请求的资源未找到。 */ + message?: string; + }; + }; + }; }; }; AgentToken: { @@ -5236,638 +8976,6 @@ export interface operations { requestBody?: never; responses: never; }; - getApiAdminV1StudioCategoryBriefs: { - parameters: { - query?: never; - header?: never; - path?: never; - cookie?: never; - }; - requestBody?: never; - responses: { - 401: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": { - /** @example false */ - success?: boolean; - /** @example Authentication required */ - message?: string; - /** @example UNAUTHENTICATED */ - code?: string; - }; - }; - }; - }; - }; - postApiAdminV1StudioCategoryBriefs: { - parameters: { - query?: never; - header?: never; - path?: never; - cookie?: never; - }; - requestBody?: never; - responses: never; - }; - getApiAdminV1StudioCategoryBriefsId: { - parameters: { - query?: never; - header?: never; - path: { - /** - * @description The ID of the category brief. - * @example architecto - */ - id: string; - }; - cookie?: never; - }; - requestBody?: never; - responses: { - 401: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": { - /** @example false */ - success?: boolean; - /** @example Authentication required */ - message?: string; - /** @example UNAUTHENTICATED */ - code?: string; - }; - }; - }; - }; - }; - putApiAdminV1StudioCategoryBriefsCategory_brief_id: { - parameters: { - query?: never; - header?: never; - path: { - /** - * @description The ID of the category brief. - * @example architecto - */ - category_brief_id: string; - }; - cookie?: never; - }; - requestBody?: never; - responses: never; - }; - deleteApiAdminV1StudioCategoryBriefsCategory_brief_id: { - parameters: { - query?: never; - header?: never; - path: { - /** - * @description The ID of the category brief. - * @example architecto - */ - category_brief_id: string; - }; - cookie?: never; - }; - requestBody?: never; - responses: never; - }; - getApiAdminV1StudioSubtopics: { - parameters: { - query?: never; - header?: never; - path?: never; - cookie?: never; - }; - requestBody?: never; - responses: { - 401: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": { - /** @example false */ - success?: boolean; - /** @example Authentication required */ - message?: string; - /** @example UNAUTHENTICATED */ - code?: string; - }; - }; - }; - }; - }; - postApiAdminV1StudioSubtopics: { - parameters: { - query?: never; - header?: never; - path?: never; - cookie?: never; - }; - requestBody?: never; - responses: never; - }; - getApiAdminV1StudioSubtopicsId: { - parameters: { - query?: never; - header?: never; - path: { - /** - * @description The ID of the subtopic. - * @example architecto - */ - id: string; - }; - cookie?: never; - }; - requestBody?: never; - responses: { - 401: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": { - /** @example false */ - success?: boolean; - /** @example Authentication required */ - message?: string; - /** @example UNAUTHENTICATED */ - code?: string; - }; - }; - }; - }; - }; - putApiAdminV1StudioSubtopicsSubtopic_id: { - parameters: { - query?: never; - header?: never; - path: { - /** - * @description The ID of the subtopic. - * @example architecto - */ - subtopic_id: string; - }; - cookie?: never; - }; - requestBody?: never; - responses: never; - }; - deleteApiAdminV1StudioSubtopicsSubtopic_id: { - parameters: { - query?: never; - header?: never; - path: { - /** - * @description The ID of the subtopic. - * @example architecto - */ - subtopic_id: string; - }; - cookie?: never; - }; - requestBody?: never; - responses: never; - }; - Dimzou: { - parameters: { - query?: never; - header?: never; - path: { - /** - * @description The article. - * @example architecto - */ - article: string; - }; - cookie?: never; - }; - requestBody?: never; - responses: { - 401: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": { - /** @example false */ - success?: boolean; - /** @example Authentication required */ - message?: string; - /** @example UNAUTHENTICATED */ - code?: string; - }; - }; - }; - }; - }; - getApiAdminV1StudioArticles: { - parameters: { - query?: never; - header?: never; - path?: never; - cookie?: never; - }; - requestBody?: never; - responses: { - 401: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": { - /** @example false */ - success?: boolean; - /** @example Authentication required */ - message?: string; - /** @example UNAUTHENTICATED */ - code?: string; - }; - }; - }; - }; - }; - postApiAdminV1StudioArticles: { - parameters: { - query?: never; - header?: never; - path?: never; - cookie?: never; - }; - requestBody?: never; - responses: never; - }; - getApiAdminV1StudioArticlesId: { - parameters: { - query?: never; - header?: never; - path: { - /** - * @description The ID of the article. - * @example architecto - */ - id: string; - }; - cookie?: never; - }; - requestBody?: never; - responses: { - 401: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": { - /** @example false */ - success?: boolean; - /** @example Authentication required */ - message?: string; - /** @example UNAUTHENTICATED */ - code?: string; - }; - }; - }; - }; - }; - putApiAdminV1StudioArticlesArticle_id: { - parameters: { - query?: never; - header?: never; - path: { - /** - * @description The ID of the article. - * @example architecto - */ - article_id: string; - }; - cookie?: never; - }; - requestBody?: never; - responses: never; - }; - deleteApiAdminV1StudioArticlesArticle_id: { - parameters: { - query?: never; - header?: never; - path: { - /** - * @description The ID of the article. - * @example architecto - */ - article_id: string; - }; - cookie?: never; - }; - requestBody?: never; - responses: never; - }; - getApiAdminV1StudioSeries: { - parameters: { - query?: never; - header?: never; - path?: never; - cookie?: never; - }; - requestBody?: never; - responses: { - 401: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": { - /** @example false */ - success?: boolean; - /** @example Authentication required */ - message?: string; - /** @example UNAUTHENTICATED */ - code?: string; - }; - }; - }; - }; - }; - postApiAdminV1StudioSeries: { - parameters: { - query?: never; - header?: never; - path?: never; - cookie?: never; - }; - requestBody?: never; - responses: never; - }; - getApiAdminV1StudioSeriesId: { - parameters: { - query?: never; - header?: never; - path: { - /** - * @description The ID of the series. - * @example architecto - */ - id: string; - }; - cookie?: never; - }; - requestBody?: never; - responses: { - 401: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": { - /** @example false */ - success?: boolean; - /** @example Authentication required */ - message?: string; - /** @example UNAUTHENTICATED */ - code?: string; - }; - }; - }; - }; - }; - putApiAdminV1StudioSeriesSeries_id: { - parameters: { - query?: never; - header?: never; - path: { - /** - * @description The ID of the series. - * @example architecto - */ - series_id: string; - }; - cookie?: never; - }; - requestBody?: never; - responses: never; - }; - deleteApiAdminV1StudioSeriesSeries_id: { - parameters: { - query?: never; - header?: never; - path: { - /** - * @description The ID of the series. - * @example architecto - */ - series_id: string; - }; - cookie?: never; - }; - requestBody?: never; - responses: never; - }; - getApiAdminV1StudioRobotAuthors: { - parameters: { - query?: never; - header?: never; - path?: never; - cookie?: never; - }; - requestBody?: never; - responses: { - 401: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": { - /** @example false */ - success?: boolean; - /** @example Authentication required */ - message?: string; - /** @example UNAUTHENTICATED */ - code?: string; - }; - }; - }; - }; - }; - postApiAdminV1StudioRobotAuthors: { - parameters: { - query?: never; - header?: never; - path?: never; - cookie?: never; - }; - requestBody?: never; - responses: never; - }; - getApiAdminV1StudioRobotAuthorsId: { - parameters: { - query?: never; - header?: never; - path: { - /** - * @description The ID of the robot author. - * @example architecto - */ - id: string; - }; - cookie?: never; - }; - requestBody?: never; - responses: { - 401: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": { - /** @example false */ - success?: boolean; - /** @example Authentication required */ - message?: string; - /** @example UNAUTHENTICATED */ - code?: string; - }; - }; - }; - }; - }; - putApiAdminV1StudioRobotAuthorsRobot_author_id: { - parameters: { - query?: never; - header?: never; - path: { - /** - * @description The ID of the robot author. - * @example architecto - */ - robot_author_id: string; - }; - cookie?: never; - }; - requestBody?: never; - responses: never; - }; - deleteApiAdminV1StudioRobotAuthorsRobot_author_id: { - parameters: { - query?: never; - header?: never; - path: { - /** - * @description The ID of the robot author. - * @example architecto - */ - robot_author_id: string; - }; - cookie?: never; - }; - requestBody?: never; - responses: never; - }; - getApiAdminV1StudioStylePresets: { - parameters: { - query?: never; - header?: never; - path?: never; - cookie?: never; - }; - requestBody?: never; - responses: { - 401: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": { - /** @example false */ - success?: boolean; - /** @example Authentication required */ - message?: string; - /** @example UNAUTHENTICATED */ - code?: string; - }; - }; - }; - }; - }; - postApiAdminV1StudioStylePresets: { - parameters: { - query?: never; - header?: never; - path?: never; - cookie?: never; - }; - requestBody?: never; - responses: never; - }; - getApiAdminV1StudioStylePresetsId: { - parameters: { - query?: never; - header?: never; - path: { - /** - * @description The ID of the style preset. - * @example architecto - */ - id: string; - }; - cookie?: never; - }; - requestBody?: never; - responses: { - 401: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": { - /** @example false */ - success?: boolean; - /** @example Authentication required */ - message?: string; - /** @example UNAUTHENTICATED */ - code?: string; - }; - }; - }; - }; - }; - putApiAdminV1StudioStylePresetsStyle_preset_id: { - parameters: { - query?: never; - header?: never; - path: { - /** - * @description The ID of the style preset. - * @example architecto - */ - style_preset_id: string; - }; - cookie?: never; - }; - requestBody?: never; - responses: never; - }; - deleteApiAdminV1StudioStylePresetsStyle_preset_id: { - parameters: { - query?: never; - header?: never; - path: { - /** - * @description The ID of the style preset. - * @example architecto - */ - style_preset_id: string; - }; - cookie?: never; - }; - requestBody?: never; - responses: never; - }; getCategoryTreeadmin: { parameters: { query?: { @@ -6557,6 +9665,45 @@ export interface operations { }; }; }; + SID: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": { + /** + * @description SID 值,支持带/不带横杠。 + * @example ABC-888888 + */ + sid: string; + }; + }; + }; + responses: { + /** @description success */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + /** @example true */ + success?: boolean; + data?: { + /** @example ABC-888888 */ + sid?: string; + /** @example true */ + reserved?: boolean; + }; + }; + }; + }; + }; + }; SID: { parameters: { query?: never; @@ -6601,7 +9748,7 @@ export interface operations { }; }; }; - SID: { + OTP: { parameters: { query?: never; header?: never; @@ -6612,10 +9759,129 @@ export interface operations { content: { "application/json": { /** - * @description SID 值,支持带/不带横杠。 - * @example ABC-888888 + * @description Admin email or phone. + * @example admin@example.com */ - sid: string; + identity: string; + /** + * @description Identity type (email/phone). + * @example email + */ + type: string; + /** + * @description required_if:type,phone ISO 3166-1 alpha-3 country code. + * @example CHN + */ + country_code?: string; + }; + }; + }; + responses: { + /** @description success */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + /** @example true */ + success?: boolean; + /** @example null */ + data?: string; + meta?: { + /** @example 4 */ + code_length?: number; + }; + /** @example OTP sent successfully */ + message?: string; + }; + }; + }; + /** @description validation_error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + /** @example false */ + success?: boolean; + /** @example VALIDATION_ERROR */ + code?: string; + /** @example Validation failed */ + message?: string; + data?: { + errors?: { + /** + * @example [ + * "Identity field is required" + * ] + */ + identity?: string[]; + /** + * @example [ + * "Type must be email or phone" + * ] + */ + type?: string[]; + /** + * @example [ + * "Country code is required for phone type" + * ] + */ + country_code?: string[]; + }; + }; + }; + }; + }; + /** @description rate_limit */ + 429: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + /** @example false */ + success?: boolean; + /** @example TOO_MANY_REQUESTS */ + code?: string; + /** @example Too many requests */ + message?: string; + data?: { + /** @example 60 */ + retry_after?: number; + }; + }; + }; + }; + }; + }; + OTP: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": { + /** + * @description Admin email or phone. + * @example admin@example.com + */ + identity: string; + /** + * @description Identity type (email/phone). + * @example email + */ + type: string; + /** + * @description OTP code. + * @example 1234 + */ + code: string; }; }; }; @@ -6630,10 +9896,111 @@ export interface operations { /** @example true */ success?: boolean; data?: { - /** @example ABC-888888 */ - sid?: string; - /** @example true */ - reserved?: boolean; + /** @example eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImp0aSI6... */ + access_token?: string; + /** @example Bearer */ + token_type?: string; + /** @example 900 */ + expires_in?: number; + /** @example refresh-token-string */ + refresh_token?: string; + }; + /** @example Admin login successful */ + message?: string; + }; + }; + }; + /** @description invalid_otp */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + /** @example false */ + success?: boolean; + /** @example INVALID_OTP */ + code?: string; + /** @example Invalid or expired OTP code */ + message?: string; + }; + }; + }; + /** @description validation_error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + /** @example false */ + success?: boolean; + /** @example VALIDATION_ERROR */ + code?: string; + /** @example Validation failed */ + message?: string; + data?: { + errors?: { + /** + * @example [ + * "OTP code is required" + * ] + */ + code?: string[]; + /** + * @example [ + * "Type must be email or phone" + * ] + */ + type?: string[]; + }; + }; + }; + }; + }; + }; + }; + getTheCurrentAdminUserWithRolesAndPermissions: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + /** @example true */ + success?: boolean; + data?: { + /** @example user */ + kind?: string; + /** @example 01J... */ + uid?: string; + /** @example Admin */ + display_name?: string; + /** @example active */ + status?: string; + }; + meta?: { + /** + * @example [ + * "admin" + * ] + */ + roles?: string[]; + /** + * @example [ + * "admin:category:read", + * "admin:category:approve" + * ] + */ + permissions?: string[]; }; }; }; diff --git a/src/api/types.ts b/src/api/types.ts index ad93b8e..19a2474 100644 --- a/src/api/types.ts +++ b/src/api/types.ts @@ -63,6 +63,38 @@ export interface AdminUser { is_robot?: boolean } +export interface PlatformUser { + kind: 'user' + uid: string + sid?: string | null + sid_display?: string | null + username: string | null + display_name: string | null + email: string | null + phone: string | null + country_code?: string | null + avatar: string | null + expertise?: string | null + city?: string | null + timezone?: string | null + timezone_offset?: number | null + locale?: string | null + status: boolean + is_robot: boolean + email_verified_at?: string | null + phone_verified_at?: string | null + roles: string[] + created_at: string + updated_at?: string +} + +export interface UserListParams extends ListParams { + q?: string + status?: boolean + is_robot?: boolean + role?: string +} + export interface MeResponse extends ApiResponse { meta: { roles: string[] @@ -70,6 +102,7 @@ export interface MeResponse extends ApiResponse { } } + // ---------- category ---------- export interface CategoryTranslation { diff --git a/src/auth/permissions.ts b/src/auth/permissions.ts index 69dc8e7..6999b32 100644 --- a/src/auth/permissions.ts +++ b/src/auth/permissions.ts @@ -36,6 +36,8 @@ export const PERM = { ROLE_DELETE: 'auth:role:delete', ROLE_ASSIGN: 'auth:role:assign', PERMISSION_READ: 'auth:permission:read', + USER_READ: 'admin:user:read', + USER_MANAGE: 'admin:user:manage', } as const export type Permission = (typeof PERM)[keyof typeof PERM] diff --git a/src/components/layout/nav.ts b/src/components/layout/nav.ts index 43a357f..2a55508 100644 --- a/src/components/layout/nav.ts +++ b/src/components/layout/nav.ts @@ -90,6 +90,7 @@ export const NAV_GROUPS: NavGroup[] = [ { label: '用户与系统', items: [ + { label: '用户列表', to: '/users', icon: Users, permission: 'auth:role:assign' }, { label: '角色管理', to: '/roles', icon: Shield, permission: 'auth:role:read' }, { label: '用户角色', to: '/admin-users', icon: Users, permission: 'auth:role:assign' }, { label: 'SID 管理', to: '/sid', icon: Hash, permission: 'admin:sid:manage' }, diff --git a/src/features/users/user-detail-sheet.tsx b/src/features/users/user-detail-sheet.tsx new file mode 100644 index 0000000..bf4fad8 --- /dev/null +++ b/src/features/users/user-detail-sheet.tsx @@ -0,0 +1,220 @@ +import { useQuery } from '@tanstack/react-query' +import { CheckCircle2, Shield, XCircle } from 'lucide-react' +import { fetchUserDetail } from '@/api/modules/users' +import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar' +import { Badge } from '@/components/ui/badge' +import { Button } from '@/components/ui/button' +import { + Sheet, + SheetContent, + SheetDescription, + SheetHeader, + SheetTitle, +} from '@/components/ui/sheet' + +interface UserDetailSheetProps { + uid: string | null + onClose: () => void + onAssignRoles?: () => void +} + +export function UserDetailSheet({ uid, onClose, onAssignRoles }: UserDetailSheetProps) { + const query = useQuery({ + queryKey: ['users', uid], + queryFn: () => (uid ? fetchUserDetail(uid) : null), + enabled: uid !== null, + }) + + const user = query.data?.data + + return ( + !open && onClose()}> + + +
+ + + + {(user?.display_name ?? user?.username ?? 'U').slice(0, 1).toUpperCase()} + + +
+ + {user?.display_name ?? user?.username ?? '用户详情'} + + + {user?.uid} + +
+
+
+ + {query.isPending ? ( +
加载中…
+ ) : !user ? ( +
未找到用户信息
+ ) : ( +
+ {/* Status overview */} +
+ + {user.status ? '正常启用' : '已禁用'} + + {user.is_robot ? ( + 机器人账号 + ) : ( + 真人账号 + )} + {user.sid_display && ( + + SID: {user.sid_display} + + )} +
+ + {/* Account Info */} +
+

+ 账号基本信息 +

+
+
+
用户名
+
{user.username ?? '—'}
+
+
+
显示名称
+
{user.display_name ?? '—'}
+
+
+
SID
+
{user.sid ?? '—'}
+
+
+
专业领域
+
{user.expertise ?? '—'}
+
+
+
+ + {/* Contact & Verification */} +
+

+ 联系方式与验证 +

+
+
+
+
邮箱
+
{user.email ?? '—'}
+
+ {user.email && ( + + {user.email_verified_at ? ( + <> + + 已验证 + + ) : ( + <> + + 未验证 + + )} + + )} +
+
+
+
手机号
+
{user.phone ?? '—'}
+
+ {user.phone && ( + + {user.phone_verified_at ? ( + <> + + 已验证 + + ) : ( + <> + + 未验证 + + )} + + )} +
+
+
+ + {/* Locale & Region */} +
+

+ 地区与时区 +

+
+
+
国家/地区
+
{user.country_code ?? '—'}
+
+
+
城市
+
{user.city ?? '—'}
+
+
+
时区
+
{user.timezone ?? '—'}
+
+
+
语言偏好
+
{user.locale ?? '—'}
+
+
+
+ + {/* Roles */} +
+
+

+ 已分配角色 +

+ {onAssignRoles && ( + + )} +
+
+ {user.roles && user.roles.length > 0 ? ( + user.roles.map((role) => ( + + {role} + + )) + ) : ( + 未分配任何角色 + )} +
+
+ + {/* Timestamps */} +
+
+ 注册时间: + {new Date(user.created_at).toLocaleString('zh-CN')} +
+ {user.updated_at && ( +
+ 更新时间: + {new Date(user.updated_at).toLocaleString('zh-CN')} +
+ )} +
+
+ )} +
+
+ ) +} diff --git a/src/routeTree.gen.ts b/src/routeTree.gen.ts index 6ae1a2d..2dd11ed 100644 --- a/src/routeTree.gen.ts +++ b/src/routeTree.gen.ts @@ -21,6 +21,7 @@ import { Route as AuthedDimzouTranslationsRouteImport } from './routes/_authed/d import { Route as AuthedDevicesRouteImport } from './routes/_authed/devices' import { Route as AuthedCommentReportsRouteImport } from './routes/_authed/comment-reports' import { Route as AuthedAdminUsersRouteImport } from './routes/_authed/admin-users' +import { Route as AuthedUsersIndexRouteImport } from './routes/_authed/users/index' import { Route as AuthedCategoriesIndexRouteImport } from './routes/_authed/categories/index' import { Route as AuthedStudioSubtopicsRouteImport } from './routes/_authed/studio/subtopics' import { Route as AuthedStudioStylePresetsRouteImport } from './routes/_authed/studio/style-presets' @@ -97,6 +98,11 @@ const AuthedAdminUsersRoute = AuthedAdminUsersRouteImport.update({ path: '/admin-users', getParentRoute: () => AuthedRoute, } as any) +const AuthedUsersIndexRoute = AuthedUsersIndexRouteImport.update({ + id: '/users/', + path: '/users/', + getParentRoute: () => AuthedRoute, +} as any) const AuthedCategoriesIndexRoute = AuthedCategoriesIndexRouteImport.update({ id: '/categories/', path: '/categories/', @@ -204,6 +210,7 @@ export interface FileRoutesByFullPath { '/studio/style-presets': typeof AuthedStudioStylePresetsRoute '/studio/subtopics': typeof AuthedStudioSubtopicsRoute '/categories/': typeof AuthedCategoriesIndexRoute + '/users/': typeof AuthedUsersIndexRoute } export interface FileRoutesByTo { '/login': typeof LoginRoute @@ -232,6 +239,7 @@ export interface FileRoutesByTo { '/studio/style-presets': typeof AuthedStudioStylePresetsRoute '/studio/subtopics': typeof AuthedStudioSubtopicsRoute '/categories': typeof AuthedCategoriesIndexRoute + '/users': typeof AuthedUsersIndexRoute } export interface FileRoutesById { __root__: typeof rootRouteImport @@ -262,6 +270,7 @@ export interface FileRoutesById { '/_authed/studio/style-presets': typeof AuthedStudioStylePresetsRoute '/_authed/studio/subtopics': typeof AuthedStudioSubtopicsRoute '/_authed/categories/': typeof AuthedCategoriesIndexRoute + '/_authed/users/': typeof AuthedUsersIndexRoute } export interface FileRouteTypes { fileRoutesByFullPath: FileRoutesByFullPath @@ -292,6 +301,7 @@ export interface FileRouteTypes { | '/studio/style-presets' | '/studio/subtopics' | '/categories/' + | '/users/' fileRoutesByTo: FileRoutesByTo to: | '/login' @@ -320,6 +330,7 @@ export interface FileRouteTypes { | '/studio/style-presets' | '/studio/subtopics' | '/categories' + | '/users' id: | '__root__' | '/_authed' @@ -349,6 +360,7 @@ export interface FileRouteTypes { | '/_authed/studio/style-presets' | '/_authed/studio/subtopics' | '/_authed/categories/' + | '/_authed/users/' fileRoutesById: FileRoutesById } export interface RootRouteChildren { @@ -442,6 +454,13 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof AuthedAdminUsersRouteImport parentRoute: typeof AuthedRoute } + '/_authed/users/': { + id: '/_authed/users/' + path: '/users' + fullPath: '/users/' + preLoaderRoute: typeof AuthedUsersIndexRouteImport + parentRoute: typeof AuthedRoute + } '/_authed/categories/': { id: '/_authed/categories/' path: '/categories' @@ -576,6 +595,7 @@ interface AuthedRouteChildren { AuthedStudioStylePresetsRoute: typeof AuthedStudioStylePresetsRoute AuthedStudioSubtopicsRoute: typeof AuthedStudioSubtopicsRoute AuthedCategoriesIndexRoute: typeof AuthedCategoriesIndexRoute + AuthedUsersIndexRoute: typeof AuthedUsersIndexRoute } const AuthedRouteChildren: AuthedRouteChildren = { @@ -604,6 +624,7 @@ const AuthedRouteChildren: AuthedRouteChildren = { AuthedStudioStylePresetsRoute: AuthedStudioStylePresetsRoute, AuthedStudioSubtopicsRoute: AuthedStudioSubtopicsRoute, AuthedCategoriesIndexRoute: AuthedCategoriesIndexRoute, + AuthedUsersIndexRoute: AuthedUsersIndexRoute, } const AuthedRouteWithChildren = diff --git a/src/routes/_authed/users/index.tsx b/src/routes/_authed/users/index.tsx new file mode 100644 index 0000000..65f7a43 --- /dev/null +++ b/src/routes/_authed/users/index.tsx @@ -0,0 +1,475 @@ +import { createFileRoute } from '@tanstack/react-router' +import { PERM, requirePermission } from '@/auth/permissions' +import { useQuery, useQueryClient } from '@tanstack/react-query' +import { useState } from 'react' +import { Eye, Search, Shield } from 'lucide-react' +import { toast } from 'sonner' +import type { ColumnDef } from '@tanstack/react-table' +import type { PlatformUser } from '@/api/types' +import { fetchUsers, updateUserStatus } from '@/api/modules/users' +import { fetchRoles, syncUserRoles } from '@/api/modules/roles' +import { useAuthStore } from '@/auth/store' +import { handleApiError } from '@/lib/errors' +import { DataTable } from '@/components/data-table/data-table' +import { PageHeader } from '@/components/page-header' +import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar' +import { + AlertDialog, + AlertDialogAction, + AlertDialogCancel, + AlertDialogContent, + AlertDialogDescription, + AlertDialogFooter, + AlertDialogHeader, + AlertDialogTitle, +} from '@/components/ui/alert-dialog' +import { Badge } from '@/components/ui/badge' +import { Button } from '@/components/ui/button' +import { Checkbox } from '@/components/ui/checkbox' +import { + Dialog, + DialogContent, + DialogFooter, + DialogHeader, + DialogTitle, +} from '@/components/ui/dialog' +import { Input } from '@/components/ui/input' +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from '@/components/ui/select' +import { Switch } from '@/components/ui/switch' +import { UserDetailSheet } from '@/features/users/user-detail-sheet' + +export const Route = createFileRoute('/_authed/users/')({ + beforeLoad: () => requirePermission(PERM.ROLE_ASSIGN), + component: UsersPage, +}) + +function UsersPage() { + const queryClient = useQueryClient() + const myUid = useAuthStore((s) => s.user?.uid) + + // Filter state + const [searchInput, setSearchInput] = useState('') + const [q, setQ] = useState('') + const [statusFilter, setStatusFilter] = useState<'all' | 'true' | 'false'>('all') + const [isRobotFilter, setIsRobotFilter] = useState<'all' | 'true' | 'false'>('all') + const [roleFilter, setRoleFilter] = useState('all') + const [page, setPage] = useState(1) + const pageSize = 20 + + // Selected user state for modals + const [detailUid, setDetailUid] = useState(null) + const [assigningUser, setAssigningUser] = useState(null) + const [togglingUser, setTogglingUser] = useState<{ user: PlatformUser; targetStatus: boolean } | null>(null) + + // Available roles query for filter dropdown + const rolesQuery = useQuery({ queryKey: ['roles'], queryFn: fetchRoles }) + + // Users listing query + const usersQuery = useQuery({ + queryKey: ['users-list', { q, statusFilter, isRobotFilter, roleFilter, page }], + queryFn: () => + fetchUsers({ + q: q || undefined, + status: statusFilter === 'all' ? undefined : statusFilter === 'true', + is_robot: isRobotFilter === 'all' ? undefined : isRobotFilter === 'true', + role: roleFilter === 'all' ? undefined : roleFilter, + page, + page_size: pageSize, + }), + }) + + const refresh = () => { + void queryClient.invalidateQueries({ queryKey: ['users-list'] }) + if (detailUid) { + void queryClient.invalidateQueries({ queryKey: ['users', detailUid] }) + } + } + + const handleStatusToggle = async (user: PlatformUser, newStatus: boolean) => { + try { + await updateUserStatus(user.uid, newStatus) + toast.success(`已${newStatus ? '启用' : '禁用'}用户 ${user.display_name ?? user.username ?? user.uid}`) + refresh() + } catch (error) { + handleApiError(error) + } + } + + const columns: ColumnDef[] = [ + { + header: '用户', + cell: ({ row }) => { + const u = row.original + return ( +
+ + + + {(u.display_name ?? u.username ?? 'U').slice(0, 1).toUpperCase()} + + +
+
+ {u.display_name ?? u.username ?? '—'} + {u.sid_display && ( + + {u.sid_display} + + )} +
+
+ {u.uid} +
+
+
+ ) + }, + }, + { + header: '联系方式', + cell: ({ row }) => { + const u = row.original + return ( +
+
{u.email ?? '—'}
+ {u.phone &&
{u.phone}
} +
+ ) + }, + }, + { + header: '类型', + cell: ({ row }) => ( + + {row.original.is_robot ? '机器人' : '真人'} + + ), + }, + { + header: '角色', + cell: ({ row }) => ( +
+ {row.original.roles && row.original.roles.length > 0 ? ( + row.original.roles.map((role) => ( + + {role} + + )) + ) : ( + + )} +
+ ), + }, + { + header: '账号状态', + cell: ({ row }) => { + const u = row.original + const isSelf = u.uid === myUid + return ( +
+ { + if (!checked) { + setTogglingUser({ user: u, targetStatus: false }) + } else { + void handleStatusToggle(u, true) + } + }} + /> + + {u.status ? '已启用' : '已禁用'} + +
+ ) + }, + }, + { + header: '注册时间', + cell: ({ row }) => ( + + {new Date(row.original.created_at).toLocaleDateString('zh-CN')} + + ), + }, + { + header: '操作', + cell: ({ row }) => { + const u = row.original + return ( +
+ + +
+ ) + }, + }, + ] + + const pagination = usersQuery.data?.pagination + const totalPages = pagination?.total_pages ?? 1 + const totalCount = pagination?.total_count ?? 0 + + return ( +
+ + + {/* Filter Bar */} +
+
+ + setSearchInput(e.target.value)} + onKeyDown={(e) => { + if (e.key === 'Enter') { + setQ(searchInput.trim()) + setPage(1) + } + }} + placeholder="搜索邮箱 / 用户名 / 昵称 / 手机号 / UID / SID (回车搜索)" + className="pl-8 h-9 text-sm" + /> +
+ + {/* Status Filter */} + + + {/* Robot Filter */} + + + {/* Role Filter */} + +
+ + {/* Table */} + + + {/* Pagination Bar */} + {totalCount > 0 && ( +
+
共 {totalCount} 条记录,当前第 {page} / {totalPages} 页
+
+ + +
+
+ )} + + {/* Disable confirmation dialog */} + !open && setTogglingUser(null)}> + + + + 确定禁用用户 {togglingUser?.user.display_name ?? togglingUser?.user.username ?? togglingUser?.user.uid}? + + + 禁用后该用户的所有有效 Token 将被吊销,且无法再登录平台。 + + + + setTogglingUser(null)}>取消 + { + if (togglingUser) { + void handleStatusToggle(togglingUser.user, false) + setTogglingUser(null) + } + }} + > + 确定禁用 + + + + + + {/* User Detail Sheet */} + setDetailUid(null)} + onAssignRoles={() => { + const u = usersQuery.data?.data?.find((x) => x.uid === detailUid) + if (u) { + setAssigningUser(u) + } + }} + /> + + {/* Assign Roles Modal */} + {assigningUser && ( + setAssigningUser(null)} + onSaved={() => { + refresh() + setAssigningUser(null) + }} + /> + )} +
+ ) +} + +function AssignRolesModal({ + user, + onClose, + onSaved, +}: { + user: PlatformUser + onClose: () => void + onSaved: () => void +}) { + const rolesQuery = useQuery({ queryKey: ['roles'], queryFn: fetchRoles }) + const [selected, setSelected] = useState>(new Set(user.roles ?? [])) + const [busy, setBusy] = useState(false) + + const toggle = (name: string, checked: boolean) => { + setSelected((prev) => { + const next = new Set(prev) + if (checked) next.add(name) + else next.delete(name) + return next + }) + } + + const save = async () => { + setBusy(true) + try { + await syncUserRoles(user.uid, [...selected]) + toast.success(`已更新 ${user.display_name ?? user.email ?? user.uid} 的角色`) + onSaved() + } catch (error) { + handleApiError(error) + } finally { + setBusy(false) + } + } + + return ( + !open && onClose()}> + + + 分配角色:{user.display_name ?? user.username ?? user.uid} + +
+ {(rolesQuery.data?.data ?? []).map((role) => ( + + ))} +
+ + + + +
+
+ ) +} diff --git a/src/routes/login.tsx b/src/routes/login.tsx index 6064893..b48a421 100644 --- a/src/routes/login.tsx +++ b/src/routes/login.tsx @@ -45,11 +45,6 @@ function LoginPage() { const me = await fetchMe() const roles = me.meta?.roles ?? [] - if (!roles.includes('admin') && !roles.includes('super-admin')) { - useAuthStore.getState().clear() - toast.error('该账号不是管理员,无法登录管理后台') - return - } useAuthStore.getState().setSession(me.data, roles, me.meta?.permissions ?? []) await navigate({ to: '/' })