diff --git a/src/app.module.ts b/src/app.module.ts index 090a7d9..4599c24 100644 --- a/src/app.module.ts +++ b/src/app.module.ts @@ -7,22 +7,29 @@ import { LoggerService } from './common/logger.service'; import { RequestLoggingMiddleware } from './infrastructure/middleware/request-logging.middleware'; import { PrismaService } from './infrastructure/database/prisma'; import { AppController } from './presentation/controllers/app.controller'; + +import { ChoiceModulesRepository } from './infrastructure/database/repositories/choicemodules.repository'; +import { UserFavoritesService } from './application/services/userfavorites.service'; +import { UserFavoritesRepository } from './infrastructure/database/repositories/userfavorites.repository'; +import { UserFavoritesController } from './presentation/controllers/userfavorites.controller'; + import { LocationRepository } from './infrastructure/database/repositories/location.repository'; import { LocationService } from './application/services/location.service'; import { LocationController } from './presentation/controllers/location.controller'; import { ModuleTagRepository } from './infrastructure/database/repositories/moduletag.repository'; +import { ModuleTagService } from './application/services/moduletag.service'; +import { ModuleTagController } from './presentation/controllers/moduletag.controller'; + +import { UserRepository } from './infrastructure/database/repositories/user.repository'; +import { ModuleService } from './application/services/module.service'; +import { ModulesController } from './presentation/controllers/modules.controller'; + class SessionActivityMiddleware { use(req: any, res: any, next: () => void) { // no-op middleware to track session activity (placeholder implementation) next(); } } -import { ChoiceModulesRepository } from './infrastructure/database/repositories/choicemodules.repository'; -import { UserRepository } from './infrastructure/database/repositories/user.repository'; -import { ModuleService } from './application/services/module.service'; -import { ModulesController } from './presentation/controllers/modules.controller'; -import { ModuleTagService } from './application/services/moduletag.service'; -import { ModuleTagController } from './presentation/controllers/moduletag.controller'; @Module({ imports: [], @@ -49,6 +56,14 @@ import { ModuleTagController } from './presentation/controllers/moduletag.contro provide: 'SERVICE.MODULE', useClass: ModuleService, }, + { + provide: 'SERVICE.USER_FAVORITES', + useClass: UserFavoritesService, + }, + { + provide: 'REPO.USER_FAVORITES', + useClass: UserFavoritesRepository, + }, { provide: 'REPO.LOCATION', useClass: LocationRepository, @@ -71,6 +86,7 @@ import { ModuleTagController } from './presentation/controllers/moduletag.contro UserController, ModulesController, AppController, + UserFavoritesController, LocationController, ModuleTagController, ], diff --git a/src/application/ports/userfavorites.port.ts b/src/application/ports/userfavorites.port.ts new file mode 100644 index 0000000..fbea03c --- /dev/null +++ b/src/application/ports/userfavorites.port.ts @@ -0,0 +1,8 @@ +import { UserFavorite } from '@/domain/userfavorites/userfavorites.model'; + +export interface IUserFavoritesService { + 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 new file mode 100644 index 0000000..1e1fff7 --- /dev/null +++ b/src/application/services/userfavorites.service.ts @@ -0,0 +1,35 @@ +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; + } + + findFavorites(userId: string): Promise { + return this.favoritesRepository.findByUserId(userId); + } + + 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 unfavoriteModule(userId: string, moduleId: string): Promise { + await this.favoritesRepository.remove(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/repositories/userfavorites.repository.ts b/src/infrastructure/database/repositories/userfavorites.repository.ts new file mode 100644 index 0000000..e45b0f7 --- /dev/null +++ b/src/infrastructure/database/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 '../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/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/presentation/controllers/userfavorites.controller.ts b/src/presentation/controllers/userfavorites.controller.ts new file mode 100644 index 0000000..c913100 --- /dev/null +++ b/src/presentation/controllers/userfavorites.controller.ts @@ -0,0 +1,77 @@ +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('user/favorites') +export class UserFavoritesController { + constructor( + @Inject('SERVICE.USER_FAVORITES') + private readonly favoritesService: UserFavoritesService, + ) {} + + // GET /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 /user/favorites/:moduleId + @Get(':moduleId') + async isModuleFavorited( + @Param('moduleId') moduleId: string, + @Session() session: SessionData, + ) { + if (!session?.user) { + throw new Error('No active session'); + } + + return { + isFavorited: await this.favoritesService.isModuleFavorited( + session.user.id, + moduleId, + ), + }; + } + + // POST /user/favorites/:moduleId + @Post(':moduleId') + async favoriteModule( + @Param('moduleId') moduleId: string, + @Session() session: SessionData, + ) { + if (!session?.user) { + throw new Error('No active session'); + } + + await this.favoritesService.favoriteModule(session.user.id, moduleId); + return { success: true }; + } + + // DELETE /user/favorites/:moduleId + @Delete(':moduleId') + async unfavoriteModule( + @Param('moduleId') moduleId: string, + @Session() session: SessionData, + ) { + if (!session?.user) { + throw new Error('No active session'); + } + + await this.favoritesService.unfavoriteModule(session.user.id, moduleId); + return { success: true }; + } +} \ No newline at end of file