@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.
@@ -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
- console.log(`🔄 Building page for module ${modulePrefix}: ${page.path}`);
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
- console.log(`📂 Added module prefix: ${page.path} -> ${effectivePath}`);
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" && modulePrefix === "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
- console.log(`⭐ Generated page: ${filePath}`);
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
- console.log(`🧭 Generated navigation metadata: ${navPath}`);
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: ${JSON.stringify(publicMenus, null, 2)},
302
- auth: ${JSON.stringify(authMenus, null, 2)},
303
- admin: ${JSON.stringify(adminMenus, null, 2)},
304
- account: ${JSON.stringify(accountMenus, null, 2)},
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
- console.log(`🍔 Generated menu configuration: ${menuPath}`);
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
- console.log(`📚 Generated docs page: ${docsPagePath}`);
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
- console.log(`🔌 Generated API route: ${filePath}`);
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
- console.log(`🗑️ Removed empty directory: ${itemPath}`);
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
- console.log(`🗑️ Cleaned generated file: ${itemPath}`);
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
- console.log(`🗑️ Cleaned root file: ${filePath}`);
646
+ if (isDebugMode) {
647
+ console.log(`🗑️ Removed: ${filePath}`);
648
+ }
572
649
  }
573
650
  });
574
- console.log("🧹 Cleanup completed");
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
- console.log(`⏭️ AppAside already exists, skipping: ${targetPath}`);
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
- console.log(`✅ Generated AppAside component: ${targetPath}`);
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
- console.log(`✅ Generated auth layout with sidebar: ${authLayoutPath}`);
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
- console.log(`✅ Generated admin layout with sidebar: ${adminLayoutPath}`);
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
- console.log(`✅ Generated realtime configuration: ${outputPath}`);
711
- console.log(`📊 Modules with realtime: ${realtimeConfigs.length}`);
712
- // Afficher un résumé
713
- realtimeConfigs.forEach((module) => {
714
- console.log(` - ${module.moduleId}: ${module.tables.length} table(s)`);
715
- module.tables.forEach((table) => {
716
- console.log(` • ${table.schema}.${table.table} (${table.event})`);
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
- console.log(`✅ Generated user tabs configuration: ${outputPath}`);
763
- console.log(`📊 User tabs count: ${userTabsConfigs.length}`);
764
- // Afficher un résumé
765
- userTabsConfigs.forEach((tab) => {
766
- console.log(` - ${tab.title} (${tab.moduleName})`);
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
- console.log("🧹 Cleaning previously generated files...");
1126
+ if (isDebugMode) {
1127
+ console.log("🧹 Cleaning previously generated files...");
1128
+ }
778
1129
  cleanGeneratedFiles();
779
1130
  const moduleConfigs = await loadModuleConfigs();
780
- console.log(`🔍 Loaded ${moduleConfigs.length} module configurations`);
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
- console.log(`📦 Processing module: ${moduleConfig.moduleName} with ${moduleConfig.pages.length} pages`);
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
- console.log("🔄 Generating realtime configuration...");
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
- console.log("📑 Generating user tabs configuration...");
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,iBAmClD"}
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"}