@gallop.software/studio 0.1.81 → 0.1.82

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.
@@ -1,80 +1,212 @@
1
1
  "use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } function _optionalChain(ops) { let lastAccessLHS = undefined; let value = ops[0]; let i = 1; while (i < ops.length) { const op = ops[i]; const fn = ops[i + 1]; i += 2; if ((op === 'optionalAccess' || op === 'optionalCall') && value == null) { return undefined; } if (op === 'access' || op === 'optionalAccess') { lastAccessLHS = value; value = fn(value); } else if (op === 'call' || op === 'optionalCall') { value = fn((...args) => value.call(lastAccessLHS, ...args)); lastAccessLHS = undefined; } } return value; }
2
2
 
3
- var _chunkCN5NRNWBjs = require('./chunk-CN5NRNWB.js');
3
+ var _chunkCN5NRNWBjs = require('../chunk-CN5NRNWB.js');
4
4
 
5
- // src/handlers.ts
5
+ // src/handlers/index.ts
6
6
  var _server = require('next/server');
7
+
8
+ // src/handlers/list.ts
9
+
7
10
  var _fs = require('fs');
8
11
  var _path = require('path'); var _path2 = _interopRequireDefault(_path);
9
12
  var _sharp = require('sharp'); var _sharp2 = _interopRequireDefault(_sharp);
13
+
14
+ // src/handlers/utils/meta.ts
15
+
16
+
17
+ async function loadMeta() {
18
+ const metaPath = _path2.default.join(process.cwd(), "_data", "_meta.json");
19
+ try {
20
+ const content = await _fs.promises.readFile(metaPath, "utf-8");
21
+ return JSON.parse(content);
22
+ } catch (e) {
23
+ return {};
24
+ }
25
+ }
26
+ async function saveMeta(meta) {
27
+ const dataDir = _path2.default.join(process.cwd(), "_data");
28
+ await _fs.promises.mkdir(dataDir, { recursive: true });
29
+ const metaPath = _path2.default.join(dataDir, "_meta.json");
30
+ await _fs.promises.writeFile(metaPath, JSON.stringify(meta, null, 2));
31
+ }
32
+
33
+ // src/handlers/utils/files.ts
34
+
35
+
36
+ function isImageFile(filename) {
37
+ const ext = _path2.default.extname(filename).toLowerCase();
38
+ return [".jpg", ".jpeg", ".png", ".gif", ".webp", ".svg", ".ico", ".bmp", ".tiff", ".tif"].includes(ext);
39
+ }
40
+ function isMediaFile(filename) {
41
+ const ext = _path2.default.extname(filename).toLowerCase();
42
+ if ([".jpg", ".jpeg", ".png", ".gif", ".webp", ".svg", ".ico", ".bmp", ".tiff", ".tif"].includes(ext)) return true;
43
+ if ([".mp4", ".webm", ".mov", ".avi", ".mkv", ".m4v"].includes(ext)) return true;
44
+ if ([".mp3", ".wav", ".ogg", ".m4a", ".flac", ".aac"].includes(ext)) return true;
45
+ if ([".pdf"].includes(ext)) return true;
46
+ return false;
47
+ }
48
+ function getContentType(filePath) {
49
+ const ext = _path2.default.extname(filePath).toLowerCase();
50
+ switch (ext) {
51
+ case ".jpg":
52
+ case ".jpeg":
53
+ return "image/jpeg";
54
+ case ".png":
55
+ return "image/png";
56
+ case ".gif":
57
+ return "image/gif";
58
+ case ".webp":
59
+ return "image/webp";
60
+ case ".svg":
61
+ return "image/svg+xml";
62
+ default:
63
+ return "application/octet-stream";
64
+ }
65
+ }
66
+ async function getFolderStats(folderPath) {
67
+ let fileCount = 0;
68
+ let totalSize = 0;
69
+ async function scanFolder(dir) {
70
+ try {
71
+ const entries = await _fs.promises.readdir(dir, { withFileTypes: true });
72
+ for (const entry of entries) {
73
+ if (entry.name.startsWith(".")) continue;
74
+ const fullPath = _path2.default.join(dir, entry.name);
75
+ if (entry.isDirectory()) {
76
+ await scanFolder(fullPath);
77
+ } else if (isMediaFile(entry.name)) {
78
+ fileCount++;
79
+ const stats = await _fs.promises.stat(fullPath);
80
+ totalSize += stats.size;
81
+ }
82
+ }
83
+ } catch (e2) {
84
+ }
85
+ }
86
+ await scanFolder(folderPath);
87
+ return { fileCount, totalSize };
88
+ }
89
+
90
+ // src/handlers/utils/thumbnails.ts
91
+
92
+
93
+
10
94
  var _blurhash = require('blurhash');
11
- var _clients3 = require('@aws-sdk/client-s3');
12
95
  var DEFAULT_SIZES = {
13
96
  small: { width: 300, suffix: "-sm" },
14
97
  medium: { width: 700, suffix: "-md" },
15
98
  large: { width: 1400, suffix: "-lg" }
16
99
  };
17
- async function GET(request) {
18
- if (process.env.NODE_ENV !== "development") {
19
- return _server.NextResponse.json({ error: "Not available in production" }, { status: 403 });
20
- }
21
- const pathname = request.nextUrl.pathname;
22
- const route = pathname.replace(/^\/api\/studio\/?/, "");
23
- if (route === "list-folders") {
24
- return handleListFolders();
25
- }
26
- if (route === "list" || route.startsWith("list")) {
27
- return handleList(request);
28
- }
29
- if (route === "count-images") {
30
- return handleCountImages();
31
- }
32
- if (route === "folder-images") {
33
- return handleFolderImages(request);
100
+ async function processImage(buffer, imageKey) {
101
+ const sharpInstance = _sharp2.default.call(void 0, buffer);
102
+ const metadata = await sharpInstance.metadata();
103
+ const originalWidth = metadata.width || 0;
104
+ const originalHeight = metadata.height || 0;
105
+ const keyWithoutSlash = imageKey.startsWith("/") ? imageKey.slice(1) : imageKey;
106
+ const baseName = _path2.default.basename(keyWithoutSlash, _path2.default.extname(keyWithoutSlash));
107
+ const ext = _path2.default.extname(keyWithoutSlash).toLowerCase();
108
+ const imageDir = _path2.default.dirname(keyWithoutSlash);
109
+ const imagesPath = _path2.default.join(process.cwd(), "public", "images", imageDir === "." ? "" : imageDir);
110
+ await _fs.promises.mkdir(imagesPath, { recursive: true });
111
+ const isPng = ext === ".png";
112
+ const outputExt = isPng ? ".png" : ".jpg";
113
+ const fullFileName = imageDir === "." ? `${baseName}${outputExt}` : `${imageDir}/${baseName}${outputExt}`;
114
+ const fullPath = _path2.default.join(process.cwd(), "public", "images", fullFileName);
115
+ if (isPng) {
116
+ await _sharp2.default.call(void 0, buffer).png({ quality: 85 }).toFile(fullPath);
117
+ } else {
118
+ await _sharp2.default.call(void 0, buffer).jpeg({ quality: 85 }).toFile(fullPath);
34
119
  }
35
- if (route === "search") {
36
- return handleSearch(request);
120
+ for (const [, sizeConfig] of Object.entries(DEFAULT_SIZES)) {
121
+ const { width: maxWidth, suffix } = sizeConfig;
122
+ if (originalWidth <= maxWidth) {
123
+ continue;
124
+ }
125
+ const ratio = originalHeight / originalWidth;
126
+ const newHeight = Math.round(maxWidth * ratio);
127
+ const sizeFileName = `${baseName}${suffix}${outputExt}`;
128
+ const sizeFilePath = imageDir === "." ? sizeFileName : `${imageDir}/${sizeFileName}`;
129
+ const sizePath = _path2.default.join(process.cwd(), "public", "images", sizeFilePath);
130
+ if (isPng) {
131
+ await _sharp2.default.call(void 0, buffer).resize(maxWidth, newHeight).png({ quality: 80 }).toFile(sizePath);
132
+ } else {
133
+ await _sharp2.default.call(void 0, buffer).resize(maxWidth, newHeight).jpeg({ quality: 80 }).toFile(sizePath);
134
+ }
37
135
  }
38
- return _server.NextResponse.json({ error: "Not found" }, { status: 404 });
136
+ const { data, info } = await _sharp2.default.call(void 0, buffer).resize(32, 32, { fit: "inside" }).ensureAlpha().raw().toBuffer({ resolveWithObject: true });
137
+ const blurhash = _blurhash.encode.call(void 0, new Uint8ClampedArray(data), info.width, info.height, 4, 4);
138
+ return {
139
+ w: originalWidth,
140
+ h: originalHeight,
141
+ blur: blurhash
142
+ };
39
143
  }
40
- async function POST(request) {
41
- if (process.env.NODE_ENV !== "development") {
42
- return _server.NextResponse.json({ error: "Not available in production" }, { status: 403 });
43
- }
44
- const pathname = request.nextUrl.pathname;
45
- const route = pathname.replace(/^\/api\/studio\/?/, "");
46
- if (route === "upload") {
47
- return handleUpload(request);
48
- }
49
- if (route === "delete") {
50
- return handleDelete(request);
51
- }
52
- if (route === "sync") {
53
- return handleSync(request);
54
- }
55
- if (route === "reprocess") {
56
- return handleReprocess(request);
57
- }
58
- if (route === "process-all") {
59
- return handleProcessAllStream();
60
- }
61
- if (route === "create-folder") {
62
- return handleCreateFolder(request);
144
+
145
+ // src/handlers/utils/cdn.ts
146
+
147
+
148
+ var _clients3 = require('@aws-sdk/client-s3');
149
+ function getR2Client() {
150
+ const accountId = process.env.CLOUDFLARE_R2_ACCOUNT_ID;
151
+ const accessKeyId = process.env.CLOUDFLARE_R2_ACCESS_KEY_ID;
152
+ const secretAccessKey = process.env.CLOUDFLARE_R2_SECRET_ACCESS_KEY;
153
+ if (!accountId || !accessKeyId || !secretAccessKey) {
154
+ throw new Error("R2 not configured");
63
155
  }
64
- if (route === "rename") {
65
- return handleRename(request);
156
+ return new (0, _clients3.S3Client)({
157
+ region: "auto",
158
+ endpoint: `https://${accountId}.r2.cloudflarestorage.com`,
159
+ credentials: { accessKeyId, secretAccessKey }
160
+ });
161
+ }
162
+ async function downloadFromCdn(originalPath) {
163
+ const bucketName = process.env.CLOUDFLARE_R2_BUCKET_NAME;
164
+ if (!bucketName) throw new Error("R2 bucket not configured");
165
+ const r2 = getR2Client();
166
+ const response = await r2.send(
167
+ new (0, _clients3.GetObjectCommand)({
168
+ Bucket: bucketName,
169
+ Key: originalPath.replace(/^\//, "")
170
+ })
171
+ );
172
+ const stream = response.Body;
173
+ const chunks = [];
174
+ for await (const chunk of stream) {
175
+ chunks.push(Buffer.from(chunk));
66
176
  }
67
- if (route === "move") {
68
- return handleMove(request);
177
+ return Buffer.concat(chunks);
178
+ }
179
+ async function uploadToCdn(imageKey) {
180
+ const bucketName = process.env.CLOUDFLARE_R2_BUCKET_NAME;
181
+ if (!bucketName) throw new Error("R2 bucket not configured");
182
+ const r2 = getR2Client();
183
+ for (const thumbPath of _chunkCN5NRNWBjs.getAllThumbnailPaths.call(void 0, imageKey)) {
184
+ const localPath = _path2.default.join(process.cwd(), "public", thumbPath);
185
+ try {
186
+ const fileBuffer = await _fs.promises.readFile(localPath);
187
+ await r2.send(
188
+ new (0, _clients3.PutObjectCommand)({
189
+ Bucket: bucketName,
190
+ Key: thumbPath.replace(/^\//, ""),
191
+ Body: fileBuffer,
192
+ ContentType: getContentType(thumbPath)
193
+ })
194
+ );
195
+ } catch (e3) {
196
+ }
69
197
  }
70
- return _server.NextResponse.json({ error: "Not found" }, { status: 404 });
71
198
  }
72
- async function DELETE(request) {
73
- if (process.env.NODE_ENV !== "development") {
74
- return _server.NextResponse.json({ error: "Not available in production" }, { status: 403 });
199
+ async function deleteLocalThumbnails(imageKey) {
200
+ for (const thumbPath of _chunkCN5NRNWBjs.getAllThumbnailPaths.call(void 0, imageKey)) {
201
+ const localPath = _path2.default.join(process.cwd(), "public", thumbPath);
202
+ try {
203
+ await _fs.promises.unlink(localPath);
204
+ } catch (e4) {
205
+ }
75
206
  }
76
- return handleDelete(request);
77
207
  }
208
+
209
+ // src/handlers/list.ts
78
210
  async function handleList(request) {
79
211
  const searchParams = request.nextUrl.searchParams;
80
212
  const requestedPath = searchParams.get("path") || "public";
@@ -120,7 +252,7 @@ async function handleList(request) {
120
252
  await _fs.promises.access(thumbnailPath);
121
253
  thumbnail = `/${thumbnailDir}/${thumbnailName}`;
122
254
  hasThumbnail = true;
123
- } catch (e) {
255
+ } catch (e5) {
124
256
  thumbnail = itemPath.replace("public", "");
125
257
  hasThumbnail = false;
126
258
  }
@@ -131,7 +263,7 @@ async function handleList(request) {
131
263
  if (metadata.width && metadata.height) {
132
264
  dimensions = { width: metadata.width, height: metadata.height };
133
265
  }
134
- } catch (e2) {
266
+ } catch (e6) {
135
267
  }
136
268
  }
137
269
  }
@@ -186,7 +318,7 @@ async function handleSearch(request) {
186
318
  await _fs.promises.access(thumbnailPath);
187
319
  thumbnail = `/${thumbnailDir}/${thumbnailName}`;
188
320
  hasThumbnail = true;
189
- } catch (e3) {
321
+ } catch (e7) {
190
322
  thumbnail = `/${itemRelPath}`;
191
323
  hasThumbnail = false;
192
324
  }
@@ -196,7 +328,7 @@ async function handleSearch(request) {
196
328
  if (metadata.width && metadata.height) {
197
329
  dimensions = { width: metadata.width, height: metadata.height };
198
330
  }
199
- } catch (e4) {
331
+ } catch (e8) {
200
332
  }
201
333
  }
202
334
  items.push({
@@ -211,7 +343,7 @@ async function handleSearch(request) {
211
343
  }
212
344
  }
213
345
  }
214
- } catch (e5) {
346
+ } catch (e9) {
215
347
  }
216
348
  }
217
349
  await searchDir(publicDir, "");
@@ -221,29 +353,113 @@ async function handleSearch(request) {
221
353
  return _server.NextResponse.json({ error: "Failed to search" }, { status: 500 });
222
354
  }
223
355
  }
224
- async function getFolderStats(folderPath) {
225
- let fileCount = 0;
226
- let totalSize = 0;
227
- async function scanFolder(dir) {
228
- try {
229
- const entries = await _fs.promises.readdir(dir, { withFileTypes: true });
230
- for (const entry of entries) {
231
- if (entry.name.startsWith(".")) continue;
232
- const fullPath = _path2.default.join(dir, entry.name);
233
- if (entry.isDirectory()) {
234
- await scanFolder(fullPath);
235
- } else if (isMediaFile(entry.name)) {
236
- fileCount++;
237
- const stats = await _fs.promises.stat(fullPath);
238
- totalSize += stats.size;
356
+ async function handleListFolders() {
357
+ try {
358
+ const publicDir = _path2.default.join(process.cwd(), "public");
359
+ const folders = [];
360
+ async function scanDir(dir, relativePath, depth) {
361
+ try {
362
+ const entries = await _fs.promises.readdir(dir, { withFileTypes: true });
363
+ for (const entry of entries) {
364
+ if (!entry.isDirectory()) continue;
365
+ if (entry.name.startsWith(".")) continue;
366
+ const folderRelativePath = relativePath ? `${relativePath}/${entry.name}` : entry.name;
367
+ folders.push({
368
+ path: `public/${folderRelativePath}`,
369
+ name: entry.name,
370
+ depth
371
+ });
372
+ await scanDir(_path2.default.join(dir, entry.name), folderRelativePath, depth + 1);
239
373
  }
374
+ } catch (e10) {
240
375
  }
241
- } catch (e6) {
242
376
  }
243
- }
244
- await scanFolder(folderPath);
245
- return { fileCount, totalSize };
377
+ folders.push({ path: "public", name: "public", depth: 0 });
378
+ await scanDir(publicDir, "", 1);
379
+ return _server.NextResponse.json({ folders });
380
+ } catch (error) {
381
+ console.error("Failed to list folders:", error);
382
+ return _server.NextResponse.json({ error: "Failed to list folders" }, { status: 500 });
383
+ }
384
+ }
385
+ async function handleCountImages() {
386
+ try {
387
+ const allImages = [];
388
+ async function scanPublicFolder(dir, relativePath = "") {
389
+ try {
390
+ const entries = await _fs.promises.readdir(dir, { withFileTypes: true });
391
+ for (const entry of entries) {
392
+ if (entry.name.startsWith(".")) continue;
393
+ const fullPath = _path2.default.join(dir, entry.name);
394
+ const relPath = relativePath ? `${relativePath}/${entry.name}` : entry.name;
395
+ if (relPath === "images" || relPath.startsWith("images/")) continue;
396
+ if (entry.isDirectory()) {
397
+ await scanPublicFolder(fullPath, relPath);
398
+ } else if (isImageFile(entry.name)) {
399
+ allImages.push(relPath);
400
+ }
401
+ }
402
+ } catch (e11) {
403
+ }
404
+ }
405
+ const publicDir = _path2.default.join(process.cwd(), "public");
406
+ await scanPublicFolder(publicDir);
407
+ return _server.NextResponse.json({
408
+ count: allImages.length,
409
+ images: allImages
410
+ });
411
+ } catch (error) {
412
+ console.error("Failed to count images:", error);
413
+ return _server.NextResponse.json({ error: "Failed to count images" }, { status: 500 });
414
+ }
415
+ }
416
+ async function handleFolderImages(request) {
417
+ try {
418
+ const searchParams = request.nextUrl.searchParams;
419
+ const foldersParam = searchParams.get("folders");
420
+ if (!foldersParam) {
421
+ return _server.NextResponse.json({ error: "No folders provided" }, { status: 400 });
422
+ }
423
+ const folders = foldersParam.split(",");
424
+ const allImages = [];
425
+ async function scanFolder(dir, relativePath = "") {
426
+ try {
427
+ const entries = await _fs.promises.readdir(dir, { withFileTypes: true });
428
+ for (const entry of entries) {
429
+ if (entry.name.startsWith(".")) continue;
430
+ const fullPath = _path2.default.join(dir, entry.name);
431
+ const relPath = relativePath ? `${relativePath}/${entry.name}` : entry.name;
432
+ if (entry.isDirectory()) {
433
+ await scanFolder(fullPath, relPath);
434
+ } else if (isImageFile(entry.name)) {
435
+ allImages.push(relPath);
436
+ }
437
+ }
438
+ } catch (e12) {
439
+ }
440
+ }
441
+ for (const folder of folders) {
442
+ const relativePath = folder.replace(/^public\/?/, "");
443
+ if (relativePath === "images" || relativePath.startsWith("images/")) continue;
444
+ const folderPath = _path2.default.join(process.cwd(), folder);
445
+ await scanFolder(folderPath, relativePath);
446
+ }
447
+ return _server.NextResponse.json({
448
+ count: allImages.length,
449
+ images: allImages
450
+ });
451
+ } catch (error) {
452
+ console.error("Failed to get folder images:", error);
453
+ return _server.NextResponse.json({ error: "Failed to get folder images" }, { status: 500 });
454
+ }
246
455
  }
456
+
457
+ // src/handlers/files.ts
458
+
459
+
460
+
461
+
462
+
247
463
  async function handleUpload(request) {
248
464
  try {
249
465
  const formData = await request.formData();
@@ -379,7 +595,7 @@ async function handleDelete(request) {
379
595
  const absoluteThumbPath = _path2.default.join(process.cwd(), "public", thumbPath);
380
596
  try {
381
597
  await _fs.promises.unlink(absoluteThumbPath);
382
- } catch (e7) {
598
+ } catch (e13) {
383
599
  }
384
600
  }
385
601
  delete meta[imageKey];
@@ -403,198 +619,313 @@ async function handleDelete(request) {
403
619
  return _server.NextResponse.json({ error: "Failed to delete files" }, { status: 500 });
404
620
  }
405
621
  }
406
- async function handleSync(request) {
407
- const accountId = process.env.CLOUDFLARE_R2_ACCOUNT_ID;
408
- const accessKeyId = process.env.CLOUDFLARE_R2_ACCESS_KEY_ID;
409
- const secretAccessKey = process.env.CLOUDFLARE_R2_SECRET_ACCESS_KEY;
410
- const bucketName = process.env.CLOUDFLARE_R2_BUCKET_NAME;
411
- const publicUrl = process.env.CLOUDFLARE_R2_PUBLIC_URL;
412
- if (!accountId || !accessKeyId || !secretAccessKey || !bucketName || !publicUrl) {
413
- return _server.NextResponse.json(
414
- { error: "R2 not configured. Set CLOUDFLARE_R2_* environment variables." },
415
- { status: 400 }
416
- );
622
+ async function handleCreateFolder(request) {
623
+ try {
624
+ const { parentPath, name } = await request.json();
625
+ if (!name || typeof name !== "string") {
626
+ return _server.NextResponse.json({ error: "Folder name is required" }, { status: 400 });
627
+ }
628
+ const sanitizedName = name.replace(/[<>:"/\\|?*]/g, "").trim();
629
+ if (!sanitizedName) {
630
+ return _server.NextResponse.json({ error: "Invalid folder name" }, { status: 400 });
631
+ }
632
+ const safePath = (parentPath || "public").replace(/\.\./g, "");
633
+ const folderPath = _path2.default.join(process.cwd(), safePath, sanitizedName);
634
+ if (!folderPath.startsWith(_path2.default.join(process.cwd(), "public"))) {
635
+ return _server.NextResponse.json({ error: "Invalid path" }, { status: 400 });
636
+ }
637
+ try {
638
+ await _fs.promises.access(folderPath);
639
+ return _server.NextResponse.json({ error: "A folder with this name already exists" }, { status: 400 });
640
+ } catch (e14) {
641
+ }
642
+ await _fs.promises.mkdir(folderPath, { recursive: true });
643
+ return _server.NextResponse.json({ success: true, path: _path2.default.join(safePath, sanitizedName) });
644
+ } catch (error) {
645
+ console.error("Failed to create folder:", error);
646
+ return _server.NextResponse.json({ error: "Failed to create folder" }, { status: 500 });
417
647
  }
648
+ }
649
+ async function handleRename(request) {
418
650
  try {
419
- const { imageKeys } = await request.json();
420
- if (!imageKeys || !Array.isArray(imageKeys) || imageKeys.length === 0) {
421
- return _server.NextResponse.json({ error: "No image keys provided" }, { status: 400 });
651
+ const { oldPath, newName } = await request.json();
652
+ if (!oldPath || !newName) {
653
+ return _server.NextResponse.json({ error: "Path and new name are required" }, { status: 400 });
422
654
  }
423
- const meta = await loadMeta();
424
- const r2 = new (0, _clients3.S3Client)({
425
- region: "auto",
426
- endpoint: `https://${accountId}.r2.cloudflarestorage.com`,
427
- credentials: { accessKeyId, secretAccessKey }
428
- });
429
- const synced = [];
430
- const errors = [];
431
- for (const imageKey of imageKeys) {
432
- const entry = meta[imageKey];
433
- if (!entry) {
434
- errors.push(`Image not found in meta: ${imageKey}`);
435
- continue;
436
- }
437
- if (entry.s) {
438
- synced.push(imageKey);
439
- continue;
440
- }
441
- try {
442
- for (const thumbPath of _chunkCN5NRNWBjs.getAllThumbnailPaths.call(void 0, imageKey)) {
443
- const localPath = _path2.default.join(process.cwd(), "public", thumbPath);
444
- try {
445
- const fileBuffer = await _fs.promises.readFile(localPath);
446
- await r2.send(
447
- new (0, _clients3.PutObjectCommand)({
448
- Bucket: bucketName,
449
- Key: thumbPath.replace(/^\//, ""),
450
- Body: fileBuffer,
451
- ContentType: getContentType(thumbPath)
452
- })
453
- );
454
- } catch (e8) {
455
- }
456
- }
457
- entry.s = 1;
458
- for (const thumbPath of _chunkCN5NRNWBjs.getAllThumbnailPaths.call(void 0, imageKey)) {
459
- const localPath = _path2.default.join(process.cwd(), "public", thumbPath);
655
+ const sanitizedName = newName.replace(/[<>:"/\\|?*]/g, "").trim();
656
+ if (!sanitizedName) {
657
+ return _server.NextResponse.json({ error: "Invalid name" }, { status: 400 });
658
+ }
659
+ const safePath = oldPath.replace(/\.\./g, "");
660
+ const absoluteOldPath = _path2.default.join(process.cwd(), safePath);
661
+ const parentDir = _path2.default.dirname(absoluteOldPath);
662
+ const absoluteNewPath = _path2.default.join(parentDir, sanitizedName);
663
+ if (!absoluteOldPath.startsWith(_path2.default.join(process.cwd(), "public"))) {
664
+ return _server.NextResponse.json({ error: "Invalid path" }, { status: 400 });
665
+ }
666
+ try {
667
+ await _fs.promises.access(absoluteOldPath);
668
+ } catch (e15) {
669
+ return _server.NextResponse.json({ error: "File or folder not found" }, { status: 404 });
670
+ }
671
+ try {
672
+ await _fs.promises.access(absoluteNewPath);
673
+ return _server.NextResponse.json({ error: "An item with this name already exists" }, { status: 400 });
674
+ } catch (e16) {
675
+ }
676
+ const stats = await _fs.promises.stat(absoluteOldPath);
677
+ const isFile = stats.isFile();
678
+ const isImage = isFile && isImageFile(_path2.default.basename(oldPath));
679
+ await _fs.promises.rename(absoluteOldPath, absoluteNewPath);
680
+ if (isImage) {
681
+ const meta = await loadMeta();
682
+ const oldRelativePath = safePath.replace(/^public\//, "");
683
+ const newRelativePath = _path2.default.join(_path2.default.dirname(oldRelativePath), sanitizedName);
684
+ const oldKey = "/" + oldRelativePath;
685
+ const newKey = "/" + newRelativePath;
686
+ if (meta[oldKey]) {
687
+ const entry = meta[oldKey];
688
+ const oldThumbPaths = _chunkCN5NRNWBjs.getAllThumbnailPaths.call(void 0, oldKey);
689
+ const newThumbPaths = _chunkCN5NRNWBjs.getAllThumbnailPaths.call(void 0, newKey);
690
+ for (let i = 0; i < oldThumbPaths.length; i++) {
691
+ const oldThumbPath = _path2.default.join(process.cwd(), "public", oldThumbPaths[i]);
692
+ const newThumbPath = _path2.default.join(process.cwd(), "public", newThumbPaths[i]);
693
+ await _fs.promises.mkdir(_path2.default.dirname(newThumbPath), { recursive: true });
460
694
  try {
461
- await _fs.promises.unlink(localPath);
462
- } catch (e9) {
695
+ await _fs.promises.rename(oldThumbPath, newThumbPath);
696
+ } catch (e17) {
463
697
  }
464
698
  }
465
- synced.push(imageKey);
466
- } catch (error) {
467
- console.error(`Failed to sync ${imageKey}:`, error);
468
- errors.push(imageKey);
699
+ delete meta[oldKey];
700
+ meta[newKey] = entry;
469
701
  }
702
+ await saveMeta(meta);
470
703
  }
471
- await saveMeta(meta);
472
- return _server.NextResponse.json({
473
- success: true,
474
- synced,
475
- errors: errors.length > 0 ? errors : void 0
476
- });
704
+ const newPath = _path2.default.join(_path2.default.dirname(safePath), sanitizedName);
705
+ return _server.NextResponse.json({ success: true, newPath });
477
706
  } catch (error) {
478
- console.error("Failed to sync:", error);
479
- return _server.NextResponse.json({ error: "Failed to sync to CDN" }, { status: 500 });
707
+ console.error("Failed to rename:", error);
708
+ return _server.NextResponse.json({ error: "Failed to rename" }, { status: 500 });
480
709
  }
481
710
  }
482
- async function handleReprocess(request) {
711
+ async function handleMove(request) {
483
712
  try {
484
- const { imageKeys } = await request.json();
485
- if (!imageKeys || !Array.isArray(imageKeys) || imageKeys.length === 0) {
486
- return _server.NextResponse.json({ error: "No image keys provided" }, { status: 400 });
713
+ const { paths, destination } = await request.json();
714
+ if (!paths || !Array.isArray(paths) || paths.length === 0) {
715
+ return _server.NextResponse.json({ error: "Paths are required" }, { status: 400 });
487
716
  }
488
- const meta = await loadMeta();
489
- const processed = [];
717
+ if (!destination || typeof destination !== "string") {
718
+ return _server.NextResponse.json({ error: "Destination is required" }, { status: 400 });
719
+ }
720
+ const safeDestination = destination.replace(/\.\./g, "");
721
+ const absoluteDestination = _path2.default.join(process.cwd(), safeDestination);
722
+ if (!absoluteDestination.startsWith(_path2.default.join(process.cwd(), "public"))) {
723
+ return _server.NextResponse.json({ error: "Invalid destination" }, { status: 400 });
724
+ }
725
+ try {
726
+ const destStats = await _fs.promises.stat(absoluteDestination);
727
+ if (!destStats.isDirectory()) {
728
+ return _server.NextResponse.json({ error: "Destination is not a folder" }, { status: 400 });
729
+ }
730
+ } catch (e18) {
731
+ return _server.NextResponse.json({ error: "Destination folder not found" }, { status: 404 });
732
+ }
733
+ const moved = [];
490
734
  const errors = [];
491
- for (const imageKey of imageKeys) {
492
- try {
493
- let buffer;
494
- const entry = meta[imageKey];
495
- const originalPath = _path2.default.join(process.cwd(), "public", imageKey);
496
- try {
497
- buffer = await _fs.promises.readFile(originalPath);
498
- } catch (e10) {
499
- if (_optionalChain([entry, 'optionalAccess', _5 => _5.s])) {
500
- buffer = await downloadFromCdn(imageKey);
501
- } else {
502
- throw new Error(`File not found: ${imageKey}`);
735
+ const meta = await loadMeta();
736
+ let metaChanged = false;
737
+ for (const itemPath of paths) {
738
+ const safePath = itemPath.replace(/\.\./g, "");
739
+ const absolutePath = _path2.default.join(process.cwd(), safePath);
740
+ const itemName = _path2.default.basename(safePath);
741
+ const newAbsolutePath = _path2.default.join(absoluteDestination, itemName);
742
+ if (absoluteDestination.startsWith(absolutePath + _path2.default.sep)) {
743
+ errors.push(`Cannot move ${itemName} into itself`);
744
+ continue;
745
+ }
746
+ try {
747
+ await _fs.promises.access(absolutePath);
748
+ } catch (e19) {
749
+ errors.push(`${itemName} not found`);
750
+ continue;
751
+ }
752
+ try {
753
+ await _fs.promises.access(newAbsolutePath);
754
+ errors.push(`${itemName} already exists in destination`);
755
+ continue;
756
+ } catch (e20) {
757
+ }
758
+ try {
759
+ await _fs.promises.rename(absolutePath, newAbsolutePath);
760
+ const stats = await _fs.promises.stat(newAbsolutePath);
761
+ if (stats.isFile() && isImageFile(itemName)) {
762
+ const oldRelativePath = safePath.replace(/^public\//, "");
763
+ const newRelativePath = _path2.default.join(safeDestination.replace(/^public\//, ""), itemName);
764
+ const oldKey = "/" + oldRelativePath;
765
+ const newKey = "/" + newRelativePath;
766
+ if (meta[oldKey]) {
767
+ const entry = meta[oldKey];
768
+ const oldThumbPaths = _chunkCN5NRNWBjs.getAllThumbnailPaths.call(void 0, oldKey);
769
+ const newThumbPaths = _chunkCN5NRNWBjs.getAllThumbnailPaths.call(void 0, newKey);
770
+ for (let i = 0; i < oldThumbPaths.length; i++) {
771
+ const oldThumbPath = _path2.default.join(process.cwd(), "public", oldThumbPaths[i]);
772
+ const newThumbPath = _path2.default.join(process.cwd(), "public", newThumbPaths[i]);
773
+ await _fs.promises.mkdir(_path2.default.dirname(newThumbPath), { recursive: true });
774
+ try {
775
+ await _fs.promises.rename(oldThumbPath, newThumbPath);
776
+ } catch (e21) {
777
+ }
778
+ }
779
+ delete meta[oldKey];
780
+ meta[newKey] = entry;
781
+ metaChanged = true;
503
782
  }
504
783
  }
505
- const updatedEntry = await processImage(buffer, imageKey);
506
- if (_optionalChain([entry, 'optionalAccess', _6 => _6.s])) {
507
- updatedEntry.s = 1;
508
- await uploadToCdn(imageKey);
509
- await deleteLocalThumbnails(imageKey);
510
- }
511
- meta[imageKey] = updatedEntry;
512
- processed.push(imageKey);
513
- } catch (error) {
514
- console.error(`Failed to reprocess ${imageKey}:`, error);
515
- errors.push(imageKey);
784
+ moved.push(itemPath);
785
+ } catch (e22) {
786
+ errors.push(`Failed to move ${itemName}`);
516
787
  }
517
788
  }
518
- await saveMeta(meta);
789
+ if (metaChanged) {
790
+ await saveMeta(meta);
791
+ }
519
792
  return _server.NextResponse.json({
520
- success: true,
521
- processed,
793
+ success: errors.length === 0,
794
+ moved,
522
795
  errors: errors.length > 0 ? errors : void 0
523
796
  });
524
797
  } catch (error) {
525
- console.error("Failed to reprocess:", error);
526
- return _server.NextResponse.json({ error: "Failed to reprocess images" }, { status: 500 });
798
+ console.error("Failed to move:", error);
799
+ return _server.NextResponse.json({ error: "Failed to move items" }, { status: 500 });
527
800
  }
528
801
  }
529
- async function handleCountImages() {
802
+
803
+ // src/handlers/images.ts
804
+
805
+
806
+
807
+
808
+ async function handleSync(request) {
809
+ const accountId = process.env.CLOUDFLARE_R2_ACCOUNT_ID;
810
+ const accessKeyId = process.env.CLOUDFLARE_R2_ACCESS_KEY_ID;
811
+ const secretAccessKey = process.env.CLOUDFLARE_R2_SECRET_ACCESS_KEY;
812
+ const bucketName = process.env.CLOUDFLARE_R2_BUCKET_NAME;
813
+ const publicUrl = process.env.CLOUDFLARE_R2_PUBLIC_URL;
814
+ if (!accountId || !accessKeyId || !secretAccessKey || !bucketName || !publicUrl) {
815
+ return _server.NextResponse.json(
816
+ { error: "R2 not configured. Set CLOUDFLARE_R2_* environment variables." },
817
+ { status: 400 }
818
+ );
819
+ }
530
820
  try {
531
- const allImages = [];
532
- async function scanPublicFolder(dir, relativePath = "") {
821
+ const { imageKeys } = await request.json();
822
+ if (!imageKeys || !Array.isArray(imageKeys) || imageKeys.length === 0) {
823
+ return _server.NextResponse.json({ error: "No image keys provided" }, { status: 400 });
824
+ }
825
+ const meta = await loadMeta();
826
+ const r2 = new (0, _clients3.S3Client)({
827
+ region: "auto",
828
+ endpoint: `https://${accountId}.r2.cloudflarestorage.com`,
829
+ credentials: { accessKeyId, secretAccessKey }
830
+ });
831
+ const synced = [];
832
+ const errors = [];
833
+ for (const imageKey of imageKeys) {
834
+ const entry = meta[imageKey];
835
+ if (!entry) {
836
+ errors.push(`Image not found in meta: ${imageKey}`);
837
+ continue;
838
+ }
839
+ if (entry.s) {
840
+ synced.push(imageKey);
841
+ continue;
842
+ }
533
843
  try {
534
- const entries = await _fs.promises.readdir(dir, { withFileTypes: true });
535
- for (const entry of entries) {
536
- if (entry.name.startsWith(".")) continue;
537
- const fullPath = _path2.default.join(dir, entry.name);
538
- const relPath = relativePath ? `${relativePath}/${entry.name}` : entry.name;
539
- if (relPath === "images" || relPath.startsWith("images/")) continue;
540
- if (entry.isDirectory()) {
541
- await scanPublicFolder(fullPath, relPath);
542
- } else if (isImageFile(entry.name)) {
543
- allImages.push(relPath);
844
+ for (const thumbPath of _chunkCN5NRNWBjs.getAllThumbnailPaths.call(void 0, imageKey)) {
845
+ const localPath = _path2.default.join(process.cwd(), "public", thumbPath);
846
+ try {
847
+ const fileBuffer = await _fs.promises.readFile(localPath);
848
+ await r2.send(
849
+ new (0, _clients3.PutObjectCommand)({
850
+ Bucket: bucketName,
851
+ Key: thumbPath.replace(/^\//, ""),
852
+ Body: fileBuffer,
853
+ ContentType: getContentType(thumbPath)
854
+ })
855
+ );
856
+ } catch (e23) {
544
857
  }
545
858
  }
546
- } catch (e11) {
859
+ entry.s = 1;
860
+ for (const thumbPath of _chunkCN5NRNWBjs.getAllThumbnailPaths.call(void 0, imageKey)) {
861
+ const localPath = _path2.default.join(process.cwd(), "public", thumbPath);
862
+ try {
863
+ await _fs.promises.unlink(localPath);
864
+ } catch (e24) {
865
+ }
866
+ }
867
+ synced.push(imageKey);
868
+ } catch (error) {
869
+ console.error(`Failed to sync ${imageKey}:`, error);
870
+ errors.push(imageKey);
547
871
  }
548
872
  }
549
- const publicDir = _path2.default.join(process.cwd(), "public");
550
- await scanPublicFolder(publicDir);
873
+ await saveMeta(meta);
551
874
  return _server.NextResponse.json({
552
- count: allImages.length,
553
- images: allImages
875
+ success: true,
876
+ synced,
877
+ errors: errors.length > 0 ? errors : void 0
554
878
  });
555
879
  } catch (error) {
556
- console.error("Failed to count images:", error);
557
- return _server.NextResponse.json({ error: "Failed to count images" }, { status: 500 });
880
+ console.error("Failed to sync:", error);
881
+ return _server.NextResponse.json({ error: "Failed to sync to CDN" }, { status: 500 });
558
882
  }
559
883
  }
560
- async function handleFolderImages(request) {
884
+ async function handleReprocess(request) {
561
885
  try {
562
- const searchParams = request.nextUrl.searchParams;
563
- const foldersParam = searchParams.get("folders");
564
- if (!foldersParam) {
565
- return _server.NextResponse.json({ error: "No folders provided" }, { status: 400 });
886
+ const { imageKeys } = await request.json();
887
+ if (!imageKeys || !Array.isArray(imageKeys) || imageKeys.length === 0) {
888
+ return _server.NextResponse.json({ error: "No image keys provided" }, { status: 400 });
566
889
  }
567
- const folders = foldersParam.split(",");
568
- const allImages = [];
569
- async function scanFolder(dir, relativePath = "") {
890
+ const meta = await loadMeta();
891
+ const processed = [];
892
+ const errors = [];
893
+ for (const imageKey of imageKeys) {
570
894
  try {
571
- const entries = await _fs.promises.readdir(dir, { withFileTypes: true });
572
- for (const entry of entries) {
573
- if (entry.name.startsWith(".")) continue;
574
- const fullPath = _path2.default.join(dir, entry.name);
575
- const relPath = relativePath ? `${relativePath}/${entry.name}` : entry.name;
576
- if (entry.isDirectory()) {
577
- await scanFolder(fullPath, relPath);
578
- } else if (isImageFile(entry.name)) {
579
- allImages.push(relPath);
895
+ let buffer;
896
+ const entry = meta[imageKey];
897
+ const originalPath = _path2.default.join(process.cwd(), "public", imageKey);
898
+ try {
899
+ buffer = await _fs.promises.readFile(originalPath);
900
+ } catch (e25) {
901
+ if (_optionalChain([entry, 'optionalAccess', _5 => _5.s])) {
902
+ buffer = await downloadFromCdn(imageKey);
903
+ } else {
904
+ throw new Error(`File not found: ${imageKey}`);
580
905
  }
581
906
  }
582
- } catch (e12) {
907
+ const updatedEntry = await processImage(buffer, imageKey);
908
+ if (_optionalChain([entry, 'optionalAccess', _6 => _6.s])) {
909
+ updatedEntry.s = 1;
910
+ await uploadToCdn(imageKey);
911
+ await deleteLocalThumbnails(imageKey);
912
+ }
913
+ meta[imageKey] = updatedEntry;
914
+ processed.push(imageKey);
915
+ } catch (error) {
916
+ console.error(`Failed to reprocess ${imageKey}:`, error);
917
+ errors.push(imageKey);
583
918
  }
584
919
  }
585
- for (const folder of folders) {
586
- const relativePath = folder.replace(/^public\/?/, "");
587
- if (relativePath === "images" || relativePath.startsWith("images/")) continue;
588
- const folderPath = _path2.default.join(process.cwd(), folder);
589
- await scanFolder(folderPath, relativePath);
590
- }
920
+ await saveMeta(meta);
591
921
  return _server.NextResponse.json({
592
- count: allImages.length,
593
- images: allImages
922
+ success: true,
923
+ processed,
924
+ errors: errors.length > 0 ? errors : void 0
594
925
  });
595
926
  } catch (error) {
596
- console.error("Failed to get folder images:", error);
597
- return _server.NextResponse.json({ error: "Failed to get folder images" }, { status: 500 });
927
+ console.error("Failed to reprocess:", error);
928
+ return _server.NextResponse.json({ error: "Failed to reprocess images" }, { status: 500 });
598
929
  }
599
930
  }
600
931
  async function handleProcessAllStream() {
@@ -626,7 +957,7 @@ async function handleProcessAllStream() {
626
957
  allImages.push({ key: relPath, fullPath });
627
958
  }
628
959
  }
629
- } catch (e13) {
960
+ } catch (e26) {
630
961
  }
631
962
  }
632
963
  const publicDir = _path2.default.join(process.cwd(), "public");
@@ -701,7 +1032,7 @@ async function handleProcessAllStream() {
701
1032
  }
702
1033
  }
703
1034
  }
704
- } catch (e14) {
1035
+ } catch (e27) {
705
1036
  }
706
1037
  }
707
1038
  const imagesDir = _path2.default.join(process.cwd(), "public", "images");
@@ -722,7 +1053,7 @@ async function handleProcessAllStream() {
722
1053
  await _fs.promises.rmdir(dir);
723
1054
  }
724
1055
  return isEmpty;
725
- } catch (e15) {
1056
+ } catch (e28) {
726
1057
  return true;
727
1058
  }
728
1059
  }
@@ -750,371 +1081,72 @@ async function handleProcessAllStream() {
750
1081
  }
751
1082
  });
752
1083
  }
753
- async function loadMeta() {
754
- const metaPath = _path2.default.join(process.cwd(), "_data", "_meta.json");
755
- try {
756
- const content = await _fs.promises.readFile(metaPath, "utf-8");
757
- return JSON.parse(content);
758
- } catch (e16) {
759
- return {};
1084
+
1085
+ // src/handlers/index.ts
1086
+ async function GET(request) {
1087
+ if (process.env.NODE_ENV !== "development") {
1088
+ return _server.NextResponse.json({ error: "Not available in production" }, { status: 403 });
760
1089
  }
1090
+ const pathname = request.nextUrl.pathname;
1091
+ const route = pathname.replace(/^\/api\/studio\/?/, "");
1092
+ if (route === "list-folders") {
1093
+ return handleListFolders();
1094
+ }
1095
+ if (route === "list" || route.startsWith("list")) {
1096
+ return handleList(request);
1097
+ }
1098
+ if (route === "count-images") {
1099
+ return handleCountImages();
1100
+ }
1101
+ if (route === "folder-images") {
1102
+ return handleFolderImages(request);
1103
+ }
1104
+ if (route === "search") {
1105
+ return handleSearch(request);
1106
+ }
1107
+ return _server.NextResponse.json({ error: "Not found" }, { status: 404 });
761
1108
  }
762
- async function saveMeta(meta) {
763
- const dataDir = _path2.default.join(process.cwd(), "_data");
764
- await _fs.promises.mkdir(dataDir, { recursive: true });
765
- const metaPath = _path2.default.join(dataDir, "_meta.json");
766
- await _fs.promises.writeFile(metaPath, JSON.stringify(meta, null, 2));
767
- }
768
- function isImageFile(filename) {
769
- const ext = _path2.default.extname(filename).toLowerCase();
770
- return [".jpg", ".jpeg", ".png", ".gif", ".webp", ".svg", ".ico", ".bmp", ".tiff", ".tif"].includes(ext);
771
- }
772
- function isMediaFile(filename) {
773
- const ext = _path2.default.extname(filename).toLowerCase();
774
- if ([".jpg", ".jpeg", ".png", ".gif", ".webp", ".svg", ".ico", ".bmp", ".tiff", ".tif"].includes(ext)) return true;
775
- if ([".mp4", ".webm", ".mov", ".avi", ".mkv", ".m4v"].includes(ext)) return true;
776
- if ([".mp3", ".wav", ".ogg", ".m4a", ".flac", ".aac"].includes(ext)) return true;
777
- if ([".pdf"].includes(ext)) return true;
778
- return false;
779
- }
780
- function getContentType(filePath) {
781
- const ext = _path2.default.extname(filePath).toLowerCase();
782
- switch (ext) {
783
- case ".jpg":
784
- case ".jpeg":
785
- return "image/jpeg";
786
- case ".png":
787
- return "image/png";
788
- case ".gif":
789
- return "image/gif";
790
- case ".webp":
791
- return "image/webp";
792
- case ".svg":
793
- return "image/svg+xml";
794
- default:
795
- return "application/octet-stream";
796
- }
797
- }
798
- async function processImage(buffer, imageKey) {
799
- const sharpInstance = _sharp2.default.call(void 0, buffer);
800
- const metadata = await sharpInstance.metadata();
801
- const originalWidth = metadata.width || 0;
802
- const originalHeight = metadata.height || 0;
803
- const keyWithoutSlash = imageKey.startsWith("/") ? imageKey.slice(1) : imageKey;
804
- const baseName = _path2.default.basename(keyWithoutSlash, _path2.default.extname(keyWithoutSlash));
805
- const ext = _path2.default.extname(keyWithoutSlash).toLowerCase();
806
- const imageDir = _path2.default.dirname(keyWithoutSlash);
807
- const imagesPath = _path2.default.join(process.cwd(), "public", "images", imageDir === "." ? "" : imageDir);
808
- await _fs.promises.mkdir(imagesPath, { recursive: true });
809
- const isPng = ext === ".png";
810
- const outputExt = isPng ? ".png" : ".jpg";
811
- const fullFileName = imageDir === "." ? `${baseName}${outputExt}` : `${imageDir}/${baseName}${outputExt}`;
812
- const fullPath = _path2.default.join(process.cwd(), "public", "images", fullFileName);
813
- if (isPng) {
814
- await _sharp2.default.call(void 0, buffer).png({ quality: 85 }).toFile(fullPath);
815
- } else {
816
- await _sharp2.default.call(void 0, buffer).jpeg({ quality: 85 }).toFile(fullPath);
817
- }
818
- for (const [, sizeConfig] of Object.entries(DEFAULT_SIZES)) {
819
- const { width: maxWidth, suffix } = sizeConfig;
820
- if (originalWidth <= maxWidth) {
821
- continue;
822
- }
823
- const ratio = originalHeight / originalWidth;
824
- const newHeight = Math.round(maxWidth * ratio);
825
- const sizeFileName = `${baseName}${suffix}${outputExt}`;
826
- const sizeFilePath = imageDir === "." ? sizeFileName : `${imageDir}/${sizeFileName}`;
827
- const sizePath = _path2.default.join(process.cwd(), "public", "images", sizeFilePath);
828
- if (isPng) {
829
- await _sharp2.default.call(void 0, buffer).resize(maxWidth, newHeight).png({ quality: 80 }).toFile(sizePath);
830
- } else {
831
- await _sharp2.default.call(void 0, buffer).resize(maxWidth, newHeight).jpeg({ quality: 80 }).toFile(sizePath);
832
- }
1109
+ async function POST(request) {
1110
+ if (process.env.NODE_ENV !== "development") {
1111
+ return _server.NextResponse.json({ error: "Not available in production" }, { status: 403 });
833
1112
  }
834
- const { data, info } = await _sharp2.default.call(void 0, buffer).resize(32, 32, { fit: "inside" }).ensureAlpha().raw().toBuffer({ resolveWithObject: true });
835
- const blurhash = _blurhash.encode.call(void 0, new Uint8ClampedArray(data), info.width, info.height, 4, 4);
836
- return {
837
- w: originalWidth,
838
- h: originalHeight,
839
- blur: blurhash
840
- };
841
- }
842
- async function downloadFromCdn(originalPath) {
843
- const accountId = process.env.CLOUDFLARE_R2_ACCOUNT_ID;
844
- const accessKeyId = process.env.CLOUDFLARE_R2_ACCESS_KEY_ID;
845
- const secretAccessKey = process.env.CLOUDFLARE_R2_SECRET_ACCESS_KEY;
846
- const bucketName = process.env.CLOUDFLARE_R2_BUCKET_NAME;
847
- if (!accountId || !accessKeyId || !secretAccessKey || !bucketName) {
848
- throw new Error("R2 not configured");
1113
+ const pathname = request.nextUrl.pathname;
1114
+ const route = pathname.replace(/^\/api\/studio\/?/, "");
1115
+ if (route === "upload") {
1116
+ return handleUpload(request);
849
1117
  }
850
- const r2 = new (0, _clients3.S3Client)({
851
- region: "auto",
852
- endpoint: `https://${accountId}.r2.cloudflarestorage.com`,
853
- credentials: { accessKeyId, secretAccessKey }
854
- });
855
- const response = await r2.send(
856
- new (0, _clients3.GetObjectCommand)({
857
- Bucket: bucketName,
858
- Key: originalPath.replace(/^\//, "")
859
- })
860
- );
861
- const stream = response.Body;
862
- const chunks = [];
863
- for await (const chunk of stream) {
864
- chunks.push(Buffer.from(chunk));
1118
+ if (route === "delete") {
1119
+ return handleDelete(request);
865
1120
  }
866
- return Buffer.concat(chunks);
867
- }
868
- async function uploadToCdn(imageKey) {
869
- const accountId = process.env.CLOUDFLARE_R2_ACCOUNT_ID;
870
- const accessKeyId = process.env.CLOUDFLARE_R2_ACCESS_KEY_ID;
871
- const secretAccessKey = process.env.CLOUDFLARE_R2_SECRET_ACCESS_KEY;
872
- const bucketName = process.env.CLOUDFLARE_R2_BUCKET_NAME;
873
- if (!accountId || !accessKeyId || !secretAccessKey || !bucketName) {
874
- throw new Error("R2 not configured");
1121
+ if (route === "sync") {
1122
+ return handleSync(request);
875
1123
  }
876
- const r2 = new (0, _clients3.S3Client)({
877
- region: "auto",
878
- endpoint: `https://${accountId}.r2.cloudflarestorage.com`,
879
- credentials: { accessKeyId, secretAccessKey }
880
- });
881
- for (const thumbPath of _chunkCN5NRNWBjs.getAllThumbnailPaths.call(void 0, imageKey)) {
882
- const localPath = _path2.default.join(process.cwd(), "public", thumbPath);
883
- try {
884
- const fileBuffer = await _fs.promises.readFile(localPath);
885
- await r2.send(
886
- new (0, _clients3.PutObjectCommand)({
887
- Bucket: bucketName,
888
- Key: thumbPath.replace(/^\//, ""),
889
- Body: fileBuffer,
890
- ContentType: getContentType(thumbPath)
891
- })
892
- );
893
- } catch (e17) {
894
- }
1124
+ if (route === "reprocess") {
1125
+ return handleReprocess(request);
895
1126
  }
896
- }
897
- async function deleteLocalThumbnails(imageKey) {
898
- for (const thumbPath of _chunkCN5NRNWBjs.getAllThumbnailPaths.call(void 0, imageKey)) {
899
- const localPath = _path2.default.join(process.cwd(), "public", thumbPath);
900
- try {
901
- await _fs.promises.unlink(localPath);
902
- } catch (e18) {
903
- }
1127
+ if (route === "process-all") {
1128
+ return handleProcessAllStream();
904
1129
  }
905
- }
906
- async function handleCreateFolder(request) {
907
- try {
908
- const { parentPath, name } = await request.json();
909
- if (!name || typeof name !== "string") {
910
- return _server.NextResponse.json({ error: "Folder name is required" }, { status: 400 });
911
- }
912
- const sanitizedName = name.replace(/[<>:"/\\|?*]/g, "").trim();
913
- if (!sanitizedName) {
914
- return _server.NextResponse.json({ error: "Invalid folder name" }, { status: 400 });
915
- }
916
- const safePath = (parentPath || "public").replace(/\.\./g, "");
917
- const folderPath = _path2.default.join(process.cwd(), safePath, sanitizedName);
918
- if (!folderPath.startsWith(_path2.default.join(process.cwd(), "public"))) {
919
- return _server.NextResponse.json({ error: "Invalid path" }, { status: 400 });
920
- }
921
- try {
922
- await _fs.promises.access(folderPath);
923
- return _server.NextResponse.json({ error: "A folder with this name already exists" }, { status: 400 });
924
- } catch (e19) {
925
- }
926
- await _fs.promises.mkdir(folderPath, { recursive: true });
927
- return _server.NextResponse.json({ success: true, path: _path2.default.join(safePath, sanitizedName) });
928
- } catch (error) {
929
- console.error("Failed to create folder:", error);
930
- return _server.NextResponse.json({ error: "Failed to create folder" }, { status: 500 });
1130
+ if (route === "create-folder") {
1131
+ return handleCreateFolder(request);
931
1132
  }
932
- }
933
- async function handleRename(request) {
934
- try {
935
- const { oldPath, newName } = await request.json();
936
- if (!oldPath || !newName) {
937
- return _server.NextResponse.json({ error: "Path and new name are required" }, { status: 400 });
938
- }
939
- const sanitizedName = newName.replace(/[<>:"/\\|?*]/g, "").trim();
940
- if (!sanitizedName) {
941
- return _server.NextResponse.json({ error: "Invalid name" }, { status: 400 });
942
- }
943
- const safePath = oldPath.replace(/\.\./g, "");
944
- const absoluteOldPath = _path2.default.join(process.cwd(), safePath);
945
- const parentDir = _path2.default.dirname(absoluteOldPath);
946
- const absoluteNewPath = _path2.default.join(parentDir, sanitizedName);
947
- if (!absoluteOldPath.startsWith(_path2.default.join(process.cwd(), "public"))) {
948
- return _server.NextResponse.json({ error: "Invalid path" }, { status: 400 });
949
- }
950
- try {
951
- await _fs.promises.access(absoluteOldPath);
952
- } catch (e20) {
953
- return _server.NextResponse.json({ error: "File or folder not found" }, { status: 404 });
954
- }
955
- try {
956
- await _fs.promises.access(absoluteNewPath);
957
- return _server.NextResponse.json({ error: "An item with this name already exists" }, { status: 400 });
958
- } catch (e21) {
959
- }
960
- const stats = await _fs.promises.stat(absoluteOldPath);
961
- const isFile = stats.isFile();
962
- const isImage = isFile && isImageFile(_path2.default.basename(oldPath));
963
- await _fs.promises.rename(absoluteOldPath, absoluteNewPath);
964
- if (isImage) {
965
- const meta = await loadMeta();
966
- const oldRelativePath = safePath.replace(/^public\//, "");
967
- const newRelativePath = _path2.default.join(_path2.default.dirname(oldRelativePath), sanitizedName);
968
- const oldKey = "/" + oldRelativePath;
969
- const newKey = "/" + newRelativePath;
970
- if (meta[oldKey]) {
971
- const entry = meta[oldKey];
972
- const oldThumbPaths = _chunkCN5NRNWBjs.getAllThumbnailPaths.call(void 0, oldKey);
973
- const newThumbPaths = _chunkCN5NRNWBjs.getAllThumbnailPaths.call(void 0, newKey);
974
- for (let i = 0; i < oldThumbPaths.length; i++) {
975
- const oldThumbPath = _path2.default.join(process.cwd(), "public", oldThumbPaths[i]);
976
- const newThumbPath = _path2.default.join(process.cwd(), "public", newThumbPaths[i]);
977
- await _fs.promises.mkdir(_path2.default.dirname(newThumbPath), { recursive: true });
978
- try {
979
- await _fs.promises.rename(oldThumbPath, newThumbPath);
980
- } catch (e22) {
981
- }
982
- }
983
- delete meta[oldKey];
984
- meta[newKey] = entry;
985
- }
986
- await saveMeta(meta);
987
- }
988
- const newPath = _path2.default.join(_path2.default.dirname(safePath), sanitizedName);
989
- return _server.NextResponse.json({ success: true, newPath });
990
- } catch (error) {
991
- console.error("Failed to rename:", error);
992
- return _server.NextResponse.json({ error: "Failed to rename" }, { status: 500 });
1133
+ if (route === "rename") {
1134
+ return handleRename(request);
993
1135
  }
994
- }
995
- async function handleMove(request) {
996
- try {
997
- const { paths, destination } = await request.json();
998
- if (!paths || !Array.isArray(paths) || paths.length === 0) {
999
- return _server.NextResponse.json({ error: "Paths are required" }, { status: 400 });
1000
- }
1001
- if (!destination || typeof destination !== "string") {
1002
- return _server.NextResponse.json({ error: "Destination is required" }, { status: 400 });
1003
- }
1004
- const safeDestination = destination.replace(/\.\./g, "");
1005
- const absoluteDestination = _path2.default.join(process.cwd(), safeDestination);
1006
- if (!absoluteDestination.startsWith(_path2.default.join(process.cwd(), "public"))) {
1007
- return _server.NextResponse.json({ error: "Invalid destination" }, { status: 400 });
1008
- }
1009
- try {
1010
- const destStats = await _fs.promises.stat(absoluteDestination);
1011
- if (!destStats.isDirectory()) {
1012
- return _server.NextResponse.json({ error: "Destination is not a folder" }, { status: 400 });
1013
- }
1014
- } catch (e23) {
1015
- return _server.NextResponse.json({ error: "Destination folder not found" }, { status: 404 });
1016
- }
1017
- const moved = [];
1018
- const errors = [];
1019
- const meta = await loadMeta();
1020
- let metaChanged = false;
1021
- for (const itemPath of paths) {
1022
- const safePath = itemPath.replace(/\.\./g, "");
1023
- const absolutePath = _path2.default.join(process.cwd(), safePath);
1024
- const itemName = _path2.default.basename(safePath);
1025
- const newAbsolutePath = _path2.default.join(absoluteDestination, itemName);
1026
- if (absoluteDestination.startsWith(absolutePath + _path2.default.sep)) {
1027
- errors.push(`Cannot move ${itemName} into itself`);
1028
- continue;
1029
- }
1030
- try {
1031
- await _fs.promises.access(absolutePath);
1032
- } catch (e24) {
1033
- errors.push(`${itemName} not found`);
1034
- continue;
1035
- }
1036
- try {
1037
- await _fs.promises.access(newAbsolutePath);
1038
- errors.push(`${itemName} already exists in destination`);
1039
- continue;
1040
- } catch (e25) {
1041
- }
1042
- try {
1043
- await _fs.promises.rename(absolutePath, newAbsolutePath);
1044
- const stats = await _fs.promises.stat(newAbsolutePath);
1045
- if (stats.isFile() && isImageFile(itemName)) {
1046
- const oldRelativePath = safePath.replace(/^public\//, "");
1047
- const newRelativePath = _path2.default.join(safeDestination.replace(/^public\//, ""), itemName);
1048
- const oldKey = "/" + oldRelativePath;
1049
- const newKey = "/" + newRelativePath;
1050
- if (meta[oldKey]) {
1051
- const entry = meta[oldKey];
1052
- const oldThumbPaths = _chunkCN5NRNWBjs.getAllThumbnailPaths.call(void 0, oldKey);
1053
- const newThumbPaths = _chunkCN5NRNWBjs.getAllThumbnailPaths.call(void 0, newKey);
1054
- for (let i = 0; i < oldThumbPaths.length; i++) {
1055
- const oldThumbPath = _path2.default.join(process.cwd(), "public", oldThumbPaths[i]);
1056
- const newThumbPath = _path2.default.join(process.cwd(), "public", newThumbPaths[i]);
1057
- await _fs.promises.mkdir(_path2.default.dirname(newThumbPath), { recursive: true });
1058
- try {
1059
- await _fs.promises.rename(oldThumbPath, newThumbPath);
1060
- } catch (e26) {
1061
- }
1062
- }
1063
- delete meta[oldKey];
1064
- meta[newKey] = entry;
1065
- metaChanged = true;
1066
- }
1067
- }
1068
- moved.push(itemPath);
1069
- } catch (error) {
1070
- errors.push(`Failed to move ${itemName}`);
1071
- }
1072
- }
1073
- if (metaChanged) {
1074
- await saveMeta(meta);
1075
- }
1076
- return _server.NextResponse.json({
1077
- success: errors.length === 0,
1078
- moved,
1079
- errors: errors.length > 0 ? errors : void 0
1080
- });
1081
- } catch (error) {
1082
- console.error("Failed to move:", error);
1083
- return _server.NextResponse.json({ error: "Failed to move items" }, { status: 500 });
1136
+ if (route === "move") {
1137
+ return handleMove(request);
1084
1138
  }
1139
+ return _server.NextResponse.json({ error: "Not found" }, { status: 404 });
1085
1140
  }
1086
- async function handleListFolders() {
1087
- try {
1088
- const publicDir = _path2.default.join(process.cwd(), "public");
1089
- const folders = [];
1090
- async function scanDir(dir, relativePath, depth) {
1091
- try {
1092
- const entries = await _fs.promises.readdir(dir, { withFileTypes: true });
1093
- for (const entry of entries) {
1094
- if (!entry.isDirectory()) continue;
1095
- if (entry.name.startsWith(".")) continue;
1096
- const folderRelativePath = relativePath ? `${relativePath}/${entry.name}` : entry.name;
1097
- folders.push({
1098
- path: `public/${folderRelativePath}`,
1099
- name: entry.name,
1100
- depth
1101
- });
1102
- await scanDir(_path2.default.join(dir, entry.name), folderRelativePath, depth + 1);
1103
- }
1104
- } catch (e27) {
1105
- }
1106
- }
1107
- folders.push({ path: "public", name: "public", depth: 0 });
1108
- await scanDir(publicDir, "", 1);
1109
- return _server.NextResponse.json({ folders });
1110
- } catch (error) {
1111
- console.error("Failed to list folders:", error);
1112
- return _server.NextResponse.json({ error: "Failed to list folders" }, { status: 500 });
1141
+ async function DELETE(request) {
1142
+ if (process.env.NODE_ENV !== "development") {
1143
+ return _server.NextResponse.json({ error: "Not available in production" }, { status: 403 });
1113
1144
  }
1145
+ return handleDelete(request);
1114
1146
  }
1115
1147
 
1116
1148
 
1117
1149
 
1118
1150
 
1119
1151
  exports.DELETE = DELETE; exports.GET = GET; exports.POST = POST;
1120
- //# sourceMappingURL=handlers.js.map
1152
+ //# sourceMappingURL=index.js.map