From 08eddc1d9aa7e83b6bee7f247e1d69ae015cd408 Mon Sep 17 00:00:00 2001 From: Jessica Orozco Date: Tue, 30 Jun 2026 21:03:45 -0400 Subject: [PATCH] feat: add Prisma ORM to backend with spaces migration and PrismaService --- backend/package.json | 6 +- .../20260615000000_init/migration.sql | 17 ++++++ backend/prisma/migrations/migration_lock.toml | 3 + backend/prisma/schema.prisma | 23 ++++++++ backend/src/app.module.ts | 3 + backend/src/prisma/prisma.module.ts | 9 +++ backend/src/prisma/prisma.service.ts | 16 ++++++ backend/src/spaces/spaces.controller.ts | 8 +-- backend/src/spaces/spaces.module.ts | 7 ++- backend/src/spaces/spaces.service.ts | 55 ++++++++++--------- 10 files changed, 114 insertions(+), 33 deletions(-) create mode 100644 backend/prisma/migrations/20260615000000_init/migration.sql create mode 100644 backend/prisma/migrations/migration_lock.toml create mode 100644 backend/prisma/schema.prisma create mode 100644 backend/src/prisma/prisma.module.ts create mode 100644 backend/src/prisma/prisma.service.ts diff --git a/backend/package.json b/backend/package.json index 931cbda..b86c268 100644 --- a/backend/package.json +++ b/backend/package.json @@ -6,7 +6,9 @@ "build": "nest build", "start": "nest start", "start:dev": "nest start --watch", - "start:prod": "node dist/main" + "start:prod": "node dist/main", + "db:migrate": "prisma migrate deploy", + "db:generate": "prisma generate" }, "dependencies": { "@nestjs/common": "^10.0.0", @@ -14,6 +16,7 @@ "@nestjs/jwt": "^10.0.0", "@nestjs/passport": "^10.0.0", "@nestjs/platform-express": "^10.0.0", + "@prisma/client": "^5.0.0", "passport": "^0.7.0", "passport-jwt": "^4.0.1", "reflect-metadata": "^0.1.13", @@ -24,6 +27,7 @@ "@nestjs/schematics": "^10.0.0", "@types/node": "^20.0.0", "@types/passport-jwt": "^4.0.0", + "prisma": "^5.0.0", "typescript": "^5.0.0" } } \ No newline at end of file diff --git a/backend/prisma/migrations/20260615000000_init/migration.sql b/backend/prisma/migrations/20260615000000_init/migration.sql new file mode 100644 index 0000000..ed42228 --- /dev/null +++ b/backend/prisma/migrations/20260615000000_init/migration.sql @@ -0,0 +1,17 @@ +-- CreateTable +CREATE TABLE "reservas" ( + "id" TEXT NOT NULL, + "resourceId" TEXT NOT NULL, + "resourceName" TEXT NOT NULL, + "date" TEXT NOT NULL, + "startTime" TEXT NOT NULL, + "endTime" TEXT NOT NULL, + "author" TEXT NOT NULL, + "notes" TEXT, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + + CONSTRAINT "reservas_pkey" PRIMARY KEY ("id") +); + +-- CreateIndex +CREATE UNIQUE INDEX "reservas_resourceName_date_startTime_key" ON "reservas"("resourceName", "date", "startTime"); diff --git a/backend/prisma/migrations/migration_lock.toml b/backend/prisma/migrations/migration_lock.toml new file mode 100644 index 0000000..99e4f20 --- /dev/null +++ b/backend/prisma/migrations/migration_lock.toml @@ -0,0 +1,3 @@ +# Please do not edit this file manually +# It should be added in your version-control system (i.e. Git) +provider = "postgresql" diff --git a/backend/prisma/schema.prisma b/backend/prisma/schema.prisma new file mode 100644 index 0000000..479ba39 --- /dev/null +++ b/backend/prisma/schema.prisma @@ -0,0 +1,23 @@ +generator client { + provider = "prisma-client-js" +} + +datasource db { + provider = "postgresql" + url = env("DATABASE_URL") +} + +model Reserva { + id String @id @default(cuid()) + resourceId String + resourceName String + date String // "YYYY-MM-DD" + startTime String // "HH:mm" + endTime String // "HH:mm" + author String + notes String? + createdAt DateTime @default(now()) + + @@unique([resourceName, date, startTime]) + @@map("reservas") +} diff --git a/backend/src/app.module.ts b/backend/src/app.module.ts index e11aa55..e6968a0 100644 --- a/backend/src/app.module.ts +++ b/backend/src/app.module.ts @@ -1,4 +1,5 @@ import { Module } from '@nestjs/common'; +import { PrismaModule } from './prisma/prisma.module'; import { AuthModule } from './auth/auth.module'; import { CmsModule } from './cms/cms.module'; import { DirectoryModule } from './directory/directory.module'; @@ -8,6 +9,7 @@ import { TicketingModule } from './ticketing/ticketing.module'; @Module({ imports: [ + PrismaModule, AuthModule, CmsModule, DirectoryModule, @@ -17,3 +19,4 @@ import { TicketingModule } from './ticketing/ticketing.module'; ], }) export class AppModule {} + diff --git a/backend/src/prisma/prisma.module.ts b/backend/src/prisma/prisma.module.ts new file mode 100644 index 0000000..7207426 --- /dev/null +++ b/backend/src/prisma/prisma.module.ts @@ -0,0 +1,9 @@ +import { Global, Module } from '@nestjs/common'; +import { PrismaService } from './prisma.service'; + +@Global() +@Module({ + providers: [PrismaService], + exports: [PrismaService], +}) +export class PrismaModule {} diff --git a/backend/src/prisma/prisma.service.ts b/backend/src/prisma/prisma.service.ts new file mode 100644 index 0000000..7ffd32d --- /dev/null +++ b/backend/src/prisma/prisma.service.ts @@ -0,0 +1,16 @@ +import { Injectable, OnModuleInit, OnModuleDestroy } from '@nestjs/common'; +import { PrismaClient } from '@prisma/client'; + +@Injectable() +export class PrismaService + extends PrismaClient + implements OnModuleInit, OnModuleDestroy +{ + async onModuleInit() { + await this.$connect(); + } + + async onModuleDestroy() { + await this.$disconnect(); + } +} diff --git a/backend/src/spaces/spaces.controller.ts b/backend/src/spaces/spaces.controller.ts index 3acabc8..b8316ae 100644 --- a/backend/src/spaces/spaces.controller.ts +++ b/backend/src/spaces/spaces.controller.ts @@ -19,7 +19,7 @@ export class SpacesController { * Devuelve todas las reservas (opcionalmente filtradas por fecha) */ @Get() - findAll(@Query('date') date?: string) { + async findAll(@Query('date') date?: string) { return this.spacesService.findAll(date); } @@ -28,7 +28,7 @@ export class SpacesController { * Crea una nueva reserva */ @Post() - create(@Body() dto: CreateReservaDto) { + async create(@Body() dto: CreateReservaDto) { return this.spacesService.create(dto); } @@ -37,8 +37,8 @@ export class SpacesController { * Elimina una reserva por ID */ @Delete(':id') - delete(@Param('id') id: string) { - this.spacesService.delete(id); + async delete(@Param('id') id: string) { + await this.spacesService.delete(id); return { message: 'Reserva eliminada correctamente.' }; } } diff --git a/backend/src/spaces/spaces.module.ts b/backend/src/spaces/spaces.module.ts index a652a8a..39c14a3 100644 --- a/backend/src/spaces/spaces.module.ts +++ b/backend/src/spaces/spaces.module.ts @@ -1,4 +1,9 @@ import { Module } from '@nestjs/common'; +import { SpacesController } from './spaces.controller'; +import { SpacesService } from './spaces.service'; -@Module({}) +@Module({ + controllers: [SpacesController], + providers: [SpacesService], +}) export class SpacesModule {} diff --git a/backend/src/spaces/spaces.service.ts b/backend/src/spaces/spaces.service.ts index 3ca79bc..e0ff639 100644 --- a/backend/src/spaces/spaces.service.ts +++ b/backend/src/spaces/spaces.service.ts @@ -1,26 +1,28 @@ -import { Injectable, ConflictException } from '@nestjs/common'; -import { CreateReservaDto, Reserva } from './reserva.dto'; +import { Injectable, ConflictException, NotFoundException } from '@nestjs/common'; +import { PrismaService } from '../prisma/prisma.service'; +import { CreateReservaDto } from './reserva.dto'; @Injectable() export class SpacesService { - private reservas: Reserva[] = []; - private idCounter = 1; + constructor(private readonly prisma: PrismaService) {} - findAll(date?: string): Reserva[] { - if (date) { - return this.reservas.filter((r) => r.date === date); - } - return this.reservas; + async findAll(date?: string) { + return this.prisma.reserva.findMany({ + where: date ? { date } : undefined, + orderBy: { createdAt: 'desc' }, + }); } - create(dto: CreateReservaDto): Reserva { - // Verificar conflicto: mismo recurso, mismo día, mismo horario de inicio - const conflict = this.reservas.find( - (r) => - r.resourceName === dto.resourceName && - r.date === dto.date && - r.startTime === dto.startTime, - ); + async create(dto: CreateReservaDto) { + const conflict = await this.prisma.reserva.findUnique({ + where: { + resourceName_date_startTime: { + resourceName: dto.resourceName, + date: dto.date, + startTime: dto.startTime, + }, + }, + }); if (conflict) { throw new ConflictException( @@ -28,17 +30,16 @@ export class SpacesService { ); } - const newReserva: Reserva = { - ...dto, - id: String(this.idCounter++), - createdAt: new Date().toISOString(), - }; - - this.reservas.unshift(newReserva); - return newReserva; + return this.prisma.reserva.create({ data: dto }); } - delete(id: string): void { - this.reservas = this.reservas.filter((r) => r.id !== id); + async delete(id: string): Promise { + const reserva = await this.prisma.reserva.findUnique({ where: { id } }); + + if (!reserva) { + throw new NotFoundException(`Reserva con id "${id}" no encontrada.`); + } + + await this.prisma.reserva.delete({ where: { id } }); } }