@allbluecn/admin-app 0.3.5 → 0.3.6
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 +11 -11
- package/src/components/__tests__/user-avatar.test.tsx +39 -0
- package/src/components/admin/admin-user-menu.tsx +2 -2
- package/src/components/auth/admin-login-form.tsx +2 -2
- package/src/components/{knowledge-public → knowledge/public}/bookmark-form-dialog.tsx +4 -4
- package/src/components/{knowledge-public → knowledge/public}/compliance-form-dialog.tsx +4 -4
- package/src/components/{knowledge-public → knowledge/public}/legal-form-dialog.tsx +4 -4
- package/src/components/ui/checkbox.tsx +4 -4
- package/src/components/user/columns.tsx +1 -1
- package/src/components/user-avatar.tsx +7 -4
- package/src/lib/__tests__/avatar-shape.test.ts +44 -0
- package/src/lib/auth.config.ts +1 -1
- package/src/lib/avatar-shape.ts +35 -0
- package/src/routes/_nav/knowledge-public/bookmarks/route.lazy.tsx +10 -10
- package/src/routes/_nav/knowledge-public/bookmarks/route.tsx +1 -1
- package/src/routes/_nav/knowledge-public/compliance/route.lazy.tsx +8 -8
- package/src/routes/_nav/knowledge-public/compliance/route.tsx +1 -1
- package/src/routes/_nav/knowledge-public/legal/route.lazy.tsx +8 -8
- package/src/routes/_nav/knowledge-public/legal/route.tsx +1 -1
- package/src/routes/_nav/users/$userId/route.lazy.tsx +1 -1
- package/src/routes/login/route.lazy.tsx +1 -1
- package/src/server/{knowledge-public → knowledge/public}/github-sync.ts +12 -2
- package/src/server/serverFns/{knowledge-public → knowledge/public}/bookmarks/create.ts +1 -1
- package/src/server/serverFns/{knowledge-public → knowledge/public}/bookmarks/list.ts +2 -1
- package/src/server/serverFns/{knowledge-public → knowledge/public}/bookmarks/sync-all-github.ts +3 -2
- package/src/server/serverFns/{knowledge-public → knowledge/public}/bookmarks/update.ts +3 -2
- package/src/server/serverFns/{knowledge-public → knowledge/public}/compliance/create.ts +1 -1
- package/src/server/serverFns/{knowledge-public → knowledge/public}/compliance/list.ts +2 -1
- package/src/server/serverFns/{knowledge-public → knowledge/public}/compliance/update.ts +3 -2
- package/src/server/serverFns/{knowledge-public → knowledge/public}/legal/create.ts +1 -1
- package/src/server/serverFns/{knowledge-public → knowledge/public}/legal/list.ts +2 -1
- package/src/server/serverFns/{knowledge-public → knowledge/public}/legal/update.ts +3 -2
- /package/src/server/{knowledge-public → knowledge/public}/canonical-id.ts +0 -0
- /package/src/server/serverFns/{knowledge-public → knowledge/public}/bookmarks/delete.ts +0 -0
- /package/src/server/serverFns/{knowledge-public → knowledge/public}/bookmarks/detail.ts +0 -0
- /package/src/server/serverFns/{knowledge-public → knowledge/public}/compliance/delete.ts +0 -0
- /package/src/server/serverFns/{knowledge-public → knowledge/public}/compliance/detail.ts +0 -0
- /package/src/server/serverFns/{knowledge-public → knowledge/public}/legal/delete.ts +0 -0
- /package/src/server/serverFns/{knowledge-public → knowledge/public}/legal/detail.ts +0 -0
package/package.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@allbluecn/admin-app",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.6",
|
|
4
4
|
"private": false,
|
|
5
5
|
"license": "AGPL-3.0-or-later",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"dependencies": {
|
|
8
|
-
"@allbluecn/database": "0.9.
|
|
9
|
-
"@allbluecn/kernel": "0.
|
|
8
|
+
"@allbluecn/database": "0.9.3",
|
|
9
|
+
"@allbluecn/kernel": "0.10.0",
|
|
10
10
|
"@auth/core": "^0.41.2",
|
|
11
11
|
"@base-ui/react": "^1.8.0",
|
|
12
12
|
"@fontsource-variable/inter": "^5.3.0",
|
|
@@ -21,15 +21,15 @@
|
|
|
21
21
|
"@radix-ui/react-tooltip": "^1.2.8",
|
|
22
22
|
"@sentry/tanstackstart-react": "^10.67.0",
|
|
23
23
|
"@tabler/icons-react": "^3.44.0",
|
|
24
|
-
"@tanstack/charts": "^
|
|
25
|
-
"@tanstack/react-form": "2.0.0-alpha.
|
|
24
|
+
"@tanstack/charts": "^1.1.0",
|
|
25
|
+
"@tanstack/react-form": "2.0.0-alpha.3",
|
|
26
26
|
"@tanstack/react-pacer": "^0.24.1",
|
|
27
|
-
"@tanstack/react-query": "^5.104.
|
|
28
|
-
"@tanstack/react-router": "1.170.
|
|
27
|
+
"@tanstack/react-query": "^5.104.5",
|
|
28
|
+
"@tanstack/react-router": "1.170.46",
|
|
29
29
|
"@tanstack/react-router-ssr-query": "^1.167.3",
|
|
30
|
-
"@tanstack/react-start": "1.
|
|
31
|
-
"@tanstack/react-table": "^9.
|
|
32
|
-
"@tanstack/react-virtual": "^3.14.
|
|
30
|
+
"@tanstack/react-start": "1.169.4",
|
|
31
|
+
"@tanstack/react-table": "^9.3.1",
|
|
32
|
+
"@tanstack/react-virtual": "^3.14.22",
|
|
33
33
|
"@tanstack/store": "^0.11.2",
|
|
34
34
|
"bcryptjs": "^3.0.3",
|
|
35
35
|
"class-variance-authority": "^0.7.1",
|
|
@@ -57,7 +57,7 @@
|
|
|
57
57
|
"@inlang/paraglide-js": "^2.25.0",
|
|
58
58
|
"@inlang/plugin-message-format": "^4.4.4",
|
|
59
59
|
"@tailwindcss/vite": "^4.3.3",
|
|
60
|
-
"@tanstack/react-query-devtools": "5.104.
|
|
60
|
+
"@tanstack/react-query-devtools": "5.104.5",
|
|
61
61
|
"@tanstack/react-router-devtools": "1.167.2",
|
|
62
62
|
"@testing-library/jest-dom": "7.0.1",
|
|
63
63
|
"@testing-library/react": "^16.3.2",
|
|
@@ -0,0 +1,39 @@
|
|
|
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 { describe, it, expect } from 'vitest'
|
|
19
|
+
import { render } from '@testing-library/react'
|
|
20
|
+
import { UserAvatar } from '../user-avatar'
|
|
21
|
+
|
|
22
|
+
// 与 web 端 shared-kit/avatars/__tests__/user-avatar.test.tsx 同构:
|
|
23
|
+
// bgShape 是 DB 字典直通的 string|null,渲染前经 toAvatarShape 归一
|
|
24
|
+
describe('UserAvatar bgShape 归一', () => {
|
|
25
|
+
it('合法 rounded 渲染 rounded-lg', () => {
|
|
26
|
+
const { container } = render(<UserAvatar name="张三" bgShape="rounded" />)
|
|
27
|
+
expect(container.firstElementChild?.className).toContain('rounded-lg')
|
|
28
|
+
})
|
|
29
|
+
|
|
30
|
+
it('不传 bgShape fallback 走 square(rounded-none)', () => {
|
|
31
|
+
const { container } = render(<UserAvatar name="张三" />)
|
|
32
|
+
expect(container.firstElementChild?.className).toContain('rounded-none')
|
|
33
|
+
})
|
|
34
|
+
|
|
35
|
+
it('非法 bgShape 不崩溃,静默归 square(rounded-none)', () => {
|
|
36
|
+
const { container } = render(<UserAvatar name="张三" bgShape="weird-shape" />)
|
|
37
|
+
expect(container.firstElementChild?.className).toContain('rounded-none')
|
|
38
|
+
})
|
|
39
|
+
})
|
|
@@ -28,7 +28,7 @@ import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip
|
|
|
28
28
|
import { useRouter, useMatches } from "@tanstack/react-router";
|
|
29
29
|
import { signOut, useSession } from "@/lib/auth-client";
|
|
30
30
|
import { UserAvatar } from "@/components/user-avatar";
|
|
31
|
-
import type { AvatarConfig
|
|
31
|
+
import type { AvatarConfig } from "@allbluecn/kernel";
|
|
32
32
|
|
|
33
33
|
const ROLE_LABELS: Record<string, () => string> = {
|
|
34
34
|
ADMIN: m.common_common_role_ADMIN,
|
|
@@ -53,7 +53,7 @@ export function AdminUserMenu() {
|
|
|
53
53
|
const userRole = (user as { role?: string })?.role || "ADMIN";
|
|
54
54
|
const roleLabel = (ROLE_LABELS[userRole] ?? (() => m.common_login_admin()))();
|
|
55
55
|
const avatarConfig = (user as { avatarConfig?: AvatarConfig })?.avatarConfig;
|
|
56
|
-
const bgShape = (user as { bgShape?: string })?.bgShape
|
|
56
|
+
const bgShape = (user as { bgShape?: string })?.bgShape;
|
|
57
57
|
const bgColor = (user as { bgColor?: string })?.bgColor;
|
|
58
58
|
|
|
59
59
|
const handleExitAdmin = async () => {
|
|
@@ -25,7 +25,7 @@ import { Field, FieldLabel } from "@/components/ui/field";
|
|
|
25
25
|
import { Card, CardContent } from "@/components/ui/card";
|
|
26
26
|
import { toast } from '@/components/ui/toast';
|
|
27
27
|
import { UserAvatar } from "@/components/user-avatar";
|
|
28
|
-
import type { AvatarConfig
|
|
28
|
+
import type { AvatarConfig } from "@allbluecn/kernel";
|
|
29
29
|
import { AuthLayout, AuthLayoutLeft, AuthLayoutRight } from "@/components/auth/auth-layout";
|
|
30
30
|
import { AuthHeader } from "@/components/auth/auth-header";
|
|
31
31
|
import { AuthBrandPanel } from "@/components/auth/auth-brand-panel";
|
|
@@ -126,7 +126,7 @@ export default function AdminLoginForm({ user, isAdmin }: AdminLoginFormProps) {
|
|
|
126
126
|
<CardContent className="flex h-full items-center gap-3 px-3 py-3">
|
|
127
127
|
<UserAvatar
|
|
128
128
|
avatarConfig={user?.avatarConfig}
|
|
129
|
-
bgShape={user?.bgShape
|
|
129
|
+
bgShape={user?.bgShape}
|
|
130
130
|
bgColor={user?.bgColor}
|
|
131
131
|
name={user?.name || user?.email || ""}
|
|
132
132
|
size="lg"
|
|
@@ -29,9 +29,9 @@ import {
|
|
|
29
29
|
DialogHeader,
|
|
30
30
|
DialogTitle,
|
|
31
31
|
} from '@/components/ui/dialog'
|
|
32
|
-
import { adminCreateBookmarkFn } from '@/server/serverFns/knowledge
|
|
33
|
-
import { adminUpdateBookmarkFn } from '@/server/serverFns/knowledge
|
|
34
|
-
import { adminGetBookmarkDetailFn } from '@/server/serverFns/knowledge
|
|
32
|
+
import { adminCreateBookmarkFn } from '@/server/serverFns/knowledge/public/bookmarks/create'
|
|
33
|
+
import { adminUpdateBookmarkFn } from '@/server/serverFns/knowledge/public/bookmarks/update'
|
|
34
|
+
import { adminGetBookmarkDetailFn } from '@/server/serverFns/knowledge/public/bookmarks/detail'
|
|
35
35
|
|
|
36
36
|
export interface BookmarkRow {
|
|
37
37
|
id: string
|
|
@@ -80,7 +80,7 @@ export function BookmarkFormDialog({ open, onOpenChange, item, onSuccess }: Book
|
|
|
80
80
|
if (item) {
|
|
81
81
|
setLoading(true)
|
|
82
82
|
adminGetBookmarkDetailFn({ data: { id: item.id } })
|
|
83
|
-
.then((d:
|
|
83
|
+
.then((d: Awaited<ReturnType<typeof adminGetBookmarkDetailFn>>) => {
|
|
84
84
|
setForm({
|
|
85
85
|
title: d.title ?? '',
|
|
86
86
|
url: d.url ?? '',
|
|
@@ -29,9 +29,9 @@ import {
|
|
|
29
29
|
DialogHeader,
|
|
30
30
|
DialogTitle,
|
|
31
31
|
} from '@/components/ui/dialog'
|
|
32
|
-
import { adminCreateComplianceFn } from '@/server/serverFns/knowledge
|
|
33
|
-
import { adminUpdateComplianceFn } from '@/server/serverFns/knowledge
|
|
34
|
-
import { adminGetComplianceDetailFn } from '@/server/serverFns/knowledge
|
|
32
|
+
import { adminCreateComplianceFn } from '@/server/serverFns/knowledge/public/compliance/create'
|
|
33
|
+
import { adminUpdateComplianceFn } from '@/server/serverFns/knowledge/public/compliance/update'
|
|
34
|
+
import { adminGetComplianceDetailFn } from '@/server/serverFns/knowledge/public/compliance/detail'
|
|
35
35
|
|
|
36
36
|
export interface ComplianceRow {
|
|
37
37
|
id: string
|
|
@@ -80,7 +80,7 @@ export function ComplianceFormDialog({ open, onOpenChange, item, onSuccess }: Co
|
|
|
80
80
|
if (item) {
|
|
81
81
|
setLoading(true)
|
|
82
82
|
adminGetComplianceDetailFn({ data: { id: item.id } })
|
|
83
|
-
.then((d:
|
|
83
|
+
.then((d: Awaited<ReturnType<typeof adminGetComplianceDetailFn>>) => {
|
|
84
84
|
setForm({
|
|
85
85
|
title: d.title ?? '',
|
|
86
86
|
code: '',
|
|
@@ -37,9 +37,9 @@ import {
|
|
|
37
37
|
DialogHeader,
|
|
38
38
|
DialogTitle,
|
|
39
39
|
} from '@/components/ui/dialog'
|
|
40
|
-
import { adminCreateLegalFn } from '@/server/serverFns/knowledge
|
|
41
|
-
import { adminUpdateLegalFn } from '@/server/serverFns/knowledge
|
|
42
|
-
import { adminGetLegalDetailFn } from '@/server/serverFns/knowledge
|
|
40
|
+
import { adminCreateLegalFn } from '@/server/serverFns/knowledge/public/legal/create'
|
|
41
|
+
import { adminUpdateLegalFn } from '@/server/serverFns/knowledge/public/legal/update'
|
|
42
|
+
import { adminGetLegalDetailFn } from '@/server/serverFns/knowledge/public/legal/detail'
|
|
43
43
|
|
|
44
44
|
export interface LegalRow {
|
|
45
45
|
id: string
|
|
@@ -106,7 +106,7 @@ export function LegalFormDialog({ open, onOpenChange, item, onSuccess }: LegalFo
|
|
|
106
106
|
if (item) {
|
|
107
107
|
setLoading(true)
|
|
108
108
|
adminGetLegalDetailFn({ data: { id: item.id } })
|
|
109
|
-
.then((d:
|
|
109
|
+
.then((d: Awaited<ReturnType<typeof adminGetLegalDetailFn>>) => {
|
|
110
110
|
setForm({
|
|
111
111
|
title: d.title ?? '',
|
|
112
112
|
code: '',
|
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
|
|
2
2
|
import { Checkbox as CheckboxPrimitive } from "@base-ui/react/checkbox"
|
|
3
3
|
import { cn } from "cn"
|
|
4
|
-
import { CheckIcon } from "lucide-react"
|
|
4
|
+
import { CheckIcon, MinusIcon } from "lucide-react"
|
|
5
5
|
|
|
6
|
-
function Checkbox({ className, ...props }: CheckboxPrimitive.Root.Props) {
|
|
6
|
+
function Checkbox({ className, indeterminate, ...props }: CheckboxPrimitive.Root.Props) {
|
|
7
7
|
return (
|
|
8
8
|
<CheckboxPrimitive.Root
|
|
9
9
|
data-slot="checkbox"
|
|
10
|
+
indeterminate={indeterminate}
|
|
10
11
|
className={cn(
|
|
11
12
|
"peer relative flex size-4 shrink-0 items-center justify-center rounded-[4px] border border-input shadow-xs transition-shadow outline-none group-has-disabled/field:opacity-50 group-has-[:focus-visible]/field-label:ring-0 group-has-[:focus-visible]/field-label:not-data-checked:border-input after:absolute after:-inset-x-3 after:-inset-y-2 focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 disabled:cursor-not-allowed disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 aria-invalid:aria-checked:border-primary dark:bg-input/30 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 data-checked:border-primary data-checked:bg-primary data-checked:text-primary-foreground group-has-[:focus-visible]/field-label:data-checked:border-primary dark:data-checked:bg-primary",
|
|
12
13
|
className
|
|
@@ -17,8 +18,7 @@ function Checkbox({ className, ...props }: CheckboxPrimitive.Root.Props) {
|
|
|
17
18
|
data-slot="checkbox-indicator"
|
|
18
19
|
className="grid place-content-center text-current transition-none [&>svg]:size-3.5"
|
|
19
20
|
>
|
|
20
|
-
<CheckIcon
|
|
21
|
-
/>
|
|
21
|
+
{indeterminate ? <MinusIcon /> : <CheckIcon />}
|
|
22
22
|
</CheckboxPrimitive.Indicator>
|
|
23
23
|
</CheckboxPrimitive.Root>
|
|
24
24
|
)
|
|
@@ -79,7 +79,7 @@ export function getUserColumns(onView: (user: UserRow) => void): AdminColumnDef<
|
|
|
79
79
|
<div className="flex items-center gap-2">
|
|
80
80
|
<UserAvatar
|
|
81
81
|
avatarConfig={user.avatarConfig as AvatarConfig | null}
|
|
82
|
-
bgShape={user.bgShape
|
|
82
|
+
bgShape={user.bgShape}
|
|
83
83
|
bgColor={user.bgColor}
|
|
84
84
|
name={user.username}
|
|
85
85
|
size="md"
|
|
@@ -17,11 +17,13 @@
|
|
|
17
17
|
|
|
18
18
|
|
|
19
19
|
import { NotionAvatar } from "./notion-avatar"
|
|
20
|
-
import
|
|
20
|
+
import { toAvatarShape } from "@/lib/avatar-shape"
|
|
21
|
+
import type { AvatarConfig } from "@allbluecn/kernel"
|
|
21
22
|
|
|
22
23
|
type UserAvatarProps = {
|
|
23
24
|
avatarConfig?: AvatarConfig | null
|
|
24
|
-
|
|
25
|
+
/** DB 字典直通的 string|null,内部经 toAvatarShape 归一,非法值回落 square */
|
|
26
|
+
bgShape?: string | null
|
|
25
27
|
bgColor?: string | null
|
|
26
28
|
name?: string | null
|
|
27
29
|
imageUrl?: string | null
|
|
@@ -50,14 +52,15 @@ export function UserAvatar({
|
|
|
50
52
|
size = "md",
|
|
51
53
|
}: UserAvatarProps) {
|
|
52
54
|
const initial = (name || "U").charAt(0).toUpperCase()
|
|
55
|
+
const shape = toAvatarShape(bgShape) ?? "square"
|
|
53
56
|
const shapeClass =
|
|
54
|
-
|
|
57
|
+
shape === "circle" ? "rounded-full" : shape === "square" ? "rounded-none" : "rounded-lg"
|
|
55
58
|
|
|
56
59
|
if (avatarConfig) {
|
|
57
60
|
return (
|
|
58
61
|
<NotionAvatar
|
|
59
62
|
config={avatarConfig}
|
|
60
|
-
shape={
|
|
63
|
+
shape={shape}
|
|
61
64
|
bgColor={bgColor ?? "#fff"}
|
|
62
65
|
className={`${sizeClasses[size]} shrink-0 ${className ?? ""}`}
|
|
63
66
|
/>
|
|
@@ -0,0 +1,44 @@
|
|
|
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 { describe, it, expect } from "vitest";
|
|
19
|
+
import { AVATAR_SHAPES, toAvatarShape } from "../avatar-shape";
|
|
20
|
+
|
|
21
|
+
describe("AVATAR_SHAPES", () => {
|
|
22
|
+
it("与 kernel ShapeType 三值一致", () => {
|
|
23
|
+
expect([...AVATAR_SHAPES]).toEqual(["circle", "rounded", "square"]);
|
|
24
|
+
});
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
describe("toAvatarShape", () => {
|
|
28
|
+
it("三个合法值原样通过", () => {
|
|
29
|
+
expect(toAvatarShape("circle")).toBe("circle");
|
|
30
|
+
expect(toAvatarShape("rounded")).toBe("rounded");
|
|
31
|
+
expect(toAvatarShape("square")).toBe("square");
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
it("非法字符串返回 null", () => {
|
|
35
|
+
expect(toAvatarShape("weird")).toBeNull();
|
|
36
|
+
expect(toAvatarShape("CIRCLE")).toBeNull();
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
it("空串与空值返回 null", () => {
|
|
40
|
+
expect(toAvatarShape("")).toBeNull();
|
|
41
|
+
expect(toAvatarShape(null)).toBeNull();
|
|
42
|
+
expect(toAvatarShape(undefined)).toBeNull();
|
|
43
|
+
});
|
|
44
|
+
});
|
package/src/lib/auth.config.ts
CHANGED
|
@@ -257,7 +257,7 @@ export const authConfig: StartAuthJSConfig = {
|
|
|
257
257
|
user.role = token.role as string
|
|
258
258
|
user.id = token.id as string
|
|
259
259
|
user.avatarConfig = token.avatarConfig as Record<string, number> | undefined
|
|
260
|
-
user.bgShape = token.bgShape
|
|
260
|
+
user.bgShape = typeof token.bgShape === "string" ? token.bgShape : undefined
|
|
261
261
|
user.bgColor = token.bgColor as string | undefined
|
|
262
262
|
if (token.name) user.name = token.name as string | null
|
|
263
263
|
if (token.email) user.email = token.email as string | null
|
|
@@ -0,0 +1,35 @@
|
|
|
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 type { ShapeType } from "@allbluecn/kernel"
|
|
19
|
+
|
|
20
|
+
export const AVATAR_SHAPES = [
|
|
21
|
+
"circle",
|
|
22
|
+
"rounded",
|
|
23
|
+
"square",
|
|
24
|
+
// satisfies 只防 kernel 收窄方向漂移(本数组塞进非 ShapeType 值会报错);
|
|
25
|
+
// kernel 新增形状值不会报错,会在 toAvatarShape 中静默归 null
|
|
26
|
+
] as const satisfies readonly ShapeType[]
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* UserBrief.bgShape 是 DB 字典直通的 string|null,UI 底形只认 ShapeType:
|
|
30
|
+
* 运行时守卫替代断言撒谎型 cast,非法/空值返回 null(由消费端决定回落)。
|
|
31
|
+
* 与 web 端 shared-kit/avatars/avatar-shape.ts 同构(双端 UI 不共享,各自维护)。
|
|
32
|
+
*/
|
|
33
|
+
export function toAvatarShape(v: string | null | undefined): ShapeType | null {
|
|
34
|
+
return AVATAR_SHAPES.find((shape) => shape === v) ?? null
|
|
35
|
+
}
|
|
@@ -46,9 +46,9 @@ import { toast } from '@/components/ui/toast'
|
|
|
46
46
|
import {
|
|
47
47
|
BookmarkFormDialog,
|
|
48
48
|
type BookmarkRow,
|
|
49
|
-
} from '@/components/knowledge
|
|
50
|
-
import { adminDeleteBookmarkFn } from '@/server/serverFns/knowledge
|
|
51
|
-
import { adminSyncAllGithubFn } from '@/server/serverFns/knowledge
|
|
49
|
+
} from '@/components/knowledge/public/bookmark-form-dialog'
|
|
50
|
+
import { adminDeleteBookmarkFn } from '@/server/serverFns/knowledge/public/bookmarks/delete'
|
|
51
|
+
import { adminSyncAllGithubFn } from '@/server/serverFns/knowledge/public/bookmarks/sync-all-github'
|
|
52
52
|
|
|
53
53
|
export const Route = createLazyFileRoute('/_nav/knowledge-public/bookmarks')({
|
|
54
54
|
pendingComponent: () => (
|
|
@@ -144,7 +144,7 @@ function BookmarksPage() {
|
|
|
144
144
|
{
|
|
145
145
|
accessorKey: 'title',
|
|
146
146
|
header: m.common_knowledgePublic_bookmarks_columns_website(),
|
|
147
|
-
cell: ({ row }
|
|
147
|
+
cell: ({ row }) => {
|
|
148
148
|
const b = row.original
|
|
149
149
|
return (
|
|
150
150
|
<div className="flex min-w-[180px] items-center gap-2">
|
|
@@ -164,7 +164,7 @@ function BookmarksPage() {
|
|
|
164
164
|
{
|
|
165
165
|
accessorKey: 'url',
|
|
166
166
|
header: m.common_knowledgePublic_bookmarks_columns_url(),
|
|
167
|
-
cell: ({ row }
|
|
167
|
+
cell: ({ row }) => {
|
|
168
168
|
const url = row.original.url
|
|
169
169
|
return (
|
|
170
170
|
<a
|
|
@@ -182,7 +182,7 @@ function BookmarksPage() {
|
|
|
182
182
|
{
|
|
183
183
|
accessorKey: 'categoryId',
|
|
184
184
|
header: m.common_knowledgePublic_bookmarks_columns_category(),
|
|
185
|
-
cell: ({ row }
|
|
185
|
+
cell: ({ row }) =>
|
|
186
186
|
row.original.categoryId ? (
|
|
187
187
|
<Badge variant="outline" className="font-mono text-xs">
|
|
188
188
|
{row.original.categoryId}
|
|
@@ -194,7 +194,7 @@ function BookmarksPage() {
|
|
|
194
194
|
{
|
|
195
195
|
id: 'github',
|
|
196
196
|
header: 'GitHub',
|
|
197
|
-
cell: ({ row }
|
|
197
|
+
cell: ({ row }) => {
|
|
198
198
|
const b = row.original
|
|
199
199
|
if (!b.isGithub) return <span className="text-muted-foreground">-</span>
|
|
200
200
|
return (
|
|
@@ -215,7 +215,7 @@ function BookmarksPage() {
|
|
|
215
215
|
{
|
|
216
216
|
accessorKey: 'tags',
|
|
217
217
|
header: m.common_knowledgePublic_bookmarks_columns_tags(),
|
|
218
|
-
cell: ({ row }
|
|
218
|
+
cell: ({ row }) => (
|
|
219
219
|
<div className="flex max-w-xs flex-wrap gap-1">
|
|
220
220
|
{row.original.tags.length === 0 ? (
|
|
221
221
|
<span className="text-muted-foreground">-</span>
|
|
@@ -232,7 +232,7 @@ function BookmarksPage() {
|
|
|
232
232
|
{
|
|
233
233
|
accessorKey: 'createdAt',
|
|
234
234
|
header: m.common_knowledgePublic_bookmarks_columns_createdAt(),
|
|
235
|
-
cell: ({ row }
|
|
235
|
+
cell: ({ row }) => (
|
|
236
236
|
<span className="text-muted-foreground">
|
|
237
237
|
{new Date(row.original.createdAt).toLocaleDateString('zh-CN')}
|
|
238
238
|
</span>
|
|
@@ -241,7 +241,7 @@ function BookmarksPage() {
|
|
|
241
241
|
{
|
|
242
242
|
id: 'actions',
|
|
243
243
|
header: '',
|
|
244
|
-
cell: ({ row }
|
|
244
|
+
cell: ({ row }) => (
|
|
245
245
|
<div className="flex items-center gap-1">
|
|
246
246
|
<Button
|
|
247
247
|
variant="ghost"
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
|
|
18
18
|
import { createFileRoute } from '@tanstack/react-router'
|
|
19
19
|
import { z } from 'zod'
|
|
20
|
-
import { adminListBookmarksFn } from '@/server/serverFns/knowledge
|
|
20
|
+
import { adminListBookmarksFn } from '@/server/serverFns/knowledge/public/bookmarks/list'
|
|
21
21
|
|
|
22
22
|
const bookmarkSearchSchema = z.object({
|
|
23
23
|
search: z.string().optional(),
|
|
@@ -28,8 +28,8 @@ import { toast } from '@/components/ui/toast'
|
|
|
28
28
|
import {
|
|
29
29
|
ComplianceFormDialog,
|
|
30
30
|
type ComplianceRow,
|
|
31
|
-
} from '@/components/knowledge
|
|
32
|
-
import { adminDeleteComplianceFn } from '@/server/serverFns/knowledge
|
|
31
|
+
} from '@/components/knowledge/public/compliance-form-dialog'
|
|
32
|
+
import { adminDeleteComplianceFn } from '@/server/serverFns/knowledge/public/compliance/delete'
|
|
33
33
|
|
|
34
34
|
export const Route = createLazyFileRoute('/_nav/knowledge-public/compliance')({
|
|
35
35
|
pendingComponent: () => (
|
|
@@ -90,19 +90,19 @@ function CompliancePage() {
|
|
|
90
90
|
{
|
|
91
91
|
accessorKey: 'title',
|
|
92
92
|
header: m.common_knowledgePublic_compliance_columns_title(),
|
|
93
|
-
cell: ({ row }
|
|
93
|
+
cell: ({ row }) => <span className="font-medium">{row.original.title}</span>,
|
|
94
94
|
},
|
|
95
95
|
{
|
|
96
96
|
accessorKey: 'industry',
|
|
97
97
|
header: m.common_knowledgePublic_compliance_columns_industry(),
|
|
98
|
-
cell: ({ row }
|
|
98
|
+
cell: ({ row }) => (
|
|
99
99
|
<Badge variant="outline">{row.original.industry}</Badge>
|
|
100
100
|
),
|
|
101
101
|
},
|
|
102
102
|
{
|
|
103
103
|
accessorKey: 'regulatoryBody',
|
|
104
104
|
header: m.common_knowledgePublic_compliance_columns_regulatoryBody(),
|
|
105
|
-
cell: ({ row }
|
|
105
|
+
cell: ({ row }) => (
|
|
106
106
|
<span className="text-muted-foreground">
|
|
107
107
|
{row.original.regulatoryBody ?? '-'}
|
|
108
108
|
</span>
|
|
@@ -111,7 +111,7 @@ function CompliancePage() {
|
|
|
111
111
|
{
|
|
112
112
|
accessorKey: 'tags',
|
|
113
113
|
header: m.common_knowledgePublic_compliance_columns_tags(),
|
|
114
|
-
cell: ({ row }
|
|
114
|
+
cell: ({ row }) => (
|
|
115
115
|
<div className="flex max-w-xs flex-wrap gap-1">
|
|
116
116
|
{row.original.tags.length === 0 ? (
|
|
117
117
|
<span className="text-muted-foreground">-</span>
|
|
@@ -128,7 +128,7 @@ function CompliancePage() {
|
|
|
128
128
|
{
|
|
129
129
|
accessorKey: 'createdAt',
|
|
130
130
|
header: m.common_knowledgePublic_compliance_columns_createdAt(),
|
|
131
|
-
cell: ({ row }
|
|
131
|
+
cell: ({ row }) => (
|
|
132
132
|
<span className="text-muted-foreground">
|
|
133
133
|
{new Date(row.original.createdAt).toLocaleDateString('zh-CN')}
|
|
134
134
|
</span>
|
|
@@ -137,7 +137,7 @@ function CompliancePage() {
|
|
|
137
137
|
{
|
|
138
138
|
id: 'actions',
|
|
139
139
|
header: '',
|
|
140
|
-
cell: ({ row }
|
|
140
|
+
cell: ({ row }) => (
|
|
141
141
|
<div className="flex items-center gap-1">
|
|
142
142
|
<Button
|
|
143
143
|
variant="ghost"
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
|
|
18
18
|
import { createFileRoute } from '@tanstack/react-router'
|
|
19
19
|
import { z } from 'zod'
|
|
20
|
-
import { adminListComplianceFn } from '@/server/serverFns/knowledge
|
|
20
|
+
import { adminListComplianceFn } from '@/server/serverFns/knowledge/public/compliance/list'
|
|
21
21
|
|
|
22
22
|
const complianceSearchSchema = z.object({
|
|
23
23
|
search: z.string().optional(),
|
|
@@ -34,8 +34,8 @@ import {
|
|
|
34
34
|
AlertDialogTitle,
|
|
35
35
|
} from '@/components/ui/alert-dialog'
|
|
36
36
|
import { toast } from '@/components/ui/toast'
|
|
37
|
-
import { LegalFormDialog, type LegalRow } from '@/components/knowledge
|
|
38
|
-
import { adminDeleteLegalFn } from '@/server/serverFns/knowledge
|
|
37
|
+
import { LegalFormDialog, type LegalRow } from '@/components/knowledge/public/legal-form-dialog'
|
|
38
|
+
import { adminDeleteLegalFn } from '@/server/serverFns/knowledge/public/legal/delete'
|
|
39
39
|
|
|
40
40
|
export const Route = createLazyFileRoute('/_nav/knowledge-public/legal')({
|
|
41
41
|
pendingComponent: () => (
|
|
@@ -96,12 +96,12 @@ function LegalPage() {
|
|
|
96
96
|
{
|
|
97
97
|
accessorKey: 'title',
|
|
98
98
|
header: m.common_knowledgePublic_legal_columns_title(),
|
|
99
|
-
cell: ({ row }
|
|
99
|
+
cell: ({ row }) => <span className="font-medium">{row.original.title}</span>,
|
|
100
100
|
},
|
|
101
101
|
{
|
|
102
102
|
accessorKey: 'jurisdiction',
|
|
103
103
|
header: m.common_knowledgePublic_legal_columns_jurisdiction(),
|
|
104
|
-
cell: ({ row }
|
|
104
|
+
cell: ({ row }) => (
|
|
105
105
|
<Badge variant="outline">
|
|
106
106
|
{row.original.jurisdiction}
|
|
107
107
|
</Badge>
|
|
@@ -110,14 +110,14 @@ function LegalPage() {
|
|
|
110
110
|
{
|
|
111
111
|
accessorKey: 'category',
|
|
112
112
|
header: m.common_knowledgePublic_legal_columns_category(),
|
|
113
|
-
cell: ({ row }
|
|
113
|
+
cell: ({ row }) => (
|
|
114
114
|
<span className="text-muted-foreground">{row.original.category}</span>
|
|
115
115
|
),
|
|
116
116
|
},
|
|
117
117
|
{
|
|
118
118
|
accessorKey: 'tags',
|
|
119
119
|
header: m.common_knowledgePublic_legal_columns_tags(),
|
|
120
|
-
cell: ({ row }
|
|
120
|
+
cell: ({ row }) => (
|
|
121
121
|
<div className="flex max-w-xs flex-wrap gap-1">
|
|
122
122
|
{row.original.tags.length === 0 ? (
|
|
123
123
|
<span className="text-muted-foreground">-</span>
|
|
@@ -134,7 +134,7 @@ function LegalPage() {
|
|
|
134
134
|
{
|
|
135
135
|
accessorKey: 'createdAt',
|
|
136
136
|
header: m.common_knowledgePublic_legal_columns_createdAt(),
|
|
137
|
-
cell: ({ row }
|
|
137
|
+
cell: ({ row }) => (
|
|
138
138
|
<span className="text-muted-foreground">
|
|
139
139
|
{new Date(row.original.createdAt).toLocaleDateString('zh-CN')}
|
|
140
140
|
</span>
|
|
@@ -143,7 +143,7 @@ function LegalPage() {
|
|
|
143
143
|
{
|
|
144
144
|
id: 'actions',
|
|
145
145
|
header: '',
|
|
146
|
-
cell: ({ row }
|
|
146
|
+
cell: ({ row }) => (
|
|
147
147
|
<div className="flex items-center gap-1">
|
|
148
148
|
<Button
|
|
149
149
|
variant="ghost"
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
|
|
18
18
|
import { createFileRoute } from '@tanstack/react-router'
|
|
19
19
|
import { z } from 'zod'
|
|
20
|
-
import { adminListLegalFn } from '@/server/serverFns/knowledge
|
|
20
|
+
import { adminListLegalFn } from '@/server/serverFns/knowledge/public/legal/list'
|
|
21
21
|
|
|
22
22
|
const legalSearchSchema = z.object({
|
|
23
23
|
search: z.string().optional(),
|
|
@@ -132,7 +132,7 @@ function UserDetailPage() {
|
|
|
132
132
|
avatar={
|
|
133
133
|
<UserAvatar
|
|
134
134
|
avatarConfig={user.avatarConfig as AvatarConfig | null}
|
|
135
|
-
bgShape={user.bgShape
|
|
135
|
+
bgShape={user.bgShape}
|
|
136
136
|
bgColor={user.bgColor}
|
|
137
137
|
name={user.username}
|
|
138
138
|
size="lg"
|
|
@@ -34,7 +34,7 @@ function AdminLoginPage() {
|
|
|
34
34
|
name: session.user.name,
|
|
35
35
|
email: session.user.email,
|
|
36
36
|
avatarConfig: (session.user as any).avatarConfig as AvatarConfig | undefined,
|
|
37
|
-
bgShape: (session.user as
|
|
37
|
+
bgShape: (session.user as { bgShape?: string | null }).bgShape ?? undefined,
|
|
38
38
|
bgColor: (session.user as any).bgColor as string | undefined,
|
|
39
39
|
}
|
|
40
40
|
: null
|
|
@@ -24,6 +24,16 @@ export interface GithubData {
|
|
|
24
24
|
versionDate: Date | null
|
|
25
25
|
}
|
|
26
26
|
|
|
27
|
+
interface GithubRepoJson {
|
|
28
|
+
stargazers_count?: number
|
|
29
|
+
updated_at?: string
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
interface GithubReleaseJson {
|
|
33
|
+
tag_name?: string
|
|
34
|
+
published_at?: string
|
|
35
|
+
}
|
|
36
|
+
|
|
27
37
|
const API = 'https://api.github.com'
|
|
28
38
|
const HEADERS: HeadersInit = {
|
|
29
39
|
Accept: 'application/vnd.github+json',
|
|
@@ -39,14 +49,14 @@ export async function fetchGithubData(
|
|
|
39
49
|
): Promise<GithubData | null> {
|
|
40
50
|
const repoRes = await fetch(`${API}/repos/${owner}/${repo}`, { headers: HEADERS })
|
|
41
51
|
if (!repoRes.ok) return null
|
|
42
|
-
const repoJson:
|
|
52
|
+
const repoJson: GithubRepoJson = await repoRes.json()
|
|
43
53
|
|
|
44
54
|
let latestVersion: string | null = null
|
|
45
55
|
let versionDate: Date | null = null
|
|
46
56
|
try {
|
|
47
57
|
const relRes = await fetch(`${API}/repos/${owner}/${repo}/releases/latest`, { headers: HEADERS })
|
|
48
58
|
if (relRes.ok) {
|
|
49
|
-
const relJson:
|
|
59
|
+
const relJson: GithubReleaseJson = await relRes.json()
|
|
50
60
|
latestVersion = relJson.tag_name ?? null
|
|
51
61
|
versionDate = relJson.published_at ? new Date(relJson.published_at) : null
|
|
52
62
|
}
|
|
@@ -19,7 +19,7 @@ import { createServerFn } from '@tanstack/react-start'
|
|
|
19
19
|
import { z } from 'zod'
|
|
20
20
|
import { getPrisma } from '@allbluecn/database/get-prisma'
|
|
21
21
|
import { requireAdmin } from '@/server/auth-helpers'
|
|
22
|
-
import { bookmarkCanonicalId, parseGithubUrl } from '@/server/knowledge
|
|
22
|
+
import { bookmarkCanonicalId, parseGithubUrl } from '@/server/knowledge/public/canonical-id'
|
|
23
23
|
|
|
24
24
|
const createSchema = z.object({
|
|
25
25
|
title: z.string().min(1).max(200),
|
|
@@ -18,6 +18,7 @@
|
|
|
18
18
|
import { createServerFn } from '@tanstack/react-start'
|
|
19
19
|
import { z } from 'zod'
|
|
20
20
|
import { getPrisma } from '@allbluecn/database/get-prisma'
|
|
21
|
+
import { Prisma } from '@allbluecn/database'
|
|
21
22
|
import { requireAdmin } from '@/server/auth-helpers'
|
|
22
23
|
|
|
23
24
|
const listSchema = z.object({
|
|
@@ -33,7 +34,7 @@ export const adminListBookmarksFn = createServerFn({ method: 'GET' })
|
|
|
33
34
|
.handler(async ({ data }) => {
|
|
34
35
|
const prisma = await getPrisma()
|
|
35
36
|
await requireAdmin()
|
|
36
|
-
const where:
|
|
37
|
+
const where: Prisma.WebsiteBookmarkWhereInput = { visibility: 'public', isDeleted: false }
|
|
37
38
|
if (data.search) {
|
|
38
39
|
where.OR = [
|
|
39
40
|
{ title: { contains: data.search, mode: 'insensitive' } },
|
package/src/server/serverFns/{knowledge-public → knowledge/public}/bookmarks/sync-all-github.ts
RENAMED
|
@@ -18,8 +18,9 @@
|
|
|
18
18
|
import { createServerFn } from '@tanstack/react-start'
|
|
19
19
|
import { z } from 'zod'
|
|
20
20
|
import { getPrisma } from '@allbluecn/database/get-prisma'
|
|
21
|
+
import { Prisma } from '@allbluecn/database'
|
|
21
22
|
import { requireAdmin } from '@/server/auth-helpers'
|
|
22
|
-
import { syncBookmarkGithub } from '@/server/knowledge
|
|
23
|
+
import { syncBookmarkGithub } from '@/server/knowledge/public/github-sync'
|
|
23
24
|
|
|
24
25
|
const syncSchema = z.object({
|
|
25
26
|
force: z.boolean().default(false),
|
|
@@ -30,7 +31,7 @@ export const adminSyncAllGithubFn = createServerFn({ method: 'POST' })
|
|
|
30
31
|
.handler(async ({ data }) => {
|
|
31
32
|
const prisma = await getPrisma()
|
|
32
33
|
await requireAdmin()
|
|
33
|
-
const where:
|
|
34
|
+
const where: Prisma.WebsiteBookmarkWhereInput = { isGithub: true, isDeleted: false }
|
|
34
35
|
if (!data.force) {
|
|
35
36
|
const sixHoursAgo = new Date(Date.now() - 6 * 60 * 60 * 1000)
|
|
36
37
|
where.OR = [
|
|
@@ -18,8 +18,9 @@
|
|
|
18
18
|
import { createServerFn } from '@tanstack/react-start'
|
|
19
19
|
import { z } from 'zod'
|
|
20
20
|
import { getPrisma } from '@allbluecn/database/get-prisma'
|
|
21
|
+
import { Prisma } from '@allbluecn/database'
|
|
21
22
|
import { requireAdmin } from '@/server/auth-helpers'
|
|
22
|
-
import { bookmarkCanonicalId, parseGithubUrl } from '@/server/knowledge
|
|
23
|
+
import { bookmarkCanonicalId, parseGithubUrl } from '@/server/knowledge/public/canonical-id'
|
|
23
24
|
|
|
24
25
|
const updateSchema = z.object({
|
|
25
26
|
id: z.string(),
|
|
@@ -38,7 +39,7 @@ export const adminUpdateBookmarkFn = createServerFn({ method: 'POST' })
|
|
|
38
39
|
const { id, ...rest } = data
|
|
39
40
|
const existing = await prisma.websiteBookmark.findUnique({ where: { id } })
|
|
40
41
|
if (!existing) throw new Error('未找到')
|
|
41
|
-
const updateData:
|
|
42
|
+
const updateData: Prisma.WebsiteBookmarkUncheckedUpdateInput = { ...rest }
|
|
42
43
|
const urlChanged = data.url !== undefined && data.url !== existing.url
|
|
43
44
|
if (urlChanged && data.url) {
|
|
44
45
|
const gh = parseGithubUrl(data.url)
|
|
@@ -19,7 +19,7 @@ import { createServerFn } from '@tanstack/react-start'
|
|
|
19
19
|
import { z } from 'zod'
|
|
20
20
|
import { getPrisma } from '@allbluecn/database/get-prisma'
|
|
21
21
|
import { requireAdmin } from '@/server/auth-helpers'
|
|
22
|
-
import { complianceCanonicalId } from '@/server/knowledge
|
|
22
|
+
import { complianceCanonicalId } from '@/server/knowledge/public/canonical-id'
|
|
23
23
|
|
|
24
24
|
const createSchema = z.object({
|
|
25
25
|
title: z.string().min(1).max(200),
|
|
@@ -18,6 +18,7 @@
|
|
|
18
18
|
import { createServerFn } from '@tanstack/react-start'
|
|
19
19
|
import { z } from 'zod'
|
|
20
20
|
import { getPrisma } from '@allbluecn/database/get-prisma'
|
|
21
|
+
import { Prisma } from '@allbluecn/database'
|
|
21
22
|
import { requireAdmin } from '@/server/auth-helpers'
|
|
22
23
|
|
|
23
24
|
const listSchema = z.object({
|
|
@@ -33,7 +34,7 @@ export const adminListComplianceFn = createServerFn({ method: 'GET' })
|
|
|
33
34
|
.handler(async ({ data }) => {
|
|
34
35
|
const prisma = await getPrisma()
|
|
35
36
|
await requireAdmin()
|
|
36
|
-
const where:
|
|
37
|
+
const where: Prisma.IndustryComplianceWhereInput = { visibility: 'public', isDeleted: false }
|
|
37
38
|
if (data.search) {
|
|
38
39
|
where.OR = [
|
|
39
40
|
{ title: { contains: data.search, mode: 'insensitive' } },
|
|
@@ -18,8 +18,9 @@
|
|
|
18
18
|
import { createServerFn } from '@tanstack/react-start'
|
|
19
19
|
import { z } from 'zod'
|
|
20
20
|
import { getPrisma } from '@allbluecn/database/get-prisma'
|
|
21
|
+
import { Prisma } from '@allbluecn/database'
|
|
21
22
|
import { requireAdmin } from '@/server/auth-helpers'
|
|
22
|
-
import { complianceCanonicalId } from '@/server/knowledge
|
|
23
|
+
import { complianceCanonicalId } from '@/server/knowledge/public/canonical-id'
|
|
23
24
|
|
|
24
25
|
const updateSchema = z.object({
|
|
25
26
|
id: z.string(),
|
|
@@ -43,7 +44,7 @@ export const adminUpdateComplianceFn = createServerFn({ method: 'POST' })
|
|
|
43
44
|
const { id, code, ...rest } = data
|
|
44
45
|
const existing = await prisma.industryCompliance.findUnique({ where: { id } })
|
|
45
46
|
if (!existing) throw new Error('未找到')
|
|
46
|
-
const updateData:
|
|
47
|
+
const updateData: Prisma.IndustryComplianceUncheckedUpdateInput = { ...rest }
|
|
47
48
|
if (code !== undefined || data.title !== undefined) {
|
|
48
49
|
updateData.canonicalId = complianceCanonicalId({
|
|
49
50
|
title: data.title ?? existing.title,
|
|
@@ -19,7 +19,7 @@ import { createServerFn } from '@tanstack/react-start'
|
|
|
19
19
|
import { z } from 'zod'
|
|
20
20
|
import { getPrisma } from '@allbluecn/database/get-prisma'
|
|
21
21
|
import { requireAdmin } from '@/server/auth-helpers'
|
|
22
|
-
import { legalCanonicalId } from '@/server/knowledge
|
|
22
|
+
import { legalCanonicalId } from '@/server/knowledge/public/canonical-id'
|
|
23
23
|
|
|
24
24
|
const createSchema = z.object({
|
|
25
25
|
title: z.string().min(1).max(200),
|
|
@@ -18,6 +18,7 @@
|
|
|
18
18
|
import { createServerFn } from '@tanstack/react-start'
|
|
19
19
|
import { z } from 'zod'
|
|
20
20
|
import { getPrisma } from '@allbluecn/database/get-prisma'
|
|
21
|
+
import { Prisma } from '@allbluecn/database'
|
|
21
22
|
import { requireAdmin } from '@/server/auth-helpers'
|
|
22
23
|
|
|
23
24
|
const listSchema = z.object({
|
|
@@ -33,7 +34,7 @@ export const adminListLegalFn = createServerFn({ method: 'GET' })
|
|
|
33
34
|
.handler(async ({ data }) => {
|
|
34
35
|
const prisma = await getPrisma()
|
|
35
36
|
await requireAdmin()
|
|
36
|
-
const where:
|
|
37
|
+
const where: Prisma.LegalRegulationWhereInput = { visibility: 'public', isDeleted: false }
|
|
37
38
|
if (data.search) {
|
|
38
39
|
where.OR = [
|
|
39
40
|
{ title: { contains: data.search, mode: 'insensitive' } },
|
|
@@ -18,8 +18,9 @@
|
|
|
18
18
|
import { createServerFn } from '@tanstack/react-start'
|
|
19
19
|
import { z } from 'zod'
|
|
20
20
|
import { getPrisma } from '@allbluecn/database/get-prisma'
|
|
21
|
+
import { Prisma } from '@allbluecn/database'
|
|
21
22
|
import { requireAdmin } from '@/server/auth-helpers'
|
|
22
|
-
import { legalCanonicalId } from '@/server/knowledge
|
|
23
|
+
import { legalCanonicalId } from '@/server/knowledge/public/canonical-id'
|
|
23
24
|
|
|
24
25
|
const updateSchema = z.object({
|
|
25
26
|
id: z.string(),
|
|
@@ -45,7 +46,7 @@ export const adminUpdateLegalFn = createServerFn({ method: 'POST' })
|
|
|
45
46
|
const { id, code, ...rest } = data
|
|
46
47
|
const existing = await prisma.legalRegulation.findUnique({ where: { id } })
|
|
47
48
|
if (!existing) throw new Error('未找到')
|
|
48
|
-
const updateData:
|
|
49
|
+
const updateData: Prisma.LegalRegulationUncheckedUpdateInput = { ...rest }
|
|
49
50
|
if (code !== undefined || data.title !== undefined) {
|
|
50
51
|
updateData.canonicalId = legalCanonicalId({
|
|
51
52
|
title: data.title ?? existing.title,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|