@gadmin2n/schematics 0.0.65 → 0.0.67

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (72) hide show
  1. package/dist/lib/application/files/gadmin2-game-angle-demo/config/prisma/system.prisma +7 -0
  2. package/dist/lib/application/files/gadmin2-game-angle-demo/server/package.json +3 -3
  3. package/dist/lib/application/files/gadmin2-game-angle-demo/web/package.json +4 -4
  4. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/authProvider.ts +2 -2
  5. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/components/SqlModal.tsx +419 -0
  6. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/components/contexts/business/index.tsx +1 -1
  7. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/components/custom-avatar.tsx +1 -1
  8. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/config/http.ts +28 -0
  9. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/config/routeRegistry.tsx +1 -1
  10. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/helpers/http.ts +87 -0
  11. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/helpers/index.tsx +6 -1
  12. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/helpers/login.ts +22 -77
  13. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/{utilities → helpers}/utils.tsx +1 -1
  14. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/audit/components/action-cell.tsx +1 -1
  15. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/page/Components/AssignRolesModal.tsx +3 -3
  16. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/page/Components/PageDetailDrawer.tsx +1 -1
  17. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/page/Components/PageFormModal.tsx +1 -1
  18. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/page/hooks/usePageManagement.ts +1 -1
  19. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/page/index.ts +0 -7
  20. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/page/list.tsx +110 -8
  21. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/page/types.ts +1 -0
  22. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/{permission-readme → permissionReadme}/index.tsx +4 -3
  23. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/resource/Components/ResourceDetailDrawer.tsx +1 -1
  24. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/resource/index.ts +0 -7
  25. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/resource/list.tsx +32 -4
  26. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/role/Components/RoleDetailDrawer.tsx +1 -1
  27. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/role/Components/modal.tsx +1 -1
  28. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/role/hooks/useRolePage.ts +1 -1
  29. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/role/index.ts +1 -8
  30. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/role/list.tsx +52 -3
  31. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/user/components/form-modal.tsx +2 -2
  32. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/user/components/show-drawer.tsx +2 -2
  33. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/user/index.ts +1 -8
  34. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/user/list.tsx +41 -11
  35. package/dist/lib/application/files/gadmin2-game-angle-demo/web/tsconfig.json +2 -1
  36. package/dist/lib/application/files/gadmin2-game-angle-demo/web/yarn.lock +8321 -0
  37. package/package.json +1 -1
  38. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/helpers/utils.ts +0 -76
  39. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/page/create.tsx +0 -113
  40. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/page/edit.tsx +0 -122
  41. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/page/index.tsx +0 -6
  42. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/page/show.tsx +0 -61
  43. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/pageResource/create.tsx +0 -113
  44. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/pageResource/edit.tsx +0 -122
  45. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/pageResource/index.tsx +0 -6
  46. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/pageResource/list.tsx +0 -243
  47. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/pageResource/show.tsx +0 -61
  48. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/resource/create.tsx +0 -113
  49. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/resource/edit.tsx +0 -122
  50. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/resource/index.tsx +0 -6
  51. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/resource/show.tsx +0 -61
  52. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/role/create.tsx +0 -113
  53. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/role/edit.tsx +0 -122
  54. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/role/index.tsx +0 -6
  55. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/role/show.tsx +0 -61
  56. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/rolePages/create.tsx +0 -113
  57. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/rolePages/edit.tsx +0 -122
  58. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/rolePages/index.tsx +0 -6
  59. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/rolePages/list.tsx +0 -243
  60. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/rolePages/show.tsx +0 -61
  61. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/roleResource/create.tsx +0 -113
  62. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/roleResource/edit.tsx +0 -122
  63. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/roleResource/index.tsx +0 -6
  64. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/roleResource/list.tsx +0 -243
  65. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/roleResource/show.tsx +0 -61
  66. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/user/create.tsx +0 -113
  67. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/user/edit.tsx +0 -122
  68. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/user/index.tsx +0 -6
  69. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/user/show.tsx +0 -61
  70. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/utilities/index.ts +0 -2
  71. /package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/{utilities → helpers}/get-name-initials.ts +0 -0
  72. /package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/{utilities → helpers}/get-random-color.ts +0 -0
@@ -6,6 +6,7 @@ model Game {
6
6
  @@map("t_admin_game")
7
7
  }
8
8
 
9
+ /// @IgnoreReactCode
9
10
  /// @IgnoreAutoField
10
11
  model User {
11
12
  id Int @id @default(autoincrement())
@@ -45,6 +46,7 @@ model Audit {
45
46
  @@map("t_admin_audit_log")
46
47
  }
47
48
 
49
+ /// @IgnoreReactCode
48
50
  /// @IgnoreAutoField
49
51
  model Resource {
50
52
  id Int @id @default(autoincrement())
@@ -62,6 +64,7 @@ model Resource {
62
64
  @@map("t_resource")
63
65
  }
64
66
 
67
+ /// @IgnoreReactCode
65
68
  /// @IgnoreAutoField
66
69
  model Role {
67
70
  id Int @id @default(autoincrement())
@@ -81,6 +84,7 @@ model Role {
81
84
  @@map("t_role")
82
85
  }
83
86
 
87
+ /// @IgnoreReactCode
84
88
  /// @IgnoreAutoField
85
89
  model Page {
86
90
  id Int @id @default(autoincrement())
@@ -108,6 +112,7 @@ model Page {
108
112
  @@map("t_page")
109
113
  }
110
114
 
115
+ /// @IgnoreReactCode
111
116
  /// @IgnoreAutoField
112
117
  model RoleResource {
113
118
  id Int @id @default(autoincrement())
@@ -127,6 +132,7 @@ model RoleResource {
127
132
  @@map("t_role_resource")
128
133
  }
129
134
 
135
+ /// @IgnoreReactCode
130
136
  /// @IgnoreAutoField
131
137
  model PageResource {
132
138
  id Int @id @default(autoincrement())
@@ -145,6 +151,7 @@ model PageResource {
145
151
  @@map("t_page_resource")
146
152
  }
147
153
 
154
+ /// @IgnoreReactCode
148
155
  /// @IgnoreAutoField
149
156
  model RolePages {
150
157
  id Int @id @default(autoincrement())
@@ -35,7 +35,7 @@
35
35
  },
36
36
  "dependencies": {
37
37
  "@azure/identity": "^4.13.0",
38
- "@gadmin2n/nest-common": "^0.0.33",
38
+ "@gadmin2n/nest-common": "^0.0.38",
39
39
  "@nestjs/cache-manager": "^3.0.1",
40
40
  "@nestjs/common": "^10.4.15",
41
41
  "@nestjs/config": "^3.2.0",
@@ -84,8 +84,8 @@
84
84
  "xlsx": "^0.18.5"
85
85
  },
86
86
  "devDependencies": {
87
- "@gadmin2n/prisma-nest-generator": "^0.0.30",
88
- "@gadmin2n/prisma-react-generator": "^0.0.46",
87
+ "@gadmin2n/prisma-nest-generator": "^0.0.32",
88
+ "@gadmin2n/prisma-react-generator": "^0.0.48",
89
89
  "@nestjs/testing": "^10.4.15",
90
90
  "@types/cookie-parser": "^1.4.3",
91
91
  "@types/express": "^4.17.21",
@@ -10,7 +10,7 @@
10
10
  "@dnd-kit/modifiers": "^9.0.0",
11
11
  "@dnd-kit/sortable": "^7.0.2",
12
12
  "@dnd-kit/utilities": "^3.2.2",
13
- "@gadmin2n/react-common": "^0.0.55",
13
+ "@gadmin2n/react-common": "^0.0.58",
14
14
  "@refinedev/antd": "^5.47.0",
15
15
  "@refinedev/cli": "^2.16.51",
16
16
  "@refinedev/core": "^4.58.0",
@@ -55,12 +55,12 @@
55
55
  "@types/react-dom": "^18.0.6",
56
56
  "@vitejs/plugin-react": "^4.3.4",
57
57
  "autoprefixer": "^10.4.24",
58
- "postcss": "^8.5.6",
59
- "postcss-nesting": "^14.0.0",
60
- "typescript": "^5.4.2",
61
58
  "husky": "^8.0.0",
62
59
  "lint-staged": "^13.0.0",
60
+ "postcss": "^8.5.6",
61
+ "postcss-nesting": "^14.0.0",
63
62
  "prettier": "^3.0.0",
63
+ "typescript": "^5.4.2",
64
64
  "vite": "^6.2.0",
65
65
  "vite-tsconfig-paths": "^5.1.4"
66
66
  },
@@ -1,6 +1,6 @@
1
1
  import { AuthBindings } from '@refinedev/core';
2
- import { getApiUrl } from 'helpers/utils';
3
- import { requestHeaders } from 'helpers/login';
2
+ import { getApiUrl } from 'config/http';
3
+ import { requestHeaders } from 'helpers';
4
4
  import memoize from 'lodash.memoize';
5
5
 
6
6
  const getUserInfoOnce = memoize(async () => {
@@ -0,0 +1,419 @@
1
+ import React, { useState } from "react";
2
+ import { Modal, Button, Typography, message, Tooltip } from "antd";
3
+ import { CopyOutlined, CheckOutlined } from "@ant-design/icons";
4
+
5
+ const { Text } = Typography;
6
+
7
+ interface SqlModalProps {
8
+ open: boolean;
9
+ onClose: () => void;
10
+ title: string;
11
+ sql: string;
12
+ }
13
+
14
+ export const SqlModal: React.FC<SqlModalProps> = ({ open, onClose, title, sql }) => {
15
+ const [copied, setCopied] = useState(false);
16
+
17
+ const handleCopy = () => {
18
+ navigator.clipboard.writeText(sql).then(() => {
19
+ setCopied(true);
20
+ message.success("SQL copied to clipboard");
21
+ setTimeout(() => setCopied(false), 2000);
22
+ });
23
+ };
24
+
25
+ return (
26
+ <Modal
27
+ title={title}
28
+ open={open}
29
+ onCancel={onClose}
30
+ width={720}
31
+ footer={[
32
+ <Tooltip key="copy" title={copied ? "Copied!" : "Copy SQL"}>
33
+ <Button
34
+ icon={copied ? <CheckOutlined /> : <CopyOutlined />}
35
+ type="primary"
36
+ onClick={handleCopy}
37
+ >
38
+ {copied ? "Copied!" : "Copy SQL"}
39
+ </Button>
40
+ </Tooltip>,
41
+ <Button key="close" onClick={onClose}>
42
+ Close
43
+ </Button>,
44
+ ]}
45
+ >
46
+ <pre
47
+ style={{
48
+ background: "#1e1e1e",
49
+ color: "#d4d4d4",
50
+ padding: "16px",
51
+ borderRadius: "6px",
52
+ overflowX: "auto",
53
+ overflowY: "auto",
54
+ maxHeight: "500px",
55
+ fontSize: "12px",
56
+ lineHeight: "1.6",
57
+ whiteSpace: "pre-wrap",
58
+ wordBreak: "break-word",
59
+ fontFamily: "'Fira Code', 'Cascadia Code', 'Consolas', 'Monaco', monospace",
60
+ }}
61
+ >
62
+ <Text
63
+ style={{
64
+ color: "inherit",
65
+ fontSize: "inherit",
66
+ fontFamily: "inherit",
67
+ }}
68
+ >
69
+ {sql}
70
+ </Text>
71
+ </pre>
72
+ </Modal>
73
+ );
74
+ };
75
+
76
+ // ─── SQL Generators ────────────────────────────────────────────────────────────
77
+
78
+ function escapeStr(val: string | null | undefined): string {
79
+ if (val == null) return "NULL";
80
+ return `'${String(val).replace(/'/g, "''")}'`;
81
+ }
82
+
83
+ function jsonArr(val: string | null | undefined): string {
84
+ // roles is stored as a JSON-encoded array string in the DB, e.g. '["GUEST","PRODUCT"]'
85
+ if (val == null) return "'{}'";
86
+ return `'${String(val).replace(/'/g, "''")}'`;
87
+ }
88
+
89
+ /**
90
+ * Generate an INSERT for t_admin_audit_log.
91
+ * authorId / authorName come from the currently logged-in user passed by the caller.
92
+ */
93
+ function auditInsert(opts: {
94
+ action: string;
95
+ resource: string;
96
+ resourceId: string;
97
+ data: string; // JSON string of the record
98
+ creator?: string | null;
99
+ authorId?: string | null;
100
+ authorName?: string | null;
101
+ }): string {
102
+ const dataJson = escapeStr(opts.data);
103
+ const authorId = opts.authorId ?? "<author_id>";
104
+ const authorName = opts.authorName ?? "<author_name>";
105
+ return `INSERT INTO public.t_admin_audit_log (creator, "authorId", "authorName", action, resource, "resourceId", data, "previousData")
106
+ VALUES (
107
+ ${escapeStr(authorId)},
108
+ ${escapeStr(authorId)},
109
+ ${escapeStr(authorName)},
110
+ ${escapeStr(opts.action)},
111
+ ${escapeStr(opts.resource)},
112
+ ${escapeStr(opts.resourceId)},
113
+ ${dataJson},
114
+ NULL
115
+ );`;
116
+ }
117
+
118
+ /**
119
+ * Generate INSERT SQL for a User record.
120
+ * Table: t_admin_user_role
121
+ */
122
+ export function generateUserSql(user: {
123
+ userid: string;
124
+ username?: string | null;
125
+ roles?: string | null;
126
+ isApiKey?: boolean;
127
+ apiKey?: string | null;
128
+ description?: string | null;
129
+ isActive?: boolean;
130
+ creator?: string | null;
131
+ }, operator?: { authorId?: string | null; authorName?: string | null }): string {
132
+ const rolesValue = user.roles ?? "[]";
133
+ const isApiKey = user.isApiKey ? "true" : "false";
134
+ const isActive = user.isActive !== false ? "true" : "false";
135
+
136
+ const userDataJson = JSON.stringify({
137
+ userid: user.userid,
138
+ username: user.username ?? null,
139
+ roles: rolesValue,
140
+ isApiKey: user.isApiKey ?? false,
141
+ description: user.description ?? null,
142
+ isActive: user.isActive !== false,
143
+ });
144
+
145
+ const mainSql = `-- Insert User: ${user.userid}
146
+ INSERT INTO public.t_admin_user_role (userid, username, roles, "isApiKey", "apiKey", description, "isActive", creator, "createdAt", "updatedAt")
147
+ VALUES (
148
+ ${escapeStr(user.userid)},
149
+ ${escapeStr(user.username)},
150
+ ${jsonArr(rolesValue)},
151
+ ${isApiKey},
152
+ ${user.apiKey ? escapeStr(user.apiKey) : "NULL"},
153
+ ${escapeStr(user.description)},
154
+ ${isActive},
155
+ ${escapeStr(user.creator)},
156
+ NOW(),
157
+ NOW()
158
+ )
159
+ ON CONFLICT (userid) DO UPDATE SET
160
+ username = EXCLUDED.username,
161
+ roles = EXCLUDED.roles,
162
+ "isApiKey" = EXCLUDED."isApiKey",
163
+ "apiKey" = EXCLUDED."apiKey",
164
+ description = EXCLUDED.description,
165
+ "isActive" = EXCLUDED."isActive",
166
+ "updatedAt" = NOW();`;
167
+
168
+ return [
169
+ mainSql,
170
+ "",
171
+ auditInsert({ action: "create", resource: "user", resourceId: user.userid, data: userDataJson, creator: user.creator, ...operator }),
172
+ ].join("\n");
173
+ }
174
+
175
+ /**
176
+ * Generate INSERT SQL for a Resource record.
177
+ * Table: t_resource
178
+ */
179
+ export function generateResourceSql(resource: {
180
+ code: string;
181
+ name: string;
182
+ description?: string | null;
183
+ isCommonResource?: boolean;
184
+ creator?: string | null;
185
+ }, operator?: { authorId?: string | null; authorName?: string | null }): string {
186
+ const isCommon = resource.isCommonResource ? "true" : "false";
187
+
188
+ const resourceDataJson = JSON.stringify({
189
+ code: resource.code,
190
+ name: resource.name,
191
+ description: resource.description ?? null,
192
+ isCommonResource: resource.isCommonResource ?? false,
193
+ });
194
+
195
+ const mainSql = `-- Insert Resource: ${resource.code}
196
+ INSERT INTO public.t_resource (code, name, description, is_common_resource, creator, "createdAt", "updatedAt")
197
+ VALUES (
198
+ ${escapeStr(resource.code)},
199
+ ${escapeStr(resource.name)},
200
+ ${escapeStr(resource.description)},
201
+ ${isCommon},
202
+ ${escapeStr(resource.creator)},
203
+ NOW(),
204
+ NOW()
205
+ )
206
+ ON CONFLICT (code) DO UPDATE SET
207
+ name = EXCLUDED.name,
208
+ description = EXCLUDED.description,
209
+ is_common_resource = EXCLUDED.is_common_resource,
210
+ "updatedAt" = NOW();`;
211
+
212
+ return [
213
+ mainSql,
214
+ "",
215
+ auditInsert({ action: "create", resource: "resource", resourceId: resource.code, data: resourceDataJson, creator: resource.creator, ...operator }),
216
+ ].join("\n");
217
+ }
218
+
219
+ /**
220
+ * Generate INSERT SQL for a Role record, including its page assignments and
221
+ * the resulting role_resource entries.
222
+ *
223
+ * rolePages: list of { pageId, pageName }
224
+ * roleResources: list of { resourceCode, resourceName, actions: string[] }
225
+ */
226
+ export function generateRoleSql(
227
+ role: {
228
+ name: string;
229
+ description?: string | null;
230
+ creator?: string | null;
231
+ },
232
+ rolePages: Array<{ pageId: number; pageName: string; pageCode: string }>,
233
+ roleResources: Array<{ resourceCode: string; resourceName: string; actions: string[] }>,
234
+ operator?: { authorId?: string | null; authorName?: string | null },
235
+ ): string {
236
+ const lines: string[] = [];
237
+
238
+ // 1. Role
239
+ lines.push(`-- ════════════════════════════════════════════════════════`);
240
+ lines.push(`-- Role: ${role.name}`);
241
+ lines.push(`-- ════════════════════════════════════════════════════════`);
242
+ lines.push(`INSERT INTO public.t_role (name, description, creator, "createdAt", "updatedAt")`);
243
+ lines.push(`VALUES (`);
244
+ lines.push(` ${escapeStr(role.name)},`);
245
+ lines.push(` ${escapeStr(role.description)},`);
246
+ lines.push(` ${escapeStr(role.creator)},`);
247
+ lines.push(` NOW(),`);
248
+ lines.push(` NOW()`);
249
+ lines.push(`)`);
250
+ lines.push(`ON CONFLICT (name) DO UPDATE SET`);
251
+ lines.push(` description = EXCLUDED.description,`);
252
+ lines.push(` "updatedAt" = NOW();`);
253
+
254
+ // 2. Role-page associations
255
+ if (rolePages.length > 0) {
256
+ lines.push(``);
257
+ lines.push(`-- Role-Page Associations for "${role.name}"`);
258
+ lines.push(`-- Pages: ${rolePages.map(p => p.pageName).join(", ")}`);
259
+
260
+ for (const rp of rolePages) {
261
+ lines.push(``);
262
+ lines.push(`INSERT INTO public.t_role_pages (role_id, page_id, "created_at", "updated_at")`);
263
+ lines.push(`SELECT r.id, p.id, NOW(), NOW()`);
264
+ lines.push(`FROM public.t_role r, public.t_page p`);
265
+ lines.push(`WHERE r.name = ${escapeStr(role.name)}`);
266
+ lines.push(` AND p.code = ${escapeStr(rp.pageCode)} -- ${rp.pageName}`);
267
+ lines.push(`ON CONFLICT (role_id, page_id) DO NOTHING;`);
268
+ }
269
+ }
270
+
271
+ // 3. Role-resource associations
272
+ if (roleResources.length > 0) {
273
+ lines.push(``);
274
+ lines.push(`-- Role-Resource Permissions for "${role.name}"`);
275
+
276
+ for (const rr of roleResources) {
277
+ const actionsJson = JSON.stringify(rr.actions);
278
+ lines.push(``);
279
+ lines.push(`INSERT INTO public.t_role_resource (role_id, resource_id, actions, "created_at", "updated_at")`);
280
+ lines.push(`SELECT r.id, res.id, '${actionsJson}'::jsonb, NOW(), NOW()`);
281
+ lines.push(`FROM public.t_role r, public.t_resource res`);
282
+ lines.push(`WHERE r.name = ${escapeStr(role.name)}`);
283
+ lines.push(` AND res.code = ${escapeStr(rr.resourceCode)} -- ${rr.resourceName}`);
284
+ lines.push(`ON CONFLICT (role_id, resource_id) DO UPDATE SET`);
285
+ lines.push(` actions = EXCLUDED.actions,`);
286
+ lines.push(` "updated_at" = NOW();`);
287
+ }
288
+ }
289
+
290
+ // 4. Audit log
291
+ const roleDataJson = JSON.stringify({
292
+ name: role.name,
293
+ description: role.description ?? null,
294
+ });
295
+ lines.push(``);
296
+ lines.push(auditInsert({ action: "create", resource: "role", resourceId: role.name, data: roleDataJson, creator: role.creator, ...operator }));
297
+
298
+ return lines.join("\n");
299
+ }
300
+
301
+ /**
302
+ * Generate INSERT SQL for a Page and (optionally) its children.
303
+ *
304
+ * When called on a parent page, pass all child pages in `children`.
305
+ * Each page includes its pageResources (resource assignments).
306
+ *
307
+ * pageResources: list of { resourceCode, resourceName, actions }
308
+ */
309
+ export function generatePageSql(
310
+ page: {
311
+ id: number;
312
+ code: string;
313
+ name: string;
314
+ path?: string | null;
315
+ description?: string | null;
316
+ zhName?: string | null;
317
+ enName?: string | null;
318
+ sortOrder?: number | null;
319
+ parentId?: number | null;
320
+ /** code of the parent page — used to generate a portable subquery instead of a raw numeric ID */
321
+ parentCode?: string | null;
322
+ isVirtual?: boolean;
323
+ creator?: string | null;
324
+ },
325
+ pageResources: Array<{ resourceCode: string; resourceName: string; actions: string[] }>,
326
+ children: Array<{
327
+ page: typeof page;
328
+ pageResources: Array<{ resourceCode: string; resourceName: string; actions: string[] }>;
329
+ }>,
330
+ operator?: { authorId?: string | null; authorName?: string | null },
331
+ ): string {
332
+ const lines: string[] = [];
333
+
334
+ const renderPageInsert = (
335
+ p: typeof page,
336
+ res: Array<{ resourceCode: string; resourceName: string; actions: string[] }>,
337
+ parentRef: string | null,
338
+ ) => {
339
+ lines.push(`-- ────────────────────────────────────────────────────────`);
340
+ lines.push(`-- Page: ${p.name} (code: ${p.code})`);
341
+ lines.push(`-- ────────────────────────────────────────────────────────`);
342
+ lines.push(`INSERT INTO public.t_page (`);
343
+ lines.push(` code, name, path, description, zh_name, en_name, sort_order, parent_id, "isVirtual", creator, created_at, updated_at`);
344
+ lines.push(`)`);
345
+ lines.push(`VALUES (`);
346
+ lines.push(` ${escapeStr(p.code)},`);
347
+ lines.push(` ${escapeStr(p.name)},`);
348
+ lines.push(` ${escapeStr(p.path)},`);
349
+ lines.push(` ${escapeStr(p.description)},`);
350
+ lines.push(` ${escapeStr(p.zhName)},`);
351
+ lines.push(` ${escapeStr(p.enName)},`);
352
+ lines.push(` ${p.sortOrder != null ? p.sortOrder : "NULL"},`);
353
+ if (parentRef) {
354
+ lines.push(` (${parentRef}), -- parent_id resolved by code`);
355
+ } else {
356
+ lines.push(` NULL,`);
357
+ }
358
+ lines.push(` ${p.isVirtual ? "true" : "false"},`);
359
+ lines.push(` ${escapeStr(p.creator)},`);
360
+ lines.push(` NOW(),`);
361
+ lines.push(` NOW()`);
362
+ lines.push(`)`);
363
+ lines.push(`ON CONFLICT (code) DO UPDATE SET`);
364
+ lines.push(` name = EXCLUDED.name,`);
365
+ lines.push(` path = EXCLUDED.path,`);
366
+ lines.push(` description = EXCLUDED.description,`);
367
+ lines.push(` zh_name = EXCLUDED.zh_name,`);
368
+ lines.push(` en_name = EXCLUDED.en_name,`);
369
+ lines.push(` sort_order = EXCLUDED.sort_order,`);
370
+ lines.push(` parent_id = EXCLUDED.parent_id,`);
371
+ lines.push(` updated_at = NOW();`);
372
+
373
+ if (res.length > 0) {
374
+ lines.push(``);
375
+ lines.push(`-- Page-Resource Associations for "${p.name}"`);
376
+ for (const pr of res) {
377
+ const actionsJson = JSON.stringify(pr.actions);
378
+ lines.push(``);
379
+ lines.push(`INSERT INTO public.t_page_resource (page_id, resource_id, actions, "created_at", "updated_at")`);
380
+ lines.push(`SELECT pg.id, res.id, '${actionsJson}'::jsonb, NOW(), NOW()`);
381
+ lines.push(`FROM public.t_page pg, public.t_resource res`);
382
+ lines.push(`WHERE pg.code = ${escapeStr(p.code)}`);
383
+ lines.push(` AND res.code = ${escapeStr(pr.resourceCode)} -- ${pr.resourceName}`);
384
+ lines.push(`ON CONFLICT (page_id, resource_id) DO UPDATE SET`);
385
+ lines.push(` actions = EXCLUDED.actions,`);
386
+ lines.push(` "updated_at" = NOW();`);
387
+ }
388
+ }
389
+
390
+ // Audit log for this page
391
+ const pageDataJson = JSON.stringify({
392
+ code: p.code,
393
+ name: p.name,
394
+ path: p.path ?? null,
395
+ description: p.description ?? null,
396
+ zhName: p.zhName ?? null,
397
+ enName: p.enName ?? null,
398
+ sortOrder: p.sortOrder ?? null,
399
+ isVirtual: p.isVirtual ?? false,
400
+ });
401
+ lines.push(``);
402
+ lines.push(auditInsert({ action: "create", resource: "page", resourceId: p.code, data: pageDataJson, creator: p.creator, ...operator }));
403
+ };
404
+
405
+ // Parent page — if it has a parentCode, resolve parent_id dynamically by code
406
+ const rootParentRef = page.parentCode
407
+ ? `SELECT id FROM public.t_page WHERE code = ${escapeStr(page.parentCode)}`
408
+ : null;
409
+ renderPageInsert(page, pageResources, rootParentRef);
410
+
411
+ // Children
412
+ for (const child of children) {
413
+ lines.push(``);
414
+ const parentRef = `SELECT id FROM public.t_page WHERE code = ${escapeStr(page.code)}`;
415
+ renderPageInsert(child.page, child.pageResources, parentRef);
416
+ }
417
+
418
+ return lines.join("\n");
419
+ }
@@ -1,5 +1,5 @@
1
1
  import { PropsWithChildren, createContext, useEffect, useState } from "react";
2
- import { findMany } from "helpers/utils";
2
+ import { findMany } from "helpers";
3
3
  import { Game } from "generated/types/prisma.types";
4
4
 
5
5
  export type BusinessInfo = {
@@ -3,7 +3,7 @@ import React, { memo } from "react";
3
3
  import type { AvatarProps } from "antd";
4
4
  import { Avatar as AntdAvatar } from "antd";
5
5
 
6
- import { getNameInitials, getRandomColorFromString } from "utilities";
6
+ import { getNameInitials, getRandomColorFromString } from "helpers";
7
7
 
8
8
  type Props = AvatarProps & {
9
9
  name?: string;
@@ -0,0 +1,28 @@
1
+ // ─── URL / 部署配置 ───────────────────────────────────────────────────────────
2
+ /**
3
+ * 读取部署子路径(Vite env: VITE_DEPLOY_NAME)
4
+ * 本地开发不设置时返回空字符串
5
+ */
6
+ export function getUrlBasename(): string {
7
+ if (
8
+ typeof import.meta !== "undefined" &&
9
+ import.meta.env?.VITE_DEPLOY_NAME?.trim()
10
+ ) {
11
+ return `/${import.meta.env.VITE_DEPLOY_NAME.trim()}`;
12
+ }
13
+ return "";
14
+ }
15
+
16
+ /**
17
+ * 读取后端 API 根路径(Vite env: VITE_API_URL 优先)
18
+ * 本地开发时自动将前端 3000 端口映射到后端 8000 端口
19
+ */
20
+ export function getApiUrl(): string {
21
+ if (
22
+ typeof import.meta !== "undefined" &&
23
+ import.meta.env?.VITE_API_URL?.trim()
24
+ ) {
25
+ return import.meta.env.VITE_API_URL.trim();
26
+ }
27
+ return `${window.location.origin.replace("3000", "8000")}${getUrlBasename()}/api`;
28
+ }
@@ -110,7 +110,7 @@ export const routeRegistry: Record<string, RouteEntry> = {
110
110
  permission_readme: {
111
111
  path: "readme",
112
112
  component: lazy(() =>
113
- import("routes/permission-readme").then((m) => ({
113
+ import("routes/permissionReadme").then((m) => ({
114
114
  default: m.PermissionReadmePage,
115
115
  })),
116
116
  ),
@@ -0,0 +1,87 @@
1
+ import axios from "axios";
2
+ import {
3
+ GadminCrud as gadminDataProvider,
4
+ applyErrorInterceptor,
5
+ customRequest as baseCustomRequest,
6
+ } from "@gadmin2n/react-common";
7
+ import { login, requestHeaders } from "./login";
8
+ import { getApiUrl } from "config/http";
9
+ // Re-export Prisma helpers from react-common for backward compatibility
10
+ export { convertPrismaDecimal, isPrismaDecimal } from "@gadmin2n/react-common";
11
+
12
+ const apiUrl = getApiUrl();
13
+
14
+ const axiosInstance = axios.create();
15
+
16
+ // 注入 auth header
17
+ axiosInstance.interceptors.request.use(
18
+ (config) => {
19
+ if (config.headers) {
20
+ Object.assign(config.headers, requestHeaders());
21
+ }
22
+ return config;
23
+ },
24
+ (error) => Promise.reject(error)
25
+ );
26
+
27
+ // 处理 401 跳转登录、403 跳权限页(app 层特有逻辑,须在 applyErrorInterceptor 之前注册)
28
+ axiosInstance.interceptors.response.use(
29
+ (response) => response,
30
+ (error) => {
31
+ if (error?.response?.status === 401) {
32
+ console.log("login expired");
33
+ login();
34
+ // 返回pending状态的promise,以免后续代码出错
35
+ return new Promise(() => {});
36
+ }
37
+ if (error?.response?.status === 403) {
38
+ // 已经在 /403 页面则不再跳转,防止死循环
39
+ if (!window.location.pathname.endsWith("/403")) {
40
+ window.location.href = "/403";
41
+ }
42
+ return new Promise(() => {});
43
+ }
44
+ return Promise.reject(error);
45
+ }
46
+ );
47
+
48
+ // 统一错误格式化(复用公共库逻辑,消除重复代码)
49
+ applyErrorInterceptor(axiosInstance);
50
+
51
+
52
+
53
+ export const gadminCrudProvider = gadminDataProvider(apiUrl, axiosInstance);
54
+
55
+ /**
56
+ * 直接用 fetch 发 findMany 请求(绕开 refine DataProvider,适合非 React 上下文)
57
+ */
58
+ export async function findMany<T>(
59
+ resource: string,
60
+ params: Record<string, any>
61
+ ): Promise<T[]> {
62
+ const url = `${apiUrl}/${resource}/findMany`;
63
+ const res = await fetch(url, {
64
+ method: "POST",
65
+ headers: {
66
+ "Content-Type": "application/json",
67
+ ...requestHeaders(),
68
+ },
69
+ body: JSON.stringify(params),
70
+ });
71
+ const data = await res.json();
72
+ return data.data as T[];
73
+ }
74
+
75
+ /**
76
+ * 向后端发送原始 HTTP 请求(绕开 refine DataProvider)。
77
+ * 底层复用公共库的 customRequest,自动携带当前 app 的 auth header 及错误拦截。
78
+ */
79
+ export async function customRequest<T>(
80
+ urlPath: string,
81
+ method: string,
82
+ params?: Record<string, any>
83
+ ) {
84
+ return baseCustomRequest<T>(axiosInstance, apiUrl, urlPath, method, params);
85
+ }
86
+
87
+
@@ -1,3 +1,8 @@
1
1
  export * from './list';
2
2
  export * from './form';
3
- export * from './show';
3
+ export * from './show';
4
+ export * from './login';
5
+ export * from './utils';
6
+ export * from './http';
7
+ export * from './get-name-initials';
8
+ export * from './get-random-color';