diff --git a/src/alokasi-ruangan/alokasi-ruangan.controller.ts b/src/alokasi-ruangan/alokasi-ruangan.controller.ts new file mode 100644 index 0000000000000000000000000000000000000000..f368ac433d1b59c803d997efcd5c588454006386 --- /dev/null +++ b/src/alokasi-ruangan/alokasi-ruangan.controller.ts @@ -0,0 +1,62 @@ +import { + Body, + Controller, + Get, + Param, + Patch, + Query, + UseGuards, +} from "@nestjs/common"; +import { + ApiBearerAuth, + ApiCookieAuth, + ApiOkResponse, + ApiTags, +} from "@nestjs/swagger"; +import { RoleEnum } from "src/entities/pengguna.entity"; +import { CustomAuthGuard } from "src/middlewares/custom-auth.guard"; +import { Roles } from "src/middlewares/roles.decorator"; +import { RolesGuard } from "src/middlewares/roles.guard"; +import { + GetAllPengajuanSidangReqQueryDto, + GetAllPengajuanSidangRespDto, + GetOnePengajuanSidangRespDto, + UpdateAlokasiRuanganReqDto, + UpdateAlokasiRuanganRespDto, +} from "./alokasi-ruangan.dto"; +import { AlokasiRuanganService } from "./alokasi-ruangan.service"; + +// TODO test if role guard works +@ApiTags("Alokasi Ruangan") +@ApiBearerAuth() +@ApiCookieAuth() +@UseGuards(CustomAuthGuard, RolesGuard) +@Roles(RoleEnum.ADMIN, RoleEnum.TU) +@Controller("alokasi-ruangan") +export class AlokasiRuanganController { + constructor(private readonly alokasiRuanganService: AlokasiRuanganService) {} + @ApiOkResponse({ type: GetAllPengajuanSidangRespDto }) + @Get() + async findAll( + @Query() query: GetAllPengajuanSidangReqQueryDto, + ): Promise<GetAllPengajuanSidangRespDto> { + return this.alokasiRuanganService.findAll(query); + } + + @ApiOkResponse({ type: GetOnePengajuanSidangRespDto }) + @Get(":id") + async findOne( + @Param("id") id: string, + ): Promise<GetOnePengajuanSidangRespDto> { + return this.alokasiRuanganService.findOne(id); + } + + @ApiOkResponse({ type: UpdateAlokasiRuanganRespDto }) + @Patch(":id") + async update( + @Param("id") id: string, + @Body() updateAlokasiRuanganDto: UpdateAlokasiRuanganReqDto, + ): Promise<UpdateAlokasiRuanganRespDto> { + return this.alokasiRuanganService.update(id, updateAlokasiRuanganDto); + } +} diff --git a/src/alokasi-ruangan/alokasi-ruangan.dto.ts b/src/alokasi-ruangan/alokasi-ruangan.dto.ts new file mode 100644 index 0000000000000000000000000000000000000000..74bf31f6687d31874f862e59eaff62ad0668c647 --- /dev/null +++ b/src/alokasi-ruangan/alokasi-ruangan.dto.ts @@ -0,0 +1,77 @@ +import { ApiProperty, ApiPropertyOptional } from "@nestjs/swagger"; +import { IsEnum, IsNumberString, IsOptional, IsString } from "class-validator"; +import { TipeSidsemEnum } from "src/entities/pendaftaranSidsem"; +import { JalurEnum } from "src/entities/pendaftaranTesis.entity"; + +export class GetAllPengajuanSidangReqQueryDto { + @ApiPropertyOptional() + @IsString() + @IsOptional() + search?: string; + + @ApiPropertyOptional({ enum: TipeSidsemEnum }) + @IsEnum(TipeSidsemEnum) + @IsOptional() + jenisSidang?: TipeSidsemEnum; + + @IsOptional() + @IsNumberString() + @ApiPropertyOptional({ description: "default: 1" }) + page?: number; + + @IsOptional() + @IsNumberString() + @ApiPropertyOptional({ description: "default: no limit" }) + limit?: number; +} + +export class GetAllPengajuanSidangItemDto { + @ApiProperty({ example: "550e8400-e29b-41d4-a716-446655440000" }) + idPengajuanSidsem: string; + + @ApiProperty() + nimMahasiswa: string; + + @ApiProperty() + namaMahasiswa: string; + + @ApiProperty() + jadwalSidang: string; + + @ApiProperty({ enum: TipeSidsemEnum }) + jenisSidang: TipeSidsemEnum; + + @ApiProperty({ nullable: true }) + ruangan: string | null; +} + +export class GetAllPengajuanSidangRespDto { + @ApiProperty() + total: number; + + @ApiProperty({ type: GetAllPengajuanSidangItemDto, isArray: true }) + data: GetAllPengajuanSidangItemDto[]; +} + +export class GetOnePengajuanSidangRespDto extends GetAllPengajuanSidangItemDto { + @ApiProperty() + emailMahasiswa: string; + @ApiProperty({ enum: JalurEnum }) + jalurPilihan: JalurEnum; + @ApiProperty() + judulTopik: string; + @ApiProperty() + deskripsiTopik: string; + @ApiProperty({ isArray: true }) + dosenPembimbing: string[]; + @ApiProperty({ isArray: true }) + dosenPenguji: string[]; +} + +export class UpdateAlokasiRuanganReqDto { + @ApiProperty() + @IsString() + ruangan: string; +} + +export class UpdateAlokasiRuanganRespDto extends GetAllPengajuanSidangItemDto {} diff --git a/src/alokasi-ruangan/alokasi-ruangan.module.ts b/src/alokasi-ruangan/alokasi-ruangan.module.ts new file mode 100644 index 0000000000000000000000000000000000000000..b0dfee65ba16b11d510fb13e54e4fc9acf4a94ed --- /dev/null +++ b/src/alokasi-ruangan/alokasi-ruangan.module.ts @@ -0,0 +1,9 @@ +import { Module } from "@nestjs/common"; +import { AlokasiRuanganController } from "./alokasi-ruangan.controller"; +import { AlokasiRuanganService } from "./alokasi-ruangan.service"; + +@Module({ + controllers: [AlokasiRuanganController], + providers: [AlokasiRuanganService], +}) +export class AlokasiRuanganModule {} diff --git a/src/alokasi-ruangan/alokasi-ruangan.service.ts b/src/alokasi-ruangan/alokasi-ruangan.service.ts new file mode 100644 index 0000000000000000000000000000000000000000..fbdfc9bc17aed2cee1e7d2f5bd25a1a8b4e801d2 --- /dev/null +++ b/src/alokasi-ruangan/alokasi-ruangan.service.ts @@ -0,0 +1,198 @@ +import { BadRequestException, Injectable } from "@nestjs/common"; +import { InjectRepository } from "@nestjs/typeorm"; +import { DosenBimbingan } from "src/entities/dosenBimbingan.entity"; +import { PendaftaranSidsem } from "src/entities/pendaftaranSidsem"; +import { PengujiSidsem } from "src/entities/pengujiSidsem.entity"; +import { Like, Repository } from "typeorm"; +import { + GetAllPengajuanSidangItemDto, + GetAllPengajuanSidangReqQueryDto, + GetAllPengajuanSidangRespDto, + GetOnePengajuanSidangRespDto, + UpdateAlokasiRuanganReqDto, + UpdateAlokasiRuanganRespDto, +} from "./alokasi-ruangan.dto"; + +@Injectable() +export class AlokasiRuanganService { + constructor( + @InjectRepository(PendaftaranSidsem) + private pendaftaranSidsemRepo: Repository<PendaftaranSidsem>, + @InjectRepository(PengujiSidsem) + private pengujiSidsemRepo: Repository<PengujiSidsem>, + @InjectRepository(DosenBimbingan) + private dosenBimbinganRepo: Repository<DosenBimbingan>, + ) {} + + async findAll( + query: GetAllPengajuanSidangReqQueryDto, + ): Promise<GetAllPengajuanSidangRespDto> { + const [queryData, total] = await this.pendaftaranSidsemRepo.findAndCount({ + select: { + id: true, + waktuMulai: true, + tipe: true, + ruangan: true, + pendaftaranTesis: { + mahasiswa: { + nim: true, + nama: true, + }, + }, + }, + relations: { + pendaftaranTesis: { + mahasiswa: true, + }, + }, + take: query.limit, + skip: (query.page - 1) * query.limit, + where: { + tipe: query.jenisSidang, + pendaftaranTesis: { + mahasiswa: [ + { nim: Like(`%${query.search}%`) }, + { nama: Like(`%${query.search}%`) }, + ], + }, + }, + }); + + const data: GetAllPengajuanSidangItemDto[] = queryData.map((res) => ({ + idPengajuanSidsem: res.id, + nimMahasiswa: res.pendaftaranTesis.mahasiswa.nim, + namaMahasiswa: res.pendaftaranTesis.mahasiswa.nama, + jadwalSidang: res.waktuMulai.toISOString(), + jenisSidang: res.tipe, + ruangan: res.ruangan, + })); + + return { data, total }; + } + + async findOne(id: string): Promise<GetOnePengajuanSidangRespDto> { + // TODO handle errors + const sidsemQueryData = await this.pendaftaranSidsemRepo.findOne({ + select: { + id: true, + waktuMulai: true, + tipe: true, + ruangan: true, + pendaftaranTesis: { + id: true, + mahasiswa: { + nim: true, + nama: true, + email: true, + }, + jalurPilihan: true, + topik: { + judul: true, + deskripsi: true, + }, + }, + }, + relations: { + pendaftaranTesis: { + mahasiswa: true, + topik: true, + }, + }, + where: { + id, + }, + }); + + const pengujiQueryData = await this.pengujiSidsemRepo.find({ + select: { + dosen: { + nama: true, + }, + }, + relations: { + dosen: true, + }, + where: { + sidsem: { + id, + }, + }, + }); + + const pembimbingQueryData = await this.dosenBimbinganRepo.find({ + select: { + dosen: { + nama: true, + }, + }, + relations: { + dosen: true, + }, + where: { + idPendaftaran: sidsemQueryData.pendaftaranTesis.id, + }, + }); + + const data: GetOnePengajuanSidangRespDto = { + idPengajuanSidsem: sidsemQueryData.id, + nimMahasiswa: sidsemQueryData.pendaftaranTesis.mahasiswa.nim, + namaMahasiswa: sidsemQueryData.pendaftaranTesis.mahasiswa.nama, + // TODO jadwal sidang pake column apa + jadwalSidang: sidsemQueryData.waktuMulai.toISOString(), + jenisSidang: sidsemQueryData.tipe, + ruangan: sidsemQueryData.ruangan, + emailMahasiswa: sidsemQueryData.pendaftaranTesis.mahasiswa.email, + jalurPilihan: sidsemQueryData.pendaftaranTesis.jalurPilihan, + judulTopik: sidsemQueryData.pendaftaranTesis.topik.judul, + deskripsiTopik: sidsemQueryData.pendaftaranTesis.topik.deskripsi, + dosenPembimbing: pembimbingQueryData.map(({ dosen: { nama } }) => nama), + dosenPenguji: pengujiQueryData.map(({ dosen: { nama } }) => nama), + }; + + return data; + } + + async update( + id: string, + updateAlokasiRuanganDto: UpdateAlokasiRuanganReqDto, + ): Promise<UpdateAlokasiRuanganRespDto> { + const pendaftaranSidsem = await this.pendaftaranSidsemRepo.findOne({ + select: { + id: true, + waktuMulai: true, + tipe: true, + pendaftaranTesis: { + mahasiswa: { + nama: true, + nim: true, + }, + }, + }, + relations: { + pendaftaranTesis: { + mahasiswa: true, + }, + }, + where: { + id, + }, + }); + + if (pendaftaranSidsem === null) + throw new BadRequestException( + "Pendaftaran sidang/seminar with diven id does not exist", + ); + + pendaftaranSidsem.ruangan = updateAlokasiRuanganDto.ruangan; + await this.pendaftaranSidsemRepo.save(pendaftaranSidsem); + + return { + idPengajuanSidsem: pendaftaranSidsem.id, + jadwalSidang: pendaftaranSidsem.waktuMulai.toISOString(), + jenisSidang: pendaftaranSidsem.tipe, + namaMahasiswa: pendaftaranSidsem.pendaftaranTesis.mahasiswa.nama, + nimMahasiswa: pendaftaranSidsem.pendaftaranTesis.mahasiswa.nim, + ruangan: pendaftaranSidsem.ruangan, + }; + } +} diff --git a/src/app.module.ts b/src/app.module.ts index e2718653c5b8f7856d0baf4d258e4cacf378060b..d614aeddadc9774f760d3b8b4d083cf6a688191f 100644 --- a/src/app.module.ts +++ b/src/app.module.ts @@ -1,39 +1,40 @@ import { Module } from "@nestjs/common"; +import { TypeOrmModule } from "@nestjs/typeorm"; import { AppController } from "./app.controller"; import { AppService } from "./app.service"; -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 { Bimbingan } from "./entities/bimbingan.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 { PengajarKelas } from "./entities/pengajarKelas.entity"; +import { Pengguna } from "./entities/pengguna.entity"; +import { Topik } from "./entities/topik.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"; -import { AuthModule } from "./auth/auth.module"; +import { AlokasiRuanganModule } from "./alokasi-ruangan/alokasi-ruangan.module"; import { AlokasiTopikModule } from "./alokasi-topik/alokasi-topik.module"; -import { DashboardModule } from "./dashboard/dashboard.module"; +import { AuthModule } from "./auth/auth.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 { DashboardModule } from "./dashboard/dashboard.module"; +import { DosenBimbinganModule } from "./dosen-bimbingan/dosen-bimbingan.module"; 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 { Konfigurasi } from "./entities/konfigurasi.entity"; +import { MataKuliah } from "./entities/mataKuliah.entity"; +import { PendaftaranSidsem } from "./entities/pendaftaranSidsem"; +import { PengujiSidsem } from "./entities/pengujiSidsem.entity"; +import { SubmisiTugas } from "./entities/submisiTugas.entity"; +import { Tugas } from "./entities/tugas.entity"; +import { validate } from "./env.validation"; import { KelasModule } from "./kelas/kelas.module"; -import { SubmisiTugasModule } from "./submisi-tugas/submisi-tugas.module"; +import { KonfigurasiModule } from "./konfigurasi/konfigurasi.module"; import { NilaiModule } from "./nilai/nilai.module"; -import { PendaftaranSidsem } from "./entities/pendaftaranSidsem"; -import { DosenBimbinganModule } from "./dosen-bimbingan/dosen-bimbingan.module"; +import { RegistrasiTesisModule } from "./registrasi-tesis/registrasi-tesis.module"; +import { SubmisiTugasModule } from "./submisi-tugas/submisi-tugas.module"; +import { TugasModule } from "./tugas/tugas.module"; @Module({ imports: [ @@ -45,7 +46,7 @@ import { DosenBimbinganModule } from "./dosen-bimbingan/dosen-bimbingan.module"; username: process.env.POSTGRES_USER, password: process.env.POSTGRES_PASSWORD, database: process.env.POSTGRES_DATABASE, - ssl: process.env.POSTGRES_HOST !== "localhost", + // ssl: process.env.POSTGRES_HOST !== "localhost", entities: [ BerkasBimbingan, Bimbingan, @@ -80,6 +81,7 @@ import { DosenBimbinganModule } from "./dosen-bimbingan/dosen-bimbingan.module"; SubmisiTugasModule, NilaiModule, DosenBimbinganModule, + AlokasiRuanganModule, ], controllers: [AppController], providers: [AppService], diff --git a/src/entities/topik.entity.ts b/src/entities/topik.entity.ts index 7523412cc562e04c5c854a3e596195e903ee6d33..127540cc0619fa7863227bf11029f78160287a84 100644 --- a/src/entities/topik.entity.ts +++ b/src/entities/topik.entity.ts @@ -1,3 +1,4 @@ +import { ApiProperty } from "@nestjs/swagger"; import { Column, Entity, @@ -6,7 +7,6 @@ import { PrimaryGeneratedColumn, } from "typeorm"; import { Pengguna } from "./pengguna.entity"; -import { ApiProperty } from "@nestjs/swagger"; @Entity() export class Topik { @@ -22,7 +22,7 @@ export class Topik { @Column({ type: "text" }) deskripsi: string; - @ApiProperty({ type: Pengguna }) + @ApiProperty({ type: () => Pengguna }) @ManyToOne(() => Pengguna, (pengguna) => pengguna.id) @JoinColumn({ name: "idPengaju" }) pengaju: Pengguna;