Merge branch 'main' into feat/7-domain-check

# Conflicts:
#	src/components/domain-admin.tsx
This commit is contained in:
Florian Wilde
2026-08-21 10:51:57 +02:00
13 changed files with 552 additions and 57 deletions
+3 -3
View File
@@ -27,9 +27,9 @@ export default async function MyCodesPage() {
Meine Codes
</h1>
<p className="mt-2 text-slate-600">
Alle dynamischen QR-Codes mit ihrem aktuellen Ziel und der Anzahl der
Scans. Statische Codes erscheinen hier nicht — sie werden nicht
gespeichert.
Dynamische Codes mit ihrem aktuellen Ziel und der Anzahl der Scans,
dazu abgelegte statische Codes. Über die Auswahl „Nach Art filtern“
lässt sich die Liste auf eine der beiden Arten einschränken.
</p>
</div>
+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),
});
function isUniqueViolation(error: unknown): boolean {
@@ -140,6 +147,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: {