diff --git a/apps/backend/src/app.module.ts b/apps/backend/src/app.module.ts index 86e5f33..871587d 100644 --- a/apps/backend/src/app.module.ts +++ b/apps/backend/src/app.module.ts @@ -3,11 +3,12 @@ import { PrismaModule } from 'nestjs-prisma'; import { AppController } from './app.controller'; import { AppService } from './app.service'; +import { DrinksModule } from './drinks/drinks.module'; import { EventsModule } from './events/events.module'; import { UsersModule } from './users/users.module'; @Module({ - imports: [PrismaModule.forRoot({ isGlobal: true }), UsersModule, EventsModule], + imports: [PrismaModule.forRoot({ isGlobal: true }), UsersModule, EventsModule, DrinksModule], controllers: [AppController], providers: [AppService], }) diff --git a/apps/backend/src/drinks/drinks.controller.ts b/apps/backend/src/drinks/drinks.controller.ts new file mode 100644 index 0000000..16c7b8b --- /dev/null +++ b/apps/backend/src/drinks/drinks.controller.ts @@ -0,0 +1,38 @@ +import { Body, Controller, Delete, Get, Param, ParseUUIDPipe, Patch, Post } from '@nestjs/common'; +import { ApiTags } from '@nestjs/swagger'; + +import { DrinksService } from './drinks.service'; +import { CreateDrinkDto } from './dto/create-drink.dto'; +import { UpdateDrinkDto } from './dto/update-drink.dto'; +import { Drink } from './entities/drink.entity'; + +@ApiTags('Drinks') +@Controller('drinks') +export class DrinksController { + constructor(private readonly drinksService: DrinksService) {} + + @Post() + create(@Body() createDrinkDto: CreateDrinkDto): Promise { + return this.drinksService.create(createDrinkDto); + } + + @Get() + findAll(): Promise { + return this.drinksService.findAll(); + } + + @Get(':id') + findOne(@Param('id', ParseUUIDPipe) id: string): Promise { + return this.drinksService.findOne(id); + } + + @Patch(':id') + update(@Param('id', ParseUUIDPipe) id: string, @Body() updateDrinkDto: UpdateDrinkDto): Promise { + return this.drinksService.update(id, updateDrinkDto); + } + + @Delete(':id') + remove(@Param('id', ParseUUIDPipe) id: string): Promise { + return this.drinksService.remove(id); + } +} diff --git a/apps/backend/src/drinks/drinks.module.ts b/apps/backend/src/drinks/drinks.module.ts new file mode 100644 index 0000000..d4b1b7b --- /dev/null +++ b/apps/backend/src/drinks/drinks.module.ts @@ -0,0 +1,10 @@ +import { Module } from '@nestjs/common'; + +import { DrinksController } from './drinks.controller'; +import { DrinksService } from './drinks.service'; + +@Module({ + controllers: [DrinksController], + providers: [DrinksService], +}) +export class DrinksModule {} diff --git a/apps/backend/src/drinks/drinks.service.ts b/apps/backend/src/drinks/drinks.service.ts new file mode 100644 index 0000000..3b71a96 --- /dev/null +++ b/apps/backend/src/drinks/drinks.service.ts @@ -0,0 +1,43 @@ +import { Injectable, NotFoundException } from '@nestjs/common'; +import { PrismaService } from 'nestjs-prisma'; + +import { CreateDrinkDto } from './dto/create-drink.dto'; +import { UpdateDrinkDto } from './dto/update-drink.dto'; +import { Drink } from './entities/drink.entity'; + +@Injectable() +export class DrinksService { + constructor(private prisma: PrismaService) {} + + create(createDrinkDto: CreateDrinkDto): Promise { + return this.prisma.drink.create({ data: createDrinkDto }); + } + + findAll(): Promise { + return this.prisma.drink.findMany(); + } + + async findOne(id: string): Promise { + const drink = await this.prisma.drink.findUnique({ where: { id } }); + if (!drink) { + throw new NotFoundException('Drink not found'); + } + return drink; + } + + async update(id: string, updateDrinkDto: UpdateDrinkDto): Promise { + try { + return this.prisma.drink.update({ where: { id }, data: updateDrinkDto }); + } catch { + throw new NotFoundException('Drink not found'); + } + } + + async remove(id: string): Promise { + try { + return this.prisma.drink.delete({ where: { id } }); + } catch { + throw new NotFoundException('Drink not found'); + } + } +} diff --git a/apps/backend/src/drinks/dto/create-drink.dto.ts b/apps/backend/src/drinks/dto/create-drink.dto.ts new file mode 100644 index 0000000..ef9a6c7 --- /dev/null +++ b/apps/backend/src/drinks/dto/create-drink.dto.ts @@ -0,0 +1,5 @@ +import { OmitType } from '@nestjs/swagger'; + +import { Drink } from '../entities/drink.entity'; + +export class CreateDrinkDto extends OmitType(Drink, ['id', 'createdAt']) {} diff --git a/apps/backend/src/drinks/dto/update-drink.dto.ts b/apps/backend/src/drinks/dto/update-drink.dto.ts new file mode 100644 index 0000000..0c6d948 --- /dev/null +++ b/apps/backend/src/drinks/dto/update-drink.dto.ts @@ -0,0 +1,5 @@ +import { PartialType } from '@nestjs/swagger'; + +import { CreateDrinkDto } from './create-drink.dto'; + +export class UpdateDrinkDto extends PartialType(CreateDrinkDto) {} diff --git a/apps/backend/src/drinks/entities/drink.entity.ts b/apps/backend/src/drinks/entities/drink.entity.ts new file mode 100644 index 0000000..3599dc3 --- /dev/null +++ b/apps/backend/src/drinks/entities/drink.entity.ts @@ -0,0 +1,50 @@ +import { DrinkType } from '@prisma/client'; +import { IsBoolean, IsDateString, IsEnum, IsNotEmpty, IsOptional, IsString, IsUUID, Min } from 'class-validator'; +import { IsFloat } from 'src/util/is-float.validator'; + +export class Drink { + /** + * @example aaaaaaaa-bbbb-cccc-dddd-eeee-ff0123456789 + */ + @IsUUID() + id: string; + + /** + * Name of the drink + * @example 'Soproni meggy' + */ + @IsString() + @IsNotEmpty() + name: string; + + /** + * Type of the drink + * @example BEER + */ + @IsEnum(DrinkType) + type: DrinkType; + + /** + * Alcohol content of the drink + * @example 4.5 + */ + @IsFloat() + @Min(0) + alcoholContent: number; + + /** + * Whether it was created by a user + */ + @IsBoolean() + custom: boolean; + + /** + * @example "A beer that doesn't really taste like beer." + */ + @IsString() + @IsOptional() + description?: string; + + @IsDateString() + createdAt: Date; +} diff --git a/apps/backend/src/events/entities/event.entity.ts b/apps/backend/src/events/entities/event.entity.ts index 4309e9b..84cdf9e 100644 --- a/apps/backend/src/events/entities/event.entity.ts +++ b/apps/backend/src/events/entities/event.entity.ts @@ -1,6 +1,6 @@ import { IsDate, IsDateString, IsNotEmpty, IsOptional, IsString, IsUUID } from 'class-validator'; -import { IsAfterDate } from '../validators/is-after-date.validator'; +import { IsAfterDate } from '../../util/is-after-date.validator'; export class Event { @IsUUID() diff --git a/apps/backend/src/events/validators/is-after-date.validator.ts b/apps/backend/src/util/is-after-date.validator.ts similarity index 100% rename from apps/backend/src/events/validators/is-after-date.validator.ts rename to apps/backend/src/util/is-after-date.validator.ts diff --git a/apps/backend/src/util/is-float.validator.ts b/apps/backend/src/util/is-float.validator.ts new file mode 100644 index 0000000..bae3a65 --- /dev/null +++ b/apps/backend/src/util/is-float.validator.ts @@ -0,0 +1,20 @@ +import { registerDecorator, ValidationArguments, ValidationOptions } from 'class-validator'; + +export function IsFloat(validationOptions?: ValidationOptions) { + return function (object: object, propertyName: string) { + registerDecorator({ + name: 'isFloat', + target: object.constructor, + propertyName: propertyName, + options: validationOptions, + validator: { + validate(value: any) { + return typeof value === 'number' && !isNaN(value); + }, + defaultMessage(args: ValidationArguments) { + return `${args.property} must be a float number`; + }, + }, + }); + }; +}