@gallop.software/studio 0.1.116 → 1.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{StudioUI-7LIOKKXE.js → StudioUI-GWMM47P7.js} +81 -15
- package/dist/StudioUI-GWMM47P7.js.map +1 -0
- package/dist/{StudioUI-CE7CEP63.mjs → StudioUI-KCUI5YUD.mjs} +81 -15
- package/dist/StudioUI-KCUI5YUD.mjs.map +1 -0
- package/dist/{chunk-RDNC5ABF.mjs → chunk-FDWPNRNZ.mjs} +1 -1
- package/dist/chunk-FDWPNRNZ.mjs.map +1 -0
- package/dist/{chunk-LEOQKJCL.js → chunk-WJJHVPLT.js} +1 -1
- package/dist/chunk-WJJHVPLT.js.map +1 -0
- package/dist/handlers/index.js +188 -56
- package/dist/handlers/index.js.map +1 -1
- package/dist/handlers/index.mjs +148 -16
- package/dist/handlers/index.mjs.map +1 -1
- package/dist/index.d.mts +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +3 -3
- package/dist/index.mjs +2 -2
- package/package.json +1 -1
- package/dist/StudioUI-7LIOKKXE.js.map +0 -1
- package/dist/StudioUI-CE7CEP63.mjs.map +0 -1
- package/dist/chunk-LEOQKJCL.js.map +0 -1
- package/dist/chunk-RDNC5ABF.mjs.map +0 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/types.ts"],"sourcesContent":["/**\n * Meta entry - works for images and non-images\n * Images have w, h, b (after processing)\n * c is the index into _cdns array (omit if not on CDN)\n */\nexport interface MetaEntry {\n w?: number // original width (images only)\n h?: number // original height (images only)\n b?: string // blurhash (images only, after processing)\n p?: 1 // processed (has thumbnails and blurhash)\n c?: number // CDN index - index into _cdns array (omit if not on CDN)\n}\n\n/**\n * Full meta schema including special keys\n * _cdns: Array of CDN base URLs\n * Other keys: file paths from public folder\n */\nexport interface FullMeta {\n _cdns?: string[] // Array of CDN base URLs\n [key: string]: MetaEntry | string[] | undefined\n}\n\n/**\n * Meta schema - keyed by path from public folder (legacy type)\n * Example: { \"/portfolio/photo.jpg\": { w: 2400, h: 1600, b: \"...\" } }\n */\nexport type LeanMeta = Record<string, MetaEntry>\n\n// Legacy alias for compatibility\nexport type LeanImageEntry = MetaEntry\n\n/**\n * File/folder item for browser\n */\nexport interface FileItem {\n name: string\n path: string\n type: 'file' | 'folder'\n size?: number\n dimensions?: { width: number; height: number }\n isProcessed?: boolean\n cdnPushed?: boolean\n cdnBaseUrl?: string // CDN base URL when pushed to cloud\n isRemote?: boolean // true if CDN URL doesn't match R2 (external import)\n isProtected?: boolean // true for images folder and its contents (cannot select/modify)\n // Folder-specific properties\n fileCount?: number\n totalSize?: number\n // For showing thumbnails - path to -sm version if exists\n thumbnail?: string\n // Whether a processed thumbnail exists\n hasThumbnail?: boolean\n}\n\n/**\n * Studio configuration\n */\nexport interface StudioConfig {\n r2AccountId?: string\n r2AccessKeyId?: string\n r2SecretAccessKey?: string\n r2BucketName?: string\n r2PublicUrl?: string\n thumbnailSizes?: {\n small: number\n medium: number\n large: number\n }\n}\n\n/**\n * Get thumbnail path from original image path\n */\nexport function getThumbnailPath(originalPath: string, size: 'sm' | 'md' | 'lg' | 'full'): string {\n if (size === 'full') {\n const ext = originalPath.match(/\\.\\w+$/)?.[0] || '.jpg'\n const base = originalPath.replace(/\\.\\w+$/, '')\n const outputExt = ext.toLowerCase() === '.png' ? '.png' : '.jpg'\n return `/images${base}${outputExt}`\n }\n const ext = originalPath.match(/\\.\\w+$/)?.[0] || '.jpg'\n const base = originalPath.replace(/\\.\\w+$/, '')\n const outputExt = ext.toLowerCase() === '.png' ? '.png' : '.jpg'\n return `/images${base}-${size}${outputExt}`\n}\n\n/**\n * Get all thumbnail paths for an image\n */\nexport function getAllThumbnailPaths(originalPath: string): string[] {\n return [\n getThumbnailPath(originalPath, 'full'),\n getThumbnailPath(originalPath, 'lg'),\n getThumbnailPath(originalPath, 'md'),\n getThumbnailPath(originalPath, 'sm'),\n ]\n}\n"],"mappings":";AA0EO,SAAS,iBAAiB,cAAsB,MAA2C;AAChG,MAAI,SAAS,QAAQ;AACnB,UAAMA,OAAM,aAAa,MAAM,QAAQ,IAAI,CAAC,KAAK;AACjD,UAAMC,QAAO,aAAa,QAAQ,UAAU,EAAE;AAC9C,UAAMC,aAAYF,KAAI,YAAY,MAAM,SAAS,SAAS;AAC1D,WAAO,UAAUC,KAAI,GAAGC,UAAS;AAAA,EACnC;AACA,QAAM,MAAM,aAAa,MAAM,QAAQ,IAAI,CAAC,KAAK;AACjD,QAAM,OAAO,aAAa,QAAQ,UAAU,EAAE;AAC9C,QAAM,YAAY,IAAI,YAAY,MAAM,SAAS,SAAS;AAC1D,SAAO,UAAU,IAAI,IAAI,IAAI,GAAG,SAAS;AAC3C;AAKO,SAAS,qBAAqB,cAAgC;AACnE,SAAO;AAAA,IACL,iBAAiB,cAAc,MAAM;AAAA,IACrC,iBAAiB,cAAc,IAAI;AAAA,IACnC,iBAAiB,cAAc,IAAI;AAAA,IACnC,iBAAiB,cAAc,IAAI;AAAA,EACrC;AACF;","names":["ext","base","outputExt"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["/Users/chrisb/Sites/studio/dist/chunk-WJJHVPLT.js","../src/types.ts"],"names":["ext","base","outputExt"],"mappings":"AAAA;AC0EO,SAAS,gBAAA,CAAiB,YAAA,EAAsB,IAAA,EAA2C;AAChG,EAAA,GAAA,CAAI,KAAA,IAAS,MAAA,EAAQ;AACnB,IAAA,MAAMA,KAAAA,kBAAM,YAAA,mBAAa,KAAA,mBAAM,QAAQ,CAAA,4BAAA,CAAI,CAAC,IAAA,GAAK,MAAA;AACjD,IAAA,MAAMC,MAAAA,EAAO,YAAA,CAAa,OAAA,CAAQ,QAAA,EAAU,EAAE,CAAA;AAC9C,IAAA,MAAMC,WAAAA,EAAYF,IAAAA,CAAI,WAAA,CAAY,EAAA,IAAM,OAAA,EAAS,OAAA,EAAS,MAAA;AAC1D,IAAA,OAAO,CAAA,OAAA,EAAUC,KAAI,CAAA,EAAA;AACvB,EAAA;AACyB,EAAA;AACZ,EAAA;AACS,EAAA;AACG,EAAA;AAC3B;AAKgB;AACP,EAAA;AACY,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACnB,EAAA;AACF;AD5E2B;AACA;AACA;AACA;AACA","file":"/Users/chrisb/Sites/studio/dist/chunk-WJJHVPLT.js","sourcesContent":[null,"/**\n * Meta entry - works for images and non-images\n * Images have w, h, b (after processing)\n * c is the index into _cdns array (omit if not on CDN)\n */\nexport interface MetaEntry {\n w?: number // original width (images only)\n h?: number // original height (images only)\n b?: string // blurhash (images only, after processing)\n p?: 1 // processed (has thumbnails and blurhash)\n c?: number // CDN index - index into _cdns array (omit if not on CDN)\n}\n\n/**\n * Full meta schema including special keys\n * _cdns: Array of CDN base URLs\n * Other keys: file paths from public folder\n */\nexport interface FullMeta {\n _cdns?: string[] // Array of CDN base URLs\n [key: string]: MetaEntry | string[] | undefined\n}\n\n/**\n * Meta schema - keyed by path from public folder (legacy type)\n * Example: { \"/portfolio/photo.jpg\": { w: 2400, h: 1600, b: \"...\" } }\n */\nexport type LeanMeta = Record<string, MetaEntry>\n\n// Legacy alias for compatibility\nexport type LeanImageEntry = MetaEntry\n\n/**\n * File/folder item for browser\n */\nexport interface FileItem {\n name: string\n path: string\n type: 'file' | 'folder'\n size?: number\n dimensions?: { width: number; height: number }\n isProcessed?: boolean\n cdnPushed?: boolean\n cdnBaseUrl?: string // CDN base URL when pushed to cloud\n isRemote?: boolean // true if CDN URL doesn't match R2 (external import)\n isProtected?: boolean // true for images folder and its contents (cannot select/modify)\n // Folder-specific properties\n fileCount?: number\n totalSize?: number\n // For showing thumbnails - path to -sm version if exists\n thumbnail?: string\n // Whether a processed thumbnail exists\n hasThumbnail?: boolean\n}\n\n/**\n * Studio configuration\n */\nexport interface StudioConfig {\n r2AccountId?: string\n r2AccessKeyId?: string\n r2SecretAccessKey?: string\n r2BucketName?: string\n r2PublicUrl?: string\n thumbnailSizes?: {\n small: number\n medium: number\n large: number\n }\n}\n\n/**\n * Get thumbnail path from original image path\n */\nexport function getThumbnailPath(originalPath: string, size: 'sm' | 'md' | 'lg' | 'full'): string {\n if (size === 'full') {\n const ext = originalPath.match(/\\.\\w+$/)?.[0] || '.jpg'\n const base = originalPath.replace(/\\.\\w+$/, '')\n const outputExt = ext.toLowerCase() === '.png' ? '.png' : '.jpg'\n return `/images${base}${outputExt}`\n }\n const ext = originalPath.match(/\\.\\w+$/)?.[0] || '.jpg'\n const base = originalPath.replace(/\\.\\w+$/, '')\n const outputExt = ext.toLowerCase() === '.png' ? '.png' : '.jpg'\n return `/images${base}-${size}${outputExt}`\n}\n\n/**\n * Get all thumbnail paths for an image\n */\nexport function getAllThumbnailPaths(originalPath: string): string[] {\n return [\n getThumbnailPath(originalPath, 'full'),\n getThumbnailPath(originalPath, 'lg'),\n getThumbnailPath(originalPath, 'md'),\n getThumbnailPath(originalPath, 'sm'),\n ]\n}\n"]}
|
package/dist/handlers/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } function _optionalChain(ops) { let lastAccessLHS = undefined; let value = ops[0]; let i = 1; while (i < ops.length) { const op = ops[i]; const fn = ops[i + 1]; i += 2; if ((op === 'optionalAccess' || op === 'optionalCall') && value == null) { return undefined; } if (op === 'access' || op === 'optionalAccess') { lastAccessLHS = value; value = fn(value); } else if (op === 'call' || op === 'optionalCall') { value = fn((...args) => value.call(lastAccessLHS, ...args)); lastAccessLHS = undefined; } } return value; }
|
|
2
2
|
|
|
3
3
|
|
|
4
|
-
var
|
|
4
|
+
var _chunkWJJHVPLTjs = require('../chunk-WJJHVPLT.js');
|
|
5
5
|
|
|
6
6
|
// src/handlers/index.ts
|
|
7
7
|
var _server = require('next/server');
|
|
@@ -219,7 +219,7 @@ async function uploadToCdn(imageKey) {
|
|
|
219
219
|
const bucketName = process.env.CLOUDFLARE_R2_BUCKET_NAME;
|
|
220
220
|
if (!bucketName) throw new Error("R2 bucket not configured");
|
|
221
221
|
const r2 = getR2Client();
|
|
222
|
-
for (const thumbPath of
|
|
222
|
+
for (const thumbPath of _chunkWJJHVPLTjs.getAllThumbnailPaths.call(void 0, imageKey)) {
|
|
223
223
|
const localPath = _path2.default.join(process.cwd(), "public", thumbPath);
|
|
224
224
|
try {
|
|
225
225
|
const fileBuffer = await _fs.promises.readFile(localPath);
|
|
@@ -236,7 +236,7 @@ async function uploadToCdn(imageKey) {
|
|
|
236
236
|
}
|
|
237
237
|
}
|
|
238
238
|
async function deleteLocalThumbnails(imageKey) {
|
|
239
|
-
for (const thumbPath of
|
|
239
|
+
for (const thumbPath of _chunkWJJHVPLTjs.getAllThumbnailPaths.call(void 0, imageKey)) {
|
|
240
240
|
const localPath = _path2.default.join(process.cwd(), "public", thumbPath);
|
|
241
241
|
try {
|
|
242
242
|
await _fs.promises.unlink(localPath);
|
|
@@ -281,7 +281,7 @@ async function deleteFromCdn(imageKey, hasThumbnails) {
|
|
|
281
281
|
} catch (e4) {
|
|
282
282
|
}
|
|
283
283
|
if (hasThumbnails) {
|
|
284
|
-
for (const thumbPath of
|
|
284
|
+
for (const thumbPath of _chunkWJJHVPLTjs.getAllThumbnailPaths.call(void 0, imageKey)) {
|
|
285
285
|
try {
|
|
286
286
|
await r2.send(
|
|
287
287
|
new (0, _clients3.DeleteObjectCommand)({
|
|
@@ -309,28 +309,61 @@ async function handleList(request) {
|
|
|
309
309
|
const items = [];
|
|
310
310
|
const seenFolders = /* @__PURE__ */ new Set();
|
|
311
311
|
const metaKeys = fileEntries.map(([key]) => key);
|
|
312
|
+
const isInsideImagesFolder = relativePath === "images" || relativePath.startsWith("images/");
|
|
312
313
|
const absoluteDir = _path2.default.join(process.cwd(), requestedPath);
|
|
313
314
|
try {
|
|
314
315
|
const dirEntries = await _fs.promises.readdir(absoluteDir, { withFileTypes: true });
|
|
315
316
|
for (const entry of dirEntries) {
|
|
316
|
-
if (entry.
|
|
317
|
+
if (entry.name.startsWith(".")) continue;
|
|
318
|
+
if (entry.isDirectory()) {
|
|
317
319
|
if (!seenFolders.has(entry.name)) {
|
|
318
320
|
seenFolders.add(entry.name);
|
|
319
|
-
const
|
|
321
|
+
const isImagesFolder = entry.name === "images" && !relativePath;
|
|
322
|
+
const folderPath = relativePath ? `public/${relativePath}/${entry.name}` : `public/${entry.name}`;
|
|
320
323
|
let fileCount = 0;
|
|
321
|
-
|
|
322
|
-
|
|
324
|
+
if (isInsideImagesFolder || isImagesFolder) {
|
|
325
|
+
const subDir = _path2.default.join(absoluteDir, entry.name);
|
|
326
|
+
try {
|
|
327
|
+
const subEntries = await _fs.promises.readdir(subDir);
|
|
328
|
+
fileCount = subEntries.filter((f) => !f.startsWith(".")).length;
|
|
329
|
+
} catch (e6) {
|
|
330
|
+
}
|
|
331
|
+
} else {
|
|
332
|
+
const folderPrefix = pathPrefix === "/" ? `/${entry.name}/` : `${pathPrefix}${entry.name}/`;
|
|
333
|
+
for (const k of metaKeys) {
|
|
334
|
+
if (k.startsWith(folderPrefix)) fileCount++;
|
|
335
|
+
}
|
|
323
336
|
}
|
|
324
337
|
items.push({
|
|
325
338
|
name: entry.name,
|
|
326
|
-
path:
|
|
339
|
+
path: folderPath,
|
|
327
340
|
type: "folder",
|
|
328
|
-
fileCount
|
|
341
|
+
fileCount,
|
|
342
|
+
isProtected: isImagesFolder || isInsideImagesFolder
|
|
329
343
|
});
|
|
330
344
|
}
|
|
345
|
+
} else if (isInsideImagesFolder) {
|
|
346
|
+
const filePath = relativePath ? `public/${relativePath}/${entry.name}` : `public/${entry.name}`;
|
|
347
|
+
const fullPath = _path2.default.join(absoluteDir, entry.name);
|
|
348
|
+
let fileSize;
|
|
349
|
+
try {
|
|
350
|
+
const stats = await _fs.promises.stat(fullPath);
|
|
351
|
+
fileSize = stats.size;
|
|
352
|
+
} catch (e7) {
|
|
353
|
+
}
|
|
354
|
+
const isImage = isImageFile(entry.name);
|
|
355
|
+
items.push({
|
|
356
|
+
name: entry.name,
|
|
357
|
+
path: filePath,
|
|
358
|
+
type: "file",
|
|
359
|
+
size: fileSize,
|
|
360
|
+
thumbnail: isImage ? `/${relativePath}/${entry.name}` : void 0,
|
|
361
|
+
hasThumbnail: false,
|
|
362
|
+
isProtected: true
|
|
363
|
+
});
|
|
331
364
|
}
|
|
332
365
|
}
|
|
333
|
-
} catch (
|
|
366
|
+
} catch (e8) {
|
|
334
367
|
}
|
|
335
368
|
if (fileEntries.length === 0 && items.length === 0) {
|
|
336
369
|
return _server.NextResponse.json({ items: [], isEmpty: true });
|
|
@@ -354,7 +387,8 @@ async function handleList(request) {
|
|
|
354
387
|
name: folderName,
|
|
355
388
|
path: relativePath ? `public/${relativePath}/${folderName}` : `public/${folderName}`,
|
|
356
389
|
type: "folder",
|
|
357
|
-
fileCount
|
|
390
|
+
fileCount,
|
|
391
|
+
isProtected: isInsideImagesFolder
|
|
358
392
|
});
|
|
359
393
|
}
|
|
360
394
|
} else {
|
|
@@ -367,7 +401,7 @@ async function handleList(request) {
|
|
|
367
401
|
let hasThumbnail = false;
|
|
368
402
|
let fileSize;
|
|
369
403
|
if (isImage && entry.p === 1) {
|
|
370
|
-
const thumbPath =
|
|
404
|
+
const thumbPath = _chunkWJJHVPLTjs.getThumbnailPath.call(void 0, key, "sm");
|
|
371
405
|
if (isPushedToCloud && entry.c !== void 0) {
|
|
372
406
|
const cdnUrl = cdnUrls[entry.c];
|
|
373
407
|
if (cdnUrl) {
|
|
@@ -380,7 +414,7 @@ async function handleList(request) {
|
|
|
380
414
|
await _fs.promises.access(localThumbPath);
|
|
381
415
|
thumbnail = thumbPath;
|
|
382
416
|
hasThumbnail = true;
|
|
383
|
-
} catch (
|
|
417
|
+
} catch (e9) {
|
|
384
418
|
thumbnail = key;
|
|
385
419
|
hasThumbnail = false;
|
|
386
420
|
}
|
|
@@ -399,7 +433,7 @@ async function handleList(request) {
|
|
|
399
433
|
const filePath = _path2.default.join(process.cwd(), "public", key);
|
|
400
434
|
const stats = await _fs.promises.stat(filePath);
|
|
401
435
|
fileSize = stats.size;
|
|
402
|
-
} catch (
|
|
436
|
+
} catch (e10) {
|
|
403
437
|
}
|
|
404
438
|
}
|
|
405
439
|
items.push({
|
|
@@ -413,6 +447,7 @@ async function handleList(request) {
|
|
|
413
447
|
cdnPushed: isPushedToCloud,
|
|
414
448
|
cdnBaseUrl: fileCdnUrl,
|
|
415
449
|
isRemote,
|
|
450
|
+
isProtected: isInsideImagesFolder,
|
|
416
451
|
dimensions: entry.w && entry.h ? { width: entry.w, height: entry.h } : void 0
|
|
417
452
|
});
|
|
418
453
|
}
|
|
@@ -446,7 +481,7 @@ async function handleSearch(request) {
|
|
|
446
481
|
let thumbnail;
|
|
447
482
|
let hasThumbnail = false;
|
|
448
483
|
if (isImage && entry.p === 1) {
|
|
449
|
-
const thumbPath =
|
|
484
|
+
const thumbPath = _chunkWJJHVPLTjs.getThumbnailPath.call(void 0, key, "sm");
|
|
450
485
|
if (isPushedToCloud && entry.c !== void 0) {
|
|
451
486
|
const cdnUrl = cdnUrls[entry.c];
|
|
452
487
|
if (cdnUrl) {
|
|
@@ -459,7 +494,7 @@ async function handleSearch(request) {
|
|
|
459
494
|
await _fs.promises.access(localThumbPath);
|
|
460
495
|
thumbnail = thumbPath;
|
|
461
496
|
hasThumbnail = true;
|
|
462
|
-
} catch (
|
|
497
|
+
} catch (e11) {
|
|
463
498
|
thumbnail = key;
|
|
464
499
|
hasThumbnail = false;
|
|
465
500
|
}
|
|
@@ -515,7 +550,7 @@ async function handleListFolders() {
|
|
|
515
550
|
await scanDir(_path2.default.join(dir, entry.name), folderRelPath);
|
|
516
551
|
}
|
|
517
552
|
}
|
|
518
|
-
} catch (
|
|
553
|
+
} catch (e12) {
|
|
519
554
|
}
|
|
520
555
|
}
|
|
521
556
|
const publicDir = _path2.default.join(process.cwd(), "public");
|
|
@@ -656,7 +691,7 @@ async function handleUpload(request) {
|
|
|
656
691
|
w: metadata.width || 0,
|
|
657
692
|
h: metadata.height || 0
|
|
658
693
|
};
|
|
659
|
-
} catch (
|
|
694
|
+
} catch (e13) {
|
|
660
695
|
meta[imageKey] = { w: 0, h: 0 };
|
|
661
696
|
}
|
|
662
697
|
} else {
|
|
@@ -701,11 +736,11 @@ async function handleDelete(request) {
|
|
|
701
736
|
for (const key of Object.keys(meta)) {
|
|
702
737
|
if (key.startsWith(prefix) || key === imageKey) {
|
|
703
738
|
if (!meta[key].c) {
|
|
704
|
-
for (const thumbPath of
|
|
739
|
+
for (const thumbPath of _chunkWJJHVPLTjs.getAllThumbnailPaths.call(void 0, key)) {
|
|
705
740
|
const absoluteThumbPath = _path2.default.join(process.cwd(), "public", thumbPath);
|
|
706
741
|
try {
|
|
707
742
|
await _fs.promises.unlink(absoluteThumbPath);
|
|
708
|
-
} catch (
|
|
743
|
+
} catch (e14) {
|
|
709
744
|
}
|
|
710
745
|
}
|
|
711
746
|
}
|
|
@@ -717,18 +752,18 @@ async function handleDelete(request) {
|
|
|
717
752
|
const isInImagesFolder = itemPath.startsWith("public/images/");
|
|
718
753
|
if (!isInImagesFolder && entry) {
|
|
719
754
|
if (!isPushedToCloud) {
|
|
720
|
-
for (const thumbPath of
|
|
755
|
+
for (const thumbPath of _chunkWJJHVPLTjs.getAllThumbnailPaths.call(void 0, imageKey)) {
|
|
721
756
|
const absoluteThumbPath = _path2.default.join(process.cwd(), "public", thumbPath);
|
|
722
757
|
try {
|
|
723
758
|
await _fs.promises.unlink(absoluteThumbPath);
|
|
724
|
-
} catch (
|
|
759
|
+
} catch (e15) {
|
|
725
760
|
}
|
|
726
761
|
}
|
|
727
762
|
}
|
|
728
763
|
delete meta[imageKey];
|
|
729
764
|
}
|
|
730
765
|
}
|
|
731
|
-
} catch (
|
|
766
|
+
} catch (e16) {
|
|
732
767
|
if (entry) {
|
|
733
768
|
delete meta[imageKey];
|
|
734
769
|
} else {
|
|
@@ -781,7 +816,7 @@ async function handleCreateFolder(request) {
|
|
|
781
816
|
try {
|
|
782
817
|
await _fs.promises.access(folderPath);
|
|
783
818
|
return _server.NextResponse.json({ error: "A folder with this name already exists" }, { status: 400 });
|
|
784
|
-
} catch (
|
|
819
|
+
} catch (e17) {
|
|
785
820
|
}
|
|
786
821
|
await _fs.promises.mkdir(folderPath, { recursive: true });
|
|
787
822
|
return _server.NextResponse.json({ success: true, path: _path2.default.join(safePath, sanitizedName) });
|
|
@@ -809,13 +844,13 @@ async function handleRename(request) {
|
|
|
809
844
|
}
|
|
810
845
|
try {
|
|
811
846
|
await _fs.promises.access(absoluteOldPath);
|
|
812
|
-
} catch (
|
|
847
|
+
} catch (e18) {
|
|
813
848
|
return _server.NextResponse.json({ error: "File or folder not found" }, { status: 404 });
|
|
814
849
|
}
|
|
815
850
|
try {
|
|
816
851
|
await _fs.promises.access(absoluteNewPath);
|
|
817
852
|
return _server.NextResponse.json({ error: "An item with this name already exists" }, { status: 400 });
|
|
818
|
-
} catch (
|
|
853
|
+
} catch (e19) {
|
|
819
854
|
}
|
|
820
855
|
const stats = await _fs.promises.stat(absoluteOldPath);
|
|
821
856
|
const isFile = stats.isFile();
|
|
@@ -829,15 +864,15 @@ async function handleRename(request) {
|
|
|
829
864
|
const newKey = "/" + newRelativePath;
|
|
830
865
|
if (meta[oldKey]) {
|
|
831
866
|
const entry = meta[oldKey];
|
|
832
|
-
const oldThumbPaths =
|
|
833
|
-
const newThumbPaths =
|
|
867
|
+
const oldThumbPaths = _chunkWJJHVPLTjs.getAllThumbnailPaths.call(void 0, oldKey);
|
|
868
|
+
const newThumbPaths = _chunkWJJHVPLTjs.getAllThumbnailPaths.call(void 0, newKey);
|
|
834
869
|
for (let i = 0; i < oldThumbPaths.length; i++) {
|
|
835
870
|
const oldThumbPath = _path2.default.join(process.cwd(), "public", oldThumbPaths[i]);
|
|
836
871
|
const newThumbPath = _path2.default.join(process.cwd(), "public", newThumbPaths[i]);
|
|
837
872
|
await _fs.promises.mkdir(_path2.default.dirname(newThumbPath), { recursive: true });
|
|
838
873
|
try {
|
|
839
874
|
await _fs.promises.rename(oldThumbPath, newThumbPath);
|
|
840
|
-
} catch (
|
|
875
|
+
} catch (e20) {
|
|
841
876
|
}
|
|
842
877
|
}
|
|
843
878
|
delete meta[oldKey];
|
|
@@ -952,7 +987,7 @@ async function handleMoveStream(request) {
|
|
|
952
987
|
await deleteFromCdn(oldKey, hasProcessedThumbnails);
|
|
953
988
|
try {
|
|
954
989
|
await _fs.promises.unlink(newAbsolutePath);
|
|
955
|
-
} catch (
|
|
990
|
+
} catch (e21) {
|
|
956
991
|
}
|
|
957
992
|
if (hasProcessedThumbnails) {
|
|
958
993
|
await deleteLocalThumbnails(newKey);
|
|
@@ -969,7 +1004,7 @@ async function handleMoveStream(request) {
|
|
|
969
1004
|
}
|
|
970
1005
|
try {
|
|
971
1006
|
await _fs.promises.access(absolutePath);
|
|
972
|
-
} catch (
|
|
1007
|
+
} catch (e22) {
|
|
973
1008
|
errors.push(`${itemName} not found`);
|
|
974
1009
|
continue;
|
|
975
1010
|
}
|
|
@@ -977,20 +1012,20 @@ async function handleMoveStream(request) {
|
|
|
977
1012
|
await _fs.promises.access(newAbsolutePath);
|
|
978
1013
|
errors.push(`${itemName} already exists in destination`);
|
|
979
1014
|
continue;
|
|
980
|
-
} catch (
|
|
1015
|
+
} catch (e23) {
|
|
981
1016
|
}
|
|
982
1017
|
await _fs.promises.rename(absolutePath, newAbsolutePath);
|
|
983
1018
|
const stats = await _fs.promises.stat(newAbsolutePath);
|
|
984
1019
|
if (stats.isFile() && isImage && entry) {
|
|
985
|
-
const oldThumbPaths =
|
|
986
|
-
const newThumbPaths =
|
|
1020
|
+
const oldThumbPaths = _chunkWJJHVPLTjs.getAllThumbnailPaths.call(void 0, oldKey);
|
|
1021
|
+
const newThumbPaths = _chunkWJJHVPLTjs.getAllThumbnailPaths.call(void 0, newKey);
|
|
987
1022
|
for (let j = 0; j < oldThumbPaths.length; j++) {
|
|
988
1023
|
const oldThumbPath = _path2.default.join(process.cwd(), "public", oldThumbPaths[j]);
|
|
989
1024
|
const newThumbPath = _path2.default.join(process.cwd(), "public", newThumbPaths[j]);
|
|
990
1025
|
await _fs.promises.mkdir(_path2.default.dirname(newThumbPath), { recursive: true });
|
|
991
1026
|
try {
|
|
992
1027
|
await _fs.promises.rename(oldThumbPath, newThumbPath);
|
|
993
|
-
} catch (
|
|
1028
|
+
} catch (e24) {
|
|
994
1029
|
}
|
|
995
1030
|
}
|
|
996
1031
|
delete meta[oldKey];
|
|
@@ -1092,7 +1127,7 @@ async function handleSync(request) {
|
|
|
1092
1127
|
const originalLocalPath = _path2.default.join(process.cwd(), "public", imageKey);
|
|
1093
1128
|
try {
|
|
1094
1129
|
originalBuffer = await _fs.promises.readFile(originalLocalPath);
|
|
1095
|
-
} catch (
|
|
1130
|
+
} catch (e25) {
|
|
1096
1131
|
errors.push(`Original file not found: ${imageKey}`);
|
|
1097
1132
|
continue;
|
|
1098
1133
|
}
|
|
@@ -1107,7 +1142,7 @@ async function handleSync(request) {
|
|
|
1107
1142
|
);
|
|
1108
1143
|
urlsToPurge.push(`${publicUrl}${imageKey}`);
|
|
1109
1144
|
if (!isRemote && entry.p) {
|
|
1110
|
-
for (const thumbPath of
|
|
1145
|
+
for (const thumbPath of _chunkWJJHVPLTjs.getAllThumbnailPaths.call(void 0, imageKey)) {
|
|
1111
1146
|
const localPath = _path2.default.join(process.cwd(), "public", thumbPath);
|
|
1112
1147
|
try {
|
|
1113
1148
|
const fileBuffer = await _fs.promises.readFile(localPath);
|
|
@@ -1120,23 +1155,23 @@ async function handleSync(request) {
|
|
|
1120
1155
|
})
|
|
1121
1156
|
);
|
|
1122
1157
|
urlsToPurge.push(`${publicUrl}${thumbPath}`);
|
|
1123
|
-
} catch (
|
|
1158
|
+
} catch (e26) {
|
|
1124
1159
|
}
|
|
1125
1160
|
}
|
|
1126
1161
|
}
|
|
1127
1162
|
entry.c = cdnIndex;
|
|
1128
1163
|
if (!isRemote) {
|
|
1129
1164
|
const originalLocalPath = _path2.default.join(process.cwd(), "public", imageKey);
|
|
1130
|
-
for (const thumbPath of
|
|
1165
|
+
for (const thumbPath of _chunkWJJHVPLTjs.getAllThumbnailPaths.call(void 0, imageKey)) {
|
|
1131
1166
|
const localPath = _path2.default.join(process.cwd(), "public", thumbPath);
|
|
1132
1167
|
try {
|
|
1133
1168
|
await _fs.promises.unlink(localPath);
|
|
1134
|
-
} catch (
|
|
1169
|
+
} catch (e27) {
|
|
1135
1170
|
}
|
|
1136
1171
|
}
|
|
1137
1172
|
try {
|
|
1138
1173
|
await _fs.promises.unlink(originalLocalPath);
|
|
1139
|
-
} catch (
|
|
1174
|
+
} catch (e28) {
|
|
1140
1175
|
}
|
|
1141
1176
|
}
|
|
1142
1177
|
pushed.push(imageKey);
|
|
@@ -1182,7 +1217,7 @@ async function handleReprocess(request) {
|
|
|
1182
1217
|
const originalPath = _path2.default.join(process.cwd(), "public", imageKey);
|
|
1183
1218
|
try {
|
|
1184
1219
|
buffer = await _fs.promises.readFile(originalPath);
|
|
1185
|
-
} catch (
|
|
1220
|
+
} catch (e29) {
|
|
1186
1221
|
if (isInOurR2) {
|
|
1187
1222
|
buffer = await downloadFromCdn(imageKey);
|
|
1188
1223
|
const dir = _path2.default.dirname(originalPath);
|
|
@@ -1202,13 +1237,13 @@ async function handleReprocess(request) {
|
|
|
1202
1237
|
if (isInOurR2) {
|
|
1203
1238
|
updatedEntry.c = existingCdnIndex;
|
|
1204
1239
|
await uploadToCdn(imageKey);
|
|
1205
|
-
for (const thumbPath of
|
|
1240
|
+
for (const thumbPath of _chunkWJJHVPLTjs.getAllThumbnailPaths.call(void 0, imageKey)) {
|
|
1206
1241
|
urlsToPurge.push(`${publicUrl}${thumbPath}`);
|
|
1207
1242
|
}
|
|
1208
1243
|
await deleteLocalThumbnails(imageKey);
|
|
1209
1244
|
try {
|
|
1210
1245
|
await _fs.promises.unlink(originalPath);
|
|
1211
|
-
} catch (
|
|
1246
|
+
} catch (e30) {
|
|
1212
1247
|
}
|
|
1213
1248
|
} else if (isRemote) {
|
|
1214
1249
|
}
|
|
@@ -1323,13 +1358,13 @@ async function handleProcessAllStream() {
|
|
|
1323
1358
|
}
|
|
1324
1359
|
if (isInOurR2) {
|
|
1325
1360
|
await uploadToCdn(key);
|
|
1326
|
-
for (const thumbPath of
|
|
1361
|
+
for (const thumbPath of _chunkWJJHVPLTjs.getAllThumbnailPaths.call(void 0, key)) {
|
|
1327
1362
|
urlsToPurge.push(`${publicUrl}${thumbPath}`);
|
|
1328
1363
|
}
|
|
1329
1364
|
await deleteLocalThumbnails(key);
|
|
1330
1365
|
try {
|
|
1331
1366
|
await _fs.promises.unlink(fullPath);
|
|
1332
|
-
} catch (
|
|
1367
|
+
} catch (e31) {
|
|
1333
1368
|
}
|
|
1334
1369
|
}
|
|
1335
1370
|
processed.push(key.slice(1));
|
|
@@ -1342,7 +1377,7 @@ async function handleProcessAllStream() {
|
|
|
1342
1377
|
const trackedPaths = /* @__PURE__ */ new Set();
|
|
1343
1378
|
for (const [imageKey, entry] of getFileEntries(meta)) {
|
|
1344
1379
|
if (entry.c === void 0) {
|
|
1345
|
-
for (const thumbPath of
|
|
1380
|
+
for (const thumbPath of _chunkWJJHVPLTjs.getAllThumbnailPaths.call(void 0, imageKey)) {
|
|
1346
1381
|
trackedPaths.add(thumbPath);
|
|
1347
1382
|
}
|
|
1348
1383
|
}
|
|
@@ -1368,13 +1403,13 @@ async function handleProcessAllStream() {
|
|
|
1368
1403
|
}
|
|
1369
1404
|
}
|
|
1370
1405
|
}
|
|
1371
|
-
} catch (
|
|
1406
|
+
} catch (e32) {
|
|
1372
1407
|
}
|
|
1373
1408
|
}
|
|
1374
1409
|
const imagesDir = _path2.default.join(process.cwd(), "public", "images");
|
|
1375
1410
|
try {
|
|
1376
1411
|
await findOrphans(imagesDir);
|
|
1377
|
-
} catch (
|
|
1412
|
+
} catch (e33) {
|
|
1378
1413
|
}
|
|
1379
1414
|
async function removeEmptyDirs(dir) {
|
|
1380
1415
|
try {
|
|
@@ -1392,13 +1427,13 @@ async function handleProcessAllStream() {
|
|
|
1392
1427
|
await _fs.promises.rmdir(dir);
|
|
1393
1428
|
}
|
|
1394
1429
|
return isEmpty;
|
|
1395
|
-
} catch (
|
|
1430
|
+
} catch (e34) {
|
|
1396
1431
|
return true;
|
|
1397
1432
|
}
|
|
1398
1433
|
}
|
|
1399
1434
|
try {
|
|
1400
1435
|
await removeEmptyDirs(imagesDir);
|
|
1401
|
-
} catch (
|
|
1436
|
+
} catch (e35) {
|
|
1402
1437
|
}
|
|
1403
1438
|
await saveMeta(meta);
|
|
1404
1439
|
if (urlsToPurge.length > 0) {
|
|
@@ -1433,6 +1468,7 @@ async function handleProcessAllStream() {
|
|
|
1433
1468
|
|
|
1434
1469
|
|
|
1435
1470
|
|
|
1471
|
+
|
|
1436
1472
|
async function handleScanStream() {
|
|
1437
1473
|
const encoder = new TextEncoder();
|
|
1438
1474
|
const stream = new ReadableStream({
|
|
@@ -1444,11 +1480,12 @@ async function handleScanStream() {
|
|
|
1444
1480
|
};
|
|
1445
1481
|
try {
|
|
1446
1482
|
const meta = await loadMeta();
|
|
1447
|
-
const existingCount = Object.keys(meta).length;
|
|
1483
|
+
const existingCount = Object.keys(meta).filter((k) => !k.startsWith("_")).length;
|
|
1448
1484
|
const existingKeys = new Set(Object.keys(meta));
|
|
1449
1485
|
const added = [];
|
|
1450
1486
|
const renamed = [];
|
|
1451
1487
|
const errors = [];
|
|
1488
|
+
const orphanedFiles = [];
|
|
1452
1489
|
const allFiles = [];
|
|
1453
1490
|
async function scanDir(dir, relativePath = "") {
|
|
1454
1491
|
try {
|
|
@@ -1464,7 +1501,7 @@ async function handleScanStream() {
|
|
|
1464
1501
|
allFiles.push({ relativePath: relPath, fullPath });
|
|
1465
1502
|
}
|
|
1466
1503
|
}
|
|
1467
|
-
} catch (
|
|
1504
|
+
} catch (e36) {
|
|
1468
1505
|
}
|
|
1469
1506
|
}
|
|
1470
1507
|
const publicDir = _path2.default.join(process.cwd(), "public");
|
|
@@ -1524,7 +1561,7 @@ async function handleScanStream() {
|
|
|
1524
1561
|
h: metadata.height || 0,
|
|
1525
1562
|
b: blurhash
|
|
1526
1563
|
};
|
|
1527
|
-
} catch (
|
|
1564
|
+
} catch (e37) {
|
|
1528
1565
|
meta[imageKey] = { w: 0, h: 0 };
|
|
1529
1566
|
}
|
|
1530
1567
|
}
|
|
@@ -1538,6 +1575,40 @@ async function handleScanStream() {
|
|
|
1538
1575
|
errors.push(relativePath);
|
|
1539
1576
|
}
|
|
1540
1577
|
}
|
|
1578
|
+
sendEvent({ type: "cleanup", message: "Checking for orphaned thumbnails..." });
|
|
1579
|
+
const expectedThumbnails = /* @__PURE__ */ new Set();
|
|
1580
|
+
const fileEntries = getFileEntries(meta);
|
|
1581
|
+
for (const [imageKey, entry] of fileEntries) {
|
|
1582
|
+
if (entry.c === void 0 && entry.p === 1) {
|
|
1583
|
+
for (const thumbPath of _chunkWJJHVPLTjs.getAllThumbnailPaths.call(void 0, imageKey)) {
|
|
1584
|
+
expectedThumbnails.add(thumbPath);
|
|
1585
|
+
}
|
|
1586
|
+
}
|
|
1587
|
+
}
|
|
1588
|
+
async function findOrphans(dir, relativePath = "") {
|
|
1589
|
+
try {
|
|
1590
|
+
const entries = await _fs.promises.readdir(dir, { withFileTypes: true });
|
|
1591
|
+
for (const entry of entries) {
|
|
1592
|
+
if (entry.name.startsWith(".")) continue;
|
|
1593
|
+
const fullPath = _path2.default.join(dir, entry.name);
|
|
1594
|
+
const relPath = relativePath ? `${relativePath}/${entry.name}` : entry.name;
|
|
1595
|
+
if (entry.isDirectory()) {
|
|
1596
|
+
await findOrphans(fullPath, relPath);
|
|
1597
|
+
} else if (isImageFile(entry.name)) {
|
|
1598
|
+
const publicPath = `/images/${relPath}`;
|
|
1599
|
+
if (!expectedThumbnails.has(publicPath)) {
|
|
1600
|
+
orphanedFiles.push(publicPath);
|
|
1601
|
+
}
|
|
1602
|
+
}
|
|
1603
|
+
}
|
|
1604
|
+
} catch (e38) {
|
|
1605
|
+
}
|
|
1606
|
+
}
|
|
1607
|
+
const imagesDir = _path2.default.join(process.cwd(), "public", "images");
|
|
1608
|
+
try {
|
|
1609
|
+
await findOrphans(imagesDir);
|
|
1610
|
+
} catch (e39) {
|
|
1611
|
+
}
|
|
1541
1612
|
await saveMeta(meta);
|
|
1542
1613
|
sendEvent({
|
|
1543
1614
|
type: "complete",
|
|
@@ -1545,7 +1616,8 @@ async function handleScanStream() {
|
|
|
1545
1616
|
added: added.length,
|
|
1546
1617
|
renamed: renamed.length,
|
|
1547
1618
|
errors: errors.length,
|
|
1548
|
-
renamedFiles: renamed
|
|
1619
|
+
renamedFiles: renamed,
|
|
1620
|
+
orphanedFiles: orphanedFiles.length > 0 ? orphanedFiles : void 0
|
|
1549
1621
|
});
|
|
1550
1622
|
} catch (error) {
|
|
1551
1623
|
console.error("Scan failed:", error);
|
|
@@ -1563,6 +1635,63 @@ async function handleScanStream() {
|
|
|
1563
1635
|
}
|
|
1564
1636
|
});
|
|
1565
1637
|
}
|
|
1638
|
+
async function handleDeleteOrphans(request) {
|
|
1639
|
+
try {
|
|
1640
|
+
const { paths } = await request.json();
|
|
1641
|
+
if (!paths || !Array.isArray(paths) || paths.length === 0) {
|
|
1642
|
+
return _server.NextResponse.json({ error: "No paths provided" }, { status: 400 });
|
|
1643
|
+
}
|
|
1644
|
+
const deleted = [];
|
|
1645
|
+
const errors = [];
|
|
1646
|
+
for (const orphanPath of paths) {
|
|
1647
|
+
if (!orphanPath.startsWith("/images/")) {
|
|
1648
|
+
errors.push(`Invalid path: ${orphanPath}`);
|
|
1649
|
+
continue;
|
|
1650
|
+
}
|
|
1651
|
+
const fullPath = _path2.default.join(process.cwd(), "public", orphanPath);
|
|
1652
|
+
try {
|
|
1653
|
+
await _fs.promises.unlink(fullPath);
|
|
1654
|
+
deleted.push(orphanPath);
|
|
1655
|
+
} catch (err) {
|
|
1656
|
+
console.error(`Failed to delete ${orphanPath}:`, err);
|
|
1657
|
+
errors.push(orphanPath);
|
|
1658
|
+
}
|
|
1659
|
+
}
|
|
1660
|
+
const imagesDir = _path2.default.join(process.cwd(), "public", "images");
|
|
1661
|
+
async function removeEmptyDirs(dir) {
|
|
1662
|
+
try {
|
|
1663
|
+
const entries = await _fs.promises.readdir(dir, { withFileTypes: true });
|
|
1664
|
+
let isEmpty = true;
|
|
1665
|
+
for (const entry of entries) {
|
|
1666
|
+
if (entry.isDirectory()) {
|
|
1667
|
+
const subDirEmpty = await removeEmptyDirs(_path2.default.join(dir, entry.name));
|
|
1668
|
+
if (!subDirEmpty) isEmpty = false;
|
|
1669
|
+
} else {
|
|
1670
|
+
isEmpty = false;
|
|
1671
|
+
}
|
|
1672
|
+
}
|
|
1673
|
+
if (isEmpty && dir !== imagesDir) {
|
|
1674
|
+
await _fs.promises.rmdir(dir);
|
|
1675
|
+
}
|
|
1676
|
+
return isEmpty;
|
|
1677
|
+
} catch (e40) {
|
|
1678
|
+
return true;
|
|
1679
|
+
}
|
|
1680
|
+
}
|
|
1681
|
+
try {
|
|
1682
|
+
await removeEmptyDirs(imagesDir);
|
|
1683
|
+
} catch (e41) {
|
|
1684
|
+
}
|
|
1685
|
+
return _server.NextResponse.json({
|
|
1686
|
+
success: true,
|
|
1687
|
+
deleted: deleted.length,
|
|
1688
|
+
errors: errors.length
|
|
1689
|
+
});
|
|
1690
|
+
} catch (error) {
|
|
1691
|
+
console.error("Failed to delete orphans:", error);
|
|
1692
|
+
return _server.NextResponse.json({ error: "Failed to delete orphaned files" }, { status: 500 });
|
|
1693
|
+
}
|
|
1694
|
+
}
|
|
1566
1695
|
|
|
1567
1696
|
// src/handlers/import.ts
|
|
1568
1697
|
|
|
@@ -1750,6 +1879,9 @@ async function POST(request) {
|
|
|
1750
1879
|
if (route === "scan") {
|
|
1751
1880
|
return handleScanStream();
|
|
1752
1881
|
}
|
|
1882
|
+
if (route === "delete-orphans") {
|
|
1883
|
+
return handleDeleteOrphans(request);
|
|
1884
|
+
}
|
|
1753
1885
|
if (route === "import") {
|
|
1754
1886
|
return handleImportUrls(request);
|
|
1755
1887
|
}
|