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