Was ist hier eigentlich passiert - eine Zeitleiste auf der Projektseite beantwortet das jetzt: Statuswechsel, Notizen, Aufgaben, neue Commits. Der Verlauf soll erzaehlen, nicht protokollieren. Deshalb bekommen Status und Repo-Verknuepfung einen eigenen Eintrag, waehrend alle uebrigen Felder zu einem zusammengefasst werden - sonst ertraenkt eine Feinjustage der Farbe alles Wichtige. Ein Titel, der sich aendert, kommt gar nicht vor. Beim Abgleich ist Zurueckhaltung noch wichtiger: er laeuft im Zweifel stuendlich. Vermerkt wird nur, wenn der letzte Commit ein anderer ist als beim letzten Mal, und beim ersten Abgleich gar nicht - dass ein Repo Commits hat, ist keine Neuigkeit. Fehlschlaege stehen nur beim Uebergang in den Fehlerzustand drin, sonst schlaegt ein abgelaufenes Token stuendlich neu auf. Der Text wird beim Schreiben formuliert, nicht beim Anzeigen. Ein Eintrag soll auch dann lesbar bleiben, wenn es die Notiz, um die es ging, laengst nicht mehr gibt. Zweihundert Eintraege je Projekt bleiben erhalten, der taegliche Durchgang stutzt den Rest. Der Export nimmt den Verlauf nicht mit - er beschriebe, was in dieser Instanz passiert ist, und verwiese auf Konten, die es anderswo nicht gibt. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
91 lines
3.0 KiB
TypeScript
91 lines
3.0 KiB
TypeScript
import { NextResponse } from "next/server";
|
||
import { recordActivity, shorten } from "@/lib/activity";
|
||
import { requireUserApi } from "@/lib/auth/guard";
|
||
import { prisma } from "@/lib/db";
|
||
import { jsonError, readJsonBody } from "@/lib/security/request";
|
||
import { formatZodError, noteUpdateSchema } from "@/lib/validation";
|
||
|
||
export const dynamic = "force-dynamic";
|
||
|
||
type Params = { params: Promise<{ id: string }> };
|
||
|
||
/** Eine Notiz gehoert dem, dem das zugehoerige Projekt gehoert. */
|
||
async function ownedNote(noteId: string, ownerId: string) {
|
||
return prisma.note.findFirst({
|
||
where: { id: noteId, project: { ownerId } },
|
||
select: { id: true, projectId: true },
|
||
});
|
||
}
|
||
|
||
export async function PATCH(request: Request, { params }: Params) {
|
||
const auth = await requireUserApi();
|
||
if (!auth.ok) return auth.response;
|
||
|
||
const { id } = await params;
|
||
const note = await ownedNote(id, auth.user.id);
|
||
if (!note) return jsonError("Notiz nicht gefunden", 404);
|
||
|
||
const body = await readJsonBody(request);
|
||
if (!body.ok) return body.response;
|
||
|
||
const parsed = noteUpdateSchema.safeParse(body.data);
|
||
if (!parsed.success) {
|
||
return NextResponse.json(
|
||
{ error: "Validierung fehlgeschlagen", fields: formatZodError(parsed.error) },
|
||
{ status: 422 },
|
||
);
|
||
}
|
||
|
||
const updated = await prisma.note.update({
|
||
where: { id },
|
||
data: {
|
||
...(parsed.data.title !== undefined ? { title: parsed.data.title } : {}),
|
||
...(parsed.data.content !== undefined ? { content: parsed.data.content } : {}),
|
||
...(parsed.data.pinned !== undefined ? { pinned: parsed.data.pinned } : {}),
|
||
},
|
||
});
|
||
|
||
// Nur Anheften ist keine inhaltliche Aenderung und kommt nicht in den
|
||
// Verlauf – sonst steht dort dreimal dieselbe Notiz, weil jemand unschluessig war.
|
||
if (parsed.data.title !== undefined || parsed.data.content !== undefined) {
|
||
await recordActivity({
|
||
projectId: note.projectId,
|
||
userId: auth.user.id,
|
||
kind: "NOTE_UPDATED",
|
||
summary: `Notiz bearbeitet: ${shorten(updated.title || updated.content)}`,
|
||
meta: { noteId: id },
|
||
});
|
||
}
|
||
|
||
return NextResponse.json({ note: updated });
|
||
}
|
||
|
||
export async function DELETE(_request: Request, { params }: Params) {
|
||
const auth = await requireUserApi();
|
||
if (!auth.ok) return auth.response;
|
||
|
||
const { id } = await params;
|
||
|
||
// Vor dem Loeschen lesen: danach ist nicht mehr feststellbar, worum es ging.
|
||
const vorher = await prisma.note.findFirst({
|
||
where: { id, project: { ownerId: auth.user.id } },
|
||
select: { projectId: true, title: true, content: true },
|
||
});
|
||
|
||
const result = await prisma.note.deleteMany({
|
||
where: { id, project: { ownerId: auth.user.id } },
|
||
});
|
||
if (result.count === 0) return jsonError("Notiz nicht gefunden", 404);
|
||
|
||
if (vorher) {
|
||
await recordActivity({
|
||
projectId: vorher.projectId,
|
||
userId: auth.user.id,
|
||
kind: "NOTE_DELETED",
|
||
summary: `Notiz gelöscht: ${shorten(vorher.title || vorher.content)}`,
|
||
});
|
||
}
|
||
|
||
return NextResponse.json({ ok: true });
|
||
}
|