sonamu 0.0.1

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 (60) hide show
  1. package/.pnp.cjs +15552 -0
  2. package/.pnp.loader.mjs +285 -0
  3. package/.vscode/extensions.json +6 -0
  4. package/.vscode/settings.json +9 -0
  5. package/.yarnrc.yml +5 -0
  6. package/dist/bin/cli.d.ts +2 -0
  7. package/dist/bin/cli.d.ts.map +1 -0
  8. package/dist/bin/cli.js +123 -0
  9. package/dist/bin/cli.js.map +1 -0
  10. package/dist/index.js +34 -0
  11. package/package.json +60 -0
  12. package/src/api/caster.ts +72 -0
  13. package/src/api/code-converters.ts +552 -0
  14. package/src/api/context.ts +20 -0
  15. package/src/api/decorators.ts +63 -0
  16. package/src/api/index.ts +5 -0
  17. package/src/api/init.ts +128 -0
  18. package/src/bin/cli.ts +115 -0
  19. package/src/database/base-model.ts +287 -0
  20. package/src/database/db.ts +95 -0
  21. package/src/database/knex-plugins/knex-on-duplicate-update.ts +41 -0
  22. package/src/database/upsert-builder.ts +231 -0
  23. package/src/exceptions/error-handler.ts +29 -0
  24. package/src/exceptions/so-exceptions.ts +91 -0
  25. package/src/index.ts +17 -0
  26. package/src/shared/web.shared.ts.txt +119 -0
  27. package/src/smd/migrator.ts +1462 -0
  28. package/src/smd/smd-manager.ts +141 -0
  29. package/src/smd/smd-utils.ts +266 -0
  30. package/src/smd/smd.ts +533 -0
  31. package/src/syncer/index.ts +1 -0
  32. package/src/syncer/syncer.ts +1283 -0
  33. package/src/templates/base-template.ts +19 -0
  34. package/src/templates/generated.template.ts +247 -0
  35. package/src/templates/generated_http.template.ts +114 -0
  36. package/src/templates/index.ts +1 -0
  37. package/src/templates/init_enums.template.ts +71 -0
  38. package/src/templates/init_generated.template.ts +44 -0
  39. package/src/templates/init_types.template.ts +38 -0
  40. package/src/templates/model.template.ts +168 -0
  41. package/src/templates/model_test.template.ts +39 -0
  42. package/src/templates/service.template.ts +263 -0
  43. package/src/templates/smd.template.ts +49 -0
  44. package/src/templates/view_enums_buttonset.template.ts +34 -0
  45. package/src/templates/view_enums_dropdown.template.ts +67 -0
  46. package/src/templates/view_enums_select.template.ts +60 -0
  47. package/src/templates/view_form.template.ts +397 -0
  48. package/src/templates/view_id_all_select.template.ts +34 -0
  49. package/src/templates/view_id_async_select.template.ts +113 -0
  50. package/src/templates/view_list.template.ts +652 -0
  51. package/src/templates/view_list_columns.template.ts +59 -0
  52. package/src/templates/view_search_input.template.ts +67 -0
  53. package/src/testing/fixture-manager.ts +271 -0
  54. package/src/types/types.ts +668 -0
  55. package/src/typings/knex.d.ts +24 -0
  56. package/src/utils/controller.ts +21 -0
  57. package/src/utils/lodash-able.ts +11 -0
  58. package/src/utils/model.ts +33 -0
  59. package/src/utils/utils.ts +28 -0
  60. package/tsconfig.json +47 -0
@@ -0,0 +1,19 @@
1
+ import { TemplateKey, TemplateOptions } from "../types/types";
2
+ import { SMDNamesRecord } from "../smd/smd-manager";
3
+ import { RenderedTemplate } from "../syncer/syncer";
4
+
5
+ export abstract class Template {
6
+ constructor(public key: TemplateKey) {}
7
+ public abstract render(
8
+ options: TemplateOptions[TemplateKey],
9
+ ...extra: unknown[]
10
+ ): RenderedTemplate;
11
+
12
+ public abstract getTargetAndPath(
13
+ names: SMDNamesRecord,
14
+ ...extra: unknown[]
15
+ ): {
16
+ target: string;
17
+ path: string;
18
+ };
19
+ }
@@ -0,0 +1,247 @@
1
+ import { camelize } from "inflection";
2
+ import { uniq } from "lodash";
3
+ import {
4
+ isDateProp,
5
+ isDateTimeProp,
6
+ isTimestampProp,
7
+ TemplateOptions,
8
+ } from "../types/types";
9
+ import { SMDManager, SMDNamesRecord } from "../smd/smd-manager";
10
+ import { SMD } from "../smd/smd";
11
+ import { SMDPropNode, SubsetQuery } from "../types/types";
12
+ import { propNodeToZodTypeDef } from "../api/code-converters";
13
+ import { Template } from "./base-template";
14
+
15
+ export class Template__generated extends Template {
16
+ constructor() {
17
+ super("generated");
18
+ }
19
+
20
+ getTargetAndPath(names: SMDNamesRecord) {
21
+ return {
22
+ target: "api/src/application",
23
+ path: `${names.fs}/${names.fs}.generated.ts`,
24
+ };
25
+ }
26
+
27
+ render({ smdId }: TemplateOptions["generated"]) {
28
+ const names = SMDManager.getNamesFromId(smdId);
29
+ const smd = SMDManager.get(smdId);
30
+
31
+ const typeSource = [
32
+ this.getBaseSchemaTypeSource(smd),
33
+ this.getBaseListParamsTypeSource(smd),
34
+ this.getSubsetTypeSource(smd),
35
+ ].reduce(
36
+ (result, ts) => {
37
+ if (ts === null) {
38
+ return result;
39
+ }
40
+ return {
41
+ lines: [...result!.lines, ...ts.lines],
42
+ importKeys: uniq([...result!.importKeys, ...ts.importKeys]),
43
+ };
44
+ },
45
+ {
46
+ lines: [],
47
+ importKeys: [],
48
+ }
49
+ );
50
+
51
+ const fieldExprs = smd
52
+ .getFieldExprs()
53
+ .map((fieldExpr) => `"${fieldExpr}"`)
54
+ .join(" | ");
55
+ const fieldExprsLine = `export type ${smd.id}FieldExpr = ${
56
+ fieldExprs.length > 0 ? fieldExprs : "string"
57
+ }`;
58
+
59
+ return {
60
+ ...this.getTargetAndPath(names),
61
+ body: [...typeSource!.lines, fieldExprsLine, "/* END Server-side Only */"]
62
+ .join("\n")
63
+ .trim(),
64
+ importKeys: typeSource?.importKeys ?? [],
65
+ customHeaders: [
66
+ `import { z } from 'zod';`,
67
+ smd.props.length > 0
68
+ ? `import { zArrayable${
69
+ smd.props.find(
70
+ (p) => isTimestampProp(p) || isDateProp(p) || isDateTimeProp(p)
71
+ )
72
+ ? ", SQLDateTimeString"
73
+ : ""
74
+ } } from "@sonamu/core";`
75
+ : "",
76
+ ],
77
+ };
78
+ }
79
+
80
+ getBaseSchemaTypeSource(
81
+ smd: SMD,
82
+ depth: number = 0,
83
+ importKeys: string[] = []
84
+ ): {
85
+ lines: string[];
86
+ importKeys: string[];
87
+ } {
88
+ const childrenIds = SMDManager.getChildrenIds(smd.id);
89
+
90
+ const schemaName = `${smd.names.module}BaseSchema`;
91
+ const propNode: SMDPropNode = {
92
+ nodeType: "object",
93
+ children: smd.props.map((prop) => {
94
+ return {
95
+ nodeType: "plain",
96
+ prop,
97
+ };
98
+ }),
99
+ };
100
+
101
+ const schemaBody = propNodeToZodTypeDef(propNode, importKeys);
102
+
103
+ const lines = [
104
+ `export const ${schemaName} = ${schemaBody}`,
105
+ `export type ${schemaName} = z.infer<typeof ${schemaName}>`,
106
+ ...childrenIds
107
+ .map((childId) => {
108
+ const child = SMDManager.get(childId);
109
+ const { lines } = this.getBaseSchemaTypeSource(
110
+ child,
111
+ depth + 1,
112
+ importKeys
113
+ );
114
+ return lines;
115
+ })
116
+ .flat(),
117
+ ];
118
+
119
+ return {
120
+ importKeys,
121
+ lines,
122
+ };
123
+ }
124
+
125
+ getBaseListParamsTypeSource(smd: SMD): {
126
+ lines: string[];
127
+ importKeys: string[];
128
+ } {
129
+ // Prop 없는 MD인 경우 생성 제외
130
+ if (smd.props.length === 0) {
131
+ return {
132
+ lines: [],
133
+ importKeys: [],
134
+ };
135
+ }
136
+
137
+ const schemaName = `${smd.names.module}BaseListParams`;
138
+
139
+ const filterProps = smd.props.filter((prop) => prop.toFilter === true);
140
+
141
+ const propNodes: SMDPropNode[] = filterProps.map((prop) => {
142
+ return {
143
+ nodeType: "plain" as const,
144
+ prop,
145
+ children: [],
146
+ };
147
+ });
148
+
149
+ const importKeys: string[] = [`${smd.id}SearchField`, `${smd.id}OrderBy`];
150
+ const filterBody = propNodes
151
+ .map((propNode) => propNodeToZodTypeDef(propNode, importKeys))
152
+ .join("\n");
153
+
154
+ const schemaBody = `
155
+ z.object({
156
+ num: z.number().int().nonnegative(),
157
+ page: z.number().int().min(1),
158
+ search: ${smd.id}SearchField,
159
+ keyword: z.string(),
160
+ orderBy: ${smd.id}OrderBy,
161
+ withoutCount: z.boolean(),
162
+ id: zArrayable(z.number().int().positive()),${filterBody}
163
+ }).partial();
164
+ `.trim();
165
+
166
+ const lines = [
167
+ `export const ${schemaName} = ${schemaBody}`,
168
+ `export type ${schemaName} = z.infer<typeof ${schemaName}>`,
169
+ ];
170
+
171
+ return {
172
+ importKeys,
173
+ lines,
174
+ };
175
+ }
176
+
177
+ getSubsetTypeSource(smd: SMD): {
178
+ lines: string[];
179
+ importKeys: string[];
180
+ } | null {
181
+ if (Object.keys(smd.subsets).length == 0) {
182
+ return null;
183
+ }
184
+
185
+ const subsetKeys = Object.keys(smd.subsets);
186
+
187
+ const subsetQueryObject = subsetKeys.reduce(
188
+ (r, subsetKey) => {
189
+ const subsetQuery = smd.getSubsetQuery(subsetKey);
190
+ r[subsetKey] = subsetQuery;
191
+ return r;
192
+ },
193
+ {} as {
194
+ [key: string]: SubsetQuery;
195
+ }
196
+ );
197
+
198
+ const importKeys: string[] = [];
199
+ const lines: string[] = [
200
+ "",
201
+ ...subsetKeys
202
+ .map((subsetKey) => {
203
+ // 서브셋에서 FieldExpr[] 가져옴
204
+ const fieldExprs = smd.subsets[subsetKey];
205
+
206
+ // FieldExpr[]로 MDPropNode[] 가져옴
207
+ const propNodes = smd.fieldExprsToPropNodes(fieldExprs);
208
+ const schemaName = `${smd.names.module}Subset${subsetKey}`;
209
+ const propNode: SMDPropNode = {
210
+ nodeType: "object",
211
+ children: propNodes,
212
+ };
213
+
214
+ // MDPropNode[]로 ZodTypeDef(string)을 가져옴
215
+ const body = propNodeToZodTypeDef(propNode, importKeys);
216
+
217
+ return [
218
+ `export const ${schemaName} = ${body}`,
219
+ `export type ${schemaName} = z.infer<typeof ${schemaName}>`,
220
+ "",
221
+ ];
222
+ })
223
+ .flat(),
224
+ "",
225
+ `export type ${smd.names.module}SubsetMapping = {`,
226
+ ...subsetKeys.map(
227
+ (subsetKey) => ` ${subsetKey}: ${smd.names.module}Subset${subsetKey};`
228
+ ),
229
+ "}",
230
+ `export const ${smd.names.module}SubsetKey = z.enum([${subsetKeys
231
+ .map((k) => `"${k}"`)
232
+ .join(",")}]);`,
233
+ `export type ${smd.names.module}SubsetKey = z.infer<typeof ${smd.names.module}SubsetKey>`,
234
+ "",
235
+ "/* BEGIN- Server-side Only */",
236
+ `import { SubsetQuery } from "@sonamu/core";`,
237
+ `export const ${camelize(smd.id, true)}SubsetQueries:{ [key in ${
238
+ smd.names.module
239
+ }SubsetKey]: SubsetQuery} = ${JSON.stringify(subsetQueryObject)}`,
240
+ "",
241
+ ];
242
+ return {
243
+ lines,
244
+ importKeys: uniq(importKeys),
245
+ };
246
+ }
247
+ }
@@ -0,0 +1,114 @@
1
+ import qs from "qs";
2
+ import { z } from "zod";
3
+ import { TemplateOptions } from "../types/types";
4
+ import { SMDManager, SMDNamesRecord } from "../smd/smd-manager";
5
+ import { getZodObjectFromApi } from "../api/code-converters";
6
+ import { ExtendedApi } from "../api/decorators";
7
+ import { Template } from "./base-template";
8
+ import prettier from "prettier";
9
+ import { DateTime } from "luxon";
10
+ import { Syncer } from "../syncer/syncer";
11
+
12
+ export class Template__generated_http extends Template {
13
+ constructor() {
14
+ super("generated_http");
15
+ }
16
+
17
+ getTargetAndPath(names: SMDNamesRecord) {
18
+ return {
19
+ target: "api/src/application",
20
+ path: `${names.fs}/${names.fs}.generated.http`,
21
+ };
22
+ }
23
+
24
+ render({ smdId }: TemplateOptions["generated"], apis: ExtendedApi[]) {
25
+ const names = SMDManager.getNamesFromId(smdId);
26
+ const references = Syncer.getInstance().types;
27
+
28
+ const lines = apis.map((api) => {
29
+ const reqObject = this.resolveApiParams(api, references);
30
+
31
+ let qsLines: string[] = [];
32
+ let bodyLines: string[] = [];
33
+ if ((api.options.httpMethod ?? "GET") === "GET") {
34
+ qsLines = [
35
+ qs.stringify(reqObject, { encode: false }).split("&").join("\n\t&"),
36
+ ];
37
+ } else {
38
+ bodyLines = [
39
+ "",
40
+ prettier.format(JSON.stringify(reqObject), {
41
+ parser: "json",
42
+ }),
43
+ ];
44
+ }
45
+ return [
46
+ [
47
+ `${api.options.httpMethod ?? "GET"} {{baseUrl}}/api${api.path}`,
48
+ ...qsLines,
49
+ ].join("\n\t?"),
50
+ `Content-Type: ${api.options.contentType ?? "application/json"}`,
51
+ ...bodyLines,
52
+ ].join("\n");
53
+ });
54
+
55
+ return {
56
+ ...this.getTargetAndPath(names),
57
+ body: lines.join("\n\n###\n\n"),
58
+ importKeys: [],
59
+ };
60
+ }
61
+
62
+ zodTypeToReqDefault(zodType: z.ZodType<unknown>, name: string): unknown {
63
+ if (zodType instanceof z.ZodObject) {
64
+ return Object.fromEntries(
65
+ Object.keys(zodType.shape).map((key) => [
66
+ key,
67
+ this.zodTypeToReqDefault(zodType.shape[key], key),
68
+ ])
69
+ );
70
+ } else if (zodType instanceof z.ZodArray) {
71
+ return [this.zodTypeToReqDefault(zodType.element, name)];
72
+ } else if (zodType instanceof z.ZodString) {
73
+ if (name.endsWith("_at") || name.endsWith("_date") || name === "range") {
74
+ return DateTime.local().toSQL().slice(0, 10);
75
+ } else {
76
+ return name.toUpperCase();
77
+ }
78
+ } else if (zodType instanceof z.ZodNumber) {
79
+ if (name === "num") {
80
+ return 24;
81
+ }
82
+ return zodType.minValue ?? 0;
83
+ } else if (zodType instanceof z.ZodBoolean) {
84
+ return false;
85
+ } else if (zodType instanceof z.ZodEnum) {
86
+ return zodType.options[0];
87
+ } else if (zodType instanceof z.ZodOptional) {
88
+ return this.zodTypeToReqDefault(zodType._def.innerType, name);
89
+ } else if (zodType instanceof z.ZodNullable) {
90
+ return null;
91
+ } else if (zodType instanceof z.ZodUnion) {
92
+ return this.zodTypeToReqDefault(zodType._def.options[0], name);
93
+ } else if (zodType instanceof z.ZodUnknown) {
94
+ return "unknown";
95
+ } else if (zodType instanceof z.ZodTuple) {
96
+ return zodType._def.items.map((item: any) =>
97
+ this.zodTypeToReqDefault(item, name)
98
+ );
99
+ } else {
100
+ // console.log(zodType);
101
+ return `unknown-${zodType._type}`;
102
+ }
103
+ }
104
+
105
+ resolveApiParams(
106
+ api: ExtendedApi,
107
+ references: { [typeName: string]: z.ZodObject<any> }
108
+ ): { [key: string]: unknown } {
109
+ const reqType = getZodObjectFromApi(api, references);
110
+ return this.zodTypeToReqDefault(reqType, "unknownName") as {
111
+ [key: string]: unknown;
112
+ };
113
+ }
114
+ }
@@ -0,0 +1 @@
1
+ export * from "./base-template";
@@ -0,0 +1,71 @@
1
+ import { camelize } from "inflection";
2
+ import { TemplateOptions } from "../types/types";
3
+ import { SMDManager, SMDNamesRecord } from "../smd/smd-manager";
4
+ import { Template } from "./base-template";
5
+
6
+ export class Template__init_enums extends Template {
7
+ constructor() {
8
+ super("init_enums");
9
+ }
10
+
11
+ getTargetAndPath(names: SMDNamesRecord) {
12
+ return {
13
+ target: "api/src/application",
14
+ path: `${names.fs}/${names.fs}.enums.ts`,
15
+ };
16
+ }
17
+
18
+ render(options: TemplateOptions["init_enums"]) {
19
+ const { smdId, def } = options;
20
+ const names = SMDManager.getNamesFromId(smdId);
21
+
22
+ const record = def ?? {};
23
+ record.ORDER_BY ??= {
24
+ "id-desc": "최신순",
25
+ };
26
+ record.SEARCH_FIELD ??= {
27
+ id: "ID",
28
+ };
29
+
30
+ return {
31
+ ...this.getTargetAndPath(names),
32
+ body: `
33
+ import { z } from "zod";
34
+ import { EnumsLabelKo } from "../../types/shared";
35
+
36
+ ${Object.entries(record)
37
+ .map(
38
+ ([key, value]) => `export const ${smdId}${camelize(
39
+ key.toLowerCase(),
40
+ false
41
+ )} = z.enum([${Object.keys(value)
42
+ .map((v) => `"${v}"`)
43
+ .join(",")}]);
44
+ export type ${smdId}${camelize(
45
+ key.toLowerCase(),
46
+ false
47
+ )} = z.infer<typeof ${smdId}${camelize(key.toLowerCase(), false)}>;`
48
+ )
49
+ .join("\n")}
50
+
51
+ export namespace ${names.constant} {
52
+ ${Object.entries(record)
53
+ .map(
54
+ ([key, value]) => `// ${key}
55
+ export const ${key}:EnumsLabelKo<${smdId}${camelize(
56
+ key.toLowerCase(),
57
+ false
58
+ )}> = {
59
+ ${Object.entries(value)
60
+ .map(([ek, ev]) => `"${ek}": { ko: "${ev}" }`)
61
+ .join(",")}
62
+ };
63
+ `
64
+ )
65
+ .join("\n")}
66
+ }
67
+ `.trim(),
68
+ importKeys: [],
69
+ };
70
+ }
71
+ }
@@ -0,0 +1,44 @@
1
+ import { TemplateOptions } from "../types/types";
2
+ import { SMDManager, SMDNamesRecord } from "../smd/smd-manager";
3
+ import { Template } from "./base-template";
4
+
5
+ export class Template__init_generated extends Template {
6
+ constructor() {
7
+ super("init_generated");
8
+ }
9
+
10
+ getTargetAndPath(names: SMDNamesRecord) {
11
+ return {
12
+ target: "api/src/application",
13
+ path: `${names.fs}/${names.fs}.generated.ts`,
14
+ };
15
+ }
16
+
17
+ render({ smdId }: TemplateOptions["init_generated"]) {
18
+ const names = SMDManager.getNamesFromId(smdId);
19
+
20
+ return {
21
+ ...this.getTargetAndPath(names),
22
+ body: `
23
+ import { z } from "zod";
24
+ import { ${smdId}SearchField, ${smdId}OrderBy } from "./${names.fs}.enums";
25
+
26
+ export const ${smdId}BaseSchema = z.object({});
27
+ export type ${smdId}BaseSchema = z.infer<typeof ${smdId}BaseSchema>;
28
+
29
+ export const ${smdId}BaseListParams = z.object({
30
+ num: z.number().int().min(0),
31
+ page: z.number().int().min(1),
32
+ search: ${smdId}SearchField,
33
+ keyword: z.string(),
34
+ orderBy: ${smdId}OrderBy,
35
+ withoutCount: z.boolean(),
36
+ });
37
+ export type ${smdId}BaseListParams = z.infer<typeof ${smdId}BaseListParams>;
38
+
39
+ export type ${smdId}FieldExpr = string;
40
+ `.trim(),
41
+ importKeys: [],
42
+ };
43
+ }
44
+ }
@@ -0,0 +1,38 @@
1
+ import { TemplateOptions } from "../types/types";
2
+ import { SMDManager, SMDNamesRecord } from "../smd/smd-manager";
3
+ import { Template } from "./base-template";
4
+
5
+ export class Template__init_types extends Template {
6
+ constructor() {
7
+ super("init_types");
8
+ }
9
+
10
+ getTargetAndPath(names: SMDNamesRecord) {
11
+ return {
12
+ target: "api/src/application",
13
+ path: `${names.fs}/${names.fs}.types.ts`,
14
+ };
15
+ }
16
+
17
+ render({ smdId }: TemplateOptions["init_types"]) {
18
+ const names = SMDManager.getNamesFromId(smdId);
19
+
20
+ return {
21
+ ...this.getTargetAndPath(names),
22
+ body: `
23
+ import { z } from "zod";
24
+ import { ${smdId}BaseSchema, ${smdId}BaseListParams } from "./${names.fs}.generated";
25
+
26
+ // ${smdId} - ListParams
27
+ export const ${smdId}ListParams = ${smdId}BaseListParams;
28
+ export type ${smdId}ListParams = z.infer<typeof ${smdId}ListParams>;
29
+
30
+ // ${smdId} - SaveParams
31
+ export const ${smdId}SaveParams = ${smdId}BaseSchema.partial({ id: true });
32
+ export type ${smdId}SaveParams = z.infer<typeof ${smdId}SaveParams>;
33
+
34
+ `.trim(),
35
+ importKeys: [],
36
+ };
37
+ }
38
+ }
@@ -0,0 +1,168 @@
1
+ import { RenderingNode, TemplateOptions } from "../types/types";
2
+ import { SMDManager, SMDNamesRecord } from "../smd/smd-manager";
3
+ import { Template } from "./base-template";
4
+ import { Template__view_list } from "./view_list.template";
5
+
6
+ export class Template__model extends Template {
7
+ constructor() {
8
+ super("model");
9
+ }
10
+
11
+ getTargetAndPath(names: SMDNamesRecord) {
12
+ return {
13
+ target: "api/src/application",
14
+ path: `${names.fs}/${names.fs}.model.ts`,
15
+ };
16
+ }
17
+
18
+ render(
19
+ { smdId }: TemplateOptions["model"],
20
+ _columnsNode: RenderingNode,
21
+ listParamsNode: RenderingNode
22
+ ) {
23
+ const names = SMDManager.getNamesFromId(smdId);
24
+ const smd = SMDManager.get(smdId);
25
+
26
+ const vlTpl = new Template__view_list();
27
+ const def = vlTpl.getDefault(listParamsNode.children!);
28
+
29
+ return {
30
+ ...this.getTargetAndPath(names),
31
+ body: `
32
+ import { BaseModelClass, ListResult, asArray, NotFoundException, BadRequestException, api } from '@sonamu/core';
33
+ import {
34
+ ${smdId}SubsetKey,
35
+ ${smdId}SubsetMapping,
36
+ ${names.camel}SubsetQueries,
37
+ } from "./${names.fs}.generated";
38
+ import { ${smdId}ListParams, ${smdId}SaveParams } from "./${names.fs}.types";
39
+
40
+ /*
41
+ ${smdId} Model
42
+ */
43
+ class ${smdId}ModelClass extends BaseModelClass {
44
+ modelName = "${smdId}";
45
+
46
+ @api({ httpMethod: "GET", clients: ["axios", "swr"], resourceName: "${smdId}" })
47
+ async findById<T extends ${smdId}SubsetKey>(
48
+ subset: T,
49
+ id: number
50
+ ): Promise<${smdId}SubsetMapping[T]> {
51
+ const { rows } = await this.findMany(subset, {
52
+ id,
53
+ num: 1,
54
+ page: 1,
55
+ });
56
+ if (rows.length == 0) {
57
+ throw new NotFoundException(\`존재하지 않는 ${names.capital} ID \${id}\`);
58
+ }
59
+
60
+ return rows[0];
61
+ }
62
+
63
+ async findOne<T extends ${smdId}SubsetKey>(
64
+ subset: T,
65
+ listParams: ${smdId}ListParams
66
+ ): Promise<${smdId}SubsetMapping[T] | null> {
67
+ const { rows } = await this.findMany(subset, {
68
+ ...listParams,
69
+ num: 1,
70
+ page: 1,
71
+ });
72
+
73
+ return rows[0] ?? null;
74
+ }
75
+
76
+ @api({ httpMethod: "GET", clients: ["axios", "swr"], resourceName: "${names.capitalPlural}" })
77
+ async findMany<T extends ${smdId}SubsetKey>(
78
+ subset: T,
79
+ params: ${smdId}ListParams = {}
80
+ ): Promise<ListResult<${smdId}SubsetMapping[T]>> {
81
+ // params with defaults
82
+ params = {
83
+ num: 24,
84
+ page: 1,
85
+ search: "${def.search}",
86
+ orderBy: "${def.orderBy}",
87
+ ...params,
88
+ };
89
+
90
+ // build queries
91
+ let { rows, total } = await this.runSubsetQuery({
92
+ subset,
93
+ params,
94
+ subsetQuery: ${names.camel}SubsetQueries[subset],
95
+ build: ({ qb }) => {
96
+ // id
97
+ if (params.id) {
98
+ qb.whereIn("${smd.table}.id", asArray(params.id));
99
+ }
100
+
101
+ // search-keyword
102
+ if (params.search && params.keyword && params.keyword.length > 0) {
103
+ if (params.search === "id") {
104
+ qb.where("${smd.table}.id", "like", \`%\${params.keyword}%\`);
105
+ } else {
106
+ throw new BadRequestException(
107
+ \`구현되지 않은 검색 필드 \${params.search}\`
108
+ );
109
+ }
110
+ }
111
+
112
+ // orderBy
113
+ if (params.orderBy) {
114
+ // default orderBy
115
+ const [orderByField, orderByDirec] = params.orderBy.split("-");
116
+ qb.orderBy("${smd.table}." + orderByField, orderByDirec);
117
+ }
118
+
119
+ return qb;
120
+ },
121
+ debug: false,
122
+ });
123
+
124
+ return {
125
+ rows,
126
+ total,
127
+ };
128
+ }
129
+
130
+ @api({ httpMethod: "POST" })
131
+ async save(
132
+ saveParamsArray: ${smdId}SaveParams[]
133
+ ): Promise<number[]> {
134
+ const wdb = this.getDB("w");
135
+ const ub = this.getUpsertBuilder();
136
+
137
+ // register
138
+ saveParamsArray.map((saveParams) => {
139
+ ub.register("${smd.table}", saveParams);
140
+ });
141
+
142
+ // transaction
143
+ return wdb.transaction(async (trx) => {
144
+ const ids = await ub.upsert(trx, "${smd.table}");
145
+
146
+ return ids;
147
+ });
148
+ }
149
+
150
+ @api({ httpMethod: "GET" })
151
+ async del(ids: number[]): Promise<number> {
152
+ const wdb = this.getDB("w");
153
+
154
+ // transaction
155
+ await wdb.transaction(async (trx) => {
156
+ return trx("${smd.table}").whereIn("${smd.table}.id", ids).delete();
157
+ });
158
+
159
+ return ids.length;
160
+ }
161
+ }
162
+
163
+ export const ${smdId}Model = new ${smdId}ModelClass();
164
+ `.trim(),
165
+ importKeys: [],
166
+ };
167
+ }
168
+ }