Merge branch 'main' into feat/8-logo-im-code

# Conflicts:
#	docs/funktionen.md
This commit is contained in:
Florian Wilde
2026-08-21 10:55:54 +02:00
20 changed files with 1285 additions and 57 deletions
+25
View File
@@ -7,6 +7,7 @@ import { requirePermission, visibleGroupIds } from "@/lib/auth/policy";
import { toJsonInput, toQrCodeDto } from "@/lib/codes";
import { prisma } from "@/lib/db";
import { resolveTargetDomain } from "@/lib/domains";
import { findStaticDuplicate } from "@/lib/duplicates";
import { errorResponse, firstZodMessage, jsonError } from "@/lib/http";
import { requireAvailableQrType } from "@/lib/qr/availability";
import { designSchema } from "@/lib/qr/design";
@@ -43,6 +44,12 @@ const createSchema = z.object({
groupId: z.string().trim().min(1).optional(),
/** Unter welcher Domain die Kurz-URL entsteht. */
domainId: z.string().trim().min(1).optional(),
/**
* Eine gemeldete Dublette bewusst in Kauf nehmen. Ohne dieses Häkchen
* antwortet das Anlegen mit 409, sobald derselbe Inhalt schon als
* statischer Code in der Gruppe liegt.
*/
allowDuplicate: z.boolean().default(false),
});
/**
@@ -158,6 +165,24 @@ export async function POST(request: NextRequest): Promise<Response> {
await assertSlugAvailable(domainId, gewuenscht);
}
// Zweimal derselbe statische Inhalt ergibt zweimal dasselbe Bild. Das ist
// erlaubt, aber selten gewollt — deshalb ein Halt mit Rückfrage statt
// eines stillen zweiten Eintrags.
if (istStatisch && !parsed.data.allowDuplicate) {
const dublette = await findStaticDuplicate(groupId, qrType.id, validated.data);
if (dublette) {
return Response.json(
{
error: dublette.label
? `Diesen Inhalt hast du schon abgelegt — als „${dublette.label}“.`
: "Diesen Inhalt hast du schon abgelegt.",
duplicate: dublette,
},
{ status: 409 },
);
}
}
if (istStatisch) {
const created = await prisma.qrCode.create({
data: {