✅ FASE 2 COMPLETADA: Perfiles, Social y Ranking
Implementados 3 módulos principales: 1. PERFILES EXTENDIDOS - Campos adicionales: ciudad, fecha nacimiento, años jugando - Estadísticas: partidos jugados/ganados/perdidos - Historial de cambios de nivel - Búsqueda de usuarios con filtros 2. SISTEMA SOCIAL - Amigos: solicitudes, aceptar, rechazar, bloquear - Grupos: crear, gestionar miembros, roles - Reservas recurrentes: fijos semanales 3. RANKING Y ESTADÍSTICAS - Registro de partidos 2v2 con confirmación - Sistema de puntos con bonus y multiplicadores - Ranking mensual, anual y global - Estadísticas personales y globales Nuevos endpoints: - /users/* - Perfiles y búsqueda - /friends/* - Gestión de amistades - /groups/* - Grupos de jugadores - /recurring/* - Reservas recurrentes - /matches/* - Registro de partidos - /ranking/* - Clasificaciones - /stats/* - Estadísticas Nuevos usuarios de prueba: - carlos@padel.com / 123456 - ana@padel.com / 123456 - pedro@padel.com / 123456 - maria@padel.com / 123456
This commit is contained in:
Binary file not shown.
@@ -0,0 +1,228 @@
|
||||
-- CreateTable
|
||||
CREATE TABLE "level_history" (
|
||||
"id" TEXT NOT NULL PRIMARY KEY,
|
||||
"oldLevel" TEXT NOT NULL,
|
||||
"newLevel" TEXT NOT NULL,
|
||||
"userId" TEXT NOT NULL,
|
||||
"changedBy" TEXT NOT NULL,
|
||||
"reason" TEXT,
|
||||
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
CONSTRAINT "level_history_userId_fkey" FOREIGN KEY ("userId") REFERENCES "users" ("id") ON DELETE CASCADE ON UPDATE CASCADE
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "friends" (
|
||||
"id" TEXT NOT NULL PRIMARY KEY,
|
||||
"requesterId" TEXT NOT NULL,
|
||||
"addresseeId" TEXT NOT NULL,
|
||||
"status" TEXT NOT NULL DEFAULT 'PENDING',
|
||||
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" DATETIME NOT NULL,
|
||||
CONSTRAINT "friends_requesterId_fkey" FOREIGN KEY ("requesterId") REFERENCES "users" ("id") ON DELETE RESTRICT ON UPDATE CASCADE,
|
||||
CONSTRAINT "friends_addresseeId_fkey" FOREIGN KEY ("addresseeId") REFERENCES "users" ("id") ON DELETE RESTRICT ON UPDATE CASCADE
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "groups" (
|
||||
"id" TEXT NOT NULL PRIMARY KEY,
|
||||
"name" TEXT NOT NULL,
|
||||
"description" TEXT,
|
||||
"createdById" TEXT NOT NULL,
|
||||
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" DATETIME NOT NULL,
|
||||
CONSTRAINT "groups_createdById_fkey" FOREIGN KEY ("createdById") REFERENCES "users" ("id") ON DELETE RESTRICT ON UPDATE CASCADE
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "group_members" (
|
||||
"id" TEXT NOT NULL PRIMARY KEY,
|
||||
"groupId" TEXT NOT NULL,
|
||||
"userId" TEXT NOT NULL,
|
||||
"role" TEXT NOT NULL DEFAULT 'MEMBER',
|
||||
"joinedAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
CONSTRAINT "group_members_groupId_fkey" FOREIGN KEY ("groupId") REFERENCES "groups" ("id") ON DELETE CASCADE ON UPDATE CASCADE,
|
||||
CONSTRAINT "group_members_userId_fkey" FOREIGN KEY ("userId") REFERENCES "users" ("id") ON DELETE RESTRICT ON UPDATE CASCADE
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "recurring_bookings" (
|
||||
"id" TEXT NOT NULL PRIMARY KEY,
|
||||
"userId" TEXT NOT NULL,
|
||||
"courtId" TEXT NOT NULL,
|
||||
"dayOfWeek" INTEGER NOT NULL,
|
||||
"startTime" TEXT NOT NULL,
|
||||
"endTime" TEXT NOT NULL,
|
||||
"startDate" DATETIME NOT NULL,
|
||||
"endDate" DATETIME,
|
||||
"isActive" BOOLEAN NOT NULL DEFAULT true,
|
||||
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" DATETIME NOT NULL,
|
||||
CONSTRAINT "recurring_bookings_userId_fkey" FOREIGN KEY ("userId") REFERENCES "users" ("id") ON DELETE RESTRICT ON UPDATE CASCADE,
|
||||
CONSTRAINT "recurring_bookings_courtId_fkey" FOREIGN KEY ("courtId") REFERENCES "courts" ("id") ON DELETE RESTRICT ON UPDATE CASCADE
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "match_results" (
|
||||
"id" TEXT NOT NULL PRIMARY KEY,
|
||||
"bookingId" TEXT,
|
||||
"team1Player1Id" TEXT NOT NULL,
|
||||
"team1Player2Id" TEXT NOT NULL,
|
||||
"team2Player1Id" TEXT NOT NULL,
|
||||
"team2Player2Id" TEXT NOT NULL,
|
||||
"team1Score" INTEGER NOT NULL,
|
||||
"team2Score" INTEGER NOT NULL,
|
||||
"winner" TEXT NOT NULL,
|
||||
"playedAt" DATETIME NOT NULL,
|
||||
"confirmedBy" TEXT NOT NULL DEFAULT '[]',
|
||||
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
CONSTRAINT "match_results_bookingId_fkey" FOREIGN KEY ("bookingId") REFERENCES "bookings" ("id") ON DELETE SET NULL ON UPDATE CASCADE,
|
||||
CONSTRAINT "match_results_team1Player1Id_fkey" FOREIGN KEY ("team1Player1Id") REFERENCES "users" ("id") ON DELETE RESTRICT ON UPDATE CASCADE,
|
||||
CONSTRAINT "match_results_team1Player2Id_fkey" FOREIGN KEY ("team1Player2Id") REFERENCES "users" ("id") ON DELETE RESTRICT ON UPDATE CASCADE,
|
||||
CONSTRAINT "match_results_team2Player1Id_fkey" FOREIGN KEY ("team2Player1Id") REFERENCES "users" ("id") ON DELETE RESTRICT ON UPDATE CASCADE,
|
||||
CONSTRAINT "match_results_team2Player2Id_fkey" FOREIGN KEY ("team2Player2Id") REFERENCES "users" ("id") ON DELETE RESTRICT ON UPDATE CASCADE
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "user_stats" (
|
||||
"id" TEXT NOT NULL PRIMARY KEY,
|
||||
"userId" TEXT NOT NULL,
|
||||
"period" TEXT NOT NULL,
|
||||
"periodValue" TEXT NOT NULL,
|
||||
"matchesPlayed" INTEGER NOT NULL DEFAULT 0,
|
||||
"matchesWon" INTEGER NOT NULL DEFAULT 0,
|
||||
"matchesLost" INTEGER NOT NULL DEFAULT 0,
|
||||
"tournamentsPlayed" INTEGER NOT NULL DEFAULT 0,
|
||||
"tournamentsWon" INTEGER NOT NULL DEFAULT 0,
|
||||
"points" INTEGER NOT NULL DEFAULT 0,
|
||||
"updatedAt" DATETIME NOT NULL,
|
||||
CONSTRAINT "user_stats_userId_fkey" FOREIGN KEY ("userId") REFERENCES "users" ("id") ON DELETE CASCADE ON UPDATE CASCADE
|
||||
);
|
||||
|
||||
-- RedefineTables
|
||||
PRAGMA defer_foreign_keys=ON;
|
||||
PRAGMA foreign_keys=OFF;
|
||||
CREATE TABLE "new_bookings" (
|
||||
"id" TEXT NOT NULL PRIMARY KEY,
|
||||
"date" DATETIME NOT NULL,
|
||||
"startTime" TEXT NOT NULL,
|
||||
"endTime" TEXT NOT NULL,
|
||||
"status" TEXT NOT NULL DEFAULT 'PENDING',
|
||||
"totalPrice" INTEGER NOT NULL,
|
||||
"notes" TEXT,
|
||||
"userId" TEXT NOT NULL,
|
||||
"courtId" TEXT NOT NULL,
|
||||
"recurringBookingId" TEXT,
|
||||
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" DATETIME NOT NULL,
|
||||
CONSTRAINT "bookings_userId_fkey" FOREIGN KEY ("userId") REFERENCES "users" ("id") ON DELETE RESTRICT ON UPDATE CASCADE,
|
||||
CONSTRAINT "bookings_courtId_fkey" FOREIGN KEY ("courtId") REFERENCES "courts" ("id") ON DELETE RESTRICT ON UPDATE CASCADE,
|
||||
CONSTRAINT "bookings_recurringBookingId_fkey" FOREIGN KEY ("recurringBookingId") REFERENCES "recurring_bookings" ("id") ON DELETE SET NULL ON UPDATE CASCADE
|
||||
);
|
||||
INSERT INTO "new_bookings" ("courtId", "createdAt", "date", "endTime", "id", "notes", "startTime", "status", "totalPrice", "updatedAt", "userId") SELECT "courtId", "createdAt", "date", "endTime", "id", "notes", "startTime", "status", "totalPrice", "updatedAt", "userId" FROM "bookings";
|
||||
DROP TABLE "bookings";
|
||||
ALTER TABLE "new_bookings" RENAME TO "bookings";
|
||||
CREATE INDEX "bookings_userId_idx" ON "bookings"("userId");
|
||||
CREATE INDEX "bookings_courtId_idx" ON "bookings"("courtId");
|
||||
CREATE INDEX "bookings_date_idx" ON "bookings"("date");
|
||||
CREATE INDEX "bookings_recurringBookingId_idx" ON "bookings"("recurringBookingId");
|
||||
CREATE TABLE "new_users" (
|
||||
"id" TEXT NOT NULL PRIMARY KEY,
|
||||
"email" TEXT NOT NULL,
|
||||
"password" TEXT NOT NULL,
|
||||
"firstName" TEXT NOT NULL,
|
||||
"lastName" TEXT NOT NULL,
|
||||
"phone" TEXT,
|
||||
"avatarUrl" TEXT,
|
||||
"city" TEXT,
|
||||
"birthDate" DATETIME,
|
||||
"role" TEXT NOT NULL DEFAULT 'PLAYER',
|
||||
"playerLevel" TEXT NOT NULL DEFAULT 'BEGINNER',
|
||||
"handPreference" TEXT NOT NULL DEFAULT 'RIGHT',
|
||||
"positionPreference" TEXT NOT NULL DEFAULT 'BOTH',
|
||||
"bio" TEXT,
|
||||
"yearsPlaying" INTEGER,
|
||||
"matchesPlayed" INTEGER NOT NULL DEFAULT 0,
|
||||
"matchesWon" INTEGER NOT NULL DEFAULT 0,
|
||||
"matchesLost" INTEGER NOT NULL DEFAULT 0,
|
||||
"totalPoints" INTEGER NOT NULL DEFAULT 0,
|
||||
"isActive" BOOLEAN NOT NULL DEFAULT true,
|
||||
"isVerified" BOOLEAN NOT NULL DEFAULT false,
|
||||
"lastLogin" DATETIME,
|
||||
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" DATETIME NOT NULL
|
||||
);
|
||||
INSERT INTO "new_users" ("avatarUrl", "bio", "createdAt", "email", "firstName", "handPreference", "id", "isActive", "isVerified", "lastLogin", "lastName", "password", "phone", "playerLevel", "positionPreference", "role", "updatedAt") SELECT "avatarUrl", "bio", "createdAt", "email", "firstName", "handPreference", "id", "isActive", "isVerified", "lastLogin", "lastName", "password", "phone", "playerLevel", "positionPreference", "role", "updatedAt" FROM "users";
|
||||
DROP TABLE "users";
|
||||
ALTER TABLE "new_users" RENAME TO "users";
|
||||
CREATE UNIQUE INDEX "users_email_key" ON "users"("email");
|
||||
PRAGMA foreign_keys=ON;
|
||||
PRAGMA defer_foreign_keys=OFF;
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "level_history_userId_idx" ON "level_history"("userId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "friends_requesterId_idx" ON "friends"("requesterId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "friends_addresseeId_idx" ON "friends"("addresseeId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "friends_status_idx" ON "friends"("status");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "friends_requesterId_addresseeId_key" ON "friends"("requesterId", "addresseeId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "groups_createdById_idx" ON "groups"("createdById");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "group_members_groupId_idx" ON "group_members"("groupId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "group_members_userId_idx" ON "group_members"("userId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "group_members_groupId_userId_key" ON "group_members"("groupId", "userId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "recurring_bookings_userId_idx" ON "recurring_bookings"("userId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "recurring_bookings_courtId_idx" ON "recurring_bookings"("courtId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "recurring_bookings_dayOfWeek_idx" ON "recurring_bookings"("dayOfWeek");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "recurring_bookings_isActive_idx" ON "recurring_bookings"("isActive");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "match_results_bookingId_key" ON "match_results"("bookingId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "match_results_team1Player1Id_idx" ON "match_results"("team1Player1Id");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "match_results_team1Player2Id_idx" ON "match_results"("team1Player2Id");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "match_results_team2Player1Id_idx" ON "match_results"("team2Player1Id");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "match_results_team2Player2Id_idx" ON "match_results"("team2Player2Id");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "match_results_playedAt_idx" ON "match_results"("playedAt");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "user_stats_userId_idx" ON "user_stats"("userId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "user_stats_period_periodValue_idx" ON "user_stats"("period", "periodValue");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "user_stats_points_idx" ON "user_stats"("points");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "user_stats_userId_period_periodValue_key" ON "user_stats"("userId", "period", "periodValue");
|
||||
@@ -0,0 +1 @@
|
||||
-- This is an empty migration.
|
||||
@@ -21,6 +21,8 @@ model User {
|
||||
lastName String
|
||||
phone String?
|
||||
avatarUrl String?
|
||||
city String?
|
||||
birthDate DateTime?
|
||||
|
||||
// Datos de juego (usamos String para simular enums en SQLite)
|
||||
role String @default("PLAYER") // PLAYER, ADMIN, SUPERADMIN
|
||||
@@ -28,6 +30,13 @@ model User {
|
||||
handPreference String @default("RIGHT") // RIGHT, LEFT, BOTH
|
||||
positionPreference String @default("BOTH") // DRIVE, BACKHAND, BOTH
|
||||
bio String?
|
||||
yearsPlaying Int?
|
||||
|
||||
// Estadísticas globales
|
||||
matchesPlayed Int @default(0)
|
||||
matchesWon Int @default(0)
|
||||
matchesLost Int @default(0)
|
||||
totalPoints Int @default(0)
|
||||
|
||||
// Estado
|
||||
isActive Boolean @default(true)
|
||||
@@ -35,13 +44,58 @@ model User {
|
||||
lastLogin DateTime?
|
||||
|
||||
// Relaciones
|
||||
bookings Booking[]
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
bookings Booking[]
|
||||
levelHistory LevelHistory[]
|
||||
|
||||
// Relaciones con MatchResult
|
||||
team1Player1Matches MatchResult[] @relation("Team1Player1")
|
||||
team1Player2Matches MatchResult[] @relation("Team1Player2")
|
||||
team2Player1Matches MatchResult[] @relation("Team2Player1")
|
||||
team2Player2Matches MatchResult[] @relation("Team2Player2")
|
||||
|
||||
// Relación con UserStats
|
||||
userStats UserStats[]
|
||||
|
||||
// Amistades
|
||||
friendsSent Friend[] @relation("FriendRequestsSent")
|
||||
friendsReceived Friend[] @relation("FriendRequestsReceived")
|
||||
|
||||
// Grupos
|
||||
groupsCreated Group[] @relation("GroupsCreated")
|
||||
groupMembers GroupMember[] @relation("GroupMemberships")
|
||||
|
||||
// Reservas recurrentes
|
||||
recurringBookings RecurringBooking[]
|
||||
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
|
||||
@@map("users")
|
||||
}
|
||||
|
||||
// Modelo de Historial de Niveles
|
||||
model LevelHistory {
|
||||
id String @id @default(uuid())
|
||||
|
||||
// Niveles
|
||||
oldLevel String
|
||||
newLevel String
|
||||
|
||||
// Referencias
|
||||
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
||||
userId String
|
||||
|
||||
// Quién realizó el cambio (admin)
|
||||
changedBy String
|
||||
|
||||
// Metadata
|
||||
reason String?
|
||||
createdAt DateTime @default(now())
|
||||
|
||||
@@index([userId])
|
||||
@@map("level_history")
|
||||
}
|
||||
|
||||
// Modelo de Cancha
|
||||
model Court {
|
||||
id String @id @default(uuid())
|
||||
@@ -66,6 +120,7 @@ model Court {
|
||||
// Relaciones
|
||||
bookings Booking[]
|
||||
schedules CourtSchedule[]
|
||||
recurringBookings RecurringBooking[]
|
||||
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
@@ -120,6 +175,13 @@ model Booking {
|
||||
court Court @relation(fields: [courtId], references: [id])
|
||||
courtId String
|
||||
|
||||
// Relación con MatchResult
|
||||
matchResult MatchResult?
|
||||
|
||||
// Referencia a reserva recurrente (si aplica)
|
||||
recurringBooking RecurringBooking? @relation(fields: [recurringBookingId], references: [id])
|
||||
recurringBookingId String?
|
||||
|
||||
// Timestamps
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
@@ -127,5 +189,195 @@ model Booking {
|
||||
@@index([userId])
|
||||
@@index([courtId])
|
||||
@@index([date])
|
||||
@@index([recurringBookingId])
|
||||
@@map("bookings")
|
||||
}
|
||||
|
||||
// Modelo de Amistad
|
||||
model Friend {
|
||||
id String @id @default(uuid())
|
||||
|
||||
// Quien envía la solicitud
|
||||
requester User @relation("FriendRequestsSent", fields: [requesterId], references: [id])
|
||||
requesterId String
|
||||
|
||||
// Quien recibe la solicitud
|
||||
addressee User @relation("FriendRequestsReceived", fields: [addresseeId], references: [id])
|
||||
addresseeId String
|
||||
|
||||
// Estado (PENDING, ACCEPTED, REJECTED, BLOCKED)
|
||||
status String @default("PENDING")
|
||||
|
||||
// Timestamps
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
|
||||
@@unique([requesterId, addresseeId])
|
||||
@@index([requesterId])
|
||||
@@index([addresseeId])
|
||||
@@index([status])
|
||||
@@map("friends")
|
||||
}
|
||||
|
||||
// Modelo de Grupo
|
||||
model Group {
|
||||
id String @id @default(uuid())
|
||||
name String
|
||||
description String?
|
||||
|
||||
// Creador del grupo
|
||||
createdBy User @relation("GroupsCreated", fields: [createdById], references: [id])
|
||||
createdById String
|
||||
|
||||
// Relaciones
|
||||
members GroupMember[]
|
||||
|
||||
// Timestamps
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
|
||||
@@index([createdById])
|
||||
@@map("groups")
|
||||
}
|
||||
|
||||
// Modelo de Miembro de Grupo
|
||||
model GroupMember {
|
||||
id String @id @default(uuid())
|
||||
|
||||
// Grupo
|
||||
group Group @relation(fields: [groupId], references: [id], onDelete: Cascade)
|
||||
groupId String
|
||||
|
||||
// Usuario
|
||||
user User @relation("GroupMemberships", fields: [userId], references: [id])
|
||||
userId String
|
||||
|
||||
// Rol (ADMIN, MEMBER)
|
||||
role String @default("MEMBER")
|
||||
|
||||
// Fecha de unión
|
||||
joinedAt DateTime @default(now())
|
||||
|
||||
@@unique([groupId, userId])
|
||||
@@index([groupId])
|
||||
@@index([userId])
|
||||
@@map("group_members")
|
||||
}
|
||||
|
||||
// Modelo de Reserva Recurrente
|
||||
model RecurringBooking {
|
||||
id String @id @default(uuid())
|
||||
|
||||
// Usuario que crea la reserva recurrente
|
||||
user User @relation(fields: [userId], references: [id])
|
||||
userId String
|
||||
|
||||
// Cancha
|
||||
court Court @relation(fields: [courtId], references: [id])
|
||||
courtId String
|
||||
|
||||
// Día de la semana (0=Domingo, 1=Lunes, ..., 6=Sábado)
|
||||
dayOfWeek Int
|
||||
|
||||
// Horario
|
||||
startTime String
|
||||
endTime String
|
||||
|
||||
// Rango de fechas
|
||||
startDate DateTime
|
||||
endDate DateTime?
|
||||
|
||||
// Estado
|
||||
isActive Boolean @default(true)
|
||||
|
||||
// Relaciones
|
||||
bookings Booking[]
|
||||
|
||||
// Timestamps
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
|
||||
@@index([userId])
|
||||
@@index([courtId])
|
||||
@@index([dayOfWeek])
|
||||
@@index([isActive])
|
||||
@@map("recurring_bookings")
|
||||
}
|
||||
|
||||
// Modelo de Resultado de Partido
|
||||
model MatchResult {
|
||||
id String @id @default(uuid())
|
||||
|
||||
// Relación opcional con reserva
|
||||
booking Booking? @relation(fields: [bookingId], references: [id], onDelete: SetNull)
|
||||
bookingId String? @unique
|
||||
|
||||
// Jugadores del Equipo 1
|
||||
team1Player1 User @relation("Team1Player1", fields: [team1Player1Id], references: [id])
|
||||
team1Player1Id String
|
||||
team1Player2 User @relation("Team1Player2", fields: [team1Player2Id], references: [id])
|
||||
team1Player2Id String
|
||||
|
||||
// Jugadores del Equipo 2
|
||||
team2Player1 User @relation("Team2Player1", fields: [team2Player1Id], references: [id])
|
||||
team2Player1Id String
|
||||
team2Player2 User @relation("Team2Player2", fields: [team2Player2Id], references: [id])
|
||||
team2Player2Id String
|
||||
|
||||
// Resultado
|
||||
team1Score Int
|
||||
team2Score Int
|
||||
winner String // TEAM1, TEAM2, DRAW
|
||||
|
||||
// Fecha en que se jugó el partido
|
||||
playedAt DateTime
|
||||
|
||||
// Confirmaciones (JSON array de userIds)
|
||||
confirmedBy String @default("[]")
|
||||
|
||||
// Timestamps
|
||||
createdAt DateTime @default(now())
|
||||
|
||||
@@index([team1Player1Id])
|
||||
@@index([team1Player2Id])
|
||||
@@index([team2Player1Id])
|
||||
@@index([team2Player2Id])
|
||||
@@index([playedAt])
|
||||
@@map("match_results")
|
||||
}
|
||||
|
||||
// Modelo de Estadísticas de Usuario por Período
|
||||
model UserStats {
|
||||
id String @id @default(uuid())
|
||||
|
||||
// Usuario
|
||||
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
||||
userId String
|
||||
|
||||
// Período (MONTH, YEAR, ALL_TIME)
|
||||
period String
|
||||
|
||||
// Valor del período (ej: "2024-01" para mes, "2024" para año)
|
||||
periodValue String
|
||||
|
||||
// Estadísticas de partidos
|
||||
matchesPlayed Int @default(0)
|
||||
matchesWon Int @default(0)
|
||||
matchesLost Int @default(0)
|
||||
|
||||
// Estadísticas de torneos
|
||||
tournamentsPlayed Int @default(0)
|
||||
tournamentsWon Int @default(0)
|
||||
|
||||
// Puntos para ranking
|
||||
points Int @default(0)
|
||||
|
||||
// Timestamps
|
||||
updatedAt DateTime @updatedAt
|
||||
|
||||
@@unique([userId, period, periodValue])
|
||||
@@index([userId])
|
||||
@@index([period, periodValue])
|
||||
@@index([points])
|
||||
@@map("user_stats")
|
||||
}
|
||||
|
||||
192
backend/prisma/seed-fase2.ts
Normal file
192
backend/prisma/seed-fase2.ts
Normal file
@@ -0,0 +1,192 @@
|
||||
import { PrismaClient } from '@prisma/client';
|
||||
import { hashPassword } from '../src/utils/password';
|
||||
import { UserRole, PlayerLevel, FriendStatus, GroupRole, CourtType } from '../src/utils/constants';
|
||||
|
||||
const prisma = new PrismaClient();
|
||||
|
||||
async function main() {
|
||||
console.log('🌱 Seeding Fase 2 data...\n');
|
||||
|
||||
// Crear jugadores adicionales para pruebas sociales
|
||||
const usersData = [
|
||||
{ email: 'carlos@padel.com', firstName: 'Carlos', lastName: 'Martínez', level: PlayerLevel.ADVANCED },
|
||||
{ email: 'ana@padel.com', firstName: 'Ana', lastName: 'López', level: PlayerLevel.INTERMEDIATE },
|
||||
{ email: 'pedro@padel.com', firstName: 'Pedro', lastName: 'Sánchez', level: PlayerLevel.BEGINNER },
|
||||
{ email: 'maria@padel.com', firstName: 'María', lastName: 'García', level: PlayerLevel.COMPETITION },
|
||||
];
|
||||
|
||||
const createdUsers = [];
|
||||
for (const userData of usersData) {
|
||||
const password = await hashPassword('123456');
|
||||
const user = await prisma.user.upsert({
|
||||
where: { email: userData.email },
|
||||
update: {},
|
||||
create: {
|
||||
email: userData.email,
|
||||
password,
|
||||
firstName: userData.firstName,
|
||||
lastName: userData.lastName,
|
||||
role: UserRole.PLAYER,
|
||||
playerLevel: userData.level,
|
||||
city: 'Madrid',
|
||||
yearsPlaying: Math.floor(Math.random() * 10) + 1,
|
||||
matchesPlayed: Math.floor(Math.random() * 50),
|
||||
matchesWon: Math.floor(Math.random() * 30),
|
||||
isActive: true,
|
||||
},
|
||||
});
|
||||
createdUsers.push(user);
|
||||
console.log(`✅ Usuario creado: ${user.firstName} ${user.lastName} (${user.email})`);
|
||||
}
|
||||
|
||||
// Obtener el admin y user original
|
||||
const admin = await prisma.user.findUnique({ where: { email: 'admin@padel.com' } });
|
||||
const user = await prisma.user.findUnique({ where: { email: 'user@padel.com' } });
|
||||
|
||||
if (admin && user && createdUsers.length >= 2) {
|
||||
// Crear relaciones de amistad
|
||||
const friendships = [
|
||||
{ requester: user.id, addressee: createdUsers[0].id }, // Juan - Carlos
|
||||
{ requester: createdUsers[1].id, addressee: user.id }, // Ana - Juan (pendiente)
|
||||
{ requester: user.id, addressee: createdUsers[2].id }, // Juan - Pedro
|
||||
];
|
||||
|
||||
for (const friendship of friendships) {
|
||||
await prisma.friend.upsert({
|
||||
where: {
|
||||
requesterId_addresseeId: {
|
||||
requesterId: friendship.requester,
|
||||
addresseeId: friendship.addressee,
|
||||
},
|
||||
},
|
||||
update: {},
|
||||
create: {
|
||||
requesterId: friendship.requester,
|
||||
addresseeId: friendship.addressee,
|
||||
status: FriendStatus.ACCEPTED,
|
||||
},
|
||||
});
|
||||
}
|
||||
console.log('\n✅ Amistades creadas');
|
||||
|
||||
// Crear un grupo
|
||||
const group = await prisma.group.upsert({
|
||||
where: { id: 'group-1' },
|
||||
update: {},
|
||||
create: {
|
||||
id: 'group-1',
|
||||
name: 'Los Padelistas',
|
||||
description: 'Grupo de jugadores regulares los fines de semana',
|
||||
createdBy: admin.id,
|
||||
members: {
|
||||
create: [
|
||||
{ userId: admin.id, role: GroupRole.ADMIN },
|
||||
{ userId: user.id, role: GroupRole.MEMBER },
|
||||
{ userId: createdUsers[0].id, role: GroupRole.MEMBER },
|
||||
{ userId: createdUsers[1].id, role: GroupRole.MEMBER },
|
||||
],
|
||||
},
|
||||
},
|
||||
});
|
||||
console.log(`✅ Grupo creado: ${group.name}`);
|
||||
|
||||
// Crear reserva recurrente
|
||||
const court = await prisma.court.findFirst({ where: { isActive: true } });
|
||||
if (court) {
|
||||
const recurring = await prisma.recurringBooking.create({
|
||||
data: {
|
||||
userId: user.id,
|
||||
courtId: court.id,
|
||||
dayOfWeek: 6, // Sábado
|
||||
startTime: '10:00',
|
||||
endTime: '12:00',
|
||||
startDate: new Date(),
|
||||
isActive: true,
|
||||
},
|
||||
});
|
||||
console.log(`✅ Reserva recurrente creada: Sábados ${recurring.startTime}-${recurring.endTime}`);
|
||||
}
|
||||
|
||||
// Registrar algunos partidos
|
||||
const matchResults = [
|
||||
{
|
||||
team1: [user.id, createdUsers[0].id],
|
||||
team2: [createdUsers[1].id, createdUsers[2].id],
|
||||
score1: 6,
|
||||
score2: 4,
|
||||
playedAt: new Date(Date.now() - 7 * 24 * 60 * 60 * 1000), // Hace 1 semana
|
||||
},
|
||||
{
|
||||
team1: [user.id, createdUsers[1].id],
|
||||
team2: [createdUsers[0].id, createdUsers[2].id],
|
||||
score1: 3,
|
||||
score2: 6,
|
||||
playedAt: new Date(Date.now() - 3 * 24 * 60 * 60 * 1000), // Hace 3 días
|
||||
},
|
||||
];
|
||||
|
||||
for (const match of matchResults) {
|
||||
await prisma.matchResult.create({
|
||||
data: {
|
||||
team1Player1Id: match.team1[0],
|
||||
team1Player2Id: match.team1[1],
|
||||
team2Player1Id: match.team2[0],
|
||||
team2Player2Id: match.team2[1],
|
||||
team1Score: match.score1,
|
||||
team2Score: match.score2,
|
||||
winner: match.score1 > match.score2 ? 'TEAM1' : 'TEAM2',
|
||||
playedAt: match.playedAt,
|
||||
confirmedBy: [match.team1[0], match.team2[0]],
|
||||
},
|
||||
});
|
||||
}
|
||||
console.log(`✅ Partidos registrados: ${matchResults.length}`);
|
||||
|
||||
// Crear estadísticas de usuario
|
||||
for (const u of [user, ...createdUsers.slice(0, 2)]) {
|
||||
await prisma.userStats.upsert({
|
||||
where: {
|
||||
userId_period_periodValue: {
|
||||
userId: u.id,
|
||||
period: 'ALL_TIME',
|
||||
periodValue: 'all',
|
||||
},
|
||||
},
|
||||
update: {},
|
||||
create: {
|
||||
userId: u.id,
|
||||
period: 'ALL_TIME',
|
||||
periodValue: 'all',
|
||||
matchesPlayed: Math.floor(Math.random() * 30) + 5,
|
||||
matchesWon: Math.floor(Math.random() * 20),
|
||||
matchesLost: Math.floor(Math.random() * 10),
|
||||
points: Math.floor(Math.random() * 500) + 100,
|
||||
},
|
||||
});
|
||||
}
|
||||
console.log(`✅ Estadísticas de usuarios creadas`);
|
||||
}
|
||||
|
||||
// Actualizar puntos totales de usuarios
|
||||
await prisma.user.updateMany({
|
||||
data: {
|
||||
totalPoints: { increment: 100 },
|
||||
},
|
||||
});
|
||||
|
||||
console.log('\n🎾 Fase 2 seed completado!');
|
||||
console.log('\nNuevos usuarios de prueba:');
|
||||
console.log(' carlos@padel.com / 123456');
|
||||
console.log(' ana@padel.com / 123456');
|
||||
console.log(' pedro@padel.com / 123456');
|
||||
console.log(' maria@padel.com / 123456');
|
||||
}
|
||||
|
||||
main()
|
||||
.catch((e) => {
|
||||
console.error(e);
|
||||
process.exit(1);
|
||||
})
|
||||
.finally(async () => {
|
||||
await prisma.$disconnect();
|
||||
});
|
||||
Reference in New Issue
Block a user