diff --git a/src/app.module.ts b/src/app.module.ts
index 712c1a38b428725a4ff9c3359155df8a01721e05..9e9ee257abb0b46a1a689a034421ad2b3648ee36 100644
--- a/src/app.module.ts
+++ b/src/app.module.ts
@@ -4,22 +4,16 @@ import { AppService } from "./app.service";
 import { TypeOrmModule } from "@nestjs/typeorm";
 import { Bimbingan } from "./entities/bimbingan.entity";
 import { Pengguna } from "./entities/pengguna.entity";
-import { RangeJadwalSeminar } from "./entities/rangeJadwalSeminar.entity";
-import { Seminar } from "./entities/seminar.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";
+import { MahasiswaKelas } from "./entities/mahasiswaKelas.entity";
 import { PengajarKelas } from "./entities/pengajarKelas.entity";
 import { PendaftaranTesis } from "./entities/pendaftaranTesis.entity";
-import { RangeJadwalSidang } from "./entities/rangeJadwalSidang.entity";
-import { Ruangan } from "./entities/ruangan.entity";
-import { Sidang } from "./entities/sidang.entity";
+// import { Ruangan } from "./entities/ruangan.entity";
 import { Tugas } from "./entities/tugas.entity";
-import { PembimbingSeminar } from "./entities/pembimbingSeminar.entity";
-import { PembimbingSidang } from "./entities/pembimbingSidang.entity";
-import { PengujiSidang } from "./entities/pengujiSidang.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";
@@ -28,16 +22,18 @@ 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 { DosenBimbinganModule } from "./dosen-bimbingan/dosen-bimbingan.module";
-import { ApprovalModule } from "./approval/approval.module";
 import { validate } from "./env.validation";
-import { BerkasBimbingan } from "./entities/berkasBimbingan";
-import { MataKuliah } from "./entities/mataKuliah";
-import { SubmisiTugas } from "./entities/submisiTugas";
+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 { BerkasSubmisiTugas } from "./entities/berkasSubmisiTugas";
-import { BerkasTugas } from "./entities/berkasTugas";
-import { SubmisiModule } from "./submisi-tugas/submisi.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";
 
 @Module({
   imports: [
@@ -54,8 +50,7 @@ import { SubmisiModule } from "./submisi-tugas/submisi.module";
         BerkasBimbingan,
         Bimbingan,
         Pengguna,
-        RangeJadwalSeminar,
-        Seminar,
+        PendaftaranSidsem,
         Topik,
         AuditLog,
         DosenBimbingan,
@@ -63,13 +58,9 @@ import { SubmisiModule } from "./submisi-tugas/submisi.module";
         MahasiswaKelas,
         PengajarKelas,
         PendaftaranTesis,
-        RangeJadwalSidang,
-        Ruangan,
-        Sidang,
+        // Ruangan,
         Tugas,
-        PembimbingSeminar,
-        PembimbingSidang,
-        PengujiSidang,
+        PengujiSidsem,
         Konfigurasi,
         MataKuliah,
         SubmisiTugas,
@@ -85,9 +76,11 @@ import { SubmisiModule } from "./submisi-tugas/submisi.module";
     BimbinganModule,
     KonfigurasiModule,
     DosenBimbinganModule,
-    ApprovalModule,
-    SubmisiModule,
     KelasModule,
+    TugasModule,
+    SubmisiTugasModule,
+    NilaiModule,
+    DosenBimbinganModule,
   ],
   controllers: [AppController],
   providers: [AppService],
diff --git a/src/approval/approval.controller.ts b/src/approval/approval.controller.ts
deleted file mode 100644
index 866410794f5f767cd84ad5489fdcdc5817c9024b..0000000000000000000000000000000000000000
--- a/src/approval/approval.controller.ts
+++ /dev/null
@@ -1,60 +0,0 @@
-import { Controller, Patch, Param, UseGuards } from "@nestjs/common";
-import { ApprovalService } from "./approval.service";
-import {
-  PendaftaranTesis,
-  RegStatus,
-} from "src/entities/pendaftaranTesis.entity";
-import { CustomAuthGuard } from "src/middlewares/custom-auth.guard";
-import { RolesGuard } from "src/middlewares/roles.guard";
-import { RoleEnum } from "src/entities/pengguna.entity";
-import { Roles } from "src/middlewares/roles.decorator";
-import {
-  ApiBearerAuth,
-  ApiCookieAuth,
-  ApiOkResponse,
-  ApiTags,
-} from "@nestjs/swagger";
-import { ByIdParamDto } from "./approval.dto";
-
-@ApiTags("Approval")
-@ApiCookieAuth()
-@ApiBearerAuth()
-@Controller("approval")
-@UseGuards(CustomAuthGuard, RolesGuard)
-@Roles(RoleEnum.S2_PEMBIMBING)
-export class ApprovalController {
-  constructor(private readonly approvalService: ApprovalService) {}
-
-  @ApiOkResponse({ type: PendaftaranTesis })
-  @Patch(":id/approve")
-  async approvePendaftaran(
-    @Param() param: ByIdParamDto,
-  ): Promise<PendaftaranTesis> {
-    return this.approvalService.approvePendaftaran(
-      param.id,
-      RegStatus.APPROVED,
-    );
-  }
-
-  @ApiOkResponse({ type: PendaftaranTesis })
-  @Patch(":id/reject")
-  async declinePendaftaran(
-    @Param() param: ByIdParamDto,
-  ): Promise<PendaftaranTesis> {
-    return this.approvalService.approvePendaftaran(
-      param.id,
-      RegStatus.REJECTED,
-    );
-  }
-
-  @ApiOkResponse({ type: PendaftaranTesis })
-  @Patch(":id/interview")
-  async interviewPendaftaran(
-    @Param() param: ByIdParamDto,
-  ): Promise<PendaftaranTesis> {
-    return this.approvalService.approvePendaftaran(
-      param.id,
-      RegStatus.INTERVIEW,
-    );
-  }
-}
diff --git a/src/approval/approval.dto.ts b/src/approval/approval.dto.ts
deleted file mode 100644
index b9fea475cfd34a9705b8b8fcd66f0a69ca1995d2..0000000000000000000000000000000000000000
--- a/src/approval/approval.dto.ts
+++ /dev/null
@@ -1,8 +0,0 @@
-import { IsUUID } from "@nestjs/class-validator";
-import { ApiProperty } from "@nestjs/swagger";
-
-export class ByIdParamDto {
-  @IsUUID()
-  @ApiProperty({ example: "550e8400-e29b-41d4-a716-446655440000" })
-  id: string;
-}
diff --git a/src/approval/approval.module.ts b/src/approval/approval.module.ts
deleted file mode 100644
index c28790252c59277b51e473a33ab7d8b38ae32065..0000000000000000000000000000000000000000
--- a/src/approval/approval.module.ts
+++ /dev/null
@@ -1,12 +0,0 @@
-import { Module } from "@nestjs/common";
-import { TypeOrmModule } from "@nestjs/typeorm";
-import { PendaftaranTesis } from "src/entities/pendaftaranTesis.entity";
-import { ApprovalController } from "./approval.controller";
-import { ApprovalService } from "./approval.service";
-
-@Module({
-  imports: [TypeOrmModule.forFeature([PendaftaranTesis])],
-  controllers: [ApprovalController],
-  providers: [ApprovalService],
-})
-export class ApprovalModule {}
diff --git a/src/approval/approval.service.ts b/src/approval/approval.service.ts
deleted file mode 100644
index 8163efeda7b43052e82b6a5441b5590f384f1b6a..0000000000000000000000000000000000000000
--- a/src/approval/approval.service.ts
+++ /dev/null
@@ -1,33 +0,0 @@
-import { Injectable } from "@nestjs/common";
-import { InjectRepository } from "@nestjs/typeorm";
-import { FindOneOptions, Repository } from "typeorm";
-import {
-  PendaftaranTesis,
-  RegStatus,
-} from "src/entities/pendaftaranTesis.entity";
-
-@Injectable()
-export class ApprovalService {
-  constructor(
-    @InjectRepository(PendaftaranTesis)
-    private readonly pendaftaranRepository: Repository<PendaftaranTesis>,
-  ) {}
-
-  async approvePendaftaran(
-    id: string,
-    status: RegStatus,
-  ): Promise<PendaftaranTesis> {
-    try {
-      const findOneOptions: FindOneOptions<PendaftaranTesis> = {
-        where: { id },
-      };
-      const pendaftaran =
-        await this.pendaftaranRepository.findOneOrFail(findOneOptions);
-      pendaftaran.status = status;
-
-      return await this.pendaftaranRepository.save(pendaftaran);
-    } catch (error) {
-      throw new Error("Pendaftaran not found");
-    }
-  }
-}
diff --git a/src/bimbingan/bimbingan.dto.ts b/src/bimbingan/bimbingan.dto.ts
index 7409b67e8c4293b1d0565e02b61883b668acde42..f3afcdac8aeeb0380633e5609bc7b35d8c864013 100644
--- a/src/bimbingan/bimbingan.dto.ts
+++ b/src/bimbingan/bimbingan.dto.ts
@@ -14,7 +14,7 @@ import {
   PickType,
 } from "@nestjs/swagger";
 import { Type } from "class-transformer";
-import { BerkasBimbingan } from "src/entities/berkasBimbingan";
+import { BerkasBimbingan } from "src/entities/berkasBimbingan.entity";
 import { Bimbingan, BimbinganStatus } from "src/entities/bimbingan.entity";
 import {
   JalurEnum,
diff --git a/src/bimbingan/bimbingan.module.ts b/src/bimbingan/bimbingan.module.ts
index 25e02da5c5205c69b800de0b59e223c65948519c..30174c69ff5a33b4a5caa742095ef9f830c5eb8d 100644
--- a/src/bimbingan/bimbingan.module.ts
+++ b/src/bimbingan/bimbingan.module.ts
@@ -6,7 +6,7 @@ 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";
+import { BerkasBimbingan } from "src/entities/berkasBimbingan.entity";
 
 @Module({
   imports: [
diff --git a/src/bimbingan/bimbingan.service.ts b/src/bimbingan/bimbingan.service.ts
index 1947b81ab45a475f276c6a8d58c4ee043d8aaec6..52c1ba28e98f9c71aad2d88e5ebeafb8d1cb1d40 100644
--- a/src/bimbingan/bimbingan.service.ts
+++ b/src/bimbingan/bimbingan.service.ts
@@ -24,7 +24,7 @@ import {
   UpdateStatusDto,
   UpdateStatusResDto,
 } from "./bimbingan.dto";
-import { BerkasBimbingan } from "src/entities/berkasBimbingan";
+import { BerkasBimbingan } from "src/entities/berkasBimbingan.entity";
 
 @Injectable()
 export class BimbinganService {
diff --git a/src/dashboard/dashboard.controller.ts b/src/dashboard/dashboard.controller.ts
index bb16aebd0a8cb044554d293950f8056baa3b6c4b..31a3a4cc1dc88afe12ba4b05e16bc015fd2a41e9 100644
--- a/src/dashboard/dashboard.controller.ts
+++ b/src/dashboard/dashboard.controller.ts
@@ -8,6 +8,7 @@ import { AuthDto } from "src/auth/auth.dto";
 import { Request } from "express";
 import {
   DashboardDto,
+  DashboardMahasiswaResDto,
   GetDashboardDosbimQueryDto,
   JalurStatisticDto,
 } from "./dashboard.dto";
@@ -47,4 +48,16 @@ export class DashboardController {
       (request.user as AuthDto).id,
     );
   }
+
+  @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,
+    );
+  }
 }
diff --git a/src/dashboard/dashboard.dto.ts b/src/dashboard/dashboard.dto.ts
index 26fc7b8a907cc911108da94f0d34271f83643484..8d279937d0616eb007400145e622316c5d4b9012 100644
--- a/src/dashboard/dashboard.dto.ts
+++ b/src/dashboard/dashboard.dto.ts
@@ -1,7 +1,17 @@
-import { ApiProperty, ApiPropertyOptional, PickType } from "@nestjs/swagger";
-import { JalurEnum } from "../entities/pendaftaranTesis.entity";
+import {
+  ApiProperty,
+  ApiPropertyOptional,
+  OmitType,
+  PickType,
+} from "@nestjs/swagger";
+import {
+  JalurEnum,
+  PendaftaranTesis,
+} from "../entities/pendaftaranTesis.entity";
 import { Topik } from "src/entities/topik.entity";
 import { Pengguna } from "src/entities/pengguna.entity";
+import { Bimbingan } from "src/entities/bimbingan.entity";
+import { PendaftaranSidsem } from "src/entities/pendaftaranSidsem";
 import { IsOptional } from "class-validator";
 import { BimbinganStatus } from "src/entities/bimbingan.entity";
 
@@ -10,8 +20,38 @@ class PickedMhsDashboard extends PickType(Pengguna, [
   "id",
   "nama",
   "nim",
+  "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;
@@ -26,7 +66,7 @@ export class DashboardDto {
   topik: PickedTopikDashboard;
 
   @ApiProperty()
-  mahasiswa: PickedMhsDashboard;
+  mahasiswa: NoEmailUserDashboard;
 }
 
 export class JalurStatisticDto {
@@ -37,6 +77,38 @@ 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 160e96061fc16dbb1c6ee6f0328dcf31239e2dad..8f8f447d98f6ce5c3d40de228881aa244d364bba 100644
--- a/src/dashboard/dashboard.module.ts
+++ b/src/dashboard/dashboard.module.ts
@@ -6,11 +6,22 @@ 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]),
+    TypeOrmModule.forFeature([
+      PendaftaranTesis,
+      Pengguna,
+      Topik,
+      Konfigurasi,
+      Bimbingan,
+      PendaftaranSidsem,
+      DosenBimbingan,
+    ]),
     BimbinganModule,
   ],
   controllers: [DashboardController],
diff --git a/src/dashboard/dashboard.service.ts b/src/dashboard/dashboard.service.ts
index c4fce66ca46e0245a43efa486e49beea9ecf33df..c2bfcb12e4e7c8bbcbfdfacf6b77a224c475a5b1 100644
--- a/src/dashboard/dashboard.service.ts
+++ b/src/dashboard/dashboard.service.ts
@@ -7,7 +7,18 @@ import {
 } from "../entities/pendaftaranTesis.entity";
 import { Pengguna } from "../entities/pengguna.entity";
 import { Konfigurasi } from "src/entities/konfigurasi.entity";
-import { DashboardDto, JalurStatisticDto } from "./dashboard.dto";
+import { Bimbingan } from "src/entities/bimbingan.entity";
+import {
+  DashboardDto,
+  DashboardMahasiswaResDto,
+  JalurStatisticDto,
+  NoNIMUserDashboard,
+} from "./dashboard.dto";
+import {
+  PendaftaranSidsem,
+  TipeSidsemEnum,
+} from "src/entities/pendaftaranSidsem";
+import { DosenBimbingan } from "src/entities/dosenBimbingan.entity";
 import { BimbinganService } from "src/bimbingan/bimbingan.service";
 
 @Injectable()
@@ -19,6 +30,12 @@ export class DashboardService {
     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>,
     private bimbinganService: BimbinganService,
   ) {}
 
@@ -131,4 +148,126 @@ 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,
+        })),
+      },
+    };
+  }
 }
diff --git a/src/dosen-bimbingan/dosen-bimbingan.controller.ts b/src/dosen-bimbingan/dosen-bimbingan.controller.ts
index ef0b6acae18dc2d24c5e25dbdf88eaa8158a46df..81a31679c5443f03c6469b478be79566aa2e80b0 100644
--- a/src/dosen-bimbingan/dosen-bimbingan.controller.ts
+++ b/src/dosen-bimbingan/dosen-bimbingan.controller.ts
@@ -1,30 +1,16 @@
-import {
-  Body,
-  Controller,
-  Delete,
-  Get,
-  NotFoundException,
-  Put,
-  Query,
-  UseGuards,
-} from "@nestjs/common";
+import { Controller, Get, UseGuards } from "@nestjs/common";
 import {
   ApiBearerAuth,
   ApiCookieAuth,
   ApiOkResponse,
+  ApiOperation,
   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 {
-  DosbimOptQueryDto,
-  DosbimQueryDto,
-  GetDosbimResDto,
-  SuccessResDto,
-  UpdateDosbimDto,
-} from "./dosen-bimbingan.dto";
+import { GetDosbimResDto } from "./dosen-bimbingan.dto";
 import { DosenBimbinganService } from "./dosen-bimbingan.service";
 
 @ApiTags("Dosen Bimbingan")
@@ -32,49 +18,17 @@ import { DosenBimbinganService } from "./dosen-bimbingan.service";
 @ApiBearerAuth()
 @Controller("dosen-bimbingan")
 @UseGuards(CustomAuthGuard, RolesGuard)
+@Roles(RoleEnum.ADMIN, RoleEnum.S2_TIM_TESIS, RoleEnum.S2_MAHASISWA)
 export class DosenBimbinganController {
   constructor(private readonly dosbimService: DosenBimbinganService) {}
 
   @ApiOkResponse({ type: [GetDosbimResDto] })
-  @Roles(
-    RoleEnum.ADMIN,
-    RoleEnum.S2_TIM_TESIS,
-    RoleEnum.S2_MAHASISWA,
-    RoleEnum.S2_TIM_TESIS,
-  )
+  @ApiOperation({
+    summary:
+      "Get all available dosen bimbingan. Roles: ADMIN, S2_TIM_TESIS, S2_MAHASISWA",
+  })
   @Get()
-  async get(@Query() query: DosbimOptQueryDto) {
-    if (!query.regId) return await this.dosbimService.getAll();
-
-    const res = await this.dosbimService.findByRegId(query.regId);
-    const mappedRes: GetDosbimResDto[] = res.map((r) => r.dosen);
-    return mappedRes;
-  }
-
-  @ApiOkResponse({ type: SuccessResDto })
-  @Roles(RoleEnum.ADMIN, RoleEnum.S2_TIM_TESIS)
-  @Put()
-  async updateByRegId(
-    @Query() query: DosbimQueryDto,
-    @Body() body: UpdateDosbimDto,
-  ): Promise<SuccessResDto> {
-    await this.dosbimService.updateByRegId(query.regId, body.dosbimIds);
-
-    return {
-      status: "ok",
-    };
-  }
-
-  @ApiOkResponse({ type: SuccessResDto })
-  @Roles(RoleEnum.ADMIN, RoleEnum.S2_TIM_TESIS)
-  @Delete()
-  async deleteByRegId(@Query() query: DosbimQueryDto): Promise<SuccessResDto> {
-    const res = await this.dosbimService.removeByRegId(query.regId);
-
-    if (!res.affected) throw new NotFoundException();
-
-    return {
-      status: "ok",
-    };
+  async get() {
+    return await this.dosbimService.getAll();
   }
 }
diff --git a/src/dosen-bimbingan/dosen-bimbingan.dto.ts b/src/dosen-bimbingan/dosen-bimbingan.dto.ts
index 11d56b1135d145102b6ac69a56474ae66975dac5..23c823e7389a759b7d532c02543e736d5bbaf0d4 100644
--- a/src/dosen-bimbingan/dosen-bimbingan.dto.ts
+++ b/src/dosen-bimbingan/dosen-bimbingan.dto.ts
@@ -1,12 +1,6 @@
-import {
-  ArrayMaxSize,
-  IsArray,
-  IsUUID,
-  ArrayMinSize,
-  ArrayUnique,
-  IsOptional,
-} from "@nestjs/class-validator";
-import { ApiProperty, ApiPropertyOptional, PickType } from "@nestjs/swagger";
+import { IsOptional } from "@nestjs/class-validator";
+import { ApiPropertyOptional, PickType } from "@nestjs/swagger";
+import { IsUUID } from "class-validator";
 import { Pengguna } from "src/entities/pengguna.entity";
 
 export class DosbimOptQueryDto {
@@ -16,30 +10,6 @@ export class DosbimOptQueryDto {
   regId?: string;
 }
 
-export class DosbimQueryDto {
-  @ApiProperty({ example: "550e8400-e29b-41d4-a716-446655440000" })
-  @IsUUID()
-  regId: string;
-}
-
-export class UpdateDosbimDto {
-  @ApiProperty({
-    type: [String],
-    example: ["550e8400-e29b-41d4-a716-446655440000"],
-  })
-  @IsArray()
-  @IsUUID("all", { each: true })
-  @ArrayMinSize(1)
-  @ArrayMaxSize(3)
-  @ArrayUnique()
-  dosbimIds: string[];
-}
-
-export class SuccessResDto {
-  @ApiProperty()
-  status: string;
-}
-
 export class GetDosbimResDto extends PickType(Pengguna, [
   "id",
   "email",
diff --git a/src/dosen-bimbingan/dosen-bimbingan.service.ts b/src/dosen-bimbingan/dosen-bimbingan.service.ts
index 456364ee4bc51946474c2cd5e54e92fc26909fe8..88f0f9d159f8f0e4949c06a4c5bbcb7ad9a9643a 100644
--- a/src/dosen-bimbingan/dosen-bimbingan.service.ts
+++ b/src/dosen-bimbingan/dosen-bimbingan.service.ts
@@ -1,29 +1,13 @@
-import {
-  BadRequestException,
-  Injectable,
-  InternalServerErrorException,
-} from "@nestjs/common";
+import { Injectable } from "@nestjs/common";
 import { InjectRepository } from "@nestjs/typeorm";
-import { DosenBimbingan } from "src/entities/dosenBimbingan.entity";
-import {
-  PendaftaranTesis,
-  RegStatus,
-} from "src/entities/pendaftaranTesis.entity";
 import { Pengguna, RoleEnum } from "src/entities/pengguna.entity";
-import { KonfigurasiService } from "src/konfigurasi/konfigurasi.service";
-import { ArrayContains, DataSource, Repository } from "typeorm";
+import { ArrayContains, Repository } from "typeorm";
 
 @Injectable()
 export class DosenBimbinganService {
   constructor(
-    @InjectRepository(DosenBimbingan)
-    private dosbimRepo: Repository<DosenBimbingan>,
     @InjectRepository(Pengguna)
     private penggunaRepo: Repository<Pengguna>,
-    @InjectRepository(PendaftaranTesis)
-    private pendaftaranRepo: Repository<PendaftaranTesis>,
-    private konfService: KonfigurasiService,
-    private dataSource: DataSource,
   ) {}
 
   async getAll() {
@@ -38,97 +22,4 @@ export class DosenBimbinganService {
       },
     });
   }
-
-  async findByRegId(regId: string) {
-    return await this.dosbimRepo.find({
-      select: {
-        id: true,
-        dosen: {
-          id: true,
-          nama: true,
-          email: true,
-        },
-      },
-      relations: {
-        dosen: true,
-      },
-      where: {
-        pendaftaran: {
-          id: regId,
-        },
-      },
-    });
-  }
-
-  async updateByRegId(regId: string, dosbimIds: string[]) {
-    const [reg, currPeriod] = await Promise.all([
-      this.pendaftaranRepo.findOne({
-        select: { id: true, status: true },
-        where: { id: regId },
-        relations: { topik: true },
-      }),
-      this.konfService.getKonfigurasiByKey(process.env.KONF_PERIODE_KEY),
-    ]);
-
-    if (!reg || reg.status !== RegStatus.APPROVED) {
-      throw new BadRequestException(
-        "Registrasi tidak ditemukan atau tidak disetujui.",
-      );
-    }
-
-    if (!currPeriod || currPeriod !== reg.topik.periode) {
-      throw new BadRequestException(
-        "Periode belum dikonfigurasi atau tidak sesuai dengan periode sekarang.",
-      );
-    }
-
-    for (const dosbimId of dosbimIds) {
-      const res = await this.penggunaRepo.findOne({
-        select: {
-          id: true,
-        },
-        where: {
-          id: dosbimId,
-          roles: ArrayContains([RoleEnum.S2_PEMBIMBING]),
-        },
-      });
-
-      if (!res) {
-        throw new BadRequestException("Invalid pembimbing id");
-      }
-    }
-
-    const queryRunner = this.dataSource.createQueryRunner();
-
-    await queryRunner.connect();
-    await queryRunner.startTransaction();
-    try {
-      await this.removeByRegId(regId);
-
-      for (const dosbimId of dosbimIds) {
-        await queryRunner.manager.getRepository(DosenBimbingan).insert({
-          idPendaftaran: regId,
-          idDosen: dosbimId,
-        });
-      }
-
-      await queryRunner.commitTransaction();
-    } catch (err) {
-      await queryRunner.rollbackTransaction();
-
-      throw new InternalServerErrorException();
-    } finally {
-      await queryRunner.release();
-    }
-  }
-
-  async removeByRegId(regId: string) {
-    return await this.dosbimRepo
-      .createQueryBuilder()
-      .delete()
-      .where("idPendaftaran = :idPendaftaran", {
-        idPendaftaran: regId,
-      })
-      .execute();
-  }
 }
diff --git a/src/entities/berkasBimbingan.ts b/src/entities/berkasBimbingan.entity.ts
similarity index 77%
rename from src/entities/berkasBimbingan.ts
rename to src/entities/berkasBimbingan.entity.ts
index 6acceea0c51e5cfaa149724cb5f7824fa331544c..35b44ace3f7a70db3a1bcbd210c3a95be701c99a 100644
--- a/src/entities/berkasBimbingan.ts
+++ b/src/entities/berkasBimbingan.entity.ts
@@ -9,7 +9,9 @@ export class BerkasBimbingan {
   @PrimaryGeneratedColumn("uuid")
   id: string;
 
-  @ManyToOne(() => Bimbingan, (bimbingan) => bimbingan.id)
+  @ManyToOne(() => Bimbingan, (bimbingan) => bimbingan.id, {
+    orphanedRowAction: "delete",
+  })
   bimbingan: Bimbingan;
 
   @Column({ type: "text" })
@@ -19,6 +21,6 @@ export class BerkasBimbingan {
 
   @Column({ type: "text" })
   @IsUrl()
-  @ApiProperty()
+  @ApiProperty({ example: "https://example.com/berkas.pdf" })
   url: string;
 }
diff --git a/src/entities/berkasSubmisiTugas.entity.ts b/src/entities/berkasSubmisiTugas.entity.ts
new file mode 100644
index 0000000000000000000000000000000000000000..e48d5e8d88f1449b767d3e88f1f9862e8e0f6560
--- /dev/null
+++ b/src/entities/berkasSubmisiTugas.entity.ts
@@ -0,0 +1,28 @@
+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/berkasSubmisiTugas.ts b/src/entities/berkasSubmisiTugas.ts
deleted file mode 100644
index 9694eb7069548036483845915c5a61b8fc7e2a75..0000000000000000000000000000000000000000
--- a/src/entities/berkasSubmisiTugas.ts
+++ /dev/null
@@ -1,17 +0,0 @@
-import { Column, Entity, ManyToOne, PrimaryGeneratedColumn } from "typeorm";
-import { SubmisiTugas } from "./submisiTugas";
-
-@Entity()
-export class BerkasSubmisiTugas {
-  @PrimaryGeneratedColumn("uuid")
-  id: string;
-
-  @ManyToOne(() => SubmisiTugas, (submisi) => submisi.id)
-  submisiTugas: SubmisiTugas;
-
-  @Column({ type: "text" })
-  nama: string;
-
-  @Column({ type: "text" })
-  url: string;
-}
diff --git a/src/entities/berkasTugas.entity.ts b/src/entities/berkasTugas.entity.ts
new file mode 100644
index 0000000000000000000000000000000000000000..c18bccfa9c839754ca8ed7bb0f5789f8841e3ff1
--- /dev/null
+++ b/src/entities/berkasTugas.entity.ts
@@ -0,0 +1,26 @@
+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/berkasTugas.ts b/src/entities/berkasTugas.ts
deleted file mode 100644
index 91cf3ec86a5b6d8f3b243e6d076e41bfa39adcbd..0000000000000000000000000000000000000000
--- a/src/entities/berkasTugas.ts
+++ /dev/null
@@ -1,17 +0,0 @@
-import { Column, Entity, ManyToOne, PrimaryGeneratedColumn } from "typeorm";
-import { Tugas } from "./tugas.entity";
-
-@Entity()
-export class BerkasTugas {
-  @PrimaryGeneratedColumn("uuid")
-  id: string;
-
-  @ManyToOne(() => Tugas, (tugas) => tugas.id)
-  tugas: Tugas;
-
-  @Column({ type: "text" })
-  nama: string;
-
-  @Column({ type: "text" })
-  url: string;
-}
diff --git a/src/entities/bimbingan.entity.ts b/src/entities/bimbingan.entity.ts
index 33df7a17badd1b84d408bb313c47c46fad2410b7..a846e824d3d98389e6a2402efad351f49188f9fb 100644
--- a/src/entities/bimbingan.entity.ts
+++ b/src/entities/bimbingan.entity.ts
@@ -7,7 +7,7 @@ import {
 } from "typeorm";
 import { PendaftaranTesis } from "./pendaftaranTesis.entity";
 import { ApiProperty, ApiPropertyOptional } from "@nestjs/swagger";
-import { BerkasBimbingan } from "./berkasBimbingan";
+import { BerkasBimbingan } from "./berkasBimbingan.entity";
 
 export enum BimbinganStatus {
   LANCAR = "LANCAR",
diff --git a/src/entities/kelas.entity.ts b/src/entities/kelas.entity.ts
index cc9df1e26aee879759d3260c02fa58378c7a5238..deec327ed9a9028f7eb8ddd886826f35a3294c80 100644
--- a/src/entities/kelas.entity.ts
+++ b/src/entities/kelas.entity.ts
@@ -6,10 +6,10 @@ import {
   OneToMany,
   PrimaryGeneratedColumn,
 } from "typeorm";
-import { MataKuliah } from "./mataKuliah";
+import { MataKuliah } from "./mataKuliah.entity";
 import { ApiProperty } from "@nestjs/swagger";
 import { PengajarKelas } from "./pengajarKelas.entity";
-import { MahasiswaKelas } from "./mahasiswaKelas";
+import { MahasiswaKelas } from "./mahasiswaKelas.entity";
 import {
   IsPositive,
   IsString,
@@ -17,6 +17,7 @@ import {
   Length,
   MaxLength,
 } from "@nestjs/class-validator";
+import { Tugas } from "./tugas.entity";
 
 @Entity()
 export class Kelas {
@@ -35,6 +36,7 @@ export class Kelas {
   @Column({ type: "text" })
   periode: string;
 
+  @ApiProperty({ type: MataKuliah })
   @ManyToOne(() => MataKuliah, (mataKuliah) => mataKuliah.kode)
   @JoinColumn({ name: "mataKuliahKode" })
   mataKuliah: MataKuliah;
@@ -56,4 +58,7 @@ export class Kelas {
 
   @OneToMany(() => MahasiswaKelas, (mahasiswa) => mahasiswa.kelas)
   mahasiswa: MahasiswaKelas[];
+
+  @OneToMany(() => Tugas, (tugas) => tugas.kelas)
+  tugas: Tugas[];
 }
diff --git a/src/entities/mahasiswaKelas.ts b/src/entities/mahasiswaKelas.entity.ts
similarity index 96%
rename from src/entities/mahasiswaKelas.ts
rename to src/entities/mahasiswaKelas.entity.ts
index 7d47e0abfa6461f68fc0fa11ef736601cd397c93..0753aa6f75f865913b7e42209248310948dc67c2 100644
--- a/src/entities/mahasiswaKelas.ts
+++ b/src/entities/mahasiswaKelas.entity.ts
@@ -28,5 +28,5 @@ export class MahasiswaKelas {
   mahasiswaId: string;
 
   @Column({ type: "real", nullable: true })
-  nilaiAkhir: number;
+  nilaiAkhir?: number;
 }
diff --git a/src/entities/mataKuliah.ts b/src/entities/mataKuliah.entity.ts
similarity index 70%
rename from src/entities/mataKuliah.ts
rename to src/entities/mataKuliah.entity.ts
index 6f78c692074b3a7c80123deac69c84643c1c7a11..9e1fed5d807a176b89288144623ec72575ef5e33 100644
--- a/src/entities/mataKuliah.ts
+++ b/src/entities/mataKuliah.entity.ts
@@ -1,6 +1,7 @@
 import { ApiProperty } from "@nestjs/swagger";
 import { IsString, Length, MaxLength } from "class-validator";
-import { Column, Entity, PrimaryColumn } from "typeorm";
+import { Column, Entity, OneToMany, PrimaryColumn } from "typeorm";
+import { Kelas } from "./kelas.entity";
 
 @Entity()
 export class MataKuliah {
@@ -15,4 +16,7 @@ export class MataKuliah {
   @MaxLength(256)
   @Column({ type: "varchar", length: 256 })
   nama: string;
+
+  @OneToMany(() => Kelas, (kelas) => kelas.mataKuliah)
+  kelas: Kelas[];
 }
diff --git a/src/entities/pembimbingSidang.entity.ts b/src/entities/pembimbingSidang.entity.ts
deleted file mode 100644
index a3d9dbec55b4d3e94d7a1e6d8b14262b10752e3d..0000000000000000000000000000000000000000
--- a/src/entities/pembimbingSidang.entity.ts
+++ /dev/null
@@ -1,15 +0,0 @@
-import { Entity, ManyToOne, PrimaryGeneratedColumn } from "typeorm";
-import { Sidang } from "./sidang.entity";
-import { Pengguna } from "./pengguna.entity";
-
-@Entity()
-export class PembimbingSidang {
-  @PrimaryGeneratedColumn("uuid")
-  id: string;
-
-  @ManyToOne(() => Sidang, (sidang) => sidang.id)
-  sidang: Sidang;
-
-  @ManyToOne(() => Pengguna, (pengguna) => pengguna.id)
-  dosen: Pengguna;
-}
diff --git a/src/entities/pendaftaranSidsem.ts b/src/entities/pendaftaranSidsem.ts
new file mode 100644
index 0000000000000000000000000000000000000000..a733c9cd7aedf8825202a92c3b509e271486847f
--- /dev/null
+++ b/src/entities/pendaftaranSidsem.ts
@@ -0,0 +1,62 @@
+import {
+  Column,
+  Entity,
+  ManyToOne,
+  OneToMany,
+  PrimaryGeneratedColumn,
+} from "typeorm";
+import { PendaftaranTesis } from "./pendaftaranTesis.entity";
+// import { Ruangan } from "./ruangan.entity";
+import { ApiProperty } from "@nestjs/swagger";
+import { PengujiSidsem } from "./pengujiSidsem.entity";
+
+export enum TipeSidsemEnum {
+  SEMINAR_1 = "SEMINAR_1",
+  SEMINAR_2 = "SEMINAR_2",
+  SIDANG = "SIDANG",
+}
+
+@Entity()
+export class PendaftaranSidsem {
+  @ApiProperty({ example: "550e8400-e29b-41d4-a716-446655440000" })
+  @PrimaryGeneratedColumn("uuid")
+  id: string;
+
+  @ApiProperty({ enum: TipeSidsemEnum })
+  @Column({ type: "enum", enum: TipeSidsemEnum })
+  tipe: TipeSidsemEnum;
+
+  @ApiProperty()
+  @Column({ type: "boolean", default: false })
+  ditolak: boolean;
+
+  @ApiProperty()
+  @Column({ type: "boolean", nullable: true })
+  lulus: boolean;
+
+  @ApiProperty()
+  @Column({ type: "timestamptz", nullable: true })
+  waktuMulai: Date;
+
+  @ApiProperty()
+  @Column({ type: "timestamptz", nullable: true })
+  waktuSelesai: Date;
+
+  @ApiProperty()
+  @Column({ type: "text", nullable: true })
+  linkw2m: string;
+
+  @ManyToOne(() => PendaftaranTesis, (pendaftaranTesis) => pendaftaranTesis.id)
+  pendaftaranTesis: PendaftaranTesis;
+
+  // @ApiProperty({ type: Ruangan, nullable: true })
+  // @ManyToOne(() => Ruangan, (ruangan) => ruangan.id)
+  // ruangan: Ruangan;
+
+  @ApiProperty()
+  @Column({ type: "text", nullable: true })
+  ruangan: string;
+
+  @OneToMany(() => PengujiSidsem, (pengujiSidsem) => pengujiSidsem.sidsem)
+  penguji: PengujiSidsem[];
+}
diff --git a/src/entities/pendaftaranTesis.entity.ts b/src/entities/pendaftaranTesis.entity.ts
index 0d4c46157b1411288e0788be8d9b3052a1650a6e..e9a8fa31c2f525bcc45c5d87cf250d018901ceda 100644
--- a/src/entities/pendaftaranTesis.entity.ts
+++ b/src/entities/pendaftaranTesis.entity.ts
@@ -1,6 +1,7 @@
 import {
   Column,
   Entity,
+  JoinColumn,
   ManyToOne,
   OneToMany,
   PrimaryGeneratedColumn,
@@ -55,12 +56,19 @@ export class PendaftaranTesis {
   @Column({ type: "enum", enum: RegStatus, default: RegStatus.NOT_ASSIGNED })
   status: RegStatus;
 
-  @ManyToOne(() => Topik, (topik) => topik.id)
+  @ApiProperty({ type: Topik })
+  @ManyToOne(() => Topik, (topik) => topik.id, { cascade: true })
   topik: Topik;
 
+  @ApiProperty()
   @ManyToOne(() => Pengguna, (pengguna) => pengguna.id)
+  @JoinColumn({ name: "mahasiswaId" })
   mahasiswa: Pengguna;
 
+  @Column()
+  mahasiswaId: string;
+
+  @ApiProperty()
   @ManyToOne(() => Pengguna, (pengguna) => pengguna.id)
   penerima: Pengguna;
 
diff --git a/src/entities/pengguna.entity.ts b/src/entities/pengguna.entity.ts
index 5739584b8d2bb86cfc6538e9180cd887505e684c..c28a606ea4031436e36fc7958c0756a78f898cbe 100644
--- a/src/entities/pengguna.entity.ts
+++ b/src/entities/pengguna.entity.ts
@@ -1,9 +1,12 @@
+import { IsString } from "@nestjs/class-validator";
 import {
   ApiHideProperty,
   ApiProperty,
   ApiPropertyOptional,
 } from "@nestjs/swagger";
-import { Column, Entity, PrimaryGeneratedColumn } from "typeorm";
+import { Column, Entity, OneToMany, PrimaryGeneratedColumn } from "typeorm";
+import { PendaftaranTesis } from "./pendaftaranTesis.entity";
+import { SubmisiTugas } from "./submisiTugas.entity";
 
 export enum RoleEnum {
   ADMIN = "ADMIN",
@@ -50,4 +53,15 @@ export class Pengguna {
     default: [],
   })
   roles: RoleEnum[];
+
+  @ApiPropertyOptional()
+  @IsString()
+  @Column({ type: "text", nullable: true })
+  kontak: string;
+
+  @OneToMany(() => PendaftaranTesis, (pendaftaran) => pendaftaran.mahasiswa)
+  pendaftaranTesis: PendaftaranTesis[];
+
+  @OneToMany(() => SubmisiTugas, (submisiTugas) => submisiTugas.mahasiswa)
+  submisiTugas: SubmisiTugas[];
 }
diff --git a/src/entities/pengujiSidang.entity.ts b/src/entities/pengujiSidang.entity.ts
deleted file mode 100644
index dfcd36b5c2f5e06bebad302b3488aedff86338c7..0000000000000000000000000000000000000000
--- a/src/entities/pengujiSidang.entity.ts
+++ /dev/null
@@ -1,15 +0,0 @@
-import { Entity, ManyToOne, PrimaryGeneratedColumn } from "typeorm";
-import { Sidang } from "./sidang.entity";
-import { Pengguna } from "./pengguna.entity";
-
-@Entity()
-export class PengujiSidang {
-  @PrimaryGeneratedColumn("uuid")
-  id: string;
-
-  @ManyToOne(() => Sidang, (sidang) => sidang.id)
-  sidang: Sidang;
-
-  @ManyToOne(() => Pengguna, (pengguna) => pengguna.id)
-  dosen: Pengguna;
-}
diff --git a/src/entities/pembimbingSeminar.entity.ts b/src/entities/pengujiSidsem.entity.ts
similarity index 54%
rename from src/entities/pembimbingSeminar.entity.ts
rename to src/entities/pengujiSidsem.entity.ts
index 024ebe5b57e6835441faa33d687137fb82565cbb..09aebe7c16acbb068ae8a26c341b02e996e6a7a3 100644
--- a/src/entities/pembimbingSeminar.entity.ts
+++ b/src/entities/pengujiSidsem.entity.ts
@@ -1,14 +1,17 @@
 import { Entity, ManyToOne, PrimaryGeneratedColumn } from "typeorm";
-import { Seminar } from "./seminar.entity";
 import { Pengguna } from "./pengguna.entity";
+import { PendaftaranSidsem } from "./pendaftaranSidsem";
 
 @Entity()
-export class PembimbingSeminar {
+export class PengujiSidsem {
   @PrimaryGeneratedColumn("uuid")
   id: string;
 
-  @ManyToOne(() => Seminar, (seminar) => seminar.id)
-  seminar: Seminar;
+  @ManyToOne(
+    () => PendaftaranSidsem,
+    (pendaftaranSidsem) => pendaftaranSidsem.id,
+  )
+  sidsem: PendaftaranSidsem;
 
   @ManyToOne(() => Pengguna, (pengguna) => pengguna.id)
   dosen: Pengguna;
diff --git a/src/entities/rangeJadwalSeminar.entity.ts b/src/entities/rangeJadwalSeminar.entity.ts
deleted file mode 100644
index fa16bf749d40cdfeff7253f61bc098063a7348a3..0000000000000000000000000000000000000000
--- a/src/entities/rangeJadwalSeminar.entity.ts
+++ /dev/null
@@ -1,7 +0,0 @@
-import { Entity, PrimaryGeneratedColumn } from "typeorm";
-
-@Entity()
-export class RangeJadwalSeminar {
-  @PrimaryGeneratedColumn("uuid")
-  id: string;
-}
diff --git a/src/entities/rangeJadwalSidang.entity.ts b/src/entities/rangeJadwalSidang.entity.ts
deleted file mode 100644
index 6479aef875d9d7b23cd9db1c00317af5e60756cd..0000000000000000000000000000000000000000
--- a/src/entities/rangeJadwalSidang.entity.ts
+++ /dev/null
@@ -1,7 +0,0 @@
-import { Entity, PrimaryGeneratedColumn } from "typeorm";
-
-@Entity()
-export class RangeJadwalSidang {
-  @PrimaryGeneratedColumn("uuid")
-  id: string;
-}
diff --git a/src/entities/ruangan.entity.ts b/src/entities/ruangan.entity.ts
index 97df4c092f30bd329d8c3ece86d385bd0e6300c2..86dfaf804683751d50f397df3a55f00488dc0bfa 100644
--- a/src/entities/ruangan.entity.ts
+++ b/src/entities/ruangan.entity.ts
@@ -1,7 +1,13 @@
-import { Entity, PrimaryGeneratedColumn } from "typeorm";
+// import { ApiProperty } from "@nestjs/swagger";
+// import { Column, Entity, PrimaryGeneratedColumn } from "typeorm";
 
-@Entity()
-export class Ruangan {
-  @PrimaryGeneratedColumn("uuid")
-  id: string;
-}
+// @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/seminar.entity.ts b/src/entities/seminar.entity.ts
deleted file mode 100644
index 7b0e30b783be1e1401448e73f5fd078f7d4cdd0c..0000000000000000000000000000000000000000
--- a/src/entities/seminar.entity.ts
+++ /dev/null
@@ -1,22 +0,0 @@
-import { Entity, ManyToOne, PrimaryGeneratedColumn } from "typeorm";
-import { Pengguna } from "./pengguna.entity";
-import { RangeJadwalSeminar } from "./rangeJadwalSeminar.entity";
-import { Ruangan } from "./ruangan.entity";
-
-@Entity()
-export class Seminar {
-  @PrimaryGeneratedColumn("uuid")
-  id: string;
-
-  @ManyToOne(() => Pengguna, (pengguna) => pengguna.id)
-  mahasiswa: Pengguna;
-
-  @ManyToOne(
-    () => RangeJadwalSeminar,
-    (rangeJadwalSeminar) => rangeJadwalSeminar.id,
-  )
-  rangeJadwal: RangeJadwalSeminar;
-
-  @ManyToOne(() => Ruangan, (ruangan) => ruangan.id)
-  ruangan: Ruangan;
-}
diff --git a/src/entities/sidang.entity.ts b/src/entities/sidang.entity.ts
deleted file mode 100644
index c2dc57ecdca72af17a7496883e74a716f46dbf1c..0000000000000000000000000000000000000000
--- a/src/entities/sidang.entity.ts
+++ /dev/null
@@ -1,22 +0,0 @@
-import { Entity, ManyToOne, PrimaryGeneratedColumn } from "typeorm";
-import { Pengguna } from "./pengguna.entity";
-import { RangeJadwalSidang } from "./rangeJadwalSidang.entity";
-import { Ruangan } from "./ruangan.entity";
-
-@Entity()
-export class Sidang {
-  @PrimaryGeneratedColumn("uuid")
-  id: string;
-
-  @ManyToOne(() => Pengguna, (pengguna) => pengguna.id)
-  mahasiswa: Pengguna;
-
-  @ManyToOne(
-    () => RangeJadwalSidang,
-    (rangeJadwalSidang) => rangeJadwalSidang.id,
-  )
-  rangeJadwal: RangeJadwalSidang;
-
-  @ManyToOne(() => Ruangan, (ruangan) => ruangan.id)
-  ruangan: Ruangan;
-}
diff --git a/src/entities/submisiTugas.ts b/src/entities/submisiTugas.entity.ts
similarity index 50%
rename from src/entities/submisiTugas.ts
rename to src/entities/submisiTugas.entity.ts
index ef0c8da1a854dc6b985b23b313a65eaba2aa7d22..be380d37d6c0b4dadf641f794735a343990e13b5 100644
--- a/src/entities/submisiTugas.ts
+++ b/src/entities/submisiTugas.entity.ts
@@ -1,34 +1,53 @@
 import {
   Column,
   Entity,
+  JoinColumn,
   ManyToOne,
   OneToMany,
   PrimaryGeneratedColumn,
 } from "typeorm";
 import { Pengguna } from "./pengguna.entity";
 import { Tugas } from "./tugas.entity";
-import { BerkasSubmisiTugas } from "./berkasSubmisiTugas";
+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",
@@ -37,5 +56,11 @@ export class SubmisiTugas {
   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/tugas.entity.ts b/src/entities/tugas.entity.ts
index 563bcb32a0d12b55e6e3a30232bea75a162a233d..2424b87973fd01fe854e40ca50079dbc40e0f4ee 100644
--- a/src/entities/tugas.entity.ts
+++ b/src/entities/tugas.entity.ts
@@ -1,46 +1,88 @@
 import {
   Column,
   Entity,
+  JoinColumn,
   ManyToOne,
   OneToMany,
   PrimaryGeneratedColumn,
 } from "typeorm";
 import { Kelas } from "./kelas.entity";
 import { Pengguna } from "./pengguna.entity";
-import { BerkasTugas } from "./berkasTugas";
+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;
 
-  @OneToMany(() => BerkasTugas, (berkasTugas) => berkasTugas.tugas)
+  @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/kelas/kelas.controller.ts b/src/kelas/kelas.controller.ts
index 88460f5dd5acbe94f91341ea795f387f2d02fe17..ae942b029bd3415ece79a2d5df0b3b4ad5befd3a 100644
--- a/src/kelas/kelas.controller.ts
+++ b/src/kelas/kelas.controller.ts
@@ -12,10 +12,12 @@ import {
   UseGuards,
 } from "@nestjs/common";
 import {
+  ByIdKelasDto,
   CreateKelasDto,
   DeleteKelasDto,
+  GetKelasDetailRespDto,
   GetKelasQueryDto,
-  GetListKelasRespDto,
+  GetKelasRespDto,
   GetNextNomorResDto,
   IdKelasResDto,
   KodeRespDto,
@@ -32,13 +34,14 @@ import {
   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";
+import { MataKuliah } from "src/entities/mataKuliah.entity";
 import { Kelas } from "src/entities/kelas.entity";
 
 @ApiTags("Kelas")
@@ -49,7 +52,11 @@ import { Kelas } from "src/entities/kelas.entity";
 export class KelasController {
   constructor(private readonly kelasServ: KelasService) {}
 
-  @ApiOkResponse({ type: GetListKelasRespDto, isArray: true })
+  @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,
@@ -148,4 +155,77 @@ export class KelasController {
   async delete(@Body() body: DeleteKelasDto): Promise<Kelas> {
     return await this.kelasServ.delete(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,
+    );
+  }
 }
diff --git a/src/kelas/kelas.dto.ts b/src/kelas/kelas.dto.ts
index e9d6b5f5b2b7b2b265d59cf87a9ec73ba3f05033..352ae36ea1a1d0c0e45ee7e9eb6eb778cd460439 100644
--- a/src/kelas/kelas.dto.ts
+++ b/src/kelas/kelas.dto.ts
@@ -6,8 +6,8 @@ import {
   ApiPropertyOptional,
 } from "@nestjs/swagger";
 import { Kelas } from "src/entities/kelas.entity";
-import { MataKuliah } from "src/entities/mataKuliah";
-import { RoleEnum } from "src/entities/pengguna.entity";
+import { MataKuliah } from "src/entities/mataKuliah.entity";
+import { Pengguna, RoleEnum } from "src/entities/pengguna.entity";
 
 export class CreateKelasDto extends PickType(Kelas, [
   "mataKuliahKode",
@@ -43,18 +43,26 @@ export class GetKelasQueryDto {
   search: string;
 }
 
-export class GetListKelasRespDto {
+export class ByIdKelasDto extends PickType(Kelas, ["id"] as const) {}
+
+export class GetKelasRespDto {
   @ApiProperty()
   id: string;
 
   @ApiProperty({ example: "K02" })
   nomor: string;
 
-  @ApiProperty({ example: "IF4031 Pengembangan Aplikasi Terdistribusi" })
-  mata_kuliah: 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) {}
@@ -63,3 +71,22 @@ 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[];
+}
diff --git a/src/kelas/kelas.module.ts b/src/kelas/kelas.module.ts
index 80ddae351a722bc5c4aecc32508e90fcd108c9ce..38dfa27f8690f2608b1450c134536f7d3d920651 100644
--- a/src/kelas/kelas.module.ts
+++ b/src/kelas/kelas.module.ts
@@ -6,7 +6,7 @@ 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";
+import { MataKuliah } from "src/entities/mataKuliah.entity";
 
 @Module({
   imports: [
diff --git a/src/kelas/kelas.service.ts b/src/kelas/kelas.service.ts
index c771ac6324a2ac862cbc114275577a633d3a334b..fa62a4707fe36efeca92ef76b8074dd392e7a92b 100644
--- a/src/kelas/kelas.service.ts
+++ b/src/kelas/kelas.service.ts
@@ -10,12 +10,13 @@ import { Brackets, Repository } from "typeorm";
 import {
   CreateKelasDto,
   DeleteKelasDto,
-  GetListKelasRespDto,
+  GetKelasDetailRespDto,
+  GetKelasRespDto,
   IdKelasResDto,
   UpdateKelasDto,
 } from "./kelas.dto";
 import { KonfigurasiService } from "src/konfigurasi/konfigurasi.service";
-import { MataKuliah } from "src/entities/mataKuliah";
+import { MataKuliah } from "src/entities/mataKuliah.entity";
 import { CARD_COLORS } from "./kelas.constant";
 
 @Injectable()
@@ -27,20 +28,13 @@ export class KelasService {
     private mataKuliahRepo: Repository<MataKuliah>,
     private konfService: KonfigurasiService,
   ) {}
-
   async getListKelas(
     idMahasiswa?: string,
     idPengajar?: string,
     kodeMatkul?: string,
     search?: string,
   ) {
-    const currPeriod = await this.konfService.getKonfigurasiByKey(
-      process.env.KONF_PERIODE_KEY,
-    );
-
-    if (!currPeriod) {
-      throw new BadRequestException("Periode belum dikonfigurasi");
-    }
+    const currPeriod = await this.konfService.getPeriodeOrFail();
 
     let baseQuery = this.kelasRepo
       .createQueryBuilder("kelas")
@@ -49,6 +43,7 @@ export class KelasService {
       .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",
@@ -95,25 +90,143 @@ export class KelasService {
       .groupBy("kelas.id, mataKuliah.kode")
       .getRawMany();
 
-    const mapped: GetListKelasRespDto[] = result.map((r) => ({
+    const mapped: GetKelasRespDto[] = result.map((r) => ({
       id: r.id,
       nomor: "K" + `${r.nomor}`.padStart(2, "0"),
-      mata_kuliah: `${r.kode_mata_kuliah} ${r.nama_mata_kuliah}`,
+      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 create(createDto: CreateKelasDto): Promise<IdKelasResDto> {
-    const currPeriod = await this.konfService.getKonfigurasiByKey(
-      process.env.KONF_PERIODE_KEY,
-    );
+  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,
+    };
 
-    if (!currPeriod) {
-      throw new BadRequestException("Periode belum dikonfigurasi");
+    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
@@ -159,13 +272,7 @@ export class KelasService {
   }
 
   async updateOrCreate(dto: UpdateKelasDto): Promise<IdKelasResDto> {
-    const currPeriod = await this.konfService.getKonfigurasiByKey(
-      process.env.KONF_PERIODE_KEY,
-    );
-
-    if (!currPeriod) {
-      throw new BadRequestException("Periode belum dikonfigurasi");
-    }
+    const currPeriod = await this.konfService.getPeriodeOrFail();
 
     if (!dto.id) {
       // Create kelas
@@ -207,13 +314,7 @@ export class KelasService {
   }
 
   async delete(dto: DeleteKelasDto): Promise<Kelas> {
-    const currPeriod = await this.konfService.getKonfigurasiByKey(
-      process.env.KONF_PERIODE_KEY,
-    );
-
-    if (!currPeriod) {
-      throw new BadRequestException("Periode belum dikonfigurasi");
-    }
+    const currPeriod = await this.konfService.getPeriodeOrFail();
 
     const kelasQuery = this.kelasRepo
       .createQueryBuilder("kelas")
@@ -243,13 +344,7 @@ export class KelasService {
   }
 
   async getNextNomorKelas(kodeMatkul: string): Promise<number> {
-    const currPeriod = await this.konfService.getKonfigurasiByKey(
-      process.env.KONF_PERIODE_KEY,
-    );
-
-    if (!currPeriod) {
-      throw new BadRequestException("Periode belum dikonfigurasi");
-    }
+    const currPeriod = await this.konfService.getPeriodeOrFail();
 
     const maxClass = await this.kelasRepo.findOne({
       where: {
diff --git a/src/konfigurasi/konfigurasi.service.ts b/src/konfigurasi/konfigurasi.service.ts
index 23ee9f8761d27dc8f257181c6f7d2abbf35de001..a8e7edaccbcafeeab8aa59f9039ce69ae88a21c2 100644
--- a/src/konfigurasi/konfigurasi.service.ts
+++ b/src/konfigurasi/konfigurasi.service.ts
@@ -1,4 +1,4 @@
-import { Injectable } from "@nestjs/common";
+import { BadRequestException, Injectable } from "@nestjs/common";
 import { InjectRepository } from "@nestjs/typeorm";
 import { Konfigurasi } from "src/entities/konfigurasi.entity";
 import { Repository } from "typeorm";
@@ -29,4 +29,16 @@ 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 11cb941afa48d31dc673e827398bac25ee2df6c0..fdce89e3372e4a1ea2d1a7bf5006ee2054c3f487 100644
--- a/src/main.ts
+++ b/src/main.ts
@@ -22,13 +22,15 @@ async function bootstrap() {
     .setDescription("GraduIT API Documentation for S2 services")
     .setVersion("1.0")
     .addTag("Alokasi Topik")
-    .addTag("Approval")
     .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/middlewares/forbidden-exception.filter.ts b/src/middlewares/forbidden-exception.filter.ts
index ec5ba9ddf450d25a3698c6fbc8ac20f32f1d468b..9d6de0d98638596e89d2c171d2c28dbbc145c205 100644
--- a/src/middlewares/forbidden-exception.filter.ts
+++ b/src/middlewares/forbidden-exception.filter.ts
@@ -13,9 +13,9 @@ export class ForbiddenExceptionFilter implements ExceptionFilter {
     const response = ctx.getResponse<Response>();
     const status = exception.getStatus();
 
-    response.clearCookie(process.env.COOKIE_NAME).status(status).json({
-      message: "Forbidden",
-      status,
-    });
+    response
+      .clearCookie(process.env.COOKIE_NAME)
+      .status(status)
+      .json(exception.getResponse());
   }
 }
diff --git a/src/nilai/nilai.controller.ts b/src/nilai/nilai.controller.ts
new file mode 100644
index 0000000000000000000000000000000000000000..6bb1cb659a4ad0347e37c69df0ad85750da2d1ad
--- /dev/null
+++ b/src/nilai/nilai.controller.ts
@@ -0,0 +1,45 @@
+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
new file mode 100644
index 0000000000000000000000000000000000000000..1b933d07468212dbfacd8fc41d441227b5084418
--- /dev/null
+++ b/src/nilai/nilai.dto.ts
@@ -0,0 +1,78 @@
+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
new file mode 100644
index 0000000000000000000000000000000000000000..f9aca923b411670865c235c946b28d698a099a52
--- /dev/null
+++ b/src/nilai/nilai.module.ts
@@ -0,0 +1,26 @@
+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";
+
+@Module({
+  imports: [
+    TypeOrmModule.forFeature([MahasiswaKelas, Kelas, MataKuliah, Konfigurasi]),
+    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
new file mode 100644
index 0000000000000000000000000000000000000000..81ebba4d7151fc7b42f495d542af94057fd4f66e
--- /dev/null
+++ b/src/nilai/nilai.service.ts
@@ -0,0 +1,98 @@
+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();
+    console.log(limit);
+
+    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/registrasi-tesis/registrasi-tesis.controller.ts b/src/registrasi-tesis/registrasi-tesis.controller.ts
index 4b0904fe472ce0dba0b8363e62f53291e5988af4..9802752f5de61d55f75b2b89791038abc827c2e2 100644
--- a/src/registrasi-tesis/registrasi-tesis.controller.ts
+++ b/src/registrasi-tesis/registrasi-tesis.controller.ts
@@ -4,7 +4,6 @@ import {
   Controller,
   ForbiddenException,
   Get,
-  NotFoundException,
   Param,
   Patch,
   Post,
@@ -13,9 +12,13 @@ import {
   UseGuards,
 } from "@nestjs/common";
 import {
+  ApiBadRequestResponse,
   ApiBearerAuth,
   ApiCookieAuth,
+  ApiCreatedResponse,
+  ApiNotFoundResponse,
   ApiOkResponse,
+  ApiOperation,
   ApiTags,
 } from "@nestjs/swagger";
 import { Request } from "express";
@@ -27,9 +30,10 @@ import { Roles } from "src/middlewares/roles.decorator";
 import { RolesGuard } from "src/middlewares/roles.guard";
 import {
   FindAllNewestRegRespDto,
+  GetByIdRespDto,
+  IdDto,
   RegByMhsParamDto,
   RegDto,
-  RegParamDto,
   RegQueryDto,
   RegStatisticsRespDto,
   UpdateByMhsParamsDto,
@@ -50,29 +54,121 @@ export class RegistrasiTesisController {
     private readonly konfService: KonfigurasiService,
   ) {}
 
+  @ApiOperation({
+    summary: "Create new registration. Roles: S2_MAHASISWA, ADMIN",
+  })
+  @ApiCreatedResponse({ type: IdDto })
+  @ApiNotFoundResponse({ description: "Penerima atau topik tidak ditemukan" })
+  @ApiBadRequestResponse({
+    description:
+      "Mahasiswa sedang memiliki pendaftaran aktif atau judul dan deskripsi topik baru tidak ada",
+  })
   @UseGuards(CustomAuthGuard, RolesGuard)
-  @Roles(RoleEnum.S2_MAHASISWA, RoleEnum.ADMIN, RoleEnum.S2_TIM_TESIS)
-  @Get("/mahasiswa/:mahasiswaId")
-  findByUserId(@Param() params: RegByMhsParamDto) {
-    return this.registrasiTesisService.findByUserId(params.mahasiswaId);
-  }
-
-  @UseGuards(CustomAuthGuard, RolesGuard)
-  @Roles(RoleEnum.S2_MAHASISWA, RoleEnum.ADMIN, RoleEnum.S2_TIM_TESIS)
+  @Roles(RoleEnum.S2_MAHASISWA, RoleEnum.ADMIN)
   @Post()
   async createTopicRegistration(
     @Body() topicRegistrationDto: RegDto,
     @Req() req: Request,
-  ) {
+  ): 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,
+    );
+  }
+
+  @ApiOperation({
+    summary:
+      "Find registrations (historical) by Mahasiswa ID. Roles: S2_MAHASISWA, ADMIN, S2_TIM_TESIS",
+  })
+  @ApiOkResponse({ type: [GetByIdRespDto] })
+  @UseGuards(CustomAuthGuard, RolesGuard)
+  @Roles(RoleEnum.S2_MAHASISWA, RoleEnum.ADMIN, RoleEnum.S2_TIM_TESIS)
+  @Get("/mahasiswa/:mahasiswaId")
+  async findByUserId(@Param() params: RegByMhsParamDto, @Req() req: Request) {
+    const { id, roles } = req.user as AuthDto;
+
+    if (
+      !roles.includes(RoleEnum.ADMIN) &&
+      !roles.includes(RoleEnum.S2_TIM_TESIS)
+    ) {
+      // roles only include RoleEnum.S2_MAHASISWA
+      if (id !== params.mahasiswaId) {
+        throw new ForbiddenException();
+      }
+    }
+
+    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,
     );
   }
 
-  // Right now only admin & timtesis view is handled (apakah dosen perlu summary juga?)
+  @ApiOperation({
+    summary:
+      "Find newest registration by Mahasiswa ID. Roles: ADMIN, S2_TIM_TESIS, S2_PEMBIMBING",
+  })
+  @ApiOkResponse({ type: GetByIdRespDto })
+  @UseGuards(CustomAuthGuard, RolesGuard)
+  @Roles(RoleEnum.ADMIN, RoleEnum.S2_TIM_TESIS, RoleEnum.S2_PEMBIMBING)
+  @Get("/mahasiswa/:mahasiswaId/newest")
+  async findNewestByUserId(
+    @Param() params: RegByMhsParamDto,
+    @Req() req: Request,
+  ) {
+    const { id, roles } = req.user as AuthDto;
+
+    let idPenerima = undefined;
+    if (
+      !roles.includes(RoleEnum.ADMIN) &&
+      !roles.includes(RoleEnum.S2_TIM_TESIS)
+    ) {
+      // roles only include RoleEnum.S2_PEMBIMBING
+      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,
+    );
+
+    return res[0];
+  }
+
+  @ApiOperation({
+    summary:
+      "Get statistics of registrations. Roles: S2_PEMBIMBING, ADMIN, S2_TIM_TESIS",
+  })
   @ApiOkResponse({ type: RegStatisticsRespDto })
   @UseGuards(CustomAuthGuard, RolesGuard)
   @Roles(RoleEnum.S2_PEMBIMBING, RoleEnum.ADMIN, RoleEnum.S2_TIM_TESIS)
@@ -96,6 +192,10 @@ export class RegistrasiTesisController {
 
   // Admin & TimTesis view will show newst reg records per Mahasiswa
   // Pembimbing view will show all regs towards them
+  @ApiOperation({
+    summary:
+      "Find all newest registration for each Mahasiswa. Roles: S2_PEMBIMBING, ADMIN, S2_TIM_TESIS",
+  })
   @ApiOkResponse({ type: FindAllNewestRegRespDto, isArray: true })
   @UseGuards(CustomAuthGuard, RolesGuard)
   @Roles(RoleEnum.S2_PEMBIMBING, RoleEnum.ADMIN, RoleEnum.S2_TIM_TESIS)
@@ -128,42 +228,18 @@ export class RegistrasiTesisController {
     });
   }
 
+  @ApiOperation({
+    summary:
+      "Update interview date of newest in process registration by Mahasiswa ID. Roles: S2_PEMBIMBING, ADMIN, S2_TIM_TESIS",
+  })
+  @ApiOkResponse({ type: IdDto })
   @UseGuards(CustomAuthGuard, RolesGuard)
-  @Roles(RoleEnum.S2_PEMBIMBING, RoleEnum.ADMIN, RoleEnum.S2_TIM_TESIS)
-  @Get("/:id")
-  async findById(
-    @Req() req: Request,
-    @Param() params: RegParamDto,
-    @Query()
-    query: ViewQueryDto,
-  ) {
-    const { id: idPenerima, roles } = req.user as AuthDto;
-
-    if (!roles.includes(query.view)) {
-      throw new ForbiddenException();
-    }
-
-    const res = await this.registrasiTesisService.findRegById(params.id);
-    if (!res) {
-      throw new NotFoundException();
-    }
-
-    if (
-      query.view === RoleEnum.S2_PEMBIMBING &&
-      res.penerima.id !== idPenerima
-    ) {
-      throw new ForbiddenException();
-    }
-
-    return res;
-  }
-
-  @UseGuards(CustomAuthGuard, RolesGuard)
-  @Roles(RoleEnum.ADMIN, RoleEnum.S2_TIM_TESIS)
+  @Roles(RoleEnum.ADMIN, RoleEnum.S2_TIM_TESIS, RoleEnum.S2_PEMBIMBING)
   @Patch("/:mhsId/interview")
   async updateInterviewDateByMhsId(
     @Param() params: UpdateByMhsParamsDto,
     @Body() body: UpdateInterviewBodyDto,
+    @Req() req: Request,
   ) {
     const periode = await this.konfService.getKonfigurasiByKey(
       process.env.KONF_PERIODE_KEY,
@@ -173,19 +249,37 @@ export class RegistrasiTesisController {
       throw new BadRequestException("Periode belum dikonfigurasi.");
     }
 
+    const { id, roles } = req.user as AuthDto;
+    let idPenerima = undefined;
+
+    if (
+      !roles.includes(RoleEnum.ADMIN) &&
+      !roles.includes(RoleEnum.S2_TIM_TESIS)
+    ) {
+      // roles only include RoleEnum.S2_PEMBIMBING
+      idPenerima = id;
+    }
+
     return await this.registrasiTesisService.updateInterviewDate(
       params.mhsId,
       periode,
       body,
+      idPenerima,
     );
   }
 
+  @ApiOperation({
+    summary:
+      "Update status of newest registration by Mahasiswa ID. Roles: S2_PEMBIMBING, ADMIN, S2_TIM_TESIS",
+  })
+  @ApiOkResponse({ type: IdDto })
   @UseGuards(CustomAuthGuard, RolesGuard)
-  @Roles(RoleEnum.ADMIN, RoleEnum.S2_TIM_TESIS)
+  @Roles(RoleEnum.ADMIN, RoleEnum.S2_TIM_TESIS, RoleEnum.S2_PEMBIMBING)
   @Patch("/:mhsId/status")
   async updateStatusByMhsId(
     @Param() params: UpdateByMhsParamsDto,
     @Body() body: UpdateStatusBodyDto,
+    @Req() req: Request,
   ) {
     const periode = await this.konfService.getKonfigurasiByKey(
       process.env.KONF_PERIODE_KEY,
@@ -195,17 +289,34 @@ export class RegistrasiTesisController {
       throw new BadRequestException("Periode belum dikonfigurasi.");
     }
 
+    const { id, roles } = req.user as AuthDto;
+    let idPenerima = undefined;
+
+    if (
+      !roles.includes(RoleEnum.ADMIN) &&
+      !roles.includes(RoleEnum.S2_TIM_TESIS)
+    ) {
+      // roles only include RoleEnum.S2_PEMBIMBING
+      idPenerima = id;
+    }
+
     return await this.registrasiTesisService.updateStatus(
       params.mhsId,
       periode,
       body,
+      idPenerima,
     );
   }
 
+  @ApiOperation({
+    summary:
+      "Update pembimbing list of approved registration by Mahasiswa ID. Roles: ADMIN, S2_TIM_TESIS",
+  })
+  @ApiOkResponse({ type: IdDto })
   @UseGuards(CustomAuthGuard, RolesGuard)
   @Roles(RoleEnum.ADMIN, RoleEnum.S2_TIM_TESIS)
   @Patch("/:mhsId/pembimbing")
-  async udpatePembimbingListByMhsId(
+  async updatePembimbingListByMhsId(
     @Param() params: UpdateByMhsParamsDto,
     @Body() body: UpdatePembimbingBodyDto,
   ) {
diff --git a/src/registrasi-tesis/registrasi-tesis.dto.ts b/src/registrasi-tesis/registrasi-tesis.dto.ts
index f18cca53a0f91f6503197d1318f7ca283cb85015..cc9541b2956aedc63449aa5144a9b87c34f6e0ba 100644
--- a/src/registrasi-tesis/registrasi-tesis.dto.ts
+++ b/src/registrasi-tesis/registrasi-tesis.dto.ts
@@ -6,31 +6,38 @@ import {
   IsString,
   IsUUID,
 } from "@nestjs/class-validator";
-import { ApiProperty, ApiPropertyOptional } from "@nestjs/swagger";
+import { ApiProperty, ApiPropertyOptional, PickType } from "@nestjs/swagger";
 import { ArrayMinSize, ArrayUnique, IsArray } from "class-validator";
-import { JalurEnum, RegStatus } from "src/entities/pendaftaranTesis.entity";
-import { RoleEnum } from "src/entities/pengguna.entity";
+import {
+  JalurEnum,
+  PendaftaranTesis,
+  RegStatus,
+} from "src/entities/pendaftaranTesis.entity";
+import { Pengguna, RoleEnum } from "src/entities/pengguna.entity";
 
 export class RegDto {
-  @IsUUID()
-  @ApiProperty()
-  idMahasiswa: string;
-
   @IsUUID()
   @ApiProperty()
   idPenerima: string;
 
-  @IsString()
-  @ApiProperty()
-  judulTopik: string;
-
-  @IsString()
-  @ApiProperty()
-  deskripsi: string;
+  @IsUUID()
+  @IsOptional()
+  @ApiPropertyOptional()
+  idTopik?: string;
 
   @IsEnum(JalurEnum)
   @ApiProperty({ enum: JalurEnum })
   jalurPilihan: JalurEnum;
+
+  @IsString()
+  @IsOptional()
+  @ApiPropertyOptional()
+  judulTopik?: string;
+
+  @IsString()
+  @IsOptional()
+  @ApiPropertyOptional()
+  deskripsiTopik?: string;
 }
 
 export class RegByMhsParamDto {
@@ -39,13 +46,21 @@ export class RegByMhsParamDto {
   mahasiswaId: string;
 }
 
-export class RegParamDto {
+export class IdDto {
   @IsUUID()
   @ApiProperty()
   id: string;
 }
 
-export class RegQueryDto {
+export class ViewQueryDto {
+  @IsEnum([RoleEnum.S2_PEMBIMBING, RoleEnum.ADMIN, RoleEnum.S2_TIM_TESIS])
+  @ApiProperty({
+    enum: [RoleEnum.S2_PEMBIMBING, RoleEnum.ADMIN, RoleEnum.S2_TIM_TESIS],
+  })
+  view: RoleEnum.S2_PEMBIMBING | RoleEnum.ADMIN | RoleEnum.S2_TIM_TESIS;
+}
+
+export class RegQueryDto extends ViewQueryDto {
   @IsOptional()
   @IsNumberString()
   @ApiPropertyOptional()
@@ -75,35 +90,29 @@ export class RegQueryDto {
   @IsEnum(["ASC", "DESC"])
   @ApiPropertyOptional({ enum: ["ASC", "DESC"] })
   sort?: "ASC" | "DESC";
-
-  @IsEnum([RoleEnum.S2_PEMBIMBING, RoleEnum.ADMIN, RoleEnum.S2_TIM_TESIS])
-  @ApiProperty({
-    enum: [RoleEnum.S2_PEMBIMBING, RoleEnum.ADMIN, RoleEnum.S2_TIM_TESIS],
-  })
-  view: RoleEnum.S2_PEMBIMBING | RoleEnum.ADMIN | RoleEnum.S2_TIM_TESIS;
-}
-
-export class ViewQueryDto {
-  @IsEnum([RoleEnum.S2_PEMBIMBING, RoleEnum.ADMIN, RoleEnum.S2_TIM_TESIS])
-  @ApiProperty({
-    enum: [RoleEnum.S2_PEMBIMBING, RoleEnum.ADMIN, RoleEnum.S2_TIM_TESIS],
-  })
-  view: RoleEnum.S2_PEMBIMBING | RoleEnum.ADMIN | RoleEnum.S2_TIM_TESIS;
 }
 
 export class FindAllNewestRegRespDataDto {
   @ApiProperty()
   pendaftaran_id: string;
+
   @ApiProperty()
   nim: string;
+
   @ApiProperty()
   mahasiswa_nama: string;
+
   @ApiProperty()
   mahasiswa_id: string;
+
   @ApiProperty()
   pembimbing_nama: string;
+
   @ApiProperty()
   status: string;
+
+  @ApiProperty()
+  jadwal_interview: Date;
 }
 
 export class FindAllNewestRegRespDto {
@@ -158,3 +167,26 @@ export class UpdatePembimbingBodyDto {
   @ArrayUnique()
   pembimbing_ids: string[];
 }
+
+class DosenPembimbingDto extends PickType(Pengguna, [
+  "id",
+  "nama",
+  "kontak",
+] as const) {}
+
+export class GetByIdRespDto extends PickType(PendaftaranTesis, [
+  "id",
+  "jadwalInterview",
+  "status",
+  "jalurPilihan",
+  "waktuPengiriman",
+] as const) {
+  @ApiProperty()
+  judulTopik: string;
+
+  @ApiProperty()
+  deskripsiTopik: string;
+
+  @ApiProperty({ type: [DosenPembimbingDto] })
+  dosenPembimbing: DosenPembimbingDto[];
+}
diff --git a/src/registrasi-tesis/registrasi-tesis.service.ts b/src/registrasi-tesis/registrasi-tesis.service.ts
index 1513caa4c7d7cae5ada90e6c5857f93349c6b533..e19127b3878b6411462d75d1a7126a6d565ae723 100644
--- a/src/registrasi-tesis/registrasi-tesis.service.ts
+++ b/src/registrasi-tesis/registrasi-tesis.service.ts
@@ -1,5 +1,6 @@
 import {
   BadRequestException,
+  ForbiddenException,
   Injectable,
   InternalServerErrorException,
   NotFoundException,
@@ -13,16 +14,17 @@ import {
 import { Pengguna, RoleEnum } from "src/entities/pengguna.entity";
 import { Topik } from "src/entities/topik.entity";
 import { generateQueryBuilderOrderByObj } from "src/helper/sorting";
-import { validateId } from "src/helper/validation";
-import { ArrayContains, DataSource, In, Repository } from "typeorm";
+import { ArrayContains, Brackets, DataSource, In, Repository } from "typeorm";
 import {
   FindAllNewestRegRespDto,
+  IdDto,
   RegDto,
   RegStatisticsRespDto,
   UpdateInterviewBodyDto,
   UpdatePembimbingBodyDto,
   UpdateStatusBodyDto,
 } from "./registrasi-tesis.dto";
+import * as dayjs from "dayjs";
 
 @Injectable()
 export class RegistrasiTesisService {
@@ -41,64 +43,153 @@ export class RegistrasiTesisService {
   async createTopicRegistration(
     userId: string,
     topicRegistrationDto: RegDto,
-  ): Promise<PendaftaranTesis> {
-    // TODO: Proper validations
-
-    // Validate id
-    validateId([
-      { id: userId, object: "Pengguna" },
-      { id: topicRegistrationDto.idPenerima, object: "Pembimbing" },
-    ]);
-
-    // Validate user id, supervisor id
-    const [user, supervisor, topic] = await Promise.all([
-      this.penggunaRepository.findOne({
-        where: { id: userId },
-      }),
+    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.penggunaRepository.findOne({
         where: { id: topicRegistrationDto.idPenerima },
       }),
-      this.topicRepostitory.findOne({
-        where: { judul: topicRegistrationDto.judulTopik },
-      }),
-    ]);
+    ];
 
-    if (!user) {
-      throw new NotFoundException("User not found.");
-    } else if (!supervisor) {
-      throw new NotFoundException("Supervisor not found.");
-    } else if (!topic) {
+    if (topicRegistrationDto.idTopik) {
+      queries.push(
+        this.topicRepostitory.findOne({
+          where: { id: topicRegistrationDto.idTopik },
+        }),
+      );
+    }
+
+    const queryResult = await Promise.all(queries);
+    const lastPendaftaran = queryResult[0] as PendaftaranTesis;
+    const penerima = queryResult[1] as Pengguna;
+    let topik = topicRegistrationDto.idTopik ? (queryResult[2] as Topik) : null;
+
+    if (!penerima) {
+      throw new NotFoundException("Penerima not found.");
+    }
+
+    if (topicRegistrationDto.idTopik && !topik) {
       throw new NotFoundException("Topic not found.");
     }
 
+    if (lastPendaftaran && lastPendaftaran.status !== RegStatus.REJECTED) {
+      throw new BadRequestException(
+        "Mahasiswa already has pending registration in this period",
+      );
+    }
+
+    if (!topik) {
+      if (
+        !topicRegistrationDto.judulTopik ||
+        !topicRegistrationDto.deskripsiTopik
+      ) {
+        throw new BadRequestException(
+          "Judul dan deskripsi topik tidak boleh kosong.",
+        );
+      }
+
+      topik = this.topicRepostitory.create({
+        judul: topicRegistrationDto.judulTopik,
+        deskripsi: topicRegistrationDto.deskripsiTopik,
+        idPengaju: userId,
+        periode,
+      });
+    }
+
     // Create new registration
     const createdRegistration = this.pendaftaranTesisRepository.create({
       ...topicRegistrationDto,
-      mahasiswa: user,
-      penerima: supervisor,
-      topik: topic,
+      mahasiswaId: userId,
+      penerima,
+      topik,
     });
 
     await this.pendaftaranTesisRepository.save(createdRegistration);
 
-    return createdRegistration;
+    return {
+      id: createdRegistration.id,
+    };
   }
 
-  async findByUserId(mahasiswaId: string) {
-    const res = await this.pendaftaranTesisRepository.find({
-      relations: ["topik", "penerima"],
-      where: { mahasiswa: { id: mahasiswaId } },
-    });
+  async findByUserId(
+    mahasiswaId: string,
+    periode: string,
+    isNewestOnly: boolean,
+    idPenerima?: string,
+  ) {
+    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")
+      .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();
+
+    if (res.length === 0) {
+      throw new NotFoundException("Tidak ada registrasi tesis yang ditemukan.");
+    }
 
-    return res.map((r) => ({
-      ...r,
-      penerima: {
-        ...r.penerima,
-        password: undefined,
-        roles: undefined,
-        nim: undefined,
-      },
+    if (idPenerima) {
+      // requester only has S2_PEMBIMBING access
+      const reg = res[0];
+
+      if (reg.penerima.id !== idPenerima) {
+        throw new ForbiddenException();
+      }
+    }
+
+    const mappedRes = res.map((r) => ({
+      id: r.id,
+      jadwalInterview: r.jadwalInterview,
+      jalurPilihan: r.jalurPilihan,
+      status: r.status,
+      waktuPengiriman: r.waktuPengiriman,
+      judulTopik: r.topik.judul,
+      deskripsiTopik: r.topik.deskripsi,
+      dosenPembimbing:
+        r.status === RegStatus.APPROVED
+          ? r.dosenBimbingan.map((db) => db.dosen)
+          : [r.penerima],
     }));
+
+    if (isNewestOnly) {
+      // only get last registration
+      // slow performance because get all records first then only returns the first one
+      // need to change to use subquery
+      mappedRes.splice(1);
+    }
+
+    return mappedRes;
   }
 
   async getRegsStatistics(options: {
@@ -109,69 +200,70 @@ export class RegistrasiTesisService {
       where: { roles: ArrayContains([RoleEnum.S2_MAHASISWA]) },
     });
 
-    // Show newest regs per Mhs if POV TimTesis or Admin
-    if (!options.idPenerima) {
-      const baseQuery = this.pendaftaranTesisRepository
-        .createQueryBuilder("pt")
-        .innerJoinAndSelect(
-          (qb) =>
-            qb
-              .select([
-                "pt.mahasiswaId AS latest_mahasiswaId",
-                "MAX(pt.waktuPengiriman) AS latestPengiriman",
-              ])
-              .from(PendaftaranTesis, "pt")
-              .groupBy("pt.mahasiswaId"),
-          "latest",
-          "latest.latest_mahasiswaId = pt.mahasiswaId AND pt.waktuPengiriman = latest.latestPengiriman",
-        )
-        .innerJoinAndSelect("pt.topik", "topik")
-        .where("topik.periode = :periode", { periode: options.periode });
-
-      const totalDiterima = baseQuery
-        .clone()
-        .andWhere("pt.status = :status", { status: RegStatus.APPROVED })
-        .getCount();
-
-      const totalProses = baseQuery
-        .clone()
-        .where("pt.status IN (:...status)", {
-          status: [RegStatus.NOT_ASSIGNED, RegStatus.INTERVIEW],
-        })
-        .getCount();
-
-      const totalDitolak = baseQuery
-        .clone()
-        .where("pt.status = :status", { status: RegStatus.REJECTED })
-        .getCount();
-
-      const [total, diterima, proses, ditolak] = await Promise.all([
-        totalMahasiswa,
-        totalDiterima,
-        totalProses,
-        totalDitolak,
-      ]);
-
-      return {
-        diterima: {
-          amount: diterima,
-          percentage: Math.round((diterima / total) * 100),
-        },
-        sedang_proses: {
-          amount: proses,
-          percentage: Math.round((proses / total) * 100),
-        },
-        ditolak: {
-          amount: ditolak,
-          percentage: Math.round((ditolak / total) * 100),
-        },
-      };
-    } else {
-      throw new InternalServerErrorException("Not implemented");
+    // Show newest regs per Mhs
+    const baseQuery = this.pendaftaranTesisRepository
+      .createQueryBuilder("pt")
+      .innerJoinAndSelect(
+        (qb) =>
+          qb
+            .select([
+              "pt.mahasiswaId AS latest_mahasiswaId",
+              "MAX(pt.waktuPengiriman) AS latestPengiriman",
+            ])
+            .from(PendaftaranTesis, "pt")
+            .groupBy("pt.mahasiswaId"),
+        "latest",
+        "latest.latest_mahasiswaId = pt.mahasiswaId AND pt.waktuPengiriman = latest.latestPengiriman",
+      )
+      .innerJoinAndSelect("pt.topik", "topik")
+      .where("topik.periode = :periode", { periode: options.periode });
+
+    if (options.idPenerima) {
+      baseQuery.andWhere("pt.penerimaId = :idPenerima", {
+        idPenerima: options.idPenerima,
+      });
     }
+
+    const totalDiterima = baseQuery
+      .clone()
+      .andWhere("pt.status = :status", { status: RegStatus.APPROVED })
+      .getCount();
+
+    const totalProses = baseQuery
+      .clone()
+      .andWhere("pt.status IN (:...status)", {
+        status: [RegStatus.NOT_ASSIGNED, RegStatus.INTERVIEW],
+      })
+      .getCount();
+
+    const totalDitolak = baseQuery
+      .clone()
+      .andWhere("pt.status = :status", { status: RegStatus.REJECTED })
+      .getCount();
+
+    const [total, diterima, proses, ditolak] = await Promise.all([
+      totalMahasiswa,
+      totalDiterima,
+      totalProses,
+      totalDitolak,
+    ]);
+
+    return {
+      diterima: {
+        amount: diterima,
+        percentage: Math.round((diterima / total) * 100),
+      },
+      sedang_proses: {
+        amount: proses,
+        percentage: Math.round((proses / total) * 100),
+      },
+      ditolak: {
+        amount: ditolak,
+        percentage: Math.round((ditolak / total) * 100),
+      },
+    };
   }
 
-  // TODO sort
   async findAllRegs(options: {
     status?: RegStatus;
     page: number;
@@ -186,22 +278,20 @@ export class RegistrasiTesisService {
       .createQueryBuilder("pt")
       .select("pt");
 
-    // Show newest regs per Mhs if POV TimTesis or Admin
+    // Show newest regs per Mhs
     // May need to make materialized view to improve performance
-    if (!options.idPenerima) {
-      baseQuery.innerJoinAndSelect(
-        (qb) =>
-          qb
-            .select([
-              "pt.mahasiswaId AS latest_mahasiswaId",
-              "MAX(pt.waktuPengiriman) AS latestPengiriman",
-            ])
-            .from(PendaftaranTesis, "pt")
-            .groupBy("pt.mahasiswaId"),
-        "latest",
-        "latest.latest_mahasiswaId = pt.mahasiswaId AND pt.waktuPengiriman = latest.latestPengiriman",
-      );
-    }
+    baseQuery.innerJoinAndSelect(
+      (qb) =>
+        qb
+          .select([
+            "pt.mahasiswaId AS latest_mahasiswaId",
+            "MAX(pt.waktuPengiriman) AS latestPengiriman",
+          ])
+          .from(PendaftaranTesis, "pt")
+          .groupBy("pt.mahasiswaId"),
+      "latest",
+      "latest.latest_mahasiswaId = pt.mahasiswaId AND pt.waktuPengiriman = latest.latestPengiriman",
+    );
 
     baseQuery
       .innerJoinAndSelect("pt.topik", "topik")
@@ -209,19 +299,25 @@ export class RegistrasiTesisService {
       .innerJoinAndSelect("pt.mahasiswa", "mahasiswa")
       .where("topik.periode = :periode", { periode: options.periode });
 
+    if (options.idPenerima) {
+      baseQuery.andWhere("pt.penerimaId = :idPenerima", {
+        idPenerima: options.idPenerima,
+      });
+    }
+
     if (options.search)
       baseQuery.andWhere(
-        "mahasiswa.nama LIKE '%' || :search || '%' OR mahasiswa.nim LIKE '%' || :search || '%'",
-        {
-          search: options.search,
-        },
+        new Brackets((qb) =>
+          qb
+            .where("mahasiswa.nama ILIKE :search", {
+              search: `%${options.search}%`,
+            })
+            .orWhere("mahasiswa.nim ILIKE :search", {
+              search: `%${options.search}%`,
+            }),
+        ),
       );
 
-    if (options.idPenerima)
-      baseQuery.andWhere("penerima.id = :idPenerima", {
-        idPenerima: options.idPenerima,
-      });
-
     if (options.status)
       baseQuery.andWhere("pt.status = :status", {
         status: options.status,
@@ -256,6 +352,7 @@ export class RegistrasiTesisService {
         mahasiswa_nama: reg.mahasiswa.nama,
         pembimbing_nama: reg.penerima.nama,
         status: reg.status,
+        jadwal_interview: reg.jadwalInterview,
       })),
       count,
     };
@@ -263,40 +360,7 @@ export class RegistrasiTesisService {
     return resData;
   }
 
-  async findRegById(id: string) {
-    // not periode-protected
-    return await this.pendaftaranTesisRepository.findOne({
-      select: {
-        id: true,
-        waktuPengiriman: true,
-        jadwalInterview: true,
-        waktuKeputusan: true,
-        status: true,
-        jalurPilihan: true,
-        penerima: {
-          id: true,
-          nama: true,
-          email: true,
-        },
-        mahasiswa: {
-          id: true,
-          nama: true,
-          email: true,
-          nim: true,
-        },
-      },
-      where: {
-        id,
-      },
-      relations: {
-        penerima: true,
-        topik: true,
-        mahasiswa: true,
-      },
-    });
-  }
-
-  async getNewestRegByMhs(mahasiswaId: string, periode: string) {
+  private async getNewestRegByMhsOrFail(mahasiswaId: string, periode: string) {
     const mahasiswa = await this.penggunaRepository.findOne({
       select: {
         id: true,
@@ -313,14 +377,23 @@ export class RegistrasiTesisService {
     const newestReg = await this.pendaftaranTesisRepository.findOne({
       select: {
         id: true,
+        jadwalInterview: true,
         status: true,
         waktuPengiriman: true,
+        jalurPilihan: true,
         topik: {
+          judul: true,
+          deskripsi: true,
           periode: true,
         },
+        penerima: {
+          id: true,
+        },
       },
       relations: {
         topik: true,
+        penerima: true,
+        mahasiswa: true,
       },
       where: {
         mahasiswa: mahasiswa,
@@ -345,8 +418,22 @@ export class RegistrasiTesisService {
     mahasiswaId: string,
     periode: string,
     dto: UpdateInterviewBodyDto,
+    idPenerima?: string,
   ) {
-    const newestReg = await this.getNewestRegByMhs(mahasiswaId, periode);
+    const minDate = new Date();
+    minDate.setDate(minDate.getDate() + 2);
+
+    if (dayjs(dto.date).isBefore(dayjs(minDate).endOf("d"))) {
+      throw new BadRequestException(
+        "Interview date must be at least 2 days from now",
+      );
+    }
+
+    const newestReg = await this.getNewestRegByMhsOrFail(mahasiswaId, periode);
+
+    if (newestReg && idPenerima && newestReg.penerima.id !== idPenerima) {
+      throw new ForbiddenException();
+    }
 
     const restrictedStatus: RegStatus[] = [
       RegStatus.APPROVED,
@@ -367,22 +454,54 @@ export class RegistrasiTesisService {
       { jadwalInterview: newDate, status: RegStatus.INTERVIEW },
     );
 
-    return { status: "ok" };
+    return { id: newestReg.id } as IdDto;
   }
 
   async updateStatus(
     mahasiswaId: string,
     periode: string,
     dto: UpdateStatusBodyDto,
+    idPenerima?: string,
   ) {
-    const newestReg = await this.getNewestRegByMhs(mahasiswaId, periode);
+    const newestReg = await this.getNewestRegByMhsOrFail(mahasiswaId, periode);
 
-    await this.pendaftaranTesisRepository.update(
-      { id: newestReg.id },
-      { status: dto.status },
-    );
+    if (newestReg && idPenerima && newestReg.penerima.id !== idPenerima) {
+      throw new ForbiddenException();
+    }
+
+    const queryRunner = this.dataSource.createQueryRunner();
+    await queryRunner.connect();
+    await queryRunner.startTransaction();
+
+    try {
+      await queryRunner.manager.update(
+        PendaftaranTesis,
+        { id: newestReg.id },
+        { status: dto.status, waktuKeputusan: new Date() },
+      );
+
+      if (dto.status === RegStatus.APPROVED) {
+        await queryRunner.manager.insert(DosenBimbingan, {
+          idPendaftaran: newestReg.id,
+          idDosen: newestReg.penerima.id,
+        });
+      } else {
+        // dto.status === RegStatus.REJECTED
+        await queryRunner.manager.delete(DosenBimbingan, {
+          idPendaftaran: newestReg.id,
+        });
+      }
 
-    return { status: "ok" };
+      await queryRunner.commitTransaction();
+    } catch (err) {
+      await queryRunner.rollbackTransaction();
+
+      throw new InternalServerErrorException();
+    } finally {
+      await queryRunner.release();
+    }
+
+    return { id: newestReg.id } as IdDto;
   }
 
   async updatePembimbingList(
@@ -390,9 +509,8 @@ export class RegistrasiTesisService {
     periode: string,
     { pembimbing_ids: dosen_ids }: UpdatePembimbingBodyDto,
   ) {
-    const newestReg = await this.getNewestRegByMhs(mahasiswaId, periode);
+    const newestReg = await this.getNewestRegByMhsOrFail(mahasiswaId, periode);
 
-    // TODO decide to allow unapproved Registrations to have their Penerima changed or not
     if (newestReg.status !== RegStatus.APPROVED)
       throw new BadRequestException(
         "Cannot update pembimbing on non-approved registration",
@@ -427,7 +545,7 @@ export class RegistrasiTesisService {
       (newId) => !newPembimbingIds.includes(newId),
     );
 
-    const queryRunner = await this.dataSource.createQueryRunner();
+    const queryRunner = this.dataSource.createQueryRunner();
     await queryRunner.connect();
     await queryRunner.startTransaction();
 
@@ -443,10 +561,12 @@ export class RegistrasiTesisService {
       await queryRunner.commitTransaction();
     } catch (err) {
       await queryRunner.rollbackTransaction();
+
+      throw new InternalServerErrorException();
     } finally {
       await queryRunner.release();
     }
 
-    return { status: "ok" };
+    return { id: newestReg.id } as IdDto;
   }
 }
diff --git a/src/submisi-tugas/submisi-tugas.controller.ts b/src/submisi-tugas/submisi-tugas.controller.ts
new file mode 100644
index 0000000000000000000000000000000000000000..cb4f45a1b1cdb0d5535344ac9d8ccee62e4db8b9
--- /dev/null
+++ b/src/submisi-tugas/submisi-tugas.controller.ts
@@ -0,0 +1,104 @@
+import {
+  Body,
+  Controller,
+  Get,
+  Param,
+  Post,
+  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 {
+  CreateSubmisiTugasDto,
+  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) {}
+
+  @Roles(RoleEnum.S2_MAHASISWA)
+  @Post()
+  async createSubmisiTugas(
+    @Body() createDto: CreateSubmisiTugasDto,
+    @Req() req: Request,
+  ) {
+    // TODO: More validation
+    const { id } = req.user as AuthDto;
+
+    return await this.submisiTugasServ.createSubmisiTugas(createDto, id);
+  }
+
+  @ApiOperation({
+    summary:
+      "Get submisi tugas by sumbisi 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
new file mode 100644
index 0000000000000000000000000000000000000000..daa8486087d0e417d1ef0c72f9b74e8b8cf15900
--- /dev/null
+++ b/src/submisi-tugas/submisi-tugas.dto.ts
@@ -0,0 +1,126 @@
+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,
+  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 CreateSubmisiTugasDto extends PickType(SubmisiTugas, [
+  "jawaban",
+  "isSubmitted",
+  "tugasId",
+]) {
+  @ApiProperty({ type: [BerkasSubmisiTugasWithoutId] })
+  @ValidateNested({ each: true })
+  @Type(() => BerkasSubmisiTugasWithoutId)
+  berkasSubmisiTugas: BerkasSubmisiTugasWithoutId[];
+}
+
+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) {}
+
+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
new file mode 100644
index 0000000000000000000000000000000000000000..1fd15428a499fe276ed0aeaef1b8168bd532b105
--- /dev/null
+++ b/src/submisi-tugas/submisi-tugas.module.ts
@@ -0,0 +1,31 @@
+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
new file mode 100644
index 0000000000000000000000000000000000000000..d3c1c11a98b638aecbf96c5b67c1b8326073ef93
--- /dev/null
+++ b/src/submisi-tugas/submisi-tugas.service.ts
@@ -0,0 +1,257 @@
+import {
+  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 {
+  CreateSubmisiTugasDto,
+  GetSubmisiTugasByIdRespDto,
+  GetSubmisiTugasByTugasIdRespDto,
+} 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";
+
+@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 createSubmisiTugas(
+    createDto: CreateSubmisiTugasDto,
+    mahasiswaId: string,
+  ) {
+    await this.tugasService.isMahasiswaTugasOrFail(
+      mahasiswaId,
+      createDto.tugasId,
+    );
+
+    const tugas = await this.tugasRepo.findOneBy({ id: createDto.tugasId });
+    const mahasiswa = await this.penggunaRepo.findOneBy({ id: mahasiswaId });
+
+    const berkasSubmisiTugas = createDto.berkasSubmisiTugas.map(
+      (berkasSubmisiTugas) =>
+        this.berkasSubmisiTugasRepo.create(berkasSubmisiTugas),
+    );
+
+    const submisiTugas = this.submisiTugasRepo.create({
+      ...createDto,
+      mahasiswa,
+      submittedAt: createDto.isSubmitted ? new Date() : null,
+      tugas,
+      berkasSubmisiTugas,
+    });
+
+    await this.submisiTugasRepo.save(submisiTugas);
+
+    return submisiTugas;
+  }
+
+  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 = await 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
new file mode 100644
index 0000000000000000000000000000000000000000..b66f4efe167c7a14fea84ac9c710f4f559155186
--- /dev/null
+++ b/src/tugas/tugas.controller.ts
@@ -0,0 +1,105 @@
+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,
+} from "./tugas.dto";
+import { Request } from "express";
+import { AuthDto } from "src/auth/auth.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,
+    );
+  }
+}
diff --git a/src/tugas/tugas.dto.ts b/src/tugas/tugas.dto.ts
new file mode 100644
index 0000000000000000000000000000000000000000..afbb6eff67825b726e20169db6dee9e73d79f0ce
--- /dev/null
+++ b/src/tugas/tugas.dto.ts
@@ -0,0 +1,109 @@
+import {
+  ApiProperty,
+  ApiPropertyOptional,
+  OmitType,
+  PickType,
+} from "@nestjs/swagger";
+import { Type } from "class-transformer";
+import {
+  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 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;
+}
diff --git a/src/tugas/tugas.module.ts b/src/tugas/tugas.module.ts
new file mode 100644
index 0000000000000000000000000000000000000000..3a8d49258c546ffaa9d9d95014f4d10af9190442
--- /dev/null
+++ b/src/tugas/tugas.module.ts
@@ -0,0 +1,44 @@
+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
new file mode 100644
index 0000000000000000000000000000000000000000..0f99625c045bdc011de895afd60bd01371658ecb
--- /dev/null
+++ b/src/tugas/tugas.service.ts
@@ -0,0 +1,277 @@
+import {
+  BadRequestException,
+  ForbiddenException,
+  Injectable,
+  NotFoundException,
+} from "@nestjs/common";
+import { InjectRepository } from "@nestjs/typeorm";
+import { Tugas } from "src/entities/tugas.entity";
+import { Repository } from "typeorm";
+import {
+  CreateTugasDto,
+  GetTugasByIdRespDto,
+  GetTugasByKelasIdRespDto,
+  GetTugasSummaryRespDto,
+  TugasIdDto,
+  UpdateTugasDto,
+} from "./tugas.dto";
+import { BerkasTugas } from "src/entities/berkasTugas.entity";
+import { SubmisiTugas } from "src/entities/submisiTugas.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";
+
+@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 = await 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 };
+  }
+}