@simplysm/sd-cli 14.0.95 → 14.0.96

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 (42) hide show
  1. package/dist/commands/init/generators/client.d.ts.map +1 -1
  2. package/dist/commands/init/generators/client.js +9 -0
  3. package/dist/commands/init/generators/client.js.map +1 -1
  4. package/dist/commands/init/generators/server.d.ts.map +1 -1
  5. package/dist/commands/init/generators/server.js +1 -0
  6. package/dist/commands/init/generators/server.js.map +1 -1
  7. package/package.json +5 -5
  8. package/src/commands/init/generators/client.ts +45 -0
  9. package/src/commands/init/generators/server.ts +5 -0
  10. package/src/commands/init/templates/client/src/app/home/home.view.ts.hbs +1 -1
  11. package/src/commands/init/templates/client/src/app/home/master/role-permission/role-permission.detail.ts.hbs +221 -0
  12. package/src/commands/init/templates/client/src/app/home/master/role-permission/role-permission.view.ts.hbs +106 -0
  13. package/src/commands/init/templates/client/src/app/home/master/role-permission/role.detail.ts.hbs +277 -0
  14. package/src/commands/init/templates/client/src/app/home/master/role-permission/role.list.ts.hbs +537 -0
  15. package/src/commands/init/templates/client/src/app/home/master/user.detail.ts.hbs +337 -0
  16. package/src/commands/init/templates/client/src/app/home/master/user.list.ts.hbs +540 -0
  17. package/src/commands/init/templates/client/src/app/home/my-info/my-info.detail.ts.hbs +4 -6
  18. package/src/commands/init/templates/client/src/app/home/system/data-log/data-log.list.ts.hbs +355 -0
  19. package/src/commands/init/templates/client/src/app/home/system/system-log/system-log.list.ts.hbs +382 -0
  20. package/src/commands/init/templates/client/src/app/login/login.view.ts.hbs +3 -4
  21. package/src/commands/init/templates/client/src/app.root.ts.hbs +9 -3
  22. package/src/commands/init/templates/client/src/index.html.hbs +1 -0
  23. package/src/commands/init/templates/client/src/main.ts.hbs +23 -18
  24. package/src/commands/init/templates/client/src/modals/text-view.modal.ts.hbs +30 -0
  25. package/src/commands/init/templates/client/src/routes.ts.hbs +22 -0
  26. package/src/commands/init/templates/client-common/src/index.ts.hbs +6 -4
  27. package/src/commands/init/templates/client-common/src/providers/app-auth.provider.ts.hbs +3 -3
  28. package/src/commands/init/templates/client-common/src/providers/app-orm.provider.ts.hbs +0 -11
  29. package/src/commands/init/templates/client-common/src/providers/app-service.provider.ts.hbs +24 -10
  30. package/src/commands/init/templates/client-common/src/providers/app-shared-data.provider.ts.hbs +2 -2
  31. package/src/commands/init/templates/common/package.json.hbs +2 -1
  32. package/src/commands/init/templates/common/src/app-structure.ts.hbs +7 -2
  33. package/src/commands/init/templates/common/src/auth-info-changed.event.ts.hbs +3 -1
  34. package/src/commands/init/templates/common/src/db/db-context.ts.hbs +2 -2
  35. package/src/commands/init/templates/common/src/db/tables/master/user.ts.hbs +2 -2
  36. package/src/commands/init/templates/common/src/db/tables/system/role.ts.hbs +1 -0
  37. package/src/commands/init/templates/common/src/index.ts.hbs +1 -1
  38. package/src/commands/init/templates/server/src/index.ts.hbs +3 -0
  39. package/src/commands/init/templates/server/src/main.ts.hbs +15 -1
  40. package/src/commands/init/templates/server/src/services/auth.service.ts.hbs +28 -22
  41. package/src/commands/init/templates/server/src/services/dev.service.ts.hbs +5 -5
  42. package/src/commands/init/templates/server/src/services/user.service.ts.hbs +191 -0
@@ -0,0 +1,277 @@
1
+ import {
2
+ ChangeDetectionStrategy,
3
+ Component,
4
+ effect,
5
+ inject,
6
+ input,
7
+ output,
8
+ signal,
9
+ untracked,
10
+ ViewEncapsulation,
11
+ } from "@angular/core";
12
+ import { obj } from "@simplysm/core-common";
13
+ import {
14
+ injectPermsSignal,
15
+ injectViewTypeSignal,
16
+ mark,
17
+ SdButton,
18
+ SdCrudDetail,
19
+ type SdModalContentDef,
20
+ SdTextfield,
21
+ SdToastProvider,
22
+ setupCanDeactivate,
23
+ } from "@simplysm/angular";
24
+ import { expr } from "@simplysm/orm-common";
25
+ import {
26
+ AppAuthProvider,
27
+ AppOrmProvider,
28
+ AppSharedDataProvider,
29
+ } from "@{{workspaceName}}/client-common";
30
+
31
+ @Component({
32
+ selector: "app-role-detail",
33
+ changeDetection: ChangeDetectionStrategy.OnPush,
34
+ encapsulation: ViewEncapsulation.None,
35
+ standalone: true,
36
+ imports: [SdCrudDetail, SdTextfield, SdButton],
37
+ template: `
38
+ <sd-crud-detail
39
+ [(ready)]="ready"
40
+ [initialized]="initialized()"
41
+ [(busyCount)]="busyCount"
42
+ [viewType]="viewType()"
43
+ [restricted]="!perms().includes('use')"
44
+ [readonly]="!perms().includes('edit')"
45
+ (submit)="onSubmit()"
46
+ >
47
+ @if (roleId() != null && perms().includes("edit")) {
48
+ <ng-template #bottomCommandTpl>
49
+ <div class="flex-row gap-sm">
50
+ @if (data().isDeleted) {
51
+ <sd-button [size]="'sm'" [theme]="'warning'" (click)="onRestore()">복구</sd-button>
52
+ } @else {
53
+ <sd-button [size]="'sm'" [theme]="'danger'" (click)="onDelete()">삭제</sd-button>
54
+ }
55
+ </div>
56
+ </ng-template>
57
+ }
58
+
59
+ <ng-template #contentTpl>
60
+ <div class="p-default">
61
+ <table class="form-table">
62
+ <tbody>
63
+ <tr>
64
+ <th>이름</th>
65
+ <td>
66
+ <sd-textfield
67
+ [type]="'text'"
68
+ [(value)]="data().name"
69
+ (valueChange)="mark(data)"
70
+ [required]="true"
71
+ [disabled]="!perms().includes('edit')"
72
+ />
73
+ </td>
74
+ </tr>
75
+ </tbody>
76
+ </table>
77
+ </div>
78
+ </ng-template>
79
+ </sd-crud-detail>
80
+ `,
81
+ })
82
+ export class RoleDetail implements SdModalContentDef<{ id: number } | undefined> {
83
+ private readonly _sdToast = inject(SdToastProvider);
84
+ private readonly _appOrm = inject(AppOrmProvider);
85
+ private readonly _appSharedData = inject(AppSharedDataProvider);
86
+ private readonly _appAuth = inject(AppAuthProvider);
87
+
88
+ roleId = input<number>();
89
+ close = output<{ id: number } | undefined>();
90
+
91
+ perms = injectPermsSignal(["master.role-permission"], ["use", "edit"]);
92
+ viewType = injectViewTypeSignal();
93
+
94
+ ready = signal(false);
95
+ initialized = signal(false);
96
+ busyCount = signal(0);
97
+
98
+ data = signal<IData>({});
99
+ private _orgData?: IData;
100
+
101
+ constructor() {
102
+ effect(() => {
103
+ this.roleId();
104
+
105
+ if (!this.perms().includes("use") || !this.ready()) {
106
+ this.initialized.set(true);
107
+ return;
108
+ }
109
+
110
+ void untracked(async () => {
111
+ this.busyCount.update((v) => v + 1);
112
+ await this._sdToast.try(async () => {
113
+ await this._refresh();
114
+ });
115
+ this.busyCount.update((v) => v - 1);
116
+ this.initialized.set(true);
117
+ });
118
+ });
119
+
120
+ setupCanDeactivate(() => this._checkIgnoreChanges());
121
+ }
122
+
123
+ async onSubmit(): Promise<void> {
124
+ if (this.busyCount() > 0) return;
125
+ if (!this.perms().includes("edit")) return;
126
+
127
+ const data = this.data();
128
+ if (this._orgData != null && obj.equal(data, this._orgData)) {
129
+ this._sdToast.info("변경사항이 없습니다.");
130
+ return;
131
+ }
132
+
133
+ this.busyCount.update((v) => v + 1);
134
+ await this._sdToast.try(async () => {
135
+ const roleId = this.roleId();
136
+ const {{userEntityCamel}}Id = this._appAuth.authInfo()?.{{userEntityCamel}}Id;
137
+
138
+ const savedId = await this._appOrm.connectAsync(async (db) => {
139
+ // 이름 중복 검증 (자기 자신 제외)
140
+ const isDuplicated = await db
141
+ .role()
142
+ .where((c) => [
143
+ expr.eq(c.name, data.name),
144
+ expr.eq(c.isDeleted, false),
145
+ ...(roleId == null ? [] : [expr.not(expr.eq(c.id, roleId))]),
146
+ ])
147
+ .exists();
148
+ if (isDuplicated) {
149
+ throw new Error("이미 존재하는 역할 이름입니다.");
150
+ }
151
+
152
+ if (roleId == null) {
153
+ const [inserted] = await db
154
+ .role()
155
+ .insert([{ name: data.name, isDeleted: false }], ["id"]);
156
+ await db.role().insertDataLog({ action: "등록", itemId: inserted.id, {{userEntityCamel}}Id });
157
+ return inserted.id;
158
+ }
159
+ await db
160
+ .role()
161
+ .where((c) => [expr.eq(c.id, roleId)])
162
+ .update(() => ({ name: data.name }));
163
+ await db.role().insertDataLog({ action: "수정", itemId: roleId, {{userEntityCamel}}Id });
164
+ return roleId;
165
+ });
166
+
167
+ await this._appSharedData.emitAsync("역할", [savedId]);
168
+ this._sdToast.success("저장되었습니다.");
169
+ this.close.emit({ id: savedId });
170
+ });
171
+ this.busyCount.update((v) => v - 1);
172
+ }
173
+
174
+ async onDelete(): Promise<void> {
175
+ if (this.busyCount() > 0) return;
176
+ if (!this.perms().includes("edit")) return;
177
+
178
+ const roleId = this.roleId();
179
+ if (roleId == null) return;
180
+
181
+ if (!confirm("삭제하시겠습니까?")) return;
182
+
183
+ this.busyCount.update((v) => v + 1);
184
+ await this._sdToast.try(async () => {
185
+ const {{userEntityCamel}}Id = this._appAuth.authInfo()?.{{userEntityCamel}}Id;
186
+ await this._appOrm.connectAsync(async (db) => {
187
+ await db
188
+ .role()
189
+ .where((c) => [expr.eq(c.id, roleId)])
190
+ .update(() => ({ isDeleted: true }));
191
+ await db.role().insertDataLog({ action: "삭제", itemId: roleId, {{userEntityCamel}}Id });
192
+ });
193
+
194
+ await this._appSharedData.emitAsync("역할", [roleId]);
195
+ this._sdToast.success("삭제되었습니다.");
196
+ this.close.emit({ id: roleId });
197
+ });
198
+ this.busyCount.update((v) => v - 1);
199
+ }
200
+
201
+ async onRestore(): Promise<void> {
202
+ if (this.busyCount() > 0) return;
203
+ if (!this.perms().includes("edit")) return;
204
+
205
+ const roleId = this.roleId();
206
+ if (roleId == null) return;
207
+
208
+ const roleName = this.data().name;
209
+
210
+ this.busyCount.update((v) => v + 1);
211
+ await this._sdToast.try(async () => {
212
+ const {{userEntityCamel}}Id = this._appAuth.authInfo()?.{{userEntityCamel}}Id;
213
+ await this._appOrm.connectAsync(async (db) => {
214
+ // 복구 전 이름 비삭제 유니크 재검증 (삭제된 동안 같은 이름의 활성 역할이 생겼을 수 있음).
215
+ const isNameDuplicated = await db
216
+ .role()
217
+ .where((c) => [
218
+ expr.eq(c.name, roleName),
219
+ expr.eq(c.isDeleted, false),
220
+ expr.not(expr.eq(c.id, roleId)),
221
+ ])
222
+ .exists();
223
+ if (isNameDuplicated) {
224
+ throw new Error("같은 이름의 활성 역할이 있어 복구할 수 없습니다.");
225
+ }
226
+
227
+ await db
228
+ .role()
229
+ .where((c) => [expr.eq(c.id, roleId)])
230
+ .update(() => ({ isDeleted: false }));
231
+ await db.role().insertDataLog({ action: "복구", itemId: roleId, {{userEntityCamel}}Id });
232
+ });
233
+
234
+ await this._appSharedData.emitAsync("역할", [roleId]);
235
+ this._sdToast.success("복구되었습니다.");
236
+ await this._refresh();
237
+ });
238
+ this.busyCount.update((v) => v - 1);
239
+ }
240
+
241
+ private async _refresh(): Promise<void> {
242
+ const roleId = this.roleId();
243
+ if (roleId == null) {
244
+ this.data.set({});
245
+ this._orgData = obj.clone(this.data());
246
+ return;
247
+ }
248
+
249
+ const loaded = await this._appOrm.connectAsync(async (db) => {
250
+ return db
251
+ .role()
252
+ .where((c) => [expr.eq(c.id, roleId)])
253
+ .select((c) => ({ name: c.name, isDeleted: c.isDeleted }))
254
+ .single();
255
+ });
256
+
257
+ if (loaded == null) throw new Error("역할을 찾을 수 없습니다.");
258
+
259
+ this.data.set({ name: loaded.name, isDeleted: loaded.isDeleted });
260
+ this._orgData = obj.clone(this.data());
261
+ }
262
+
263
+ private _checkIgnoreChanges(): boolean {
264
+ return (
265
+ this._orgData == null ||
266
+ obj.equal(this.data(), this._orgData) ||
267
+ confirm("변경사항이 있습니다. 무시하고 진행하시겠습니까?")
268
+ );
269
+ }
270
+
271
+ protected readonly mark = mark;
272
+ }
273
+
274
+ interface IData {
275
+ name?: string;
276
+ isDeleted?: boolean;
277
+ }