feat: add test accounts and secure event slugs

Security improvements:
- Add @default(cuid()) to Event.slug for auto-generated random slugs
- Prevent ID enumeration attacks (no more predictable slugs like "warsaw-dance-festival-2025")
- Event slugs now generated as secure cuid strings (e.g., "cmhz3lcgb00018vbn34v4phoi")

Test accounts:
- Add 3 test users to seed (john_dancer, sarah_swings, mike_blues)
- All users checked in to Warsaw Dance Festival 2025
- Pre-configured heats for testing matchmaking system
- Full profiles with WSDC IDs, social media, and locations

Seed improvements:
- Add bcryptjs for password hashing
- Add Prisma seed configuration to package.json
- Use worldsdcId for event upsert (instead of slug)
- Auto-generate event slugs via Prisma default

Documentation:
- Add test account credentials to SESSION_CONTEXT.md
- Document event slug security model
- Include sample heats for each test user
This commit is contained in:
Radosław Gierwiało
2025-11-14 17:55:29 +01:00
parent b4960da1b1
commit 92315d5a8c
4 changed files with 256 additions and 19 deletions

View File

@@ -54,5 +54,8 @@
"testMatch": [
"**/__tests__/**/*.test.js"
]
},
"prisma": {
"seed": "node prisma/seed.js"
}
}

View File

@@ -62,7 +62,7 @@ model User {
// Events table (dance events from worldsdc.com)
model Event {
id Int @id @default(autoincrement())
slug String @unique @db.VarChar(50)
slug String @unique @default(cuid()) @db.VarChar(50)
name String @db.VarChar(255)
location String @db.VarChar(255)
startDate DateTime @map("start_date") @db.Date

View File

@@ -1,10 +1,70 @@
const { PrismaClient } = require('@prisma/client');
const bcrypt = require('bcryptjs');
const prisma = new PrismaClient();
async function main() {
console.log('🌱 Seeding database...');
// Create test users
const testUsers = await Promise.all([
prisma.user.upsert({
where: { email: 'john@example.com' },
update: {},
create: {
username: 'john_dancer',
email: 'john@example.com',
passwordHash: await bcrypt.hash('Dance123!', 10),
firstName: 'John',
lastName: 'Smith',
wsdcId: '12345',
emailVerified: true,
country: 'United States',
city: 'Los Angeles',
instagramUrl: 'https://instagram.com/johndancer',
youtubeUrl: 'https://youtube.com/@johndancer',
},
}),
prisma.user.upsert({
where: { email: 'sarah@example.com' },
update: {},
create: {
username: 'sarah_swings',
email: 'sarah@example.com',
passwordHash: await bcrypt.hash('Swing456!', 10),
firstName: 'Sarah',
lastName: 'Johnson',
wsdcId: '23456',
emailVerified: true,
country: 'United Kingdom',
city: 'London',
tiktokUrl: 'https://tiktok.com/@sarahswings',
facebookUrl: 'https://facebook.com/sarah.swings',
},
}),
prisma.user.upsert({
where: { email: 'mike@example.com' },
update: {},
create: {
username: 'mike_blues',
email: 'mike@example.com',
passwordHash: await bcrypt.hash('Blues789!', 10),
firstName: 'Mike',
lastName: 'Williams',
wsdcId: '34567',
emailVerified: true,
country: 'Sweden',
city: 'Stockholm',
instagramUrl: 'https://instagram.com/mikeblues',
youtubeUrl: 'https://youtube.com/@mikeblues',
tiktokUrl: 'https://tiktok.com/@mikeblues',
facebookUrl: 'https://facebook.com/mike.blues',
},
}),
]);
console.log(`✅ Created ${testUsers.length} test users`);
// Create divisions
const divisions = await Promise.all([
prisma.division.upsert({
@@ -57,13 +117,12 @@ async function main() {
console.log(`✅ Created ${competitionTypes.length} competition types`);
// Create events
// Create events (slug will be auto-generated as cuid for security)
const events = await Promise.all([
prisma.event.upsert({
where: { slug: 'warsaw-dance-festival-2025' },
where: { worldsdcId: 'wdf-2025' },
update: {},
create: {
slug: 'warsaw-dance-festival-2025',
name: 'Warsaw Dance Festival 2025',
location: 'Warsaw, Poland',
startDate: new Date('2025-03-15'),
@@ -74,10 +133,9 @@ async function main() {
},
}),
prisma.event.upsert({
where: { slug: 'swing-camp-barcelona-2025' },
where: { worldsdcId: 'scb-2025' },
update: {},
create: {
slug: 'swing-camp-barcelona-2025',
name: 'Swing Camp Barcelona 2025',
location: 'Barcelona, Spain',
startDate: new Date('2025-04-20'),
@@ -88,10 +146,9 @@ async function main() {
},
}),
prisma.event.upsert({
where: { slug: 'blues-week-herrang-2025' },
where: { worldsdcId: 'bwh-2025' },
update: {},
create: {
slug: 'blues-week-herrang-2025',
name: 'Blues Week Herräng 2025',
location: 'Herräng, Sweden',
startDate: new Date('2025-07-14'),
@@ -102,10 +159,9 @@ async function main() {
},
}),
prisma.event.upsert({
where: { slug: 'krakow-swing-connection-2025' },
where: { worldsdcId: 'ksc-2025' },
update: {},
create: {
slug: 'krakow-swing-connection-2025',
name: 'Krakow Swing Connection 2025',
location: 'Krakow, Poland',
startDate: new Date('2025-05-10'),
@@ -133,13 +189,161 @@ async function main() {
console.log(`✅ Created ${chatRooms.length} event chat rooms`);
// Add test users as event participants (check-in)
const warsawEvent = events[0]; // Warsaw Dance Festival
const barcelonaEvent = events[1]; // Swing Camp Barcelona
const eventParticipants = await Promise.all([
// John participates in Warsaw
prisma.eventParticipant.upsert({
where: {
userId_eventId: {
userId: testUsers[0].id,
eventId: warsawEvent.id,
},
},
update: {},
create: {
userId: testUsers[0].id,
eventId: warsawEvent.id,
},
}),
// Sarah participates in Warsaw
prisma.eventParticipant.upsert({
where: {
userId_eventId: {
userId: testUsers[1].id,
eventId: warsawEvent.id,
},
},
update: {},
create: {
userId: testUsers[1].id,
eventId: warsawEvent.id,
},
}),
// Mike participates in Warsaw
prisma.eventParticipant.upsert({
where: {
userId_eventId: {
userId: testUsers[2].id,
eventId: warsawEvent.id,
},
},
update: {},
create: {
userId: testUsers[2].id,
eventId: warsawEvent.id,
},
}),
// John also in Barcelona
prisma.eventParticipant.upsert({
where: {
userId_eventId: {
userId: testUsers[0].id,
eventId: barcelonaEvent.id,
},
},
update: {},
create: {
userId: testUsers[0].id,
eventId: barcelonaEvent.id,
},
}),
]);
console.log(`✅ Added ${eventParticipants.length} event participants`);
// Create sample heats for test users in Warsaw event
const jjType = competitionTypes[0]; // Jack & Jill
const strType = competitionTypes[1]; // Strictly
const novDiv = divisions[1]; // Novice
const intDiv = divisions[2]; // Intermediate
const advDiv = divisions[3]; // Advanced
const heats = await Promise.all([
// John: J&J Novice Heat 1 (Leader), Strictly Intermediate Heat 2 (Leader)
prisma.eventUserHeat.create({
data: {
userId: testUsers[0].id,
eventId: warsawEvent.id,
divisionId: novDiv.id,
competitionTypeId: jjType.id,
heatNumber: 1,
role: 'Leader',
},
}),
prisma.eventUserHeat.create({
data: {
userId: testUsers[0].id,
eventId: warsawEvent.id,
divisionId: intDiv.id,
competitionTypeId: strType.id,
heatNumber: 2,
role: 'Leader',
},
}),
// Sarah: J&J Novice Heat 1 (Follower), J&J Advanced Heat 3
prisma.eventUserHeat.create({
data: {
userId: testUsers[1].id,
eventId: warsawEvent.id,
divisionId: novDiv.id,
competitionTypeId: jjType.id,
heatNumber: 1,
role: 'Follower',
},
}),
prisma.eventUserHeat.create({
data: {
userId: testUsers[1].id,
eventId: warsawEvent.id,
divisionId: advDiv.id,
competitionTypeId: jjType.id,
heatNumber: 3,
role: null,
},
}),
// Mike: J&J Intermediate Heat 5 (Leader), Strictly Advanced Heat 1 (Follower)
prisma.eventUserHeat.create({
data: {
userId: testUsers[2].id,
eventId: warsawEvent.id,
divisionId: intDiv.id,
competitionTypeId: jjType.id,
heatNumber: 5,
role: 'Leader',
},
}),
prisma.eventUserHeat.create({
data: {
userId: testUsers[2].id,
eventId: warsawEvent.id,
divisionId: advDiv.id,
competitionTypeId: strType.id,
heatNumber: 1,
role: 'Follower',
},
}),
]);
console.log(`✅ Created ${heats.length} sample heats`);
console.log('🎉 Seeding completed successfully!');
console.log('');
console.log('Created:');
console.log(` - ${testUsers.length} test users`);
console.log(` - ${divisions.length} divisions`);
console.log(` - ${competitionTypes.length} competition types`);
console.log(` - ${events.length} events`);
console.log(` - ${chatRooms.length} chat rooms`);
console.log(` - ${eventParticipants.length} event participants`);
console.log(` - ${heats.length} sample heats`);
console.log('');
console.log('Test accounts:');
console.log(' 1. john_dancer / Dance123!');
console.log(' 2. sarah_swings / Swing456!');
console.log(' 3. mike_blues / Blues789!');
}
main()

View File

@@ -339,17 +339,47 @@ RUN apk add --no-cache openssl
---
## Quick Reference - Mock Data
## Test Accounts
**Mock User (logged in):**
- ID: 1
- Username: john_doe
- Email: john@example.com
**Test users for manual testing (seeded in database):**
**Mock Events:**
- ID: 1 - Warsaw Dance Festival 2025
- ID: 2 - Swing Camp Barcelona 2025
- ID: 3 - Blues Week Herräng 2025
1. **john_dancer**
- Email: `john@example.com`
- Password: `Dance123!`
- Profile: John Smith (WSDC #12345), Los Angeles, USA
- Social: Instagram, YouTube
- Events: Warsaw Dance Festival (checked in), Swing Camp Barcelona (checked in)
- Heats in Warsaw: J&J NOV 1 L, STR INT 2 L
2. **sarah_swings**
- Email: `sarah@example.com`
- Password: `Swing456!`
- Profile: Sarah Johnson (WSDC #23456), London, UK
- Social: TikTok, Facebook
- Events: Warsaw Dance Festival (checked in)
- Heats in Warsaw: J&J NOV 1 F, J&J ADV 3
3. **mike_blues**
- Email: `mike@example.com`
- Password: `Blues789!`
- Profile: Mike Williams (WSDC #34567), Stockholm, Sweden
- Social: Instagram, YouTube, TikTok, Facebook
- Events: Warsaw Dance Festival (checked in)
- Heats in Warsaw: J&J INT 5 L, STR ADV 1 F
**Note:** All test users are verified and checked in to Warsaw Dance Festival 2025 event. They have pre-configured heats to test the matchmaking system.
---
## Quick Reference - Events
**Seeded Events (slugs are auto-generated cuid for security):**
- Warsaw Dance Festival 2025 (slug: auto-generated, e.g. `cmhz3lcgb00018vbn34v4phoi`)
- Swing Camp Barcelona 2025 (slug: auto-generated)
- Blues Week Herräng 2025 (slug: auto-generated)
- Krakow Swing Connection 2025 (slug: auto-generated)
**Note:** Event slugs are randomly generated cuid strings to prevent ID enumeration attacks. To find the actual slug, log in and check the Events page URL.
---