@allbluecn/admin-app 0.3.1 → 0.3.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +19 -17
- package/src/components/admin/admin-user-menu.tsx +4 -4
- package/src/components/admin/developer-log.tsx +16 -32
- package/src/components/ai-memory/memory-facts-panel.tsx +30 -70
- package/src/components/ai-prompts/preview-panel.tsx +1 -1
- package/src/components/ai-prompts/prompt-editor.tsx +8 -9
- package/src/components/ai-prompts/prompt-table.tsx +6 -6
- package/src/components/ai-prompts/section-editor.tsx +3 -3
- package/src/components/ai-prompts/variable-panel.tsx +2 -2
- package/src/components/ai-prompts/version-history.tsx +21 -41
- package/src/components/ai-provider/model-list-editor.tsx +2 -3
- package/src/components/ai-provider/provider-form.tsx +7 -7
- package/src/components/ai-provider/provider-table.tsx +17 -40
- package/src/components/auth/admin-login-form.tsx +7 -7
- package/src/components/auth/icon-input.tsx +2 -2
- package/src/components/auth/password-input.tsx +2 -2
- package/src/components/component/component-form-dialog.tsx +10 -18
- package/src/components/component/component-import-dialog.tsx +6 -6
- package/src/components/component/component-preview.tsx +311 -0
- package/src/components/component/component-table.tsx +3 -3
- package/src/components/dependency/changelog-dialog.tsx +2 -2
- package/src/components/dependency/columns.tsx +3 -3
- package/src/components/dependency/dependency-table.tsx +7 -7
- package/src/components/dependency/edit-dialog.tsx +8 -8
- package/src/components/invitations/invitations-page.tsx +26 -49
- package/src/components/knowledge-public/bookmark-form-dialog.tsx +6 -6
- package/src/components/knowledge-public/compliance-form-dialog.tsx +6 -6
- package/src/components/knowledge-public/legal-form-dialog.tsx +7 -7
- package/src/components/layout/sidebar-02/app-sidebar.tsx +1 -2
- package/src/components/layout/sidebar-02/index.tsx +1 -1
- package/src/components/layout/sidebar-02/nav-main.tsx +2 -3
- package/src/components/layout/sidebar-02/nav-notifications.tsx +3 -3
- package/src/components/market/columns.tsx +4 -4
- package/src/components/market/market-app-form.tsx +6 -7
- package/src/components/market/market-app-list.tsx +14 -29
- package/src/components/monitoring/error-list.tsx +2 -2
- package/src/components/monitoring/metrics-stats.tsx +1 -1
- package/src/components/monitoring/request-log-table.tsx +2 -2
- package/src/components/oauth-providers/provider-page.tsx +20 -38
- package/src/components/permissions/assign-dialog.tsx +135 -0
- package/src/components/permissions/create-role-dialog.tsx +135 -0
- package/src/components/permissions/types.ts +49 -0
- package/src/components/user/columns.tsx +6 -6
- package/src/components/user/settings-activity-log.tsx +7 -7
- package/src/components/user/settings-profile/avatar-uploader.tsx +118 -0
- package/src/components/user/settings-profile/social-links.tsx +63 -0
- package/src/components/user/settings-profile.tsx +26 -125
- package/src/components/user/user-create-dialog.tsx +6 -6
- package/src/components/user/user-detail/error-boundary.tsx +19 -0
- package/src/components/user/user-detail/user-detail-dialogs.tsx +203 -0
- package/src/components/user/user-filters.tsx +4 -4
- package/src/lib/auth/page-gate.ts +28 -0
- package/src/lib/auth.config.ts +25 -9
- package/src/lib/i18n/detect.ts +1 -1
- package/src/lib/loader-serial.ts +14 -0
- package/src/paraglide/README.md +1 -1
- package/src/providers/index.tsx +1 -1
- package/src/routeTree.gen.ts +67 -78
- package/src/routes/__root.tsx +4 -4
- package/src/routes/_nav/audit/route.lazy.tsx +1 -2
- package/src/routes/_nav/compliance/route.lazy.tsx +5 -3
- package/src/routes/_nav/components/route.lazy.tsx +14 -34
- package/src/routes/_nav/knowledge-public/bookmarks/route.lazy.tsx +18 -38
- package/src/routes/_nav/knowledge-public/compliance/route.lazy.tsx +18 -38
- package/src/routes/_nav/knowledge-public/legal/route.lazy.tsx +6 -6
- package/src/routes/_nav/licenses/$licenseId/route.lazy.tsx +3 -3
- package/src/routes/_nav/licenses/new/route.lazy.tsx +5 -5
- package/src/routes/_nav/licenses/route.lazy.tsx +1 -1
- package/src/routes/_nav/monitoring/ai-usage/route.lazy.tsx +2 -2
- package/src/routes/_nav/monitoring/errors/route.lazy.tsx +10 -10
- package/src/routes/_nav/monitoring/errors/route.tsx +7 -0
- package/src/routes/_nav/monitoring/health/route.lazy.tsx +13 -15
- package/src/routes/_nav/monitoring/health/route.tsx +14 -0
- package/src/routes/_nav/monitoring/logs/route.lazy.tsx +24 -9
- package/src/routes/_nav/monitoring/logs/route.tsx +13 -0
- package/src/routes/_nav/monitoring/metrics/route.lazy.tsx +12 -14
- package/src/routes/_nav/monitoring/metrics/route.tsx +7 -0
- package/src/routes/_nav/monitoring/overview/route.lazy.tsx +14 -13
- package/src/routes/_nav/monitoring/overview/route.tsx +17 -0
- package/src/routes/_nav/monitoring/route.lazy.tsx +6 -6
- package/src/routes/_nav/monitoring/sentry/route.lazy.tsx +2 -2
- package/src/routes/_nav/permissions/route.lazy.tsx +21 -311
- package/src/routes/_nav/permissions/route.tsx +1 -1
- package/src/routes/_nav/route.tsx +5 -13
- package/src/routes/_nav/sso/route.lazy.tsx +2 -3
- package/src/routes/_nav/users/$userId/route.lazy.tsx +91 -156
- package/src/routes/_nav/users/-components/users-table.tsx +1 -1
- package/src/routes/_nav/users/route.lazy.tsx +1 -1
- package/src/routes/login/route.lazy.tsx +1 -1
- package/src/routes/setup/route.lazy.tsx +2 -2
- package/src/server/__tests__/admin-billing.test.ts +191 -16
- package/src/server/__tests__/compliance.test.ts +44 -7
- package/src/server/__tests__/permissions.test.ts +10 -5
- package/src/server/__tests__/prompt-defaults.test.ts +3 -3
- package/src/server/admin-ai-alerts.ts +2 -1
- package/src/server/admin-ai-usage.ts +2 -1
- package/src/server/ai-memory/store.ts +6 -2
- package/src/server/{serverFns/ai-prompts → ai-prompts}/prompt-defaults.ts +5 -3
- package/src/server/{serverFns/dependency → dependency}/helpers.ts +4 -3
- package/src/server/knowledge-public/github-sync.ts +2 -1
- package/src/server/license-issue.ts +2 -1
- package/src/server/middlewares/auth-rate-limit.ts +117 -0
- package/src/server/observability/health-check.ts +4 -1
- package/src/server/observability/metrics.ts +17 -15
- package/src/server/observability/request-middleware.ts +2 -1
- package/src/server/observability/timing.ts +14 -16
- package/src/server/serverFns/admin-billing.ts +107 -12
- package/src/server/serverFns/ai-memory/list-users.ts +2 -1
- package/src/server/serverFns/ai-prompts/activate.ts +2 -1
- package/src/server/serverFns/ai-prompts/get.ts +3 -2
- package/src/server/serverFns/ai-prompts/list.ts +3 -2
- package/src/server/serverFns/ai-prompts/rollback.ts +3 -1
- package/src/server/serverFns/ai-prompts/save.ts +5 -3
- package/src/server/serverFns/ai-provider/create.ts +4 -1
- package/src/server/serverFns/ai-provider/delete.ts +4 -1
- package/src/server/serverFns/ai-provider/get.ts +4 -1
- package/src/server/serverFns/ai-provider/list.ts +28 -1
- package/src/server/serverFns/ai-provider/seed-from-seed.ts +2 -1
- package/src/server/serverFns/ai-provider/update.ts +4 -1
- package/src/server/serverFns/anyhub/market.ts +8 -1
- package/src/server/serverFns/audit.ts +14 -1
- package/src/server/serverFns/auth/setup.ts +3 -1
- package/src/server/serverFns/compliance.ts +32 -3
- package/src/server/serverFns/components/import.ts +69 -14
- package/src/server/serverFns/components/list.ts +4 -1
- package/src/server/serverFns/components/mutations.ts +9 -1
- package/src/server/serverFns/dependency/list.ts +28 -5
- package/src/server/serverFns/dependency/sync.ts +7 -2
- package/src/server/serverFns/dependency/update.ts +4 -1
- package/src/server/serverFns/developer-log-auto-generate.ts +63 -32
- package/src/server/serverFns/developer-logs.ts +5 -1
- package/src/server/serverFns/invitations.ts +7 -1
- package/src/server/serverFns/knowledge-public/bookmarks/create.ts +2 -1
- package/src/server/serverFns/knowledge-public/bookmarks/delete.ts +2 -1
- package/src/server/serverFns/knowledge-public/bookmarks/detail.ts +2 -1
- package/src/server/serverFns/knowledge-public/bookmarks/list.ts +2 -1
- package/src/server/serverFns/knowledge-public/bookmarks/sync-all-github.ts +2 -1
- package/src/server/serverFns/knowledge-public/bookmarks/update.ts +2 -1
- package/src/server/serverFns/knowledge-public/compliance/create.ts +2 -1
- package/src/server/serverFns/knowledge-public/compliance/delete.ts +2 -1
- package/src/server/serverFns/knowledge-public/compliance/detail.ts +2 -1
- package/src/server/serverFns/knowledge-public/compliance/list.ts +2 -1
- package/src/server/serverFns/knowledge-public/compliance/update.ts +2 -1
- package/src/server/serverFns/knowledge-public/legal/create.ts +2 -1
- package/src/server/serverFns/knowledge-public/legal/delete.ts +2 -1
- package/src/server/serverFns/knowledge-public/legal/detail.ts +2 -1
- package/src/server/serverFns/knowledge-public/legal/list.ts +2 -1
- package/src/server/serverFns/knowledge-public/legal/update.ts +2 -1
- package/src/server/serverFns/licenses.ts +8 -2
- package/src/server/serverFns/monitoring/get-errors.ts +2 -1
- package/src/server/serverFns/monitoring/get-health.ts +2 -1
- package/src/server/serverFns/monitoring/get-metrics.ts +2 -1
- package/src/server/serverFns/monitoring/get-request-logs.ts +2 -1
- package/src/server/serverFns/monitoring/report-error.ts +2 -1
- package/src/server/serverFns/monitoring/reset.ts +2 -1
- package/src/server/serverFns/oauth-providers.ts +4 -1
- package/src/server/serverFns/permissions.ts +18 -11
- package/src/server/serverFns/sentry/config.ts +11 -8
- package/src/server/serverFns/sso-config.ts +17 -2
- package/src/server/serverFns/user/create.ts +6 -1
- package/src/server/serverFns/user/delete.ts +6 -1
- package/src/server/serverFns/user/detail.ts +5 -2
- package/src/server/serverFns/user/list.ts +19 -4
- package/src/server/serverFns/user/reset-password.ts +10 -2
- package/src/server/serverFns/user/update-role.ts +10 -2
- package/src/server/serverFns/user/update-status.ts +10 -2
- package/src/styles/globals.css +4 -0
- package/src/components/data-table/data-table-pagination.tsx +0 -112
- package/src/components/data-table/data-table.tsx +0 -218
- package/src/components/data-table/table-detail-layout.tsx +0 -98
- package/src/components/ui/alert-dialog.tsx +0 -187
- package/src/components/ui/alert.tsx +0 -75
- package/src/components/ui/avatar.tsx +0 -106
- package/src/components/ui/badge.tsx +0 -51
- package/src/components/ui/button.tsx +0 -57
- package/src/components/ui/card.tsx +0 -102
- package/src/components/ui/checkbox.tsx +0 -28
- package/src/components/ui/collapsible.tsx +0 -19
- package/src/components/ui/dialog.tsx +0 -155
- package/src/components/ui/dropdown-menu.tsx +0 -267
- package/src/components/ui/field.tsx +0 -236
- package/src/components/ui/input-group.tsx +0 -158
- package/src/components/ui/input.tsx +0 -19
- package/src/components/ui/label.tsx +0 -17
- package/src/components/ui/pagination.tsx +0 -130
- package/src/components/ui/select.tsx +0 -234
- package/src/components/ui/separator.tsx +0 -22
- package/src/components/ui/sheet.tsx +0 -135
- package/src/components/ui/sidebar.tsx +0 -721
- package/src/components/ui/skeleton.tsx +0 -13
- package/src/components/ui/switch.tsx +0 -29
- package/src/components/ui/table.tsx +0 -115
- package/src/components/ui/textarea.tsx +0 -17
- package/src/components/ui/toast.tsx +0 -282
- package/src/components/ui/tooltip.tsx +0 -63
- package/src/hooks/use-mobile.ts +0 -19
- package/src/lib/avatar.ts +0 -41
- /package/src/routes/{api.auth.$.ts → api/auth/$.ts} +0 -0
- /package/src/routes/{api.sentry-test.ts → api/sentry-test.ts} +0 -0
|
@@ -17,21 +17,52 @@
|
|
|
17
17
|
|
|
18
18
|
import { createServerFn } from '@tanstack/react-start'
|
|
19
19
|
import { z } from 'zod'
|
|
20
|
-
import {
|
|
20
|
+
import { getPrisma } from '@allbluecn/database/get-prisma'
|
|
21
21
|
import { execFileSync } from 'node:child_process'
|
|
22
22
|
import { writeFileSync, mkdirSync, existsSync, readFileSync } from 'node:fs'
|
|
23
23
|
import { resolve, join } from 'node:path'
|
|
24
|
-
|
|
25
|
-
|
|
24
|
+
import { requireAdmin } from '@/server/auth-helpers'
|
|
25
|
+
|
|
26
|
+
// ── 供应链风险决策(SEC-01 联动)──────────────────────────────────────────
|
|
27
|
+
// `pnpm add <包名>` 会执行该包的 postinstall 等生命周期脚本,等同在服务器上
|
|
28
|
+
// 任意代码执行;`pnpm dlx shadcn add <path>` 也会按 registry 路径拉取并写入
|
|
29
|
+
// 任意来源的组件文件。除 requireAdmin 鉴权外,追加以下防线:
|
|
30
|
+
// ① 包名白名单格式:仅小写字母/数字开头的扁平包名(允许 . _ -),拒绝
|
|
31
|
+
// @scope、`/`(GitHub shorthand 与路径分隔符,防 a/../../evil 绕过
|
|
32
|
+
// registry 固定)、URL、绝对路径等一切非常规形态;
|
|
33
|
+
// ② registry 显式固定为 npm 官方源,防止 .npmrc / 环境变量被劫持改指向
|
|
34
|
+
// 恶意镜像(typosquatting 面收窄到官方 registry 的审计能力内);
|
|
35
|
+
// ③ --ignore-scripts:跳过被安装包的全部生命周期脚本,组件类纯 JS 包
|
|
36
|
+
// 无需 install 脚本,个别依赖构建脚本的包会装出不可用组件——宁可
|
|
37
|
+
// 导入失败人工介入,不在服务端静默执行第三方脚本;
|
|
38
|
+
// ④ shadcn 路径(dlx 分支)校验为合法 registry item 形态(组件名或
|
|
39
|
+
// @scope/name),拒绝任意 URL / 绝对路径;@/ 本地路径同样做段字符
|
|
40
|
+
// 白名单(拒引号/`${`/空白等模板注入字符)且 resolve 后强制约束在
|
|
41
|
+
// apps/web/src 之内,堵死路径穿越式任意文件读;
|
|
42
|
+
// ⑤ 所有插值进生成的 .tsx wrapper 的字符串(displayName、exportName、
|
|
43
|
+
// importSource、componentKey)一律先过字符白名单/标识符校验——
|
|
44
|
+
// wrapper 是会被 Vite 编译执行的代码,插值即模板注入面。
|
|
45
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
46
|
+
const NPM_PACKAGE_NAME_RE = /^[a-z0-9][a-z0-9._-]*$/
|
|
47
|
+
const SHADCN_REGISTRY_PATH_RE = /^(?:@[a-z0-9][a-z0-9._-]*\/)?[a-z0-9][a-z0-9._-]*$/
|
|
48
|
+
// @/ 本地路径:段字符白名单 [a-z0-9._-],段必须以字母/数字开头(. 与 ..
|
|
49
|
+
// 段天然被拒),拒引号、反斜杠、空白、`${}` 等一切模板注入字符
|
|
50
|
+
const LOCAL_IMPORT_PATH_RE = /^@\/[a-z0-9][a-z0-9._-]*(?:\/[a-z0-9][a-z0-9._-]*)*$/
|
|
51
|
+
// displayName 会插值进 wrapper 的 `name: "${displayName}"`:仅允许中文、
|
|
52
|
+
// 字母、数字、空格与 . _ -,拒引号/反引号/反斜杠/`${}`/尖括号等
|
|
53
|
+
const DISPLAY_NAME_RE = /^[\p{Script=Han}a-zA-Z0-9][\p{Script=Han}a-zA-Z0-9 ._-]*$/u
|
|
54
|
+
// exportName 会插值进 `import { x as Comp } from ...`:必须为合法 JS 标识符
|
|
55
|
+
const EXPORT_NAME_RE = /^[A-Za-z_$][A-Za-z0-9_$]*$/
|
|
56
|
+
const PINNED_REGISTRY = 'https://registry.npmjs.org/'
|
|
26
57
|
const COMPONENT_KEY_RE = /^[a-z0-9]+(?:-[a-z0-9]+)*$/
|
|
27
58
|
|
|
28
59
|
const importInput = z.object({
|
|
29
60
|
source: z.enum(['npm', 'local']).default('npm'),
|
|
30
|
-
packageName: z.string().regex(
|
|
61
|
+
packageName: z.string().regex(NPM_PACKAGE_NAME_RE, '无效的 npm 包名(仅允许小写字母、数字与 . _ -,拒绝 @scope 与斜杠)').optional(),
|
|
31
62
|
importPath: z.string().min(1, '不能为空').optional(),
|
|
32
|
-
exportName: z.string().default('default'),
|
|
63
|
+
exportName: z.string().regex(EXPORT_NAME_RE, '导出名必须为合法标识符').default('default'),
|
|
33
64
|
componentKey: z.string().regex(COMPONENT_KEY_RE, '标识只能含小写字母、数字、连字符'),
|
|
34
|
-
displayName: z.string().
|
|
65
|
+
displayName: z.string().regex(DISPLAY_NAME_RE, '显示名称只能含中文、字母、数字、空格与 . _ -'),
|
|
35
66
|
category: z.enum(['基础组件', '内容区块', '页面模版']),
|
|
36
67
|
type: z.enum(['component', 'block', 'template']).default('component'),
|
|
37
68
|
defaultWidth: z.number().int().positive().default(200),
|
|
@@ -64,6 +95,8 @@ function detectExportName(filePath: string, fallback: string): string {
|
|
|
64
95
|
export const importComponentFn = createServerFn({ method: 'POST' })
|
|
65
96
|
.validator(importInput)
|
|
66
97
|
.handler(async ({ data }) => {
|
|
98
|
+
const prisma = await getPrisma()
|
|
99
|
+
await requireAdmin()
|
|
67
100
|
const {
|
|
68
101
|
source,
|
|
69
102
|
packageName,
|
|
@@ -87,13 +120,20 @@ export const importComponentFn = createServerFn({ method: 'POST' })
|
|
|
87
120
|
|
|
88
121
|
if (source === 'npm') {
|
|
89
122
|
if (!packageName) throw new Error('npm 包名不能为空')
|
|
123
|
+
if (!NPM_PACKAGE_NAME_RE.test(packageName)) {
|
|
124
|
+
throw new Error(`拒绝安装非常规包名 "${packageName}":仅允许小写字母/数字开头的非 scope 包(供应链防护)`)
|
|
125
|
+
}
|
|
90
126
|
const prototypeDir = join(monorepoRoot, 'packages/prototype')
|
|
91
127
|
try {
|
|
92
|
-
execFileSync(
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
128
|
+
execFileSync(
|
|
129
|
+
'pnpm',
|
|
130
|
+
['add', packageName, `--registry=${PINNED_REGISTRY}`, '--ignore-scripts'],
|
|
131
|
+
{
|
|
132
|
+
cwd: prototypeDir,
|
|
133
|
+
stdio: 'pipe',
|
|
134
|
+
timeout: 60000,
|
|
135
|
+
},
|
|
136
|
+
)
|
|
97
137
|
} catch {
|
|
98
138
|
throw new Error(`安装包 "${packageName}" 失败,请检查包名或网络`)
|
|
99
139
|
}
|
|
@@ -107,11 +147,25 @@ export const importComponentFn = createServerFn({ method: 'POST' })
|
|
|
107
147
|
let componentFilePath: string | null = null
|
|
108
148
|
|
|
109
149
|
if (importPath.startsWith('@/')) {
|
|
150
|
+
// 段字符白名单:拒引号/`${`/空白等模板注入字符与 . .. 段(与 resolve
|
|
151
|
+
// 前缀检查构成双防线——前者护 wrapper 代码生成,后者护文件读取)
|
|
152
|
+
if (!LOCAL_IMPORT_PATH_RE.test(importPath)) {
|
|
153
|
+
throw new Error(`拒绝非法本地路径 "${importPath}":仅允许 @/ 开头的小写路径段(供应链防护)`)
|
|
154
|
+
}
|
|
155
|
+
const resolvedPath = resolve(webSrc, `${importPath.slice(2)}.tsx`)
|
|
156
|
+
// 路径穿越防护:@/ 别名只允许解析到 apps/web/src 内部的 .tsx
|
|
157
|
+
if (!resolvedPath.startsWith(webSrc + '/')) {
|
|
158
|
+
throw new Error('非法路径:importPath 越界')
|
|
159
|
+
}
|
|
110
160
|
importSource = importPath
|
|
111
|
-
|
|
112
|
-
const tryPath = join(webSrc, `${relPath}.tsx`)
|
|
113
|
-
if (existsSync(tryPath)) componentFilePath = tryPath
|
|
161
|
+
if (existsSync(resolvedPath)) componentFilePath = resolvedPath
|
|
114
162
|
} else {
|
|
163
|
+
// registry 路径(如 @magicui/safari 或 button):拒绝 URL / 绝对路径 / ..
|
|
164
|
+
// shadcn CLI 无 --registry 旗标,registry 来源由项目 components.json 的
|
|
165
|
+
// 受信 registries 配置决定;此处以路径格式校验挡住任意 URL / 路径注入。
|
|
166
|
+
if (!SHADCN_REGISTRY_PATH_RE.test(importPath)) {
|
|
167
|
+
throw new Error(`拒绝非 registry 路径 "${importPath}":仅允许组件名或 @scope/name(供应链防护)`)
|
|
168
|
+
}
|
|
115
169
|
const compName = importPath.split('/').pop() ?? importPath
|
|
116
170
|
try {
|
|
117
171
|
execFileSync('pnpm', ['dlx', 'shadcn@latest', 'add', importPath, '--yes'], {
|
|
@@ -158,6 +212,7 @@ import React from "react";
|
|
|
158
212
|
${importStmt}
|
|
159
213
|
|
|
160
214
|
export function Imported${pascalName}({ props, style }: { props?: Record<string, any>; style?: Record<string, any> }) {
|
|
215
|
+
const prisma = await getPrisma()
|
|
161
216
|
return <Comp {...props} style={style} />;
|
|
162
217
|
}
|
|
163
218
|
|
|
@@ -17,7 +17,8 @@
|
|
|
17
17
|
|
|
18
18
|
import { createServerFn } from '@tanstack/react-start'
|
|
19
19
|
import { z } from 'zod'
|
|
20
|
-
import {
|
|
20
|
+
import { getPrisma } from '@allbluecn/database/get-prisma'
|
|
21
|
+
import { requireAdmin } from '@/server/auth-helpers'
|
|
21
22
|
|
|
22
23
|
const listInput = z.object({
|
|
23
24
|
page: z.number().default(1),
|
|
@@ -29,6 +30,8 @@ const listInput = z.object({
|
|
|
29
30
|
export const listComponentsFn = createServerFn({ method: 'GET' })
|
|
30
31
|
.validator(listInput)
|
|
31
32
|
.handler(async ({ data }) => {
|
|
33
|
+
const prisma = await getPrisma()
|
|
34
|
+
await requireAdmin()
|
|
32
35
|
const { page, pageSize, search, category } = data
|
|
33
36
|
const where: any = { isDeleted: false }
|
|
34
37
|
if (search) {
|
|
@@ -17,7 +17,9 @@
|
|
|
17
17
|
|
|
18
18
|
import { createServerFn } from '@tanstack/react-start'
|
|
19
19
|
import { z } from 'zod'
|
|
20
|
-
import {
|
|
20
|
+
import { getPrisma } from '@allbluecn/database/get-prisma'
|
|
21
|
+
import type { Prisma } from '@allbluecn/database'
|
|
22
|
+
import { requireAdmin } from '@/server/auth-helpers'
|
|
21
23
|
|
|
22
24
|
const createInput = z.object({
|
|
23
25
|
name: z.string().min(1),
|
|
@@ -29,6 +31,8 @@ const createInput = z.object({
|
|
|
29
31
|
export const createComponentFn = createServerFn({ method: 'POST' })
|
|
30
32
|
.validator(createInput)
|
|
31
33
|
.handler(async ({ data }) => {
|
|
34
|
+
const prisma = await getPrisma()
|
|
35
|
+
await requireAdmin()
|
|
32
36
|
return prisma.component.create({
|
|
33
37
|
data: {
|
|
34
38
|
name: data.name,
|
|
@@ -61,6 +65,8 @@ const updateInput = z.object({
|
|
|
61
65
|
export const updateComponentFn = createServerFn({ method: 'POST' })
|
|
62
66
|
.validator(updateInput)
|
|
63
67
|
.handler(async ({ data }) => {
|
|
68
|
+
const prisma = await getPrisma()
|
|
69
|
+
await requireAdmin()
|
|
64
70
|
const { id, defaultProps, defaultStyle, ...rest } = data
|
|
65
71
|
return prisma.component.update({
|
|
66
72
|
where: { id },
|
|
@@ -77,6 +83,8 @@ const deleteInput = z.object({ id: z.string() })
|
|
|
77
83
|
export const deleteComponentFn = createServerFn({ method: 'POST' })
|
|
78
84
|
.validator(deleteInput)
|
|
79
85
|
.handler(async ({ data }) => {
|
|
86
|
+
const prisma = await getPrisma()
|
|
87
|
+
await requireAdmin()
|
|
80
88
|
return prisma.component.update({
|
|
81
89
|
where: { id: data.id },
|
|
82
90
|
data: { isDeleted: true },
|
|
@@ -18,8 +18,9 @@
|
|
|
18
18
|
// apps/admin/src/server/serverFns/dependency/list.ts
|
|
19
19
|
import { createServerFn } from "@tanstack/react-start"
|
|
20
20
|
import { z } from "zod"
|
|
21
|
-
import {
|
|
21
|
+
import { getPrisma } from "@allbluecn/database/get-prisma"
|
|
22
22
|
import type { ThirdPartyDependencyRow } from "@allbluecn/kernel"
|
|
23
|
+
import { requireAdmin } from "@/server/auth-helpers"
|
|
23
24
|
import {
|
|
24
25
|
aggregateUsages,
|
|
25
26
|
compareVersions,
|
|
@@ -27,12 +28,13 @@ import {
|
|
|
27
28
|
npmUrlOf,
|
|
28
29
|
pickCurrentVersion,
|
|
29
30
|
readMonorepoPackages,
|
|
30
|
-
} from "
|
|
31
|
+
} from "@/server/dependency/helpers"
|
|
31
32
|
|
|
32
33
|
const listInput = z.object({
|
|
33
34
|
/** 仅返回关注列表 */
|
|
34
35
|
watchedOnly: z.boolean().optional(),
|
|
35
|
-
|
|
36
|
+
// 与 DB enum DependencyCategory(kernel DependencyCategory 联合类型)对齐,同 update.ts
|
|
37
|
+
category: z.enum(["framework", "ui", "editor", "ai", "state", "auth", "tool", "other"]).optional(),
|
|
36
38
|
search: z.string().optional(),
|
|
37
39
|
})
|
|
38
40
|
|
|
@@ -46,6 +48,8 @@ const listInput = z.object({
|
|
|
46
48
|
export const listDependenciesFn = createServerFn({ method: "GET" })
|
|
47
49
|
.validator(listInput)
|
|
48
50
|
.handler(async ({ data }) => {
|
|
51
|
+
const prisma = await getPrisma()
|
|
52
|
+
await requireAdmin()
|
|
49
53
|
await ensureSeedData()
|
|
50
54
|
|
|
51
55
|
// 一次读取所有 package.json,供聚合复用
|
|
@@ -60,10 +64,29 @@ export const listDependenciesFn = createServerFn({ method: "GET" })
|
|
|
60
64
|
}
|
|
61
65
|
|
|
62
66
|
// 读 DB 所有记录
|
|
63
|
-
const where: { isWatched?: boolean; category?:
|
|
67
|
+
const where: { isWatched?: boolean; category?: "framework" | "ui" | "editor" | "ai" | "state" | "auth" | "tool" | "other" } = {}
|
|
64
68
|
if (data.watchedOnly) where.isWatched = true
|
|
65
69
|
if (data.category) where.category = data.category
|
|
66
|
-
|
|
70
|
+
// DB-05:白名单 select(createdAt 不进聚合结果,整行排除)
|
|
71
|
+
const dbRows = await prisma.thirdPartyDependency.findMany({
|
|
72
|
+
where,
|
|
73
|
+
select: {
|
|
74
|
+
id: true,
|
|
75
|
+
name: true,
|
|
76
|
+
displayName: true,
|
|
77
|
+
description: true,
|
|
78
|
+
homepage: true,
|
|
79
|
+
category: true,
|
|
80
|
+
isWatched: true,
|
|
81
|
+
priority: true,
|
|
82
|
+
latestVersion: true,
|
|
83
|
+
latestChangelog: true,
|
|
84
|
+
lastSyncAt: true,
|
|
85
|
+
notes: true,
|
|
86
|
+
sortOrder: true,
|
|
87
|
+
updatedAt: true,
|
|
88
|
+
},
|
|
89
|
+
})
|
|
67
90
|
const dbMap = new Map(dbRows.map((r) => [r.name, r]))
|
|
68
91
|
|
|
69
92
|
// 构造结果集:DB 中所有记录 + package.json 中存在但 DB 未跟踪的包
|
|
@@ -18,9 +18,10 @@
|
|
|
18
18
|
// apps/admin/src/server/serverFns/dependency/sync.ts
|
|
19
19
|
import { createServerFn } from "@tanstack/react-start"
|
|
20
20
|
import { z } from "zod"
|
|
21
|
-
import {
|
|
21
|
+
import { getPrisma } from "@allbluecn/database/get-prisma"
|
|
22
22
|
import type { SyncDependencyResult } from "@allbluecn/kernel"
|
|
23
|
-
import {
|
|
23
|
+
import { requireAdmin } from "@/server/auth-helpers"
|
|
24
|
+
import { queryNpmRegistry } from "@/server/dependency/helpers"
|
|
24
25
|
|
|
25
26
|
const syncInput = z.object({
|
|
26
27
|
/** npm 包名;不存在于 DB 时会 upsert 创建 */
|
|
@@ -34,6 +35,8 @@ const syncInput = z.object({
|
|
|
34
35
|
export const syncDependencyFn = createServerFn({ method: "POST" })
|
|
35
36
|
.validator(syncInput)
|
|
36
37
|
.handler(async ({ data }): Promise<SyncDependencyResult> => {
|
|
38
|
+
const prisma = await getPrisma()
|
|
39
|
+
await requireAdmin()
|
|
37
40
|
try {
|
|
38
41
|
const { latestVersion, homepage, changelog } = await queryNpmRegistry(data.name)
|
|
39
42
|
await prisma.thirdPartyDependency.upsert({
|
|
@@ -85,6 +88,8 @@ const syncAllInput = z.object({
|
|
|
85
88
|
export const syncAllDependenciesFn = createServerFn({ method: "POST" })
|
|
86
89
|
.validator(syncAllInput)
|
|
87
90
|
.handler(async ({ data }): Promise<SyncDependencyResult[]> => {
|
|
91
|
+
const prisma = await getPrisma()
|
|
92
|
+
await requireAdmin()
|
|
88
93
|
const results: SyncDependencyResult[] = []
|
|
89
94
|
// 顺序执行(npm registry 建议不并发轰炸)
|
|
90
95
|
for (const name of data.names) {
|
|
@@ -18,8 +18,9 @@
|
|
|
18
18
|
// apps/admin/src/server/serverFns/dependency/update.ts
|
|
19
19
|
import { createServerFn } from "@tanstack/react-start"
|
|
20
20
|
import { z } from "zod"
|
|
21
|
-
import {
|
|
21
|
+
import { getPrisma } from "@allbluecn/database/get-prisma"
|
|
22
22
|
import type { UpdateDependencyInput } from "@allbluecn/kernel"
|
|
23
|
+
import { requireAdmin } from "@/server/auth-helpers"
|
|
23
24
|
|
|
24
25
|
const updateInput = z.object({
|
|
25
26
|
id: z.string().min(1),
|
|
@@ -37,6 +38,8 @@ const updateInput = z.object({
|
|
|
37
38
|
export const updateDependencyFn = createServerFn({ method: "POST" })
|
|
38
39
|
.validator(updateInput)
|
|
39
40
|
.handler(async ({ data }) => {
|
|
41
|
+
const prisma = await getPrisma()
|
|
42
|
+
await requireAdmin()
|
|
40
43
|
const { id, ...patch } = data
|
|
41
44
|
// 过滤 undefined 字段,避免覆盖为 null
|
|
42
45
|
const cleanPatch: Record<string, unknown> = {}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { createServerFn } from '@tanstack/react-start'
|
|
2
2
|
import { z } from 'zod'
|
|
3
|
-
import {
|
|
3
|
+
import { getPrisma } from '@allbluecn/database/get-prisma'
|
|
4
4
|
import { execFileSync } from 'node:child_process'
|
|
5
5
|
import path from 'node:path'
|
|
6
6
|
import { requireAdmin } from '@/server/auth-helpers'
|
|
@@ -86,6 +86,7 @@ export const autoGenerateLogsFn = createServerFn({ method: 'POST' })
|
|
|
86
86
|
daysBack: z.number().default(60),
|
|
87
87
|
}))
|
|
88
88
|
.handler(async ({ data }) => {
|
|
89
|
+
const prisma = await getPrisma()
|
|
89
90
|
await requireAdmin()
|
|
90
91
|
|
|
91
92
|
const repoRoot = path.resolve(process.cwd(), '../..')
|
|
@@ -97,8 +98,20 @@ export const autoGenerateLogsFn = createServerFn({ method: 'POST' })
|
|
|
97
98
|
)
|
|
98
99
|
|
|
99
100
|
const commitBlocks = stdout.split('\n\n').filter((block) => block.trim())
|
|
100
|
-
|
|
101
|
-
|
|
101
|
+
// DB-01:查重从逐 commit findFirst(N 次查询)改为一次 findMany in 预取 sha 集合 + 批内存重去重;
|
|
102
|
+
// 写入用 createManyAndReturn 以精确统计实际插入行数(等价原 created 计数)。
|
|
103
|
+
// DB-07 核实(任务 3.4):写入已是单步批量 createManyAndReturn(Prisma 单语句自带
|
|
104
|
+
// 原子性),无多步半批残留风险,按任务 3.3 先例不再包 $transaction。
|
|
105
|
+
// 查询次数 before: 2N → after: 2
|
|
106
|
+
interface PendingLog {
|
|
107
|
+
date: string
|
|
108
|
+
year: string
|
|
109
|
+
title: string
|
|
110
|
+
desc: string
|
|
111
|
+
tags: string[]
|
|
112
|
+
commitSha: string
|
|
113
|
+
}
|
|
114
|
+
const pending = new Map<string, PendingLog>()
|
|
102
115
|
for (const block of commitBlocks) {
|
|
103
116
|
const lines = block.trim().split('\n')
|
|
104
117
|
if (lines.length < 2) continue
|
|
@@ -117,24 +130,34 @@ export const autoGenerateLogsFn = createServerFn({ method: 'POST' })
|
|
|
117
130
|
|
|
118
131
|
const { date, year } = formatCommitDate(dateStr)
|
|
119
132
|
|
|
120
|
-
|
|
121
|
-
|
|
133
|
+
// 批内重复 sha:首个入 Map,后续跳过(与原「已存在则 continue」语义一致)
|
|
134
|
+
if (pending.has(sha)) continue
|
|
135
|
+
pending.set(sha, {
|
|
136
|
+
date,
|
|
137
|
+
year,
|
|
138
|
+
title: parsed.title,
|
|
139
|
+
desc: generateMarkdownDetail(parsed.title, parsed.body, sha, dateStr),
|
|
140
|
+
tags: parsed.tags,
|
|
141
|
+
commitSha: sha,
|
|
122
142
|
})
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
let created = 0
|
|
146
|
+
if (pending.size > 0) {
|
|
147
|
+
const shas = [...pending.keys()]
|
|
148
|
+
const existingShas = new Set(
|
|
149
|
+
(
|
|
150
|
+
await prisma.developerLog.findMany({
|
|
151
|
+
where: { commitSha: { in: shas }, isDeleted: false },
|
|
152
|
+
select: { commitSha: true },
|
|
153
|
+
})
|
|
154
|
+
).map((row) => row.commitSha),
|
|
155
|
+
)
|
|
156
|
+
const toCreate = [...pending.values()].filter((row) => !existingShas.has(row.commitSha))
|
|
157
|
+
if (toCreate.length > 0) {
|
|
158
|
+
const inserted = await prisma.developerLog.createManyAndReturn({ data: toCreate })
|
|
159
|
+
created = inserted.length
|
|
160
|
+
}
|
|
138
161
|
}
|
|
139
162
|
|
|
140
163
|
return { success: true, count: created }
|
|
@@ -145,6 +168,7 @@ export const autoGenerateLogsFn = createServerFn({ method: 'POST' })
|
|
|
145
168
|
|
|
146
169
|
export const backfillLogDetailsFn = createServerFn({ method: 'POST' })
|
|
147
170
|
.handler(async () => {
|
|
171
|
+
const prisma = await getPrisma()
|
|
148
172
|
await requireAdmin()
|
|
149
173
|
|
|
150
174
|
const entries = await prisma.developerLog.findMany({
|
|
@@ -157,19 +181,26 @@ export const backfillLogDetailsFn = createServerFn({ method: 'POST' })
|
|
|
157
181
|
},
|
|
158
182
|
})
|
|
159
183
|
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
184
|
+
// DB-01:待回填行(无 markdown)先内存过滤,更新并行提交。每行 desc 不同,
|
|
185
|
+
// 无法合并为单条 updateMany;原逐条串行 await 改 Promise.all 消除串行放大。
|
|
186
|
+
// 查询次数 before: N(串行) → after: N(并行)
|
|
187
|
+
const stale = entries.filter(
|
|
188
|
+
(entry) => !(entry.desc.includes('## ') || entry.desc.includes('```') || entry.desc.includes('---')),
|
|
189
|
+
)
|
|
165
190
|
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
await
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
191
|
+
let updated = 0
|
|
192
|
+
if (stale.length > 0) {
|
|
193
|
+
const results = await Promise.all(
|
|
194
|
+
stale.map(async (entry) => {
|
|
195
|
+
const descMarkdown = generateMarkdownDetailFromLog(entry.title, entry.desc, entry.commitSha || '')
|
|
196
|
+
await prisma.developerLog.update({
|
|
197
|
+
where: { id: entry.id },
|
|
198
|
+
data: { desc: descMarkdown },
|
|
199
|
+
})
|
|
200
|
+
return true
|
|
201
|
+
}),
|
|
202
|
+
)
|
|
203
|
+
updated = results.length
|
|
173
204
|
}
|
|
174
205
|
|
|
175
206
|
return { success: true, count: updated }
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { createServerFn } from '@tanstack/react-start'
|
|
2
2
|
import { z } from 'zod'
|
|
3
|
-
import {
|
|
3
|
+
import { getPrisma } from '@allbluecn/database/get-prisma'
|
|
4
4
|
import type { DeveloperLogEntry } from '@allbluecn/kernel'
|
|
5
5
|
import { requireAdmin } from '@/server/auth-helpers'
|
|
6
6
|
|
|
@@ -24,6 +24,7 @@ export const listDeveloperLogsFn = createServerFn({ method: 'GET' })
|
|
|
24
24
|
tag: z.string().optional(),
|
|
25
25
|
}))
|
|
26
26
|
.handler(async ({ data }) => {
|
|
27
|
+
const prisma = await getPrisma()
|
|
27
28
|
await requireAdmin()
|
|
28
29
|
const where: Record<string, unknown> = { isDeleted: false }
|
|
29
30
|
if (data.search) {
|
|
@@ -67,6 +68,7 @@ export const createDeveloperLogFn = createServerFn({ method: 'POST' })
|
|
|
67
68
|
commitSha: z.string().optional(),
|
|
68
69
|
}))
|
|
69
70
|
.handler(async ({ data }) => {
|
|
71
|
+
const prisma = await getPrisma()
|
|
70
72
|
await requireAdmin()
|
|
71
73
|
const log = await prisma.developerLog.create({
|
|
72
74
|
data: {
|
|
@@ -98,6 +100,7 @@ export const updateDeveloperLogFn = createServerFn({ method: 'POST' })
|
|
|
98
100
|
commitSha: z.string().nullable().optional(),
|
|
99
101
|
}))
|
|
100
102
|
.handler(async ({ data }) => {
|
|
103
|
+
const prisma = await getPrisma()
|
|
101
104
|
await requireAdmin()
|
|
102
105
|
const updateData: Record<string, unknown> = {}
|
|
103
106
|
if (data.date !== undefined) updateData.date = data.date
|
|
@@ -123,6 +126,7 @@ export const updateDeveloperLogFn = createServerFn({ method: 'POST' })
|
|
|
123
126
|
export const deleteDeveloperLogFn = createServerFn({ method: 'POST' })
|
|
124
127
|
.validator(z.object({ id: z.string() }))
|
|
125
128
|
.handler(async ({ data }) => {
|
|
129
|
+
const prisma = await getPrisma()
|
|
126
130
|
await requireAdmin()
|
|
127
131
|
await prisma.developerLog.update({
|
|
128
132
|
where: { id: data.id },
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
|
|
18
18
|
import { createServerFn } from '@tanstack/react-start'
|
|
19
19
|
import { z } from 'zod'
|
|
20
|
-
import {
|
|
20
|
+
import { getPrisma } from '@allbluecn/database/get-prisma'
|
|
21
21
|
import { requireAdmin } from '@/server/auth-helpers'
|
|
22
22
|
|
|
23
23
|
function randomInvitationCode(): string {
|
|
@@ -35,6 +35,7 @@ const pageSchema = z.object({
|
|
|
35
35
|
export const listInvitationCodesFn = createServerFn({ method: 'GET' })
|
|
36
36
|
.validator(pageSchema)
|
|
37
37
|
.handler(async ({ data }) => {
|
|
38
|
+
const prisma = await getPrisma()
|
|
38
39
|
await requireAdmin()
|
|
39
40
|
const skip = (data.page - 1) * data.pageSize
|
|
40
41
|
const [codes, total] = await Promise.all([
|
|
@@ -94,6 +95,7 @@ const createCodeSchema = z.object({
|
|
|
94
95
|
export const createInvitationCodeFn = createServerFn({ method: 'POST' })
|
|
95
96
|
.validator(createCodeSchema)
|
|
96
97
|
.handler(async ({ data }) => {
|
|
98
|
+
const prisma = await getPrisma()
|
|
97
99
|
const session = await requireAdmin()
|
|
98
100
|
const code = data.code ? data.code.trim().toUpperCase() : randomInvitationCode()
|
|
99
101
|
const row = await prisma.invitationCode.create({
|
|
@@ -112,6 +114,7 @@ const idSchema = z.object({ id: z.string().min(1) })
|
|
|
112
114
|
export const toggleInvitationCodeFn = createServerFn({ method: 'POST' })
|
|
113
115
|
.validator(idSchema)
|
|
114
116
|
.handler(async ({ data }) => {
|
|
117
|
+
const prisma = await getPrisma()
|
|
115
118
|
await requireAdmin()
|
|
116
119
|
const row = await prisma.invitationCode.findUnique({ where: { id: data.id } })
|
|
117
120
|
if (!row) throw new Error('邀请码不存在')
|
|
@@ -126,6 +129,7 @@ export const toggleInvitationCodeFn = createServerFn({ method: 'POST' })
|
|
|
126
129
|
export const deleteInvitationCodeFn = createServerFn({ method: 'POST' })
|
|
127
130
|
.validator(idSchema)
|
|
128
131
|
.handler(async ({ data }) => {
|
|
132
|
+
const prisma = await getPrisma()
|
|
129
133
|
await requireAdmin()
|
|
130
134
|
const row = await prisma.invitationCode.findUnique({
|
|
131
135
|
where: { id: data.id },
|
|
@@ -140,6 +144,7 @@ export const deleteInvitationCodeFn = createServerFn({ method: 'POST' })
|
|
|
140
144
|
export const getRegistrationModeFn = createServerFn({ method: 'GET' })
|
|
141
145
|
.validator(z.object({}))
|
|
142
146
|
.handler(async () => {
|
|
147
|
+
const prisma = await getPrisma()
|
|
143
148
|
await requireAdmin()
|
|
144
149
|
const setting = await prisma.systemSetting.findUnique({
|
|
145
150
|
where: { key: 'auth.registrationMode' },
|
|
@@ -150,6 +155,7 @@ export const getRegistrationModeFn = createServerFn({ method: 'GET' })
|
|
|
150
155
|
export const setRegistrationModeFn = createServerFn({ method: 'POST' })
|
|
151
156
|
.validator(z.object({ mode: z.enum(['invite', 'open']) }))
|
|
152
157
|
.handler(async ({ data }) => {
|
|
158
|
+
const prisma = await getPrisma()
|
|
153
159
|
await requireAdmin()
|
|
154
160
|
await prisma.systemSetting.upsert({
|
|
155
161
|
where: { key: 'auth.registrationMode' },
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
|
|
18
18
|
import { createServerFn } from '@tanstack/react-start'
|
|
19
19
|
import { z } from 'zod'
|
|
20
|
-
import {
|
|
20
|
+
import { getPrisma } from '@allbluecn/database/get-prisma'
|
|
21
21
|
import { requireAdmin } from '@/server/auth-helpers'
|
|
22
22
|
import { bookmarkCanonicalId, parseGithubUrl } from '@/server/knowledge-public/canonical-id'
|
|
23
23
|
|
|
@@ -32,6 +32,7 @@ const createSchema = z.object({
|
|
|
32
32
|
export const adminCreateBookmarkFn = createServerFn({ method: 'POST' })
|
|
33
33
|
.validator(createSchema)
|
|
34
34
|
.handler(async ({ data }) => {
|
|
35
|
+
const prisma = await getPrisma()
|
|
35
36
|
const session = await requireAdmin()
|
|
36
37
|
const gh = parseGithubUrl(data.url)
|
|
37
38
|
return prisma.websiteBookmark.create({
|
|
@@ -17,12 +17,13 @@
|
|
|
17
17
|
|
|
18
18
|
import { createServerFn } from '@tanstack/react-start'
|
|
19
19
|
import { z } from 'zod'
|
|
20
|
-
import {
|
|
20
|
+
import { getPrisma } from '@allbluecn/database/get-prisma'
|
|
21
21
|
import { requireAdmin } from '@/server/auth-helpers'
|
|
22
22
|
|
|
23
23
|
export const adminDeleteBookmarkFn = createServerFn({ method: 'POST' })
|
|
24
24
|
.validator(z.object({ id: z.string() }))
|
|
25
25
|
.handler(async ({ data }) => {
|
|
26
|
+
const prisma = await getPrisma()
|
|
26
27
|
await requireAdmin()
|
|
27
28
|
const existing = await prisma.websiteBookmark.findUnique({ where: { id: data.id } })
|
|
28
29
|
if (!existing) throw new Error('未找到')
|
|
@@ -17,12 +17,13 @@
|
|
|
17
17
|
|
|
18
18
|
import { createServerFn } from '@tanstack/react-start'
|
|
19
19
|
import { z } from 'zod'
|
|
20
|
-
import {
|
|
20
|
+
import { getPrisma } from '@allbluecn/database/get-prisma'
|
|
21
21
|
import { requireAdmin } from '@/server/auth-helpers'
|
|
22
22
|
|
|
23
23
|
export const adminGetBookmarkDetailFn = createServerFn({ method: 'GET' })
|
|
24
24
|
.validator(z.object({ id: z.string() }))
|
|
25
25
|
.handler(async ({ data }) => {
|
|
26
|
+
const prisma = await getPrisma()
|
|
26
27
|
await requireAdmin()
|
|
27
28
|
const item = await prisma.websiteBookmark.findFirst({
|
|
28
29
|
where: { id: data.id, visibility: 'public', isDeleted: false },
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
|
|
18
18
|
import { createServerFn } from '@tanstack/react-start'
|
|
19
19
|
import { z } from 'zod'
|
|
20
|
-
import {
|
|
20
|
+
import { getPrisma } from '@allbluecn/database/get-prisma'
|
|
21
21
|
import { requireAdmin } from '@/server/auth-helpers'
|
|
22
22
|
|
|
23
23
|
const listSchema = z.object({
|
|
@@ -31,6 +31,7 @@ const listSchema = z.object({
|
|
|
31
31
|
export const adminListBookmarksFn = createServerFn({ method: 'GET' })
|
|
32
32
|
.validator(listSchema)
|
|
33
33
|
.handler(async ({ data }) => {
|
|
34
|
+
const prisma = await getPrisma()
|
|
34
35
|
await requireAdmin()
|
|
35
36
|
const where: any = { visibility: 'public', isDeleted: false }
|
|
36
37
|
if (data.search) {
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
|
|
18
18
|
import { createServerFn } from '@tanstack/react-start'
|
|
19
19
|
import { z } from 'zod'
|
|
20
|
-
import {
|
|
20
|
+
import { getPrisma } from '@allbluecn/database/get-prisma'
|
|
21
21
|
import { requireAdmin } from '@/server/auth-helpers'
|
|
22
22
|
import { syncBookmarkGithub } from '@/server/knowledge-public/github-sync'
|
|
23
23
|
|
|
@@ -28,6 +28,7 @@ const syncSchema = z.object({
|
|
|
28
28
|
export const adminSyncAllGithubFn = createServerFn({ method: 'POST' })
|
|
29
29
|
.validator(syncSchema)
|
|
30
30
|
.handler(async ({ data }) => {
|
|
31
|
+
const prisma = await getPrisma()
|
|
31
32
|
await requireAdmin()
|
|
32
33
|
const where: any = { isGithub: true, isDeleted: false }
|
|
33
34
|
if (!data.force) {
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
|
|
18
18
|
import { createServerFn } from '@tanstack/react-start'
|
|
19
19
|
import { z } from 'zod'
|
|
20
|
-
import {
|
|
20
|
+
import { getPrisma } from '@allbluecn/database/get-prisma'
|
|
21
21
|
import { requireAdmin } from '@/server/auth-helpers'
|
|
22
22
|
import { bookmarkCanonicalId, parseGithubUrl } from '@/server/knowledge-public/canonical-id'
|
|
23
23
|
|
|
@@ -33,6 +33,7 @@ const updateSchema = z.object({
|
|
|
33
33
|
export const adminUpdateBookmarkFn = createServerFn({ method: 'POST' })
|
|
34
34
|
.validator(updateSchema)
|
|
35
35
|
.handler(async ({ data }) => {
|
|
36
|
+
const prisma = await getPrisma()
|
|
36
37
|
await requireAdmin()
|
|
37
38
|
const { id, ...rest } = data
|
|
38
39
|
const existing = await prisma.websiteBookmark.findUnique({ where: { id } })
|