plugin-migration-manager 2.0.0
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/INSTALL.md +100 -0
- package/LICENSE +201 -0
- package/Migration_Manager_Documentation_1.0.pdf +0 -0
- package/README.md +30 -0
- package/client.d.ts +2 -0
- package/client.js +1 -0
- package/dist/client/bf902aacdcc9b8b8.js +10 -0
- package/dist/client/index.js +10 -0
- package/dist/externalVersion.js +18 -0
- package/dist/index.js +48 -0
- package/dist/locale/en-US.json +8 -0
- package/dist/locale/id-ID.json +8 -0
- package/dist/server/controllers/migration.js +1161 -0
- package/dist/server/index.js +70 -0
- package/package.json +23 -0
- package/plugin-migration-manager-2.0.0.tgz +0 -0
- package/server.d.ts +2 -0
- package/server.js +1 -0
- package/src/client/index.tsx +18 -0
- package/src/client/pages/MigrationPage.tsx +565 -0
- package/src/index.ts +2 -0
- package/src/locale/en-US.json +8 -0
- package/src/locale/id-ID.json +8 -0
- package/src/server/controllers/migration.ts +1217 -0
- package/src/server/index.ts +34 -0
|
@@ -0,0 +1,1161 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* This file is part of the NocoBase (R) project.
|
|
3
|
+
* Copyright (c) 2020-2024 NocoBase Co., Ltd.
|
|
4
|
+
* Authors: NocoBase Team.
|
|
5
|
+
*
|
|
6
|
+
* This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
|
|
7
|
+
* For more information, please refer to: https://www.nocobase.com/agreement.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
var __defProp = Object.defineProperty;
|
|
11
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
12
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
13
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
14
|
+
var __export = (target, all) => {
|
|
15
|
+
for (var name in all)
|
|
16
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
17
|
+
};
|
|
18
|
+
var __copyProps = (to, from, except, desc) => {
|
|
19
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
20
|
+
for (let key of __getOwnPropNames(from))
|
|
21
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
22
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
23
|
+
}
|
|
24
|
+
return to;
|
|
25
|
+
};
|
|
26
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
27
|
+
var migration_exports = {};
|
|
28
|
+
__export(migration_exports, {
|
|
29
|
+
MigrationController: () => MigrationController
|
|
30
|
+
});
|
|
31
|
+
module.exports = __toCommonJS(migration_exports);
|
|
32
|
+
function asArray(v) {
|
|
33
|
+
return Array.isArray(v) ? v : [];
|
|
34
|
+
}
|
|
35
|
+
function isPlainObject(v) {
|
|
36
|
+
return v && typeof v === "object" && v.constructor === Object;
|
|
37
|
+
}
|
|
38
|
+
function isEmptyObject(v) {
|
|
39
|
+
return isPlainObject(v) && Object.keys(v).length === 0;
|
|
40
|
+
}
|
|
41
|
+
function pickRouteFields(r) {
|
|
42
|
+
return {
|
|
43
|
+
title: r.title ?? null,
|
|
44
|
+
tooltip: r.tooltip ?? null,
|
|
45
|
+
icon: r.icon ?? null,
|
|
46
|
+
schemaUid: r.schemaUid ?? null,
|
|
47
|
+
menuSchemaUid: r.menuSchemaUid ?? null,
|
|
48
|
+
tabSchemaName: r.tabSchemaName ?? null,
|
|
49
|
+
type: r.type ?? null,
|
|
50
|
+
options: r.options ?? null,
|
|
51
|
+
sort: r.sort ?? null,
|
|
52
|
+
hideInMenu: r.hideInMenu ?? null,
|
|
53
|
+
enableTabs: r.enableTabs ?? null,
|
|
54
|
+
enableHeader: r.enableHeader ?? null,
|
|
55
|
+
displayTitle: r.displayTitle ?? null,
|
|
56
|
+
hidden: r.hidden ?? null
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
class MigrationController {
|
|
60
|
+
static async export(ctx) {
|
|
61
|
+
var _a;
|
|
62
|
+
const actionParams = ((_a = ctx.action) == null ? void 0 : _a.params) || {};
|
|
63
|
+
const reqBody = ctx.request.body || {};
|
|
64
|
+
const requestData = (actionParams.data && Object.keys(actionParams.data).length ? actionParams.data : void 0) || (reqBody.data && Object.keys(reqBody.data).length ? reqBody.data : void 0) || (actionParams.values && Object.keys(actionParams.values).length ? actionParams.values : void 0) || (Object.keys(reqBody).length ? reqBody : void 0) || {};
|
|
65
|
+
const collections = asArray(requestData.collections);
|
|
66
|
+
const workflows = asArray(requestData.workflows);
|
|
67
|
+
const uiSchemasInput = asArray(requestData.uiSchemas);
|
|
68
|
+
const routesInput = asArray(requestData.desktopRoutes || requestData.routes);
|
|
69
|
+
const db = ctx.db;
|
|
70
|
+
const app = ctx.app;
|
|
71
|
+
try {
|
|
72
|
+
const collectionsRepo = db.getRepository("collections");
|
|
73
|
+
const fieldsRepo = db.getRepository("fields");
|
|
74
|
+
const workflowRepo = db.getRepository("workflows");
|
|
75
|
+
const routeRepo = db.getRepository("desktopRoutes");
|
|
76
|
+
const uiSchemaRepo = db.getRepository("uiSchemas");
|
|
77
|
+
const payload = {
|
|
78
|
+
version: typeof (app == null ? void 0 : app.version) === "string" ? app.version : "unknown",
|
|
79
|
+
exportDate: (/* @__PURE__ */ new Date()).toISOString(),
|
|
80
|
+
collections: [],
|
|
81
|
+
workflows: [],
|
|
82
|
+
uiSchemas: [],
|
|
83
|
+
desktopRoutes: []
|
|
84
|
+
};
|
|
85
|
+
for (const name of collections) {
|
|
86
|
+
try {
|
|
87
|
+
const colMeta = await collectionsRepo.findOne({ filter: { name } });
|
|
88
|
+
if (!colMeta) continue;
|
|
89
|
+
const primaryKey = colMeta.get("primaryKey") || "id";
|
|
90
|
+
const title = colMeta.get("title") || name;
|
|
91
|
+
const template = colMeta.get("template") || "general";
|
|
92
|
+
const runtimeCol = (() => {
|
|
93
|
+
try {
|
|
94
|
+
return db.getCollection(name);
|
|
95
|
+
} catch {
|
|
96
|
+
return null;
|
|
97
|
+
}
|
|
98
|
+
})();
|
|
99
|
+
const fieldRows = await fieldsRepo.find({ filter: { collectionName: name } });
|
|
100
|
+
const fields = (fieldRows || []).map((r) => {
|
|
101
|
+
const row = r.get ? r.get() : r;
|
|
102
|
+
let options = row.options ?? {};
|
|
103
|
+
if (isEmptyObject(options) && runtimeCol) {
|
|
104
|
+
try {
|
|
105
|
+
const rf = runtimeCol.getField(row.name);
|
|
106
|
+
if ((rf == null ? void 0 : rf.options) && !isEmptyObject(rf.options)) options = rf.options;
|
|
107
|
+
} catch {
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
return { name: row.name, type: row.type, interface: row.interface, options };
|
|
111
|
+
});
|
|
112
|
+
payload.collections.push({ name, title, primaryKey, template, fields });
|
|
113
|
+
} catch {
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
for (const id of workflows) {
|
|
117
|
+
try {
|
|
118
|
+
const wRow = await workflowRepo.findOne({ filter: { id }, appends: ["nodes"] });
|
|
119
|
+
if (!wRow) continue;
|
|
120
|
+
const w = wRow.get ? wRow.get() : wRow;
|
|
121
|
+
const idToKey = /* @__PURE__ */ new Map();
|
|
122
|
+
for (const n of w.nodes || []) idToKey.set(n.id, n.key);
|
|
123
|
+
const nodes = (w.nodes || []).map((n) => ({
|
|
124
|
+
key: n.key,
|
|
125
|
+
type: n.type,
|
|
126
|
+
title: n.title ?? null,
|
|
127
|
+
upstreamKey: n.upstreamId ? idToKey.get(n.upstreamId) ?? null : null,
|
|
128
|
+
downstreamKey: n.downstreamId ? idToKey.get(n.downstreamId) ?? null : null,
|
|
129
|
+
branchIndex: n.branchIndex ?? null,
|
|
130
|
+
config: n.config ?? {}
|
|
131
|
+
}));
|
|
132
|
+
payload.workflows.push({
|
|
133
|
+
key: w.key,
|
|
134
|
+
title: w.title ?? null,
|
|
135
|
+
description: w.description ?? null,
|
|
136
|
+
type: w.type,
|
|
137
|
+
sync: !!w.sync,
|
|
138
|
+
current: true,
|
|
139
|
+
triggerTitle: w.triggerTitle ?? null,
|
|
140
|
+
options: w.options ?? {},
|
|
141
|
+
config: w.config ?? {},
|
|
142
|
+
nodes
|
|
143
|
+
});
|
|
144
|
+
} catch {
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
const getCompleteSchemaTree = async (uid) => {
|
|
148
|
+
try {
|
|
149
|
+
const schema = await uiSchemaRepo.getJsonSchema(uid);
|
|
150
|
+
return schema || null;
|
|
151
|
+
} catch {
|
|
152
|
+
return null;
|
|
153
|
+
}
|
|
154
|
+
};
|
|
155
|
+
const pushedUids = /* @__PURE__ */ new Set();
|
|
156
|
+
const pushSchemaPayload = async (uid, sourceType) => {
|
|
157
|
+
if (!uid || pushedUids.has(uid)) return;
|
|
158
|
+
const schema = await getCompleteSchemaTree(uid);
|
|
159
|
+
if (schema) {
|
|
160
|
+
payload.uiSchemas.push({ mode: "complete", rootUid: uid, sourceType, data: schema });
|
|
161
|
+
pushedUids.add(uid);
|
|
162
|
+
}
|
|
163
|
+
};
|
|
164
|
+
for (const item of uiSchemasInput) {
|
|
165
|
+
try {
|
|
166
|
+
if (typeof item === "string") {
|
|
167
|
+
await pushSchemaPayload(item, "direct");
|
|
168
|
+
continue;
|
|
169
|
+
}
|
|
170
|
+
if (isPlainObject(item) && item.uid) {
|
|
171
|
+
await pushSchemaPayload(String(item.uid), "direct");
|
|
172
|
+
continue;
|
|
173
|
+
}
|
|
174
|
+
} catch {
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
const allRoutesRaw = await routeRepo.find();
|
|
178
|
+
const allRoutes = (allRoutesRaw || []).map((x) => x.get ? x.get() : x);
|
|
179
|
+
const byId = /* @__PURE__ */ new Map();
|
|
180
|
+
const childrenMap = /* @__PURE__ */ new Map();
|
|
181
|
+
for (const r of allRoutes) {
|
|
182
|
+
byId.set(r.id, r);
|
|
183
|
+
const pid = r.parentId ?? null;
|
|
184
|
+
if (!childrenMap.has(pid)) childrenMap.set(pid, []);
|
|
185
|
+
childrenMap.get(pid).push(r);
|
|
186
|
+
}
|
|
187
|
+
const selectedRoots = [];
|
|
188
|
+
for (const r of routesInput) {
|
|
189
|
+
let row = null;
|
|
190
|
+
if (typeof r === "number" || /^\d+$/.test(String(r))) {
|
|
191
|
+
row = await routeRepo.findOne({ filter: { id: Number(r) } });
|
|
192
|
+
row = row ? row.get ? row.get() : row : null;
|
|
193
|
+
} else if (isPlainObject(r) && (r.id || r.routeId)) {
|
|
194
|
+
const rid = Number(r.id ?? r.routeId);
|
|
195
|
+
row = byId.get(rid) || null;
|
|
196
|
+
} else if (typeof r === "string") {
|
|
197
|
+
row = allRoutes.find((x) => x.schemaUid === r) || null;
|
|
198
|
+
}
|
|
199
|
+
if (row) selectedRoots.push(row);
|
|
200
|
+
}
|
|
201
|
+
const buildTree = async (node, parentType) => {
|
|
202
|
+
const t = String(node.type || "").toLowerCase();
|
|
203
|
+
if (t === "tabs") {
|
|
204
|
+
if (parentType === "page") {
|
|
205
|
+
if (node.schemaUid) await pushSchemaPayload(String(node.schemaUid), "tabs");
|
|
206
|
+
return {
|
|
207
|
+
type: "tabs",
|
|
208
|
+
schemaUid: node.schemaUid ?? null,
|
|
209
|
+
tabSchemaName: node.tabSchemaName ?? null,
|
|
210
|
+
hidden: !!node.hidden
|
|
211
|
+
};
|
|
212
|
+
}
|
|
213
|
+
return null;
|
|
214
|
+
}
|
|
215
|
+
const entry = pickRouteFields(node);
|
|
216
|
+
if (node.schemaUid) {
|
|
217
|
+
await pushSchemaPayload(String(node.schemaUid), String(node.type || "route"));
|
|
218
|
+
}
|
|
219
|
+
const kids = childrenMap.get(node.id) || [];
|
|
220
|
+
const tabKids = kids.filter((k) => String(k.type || "").toLowerCase() === "tabs");
|
|
221
|
+
const otherKids = kids.filter((k) => String(k.type || "").toLowerCase() !== "tabs");
|
|
222
|
+
entry.children = [];
|
|
223
|
+
if (t === "page" && tabKids.length) {
|
|
224
|
+
for (const tk of tabKids) {
|
|
225
|
+
const packed = await buildTree(tk, "page");
|
|
226
|
+
if (packed) entry.children.push(packed);
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
for (const ok of otherKids) {
|
|
230
|
+
const childEntry = await buildTree(ok, t);
|
|
231
|
+
if (childEntry) entry.children.push(childEntry);
|
|
232
|
+
}
|
|
233
|
+
return entry;
|
|
234
|
+
};
|
|
235
|
+
for (const root of selectedRoots) {
|
|
236
|
+
const built = await buildTree(root, null);
|
|
237
|
+
if (built) payload.desktopRoutes.push(built);
|
|
238
|
+
}
|
|
239
|
+
ctx.status = 200;
|
|
240
|
+
ctx.type = "application/json";
|
|
241
|
+
ctx.body = { success: true, data: payload };
|
|
242
|
+
} catch (err) {
|
|
243
|
+
ctx.status = 500;
|
|
244
|
+
ctx.type = "application/json";
|
|
245
|
+
ctx.body = { success: false, message: String(err.message || "Export failed"), stack: err.stack };
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
static async import(ctx) {
|
|
249
|
+
var _a, _b, _c;
|
|
250
|
+
const actionParams = ((_a = ctx.action) == null ? void 0 : _a.params) || {};
|
|
251
|
+
const app = ctx.app;
|
|
252
|
+
const reqBody = ctx.request.body || {};
|
|
253
|
+
let importData = (actionParams.data && Object.keys(actionParams.data).length ? actionParams.data : void 0) || (reqBody.data && Object.keys(reqBody.data).length ? reqBody.data : void 0) || (actionParams.values && Object.keys(actionParams.values).length ? actionParams.values : void 0) || reqBody;
|
|
254
|
+
const collections = asArray(importData == null ? void 0 : importData.collections);
|
|
255
|
+
const workflows = asArray(importData == null ? void 0 : importData.workflows);
|
|
256
|
+
const uiSchemas = asArray(importData == null ? void 0 : importData.uiSchemas);
|
|
257
|
+
const desktopRoutes = asArray((importData == null ? void 0 : importData.desktopRoutes) || (importData == null ? void 0 : importData.routes));
|
|
258
|
+
const options = isPlainObject(importData == null ? void 0 : importData.options) ? importData.options : {};
|
|
259
|
+
const preview = !!options.preview;
|
|
260
|
+
const overwrite = !!options.overwrite;
|
|
261
|
+
const db = ctx.db;
|
|
262
|
+
const results = {
|
|
263
|
+
collections: { success: 0, failed: 0, skipped: 0, updated: 0, errors: [], pendingCreates: [] },
|
|
264
|
+
workflows: { success: 0, failed: 0, skipped: 0, updated: 0, errors: [], pendingCreates: [] },
|
|
265
|
+
uiSchemas: { success: 0, failed: 0, skipped: 0, updated: 0, errors: [], pendingCreates: [] },
|
|
266
|
+
desktopRoutes: { success: 0, failed: 0, skipped: 0, updated: 0, errors: [], pendingCreates: [] }
|
|
267
|
+
};
|
|
268
|
+
try {
|
|
269
|
+
const collectionsRepo = db.getRepository("collections");
|
|
270
|
+
const fieldsRepo = db.getRepository("fields");
|
|
271
|
+
for (const colConfig of collections) {
|
|
272
|
+
const collectionName = colConfig.name;
|
|
273
|
+
try {
|
|
274
|
+
const existingColMeta = await collectionsRepo.findOne({ filter: { name: collectionName } });
|
|
275
|
+
if (!existingColMeta) {
|
|
276
|
+
if (preview) {
|
|
277
|
+
results.collections.pendingCreates.push({
|
|
278
|
+
collection: collectionName,
|
|
279
|
+
title: colConfig.title || collectionName,
|
|
280
|
+
fieldsCount: (colConfig.fields || []).length
|
|
281
|
+
});
|
|
282
|
+
} else {
|
|
283
|
+
await collectionsRepo.create({
|
|
284
|
+
values: {
|
|
285
|
+
name: collectionName,
|
|
286
|
+
title: colConfig.title || collectionName,
|
|
287
|
+
template: colConfig.template || "general",
|
|
288
|
+
logging: true,
|
|
289
|
+
autoGenId: true,
|
|
290
|
+
createdBy: true,
|
|
291
|
+
updatedBy: true,
|
|
292
|
+
createdAt: true,
|
|
293
|
+
updatedAt: true,
|
|
294
|
+
sortable: true,
|
|
295
|
+
primaryKey: colConfig.primaryKey || "id",
|
|
296
|
+
dataSource: "main"
|
|
297
|
+
}
|
|
298
|
+
});
|
|
299
|
+
for (const f of colConfig.fields || []) {
|
|
300
|
+
await fieldsRepo.create({
|
|
301
|
+
values: {
|
|
302
|
+
collectionName,
|
|
303
|
+
name: f.name,
|
|
304
|
+
type: f.type || "string",
|
|
305
|
+
interface: f.interface || "input",
|
|
306
|
+
dataSource: "main",
|
|
307
|
+
...f.options || {}
|
|
308
|
+
}
|
|
309
|
+
});
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
results.collections.success++;
|
|
313
|
+
} else {
|
|
314
|
+
for (const fieldCfg of colConfig.fields || []) {
|
|
315
|
+
const existingField = await fieldsRepo.findOne({ filter: { name: fieldCfg.name, collectionName } });
|
|
316
|
+
if (!existingField && !preview) {
|
|
317
|
+
await fieldsRepo.create({
|
|
318
|
+
values: {
|
|
319
|
+
name: fieldCfg.name,
|
|
320
|
+
type: fieldCfg.type || "string",
|
|
321
|
+
interface: fieldCfg.interface || "input",
|
|
322
|
+
collectionName,
|
|
323
|
+
dataSource: "main",
|
|
324
|
+
...fieldCfg.options || {}
|
|
325
|
+
}
|
|
326
|
+
});
|
|
327
|
+
} else if (existingField && overwrite && !preview) {
|
|
328
|
+
await fieldsRepo.update({
|
|
329
|
+
filterByTk: existingField.get("id"),
|
|
330
|
+
values: {
|
|
331
|
+
type: fieldCfg.type || existingField.get("type"),
|
|
332
|
+
interface: fieldCfg.interface || existingField.get("interface"),
|
|
333
|
+
...fieldCfg.options || {}
|
|
334
|
+
}
|
|
335
|
+
});
|
|
336
|
+
results.collections.updated++;
|
|
337
|
+
} else if (!existingField && preview) {
|
|
338
|
+
results.collections.pendingCreates.push({ collection: collectionName, field: fieldCfg.name });
|
|
339
|
+
} else {
|
|
340
|
+
results.collections.skipped++;
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
results.collections.success++;
|
|
344
|
+
}
|
|
345
|
+
} catch (err) {
|
|
346
|
+
results.collections.failed++;
|
|
347
|
+
results.collections.errors.push({ collection: collectionName, error: String(err.message || err) });
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
const workflowRepo = db.getRepository("workflows");
|
|
351
|
+
let flowNodeRepo = null;
|
|
352
|
+
try {
|
|
353
|
+
flowNodeRepo = db.getRepository("flowNodes");
|
|
354
|
+
} catch {
|
|
355
|
+
}
|
|
356
|
+
if (!flowNodeRepo) {
|
|
357
|
+
try {
|
|
358
|
+
flowNodeRepo = db.getRepository("flow_nodes");
|
|
359
|
+
} catch {
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
if (!flowNodeRepo) {
|
|
363
|
+
try {
|
|
364
|
+
flowNodeRepo = db.getRepository("nodes");
|
|
365
|
+
} catch {
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
if (!flowNodeRepo) throw new Error("flowNodes repository not found");
|
|
369
|
+
for (const wf of workflows) {
|
|
370
|
+
try {
|
|
371
|
+
if (!wf || !wf.title || !wf.type) {
|
|
372
|
+
results.workflows.skipped++;
|
|
373
|
+
continue;
|
|
374
|
+
}
|
|
375
|
+
let existingWorkflow = null;
|
|
376
|
+
if (wf.key) {
|
|
377
|
+
existingWorkflow = await workflowRepo.findOne({
|
|
378
|
+
filter: { key: wf.key },
|
|
379
|
+
appends: ["nodes"]
|
|
380
|
+
});
|
|
381
|
+
}
|
|
382
|
+
if (!existingWorkflow) {
|
|
383
|
+
const allWorkflows = await workflowRepo.find({
|
|
384
|
+
filter: {
|
|
385
|
+
title: wf.title,
|
|
386
|
+
type: wf.type
|
|
387
|
+
},
|
|
388
|
+
appends: ["nodes"]
|
|
389
|
+
});
|
|
390
|
+
if (allWorkflows && allWorkflows.length > 0) {
|
|
391
|
+
existingWorkflow = allWorkflows[0];
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
if (existingWorkflow && !overwrite) {
|
|
395
|
+
results.workflows.skipped++;
|
|
396
|
+
continue;
|
|
397
|
+
}
|
|
398
|
+
if (preview) {
|
|
399
|
+
results.workflows.pendingCreates.push({
|
|
400
|
+
key: wf.key || "(auto)",
|
|
401
|
+
title: wf.title,
|
|
402
|
+
action: existingWorkflow ? "update" : "create"
|
|
403
|
+
});
|
|
404
|
+
results.workflows.success++;
|
|
405
|
+
continue;
|
|
406
|
+
}
|
|
407
|
+
let workflowId;
|
|
408
|
+
if (existingWorkflow && overwrite) {
|
|
409
|
+
await workflowRepo.update({
|
|
410
|
+
filterByTk: existingWorkflow.get("id"),
|
|
411
|
+
values: {
|
|
412
|
+
title: wf.title,
|
|
413
|
+
type: wf.type,
|
|
414
|
+
sync: !!wf.sync,
|
|
415
|
+
description: wf.description ?? null,
|
|
416
|
+
triggerTitle: wf.triggerTitle ?? null,
|
|
417
|
+
options: wf.options ?? {},
|
|
418
|
+
config: wf.config ?? {}
|
|
419
|
+
}
|
|
420
|
+
});
|
|
421
|
+
workflowId = existingWorkflow.get("id");
|
|
422
|
+
const oldNodes = existingWorkflow.get("nodes") || [];
|
|
423
|
+
for (const oldNode of oldNodes) {
|
|
424
|
+
await flowNodeRepo.destroy({ filterByTk: oldNode.id });
|
|
425
|
+
}
|
|
426
|
+
results.workflows.updated++;
|
|
427
|
+
} else {
|
|
428
|
+
const createdW = await workflowRepo.create({
|
|
429
|
+
values: {
|
|
430
|
+
current: true,
|
|
431
|
+
title: wf.title,
|
|
432
|
+
type: wf.type,
|
|
433
|
+
sync: !!wf.sync,
|
|
434
|
+
description: wf.description ?? null,
|
|
435
|
+
triggerTitle: wf.triggerTitle ?? null,
|
|
436
|
+
options: wf.options ?? {},
|
|
437
|
+
config: wf.config ?? {}
|
|
438
|
+
}
|
|
439
|
+
});
|
|
440
|
+
workflowId = (createdW == null ? void 0 : createdW.get) ? createdW.get("id") : createdW == null ? void 0 : createdW.id;
|
|
441
|
+
results.workflows.success++;
|
|
442
|
+
}
|
|
443
|
+
const nodesInput = Array.isArray(wf.nodes) ? wf.nodes : [];
|
|
444
|
+
const keyToId = /* @__PURE__ */ new Map();
|
|
445
|
+
const existingByKey = /* @__PURE__ */ new Map();
|
|
446
|
+
try {
|
|
447
|
+
const existingNodes = await flowNodeRepo.find({ filter: { workflowId } });
|
|
448
|
+
for (const en of existingNodes || []) {
|
|
449
|
+
const row = en.get ? en.get() : en;
|
|
450
|
+
if (row.key) existingByKey.set(row.key, row);
|
|
451
|
+
}
|
|
452
|
+
} catch {
|
|
453
|
+
}
|
|
454
|
+
for (const n of nodesInput) {
|
|
455
|
+
if (!n || !n.key) continue;
|
|
456
|
+
const existing = existingByKey.get(n.key);
|
|
457
|
+
if (existing) {
|
|
458
|
+
const id = existing.id ?? ((_b = existing.get) == null ? void 0 : _b.call(existing, "id"));
|
|
459
|
+
await flowNodeRepo.update({
|
|
460
|
+
filterByTk: id,
|
|
461
|
+
values: {
|
|
462
|
+
type: n.type ?? existing.type ?? null,
|
|
463
|
+
title: n.title ?? existing.title ?? null,
|
|
464
|
+
config: n.config ?? existing.config ?? {},
|
|
465
|
+
branchIndex: n.branchIndex ?? null
|
|
466
|
+
}
|
|
467
|
+
});
|
|
468
|
+
keyToId.set(n.key, Number(id));
|
|
469
|
+
} else {
|
|
470
|
+
const created = await flowNodeRepo.create({
|
|
471
|
+
values: {
|
|
472
|
+
workflowId,
|
|
473
|
+
key: n.key,
|
|
474
|
+
type: n.type,
|
|
475
|
+
title: n.title ?? null,
|
|
476
|
+
config: n.config ?? {},
|
|
477
|
+
upstreamId: null,
|
|
478
|
+
downstreamId: null,
|
|
479
|
+
branchIndex: n.branchIndex ?? null
|
|
480
|
+
}
|
|
481
|
+
});
|
|
482
|
+
const id = (created == null ? void 0 : created.get) ? created.get("id") : created == null ? void 0 : created.id;
|
|
483
|
+
if (n.key) keyToId.set(n.key, Number(id));
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
if (overwrite) {
|
|
487
|
+
try {
|
|
488
|
+
const existingNodes = await flowNodeRepo.find({ filter: { workflowId } });
|
|
489
|
+
const inputKeys = new Set(nodesInput.filter((x) => x && x.key).map((x) => x.key));
|
|
490
|
+
for (const en of existingNodes || []) {
|
|
491
|
+
const row = en.get ? en.get() : en;
|
|
492
|
+
if (row.key && !inputKeys.has(row.key)) {
|
|
493
|
+
await flowNodeRepo.destroy({ filterByTk: row.id });
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
} catch {
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
for (const n of nodesInput) {
|
|
500
|
+
if (!n || !n.key) continue;
|
|
501
|
+
const id = keyToId.get(n.key);
|
|
502
|
+
if (!id) continue;
|
|
503
|
+
const upstreamId = n.upstreamKey ? keyToId.get(n.upstreamKey) ?? null : null;
|
|
504
|
+
const downstreamId = n.downstreamKey ? keyToId.get(n.downstreamKey) ?? null : null;
|
|
505
|
+
await flowNodeRepo.update({
|
|
506
|
+
filterByTk: id,
|
|
507
|
+
values: {
|
|
508
|
+
upstreamId,
|
|
509
|
+
downstreamId,
|
|
510
|
+
branchIndex: n.branchIndex ?? null
|
|
511
|
+
}
|
|
512
|
+
});
|
|
513
|
+
}
|
|
514
|
+
} catch (e) {
|
|
515
|
+
results.workflows.failed++;
|
|
516
|
+
results.workflows.errors.push({
|
|
517
|
+
workflow: (wf == null ? void 0 : wf.title) || (wf == null ? void 0 : wf.key) || "unknown",
|
|
518
|
+
error: String((e == null ? void 0 : e.message) || e)
|
|
519
|
+
});
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
const routeRepo = db.getRepository("desktopRoutes");
|
|
523
|
+
const uiSchemaRepo = db.getRepository("uiSchemas");
|
|
524
|
+
const treeRepo = db.getRepository("uiSchemaTreePath");
|
|
525
|
+
const createRouteRecursive = async (node, parentId) => {
|
|
526
|
+
try {
|
|
527
|
+
const nodeType = String(node.type || "").toLowerCase();
|
|
528
|
+
if (nodeType === "tabs") {
|
|
529
|
+
return null;
|
|
530
|
+
}
|
|
531
|
+
if ((node == null ? void 0 : node.schemaUid) && nodeType !== "link") {
|
|
532
|
+
const existBySchema = await routeRepo.findOne({ filter: { schemaUid: node.schemaUid } });
|
|
533
|
+
if (existBySchema) {
|
|
534
|
+
results.desktopRoutes.skipped++;
|
|
535
|
+
return existBySchema.get ? existBySchema.get("id") : existBySchema.id;
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
const values = {
|
|
539
|
+
parentId,
|
|
540
|
+
title: node.title ?? null,
|
|
541
|
+
tooltip: node.tooltip ?? null,
|
|
542
|
+
icon: node.icon ?? null,
|
|
543
|
+
schemaUid: node.schemaUid ?? null,
|
|
544
|
+
menuSchemaUid: node.menuSchemaUid ?? null,
|
|
545
|
+
tabSchemaName: node.tabSchemaName ?? null,
|
|
546
|
+
type: node.type ?? null,
|
|
547
|
+
options: node.options ?? null,
|
|
548
|
+
sort: node.sort ?? null,
|
|
549
|
+
hideInMenu: node.hideInMenu ?? null,
|
|
550
|
+
enableTabs: node.enableTabs ?? null,
|
|
551
|
+
enableHeader: node.enableHeader ?? null,
|
|
552
|
+
displayTitle: node.displayTitle ?? null,
|
|
553
|
+
hidden: node.hidden ?? null
|
|
554
|
+
};
|
|
555
|
+
const tabsChildren = asArray(node.children).filter((c) => String(c == null ? void 0 : c.type).toLowerCase() === "tabs");
|
|
556
|
+
if (nodeType === "page" && tabsChildren.length > 0) {
|
|
557
|
+
values.children = tabsChildren.map((c) => ({
|
|
558
|
+
type: "tabs",
|
|
559
|
+
schemaUid: c.schemaUid || null,
|
|
560
|
+
tabSchemaName: c.tabSchemaName || null,
|
|
561
|
+
hidden: !!c.hidden
|
|
562
|
+
}));
|
|
563
|
+
}
|
|
564
|
+
if (preview) {
|
|
565
|
+
results.desktopRoutes.success++;
|
|
566
|
+
let fakeId = 0;
|
|
567
|
+
for (const child of asArray(node.children)) {
|
|
568
|
+
const ct = String((child == null ? void 0 : child.type) || "").toLowerCase();
|
|
569
|
+
if (ct === "tabs" && nodeType === "page") continue;
|
|
570
|
+
await createRouteRecursive(child, fakeId);
|
|
571
|
+
}
|
|
572
|
+
return null;
|
|
573
|
+
}
|
|
574
|
+
const created = await routeRepo.create({ values });
|
|
575
|
+
const newId = (created == null ? void 0 : created.get) ? created.get("id") : created == null ? void 0 : created.id;
|
|
576
|
+
results.desktopRoutes.success++;
|
|
577
|
+
for (const child of asArray(node.children)) {
|
|
578
|
+
const ct = String((child == null ? void 0 : child.type) || "").toLowerCase();
|
|
579
|
+
if (ct === "tabs" && nodeType === "page") continue;
|
|
580
|
+
await createRouteRecursive(child, newId);
|
|
581
|
+
}
|
|
582
|
+
return newId;
|
|
583
|
+
} catch (e) {
|
|
584
|
+
results.desktopRoutes.failed++;
|
|
585
|
+
results.desktopRoutes.errors.push({ route: (node == null ? void 0 : node.title) || (node == null ? void 0 : node.schemaUid) || "unknown", error: String(e.message || e) });
|
|
586
|
+
return null;
|
|
587
|
+
}
|
|
588
|
+
};
|
|
589
|
+
for (const root of desktopRoutes) {
|
|
590
|
+
await createRouteRecursive(root, null);
|
|
591
|
+
}
|
|
592
|
+
for (const bundle of uiSchemas) {
|
|
593
|
+
try {
|
|
594
|
+
const uid = bundle == null ? void 0 : bundle.rootUid;
|
|
595
|
+
const data = bundle == null ? void 0 : bundle.data;
|
|
596
|
+
if (!uid || !data) {
|
|
597
|
+
results.uiSchemas.failed++;
|
|
598
|
+
continue;
|
|
599
|
+
}
|
|
600
|
+
if (preview) {
|
|
601
|
+
results.uiSchemas.pendingCreates.push({ uid, action: "replace" });
|
|
602
|
+
results.uiSchemas.success++;
|
|
603
|
+
continue;
|
|
604
|
+
}
|
|
605
|
+
const exist = await uiSchemaRepo.findOne({ filter: { "x-uid": uid } });
|
|
606
|
+
if (exist) {
|
|
607
|
+
try {
|
|
608
|
+
if ((_c = app.schemaManager) == null ? void 0 : _c.removeSchema) await app.schemaManager.removeSchema(uid);
|
|
609
|
+
} catch {
|
|
610
|
+
}
|
|
611
|
+
try {
|
|
612
|
+
await treeRepo.destroy({ filter: { ancestor: uid }, force: true });
|
|
613
|
+
} catch {
|
|
614
|
+
}
|
|
615
|
+
try {
|
|
616
|
+
await treeRepo.destroy({ filter: { descendant: uid }, force: true });
|
|
617
|
+
} catch {
|
|
618
|
+
}
|
|
619
|
+
try {
|
|
620
|
+
await uiSchemaRepo.destroy({ filter: { "x-uid": uid }, force: true });
|
|
621
|
+
} catch {
|
|
622
|
+
}
|
|
623
|
+
}
|
|
624
|
+
if (typeof uiSchemaRepo.insert === "function") {
|
|
625
|
+
await uiSchemaRepo.insert(data);
|
|
626
|
+
} else {
|
|
627
|
+
await uiSchemaRepo.create({ values: { "x-uid": uid, name: (data == null ? void 0 : data.name) || "", schema: data } });
|
|
628
|
+
const ex0 = await treeRepo.findOne({ filter: { ancestor: uid, descendant: uid, depth: 0 } });
|
|
629
|
+
if (!ex0) {
|
|
630
|
+
await treeRepo.create({ values: { ancestor: uid, descendant: uid, depth: 0, async: false, type: null, sort: null } });
|
|
631
|
+
}
|
|
632
|
+
}
|
|
633
|
+
if (exist) results.uiSchemas.updated++;
|
|
634
|
+
else results.uiSchemas.success++;
|
|
635
|
+
} catch (e) {
|
|
636
|
+
results.uiSchemas.failed++;
|
|
637
|
+
results.uiSchemas.errors.push({ schema: (bundle == null ? void 0 : bundle.rootUid) || "unknown", error: String(e.message || e) });
|
|
638
|
+
}
|
|
639
|
+
}
|
|
640
|
+
const totalProcessed = collections.length + workflows.length + uiSchemas.length + desktopRoutes.length;
|
|
641
|
+
const totalSuccess = results.collections.success + results.workflows.success + results.uiSchemas.success + results.desktopRoutes.success;
|
|
642
|
+
const totalUpdated = results.collections.updated + results.workflows.updated + results.uiSchemas.updated + results.desktopRoutes.updated;
|
|
643
|
+
ctx.status = 200;
|
|
644
|
+
ctx.type = "application/json";
|
|
645
|
+
ctx.body = {
|
|
646
|
+
success: totalSuccess > 0 || totalUpdated > 0,
|
|
647
|
+
preview,
|
|
648
|
+
overwrite,
|
|
649
|
+
results,
|
|
650
|
+
summary: {
|
|
651
|
+
totalProcessed,
|
|
652
|
+
totalSuccess,
|
|
653
|
+
totalUpdated,
|
|
654
|
+
totalFailed: results.collections.failed + results.workflows.failed + results.uiSchemas.failed + results.desktopRoutes.failed,
|
|
655
|
+
totalSkipped: results.collections.skipped + results.workflows.skipped + results.uiSchemas.skipped + results.desktopRoutes.skipped
|
|
656
|
+
},
|
|
657
|
+
message: preview ? "Preview completed. Set preview:false to apply changes." : totalSuccess + totalUpdated > 0 ? `Import completed. ${totalSuccess} created, ${totalUpdated} updated.` : "No changes applied."
|
|
658
|
+
};
|
|
659
|
+
} catch (err) {
|
|
660
|
+
ctx.status = 500;
|
|
661
|
+
ctx.type = "application/json";
|
|
662
|
+
ctx.body = { success: false, message: String(err.message || "Import failed"), stack: err.stack };
|
|
663
|
+
}
|
|
664
|
+
}
|
|
665
|
+
static async list(ctx) {
|
|
666
|
+
const db = ctx.db;
|
|
667
|
+
try {
|
|
668
|
+
const collectionsRepo = db.getRepository("collections");
|
|
669
|
+
const fieldsRepo = db.getRepository("fields");
|
|
670
|
+
const workflowRepo = db.getRepository("workflows");
|
|
671
|
+
const routeRepo = db.getRepository("desktopRoutes");
|
|
672
|
+
const colRows = await collectionsRepo.find();
|
|
673
|
+
const cols = (colRows || []).map((r) => r.get ? r.get() : r);
|
|
674
|
+
const nonSystem = cols.filter((c) => !((c == null ? void 0 : c.options) && c.options.origin));
|
|
675
|
+
const names = nonSystem.map((c) => c.name);
|
|
676
|
+
const fldRows = await fieldsRepo.find({ filter: { collectionName: { $in: names } } });
|
|
677
|
+
const fieldsByCollection = /* @__PURE__ */ new Map();
|
|
678
|
+
for (const fr of fldRows || []) {
|
|
679
|
+
const row = fr.get ? fr.get() : fr;
|
|
680
|
+
const k = String(row.collectionName || "");
|
|
681
|
+
fieldsByCollection.set(k, (fieldsByCollection.get(k) || 0) + 1);
|
|
682
|
+
}
|
|
683
|
+
const collectionsArray = nonSystem.map((c) => ({
|
|
684
|
+
name: String(c.name || ""),
|
|
685
|
+
title: String(c.title || c.name || ""),
|
|
686
|
+
fields: Number(fieldsByCollection.get(String(c.name || "")) || 0)
|
|
687
|
+
})).filter((c) => !!c.name && !c.name.startsWith("_")).sort((a, b) => a.name.localeCompare(b.name));
|
|
688
|
+
let workflows = [];
|
|
689
|
+
try {
|
|
690
|
+
const workflowsData = await workflowRepo.find();
|
|
691
|
+
workflows = (workflowsData || []).map((w) => ({
|
|
692
|
+
id: w.get("id"),
|
|
693
|
+
title: String(w.get("title") || ""),
|
|
694
|
+
key: String(w.get("key") || ""),
|
|
695
|
+
enabled: Boolean(w.get("enabled"))
|
|
696
|
+
}));
|
|
697
|
+
} catch {
|
|
698
|
+
}
|
|
699
|
+
let uiSchemas = [];
|
|
700
|
+
try {
|
|
701
|
+
const routes = await routeRepo.find();
|
|
702
|
+
const routeRows = (routes || []).map((r) => r.get ? r.get() : r);
|
|
703
|
+
const schemaTypes = /* @__PURE__ */ new Set(["page", "menu", "group"]);
|
|
704
|
+
const linkTypes = /* @__PURE__ */ new Set(["link"]);
|
|
705
|
+
const roots = routeRows.filter((r) => r.parentId == null);
|
|
706
|
+
const schemaEntries = roots.filter((r) => !!r.schemaUid && schemaTypes.has(String(r.type || "").toLowerCase())).map((r) => ({
|
|
707
|
+
routeId: r.id,
|
|
708
|
+
displayTitle: r.title || r.displayTitle || "Untitled",
|
|
709
|
+
type: r.type || "",
|
|
710
|
+
uid: r.schemaUid || null,
|
|
711
|
+
schemaUid: r.schemaUid || null,
|
|
712
|
+
menuSchemaUid: r.menuSchemaUid || null,
|
|
713
|
+
isLink: false,
|
|
714
|
+
linkTarget: null
|
|
715
|
+
}));
|
|
716
|
+
const linkEntries = roots.filter((r) => linkTypes.has(String(r.type || "").toLowerCase())).map((r) => {
|
|
717
|
+
var _a, _b, _c;
|
|
718
|
+
return {
|
|
719
|
+
routeId: r.id,
|
|
720
|
+
displayTitle: r.title || r.displayTitle || "Untitled",
|
|
721
|
+
type: r.type || "link",
|
|
722
|
+
uid: r.schemaUid || null,
|
|
723
|
+
schemaUid: r.schemaUid || null,
|
|
724
|
+
menuSchemaUid: r.menuSchemaUid || null,
|
|
725
|
+
isLink: true,
|
|
726
|
+
linkTarget: ((_a = r.options) == null ? void 0 : _a.to) ?? ((_b = r.options) == null ? void 0 : _b.url) ?? ((_c = r.options) == null ? void 0 : _c.path) ?? null
|
|
727
|
+
};
|
|
728
|
+
});
|
|
729
|
+
uiSchemas = [...schemaEntries, ...linkEntries];
|
|
730
|
+
} catch {
|
|
731
|
+
}
|
|
732
|
+
ctx.status = 200;
|
|
733
|
+
ctx.type = "application/json";
|
|
734
|
+
ctx.body = {
|
|
735
|
+
success: true,
|
|
736
|
+
data: {
|
|
737
|
+
collections: collectionsArray,
|
|
738
|
+
workflows,
|
|
739
|
+
uiSchemas
|
|
740
|
+
}
|
|
741
|
+
};
|
|
742
|
+
} catch (err) {
|
|
743
|
+
ctx.status = 500;
|
|
744
|
+
ctx.type = "application/json";
|
|
745
|
+
ctx.body = { success: false, message: String(err.message || "List failed") };
|
|
746
|
+
}
|
|
747
|
+
}
|
|
748
|
+
static async validate(ctx) {
|
|
749
|
+
var _a, _b;
|
|
750
|
+
const importData = ((_b = (_a = ctx.action) == null ? void 0 : _a.params) == null ? void 0 : _b.values) || ctx.request.body || {};
|
|
751
|
+
const collections = asArray(importData == null ? void 0 : importData.collections);
|
|
752
|
+
const workflows = asArray(importData == null ? void 0 : importData.workflows);
|
|
753
|
+
const uiSchemas = asArray(importData == null ? void 0 : importData.uiSchemas);
|
|
754
|
+
const db = ctx.db;
|
|
755
|
+
try {
|
|
756
|
+
const validation = { valid: true, warnings: [], errors: [] };
|
|
757
|
+
const collectionsRepo = db.getRepository("collections");
|
|
758
|
+
const fieldsRepo = db.getRepository("fields");
|
|
759
|
+
for (const col of collections) {
|
|
760
|
+
try {
|
|
761
|
+
const existing = await collectionsRepo.findOne({ filter: { name: col.name } });
|
|
762
|
+
if (existing) {
|
|
763
|
+
let newFieldsCount = 0;
|
|
764
|
+
let existingFieldsCount = 0;
|
|
765
|
+
for (const field of col.fields || []) {
|
|
766
|
+
const existingField = await fieldsRepo.findOne({ filter: { name: field.name, collectionName: col.name } });
|
|
767
|
+
if (existingField) existingFieldsCount++;
|
|
768
|
+
else newFieldsCount++;
|
|
769
|
+
}
|
|
770
|
+
validation.warnings.push({
|
|
771
|
+
type: "collection",
|
|
772
|
+
name: String(col.name),
|
|
773
|
+
message: `Collection exists. ${newFieldsCount} new field(s) will be added. ${existingFieldsCount} existing field(s) will be preserved.`
|
|
774
|
+
});
|
|
775
|
+
}
|
|
776
|
+
} catch {
|
|
777
|
+
}
|
|
778
|
+
}
|
|
779
|
+
if (workflows.length > 0) {
|
|
780
|
+
try {
|
|
781
|
+
const workflowRepo = db.getRepository("workflows");
|
|
782
|
+
for (const wf of workflows) {
|
|
783
|
+
if (!wf.key && !wf.title) continue;
|
|
784
|
+
let existing = null;
|
|
785
|
+
if (wf.key) {
|
|
786
|
+
existing = await workflowRepo.findOne({ filter: { key: wf.key } });
|
|
787
|
+
}
|
|
788
|
+
if (!existing && wf.title && wf.type) {
|
|
789
|
+
const matches = await workflowRepo.find({
|
|
790
|
+
filter: { title: wf.title, type: wf.type }
|
|
791
|
+
});
|
|
792
|
+
if (matches && matches.length > 0) existing = matches[0];
|
|
793
|
+
}
|
|
794
|
+
if (existing) {
|
|
795
|
+
validation.warnings.push({
|
|
796
|
+
type: "workflow",
|
|
797
|
+
name: String(wf.title || wf.key),
|
|
798
|
+
message: "Workflow already exists (will be updated if overwrite=true)."
|
|
799
|
+
});
|
|
800
|
+
}
|
|
801
|
+
}
|
|
802
|
+
} catch {
|
|
803
|
+
}
|
|
804
|
+
}
|
|
805
|
+
if (uiSchemas.length > 0) {
|
|
806
|
+
try {
|
|
807
|
+
const uiSchemaRepo = db.getRepository("uiSchemas");
|
|
808
|
+
for (const schema of uiSchemas) {
|
|
809
|
+
const uid = schema == null ? void 0 : schema.rootUid;
|
|
810
|
+
if (!uid) continue;
|
|
811
|
+
const existing = await uiSchemaRepo.findOne({ filter: { "x-uid": uid } });
|
|
812
|
+
if (existing) {
|
|
813
|
+
validation.warnings.push({
|
|
814
|
+
type: "uiSchema",
|
|
815
|
+
name: String(uid),
|
|
816
|
+
message: "UI Schema already exists (will be replaced if overwrite=true)."
|
|
817
|
+
});
|
|
818
|
+
}
|
|
819
|
+
}
|
|
820
|
+
} catch {
|
|
821
|
+
}
|
|
822
|
+
}
|
|
823
|
+
ctx.status = 200;
|
|
824
|
+
ctx.type = "application/json";
|
|
825
|
+
ctx.body = { success: true, validation };
|
|
826
|
+
} catch (err) {
|
|
827
|
+
ctx.status = 500;
|
|
828
|
+
ctx.type = "application/json";
|
|
829
|
+
ctx.body = { success: false, message: String(err.message || "Validation failed") };
|
|
830
|
+
}
|
|
831
|
+
}
|
|
832
|
+
static async apply(ctx) {
|
|
833
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l;
|
|
834
|
+
const actionParams = ((_a = ctx.action) == null ? void 0 : _a.params) || {};
|
|
835
|
+
const reqBody = ctx.request.body || {};
|
|
836
|
+
const importData = (actionParams.data && Object.keys(actionParams.data).length ? actionParams.data : void 0) || (reqBody.data && Object.keys(reqBody.data).length ? reqBody.data : void 0) || (Object.keys(reqBody).length ? reqBody : void 0) || {};
|
|
837
|
+
const db = ctx.db;
|
|
838
|
+
const app = ctx.app;
|
|
839
|
+
const overwrite = !!((_b = importData == null ? void 0 : importData.options) == null ? void 0 : _b.overwrite);
|
|
840
|
+
const uiSchemasInput = Array.isArray(importData == null ? void 0 : importData.uiSchemas) ? importData.uiSchemas : [];
|
|
841
|
+
const workflowsInput = Array.isArray(importData == null ? void 0 : importData.workflows) ? importData.workflows : [];
|
|
842
|
+
const collectionsInput = Array.isArray(importData == null ? void 0 : importData.collections) ? importData.collections : [];
|
|
843
|
+
const results = {
|
|
844
|
+
collections: { synced: 0, rebuilt: 0, skipped: 0, errors: [] },
|
|
845
|
+
workflows: { updated: 0, created: 0, nodesUpserted: 0, nodesDeleted: 0, errors: [] },
|
|
846
|
+
uiSchemas: { updated: 0, created: 0, errors: [] }
|
|
847
|
+
};
|
|
848
|
+
try {
|
|
849
|
+
let pickChildren = function(schema) {
|
|
850
|
+
const props = schema && schema.properties || {};
|
|
851
|
+
return Object.keys(props).map((k) => props[k]).filter(Boolean);
|
|
852
|
+
};
|
|
853
|
+
try {
|
|
854
|
+
if ((_c = app == null ? void 0 : app.collectionManager) == null ? void 0 : _c.reload) await app.collectionManager.reload();
|
|
855
|
+
} catch {
|
|
856
|
+
}
|
|
857
|
+
try {
|
|
858
|
+
const collectionsRepo = db.getRepository("collections");
|
|
859
|
+
const fieldsRepo = db.getRepository("fields");
|
|
860
|
+
for (const col of collectionsInput || []) {
|
|
861
|
+
const name = typeof col === "string" ? col : col == null ? void 0 : col.name;
|
|
862
|
+
if (!name) continue;
|
|
863
|
+
let runtimeCol = null;
|
|
864
|
+
try {
|
|
865
|
+
runtimeCol = db.getCollection(name);
|
|
866
|
+
} catch {
|
|
867
|
+
}
|
|
868
|
+
if (!runtimeCol) {
|
|
869
|
+
try {
|
|
870
|
+
if ((_d = app == null ? void 0 : app.collectionManager) == null ? void 0 : _d.reload) await app.collectionManager.reload();
|
|
871
|
+
} catch {
|
|
872
|
+
}
|
|
873
|
+
try {
|
|
874
|
+
runtimeCol = db.getCollection(name);
|
|
875
|
+
} catch {
|
|
876
|
+
}
|
|
877
|
+
}
|
|
878
|
+
if (!runtimeCol) {
|
|
879
|
+
const meta = await collectionsRepo.findOne({ filter: { name } }).catch(() => null);
|
|
880
|
+
if (!meta) {
|
|
881
|
+
results.collections.skipped++;
|
|
882
|
+
continue;
|
|
883
|
+
}
|
|
884
|
+
const title = meta.get ? meta.get("title") || name : meta.title || name;
|
|
885
|
+
const primaryKey = meta.get ? meta.get("primaryKey") || "id" : meta.primaryKey || "id";
|
|
886
|
+
const fieldRows = await fieldsRepo.find({ filter: { collectionName: name } }).catch(() => []);
|
|
887
|
+
const fields = (fieldRows || []).map((r) => {
|
|
888
|
+
const row = r.get ? r.get() : r;
|
|
889
|
+
const f = { name: row.name, type: row.type || "string", interface: row.interface || "input" };
|
|
890
|
+
for (const k of Object.keys(row)) {
|
|
891
|
+
if (["name", "type", "interface", "collectionName"].includes(k)) continue;
|
|
892
|
+
f[k] = row[k];
|
|
893
|
+
}
|
|
894
|
+
return f;
|
|
895
|
+
});
|
|
896
|
+
await db.import({ collections: [{ name, title, logging: true, autoGenId: true, createdBy: true, updatedBy: true, createdAt: true, updatedAt: true, sortable: true, primaryKey, fields }] });
|
|
897
|
+
try {
|
|
898
|
+
runtimeCol = db.getCollection(name);
|
|
899
|
+
} catch {
|
|
900
|
+
}
|
|
901
|
+
if (!runtimeCol) continue;
|
|
902
|
+
results.collections.rebuilt++;
|
|
903
|
+
}
|
|
904
|
+
await runtimeCol.sync({ force: false, alter: true });
|
|
905
|
+
results.collections.synced++;
|
|
906
|
+
}
|
|
907
|
+
} catch (e) {
|
|
908
|
+
results.collections.errors.push({ error: String((e == null ? void 0 : e.message) || e) });
|
|
909
|
+
}
|
|
910
|
+
if (workflowsInput.length) {
|
|
911
|
+
try {
|
|
912
|
+
const workflowRepo = db.getRepository("workflows");
|
|
913
|
+
const flowNodeRepo = db.getRepository("flow_nodes");
|
|
914
|
+
for (const wf of workflowsInput) {
|
|
915
|
+
const wfKey = (wf == null ? void 0 : wf.key) || (wf == null ? void 0 : wf.slug) || (wf == null ? void 0 : wf.id) || (wf == null ? void 0 : wf.name);
|
|
916
|
+
if (!wfKey) continue;
|
|
917
|
+
const existing = await workflowRepo.findOne({
|
|
918
|
+
filter: { key: wfKey },
|
|
919
|
+
appends: ["nodes"]
|
|
920
|
+
}).catch(() => null);
|
|
921
|
+
let workflowId;
|
|
922
|
+
if (existing) {
|
|
923
|
+
await workflowRepo.update({
|
|
924
|
+
filterByTk: existing.get("id"),
|
|
925
|
+
values: {
|
|
926
|
+
title: wf.title,
|
|
927
|
+
type: wf.type,
|
|
928
|
+
sync: !!wf.sync,
|
|
929
|
+
description: wf.description ?? null,
|
|
930
|
+
triggerTitle: wf.triggerTitle ?? null,
|
|
931
|
+
options: wf.options ?? {},
|
|
932
|
+
config: wf.config ?? {}
|
|
933
|
+
}
|
|
934
|
+
});
|
|
935
|
+
workflowId = existing.get("id");
|
|
936
|
+
} else {
|
|
937
|
+
const created = await workflowRepo.create({
|
|
938
|
+
values: {
|
|
939
|
+
title: wf.title,
|
|
940
|
+
type: wf.type,
|
|
941
|
+
key: wfKey,
|
|
942
|
+
sync: !!wf.sync,
|
|
943
|
+
description: wf.description ?? null,
|
|
944
|
+
triggerTitle: wf.triggerTitle ?? null,
|
|
945
|
+
options: wf.options ?? {},
|
|
946
|
+
config: wf.config ?? {}
|
|
947
|
+
}
|
|
948
|
+
});
|
|
949
|
+
workflowId = (created == null ? void 0 : created.get) ? created.get("id") : created.id;
|
|
950
|
+
results.workflows.created++;
|
|
951
|
+
}
|
|
952
|
+
const nodesInput = Array.isArray(wf.nodes) ? wf.nodes : [];
|
|
953
|
+
const keyToId = /* @__PURE__ */ new Map();
|
|
954
|
+
const existingByKey = /* @__PURE__ */ new Map();
|
|
955
|
+
try {
|
|
956
|
+
const existingNodes = await flowNodeRepo.find({ filter: { workflowId } });
|
|
957
|
+
for (const en of existingNodes || []) {
|
|
958
|
+
const row = en.get ? en.get() : en;
|
|
959
|
+
if (row.key) existingByKey.set(row.key, row);
|
|
960
|
+
}
|
|
961
|
+
} catch {
|
|
962
|
+
}
|
|
963
|
+
for (const n of nodesInput) {
|
|
964
|
+
if (!n || !n.key) continue;
|
|
965
|
+
const ex = existingByKey.get(n.key);
|
|
966
|
+
if (ex) {
|
|
967
|
+
await flowNodeRepo.update({
|
|
968
|
+
filterByTk: ex.id ?? ((_e = ex.get) == null ? void 0 : _e.call(ex, "id")),
|
|
969
|
+
values: {
|
|
970
|
+
type: n.type ?? ex.type ?? null,
|
|
971
|
+
title: n.title ?? ex.title ?? null,
|
|
972
|
+
config: n.config ?? ex.config ?? {},
|
|
973
|
+
branchIndex: n.branchIndex ?? null
|
|
974
|
+
}
|
|
975
|
+
});
|
|
976
|
+
keyToId.set(n.key, Number(ex.id ?? ((_f = ex.get) == null ? void 0 : _f.call(ex, "id"))));
|
|
977
|
+
} else {
|
|
978
|
+
const createdNode = await flowNodeRepo.create({
|
|
979
|
+
values: {
|
|
980
|
+
workflowId,
|
|
981
|
+
key: n.key,
|
|
982
|
+
type: n.type,
|
|
983
|
+
title: n.title ?? null,
|
|
984
|
+
config: n.config ?? {},
|
|
985
|
+
upstreamId: null,
|
|
986
|
+
downstreamId: null,
|
|
987
|
+
branchIndex: n.branchIndex ?? null
|
|
988
|
+
}
|
|
989
|
+
});
|
|
990
|
+
const id = (createdNode == null ? void 0 : createdNode.get) ? createdNode.get("id") : createdNode.id;
|
|
991
|
+
keyToId.set(n.key, Number(id));
|
|
992
|
+
}
|
|
993
|
+
results.workflows.nodesUpserted++;
|
|
994
|
+
}
|
|
995
|
+
if (overwrite) {
|
|
996
|
+
try {
|
|
997
|
+
const existingNodes = await flowNodeRepo.find({ filter: { workflowId } });
|
|
998
|
+
const inputKeys = new Set(nodesInput.filter((x) => x && x.key).map((x) => x.key));
|
|
999
|
+
for (const en of existingNodes || []) {
|
|
1000
|
+
const row = en.get ? en.get() : en;
|
|
1001
|
+
if (row.key && !inputKeys.has(row.key)) {
|
|
1002
|
+
await flowNodeRepo.destroy({ filterByTk: row.id });
|
|
1003
|
+
results.workflows.nodesDeleted++;
|
|
1004
|
+
}
|
|
1005
|
+
}
|
|
1006
|
+
} catch {
|
|
1007
|
+
}
|
|
1008
|
+
}
|
|
1009
|
+
for (const n of nodesInput) {
|
|
1010
|
+
if (!n || !n.key) continue;
|
|
1011
|
+
const id = keyToId.get(n.key);
|
|
1012
|
+
if (!id) continue;
|
|
1013
|
+
const upstreamId = n.upstreamKey ? keyToId.get(n.upstreamKey) ?? null : null;
|
|
1014
|
+
const downstreamId = n.downstreamKey ? keyToId.get(n.downstreamKey) ?? null : null;
|
|
1015
|
+
await flowNodeRepo.update({
|
|
1016
|
+
filterByTk: id,
|
|
1017
|
+
values: {
|
|
1018
|
+
upstreamId,
|
|
1019
|
+
downstreamId,
|
|
1020
|
+
branchIndex: n.branchIndex ?? null
|
|
1021
|
+
}
|
|
1022
|
+
});
|
|
1023
|
+
}
|
|
1024
|
+
results.workflows.updated++;
|
|
1025
|
+
}
|
|
1026
|
+
} catch (e) {
|
|
1027
|
+
results.workflows.errors.push({ error: String((e == null ? void 0 : e.message) || e) });
|
|
1028
|
+
}
|
|
1029
|
+
}
|
|
1030
|
+
const uiBundles = uiSchemasInput;
|
|
1031
|
+
const uiRepo = db.getRepository("ui_schemas");
|
|
1032
|
+
const treeRepo = db.getRepository("ui_schema_tree");
|
|
1033
|
+
async function insertAdjacent(targetUid, node) {
|
|
1034
|
+
const base = { ...node };
|
|
1035
|
+
delete base.properties;
|
|
1036
|
+
if (typeof uiRepo.insertAdjacent === "function") {
|
|
1037
|
+
const r = await uiRepo.insertAdjacent({ target: targetUid, position: node.position || "beforeEnd", schema: base, wrap: null });
|
|
1038
|
+
const ins = (r == null ? void 0 : r.get) ? r.get() : r;
|
|
1039
|
+
const inserted = (ins == null ? void 0 : ins.data) || ins;
|
|
1040
|
+
const uid = (inserted == null ? void 0 : inserted["x-uid"]) || (inserted == null ? void 0 : inserted.xUid) || (inserted == null ? void 0 : inserted.uid) || (base == null ? void 0 : base["x-uid"]);
|
|
1041
|
+
const children = pickChildren(node);
|
|
1042
|
+
for (const child of children) {
|
|
1043
|
+
await insertAdjacent(uid, child);
|
|
1044
|
+
}
|
|
1045
|
+
return uid;
|
|
1046
|
+
} else {
|
|
1047
|
+
const created = await uiRepo.create({ values: { "x-uid": (base == null ? void 0 : base["x-uid"]) || (base == null ? void 0 : base.uid) || (base == null ? void 0 : base.name), name: (base == null ? void 0 : base.name) || "", schema: base } });
|
|
1048
|
+
const uid = (created == null ? void 0 : created.get) ? created.get("x-uid") : (created == null ? void 0 : created["x-uid"]) || (created == null ? void 0 : created.uid) || (base == null ? void 0 : base["x-uid"]);
|
|
1049
|
+
const ex0 = await treeRepo.findOne({ filter: { ancestor: uid, descendant: uid, depth: 0 } });
|
|
1050
|
+
if (!ex0) {
|
|
1051
|
+
await treeRepo.create({ values: { ancestor: uid, descendant: uid, depth: 0, async: false, type: null, sort: null } });
|
|
1052
|
+
}
|
|
1053
|
+
const children = pickChildren(node);
|
|
1054
|
+
for (const child of children) {
|
|
1055
|
+
await insertAdjacent(uid, child);
|
|
1056
|
+
}
|
|
1057
|
+
return uid;
|
|
1058
|
+
}
|
|
1059
|
+
}
|
|
1060
|
+
async function removeDescendants(rootUid) {
|
|
1061
|
+
var _a2;
|
|
1062
|
+
const rows = await treeRepo.find({ filter: { ancestor: rootUid } }).catch(() => []);
|
|
1063
|
+
const list = (rows || []).map((r) => r.get ? r.get() : r).filter((x) => x.depth > 0);
|
|
1064
|
+
for (const row of list) {
|
|
1065
|
+
const uid = row.descendant;
|
|
1066
|
+
if ((_a2 = app == null ? void 0 : app.schemaManager) == null ? void 0 : _a2.removeSchema) {
|
|
1067
|
+
try {
|
|
1068
|
+
await app.schemaManager.removeSchema(uid);
|
|
1069
|
+
} catch {
|
|
1070
|
+
}
|
|
1071
|
+
} else {
|
|
1072
|
+
try {
|
|
1073
|
+
await uiRepo.destroy({ filter: { "x-uid": uid }, force: true });
|
|
1074
|
+
} catch {
|
|
1075
|
+
}
|
|
1076
|
+
try {
|
|
1077
|
+
await treeRepo.destroy({ filter: { ancestor: uid }, force: true });
|
|
1078
|
+
} catch {
|
|
1079
|
+
}
|
|
1080
|
+
try {
|
|
1081
|
+
await treeRepo.destroy({ filter: { descendant: uid }, force: true });
|
|
1082
|
+
} catch {
|
|
1083
|
+
}
|
|
1084
|
+
}
|
|
1085
|
+
}
|
|
1086
|
+
}
|
|
1087
|
+
for (const bundle of uiBundles) {
|
|
1088
|
+
const incoming = bundle == null ? void 0 : bundle.data;
|
|
1089
|
+
if (!incoming) continue;
|
|
1090
|
+
const name = incoming == null ? void 0 : incoming.name;
|
|
1091
|
+
const type = incoming == null ? void 0 : incoming.type;
|
|
1092
|
+
if (!name || !type) continue;
|
|
1093
|
+
let target = await uiRepo.findOne({ filter: { name, type } }).catch(() => null);
|
|
1094
|
+
if (!target) {
|
|
1095
|
+
const rootUid2 = (incoming == null ? void 0 : incoming["x-uid"]) || (incoming == null ? void 0 : incoming.uid) || (incoming == null ? void 0 : incoming.schemaUid) || (incoming == null ? void 0 : incoming.xUid) || (incoming == null ? void 0 : incoming.name);
|
|
1096
|
+
const base2 = { ...incoming };
|
|
1097
|
+
delete base2.properties;
|
|
1098
|
+
if (typeof uiRepo.insert === "function") {
|
|
1099
|
+
const r = await uiRepo.insert(base2);
|
|
1100
|
+
const ins = (r == null ? void 0 : r.get) ? r.get() : r;
|
|
1101
|
+
const inserted = (ins == null ? void 0 : ins.data) || ins;
|
|
1102
|
+
const uid = (inserted == null ? void 0 : inserted["x-uid"]) || (inserted == null ? void 0 : inserted.xUid) || (inserted == null ? void 0 : inserted.uid) || (base2 == null ? void 0 : base2["x-uid"]) || rootUid2;
|
|
1103
|
+
const children2 = pickChildren(incoming);
|
|
1104
|
+
for (const child of children2) {
|
|
1105
|
+
await insertAdjacent(uid, child);
|
|
1106
|
+
}
|
|
1107
|
+
} else {
|
|
1108
|
+
const created = await uiRepo.create({ values: { "x-uid": rootUid2, name: (base2 == null ? void 0 : base2.name) || "", schema: base2 } });
|
|
1109
|
+
const uid = (created == null ? void 0 : created.get) ? created.get("x-uid") : (created == null ? void 0 : created["x-uid"]) || (created == null ? void 0 : created.uid) || rootUid2;
|
|
1110
|
+
const ex0 = await treeRepo.findOne({ filter: { ancestor: uid, descendant: uid, depth: 0 } });
|
|
1111
|
+
if (!ex0) {
|
|
1112
|
+
await treeRepo.create({ values: { ancestor: uid, descendant: uid, depth: 0, async: false, type: null, sort: null } });
|
|
1113
|
+
}
|
|
1114
|
+
const children2 = pickChildren(incoming);
|
|
1115
|
+
for (const child of children2) {
|
|
1116
|
+
await insertAdjacent(uid, child);
|
|
1117
|
+
}
|
|
1118
|
+
}
|
|
1119
|
+
results.uiSchemas.created++;
|
|
1120
|
+
continue;
|
|
1121
|
+
}
|
|
1122
|
+
const rootUid = target.get ? target.get("x-uid") : target["x-uid"] || target.uid;
|
|
1123
|
+
const base = { ...incoming };
|
|
1124
|
+
delete base.properties;
|
|
1125
|
+
await uiRepo.update({ filterByTk: target.get ? target.get("id") : target.id, values: { schema: base, name } });
|
|
1126
|
+
await removeDescendants(rootUid);
|
|
1127
|
+
const children = pickChildren(incoming);
|
|
1128
|
+
for (const child of children) {
|
|
1129
|
+
await insertAdjacent(rootUid, child);
|
|
1130
|
+
}
|
|
1131
|
+
results.uiSchemas.merged++;
|
|
1132
|
+
}
|
|
1133
|
+
try {
|
|
1134
|
+
if ((_g = app == null ? void 0 : app.collectionManager) == null ? void 0 : _g.reload) await app.collectionManager.reload();
|
|
1135
|
+
} catch {
|
|
1136
|
+
}
|
|
1137
|
+
try {
|
|
1138
|
+
if ((_h = app == null ? void 0 : app.schemaManager) == null ? void 0 : _h.reload) await app.schemaManager.reload();
|
|
1139
|
+
} catch {
|
|
1140
|
+
}
|
|
1141
|
+
try {
|
|
1142
|
+
const wf = ((_j = (_i = app == null ? void 0 : app.pm) == null ? void 0 : _i.get) == null ? void 0 : _j.call(_i, "workflow")) || (app == null ? void 0 : app.workflow) || ((_k = app == null ? void 0 : app.plugins) == null ? void 0 : _k.workflow);
|
|
1143
|
+
if ((_l = wf == null ? void 0 : wf.engine) == null ? void 0 : _l.reload) await wf.engine.reload();
|
|
1144
|
+
if (wf == null ? void 0 : wf.reload) await wf.reload();
|
|
1145
|
+
} catch {
|
|
1146
|
+
}
|
|
1147
|
+
ctx.set && ctx.set("X-Noco-Refresh", "schema");
|
|
1148
|
+
ctx.status = 200;
|
|
1149
|
+
ctx.type = "application/json";
|
|
1150
|
+
ctx.body = { success: true, results, message: "Apply completed.", refresh: { schema: true, collections: true, workflows: true } };
|
|
1151
|
+
} catch (err) {
|
|
1152
|
+
ctx.status = 500;
|
|
1153
|
+
ctx.type = "application/json";
|
|
1154
|
+
ctx.body = { success: false, message: String((err == null ? void 0 : err.message) || "Apply failed") };
|
|
1155
|
+
}
|
|
1156
|
+
}
|
|
1157
|
+
}
|
|
1158
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
1159
|
+
0 && (module.exports = {
|
|
1160
|
+
MigrationController
|
|
1161
|
+
});
|