@quatrain/app 1.1.1 → 1.1.2

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.
@@ -6,5 +6,6 @@ export declare class CodeGenerator {
6
6
  private static generateApiEndpoints;
7
7
  private static generateIndex;
8
8
  private static generateMigration;
9
+ private static generateFrontend;
9
10
  }
10
11
  //# sourceMappingURL=CodeGenerator.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"CodeGenerator.d.ts","sourceRoot":"","sources":["../src/CodeGenerator.ts"],"names":[],"mappings":"AAIA,qBAAa,aAAa;WAIT,QAAQ,CAAC,MAAM,EAAE,GAAG,EAAE,SAAS,EAAE,MAAM,GAAG,IAAI;IAuB5D,OAAO,CAAC,MAAM,CAAC,mBAAmB;IAoClC,OAAO,CAAC,MAAM,CAAC,gBAAgB;IAiB/B,OAAO,CAAC,MAAM,CAAC,cAAc;IA4C7B,OAAO,CAAC,MAAM,CAAC,oBAAoB;IAenC,OAAO,CAAC,MAAM,CAAC,aAAa;IAmD5B,OAAO,CAAC,MAAM,CAAC,iBAAiB;CAmDlC"}
1
+ {"version":3,"file":"CodeGenerator.d.ts","sourceRoot":"","sources":["../src/CodeGenerator.ts"],"names":[],"mappings":"AAIA,qBAAa,aAAa;WAIT,QAAQ,CAAC,MAAM,EAAE,GAAG,EAAE,SAAS,EAAE,MAAM,GAAG,IAAI;IAyB5D,OAAO,CAAC,MAAM,CAAC,mBAAmB;IAqClC,OAAO,CAAC,MAAM,CAAC,gBAAgB;IAiB/B,OAAO,CAAC,MAAM,CAAC,cAAc;IAkD7B,OAAO,CAAC,MAAM,CAAC,oBAAoB;IAWnC,OAAO,CAAC,MAAM,CAAC,aAAa;IAuD5B,OAAO,CAAC,MAAM,CAAC,iBAAiB;IAyEhC,OAAO,CAAC,MAAM,CAAC,gBAAgB;CA0VjC"}
@@ -53,6 +53,7 @@ class CodeGenerator {
53
53
  this.generateApiEndpoints(config, fullTargetDir, generatedModels);
54
54
  this.generateIndex(config, fullTargetDir, generatedModels);
55
55
  this.generateMigration(config, fullTargetDir);
56
+ this.generateFrontend(config, fullTargetDir, generatedModels);
56
57
  log_1.Log.info(`[CodeGenerator] Application générée avec succès dans ${fullTargetDir}`);
57
58
  }
58
59
  static generatePackageJson(config, targetDir) {
@@ -65,7 +66,8 @@ class CodeGenerator {
65
66
  main: "src/index.ts",
66
67
  type: "module",
67
68
  scripts: {
68
- start: "bun run src/index.ts"
69
+ "start:dev": "NODE_ENV=development bun run --watch src/index.ts",
70
+ "start:prod": "NODE_ENV=production bun run src/index.ts"
69
71
  },
70
72
  dependencies: {
71
73
  "@quatrain/core": targetNpmTag,
@@ -110,15 +112,20 @@ class CodeGenerator {
110
112
  const className = model.name;
111
113
  const collectionName = model.collectionName || className.toLowerCase();
112
114
  generatedNames.push(className);
115
+ const mapTypeToClass = (type) => {
116
+ if (type.toLowerCase() === 'datetime')
117
+ return 'DateTimeProperty';
118
+ return type.charAt(0).toUpperCase() + type.slice(1) + 'Property';
119
+ };
113
120
  const propsCode = model.properties.map((p) => {
114
- const propType = p.type;
121
+ const propClass = mapTypeToClass(p.type);
115
122
  return ` {
116
123
  name: '${p.name}',
117
- type: ${propType}.TYPE,
124
+ type: ${propClass}.TYPE,
118
125
  mandatory: ${p.options?.mandatory ? 'true' : 'false'}
119
126
  }`;
120
127
  }).join(',\n');
121
- const propTypesToImport = Array.from(new Set(model.properties.map((p) => p.type)));
128
+ const propTypesToImport = Array.from(new Set(model.properties.map((p) => mapTypeToClass(p.type))));
122
129
  const modelCode = `import { PersistedBaseObject } from '@quatrain/backend'
123
130
  import { ${propTypesToImport.join(', ')} } from '@quatrain/core'
124
131
 
@@ -144,11 +151,7 @@ export class ${className} extends PersistedBaseObject {
144
151
  const apiCode = `import { CrudEndpoint } from '@quatrain/api-server'
145
152
  import { ${className} } from '../models/${className}'
146
153
 
147
- export class ${className}Api extends CrudEndpoint {
148
- constructor() {
149
- super('/api/${className.toLowerCase()}s', ${className})
150
- }
151
- }
154
+ export const ${className}Api = CrudEndpoint(${className})
152
155
  `;
153
156
  fs.writeFileSync(path.resolve(targetDir, `src/api/${className}Api.ts`), apiCode);
154
157
  }
@@ -158,27 +161,31 @@ export class ${className}Api extends CrudEndpoint {
158
161
  const adapterImport = config.backend?.adapter === 'PostgresAdapter' ? '@quatrain/backend-postgres' : '@quatrain/backend-sqlite';
159
162
  const modelImports = models.map(m => `import { ${m} } from './models/${m}'`).join('\n');
160
163
  const apiImports = models.map(m => `import { ${m}Api } from './api/${m}Api'`).join('\n');
161
- const apiRegisters = models.map(m => ` server.addEndpoint(new ${m}Api())`).join('\n');
164
+ const apiRegisters = models.map(m => ` server.addEndpoint(${m}Api, '/api/${m.toLowerCase()}s')`).join('\n');
162
165
  const indexCode = `import * as path from 'path'
163
166
  import { Backend, InjectMetaMiddleware } from '@quatrain/backend'
164
167
  import { ${adapterClass} } from '${adapterImport}'
165
168
  import { ExpressAdapter } from '@quatrain/api-server'
166
169
  import { MigrationManager } from '@quatrain/backend-migrations'
170
+ import { Log, DefaultLoggerAdapter, LogLevel } from '@quatrain/log'
167
171
  ${config.authMode === 'oauth' ? `import { AuthOIDC } from '@quatrain/auth-oidc'\n` : ''}
168
172
  ${modelImports}
169
173
  ${apiImports}
170
174
 
171
175
  ;(async () => {
172
176
  try {
177
+ const isProd = process.env.NODE_ENV === 'production'
178
+ Log.addLogger('default', new DefaultLoggerAdapter('', isProd ? LogLevel.INFO : LogLevel.DEBUG), true)
179
+
173
180
  const adapter = new ${adapterClass}({
174
181
  config: ${adapterClass === 'SQLiteAdapter' ? '{ database: path.resolve(process.cwd(), "data/app.sqlite") }' : 'process.env.DATABASE_URL'},
175
182
  middlewares: [new InjectMetaMiddleware()]
176
183
  })
177
184
 
178
- Backend.init(adapter)
185
+ Backend.addBackend(adapter, 'default', true)
179
186
 
180
- const mm = new MigrationManager()
181
- await mm.up()
187
+ const mm = new MigrationManager(adapter)
188
+ await mm.executeMigrations()
182
189
 
183
190
  const server = new ExpressAdapter()
184
191
 
@@ -186,7 +193,7 @@ ${apiRegisters}
186
193
 
187
194
  ${config.authMode === 'oauth' ? ` // Initialize OIDC Auth
188
195
  const oidc = AuthOIDC.init('http://localhost:4001')
189
- server.getApp().use('/oidc', oidc.callback())
196
+ server.getNativeInstance().use('/oidc', oidc.callback())
190
197
  ` : ''}
191
198
 
192
199
  const PORT = Number(process.env.PORT) || 4001
@@ -209,21 +216,30 @@ ${config.authMode === 'oauth' ? ` // Initialize OIDC Auth
209
216
  if (!model.name)
210
217
  continue;
211
218
  const collectionName = model.collectionName || model.name.toLowerCase();
212
- const columns = model.properties.map((p) => {
219
+ const baseColumns = ['id', 'name', 'status', 'createdby', 'createdat', 'updatedby', 'updatedat', 'deletedby', 'deletedat'];
220
+ const customColumns = model.properties
221
+ .filter((p) => !baseColumns.includes(p.name.toLowerCase()))
222
+ .map((p) => {
213
223
  let sqlType = 'TEXT';
214
224
  if (p.type === 'NumberProperty' || p.type === 'BooleanProperty')
215
225
  sqlType = 'INTEGER';
216
- return `${p.name} ${sqlType}`;
217
- }).join(',\n ');
226
+ return `${p.name.toLowerCase()} ${sqlType}`;
227
+ });
228
+ const allColumns = [
229
+ 'id TEXT PRIMARY KEY',
230
+ 'name TEXT',
231
+ 'status TEXT',
232
+ 'createdat INTEGER',
233
+ 'updatedat INTEGER',
234
+ 'deletedat INTEGER',
235
+ 'createdby TEXT',
236
+ 'updatedby TEXT',
237
+ 'deletedby TEXT',
238
+ ...customColumns
239
+ ].join(',\n ');
218
240
  upQueries += `
219
241
  await adapter.rawQuery(\`CREATE TABLE IF NOT EXISTS ${collectionName} (
220
- id TEXT PRIMARY KEY,
221
- status TEXT,
222
- created_at TEXT,
223
- updated_at TEXT,
224
- created_by TEXT,
225
- updated_by TEXT,
226
- ${columns}
242
+ ${allColumns}
227
243
  )\`)\n`;
228
244
  downQueries += `
229
245
  await adapter.rawQuery('DROP TABLE IF EXISTS ${collectionName}')\n`;
@@ -244,8 +260,331 @@ export const down = async ({ context: adapter }: { context: AbstractBackendAdapt
244
260
  }
245
261
  }
246
262
  `;
263
+ const migrationsDir = path.resolve(targetDir, 'data/migrations/default');
264
+ if (fs.existsSync(migrationsDir)) {
265
+ fs.readdirSync(migrationsDir).forEach(file => {
266
+ if (file.endsWith('.ts')) {
267
+ fs.unlinkSync(path.join(migrationsDir, file));
268
+ }
269
+ });
270
+ }
247
271
  const timestamp = new Date().toISOString().replace(/[-:T]/g, '').slice(0, 14);
248
- fs.writeFileSync(path.resolve(targetDir, `data/migrations/default/${timestamp}_init.ts`), migrationCode);
272
+ fs.writeFileSync(path.join(migrationsDir, `${timestamp}_init.ts`), migrationCode);
273
+ }
274
+ static generateFrontend(config, targetDir, models) {
275
+ const webDir = path.resolve(targetDir, 'web');
276
+ if (!fs.existsSync(webDir)) {
277
+ fs.mkdirSync(webDir, { recursive: true });
278
+ }
279
+ const srcDir = path.resolve(webDir, 'src');
280
+ if (!fs.existsSync(srcDir)) {
281
+ fs.mkdirSync(srcDir, { recursive: true });
282
+ }
283
+ const pagesDir = path.resolve(srcDir, 'pages');
284
+ if (!fs.existsSync(pagesDir)) {
285
+ fs.mkdirSync(pagesDir, { recursive: true });
286
+ }
287
+ const packageJson = {
288
+ name: "quatrain-ui-front",
289
+ private: true,
290
+ version: "1.0.0",
291
+ type: "module",
292
+ scripts: {
293
+ "dev": "vite",
294
+ "build": "tsc && vite build",
295
+ "preview": "vite preview"
296
+ },
297
+ dependencies: {
298
+ "@mantine/core": "^7.11.1",
299
+ "@mantine/hooks": "^7.11.1",
300
+ "@quatrain/api-client": "latest-dev",
301
+ "react": "^18.3.1",
302
+ "react-dom": "^18.3.1",
303
+ "react-router-dom": "^6.24.1"
304
+ },
305
+ devDependencies: {
306
+ "@types/react": "^18.3.3",
307
+ "@types/react-dom": "^18.3.0",
308
+ "@vitejs/plugin-react": "^4.3.1",
309
+ "typescript": "^5.5.3",
310
+ "vite": "^5.3.4"
311
+ }
312
+ };
313
+ fs.writeFileSync(path.join(webDir, 'package.json'), JSON.stringify(packageJson, null, 2));
314
+ const tsConfig = {
315
+ compilerOptions: {
316
+ target: "ES2020",
317
+ useDefineForClassFields: true,
318
+ lib: ["ES2020", "DOM", "DOM.Iterable"],
319
+ module: "ESNext",
320
+ skipLibCheck: true,
321
+ moduleResolution: "bundler",
322
+ allowImportingTsExtensions: true,
323
+ resolveJsonModule: true,
324
+ isolatedModules: true,
325
+ noEmit: true,
326
+ jsx: "react-jsx",
327
+ strict: true,
328
+ noUnusedLocals: true,
329
+ noUnusedParameters: true,
330
+ noFallthroughCasesInSwitch: true
331
+ },
332
+ include: ["src"]
333
+ };
334
+ fs.writeFileSync(path.join(webDir, 'tsconfig.json'), JSON.stringify(tsConfig, null, 2));
335
+ const viteConfig = `import { defineConfig } from 'vite'
336
+ import react from '@vitejs/plugin-react'
337
+
338
+ // https://vitejs.dev/config/
339
+ export default defineConfig({
340
+ plugins: [react()],
341
+ server: {
342
+ host: '0.0.0.0',
343
+ port: 3000,
344
+ proxy: {
345
+ '/api': {
346
+ target: 'http://localhost:4001',
347
+ changeOrigin: true
348
+ }
349
+ }
350
+ }
351
+ })
352
+ `;
353
+ fs.writeFileSync(path.join(webDir, 'vite.config.ts'), viteConfig);
354
+ const indexHtml = `<!doctype html>
355
+ <html lang="en">
356
+ <head>
357
+ <meta charset="UTF-8" />
358
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
359
+ <title>Quatrain App</title>
360
+ </head>
361
+ <body>
362
+ <div id="root"></div>
363
+ <script type="module" src="/src/main.tsx"></script>
364
+ </body>
365
+ </html>
366
+ `;
367
+ fs.writeFileSync(path.join(webDir, 'index.html'), indexHtml);
368
+ const mainTsx = `import React from 'react'
369
+ import ReactDOM from 'react-dom/client'
370
+ import { MantineProvider } from '@mantine/core'
371
+ import '@mantine/core/styles.css'
372
+ import { BrowserRouter } from 'react-router-dom'
373
+ import App from './App.tsx'
374
+
375
+ ReactDOM.createRoot(document.getElementById('root')!).render(
376
+ <React.StrictMode>
377
+ <MantineProvider defaultColorScheme="dark">
378
+ <BrowserRouter>
379
+ <App />
380
+ </BrowserRouter>
381
+ </MantineProvider>
382
+ </React.StrictMode>,
383
+ )
384
+ `;
385
+ fs.writeFileSync(path.join(srcDir, 'main.tsx'), mainTsx);
386
+ const apiTs = `import { ApiClient } from '@quatrain/api-client'
387
+
388
+ export const api = new ApiClient('/api')
389
+ `;
390
+ fs.writeFileSync(path.join(srcDir, 'api.ts'), apiTs);
391
+ const menuItems = models.map(m => ` <a href="/${m.toLowerCase()}s" style={{display: 'block', padding: '10px', color: 'white', textDecoration: 'none'}}>${m}s</a>`).join('\n');
392
+ const routes = models.map(m => ` <Route path="/${m.toLowerCase()}s" element={<${m}List />} />\n <Route path="/${m.toLowerCase()}s/:id" element={<${m}Form />} />`).join('\n');
393
+ const imports = models.map(m => `import { ${m}List } from './pages/${m}List'\nimport { ${m}Form } from './pages/${m}Form'`).join('\n');
394
+ const appTsx = `import { AppShell, Burger, Group, Title } from '@mantine/core'
395
+ import { useDisclosure } from '@mantine/hooks'
396
+ import { Routes, Route } from 'react-router-dom'
397
+ ${imports}
398
+
399
+ export default function App() {
400
+ const [opened, { toggle }] = useDisclosure()
401
+
402
+ return (
403
+ <AppShell
404
+ header={{ height: 60 }}
405
+ navbar={{
406
+ width: 300,
407
+ breakpoint: 'sm',
408
+ collapsed: { mobile: !opened },
409
+ }}
410
+ padding="md"
411
+ >
412
+ <AppShell.Header>
413
+ <Group h="100%" px="md">
414
+ <Burger opened={opened} onClick={toggle} hiddenFrom="sm" size="sm" />
415
+ <Title order={3}>Quatrain App</Title>
416
+ </Group>
417
+ </AppShell.Header>
418
+
419
+ <AppShell.Navbar p="md">
420
+ ${menuItems}
421
+ </AppShell.Navbar>
422
+
423
+ <AppShell.Main>
424
+ <Routes>
425
+ ${routes}
426
+ <Route path="/" element={<div>Welcome to your Quatrain Application</div>} />
427
+ </Routes>
428
+ </AppShell.Main>
429
+ </AppShell>
430
+ )
431
+ }
432
+ `;
433
+ fs.writeFileSync(path.join(srcDir, 'App.tsx'), appTsx);
434
+ for (const m of models) {
435
+ const modelConfig = config.models.find((mod) => mod.name === m);
436
+ const props = modelConfig?.properties || [];
437
+ const listTsx = `import { useEffect, useState } from 'react'
438
+ import { Table, Button, Group, Title } from '@mantine/core'
439
+ import { useNavigate } from 'react-router-dom'
440
+ import { api } from '../api'
441
+
442
+ export function ${m}List() {
443
+ const [data, setData] = useState<any[]>([])
444
+ const navigate = useNavigate()
445
+
446
+ useEffect(() => {
447
+ api.get('${m.toLowerCase()}s').then((res) => {
448
+ if (res && res.data) {
449
+ setData(res.data)
450
+ }
451
+ })
452
+ }, [])
453
+
454
+ const handleDelete = async (id: string) => {
455
+ if (confirm('Are you sure?')) {
456
+ await api.delete('${m.toLowerCase()}s/' + id, {})
457
+ setData(data.filter(item => item.uid !== id))
458
+ }
459
+ }
460
+
461
+ return (
462
+ <div>
463
+ <Group justify="space-between" mb="md">
464
+ <Title order={2}>${m}s</Title>
465
+ <Button onClick={() => navigate('/${m.toLowerCase()}s/new')}>Create ${m}</Button>
466
+ </Group>
467
+ <Table>
468
+ <Table.Thead>
469
+ <Table.Tr>
470
+ <Table.Th>Name</Table.Th>
471
+ <Table.Th>Status</Table.Th>
472
+ <Table.Th>Actions</Table.Th>
473
+ </Table.Tr>
474
+ </Table.Thead>
475
+ <Table.Tbody>
476
+ {data.map(item => (
477
+ <Table.Tr key={item.uid}>
478
+ <Table.Td>{item.name || item.uid}</Table.Td>
479
+ <Table.Td>{item.status}</Table.Td>
480
+ <Table.Td>
481
+ <Group>
482
+ <Button size="xs" variant="outline" onClick={() => navigate(\`/${m.toLowerCase()}s/\${item.uid}\`)}>Edit</Button>
483
+ <Button size="xs" color="red" variant="outline" onClick={() => handleDelete(item.uid)}>Delete</Button>
484
+ </Group>
485
+ </Table.Td>
486
+ </Table.Tr>
487
+ ))}
488
+ </Table.Tbody>
489
+ </Table>
490
+ </div>
491
+ )
492
+ }
493
+ `;
494
+ fs.writeFileSync(path.join(pagesDir, `${m}List.tsx`), listTsx);
495
+ const formTsx = `import { useEffect, useState } from 'react'
496
+ import { TextInput, Button, Group, Title, Paper, Select } from '@mantine/core'
497
+ import { useNavigate, useParams } from 'react-router-dom'
498
+ import { api } from '../api'
499
+
500
+ export function ${m}Form() {
501
+ const { id } = useParams()
502
+ const navigate = useNavigate()
503
+ const [formData, setFormData] = useState<any>({ status: 'created' })
504
+
505
+ useEffect(() => {
506
+ if (id && id !== 'new') {
507
+ api.get('${m.toLowerCase()}s/' + id).then(res => {
508
+ if (res && res.data) {
509
+ setFormData(res.data)
510
+ }
511
+ })
512
+ }
513
+ }, [id])
514
+
515
+ const handleSubmit = async (e: React.FormEvent) => {
516
+ e.preventDefault()
517
+ if (id === 'new') {
518
+ await api.post('${m.toLowerCase()}s', formData)
519
+ } else {
520
+ await api.patch('${m.toLowerCase()}s/' + id, formData)
521
+ }
522
+ navigate('/${m.toLowerCase()}s')
523
+ }
524
+
525
+ return (
526
+ <Paper p="md" shadow="sm">
527
+ <Title order={2} mb="md">{id === 'new' ? \`Create ${m}\` : \`Edit ${m}\`}</Title>
528
+ <form onSubmit={handleSubmit}>
529
+ <TextInput
530
+ label="Name"
531
+ value={formData.name || ''}
532
+ onChange={e => setFormData({...formData, name: e.target.value})}
533
+ mb="sm"
534
+ />
535
+ <Select
536
+ label="Status"
537
+ data={['created', 'pending', 'active', 'deleted']}
538
+ value={formData.status || 'created'}
539
+ onChange={val => setFormData({...formData, status: val})}
540
+ mb="md"
541
+ />
542
+ <Group>
543
+ <Button type="submit">Save</Button>
544
+ <Button variant="outline" onClick={() => navigate('/${m.toLowerCase()}s')}>Cancel</Button>
545
+ </Group>
546
+ </form>
547
+ </Paper>
548
+ )
549
+ }
550
+ `;
551
+ fs.writeFileSync(path.join(pagesDir, `${m}Form.tsx`), formTsx);
552
+ }
553
+ const dockerfile = `FROM node:22-alpine as builder
554
+ WORKDIR /app
555
+ COPY package*.json ./
556
+ RUN yarn install
557
+ COPY . .
558
+ RUN yarn build
559
+
560
+ FROM nginx:alpine
561
+ COPY --from=builder /app/dist /usr/share/nginx/html
562
+ COPY nginx.conf /etc/nginx/conf.d/default.conf
563
+ EXPOSE 80
564
+ CMD ["nginx", "-g", "daemon off;"]
565
+ `;
566
+ fs.writeFileSync(path.join(webDir, 'Dockerfile'), dockerfile);
567
+ const nginxConf = `server {
568
+ listen 80;
569
+ server_name localhost;
570
+
571
+ location / {
572
+ root /usr/share/nginx/html;
573
+ index index.html index.htm;
574
+ try_files $uri $uri/ /index.html;
575
+ }
576
+
577
+ location /api/ {
578
+ proxy_pass http://api:4001/;
579
+ proxy_http_version 1.1;
580
+ proxy_set_header Upgrade $http_upgrade;
581
+ proxy_set_header Connection 'upgrade';
582
+ proxy_set_header Host $host;
583
+ proxy_cache_bypass $http_upgrade;
584
+ }
585
+ }
586
+ `;
587
+ fs.writeFileSync(path.join(webDir, 'nginx.conf'), nginxConf);
249
588
  }
250
589
  }
251
590
  exports.CodeGenerator = CodeGenerator;
@@ -1 +1 @@
1
- {"version":3,"file":"CodeGenerator.js","sourceRoot":"","sources":["../src/CodeGenerator.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"CodeGenerator.js","sourceRoot":"","sources":["../src/CodeGenerator.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"InfraBuilder.d.ts","sourceRoot":"","sources":["../src/InfraBuilder.ts"],"names":[],"mappings":"AAEA,MAAM,WAAW,cAAc;IAC5B,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,KAAK,CAAC,EAAE,MAAM,GAAG;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,UAAU,CAAC,EAAE,MAAM,CAAA;KAAE,CAAA;IACzD,cAAc,CAAC,EAAE,MAAM,CAAA;IACvB,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,KAAK,CAAC,EAAE,MAAM,EAAE,CAAA;IAChB,WAAW,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IACpC,OAAO,CAAC,EAAE,MAAM,EAAE,CAAA;IAClB,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,UAAU,CAAC,EAAE,MAAM,EAAE,CAAA;CACvB;AAED,MAAM,WAAW,WAAW;IACzB,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,cAAc,CAAC,CAAA;IACxC,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAA;CAC/B;AAED,qBAAa,YAAY;WAIR,KAAK,CAAC,MAAM,EAAE,GAAG,EAAE,OAAO,GAAE,MAAuB,GAAG;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,MAAM,CAAC;QAAC,UAAU,EAAE,MAAM,CAAA;KAAE;CAoH1H"}
1
+ {"version":3,"file":"InfraBuilder.d.ts","sourceRoot":"","sources":["../src/InfraBuilder.ts"],"names":[],"mappings":"AAEA,MAAM,WAAW,cAAc;IAC5B,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,KAAK,CAAC,EAAE,MAAM,GAAG;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,UAAU,CAAC,EAAE,MAAM,CAAA;KAAE,CAAA;IACzD,cAAc,CAAC,EAAE,MAAM,CAAA;IACvB,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,KAAK,CAAC,EAAE,MAAM,EAAE,CAAA;IAChB,WAAW,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IACpC,OAAO,CAAC,EAAE,MAAM,EAAE,CAAA;IAClB,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,UAAU,CAAC,EAAE,MAAM,EAAE,CAAA;CACvB;AAED,MAAM,WAAW,WAAW;IACzB,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,cAAc,CAAC,CAAA;IACxC,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAA;CAC/B;AAED,qBAAa,YAAY;WAIR,KAAK,CAAC,MAAM,EAAE,GAAG,EAAE,OAAO,GAAE,MAAuB,GAAG;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,MAAM,CAAC;QAAC,UAAU,EAAE,MAAM,CAAA;KAAE;CAgI1H"}
@@ -49,13 +49,23 @@ class InfraBuilder {
49
49
  },
50
50
  container_name: `${appName}-engine`,
51
51
  restart: 'unless-stopped',
52
- ports: ['3000:3000', '4001:4000'],
52
+ ports: ['4001:4001'],
53
53
  environment: {
54
54
  NODE_ENV: 'production'
55
55
  },
56
56
  volumes: ['./quatrain.json:/app/quatrain.json:ro'],
57
57
  depends_on: []
58
58
  };
59
+ compose.services['ui'] = {
60
+ build: {
61
+ context: './web',
62
+ dockerfile: 'Dockerfile'
63
+ },
64
+ container_name: `${appName}-ui`,
65
+ restart: 'unless-stopped',
66
+ ports: ['3000:80'],
67
+ depends_on: ['engine']
68
+ };
59
69
  if (config.backend && config.backend.adapter === 'PostgresAdapter') {
60
70
  const dbUser = 'quatrain';
61
71
  const dbPass = 'quatrain_pass';
@@ -1 +1 @@
1
- {"version":3,"file":"InfraBuilder.js","sourceRoot":"","sources":["../src/InfraBuilder.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"InfraBuilder.js","sourceRoot":"","sources":["../src/InfraBuilder.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@quatrain/app",
3
- "version": "1.1.1",
3
+ "version": "1.1.2",
4
4
  "license": "AGPL-3.0-only",
5
5
  "description": "Quatrain App Bootloader and configuration helpers",
6
6
  "main": "dist/index.js",
@@ -20,13 +20,13 @@
20
20
  },
21
21
  "author": "Quatrain Développement SAS <developers@quatrain.com>",
22
22
  "dependencies": {
23
- "@quatrain/auth": "^1.2.0",
24
- "@quatrain/backend": "^1.2.0",
25
- "@quatrain/core": "^1.2.0",
26
- "@quatrain/log": "^1.2.0",
27
- "@quatrain/messaging": "^1.1.0",
28
- "@quatrain/queue": "^1.2.0",
29
- "@quatrain/storage": "^1.2.0",
23
+ "@quatrain/auth": "^1.2.1",
24
+ "@quatrain/backend": "^1.2.1",
25
+ "@quatrain/core": "^1.2.1",
26
+ "@quatrain/log": "^1.2.1",
27
+ "@quatrain/messaging": "^1.1.1",
28
+ "@quatrain/queue": "^1.2.1",
29
+ "@quatrain/storage": "^1.2.1",
30
30
  "yaml": "^2.4.0"
31
31
  },
32
32
  "devDependencies": {
@@ -25,6 +25,8 @@ export class CodeGenerator {
25
25
  this.generateApiEndpoints(config, fullTargetDir, generatedModels)
26
26
  this.generateIndex(config, fullTargetDir, generatedModels)
27
27
  this.generateMigration(config, fullTargetDir)
28
+
29
+ this.generateFrontend(config, fullTargetDir, generatedModels)
28
30
 
29
31
  Log.info(`[CodeGenerator] Application générée avec succès dans ${fullTargetDir}`)
30
32
  }
@@ -41,7 +43,8 @@ export class CodeGenerator {
41
43
  main: "src/index.ts",
42
44
  type: "module",
43
45
  scripts: {
44
- start: "bun run src/index.ts"
46
+ "start:dev": "NODE_ENV=development bun run --watch src/index.ts",
47
+ "start:prod": "NODE_ENV=production bun run src/index.ts"
45
48
  },
46
49
  dependencies: {
47
50
  "@quatrain/core": targetNpmTag,
@@ -92,17 +95,23 @@ export class CodeGenerator {
92
95
  const collectionName = model.collectionName || className.toLowerCase()
93
96
  generatedNames.push(className)
94
97
 
98
+ // Helper to map raw types to Property classes
99
+ const mapTypeToClass = (type: string) => {
100
+ if (type.toLowerCase() === 'datetime') return 'DateTimeProperty'
101
+ return type.charAt(0).toUpperCase() + type.slice(1) + 'Property'
102
+ }
103
+
95
104
  // Use imports from core properties
96
105
  const propsCode = model.properties.map((p: any) => {
97
- const propType = p.type // e.g. "StringProperty"
106
+ const propClass = mapTypeToClass(p.type)
98
107
  return ` {
99
108
  name: '${p.name}',
100
- type: ${propType}.TYPE,
109
+ type: ${propClass}.TYPE,
101
110
  mandatory: ${p.options?.mandatory ? 'true' : 'false'}
102
111
  }`
103
112
  }).join(',\n')
104
113
 
105
- const propTypesToImport = Array.from(new Set(model.properties.map((p: any) => p.type)))
114
+ const propTypesToImport = Array.from(new Set(model.properties.map((p: any) => mapTypeToClass(p.type))))
106
115
 
107
116
  const modelCode = `import { PersistedBaseObject } from '@quatrain/backend'
108
117
  import { ${propTypesToImport.join(', ')} } from '@quatrain/core'
@@ -131,11 +140,7 @@ export class ${className} extends PersistedBaseObject {
131
140
  const apiCode = `import { CrudEndpoint } from '@quatrain/api-server'
132
141
  import { ${className} } from '../models/${className}'
133
142
 
134
- export class ${className}Api extends CrudEndpoint {
135
- constructor() {
136
- super('/api/${className.toLowerCase()}s', ${className})
137
- }
138
- }
143
+ export const ${className}Api = CrudEndpoint(${className})
139
144
  `
140
145
  fs.writeFileSync(path.resolve(targetDir, `src/api/${className}Api.ts`), apiCode)
141
146
  }
@@ -147,28 +152,32 @@ export class ${className}Api extends CrudEndpoint {
147
152
 
148
153
  const modelImports = models.map(m => `import { ${m} } from './models/${m}'`).join('\n')
149
154
  const apiImports = models.map(m => `import { ${m}Api } from './api/${m}Api'`).join('\n')
150
- const apiRegisters = models.map(m => ` server.addEndpoint(new ${m}Api())`).join('\n')
155
+ const apiRegisters = models.map(m => ` server.addEndpoint(${m}Api, '/api/${m.toLowerCase()}s')`).join('\n')
151
156
 
152
157
  const indexCode = `import * as path from 'path'
153
158
  import { Backend, InjectMetaMiddleware } from '@quatrain/backend'
154
159
  import { ${adapterClass} } from '${adapterImport}'
155
160
  import { ExpressAdapter } from '@quatrain/api-server'
156
161
  import { MigrationManager } from '@quatrain/backend-migrations'
162
+ import { Log, DefaultLoggerAdapter, LogLevel } from '@quatrain/log'
157
163
  ${config.authMode === 'oauth' ? `import { AuthOIDC } from '@quatrain/auth-oidc'\n` : ''}
158
164
  ${modelImports}
159
165
  ${apiImports}
160
166
 
161
167
  ;(async () => {
162
168
  try {
169
+ const isProd = process.env.NODE_ENV === 'production'
170
+ Log.addLogger('default', new DefaultLoggerAdapter('', isProd ? LogLevel.INFO : LogLevel.DEBUG), true)
171
+
163
172
  const adapter = new ${adapterClass}({
164
173
  config: ${adapterClass === 'SQLiteAdapter' ? '{ database: path.resolve(process.cwd(), "data/app.sqlite") }' : 'process.env.DATABASE_URL'},
165
174
  middlewares: [new InjectMetaMiddleware()]
166
175
  })
167
176
 
168
- Backend.init(adapter)
177
+ Backend.addBackend(adapter, 'default', true)
169
178
 
170
- const mm = new MigrationManager()
171
- await mm.up()
179
+ const mm = new MigrationManager(adapter)
180
+ await mm.executeMigrations()
172
181
 
173
182
  const server = new ExpressAdapter()
174
183
 
@@ -176,7 +185,7 @@ ${apiRegisters}
176
185
 
177
186
  ${config.authMode === 'oauth' ? ` // Initialize OIDC Auth
178
187
  const oidc = AuthOIDC.init('http://localhost:4001')
179
- server.getApp().use('/oidc', oidc.callback())
188
+ server.getNativeInstance().use('/oidc', oidc.callback())
180
189
  ` : ''}
181
190
 
182
191
  const PORT = Number(process.env.PORT) || 4001
@@ -202,22 +211,33 @@ ${config.authMode === 'oauth' ? ` // Initialize OIDC Auth
202
211
  if (!model.name) continue
203
212
  const collectionName = model.collectionName || model.name.toLowerCase()
204
213
 
205
- const columns = model.properties.map((p: any) => {
206
- // Very simplified SQLite type mapping for the PoC
207
- let sqlType = 'TEXT'
208
- if (p.type === 'NumberProperty' || p.type === 'BooleanProperty') sqlType = 'INTEGER'
209
- return `${p.name} ${sqlType}`
210
- }).join(',\n ')
214
+ const baseColumns = ['id', 'name', 'status', 'createdby', 'createdat', 'updatedby', 'updatedat', 'deletedby', 'deletedat']
215
+
216
+ const customColumns = model.properties
217
+ .filter((p: any) => !baseColumns.includes(p.name.toLowerCase()))
218
+ .map((p: any) => {
219
+ // Very simplified SQLite type mapping for the PoC
220
+ let sqlType = 'TEXT'
221
+ if (p.type === 'NumberProperty' || p.type === 'BooleanProperty') sqlType = 'INTEGER'
222
+ return `${p.name.toLowerCase()} ${sqlType}`
223
+ })
224
+
225
+ const allColumns = [
226
+ 'id TEXT PRIMARY KEY',
227
+ 'name TEXT',
228
+ 'status TEXT',
229
+ 'createdat INTEGER',
230
+ 'updatedat INTEGER',
231
+ 'deletedat INTEGER',
232
+ 'createdby TEXT',
233
+ 'updatedby TEXT',
234
+ 'deletedby TEXT',
235
+ ...customColumns
236
+ ].join(',\n ')
211
237
 
212
238
  upQueries += `
213
239
  await adapter.rawQuery(\`CREATE TABLE IF NOT EXISTS ${collectionName} (
214
- id TEXT PRIMARY KEY,
215
- status TEXT,
216
- created_at TEXT,
217
- updated_at TEXT,
218
- created_by TEXT,
219
- updated_by TEXT,
220
- ${columns}
240
+ ${allColumns}
221
241
  )\`)\n`
222
242
 
223
243
  downQueries += `
@@ -240,7 +260,364 @@ export const down = async ({ context: adapter }: { context: AbstractBackendAdapt
240
260
  }
241
261
  }
242
262
  `
263
+ const migrationsDir = path.resolve(targetDir, 'data/migrations/default')
264
+ // Clear old migrations to prevent executing buggy or duplicate past generations
265
+ if (fs.existsSync(migrationsDir)) {
266
+ fs.readdirSync(migrationsDir).forEach(file => {
267
+ if (file.endsWith('.ts')) {
268
+ fs.unlinkSync(path.join(migrationsDir, file))
269
+ }
270
+ })
271
+ }
272
+
243
273
  const timestamp = new Date().toISOString().replace(/[-:T]/g, '').slice(0, 14)
244
- fs.writeFileSync(path.resolve(targetDir, `data/migrations/default/${timestamp}_init.ts`), migrationCode)
274
+ fs.writeFileSync(path.join(migrationsDir, `${timestamp}_init.ts`), migrationCode)
275
+ }
276
+
277
+ private static generateFrontend(config: any, targetDir: string, models: string[]): void {
278
+ const webDir = path.resolve(targetDir, 'web')
279
+ if (!fs.existsSync(webDir)) {
280
+ fs.mkdirSync(webDir, { recursive: true })
281
+ }
282
+ const srcDir = path.resolve(webDir, 'src')
283
+ if (!fs.existsSync(srcDir)) {
284
+ fs.mkdirSync(srcDir, { recursive: true })
285
+ }
286
+ const pagesDir = path.resolve(srcDir, 'pages')
287
+ if (!fs.existsSync(pagesDir)) {
288
+ fs.mkdirSync(pagesDir, { recursive: true })
289
+ }
290
+
291
+ // package.json
292
+ const packageJson = {
293
+ name: "quatrain-ui-front",
294
+ private: true,
295
+ version: "1.0.0",
296
+ type: "module",
297
+ scripts: {
298
+ "dev": "vite",
299
+ "build": "tsc && vite build",
300
+ "preview": "vite preview"
301
+ },
302
+ dependencies: {
303
+ "@mantine/core": "^7.11.1",
304
+ "@mantine/hooks": "^7.11.1",
305
+ "@quatrain/api-client": "latest-dev",
306
+ "react": "^18.3.1",
307
+ "react-dom": "^18.3.1",
308
+ "react-router-dom": "^6.24.1"
309
+ },
310
+ devDependencies: {
311
+ "@types/react": "^18.3.3",
312
+ "@types/react-dom": "^18.3.0",
313
+ "@vitejs/plugin-react": "^4.3.1",
314
+ "typescript": "^5.5.3",
315
+ "vite": "^5.3.4"
316
+ }
317
+ }
318
+ fs.writeFileSync(path.join(webDir, 'package.json'), JSON.stringify(packageJson, null, 2))
319
+
320
+ // tsconfig.json
321
+ const tsConfig = {
322
+ compilerOptions: {
323
+ target: "ES2020",
324
+ useDefineForClassFields: true,
325
+ lib: ["ES2020", "DOM", "DOM.Iterable"],
326
+ module: "ESNext",
327
+ skipLibCheck: true,
328
+ moduleResolution: "bundler",
329
+ allowImportingTsExtensions: true,
330
+ resolveJsonModule: true,
331
+ isolatedModules: true,
332
+ noEmit: true,
333
+ jsx: "react-jsx",
334
+ strict: true,
335
+ noUnusedLocals: true,
336
+ noUnusedParameters: true,
337
+ noFallthroughCasesInSwitch: true
338
+ },
339
+ include: ["src"]
340
+ }
341
+ fs.writeFileSync(path.join(webDir, 'tsconfig.json'), JSON.stringify(tsConfig, null, 2))
342
+
343
+ // vite.config.ts
344
+ const viteConfig = `import { defineConfig } from 'vite'
345
+ import react from '@vitejs/plugin-react'
346
+
347
+ // https://vitejs.dev/config/
348
+ export default defineConfig({
349
+ plugins: [react()],
350
+ server: {
351
+ host: '0.0.0.0',
352
+ port: 3000,
353
+ proxy: {
354
+ '/api': {
355
+ target: 'http://localhost:4001',
356
+ changeOrigin: true
357
+ }
358
+ }
359
+ }
360
+ })
361
+ `
362
+ fs.writeFileSync(path.join(webDir, 'vite.config.ts'), viteConfig)
363
+
364
+ // index.html
365
+ const indexHtml = `<!doctype html>
366
+ <html lang="en">
367
+ <head>
368
+ <meta charset="UTF-8" />
369
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
370
+ <title>Quatrain App</title>
371
+ </head>
372
+ <body>
373
+ <div id="root"></div>
374
+ <script type="module" src="/src/main.tsx"></script>
375
+ </body>
376
+ </html>
377
+ `
378
+ fs.writeFileSync(path.join(webDir, 'index.html'), indexHtml)
379
+
380
+ // main.tsx
381
+ const mainTsx = `import React from 'react'
382
+ import ReactDOM from 'react-dom/client'
383
+ import { MantineProvider } from '@mantine/core'
384
+ import '@mantine/core/styles.css'
385
+ import { BrowserRouter } from 'react-router-dom'
386
+ import App from './App.tsx'
387
+
388
+ ReactDOM.createRoot(document.getElementById('root')!).render(
389
+ <React.StrictMode>
390
+ <MantineProvider defaultColorScheme="dark">
391
+ <BrowserRouter>
392
+ <App />
393
+ </BrowserRouter>
394
+ </MantineProvider>
395
+ </React.StrictMode>,
396
+ )
397
+ `
398
+ fs.writeFileSync(path.join(srcDir, 'main.tsx'), mainTsx)
399
+
400
+ // api.ts
401
+ const apiTs = `import { ApiClient } from '@quatrain/api-client'
402
+
403
+ export const api = new ApiClient('/api')
404
+ `
405
+ fs.writeFileSync(path.join(srcDir, 'api.ts'), apiTs)
406
+
407
+ // App.tsx
408
+ const menuItems = models.map(m =>
409
+ ` <a href="/${m.toLowerCase()}s" style={{display: 'block', padding: '10px', color: 'white', textDecoration: 'none'}}>${m}s</a>`
410
+ ).join('\n')
411
+
412
+ const routes = models.map(m =>
413
+ ` <Route path="/${m.toLowerCase()}s" element={<${m}List />} />\n <Route path="/${m.toLowerCase()}s/:id" element={<${m}Form />} />`
414
+ ).join('\n')
415
+
416
+ const imports = models.map(m =>
417
+ `import { ${m}List } from './pages/${m}List'\nimport { ${m}Form } from './pages/${m}Form'`
418
+ ).join('\n')
419
+
420
+ const appTsx = `import { AppShell, Burger, Group, Title } from '@mantine/core'
421
+ import { useDisclosure } from '@mantine/hooks'
422
+ import { Routes, Route } from 'react-router-dom'
423
+ ${imports}
424
+
425
+ export default function App() {
426
+ const [opened, { toggle }] = useDisclosure()
427
+
428
+ return (
429
+ <AppShell
430
+ header={{ height: 60 }}
431
+ navbar={{
432
+ width: 300,
433
+ breakpoint: 'sm',
434
+ collapsed: { mobile: !opened },
435
+ }}
436
+ padding="md"
437
+ >
438
+ <AppShell.Header>
439
+ <Group h="100%" px="md">
440
+ <Burger opened={opened} onClick={toggle} hiddenFrom="sm" size="sm" />
441
+ <Title order={3}>Quatrain App</Title>
442
+ </Group>
443
+ </AppShell.Header>
444
+
445
+ <AppShell.Navbar p="md">
446
+ ${menuItems}
447
+ </AppShell.Navbar>
448
+
449
+ <AppShell.Main>
450
+ <Routes>
451
+ ${routes}
452
+ <Route path="/" element={<div>Welcome to your Quatrain Application</div>} />
453
+ </Routes>
454
+ </AppShell.Main>
455
+ </AppShell>
456
+ )
457
+ }
458
+ `
459
+ fs.writeFileSync(path.join(srcDir, 'App.tsx'), appTsx)
460
+
461
+ // Generate CRUD pages
462
+ for (const m of models) {
463
+ const modelConfig = config.models.find((mod: any) => mod.name === m)
464
+ const props = modelConfig?.properties || []
465
+
466
+ const listTsx = `import { useEffect, useState } from 'react'
467
+ import { Table, Button, Group, Title } from '@mantine/core'
468
+ import { useNavigate } from 'react-router-dom'
469
+ import { api } from '../api'
470
+
471
+ export function ${m}List() {
472
+ const [data, setData] = useState<any[]>([])
473
+ const navigate = useNavigate()
474
+
475
+ useEffect(() => {
476
+ api.get('${m.toLowerCase()}s').then((res) => {
477
+ if (res && res.data) {
478
+ setData(res.data)
479
+ }
480
+ })
481
+ }, [])
482
+
483
+ const handleDelete = async (id: string) => {
484
+ if (confirm('Are you sure?')) {
485
+ await api.delete('${m.toLowerCase()}s/' + id, {})
486
+ setData(data.filter(item => item.uid !== id))
487
+ }
488
+ }
489
+
490
+ return (
491
+ <div>
492
+ <Group justify="space-between" mb="md">
493
+ <Title order={2}>${m}s</Title>
494
+ <Button onClick={() => navigate('/${m.toLowerCase()}s/new')}>Create ${m}</Button>
495
+ </Group>
496
+ <Table>
497
+ <Table.Thead>
498
+ <Table.Tr>
499
+ <Table.Th>Name</Table.Th>
500
+ <Table.Th>Status</Table.Th>
501
+ <Table.Th>Actions</Table.Th>
502
+ </Table.Tr>
503
+ </Table.Thead>
504
+ <Table.Tbody>
505
+ {data.map(item => (
506
+ <Table.Tr key={item.uid}>
507
+ <Table.Td>{item.name || item.uid}</Table.Td>
508
+ <Table.Td>{item.status}</Table.Td>
509
+ <Table.Td>
510
+ <Group>
511
+ <Button size="xs" variant="outline" onClick={() => navigate(\`/${m.toLowerCase()}s/\${item.uid}\`)}>Edit</Button>
512
+ <Button size="xs" color="red" variant="outline" onClick={() => handleDelete(item.uid)}>Delete</Button>
513
+ </Group>
514
+ </Table.Td>
515
+ </Table.Tr>
516
+ ))}
517
+ </Table.Tbody>
518
+ </Table>
519
+ </div>
520
+ )
521
+ }
522
+ `
523
+ fs.writeFileSync(path.join(pagesDir, `${m}List.tsx`), listTsx)
524
+
525
+ const formTsx = `import { useEffect, useState } from 'react'
526
+ import { TextInput, Button, Group, Title, Paper, Select } from '@mantine/core'
527
+ import { useNavigate, useParams } from 'react-router-dom'
528
+ import { api } from '../api'
529
+
530
+ export function ${m}Form() {
531
+ const { id } = useParams()
532
+ const navigate = useNavigate()
533
+ const [formData, setFormData] = useState<any>({ status: 'created' })
534
+
535
+ useEffect(() => {
536
+ if (id && id !== 'new') {
537
+ api.get('${m.toLowerCase()}s/' + id).then(res => {
538
+ if (res && res.data) {
539
+ setFormData(res.data)
540
+ }
541
+ })
542
+ }
543
+ }, [id])
544
+
545
+ const handleSubmit = async (e: React.FormEvent) => {
546
+ e.preventDefault()
547
+ if (id === 'new') {
548
+ await api.post('${m.toLowerCase()}s', formData)
549
+ } else {
550
+ await api.patch('${m.toLowerCase()}s/' + id, formData)
551
+ }
552
+ navigate('/${m.toLowerCase()}s')
553
+ }
554
+
555
+ return (
556
+ <Paper p="md" shadow="sm">
557
+ <Title order={2} mb="md">{id === 'new' ? \`Create ${m}\` : \`Edit ${m}\`}</Title>
558
+ <form onSubmit={handleSubmit}>
559
+ <TextInput
560
+ label="Name"
561
+ value={formData.name || ''}
562
+ onChange={e => setFormData({...formData, name: e.target.value})}
563
+ mb="sm"
564
+ />
565
+ <Select
566
+ label="Status"
567
+ data={['created', 'pending', 'active', 'deleted']}
568
+ value={formData.status || 'created'}
569
+ onChange={val => setFormData({...formData, status: val})}
570
+ mb="md"
571
+ />
572
+ <Group>
573
+ <Button type="submit">Save</Button>
574
+ <Button variant="outline" onClick={() => navigate('/${m.toLowerCase()}s')}>Cancel</Button>
575
+ </Group>
576
+ </form>
577
+ </Paper>
578
+ )
579
+ }
580
+ `
581
+ fs.writeFileSync(path.join(pagesDir, `${m}Form.tsx`), formTsx)
582
+ }
583
+
584
+ // Dockerfile
585
+ const dockerfile = `FROM node:22-alpine as builder
586
+ WORKDIR /app
587
+ COPY package*.json ./
588
+ RUN yarn install
589
+ COPY . .
590
+ RUN yarn build
591
+
592
+ FROM nginx:alpine
593
+ COPY --from=builder /app/dist /usr/share/nginx/html
594
+ COPY nginx.conf /etc/nginx/conf.d/default.conf
595
+ EXPOSE 80
596
+ CMD ["nginx", "-g", "daemon off;"]
597
+ `
598
+ fs.writeFileSync(path.join(webDir, 'Dockerfile'), dockerfile)
599
+
600
+ // nginx.conf
601
+ const nginxConf = `server {
602
+ listen 80;
603
+ server_name localhost;
604
+
605
+ location / {
606
+ root /usr/share/nginx/html;
607
+ index index.html index.htm;
608
+ try_files $uri $uri/ /index.html;
609
+ }
610
+
611
+ location /api/ {
612
+ proxy_pass http://api:4001/;
613
+ proxy_http_version 1.1;
614
+ proxy_set_header Upgrade $http_upgrade;
615
+ proxy_set_header Connection 'upgrade';
616
+ proxy_set_header Host $host;
617
+ proxy_cache_bypass $http_upgrade;
618
+ }
619
+ }
620
+ `
621
+ fs.writeFileSync(path.join(webDir, 'nginx.conf'), nginxConf)
245
622
  }
246
623
  }
@@ -30,7 +30,7 @@ export class InfraBuilder {
30
30
 
31
31
  const envVars: Record<string, string> = {}
32
32
 
33
- // 1. Unified Engine Container (API + Front)
33
+ // 1. Backend Engine Container
34
34
  compose.services['engine'] = {
35
35
  build: {
36
36
  context: '.',
@@ -38,7 +38,7 @@ export class InfraBuilder {
38
38
  },
39
39
  container_name: `${appName}-engine`,
40
40
  restart: 'unless-stopped',
41
- ports: ['3000:3000', '4001:4000'],
41
+ ports: ['4001:4001'],
42
42
  environment: {
43
43
  NODE_ENV: 'production'
44
44
  },
@@ -46,6 +46,18 @@ export class InfraBuilder {
46
46
  depends_on: []
47
47
  }
48
48
 
49
+ // 1b. Frontend UI Container
50
+ compose.services['ui'] = {
51
+ build: {
52
+ context: './web',
53
+ dockerfile: 'Dockerfile'
54
+ },
55
+ container_name: `${appName}-ui`,
56
+ restart: 'unless-stopped',
57
+ ports: ['3000:80'],
58
+ depends_on: ['engine']
59
+ }
60
+
49
61
  // 2. Backend Infrastructure
50
62
  if (config.backend && config.backend.adapter === 'PostgresAdapter') {
51
63
  const dbUser = 'quatrain'