feat: add QR code event check-in system

Backend:
- Add event_checkin_tokens table with unique tokens per event
- Implement GET /api/events/:slug/details endpoint (on-demand token generation)
- Implement POST /api/events/checkin/:token endpoint (date validation only in production)
- Implement DELETE /api/events/:slug/leave endpoint
- Add comprehensive test suite for check-in endpoints

Frontend:
- Add EventDetailsPage with QR code display, participant list, and stats
- Add EventCheckinPage with success/error screens
- Add "Leave Event" button with confirmation modal to EventChatPage
- Install qrcode.react library for QR code generation
- Update routing and API client with new endpoints

Features:
- QR codes valid from (startDate-1d) to (endDate+1d)
- Development mode bypasses date validation for testing
- Automatic participant count tracking
- Duplicate check-in prevention
- Token reuse for same event (generated once, cached)
This commit is contained in:
Radosław Gierwiało
2025-11-14 14:11:24 +01:00
parent 5bea2ad133
commit 71cba01db3
11 changed files with 1095 additions and 1 deletions

View File

@@ -0,0 +1,18 @@
-- CreateTable
CREATE TABLE "event_checkin_tokens" (
"id" SERIAL NOT NULL,
"event_id" INTEGER NOT NULL,
"token" VARCHAR(50) NOT NULL,
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "event_checkin_tokens_pkey" PRIMARY KEY ("id")
);
-- CreateIndex
CREATE UNIQUE INDEX "event_checkin_tokens_event_id_key" ON "event_checkin_tokens"("event_id");
-- CreateIndex
CREATE UNIQUE INDEX "event_checkin_tokens_token_key" ON "event_checkin_tokens"("token");
-- AddForeignKey
ALTER TABLE "event_checkin_tokens" ADD CONSTRAINT "event_checkin_tokens_event_id_fkey" FOREIGN KEY ("event_id") REFERENCES "events"("id") ON DELETE CASCADE ON UPDATE CASCADE;

View File

@@ -75,10 +75,24 @@ model Event {
chatRooms ChatRoom[]
matches Match[]
participants EventParticipant[]
checkinToken EventCheckinToken?
@@map("events")
}
// Event check-in tokens (QR code tokens for event access)
model EventCheckinToken {
id Int @id @default(autoincrement())
eventId Int @unique @map("event_id")
token String @unique @default(cuid()) @db.VarChar(50)
createdAt DateTime @default(now()) @map("created_at")
// Relations
event Event @relation(fields: [eventId], references: [id], onDelete: Cascade)
@@map("event_checkin_tokens")
}
// Chat rooms (event chat and private 1:1 chat)
model ChatRoom {
id Int @id @default(autoincrement())