diff --git a/.env.example b/.env.example index 72f7f62791250d9e59c7aa77dcac87908056c761..636ce5ecb1f6cbdeac73878e12263937afd06f5b 100644 --- a/.env.example +++ b/.env.example @@ -6,15 +6,4 @@ POSTGRES_DATABASE= AUTH_SERVICE_URL=http://localhost:3001 FE_URL=http://localhost:5173 -KONF_PERIODE_KEY=PERIODE -KONF_MIN_BIMBINGAN_KEY=MINIMAL_BIMBINGAN -KONF_AWAL_PENDAFTARAN_KEY=AWAL_PENDAFTARAN -KONF_AKHIR_PENDAFTARAN_KEY=AKHIR_PENDAFTARAN -KONF_AWAL_SEMPRO_KEY=AWAL_SEMPRO -KONF_AKHIR_SEMPRO_KEY=AKHIR_SEMPRO -KONF_AWAL_SEM_TESIS_KEY=AWAL_SEM_TESIS -KONF_AKHIR_SEM_TESIS_KEY=AKHIR_SEM_TESIS -KONF_AWAL_SIDANG_KEY=AWAL_SIDANG -KONF_AKHIR_SIDANG_KEY=AKHIR_SIDANG - COOKIE_NAME=gradu-it.access-token diff --git a/src/alokasi-topik/alokasi-topik.controller.ts b/src/alokasi-topik/alokasi-topik.controller.ts index 6fa4e26ac50478a46780cab0a29e11ccda0d7035..069ae9fb2290d3831476d20cb7068beae81fbed4 100644 --- a/src/alokasi-topik/alokasi-topik.controller.ts +++ b/src/alokasi-topik/alokasi-topik.controller.ts @@ -9,22 +9,24 @@ import { Post, Put, Query, + Req, UseGuards, } from "@nestjs/common"; import { ApiBearerAuth, ApiCookieAuth, + ApiCreatedResponse, ApiOkResponse, + ApiOperation, ApiTags, } from "@nestjs/swagger"; import { RoleEnum } from "src/entities/pengguna.entity"; -import { KonfigurasiService } from "src/konfigurasi/konfigurasi.service"; import { CustomAuthGuard } from "src/middlewares/custom-auth.guard"; import { Roles } from "src/middlewares/roles.decorator"; import { RolesGuard } from "src/middlewares/roles.guard"; import { CreateBulkTopikDto, - CreateRespDto, + TopikIdRespDto, CreateTopikDto, GetAllRespDto, OmittedTopik, @@ -34,6 +36,9 @@ import { createBulkRespDto, } from "./alokasi-topik.dto"; import { AlokasiTopikService } from "./alokasi-topik.service"; +import { Request } from "express"; +import { AuthDto } from "src/auth/auth.dto"; +import { HIGH_AUTHORITY_ROLES, isHighAuthority } from "src/helper/roles"; @ApiTags("Alokasi Topik") @ApiCookieAuth() @@ -41,82 +46,129 @@ import { AlokasiTopikService } from "./alokasi-topik.service"; @Controller("alokasi-topik") @UseGuards(CustomAuthGuard, RolesGuard) export class AlokasiTopikController { - constructor( - private alokasiTopikService: AlokasiTopikService, - private konfService: KonfigurasiService, - ) {} + constructor(private alokasiTopikService: AlokasiTopikService) {} - @ApiOkResponse({ type: CreateRespDto }) - @Roles(RoleEnum.S2_TIM_TESIS, RoleEnum.ADMIN) + @ApiOperation({ + summary: "Create new topik. Roles: S2_TIM_TESIS, ADMIN, S2_PEMBIMBING", + }) + @ApiCreatedResponse({ type: TopikIdRespDto }) + @Roles(...HIGH_AUTHORITY_ROLES, RoleEnum.S2_PEMBIMBING) @Post() - async create(@Body() createDto: CreateTopikDto) { - const periode = await this.konfService.getKonfigurasiByKey( - process.env.KONF_PERIODE_KEY, - ); - - if (!periode) throw new BadRequestException("Periode belum dikonfigurasi."); + async create( + @Body() createDto: CreateTopikDto, + @Req() req: Request, + ): Promise<TopikIdRespDto> { + const { roles, id } = req.user as AuthDto; + // user only has S2_PEMBIMBING role + if (!isHighAuthority(roles) && createDto.idPengaju !== id) { + throw new BadRequestException("Pengaju ID harus sama dengan user ID"); + } - return await this.alokasiTopikService.create({ ...createDto, periode }); + return await this.alokasiTopikService.create(createDto); } + @ApiOperation({ + summary: "Create multiple topik. Roles: S2_TIM_TESIS, ADMIN", + }) @ApiOkResponse({ type: createBulkRespDto }) - @Roles(RoleEnum.S2_TIM_TESIS, RoleEnum.ADMIN) + @Roles(...HIGH_AUTHORITY_ROLES) @Post("/bulk") async createBulk(@Body() createDto: CreateBulkTopikDto) { - const periode = await this.konfService.getKonfigurasiByKey( - process.env.KONF_PERIODE_KEY, - ); - - if (!periode) throw new BadRequestException("Periode belum dikonfigurasi."); - - return await this.alokasiTopikService.createBulk(createDto, periode); + return await this.alokasiTopikService.createBulk(createDto); } + @ApiOperation({ + summary: + "Get topik by ID. Roles: S2_TIM_TESIS, ADMIN, S2_PEMBIMBING, S2_MAHASISWA", + }) @ApiOkResponse({ type: OmittedTopik }) - @Roles(RoleEnum.S2_TIM_TESIS, RoleEnum.ADMIN) + @Roles(...HIGH_AUTHORITY_ROLES, RoleEnum.S2_PEMBIMBING, RoleEnum.S2_MAHASISWA) @Get("/:id") async getById(@Param() params: TopikParamDto) { - const res = await this.alokasiTopikService.findById(params.id); + const res = await this.alokasiTopikService.findActiveTopikById(params.id); if (!res) throw new NotFoundException(); return res as OmittedTopik; } + @ApiOperation({ + summary: + "Get all topik. Roles: S2_TIM_TESIS, ADMIN, S2_MAHASISWA, S2_PEMBIMBING", + }) @ApiOkResponse({ type: GetAllRespDto }) - @Roles(RoleEnum.S2_TIM_TESIS, RoleEnum.ADMIN, RoleEnum.S2_MAHASISWA) + @Roles(...HIGH_AUTHORITY_ROLES, RoleEnum.S2_MAHASISWA, RoleEnum.S2_PEMBIMBING) @Get() async getAll( @Query() query: TopikQueryDto, ) { - const periode = await this.konfService.getKonfigurasiByKey( - process.env.KONF_PERIODE_KEY, + return await this.alokasiTopikService.findAllActiveTopikCreatedByPembimbing( + { + ...query, + page: query.page || 1, + }, ); - - if (!periode) throw new BadRequestException("Periode belum dikonfigurasi."); - - return await this.alokasiTopikService.findAllCreatedByPembimbing({ - page: query.page || 1, - ...query, - periode, - }); } - @Roles(RoleEnum.S2_TIM_TESIS, RoleEnum.ADMIN) + @ApiOperation({ + summary: "Update topik. Roles: S2_TIM_TESIS, ADMIN, S2_PEMBIMBING", + }) + @ApiOkResponse({ type: TopikIdRespDto }) + @Roles(...HIGH_AUTHORITY_ROLES, RoleEnum.S2_PEMBIMBING) @Put("/:id") async update( @Param() params: TopikParamDto, @Body() updateDto: UpdateTopikDto, - ) { - const res = await this.alokasiTopikService.update(params.id, updateDto); - if (!res.affected) throw new NotFoundException(); - return res; + @Req() req: Request, + ): Promise<TopikIdRespDto> { + let idPengaju = undefined; + const { roles, id } = req.user as AuthDto; + // user only has S2_PEMBIMBING role + if (!isHighAuthority(roles)) { + if (updateDto.idPengaju !== id) { + throw new BadRequestException("Pengaju ID harus sama dengan user ID"); + } + idPengaju = id; + } + + const res = await this.alokasiTopikService.update( + params.id, + updateDto, + idPengaju, + ); + if (!res.affected) + throw new NotFoundException( + "Topik tidak ditemukan di antara topik yang dapat Anda akses", + ); + + const resp: TopikIdRespDto = { id: params.id }; + + return resp; } - @Roles(RoleEnum.S2_TIM_TESIS, RoleEnum.ADMIN) + @ApiOperation({ + summary: "Delete topik. Roles: S2_TIM_TESIS, ADMIN, S2_PEMBIMBING", + }) + @ApiOkResponse({ type: TopikIdRespDto }) + @Roles(...HIGH_AUTHORITY_ROLES, RoleEnum.S2_PEMBIMBING) @Delete("/:id") - async delete(@Param() params: TopikParamDto) { - const res = await this.alokasiTopikService.remove(params.id); - if (!res.affected) throw new NotFoundException(); - return res; + async delete( + @Param() params: TopikParamDto, + @Req() req: Request, + ): Promise<TopikIdRespDto> { + let idPengaju = undefined; + const { roles, id } = req.user as AuthDto; + // user only has S2_PEMBIMBING role + if (!isHighAuthority(roles)) { + idPengaju = id; + } + + const res = await this.alokasiTopikService.remove(params.id, idPengaju); + if (!res.affected) + throw new NotFoundException( + "Topik tidak ditemukan di antara topik yang dapat Anda akses", + ); + + const resp: TopikIdRespDto = { id: params.id }; + return resp; } } diff --git a/src/alokasi-topik/alokasi-topik.dto.ts b/src/alokasi-topik/alokasi-topik.dto.ts index 93e7dd82db19970390d4cef921e1a774ed2ffabc..5c41225c23ddf1fbefe996dfc7f961ad5ecebc01 100644 --- a/src/alokasi-topik/alokasi-topik.dto.ts +++ b/src/alokasi-topik/alokasi-topik.dto.ts @@ -61,7 +61,7 @@ export class TopikQueryDto { idPembimbing?: string; } -export class PengajuDto extends OmitType(Pengguna, ["nim"] as const) {} +export class PengajuDto extends OmitType(Pengguna, ["nim", "aktif"] as const) {} export class OmittedTopik extends OmitType(Topik, ["idPengaju"] as const) { @ApiProperty({ type: PengajuDto }) @@ -81,7 +81,7 @@ export class createBulkRespDto { ids: string[]; } -export class CreateRespDto { +export class TopikIdRespDto { @ApiProperty() id: string; } diff --git a/src/alokasi-topik/alokasi-topik.module.ts b/src/alokasi-topik/alokasi-topik.module.ts index e9769a4998d5a192e5ee21fa17ad23f4449353fb..f7daaff6363cf0e2d4d1bcedeaea2c38023f6a67 100644 --- a/src/alokasi-topik/alokasi-topik.module.ts +++ b/src/alokasi-topik/alokasi-topik.module.ts @@ -5,10 +5,9 @@ import { TypeOrmModule } from "@nestjs/typeorm"; import { Topik } from "src/entities/topik.entity"; import { CustomStrategy } from "src/middlewares/custom.strategy"; import { AuthModule } from "src/auth/auth.module"; -import { KonfigurasiModule } from "src/konfigurasi/konfigurasi.module"; @Module({ - imports: [TypeOrmModule.forFeature([Topik]), AuthModule, KonfigurasiModule], + imports: [TypeOrmModule.forFeature([Topik]), AuthModule], providers: [AlokasiTopikService, CustomStrategy], controllers: [AlokasiTopikController], }) diff --git a/src/alokasi-topik/alokasi-topik.service.ts b/src/alokasi-topik/alokasi-topik.service.ts index b9056ab744f4cb42d6fc970e629cf3f189afcaec..d52534adfd7f8155ba67dbf0ffe4684b689e9773 100644 --- a/src/alokasi-topik/alokasi-topik.service.ts +++ b/src/alokasi-topik/alokasi-topik.service.ts @@ -2,10 +2,10 @@ import { Injectable } from "@nestjs/common"; import { InjectRepository } from "@nestjs/typeorm"; import { RoleEnum } from "src/entities/pengguna.entity"; import { Topik } from "src/entities/topik.entity"; -import { ArrayContains, Like, Repository } from "typeorm"; +import { ArrayContains, ILike, Repository } from "typeorm"; import { CreateBulkTopikDto, - CreateRespDto, + TopikIdRespDto, CreateTopikDto, GetAllRespDto, UpdateTopikDto, @@ -16,35 +16,26 @@ import { export class AlokasiTopikService { constructor(@InjectRepository(Topik) private topikRepo: Repository<Topik>) {} - async create( - createDto: CreateTopikDto & { periode: string }, - ): Promise<CreateRespDto> { + async create(createDto: CreateTopikDto): Promise<TopikIdRespDto> { const ids = (await this.topikRepo.insert(createDto)).identifiers; return { id: ids[0].id }; } - async createBulk( - createDto: CreateBulkTopikDto, - periode: string, - ): Promise<createBulkRespDto> { + async createBulk(createDto: CreateBulkTopikDto): Promise<createBulkRespDto> { const ids = ( - await this.topikRepo.insert( - createDto.data.map((dto) => ({ ...dto, periode })), - ) + await this.topikRepo.insert(createDto.data.map((dto) => ({ ...dto }))) ).identifiers; return { ids: ids.map(({ id }) => id) }; } - async findById(id: string) { - // not periode-protected + async findActiveTopikById(id: string) { return await this.topikRepo.findOne({ select: { id: true, judul: true, deskripsi: true, - periode: true, pengaju: { id: true, nama: true, @@ -54,6 +45,7 @@ export class AlokasiTopikService { }, where: { id, + aktif: true, }, relations: { pengaju: true, @@ -61,19 +53,17 @@ export class AlokasiTopikService { }); } - async findAllCreatedByPembimbing(options: { + async findAllActiveTopikCreatedByPembimbing(options: { page: number; limit?: number; search?: string; idPembimbing?: string; - periode: string; }): Promise<GetAllRespDto> { const dataQuery = this.topikRepo.find({ select: { id: true, judul: true, deskripsi: true, - periode: true, pengaju: { id: true, nama: true, @@ -82,12 +72,12 @@ export class AlokasiTopikService { }, }, where: { - periode: options.periode, + aktif: true, pengaju: { id: options.idPembimbing || undefined, roles: ArrayContains([RoleEnum.S2_PEMBIMBING]), }, - judul: Like(`%${options.search || ""}%`), + judul: ILike(`%${options.search || ""}%`), }, relations: { pengaju: true, @@ -107,8 +97,7 @@ export class AlokasiTopikService { .createQueryBuilder("topik") .select("topik.id") .innerJoinAndSelect("topik.pengaju", "pengaju") - .where("topik.periode = :periode", { periode: options.periode }) - .andWhere("pengaju.roles @> :role", { + .where("pengaju.roles @> :role", { role: [RoleEnum.S2_PEMBIMBING], }); @@ -142,13 +131,15 @@ export class AlokasiTopikService { } } - async update(id: string, updateDto: UpdateTopikDto) { - // not periode-protected - return await this.topikRepo.update(id, updateDto); + async update(id: string, updateDto: UpdateTopikDto, idPengaju?: string) { + const findOpt = idPengaju + ? { id, idPengaju, aktif: true } + : { id, aktif: true }; + return await this.topikRepo.update(findOpt, updateDto); } - async remove(id: string) { - // not periode-protected - return await this.topikRepo.delete({ id }); // TODO: manage relation cascading option + async remove(id: string, idPengaju?: string) { + const findOpt = idPengaju ? { id, idPengaju } : { id }; + return await this.topikRepo.update(findOpt, { aktif: false }); } } diff --git a/src/app.module.ts b/src/app.module.ts index 9e9ee257abb0b46a1a689a034421ad2b3648ee36..3dd3d3ee0049d15609edbdae4563daac699c1ac7 100644 --- a/src/app.module.ts +++ b/src/app.module.ts @@ -5,14 +5,8 @@ import { TypeOrmModule } from "@nestjs/typeorm"; import { Bimbingan } from "./entities/bimbingan.entity"; import { Pengguna } from "./entities/pengguna.entity"; import { Topik } from "./entities/topik.entity"; -import { AuditLog } from "./entities/auditLog.entity"; import { DosenBimbingan } from "./entities/dosenBimbingan.entity"; -import { Kelas } from "./entities/kelas.entity"; -import { MahasiswaKelas } from "./entities/mahasiswaKelas.entity"; -import { PengajarKelas } from "./entities/pengajarKelas.entity"; import { PendaftaranTesis } from "./entities/pendaftaranTesis.entity"; -// import { Ruangan } from "./entities/ruangan.entity"; -import { Tugas } from "./entities/tugas.entity"; import { PengujiSidsem } from "./entities/pengujiSidsem.entity"; import { RegistrasiTesisModule } from "./registrasi-tesis/registrasi-tesis.module"; import { ConfigModule } from "@nestjs/config"; @@ -20,20 +14,13 @@ import { AuthModule } from "./auth/auth.module"; import { AlokasiTopikModule } from "./alokasi-topik/alokasi-topik.module"; import { DashboardModule } from "./dashboard/dashboard.module"; import { BimbinganModule } from "./bimbingan/bimbingan.module"; -import { Konfigurasi } from "./entities/konfigurasi.entity"; -import { KonfigurasiModule } from "./konfigurasi/konfigurasi.module"; import { validate } from "./env.validation"; import { BerkasBimbingan } from "./entities/berkasBimbingan.entity"; -import { MataKuliah } from "./entities/mataKuliah.entity"; -import { SubmisiTugas } from "./entities/submisiTugas.entity"; -import { BerkasSubmisiTugas } from "./entities/berkasSubmisiTugas.entity"; -import { BerkasTugas } from "./entities/berkasTugas.entity"; -import { TugasModule } from "./tugas/tugas.module"; -import { KelasModule } from "./kelas/kelas.module"; -import { SubmisiTugasModule } from "./submisi-tugas/submisi-tugas.module"; -import { NilaiModule } from "./nilai/nilai.module"; import { PendaftaranSidsem } from "./entities/pendaftaranSidsem"; import { DosenBimbinganModule } from "./dosen-bimbingan/dosen-bimbingan.module"; +import { PenggunaModule } from "./pengguna/pengguna.module"; +import { KonfigurasiModule } from "./konfigurasi/konfigurasi.module"; +import { Konfigurasi } from "./entities/konfigurasi.entity"; @Module({ imports: [ @@ -52,20 +39,10 @@ import { DosenBimbinganModule } from "./dosen-bimbingan/dosen-bimbingan.module"; Pengguna, PendaftaranSidsem, Topik, - AuditLog, DosenBimbingan, - Kelas, - MahasiswaKelas, - PengajarKelas, + Konfigurasi, PendaftaranTesis, - // Ruangan, - Tugas, PengujiSidsem, - Konfigurasi, - MataKuliah, - SubmisiTugas, - BerkasSubmisiTugas, - BerkasTugas, ], synchronize: true, }), @@ -74,13 +51,9 @@ import { DosenBimbinganModule } from "./dosen-bimbingan/dosen-bimbingan.module"; AlokasiTopikModule, DashboardModule, BimbinganModule, - KonfigurasiModule, - DosenBimbinganModule, - KelasModule, - TugasModule, - SubmisiTugasModule, - NilaiModule, DosenBimbinganModule, + PenggunaModule, + KonfigurasiModule, ], controllers: [AppController], providers: [AppService], diff --git a/src/bimbingan/bimbingan.controller.ts b/src/bimbingan/bimbingan.controller.ts index 99eab5d006d76622b2d8cf3a7699c41f93e4ead2..4f03874b94e456fe8a890c47cc05661fb890835d 100644 --- a/src/bimbingan/bimbingan.controller.ts +++ b/src/bimbingan/bimbingan.controller.ts @@ -45,9 +45,7 @@ export class BimbinganController { constructor(private readonly bimbinganService: BimbinganService) {} @ApiOkResponse({ type: GetByMahasiswaIdResDto }) - @ApiNotFoundResponse({ - description: "Tidak ada pendaftaran pada periode sekarang", - }) + @ApiNotFoundResponse({ description: "Tidak ada pendaftaran" }) @Roles(RoleEnum.S2_PEMBIMBING, RoleEnum.ADMIN, RoleEnum.S2_TIM_TESIS) @Get("/mahasiswa/:mahasiswaId") async getByMahasiswaId( @@ -61,9 +59,7 @@ export class BimbinganController { } @ApiOkResponse({ type: GetByMahasiswaIdResDto }) - @ApiNotFoundResponse({ - description: "Tidak ada pendaftaran pada periode sekarang", - }) + @ApiNotFoundResponse({ description: "Tidak ada pendaftaran" }) @Roles(RoleEnum.S2_MAHASISWA) @Get("/") async getOwnBimbingan( @@ -80,9 +76,7 @@ export class BimbinganController { description: "Waktu bimbingan lebih dari hari ini atau bimbingan berikutnya sebelum waktu bimbingan yang dimasukkan", }) - @ApiNotFoundResponse({ - description: "Tidak ada pendaftaran pada periode sekarang", - }) + @ApiNotFoundResponse({ description: "Tidak ada pendaftaran" }) @Roles(RoleEnum.S2_MAHASISWA) @ApiBody({ type: CreateBimbinganReqDto }) @Post("/") @@ -94,9 +88,7 @@ export class BimbinganController { } @ApiOkResponse({ type: UpdateStatusResDto }) - @ApiNotFoundResponse({ - description: "Bimbingan tidak ditemukan", - }) + @ApiNotFoundResponse({ description: "Bimbingan tidak ditemukan" }) @ApiForbiddenResponse({ description: "Anda tidak memiliki akses untuk mengubah status bimbingan", }) @@ -111,9 +103,7 @@ export class BimbinganController { } @ApiOkResponse({ type: GetByBimbinganIdResDto }) - @ApiNotFoundResponse({ - description: "Bimbingan tidak ditemukan", - }) + @ApiNotFoundResponse({ description: "Bimbingan tidak ditemukan" }) @Roles(RoleEnum.S2_PEMBIMBING, RoleEnum.ADMIN) @Get("/:bimbinganId") async getByBimbinganId( diff --git a/src/bimbingan/bimbingan.dto.ts b/src/bimbingan/bimbingan.dto.ts index f3afcdac8aeeb0380633e5609bc7b35d8c864013..649f9c95366f56738a281689e0520c876ec8cef7 100644 --- a/src/bimbingan/bimbingan.dto.ts +++ b/src/bimbingan/bimbingan.dto.ts @@ -33,7 +33,6 @@ class PickedTopikBimbingan extends PickType(Topik, [ "judul", "deskripsi", "idPengaju", - "periode", ] as const) {} export class GetByMahasiswaIdResDto { diff --git a/src/bimbingan/bimbingan.module.ts b/src/bimbingan/bimbingan.module.ts index 30174c69ff5a33b4a5caa742095ef9f830c5eb8d..b77070fda6099172ac83112b4163af9a742f656d 100644 --- a/src/bimbingan/bimbingan.module.ts +++ b/src/bimbingan/bimbingan.module.ts @@ -4,22 +4,25 @@ import { BimbinganService } from "./bimbingan.service"; import { TypeOrmModule } from "@nestjs/typeorm"; import { Bimbingan } from "src/entities/bimbingan.entity"; import { PendaftaranTesis } from "src/entities/pendaftaranTesis.entity"; -import { Konfigurasi } from "src/entities/konfigurasi.entity"; import { DosenBimbingan } from "src/entities/dosenBimbingan.entity"; import { BerkasBimbingan } from "src/entities/berkasBimbingan.entity"; +import { PenggunaModule } from "src/pengguna/pengguna.module"; +import { PenggunaService } from "src/pengguna/pengguna.service"; +import { Pengguna } from "src/entities/pengguna.entity"; @Module({ imports: [ TypeOrmModule.forFeature([ Bimbingan, PendaftaranTesis, - Konfigurasi, DosenBimbingan, BerkasBimbingan, + Pengguna, ]), + PenggunaModule, ], controllers: [BimbinganController], - providers: [BimbinganService], + providers: [BimbinganService, PenggunaService], exports: [BimbinganService], }) export class BimbinganModule {} diff --git a/src/bimbingan/bimbingan.service.ts b/src/bimbingan/bimbingan.service.ts index 52c1ba28e98f9c71aad2d88e5ebeafb8d1cb1d40..825ce9109b5ac2c4be8f5e68aa0e600b2eda67e0 100644 --- a/src/bimbingan/bimbingan.service.ts +++ b/src/bimbingan/bimbingan.service.ts @@ -9,7 +9,6 @@ import * as dayjs from "dayjs"; import { AuthDto } from "src/auth/auth.dto"; import { Bimbingan, BimbinganStatus } from "src/entities/bimbingan.entity"; import { DosenBimbingan } from "src/entities/dosenBimbingan.entity"; -import { Konfigurasi } from "src/entities/konfigurasi.entity"; import { PendaftaranTesis, RegStatus, @@ -25,6 +24,7 @@ import { UpdateStatusResDto, } from "./bimbingan.dto"; import { BerkasBimbingan } from "src/entities/berkasBimbingan.entity"; +import { PenggunaService } from "src/pengguna/pengguna.service"; @Injectable() export class BimbinganService { @@ -33,29 +33,23 @@ export class BimbinganService { private bimbinganRepository: Repository<Bimbingan>, @InjectRepository(PendaftaranTesis) private pendaftaranTesisRepository: Repository<PendaftaranTesis>, - @InjectRepository(Konfigurasi) - private konfigurasiRepository: Repository<Konfigurasi>, @InjectRepository(DosenBimbingan) private dosenBimbinganRepository: Repository<DosenBimbingan>, @InjectRepository(BerkasBimbingan) private berkasBimbinganRepository: Repository<BerkasBimbingan>, + private penggunaService: PenggunaService, ) {} async getByMahasiswaId( mahasiswaId: string, user: AuthDto, ): Promise<GetByMahasiswaIdResDto> { - const currentPeriode = await this.konfigurasiRepository.findOne({ - where: { key: process.env.KONF_PERIODE_KEY }, - }); + await this.penggunaService.isMahasiswaAktifOrFail(mahasiswaId); const pendaftaran = await this.pendaftaranTesisRepository.findOne({ where: { mahasiswa: { id: mahasiswaId }, status: RegStatus.APPROVED, - topik: { - periode: currentPeriode.value, - }, }, relations: { mahasiswa: true, @@ -65,9 +59,7 @@ export class BimbinganService { }); if (!pendaftaran) { - throw new NotFoundException( - "Tidak ada pendaftaran yang disetujui pada periode ini", - ); + throw new NotFoundException("Tidak ada pendaftaran yang disetujui"); } // Validate bimbingan data by its dosbim @@ -119,17 +111,10 @@ export class BimbinganService { createDto: CreateBimbinganReqDto, ): Promise<CreateBimbinganResDto> { // Check if user registered in bimbingan - const currentPeriode = await this.konfigurasiRepository.findOne({ - where: { key: process.env.KONF_PERIODE_KEY }, - }); - const pendaftaran = await this.pendaftaranTesisRepository.findOne({ where: { mahasiswa: { id: mahasiswaId }, status: RegStatus.APPROVED, - topik: { - periode: currentPeriode.value, - }, }, relations: { mahasiswa: true, @@ -139,9 +124,7 @@ export class BimbinganService { }); if (!pendaftaran) { - throw new NotFoundException( - "Tidak ada pendaftaran yang disetujui pada periode ini", - ); + throw new NotFoundException("Tidak ada pendaftaran yang disetujui"); } if (dayjs(createDto.waktuBimbingan).isAfter(dayjs(new Date()).endOf("d"))) @@ -180,7 +163,7 @@ export class BimbinganService { user: AuthDto, dto: UpdateStatusDto, ): Promise<UpdateStatusResDto> { - const bimbingan = await this.getByBimbinganId(user, dto.bimbinganId); + const bimbingan = await this.getByBimbinganId(user, dto.bimbinganId); // already check if mahasiswa is aktif await this.bimbinganRepository.update(bimbingan.id, { disahkan: dto.status, @@ -195,19 +178,13 @@ export class BimbinganService { user: AuthDto, bimbinganId: string, ): Promise<GetByBimbinganIdResDto> { - const currentPeriode = await this.konfigurasiRepository.findOne({ - where: { key: process.env.KONF_PERIODE_KEY }, - }); - const bimbinganQuery = this.bimbinganRepository .createQueryBuilder("bimbingan") .leftJoinAndSelect("bimbingan.pendaftaran", "pendaftaran") .leftJoinAndSelect("pendaftaran.dosenBimbingan", "dosenBimbingan") .leftJoinAndSelect("dosenBimbingan.dosen", "dosen") .leftJoinAndSelect("bimbingan.berkas", "berkas") - .leftJoin("pendaftaran.topik", "topik", "topik.periode = :periode", { - periode: currentPeriode.value, - }) + .leftJoin("pendaftaran.topik", "topik") .leftJoinAndSelect("pendaftaran.mahasiswa", "mahasiswa") .where("bimbingan.id = :id", { id: bimbinganId }); const bimbingan = await bimbinganQuery.getOne(); @@ -216,6 +193,10 @@ export class BimbinganService { throw new NotFoundException("Bimbingan tidak ditemukan"); } + if (!bimbingan.pendaftaran.mahasiswa.aktif) { + throw new BadRequestException("Bimbingan milik mahasiswa tidak aktif"); + } + if ( !user.roles.includes(RoleEnum.ADMIN) && !bimbingan.pendaftaran.dosenBimbingan diff --git a/src/dashboard/dashboard.controller.ts b/src/dashboard/dashboard.controller.ts index 5631e9daa9da0d30a886abbb1e631faf3e7b03f9..8cec5c50418c02eac5f35279a28b7582647e149e 100644 --- a/src/dashboard/dashboard.controller.ts +++ b/src/dashboard/dashboard.controller.ts @@ -13,7 +13,6 @@ import { Roles } from "src/middlewares/roles.decorator"; import { RolesGuard } from "src/middlewares/roles.guard"; import { DashboardDto, - DashboardMahasiswaResDto, GetDashboardDosbimQueryDto, GetDashboardTimTesisReqQueryDto, GetDashboardTimTesisRespDto, @@ -51,18 +50,6 @@ export class DashboardController { ); } - @UseGuards(CustomAuthGuard, RolesGuard) - @Roles(RoleEnum.S2_MAHASISWA) - @ApiOkResponse({ type: DashboardMahasiswaResDto }) - @Get("/mahasiswa") - async getDashboardMahasiswa( - @Req() request: Request, - ): Promise<DashboardMahasiswaResDto> { - return this.dashboardService.getDashboardMahasiswa( - (request.user as AuthDto).id, - ); - } - @UseGuards(CustomAuthGuard, RolesGuard) @Roles(RoleEnum.S2_TIM_TESIS, RoleEnum.ADMIN) @ApiOkResponse({ type: GetDashboardTimTesisRespDto }) diff --git a/src/dashboard/dashboard.dto.ts b/src/dashboard/dashboard.dto.ts index d49ba0fd308a77aa3f0ea54ef0de78032f1ce200..22d667de53d46fd14fd84d98baa2caf73e92644e 100644 --- a/src/dashboard/dashboard.dto.ts +++ b/src/dashboard/dashboard.dto.ts @@ -5,14 +5,10 @@ import { PickType, } from "@nestjs/swagger"; import { IsNumberString, IsOptional, IsString } from "class-validator"; -import { Bimbingan, BimbinganStatus } from "src/entities/bimbingan.entity"; -import { PendaftaranSidsem } from "src/entities/pendaftaranSidsem"; +import { BimbinganStatus } from "src/entities/bimbingan.entity"; import { Pengguna } from "src/entities/pengguna.entity"; import { Topik } from "src/entities/topik.entity"; -import { - JalurEnum, - PendaftaranTesis, -} from "../entities/pendaftaranTesis.entity"; +import { JalurEnum } from "../entities/pendaftaranTesis.entity"; class PickedTopikDashboard extends PickType(Topik, ["id", "judul"] as const) {} class PickedMhsDashboard extends PickType(Pengguna, [ @@ -22,35 +18,10 @@ class PickedMhsDashboard extends PickType(Pengguna, [ "email", ] as const) {} -class OmittedTopikMhsDashboard extends OmitType(Topik, ["pengaju"] as const) {} - class NoEmailUserDashboard extends OmitType(PickedMhsDashboard, [ "email", ] as const) {} -export class NoNIMUserDashboard extends OmitType(PickedMhsDashboard, [ - "nim", -] as const) {} - -class OmittedPendaftaranTesisMhsDashboard extends OmitType(PendaftaranTesis, [ - "mahasiswa", - "topik", - "penerima", -] as const) { - @ApiProperty() - topik: OmittedTopikMhsDashboard; - - @ApiProperty() - penerima: NoEmailUserDashboard; -} - -class SidsemWithPenguji extends OmitType(PendaftaranSidsem, [ - "penguji", -] as const) { - @ApiProperty({ type: [NoNIMUserDashboard] }) - penguji: NoNIMUserDashboard[]; -} - export class DashboardDto { @ApiProperty({ example: "550e8400-e29b-41d4-a716-446655440000" }) id: string; @@ -76,38 +47,6 @@ export class JalurStatisticDto { count: number; } -export class DashboardMahasiswaResDto { - @ApiProperty() - mahasiswa: PickedMhsDashboard; - - @ApiProperty({ type: OmittedPendaftaranTesisMhsDashboard, nullable: true }) - pendaftaranTesis: OmittedPendaftaranTesisMhsDashboard; - - @ApiProperty({ type: [NoNIMUserDashboard] }) - dosenBimbingan: NoNIMUserDashboard[]; - - @ApiProperty({ type: [Bimbingan] }) - bimbingan: Bimbingan[]; - - @ApiProperty({ - type: PendaftaranSidsem, - nullable: true, - }) - seminarSatu: PendaftaranSidsem; - - @ApiProperty({ - type: SidsemWithPenguji, - nullable: true, - }) - seminarDua: SidsemWithPenguji; - - @ApiProperty({ - type: SidsemWithPenguji, - nullable: true, - }) - sidang: SidsemWithPenguji; -} - export class GetDashboardDosbimQueryDto { @ApiPropertyOptional({}) @IsOptional() diff --git a/src/dashboard/dashboard.module.ts b/src/dashboard/dashboard.module.ts index 8f8f447d98f6ce5c3d40de228881aa244d364bba..4f310dc17f1ecef4ffba88fe01b46b60cfacddca 100644 --- a/src/dashboard/dashboard.module.ts +++ b/src/dashboard/dashboard.module.ts @@ -4,24 +4,11 @@ import { DashboardController } from "./dashboard.controller"; import { DashboardService } from "./dashboard.service"; import { PendaftaranTesis } from "../entities/pendaftaranTesis.entity"; import { Pengguna } from "../entities/pengguna.entity"; -import { Topik } from "../entities/topik.entity"; -import { Konfigurasi } from "src/entities/konfigurasi.entity"; -import { Bimbingan } from "src/entities/bimbingan.entity"; -import { PendaftaranSidsem } from "src/entities/pendaftaranSidsem"; -import { DosenBimbingan } from "src/entities/dosenBimbingan.entity"; import { BimbinganModule } from "src/bimbingan/bimbingan.module"; @Module({ imports: [ - TypeOrmModule.forFeature([ - PendaftaranTesis, - Pengguna, - Topik, - Konfigurasi, - Bimbingan, - PendaftaranSidsem, - DosenBimbingan, - ]), + TypeOrmModule.forFeature([PendaftaranTesis, Pengguna]), BimbinganModule, ], controllers: [DashboardController], diff --git a/src/dashboard/dashboard.service.ts b/src/dashboard/dashboard.service.ts index a4360e8f1010086dffb050df8315bc57fa92ce2c..80fe742036d2aab8a6c953993b43e6ce2feb43ec 100644 --- a/src/dashboard/dashboard.service.ts +++ b/src/dashboard/dashboard.service.ts @@ -1,9 +1,7 @@ import { BadRequestException, Injectable } from "@nestjs/common"; import { InjectRepository } from "@nestjs/typeorm"; import { BimbinganService } from "src/bimbingan/bimbingan.service"; -import { Bimbingan } from "src/entities/bimbingan.entity"; import { DosenBimbingan } from "src/entities/dosenBimbingan.entity"; -import { Konfigurasi } from "src/entities/konfigurasi.entity"; import { PendaftaranSidsem, TipeSidsemEnum, @@ -16,12 +14,10 @@ import { import { Pengguna, RoleEnum } from "../entities/pengguna.entity"; import { DashboardDto, - DashboardMahasiswaResDto, DashboardTimTesisStatusEnum, GetDashboardTimTesisReqQueryDto, GetDashboardTimTesisRespDto, JalurStatisticDto, - NoNIMUserDashboard, } from "./dashboard.dto"; @Injectable() @@ -31,14 +27,10 @@ export class DashboardService { private pendaftaranTesisRepository: Repository<PendaftaranTesis>, @InjectRepository(Pengguna) private penggunaRepository: Repository<Pengguna>, - @InjectRepository(Konfigurasi) - private konfigurasiRepository: Repository<Konfigurasi>, - @InjectRepository(Bimbingan) - private bimbinganRepository: Repository<Bimbingan>, - @InjectRepository(PendaftaranSidsem) - private pendaftaranSidsemRepository: Repository<PendaftaranSidsem>, @InjectRepository(DosenBimbingan) private dosenBimbinganRepository: Repository<DosenBimbingan>, + @InjectRepository(PendaftaranSidsem) + private pendaftaranSidsemRepository: Repository<PendaftaranSidsem>, private bimbinganService: BimbinganService, ) {} @@ -52,14 +44,6 @@ export class DashboardService { dosenId: string, search?: string, ): Promise<DashboardDto[]> { - const currentPeriode = await this.konfigurasiRepository.findOne({ - where: { key: process.env.KONF_PERIODE_KEY }, - }); - - if (!currentPeriode) { - throw new BadRequestException("Periode belum dikonfigurasi"); - } - let pendaftaranTesisQuery = this.pendaftaranTesisRepository .createQueryBuilder("pendaftaranTesis") .leftJoinAndSelect("pendaftaranTesis.mahasiswa", "mahasiswa") @@ -72,10 +56,10 @@ export class DashboardService { dosenId, }, ) + .where("mahasiswa.aktif = true") .andWhere("pendaftaranTesis.status = :status", { status: RegStatus.APPROVED, - }) - .andWhere("topik.periode = :periode", { periode: currentPeriode.value }); + }); if (search) { pendaftaranTesisQuery = pendaftaranTesisQuery.andWhere( @@ -115,15 +99,9 @@ export class DashboardService { async getStatisticsByJalurPilihan( dosenId: string, ): Promise<JalurStatisticDto[]> { - const [currentPeriode, dosen] = await Promise.all([ - this.konfigurasiRepository.findOne({ - where: { key: process.env.KONF_PERIODE_KEY }, - }), - this.penggunaRepository.findOne({ - where: { id: dosenId }, - }), - ]); - + const dosen = await this.penggunaRepository.findOne({ + where: { id: dosenId }, + }); if (!dosen) { throw new BadRequestException("Dosen tidak ditemukan"); } @@ -132,9 +110,7 @@ export class DashboardService { .createQueryBuilder("pendaftaranTesis") .select("pendaftaranTesis.jalurPilihan", "jalurPilihan") .addSelect("COUNT(*)", "count") - .leftJoin("pendaftaranTesis.topik", "topik", "topik.periode = :periode", { - periode: currentPeriode.value, - }) + .leftJoin("pendaftaranTesis.mahasiswa", "mahasiswa") .innerJoin( "pendaftaranTesis.dosenBimbingan", "dosenBimbingan", @@ -143,6 +119,7 @@ export class DashboardService { dosenId, }, ) + .where("mahasiswa.aktif = true") .andWhere("pendaftaranTesis.status = :status", { status: RegStatus.APPROVED, }) @@ -152,128 +129,6 @@ export class DashboardService { return statistics as JalurStatisticDto[]; } - async getDashboardMahasiswa( - mahasiswaId: string, - ): Promise<DashboardMahasiswaResDto> { - const currentPeriode = await this.konfigurasiRepository.findOne({ - where: { key: process.env.KONF_PERIODE_KEY }, - }); - - const mahasiswaQuery = this.penggunaRepository - .createQueryBuilder("pengguna") - .select([ - "pengguna.id", - "pengguna.nama", - "pengguna.email", - "pengguna.nim", - ]) - .where("pengguna.id = :id", { id: mahasiswaId }); - const pendaftaranTesisQuery = this.pendaftaranTesisRepository - .createQueryBuilder("pendaftaranTesis") - .select([ - "pendaftaranTesis.id", - "pendaftaranTesis.jalurPilihan", - "pendaftaranTesis.waktuPengiriman", - "pendaftaranTesis.jadwalInterview", - "pendaftaranTesis.waktuKeputusan", - "pendaftaranTesis.status", - "penerima.id", - "penerima.nama", - "penerima.email", - ]) - .leftJoin("pendaftaranTesis.mahasiswa", "mahasiswa") - .leftJoinAndSelect("pendaftaranTesis.topik", "topik") - .leftJoin("pendaftaranTesis.penerima", "penerima") - .where("mahasiswa.id = :id", { id: mahasiswaId }) - .andWhere("topik.periode = :periode", { periode: currentPeriode.value }) - .orderBy("pendaftaranTesis.waktuPengiriman", "DESC"); - - const [mahasiswa, pendaftaranTesis] = await Promise.all([ - mahasiswaQuery.getOne(), - pendaftaranTesisQuery.getOne(), - ]); - - let dosenBimbingan: DosenBimbingan[] = []; - let bimbingan: Bimbingan[] = []; - let seminarSatu: PendaftaranSidsem | null = null; - let seminarDua: PendaftaranSidsem | null = null; - let sidang: PendaftaranSidsem | null = null; - - if (pendaftaranTesis) { - const dosenBimbinganQuery = this.dosenBimbinganRepository - .createQueryBuilder("dosenBimbingan") - .select(["dosen.id", "dosen.nama", "dosen.email"]) - .leftJoin("dosenBimbingan.dosen", "dosen") - .where("dosenBimbingan.idPendaftaran = :id", { - id: pendaftaranTesis.id, - }); - const bimbinganQuery = this.bimbinganRepository - .createQueryBuilder("bimbingan") - .leftJoinAndSelect("bimbingan.berkas", "berkas") - .where("bimbingan.pendaftaranId = :id", { - id: pendaftaranTesis.id, - }); - const [seminarSatuQuery, seminarDuaQuery, sidangQuery] = Object.values( - TipeSidsemEnum, - ).map((tipe) => { - let temp = this.pendaftaranSidsemRepository - .createQueryBuilder("pendaftaranSidsem") - .leftJoinAndSelect("pendaftaranSidsem.ruangan", "ruangan") - .where("pendaftaranSidsem.pendaftaranTesisId = :id", { - id: pendaftaranTesis.id, - }) - .andWhere("pendaftaranSidsem.tipe = :tipe", { - tipe, - }) - .andWhere("NOT pendaftaranSidsem.ditolak"); - - if (tipe !== TipeSidsemEnum.SEMINAR_1) { - temp = temp - .leftJoinAndSelect("pendaftaranSidsem.penguji", "penguji") - .leftJoinAndSelect("penguji.dosen", "dosen"); - } - - return temp; - }); - - [dosenBimbingan, bimbingan, seminarSatu, seminarDua, sidang] = - await Promise.all([ - dosenBimbinganQuery.getMany(), - bimbinganQuery.getMany(), - seminarSatuQuery.getOne(), - seminarDuaQuery.getOne(), - sidangQuery.getOne(), - ]); - } - - return { - mahasiswa, - pendaftaranTesis, - dosenBimbingan: - dosenBimbingan.length > 0 - ? (dosenBimbingan as any as NoNIMUserDashboard[]) - : [pendaftaranTesis.penerima], - bimbingan, - seminarSatu, - seminarDua: { - ...seminarDua, - penguji: seminarDua?.penguji.map((p) => ({ - id: p.dosen.id, - nama: p.dosen.nama, - email: p.dosen.email, - })), - }, - sidang: { - ...sidang, - penguji: sidang?.penguji.map((p) => ({ - id: p.dosen.id, - nama: p.dosen.nama, - email: p.dosen.email, - })), - }, - }; - } - async getDashboardTimTesis( query: GetDashboardTimTesisReqQueryDto, ): Promise<GetDashboardTimTesisRespDto> { diff --git a/src/dosen-bimbingan/dosen-bimbingan.dto.ts b/src/dosen-bimbingan/dosen-bimbingan.dto.ts index 23c823e7389a759b7d532c02543e736d5bbaf0d4..16e493a95a99c7f62c02cf894b7386b3e5974383 100644 --- a/src/dosen-bimbingan/dosen-bimbingan.dto.ts +++ b/src/dosen-bimbingan/dosen-bimbingan.dto.ts @@ -14,4 +14,5 @@ export class GetDosbimResDto extends PickType(Pengguna, [ "id", "email", "nama", + "keahlian", ] as const) {} diff --git a/src/dosen-bimbingan/dosen-bimbingan.module.ts b/src/dosen-bimbingan/dosen-bimbingan.module.ts index 1929f62932c353150ab780e6a6fb785c560a9357..1721d9d65412532695c2c187b62f6aec9e248070 100644 --- a/src/dosen-bimbingan/dosen-bimbingan.module.ts +++ b/src/dosen-bimbingan/dosen-bimbingan.module.ts @@ -3,18 +3,11 @@ import { DosenBimbinganController } from "./dosen-bimbingan.controller"; import { DosenBimbinganService } from "./dosen-bimbingan.service"; import { AuthModule } from "src/auth/auth.module"; import { TypeOrmModule } from "@nestjs/typeorm"; -import { PendaftaranTesis } from "src/entities/pendaftaranTesis.entity"; -import { DosenBimbingan } from "src/entities/dosenBimbingan.entity"; import { Pengguna } from "src/entities/pengguna.entity"; -import { KonfigurasiModule } from "src/konfigurasi/konfigurasi.module"; import { CustomStrategy } from "src/middlewares/custom.strategy"; @Module({ - imports: [ - TypeOrmModule.forFeature([PendaftaranTesis, DosenBimbingan, Pengguna]), - AuthModule, - KonfigurasiModule, - ], + imports: [TypeOrmModule.forFeature([Pengguna]), AuthModule], controllers: [DosenBimbinganController], providers: [DosenBimbinganService, CustomStrategy], }) diff --git a/src/dosen-bimbingan/dosen-bimbingan.service.ts b/src/dosen-bimbingan/dosen-bimbingan.service.ts index 88f0f9d159f8f0e4949c06a4c5bbcb7ad9a9643a..9d9039a1dbc62780d044cb2fac6f09239e2ebc07 100644 --- a/src/dosen-bimbingan/dosen-bimbingan.service.ts +++ b/src/dosen-bimbingan/dosen-bimbingan.service.ts @@ -16,6 +16,7 @@ export class DosenBimbinganService { id: true, nama: true, email: true, + keahlian: true, }, where: { roles: ArrayContains([RoleEnum.S2_PEMBIMBING]), diff --git a/src/entities/auditLog.entity.ts b/src/entities/auditLog.entity.ts deleted file mode 100644 index c95d05a03fb11fb13d835bc1e6c64b22e107bff6..0000000000000000000000000000000000000000 --- a/src/entities/auditLog.entity.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { Entity, ManyToOne, PrimaryGeneratedColumn } from "typeorm"; -import { Pengguna } from "./pengguna.entity"; - -@Entity() -export class AuditLog { - @PrimaryGeneratedColumn("uuid") - id: string; - - @ManyToOne(() => Pengguna, (pengguna) => pengguna.id) - pengguna: Pengguna; -} diff --git a/src/entities/berkasSubmisiTugas.entity.ts b/src/entities/berkasSubmisiTugas.entity.ts deleted file mode 100644 index e48d5e8d88f1449b767d3e88f1f9862e8e0f6560..0000000000000000000000000000000000000000 --- a/src/entities/berkasSubmisiTugas.entity.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { Column, Entity, ManyToOne, PrimaryGeneratedColumn } from "typeorm"; -import { SubmisiTugas } from "./submisiTugas.entity"; -import { ApiProperty } from "@nestjs/swagger"; -import { IsString, IsUUID } from "@nestjs/class-validator"; -import { IsUrl } from "class-validator"; - -@Entity() -export class BerkasSubmisiTugas { - @ApiProperty({ example: "550e8400-e29b-41d4-a716-446655440000" }) - @IsUUID() - @PrimaryGeneratedColumn("uuid") - id: string; - - @ManyToOne(() => SubmisiTugas, (submisi) => submisi.id, { - orphanedRowAction: "delete", - }) - submisiTugas: SubmisiTugas; - - @ApiProperty() - @IsString() - @Column({ type: "text" }) - nama: string; - - @ApiProperty({ example: "https://example.com/berkas.pdf" }) - @IsUrl() - @Column({ type: "text" }) - url: string; -} diff --git a/src/entities/berkasTugas.entity.ts b/src/entities/berkasTugas.entity.ts deleted file mode 100644 index c18bccfa9c839754ca8ed7bb0f5789f8841e3ff1..0000000000000000000000000000000000000000 --- a/src/entities/berkasTugas.entity.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { Column, Entity, ManyToOne, PrimaryGeneratedColumn } from "typeorm"; -import { Tugas } from "./tugas.entity"; -import { ApiProperty } from "@nestjs/swagger"; -import { IsUrl, IsUUID } from "class-validator"; -import { IsString } from "@nestjs/class-validator"; - -@Entity() -export class BerkasTugas { - @ApiProperty({ example: "550e8400-e29b-41d4-a716-446655440000" }) - @IsUUID() - @PrimaryGeneratedColumn("uuid") - id: string; - - @ManyToOne(() => Tugas, (tugas) => tugas.id, { orphanedRowAction: "delete" }) - tugas: Tugas; - - @ApiProperty() - @IsString() - @Column({ type: "text" }) - nama: string; - - @ApiProperty({ example: "https://example.com/berkas.pdf" }) - @IsUrl() - @Column({ type: "text" }) - url: string; -} diff --git a/src/entities/kelas.entity.ts b/src/entities/kelas.entity.ts deleted file mode 100644 index 7070b47519d5ed99f52281c454c32e04f01ef2f3..0000000000000000000000000000000000000000 --- a/src/entities/kelas.entity.ts +++ /dev/null @@ -1,65 +0,0 @@ -import { - Column, - Entity, - JoinColumn, - ManyToOne, - OneToMany, - PrimaryGeneratedColumn, -} from "typeorm"; -import { MataKuliah } from "./mataKuliah.entity"; -import { ApiProperty } from "@nestjs/swagger"; -import { PengajarKelas } from "./pengajarKelas.entity"; -import { MahasiswaKelas } from "./mahasiswaKelas.entity"; -import { - IsPositive, - IsString, - IsUUID, - Length, - MaxLength, -} from "@nestjs/class-validator"; -import { Tugas } from "./tugas.entity"; - -@Entity() -export class Kelas { - @ApiProperty({ example: "550e8400-e29b-41d4-a716-446655440000" }) - @IsUUID() - @PrimaryGeneratedColumn("uuid") - id: string; - - @ApiProperty({ example: 1 }) - @Column({ type: "smallint" }) - @ApiProperty({ example: 1 }) - @IsPositive() - nomor: number; - - @ApiProperty() - @IsString() - @Column({ type: "text" }) - periode: string; - - @ApiProperty({ type: MataKuliah }) - @ManyToOne(() => MataKuliah, (mataKuliah) => mataKuliah.kode) - @JoinColumn({ name: "mataKuliahKode" }) - mataKuliah: MataKuliah; - - @ApiProperty({ minLength: 6, maxLength: 6, example: "IF4031" }) - @IsString() - @Length(6) - @Column({ nullable: true }) - mataKuliahKode: string; - - @ApiProperty({ example: "bg-blue-600/20" }) - @IsString() - @MaxLength(24) - @Column({ type: "varchar", length: 24 }) - warna: string; - - @OneToMany(() => PengajarKelas, (pengajar) => pengajar.kelas) - pengajar: PengajarKelas[]; - - @OneToMany(() => MahasiswaKelas, (mahasiswa) => mahasiswa.kelas) - mahasiswa: MahasiswaKelas[]; - - @OneToMany(() => Tugas, (tugas) => tugas.kelas) - tugas: Tugas[]; -} diff --git a/src/entities/konfigurasi.entity.ts b/src/entities/konfigurasi.entity.ts index 538a9fa944cad11819c8323dafa99decc9801908..26ee3cee556d63ef6ae248c1f957afe0c56254f8 100644 --- a/src/entities/konfigurasi.entity.ts +++ b/src/entities/konfigurasi.entity.ts @@ -1,10 +1,27 @@ +import { ApiProperty } from "@nestjs/swagger"; +import { IsEnum, IsString } from "class-validator"; import { Column, Entity, PrimaryColumn } from "typeorm"; +export enum KonfigurasiKeyEnum { + AWAL_PENDAFTARAN = "AWAL_PENDAFTARAN", + AKHIR_PENDAFTARAN = "AKHIR_PENDAFTARAN", + AWAL_SEMPRO = "AWAL_SEMPRO", + AKHIR_SEMPRO = "AKHIR_SEMPRO", + AWAL_SEM_TESIS = "AWAL_SEM_TESIS", + AKHIR_SEM_TESIS = "AKHIR_SEM_TESIS", + AWAL_SIDANG = "AWAL_SIDANG", + AKHIR_SIDANG = "AKHIR_SIDANG", +} + @Entity() export class Konfigurasi { - @PrimaryColumn({ type: "text" }) - key: string; + @ApiProperty({ enum: KonfigurasiKeyEnum }) + @IsEnum(KonfigurasiKeyEnum) + @PrimaryColumn({ type: "enum", enum: KonfigurasiKeyEnum }) + key: KonfigurasiKeyEnum; + @ApiProperty() + @IsString() @Column({ type: "text" }) value: string; } diff --git a/src/entities/mahasiswaKelas.entity.ts b/src/entities/mahasiswaKelas.entity.ts deleted file mode 100644 index 0753aa6f75f865913b7e42209248310948dc67c2..0000000000000000000000000000000000000000 --- a/src/entities/mahasiswaKelas.entity.ts +++ /dev/null @@ -1,32 +0,0 @@ -import { - Column, - Entity, - JoinColumn, - ManyToOne, - PrimaryGeneratedColumn, -} from "typeorm"; -import { Kelas } from "./kelas.entity"; -import { Pengguna } from "./pengguna.entity"; - -@Entity() -export class MahasiswaKelas { - @PrimaryGeneratedColumn("uuid") - id: string; - - @ManyToOne(() => Kelas, (kelas) => kelas.id) - @JoinColumn({ name: "kelasId" }) - kelas: Kelas; - - @Column() - kelasId: string; - - @ManyToOne(() => Pengguna, (pengguna) => pengguna.id) - @JoinColumn({ name: "mahasiswaId" }) - mahasiswa: Pengguna; - - @Column() - mahasiswaId: string; - - @Column({ type: "real", nullable: true }) - nilaiAkhir?: number; -} diff --git a/src/entities/mataKuliah.entity.ts b/src/entities/mataKuliah.entity.ts deleted file mode 100644 index 9e1fed5d807a176b89288144623ec72575ef5e33..0000000000000000000000000000000000000000 --- a/src/entities/mataKuliah.entity.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { ApiProperty } from "@nestjs/swagger"; -import { IsString, Length, MaxLength } from "class-validator"; -import { Column, Entity, OneToMany, PrimaryColumn } from "typeorm"; -import { Kelas } from "./kelas.entity"; - -@Entity() -export class MataKuliah { - @ApiProperty({ minLength: 6, maxLength: 6, example: "IF4031" }) - @IsString() - @Length(6) - @PrimaryColumn({ type: "varchar", length: 6 }) - kode: string; - - @ApiProperty({ maxLength: 256 }) - @IsString() - @MaxLength(256) - @Column({ type: "varchar", length: 256 }) - nama: string; - - @OneToMany(() => Kelas, (kelas) => kelas.mataKuliah) - kelas: Kelas[]; -} diff --git a/src/entities/pengajarKelas.entity.ts b/src/entities/pengajarKelas.entity.ts deleted file mode 100644 index 61a314c4f21548b3b48e9aeb510b2d195d9a3662..0000000000000000000000000000000000000000 --- a/src/entities/pengajarKelas.entity.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { - Column, - Entity, - JoinColumn, - ManyToOne, - PrimaryGeneratedColumn, -} from "typeorm"; -import { Kelas } from "./kelas.entity"; -import { Pengguna } from "./pengguna.entity"; - -@Entity() -export class PengajarKelas { - @PrimaryGeneratedColumn("uuid") - id: string; - - @ManyToOne(() => Kelas, (kelas) => kelas.id) - @JoinColumn({ name: "kelasId" }) - kelas: Kelas; - - @Column() - kelasId: string; - - @ManyToOne(() => Pengguna, (pengguna) => pengguna.id) - @JoinColumn({ name: "pengajarId" }) - pengajar: Pengguna; - - @Column() - pengajarId: string; -} diff --git a/src/entities/pengguna.entity.ts b/src/entities/pengguna.entity.ts index 7e4899c2764d11b3871fa2d7e502aed033a04406..c6011ee1dc0ec6adb74010bc2a21bae668198d23 100644 --- a/src/entities/pengguna.entity.ts +++ b/src/entities/pengguna.entity.ts @@ -1,28 +1,22 @@ -import { IsString } from "@nestjs/class-validator"; import { ApiHideProperty, ApiProperty, ApiPropertyOptional, } from "@nestjs/swagger"; import { Column, Entity, OneToMany, PrimaryGeneratedColumn } from "typeorm"; -import { MahasiswaKelas } from "./mahasiswaKelas.entity"; -import { PengajarKelas } from "./pengajarKelas.entity"; import { PendaftaranTesis } from "./pendaftaranTesis.entity"; -import { SubmisiTugas } from "./submisiTugas.entity"; +import { DosenBimbingan } from "./dosenBimbingan.entity"; export enum RoleEnum { ADMIN = "ADMIN", - TU = "TU", S2_MAHASISWA = "S2_MAHASISWA", S2_PEMBIMBING = "S2_PEMBIMBING", S2_PENGUJI = "S2_PENGUJI", S2_TIM_TESIS = "S2_TIM_TESIS", - S2_KULIAH = "S2_KULIAH", S1_MAHASISWA = "S1_MAHASISWA", S1_PEMBIMBING = "S1_PEMBIMBING", S1_PENGUJI = "S1_PENGUJI", S1_TIM_TA = "S1_TIM_TA", - S1_KULIAH = "S1_KULIAH", } @Entity() @@ -56,19 +50,37 @@ export class Pengguna { }) roles: RoleEnum[]; - @OneToMany(() => MahasiswaKelas, (mahasiswaKelas) => mahasiswaKelas.mahasiswa) - mahasiswaKelas: MahasiswaKelas[]; + @ApiPropertyOptional() + @Column({ type: "text", nullable: true }) + kontakWhatsApp: string; + + @ApiPropertyOptional() + @Column({ type: "text", nullable: true }) + kontakMsTeams: string; + + @ApiPropertyOptional() + @Column({ type: "text", nullable: true }) + kontakEmail: string; - @OneToMany(() => PengajarKelas, (pengajarKelas) => pengajarKelas.pengajar) - pengajarKelas: PengajarKelas[]; @ApiPropertyOptional() - @IsString() @Column({ type: "text", nullable: true }) - kontak: string; + kontakTelp: string; + + @ApiPropertyOptional() + @Column({ type: "text", nullable: true }) + kontakLainnya: string; + + @ApiPropertyOptional() + @Column({ type: "text", nullable: true }) + keahlian: string; + + @ApiHideProperty() + @Column({ type: "boolean", default: true }) + aktif: boolean; @OneToMany(() => PendaftaranTesis, (pendaftaran) => pendaftaran.mahasiswa) pendaftaranTesis: PendaftaranTesis[]; - @OneToMany(() => SubmisiTugas, (submisiTugas) => submisiTugas.mahasiswa) - submisiTugas: SubmisiTugas[]; + @OneToMany(() => DosenBimbingan, (dosen) => dosen.dosen) + dosenBimbingan: DosenBimbingan[]; } diff --git a/src/entities/ruangan.entity.ts b/src/entities/ruangan.entity.ts deleted file mode 100644 index 86dfaf804683751d50f397df3a55f00488dc0bfa..0000000000000000000000000000000000000000 --- a/src/entities/ruangan.entity.ts +++ /dev/null @@ -1,13 +0,0 @@ -// import { ApiProperty } from "@nestjs/swagger"; -// import { Column, Entity, PrimaryGeneratedColumn } from "typeorm"; - -// @Entity() -// export class Ruangan { -// @ApiProperty({ example: "550e8400-e29b-41d4-a716-446655440000" }) -// @PrimaryGeneratedColumn("uuid") -// id: string; - -// @ApiProperty() -// @Column({ type: "text" }) -// nama: string; -// } diff --git a/src/entities/submisiTugas.entity.ts b/src/entities/submisiTugas.entity.ts deleted file mode 100644 index be380d37d6c0b4dadf641f794735a343990e13b5..0000000000000000000000000000000000000000 --- a/src/entities/submisiTugas.entity.ts +++ /dev/null @@ -1,66 +0,0 @@ -import { - Column, - Entity, - JoinColumn, - ManyToOne, - OneToMany, - PrimaryGeneratedColumn, -} from "typeorm"; -import { Pengguna } from "./pengguna.entity"; -import { Tugas } from "./tugas.entity"; -import { BerkasSubmisiTugas } from "./berkasSubmisiTugas.entity"; -import { ApiProperty } from "@nestjs/swagger"; -import { IsString, IsUUID } from "class-validator"; -import { IsBoolean } from "@nestjs/class-validator"; - -@Entity() -export class SubmisiTugas { - @ApiProperty({ example: "550e8400-e29b-41d4-a716-446655440000" }) - @IsUUID() - @PrimaryGeneratedColumn("uuid") - id: string; - - @ManyToOne(() => Pengguna, (pengguna) => pengguna.id) - @JoinColumn({ name: "mahasiswaId" }) - mahasiswa: Pengguna; - - @ApiProperty({ example: "550e8400-e29b-41d4-a716-446655440000" }) - @IsUUID() - @Column() - mahasiswaId: string; - - @ApiProperty() - @IsString() - @Column({ type: "text" }) - jawaban: string; - - @ApiProperty({ description: "true means submitted, false means draft" }) - @IsBoolean() - @Column({ type: "boolean" }) - isSubmitted: boolean; // false means draft (saved), true means submitted - - @ApiProperty({ type: [BerkasSubmisiTugas] }) - @OneToMany( - () => BerkasSubmisiTugas, - (berkasSubmisiTugas) => berkasSubmisiTugas.submisiTugas, - { cascade: true }, - ) - berkasSubmisiTugas: BerkasSubmisiTugas[]; - - @ApiProperty() - @Column({ - type: "timestamptz", - default: () => "CURRENT_TIMESTAMP", - nullable: true, - }) - submittedAt: Date; - - @ManyToOne(() => Tugas, (tugas) => tugas.id) - @JoinColumn({ name: "tugasId" }) - tugas: Tugas; - - @ApiProperty({ example: "550e8400-e29b-41d4-a716-446655440000" }) - @IsUUID() - @Column() - tugasId: string; -} diff --git a/src/entities/topik.entity.ts b/src/entities/topik.entity.ts index 127540cc0619fa7863227bf11029f78160287a84..75bbc9703a7c3c994c434b72b9b392a047ff6300 100644 --- a/src/entities/topik.entity.ts +++ b/src/entities/topik.entity.ts @@ -1,4 +1,4 @@ -import { ApiProperty } from "@nestjs/swagger"; +import { ApiHideProperty, ApiProperty } from "@nestjs/swagger"; import { Column, Entity, @@ -31,7 +31,7 @@ export class Topik { @Column({ nullable: true }) idPengaju: string; - @ApiProperty() - @Column({ type: "text" }) - periode: string; + @ApiHideProperty() + @Column({ type: "boolean", default: true }) + aktif: boolean; } diff --git a/src/entities/tugas.entity.ts b/src/entities/tugas.entity.ts deleted file mode 100644 index 2424b87973fd01fe854e40ca50079dbc40e0f4ee..0000000000000000000000000000000000000000 --- a/src/entities/tugas.entity.ts +++ /dev/null @@ -1,88 +0,0 @@ -import { - Column, - Entity, - JoinColumn, - ManyToOne, - OneToMany, - PrimaryGeneratedColumn, -} from "typeorm"; -import { Kelas } from "./kelas.entity"; -import { Pengguna } from "./pengguna.entity"; -import { BerkasTugas } from "./berkasTugas.entity"; -import { ApiProperty } from "@nestjs/swagger"; -import { IsDateString, IsUUID, MaxLength } from "class-validator"; -import { IsString } from "@nestjs/class-validator"; -import { SubmisiTugas } from "./submisiTugas.entity"; - -@Entity() -export class Tugas { - @ApiProperty({ example: "550e8400-e29b-41d4-a716-446655440000" }) - @IsUUID() - @PrimaryGeneratedColumn("uuid") - id: string; - - @ManyToOne(() => Pengguna, (pengguna) => pengguna.id) - @JoinColumn({ name: "pembuatId" }) - pembuat: Pengguna; - - @ApiProperty({ example: "550e8400-e29b-41d4-a716-446655440000" }) - @IsUUID() - @Column() - pembuatId: string; - - @ManyToOne(() => Pengguna, (pengguna) => pengguna.id) - @JoinColumn({ name: "pengubahId" }) - pengubah: Pengguna; - - @ApiProperty({ example: "550e8400-e29b-41d4-a716-446655440000" }) - @IsUUID() - @Column() - pengubahId: string; - - @ApiProperty() - @IsString() - @MaxLength(256) - @Column({ type: "varchar", length: 256 }) - judul: string; - - @ApiProperty() - @IsDateString() - @Column({ type: "timestamptz", default: () => "CURRENT_TIMESTAMP" }) - waktuMulai: Date; - - @ApiProperty() - @IsDateString() - @Column({ type: "timestamptz" }) - waktuSelesai: Date; - - @ApiProperty() - @IsString() - @Column({ type: "text" }) - deskripsi: string; - - @ApiProperty({ type: [BerkasTugas] }) - @OneToMany(() => BerkasTugas, (berkasTugas) => berkasTugas.tugas, { - cascade: true, - }) - berkasTugas: BerkasTugas[]; - - @ApiProperty() - @Column({ type: "timestamptz", default: () => "CURRENT_TIMESTAMP" }) - createdAt: Date; - - @ApiProperty() - @Column({ type: "timestamptz", default: () => "CURRENT_TIMESTAMP" }) - updatedAt: Date; - - @ManyToOne(() => Kelas, (kelas) => kelas.id) - @JoinColumn({ name: "kelasId" }) - kelas: Kelas; - - @ApiProperty({ example: "550e8400-e29b-41d4-a716-446655440000" }) - @IsUUID() - @Column() - kelasId: string; - - @OneToMany(() => SubmisiTugas, (submisiTugas) => submisiTugas.tugas) - submisiTugas: SubmisiTugas[]; -} diff --git a/src/env.validation.ts b/src/env.validation.ts index e14a7cc35fd80b3e9bd3c0dbb3fbcbee7c31e157..1d5e6d037f02d9f54e2c3c7a0af08adc63785ff6 100644 --- a/src/env.validation.ts +++ b/src/env.validation.ts @@ -34,36 +34,6 @@ class EnvironmentVariables { @IsUrl({ require_tld: false }) FE_URL: string; - @IsString() - KONF_PERIODE_KEY: string; - - @IsString() - KONF_MIN_BIMBINGAN_KEY: string; - - @IsString() - KONF_AWAL_PENDAFTARAN_KEY: string; - - @IsString() - KONF_AKHIR_PENDAFTARAN_KEY: string; - - @IsString() - KONF_AWAL_SEMPRO_KEY: string; - - @IsString() - KONF_AKHIR_SEMPRO_KEY: string; - - @IsString() - KONF_AWAL_SEM_TESIS_KEY: string; - - @IsString() - KONF_AKHIR_SEM_TESIS_KEY: string; - - @IsString() - KONF_AWAL_SIDANG_KEY: string; - - @IsString() - KONF_AKHIR_SIDANG_KEY: string; - @IsString() COOKIE_NAME: string; } diff --git a/src/helper/roles.ts b/src/helper/roles.ts new file mode 100644 index 0000000000000000000000000000000000000000..690f29dd520df4c348a4520cfc75aee48540dca5 --- /dev/null +++ b/src/helper/roles.ts @@ -0,0 +1,7 @@ +import { RoleEnum } from "src/entities/pengguna.entity"; + +export const HIGH_AUTHORITY_ROLES = [RoleEnum.ADMIN, RoleEnum.S2_TIM_TESIS]; + +export function isHighAuthority(roles: RoleEnum[]) { + return roles.some((role) => HIGH_AUTHORITY_ROLES.includes(role)); +} diff --git a/src/helper/validation.ts b/src/helper/validation.ts deleted file mode 100644 index cfc65eb722c4859bd1569749bccb0e8785721b77..0000000000000000000000000000000000000000 --- a/src/helper/validation.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { NotFoundException } from "@nestjs/common"; -import { validate as uuidValidate } from "uuid"; - -interface ID { - id: string; - object: string; -} - -export function validateId(items: ID[]) { - for (const item of items) { - const isValidUUID = uuidValidate(item.id); - - if (!isValidUUID) { - throw new NotFoundException(`${item.object} not found.`); - } - } -} diff --git a/src/kelas/kelas.constant.ts b/src/kelas/kelas.constant.ts deleted file mode 100644 index 050cbbca5914f9eb24c9e646e62e2629d047a51b..0000000000000000000000000000000000000000 --- a/src/kelas/kelas.constant.ts +++ /dev/null @@ -1,20 +0,0 @@ -export const CARD_COLORS = [ - "bg-blue-600/20", - "bg-yellow-600/20", - "bg-green-600/20", - "bg-red-600/20", - "bg-purple-600/20", - "bg-pink-600/20", - "bg-indigo-600/20", - "bg-cyan-600/20", - "bg-amber-600/20", - "bg-lime-600/20", - "bg-emerald-600/20", - "bg-teal-600/20", - "bg-cyan-600/20", - "bg-violet-600/20", - "bg-fuchsia-600/20", - "bg-rose-600/20", - "bg-sky-600/20", - "bg-cyan-600/20", -]; diff --git a/src/kelas/kelas.controller.ts b/src/kelas/kelas.controller.ts deleted file mode 100644 index 3c64b3d4c8c22e85d157f750ee961ee852f9cf6d..0000000000000000000000000000000000000000 --- a/src/kelas/kelas.controller.ts +++ /dev/null @@ -1,314 +0,0 @@ -import { - Body, - Controller, - Delete, - ForbiddenException, - Get, - Param, - Post, - Put, - Query, - Req, - UseGuards, -} from "@nestjs/common"; -import { - AssignKelasDto, - ByIdKelasDto, - CreateKelasDto, - DeleteKelasDto, - GetKelasDetailRespDto, - GetKelasQueryDto, - GetKelasRespDto, - GetNextNomorResDto, - IdKelasResDto, - KodeRespDto, - MessageResDto, - UnassignKelasDto, - UserKelasResDto, - UpdateKelasDto, - SearchQueryDto, - UpdateKelasPenggunaDto, -} from "./kelas.dto"; -import { Request } from "express"; -import { AuthDto } from "src/auth/auth.dto"; -import { RoleEnum } from "src/entities/pengguna.entity"; -import { - ApiBadRequestResponse, - ApiBearerAuth, - ApiCookieAuth, - ApiCreatedResponse, - ApiInternalServerErrorResponse, - ApiNotFoundResponse, - ApiOkResponse, - ApiOperation, - ApiTags, -} from "@nestjs/swagger"; -import { CustomAuthGuard } from "src/middlewares/custom-auth.guard"; -import { RolesGuard } from "src/middlewares/roles.guard"; -import { KelasService } from "./kelas.service"; -import { Roles } from "src/middlewares/roles.decorator"; -import { MataKuliah } from "src/entities/mataKuliah.entity"; -import { Kelas } from "src/entities/kelas.entity"; - -@ApiTags("Kelas") -@ApiBearerAuth() -@ApiCookieAuth() -@Controller("kelas") -@UseGuards(CustomAuthGuard, RolesGuard) -export class KelasController { - constructor(private readonly kelasServ: KelasService) {} - - @ApiOperation({ - summary: - "Get list of kelas. Roles: S2_KULIAH, S2_MAHASISWA, S2_TIM_TESIS, ADMIN", - }) - @ApiOkResponse({ type: GetKelasRespDto, isArray: true }) - @Roles( - RoleEnum.S2_KULIAH, - RoleEnum.S2_MAHASISWA, - RoleEnum.S2_TIM_TESIS, - RoleEnum.ADMIN, - ) - @Get() - async getListKelas(@Query() query: GetKelasQueryDto, @Req() req: Request) { - let idMahasiswa = undefined; - let idPengajar = undefined; - - const { id, roles } = req.user as AuthDto; - - if (!roles.includes(query.view)) { - throw new ForbiddenException(); - } - - if (query.view === RoleEnum.S2_KULIAH) { - idPengajar = id; - } - - if (query.view === RoleEnum.S2_MAHASISWA) { - idMahasiswa = id; - } - - return await this.kelasServ.getListKelas( - idMahasiswa, - idPengajar, - query.kodeMatkul, - query.search, - ); - } - - @ApiOkResponse({ type: MataKuliah, isArray: true }) - @Get("/mata-kuliah") - async getAllMatkul() { - return await this.kelasServ.getAllMatkul(); - } - - @Roles(RoleEnum.S2_TIM_TESIS, RoleEnum.ADMIN) - @ApiOkResponse({ type: GetNextNomorResDto }) - @Get("/next-nomor/:kodeMatkul") - async getNextNomor( - @Param("kodeMatkul") kodeMatkul: string, - ): Promise<GetNextNomorResDto> { - const nomor = await this.kelasServ.getNextNomorKelas(kodeMatkul); - - return { - nomor, - }; - } - - @Roles(RoleEnum.S2_TIM_TESIS, RoleEnum.ADMIN) - @ApiOkResponse({ type: IdKelasResDto }) - @ApiBadRequestResponse({ - description: "Nomor kelas sudah ada", - }) - @ApiInternalServerErrorResponse({ - description: "Gagal membuat kelas", - }) - @Post() - async createKelas(@Body() body: CreateKelasDto): Promise<IdKelasResDto> { - return await this.kelasServ.create(body); - } - - // TODO: restrict payload - @Roles(RoleEnum.S2_TIM_TESIS, RoleEnum.ADMIN) - @ApiOkResponse({ type: IdKelasResDto }) - @ApiNotFoundResponse({ - description: "Kelas dengan id (dan nomor) yang terkait tidak ditemukan", - }) - @ApiBadRequestResponse({ - description: - "(Saat pembuatan kelas) nomor kelas sudah ada atau mataKuliahKode tidak ada", - }) - @ApiInternalServerErrorResponse({ - description: "Gagal memperbarui kelas atau gagal membuat kelas", - }) - @Put() - async updateKelas(@Body() body: UpdateKelasDto): Promise<IdKelasResDto> { - return await this.kelasServ.updateOrCreate(body); - } - - @ApiCreatedResponse({ type: KodeRespDto }) - @Roles(RoleEnum.S2_TIM_TESIS, RoleEnum.ADMIN) - @Post("mata-kuliah") - async createMataKuliah(@Body() body: MataKuliah) { - return await this.kelasServ.createMatkul(body); - } - - @ApiOkResponse({ type: Kelas }) - @ApiNotFoundResponse({ description: "Kelas tidak ditemukan" }) - @ApiInternalServerErrorResponse({ description: "Gagal menghapus kelas" }) - @Delete() - async delete(@Body() body: DeleteKelasDto): Promise<Kelas> { - return await this.kelasServ.delete(body); - } - - @Roles(RoleEnum.S2_TIM_TESIS, RoleEnum.ADMIN) - @ApiOkResponse({ type: UserKelasResDto, isArray: true }) - @Get("/mahasiswa") - async getMahasiswa( - @Query() query: SearchQueryDto, - ): Promise<UserKelasResDto[]> { - return await this.kelasServ.getKelasPengguna("MAHASISWA", query.search); - } - - @Roles(RoleEnum.S2_TIM_TESIS, RoleEnum.ADMIN) - @ApiCreatedResponse({ type: MessageResDto }) - @ApiInternalServerErrorResponse({ description: "Gagal menambahkan kelas" }) - @Post("/mahasiswa/assign") - async assignKelasMahasiswa( - @Body() body: AssignKelasDto, - ): Promise<MessageResDto> { - return await this.kelasServ.assignKelasMahasiswa(body); - } - - @Roles(RoleEnum.S2_TIM_TESIS, RoleEnum.ADMIN) - @ApiOkResponse({ type: MessageResDto }) - @ApiInternalServerErrorResponse({ description: "Gagal menghapus kelas" }) - @Delete("/mahasiswa/unassign") - async unassignKelasMahasiswa( - @Body() body: UnassignKelasDto, - ): Promise<MessageResDto> { - return await this.kelasServ.unassignKelasMahasiswa(body); - } - - @Roles(RoleEnum.S2_TIM_TESIS, RoleEnum.ADMIN) - @ApiOkResponse({ type: UserKelasResDto, isArray: true }) - @Get("/dosen") - async getDosen(@Query() query: SearchQueryDto): Promise<UserKelasResDto[]> { - return await this.kelasServ.getKelasPengguna("DOSEN", query.search); - } - - @Roles(RoleEnum.S2_TIM_TESIS, RoleEnum.ADMIN) - @ApiCreatedResponse({ type: MessageResDto }) - @ApiInternalServerErrorResponse({ description: "Gagal menambahkan kelas" }) - @Post("/dosen/assign") - async assignKelasDosen(@Body() body: AssignKelasDto): Promise<MessageResDto> { - return await this.kelasServ.assignKelasDosen(body); - } - - @Roles(RoleEnum.S2_TIM_TESIS, RoleEnum.ADMIN) - @ApiOkResponse({ type: MessageResDto }) - @ApiInternalServerErrorResponse({ description: "Gagal menghapus kelas" }) - @Delete("/dosen/unassign") - async unassignKelasDosen( - @Body() body: UnassignKelasDto, - ): Promise<MessageResDto> { - return await this.kelasServ.unassignKelasDosen(body); - } - - @Roles( - RoleEnum.S2_TIM_TESIS, - RoleEnum.ADMIN, - RoleEnum.S2_KULIAH, - RoleEnum.S2_MAHASISWA, - ) - @ApiOkResponse({ type: GetKelasRespDto }) - @ApiOperation({ - summary: - "Get kelas general information by kelas id. Roles: S2_KULIAH, S2_MAHASISWA, S2_TIM_TESIS, ADMIN", - }) - @Get("/:id") - async getById( - @Param() param: ByIdKelasDto, - @Req() req: Request, - ): Promise<GetKelasRespDto> { - let idMahasiswa = undefined; - let idPengajar = undefined; - - const { id, roles } = req.user as AuthDto; - - if ( - !roles.includes(RoleEnum.S2_TIM_TESIS) && - !roles.includes(RoleEnum.ADMIN) - ) { - if (roles.includes(RoleEnum.S2_KULIAH)) { - idPengajar = id; - } else { - // requester only has S2_MAHASISWA access - idMahasiswa = id; - } - } - - return await this.kelasServ.getById(param.id, idMahasiswa, idPengajar); - } - - @Roles( - RoleEnum.S2_TIM_TESIS, - RoleEnum.ADMIN, - RoleEnum.S2_KULIAH, - RoleEnum.S2_MAHASISWA, - ) - @ApiOperation({ - summary: - "Get kelas mahasiswa and pengajar list by kelas id. Roles: S2_KULIAH, S2_MAHASISWA, S2_TIM_TESIS, ADMIN", - }) - @ApiOkResponse({ type: GetKelasDetailRespDto }) - @Get("/:id/detail") - async getKelasDetail(@Param() param: ByIdKelasDto, @Req() req: Request) { - let idMahasiswa = undefined; - let idPengajar = undefined; - - const { id, roles } = req.user as AuthDto; - - if ( - !roles.includes(RoleEnum.S2_TIM_TESIS) && - !roles.includes(RoleEnum.ADMIN) - ) { - if (roles.includes(RoleEnum.S2_KULIAH)) { - idPengajar = id; - } else { - // requester only has S2_MAHASISWA access - idMahasiswa = id; - } - } - - return await this.kelasServ.getKelasDetail( - param.id, - idMahasiswa, - idPengajar, - ); - } - - @Roles(RoleEnum.S2_TIM_TESIS, RoleEnum.ADMIN) - @ApiOperation({ - summary: "Update kelas mahasiswa. Roles: S2_TIM_TESIS, ADMIN", - }) - @ApiOkResponse({ type: ByIdKelasDto }) - @Put("/mahasiswa") - async updateKelasMahasiswa( - @Body() body: UpdateKelasPenggunaDto, - ): Promise<ByIdKelasDto> { - return await this.kelasServ.updateKelas(body, "MAHASISWA"); - } - - @Roles(RoleEnum.S2_TIM_TESIS, RoleEnum.ADMIN) - @ApiOperation({ - summary: "Update kelas dosen. Roles: S2_TIM_TESIS, ADMIN", - }) - @ApiOkResponse({ type: ByIdKelasDto }) - @Put("/dosen") - async updateKelasDosen( - @Body() body: UpdateKelasPenggunaDto, - ): Promise<ByIdKelasDto> { - return await this.kelasServ.updateKelas(body, "DOSEN"); - } -} diff --git a/src/kelas/kelas.dto.ts b/src/kelas/kelas.dto.ts deleted file mode 100644 index 3647164851b0a08f38fda3c651db4cc17587c086..0000000000000000000000000000000000000000 --- a/src/kelas/kelas.dto.ts +++ /dev/null @@ -1,147 +0,0 @@ -import { - IsEnum, - IsOptional, - IsPositive, - IsUUID, -} from "@nestjs/class-validator"; -import { - ApiProperty, - PickType, - PartialType, - ApiPropertyOptional, -} from "@nestjs/swagger"; -import { Kelas } from "src/entities/kelas.entity"; -import { MataKuliah } from "src/entities/mataKuliah.entity"; -import { Pengguna, RoleEnum } from "src/entities/pengguna.entity"; - -export class CreateKelasDto extends PickType(Kelas, [ - "mataKuliahKode", -] as const) { - @ApiPropertyOptional({ example: 1 }) - @IsOptional() - @IsPositive() - nomor: number; -} - -export class UpdateKelasDto extends PartialType(Kelas) {} - -export class IdKelasResDto extends PickType(Kelas, ["id"] as const) {} - -export class DeleteKelasDto extends PickType(Kelas, [ - "mataKuliahKode", - "nomor", -] as const) {} - -export class GetKelasQueryDto { - @IsEnum([RoleEnum.S2_KULIAH, RoleEnum.S2_MAHASISWA, RoleEnum.S2_TIM_TESIS]) - @ApiProperty({ - enum: [RoleEnum.S2_KULIAH, RoleEnum.S2_MAHASISWA, RoleEnum.S2_TIM_TESIS], - }) - view: RoleEnum.S2_KULIAH | RoleEnum.S2_MAHASISWA | RoleEnum.S2_TIM_TESIS; - - @ApiPropertyOptional({ example: "IF3270" }) - @IsOptional() - kodeMatkul: string; - - @ApiPropertyOptional({ example: "Intelegensi Buatan" }) - @IsOptional() - search: string; -} - -export class SearchQueryDto { - @ApiPropertyOptional({ example: "Intelegensi Buatan" }) - @IsOptional() - search: string; -} - -export class ByIdKelasDto extends PickType(Kelas, ["id"] as const) {} - -export class GetKelasRespDto { - @ApiProperty() - id: string; - - @ApiProperty({ example: "K02" }) - nomor: string; - - @ApiProperty({ example: "IF3270" }) - kode_mata_kuliah: string; - - @ApiProperty({ example: "Pengembangan Aplikasi Terdistribusi" }) - nama_mata_kuliah: string; - - @ApiProperty() - jumlah_mahasiswa: number; - - @ApiProperty({ example: "bg-blue-600/20" }) - warna: string; -} - -export class KodeRespDto extends PickType(MataKuliah, ["kode"] as const) {} - -export class AssignKelasDto { - @ApiProperty() - @IsUUID("all", { each: true }) - kelasIds: string[]; - - @ApiProperty() - @IsUUID("all", { each: true }) - penggunaIds: string[]; -} - -export class UnassignKelasDto extends PickType(AssignKelasDto, [ - "penggunaIds", -] as const) {} - -export class MessageResDto { - @ApiProperty() - message: string; -} - -class KelasUser extends PickType(Kelas, [ - "id", - "nomor", - "mataKuliahKode", -] as const) { - @ApiProperty() - mataKuliahNama: string; -} - -export class UserKelasResDto extends PickType(Pengguna, [ - "id", - "nama", - "email", -] as const) { - @ApiProperty({ type: [KelasUser] }) - kelas: KelasUser[]; -} -export class GetNextNomorResDto { - @ApiProperty({ example: 2 }) - nomor: number; -} - -class PickedPengajarKelasDto extends PickType(Pengguna, [ - "id", - "nama", -] as const) {} - -class PickedMahasiswaKelasDto extends PickType(Pengguna, [ - "id", - "nama", - "nim", -] as const) {} - -export class GetKelasDetailRespDto extends PickType(Kelas, ["id"] as const) { - @ApiProperty({ type: [PickedPengajarKelasDto] }) - pengajar: PickedPengajarKelasDto[]; - - @ApiProperty({ type: [PickedMahasiswaKelasDto] }) - mahasiswa: PickedMahasiswaKelasDto[]; -} - -export class UpdateKelasPenggunaDto extends PickType(AssignKelasDto, [ - "kelasIds" as const, -]) { - @ApiProperty({ example: "550e8400-e29b-41d4-a716-446655440000" }) - @IsUUID() - penggunaId: string; -} diff --git a/src/kelas/kelas.module.ts b/src/kelas/kelas.module.ts deleted file mode 100644 index dde13c556b1ffb854621c5394945ace1ed81225c..0000000000000000000000000000000000000000 --- a/src/kelas/kelas.module.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { Module } from "@nestjs/common"; -import { KelasService } from "./kelas.service"; -import { TypeOrmModule } from "@nestjs/typeorm"; -import { Kelas } from "src/entities/kelas.entity"; -import { AuthModule } from "src/auth/auth.module"; -import { KonfigurasiModule } from "src/konfigurasi/konfigurasi.module"; -import { KelasController } from "./kelas.controller"; -import { CustomStrategy } from "src/middlewares/custom.strategy"; -import { MataKuliah } from "src/entities/mataKuliah.entity"; -import { Pengguna } from "src/entities/pengguna.entity"; -import { MahasiswaKelas } from "src/entities/mahasiswaKelas.entity"; -import { PengajarKelas } from "src/entities/pengajarKelas.entity"; - -@Module({ - imports: [ - TypeOrmModule.forFeature([ - Kelas, - MataKuliah, - Pengguna, - MahasiswaKelas, - PengajarKelas, - ]), - AuthModule, - KonfigurasiModule, - ], - controllers: [KelasController], - providers: [KelasService, CustomStrategy], -}) -export class KelasModule {} diff --git a/src/kelas/kelas.service.ts b/src/kelas/kelas.service.ts deleted file mode 100644 index f55b832ce066eb60d33e6e2ce1900ddbd9de5cbe..0000000000000000000000000000000000000000 --- a/src/kelas/kelas.service.ts +++ /dev/null @@ -1,708 +0,0 @@ -import { - BadRequestException, - Injectable, - InternalServerErrorException, - NotFoundException, -} from "@nestjs/common"; -import { InjectRepository } from "@nestjs/typeorm"; -import { Kelas } from "src/entities/kelas.entity"; -import { Brackets, DataSource, Repository } from "typeorm"; -import { - CreateKelasDto, - AssignKelasDto, - DeleteKelasDto, - GetKelasDetailRespDto, - GetKelasRespDto, - IdKelasResDto, - UpdateKelasDto, - MessageResDto, - UnassignKelasDto, - UpdateKelasPenggunaDto, - ByIdKelasDto, -} from "./kelas.dto"; -import { KonfigurasiService } from "src/konfigurasi/konfigurasi.service"; -import { MataKuliah } from "src/entities/mataKuliah.entity"; -import { CARD_COLORS } from "./kelas.constant"; -import { Pengguna } from "src/entities/pengguna.entity"; -import { MahasiswaKelas } from "src/entities/mahasiswaKelas.entity"; -import { PengajarKelas } from "src/entities/pengajarKelas.entity"; - -@Injectable() -export class KelasService { - constructor( - @InjectRepository(Kelas) - private kelasRepo: Repository<Kelas>, - @InjectRepository(MataKuliah) - private mataKuliahRepo: Repository<MataKuliah>, - private konfService: KonfigurasiService, - @InjectRepository(Pengguna) - private penggunaRepo: Repository<Pengguna>, - @InjectRepository(MahasiswaKelas) - private mahasiswaKelasRepo: Repository<MahasiswaKelas>, - @InjectRepository(PengajarKelas) - private pengajarKelasRepo: Repository<PengajarKelas>, - private datasource: DataSource, - ) {} - async getListKelas( - idMahasiswa?: string, - idPengajar?: string, - kodeMatkul?: string, - search?: string, - ) { - const currPeriod = await this.konfService.getPeriodeOrFail(); - - let baseQuery = this.kelasRepo - .createQueryBuilder("kelas") - .leftJoinAndSelect("kelas.mahasiswa", "mahasiswa") - .leftJoinAndSelect("kelas.mataKuliah", "mataKuliah") - .select([ - "kelas.id AS id", - "kelas.nomor AS nomor", - "kelas.warna AS warna", - "mataKuliah.kode AS kode_mata_kuliah", - "mataKuliah.nama AS nama_mata_kuliah", - "COUNT(mahasiswa) AS jumlah_mahasiswa", - ]) - .orderBy("mataKuliah.kode", "ASC") - .addOrderBy("kelas.nomor", "ASC") - .where("kelas.periode = :periode", { periode: currPeriod }); - - if (idMahasiswa) { - baseQuery = baseQuery - .innerJoin("kelas.mahasiswa", "mahasiswa_filter") - .andWhere("mahasiswa_filter.mahasiswaId = :idMahasiswa", { - idMahasiswa, - }); - } - - if (idPengajar) { - baseQuery = baseQuery - .innerJoin("kelas.pengajar", "pengajar") - .andWhere("pengajar.pengajarId = :idPengajar", { - idPengajar, - }); - } - - if (kodeMatkul) { - baseQuery = baseQuery.andWhere("mataKuliah.kode = :kodeMatkul", { - kodeMatkul, - }); - } - - if (search) { - baseQuery = baseQuery.andWhere( - new Brackets((qb) => { - qb.where("mataKuliah.kode ILIKE :search", { - search: `%${search}%`, - }).orWhere("mataKuliah.nama ILIKE :search", { - search: `%${search}%`, - }); - }), - ); - } - - const result = await baseQuery - .groupBy("kelas.id, mataKuliah.kode") - .getRawMany(); - - const mapped: GetKelasRespDto[] = result.map((r) => ({ - id: r.id, - nomor: "K" + `${r.nomor}`.padStart(2, "0"), - kode_mata_kuliah: r.kode_mata_kuliah, - nama_mata_kuliah: r.nama_mata_kuliah, - jumlah_mahasiswa: parseInt(r.jumlah_mahasiswa), - warna: r.warna, - })); - - return mapped; - } - - async getById(id: string, idMahasiswa?: string, idPengajar?: string) { - const currPeriod = await this.konfService.getPeriodeOrFail(); - - let baseQuery = this.kelasRepo - .createQueryBuilder("kelas") - .leftJoinAndSelect("kelas.mahasiswa", "mahasiswa") - .leftJoinAndSelect("kelas.mataKuliah", "mataKuliah") - .select([ - "kelas.id AS id", - "kelas.nomor AS nomor", - "kelas.warna AS warna", - "mataKuliah.kode AS kode_mata_kuliah", - "mataKuliah.nama AS nama_mata_kuliah", - "COUNT(mahasiswa) AS jumlah_mahasiswa", - ]) - .where("kelas.id = :id", { id }) - .andWhere("kelas.periode = :periode", { periode: currPeriod }); - - if (idMahasiswa) { - baseQuery = baseQuery - .innerJoin("kelas.mahasiswa", "mahasiswa_filter") - .andWhere("mahasiswa_filter.mahasiswaId = :idMahasiswa", { - idMahasiswa, - }); - } - - if (idPengajar) { - baseQuery = baseQuery - .innerJoin("kelas.pengajar", "pengajar") - .andWhere("pengajar.pengajarId = :idPengajar", { - idPengajar, - }); - } - - const result = await baseQuery - .groupBy("kelas.id, mataKuliah.kode") - .getRawOne(); - - if (!result) { - throw new NotFoundException("Kelas tidak ditemukan"); - } - - const mapped: GetKelasRespDto = { - id: result.id, - nomor: "K" + `${result.nomor}`.padStart(2, "0"), - kode_mata_kuliah: result.kode_mata_kuliah, - nama_mata_kuliah: result.nama_mata_kuliah, - jumlah_mahasiswa: parseInt(result.jumlah_mahasiswa), - warna: result.warna, - }; - - return mapped; - } - - async getKelasDetail( - idKelas: string, - idMahasiswa?: string, - idPengajar?: string, - ) { - const currPeriod = await this.konfService.getPeriodeOrFail(); - - let baseQuery = this.kelasRepo - .createQueryBuilder("kelas") - .leftJoinAndSelect("kelas.mahasiswa", "mahasiswaKelas") - .leftJoinAndSelect("mahasiswaKelas.mahasiswa", "mahasiswa") - .leftJoinAndSelect("kelas.pengajar", "pengajarKelas") - .leftJoinAndSelect("pengajarKelas.pengajar", "pengajar") - .select([ - "kelas.id", - "mahasiswaKelas.id", - "mahasiswa.id", - "mahasiswa.nim", - "mahasiswa.nama", - "pengajarKelas.id", - "pengajar.id", - "pengajar.nama", - ]) - .orderBy("pengajar.nama", "ASC") - .addOrderBy("mahasiswa.nim", "ASC") - .where("kelas.id = :idKelas", { idKelas }) - .andWhere("kelas.periode = :periode", { periode: currPeriod }); - - if (idMahasiswa) { - baseQuery = baseQuery - .innerJoin("kelas.mahasiswa", "mahasiswaFilter") - .andWhere("mahasiswaFilter.mahasiswaId = :idMahasiswa", { - idMahasiswa, - }); - } - - if (idPengajar) { - baseQuery = baseQuery - .innerJoin("kelas.pengajar", "pengajarFilter") - .andWhere("pengajarFilter.pengajarId = :idPengajar", { - idPengajar, - }); - } - - const result = await baseQuery.getOne(); - - if (!result) { - throw new NotFoundException( - "Kelas tidak ditemukan di antara kelas yang dapat Anda akses", - ); - } - - const mapped: GetKelasDetailRespDto = { - id: result.id, - pengajar: result.pengajar.map((p) => ({ - id: p.pengajar.id, - nama: p.pengajar.nama, - })), - mahasiswa: result.mahasiswa.map((m) => ({ - id: m.mahasiswa.id, - nim: m.mahasiswa.nim, - nama: m.mahasiswa.nama, - })), - }; - - return mapped; - } - - async create(createDto: CreateKelasDto): Promise<IdKelasResDto> { - const currPeriod = await this.konfService.getPeriodeOrFail(); - - let nomor = createDto.nomor; - if (nomor) { - const checkClassQueary = this.kelasRepo - .createQueryBuilder("kelas") - .where("kelas.nomor = :nomor", { nomor }) - .andWhere("kelas.mataKuliahKode = :mataKuliahKode", { - mataKuliahKode: createDto.mataKuliahKode, - }) - .andWhere("kelas.periode = :periode", { periode: currPeriod }); - - const checkClass = await checkClassQueary.getOne(); - - if (checkClass) { - throw new BadRequestException(`Kelas dengan nomor ${nomor} sudah ada`); - } - } else { - nomor = await this.getNextNomorKelas(createDto.mataKuliahKode); - } - - const colorIdx = Math.floor(Math.random() * CARD_COLORS.length); - const kelas = this.kelasRepo.create({ - ...createDto, - nomor, - periode: currPeriod, - warna: CARD_COLORS[colorIdx], - }); - - try { - await this.kelasRepo.save(kelas); - } catch { - throw new InternalServerErrorException("Gagal membuat kelas baru"); - } - - return { - id: kelas.id, - }; - } - - async createMatkul(createDto: MataKuliah) { - await this.mataKuliahRepo.insert(createDto); - - return { kode: createDto.kode }; - } - - async getKelasPengguna( - mode: "MAHASISWA" | "DOSEN", - search?: string, - id?: string, - ) { - const currPeriod = await this.konfService.getKonfigurasiByKey( - process.env.KONF_PERIODE_KEY, - ); - - if (!currPeriod) { - throw new BadRequestException("Periode belum dikonfigurasi"); - } - - const relation = mode === "MAHASISWA" ? "mahasiswaKelas" : "pengajarKelas"; - - let penggunaQuery = this.penggunaRepo - .createQueryBuilder("pengguna") - .select(["pengguna.id", "pengguna.nama", "pengguna.email"]) - .leftJoinAndSelect(`pengguna.${relation}`, relation) - .leftJoinAndSelect( - `${relation}.kelas`, - "kelas", - "kelas.periode = :periode", - { - periode: currPeriod, - }, - ) - .leftJoinAndSelect("kelas.mataKuliah", "mataKuliah") - .where("pengguna.roles @> :role", { - role: [mode === "MAHASISWA" ? "S2_MAHASISWA" : "S2_KULIAH"], - }); - - if (search) { - penggunaQuery = penggunaQuery.andWhere( - new Brackets((qb) => { - qb.where("pengguna.nama ILIKE :search", { search: `%${search}%` }); - qb.orWhere("pengguna.email ILIKE :search", { search: `%${search}%` }); - }), - ); - } - - if (id) { - penggunaQuery = penggunaQuery.andWhere("pengguna.id = :id", { id }); - } - - const mhs = await penggunaQuery.getMany(); - - return mhs.map((m) => ({ - id: m.id, - nama: m.nama, - email: m.email, - kelas: m?.[relation].map((k) => ({ - id: k.kelas.id, - nomor: k.kelas.nomor, - mataKuliahKode: k.kelas.mataKuliahKode, - mataKuliahNama: k.kelas.mataKuliah.nama, - })), - })); - } - - async assignKelasMahasiswa(dto: AssignKelasDto): Promise<MessageResDto> { - const currPeriod = await this.konfService.getKonfigurasiByKey( - process.env.KONF_PERIODE_KEY, - ); - - if (!currPeriod) { - throw new BadRequestException("Periode belum dikonfigurasi"); - } - - const queryRunner = this.datasource.createQueryRunner(); - - await queryRunner.connect(); - await queryRunner.startTransaction(); - try { - for (const mhsId of dto.penggunaIds) { - const currKelasQuery = queryRunner.manager - .createQueryBuilder(MahasiswaKelas, "mahasiswaKelas") - .leftJoinAndSelect("mahasiswaKelas.kelas", "kelas") - .where("mahasiswaKelas.mahasiswaId = :mhsId", { mhsId }) - .andWhere("kelas.periode = :periode", { periode: currPeriod }); - - const currKelas = (await currKelasQuery.getMany()).map( - (k) => k.kelasId, - ); - - for (const kelasId of dto.kelasIds) { - if (currKelas.includes(kelasId)) { - continue; - } - - await queryRunner.manager.insert(MahasiswaKelas, { - mahasiswaId: mhsId, - kelasId, - }); - } - } - - await queryRunner.commitTransaction(); - } catch { - await queryRunner.rollbackTransaction(); - - throw new InternalServerErrorException("Gagal menambahkan kelas"); - } finally { - await queryRunner.release(); - } - - return { message: "Kelas berhasil diassign" }; - } - - async unassignKelasMahasiswa(dto: UnassignKelasDto): Promise<MessageResDto> { - const currPeriod = await this.konfService.getKonfigurasiByKey( - process.env.KONF_PERIODE_KEY, - ); - - if (!currPeriod) { - throw new BadRequestException("Periode belum dikonfigurasi"); - } - - const queryRunner = this.datasource.createQueryRunner(); - - await queryRunner.connect(); - await queryRunner.startTransaction(); - try { - for (const mhsId of dto.penggunaIds) { - const currKelasQuery = queryRunner.manager - .createQueryBuilder(MahasiswaKelas, "mahasiswaKelas") - .leftJoinAndSelect("mahasiswaKelas.kelas", "kelas") - .where("mahasiswaKelas.mahasiswaId = :mhsId", { mhsId }) - .andWhere("kelas.periode = :periode", { periode: currPeriod }); - - const currKelas = (await currKelasQuery.getMany()).map( - (k) => k.kelasId, - ); - - for (const kelasId of currKelas) { - await queryRunner.manager.delete(MahasiswaKelas, { - mahasiswaId: mhsId, - kelasId, - }); - } - } - - await queryRunner.commitTransaction(); - } catch { - await queryRunner.rollbackTransaction(); - - throw new InternalServerErrorException("Gagal menghapus kelas"); - } finally { - await queryRunner.release(); - } - - return { message: "Kelas berhasil dihapus" }; - } - - async assignKelasDosen(dto: AssignKelasDto): Promise<MessageResDto> { - const currPeriod = await this.konfService.getKonfigurasiByKey( - process.env.KONF_PERIODE_KEY, - ); - - if (!currPeriod) { - throw new BadRequestException("Periode belum dikonfigurasi"); - } - - const queryRunner = this.datasource.createQueryRunner(); - - await queryRunner.connect(); - await queryRunner.startTransaction(); - try { - for (const dosenId of dto.penggunaIds) { - const currKelasQuery = queryRunner.manager - .createQueryBuilder(PengajarKelas, "pengajarKelas") - .leftJoinAndSelect("pengajarKelas.kelas", "kelas") - .where("pengajarKelas.pengajarId = :dosenId", { dosenId }) - .andWhere("kelas.periode = :periode", { periode: currPeriod }); - - const currKelas = (await currKelasQuery.getMany()).map( - (k) => k.kelasId, - ); - - for (const kelasId of dto.kelasIds) { - if (currKelas.includes(kelasId)) { - continue; - } - - await queryRunner.manager.insert(PengajarKelas, { - pengajarId: dosenId, - kelasId, - }); - } - } - - await queryRunner.commitTransaction(); - } catch { - await queryRunner.rollbackTransaction(); - - throw new InternalServerErrorException("Gagal menambahkan kelas"); - } finally { - await queryRunner.release(); - } - - return { message: "Kelas berhasil diassign" }; - } - - async unassignKelasDosen(dto: UnassignKelasDto): Promise<MessageResDto> { - const currPeriod = await this.konfService.getKonfigurasiByKey( - process.env.KONF_PERIODE_KEY, - ); - - if (!currPeriod) { - throw new BadRequestException("Periode belum dikonfigurasi"); - } - - const queryRunner = this.datasource.createQueryRunner(); - await queryRunner.connect(); - await queryRunner.startTransaction(); - try { - for (const dosenId of dto.penggunaIds) { - const currKelasQuery = queryRunner.manager - .createQueryBuilder(PengajarKelas, "pengajarKelas") - .leftJoinAndSelect("pengajarKelas.kelas", "kelas") - .where("pengajarKelas.pengajarId = :dosenId", { dosenId }) - .andWhere("kelas.periode = :periode", { periode: currPeriod }); - - const currKelas = (await currKelasQuery.getMany()).map( - (k) => k.kelasId, - ); - - for (const kelasId of currKelas) { - await queryRunner.manager.delete(PengajarKelas, { - pengajarId: dosenId, - kelasId, - }); - } - } - - await queryRunner.commitTransaction(); - } catch { - await queryRunner.rollbackTransaction(); - - throw new InternalServerErrorException("Gagal menghapus kelas"); - } finally { - await queryRunner.release(); - } - - return { message: "Kelas berhasil dihapus" }; - } - - async updateOrCreate(dto: UpdateKelasDto): Promise<IdKelasResDto> { - const currPeriod = await this.konfService.getPeriodeOrFail(); - - if (!dto.id) { - // Create kelas - if (!dto.mataKuliahKode) { - throw new BadRequestException("Kode mata kuliah tidak boleh kosong"); - } - - return await this.create({ - mataKuliahKode: dto.mataKuliahKode, - nomor: dto.nomor, - }); - } else { - // Update kelas - const kelasQuery = this.kelasRepo - .createQueryBuilder("kelas") - .where("kelas.id = :id", { id: dto.id }) - .andWhere("kelas.periode = :periode", { periode: currPeriod }); - - if (dto.nomor) { - kelasQuery.andWhere("kelas.nomor = :nomor", { nomor: dto.nomor }); - } - - const kelas = await kelasQuery.getOne(); - - if (!kelas) { - throw new NotFoundException("Kelas tidak ditemukan"); - } - - try { - await this.kelasRepo.update(kelas.id, dto); - } catch { - throw new InternalServerErrorException("Gagal memperbarui kelas"); - } - - return { - id: kelas.id, - }; - } - } - - async delete(dto: DeleteKelasDto): Promise<Kelas> { - const currPeriod = await this.konfService.getPeriodeOrFail(); - - const kelasQuery = this.kelasRepo - .createQueryBuilder("kelas") - .where("kelas.nomor = :nomor", { nomor: dto.nomor }) - .andWhere("kelas.mataKuliahKode = :mataKuliahKode", { - mataKuliahKode: dto.mataKuliahKode, - }) - .andWhere("kelas.periode = :periode", { periode: currPeriod }); - - const kelas = await kelasQuery.getOne(); - - if (!kelas) { - throw new BadRequestException("Kelas tidak ditemukan"); - } - - try { - await this.kelasRepo.delete(kelas.id); - } catch { - throw new InternalServerErrorException("Gagal menghapus kelas"); - } - - return kelas; - } - - async getAllMatkul(): Promise<MataKuliah[]> { - return await this.mataKuliahRepo.find(); - } - - async getNextNomorKelas(kodeMatkul: string): Promise<number> { - const currPeriod = await this.konfService.getPeriodeOrFail(); - - const maxClass = await this.kelasRepo.findOne({ - where: { - mataKuliahKode: kodeMatkul, - periode: currPeriod, - }, - order: { - nomor: "DESC", - }, - }); - - return maxClass ? maxClass.nomor + 1 : 1; - } - - async updateKelas( - dto: UpdateKelasPenggunaDto, - mode: "MAHASISWA" | "DOSEN", - ): Promise<ByIdKelasDto> { - const currPeriod = await this.konfService.getKonfigurasiByKey( - process.env.KONF_PERIODE_KEY, - ); - - if (!currPeriod) { - throw new BadRequestException("Periode belum dikonfigurasi"); - } - - const queryRunner = this.datasource.createQueryRunner(); - - await queryRunner.connect(); - await queryRunner.startTransaction(); - try { - if (mode === "MAHASISWA") { - const currKelasQuery = queryRunner.manager - .createQueryBuilder(MahasiswaKelas, "mahasiswaKelas") - .leftJoinAndSelect("mahasiswaKelas.kelas", "kelas") - .where("mahasiswaKelas.mahasiswaId = :mhsId", { - mhsId: dto.penggunaId, - }) - .andWhere("kelas.periode = :periode", { periode: currPeriod }); - - const currKelas = (await currKelasQuery.getMany()).map( - (k) => k.kelasId, - ); - - // Delete all kelas mahasiswa - for (const kelasId of currKelas) { - await queryRunner.manager.delete(MahasiswaKelas, { - mahasiswaId: dto.penggunaId, - kelasId, - }); - } - - // Assign kelas mahasiswa - for (const kelasId of dto.kelasIds) { - await queryRunner.manager.insert(MahasiswaKelas, { - mahasiswaId: dto.penggunaId, - kelasId, - }); - } - } else { - const currKelasQuery = queryRunner.manager - .createQueryBuilder(PengajarKelas, "pengajarKelas") - .leftJoinAndSelect("pengajarKelas.kelas", "kelas") - .where("pengajarKelas.pengajarId = :dosenId", { - dosenId: dto.penggunaId, - }) - .andWhere("kelas.periode = :periode", { periode: currPeriod }); - - const currKelas = (await currKelasQuery.getMany()).map( - (k) => k.kelasId, - ); - - for (const kelasId of currKelas) { - await queryRunner.manager.delete(PengajarKelas, { - pengajarId: dto.penggunaId, - kelasId, - }); - } - - for (const kelasId of dto.kelasIds) { - await queryRunner.manager.insert(PengajarKelas, { - pengajarId: dto.penggunaId, - kelasId, - }); - } - } - - await queryRunner.commitTransaction(); - } catch { - await queryRunner.rollbackTransaction(); - - throw new InternalServerErrorException("Gagal mengupdate kelas pengguna"); - } finally { - await queryRunner.release(); - } - - return { id: dto.penggunaId }; - } -} diff --git a/src/konfigurasi/konfigurasi.controller.ts b/src/konfigurasi/konfigurasi.controller.ts index 8f5b99b300caba5f8c9d09d5b032feaa08f613ab..caf6a97a7950a431351f9d130913c7be03783420 100644 --- a/src/konfigurasi/konfigurasi.controller.ts +++ b/src/konfigurasi/konfigurasi.controller.ts @@ -26,8 +26,7 @@ export class KonfigurasiController { async updateKonfigurasi( @Body() data: KonfigurasiArrDto, ): Promise<UpdateKonfigurasiResDto> { - await this.konfigurasiService.updateKonfigurasi(data); - return { message: "success" }; + return await this.konfigurasiService.updateKonfigurasi(data); } @ApiOkResponse({ type: KonfigurasiArrDto }) diff --git a/src/konfigurasi/konfigurasi.dto.ts b/src/konfigurasi/konfigurasi.dto.ts index 0de2f06b9f82d10df61c2c475ab1e56a26d3342a..1ebdd10da2f421f1aabdd53afe02b26466269032 100644 --- a/src/konfigurasi/konfigurasi.dto.ts +++ b/src/konfigurasi/konfigurasi.dto.ts @@ -1,24 +1,19 @@ import { ApiProperty } from "@nestjs/swagger"; import { Type } from "class-transformer"; -import { IsString, ValidateNested } from "class-validator"; +import { ValidateNested } from "class-validator"; +import { + Konfigurasi, + KonfigurasiKeyEnum, +} from "src/entities/konfigurasi.entity"; -export class KonfigurasiDto { - @ApiProperty() - @IsString() - key: string; - - @ApiProperty() - @IsString() - value: string; -} export class KonfigurasiArrDto { - @ApiProperty({ type: [KonfigurasiDto] }) + @ApiProperty({ type: [Konfigurasi] }) @ValidateNested({ each: true }) - @Type(() => KonfigurasiDto) - data: KonfigurasiDto[]; + @Type(() => Konfigurasi) + data: Konfigurasi[]; } export class UpdateKonfigurasiResDto { - @ApiProperty() - message: string; + @ApiProperty({ enum: KonfigurasiKeyEnum, isArray: true }) + keys: KonfigurasiKeyEnum[]; } diff --git a/src/konfigurasi/konfigurasi.service.ts b/src/konfigurasi/konfigurasi.service.ts index a8e7edaccbcafeeab8aa59f9039ce69ae88a21c2..6702c21184cc55ec1124dc165b0abd80abf4ad43 100644 --- a/src/konfigurasi/konfigurasi.service.ts +++ b/src/konfigurasi/konfigurasi.service.ts @@ -1,8 +1,11 @@ -import { BadRequestException, Injectable } from "@nestjs/common"; +import { Injectable } from "@nestjs/common"; import { InjectRepository } from "@nestjs/typeorm"; -import { Konfigurasi } from "src/entities/konfigurasi.entity"; +import { + Konfigurasi, + KonfigurasiKeyEnum, +} from "src/entities/konfigurasi.entity"; import { Repository } from "typeorm"; -import { KonfigurasiArrDto } from "./konfigurasi.dto"; +import { KonfigurasiArrDto, UpdateKonfigurasiResDto } from "./konfigurasi.dto"; @Injectable() export class KonfigurasiService { @@ -11,16 +14,25 @@ export class KonfigurasiService { private konfigurasiRepository: Repository<Konfigurasi>, ) {} - async updateKonfigurasi({ data }: KonfigurasiArrDto) { - return await this.konfigurasiRepository.upsert(data, ["key"]); + async updateKonfigurasi({ + data, + }: KonfigurasiArrDto): Promise<UpdateKonfigurasiResDto> { + await this.konfigurasiRepository.upsert(data, ["key"]); + const res = { + keys: data.map((d) => d.key), + }; + return res; } async getKonfigurasi(): Promise<KonfigurasiArrDto> { const data = await this.konfigurasiRepository.find(); + return { data }; } - async getKonfigurasiByKey(key: string): Promise<string | undefined> { + async getKonfigurasiByKey( + key: KonfigurasiKeyEnum, + ): Promise<string | undefined> { const data = await this.konfigurasiRepository.findOne({ where: { key, @@ -29,16 +41,4 @@ export class KonfigurasiService { return data?.value; } - - async getPeriodeOrFail() { - const currPeriod = await this.getKonfigurasiByKey( - process.env.KONF_PERIODE_KEY, - ); - - if (!currPeriod) { - throw new BadRequestException("Periode belum dikonfigurasi"); - } - - return currPeriod; - } } diff --git a/src/main.ts b/src/main.ts index fdce89e3372e4a1ea2d1a7bf5006ee2054c3f487..8f39d1c33675cd4f871fe6271f9bfbd301979f3a 100644 --- a/src/main.ts +++ b/src/main.ts @@ -25,12 +25,8 @@ async function bootstrap() { .addTag("Bimbingan") .addTag("Dashboard") .addTag("Dosen Bimbingan") - .addTag("Kelas") .addTag("Konfigurasi") - .addTag("Nilai") .addTag("Registrasi Tesis") - .addTag("Submisi Tugas") - .addTag("Tugas") .addCookieAuth(process.env.COOKIE_NAME) .addBearerAuth() .build(); diff --git a/src/nilai/nilai.controller.ts b/src/nilai/nilai.controller.ts deleted file mode 100644 index 6bb1cb659a4ad0347e37c69df0ad85750da2d1ad..0000000000000000000000000000000000000000 --- a/src/nilai/nilai.controller.ts +++ /dev/null @@ -1,45 +0,0 @@ -import { Body, Controller, Get, Patch, Query, UseGuards } from "@nestjs/common"; -import { NilaiService } from "./nilai.service"; -import { - ApiBearerAuth, - ApiCookieAuth, - ApiOkResponse, - ApiTags, -} from "@nestjs/swagger"; -import { CustomAuthGuard } from "src/middlewares/custom-auth.guard"; -import { RolesGuard } from "src/middlewares/roles.guard"; -import { Roles } from "src/middlewares/roles.decorator"; -import { RoleEnum } from "src/entities/pengguna.entity"; -import { - GetNilaiByMatkulQueryDto, - GetNilaiByMatkulRespDto, - UpdateNilaiDto, - UpdateNilaiRespDto, -} from "./nilai.dto"; - -@ApiBearerAuth() -@ApiCookieAuth() -@ApiTags("Nilai") -@UseGuards(CustomAuthGuard, RolesGuard) -@Roles(RoleEnum.ADMIN, RoleEnum.S2_TIM_TESIS) -@Controller("nilai") -export class NilaiController { - constructor(private nilaiServ: NilaiService) {} - - @ApiOkResponse({ type: [GetNilaiByMatkulRespDto] }) - @Get() - async getNilaiByMatkul(@Query() query: GetNilaiByMatkulQueryDto) { - return this.nilaiServ.getNilaiByMatkul( - query.kode, - query.page || 1, - query.limit || 10, - query.search || "", - ); - } - - @ApiOkResponse({ type: UpdateNilaiRespDto }) - @Patch() - async updateNilai(@Body() body: UpdateNilaiDto) { - return this.nilaiServ.updateNilai(body.mahasiswaKelasIds, body.nilaiAkhir); - } -} diff --git a/src/nilai/nilai.dto.ts b/src/nilai/nilai.dto.ts deleted file mode 100644 index 1b933d07468212dbfacd8fc41d441227b5084418..0000000000000000000000000000000000000000 --- a/src/nilai/nilai.dto.ts +++ /dev/null @@ -1,78 +0,0 @@ -import { IsNumberString } from "@nestjs/class-validator"; -import { ApiProperty, ApiPropertyOptional } from "@nestjs/swagger"; -import { - IsNumber, - IsOptional, - IsPositive, - IsString, - IsUUID, - MaxLength, - MinLength, -} from "class-validator"; - -export class GetNilaiByMatkulQueryDto { - @ApiPropertyOptional({ example: "IF4031" }) - @IsOptional() - @IsString() - @MinLength(6) - @MaxLength(6) - kode?: string; - - @ApiPropertyOptional() - @IsOptional() - @IsString() - search?: string; - - @IsOptional() - @IsNumberString() - @ApiPropertyOptional({ description: "default: 1" }) - page?: number; - - @IsOptional() - @IsNumberString() - @ApiPropertyOptional({ description: "default: 10" }) - limit?: number; -} - -export class GetNilaiByMatkulRespDto { - @ApiProperty({ example: "IF4031" }) - mata_kuliah_kode: string; - - @ApiProperty({ example: "Pemrograman Berbasis Kerangka Kerja" }) - mata_kuliah_nama: string; - - @ApiProperty() - kelas_nomor: number; - - @ApiProperty({ example: "550e8400-e29b-41d4-a716-446655440000" }) - mahasiswa_kelas_id: string; - - @ApiProperty({ example: "550e8400-e29b-41d4-a716-446655440000" }) - mahasiswa_id: string; - - @ApiProperty() - mahasiswa_nama: string; - - @ApiProperty({ example: "13517000" }) - mahasiswa_nim: string; - - @ApiProperty() - nilai_akhir: string | null; -} - -export class UpdateNilaiRespDto { - @ApiProperty({ - type: [String], - example: ["550e8400-e29b-41d4-a716-446655440000"], - }) - @IsUUID("all", { each: true }) - mahasiswaKelasIds: string[]; -} - -export class UpdateNilaiDto extends UpdateNilaiRespDto { - @ApiPropertyOptional({ description: "undefined: assign nilai to null" }) - @IsOptional() - @IsNumber() - @IsPositive() - nilaiAkhir?: number; -} diff --git a/src/nilai/nilai.module.ts b/src/nilai/nilai.module.ts deleted file mode 100644 index c0a7c9c19ed393373632c2a8f3f8f5c6cc5610a8..0000000000000000000000000000000000000000 --- a/src/nilai/nilai.module.ts +++ /dev/null @@ -1,35 +0,0 @@ -import { Module } from "@nestjs/common"; -import { TypeOrmModule } from "@nestjs/typeorm"; -import { AuthModule } from "src/auth/auth.module"; -import { Kelas } from "src/entities/kelas.entity"; -import { Konfigurasi } from "src/entities/konfigurasi.entity"; -import { MahasiswaKelas } from "src/entities/mahasiswaKelas.entity"; -import { MataKuliah } from "src/entities/mataKuliah.entity"; -import { KelasModule } from "src/kelas/kelas.module"; -import { KonfigurasiModule } from "src/konfigurasi/konfigurasi.module"; -import { NilaiController } from "./nilai.controller"; -import { NilaiService } from "./nilai.service"; -import { CustomStrategy } from "src/middlewares/custom.strategy"; -import { KonfigurasiService } from "src/konfigurasi/konfigurasi.service"; -import { KelasService } from "src/kelas/kelas.service"; -import { Pengguna } from "src/entities/pengguna.entity"; -import { PengajarKelas } from "src/entities/pengajarKelas.entity"; - -@Module({ - imports: [ - TypeOrmModule.forFeature([ - MahasiswaKelas, - Kelas, - MataKuliah, - Konfigurasi, - Pengguna, - PengajarKelas, - ]), - AuthModule, - KonfigurasiModule, - KelasModule, - ], - controllers: [NilaiController], - providers: [NilaiService, CustomStrategy, KelasService, KonfigurasiService], -}) -export class NilaiModule {} diff --git a/src/nilai/nilai.service.ts b/src/nilai/nilai.service.ts deleted file mode 100644 index 973251cd9ba63a7be3a5aa0ef6a04903d8f77e54..0000000000000000000000000000000000000000 --- a/src/nilai/nilai.service.ts +++ /dev/null @@ -1,97 +0,0 @@ -import { Injectable } from "@nestjs/common"; -import { InjectRepository } from "@nestjs/typeorm"; -import { Kelas } from "src/entities/kelas.entity"; -import { MahasiswaKelas } from "src/entities/mahasiswaKelas.entity"; -import { MataKuliah } from "src/entities/mataKuliah.entity"; -import { KonfigurasiService } from "src/konfigurasi/konfigurasi.service"; -import { Brackets, In, Repository } from "typeorm"; -import { GetNilaiByMatkulRespDto, UpdateNilaiRespDto } from "./nilai.dto"; - -@Injectable() -export class NilaiService { - constructor( - @InjectRepository(MahasiswaKelas) - private mhsKelasRepo: Repository<MahasiswaKelas>, - @InjectRepository(Kelas) - private kelasRepo: Repository<Kelas>, - @InjectRepository(MataKuliah) - private mataKuliahRepo: Repository<MataKuliah>, - private konfServ: KonfigurasiService, - ) {} - - private async isMhsKelasOrFail(mhsKelasIds: string[]) { - const periode = await this.konfServ.getPeriodeOrFail(); - - const mhsKelas = await this.mhsKelasRepo.find({ - select: { id: true }, - where: { id: In(mhsKelasIds), kelas: { periode } }, - }); - - for (const mhsKelasId of mhsKelasIds) { - if (!mhsKelas.find((mk) => mk.id === mhsKelasId)) { - throw new Error(`Mahasiswa kelas ${mhsKelasId} tidak ditemukan`); - } - } - } - - async getNilaiByMatkul( - mataKuliahKode: string, - page: number, - limit: number, - search: string, - ) { - const currPeriode = await this.konfServ.getPeriodeOrFail(); - - const baseQuery = this.mataKuliahRepo - .createQueryBuilder("matkul") - .select([ - "matkul.kode AS mata_kuliah_kode", - "matkul.nama AS mata_kuliah_nama", - "kelas.nomor AS kelas_nomor", - "mhsKelas.id AS mahasiswa_kelas_id", - "mahasiswa.id AS mahasiswa_id", - "mahasiswa.nama AS mahasiswa_nama", - "mahasiswa.nim AS mahasiswa_nim", - "mhsKelas.nilaiAkhir AS nilai_akhir", - ]) - .innerJoin("matkul.kelas", "kelas") - .leftJoin("kelas.mahasiswa", "mhsKelas") - .innerJoin("mhsKelas.mahasiswa", "mahasiswa") - .where("kelas.periode = :periode", { periode: currPeriode }) - .andWhere( - new Brackets((qb) => { - qb.where("mahasiswa.nama ILIKE :search", { - search: `%${search}%`, - }).orWhere("mahasiswa.nim ILIKE :search", { search: `%${search}%` }); - }), - ); - - if (mataKuliahKode) { - baseQuery.andWhere("matkul.kode = :kode", { kode: mataKuliahKode }); - } - - const mhsKelas: GetNilaiByMatkulRespDto[] = await baseQuery - .orderBy("matkul.kode") - .addOrderBy("kelas.nomor") - .addOrderBy("mahasiswa.nim") - .skip((page - 1) * limit) - .limit(limit) - .getRawMany(); - - return mhsKelas; - } - - async updateNilai( - mhsKelasIds: string[], - nilaiAkhir?: number, - ): Promise<UpdateNilaiRespDto> { - await this.isMhsKelasOrFail(mhsKelasIds); - - await this.mhsKelasRepo.update( - { id: In(mhsKelasIds) }, - { nilaiAkhir: nilaiAkhir ?? null }, - ); - - return { mahasiswaKelasIds: mhsKelasIds }; - } -} diff --git a/src/pengguna/pengguna.module.ts b/src/pengguna/pengguna.module.ts new file mode 100644 index 0000000000000000000000000000000000000000..3e5276cbb4849fcc7453b7b2618834fcd3a2f3c7 --- /dev/null +++ b/src/pengguna/pengguna.module.ts @@ -0,0 +1,11 @@ +import { Module } from "@nestjs/common"; +import { PenggunaService } from "./pengguna.service"; +import { TypeOrmModule } from "@nestjs/typeorm"; +import { Pengguna } from "src/entities/pengguna.entity"; + +@Module({ + imports: [TypeOrmModule.forFeature([Pengguna])], + providers: [PenggunaService], + exports: [PenggunaService], +}) +export class PenggunaModule {} diff --git a/src/pengguna/pengguna.service.ts b/src/pengguna/pengguna.service.ts new file mode 100644 index 0000000000000000000000000000000000000000..28281715431f789c616d7213ec8a8cea18b7c80c --- /dev/null +++ b/src/pengguna/pengguna.service.ts @@ -0,0 +1,24 @@ +import { Injectable, NotFoundException } from "@nestjs/common"; +import { InjectRepository } from "@nestjs/typeorm"; +import { Pengguna, RoleEnum } from "src/entities/pengguna.entity"; +import { ArrayContains, Repository } from "typeorm"; + +@Injectable() +export class PenggunaService { + constructor( + @InjectRepository(Pengguna) + private penggunaRepo: Repository<Pengguna>, + ) {} + + async isMahasiswaAktifOrFail(id: string) { + const mhs = await this.penggunaRepo.findOneBy({ + id, + aktif: true, + roles: ArrayContains([RoleEnum.S2_MAHASISWA]), + }); + + if (!mhs) { + throw new NotFoundException("Mahasiswa aktif tidak ditemukan"); + } + } +} diff --git a/src/registrasi-tesis/registrasi-tesis.controller.ts b/src/registrasi-tesis/registrasi-tesis.controller.ts index 9802752f5de61d55f75b2b89791038abc827c2e2..0ac28aece930cb6f6c9ea7ec037540d9ec3d5f1e 100644 --- a/src/registrasi-tesis/registrasi-tesis.controller.ts +++ b/src/registrasi-tesis/registrasi-tesis.controller.ts @@ -1,5 +1,4 @@ import { - BadRequestException, Body, Controller, ForbiddenException, @@ -24,7 +23,6 @@ import { import { Request } from "express"; import { AuthDto } from "src/auth/auth.dto"; import { RoleEnum } from "src/entities/pengguna.entity"; -import { KonfigurasiService } from "src/konfigurasi/konfigurasi.service"; import { CustomAuthGuard } from "src/middlewares/custom-auth.guard"; import { Roles } from "src/middlewares/roles.decorator"; import { RolesGuard } from "src/middlewares/roles.guard"; @@ -51,11 +49,10 @@ import { RegistrasiTesisService } from "./registrasi-tesis.service"; export class RegistrasiTesisController { constructor( private readonly registrasiTesisService: RegistrasiTesisService, - private readonly konfService: KonfigurasiService, ) {} @ApiOperation({ - summary: "Create new registration. Roles: S2_MAHASISWA, ADMIN", + summary: "Create new registration. Roles: S2_MAHASISWA", }) @ApiCreatedResponse({ type: IdDto }) @ApiNotFoundResponse({ description: "Penerima atau topik tidak ditemukan" }) @@ -64,7 +61,7 @@ export class RegistrasiTesisController { "Mahasiswa sedang memiliki pendaftaran aktif atau judul dan deskripsi topik baru tidak ada", }) @UseGuards(CustomAuthGuard, RolesGuard) - @Roles(RoleEnum.S2_MAHASISWA, RoleEnum.ADMIN) + @Roles(RoleEnum.S2_MAHASISWA) @Post() async createTopicRegistration( @Body() topicRegistrationDto: RegDto, @@ -72,18 +69,9 @@ export class RegistrasiTesisController { ): Promise<IdDto> { const { id } = req.user as AuthDto; - const periode = await this.konfService.getKonfigurasiByKey( - process.env.KONF_PERIODE_KEY, - ); - - if (!periode) { - throw new BadRequestException("Periode belum dikonfigurasi."); - } - return this.registrasiTesisService.createTopicRegistration( id, topicRegistrationDto, - periode, ); } @@ -108,17 +96,8 @@ export class RegistrasiTesisController { } } - const periode = await this.konfService.getKonfigurasiByKey( - process.env.KONF_PERIODE_KEY, - ); - - if (!periode) { - throw new BadRequestException("Periode belum dikonfigurasi."); - } - return this.registrasiTesisService.findByUserId( params.mahasiswaId, - periode, false, undefined, ); @@ -147,17 +126,8 @@ export class RegistrasiTesisController { idPenerima = id; } - const periode = await this.konfService.getKonfigurasiByKey( - process.env.KONF_PERIODE_KEY, - ); - - if (!periode) { - throw new BadRequestException("Periode belum dikonfigurasi."); - } - const res = await this.registrasiTesisService.findByUserId( params.mahasiswaId, - periode, true, idPenerima, ); @@ -180,12 +150,7 @@ export class RegistrasiTesisController { throw new ForbiddenException(); } - const periode = await this.konfService.getKonfigurasiByKey( - process.env.KONF_PERIODE_KEY, - ); - return this.registrasiTesisService.getRegsStatistics({ - periode, idPenerima: query.view == RoleEnum.S2_PEMBIMBING ? idPenerima : undefined, }); } @@ -211,20 +176,11 @@ export class RegistrasiTesisController { throw new ForbiddenException(); } - const periode = await this.konfService.getKonfigurasiByKey( - process.env.KONF_PERIODE_KEY, - ); - - if (!periode) { - throw new BadRequestException("Periode belum dikonfigurasi."); - } - return await this.registrasiTesisService.findAllRegs({ ...query, page: query.page || 1, idPenerima: query.view === RoleEnum.S2_PEMBIMBING ? idPenerima : undefined, - periode, }); } @@ -241,14 +197,6 @@ export class RegistrasiTesisController { @Body() body: UpdateInterviewBodyDto, @Req() req: Request, ) { - const periode = await this.konfService.getKonfigurasiByKey( - process.env.KONF_PERIODE_KEY, - ); - - if (!periode) { - throw new BadRequestException("Periode belum dikonfigurasi."); - } - const { id, roles } = req.user as AuthDto; let idPenerima = undefined; @@ -262,7 +210,6 @@ export class RegistrasiTesisController { return await this.registrasiTesisService.updateInterviewDate( params.mhsId, - periode, body, idPenerima, ); @@ -281,14 +228,6 @@ export class RegistrasiTesisController { @Body() body: UpdateStatusBodyDto, @Req() req: Request, ) { - const periode = await this.konfService.getKonfigurasiByKey( - process.env.KONF_PERIODE_KEY, - ); - - if (!periode) { - throw new BadRequestException("Periode belum dikonfigurasi."); - } - const { id, roles } = req.user as AuthDto; let idPenerima = undefined; @@ -302,7 +241,6 @@ export class RegistrasiTesisController { return await this.registrasiTesisService.updateStatus( params.mhsId, - periode, body, idPenerima, ); @@ -320,17 +258,8 @@ export class RegistrasiTesisController { @Param() params: UpdateByMhsParamsDto, @Body() body: UpdatePembimbingBodyDto, ) { - const periode = await this.konfService.getKonfigurasiByKey( - process.env.KONF_PERIODE_KEY, - ); - - if (!periode) { - throw new BadRequestException("Periode belum dikonfigurasi."); - } - return await this.registrasiTesisService.updatePembimbingList( params.mhsId, - periode, body, ); } diff --git a/src/registrasi-tesis/registrasi-tesis.dto.ts b/src/registrasi-tesis/registrasi-tesis.dto.ts index cc9541b2956aedc63449aa5144a9b87c34f6e0ba..7d5d1bf284f8957d52f75af71505fad198e74b52 100644 --- a/src/registrasi-tesis/registrasi-tesis.dto.ts +++ b/src/registrasi-tesis/registrasi-tesis.dto.ts @@ -171,7 +171,11 @@ export class UpdatePembimbingBodyDto { class DosenPembimbingDto extends PickType(Pengguna, [ "id", "nama", - "kontak", + "kontakWhatsApp", + "kontakMsTeams", + "kontakEmail", + "kontakTelp", + "kontakLainnya", ] as const) {} export class GetByIdRespDto extends PickType(PendaftaranTesis, [ diff --git a/src/registrasi-tesis/registrasi-tesis.module.ts b/src/registrasi-tesis/registrasi-tesis.module.ts index 65926b077ae351036dcd06e380b17bec135f42bc..0b64d44cec70b955ad05a008eedd82f63760e1c3 100644 --- a/src/registrasi-tesis/registrasi-tesis.module.ts +++ b/src/registrasi-tesis/registrasi-tesis.module.ts @@ -8,7 +8,8 @@ import { RegistrasiTesisService } from "./registrasi-tesis.service"; import { Topik } from "src/entities/topik.entity"; import { CustomStrategy } from "src/middlewares/custom.strategy"; import { AuthModule } from "src/auth/auth.module"; -import { KonfigurasiModule } from "src/konfigurasi/konfigurasi.module"; +import { PenggunaModule } from "src/pengguna/pengguna.module"; +import { PenggunaService } from "src/pengguna/pengguna.service"; @Module({ imports: [ @@ -19,9 +20,9 @@ import { KonfigurasiModule } from "src/konfigurasi/konfigurasi.module"; Topik, ]), AuthModule, - KonfigurasiModule, + PenggunaModule, ], controllers: [RegistrasiTesisController], - providers: [RegistrasiTesisService, CustomStrategy], + providers: [RegistrasiTesisService, CustomStrategy, PenggunaService], }) export class RegistrasiTesisModule {} diff --git a/src/registrasi-tesis/registrasi-tesis.service.ts b/src/registrasi-tesis/registrasi-tesis.service.ts index e19127b3878b6411462d75d1a7126a6d565ae723..17e59c6096fcf7f65aff48a21dda444938e8515e 100644 --- a/src/registrasi-tesis/registrasi-tesis.service.ts +++ b/src/registrasi-tesis/registrasi-tesis.service.ts @@ -6,6 +6,7 @@ import { NotFoundException, } from "@nestjs/common"; import { InjectRepository } from "@nestjs/typeorm"; +import * as dayjs from "dayjs"; import { DosenBimbingan } from "src/entities/dosenBimbingan.entity"; import { PendaftaranTesis, @@ -24,7 +25,7 @@ import { UpdatePembimbingBodyDto, UpdateStatusBodyDto, } from "./registrasi-tesis.dto"; -import * as dayjs from "dayjs"; +import { PenggunaService } from "src/pengguna/pengguna.service"; @Injectable() export class RegistrasiTesisService { @@ -38,26 +39,24 @@ export class RegistrasiTesisService { @InjectRepository(DosenBimbingan) private dosenBimbinganRepository: Repository<DosenBimbingan>, private dataSource: DataSource, + private penggunaService: PenggunaService, ) {} async createTopicRegistration( userId: string, topicRegistrationDto: RegDto, - periode: string, ): Promise<IdDto> { const queries: ( | Promise<void | PendaftaranTesis> | Promise<Pengguna> | Promise<Topik> )[] = [ - this.getNewestRegByMhsOrFail(userId, periode).catch( - (ex: BadRequestException) => { - if (ex.message === "No mahasiswa user with given id exists") { - throw ex; - } - // else: mahasiswa does not have pending registration -> allowed - }, - ), + this.getNewestRegByMhsOrFail(userId).catch((ex: BadRequestException) => { + if (ex.message === "No mahasiswa user with given id exists") { + throw ex; + } + // else: mahasiswa does not have pending registration -> allowed + }), this.penggunaRepository.findOne({ where: { id: topicRegistrationDto.idPenerima }, }), @@ -80,8 +79,14 @@ export class RegistrasiTesisService { throw new NotFoundException("Penerima not found."); } - if (topicRegistrationDto.idTopik && !topik) { - throw new NotFoundException("Topic not found."); + if (topicRegistrationDto.idTopik) { + if (!topik) { + throw new NotFoundException("Topic not found."); + } + + if (!topik.aktif) { + throw new BadRequestException("Topic is not active."); + } } if (lastPendaftaran && lastPendaftaran.status !== RegStatus.REJECTED) { @@ -104,7 +109,6 @@ export class RegistrasiTesisService { judul: topicRegistrationDto.judulTopik, deskripsi: topicRegistrationDto.deskripsiTopik, idPengaju: userId, - periode, }); } @@ -125,32 +129,42 @@ export class RegistrasiTesisService { async findByUserId( mahasiswaId: string, - periode: string, isNewestOnly: boolean, idPenerima?: string, ) { + await this.penggunaService.isMahasiswaAktifOrFail(mahasiswaId); + const baseQuery = this.pendaftaranTesisRepository .createQueryBuilder("pt") - .select("pt.id") - .addSelect("pt.jadwalInterview") - .addSelect("pt.status") - .addSelect("pt.jalurPilihan") - .addSelect("pt.waktuPengiriman") - .addSelect("topik.judul") - .addSelect("penerima.id") - .addSelect("penerima.nama") - .addSelect("dosenBimbingan") - .addSelect("dosen.id") - .addSelect("dosen.nama") - .addSelect("dosen.kontak") - .addSelect("topik.judul") - .addSelect("topik.deskripsi") + .select([ + "pt.id", + "pt.jadwalInterview", + "pt.status", + "pt.jalurPilihan", + "pt.waktuPengiriman", + "topik.judul", + "topik.deskripsi", + "penerima.id", + "penerima.nama", + "penerima.kontakWhatsApp", + "penerima.kontakMsTeams", + "penerima.kontakEmail", + "penerima.kontakTelp", + "penerima.kontakLainnya", + "dosenBimbingan", + "dosen.id", + "dosen.nama", + "dosen.kontakWhatsApp", + "dosen.kontakMsTeams", + "dosen.kontakEmail", + "dosen.kontakTelp", + "dosen.kontakLainnya", + ]) .leftJoin("pt.topik", "topik") .leftJoin("pt.penerima", "penerima") .leftJoin("pt.dosenBimbingan", "dosenBimbingan") .leftJoin("dosenBimbingan.dosen", "dosen") .where("pt.mahasiswaId = :mahasiswaId", { mahasiswaId }) - .andWhere("topik.periode = :periode", { periode }) .orderBy("pt.waktuPengiriman", "DESC"); const res = await baseQuery.getMany(); @@ -193,10 +207,9 @@ export class RegistrasiTesisService { } async getRegsStatistics(options: { - periode: string; idPenerima?: string; }): Promise<RegStatisticsRespDto> { - const totalMahasiswa = this.penggunaRepository.count({ + let totalMahasiswa = this.penggunaRepository.count({ where: { roles: ArrayContains([RoleEnum.S2_MAHASISWA]) }, }); @@ -215,13 +228,15 @@ export class RegistrasiTesisService { "latest", "latest.latest_mahasiswaId = pt.mahasiswaId AND pt.waktuPengiriman = latest.latestPengiriman", ) - .innerJoinAndSelect("pt.topik", "topik") - .where("topik.periode = :periode", { periode: options.periode }); + .innerJoin("pt.mahasiswa", "mahasiswa") + .where("mahasiswa.aktif = true"); if (options.idPenerima) { baseQuery.andWhere("pt.penerimaId = :idPenerima", { idPenerima: options.idPenerima, }); + + totalMahasiswa = baseQuery.getCount(); } const totalDiterima = baseQuery @@ -272,7 +287,6 @@ export class RegistrasiTesisService { search?: string; order_by?: "nim"; sort?: "ASC" | "DESC"; - periode: string; }) { const baseQuery = this.pendaftaranTesisRepository .createQueryBuilder("pt") @@ -294,10 +308,9 @@ export class RegistrasiTesisService { ); baseQuery - .innerJoinAndSelect("pt.topik", "topik") .innerJoinAndSelect("pt.penerima", "penerima") .innerJoinAndSelect("pt.mahasiswa", "mahasiswa") - .where("topik.periode = :periode", { periode: options.periode }); + .where("mahasiswa.aktif = true"); if (options.idPenerima) { baseQuery.andWhere("pt.penerimaId = :idPenerima", { @@ -305,7 +318,7 @@ export class RegistrasiTesisService { }); } - if (options.search) + if (options.search) { baseQuery.andWhere( new Brackets((qb) => qb @@ -317,11 +330,11 @@ export class RegistrasiTesisService { }), ), ); + } - if (options.status) - baseQuery.andWhere("pt.status = :status", { - status: options.status, - }); + if (options.status) { + baseQuery.andWhere("pt.status = :status", { status: options.status }); + } if (options.order_by) { const orderByMapping = { @@ -360,7 +373,7 @@ export class RegistrasiTesisService { return resData; } - private async getNewestRegByMhsOrFail(mahasiswaId: string, periode: string) { + private async getNewestRegByMhsOrFail(mahasiswaId: string) { const mahasiswa = await this.penggunaRepository.findOne({ select: { id: true, @@ -384,7 +397,6 @@ export class RegistrasiTesisService { topik: { judul: true, deskripsi: true, - periode: true, }, penerima: { id: true, @@ -397,9 +409,6 @@ export class RegistrasiTesisService { }, where: { mahasiswa: mahasiswa, - topik: { - periode, - }, }, order: { waktuPengiriman: "DESC", @@ -416,10 +425,11 @@ export class RegistrasiTesisService { async updateInterviewDate( mahasiswaId: string, - periode: string, dto: UpdateInterviewBodyDto, idPenerima?: string, ) { + await this.penggunaService.isMahasiswaAktifOrFail(mahasiswaId); + const minDate = new Date(); minDate.setDate(minDate.getDate() + 2); @@ -429,7 +439,7 @@ export class RegistrasiTesisService { ); } - const newestReg = await this.getNewestRegByMhsOrFail(mahasiswaId, periode); + const newestReg = await this.getNewestRegByMhsOrFail(mahasiswaId); if (newestReg && idPenerima && newestReg.penerima.id !== idPenerima) { throw new ForbiddenException(); @@ -459,11 +469,12 @@ export class RegistrasiTesisService { async updateStatus( mahasiswaId: string, - periode: string, dto: UpdateStatusBodyDto, idPenerima?: string, ) { - const newestReg = await this.getNewestRegByMhsOrFail(mahasiswaId, periode); + await this.penggunaService.isMahasiswaAktifOrFail(mahasiswaId); + + const newestReg = await this.getNewestRegByMhsOrFail(mahasiswaId); if (newestReg && idPenerima && newestReg.penerima.id !== idPenerima) { throw new ForbiddenException(); @@ -506,10 +517,11 @@ export class RegistrasiTesisService { async updatePembimbingList( mahasiswaId: string, - periode: string, { pembimbing_ids: dosen_ids }: UpdatePembimbingBodyDto, ) { - const newestReg = await this.getNewestRegByMhsOrFail(mahasiswaId, periode); + await this.penggunaService.isMahasiswaAktifOrFail(mahasiswaId); + + const newestReg = await this.getNewestRegByMhsOrFail(mahasiswaId); if (newestReg.status !== RegStatus.APPROVED) throw new BadRequestException( diff --git a/src/submisi-tugas/submisi-tugas.controller.ts b/src/submisi-tugas/submisi-tugas.controller.ts deleted file mode 100644 index 4d48893e7288fc3766880718af9e6b4982d1a1aa..0000000000000000000000000000000000000000 --- a/src/submisi-tugas/submisi-tugas.controller.ts +++ /dev/null @@ -1,107 +0,0 @@ -import { - Body, - Controller, - Get, - Param, - Put, - Query, - Req, - UseGuards, -} from "@nestjs/common"; -import { SubmisiTugasService } from "./submisi-tugas.service"; -import { - ApiBearerAuth, - ApiCookieAuth, - ApiOkResponse, - ApiOperation, - ApiTags, -} from "@nestjs/swagger"; -import { CustomAuthGuard } from "src/middlewares/custom-auth.guard"; -import { RolesGuard } from "src/middlewares/roles.guard"; -import { Roles } from "src/middlewares/roles.decorator"; -import { RoleEnum } from "src/entities/pengguna.entity"; -import { - CreateOrUpdateSubmisiTugasDto, - GetSubmisiTugasByIdRespDto, - GetSubmisiTugasByTugasIdQueryDto, - GetSubmisiTugasByTugasIdRespDto, - SubmisiTugasIdDto, -} from "./submisi-tugas.dto"; -import { AuthDto } from "src/auth/auth.dto"; -import { Request } from "express"; - -@ApiTags("Submisi Tugas") -@ApiBearerAuth() -@ApiCookieAuth() -@UseGuards(CustomAuthGuard, RolesGuard) -@Controller("submisi-tugas") -export class SubmisiTugasController { - constructor(private submisiTugasServ: SubmisiTugasService) {} - - @ApiOperation({ - summary: "Create submisi tugas. Roles: S2_MAHASISWA", - }) - @ApiOkResponse({ type: SubmisiTugasIdDto }) - @Roles(RoleEnum.S2_MAHASISWA) - @Put() - async upsertSubmisiTugas( - @Body() dto: CreateOrUpdateSubmisiTugasDto, - @Req() req: Request, - ) { - const { id } = req.user as AuthDto; - - return await this.submisiTugasServ.upsertSubmisiTugas(dto, id); - } - - @ApiOperation({ - summary: - "Get submisi tugas by submisi tugas id. Roles: S2_KULIAH, S2_MAHASISWA", - }) - @ApiOkResponse({ type: GetSubmisiTugasByIdRespDto }) - @Roles(RoleEnum.S2_KULIAH, RoleEnum.S2_MAHASISWA) - @Get("/:id") - async getSubmisiTugasById( - @Req() req: Request, - @Param() param: SubmisiTugasIdDto, - ) { - const { id, roles } = req.user as AuthDto; - - let idMahasiswa = undefined; - let idPengajar = undefined; - - if (!roles.includes(RoleEnum.S2_KULIAH)) { - idMahasiswa = id; - } else { - idPengajar = id; - } - - return await this.submisiTugasServ.getSubmisiTugasById( - param.id, - idMahasiswa, - idPengajar, - ); - } - - @ApiOperation({ - summary: "Get list of submisi tugas summary by tugas id. Roles: S2_KULIAH", - }) - @ApiOkResponse({ type: [GetSubmisiTugasByTugasIdRespDto] }) - @Roles(RoleEnum.S2_KULIAH) - @Get() - async getSubmisiTugasByTugasId( - @Req() req: Request, - @Query() query: GetSubmisiTugasByTugasIdQueryDto, - ) { - const { id } = req.user as AuthDto; - - return await this.submisiTugasServ.getSubmisiTugasByTugasId( - query.tugasId, - id, - query.search || "", - query.page || 1, - query.limit || 10, - query.order || "ASC", - query.isSubmitted, - ); - } -} diff --git a/src/submisi-tugas/submisi-tugas.dto.ts b/src/submisi-tugas/submisi-tugas.dto.ts deleted file mode 100644 index 86bbae65c8d1f21b2502e4679e16de8acf272ea9..0000000000000000000000000000000000000000 --- a/src/submisi-tugas/submisi-tugas.dto.ts +++ /dev/null @@ -1,132 +0,0 @@ -import { IsString } from "@nestjs/class-validator"; -import { - ApiProperty, - ApiPropertyOptional, - OmitType, - PickType, -} from "@nestjs/swagger"; -import { Transform, Type } from "class-transformer"; -import { - IsBoolean, - IsEnum, - IsNumberString, - IsOptional, - IsUUID, - ValidateNested, -} from "class-validator"; -import { BerkasSubmisiTugas } from "src/entities/berkasSubmisiTugas.entity"; -import { PendaftaranTesis } from "src/entities/pendaftaranTesis.entity"; -import { Pengguna } from "src/entities/pengguna.entity"; -import { SubmisiTugas } from "src/entities/submisiTugas.entity"; -import { GetTugasByIdRespDto } from "src/tugas/tugas.dto"; - -class BerkasSubmisiTugasWithoutId extends OmitType(BerkasSubmisiTugas, [ - "id", -] as const) {} - -export class CreateOrUpdateSubmisiTugasDto extends PickType(SubmisiTugas, [ - "jawaban", - "isSubmitted", - "tugasId", -]) { - @ApiProperty({ type: [BerkasSubmisiTugasWithoutId] }) - @ValidateNested({ each: true }) - @Type(() => BerkasSubmisiTugasWithoutId) - berkasSubmisiTugas: BerkasSubmisiTugasWithoutId[]; - - @ApiPropertyOptional({ example: "550e8400-e29b-41d4-a716-446655440000" }) - @IsOptional() - @IsUUID() - id?: string; -} - -export class SubmisiTugasIdDto extends PickType(SubmisiTugas, [ - "id", -] as const) {} - -export class GetSubmisiTugasByTugasIdQueryDto extends PickType(SubmisiTugas, [ - "tugasId", -] as const) { - @ApiPropertyOptional() - @IsOptional() - @IsString() - search?: string; - - @IsOptional() - @IsNumberString() - @ApiPropertyOptional({ description: "default: 1" }) - page?: number; - - @IsOptional() - @IsNumberString() - @ApiPropertyOptional({ description: "default: 10" }) - limit?: number; - - @IsOptional() - @IsBoolean() - @Transform(({ value }) => value === "true") - @ApiPropertyOptional({ - description: "if not specified, will return all submisi tugas", - }) - isSubmitted?: boolean; - - @ApiPropertyOptional({ - enum: ["ASC", "DESC"], - description: "order by nim. default: ASC", - }) - @IsOptional() - @IsEnum(["ASC", "DESC"]) - order?: "ASC" | "DESC"; -} - -class PickedSubmisiTugas extends PickType(SubmisiTugas, [ - "id", - "isSubmitted", - "berkasSubmisiTugas", -] as const) {} - -export class PickedSubmisiTugasExtended extends PickType(SubmisiTugas, [ - "id", - "isSubmitted", - "jawaban", - "submittedAt", - "berkasSubmisiTugas", -] as const) {} - -class PickedPendaftaranTesis extends PickType(PendaftaranTesis, [ - "id", - "jalurPilihan", - "waktuPengiriman", - "jadwalInterview", - "status", - "topik", -] as const) {} - -class PickedPendaftaran extends PickType(Pengguna, [ - "id", - "nama", - "email", -] as const) { - @ApiProperty({ type: PickedPendaftaranTesis }) - pendaftaranTesis: PickedPendaftaranTesis; -} - -export class GetSubmisiTugasByTugasIdRespDto extends PickType(Pengguna, [ - "id", - "nim", - "nama", -] as const) { - @ApiPropertyOptional({ type: PickedSubmisiTugas }) - submisiTugas?: PickedSubmisiTugas; -} - -export class GetSubmisiTugasByIdRespDto { - @ApiProperty({ type: GetTugasByIdRespDto }) - tugas: GetTugasByIdRespDto; - - @ApiPropertyOptional({ type: PickedPendaftaran }) - pendaftaran?: PickedPendaftaran; - - @ApiProperty({ type: PickedSubmisiTugasExtended }) - submisiTugas: PickedSubmisiTugasExtended; -} diff --git a/src/submisi-tugas/submisi-tugas.module.ts b/src/submisi-tugas/submisi-tugas.module.ts deleted file mode 100644 index 1fd15428a499fe276ed0aeaef1b8168bd532b105..0000000000000000000000000000000000000000 --- a/src/submisi-tugas/submisi-tugas.module.ts +++ /dev/null @@ -1,31 +0,0 @@ -import { Module } from "@nestjs/common"; -import { SubmisiTugasController } from "./submisi-tugas.controller"; -import { SubmisiTugasService } from "./submisi-tugas.service"; -import { CustomStrategy } from "src/middlewares/custom.strategy"; -import { TypeOrmModule } from "@nestjs/typeorm"; -import { AuthModule } from "src/auth/auth.module"; -import { KonfigurasiModule } from "src/konfigurasi/konfigurasi.module"; -import { SubmisiTugas } from "src/entities/submisiTugas.entity"; -import { BerkasSubmisiTugas } from "src/entities/berkasSubmisiTugas.entity"; -import { TugasModule } from "src/tugas/tugas.module"; -import { Pengguna } from "src/entities/pengguna.entity"; -import { Tugas } from "src/entities/tugas.entity"; -import { MahasiswaKelas } from "src/entities/mahasiswaKelas.entity"; - -@Module({ - imports: [ - TypeOrmModule.forFeature([ - SubmisiTugas, - BerkasSubmisiTugas, - Pengguna, - Tugas, - MahasiswaKelas, - ]), - AuthModule, - KonfigurasiModule, - TugasModule, - ], - controllers: [SubmisiTugasController], - providers: [SubmisiTugasService, CustomStrategy], -}) -export class SubmisiTugasModule {} diff --git a/src/submisi-tugas/submisi-tugas.service.ts b/src/submisi-tugas/submisi-tugas.service.ts deleted file mode 100644 index 59b4be284e535ec40456da9d3531badb009ea614..0000000000000000000000000000000000000000 --- a/src/submisi-tugas/submisi-tugas.service.ts +++ /dev/null @@ -1,319 +0,0 @@ -import { - BadRequestException, - ForbiddenException, - Injectable, - NotFoundException, -} from "@nestjs/common"; -import { InjectRepository } from "@nestjs/typeorm"; -import { BerkasSubmisiTugas } from "src/entities/berkasSubmisiTugas.entity"; -import { SubmisiTugas } from "src/entities/submisiTugas.entity"; -import { TugasService } from "src/tugas/tugas.service"; -import { Brackets, Repository } from "typeorm"; -import { - CreateOrUpdateSubmisiTugasDto, - GetSubmisiTugasByIdRespDto, - GetSubmisiTugasByTugasIdRespDto, - SubmisiTugasIdDto, -} from "./submisi-tugas.dto"; -import { Pengguna } from "src/entities/pengguna.entity"; -import { Tugas } from "src/entities/tugas.entity"; -import { RegStatus } from "src/entities/pendaftaranTesis.entity"; -import { KonfigurasiService } from "src/konfigurasi/konfigurasi.service"; -import { MahasiswaKelas } from "src/entities/mahasiswaKelas.entity"; -import * as dayjs from "dayjs"; - -@Injectable() -export class SubmisiTugasService { - constructor( - @InjectRepository(SubmisiTugas) - private submisiTugasRepo: Repository<SubmisiTugas>, - @InjectRepository(BerkasSubmisiTugas) - private berkasSubmisiTugasRepo: Repository<BerkasSubmisiTugas>, - @InjectRepository(Pengguna) - private penggunaRepo: Repository<Pengguna>, - @InjectRepository(Tugas) - private tugasRepo: Repository<Tugas>, - @InjectRepository(MahasiswaKelas) - private mahasiswaKelasRepo: Repository<MahasiswaKelas>, - private tugasService: TugasService, - private konfService: KonfigurasiService, - ) {} - private async isMahasiswaSubmisiTugasOrFail( - submisiTugasId: string, - mahasiswaId: string, - ) { - const submisiTugas = await this.submisiTugasRepo.findOne({ - where: { id: submisiTugasId }, - }); - - if (!submisiTugas) { - throw new NotFoundException("Submisi tugas tidak ditemukan"); - } - - if (submisiTugas.mahasiswaId !== mahasiswaId) { - throw new ForbiddenException("Anda tidak memiliki akses"); - } - - // validate periode - await this.tugasService.isMahasiswaTugasOrFail( - mahasiswaId, - submisiTugas.tugasId, - ); - } - - private async isPengajarSubmisiTugasOrFail( - submisiTugasId: string, - pengajarId: string, - ) { - const submisiTugas = await this.submisiTugasRepo.findOne({ - where: { id: submisiTugasId }, - relations: ["tugas"], - }); - - if (!submisiTugas) { - throw new NotFoundException("Submisi tugas tidak ditemukan"); - } - - await this.tugasService.isPengajarTugasOrFail( - pengajarId, - submisiTugas.tugas.id, - ); - } - - async upsertSubmisiTugas( - dto: CreateOrUpdateSubmisiTugasDto, - mahasiswaId: string, - ): Promise<SubmisiTugasIdDto> { - await this.tugasService.isMahasiswaTugasOrFail(mahasiswaId, dto.tugasId); - - // const tugas = await this.tugasRepo.findOne({ - // where: { id: dto.tugasId, submisiTugas: { mahasiswaId } }, - // relations: { - // submisiTugas: true, - // }, - // }); - - const tugas = await this.tugasRepo - .createQueryBuilder("tugas") - .leftJoinAndSelect( - "tugas.submisiTugas", - "submisiTugas", - "submisiTugas.mahasiswaId = :mahasiswaId", - { mahasiswaId }, - ) - .where("tugas.id = :id", { id: dto.tugasId }) - .getOne(); - - const mahasiswa = await this.penggunaRepo.findOneBy({ id: mahasiswaId }); - - const berkasSubmisiTugas = dto.berkasSubmisiTugas.map( - (berkasSubmisiTugas) => - this.berkasSubmisiTugasRepo.create(berkasSubmisiTugas), - ); - - // check deadline - if (dayjs(new Date()).isAfter(dayjs(new Date(tugas.waktuSelesai)))) { - throw new ForbiddenException("Tugas sudah melewati deadline"); - } - - if (!dto.id) { - // create - // no duplicate - if (tugas.submisiTugas.length > 0) { - throw new ForbiddenException("Submisi tugas sudah ada"); - } - - const submisiTugas = this.submisiTugasRepo.create({ - ...dto, - mahasiswa, - submittedAt: dto.isSubmitted ? new Date() : null, - tugas, - berkasSubmisiTugas, - }); - - const result = await this.submisiTugasRepo.save(submisiTugas); - - return { id: result.id }; - } else { - // update - // check if submisi tugas exists - if (tugas.submisiTugas.length === 0) { - throw new BadRequestException("Submisi tugas belum ada"); - } - - // check if submisi tugas id is valid - if (tugas.submisiTugas[0].id !== dto.id) { - throw new NotFoundException("Submisi tugas tidak ditemukan"); - } - - // check if submisi tugas is already submitted - if (tugas.submisiTugas[0].isSubmitted) { - throw new ForbiddenException("Submisi tugas sudah dikumpulkan"); - } - - // eslint-disable-next-line @typescript-eslint/no-unused-vars - const { submisiTugas: _, ...omittedTugas } = tugas; - - const data = { - ...tugas.submisiTugas[0], - ...dto, - berkasSubmisiTugas, - mahasiswa, - tugas: omittedTugas, - submittedAt: dto.isSubmitted ? new Date() : null, - }; - - await this.submisiTugasRepo.save(data); - - return { id: dto.id }; - } - } - - private async getSubmisiTugas(id: string) { - const submisiTugas = await this.submisiTugasRepo.findOne({ - where: { id }, - relations: ["berkasSubmisiTugas"], - }); - - if (!submisiTugas) { - throw new NotFoundException("Submisi tugas tidak ditemukan"); - } - - return submisiTugas; - } - - async getSubmisiTugasById( - id: string, - mahasiswaId?: string, - pengajarId?: string, - ) { - if (mahasiswaId) { - await this.isMahasiswaSubmisiTugasOrFail(id, mahasiswaId); - } - - if (pengajarId) { - await this.isPengajarSubmisiTugasOrFail(id, pengajarId); - } - - const currPeriod = await this.konfService.getPeriodeOrFail(); - const submisiTugas = await this.getSubmisiTugas(id); - - const pendaftaranQuery = this.penggunaRepo - .createQueryBuilder("pengguna") - .select([ - "pengguna.id", - "pengguna.nama", - "pengguna.email", - "pendaftaranTesis.jalurPilihan", - "pendaftaranTesis.waktuPengiriman", - "topik.id", - "topik.judul", - "topik.deskripsi", - ]) - .leftJoinAndSelect("pengguna.pendaftaranTesis", "pendaftaranTesis") - .leftJoinAndSelect("pendaftaranTesis.topik", "topik") - .where("pengguna.id = :id", { id: submisiTugas.mahasiswaId }) - .andWhere("pendaftaranTesis.status = :status", { - status: RegStatus.APPROVED, - }) - .andWhere("topik.periode = :periode", { periode: currPeriod }) - .getOne(); - - const [tugas, pendaftaran] = await Promise.all([ - this.tugasService.getTugasById(submisiTugas.tugasId), - pendaftaranQuery, - ]); - - const result: GetSubmisiTugasByIdRespDto = { - tugas, - submisiTugas, - pendaftaran: { - ...pendaftaran, - pendaftaranTesis: - pendaftaran.pendaftaranTesis.length > 0 - ? pendaftaran.pendaftaranTesis[0] - : undefined, - }, - }; - - return result; - } - - async getSubmisiTugasByTugasId( - tugasId: string, - idPenerima: string, - search: string, - page: number, - limit: number, - order: "ASC" | "DESC", - isSubmitted?: boolean, - ) { - await this.tugasService.isPengajarTugasOrFail(idPenerima, tugasId); - - const baseQuery = this.mahasiswaKelasRepo - .createQueryBuilder("mk") - .innerJoin("mk.kelas", "kelas", "kelas.id = mk.kelasId") - .innerJoinAndSelect("mk.mahasiswa", "mahasiswa") - .leftJoinAndSelect( - "mahasiswa.submisiTugas", - "submisiTugas", - "submisiTugas.tugasId = :tugasId", - { tugasId }, - ) - .leftJoinAndSelect( - "submisiTugas.berkasSubmisiTugas", - "berkasSubmisiTugas", - ) - .select([ - "mk.id", - "mahasiswa.id", - "mahasiswa.nim", - "mahasiswa.nama", - "submisiTugas.id", - "submisiTugas.isSubmitted", - "berkasSubmisiTugas", - ]) - .distinctOn(["mahasiswa.nim"]) - .where( - new Brackets((qb) => { - qb.where("mahasiswa.nama ILIKE :search", { - search: `%${search}%`, - }).orWhere("mahasiswa.nim ILIKE :search", { search: `%${search}%` }); - }), - ) - .orderBy("mahasiswa.nim", order); - - if (isSubmitted !== undefined) { - if (isSubmitted) { - baseQuery.andWhere("submisiTugas.isSubmitted = true"); - } else { - baseQuery.andWhere( - new Brackets((qb) => - qb - .where("submisiTugas.isSubmitted <> true") - .orWhere("submisiTugas.isSubmitted IS NULL"), - ), - ); - } - } - - const submisiTugas = await baseQuery - .limit(limit) - .skip((page - 1) * limit) - .getMany(); - - const mappedResult: GetSubmisiTugasByTugasIdRespDto[] = submisiTugas.map( - (submisi) => ({ - id: submisi.mahasiswa.id, - nim: submisi.mahasiswa.nim, - nama: submisi.mahasiswa.nama, - submisiTugas: - submisi.mahasiswa.submisiTugas.length > 0 - ? submisi.mahasiswa.submisiTugas[0] - : undefined, - }), - ); - - return mappedResult; - } -} diff --git a/src/tugas/tugas.controller.ts b/src/tugas/tugas.controller.ts deleted file mode 100644 index 1e2b396d33bcdebaab6e7f7b79b292b9022f7224..0000000000000000000000000000000000000000 --- a/src/tugas/tugas.controller.ts +++ /dev/null @@ -1,148 +0,0 @@ -import { - Body, - Controller, - Get, - Param, - Post, - Put, - Query, - Req, - UseGuards, -} from "@nestjs/common"; -import { TugasService } from "./tugas.service"; -import { Roles } from "src/middlewares/roles.decorator"; -import { RoleEnum } from "src/entities/pengguna.entity"; -import { - ApiBearerAuth, - ApiCookieAuth, - ApiCreatedResponse, - ApiOkResponse, - ApiOperation, - ApiTags, -} from "@nestjs/swagger"; -import { CustomAuthGuard } from "src/middlewares/custom-auth.guard"; -import { RolesGuard } from "src/middlewares/roles.guard"; -import { - TugasIdDto, - CreateTugasDto, - UpdateTugasDto, - GetTugasByIdRespDto, - GetTugasByKelasIdQueryDto, - GetTugasByKelasIdRespDto, - GetTugasByMahasiswaIdQueryDto, - GetDaftarTugasByMahasiswaIdRespDto, -} from "./tugas.dto"; -import { Request } from "express"; -import { AuthDto } from "src/auth/auth.dto"; -import { PickedSubmisiTugasExtended } from "src/submisi-tugas/submisi-tugas.dto"; - -@ApiCookieAuth() -@ApiBearerAuth() -@ApiTags("Tugas") -@UseGuards(CustomAuthGuard, RolesGuard) -@Controller("tugas") -export class TugasController { - constructor(private readonly tugasService: TugasService) {} - - @ApiOperation({ summary: "Create Tugas. Roles: S2_KULIAH" }) - @ApiCreatedResponse({ type: TugasIdDto }) - @Roles(RoleEnum.S2_KULIAH) - @Post() - async createTugas(@Body() createDto: CreateTugasDto, @Req() req: Request) { - const { id } = req.user as AuthDto; - - return await this.tugasService.createTugas(createDto, id); - } - - @ApiOperation({ summary: "Update Tugas. Roles: S2_KULIAH" }) - @ApiOkResponse({ type: TugasIdDto }) - @Roles(RoleEnum.S2_KULIAH) - @Put() - async updateTugas(@Body() updateDto: UpdateTugasDto, @Req() req: Request) { - const { id } = req.user as AuthDto; - - return await this.tugasService.updateTugasById(updateDto, id); - } - - @ApiOperation({ - summary: "Get Tugas by id. Roles: S2_KULIAH, S2_MAHASISWA", - }) - @ApiOkResponse({ type: GetTugasByIdRespDto }) - @Roles(RoleEnum.S2_KULIAH, RoleEnum.S2_MAHASISWA) - @Get("/:id") - async getTugasById(@Param() param: TugasIdDto, @Req() req: Request) { - let idPengajar = undefined; - let idMahasiswa = undefined; - - const { id, roles } = req.user as AuthDto; - - if (!roles.includes(RoleEnum.S2_KULIAH)) { - idMahasiswa = id; - } else { - idPengajar = id; - } - - return this.tugasService.getTugasById(param.id, idMahasiswa, idPengajar); - } - - @ApiOperation({ - summary: "Get Tugas list by kelas id. Roles: S2_KULIAH", - }) - @ApiOkResponse({ type: GetTugasByKelasIdRespDto }) - @Roles(RoleEnum.S2_KULIAH) - @Get() - async getTugasByKelasId( - @Req() req: Request, - @Query() query: GetTugasByKelasIdQueryDto, - ) { - const { id } = req.user as AuthDto; - - return this.tugasService.getTugasByKelasId( - query.kelasId, - id, - query.search || "", - query.page || 1, - query.limit || 10, - ); - } - - @ApiOperation({ - summary: - "Get a specific submisi tugas by mahasiswa ID and tugas ID. Roles: S2_MAHASISWA", - }) - @ApiOkResponse({ type: PickedSubmisiTugasExtended }) - @Roles(RoleEnum.S2_MAHASISWA) - @Get("/:id/submisi-tugas") - async getSubmisiTugasByMahasiswaAndTugasId( - @Req() req: Request, - @Param() param: TugasIdDto, - ) { - const { id: mahasiswaId } = req.user as AuthDto; - - return await this.tugasService.getSubmisiTugasByMahasiswaAndTugasId( - mahasiswaId, - param.id, - ); - } - - @ApiOperation({ - summary: "Get Tugas list by mahasiswa Id. Roles: S2_MAHASISWA", - }) - @ApiOkResponse({ type: [GetDaftarTugasByMahasiswaIdRespDto] }) - @Roles(RoleEnum.S2_MAHASISWA) - @Get("/-/daftar-tugas") - async getTugasByMahasiswaId( - @Query() query: GetTugasByMahasiswaIdQueryDto, - @Req() req: Request, - ) { - const { id } = req.user as AuthDto; - - return this.tugasService.getDaftarTugasByMahasiswa( - id, - query.search || "", - query.page || 1, - query.limit || 10, - query.isSubmitted || undefined, - ); - } -} diff --git a/src/tugas/tugas.dto.ts b/src/tugas/tugas.dto.ts deleted file mode 100644 index 1bf372b3ce03a32ce1e16c50e39d08af6313c921..0000000000000000000000000000000000000000 --- a/src/tugas/tugas.dto.ts +++ /dev/null @@ -1,167 +0,0 @@ -import { - ApiProperty, - ApiPropertyOptional, - OmitType, - PickType, -} from "@nestjs/swagger"; -import { Transform, Type } from "class-transformer"; -import { - IsBoolean, - IsNumberString, - IsOptional, - IsString, - IsUUID, - ValidateNested, -} from "class-validator"; -import { BerkasTugas } from "src/entities/berkasTugas.entity"; -import { Kelas } from "src/entities/kelas.entity"; -import { Pengguna } from "src/entities/pengguna.entity"; -import { Tugas } from "src/entities/tugas.entity"; -import { GetKelasRespDto } from "src/kelas/kelas.dto"; - -class BerkasTugasWithoutId extends OmitType(BerkasTugas, ["id"] as const) {} - -export class CreateTugasDto extends PickType(Tugas, [ - "judul", - "waktuMulai", - "waktuSelesai", - "deskripsi", - "kelasId", -]) { - @ApiProperty({ type: [BerkasTugasWithoutId] }) - @ValidateNested({ each: true }) - @Type(() => BerkasTugasWithoutId) - berkasTugas: BerkasTugasWithoutId[]; -} - -export class UpdateTugasDto extends OmitType(CreateTugasDto, [ - "kelasId", -] as const) { - @ApiProperty({ example: "550e8400-e29b-41d4-a716-446655440000" }) - @IsUUID() - id: string; -} - -export class TugasIdDto extends PickType(Tugas, ["id"] as const) {} - -class PickedPengajarKelas extends PickType(Pengguna, ["id", "nama"] as const) {} - -class PickedTugasKelas extends PickType(Kelas, [ - "id", - "nomor", - "mataKuliah", -] as const) {} - -export class GetTugasByIdRespDto extends PickType(Tugas, [ - "id", - "judul", - "waktuMulai", - "waktuSelesai", - "deskripsi", - "createdAt", - "updatedAt", - "berkasTugas", -] as const) { - @ApiProperty({ type: PickedPengajarKelas }) - pembuat: PickedPengajarKelas; - - @ApiProperty({ type: PickedPengajarKelas }) - pengubah: PickedPengajarKelas; - - @ApiProperty({ type: PickedTugasKelas }) - kelas: PickedTugasKelas; -} - -export class GetTugasByKelasIdQueryDto extends PickType(Tugas, [ - "kelasId", -] as const) { - @ApiPropertyOptional() - @IsOptional() - @IsString() - search?: string; - - @IsOptional() - @IsNumberString() - @ApiPropertyOptional({ description: "default: 1" }) - page?: number; - - @IsOptional() - @IsNumberString() - @ApiPropertyOptional({ description: "default: 10" }) - limit?: number; -} - -export class GetTugasByMahasiswaIdQueryDto { - @ApiPropertyOptional() - @IsOptional() - @IsString() - search?: string; - - @IsOptional() - @IsNumberString() - @ApiPropertyOptional({ description: "default: 1" }) - page?: number; - - @IsOptional() - @IsNumberString() - @ApiPropertyOptional({ description: "default: 10" }) - limit?: number; - - @IsOptional() - @IsBoolean() - @Transform(({ value }) => value === "true") - @ApiPropertyOptional({ - description: "if not specified, will return all submisi tugas", - }) - isSubmitted?: boolean; -} - -export class GetTugasSummaryRespDto extends PickType(Tugas, [ - "id", - "judul", - "waktuMulai", - "waktuSelesai", -] as const) { - @ApiProperty() - totalSubmisi: number; -} - -export class GetTugasByKelasIdRespDto { - @ApiProperty({ type: [GetTugasSummaryRespDto] }) - tugas: GetTugasSummaryRespDto[]; - - @ApiProperty({ type: GetKelasRespDto }) - kelas: GetKelasRespDto; -} - -export class GetDaftarTugasByMahasiswaIdRespDto { - @ApiProperty({ example: "IF4031" }) - kodeMataKuliah: string; - - @ApiProperty({ example: "Pengembangan Aplikasi" }) - namaMataKuliah: string; - - @ApiProperty({ example: "550e8400-e29b-41d4-a716-446655440000" }) - kelasId: string; - - @ApiProperty({ - example: "550e8400-e29b-41d4-a716-446655440000", - description: "tugas id", - }) - id: string; - - @ApiProperty() - judul: string; - - @ApiProperty() - waktuMulai: Date; - - @ApiProperty() - waktuSelesai: Date; - - @ApiPropertyOptional({ example: "550e8400-e29b-41d4-a716-446655440000" }) - submisiTugasId: string; - - @ApiPropertyOptional() - isSubmitted: boolean; -} diff --git a/src/tugas/tugas.module.ts b/src/tugas/tugas.module.ts deleted file mode 100644 index 3a8d49258c546ffaa9d9d95014f4d10af9190442..0000000000000000000000000000000000000000 --- a/src/tugas/tugas.module.ts +++ /dev/null @@ -1,44 +0,0 @@ -import { Module } from "@nestjs/common"; -import { TugasController } from "./tugas.controller"; -import { TugasService } from "./tugas.service"; -import { TypeOrmModule } from "@nestjs/typeorm"; -import { PengajarKelas } from "src/entities/pengajarKelas.entity"; -import { MahasiswaKelas } from "src/entities/mahasiswaKelas.entity"; -import { Tugas } from "src/entities/tugas.entity"; -import { SubmisiTugas } from "src/entities/submisiTugas.entity"; -import { AuthModule } from "src/auth/auth.module"; -import { KonfigurasiModule } from "src/konfigurasi/konfigurasi.module"; -import { CustomStrategy } from "src/middlewares/custom.strategy"; -import { BerkasTugas } from "src/entities/berkasTugas.entity"; -import { BerkasSubmisiTugas } from "src/entities/berkasSubmisiTugas.entity"; -import { Kelas } from "src/entities/kelas.entity"; -import { Pengguna } from "src/entities/pengguna.entity"; -import { KelasModule } from "src/kelas/kelas.module"; -import { KelasService } from "src/kelas/kelas.service"; -import { MataKuliah } from "src/entities/mataKuliah.entity"; -import { KonfigurasiService } from "src/konfigurasi/konfigurasi.service"; -import { Konfigurasi } from "src/entities/konfigurasi.entity"; - -@Module({ - imports: [ - TypeOrmModule.forFeature([ - PengajarKelas, - MahasiswaKelas, - Tugas, - SubmisiTugas, - BerkasTugas, - BerkasSubmisiTugas, - Kelas, - Pengguna, - MataKuliah, - Konfigurasi, - ]), - AuthModule, - KonfigurasiModule, - KelasModule, - ], - controllers: [TugasController], - providers: [TugasService, CustomStrategy, KelasService, KonfigurasiService], - exports: [TugasService], -}) -export class TugasModule {} diff --git a/src/tugas/tugas.service.ts b/src/tugas/tugas.service.ts deleted file mode 100644 index 0e33561b73063e615f4229787ec4b9e8f92730d0..0000000000000000000000000000000000000000 --- a/src/tugas/tugas.service.ts +++ /dev/null @@ -1,383 +0,0 @@ -import { - BadRequestException, - ForbiddenException, - Injectable, - NotFoundException, -} from "@nestjs/common"; -import { InjectRepository } from "@nestjs/typeorm"; -import { Tugas } from "src/entities/tugas.entity"; -import { Brackets, Repository } from "typeorm"; -import { - CreateTugasDto, - GetDaftarTugasByMahasiswaIdRespDto, - GetTugasByIdRespDto, - GetTugasByKelasIdRespDto, - GetTugasSummaryRespDto, - TugasIdDto, - UpdateTugasDto, -} from "./tugas.dto"; -import { BerkasTugas } from "src/entities/berkasTugas.entity"; -import { PengajarKelas } from "src/entities/pengajarKelas.entity"; -import { MahasiswaKelas } from "src/entities/mahasiswaKelas.entity"; -import { Kelas } from "src/entities/kelas.entity"; -import { Pengguna } from "src/entities/pengguna.entity"; -import { KelasService } from "src/kelas/kelas.service"; -import { KonfigurasiService } from "src/konfigurasi/konfigurasi.service"; -import * as dayjs from "dayjs"; -import { SubmisiTugas } from "src/entities/submisiTugas.entity"; -import { PickedSubmisiTugasExtended } from "src/submisi-tugas/submisi-tugas.dto"; - -@Injectable() -export class TugasService { - constructor( - @InjectRepository(Tugas) private tugasRepo: Repository<Tugas>, - @InjectRepository(BerkasTugas) - private berkasTugasRepo: Repository<BerkasTugas>, - @InjectRepository(SubmisiTugas) - private submisiTugasRepo: Repository<SubmisiTugas>, - @InjectRepository(PengajarKelas) - private doskelRepo: Repository<PengajarKelas>, - @InjectRepository(MahasiswaKelas) - private mahasiswaKelasRepo: Repository<MahasiswaKelas>, - @InjectRepository(Kelas) - private kelasRepo: Repository<Kelas>, - @InjectRepository(Pengguna) - private penggunaRepo: Repository<Pengguna>, - private kelasService: KelasService, - private konfService: KonfigurasiService, - ) {} - - private async isPengajarKelasOrFail(pengajarId: string, kelasId: string) { - const periode = await this.konfService.getPeriodeOrFail(); - - const doskel = await this.doskelRepo.findOne({ - where: { - pengajarId, - kelasId, - kelas: { - periode, - }, - }, - relations: ["kelas"], - }); - - if (!doskel) { - throw new ForbiddenException("Anda tidak memiliki akses"); - } - } - - private async isMahasiswaKelasOrFail(mahasiswaId: string, kelasId: string) { - const periode = await this.konfService.getPeriodeOrFail(); - - const mahasiswaKelas = await this.mahasiswaKelasRepo.findOne({ - where: { - mahasiswaId, - kelasId, - kelas: { - periode, - }, - }, - relations: ["kelas"], - }); - - if (!mahasiswaKelas) { - throw new ForbiddenException("Anda tidak memiliki akses"); - } - } - - async isPengajarTugasOrFail(pengajarId: string, tugasId: string) { - const tugas = await this.tugasRepo.findOne({ - where: { id: tugasId }, - }); - - if (!tugas) { - throw new NotFoundException("Tugas tidak ditemukan"); - } - - await this.isPengajarKelasOrFail(pengajarId, tugas.kelasId); - } - - async isMahasiswaTugasOrFail(mahasiswaId: string, tugasId: string) { - const tugas = await this.tugasRepo.findOne({ - where: { id: tugasId }, - }); - - if (!tugas) { - throw new NotFoundException("Tugas tidak ditemukan"); - } - - return await this.isMahasiswaKelasOrFail(mahasiswaId, tugas.kelasId); - } - - private async getTugas(tugasId: string): Promise<GetTugasByIdRespDto> { - const result: GetTugasByIdRespDto[] = await this.tugasRepo - .createQueryBuilder("tugas") - .leftJoinAndSelect("tugas.pembuat", "pembuat") - .leftJoinAndSelect("tugas.pengubah", "pengubah") - .leftJoinAndSelect("tugas.kelas", "kelas") - .leftJoinAndSelect("kelas.mataKuliah", "mataKuliah") - .leftJoinAndSelect("tugas.berkasTugas", "berkasTugas") - .select([ - "tugas.id", - "pembuat.id", - "pembuat.nama", - "pengubah.id", - "pengubah.nama", - "tugas.judul", - "tugas.waktuMulai", - "tugas.waktuSelesai", - "tugas.deskripsi", - "tugas.createdAt", - "tugas.updatedAt", - "berkasTugas", - "kelas.id", - "kelas.nomor", - "mataKuliah.kode", - "mataKuliah.nama", - ]) - .where("tugas.id = :tugasId", { tugasId }) - .getMany(); - - return result[0]; - } - - async createTugas( - createDto: CreateTugasDto, - pembuatId: string, - ): Promise<TugasIdDto> { - await this.isPengajarKelasOrFail(pembuatId, createDto.kelasId); - - if (dayjs(createDto.waktuMulai).isAfter(dayjs(createDto.waktuSelesai))) { - throw new BadRequestException( - "Waktu mulai tidak boleh setelah waktu selesai", - ); - } - - const kelas = await this.kelasRepo.findOne({ - where: { id: createDto.kelasId }, - }); - - const pembuat = await this.penggunaRepo.findOne({ - where: { id: pembuatId }, - }); - - const berkasTugas = createDto.berkasTugas.map((berkas) => - this.berkasTugasRepo.create(berkas), - ); - - const tugas = this.tugasRepo.create({ - ...createDto, - kelas, - pembuat, - pengubah: pembuat, - berkasTugas, - }); - - const result = await this.tugasRepo.save(tugas); - - return { id: result.id }; - } - - async updateTugasById( - updateDto: UpdateTugasDto, - pengubahId: string, - ): Promise<TugasIdDto> { - await this.isPengajarTugasOrFail(pengubahId, updateDto.id); - - if (dayjs(updateDto.waktuMulai).isAfter(dayjs(updateDto.waktuSelesai))) { - throw new BadRequestException( - "Waktu mulai tidak boleh setelah waktu selesai", - ); - } - - const berkasTugas = updateDto.berkasTugas.map((berkas) => - this.berkasTugasRepo.create(berkas), - ); - - const pengubah = await this.penggunaRepo.findOne({ - where: { id: pengubahId }, - }); - - const prevTugas = await this.tugasRepo.findOne({ - where: { id: updateDto.id }, - relations: ["kelas", "pembuat"], - }); - - const data = { - ...updateDto, - updatedAt: new Date(), - pengubah, - kelas: prevTugas.kelas, - pembuat: prevTugas.pembuat, - berkasTugas, - }; - - await this.tugasRepo.save(data); - - return { id: updateDto.id }; - } - - async getTugasById(id: string, idMahasiswa?: string, idPengajar?: string) { - if (idMahasiswa) { - await this.isMahasiswaTugasOrFail(idMahasiswa, id); - } - - if (idPengajar) { - await this.isPengajarTugasOrFail(idPengajar, id); - } - - const result = await this.getTugas(id); - - return result; - } - - async getTugasByKelasId( - kelasId: string, - idPengajar: string, - search: string, - page: number, - limit: number, - ): Promise<GetTugasByKelasIdRespDto> { - await this.isPengajarKelasOrFail(idPengajar, kelasId); - - const kelasQuery = this.kelasService.getById(kelasId); - const tugasQuery = this.tugasRepo - .createQueryBuilder("tugas") - .leftJoinAndSelect( - "tugas.submisiTugas", - "submisi_tugas", - "submisi_tugas.isSubmitted = true", - ) - .select([ - "tugas.id AS id", - "tugas.judul AS judul", - "tugas.waktuMulai AS waktu_mulai", - "tugas.waktuSelesai AS waktu_selesai", - "COUNT(submisi_tugas) AS total_submisi", - ]) - .where("tugas.kelasId = :kelasId", { - kelasId, - }) - .andWhere("tugas.judul ILIKE :search", { search: `%${search}%` }) - .groupBy("tugas.id") - .orderBy("tugas.createdAt", "DESC") - .limit(limit) - .skip((page - 1) * limit) - .getRawMany(); - - const [kelas, tugas] = await Promise.all([kelasQuery, tugasQuery]); - const mappedTugas: GetTugasSummaryRespDto[] = tugas.map((tugas) => ({ - id: tugas.id, - judul: tugas.judul, - waktuMulai: tugas.waktu_mulai, - waktuSelesai: tugas.waktu_selesai, - totalSubmisi: parseInt(tugas.total_submisi), - })); - - return { kelas, tugas: mappedTugas }; - } - - async getSubmisiTugasByMahasiswaAndTugasId( - mahasiswaId: string, - tugasId: string, - ): Promise<PickedSubmisiTugasExtended> { - await this.isMahasiswaTugasOrFail(mahasiswaId, tugasId); - - const submisiTugas = await this.submisiTugasRepo.findOne({ - select: [ - "id", - "isSubmitted", - "jawaban", - "submittedAt", - "berkasSubmisiTugas", - ], - where: { - mahasiswaId: mahasiswaId, - tugasId: tugasId, - }, - relations: ["berkasSubmisiTugas"], - }); - - if (!submisiTugas) { - throw new NotFoundException( - `Submisi tugas tidak ditemukan untuk mahasiswa ID: ${mahasiswaId} dan tugas ID: ${tugasId}`, - ); - } - - return submisiTugas; - } - - async getDaftarTugasByMahasiswa( - mahasiswaId: string, - search: string, - page: number, - limit: number, - isSubmitted?: boolean, - ): Promise<GetDaftarTugasByMahasiswaIdRespDto[]> { - const baseQuery = this.mahasiswaKelasRepo - .createQueryBuilder("mk") - .innerJoin("mk.kelas", "kelas", "kelas.id = mk.kelasId") - .innerJoin("kelas.mataKuliah", "mataKuliah") - .innerJoin("kelas.tugas", "tugas") - .leftJoinAndSelect( - "tugas.submisiTugas", - "submisiTugas", - "submisiTugas.mahasiswaId = :mahasiswaId", - { mahasiswaId }, - ) - .select([ - "mk.id", - "kelas.id AS kelas_id", - "mataKuliah.kode AS kode_mata_kuliah", - "mataKuliah.nama AS nama_mata_kuliah", - "tugas.id AS tugas_id", - "tugas.judul AS tugas_judul", - "tugas.waktuMulai AS tugas_waktu_mulai", - "tugas.waktuSelesai AS tugas_waktu_selesai", - "submisiTugas.id AS submisi_tugas_id", - "submisiTugas.isSubmitted AS submisi_tugas_is_submitted", - ]) - .where("mk.mahasiswaId = :mahasiswaId", { - mahasiswaId, - }) - .andWhere("tugas.judul ILIKE :search", { search: `%${search}%` }) - .orderBy("tugas.createdAt", "DESC"); - - if (isSubmitted !== undefined) { - if (isSubmitted) { - baseQuery.andWhere("submisiTugas.isSubmitted = true"); - } else { - baseQuery.andWhere( - new Brackets((qb) => - qb - .where("submisiTugas.isSubmitted <> true") - .orWhere("submisiTugas.isSubmitted IS NULL"), - ), - ); - } - } - - const daftarTugas = await baseQuery - .limit(limit) - .skip((page - 1) * limit) - .getRawMany(); - - const mappedDaftarTugas: GetDaftarTugasByMahasiswaIdRespDto[] = - daftarTugas.map((tugas) => ({ - kodeMataKuliah: tugas.kode_mata_kuliah, - namaMataKuliah: tugas.nama_mata_kuliah, - kelasId: tugas.kelas_id, - id: tugas.tugas_id, - judul: tugas.tugas_judul, - waktuMulai: tugas.tugas_waktu_mulai, - waktuSelesai: tugas.tugas_waktu_selesai, - submisiTugasId: tugas.submisi_tugas_id || undefined, - isSubmitted: - tugas.submisi_tugas_is_submitted === null - ? undefined - : tugas.submisi_tugas_is_submitted, - })); - - return mappedDaftarTugas; - } -}