@gallop.software/studio 1.3.5 → 1.4.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.
- package/dist/{StudioUI-DNDMPY7Q.mjs → StudioUI-XLQBCXNU.mjs} +147 -2
- package/dist/StudioUI-XLQBCXNU.mjs.map +1 -0
- package/dist/{StudioUI-OY33OEG2.js → StudioUI-YRXPBDUX.js} +148 -3
- package/dist/StudioUI-YRXPBDUX.js.map +1 -0
- package/dist/handlers/index.js +154 -41
- package/dist/handlers/index.js.map +1 -1
- package/dist/handlers/index.mjs +114 -1
- package/dist/handlers/index.mjs.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.mjs +1 -1
- package/package.json +1 -1
- package/dist/StudioUI-DNDMPY7Q.mjs.map +0 -1
- package/dist/StudioUI-OY33OEG2.js.map +0 -1
package/dist/handlers/index.mjs
CHANGED
|
@@ -309,6 +309,22 @@ async function deleteFromCdn(imageKey, hasThumbnails) {
|
|
|
309
309
|
}
|
|
310
310
|
}
|
|
311
311
|
}
|
|
312
|
+
async function deleteThumbnailsFromCdn(imageKey) {
|
|
313
|
+
const bucketName = process.env.CLOUDFLARE_R2_BUCKET_NAME;
|
|
314
|
+
if (!bucketName) throw new Error("R2 bucket not configured");
|
|
315
|
+
const r2 = getR2Client();
|
|
316
|
+
for (const thumbPath of getAllThumbnailPaths(imageKey)) {
|
|
317
|
+
try {
|
|
318
|
+
await r2.send(
|
|
319
|
+
new DeleteObjectCommand({
|
|
320
|
+
Bucket: bucketName,
|
|
321
|
+
Key: thumbPath.replace(/^\//, "")
|
|
322
|
+
})
|
|
323
|
+
);
|
|
324
|
+
} catch {
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
}
|
|
312
328
|
|
|
313
329
|
// src/handlers/list.ts
|
|
314
330
|
function getExistingThumbnails(originalPath, entry) {
|
|
@@ -1393,6 +1409,101 @@ async function handleReprocess(request) {
|
|
|
1393
1409
|
return NextResponse3.json({ error: "Failed to reprocess images" }, { status: 500 });
|
|
1394
1410
|
}
|
|
1395
1411
|
}
|
|
1412
|
+
async function handleUnprocessStream(request) {
|
|
1413
|
+
const publicUrl = process.env.CLOUDFLARE_R2_PUBLIC_URL?.replace(/\/\s*$/, "");
|
|
1414
|
+
const encoder = new TextEncoder();
|
|
1415
|
+
let imageKeys;
|
|
1416
|
+
try {
|
|
1417
|
+
const body = await request.json();
|
|
1418
|
+
imageKeys = body.imageKeys;
|
|
1419
|
+
if (!imageKeys || !Array.isArray(imageKeys) || imageKeys.length === 0) {
|
|
1420
|
+
return NextResponse3.json({ error: "No image keys provided" }, { status: 400 });
|
|
1421
|
+
}
|
|
1422
|
+
} catch {
|
|
1423
|
+
return NextResponse3.json({ error: "Invalid request body" }, { status: 400 });
|
|
1424
|
+
}
|
|
1425
|
+
const stream = new ReadableStream({
|
|
1426
|
+
async start(controller) {
|
|
1427
|
+
const sendEvent = (data) => {
|
|
1428
|
+
controller.enqueue(encoder.encode(`data: ${JSON.stringify(data)}
|
|
1429
|
+
|
|
1430
|
+
`));
|
|
1431
|
+
};
|
|
1432
|
+
try {
|
|
1433
|
+
const meta = await loadMeta();
|
|
1434
|
+
const cdnUrls = getCdnUrls(meta);
|
|
1435
|
+
const unprocessed = [];
|
|
1436
|
+
const errors = [];
|
|
1437
|
+
const urlsToPurge = [];
|
|
1438
|
+
const total = imageKeys.length;
|
|
1439
|
+
sendEvent({ type: "start", total });
|
|
1440
|
+
for (let i = 0; i < imageKeys.length; i++) {
|
|
1441
|
+
let imageKey = imageKeys[i];
|
|
1442
|
+
if (!imageKey.startsWith("/")) {
|
|
1443
|
+
imageKey = `/${imageKey}`;
|
|
1444
|
+
}
|
|
1445
|
+
sendEvent({
|
|
1446
|
+
type: "progress",
|
|
1447
|
+
current: i + 1,
|
|
1448
|
+
total,
|
|
1449
|
+
percent: Math.round((i + 1) / total * 100),
|
|
1450
|
+
message: `Removing thumbnails for ${imageKey.slice(1)}...`
|
|
1451
|
+
});
|
|
1452
|
+
try {
|
|
1453
|
+
const entry = getMetaEntry(meta, imageKey);
|
|
1454
|
+
if (!entry) {
|
|
1455
|
+
errors.push(imageKey);
|
|
1456
|
+
continue;
|
|
1457
|
+
}
|
|
1458
|
+
const existingCdnIndex = entry.c;
|
|
1459
|
+
const existingCdnUrl = existingCdnIndex !== void 0 ? cdnUrls[existingCdnIndex] : void 0;
|
|
1460
|
+
const isInOurR2 = existingCdnUrl === publicUrl;
|
|
1461
|
+
await deleteLocalThumbnails(imageKey);
|
|
1462
|
+
if (isInOurR2) {
|
|
1463
|
+
await deleteThumbnailsFromCdn(imageKey);
|
|
1464
|
+
for (const thumbPath of getAllThumbnailPaths(imageKey)) {
|
|
1465
|
+
urlsToPurge.push(`${publicUrl}${thumbPath}`);
|
|
1466
|
+
}
|
|
1467
|
+
}
|
|
1468
|
+
meta[imageKey] = {
|
|
1469
|
+
o: entry.o,
|
|
1470
|
+
b: entry.b,
|
|
1471
|
+
...entry.c !== void 0 ? { c: entry.c } : {}
|
|
1472
|
+
};
|
|
1473
|
+
unprocessed.push(imageKey);
|
|
1474
|
+
} catch (error) {
|
|
1475
|
+
console.error(`Failed to unprocess ${imageKey}:`, error);
|
|
1476
|
+
errors.push(imageKey);
|
|
1477
|
+
}
|
|
1478
|
+
}
|
|
1479
|
+
sendEvent({ type: "cleanup", message: "Saving metadata..." });
|
|
1480
|
+
await saveMeta(meta);
|
|
1481
|
+
if (urlsToPurge.length > 0) {
|
|
1482
|
+
sendEvent({ type: "cleanup", message: "Purging CDN cache..." });
|
|
1483
|
+
await purgeCloudflareCache(urlsToPurge);
|
|
1484
|
+
}
|
|
1485
|
+
sendEvent({
|
|
1486
|
+
type: "complete",
|
|
1487
|
+
processed: unprocessed.length,
|
|
1488
|
+
errors: errors.length,
|
|
1489
|
+
message: `Removed thumbnails from ${unprocessed.length} image${unprocessed.length !== 1 ? "s" : ""}${errors.length > 0 ? `, ${errors.length} error${errors.length !== 1 ? "s" : ""}` : ""}`
|
|
1490
|
+
});
|
|
1491
|
+
controller.close();
|
|
1492
|
+
} catch (error) {
|
|
1493
|
+
console.error("Unprocess stream error:", error);
|
|
1494
|
+
sendEvent({ type: "error", message: "Failed to remove thumbnails" });
|
|
1495
|
+
controller.close();
|
|
1496
|
+
}
|
|
1497
|
+
}
|
|
1498
|
+
});
|
|
1499
|
+
return new Response(stream, {
|
|
1500
|
+
headers: {
|
|
1501
|
+
"Content-Type": "text/event-stream",
|
|
1502
|
+
"Cache-Control": "no-cache",
|
|
1503
|
+
Connection: "keep-alive"
|
|
1504
|
+
}
|
|
1505
|
+
});
|
|
1506
|
+
}
|
|
1396
1507
|
async function handleReprocessStream(request) {
|
|
1397
1508
|
const publicUrl = process.env.CLOUDFLARE_R2_PUBLIC_URL?.replace(/\/\s*$/, "");
|
|
1398
1509
|
const encoder = new TextEncoder();
|
|
@@ -1433,7 +1544,6 @@ async function handleReprocessStream(request) {
|
|
|
1433
1544
|
percent: Math.round((i + 1) / total * 100),
|
|
1434
1545
|
message: `Processing ${imageKey.slice(1)}...`
|
|
1435
1546
|
});
|
|
1436
|
-
await new Promise((resolve) => setTimeout(resolve, 1e3));
|
|
1437
1547
|
try {
|
|
1438
1548
|
let buffer;
|
|
1439
1549
|
const entry = getMetaEntry(meta, imageKey);
|
|
@@ -2123,6 +2233,9 @@ async function POST(request) {
|
|
|
2123
2233
|
if (route === "reprocess-stream") {
|
|
2124
2234
|
return handleReprocessStream(request);
|
|
2125
2235
|
}
|
|
2236
|
+
if (route === "unprocess-stream") {
|
|
2237
|
+
return handleUnprocessStream(request);
|
|
2238
|
+
}
|
|
2126
2239
|
if (route === "process-all") {
|
|
2127
2240
|
return handleProcessAllStream();
|
|
2128
2241
|
}
|