From 5ecb21e9e7a47a5e04a01a24f3607186450bf114 Mon Sep 17 00:00:00 2001 From: Reinout <94532201+ReinoutvdG@users.noreply.github.com> Date: Wed, 17 Dec 2025 12:58:22 +0100 Subject: [PATCH 1/3] userfavorites versie 1 moet nog getest worden maar is compleet en start op --- src/app.module.ts | 12 ++++ src/application/ports/userfavorites.port.ts | 8 +++ .../services/userfavorites.service.ts | 41 ++++++++++++ .../userfavorites-repository.interface.ts | 8 +++ .../userfavorites/userfavorites.model.ts | 6 ++ .../database/schema/schema.prisma | 51 ++++++++------- .../repositories/userfavorites.repository.ts | 52 +++++++++++++++ .../controllers/userfavorites.controller.ts | 63 +++++++++++++++++++ 8 files changed, 217 insertions(+), 24 deletions(-) create mode 100644 src/application/ports/userfavorites.port.ts create mode 100644 src/application/services/userfavorites.service.ts create mode 100644 src/domain/userfavorites/userfavorites-repository.interface.ts create mode 100644 src/domain/userfavorites/userfavorites.model.ts create mode 100644 src/infrastructure/repositories/userfavorites.repository.ts create mode 100644 src/presentation/controllers/userfavorites.controller.ts diff --git a/src/app.module.ts b/src/app.module.ts index 247f455..006ba07 100644 --- a/src/app.module.ts +++ b/src/app.module.ts @@ -8,6 +8,9 @@ import { PrismaService } from './infrastructure/database/prisma'; import { SessionActivityMiddleware } from './infrastructure/middleware/session-activity.middleware'; import { AppController } from './presentation/controllers/app.controller'; import { ChoiceModulesRepository } from './infrastructure/repositories/choicemodules.repository'; +import { UserFavoritesService } from './application/services/userfavorites.service'; +import { UserFavoritesRepository } from './infrastructure/repositories/userfavorites.repository'; +import { UserFavoritesController } from './presentation/controllers/userfavorites.controller'; import { ModuleService } from './application/services/module.service'; import { ModulesController } from './presentation/controllers/modules.controller'; @Module({ @@ -34,12 +37,21 @@ import { ModulesController } from './presentation/controllers/modules.controller provide: 'SERVICE.MODULE', useClass: ModuleService, }, + { + provide: 'SERVICE.USER_FAVORITES', + useClass: UserFavoritesService, + }, + { + provide: 'REPO.USER_FAVORITES', + useClass: UserFavoritesRepository, + }, ], controllers: [ AuthController, UserController, ModulesController, AppController, + UserFavoritesController, ], }) export class AppModule implements NestModule { diff --git a/src/application/ports/userfavorites.port.ts b/src/application/ports/userfavorites.port.ts new file mode 100644 index 0000000..c9fbad0 --- /dev/null +++ b/src/application/ports/userfavorites.port.ts @@ -0,0 +1,8 @@ +import { UserFavorite } from '@/domain/userfavorites/userfavorites.model'; + +export interface IUserFavoritesService { + getFavoritesForUser(userId: string): Promise; + addFavorite(userId: string, moduleId: string): Promise; + removeFavorite(userId: string, moduleId: string): Promise; + toggleFavorite(userId: string, moduleId: string): Promise; +} diff --git a/src/application/services/userfavorites.service.ts b/src/application/services/userfavorites.service.ts new file mode 100644 index 0000000..0e23db5 --- /dev/null +++ b/src/application/services/userfavorites.service.ts @@ -0,0 +1,41 @@ +import { Injectable, Inject } from '@nestjs/common'; +import { IUserFavoritesService } from '@/application/ports/userfavorites.port'; +import { IUserFavoritesRepository } from '@/domain/userfavorites/userfavorites-repository.interface'; +import { UserFavorite } from '@/domain/userfavorites/userfavorites.model'; + +@Injectable() +export class UserFavoritesService implements IUserFavoritesService { + private readonly favoritesRepository: IUserFavoritesRepository; + + constructor( + @Inject('REPO.USER_FAVORITES') + _favoritesRepository: IUserFavoritesRepository, + ) { + this.favoritesRepository = _favoritesRepository; + } + + getFavoritesForUser(userId: string): Promise { + return this.favoritesRepository.findByUserId(userId); + } + + async addFavorite(userId: string, moduleId: string): Promise { + const exists = await this.favoritesRepository.exists(userId, moduleId); + if (!exists) { + await this.favoritesRepository.add(userId, moduleId); + } + } + + async removeFavorite(userId: string, moduleId: string): Promise { + await this.favoritesRepository.remove(userId, moduleId); + } + + async toggleFavorite(userId: string, moduleId: string): Promise { + const exists = await this.favoritesRepository.exists(userId, moduleId); + + if (exists) { + await this.favoritesRepository.remove(userId, moduleId); + } else { + await this.favoritesRepository.add(userId, moduleId); + } + } +} diff --git a/src/domain/userfavorites/userfavorites-repository.interface.ts b/src/domain/userfavorites/userfavorites-repository.interface.ts new file mode 100644 index 0000000..ada4acb --- /dev/null +++ b/src/domain/userfavorites/userfavorites-repository.interface.ts @@ -0,0 +1,8 @@ +import { UserFavorite } from './userfavorites.model'; + +export interface IUserFavoritesRepository { + findByUserId(userId: string): Promise; + add(userId: string, itemId: string): Promise; + remove(userId: string, itemId: string): Promise; + exists(userId: string, itemId: string): Promise; +} diff --git a/src/domain/userfavorites/userfavorites.model.ts b/src/domain/userfavorites/userfavorites.model.ts new file mode 100644 index 0000000..a573fba --- /dev/null +++ b/src/domain/userfavorites/userfavorites.model.ts @@ -0,0 +1,6 @@ +export interface UserFavorite { + id: string; + userId: string; + choiceModuleId: string; + createdAt: Date; +} \ No newline at end of file diff --git a/src/infrastructure/database/schema/schema.prisma b/src/infrastructure/database/schema/schema.prisma index ae73feb..9baf4e1 100644 --- a/src/infrastructure/database/schema/schema.prisma +++ b/src/infrastructure/database/schema/schema.prisma @@ -16,17 +16,17 @@ enum Role { // Hier kan je nieuwe databaseschema's toevoegen. run "npx prisma generate" om de changes door te voeren. model User { - id String @id @default(uuid()) - name String - email String @unique + id String @id @default(uuid()) + name String + email String @unique hashedPassword String - role Role @default(STUDENT) + role Role @default(STUDENT) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt } -model UserFavourites { +model UserFavorites { id String @id @default(uuid()) userId String choiceModuleId String @@ -38,7 +38,7 @@ model UserFavourites { } model ChoiceModule { - id String @id @default(uuid()) + id String @id @default(uuid()) name String shortDescription String? description String? @@ -53,35 +53,38 @@ model ChoiceModule { availableSpots Int startDate String location ChoiceModuleLocation[] - moduleTags ChoiceModuleTags[] + moduleTags ChoiceModuleTags[] createdAt DateTime @default(now()) updatedAt DateTime @updatedAt } -model Location{ - id String @id @default(uuid()) - name String @unique - choiceModules ChoiceModuleLocation[] + +model Location { + id String @id @default(uuid()) + name String @unique + choiceModules ChoiceModuleLocation[] } -model ChoiceModuleLocation{ - ChoiceModule ChoiceModule @relation(fields: [choiceModuleId], references: [id]) - choiceModuleId String - Location Location @relation(fields: [locationId], references: [id]) - locationId String +model ChoiceModuleLocation { + ChoiceModule ChoiceModule @relation(fields: [choiceModuleId], references: [id]) + choiceModuleId String + Location Location @relation(fields: [locationId], references: [id]) + locationId String @@id([choiceModuleId, locationId]) } -model ModuleTags{ - id String @id @default(uuid()) - name String @unique - choiceModules ChoiceModuleTags[] + +model ModuleTags { + id String @id @default(uuid()) + name String @unique + choiceModules ChoiceModuleTags[] } + model ChoiceModuleTags { - ChoiceModule ChoiceModule @relation(fields: [choiceModuleId], references: [id]) - choiceModuleId String - ModuleTags ModuleTags @relation(fields: [moduleTagsId], references: [id]) - moduleTagsId String + ChoiceModule ChoiceModule @relation(fields: [choiceModuleId], references: [id]) + choiceModuleId String + ModuleTags ModuleTags @relation(fields: [moduleTagsId], references: [id]) + moduleTagsId String @@id([choiceModuleId, moduleTagsId]) } diff --git a/src/infrastructure/repositories/userfavorites.repository.ts b/src/infrastructure/repositories/userfavorites.repository.ts new file mode 100644 index 0000000..f9bce74 --- /dev/null +++ b/src/infrastructure/repositories/userfavorites.repository.ts @@ -0,0 +1,52 @@ +import { IUserFavoritesRepository } from '@/domain/userfavorites/userfavorites-repository.interface'; +import { UserFavorite } from '@/domain/userfavorites/userfavorites.model'; +import { Injectable } from '@nestjs/common'; +import { PrismaService } from '../database/prisma'; + +@Injectable() +export class UserFavoritesRepository implements IUserFavoritesRepository { + constructor(private readonly prisma: PrismaService) {} + + async findByUserId(userId: string): Promise { + const favorites = await this.prisma.userFavorites.findMany({ + where: { userId }, + orderBy: { createdAt: 'desc' }, + }); + + return favorites.map((fav) => ({ + id: fav.id, + userId: fav.userId, + choiceModuleId: fav.choiceModuleId, + createdAt: fav.createdAt, + })); + } + + async add(userId: string, itemId: string): Promise { + await this.prisma.userFavorites.create({ + data: { + userId, + choiceModuleId: itemId, + }, + }); + } + + async remove(userId: string, itemId: string): Promise { + await this.prisma.userFavorites.deleteMany({ + where: { + userId, + choiceModuleId: itemId, + }, + }); + } + + async exists(userId: string, itemId: string): Promise { + const favorite = await this.prisma.userFavorites.findFirst({ + where: { + userId, + choiceModuleId: itemId, + }, + }); + + return favorite !== null; + } +} diff --git a/src/presentation/controllers/userfavorites.controller.ts b/src/presentation/controllers/userfavorites.controller.ts new file mode 100644 index 0000000..79ca0f3 --- /dev/null +++ b/src/presentation/controllers/userfavorites.controller.ts @@ -0,0 +1,63 @@ +import { + Controller, + Get, + Post, + Delete, + Param, + Session, + Inject, +} from '@nestjs/common'; +import { SessionData } from '@/types/session.types'; +import { UserFavoritesService } from '@/application/services/userfavorites.service'; + +@Controller('favorites') +export class UserFavoritesController { + private readonly favoritesService: UserFavoritesService; + + constructor( + @Inject('SERVICE.USER_FAVORITES') + favoritesService: UserFavoritesService, + ) { + this.favoritesService = favoritesService; + } + + @Get() + async getMyFavorites(@Session() session: SessionData) { + const userId = session.user?.id; + if (!userId) { + throw new Error('No active session'); + } + + return { + favorites: await this.favoritesService.getFavoritesForUser(userId), + }; + } + + @Post(':moduleId') + async addFavorite( + @Param('moduleId') moduleId: string, + @Session() session: SessionData, + ) { + const userId = session.user?.id; + if (!userId) { + throw new Error('No active session'); + } + + await this.favoritesService.addFavorite(userId, moduleId); + return { success: true }; + } + + @Delete(':moduleId') + async removeFavorite( + @Param('moduleId') moduleId: string, + @Session() session: SessionData, + ) { + const userId = session.user?.id; + if (!userId) { + throw new Error('No active session'); + } + + await this.favoritesService.removeFavorite(userId, moduleId); + return { success: true }; + } +} From 826f9c233e054ab7b140bbedf1a274040fdd5495 Mon Sep 17 00:00:00 2001 From: Reinout <94532201+ReinoutvdG@users.noreply.github.com> Date: Wed, 17 Dec 2025 15:05:48 +0100 Subject: [PATCH 2/3] controller functies aangepast naar benodigde --- src/application/ports/userfavorites.port.ts | 8 +- .../services/userfavorites.service.ts | 20 ++--- .../controllers/userfavorites.controller.ts | 76 +++++++++++-------- 3 files changed, 56 insertions(+), 48 deletions(-) diff --git a/src/application/ports/userfavorites.port.ts b/src/application/ports/userfavorites.port.ts index c9fbad0..fbea03c 100644 --- a/src/application/ports/userfavorites.port.ts +++ b/src/application/ports/userfavorites.port.ts @@ -1,8 +1,8 @@ import { UserFavorite } from '@/domain/userfavorites/userfavorites.model'; export interface IUserFavoritesService { - getFavoritesForUser(userId: string): Promise; - addFavorite(userId: string, moduleId: string): Promise; - removeFavorite(userId: string, moduleId: string): Promise; - toggleFavorite(userId: string, moduleId: string): Promise; + findFavorites(userId: string): Promise; + isModuleFavorited(userId: string, moduleId: string): Promise; + favoriteModule(userId: string, moduleId: string): Promise; + unfavoriteModule(userId: string, moduleId: string): Promise; } diff --git a/src/application/services/userfavorites.service.ts b/src/application/services/userfavorites.service.ts index 0e23db5..1e1fff7 100644 --- a/src/application/services/userfavorites.service.ts +++ b/src/application/services/userfavorites.service.ts @@ -14,28 +14,22 @@ export class UserFavoritesService implements IUserFavoritesService { this.favoritesRepository = _favoritesRepository; } - getFavoritesForUser(userId: string): Promise { + findFavorites(userId: string): Promise { return this.favoritesRepository.findByUserId(userId); } - async addFavorite(userId: string, moduleId: string): Promise { + isModuleFavorited(userId: string, moduleId: string): Promise { + return this.favoritesRepository.exists(userId, moduleId); + } + + async favoriteModule(userId: string, moduleId: string): Promise { const exists = await this.favoritesRepository.exists(userId, moduleId); if (!exists) { await this.favoritesRepository.add(userId, moduleId); } } - async removeFavorite(userId: string, moduleId: string): Promise { + async unfavoriteModule(userId: string, moduleId: string): Promise { await this.favoritesRepository.remove(userId, moduleId); } - - async toggleFavorite(userId: string, moduleId: string): Promise { - const exists = await this.favoritesRepository.exists(userId, moduleId); - - if (exists) { - await this.favoritesRepository.remove(userId, moduleId); - } else { - await this.favoritesRepository.add(userId, moduleId); - } - } } diff --git a/src/presentation/controllers/userfavorites.controller.ts b/src/presentation/controllers/userfavorites.controller.ts index 79ca0f3..907d1d6 100644 --- a/src/presentation/controllers/userfavorites.controller.ts +++ b/src/presentation/controllers/userfavorites.controller.ts @@ -10,54 +10,68 @@ import { import { SessionData } from '@/types/session.types'; import { UserFavoritesService } from '@/application/services/userfavorites.service'; -@Controller('favorites') +@Controller('api/user/favorites') export class UserFavoritesController { - private readonly favoritesService: UserFavoritesService; - constructor( @Inject('SERVICE.USER_FAVORITES') - favoritesService: UserFavoritesService, - ) { - this.favoritesService = favoritesService; - } + private readonly favoritesService: UserFavoritesService, + ) {} + + // GET /api/user/favorites + @Get() + async findFavorites(@Session() session: SessionData) { + if (!session?.user) { + throw new Error('No active session'); + } + + return { + favorites: await this.favoritesService.findFavorites(session.user.id), + }; + } - @Get() - async getMyFavorites(@Session() session: SessionData) { - const userId = session.user?.id; - if (!userId) { - throw new Error('No active session'); + // GET /api/user/favorites/:moduleId + @Get(':moduleId') + async isModuleFavorited( + @Param('moduleId') moduleId: string, + @Session() session: SessionData, + ) { + if (!session?.user) { + throw new Error('No active session'); } return { - favorites: await this.favoritesService.getFavoritesForUser(userId), + isFavorited: await this.favoritesService.isModuleFavorited( + session.user.id, + moduleId, + ), }; - } + } - @Post(':moduleId') - async addFavorite( + // POST /api/user/favorites/:moduleId + @Post(':moduleId') + async favoriteModule( @Param('moduleId') moduleId: string, @Session() session: SessionData, - ) { - const userId = session.user?.id; - if (!userId) { - throw new Error('No active session'); + ) { + if (!session?.user) { + throw new Error('No active session'); } - await this.favoritesService.addFavorite(userId, moduleId); + await this.favoritesService.favoriteModule(session.user.id, moduleId); return { success: true }; - } + } - @Delete(':moduleId') - async removeFavorite( + // DELETE /api/user/favorites/:moduleId + @Delete(':moduleId') + async unfavoriteModule( @Param('moduleId') moduleId: string, @Session() session: SessionData, - ) { - const userId = session.user?.id; - if (!userId) { - throw new Error('No active session'); + ) { + if (!session?.user) { + throw new Error('No active session'); } - await this.favoritesService.removeFavorite(userId, moduleId); + await this.favoritesService.unfavoriteModule(session.user.id, moduleId); return { success: true }; - } -} + } +} \ No newline at end of file From 5c6db81ef7852657bb0867ad04ae60cfbddf79e8 Mon Sep 17 00:00:00 2001 From: Reinout <94532201+ReinoutvdG@users.noreply.github.com> Date: Thu, 18 Dec 2025 20:41:36 +0100 Subject: [PATCH 3/3] api endpoint fix (was api/api) --- .../controllers/userfavorites.controller.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/presentation/controllers/userfavorites.controller.ts b/src/presentation/controllers/userfavorites.controller.ts index 907d1d6..c913100 100644 --- a/src/presentation/controllers/userfavorites.controller.ts +++ b/src/presentation/controllers/userfavorites.controller.ts @@ -10,14 +10,14 @@ import { import { SessionData } from '@/types/session.types'; import { UserFavoritesService } from '@/application/services/userfavorites.service'; -@Controller('api/user/favorites') +@Controller('user/favorites') export class UserFavoritesController { constructor( @Inject('SERVICE.USER_FAVORITES') private readonly favoritesService: UserFavoritesService, ) {} - // GET /api/user/favorites + // GET /user/favorites @Get() async findFavorites(@Session() session: SessionData) { if (!session?.user) { @@ -29,7 +29,7 @@ export class UserFavoritesController { }; } - // GET /api/user/favorites/:moduleId + // GET /user/favorites/:moduleId @Get(':moduleId') async isModuleFavorited( @Param('moduleId') moduleId: string, @@ -47,7 +47,7 @@ export class UserFavoritesController { }; } - // POST /api/user/favorites/:moduleId + // POST /user/favorites/:moduleId @Post(':moduleId') async favoriteModule( @Param('moduleId') moduleId: string, @@ -61,7 +61,7 @@ export class UserFavoritesController { return { success: true }; } - // DELETE /api/user/favorites/:moduleId + // DELETE /user/favorites/:moduleId @Delete(':moduleId') async unfavoriteModule( @Param('moduleId') moduleId: string,