@mostajs/setup 2.1.22 → 2.1.25

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.
@@ -8,10 +8,11 @@ import { runInstall } from '../lib/setup.js';
8
8
  */
9
9
  export function createInstallHandler(needsSetup, setupConfig) {
10
10
  async function POST(req) {
11
- if (!(await needsSetup())) {
12
- return Response.json({ error: 'Already installed' }, { status: 400 });
13
- }
14
11
  const body = await req.json();
12
+ // Skip needsSetup check if admin was already created (wizard creates admin before seed)
13
+ if (!body.skipCheck && !(await needsSetup())) {
14
+ return Response.json({ ok: false, error: 'Installation deja effectuee' }, { status: 400 });
15
+ }
15
16
  const result = await runInstall(body, setupConfig);
16
17
  return Response.json(result);
17
18
  }
@@ -124,6 +124,29 @@ export function createSetupRoutes(config) {
124
124
  }
125
125
  },
126
126
  },
127
+ // Create admin user — delegates to @mostajs/rbac via module discovery
128
+ 'create-admin': {
129
+ POST: async (req) => {
130
+ const body = await req.json();
131
+ if (!body?.email || !body?.password || !body?.firstName) {
132
+ return Response.json({ ok: false, error: 'email, password, firstName requis' }, { status: 400 });
133
+ }
134
+ try {
135
+ // Try rbac module createAdmin (works in both ORM and NET mode)
136
+ const { createAdmin } = await import('@mostajs/rbac/lib/create-admin');
137
+ const result = await createAdmin({
138
+ email: body.email,
139
+ password: body.password,
140
+ firstName: body.firstName,
141
+ lastName: body.lastName || '',
142
+ });
143
+ return Response.json(result);
144
+ }
145
+ catch (e) {
146
+ return Response.json({ ok: false, error: e instanceof Error ? e.message : 'Erreur creation admin' });
147
+ }
148
+ },
149
+ },
127
150
  };
128
151
  return table;
129
152
  }
@@ -369,6 +369,8 @@ export default function SetupWizard({ t: tProp, onComplete, endpoints = {}, dbNa
369
369
  const [netTesting, setNetTesting] = useState(false);
370
370
  const [schemaUploadStatus, setSchemaUploadStatus] = useState(null);
371
371
  const [schemasReady, setSchemasReady] = useState(false);
372
+ const [adminSaving, setAdminSaving] = useState(false);
373
+ const [adminSaveResult, setAdminSaveResult] = useState(null);
372
374
  const [currentStep, setCurrentStep] = useState(0);
373
375
  const [dialect, setDialect] = useState('mongodb');
374
376
  const [dbConfig, setDbConfig] = useState({ ...DIALECT_DEFAULTS.mongodb, name: `${dbNamePrefix}_prod` });
@@ -747,6 +749,7 @@ export default function SetupWizard({ t: tProp, onComplete, endpoints = {}, dbNa
747
749
  admin: { email: adminConfig.email, password: adminConfig.password, firstName: adminConfig.firstName, lastName: adminConfig.lastName },
748
750
  seed: seedOptions,
749
751
  modules: selectedModules,
752
+ skipCheck: adminSaveResult?.ok || false, // Skip needsSetup check if admin was created via wizard
750
753
  }),
751
754
  });
752
755
  const data = await safeJson(res);
@@ -965,7 +968,41 @@ export default function SetupWizard({ t: tProp, onComplete, endpoints = {}, dbNa
965
968
  }, children: "\uD83D\uDCC1 Scanner un r\u00E9pertoire" })] }), schemaUploadStatus && (_jsx("div", { style: { fontSize: 13, fontWeight: 500, color: schemaUploadStatus.color }, children: schemaUploadStatus.phase }))] })), netTestResult?.ok && (netTestResult.entities?.length ?? 0) > 0 && (_jsx("div", { style: {
966
969
  padding: 12, borderRadius: 8, marginBottom: 16,
967
970
  backgroundColor: '#f0fdf4', border: '1px solid #bbf7d0',
968
- }, children: _jsxs("div", { style: { fontWeight: 600, color: '#166534' }, children: ["\u2705 Serveur pr\u00EAt \u2014 ", netTestResult.entities?.length, " entit\u00E9s charg\u00E9es"] }) })), _jsxs("div", { style: S.navRow, children: [_jsxs("button", { style: S.btn('outline'), onClick: goBack, children: ["\u2190 ", t('setup.back')] }), _jsxs("button", { style: S.btn('primary', !canGoNext()), onClick: goNext, disabled: !canGoNext(), children: [t('setup.next'), " \u2192"] })] })] })), step === 'admin' && (_jsxs("div", { children: [_jsxs("div", { style: S.sectionHeader, children: [_jsx("span", { style: S.sectionIcon, children: "\uD83D\uDC64" }), _jsxs("div", { children: [_jsx("div", { style: S.sectionTitle, children: t('setup.admin.title') }), _jsx("div", { style: S.sectionDesc, children: t('setup.admin.description') })] })] }), _jsxs("div", { style: S.formRow, children: [_jsxs("div", { style: S.formGroup, children: [_jsx("label", { style: S.label, children: t('setup.admin.firstName') }), _jsx("input", { style: S.input, value: adminConfig.firstName, onChange: e => setAdminConfig({ ...adminConfig, firstName: e.target.value }) })] }), _jsxs("div", { style: S.formGroup, children: [_jsx("label", { style: S.label, children: t('setup.admin.lastName') }), _jsx("input", { style: S.input, value: adminConfig.lastName, onChange: e => setAdminConfig({ ...adminConfig, lastName: e.target.value }) })] })] }), _jsxs("div", { style: S.formGroup, children: [_jsx("label", { style: S.label, children: t('setup.admin.email') }), _jsx("input", { style: S.input, type: "email", value: adminConfig.email, onChange: e => setAdminConfig({ ...adminConfig, email: e.target.value }), placeholder: "admin@example.com" })] }), _jsxs("div", { style: S.formRow, children: [_jsxs("div", { style: S.formGroup, children: [_jsx("label", { style: S.label, children: t('setup.admin.password') }), _jsx("input", { style: S.input, type: "password", value: adminConfig.password, onChange: e => setAdminConfig({ ...adminConfig, password: e.target.value }) })] }), _jsxs("div", { style: S.formGroup, children: [_jsx("label", { style: S.label, children: t('setup.admin.confirmPassword') }), _jsx("input", { style: S.input, type: "password", value: adminConfig.confirmPassword, onChange: e => setAdminConfig({ ...adminConfig, confirmPassword: e.target.value }) })] })] }), adminConfig.password && adminConfig.confirmPassword && adminConfig.password !== adminConfig.confirmPassword && (_jsx("p", { style: { fontSize: 13, color: '#dc2626' }, children: t('setup.admin.passwordMismatch') })), _jsxs("div", { style: S.navRow, children: [_jsxs("button", { style: S.btn('outline'), onClick: goBack, children: ["\u2190 ", t('setup.back')] }), _jsxs("button", { style: S.btn('primary', !canGoNext()), onClick: goNext, disabled: !canGoNext(), children: [t('setup.next'), " \u2192"] })] })] })), step === 'summary' && (_jsxs("div", { children: [_jsxs("div", { style: S.sectionHeader, children: [_jsx("span", { style: S.sectionIcon, children: "\u2699\uFE0F" }), _jsxs("div", { children: [_jsx("div", { style: S.sectionTitle, children: t('setup.summary.title') }), _jsx("div", { style: S.sectionDesc, children: t('setup.summary.description') })] })] }), _jsxs("div", { style: S.summaryCard, children: [_jsx("div", { style: S.summaryTitle, children: setupMode === 'net' ? 'Serveur @mostajs/net' : t('setup.summary.dbConfig') }), _jsxs("div", { style: S.summaryText, children: [_jsx("span", { style: { fontFamily: 'monospace' }, children: dbSummaryLabel() }), setupMode === 'net' && netTransport && _jsxs("span", { style: { display: 'block', marginTop: 4 }, children: ["Transport: ", netTransport] }), setupMode !== 'net' && dialect !== 'sqlite' && dbConfig.user && _jsxs("span", { style: { display: 'block', marginTop: 4 }, children: ["Utilisateur: ", dbConfig.user] })] })] }), _jsxs("div", { style: S.summaryCard, children: [_jsx("div", { style: S.summaryTitle, children: t('setup.summary.adminConfig') }), _jsxs("div", { style: S.summaryText, children: [_jsxs("div", { children: [adminConfig.firstName, " ", adminConfig.lastName] }), _jsx("div", { children: adminConfig.email })] })] }), _jsxs("div", { style: S.summaryCard, children: [_jsx("div", { style: S.summaryTitle, children: t('setup.modules.title') }), _jsx("div", { style: S.flexWrap, children: selectedModules.map(key => {
971
+ }, children: _jsxs("div", { style: { fontWeight: 600, color: '#166534' }, children: ["\u2705 Serveur pr\u00EAt \u2014 ", netTestResult.entities?.length, " entit\u00E9s charg\u00E9es"] }) })), _jsxs("div", { style: S.navRow, children: [_jsxs("button", { style: S.btn('outline'), onClick: goBack, children: ["\u2190 ", t('setup.back')] }), _jsxs("button", { style: S.btn('primary', !canGoNext()), onClick: goNext, disabled: !canGoNext(), children: [t('setup.next'), " \u2192"] })] })] })), step === 'admin' && (_jsxs("div", { children: [_jsxs("div", { style: S.sectionHeader, children: [_jsx("span", { style: S.sectionIcon, children: "\uD83D\uDC64" }), _jsxs("div", { children: [_jsx("div", { style: S.sectionTitle, children: t('setup.admin.title') }), _jsx("div", { style: S.sectionDesc, children: t('setup.admin.description') })] })] }), _jsxs("div", { style: S.formRow, children: [_jsxs("div", { style: S.formGroup, children: [_jsx("label", { style: S.label, children: t('setup.admin.firstName') }), _jsx("input", { style: S.input, value: adminConfig.firstName, onChange: e => setAdminConfig({ ...adminConfig, firstName: e.target.value }) })] }), _jsxs("div", { style: S.formGroup, children: [_jsx("label", { style: S.label, children: t('setup.admin.lastName') }), _jsx("input", { style: S.input, value: adminConfig.lastName, onChange: e => setAdminConfig({ ...adminConfig, lastName: e.target.value }) })] })] }), _jsxs("div", { style: S.formGroup, children: [_jsx("label", { style: S.label, children: t('setup.admin.email') }), _jsx("input", { style: S.input, type: "email", value: adminConfig.email, onChange: e => setAdminConfig({ ...adminConfig, email: e.target.value }), placeholder: "admin@example.com" })] }), _jsxs("div", { style: S.formRow, children: [_jsxs("div", { style: S.formGroup, children: [_jsx("label", { style: S.label, children: t('setup.admin.password') }), _jsx("input", { style: S.input, type: "password", value: adminConfig.password, onChange: e => setAdminConfig({ ...adminConfig, password: e.target.value }) })] }), _jsxs("div", { style: S.formGroup, children: [_jsx("label", { style: S.label, children: t('setup.admin.confirmPassword') }), _jsx("input", { style: S.input, type: "password", value: adminConfig.confirmPassword, onChange: e => setAdminConfig({ ...adminConfig, confirmPassword: e.target.value }) })] })] }), adminConfig.password && adminConfig.confirmPassword && adminConfig.password !== adminConfig.confirmPassword && (_jsx("p", { style: { fontSize: 13, color: '#dc2626' }, children: t('setup.admin.passwordMismatch') })), setupMode === 'net' && (_jsxs("div", { style: { marginTop: 16, marginBottom: 16 }, children: [_jsx("button", { style: { ...S.btn('primary'), backgroundColor: '#16a34a' }, disabled: adminSaving || !adminConfig.email || !adminConfig.password || !adminConfig.firstName || adminConfig.password !== adminConfig.confirmPassword, onClick: async () => {
972
+ setAdminSaving(true);
973
+ setAdminSaveResult(null);
974
+ try {
975
+ // 1. Hash password côté serveur via un endpoint dédié, ou côté client
976
+ // On envoie le mot de passe en clair — le serveur Next.js le hashera
977
+ const res = await fetch(ep.setupJson.replace('setup-json', 'create-admin'), {
978
+ method: 'POST',
979
+ headers: { 'Content-Type': 'application/json' },
980
+ body: JSON.stringify({
981
+ url: netUrl,
982
+ email: adminConfig.email,
983
+ password: adminConfig.password,
984
+ firstName: adminConfig.firstName,
985
+ lastName: adminConfig.lastName,
986
+ }),
987
+ });
988
+ const data = await res.json();
989
+ if (data.ok) {
990
+ setAdminSaveResult({ ok: true, message: `✅ Admin ${adminConfig.email} créé sur le serveur NET` });
991
+ }
992
+ else {
993
+ setAdminSaveResult({ ok: false, message: `❌ ${data.error || 'Erreur'}` });
994
+ }
995
+ }
996
+ catch (err) {
997
+ setAdminSaveResult({ ok: false, message: `❌ ${err.message}` });
998
+ }
999
+ setAdminSaving(false);
1000
+ }, children: adminSaving ? '⏳ Enregistrement...' : '💾 Enregistrer l\'admin sur le serveur' }), adminSaveResult && (_jsx("div", { style: {
1001
+ marginTop: 8, padding: 8, borderRadius: 6, fontSize: 13, fontWeight: 500,
1002
+ backgroundColor: adminSaveResult.ok ? '#f0fdf4' : '#fef2f2',
1003
+ color: adminSaveResult.ok ? '#166534' : '#991b1b',
1004
+ border: `1px solid ${adminSaveResult.ok ? '#bbf7d0' : '#fecaca'}`,
1005
+ }, children: adminSaveResult.message }))] })), _jsxs("div", { style: S.navRow, children: [_jsxs("button", { style: S.btn('outline'), onClick: goBack, children: ["\u2190 ", t('setup.back')] }), _jsxs("button", { style: S.btn('primary', !canGoNext()), onClick: goNext, disabled: !canGoNext(), children: [t('setup.next'), " \u2192"] })] })] })), step === 'summary' && (_jsxs("div", { children: [_jsxs("div", { style: S.sectionHeader, children: [_jsx("span", { style: S.sectionIcon, children: "\u2699\uFE0F" }), _jsxs("div", { children: [_jsx("div", { style: S.sectionTitle, children: t('setup.summary.title') }), _jsx("div", { style: S.sectionDesc, children: t('setup.summary.description') })] })] }), _jsxs("div", { style: S.summaryCard, children: [_jsx("div", { style: S.summaryTitle, children: setupMode === 'net' ? 'Serveur @mostajs/net' : t('setup.summary.dbConfig') }), _jsxs("div", { style: S.summaryText, children: [_jsx("span", { style: { fontFamily: 'monospace' }, children: dbSummaryLabel() }), setupMode === 'net' && netTransport && _jsxs("span", { style: { display: 'block', marginTop: 4 }, children: ["Transport: ", netTransport] }), setupMode !== 'net' && dialect !== 'sqlite' && dbConfig.user && _jsxs("span", { style: { display: 'block', marginTop: 4 }, children: ["Utilisateur: ", dbConfig.user] })] })] }), _jsxs("div", { style: S.summaryCard, children: [_jsx("div", { style: S.summaryTitle, children: t('setup.summary.adminConfig') }), _jsxs("div", { style: S.summaryText, children: [_jsxs("div", { children: [adminConfig.firstName, " ", adminConfig.lastName] }), _jsx("div", { children: adminConfig.email })] })] }), _jsxs("div", { style: S.summaryCard, children: [_jsx("div", { style: S.summaryTitle, children: t('setup.modules.title') }), _jsx("div", { style: S.flexWrap, children: selectedModules.map(key => {
969
1006
  const mod = availableModules.find(m => m.key === key);
970
1007
  return mod ? (_jsxs("span", { style: {
971
1008
  display: 'inline-flex', alignItems: 'center', gap: 4,
@@ -0,0 +1,21 @@
1
+ export interface ModuleDescriptor {
2
+ name: string;
3
+ label: string;
4
+ getSchemas: () => any[];
5
+ seed: (options?: any) => Promise<any>;
6
+ createAdmin?: (options: {
7
+ email: string;
8
+ password: string;
9
+ firstName: string;
10
+ lastName: string;
11
+ }) => Promise<any>;
12
+ }
13
+ /**
14
+ * Discover installed @mostajs modules and return their descriptors.
15
+ * Tries dynamic import of each module's module-info.
16
+ */
17
+ export declare function discoverModules(moduleNames?: string[]): Promise<ModuleDescriptor[]>;
18
+ /**
19
+ * Collect all schemas from discovered modules.
20
+ */
21
+ export declare function collectSchemas(modules: ModuleDescriptor[]): any[];
@@ -0,0 +1,58 @@
1
+ // @mostajs/setup — Module registry
2
+ // Discovers installed @mostajs modules and their schemas/seeds
3
+ // Author: Dr Hamid MADANI drmdh@msn.com
4
+ /**
5
+ * Discover installed @mostajs modules and return their descriptors.
6
+ * Tries dynamic import of each module's module-info.
7
+ */
8
+ export async function discoverModules(moduleNames) {
9
+ const modules = [];
10
+ const candidates = moduleNames || ['rbac', 'audit', 'settings'];
11
+ for (const name of candidates) {
12
+ try {
13
+ // Try importing module-info from each @mostajs module
14
+ let info = null;
15
+ switch (name) {
16
+ case 'rbac':
17
+ case 'auth': // auth delegates to rbac
18
+ info = await import('@mostajs/rbac/lib/module-info').catch(() => null);
19
+ break;
20
+ case 'audit':
21
+ info = await import('@mostajs/audit/lib/module-info').catch(() => null);
22
+ break;
23
+ case 'settings':
24
+ info = await import('@mostajs/settings/lib/module-info').catch(() => null);
25
+ break;
26
+ }
27
+ if (info?.moduleInfo) {
28
+ modules.push({
29
+ name: info.moduleInfo.name,
30
+ label: info.moduleInfo.label,
31
+ getSchemas: info.moduleInfo.schemas || info.getSchemas,
32
+ seed: info.moduleInfo.seed,
33
+ createAdmin: info.moduleInfo.createAdmin,
34
+ });
35
+ }
36
+ }
37
+ catch {
38
+ // Module not installed — skip
39
+ }
40
+ }
41
+ return modules;
42
+ }
43
+ /**
44
+ * Collect all schemas from discovered modules.
45
+ */
46
+ export function collectSchemas(modules) {
47
+ const schemas = [];
48
+ const seen = new Set();
49
+ for (const mod of modules) {
50
+ for (const schema of mod.getSchemas()) {
51
+ if (!seen.has(schema.name)) {
52
+ seen.add(schema.name);
53
+ schemas.push(schema);
54
+ }
55
+ }
56
+ }
57
+ return schemas;
58
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mostajs/setup",
3
- "version": "2.1.22",
3
+ "version": "2.1.25",
4
4
  "description": "Reusable setup wizard module — multi-dialect DB configuration, .env.local writer, seed runner",
5
5
  "author": "Dr Hamid MADANI <drmdh@msn.com>",
6
6
  "license": "MIT",
@@ -104,8 +104,11 @@
104
104
  "prepublishOnly": "npm run build"
105
105
  },
106
106
  "dependencies": {
107
+ "@mostajs/audit": "^2.0.2",
107
108
  "@mostajs/net": "^2.0.0",
108
109
  "@mostajs/orm": "^1.7.0",
110
+ "@mostajs/rbac": "^2.0.3",
111
+ "@mostajs/settings": "^2.0.3",
109
112
  "bcryptjs": "^2.4.3"
110
113
  },
111
114
  "devDependencies": {