@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.
Files changed (276) hide show
  1. package/package.json +86 -0
  2. package/src/client.tsx +31 -0
  3. package/src/components/admin/admin-user-menu.tsx +90 -0
  4. package/src/components/admin/developer-log.tsx +356 -0
  5. package/src/components/ai-provider/model-list-editor.tsx +147 -0
  6. package/src/components/ai-provider/provider-form.tsx +434 -0
  7. package/src/components/ai-provider/provider-icon.tsx +43 -0
  8. package/src/components/ai-provider/provider-logo.tsx +87 -0
  9. package/src/components/ai-provider/provider-table.tsx +258 -0
  10. package/src/components/auth/admin-login-form.tsx +214 -0
  11. package/src/components/auth/auth-brand-panel.tsx +60 -0
  12. package/src/components/auth/auth-header.tsx +38 -0
  13. package/src/components/auth/auth-layout.tsx +66 -0
  14. package/src/components/auth/auth-providers.tsx +34 -0
  15. package/src/components/auth/icon-input.tsx +46 -0
  16. package/src/components/auth/oauth-section.tsx +51 -0
  17. package/src/components/auth/password-input.tsx +67 -0
  18. package/src/components/common/page-header.tsx +42 -0
  19. package/src/components/common/placeholder-page.tsx +42 -0
  20. package/src/components/component/component-form-dialog.tsx +348 -0
  21. package/src/components/component/component-import-dialog.tsx +235 -0
  22. package/src/components/component/component-table.tsx +153 -0
  23. package/src/components/data-table/data-table-pagination.tsx +114 -0
  24. package/src/components/data-table/data-table.tsx +218 -0
  25. package/src/components/data-table/table-detail-layout.tsx +99 -0
  26. package/src/components/dependency/changelog-dialog.tsx +108 -0
  27. package/src/components/dependency/columns.tsx +232 -0
  28. package/src/components/dependency/dependency-table.tsx +368 -0
  29. package/src/components/dependency/edit-dialog.tsx +200 -0
  30. package/src/components/knowledge-public/bookmark-form-dialog.tsx +215 -0
  31. package/src/components/knowledge-public/compliance-form-dialog.tsx +308 -0
  32. package/src/components/knowledge-public/legal-form-dialog.tsx +315 -0
  33. package/src/components/layout/sidebar-02/app-sidebar.tsx +160 -0
  34. package/src/components/layout/sidebar-02/index.tsx +33 -0
  35. package/src/components/layout/sidebar-02/logo.tsx +40 -0
  36. package/src/components/layout/sidebar-02/nav-main.tsx +153 -0
  37. package/src/components/layout/sidebar-02/nav-notifications.tsx +78 -0
  38. package/src/components/layout/sidebar-02/team-switcher.tsx +106 -0
  39. package/src/components/magicui/animated-theme-toggler.tsx +272 -0
  40. package/src/components/magicui/rainbow-button.tsx +78 -0
  41. package/src/components/market/columns.tsx +151 -0
  42. package/src/components/market/market-app-form.tsx +237 -0
  43. package/src/components/market/market-app-list.tsx +156 -0
  44. package/src/components/monitoring/error-list.tsx +143 -0
  45. package/src/components/monitoring/health-cards.tsx +152 -0
  46. package/src/components/monitoring/metrics-stats.tsx +184 -0
  47. package/src/components/monitoring/request-log-table.tsx +179 -0
  48. package/src/components/providers/theme-toggle.tsx +59 -0
  49. package/src/components/settings/avatar/avatar-config.ts +46 -0
  50. package/src/components/ui/alert-dialog.tsx +216 -0
  51. package/src/components/ui/alert.tsx +76 -0
  52. package/src/components/ui/avatar.tsx +129 -0
  53. package/src/components/ui/badge.tsx +66 -0
  54. package/src/components/ui/button.tsx +84 -0
  55. package/src/components/ui/card.tsx +120 -0
  56. package/src/components/ui/checkbox.tsx +50 -0
  57. package/src/components/ui/collapsible.tsx +50 -0
  58. package/src/components/ui/dialog.tsx +185 -0
  59. package/src/components/ui/dropdown-menu.tsx +286 -0
  60. package/src/components/ui/field.tsx +255 -0
  61. package/src/components/ui/input-group.tsx +171 -0
  62. package/src/components/ui/input.tsx +36 -0
  63. package/src/components/ui/label.tsx +41 -0
  64. package/src/components/ui/pagination.tsx +146 -0
  65. package/src/components/ui/select.tsx +209 -0
  66. package/src/components/ui/separator.tsx +45 -0
  67. package/src/components/ui/sheet.tsx +164 -0
  68. package/src/components/ui/sidebar.tsx +719 -0
  69. package/src/components/ui/skeleton.tsx +30 -0
  70. package/src/components/ui/sonner.tsx +37 -0
  71. package/src/components/ui/switch.tsx +48 -0
  72. package/src/components/ui/table.tsx +133 -0
  73. package/src/components/ui/textarea.tsx +35 -0
  74. package/src/components/ui/tooltip.tsx +74 -0
  75. package/src/components/user/columns.tsx +153 -0
  76. package/src/components/user/settings-activity-log.tsx +358 -0
  77. package/src/components/user/settings-profile/constants.ts +42 -0
  78. package/src/components/user/settings-profile/types.ts +47 -0
  79. package/src/components/user/settings-profile.tsx +664 -0
  80. package/src/components/user/user-create-dialog.tsx +137 -0
  81. package/src/components/user/user-filters.tsx +117 -0
  82. package/src/hooks/use-mobile.ts +36 -0
  83. package/src/instrument.client.ts +34 -0
  84. package/src/lib/anyhub/capabilities.ts +59 -0
  85. package/src/lib/anyhub/types.ts +43 -0
  86. package/src/lib/auth-client.ts +83 -0
  87. package/src/lib/auth-utils.ts +24 -0
  88. package/src/lib/auth.config.ts +264 -0
  89. package/src/lib/avatar.ts +41 -0
  90. package/src/lib/i18n/client.ts +32 -0
  91. package/src/lib/i18n/config.ts +40 -0
  92. package/src/lib/i18n/detect.ts +43 -0
  93. package/src/lib/i18n/locales/en/admin-ai.json +128 -0
  94. package/src/lib/i18n/locales/en/admin-billing.json +56 -0
  95. package/src/lib/i18n/locales/en/admin-users.json +88 -0
  96. package/src/lib/i18n/locales/en/common.json +877 -0
  97. package/src/lib/i18n/locales/en/enterprise.json +208 -0
  98. package/src/lib/i18n/locales/zh/admin-ai.json +128 -0
  99. package/src/lib/i18n/locales/zh/admin-billing.json +56 -0
  100. package/src/lib/i18n/locales/zh/admin-users.json +88 -0
  101. package/src/lib/i18n/locales/zh/common.json +877 -0
  102. package/src/lib/i18n/locales/zh/enterprise.json +208 -0
  103. package/src/lib/i18n/server.ts +18 -0
  104. package/src/lib/i18n/types.ts +5 -0
  105. package/src/lib/monitoring-benchmarks.ts +85 -0
  106. package/src/lib/monitoring-descriptions.ts +78 -0
  107. package/src/lib/prisma.ts +18 -0
  108. package/src/lib/router-context.ts +38 -0
  109. package/src/lib/session.ts +38 -0
  110. package/src/lib/utils.ts +23 -0
  111. package/src/providers/index.tsx +45 -0
  112. package/src/routeTree.gen.ts +1199 -0
  113. package/src/router.tsx +51 -0
  114. package/src/routes/__root.tsx +170 -0
  115. package/src/routes/_nav/ai-providers/$id/edit/route.lazy.tsx +74 -0
  116. package/src/routes/_nav/ai-providers/$id/edit/route.tsx +35 -0
  117. package/src/routes/_nav/ai-providers/index/route.lazy.tsx +33 -0
  118. package/src/routes/_nav/ai-providers/new/route.lazy.tsx +31 -0
  119. package/src/routes/_nav/ai-providers/new/route.tsx +20 -0
  120. package/src/routes/_nav/ai-providers/route.lazy.tsx +26 -0
  121. package/src/routes/_nav/ai-providers/route.tsx +31 -0
  122. package/src/routes/_nav/apps/route.lazy.tsx +30 -0
  123. package/src/routes/_nav/apps/route.tsx +20 -0
  124. package/src/routes/_nav/audit/route.lazy.tsx +100 -0
  125. package/src/routes/_nav/audit/route.tsx +25 -0
  126. package/src/routes/_nav/billing/invoices/route.lazy.tsx +86 -0
  127. package/src/routes/_nav/billing/invoices/route.tsx +25 -0
  128. package/src/routes/_nav/billing/route.lazy.tsx +48 -0
  129. package/src/routes/_nav/billing/route.tsx +27 -0
  130. package/src/routes/_nav/billing/subscriptions/$subscriptionId/route.lazy.tsx +160 -0
  131. package/src/routes/_nav/billing/subscriptions/$subscriptionId/route.tsx +25 -0
  132. package/src/routes/_nav/billing/subscriptions/route.lazy.tsx +113 -0
  133. package/src/routes/_nav/billing/subscriptions/route.tsx +28 -0
  134. package/src/routes/_nav/compliance/route.lazy.tsx +184 -0
  135. package/src/routes/_nav/compliance/route.tsx +25 -0
  136. package/src/routes/_nav/components/route.lazy.tsx +184 -0
  137. package/src/routes/_nav/components/route.tsx +40 -0
  138. package/src/routes/_nav/dashboard/route.lazy.tsx +33 -0
  139. package/src/routes/_nav/dashboard/route.tsx +20 -0
  140. package/src/routes/_nav/dependencies/route.lazy.tsx +66 -0
  141. package/src/routes/_nav/dependencies/route.tsx +32 -0
  142. package/src/routes/_nav/developer-logs/route.lazy.tsx +6 -0
  143. package/src/routes/_nav/developer-logs/route.tsx +14 -0
  144. package/src/routes/_nav/docs/route.lazy.tsx +30 -0
  145. package/src/routes/_nav/docs/route.tsx +20 -0
  146. package/src/routes/_nav/knowledge-public/bookmarks/route.lazy.tsx +355 -0
  147. package/src/routes/_nav/knowledge-public/bookmarks/route.tsx +40 -0
  148. package/src/routes/_nav/knowledge-public/compliance/route.lazy.tsx +245 -0
  149. package/src/routes/_nav/knowledge-public/compliance/route.tsx +40 -0
  150. package/src/routes/_nav/knowledge-public/legal/route.lazy.tsx +242 -0
  151. package/src/routes/_nav/knowledge-public/legal/route.tsx +40 -0
  152. package/src/routes/_nav/licenses/$licenseId/route.lazy.tsx +235 -0
  153. package/src/routes/_nav/licenses/$licenseId/route.tsx +25 -0
  154. package/src/routes/_nav/licenses/new/route.lazy.tsx +237 -0
  155. package/src/routes/_nav/licenses/route.lazy.tsx +143 -0
  156. package/src/routes/_nav/licenses/route.tsx +25 -0
  157. package/src/routes/_nav/market/route.lazy.tsx +31 -0
  158. package/src/routes/_nav/market/route.tsx +20 -0
  159. package/src/routes/_nav/monitoring/ai-usage/route.lazy.tsx +305 -0
  160. package/src/routes/_nav/monitoring/ai-usage/route.tsx +3 -0
  161. package/src/routes/_nav/monitoring/errors/route.lazy.tsx +111 -0
  162. package/src/routes/_nav/monitoring/health/route.lazy.tsx +260 -0
  163. package/src/routes/_nav/monitoring/logs/route.lazy.tsx +165 -0
  164. package/src/routes/_nav/monitoring/metrics/route.lazy.tsx +240 -0
  165. package/src/routes/_nav/monitoring/overview/route.lazy.tsx +138 -0
  166. package/src/routes/_nav/monitoring/route.lazy.tsx +68 -0
  167. package/src/routes/_nav/monitoring/route.tsx +20 -0
  168. package/src/routes/_nav/monitoring/sentry/route.lazy.tsx +399 -0
  169. package/src/routes/_nav/monitoring/sentry/route.tsx +20 -0
  170. package/src/routes/_nav/permissions/route.lazy.tsx +504 -0
  171. package/src/routes/_nav/permissions/route.tsx +26 -0
  172. package/src/routes/_nav/route.lazy.tsx +31 -0
  173. package/src/routes/_nav/route.tsx +34 -0
  174. package/src/routes/_nav/settings/route.lazy.tsx +30 -0
  175. package/src/routes/_nav/settings/route.tsx +20 -0
  176. package/src/routes/_nav/sso/route.lazy.tsx +145 -0
  177. package/src/routes/_nav/sso/route.tsx +25 -0
  178. package/src/routes/_nav/users/$userId/route.lazy.tsx +432 -0
  179. package/src/routes/_nav/users/$userId/route.tsx +27 -0
  180. package/src/routes/_nav/users/-components/users-table.tsx +38 -0
  181. package/src/routes/_nav/users/route.lazy.tsx +74 -0
  182. package/src/routes/_nav/users/route.tsx +43 -0
  183. package/src/routes/api/compliance-download/$jobId.ts +35 -0
  184. package/src/routes/api/observability/errors.ts +41 -0
  185. package/src/routes/api/observability/health.ts +30 -0
  186. package/src/routes/api.auth.$.ts +31 -0
  187. package/src/routes/api.sentry-test.ts +33 -0
  188. package/src/routes/index/route.lazy.tsx +22 -0
  189. package/src/routes/index/route.tsx +24 -0
  190. package/src/routes/login/route.lazy.tsx +46 -0
  191. package/src/routes/login/route.tsx +28 -0
  192. package/src/routes/sentry-tunnel.ts +61 -0
  193. package/src/routes/setup/route.lazy.tsx +94 -0
  194. package/src/routes/setup/route.tsx +28 -0
  195. package/src/server/__tests__/admin-ai-alerts.test.ts +113 -0
  196. package/src/server/__tests__/licenses-issue.test.ts +98 -0
  197. package/src/server/admin-ai-alerts.ts +177 -0
  198. package/src/server/admin-ai-usage.ts +134 -0
  199. package/src/server/auth-helpers.ts +51 -0
  200. package/src/server/entitlements.ts +24 -0
  201. package/src/server/knowledge-public/canonical-id.ts +55 -0
  202. package/src/server/knowledge-public/github-sync.ts +86 -0
  203. package/src/server/license-issue.ts +78 -0
  204. package/src/server/middlewares/request.ts +26 -0
  205. package/src/server/observability/debug-middleware.ts +39 -0
  206. package/src/server/observability/error-reporter.ts +99 -0
  207. package/src/server/observability/health-check.ts +140 -0
  208. package/src/server/observability/index.ts +33 -0
  209. package/src/server/observability/logger.ts +49 -0
  210. package/src/server/observability/metrics.ts +105 -0
  211. package/src/server/observability/request-middleware.ts +125 -0
  212. package/src/server/observability/timing.ts +42 -0
  213. package/src/server/pro-services.ts +32 -0
  214. package/src/server/serverFns/admin-billing.ts +94 -0
  215. package/src/server/serverFns/ai-provider/create.ts +92 -0
  216. package/src/server/serverFns/ai-provider/delete.ts +42 -0
  217. package/src/server/serverFns/ai-provider/get.ts +35 -0
  218. package/src/server/serverFns/ai-provider/list.ts +92 -0
  219. package/src/server/serverFns/ai-provider/update.ts +73 -0
  220. package/src/server/serverFns/anyhub/market.ts +64 -0
  221. package/src/server/serverFns/audit.ts +57 -0
  222. package/src/server/serverFns/auth/session.ts +26 -0
  223. package/src/server/serverFns/auth/setup.ts +73 -0
  224. package/src/server/serverFns/compliance.ts +85 -0
  225. package/src/server/serverFns/components/import.ts +197 -0
  226. package/src/server/serverFns/components/list.ts +51 -0
  227. package/src/server/serverFns/components/mutations.ts +84 -0
  228. package/src/server/serverFns/dependency/helpers.ts +245 -0
  229. package/src/server/serverFns/dependency/list.ts +127 -0
  230. package/src/server/serverFns/dependency/sync.ts +123 -0
  231. package/src/server/serverFns/dependency/update.ts +51 -0
  232. package/src/server/serverFns/developer-log-auto-generate.ts +176 -0
  233. package/src/server/serverFns/developer-logs.ts +132 -0
  234. package/src/server/serverFns/knowledge-public/bookmarks/create.ts +53 -0
  235. package/src/server/serverFns/knowledge-public/bookmarks/delete.ts +31 -0
  236. package/src/server/serverFns/knowledge-public/bookmarks/detail.ts +32 -0
  237. package/src/server/serverFns/knowledge-public/bookmarks/list.ts +66 -0
  238. package/src/server/serverFns/knowledge-public/bookmarks/sync-all-github.ts +56 -0
  239. package/src/server/serverFns/knowledge-public/bookmarks/update.ts +50 -0
  240. package/src/server/serverFns/knowledge-public/compliance/create.ts +58 -0
  241. package/src/server/serverFns/knowledge-public/compliance/delete.ts +31 -0
  242. package/src/server/serverFns/knowledge-public/compliance/detail.ts +32 -0
  243. package/src/server/serverFns/knowledge-public/compliance/list.ts +59 -0
  244. package/src/server/serverFns/knowledge-public/compliance/update.ts +53 -0
  245. package/src/server/serverFns/knowledge-public/legal/create.ts +62 -0
  246. package/src/server/serverFns/knowledge-public/legal/delete.ts +31 -0
  247. package/src/server/serverFns/knowledge-public/legal/detail.ts +32 -0
  248. package/src/server/serverFns/knowledge-public/legal/list.ts +60 -0
  249. package/src/server/serverFns/knowledge-public/legal/update.ts +55 -0
  250. package/src/server/serverFns/licenses.ts +92 -0
  251. package/src/server/serverFns/locale.ts +10 -0
  252. package/src/server/serverFns/monitoring/check-ai-alerts.ts +13 -0
  253. package/src/server/serverFns/monitoring/get-ai-usage-overview.ts +25 -0
  254. package/src/server/serverFns/monitoring/get-errors.ts +43 -0
  255. package/src/server/serverFns/monitoring/get-health-settings.ts +35 -0
  256. package/src/server/serverFns/monitoring/get-health.ts +55 -0
  257. package/src/server/serverFns/monitoring/get-metrics.ts +46 -0
  258. package/src/server/serverFns/monitoring/get-request-logs.ts +66 -0
  259. package/src/server/serverFns/monitoring/report-error.ts +55 -0
  260. package/src/server/serverFns/monitoring/reset.ts +70 -0
  261. package/src/server/serverFns/permissions.ts +214 -0
  262. package/src/server/serverFns/sentry/config.ts +88 -0
  263. package/src/server/serverFns/sso-config.ts +83 -0
  264. package/src/server/serverFns/user/create.ts +40 -0
  265. package/src/server/serverFns/user/delete.ts +29 -0
  266. package/src/server/serverFns/user/detail.ts +45 -0
  267. package/src/server/serverFns/user/list.ts +56 -0
  268. package/src/server/serverFns/user/reset-password.ts +28 -0
  269. package/src/server/serverFns/user/update-role.ts +26 -0
  270. package/src/server/serverFns/user/update-status.ts +26 -0
  271. package/src/server.ts +27 -0
  272. package/src/start.ts +85 -0
  273. package/src/styles/_keyframe-animations.scss +91 -0
  274. package/src/styles/_variables.scss +296 -0
  275. package/src/styles/globals.css +200 -0
  276. 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
+ })