Shoot sketches templates
Deploy / deploy (push) Failing after 44s

This commit is contained in:
twotalesanimation
2026-07-12 12:27:21 +02:00
parent 4ae14cf8b2
commit 2f91c71307
7 changed files with 876 additions and 7 deletions
+10
View File
@@ -4,6 +4,7 @@ import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
import { getInitials } from "@/lib/utils";
import { ChangePasswordForm } from "@/components/settings/ChangePasswordForm";
import { HetznerConfigForm } from "@/components/settings/HetznerConfigForm";
import { SketchTemplatesSection } from "@/components/settings/SketchTemplatesSection";
import { getHetznerConfig } from "@/actions/settings";
export const metadata = { title: "Settings" };
@@ -13,6 +14,7 @@ export default async function SettingsPage() {
if (!session?.user) return null;
const isAdmin = session.user.role === "ADMIN";
const canManageTemplates = ["ADMIN", "PRODUCER", "SUPERVISOR"].includes(session.user.role ?? "");
const hetznerConfig = isAdmin ? await getHetznerConfig() : null;
return (
@@ -48,6 +50,14 @@ export default async function SettingsPage() {
<HetznerConfigForm initialConfig={hetznerConfig} />
</div>
)}
{canManageTemplates && (
<Card>
<CardContent className="pt-6">
<SketchTemplatesSection />
</CardContent>
</Card>
)}
</div>
);
}
+49
View File
@@ -0,0 +1,49 @@
import { NextRequest, NextResponse } from "next/server";
import { auth } from "@/auth";
import { db } from "@/lib/db";
function canManage(role: string) {
return ["ADMIN", "PRODUCER", "SUPERVISOR"].includes(role);
}
// DELETE /api/sketch-templates/[id]
export async function DELETE(
_req: NextRequest,
{ params }: { params: Promise<{ id: string }> }
) {
const session = await auth();
if (!session?.user) return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
if (!canManage(session.user.role))
return NextResponse.json({ error: "Forbidden" }, { status: 403 });
const { id } = await params;
const template = await db.sketchTemplate.findUnique({ where: { id } });
if (!template) return NextResponse.json({ error: "Not found" }, { status: 404 });
await db.sketchTemplate.delete({ where: { id } });
return NextResponse.json({ ok: true });
}
// PATCH /api/sketch-templates/[id] — rename
export async function PATCH(
req: NextRequest,
{ params }: { params: Promise<{ id: string }> }
) {
const session = await auth();
if (!session?.user) return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
if (!canManage(session.user.role))
return NextResponse.json({ error: "Forbidden" }, { status: 403 });
const { id } = await params;
const { name } = await req.json();
if (!name?.trim()) return NextResponse.json({ error: "Name required" }, { status: 400 });
const template = await db.sketchTemplate.update({
where: { id },
data: { name: name.trim() },
});
return NextResponse.json({
template: { ...template, fileSize: template.fileSize != null ? Number(template.fileSize) : null },
});
}
+66
View File
@@ -0,0 +1,66 @@
import { NextRequest, NextResponse } from "next/server";
import { auth } from "@/auth";
import { db } from "@/lib/db";
import { uploadFile } from "@/lib/storage";
function canManage(role: string) {
return ["ADMIN", "PRODUCER", "SUPERVISOR"].includes(role);
}
// GET /api/sketch-templates — list all templates (any authenticated user)
export async function GET() {
const session = await auth();
if (!session?.user) return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
const templates = await db.sketchTemplate.findMany({
orderBy: { sortOrder: "asc" },
});
return NextResponse.json({
templates: templates.map((t) => ({
...t,
fileSize: t.fileSize != null ? Number(t.fileSize) : null,
})),
});
}
// POST /api/sketch-templates — upload + create a new template
export async function POST(req: NextRequest) {
const session = await auth();
if (!session?.user) return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
if (!canManage(session.user.role))
return NextResponse.json({ error: "Forbidden" }, { status: 403 });
const formData = await req.formData();
const file = formData.get("file") as File | null;
const name = ((formData.get("name") as string) || "").trim();
if (!file) return NextResponse.json({ error: "No file provided" }, { status: 400 });
if (!file.type.startsWith("image/"))
return NextResponse.json({ error: "Only image files accepted" }, { status: 400 });
const maxSize = 20 * 1024 * 1024; // 20 MB
if (file.size > maxSize)
return NextResponse.json({ error: "File too large (max 20 MB)" }, { status: 413 });
const buffer = Buffer.from(await file.arrayBuffer());
const uploaded = await uploadFile(buffer, file.name, file.type, "image");
const maxOrder = await db.sketchTemplate.aggregate({ _max: { sortOrder: true } });
const sortOrder = (maxOrder._max.sortOrder ?? -1) + 1;
const template = await db.sketchTemplate.create({
data: {
name: name || file.name.replace(/\.[^.]+$/, ""),
fileUrl: uploaded.url,
fileKey: uploaded.key,
fileName: file.name,
fileSize: BigInt(file.size),
sortOrder,
},
});
return NextResponse.json({
template: { ...template, fileSize: Number(template.fileSize) },
});
}