mirror of
https://github.com/xCyanGrizzly/DragonsStash.git
synced 2026-05-11 06:11:15 +00:00
- Auto-extract preview images from ZIP/RAR/7z archives during ingestion - Upload custom preview images via package drawer - Select preview from archive contents with on-demand extraction UI - Manually add Telegram channels by t.me link, username, or invite link - Invite code UX: bulk create, copy link, usage tracking, delete confirm - Incomplete upload recovery: verify dest messages on worker startup - Rebuild package DB by scanning destination channel with live progress Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
119 lines
2.9 KiB
TypeScript
119 lines
2.9 KiB
TypeScript
import { NextResponse } from "next/server";
|
|
import { authenticateApiRequest } from "@/lib/telegram/api-auth";
|
|
import { prisma } from "@/lib/prisma";
|
|
|
|
export const dynamic = "force-dynamic";
|
|
|
|
/**
|
|
* POST /api/zips/:id/extract
|
|
* Request extraction of an image from a package archive.
|
|
* Body: { filePath: string }
|
|
* Returns: { requestId: string, status: string }
|
|
*
|
|
* If a completed extraction already exists for this package+filePath,
|
|
* returns it immediately.
|
|
*/
|
|
export async function POST(
|
|
request: Request,
|
|
{ params }: { params: Promise<{ id: string }> }
|
|
) {
|
|
const authResult = await authenticateApiRequest(request);
|
|
if ("error" in authResult) return authResult.error;
|
|
|
|
const { id } = await params;
|
|
const body = await request.json();
|
|
const filePath = body?.filePath;
|
|
|
|
if (!filePath || typeof filePath !== "string") {
|
|
return NextResponse.json(
|
|
{ error: "filePath is required" },
|
|
{ status: 400 }
|
|
);
|
|
}
|
|
|
|
// Verify package exists
|
|
const pkg = await prisma.package.findUnique({
|
|
where: { id },
|
|
select: { id: true, destChannelId: true, destMessageId: true, archiveType: true, isMultipart: true, partCount: true },
|
|
});
|
|
|
|
if (!pkg) {
|
|
return NextResponse.json({ error: "Package not found" }, { status: 404 });
|
|
}
|
|
|
|
if (!pkg.destChannelId || !pkg.destMessageId) {
|
|
return NextResponse.json(
|
|
{ error: "Package has not been uploaded to destination channel" },
|
|
{ status: 400 }
|
|
);
|
|
}
|
|
|
|
if (pkg.archiveType === "DOCUMENT") {
|
|
return NextResponse.json(
|
|
{ error: "Cannot extract images from standalone documents" },
|
|
{ status: 400 }
|
|
);
|
|
}
|
|
|
|
if (pkg.isMultipart && pkg.partCount > 1) {
|
|
return NextResponse.json(
|
|
{ error: "Image extraction is not supported for multipart archives" },
|
|
{ status: 400 }
|
|
);
|
|
}
|
|
|
|
// Check for an existing completed extraction
|
|
const existing = await prisma.archiveExtractRequest.findFirst({
|
|
where: {
|
|
packageId: id,
|
|
filePath,
|
|
status: "COMPLETED",
|
|
imageData: { not: null },
|
|
},
|
|
select: { id: true, status: true },
|
|
});
|
|
|
|
if (existing) {
|
|
return NextResponse.json({
|
|
requestId: existing.id,
|
|
status: "COMPLETED",
|
|
});
|
|
}
|
|
|
|
// Check for an in-progress request
|
|
const pending = await prisma.archiveExtractRequest.findFirst({
|
|
where: {
|
|
packageId: id,
|
|
filePath,
|
|
status: { in: ["PENDING", "IN_PROGRESS"] },
|
|
},
|
|
select: { id: true, status: true },
|
|
});
|
|
|
|
if (pending) {
|
|
return NextResponse.json({
|
|
requestId: pending.id,
|
|
status: pending.status,
|
|
});
|
|
}
|
|
|
|
// Create a new extraction request
|
|
const extractRequest = await prisma.archiveExtractRequest.create({
|
|
data: {
|
|
packageId: id,
|
|
filePath,
|
|
},
|
|
});
|
|
|
|
// Notify the worker via pg_notify
|
|
await prisma.$queryRawUnsafe(
|
|
`SELECT pg_notify('archive_extract', $1)`,
|
|
extractRequest.id
|
|
);
|
|
|
|
return NextResponse.json({
|
|
requestId: extractRequest.id,
|
|
status: "PENDING",
|
|
});
|
|
}
|