Initial commit: KOC LOOP platform
This commit is contained in:
129
app/api/partner-image/route.ts
Normal file
129
app/api/partner-image/route.ts
Normal file
@@ -0,0 +1,129 @@
|
||||
import { env } from "cloudflare:workers";
|
||||
import {
|
||||
ensureSchema,
|
||||
getRawDb,
|
||||
getUploadBucket,
|
||||
} from "../../../lib/mvp-db";
|
||||
import {
|
||||
downloadFeishuMedia,
|
||||
type FeishuBindings,
|
||||
} from "../../../lib/feishu-client";
|
||||
import {
|
||||
partnerOptions,
|
||||
withPartnerCors,
|
||||
} from "../../../lib/partner-cors";
|
||||
|
||||
export const runtime = "edge";
|
||||
|
||||
type StoredAsset = {
|
||||
index: number;
|
||||
key: string;
|
||||
fileToken?: string;
|
||||
};
|
||||
|
||||
function textValue(value: string | null, maxLength = 100) {
|
||||
return String(value ?? "").trim().slice(0, maxLength);
|
||||
}
|
||||
|
||||
function findAsset(value: string, imageIndex: number) {
|
||||
try {
|
||||
const assets = JSON.parse(value) as StoredAsset[];
|
||||
return Array.isArray(assets)
|
||||
? assets.find(
|
||||
(asset) =>
|
||||
asset.index === imageIndex &&
|
||||
typeof asset.key === "string" &&
|
||||
asset.key.startsWith("content-assets/") &&
|
||||
(asset.fileToken === undefined ||
|
||||
typeof asset.fileToken === "string"),
|
||||
)
|
||||
: undefined;
|
||||
} catch {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
async function handleGet(request: Request) {
|
||||
try {
|
||||
await ensureSchema();
|
||||
const url = new URL(request.url);
|
||||
const taskToken = textValue(url.searchParams.get("task"));
|
||||
const claimToken = textValue(url.searchParams.get("claim"));
|
||||
const delegationToken = textValue(url.searchParams.get("share"));
|
||||
const distributionId = textValue(url.searchParams.get("distribution"));
|
||||
const imageIndex = Number(url.searchParams.get("index"));
|
||||
if (
|
||||
(!delegationToken && (!taskToken || !claimToken)) ||
|
||||
!distributionId ||
|
||||
!Number.isInteger(imageIndex) ||
|
||||
imageIndex < 1
|
||||
) {
|
||||
return Response.json({ error: "图片链接不完整" }, { status: 400 });
|
||||
}
|
||||
const row = delegationToken
|
||||
? await getRawDb()
|
||||
.prepare(
|
||||
`SELECT c.image_assets
|
||||
FROM distributions d
|
||||
JOIN contents c ON c.id = d.content_id
|
||||
JOIN delegation_bundles b ON b.id = d.delegation_bundle_id
|
||||
WHERE d.id = ?
|
||||
AND b.share_token = ?
|
||||
AND b.status = 'active'`,
|
||||
)
|
||||
.bind(distributionId, delegationToken)
|
||||
.first<{ image_assets: string }>()
|
||||
: await getRawDb()
|
||||
.prepare(
|
||||
`SELECT c.image_assets
|
||||
FROM distributions d
|
||||
JOIN contents c ON c.id = d.content_id
|
||||
JOIN claims cl ON cl.id = d.claim_id
|
||||
JOIN tasks t ON t.id = d.task_id
|
||||
WHERE d.id = ?
|
||||
AND cl.claim_token = ?
|
||||
AND t.share_token = ?
|
||||
AND cl.task_id = t.id`,
|
||||
)
|
||||
.bind(distributionId, claimToken, taskToken)
|
||||
.first<{ image_assets: string }>();
|
||||
const asset = row ? findAsset(row.image_assets, imageIndex) : undefined;
|
||||
if (!asset) {
|
||||
return Response.json({ error: "没有找到这张笔记图片" }, { status: 404 });
|
||||
}
|
||||
const bucket = getUploadBucket();
|
||||
let object = await bucket.get(asset.key);
|
||||
if (!object && asset.fileToken) {
|
||||
const media = await downloadFeishuMedia(
|
||||
asset.fileToken,
|
||||
env as unknown as FeishuBindings,
|
||||
);
|
||||
await bucket.put(asset.key, media.bytes, {
|
||||
httpMetadata: { contentType: media.contentType },
|
||||
customMetadata: { source: "feishu-api" },
|
||||
});
|
||||
object = await bucket.get(asset.key);
|
||||
}
|
||||
if (!object) {
|
||||
return Response.json({ error: "图片同步失败,请稍后重试" }, { status: 404 });
|
||||
}
|
||||
const headers = new Headers();
|
||||
object.writeHttpMetadata(headers);
|
||||
headers.set("Cache-Control", "private, max-age=3600");
|
||||
headers.set("Content-Disposition", `inline; filename="note-${imageIndex}"`);
|
||||
return new Response(object.body, { headers });
|
||||
} catch (error) {
|
||||
return Response.json(
|
||||
{ error: error instanceof Error ? error.message : "图片读取失败" },
|
||||
{ status: 500 },
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export async function GET(request: Request) {
|
||||
return withPartnerCors(request, await handleGet(request));
|
||||
}
|
||||
|
||||
export async function OPTIONS(request: Request) {
|
||||
return partnerOptions(request);
|
||||
}
|
||||
Reference in New Issue
Block a user