import { Body, Controller, Delete, Get, Path, Post, Put, Request, Route, Security, SuccessResponse, Tags, } from "tsoa"; import * as Minio from "minio"; import minioClient from "../storage"; import HttpStatusCode from "../interfaces/http-status"; import HttpError from "../interfaces/http-error"; import { listFolder, listItem, pathExist, replaceIllegalChars } from "../utils/minio"; import esClient from "../elasticsearch"; import { EhrFile, EhrFolder } from "../interfaces/ehr-fs"; @Route("/cabinet/{cabinetName}/drawer") export class DrawerController extends Controller { @Get("/") @Tags("Drawer") @SuccessResponse(HttpStatusCode.OK) public async listDrawer(@Path() cabinetName: string): Promise { const fullpath = [cabinetName, ""].join("/"); if (!(await pathExist(fullpath))) { throw new HttpError(HttpStatusCode.NOT_FOUND, "Provided path does not exist."); } return listFolder(fullpath); } @Post("/") @Tags("Drawer") @Security("bearerAuth") @SuccessResponse(HttpStatusCode.CREATED) public async createDrawer( @Request() request: { user: { preferred_username: string } }, @Path() cabinetName: string, @Body() body: { name: string }, ) { if (!(await pathExist(`${cabinetName}/`))) { throw new HttpError(HttpStatusCode.PRECONDITION_FAILED, "Cabinet cannot be found."); } const uploaded = await minioClient .putObject("ehr", `${cabinetName}/${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("/{drawerName}") @Tags("Drawer") @Security("bearerAuth") @SuccessResponse(HttpStatusCode.NO_CONTENT) public async editDrawer( @Path() cabinetName: string, @Path() drawerName: string, @Body() body: { name: string }, ): Promise { const fullpath = `${cabinetName}/${drawerName}/`; 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}/${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("/{drawerName}") @Tags("Drawer") @Security("bearerAuth") @SuccessResponse(HttpStatusCode.NO_CONTENT) public async deleteDrawer(@Path() cabinetName: string, @Path() drawerName: string) { await new Promise((resolve, reject) => { const objects: string[] = []; const stream = minioClient.listObjectsV2("ehr", `${cabinetName}/${drawerName}/`, true); stream.on("data", (v) => { if (!(v && v.name)) return; objects.push(v.name); }); stream.on("close", async () => { minioClient.removeObjects("ehr", objects); resolve(); }); stream.on("error", () => reject(new Error("Object storage error occured."))); }); const searchResult = await esClient.search({ index: process.env.ELASTICSEARCH_INDEX ?? "ehr-index", query: { prefix: { pathname: `${cabinetName}/${drawerName}/` }, }, }); await Promise.all( searchResult.hits.hits.map(async (v) => { return esClient .delete({ index: process.env.ELASTICSEARCH_INDEX ?? "ehr-index", id: v._id, }) .catch((e) => console.error(`ElasticSearch Error: ${e}`)); }), ); return this.setStatus(HttpStatusCode.NO_CONTENT); } }