@lastbrain/app 0.1.40 → 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/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/module-add.d.ts.map +1 -1
- package/dist/scripts/module-add.js +22 -52
- package/dist/scripts/module-build.d.ts.map +1 -1
- package/dist/scripts/module-build.js +330 -9
- 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 +1 -1
- package/src/hooks/useNotifications.ts +1 -0
- package/src/layouts/PublicLayout.tsx +1 -1
- package/src/scripts/module-add.ts +36 -82
- package/src/scripts/module-build.ts +388 -13
- package/src/templates/DefaultDoc.tsx +325 -12
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useNotifications.d.ts","sourceRoot":"","sources":["../../src/hooks/useNotifications.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,uBAAuB,CAAC;AAElD,MAAM,WAAW,gBAAgB;IAC/B,EAAE,EAAE,MAAM,CAAC;IACX,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,OAAO,CAAC;IACd,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,iBAAiB;IAChC,aAAa,EAAE,gBAAgB,EAAE,CAAC;IAClC,WAAW,EAAE,MAAM,CAAC;CACrB;AAED,wBAAgB,gBAAgB,CAAC,IAAI,CAAC,EAAE,IAAI,GAAG,IAAI;;;;;iCAwExB,MAAM;;yCAiEN,MAAM;;;;;EAwDhC"}
|
|
1
|
+
{"version":3,"file":"useNotifications.d.ts","sourceRoot":"","sources":["../../src/hooks/useNotifications.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,uBAAuB,CAAC;AAElD,MAAM,WAAW,gBAAgB;IAC/B,EAAE,EAAE,MAAM,CAAC;IACX,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,OAAO,CAAC;IACd,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,iBAAiB;IAChC,aAAa,EAAE,gBAAgB,EAAE,CAAC;IAClC,WAAW,EAAE,MAAM,CAAC;CACrB;AAED,wBAAgB,gBAAgB,CAAC,IAAI,CAAC,EAAE,IAAI,GAAG,IAAI;;;;;iCAwExB,MAAM;;yCAiEN,MAAM;;;;;EAwDhC"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
3
3
|
export function PublicLayout({ children }) {
|
|
4
|
-
return _jsx("section", { className: "pt-16
|
|
4
|
+
return _jsx("section", { className: "pt-16 ", children: children });
|
|
5
5
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"module-add.d.ts","sourceRoot":"","sources":["../../src/scripts/module-add.ts"],"names":[],"mappings":"AAiCA,wBAAsB,SAAS,CAAC,UAAU,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,
|
|
1
|
+
{"version":3,"file":"module-add.d.ts","sourceRoot":"","sources":["../../src/scripts/module-add.ts"],"names":[],"mappings":"AAiCA,wBAAsB,SAAS,CAAC,UAAU,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,iBA+SpE"}
|
|
@@ -145,62 +145,32 @@ export async function addModule(moduleName, targetDir) {
|
|
|
145
145
|
}
|
|
146
146
|
else if (migrationAction === "push") {
|
|
147
147
|
console.log(chalk.yellow("\n⬆️ Application des nouvelles migrations..."));
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
148
|
+
// Appliquer directement avec psql au lieu de supabase migration up
|
|
149
|
+
const migrationsDir = path.join(targetDir, "supabase", "migrations");
|
|
150
|
+
const migrationFiles = fs
|
|
151
|
+
.readdirSync(migrationsDir)
|
|
152
|
+
.filter((f) => f.endsWith(".sql"))
|
|
153
|
+
.filter((f) => copiedMigrationFiles.includes(f))
|
|
154
|
+
.sort();
|
|
155
|
+
if (migrationFiles.length === 0) {
|
|
156
|
+
console.log(chalk.yellow("⚠️ Aucune nouvelle migration à appliquer"));
|
|
155
157
|
}
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
migrationDates = repairMatch[1].trim().split(/\s+/);
|
|
167
|
-
}
|
|
168
|
-
else {
|
|
169
|
-
// Fallback: chercher toutes les dates dans le message
|
|
170
|
-
const allDates = output.match(/20\d{6,14}/g);
|
|
171
|
-
migrationDates = allDates ? [...new Set(allDates)] : [];
|
|
172
|
-
}
|
|
173
|
-
if (migrationDates.length === 0) {
|
|
174
|
-
console.error(chalk.red("❌ Impossible d'extraire les dates de migration"));
|
|
175
|
-
console.log(chalk.gray("\nFaites un reset complet:\n supabase db reset\n"));
|
|
176
|
-
}
|
|
177
|
-
else {
|
|
178
|
-
// Réparation automatique de l'historique
|
|
179
|
-
const datesStr = migrationDates.join(" ");
|
|
180
|
-
console.log(chalk.yellow(`\n🔧 Réparation automatique de l'historique...`));
|
|
181
|
-
console.log(chalk.gray(` Dates: ${datesStr}`));
|
|
182
|
-
try {
|
|
183
|
-
execSync(`supabase migration repair --status reverted ${datesStr} --local`, {
|
|
184
|
-
cwd: targetDir,
|
|
185
|
-
stdio: "inherit",
|
|
186
|
-
});
|
|
187
|
-
console.log(chalk.green("✓ Historique réparé"));
|
|
188
|
-
console.log(chalk.yellow("\n⬆️ Application des nouvelles migrations..."));
|
|
189
|
-
execSync("supabase migration up --local", {
|
|
190
|
-
cwd: targetDir,
|
|
191
|
-
stdio: "inherit",
|
|
192
|
-
});
|
|
193
|
-
console.log(chalk.green("✓ Migrations appliquées"));
|
|
194
|
-
}
|
|
195
|
-
catch (e) {
|
|
196
|
-
console.error(chalk.red("❌ Échec de la réparation automatique"));
|
|
197
|
-
console.log(chalk.gray(`\nVous pouvez essayer manuellement:\n supabase migration repair --status reverted ${datesStr} --local\n supabase migration up --local`));
|
|
198
|
-
console.log(chalk.gray(`\nOu faire un reset complet:\n supabase db reset\n`));
|
|
199
|
-
}
|
|
158
|
+
else {
|
|
159
|
+
try {
|
|
160
|
+
const dbUrl = "postgresql://postgres:postgres@127.0.0.1:54322/postgres";
|
|
161
|
+
for (const file of migrationFiles) {
|
|
162
|
+
const filePath = path.join(migrationsDir, file);
|
|
163
|
+
console.log(chalk.gray(` Exécution de ${file}...`));
|
|
164
|
+
execSync(`psql "${dbUrl}" -f "${filePath}"`, {
|
|
165
|
+
cwd: targetDir,
|
|
166
|
+
stdio: "pipe",
|
|
167
|
+
});
|
|
200
168
|
}
|
|
169
|
+
console.log(chalk.green(`✓ ${migrationFiles.length} migration(s) appliquée(s)`));
|
|
201
170
|
}
|
|
202
|
-
|
|
171
|
+
catch (error) {
|
|
203
172
|
console.error(chalk.red("❌ Erreur lors de l'application des migrations"));
|
|
173
|
+
console.log(chalk.gray("\nVous pouvez essayer manuellement:\n supabase db reset\n"));
|
|
204
174
|
}
|
|
205
175
|
}
|
|
206
176
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"module-build.d.ts","sourceRoot":"","sources":["../../src/scripts/module-build.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"module-build.d.ts","sourceRoot":"","sources":["../../src/scripts/module-build.ts"],"names":[],"mappings":"AAw0CA,wBAAsB,cAAc,kBAyFnC"}
|
|
@@ -259,7 +259,7 @@ function generateMenuConfig(moduleConfigs) {
|
|
|
259
259
|
const configDir = path.join(projectRoot, "config");
|
|
260
260
|
ensureDirectory(configDir);
|
|
261
261
|
const menuPath = path.join(configDir, "menu.ts");
|
|
262
|
-
// Collecter les menus de tous les modules
|
|
262
|
+
// Collecter les menus de tous les modules avec leurs modules sources
|
|
263
263
|
const publicMenus = [];
|
|
264
264
|
const authMenus = [];
|
|
265
265
|
const adminMenus = [];
|
|
@@ -267,16 +267,28 @@ function generateMenuConfig(moduleConfigs) {
|
|
|
267
267
|
moduleConfigs.forEach((moduleConfig) => {
|
|
268
268
|
if (moduleConfig.menu) {
|
|
269
269
|
if (moduleConfig.menu.public) {
|
|
270
|
-
publicMenus.push(...moduleConfig.menu.public)
|
|
270
|
+
publicMenus.push(...moduleConfig.menu.public.map((item) => ({
|
|
271
|
+
...item,
|
|
272
|
+
moduleName: moduleConfig.moduleName,
|
|
273
|
+
})));
|
|
271
274
|
}
|
|
272
275
|
if (moduleConfig.menu.auth) {
|
|
273
|
-
authMenus.push(...moduleConfig.menu.auth)
|
|
276
|
+
authMenus.push(...moduleConfig.menu.auth.map((item) => ({
|
|
277
|
+
...item,
|
|
278
|
+
moduleName: moduleConfig.moduleName,
|
|
279
|
+
})));
|
|
274
280
|
}
|
|
275
281
|
if (moduleConfig.menu.admin) {
|
|
276
|
-
adminMenus.push(...moduleConfig.menu.admin)
|
|
282
|
+
adminMenus.push(...moduleConfig.menu.admin.map((item) => ({
|
|
283
|
+
...item,
|
|
284
|
+
moduleName: moduleConfig.moduleName,
|
|
285
|
+
})));
|
|
277
286
|
}
|
|
278
287
|
if (moduleConfig.menu.account) {
|
|
279
|
-
accountMenus.push(...moduleConfig.menu.account)
|
|
288
|
+
accountMenus.push(...moduleConfig.menu.account.map((item) => ({
|
|
289
|
+
...item,
|
|
290
|
+
moduleName: moduleConfig.moduleName,
|
|
291
|
+
})));
|
|
280
292
|
}
|
|
281
293
|
}
|
|
282
294
|
});
|
|
@@ -288,9 +300,45 @@ function generateMenuConfig(moduleConfigs) {
|
|
|
288
300
|
authMenus.sort(sortByOrder);
|
|
289
301
|
adminMenus.sort(sortByOrder);
|
|
290
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);
|
|
291
334
|
// Générer le contenu du fichier
|
|
292
335
|
const content = `// Auto-generated menu configuration
|
|
293
336
|
// Generated from module build configs
|
|
337
|
+
"use client";
|
|
338
|
+
|
|
339
|
+
import dynamic from "next/dynamic";
|
|
340
|
+
|
|
341
|
+
${componentImports.join("\n")}
|
|
294
342
|
|
|
295
343
|
export interface MenuItem {
|
|
296
344
|
title: string;
|
|
@@ -300,6 +348,11 @@ export interface MenuItem {
|
|
|
300
348
|
order?: number;
|
|
301
349
|
shortcut?: string;
|
|
302
350
|
shortcutDisplay?: string;
|
|
351
|
+
type?: 'text' | 'icon' | 'textIcon';
|
|
352
|
+
position?: 'center' | 'end';
|
|
353
|
+
component?: React.ComponentType<any>;
|
|
354
|
+
componentExport?: string;
|
|
355
|
+
entryPoint?: string;
|
|
303
356
|
}
|
|
304
357
|
|
|
305
358
|
export interface MenuConfig {
|
|
@@ -310,10 +363,10 @@ export interface MenuConfig {
|
|
|
310
363
|
}
|
|
311
364
|
|
|
312
365
|
export const menuConfig: MenuConfig = {
|
|
313
|
-
public: ${
|
|
314
|
-
auth: ${
|
|
315
|
-
admin: ${
|
|
316
|
-
account: ${
|
|
366
|
+
public: [${publicMenusExport.join(",\n ")}],
|
|
367
|
+
auth: [${authMenusExport.join(",\n ")}],
|
|
368
|
+
admin: [${adminMenusExport.join(",\n ")}],
|
|
369
|
+
account: [${accountMenusExport.join(",\n ")}],
|
|
317
370
|
};
|
|
318
371
|
`;
|
|
319
372
|
fs.writeFileSync(menuPath, content);
|
|
@@ -809,6 +862,264 @@ async function generateUserTabsConfig(moduleConfigs) {
|
|
|
809
862
|
console.error("❌ Error generating user tabs configuration:", error);
|
|
810
863
|
}
|
|
811
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
|
+
}
|
|
812
1123
|
export async function runModuleBuild() {
|
|
813
1124
|
ensureDirectory(appDirectory);
|
|
814
1125
|
// Nettoyer les fichiers générés précédemment
|
|
@@ -863,6 +1174,16 @@ export async function runModuleBuild() {
|
|
|
863
1174
|
console.log("📑 Generating user tabs configuration...");
|
|
864
1175
|
}
|
|
865
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);
|
|
866
1187
|
// Message de succès final
|
|
867
1188
|
if (!isDebugMode) {
|
|
868
1189
|
console.log("\n✅ Module build completed successfully!");
|