diff --git a/src/alokasi-topik/alokasi-topik.controller.ts b/src/alokasi-topik/alokasi-topik.controller.ts index 8c095da4d27d04e961dce48cc605c5df62bd0f8d..069ae9fb2290d3831476d20cb7068beae81fbed4 100644 --- a/src/alokasi-topik/alokasi-topik.controller.ts +++ b/src/alokasi-topik/alokasi-topik.controller.ts @@ -1,4 +1,5 @@ import { + BadRequestException, Body, Controller, Delete, @@ -8,12 +9,15 @@ import { Post, Put, Query, + Req, UseGuards, } from "@nestjs/common"; import { ApiBearerAuth, ApiCookieAuth, + ApiCreatedResponse, ApiOkResponse, + ApiOperation, ApiTags, } from "@nestjs/swagger"; import { RoleEnum } from "src/entities/pengguna.entity"; @@ -22,7 +26,7 @@ import { Roles } from "src/middlewares/roles.decorator"; import { RolesGuard } from "src/middlewares/roles.guard"; import { CreateBulkTopikDto, - CreateRespDto, + TopikIdRespDto, CreateTopikDto, GetAllRespDto, OmittedTopik, @@ -32,6 +36,9 @@ import { createBulkRespDto, } from "./alokasi-topik.dto"; import { AlokasiTopikService } from "./alokasi-topik.service"; +import { Request } from "express"; +import { AuthDto } from "src/auth/auth.dto"; +import { HIGH_AUTHORITY_ROLES, isHighAuthority } from "src/helper/roles"; @ApiTags("Alokasi Topik") @ApiCookieAuth() @@ -41,22 +48,41 @@ import { AlokasiTopikService } from "./alokasi-topik.service"; export class AlokasiTopikController { constructor(private alokasiTopikService: AlokasiTopikService) {} - @ApiOkResponse({ type: CreateRespDto }) - @Roles(RoleEnum.S2_TIM_TESIS, RoleEnum.ADMIN) + @ApiOperation({ + summary: "Create new topik. Roles: S2_TIM_TESIS, ADMIN, S2_PEMBIMBING", + }) + @ApiCreatedResponse({ type: TopikIdRespDto }) + @Roles(...HIGH_AUTHORITY_ROLES, RoleEnum.S2_PEMBIMBING) @Post() - async create(@Body() createDto: CreateTopikDto) { - return await this.alokasiTopikService.create({ ...createDto }); + async create( + @Body() createDto: CreateTopikDto, + @Req() req: Request, + ): Promise<TopikIdRespDto> { + const { roles, id } = req.user as AuthDto; + // user only has S2_PEMBIMBING role + if (!isHighAuthority(roles) && createDto.idPengaju !== id) { + throw new BadRequestException("Pengaju ID harus sama dengan user ID"); + } + + return await this.alokasiTopikService.create(createDto); } + @ApiOperation({ + summary: "Create multiple topik. Roles: S2_TIM_TESIS, ADMIN", + }) @ApiOkResponse({ type: createBulkRespDto }) - @Roles(RoleEnum.S2_TIM_TESIS, RoleEnum.ADMIN) + @Roles(...HIGH_AUTHORITY_ROLES) @Post("/bulk") async createBulk(@Body() createDto: CreateBulkTopikDto) { return await this.alokasiTopikService.createBulk(createDto); } + @ApiOperation({ + summary: + "Get topik by ID. Roles: S2_TIM_TESIS, ADMIN, S2_PEMBIMBING, S2_MAHASISWA", + }) @ApiOkResponse({ type: OmittedTopik }) - @Roles(RoleEnum.S2_TIM_TESIS, RoleEnum.ADMIN) + @Roles(...HIGH_AUTHORITY_ROLES, RoleEnum.S2_PEMBIMBING, RoleEnum.S2_MAHASISWA) @Get("/:id") async getById(@Param() params: TopikParamDto) { const res = await this.alokasiTopikService.findActiveTopikById(params.id); @@ -64,8 +90,12 @@ export class AlokasiTopikController { return res as OmittedTopik; } + @ApiOperation({ + summary: + "Get all topik. Roles: S2_TIM_TESIS, ADMIN, S2_MAHASISWA, S2_PEMBIMBING", + }) @ApiOkResponse({ type: GetAllRespDto }) - @Roles(RoleEnum.S2_TIM_TESIS, RoleEnum.ADMIN, RoleEnum.S2_MAHASISWA) + @Roles(...HIGH_AUTHORITY_ROLES, RoleEnum.S2_MAHASISWA, RoleEnum.S2_PEMBIMBING) @Get() async getAll( @Query() @@ -73,28 +103,72 @@ export class AlokasiTopikController { ) { return await this.alokasiTopikService.findAllActiveTopikCreatedByPembimbing( { - page: query.page || 1, ...query, + page: query.page || 1, }, ); } - @Roles(RoleEnum.S2_TIM_TESIS, RoleEnum.ADMIN) + @ApiOperation({ + summary: "Update topik. Roles: S2_TIM_TESIS, ADMIN, S2_PEMBIMBING", + }) + @ApiOkResponse({ type: TopikIdRespDto }) + @Roles(...HIGH_AUTHORITY_ROLES, RoleEnum.S2_PEMBIMBING) @Put("/:id") async update( @Param() params: TopikParamDto, @Body() updateDto: UpdateTopikDto, - ) { - const res = await this.alokasiTopikService.update(params.id, updateDto); - if (!res.affected) throw new NotFoundException(); - return res; + @Req() req: Request, + ): Promise<TopikIdRespDto> { + let idPengaju = undefined; + const { roles, id } = req.user as AuthDto; + // user only has S2_PEMBIMBING role + if (!isHighAuthority(roles)) { + if (updateDto.idPengaju !== id) { + throw new BadRequestException("Pengaju ID harus sama dengan user ID"); + } + idPengaju = id; + } + + const res = await this.alokasiTopikService.update( + params.id, + updateDto, + idPengaju, + ); + if (!res.affected) + throw new NotFoundException( + "Topik tidak ditemukan di antara topik yang dapat Anda akses", + ); + + const resp: TopikIdRespDto = { id: params.id }; + + return resp; } - @Roles(RoleEnum.S2_TIM_TESIS, RoleEnum.ADMIN) + @ApiOperation({ + summary: "Delete topik. Roles: S2_TIM_TESIS, ADMIN, S2_PEMBIMBING", + }) + @ApiOkResponse({ type: TopikIdRespDto }) + @Roles(...HIGH_AUTHORITY_ROLES, RoleEnum.S2_PEMBIMBING) @Delete("/:id") - async delete(@Param() params: TopikParamDto) { - const res = await this.alokasiTopikService.remove(params.id); - if (!res.affected) throw new NotFoundException(); - return res; + async delete( + @Param() params: TopikParamDto, + @Req() req: Request, + ): Promise<TopikIdRespDto> { + let idPengaju = undefined; + const { roles, id } = req.user as AuthDto; + // user only has S2_PEMBIMBING role + if (!isHighAuthority(roles)) { + idPengaju = id; + } + + const res = await this.alokasiTopikService.remove(params.id, idPengaju); + if (!res.affected) + throw new NotFoundException( + "Topik tidak ditemukan di antara topik yang dapat Anda akses", + ); + + const resp: TopikIdRespDto = { id: params.id }; + return resp; } } diff --git a/src/alokasi-topik/alokasi-topik.dto.ts b/src/alokasi-topik/alokasi-topik.dto.ts index 90db74b42c003f32e2bccdcaeb7ace2a10acb265..5c41225c23ddf1fbefe996dfc7f961ad5ecebc01 100644 --- a/src/alokasi-topik/alokasi-topik.dto.ts +++ b/src/alokasi-topik/alokasi-topik.dto.ts @@ -81,7 +81,7 @@ export class createBulkRespDto { ids: string[]; } -export class CreateRespDto { +export class TopikIdRespDto { @ApiProperty() id: string; } diff --git a/src/alokasi-topik/alokasi-topik.service.ts b/src/alokasi-topik/alokasi-topik.service.ts index 6a272d71cd1632ada903a4fcddae1a010af2e1b6..dd00add6e9de08317fbeff35de231b06c831e02f 100644 --- a/src/alokasi-topik/alokasi-topik.service.ts +++ b/src/alokasi-topik/alokasi-topik.service.ts @@ -5,7 +5,7 @@ import { Topik } from "src/entities/topik.entity"; import { ArrayContains, Like, Repository } from "typeorm"; import { CreateBulkTopikDto, - CreateRespDto, + TopikIdRespDto, CreateTopikDto, GetAllRespDto, UpdateTopikDto, @@ -16,7 +16,7 @@ import { export class AlokasiTopikService { constructor(@InjectRepository(Topik) private topikRepo: Repository<Topik>) {} - async create(createDto: CreateTopikDto): Promise<CreateRespDto> { + async create(createDto: CreateTopikDto): Promise<TopikIdRespDto> { const ids = (await this.topikRepo.insert(createDto)).identifiers; return { id: ids[0].id }; @@ -131,11 +131,15 @@ export class AlokasiTopikService { } } - async update(id: string, updateDto: UpdateTopikDto) { - return await this.topikRepo.update({ id, aktif: true }, updateDto); + async update(id: string, updateDto: UpdateTopikDto, idPengaju?: string) { + const findOpt = idPengaju + ? { id, idPengaju, aktif: true } + : { id, aktif: true }; + return await this.topikRepo.update(findOpt, updateDto); } - async remove(id: string) { - return await this.topikRepo.update({ id }, { aktif: false }); + async remove(id: string, idPengaju?: string) { + const findOpt = idPengaju ? { id, idPengaju } : { id }; + return await this.topikRepo.update(findOpt, { aktif: false }); } } diff --git a/src/helper/roles.ts b/src/helper/roles.ts new file mode 100644 index 0000000000000000000000000000000000000000..690f29dd520df4c348a4520cfc75aee48540dca5 --- /dev/null +++ b/src/helper/roles.ts @@ -0,0 +1,7 @@ +import { RoleEnum } from "src/entities/pengguna.entity"; + +export const HIGH_AUTHORITY_ROLES = [RoleEnum.ADMIN, RoleEnum.S2_TIM_TESIS]; + +export function isHighAuthority(roles: RoleEnum[]) { + return roles.some((role) => HIGH_AUTHORITY_ROLES.includes(role)); +}