@lastbrain/app 0.1.39 → 0.1.42
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/cli.js +6 -1
- package/dist/hooks/useNotifications.d.ts +1 -0
- package/dist/hooks/useNotifications.d.ts.map +1 -1
- package/dist/layouts/PublicLayout.js +1 -1
- package/dist/scripts/init-app.js +54 -12
- package/dist/scripts/module-add.d.ts.map +1 -1
- package/dist/scripts/module-add.js +28 -10
- package/dist/scripts/module-build.d.ts.map +1 -1
- package/dist/scripts/module-build.js +432 -48
- package/dist/scripts/module-list.d.ts.map +1 -1
- package/dist/scripts/module-list.js +38 -8
- package/dist/styles.css +1 -1
- package/dist/templates/DefaultDoc.d.ts.map +1 -1
- package/dist/templates/DefaultDoc.js +2 -2
- package/package.json +2 -2
- package/src/cli.ts +6 -1
- package/src/hooks/useNotifications.ts +1 -0
- package/src/layouts/PublicLayout.tsx +1 -1
- package/src/scripts/init-app.ts +56 -12
- package/src/scripts/module-add.ts +44 -11
- package/src/scripts/module-build.ts +499 -54
- package/src/scripts/module-list.ts +45 -8
- package/src/templates/DefaultDoc.tsx +325 -12
|
@@ -8,6 +8,8 @@ const monorepoRoot = projectRoot.includes("/apps/")
|
|
|
8
8
|
? path.resolve(projectRoot, "..", "..")
|
|
9
9
|
: projectRoot;
|
|
10
10
|
const appDirectory = path.join(projectRoot, "app");
|
|
11
|
+
// Mode debug activé via --debug
|
|
12
|
+
const isDebugMode = process.argv.includes("--debug");
|
|
11
13
|
// Créer un require dans le contexte de l'application pour résoudre les modules installés dans l'app
|
|
12
14
|
const projectRequire = createRequire(path.join(projectRoot, "package.json"));
|
|
13
15
|
// Charger les modules depuis modules.json
|
|
@@ -121,7 +123,9 @@ function buildPage(moduleConfig, page) {
|
|
|
121
123
|
const modulePrefix = moduleConfig.moduleName
|
|
122
124
|
.replace(/^@lastbrain\/module-/, "")
|
|
123
125
|
.toLowerCase();
|
|
124
|
-
|
|
126
|
+
if (isDebugMode) {
|
|
127
|
+
console.log(`🔄 Building page for module ${modulePrefix}: ${page.path}`);
|
|
128
|
+
}
|
|
125
129
|
// Ajouter le préfixe du module au path pour les sections admin et auth,
|
|
126
130
|
// MAIS seulement quand la section ne correspond PAS au module lui-même
|
|
127
131
|
let effectivePath = page.path;
|
|
@@ -130,13 +134,17 @@ function buildPage(moduleConfig, page) {
|
|
|
130
134
|
// Éviter les doublons si le préfixe est déjà présent
|
|
131
135
|
if (!page.path.startsWith(`/${modulePrefix}/`)) {
|
|
132
136
|
effectivePath = `/${modulePrefix}${page.path}`;
|
|
133
|
-
|
|
137
|
+
if (isDebugMode) {
|
|
138
|
+
console.log(`📂 Added module prefix: ${page.path} -> ${effectivePath}`);
|
|
139
|
+
}
|
|
134
140
|
}
|
|
135
|
-
else {
|
|
141
|
+
else if (isDebugMode) {
|
|
136
142
|
console.log(`✅ Module prefix already present: ${page.path}`);
|
|
137
143
|
}
|
|
138
144
|
}
|
|
139
|
-
else if (page.section === "auth" &&
|
|
145
|
+
else if (page.section === "auth" &&
|
|
146
|
+
modulePrefix === "auth" &&
|
|
147
|
+
isDebugMode) {
|
|
140
148
|
console.log(`🏠 Auth module in auth section, no prefix needed: ${page.path}`);
|
|
141
149
|
}
|
|
142
150
|
const segments = effectivePath.replace(/^\/+/, "").split("/").filter(Boolean);
|
|
@@ -215,7 +223,9 @@ export default function ${wrapperName}${hasDynamicParams ? "(props: any)" : "()"
|
|
|
215
223
|
`;
|
|
216
224
|
}
|
|
217
225
|
fs.writeFileSync(filePath, content);
|
|
218
|
-
|
|
226
|
+
if (isDebugMode) {
|
|
227
|
+
console.log(`⭐ Generated page: ${filePath}`);
|
|
228
|
+
}
|
|
219
229
|
const entry = {
|
|
220
230
|
label: `Module:${moduleConfig.moduleName} ${page.componentExport}`,
|
|
221
231
|
path: effectivePath,
|
|
@@ -238,7 +248,9 @@ export const navigation = ${JSON.stringify(navigation, null, 2)};
|
|
|
238
248
|
export const userMenu = ${JSON.stringify(userMenu, null, 2)};
|
|
239
249
|
`;
|
|
240
250
|
fs.writeFileSync(navPath, content);
|
|
241
|
-
|
|
251
|
+
if (isDebugMode) {
|
|
252
|
+
console.log(`🧭 Generated navigation metadata: ${navPath}`);
|
|
253
|
+
}
|
|
242
254
|
}
|
|
243
255
|
/**
|
|
244
256
|
* Génère le fichier config/menu.ts à partir des configurations de menu des modules
|
|
@@ -247,7 +259,7 @@ function generateMenuConfig(moduleConfigs) {
|
|
|
247
259
|
const configDir = path.join(projectRoot, "config");
|
|
248
260
|
ensureDirectory(configDir);
|
|
249
261
|
const menuPath = path.join(configDir, "menu.ts");
|
|
250
|
-
// Collecter les menus de tous les modules
|
|
262
|
+
// Collecter les menus de tous les modules avec leurs modules sources
|
|
251
263
|
const publicMenus = [];
|
|
252
264
|
const authMenus = [];
|
|
253
265
|
const adminMenus = [];
|
|
@@ -255,16 +267,28 @@ function generateMenuConfig(moduleConfigs) {
|
|
|
255
267
|
moduleConfigs.forEach((moduleConfig) => {
|
|
256
268
|
if (moduleConfig.menu) {
|
|
257
269
|
if (moduleConfig.menu.public) {
|
|
258
|
-
publicMenus.push(...moduleConfig.menu.public)
|
|
270
|
+
publicMenus.push(...moduleConfig.menu.public.map((item) => ({
|
|
271
|
+
...item,
|
|
272
|
+
moduleName: moduleConfig.moduleName,
|
|
273
|
+
})));
|
|
259
274
|
}
|
|
260
275
|
if (moduleConfig.menu.auth) {
|
|
261
|
-
authMenus.push(...moduleConfig.menu.auth)
|
|
276
|
+
authMenus.push(...moduleConfig.menu.auth.map((item) => ({
|
|
277
|
+
...item,
|
|
278
|
+
moduleName: moduleConfig.moduleName,
|
|
279
|
+
})));
|
|
262
280
|
}
|
|
263
281
|
if (moduleConfig.menu.admin) {
|
|
264
|
-
adminMenus.push(...moduleConfig.menu.admin)
|
|
282
|
+
adminMenus.push(...moduleConfig.menu.admin.map((item) => ({
|
|
283
|
+
...item,
|
|
284
|
+
moduleName: moduleConfig.moduleName,
|
|
285
|
+
})));
|
|
265
286
|
}
|
|
266
287
|
if (moduleConfig.menu.account) {
|
|
267
|
-
accountMenus.push(...moduleConfig.menu.account)
|
|
288
|
+
accountMenus.push(...moduleConfig.menu.account.map((item) => ({
|
|
289
|
+
...item,
|
|
290
|
+
moduleName: moduleConfig.moduleName,
|
|
291
|
+
})));
|
|
268
292
|
}
|
|
269
293
|
}
|
|
270
294
|
});
|
|
@@ -276,9 +300,45 @@ function generateMenuConfig(moduleConfigs) {
|
|
|
276
300
|
authMenus.sort(sortByOrder);
|
|
277
301
|
adminMenus.sort(sortByOrder);
|
|
278
302
|
accountMenus.sort(sortByOrder);
|
|
303
|
+
// Collecter les composants à importer dynamiquement
|
|
304
|
+
const componentImports = [];
|
|
305
|
+
const allMenus = [
|
|
306
|
+
...publicMenus,
|
|
307
|
+
...authMenus,
|
|
308
|
+
...adminMenus,
|
|
309
|
+
...accountMenus,
|
|
310
|
+
];
|
|
311
|
+
allMenus.forEach((menu, index) => {
|
|
312
|
+
if (menu.componentExport && menu.moduleName) {
|
|
313
|
+
const componentName = `MenuComponent${index}`;
|
|
314
|
+
componentImports.push(`const ${componentName} = dynamic(() => import("${menu.moduleName}").then(mod => ({ default: mod.${menu.componentExport} })), { ssr: false });`);
|
|
315
|
+
// Ajouter une référence au composant
|
|
316
|
+
menu.__componentRef = componentName;
|
|
317
|
+
}
|
|
318
|
+
});
|
|
319
|
+
// Fonction pour préparer les menus avec les composants
|
|
320
|
+
const prepareMenusForExport = (menus) => {
|
|
321
|
+
return menus.map((menu) => {
|
|
322
|
+
const { moduleName, __componentRef, ...cleanMenu } = menu;
|
|
323
|
+
if (__componentRef) {
|
|
324
|
+
// Retourner une référence au composant au lieu de la sérialiser
|
|
325
|
+
return `{ ...${JSON.stringify(cleanMenu)}, component: ${__componentRef} }`;
|
|
326
|
+
}
|
|
327
|
+
return JSON.stringify(cleanMenu);
|
|
328
|
+
});
|
|
329
|
+
};
|
|
330
|
+
const publicMenusExport = prepareMenusForExport(publicMenus);
|
|
331
|
+
const authMenusExport = prepareMenusForExport(authMenus);
|
|
332
|
+
const adminMenusExport = prepareMenusForExport(adminMenus);
|
|
333
|
+
const accountMenusExport = prepareMenusForExport(accountMenus);
|
|
279
334
|
// Générer le contenu du fichier
|
|
280
335
|
const content = `// Auto-generated menu configuration
|
|
281
336
|
// Generated from module build configs
|
|
337
|
+
"use client";
|
|
338
|
+
|
|
339
|
+
import dynamic from "next/dynamic";
|
|
340
|
+
|
|
341
|
+
${componentImports.join("\n")}
|
|
282
342
|
|
|
283
343
|
export interface MenuItem {
|
|
284
344
|
title: string;
|
|
@@ -288,6 +348,11 @@ export interface MenuItem {
|
|
|
288
348
|
order?: number;
|
|
289
349
|
shortcut?: string;
|
|
290
350
|
shortcutDisplay?: string;
|
|
351
|
+
type?: 'text' | 'icon' | 'textIcon';
|
|
352
|
+
position?: 'center' | 'end';
|
|
353
|
+
component?: React.ComponentType<any>;
|
|
354
|
+
componentExport?: string;
|
|
355
|
+
entryPoint?: string;
|
|
291
356
|
}
|
|
292
357
|
|
|
293
358
|
export interface MenuConfig {
|
|
@@ -298,14 +363,16 @@ export interface MenuConfig {
|
|
|
298
363
|
}
|
|
299
364
|
|
|
300
365
|
export const menuConfig: MenuConfig = {
|
|
301
|
-
public: ${
|
|
302
|
-
auth: ${
|
|
303
|
-
admin: ${
|
|
304
|
-
account: ${
|
|
366
|
+
public: [${publicMenusExport.join(",\n ")}],
|
|
367
|
+
auth: [${authMenusExport.join(",\n ")}],
|
|
368
|
+
admin: [${adminMenusExport.join(",\n ")}],
|
|
369
|
+
account: [${accountMenusExport.join(",\n ")}],
|
|
305
370
|
};
|
|
306
371
|
`;
|
|
307
372
|
fs.writeFileSync(menuPath, content);
|
|
308
|
-
|
|
373
|
+
if (isDebugMode) {
|
|
374
|
+
console.log(`🍔 Generated menu configuration: ${menuPath}`);
|
|
375
|
+
}
|
|
309
376
|
}
|
|
310
377
|
function copyModuleMigrations(moduleConfigs) {
|
|
311
378
|
const supabaseMigrationsDir = path.join(projectRoot, "supabase", "migrations");
|
|
@@ -441,7 +508,9 @@ ${moduleConfigurations.join(",\n")}
|
|
|
441
508
|
}
|
|
442
509
|
`;
|
|
443
510
|
fs.writeFileSync(docsPagePath, docsContent);
|
|
444
|
-
|
|
511
|
+
if (isDebugMode) {
|
|
512
|
+
console.log(`📚 Generated docs page: ${docsPagePath}`);
|
|
513
|
+
}
|
|
445
514
|
}
|
|
446
515
|
function buildGroupedApi(apis, routePath) {
|
|
447
516
|
const segments = routePath.replace(/^\/+/, "").split("/").filter(Boolean);
|
|
@@ -468,7 +537,9 @@ function buildGroupedApi(apis, routePath) {
|
|
|
468
537
|
const contentWithMarker = `// GENERATED BY LASTBRAIN MODULE BUILD
|
|
469
538
|
${content}`;
|
|
470
539
|
fs.writeFileSync(filePath, contentWithMarker);
|
|
471
|
-
|
|
540
|
+
if (isDebugMode) {
|
|
541
|
+
console.log(`🔌 Generated API route: ${filePath}`);
|
|
542
|
+
}
|
|
472
543
|
}
|
|
473
544
|
function getModuleDescription(moduleConfig) {
|
|
474
545
|
// Essayer de déduire la description depuis les pages ou retourner une description par défaut
|
|
@@ -523,7 +594,9 @@ function cleanGeneratedFiles() {
|
|
|
523
594
|
try {
|
|
524
595
|
if (!isProtected(itemPath) && fs.readdirSync(itemPath).length === 0) {
|
|
525
596
|
fs.rmdirSync(itemPath);
|
|
526
|
-
|
|
597
|
+
if (isDebugMode) {
|
|
598
|
+
console.log(`📁 Removed empty directory: ${itemPath}`);
|
|
599
|
+
}
|
|
527
600
|
}
|
|
528
601
|
}
|
|
529
602
|
catch {
|
|
@@ -541,14 +614,16 @@ function cleanGeneratedFiles() {
|
|
|
541
614
|
(content.includes("export {") &&
|
|
542
615
|
content.includes('} from "@lastbrain/module-'))) {
|
|
543
616
|
fs.unlinkSync(itemPath);
|
|
544
|
-
|
|
617
|
+
if (isDebugMode) {
|
|
618
|
+
console.log(`🗑️ Removed: ${itemPath}`);
|
|
619
|
+
}
|
|
545
620
|
}
|
|
546
621
|
}
|
|
547
622
|
catch {
|
|
548
623
|
// Ignorer les erreurs de lecture/suppression
|
|
549
624
|
}
|
|
550
625
|
}
|
|
551
|
-
else {
|
|
626
|
+
else if (isDebugMode) {
|
|
552
627
|
console.log(`🔒 Protected file skipped: ${itemPath}`);
|
|
553
628
|
}
|
|
554
629
|
}
|
|
@@ -568,16 +643,22 @@ function cleanGeneratedFiles() {
|
|
|
568
643
|
const filePath = path.join(appDirectory, file);
|
|
569
644
|
if (fs.existsSync(filePath)) {
|
|
570
645
|
fs.unlinkSync(filePath);
|
|
571
|
-
|
|
646
|
+
if (isDebugMode) {
|
|
647
|
+
console.log(`🗑️ Removed: ${filePath}`);
|
|
648
|
+
}
|
|
572
649
|
}
|
|
573
650
|
});
|
|
574
|
-
|
|
651
|
+
if (isDebugMode) {
|
|
652
|
+
console.log("🧹 Cleanup completed");
|
|
653
|
+
}
|
|
575
654
|
}
|
|
576
655
|
function generateAppAside() {
|
|
577
656
|
const targetPath = path.join(appDirectory, "components", "AppAside.tsx");
|
|
578
657
|
// Ne pas écraser si le fichier existe déjà
|
|
579
658
|
if (fs.existsSync(targetPath)) {
|
|
580
|
-
|
|
659
|
+
if (isDebugMode) {
|
|
660
|
+
console.log(`⏭️ AppAside already exists, skipping: ${targetPath}`);
|
|
661
|
+
}
|
|
581
662
|
return;
|
|
582
663
|
}
|
|
583
664
|
const templateContent = `"use client";
|
|
@@ -606,7 +687,9 @@ export function AppAside({ className = "", isVisible = true }: AppAsideProps) {
|
|
|
606
687
|
try {
|
|
607
688
|
ensureDirectory(path.dirname(targetPath));
|
|
608
689
|
fs.writeFileSync(targetPath, templateContent, "utf-8");
|
|
609
|
-
|
|
690
|
+
if (isDebugMode) {
|
|
691
|
+
console.log(`✅ Generated AppAside component: ${targetPath}`);
|
|
692
|
+
}
|
|
610
693
|
}
|
|
611
694
|
catch (error) {
|
|
612
695
|
console.error(`❌ Error generating AppAside component: ${error}`);
|
|
@@ -633,13 +716,15 @@ export default function SectionLayout({
|
|
|
633
716
|
try {
|
|
634
717
|
ensureDirectory(path.dirname(authLayoutPath));
|
|
635
718
|
fs.writeFileSync(authLayoutPath, authLayoutContent, "utf-8");
|
|
636
|
-
|
|
719
|
+
if (isDebugMode) {
|
|
720
|
+
console.log(`✅ Generated auth layout with sidebar: ${authLayoutPath}`);
|
|
721
|
+
}
|
|
637
722
|
}
|
|
638
723
|
catch (error) {
|
|
639
724
|
console.error(`❌ Error generating auth layout: ${error}`);
|
|
640
725
|
}
|
|
641
726
|
}
|
|
642
|
-
else {
|
|
727
|
+
else if (isDebugMode) {
|
|
643
728
|
console.log(`⏭️ Auth layout already exists, skipping: ${authLayoutPath}`);
|
|
644
729
|
}
|
|
645
730
|
// Générer layout admin avec sidebar
|
|
@@ -662,13 +747,15 @@ export default function AdminLayout({
|
|
|
662
747
|
try {
|
|
663
748
|
ensureDirectory(path.dirname(adminLayoutPath));
|
|
664
749
|
fs.writeFileSync(adminLayoutPath, adminLayoutContent, "utf-8");
|
|
665
|
-
|
|
750
|
+
if (isDebugMode) {
|
|
751
|
+
console.log(`✅ Generated admin layout with sidebar: ${adminLayoutPath}`);
|
|
752
|
+
}
|
|
666
753
|
}
|
|
667
754
|
catch (error) {
|
|
668
755
|
console.error(`❌ Error generating admin layout: ${error}`);
|
|
669
756
|
}
|
|
670
757
|
}
|
|
671
|
-
else {
|
|
758
|
+
else if (isDebugMode) {
|
|
672
759
|
console.log(`⏭️ Admin layout already exists, skipping: ${adminLayoutPath}`);
|
|
673
760
|
}
|
|
674
761
|
}
|
|
@@ -707,15 +794,17 @@ export default realtimeConfig;
|
|
|
707
794
|
}
|
|
708
795
|
// Écrire le fichier TypeScript
|
|
709
796
|
fs.writeFileSync(outputPath, content);
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
797
|
+
if (isDebugMode) {
|
|
798
|
+
console.log(`✅ Generated realtime configuration: ${outputPath}`);
|
|
799
|
+
console.log(`📊 Modules with realtime: ${realtimeConfigs.length}`);
|
|
800
|
+
// Afficher un résumé
|
|
801
|
+
realtimeConfigs.forEach((module) => {
|
|
802
|
+
console.log(` - ${module.moduleId}: ${module.tables.length} table(s)`);
|
|
803
|
+
module.tables.forEach((table) => {
|
|
804
|
+
console.log(` • ${table.schema}.${table.table} (${table.event})`);
|
|
805
|
+
});
|
|
717
806
|
});
|
|
718
|
-
}
|
|
807
|
+
}
|
|
719
808
|
}
|
|
720
809
|
catch (error) {
|
|
721
810
|
console.error("❌ Error generating realtime configuration:", error);
|
|
@@ -759,31 +848,303 @@ async function generateUserTabsConfig(moduleConfigs) {
|
|
|
759
848
|
}
|
|
760
849
|
// Écrire le fichier TypeScript
|
|
761
850
|
fs.writeFileSync(outputPath, appContent);
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
851
|
+
if (isDebugMode) {
|
|
852
|
+
console.log(`✅ Generated user tabs configuration: ${outputPath}`);
|
|
853
|
+
console.log(`📊 User tabs count: ${userTabsConfigs.length}`);
|
|
854
|
+
// Afficher un résumé
|
|
855
|
+
userTabsConfigs.forEach((tab) => {
|
|
856
|
+
console.log(` - ${tab.title} (${tab.moduleName})`);
|
|
857
|
+
});
|
|
858
|
+
}
|
|
768
859
|
// Plus de copie vers app/config ni stub core
|
|
769
860
|
}
|
|
770
861
|
catch (error) {
|
|
771
862
|
console.error("❌ Error generating user tabs configuration:", error);
|
|
772
863
|
}
|
|
773
864
|
}
|
|
865
|
+
async function generateBucketsConfig(moduleConfigs) {
|
|
866
|
+
try {
|
|
867
|
+
// Extraire les configurations storage des modules
|
|
868
|
+
const allBuckets = moduleConfigs
|
|
869
|
+
.filter((config) => config.storage?.buckets && config.storage.buckets.length > 0)
|
|
870
|
+
.flatMap((config) => config.storage.buckets);
|
|
871
|
+
if (allBuckets.length === 0) {
|
|
872
|
+
console.log("⏭️ No storage buckets configuration found in modules");
|
|
873
|
+
return;
|
|
874
|
+
}
|
|
875
|
+
// Générer le contenu du fichier
|
|
876
|
+
const timestamp = new Date().toISOString();
|
|
877
|
+
const bucketsEntries = allBuckets
|
|
878
|
+
.map((bucket) => {
|
|
879
|
+
// Sérialiser customAccessControl si elle existe
|
|
880
|
+
const customAccessControl = bucket.customAccessControl
|
|
881
|
+
? `\n customAccessControl: ${bucket.customAccessControl.toString()},`
|
|
882
|
+
: "";
|
|
883
|
+
return ` ${bucket.name}: {
|
|
884
|
+
name: "${bucket.name}",
|
|
885
|
+
isPublic: ${bucket.public},
|
|
886
|
+
description: "${bucket.description || `Storage bucket for ${bucket.name}`}",${bucket.allowedMimeTypes ? `\n allowedFileTypes: ${JSON.stringify(bucket.allowedMimeTypes)},` : ""}${bucket.maxFileSize ? `\n maxFileSize: ${bucket.maxFileSize}, // ${bucket.fileSizeLimit || `${Math.round(bucket.maxFileSize / 1024 / 1024)}MB`}` : ""}${customAccessControl}
|
|
887
|
+
}`;
|
|
888
|
+
})
|
|
889
|
+
.join(",\n");
|
|
890
|
+
const content = `/**
|
|
891
|
+
* Storage configuration for buckets and access control
|
|
892
|
+
*
|
|
893
|
+
* GENERATED FILE - DO NOT EDIT MANUALLY
|
|
894
|
+
* Generated at: ${timestamp}
|
|
895
|
+
* Generated from module build configs
|
|
896
|
+
*/
|
|
897
|
+
|
|
898
|
+
export interface BucketConfig {
|
|
899
|
+
name: string;
|
|
900
|
+
isPublic: boolean;
|
|
901
|
+
description: string;
|
|
902
|
+
allowedFileTypes?: string[];
|
|
903
|
+
maxFileSize?: number; // in bytes
|
|
904
|
+
customAccessControl?: (userId: string, filePath: string) => boolean;
|
|
905
|
+
}
|
|
906
|
+
|
|
907
|
+
export const BUCKET_CONFIGS: Record<string, BucketConfig> = {
|
|
908
|
+
${bucketsEntries}
|
|
909
|
+
};
|
|
910
|
+
|
|
911
|
+
/**
|
|
912
|
+
* Get bucket configuration
|
|
913
|
+
*/
|
|
914
|
+
export function getBucketConfig(bucketName: string): BucketConfig | null {
|
|
915
|
+
return BUCKET_CONFIGS[bucketName] || null;
|
|
916
|
+
}
|
|
917
|
+
|
|
918
|
+
/**
|
|
919
|
+
* Check if bucket is public
|
|
920
|
+
*/
|
|
921
|
+
export function isPublicBucket(bucketName: string): boolean {
|
|
922
|
+
const config = getBucketConfig(bucketName);
|
|
923
|
+
return config?.isPublic ?? false;
|
|
924
|
+
}
|
|
925
|
+
|
|
926
|
+
/**
|
|
927
|
+
* Check if user has access to a specific file
|
|
928
|
+
*/
|
|
929
|
+
export function hasFileAccess(bucketName: string, userId: string, filePath: string): boolean {
|
|
930
|
+
const config = getBucketConfig(bucketName);
|
|
931
|
+
if (!config) return false;
|
|
932
|
+
|
|
933
|
+
// Public buckets are accessible to everyone
|
|
934
|
+
if (config.isPublic) return true;
|
|
935
|
+
|
|
936
|
+
// Private buckets require authentication
|
|
937
|
+
if (!userId) return false;
|
|
938
|
+
|
|
939
|
+
// Apply custom access control if defined
|
|
940
|
+
if (config.customAccessControl) {
|
|
941
|
+
return config.customAccessControl(userId, filePath);
|
|
942
|
+
}
|
|
943
|
+
|
|
944
|
+
return true;
|
|
945
|
+
}
|
|
946
|
+
|
|
947
|
+
/**
|
|
948
|
+
* Validate file type for bucket
|
|
949
|
+
*/
|
|
950
|
+
export function isValidFileType(bucketName: string, contentType: string): boolean {
|
|
951
|
+
const config = getBucketConfig(bucketName);
|
|
952
|
+
if (!config || !config.allowedFileTypes) return true;
|
|
953
|
+
|
|
954
|
+
return config.allowedFileTypes.includes(contentType);
|
|
955
|
+
}
|
|
956
|
+
|
|
957
|
+
/**
|
|
958
|
+
* Check if file size is within bucket limits
|
|
959
|
+
*/
|
|
960
|
+
export function isValidFileSize(bucketName: string, fileSize: number): boolean {
|
|
961
|
+
const config = getBucketConfig(bucketName);
|
|
962
|
+
if (!config || !config.maxFileSize) return true;
|
|
963
|
+
|
|
964
|
+
return fileSize <= config.maxFileSize;
|
|
965
|
+
}
|
|
966
|
+
`;
|
|
967
|
+
// Créer le fichier dans lib/
|
|
968
|
+
const outputPath = path.join(projectRoot, "lib", "bucket-config.ts");
|
|
969
|
+
const libDir = path.dirname(outputPath);
|
|
970
|
+
// Créer le dossier lib s'il n'existe pas
|
|
971
|
+
if (!fs.existsSync(libDir)) {
|
|
972
|
+
fs.mkdirSync(libDir, { recursive: true });
|
|
973
|
+
}
|
|
974
|
+
// Écrire le fichier TypeScript
|
|
975
|
+
fs.writeFileSync(outputPath, content);
|
|
976
|
+
if (isDebugMode) {
|
|
977
|
+
console.log(`✅ Generated storage buckets configuration: ${outputPath}`);
|
|
978
|
+
console.log(`📊 Storage buckets count: ${allBuckets.length}`);
|
|
979
|
+
// Afficher un résumé
|
|
980
|
+
allBuckets.forEach((bucket) => {
|
|
981
|
+
const access = bucket.public ? "public" : "private";
|
|
982
|
+
console.log(` - ${bucket.name} (${access})`);
|
|
983
|
+
});
|
|
984
|
+
}
|
|
985
|
+
}
|
|
986
|
+
catch (error) {
|
|
987
|
+
console.error("❌ Error generating buckets configuration:", error);
|
|
988
|
+
}
|
|
989
|
+
}
|
|
990
|
+
async function generateStorageProxyApi(moduleConfigs) {
|
|
991
|
+
try {
|
|
992
|
+
// Extraire les configurations storage des modules
|
|
993
|
+
const allBuckets = moduleConfigs
|
|
994
|
+
.filter((config) => config.storage?.buckets && config.storage.buckets.length > 0)
|
|
995
|
+
.flatMap((config) => config.storage.buckets);
|
|
996
|
+
if (allBuckets.length === 0) {
|
|
997
|
+
console.log("⏭️ No storage buckets found, skipping proxy API generation");
|
|
998
|
+
return;
|
|
999
|
+
}
|
|
1000
|
+
// Identifier les buckets publics et privés
|
|
1001
|
+
const publicBuckets = allBuckets.filter((b) => b.public);
|
|
1002
|
+
const privateBuckets = allBuckets.filter((b) => !b.public);
|
|
1003
|
+
// Générer les conditions pour les buckets publics
|
|
1004
|
+
const publicBucketConditions = publicBuckets
|
|
1005
|
+
.map((bucket) => `bucket === "${bucket.name}"`)
|
|
1006
|
+
.filter(Boolean);
|
|
1007
|
+
// Ajouter les conditions pour les chemins publics dans les buckets privés
|
|
1008
|
+
const publicPathConditions = [];
|
|
1009
|
+
if (publicBuckets.some((b) => b.name === "recipes")) {
|
|
1010
|
+
publicPathConditions.push('storagePath.startsWith("recipes/")');
|
|
1011
|
+
}
|
|
1012
|
+
const allPublicConditions = [
|
|
1013
|
+
...publicBucketConditions,
|
|
1014
|
+
...publicPathConditions,
|
|
1015
|
+
];
|
|
1016
|
+
const publicCondition = allPublicConditions.length > 0
|
|
1017
|
+
? allPublicConditions.join(" || ")
|
|
1018
|
+
: "false";
|
|
1019
|
+
const timestamp = new Date().toISOString();
|
|
1020
|
+
const content = `import { getSupabaseServerClient } from "@lastbrain/core/server";
|
|
1021
|
+
import { NextRequest, NextResponse } from "next/server";
|
|
1022
|
+
|
|
1023
|
+
/**
|
|
1024
|
+
* GET /api/storage/[bucket]/[...path]
|
|
1025
|
+
* Proxy pour servir les images avec authentication
|
|
1026
|
+
*
|
|
1027
|
+
* GENERATED FILE - DO NOT EDIT MANUALLY
|
|
1028
|
+
* Generated at: ${timestamp}
|
|
1029
|
+
* Generated from module storage configurations
|
|
1030
|
+
*
|
|
1031
|
+
* Buckets configurés:
|
|
1032
|
+
${allBuckets.map((b) => ` * - ${b.name} (${b.public ? "public" : "private"})`).join("\n")}
|
|
1033
|
+
*/
|
|
1034
|
+
export async function GET(
|
|
1035
|
+
request: NextRequest,
|
|
1036
|
+
context: { params: Promise<{ bucket: string; path: string[] }> },
|
|
1037
|
+
) {
|
|
1038
|
+
try {
|
|
1039
|
+
const { bucket, path } = await context.params;
|
|
1040
|
+
const storagePath = path.join("/");
|
|
1041
|
+
|
|
1042
|
+
// Les images publiques sont accessibles sans auth
|
|
1043
|
+
if (${publicCondition}) {
|
|
1044
|
+
const supabase = await getSupabaseServerClient();
|
|
1045
|
+
const { data, error } = await supabase.storage
|
|
1046
|
+
.from(bucket)
|
|
1047
|
+
.createSignedUrl(storagePath, 3600); // 1 heure
|
|
1048
|
+
|
|
1049
|
+
if (error) {
|
|
1050
|
+
console.error(\`[storage] Error creating signed URL for public image:\`, error);
|
|
1051
|
+
return new NextResponse("Not found", { status: 404 });
|
|
1052
|
+
}
|
|
1053
|
+
|
|
1054
|
+
// Rediriger vers l'URL signée
|
|
1055
|
+
return NextResponse.redirect(data.signedUrl);
|
|
1056
|
+
}
|
|
1057
|
+
|
|
1058
|
+
// Les images privées nécessitent une authentification
|
|
1059
|
+
const supabase = await getSupabaseServerClient();
|
|
1060
|
+
const {
|
|
1061
|
+
data: { user },
|
|
1062
|
+
error: authError,
|
|
1063
|
+
} = await supabase.auth.getUser();
|
|
1064
|
+
|
|
1065
|
+
if (authError || !user) {
|
|
1066
|
+
return new NextResponse("Unauthorized", { status: 401 });
|
|
1067
|
+
}
|
|
1068
|
+
|
|
1069
|
+
// Cas spécial: si le chemin commence par /product/ ou /recipe/,
|
|
1070
|
+
// c'est une image avec format court qui nécessite le préfixe userId
|
|
1071
|
+
let actualStoragePath = storagePath;
|
|
1072
|
+
|
|
1073
|
+
if (storagePath.startsWith("product/") || storagePath.startsWith("recipe/")) {
|
|
1074
|
+
actualStoragePath = \`\${user.id}/\${storagePath}\`;
|
|
1075
|
+
}
|
|
1076
|
+
|
|
1077
|
+
// Vérifier que l'utilisateur a accès à cette image
|
|
1078
|
+
// Format: {userId}/recipe/{recipeId}/{filename} ou {userId}/product/{productId}/{filename}
|
|
1079
|
+
const pathParts = actualStoragePath.split("/");
|
|
1080
|
+
const pathUserId = pathParts[0];
|
|
1081
|
+
|
|
1082
|
+
if (pathUserId !== user.id) {
|
|
1083
|
+
return new NextResponse("Forbidden", { status: 403 });
|
|
1084
|
+
}
|
|
1085
|
+
|
|
1086
|
+
// Créer une URL signée pour l'image privée
|
|
1087
|
+
const { data, error } = await supabase.storage
|
|
1088
|
+
.from(bucket)
|
|
1089
|
+
.createSignedUrl(actualStoragePath, 3600); // 1 heure
|
|
1090
|
+
|
|
1091
|
+
if (error) {
|
|
1092
|
+
console.error(\`[storage] Error creating signed URL:\`, error);
|
|
1093
|
+
return new NextResponse("Not found", { status: 404 });
|
|
1094
|
+
}
|
|
1095
|
+
|
|
1096
|
+
// Rediriger vers l'URL signée
|
|
1097
|
+
return NextResponse.redirect(data.signedUrl);
|
|
1098
|
+
} catch (error) {
|
|
1099
|
+
console.error("[storage] Error:", error);
|
|
1100
|
+
return new NextResponse("Internal server error", { status: 500 });
|
|
1101
|
+
}
|
|
1102
|
+
}
|
|
1103
|
+
`;
|
|
1104
|
+
// Créer le fichier dans app/api/storage/[bucket]/[...path]/
|
|
1105
|
+
const outputPath = path.join(projectRoot, "app", "api", "storage", "[bucket]", "[...path]", "route.ts");
|
|
1106
|
+
const outputDir = path.dirname(outputPath);
|
|
1107
|
+
// Créer le dossier s'il n'existe pas
|
|
1108
|
+
if (!fs.existsSync(outputDir)) {
|
|
1109
|
+
fs.mkdirSync(outputDir, { recursive: true });
|
|
1110
|
+
}
|
|
1111
|
+
// Écrire le fichier TypeScript
|
|
1112
|
+
fs.writeFileSync(outputPath, content);
|
|
1113
|
+
if (isDebugMode) {
|
|
1114
|
+
console.log(`✅ Generated storage proxy API: ${outputPath}`);
|
|
1115
|
+
console.log(`📊 Public buckets: ${publicBuckets.map((b) => b.name).join(", ") || "none"}`);
|
|
1116
|
+
console.log(`📊 Private buckets: ${privateBuckets.map((b) => b.name).join(", ") || "none"}`);
|
|
1117
|
+
}
|
|
1118
|
+
}
|
|
1119
|
+
catch (error) {
|
|
1120
|
+
console.error("❌ Error generating storage proxy API:", error);
|
|
1121
|
+
}
|
|
1122
|
+
}
|
|
774
1123
|
export async function runModuleBuild() {
|
|
775
1124
|
ensureDirectory(appDirectory);
|
|
776
1125
|
// Nettoyer les fichiers générés précédemment
|
|
777
|
-
|
|
1126
|
+
if (isDebugMode) {
|
|
1127
|
+
console.log("🧹 Cleaning previously generated files...");
|
|
1128
|
+
}
|
|
778
1129
|
cleanGeneratedFiles();
|
|
779
1130
|
const moduleConfigs = await loadModuleConfigs();
|
|
780
|
-
|
|
1131
|
+
if (isDebugMode) {
|
|
1132
|
+
console.log(`🔍 Loaded ${moduleConfigs.length} module configurations`);
|
|
1133
|
+
}
|
|
781
1134
|
// Générer les pages
|
|
1135
|
+
if (isDebugMode) {
|
|
1136
|
+
console.log("\n📝 Generating pages...");
|
|
1137
|
+
}
|
|
782
1138
|
moduleConfigs.forEach((moduleConfig) => {
|
|
783
|
-
|
|
1139
|
+
if (isDebugMode) {
|
|
1140
|
+
console.log(`📦 Processing module: ${moduleConfig.moduleName} with ${moduleConfig.pages.length} pages`);
|
|
1141
|
+
}
|
|
784
1142
|
moduleConfig.pages.forEach((page) => buildPage(moduleConfig, page));
|
|
785
1143
|
});
|
|
786
1144
|
// Grouper les APIs par chemin pour éviter les écrasements de fichier
|
|
1145
|
+
if (isDebugMode) {
|
|
1146
|
+
console.log("\n🔌 Generating API routes...");
|
|
1147
|
+
}
|
|
787
1148
|
const apisByPath = new Map();
|
|
788
1149
|
moduleConfigs.forEach((moduleConfig) => {
|
|
789
1150
|
moduleConfig.apis.forEach((api) => {
|
|
@@ -804,9 +1165,32 @@ export async function runModuleBuild() {
|
|
|
804
1165
|
generateLayouts();
|
|
805
1166
|
copyModuleMigrations(moduleConfigs);
|
|
806
1167
|
// Générer la configuration realtime
|
|
807
|
-
|
|
1168
|
+
if (isDebugMode) {
|
|
1169
|
+
console.log("🔄 Generating realtime configuration...");
|
|
1170
|
+
}
|
|
808
1171
|
await generateRealtimeConfig(moduleConfigs);
|
|
809
1172
|
// Générer la configuration des user tabs
|
|
810
|
-
|
|
1173
|
+
if (isDebugMode) {
|
|
1174
|
+
console.log("📑 Generating user tabs configuration...");
|
|
1175
|
+
}
|
|
811
1176
|
await generateUserTabsConfig(moduleConfigs);
|
|
1177
|
+
// Générer la configuration des buckets storage
|
|
1178
|
+
if (isDebugMode) {
|
|
1179
|
+
console.log("🗄️ Generating storage buckets configuration...");
|
|
1180
|
+
}
|
|
1181
|
+
await generateBucketsConfig(moduleConfigs);
|
|
1182
|
+
// Générer le proxy storage API
|
|
1183
|
+
if (isDebugMode) {
|
|
1184
|
+
console.log("🔌 Generating storage proxy API...");
|
|
1185
|
+
}
|
|
1186
|
+
await generateStorageProxyApi(moduleConfigs);
|
|
1187
|
+
// Message de succès final
|
|
1188
|
+
if (!isDebugMode) {
|
|
1189
|
+
console.log("\n✅ Module build completed successfully!");
|
|
1190
|
+
console.log(`📦 ${moduleConfigs.length} module(s) processed`);
|
|
1191
|
+
console.log("💡 Use --debug flag for detailed logs\n");
|
|
1192
|
+
}
|
|
1193
|
+
else {
|
|
1194
|
+
console.log("\n✅ Module build completed (debug mode)\n");
|
|
1195
|
+
}
|
|
812
1196
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"module-list.d.ts","sourceRoot":"","sources":["../../src/scripts/module-list.ts"],"names":[],"mappings":"AAQA,wBAAsB,WAAW,CAAC,SAAS,EAAE,MAAM,
|
|
1
|
+
{"version":3,"file":"module-list.d.ts","sourceRoot":"","sources":["../../src/scripts/module-list.ts"],"names":[],"mappings":"AAQA,wBAAsB,WAAW,CAAC,SAAS,EAAE,MAAM,iBAwElD"}
|