Ein Projekt, das sich bewaehrt hat, laesst sich im Projektkopf als Vorlage sichern: Status, Prioritaet, Farbe, Tags, Notizen und offene Aufgaben. Beim Anlegen steht sie oben im Dialog zur Auswahl. Benutzereigen statt fest eingebaut. Welche Tags und welche Startaufgaben sinnvoll sind, weiss nur, wer damit arbeitet - eine mitgelieferte Liste waere geraten. Zwei Dinge gehen bewusst nicht mit. Erledigte Aufgaben nicht: eine Vorlage beschreibt, was zu tun ist, nicht was einmal getan wurde. Und Repositories nicht: eine Vorlage, die jedes neue Projekt an dasselbe Repository haengt, waere eine Falle. Die Stammdaten uebernimmt der Dialog sichtbar in die Felder, damit man sie vor dem Speichern noch aendern kann; bereits Getipptes bleibt stehen. Notizen und Aufgaben legt erst der Server an - im Dialog liessen sie sich nicht darstellen, und wer die Vorlage wieder abwaehlt, soll sie nicht mitgeschleppt bekommen. templateId gilt nur beim Anlegen. Ein bestehendes Projekt nachtraeglich aus einer Vorlage zu befuellen wuerde Notizen und Aufgaben verdoppeln. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
88 lines
2.9 KiB
TypeScript
88 lines
2.9 KiB
TypeScript
import { NextResponse } from "next/server";
|
||
import { requireUserApi } from "@/lib/auth/guard";
|
||
import { prisma } from "@/lib/db";
|
||
import { jsonError, readJsonBody } from "@/lib/security/request";
|
||
import { formatZodError, templateFromProjectSchema } from "@/lib/validation";
|
||
|
||
export const dynamic = "force-dynamic";
|
||
|
||
/**
|
||
* Aus einem vorhandenen Projekt eine Vorlage machen.
|
||
*
|
||
* Das ist der Weg, auf dem Vorlagen ueblicherweise entstehen: man richtet ein
|
||
* Projekt so ein, wie es passt, und haelt das dann fest. Eine Vorlage von
|
||
* Grund auf zu tippen kann man auch – ueber POST /api/templates.
|
||
*
|
||
* Nicht mitgenommen wird alles Projektbezogene: Name, Slug, Fortschritt,
|
||
* Favorit und vor allem die Repositories. Eine Vorlage, die jedes neue
|
||
* Projekt an dasselbe Repository haengt, waere eine Falle.
|
||
*/
|
||
export async function POST(request: Request) {
|
||
const auth = await requireUserApi();
|
||
if (!auth.ok) return auth.response;
|
||
|
||
const body = await readJsonBody(request);
|
||
if (!body.ok) return body.response;
|
||
|
||
const parsed = templateFromProjectSchema.safeParse(body.data);
|
||
if (!parsed.success) {
|
||
return NextResponse.json(
|
||
{ error: "Validierung fehlgeschlagen", fields: formatZodError(parsed.error) },
|
||
{ status: 422 },
|
||
);
|
||
}
|
||
|
||
const { projectId, name, description, withNotes, withTasks } = parsed.data;
|
||
|
||
const project = await prisma.project.findFirst({
|
||
where: { id: projectId, ownerId: auth.user.id },
|
||
include: {
|
||
notes: { orderBy: [{ pinned: "desc" }, { createdAt: "asc" }] },
|
||
tasks: { orderBy: [{ status: "asc" }, { position: "asc" }] },
|
||
},
|
||
});
|
||
if (!project) return jsonError("Projekt nicht gefunden", 404);
|
||
|
||
const doppelt = await prisma.projectTemplate.findFirst({
|
||
where: { ownerId: auth.user.id, name },
|
||
select: { id: true },
|
||
});
|
||
if (doppelt) return jsonError(`Eine Vorlage „${name}“ gibt es bereits.`, 409);
|
||
|
||
const template = await prisma.projectTemplate.create({
|
||
data: {
|
||
ownerId: auth.user.id,
|
||
name,
|
||
description: description ?? null,
|
||
summary: project.summary,
|
||
projectDescription: project.description,
|
||
status: project.status,
|
||
priority: project.priority,
|
||
accent: project.accent,
|
||
tags: project.tags,
|
||
progressFromTasks: project.progressFromTasks,
|
||
notes: withNotes
|
||
? project.notes.map((note) => ({
|
||
title: note.title,
|
||
content: note.content,
|
||
pinned: note.pinned,
|
||
}))
|
||
: [],
|
||
// Erledigte Aufgaben kommen nicht mit: eine Vorlage beschreibt, was zu
|
||
// tun ist, nicht was einmal getan wurde.
|
||
tasks: withTasks
|
||
? project.tasks
|
||
.filter((task) => task.status !== "DONE")
|
||
.map((task) => ({
|
||
title: task.title,
|
||
description: task.description,
|
||
status: task.status,
|
||
labels: task.labels,
|
||
}))
|
||
: [],
|
||
},
|
||
});
|
||
|
||
return NextResponse.json({ template }, { status: 201 });
|
||
}
|