feat: add PostgreSQL database with Prisma ORM
Phase 1 - Step 2: PostgreSQL Setup **Infrastructure:** - Add PostgreSQL 15 Alpine container to docker-compose.yml - Configure persistent volume for database data - Update backend Dockerfile with OpenSSL for Prisma compatibility **Database Schema (Prisma):** - 6 tables: users, events, chat_rooms, messages, matches, ratings - Foreign key relationships and cascading deletes - Performance indexes on frequently queried columns - Unique constraints for data integrity **Prisma Setup:** - Prisma Client for database queries - Migration system with initial migration - Seed script with 4 test events and chat rooms - Database connection utility with singleton pattern **API Implementation:** - GET /api/events - List all events (with filtering and sorting) - GET /api/events/:id - Get single event with relations - Database connection test on server startup - Graceful database disconnect on shutdown **Seed Data:** - Warsaw Dance Festival 2025 - Swing Camp Barcelona 2025 - Blues Week Herräng 2025 - Krakow Swing Connection 2025 **Testing:** - Database connection verified ✅ - API endpoints returning data from PostgreSQL ✅ - Migrations applied successfully ✅ All systems operational 🚀
This commit is contained in:
128
backend/prisma/schema.prisma
Normal file
128
backend/prisma/schema.prisma
Normal file
@@ -0,0 +1,128 @@
|
||||
// Prisma schema for spotlight.cam
|
||||
// Database: PostgreSQL 15
|
||||
|
||||
generator client {
|
||||
provider = "prisma-client-js"
|
||||
}
|
||||
|
||||
datasource db {
|
||||
provider = "postgresql"
|
||||
url = env("DATABASE_URL")
|
||||
}
|
||||
|
||||
// Users table
|
||||
model User {
|
||||
id Int @id @default(autoincrement())
|
||||
username String @unique @db.VarChar(50)
|
||||
email String @unique @db.VarChar(255)
|
||||
passwordHash String @map("password_hash") @db.VarChar(255)
|
||||
avatar String? @db.VarChar(255)
|
||||
createdAt DateTime @default(now()) @map("created_at")
|
||||
updatedAt DateTime @updatedAt @map("updated_at")
|
||||
|
||||
// Relations
|
||||
messages Message[]
|
||||
matchesAsUser1 Match[] @relation("MatchUser1")
|
||||
matchesAsUser2 Match[] @relation("MatchUser2")
|
||||
ratingsGiven Rating[] @relation("RaterRatings")
|
||||
ratingsReceived Rating[] @relation("RatedRatings")
|
||||
|
||||
@@map("users")
|
||||
}
|
||||
|
||||
// Events table (dance events from worldsdc.com)
|
||||
model Event {
|
||||
id Int @id @default(autoincrement())
|
||||
name String @db.VarChar(255)
|
||||
location String @db.VarChar(255)
|
||||
startDate DateTime @map("start_date") @db.Date
|
||||
endDate DateTime @map("end_date") @db.Date
|
||||
worldsdcId String? @unique @map("worldsdc_id") @db.VarChar(100)
|
||||
participantsCount Int @default(0) @map("participants_count")
|
||||
description String? @db.Text
|
||||
createdAt DateTime @default(now()) @map("created_at")
|
||||
|
||||
// Relations
|
||||
chatRooms ChatRoom[]
|
||||
matches Match[]
|
||||
|
||||
@@map("events")
|
||||
}
|
||||
|
||||
// Chat rooms (event chat and private 1:1 chat)
|
||||
model ChatRoom {
|
||||
id Int @id @default(autoincrement())
|
||||
eventId Int? @map("event_id")
|
||||
type String @db.VarChar(20) // 'event' or 'private'
|
||||
createdAt DateTime @default(now()) @map("created_at")
|
||||
|
||||
// Relations
|
||||
event Event? @relation(fields: [eventId], references: [id])
|
||||
messages Message[]
|
||||
matches Match[]
|
||||
|
||||
@@map("chat_rooms")
|
||||
}
|
||||
|
||||
// Messages (text messages and video links)
|
||||
model Message {
|
||||
id Int @id @default(autoincrement())
|
||||
roomId Int @map("room_id")
|
||||
userId Int @map("user_id")
|
||||
content String @db.Text
|
||||
type String @db.VarChar(20) // 'text', 'link', 'video'
|
||||
createdAt DateTime @default(now()) @map("created_at")
|
||||
|
||||
// Relations
|
||||
room ChatRoom @relation(fields: [roomId], references: [id], onDelete: Cascade)
|
||||
user User @relation(fields: [userId], references: [id])
|
||||
|
||||
@@index([roomId])
|
||||
@@index([createdAt])
|
||||
@@map("messages")
|
||||
}
|
||||
|
||||
// Matches (pairs of users for collaboration)
|
||||
model Match {
|
||||
id Int @id @default(autoincrement())
|
||||
user1Id Int @map("user1_id")
|
||||
user2Id Int @map("user2_id")
|
||||
eventId Int @map("event_id")
|
||||
roomId Int? @map("room_id")
|
||||
status String @default("pending") @db.VarChar(20) // 'pending', 'accepted', 'completed'
|
||||
createdAt DateTime @default(now()) @map("created_at")
|
||||
|
||||
// Relations
|
||||
user1 User @relation("MatchUser1", fields: [user1Id], references: [id])
|
||||
user2 User @relation("MatchUser2", fields: [user2Id], references: [id])
|
||||
event Event @relation(fields: [eventId], references: [id])
|
||||
room ChatRoom? @relation(fields: [roomId], references: [id])
|
||||
ratings Rating[]
|
||||
|
||||
@@unique([user1Id, user2Id, eventId])
|
||||
@@index([user1Id])
|
||||
@@index([user2Id])
|
||||
@@index([eventId])
|
||||
@@map("matches")
|
||||
}
|
||||
|
||||
// Ratings (user ratings after collaboration)
|
||||
model Rating {
|
||||
id Int @id @default(autoincrement())
|
||||
matchId Int @map("match_id")
|
||||
raterId Int @map("rater_id")
|
||||
ratedId Int @map("rated_id")
|
||||
score Int // 1-5
|
||||
comment String? @db.Text
|
||||
wouldCollaborateAgain Boolean @default(false) @map("would_collaborate_again")
|
||||
createdAt DateTime @default(now()) @map("created_at")
|
||||
|
||||
// Relations
|
||||
match Match @relation(fields: [matchId], references: [id])
|
||||
rater User @relation("RaterRatings", fields: [raterId], references: [id])
|
||||
rated User @relation("RatedRatings", fields: [ratedId], references: [id])
|
||||
|
||||
@@unique([matchId, raterId, ratedId])
|
||||
@@index([ratedId])
|
||||
@@map("ratings")
|
||||
}
|
||||
Reference in New Issue
Block a user