import { Body, Controller, Delete, Get, Path, Post, Put, Route, Security, SuccessResponse, Tags, Request, } from "tsoa"; import * as Minio from "minio"; import minioClient from "../storage"; import { EhrFile, EhrFolder } from "../interfaces/ehr-fs"; import HttpStatusCode from "../interfaces/http-status"; import { listFolder, listItem, replaceIllegalChars } from "../utils/minio"; import esClient from "../elasticsearch"; @Route("cabinet") export class CabinetController extends Controller { @Get("/") @Tags("Cabinet") @SuccessResponse(HttpStatusCode.OK) public async listCabinet(): Promise { const list = await listFolder().catch((e) => console.error(`Error List Folder: ${e}`)); if (!list) { throw new Error("Error listing folder"); } return list; } @Post("/") @Tags("Cabinet") @Security("bearerAuth") @SuccessResponse(HttpStatusCode.CREATED) public async createCabinet( @Request() request: { user: { preferred_username: string } }, @Body() body: { name: string }, ) { const uploaded = await minioClient .putObject("ehr", `${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("/{cabinetName}") @Tags("Cabinet") @Security("bearerAuth") @SuccessResponse(HttpStatusCode.NO_CONTENT, "Success") public async editCabinet( @Path() cabinetName: string, @Body() body: { name: string }, ): Promise { const list = await listItem(`${cabinetName}/`, true); const cond = new Minio.CopyConditions(); await Promise.all( list.map(async (current) => { if (!current.name) return; const destination = `${replaceIllegalChars(body.name)}/${current.name.slice( cabinetName.length + 1, )}`; 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("/{cabinetName}") @Tags("Cabinet") @Security("bearerAuth") @SuccessResponse(HttpStatusCode.NO_CONTENT) public async deleteCabinet(@Path() cabinetName: string) { await new Promise((resolve, reject) => { const objects: string[] = []; const stream = minioClient.listObjectsV2("ehr", `${cabinetName}/`, 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}/` }, }, }); 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); } }