@gallop.software/studio 0.1.92 → 0.1.94
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-7VQHCHFQ.mjs → StudioUI-GKE5ZWKW.mjs} +1155 -585
- package/dist/StudioUI-GKE5ZWKW.mjs.map +1 -0
- package/dist/{StudioUI-VRSG32E3.js → StudioUI-QBIGDYYL.js} +811 -241
- package/dist/StudioUI-QBIGDYYL.js.map +1 -0
- package/dist/{chunk-DTVEVFQ2.mjs → chunk-IHXG2EE4.mjs} +1 -1
- package/dist/chunk-IHXG2EE4.mjs.map +1 -0
- package/dist/{chunk-L36EH3PM.js → chunk-MCJNUXQ6.js} +1 -1
- package/dist/chunk-MCJNUXQ6.js.map +1 -0
- package/dist/handlers/index.d.mts +1 -15
- package/dist/handlers/index.d.ts +1 -15
- package/dist/handlers/index.js +222 -42
- package/dist/handlers/index.js.map +1 -1
- package/dist/handlers/index.mjs +209 -29
- package/dist/handlers/index.mjs.map +1 -1
- package/dist/index.d.mts +62 -2
- package/dist/index.d.ts +62 -2
- package/dist/index.js +3 -3
- package/dist/index.mjs +2 -2
- package/package.json +1 -1
- package/dist/StudioUI-7VQHCHFQ.mjs.map +0 -1
- package/dist/StudioUI-VRSG32E3.js.map +0 -1
- package/dist/chunk-DTVEVFQ2.mjs.map +0 -1
- package/dist/chunk-L36EH3PM.js.map +0 -1
- package/dist/types-C4hCz2w8.d.mts +0 -62
- package/dist/types-C4hCz2w8.d.ts +0 -62
package/dist/handlers/index.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
2
|
getAllThumbnailPaths,
|
|
3
3
|
getThumbnailPath
|
|
4
|
-
} from "../chunk-
|
|
4
|
+
} from "../chunk-IHXG2EE4.mjs";
|
|
5
5
|
|
|
6
6
|
// src/handlers/index.ts
|
|
7
7
|
import { NextResponse as NextResponse4 } from "next/server";
|
|
@@ -29,6 +29,35 @@ async function saveMeta(meta) {
|
|
|
29
29
|
const metaPath = path.join(dataDir, "_meta.json");
|
|
30
30
|
await fs.writeFile(metaPath, JSON.stringify(meta, null, 2));
|
|
31
31
|
}
|
|
32
|
+
function getCdnUrls(meta) {
|
|
33
|
+
return meta._cdns || [];
|
|
34
|
+
}
|
|
35
|
+
function getOrAddCdnIndex(meta, cdnUrl) {
|
|
36
|
+
if (!meta._cdns) {
|
|
37
|
+
meta._cdns = [];
|
|
38
|
+
}
|
|
39
|
+
const normalizedUrl = cdnUrl.replace(/\/$/, "");
|
|
40
|
+
const existingIndex = meta._cdns.indexOf(normalizedUrl);
|
|
41
|
+
if (existingIndex >= 0) {
|
|
42
|
+
return existingIndex;
|
|
43
|
+
}
|
|
44
|
+
meta._cdns.push(normalizedUrl);
|
|
45
|
+
return meta._cdns.length - 1;
|
|
46
|
+
}
|
|
47
|
+
function getMetaEntry(meta, key) {
|
|
48
|
+
if (key.startsWith("_")) return void 0;
|
|
49
|
+
const value = meta[key];
|
|
50
|
+
if (Array.isArray(value)) return void 0;
|
|
51
|
+
return value;
|
|
52
|
+
}
|
|
53
|
+
function setMetaEntry(meta, key, entry) {
|
|
54
|
+
meta[key] = entry;
|
|
55
|
+
}
|
|
56
|
+
function getFileEntries(meta) {
|
|
57
|
+
return Object.entries(meta).filter(
|
|
58
|
+
([key, value]) => !key.startsWith("_") && !Array.isArray(value)
|
|
59
|
+
);
|
|
60
|
+
}
|
|
32
61
|
|
|
33
62
|
// src/handlers/utils/files.ts
|
|
34
63
|
import path2 from "path";
|
|
@@ -188,16 +217,17 @@ async function handleList(request) {
|
|
|
188
217
|
const requestedPath = searchParams.get("path") || "public";
|
|
189
218
|
try {
|
|
190
219
|
const meta = await loadMeta();
|
|
191
|
-
const
|
|
192
|
-
|
|
220
|
+
const fileEntries = getFileEntries(meta);
|
|
221
|
+
const cdnUrls = getCdnUrls(meta);
|
|
222
|
+
if (fileEntries.length === 0) {
|
|
193
223
|
return NextResponse.json({ items: [], isEmpty: true });
|
|
194
224
|
}
|
|
195
225
|
const relativePath = requestedPath.replace(/^public\/?/, "");
|
|
196
226
|
const pathPrefix = relativePath ? `/${relativePath}/` : "/";
|
|
197
227
|
const items = [];
|
|
198
228
|
const seenFolders = /* @__PURE__ */ new Set();
|
|
199
|
-
|
|
200
|
-
|
|
229
|
+
const metaKeys = fileEntries.map(([key]) => key);
|
|
230
|
+
for (const [key, entry] of fileEntries) {
|
|
201
231
|
if (!key.startsWith(pathPrefix) && pathPrefix !== "/") continue;
|
|
202
232
|
if (pathPrefix === "/" && !key.startsWith("/")) continue;
|
|
203
233
|
const remaining = pathPrefix === "/" ? key.slice(1) : key.slice(pathPrefix.length);
|
|
@@ -222,14 +252,14 @@ async function handleList(request) {
|
|
|
222
252
|
} else {
|
|
223
253
|
const fileName = remaining;
|
|
224
254
|
const isImage = isImageFile(fileName);
|
|
225
|
-
const isPushedToCloud = entry.c
|
|
255
|
+
const isPushedToCloud = entry.c !== void 0;
|
|
226
256
|
let thumbnail;
|
|
227
257
|
let hasThumbnail = false;
|
|
228
258
|
let fileSize;
|
|
229
259
|
if (isImage && (entry.w || entry.b)) {
|
|
230
260
|
const thumbPath = getThumbnailPath(key, "sm");
|
|
231
|
-
if (isPushedToCloud) {
|
|
232
|
-
const cdnUrl =
|
|
261
|
+
if (isPushedToCloud && entry.c !== void 0) {
|
|
262
|
+
const cdnUrl = cdnUrls[entry.c];
|
|
233
263
|
if (cdnUrl) {
|
|
234
264
|
thumbnail = `${cdnUrl}${thumbPath}`;
|
|
235
265
|
hasThumbnail = true;
|
|
@@ -284,19 +314,21 @@ async function handleSearch(request) {
|
|
|
284
314
|
}
|
|
285
315
|
try {
|
|
286
316
|
const meta = await loadMeta();
|
|
317
|
+
const fileEntries = getFileEntries(meta);
|
|
318
|
+
const cdnUrls = getCdnUrls(meta);
|
|
287
319
|
const items = [];
|
|
288
|
-
for (const [key, entry] of
|
|
320
|
+
for (const [key, entry] of fileEntries) {
|
|
289
321
|
if (!key.toLowerCase().includes(query)) continue;
|
|
290
322
|
const fileName = path5.basename(key);
|
|
291
323
|
const relativePath = key.slice(1);
|
|
292
324
|
const isImage = isImageFile(fileName);
|
|
293
|
-
const isPushedToCloud = entry.c
|
|
325
|
+
const isPushedToCloud = entry.c !== void 0;
|
|
294
326
|
let thumbnail;
|
|
295
327
|
let hasThumbnail = false;
|
|
296
328
|
if (isImage && (entry.w || entry.b)) {
|
|
297
329
|
const thumbPath = getThumbnailPath(key, "sm");
|
|
298
|
-
if (isPushedToCloud) {
|
|
299
|
-
const cdnUrl =
|
|
330
|
+
if (isPushedToCloud && entry.c !== void 0) {
|
|
331
|
+
const cdnUrl = cdnUrls[entry.c];
|
|
300
332
|
if (cdnUrl) {
|
|
301
333
|
thumbnail = `${cdnUrl}${thumbPath}`;
|
|
302
334
|
hasThumbnail = true;
|
|
@@ -336,8 +368,9 @@ async function handleSearch(request) {
|
|
|
336
368
|
async function handleListFolders() {
|
|
337
369
|
try {
|
|
338
370
|
const meta = await loadMeta();
|
|
371
|
+
const fileEntries = getFileEntries(meta);
|
|
339
372
|
const folderSet = /* @__PURE__ */ new Set();
|
|
340
|
-
for (const key of
|
|
373
|
+
for (const [key] of fileEntries) {
|
|
341
374
|
const parts = key.split("/");
|
|
342
375
|
let current = "";
|
|
343
376
|
for (let i = 1; i < parts.length - 1; i++) {
|
|
@@ -366,8 +399,9 @@ async function handleListFolders() {
|
|
|
366
399
|
async function handleCountImages() {
|
|
367
400
|
try {
|
|
368
401
|
const meta = await loadMeta();
|
|
402
|
+
const fileEntries = getFileEntries(meta);
|
|
369
403
|
const allImages = [];
|
|
370
|
-
for (const key of
|
|
404
|
+
for (const [key] of fileEntries) {
|
|
371
405
|
const fileName = path5.basename(key);
|
|
372
406
|
if (isImageFile(fileName)) {
|
|
373
407
|
allImages.push(key.slice(1));
|
|
@@ -391,12 +425,13 @@ async function handleFolderImages(request) {
|
|
|
391
425
|
}
|
|
392
426
|
const folders = foldersParam.split(",");
|
|
393
427
|
const meta = await loadMeta();
|
|
428
|
+
const fileEntries = getFileEntries(meta);
|
|
394
429
|
const allImages = [];
|
|
395
430
|
const prefixes = folders.map((f) => {
|
|
396
431
|
const rel = f.replace(/^public\/?/, "");
|
|
397
432
|
return rel ? `/${rel}/` : "/";
|
|
398
433
|
});
|
|
399
|
-
for (const key of
|
|
434
|
+
for (const [key] of fileEntries) {
|
|
400
435
|
const fileName = path5.basename(key);
|
|
401
436
|
if (!isImageFile(fileName)) continue;
|
|
402
437
|
for (const prefix of prefixes) {
|
|
@@ -790,6 +825,7 @@ async function handleSync(request) {
|
|
|
790
825
|
return NextResponse3.json({ error: "No image keys provided" }, { status: 400 });
|
|
791
826
|
}
|
|
792
827
|
const meta = await loadMeta();
|
|
828
|
+
const cdnIndex = getOrAddCdnIndex(meta, publicUrl);
|
|
793
829
|
const r2 = new S3Client2({
|
|
794
830
|
region: "auto",
|
|
795
831
|
endpoint: `https://${accountId}.r2.cloudflarestorage.com`,
|
|
@@ -798,12 +834,12 @@ async function handleSync(request) {
|
|
|
798
834
|
const pushed = [];
|
|
799
835
|
const errors = [];
|
|
800
836
|
for (const imageKey of imageKeys) {
|
|
801
|
-
const entry = meta
|
|
837
|
+
const entry = getMetaEntry(meta, imageKey);
|
|
802
838
|
if (!entry) {
|
|
803
839
|
errors.push(`Image not found in meta: ${imageKey}. Run Scan first.`);
|
|
804
840
|
continue;
|
|
805
841
|
}
|
|
806
|
-
if (entry.c) {
|
|
842
|
+
if (entry.c !== void 0) {
|
|
807
843
|
pushed.push(imageKey);
|
|
808
844
|
continue;
|
|
809
845
|
}
|
|
@@ -842,7 +878,7 @@ async function handleSync(request) {
|
|
|
842
878
|
} catch {
|
|
843
879
|
}
|
|
844
880
|
}
|
|
845
|
-
entry.c =
|
|
881
|
+
entry.c = cdnIndex;
|
|
846
882
|
for (const thumbPath of getAllThumbnailPaths(imageKey)) {
|
|
847
883
|
const localPath = path7.join(process.cwd(), "public", thumbPath);
|
|
848
884
|
try {
|
|
@@ -883,8 +919,9 @@ async function handleReprocess(request) {
|
|
|
883
919
|
for (const imageKey of imageKeys) {
|
|
884
920
|
try {
|
|
885
921
|
let buffer;
|
|
886
|
-
const entry = meta
|
|
887
|
-
const isPushedToCloud = entry?.c
|
|
922
|
+
const entry = getMetaEntry(meta, imageKey);
|
|
923
|
+
const isPushedToCloud = entry?.c !== void 0;
|
|
924
|
+
const existingCdnIndex = entry?.c;
|
|
888
925
|
const originalPath = path7.join(process.cwd(), "public", imageKey);
|
|
889
926
|
try {
|
|
890
927
|
buffer = await fs6.readFile(originalPath);
|
|
@@ -900,7 +937,7 @@ async function handleReprocess(request) {
|
|
|
900
937
|
}
|
|
901
938
|
const updatedEntry = await processImage(buffer, imageKey);
|
|
902
939
|
if (isPushedToCloud) {
|
|
903
|
-
updatedEntry.c =
|
|
940
|
+
updatedEntry.c = existingCdnIndex;
|
|
904
941
|
await uploadToCdn(imageKey);
|
|
905
942
|
await deleteLocalThumbnails(imageKey);
|
|
906
943
|
try {
|
|
@@ -942,7 +979,7 @@ async function handleProcessAllStream() {
|
|
|
942
979
|
const orphansRemoved = [];
|
|
943
980
|
let alreadyProcessed = 0;
|
|
944
981
|
const imagesToProcess = [];
|
|
945
|
-
for (const [key, entry] of
|
|
982
|
+
for (const [key, entry] of getFileEntries(meta)) {
|
|
946
983
|
const fileName = path7.basename(key);
|
|
947
984
|
if (!isImageFile(fileName)) continue;
|
|
948
985
|
if (!entry.p) {
|
|
@@ -956,7 +993,8 @@ async function handleProcessAllStream() {
|
|
|
956
993
|
for (let i = 0; i < imagesToProcess.length; i++) {
|
|
957
994
|
const { key, entry } = imagesToProcess[i];
|
|
958
995
|
const fullPath = path7.join(process.cwd(), "public", key);
|
|
959
|
-
const isInCloud = entry.c
|
|
996
|
+
const isInCloud = entry.c !== void 0;
|
|
997
|
+
const existingCdnIndex = entry.c;
|
|
960
998
|
sendEvent({
|
|
961
999
|
type: "progress",
|
|
962
1000
|
current: i + 1,
|
|
@@ -996,7 +1034,7 @@ async function handleProcessAllStream() {
|
|
|
996
1034
|
meta[key] = {
|
|
997
1035
|
...processedEntry,
|
|
998
1036
|
p: 1,
|
|
999
|
-
...isInCloud ? { c:
|
|
1037
|
+
...isInCloud ? { c: existingCdnIndex } : {}
|
|
1000
1038
|
};
|
|
1001
1039
|
}
|
|
1002
1040
|
if (isInCloud) {
|
|
@@ -1015,8 +1053,8 @@ async function handleProcessAllStream() {
|
|
|
1015
1053
|
}
|
|
1016
1054
|
sendEvent({ type: "cleanup", message: "Removing orphaned thumbnails..." });
|
|
1017
1055
|
const trackedPaths = /* @__PURE__ */ new Set();
|
|
1018
|
-
for (const imageKey of
|
|
1019
|
-
if (
|
|
1056
|
+
for (const [imageKey, entry] of getFileEntries(meta)) {
|
|
1057
|
+
if (entry.c === void 0) {
|
|
1020
1058
|
for (const thumbPath of getAllThumbnailPaths(imageKey)) {
|
|
1021
1059
|
trackedPaths.add(thumbPath);
|
|
1022
1060
|
}
|
|
@@ -1104,6 +1142,7 @@ async function handleProcessAllStream() {
|
|
|
1104
1142
|
import { promises as fs7 } from "fs";
|
|
1105
1143
|
import path8 from "path";
|
|
1106
1144
|
import sharp3 from "sharp";
|
|
1145
|
+
import { encode as encode2 } from "blurhash";
|
|
1107
1146
|
async function handleScanStream() {
|
|
1108
1147
|
const encoder = new TextEncoder();
|
|
1109
1148
|
const stream = new ReadableStream({
|
|
@@ -1115,6 +1154,7 @@ async function handleScanStream() {
|
|
|
1115
1154
|
};
|
|
1116
1155
|
try {
|
|
1117
1156
|
const meta = await loadMeta();
|
|
1157
|
+
const existingCount = Object.keys(meta).length;
|
|
1118
1158
|
const existingKeys = new Set(Object.keys(meta));
|
|
1119
1159
|
const added = [];
|
|
1120
1160
|
const renamed = [];
|
|
@@ -1182,13 +1222,17 @@ async function handleScanStream() {
|
|
|
1182
1222
|
if (isImage) {
|
|
1183
1223
|
const ext = path8.extname(relativePath).toLowerCase();
|
|
1184
1224
|
if (ext === ".svg") {
|
|
1185
|
-
meta[imageKey] = { w: 0, h: 0 };
|
|
1225
|
+
meta[imageKey] = { w: 0, h: 0, b: "" };
|
|
1186
1226
|
} else {
|
|
1187
1227
|
try {
|
|
1188
|
-
const
|
|
1228
|
+
const buffer = await fs7.readFile(fullPath);
|
|
1229
|
+
const metadata = await sharp3(buffer).metadata();
|
|
1230
|
+
const { data, info } = await sharp3(buffer).resize(32, 32, { fit: "inside" }).ensureAlpha().raw().toBuffer({ resolveWithObject: true });
|
|
1231
|
+
const blurhash = encode2(new Uint8ClampedArray(data), info.width, info.height, 4, 4);
|
|
1189
1232
|
meta[imageKey] = {
|
|
1190
1233
|
w: metadata.width || 0,
|
|
1191
|
-
h: metadata.height || 0
|
|
1234
|
+
h: metadata.height || 0,
|
|
1235
|
+
b: blurhash
|
|
1192
1236
|
};
|
|
1193
1237
|
} catch {
|
|
1194
1238
|
meta[imageKey] = { w: 0, h: 0 };
|
|
@@ -1207,6 +1251,7 @@ async function handleScanStream() {
|
|
|
1207
1251
|
await saveMeta(meta);
|
|
1208
1252
|
sendEvent({
|
|
1209
1253
|
type: "complete",
|
|
1254
|
+
existingCount,
|
|
1210
1255
|
added: added.length,
|
|
1211
1256
|
renamed: renamed.length,
|
|
1212
1257
|
errors: errors.length,
|
|
@@ -1229,6 +1274,132 @@ async function handleScanStream() {
|
|
|
1229
1274
|
});
|
|
1230
1275
|
}
|
|
1231
1276
|
|
|
1277
|
+
// src/handlers/import.ts
|
|
1278
|
+
import sharp4 from "sharp";
|
|
1279
|
+
import { encode as encode3 } from "blurhash";
|
|
1280
|
+
function parseImageUrl(url) {
|
|
1281
|
+
const parsed = new URL(url);
|
|
1282
|
+
const base = `${parsed.protocol}//${parsed.host}`;
|
|
1283
|
+
const path9 = parsed.pathname;
|
|
1284
|
+
return { base, path: path9 };
|
|
1285
|
+
}
|
|
1286
|
+
async function processRemoteImage(url) {
|
|
1287
|
+
const response = await fetch(url);
|
|
1288
|
+
if (!response.ok) {
|
|
1289
|
+
throw new Error(`Failed to fetch: ${response.status}`);
|
|
1290
|
+
}
|
|
1291
|
+
const buffer = Buffer.from(await response.arrayBuffer());
|
|
1292
|
+
const metadata = await sharp4(buffer).metadata();
|
|
1293
|
+
const { data, info } = await sharp4(buffer).resize(32, 32, { fit: "inside" }).ensureAlpha().raw().toBuffer({ resolveWithObject: true });
|
|
1294
|
+
const blurhash = encode3(new Uint8ClampedArray(data), info.width, info.height, 4, 4);
|
|
1295
|
+
return {
|
|
1296
|
+
w: metadata.width || 0,
|
|
1297
|
+
h: metadata.height || 0,
|
|
1298
|
+
b: blurhash
|
|
1299
|
+
};
|
|
1300
|
+
}
|
|
1301
|
+
async function handleImportUrls(request) {
|
|
1302
|
+
const encoder = new TextEncoder();
|
|
1303
|
+
const stream = new ReadableStream({
|
|
1304
|
+
async start(controller) {
|
|
1305
|
+
const sendEvent = (data) => {
|
|
1306
|
+
controller.enqueue(encoder.encode(`data: ${JSON.stringify(data)}
|
|
1307
|
+
|
|
1308
|
+
`));
|
|
1309
|
+
};
|
|
1310
|
+
try {
|
|
1311
|
+
const { urls } = await request.json();
|
|
1312
|
+
if (!urls || !Array.isArray(urls) || urls.length === 0) {
|
|
1313
|
+
sendEvent({ type: "error", message: "No URLs provided" });
|
|
1314
|
+
controller.close();
|
|
1315
|
+
return;
|
|
1316
|
+
}
|
|
1317
|
+
const meta = await loadMeta();
|
|
1318
|
+
const added = [];
|
|
1319
|
+
const skipped = [];
|
|
1320
|
+
const errors = [];
|
|
1321
|
+
const total = urls.length;
|
|
1322
|
+
sendEvent({ type: "start", total });
|
|
1323
|
+
for (let i = 0; i < urls.length; i++) {
|
|
1324
|
+
const url = urls[i].trim();
|
|
1325
|
+
if (!url) continue;
|
|
1326
|
+
sendEvent({
|
|
1327
|
+
type: "progress",
|
|
1328
|
+
current: i + 1,
|
|
1329
|
+
total,
|
|
1330
|
+
percent: Math.round((i + 1) / total * 100),
|
|
1331
|
+
currentFile: url
|
|
1332
|
+
});
|
|
1333
|
+
try {
|
|
1334
|
+
const { base, path: path9 } = parseImageUrl(url);
|
|
1335
|
+
const existingEntry = getMetaEntry(meta, path9);
|
|
1336
|
+
if (existingEntry) {
|
|
1337
|
+
skipped.push(path9);
|
|
1338
|
+
continue;
|
|
1339
|
+
}
|
|
1340
|
+
const cdnIndex = getOrAddCdnIndex(meta, base);
|
|
1341
|
+
const imageData = await processRemoteImage(url);
|
|
1342
|
+
setMetaEntry(meta, path9, {
|
|
1343
|
+
w: imageData.w,
|
|
1344
|
+
h: imageData.h,
|
|
1345
|
+
b: imageData.b,
|
|
1346
|
+
c: cdnIndex
|
|
1347
|
+
});
|
|
1348
|
+
added.push(path9);
|
|
1349
|
+
} catch (error) {
|
|
1350
|
+
console.error(`Failed to import ${url}:`, error);
|
|
1351
|
+
errors.push(url);
|
|
1352
|
+
}
|
|
1353
|
+
}
|
|
1354
|
+
await saveMeta(meta);
|
|
1355
|
+
sendEvent({
|
|
1356
|
+
type: "complete",
|
|
1357
|
+
added: added.length,
|
|
1358
|
+
skipped: skipped.length,
|
|
1359
|
+
errors: errors.length
|
|
1360
|
+
});
|
|
1361
|
+
} catch (error) {
|
|
1362
|
+
console.error("Import failed:", error);
|
|
1363
|
+
sendEvent({ type: "error", message: "Import failed" });
|
|
1364
|
+
} finally {
|
|
1365
|
+
controller.close();
|
|
1366
|
+
}
|
|
1367
|
+
}
|
|
1368
|
+
});
|
|
1369
|
+
return new Response(stream, {
|
|
1370
|
+
headers: {
|
|
1371
|
+
"Content-Type": "text/event-stream",
|
|
1372
|
+
"Cache-Control": "no-cache",
|
|
1373
|
+
"Connection": "keep-alive"
|
|
1374
|
+
}
|
|
1375
|
+
});
|
|
1376
|
+
}
|
|
1377
|
+
async function handleGetCdns() {
|
|
1378
|
+
try {
|
|
1379
|
+
const meta = await loadMeta();
|
|
1380
|
+
const cdns = meta._cdns || [];
|
|
1381
|
+
return Response.json({ cdns });
|
|
1382
|
+
} catch (error) {
|
|
1383
|
+
console.error("Failed to get CDNs:", error);
|
|
1384
|
+
return Response.json({ error: "Failed to get CDNs" }, { status: 500 });
|
|
1385
|
+
}
|
|
1386
|
+
}
|
|
1387
|
+
async function handleUpdateCdns(request) {
|
|
1388
|
+
try {
|
|
1389
|
+
const { cdns } = await request.json();
|
|
1390
|
+
if (!Array.isArray(cdns)) {
|
|
1391
|
+
return Response.json({ error: "Invalid CDN array" }, { status: 400 });
|
|
1392
|
+
}
|
|
1393
|
+
const meta = await loadMeta();
|
|
1394
|
+
meta._cdns = cdns.map((url) => url.replace(/\/$/, ""));
|
|
1395
|
+
await saveMeta(meta);
|
|
1396
|
+
return Response.json({ success: true, cdns: meta._cdns });
|
|
1397
|
+
} catch (error) {
|
|
1398
|
+
console.error("Failed to update CDNs:", error);
|
|
1399
|
+
return Response.json({ error: "Failed to update CDNs" }, { status: 500 });
|
|
1400
|
+
}
|
|
1401
|
+
}
|
|
1402
|
+
|
|
1232
1403
|
// src/handlers/index.ts
|
|
1233
1404
|
async function GET(request) {
|
|
1234
1405
|
if (process.env.NODE_ENV !== "development") {
|
|
@@ -1251,6 +1422,9 @@ async function GET(request) {
|
|
|
1251
1422
|
if (route === "search") {
|
|
1252
1423
|
return handleSearch(request);
|
|
1253
1424
|
}
|
|
1425
|
+
if (route === "cdns") {
|
|
1426
|
+
return handleGetCdns();
|
|
1427
|
+
}
|
|
1254
1428
|
return NextResponse4.json({ error: "Not found" }, { status: 404 });
|
|
1255
1429
|
}
|
|
1256
1430
|
async function POST(request) {
|
|
@@ -1286,6 +1460,12 @@ async function POST(request) {
|
|
|
1286
1460
|
if (route === "scan") {
|
|
1287
1461
|
return handleScanStream();
|
|
1288
1462
|
}
|
|
1463
|
+
if (route === "import") {
|
|
1464
|
+
return handleImportUrls(request);
|
|
1465
|
+
}
|
|
1466
|
+
if (route === "cdns") {
|
|
1467
|
+
return handleUpdateCdns(request);
|
|
1468
|
+
}
|
|
1289
1469
|
return NextResponse4.json({ error: "Not found" }, { status: 404 });
|
|
1290
1470
|
}
|
|
1291
1471
|
async function DELETE(request) {
|