Skip to content
This repository was archived by the owner on Feb 11, 2026. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 22 additions & 6 deletions src/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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: [],
Expand All @@ -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,
Expand All @@ -71,6 +86,7 @@ import { ModuleTagController } from './presentation/controllers/moduletag.contro
UserController,
ModulesController,
AppController,
UserFavoritesController,
LocationController,
ModuleTagController,
],
Expand Down
8 changes: 8 additions & 0 deletions src/application/ports/userfavorites.port.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { UserFavorite } from '@/domain/userfavorites/userfavorites.model';

export interface IUserFavoritesService {
findFavorites(userId: string): Promise<UserFavorite[]>;
isModuleFavorited(userId: string, moduleId: string): Promise<boolean>;
favoriteModule(userId: string, moduleId: string): Promise<void>;
unfavoriteModule(userId: string, moduleId: string): Promise<void>;
}
35 changes: 35 additions & 0 deletions src/application/services/userfavorites.service.ts
Original file line number Diff line number Diff line change
@@ -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<UserFavorite[]> {
return this.favoritesRepository.findByUserId(userId);
}

isModuleFavorited(userId: string, moduleId: string): Promise<boolean> {
return this.favoritesRepository.exists(userId, moduleId);
}

async favoriteModule(userId: string, moduleId: string): Promise<void> {
const exists = await this.favoritesRepository.exists(userId, moduleId);
if (!exists) {
await this.favoritesRepository.add(userId, moduleId);
}
}

async unfavoriteModule(userId: string, moduleId: string): Promise<void> {
await this.favoritesRepository.remove(userId, moduleId);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { UserFavorite } from './userfavorites.model';

export interface IUserFavoritesRepository {
findByUserId(userId: string): Promise<UserFavorite[]>;
add(userId: string, itemId: string): Promise<void>;
remove(userId: string, itemId: string): Promise<void>;
exists(userId: string, itemId: string): Promise<boolean>;
}
6 changes: 6 additions & 0 deletions src/domain/userfavorites/userfavorites.model.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
export interface UserFavorite {
id: string;
userId: string;
choiceModuleId: string;
createdAt: Date;
}
Original file line number Diff line number Diff line change
@@ -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<UserFavorite[]> {
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<void> {
await this.prisma.userFavorites.create({
data: {
userId,
choiceModuleId: itemId,
},
});
}

async remove(userId: string, itemId: string): Promise<void> {
await this.prisma.userFavorites.deleteMany({
where: {
userId,
choiceModuleId: itemId,
},
});
}

async exists(userId: string, itemId: string): Promise<boolean> {
const favorite = await this.prisma.userFavorites.findFirst({
where: {
userId,
choiceModuleId: itemId,
},
});

return favorite !== null;
}
}
51 changes: 27 additions & 24 deletions src/infrastructure/database/schema/schema.prisma
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -38,7 +38,7 @@ model UserFavourites {
}

model ChoiceModule {
id String @id @default(uuid())
id String @id @default(uuid())
name String
shortDescription String?
description String?
Expand All @@ -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])
}
77 changes: 77 additions & 0 deletions src/presentation/controllers/userfavorites.controller.ts
Original file line number Diff line number Diff line change
@@ -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 };
}
}