@mostajs/setup 2.1.9 → 2.1.11
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/dist/lib/net-client.d.ts +5 -0
- package/dist/lib/net-client.js +30 -4
- package/dist/lib/setup.js +89 -16
- package/package.json +1 -1
package/dist/lib/net-client.d.ts
CHANGED
|
@@ -14,7 +14,12 @@ export interface NetHealthResponse {
|
|
|
14
14
|
export declare class NetClient {
|
|
15
15
|
private baseUrl;
|
|
16
16
|
private headers;
|
|
17
|
+
private collectionMap;
|
|
17
18
|
constructor(config: NetClientConfig);
|
|
19
|
+
/** Load schema config and build collection name mapping */
|
|
20
|
+
loadCollectionMap(): Promise<void>;
|
|
21
|
+
/** Resolve a seed collection name to the actual REST collection */
|
|
22
|
+
private resolveCollection;
|
|
18
23
|
/** Test connectivity to the NET server */
|
|
19
24
|
health(): Promise<NetHealthResponse>;
|
|
20
25
|
/** Test database connection via NET */
|
package/dist/lib/net-client.js
CHANGED
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
export class NetClient {
|
|
9
9
|
baseUrl;
|
|
10
10
|
headers;
|
|
11
|
+
collectionMap = {}; // seedName → REST collection
|
|
11
12
|
constructor(config) {
|
|
12
13
|
this.baseUrl = config.url.replace(/\/$/, '');
|
|
13
14
|
this.headers = { 'Content-Type': 'application/json' };
|
|
@@ -15,6 +16,27 @@ export class NetClient {
|
|
|
15
16
|
this.headers['Authorization'] = `Bearer ${config.apiKey}`;
|
|
16
17
|
}
|
|
17
18
|
}
|
|
19
|
+
/** Load schema config and build collection name mapping */
|
|
20
|
+
async loadCollectionMap() {
|
|
21
|
+
try {
|
|
22
|
+
const config = await this.getSchemasConfig();
|
|
23
|
+
for (const s of config.schemas || []) {
|
|
24
|
+
// Map: entity name lowercase → actual collection
|
|
25
|
+
// e.g. "Experience" → "experiences", "PermissionCategory" → "permission_categories"
|
|
26
|
+
this.collectionMap[s.name.toLowerCase()] = s.collection;
|
|
27
|
+
// Also map collection without trailing 's' → actual collection
|
|
28
|
+
// e.g. "experience" → "experiences", "passenger" → "passengers"
|
|
29
|
+
const singular = s.collection.replace(/s$/, '').replace(/ies$/, 'y');
|
|
30
|
+
this.collectionMap[singular] = s.collection;
|
|
31
|
+
this.collectionMap[s.collection] = s.collection;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
catch { }
|
|
35
|
+
}
|
|
36
|
+
/** Resolve a seed collection name to the actual REST collection */
|
|
37
|
+
resolveCollection(name) {
|
|
38
|
+
return this.collectionMap[name] || this.collectionMap[name.toLowerCase()] || name;
|
|
39
|
+
}
|
|
18
40
|
// ── Health & Status ──────────────────────────────
|
|
19
41
|
/** Test connectivity to the NET server */
|
|
20
42
|
async health() {
|
|
@@ -47,7 +69,8 @@ export class NetClient {
|
|
|
47
69
|
// ── CRUD ─────────────────────────────────────────
|
|
48
70
|
/** Create an entity via REST */
|
|
49
71
|
async create(collection, data) {
|
|
50
|
-
const
|
|
72
|
+
const col = this.resolveCollection(collection);
|
|
73
|
+
const res = await fetch(`${this.baseUrl}/api/v1/${col}`, {
|
|
51
74
|
method: 'POST',
|
|
52
75
|
headers: this.headers,
|
|
53
76
|
body: JSON.stringify(data),
|
|
@@ -59,8 +82,9 @@ export class NetClient {
|
|
|
59
82
|
}
|
|
60
83
|
/** Find one entity by filter */
|
|
61
84
|
async findOne(collection, filter) {
|
|
85
|
+
const col = this.resolveCollection(collection);
|
|
62
86
|
const params = new URLSearchParams({ filter: JSON.stringify(filter), limit: '1' });
|
|
63
|
-
const res = await fetch(`${this.baseUrl}/api/v1/${
|
|
87
|
+
const res = await fetch(`${this.baseUrl}/api/v1/${col}?${params}`, { headers: this.headers });
|
|
64
88
|
const json = await res.json();
|
|
65
89
|
if (json.status === 'error')
|
|
66
90
|
return null;
|
|
@@ -69,9 +93,10 @@ export class NetClient {
|
|
|
69
93
|
}
|
|
70
94
|
/** Upsert: find by filter, update if exists, create if not */
|
|
71
95
|
async upsert(collection, filter, data) {
|
|
96
|
+
const col = this.resolveCollection(collection);
|
|
72
97
|
const existing = await this.findOne(collection, filter);
|
|
73
98
|
if (existing) {
|
|
74
|
-
const res = await fetch(`${this.baseUrl}/api/v1/${
|
|
99
|
+
const res = await fetch(`${this.baseUrl}/api/v1/${col}/${existing.id}`, {
|
|
75
100
|
method: 'PUT',
|
|
76
101
|
headers: this.headers,
|
|
77
102
|
body: JSON.stringify(data),
|
|
@@ -85,8 +110,9 @@ export class NetClient {
|
|
|
85
110
|
}
|
|
86
111
|
/** Count entities in a collection */
|
|
87
112
|
async count(collection, filter) {
|
|
113
|
+
const col = this.resolveCollection(collection);
|
|
88
114
|
const params = filter ? new URLSearchParams({ filter: JSON.stringify(filter) }) : '';
|
|
89
|
-
const res = await fetch(`${this.baseUrl}/api/v1/${
|
|
115
|
+
const res = await fetch(`${this.baseUrl}/api/v1/${col}/count${params ? '?' + params : ''}`, { headers: this.headers });
|
|
90
116
|
const json = await res.json();
|
|
91
117
|
return json.data ?? 0;
|
|
92
118
|
}
|
package/dist/lib/setup.js
CHANGED
|
@@ -137,25 +137,67 @@ async function runNetInstall(installConfig, setupConfig) {
|
|
|
137
137
|
port: setupConfig.defaultPort,
|
|
138
138
|
});
|
|
139
139
|
const seeded = [];
|
|
140
|
-
// 2. Verify NET server is reachable
|
|
140
|
+
// 2. Verify NET server is reachable + load collection mapping
|
|
141
141
|
const health = await net.health();
|
|
142
142
|
if (!health.entities?.length) {
|
|
143
143
|
return { ok: false, error: 'Serveur NET accessible mais aucun schema chargé', needsRestart: false };
|
|
144
144
|
}
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
145
|
+
await net.loadCollectionMap();
|
|
146
|
+
// 3. Seed RBAC via NET REST
|
|
147
|
+
// Read setup.json directly to get RBAC definitions
|
|
148
|
+
const fs = await import('fs');
|
|
149
|
+
const path = await import('path');
|
|
150
|
+
let setupJson = null;
|
|
151
|
+
const setupJsonPath = path.resolve(process.cwd(), 'setup.json');
|
|
152
|
+
if (fs.existsSync(setupJsonPath)) {
|
|
153
|
+
try {
|
|
154
|
+
setupJson = JSON.parse(fs.readFileSync(setupJsonPath, 'utf-8'));
|
|
155
|
+
}
|
|
156
|
+
catch { }
|
|
157
|
+
}
|
|
158
|
+
if (setupJson?.rbac) {
|
|
159
|
+
const rbac = setupJson.rbac;
|
|
160
|
+
// 3a. Upsert categories
|
|
161
|
+
if (rbac.categories?.length) {
|
|
162
|
+
for (const cat of rbac.categories) {
|
|
163
|
+
await net.upsert('permission_categories', { name: cat.name }, {
|
|
164
|
+
name: cat.name, label: cat.label, description: cat.description ?? '',
|
|
165
|
+
icon: cat.icon ?? '', order: cat.order ?? 0, system: cat.system ?? true,
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
seeded.push('categories');
|
|
169
|
+
}
|
|
170
|
+
// 3b. Upsert permissions — build code→ID map
|
|
171
|
+
const permissionMap = {};
|
|
172
|
+
if (rbac.permissions?.length) {
|
|
173
|
+
for (const pDef of rbac.permissions) {
|
|
174
|
+
const displayName = pDef.name ?? pDef.code;
|
|
175
|
+
const perm = await net.upsert('permissions', { name: displayName }, {
|
|
176
|
+
name: displayName, description: pDef.description, category: pDef.category,
|
|
177
|
+
});
|
|
178
|
+
permissionMap[pDef.code] = perm.id;
|
|
179
|
+
}
|
|
180
|
+
seeded.push('permissions');
|
|
181
|
+
}
|
|
182
|
+
// 3c. Upsert roles with permission IDs
|
|
183
|
+
if (rbac.roles?.length) {
|
|
184
|
+
const allPermIds = Object.values(permissionMap);
|
|
185
|
+
for (const roleDef of rbac.roles) {
|
|
186
|
+
const permissionIds = roleDef.permissions.includes('*')
|
|
187
|
+
? allPermIds
|
|
188
|
+
: roleDef.permissions.map((code) => permissionMap[code]).filter(Boolean);
|
|
189
|
+
await net.upsert('roles', { name: roleDef.name }, {
|
|
190
|
+
name: roleDef.name, description: roleDef.description ?? '', permissions: permissionIds,
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
seeded.push('roles');
|
|
194
|
+
}
|
|
152
195
|
}
|
|
153
|
-
// 4. Create admin user via REST
|
|
196
|
+
// 4. Create admin user via NET REST
|
|
154
197
|
if (installConfig.admin.email) {
|
|
155
198
|
const bcryptModule = await import('bcryptjs');
|
|
156
199
|
const bcrypt = bcryptModule.default || bcryptModule;
|
|
157
200
|
const hashedPassword = await bcrypt.hash(installConfig.admin.password, 12);
|
|
158
|
-
// Find admin role
|
|
159
201
|
const adminRole = await net.findOne('roles', { name: 'admin' });
|
|
160
202
|
await net.create('users', {
|
|
161
203
|
email: installConfig.admin.email,
|
|
@@ -167,13 +209,44 @@ async function runNetInstall(installConfig, setupConfig) {
|
|
|
167
209
|
});
|
|
168
210
|
seeded.push('admin');
|
|
169
211
|
}
|
|
170
|
-
// 5. Optional seeds via REST
|
|
171
|
-
if (
|
|
172
|
-
for (const seedDef of
|
|
173
|
-
if (installConfig.seed[seedDef.key])
|
|
174
|
-
|
|
175
|
-
|
|
212
|
+
// 5. Optional seeds via NET REST
|
|
213
|
+
if (setupJson?.seeds && installConfig.seed) {
|
|
214
|
+
for (const seedDef of setupJson.seeds) {
|
|
215
|
+
if (!installConfig.seed[seedDef.key])
|
|
216
|
+
continue;
|
|
217
|
+
// Resolve lookupFields
|
|
218
|
+
const resolved = {};
|
|
219
|
+
if (seedDef.lookupFields) {
|
|
220
|
+
for (const [field, lookup] of Object.entries(seedDef.lookupFields)) {
|
|
221
|
+
const found = await net.findOne(lookup.collection, { [lookup.match]: lookup.value });
|
|
222
|
+
if (found)
|
|
223
|
+
resolved[field] = found.id;
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
for (const rawItem of seedDef.data) {
|
|
227
|
+
const item = { ...(seedDef.defaults ?? {}), ...resolved, ...rawItem };
|
|
228
|
+
// Hash field if configured
|
|
229
|
+
if (seedDef.hashField && item[seedDef.hashField]) {
|
|
230
|
+
const bcryptModule = await import('bcryptjs');
|
|
231
|
+
const bcrypt = bcryptModule.default || bcryptModule;
|
|
232
|
+
item[seedDef.hashField] = await bcrypt.hash(String(item[seedDef.hashField]), 12);
|
|
233
|
+
}
|
|
234
|
+
// Resolve role name → role ID
|
|
235
|
+
if (seedDef.roleField && item[seedDef.roleField]) {
|
|
236
|
+
const role = await net.findOne('roles', { name: String(item[seedDef.roleField]) });
|
|
237
|
+
if (role)
|
|
238
|
+
item.roles = [role.id];
|
|
239
|
+
delete item[seedDef.roleField];
|
|
240
|
+
}
|
|
241
|
+
// Upsert or create
|
|
242
|
+
if (seedDef.match) {
|
|
243
|
+
await net.upsert(seedDef.collection, { [seedDef.match]: item[seedDef.match] }, item);
|
|
244
|
+
}
|
|
245
|
+
else {
|
|
246
|
+
await net.create(seedDef.collection, item);
|
|
247
|
+
}
|
|
176
248
|
}
|
|
249
|
+
seeded.push(seedDef.key);
|
|
177
250
|
}
|
|
178
251
|
}
|
|
179
252
|
return { ok: true, needsRestart: false, seeded };
|
package/package.json
CHANGED