- Sign all image URLs server-side with 60s expiry presigned URLs - Add /api/pages endpoint for batched page fetching (7 per batch) - PageReader prefetches next batch when user scrolls to 3rd page - Move chapter count badge outside overflow-hidden for 3D effect - Fix missing URL signing on search and genre pages - Extract signCoverUrls helper to reduce duplication - Clamp API limit param to prevent abuse Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
55 lines
1.4 KiB
TypeScript
55 lines
1.4 KiB
TypeScript
import {
|
|
S3Client,
|
|
PutObjectCommand,
|
|
GetObjectCommand,
|
|
} from "@aws-sdk/client-s3";
|
|
import { getSignedUrl } from "@aws-sdk/s3-request-presigner";
|
|
|
|
const s3 = new S3Client({
|
|
region: "auto",
|
|
endpoint: `https://${process.env.R2_ACCOUNT_ID}.r2.cloudflarestorage.com`,
|
|
credentials: {
|
|
accessKeyId: process.env.R2_ACCESS_KEY!,
|
|
secretAccessKey: process.env.R2_SECRET_KEY!,
|
|
},
|
|
});
|
|
|
|
export async function getPresignedUploadUrl(key: string) {
|
|
const command = new PutObjectCommand({
|
|
Bucket: process.env.R2_BUCKET!,
|
|
Key: key,
|
|
ContentType: "image/webp",
|
|
});
|
|
return getSignedUrl(s3, command, { expiresIn: 3600 });
|
|
}
|
|
|
|
export function getPublicUrl(key: string) {
|
|
return `${process.env.R2_PUBLIC_URL}/${key}`;
|
|
}
|
|
|
|
export async function getPresignedReadUrl(key: string) {
|
|
const command = new GetObjectCommand({
|
|
Bucket: process.env.R2_BUCKET!,
|
|
Key: key,
|
|
});
|
|
return getSignedUrl(s3, command, { expiresIn: 60 });
|
|
}
|
|
|
|
export async function signUrl(publicUrl: string) {
|
|
const prefix = process.env.R2_PUBLIC_URL!;
|
|
if (!publicUrl.startsWith(prefix)) return publicUrl;
|
|
const key = publicUrl.replace(prefix, "").replace(/^\//, "");
|
|
return getPresignedReadUrl(key);
|
|
}
|
|
|
|
export async function signCoverUrls<T extends { coverUrl: string }>(
|
|
items: T[]
|
|
): Promise<T[]> {
|
|
return Promise.all(
|
|
items.map(async (item) => ({
|
|
...item,
|
|
coverUrl: await signUrl(item.coverUrl),
|
|
}))
|
|
);
|
|
}
|