@allbluecn/admin-app 0.1.0
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 +86 -0
- package/src/client.tsx +31 -0
- package/src/components/admin/admin-user-menu.tsx +90 -0
- package/src/components/admin/developer-log.tsx +356 -0
- package/src/components/ai-provider/model-list-editor.tsx +147 -0
- package/src/components/ai-provider/provider-form.tsx +434 -0
- package/src/components/ai-provider/provider-icon.tsx +43 -0
- package/src/components/ai-provider/provider-logo.tsx +87 -0
- package/src/components/ai-provider/provider-table.tsx +258 -0
- package/src/components/auth/admin-login-form.tsx +214 -0
- package/src/components/auth/auth-brand-panel.tsx +60 -0
- package/src/components/auth/auth-header.tsx +38 -0
- package/src/components/auth/auth-layout.tsx +66 -0
- package/src/components/auth/auth-providers.tsx +34 -0
- package/src/components/auth/icon-input.tsx +46 -0
- package/src/components/auth/oauth-section.tsx +51 -0
- package/src/components/auth/password-input.tsx +67 -0
- package/src/components/common/page-header.tsx +42 -0
- package/src/components/common/placeholder-page.tsx +42 -0
- package/src/components/component/component-form-dialog.tsx +348 -0
- package/src/components/component/component-import-dialog.tsx +235 -0
- package/src/components/component/component-table.tsx +153 -0
- package/src/components/data-table/data-table-pagination.tsx +114 -0
- package/src/components/data-table/data-table.tsx +218 -0
- package/src/components/data-table/table-detail-layout.tsx +99 -0
- package/src/components/dependency/changelog-dialog.tsx +108 -0
- package/src/components/dependency/columns.tsx +232 -0
- package/src/components/dependency/dependency-table.tsx +368 -0
- package/src/components/dependency/edit-dialog.tsx +200 -0
- package/src/components/knowledge-public/bookmark-form-dialog.tsx +215 -0
- package/src/components/knowledge-public/compliance-form-dialog.tsx +308 -0
- package/src/components/knowledge-public/legal-form-dialog.tsx +315 -0
- package/src/components/layout/sidebar-02/app-sidebar.tsx +160 -0
- package/src/components/layout/sidebar-02/index.tsx +33 -0
- package/src/components/layout/sidebar-02/logo.tsx +40 -0
- package/src/components/layout/sidebar-02/nav-main.tsx +153 -0
- package/src/components/layout/sidebar-02/nav-notifications.tsx +78 -0
- package/src/components/layout/sidebar-02/team-switcher.tsx +106 -0
- package/src/components/magicui/animated-theme-toggler.tsx +272 -0
- package/src/components/magicui/rainbow-button.tsx +78 -0
- package/src/components/market/columns.tsx +151 -0
- package/src/components/market/market-app-form.tsx +237 -0
- package/src/components/market/market-app-list.tsx +156 -0
- package/src/components/monitoring/error-list.tsx +143 -0
- package/src/components/monitoring/health-cards.tsx +152 -0
- package/src/components/monitoring/metrics-stats.tsx +184 -0
- package/src/components/monitoring/request-log-table.tsx +179 -0
- package/src/components/providers/theme-toggle.tsx +59 -0
- package/src/components/settings/avatar/avatar-config.ts +46 -0
- package/src/components/ui/alert-dialog.tsx +216 -0
- package/src/components/ui/alert.tsx +76 -0
- package/src/components/ui/avatar.tsx +129 -0
- package/src/components/ui/badge.tsx +66 -0
- package/src/components/ui/button.tsx +84 -0
- package/src/components/ui/card.tsx +120 -0
- package/src/components/ui/checkbox.tsx +50 -0
- package/src/components/ui/collapsible.tsx +50 -0
- package/src/components/ui/dialog.tsx +185 -0
- package/src/components/ui/dropdown-menu.tsx +286 -0
- package/src/components/ui/field.tsx +255 -0
- package/src/components/ui/input-group.tsx +171 -0
- package/src/components/ui/input.tsx +36 -0
- package/src/components/ui/label.tsx +41 -0
- package/src/components/ui/pagination.tsx +146 -0
- package/src/components/ui/select.tsx +209 -0
- package/src/components/ui/separator.tsx +45 -0
- package/src/components/ui/sheet.tsx +164 -0
- package/src/components/ui/sidebar.tsx +719 -0
- package/src/components/ui/skeleton.tsx +30 -0
- package/src/components/ui/sonner.tsx +37 -0
- package/src/components/ui/switch.tsx +48 -0
- package/src/components/ui/table.tsx +133 -0
- package/src/components/ui/textarea.tsx +35 -0
- package/src/components/ui/tooltip.tsx +74 -0
- package/src/components/user/columns.tsx +153 -0
- package/src/components/user/settings-activity-log.tsx +358 -0
- package/src/components/user/settings-profile/constants.ts +42 -0
- package/src/components/user/settings-profile/types.ts +47 -0
- package/src/components/user/settings-profile.tsx +664 -0
- package/src/components/user/user-create-dialog.tsx +137 -0
- package/src/components/user/user-filters.tsx +117 -0
- package/src/hooks/use-mobile.ts +36 -0
- package/src/instrument.client.ts +34 -0
- package/src/lib/anyhub/capabilities.ts +59 -0
- package/src/lib/anyhub/types.ts +43 -0
- package/src/lib/auth-client.ts +83 -0
- package/src/lib/auth-utils.ts +24 -0
- package/src/lib/auth.config.ts +264 -0
- package/src/lib/avatar.ts +41 -0
- package/src/lib/i18n/client.ts +32 -0
- package/src/lib/i18n/config.ts +40 -0
- package/src/lib/i18n/detect.ts +43 -0
- package/src/lib/i18n/locales/en/admin-ai.json +128 -0
- package/src/lib/i18n/locales/en/admin-billing.json +56 -0
- package/src/lib/i18n/locales/en/admin-users.json +88 -0
- package/src/lib/i18n/locales/en/common.json +877 -0
- package/src/lib/i18n/locales/en/enterprise.json +208 -0
- package/src/lib/i18n/locales/zh/admin-ai.json +128 -0
- package/src/lib/i18n/locales/zh/admin-billing.json +56 -0
- package/src/lib/i18n/locales/zh/admin-users.json +88 -0
- package/src/lib/i18n/locales/zh/common.json +877 -0
- package/src/lib/i18n/locales/zh/enterprise.json +208 -0
- package/src/lib/i18n/server.ts +18 -0
- package/src/lib/i18n/types.ts +5 -0
- package/src/lib/monitoring-benchmarks.ts +85 -0
- package/src/lib/monitoring-descriptions.ts +78 -0
- package/src/lib/prisma.ts +18 -0
- package/src/lib/router-context.ts +38 -0
- package/src/lib/session.ts +38 -0
- package/src/lib/utils.ts +23 -0
- package/src/providers/index.tsx +45 -0
- package/src/routeTree.gen.ts +1199 -0
- package/src/router.tsx +51 -0
- package/src/routes/__root.tsx +170 -0
- package/src/routes/_nav/ai-providers/$id/edit/route.lazy.tsx +74 -0
- package/src/routes/_nav/ai-providers/$id/edit/route.tsx +35 -0
- package/src/routes/_nav/ai-providers/index/route.lazy.tsx +33 -0
- package/src/routes/_nav/ai-providers/new/route.lazy.tsx +31 -0
- package/src/routes/_nav/ai-providers/new/route.tsx +20 -0
- package/src/routes/_nav/ai-providers/route.lazy.tsx +26 -0
- package/src/routes/_nav/ai-providers/route.tsx +31 -0
- package/src/routes/_nav/apps/route.lazy.tsx +30 -0
- package/src/routes/_nav/apps/route.tsx +20 -0
- package/src/routes/_nav/audit/route.lazy.tsx +100 -0
- package/src/routes/_nav/audit/route.tsx +25 -0
- package/src/routes/_nav/billing/invoices/route.lazy.tsx +86 -0
- package/src/routes/_nav/billing/invoices/route.tsx +25 -0
- package/src/routes/_nav/billing/route.lazy.tsx +48 -0
- package/src/routes/_nav/billing/route.tsx +27 -0
- package/src/routes/_nav/billing/subscriptions/$subscriptionId/route.lazy.tsx +160 -0
- package/src/routes/_nav/billing/subscriptions/$subscriptionId/route.tsx +25 -0
- package/src/routes/_nav/billing/subscriptions/route.lazy.tsx +113 -0
- package/src/routes/_nav/billing/subscriptions/route.tsx +28 -0
- package/src/routes/_nav/compliance/route.lazy.tsx +184 -0
- package/src/routes/_nav/compliance/route.tsx +25 -0
- package/src/routes/_nav/components/route.lazy.tsx +184 -0
- package/src/routes/_nav/components/route.tsx +40 -0
- package/src/routes/_nav/dashboard/route.lazy.tsx +33 -0
- package/src/routes/_nav/dashboard/route.tsx +20 -0
- package/src/routes/_nav/dependencies/route.lazy.tsx +66 -0
- package/src/routes/_nav/dependencies/route.tsx +32 -0
- package/src/routes/_nav/developer-logs/route.lazy.tsx +6 -0
- package/src/routes/_nav/developer-logs/route.tsx +14 -0
- package/src/routes/_nav/docs/route.lazy.tsx +30 -0
- package/src/routes/_nav/docs/route.tsx +20 -0
- package/src/routes/_nav/knowledge-public/bookmarks/route.lazy.tsx +355 -0
- package/src/routes/_nav/knowledge-public/bookmarks/route.tsx +40 -0
- package/src/routes/_nav/knowledge-public/compliance/route.lazy.tsx +245 -0
- package/src/routes/_nav/knowledge-public/compliance/route.tsx +40 -0
- package/src/routes/_nav/knowledge-public/legal/route.lazy.tsx +242 -0
- package/src/routes/_nav/knowledge-public/legal/route.tsx +40 -0
- package/src/routes/_nav/licenses/$licenseId/route.lazy.tsx +235 -0
- package/src/routes/_nav/licenses/$licenseId/route.tsx +25 -0
- package/src/routes/_nav/licenses/new/route.lazy.tsx +237 -0
- package/src/routes/_nav/licenses/route.lazy.tsx +143 -0
- package/src/routes/_nav/licenses/route.tsx +25 -0
- package/src/routes/_nav/market/route.lazy.tsx +31 -0
- package/src/routes/_nav/market/route.tsx +20 -0
- package/src/routes/_nav/monitoring/ai-usage/route.lazy.tsx +305 -0
- package/src/routes/_nav/monitoring/ai-usage/route.tsx +3 -0
- package/src/routes/_nav/monitoring/errors/route.lazy.tsx +111 -0
- package/src/routes/_nav/monitoring/health/route.lazy.tsx +260 -0
- package/src/routes/_nav/monitoring/logs/route.lazy.tsx +165 -0
- package/src/routes/_nav/monitoring/metrics/route.lazy.tsx +240 -0
- package/src/routes/_nav/monitoring/overview/route.lazy.tsx +138 -0
- package/src/routes/_nav/monitoring/route.lazy.tsx +68 -0
- package/src/routes/_nav/monitoring/route.tsx +20 -0
- package/src/routes/_nav/monitoring/sentry/route.lazy.tsx +399 -0
- package/src/routes/_nav/monitoring/sentry/route.tsx +20 -0
- package/src/routes/_nav/permissions/route.lazy.tsx +504 -0
- package/src/routes/_nav/permissions/route.tsx +26 -0
- package/src/routes/_nav/route.lazy.tsx +31 -0
- package/src/routes/_nav/route.tsx +34 -0
- package/src/routes/_nav/settings/route.lazy.tsx +30 -0
- package/src/routes/_nav/settings/route.tsx +20 -0
- package/src/routes/_nav/sso/route.lazy.tsx +145 -0
- package/src/routes/_nav/sso/route.tsx +25 -0
- package/src/routes/_nav/users/$userId/route.lazy.tsx +432 -0
- package/src/routes/_nav/users/$userId/route.tsx +27 -0
- package/src/routes/_nav/users/-components/users-table.tsx +38 -0
- package/src/routes/_nav/users/route.lazy.tsx +74 -0
- package/src/routes/_nav/users/route.tsx +43 -0
- package/src/routes/api/compliance-download/$jobId.ts +35 -0
- package/src/routes/api/observability/errors.ts +41 -0
- package/src/routes/api/observability/health.ts +30 -0
- package/src/routes/api.auth.$.ts +31 -0
- package/src/routes/api.sentry-test.ts +33 -0
- package/src/routes/index/route.lazy.tsx +22 -0
- package/src/routes/index/route.tsx +24 -0
- package/src/routes/login/route.lazy.tsx +46 -0
- package/src/routes/login/route.tsx +28 -0
- package/src/routes/sentry-tunnel.ts +61 -0
- package/src/routes/setup/route.lazy.tsx +94 -0
- package/src/routes/setup/route.tsx +28 -0
- package/src/server/__tests__/admin-ai-alerts.test.ts +113 -0
- package/src/server/__tests__/licenses-issue.test.ts +98 -0
- package/src/server/admin-ai-alerts.ts +177 -0
- package/src/server/admin-ai-usage.ts +134 -0
- package/src/server/auth-helpers.ts +51 -0
- package/src/server/entitlements.ts +24 -0
- package/src/server/knowledge-public/canonical-id.ts +55 -0
- package/src/server/knowledge-public/github-sync.ts +86 -0
- package/src/server/license-issue.ts +78 -0
- package/src/server/middlewares/request.ts +26 -0
- package/src/server/observability/debug-middleware.ts +39 -0
- package/src/server/observability/error-reporter.ts +99 -0
- package/src/server/observability/health-check.ts +140 -0
- package/src/server/observability/index.ts +33 -0
- package/src/server/observability/logger.ts +49 -0
- package/src/server/observability/metrics.ts +105 -0
- package/src/server/observability/request-middleware.ts +125 -0
- package/src/server/observability/timing.ts +42 -0
- package/src/server/pro-services.ts +32 -0
- package/src/server/serverFns/admin-billing.ts +94 -0
- package/src/server/serverFns/ai-provider/create.ts +92 -0
- package/src/server/serverFns/ai-provider/delete.ts +42 -0
- package/src/server/serverFns/ai-provider/get.ts +35 -0
- package/src/server/serverFns/ai-provider/list.ts +92 -0
- package/src/server/serverFns/ai-provider/update.ts +73 -0
- package/src/server/serverFns/anyhub/market.ts +64 -0
- package/src/server/serverFns/audit.ts +57 -0
- package/src/server/serverFns/auth/session.ts +26 -0
- package/src/server/serverFns/auth/setup.ts +73 -0
- package/src/server/serverFns/compliance.ts +85 -0
- package/src/server/serverFns/components/import.ts +197 -0
- package/src/server/serverFns/components/list.ts +51 -0
- package/src/server/serverFns/components/mutations.ts +84 -0
- package/src/server/serverFns/dependency/helpers.ts +245 -0
- package/src/server/serverFns/dependency/list.ts +127 -0
- package/src/server/serverFns/dependency/sync.ts +123 -0
- package/src/server/serverFns/dependency/update.ts +51 -0
- package/src/server/serverFns/developer-log-auto-generate.ts +176 -0
- package/src/server/serverFns/developer-logs.ts +132 -0
- package/src/server/serverFns/knowledge-public/bookmarks/create.ts +53 -0
- package/src/server/serverFns/knowledge-public/bookmarks/delete.ts +31 -0
- package/src/server/serverFns/knowledge-public/bookmarks/detail.ts +32 -0
- package/src/server/serverFns/knowledge-public/bookmarks/list.ts +66 -0
- package/src/server/serverFns/knowledge-public/bookmarks/sync-all-github.ts +56 -0
- package/src/server/serverFns/knowledge-public/bookmarks/update.ts +50 -0
- package/src/server/serverFns/knowledge-public/compliance/create.ts +58 -0
- package/src/server/serverFns/knowledge-public/compliance/delete.ts +31 -0
- package/src/server/serverFns/knowledge-public/compliance/detail.ts +32 -0
- package/src/server/serverFns/knowledge-public/compliance/list.ts +59 -0
- package/src/server/serverFns/knowledge-public/compliance/update.ts +53 -0
- package/src/server/serverFns/knowledge-public/legal/create.ts +62 -0
- package/src/server/serverFns/knowledge-public/legal/delete.ts +31 -0
- package/src/server/serverFns/knowledge-public/legal/detail.ts +32 -0
- package/src/server/serverFns/knowledge-public/legal/list.ts +60 -0
- package/src/server/serverFns/knowledge-public/legal/update.ts +55 -0
- package/src/server/serverFns/licenses.ts +92 -0
- package/src/server/serverFns/locale.ts +10 -0
- package/src/server/serverFns/monitoring/check-ai-alerts.ts +13 -0
- package/src/server/serverFns/monitoring/get-ai-usage-overview.ts +25 -0
- package/src/server/serverFns/monitoring/get-errors.ts +43 -0
- package/src/server/serverFns/monitoring/get-health-settings.ts +35 -0
- package/src/server/serverFns/monitoring/get-health.ts +55 -0
- package/src/server/serverFns/monitoring/get-metrics.ts +46 -0
- package/src/server/serverFns/monitoring/get-request-logs.ts +66 -0
- package/src/server/serverFns/monitoring/report-error.ts +55 -0
- package/src/server/serverFns/monitoring/reset.ts +70 -0
- package/src/server/serverFns/permissions.ts +214 -0
- package/src/server/serverFns/sentry/config.ts +88 -0
- package/src/server/serverFns/sso-config.ts +83 -0
- package/src/server/serverFns/user/create.ts +40 -0
- package/src/server/serverFns/user/delete.ts +29 -0
- package/src/server/serverFns/user/detail.ts +45 -0
- package/src/server/serverFns/user/list.ts +56 -0
- package/src/server/serverFns/user/reset-password.ts +28 -0
- package/src/server/serverFns/user/update-role.ts +26 -0
- package/src/server/serverFns/user/update-status.ts +26 -0
- package/src/server.ts +27 -0
- package/src/start.ts +85 -0
- package/src/styles/_keyframe-animations.scss +91 -0
- package/src/styles/_variables.scss +296 -0
- package/src/styles/globals.css +200 -0
- package/src/types/start-authjs.d.ts +27 -0
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
import { createServerFn } from '@tanstack/react-start'
|
|
2
|
+
import { z } from 'zod'
|
|
3
|
+
import { prisma } from '@allbluecn/database'
|
|
4
|
+
import { execFileSync } from 'node:child_process'
|
|
5
|
+
import path from 'node:path'
|
|
6
|
+
import { requireAdmin } from '@/server/auth-helpers'
|
|
7
|
+
|
|
8
|
+
const COMMIT_TAG_MAP: Record<string, string[]> = {
|
|
9
|
+
feat: ['feat'],
|
|
10
|
+
fix: ['fix'],
|
|
11
|
+
hotfix: ['fix'],
|
|
12
|
+
refactor: ['refactor'],
|
|
13
|
+
design: ['design'],
|
|
14
|
+
perf: ['perf'],
|
|
15
|
+
docs: ['docs'],
|
|
16
|
+
security: ['security'],
|
|
17
|
+
breaking: ['breaking'],
|
|
18
|
+
improvement: ['improvement'],
|
|
19
|
+
chore: ['chore'],
|
|
20
|
+
ci: ['chore'],
|
|
21
|
+
build: ['chore'],
|
|
22
|
+
test: ['chore'],
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function parseCommitMessage(msg: string) {
|
|
26
|
+
const lines = msg.trim().split('\n')
|
|
27
|
+
const header = lines[0] || ''
|
|
28
|
+
const body = lines.slice(1).join('\n').trim()
|
|
29
|
+
|
|
30
|
+
const typeMatch = header.match(/^(feat|fix|hotfix|refactor|design|perf|docs|security|breaking|improvement|chore|ci|build|test)(\(.+\))?(!)?:.+$/)
|
|
31
|
+
if (!typeMatch) return null
|
|
32
|
+
|
|
33
|
+
const type = typeMatch[1]
|
|
34
|
+
const title = header
|
|
35
|
+
const tags = COMMIT_TAG_MAP[type] || [type]
|
|
36
|
+
|
|
37
|
+
return { type, title, body, tags }
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function formatCommitDate(dateStr: string): { date: string; year: string } {
|
|
41
|
+
const d = new Date(dateStr)
|
|
42
|
+
const months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']
|
|
43
|
+
const date = `${months[d.getMonth()]} ${d.getDate()}, ${d.getFullYear()}`
|
|
44
|
+
const year = d.getFullYear().toString()
|
|
45
|
+
return { date, year }
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function generateMarkdownDetail(title: string, body: string, sha: string, dateStr: string): string {
|
|
49
|
+
const lines: string[] = []
|
|
50
|
+
lines.push(`## ${title}`)
|
|
51
|
+
lines.push('')
|
|
52
|
+
|
|
53
|
+
if (body) {
|
|
54
|
+
lines.push(body)
|
|
55
|
+
lines.push('')
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const d = new Date(dateStr)
|
|
59
|
+
const months = ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December']
|
|
60
|
+
const date = `${months[d.getMonth()]} ${d.getDate()}, ${d.getFullYear()}`
|
|
61
|
+
|
|
62
|
+
lines.push('---')
|
|
63
|
+
lines.push('')
|
|
64
|
+
lines.push(`> Commit: \`${sha.slice(0, 12)}\` · ${date}`)
|
|
65
|
+
lines.push('')
|
|
66
|
+
|
|
67
|
+
return lines.join('\n')
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function generateMarkdownDetailFromLog(title: string, existingDesc: string, sha: string): string {
|
|
71
|
+
const lines: string[] = []
|
|
72
|
+
lines.push(`## ${title}`)
|
|
73
|
+
lines.push('')
|
|
74
|
+
lines.push(existingDesc)
|
|
75
|
+
lines.push('')
|
|
76
|
+
if (sha) {
|
|
77
|
+
lines.push('---')
|
|
78
|
+
lines.push('')
|
|
79
|
+
lines.push(`> Commit: \`${sha.slice(0, 12)}\``)
|
|
80
|
+
}
|
|
81
|
+
return lines.join('\n')
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export const autoGenerateLogsFn = createServerFn({ method: 'POST' })
|
|
85
|
+
.validator(z.object({
|
|
86
|
+
daysBack: z.number().default(60),
|
|
87
|
+
}))
|
|
88
|
+
.handler(async ({ data }) => {
|
|
89
|
+
await requireAdmin()
|
|
90
|
+
|
|
91
|
+
const repoRoot = path.resolve(process.cwd(), '../..')
|
|
92
|
+
try {
|
|
93
|
+
const stdout = execFileSync(
|
|
94
|
+
'git',
|
|
95
|
+
['log', `--since=${data.daysBack} days ago`, '--format=%H|%ai|%s%n%B', '--no-merges'],
|
|
96
|
+
{ cwd: repoRoot, encoding: 'utf8', timeout: 30000 }
|
|
97
|
+
)
|
|
98
|
+
|
|
99
|
+
const commitBlocks = stdout.split('\n\n').filter((block) => block.trim())
|
|
100
|
+
let created = 0
|
|
101
|
+
|
|
102
|
+
for (const block of commitBlocks) {
|
|
103
|
+
const lines = block.trim().split('\n')
|
|
104
|
+
if (lines.length < 2) continue
|
|
105
|
+
|
|
106
|
+
const metaLine = lines[0]
|
|
107
|
+
const metaParts = metaLine.split('|')
|
|
108
|
+
if (metaParts.length < 3) continue
|
|
109
|
+
|
|
110
|
+
const sha = metaParts[0]
|
|
111
|
+
const dateStr = metaParts[1]
|
|
112
|
+
const subject = metaParts.slice(2).join('|')
|
|
113
|
+
const fullMessage = subject + '\n' + lines.slice(1).join('\n')
|
|
114
|
+
|
|
115
|
+
const parsed = parseCommitMessage(fullMessage)
|
|
116
|
+
if (!parsed) continue
|
|
117
|
+
|
|
118
|
+
const { date, year } = formatCommitDate(dateStr)
|
|
119
|
+
|
|
120
|
+
const existing = await prisma.developerLog.findFirst({
|
|
121
|
+
where: { commitSha: sha, isDeleted: false },
|
|
122
|
+
})
|
|
123
|
+
if (existing) continue
|
|
124
|
+
|
|
125
|
+
const descMarkdown = generateMarkdownDetail(parsed.title, parsed.body, sha, dateStr)
|
|
126
|
+
|
|
127
|
+
await prisma.developerLog.create({
|
|
128
|
+
data: {
|
|
129
|
+
date,
|
|
130
|
+
year,
|
|
131
|
+
title: parsed.title,
|
|
132
|
+
desc: descMarkdown,
|
|
133
|
+
tags: parsed.tags,
|
|
134
|
+
commitSha: sha,
|
|
135
|
+
},
|
|
136
|
+
})
|
|
137
|
+
created++
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
return { success: true, count: created }
|
|
141
|
+
} catch {
|
|
142
|
+
return { success: false, count: 0 }
|
|
143
|
+
}
|
|
144
|
+
})
|
|
145
|
+
|
|
146
|
+
export const backfillLogDetailsFn = createServerFn({ method: 'POST' })
|
|
147
|
+
.handler(async () => {
|
|
148
|
+
await requireAdmin()
|
|
149
|
+
|
|
150
|
+
const entries = await prisma.developerLog.findMany({
|
|
151
|
+
where: { isDeleted: false },
|
|
152
|
+
select: {
|
|
153
|
+
id: true,
|
|
154
|
+
title: true,
|
|
155
|
+
desc: true,
|
|
156
|
+
commitSha: true,
|
|
157
|
+
},
|
|
158
|
+
})
|
|
159
|
+
|
|
160
|
+
let updated = 0
|
|
161
|
+
|
|
162
|
+
for (const entry of entries) {
|
|
163
|
+
const hasMarkdown = entry.desc.includes('## ') || entry.desc.includes('```') || entry.desc.includes('---')
|
|
164
|
+
if (hasMarkdown) continue
|
|
165
|
+
|
|
166
|
+
const descMarkdown = generateMarkdownDetailFromLog(entry.title, entry.desc, entry.commitSha || '')
|
|
167
|
+
|
|
168
|
+
await prisma.developerLog.update({
|
|
169
|
+
where: { id: entry.id },
|
|
170
|
+
data: { desc: descMarkdown },
|
|
171
|
+
})
|
|
172
|
+
updated++
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
return { success: true, count: updated }
|
|
176
|
+
})
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
import { createServerFn } from '@tanstack/react-start'
|
|
2
|
+
import { z } from 'zod'
|
|
3
|
+
import { prisma } from '@allbluecn/database'
|
|
4
|
+
import type { DeveloperLogEntry } from '@allbluecn/kernel'
|
|
5
|
+
import { requireAdmin } from '@/server/auth-helpers'
|
|
6
|
+
|
|
7
|
+
const baseSelect = {
|
|
8
|
+
id: true,
|
|
9
|
+
date: true,
|
|
10
|
+
year: true,
|
|
11
|
+
title: true,
|
|
12
|
+
desc: true,
|
|
13
|
+
tags: true,
|
|
14
|
+
commitSha: true,
|
|
15
|
+
createdAt: true,
|
|
16
|
+
updatedAt: true,
|
|
17
|
+
} as const
|
|
18
|
+
|
|
19
|
+
export const listDeveloperLogsFn = createServerFn({ method: 'GET' })
|
|
20
|
+
.validator(z.object({
|
|
21
|
+
page: z.number().default(1),
|
|
22
|
+
pageSize: z.number().default(20),
|
|
23
|
+
search: z.string().optional(),
|
|
24
|
+
tag: z.string().optional(),
|
|
25
|
+
}))
|
|
26
|
+
.handler(async ({ data }) => {
|
|
27
|
+
await requireAdmin()
|
|
28
|
+
const where: Record<string, unknown> = { isDeleted: false }
|
|
29
|
+
if (data.search) {
|
|
30
|
+
where.OR = [
|
|
31
|
+
{ title: { contains: data.search, mode: 'insensitive' as const } },
|
|
32
|
+
{ desc: { contains: data.search, mode: 'insensitive' as const } },
|
|
33
|
+
]
|
|
34
|
+
}
|
|
35
|
+
if (data.tag) {
|
|
36
|
+
where.tags = { contains: data.tag }
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const [items, total] = await Promise.all([
|
|
40
|
+
prisma.developerLog.findMany({
|
|
41
|
+
where,
|
|
42
|
+
orderBy: [{ year: 'desc' }, { createdAt: 'desc' }],
|
|
43
|
+
skip: (data.page - 1) * data.pageSize,
|
|
44
|
+
take: data.pageSize,
|
|
45
|
+
select: baseSelect,
|
|
46
|
+
}),
|
|
47
|
+
prisma.developerLog.count({ where }),
|
|
48
|
+
])
|
|
49
|
+
return {
|
|
50
|
+
items: items.map((item) => ({
|
|
51
|
+
...item,
|
|
52
|
+
tags: (item.tags as string[]) ?? [],
|
|
53
|
+
createdAt: item.createdAt.toISOString(),
|
|
54
|
+
updatedAt: item.updatedAt.toISOString(),
|
|
55
|
+
})) as DeveloperLogEntry[],
|
|
56
|
+
total,
|
|
57
|
+
}
|
|
58
|
+
})
|
|
59
|
+
|
|
60
|
+
export const createDeveloperLogFn = createServerFn({ method: 'POST' })
|
|
61
|
+
.validator(z.object({
|
|
62
|
+
date: z.string(),
|
|
63
|
+
year: z.string(),
|
|
64
|
+
title: z.string(),
|
|
65
|
+
desc: z.string(),
|
|
66
|
+
tags: z.array(z.string()).default([]),
|
|
67
|
+
commitSha: z.string().optional(),
|
|
68
|
+
}))
|
|
69
|
+
.handler(async ({ data }) => {
|
|
70
|
+
await requireAdmin()
|
|
71
|
+
const log = await prisma.developerLog.create({
|
|
72
|
+
data: {
|
|
73
|
+
date: data.date,
|
|
74
|
+
year: data.year,
|
|
75
|
+
title: data.title,
|
|
76
|
+
desc: data.desc,
|
|
77
|
+
tags: data.tags,
|
|
78
|
+
commitSha: data.commitSha,
|
|
79
|
+
},
|
|
80
|
+
select: baseSelect,
|
|
81
|
+
})
|
|
82
|
+
return {
|
|
83
|
+
...log,
|
|
84
|
+
tags: (log.tags as string[]) ?? [],
|
|
85
|
+
createdAt: log.createdAt.toISOString(),
|
|
86
|
+
updatedAt: log.updatedAt.toISOString(),
|
|
87
|
+
} as DeveloperLogEntry
|
|
88
|
+
})
|
|
89
|
+
|
|
90
|
+
export const updateDeveloperLogFn = createServerFn({ method: 'POST' })
|
|
91
|
+
.validator(z.object({
|
|
92
|
+
id: z.string(),
|
|
93
|
+
date: z.string().optional(),
|
|
94
|
+
year: z.string().optional(),
|
|
95
|
+
title: z.string().optional(),
|
|
96
|
+
desc: z.string().optional(),
|
|
97
|
+
tags: z.array(z.string()).optional(),
|
|
98
|
+
commitSha: z.string().nullable().optional(),
|
|
99
|
+
}))
|
|
100
|
+
.handler(async ({ data }) => {
|
|
101
|
+
await requireAdmin()
|
|
102
|
+
const updateData: Record<string, unknown> = {}
|
|
103
|
+
if (data.date !== undefined) updateData.date = data.date
|
|
104
|
+
if (data.year !== undefined) updateData.year = data.year
|
|
105
|
+
if (data.title !== undefined) updateData.title = data.title
|
|
106
|
+
if (data.desc !== undefined) updateData.desc = data.desc
|
|
107
|
+
if (data.tags !== undefined) updateData.tags = data.tags
|
|
108
|
+
if (data.commitSha !== undefined) updateData.commitSha = data.commitSha
|
|
109
|
+
|
|
110
|
+
const log = await prisma.developerLog.update({
|
|
111
|
+
where: { id: data.id },
|
|
112
|
+
data: updateData,
|
|
113
|
+
select: baseSelect,
|
|
114
|
+
})
|
|
115
|
+
return {
|
|
116
|
+
...log,
|
|
117
|
+
tags: (log.tags as string[]) ?? [],
|
|
118
|
+
createdAt: log.createdAt.toISOString(),
|
|
119
|
+
updatedAt: log.updatedAt.toISOString(),
|
|
120
|
+
} as DeveloperLogEntry
|
|
121
|
+
})
|
|
122
|
+
|
|
123
|
+
export const deleteDeveloperLogFn = createServerFn({ method: 'POST' })
|
|
124
|
+
.validator(z.object({ id: z.string() }))
|
|
125
|
+
.handler(async ({ data }) => {
|
|
126
|
+
await requireAdmin()
|
|
127
|
+
await prisma.developerLog.update({
|
|
128
|
+
where: { id: data.id },
|
|
129
|
+
data: { isDeleted: true },
|
|
130
|
+
})
|
|
131
|
+
return { success: true }
|
|
132
|
+
})
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
// SPDX-License-Identifier: AGPL-3.0-or-later
|
|
2
|
+
// AllBlue - 理想之海
|
|
3
|
+
// Copyright (C) 2026 AllBlue Contributors
|
|
4
|
+
//
|
|
5
|
+
// This program is free software: you can redistribute it and/or modify
|
|
6
|
+
// it under the terms of the GNU Affero General Public License as published by
|
|
7
|
+
// the Free Software Foundation, either version 3 of the License, or
|
|
8
|
+
// (at your option) any later version.
|
|
9
|
+
//
|
|
10
|
+
// This program is distributed in the hope that it will be useful,
|
|
11
|
+
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
12
|
+
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
13
|
+
// GNU Affero General Public License for more details.
|
|
14
|
+
//
|
|
15
|
+
// You should have received a copy of the GNU Affero General Public License
|
|
16
|
+
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
17
|
+
|
|
18
|
+
import { createServerFn } from '@tanstack/react-start'
|
|
19
|
+
import { z } from 'zod'
|
|
20
|
+
import { prisma } from '@allbluecn/database'
|
|
21
|
+
import { requireAdmin } from '@/server/auth-helpers'
|
|
22
|
+
import { bookmarkCanonicalId, parseGithubUrl } from '@/server/knowledge-public/canonical-id'
|
|
23
|
+
|
|
24
|
+
const createSchema = z.object({
|
|
25
|
+
title: z.string().min(1).max(200),
|
|
26
|
+
url: z.string().url(),
|
|
27
|
+
categoryId: z.string().optional(),
|
|
28
|
+
summary: z.string().max(1000).optional(),
|
|
29
|
+
tags: z.array(z.string()).default([]),
|
|
30
|
+
})
|
|
31
|
+
|
|
32
|
+
export const adminCreateBookmarkFn = createServerFn({ method: 'POST' })
|
|
33
|
+
.validator(createSchema)
|
|
34
|
+
.handler(async ({ data }) => {
|
|
35
|
+
const session = await requireAdmin()
|
|
36
|
+
const gh = parseGithubUrl(data.url)
|
|
37
|
+
return prisma.websiteBookmark.create({
|
|
38
|
+
data: {
|
|
39
|
+
canonicalId: bookmarkCanonicalId(data.url),
|
|
40
|
+
title: data.title,
|
|
41
|
+
url: data.url,
|
|
42
|
+
categoryId: data.categoryId || null,
|
|
43
|
+
summary: data.summary || null,
|
|
44
|
+
tags: data.tags,
|
|
45
|
+
visibility: 'public',
|
|
46
|
+
userId: session.user!.id!,
|
|
47
|
+
isGithub: !!gh,
|
|
48
|
+
githubOwner: gh?.owner ?? null,
|
|
49
|
+
githubRepo: gh?.repo ?? null,
|
|
50
|
+
},
|
|
51
|
+
select: { id: true },
|
|
52
|
+
})
|
|
53
|
+
})
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
// SPDX-License-Identifier: AGPL-3.0-or-later
|
|
2
|
+
// AllBlue - 理想之海
|
|
3
|
+
// Copyright (C) 2026 AllBlue Contributors
|
|
4
|
+
//
|
|
5
|
+
// This program is free software: you can redistribute it and/or modify
|
|
6
|
+
// it under the terms of the GNU Affero General Public License as published by
|
|
7
|
+
// the Free Software Foundation, either version 3 of the License, or
|
|
8
|
+
// (at your option) any later version.
|
|
9
|
+
//
|
|
10
|
+
// This program is distributed in the hope that it will be useful,
|
|
11
|
+
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
12
|
+
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
13
|
+
// GNU Affero General Public License for more details.
|
|
14
|
+
//
|
|
15
|
+
// You should have received a copy of the GNU Affero General Public License
|
|
16
|
+
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
17
|
+
|
|
18
|
+
import { createServerFn } from '@tanstack/react-start'
|
|
19
|
+
import { z } from 'zod'
|
|
20
|
+
import { prisma } from '@allbluecn/database'
|
|
21
|
+
import { requireAdmin } from '@/server/auth-helpers'
|
|
22
|
+
|
|
23
|
+
export const adminDeleteBookmarkFn = createServerFn({ method: 'POST' })
|
|
24
|
+
.validator(z.object({ id: z.string() }))
|
|
25
|
+
.handler(async ({ data }) => {
|
|
26
|
+
await requireAdmin()
|
|
27
|
+
const existing = await prisma.websiteBookmark.findUnique({ where: { id: data.id } })
|
|
28
|
+
if (!existing) throw new Error('未找到')
|
|
29
|
+
await prisma.websiteBookmark.update({ where: { id: data.id }, data: { isDeleted: true } })
|
|
30
|
+
return { success: true }
|
|
31
|
+
})
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
// SPDX-License-Identifier: AGPL-3.0-or-later
|
|
2
|
+
// AllBlue - 理想之海
|
|
3
|
+
// Copyright (C) 2026 AllBlue Contributors
|
|
4
|
+
//
|
|
5
|
+
// This program is free software: you can redistribute it and/or modify
|
|
6
|
+
// it under the terms of the GNU Affero General Public License as published by
|
|
7
|
+
// the Free Software Foundation, either version 3 of the License, or
|
|
8
|
+
// (at your option) any later version.
|
|
9
|
+
//
|
|
10
|
+
// This program is distributed in the hope that it will be useful,
|
|
11
|
+
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
12
|
+
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
13
|
+
// GNU Affero General Public License for more details.
|
|
14
|
+
//
|
|
15
|
+
// You should have received a copy of the GNU Affero General Public License
|
|
16
|
+
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
17
|
+
|
|
18
|
+
import { createServerFn } from '@tanstack/react-start'
|
|
19
|
+
import { z } from 'zod'
|
|
20
|
+
import { prisma } from '@allbluecn/database'
|
|
21
|
+
import { requireAdmin } from '@/server/auth-helpers'
|
|
22
|
+
|
|
23
|
+
export const adminGetBookmarkDetailFn = createServerFn({ method: 'GET' })
|
|
24
|
+
.validator(z.object({ id: z.string() }))
|
|
25
|
+
.handler(async ({ data }) => {
|
|
26
|
+
await requireAdmin()
|
|
27
|
+
const item = await prisma.websiteBookmark.findFirst({
|
|
28
|
+
where: { id: data.id, visibility: 'public', isDeleted: false },
|
|
29
|
+
})
|
|
30
|
+
if (!item) throw new Error('未找到')
|
|
31
|
+
return item
|
|
32
|
+
})
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
// SPDX-License-Identifier: AGPL-3.0-or-later
|
|
2
|
+
// AllBlue - 理想之海
|
|
3
|
+
// Copyright (C) 2026 AllBlue Contributors
|
|
4
|
+
//
|
|
5
|
+
// This program is free software: you can redistribute it and/or modify
|
|
6
|
+
// it under the terms of the GNU Affero General Public License as published by
|
|
7
|
+
// the Free Software Foundation, either version 3 of the License, or
|
|
8
|
+
// (at your option) any later version.
|
|
9
|
+
//
|
|
10
|
+
// This program is distributed in the hope that it will be useful,
|
|
11
|
+
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
12
|
+
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
13
|
+
// GNU Affero General Public License for more details.
|
|
14
|
+
//
|
|
15
|
+
// You should have received a copy of the GNU Affero General Public License
|
|
16
|
+
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
17
|
+
|
|
18
|
+
import { createServerFn } from '@tanstack/react-start'
|
|
19
|
+
import { z } from 'zod'
|
|
20
|
+
import { prisma } from '@allbluecn/database'
|
|
21
|
+
import { requireAdmin } from '@/server/auth-helpers'
|
|
22
|
+
|
|
23
|
+
const listSchema = z.object({
|
|
24
|
+
search: z.string().optional(),
|
|
25
|
+
categoryId: z.string().optional(),
|
|
26
|
+
isGithub: z.boolean().optional(),
|
|
27
|
+
page: z.number().int().min(1).default(1),
|
|
28
|
+
pageSize: z.number().int().min(1).max(60).default(24),
|
|
29
|
+
})
|
|
30
|
+
|
|
31
|
+
export const adminListBookmarksFn = createServerFn({ method: 'GET' })
|
|
32
|
+
.validator(listSchema)
|
|
33
|
+
.handler(async ({ data }) => {
|
|
34
|
+
await requireAdmin()
|
|
35
|
+
const where: any = { visibility: 'public', isDeleted: false }
|
|
36
|
+
if (data.search) {
|
|
37
|
+
where.OR = [
|
|
38
|
+
{ title: { contains: data.search, mode: 'insensitive' } },
|
|
39
|
+
{ summary: { contains: data.search, mode: 'insensitive' } },
|
|
40
|
+
{ url: { contains: data.search, mode: 'insensitive' } },
|
|
41
|
+
]
|
|
42
|
+
}
|
|
43
|
+
if (data.categoryId) where.categoryId = data.categoryId
|
|
44
|
+
if (data.isGithub !== undefined) where.isGithub = data.isGithub
|
|
45
|
+
const [items, total] = await Promise.all([
|
|
46
|
+
prisma.websiteBookmark.findMany({
|
|
47
|
+
where,
|
|
48
|
+
select: {
|
|
49
|
+
id: true, canonicalId: true, title: true, url: true, categoryId: true,
|
|
50
|
+
summary: true, faviconUrl: true, isGithub: true, githubStars: true,
|
|
51
|
+
githubLatestVersion: true, githubVersionDate: true, githubRepoUpdatedAt: true,
|
|
52
|
+
githubSyncedAt: true, tags: true, visibility: true, userId: true, createdAt: true,
|
|
53
|
+
category: { select: { icon: true } },
|
|
54
|
+
},
|
|
55
|
+
orderBy: { createdAt: 'desc' },
|
|
56
|
+
skip: (data.page - 1) * data.pageSize,
|
|
57
|
+
take: data.pageSize,
|
|
58
|
+
}),
|
|
59
|
+
prisma.websiteBookmark.count({ where }),
|
|
60
|
+
])
|
|
61
|
+
const mapped = items.map(({ category: _c, ...rest }) => ({
|
|
62
|
+
...rest,
|
|
63
|
+
categoryIcon: _c?.icon ?? 'Folder',
|
|
64
|
+
}))
|
|
65
|
+
return { items: mapped, total, page: data.page, pageSize: data.pageSize }
|
|
66
|
+
})
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
// SPDX-License-Identifier: AGPL-3.0-or-later
|
|
2
|
+
// AllBlue - 理想之海
|
|
3
|
+
// Copyright (C) 2026 AllBlue Contributors
|
|
4
|
+
//
|
|
5
|
+
// This program is free software: you can redistribute it and/or modify
|
|
6
|
+
// it under the terms of the GNU Affero General Public License as published by
|
|
7
|
+
// the Free Software Foundation, either version 3 of the License, or
|
|
8
|
+
// (at your option) any later version.
|
|
9
|
+
//
|
|
10
|
+
// This program is distributed in the hope that it will be useful,
|
|
11
|
+
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
12
|
+
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
13
|
+
// GNU Affero General Public License for more details.
|
|
14
|
+
//
|
|
15
|
+
// You should have received a copy of the GNU Affero General Public License
|
|
16
|
+
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
17
|
+
|
|
18
|
+
import { createServerFn } from '@tanstack/react-start'
|
|
19
|
+
import { z } from 'zod'
|
|
20
|
+
import { prisma } from '@allbluecn/database'
|
|
21
|
+
import { requireAdmin } from '@/server/auth-helpers'
|
|
22
|
+
import { syncBookmarkGithub } from '@/server/knowledge-public/github-sync'
|
|
23
|
+
|
|
24
|
+
const syncSchema = z.object({
|
|
25
|
+
force: z.boolean().default(false),
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
export const adminSyncAllGithubFn = createServerFn({ method: 'POST' })
|
|
29
|
+
.validator(syncSchema)
|
|
30
|
+
.handler(async ({ data }) => {
|
|
31
|
+
await requireAdmin()
|
|
32
|
+
const where: any = { isGithub: true, isDeleted: false }
|
|
33
|
+
if (!data.force) {
|
|
34
|
+
const sixHoursAgo = new Date(Date.now() - 6 * 60 * 60 * 1000)
|
|
35
|
+
where.OR = [
|
|
36
|
+
{ githubSyncedAt: null },
|
|
37
|
+
{ githubSyncedAt: { lt: sixHoursAgo } },
|
|
38
|
+
]
|
|
39
|
+
}
|
|
40
|
+
const list = await prisma.websiteBookmark.findMany({
|
|
41
|
+
where,
|
|
42
|
+
select: { id: true },
|
|
43
|
+
})
|
|
44
|
+
let success = 0
|
|
45
|
+
let failed = 0
|
|
46
|
+
for (const bm of list) {
|
|
47
|
+
try {
|
|
48
|
+
const ok = await syncBookmarkGithub(bm.id)
|
|
49
|
+
if (ok) success++
|
|
50
|
+
else failed++
|
|
51
|
+
} catch {
|
|
52
|
+
failed++
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
return { total: list.length, success, failed }
|
|
56
|
+
})
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
// SPDX-License-Identifier: AGPL-3.0-or-later
|
|
2
|
+
// AllBlue - 理想之海
|
|
3
|
+
// Copyright (C) 2026 AllBlue Contributors
|
|
4
|
+
//
|
|
5
|
+
// This program is free software: you can redistribute it and/or modify
|
|
6
|
+
// it under the terms of the GNU Affero General Public License as published by
|
|
7
|
+
// the Free Software Foundation, either version 3 of the License, or
|
|
8
|
+
// (at your option) any later version.
|
|
9
|
+
//
|
|
10
|
+
// This program is distributed in the hope that it will be useful,
|
|
11
|
+
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
12
|
+
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
13
|
+
// GNU Affero General Public License for more details.
|
|
14
|
+
//
|
|
15
|
+
// You should have received a copy of the GNU Affero General Public License
|
|
16
|
+
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
17
|
+
|
|
18
|
+
import { createServerFn } from '@tanstack/react-start'
|
|
19
|
+
import { z } from 'zod'
|
|
20
|
+
import { prisma } from '@allbluecn/database'
|
|
21
|
+
import { requireAdmin } from '@/server/auth-helpers'
|
|
22
|
+
import { bookmarkCanonicalId, parseGithubUrl } from '@/server/knowledge-public/canonical-id'
|
|
23
|
+
|
|
24
|
+
const updateSchema = z.object({
|
|
25
|
+
id: z.string(),
|
|
26
|
+
title: z.string().min(1).max(200).optional(),
|
|
27
|
+
url: z.string().url().optional(),
|
|
28
|
+
categoryId: z.string().nullable().optional(),
|
|
29
|
+
summary: z.string().max(1000).nullable().optional(),
|
|
30
|
+
tags: z.array(z.string()).optional(),
|
|
31
|
+
})
|
|
32
|
+
|
|
33
|
+
export const adminUpdateBookmarkFn = createServerFn({ method: 'POST' })
|
|
34
|
+
.validator(updateSchema)
|
|
35
|
+
.handler(async ({ data }) => {
|
|
36
|
+
await requireAdmin()
|
|
37
|
+
const { id, ...rest } = data
|
|
38
|
+
const existing = await prisma.websiteBookmark.findUnique({ where: { id } })
|
|
39
|
+
if (!existing) throw new Error('未找到')
|
|
40
|
+
const updateData: any = { ...rest }
|
|
41
|
+
const urlChanged = data.url !== undefined && data.url !== existing.url
|
|
42
|
+
if (urlChanged && data.url) {
|
|
43
|
+
const gh = parseGithubUrl(data.url)
|
|
44
|
+
updateData.canonicalId = bookmarkCanonicalId(data.url)
|
|
45
|
+
updateData.isGithub = !!gh
|
|
46
|
+
updateData.githubOwner = gh?.owner ?? null
|
|
47
|
+
updateData.githubRepo = gh?.repo ?? null
|
|
48
|
+
}
|
|
49
|
+
return prisma.websiteBookmark.update({ where: { id }, data: updateData, select: { id: true } })
|
|
50
|
+
})
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
// SPDX-License-Identifier: AGPL-3.0-or-later
|
|
2
|
+
// AllBlue - 理想之海
|
|
3
|
+
// Copyright (C) 2026 AllBlue Contributors
|
|
4
|
+
//
|
|
5
|
+
// This program is free software: you can redistribute it and/or modify
|
|
6
|
+
// it under the terms of the GNU Affero General Public License as published by
|
|
7
|
+
// the Free Software Foundation, either version 3 of the License, or
|
|
8
|
+
// (at your option) any later version.
|
|
9
|
+
//
|
|
10
|
+
// This program is distributed in the hope that it will be useful,
|
|
11
|
+
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
12
|
+
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
13
|
+
// GNU Affero General Public License for more details.
|
|
14
|
+
//
|
|
15
|
+
// You should have received a copy of the GNU Affero General Public License
|
|
16
|
+
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
17
|
+
|
|
18
|
+
import { createServerFn } from '@tanstack/react-start'
|
|
19
|
+
import { z } from 'zod'
|
|
20
|
+
import { prisma } from '@allbluecn/database'
|
|
21
|
+
import { requireAdmin } from '@/server/auth-helpers'
|
|
22
|
+
import { complianceCanonicalId } from '@/server/knowledge-public/canonical-id'
|
|
23
|
+
|
|
24
|
+
const createSchema = z.object({
|
|
25
|
+
title: z.string().min(1).max(200),
|
|
26
|
+
code: z.string().max(100).optional(),
|
|
27
|
+
industry: z.string().min(1),
|
|
28
|
+
regulatoryBody: z.string().optional(),
|
|
29
|
+
region: z.string().optional(),
|
|
30
|
+
summary: z.string().max(1000).optional(),
|
|
31
|
+
content: z.string().optional(),
|
|
32
|
+
requirements: z.array(z.object({ title: z.string(), detail: z.string() })).default([]),
|
|
33
|
+
sourceUrl: z.string().url().optional().or(z.literal('')),
|
|
34
|
+
tags: z.array(z.string()).default([]),
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
export const adminCreateComplianceFn = createServerFn({ method: 'POST' })
|
|
38
|
+
.validator(createSchema)
|
|
39
|
+
.handler(async ({ data }) => {
|
|
40
|
+
const session = await requireAdmin()
|
|
41
|
+
return prisma.industryCompliance.create({
|
|
42
|
+
data: {
|
|
43
|
+
canonicalId: complianceCanonicalId({ title: data.title, code: data.code }),
|
|
44
|
+
title: data.title,
|
|
45
|
+
industry: data.industry,
|
|
46
|
+
regulatoryBody: data.regulatoryBody || null,
|
|
47
|
+
region: data.region || null,
|
|
48
|
+
summary: data.summary || null,
|
|
49
|
+
content: data.content || null,
|
|
50
|
+
requirements: data.requirements,
|
|
51
|
+
sourceUrl: data.sourceUrl || null,
|
|
52
|
+
tags: data.tags,
|
|
53
|
+
visibility: 'public',
|
|
54
|
+
userId: session.user!.id!,
|
|
55
|
+
},
|
|
56
|
+
select: { id: true },
|
|
57
|
+
})
|
|
58
|
+
})
|