diff --git a/src/app.module.ts b/src/app.module.ts index bbedfc0..090a7d9 100644 --- a/src/app.module.ts +++ b/src/app.module.ts @@ -7,7 +7,10 @@ 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 { 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'; class SessionActivityMiddleware { use(req: any, res: any, next: () => void) { // no-op middleware to track session activity (placeholder implementation) @@ -18,6 +21,9 @@ import { ChoiceModulesRepository } from './infrastructure/database/repositories/ 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: [], providers: [ @@ -43,12 +49,30 @@ import { ModulesController } from './presentation/controllers/modules.controller provide: 'SERVICE.MODULE', useClass: ModuleService, }, + { + provide: 'REPO.LOCATION', + useClass: LocationRepository, + }, + { + provide: 'SERVICE.LOCATION', + useClass: LocationService, + }, + { + provide: 'REPO.MODULETAG', + useClass: ModuleTagRepository, + }, + { + provide: 'SERVICE.MODULETAG', + useClass: ModuleTagService, + }, ], controllers: [ AuthController, UserController, ModulesController, AppController, + LocationController, + ModuleTagController, ], }) export class AppModule implements NestModule { diff --git a/src/application/ports/location.port.ts b/src/application/ports/location.port.ts new file mode 100644 index 0000000..d7aa6aa --- /dev/null +++ b/src/application/ports/location.port.ts @@ -0,0 +1,4 @@ +import { Location } from '@/domain/locations/location.model'; +export interface ILocationService { + getAllLocation(): Promise; +} diff --git a/src/application/ports/module.port.ts b/src/application/ports/module.port.ts index a6cebaf..35ba9f4 100644 --- a/src/application/ports/module.port.ts +++ b/src/application/ports/module.port.ts @@ -1,5 +1,10 @@ -import { Module } from '@/domain/modules/module.model'; +import { + Module, + moduleDetail, + createModule, +} from '@/domain/modules/module.model'; export interface IModuleService { getAllModules(): Promise; - // findById(id: string): Promise; + findById(id: string): Promise; + createModule(module: createModule): Promise; } diff --git a/src/application/ports/moduletag.port.ts b/src/application/ports/moduletag.port.ts new file mode 100644 index 0000000..410daf4 --- /dev/null +++ b/src/application/ports/moduletag.port.ts @@ -0,0 +1,4 @@ +import { ModuleTag } from '@/domain/moduletags/moduletag.model'; +export interface IModuleTagService { + getAllModuleTags(): Promise; +} diff --git a/src/application/services/location.service.ts b/src/application/services/location.service.ts new file mode 100644 index 0000000..fd7d59c --- /dev/null +++ b/src/application/services/location.service.ts @@ -0,0 +1,16 @@ +import { Injectable, Inject } from '@nestjs/common'; +import type { Location } from '@/domain/locations/location.model'; +import { ILocationService } from '../ports/location.port'; +import { ILocationRepository } from '@/domain/locations/location-repository.interface'; +@Injectable() +export class LocationService implements ILocationService { + private readonly locationRepository: ILocationRepository; + constructor( + @Inject('REPO.LOCATION') locationRepository: ILocationRepository, + ) { + this.locationRepository = locationRepository; + } + async getAllLocation(): Promise { + return await this.locationRepository.getAllLocation(); + } +} diff --git a/src/application/services/module.service.ts b/src/application/services/module.service.ts index d7061c5..868cfb1 100644 --- a/src/application/services/module.service.ts +++ b/src/application/services/module.service.ts @@ -1,7 +1,11 @@ import { Injectable, Inject } from '@nestjs/common'; import { IModuleService } from '@/application/ports/module.port'; import { type IModuleRepository } from '@/domain/modules/module-repository.interface'; -import { Module, moduleDetail } from '@/domain/modules/module.model'; +import { + Module, + moduleDetail, + createModule, +} from '@/domain/modules/module.model'; @Injectable() export class ModuleService implements IModuleService { @@ -9,10 +13,14 @@ export class ModuleService implements IModuleService { constructor(@Inject('REPO.MODULE') _moduleRepository: IModuleRepository) { this.moduleRepository = _moduleRepository; } - getAllModules(): Promise { - return this.moduleRepository.getAllModules(); + async getAllModules(): Promise { + return await this.moduleRepository.getAllModules(); } - findById(id: string): Promise { - return this.moduleRepository.findById(id); + async findById(id: string): Promise { + return await this.moduleRepository.findById(id); + } + async createModule(module: createModule): Promise { + console.log(`Creating module: ${JSON.stringify(module)}`); + return await this.moduleRepository.createModule(module); } } diff --git a/src/application/services/moduletag.service.ts b/src/application/services/moduletag.service.ts new file mode 100644 index 0000000..133ca02 --- /dev/null +++ b/src/application/services/moduletag.service.ts @@ -0,0 +1,16 @@ +import { Inject, Injectable } from '@nestjs/common'; +import { IModuleTagService } from '../ports/moduletag.port'; +import { IModuleTagRepository } from '@/domain/moduletags/moduletag-repository'; +import { ModuleTag } from '@/domain/moduletags/moduletag.model'; + +@Injectable() +export class ModuleTagService implements IModuleTagService { + constructor( + @Inject('REPO.MODULETAG') + private readonly moduleTagRepository: IModuleTagRepository, + ) {} + + async getAllModuleTags(): Promise { + return await this.moduleTagRepository.getAllModuleTags(); + } +} diff --git a/src/domain/locations/location-repository.interface.ts b/src/domain/locations/location-repository.interface.ts new file mode 100644 index 0000000..5ea1ad1 --- /dev/null +++ b/src/domain/locations/location-repository.interface.ts @@ -0,0 +1,4 @@ +import { Location } from './location.model'; +export interface ILocationRepository { + getAllLocation(): Promise; +} diff --git a/src/domain/locations/location.model.ts b/src/domain/locations/location.model.ts new file mode 100644 index 0000000..2cd5b4e --- /dev/null +++ b/src/domain/locations/location.model.ts @@ -0,0 +1,4 @@ +export interface Location { + id: string; + name: string; +} diff --git a/src/domain/modules/module-repository.interface.ts b/src/domain/modules/module-repository.interface.ts index 5e56ad1..4c7693d 100644 --- a/src/domain/modules/module-repository.interface.ts +++ b/src/domain/modules/module-repository.interface.ts @@ -1,5 +1,10 @@ -import { Module } from '@/domain/modules/module.model'; +import { + Module, + moduleDetail, + createModule, +} from '@/domain/modules/module.model'; export interface IModuleRepository { getAllModules(): Promise; - findById(id: string): Promise; + findById(id: string): Promise; + createModule(module: createModule): Promise; } diff --git a/src/domain/modules/module.model.ts b/src/domain/modules/module.model.ts index 87f94dc..6dd9dfb 100644 --- a/src/domain/modules/module.model.ts +++ b/src/domain/modules/module.model.ts @@ -20,3 +20,16 @@ export interface Module { location: { id: string; name: string }[]; startDate: string; } +export interface createModule { + name: string; + description: string; + content: string; + level: string; + studyCredits: number; + location: { id: string; name: string }[]; + moduleTags: { id: string; name: string }[]; + learningOutcomes: string; + availableSpots: number; + startDate: string; + shortdescription: string; +} diff --git a/src/domain/moduletags/moduletag-repository.ts b/src/domain/moduletags/moduletag-repository.ts new file mode 100644 index 0000000..a62285c --- /dev/null +++ b/src/domain/moduletags/moduletag-repository.ts @@ -0,0 +1,4 @@ +import { ModuleTag } from '@/domain/moduletags/moduletag.model'; +export interface IModuleTagRepository { + getAllModuleTags(): Promise; +} diff --git a/src/domain/moduletags/moduletag.model.ts b/src/domain/moduletags/moduletag.model.ts new file mode 100644 index 0000000..24770bc --- /dev/null +++ b/src/domain/moduletags/moduletag.model.ts @@ -0,0 +1,4 @@ +export interface ModuleTag { + id: string; + name: string; +} diff --git a/src/infrastructure/database/repositories/choicemodules.repository.ts b/src/infrastructure/database/repositories/choicemodules.repository.ts index 79c39a4..178fd62 100644 --- a/src/infrastructure/database/repositories/choicemodules.repository.ts +++ b/src/infrastructure/database/repositories/choicemodules.repository.ts @@ -1,5 +1,9 @@ import { Injectable } from '@nestjs/common'; -import type { Module, moduleDetail } from '@/domain/modules/module.model'; +import type { + createModule, + Module, + moduleDetail, +} from '@/domain/modules/module.model'; import { PrismaService } from '../prisma'; import { IModuleRepository } from '@/domain/modules/module-repository.interface'; @@ -67,4 +71,72 @@ export class ChoiceModulesRepository implements IModuleRepository { availableSpots: mod.availableSpots!, } as moduleDetail; } + async createModule(module: createModule): Promise { + const created = await this.prisma.$transaction(async (tx) => { + const baseModule = await tx.choiceModule.create({ + data: { + name: module.name, + shortDescription: module.shortdescription, + description: module.description, + content: module.content, + level: module.level, + studyCredits: module.studyCredits, + startDate: module.startDate, + availableSpots: module.availableSpots, + learningOutcomes: module.learningOutcomes, + }, + }); + console.log('Created base module:', baseModule); + + if (module.location.length > 0) { + await tx.choiceModuleLocation.createMany({ + data: module.location.map((loc) => ({ + choiceModuleId: baseModule.id, + locationId: loc.id, + })), + }); + } + + if (module.moduleTags.length > 0) { + await tx.choiceModuleTags.createMany({ + data: module.moduleTags.map((tag) => ({ + choiceModuleId: baseModule.id, + moduleTagsId: tag.id, + })), + }); + } + + return tx.choiceModule.findUnique({ + where: { id: baseModule.id }, + include: { + location: { include: { Location: true } }, + moduleTags: { include: { ModuleTags: true } }, + }, + }); + }); + + if (!created) { + throw new Error('Failed to create module'); + } + + return { + id: created.id, + name: created.name!, + description: created.description!, + content: created.content!, + level: created.level!, + studyCredits: created.studyCredits!, + startDate: created.startDate, + location: created.location.map((cml) => ({ + id: cml.Location.id, + name: cml.Location.name, + })), + moduleTags: created.moduleTags.map((cmt) => ({ + id: cmt.ModuleTags.id, + name: cmt.ModuleTags.name, + })), + learningOutcomes: created.learningOutcomes!, + availableSpots: created.availableSpots!, + } as moduleDetail; + } } diff --git a/src/infrastructure/database/repositories/location.repository.ts b/src/infrastructure/database/repositories/location.repository.ts new file mode 100644 index 0000000..9c2389c --- /dev/null +++ b/src/infrastructure/database/repositories/location.repository.ts @@ -0,0 +1,17 @@ +import { Injectable } from '@nestjs/common'; +import { ILocationRepository } from '@/domain/locations/location-repository.interface'; +import { Location } from '@/domain/locations/location.model'; +import { PrismaService } from '../prisma'; + +@Injectable() +export class LocationRepository implements ILocationRepository { + constructor(private readonly prisma: PrismaService) {} + + async getAllLocation(): Promise { + const locations = await this.prisma.location.findMany(); + return locations.map((loc) => ({ + id: loc.id, + name: loc.name, + })); + } +} diff --git a/src/infrastructure/database/repositories/moduletag.repository.ts b/src/infrastructure/database/repositories/moduletag.repository.ts new file mode 100644 index 0000000..4c4adc9 --- /dev/null +++ b/src/infrastructure/database/repositories/moduletag.repository.ts @@ -0,0 +1,16 @@ +import { Injectable } from '@nestjs/common'; +import { IModuleTagRepository } from '@/domain/moduletags/moduletag-repository'; +import { ModuleTag } from '@/domain/moduletags/moduletag.model'; +import { PrismaService } from '@/infrastructure/database/prisma'; + +@Injectable() +export class ModuleTagRepository implements IModuleTagRepository { + constructor(private readonly prisma: PrismaService) {} + async getAllModuleTags(): Promise { + const moduleTags = await this.prisma.moduleTags.findMany(); + return moduleTags.map((mt) => ({ + id: mt.id, + name: mt.name, + })); + } +} diff --git a/src/presentation/controllers/location.controller.ts b/src/presentation/controllers/location.controller.ts new file mode 100644 index 0000000..f730bc8 --- /dev/null +++ b/src/presentation/controllers/location.controller.ts @@ -0,0 +1,16 @@ +import { Controller, Get } from '@nestjs/common'; +import { ILocationService } from '@/application/ports/location.port'; +import { Inject } from '@nestjs/common'; +import { Location } from '@/domain/locations/location.model'; + +@Controller('locations') +export class LocationController { + private readonly locationService: ILocationService; + constructor(@Inject('SERVICE.LOCATION') locationService: ILocationService) { + this.locationService = locationService; + } + @Get() + async getAllLocation(): Promise { + return await this.locationService.getAllLocation(); + } +} diff --git a/src/presentation/controllers/modules.controller.ts b/src/presentation/controllers/modules.controller.ts index edfaf50..ad5f75c 100644 --- a/src/presentation/controllers/modules.controller.ts +++ b/src/presentation/controllers/modules.controller.ts @@ -1,7 +1,21 @@ -import { Controller, Get, Param, Session } from '@nestjs/common'; -import { SessionData } from 'express-session'; +import { + Controller, + Get, + Param, + Post, + Session, + UnauthorizedException, + Body, +} from '@nestjs/common'; +import { SessionData } from '@/types/session.types'; import { ModuleService } from '@/application/services/module.service'; import { Inject } from '@nestjs/common'; +import { + Module, + moduleDetail, + createModule, +} from '@/domain/modules/module.model'; + @Controller('modules') export class ModulesController { private readonly moduleService: ModuleService; @@ -32,4 +46,31 @@ export class ModulesController { module: await this.moduleService.findById(id), }; } + @Post() + async createModule( + @Session() session: SessionData, + @Body() moduleData: createModule, + ): Promise { + if (!session) { + throw new UnauthorizedException('No active session'); + } + if (!session.user || session.user.role === 'STUDENT') { + console.log(session.user); + return { message: 'Unauthorized' }; + } + + if (!session.user || session.user.role === 'STUDENT') { + console.log(session.user); + return { message: `Unauthorized ${session.user}` }; + } + const module = await this.moduleService.createModule(moduleData); + if (!module) { + console.log(module); + return { message: `Failed to create module` }; + } + + return { + module, + }; + } } diff --git a/src/presentation/controllers/moduletag.controller.ts b/src/presentation/controllers/moduletag.controller.ts new file mode 100644 index 0000000..3298460 --- /dev/null +++ b/src/presentation/controllers/moduletag.controller.ts @@ -0,0 +1,16 @@ +import { Controller, Inject, Get } from '@nestjs/common'; +import { IModuleTagService } from '@/application/ports/moduletag.port'; +import { ModuleTag } from '@/domain/moduletags/moduletag.model'; +@Controller('moduletags') +export class ModuleTagController { + private readonly moduleTagService: IModuleTagService; + constructor( + @Inject('SERVICE.MODULETAG') moduleTagService: IModuleTagService, + ) { + this.moduleTagService = moduleTagService; + } + @Get() + async getAllModuleTags(): Promise { + return await this.moduleTagService.getAllModuleTags(); + } +}