@goplusvn/core 0.1.71 → 0.1.73
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 +2 -1
- package/src/auth/index.ts +5 -1
- package/src/branding/__tests__/identity.test.ts +101 -0
- package/src/branding/__tests__/logo-route.test.ts +143 -0
- package/src/branding/identity.ts +74 -0
- package/src/branding/index.ts +36 -0
- package/src/branding/logo-route.ts +249 -0
- package/src/branding/pwa.tsx +146 -0
- package/src/branding/types.ts +45 -0
- package/src/configs/entities/departments.config.ts +1 -0
- package/src/configs/entities/material-categories.config.ts +1 -0
- package/src/crud/components/crud-export-button.tsx +3 -2
- package/src/crud/components/crud-page.tsx +56 -25
- package/src/crud/components/crud-row-actions.tsx +16 -4
- package/src/crud/crud-route-handlers.test.ts +352 -0
- package/src/crud/crud-route-handlers.ts +260 -13
- package/src/crud/index.ts +3 -0
- package/src/crud/lib/coerce.test.ts +118 -0
- package/src/crud/lib/coerce.ts +95 -0
- package/src/crud/lib/crud-utils.ts +4 -0
- package/src/crud/lib/entity-endpoints.test.ts +41 -0
- package/src/crud/lib/entity-endpoints.ts +23 -0
- package/src/crud/lib/errors.ts +14 -0
- package/src/crud/lib/import-request.ts +99 -0
- package/src/crud/lib/mutation-builder.test.ts +114 -0
- package/src/crud/lib/mutation-builder.ts +27 -34
- package/src/crud/lib/permissions.test.ts +57 -0
- package/src/crud/lib/permissions.ts +25 -13
- package/src/crud/lib/query-builder.ts +13 -6
- package/src/crud/pages/entity-crud-page.tsx +11 -8
- package/src/crud/server-service.test.ts +112 -0
- package/src/crud/server-service.ts +52 -27
- package/src/crud/server.ts +11 -1
- package/src/guardrails/rules/design.ts +3 -2
- package/src/styles/base.css +16 -0
- package/src/types/index.ts +75 -10
- package/src/ui/data-display/__tests__/use-client-pagination.test.ts +59 -0
- package/src/ui/data-display/data-table/__tests__/data-table-row-memo.test.tsx +96 -0
- package/src/ui/data-display/data-table/data-table-context.tsx +5 -0
- package/src/ui/data-display/data-table/data-table.tsx +68 -23
- package/src/ui/data-display/data-table-pagination.tsx +25 -8
- package/src/ui/data-display/index.tsx +1 -0
- package/src/ui/data-display/use-client-pagination.ts +48 -0
- package/src/ui/errors/error-view.tsx +80 -87
- package/src/ui/errors/index.ts +6 -1
- package/src/ui/errors/not-found-view.tsx +89 -16
- package/src/ui/errors/status-page.tsx +132 -0
- package/src/ui/layout/logo.tsx +5 -0
- package/src/ui/pages/not-found.tsx +11 -27
- package/templates/starter-app/AGENTS.md +1 -1
- package/templates/starter-app/next.config.mjs +8 -0
- package/templates/starter-app/package.json +1 -0
- package/templates/starter-app/prisma/migrations/20260807115347_platform_pages/migration.sql +87 -0
- package/templates/starter-app/prisma/migrations/20260808003811_company_profile/migration.sql +25 -0
- package/templates/starter-app/public/images/illustrations/characters/character-01.svg +33 -0
- package/templates/starter-app/public/images/illustrations/characters/character-02.svg +30 -0
- package/templates/starter-app/public/images/illustrations/characters/character-03.svg +30 -0
- package/templates/starter-app/public/images/illustrations/characters/character-04.svg +27 -0
- package/templates/starter-app/public/images/logos/goeat_logo.png +0 -0
- package/templates/starter-app/scripts/rbac-sync.ts +49 -21
- package/templates/starter-app/src/__tests__/architecture.test.ts +3 -2
- package/templates/starter-app/src/app/[lang]/(main)/actions/page.tsx +4 -4
- package/templates/starter-app/src/app/[lang]/(main)/admin/system/cache/page.tsx +1 -2
- package/templates/starter-app/src/app/[lang]/(main)/admin/system/company/actions.ts +7 -5
- package/templates/starter-app/src/app/[lang]/(main)/admin/system/company/company-profile-form.tsx +38 -11
- package/templates/starter-app/src/app/[lang]/(main)/admin/system/company/page.tsx +2 -1
- package/templates/starter-app/src/app/[lang]/(main)/crud/[entity]/page.tsx +4 -2
- package/templates/starter-app/src/app/[lang]/(main)/layout.tsx +2 -1
- package/templates/starter-app/src/app/[lang]/(main)/page.tsx +4 -4
- package/templates/starter-app/src/app/[lang]/(main)/resources/page.tsx +5 -5
- package/templates/starter-app/src/app/[lang]/(main)/roles/[id]/edit/page.tsx +5 -2
- package/templates/starter-app/src/app/[lang]/(main)/roles/new/page.tsx +1 -0
- package/templates/starter-app/src/app/[lang]/(main)/roles/page.tsx +1 -0
- package/templates/starter-app/src/app/[lang]/(main)/user/profile/actions.ts +4 -2
- package/templates/starter-app/src/app/[lang]/(main)/user/profile/page.tsx +3 -1
- package/templates/starter-app/src/app/[lang]/(main)/user/profile/profile-client-page.tsx +13 -5
- package/templates/starter-app/src/app/[lang]/(main)/users/page.tsx +3 -2
- package/templates/starter-app/src/app/[lang]/layout.tsx +27 -2
- package/templates/starter-app/src/app/api/admin/system/audit/route.ts +4 -3
- package/templates/starter-app/src/app/api/admin/system/jobs/[name]/history/route.ts +1 -1
- package/templates/starter-app/src/app/api/admin/system/jobs/route.ts +31 -12
- package/templates/starter-app/src/app/api/admin/system/settings/all/route.ts +2 -2
- package/templates/starter-app/src/app/api/admin/system/settings/create/route.ts +10 -3
- package/templates/starter-app/src/app/api/admin/system/settings/delete/route.ts +10 -3
- package/templates/starter-app/src/app/api/admin/system/settings/route.ts +14 -5
- package/templates/starter-app/src/app/api/admin/system/settings/toggle-status/route.ts +16 -7
- package/templates/starter-app/src/app/api/admin/system/settings/update/route.ts +12 -6
- package/templates/starter-app/src/app/api/admin/system/settings/update-full/route.ts +12 -6
- package/templates/starter-app/src/app/api/better-auth/[...all]/route.ts +1 -1
- package/templates/starter-app/src/app/api/branding/logo/route.ts +25 -0
- package/templates/starter-app/src/app/api/crud/[entity]/[id]/route.ts +2 -2
- package/templates/starter-app/src/app/api/crud/[entity]/export/route.ts +16 -0
- package/templates/starter-app/src/app/api/crud/[entity]/import/route.ts +17 -0
- package/templates/starter-app/src/app/api/crud/[entity]/route.ts +2 -2
- package/templates/starter-app/src/app/api/error-logs/[id]/route.ts +4 -2
- package/templates/starter-app/src/app/api/error-logs/route.ts +26 -19
- package/templates/starter-app/src/app/api/files/[...key]/route.ts +1 -2
- package/templates/starter-app/src/app/api/job-titles/route.ts +1 -1
- package/templates/starter-app/src/app/api/notifications/read/route.ts +1 -1
- package/templates/starter-app/src/app/api/notifications/route.ts +1 -1
- package/templates/starter-app/src/app/api/notifications/unread-count/route.ts +1 -1
- package/templates/starter-app/src/app/api/rbac/permissions-version/route.ts +4 -1
- package/templates/starter-app/src/app/api/roles/[id]/route.ts +2 -1
- package/templates/starter-app/src/app/api/roles/route.ts +2 -1
- package/templates/starter-app/src/app/api/security/sessions/[id]/route.ts +5 -5
- package/templates/starter-app/src/app/api/security/sessions/revoke-user/route.ts +1 -1
- package/templates/starter-app/src/app/api/security/sessions/route.ts +2 -2
- package/templates/starter-app/src/app/api/system-categories/route.ts +39 -14
- package/templates/starter-app/src/app/api/system-category-groups/route.ts +32 -14
- package/templates/starter-app/src/app/api/tasks/[id]/download/route.ts +7 -3
- package/templates/starter-app/src/app/api/tasks/route.ts +5 -2
- package/templates/starter-app/src/app/api/upload/route.ts +1 -2
- package/templates/starter-app/src/app/api/users/[id]/route.ts +20 -12
- package/templates/starter-app/src/app/api/users/route.ts +11 -9
- package/templates/starter-app/src/app/globals.css +0 -1
- package/templates/starter-app/src/app/layout.tsx +42 -1
- package/templates/starter-app/src/app/manifest.ts +23 -19
- package/templates/starter-app/src/components/layout/main-layout-wrapper.tsx +5 -4
- package/templates/starter-app/src/configs/entities/department.config.ts +7 -4
- package/templates/starter-app/src/configs/entities/index.ts +1 -1
- package/templates/starter-app/src/configs/entities/job-titles.config.ts +2 -0
- package/templates/starter-app/src/configs/entities/system-alerts.config.ts +2 -0
- package/templates/starter-app/src/configs/entities/users.config.ts +15 -2
- package/templates/starter-app/src/configs/i18n.ts +6 -6
- package/templates/starter-app/src/configs/permissions/menu-tree.ts +1 -1
- package/templates/starter-app/src/configs/tenant.ts +6 -1
- package/templates/starter-app/src/data/dictionary.ts +2 -1
- package/templates/starter-app/src/data/navigations.ts +12 -2
- package/templates/starter-app/src/lib/action-guard.ts +3 -4
- package/templates/starter-app/src/lib/api-handler.ts +27 -19
- package/templates/starter-app/src/lib/auth.ts +3 -1
- package/templates/starter-app/src/lib/branch-scope.ts +1 -1
- package/templates/starter-app/src/lib/crud/index.ts +5 -12
- package/templates/starter-app/src/lib/errors/log-server-error.ts +5 -4
- package/templates/starter-app/src/lib/logger.ts +11 -4
- package/templates/starter-app/src/lib/page-guard.ts +2 -3
- package/templates/starter-app/src/lib/prisma.ts +13 -5
- package/templates/starter-app/src/lib/rbac/access.ts +2 -2
- package/templates/starter-app/src/lib/storage.ts +3 -1
- package/templates/starter-app/src/providers/index.tsx +13 -2
- package/templates/starter-app/src/providers/theme-provider.tsx +4 -3
- package/templates/starter-app/src/proxy.ts +3 -1
- package/templates/starter-app/src/server/services/company-service.ts +4 -2
- package/templates/starter-app/src/server/services/notification-service.ts +6 -6
- package/templates/starter-app/src/server/services/system-config-service.ts +21 -9
- package/templates/starter-app/src/server/services/user-service.ts +2 -2
- package/templates/starter-app/src/server/tasks/handlers/export-departments.ts +15 -9
- package/templates/starter-app/src/server/tasks/task-runner.ts +6 -7
- package/templates/starter-app/tsconfig.json +8 -0
- package/templates/starter-app/vitest.config.ts +17 -1
|
@@ -2,8 +2,6 @@
|
|
|
2
2
|
|
|
3
3
|
import * as React from "react"
|
|
4
4
|
import { useActionState } from "react"
|
|
5
|
-
import { toast } from "sonner"
|
|
6
|
-
|
|
7
5
|
import {
|
|
8
6
|
Avatar,
|
|
9
7
|
AvatarFallback,
|
|
@@ -17,8 +15,11 @@ import {
|
|
|
17
15
|
import { Button } from "@goerp/core/ui/primitives/button"
|
|
18
16
|
import { Input } from "@goerp/core/ui/primitives/input"
|
|
19
17
|
import { Label } from "@goerp/core/ui/primitives/label"
|
|
18
|
+
import { toast } from "sonner"
|
|
20
19
|
|
|
21
|
-
import {
|
|
20
|
+
import type { ChangePasswordState } from "./actions"
|
|
21
|
+
|
|
22
|
+
import { changePassword } from "./actions"
|
|
22
23
|
|
|
23
24
|
interface ProfileUser {
|
|
24
25
|
id: string
|
|
@@ -58,7 +59,10 @@ export function ProfileClientPage({ user }: { user: ProfileUser }) {
|
|
|
58
59
|
<CardContent className="flex flex-col gap-6">
|
|
59
60
|
<div className="flex items-center gap-4">
|
|
60
61
|
<Avatar className="size-14">
|
|
61
|
-
<AvatarImage
|
|
62
|
+
<AvatarImage
|
|
63
|
+
src={user.image ?? undefined}
|
|
64
|
+
alt={user.name ?? ""}
|
|
65
|
+
/>
|
|
62
66
|
<AvatarFallback>
|
|
63
67
|
{(user.name ?? user.email).slice(0, 2).toUpperCase()}
|
|
64
68
|
</AvatarFallback>
|
|
@@ -107,7 +111,11 @@ export function ProfileClientPage({ user }: { user: ProfileUser }) {
|
|
|
107
111
|
<CardTitle>Đổi mật khẩu</CardTitle>
|
|
108
112
|
</CardHeader>
|
|
109
113
|
<CardContent>
|
|
110
|
-
<form
|
|
114
|
+
<form
|
|
115
|
+
ref={formRef}
|
|
116
|
+
action={formAction}
|
|
117
|
+
className="grid gap-4 md:max-w-md"
|
|
118
|
+
>
|
|
111
119
|
<div className="space-y-2">
|
|
112
120
|
<Label htmlFor="currentPassword">Mật khẩu hiện tại</Label>
|
|
113
121
|
<Input
|
|
@@ -1,16 +1,17 @@
|
|
|
1
|
+
import { getUsersList } from "@/server/services/user-service"
|
|
1
2
|
import { getCrudPermissions } from "@goerp/core/crud/server"
|
|
2
3
|
import { getActiveRoles, getUserStats } from "@goerp/core/user"
|
|
3
4
|
import { UsersClientPage } from "@goerp/core/user/client-page"
|
|
4
5
|
|
|
5
6
|
import type { Metadata } from "next"
|
|
6
7
|
|
|
8
|
+
import { dictionary } from "@/data/dictionary"
|
|
9
|
+
|
|
7
10
|
import { usersConfig } from "@/configs/entities/users.config"
|
|
8
11
|
import { getActionMetaMap } from "@/configs/permissions"
|
|
9
12
|
import { MENU_TREE } from "@/configs/permissions/menu-tree"
|
|
10
|
-
import { dictionary } from "@/data/dictionary"
|
|
11
13
|
import { requirePageAccess } from "@/lib/page-guard"
|
|
12
14
|
import { db } from "@/lib/prisma"
|
|
13
|
-
import { getUsersList } from "@/server/services/user-service"
|
|
14
15
|
|
|
15
16
|
export const metadata: Metadata = { title: "Người dùng" }
|
|
16
17
|
export const dynamic = "force-dynamic"
|
|
@@ -1,12 +1,28 @@
|
|
|
1
|
+
import { resolveBranding, tenantWithBranding } from "@goerp/core/branding"
|
|
2
|
+
|
|
3
|
+
import { Providers } from "@/providers"
|
|
4
|
+
import { getSystemCompany } from "@/server/services/company-service"
|
|
5
|
+
|
|
1
6
|
import type { LocaleType } from "@goerp/core/types"
|
|
2
7
|
import type { ReactNode } from "react"
|
|
3
8
|
|
|
4
|
-
import {
|
|
9
|
+
import { tenant } from "@/configs/tenant"
|
|
5
10
|
|
|
6
11
|
/**
|
|
7
12
|
* Không truyền session xuống Providers: Better Auth lấy session ở CLIENT qua
|
|
8
13
|
* authClient.useSession() (bridge). Server component nào cần session thì tự gọi
|
|
9
14
|
* getSession() — nó đã được React cache theo request.
|
|
15
|
+
*
|
|
16
|
+
* CÓ đọc hồ sơ công ty: logo/tên admin lưu ở /admin/system/company phải đè lên
|
|
17
|
+
* cấu hình tĩnh trong src/configs/tenant.ts, và phải có mặt ở CẢ vỏ app lẫn
|
|
18
|
+
* trang đăng nhập (hai nhóm route (main)/(plain) đều nằm dưới layout này).
|
|
19
|
+
* Đây là MỘT findFirst đã bọc `cache()`; `<html>` do app/layout.tsx (sync) xả
|
|
20
|
+
* ra trước nên trình duyệt vẫn nhận CSS ngay — đừng thêm truy vấn DB nào nữa ở
|
|
21
|
+
* tầng này, chỗ cần dữ liệu nặng thì bọc Suspense trong trang.
|
|
22
|
+
*
|
|
23
|
+
* `resolveBranding` trả logo dưới dạng `/api/branding/logo?v=<updatedAt>` chứ
|
|
24
|
+
* KHÔNG phải `Company.logoUrl` thô: URL thô trỏ `/api/files/<key>` cần phiên
|
|
25
|
+
* đăng nhập, dùng thẳng thì logo trên trang đăng nhập 401 và mất ảnh.
|
|
10
26
|
*/
|
|
11
27
|
export default async function LangLayout({
|
|
12
28
|
children,
|
|
@@ -16,6 +32,15 @@ export default async function LangLayout({
|
|
|
16
32
|
params: Promise<{ lang: string }>
|
|
17
33
|
}) {
|
|
18
34
|
const { lang } = await params
|
|
35
|
+
const company = await getSystemCompany()
|
|
36
|
+
const identity = resolveBranding({ company, tenant })
|
|
19
37
|
|
|
20
|
-
return
|
|
38
|
+
return (
|
|
39
|
+
<Providers
|
|
40
|
+
locale={lang as LocaleType}
|
|
41
|
+
tenant={tenantWithBranding(tenant, identity)}
|
|
42
|
+
>
|
|
43
|
+
{children}
|
|
44
|
+
</Providers>
|
|
45
|
+
)
|
|
21
46
|
}
|
|
@@ -18,7 +18,6 @@ export const GET = apiHandler(
|
|
|
18
18
|
const skip = parseInt(sp.get("skip") || "0", 10)
|
|
19
19
|
const take = Math.min(parseInt(sp.get("take") || "50", 10), 100)
|
|
20
20
|
|
|
21
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
22
21
|
const where: Record<string, any> = {}
|
|
23
22
|
if (action && action !== "all") where.action = action
|
|
24
23
|
if (resource && resource !== "all") where.resource = resource
|
|
@@ -53,8 +52,10 @@ export const GET = apiHandler(
|
|
|
53
52
|
meta: { total, skip, take, hasMore: skip + take < total },
|
|
54
53
|
})
|
|
55
54
|
} catch (error) {
|
|
56
|
-
return serverError(error, req, {
|
|
55
|
+
return serverError(error, req, {
|
|
56
|
+
message: "Không tải được nhật ký thao tác",
|
|
57
|
+
})
|
|
57
58
|
}
|
|
58
59
|
},
|
|
59
|
-
{ resource: "audit-log", action: "view" }
|
|
60
|
+
{ resource: "audit-log", action: "view" }
|
|
60
61
|
)
|
|
@@ -17,13 +17,18 @@ import { db } from "@/lib/prisma"
|
|
|
17
17
|
export const GET = apiHandler(
|
|
18
18
|
async (req) => {
|
|
19
19
|
try {
|
|
20
|
-
const memoryJobs = new Map(
|
|
20
|
+
const memoryJobs = new Map(
|
|
21
|
+
cronManager.getJobsInfo().map((j) => [j.name, j])
|
|
22
|
+
)
|
|
21
23
|
const dbJobs = new Map(
|
|
22
|
-
(await db.systemJob.findMany({ orderBy: { name: "asc" } })).map((j) => [
|
|
24
|
+
(await db.systemJob.findMany({ orderBy: { name: "asc" } })).map((j) => [
|
|
25
|
+
j.name,
|
|
26
|
+
j,
|
|
27
|
+
])
|
|
23
28
|
)
|
|
24
29
|
|
|
25
|
-
const names = [...new Set([...dbJobs.keys(), ...memoryJobs.keys()])].sort(
|
|
26
|
-
a.localeCompare(b)
|
|
30
|
+
const names = [...new Set([...dbJobs.keys(), ...memoryJobs.keys()])].sort(
|
|
31
|
+
(a, b) => a.localeCompare(b)
|
|
27
32
|
)
|
|
28
33
|
|
|
29
34
|
const jobs = names.map((name) => {
|
|
@@ -45,10 +50,12 @@ export const GET = apiHandler(
|
|
|
45
50
|
|
|
46
51
|
return NextResponse.json({ data: jobs })
|
|
47
52
|
} catch (error) {
|
|
48
|
-
return serverError(error, req, {
|
|
53
|
+
return serverError(error, req, {
|
|
54
|
+
message: "Không tải được danh sách job",
|
|
55
|
+
})
|
|
49
56
|
}
|
|
50
57
|
},
|
|
51
|
-
{ resource: "system-job", action: "view" }
|
|
58
|
+
{ resource: "system-job", action: "view" }
|
|
52
59
|
)
|
|
53
60
|
|
|
54
61
|
/** POST { name, action: "toggle" | "run" | "remove" }. */
|
|
@@ -62,7 +69,10 @@ export const POST = apiHandler(
|
|
|
62
69
|
|
|
63
70
|
if (action === "toggle") {
|
|
64
71
|
if (!(await cronManager.toggleJob(name))) {
|
|
65
|
-
return NextResponse.json(
|
|
72
|
+
return NextResponse.json(
|
|
73
|
+
{ error: `Không tìm thấy job "${name}"` },
|
|
74
|
+
{ status: 404 }
|
|
75
|
+
)
|
|
66
76
|
}
|
|
67
77
|
const info = cronManager.getJobsInfo().find((j) => j.name === name)
|
|
68
78
|
return NextResponse.json({
|
|
@@ -74,23 +84,32 @@ export const POST = apiHandler(
|
|
|
74
84
|
|
|
75
85
|
if (action === "run") {
|
|
76
86
|
if (!(await cronManager.runJobNow(name))) {
|
|
77
|
-
return NextResponse.json(
|
|
87
|
+
return NextResponse.json(
|
|
88
|
+
{ error: `Không tìm thấy job "${name}"` },
|
|
89
|
+
{ status: 404 }
|
|
90
|
+
)
|
|
78
91
|
}
|
|
79
|
-
return NextResponse.json({
|
|
92
|
+
return NextResponse.json({
|
|
93
|
+
success: true,
|
|
94
|
+
message: `Đã chạy tay job "${name}"`,
|
|
95
|
+
})
|
|
80
96
|
}
|
|
81
97
|
|
|
82
98
|
if (action === "remove") {
|
|
83
99
|
cronManager.removeJob(name)
|
|
84
|
-
return NextResponse.json({
|
|
100
|
+
return NextResponse.json({
|
|
101
|
+
success: true,
|
|
102
|
+
message: `Đã gỡ job "${name}"`,
|
|
103
|
+
})
|
|
85
104
|
}
|
|
86
105
|
|
|
87
106
|
return NextResponse.json(
|
|
88
107
|
{ error: "action phải là 'toggle', 'run' hoặc 'remove'" },
|
|
89
|
-
{ status: 400 }
|
|
108
|
+
{ status: 400 }
|
|
90
109
|
)
|
|
91
110
|
} catch (error) {
|
|
92
111
|
return serverError(error, req, { message: "Không thao tác được với job" })
|
|
93
112
|
}
|
|
94
113
|
},
|
|
95
|
-
{ resource: "system-job", action: "update" }
|
|
114
|
+
{ resource: "system-job", action: "update" }
|
|
96
115
|
)
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { NextResponse } from "next/server"
|
|
2
|
+
import { listConfigs } from "@/server/services/system-config-service"
|
|
2
3
|
|
|
3
4
|
import { apiHandler } from "@/lib/api-handler"
|
|
4
5
|
import { serverError } from "@/lib/errors/server-error"
|
|
5
|
-
import { listConfigs } from "@/server/services/system-config-service"
|
|
6
6
|
|
|
7
7
|
/** Toàn bộ cấu hình đang bật — bảng chính của trang cài đặt hệ thống. */
|
|
8
8
|
export const GET = apiHandler(
|
|
@@ -13,5 +13,5 @@ export const GET = apiHandler(
|
|
|
13
13
|
return serverError(error, req, { message: "Không tải được cấu hình" })
|
|
14
14
|
}
|
|
15
15
|
},
|
|
16
|
-
{ resource: "system-setting", action: "view" }
|
|
16
|
+
{ resource: "system-setting", action: "view" }
|
|
17
17
|
)
|
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
import { NextResponse } from "next/server"
|
|
2
|
+
import {
|
|
3
|
+
configErrorInfo,
|
|
4
|
+
createConfig,
|
|
5
|
+
} from "@/server/services/system-config-service"
|
|
2
6
|
|
|
3
7
|
import { apiHandler } from "@/lib/api-handler"
|
|
4
8
|
import { serverError } from "@/lib/errors/server-error"
|
|
5
|
-
import { configErrorInfo, createConfig } from "@/server/services/system-config-service"
|
|
6
9
|
|
|
7
10
|
export const POST = apiHandler(
|
|
8
11
|
async (req, { session }) => {
|
|
@@ -11,9 +14,13 @@ export const POST = apiHandler(
|
|
|
11
14
|
return NextResponse.json({ success: true, data })
|
|
12
15
|
} catch (error) {
|
|
13
16
|
const known = configErrorInfo(error)
|
|
14
|
-
if (known)
|
|
17
|
+
if (known)
|
|
18
|
+
return NextResponse.json(
|
|
19
|
+
{ error: known.message },
|
|
20
|
+
{ status: known.status }
|
|
21
|
+
)
|
|
15
22
|
return serverError(error, req, { message: "Không tạo được cấu hình" })
|
|
16
23
|
}
|
|
17
24
|
},
|
|
18
|
-
{ resource: "system-setting", action: "create" }
|
|
25
|
+
{ resource: "system-setting", action: "create" }
|
|
19
26
|
)
|
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
import { NextResponse } from "next/server"
|
|
2
|
+
import {
|
|
3
|
+
configErrorInfo,
|
|
4
|
+
deleteConfig,
|
|
5
|
+
} from "@/server/services/system-config-service"
|
|
2
6
|
|
|
3
7
|
import { apiHandler } from "@/lib/api-handler"
|
|
4
8
|
import { serverError } from "@/lib/errors/server-error"
|
|
5
|
-
import { configErrorInfo, deleteConfig } from "@/server/services/system-config-service"
|
|
6
9
|
|
|
7
10
|
export const POST = apiHandler(
|
|
8
11
|
async (req) => {
|
|
@@ -12,9 +15,13 @@ export const POST = apiHandler(
|
|
|
12
15
|
return NextResponse.json({ success: true, message: "Đã xóa cấu hình" })
|
|
13
16
|
} catch (error) {
|
|
14
17
|
const known = configErrorInfo(error)
|
|
15
|
-
if (known)
|
|
18
|
+
if (known)
|
|
19
|
+
return NextResponse.json(
|
|
20
|
+
{ error: known.message },
|
|
21
|
+
{ status: known.status }
|
|
22
|
+
)
|
|
16
23
|
return serverError(error, req, { message: "Không xóa được cấu hình" })
|
|
17
24
|
}
|
|
18
25
|
},
|
|
19
|
-
{ resource: "system-setting", action: "delete" }
|
|
26
|
+
{ resource: "system-setting", action: "delete" }
|
|
20
27
|
)
|
|
@@ -23,7 +23,7 @@ export const GET = apiHandler(
|
|
|
23
23
|
return serverError(error, req, { message: "Không tải được cấu hình" })
|
|
24
24
|
}
|
|
25
25
|
},
|
|
26
|
-
{ resource: "system-setting", action: "view" }
|
|
26
|
+
{ resource: "system-setting", action: "view" }
|
|
27
27
|
)
|
|
28
28
|
|
|
29
29
|
export const POST = apiHandler(
|
|
@@ -33,17 +33,26 @@ export const POST = apiHandler(
|
|
|
33
33
|
|
|
34
34
|
if (type === "audit") {
|
|
35
35
|
await SettingsService.saveAuditSettings(settings)
|
|
36
|
-
return NextResponse.json({
|
|
36
|
+
return NextResponse.json({
|
|
37
|
+
success: true,
|
|
38
|
+
message: "Đã lưu cấu hình nhật ký",
|
|
39
|
+
})
|
|
37
40
|
}
|
|
38
41
|
if (type === "notification") {
|
|
39
42
|
await SettingsService.saveNotificationSettings(settings)
|
|
40
|
-
return NextResponse.json({
|
|
43
|
+
return NextResponse.json({
|
|
44
|
+
success: true,
|
|
45
|
+
message: "Đã lưu cấu hình thông báo",
|
|
46
|
+
})
|
|
41
47
|
}
|
|
42
48
|
|
|
43
|
-
return NextResponse.json(
|
|
49
|
+
return NextResponse.json(
|
|
50
|
+
{ error: "Loại cấu hình không hợp lệ" },
|
|
51
|
+
{ status: 400 }
|
|
52
|
+
)
|
|
44
53
|
} catch (error) {
|
|
45
54
|
return serverError(error, req, { message: "Không lưu được cấu hình" })
|
|
46
55
|
}
|
|
47
56
|
},
|
|
48
|
-
{ resource: "system-setting", action: "update" }
|
|
57
|
+
{ resource: "system-setting", action: "update" }
|
|
49
58
|
)
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import { NextResponse } from "next/server"
|
|
2
|
-
|
|
3
|
-
import { apiHandler } from "@/lib/api-handler"
|
|
4
|
-
import { serverError } from "@/lib/errors/server-error"
|
|
5
2
|
import {
|
|
6
3
|
configErrorInfo,
|
|
7
4
|
toggleConfigStatus,
|
|
8
5
|
} from "@/server/services/system-config-service"
|
|
9
6
|
|
|
7
|
+
import { apiHandler } from "@/lib/api-handler"
|
|
8
|
+
import { serverError } from "@/lib/errors/server-error"
|
|
9
|
+
|
|
10
10
|
/** Bật/tạm ngưng một cấu hình mà không xóa nó. */
|
|
11
11
|
export const POST = apiHandler(
|
|
12
12
|
async (req, { session }) => {
|
|
@@ -16,13 +16,22 @@ export const POST = apiHandler(
|
|
|
16
16
|
return NextResponse.json({
|
|
17
17
|
success: true,
|
|
18
18
|
data,
|
|
19
|
-
message:
|
|
19
|
+
message:
|
|
20
|
+
status === "active"
|
|
21
|
+
? "Đã kích hoạt cấu hình"
|
|
22
|
+
: "Đã tạm ngưng cấu hình",
|
|
20
23
|
})
|
|
21
24
|
} catch (error) {
|
|
22
25
|
const known = configErrorInfo(error)
|
|
23
|
-
if (known)
|
|
24
|
-
|
|
26
|
+
if (known)
|
|
27
|
+
return NextResponse.json(
|
|
28
|
+
{ error: known.message },
|
|
29
|
+
{ status: known.status }
|
|
30
|
+
)
|
|
31
|
+
return serverError(error, req, {
|
|
32
|
+
message: "Không cập nhật được trạng thái",
|
|
33
|
+
})
|
|
25
34
|
}
|
|
26
35
|
},
|
|
27
|
-
{ resource: "system-setting", action: "update" }
|
|
36
|
+
{ resource: "system-setting", action: "update" }
|
|
28
37
|
)
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import { NextResponse } from "next/server"
|
|
2
|
-
|
|
3
|
-
import { apiHandler } from "@/lib/api-handler"
|
|
4
|
-
import { serverError } from "@/lib/errors/server-error"
|
|
5
2
|
import {
|
|
6
3
|
configErrorInfo,
|
|
7
4
|
updateConfigValue,
|
|
8
5
|
} from "@/server/services/system-config-service"
|
|
9
6
|
|
|
7
|
+
import { apiHandler } from "@/lib/api-handler"
|
|
8
|
+
import { serverError } from "@/lib/errors/server-error"
|
|
9
|
+
|
|
10
10
|
/** Đổi mỗi giá trị của một cấu hình theo key. */
|
|
11
11
|
export const POST = apiHandler(
|
|
12
12
|
async (req, { session }) => {
|
|
@@ -16,9 +16,15 @@ export const POST = apiHandler(
|
|
|
16
16
|
return NextResponse.json({ success: true, data })
|
|
17
17
|
} catch (error) {
|
|
18
18
|
const known = configErrorInfo(error)
|
|
19
|
-
if (known)
|
|
20
|
-
|
|
19
|
+
if (known)
|
|
20
|
+
return NextResponse.json(
|
|
21
|
+
{ error: known.message },
|
|
22
|
+
{ status: known.status }
|
|
23
|
+
)
|
|
24
|
+
return serverError(error, req, {
|
|
25
|
+
message: "Không cập nhật được cấu hình",
|
|
26
|
+
})
|
|
21
27
|
}
|
|
22
28
|
},
|
|
23
|
-
{ resource: "system-setting", action: "update" }
|
|
29
|
+
{ resource: "system-setting", action: "update" }
|
|
24
30
|
)
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import { NextResponse } from "next/server"
|
|
2
|
-
|
|
3
|
-
import { apiHandler } from "@/lib/api-handler"
|
|
4
|
-
import { serverError } from "@/lib/errors/server-error"
|
|
5
2
|
import {
|
|
6
3
|
configErrorInfo,
|
|
7
4
|
updateConfigFull,
|
|
8
5
|
} from "@/server/services/system-config-service"
|
|
9
6
|
|
|
7
|
+
import { apiHandler } from "@/lib/api-handler"
|
|
8
|
+
import { serverError } from "@/lib/errors/server-error"
|
|
9
|
+
|
|
10
10
|
/** Sửa toàn bộ thuộc tính của một cấu hình (dialog "Sửa"). */
|
|
11
11
|
export const POST = apiHandler(
|
|
12
12
|
async (req, { session }) => {
|
|
@@ -15,9 +15,15 @@ export const POST = apiHandler(
|
|
|
15
15
|
return NextResponse.json({ success: true, data })
|
|
16
16
|
} catch (error) {
|
|
17
17
|
const known = configErrorInfo(error)
|
|
18
|
-
if (known)
|
|
19
|
-
|
|
18
|
+
if (known)
|
|
19
|
+
return NextResponse.json(
|
|
20
|
+
{ error: known.message },
|
|
21
|
+
{ status: known.status }
|
|
22
|
+
)
|
|
23
|
+
return serverError(error, req, {
|
|
24
|
+
message: "Không cập nhật được cấu hình",
|
|
25
|
+
})
|
|
20
26
|
}
|
|
21
27
|
},
|
|
22
|
-
{ resource: "system-setting", action: "update" }
|
|
28
|
+
{ resource: "system-setting", action: "update" }
|
|
23
29
|
)
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { createBrandingLogoHandler } from "@goerp/core/branding"
|
|
2
|
+
|
|
3
|
+
import { getSystemCompany } from "@/server/services/company-service"
|
|
4
|
+
import { getFile } from "@/lib/storage"
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* GET /api/branding/logo — logo cấu hình ở "Hồ sơ công ty".
|
|
8
|
+
*
|
|
9
|
+
* URL DUY NHẤT mà mọi bề mặt tham chiếu: sidebar, trang đăng nhập, đầu trang
|
|
10
|
+
* in, favicon, icon PWA, thẻ chia sẻ. Chưa upload gì ⇒ logo GoEat mặc định
|
|
11
|
+
* (`public/images/logos/goeat_logo.png`).
|
|
12
|
+
*
|
|
13
|
+
* Công khai — đã khai `/api/branding` trong `publicPrefixes` của `proxy.ts`:
|
|
14
|
+
* favicon và trang đăng nhập render trước khi có phiên.
|
|
15
|
+
*/
|
|
16
|
+
export const dynamic = "force-dynamic"
|
|
17
|
+
|
|
18
|
+
export const GET = createBrandingLogoHandler({
|
|
19
|
+
getCompany: getSystemCompany,
|
|
20
|
+
getFile,
|
|
21
|
+
// `import` chữ literal phải nằm ở đây (mã app) thì Next mới trace `sharp`
|
|
22
|
+
// vào bản standalone. Core cố ý không tự import — xem chú thích ở
|
|
23
|
+
// `createBrandingLogoHandler`.
|
|
24
|
+
loadSharp: () => import("sharp"),
|
|
25
|
+
})
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { createCrudItemHandlers } from "@goerp/core/crud/server"
|
|
2
2
|
|
|
3
|
-
import { getSession } from "@/lib/auth"
|
|
4
3
|
import { getEntityConfig } from "@/configs/entities"
|
|
5
|
-
import {
|
|
4
|
+
import { getSession } from "@/lib/auth"
|
|
6
5
|
import { crudService } from "@/lib/crud"
|
|
6
|
+
import { serverError } from "@/lib/errors/server-error"
|
|
7
7
|
|
|
8
8
|
export const { GET, PUT, PATCH, DELETE } = createCrudItemHandlers({
|
|
9
9
|
getSession,
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { createCrudExportHandler } from "@goerp/core/crud/server"
|
|
2
|
+
|
|
3
|
+
import { getEntityConfig } from "@/configs/entities"
|
|
4
|
+
import { getSession } from "@/lib/auth"
|
|
5
|
+
import { crudService } from "@/lib/crud"
|
|
6
|
+
import { serverError } from "@/lib/errors/server-error"
|
|
7
|
+
|
|
8
|
+
// Xuất xlsx/csv/json theo đúng search/sort/filter đang áp trên bảng. Nút
|
|
9
|
+
// Export của CrudPage mặc định trỏ về đây (`${apiEndpoint}/export`); gác
|
|
10
|
+
// quyền `<resource>:export` ở core.
|
|
11
|
+
export const { GET } = createCrudExportHandler({
|
|
12
|
+
getSession,
|
|
13
|
+
getEntityConfig,
|
|
14
|
+
service: crudService,
|
|
15
|
+
onError: serverError,
|
|
16
|
+
})
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { createCrudImportHandler } from "@goerp/core/crud/server"
|
|
2
|
+
|
|
3
|
+
import { getEntityConfig } from "@/configs/entities"
|
|
4
|
+
import { getSession } from "@/lib/auth"
|
|
5
|
+
import { crudService } from "@/lib/crud"
|
|
6
|
+
import { serverError } from "@/lib/errors/server-error"
|
|
7
|
+
|
|
8
|
+
// Nhập từ file (multipart của ImportDialog) hoặc JSON array. Mỗi dòng đi qua
|
|
9
|
+
// service.create như tạo tay — coercion/validate/audit của engine áp đủ; dòng
|
|
10
|
+
// lỗi gom vào ImportResult.errors, không chặn dòng sau. Gác quyền
|
|
11
|
+
// `<resource>:import` ở core.
|
|
12
|
+
export const { POST } = createCrudImportHandler({
|
|
13
|
+
getSession,
|
|
14
|
+
getEntityConfig,
|
|
15
|
+
service: crudService,
|
|
16
|
+
onError: serverError,
|
|
17
|
+
})
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { createCrudCollectionHandlers } from "@goerp/core/crud/server"
|
|
2
2
|
|
|
3
|
-
import { getSession } from "@/lib/auth"
|
|
4
3
|
import { getEntityConfig } from "@/configs/entities"
|
|
5
|
-
import {
|
|
4
|
+
import { getSession } from "@/lib/auth"
|
|
6
5
|
import { crudService } from "@/lib/crud"
|
|
6
|
+
import { serverError } from "@/lib/errors/server-error"
|
|
7
7
|
|
|
8
8
|
// GET danh sách + POST tạo. Gác quyền + query đều ở core.
|
|
9
9
|
export const { GET, POST } = createCrudCollectionHandlers({
|
|
@@ -16,8 +16,10 @@ export const PATCH = apiHandler(
|
|
|
16
16
|
})
|
|
17
17
|
return NextResponse.json({ success: true, log })
|
|
18
18
|
} catch (error) {
|
|
19
|
-
return serverError(error, req, {
|
|
19
|
+
return serverError(error, req, {
|
|
20
|
+
message: "Không cập nhật được trạng thái lỗi",
|
|
21
|
+
})
|
|
20
22
|
}
|
|
21
23
|
},
|
|
22
|
-
{ resource: "error-log", action: "update" }
|
|
24
|
+
{ resource: "error-log", action: "update" }
|
|
23
25
|
)
|
|
@@ -12,7 +12,6 @@ import { db } from "@/lib/prisma"
|
|
|
12
12
|
* và dọn.
|
|
13
13
|
*/
|
|
14
14
|
|
|
15
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
16
15
|
type Where = Record<string, any>
|
|
17
16
|
|
|
18
17
|
export const GET = apiHandler(
|
|
@@ -21,7 +20,7 @@ export const GET = apiHandler(
|
|
|
21
20
|
const sp = new URL(req.url).searchParams
|
|
22
21
|
const search = sp.get("search") || undefined
|
|
23
22
|
const severity = sp.get("severity") || undefined
|
|
24
|
-
const
|
|
23
|
+
const moduleFilter = sp.get("module") || undefined
|
|
25
24
|
const resolved = sp.get("resolved")
|
|
26
25
|
const startDate = sp.get("startDate")
|
|
27
26
|
const endDate = sp.get("endDate")
|
|
@@ -38,7 +37,7 @@ export const GET = apiHandler(
|
|
|
38
37
|
]
|
|
39
38
|
}
|
|
40
39
|
if (severity && severity !== "all") where.severity = severity
|
|
41
|
-
if (
|
|
40
|
+
if (moduleFilter && moduleFilter !== "all") where.module = moduleFilter
|
|
42
41
|
if (resolved === "true") where.resolved = true
|
|
43
42
|
else if (resolved === "false") where.resolved = false
|
|
44
43
|
if (startDate || endDate) {
|
|
@@ -50,23 +49,31 @@ export const GET = apiHandler(
|
|
|
50
49
|
const startOfToday = new Date()
|
|
51
50
|
startOfToday.setHours(0, 0, 0, 0)
|
|
52
51
|
|
|
53
|
-
const [logs, total, todayCount, unresolvedCount, moduleStats] =
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
52
|
+
const [logs, total, todayCount, unresolvedCount, moduleStats] =
|
|
53
|
+
await Promise.all([
|
|
54
|
+
db.errorLog.findMany({
|
|
55
|
+
where,
|
|
56
|
+
orderBy: { lastSeenAt: "desc" },
|
|
57
|
+
skip,
|
|
58
|
+
take,
|
|
59
|
+
}),
|
|
60
|
+
db.errorLog.count({ where }),
|
|
61
|
+
db.errorLog.count({ where: { createdAt: { gte: startOfToday } } }),
|
|
62
|
+
db.errorLog.count({ where: { resolved: false } }),
|
|
63
|
+
db.errorLog.groupBy({
|
|
64
|
+
by: ["module"],
|
|
65
|
+
_count: { module: true },
|
|
66
|
+
where: { resolved: false },
|
|
67
|
+
orderBy: { _count: { module: "desc" } },
|
|
68
|
+
take: 8,
|
|
69
|
+
}),
|
|
70
|
+
])
|
|
66
71
|
|
|
67
72
|
// ErrorLog không có quan hệ tới User (lỗi có thể xảy ra khi chưa đăng
|
|
68
73
|
// nhập) — tra tên riêng thay vì include.
|
|
69
|
-
const userIds = [
|
|
74
|
+
const userIds = [
|
|
75
|
+
...new Set(logs.map((l) => l.userId).filter(Boolean)),
|
|
76
|
+
] as string[]
|
|
70
77
|
const nameById = new Map<string, string>()
|
|
71
78
|
if (userIds.length) {
|
|
72
79
|
const users = await db.user.findMany({
|
|
@@ -98,7 +105,7 @@ export const GET = apiHandler(
|
|
|
98
105
|
return serverError(error, req, { message: "Không tải được nhật ký lỗi" })
|
|
99
106
|
}
|
|
100
107
|
},
|
|
101
|
-
{ resource: "error-log", action: "view" }
|
|
108
|
+
{ resource: "error-log", action: "view" }
|
|
102
109
|
)
|
|
103
110
|
|
|
104
111
|
/** Dọn log: ?mode=resolved (đã xử lý) hoặc ?mode=older_than&days=30. */
|
|
@@ -120,5 +127,5 @@ export const DELETE = apiHandler(
|
|
|
120
127
|
return serverError(error, req, { message: "Không xóa được nhật ký lỗi" })
|
|
121
128
|
}
|
|
122
129
|
},
|
|
123
|
-
{ resource: "error-log", action: "delete" }
|
|
130
|
+
{ resource: "error-log", action: "delete" }
|
|
124
131
|
)
|