import { Prisma, Status } from "@prisma/client"; import { Body, Controller, Delete, Get, Head, OperationId, Path, Post, Put, Query, Request, Route, Security, Tags, } from "tsoa"; import prisma from "../db"; import { isUsedError, notFoundError } from "../utils/error"; import { queryOrNot } from "../utils/relation"; import { RequestWithUser } from "../interfaces/user"; import { deleteFile, fileLocation, getFile, getPresigned, listFile, setFile } from "../utils/minio"; import HttpError from "../interfaces/http-error"; import HttpStatus from "../interfaces/http-status"; import { filterStatus } from "../services/prisma"; type InstitutionPayload = { name: string; nameEN: string; code: string; addressEN: string; address: string; soi?: string | null; soiEN?: string | null; moo?: string | null; mooEN?: string | null; street?: string | null; streetEN?: string | null; subDistrictId: string; districtId: string; provinceId: string; selectedImage?: string | null; }; @Route("api/v1/institution") @Tags("Institution") export class InstitutionController extends Controller { @Get() @Security("keycloak") @OperationId("getInstitutionList") async getInstitutionList( @Query() query: string = "", @Query() page: number = 1, @Query() pageSize: number = 30, @Query() status?: Status, @Query() activeOnly?: boolean, @Query() group?: string, ) { return this.getInstitutionListByCriteria(query, page, pageSize, status, activeOnly, group); } @Post("list") @Security("keycloak") @OperationId("getInstitutionListByCriteria") async getInstitutionListByCriteria( @Query() query: string = "", @Query() page: number = 1, @Query() pageSize: number = 30, @Query() status?: Status, @Query() activeOnly?: boolean, @Query() group?: string, @Body() body?: { group?: string[]; }, ) { const where = { ...filterStatus(activeOnly ? Status.ACTIVE : status), group: body?.group ? { in: body.group } : group, OR: queryOrNot(query, [ { name: { contains: query } }, { code: { contains: query, mode: "insensitive" } }, ]), } satisfies Prisma.InstitutionWhereInput; const [result, total] = await prisma.$transaction([ prisma.institution.findMany({ where, include: { province: true, district: true, subDistrict: true, }, orderBy: [{ statusOrder: "asc" }, { code: "asc" }], take: pageSize, skip: (page - 1) * pageSize, }), prisma.institution.count({ where }), ]); return { result, page, pageSize, total }; } @Get("{institutionId}") @Security("keycloak") @OperationId("getInstitution") async getInstitution(@Path() institutionId: string, @Query() group?: string) { return await prisma.institution.findFirst({ include: { province: true, district: true, subDistrict: true, }, where: { id: institutionId, group }, }); } @Post() @Security("keycloak") @OperationId("createInstitution") async createInstitution( @Body() body: InstitutionPayload & { status?: Status; }, ) { return await prisma.$transaction(async (tx) => { const last = await tx.runningNo.upsert({ where: { key: `INST_${body.code}`, }, create: { key: `INST_${body.code}`, value: 1, }, update: { value: { increment: 1 } }, }); return await tx.institution.create({ data: { ...body, code: `${body.code}${last.value.toString().padStart(5, "0")}`, group: body.code, }, }); }); } @Put("{institutionId}") @Security("keycloak") @OperationId("updateInstitution") async updateInstitution( @Path() institutionId: string, @Body() body: InstitutionPayload & { status?: "ACTIVE" | "INACTIVE"; }, ) { return await prisma.institution.update({ where: { id: institutionId }, data: { ...body, statusOrder: +(body.status === "INACTIVE") }, }); } @Delete("{institutionId}") @Security("keycloak") @OperationId("deleteInstitution") async deleteInstitution(@Path() institutionId: string) { return await prisma.$transaction(async (tx) => { const record = await tx.institution.findFirst({ where: { id: institutionId }, include: { taskOrder: { take: 1, }, }, }); if (!record) throw notFoundError("Institution"); if (record.status !== "CREATED" || record.taskOrder.length > 0) { throw isUsedError("Institution"); } return await tx.institution.delete({ where: { id: institutionId }, }); }); } } @Route("api/v1/institution/{institutionId}") @Tags("Institution") export class InstitutionFileController extends Controller { private async checkPermission(_user: RequestWithUser["user"], id: string) { const data = await prisma.institution.findUnique({ where: { id }, }); if (!data) throw notFoundError("Institution"); } @Get("image") @Security("keycloak") async listImage(@Request() req: RequestWithUser, @Path() institutionId: string) { await this.checkPermission(req.user, institutionId); return await listFile(fileLocation.institution.img(institutionId)); } @Get("image/{name}") async getImage( @Request() req: RequestWithUser, @Path() institutionId: string, @Path() name: string, ) { return req.res?.redirect(await getFile(fileLocation.institution.img(institutionId, name))); } @Head("image/{name}") async headImage( @Request() req: RequestWithUser, @Path() institutionId: string, @Path() name: string, ) { return req.res?.redirect( await getPresigned("head", fileLocation.institution.img(institutionId, name)), ); } @Put("image/{name}") @Security("keycloak") async putImage( @Request() req: RequestWithUser, @Path() institutionId: string, @Path() name: string, ) { if (!req.headers["content-type"]?.startsWith("image/")) { throw new HttpError(HttpStatus.BAD_REQUEST, "Not a valid image.", "notValidImage"); } await this.checkPermission(req.user, institutionId); return req.res?.redirect(await setFile(fileLocation.institution.img(institutionId, name))); } @Delete("image/{name}") @Security("keycloak") async delImage( @Request() req: RequestWithUser, @Path() institutionId: string, @Path() name: string, ) { await this.checkPermission(req.user, institutionId); return await deleteFile(fileLocation.institution.img(institutionId, name)); } @Get("attachment") @Security("keycloak") async listAttachment(@Request() req: RequestWithUser, @Path() institutionId: string) { await this.checkPermission(req.user, institutionId); return await listFile(fileLocation.institution.attachment(institutionId)); } @Get("attachment/{name}") @Security("keycloak") async getAttachment(@Path() institutionId: string, @Path() name: string) { return await getFile(fileLocation.institution.attachment(institutionId, name)); } @Head("attachment/{name}") @Security("keycloak") async headAttachment(@Path() institutionId: string, @Path() name: string) { return await getPresigned("head", fileLocation.institution.attachment(institutionId, name)); } @Put("attachment/{name}") @Security("keycloak") async putAttachment( @Request() req: RequestWithUser, @Path() institutionId: string, @Path() name: string, ) { await this.checkPermission(req.user, institutionId); return await setFile(fileLocation.institution.attachment(institutionId, name)); } @Delete("attachment/{name}") @Security("keycloak") async delAttachment( @Request() req: RequestWithUser, @Path() institutionId: string, @Path() name: string, ) { await this.checkPermission(req.user, institutionId); return await deleteFile(fileLocation.institution.attachment(institutionId, name)); } }