@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
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"init-app.d.ts","sourceRoot":"","sources":["../../src/scripts/init-app.ts"],"names":[],"mappings":"AAWA,UAAU,cAAc;IACtB,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,OAAO,CAAC;IACf,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE,OAAO,CAAC;IACnB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,WAAW,CAAC,EAAE,OAAO,CAAC;CACvB;AAED,wBAAsB,OAAO,CAAC,OAAO,EAAE,cAAc,
|
|
1
|
+
{"version":3,"file":"init-app.d.ts","sourceRoot":"","sources":["../../src/scripts/init-app.ts"],"names":[],"mappings":"AAWA,UAAU,cAAc;IACtB,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,OAAO,CAAC;IACf,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE,OAAO,CAAC;IACnB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,WAAW,CAAC,EAAE,OAAO,CAAC;CACvB;AAED,wBAAsB,OAAO,CAAC,OAAO,EAAE,cAAc,iBAwJpD"}
|
package/dist/scripts/init-app.js
CHANGED
|
@@ -48,14 +48,16 @@ export async function initApp(options) {
|
|
|
48
48
|
await createNextStructure(targetDir, force, useHeroUI, withAuth);
|
|
49
49
|
// 4. Créer les fichiers de configuration
|
|
50
50
|
await createConfigFiles(targetDir, force, useHeroUI);
|
|
51
|
-
// 5. Créer
|
|
51
|
+
// 5. Créer le système de proxy storage
|
|
52
|
+
await createStorageProxy(targetDir, force);
|
|
53
|
+
// 6. Créer .gitignore et .env.local.example
|
|
52
54
|
await createGitIgnore(targetDir, force);
|
|
53
55
|
await createEnvExample(targetDir, force);
|
|
54
|
-
//
|
|
56
|
+
// 7. Créer la structure Supabase avec migrations
|
|
55
57
|
await createSupabaseStructure(targetDir, force);
|
|
56
|
-
//
|
|
58
|
+
// 8. Ajouter les scripts NPM
|
|
57
59
|
await addScriptsToPackageJson(targetDir);
|
|
58
|
-
//
|
|
60
|
+
// 9. Enregistrer les modules sélectionnés
|
|
59
61
|
if (withAuth || selectedModules.length > 0) {
|
|
60
62
|
await saveModulesConfig(targetDir, selectedModules, withAuth);
|
|
61
63
|
}
|
|
@@ -146,34 +148,76 @@ async function ensurePackageJson(targetDir, projectName) {
|
|
|
146
148
|
console.log(chalk.green("✓ package.json existe"));
|
|
147
149
|
}
|
|
148
150
|
}
|
|
151
|
+
/**
|
|
152
|
+
* Récupère les versions actuelles des packages LastBrain depuis npm ou le monorepo
|
|
153
|
+
*/
|
|
154
|
+
function getLastBrainVersions(targetDir) {
|
|
155
|
+
const targetIsInMonorepo = fs.existsSync(path.join(targetDir, "../../../packages/core/package.json")) ||
|
|
156
|
+
fs.existsSync(path.join(targetDir, "../../packages/core/package.json"));
|
|
157
|
+
if (targetIsInMonorepo) {
|
|
158
|
+
// Dans le monorepo, utiliser workspace:*
|
|
159
|
+
return {
|
|
160
|
+
app: "workspace:*",
|
|
161
|
+
core: "workspace:*",
|
|
162
|
+
ui: "workspace:*",
|
|
163
|
+
moduleAuth: "workspace:*",
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
// Hors monorepo, lire les versions depuis les package.json locaux (plus fiable que "latest")
|
|
167
|
+
try {
|
|
168
|
+
// Essayer de lire depuis node_modules/@lastbrain (si init-app a été installé)
|
|
169
|
+
const appPkgPath = path.join(__dirname, "../../package.json");
|
|
170
|
+
if (fs.existsSync(appPkgPath)) {
|
|
171
|
+
const appPkg = JSON.parse(fs.readFileSync(appPkgPath, "utf-8"));
|
|
172
|
+
const appVersion = `^${appPkg.version}`;
|
|
173
|
+
// Lire les versions des dépendances de @lastbrain/app
|
|
174
|
+
const coreDep = appPkg.dependencies?.["@lastbrain/core"];
|
|
175
|
+
const uiDep = appPkg.dependencies?.["@lastbrain/ui"];
|
|
176
|
+
const authDep = appPkg.dependencies?.["@lastbrain/module-auth"];
|
|
177
|
+
return {
|
|
178
|
+
app: appVersion,
|
|
179
|
+
core: coreDep || appVersion,
|
|
180
|
+
ui: uiDep || appVersion,
|
|
181
|
+
moduleAuth: authDep || appVersion,
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
catch (error) {
|
|
186
|
+
console.warn(chalk.yellow("⚠️ Impossible de lire les versions locales, utilisation de 'latest'"));
|
|
187
|
+
}
|
|
188
|
+
// Fallback: utiliser "latest"
|
|
189
|
+
return {
|
|
190
|
+
app: "latest",
|
|
191
|
+
core: "latest",
|
|
192
|
+
ui: "latest",
|
|
193
|
+
moduleAuth: "latest",
|
|
194
|
+
};
|
|
195
|
+
}
|
|
149
196
|
async function addDependencies(targetDir, useHeroUI, withAuth, selectedModules = []) {
|
|
150
197
|
const pkgPath = path.join(targetDir, "package.json");
|
|
151
198
|
const pkg = await fs.readJson(pkgPath);
|
|
152
199
|
console.log(chalk.yellow("\n📦 Configuration des dépendances..."));
|
|
153
|
-
|
|
154
|
-
// Vérifier si le projet cible est à l'intérieur du monorepo
|
|
155
|
-
const targetIsInMonorepo = fs.existsSync(path.join(targetDir, "../../../packages/core/package.json")) ||
|
|
156
|
-
fs.existsSync(path.join(targetDir, "../../packages/core/package.json"));
|
|
157
|
-
const latestVersion = targetIsInMonorepo ? "workspace:*" : "latest";
|
|
200
|
+
const versions = getLastBrainVersions(targetDir);
|
|
158
201
|
// Dependencies
|
|
159
202
|
const requiredDeps = {
|
|
160
203
|
next: "^15.0.3",
|
|
161
204
|
react: "^18.3.1",
|
|
162
205
|
"react-dom": "^18.3.1",
|
|
163
|
-
"@lastbrain/app":
|
|
164
|
-
"@lastbrain/core":
|
|
165
|
-
"@lastbrain/ui":
|
|
206
|
+
"@lastbrain/app": versions.app,
|
|
207
|
+
"@lastbrain/core": versions.core,
|
|
208
|
+
"@lastbrain/ui": versions.ui,
|
|
166
209
|
"next-themes": "^0.4.6",
|
|
167
210
|
};
|
|
168
211
|
// Ajouter module-auth si demandé
|
|
169
212
|
if (withAuth) {
|
|
170
|
-
requiredDeps["@lastbrain/module-auth"] =
|
|
213
|
+
requiredDeps["@lastbrain/module-auth"] = versions.moduleAuth;
|
|
171
214
|
}
|
|
172
215
|
// Ajouter les autres modules sélectionnés
|
|
173
216
|
for (const moduleName of selectedModules) {
|
|
174
217
|
const moduleInfo = AVAILABLE_MODULES.find(m => m.name === moduleName);
|
|
175
218
|
if (moduleInfo && moduleInfo.package !== "@lastbrain/module-auth") {
|
|
176
|
-
|
|
219
|
+
// Pour les autres modules, utiliser "latest" ou la version depuis le package
|
|
220
|
+
requiredDeps[moduleInfo.package] = versions.app; // Utiliser la même version que app
|
|
177
221
|
}
|
|
178
222
|
}
|
|
179
223
|
// Ajouter les dépendances HeroUI si nécessaire
|
|
@@ -213,6 +257,7 @@ async function addDependencies(targetDir, useHeroUI, withAuth, selectedModules =
|
|
|
213
257
|
requiredDeps["@heroui/switch"] = "^2.2.24";
|
|
214
258
|
requiredDeps["@heroui/table"] = "^2.2.27";
|
|
215
259
|
requiredDeps["@heroui/tabs"] = "^2.2.24";
|
|
260
|
+
requiredDeps["@heroui/system"] = "^2.4.23"; // Ajout pour HeroUIProvider
|
|
216
261
|
requiredDeps["@heroui/toast"] = "^2.0.17";
|
|
217
262
|
requiredDeps["@heroui/tooltip"] = "^2.2.24";
|
|
218
263
|
requiredDeps["@heroui/user"] = "^2.2.22";
|
|
@@ -868,21 +913,11 @@ async function addScriptsToPackageJson(targetDir) {
|
|
|
868
913
|
build: "next build",
|
|
869
914
|
start: "next start",
|
|
870
915
|
lint: "next lint",
|
|
871
|
-
lastbrain:
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
"
|
|
875
|
-
|
|
876
|
-
: "node node_modules/@lastbrain/app/dist/scripts/module-build.js",
|
|
877
|
-
"db:migrations:sync": targetIsInMonorepo
|
|
878
|
-
? "pnpm exec lastbrain db:migrations:sync"
|
|
879
|
-
: "node node_modules/@lastbrain/app/dist/scripts/db-migrations-sync.js",
|
|
880
|
-
"db:init": targetIsInMonorepo
|
|
881
|
-
? "pnpm exec lastbrain db:init"
|
|
882
|
-
: "node node_modules/@lastbrain/app/dist/scripts/db-init.js",
|
|
883
|
-
"readme:create": targetIsInMonorepo
|
|
884
|
-
? "pnpm exec lastbrain readme:create"
|
|
885
|
-
: "node node_modules/@lastbrain/app/dist/scripts/readme-build.js",
|
|
916
|
+
lastbrain: "node node_modules/@lastbrain/app/dist/cli.js",
|
|
917
|
+
"build:modules": "node node_modules/@lastbrain/app/dist/scripts/module-build.js",
|
|
918
|
+
"db:migrations:sync": "node node_modules/@lastbrain/app/dist/scripts/db-migrations-sync.js",
|
|
919
|
+
"db:init": "node node_modules/@lastbrain/app/dist/scripts/db-init.js",
|
|
920
|
+
"readme:create": "node node_modules/@lastbrain/app/dist/scripts/readme-build.js",
|
|
886
921
|
};
|
|
887
922
|
pkg.scripts = { ...pkg.scripts, ...scripts };
|
|
888
923
|
await fs.writeJson(pkgPath, pkg, { spaces: 2 });
|
|
@@ -915,3 +950,339 @@ async function saveModulesConfig(targetDir, selectedModules, withAuth) {
|
|
|
915
950
|
await fs.writeJson(modulesConfigPath, { modules }, { spaces: 2 });
|
|
916
951
|
console.log(chalk.green("✓ Configuration des modules sauvegardée"));
|
|
917
952
|
}
|
|
953
|
+
async function createStorageProxy(targetDir, force) {
|
|
954
|
+
console.log(chalk.yellow("\n🗂️ Création du système de proxy storage..."));
|
|
955
|
+
// Créer le dossier lib
|
|
956
|
+
const libDir = path.join(targetDir, "lib");
|
|
957
|
+
await fs.ensureDir(libDir);
|
|
958
|
+
// 1. Créer lib/bucket-config.ts
|
|
959
|
+
const bucketConfigPath = path.join(libDir, "bucket-config.ts");
|
|
960
|
+
if (!fs.existsSync(bucketConfigPath) || force) {
|
|
961
|
+
const bucketConfigContent = `/**
|
|
962
|
+
* Storage configuration for buckets and access control
|
|
963
|
+
*/
|
|
964
|
+
|
|
965
|
+
export interface BucketConfig {
|
|
966
|
+
name: string;
|
|
967
|
+
isPublic: boolean;
|
|
968
|
+
description: string;
|
|
969
|
+
allowedFileTypes?: string[];
|
|
970
|
+
maxFileSize?: number; // in bytes
|
|
971
|
+
customAccessControl?: (userId: string, filePath: string) => boolean;
|
|
972
|
+
}
|
|
973
|
+
|
|
974
|
+
export const BUCKET_CONFIGS: Record<string, BucketConfig> = {
|
|
975
|
+
avatar: {
|
|
976
|
+
name: "avatar",
|
|
977
|
+
isPublic: true,
|
|
978
|
+
description: "User profile pictures and avatars",
|
|
979
|
+
allowedFileTypes: ["image/jpeg", "image/png", "image/webp", "image/gif"],
|
|
980
|
+
maxFileSize: 10 * 1024 * 1024, // 10MB
|
|
981
|
+
},
|
|
982
|
+
app: {
|
|
983
|
+
name: "app",
|
|
984
|
+
isPublic: false,
|
|
985
|
+
description: "Private user files and documents",
|
|
986
|
+
maxFileSize: 100 * 1024 * 1024, // 100MB
|
|
987
|
+
customAccessControl: (userId: string, filePath: string) => {
|
|
988
|
+
// Users can only access files in their own folder (app/{userId}/...)
|
|
989
|
+
return filePath.startsWith(\`\${userId}/\`);
|
|
990
|
+
},
|
|
991
|
+
},
|
|
992
|
+
// Example for future buckets:
|
|
993
|
+
// public: {
|
|
994
|
+
// name: "public",
|
|
995
|
+
// isPublic: true,
|
|
996
|
+
// description: "Publicly accessible files like logos, banners",
|
|
997
|
+
// allowedFileTypes: ["image/jpeg", "image/png", "image/webp", "application/pdf"],
|
|
998
|
+
// maxFileSize: 50 * 1024 * 1024, // 50MB
|
|
999
|
+
// },
|
|
1000
|
+
// documents: {
|
|
1001
|
+
// name: "documents",
|
|
1002
|
+
// isPublic: false,
|
|
1003
|
+
// description: "Private documents requiring authentication",
|
|
1004
|
+
// allowedFileTypes: ["application/pdf", "application/msword", "text/plain"],
|
|
1005
|
+
// maxFileSize: 25 * 1024 * 1024, // 25MB
|
|
1006
|
+
// },
|
|
1007
|
+
};
|
|
1008
|
+
|
|
1009
|
+
/**
|
|
1010
|
+
* Get bucket configuration
|
|
1011
|
+
*/
|
|
1012
|
+
export function getBucketConfig(bucketName: string): BucketConfig | null {
|
|
1013
|
+
return BUCKET_CONFIGS[bucketName] || null;
|
|
1014
|
+
}
|
|
1015
|
+
|
|
1016
|
+
/**
|
|
1017
|
+
* Check if bucket is public
|
|
1018
|
+
*/
|
|
1019
|
+
export function isPublicBucket(bucketName: string): boolean {
|
|
1020
|
+
const config = getBucketConfig(bucketName);
|
|
1021
|
+
return config?.isPublic ?? false;
|
|
1022
|
+
}
|
|
1023
|
+
|
|
1024
|
+
/**
|
|
1025
|
+
* Check if user has access to a specific file
|
|
1026
|
+
*/
|
|
1027
|
+
export function hasFileAccess(bucketName: string, userId: string, filePath: string): boolean {
|
|
1028
|
+
const config = getBucketConfig(bucketName);
|
|
1029
|
+
if (!config) return false;
|
|
1030
|
+
|
|
1031
|
+
// Public buckets are accessible to everyone
|
|
1032
|
+
if (config.isPublic) return true;
|
|
1033
|
+
|
|
1034
|
+
// Private buckets require authentication
|
|
1035
|
+
if (!userId) return false;
|
|
1036
|
+
|
|
1037
|
+
// Apply custom access control if defined
|
|
1038
|
+
if (config.customAccessControl) {
|
|
1039
|
+
return config.customAccessControl(userId, filePath);
|
|
1040
|
+
}
|
|
1041
|
+
|
|
1042
|
+
return true;
|
|
1043
|
+
}
|
|
1044
|
+
|
|
1045
|
+
/**
|
|
1046
|
+
* Validate file type for bucket
|
|
1047
|
+
*/
|
|
1048
|
+
export function isValidFileType(bucketName: string, contentType: string): boolean {
|
|
1049
|
+
const config = getBucketConfig(bucketName);
|
|
1050
|
+
if (!config || !config.allowedFileTypes) return true;
|
|
1051
|
+
|
|
1052
|
+
return config.allowedFileTypes.includes(contentType);
|
|
1053
|
+
}
|
|
1054
|
+
|
|
1055
|
+
/**
|
|
1056
|
+
* Check if file size is within bucket limits
|
|
1057
|
+
*/
|
|
1058
|
+
export function isValidFileSize(bucketName: string, fileSize: number): boolean {
|
|
1059
|
+
const config = getBucketConfig(bucketName);
|
|
1060
|
+
if (!config || !config.maxFileSize) return true;
|
|
1061
|
+
|
|
1062
|
+
return fileSize <= config.maxFileSize;
|
|
1063
|
+
}`;
|
|
1064
|
+
await fs.writeFile(bucketConfigPath, bucketConfigContent);
|
|
1065
|
+
console.log(chalk.green("✓ lib/bucket-config.ts créé"));
|
|
1066
|
+
}
|
|
1067
|
+
// 2. Créer lib/storage.ts
|
|
1068
|
+
const storagePath = path.join(libDir, "storage.ts");
|
|
1069
|
+
if (!fs.existsSync(storagePath) || force) {
|
|
1070
|
+
const storageContent = `/**
|
|
1071
|
+
* Build storage proxy URL for files in Supabase buckets
|
|
1072
|
+
*
|
|
1073
|
+
* @param bucket - The bucket name (e.g., "avatar", "app")
|
|
1074
|
+
* @param path - The file path within the bucket
|
|
1075
|
+
* @returns Proxied URL (e.g., "/api/storage/avatar/user_128_123456.webp")
|
|
1076
|
+
*/
|
|
1077
|
+
export function buildStorageUrl(bucket: string, path: string): string {
|
|
1078
|
+
// Remove leading slash if present
|
|
1079
|
+
const cleanPath = path.startsWith("/") ? path.slice(1) : path;
|
|
1080
|
+
|
|
1081
|
+
// Remove bucket prefix from path if present (e.g., "avatar/file.jpg" -> "file.jpg")
|
|
1082
|
+
const pathWithoutBucket = cleanPath.startsWith(bucket + "/")
|
|
1083
|
+
? cleanPath.slice(bucket.length + 1)
|
|
1084
|
+
: cleanPath;
|
|
1085
|
+
|
|
1086
|
+
return \`/api/storage/\${bucket}/\${pathWithoutBucket}\`;
|
|
1087
|
+
}
|
|
1088
|
+
|
|
1089
|
+
/**
|
|
1090
|
+
* Extract bucket and path from a storage URL
|
|
1091
|
+
*
|
|
1092
|
+
* @param url - Storage URL (can be proxied URL or Supabase public URL)
|
|
1093
|
+
* @returns Object with bucket and path, or null if not a valid storage URL
|
|
1094
|
+
*/
|
|
1095
|
+
export function parseStorageUrl(url: string): { bucket: string; path: string } | null {
|
|
1096
|
+
// Handle proxy URLs like "/api/storage/avatar/file.jpg"
|
|
1097
|
+
const proxyMatch = url.match(/^\\/api\\/storage\\/([^\\/]+)\\/(.+)$/);
|
|
1098
|
+
if (proxyMatch) {
|
|
1099
|
+
return {
|
|
1100
|
+
bucket: proxyMatch[1],
|
|
1101
|
+
path: proxyMatch[2]
|
|
1102
|
+
};
|
|
1103
|
+
}
|
|
1104
|
+
|
|
1105
|
+
// Handle Supabase public URLs
|
|
1106
|
+
const supabaseMatch = url.match(/\\/storage\\/v1\\/object\\/public\\/([^\\/]+)\\/(.+)$/);
|
|
1107
|
+
if (supabaseMatch) {
|
|
1108
|
+
return {
|
|
1109
|
+
bucket: supabaseMatch[1],
|
|
1110
|
+
path: supabaseMatch[2]
|
|
1111
|
+
};
|
|
1112
|
+
}
|
|
1113
|
+
|
|
1114
|
+
return null;
|
|
1115
|
+
}
|
|
1116
|
+
|
|
1117
|
+
/**
|
|
1118
|
+
* Convert a Supabase storage path to proxy URL
|
|
1119
|
+
*
|
|
1120
|
+
* @param storagePath - Path like "avatar/file.jpg" or "app/user/file.pdf"
|
|
1121
|
+
* @returns Proxied URL
|
|
1122
|
+
*/
|
|
1123
|
+
export function storagePathToProxyUrl(storagePath: string): string {
|
|
1124
|
+
const parts = storagePath.split("/");
|
|
1125
|
+
if (parts.length < 2) {
|
|
1126
|
+
throw new Error("Invalid storage path format");
|
|
1127
|
+
}
|
|
1128
|
+
|
|
1129
|
+
const bucket = parts[0];
|
|
1130
|
+
const path = parts.slice(1).join("/");
|
|
1131
|
+
|
|
1132
|
+
return buildStorageUrl(bucket, path);
|
|
1133
|
+
}
|
|
1134
|
+
|
|
1135
|
+
/**
|
|
1136
|
+
* List of public buckets that don't require authentication
|
|
1137
|
+
*/
|
|
1138
|
+
export const PUBLIC_BUCKETS = ["avatar"];
|
|
1139
|
+
|
|
1140
|
+
/**
|
|
1141
|
+
* List of private buckets that require authentication
|
|
1142
|
+
*/
|
|
1143
|
+
export const PRIVATE_BUCKETS = ["app"];
|
|
1144
|
+
|
|
1145
|
+
/**
|
|
1146
|
+
* Check if a bucket is public
|
|
1147
|
+
*/
|
|
1148
|
+
export function isPublicBucket(bucket: string): boolean {
|
|
1149
|
+
return PUBLIC_BUCKETS.includes(bucket);
|
|
1150
|
+
}
|
|
1151
|
+
|
|
1152
|
+
/**
|
|
1153
|
+
* Check if a bucket is private
|
|
1154
|
+
*/
|
|
1155
|
+
export function isPrivateBucket(bucket: string): boolean {
|
|
1156
|
+
return PRIVATE_BUCKETS.includes(bucket);
|
|
1157
|
+
}`;
|
|
1158
|
+
await fs.writeFile(storagePath, storageContent);
|
|
1159
|
+
console.log(chalk.green("✓ lib/storage.ts créé"));
|
|
1160
|
+
}
|
|
1161
|
+
// 3. Créer app/api/storage/[bucket]/[...path]/route.ts
|
|
1162
|
+
const apiStorageDir = path.join(targetDir, "app", "api", "storage", "[bucket]", "[...path]");
|
|
1163
|
+
await fs.ensureDir(apiStorageDir);
|
|
1164
|
+
const routePath = path.join(apiStorageDir, "route.ts");
|
|
1165
|
+
if (!fs.existsSync(routePath) || force) {
|
|
1166
|
+
const routeContent = `import { NextRequest, NextResponse } from "next/server";
|
|
1167
|
+
import { getSupabaseServerClient } from "@lastbrain/core/server";
|
|
1168
|
+
import { getBucketConfig, hasFileAccess } from "@/lib/bucket-config";
|
|
1169
|
+
|
|
1170
|
+
/**
|
|
1171
|
+
* GET /api/storage/[bucket]/[...path]
|
|
1172
|
+
* Proxy for Supabase Storage files with clean URLs and access control
|
|
1173
|
+
*
|
|
1174
|
+
* Examples:
|
|
1175
|
+
* - /api/storage/avatar/user_128_123456.webp
|
|
1176
|
+
* - /api/storage/app/user/documents/file.pdf
|
|
1177
|
+
*/
|
|
1178
|
+
export async function GET(
|
|
1179
|
+
request: NextRequest,
|
|
1180
|
+
props: { params: Promise<{ bucket: string; path: string[] }> }
|
|
1181
|
+
) {
|
|
1182
|
+
try {
|
|
1183
|
+
const { bucket, path } = await props.params;
|
|
1184
|
+
const filePath = path.join("/");
|
|
1185
|
+
|
|
1186
|
+
// Check if bucket exists in our configuration
|
|
1187
|
+
const bucketConfig = getBucketConfig(bucket);
|
|
1188
|
+
if (!bucketConfig) {
|
|
1189
|
+
return new NextResponse("Bucket not allowed", { status: 403 });
|
|
1190
|
+
}
|
|
1191
|
+
|
|
1192
|
+
const supabase = await getSupabaseServerClient();
|
|
1193
|
+
let userId: string | null = null;
|
|
1194
|
+
|
|
1195
|
+
// Get user for private buckets or custom access control
|
|
1196
|
+
if (!bucketConfig.isPublic) {
|
|
1197
|
+
const { data: { user }, error: authError } = await supabase.auth.getUser();
|
|
1198
|
+
|
|
1199
|
+
if (authError || !user) {
|
|
1200
|
+
return new NextResponse("Unauthorized", { status: 401 });
|
|
1201
|
+
}
|
|
1202
|
+
|
|
1203
|
+
userId = user.id;
|
|
1204
|
+
}
|
|
1205
|
+
|
|
1206
|
+
// Check file access permissions
|
|
1207
|
+
if (!hasFileAccess(bucket, userId || "", filePath)) {
|
|
1208
|
+
return new NextResponse("Forbidden - Access denied to this file", { status: 403 });
|
|
1209
|
+
}
|
|
1210
|
+
|
|
1211
|
+
// Get file from Supabase Storage
|
|
1212
|
+
const { data: file, error } = await supabase.storage
|
|
1213
|
+
.from(bucket)
|
|
1214
|
+
.download(filePath);
|
|
1215
|
+
|
|
1216
|
+
if (error) {
|
|
1217
|
+
console.error("Storage download error:", error);
|
|
1218
|
+
if (error.message.includes("not found")) {
|
|
1219
|
+
return new NextResponse("File not found", { status: 404 });
|
|
1220
|
+
}
|
|
1221
|
+
return new NextResponse("Storage error", { status: 500 });
|
|
1222
|
+
}
|
|
1223
|
+
|
|
1224
|
+
if (!file) {
|
|
1225
|
+
return new NextResponse("File not found", { status: 404 });
|
|
1226
|
+
}
|
|
1227
|
+
|
|
1228
|
+
// Convert blob to array buffer
|
|
1229
|
+
const arrayBuffer = await file.arrayBuffer();
|
|
1230
|
+
|
|
1231
|
+
// Determine content type from file extension
|
|
1232
|
+
const getContentType = (filename: string): string => {
|
|
1233
|
+
const ext = filename.toLowerCase().split(".").pop();
|
|
1234
|
+
const mimeTypes: Record<string, string> = {
|
|
1235
|
+
// Images
|
|
1236
|
+
jpg: "image/jpeg",
|
|
1237
|
+
jpeg: "image/jpeg",
|
|
1238
|
+
png: "image/png",
|
|
1239
|
+
gif: "image/gif",
|
|
1240
|
+
webp: "image/webp",
|
|
1241
|
+
svg: "image/svg+xml",
|
|
1242
|
+
// Documents
|
|
1243
|
+
pdf: "application/pdf",
|
|
1244
|
+
doc: "application/msword",
|
|
1245
|
+
docx: "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
|
|
1246
|
+
xls: "application/vnd.ms-excel",
|
|
1247
|
+
xlsx: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
|
|
1248
|
+
// Text
|
|
1249
|
+
txt: "text/plain",
|
|
1250
|
+
csv: "text/csv",
|
|
1251
|
+
// Videos
|
|
1252
|
+
mp4: "video/mp4",
|
|
1253
|
+
avi: "video/x-msvideo",
|
|
1254
|
+
mov: "video/quicktime",
|
|
1255
|
+
// Audio
|
|
1256
|
+
mp3: "audio/mpeg",
|
|
1257
|
+
wav: "audio/wav",
|
|
1258
|
+
// Archives
|
|
1259
|
+
zip: "application/zip",
|
|
1260
|
+
rar: "application/x-rar-compressed",
|
|
1261
|
+
};
|
|
1262
|
+
return mimeTypes[ext || ""] || "application/octet-stream";
|
|
1263
|
+
};
|
|
1264
|
+
|
|
1265
|
+
const contentType = getContentType(filePath);
|
|
1266
|
+
|
|
1267
|
+
// Create response with proper headers
|
|
1268
|
+
const response = new NextResponse(arrayBuffer, {
|
|
1269
|
+
status: 200,
|
|
1270
|
+
headers: {
|
|
1271
|
+
"Content-Type": contentType,
|
|
1272
|
+
"Cache-Control": "public, max-age=31536000, immutable", // Cache for 1 year
|
|
1273
|
+
"Content-Length": arrayBuffer.byteLength.toString(),
|
|
1274
|
+
},
|
|
1275
|
+
});
|
|
1276
|
+
|
|
1277
|
+
return response;
|
|
1278
|
+
|
|
1279
|
+
} catch (error) {
|
|
1280
|
+
console.error("Storage proxy error:", error);
|
|
1281
|
+
return new NextResponse("Internal server error", { status: 500 });
|
|
1282
|
+
}
|
|
1283
|
+
}`;
|
|
1284
|
+
await fs.writeFile(routePath, routeContent);
|
|
1285
|
+
console.log(chalk.green("✓ app/api/storage/[bucket]/[...path]/route.ts créé"));
|
|
1286
|
+
}
|
|
1287
|
+
console.log(chalk.green("✓ Système de proxy storage configuré"));
|
|
1288
|
+
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"module-create.d.ts","sourceRoot":"","sources":["../../src/scripts/module-create.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"module-create.d.ts","sourceRoot":"","sources":["../../src/scripts/module-create.ts"],"names":[],"mappings":"AAirBA;;GAEG;AACH,wBAAsB,YAAY,kBAsIjC"}
|
|
@@ -26,10 +26,31 @@ function parseTablesList(input) {
|
|
|
26
26
|
.map((t) => t.trim())
|
|
27
27
|
.filter((t) => t.length > 0);
|
|
28
28
|
}
|
|
29
|
+
/**
|
|
30
|
+
* Récupère les versions actuelles des packages LastBrain
|
|
31
|
+
*/
|
|
32
|
+
function getLastBrainPackageVersions(rootDir) {
|
|
33
|
+
try {
|
|
34
|
+
const corePackageJson = JSON.parse(fs.readFileSync(path.join(rootDir, "packages", "core", "package.json"), "utf-8"));
|
|
35
|
+
const uiPackageJson = JSON.parse(fs.readFileSync(path.join(rootDir, "packages", "ui", "package.json"), "utf-8"));
|
|
36
|
+
return {
|
|
37
|
+
core: `^${corePackageJson.version}`,
|
|
38
|
+
ui: `^${uiPackageJson.version}`,
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
catch (error) {
|
|
42
|
+
console.warn(chalk.yellow("⚠️ Impossible de lire les versions des packages, utilisation des versions par défaut"));
|
|
43
|
+
return {
|
|
44
|
+
core: "^0.1.0",
|
|
45
|
+
ui: "^0.1.4",
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
}
|
|
29
49
|
/**
|
|
30
50
|
* Génère le contenu du package.json
|
|
31
51
|
*/
|
|
32
|
-
function generatePackageJson(moduleName, slug) {
|
|
52
|
+
function generatePackageJson(moduleName, slug, rootDir) {
|
|
53
|
+
const versions = getLastBrainPackageVersions(rootDir);
|
|
33
54
|
const buildConfigExport = `./${slug}.build.config`;
|
|
34
55
|
return JSON.stringify({
|
|
35
56
|
name: moduleName,
|
|
@@ -38,14 +59,14 @@ function generatePackageJson(moduleName, slug) {
|
|
|
38
59
|
type: "module",
|
|
39
60
|
main: "dist/index.js",
|
|
40
61
|
types: "dist/index.d.ts",
|
|
41
|
-
files: ["dist", "supabase"],
|
|
62
|
+
files: ["dist", "src", "supabase"],
|
|
42
63
|
scripts: {
|
|
43
64
|
build: "tsc -p tsconfig.json",
|
|
44
65
|
dev: "tsc -p tsconfig.json --watch",
|
|
45
66
|
},
|
|
46
67
|
dependencies: {
|
|
47
|
-
"@lastbrain/core":
|
|
48
|
-
"@lastbrain/ui":
|
|
68
|
+
"@lastbrain/core": versions.core,
|
|
69
|
+
"@lastbrain/ui": versions.ui,
|
|
49
70
|
react: "^19.0.0",
|
|
50
71
|
"lucide-react": "^0.554.0",
|
|
51
72
|
"react-dom": "^19.0.0",
|
|
@@ -503,7 +524,7 @@ async function createModuleStructure(config, rootDir) {
|
|
|
503
524
|
await fs.ensureDir(path.join(moduleDir, "supabase", "migrations"));
|
|
504
525
|
// Créer package.json
|
|
505
526
|
console.log(chalk.yellow(" 📄 package.json"));
|
|
506
|
-
await fs.writeFile(path.join(moduleDir, "package.json"), generatePackageJson(config.moduleName, config.slug));
|
|
527
|
+
await fs.writeFile(path.join(moduleDir, "package.json"), generatePackageJson(config.moduleName, config.slug, rootDir));
|
|
507
528
|
// Créer tsconfig.json
|
|
508
529
|
console.log(chalk.yellow(" 📄 tsconfig.json"));
|
|
509
530
|
await fs.writeFile(path.join(moduleDir, "tsconfig.json"), generateTsConfig());
|