import { Body, Controller, Delete, Get, Path, Post, Put, Request, Route, Security, SuccessResponse, Tags, } from "tsoa"; import * as Minio from "minio"; import HttpError from "../interfaces/http-error"; import HttpStatusCode from "../interfaces/http-status"; import { listFolder, listItem, pathExist, replaceIllegalChars } from "../utils/minio"; import { EhrFile, EhrFolder } from "../interfaces/ehr-fs"; import minioClient from "../storage"; import esClient from "../elasticsearch"; @Route("/cabinet/{cabinetName}/drawer/{drawerName}/folder") export class FolderController extends Controller { @Get("/") @Tags("Folder") @SuccessResponse(HttpStatusCode.OK) public async listFolder( @Path() cabinetName: string, @Path() drawerName: string, ): Promise { const fullpath = [cabinetName, drawerName, ""].join("/"); if (!(await pathExist(fullpath))) { throw new HttpError(HttpStatusCode.NOT_FOUND, "Provided path does not exist."); } return listFolder(fullpath); } @Post("/") @Tags("Folder") @Security("bearerAuth") @SuccessResponse(HttpStatusCode.CREATED) public async createFolder( @Request() request: { user: { preferred_username: string } }, @Body() body: { name: string }, @Path() cabinetName: string, @Path() drawerName: string, ) { if (!(await pathExist(`${cabinetName}/${drawerName}/`))) { throw new HttpError(HttpStatusCode.PRECONDITION_FAILED, "Cabinet or drawer cannot be found."); } const uploaded = await minioClient .putObject( "ehr", `${cabinetName}/${drawerName}/${replaceIllegalChars(body.name)}/.keep`, "", 0, { createdAt: new Date().toISOString(), createdBy: request.user.preferred_username, }, ) .catch((e) => console.error(e)); if (!uploaded) { throw new Error("Object storage error occured."); } return this.setStatus(HttpStatusCode.CREATED); } @Put("/{folderName}") @Tags("Folder") @Security("bearerAuth") @SuccessResponse(HttpStatusCode.NO_CONTENT) public async editFolder( @Body() body: { name: string }, @Path() cabinetName: string, @Path() drawerName: string, @Path() folderName: string, ) { const fullpath = `${cabinetName}/${drawerName}/${folderName}`; const list = await listItem(fullpath, true); const cond = new Minio.CopyConditions(); await Promise.all( list.map(async (current) => { if (!current.name) return; const destination = `${cabinetName}/${drawerName}/${replaceIllegalChars( body.name, )}/${current.name.slice(fullpath.length)}`; const source = `/ehr/${current.name}`; return await minioClient .copyObject("ehr", destination, source, cond) .then(async () => { if (!current.name) return; await minioClient.removeObject("ehr", current.name); if (current.name.includes(".keep")) return; const search = await esClient.search }>({ index: process.env.ELASTICSEARCH_INDEX ?? "ehr-index", query: { match: { pathname: current.name, }, }, }); if (search && search.hits.hits.length === 0) { throw new Error("Data cannot be found in database."); } const data = search.hits.hits[0]; await esClient.update({ index: process.env.ELASTICSEARCH_INDEX ?? "ehr-index", id: data._id, doc: { pathname: destination }, }); }) .catch((e) => { console.error(e); throw new Error("Failed to move."); }); }), ); return this.setStatus(HttpStatusCode.NO_CONTENT); } @Delete("/{folderName}") @Tags("Folder") @Security("bearerAuth") @SuccessResponse(HttpStatusCode.NO_CONTENT) public async deleteFolder( @Path() cabinetName: string, @Path() drawerName: string, @Path() folderName: string, ) { return new Promise((resolve, reject) => { const objects: string[] = []; const stream = minioClient.listObjectsV2( "ehr", `${cabinetName}/${drawerName}/${folderName}`, true, ); stream.on("data", (v) => { if (!(v && v.name)) return; objects.push(v.name); }); stream.on("close", () => minioClient.removeObjects("ehr", objects)); stream.on("error", () => reject(new Error("Object storage error occured."))); resolve(this.setStatus(HttpStatusCode.NO_CONTENT)); }); } }