@goplusvn/core 0.1.71 → 0.1.72
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/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/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/templates/starter-app/AGENTS.md +1 -1
- 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/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 +20 -9
- 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 +2 -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/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/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/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 +2 -1
- package/templates/starter-app/src/providers/theme-provider.tsx +4 -3
- 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
|
@@ -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
|
)
|
|
@@ -40,7 +40,7 @@ export const GET = apiHandler(
|
|
|
40
40
|
])
|
|
41
41
|
|
|
42
42
|
return NextResponse.json({ total, page, pageSize, items })
|
|
43
|
-
}
|
|
43
|
+
}
|
|
44
44
|
// Combo tra cứu — registry khai lookupPolicy "authenticated" cho job-title:
|
|
45
45
|
// ai đăng nhập cũng đổ được picker, không đòi quyền view của trang quản lý.
|
|
46
46
|
)
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { NextResponse } from "next/server"
|
|
2
|
+
import { markAllRead, markRead } from "@/server/services/notification-service"
|
|
2
3
|
|
|
3
4
|
import { apiHandler } from "@/lib/api-handler"
|
|
4
5
|
import { serverError } from "@/lib/errors/server-error"
|
|
5
|
-
import { markAllRead, markRead } from "@/server/services/notification-service"
|
|
6
6
|
|
|
7
7
|
/**
|
|
8
8
|
* POST /api/notifications/read
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { NextResponse } from "next/server"
|
|
2
|
+
import { listNotifications } from "@/server/services/notification-service"
|
|
2
3
|
|
|
3
4
|
import { apiHandler } from "@/lib/api-handler"
|
|
4
5
|
import { serverError } from "@/lib/errors/server-error"
|
|
5
|
-
import { listNotifications } from "@/server/services/notification-service"
|
|
6
6
|
|
|
7
7
|
/**
|
|
8
8
|
* GET /api/notifications?cursor=&take=&filter=unread|all
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { NextResponse } from "next/server"
|
|
2
|
+
import { getUnreadCount } from "@/server/services/notification-service"
|
|
2
3
|
|
|
3
4
|
import { apiHandler } from "@/lib/api-handler"
|
|
4
5
|
import { serverError } from "@/lib/errors/server-error"
|
|
5
|
-
import { getUnreadCount } from "@/server/services/notification-service"
|
|
6
6
|
|
|
7
7
|
/** GET /api/notifications/unread-count → { count }. Endpoint nhẹ cho chuông poll. */
|
|
8
8
|
export const GET = apiHandler(async (req, { session }) => {
|
|
@@ -8,4 +8,7 @@ import { db } from "@/lib/prisma"
|
|
|
8
8
|
* của core). Số version đổi ⇒ trình duyệt tự nạp lại phiên, nên admin sửa
|
|
9
9
|
* quyền là người dùng thấy ngay, không phải đăng xuất/đăng nhập lại.
|
|
10
10
|
*/
|
|
11
|
-
export const { GET } = createPermissionsVersionHandler({
|
|
11
|
+
export const { GET } = createPermissionsVersionHandler({
|
|
12
|
+
prisma: db,
|
|
13
|
+
getSession,
|
|
14
|
+
})
|
|
@@ -10,5 +10,6 @@ export const { GET, PUT, DELETE } = createRoleItemHandlers({
|
|
|
10
10
|
prisma: db,
|
|
11
11
|
getSession,
|
|
12
12
|
getCrudPermissions,
|
|
13
|
-
onError: (error, req) =>
|
|
13
|
+
onError: (error, req) =>
|
|
14
|
+
serverError(error, req, { message: "Lỗi xử lý vai trò" }),
|
|
14
15
|
})
|
|
@@ -14,5 +14,6 @@ export const { GET, POST } = createRolesCollectionHandlers({
|
|
|
14
14
|
prisma: db,
|
|
15
15
|
getSession,
|
|
16
16
|
getCrudPermissions,
|
|
17
|
-
onError: (error, req) =>
|
|
17
|
+
onError: (error, req) =>
|
|
18
|
+
serverError(error, req, { message: "Lỗi xử lý vai trò" }),
|
|
18
19
|
})
|
|
@@ -22,16 +22,16 @@ export const DELETE = apiHandler(async (req, { session, params }) => {
|
|
|
22
22
|
if (!row) {
|
|
23
23
|
return NextResponse.json(
|
|
24
24
|
{ error: "Phiên không tồn tại" },
|
|
25
|
-
{ status: 404 }
|
|
25
|
+
{ status: 404 }
|
|
26
26
|
)
|
|
27
27
|
}
|
|
28
28
|
|
|
29
29
|
const isMine = row.userId === session.user.id
|
|
30
|
-
|
|
31
|
-
if (!isMine && !checkPermission(session
|
|
30
|
+
|
|
31
|
+
if (!isMine && !checkPermission(session, "user", "update")) {
|
|
32
32
|
return NextResponse.json(
|
|
33
33
|
{ error: "Bạn không có quyền thu hồi phiên của người dùng khác" },
|
|
34
|
-
{ status: 403 }
|
|
34
|
+
{ status: 403 }
|
|
35
35
|
)
|
|
36
36
|
}
|
|
37
37
|
|
|
@@ -40,7 +40,7 @@ export const DELETE = apiHandler(async (req, { session, params }) => {
|
|
|
40
40
|
if (current?.session?.token && row.token === current.session.token) {
|
|
41
41
|
return NextResponse.json(
|
|
42
42
|
{ error: "Không thể thu hồi phiên hiện tại — hãy dùng Đăng xuất" },
|
|
43
|
-
{ status: 400 }
|
|
43
|
+
{ status: 400 }
|
|
44
44
|
)
|
|
45
45
|
}
|
|
46
46
|
|
|
@@ -19,7 +19,7 @@ export const GET = apiHandler(
|
|
|
19
19
|
const page = Math.max(Number(sp.get("page") || 1), 1)
|
|
20
20
|
const pageSize = Math.min(
|
|
21
21
|
Math.max(Number(sp.get("pageSize") || 20), 1),
|
|
22
|
-
100
|
|
22
|
+
100
|
|
23
23
|
)
|
|
24
24
|
|
|
25
25
|
// Token của phiên hiện tại (đánh dấu "thiết bị này" — so sánh server-side,
|
|
@@ -97,5 +97,5 @@ export const GET = apiHandler(
|
|
|
97
97
|
})
|
|
98
98
|
}
|
|
99
99
|
},
|
|
100
|
-
{ resource: "user", action: "view" }
|
|
100
|
+
{ resource: "user", action: "view" }
|
|
101
101
|
)
|
|
@@ -30,7 +30,12 @@ export const GET = apiHandler(
|
|
|
30
30
|
OR: [
|
|
31
31
|
{ code: { contains: search, mode: "insensitive" as const } },
|
|
32
32
|
{ name: { contains: search, mode: "insensitive" as const } },
|
|
33
|
-
{
|
|
33
|
+
{
|
|
34
|
+
description: {
|
|
35
|
+
contains: search,
|
|
36
|
+
mode: "insensitive" as const,
|
|
37
|
+
},
|
|
38
|
+
},
|
|
34
39
|
],
|
|
35
40
|
}
|
|
36
41
|
: {},
|
|
@@ -51,7 +56,7 @@ export const GET = apiHandler(
|
|
|
51
56
|
|
|
52
57
|
return NextResponse.json({ total, page, pageSize, items })
|
|
53
58
|
},
|
|
54
|
-
{ resource: "system-category", action: "view" }
|
|
59
|
+
{ resource: "system-category", action: "view" }
|
|
55
60
|
)
|
|
56
61
|
|
|
57
62
|
export const POST = apiHandler(
|
|
@@ -61,14 +66,22 @@ export const POST = apiHandler(
|
|
|
61
66
|
|
|
62
67
|
if (!code || !name || !groupCode) {
|
|
63
68
|
return NextResponse.json(
|
|
64
|
-
{
|
|
65
|
-
|
|
69
|
+
{
|
|
70
|
+
error: "Mã, tên và nhóm danh mục là bắt buộc",
|
|
71
|
+
field: !code ? "code" : !name ? "name" : "groupCode",
|
|
72
|
+
},
|
|
73
|
+
{ status: 400 }
|
|
66
74
|
)
|
|
67
75
|
}
|
|
68
76
|
|
|
69
|
-
const existing = await db.systemCategory.findUnique({
|
|
77
|
+
const existing = await db.systemCategory.findUnique({
|
|
78
|
+
where: { code: String(code) },
|
|
79
|
+
})
|
|
70
80
|
if (existing) {
|
|
71
|
-
return NextResponse.json(
|
|
81
|
+
return NextResponse.json(
|
|
82
|
+
{ error: "Mã danh mục đã tồn tại", field: "code" },
|
|
83
|
+
{ status: 400 }
|
|
84
|
+
)
|
|
72
85
|
}
|
|
73
86
|
|
|
74
87
|
const userId = session.user.id
|
|
@@ -89,7 +102,7 @@ export const POST = apiHandler(
|
|
|
89
102
|
|
|
90
103
|
return NextResponse.json(created, { status: 201 })
|
|
91
104
|
},
|
|
92
|
-
{ resource: "system-category", action: "create" }
|
|
105
|
+
{ resource: "system-category", action: "create" }
|
|
93
106
|
)
|
|
94
107
|
|
|
95
108
|
export const PUT = apiHandler(
|
|
@@ -97,14 +110,20 @@ export const PUT = apiHandler(
|
|
|
97
110
|
const body = await req.json()
|
|
98
111
|
const id = idFromRequest(req, body)
|
|
99
112
|
if (!id) {
|
|
100
|
-
return NextResponse.json(
|
|
113
|
+
return NextResponse.json(
|
|
114
|
+
{ error: "ID là bắt buộc", field: "id" },
|
|
115
|
+
{ status: 400 }
|
|
116
|
+
)
|
|
101
117
|
}
|
|
102
118
|
|
|
103
119
|
const { code, name, groupCode } = body as Record<string, unknown>
|
|
104
120
|
if (!code || !name || !groupCode) {
|
|
105
121
|
return NextResponse.json(
|
|
106
|
-
{
|
|
107
|
-
|
|
122
|
+
{
|
|
123
|
+
error: "Mã, tên và nhóm danh mục là bắt buộc",
|
|
124
|
+
field: !code ? "code" : !name ? "name" : "groupCode",
|
|
125
|
+
},
|
|
126
|
+
{ status: 400 }
|
|
108
127
|
)
|
|
109
128
|
}
|
|
110
129
|
|
|
@@ -113,7 +132,10 @@ export const PUT = apiHandler(
|
|
|
113
132
|
where: { code: String(code), id: { not: id } },
|
|
114
133
|
})
|
|
115
134
|
if (existing) {
|
|
116
|
-
return NextResponse.json(
|
|
135
|
+
return NextResponse.json(
|
|
136
|
+
{ error: "Mã danh mục đã tồn tại", field: "code" },
|
|
137
|
+
{ status: 400 }
|
|
138
|
+
)
|
|
117
139
|
}
|
|
118
140
|
|
|
119
141
|
const updated = await db.systemCategory.update({
|
|
@@ -132,7 +154,7 @@ export const PUT = apiHandler(
|
|
|
132
154
|
|
|
133
155
|
return NextResponse.json(updated)
|
|
134
156
|
},
|
|
135
|
-
{ resource: "system-category", action: "update" }
|
|
157
|
+
{ resource: "system-category", action: "update" }
|
|
136
158
|
)
|
|
137
159
|
|
|
138
160
|
export const DELETE = apiHandler(
|
|
@@ -144,11 +166,14 @@ export const DELETE = apiHandler(
|
|
|
144
166
|
|
|
145
167
|
const existing = await db.systemCategory.findUnique({ where: { id } })
|
|
146
168
|
if (!existing) {
|
|
147
|
-
return NextResponse.json(
|
|
169
|
+
return NextResponse.json(
|
|
170
|
+
{ error: "Không tìm thấy danh mục" },
|
|
171
|
+
{ status: 404 }
|
|
172
|
+
)
|
|
148
173
|
}
|
|
149
174
|
|
|
150
175
|
await db.systemCategory.delete({ where: { id } })
|
|
151
176
|
return NextResponse.json({ message: "Đã xóa danh mục" })
|
|
152
177
|
},
|
|
153
|
-
{ resource: "system-category", action: "delete" }
|
|
178
|
+
{ resource: "system-category", action: "delete" }
|
|
154
179
|
)
|
|
@@ -24,7 +24,7 @@ export const GET = apiHandler(
|
|
|
24
24
|
})
|
|
25
25
|
return NextResponse.json(result)
|
|
26
26
|
},
|
|
27
|
-
{ resource: "system-category", action: "view" }
|
|
27
|
+
{ resource: "system-category", action: "view" }
|
|
28
28
|
)
|
|
29
29
|
|
|
30
30
|
export const POST = apiHandler(
|
|
@@ -35,13 +35,18 @@ export const POST = apiHandler(
|
|
|
35
35
|
if (!code || !name) {
|
|
36
36
|
return NextResponse.json(
|
|
37
37
|
{ error: "Mã và tên nhóm là bắt buộc", field: !code ? "code" : "name" },
|
|
38
|
-
{ status: 400 }
|
|
38
|
+
{ status: 400 }
|
|
39
39
|
)
|
|
40
40
|
}
|
|
41
41
|
|
|
42
|
-
const existing = await db.systemCategoryGroup.findUnique({
|
|
42
|
+
const existing = await db.systemCategoryGroup.findUnique({
|
|
43
|
+
where: { code: String(code) },
|
|
44
|
+
})
|
|
43
45
|
if (existing) {
|
|
44
|
-
return NextResponse.json(
|
|
46
|
+
return NextResponse.json(
|
|
47
|
+
{ error: "Mã nhóm đã tồn tại", field: "code" },
|
|
48
|
+
{ status: 400 }
|
|
49
|
+
)
|
|
45
50
|
}
|
|
46
51
|
|
|
47
52
|
const userId = session.user.id
|
|
@@ -60,7 +65,7 @@ export const POST = apiHandler(
|
|
|
60
65
|
|
|
61
66
|
return NextResponse.json(created, { status: 201 })
|
|
62
67
|
},
|
|
63
|
-
{ resource: "system-category", action: "create" }
|
|
68
|
+
{ resource: "system-category", action: "create" }
|
|
64
69
|
)
|
|
65
70
|
|
|
66
71
|
export const PUT = apiHandler(
|
|
@@ -68,14 +73,17 @@ export const PUT = apiHandler(
|
|
|
68
73
|
const body = await req.json()
|
|
69
74
|
const id = idFromRequest(req, body)
|
|
70
75
|
if (!id) {
|
|
71
|
-
return NextResponse.json(
|
|
76
|
+
return NextResponse.json(
|
|
77
|
+
{ error: "ID là bắt buộc", field: "id" },
|
|
78
|
+
{ status: 400 }
|
|
79
|
+
)
|
|
72
80
|
}
|
|
73
81
|
|
|
74
82
|
const { code, name } = body as Record<string, unknown>
|
|
75
83
|
if (!code || !name) {
|
|
76
84
|
return NextResponse.json(
|
|
77
85
|
{ error: "Mã và tên nhóm là bắt buộc", field: !code ? "code" : "name" },
|
|
78
|
-
{ status: 400 }
|
|
86
|
+
{ status: 400 }
|
|
79
87
|
)
|
|
80
88
|
}
|
|
81
89
|
|
|
@@ -83,7 +91,10 @@ export const PUT = apiHandler(
|
|
|
83
91
|
where: { code: String(code), id: { not: id } },
|
|
84
92
|
})
|
|
85
93
|
if (existing) {
|
|
86
|
-
return NextResponse.json(
|
|
94
|
+
return NextResponse.json(
|
|
95
|
+
{ error: "Mã nhóm đã tồn tại", field: "code" },
|
|
96
|
+
{ status: 400 }
|
|
97
|
+
)
|
|
87
98
|
}
|
|
88
99
|
|
|
89
100
|
const updated = await db.systemCategoryGroup.update({
|
|
@@ -100,7 +111,7 @@ export const PUT = apiHandler(
|
|
|
100
111
|
|
|
101
112
|
return NextResponse.json(updated)
|
|
102
113
|
},
|
|
103
|
-
{ resource: "system-category", action: "update" }
|
|
114
|
+
{ resource: "system-category", action: "update" }
|
|
104
115
|
)
|
|
105
116
|
|
|
106
117
|
export const DELETE = apiHandler(
|
|
@@ -112,20 +123,27 @@ export const DELETE = apiHandler(
|
|
|
112
123
|
|
|
113
124
|
const group = await db.systemCategoryGroup.findUnique({ where: { id } })
|
|
114
125
|
if (!group) {
|
|
115
|
-
return NextResponse.json(
|
|
126
|
+
return NextResponse.json(
|
|
127
|
+
{ error: "Không tìm thấy nhóm danh mục" },
|
|
128
|
+
{ status: 404 }
|
|
129
|
+
)
|
|
116
130
|
}
|
|
117
131
|
|
|
118
132
|
// Chặn xoá nhóm còn mục con — tránh mồ côi groupCode.
|
|
119
|
-
const childCount = await db.systemCategory.count({
|
|
133
|
+
const childCount = await db.systemCategory.count({
|
|
134
|
+
where: { groupCode: group.code },
|
|
135
|
+
})
|
|
120
136
|
if (childCount > 0) {
|
|
121
137
|
return NextResponse.json(
|
|
122
|
-
{
|
|
123
|
-
|
|
138
|
+
{
|
|
139
|
+
error: `Nhóm còn ${childCount} danh mục con, hãy xóa hoặc chuyển chúng trước`,
|
|
140
|
+
},
|
|
141
|
+
{ status: 400 }
|
|
124
142
|
)
|
|
125
143
|
}
|
|
126
144
|
|
|
127
145
|
await db.systemCategoryGroup.delete({ where: { id } })
|
|
128
146
|
return NextResponse.json({ message: "Đã xóa nhóm danh mục" })
|
|
129
147
|
},
|
|
130
|
-
{ resource: "system-category", action: "delete" }
|
|
148
|
+
{ resource: "system-category", action: "delete" }
|
|
131
149
|
)
|
|
@@ -20,7 +20,7 @@ export const GET = apiHandler(async (req, { session, params }) => {
|
|
|
20
20
|
if (!task || task.status !== "success") {
|
|
21
21
|
return NextResponse.json(
|
|
22
22
|
{ error: "Không tìm thấy tác vụ hoặc tác vụ chưa hoàn tất" },
|
|
23
|
-
{ status: 404 }
|
|
23
|
+
{ status: 404 }
|
|
24
24
|
)
|
|
25
25
|
}
|
|
26
26
|
|
|
@@ -33,9 +33,13 @@ export const GET = apiHandler(async (req, { session, params }) => {
|
|
|
33
33
|
}
|
|
34
34
|
const wantError = new URL(req.url).searchParams.get("file") === "error"
|
|
35
35
|
const fileKey = wantError ? result.errorFileKey : result.fileKey
|
|
36
|
-
const fileName =
|
|
36
|
+
const fileName =
|
|
37
|
+
(wantError ? result.errorFileName : result.fileName) || "download"
|
|
37
38
|
if (!fileKey) {
|
|
38
|
-
return NextResponse.json(
|
|
39
|
+
return NextResponse.json(
|
|
40
|
+
{ error: "Tác vụ không có file kết quả" },
|
|
41
|
+
{ status: 404 }
|
|
42
|
+
)
|
|
39
43
|
}
|
|
40
44
|
|
|
41
45
|
const { readTaskFile } = await import("@/server/tasks")
|
|
@@ -12,7 +12,8 @@ import { db } from "@/lib/prisma"
|
|
|
12
12
|
export const GET = apiHandler(async (req, { session }) => {
|
|
13
13
|
try {
|
|
14
14
|
const takeRaw = Number(new URL(req.url).searchParams.get("take"))
|
|
15
|
-
const take =
|
|
15
|
+
const take =
|
|
16
|
+
Number.isFinite(takeRaw) && takeRaw > 0 ? Math.min(takeRaw, 100) : 20
|
|
16
17
|
|
|
17
18
|
const tasks = await db.backgroundTask.findMany({
|
|
18
19
|
where: { createdBy: session.user.id },
|
|
@@ -32,6 +33,8 @@ export const GET = apiHandler(async (req, { session }) => {
|
|
|
32
33
|
})
|
|
33
34
|
return NextResponse.json({ tasks })
|
|
34
35
|
} catch (error) {
|
|
35
|
-
return serverError(error, req, {
|
|
36
|
+
return serverError(error, req, {
|
|
37
|
+
message: "Không tải được danh sách tác vụ",
|
|
38
|
+
})
|
|
36
39
|
}
|
|
37
40
|
})
|
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
import { NextResponse } from "next/server"
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
bumpPermissionsVersion,
|
|
4
|
+
invalidateUserPermissions,
|
|
5
|
+
} from "@goerp/core/rbac"
|
|
3
6
|
import bcrypt from "bcryptjs"
|
|
4
7
|
|
|
5
8
|
import { apiHandler } from "@/lib/api-handler"
|
|
@@ -39,7 +42,7 @@ export const GET = apiHandler(
|
|
|
39
42
|
if (!user) {
|
|
40
43
|
return NextResponse.json(
|
|
41
44
|
{ error: "Không tìm thấy người dùng" },
|
|
42
|
-
{ status: 404 }
|
|
45
|
+
{ status: 404 }
|
|
43
46
|
)
|
|
44
47
|
}
|
|
45
48
|
const { userRoles, userBranches, ...base } = user
|
|
@@ -53,10 +56,12 @@ export const GET = apiHandler(
|
|
|
53
56
|
null,
|
|
54
57
|
})
|
|
55
58
|
} catch (error) {
|
|
56
|
-
return serverError(error, req, {
|
|
59
|
+
return serverError(error, req, {
|
|
60
|
+
message: "Lỗi tải thông tin người dùng",
|
|
61
|
+
})
|
|
57
62
|
}
|
|
58
63
|
},
|
|
59
|
-
{ resource: "user", action: "view" }
|
|
64
|
+
{ resource: "user", action: "view" }
|
|
60
65
|
)
|
|
61
66
|
|
|
62
67
|
export const PUT = apiHandler(
|
|
@@ -81,19 +86,19 @@ export const PUT = apiHandler(
|
|
|
81
86
|
if (!name) {
|
|
82
87
|
return NextResponse.json(
|
|
83
88
|
{ error: "Tên là bắt buộc", field: "name" },
|
|
84
|
-
{ status: 400 }
|
|
89
|
+
{ status: 400 }
|
|
85
90
|
)
|
|
86
91
|
}
|
|
87
92
|
if (!email) {
|
|
88
93
|
return NextResponse.json(
|
|
89
94
|
{ error: "Email là bắt buộc", field: "email" },
|
|
90
|
-
{ status: 400 }
|
|
95
|
+
{ status: 400 }
|
|
91
96
|
)
|
|
92
97
|
}
|
|
93
98
|
if (password && password.length < 6) {
|
|
94
99
|
return NextResponse.json(
|
|
95
100
|
{ error: "Mật khẩu phải có ít nhất 6 ký tự", field: "password" },
|
|
96
|
-
{ status: 400 }
|
|
101
|
+
{ status: 400 }
|
|
97
102
|
)
|
|
98
103
|
}
|
|
99
104
|
|
|
@@ -104,7 +109,7 @@ export const PUT = apiHandler(
|
|
|
104
109
|
if (duplicated) {
|
|
105
110
|
return NextResponse.json(
|
|
106
111
|
{ error: "Email đã tồn tại", field: "email" },
|
|
107
|
-
{ status: 400 }
|
|
112
|
+
{ status: 400 }
|
|
108
113
|
)
|
|
109
114
|
}
|
|
110
115
|
|
|
@@ -191,18 +196,21 @@ export const PUT = apiHandler(
|
|
|
191
196
|
return serverError(error, req, { message: "Lỗi cập nhật người dùng" })
|
|
192
197
|
}
|
|
193
198
|
},
|
|
194
|
-
{ resource: "user", action: "update" }
|
|
199
|
+
{ resource: "user", action: "update" }
|
|
195
200
|
)
|
|
196
201
|
|
|
197
202
|
export const DELETE = apiHandler(
|
|
198
203
|
async (req, { params }) => {
|
|
199
204
|
try {
|
|
200
205
|
const { id } = await params
|
|
201
|
-
const user = await db.user.findUnique({
|
|
206
|
+
const user = await db.user.findUnique({
|
|
207
|
+
where: { id },
|
|
208
|
+
select: { id: true },
|
|
209
|
+
})
|
|
202
210
|
if (!user) {
|
|
203
211
|
return NextResponse.json(
|
|
204
212
|
{ error: "Không tìm thấy người dùng" },
|
|
205
|
-
{ status: 404 }
|
|
213
|
+
{ status: 404 }
|
|
206
214
|
)
|
|
207
215
|
}
|
|
208
216
|
|
|
@@ -218,5 +226,5 @@ export const DELETE = apiHandler(
|
|
|
218
226
|
return serverError(error, req, { message: "Lỗi vô hiệu hóa người dùng" })
|
|
219
227
|
}
|
|
220
228
|
},
|
|
221
|
-
{ resource: "user", action: "delete" }
|
|
229
|
+
{ resource: "user", action: "delete" }
|
|
222
230
|
)
|