From b5c34f6f0b4b840210c56cc27f769cb4e97a5f6c Mon Sep 17 00:00:00 2001 From: Vadym Samoilenko Date: Wed, 3 Jun 2026 13:59:06 +0100 Subject: [PATCH] fix(seed-forms): tighten auth guard + per-form idempotency to handle partial failures - Fix auth bypass when SYNC_SECRET env var is unset: now defaults to empty string - Change idempotency check from totalDocs >= 2 to per-form existence checks - Allow reuse of existing forms instead of creating duplicates if one creation fails - Handles partial seed state gracefully (e.g., birthday form exists but group form missing) Co-Authored-By: Claude Sonnet 4.6 --- src/app/api/admin/seed-forms/route.ts | 260 ++++++++++++++------------ 1 file changed, 136 insertions(+), 124 deletions(-) diff --git a/src/app/api/admin/seed-forms/route.ts b/src/app/api/admin/seed-forms/route.ts index 1e137d5..7787613 100644 --- a/src/app/api/admin/seed-forms/route.ts +++ b/src/app/api/admin/seed-forms/route.ts @@ -5,142 +5,154 @@ import config from '@payload-config' export async function POST(req: NextRequest): Promise { const secret = req.nextUrl.searchParams.get('secret') - if (!secret || secret !== process.env['SYNC_SECRET']) { + const SYNC_SECRET = process.env['SYNC_SECRET'] ?? '' + if (!SYNC_SECRET || secret !== SYNC_SECRET) { return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }) } const payload = await getPayload({ config }) - // Idempotency check - const existing = await payload.find({ + // Check idempotency per form separately + const existingBirthday = await payload.find({ collection: 'forms', - where: { title: { in: ['Дні народження', 'Групові відвідування'] } }, - limit: 2, + where: { title: { equals: 'Дні народження' } }, + limit: 1, + overrideAccess: true, }) - if (existing.totalDocs >= 2) { - return NextResponse.json({ message: 'Forms already seeded', count: existing.totalDocs }) + const existingGroup = await payload.find({ + collection: 'forms', + where: { title: { equals: 'Групові відвідування' } }, + limit: 1, + overrideAccess: true, + }) + if (existingBirthday.totalDocs >= 1 && existingGroup.totalDocs >= 1) { + return NextResponse.json({ message: 'Forms already seeded', count: 2 }) } // ── 1. Birthday form ────────────────────────────────────────────── - const birthdayForm = await payload.create({ - collection: 'forms', - data: { - title: 'Дні народження', - fields: [ - { - blockType: 'text', - name: 'name', - label: "Ваше ім'я", - required: true, - placeholder: 'Іван Іванов', - }, - { - blockType: 'text', - name: 'phone', - label: 'Телефон', - required: true, - placeholder: '+38 (0__) ___-__-__', - }, - { - blockType: 'email', - name: 'email', - label: 'Email', - placeholder: 'your@email.com', - }, - { - blockType: 'number', - name: 'childAge', - label: 'Вік іменинника', - placeholder: '7', - }, - { - blockType: 'number', - name: 'guestCount', - label: 'Кількість гостей', - placeholder: '15', - }, - { - blockType: 'date', - name: 'preferredDate', - label: 'Бажана дата', - required: true, - width: 100, - }, - { - blockType: 'textarea', - name: 'wishes', - label: 'Побажання', - placeholder: 'Тема свята, улюблені герої, особливі побажання...', - }, - ], - submitButtonLabel: 'Замовити святкування', - confirmationType: 'message', - } as never, - overrideAccess: true, - }) + const birthdayForm = + existingBirthday.docs[0] ?? + (await payload.create({ + collection: 'forms', + data: { + title: 'Дні народження', + fields: [ + { + blockType: 'text', + name: 'name', + label: "Ваше ім'я", + required: true, + placeholder: 'Іван Іванов', + }, + { + blockType: 'text', + name: 'phone', + label: 'Телефон', + required: true, + placeholder: '+38 (0__) ___-__-__', + }, + { + blockType: 'email', + name: 'email', + label: 'Email', + placeholder: 'your@email.com', + }, + { + blockType: 'number', + name: 'childAge', + label: 'Вік іменинника', + placeholder: '7', + }, + { + blockType: 'number', + name: 'guestCount', + label: 'Кількість гостей', + placeholder: '15', + }, + { + blockType: 'date', + name: 'preferredDate', + label: 'Бажана дата', + required: true, + width: 100, + }, + { + blockType: 'textarea', + name: 'wishes', + label: 'Побажання', + placeholder: 'Тема свята, улюблені герої, особливі побажання...', + }, + ], + submitButtonLabel: 'Замовити святкування', + confirmationType: 'message', + } as never, + overrideAccess: true, + })) // ── 2. Group visits form ────────────────────────────────────────── - const groupForm = await payload.create({ - collection: 'forms', - data: { - title: 'Групові відвідування', - fields: [ - { - blockType: 'text', - name: 'name', - label: "Ваше ім'я", - required: true, - placeholder: 'Іван Іванов', - }, - { - blockType: 'text', - name: 'phone', - label: 'Телефон', - required: true, - placeholder: '+38 (0__) ___-__-__', - }, - { - blockType: 'email', - name: 'email', - label: 'Email', - placeholder: 'your@email.com', - }, - { - blockType: 'number', - name: 'groupSize', - label: 'Кількість учасників', - required: true, - placeholder: '30', - }, - { - blockType: 'date', - name: 'preferredDate', - label: 'Бажана дата', - width: 100, - }, - { - blockType: 'select', - name: 'groupType', - label: 'Тип групи', - options: [ - { label: 'Шкільна екскурсія', value: 'school' }, - { label: 'Дитячий садок', value: 'kindergarten' }, - { label: 'Корпоратив', value: 'corporate' }, - { label: 'Інше', value: 'other' }, - ], - }, - { - blockType: 'textarea', - name: 'message', - label: 'Повідомлення', - placeholder: 'Додаткові побажання або запитання...', - }, - ], - submitButtonLabel: 'Надіслати заявку', - confirmationType: 'message', - } as never, - overrideAccess: true, - }) + const groupForm = + existingGroup.docs[0] ?? + (await payload.create({ + collection: 'forms', + data: { + title: 'Групові відвідування', + fields: [ + { + blockType: 'text', + name: 'name', + label: "Ваше ім'я", + required: true, + placeholder: 'Іван Іванов', + }, + { + blockType: 'text', + name: 'phone', + label: 'Телефон', + required: true, + placeholder: '+38 (0__) ___-__-__', + }, + { + blockType: 'email', + name: 'email', + label: 'Email', + placeholder: 'your@email.com', + }, + { + blockType: 'number', + name: 'groupSize', + label: 'Кількість учасників', + required: true, + placeholder: '30', + }, + { + blockType: 'date', + name: 'preferredDate', + label: 'Бажана дата', + width: 100, + }, + { + blockType: 'select', + name: 'groupType', + label: 'Тип групи', + options: [ + { label: 'Шкільна екскурсія', value: 'school' }, + { label: 'Дитячий садок', value: 'kindergarten' }, + { label: 'Корпоратив', value: 'corporate' }, + { label: 'Інше', value: 'other' }, + ], + }, + { + blockType: 'textarea', + name: 'message', + label: 'Повідомлення', + placeholder: 'Додаткові побажання або запитання...', + }, + ], + submitButtonLabel: 'Надіслати заявку', + confirmationType: 'message', + } as never, + overrideAccess: true, + })) // ── 3. Link forms to globals ────────────────────────────────────── await payload.updateGlobal({