refactor: file upload and image

This commit is contained in:
Methapon Metanipat 2024-09-11 10:04:20 +07:00
parent ceb72f9c50
commit b1bd14f6aa

View file

@ -17,7 +17,6 @@ import { RequestWithUser } from "../interfaces/user";
import prisma from "../db"; import prisma from "../db";
import HttpStatus from "../interfaces/http-status"; import HttpStatus from "../interfaces/http-status";
import HttpError from "../interfaces/http-error"; import HttpError from "../interfaces/http-error";
import minio, { presignedGetObjectIfExist } from "../services/minio";
import { isSystem } from "../utils/keycloak"; import { isSystem } from "../utils/keycloak";
import { filterStatus } from "../services/prisma"; import { filterStatus } from "../services/prisma";
import { import {
@ -26,13 +25,13 @@ import {
createPermCondition, createPermCondition,
} from "../services/permission"; } from "../services/permission";
import { connectOrDisconnect, connectOrNot } from "../utils/relation"; import { connectOrDisconnect, connectOrNot } from "../utils/relation";
import { throwRelationError } from "../utils/error"; import { throwNotFound, throwRelationError } from "../utils/error";
import { deleteFile, fileLocation, getFile, listFile, setFile } from "../utils/minio";
if (!process.env.MINIO_BUCKET) { if (!process.env.MINIO_BUCKET) {
throw Error("Require MinIO bucket."); throw Error("Require MinIO bucket.");
} }
const MINIO_BUCKET = process.env.MINIO_BUCKET;
const MANAGE_ROLES = [ const MANAGE_ROLES = [
"system", "system",
"head_of_admin", "head_of_admin",
@ -50,14 +49,6 @@ function globalAllow(user: RequestWithUser["user"]) {
const permissionCond = createPermCondition(globalAllow); const permissionCond = createPermCondition(globalAllow);
const permissionCheck = createPermCheck(globalAllow); const permissionCheck = createPermCheck(globalAllow);
function imageLocation(id: string) {
return `employee/${id}/profile-image`;
}
function attachmentLocation(id: string, filename?: string) {
return `employee/${id}/attachment/${filename || ""}`;
}
type EmployeeCreate = { type EmployeeCreate = {
customerBranchId: string; customerBranchId: string;
@ -364,17 +355,9 @@ export class EmployeeController extends Controller {
where: { id: employeeId }, where: { id: employeeId },
}); });
if (!record) { if (!record) return throwNotFound("Employee");
throw new HttpError(HttpStatus.NOT_FOUND, "Employee cannot be found.", "employeeNotFound");
}
return Object.assign(record, { return record;
profileImageUrl: await presignedGetObjectIfExist(
MINIO_BUCKET,
imageLocation(employeeId),
12 * 60 * 60,
),
});
} }
@Post() @Post()
@ -750,9 +733,7 @@ export class EmployeeController extends Controller {
}, },
}); });
if (!record) { if (!record) return throwNotFound("Employee");
throw new HttpError(HttpStatus.NOT_FOUND, "Employee cannot be found.", "employeeNotFound");
}
await permissionCheck(req.user, record.customerBranch.customer.registeredBranch); await permissionCheck(req.user, record.customerBranch.customer.registeredBranch);
@ -778,114 +759,105 @@ export class EmployeeController extends Controller {
where: { masterId: employeeId }, where: { masterId: employeeId },
}); });
} }
@Get("{employeeId}/image")
async getImageByEmployeeId(@Request() req: RequestWithUser, @Path() employeeId: string) {
const url = await presignedGetObjectIfExist(MINIO_BUCKET, imageLocation(employeeId), 60 * 60);
if (!url) {
throw new HttpError(HttpStatus.NOT_FOUND, "Image cannot be found", "imageNotFound");
} }
return req.res?.redirect(url); @Route("api/v1/employee/{employeeId}")
} @Tags("Branch")
export class EmployeeFileController extends Controller {
@Put("{employeeId}/image") private async checkPermission(user: RequestWithUser["user"], id: string) {
@Security("keycloak", MANAGE_ROLES) const data = await prisma.employee.findFirst({
async setImageByEmployeeId(@Request() req: RequestWithUser, @Path() employeeId: string) { where: { id },
const record = await prisma.employee.findFirst({ include: {
where: { id: employeeId }, customerBranch: {
include: {
customer: {
include: {
registeredBranch: {
include: branchRelationPermInclude(user),
},
},
},
},
},
},
}); });
if (!data) return throwNotFound("Employee");
if (!record) { await permissionCheck(user, data.customerBranch.customer.registeredBranch);
throw new HttpError(HttpStatus.NOT_FOUND, "Employee cannot be found.", "employeeNotFound");
} }
return req.res?.redirect( @Get("image")
await minio.presignedPutObject(MINIO_BUCKET, imageLocation(employeeId), 12 * 60 * 60),
);
}
}
@Route("api/v1/employee/{employeeId}/attachment")
@Tags("Employee")
@Security("keycloak") @Security("keycloak")
export class EmployeeAttachmentController extends Controller { async listImage(@Request() req: RequestWithUser, @Path() employeeId: string) {
@Get() await this.checkPermission(req.user, employeeId);
async listAttachment(@Path() employeeId: string) { return await listFile(fileLocation.employee.img(employeeId));
const record = await prisma.employee.findFirst({
where: { id: employeeId },
});
if (!record) {
throw new HttpError(HttpStatus.NOT_FOUND, "Employee cannot be found.", "employeeNotFound");
} }
const list = await new Promise<string[]>((resolve, reject) => { @Get("image/{name}")
const item: string[] = []; async getImage(
const stream = minio.listObjectsV2(MINIO_BUCKET, attachmentLocation(employeeId));
stream.on("data", (v) => v && v.name && item.push(v.name));
stream.on("end", () => resolve(item));
stream.on("error", () => reject(new Error("MinIO error.")));
});
return list.map((v) => v.split("/").at(-1) as string);
}
@Get("{filename}")
async getAttachment(@Path() employeeId: string, @Path() filename: string) {
const record = await prisma.employee.findFirst({
where: { id: employeeId },
});
if (!record) {
throw new HttpError(HttpStatus.NOT_FOUND, "Employee cannot be found.", "employeeNotFound");
}
return await minio.presignedGetObject(
MINIO_BUCKET,
attachmentLocation(record.id, filename),
12 * 60 * 60,
);
}
@Put("{filename}")
async addAttachment(
@Request() req: RequestWithUser, @Request() req: RequestWithUser,
@Path() employeeId: string, @Path() employeeId: string,
@Path() filename: string, @Path() name: string,
) { ) {
const record = await prisma.employee.findFirst({ return req.res?.redirect(await getFile(fileLocation.employee.img(employeeId, name)));
where: { id: employeeId },
});
if (!record) {
throw new HttpError(HttpStatus.NOT_FOUND, "Employee cannot be found.", "employeeNotFound");
} }
return req.res?.redirect( @Put("image/{name}")
await minio.presignedPutObject( @Security("keycloak")
MINIO_BUCKET, async putImage(
attachmentLocation(record.id, filename), @Request() req: RequestWithUser,
12 * 60 * 60, @Path() employeeId: 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, employeeId);
return req.res?.redirect(await setFile(fileLocation.employee.img(employeeId, name)));
} }
@Delete("{filename}") @Delete("image/{name}")
async deleteAttachment(@Path() employeeId: string, @Path() filename: string) { @Security("keycloak")
const record = await prisma.employee.findFirst({ async delImage(
where: { id: employeeId }, @Request() req: RequestWithUser,
}); @Path() employeeId: string,
@Path() name: string,
if (!record) { ) {
throw new HttpError(HttpStatus.NOT_FOUND, "Employee cannot be found.", "employeeNotFound"); await this.checkPermission(req.user, employeeId);
return await deleteFile(fileLocation.employee.img(employeeId, name));
} }
await minio.removeObject(MINIO_BUCKET, attachmentLocation(record.id, filename), { @Get("attachment")
forceDelete: true, @Security("keycloak")
}); async listAttachment(@Request() req: RequestWithUser, @Path() employeeId: string) {
await this.checkPermission(req.user, employeeId);
return await listFile(fileLocation.employee.attachment(employeeId));
}
@Get("attachment/{name}")
@Security("keycloak")
async getAttachment(@Path() employeeId: string, @Path() name: string) {
return await getFile(fileLocation.employee.attachment(employeeId, name));
}
@Put("attachment/{name}")
@Security("keycloak")
async putAttachment(
@Request() req: RequestWithUser,
@Path() employeeId: string,
@Path() name: string,
) {
await this.checkPermission(req.user, employeeId);
return req.res?.redirect(await setFile(fileLocation.employee.attachment(employeeId, name)));
}
@Delete("attachment/{name}")
@Security("keycloak")
async delAttachment(
@Request() req: RequestWithUser,
@Path() employeeId: string,
@Path() name: string,
) {
await this.checkPermission(req.user, employeeId);
return await deleteFile(fileLocation.employee.attachment(employeeId, name));
} }
} }