@magnet-cms/plugin-playground 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/dist/backend/index.cjs +1023 -0
- package/dist/backend/index.d.cts +10 -0
- package/dist/backend/index.d.ts +10 -0
- package/dist/backend/index.js +1 -0
- package/dist/chunk-WY4YMBWZ.js +1044 -0
- package/dist/frontend/bundle.iife.js +2163 -0
- package/dist/frontend/bundle.iife.js.map +1 -0
- package/dist/index.cjs +1135 -0
- package/dist/index.d.cts +36 -0
- package/dist/index.d.ts +36 -0
- package/dist/index.js +76 -0
- package/package.json +81 -0
- package/src/frontend/index.ts +110 -0
- package/src/frontend/pages/Playground/Editor/AddFieldDialog.tsx +187 -0
- package/src/frontend/pages/Playground/Editor/CodePreview.tsx +59 -0
- package/src/frontend/pages/Playground/Editor/FieldCard.tsx +161 -0
- package/src/frontend/pages/Playground/Editor/FieldList.tsx +121 -0
- package/src/frontend/pages/Playground/Editor/FieldSettingsPanel.tsx +652 -0
- package/src/frontend/pages/Playground/Editor/RelationConfigModal.tsx +292 -0
- package/src/frontend/pages/Playground/Editor/SchemaList.tsx +76 -0
- package/src/frontend/pages/Playground/Editor/SchemaOptionsDialog.tsx +109 -0
- package/src/frontend/pages/Playground/Editor/index.tsx +322 -0
- package/src/frontend/pages/Playground/constants/field-types.ts +384 -0
- package/src/frontend/pages/Playground/hooks/useSchemaBuilder.ts +280 -0
- package/src/frontend/pages/Playground/index.tsx +19 -0
- package/src/frontend/pages/Playground/types/builder.types.ts +191 -0
- package/src/frontend/pages/Playground/utils/code-generator.ts +319 -0
|
@@ -0,0 +1,1023 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var common = require('@nestjs/common');
|
|
4
|
+
var core = require('@magnet-cms/core');
|
|
5
|
+
var fs = require('fs');
|
|
6
|
+
var path = require('path');
|
|
7
|
+
var common$1 = require('@magnet-cms/common');
|
|
8
|
+
|
|
9
|
+
function _interopNamespace(e) {
|
|
10
|
+
if (e && e.__esModule) return e;
|
|
11
|
+
var n = Object.create(null);
|
|
12
|
+
if (e) {
|
|
13
|
+
Object.keys(e).forEach(function (k) {
|
|
14
|
+
if (k !== 'default') {
|
|
15
|
+
var d = Object.getOwnPropertyDescriptor(e, k);
|
|
16
|
+
Object.defineProperty(n, k, d.get ? d : {
|
|
17
|
+
enumerable: true,
|
|
18
|
+
get: function () { return e[k]; }
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
n.default = e;
|
|
24
|
+
return Object.freeze(n);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
var fs__namespace = /*#__PURE__*/_interopNamespace(fs);
|
|
28
|
+
var path__namespace = /*#__PURE__*/_interopNamespace(path);
|
|
29
|
+
|
|
30
|
+
var __defProp = Object.defineProperty;
|
|
31
|
+
var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
|
|
32
|
+
function _ts_decorate(decorators, target, key, desc) {
|
|
33
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
34
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
35
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
36
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
37
|
+
}
|
|
38
|
+
__name(_ts_decorate, "_ts_decorate");
|
|
39
|
+
function _ts_metadata(k, v) {
|
|
40
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
41
|
+
}
|
|
42
|
+
__name(_ts_metadata, "_ts_metadata");
|
|
43
|
+
function _ts_param(paramIndex, decorator) {
|
|
44
|
+
return function(target, key) {
|
|
45
|
+
decorator(target, key, paramIndex);
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
__name(_ts_param, "_ts_param");
|
|
49
|
+
var PlaygroundService = class _PlaygroundService {
|
|
50
|
+
static {
|
|
51
|
+
__name(this, "PlaygroundService");
|
|
52
|
+
}
|
|
53
|
+
magnetOptions;
|
|
54
|
+
pluginOptions;
|
|
55
|
+
logger;
|
|
56
|
+
constructor(magnetOptions, pluginOptions) {
|
|
57
|
+
this.magnetOptions = magnetOptions;
|
|
58
|
+
this.pluginOptions = pluginOptions;
|
|
59
|
+
this.logger = new common.Logger(_PlaygroundService.name);
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Get the modules directory path from options or use default.
|
|
63
|
+
* When MAGNET_PLAYGROUND_MODULES_PATH is set (e.g. for e2e), use it so
|
|
64
|
+
* generated modules are not written into the app's src/modules.
|
|
65
|
+
*/
|
|
66
|
+
getModulesDir() {
|
|
67
|
+
const envPath = process.env.MAGNET_PLAYGROUND_MODULES_PATH;
|
|
68
|
+
if (envPath?.trim()) {
|
|
69
|
+
return path__namespace.resolve(envPath.trim());
|
|
70
|
+
}
|
|
71
|
+
if (this.pluginOptions?.modulesPath) {
|
|
72
|
+
return this.pluginOptions.modulesPath;
|
|
73
|
+
}
|
|
74
|
+
const opts = this.magnetOptions;
|
|
75
|
+
return opts?.playground?.modulesPath || opts?.playground?.schemasPath || path__namespace.join(process.cwd(), "src", "modules");
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* List all schemas by scanning module directories
|
|
79
|
+
*/
|
|
80
|
+
async listSchemas() {
|
|
81
|
+
const modulesDir = this.getModulesDir();
|
|
82
|
+
if (!fs__namespace.existsSync(modulesDir)) {
|
|
83
|
+
return [];
|
|
84
|
+
}
|
|
85
|
+
const schemas = [];
|
|
86
|
+
const entries = fs__namespace.readdirSync(modulesDir, {
|
|
87
|
+
withFileTypes: true
|
|
88
|
+
});
|
|
89
|
+
for (const entry of entries) {
|
|
90
|
+
if (!entry.isDirectory()) continue;
|
|
91
|
+
const schemaFile = path__namespace.join(modulesDir, entry.name, `${entry.name}.schema.ts`);
|
|
92
|
+
const schemaFileAlt = path__namespace.join(modulesDir, entry.name, "schemas", `${entry.name}.schema.ts`);
|
|
93
|
+
const filePath = fs__namespace.existsSync(schemaFile) ? schemaFile : fs__namespace.existsSync(schemaFileAlt) ? schemaFileAlt : null;
|
|
94
|
+
if (!filePath) continue;
|
|
95
|
+
const content = fs__namespace.readFileSync(filePath, "utf-8");
|
|
96
|
+
const parsed = this.parseSchemaFile(content);
|
|
97
|
+
if (parsed) {
|
|
98
|
+
const stats = fs__namespace.statSync(filePath);
|
|
99
|
+
schemas.push({
|
|
100
|
+
name: parsed.name,
|
|
101
|
+
apiId: parsed.name.toLowerCase(),
|
|
102
|
+
fieldCount: parsed.fields.length,
|
|
103
|
+
hasVersioning: parsed.options?.versioning ?? true,
|
|
104
|
+
hasI18n: parsed.options?.i18n ?? true,
|
|
105
|
+
createdAt: stats.birthtime,
|
|
106
|
+
updatedAt: stats.mtime
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
return schemas;
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Get a schema by name
|
|
114
|
+
*/
|
|
115
|
+
async getSchema(name) {
|
|
116
|
+
const modulesDir = this.getModulesDir();
|
|
117
|
+
const lowerName = name.toLowerCase();
|
|
118
|
+
const schemaFile = path__namespace.join(modulesDir, lowerName, `${lowerName}.schema.ts`);
|
|
119
|
+
const schemaFileAlt = path__namespace.join(modulesDir, lowerName, "schemas", `${lowerName}.schema.ts`);
|
|
120
|
+
const filePath = fs__namespace.existsSync(schemaFile) ? schemaFile : fs__namespace.existsSync(schemaFileAlt) ? schemaFileAlt : null;
|
|
121
|
+
if (!filePath) {
|
|
122
|
+
return null;
|
|
123
|
+
}
|
|
124
|
+
const content = fs__namespace.readFileSync(filePath, "utf-8");
|
|
125
|
+
const parsed = this.parseSchemaFile(content);
|
|
126
|
+
if (!parsed) {
|
|
127
|
+
return null;
|
|
128
|
+
}
|
|
129
|
+
return {
|
|
130
|
+
name: parsed.name,
|
|
131
|
+
apiId: parsed.name.toLowerCase(),
|
|
132
|
+
options: parsed.options || {
|
|
133
|
+
versioning: true,
|
|
134
|
+
i18n: true
|
|
135
|
+
},
|
|
136
|
+
fields: parsed.fields,
|
|
137
|
+
generatedCode: content
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* Check if a schema/module already exists
|
|
142
|
+
*/
|
|
143
|
+
schemaExists(name) {
|
|
144
|
+
const modulesDir = this.getModulesDir();
|
|
145
|
+
const lowerName = name.toLowerCase();
|
|
146
|
+
const moduleDir = path__namespace.join(modulesDir, lowerName);
|
|
147
|
+
return fs__namespace.existsSync(moduleDir);
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
* Create a new module with all files (schema, controller, service, module, dto)
|
|
151
|
+
*/
|
|
152
|
+
async createModule(dto) {
|
|
153
|
+
const modulesDir = this.getModulesDir();
|
|
154
|
+
const lowerName = dto.name.toLowerCase();
|
|
155
|
+
const moduleDir = path__namespace.join(modulesDir, lowerName);
|
|
156
|
+
const dtoDir = path__namespace.join(moduleDir, "dto");
|
|
157
|
+
fs__namespace.mkdirSync(dtoDir, {
|
|
158
|
+
recursive: true
|
|
159
|
+
});
|
|
160
|
+
const createdFiles = [];
|
|
161
|
+
const schemaCode = this.generateSchemaCode(dto);
|
|
162
|
+
const files = [
|
|
163
|
+
{
|
|
164
|
+
name: `${lowerName}.schema.ts`,
|
|
165
|
+
content: schemaCode
|
|
166
|
+
},
|
|
167
|
+
{
|
|
168
|
+
name: `${lowerName}.module.ts`,
|
|
169
|
+
content: this.generateModuleCode(dto.name)
|
|
170
|
+
},
|
|
171
|
+
{
|
|
172
|
+
name: `${lowerName}.controller.ts`,
|
|
173
|
+
content: this.generateControllerCode(dto.name)
|
|
174
|
+
},
|
|
175
|
+
{
|
|
176
|
+
name: `${lowerName}.service.ts`,
|
|
177
|
+
content: this.generateServiceCode(dto.name)
|
|
178
|
+
},
|
|
179
|
+
{
|
|
180
|
+
name: `dto/create-${lowerName}.dto.ts`,
|
|
181
|
+
content: this.generateDtoCode(dto.name, dto.fields)
|
|
182
|
+
}
|
|
183
|
+
];
|
|
184
|
+
for (const file of files) {
|
|
185
|
+
const filePath = path__namespace.join(moduleDir, file.name);
|
|
186
|
+
fs__namespace.writeFileSync(filePath, file.content, "utf-8");
|
|
187
|
+
createdFiles.push(filePath);
|
|
188
|
+
this.logger.log(`Created: ${filePath}`);
|
|
189
|
+
}
|
|
190
|
+
return {
|
|
191
|
+
name: dto.name,
|
|
192
|
+
apiId: lowerName,
|
|
193
|
+
options: dto.options || {
|
|
194
|
+
versioning: true,
|
|
195
|
+
i18n: true
|
|
196
|
+
},
|
|
197
|
+
fields: dto.fields,
|
|
198
|
+
generatedCode: schemaCode,
|
|
199
|
+
createdFiles,
|
|
200
|
+
message: `Module "${dto.name}" created successfully. Import ${dto.name}Module in your app.module.ts to use it.`
|
|
201
|
+
};
|
|
202
|
+
}
|
|
203
|
+
/**
|
|
204
|
+
* Update only the schema file for an existing module
|
|
205
|
+
*/
|
|
206
|
+
async updateSchema(name, dto) {
|
|
207
|
+
const modulesDir = this.getModulesDir();
|
|
208
|
+
const lowerName = name.toLowerCase();
|
|
209
|
+
const schemaFile = path__namespace.join(modulesDir, lowerName, `${lowerName}.schema.ts`);
|
|
210
|
+
const schemaFileAlt = path__namespace.join(modulesDir, lowerName, "schemas", `${lowerName}.schema.ts`);
|
|
211
|
+
const filePath = fs__namespace.existsSync(schemaFile) ? schemaFile : fs__namespace.existsSync(schemaFileAlt) ? schemaFileAlt : null;
|
|
212
|
+
if (!filePath) {
|
|
213
|
+
throw new Error(`Schema "${name}" not found`);
|
|
214
|
+
}
|
|
215
|
+
const existingContent = fs__namespace.readFileSync(filePath, "utf-8");
|
|
216
|
+
const existingParsed = this.parseSchemaFile(existingContent);
|
|
217
|
+
const conflicts = existingParsed ? this.detectConflicts(existingParsed.fields, dto.fields) : [];
|
|
218
|
+
const newCode = this.generateSchemaCode(dto);
|
|
219
|
+
fs__namespace.writeFileSync(filePath, newCode, "utf-8");
|
|
220
|
+
this.logger.log(`Schema updated: ${filePath}`);
|
|
221
|
+
return {
|
|
222
|
+
detail: {
|
|
223
|
+
name: dto.name,
|
|
224
|
+
apiId: dto.name.toLowerCase(),
|
|
225
|
+
options: dto.options || {
|
|
226
|
+
versioning: true,
|
|
227
|
+
i18n: true
|
|
228
|
+
},
|
|
229
|
+
fields: dto.fields,
|
|
230
|
+
generatedCode: newCode
|
|
231
|
+
},
|
|
232
|
+
conflicts
|
|
233
|
+
};
|
|
234
|
+
}
|
|
235
|
+
/**
|
|
236
|
+
* Detect conflicts between existing and updated schema fields
|
|
237
|
+
*/
|
|
238
|
+
detectConflicts(existing, updated) {
|
|
239
|
+
const conflicts = [];
|
|
240
|
+
for (const updatedField of updated) {
|
|
241
|
+
const existingField = existing.find((f) => f.name === updatedField.name);
|
|
242
|
+
if (existingField) {
|
|
243
|
+
if (existingField.tsType !== updatedField.tsType) {
|
|
244
|
+
conflicts.push({
|
|
245
|
+
fieldName: updatedField.name,
|
|
246
|
+
type: "type_change",
|
|
247
|
+
message: `Type changed from "${existingField.tsType}" to "${updatedField.tsType}". Update your DTO accordingly.`,
|
|
248
|
+
oldValue: existingField.tsType,
|
|
249
|
+
newValue: updatedField.tsType
|
|
250
|
+
});
|
|
251
|
+
}
|
|
252
|
+
if (existingField.prop.required && !updatedField.prop.required) {
|
|
253
|
+
conflicts.push({
|
|
254
|
+
fieldName: updatedField.name,
|
|
255
|
+
type: "required_change",
|
|
256
|
+
message: "Field changed from required to optional. Consider updating your DTO.",
|
|
257
|
+
oldValue: "required",
|
|
258
|
+
newValue: "optional"
|
|
259
|
+
});
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
for (const existingField of existing) {
|
|
264
|
+
const stillExists = updated.find((f) => f.name === existingField.name);
|
|
265
|
+
if (!stillExists) {
|
|
266
|
+
conflicts.push({
|
|
267
|
+
fieldName: existingField.name,
|
|
268
|
+
type: "field_removed",
|
|
269
|
+
message: `Field "${existingField.name}" was removed. Update your DTO and service accordingly.`,
|
|
270
|
+
oldValue: existingField.name,
|
|
271
|
+
newValue: void 0
|
|
272
|
+
});
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
return conflicts;
|
|
276
|
+
}
|
|
277
|
+
/**
|
|
278
|
+
* Delete a schema and its entire module directory (controller, service, dto, etc.)
|
|
279
|
+
*/
|
|
280
|
+
async deleteSchema(name) {
|
|
281
|
+
const modulesDir = this.getModulesDir();
|
|
282
|
+
const lowerName = name.toLowerCase();
|
|
283
|
+
const moduleDir = path__namespace.join(modulesDir, lowerName);
|
|
284
|
+
const schemaFile = path__namespace.join(moduleDir, `${lowerName}.schema.ts`);
|
|
285
|
+
const schemaFileAlt = path__namespace.join(moduleDir, "schemas", `${lowerName}.schema.ts`);
|
|
286
|
+
const filePath = fs__namespace.existsSync(schemaFile) ? schemaFile : fs__namespace.existsSync(schemaFileAlt) ? schemaFileAlt : null;
|
|
287
|
+
if (!filePath) {
|
|
288
|
+
return false;
|
|
289
|
+
}
|
|
290
|
+
if (fs__namespace.existsSync(moduleDir)) {
|
|
291
|
+
fs__namespace.rmSync(moduleDir, {
|
|
292
|
+
recursive: true
|
|
293
|
+
});
|
|
294
|
+
this.logger.log(`Module deleted: ${moduleDir}`);
|
|
295
|
+
}
|
|
296
|
+
return true;
|
|
297
|
+
}
|
|
298
|
+
/**
|
|
299
|
+
* Generate code preview without saving
|
|
300
|
+
*/
|
|
301
|
+
previewCode(dto) {
|
|
302
|
+
const code = this.generateSchemaCode(dto);
|
|
303
|
+
const json = this.generateSchemaJSON(dto);
|
|
304
|
+
return {
|
|
305
|
+
code,
|
|
306
|
+
json
|
|
307
|
+
};
|
|
308
|
+
}
|
|
309
|
+
// ============================================================================
|
|
310
|
+
// Code Generation Methods
|
|
311
|
+
// ============================================================================
|
|
312
|
+
/**
|
|
313
|
+
* Generate module.ts code
|
|
314
|
+
*/
|
|
315
|
+
generateModuleCode(name) {
|
|
316
|
+
const lowerName = name.toLowerCase();
|
|
317
|
+
return `import { MagnetModule } from '@magnet-cms/core'
|
|
318
|
+
import { Module } from '@nestjs/common'
|
|
319
|
+
import { ${name}Controller } from './${lowerName}.controller'
|
|
320
|
+
import { ${name}Service } from './${lowerName}.service'
|
|
321
|
+
import { ${name} } from './${lowerName}.schema'
|
|
322
|
+
|
|
323
|
+
@Module({
|
|
324
|
+
imports: [MagnetModule.forFeature(${name})],
|
|
325
|
+
controllers: [${name}Controller],
|
|
326
|
+
providers: [${name}Service],
|
|
327
|
+
})
|
|
328
|
+
export class ${name}Module {}
|
|
329
|
+
`;
|
|
330
|
+
}
|
|
331
|
+
/**
|
|
332
|
+
* Generate controller.ts code
|
|
333
|
+
*/
|
|
334
|
+
generateControllerCode(name) {
|
|
335
|
+
const lowerName = name.toLowerCase();
|
|
336
|
+
return `import { Resolve } from '@magnet-cms/common'
|
|
337
|
+
import {
|
|
338
|
+
Body,
|
|
339
|
+
Controller,
|
|
340
|
+
Delete,
|
|
341
|
+
Get,
|
|
342
|
+
Param,
|
|
343
|
+
Post,
|
|
344
|
+
Put,
|
|
345
|
+
} from '@nestjs/common'
|
|
346
|
+
import { Create${name}Dto } from './dto/create-${lowerName}.dto'
|
|
347
|
+
import { ${name}Service } from './${lowerName}.service'
|
|
348
|
+
import { ${name} } from './${lowerName}.schema'
|
|
349
|
+
|
|
350
|
+
@Controller('${lowerName}')
|
|
351
|
+
export class ${name}Controller {
|
|
352
|
+
constructor(private readonly ${lowerName}Service: ${name}Service) {}
|
|
353
|
+
|
|
354
|
+
@Post()
|
|
355
|
+
@Resolve(() => ${name})
|
|
356
|
+
create(@Body() dto: Create${name}Dto) {
|
|
357
|
+
return this.${lowerName}Service.create(dto)
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
@Get()
|
|
361
|
+
@Resolve(() => [${name}])
|
|
362
|
+
findAll() {
|
|
363
|
+
return this.${lowerName}Service.findAll()
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
@Get(':id')
|
|
367
|
+
@Resolve(() => ${name})
|
|
368
|
+
findOne(@Param('id') id: string) {
|
|
369
|
+
return this.${lowerName}Service.findOne(id)
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
@Put(':id')
|
|
373
|
+
@Resolve(() => Boolean)
|
|
374
|
+
update(@Param('id') id: string, @Body() dto: Create${name}Dto) {
|
|
375
|
+
return this.${lowerName}Service.update(id, dto)
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
@Delete(':id')
|
|
379
|
+
@Resolve(() => Boolean)
|
|
380
|
+
remove(@Param('id') id: string) {
|
|
381
|
+
return this.${lowerName}Service.remove(id)
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
`;
|
|
385
|
+
}
|
|
386
|
+
/**
|
|
387
|
+
* Generate service.ts code
|
|
388
|
+
*/
|
|
389
|
+
generateServiceCode(name) {
|
|
390
|
+
const lowerName = name.toLowerCase();
|
|
391
|
+
return `import { InjectModel, Model } from '@magnet-cms/common'
|
|
392
|
+
import { Injectable } from '@nestjs/common'
|
|
393
|
+
import { Create${name}Dto } from './dto/create-${lowerName}.dto'
|
|
394
|
+
import { ${name} } from './${lowerName}.schema'
|
|
395
|
+
|
|
396
|
+
@Injectable()
|
|
397
|
+
export class ${name}Service {
|
|
398
|
+
constructor(
|
|
399
|
+
@InjectModel(${name})
|
|
400
|
+
private model: Model<${name}>,
|
|
401
|
+
) {}
|
|
402
|
+
|
|
403
|
+
create(dto: Create${name}Dto) {
|
|
404
|
+
return this.model.create(dto)
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
findAll() {
|
|
408
|
+
return this.model.find()
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
findOne(id: string) {
|
|
412
|
+
return this.model.findById(id)
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
update(id: string, dto: Create${name}Dto) {
|
|
416
|
+
return this.model.update(id, dto)
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
remove(id: string) {
|
|
420
|
+
return this.model.delete(id)
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
`;
|
|
424
|
+
}
|
|
425
|
+
/**
|
|
426
|
+
* Generate DTO code
|
|
427
|
+
*/
|
|
428
|
+
generateDtoCode(name, fields) {
|
|
429
|
+
const validatorImports = /* @__PURE__ */ new Set();
|
|
430
|
+
for (const field of fields) {
|
|
431
|
+
switch (field.tsType) {
|
|
432
|
+
case "string":
|
|
433
|
+
validatorImports.add("IsString");
|
|
434
|
+
break;
|
|
435
|
+
case "number":
|
|
436
|
+
validatorImports.add("IsNumber");
|
|
437
|
+
break;
|
|
438
|
+
case "boolean":
|
|
439
|
+
validatorImports.add("IsBoolean");
|
|
440
|
+
break;
|
|
441
|
+
case "Date":
|
|
442
|
+
validatorImports.add("IsDate");
|
|
443
|
+
break;
|
|
444
|
+
}
|
|
445
|
+
if (field.prop.required) {
|
|
446
|
+
validatorImports.add("IsNotEmpty");
|
|
447
|
+
} else {
|
|
448
|
+
validatorImports.add("IsOptional");
|
|
449
|
+
}
|
|
450
|
+
for (const v of field.validations) {
|
|
451
|
+
validatorImports.add(v.type);
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
const imports = Array.from(validatorImports).sort().join(",\n ");
|
|
455
|
+
const properties = fields.map((f) => this.generateDtoProperty(f)).join("\n\n");
|
|
456
|
+
return `import {
|
|
457
|
+
${imports},
|
|
458
|
+
} from 'class-validator'
|
|
459
|
+
|
|
460
|
+
export class Create${name}Dto {
|
|
461
|
+
${properties}
|
|
462
|
+
}
|
|
463
|
+
`;
|
|
464
|
+
}
|
|
465
|
+
/**
|
|
466
|
+
* Generate a single DTO property with decorators
|
|
467
|
+
*/
|
|
468
|
+
generateDtoProperty(field) {
|
|
469
|
+
const decorators = [];
|
|
470
|
+
const indent = " ";
|
|
471
|
+
switch (field.tsType) {
|
|
472
|
+
case "string":
|
|
473
|
+
decorators.push(`${indent}@IsString()`);
|
|
474
|
+
break;
|
|
475
|
+
case "number":
|
|
476
|
+
decorators.push(`${indent}@IsNumber()`);
|
|
477
|
+
break;
|
|
478
|
+
case "boolean":
|
|
479
|
+
decorators.push(`${indent}@IsBoolean()`);
|
|
480
|
+
break;
|
|
481
|
+
case "Date":
|
|
482
|
+
decorators.push(`${indent}@IsDate()`);
|
|
483
|
+
break;
|
|
484
|
+
}
|
|
485
|
+
if (field.prop.required) {
|
|
486
|
+
decorators.push(`${indent}@IsNotEmpty()`);
|
|
487
|
+
} else {
|
|
488
|
+
decorators.push(`${indent}@IsOptional()`);
|
|
489
|
+
}
|
|
490
|
+
for (const v of field.validations) {
|
|
491
|
+
if ([
|
|
492
|
+
"IsString",
|
|
493
|
+
"IsNumber",
|
|
494
|
+
"IsBoolean",
|
|
495
|
+
"IsDate",
|
|
496
|
+
"IsNotEmpty"
|
|
497
|
+
].includes(v.type)) {
|
|
498
|
+
continue;
|
|
499
|
+
}
|
|
500
|
+
decorators.push(`${indent}@${this.formatValidator(v)}`);
|
|
501
|
+
}
|
|
502
|
+
const optional = field.prop.required ? "" : "?";
|
|
503
|
+
const declaration = `${indent}${field.name}${optional}: ${field.tsType}`;
|
|
504
|
+
return [
|
|
505
|
+
...decorators,
|
|
506
|
+
declaration
|
|
507
|
+
].join("\n");
|
|
508
|
+
}
|
|
509
|
+
/**
|
|
510
|
+
* Generate TypeScript schema code from DTO
|
|
511
|
+
*/
|
|
512
|
+
generateSchemaCode(dto) {
|
|
513
|
+
if (!dto.name) {
|
|
514
|
+
return "// Enter a schema name to generate code";
|
|
515
|
+
}
|
|
516
|
+
const imports = this.generateImports(dto.fields);
|
|
517
|
+
const classDecorator = this.generateSchemaDecorator(dto.options);
|
|
518
|
+
const properties = dto.fields.map((field) => this.generateFieldCode(field)).join("\n\n");
|
|
519
|
+
return `${imports}
|
|
520
|
+
|
|
521
|
+
${classDecorator}
|
|
522
|
+
export class ${dto.name} {
|
|
523
|
+
${properties}
|
|
524
|
+
}
|
|
525
|
+
`;
|
|
526
|
+
}
|
|
527
|
+
/**
|
|
528
|
+
* Generate import statements based on used features
|
|
529
|
+
*/
|
|
530
|
+
generateImports(fields) {
|
|
531
|
+
const magnetImports = /* @__PURE__ */ new Set([
|
|
532
|
+
"Schema",
|
|
533
|
+
"Prop",
|
|
534
|
+
"UI"
|
|
535
|
+
]);
|
|
536
|
+
const validatorImports = /* @__PURE__ */ new Set();
|
|
537
|
+
let needsTypeTransformer = false;
|
|
538
|
+
for (const field of fields) {
|
|
539
|
+
if (field.validations.length > 0) {
|
|
540
|
+
magnetImports.add("Validators");
|
|
541
|
+
for (const v of field.validations) {
|
|
542
|
+
validatorImports.add(v.type);
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
if (field.type === "date") {
|
|
546
|
+
needsTypeTransformer = true;
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
const lines = [];
|
|
550
|
+
lines.push(`import { ${Array.from(magnetImports).sort().join(", ")} } from '@magnet-cms/common'`);
|
|
551
|
+
if (needsTypeTransformer) {
|
|
552
|
+
lines.push(`import { Type } from 'class-transformer'`);
|
|
553
|
+
}
|
|
554
|
+
if (validatorImports.size > 0) {
|
|
555
|
+
lines.push(`import {
|
|
556
|
+
${Array.from(validatorImports).sort().join(",\n ")},
|
|
557
|
+
} from 'class-validator'`);
|
|
558
|
+
}
|
|
559
|
+
return lines.join("\n");
|
|
560
|
+
}
|
|
561
|
+
/**
|
|
562
|
+
* Generate @Schema() decorator
|
|
563
|
+
*/
|
|
564
|
+
generateSchemaDecorator(options) {
|
|
565
|
+
const opts = [];
|
|
566
|
+
if (options?.versioning !== void 0) {
|
|
567
|
+
opts.push(`versioning: ${options.versioning}`);
|
|
568
|
+
}
|
|
569
|
+
if (options?.i18n !== void 0) {
|
|
570
|
+
opts.push(`i18n: ${options.i18n}`);
|
|
571
|
+
}
|
|
572
|
+
if (opts.length === 0) {
|
|
573
|
+
return "@Schema()";
|
|
574
|
+
}
|
|
575
|
+
return `@Schema({ ${opts.join(", ")} })`;
|
|
576
|
+
}
|
|
577
|
+
/**
|
|
578
|
+
* Generate code for a single field
|
|
579
|
+
*/
|
|
580
|
+
generateFieldCode(field) {
|
|
581
|
+
const decorators = [];
|
|
582
|
+
const indent = " ";
|
|
583
|
+
if (field.type === "date") {
|
|
584
|
+
decorators.push(`${indent}@Type(() => Date)`);
|
|
585
|
+
}
|
|
586
|
+
decorators.push(`${indent}@Prop(${this.generatePropOptions(field)})`);
|
|
587
|
+
if (field.validations.length > 0) {
|
|
588
|
+
const validators = field.validations.map((v) => this.formatValidator(v)).join(", ");
|
|
589
|
+
decorators.push(`${indent}@Validators(${validators})`);
|
|
590
|
+
}
|
|
591
|
+
decorators.push(`${indent}@UI(${this.generateUIOptions(field)})`);
|
|
592
|
+
const declaration = `${indent}${field.name}: ${field.tsType}`;
|
|
593
|
+
return [
|
|
594
|
+
...decorators,
|
|
595
|
+
declaration
|
|
596
|
+
].join("\n");
|
|
597
|
+
}
|
|
598
|
+
/**
|
|
599
|
+
* Generate @Prop() options object
|
|
600
|
+
*/
|
|
601
|
+
generatePropOptions(field) {
|
|
602
|
+
const options = [];
|
|
603
|
+
if (field.prop.required) {
|
|
604
|
+
options.push("required: true");
|
|
605
|
+
}
|
|
606
|
+
if (field.prop.unique) {
|
|
607
|
+
options.push("unique: true");
|
|
608
|
+
}
|
|
609
|
+
if (field.prop.intl) {
|
|
610
|
+
options.push("intl: true");
|
|
611
|
+
}
|
|
612
|
+
if (field.prop.hidden) {
|
|
613
|
+
options.push("hidden: true");
|
|
614
|
+
}
|
|
615
|
+
if (field.prop.readonly) {
|
|
616
|
+
options.push("readonly: true");
|
|
617
|
+
}
|
|
618
|
+
if (field.prop.default !== void 0) {
|
|
619
|
+
options.push(`default: ${JSON.stringify(field.prop.default)}`);
|
|
620
|
+
}
|
|
621
|
+
if (options.length === 0) {
|
|
622
|
+
return "";
|
|
623
|
+
}
|
|
624
|
+
return `{ ${options.join(", ")} }`;
|
|
625
|
+
}
|
|
626
|
+
/**
|
|
627
|
+
* Generate @UI() options object
|
|
628
|
+
*/
|
|
629
|
+
generateUIOptions(field) {
|
|
630
|
+
const options = [];
|
|
631
|
+
if (field.ui.tab) {
|
|
632
|
+
options.push(`tab: '${field.ui.tab}'`);
|
|
633
|
+
}
|
|
634
|
+
if (field.ui.side) {
|
|
635
|
+
options.push("side: true");
|
|
636
|
+
}
|
|
637
|
+
if (field.ui.type) {
|
|
638
|
+
options.push(`type: '${field.ui.type}'`);
|
|
639
|
+
}
|
|
640
|
+
if (field.ui.label && field.ui.label !== field.displayName) {
|
|
641
|
+
options.push(`label: '${this.escapeString(field.ui.label)}'`);
|
|
642
|
+
}
|
|
643
|
+
if (field.ui.description) {
|
|
644
|
+
options.push(`description: '${this.escapeString(field.ui.description)}'`);
|
|
645
|
+
}
|
|
646
|
+
if (field.ui.placeholder) {
|
|
647
|
+
options.push(`placeholder: '${this.escapeString(field.ui.placeholder)}'`);
|
|
648
|
+
}
|
|
649
|
+
if (field.ui.row) {
|
|
650
|
+
options.push("row: true");
|
|
651
|
+
}
|
|
652
|
+
if (field.ui.options && field.ui.options.length > 0) {
|
|
653
|
+
const optionsStr = field.ui.options.map((o) => `{ key: '${this.escapeString(o.key)}', value: '${this.escapeString(o.value)}' }`).join(", ");
|
|
654
|
+
options.push(`options: [${optionsStr}]`);
|
|
655
|
+
}
|
|
656
|
+
if (options.length === 0) {
|
|
657
|
+
return "{}";
|
|
658
|
+
}
|
|
659
|
+
return `{ ${options.join(", ")} }`;
|
|
660
|
+
}
|
|
661
|
+
/**
|
|
662
|
+
* Format a validator call
|
|
663
|
+
*/
|
|
664
|
+
formatValidator(rule) {
|
|
665
|
+
if (!rule.constraints || rule.constraints.length === 0) {
|
|
666
|
+
return `${rule.type}()`;
|
|
667
|
+
}
|
|
668
|
+
const args = rule.constraints.map((c) => {
|
|
669
|
+
if (typeof c === "string") {
|
|
670
|
+
if (rule.type === "Matches") {
|
|
671
|
+
return c.startsWith("/") ? c : `/${c}/`;
|
|
672
|
+
}
|
|
673
|
+
return `'${this.escapeString(String(c))}'`;
|
|
674
|
+
}
|
|
675
|
+
return String(c);
|
|
676
|
+
}).join(", ");
|
|
677
|
+
return `${rule.type}(${args})`;
|
|
678
|
+
}
|
|
679
|
+
/**
|
|
680
|
+
* Escape string for use in generated code
|
|
681
|
+
*/
|
|
682
|
+
escapeString(str) {
|
|
683
|
+
return str.replace(/\\/g, "\\\\").replace(/'/g, "\\'").replace(/\n/g, "\\n").replace(/\r/g, "\\r").replace(/\t/g, "\\t");
|
|
684
|
+
}
|
|
685
|
+
/**
|
|
686
|
+
* Generate JSON representation of the schema
|
|
687
|
+
*/
|
|
688
|
+
generateSchemaJSON(dto) {
|
|
689
|
+
return {
|
|
690
|
+
name: dto.name,
|
|
691
|
+
options: {
|
|
692
|
+
versioning: dto.options?.versioning ?? true,
|
|
693
|
+
i18n: dto.options?.i18n ?? true
|
|
694
|
+
},
|
|
695
|
+
properties: dto.fields.map((field) => ({
|
|
696
|
+
name: field.name,
|
|
697
|
+
displayName: field.displayName,
|
|
698
|
+
type: field.type,
|
|
699
|
+
tsType: field.tsType,
|
|
700
|
+
required: field.prop.required,
|
|
701
|
+
unique: field.prop.unique,
|
|
702
|
+
intl: field.prop.intl,
|
|
703
|
+
ui: field.ui,
|
|
704
|
+
validations: field.validations,
|
|
705
|
+
...field.relationConfig && {
|
|
706
|
+
relationConfig: field.relationConfig
|
|
707
|
+
}
|
|
708
|
+
}))
|
|
709
|
+
};
|
|
710
|
+
}
|
|
711
|
+
/**
|
|
712
|
+
* Parse a schema file content to extract metadata
|
|
713
|
+
*/
|
|
714
|
+
parseSchemaFile(content) {
|
|
715
|
+
try {
|
|
716
|
+
const classMatch = content.match(/export\s+class\s+(\w+)/);
|
|
717
|
+
if (!classMatch?.[1]) return null;
|
|
718
|
+
const name = classMatch[1];
|
|
719
|
+
const schemaMatch = content.match(/@Schema\(\{([^}]*)\}\)/);
|
|
720
|
+
const options = {};
|
|
721
|
+
if (schemaMatch?.[1]) {
|
|
722
|
+
const optionsStr = schemaMatch[1];
|
|
723
|
+
if (optionsStr.includes("versioning: true")) options.versioning = true;
|
|
724
|
+
if (optionsStr.includes("versioning: false")) options.versioning = false;
|
|
725
|
+
if (optionsStr.includes("i18n: true")) options.i18n = true;
|
|
726
|
+
if (optionsStr.includes("i18n: false")) options.i18n = false;
|
|
727
|
+
}
|
|
728
|
+
const fields = [];
|
|
729
|
+
const fieldRegex = /@Prop\(([^)]*)\)[^@]*@UI\(([^)]*)\)[^:]*(\w+):\s*(\w+)/g;
|
|
730
|
+
let fieldMatch = fieldRegex.exec(content);
|
|
731
|
+
while (fieldMatch !== null) {
|
|
732
|
+
const propStr = fieldMatch[1];
|
|
733
|
+
const uiStr = fieldMatch[2];
|
|
734
|
+
const fieldName = fieldMatch[3];
|
|
735
|
+
const tsType = fieldMatch[4];
|
|
736
|
+
if (propStr !== void 0 && uiStr && fieldName && tsType) {
|
|
737
|
+
fields.push({
|
|
738
|
+
name: fieldName,
|
|
739
|
+
displayName: fieldName,
|
|
740
|
+
type: this.inferFieldType(tsType, uiStr),
|
|
741
|
+
tsType,
|
|
742
|
+
prop: {
|
|
743
|
+
required: propStr.includes("required: true"),
|
|
744
|
+
unique: propStr.includes("unique: true"),
|
|
745
|
+
intl: propStr.includes("intl: true")
|
|
746
|
+
},
|
|
747
|
+
ui: this.parseUIOptions(uiStr),
|
|
748
|
+
validations: []
|
|
749
|
+
});
|
|
750
|
+
}
|
|
751
|
+
fieldMatch = fieldRegex.exec(content);
|
|
752
|
+
}
|
|
753
|
+
return {
|
|
754
|
+
name,
|
|
755
|
+
options,
|
|
756
|
+
fields
|
|
757
|
+
};
|
|
758
|
+
} catch (error) {
|
|
759
|
+
this.logger.error("Failed to parse schema file", error);
|
|
760
|
+
return null;
|
|
761
|
+
}
|
|
762
|
+
}
|
|
763
|
+
/**
|
|
764
|
+
* Infer field type from TypeScript type and UI options
|
|
765
|
+
*/
|
|
766
|
+
inferFieldType(tsType, uiStr) {
|
|
767
|
+
if (uiStr.includes("type: 'switch'") || uiStr.includes("type: 'checkbox'")) return "boolean";
|
|
768
|
+
if (uiStr.includes("type: 'date'")) return "date";
|
|
769
|
+
if (uiStr.includes("type: 'number'")) return "number";
|
|
770
|
+
if (uiStr.includes("type: 'select'") || uiStr.includes("type: 'radio'")) return "select";
|
|
771
|
+
if (uiStr.includes("type: 'relationship'")) return "relation";
|
|
772
|
+
const lowerType = tsType.toLowerCase();
|
|
773
|
+
if (lowerType === "number") return "number";
|
|
774
|
+
if (lowerType === "boolean") return "boolean";
|
|
775
|
+
if (lowerType === "date") return "date";
|
|
776
|
+
return "text";
|
|
777
|
+
}
|
|
778
|
+
/**
|
|
779
|
+
* Parse UI options from string
|
|
780
|
+
*/
|
|
781
|
+
parseUIOptions(uiStr) {
|
|
782
|
+
const ui = {};
|
|
783
|
+
const tabMatch = uiStr.match(/tab:\s*'([^']*)'/);
|
|
784
|
+
if (tabMatch) ui.tab = tabMatch[1];
|
|
785
|
+
const typeMatch = uiStr.match(/type:\s*'([^']*)'/);
|
|
786
|
+
if (typeMatch) ui.type = typeMatch[1];
|
|
787
|
+
const labelMatch = uiStr.match(/label:\s*'([^']*)'/);
|
|
788
|
+
if (labelMatch) ui.label = labelMatch[1];
|
|
789
|
+
const descMatch = uiStr.match(/description:\s*'([^']*)'/);
|
|
790
|
+
if (descMatch) ui.description = descMatch[1];
|
|
791
|
+
if (uiStr.includes("side: true")) ui.side = true;
|
|
792
|
+
if (uiStr.includes("row: true")) ui.row = true;
|
|
793
|
+
return ui;
|
|
794
|
+
}
|
|
795
|
+
};
|
|
796
|
+
PlaygroundService = _ts_decorate([
|
|
797
|
+
common.Injectable(),
|
|
798
|
+
_ts_param(0, common.Optional()),
|
|
799
|
+
_ts_param(0, common.Inject(common$1.MagnetModuleOptions)),
|
|
800
|
+
_ts_param(1, common.Optional()),
|
|
801
|
+
_ts_param(1, core.InjectPluginOptions("playground")),
|
|
802
|
+
_ts_metadata("design:type", Function),
|
|
803
|
+
_ts_metadata("design:paramtypes", [
|
|
804
|
+
typeof common$1.MagnetModuleOptions === "undefined" ? Object : common$1.MagnetModuleOptions,
|
|
805
|
+
typeof PlaygroundPluginOptions === "undefined" ? Object : PlaygroundPluginOptions
|
|
806
|
+
])
|
|
807
|
+
], PlaygroundService);
|
|
808
|
+
|
|
809
|
+
// src/backend/playground.controller.ts
|
|
810
|
+
function _ts_decorate2(decorators, target, key, desc) {
|
|
811
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
812
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
813
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
814
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
815
|
+
}
|
|
816
|
+
__name(_ts_decorate2, "_ts_decorate");
|
|
817
|
+
function _ts_metadata2(k, v) {
|
|
818
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
819
|
+
}
|
|
820
|
+
__name(_ts_metadata2, "_ts_metadata");
|
|
821
|
+
function _ts_param2(paramIndex, decorator) {
|
|
822
|
+
return function(target, key) {
|
|
823
|
+
decorator(target, key, paramIndex);
|
|
824
|
+
};
|
|
825
|
+
}
|
|
826
|
+
__name(_ts_param2, "_ts_param");
|
|
827
|
+
var PlaygroundController = class {
|
|
828
|
+
static {
|
|
829
|
+
__name(this, "PlaygroundController");
|
|
830
|
+
}
|
|
831
|
+
playgroundService;
|
|
832
|
+
constructor(playgroundService) {
|
|
833
|
+
this.playgroundService = playgroundService;
|
|
834
|
+
}
|
|
835
|
+
/**
|
|
836
|
+
* List all schemas
|
|
837
|
+
* GET /playground/schemas
|
|
838
|
+
*/
|
|
839
|
+
async listSchemas() {
|
|
840
|
+
try {
|
|
841
|
+
return await this.playgroundService.listSchemas();
|
|
842
|
+
} catch (error) {
|
|
843
|
+
throw new common.HttpException(error instanceof Error ? error.message : "Failed to list schemas", common.HttpStatus.INTERNAL_SERVER_ERROR);
|
|
844
|
+
}
|
|
845
|
+
}
|
|
846
|
+
/**
|
|
847
|
+
* Get a schema by name
|
|
848
|
+
* GET /playground/schemas/:name
|
|
849
|
+
*/
|
|
850
|
+
async getSchema(name) {
|
|
851
|
+
try {
|
|
852
|
+
const schema = await this.playgroundService.getSchema(name);
|
|
853
|
+
if (!schema) {
|
|
854
|
+
throw new common.HttpException("Schema not found", common.HttpStatus.NOT_FOUND);
|
|
855
|
+
}
|
|
856
|
+
return schema;
|
|
857
|
+
} catch (error) {
|
|
858
|
+
if (error instanceof common.HttpException) throw error;
|
|
859
|
+
throw new common.HttpException(error instanceof Error ? error.message : "Failed to get schema", common.HttpStatus.INTERNAL_SERVER_ERROR);
|
|
860
|
+
}
|
|
861
|
+
}
|
|
862
|
+
/**
|
|
863
|
+
* Create a new module with schema, controller, service, and DTO
|
|
864
|
+
* POST /playground/schemas
|
|
865
|
+
*/
|
|
866
|
+
async createSchema(body) {
|
|
867
|
+
try {
|
|
868
|
+
if (!body.name || !/^[A-Z][A-Za-z0-9]*$/.test(body.name)) {
|
|
869
|
+
throw new common.HttpException("Invalid schema name. Must start with uppercase letter and contain only alphanumeric characters.", common.HttpStatus.BAD_REQUEST);
|
|
870
|
+
}
|
|
871
|
+
if (this.playgroundService.schemaExists(body.name)) {
|
|
872
|
+
throw new common.HttpException("Module with this name already exists. Use PUT to update the schema.", common.HttpStatus.CONFLICT);
|
|
873
|
+
}
|
|
874
|
+
return await this.playgroundService.createModule(body);
|
|
875
|
+
} catch (error) {
|
|
876
|
+
if (error instanceof common.HttpException) throw error;
|
|
877
|
+
throw new common.HttpException(error instanceof Error ? error.message : "Failed to create module", common.HttpStatus.INTERNAL_SERVER_ERROR);
|
|
878
|
+
}
|
|
879
|
+
}
|
|
880
|
+
/**
|
|
881
|
+
* Update an existing schema (only the schema file, not the whole module)
|
|
882
|
+
* Returns conflicts if field types have changed
|
|
883
|
+
* PUT /playground/schemas/:name
|
|
884
|
+
*/
|
|
885
|
+
async updateSchema(name, body) {
|
|
886
|
+
try {
|
|
887
|
+
if (!body.name || !/^[A-Z][A-Za-z0-9]*$/.test(body.name)) {
|
|
888
|
+
throw new common.HttpException("Invalid schema name. Must start with uppercase letter and contain only alphanumeric characters.", common.HttpStatus.BAD_REQUEST);
|
|
889
|
+
}
|
|
890
|
+
if (!this.playgroundService.schemaExists(name)) {
|
|
891
|
+
throw new common.HttpException("Schema not found. Use POST to create a new module.", common.HttpStatus.NOT_FOUND);
|
|
892
|
+
}
|
|
893
|
+
if (body.name.toLowerCase() !== name.toLowerCase()) {
|
|
894
|
+
throw new common.HttpException("Renaming schemas is not supported. Create a new module instead.", common.HttpStatus.BAD_REQUEST);
|
|
895
|
+
}
|
|
896
|
+
const result = await this.playgroundService.updateSchema(name, body);
|
|
897
|
+
return {
|
|
898
|
+
...result.detail,
|
|
899
|
+
conflicts: result.conflicts
|
|
900
|
+
};
|
|
901
|
+
} catch (error) {
|
|
902
|
+
if (error instanceof common.HttpException) throw error;
|
|
903
|
+
throw new common.HttpException(error instanceof Error ? error.message : "Failed to update schema", common.HttpStatus.INTERNAL_SERVER_ERROR);
|
|
904
|
+
}
|
|
905
|
+
}
|
|
906
|
+
/**
|
|
907
|
+
* Delete a schema
|
|
908
|
+
* DELETE /playground/schemas/:name
|
|
909
|
+
*/
|
|
910
|
+
async deleteSchema(name) {
|
|
911
|
+
try {
|
|
912
|
+
const deleted = await this.playgroundService.deleteSchema(name);
|
|
913
|
+
if (!deleted) {
|
|
914
|
+
throw new common.HttpException("Schema not found", common.HttpStatus.NOT_FOUND);
|
|
915
|
+
}
|
|
916
|
+
return {
|
|
917
|
+
success: true
|
|
918
|
+
};
|
|
919
|
+
} catch (error) {
|
|
920
|
+
if (error instanceof common.HttpException) throw error;
|
|
921
|
+
throw new common.HttpException(error instanceof Error ? error.message : "Failed to delete schema", common.HttpStatus.INTERNAL_SERVER_ERROR);
|
|
922
|
+
}
|
|
923
|
+
}
|
|
924
|
+
/**
|
|
925
|
+
* Generate code preview without saving
|
|
926
|
+
* POST /playground/preview
|
|
927
|
+
*/
|
|
928
|
+
previewCode(body) {
|
|
929
|
+
try {
|
|
930
|
+
return this.playgroundService.previewCode(body);
|
|
931
|
+
} catch (error) {
|
|
932
|
+
throw new common.HttpException(error instanceof Error ? error.message : "Failed to generate preview", common.HttpStatus.INTERNAL_SERVER_ERROR);
|
|
933
|
+
}
|
|
934
|
+
}
|
|
935
|
+
};
|
|
936
|
+
_ts_decorate2([
|
|
937
|
+
common.Get("schemas"),
|
|
938
|
+
_ts_metadata2("design:type", Function),
|
|
939
|
+
_ts_metadata2("design:paramtypes", []),
|
|
940
|
+
_ts_metadata2("design:returntype", Promise)
|
|
941
|
+
], PlaygroundController.prototype, "listSchemas", null);
|
|
942
|
+
_ts_decorate2([
|
|
943
|
+
common.Get("schemas/:name"),
|
|
944
|
+
_ts_param2(0, common.Param("name")),
|
|
945
|
+
_ts_metadata2("design:type", Function),
|
|
946
|
+
_ts_metadata2("design:paramtypes", [
|
|
947
|
+
String
|
|
948
|
+
]),
|
|
949
|
+
_ts_metadata2("design:returntype", Promise)
|
|
950
|
+
], PlaygroundController.prototype, "getSchema", null);
|
|
951
|
+
_ts_decorate2([
|
|
952
|
+
common.Post("schemas"),
|
|
953
|
+
_ts_param2(0, common.Body()),
|
|
954
|
+
_ts_metadata2("design:type", Function),
|
|
955
|
+
_ts_metadata2("design:paramtypes", [
|
|
956
|
+
typeof CreateSchemaDto === "undefined" ? Object : CreateSchemaDto
|
|
957
|
+
]),
|
|
958
|
+
_ts_metadata2("design:returntype", Promise)
|
|
959
|
+
], PlaygroundController.prototype, "createSchema", null);
|
|
960
|
+
_ts_decorate2([
|
|
961
|
+
common.Put("schemas/:name"),
|
|
962
|
+
_ts_param2(0, common.Param("name")),
|
|
963
|
+
_ts_param2(1, common.Body()),
|
|
964
|
+
_ts_metadata2("design:type", Function),
|
|
965
|
+
_ts_metadata2("design:paramtypes", [
|
|
966
|
+
String,
|
|
967
|
+
typeof CreateSchemaDto === "undefined" ? Object : CreateSchemaDto
|
|
968
|
+
]),
|
|
969
|
+
_ts_metadata2("design:returntype", Promise)
|
|
970
|
+
], PlaygroundController.prototype, "updateSchema", null);
|
|
971
|
+
_ts_decorate2([
|
|
972
|
+
common.Delete("schemas/:name"),
|
|
973
|
+
_ts_param2(0, common.Param("name")),
|
|
974
|
+
_ts_metadata2("design:type", Function),
|
|
975
|
+
_ts_metadata2("design:paramtypes", [
|
|
976
|
+
String
|
|
977
|
+
]),
|
|
978
|
+
_ts_metadata2("design:returntype", Promise)
|
|
979
|
+
], PlaygroundController.prototype, "deleteSchema", null);
|
|
980
|
+
_ts_decorate2([
|
|
981
|
+
common.Post("preview"),
|
|
982
|
+
_ts_param2(0, common.Body()),
|
|
983
|
+
_ts_metadata2("design:type", Function),
|
|
984
|
+
_ts_metadata2("design:paramtypes", [
|
|
985
|
+
typeof CreateSchemaDto === "undefined" ? Object : CreateSchemaDto
|
|
986
|
+
]),
|
|
987
|
+
_ts_metadata2("design:returntype", void 0)
|
|
988
|
+
], PlaygroundController.prototype, "previewCode", null);
|
|
989
|
+
PlaygroundController = _ts_decorate2([
|
|
990
|
+
common.Controller("playground"),
|
|
991
|
+
core.RestrictedRoute(),
|
|
992
|
+
_ts_metadata2("design:type", Function),
|
|
993
|
+
_ts_metadata2("design:paramtypes", [
|
|
994
|
+
typeof PlaygroundService === "undefined" ? Object : PlaygroundService
|
|
995
|
+
])
|
|
996
|
+
], PlaygroundController);
|
|
997
|
+
|
|
998
|
+
// src/backend/playground.module.ts
|
|
999
|
+
function _ts_decorate3(decorators, target, key, desc) {
|
|
1000
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
1001
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
1002
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
1003
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
1004
|
+
}
|
|
1005
|
+
__name(_ts_decorate3, "_ts_decorate");
|
|
1006
|
+
exports.PlaygroundModule = class PlaygroundModule {
|
|
1007
|
+
static {
|
|
1008
|
+
__name(this, "PlaygroundModule");
|
|
1009
|
+
}
|
|
1010
|
+
};
|
|
1011
|
+
exports.PlaygroundModule = _ts_decorate3([
|
|
1012
|
+
common.Module({
|
|
1013
|
+
controllers: [
|
|
1014
|
+
PlaygroundController
|
|
1015
|
+
],
|
|
1016
|
+
providers: [
|
|
1017
|
+
PlaygroundService
|
|
1018
|
+
],
|
|
1019
|
+
exports: [
|
|
1020
|
+
PlaygroundService
|
|
1021
|
+
]
|
|
1022
|
+
})
|
|
1023
|
+
], exports.PlaygroundModule);
|