@simplysm/sd-cli 13.0.70 → 13.0.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.
Files changed (103) hide show
  1. package/dist/commands/init.d.ts +4 -5
  2. package/dist/commands/init.d.ts.map +1 -1
  3. package/dist/commands/init.js +26 -8
  4. package/dist/commands/init.js.map +1 -1
  5. package/dist/sd-cli-entry.d.ts.map +1 -1
  6. package/dist/sd-cli-entry.js +0 -20
  7. package/dist/sd-cli-entry.js.map +1 -1
  8. package/package.json +4 -4
  9. package/src/commands/init.ts +40 -21
  10. package/src/sd-cli-entry.ts +0 -24
  11. package/templates/init/{.prettierrc.yaml.hbs → .prettierrc.yaml} +1 -1
  12. package/templates/init/eslint.config.ts +15 -0
  13. package/templates/init/mise.toml +3 -0
  14. package/templates/init/package.json.hbs +8 -7
  15. package/templates/init/packages/client-admin/index.html.hbs +144 -0
  16. package/templates/init/packages/client-admin/package.json.hbs +26 -0
  17. package/templates/init/packages/client-admin/public/assets/logo-landscape.png +0 -0
  18. package/templates/init/packages/client-admin/public/assets/logo.png +0 -0
  19. package/templates/init/packages/client-admin/src/App.tsx +42 -0
  20. package/templates/init/packages/client-admin/src/dev/DevDialog.tsx +34 -0
  21. package/templates/{add-client/__CLIENT__/src/main.css.hbs → init/packages/client-admin/src/main.css} +1 -1
  22. package/templates/init/packages/client-admin/src/main.tsx.hbs +146 -0
  23. package/templates/init/packages/client-admin/src/providers/AppServiceProvider.tsx.hbs +103 -0
  24. package/templates/init/packages/client-admin/src/providers/AppStructureProvider.tsx +84 -0
  25. package/templates/init/packages/client-admin/src/providers/AuthProvider.tsx.hbs +71 -0
  26. package/templates/init/packages/client-admin/src/providers/configureSharedData.ts.hbs +67 -0
  27. package/templates/init/packages/client-admin/src/views/auth/LoginView.tsx +132 -0
  28. package/templates/init/packages/client-admin/src/views/home/HomeView.tsx +108 -0
  29. package/templates/init/packages/client-admin/src/views/home/base/employee/EmployeeDetail.tsx.hbs +262 -0
  30. package/templates/init/packages/client-admin/src/views/home/base/employee/EmployeeSheet.tsx.hbs +271 -0
  31. package/templates/init/packages/client-admin/src/views/home/base/role-permission/RoleDetail.tsx.hbs +154 -0
  32. package/templates/init/packages/client-admin/src/views/home/base/role-permission/RolePermissionDetail.tsx.hbs +123 -0
  33. package/templates/init/packages/client-admin/src/views/home/base/role-permission/RolePermissionView.tsx +52 -0
  34. package/templates/init/packages/client-admin/src/views/home/base/role-permission/RoleSheet.tsx.hbs +125 -0
  35. package/templates/init/packages/client-admin/src/views/home/main/MainView.tsx.hbs +13 -0
  36. package/templates/init/packages/client-admin/src/views/home/my-info/MyInfoDetail.tsx.hbs +248 -0
  37. package/templates/init/packages/client-admin/src/views/home/system/system-log/SystemLogSheet.tsx.hbs +169 -0
  38. package/templates/init/packages/client-admin/src/views/not-found/NotFoundView.tsx +15 -0
  39. package/templates/init/packages/client-admin/tailwind.config.ts +10 -0
  40. package/templates/init/packages/db-main/package.json.hbs +13 -0
  41. package/templates/init/packages/db-main/src/MainDbContext.ts +20 -0
  42. package/templates/init/packages/db-main/src/dataLogExt.ts +127 -0
  43. package/templates/init/packages/db-main/src/index.ts +10 -0
  44. package/templates/init/packages/db-main/src/tables/Employee.ts +24 -0
  45. package/templates/init/packages/db-main/src/tables/EmployeeConfig.ts +13 -0
  46. package/templates/init/packages/db-main/src/tables/Role.ts +9 -0
  47. package/templates/init/packages/db-main/src/tables/RolePermission.ts +13 -0
  48. package/templates/init/packages/db-main/src/tables/_DataLog.ts +19 -0
  49. package/templates/init/packages/db-main/src/tables/_Log.ts +16 -0
  50. package/templates/init/packages/server/package.json.hbs +20 -0
  51. package/templates/init/packages/server/public-dev/dev//354/264/210/352/270/260/355/231/224.xlsx +0 -0
  52. package/templates/init/packages/server/src/index.ts +4 -0
  53. package/templates/init/packages/server/src/main.ts.hbs +34 -0
  54. package/templates/init/packages/server/src/services/AuthService.ts.hbs +171 -0
  55. package/templates/init/packages/server/src/services/DevService.ts.hbs +94 -0
  56. package/templates/init/packages/server/src/services/EmployeeService.ts.hbs +122 -0
  57. package/templates/init/packages/server/src/services/RoleService.ts.hbs +59 -0
  58. package/templates/init/{pnpm-workspace.yaml.hbs → pnpm-workspace.yaml} +3 -1
  59. package/templates/init/sd.config.ts.hbs +30 -1
  60. package/templates/init/tests/e2e/package.json.hbs +16 -0
  61. package/templates/init/tests/e2e/src/e2e.spec.ts +36 -0
  62. package/templates/init/tests/e2e/src/employee-crud.ts +204 -0
  63. package/templates/init/tests/e2e/src/login.ts +61 -0
  64. package/templates/init/tests/e2e/vitest.setup.ts.hbs +220 -0
  65. package/templates/init/tsconfig.json.hbs +0 -11
  66. package/templates/init/{vitest.config.ts.hbs → vitest.config.ts} +16 -12
  67. package/tests/infra/WorkerManager.spec.ts +1 -1
  68. package/tests/replace-deps.spec.ts +2 -2
  69. package/tests/run-lint.spec.ts +6 -6
  70. package/tests/run-typecheck.spec.ts +3 -3
  71. package/tests/sd-cli.spec.ts +1 -1
  72. package/dist/commands/add-client.d.ts +0 -18
  73. package/dist/commands/add-client.d.ts.map +0 -1
  74. package/dist/commands/add-client.js +0 -79
  75. package/dist/commands/add-client.js.map +0 -6
  76. package/dist/commands/add-server.d.ts +0 -18
  77. package/dist/commands/add-server.d.ts.map +0 -1
  78. package/dist/commands/add-server.js +0 -83
  79. package/dist/commands/add-server.js.map +0 -6
  80. package/dist/utils/config-editor.d.ts +0 -17
  81. package/dist/utils/config-editor.d.ts.map +0 -1
  82. package/dist/utils/config-editor.js +0 -79
  83. package/dist/utils/config-editor.js.map +0 -6
  84. package/src/commands/add-client.ts +0 -126
  85. package/src/commands/add-server.ts +0 -138
  86. package/src/utils/config-editor.ts +0 -141
  87. package/templates/add-client/__CLIENT__/index.html.hbs +0 -13
  88. package/templates/add-client/__CLIENT__/package.json.hbs +0 -16
  89. package/templates/add-client/__CLIENT__/src/App.tsx.hbs +0 -65
  90. package/templates/add-client/__CLIENT__/src/appStructure.ts.hbs +0 -20
  91. package/templates/add-client/__CLIENT__/src/main.tsx.hbs +0 -24
  92. package/templates/add-client/__CLIENT__/src/pages/HomePage.tsx.hbs +0 -9
  93. package/templates/add-client/__CLIENT__/tailwind.config.ts.hbs +0 -15
  94. package/templates/add-server/__SERVER__/package.json.hbs +0 -10
  95. package/templates/add-server/__SERVER__/src/main.ts.hbs +0 -14
  96. package/templates/init/.gitignore.hbs +0 -26
  97. package/templates/init/.npmrc.hbs +0 -1
  98. package/templates/init/eslint.config.ts.hbs +0 -5
  99. package/templates/init/mise.toml.hbs +0 -3
  100. package/tests/config-editor.spec.ts +0 -160
  101. /package/templates/init/{.prettierignore.hbs → .prettierignore} +0 -0
  102. /package/templates/{add-client/__CLIENT__ → init/packages/client-admin}/public/favicon.ico +0 -0
  103. /package/templates/init/{stylelint.config.ts.hbs → stylelint.config.ts} +0 -0
@@ -0,0 +1,248 @@
1
+ import { createMemo } from "solid-js";
2
+ import { CrudDetail, FormTable, Icon, Select, TextInput } from "@simplysm/solid";
3
+ import { useAuth } from "../../../providers/AuthProvider";
4
+ import { useAppService } from "../../../providers/AppServiceProvider";
5
+ import { useAppStructure } from "../../../providers/AppStructureProvider";
6
+ import { expr } from "@{{projectName}}/db-main";
7
+ import { IconX } from "@tabler/icons-solidjs";
8
+ import { jsonParse } from "@simplysm/core-common";
9
+
10
+ type DetailData = {
11
+ email: string;
12
+ currentPassword: string;
13
+ newPassword: string;
14
+ confirmPassword: string;
15
+ firstRouterLink?: string;
16
+ };
17
+
18
+ export function MyInfoDetail() {
19
+ const auth = useAuth();
20
+ const appService = useAppService();
21
+ const appStructure = useAppStructure();
22
+
23
+ const routeMap = createMemo(() => {
24
+ const menus = appStructure.usableMenus();
25
+ const map = new Map<string, string>();
26
+
27
+ const flatten = (items: typeof menus, prefix = ""): void => {
28
+ for (const item of items) {
29
+ const label = prefix !== "" ? `${prefix} > ${item.title}` : item.title;
30
+ if (item.href !== undefined) {
31
+ map.set(item.href, label);
32
+ }
33
+ if (item.children !== undefined) {
34
+ flatten(item.children, label);
35
+ }
36
+ }
37
+ };
38
+
39
+ flatten(menus);
40
+ return map;
41
+ });
42
+
43
+ const routeOptions = createMemo(() => Array.from(routeMap().keys()));
44
+
45
+ async function handleLoad() {
46
+ const authData = auth.authInfo();
47
+ if (!authData) throw new Error("직원 정보를 불러올 수 없습니다.");
48
+
49
+ return appService.orm.connect(async (db) => {
50
+ const employee = await db
51
+ .employee()
52
+ .where((c: any) => [expr.eq(c.id, authData.employeeId)])
53
+ .single();
54
+
55
+ const config = await db
56
+ .employeeConfig()
57
+ .where((c: any) => [
58
+ expr.eq(c.employeeId, authData.employeeId),
59
+ expr.eq(c.code, "firstRouterLink"),
60
+ ])
61
+ .first();
62
+
63
+ let firstRouterLink: string | undefined;
64
+ if (config?.valueJson !== undefined && config.valueJson !== "") {
65
+ try {
66
+ firstRouterLink = jsonParse(config.valueJson);
67
+ } catch {
68
+ // JSON 파싱 실패 시 기본값 유지
69
+ }
70
+ }
71
+
72
+ return {
73
+ data: {
74
+ email: employee?.email ?? "",
75
+ currentPassword: "",
76
+ newPassword: "",
77
+ confirmPassword: "",
78
+ firstRouterLink,
79
+ } as DetailData,
80
+ info: { isNew: false, isDeleted: false },
81
+ };
82
+ });
83
+ }
84
+
85
+ async function handleSubmit(data: DetailData) {
86
+ const authData = auth.authInfo();
87
+ if (!authData) throw new Error("직원 정보를 불러올 수 없습니다.");
88
+
89
+ const isPasswordChanging =
90
+ data.currentPassword.trim() !== "" ||
91
+ data.newPassword.trim() !== "" ||
92
+ data.confirmPassword.trim() !== "";
93
+
94
+ if (isPasswordChanging) {
95
+ await appService.auth.changePassword(data.currentPassword.trim(), data.newPassword.trim());
96
+ }
97
+
98
+ await appService.orm.connect(async (db) => {
99
+ await db
100
+ .employee()
101
+ .where((c: any) => [expr.eq(c.id, authData.employeeId)])
102
+ .update(() => ({
103
+ email: expr.val("string", data.email.trim()),
104
+ }));
105
+
106
+ const existingConfig = await db
107
+ .employeeConfig()
108
+ .where((c: any) => [
109
+ expr.eq(c.employeeId, authData.employeeId),
110
+ expr.eq(c.code, "firstRouterLink"),
111
+ ])
112
+ .first();
113
+
114
+ const valueJson =
115
+ data.firstRouterLink !== undefined
116
+ ? JSON.stringify(data.firstRouterLink)
117
+ : JSON.stringify(null);
118
+
119
+ if (existingConfig) {
120
+ await db
121
+ .employeeConfig()
122
+ .where((c: any) => [
123
+ expr.eq(c.employeeId, authData.employeeId),
124
+ expr.eq(c.code, "firstRouterLink"),
125
+ ])
126
+ .update(() => ({
127
+ valueJson: expr.val("string", valueJson),
128
+ }));
129
+ } else {
130
+ await db.employeeConfig().insert([
131
+ {
132
+ employeeId: authData.employeeId,
133
+ code: "firstRouterLink",
134
+ valueJson,
135
+ },
136
+ ]);
137
+ }
138
+ });
139
+
140
+ await auth.tryReloadAuth();
141
+ return true;
142
+ }
143
+
144
+ return (
145
+ <CrudDetail<DetailData> class={"px-2 py-4"} load={handleLoad} submit={handleSubmit}>
146
+ {(ctx) => (
147
+ <div class="flex flex-col gap-8">
148
+ <section>
149
+ <h3 class="mb-4 border-l-4 border-base-500 pl-3 font-bold text-base-500">인증정보</h3>
150
+ <FormTable>
151
+ <tr>
152
+ <th>이메일</th>
153
+ <td>
154
+ <TextInput
155
+ type="email"
156
+ required
157
+ value={ctx.data.email}
158
+ onValueChange={(v) => ctx.setData("email", v)}
159
+ autocomplete="off"
160
+ />
161
+ </td>
162
+ </tr>
163
+ <tr>
164
+ <th>현재 비밀번호</th>
165
+ <td>
166
+ <TextInput
167
+ required={Boolean(ctx.data.newPassword || ctx.data.confirmPassword)}
168
+ class="w-56"
169
+ type="password"
170
+ autocomplete="off"
171
+ value={ctx.data.currentPassword}
172
+ onValueChange={(v) => ctx.setData("currentPassword", v)}
173
+ placeholder="비밀번호 변경 시에만 입력"
174
+ />
175
+ </td>
176
+ </tr>
177
+ <tr>
178
+ <th>새 비밀번호</th>
179
+ <td>
180
+ <TextInput
181
+ required={Boolean(ctx.data.currentPassword || ctx.data.confirmPassword)}
182
+ minLength={8}
183
+ class="w-56"
184
+ type="password"
185
+ autocomplete="off"
186
+ value={ctx.data.newPassword}
187
+ onValueChange={(v) => ctx.setData("newPassword", v)}
188
+ placeholder="비밀번호 변경 시에만 입력"
189
+ />
190
+ </td>
191
+ </tr>
192
+ <tr>
193
+ <th>새 비밀번호 확인</th>
194
+ <td>
195
+ <TextInput
196
+ required={Boolean(ctx.data.currentPassword || ctx.data.newPassword)}
197
+ minLength={8}
198
+ validate={() =>
199
+ ctx.data.newPassword !== ctx.data.confirmPassword
200
+ ? "비밀번호가 일치하지 않습니다"
201
+ : ""
202
+ }
203
+ class="w-56"
204
+ type="password"
205
+ autocomplete="off"
206
+ value={ctx.data.confirmPassword}
207
+ onValueChange={(v) => ctx.setData("confirmPassword", v)}
208
+ placeholder="비밀번호 변경 시에만 입력"
209
+ />
210
+ </td>
211
+ </tr>
212
+ </FormTable>
213
+ </section>
214
+
215
+ <section>
216
+ <h3 class="mb-4 border-l-4 border-base-500 pl-3 font-bold text-base-500">시스템설정</h3>
217
+ <FormTable>
218
+ <tbody>
219
+ <tr>
220
+ <th>로그인 시 화면</th>
221
+ <td>
222
+ <Select
223
+ class="w-full"
224
+ value={ctx.data.firstRouterLink}
225
+ onValueChange={(v) => ctx.setData("firstRouterLink", v)}
226
+ items={routeOptions()}
227
+ renderValue={(v) => <>{routeMap().get(v) ?? v}</>}
228
+ placeholder="선택 안함"
229
+ >
230
+ <Select.ItemTemplate>
231
+ {(href: string) => <>{routeMap().get(href) ?? href}</>}
232
+ </Select.ItemTemplate>
233
+ <Select.Action
234
+ onClick={() => ctx.setData("firstRouterLink", undefined as any)}
235
+ >
236
+ <Icon icon={IconX} class={"text-danger-500"} />
237
+ </Select.Action>
238
+ </Select>
239
+ </td>
240
+ </tr>
241
+ </tbody>
242
+ </FormTable>
243
+ </section>
244
+ </div>
245
+ )}
246
+ </CrudDetail>
247
+ );
248
+ }
@@ -0,0 +1,169 @@
1
+ import {
2
+ CrudSheet,
3
+ DateRangePicker,
4
+ type ExcelConfig,
5
+ FormGroup,
6
+ type SortingDef,
7
+ TextInput,
8
+ } from "@simplysm/solid";
9
+ import { DateOnly, type DateTime, objGetChainValue, strIsNullOrEmpty } from "@simplysm/core-common";
10
+ import { expr } from "@{{projectName}}/db-main";
11
+ import { type ExprUnit } from "@simplysm/orm-common";
12
+ import { ExcelWrapper } from "@simplysm/excel";
13
+ import { downloadBlob } from "@simplysm/core-browser";
14
+ import { z } from "zod";
15
+ import { useAppService } from "../../../../providers/AppServiceProvider";
16
+
17
+ type SheetItem = {
18
+ id: number;
19
+ clientName?: string;
20
+ dateTime?: DateTime;
21
+ severity?: string;
22
+ message?: string;
23
+ employeeName?: string;
24
+ };
25
+
26
+ type Filter = {
27
+ fromDate?: DateOnly;
28
+ toDate?: DateOnly;
29
+ searchText?: string;
30
+ };
31
+
32
+ const ITEMS_PER_PAGE = 50;
33
+
34
+ const excelWrapper = new ExcelWrapper(
35
+ z.object({
36
+ id: z.number().describe("ID"),
37
+ dateTime: z.custom<DateTime>().optional().describe("일시"),
38
+ severity: z.string().optional().describe("심각도"),
39
+ employeeName: z.string().optional().describe("사용자명"),
40
+ clientName: z.string().optional().describe("클라이언트"),
41
+ message: z.string().optional().describe("메시지"),
42
+ }),
43
+ );
44
+
45
+ export function SystemLogSheet() {
46
+ const appService = useAppService();
47
+
48
+ const excel: ExcelConfig<SheetItem> = {
49
+ download: async (items) => {
50
+ await using wb = await excelWrapper.write("시스템로그", items);
51
+ const blob = await wb.getBlob();
52
+ downloadBlob(blob, "시스템로그.xlsx");
53
+ },
54
+ };
55
+
56
+ async function search(filter: Filter, page: number | undefined, sorts: SortingDef[]) {
57
+ return appService.orm.connect(async (db) => {
58
+ let qr = db._log();
59
+
60
+ // 조회기간 필터
61
+ if (filter.fromDate != null || filter.toDate != null) {
62
+ qr = qr.where((c) => [
63
+ expr.between(expr.cast(c.dateTime, { type: "date" }), filter.fromDate, filter.toDate),
64
+ ]);
65
+ }
66
+
67
+ // 검색어 필터
68
+ const searchText = filter.searchText?.trim();
69
+ if (!strIsNullOrEmpty(searchText)) {
70
+ qr = qr.search((c) => [c.clientName, c.severity, c.employee!.name], searchText);
71
+ }
72
+
73
+ // 페이지 수
74
+ const pageCount = page != null ? Math.ceil((await qr.count()) / ITEMS_PER_PAGE) : undefined;
75
+
76
+ // select + include
77
+ let qr2 = qr.include((c) => c.employee).select((c) => ({
78
+ id: c.id,
79
+ clientName: c.clientName,
80
+ dateTime: c.dateTime,
81
+ severity: c.severity,
82
+ message: c.message,
83
+ employeeName: c.employee?.name,
84
+ }));
85
+
86
+ // 정렬
87
+ for (const sort of sorts) {
88
+ qr2 = qr2.orderBy(
89
+ (c) => objGetChainValue(c, sort.key) as ExprUnit<any>,
90
+ sort.desc ? "DESC" : "ASC",
91
+ );
92
+ }
93
+ if (!sorts.some((s) => s.key === "id")) {
94
+ qr2 = qr2.orderBy((c) => c.id, "DESC");
95
+ }
96
+
97
+ // 페이지네이션
98
+ if (page != null) {
99
+ qr2 = qr2.limit((page - 1) * ITEMS_PER_PAGE, ITEMS_PER_PAGE);
100
+ }
101
+
102
+ const items = (await qr2.result()) as SheetItem[];
103
+ return { items, pageCount };
104
+ });
105
+ }
106
+
107
+ return (
108
+ <CrudSheet<SheetItem, Filter>
109
+ search={search}
110
+ getItemKey={(item) => item.id}
111
+ persistKey="system-log"
112
+ editable={false}
113
+ excel={excel}
114
+ >
115
+ <CrudSheet.Filter<Filter>>
116
+ {(filter, setFilter) => (
117
+ <>
118
+ <FormGroup.Item label="조회기간">
119
+ <DateRangePicker
120
+ from={filter.fromDate}
121
+ onFromChange={(v) => setFilter("fromDate", v)}
122
+ to={filter.toDate}
123
+ onToChange={(v) => setFilter("toDate", v)}
124
+ />
125
+ </FormGroup.Item>
126
+ <FormGroup.Item label="검색어">
127
+ <TextInput
128
+ value={filter.searchText}
129
+ onValueChange={(v) => setFilter("searchText", v)}
130
+ />
131
+ </FormGroup.Item>
132
+ </>
133
+ )}
134
+ </CrudSheet.Filter>
135
+
136
+ <CrudSheet.Column<SheetItem> key="id" header="#" fixed>
137
+ {({ item }) => <div class="px-2 py-1 text-right">{item.id}</div>}
138
+ </CrudSheet.Column>
139
+
140
+ <CrudSheet.Column<SheetItem> key="dateTime" header="일시">
141
+ {({ item }) => (
142
+ <div class="px-2 py-1">
143
+ {item.dateTime?.toFormatString("yyyy-MM-dd HH:mm") ?? ""}
144
+ </div>
145
+ )}
146
+ </CrudSheet.Column>
147
+
148
+ <CrudSheet.Column<SheetItem> key="severity" header="심각도">
149
+ {({ item }) => <div class="px-2 py-1">{item.severity}</div>}
150
+ </CrudSheet.Column>
151
+
152
+ <CrudSheet.Column<SheetItem> key="employeeName" header="사용자명">
153
+ {({ item }) => <div class="px-2 py-1">{item.employeeName}</div>}
154
+ </CrudSheet.Column>
155
+
156
+ <CrudSheet.Column<SheetItem> key="clientName" header="클라이언트">
157
+ {({ item }) => <div class="px-2 py-1">{item.clientName}</div>}
158
+ </CrudSheet.Column>
159
+
160
+ <CrudSheet.Column<SheetItem> key="message" header="메시지">
161
+ {({ item }) => (
162
+ <div class="px-2 py-1">
163
+ <pre class="whitespace-pre-wrap">{item.message}</pre>
164
+ </div>
165
+ )}
166
+ </CrudSheet.Column>
167
+ </CrudSheet>
168
+ );
169
+ }
@@ -0,0 +1,15 @@
1
+ import { A } from "@solidjs/router";
2
+
3
+ export function NotFoundView() {
4
+ return (
5
+ <div class="flex h-full items-center justify-center">
6
+ <div class="rounded-lg border border-base-200 bg-base-50 p-8 text-center dark:border-base-700 dark:bg-base-800">
7
+ <h1 class="mb-4 text-2xl font-bold">404</h1>
8
+ <p class="mb-4 text-base-600 dark:text-base-400">페이지를 찾을 수 없습니다.</p>
9
+ <A href="/home" class="text-primary-500 hover:underline">
10
+ 홈으로 돌아가기
11
+ </A>
12
+ </div>
13
+ </div>
14
+ );
15
+ }
@@ -0,0 +1,10 @@
1
+ import { fileURLToPath } from "url";
2
+ import simplysmPreset from "@simplysm/solid/tailwind.config";
3
+
4
+ const __dirname = fileURLToPath(new URL(".", import.meta.url));
5
+
6
+ export default {
7
+ darkMode: "class",
8
+ presets: [simplysmPreset],
9
+ content: [`${__dirname}index.html`, `${__dirname}src/**/*.{ts,tsx}`, ...simplysmPreset.content],
10
+ };
@@ -0,0 +1,13 @@
1
+ {
2
+ "name": "@{{projectName}}/db-main",
3
+ "version": "1.0.0",
4
+ "type": "module",
5
+ "private": true,
6
+ "exports": {
7
+ ".": "./src/index.ts"
8
+ },
9
+ "dependencies": {
10
+ "@simplysm/core-common": "~13.0.72",
11
+ "@simplysm/orm-common": "~13.0.72"
12
+ }
13
+ }
@@ -0,0 +1,20 @@
1
+ import { defineDbContext, type DbContextInstance } from "@simplysm/orm-common";
2
+ import { Employee } from "./tables/Employee";
3
+ import { Role } from "./tables/Role";
4
+ import { RolePermission } from "./tables/RolePermission";
5
+ import { EmployeeConfig } from "./tables/EmployeeConfig";
6
+ import { _Log } from "./tables/_Log";
7
+ import { _DataLog } from "./tables/_DataLog";
8
+
9
+ export const MainDbContext = defineDbContext({
10
+ tables: {
11
+ employee: Employee,
12
+ role: Role,
13
+ rolePermission: RolePermission,
14
+ employeeConfig: EmployeeConfig,
15
+ _log: _Log,
16
+ _dataLog: _DataLog,
17
+ },
18
+ });
19
+
20
+ export type MainDbContext = DbContextInstance<typeof MainDbContext>;
@@ -0,0 +1,127 @@
1
+ import { DateTime } from "@simplysm/core-common";
2
+ import {
3
+ type DataRecord,
4
+ expr,
5
+ Queryable,
6
+ queryable,
7
+ type TableBuilder,
8
+ } from "@simplysm/orm-common";
9
+ import { _DataLog } from "./tables/_DataLog";
10
+
11
+ // ── Type Declarations ──
12
+
13
+ export interface IDataLogJoinOptions {
14
+ includeActions?: string[];
15
+ excludeActions?: string[];
16
+ }
17
+
18
+ export interface IDataLogJoinResult {
19
+ action?: string;
20
+ dateTime?: DateTime;
21
+ employeeId?: number;
22
+ employeeName?: string;
23
+ }
24
+
25
+ export interface IInsertDataLogParam {
26
+ action: string;
27
+ itemId: number;
28
+ employeeId?: number;
29
+ valueJson?: string;
30
+ }
31
+
32
+ declare module "@simplysm/orm-common" {
33
+ interface Queryable<TData extends DataRecord, TFrom extends TableBuilder<any, any> | never> {
34
+ joinLastDataLog(
35
+ opts?: IDataLogJoinOptions,
36
+ ): Queryable<TData & { lastDataLog?: IDataLogJoinResult }, TFrom>;
37
+
38
+ joinFirstDataLog(
39
+ opts?: IDataLogJoinOptions,
40
+ ): Queryable<TData & { firstDataLog?: IDataLogJoinResult }, TFrom>;
41
+
42
+ insertDataLog(log: IInsertDataLogParam): Promise<void>;
43
+ }
44
+ }
45
+
46
+ // ── Runtime: insertDataLog ──
47
+
48
+ Queryable.prototype.insertDataLog = async function (
49
+ this: Queryable<any, any>,
50
+ log: IInsertDataLogParam,
51
+ ): Promise<void> {
52
+ const tableName = (this.meta.from as TableBuilder<any, any>).meta.name;
53
+ const dataLogQr = queryable(this.meta.db, _DataLog);
54
+
55
+ await dataLogQr().insert([
56
+ {
57
+ tableName,
58
+ action: log.action,
59
+ itemId: log.itemId,
60
+ valueJson: log.valueJson,
61
+ dateTime: new DateTime(),
62
+ employeeId: log.employeeId,
63
+ },
64
+ ]);
65
+ };
66
+
67
+ // ── Runtime: joinLastDataLog ──
68
+
69
+ Queryable.prototype.joinLastDataLog = function (
70
+ this: Queryable<any, any>,
71
+ opts?: IDataLogJoinOptions,
72
+ ) {
73
+ const tableName = (this.meta.from as TableBuilder<any, any>).meta.name;
74
+
75
+ return this.joinSingle("lastDataLog", (qr, en) =>
76
+ qr
77
+ .from(_DataLog)
78
+ .where((dl) => [
79
+ expr.eq(dl.tableName, tableName),
80
+ expr.eq(dl.itemId, en["id"]),
81
+ ...(opts?.includeActions ? [expr.in(dl.action, opts.includeActions)] : []),
82
+ ...(opts?.excludeActions ? [expr.not(expr.in(dl.action, opts.excludeActions))] : []),
83
+ ])
84
+ .orderBy((dl) => dl.tableName, "DESC")
85
+ .orderBy((dl) => dl.itemId, "DESC")
86
+ .orderBy((dl) => dl.dateTime, "DESC")
87
+ .top(1)
88
+ .include((dl) => dl.employee)
89
+ .select((dl) => ({
90
+ action: dl.action,
91
+ dateTime: dl.dateTime,
92
+ employeeId: dl.employeeId,
93
+ employeeName: dl.employee!.name,
94
+ })),
95
+ );
96
+ };
97
+
98
+ // ── Runtime: joinFirstDataLog ──
99
+
100
+ Queryable.prototype.joinFirstDataLog = function (
101
+ this: Queryable<any, any>,
102
+ opts?: IDataLogJoinOptions,
103
+ ) {
104
+ const tableName = (this.meta.from as TableBuilder<any, any>).meta.name;
105
+
106
+ return this.joinSingle("firstDataLog", (qr, en) =>
107
+ qr
108
+ .from(_DataLog)
109
+ .where((dl) => [
110
+ expr.eq(dl.tableName, tableName),
111
+ expr.eq(dl.itemId, en["id"]),
112
+ ...(opts?.includeActions ? [expr.in(dl.action, opts.includeActions)] : []),
113
+ ...(opts?.excludeActions ? [expr.not(expr.in(dl.action, opts.excludeActions))] : []),
114
+ ])
115
+ .orderBy((dl) => dl.tableName, "ASC")
116
+ .orderBy((dl) => dl.itemId, "ASC")
117
+ .orderBy((dl) => dl.dateTime, "ASC")
118
+ .top(1)
119
+ .include((dl) => dl.employee)
120
+ .select((dl) => ({
121
+ action: dl.action,
122
+ dateTime: dl.dateTime,
123
+ employeeId: dl.employeeId,
124
+ employeeName: dl.employee!.name,
125
+ })),
126
+ );
127
+ };
@@ -0,0 +1,10 @@
1
+ export { Role } from "./tables/Role";
2
+ export { Employee } from "./tables/Employee";
3
+ export { RolePermission } from "./tables/RolePermission";
4
+ export { EmployeeConfig } from "./tables/EmployeeConfig";
5
+ export { _Log } from "./tables/_Log";
6
+ export { _DataLog } from "./tables/_DataLog";
7
+ export { MainDbContext } from "./MainDbContext";
8
+ export { expr } from "@simplysm/orm-common";
9
+ import "./dataLogExt";
10
+ export type { IDataLogJoinOptions, IDataLogJoinResult, IInsertDataLogParam } from "./dataLogExt";
@@ -0,0 +1,24 @@
1
+ import { Table } from "@simplysm/orm-common";
2
+ import { Role } from "./Role";
3
+
4
+ export const Employee = Table("Employee")
5
+ .columns((c) => ({
6
+ id: c.bigint().autoIncrement(),
7
+ name: c.varchar(100),
8
+ email: c.varchar(200).nullable(),
9
+ phoneNumber: c.varchar(50).nullable(),
10
+ birthDate: c.date().nullable(),
11
+ enteringDate: c.date().nullable(),
12
+ leavingDate: c.date().nullable(),
13
+ socialSecurityNumber: c.varchar(20).nullable(),
14
+ payrollAccountBank: c.varchar(100).nullable(),
15
+ payrollAccountNumber: c.varchar(100).nullable(),
16
+ encryptedPassword: c.varchar(200).nullable(),
17
+ roleId: c.bigint().nullable(),
18
+ isDeleted: c.boolean().default(false),
19
+ }))
20
+ .primaryKey("id")
21
+ .indexes((i) => [i.index("email").unique()])
22
+ .relations((r) => ({
23
+ role: r.foreignKey(["roleId"], () => Role),
24
+ }));
@@ -0,0 +1,13 @@
1
+ import { Table } from "@simplysm/orm-common";
2
+ import { Employee } from "./Employee";
3
+
4
+ export const EmployeeConfig = Table("EmployeeConfig")
5
+ .columns((c) => ({
6
+ employeeId: c.bigint(),
7
+ code: c.varchar(200),
8
+ valueJson: c.text(),
9
+ }))
10
+ .primaryKey("employeeId", "code")
11
+ .relations((r) => ({
12
+ employee: r.foreignKey(["employeeId"], () => Employee),
13
+ }));
@@ -0,0 +1,9 @@
1
+ import { Table } from "@simplysm/orm-common";
2
+
3
+ export const Role = Table("Role")
4
+ .columns((c) => ({
5
+ id: c.bigint().autoIncrement(),
6
+ name: c.varchar(100),
7
+ isDeleted: c.boolean().default(false),
8
+ }))
9
+ .primaryKey("id");
@@ -0,0 +1,13 @@
1
+ import { Table } from "@simplysm/orm-common";
2
+ import { Role } from "./Role";
3
+
4
+ export const RolePermission = Table("RolePermission")
5
+ .columns((c) => ({
6
+ roleId: c.bigint(),
7
+ code: c.varchar(200),
8
+ valueJson: c.text(),
9
+ }))
10
+ .primaryKey("roleId", "code")
11
+ .relations((r) => ({
12
+ role: r.foreignKey(["roleId"], () => Role),
13
+ }));