@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
|
}
|
package/dist/api/routes.js
CHANGED
|
@@ -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') })),
|
|
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.
|
|
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": {
|