@lastbrain/app 0.1.22 → 0.1.24
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/scripts/init-app.d.ts.map +1 -1
- package/dist/scripts/init-app.js +400 -29
- package/dist/scripts/module-create.d.ts.map +1 -1
- package/dist/scripts/module-create.js +26 -5
- package/dist/styles.css +1 -1
- package/dist/templates/DefaultDoc.d.ts.map +1 -1
- package/dist/templates/DefaultDoc.js +16 -1
- package/dist/templates/DocPage.d.ts.map +1 -1
- package/dist/templates/DocPage.js +9 -3
- package/package.json +19 -20
- package/src/scripts/init-app.ts +422 -29
- package/src/scripts/module-create.ts +31 -5
- package/src/templates/DefaultDoc.tsx +127 -0
- package/src/templates/DocPage.tsx +9 -3
package/src/scripts/init-app.ts
CHANGED
|
@@ -75,17 +75,20 @@ export async function initApp(options: InitAppOptions) {
|
|
|
75
75
|
// 4. Créer les fichiers de configuration
|
|
76
76
|
await createConfigFiles(targetDir, force, useHeroUI);
|
|
77
77
|
|
|
78
|
-
// 5. Créer
|
|
78
|
+
// 5. Créer le système de proxy storage
|
|
79
|
+
await createStorageProxy(targetDir, force);
|
|
80
|
+
|
|
81
|
+
// 6. Créer .gitignore et .env.local.example
|
|
79
82
|
await createGitIgnore(targetDir, force);
|
|
80
83
|
await createEnvExample(targetDir, force);
|
|
81
84
|
|
|
82
|
-
//
|
|
85
|
+
// 7. Créer la structure Supabase avec migrations
|
|
83
86
|
await createSupabaseStructure(targetDir, force);
|
|
84
87
|
|
|
85
|
-
//
|
|
88
|
+
// 8. Ajouter les scripts NPM
|
|
86
89
|
await addScriptsToPackageJson(targetDir);
|
|
87
90
|
|
|
88
|
-
//
|
|
91
|
+
// 9. Enregistrer les modules sélectionnés
|
|
89
92
|
if (withAuth || selectedModules.length > 0) {
|
|
90
93
|
await saveModulesConfig(targetDir, selectedModules, withAuth);
|
|
91
94
|
}
|
|
@@ -192,6 +195,62 @@ async function ensurePackageJson(targetDir: string, projectName: string) {
|
|
|
192
195
|
}
|
|
193
196
|
}
|
|
194
197
|
|
|
198
|
+
/**
|
|
199
|
+
* Récupère les versions actuelles des packages LastBrain depuis npm ou le monorepo
|
|
200
|
+
*/
|
|
201
|
+
function getLastBrainVersions(targetDir: string): {
|
|
202
|
+
app: string;
|
|
203
|
+
core: string;
|
|
204
|
+
ui: string;
|
|
205
|
+
moduleAuth: string;
|
|
206
|
+
} {
|
|
207
|
+
const targetIsInMonorepo = fs.existsSync(path.join(targetDir, "../../../packages/core/package.json")) ||
|
|
208
|
+
fs.existsSync(path.join(targetDir, "../../packages/core/package.json"));
|
|
209
|
+
|
|
210
|
+
if (targetIsInMonorepo) {
|
|
211
|
+
// Dans le monorepo, utiliser workspace:*
|
|
212
|
+
return {
|
|
213
|
+
app: "workspace:*",
|
|
214
|
+
core: "workspace:*",
|
|
215
|
+
ui: "workspace:*",
|
|
216
|
+
moduleAuth: "workspace:*",
|
|
217
|
+
};
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
// Hors monorepo, lire les versions depuis les package.json locaux (plus fiable que "latest")
|
|
221
|
+
try {
|
|
222
|
+
// Essayer de lire depuis node_modules/@lastbrain (si init-app a été installé)
|
|
223
|
+
const appPkgPath = path.join(__dirname, "../../package.json");
|
|
224
|
+
|
|
225
|
+
if (fs.existsSync(appPkgPath)) {
|
|
226
|
+
const appPkg = JSON.parse(fs.readFileSync(appPkgPath, "utf-8"));
|
|
227
|
+
const appVersion = `^${appPkg.version}`;
|
|
228
|
+
|
|
229
|
+
// Lire les versions des dépendances de @lastbrain/app
|
|
230
|
+
const coreDep = appPkg.dependencies?.["@lastbrain/core"];
|
|
231
|
+
const uiDep = appPkg.dependencies?.["@lastbrain/ui"];
|
|
232
|
+
const authDep = appPkg.dependencies?.["@lastbrain/module-auth"];
|
|
233
|
+
|
|
234
|
+
return {
|
|
235
|
+
app: appVersion,
|
|
236
|
+
core: coreDep || appVersion,
|
|
237
|
+
ui: uiDep || appVersion,
|
|
238
|
+
moduleAuth: authDep || appVersion,
|
|
239
|
+
};
|
|
240
|
+
}
|
|
241
|
+
} catch (error) {
|
|
242
|
+
console.warn(chalk.yellow("⚠️ Impossible de lire les versions locales, utilisation de 'latest'"));
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
// Fallback: utiliser "latest"
|
|
246
|
+
return {
|
|
247
|
+
app: "latest",
|
|
248
|
+
core: "latest",
|
|
249
|
+
ui: "latest",
|
|
250
|
+
moduleAuth: "latest",
|
|
251
|
+
};
|
|
252
|
+
}
|
|
253
|
+
|
|
195
254
|
async function addDependencies(
|
|
196
255
|
targetDir: string,
|
|
197
256
|
useHeroUI: boolean,
|
|
@@ -203,33 +262,30 @@ async function addDependencies(
|
|
|
203
262
|
|
|
204
263
|
console.log(chalk.yellow("\n📦 Configuration des dépendances..."));
|
|
205
264
|
|
|
206
|
-
|
|
207
|
-
// Vérifier si le projet cible est à l'intérieur du monorepo
|
|
208
|
-
const targetIsInMonorepo = fs.existsSync(path.join(targetDir, "../../../packages/core/package.json")) ||
|
|
209
|
-
fs.existsSync(path.join(targetDir, "../../packages/core/package.json"));
|
|
210
|
-
const latestVersion = targetIsInMonorepo ? "workspace:*" : "latest";
|
|
265
|
+
const versions = getLastBrainVersions(targetDir);
|
|
211
266
|
|
|
212
267
|
// Dependencies
|
|
213
268
|
const requiredDeps: Record<string, string> = {
|
|
214
269
|
next: "^15.0.3",
|
|
215
270
|
react: "^18.3.1",
|
|
216
271
|
"react-dom": "^18.3.1",
|
|
217
|
-
"@lastbrain/app":
|
|
218
|
-
"@lastbrain/core":
|
|
219
|
-
"@lastbrain/ui":
|
|
272
|
+
"@lastbrain/app": versions.app,
|
|
273
|
+
"@lastbrain/core": versions.core,
|
|
274
|
+
"@lastbrain/ui": versions.ui,
|
|
220
275
|
"next-themes": "^0.4.6",
|
|
221
276
|
};
|
|
222
277
|
|
|
223
278
|
// Ajouter module-auth si demandé
|
|
224
279
|
if (withAuth) {
|
|
225
|
-
requiredDeps["@lastbrain/module-auth"] =
|
|
280
|
+
requiredDeps["@lastbrain/module-auth"] = versions.moduleAuth;
|
|
226
281
|
}
|
|
227
282
|
|
|
228
283
|
// Ajouter les autres modules sélectionnés
|
|
229
284
|
for (const moduleName of selectedModules) {
|
|
230
285
|
const moduleInfo = AVAILABLE_MODULES.find(m => m.name === moduleName);
|
|
231
286
|
if (moduleInfo && moduleInfo.package !== "@lastbrain/module-auth") {
|
|
232
|
-
|
|
287
|
+
// Pour les autres modules, utiliser "latest" ou la version depuis le package
|
|
288
|
+
requiredDeps[moduleInfo.package] = versions.app; // Utiliser la même version que app
|
|
233
289
|
}
|
|
234
290
|
}
|
|
235
291
|
|
|
@@ -270,6 +326,7 @@ async function addDependencies(
|
|
|
270
326
|
requiredDeps["@heroui/switch"] = "^2.2.24";
|
|
271
327
|
requiredDeps["@heroui/table"] = "^2.2.27";
|
|
272
328
|
requiredDeps["@heroui/tabs"] = "^2.2.24";
|
|
329
|
+
requiredDeps["@heroui/system"] = "^2.4.23"; // Ajout pour HeroUIProvider
|
|
273
330
|
requiredDeps["@heroui/toast"] = "^2.0.17";
|
|
274
331
|
requiredDeps["@heroui/tooltip"] = "^2.2.24";
|
|
275
332
|
requiredDeps["@heroui/user"] = "^2.2.22";
|
|
@@ -1009,21 +1066,11 @@ async function addScriptsToPackageJson(targetDir: string) {
|
|
|
1009
1066
|
build: "next build",
|
|
1010
1067
|
start: "next start",
|
|
1011
1068
|
lint: "next lint",
|
|
1012
|
-
lastbrain:
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
"
|
|
1016
|
-
|
|
1017
|
-
: "node node_modules/@lastbrain/app/dist/scripts/module-build.js",
|
|
1018
|
-
"db:migrations:sync": targetIsInMonorepo
|
|
1019
|
-
? "pnpm exec lastbrain db:migrations:sync"
|
|
1020
|
-
: "node node_modules/@lastbrain/app/dist/scripts/db-migrations-sync.js",
|
|
1021
|
-
"db:init": targetIsInMonorepo
|
|
1022
|
-
? "pnpm exec lastbrain db:init"
|
|
1023
|
-
: "node node_modules/@lastbrain/app/dist/scripts/db-init.js",
|
|
1024
|
-
"readme:create": targetIsInMonorepo
|
|
1025
|
-
? "pnpm exec lastbrain readme:create"
|
|
1026
|
-
: "node node_modules/@lastbrain/app/dist/scripts/readme-build.js",
|
|
1069
|
+
lastbrain: "node node_modules/@lastbrain/app/dist/cli.js",
|
|
1070
|
+
"build:modules": "node node_modules/@lastbrain/app/dist/scripts/module-build.js",
|
|
1071
|
+
"db:migrations:sync": "node node_modules/@lastbrain/app/dist/scripts/db-migrations-sync.js",
|
|
1072
|
+
"db:init": "node node_modules/@lastbrain/app/dist/scripts/db-init.js",
|
|
1073
|
+
"readme:create": "node node_modules/@lastbrain/app/dist/scripts/readme-build.js",
|
|
1027
1074
|
};
|
|
1028
1075
|
|
|
1029
1076
|
pkg.scripts = { ...pkg.scripts, ...scripts };
|
|
@@ -1075,3 +1122,349 @@ async function saveModulesConfig(
|
|
|
1075
1122
|
await fs.writeJson(modulesConfigPath, { modules }, { spaces: 2 });
|
|
1076
1123
|
console.log(chalk.green("✓ Configuration des modules sauvegardée"));
|
|
1077
1124
|
}
|
|
1125
|
+
|
|
1126
|
+
async function createStorageProxy(targetDir: string, force: boolean) {
|
|
1127
|
+
console.log(chalk.yellow("\n🗂️ Création du système de proxy storage..."));
|
|
1128
|
+
|
|
1129
|
+
// Créer le dossier lib
|
|
1130
|
+
const libDir = path.join(targetDir, "lib");
|
|
1131
|
+
await fs.ensureDir(libDir);
|
|
1132
|
+
|
|
1133
|
+
// 1. Créer lib/bucket-config.ts
|
|
1134
|
+
const bucketConfigPath = path.join(libDir, "bucket-config.ts");
|
|
1135
|
+
if (!fs.existsSync(bucketConfigPath) || force) {
|
|
1136
|
+
const bucketConfigContent = `/**
|
|
1137
|
+
* Storage configuration for buckets and access control
|
|
1138
|
+
*/
|
|
1139
|
+
|
|
1140
|
+
export interface BucketConfig {
|
|
1141
|
+
name: string;
|
|
1142
|
+
isPublic: boolean;
|
|
1143
|
+
description: string;
|
|
1144
|
+
allowedFileTypes?: string[];
|
|
1145
|
+
maxFileSize?: number; // in bytes
|
|
1146
|
+
customAccessControl?: (userId: string, filePath: string) => boolean;
|
|
1147
|
+
}
|
|
1148
|
+
|
|
1149
|
+
export const BUCKET_CONFIGS: Record<string, BucketConfig> = {
|
|
1150
|
+
avatar: {
|
|
1151
|
+
name: "avatar",
|
|
1152
|
+
isPublic: true,
|
|
1153
|
+
description: "User profile pictures and avatars",
|
|
1154
|
+
allowedFileTypes: ["image/jpeg", "image/png", "image/webp", "image/gif"],
|
|
1155
|
+
maxFileSize: 10 * 1024 * 1024, // 10MB
|
|
1156
|
+
},
|
|
1157
|
+
app: {
|
|
1158
|
+
name: "app",
|
|
1159
|
+
isPublic: false,
|
|
1160
|
+
description: "Private user files and documents",
|
|
1161
|
+
maxFileSize: 100 * 1024 * 1024, // 100MB
|
|
1162
|
+
customAccessControl: (userId: string, filePath: string) => {
|
|
1163
|
+
// Users can only access files in their own folder (app/{userId}/...)
|
|
1164
|
+
return filePath.startsWith(\`\${userId}/\`);
|
|
1165
|
+
},
|
|
1166
|
+
},
|
|
1167
|
+
// Example for future buckets:
|
|
1168
|
+
// public: {
|
|
1169
|
+
// name: "public",
|
|
1170
|
+
// isPublic: true,
|
|
1171
|
+
// description: "Publicly accessible files like logos, banners",
|
|
1172
|
+
// allowedFileTypes: ["image/jpeg", "image/png", "image/webp", "application/pdf"],
|
|
1173
|
+
// maxFileSize: 50 * 1024 * 1024, // 50MB
|
|
1174
|
+
// },
|
|
1175
|
+
// documents: {
|
|
1176
|
+
// name: "documents",
|
|
1177
|
+
// isPublic: false,
|
|
1178
|
+
// description: "Private documents requiring authentication",
|
|
1179
|
+
// allowedFileTypes: ["application/pdf", "application/msword", "text/plain"],
|
|
1180
|
+
// maxFileSize: 25 * 1024 * 1024, // 25MB
|
|
1181
|
+
// },
|
|
1182
|
+
};
|
|
1183
|
+
|
|
1184
|
+
/**
|
|
1185
|
+
* Get bucket configuration
|
|
1186
|
+
*/
|
|
1187
|
+
export function getBucketConfig(bucketName: string): BucketConfig | null {
|
|
1188
|
+
return BUCKET_CONFIGS[bucketName] || null;
|
|
1189
|
+
}
|
|
1190
|
+
|
|
1191
|
+
/**
|
|
1192
|
+
* Check if bucket is public
|
|
1193
|
+
*/
|
|
1194
|
+
export function isPublicBucket(bucketName: string): boolean {
|
|
1195
|
+
const config = getBucketConfig(bucketName);
|
|
1196
|
+
return config?.isPublic ?? false;
|
|
1197
|
+
}
|
|
1198
|
+
|
|
1199
|
+
/**
|
|
1200
|
+
* Check if user has access to a specific file
|
|
1201
|
+
*/
|
|
1202
|
+
export function hasFileAccess(bucketName: string, userId: string, filePath: string): boolean {
|
|
1203
|
+
const config = getBucketConfig(bucketName);
|
|
1204
|
+
if (!config) return false;
|
|
1205
|
+
|
|
1206
|
+
// Public buckets are accessible to everyone
|
|
1207
|
+
if (config.isPublic) return true;
|
|
1208
|
+
|
|
1209
|
+
// Private buckets require authentication
|
|
1210
|
+
if (!userId) return false;
|
|
1211
|
+
|
|
1212
|
+
// Apply custom access control if defined
|
|
1213
|
+
if (config.customAccessControl) {
|
|
1214
|
+
return config.customAccessControl(userId, filePath);
|
|
1215
|
+
}
|
|
1216
|
+
|
|
1217
|
+
return true;
|
|
1218
|
+
}
|
|
1219
|
+
|
|
1220
|
+
/**
|
|
1221
|
+
* Validate file type for bucket
|
|
1222
|
+
*/
|
|
1223
|
+
export function isValidFileType(bucketName: string, contentType: string): boolean {
|
|
1224
|
+
const config = getBucketConfig(bucketName);
|
|
1225
|
+
if (!config || !config.allowedFileTypes) return true;
|
|
1226
|
+
|
|
1227
|
+
return config.allowedFileTypes.includes(contentType);
|
|
1228
|
+
}
|
|
1229
|
+
|
|
1230
|
+
/**
|
|
1231
|
+
* Check if file size is within bucket limits
|
|
1232
|
+
*/
|
|
1233
|
+
export function isValidFileSize(bucketName: string, fileSize: number): boolean {
|
|
1234
|
+
const config = getBucketConfig(bucketName);
|
|
1235
|
+
if (!config || !config.maxFileSize) return true;
|
|
1236
|
+
|
|
1237
|
+
return fileSize <= config.maxFileSize;
|
|
1238
|
+
}`;
|
|
1239
|
+
|
|
1240
|
+
await fs.writeFile(bucketConfigPath, bucketConfigContent);
|
|
1241
|
+
console.log(chalk.green("✓ lib/bucket-config.ts créé"));
|
|
1242
|
+
}
|
|
1243
|
+
|
|
1244
|
+
// 2. Créer lib/storage.ts
|
|
1245
|
+
const storagePath = path.join(libDir, "storage.ts");
|
|
1246
|
+
if (!fs.existsSync(storagePath) || force) {
|
|
1247
|
+
const storageContent = `/**
|
|
1248
|
+
* Build storage proxy URL for files in Supabase buckets
|
|
1249
|
+
*
|
|
1250
|
+
* @param bucket - The bucket name (e.g., "avatar", "app")
|
|
1251
|
+
* @param path - The file path within the bucket
|
|
1252
|
+
* @returns Proxied URL (e.g., "/api/storage/avatar/user_128_123456.webp")
|
|
1253
|
+
*/
|
|
1254
|
+
export function buildStorageUrl(bucket: string, path: string): string {
|
|
1255
|
+
// Remove leading slash if present
|
|
1256
|
+
const cleanPath = path.startsWith("/") ? path.slice(1) : path;
|
|
1257
|
+
|
|
1258
|
+
// Remove bucket prefix from path if present (e.g., "avatar/file.jpg" -> "file.jpg")
|
|
1259
|
+
const pathWithoutBucket = cleanPath.startsWith(bucket + "/")
|
|
1260
|
+
? cleanPath.slice(bucket.length + 1)
|
|
1261
|
+
: cleanPath;
|
|
1262
|
+
|
|
1263
|
+
return \`/api/storage/\${bucket}/\${pathWithoutBucket}\`;
|
|
1264
|
+
}
|
|
1265
|
+
|
|
1266
|
+
/**
|
|
1267
|
+
* Extract bucket and path from a storage URL
|
|
1268
|
+
*
|
|
1269
|
+
* @param url - Storage URL (can be proxied URL or Supabase public URL)
|
|
1270
|
+
* @returns Object with bucket and path, or null if not a valid storage URL
|
|
1271
|
+
*/
|
|
1272
|
+
export function parseStorageUrl(url: string): { bucket: string; path: string } | null {
|
|
1273
|
+
// Handle proxy URLs like "/api/storage/avatar/file.jpg"
|
|
1274
|
+
const proxyMatch = url.match(/^\\/api\\/storage\\/([^\\/]+)\\/(.+)$/);
|
|
1275
|
+
if (proxyMatch) {
|
|
1276
|
+
return {
|
|
1277
|
+
bucket: proxyMatch[1],
|
|
1278
|
+
path: proxyMatch[2]
|
|
1279
|
+
};
|
|
1280
|
+
}
|
|
1281
|
+
|
|
1282
|
+
// Handle Supabase public URLs
|
|
1283
|
+
const supabaseMatch = url.match(/\\/storage\\/v1\\/object\\/public\\/([^\\/]+)\\/(.+)$/);
|
|
1284
|
+
if (supabaseMatch) {
|
|
1285
|
+
return {
|
|
1286
|
+
bucket: supabaseMatch[1],
|
|
1287
|
+
path: supabaseMatch[2]
|
|
1288
|
+
};
|
|
1289
|
+
}
|
|
1290
|
+
|
|
1291
|
+
return null;
|
|
1292
|
+
}
|
|
1293
|
+
|
|
1294
|
+
/**
|
|
1295
|
+
* Convert a Supabase storage path to proxy URL
|
|
1296
|
+
*
|
|
1297
|
+
* @param storagePath - Path like "avatar/file.jpg" or "app/user/file.pdf"
|
|
1298
|
+
* @returns Proxied URL
|
|
1299
|
+
*/
|
|
1300
|
+
export function storagePathToProxyUrl(storagePath: string): string {
|
|
1301
|
+
const parts = storagePath.split("/");
|
|
1302
|
+
if (parts.length < 2) {
|
|
1303
|
+
throw new Error("Invalid storage path format");
|
|
1304
|
+
}
|
|
1305
|
+
|
|
1306
|
+
const bucket = parts[0];
|
|
1307
|
+
const path = parts.slice(1).join("/");
|
|
1308
|
+
|
|
1309
|
+
return buildStorageUrl(bucket, path);
|
|
1310
|
+
}
|
|
1311
|
+
|
|
1312
|
+
/**
|
|
1313
|
+
* List of public buckets that don't require authentication
|
|
1314
|
+
*/
|
|
1315
|
+
export const PUBLIC_BUCKETS = ["avatar"];
|
|
1316
|
+
|
|
1317
|
+
/**
|
|
1318
|
+
* List of private buckets that require authentication
|
|
1319
|
+
*/
|
|
1320
|
+
export const PRIVATE_BUCKETS = ["app"];
|
|
1321
|
+
|
|
1322
|
+
/**
|
|
1323
|
+
* Check if a bucket is public
|
|
1324
|
+
*/
|
|
1325
|
+
export function isPublicBucket(bucket: string): boolean {
|
|
1326
|
+
return PUBLIC_BUCKETS.includes(bucket);
|
|
1327
|
+
}
|
|
1328
|
+
|
|
1329
|
+
/**
|
|
1330
|
+
* Check if a bucket is private
|
|
1331
|
+
*/
|
|
1332
|
+
export function isPrivateBucket(bucket: string): boolean {
|
|
1333
|
+
return PRIVATE_BUCKETS.includes(bucket);
|
|
1334
|
+
}`;
|
|
1335
|
+
|
|
1336
|
+
await fs.writeFile(storagePath, storageContent);
|
|
1337
|
+
console.log(chalk.green("✓ lib/storage.ts créé"));
|
|
1338
|
+
}
|
|
1339
|
+
|
|
1340
|
+
// 3. Créer app/api/storage/[bucket]/[...path]/route.ts
|
|
1341
|
+
const apiStorageDir = path.join(targetDir, "app", "api", "storage", "[bucket]", "[...path]");
|
|
1342
|
+
await fs.ensureDir(apiStorageDir);
|
|
1343
|
+
|
|
1344
|
+
const routePath = path.join(apiStorageDir, "route.ts");
|
|
1345
|
+
if (!fs.existsSync(routePath) || force) {
|
|
1346
|
+
const routeContent = `import { NextRequest, NextResponse } from "next/server";
|
|
1347
|
+
import { getSupabaseServerClient } from "@lastbrain/core/server";
|
|
1348
|
+
import { getBucketConfig, hasFileAccess } from "@/lib/bucket-config";
|
|
1349
|
+
|
|
1350
|
+
/**
|
|
1351
|
+
* GET /api/storage/[bucket]/[...path]
|
|
1352
|
+
* Proxy for Supabase Storage files with clean URLs and access control
|
|
1353
|
+
*
|
|
1354
|
+
* Examples:
|
|
1355
|
+
* - /api/storage/avatar/user_128_123456.webp
|
|
1356
|
+
* - /api/storage/app/user/documents/file.pdf
|
|
1357
|
+
*/
|
|
1358
|
+
export async function GET(
|
|
1359
|
+
request: NextRequest,
|
|
1360
|
+
props: { params: Promise<{ bucket: string; path: string[] }> }
|
|
1361
|
+
) {
|
|
1362
|
+
try {
|
|
1363
|
+
const { bucket, path } = await props.params;
|
|
1364
|
+
const filePath = path.join("/");
|
|
1365
|
+
|
|
1366
|
+
// Check if bucket exists in our configuration
|
|
1367
|
+
const bucketConfig = getBucketConfig(bucket);
|
|
1368
|
+
if (!bucketConfig) {
|
|
1369
|
+
return new NextResponse("Bucket not allowed", { status: 403 });
|
|
1370
|
+
}
|
|
1371
|
+
|
|
1372
|
+
const supabase = await getSupabaseServerClient();
|
|
1373
|
+
let userId: string | null = null;
|
|
1374
|
+
|
|
1375
|
+
// Get user for private buckets or custom access control
|
|
1376
|
+
if (!bucketConfig.isPublic) {
|
|
1377
|
+
const { data: { user }, error: authError } = await supabase.auth.getUser();
|
|
1378
|
+
|
|
1379
|
+
if (authError || !user) {
|
|
1380
|
+
return new NextResponse("Unauthorized", { status: 401 });
|
|
1381
|
+
}
|
|
1382
|
+
|
|
1383
|
+
userId = user.id;
|
|
1384
|
+
}
|
|
1385
|
+
|
|
1386
|
+
// Check file access permissions
|
|
1387
|
+
if (!hasFileAccess(bucket, userId || "", filePath)) {
|
|
1388
|
+
return new NextResponse("Forbidden - Access denied to this file", { status: 403 });
|
|
1389
|
+
}
|
|
1390
|
+
|
|
1391
|
+
// Get file from Supabase Storage
|
|
1392
|
+
const { data: file, error } = await supabase.storage
|
|
1393
|
+
.from(bucket)
|
|
1394
|
+
.download(filePath);
|
|
1395
|
+
|
|
1396
|
+
if (error) {
|
|
1397
|
+
console.error("Storage download error:", error);
|
|
1398
|
+
if (error.message.includes("not found")) {
|
|
1399
|
+
return new NextResponse("File not found", { status: 404 });
|
|
1400
|
+
}
|
|
1401
|
+
return new NextResponse("Storage error", { status: 500 });
|
|
1402
|
+
}
|
|
1403
|
+
|
|
1404
|
+
if (!file) {
|
|
1405
|
+
return new NextResponse("File not found", { status: 404 });
|
|
1406
|
+
}
|
|
1407
|
+
|
|
1408
|
+
// Convert blob to array buffer
|
|
1409
|
+
const arrayBuffer = await file.arrayBuffer();
|
|
1410
|
+
|
|
1411
|
+
// Determine content type from file extension
|
|
1412
|
+
const getContentType = (filename: string): string => {
|
|
1413
|
+
const ext = filename.toLowerCase().split(".").pop();
|
|
1414
|
+
const mimeTypes: Record<string, string> = {
|
|
1415
|
+
// Images
|
|
1416
|
+
jpg: "image/jpeg",
|
|
1417
|
+
jpeg: "image/jpeg",
|
|
1418
|
+
png: "image/png",
|
|
1419
|
+
gif: "image/gif",
|
|
1420
|
+
webp: "image/webp",
|
|
1421
|
+
svg: "image/svg+xml",
|
|
1422
|
+
// Documents
|
|
1423
|
+
pdf: "application/pdf",
|
|
1424
|
+
doc: "application/msword",
|
|
1425
|
+
docx: "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
|
|
1426
|
+
xls: "application/vnd.ms-excel",
|
|
1427
|
+
xlsx: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
|
|
1428
|
+
// Text
|
|
1429
|
+
txt: "text/plain",
|
|
1430
|
+
csv: "text/csv",
|
|
1431
|
+
// Videos
|
|
1432
|
+
mp4: "video/mp4",
|
|
1433
|
+
avi: "video/x-msvideo",
|
|
1434
|
+
mov: "video/quicktime",
|
|
1435
|
+
// Audio
|
|
1436
|
+
mp3: "audio/mpeg",
|
|
1437
|
+
wav: "audio/wav",
|
|
1438
|
+
// Archives
|
|
1439
|
+
zip: "application/zip",
|
|
1440
|
+
rar: "application/x-rar-compressed",
|
|
1441
|
+
};
|
|
1442
|
+
return mimeTypes[ext || ""] || "application/octet-stream";
|
|
1443
|
+
};
|
|
1444
|
+
|
|
1445
|
+
const contentType = getContentType(filePath);
|
|
1446
|
+
|
|
1447
|
+
// Create response with proper headers
|
|
1448
|
+
const response = new NextResponse(arrayBuffer, {
|
|
1449
|
+
status: 200,
|
|
1450
|
+
headers: {
|
|
1451
|
+
"Content-Type": contentType,
|
|
1452
|
+
"Cache-Control": "public, max-age=31536000, immutable", // Cache for 1 year
|
|
1453
|
+
"Content-Length": arrayBuffer.byteLength.toString(),
|
|
1454
|
+
},
|
|
1455
|
+
});
|
|
1456
|
+
|
|
1457
|
+
return response;
|
|
1458
|
+
|
|
1459
|
+
} catch (error) {
|
|
1460
|
+
console.error("Storage proxy error:", error);
|
|
1461
|
+
return new NextResponse("Internal server error", { status: 500 });
|
|
1462
|
+
}
|
|
1463
|
+
}`;
|
|
1464
|
+
|
|
1465
|
+
await fs.writeFile(routePath, routeContent);
|
|
1466
|
+
console.log(chalk.green("✓ app/api/storage/[bucket]/[...path]/route.ts créé"));
|
|
1467
|
+
}
|
|
1468
|
+
|
|
1469
|
+
console.log(chalk.green("✓ Système de proxy storage configuré"));
|
|
1470
|
+
}
|
|
@@ -45,10 +45,36 @@ function parseTablesList(input: string): string[] {
|
|
|
45
45
|
.filter((t) => t.length > 0);
|
|
46
46
|
}
|
|
47
47
|
|
|
48
|
+
/**
|
|
49
|
+
* Récupère les versions actuelles des packages LastBrain
|
|
50
|
+
*/
|
|
51
|
+
function getLastBrainPackageVersions(rootDir: string): { core: string; ui: string } {
|
|
52
|
+
try {
|
|
53
|
+
const corePackageJson = JSON.parse(
|
|
54
|
+
fs.readFileSync(path.join(rootDir, "packages", "core", "package.json"), "utf-8")
|
|
55
|
+
);
|
|
56
|
+
const uiPackageJson = JSON.parse(
|
|
57
|
+
fs.readFileSync(path.join(rootDir, "packages", "ui", "package.json"), "utf-8")
|
|
58
|
+
);
|
|
59
|
+
|
|
60
|
+
return {
|
|
61
|
+
core: `^${corePackageJson.version}`,
|
|
62
|
+
ui: `^${uiPackageJson.version}`,
|
|
63
|
+
};
|
|
64
|
+
} catch (error) {
|
|
65
|
+
console.warn(chalk.yellow("⚠️ Impossible de lire les versions des packages, utilisation des versions par défaut"));
|
|
66
|
+
return {
|
|
67
|
+
core: "^0.1.0",
|
|
68
|
+
ui: "^0.1.4",
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
48
73
|
/**
|
|
49
74
|
* Génère le contenu du package.json
|
|
50
75
|
*/
|
|
51
|
-
function generatePackageJson(moduleName: string, slug: string): string {
|
|
76
|
+
function generatePackageJson(moduleName: string, slug: string, rootDir: string): string {
|
|
77
|
+
const versions = getLastBrainPackageVersions(rootDir);
|
|
52
78
|
const buildConfigExport = `./${slug}.build.config`;
|
|
53
79
|
return JSON.stringify(
|
|
54
80
|
{
|
|
@@ -58,14 +84,14 @@ function generatePackageJson(moduleName: string, slug: string): string {
|
|
|
58
84
|
type: "module",
|
|
59
85
|
main: "dist/index.js",
|
|
60
86
|
types: "dist/index.d.ts",
|
|
61
|
-
files: ["dist", "supabase"],
|
|
87
|
+
files: ["dist", "src", "supabase"],
|
|
62
88
|
scripts: {
|
|
63
89
|
build: "tsc -p tsconfig.json",
|
|
64
90
|
dev: "tsc -p tsconfig.json --watch",
|
|
65
91
|
},
|
|
66
92
|
dependencies: {
|
|
67
|
-
"@lastbrain/core":
|
|
68
|
-
"@lastbrain/ui":
|
|
93
|
+
"@lastbrain/core": versions.core,
|
|
94
|
+
"@lastbrain/ui": versions.ui,
|
|
69
95
|
react: "^19.0.0",
|
|
70
96
|
"lucide-react": "^0.554.0",
|
|
71
97
|
"react-dom": "^19.0.0",
|
|
@@ -560,7 +586,7 @@ async function createModuleStructure(config: ModuleConfig, rootDir: string) {
|
|
|
560
586
|
console.log(chalk.yellow(" 📄 package.json"));
|
|
561
587
|
await fs.writeFile(
|
|
562
588
|
path.join(moduleDir, "package.json"),
|
|
563
|
-
generatePackageJson(config.moduleName, config.slug)
|
|
589
|
+
generatePackageJson(config.moduleName, config.slug, rootDir)
|
|
564
590
|
);
|
|
565
591
|
|
|
566
592
|
// Créer tsconfig.json
|