@quatrain/app 1.1.1 → 1.1.3

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