sonamu 0.7.11 → 0.7.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/api/config.d.ts +10 -6
- package/dist/api/config.d.ts.map +1 -1
- package/dist/api/config.js +2 -1
- package/dist/api/sonamu.d.ts +4 -0
- package/dist/api/sonamu.d.ts.map +1 -1
- package/dist/api/sonamu.js +49 -5
- package/dist/bin/cli.js +118 -170
- package/dist/database/base-model.d.ts +10 -50
- package/dist/database/base-model.d.ts.map +1 -1
- package/dist/database/base-model.js +19 -84
- package/dist/database/base-model.types.d.ts +4 -4
- package/dist/database/base-model.types.d.ts.map +1 -1
- package/dist/database/base-model.types.js +1 -1
- package/dist/database/db.d.ts +1 -0
- package/dist/database/db.d.ts.map +1 -1
- package/dist/database/db.js +24 -13
- package/dist/database/puri-subset.test-d.js +1 -1
- package/dist/database/puri-subset.types.d.ts +1 -0
- package/dist/database/puri-subset.types.d.ts.map +1 -1
- package/dist/database/puri-subset.types.js +2 -2
- package/dist/database/puri.d.ts +82 -3
- package/dist/database/puri.d.ts.map +1 -1
- package/dist/database/puri.js +180 -14
- package/dist/database/puri.types.d.ts +33 -6
- package/dist/database/puri.types.d.ts.map +1 -1
- package/dist/database/puri.types.js +1 -1
- package/dist/database/puri.types.test-d.js +1 -1
- package/dist/entity/entity-manager.d.ts +5 -4
- package/dist/entity/entity-manager.d.ts.map +1 -1
- package/dist/entity/entity-manager.js +8 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -3
- package/dist/migration/code-generation.d.ts.map +1 -1
- package/dist/migration/code-generation.js +33 -2
- package/dist/migration/postgresql-schema-reader.d.ts.map +1 -1
- package/dist/migration/postgresql-schema-reader.js +53 -22
- package/dist/naite/messaging-types.d.ts.map +1 -1
- package/dist/naite/messaging-types.js +1 -1
- package/dist/naite/naite.js +2 -2
- package/dist/stream/sse.d.ts +2 -6
- package/dist/stream/sse.d.ts.map +1 -1
- package/dist/stream/sse.js +9 -3
- package/dist/syncer/api-parser.d.ts.map +1 -1
- package/dist/syncer/api-parser.js +7 -2
- package/dist/syncer/file-patterns.d.ts +1 -1
- package/dist/syncer/file-patterns.d.ts.map +1 -1
- package/dist/syncer/file-patterns.js +6 -5
- package/dist/syncer/module-loader.d.ts +5 -0
- package/dist/syncer/module-loader.d.ts.map +1 -1
- package/dist/syncer/module-loader.js +17 -1
- package/dist/syncer/syncer.d.ts +5 -1
- package/dist/syncer/syncer.d.ts.map +1 -1
- package/dist/syncer/syncer.js +28 -19
- package/dist/tasks/decorator.d.ts +26 -0
- package/dist/tasks/decorator.d.ts.map +1 -0
- package/dist/tasks/decorator.js +28 -0
- package/dist/tasks/step-wrapper.d.ts +18 -0
- package/dist/tasks/step-wrapper.d.ts.map +1 -0
- package/dist/tasks/step-wrapper.js +38 -0
- package/dist/tasks/workflow-manager.d.ts +40 -0
- package/dist/tasks/workflow-manager.d.ts.map +1 -0
- package/dist/tasks/workflow-manager.js +193 -0
- package/dist/template/implementations/generated.template.d.ts.map +1 -1
- package/dist/template/implementations/generated.template.js +7 -3
- package/dist/types/types.d.ts +26 -10
- package/dist/types/types.d.ts.map +1 -1
- package/dist/types/types.js +15 -2
- package/dist/ui/ai-api.d.ts +1 -0
- package/dist/ui/ai-api.d.ts.map +1 -0
- package/dist/ui/ai-api.js +50 -0
- package/dist/ui/ai-client.d.ts +1 -0
- package/dist/ui/ai-client.d.ts.map +1 -0
- package/dist/ui/ai-client.js +438 -0
- package/dist/ui/api.d.ts +3 -0
- package/dist/ui/api.d.ts.map +1 -0
- package/dist/ui/api.js +680 -0
- package/dist/ui-web/assets/brand-icons-Cu_C0hZ4.svg +1008 -0
- package/dist/ui-web/assets/brand-icons-F3SPCeH1.woff +0 -0
- package/dist/ui-web/assets/brand-icons-XL9sxUpA.woff2 +0 -0
- package/dist/ui-web/assets/brand-icons-sqJ2Pg7a.eot +0 -0
- package/dist/ui-web/assets/brand-icons-ubhWoxly.ttf +0 -0
- package/dist/ui-web/assets/flags-DOLqOU7Y.png +0 -0
- package/dist/ui-web/assets/icons-BOCtAERH.woff +0 -0
- package/dist/ui-web/assets/icons-CHzK1VD9.eot +0 -0
- package/dist/ui-web/assets/icons-D29ZQHHw.ttf +0 -0
- package/dist/ui-web/assets/icons-Du6TOHnR.woff2 +0 -0
- package/dist/ui-web/assets/icons-RwhydX30.svg +1518 -0
- package/dist/ui-web/assets/index-CpaB9P6g.css +1 -0
- package/dist/ui-web/assets/index-J9MCfjCd.js +95 -0
- package/dist/ui-web/assets/outline-icons-BfdLr8tr.svg +366 -0
- package/dist/ui-web/assets/outline-icons-DD8jm0uy.ttf +0 -0
- package/dist/ui-web/assets/outline-icons-DInHoiqI.woff2 +0 -0
- package/dist/ui-web/assets/outline-icons-LX8adJ4n.eot +0 -0
- package/dist/ui-web/assets/outline-icons-aQ88nltS.woff +0 -0
- package/dist/ui-web/assets/provider-utils_false-BKJD46kk.js +1 -0
- package/dist/ui-web/assets/provider-utils_false-Bu5lmX18.js +1 -0
- package/dist/ui-web/index.html +13 -0
- package/dist/ui-web/vite.svg +1 -0
- package/dist/utils/formatter.d.ts.map +1 -1
- package/dist/utils/formatter.js +10 -2
- package/dist/utils/model.d.ts +9 -2
- package/dist/utils/model.d.ts.map +1 -1
- package/dist/utils/model.js +16 -1
- package/dist/utils/type-utils.d.ts.map +1 -1
- package/dist/utils/type-utils.js +3 -1
- package/dist/vector/embedding.d.ts +2 -5
- package/dist/vector/embedding.d.ts.map +1 -1
- package/dist/vector/embedding.js +9 -13
- package/dist/vector/types.d.ts.map +1 -1
- package/dist/vector/types.js +1 -1
- package/package.json +9 -5
- package/src/api/config.ts +15 -11
- package/src/api/sonamu.ts +60 -6
- package/src/bin/cli.ts +57 -119
- package/src/database/base-model.ts +21 -128
- package/src/database/base-model.types.ts +3 -4
- package/src/database/db.ts +28 -18
- package/src/database/puri-subset.test-d.ts +1 -0
- package/src/database/puri-subset.types.ts +2 -0
- package/src/database/puri.ts +238 -27
- package/src/database/puri.types.test-d.ts +1 -1
- package/src/database/puri.types.ts +49 -6
- package/src/entity/entity-manager.ts +9 -0
- package/src/index.ts +1 -1
- package/src/migration/code-generation.ts +40 -1
- package/src/migration/postgresql-schema-reader.ts +53 -22
- package/src/naite/messaging-types.ts +43 -44
- package/src/naite/naite.ts +1 -1
- package/src/shared/app.shared.ts.txt +13 -0
- package/src/shared/web.shared.ts.txt +13 -0
- package/src/stream/sse.ts +15 -3
- package/src/syncer/api-parser.ts +6 -1
- package/src/syncer/file-patterns.ts +11 -9
- package/src/syncer/module-loader.ts +35 -0
- package/src/syncer/syncer.ts +34 -21
- package/src/tasks/decorator.ts +71 -0
- package/src/tasks/step-wrapper.ts +84 -0
- package/src/tasks/workflow-manager.ts +330 -0
- package/src/template/implementations/generated.template.ts +19 -6
- package/src/types/types.ts +20 -4
- package/src/ui/ai-api.ts +60 -0
- package/src/ui/ai-client.ts +499 -0
- package/src/ui/api.ts +786 -0
- package/src/utils/formatter.ts +8 -1
- package/src/utils/model.ts +26 -2
- package/src/utils/type-utils.ts +2 -0
- package/src/vector/embedding.ts +10 -14
- package/src/vector/types.ts +1 -2
- package/dist/vector/vector-search.d.ts +0 -47
- package/dist/vector/vector-search.d.ts.map +0 -1
- package/dist/vector/vector-search.js +0 -176
- package/src/vector/vector-search.ts +0 -261
package/src/ui/api.ts
ADDED
|
@@ -0,0 +1,786 @@
|
|
|
1
|
+
import { execSync } from "child_process";
|
|
2
|
+
import type { FastifyInstance } from "fastify";
|
|
3
|
+
import fs from "fs";
|
|
4
|
+
import inflection from "inflection";
|
|
5
|
+
import path from "path";
|
|
6
|
+
import { range } from "radashi";
|
|
7
|
+
import { Sonamu } from "../api/sonamu";
|
|
8
|
+
import type { SonamuDBConfig } from "../database/db";
|
|
9
|
+
import type { Entity } from "../entity/entity";
|
|
10
|
+
import { EntityManager } from "../entity/entity-manager";
|
|
11
|
+
import {
|
|
12
|
+
BadRequestException,
|
|
13
|
+
isSoException,
|
|
14
|
+
ServiceUnavailableException,
|
|
15
|
+
} from "../exceptions/so-exceptions";
|
|
16
|
+
import { type MigrationResult, Migrator } from "../migration/migrator";
|
|
17
|
+
import { type DuplicateCheckOptions, FixtureManager } from "../testing/fixture-manager";
|
|
18
|
+
import {
|
|
19
|
+
type EntityIndex,
|
|
20
|
+
type EntityProp,
|
|
21
|
+
type EntitySubsetRow,
|
|
22
|
+
type FixtureRecord,
|
|
23
|
+
type FixtureSearchOptions,
|
|
24
|
+
type FlattenSubsetRow,
|
|
25
|
+
type PathAndCode,
|
|
26
|
+
TemplateKey,
|
|
27
|
+
} from "../types/types";
|
|
28
|
+
import { nonNullable } from "../utils/utils";
|
|
29
|
+
|
|
30
|
+
export async function sonamuUIApiPlugin(fastify: FastifyInstance) {
|
|
31
|
+
fastify.register(
|
|
32
|
+
async (server) => {
|
|
33
|
+
// migrator
|
|
34
|
+
const migrator = new Migrator();
|
|
35
|
+
|
|
36
|
+
// waitForHMRCompleted
|
|
37
|
+
async function waitForHMRCompleted<T>(fn: () => Promise<T>): Promise<T> {
|
|
38
|
+
const waitPromise = new Promise<void>((resolve) => {
|
|
39
|
+
const timeout = setTimeout(() => {
|
|
40
|
+
resolve();
|
|
41
|
+
}, 1500);
|
|
42
|
+
|
|
43
|
+
const handler = () => {
|
|
44
|
+
clearTimeout(timeout);
|
|
45
|
+
Sonamu.syncer.eventEmitter.off("onHMRCompleted", handler);
|
|
46
|
+
resolve();
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
Sonamu.syncer.eventEmitter.once("onHMRCompleted", handler);
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
const result = await fn();
|
|
53
|
+
await waitPromise;
|
|
54
|
+
return result;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
server.get("/api/sonamu/config", async () => {
|
|
58
|
+
return Sonamu.config;
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
server.get<{
|
|
62
|
+
Querystring: {
|
|
63
|
+
entityId?: string;
|
|
64
|
+
preset?: "types" | "entity.json" | "generated";
|
|
65
|
+
absPath?: string;
|
|
66
|
+
};
|
|
67
|
+
}>("/api/tools/openVscode", async (request) => {
|
|
68
|
+
const { entityId, preset, absPath } = request.query;
|
|
69
|
+
|
|
70
|
+
const targetPath = (() => {
|
|
71
|
+
if (entityId && preset) {
|
|
72
|
+
const entity = EntityManager.get(entityId);
|
|
73
|
+
const { names } = entity;
|
|
74
|
+
|
|
75
|
+
const { apiRootPath } = Sonamu;
|
|
76
|
+
const filename = (() => {
|
|
77
|
+
switch (preset) {
|
|
78
|
+
case "types":
|
|
79
|
+
return `${names.fs}.types.ts`;
|
|
80
|
+
case "entity.json":
|
|
81
|
+
return `${names.fs}.entity.json`;
|
|
82
|
+
case "generated":
|
|
83
|
+
return `${names.fs}.generated.ts`;
|
|
84
|
+
}
|
|
85
|
+
})();
|
|
86
|
+
return `${apiRootPath}/src/application/${entity.names.parentFs}/${filename}`;
|
|
87
|
+
} else {
|
|
88
|
+
if (!absPath) {
|
|
89
|
+
throw new BadRequestException("preset or absPath must be provided");
|
|
90
|
+
}
|
|
91
|
+
return absPath;
|
|
92
|
+
}
|
|
93
|
+
})();
|
|
94
|
+
execSync(`code ${targetPath}`);
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
server.get<{
|
|
98
|
+
Querystring: {
|
|
99
|
+
origin: string;
|
|
100
|
+
entityId?: string;
|
|
101
|
+
};
|
|
102
|
+
}>("/api/tools/getSuggestion", async (request) => {
|
|
103
|
+
const { origin, entityId } = request.query;
|
|
104
|
+
|
|
105
|
+
// 치환 용어집
|
|
106
|
+
const glossary = new Map<string, string>([
|
|
107
|
+
["status", "상태"],
|
|
108
|
+
["type", "타입"],
|
|
109
|
+
["image", "이미지"],
|
|
110
|
+
["images", "이미지리스트"],
|
|
111
|
+
["url", "URL"],
|
|
112
|
+
["id", "ID"],
|
|
113
|
+
["name", `{EntityID}명`],
|
|
114
|
+
["title", "{EntityID}명"],
|
|
115
|
+
["parent", "상위{EntityID}"],
|
|
116
|
+
["desc", "설명"],
|
|
117
|
+
["at", "일시"],
|
|
118
|
+
["created", "등록"],
|
|
119
|
+
["updated", "수정"],
|
|
120
|
+
["deleted", "삭제"],
|
|
121
|
+
["by", "유저"],
|
|
122
|
+
["date", "일자"],
|
|
123
|
+
["time", "시간"],
|
|
124
|
+
["ko", "(한글)"],
|
|
125
|
+
["en", "(영문)"],
|
|
126
|
+
["krw", "(원)"],
|
|
127
|
+
["usd", "(USD)"],
|
|
128
|
+
["color", "컬러"],
|
|
129
|
+
["code", "코드"],
|
|
130
|
+
["x", "X좌표"],
|
|
131
|
+
["y", "Y좌표"],
|
|
132
|
+
["current", "현재"],
|
|
133
|
+
["stock", "재고"],
|
|
134
|
+
["total", "총"],
|
|
135
|
+
["admin", "관리자"],
|
|
136
|
+
["group", "그룹"],
|
|
137
|
+
["item", "아이템"],
|
|
138
|
+
["cnt", "수량"],
|
|
139
|
+
["price", "가격"],
|
|
140
|
+
["preset", "프리셋"],
|
|
141
|
+
["acct", "계좌"],
|
|
142
|
+
["tel", "전화번호"],
|
|
143
|
+
["no", "번호"],
|
|
144
|
+
["body", "내용"],
|
|
145
|
+
["content", "내용"],
|
|
146
|
+
["orderno", "정렬순서"],
|
|
147
|
+
["priority", "우선순위"],
|
|
148
|
+
["text", "텍스트"],
|
|
149
|
+
["key", "키"],
|
|
150
|
+
["sum", "합산"],
|
|
151
|
+
["expected", "예상"],
|
|
152
|
+
["actual", "실제"],
|
|
153
|
+
]);
|
|
154
|
+
// 전체 엔티티 순회하며, 엔티티 타이틀과 프롭 설명을 치환 용어집에 추가
|
|
155
|
+
for (const entityId of EntityManager.getAllIds()) {
|
|
156
|
+
const entity = EntityManager.get(entityId);
|
|
157
|
+
if ((entity.title ?? "") !== "") {
|
|
158
|
+
glossary.set(inflection.underscore(entity.id), entity.title);
|
|
159
|
+
glossary.set(
|
|
160
|
+
inflection.underscore(inflection.pluralize(entity.id)),
|
|
161
|
+
`${entity.title}리스트`,
|
|
162
|
+
);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
entity.props.forEach((prop) => {
|
|
166
|
+
if (glossary.has(prop.name)) {
|
|
167
|
+
return;
|
|
168
|
+
}
|
|
169
|
+
if (prop.desc) {
|
|
170
|
+
glossary.set(prop.name, prop.desc.replace(entity.title ?? "", "{EntityID}"));
|
|
171
|
+
}
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
const suggested = (() => {
|
|
176
|
+
// 단어 분리, 가능한 조합 생성
|
|
177
|
+
const words = origin.split("_");
|
|
178
|
+
const combinations = [...range(words.length, 0, -1)].flatMap((len) => {
|
|
179
|
+
return [
|
|
180
|
+
...range(0, words.length - len + 1, (idx) => {
|
|
181
|
+
return {
|
|
182
|
+
len,
|
|
183
|
+
w: words.slice(idx, idx + len).join("_"),
|
|
184
|
+
};
|
|
185
|
+
}),
|
|
186
|
+
];
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
// 조합을 순회하며, 치환 용어집에 있는 단어가 포함된 경우, 치환 용어로 치환
|
|
190
|
+
const REPLACED_PREFIX = "#REPLACED//"; // 치환된 단어를 join 이후에도 식별하기 위해 prefix 추가
|
|
191
|
+
let remainArr: string[] = [...words];
|
|
192
|
+
for (const comb of combinations) {
|
|
193
|
+
const remainStr = remainArr.join("_");
|
|
194
|
+
if (remainStr.includes(comb.w) && glossary.has(comb.w)) {
|
|
195
|
+
remainArr = remainStr
|
|
196
|
+
.replace(comb.w, REPLACED_PREFIX + glossary.get(comb.w))
|
|
197
|
+
.split("_");
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
return remainArr
|
|
202
|
+
.map((r) => {
|
|
203
|
+
if (r.startsWith(REPLACED_PREFIX)) {
|
|
204
|
+
return r.replace(REPLACED_PREFIX, "");
|
|
205
|
+
} else {
|
|
206
|
+
return r.toUpperCase();
|
|
207
|
+
}
|
|
208
|
+
})
|
|
209
|
+
.join("")
|
|
210
|
+
.replace(/{EntityID}/g, entityId ? EntityManager.get(entityId).title : "");
|
|
211
|
+
})();
|
|
212
|
+
|
|
213
|
+
console.log({ entityId, origin, suggested });
|
|
214
|
+
return { suggested };
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
server.get("/api/entity/findMany", async () => {
|
|
218
|
+
const entityIds = EntityManager.getAllIds();
|
|
219
|
+
|
|
220
|
+
function flattenSubsetRows(subsetRows: EntitySubsetRow[]): FlattenSubsetRow[] {
|
|
221
|
+
return subsetRows.flatMap((subsetRow) => {
|
|
222
|
+
const { children, ...sRow } = subsetRow;
|
|
223
|
+
return [sRow, ...flattenSubsetRows(children)];
|
|
224
|
+
});
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
const entities = await Promise.all(
|
|
228
|
+
entityIds.map((entityId) => {
|
|
229
|
+
const entity = EntityManager.get(entityId);
|
|
230
|
+
const subsetRows = entity.getSubsetRows();
|
|
231
|
+
|
|
232
|
+
return {
|
|
233
|
+
...entity,
|
|
234
|
+
flattenSubsetRows: flattenSubsetRows(subsetRows),
|
|
235
|
+
};
|
|
236
|
+
}),
|
|
237
|
+
);
|
|
238
|
+
|
|
239
|
+
entities.sort((a, b) => {
|
|
240
|
+
const aId = a.parentId ?? a.id;
|
|
241
|
+
const bId = b.parentId ?? b.id;
|
|
242
|
+
if (aId < bId) return -1;
|
|
243
|
+
if (aId > bId) return 1;
|
|
244
|
+
if (aId === bId) {
|
|
245
|
+
if (a.parentId === undefined) return -1;
|
|
246
|
+
if (b.parentId === undefined) return 1;
|
|
247
|
+
return 0;
|
|
248
|
+
}
|
|
249
|
+
return 0;
|
|
250
|
+
});
|
|
251
|
+
return { entities };
|
|
252
|
+
});
|
|
253
|
+
|
|
254
|
+
server.get<{
|
|
255
|
+
Querystring: {
|
|
256
|
+
filter?: "enums" | "types";
|
|
257
|
+
reload?: "1";
|
|
258
|
+
};
|
|
259
|
+
}>("/api/entity/typeIds", async (request): Promise<{ typeIds: string[] }> => {
|
|
260
|
+
const { filter, reload } = request.query;
|
|
261
|
+
|
|
262
|
+
if (reload === "1") {
|
|
263
|
+
await Sonamu.syncer.autoloadTypes();
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
const typeIds = (() => {
|
|
267
|
+
const typeIds = Object.entries(Sonamu.syncer.types)
|
|
268
|
+
.filter(([_typeId, zodType]) => (zodType.def.type as string) !== "enum")
|
|
269
|
+
.map(([typeId, _zodType]) => typeId);
|
|
270
|
+
|
|
271
|
+
if (filter === "types") {
|
|
272
|
+
return typeIds;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
const enumIds = EntityManager.getAllIds().flatMap((entityId) => {
|
|
276
|
+
const entity = EntityManager.get(entityId);
|
|
277
|
+
return Object.keys(entity.enumLabels);
|
|
278
|
+
});
|
|
279
|
+
|
|
280
|
+
if (filter === "enums") {
|
|
281
|
+
return enumIds;
|
|
282
|
+
} else {
|
|
283
|
+
return [...typeIds, ...enumIds];
|
|
284
|
+
}
|
|
285
|
+
})();
|
|
286
|
+
|
|
287
|
+
return {
|
|
288
|
+
typeIds,
|
|
289
|
+
};
|
|
290
|
+
});
|
|
291
|
+
|
|
292
|
+
server.post<{
|
|
293
|
+
Body: {
|
|
294
|
+
form: {
|
|
295
|
+
id: string;
|
|
296
|
+
title: string;
|
|
297
|
+
table: string;
|
|
298
|
+
parentId?: string;
|
|
299
|
+
};
|
|
300
|
+
};
|
|
301
|
+
}>("/api/entity/create", async (request) => {
|
|
302
|
+
return await waitForHMRCompleted(async () => {
|
|
303
|
+
const { form } = request.body;
|
|
304
|
+
await Sonamu.syncer.createEntity({ ...form, entityId: form.id });
|
|
305
|
+
|
|
306
|
+
return 1;
|
|
307
|
+
});
|
|
308
|
+
});
|
|
309
|
+
|
|
310
|
+
server.post<{
|
|
311
|
+
Body: {
|
|
312
|
+
entityId: string;
|
|
313
|
+
};
|
|
314
|
+
}>("/api/entity/del", async (request) => {
|
|
315
|
+
return await waitForHMRCompleted(async () => {
|
|
316
|
+
const { entityId } = request.body;
|
|
317
|
+
return await Sonamu.syncer.delEntity(entityId);
|
|
318
|
+
});
|
|
319
|
+
});
|
|
320
|
+
|
|
321
|
+
server.post<{
|
|
322
|
+
Body: {
|
|
323
|
+
entityId: string;
|
|
324
|
+
newValues: {
|
|
325
|
+
title: string;
|
|
326
|
+
table: string;
|
|
327
|
+
parentId?: string;
|
|
328
|
+
};
|
|
329
|
+
};
|
|
330
|
+
}>("/api/entity/modifyEntityBase", async (request) => {
|
|
331
|
+
return await waitForHMRCompleted(async () => {
|
|
332
|
+
const { entityId, newValues } = request.body;
|
|
333
|
+
const entity = EntityManager.get(entityId);
|
|
334
|
+
entity.title = newValues.title;
|
|
335
|
+
entity.table = newValues.table;
|
|
336
|
+
entity.parentId = newValues.parentId;
|
|
337
|
+
await entity.save();
|
|
338
|
+
|
|
339
|
+
return 1;
|
|
340
|
+
});
|
|
341
|
+
});
|
|
342
|
+
|
|
343
|
+
server.post<{
|
|
344
|
+
Body: {
|
|
345
|
+
entityId: string;
|
|
346
|
+
subsetKey: string;
|
|
347
|
+
fields: string[];
|
|
348
|
+
};
|
|
349
|
+
}>("/api/entity/modifySubset", async (request) => {
|
|
350
|
+
return await waitForHMRCompleted(async () => {
|
|
351
|
+
const { entityId, subsetKey, fields } = request.body;
|
|
352
|
+
const entity = EntityManager.get(entityId);
|
|
353
|
+
entity.subsets[subsetKey] = fields;
|
|
354
|
+
await entity.save();
|
|
355
|
+
|
|
356
|
+
return { updated: fields };
|
|
357
|
+
});
|
|
358
|
+
});
|
|
359
|
+
|
|
360
|
+
server.post<{
|
|
361
|
+
Body: {
|
|
362
|
+
entityId: string;
|
|
363
|
+
subsetKey: string;
|
|
364
|
+
};
|
|
365
|
+
}>("/api/entity/delSubset", async (request) => {
|
|
366
|
+
return await waitForHMRCompleted(async () => {
|
|
367
|
+
const { entityId, subsetKey } = request.body;
|
|
368
|
+
const entity = EntityManager.get(entityId);
|
|
369
|
+
delete entity.subsets[subsetKey];
|
|
370
|
+
await entity.save();
|
|
371
|
+
|
|
372
|
+
return 1;
|
|
373
|
+
});
|
|
374
|
+
});
|
|
375
|
+
|
|
376
|
+
server.post<{
|
|
377
|
+
Body: {
|
|
378
|
+
entityId: string;
|
|
379
|
+
newProp: EntityProp;
|
|
380
|
+
at?: number;
|
|
381
|
+
};
|
|
382
|
+
}>("/api/entity/createProp", async (request) => {
|
|
383
|
+
return await waitForHMRCompleted(async () => {
|
|
384
|
+
const { entityId, at, newProp } = request.body;
|
|
385
|
+
const entity = EntityManager.get(entityId);
|
|
386
|
+
await entity.createProp(newProp, at);
|
|
387
|
+
return true;
|
|
388
|
+
});
|
|
389
|
+
});
|
|
390
|
+
|
|
391
|
+
server.post<{
|
|
392
|
+
Body: {
|
|
393
|
+
entityId: string;
|
|
394
|
+
newProp: EntityProp;
|
|
395
|
+
at: number;
|
|
396
|
+
};
|
|
397
|
+
}>("/api/entity/modifyProp", async (request) => {
|
|
398
|
+
return await waitForHMRCompleted(async () => {
|
|
399
|
+
const { entityId, at, newProp } = request.body;
|
|
400
|
+
|
|
401
|
+
const entity = EntityManager.get(entityId);
|
|
402
|
+
entity.modifyProp(newProp, at);
|
|
403
|
+
|
|
404
|
+
return true;
|
|
405
|
+
});
|
|
406
|
+
});
|
|
407
|
+
|
|
408
|
+
server.post<{
|
|
409
|
+
Body: {
|
|
410
|
+
entityId: string;
|
|
411
|
+
at: number;
|
|
412
|
+
};
|
|
413
|
+
}>("/api/entity/delProp", async (request) => {
|
|
414
|
+
return await waitForHMRCompleted(async () => {
|
|
415
|
+
const { entityId, at } = request.body;
|
|
416
|
+
|
|
417
|
+
const entity = EntityManager.get(entityId);
|
|
418
|
+
entity.delProp(at);
|
|
419
|
+
return true;
|
|
420
|
+
});
|
|
421
|
+
});
|
|
422
|
+
|
|
423
|
+
server.post<{
|
|
424
|
+
Body: {
|
|
425
|
+
entityId: string;
|
|
426
|
+
at: number;
|
|
427
|
+
to: number;
|
|
428
|
+
};
|
|
429
|
+
}>("/api/entity/moveProp", async (request) => {
|
|
430
|
+
return await waitForHMRCompleted(async () => {
|
|
431
|
+
const { entityId, at, to } = request.body;
|
|
432
|
+
|
|
433
|
+
const entity = EntityManager.get(entityId);
|
|
434
|
+
entity.moveProp(at, to);
|
|
435
|
+
|
|
436
|
+
return true;
|
|
437
|
+
});
|
|
438
|
+
});
|
|
439
|
+
|
|
440
|
+
server.post<{
|
|
441
|
+
Body: {
|
|
442
|
+
entityId: string;
|
|
443
|
+
indexes: EntityIndex[];
|
|
444
|
+
};
|
|
445
|
+
}>("/api/entity/modifyIndexes", async (request) => {
|
|
446
|
+
return await waitForHMRCompleted(async () => {
|
|
447
|
+
const { entityId, indexes } = request.body;
|
|
448
|
+
const entity = EntityManager.get(entityId);
|
|
449
|
+
entity.indexes = indexes;
|
|
450
|
+
await entity.save();
|
|
451
|
+
|
|
452
|
+
return { updated: indexes };
|
|
453
|
+
});
|
|
454
|
+
});
|
|
455
|
+
|
|
456
|
+
server.post<{
|
|
457
|
+
Body: {
|
|
458
|
+
entityId: string;
|
|
459
|
+
enumLabels: Entity["enumLabels"];
|
|
460
|
+
};
|
|
461
|
+
}>("/api/entity/modifyEnumLabels", async (request) => {
|
|
462
|
+
return await waitForHMRCompleted(async () => {
|
|
463
|
+
const { entityId, enumLabels } = request.body;
|
|
464
|
+
const entity = EntityManager.get(entityId);
|
|
465
|
+
entity.enumLabels = enumLabels;
|
|
466
|
+
await entity.save();
|
|
467
|
+
|
|
468
|
+
return { updated: enumLabels };
|
|
469
|
+
});
|
|
470
|
+
});
|
|
471
|
+
|
|
472
|
+
server.post<{
|
|
473
|
+
Body: {
|
|
474
|
+
entityId: string;
|
|
475
|
+
newEnumId: string;
|
|
476
|
+
};
|
|
477
|
+
}>("/api/entity/createEnumId", async (request) => {
|
|
478
|
+
return await waitForHMRCompleted(async () => {
|
|
479
|
+
const { entityId, newEnumId } = request.body;
|
|
480
|
+
const entity = EntityManager.get(entityId);
|
|
481
|
+
|
|
482
|
+
if (entity.enumLabels[newEnumId]) {
|
|
483
|
+
throw new Error(`이미 존재하는 enumId입니다: ${newEnumId}`);
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
entity.enumLabels[newEnumId] = {
|
|
487
|
+
...(newEnumId.endsWith("Status")
|
|
488
|
+
? {
|
|
489
|
+
active: "노출",
|
|
490
|
+
hidden: "숨김",
|
|
491
|
+
}
|
|
492
|
+
: {
|
|
493
|
+
"": "",
|
|
494
|
+
}),
|
|
495
|
+
};
|
|
496
|
+
await entity.save();
|
|
497
|
+
|
|
498
|
+
return 1;
|
|
499
|
+
});
|
|
500
|
+
});
|
|
501
|
+
|
|
502
|
+
server.post<{
|
|
503
|
+
Body: {
|
|
504
|
+
entityId: string;
|
|
505
|
+
enumId: {
|
|
506
|
+
before: string;
|
|
507
|
+
after: string;
|
|
508
|
+
};
|
|
509
|
+
};
|
|
510
|
+
}>("/api/entity/modifyEnumId", async (request) => {
|
|
511
|
+
return await waitForHMRCompleted(async () => {
|
|
512
|
+
const { entityId, enumId } = request.body;
|
|
513
|
+
const entityIds = EntityManager.getAllIds();
|
|
514
|
+
const isExists = entityIds.some((entityId) => {
|
|
515
|
+
const entity = EntityManager.get(entityId);
|
|
516
|
+
return Object.keys(entity.enumLabels).includes(enumId.after);
|
|
517
|
+
});
|
|
518
|
+
if (isExists) {
|
|
519
|
+
throw new Error(`이미 존재하는 EnumId입니다: ${enumId.after}`);
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
const entity = EntityManager.get(entityId);
|
|
523
|
+
entity.enumLabels[enumId.after] = entity.enumLabels[enumId.before];
|
|
524
|
+
delete entity.enumLabels[enumId.before];
|
|
525
|
+
|
|
526
|
+
await entity.save();
|
|
527
|
+
|
|
528
|
+
for (const entityId of entityIds) {
|
|
529
|
+
const entity = EntityManager.get(entityId);
|
|
530
|
+
for (const prop of entity.props) {
|
|
531
|
+
if (prop.type === "enum" && prop.id === enumId.before) {
|
|
532
|
+
prop.id = enumId.after;
|
|
533
|
+
}
|
|
534
|
+
}
|
|
535
|
+
await entity.save();
|
|
536
|
+
}
|
|
537
|
+
});
|
|
538
|
+
});
|
|
539
|
+
|
|
540
|
+
server.post<{
|
|
541
|
+
Body: {
|
|
542
|
+
entityId: string;
|
|
543
|
+
enumId: string;
|
|
544
|
+
};
|
|
545
|
+
}>("/api/entity/deleteEnumId", async (request) => {
|
|
546
|
+
return await waitForHMRCompleted(async () => {
|
|
547
|
+
const { entityId, enumId } = request.body;
|
|
548
|
+
|
|
549
|
+
const entityIds = EntityManager.getAllIds();
|
|
550
|
+
const isReferenced = entityIds
|
|
551
|
+
.flatMap((entityId) => EntityManager.get(entityId).props)
|
|
552
|
+
.some((prop) => prop.type === "enum" && prop.id === enumId);
|
|
553
|
+
if (isReferenced) {
|
|
554
|
+
throw new Error(`${enumId}를 참조하는 프로퍼티가 존재합니다.`);
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
const entity = EntityManager.get(entityId);
|
|
558
|
+
delete entity.enumLabels[enumId];
|
|
559
|
+
await entity.save();
|
|
560
|
+
});
|
|
561
|
+
});
|
|
562
|
+
|
|
563
|
+
server.get<{
|
|
564
|
+
Querystring: {
|
|
565
|
+
entityId: string;
|
|
566
|
+
};
|
|
567
|
+
}>("/api/entity/getTableColumns", async (request) => {
|
|
568
|
+
const { entityId } = request.query;
|
|
569
|
+
const entity = EntityManager.get(entityId);
|
|
570
|
+
const columns = entity.getTableColumns();
|
|
571
|
+
return { columns };
|
|
572
|
+
});
|
|
573
|
+
|
|
574
|
+
server.get("/api/migrations/status", async () => {
|
|
575
|
+
const status = await migrator.getStatus();
|
|
576
|
+
|
|
577
|
+
return { status };
|
|
578
|
+
});
|
|
579
|
+
|
|
580
|
+
server.post<{
|
|
581
|
+
Body: {
|
|
582
|
+
action: "apply" | "rollback" | "shadow";
|
|
583
|
+
targets: (keyof SonamuDBConfig)[];
|
|
584
|
+
};
|
|
585
|
+
}>("/api/migrations/runAction", async (request): Promise<MigrationResult> => {
|
|
586
|
+
const { action, targets } = request.body;
|
|
587
|
+
|
|
588
|
+
if (action === "shadow") {
|
|
589
|
+
return migrator.runShadowTest();
|
|
590
|
+
} else {
|
|
591
|
+
return migrator.runAction(action, targets);
|
|
592
|
+
}
|
|
593
|
+
});
|
|
594
|
+
|
|
595
|
+
server.post<{
|
|
596
|
+
Body: {
|
|
597
|
+
codeNames: string[];
|
|
598
|
+
};
|
|
599
|
+
}>("/api/migrations/delCodes", async (request) => {
|
|
600
|
+
const { codeNames } = request.body;
|
|
601
|
+
return await migrator.delCodes(codeNames);
|
|
602
|
+
});
|
|
603
|
+
|
|
604
|
+
server.post("/api/migrations/generatePreparedCodes", async (_requestt) => {
|
|
605
|
+
return await migrator.generatePreparedCodes();
|
|
606
|
+
});
|
|
607
|
+
|
|
608
|
+
server.post<{
|
|
609
|
+
Body: {
|
|
610
|
+
templateGroupName: "Entity" | "Enums";
|
|
611
|
+
entityIds: string[];
|
|
612
|
+
templateKeys: string[];
|
|
613
|
+
enumIds: string[];
|
|
614
|
+
};
|
|
615
|
+
}>("/api/scaffolding/getStatus", async (request) => {
|
|
616
|
+
const { templateGroupName, entityIds, templateKeys: _templateKeys, enumIds } = request.body;
|
|
617
|
+
if ((entityIds ?? []).length === 0) {
|
|
618
|
+
throw new BadRequestException("entityIds must be provided");
|
|
619
|
+
} else if ((_templateKeys ?? []).length === 0) {
|
|
620
|
+
throw new BadRequestException("templateKeys must be provided");
|
|
621
|
+
} else if (templateGroupName === "Enums" && (enumIds ?? []).length === 0) {
|
|
622
|
+
throw new BadRequestException("enumIds must be provided");
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
// sorting
|
|
626
|
+
entityIds.sort((a, b) => (a < b ? -1 : a > b ? 1 : 0));
|
|
627
|
+
const templateKeys = TemplateKey.options.filter((tk) => _templateKeys.includes(tk));
|
|
628
|
+
|
|
629
|
+
const combinations = entityIds.flatMap((entityId) => {
|
|
630
|
+
if (templateGroupName === "Enums") {
|
|
631
|
+
const entityIds = [entityId, ...EntityManager.getChildrenIds(entityId)];
|
|
632
|
+
const allEnumIds = entityIds.flatMap((entityId) =>
|
|
633
|
+
Object.keys(EntityManager.get(entityId).enumLabels),
|
|
634
|
+
);
|
|
635
|
+
return templateKeys.flatMap((templateKey) =>
|
|
636
|
+
allEnumIds
|
|
637
|
+
.filter((enumId) => enumIds.includes(enumId))
|
|
638
|
+
.map((enumId) => [entityId, templateKey, enumId]),
|
|
639
|
+
);
|
|
640
|
+
} else {
|
|
641
|
+
return templateKeys.map((templateKey) => [entityId, templateKey]);
|
|
642
|
+
}
|
|
643
|
+
});
|
|
644
|
+
|
|
645
|
+
const statuses = await Promise.all(
|
|
646
|
+
combinations.map(async ([entityId, templateKey, enumId]) => {
|
|
647
|
+
const { subPath, fullPath, isExists } = await Sonamu.syncer.checkExistsGenCode(
|
|
648
|
+
entityId,
|
|
649
|
+
templateKey as TemplateKey,
|
|
650
|
+
enumId,
|
|
651
|
+
);
|
|
652
|
+
return {
|
|
653
|
+
entityId,
|
|
654
|
+
templateGroupName,
|
|
655
|
+
templateKey,
|
|
656
|
+
enumId,
|
|
657
|
+
subPath,
|
|
658
|
+
fullPath,
|
|
659
|
+
isExists,
|
|
660
|
+
};
|
|
661
|
+
}),
|
|
662
|
+
);
|
|
663
|
+
return { statuses };
|
|
664
|
+
});
|
|
665
|
+
|
|
666
|
+
server.post<{
|
|
667
|
+
Body: {
|
|
668
|
+
options: {
|
|
669
|
+
entityId: string;
|
|
670
|
+
templateKey: string;
|
|
671
|
+
enumId?: string;
|
|
672
|
+
overwrite?: boolean;
|
|
673
|
+
}[];
|
|
674
|
+
};
|
|
675
|
+
}>("/api/scaffolding/generate", async (request) => {
|
|
676
|
+
const { options } = request.body;
|
|
677
|
+
if (options.length === 0) {
|
|
678
|
+
throw new BadRequestException("options must be provided");
|
|
679
|
+
}
|
|
680
|
+
|
|
681
|
+
const result = await Promise.all(
|
|
682
|
+
options.map(async ({ entityId, templateKey, enumId, overwrite }) => {
|
|
683
|
+
try {
|
|
684
|
+
return await Sonamu.syncer.generateTemplate(
|
|
685
|
+
templateKey as TemplateKey,
|
|
686
|
+
{
|
|
687
|
+
entityId,
|
|
688
|
+
enumId,
|
|
689
|
+
} as {
|
|
690
|
+
entityId: string;
|
|
691
|
+
enumId?: string;
|
|
692
|
+
},
|
|
693
|
+
{
|
|
694
|
+
overwrite,
|
|
695
|
+
},
|
|
696
|
+
);
|
|
697
|
+
} catch (e) {
|
|
698
|
+
if (isSoException(e) && e.statusCode === 541) {
|
|
699
|
+
return null;
|
|
700
|
+
} else {
|
|
701
|
+
console.error(e);
|
|
702
|
+
throw e;
|
|
703
|
+
}
|
|
704
|
+
}
|
|
705
|
+
}),
|
|
706
|
+
);
|
|
707
|
+
console.log(result);
|
|
708
|
+
|
|
709
|
+
if (result.filter(nonNullable).length === 0) {
|
|
710
|
+
throw new ServiceUnavailableException("이미 모든 파일이 생성된 상태입니다.");
|
|
711
|
+
}
|
|
712
|
+
return result;
|
|
713
|
+
});
|
|
714
|
+
|
|
715
|
+
server.post<{
|
|
716
|
+
Body: {
|
|
717
|
+
option: {
|
|
718
|
+
entityId: string;
|
|
719
|
+
templateKey: string;
|
|
720
|
+
enumId?: string;
|
|
721
|
+
};
|
|
722
|
+
};
|
|
723
|
+
}>("/api/scaffolding/preview", async (request): Promise<{ pathAndCodes: PathAndCode[] }> => {
|
|
724
|
+
const { option } = request.body;
|
|
725
|
+
|
|
726
|
+
try {
|
|
727
|
+
const { templateKey, ...templateOptions } = option;
|
|
728
|
+
const pathAndCodes = await Sonamu.syncer.renderTemplate(
|
|
729
|
+
templateKey as TemplateKey,
|
|
730
|
+
templateOptions,
|
|
731
|
+
);
|
|
732
|
+
|
|
733
|
+
return { pathAndCodes };
|
|
734
|
+
} catch (e) {
|
|
735
|
+
console.error(e);
|
|
736
|
+
throw e;
|
|
737
|
+
}
|
|
738
|
+
});
|
|
739
|
+
|
|
740
|
+
server.post("/api/fixture", async (request) => {
|
|
741
|
+
const { sourceDB, targetDB, search, duplicateCheck } = request.body as {
|
|
742
|
+
sourceDB: keyof SonamuDBConfig;
|
|
743
|
+
targetDB: keyof SonamuDBConfig;
|
|
744
|
+
search: FixtureSearchOptions;
|
|
745
|
+
duplicateCheck?: DuplicateCheckOptions;
|
|
746
|
+
};
|
|
747
|
+
|
|
748
|
+
return FixtureManager.getFixtures(sourceDB, targetDB, search, duplicateCheck);
|
|
749
|
+
});
|
|
750
|
+
|
|
751
|
+
server.post("/api/fixture/import", async (request) => {
|
|
752
|
+
const { db, fixtures } = request.body as {
|
|
753
|
+
db: keyof SonamuDBConfig;
|
|
754
|
+
fixtures: FixtureRecord[];
|
|
755
|
+
};
|
|
756
|
+
|
|
757
|
+
return FixtureManager.insertFixtures(db, fixtures);
|
|
758
|
+
});
|
|
759
|
+
|
|
760
|
+
server.post("/api/fixture/addFixtureLoader", async (request) => {
|
|
761
|
+
const { code } = request.body as { code: string };
|
|
762
|
+
|
|
763
|
+
return FixtureManager.addFixtureLoader(code);
|
|
764
|
+
});
|
|
765
|
+
|
|
766
|
+
// ui-web 빌드 파일 서빙
|
|
767
|
+
const uiDistPath = path.resolve(import.meta.dirname, "../ui-web");
|
|
768
|
+
server.register(await import("@fastify/static"), {
|
|
769
|
+
root: path.join(uiDistPath, "assets"),
|
|
770
|
+
prefix: "/assets",
|
|
771
|
+
decorateReply: false,
|
|
772
|
+
});
|
|
773
|
+
|
|
774
|
+
// SPA fallback - /sonamu-ui/* 경로는 전부 index.html로
|
|
775
|
+
server.get("*", async (_request, reply) => {
|
|
776
|
+
reply.headers({ "Content-type": "text/html" }).send(
|
|
777
|
+
fs
|
|
778
|
+
.readFileSync(path.resolve(import.meta.dirname, "../ui-web/index.html"))
|
|
779
|
+
.toString()
|
|
780
|
+
.replace("{{projectName}}", Sonamu.config.projectName ?? "UnknownSonamuProject"),
|
|
781
|
+
);
|
|
782
|
+
});
|
|
783
|
+
},
|
|
784
|
+
{ prefix: "/sonamu-ui" },
|
|
785
|
+
);
|
|
786
|
+
}
|