diff --git a/src/controllers/customer-controller.ts b/src/controllers/customer-controller.ts new file mode 100644 index 0000000..fe7d347 --- /dev/null +++ b/src/controllers/customer-controller.ts @@ -0,0 +1,171 @@ +import { Prisma, Status } from "@prisma/client"; +import { + Body, + Controller, + Delete, + Get, + Path, + Post, + Put, + Query, + Request, + Route, + Security, + Tags, +} from "tsoa"; +import { RequestWithUser } from "../interfaces/user"; +import prisma from "../db"; +import minio from "../services/minio"; +import HttpStatus from "../interfaces/http-status"; +import HttpError from "../interfaces/http-error"; + +if (!process.env.MINIO_BUCKET) { + throw Error("Require MinIO bucket."); +} + +const MINIO_BUCKET = process.env.MINIO_BUCKET; + +export type CustomerCreate = { + code?: string; + status?: Status; + customerType: string; + customerName: string; + customerNameEN: string; +}; + +export type CustomerUpdate = { + code?: string; + status?: "ACTIVE" | "INACTIVE"; + customerType?: string; + customerName?: string; + customerNameEN?: string; +}; + +function imageLocation(id: string) { + return `customer/img-${id}`; +} + +@Route("api/customer") +@Tags("Customer") +@Security("keycloak") +export class CustomerController extends Controller { + @Get() + async list( + @Query() query: string = "", + @Query() page: number = 1, + @Query() pageSize: number = 30, + ) { + const where = { + OR: [{ customerName: { contains: query } }, { customerNameEN: { contains: query } }], + } satisfies Prisma.CustomerWhereInput; + + const [result, total] = await prisma.$transaction([ + prisma.customer.findMany({ + where, + take: pageSize, + skip: (page - 1) * pageSize, + }), + prisma.customer.count({ where }), + ]); + + return { + result: await Promise.all( + result.map(async (v) => ({ + ...v, + imageUrl: await minio.presignedGetObject(MINIO_BUCKET, imageLocation(v.id), 12 * 60 * 60), + })), + ), + page, + pageSize, + total, + }; + } + + @Get("{customerId}") + async getById(@Path() customerId: string) { + const record = await prisma.customer.findFirst({ where: { id: customerId } }); + if (!record) + throw new HttpError(HttpStatus.NOT_FOUND, "Customer cannot be found.", "data_not_found"); + return Object.assign(record, { + imageUrl: await minio.presignedGetObject( + MINIO_BUCKET, + imageLocation(record.id), + 12 * 60 * 60, + ), + }); + } + + @Post() + async create(@Request() req: RequestWithUser, @Body() body: CustomerCreate) { + const record = await prisma.customer.create({ + data: { + ...body, + createdBy: req.user.name, + updateBy: req.user.name, + }, + }); + + this.setStatus(HttpStatus.CREATED); + + return Object.assign(record, { + imageUrl: await minio.presignedGetObject( + MINIO_BUCKET, + imageLocation(record.id), + 12 * 60 * 60, + ), + imageUploadUrl: await minio.presignedPutObject( + MINIO_BUCKET, + imageLocation(record.id), + 12 * 60 * 60, + ), + }); + } + + @Put("{customerId}") + async editById( + @Path() customerId: string, + @Request() req: RequestWithUser, + @Body() body: CustomerUpdate, + ) { + const record = await prisma.customer.update({ + where: { id: customerId }, + data: { + ...body, + createdBy: req.user.name, + updateBy: req.user.name, + }, + }); + + if (!record) { + throw new HttpError(HttpStatus.NOT_FOUND, "Customer cannot be found."); + } + + return Object.assign(record, { + imageUrl: await minio.presignedGetObject( + MINIO_BUCKET, + imageLocation(record.id), + 12 * 60 * 60, + ), + imageUploadUrl: await minio.presignedPutObject( + MINIO_BUCKET, + imageLocation(record.id), + 12 * 60 * 60, + ), + }); + } + + @Delete("{customerId}") + async deleteById(@Path() customerId: string) { + const record = await prisma.customer.findFirst({ where: { id: customerId } }); + + if (!record) { + throw new HttpError(HttpStatus.NOT_FOUND, "Customer cannot be found.", "data_not_found"); + } + + if (record.status !== Status.CREATED) { + throw new HttpError(HttpStatus.FORBIDDEN, "Customer is in used.", "data_in_used"); + } + + return await prisma.customer.delete({ where: { id: customerId } }); + } +} diff --git a/tsoa.json b/tsoa.json index 2f059de..92b82d2 100644 --- a/tsoa.json +++ b/tsoa.json @@ -12,6 +12,19 @@ "description": "Keycloak Bearer Token", "in": "header" } + }, + "spec": { + "tags": [ + { "name": "OpenAPI" }, + { "name": "Address" }, + { "name": "Branch" }, + { "name": "User" }, + { "name": "Branch User" }, + { "name": "User Branch" }, + { "name": "Customer" }, + { "name": "Customer Branch" }, + { "name": "Employee" } + ] } }, "routes": {