Refactor code

This commit is contained in:
2026-02-03 12:17:47 +01:00
parent ea5eb6fa59
commit 8572e22c5d
185 changed files with 1268 additions and 1458 deletions

View File

@ -1,6 +1,7 @@
import { getArtworksPage } from "@/lib/queryArtworks";
import { NextResponse, type NextRequest } from "next/server";
// Public API for paginated artworks listing.
export async function GET(req: NextRequest) {
const publishedParam = req.nextUrl.searchParams.get("published") ?? "all";

View File

@ -1,4 +1,5 @@
import { auth } from "@/lib/auth";
import { toNextJsHandler } from "better-auth/next-js";
export const { POST, GET } = toNextJsHandler(auth);
// Better Auth route handlers.
export const { POST, GET } = toNextJsHandler(auth);

View File

@ -1,10 +1,27 @@
import { s3 } from "@/lib/s3";
import type { S3Body } from "@/types/s3";
import { GetObjectCommand } from "@aws-sdk/client-s3";
import type { NextRequest } from "next/server";
import { Readable } from "stream";
function isWebReadableStream(value: unknown): value is ReadableStream<Uint8Array> {
return !!value && typeof (value as ReadableStream<Uint8Array>).getReader === "function";
}
function toBodyInit(body: S3Body): BodyInit {
if (body instanceof Readable) {
return Readable.toWeb(body) as ReadableStream<Uint8Array>;
}
if (isWebReadableStream(body)) {
return body;
}
return body as BodyInit;
}
// Streams images from S3 for the admin app.
export async function GET(_req: NextRequest, context: { params: Promise<{ key: string[] }> }) {
const { key } = await context.params;
const s3Key = key.join("/");
const s3Key = key.join("/");
try {
const command = new GetObjectCommand({
@ -20,7 +37,7 @@ export async function GET(_req: NextRequest, context: { params: Promise<{ key: s
const contentType = response.ContentType ?? "application/octet-stream";
return new Response(response.Body as ReadableStream, {
return new Response(toBodyInit(response.Body as S3Body), {
headers: {
"Content-Type": contentType,
"Cache-Control": "public, max-age=3600",
@ -28,7 +45,7 @@ export async function GET(_req: NextRequest, context: { params: Promise<{ key: s
},
});
} catch (err) {
console.log(err)
console.error(err);
return new Response("Image not found", { status: 404 });
}
}
}

View File

@ -1,9 +1,12 @@
import { prisma } from "@/lib/prisma";
import { s3 } from "@/lib/s3";
import type { S3Body } from "@/types/s3";
import { GetObjectCommand } from "@aws-sdk/client-s3";
import archiver from "archiver";
import { NextRequest } from "next/server";
import type { NextRequest } from "next/server";
import { Readable } from "stream";
// Streams commission request files (single or zip) from S3.
type Mode = "display" | "download" | "bulk";
function contentDisposition(filename: string, mode: Mode) {
@ -17,6 +20,20 @@ function sanitizeZipEntryName(name: string) {
return name.replace(/[^\w.\- ()\[\]]+/g, "_").slice(0, 180);
}
function isWebReadableStream(value: unknown): value is ReadableStream<Uint8Array> {
return !!value && typeof (value as ReadableStream<Uint8Array>).getReader === "function";
}
function toBodyInit(body: S3Body): BodyInit {
if (body instanceof Readable) {
return Readable.toWeb(body) as ReadableStream<Uint8Array>;
}
if (isWebReadableStream(body)) {
return body;
}
return body as BodyInit;
}
export async function GET(req: NextRequest) {
try {
const bucket = process.env.BUCKET_NAME;
@ -52,7 +69,7 @@ export async function GET(req: NextRequest) {
const contentType = file.fileType || s3Res.ContentType || "application/octet-stream";
return new Response(s3Res.Body as ReadableStream, {
return new Response(toBodyInit(s3Res.Body as S3Body), {
headers: {
"Content-Type": contentType,
// You can tune caching; admin-only content usually should be private.
@ -117,8 +134,17 @@ export async function GET(req: NextRequest) {
f.originalFile || f.fileKey.split("/").pop() || "file"
);
// obj.Body is a Node stream in Node runtime; works with archiver
archive.append(obj.Body as any, { name: entryName });
// obj.Body can be a Node Readable, web ReadableStream, or Buffer.
const body = obj.Body;
if (!body) continue;
if (body instanceof Readable) {
archive.append(body, { name: entryName });
} else if (isWebReadableStream(body)) {
archive.append(Readable.from(body as AsyncIterable<Uint8Array>), { name: entryName });
} else {
archive.append(body as Buffer, { name: entryName });
}
}
await archive.finalize();

View File

@ -1,38 +1,11 @@
import { prisma } from "@/lib/prisma";
import { s3 } from "@/lib/s3";
import { publicCommissionRequestSchema } from "@/schemas/commissions/publicRequest";
import { DeleteObjectsCommand, PutObjectCommand } from "@aws-sdk/client-s3";
import { NextResponse } from "next/server";
import { v4 as uuidv4 } from "uuid";
import { z } from "zod/v4";
const payloadSchema = z.object({
typeId: z.string().min(1).optional().nullable(),
customCardId: z.string().min(1).optional().nullable(),
optionId: z.string().min(1).optional().nullable(),
extraIds: z.array(z.string().min(1)).default([]),
customerName: z.string().min(1).max(200),
customerEmail: z.string().email().max(320),
customerSocials: z.string().max(2000).optional().nullable(),
message: z.string().min(1).max(20_000),
}).superRefine((data, ctx) => {
const hasType = Boolean(data.typeId);
const hasCustom = Boolean(data.customCardId);
if (!hasType && !hasCustom) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
path: ["typeId"],
message: "Missing commission type or custom card",
});
}
if (hasType && hasCustom) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
path: ["typeId"],
message: "Only one of typeId or customCardId is allowed",
});
}
});
// Public API endpoint for commission submissions (multipart form).
function safeJsonParse(input: string) {
try {
@ -64,7 +37,7 @@ export async function POST(request: Request) {
return NextResponse.json({ error: "Invalid payload JSON" }, { status: 400 });
}
const payload = payloadSchema.safeParse(parsedJson);
const payload = publicCommissionRequestSchema.safeParse(parsedJson);
if (!payload.success) {
return NextResponse.json(
{ error: "Validation error", issues: payload.error.issues },