@gallop.software/studio 2.0.7 → 2.1.0

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,2587 +0,0 @@
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
-
3
-
4
-
5
- var _chunkVI6QG6WTjs = require('../chunk-VI6QG6WT.js');
6
-
7
- // src/handlers/index.ts
8
- var _server = require('next/server');
9
-
10
- // src/handlers/list.ts
11
-
12
- var _fs = require('fs');
13
- var _path = require('path'); var _path2 = _interopRequireDefault(_path);
14
-
15
- // src/handlers/utils/meta.ts
16
-
17
-
18
- // src/config/workspace.ts
19
-
20
- var workspacePath = null;
21
- function getWorkspace() {
22
- if (workspacePath === null) {
23
- workspacePath = process.env.STUDIO_WORKSPACE || process.cwd();
24
- }
25
- return workspacePath;
26
- }
27
- function getPublicPath(...segments) {
28
- return _path2.default.join(getWorkspace(), "public", ...segments);
29
- }
30
- function getDataPath(...segments) {
31
- return _path2.default.join(getWorkspace(), "_data", ...segments);
32
- }
33
- function getSrcAppPath(...segments) {
34
- return _path2.default.join(getWorkspace(), "src", "app", ...segments);
35
- }
36
- function getWorkspacePath(...segments) {
37
- return _path2.default.join(getWorkspace(), ...segments);
38
- }
39
-
40
- // src/handlers/utils/meta.ts
41
- async function loadMeta() {
42
- const metaPath = getDataPath("_studio.json");
43
- try {
44
- const content = await _fs.promises.readFile(metaPath, "utf-8");
45
- return JSON.parse(content);
46
- } catch (e) {
47
- return {};
48
- }
49
- }
50
- async function saveMeta(meta) {
51
- const dataDir = getDataPath();
52
- await _fs.promises.mkdir(dataDir, { recursive: true });
53
- const metaPath = getDataPath("_studio.json");
54
- const ordered = {};
55
- if (meta._cdns) {
56
- ordered._cdns = meta._cdns;
57
- }
58
- for (const [key, value] of Object.entries(meta)) {
59
- if (key !== "_cdns") {
60
- ordered[key] = value;
61
- }
62
- }
63
- await _fs.promises.writeFile(metaPath, JSON.stringify(ordered, null, 2));
64
- }
65
- function getCdnUrls(meta) {
66
- return meta._cdns || [];
67
- }
68
- function getOrAddCdnIndex(meta, cdnUrl) {
69
- if (!meta._cdns) {
70
- meta._cdns = [];
71
- }
72
- const normalizedUrl = cdnUrl.replace(/\/$/, "");
73
- const existingIndex = meta._cdns.indexOf(normalizedUrl);
74
- if (existingIndex >= 0) {
75
- return existingIndex;
76
- }
77
- meta._cdns.push(normalizedUrl);
78
- return meta._cdns.length - 1;
79
- }
80
- function getMetaEntry(meta, key) {
81
- if (key.startsWith("_")) return void 0;
82
- const value = meta[key];
83
- if (Array.isArray(value)) return void 0;
84
- return value;
85
- }
86
- function setMetaEntry(meta, key, entry) {
87
- meta[key] = entry;
88
- }
89
- function getFileEntries(meta) {
90
- return Object.entries(meta).filter(
91
- ([key, value]) => !key.startsWith("_") && !Array.isArray(value)
92
- );
93
- }
94
-
95
- // src/handlers/utils/files.ts
96
-
97
- function isImageFile(filename) {
98
- const ext = _path2.default.extname(filename).toLowerCase();
99
- return [".jpg", ".jpeg", ".png", ".gif", ".webp", ".svg", ".ico", ".bmp", ".tiff", ".tif"].includes(ext);
100
- }
101
- function isMediaFile(filename) {
102
- const ext = _path2.default.extname(filename).toLowerCase();
103
- if ([".jpg", ".jpeg", ".png", ".gif", ".webp", ".svg", ".ico", ".bmp", ".tiff", ".tif"].includes(ext)) return true;
104
- if ([".mp4", ".webm", ".mov", ".avi", ".mkv", ".m4v"].includes(ext)) return true;
105
- if ([".mp3", ".wav", ".ogg", ".m4a", ".flac", ".aac"].includes(ext)) return true;
106
- if ([".pdf"].includes(ext)) return true;
107
- return false;
108
- }
109
- function getContentType(filePath) {
110
- const ext = _path2.default.extname(filePath).toLowerCase();
111
- switch (ext) {
112
- case ".jpg":
113
- case ".jpeg":
114
- return "image/jpeg";
115
- case ".png":
116
- return "image/png";
117
- case ".gif":
118
- return "image/gif";
119
- case ".webp":
120
- return "image/webp";
121
- case ".svg":
122
- return "image/svg+xml";
123
- default:
124
- return "application/octet-stream";
125
- }
126
- }
127
-
128
- // src/handlers/utils/thumbnails.ts
129
-
130
-
131
- var _sharp = require('sharp'); var _sharp2 = _interopRequireDefault(_sharp);
132
- var _blurhash = require('blurhash');
133
- var FULL_MAX_WIDTH = 2560;
134
- var DEFAULT_SIZES = {
135
- small: { width: 300, suffix: "-sm", key: "sm" },
136
- medium: { width: 700, suffix: "-md", key: "md" },
137
- large: { width: 1400, suffix: "-lg", key: "lg" }
138
- };
139
- async function processImage(buffer, imageKey) {
140
- const sharpInstance = _sharp2.default.call(void 0, buffer);
141
- const metadata = await sharpInstance.metadata();
142
- const originalWidth = metadata.width || 0;
143
- const originalHeight = metadata.height || 0;
144
- const ratio = originalHeight / originalWidth;
145
- const keyWithoutSlash = imageKey.startsWith("/") ? imageKey.slice(1) : imageKey;
146
- const baseName = _path2.default.basename(keyWithoutSlash, _path2.default.extname(keyWithoutSlash));
147
- const ext = _path2.default.extname(keyWithoutSlash).toLowerCase();
148
- const imageDir = _path2.default.dirname(keyWithoutSlash);
149
- const imagesPath = getPublicPath("images", imageDir === "." ? "" : imageDir);
150
- await _fs.promises.mkdir(imagesPath, { recursive: true });
151
- const isPng = ext === ".png";
152
- const outputExt = isPng ? ".png" : ".jpg";
153
- const entry = {
154
- o: { w: originalWidth, h: originalHeight }
155
- };
156
- const fullFileName = imageDir === "." ? `${baseName}${outputExt}` : `${imageDir}/${baseName}${outputExt}`;
157
- const fullPath = getPublicPath("images", fullFileName);
158
- let fullWidth = originalWidth;
159
- let fullHeight = originalHeight;
160
- if (originalWidth > FULL_MAX_WIDTH) {
161
- fullWidth = FULL_MAX_WIDTH;
162
- fullHeight = Math.round(FULL_MAX_WIDTH * ratio);
163
- if (isPng) {
164
- await _sharp2.default.call(void 0, buffer).resize(fullWidth, fullHeight).png({ quality: 85 }).toFile(fullPath);
165
- } else {
166
- await _sharp2.default.call(void 0, buffer).resize(fullWidth, fullHeight).jpeg({ quality: 85 }).toFile(fullPath);
167
- }
168
- } else {
169
- if (isPng) {
170
- await _sharp2.default.call(void 0, buffer).png({ quality: 85 }).toFile(fullPath);
171
- } else {
172
- await _sharp2.default.call(void 0, buffer).jpeg({ quality: 85 }).toFile(fullPath);
173
- }
174
- }
175
- entry.f = { w: fullWidth, h: fullHeight };
176
- for (const [, sizeConfig] of Object.entries(DEFAULT_SIZES)) {
177
- const { width: maxWidth, suffix, key } = sizeConfig;
178
- if (originalWidth <= maxWidth) {
179
- continue;
180
- }
181
- const newHeight = Math.round(maxWidth * ratio);
182
- const sizeFileName = `${baseName}${suffix}${outputExt}`;
183
- const sizeFilePath = imageDir === "." ? sizeFileName : `${imageDir}/${sizeFileName}`;
184
- const sizePath = getPublicPath("images", sizeFilePath);
185
- if (isPng) {
186
- await _sharp2.default.call(void 0, buffer).resize(maxWidth, newHeight).png({ quality: 80 }).toFile(sizePath);
187
- } else {
188
- await _sharp2.default.call(void 0, buffer).resize(maxWidth, newHeight).jpeg({ quality: 80 }).toFile(sizePath);
189
- }
190
- entry[key] = { w: maxWidth, h: newHeight };
191
- }
192
- const { data, info } = await _sharp2.default.call(void 0, buffer).resize(32, 32, { fit: "inside" }).ensureAlpha().raw().toBuffer({ resolveWithObject: true });
193
- entry.b = _blurhash.encode.call(void 0, new Uint8ClampedArray(data), info.width, info.height, 4, 4);
194
- return entry;
195
- }
196
-
197
- // src/handlers/utils/cdn.ts
198
-
199
- var _clients3 = require('@aws-sdk/client-s3');
200
- async function purgeCloudflareCache(urls) {
201
- const zoneId = process.env.CLOUDFLARE_ZONE_ID;
202
- const apiToken = process.env.CLOUDFLARE_API_TOKEN;
203
- if (!zoneId || !apiToken || urls.length === 0) {
204
- return;
205
- }
206
- try {
207
- const response = await fetch(
208
- `https://api.cloudflare.com/client/v4/zones/${zoneId}/purge_cache`,
209
- {
210
- method: "POST",
211
- headers: {
212
- "Authorization": `Bearer ${apiToken}`,
213
- "Content-Type": "application/json"
214
- },
215
- body: JSON.stringify({ files: urls })
216
- }
217
- );
218
- if (!response.ok) {
219
- console.error("Cache purge failed:", await response.text());
220
- }
221
- } catch (error) {
222
- console.error("Cache purge error:", error);
223
- }
224
- }
225
- function getR2Client() {
226
- const accountId = process.env.CLOUDFLARE_R2_ACCOUNT_ID;
227
- const accessKeyId = process.env.CLOUDFLARE_R2_ACCESS_KEY_ID;
228
- const secretAccessKey = process.env.CLOUDFLARE_R2_SECRET_ACCESS_KEY;
229
- if (!accountId || !accessKeyId || !secretAccessKey) {
230
- throw new Error("R2 not configured");
231
- }
232
- return new (0, _clients3.S3Client)({
233
- region: "auto",
234
- endpoint: `https://${accountId}.r2.cloudflarestorage.com`,
235
- credentials: { accessKeyId, secretAccessKey }
236
- });
237
- }
238
- async function downloadFromCdn(originalPath) {
239
- const bucketName = process.env.CLOUDFLARE_R2_BUCKET_NAME;
240
- if (!bucketName) throw new Error("R2 bucket not configured");
241
- const r2 = getR2Client();
242
- const maxRetries = 3;
243
- let lastError;
244
- for (let attempt = 0; attempt < maxRetries; attempt++) {
245
- try {
246
- const response = await r2.send(
247
- new (0, _clients3.GetObjectCommand)({
248
- Bucket: bucketName,
249
- Key: originalPath.replace(/^\//, "")
250
- })
251
- );
252
- const stream = response.Body;
253
- const chunks = [];
254
- for await (const chunk of stream) {
255
- chunks.push(Buffer.from(chunk));
256
- }
257
- return Buffer.concat(chunks);
258
- } catch (error) {
259
- lastError = error;
260
- if (attempt < maxRetries - 1) {
261
- await new Promise((resolve) => setTimeout(resolve, 500 * (attempt + 1)));
262
- }
263
- }
264
- }
265
- throw lastError || new Error(`Failed to download ${originalPath} after ${maxRetries} attempts`);
266
- }
267
- async function uploadToCdn(imageKey) {
268
- const bucketName = process.env.CLOUDFLARE_R2_BUCKET_NAME;
269
- if (!bucketName) throw new Error("R2 bucket not configured");
270
- const r2 = getR2Client();
271
- for (const thumbPath of _chunkVI6QG6WTjs.getAllThumbnailPaths.call(void 0, imageKey)) {
272
- const localPath = getPublicPath(thumbPath);
273
- try {
274
- const fileBuffer = await _fs.promises.readFile(localPath);
275
- await r2.send(
276
- new (0, _clients3.PutObjectCommand)({
277
- Bucket: bucketName,
278
- Key: thumbPath.replace(/^\//, ""),
279
- Body: fileBuffer,
280
- ContentType: getContentType(thumbPath)
281
- })
282
- );
283
- } catch (e2) {
284
- }
285
- }
286
- }
287
- async function deleteLocalThumbnails(imageKey) {
288
- for (const thumbPath of _chunkVI6QG6WTjs.getAllThumbnailPaths.call(void 0, imageKey)) {
289
- const localPath = getPublicPath(thumbPath);
290
- try {
291
- await _fs.promises.unlink(localPath);
292
- } catch (e3) {
293
- }
294
- }
295
- }
296
- async function downloadFromRemoteUrl(url) {
297
- const maxRetries = 3;
298
- let lastError;
299
- for (let attempt = 0; attempt < maxRetries; attempt++) {
300
- try {
301
- const response = await fetch(url);
302
- if (!response.ok) {
303
- throw new Error(`Failed to download from ${url}: ${response.status}`);
304
- }
305
- const arrayBuffer = await response.arrayBuffer();
306
- return Buffer.from(arrayBuffer);
307
- } catch (error) {
308
- lastError = error;
309
- if (attempt < maxRetries - 1) {
310
- await new Promise((resolve) => setTimeout(resolve, 500 * (attempt + 1)));
311
- }
312
- }
313
- }
314
- throw lastError || new Error(`Failed to download from ${url} after ${maxRetries} attempts`);
315
- }
316
- async function uploadOriginalToCdn(imageKey) {
317
- const bucketName = process.env.CLOUDFLARE_R2_BUCKET_NAME;
318
- if (!bucketName) throw new Error("R2 bucket not configured");
319
- const r2 = getR2Client();
320
- const localPath = getPublicPath(imageKey);
321
- const fileBuffer = await _fs.promises.readFile(localPath);
322
- await r2.send(
323
- new (0, _clients3.PutObjectCommand)({
324
- Bucket: bucketName,
325
- Key: imageKey.replace(/^\//, ""),
326
- Body: fileBuffer,
327
- ContentType: getContentType(imageKey)
328
- })
329
- );
330
- }
331
- async function deleteFromCdn(imageKey, hasThumbnails) {
332
- const bucketName = process.env.CLOUDFLARE_R2_BUCKET_NAME;
333
- if (!bucketName) throw new Error("R2 bucket not configured");
334
- const r2 = getR2Client();
335
- try {
336
- await r2.send(
337
- new (0, _clients3.DeleteObjectCommand)({
338
- Bucket: bucketName,
339
- Key: imageKey.replace(/^\//, "")
340
- })
341
- );
342
- } catch (e4) {
343
- }
344
- if (hasThumbnails) {
345
- for (const thumbPath of _chunkVI6QG6WTjs.getAllThumbnailPaths.call(void 0, imageKey)) {
346
- try {
347
- await r2.send(
348
- new (0, _clients3.DeleteObjectCommand)({
349
- Bucket: bucketName,
350
- Key: thumbPath.replace(/^\//, "")
351
- })
352
- );
353
- } catch (e5) {
354
- }
355
- }
356
- }
357
- }
358
- async function deleteThumbnailsFromCdn(imageKey) {
359
- const bucketName = process.env.CLOUDFLARE_R2_BUCKET_NAME;
360
- if (!bucketName) throw new Error("R2 bucket not configured");
361
- const r2 = getR2Client();
362
- for (const thumbPath of _chunkVI6QG6WTjs.getAllThumbnailPaths.call(void 0, imageKey)) {
363
- try {
364
- await r2.send(
365
- new (0, _clients3.DeleteObjectCommand)({
366
- Bucket: bucketName,
367
- Key: thumbPath.replace(/^\//, "")
368
- })
369
- );
370
- } catch (e6) {
371
- }
372
- }
373
- }
374
-
375
- // src/handlers/list.ts
376
- function getExistingThumbnails(originalPath, entry) {
377
- const thumbnails = [];
378
- if (entry.f) {
379
- thumbnails.push({ path: _chunkVI6QG6WTjs.getThumbnailPath.call(void 0, originalPath, "full"), size: "f" });
380
- }
381
- if (entry.lg) {
382
- thumbnails.push({ path: _chunkVI6QG6WTjs.getThumbnailPath.call(void 0, originalPath, "lg"), size: "lg" });
383
- }
384
- if (entry.md) {
385
- thumbnails.push({ path: _chunkVI6QG6WTjs.getThumbnailPath.call(void 0, originalPath, "md"), size: "md" });
386
- }
387
- if (entry.sm) {
388
- thumbnails.push({ path: _chunkVI6QG6WTjs.getThumbnailPath.call(void 0, originalPath, "sm"), size: "sm" });
389
- }
390
- return thumbnails;
391
- }
392
- function countFileTypes(folderPrefix, fileEntries, cdnUrls, r2PublicUrl) {
393
- let cloudCount = 0;
394
- let remoteCount = 0;
395
- let localCount = 0;
396
- for (const [key, entry] of fileEntries) {
397
- if (key.startsWith(folderPrefix)) {
398
- if (entry.c !== void 0) {
399
- const cdnUrl = cdnUrls[entry.c];
400
- if (cdnUrl === r2PublicUrl) {
401
- cloudCount++;
402
- } else {
403
- remoteCount++;
404
- }
405
- } else {
406
- localCount++;
407
- }
408
- }
409
- }
410
- return { cloudCount, remoteCount, localCount };
411
- }
412
- async function handleList(request) {
413
- const searchParams = request.nextUrl.searchParams;
414
- const requestedPath = searchParams.get("path") || "public";
415
- try {
416
- const meta = await loadMeta();
417
- const fileEntries = getFileEntries(meta);
418
- const cdnUrls = getCdnUrls(meta);
419
- const r2PublicUrl = _optionalChain([process, 'access', _ => _.env, 'access', _2 => _2.CLOUDFLARE_R2_PUBLIC_URL, 'optionalAccess', _3 => _3.replace, 'call', _4 => _4(/\/$/, "")]) || "";
420
- const relativePath = requestedPath.replace(/^public\/?/, "");
421
- const pathPrefix = relativePath ? `/${relativePath}/` : "/";
422
- const items = [];
423
- const seenFolders = /* @__PURE__ */ new Set();
424
- const metaKeys = fileEntries.map(([key]) => key);
425
- const isInsideImagesFolder = relativePath === "images" || relativePath.startsWith("images/");
426
- if (isInsideImagesFolder) {
427
- const imagesSubPath = relativePath.replace(/^images\/?/, "");
428
- const imagesPrefix = imagesSubPath ? `/${imagesSubPath}/` : "/";
429
- const allThumbnails = [];
430
- for (const [key, entry] of fileEntries) {
431
- if (_chunkVI6QG6WTjs.isProcessed.call(void 0, entry)) {
432
- const thumbnails = getExistingThumbnails(key, entry);
433
- for (const thumb of thumbnails) {
434
- allThumbnails.push({ ...thumb, originalKey: key });
435
- }
436
- }
437
- }
438
- for (const thumb of allThumbnails) {
439
- const thumbRelative = thumb.path.replace(/^\/images\/?/, "");
440
- const originalEntry = _optionalChain([fileEntries, 'access', _5 => _5.find, 'call', _6 => _6(([k]) => k === thumb.originalKey), 'optionalAccess', _7 => _7[1]]);
441
- const cdnIndex = _optionalChain([originalEntry, 'optionalAccess', _8 => _8.c]);
442
- const cdnBaseUrl = cdnIndex !== void 0 ? cdnUrls[cdnIndex] : void 0;
443
- const thumbnailUrl = cdnBaseUrl ? `${cdnBaseUrl}${thumb.path}` : thumb.path;
444
- const isPushedToCloud = cdnIndex !== void 0;
445
- const isRemote = isPushedToCloud && cdnBaseUrl !== r2PublicUrl;
446
- const thumbDims = _optionalChain([originalEntry, 'optionalAccess', _9 => _9[thumb.size]]);
447
- const dimensions = thumbDims ? { width: thumbDims.w, height: thumbDims.h } : void 0;
448
- if (imagesSubPath === "") {
449
- const slashIndex = thumbRelative.indexOf("/");
450
- if (slashIndex === -1) {
451
- const fileName = thumbRelative;
452
- items.push({
453
- name: fileName,
454
- path: `public/images/${fileName}`,
455
- type: "file",
456
- thumbnail: thumbnailUrl,
457
- hasThumbnail: false,
458
- isProtected: true,
459
- cdnPushed: isPushedToCloud,
460
- cdnBaseUrl,
461
- isRemote,
462
- dimensions
463
- });
464
- } else {
465
- const folderName = thumbRelative.slice(0, slashIndex);
466
- if (!seenFolders.has(folderName)) {
467
- seenFolders.add(folderName);
468
- const folderPrefix = `/${folderName}/`;
469
- const fileCount = allThumbnails.filter(
470
- (t) => t.path.replace(/^\/images/, "").startsWith(folderPrefix)
471
- ).length;
472
- items.push({
473
- name: folderName,
474
- path: `public/images/${folderName}`,
475
- type: "folder",
476
- fileCount,
477
- isProtected: true
478
- });
479
- }
480
- }
481
- } else {
482
- if (!thumbRelative.startsWith(imagesSubPath + "/") && thumbRelative !== imagesSubPath) continue;
483
- const remaining = thumbRelative.slice(imagesSubPath.length + 1);
484
- if (!remaining) continue;
485
- const slashIndex = remaining.indexOf("/");
486
- if (slashIndex === -1) {
487
- items.push({
488
- name: remaining,
489
- path: `public/images/${imagesSubPath}/${remaining}`,
490
- type: "file",
491
- thumbnail: thumbnailUrl,
492
- hasThumbnail: false,
493
- isProtected: true,
494
- cdnPushed: isPushedToCloud,
495
- cdnBaseUrl,
496
- isRemote,
497
- dimensions
498
- });
499
- } else {
500
- const folderName = remaining.slice(0, slashIndex);
501
- if (!seenFolders.has(folderName)) {
502
- seenFolders.add(folderName);
503
- const folderPrefix = `${imagesSubPath}/${folderName}/`;
504
- const fileCount = allThumbnails.filter(
505
- (t) => t.path.replace(/^\/images\//, "").startsWith(folderPrefix)
506
- ).length;
507
- items.push({
508
- name: folderName,
509
- path: `public/images/${imagesSubPath}/${folderName}`,
510
- type: "folder",
511
- fileCount,
512
- isProtected: true
513
- });
514
- }
515
- }
516
- }
517
- }
518
- return _server.NextResponse.json({ items });
519
- }
520
- const absoluteDir = getWorkspacePath(requestedPath);
521
- try {
522
- const dirEntries = await _fs.promises.readdir(absoluteDir, { withFileTypes: true });
523
- for (const entry of dirEntries) {
524
- if (entry.name.startsWith(".")) continue;
525
- if (entry.isDirectory()) {
526
- if (!seenFolders.has(entry.name)) {
527
- seenFolders.add(entry.name);
528
- const isImagesFolder = entry.name === "images" && !relativePath;
529
- const folderPath = relativePath ? `public/${relativePath}/${entry.name}` : `public/${entry.name}`;
530
- let fileCount = 0;
531
- let cloudCount = 0;
532
- let remoteCount = 0;
533
- let localCount = 0;
534
- if (isImagesFolder) {
535
- for (const [key, metaEntry] of fileEntries) {
536
- if (_chunkVI6QG6WTjs.isProcessed.call(void 0, metaEntry)) {
537
- fileCount += getExistingThumbnails(key, metaEntry).length;
538
- }
539
- }
540
- } else {
541
- const folderPrefix = pathPrefix === "/" ? `/${entry.name}/` : `${pathPrefix}${entry.name}/`;
542
- for (const k of metaKeys) {
543
- if (k.startsWith(folderPrefix)) fileCount++;
544
- }
545
- const counts = countFileTypes(folderPrefix, fileEntries, cdnUrls, r2PublicUrl);
546
- cloudCount = counts.cloudCount;
547
- remoteCount = counts.remoteCount;
548
- localCount = counts.localCount;
549
- }
550
- items.push({
551
- name: entry.name,
552
- path: folderPath,
553
- type: "folder",
554
- fileCount,
555
- cloudCount,
556
- remoteCount,
557
- localCount,
558
- isProtected: isImagesFolder
559
- });
560
- }
561
- }
562
- }
563
- } catch (e7) {
564
- }
565
- if (!relativePath && !seenFolders.has("images")) {
566
- let thumbnailCount = 0;
567
- for (const [key, entry] of fileEntries) {
568
- if (_chunkVI6QG6WTjs.isProcessed.call(void 0, entry)) {
569
- thumbnailCount += getExistingThumbnails(key, entry).length;
570
- }
571
- }
572
- if (thumbnailCount > 0) {
573
- items.push({
574
- name: "images",
575
- path: "public/images",
576
- type: "folder",
577
- fileCount: thumbnailCount,
578
- isProtected: true
579
- });
580
- }
581
- }
582
- if (fileEntries.length === 0 && items.length === 0) {
583
- return _server.NextResponse.json({ items: [], isEmpty: true });
584
- }
585
- for (const [key, entry] of fileEntries) {
586
- if (!key.startsWith(pathPrefix) && pathPrefix !== "/") continue;
587
- if (pathPrefix === "/" && !key.startsWith("/")) continue;
588
- const remaining = pathPrefix === "/" ? key.slice(1) : key.slice(pathPrefix.length);
589
- if (!remaining) continue;
590
- const slashIndex = remaining.indexOf("/");
591
- if (slashIndex !== -1) {
592
- const folderName = remaining.slice(0, slashIndex);
593
- if (!seenFolders.has(folderName)) {
594
- seenFolders.add(folderName);
595
- const folderPrefix = pathPrefix === "/" ? `/${folderName}/` : `${pathPrefix}${folderName}/`;
596
- let fileCount = 0;
597
- for (const k of metaKeys) {
598
- if (k.startsWith(folderPrefix)) fileCount++;
599
- }
600
- const counts = countFileTypes(folderPrefix, fileEntries, cdnUrls, r2PublicUrl);
601
- items.push({
602
- name: folderName,
603
- path: relativePath ? `public/${relativePath}/${folderName}` : `public/${folderName}`,
604
- type: "folder",
605
- fileCount,
606
- cloudCount: counts.cloudCount,
607
- remoteCount: counts.remoteCount,
608
- localCount: counts.localCount,
609
- isProtected: isInsideImagesFolder
610
- });
611
- }
612
- } else {
613
- const fileName = remaining;
614
- const isImage = isImageFile(fileName);
615
- const isPushedToCloud = entry.c !== void 0;
616
- const fileCdnUrl = isPushedToCloud && entry.c !== void 0 ? cdnUrls[entry.c] : void 0;
617
- const isRemote = isPushedToCloud && (!r2PublicUrl || fileCdnUrl !== r2PublicUrl);
618
- let thumbnail;
619
- let hasThumbnail = false;
620
- let fileSize;
621
- const entryIsProcessed = _chunkVI6QG6WTjs.isProcessed.call(void 0, entry);
622
- if (isImage && entryIsProcessed) {
623
- const thumbPath = _chunkVI6QG6WTjs.getThumbnailPath.call(void 0, key, "sm");
624
- if (isPushedToCloud && entry.c !== void 0) {
625
- const cdnUrl = cdnUrls[entry.c];
626
- if (cdnUrl) {
627
- thumbnail = `${cdnUrl}${thumbPath}`;
628
- hasThumbnail = true;
629
- }
630
- } else {
631
- const localThumbPath = getPublicPath(thumbPath);
632
- try {
633
- await _fs.promises.access(localThumbPath);
634
- thumbnail = thumbPath;
635
- hasThumbnail = true;
636
- } catch (e8) {
637
- thumbnail = key;
638
- hasThumbnail = false;
639
- }
640
- }
641
- } else if (isImage) {
642
- if (isPushedToCloud && entry.c !== void 0) {
643
- const cdnUrl = cdnUrls[entry.c];
644
- thumbnail = cdnUrl ? `${cdnUrl}${key}` : key;
645
- } else {
646
- thumbnail = key;
647
- }
648
- hasThumbnail = false;
649
- }
650
- if (!isPushedToCloud) {
651
- try {
652
- const filePath = getPublicPath(key);
653
- const stats = await _fs.promises.stat(filePath);
654
- fileSize = stats.size;
655
- } catch (e9) {
656
- }
657
- }
658
- items.push({
659
- name: fileName,
660
- path: relativePath ? `public/${relativePath}/${fileName}` : `public/${fileName}`,
661
- type: "file",
662
- size: fileSize,
663
- thumbnail,
664
- hasThumbnail,
665
- isProcessed: entryIsProcessed,
666
- cdnPushed: isPushedToCloud,
667
- cdnBaseUrl: fileCdnUrl,
668
- isRemote,
669
- isProtected: isInsideImagesFolder,
670
- dimensions: entry.o ? { width: entry.o.w, height: entry.o.h } : void 0
671
- });
672
- }
673
- }
674
- return _server.NextResponse.json({ items });
675
- } catch (error) {
676
- console.error("Failed to list directory:", error);
677
- return _server.NextResponse.json({ error: "Failed to list directory" }, { status: 500 });
678
- }
679
- }
680
- async function handleSearch(request) {
681
- const searchParams = request.nextUrl.searchParams;
682
- const query = _optionalChain([searchParams, 'access', _10 => _10.get, 'call', _11 => _11("q"), 'optionalAccess', _12 => _12.toLowerCase, 'call', _13 => _13()]) || "";
683
- if (query.length < 2) {
684
- return _server.NextResponse.json({ items: [] });
685
- }
686
- try {
687
- const meta = await loadMeta();
688
- const fileEntries = getFileEntries(meta);
689
- const cdnUrls = getCdnUrls(meta);
690
- const r2PublicUrl = _optionalChain([process, 'access', _14 => _14.env, 'access', _15 => _15.CLOUDFLARE_R2_PUBLIC_URL, 'optionalAccess', _16 => _16.replace, 'call', _17 => _17(/\/$/, "")]) || "";
691
- const items = [];
692
- for (const [key, entry] of fileEntries) {
693
- if (!key.toLowerCase().includes(query)) continue;
694
- const fileName = _path2.default.basename(key);
695
- const relativePath = key.slice(1);
696
- const isImage = isImageFile(fileName);
697
- const isPushedToCloud = entry.c !== void 0;
698
- const fileCdnUrl = isPushedToCloud && entry.c !== void 0 ? cdnUrls[entry.c] : void 0;
699
- const isRemote = isPushedToCloud && (!r2PublicUrl || fileCdnUrl !== r2PublicUrl);
700
- let thumbnail;
701
- let hasThumbnail = false;
702
- const entryIsProcessed = _chunkVI6QG6WTjs.isProcessed.call(void 0, entry);
703
- if (isImage && entryIsProcessed) {
704
- const thumbPath = _chunkVI6QG6WTjs.getThumbnailPath.call(void 0, key, "sm");
705
- if (isPushedToCloud && entry.c !== void 0) {
706
- const cdnUrl = cdnUrls[entry.c];
707
- if (cdnUrl) {
708
- thumbnail = `${cdnUrl}${thumbPath}`;
709
- hasThumbnail = true;
710
- }
711
- } else {
712
- const localThumbPath = getPublicPath(thumbPath);
713
- try {
714
- await _fs.promises.access(localThumbPath);
715
- thumbnail = thumbPath;
716
- hasThumbnail = true;
717
- } catch (e10) {
718
- thumbnail = key;
719
- hasThumbnail = false;
720
- }
721
- }
722
- } else if (isImage) {
723
- if (isPushedToCloud && entry.c !== void 0) {
724
- const cdnUrl = cdnUrls[entry.c];
725
- thumbnail = cdnUrl ? `${cdnUrl}${key}` : key;
726
- } else {
727
- thumbnail = key;
728
- }
729
- hasThumbnail = false;
730
- }
731
- items.push({
732
- name: fileName,
733
- path: `public/${relativePath}`,
734
- type: "file",
735
- thumbnail,
736
- hasThumbnail,
737
- isProcessed: entryIsProcessed,
738
- cdnPushed: isPushedToCloud,
739
- cdnBaseUrl: fileCdnUrl,
740
- isRemote,
741
- dimensions: entry.o ? { width: entry.o.w, height: entry.o.h } : void 0
742
- });
743
- }
744
- return _server.NextResponse.json({ items });
745
- } catch (error) {
746
- console.error("Failed to search:", error);
747
- return _server.NextResponse.json({ error: "Failed to search" }, { status: 500 });
748
- }
749
- }
750
- async function handleListFolders() {
751
- try {
752
- const meta = await loadMeta();
753
- const fileEntries = getFileEntries(meta);
754
- const folderSet = /* @__PURE__ */ new Set();
755
- for (const [key] of fileEntries) {
756
- const parts = key.split("/");
757
- let current = "";
758
- for (let i = 1; i < parts.length - 1; i++) {
759
- current = current ? `${current}/${parts[i]}` : parts[i];
760
- folderSet.add(current);
761
- }
762
- }
763
- async function scanDir(dir, relativePath) {
764
- try {
765
- const entries = await _fs.promises.readdir(dir, { withFileTypes: true });
766
- for (const entry of entries) {
767
- if (entry.isDirectory() && !entry.name.startsWith(".") && entry.name !== "images") {
768
- const folderRelPath = relativePath ? `${relativePath}/${entry.name}` : entry.name;
769
- folderSet.add(folderRelPath);
770
- await scanDir(_path2.default.join(dir, entry.name), folderRelPath);
771
- }
772
- }
773
- } catch (e11) {
774
- }
775
- }
776
- const publicDir = getPublicPath();
777
- await scanDir(publicDir, "");
778
- const folders = [];
779
- folders.push({ path: "public", name: "public", depth: 0 });
780
- const sortedFolders = Array.from(folderSet).sort();
781
- for (const folderPath of sortedFolders) {
782
- const depth = folderPath.split("/").length;
783
- const name = folderPath.split("/").pop() || folderPath;
784
- folders.push({
785
- path: `public/${folderPath}`,
786
- name,
787
- depth
788
- });
789
- }
790
- return _server.NextResponse.json({ folders });
791
- } catch (error) {
792
- console.error("Failed to list folders:", error);
793
- return _server.NextResponse.json({ error: "Failed to list folders" }, { status: 500 });
794
- }
795
- }
796
- async function handleCountImages() {
797
- try {
798
- const meta = await loadMeta();
799
- const fileEntries = getFileEntries(meta);
800
- const allImages = [];
801
- for (const [key] of fileEntries) {
802
- const fileName = _path2.default.basename(key);
803
- if (isImageFile(fileName)) {
804
- allImages.push(key.slice(1));
805
- }
806
- }
807
- return _server.NextResponse.json({
808
- count: allImages.length,
809
- images: allImages
810
- });
811
- } catch (error) {
812
- console.error("Failed to count images:", error);
813
- return _server.NextResponse.json({ error: "Failed to count images" }, { status: 500 });
814
- }
815
- }
816
- async function handleFolderImages(request) {
817
- try {
818
- const searchParams = request.nextUrl.searchParams;
819
- const foldersParam = searchParams.get("folders");
820
- if (!foldersParam) {
821
- return _server.NextResponse.json({ error: "No folders provided" }, { status: 400 });
822
- }
823
- const folders = foldersParam.split(",");
824
- const meta = await loadMeta();
825
- const fileEntries = getFileEntries(meta);
826
- const allFiles = [];
827
- const prefixes = folders.map((f) => {
828
- const rel = f.replace(/^public\/?/, "");
829
- return rel ? `/${rel}/` : "/";
830
- });
831
- for (const [key] of fileEntries) {
832
- for (const prefix of prefixes) {
833
- if (key.startsWith(prefix) || prefix === "/" && key.startsWith("/")) {
834
- allFiles.push(key.slice(1));
835
- break;
836
- }
837
- }
838
- }
839
- return _server.NextResponse.json({
840
- count: allFiles.length,
841
- images: allFiles
842
- // Keep as 'images' for backwards compatibility
843
- });
844
- } catch (error) {
845
- console.error("Failed to get folder files:", error);
846
- return _server.NextResponse.json({ error: "Failed to get folder files" }, { status: 500 });
847
- }
848
- }
849
-
850
- // src/handlers/files.ts
851
-
852
-
853
-
854
-
855
- async function handleUpload(request) {
856
- try {
857
- const formData = await request.formData();
858
- const file = formData.get("file");
859
- const targetPath = formData.get("path") || "public";
860
- if (!file) {
861
- return _server.NextResponse.json({ error: "No file provided" }, { status: 400 });
862
- }
863
- const bytes = await file.arrayBuffer();
864
- const buffer = Buffer.from(bytes);
865
- const fileName = file.name;
866
- const ext = _path2.default.extname(fileName).toLowerCase();
867
- const isImage = isImageFile(fileName);
868
- const isMedia = isMediaFile(fileName);
869
- const meta = await loadMeta();
870
- let relativeDir = "";
871
- if (targetPath === "public") {
872
- relativeDir = "";
873
- } else if (targetPath.startsWith("public/")) {
874
- relativeDir = targetPath.replace("public/", "");
875
- }
876
- if (relativeDir === "images" || relativeDir.startsWith("images/")) {
877
- return _server.NextResponse.json(
878
- { error: "Cannot upload to images/ folder. Upload to public/ instead - thumbnails are generated automatically." },
879
- { status: 400 }
880
- );
881
- }
882
- let imageKey = "/" + (relativeDir ? `${relativeDir}/${fileName}` : fileName);
883
- if (meta[imageKey]) {
884
- const baseName = _path2.default.basename(fileName, ext);
885
- let counter = 1;
886
- let newFileName = `${baseName}-${counter}${ext}`;
887
- let newKey = "/" + (relativeDir ? `${relativeDir}/${newFileName}` : newFileName);
888
- while (meta[newKey]) {
889
- counter++;
890
- newFileName = `${baseName}-${counter}${ext}`;
891
- newKey = "/" + (relativeDir ? `${relativeDir}/${newFileName}` : newFileName);
892
- }
893
- imageKey = newKey;
894
- }
895
- const actualFileName = _path2.default.basename(imageKey);
896
- const uploadDir = getPublicPath(relativeDir);
897
- await _fs.promises.mkdir(uploadDir, { recursive: true });
898
- await _fs.promises.writeFile(_path2.default.join(uploadDir, actualFileName), buffer);
899
- if (!isMedia) {
900
- return _server.NextResponse.json({
901
- success: true,
902
- message: "File uploaded (not a media file)",
903
- path: `public/${relativeDir ? relativeDir + "/" : ""}${actualFileName}`
904
- });
905
- }
906
- if (isImage && ext !== ".svg") {
907
- try {
908
- const metadata = await _sharp2.default.call(void 0, buffer).metadata();
909
- meta[imageKey] = {
910
- o: { w: metadata.width || 0, h: metadata.height || 0 }
911
- };
912
- } catch (e12) {
913
- meta[imageKey] = { o: { w: 0, h: 0 } };
914
- }
915
- } else {
916
- meta[imageKey] = {};
917
- }
918
- await saveMeta(meta);
919
- return _server.NextResponse.json({
920
- success: true,
921
- imageKey,
922
- message: 'File uploaded. Run "Process Images" to generate thumbnails.'
923
- });
924
- } catch (error) {
925
- console.error("Failed to upload:", error);
926
- const message = error instanceof Error ? error.message : "Unknown error";
927
- return _server.NextResponse.json({ error: `Failed to upload file: ${message}` }, { status: 500 });
928
- }
929
- }
930
- async function handleDelete(request) {
931
- try {
932
- const { paths } = await request.json();
933
- if (!paths || !Array.isArray(paths) || paths.length === 0) {
934
- return _server.NextResponse.json({ error: "No paths provided" }, { status: 400 });
935
- }
936
- const meta = await loadMeta();
937
- const deleted = [];
938
- const errors = [];
939
- for (const itemPath of paths) {
940
- try {
941
- if (!itemPath.startsWith("public/")) {
942
- errors.push(`Invalid path: ${itemPath}`);
943
- continue;
944
- }
945
- const absolutePath = getWorkspacePath(itemPath);
946
- const imageKey = "/" + itemPath.replace(/^public\//, "");
947
- const entry = meta[imageKey];
948
- const isPushedToCloud = _optionalChain([entry, 'optionalAccess', _18 => _18.c]) !== void 0;
949
- try {
950
- const stats = await _fs.promises.stat(absolutePath);
951
- if (stats.isDirectory()) {
952
- await _fs.promises.rm(absolutePath, { recursive: true });
953
- const prefix = imageKey + "/";
954
- for (const key of Object.keys(meta)) {
955
- if (key.startsWith(prefix) || key === imageKey) {
956
- const keyEntry = meta[key];
957
- if (keyEntry && keyEntry.c === void 0) {
958
- for (const thumbPath of _chunkVI6QG6WTjs.getAllThumbnailPaths.call(void 0, key)) {
959
- const absoluteThumbPath = getPublicPath(thumbPath);
960
- try {
961
- await _fs.promises.unlink(absoluteThumbPath);
962
- } catch (e13) {
963
- }
964
- }
965
- }
966
- delete meta[key];
967
- }
968
- }
969
- } else {
970
- await _fs.promises.unlink(absolutePath);
971
- const isInImagesFolder = itemPath.startsWith("public/images/");
972
- if (!isInImagesFolder && entry) {
973
- if (!isPushedToCloud) {
974
- for (const thumbPath of _chunkVI6QG6WTjs.getAllThumbnailPaths.call(void 0, imageKey)) {
975
- const absoluteThumbPath = getPublicPath(thumbPath);
976
- try {
977
- await _fs.promises.unlink(absoluteThumbPath);
978
- } catch (e14) {
979
- }
980
- }
981
- }
982
- delete meta[imageKey];
983
- }
984
- }
985
- } catch (e15) {
986
- if (entry) {
987
- delete meta[imageKey];
988
- } else {
989
- const prefix = imageKey + "/";
990
- let foundAny = false;
991
- for (const key of Object.keys(meta)) {
992
- if (key.startsWith(prefix)) {
993
- delete meta[key];
994
- foundAny = true;
995
- }
996
- }
997
- if (!foundAny) {
998
- errors.push(`Not found: ${itemPath}`);
999
- continue;
1000
- }
1001
- }
1002
- }
1003
- deleted.push(itemPath);
1004
- } catch (error) {
1005
- console.error(`Failed to delete ${itemPath}:`, error);
1006
- errors.push(itemPath);
1007
- }
1008
- }
1009
- await saveMeta(meta);
1010
- return _server.NextResponse.json({
1011
- success: true,
1012
- deleted,
1013
- errors: errors.length > 0 ? errors : void 0
1014
- });
1015
- } catch (error) {
1016
- console.error("Failed to delete:", error);
1017
- return _server.NextResponse.json({ error: "Failed to delete files" }, { status: 500 });
1018
- }
1019
- }
1020
- async function handleCreateFolder(request) {
1021
- try {
1022
- const { parentPath, name } = await request.json();
1023
- if (!name || typeof name !== "string") {
1024
- return _server.NextResponse.json({ error: "Folder name is required" }, { status: 400 });
1025
- }
1026
- const sanitizedName = name.replace(/[<>:"/\\|?*]/g, "").trim();
1027
- if (!sanitizedName) {
1028
- return _server.NextResponse.json({ error: "Invalid folder name" }, { status: 400 });
1029
- }
1030
- const safePath = (parentPath || "public").replace(/\.\./g, "");
1031
- const folderPath = getWorkspacePath(safePath, sanitizedName);
1032
- if (!folderPath.startsWith(getPublicPath())) {
1033
- return _server.NextResponse.json({ error: "Invalid path" }, { status: 400 });
1034
- }
1035
- try {
1036
- await _fs.promises.access(folderPath);
1037
- return _server.NextResponse.json({ error: "A folder with this name already exists" }, { status: 400 });
1038
- } catch (e16) {
1039
- }
1040
- await _fs.promises.mkdir(folderPath, { recursive: true });
1041
- return _server.NextResponse.json({ success: true, path: _path2.default.join(safePath, sanitizedName) });
1042
- } catch (error) {
1043
- console.error("Failed to create folder:", error);
1044
- return _server.NextResponse.json({ error: "Failed to create folder" }, { status: 500 });
1045
- }
1046
- }
1047
- async function handleRename(request) {
1048
- try {
1049
- const { oldPath, newName } = await request.json();
1050
- if (!oldPath || !newName) {
1051
- return _server.NextResponse.json({ error: "Path and new name are required" }, { status: 400 });
1052
- }
1053
- const sanitizedName = newName.replace(/[<>:"/\\|?*]/g, "").trim();
1054
- if (!sanitizedName) {
1055
- return _server.NextResponse.json({ error: "Invalid name" }, { status: 400 });
1056
- }
1057
- const safePath = oldPath.replace(/\.\./g, "");
1058
- const absoluteOldPath = getWorkspacePath(safePath);
1059
- const parentDir = _path2.default.dirname(absoluteOldPath);
1060
- const absoluteNewPath = _path2.default.join(parentDir, sanitizedName);
1061
- if (!absoluteOldPath.startsWith(getPublicPath())) {
1062
- return _server.NextResponse.json({ error: "Invalid path" }, { status: 400 });
1063
- }
1064
- try {
1065
- await _fs.promises.access(absoluteOldPath);
1066
- } catch (e17) {
1067
- return _server.NextResponse.json({ error: "File or folder not found" }, { status: 404 });
1068
- }
1069
- try {
1070
- await _fs.promises.access(absoluteNewPath);
1071
- return _server.NextResponse.json({ error: "An item with this name already exists" }, { status: 400 });
1072
- } catch (e18) {
1073
- }
1074
- const stats = await _fs.promises.stat(absoluteOldPath);
1075
- const isFile = stats.isFile();
1076
- const isImage = isFile && isImageFile(_path2.default.basename(oldPath));
1077
- await _fs.promises.rename(absoluteOldPath, absoluteNewPath);
1078
- if (isImage) {
1079
- const meta = await loadMeta();
1080
- const oldRelativePath = safePath.replace(/^public\//, "");
1081
- const newRelativePath = _path2.default.join(_path2.default.dirname(oldRelativePath), sanitizedName);
1082
- const oldKey = "/" + oldRelativePath;
1083
- const newKey = "/" + newRelativePath;
1084
- if (meta[oldKey]) {
1085
- const entry = meta[oldKey];
1086
- const oldThumbPaths = _chunkVI6QG6WTjs.getAllThumbnailPaths.call(void 0, oldKey);
1087
- const newThumbPaths = _chunkVI6QG6WTjs.getAllThumbnailPaths.call(void 0, newKey);
1088
- for (let i = 0; i < oldThumbPaths.length; i++) {
1089
- const oldThumbPath = getPublicPath(oldThumbPaths[i]);
1090
- const newThumbPath = getPublicPath(newThumbPaths[i]);
1091
- await _fs.promises.mkdir(_path2.default.dirname(newThumbPath), { recursive: true });
1092
- try {
1093
- await _fs.promises.rename(oldThumbPath, newThumbPath);
1094
- } catch (e19) {
1095
- }
1096
- }
1097
- delete meta[oldKey];
1098
- meta[newKey] = entry;
1099
- }
1100
- await saveMeta(meta);
1101
- }
1102
- const newPath = _path2.default.join(_path2.default.dirname(safePath), sanitizedName);
1103
- return _server.NextResponse.json({ success: true, newPath });
1104
- } catch (error) {
1105
- console.error("Failed to rename:", error);
1106
- return _server.NextResponse.json({ error: "Failed to rename" }, { status: 500 });
1107
- }
1108
- }
1109
- async function handleMoveStream(request) {
1110
- const encoder = new TextEncoder();
1111
- const stream = new ReadableStream({
1112
- async start(controller) {
1113
- const sendEvent = (data) => {
1114
- controller.enqueue(encoder.encode(`data: ${JSON.stringify(data)}
1115
-
1116
- `));
1117
- };
1118
- try {
1119
- const { paths, destination } = await request.json();
1120
- if (!paths || !Array.isArray(paths) || paths.length === 0) {
1121
- sendEvent({ type: "error", message: "Paths are required" });
1122
- controller.close();
1123
- return;
1124
- }
1125
- if (!destination || typeof destination !== "string") {
1126
- sendEvent({ type: "error", message: "Destination is required" });
1127
- controller.close();
1128
- return;
1129
- }
1130
- const safeDestination = destination.replace(/\.\./g, "");
1131
- const absoluteDestination = getWorkspacePath(safeDestination);
1132
- if (!absoluteDestination.startsWith(getPublicPath())) {
1133
- sendEvent({ type: "error", message: "Invalid destination" });
1134
- controller.close();
1135
- return;
1136
- }
1137
- await _fs.promises.mkdir(absoluteDestination, { recursive: true });
1138
- const meta = await loadMeta();
1139
- const cdnUrls = getCdnUrls(meta);
1140
- const r2PublicUrl = _optionalChain([process, 'access', _19 => _19.env, 'access', _20 => _20.CLOUDFLARE_R2_PUBLIC_URL, 'optionalAccess', _21 => _21.replace, 'call', _22 => _22(/\/$/, "")]) || "";
1141
- const moved = [];
1142
- const errors = [];
1143
- const total = paths.length;
1144
- sendEvent({ type: "start", total });
1145
- for (let i = 0; i < paths.length; i++) {
1146
- const itemPath = paths[i];
1147
- const safePath = itemPath.replace(/\.\./g, "");
1148
- const itemName = _path2.default.basename(safePath);
1149
- const newAbsolutePath = _path2.default.join(absoluteDestination, itemName);
1150
- const oldRelativePath = safePath.replace(/^public\//, "");
1151
- const newRelativePath = _path2.default.join(safeDestination.replace(/^public\//, ""), itemName);
1152
- const oldKey = "/" + oldRelativePath;
1153
- const newKey = "/" + newRelativePath;
1154
- sendEvent({
1155
- type: "progress",
1156
- current: i + 1,
1157
- total,
1158
- percent: Math.round((i + 1) / total * 100),
1159
- currentFile: itemName
1160
- });
1161
- if (meta[newKey]) {
1162
- errors.push(`${itemName} already exists in destination`);
1163
- continue;
1164
- }
1165
- const entry = meta[oldKey];
1166
- const isImage = isImageFile(itemName);
1167
- const isInCloud = _optionalChain([entry, 'optionalAccess', _23 => _23.c]) !== void 0;
1168
- const fileCdnUrl = isInCloud && entry.c !== void 0 ? cdnUrls[entry.c] : void 0;
1169
- const isRemote = isInCloud && (!r2PublicUrl || fileCdnUrl !== r2PublicUrl);
1170
- const isPushedToR2 = isInCloud && r2PublicUrl && fileCdnUrl === r2PublicUrl;
1171
- const hasProcessedThumbnails = _chunkVI6QG6WTjs.isProcessed.call(void 0, entry);
1172
- try {
1173
- if (isRemote && isImage) {
1174
- const remoteUrl = `${fileCdnUrl}${oldKey}`;
1175
- const buffer = await downloadFromRemoteUrl(remoteUrl);
1176
- await _fs.promises.mkdir(_path2.default.dirname(newAbsolutePath), { recursive: true });
1177
- await _fs.promises.writeFile(newAbsolutePath, buffer);
1178
- const newEntry = {
1179
- o: _optionalChain([entry, 'optionalAccess', _24 => _24.o]),
1180
- b: _optionalChain([entry, 'optionalAccess', _25 => _25.b])
1181
- };
1182
- delete meta[oldKey];
1183
- meta[newKey] = newEntry;
1184
- moved.push(itemPath);
1185
- } else if (isPushedToR2 && isImage) {
1186
- const buffer = await downloadFromCdn(oldKey);
1187
- await _fs.promises.mkdir(_path2.default.dirname(newAbsolutePath), { recursive: true });
1188
- await _fs.promises.writeFile(newAbsolutePath, buffer);
1189
- let newEntry = {
1190
- o: _optionalChain([entry, 'optionalAccess', _26 => _26.o]),
1191
- b: _optionalChain([entry, 'optionalAccess', _27 => _27.b])
1192
- };
1193
- if (hasProcessedThumbnails) {
1194
- const processedEntry = await processImage(buffer, newKey);
1195
- newEntry = { ...newEntry, ...processedEntry };
1196
- }
1197
- await uploadOriginalToCdn(newKey);
1198
- if (hasProcessedThumbnails) {
1199
- await uploadToCdn(newKey);
1200
- }
1201
- await deleteFromCdn(oldKey, hasProcessedThumbnails);
1202
- try {
1203
- await _fs.promises.unlink(newAbsolutePath);
1204
- } catch (e20) {
1205
- }
1206
- if (hasProcessedThumbnails) {
1207
- await deleteLocalThumbnails(newKey);
1208
- }
1209
- newEntry.c = _optionalChain([entry, 'optionalAccess', _28 => _28.c]);
1210
- delete meta[oldKey];
1211
- meta[newKey] = newEntry;
1212
- moved.push(itemPath);
1213
- } else {
1214
- const absolutePath = getWorkspacePath(safePath);
1215
- if (absoluteDestination.startsWith(absolutePath + _path2.default.sep)) {
1216
- errors.push(`Cannot move ${itemName} into itself`);
1217
- continue;
1218
- }
1219
- try {
1220
- await _fs.promises.access(absolutePath);
1221
- } catch (e21) {
1222
- errors.push(`${itemName} not found`);
1223
- continue;
1224
- }
1225
- try {
1226
- await _fs.promises.access(newAbsolutePath);
1227
- errors.push(`${itemName} already exists in destination`);
1228
- continue;
1229
- } catch (e22) {
1230
- }
1231
- await _fs.promises.rename(absolutePath, newAbsolutePath);
1232
- const stats = await _fs.promises.stat(newAbsolutePath);
1233
- if (stats.isFile() && isImage && entry) {
1234
- const oldThumbPaths = _chunkVI6QG6WTjs.getAllThumbnailPaths.call(void 0, oldKey);
1235
- const newThumbPaths = _chunkVI6QG6WTjs.getAllThumbnailPaths.call(void 0, newKey);
1236
- for (let j = 0; j < oldThumbPaths.length; j++) {
1237
- const oldThumbPath = getPublicPath(oldThumbPaths[j]);
1238
- const newThumbPath = getPublicPath(newThumbPaths[j]);
1239
- await _fs.promises.mkdir(_path2.default.dirname(newThumbPath), { recursive: true });
1240
- try {
1241
- await _fs.promises.rename(oldThumbPath, newThumbPath);
1242
- } catch (e23) {
1243
- }
1244
- }
1245
- delete meta[oldKey];
1246
- meta[newKey] = entry;
1247
- } else if (stats.isDirectory()) {
1248
- const oldPrefix = oldKey + "/";
1249
- const newPrefix = newKey + "/";
1250
- for (const key of Object.keys(meta)) {
1251
- if (key.startsWith(oldPrefix)) {
1252
- const newMetaKey = newPrefix + key.slice(oldPrefix.length);
1253
- meta[newMetaKey] = meta[key];
1254
- delete meta[key];
1255
- }
1256
- }
1257
- }
1258
- moved.push(itemPath);
1259
- }
1260
- } catch (err) {
1261
- console.error(`Failed to move ${itemName}:`, err);
1262
- errors.push(`Failed to move ${itemName}`);
1263
- }
1264
- }
1265
- await saveMeta(meta);
1266
- sendEvent({
1267
- type: "complete",
1268
- moved: moved.length,
1269
- errors: errors.length,
1270
- errorMessages: errors
1271
- });
1272
- } catch (error) {
1273
- console.error("Failed to move:", error);
1274
- sendEvent({ type: "error", message: "Failed to move items" });
1275
- } finally {
1276
- controller.close();
1277
- }
1278
- }
1279
- });
1280
- return new Response(stream, {
1281
- headers: {
1282
- "Content-Type": "text/event-stream",
1283
- "Cache-Control": "no-cache",
1284
- "Connection": "keep-alive"
1285
- }
1286
- });
1287
- }
1288
-
1289
- // src/handlers/images.ts
1290
-
1291
-
1292
-
1293
-
1294
- async function handleSync(request) {
1295
- const accountId = process.env.CLOUDFLARE_R2_ACCOUNT_ID;
1296
- const accessKeyId = process.env.CLOUDFLARE_R2_ACCESS_KEY_ID;
1297
- const secretAccessKey = process.env.CLOUDFLARE_R2_SECRET_ACCESS_KEY;
1298
- const bucketName = process.env.CLOUDFLARE_R2_BUCKET_NAME;
1299
- const publicUrl = _optionalChain([process, 'access', _29 => _29.env, 'access', _30 => _30.CLOUDFLARE_R2_PUBLIC_URL, 'optionalAccess', _31 => _31.replace, 'call', _32 => _32(/\/\s*$/, "")]);
1300
- if (!accountId || !accessKeyId || !secretAccessKey || !bucketName || !publicUrl) {
1301
- return _server.NextResponse.json(
1302
- { error: "R2 not configured. Set CLOUDFLARE_R2_* environment variables." },
1303
- { status: 400 }
1304
- );
1305
- }
1306
- try {
1307
- const { imageKeys } = await request.json();
1308
- if (!imageKeys || !Array.isArray(imageKeys) || imageKeys.length === 0) {
1309
- return _server.NextResponse.json({ error: "No image keys provided" }, { status: 400 });
1310
- }
1311
- const meta = await loadMeta();
1312
- const cdnUrls = getCdnUrls(meta);
1313
- const cdnIndex = getOrAddCdnIndex(meta, publicUrl);
1314
- const r2 = new (0, _clients3.S3Client)({
1315
- region: "auto",
1316
- endpoint: `https://${accountId}.r2.cloudflarestorage.com`,
1317
- credentials: { accessKeyId, secretAccessKey }
1318
- });
1319
- const pushed = [];
1320
- const errors = [];
1321
- const urlsToPurge = [];
1322
- for (let imageKey of imageKeys) {
1323
- if (!imageKey.startsWith("/")) {
1324
- imageKey = `/${imageKey}`;
1325
- }
1326
- const entry = getMetaEntry(meta, imageKey);
1327
- if (!entry) {
1328
- errors.push(`Image not found in meta: ${imageKey}. Run Scan first.`);
1329
- continue;
1330
- }
1331
- const existingCdnUrl = entry.c !== void 0 ? cdnUrls[entry.c] : void 0;
1332
- const isAlreadyInOurR2 = existingCdnUrl === publicUrl;
1333
- if (isAlreadyInOurR2) {
1334
- pushed.push(imageKey);
1335
- continue;
1336
- }
1337
- const isRemote = entry.c !== void 0 && existingCdnUrl !== publicUrl;
1338
- try {
1339
- let originalBuffer;
1340
- if (isRemote) {
1341
- const remoteUrl = `${existingCdnUrl}${imageKey}`;
1342
- originalBuffer = await downloadFromRemoteUrl(remoteUrl);
1343
- } else {
1344
- const originalLocalPath = getPublicPath(imageKey);
1345
- try {
1346
- originalBuffer = await _fs.promises.readFile(originalLocalPath);
1347
- } catch (e24) {
1348
- errors.push(`Original file not found: ${imageKey}`);
1349
- continue;
1350
- }
1351
- }
1352
- await r2.send(
1353
- new (0, _clients3.PutObjectCommand)({
1354
- Bucket: bucketName,
1355
- Key: imageKey.replace(/^\//, ""),
1356
- Body: originalBuffer,
1357
- ContentType: getContentType(imageKey)
1358
- })
1359
- );
1360
- urlsToPurge.push(`${publicUrl}${imageKey}`);
1361
- if (!isRemote && _chunkVI6QG6WTjs.isProcessed.call(void 0, entry)) {
1362
- for (const thumbPath of _chunkVI6QG6WTjs.getAllThumbnailPaths.call(void 0, imageKey)) {
1363
- const localPath = getPublicPath(thumbPath);
1364
- try {
1365
- const fileBuffer = await _fs.promises.readFile(localPath);
1366
- await r2.send(
1367
- new (0, _clients3.PutObjectCommand)({
1368
- Bucket: bucketName,
1369
- Key: thumbPath.replace(/^\//, ""),
1370
- Body: fileBuffer,
1371
- ContentType: getContentType(thumbPath)
1372
- })
1373
- );
1374
- urlsToPurge.push(`${publicUrl}${thumbPath}`);
1375
- } catch (e25) {
1376
- }
1377
- }
1378
- }
1379
- entry.c = cdnIndex;
1380
- if (!isRemote) {
1381
- const originalLocalPath = getPublicPath(imageKey);
1382
- for (const thumbPath of _chunkVI6QG6WTjs.getAllThumbnailPaths.call(void 0, imageKey)) {
1383
- const localPath = getPublicPath(thumbPath);
1384
- try {
1385
- await _fs.promises.unlink(localPath);
1386
- } catch (e26) {
1387
- }
1388
- }
1389
- try {
1390
- await _fs.promises.unlink(originalLocalPath);
1391
- } catch (e27) {
1392
- }
1393
- }
1394
- pushed.push(imageKey);
1395
- } catch (error) {
1396
- console.error(`Failed to push ${imageKey}:`, error);
1397
- errors.push(`Failed to push: ${imageKey}`);
1398
- }
1399
- }
1400
- await saveMeta(meta);
1401
- if (urlsToPurge.length > 0) {
1402
- await purgeCloudflareCache(urlsToPurge);
1403
- }
1404
- return _server.NextResponse.json({
1405
- success: true,
1406
- pushed,
1407
- errors: errors.length > 0 ? errors : void 0
1408
- });
1409
- } catch (error) {
1410
- console.error("Failed to push:", error);
1411
- return _server.NextResponse.json({ error: "Failed to push to CDN" }, { status: 500 });
1412
- }
1413
- }
1414
- async function handleReprocess(request) {
1415
- const publicUrl = _optionalChain([process, 'access', _33 => _33.env, 'access', _34 => _34.CLOUDFLARE_R2_PUBLIC_URL, 'optionalAccess', _35 => _35.replace, 'call', _36 => _36(/\/\s*$/, "")]);
1416
- try {
1417
- const { imageKeys } = await request.json();
1418
- if (!imageKeys || !Array.isArray(imageKeys) || imageKeys.length === 0) {
1419
- return _server.NextResponse.json({ error: "No image keys provided" }, { status: 400 });
1420
- }
1421
- const meta = await loadMeta();
1422
- const cdnUrls = getCdnUrls(meta);
1423
- const processed = [];
1424
- const errors = [];
1425
- const urlsToPurge = [];
1426
- for (let imageKey of imageKeys) {
1427
- if (!imageKey.startsWith("/")) {
1428
- imageKey = `/${imageKey}`;
1429
- }
1430
- try {
1431
- let buffer;
1432
- const entry = getMetaEntry(meta, imageKey);
1433
- const existingCdnIndex = _optionalChain([entry, 'optionalAccess', _37 => _37.c]);
1434
- const existingCdnUrl = existingCdnIndex !== void 0 ? cdnUrls[existingCdnIndex] : void 0;
1435
- const isInOurR2 = existingCdnUrl === publicUrl;
1436
- const isRemote = existingCdnIndex !== void 0 && !isInOurR2;
1437
- const originalPath = getPublicPath(imageKey);
1438
- try {
1439
- buffer = await _fs.promises.readFile(originalPath);
1440
- } catch (e28) {
1441
- if (isInOurR2) {
1442
- buffer = await downloadFromCdn(imageKey);
1443
- const dir = _path2.default.dirname(originalPath);
1444
- await _fs.promises.mkdir(dir, { recursive: true });
1445
- await _fs.promises.writeFile(originalPath, buffer);
1446
- } else if (isRemote && existingCdnUrl) {
1447
- const remoteUrl = `${existingCdnUrl}${imageKey}`;
1448
- buffer = await downloadFromRemoteUrl(remoteUrl);
1449
- const dir = _path2.default.dirname(originalPath);
1450
- await _fs.promises.mkdir(dir, { recursive: true });
1451
- await _fs.promises.writeFile(originalPath, buffer);
1452
- } else {
1453
- throw new Error(`File not found: ${imageKey}`);
1454
- }
1455
- }
1456
- const updatedEntry = await processImage(buffer, imageKey);
1457
- if (isInOurR2) {
1458
- updatedEntry.c = existingCdnIndex;
1459
- await uploadToCdn(imageKey);
1460
- for (const thumbPath of _chunkVI6QG6WTjs.getAllThumbnailPaths.call(void 0, imageKey)) {
1461
- urlsToPurge.push(`${publicUrl}${thumbPath}`);
1462
- }
1463
- await deleteLocalThumbnails(imageKey);
1464
- try {
1465
- await _fs.promises.unlink(originalPath);
1466
- } catch (e29) {
1467
- }
1468
- } else if (isRemote) {
1469
- }
1470
- meta[imageKey] = updatedEntry;
1471
- processed.push(imageKey);
1472
- } catch (error) {
1473
- console.error(`Failed to reprocess ${imageKey}:`, error);
1474
- errors.push(imageKey);
1475
- }
1476
- }
1477
- await saveMeta(meta);
1478
- if (urlsToPurge.length > 0) {
1479
- await purgeCloudflareCache(urlsToPurge);
1480
- }
1481
- return _server.NextResponse.json({
1482
- success: true,
1483
- processed,
1484
- errors: errors.length > 0 ? errors : void 0
1485
- });
1486
- } catch (error) {
1487
- console.error("Failed to reprocess:", error);
1488
- return _server.NextResponse.json({ error: "Failed to reprocess images" }, { status: 500 });
1489
- }
1490
- }
1491
- async function handleUnprocessStream(request) {
1492
- const publicUrl = _optionalChain([process, 'access', _38 => _38.env, 'access', _39 => _39.CLOUDFLARE_R2_PUBLIC_URL, 'optionalAccess', _40 => _40.replace, 'call', _41 => _41(/\/\s*$/, "")]);
1493
- const encoder = new TextEncoder();
1494
- let imageKeys;
1495
- try {
1496
- const body = await request.json();
1497
- imageKeys = body.imageKeys;
1498
- if (!imageKeys || !Array.isArray(imageKeys) || imageKeys.length === 0) {
1499
- return _server.NextResponse.json({ error: "No image keys provided" }, { status: 400 });
1500
- }
1501
- } catch (e30) {
1502
- return _server.NextResponse.json({ error: "Invalid request body" }, { status: 400 });
1503
- }
1504
- const stream = new ReadableStream({
1505
- async start(controller) {
1506
- const sendEvent = (data) => {
1507
- controller.enqueue(encoder.encode(`data: ${JSON.stringify(data)}
1508
-
1509
- `));
1510
- };
1511
- try {
1512
- const meta = await loadMeta();
1513
- const cdnUrls = getCdnUrls(meta);
1514
- const removed = [];
1515
- const skipped = [];
1516
- const errors = [];
1517
- const urlsToPurge = [];
1518
- const total = imageKeys.length;
1519
- sendEvent({ type: "start", total });
1520
- for (let i = 0; i < imageKeys.length; i++) {
1521
- let imageKey = imageKeys[i];
1522
- if (!imageKey.startsWith("/")) {
1523
- imageKey = `/${imageKey}`;
1524
- }
1525
- sendEvent({
1526
- type: "progress",
1527
- current: i + 1,
1528
- total,
1529
- percent: Math.round((i + 1) / total * 100),
1530
- message: `Removing thumbnails for ${imageKey.slice(1)}...`
1531
- });
1532
- try {
1533
- const entry = getMetaEntry(meta, imageKey);
1534
- if (!entry) {
1535
- errors.push(imageKey);
1536
- continue;
1537
- }
1538
- const hasThumbnails = entry.sm || entry.md || entry.lg || entry.f;
1539
- if (!hasThumbnails) {
1540
- skipped.push(imageKey);
1541
- continue;
1542
- }
1543
- const existingCdnIndex = entry.c;
1544
- const existingCdnUrl = existingCdnIndex !== void 0 ? cdnUrls[existingCdnIndex] : void 0;
1545
- const isInOurR2 = existingCdnUrl === publicUrl;
1546
- await deleteLocalThumbnails(imageKey);
1547
- if (isInOurR2) {
1548
- await deleteThumbnailsFromCdn(imageKey);
1549
- for (const thumbPath of _chunkVI6QG6WTjs.getAllThumbnailPaths.call(void 0, imageKey)) {
1550
- urlsToPurge.push(`${publicUrl}${thumbPath}`);
1551
- }
1552
- }
1553
- meta[imageKey] = {
1554
- o: entry.o,
1555
- b: entry.b,
1556
- ...entry.c !== void 0 ? { c: entry.c } : {}
1557
- };
1558
- removed.push(imageKey);
1559
- } catch (error) {
1560
- console.error(`Failed to unprocess ${imageKey}:`, error);
1561
- errors.push(imageKey);
1562
- }
1563
- }
1564
- sendEvent({ type: "cleanup", message: "Saving metadata..." });
1565
- await saveMeta(meta);
1566
- if (urlsToPurge.length > 0) {
1567
- sendEvent({ type: "cleanup", message: "Purging CDN cache..." });
1568
- await purgeCloudflareCache(urlsToPurge);
1569
- }
1570
- let message = `Removed thumbnails from ${removed.length} image${removed.length !== 1 ? "s" : ""}.`;
1571
- if (skipped.length > 0) {
1572
- message += ` ${skipped.length} image${skipped.length !== 1 ? "s" : ""} had no thumbnails.`;
1573
- }
1574
- if (errors.length > 0) {
1575
- message += ` ${errors.length} image${errors.length !== 1 ? "s" : ""} failed.`;
1576
- }
1577
- sendEvent({
1578
- type: "complete",
1579
- processed: removed.length,
1580
- skipped: skipped.length,
1581
- errors: errors.length,
1582
- message
1583
- });
1584
- controller.close();
1585
- } catch (error) {
1586
- console.error("Unprocess stream error:", error);
1587
- sendEvent({ type: "error", message: "Failed to remove thumbnails" });
1588
- controller.close();
1589
- }
1590
- }
1591
- });
1592
- return new Response(stream, {
1593
- headers: {
1594
- "Content-Type": "text/event-stream",
1595
- "Cache-Control": "no-cache",
1596
- Connection: "keep-alive"
1597
- }
1598
- });
1599
- }
1600
- async function handleReprocessStream(request) {
1601
- const publicUrl = _optionalChain([process, 'access', _42 => _42.env, 'access', _43 => _43.CLOUDFLARE_R2_PUBLIC_URL, 'optionalAccess', _44 => _44.replace, 'call', _45 => _45(/\/\s*$/, "")]);
1602
- const encoder = new TextEncoder();
1603
- let imageKeys;
1604
- try {
1605
- const body = await request.json();
1606
- imageKeys = body.imageKeys;
1607
- if (!imageKeys || !Array.isArray(imageKeys) || imageKeys.length === 0) {
1608
- return _server.NextResponse.json({ error: "No image keys provided" }, { status: 400 });
1609
- }
1610
- } catch (e31) {
1611
- return _server.NextResponse.json({ error: "Invalid request body" }, { status: 400 });
1612
- }
1613
- const stream = new ReadableStream({
1614
- async start(controller) {
1615
- const sendEvent = (data) => {
1616
- controller.enqueue(encoder.encode(`data: ${JSON.stringify(data)}
1617
-
1618
- `));
1619
- };
1620
- try {
1621
- const meta = await loadMeta();
1622
- const cdnUrls = getCdnUrls(meta);
1623
- const processed = [];
1624
- const errors = [];
1625
- const urlsToPurge = [];
1626
- const total = imageKeys.length;
1627
- sendEvent({ type: "start", total });
1628
- for (let i = 0; i < imageKeys.length; i++) {
1629
- let imageKey = imageKeys[i];
1630
- if (!imageKey.startsWith("/")) {
1631
- imageKey = `/${imageKey}`;
1632
- }
1633
- sendEvent({
1634
- type: "progress",
1635
- current: i + 1,
1636
- total,
1637
- percent: Math.round((i + 1) / total * 100),
1638
- message: `Processing ${imageKey.slice(1)}...`
1639
- });
1640
- try {
1641
- let buffer;
1642
- const entry = getMetaEntry(meta, imageKey);
1643
- const existingCdnIndex = _optionalChain([entry, 'optionalAccess', _46 => _46.c]);
1644
- const existingCdnUrl = existingCdnIndex !== void 0 ? cdnUrls[existingCdnIndex] : void 0;
1645
- const isInOurR2 = existingCdnUrl === publicUrl;
1646
- const isRemote = existingCdnIndex !== void 0 && !isInOurR2;
1647
- const originalPath = getPublicPath(imageKey);
1648
- try {
1649
- buffer = await _fs.promises.readFile(originalPath);
1650
- } catch (e32) {
1651
- if (isInOurR2) {
1652
- buffer = await downloadFromCdn(imageKey);
1653
- const dir = _path2.default.dirname(originalPath);
1654
- await _fs.promises.mkdir(dir, { recursive: true });
1655
- await _fs.promises.writeFile(originalPath, buffer);
1656
- } else if (isRemote && existingCdnUrl) {
1657
- const remoteUrl = `${existingCdnUrl}${imageKey}`;
1658
- buffer = await downloadFromRemoteUrl(remoteUrl);
1659
- const dir = _path2.default.dirname(originalPath);
1660
- await _fs.promises.mkdir(dir, { recursive: true });
1661
- await _fs.promises.writeFile(originalPath, buffer);
1662
- } else {
1663
- throw new Error(`File not found: ${imageKey}`);
1664
- }
1665
- }
1666
- const ext = _path2.default.extname(imageKey).toLowerCase();
1667
- const isSvg = ext === ".svg";
1668
- if (isSvg) {
1669
- const imageDir = _path2.default.dirname(imageKey.slice(1));
1670
- const imagesPath = getPublicPath("images", imageDir === "." ? "" : imageDir);
1671
- await _fs.promises.mkdir(imagesPath, { recursive: true });
1672
- const fileName = _path2.default.basename(imageKey);
1673
- const destPath = _path2.default.join(imagesPath, fileName);
1674
- await _fs.promises.writeFile(destPath, buffer);
1675
- meta[imageKey] = {
1676
- ...entry,
1677
- o: { w: 0, h: 0 },
1678
- b: "",
1679
- f: { w: 0, h: 0 }
1680
- };
1681
- if (isRemote) {
1682
- delete meta[imageKey].c;
1683
- }
1684
- } else {
1685
- const updatedEntry = await processImage(buffer, imageKey);
1686
- if (isInOurR2) {
1687
- updatedEntry.c = existingCdnIndex;
1688
- await uploadToCdn(imageKey);
1689
- for (const thumbPath of _chunkVI6QG6WTjs.getAllThumbnailPaths.call(void 0, imageKey)) {
1690
- urlsToPurge.push(`${publicUrl}${thumbPath}`);
1691
- }
1692
- await deleteLocalThumbnails(imageKey);
1693
- try {
1694
- await _fs.promises.unlink(originalPath);
1695
- } catch (e33) {
1696
- }
1697
- }
1698
- meta[imageKey] = updatedEntry;
1699
- }
1700
- processed.push(imageKey);
1701
- } catch (error) {
1702
- console.error(`Failed to reprocess ${imageKey}:`, error);
1703
- errors.push(imageKey);
1704
- }
1705
- }
1706
- sendEvent({ type: "cleanup", message: "Saving metadata..." });
1707
- await saveMeta(meta);
1708
- if (urlsToPurge.length > 0) {
1709
- sendEvent({ type: "cleanup", message: "Purging CDN cache..." });
1710
- await purgeCloudflareCache(urlsToPurge);
1711
- }
1712
- let message = `Generated thumbnails for ${processed.length} image${processed.length !== 1 ? "s" : ""}.`;
1713
- if (errors.length > 0) {
1714
- message += ` ${errors.length} image${errors.length !== 1 ? "s" : ""} failed.`;
1715
- }
1716
- sendEvent({
1717
- type: "complete",
1718
- processed: processed.length,
1719
- errors: errors.length,
1720
- message
1721
- });
1722
- controller.close();
1723
- } catch (error) {
1724
- console.error("Reprocess stream error:", error);
1725
- sendEvent({ type: "error", message: "Failed to generate thumbnails" });
1726
- controller.close();
1727
- }
1728
- }
1729
- });
1730
- return new Response(stream, {
1731
- headers: {
1732
- "Content-Type": "text/event-stream",
1733
- "Cache-Control": "no-cache",
1734
- Connection: "keep-alive"
1735
- }
1736
- });
1737
- }
1738
- async function handleProcessAllStream() {
1739
- const publicUrl = _optionalChain([process, 'access', _47 => _47.env, 'access', _48 => _48.CLOUDFLARE_R2_PUBLIC_URL, 'optionalAccess', _49 => _49.replace, 'call', _50 => _50(/\/\s*$/, "")]);
1740
- const encoder = new TextEncoder();
1741
- const stream = new ReadableStream({
1742
- async start(controller) {
1743
- const sendEvent = (data) => {
1744
- controller.enqueue(encoder.encode(`data: ${JSON.stringify(data)}
1745
-
1746
- `));
1747
- };
1748
- try {
1749
- const meta = await loadMeta();
1750
- const cdnUrls = getCdnUrls(meta);
1751
- const processed = [];
1752
- const errors = [];
1753
- const orphansRemoved = [];
1754
- const urlsToPurge = [];
1755
- let alreadyProcessed = 0;
1756
- const imagesToProcess = [];
1757
- for (const [key, entry] of getFileEntries(meta)) {
1758
- const fileName = _path2.default.basename(key);
1759
- if (!isImageFile(fileName)) continue;
1760
- if (!_chunkVI6QG6WTjs.isProcessed.call(void 0, entry)) {
1761
- imagesToProcess.push({ key, entry });
1762
- } else {
1763
- alreadyProcessed++;
1764
- }
1765
- }
1766
- const total = imagesToProcess.length;
1767
- sendEvent({ type: "start", total });
1768
- for (let i = 0; i < imagesToProcess.length; i++) {
1769
- const { key, entry } = imagesToProcess[i];
1770
- const fullPath = getPublicPath(key);
1771
- const existingCdnIndex = entry.c;
1772
- const existingCdnUrl = existingCdnIndex !== void 0 ? cdnUrls[existingCdnIndex] : void 0;
1773
- const isInOurR2 = existingCdnUrl === publicUrl;
1774
- const isRemote = existingCdnIndex !== void 0 && !isInOurR2;
1775
- sendEvent({
1776
- type: "progress",
1777
- current: i + 1,
1778
- total,
1779
- percent: Math.round((i + 1) / total * 100),
1780
- currentFile: key.slice(1)
1781
- // Remove leading /
1782
- });
1783
- try {
1784
- let buffer;
1785
- if (isInOurR2) {
1786
- buffer = await downloadFromCdn(key);
1787
- const dir = _path2.default.dirname(fullPath);
1788
- await _fs.promises.mkdir(dir, { recursive: true });
1789
- await _fs.promises.writeFile(fullPath, buffer);
1790
- } else if (isRemote && existingCdnUrl) {
1791
- const remoteUrl = `${existingCdnUrl}${key}`;
1792
- buffer = await downloadFromRemoteUrl(remoteUrl);
1793
- const dir = _path2.default.dirname(fullPath);
1794
- await _fs.promises.mkdir(dir, { recursive: true });
1795
- await _fs.promises.writeFile(fullPath, buffer);
1796
- } else {
1797
- buffer = await _fs.promises.readFile(fullPath);
1798
- }
1799
- const ext = _path2.default.extname(key).toLowerCase();
1800
- const isSvg = ext === ".svg";
1801
- if (isSvg) {
1802
- const imageDir = _path2.default.dirname(key.slice(1));
1803
- const imagesPath = getPublicPath("images", imageDir === "." ? "" : imageDir);
1804
- await _fs.promises.mkdir(imagesPath, { recursive: true });
1805
- const fileName = _path2.default.basename(key);
1806
- const destPath = _path2.default.join(imagesPath, fileName);
1807
- await _fs.promises.writeFile(destPath, buffer);
1808
- meta[key] = {
1809
- ...entry,
1810
- o: { w: 0, h: 0 },
1811
- b: "",
1812
- f: { w: 0, h: 0 }
1813
- // SVG has "full" to indicate processed
1814
- };
1815
- if (isRemote) {
1816
- delete meta[key].c;
1817
- }
1818
- } else {
1819
- const processedEntry = await processImage(buffer, key);
1820
- meta[key] = {
1821
- ...processedEntry,
1822
- ...isInOurR2 ? { c: existingCdnIndex } : {}
1823
- };
1824
- }
1825
- if (isInOurR2) {
1826
- await uploadToCdn(key);
1827
- for (const thumbPath of _chunkVI6QG6WTjs.getAllThumbnailPaths.call(void 0, key)) {
1828
- urlsToPurge.push(`${publicUrl}${thumbPath}`);
1829
- }
1830
- await deleteLocalThumbnails(key);
1831
- try {
1832
- await _fs.promises.unlink(fullPath);
1833
- } catch (e34) {
1834
- }
1835
- }
1836
- processed.push(key.slice(1));
1837
- } catch (error) {
1838
- console.error(`Failed to process ${key}:`, error);
1839
- errors.push(key.slice(1));
1840
- }
1841
- }
1842
- sendEvent({ type: "cleanup", message: "Removing orphaned thumbnails..." });
1843
- const trackedPaths = /* @__PURE__ */ new Set();
1844
- for (const [imageKey, entry] of getFileEntries(meta)) {
1845
- if (entry.c === void 0) {
1846
- for (const thumbPath of _chunkVI6QG6WTjs.getAllThumbnailPaths.call(void 0, imageKey)) {
1847
- trackedPaths.add(thumbPath);
1848
- }
1849
- }
1850
- }
1851
- async function findOrphans(dir, relativePath = "") {
1852
- try {
1853
- const entries = await _fs.promises.readdir(dir, { withFileTypes: true });
1854
- for (const fsEntry of entries) {
1855
- if (fsEntry.name.startsWith(".")) continue;
1856
- const entryFullPath = _path2.default.join(dir, fsEntry.name);
1857
- const relPath = relativePath ? `${relativePath}/${fsEntry.name}` : fsEntry.name;
1858
- if (fsEntry.isDirectory()) {
1859
- await findOrphans(entryFullPath, relPath);
1860
- } else if (isImageFile(fsEntry.name)) {
1861
- const publicPath = `/images/${relPath}`;
1862
- if (!trackedPaths.has(publicPath)) {
1863
- try {
1864
- await _fs.promises.unlink(entryFullPath);
1865
- orphansRemoved.push(publicPath);
1866
- } catch (err) {
1867
- console.error(`Failed to remove orphan ${publicPath}:`, err);
1868
- }
1869
- }
1870
- }
1871
- }
1872
- } catch (e35) {
1873
- }
1874
- }
1875
- const imagesDir = getPublicPath("images");
1876
- try {
1877
- await findOrphans(imagesDir);
1878
- } catch (e36) {
1879
- }
1880
- async function removeEmptyDirs(dir) {
1881
- try {
1882
- const entries = await _fs.promises.readdir(dir, { withFileTypes: true });
1883
- let isEmpty = true;
1884
- for (const fsEntry of entries) {
1885
- if (fsEntry.isDirectory()) {
1886
- const subDirEmpty = await removeEmptyDirs(_path2.default.join(dir, fsEntry.name));
1887
- if (!subDirEmpty) isEmpty = false;
1888
- } else {
1889
- isEmpty = false;
1890
- }
1891
- }
1892
- if (isEmpty && dir !== imagesDir) {
1893
- await _fs.promises.rmdir(dir);
1894
- }
1895
- return isEmpty;
1896
- } catch (e37) {
1897
- return true;
1898
- }
1899
- }
1900
- try {
1901
- await removeEmptyDirs(imagesDir);
1902
- } catch (e38) {
1903
- }
1904
- await saveMeta(meta);
1905
- if (urlsToPurge.length > 0) {
1906
- await purgeCloudflareCache(urlsToPurge);
1907
- }
1908
- sendEvent({
1909
- type: "complete",
1910
- processed: processed.length,
1911
- alreadyProcessed,
1912
- orphansRemoved: orphansRemoved.length,
1913
- errors: errors.length
1914
- });
1915
- } catch (error) {
1916
- console.error("Failed to process all:", error);
1917
- sendEvent({ type: "error", message: "Failed to process images" });
1918
- } finally {
1919
- controller.close();
1920
- }
1921
- }
1922
- });
1923
- return new Response(stream, {
1924
- headers: {
1925
- "Content-Type": "text/event-stream",
1926
- "Cache-Control": "no-cache",
1927
- "Connection": "keep-alive"
1928
- }
1929
- });
1930
- }
1931
- async function handleDownloadStream(request) {
1932
- const { imageKeys } = await request.json();
1933
- if (!imageKeys || !Array.isArray(imageKeys) || imageKeys.length === 0) {
1934
- return _server.NextResponse.json({ error: "No image keys provided" }, { status: 400 });
1935
- }
1936
- const stream = new ReadableStream({
1937
- async start(controller) {
1938
- const encoder = new TextEncoder();
1939
- const sendEvent = (data) => {
1940
- controller.enqueue(encoder.encode(`data: ${JSON.stringify(data)}
1941
-
1942
- `));
1943
- };
1944
- sendEvent({ type: "start", total: imageKeys.length });
1945
- const downloaded = [];
1946
- const skipped = [];
1947
- const errors = [];
1948
- try {
1949
- const meta = await loadMeta();
1950
- for (let i = 0; i < imageKeys.length; i++) {
1951
- const imageKey = imageKeys[i];
1952
- const entry = getMetaEntry(meta, imageKey);
1953
- if (!entry || entry.c === void 0) {
1954
- skipped.push(imageKey);
1955
- sendEvent({
1956
- type: "progress",
1957
- current: i + 1,
1958
- total: imageKeys.length,
1959
- message: `Skipped ${imageKey} (not on cloud)`
1960
- });
1961
- continue;
1962
- }
1963
- try {
1964
- const imageBuffer = await downloadFromCdn(imageKey);
1965
- const localPath = getPublicPath(imageKey.replace(/^\//, ""));
1966
- await _fs.promises.mkdir(_path2.default.dirname(localPath), { recursive: true });
1967
- await _fs.promises.writeFile(localPath, imageBuffer);
1968
- await deleteThumbnailsFromCdn(imageKey);
1969
- const wasProcessed = _chunkVI6QG6WTjs.isProcessed.call(void 0, entry);
1970
- delete entry.c;
1971
- if (wasProcessed) {
1972
- const processedEntry = await processImage(imageBuffer, imageKey);
1973
- entry.sm = processedEntry.sm;
1974
- entry.md = processedEntry.md;
1975
- entry.lg = processedEntry.lg;
1976
- entry.f = processedEntry.f;
1977
- }
1978
- downloaded.push(imageKey);
1979
- sendEvent({
1980
- type: "progress",
1981
- current: i + 1,
1982
- total: imageKeys.length,
1983
- message: `Downloaded ${imageKey}`
1984
- });
1985
- } catch (error) {
1986
- console.error(`Failed to download ${imageKey}:`, error);
1987
- errors.push(imageKey);
1988
- sendEvent({
1989
- type: "progress",
1990
- current: i + 1,
1991
- total: imageKeys.length,
1992
- message: `Failed to download ${imageKey}`
1993
- });
1994
- }
1995
- }
1996
- await saveMeta(meta);
1997
- let message = `Downloaded ${downloaded.length} image${downloaded.length !== 1 ? "s" : ""}.`;
1998
- if (skipped.length > 0) {
1999
- message += ` ${skipped.length} image${skipped.length !== 1 ? "s were" : " was"} not on cloud.`;
2000
- }
2001
- if (errors.length > 0) {
2002
- message += ` ${errors.length} image${errors.length !== 1 ? "s" : ""} failed.`;
2003
- }
2004
- sendEvent({
2005
- type: "complete",
2006
- downloaded: downloaded.length,
2007
- skipped: skipped.length,
2008
- errors: errors.length,
2009
- message
2010
- });
2011
- } catch (error) {
2012
- console.error("Download stream error:", error);
2013
- sendEvent({ type: "error", message: "Failed to download images" });
2014
- } finally {
2015
- controller.close();
2016
- }
2017
- }
2018
- });
2019
- return new Response(stream, {
2020
- headers: {
2021
- "Content-Type": "text/event-stream",
2022
- "Cache-Control": "no-cache",
2023
- "Connection": "keep-alive"
2024
- }
2025
- });
2026
- }
2027
-
2028
- // src/handlers/scan.ts
2029
-
2030
-
2031
-
2032
-
2033
-
2034
- async function handleScanStream() {
2035
- const encoder = new TextEncoder();
2036
- const stream = new ReadableStream({
2037
- async start(controller) {
2038
- const sendEvent = (data) => {
2039
- controller.enqueue(encoder.encode(`data: ${JSON.stringify(data)}
2040
-
2041
- `));
2042
- };
2043
- try {
2044
- const meta = await loadMeta();
2045
- const existingCount = Object.keys(meta).filter((k) => !k.startsWith("_")).length;
2046
- const existingKeys = new Set(Object.keys(meta));
2047
- const added = [];
2048
- const renamed = [];
2049
- const errors = [];
2050
- const orphanedFiles = [];
2051
- const allFiles = [];
2052
- async function scanDir(dir, relativePath = "") {
2053
- try {
2054
- const entries = await _fs.promises.readdir(dir, { withFileTypes: true });
2055
- for (const entry of entries) {
2056
- if (entry.name.startsWith(".")) continue;
2057
- const fullPath = _path2.default.join(dir, entry.name);
2058
- const relPath = relativePath ? `${relativePath}/${entry.name}` : entry.name;
2059
- if (relPath === "images" || relPath.startsWith("images/")) continue;
2060
- if (entry.isDirectory()) {
2061
- await scanDir(fullPath, relPath);
2062
- } else if (isMediaFile(entry.name)) {
2063
- allFiles.push({ relativePath: relPath, fullPath });
2064
- }
2065
- }
2066
- } catch (e39) {
2067
- }
2068
- }
2069
- const publicDir = getPublicPath();
2070
- await scanDir(publicDir);
2071
- const total = allFiles.length;
2072
- sendEvent({ type: "start", total });
2073
- for (let i = 0; i < allFiles.length; i++) {
2074
- let { relativePath, fullPath } = allFiles[i];
2075
- let imageKey = "/" + relativePath;
2076
- sendEvent({
2077
- type: "progress",
2078
- current: i + 1,
2079
- total,
2080
- percent: Math.round((i + 1) / total * 100),
2081
- currentFile: relativePath
2082
- });
2083
- if (existingKeys.has(imageKey)) {
2084
- continue;
2085
- }
2086
- if (meta[imageKey]) {
2087
- const ext = _path2.default.extname(relativePath);
2088
- const baseName = relativePath.slice(0, -ext.length);
2089
- let counter = 1;
2090
- let newKey = `/${baseName}-${counter}${ext}`;
2091
- while (meta[newKey]) {
2092
- counter++;
2093
- newKey = `/${baseName}-${counter}${ext}`;
2094
- }
2095
- const newRelativePath = `${baseName}-${counter}${ext}`;
2096
- const newFullPath = getPublicPath(newRelativePath);
2097
- try {
2098
- await _fs.promises.rename(fullPath, newFullPath);
2099
- renamed.push({ from: relativePath, to: newRelativePath });
2100
- relativePath = newRelativePath;
2101
- fullPath = newFullPath;
2102
- imageKey = newKey;
2103
- } catch (err) {
2104
- console.error(`Failed to rename ${relativePath}:`, err);
2105
- errors.push(`Failed to rename ${relativePath}`);
2106
- continue;
2107
- }
2108
- }
2109
- try {
2110
- const isImage = isImageFile(relativePath);
2111
- if (isImage) {
2112
- const ext = _path2.default.extname(relativePath).toLowerCase();
2113
- if (ext === ".svg") {
2114
- meta[imageKey] = { o: { w: 0, h: 0 }, b: "" };
2115
- } else {
2116
- try {
2117
- const buffer = await _fs.promises.readFile(fullPath);
2118
- const metadata = await _sharp2.default.call(void 0, buffer).metadata();
2119
- const { data, info } = await _sharp2.default.call(void 0, buffer).resize(32, 32, { fit: "inside" }).ensureAlpha().raw().toBuffer({ resolveWithObject: true });
2120
- const blurhash = _blurhash.encode.call(void 0, new Uint8ClampedArray(data), info.width, info.height, 4, 4);
2121
- meta[imageKey] = {
2122
- o: { w: metadata.width || 0, h: metadata.height || 0 },
2123
- b: blurhash
2124
- };
2125
- } catch (e40) {
2126
- meta[imageKey] = { o: { w: 0, h: 0 } };
2127
- }
2128
- }
2129
- } else {
2130
- meta[imageKey] = {};
2131
- }
2132
- existingKeys.add(imageKey);
2133
- added.push(imageKey);
2134
- } catch (error) {
2135
- console.error(`Failed to process ${relativePath}:`, error);
2136
- errors.push(relativePath);
2137
- }
2138
- }
2139
- sendEvent({ type: "cleanup", message: "Checking for orphaned thumbnails..." });
2140
- const expectedThumbnails = /* @__PURE__ */ new Set();
2141
- const fileEntries = getFileEntries(meta);
2142
- for (const [imageKey, entry] of fileEntries) {
2143
- if (entry.c === void 0 && _chunkVI6QG6WTjs.isProcessed.call(void 0, entry)) {
2144
- for (const thumbPath of _chunkVI6QG6WTjs.getAllThumbnailPaths.call(void 0, imageKey)) {
2145
- expectedThumbnails.add(thumbPath);
2146
- }
2147
- }
2148
- }
2149
- async function findOrphans(dir, relativePath = "") {
2150
- try {
2151
- const entries = await _fs.promises.readdir(dir, { withFileTypes: true });
2152
- for (const entry of entries) {
2153
- if (entry.name.startsWith(".")) continue;
2154
- const fullPath = _path2.default.join(dir, entry.name);
2155
- const relPath = relativePath ? `${relativePath}/${entry.name}` : entry.name;
2156
- if (entry.isDirectory()) {
2157
- await findOrphans(fullPath, relPath);
2158
- } else if (isImageFile(entry.name)) {
2159
- const publicPath = `/images/${relPath}`;
2160
- if (!expectedThumbnails.has(publicPath)) {
2161
- orphanedFiles.push(publicPath);
2162
- }
2163
- }
2164
- }
2165
- } catch (e41) {
2166
- }
2167
- }
2168
- const imagesDir = getPublicPath("images");
2169
- try {
2170
- await findOrphans(imagesDir);
2171
- } catch (e42) {
2172
- }
2173
- await saveMeta(meta);
2174
- sendEvent({
2175
- type: "complete",
2176
- existingCount,
2177
- added: added.length,
2178
- renamed: renamed.length,
2179
- errors: errors.length,
2180
- renamedFiles: renamed,
2181
- orphanedFiles: orphanedFiles.length > 0 ? orphanedFiles : void 0
2182
- });
2183
- } catch (error) {
2184
- console.error("Scan failed:", error);
2185
- sendEvent({ type: "error", message: "Scan failed" });
2186
- } finally {
2187
- controller.close();
2188
- }
2189
- }
2190
- });
2191
- return new Response(stream, {
2192
- headers: {
2193
- "Content-Type": "text/event-stream",
2194
- "Cache-Control": "no-cache",
2195
- "Connection": "keep-alive"
2196
- }
2197
- });
2198
- }
2199
- async function handleDeleteOrphans(request) {
2200
- try {
2201
- const { paths } = await request.json();
2202
- if (!paths || !Array.isArray(paths) || paths.length === 0) {
2203
- return _server.NextResponse.json({ error: "No paths provided" }, { status: 400 });
2204
- }
2205
- const deleted = [];
2206
- const errors = [];
2207
- for (const orphanPath of paths) {
2208
- if (!orphanPath.startsWith("/images/")) {
2209
- errors.push(`Invalid path: ${orphanPath}`);
2210
- continue;
2211
- }
2212
- const fullPath = getPublicPath(orphanPath);
2213
- try {
2214
- await _fs.promises.unlink(fullPath);
2215
- deleted.push(orphanPath);
2216
- } catch (err) {
2217
- console.error(`Failed to delete ${orphanPath}:`, err);
2218
- errors.push(orphanPath);
2219
- }
2220
- }
2221
- const imagesDir = getPublicPath("images");
2222
- async function removeEmptyDirs(dir) {
2223
- try {
2224
- const entries = await _fs.promises.readdir(dir, { withFileTypes: true });
2225
- let isEmpty = true;
2226
- for (const entry of entries) {
2227
- if (entry.isDirectory()) {
2228
- const subDirEmpty = await removeEmptyDirs(_path2.default.join(dir, entry.name));
2229
- if (!subDirEmpty) isEmpty = false;
2230
- } else {
2231
- isEmpty = false;
2232
- }
2233
- }
2234
- if (isEmpty && dir !== imagesDir) {
2235
- await _fs.promises.rmdir(dir);
2236
- }
2237
- return isEmpty;
2238
- } catch (e43) {
2239
- return true;
2240
- }
2241
- }
2242
- try {
2243
- await removeEmptyDirs(imagesDir);
2244
- } catch (e44) {
2245
- }
2246
- return _server.NextResponse.json({
2247
- success: true,
2248
- deleted: deleted.length,
2249
- errors: errors.length
2250
- });
2251
- } catch (error) {
2252
- console.error("Failed to delete orphans:", error);
2253
- return _server.NextResponse.json({ error: "Failed to delete orphaned files" }, { status: 500 });
2254
- }
2255
- }
2256
-
2257
- // src/handlers/import.ts
2258
-
2259
-
2260
- function parseImageUrl(url) {
2261
- const parsed = new URL(url);
2262
- const base = `${parsed.protocol}//${parsed.host}`;
2263
- const path9 = parsed.pathname;
2264
- return { base, path: path9 };
2265
- }
2266
- async function processRemoteImage(url) {
2267
- const response = await fetch(url);
2268
- if (!response.ok) {
2269
- throw new Error(`Failed to fetch: ${response.status}`);
2270
- }
2271
- const buffer = Buffer.from(await response.arrayBuffer());
2272
- const metadata = await _sharp2.default.call(void 0, buffer).metadata();
2273
- const { data, info } = await _sharp2.default.call(void 0, buffer).resize(32, 32, { fit: "inside" }).ensureAlpha().raw().toBuffer({ resolveWithObject: true });
2274
- const blurhash = _blurhash.encode.call(void 0, new Uint8ClampedArray(data), info.width, info.height, 4, 4);
2275
- return {
2276
- o: { w: metadata.width || 0, h: metadata.height || 0 },
2277
- b: blurhash
2278
- };
2279
- }
2280
- async function handleImportUrls(request) {
2281
- const encoder = new TextEncoder();
2282
- const stream = new ReadableStream({
2283
- async start(controller) {
2284
- const sendEvent = (data) => {
2285
- controller.enqueue(encoder.encode(`data: ${JSON.stringify(data)}
2286
-
2287
- `));
2288
- };
2289
- try {
2290
- const { urls } = await request.json();
2291
- if (!urls || !Array.isArray(urls) || urls.length === 0) {
2292
- sendEvent({ type: "error", message: "No URLs provided" });
2293
- controller.close();
2294
- return;
2295
- }
2296
- const meta = await loadMeta();
2297
- const added = [];
2298
- const skipped = [];
2299
- const errors = [];
2300
- const total = urls.length;
2301
- sendEvent({ type: "start", total });
2302
- for (let i = 0; i < urls.length; i++) {
2303
- const url = urls[i].trim();
2304
- if (!url) continue;
2305
- sendEvent({
2306
- type: "progress",
2307
- current: i + 1,
2308
- total,
2309
- percent: Math.round((i + 1) / total * 100),
2310
- currentFile: url
2311
- });
2312
- try {
2313
- const { base, path: path9 } = parseImageUrl(url);
2314
- const existingEntry = getMetaEntry(meta, path9);
2315
- if (existingEntry) {
2316
- skipped.push(path9);
2317
- continue;
2318
- }
2319
- const cdnIndex = getOrAddCdnIndex(meta, base);
2320
- const imageData = await processRemoteImage(url);
2321
- setMetaEntry(meta, path9, {
2322
- o: imageData.o,
2323
- b: imageData.b,
2324
- c: cdnIndex
2325
- });
2326
- added.push(path9);
2327
- } catch (error) {
2328
- console.error(`Failed to import ${url}:`, error);
2329
- errors.push(url);
2330
- }
2331
- }
2332
- await saveMeta(meta);
2333
- sendEvent({
2334
- type: "complete",
2335
- added: added.length,
2336
- skipped: skipped.length,
2337
- errors: errors.length
2338
- });
2339
- } catch (error) {
2340
- console.error("Import failed:", error);
2341
- sendEvent({ type: "error", message: "Import failed" });
2342
- } finally {
2343
- controller.close();
2344
- }
2345
- }
2346
- });
2347
- return new Response(stream, {
2348
- headers: {
2349
- "Content-Type": "text/event-stream",
2350
- "Cache-Control": "no-cache",
2351
- "Connection": "keep-alive"
2352
- }
2353
- });
2354
- }
2355
- async function handleGetCdns() {
2356
- try {
2357
- const meta = await loadMeta();
2358
- const cdns = meta._cdns || [];
2359
- return Response.json({ cdns });
2360
- } catch (error) {
2361
- console.error("Failed to get CDNs:", error);
2362
- return Response.json({ error: "Failed to get CDNs" }, { status: 500 });
2363
- }
2364
- }
2365
- async function handleUpdateCdns(request) {
2366
- try {
2367
- const { cdns } = await request.json();
2368
- if (!Array.isArray(cdns)) {
2369
- return Response.json({ error: "Invalid CDN array" }, { status: 400 });
2370
- }
2371
- const meta = await loadMeta();
2372
- meta._cdns = cdns.map((url) => url.replace(/\/$/, ""));
2373
- await saveMeta(meta);
2374
- return Response.json({ success: true, cdns: meta._cdns });
2375
- } catch (error) {
2376
- console.error("Failed to update CDNs:", error);
2377
- return Response.json({ error: "Failed to update CDNs" }, { status: 500 });
2378
- }
2379
- }
2380
-
2381
- // src/handlers/favicon.ts
2382
-
2383
-
2384
-
2385
- var _promises = require('fs/promises'); var _promises2 = _interopRequireDefault(_promises);
2386
- var FAVICON_CONFIGS = [
2387
- { name: "favicon.ico", size: 48 },
2388
- { name: "icon.png", size: 32 },
2389
- { name: "apple-icon.png", size: 180 }
2390
- ];
2391
- async function handleGenerateFavicon(request) {
2392
- const encoder = new TextEncoder();
2393
- let imagePath;
2394
- try {
2395
- const body = await request.json();
2396
- imagePath = body.imagePath;
2397
- if (!imagePath) {
2398
- return _server.NextResponse.json({ error: "No image path provided" }, { status: 400 });
2399
- }
2400
- } catch (e45) {
2401
- return _server.NextResponse.json({ error: "Invalid request body" }, { status: 400 });
2402
- }
2403
- const fileName = _path2.default.basename(imagePath).toLowerCase();
2404
- if (fileName !== "favicon.png" && fileName !== "favicon.jpg") {
2405
- return _server.NextResponse.json({
2406
- error: "Source file must be named favicon.png or favicon.jpg"
2407
- }, { status: 400 });
2408
- }
2409
- const sourcePath = getPublicPath(imagePath.replace(/^\//, ""));
2410
- try {
2411
- await _promises2.default.access(sourcePath);
2412
- } catch (e46) {
2413
- return _server.NextResponse.json({ error: "Source file not found" }, { status: 404 });
2414
- }
2415
- let metadata;
2416
- try {
2417
- metadata = await _sharp2.default.call(void 0, sourcePath).metadata();
2418
- } catch (e47) {
2419
- return _server.NextResponse.json({ error: "Source file is not a valid image" }, { status: 400 });
2420
- }
2421
- const outputDir = getSrcAppPath();
2422
- try {
2423
- await _promises2.default.access(outputDir);
2424
- } catch (e48) {
2425
- return _server.NextResponse.json({
2426
- error: "Output directory src/app/ not found"
2427
- }, { status: 500 });
2428
- }
2429
- const stream = new ReadableStream({
2430
- async start(controller) {
2431
- const sendEvent = (data) => {
2432
- controller.enqueue(encoder.encode(`data: ${JSON.stringify(data)}
2433
-
2434
- `));
2435
- };
2436
- try {
2437
- const total = FAVICON_CONFIGS.length;
2438
- const generated = [];
2439
- const errors = [];
2440
- sendEvent({
2441
- type: "start",
2442
- total,
2443
- sourceSize: `${metadata.width}x${metadata.height}`
2444
- });
2445
- for (let i = 0; i < FAVICON_CONFIGS.length; i++) {
2446
- const config = FAVICON_CONFIGS[i];
2447
- sendEvent({
2448
- type: "progress",
2449
- current: i + 1,
2450
- total,
2451
- percent: Math.round((i + 1) / total * 100),
2452
- message: `Generating ${config.name} (${config.size}x${config.size})...`
2453
- });
2454
- try {
2455
- const outputPath = _path2.default.join(outputDir, config.name);
2456
- await _sharp2.default.call(void 0, sourcePath).resize(config.size, config.size, {
2457
- fit: "cover",
2458
- position: "center"
2459
- }).png({ quality: 100 }).toFile(outputPath);
2460
- generated.push(config.name);
2461
- } catch (error) {
2462
- console.error(`Failed to generate ${config.name}:`, error);
2463
- errors.push(config.name);
2464
- }
2465
- }
2466
- let message = `Generated ${generated.length} favicon${generated.length !== 1 ? "s" : ""} to src/app/.`;
2467
- if (errors.length > 0) {
2468
- message += ` ${errors.length} failed.`;
2469
- }
2470
- sendEvent({
2471
- type: "complete",
2472
- processed: generated.length,
2473
- errors: errors.length,
2474
- message
2475
- });
2476
- controller.close();
2477
- } catch (error) {
2478
- console.error("Favicon generation error:", error);
2479
- sendEvent({ type: "error", message: "Failed to generate favicons" });
2480
- controller.close();
2481
- }
2482
- }
2483
- });
2484
- return new Response(stream, {
2485
- headers: {
2486
- "Content-Type": "text/event-stream",
2487
- "Cache-Control": "no-cache",
2488
- Connection: "keep-alive"
2489
- }
2490
- });
2491
- }
2492
-
2493
- // src/handlers/index.ts
2494
- async function GET(request) {
2495
- if (process.env.NODE_ENV !== "development") {
2496
- return _server.NextResponse.json({ error: "Not available in production" }, { status: 403 });
2497
- }
2498
- const pathname = request.nextUrl.pathname;
2499
- const route = pathname.replace(/^\/api\/studio\/?/, "");
2500
- if (route === "list-folders") {
2501
- return handleListFolders();
2502
- }
2503
- if (route === "list" || route.startsWith("list")) {
2504
- return handleList(request);
2505
- }
2506
- if (route === "count-images") {
2507
- return handleCountImages();
2508
- }
2509
- if (route === "folder-images") {
2510
- return handleFolderImages(request);
2511
- }
2512
- if (route === "search") {
2513
- return handleSearch(request);
2514
- }
2515
- if (route === "cdns") {
2516
- return handleGetCdns();
2517
- }
2518
- return _server.NextResponse.json({ error: "Not found" }, { status: 404 });
2519
- }
2520
- async function POST(request) {
2521
- if (process.env.NODE_ENV !== "development") {
2522
- return _server.NextResponse.json({ error: "Not available in production" }, { status: 403 });
2523
- }
2524
- const pathname = request.nextUrl.pathname;
2525
- const route = pathname.replace(/^\/api\/studio\/?/, "");
2526
- if (route === "upload") {
2527
- return handleUpload(request);
2528
- }
2529
- if (route === "delete") {
2530
- return handleDelete(request);
2531
- }
2532
- if (route === "sync") {
2533
- return handleSync(request);
2534
- }
2535
- if (route === "reprocess") {
2536
- return handleReprocess(request);
2537
- }
2538
- if (route === "reprocess-stream") {
2539
- return handleReprocessStream(request);
2540
- }
2541
- if (route === "unprocess-stream") {
2542
- return handleUnprocessStream(request);
2543
- }
2544
- if (route === "process-all") {
2545
- return handleProcessAllStream();
2546
- }
2547
- if (route === "download-stream") {
2548
- return handleDownloadStream(request);
2549
- }
2550
- if (route === "create-folder") {
2551
- return handleCreateFolder(request);
2552
- }
2553
- if (route === "rename") {
2554
- return handleRename(request);
2555
- }
2556
- if (route === "move") {
2557
- return handleMoveStream(request);
2558
- }
2559
- if (route === "scan") {
2560
- return handleScanStream();
2561
- }
2562
- if (route === "delete-orphans") {
2563
- return handleDeleteOrphans(request);
2564
- }
2565
- if (route === "import") {
2566
- return handleImportUrls(request);
2567
- }
2568
- if (route === "cdns") {
2569
- return handleUpdateCdns(request);
2570
- }
2571
- if (route === "generate-favicon") {
2572
- return handleGenerateFavicon(request);
2573
- }
2574
- return _server.NextResponse.json({ error: "Not found" }, { status: 404 });
2575
- }
2576
- async function DELETE(request) {
2577
- if (process.env.NODE_ENV !== "development") {
2578
- return _server.NextResponse.json({ error: "Not available in production" }, { status: 403 });
2579
- }
2580
- return handleDelete(request);
2581
- }
2582
-
2583
-
2584
-
2585
-
2586
- exports.DELETE = DELETE; exports.GET = GET; exports.POST = POST;
2587
- //# sourceMappingURL=index.js.map