@mostajs/setup 2.1.25 → 2.1.28
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/README.md +45 -1142
- package/dist/api/routes.js +25 -1
- package/dist/components/SetupWizard.js +1 -1
- package/dist/lib/load-setup-json.js +1 -1
- package/dist/lib/setup.js +76 -20
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,1181 +1,84 @@
|
|
|
1
1
|
# @mostajs/setup
|
|
2
2
|
|
|
3
|
-
>
|
|
3
|
+
> Setup wizard — multi-dialect DB config, schema management, seed runner, module discovery.
|
|
4
|
+
> Author: Dr Hamid MADANI drmdh@msn.com
|
|
4
5
|
|
|
5
|
-
|
|
6
|
-
[](LICENSE)
|
|
7
|
-
|
|
8
|
-
Part of the [@mosta suite](https://mostajs.dev).
|
|
9
|
-
|
|
10
|
-
---
|
|
11
|
-
|
|
12
|
-
## Table des matieres
|
|
13
|
-
|
|
14
|
-
1. [Installation](#1-installation)
|
|
15
|
-
2. [Architecture du package](#2-architecture-du-package)
|
|
16
|
-
3. [Didacticiel : integrer @mostajs/setup dans une nouvelle app](#3-didacticiel--integrer-mostajssetup-dans-une-nouvelle-app)
|
|
17
|
-
- [Etape 1 — Installation des dependances](#etape-1--installation-des-dependances)
|
|
18
|
-
- [Etape 2 — Configurer le setup (lib/setup.ts)](#etape-2--configurer-le-setup-libsetupts)
|
|
19
|
-
- [Etape 3 — Creer les routes API](#etape-3--creer-les-routes-api)
|
|
20
|
-
- [Etape 4 — Creer la page Setup (frontend)](#etape-4--creer-la-page-setup-frontend)
|
|
21
|
-
- [Etape 5 — Middleware : rediriger vers /setup](#etape-5--middleware--rediriger-vers-setup)
|
|
22
|
-
- [Etape 6 — Tester le flux complet](#etape-6--tester-le-flux-complet)
|
|
23
|
-
4. [API Reference](#4-api-reference)
|
|
24
|
-
5. [Les 13 dialectes supportes](#5-les-13-dialectes-supportes)
|
|
25
|
-
6. [Systeme de modules](#6-systeme-de-modules)
|
|
26
|
-
7. [Exemples avances](#7-exemples-avances)
|
|
27
|
-
8. [Reconfiguration (post-installation)](#8-reconfiguration-post-installation)
|
|
28
|
-
9. [Mode declaratif : setup.json](#9-mode-declaratif--setupjson)
|
|
29
|
-
10. [FAQ / Troubleshooting](#10-faq--troubleshooting)
|
|
30
|
-
|
|
31
|
-
---
|
|
32
|
-
|
|
33
|
-
## 1. Installation
|
|
6
|
+
## Install
|
|
34
7
|
|
|
35
8
|
```bash
|
|
36
9
|
npm install @mostajs/setup @mostajs/orm
|
|
37
10
|
```
|
|
38
11
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
---
|
|
42
|
-
|
|
43
|
-
## 2. Architecture du package
|
|
44
|
-
|
|
45
|
-
```
|
|
46
|
-
@mostajs/setup
|
|
47
|
-
├── data/
|
|
48
|
-
│ ├── dialects.ts # Metadata des 13 SGBD (icone, port, driver)
|
|
49
|
-
│ └── module-definitions.ts # Liste statique des modules @mostajs/*
|
|
50
|
-
├── lib/
|
|
51
|
-
│ ├── setup.ts # needsSetup() + runInstall()
|
|
52
|
-
│ ├── compose-uri.ts # Compose URI de connexion (mongo, pg, mysql...)
|
|
53
|
-
│ ├── db-test.ts # Test de connexion ephemere
|
|
54
|
-
│ ├── env-writer.ts # Ecriture/maj de .env.local
|
|
55
|
-
│ └── discover-modules.ts # Decouverte npm dynamique
|
|
56
|
-
├── api/
|
|
57
|
-
│ ├── status.route.ts # Factory GET /api/setup/status
|
|
58
|
-
│ ├── test-db.route.ts # Factory POST /api/setup/test-db
|
|
59
|
-
│ ├── install.route.ts # Factory POST /api/setup/install
|
|
60
|
-
│ ├── detect-modules.route.ts # Factory GET /api/setup/detect-modules
|
|
61
|
-
│ ├── install-modules.route.ts # Factory POST /api/setup/install-modules
|
|
62
|
-
│ ├── reconfig.route.ts # Factory GET+POST /api/setup/reconfig
|
|
63
|
-
│ └── upload-jar.route.ts # Factory GET+POST+DELETE /api/setup/upload-jar
|
|
64
|
-
├── components/
|
|
65
|
-
│ ├── SetupWizard.tsx # Wizard d'installation complet (6 etapes)
|
|
66
|
-
│ └── ReconfigPanel.tsx # UI reconfiguration (modules + DB)
|
|
67
|
-
├── types/
|
|
68
|
-
│ └── index.ts # Tous les types TypeScript
|
|
69
|
-
└── index.ts # Barrel exports
|
|
70
|
-
```
|
|
71
|
-
|
|
72
|
-
**Principe** : le package fournit des **factory functions** qui retournent des handlers `{ GET }` ou `{ POST }`. Votre app Next.js les expose en une ligne par route.
|
|
73
|
-
|
|
74
|
-
---
|
|
75
|
-
|
|
76
|
-
## 3. Didacticiel : integrer @mostajs/setup dans une nouvelle app
|
|
77
|
-
|
|
78
|
-
### Prerequis
|
|
79
|
-
|
|
80
|
-
- Next.js 14+ (App Router)
|
|
81
|
-
- Node.js >= 18
|
|
82
|
-
- `@mostajs/orm` installe
|
|
83
|
-
|
|
84
|
-
### Etape 1 — Installation des dependances
|
|
85
|
-
|
|
86
|
-
```bash
|
|
87
|
-
npm install @mostajs/setup @mostajs/orm bcryptjs
|
|
88
|
-
npm install -D @types/bcryptjs
|
|
89
|
-
```
|
|
90
|
-
|
|
91
|
-
### Etape 2 — Configurer le setup (`lib/setup.ts`)
|
|
92
|
-
|
|
93
|
-
Ce fichier est le **pont** entre le package generique et votre application. Il definit :
|
|
94
|
-
- Comment compter les utilisateurs (pour `needsSetup`)
|
|
95
|
-
- Comment seeder les roles/permissions
|
|
96
|
-
- Comment creer l'admin
|
|
97
|
-
- Les seeds optionnels propres a votre app
|
|
98
|
-
|
|
99
|
-
```typescript
|
|
100
|
-
// src/lib/setup.ts
|
|
101
|
-
import {
|
|
102
|
-
needsSetup as _needsSetup,
|
|
103
|
-
runInstall as _runInstall,
|
|
104
|
-
} from '@mostajs/setup/lib/setup'
|
|
105
|
-
import type {
|
|
106
|
-
DialectType,
|
|
107
|
-
MostaSetupConfig,
|
|
108
|
-
SeedDefinition,
|
|
109
|
-
InstallConfig,
|
|
110
|
-
} from '@mostajs/setup'
|
|
111
|
-
|
|
112
|
-
export type { DialectType }
|
|
113
|
-
|
|
114
|
-
// ─── needsSetup : verifie si la DB a 0 users ──────────────
|
|
115
|
-
export async function needsSetup(): Promise<boolean> {
|
|
116
|
-
return _needsSetup(async () => {
|
|
117
|
-
// Adaptez cette ligne a votre couche d'acces aux donnees
|
|
118
|
-
const { userRepo } = await import('@/dal/service')
|
|
119
|
-
const repo = await userRepo()
|
|
120
|
-
return repo.count()
|
|
121
|
-
})
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
// ─── Seeds optionnels (propres a votre app) ────────────────
|
|
125
|
-
const optionalSeeds: SeedDefinition[] = [
|
|
126
|
-
{
|
|
127
|
-
key: 'demoData',
|
|
128
|
-
label: 'Donnees de demonstration',
|
|
129
|
-
description: 'Quelques enregistrements pour tester',
|
|
130
|
-
run: async () => {
|
|
131
|
-
// Votre logique de seed
|
|
132
|
-
const { productRepo } = await import('@/dal/service')
|
|
133
|
-
const repo = await productRepo()
|
|
134
|
-
await repo.create({ name: 'Produit A', price: 1000 })
|
|
135
|
-
await repo.create({ name: 'Produit B', price: 2000 })
|
|
136
|
-
},
|
|
137
|
-
},
|
|
138
|
-
]
|
|
139
|
-
|
|
140
|
-
// ─── Configuration du setup ────────────────────────────────
|
|
141
|
-
const SETUP_CONFIG: MostaSetupConfig = {
|
|
142
|
-
appName: 'Mon Application',
|
|
143
|
-
defaultPort: 3000,
|
|
144
|
-
|
|
145
|
-
// Callback pour seeder les roles et permissions
|
|
146
|
-
seedRBAC: async () => {
|
|
147
|
-
const { roleRepo, permissionRepo } = await import('@/dal/service')
|
|
148
|
-
const pRepo = await permissionRepo()
|
|
149
|
-
const rRepo = await roleRepo()
|
|
150
|
-
|
|
151
|
-
// Creer les permissions
|
|
152
|
-
const readPerm = await pRepo.upsert(
|
|
153
|
-
{ name: 'read' },
|
|
154
|
-
{ name: 'read', description: 'Lecture' },
|
|
155
|
-
)
|
|
156
|
-
const writePerm = await pRepo.upsert(
|
|
157
|
-
{ name: 'write' },
|
|
158
|
-
{ name: 'write', description: 'Ecriture' },
|
|
159
|
-
)
|
|
160
|
-
|
|
161
|
-
// Creer les roles avec permissions
|
|
162
|
-
await rRepo.upsert(
|
|
163
|
-
{ name: 'admin' },
|
|
164
|
-
{ name: 'admin', description: 'Administrateur', permissions: [readPerm.id, writePerm.id] },
|
|
165
|
-
)
|
|
166
|
-
await rRepo.upsert(
|
|
167
|
-
{ name: 'viewer' },
|
|
168
|
-
{ name: 'viewer', description: 'Lecteur', permissions: [readPerm.id] },
|
|
169
|
-
)
|
|
170
|
-
},
|
|
171
|
-
|
|
172
|
-
// Callback pour creer le premier administrateur
|
|
173
|
-
// Le mot de passe est DEJA hashe par le package (bcrypt, 12 rounds)
|
|
174
|
-
createAdmin: async ({ email, hashedPassword, firstName, lastName }) => {
|
|
175
|
-
const { userRepo, roleRepo } = await import('@/dal/service')
|
|
176
|
-
const uRepo = await userRepo()
|
|
177
|
-
const rRepo = await roleRepo()
|
|
178
|
-
|
|
179
|
-
const adminRole = await rRepo.findOne({ name: 'admin' })
|
|
180
|
-
await uRepo.upsert(
|
|
181
|
-
{ email: email.toLowerCase() },
|
|
182
|
-
{
|
|
183
|
-
email: email.toLowerCase(),
|
|
184
|
-
password: hashedPassword,
|
|
185
|
-
firstName,
|
|
186
|
-
lastName,
|
|
187
|
-
roles: adminRole ? [adminRole.id] : [],
|
|
188
|
-
status: 'active',
|
|
189
|
-
},
|
|
190
|
-
)
|
|
191
|
-
},
|
|
192
|
-
|
|
193
|
-
optionalSeeds,
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
// ─── runInstall : expose au route handler ──────────────────
|
|
197
|
-
export async function runInstall(config: InstallConfig) {
|
|
198
|
-
return _runInstall(config, SETUP_CONFIG)
|
|
199
|
-
}
|
|
200
|
-
```
|
|
201
|
-
|
|
202
|
-
**Points cles :**
|
|
203
|
-
- `needsSetup()` retourne `true` si 0 users en base → l'app doit s'installer
|
|
204
|
-
- `runInstall()` execute les 6 etapes : ecriture `.env.local`, connexion DB, seed RBAC, creation admin, seeds optionnels
|
|
205
|
-
- Le mot de passe admin est **automatiquement hashe** par le package (bcrypt, 12 rounds). Ne le hachez PAS vous-meme.
|
|
206
|
-
- `MOSTAJS_MODULES` est ecrit dans `.env.local` si `config.modules` est fourni
|
|
207
|
-
|
|
208
|
-
### Etape 3 — Creer les routes API
|
|
209
|
-
|
|
210
|
-
Creez 5 fichiers sous `src/app/api/setup/` :
|
|
211
|
-
|
|
212
|
-
#### 3.1 — Status (GET)
|
|
213
|
-
|
|
214
|
-
```typescript
|
|
215
|
-
// src/app/api/setup/status/route.ts
|
|
216
|
-
import { createStatusHandler } from '@mostajs/setup'
|
|
217
|
-
import { needsSetup } from '@/lib/setup'
|
|
218
|
-
|
|
219
|
-
export const { GET } = createStatusHandler(needsSetup)
|
|
220
|
-
```
|
|
221
|
-
|
|
222
|
-
Reponse : `{ "needsSetup": true }` ou `{ "needsSetup": false }`
|
|
12
|
+
## How to Use
|
|
223
13
|
|
|
224
|
-
|
|
14
|
+
### 1. Setup Route (Next.js catch-all)
|
|
225
15
|
|
|
226
16
|
```typescript
|
|
227
|
-
// src/app/api/setup/
|
|
228
|
-
import {
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
```
|
|
233
|
-
|
|
234
|
-
Body attendu :
|
|
235
|
-
```json
|
|
236
|
-
{
|
|
237
|
-
"dialect": "postgres",
|
|
238
|
-
"host": "localhost",
|
|
239
|
-
"port": 5432,
|
|
240
|
-
"name": "mydb",
|
|
241
|
-
"user": "postgres",
|
|
242
|
-
"password": "secret"
|
|
243
|
-
}
|
|
244
|
-
```
|
|
245
|
-
|
|
246
|
-
Reponse : `{ "ok": true }` ou `{ "ok": false, "error": "..." }`
|
|
247
|
-
|
|
248
|
-
#### 3.3 — Installation (POST)
|
|
249
|
-
|
|
250
|
-
```typescript
|
|
251
|
-
// src/app/api/setup/install/route.ts
|
|
252
|
-
import { NextRequest, NextResponse } from 'next/server'
|
|
253
|
-
import { needsSetup, runInstall } from '@/lib/setup'
|
|
254
|
-
import { z } from 'zod'
|
|
255
|
-
|
|
256
|
-
const ALL_DIALECTS = [
|
|
257
|
-
'mongodb', 'sqlite', 'postgres', 'mysql', 'mariadb',
|
|
258
|
-
'oracle', 'mssql', 'cockroachdb', 'db2', 'hana',
|
|
259
|
-
'hsqldb', 'spanner', 'sybase',
|
|
260
|
-
] as const
|
|
261
|
-
|
|
262
|
-
const installSchema = z.object({
|
|
263
|
-
dialect: z.enum(ALL_DIALECTS),
|
|
264
|
-
db: z.object({
|
|
265
|
-
host: z.string().default('localhost'),
|
|
266
|
-
port: z.number().int().min(0).max(65535).default(0),
|
|
267
|
-
name: z.string().min(1),
|
|
268
|
-
user: z.string().default(''),
|
|
269
|
-
password: z.string().default(''),
|
|
270
|
-
}),
|
|
271
|
-
admin: z.object({
|
|
272
|
-
email: z.string().email(),
|
|
273
|
-
password: z.string().min(6),
|
|
274
|
-
firstName: z.string().min(1),
|
|
275
|
-
lastName: z.string().min(1),
|
|
276
|
-
}),
|
|
277
|
-
seed: z.record(z.boolean()).optional(),
|
|
278
|
-
modules: z.array(z.string()).optional(),
|
|
17
|
+
// src/app/api/setup/[...slug]/route.ts
|
|
18
|
+
import { createSetupRoutes } from '@mostajs/setup'
|
|
19
|
+
export const { GET, POST, DELETE, PATCH } = createSetupRoutes({
|
|
20
|
+
needsSetup: appNeedsSetup,
|
|
21
|
+
getSetupConfig,
|
|
279
22
|
})
|
|
280
|
-
|
|
281
|
-
export async function POST(req: NextRequest) {
|
|
282
|
-
const setupNeeded = await needsSetup()
|
|
283
|
-
if (!setupNeeded) {
|
|
284
|
-
return NextResponse.json(
|
|
285
|
-
{ error: { code: 'FORBIDDEN', message: 'Installation deja effectuee' } },
|
|
286
|
-
{ status: 403 },
|
|
287
|
-
)
|
|
288
|
-
}
|
|
289
|
-
|
|
290
|
-
const body = await req.json()
|
|
291
|
-
const parsed = installSchema.safeParse(body)
|
|
292
|
-
if (!parsed.success) {
|
|
293
|
-
return NextResponse.json(
|
|
294
|
-
{ error: { code: 'VALIDATION_ERROR', details: parsed.error.flatten() } },
|
|
295
|
-
{ status: 400 },
|
|
296
|
-
)
|
|
297
|
-
}
|
|
298
|
-
|
|
299
|
-
const result = await runInstall(parsed.data)
|
|
300
|
-
if (!result.ok) {
|
|
301
|
-
return NextResponse.json(
|
|
302
|
-
{ error: { code: 'INSTALL_ERROR', message: result.error } },
|
|
303
|
-
{ status: 500 },
|
|
304
|
-
)
|
|
305
|
-
}
|
|
306
|
-
|
|
307
|
-
return NextResponse.json({
|
|
308
|
-
data: { ok: true, needsRestart: result.needsRestart, seeded: result.seeded },
|
|
309
|
-
})
|
|
310
|
-
}
|
|
311
|
-
```
|
|
312
|
-
|
|
313
|
-
#### 3.4 — Detection des modules (GET)
|
|
314
|
-
|
|
315
|
-
```typescript
|
|
316
|
-
// src/app/api/setup/detect-modules/route.ts
|
|
317
|
-
import { createDetectModulesHandler } from '@mostajs/setup'
|
|
318
|
-
|
|
319
|
-
export const { GET } = createDetectModulesHandler()
|
|
320
|
-
```
|
|
321
|
-
|
|
322
|
-
Reponse :
|
|
323
|
-
```json
|
|
324
|
-
{
|
|
325
|
-
"modules": [
|
|
326
|
-
{ "key": "orm", "packageName": "@mostajs/orm", "label": "ORM", "required": true, ... },
|
|
327
|
-
{ "key": "auth", "packageName": "@mostajs/auth", ... },
|
|
328
|
-
{ "key": "new-plugin", "discovered": true, "icon": "📦", ... }
|
|
329
|
-
],
|
|
330
|
-
"installed": ["orm", "auth", "setup"]
|
|
331
|
-
}
|
|
332
|
-
```
|
|
333
|
-
|
|
334
|
-
#### 3.5 — Installation des modules (POST)
|
|
335
|
-
|
|
336
|
-
```typescript
|
|
337
|
-
// src/app/api/setup/install-modules/route.ts
|
|
338
|
-
import { createInstallModulesHandler } from '@mostajs/setup'
|
|
339
|
-
import { needsSetup } from '@/lib/setup'
|
|
340
|
-
|
|
341
|
-
export const { POST } = createInstallModulesHandler(needsSetup)
|
|
342
23
|
```
|
|
343
24
|
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
Ce handler :
|
|
347
|
-
1. Resout les dependances transitives (`rbac` → `auth` + `audit`)
|
|
348
|
-
2. Installe via `npm install` (local `file:./packages/...` si present, sinon npm registry)
|
|
349
|
-
3. Ecrit `MOSTAJS_MODULES=orm,auth,audit,rbac` dans `.env.local`
|
|
350
|
-
|
|
351
|
-
### Etape 4 — Creer la page Setup (frontend)
|
|
352
|
-
|
|
353
|
-
Le module fournit un composant **`SetupWizard`** pret a l'emploi avec inline styles (aucune dependance Tailwind/shadcn).
|
|
354
|
-
Votre page Next.js n'est qu'un **wrapper** d'une vingtaine de lignes :
|
|
25
|
+
### 2. Setup Page
|
|
355
26
|
|
|
356
27
|
```tsx
|
|
357
28
|
// src/app/setup/page.tsx
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
import { useRouter } from 'next/navigation'
|
|
361
|
-
import SetupWizard from '@mostajs/setup/components/SetupWizard'
|
|
362
|
-
import { t } from '@/i18n' // ou toute fonction de traduction
|
|
363
|
-
|
|
364
|
-
export default function SetupPage() {
|
|
365
|
-
const router = useRouter()
|
|
366
|
-
|
|
367
|
-
return (
|
|
368
|
-
<SetupWizard
|
|
369
|
-
t={t}
|
|
370
|
-
onComplete={() => router.push('/login')}
|
|
371
|
-
dbNamePrefix="secuaccessdb"
|
|
372
|
-
endpoints={{
|
|
373
|
-
detectModules: '/api/setup/detect-modules',
|
|
374
|
-
testDb: '/api/setup/test-db',
|
|
375
|
-
installModules: '/api/setup/install-modules',
|
|
376
|
-
install: '/api/setup/install',
|
|
377
|
-
uploadJar: '/api/setup/upload-jar',
|
|
378
|
-
}}
|
|
379
|
-
/>
|
|
380
|
-
)
|
|
381
|
-
}
|
|
382
|
-
```
|
|
383
|
-
|
|
384
|
-
#### Props de SetupWizard
|
|
385
|
-
|
|
386
|
-
| Prop | Type | Default | Description |
|
|
387
|
-
|------|------|---------|-------------|
|
|
388
|
-
| `t` | `(key: string) => string` | `(k) => k` | Fonction de traduction (recoit des cles `setup.xxx`) |
|
|
389
|
-
| `onComplete` | `() => void` | — | Callback apres installation reussie (ex: `router.push('/login')`) |
|
|
390
|
-
| `dbNamePrefix` | `string` | `'mydb'` | Prefixe pour le nom de base par defaut |
|
|
391
|
-
| `persistState` | `boolean` | `true` | Persister l'etat du wizard dans `sessionStorage` |
|
|
392
|
-
| `endpoints` | `object` | voir ci-dessous | URLs des routes API |
|
|
393
|
-
|
|
394
|
-
**Endpoints par defaut :**
|
|
395
|
-
```json
|
|
396
|
-
{
|
|
397
|
-
"detectModules": "/api/setup/detect-modules",
|
|
398
|
-
"testDb": "/api/setup/test-db",
|
|
399
|
-
"installModules": "/api/setup/install-modules",
|
|
400
|
-
"install": "/api/setup/install",
|
|
401
|
-
"uploadJar": "/api/setup/upload-jar"
|
|
402
|
-
}
|
|
403
|
-
```
|
|
404
|
-
|
|
405
|
-
**Cles i18n attendues :** `setup.steps.*`, `setup.welcome.*`, `setup.modules.*`, `setup.dialect.*`, `setup.database.*`, `setup.admin.*`, `setup.summary.*`, `setup.back`, `setup.next`.
|
|
406
|
-
|
|
407
|
-
#### Le wizard inclut :
|
|
408
|
-
- **6 etapes** : Accueil → Modules → Dialecte → Base de donnees → Admin → Recapitulatif
|
|
409
|
-
- **13 dialectes** avec distinction Premium (grises, non cliquables) et badges JDBC
|
|
410
|
-
- **Upload JAR** integre pour les dialectes JDBC (hsqldb, oracle, db2, hana, sybase)
|
|
411
|
-
- **Persistence sessionStorage** pour survivre aux hot-reloads Next.js
|
|
412
|
-
- **Resolution des dependances** entre modules
|
|
413
|
-
- **Retry automatique** apres npm install (le serveur peut redemarrer)
|
|
414
|
-
|
|
415
|
-
### Etape 5 — Middleware : rediriger vers /setup
|
|
416
|
-
|
|
417
|
-
```typescript
|
|
418
|
-
// src/middleware.ts
|
|
419
|
-
import { NextResponse } from 'next/server'
|
|
420
|
-
import type { NextRequest } from 'next/server'
|
|
421
|
-
|
|
422
|
-
export async function middleware(req: NextRequest) {
|
|
423
|
-
const { pathname } = req.nextUrl
|
|
424
|
-
|
|
425
|
-
// Laisser passer les routes publiques
|
|
426
|
-
if (
|
|
427
|
-
pathname.startsWith('/setup') ||
|
|
428
|
-
pathname.startsWith('/api/setup') ||
|
|
429
|
-
pathname.startsWith('/api/auth') ||
|
|
430
|
-
pathname.startsWith('/_next')
|
|
431
|
-
) {
|
|
432
|
-
return NextResponse.next()
|
|
433
|
-
}
|
|
434
|
-
|
|
435
|
-
// Verifier si l'app a besoin du setup
|
|
436
|
-
try {
|
|
437
|
-
const res = await fetch(new URL('/api/setup/status', req.url))
|
|
438
|
-
const data = await res.json()
|
|
439
|
-
if (data.needsSetup) {
|
|
440
|
-
return NextResponse.redirect(new URL('/setup', req.url))
|
|
441
|
-
}
|
|
442
|
-
} catch {
|
|
443
|
-
// Si la DB n'est pas accessible, rediriger vers setup
|
|
444
|
-
return NextResponse.redirect(new URL('/setup', req.url))
|
|
445
|
-
}
|
|
446
|
-
|
|
447
|
-
return NextResponse.next()
|
|
448
|
-
}
|
|
449
|
-
|
|
450
|
-
export const config = {
|
|
451
|
-
matcher: ['/((?!_next|favicon.ico|icons|manifest.json).*)'],
|
|
452
|
-
}
|
|
453
|
-
```
|
|
454
|
-
|
|
455
|
-
### Etape 6 — Tester le flux complet
|
|
456
|
-
|
|
457
|
-
```bash
|
|
458
|
-
# 1. Demarrer le serveur
|
|
459
|
-
npm run dev
|
|
460
|
-
|
|
461
|
-
# 2. Ouvrir http://localhost:3000
|
|
462
|
-
# → Redirige automatiquement vers /setup
|
|
463
|
-
|
|
464
|
-
# 3. Suivre le wizard :
|
|
465
|
-
# - Choisir les modules
|
|
466
|
-
# - Selectionner le dialecte (ex: MongoDB, SQLite, Postgres...)
|
|
467
|
-
# - Tester la connexion
|
|
468
|
-
# - Renseigner l'admin
|
|
469
|
-
# - Lancer l'installation
|
|
470
|
-
|
|
471
|
-
# 4. Verifier les resultats :
|
|
472
|
-
# - .env.local contient DB_DIALECT, SGBD_URI, MOSTAJS_MODULES
|
|
473
|
-
# - La DB contient les roles, permissions et l'admin
|
|
474
|
-
# - /api/setup/status retourne { needsSetup: false }
|
|
475
|
-
```
|
|
476
|
-
|
|
477
|
-
---
|
|
478
|
-
|
|
479
|
-
## 4. API Reference
|
|
480
|
-
|
|
481
|
-
### Fonctions principales
|
|
482
|
-
|
|
483
|
-
| Export | Signature | Description |
|
|
484
|
-
|--------|-----------|-------------|
|
|
485
|
-
| `needsSetup` | `(countFn: () => Promise<number>) => Promise<boolean>` | Retourne `true` si 0 users en base |
|
|
486
|
-
| `runInstall` | `(config: InstallConfig, setup: MostaSetupConfig) => Promise<Result>` | Flux complet : env → DB → seed → admin |
|
|
487
|
-
| `testDbConnection` | `(config: DbTestConfig) => Promise<{ ok, error? }>` | Test ephemere (ne modifie pas la connexion globale) |
|
|
488
|
-
| `composeDbUri` | `(dialect: DialectType, db: DbConfig) => string` | Compose l'URI de connexion |
|
|
489
|
-
| `writeEnvLocal` | `(options: EnvWriterOptions) => Promise<boolean>` | Ecrit `.env.local`, retourne `true` si dialect change |
|
|
490
|
-
|
|
491
|
-
### Factories API routes
|
|
492
|
-
|
|
493
|
-
| Export | Route | Methode | Description |
|
|
494
|
-
|--------|-------|---------|-------------|
|
|
495
|
-
| `createStatusHandler(needsSetup)` | `/api/setup/status` | GET | Retourne `{ needsSetup: boolean }` |
|
|
496
|
-
| `createTestDbHandler(needsSetup)` | `/api/setup/test-db` | POST | Teste la connexion DB |
|
|
497
|
-
| `createInstallHandler(needsSetup, config)` | `/api/setup/install` | POST | Lance l'installation complete |
|
|
498
|
-
| `createDetectModulesHandler()` | `/api/setup/detect-modules` | GET | Liste modules (statiques + npm) + installes |
|
|
499
|
-
| `createInstallModulesHandler(needsSetup)` | `/api/setup/install-modules` | POST | Installe les modules npm selectionnes |
|
|
500
|
-
|
|
501
|
-
### setup.json (declaratif)
|
|
502
|
-
|
|
503
|
-
| Export | Signature | Description |
|
|
504
|
-
|--------|-----------|-------------|
|
|
505
|
-
| `loadSetupJson` | `(source?, repoFactory?) => Promise<MostaSetupConfig>` | Charge `setup.json` et retourne un config complet |
|
|
506
|
-
| `createSetupJsonHandler` | `(needsSetup) => { GET, POST }` | Route API pour verifier/uploader `setup.json` |
|
|
507
|
-
|
|
508
|
-
### Data exports
|
|
509
|
-
|
|
510
|
-
| Export | Description |
|
|
511
|
-
|--------|-------------|
|
|
512
|
-
| `DIALECT_INFO` | `Record<DialectType, DialectInfo>` — metadata de chaque SGBD |
|
|
513
|
-
| `ALL_DIALECTS` | `DialectType[]` — liste des 13 dialectes |
|
|
514
|
-
| `MODULES` | `ModuleDefinition[]` — liste statique des 7 modules connus |
|
|
515
|
-
| `resolveModuleDependencies(selected, modules?)` | Resout les dependances transitives |
|
|
516
|
-
| `discoverNpmModules()` | Decouvre les packages `@mostajs/*` sur npm |
|
|
517
|
-
|
|
518
|
-
### Types
|
|
519
|
-
|
|
520
|
-
```typescript
|
|
521
|
-
interface InstallConfig {
|
|
522
|
-
dialect: DialectType
|
|
523
|
-
db: DbConfig
|
|
524
|
-
admin: { email: string; password: string; firstName: string; lastName: string }
|
|
525
|
-
seed?: SeedOptions // { activities: true, demoData: false, ... }
|
|
526
|
-
modules?: string[] // ['orm', 'auth', 'rbac'] → ecrit MOSTAJS_MODULES
|
|
527
|
-
}
|
|
528
|
-
|
|
529
|
-
interface MostaSetupConfig {
|
|
530
|
-
appName: string
|
|
531
|
-
defaultPort?: number
|
|
532
|
-
seedRBAC?: () => Promise<void>
|
|
533
|
-
createAdmin?: (admin: { email: string; hashedPassword: string; firstName: string; lastName: string }) => Promise<void>
|
|
534
|
-
optionalSeeds?: SeedDefinition[]
|
|
535
|
-
extraEnvVars?: Record<string, string>
|
|
536
|
-
}
|
|
537
|
-
|
|
538
|
-
interface SeedDefinition {
|
|
539
|
-
key: string
|
|
540
|
-
label: string
|
|
541
|
-
description: string
|
|
542
|
-
icon?: string
|
|
543
|
-
default?: boolean
|
|
544
|
-
run: (repos: any) => Promise<void>
|
|
545
|
-
}
|
|
546
|
-
|
|
547
|
-
interface ModuleDefinition {
|
|
548
|
-
key: string
|
|
549
|
-
packageName: string // '@mostajs/auth'
|
|
550
|
-
localDir?: string // 'mosta-auth' (sous packages/)
|
|
551
|
-
label: string
|
|
552
|
-
description: string
|
|
553
|
-
icon: string
|
|
554
|
-
required?: boolean
|
|
555
|
-
default?: boolean
|
|
556
|
-
dependsOn?: string[]
|
|
557
|
-
discovered?: boolean // true si trouve via npm search
|
|
558
|
-
}
|
|
559
|
-
```
|
|
560
|
-
|
|
561
|
-
---
|
|
562
|
-
|
|
563
|
-
## 5. Les 13 dialectes supportes
|
|
564
|
-
|
|
565
|
-
| Dialecte | Icone | Port | Categorie | Driver |
|
|
566
|
-
|----------|-------|------|-----------|--------|
|
|
567
|
-
| MongoDB | 🍃 | 27017 | document | mongoose |
|
|
568
|
-
| SQLite | 📁 | — | file | better-sqlite3 |
|
|
569
|
-
| PostgreSQL | 🐘 | 5432 | sql | pg |
|
|
570
|
-
| MySQL | 🐬 | 3306 | sql | mysql2 |
|
|
571
|
-
| MariaDB | 🦭 | 3306 | sql | mariadb |
|
|
572
|
-
| Oracle | 🔴 | 1521 | enterprise | oracledb |
|
|
573
|
-
| SQL Server | 🟦 | 1433 | enterprise | tedious |
|
|
574
|
-
| CockroachDB | 🪳 | 26257 | distributed | pg |
|
|
575
|
-
| IBM DB2 | 🏢 | 50000 | enterprise | ibm_db |
|
|
576
|
-
| SAP HANA | 💎 | 39013 | enterprise | @sap/hana-client |
|
|
577
|
-
| HyperSQL | ⚡ | 9001 | legacy | — |
|
|
578
|
-
| Cloud Spanner | ☁️ | — | distributed | @google-cloud/spanner |
|
|
579
|
-
| Sybase ASE | 🔷 | 5000 | legacy | sybase |
|
|
580
|
-
|
|
581
|
-
Les URI sont composees automatiquement par `composeDbUri()` :
|
|
582
|
-
|
|
583
|
-
```
|
|
584
|
-
mongodb://user:pass@host:27017/mydb
|
|
585
|
-
postgresql://user:pass@host:5432/mydb
|
|
586
|
-
mysql://user:pass@host:3306/mydb
|
|
587
|
-
./data/mydb.db (SQLite)
|
|
588
|
-
spanner://projects/my-project (Cloud Spanner)
|
|
589
|
-
```
|
|
590
|
-
|
|
591
|
-
---
|
|
592
|
-
|
|
593
|
-
## 6. Systeme de modules
|
|
594
|
-
|
|
595
|
-
### Catalogue des 7 modules connus
|
|
596
|
-
|
|
597
|
-
| Module | Package | Requis | Depend de | Standalone | Description |
|
|
598
|
-
|--------|---------|--------|-----------|------------|-------------|
|
|
599
|
-
| **orm** | `@mostajs/orm` | oui | — | — | ORM multi-dialecte (13 SGBD), pattern Hibernate |
|
|
600
|
-
| **auth** | `@mostajs/auth` | oui | orm | non | NextAuth, sessions, hashage mots de passe |
|
|
601
|
-
| **audit** | `@mostajs/audit` | non | orm | non | Journalisation des actions, tracabilite |
|
|
602
|
-
| **rbac** | `@mostajs/rbac` | non | auth, audit | non | Roles, permissions, matrice RBAC |
|
|
603
|
-
| **settings** | `@mostajs/settings` | non | orm | non | Parametres cle-valeur, formulaire auto, provider React |
|
|
604
|
-
| **face** | `@mostajs/face` | non | **aucune** | **oui** | Detection de visage, descripteurs, matching 1:N |
|
|
605
|
-
| **setup** | `@mostajs/setup` | oui | orm | non | Wizard d'installation, test DB, seed runner |
|
|
606
|
-
|
|
607
|
-
### Module standalone : @mostajs/face
|
|
608
|
-
|
|
609
|
-
`@mostajs/face` est **100% independant** — il n'importe aucun package `@mostajs/*` et peut etre utilise dans n'importe quelle application React >= 18 sans `@mostajs/orm` ni base de donnees.
|
|
610
|
-
|
|
611
|
-
**Dependance unique** : `@vladmandic/face-api` (reconnaissance faciale TensorFlow.js)
|
|
612
|
-
|
|
613
|
-
```bash
|
|
614
|
-
npm install @mostajs/face
|
|
615
|
-
```
|
|
616
|
-
|
|
617
|
-
```tsx
|
|
618
|
-
import { useCamera, useFaceDetection, compareFaces } from '@mostajs/face'
|
|
619
|
-
|
|
620
|
-
// Hooks React pour camera et detection
|
|
621
|
-
const { videoRef, start, stop } = useCamera()
|
|
622
|
-
const { detect, result } = useFaceDetection()
|
|
623
|
-
|
|
624
|
-
// API bas niveau
|
|
625
|
-
import { loadModels, detectFace, extractDescriptor } from '@mostajs/face'
|
|
626
|
-
import { findMatch, descriptorToArray, arrayToDescriptor } from '@mostajs/face'
|
|
627
|
-
```
|
|
628
|
-
|
|
629
|
-
Exports : `loadModels`, `detectFace`, `detectAllFaces`, `extractDescriptor`, `compareFaces`, `findMatch`, `findAllMatches`, `descriptorToArray`, `arrayToDescriptor`, `isValidDescriptor`, `drawDetection`, `useCamera`, `useFaceDetection`.
|
|
630
|
-
|
|
631
|
-
### Graphe de dependances
|
|
632
|
-
|
|
633
|
-
```
|
|
634
|
-
┌──────────┐
|
|
635
|
-
│ orm (R) │ R = requis
|
|
636
|
-
└────┬─────┘
|
|
637
|
-
┌────────┼────────┬──────────┐
|
|
638
|
-
v v v v
|
|
639
|
-
┌──────────┐ ┌──────┐ ┌──────────┐ ┌───────┐
|
|
640
|
-
│ auth (R) │ │audit │ │ settings │ │setup(R)│
|
|
641
|
-
└────┬─────┘ └──┬───┘ └──────────┘ └───────┘
|
|
642
|
-
│ │
|
|
643
|
-
v v
|
|
644
|
-
┌──────────────────┐
|
|
645
|
-
│ rbac │
|
|
646
|
-
└──────────────────┘
|
|
647
|
-
|
|
648
|
-
┌──────────────────────────┐
|
|
649
|
-
│ face (100% standalone) │ ← aucune dependance @mostajs
|
|
650
|
-
└──────────────────────────┘
|
|
651
|
-
```
|
|
652
|
-
|
|
653
|
-
### Liste statique vs decouverte dynamique
|
|
654
|
-
|
|
655
|
-
Le package maintient une **liste statique** des 7 modules ci-dessus avec metadata riches (required, dependsOn, icon, description).
|
|
656
|
-
|
|
657
|
-
Au runtime, `GET /api/setup/detect-modules` interroge aussi **npm** (`npm search @mostajs --json`) pour trouver des packages publies apres le deploiement. Ces modules decouverts sont ajoutes avec `discovered: true` et l'icone 📦.
|
|
658
|
-
|
|
659
|
-
### Resolution des dependances
|
|
660
|
-
|
|
661
|
-
```typescript
|
|
662
|
-
import { resolveModuleDependencies } from '@mostajs/setup'
|
|
663
|
-
|
|
664
|
-
resolveModuleDependencies(['rbac'])
|
|
665
|
-
// → ['rbac', 'auth', 'audit', 'orm', 'setup']
|
|
666
|
-
// (rbac depend de auth + audit, auth depend de orm, setup est requis)
|
|
667
|
-
|
|
668
|
-
resolveModuleDependencies(['face'])
|
|
669
|
-
// → ['face', 'orm', 'auth', 'setup']
|
|
670
|
-
// (face n'a pas de dependance @mostajs, mais orm/auth/setup sont requis)
|
|
671
|
-
```
|
|
672
|
-
|
|
673
|
-
### Installation hybride (local + npm)
|
|
674
|
-
|
|
675
|
-
Le handler `install-modules` utilise une strategie hybride :
|
|
676
|
-
|
|
677
|
-
1. **Skip** : si deja dans `node_modules/@mostajs/xxx` → pas de `npm install` (evite hot-reload)
|
|
678
|
-
2. **Local** : si `packages/mosta-xxx/` existe → `npm install file:./packages/mosta-xxx`
|
|
679
|
-
3. **npm registry** : sinon → `npm install @mostajs/xxx`
|
|
680
|
-
|
|
681
|
-
Cela evite les 404 npm pour les packages non encore publies et les hot-reloads Next.js inutiles.
|
|
682
|
-
|
|
683
|
-
---
|
|
684
|
-
|
|
685
|
-
## 7. Exemples avances
|
|
686
|
-
|
|
687
|
-
### Utiliser runInstall sans le wizard UI
|
|
688
|
-
|
|
689
|
-
```typescript
|
|
690
|
-
import { runInstall } from '@mostajs/setup'
|
|
691
|
-
import { setupConfig } from './my-setup-config'
|
|
692
|
-
|
|
693
|
-
const result = await runInstall(
|
|
694
|
-
{
|
|
695
|
-
dialect: 'postgres',
|
|
696
|
-
db: { host: 'localhost', port: 5432, name: 'mydb', user: 'pg', password: 'secret' },
|
|
697
|
-
admin: { email: 'admin@example.com', password: 'Admin@123', firstName: 'Admin', lastName: 'User' },
|
|
698
|
-
seed: { demoData: true },
|
|
699
|
-
modules: ['orm', 'auth', 'rbac'],
|
|
700
|
-
},
|
|
701
|
-
setupConfig,
|
|
702
|
-
)
|
|
703
|
-
|
|
704
|
-
console.log(result)
|
|
705
|
-
// { ok: true, needsRestart: false, seeded: ['categories', 'permissions', 'roles', 'admin', 'demoData'] }
|
|
706
|
-
```
|
|
707
|
-
|
|
708
|
-
### Ajouter des variables d'environnement personnalisees
|
|
709
|
-
|
|
710
|
-
```typescript
|
|
711
|
-
const config: MostaSetupConfig = {
|
|
712
|
-
appName: 'MyApp',
|
|
713
|
-
extraEnvVars: {
|
|
714
|
-
SMTP_HOST: 'smtp.example.com',
|
|
715
|
-
STRIPE_KEY: 'sk_test_...',
|
|
716
|
-
},
|
|
717
|
-
}
|
|
718
|
-
// → .env.local contiendra aussi SMTP_HOST et STRIPE_KEY
|
|
719
|
-
```
|
|
720
|
-
|
|
721
|
-
### Tester la connexion DB programmatiquement
|
|
722
|
-
|
|
723
|
-
```typescript
|
|
724
|
-
import { testDbConnection } from '@mostajs/setup'
|
|
725
|
-
|
|
726
|
-
const result = await testDbConnection({
|
|
727
|
-
dialect: 'mongodb',
|
|
728
|
-
host: 'localhost',
|
|
729
|
-
port: 27017,
|
|
730
|
-
name: 'testdb',
|
|
731
|
-
user: '',
|
|
732
|
-
password: '',
|
|
733
|
-
})
|
|
734
|
-
// { ok: true } ou { ok: false, error: 'Connection refused' }
|
|
735
|
-
```
|
|
736
|
-
|
|
737
|
-
### Composer une URI manuellement
|
|
738
|
-
|
|
739
|
-
```typescript
|
|
740
|
-
import { composeDbUri } from '@mostajs/setup'
|
|
741
|
-
|
|
742
|
-
composeDbUri('postgres', { host: 'db.example.com', port: 5432, name: 'prod', user: 'app', password: 's3cret' })
|
|
743
|
-
// → 'postgresql://app:s3cret@db.example.com:5432/prod'
|
|
744
|
-
|
|
745
|
-
composeDbUri('sqlite', { host: '', port: 0, name: 'myapp', user: '', password: '' })
|
|
746
|
-
// → './data/myapp.db'
|
|
747
|
-
```
|
|
748
|
-
|
|
749
|
-
### Ecrire .env.local directement
|
|
750
|
-
|
|
751
|
-
```typescript
|
|
752
|
-
import { writeEnvLocal } from '@mostajs/setup'
|
|
753
|
-
|
|
754
|
-
const dialectChanged = await writeEnvLocal({
|
|
755
|
-
dialect: 'mongodb',
|
|
756
|
-
uri: 'mongodb://localhost:27017/mydb',
|
|
757
|
-
port: 3000,
|
|
758
|
-
extraVars: { MOSTAJS_MODULES: 'orm,auth,rbac' },
|
|
759
|
-
})
|
|
760
|
-
// dialectChanged = true si le dialecte a change (necessite un redemarrage)
|
|
761
|
-
```
|
|
762
|
-
|
|
763
|
-
---
|
|
764
|
-
|
|
765
|
-
## 8. Reconfiguration (post-installation)
|
|
766
|
-
|
|
767
|
-
Apres l'installation initiale, le module fournit un **panneau de reconfiguration** permettant de :
|
|
768
|
-
- Changer de base de donnees (dialecte, connexion, test)
|
|
769
|
-
- Activer / desactiver des modules @mostajs
|
|
770
|
-
- Optionnellement re-seeder la nouvelle base
|
|
771
|
-
|
|
772
|
-
### Integration dans le projet hote
|
|
773
|
-
|
|
774
|
-
Ce module exporte un **composant React** (`ReconfigPanel`) et une **factory API** (`createReconfigHandlers`),
|
|
775
|
-
mais ne cree pas de pages Next.js. Le projet hote doit creer la route API et la page.
|
|
776
|
-
|
|
777
|
-
#### 1. Route API
|
|
778
|
-
|
|
779
|
-
**`src/app/api/setup/reconfig/route.ts`**
|
|
780
|
-
```typescript
|
|
781
|
-
import { createReconfigHandlers } from '@mostajs/setup/api/reconfig'
|
|
782
|
-
|
|
783
|
-
const { GET, POST } = createReconfigHandlers()
|
|
784
|
-
export { GET, POST }
|
|
785
|
-
```
|
|
786
|
-
|
|
787
|
-
#### 2. Route API JAR Upload (drivers JDBC)
|
|
788
|
-
|
|
789
|
-
**`src/app/api/setup/upload-jar/route.ts`**
|
|
790
|
-
```typescript
|
|
791
|
-
// Author: Dr Hamid MADANI drmdh@msn.com
|
|
792
|
-
import { createUploadJarHandlers } from '@mostajs/setup/api/upload-jar'
|
|
793
|
-
|
|
794
|
-
const { GET, POST, DELETE } = createUploadJarHandlers()
|
|
795
|
-
export { GET, POST, DELETE }
|
|
796
|
-
```
|
|
797
|
-
|
|
798
|
-
> La logique d'upload est dans `@mostajs/orm` (`saveJarFile`, `deleteJarFile`, `listJarFiles`).
|
|
799
|
-
> La route factory dans `@mostajs/setup` ne fait que la deleguer.
|
|
800
|
-
>
|
|
801
|
-
> - **GET** — liste les JARs et le statut des dialects JDBC
|
|
802
|
-
> - **POST** — upload un fichier `.jar` (multipart/form-data, champ `jar`)
|
|
803
|
-
> - **DELETE** — supprime un JAR (`{ "fileName": "hsqldb-2.7.2.jar" }`)
|
|
804
|
-
|
|
805
|
-
#### 3. Page de reconfiguration
|
|
806
|
-
|
|
807
|
-
**`src/app/dashboard/settings/reconfig/page.tsx`**
|
|
808
|
-
```tsx
|
|
809
|
-
'use client'
|
|
810
|
-
import ReconfigPanel from '@mostajs/setup/components/ReconfigPanel'
|
|
811
|
-
|
|
812
|
-
export default function ReconfigPage() {
|
|
813
|
-
return (
|
|
814
|
-
<div className="space-y-6">
|
|
815
|
-
<h1 className="text-2xl font-bold">Reconfiguration</h1>
|
|
816
|
-
<ReconfigPanel
|
|
817
|
-
apiEndpoint="/api/setup/reconfig"
|
|
818
|
-
detectEndpoint="/api/setup/detect-modules"
|
|
819
|
-
jarEndpoint="/api/setup/upload-jar"
|
|
820
|
-
showSeedOption
|
|
821
|
-
onDbChanged={() => window.location.reload()}
|
|
822
|
-
onSeedRequested={async () => {
|
|
823
|
-
await fetch('/api/setup/install', {
|
|
824
|
-
method: 'POST',
|
|
825
|
-
headers: { 'Content-Type': 'application/json' },
|
|
826
|
-
body: JSON.stringify({ action: 'seed-only' }),
|
|
827
|
-
})
|
|
828
|
-
}}
|
|
829
|
-
/>
|
|
830
|
-
</div>
|
|
831
|
-
)
|
|
832
|
-
}
|
|
833
|
-
```
|
|
834
|
-
|
|
835
|
-
#### 3. Menu dynamique
|
|
836
|
-
|
|
837
|
-
Le module exporte `setupMenuContribution` qui declare la route `/dashboard/settings/reconfig`
|
|
838
|
-
dans le groupe "Administration". Importez-le via le deep import :
|
|
839
|
-
|
|
840
|
-
```tsx
|
|
841
|
-
import { setupMenuContribution } from '@mostajs/setup/lib/menu'
|
|
842
|
-
```
|
|
843
|
-
|
|
844
|
-
#### 4. Props de ReconfigPanel
|
|
845
|
-
|
|
846
|
-
| Prop | Type | Description |
|
|
847
|
-
|------|------|-------------|
|
|
848
|
-
| `apiEndpoint` | `string` | URL de l'API reconfig (ex: `/api/setup/reconfig`) |
|
|
849
|
-
| `detectEndpoint` | `string` | URL de l'API detect-modules (ex: `/api/setup/detect-modules`) |
|
|
850
|
-
| `t` | `(key: string) => string` | Fonction de traduction (optionnel) |
|
|
851
|
-
| `showSeedOption` | `boolean` | Afficher la checkbox "Re-seeder" lors d'un changement de DB |
|
|
852
|
-
| `onDbChanged` | `() => void` | Callback apres changement de DB reussi |
|
|
853
|
-
| `onModulesChanged` | `(modules: string[]) => void` | Callback apres maj des modules |
|
|
854
|
-
| `onSeedRequested` | `() => Promise<void>` | Callback pour executer le seed |
|
|
855
|
-
|
|
856
|
-
#### 5. Pourquoi le projet hote doit creer les pages ?
|
|
857
|
-
|
|
858
|
-
Les modules `@mostajs/*` sont des **bibliotheques npm** (composants, hooks, utilitaires), pas des applications.
|
|
859
|
-
Next.js App Router exige que les fichiers `page.tsx` soient dans le dossier `src/app/` du projet.
|
|
860
|
-
Un package npm ne peut pas injecter de pages dans le routeur — c'est donc au projet hote de creer ces fichiers wrapper, meme s'ils ne font qu'importer et afficher un composant du module.
|
|
861
|
-
|
|
862
|
-
---
|
|
863
|
-
|
|
864
|
-
## 9. Mode declaratif : setup.json
|
|
865
|
-
|
|
866
|
-
> **Nouveau** — Depuis v1.5, le setup peut etre entierement configure via un fichier JSON declaratif.
|
|
867
|
-
|
|
868
|
-
### Principe
|
|
869
|
-
|
|
870
|
-
Au lieu d'ecrire du TypeScript pour definir les categories, permissions, roles et seeds,
|
|
871
|
-
vous declarez tout dans un fichier `setup.json` a la racine du projet. Le module le lit
|
|
872
|
-
et genere automatiquement les callbacks `seedRBAC`, `createAdmin` et `optionalSeeds`.
|
|
873
|
-
|
|
874
|
-
### Creer un setup.json
|
|
875
|
-
|
|
876
|
-
**3 methodes :**
|
|
877
|
-
|
|
878
|
-
| Methode | Commande | Pour qui |
|
|
879
|
-
|---------|----------|----------|
|
|
880
|
-
| **Studio visuel** | Ouvrir [MostaSetup Studio](https://github.com/apolocine/mosta-setup-studio) | Non-developpeurs, design RBAC |
|
|
881
|
-
| **CLI interactif** | `npx mosta-setup` | Developpeurs en terminal |
|
|
882
|
-
| **CLI rapide** | `npx mosta-setup --quick --name MonApp --port 3000` | CI/CD, scripts |
|
|
883
|
-
|
|
884
|
-
### Structure du fichier
|
|
885
|
-
|
|
886
|
-
```json
|
|
887
|
-
{
|
|
888
|
-
"$schema": "https://mostajs.dev/schemas/setup.v1.json",
|
|
889
|
-
"app": {
|
|
890
|
-
"name": "MonApp",
|
|
891
|
-
"port": 3000,
|
|
892
|
-
"dbNamePrefix": "monappdb"
|
|
893
|
-
},
|
|
894
|
-
"env": {
|
|
895
|
-
"MOSTAJS_MODULES": "orm,auth,audit,rbac,settings,setup"
|
|
896
|
-
},
|
|
897
|
-
"rbac": {
|
|
898
|
-
"categories": [
|
|
899
|
-
{ "name": "admin", "label": "Administration", "icon": "Settings", "order": 0 }
|
|
900
|
-
],
|
|
901
|
-
"permissions": [
|
|
902
|
-
{ "code": "admin:access", "description": "Acceder au panneau", "category": "admin" }
|
|
903
|
-
],
|
|
904
|
-
"roles": [
|
|
905
|
-
{ "name": "admin", "description": "Administrateur", "permissions": ["*"] }
|
|
906
|
-
]
|
|
907
|
-
},
|
|
908
|
-
"seeds": [
|
|
909
|
-
{
|
|
910
|
-
"key": "products",
|
|
911
|
-
"label": "Produits demo",
|
|
912
|
-
"collection": "product",
|
|
913
|
-
"match": "slug",
|
|
914
|
-
"default": true,
|
|
915
|
-
"data": [
|
|
916
|
-
{ "name": "Produit A", "slug": "produit-a", "price": 1000 }
|
|
917
|
-
]
|
|
918
|
-
}
|
|
919
|
-
]
|
|
920
|
-
}
|
|
29
|
+
import { SetupWizard } from '@mostajs/setup'
|
|
30
|
+
export default function Setup() { return <SetupWizard /> }
|
|
921
31
|
```
|
|
922
32
|
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
### Utiliser loadSetupJson()
|
|
33
|
+
### 3. Config from setup.json (generated by Studio)
|
|
926
34
|
|
|
927
35
|
```typescript
|
|
928
|
-
// src/lib/setup-config.ts
|
|
929
36
|
import { loadSetupJson } from '@mostajs/setup'
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
// repoFactory : adapte a votre couche d'acces aux donnees
|
|
933
|
-
async function repoFactory(collection: string) {
|
|
934
|
-
const service = await import('@/dal/service')
|
|
935
|
-
const factories: Record<string, () => Promise<unknown>> = {
|
|
936
|
-
permissionCategory: service.permissionCategoryRepo,
|
|
937
|
-
permission: service.permissionRepo,
|
|
938
|
-
role: service.roleRepo,
|
|
939
|
-
user: service.userRepo,
|
|
940
|
-
activity: service.activityRepo,
|
|
941
|
-
}
|
|
942
|
-
return factories[collection]() as Promise<any>
|
|
943
|
-
}
|
|
944
|
-
|
|
945
|
-
export async function getSetupConfig(): Promise<MostaSetupConfig> {
|
|
946
|
-
return loadSetupJson('./setup.json', repoFactory)
|
|
947
|
-
}
|
|
948
|
-
```
|
|
949
|
-
|
|
950
|
-
```typescript
|
|
951
|
-
// src/app/api/setup/install/route.ts
|
|
952
|
-
import { runInstall } from '@mostajs/setup'
|
|
953
|
-
import type { InstallConfig } from '@mostajs/setup'
|
|
954
|
-
import { appNeedsSetup, getSetupConfig } from '@/lib/setup-config'
|
|
955
|
-
|
|
956
|
-
export async function POST(req: Request) {
|
|
957
|
-
if (!(await appNeedsSetup())) {
|
|
958
|
-
return Response.json({ error: 'Already installed' }, { status: 400 })
|
|
959
|
-
}
|
|
960
|
-
const body: InstallConfig = await req.json()
|
|
961
|
-
const config = await getSetupConfig()
|
|
962
|
-
return Response.json(await runInstall(body, config))
|
|
963
|
-
}
|
|
964
|
-
```
|
|
965
|
-
|
|
966
|
-
### Fonctionnalites des seeds JSON
|
|
967
|
-
|
|
968
|
-
| Champ | Type | Description |
|
|
969
|
-
|-------|------|-------------|
|
|
970
|
-
| `key` | `string` | Identifiant unique du seed |
|
|
971
|
-
| `label` | `string` | Label affiche dans le wizard (checkbox) |
|
|
972
|
-
| `collection` | `string` | Collection/table cible (doit matcher un schema enregistre) |
|
|
973
|
-
| `match` | `string` | Champ pour upsert idempotent (ex: `slug`, `email`) |
|
|
974
|
-
| `hashField` | `string` | Champ a hasher avec bcrypt avant insertion (ex: `password`) |
|
|
975
|
-
| `roleField` | `string` | Champ contenant un nom de role — resolu en ID a l'execution |
|
|
976
|
-
| `defaults` | `object` | Valeurs par defaut fusionnees dans chaque ligne |
|
|
977
|
-
| `default` | `boolean` | Si `true`, la checkbox est cochee par defaut dans le wizard |
|
|
978
|
-
| `data` | `array` | Tableau d'objets a seeder |
|
|
979
|
-
|
|
980
|
-
**Exemple : seed utilisateurs avec hash + resolution de role :**
|
|
981
|
-
```json
|
|
982
|
-
{
|
|
983
|
-
"key": "demoUsers",
|
|
984
|
-
"collection": "user",
|
|
985
|
-
"match": "email",
|
|
986
|
-
"hashField": "password",
|
|
987
|
-
"roleField": "role",
|
|
988
|
-
"defaults": { "status": "active" },
|
|
989
|
-
"data": [
|
|
990
|
-
{ "email": "agent@app.dz", "password": "Agent@123", "firstName": "Karim", "role": "agent_accueil" }
|
|
991
|
-
]
|
|
992
|
-
}
|
|
993
|
-
```
|
|
994
|
-
|
|
995
|
-
A l'execution :
|
|
996
|
-
1. `password` est hashe avec bcrypt (12 rounds)
|
|
997
|
-
2. `role: "agent_accueil"` est resolu en `roles: ["<id-du-role>"]`
|
|
998
|
-
3. `defaults.status` est fusionne → `status: "active"`
|
|
999
|
-
4. Si `match: "email"` et l'email existe deja → upsert (pas de doublon)
|
|
1000
|
-
|
|
1001
|
-
### setup.json manquant : upload automatique
|
|
1002
|
-
|
|
1003
|
-
Si le projet accede a `/setup` et que `setup.json` n'existe pas, la page affiche
|
|
1004
|
-
automatiquement un formulaire d'upload (drag & drop ou selection de fichier).
|
|
1005
|
-
|
|
1006
|
-
Pour activer cette fonctionnalite, ajoutez la route API :
|
|
1007
|
-
|
|
1008
|
-
```typescript
|
|
1009
|
-
// src/app/api/setup/setup-json/route.ts
|
|
1010
|
-
import { createSetupJsonHandler } from '@mostajs/setup'
|
|
1011
|
-
import { appNeedsSetup } from '@/lib/setup-config'
|
|
1012
|
-
|
|
1013
|
-
export const { GET, POST } = createSetupJsonHandler(appNeedsSetup)
|
|
1014
|
-
```
|
|
1015
|
-
|
|
1016
|
-
- **GET** `/api/setup/setup-json` → `{ exists: boolean, config?: {...} }`
|
|
1017
|
-
- **POST** `/api/setup/setup-json` → recoit le JSON, ecrit `./setup.json`
|
|
1018
|
-
|
|
1019
|
-
### Mixer JSON + code TypeScript
|
|
1020
|
-
|
|
1021
|
-
Les seeds simples (insert de donnees) vont dans `setup.json`. Les seeds complexes
|
|
1022
|
-
(relations, logique conditionnelle) restent en TypeScript :
|
|
1023
|
-
|
|
1024
|
-
```typescript
|
|
1025
|
-
const config = await loadSetupJson('./setup.json', repoFactory)
|
|
1026
|
-
// Ajouter un seed code-only
|
|
1027
|
-
config.optionalSeeds = [
|
|
1028
|
-
...(config.optionalSeeds ?? []),
|
|
1029
|
-
{ key: 'demoData', label: 'Donnees complexes', run: async () => { /* ... */ } },
|
|
1030
|
-
]
|
|
1031
|
-
```
|
|
1032
|
-
|
|
1033
|
-
### Validation
|
|
1034
|
-
|
|
1035
|
-
`loadSetupJson()` valide automatiquement :
|
|
1036
|
-
- `app.name` est requis
|
|
1037
|
-
- Chaque permission reference une categorie existante
|
|
1038
|
-
- Chaque role reference des permissions existantes (sauf `*`)
|
|
1039
|
-
- Erreur descriptive en cas de reference croisee invalide
|
|
1040
|
-
|
|
1041
|
-
### CLI : npx mosta-setup
|
|
1042
|
-
|
|
1043
|
-
```bash
|
|
1044
|
-
# Mode interactif (terminal)
|
|
1045
|
-
npx mosta-setup
|
|
1046
|
-
|
|
1047
|
-
# Mode rapide (CI, scripts, Dockerfile)
|
|
1048
|
-
npx mosta-setup --quick --name MonApp --port 4567 --db monappdb
|
|
1049
|
-
|
|
1050
|
-
# Avec modules
|
|
1051
|
-
npx mosta-setup --quick --name MonApp --modules "orm,auth,audit,rbac,settings,setup"
|
|
1052
|
-
|
|
1053
|
-
# Sortie stdout (pour pipe)
|
|
1054
|
-
npx mosta-setup --quick --name MonApp --stdout | jq .
|
|
37
|
+
const config = await loadSetupJson('./setup.json', dalRepoFactory)
|
|
1055
38
|
```
|
|
1056
39
|
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
## 10. FAQ / Troubleshooting
|
|
1060
|
-
|
|
1061
|
-
### L'installation tourne en boucle (GET /setup se repete)
|
|
1062
|
-
|
|
1063
|
-
**Cause** : `npm install` modifie `package.json` / `node_modules`, ce qui declenche un hot-reload Next.js et reinitialise le state React.
|
|
1064
|
-
|
|
1065
|
-
**Solution** : Le handler `install-modules` **skip les packages deja installes** dans `node_modules/`. Si le probleme persiste, ajoutez une persistence du state wizard dans `sessionStorage` (voir l'implementation de SecuAccess Pro).
|
|
1066
|
-
|
|
1067
|
-
### E11000 duplicate key error (MongoDB)
|
|
1068
|
-
|
|
1069
|
-
**Cause** : Un champ `unique: true` sans `required: true` dans le schema. MongoDB indexe les `null` et refuse le doublon.
|
|
1070
|
-
|
|
1071
|
-
**Solution** : Ajoutez `sparse: true` au champ dans votre `EntitySchema`. Cela fonctionne avec MongoDB (index sparse) et est ignore sans risque par les dialectes SQL.
|
|
40
|
+
### 4. Module Discovery
|
|
1072
41
|
|
|
1073
42
|
```typescript
|
|
1074
|
-
|
|
1075
|
-
```
|
|
1076
|
-
|
|
1077
|
-
### JSON.parse: unexpected character at line 1 column 1
|
|
43
|
+
import { discoverModules, collectSchemas } from '@mostajs/setup/lib/module-registry'
|
|
1078
44
|
|
|
1079
|
-
|
|
45
|
+
const modules = await discoverModules(['rbac', 'audit', 'settings'])
|
|
46
|
+
const allSchemas = collectSchemas(modules)
|
|
1080
47
|
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
async function safeJson(res: Response) {
|
|
1085
|
-
try { return JSON.parse(await res.text()) }
|
|
1086
|
-
catch { return null }
|
|
48
|
+
// Seed each module
|
|
49
|
+
for (const mod of modules) {
|
|
50
|
+
await mod.seed(setupJson[mod.name])
|
|
1087
51
|
}
|
|
1088
|
-
```
|
|
1089
|
-
|
|
1090
|
-
### npm search timeout (pas d'internet)
|
|
1091
|
-
|
|
1092
|
-
Le handler `detect-modules` a un timeout de 10 secondes sur `npm search`. En cas d'echec (offline, timeout), il retourne la **liste statique** des 7 modules connus. Aucune action necessaire.
|
|
1093
|
-
|
|
1094
|
-
### Comment ajouter un nouveau module a la liste statique ?
|
|
1095
|
-
|
|
1096
|
-
Editez `packages/mosta-setup/data/module-definitions.ts` et ajoutez une entree a `MODULES` :
|
|
1097
52
|
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
packageName: '@mostajs/notifications',
|
|
1102
|
-
localDir: 'mosta-notifications', // si disponible localement
|
|
1103
|
-
label: 'Notifications',
|
|
1104
|
-
description: 'Push, email, SMS',
|
|
1105
|
-
icon: '🔔',
|
|
1106
|
-
default: false,
|
|
1107
|
-
dependsOn: ['orm'],
|
|
1108
|
-
}
|
|
53
|
+
// Create admin via rbac
|
|
54
|
+
const rbac = modules.find(m => m.createAdmin)
|
|
55
|
+
await rbac.createAdmin({ email, password, firstName, lastName })
|
|
1109
56
|
```
|
|
1110
57
|
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
---
|
|
58
|
+
### 5. Dual ORM/NET Mode
|
|
1114
59
|
|
|
1115
|
-
|
|
60
|
+
The wizard supports both:
|
|
61
|
+
- **ORM mode**: direct DB connection (dialect selection, URI, create DB, apply schema)
|
|
62
|
+
- **NET mode**: remote @mostajs/net server (test connection, upload schemas, wait restart)
|
|
1116
63
|
|
|
1117
|
-
|
|
1118
|
-
|---------|:---:|:---:|-------------|
|
|
1119
|
-
| [@mostajs/orm](https://www.npmjs.com/package/@mostajs/orm) | — | — | Multi-dialect ORM, 13 SGBD (requis) |
|
|
1120
|
-
| [@mostajs/auth](https://www.npmjs.com/package/@mostajs/auth) | oui | non | Authentication NextAuth, sessions |
|
|
1121
|
-
| [@mostajs/audit](https://www.npmjs.com/package/@mostajs/audit) | oui | non | Audit logging, tracabilite |
|
|
1122
|
-
| [@mostajs/rbac](https://www.npmjs.com/package/@mostajs/rbac) | oui | non | Roles & Permissions RBAC |
|
|
1123
|
-
| [@mostajs/settings](https://www.npmjs.com/package/@mostajs/settings) | oui | non | Parametres cle-valeur |
|
|
1124
|
-
| [@mostajs/face](https://www.npmjs.com/package/@mostajs/face) | **non** | **oui** | Reconnaissance faciale (independant) |
|
|
64
|
+
### 6. Setup Wizard Features
|
|
1125
65
|
|
|
1126
|
-
|
|
66
|
+
- Welcome: choose ORM or NET mode
|
|
67
|
+
- Modules: select @mostajs modules
|
|
68
|
+
- Dialect/Database or NET config
|
|
69
|
+
- Admin user creation (delegates to @mostajs/rbac)
|
|
70
|
+
- Schema upload (JSON, ZIP, scan directory)
|
|
71
|
+
- Seeds (from setup.json)
|
|
72
|
+
- Summary with config review
|
|
1127
73
|
|
|
1128
|
-
|
|
74
|
+
### 7. API Routes
|
|
1129
75
|
|
|
1130
|
-
```typescript
|
|
1131
|
-
// src/app/api/setup/[...slug]/route.ts — 7 lines replaces 6 files
|
|
1132
|
-
import { createSetupRoutes } from '@mostajs/setup'
|
|
1133
|
-
import { appNeedsSetup, getSetupConfig } from '@/lib/setup-config'
|
|
1134
|
-
|
|
1135
|
-
export const { GET, POST, DELETE, PATCH } = createSetupRoutes({
|
|
1136
|
-
needsSetup: appNeedsSetup,
|
|
1137
|
-
getSetupConfig,
|
|
1138
|
-
})
|
|
1139
76
|
```
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
2. Gets the `role` repo to resolve the `admin` role
|
|
1148
|
-
3. Creates the admin user with bcrypt-hashed password and admin role
|
|
1149
|
-
|
|
1150
|
-
No need to define `createAdmin` manually — it comes from `loadSetupJson()`.
|
|
1151
|
-
|
|
1152
|
-
## lookupFields (cross-entity references in seeds)
|
|
1153
|
-
|
|
1154
|
-
Resolve a field value from another collection before inserting seed data :
|
|
1155
|
-
|
|
1156
|
-
```json
|
|
1157
|
-
{
|
|
1158
|
-
"key": "demoClients",
|
|
1159
|
-
"collection": "client",
|
|
1160
|
-
"lookupFields": {
|
|
1161
|
-
"createdBy": { "collection": "user", "match": "status", "value": "active" }
|
|
1162
|
-
},
|
|
1163
|
-
"data": [
|
|
1164
|
-
{ "firstName": "Samir", "lastName": "Boudjema", "phone": "0550100001" }
|
|
1165
|
-
]
|
|
1166
|
-
}
|
|
77
|
+
GET /api/setup/status — needsSetup check
|
|
78
|
+
POST /api/setup/install — run full installation
|
|
79
|
+
POST /api/setup/test-db — test DB connection
|
|
80
|
+
POST /api/setup/create-db — create database
|
|
81
|
+
POST /api/setup/create-admin — create admin (delegates to rbac)
|
|
82
|
+
GET /api/setup/detect-modules — list available modules
|
|
83
|
+
POST /api/setup/net-test — test NET server connection
|
|
1167
84
|
```
|
|
1168
|
-
|
|
1169
|
-
`lookupFields.createdBy` : find the first `user` where `status = "active"`, inject its `id` as `createdBy` in every data item.
|
|
1170
|
-
|
|
1171
|
-
Useful for seeds that reference entities created by earlier seeds (RBAC roles, admin user, etc.).
|
|
1172
|
-
|
|
1173
|
-
## Dynamic Seeds in SetupWizard
|
|
1174
|
-
|
|
1175
|
-
The wizard automatically loads available seeds from the `/api/setup/setup-json` endpoint and displays them as checkboxes. Seeds with `"default": true` in setup.json are pre-checked.
|
|
1176
|
-
|
|
1177
|
-
No need to hardcode seed checkboxes — add a seed to `setup.json` and it appears in the wizard automatically.
|
|
1178
|
-
|
|
1179
|
-
## License
|
|
1180
|
-
|
|
1181
|
-
MIT — (c) 2025-2026 Dr Hamid MADANI <drmdh@msn.com>
|