Merge branch 'development'

This commit is contained in:
Methapon2001 2023-12-15 15:16:28 +07:00
commit e73b51880f
No known key found for this signature in database
GPG key ID: 849924FEF46BD132
18 changed files with 474 additions and 5503 deletions

View file

@ -211,6 +211,7 @@ const file = ref<File | undefined>()
label="กดปุ่มEnterเพื่อเพิ่ม"
use-input
use-chips
hide-dropdown-icon
multiple
input-debounce="0"
@new-value="createCategory"
@ -220,11 +221,6 @@ const file = ref<File | undefined>()
@update:model-value="(v) => $emit('update:category', v)"
data-testid="filterDataCategory"
>
<template v-slot:no-option>
<q-item>
<q-item-section class="text-grey"> No results </q-item-section>
</q-item>
</template>
</q-select>
</div>
</section>

View file

@ -2,6 +2,7 @@
import type { QTableProps } from 'quasar'
import { onMounted, ref, watch } from 'vue'
import { storeToRefs } from 'pinia'
import io from 'socket.io-client'
import { useSearchDataStore } from '@/stores/searched-data'
import { useFileInfoStore } from '@/stores/file-info-data'
@ -22,7 +23,7 @@ const props = withDefaults(
action: false,
},
)
const { foundFile, isActFoundFile } = storeToRefs(useSearchDataStore())
const { foundFile } = storeToRefs(useSearchDataStore())
const { getFileInfo, getSize, getType } = useFileInfoStore()
const storageStore = useStorage()
@ -75,6 +76,29 @@ const columns: QTableProps['columns'] = [
style: 'width: 20px',
},
]
const socket = io(import.meta.env.VITE_API_HOST)
socket.on('FileUpload', (data: StorageFile) => {
replaceSearchItem(data.pathname, data)
})
socket.on('FileMove', (data: { from: StorageFile; to: StorageFile }) => {
replaceSearchItem(data.from.pathname, data.to)
})
socket.on('FileDelete', (data: { pathname: string }) => {
removeSearchItem(data.pathname)
})
function removeSearchItem(pathname: string) {
const idx = foundFile.value.findIndex((v) => v.pathname === pathname)
if (idx !== -1) foundFile.value.splice(idx, 1)
filterSearch()
}
function replaceSearchItem(pathname: string, data: StorageFile) {
const idx = foundFile.value.findIndex((v) => v.pathname === pathname)
if (idx !== -1) foundFile.value[idx] = data
filterSearch()
}
function triggerFileDelete(pathname: string) {
deleteFormType.value = 'deleteFile'
@ -85,10 +109,6 @@ function triggerFileDelete(pathname: string) {
function confirmDelete() {
if (deleteFormType) {
deleteFile(deleteFormPath.value)
setTimeout(() => {
isActFoundFile.value = true
}, 1000)
}
}
@ -162,7 +182,9 @@ onMounted(() => {
</div>
<div v-if="props.viewMode === 'view_list' && foundFile.length > 0">
<span class="text-body text-grey">จำนวน {{ filterFoundFile?.length }} รายการ</span>
<span class="text-body text-grey"
>จำนวน {{ filterFoundFile?.length }} รายการ</span
>
<div class="grid q-mt-md">
<div v-for="(value, index) in filterFoundFile" :key="value.title">
<div

View file

@ -18,7 +18,7 @@ async function downloadSubmit(path: string | undefined) {
const arr = path.split('/')
if (arr.length < 3 || arr.length > 4) return
if (arr.length < 4 || arr.length > 5) return
const file = arr.pop()

View file

@ -3,7 +3,6 @@ import { ref, watch } from 'vue'
import { storeToRefs } from 'pinia'
import axiosClient from '@/services/HttpService'
import mime from 'mime'
import io from 'socket.io-client'
import type { StorageFile } from '@/stores/storage'
import { useSearchDataStore } from '@/stores/searched-data'
@ -15,11 +14,9 @@ import AdvancedSearch from '@/modules/01_user/components/AdvancedSearch.vue'
const loaderStore = useLoader()
const { isFilePreview } = storeToRefs(useFileInfoStore())
const {
foundFile,
isExact,
isSearch,
isAdvSearchCall,
isActFoundFile,
searchData,
advSearchDataField,
advSearchDataRow,
@ -41,20 +38,6 @@ const submitSearchData = ref<{
const props = defineProps<{
mode: 'admin' | 'user'
}>()
const socket = io(import.meta.env.VITE_API_HOST)
socket.on('FileUpload', (data: StorageFile) => {
replaceSearchItem(data.pathname, data)
})
socket.on('FileMove', (data: { from: StorageFile; to: StorageFile }) => {
replaceSearchItem(data.from.pathname, data.to)
})
function replaceSearchItem(pathname: string, data: StorageFile) {
const idx = foundFile.value.findIndex((v) => v.pathname === pathname)
if (idx !== -1) foundFile.value[idx] = data
}
async function submitSearch() {
isFilePreview.value = false
@ -136,18 +119,6 @@ async function submitSearch() {
}
}
watch(
() => isActFoundFile.value,
(edited) => {
if (edited === true) {
submitSearch()
setTimeout(() => {
isActFoundFile.value = false
}, 1000)
}
},
)
watch(
() => searchData.value.value,
(search) => {
@ -165,7 +136,7 @@ watch(
outlined
dense
label="ค้นหา"
debounce="500"
debounce="300"
bg-color="white"
v-model="searchData.value"
id="inputSearch"

View file

@ -24,7 +24,6 @@ export const useSearchDataStore = defineStore('searched', () => {
const isAdvSearchCall = ref<boolean>(false)
const isSearch = ref<Boolean>(false)
const isExact = ref<boolean>(false)
const isActFoundFile = ref<Boolean>(false)
const searchData = ref<Search>({
field: 'title',
value: '',
@ -51,7 +50,6 @@ export const useSearchDataStore = defineStore('searched', () => {
isSearch,
isExact,
isAdvSearchCall,
isActFoundFile,
searchData,
advSearchDataRow,
advSearchDataField,

View file

@ -19,10 +19,10 @@ test.afterAll(async () => {
})
test('Login', async () => {
await page.goto('http://localhost:3010/admin')
await page.goto('https://edm.frappet.synology.me/admin')
await expect(page).toHaveTitle('Sign in to EDM')
await page.fill("input[name='username']", 'oom')
await page.fill("input[name='password']", 'oom')
await page.fill("input[name='username']", 'net')
await page.fill("input[name='password']", 'P@ssw0rd')
await page.click("input[name='login']")
await page.waitForTimeout(2000)
@ -31,18 +31,13 @@ test('Login', async () => {
test('Create Cabinet', async () => {
await page.waitForTimeout(2000)
await page.click("//div[@id='triggerFolderCreateFileItem']")
await page.fill(
"//input[@placeholder='กรอกชื่อ']",
cabinet,
)
await page.fill("//input[@placeholder='กรอกชื่อ']", cabinet)
await page.click("//button[@type='submit']")
await page.waitForTimeout(300)
await expect(
page.locator("(//div[contains(@class,'bg-white rounded-borders')])[2]"),
).toContainText(cabinet)
await expect(page.locator("(//div[@class='col'])[3]")).toContainText(cabinet)
await page.waitForTimeout(300)
})
@ -53,74 +48,159 @@ test('Go to Cabinet', async () => {
test('Create drawer', async () => {
await page.click("//div[@id='triggerFolderCreateFileItem']")
await page.fill(
"//input[@placeholder='กรอกชื่อ']",
drawer,
)
await page.fill("//input[@placeholder='กรอกชื่อ']", drawer)
await page.click("//button[@type='submit']")
await page.waitForTimeout(300)
await expect(
page.locator("(//div[contains(@class,'bg-white rounded-borders')])[2]"),
).toContainText(drawer)
await expect(page.locator("(//div[@class='col'])[3]")).toContainText(drawer)
await page.waitForTimeout(300)
})
test('Go into drawer', async () => {
await page.click(`(//div[text()='${drawer}'])[2]`)
await expect(
page.locator("(//div[contains(@class,'bg-white rounded-borders')])[2]"),
).not.toHaveText(drawer)
await expect(page.locator("(//div[@class='col'])[3]")).not.toHaveText(drawer)
})
test('Create Folder', async () => {
await page.click("//div[@id='triggerFolderCreateFileItem']")
await page.fill(
"//input[@placeholder='กรอกชื่อ']",
folder,
)
await page.fill("//input[@placeholder='กรอกชื่อ']", folder)
await page.click("//button[@type='submit']")
await page.waitForTimeout(300)
await expect(
page.locator("(//div[contains(@class,'bg-white rounded-borders')])[2]"),
).toContainText(folder)
await expect(page.locator("(//div[@class='col'])[3]")).toContainText(folder)
})
test('Go into Folder', async () => {
await page.click(`(//div[text()='${folder}'])[2]`)
await expect(
page.locator("(//div[contains(@class,'bg-white rounded-borders')])[2]"),
).not.toHaveText(folder)
await expect(page.locator("(//div[@class='col'])[3]")).not.toHaveText(folder)
})
test('Create Subfolder', async () => {
await page.click("//div[@id='triggerFolderCreateFileItem']")
await page.fill(
"//input[@placeholder='กรอกชื่อ']",
await page.fill("//input[@placeholder='กรอกชื่อ']", subfolder)
await page.click("//button[@type='submit']")
await page.waitForTimeout(300)
await expect(page.locator("(//div[@class='col'])[3]")).toContainText(
subfolder,
)
await page.click("(//button[@type='submit'])[3]")
await page.waitForTimeout(300)
await expect(
page.locator("(//div[contains(@class,'bg-white rounded-borders')])[2]"),
).toContainText(subfolder)
})
test('Go into Subfolder', async () => {
await page.click(`//div[text()='${subfolder}']`)
await expect(
page.locator("(//div[contains(@class,'bg-white rounded-borders')])[2]"),
).not.toHaveText(folder)
await page.click(`(//div[text()='${subfolder}'])[2]`)
await expect(page.locator("(//div[@class='col'])[3]")).not.toHaveText(folder)
})
test('Upload File in SubFolder Level', async () => {
await page.click("//span[text()='สร้างเอกสาร']")
await page
.locator("//input[@type='file']")
.setInputFiles('tests/searchtest.txt')
await page.fill("//input[@placeholder='กรอกชื่อเรื่อง']", 'testtext')
await page.click("(//button[@type='submit'])[1]")
await page.waitForTimeout(2500)
await page.click('button#getFolder')
await page.waitForTimeout(1000)
await expect(page.locator("(//div[@class='col'])[3]")).toContainText(
'testtext',
)
})
test('Go into Text File', async () => {
await page.click("//div[text()='testtext']")
})
test('Download File Text', async () => {
await page.click("//span[text()='ดาวน์โหลด']")
await page.waitForTimeout(2000)
})
test('Go back to SubFolder Level Again', async () => {
await page.click("//i[text()='arrow_back']")
})
test('Delete file Cancel', async () => {
await page.click(
`//button[@data-testid='action${cabinet}/${drawer}/${folder}/${subfolder}/searchtest.txt']`,
)
await page.click("(//div[@role='listitem'])[2]")
await page.click("(//div[@class='q-space']/following-sibling::button)[2]")
await expect(page.locator("(//div[@class='col'])[3]")).toContainText(
'testtext',
)
})
test('Delete file Confirm', async () => {
await page.click(
`//button[@data-testid='action${cabinet}/${drawer}/${folder}/${subfolder}/searchtest.txt']`,
)
await page.click("(//div[@role='listitem'])[2]")
await page.click(
"(//div[contains(@class,'q-card__actions justify-end')]//button)[2]",
)
await expect(page.locator("(//div[@class='col'])[3]")).not.toHaveText(
'testtext',
)
})
test('Back to Folder', async () => {
await page.click("//i[text()='arrow_back']")
await expect(
page.locator("(//div[contains(@class,'bg-white rounded-borders')])[2]"),
).toContainText(subfolder)
await expect(page.locator("(//div[@class='col'])[3]")).toContainText(
subfolder,
)
})
test('Upload File in Folder Level', async () => {
await page.click("//span[text()='สร้างเอกสาร']")
await page
.locator("//input[@type='file']")
.setInputFiles('tests/searchtest.txt')
await page.fill("//input[@placeholder='กรอกชื่อเรื่อง']", 'testtext')
await page.click("(//button[@type='submit'])[1]")
await page.waitForTimeout(2500)
await page.click('button#getFolder')
await page.waitForTimeout(1000)
await expect(page.locator("(//div[@class='col'])[3]")).toContainText(
'testtext',
)
})
test('Go into Text File in Folder', async () => {
await page.click("//div[text()='testtext']")
})
test('Download File Text in Folder', async () => {
await page.click("//span[text()='ดาวน์โหลด']")
await page.waitForTimeout(2000)
})
test('Go back to Folder Level Again', async () => {
await page.click("//i[text()='arrow_back']")
})
test('Delete file Cancel in Folder', async () => {
await page.click(
`//button[@data-testid='action${cabinet}/${drawer}/${folder}/searchtest.txt']`,
)
await page.click("(//div[@role='listitem'])[2]")
await page.click("(//div[@class='q-space']/following-sibling::button)[2]")
await expect(page.locator("(//div[@class='col'])[3]")).toContainText(
'testtext',
)
})
test('Delete file Confirm in Folder', async () => {
await page.click(
`//button[@data-testid='action${cabinet}/${drawer}/${folder}/searchtest.txt']`,
)
await page.click("(//div[@role='listitem'])[2]")
await page.click(
"(//div[contains(@class,'q-card__actions justify-end')]//button)[2]",
)
await expect(page.locator("(//div[@class='col'])[3]")).not.toHaveText(
'testtext',
)
})
test('Edit Subfolder', async () => {
@ -130,9 +210,7 @@ test('Edit Subfolder', async () => {
await page.click("(//div[@role='listitem'])[1]")
await page.fill("(//input[@placeholder='กรอกชื่อ'])[1]", newName)
await page.click("(//button[@id='FoldeSubmit'])[1]")
await expect(
page.locator("(//div[contains(@class,'bg-white rounded-borders')])[2]"),
).toContainText(newName)
await expect(page.locator("(//div[@class='col'])[3]")).toContainText(newName)
})
test('Delete Subfolder Cancel', async () => {
@ -141,9 +219,7 @@ test('Delete Subfolder Cancel', async () => {
)
await page.click("(//div[@role='listitem'])[2]")
await page.click("(//div[@class='q-space']/following-sibling::button)[2]")
await expect(
page.locator("(//div[contains(@class,'bg-white rounded-borders')])[2]"),
).toContainText(newName)
await expect(page.locator("(//div[@class='col'])[3]")).toContainText(newName)
})
test('Delete Subfolder Confirm', async () => {
@ -154,16 +230,12 @@ test('Delete Subfolder Confirm', async () => {
await page.click(
"(//div[contains(@class,'q-card__actions justify-end')]//button)[2]",
)
await expect(
page.locator("(//div[contains(@class,'bg-white rounded-borders')])[2]"),
).not.toHaveText(newName)
await expect(page.locator("(//div[@class='col'])[3]")).not.toHaveText(newName)
})
test('Back to drawer', async () => {
await page.click("//i[text()='arrow_back']")
await expect(
page.locator("(//div[contains(@class,'bg-white rounded-borders')])[2]"),
).toContainText(folder)
await expect(page.locator("(//div[@class='col'])[3]")).toContainText(folder)
})
test('Edit Folder', async () => {
@ -173,9 +245,7 @@ test('Edit Folder', async () => {
await page.click("(//div[@role='listitem'])[1]")
await page.fill("(//input[@placeholder='กรอกชื่อ'])[1]", newName)
await page.click("(//button[@id='FoldeSubmit'])[1]")
await expect(
page.locator("(//div[contains(@class,'bg-white rounded-borders')])[2]"),
).toContainText(newName)
await expect(page.locator("(//div[@class='col'])[3]")).toContainText(newName)
})
test('Delete Folder Cancel', async () => {
@ -184,9 +254,7 @@ test('Delete Folder Cancel', async () => {
)
await page.click("(//div[@role='listitem'])[2]")
await page.click("(//div[@class='q-space']/following-sibling::button)[2]")
await expect(
page.locator("(//div[contains(@class,'bg-white rounded-borders')])[2]"),
).toContainText(newName)
await expect(page.locator("(//div[@class='col'])[3]")).toContainText(newName)
})
test('Delete Folder Confirm', async () => {
@ -197,16 +265,12 @@ test('Delete Folder Confirm', async () => {
await page.click(
"(//div[contains(@class,'q-card__actions justify-end')]//button)[2]",
)
await expect(
page.locator("(//div[contains(@class,'bg-white rounded-borders')])[2]"),
).not.toHaveText(newName)
await expect(page.locator("(//div[@class='col'])[3]")).not.toHaveText(newName)
})
test('Back to Cabinet', async () => {
await page.click("//i[text()='arrow_back']")
await expect(
page.locator("(//div[contains(@class,'bg-white rounded-borders')])[2]"),
).toContainText(drawer)
await expect(page.locator("(//div[@class='col'])[3]")).toContainText(drawer)
})
test('Edit drawer', async () => {
@ -214,18 +278,14 @@ test('Edit drawer', async () => {
await page.click("(//div[@role='listitem'])[1]")
await page.fill("(//input[@placeholder='กรอกชื่อ'])[1]", newName)
await page.click("(//button[@id='FoldeSubmit'])[1]")
await expect(
page.locator("(//div[contains(@class,'bg-white rounded-borders')])[2]"),
).toContainText(newName)
await expect(page.locator("(//div[@class='col'])[3]")).toContainText(newName)
})
test('Delete Drawer Cancel', async () => {
await page.click(`//button[@data-testid='action${cabinet}/${newName}/']`)
await page.click("(//div[@role='listitem'])[2]")
await page.click("(//div[@class='q-space']/following-sibling::button)[2]")
await expect(
page.locator("(//div[contains(@class,'bg-white rounded-borders')])[2]"),
).toContainText(newName)
await expect(page.locator("(//div[@class='col'])[3]")).toContainText(newName)
})
test('Delete Drawer Confirm', async () => {
@ -234,35 +294,30 @@ test('Delete Drawer Confirm', async () => {
await page.click(
"(//div[contains(@class,'q-card__actions justify-end')]//button)[2]",
)
await expect(
page.locator("(//div[contains(@class,'bg-white rounded-borders')])[2]"),
).not.toHaveText(newName)
await expect(page.locator("(//div[@class='col'])[3]")).not.toHaveText(newName)
})
test('Back to Home', async () => {
await page.click("//i[text()='arrow_back']")
await expect(
page.locator("(//div[contains(@class,'bg-white rounded-borders')])[2]"),
).toContainText(cabinet)
await expect(page.locator("(//div[@class='col'])[3]")).toContainText(cabinet)
})
test('Edit Cabinet', async () => {
await page.click(`//button[@data-testid='action${cabinet}/']`)
await page.click("(//div[@role='listitem'])[1]")
await page.fill("(//input[@placeholder='กรอกชื่อ'])[1]", newName)
await page.click("(//button[@id='FoldeSubmit'])[1]")
await expect(
page.locator("(//div[contains(@class,'bg-white rounded-borders')])[2]"),
).toContainText(newName)
console.log(cabinet)
await page.click(`"//button[@data-testid='action${cabinet}/']"`)
//button[@data-testid='actionI0Xc50SHxGe4kYehU4t2B/']
await page.click("(//div[@role='listitem'])[2]")
await page.fill("(//input[@placeholder='กรอกชื่อ'])2]", newName)
await page.click("(//button[@id='FoldeSubmit'])[2]")
await expect(page.locator("(//div[@class='col'])[3]")).toContainText(newName)
})
test('Delete Cabinet Cancel', async () => {
await page.click(`//button[@data-testid='action${newName}/']`)
await page.click("(//div[@role='listitem'])[2]")
await page.click("(//div[@class='q-space']/following-sibling::button)[2]")
await expect(
page.locator("(//div[contains(@class,'bg-white rounded-borders')])[2]"),
).toContainText(newName)
await expect(page.locator("(//div[@class='col'])[3]")).toContainText(newName)
})
test('Delete Cabinet Confirm', async () => {
@ -271,7 +326,5 @@ test('Delete Cabinet Confirm', async () => {
await page.click(
"(//div[contains(@class,'q-card__actions justify-end')]//button)[2]",
)
await expect(
page.locator("(//div[contains(@class,'bg-white rounded-borders')])[2]"),
).not.toHaveText(newName)
await expect(page.locator("(//div[@class='col'])[3]")).not.toHaveText(newName)
})

View file

@ -1,218 +0,0 @@
import {
Body,
Controller,
Delete,
Get,
Path,
Post,
Put,
Route,
Security,
SuccessResponse,
Tags,
Request,
Response,
Example,
} from "tsoa";
import minioClient from "../minio";
import esClient from "../elasticsearch";
import { copyCond, listFolder, listItem, replaceIllegalChars } from "../utils/minio";
import HttpStatusCode from "../interfaces/http-status";
import { StorageFile, StorageFolder } from "../interfaces/storage-fs";
import { getInstance } from "../lib/websocket";
const DEFAULT_BUCKET = process.env.MINIO_BUCKET;
const DEFAULT_INDEX = process.env.ELASTICSEARCH_INDEX;
if (!DEFAULT_BUCKET) throw Error("Default MinIO bucket must be specified.");
if (!DEFAULT_INDEX) throw Error("Default ElasticSearch index must be specified.");
@Route("cabinet")
export class CabinetController extends Controller {
@Get("/")
@Tags("ตู้เอกสาร")
@Security("bearerAuth")
@Response(
HttpStatusCode.INTERNAL_SERVER_ERROR,
"เกิดข้อผิดพลาด ไม่สามารถแสดงรายการตู้เอกสารได้ กรุณาลองใหม่ในภายหลัง",
)
@SuccessResponse(HttpStatusCode.OK, "สำเร็จ")
@Example([
{
path: "ตู้เอกสาร 1/",
name: "ตู้เอกสาร 1",
createdAt: "2021-07-20T12:33:13.018Z",
createdBy: "admin",
},
{
path: "ตู้เอกสาร 2/",
name: "ตู้เอกสาร 2",
createdAt: "2022-01-23T16:05:02.114Z",
createdBy: "admin",
},
])
public async listCabinet(): Promise<StorageFolder[]> {
const list = await listFolder(DEFAULT_BUCKET!).catch((e) =>
console.error(`Error List Folder: ${e}`),
);
if (!list)
throw new Error("เกิดข้อผิดพลาด ไม่สามารถแสดงรายการตู้เอกสารได้ กรุณาลองใหม่ในภายหลัง");
return list;
}
@Post("/")
@Tags("ตู้เอกสาร")
@Security("bearerAuth", ["admin", "management-role"])
@Response(HttpStatusCode.INTERNAL_SERVER_ERROR, "เกิดข้อผิดพลาดกับระบบจัดการไฟล์")
@SuccessResponse(HttpStatusCode.CREATED, "สำเร็จ")
public async createCabinet(
@Request() request: { user: { preferred_username: string } },
@Body()
body: {
/**
* @example "ตู้เอกสาร 1"
*/
name: string;
},
) {
const meta = {
createdAt: new Date().toISOString(),
createdBy: request.user.preferred_username,
};
const created = await minioClient
.putObject(DEFAULT_BUCKET!, `${replaceIllegalChars(body.name)}/.keep`, "", 0, meta)
.catch((e) => console.error(e));
if (!created) throw new Error("เกิดข้อผิดพลาดกับระบบจัดการไฟล์");
const io = getInstance();
io?.emit("CreateFolder", {
pathname: `${replaceIllegalChars(body.name)}/`,
name: replaceIllegalChars(body.name),
...meta,
});
return this.setStatus(HttpStatusCode.CREATED);
}
/**
* @example cabinetName "ตู้เอกสาร 1"
*/
@Put("/{cabinetName}")
@Tags("ตู้เอกสาร")
@Security("bearerAuth", ["admin", "management-role"])
@Response(HttpStatusCode.INTERNAL_SERVER_ERROR, "เกิดข้อผิดพลาดไม่สามารถย้ายไฟล์ได้")
@SuccessResponse(HttpStatusCode.NO_CONTENT, "สำเร็จ")
public async editCabinet(
@Path() cabinetName: string,
@Body()
body: {
/**
* @example "ตู้เอกสารใหม่"
*/
name: string;
},
): Promise<void> {
const path = `${cabinetName}/`;
const list = await listItem(DEFAULT_BUCKET!, path, true);
await Promise.all(
list.map(async (current) => {
if (!current.name) return;
const base = `${replaceIllegalChars(body.name)}/`;
const destination = `${base}${current.name.slice(path.length)}`;
const source = `/${DEFAULT_BUCKET}/${current.name}`;
return await minioClient
.copyObject(DEFAULT_BUCKET!, destination, source, copyCond)
.then(async () => {
if (current.name.includes(".keep")) {
return await minioClient.removeObject(DEFAULT_BUCKET!, current.name);
}
const search = await esClient.search<
StorageFile & { attachment: Record<string, string> }
>({
index: DEFAULT_INDEX!,
query: { match: { pathname: current.name } },
});
if (search && search.hits.hits.length === 0) throw new Error("ไม่พบข้อมูลในฐานข้อมูล");
const data = search.hits.hits[0];
await esClient.update({
index: DEFAULT_INDEX!,
id: data._id,
doc: {
pathname: destination,
path: destination.split("/").slice(0, -1).join("/") + "/",
},
refresh: "wait_for",
});
await minioClient.removeObject(DEFAULT_BUCKET!, current.name);
})
.catch((e) => {
console.error(e);
throw new Error("เกิดข้อผิดพลาด ไม่สามารถย้ายไฟล์ได้");
});
}),
);
const io = getInstance();
io?.emit("EditFolder", { from: `${cabinetName}/`, to: `${replaceIllegalChars(body.name)}/` });
return this.setStatus(HttpStatusCode.NO_CONTENT);
}
/**
* @example cabinetName "ตู้เอกสาร 1"
*/
@Delete("/{cabinetName}")
@Tags("ตู้เอกสาร")
@Security("bearerAuth", ["admin", "management-role"])
@Response(HttpStatusCode.INTERNAL_SERVER_ERROR, "เกิดข้อผิดพลาด ไม่สามารถลบไฟล์ได้")
@SuccessResponse(HttpStatusCode.NO_CONTENT, "สำเร็จ")
public async deleteCabinet(@Path() cabinetName: string) {
await new Promise<void>((resolve, reject) => {
const objects: string[] = [];
const stream = minioClient.listObjectsV2(DEFAULT_BUCKET!, `${cabinetName}/`, true);
stream.on("data", (v) => {
if (v && v.name) objects.push(v.name);
});
stream.on("close", async () =>
resolve(await minioClient.removeObjects(DEFAULT_BUCKET!, objects)),
);
stream.on("error", () => reject(new Error("เกิดข้อผิดพลาด ไม่สามารถลบไฟล์ได้")));
});
const io = getInstance();
io?.emit("DeleteFolder", { pathname: `${cabinetName}/` });
return this.setStatus(HttpStatusCode.NO_CONTENT);
}
/**
* @example cabinetName "ตู้เอกสาร 1"
*/
@Get("/{cabinetName}/size")
@Tags("ตู้เอกสาร")
@Security("bearerAuth")
@SuccessResponse(HttpStatusCode.OK, "สำเร็จ")
public async calc(@Path() cabinetName: string) {
const list = await listItem(DEFAULT_BUCKET!, `${cabinetName}/`, true).catch((e) =>
console.error(`Error List Folder: ${e}`),
);
if (!list) throw new Error("เกิดข้อผิดพลาด ไม่สามารถแสดงรายการแฟ้มได้ กรุณาลองใหม่ในภายหลัง");
return { size: list.reduce<number>((a, c) => a + c.size, 0) };
}
}

View file

@ -1,245 +0,0 @@
import {
Body,
Controller,
Delete,
Get,
Path,
Post,
Put,
Route,
Security,
SuccessResponse,
Tags,
Request,
Response,
Example,
} from "tsoa";
import minioClient from "../minio";
import esClient from "../elasticsearch";
import { copyCond, listFolder, listItem, pathExist, replaceIllegalChars } from "../utils/minio";
import HttpStatusCode from "../interfaces/http-status";
import { StorageFile, StorageFolder } from "../interfaces/storage-fs";
import HttpError from "../interfaces/http-error";
import { getInstance } from "../lib/websocket";
const DEFAULT_BUCKET = process.env.MINIO_BUCKET;
const DEFAULT_INDEX = process.env.ELASTICSEARCH_INDEX;
if (!DEFAULT_BUCKET) throw Error("Default MinIO bucket must be specified.");
if (!DEFAULT_INDEX) throw Error("Default ElasticSearch index must be specified.");
@Route("/cabinet/{cabinetName}/drawer")
export class DrawerController extends Controller {
/**
* @example cabinetName "ตู้เอกสาร 1"
*/
@Get("/")
@Tags("ลิ้นชัก")
@Security("bearerAuth")
@Response(
HttpStatusCode.INTERNAL_SERVER_ERROR,
"เกิดข้อผิดพลาด ไม่สามารถแสดงรายการลิ้นชักได้ กรุณาลองใหม่ในภายหลัง",
)
@SuccessResponse(HttpStatusCode.OK, "สำเร็จ")
@Example([
{
path: "ตู้เอกสาร 1/ลิ้นชัก 1/",
name: "ลิ้นชัก 1",
createdAt: "2021-07-20T12:33:13.018Z",
createdBy: "admin",
},
{
path: "ตู้เอกสาร 1/ลิ้นชัก 2/",
name: "ลิ้นชัก 2",
createdAt: "2022-01-23T16:05:02.114Z",
createdBy: "admin",
},
])
public async listDrawer(@Path() cabinetName: string): Promise<StorageFolder[]> {
const list = await listFolder(DEFAULT_BUCKET!, `${cabinetName}/`).catch((e) =>
console.error(`Error List Folder: ${e}`),
);
if (!list)
throw new Error("เกิดข้อผิดพลาด ไม่สามารถแสดงรายการลิ้นชักได้ กรุณาลองใหม่ในภายหลัง");
return list;
}
/**
* @example cabinetName "ตู้เอกสาร 1"
*/
@Post("/")
@Tags("ลิ้นชัก")
@Security("bearerAuth", ["admin", "management-role"])
@Response(HttpStatusCode.NOT_FOUND, "ไม่พบลิ้นชัก")
@Response(HttpStatusCode.INTERNAL_SERVER_ERROR, "เกิดข้อผิดพลาดกับระบบจัดการไฟล์")
@SuccessResponse(HttpStatusCode.CREATED, "สำเร็จ")
public async createDrawer(
@Request() request: { user: { preferred_username: string } },
@Path() cabinetName: string,
@Body()
body: {
/**
* @example "ลิ้นชัก 1"
*/
name: string;
},
) {
const basePath = `${cabinetName}/`;
if (!(await pathExist(DEFAULT_BUCKET!, basePath))) {
throw new HttpError(HttpStatusCode.NOT_FOUND, "ไม่พบตำแหน่งที่ต้องการสร้างลิ้นชัก");
}
const meta = {
createdAt: new Date().toISOString(),
createdBy: request.user.preferred_username,
};
const created = await minioClient
.putObject(DEFAULT_BUCKET!, `${basePath}${replaceIllegalChars(body.name)}/.keep`, "", 0, meta)
.catch((e) => console.error(e));
if (!created) throw new Error("เกิดข้อผิดพลาดกับระบบจัดการไฟล์");
const io = getInstance();
io?.emit("CreateFolder", {
pathname: `${basePath}${replaceIllegalChars(body.name)}/`,
name: replaceIllegalChars(body.name),
...meta,
});
return this.setStatus(HttpStatusCode.CREATED);
}
/**
* @example cabinetName "ตู้เอกสาร 1"
* @example drawerName "ลิ้นชัก 1"
*/
@Put("/{drawerName}")
@Tags("ลิ้นชัก")
@Security("bearerAuth", ["admin", "management-role"])
@Response(HttpStatusCode.INTERNAL_SERVER_ERROR, "เกิดข้อผิดพลาดไม่สามารถย้ายไฟล์ได้")
@SuccessResponse(HttpStatusCode.NO_CONTENT, "สำเร็จ")
public async editDrawer(
@Path() cabinetName: string,
@Path() drawerName: string,
@Body()
body: {
/**
* @example "ลิ้นชักใหม่"
*/
name: string;
},
): Promise<void> {
const path = `${cabinetName}/${drawerName}/`;
const list = await listItem(DEFAULT_BUCKET!, path, true);
await Promise.all(
list.map(async (current) => {
if (!current.name) return;
const base = `${cabinetName}/${replaceIllegalChars(body.name)}/`;
const destination = `${base}${current.name.slice(path.length)}`;
const source = `/${DEFAULT_BUCKET}/${current.name}`;
return await minioClient
.copyObject(DEFAULT_BUCKET!, destination, source, copyCond)
.then(async () => {
if (current.name.includes(".keep")) {
return await minioClient.removeObject(DEFAULT_BUCKET!, current.name);
}
const search = await esClient.search<
StorageFile & { attachment: Record<string, string> }
>({
index: DEFAULT_INDEX!,
query: { match: { pathname: current.name } },
});
if (search && search.hits.hits.length === 0) throw new Error("ไม่พบข้อมูลในฐานข้อมูล");
const data = search.hits.hits[0];
await esClient.update({
index: DEFAULT_INDEX!,
id: data._id,
doc: {
pathname: destination,
path: destination.split("/").slice(0, -1).join("/") + "/",
},
refresh: "wait_for",
});
await minioClient.removeObject(DEFAULT_BUCKET!, current.name);
})
.catch((e) => {
console.error(e);
throw new Error("เกิดข้อผิดพลาด ไม่สามารถย้ายไฟล์ได้");
});
}),
);
const io = getInstance();
io?.emit("EditFolder", {
from: `${cabinetName}/${drawerName}/`,
to: `${cabinetName}/${replaceIllegalChars(body.name)}/`,
});
return this.setStatus(HttpStatusCode.NO_CONTENT);
}
/**
* @example cabinetName "ตู้เอกสาร 1"
* @example drawerName "ลิ้นชัก 1"
*/
@Delete("/{drawerName}")
@Tags("ลิ้นชัก")
@Security("bearerAuth", ["admin", "management-role"])
@SuccessResponse(HttpStatusCode.NO_CONTENT, "สำเร็จ")
public async deleteDrawer(@Path() cabinetName: string, @Path() drawerName: string) {
await new Promise<void>((resolve, reject) => {
const objects: string[] = [];
const stream = minioClient.listObjectsV2(
DEFAULT_BUCKET!,
`${cabinetName}/${drawerName}/`,
true,
);
stream.on("data", (v) => {
if (v && v.name) objects.push(v.name);
});
stream.on("close", async () =>
resolve(await minioClient.removeObjects(DEFAULT_BUCKET!, objects)),
);
stream.on("error", () => reject(new Error("เกิดข้อผิดพลาด ไม่สามารถลบไฟล์ได้")));
});
const io = getInstance();
io?.emit("DeleteFolder", {
pathname: `${cabinetName}/${drawerName}/`,
});
return this.setStatus(HttpStatusCode.NO_CONTENT);
}
/**
* @example cabinetName "ตู้เอกสาร 1"
* @example drawerName "ลิ้นชัก 1"
*/
@Get("/{drawerName}/size")
@Tags("ลิ้นชัก")
@Security("bearerAuth")
@SuccessResponse(HttpStatusCode.OK, "สำเร็จ")
public async calc(@Path() cabinetName: string, @Path() drawerName: string) {
const list = await listItem(DEFAULT_BUCKET!, `${cabinetName}/${drawerName}/`, true).catch((e) =>
console.error(`Error List Folder: ${e}`),
);
if (!list) throw new Error("เกิดข้อผิดพลาด ไม่สามารถแสดงรายการแฟ้มได้ กรุณาลองใหม่ในภายหลัง");
return { size: list.reduce<number>((a, c) => a + c.size, 0) };
}
}

View file

@ -1,438 +0,0 @@
import {
Body,
Controller,
Delete,
Example,
Get,
Patch,
Path,
Post,
Request,
Response,
Route,
Security,
SuccessResponse,
Tags,
} from "tsoa";
import esClient from "../elasticsearch";
import minioClient from "../minio";
import HttpStatusCode from "../interfaces/http-status";
import { StorageFile } from "../interfaces/storage-fs";
import HttpError from "../interfaces/http-error";
import { copyCond, pathExist, replaceIllegalChars } from "../utils/minio";
import { getInstance } from "../lib/websocket";
const DEFAULT_BUCKET = process.env.MINIO_BUCKET;
const DEFAULT_INDEX = process.env.ELASTICSEARCH_INDEX;
if (!DEFAULT_BUCKET) throw Error("Default MinIO bucket must be specified.");
if (!DEFAULT_INDEX) throw Error("Default ElasticSearch index must be specified.");
@Route("/cabinet/{cabinetName}/drawer/{drawerName}/folder/{folderName}/file")
export class FileController extends Controller {
/**
* @example cabinetName "ตู้เอกสาร 1"
* @example drawerName "ลิ้นชัก 1"
* @example folderName "แฟ้ม 1"
*/
@Get("/")
@Tags("ไฟล์")
@Security("bearerAuth")
@SuccessResponse(HttpStatusCode.OK, "สำเร็จ")
@Example([
{
pathname: "ตู้เอกสาร 1/ลิ้นชัก 1/แฟ้ม 1/เอกสาร 1",
path: "ตู้เอกสาร 1/ลิ้นชัก 1/แฟ้ม 1/",
title: "เอกสาร",
description: "เอกสารการเงิน",
category: ["บัญชี"],
keyword: ["เงิน", "บัญชี", "รายจ่าย", "รายรับ"],
upload: false,
fileName: "เอกสาร 1",
fileSize: 10240,
fileType: "application/pdf",
createdAt: "2021-07-20T12:33:13.018Z",
createdBy: "admin",
updatedAt: "2021-07-20T12:33:13.018Z",
updatedBy: "admin",
},
])
public async getFile(
@Path() cabinetName: string,
@Path() drawerName: string,
@Path() folderName: string,
): Promise<StorageFile[]> {
const search = await esClient.search<StorageFile & { attachment: Record<string, string> }>({
index: DEFAULT_INDEX!,
query: {
match: {
path: `${cabinetName}/${drawerName}/${folderName}/`,
},
},
size: 10000,
});
const records = search.hits.hits
.map((v) => {
if (v._source) {
const { attachment, ...rest } = v._source;
return rest satisfies StorageFile;
}
})
.filter((v: StorageFile | undefined): v is StorageFile => !!v);
return records;
}
/**
* @example cabinetName "ตู้เอกสาร 1"
* @example drawerName "ลิ้นชัก 1"
* @example folderName "แฟ้ม 1"
*/
@Post("/")
@Tags("ไฟล์")
@Security("bearerAuth", ["admin", "management-role"])
@Response(
HttpStatusCode.NOT_FOUND,
"ตำแหน่งที่ระบุไม่พบ กรุณาเตรียมตำแหน่งที่ต้องการก่อนดำเนินการ",
)
@SuccessResponse(HttpStatusCode.CREATED, "สำเร็จ")
@Example({
pathname: "ตู้เอกสาร 1/ลิ้นชัก 1/แฟ้ม 1/เอกสาร 1",
path: "ตู้เอกสาร 1/ลิ้นชัก 1/แฟ้ม 1/",
title: "เอกสาร",
description: "เอกสารการเงิน",
category: ["บัญชี"],
keyword: ["เงิน", "บัญชี", "รายจ่าย", "รายรับ"],
upload: false,
fileName: "เอกสาร 1",
fileSize: 10240,
fileType: "application/pdf",
createdAt: "2021-07-20T12:33:13.018Z",
createdBy: "admin",
updatedAt: "2021-07-20T12:33:13.018Z",
updatedBy: "admin",
})
public async uploadFile(
@Request() request: { user: { preferred_username: string } },
@Body()
body: {
/**
* @example "เอกสาร 1"
*/
file: string;
/**
* @example "เอกสาร"
*/
title?: string;
/**
* @example "เอกสารการเงิน"
*/
description?: string;
/**
* @example ["บัญชี"]
*/
category?: string[];
/**
* @example ["เงิน", "บัญชี", "รายจ่าย", "รายรับ"]
*/
keyword?: string[];
},
@Path() cabinetName: string,
@Path() drawerName: string,
@Path() folderName: string,
) {
if (body.file.length > 85) {
throw new HttpError(HttpStatusCode.BAD_REQUEST, "ชื่อไฟล์ยาวเกินกำหนด");
}
const basePath = `${cabinetName}/${drawerName}/${folderName}/`;
const pathname = `${basePath}${replaceIllegalChars(body.file)}`;
if (!(await pathExist(DEFAULT_BUCKET!, basePath))) {
throw new HttpError(
HttpStatusCode.NOT_FOUND,
"ตำแหน่งที่ระบุไม่พบ กรุณาเตรียมตำแหน่งที่ต้องการก่อนดำเนินการ",
);
}
const result = await esClient
.search<StorageFile & { attachment?: Record<string, unknown> }>({
index: DEFAULT_INDEX!,
query: { match: { pathname } },
})
.catch((e) => console.error(e));
// pathname is unique and should not have multiple record with same path
if (result && result.hits.hits.length > 0 && result.hits.hits[0]._source) {
await esClient
.delete({
index: DEFAULT_INDEX!,
id: result.hits.hits[0]._id,
})
.catch((e) => console.error(e));
}
const rec = result && result.hits.hits.length > 0 ? result.hits.hits[0]._source : false;
const metadata: Partial<StorageFile> = {
pathname,
path: basePath,
fileName: replaceIllegalChars(body.file),
fileSize: 0,
fileType: "",
title: body.title ?? "",
description: body.description ?? "",
category: body.category ?? [],
keyword: body.keyword ?? [],
upload: false,
createdAt: new Date().toISOString(),
createdBy: rec ? rec.createdBy : "n/a",
updatedAt: new Date().toISOString(),
updatedBy: request.user.preferred_username ?? "n/a",
};
await esClient.index({
index: DEFAULT_INDEX!,
document: metadata,
refresh: "wait_for",
});
const io = getInstance();
io?.emit("FileUploadRequest", metadata);
return {
...body,
createdAt: metadata.createdAt,
createdBy: metadata.createdBy,
updatedAt: metadata.updatedAt,
updatedBy: metadata.updatedBy,
upload: await minioClient.presignedPutObject(DEFAULT_BUCKET!, pathname),
};
}
/**
* @example cabinetName "ตู้เอกสาร 1"
* @example drawerName "ลิ้นชัก 1"
* @example folderName "แฟ้ม 1"
* @example fileName "เอกสาร 1"
*/
@Patch("/{fileName}")
@Tags("ไฟล์")
@Security("bearerAuth", ["admin", "management-role"])
@Response(HttpStatusCode.NOT_FOUND, "ไม่พบตำแหน่งที่ต้องการสร้างแฟ้ม")
@Response(HttpStatusCode.NO_CONTENT, "สำเร็จ")
@SuccessResponse(HttpStatusCode.OK, "สำเร็จ")
public async updateFile(
@Request() request: { user: { preferred_username: string } },
@Path() cabinetName: string,
@Path() drawerName: string,
@Path() folderName: string,
@Path() fileName: string,
@Body()
body: {
/**
* @example "เอกสารใหม่"
*/
file?: string;
/**
* @example "เอกสารการเงิน"
*/
title?: string;
/**
* @example "เอกสารการเงินฉบับใหม่"
*/
description?: string;
/**
* @example ["บัญชี"]
*/
category?: string[];
/**
* @example ["เงิน", "บัญชี", "รายจ่าย", "รายรับ"]
*/
keyword?: string[];
},
): Promise<void | { upload: string }> {
if (body.file && body.file.length > 85) {
throw new HttpError(HttpStatusCode.BAD_REQUEST, "ชื่อไฟล์ยาวเกินกำหนด");
}
const basePath = `${cabinetName}/${drawerName}/${folderName}/`;
const pathname = `${basePath}${fileName}`;
if (
!Boolean(
await minioClient.statObject(DEFAULT_BUCKET!, `${pathname}`).catch((e) => {
if (e.code === "NotFound") return false;
throw new Error("เกิดข้อผิดพลาดกับระบบจัดการไฟล์");
}),
)
) {
throw new HttpError(HttpStatusCode.NOT_FOUND, "ไม่พบไฟล์");
}
const { file, ...metadata } = body;
// assume user will probably replace file by re-upload but maybe just rename
if (file) {
const destination = `${basePath}${replaceIllegalChars(file)}`;
const source = `/${DEFAULT_BUCKET}/${basePath}${fileName}`;
const copy = await minioClient.copyObject(DEFAULT_BUCKET!, destination, source, copyCond);
if (copy) {
const search = await esClient
.search<StorageFile & { attachment?: Record<string, unknown> }>({
index: DEFAULT_INDEX!,
query: { match: { pathname } },
})
.catch((e) => console.error(e));
if (search && search.hits.hits.length > 0 && search.hits.hits[0]._source) {
const { _index: index, _id: id } = search.hits.hits[0];
const meta = {
updatedAt: new Date().toISOString(),
updatedBy: request.user.preferred_username ?? "n/a",
};
await esClient
.update({
index,
id,
doc: {
...metadata,
pathname: destination,
path: basePath,
fileName: replaceIllegalChars(file),
...meta,
},
refresh: "wait_for",
})
.then(() => minioClient.removeObject(DEFAULT_BUCKET!, pathname));
const io = getInstance();
io?.emit("FileUpdateMove", {
from: search.hits.hits[0]._source,
to: {
...search.hits.hits[0]._source,
...metadata,
pathname: destination,
path: basePath,
fileName: replaceIllegalChars(file),
...meta,
},
});
} else {
await minioClient.removeObject(DEFAULT_BUCKET!, pathname);
}
}
} else {
const search = await esClient
.search<StorageFile & { attachment?: Record<string, unknown> }>({
index: DEFAULT_INDEX!,
query: { match: { pathname } },
})
.catch((e) => console.error(e));
if (search && search.hits.hits.length > 0 && search.hits.hits[0]._source) {
const { _index: index, _id: id } = search.hits.hits[0];
const meta = {
updatedAt: new Date().toISOString(),
updatedBy: request.user.preferred_username ?? "n/a",
};
await esClient.update({
index,
id,
doc: { ...metadata, ...meta },
refresh: "wait_for",
});
const updated: StorageFile = {
...search.hits.hits[0]._source,
...metadata,
...meta,
};
const io = getInstance();
io?.emit("FileUpdate", updated);
}
}
return body.file
? {
upload: await minioClient.presignedPutObject(
DEFAULT_BUCKET!,
`${basePath}${replaceIllegalChars(body.file) ?? fileName}`,
),
}
: this.setStatus(HttpStatusCode.NO_CONTENT);
}
/**
* @example cabinetName "ตู้เอกสาร 1"
* @example drawerName "ลิ้นชัก 1"
* @example folderName "แฟ้ม 1"
* @example fileName "เอกสารใหม่"
*/
@Delete("/{fileName}")
@Tags("ไฟล์")
@Security("bearerAuth", ["admin", "management-role"])
@SuccessResponse(HttpStatusCode.OK, "สำเร็จ")
public async deleteFile(
@Path() cabinetName: string,
@Path() drawerName: string,
@Path() folderName: string,
@Path() fileName: string,
) {
await minioClient.removeObject(
DEFAULT_BUCKET!,
`${cabinetName}/${drawerName}/${folderName}/${fileName}`,
);
await esClient.deleteByQuery({
index: DEFAULT_INDEX!,
query: {
match: { pathname: `${cabinetName}/${drawerName}/${folderName}/${fileName}` },
},
});
return this.setStatus(HttpStatusCode.NO_CONTENT);
}
/**
* @example cabinetName "ตู้เอกสาร 1"
* @example drawerName "ลิ้นชัก 1"
* @example folderName "แฟ้ม 1"
* @example fileName "เอกสารใหม่"
*/
@Get("/{fileName}")
@Tags("ดาวน์โหลด")
@Security("bearerAuth")
@SuccessResponse(HttpStatusCode.OK, "สำเร็จ")
public async downloadFile(
@Path() cabinetName: string,
@Path() drawerName: string,
@Path() folderName: string,
@Path() fileName: string,
) {
const search = await esClient.search<StorageFile & { attachment: Record<string, string> }>({
index: DEFAULT_INDEX!,
query: {
match: { pathname: `${cabinetName}/${drawerName}/${folderName}/${fileName}` },
},
});
if (search && search.hits.hits.length === 0) {
throw new HttpError(HttpStatusCode.NOT_FOUND, "Not found");
}
const { attachment, ...rest } = search.hits.hits[0]._source!;
return {
...rest,
download: await minioClient.presignedGetObject(
DEFAULT_BUCKET!,
`${cabinetName}/${drawerName}/${folderName}/${fileName}`,
),
};
}
}

View file

@ -1,262 +0,0 @@
import {
Body,
Controller,
Delete,
Get,
Path,
Post,
Put,
Route,
Security,
SuccessResponse,
Tags,
Request,
Response,
Example,
} from "tsoa";
import minioClient from "../minio";
import esClient from "../elasticsearch";
import { copyCond, listFolder, listItem, pathExist, replaceIllegalChars } from "../utils/minio";
import HttpStatusCode from "../interfaces/http-status";
import { StorageFile, StorageFolder } from "../interfaces/storage-fs";
import HttpError from "../interfaces/http-error";
import { getInstance } from "../lib/websocket";
const DEFAULT_BUCKET = process.env.MINIO_BUCKET;
const DEFAULT_INDEX = process.env.ELASTICSEARCH_INDEX;
if (!DEFAULT_BUCKET) throw Error("Default MinIO bucket must be specified.");
if (!DEFAULT_INDEX) throw Error("Default ElasticSearch index must be specified.");
@Route("/cabinet/{cabinetName}/drawer/{drawerName}/folder")
export class FolderController extends Controller {
/**
* @example cabinetName "ตู้เอกสาร 1"
* @example drawerName "ลิ้นชัก 1"
*/
@Get("/")
@Tags("แฟ้ม")
@Security("bearerAuth")
@Response(
HttpStatusCode.INTERNAL_SERVER_ERROR,
"เกิดข้อผิดพลาด ไม่สามารถแสดงรายการแฟ้มได้ กรุณาลองใหม่ในภายหลัง",
)
@SuccessResponse(HttpStatusCode.OK, "สำเร็จ")
@Example([
{
path: "ตู้เอกสาร 1/ลิ้นชัก 1/แฟ้ม 1",
name: "แฟ้ม 1",
createdAt: "2021-07-20T12:33:13.018Z",
createdBy: "admin",
},
{
path: "ตู้เอกสาร 1/ลิ้นชัก 1/แฟ้ม 2",
name: "แฟ้ม 2",
createdAt: "2022-01-23T16:05:02.114Z",
createdBy: "admin",
},
])
public async listFolder(
@Path() cabinetName: string,
@Path() drawerName: string,
): Promise<StorageFolder[]> {
const list = await listFolder(DEFAULT_BUCKET!, `${cabinetName}/${drawerName}`).catch((e) =>
console.error(`Error List Folder: ${e}`),
);
if (!list) throw new Error("เกิดข้อผิดพลาด ไม่สามารถแสดงรายการแฟ้มได้ กรุณาลองใหม่ในภายหลัง");
return list;
}
/**
* @example cabinetName "ตู้เอกสาร 1"
* @example drawerName "ลิ้นชัก 1"
*/
@Post("/")
@Tags("แฟ้ม")
@Security("bearerAuth", ["admin", "management-role"])
@Response(HttpStatusCode.NOT_FOUND, "ไม่พบตำแหน่งที่ต้องการสร้างแฟ้ม")
@Response(HttpStatusCode.INTERNAL_SERVER_ERROR, "เกิดข้อผิดพลาดกับระบบจัดการไฟล์")
@SuccessResponse(HttpStatusCode.CREATED, "สำเร็จ")
public async createFolder(
@Request() request: { user: { preferred_username: string } },
@Body()
body: {
/**
* @example "แฟ้ม 1"
*/
name: string;
},
@Path() cabinetName: string,
@Path() drawerName: string,
) {
const basePath = `${cabinetName}/${drawerName}/`;
if (!(await pathExist(DEFAULT_BUCKET!, basePath))) {
throw new HttpError(HttpStatusCode.NOT_FOUND, "ไม่พบตำแหน่งที่ต้องการสร้างลิ้นชัก");
}
const meta = {
createdAt: new Date().toISOString(),
createdBy: request.user.preferred_username,
};
const created = await minioClient
.putObject(DEFAULT_BUCKET!, `${basePath}${replaceIllegalChars(body.name)}/.keep`, "", 0, meta)
.catch((e) => console.error(e));
if (!created) throw new Error("เกิดข้อผิดพลาดกับระบบจัดการไฟล์");
const io = getInstance();
io?.emit("CreateFolder", {
pathname: `${basePath}${replaceIllegalChars(body.name)}/`,
name: replaceIllegalChars(body.name),
...meta,
});
return this.setStatus(HttpStatusCode.CREATED);
}
/**
* @example cabinetName "ตู้เอกสาร 1"
* @example drawerName "ลิ้นชัก 1"
* @example folderName "แฟ้ม 1"
*/
@Put("/{folderName}")
@Tags("แฟ้ม")
@Security("bearerAuth", ["admin", "management-role"])
@Response(HttpStatusCode.INTERNAL_SERVER_ERROR, "เกิดข้อผิดพลาดไม่สามารถย้ายไฟล์ได้")
@SuccessResponse(HttpStatusCode.NO_CONTENT, "สำเร็จ")
public async editFolder(
@Body()
body: {
/**
* @example "แฟ้มใหม่"
*/
name: string;
},
@Path() cabinetName: string,
@Path() drawerName: string,
@Path() folderName: string,
) {
const path = `${cabinetName}/${drawerName}/${folderName}/`;
const list = await listItem(DEFAULT_BUCKET!, path, true);
await Promise.all(
list.map(async (current) => {
if (!current.name) return;
const base = `${cabinetName}/${drawerName}/${replaceIllegalChars(body.name)}/`;
const destination = `${base}${current.name.slice(path.length)}`;
const source = `/${DEFAULT_BUCKET}/${current.name}`;
return await minioClient
.copyObject(DEFAULT_BUCKET!, destination, source, copyCond)
.then(async () => {
if (current.name.includes(".keep")) {
return await minioClient.removeObject(DEFAULT_BUCKET!, current.name);
}
const search = await esClient.search<
StorageFile & { attachment: Record<string, string> }
>({
index: DEFAULT_INDEX!,
query: { match: { pathname: current.name } },
});
if (search && search.hits.hits.length === 0) throw new Error("ไม่พบข้อมูลในฐานข้อมูล");
const data = search.hits.hits[0];
await esClient.update({
index: DEFAULT_INDEX!,
id: data._id,
doc: {
pathname: destination,
path: destination.split("/").slice(0, -1).join("/") + "/",
},
refresh: "wait_for",
});
await minioClient.removeObject(DEFAULT_BUCKET!, current.name);
})
.catch((e) => {
console.error(e);
throw new Error("เกิดข้อผิดพลาด ไม่สามารถย้ายไฟล์ได้");
});
}),
);
const io = getInstance();
io?.emit("EditFolder", {
from: `${cabinetName}/${drawerName}/${folderName}/`,
to: `${cabinetName}/${drawerName}/${replaceIllegalChars(body.name)}/`,
});
return this.setStatus(HttpStatusCode.NO_CONTENT);
}
/**
* @example cabinetName "ตู้เอกสาร 1"
* @example drawerName "ลิ้นชัก 1"
* @example folderName "แฟ้ม 1"
*/
@Delete("/{folderName}")
@Tags("แฟ้ม")
@Security("bearerAuth", ["admin", "management-role"])
@SuccessResponse(HttpStatusCode.NO_CONTENT, "สำเร็จ")
public async deleteFolder(
@Path() cabinetName: string,
@Path() drawerName: string,
@Path() folderName: string,
) {
await new Promise<void>((resolve, reject) => {
const objects: string[] = [];
const stream = minioClient.listObjectsV2(
DEFAULT_BUCKET!,
`${cabinetName}/${drawerName}/${folderName}`,
true,
);
stream.on("data", (v) => {
if (v && v.name) objects.push(v.name);
});
stream.on("close", async () =>
resolve(await minioClient.removeObjects(DEFAULT_BUCKET!, objects)),
);
stream.on("error", () => reject(new Error("เกิดข้อผิดพลาด ไม่สามารถลบไฟล์ได้")));
});
const io = getInstance();
io?.emit("DeleteFolder", { pathname: `${cabinetName}/${drawerName}/${folderName}/` });
return this.setStatus(HttpStatusCode.NO_CONTENT);
}
/**
* @example cabinetName "ตู้เอกสาร 1"
* @example drawerName "ลิ้นชัก 1"
* @example folderName "แฟ้ม 1"
*/
@Get("/{folderName}/size")
@Tags("แฟ้ม")
@Security("bearerAuth")
@SuccessResponse(HttpStatusCode.OK, "สำเร็จ")
public async calc(
@Path() cabinetName: string,
@Path() drawerName: string,
@Path() folderName: string,
) {
const list = await listItem(
DEFAULT_BUCKET!,
`${cabinetName}/${drawerName}/${folderName}/`,
true,
).catch((e) => console.error(`Error List Folder: ${e}`));
if (!list) throw new Error("เกิดข้อผิดพลาด ไม่สามารถแสดงรายการแฟ้มได้ กรุณาลองใหม่ในภายหลัง");
return { size: list.reduce<number>((a, c) => a + c.size, 0) };
}
}

View file

@ -11,7 +11,7 @@ if (!DEFAULT_INDEX) throw Error("Default ElasticSearch index must be specified."
@Route("/search")
export class SearchController extends Controller {
@Post("/")
@Tags("ค้นหา")
@Tags("Search")
@Security("bearerAuth")
@SuccessResponse(HttpStatusCode.OK, "สำเร็จ")
public async searchFile(@Body() search: Search): Promise<StorageFile[]> {

View file

@ -6,6 +6,7 @@ import {
Post,
Put,
Request,
Response,
Route,
Security,
SuccessResponse,
@ -114,6 +115,10 @@ interface DownloadFileBody {
file: string;
}
function stripLeadingSlash(str: string) {
return str.replace(/^\//, "");
}
async function folderSize(path: string[]) {
const size = await new Promise<number>((resolve, reject) => {
const stream = minioClient.listObjectsV2(
@ -138,7 +143,7 @@ async function listFolder(path: string[], hidden: boolean = false) {
const stream = minioClient.listObjectsV2(
DEFAULT_BUCKET,
path.length === 0 ? "" : path.join("/") + "/",
stripLeadingSlash(`${path.join("/")}/`),
);
stream.on("data", (v) => {
if (v && v.prefix)
@ -182,7 +187,7 @@ async function listFile(path: string[], hidden: boolean = false) {
sort: [{ pathname: "asc" }],
query: {
bool: {
must: { match: { path: path.join("/") + "/" } },
must: { match: { path: stripLeadingSlash(`${path.join("/")}/`) } },
must_not: !hidden ? { match: { hidden: true } } : undefined,
},
},
@ -202,14 +207,14 @@ async function listFile(path: string[], hidden: boolean = false) {
return records;
}
async function checkPathExist(bucket: string, path: string) {
if (path.split("/").filter(Boolean).length === 0) return true; // root does not contain any mark
return await checkFileExist(bucket, `${path}/.keep`);
async function checkPathExist(bucket: string, path: string[]) {
if (path.filter(Boolean).length === 0) return true; // root does not contain any mark
return await checkFileExist(bucket, `${path.filter(Boolean).join("/")}/.keep`);
}
async function checkFileExist(bucket: string, pathname: string) {
return Boolean(
await minioClient.statObject(bucket, pathname).catch((e) => {
await minioClient.statObject(bucket, stripLeadingSlash(pathname)).catch((e) => {
if (e.code === "NotFound") return false;
console.error(`Storage Error: ${e}`);
throw new Error(MINIO_ERROR_MESSAGE);
@ -219,16 +224,21 @@ async function checkFileExist(bucket: string, pathname: string) {
@Route("storage")
export class StorageController extends Controller {
/**
* @summary
*/
@Post("list")
@Tags("Storage Folder", "Storage File")
@Security("bearerAuth")
@Example([
{
path: "ตู้เอกสาร 1/ลิ้นชัก 1/แฟ้ม 1",
path: "ตู้เอกสาร 1/ลิ้นชัก 1/แฟ้ม 1/",
name: "แฟ้ม 1",
createdAt: "2021-07-20T12:33:13.018Z",
createdBy: "admin",
},
{
path: "ตู้เอกสาร 1/ลิ้นชัก 1/แฟ้ม 2",
path: "ตู้เอกสาร 1/ลิ้นชัก 1/แฟ้ม 2/",
name: "แฟ้ม 2",
createdAt: "2022-01-23T16:05:02.114Z",
createdBy: "admin",
@ -243,6 +253,7 @@ export class StorageController extends Controller {
category: ["บัญชี"],
keyword: ["เงิน", "บัญชี", "รายจ่าย", "รายรับ"],
upload: false,
hidden: false,
fileName: "เอกสาร 1.pdf",
fileSize: 10240,
fileType: "application/pdf",
@ -252,15 +263,21 @@ export class StorageController extends Controller {
updatedBy: "admin",
},
])
@Tags("Storage Folder", "Storage File")
@Security("bearerAuth")
@SuccessResponse(HttpStatusCode.OK, "สำเร็จ")
public async getList(@Body() body: ListRequestBody) {
const path = body.path.filter(Boolean);
if (!(await checkPathExist(DEFAULT_BUCKET, path))) {
throw new HttpError(HttpStatusCode.NOT_FOUND, PATH_NOT_FOUND_MESSAGE);
}
if (body.operation === "folder") return await listFolder(path, body.hidden);
if (body.operation === "file") return await listFile(path);
}
/**
* @summary
*/
@Post("folder")
@Tags("Storage Folder")
@Security("bearerAuth", ["management-role", "admin"])
@ -271,7 +288,7 @@ export class StorageController extends Controller {
) {
const { path, name } = body;
if (!(await checkPathExist(DEFAULT_BUCKET, path.join("/")))) {
if (!(await checkPathExist(DEFAULT_BUCKET, path))) {
throw new HttpError(HttpStatusCode.NOT_FOUND, PATH_NOT_FOUND_MESSAGE);
}
@ -283,7 +300,7 @@ export class StorageController extends Controller {
const created = await minioClient
.putObject(
DEFAULT_BUCKET,
`${path.join("/")}/${name.replace(/[/\\?%*:|"<>#]/g, "-").trim()}/.keep`,
stripLeadingSlash(`${path.join("/")}/${name.replace(/[/\\?%*:|"<>#]/g, "-").trim()}/.keep`),
"",
0,
meta,
@ -293,7 +310,9 @@ export class StorageController extends Controller {
if (!created) throw new Error(MINIO_ERROR_MESSAGE);
io.getInstance()?.emit("FolderCreate", {
pathname: `${path.join("/")}/${name.replace(/[/\\?%*:|"<>#]/g, "-").trim()}/`,
pathname: stripLeadingSlash(
`${path.join("/")}/${name.replace(/[/\\?%*:|"<>#]/g, "-").trim()}/`,
),
name: name.replace(/[/\\?%*:|"<>#]/g, "-").trim(),
...meta,
});
@ -302,23 +321,26 @@ export class StorageController extends Controller {
}
/**
* Folder Folder (Path) Folder (Path)
*
*
*
* @summary
*/
@Put("folder")
@Tags("Storage Folder")
@Security("bearerAuth", ["management-role", "admin"])
@SuccessResponse(HttpStatusCode.NO_CONTENT, "สำเร็จ")
public async moveFolder(@Body() body: PutFolderBody) {
const src = `${body.from.path.join("/")}/${body.from.name}`;
const dst = `${body.to.path.join("/")}/${body.to.name}`;
const src = stripLeadingSlash(`${body.from.path.join("/")}/${body.from.name}`);
const dst = stripLeadingSlash(`${body.to.path.join("/")}/${body.to.name}`);
if (!(await checkPathExist(DEFAULT_BUCKET, src))) {
if (!(await checkPathExist(DEFAULT_BUCKET, src.split("/")))) {
throw new HttpError(HttpStatusCode.NOT_FOUND, PATH_NOT_FOUND_MESSAGE);
}
if (await checkPathExist(DEFAULT_BUCKET, dst)) {
if (await checkPathExist(DEFAULT_BUCKET, dst.split("/"))) {
throw new HttpError(HttpStatusCode.CONFLICT, PATH_ALREADY_EXIST);
}
if (!(await checkPathExist(DEFAULT_BUCKET, body.to.path.join("/")))) {
if (!(await checkPathExist(DEFAULT_BUCKET, body.to.path))) {
throw new HttpError(HttpStatusCode.PRECONDITION_FAILED, "ไม่พบตำแหน่งที่ต้องการย้าย");
}
@ -336,7 +358,7 @@ export class StorageController extends Controller {
await Promise.all(
list.map(async (v) => {
const from = `/${DEFAULT_BUCKET}/${v.pathname}`;
const to = `${dst}/${v.pathname.slice(`${src}/`.length)}`;
const to = stripLeadingSlash(`${dst}/${v.pathname.slice(`${src}/`.length)}`);
const result = await minioClient
.copyObject(DEFAULT_BUCKET, to, from, copyCond)
@ -376,7 +398,7 @@ export class StorageController extends Controller {
id: data._id,
doc: {
pathname: to,
path: to.split("/").slice(0, -1).join("/") + "/",
path: stripLeadingSlash(`${to.split("/").slice(0, -1).join("/")}/`),
},
refresh: "wait_for",
})
@ -390,8 +412,8 @@ export class StorageController extends Controller {
);
io.getInstance()?.emit("FolderMove", {
from: `${src}/`,
to: `${dst}/`,
from: stripLeadingSlash(`${src}/`),
to: stripLeadingSlash(`${dst}/`),
});
return this.setStatus(HttpStatusCode.NO_CONTENT);
@ -400,6 +422,10 @@ export class StorageController extends Controller {
@Post("folder/size")
@Tags("Storage Folder")
@Security("bearerAuth", ["management-role", "admin"])
@SuccessResponse(HttpStatusCode.OK, "สำเร็จ")
@Example({
size: 10240,
})
public async folderSize(@Body() body: FolderBody) {
return { size: await folderSize([...body.path, body.name]) };
}
@ -412,9 +438,17 @@ export class StorageController extends Controller {
@Security("bearerAuth", ["management-role", "admin"])
@SuccessResponse(HttpStatusCode.NO_CONTENT, "สำเร็จ")
public async deleteStorage(@Body() body: DeleteFolderBody) {
if (body.path.length === 0) {
throw new HttpError(HttpStatusCode.BAD_REQUEST, "ไม่สามารถลบได้");
}
await new Promise<void>((resolve, reject) => {
const objects: string[] = [];
const stream = minioClient.listObjectsV2(DEFAULT_BUCKET, body.path.join("/") + "/", true);
const stream = minioClient.listObjectsV2(
DEFAULT_BUCKET,
stripLeadingSlash(`${body.path.join("/")}/`),
true,
);
stream.on("data", (v) => v && v.name && objects.push(v.name));
stream.on("close", async () => {
@ -435,30 +469,53 @@ export class StorageController extends Controller {
@Tags("Storage File")
@Security("bearerAuth", ["management-role", "admin"])
@SuccessResponse(HttpStatusCode.OK, "สำเร็จ")
@Example({
pathname: "ตู้เอกสาร 1/ลิ้นชัก 1/แฟ้ม 1/เอกสาร 1.pdf",
path: "ตู้เอกสาร 1/ลิ้นชัก 1/แฟ้ม 1/",
title: "เอกสาร",
description: "เอกสารการเงิน",
category: ["บัญชี"],
keyword: ["เงิน", "บัญชี", "รายจ่าย", "รายรับ"],
upload: false,
hidden: false,
fileName: "เอกสาร 1.pdf",
fileSize: 10240,
fileType: "application/pdf",
createdAt: "2021-07-20T12:33:13.018Z",
createdBy: "admin",
updatedAt: "2021-07-20T12:33:13.018Z",
updatedBy: "admin",
uploadUrl: "s3.storage.upload",
})
public async postFile(
@Request() request: { user: { preferred_username: string } },
@Body() body: FileBody,
) {
const { path, file } = body;
const validFileName = file.replace(/[/\\?%*:|"<>#]/g, "-").trim();
if (!(await checkPathExist(DEFAULT_BUCKET, path.join("/")))) {
if (!(await checkPathExist(DEFAULT_BUCKET, path))) {
throw new HttpError(HttpStatusCode.NOT_FOUND, PATH_NOT_FOUND_MESSAGE);
}
const result = await esClient
.search<StorageFile & { attachment?: Record<string, unknown> }>({
index: DEFAULT_INDEX!,
query: { match: { pathname: path.join("/") + "/" } },
query: {
match: {
pathname: stripLeadingSlash(`${path.join("/")}/${validFileName}`),
},
},
})
.catch((e) => console.error(`MinIO Error: ${e}`));
const metadata: StorageFile = {
path: path.join("/") + "/",
pathname: path.join("/") + "/" + file.replace(/[/\\?%*:|"<>#]/g, "-").trim(),
fileName: file.replace(/[/\\?%*:|"<>#]/g, "-").trim(),
path: stripLeadingSlash(`${path.join("/")}/`),
pathname: stripLeadingSlash(`${path.join("/")}/${validFileName}`),
fileName: validFileName,
fileSize: 0, // Will be get by minio object storage after file is uploaded
fileType: "", // Will be determined by minio object storage after file is uploaded
title: body.title ?? file.replace(/[/\\?%*:|"<>#]/g, "-").trim(), // default to same as filename
title: body.title ?? validFileName, // default to same as filename
description: body.description ?? "",
category: body.category ?? [],
keyword: body.keyword ?? [],
@ -504,6 +561,8 @@ export class StorageController extends Controller {
@Tags("Storage File")
@Security("bearerAuth", ["management-role", "admin"])
@SuccessResponse(HttpStatusCode.OK, "สำเร็จ")
@Response(HttpStatusCode.NO_CONTENT, "สำเร็จ")
@Example({ uploadUrl: "s3.storage.upload" })
public async moveFile(
@Request() request: { user: { preferred_username: string } },
@Body() body: PutFileBody,
@ -512,7 +571,9 @@ export class StorageController extends Controller {
.search<StorageFile & { attachment: Record<string, any> }>({
index: DEFAULT_INDEX,
query: {
match: { pathname: body.from.path.join("/") + `/${body.from.file}` },
match: {
pathname: stripLeadingSlash(`${body.from.path.join("/")}/${body.from.file}`),
},
},
})
.catch((e) => console.error(`ElasticSearch Error: ${e}`));
@ -523,7 +584,12 @@ export class StorageController extends Controller {
if (search && search.hits.hits.length === 0) {
throw new HttpError(HttpStatusCode.NOT_FOUND, "ไม่พบไฟล์ดังกล่าว");
}
if (!(await checkFileExist(DEFAULT_BUCKET, body.from.path.join("/") + `/${body.from.file}`))) {
if (
!(await checkFileExist(
DEFAULT_BUCKET,
stripLeadingSlash(`${body.from.path.join("/")}/${body.from.file}`),
))
) {
await esClient.delete({
index: DEFAULT_INDEX,
id: search.hits.hits[0]._id,
@ -531,10 +597,15 @@ export class StorageController extends Controller {
throw new HttpError(HttpStatusCode.NOT_FOUND, "ไม่พบไฟล์ดังกล่าว");
}
if (body.to && JSON.stringify(body.from) !== JSON.stringify(body.to)) {
if (!(await checkPathExist(DEFAULT_BUCKET, body.to.path.join("/")))) {
if (!(await checkPathExist(DEFAULT_BUCKET, body.to.path))) {
throw new HttpError(HttpStatusCode.PRECONDITION_FAILED, "ไม่พบตำแหน่งที่ต้องการย้าย");
}
if (await checkFileExist(DEFAULT_BUCKET, body.to.path.join("/") + `/${body.to.file}`)) {
if (
await checkFileExist(
DEFAULT_BUCKET,
stripLeadingSlash(`${body.to.path.join("/")}/${body.to.file}`),
)
) {
throw new HttpError(
HttpStatusCode.PRECONDITION_FAILED,
"พบไฟล์ในต้ำแหน่งปลายทาง ไม่สามารถย้ายได้",
@ -563,7 +634,7 @@ export class StorageController extends Controller {
if (from && to && JSON.stringify(from) !== JSON.stringify(to)) {
const src = [DEFAULT_BUCKET, ...from.path, ""].join("/") + from.file;
const dst = to.path.join("/") + `/${to.file}`;
const dst = stripLeadingSlash(`${to.path.join("/")}/${to.file}`);
const result = await minioClient.copyObject(DEFAULT_BUCKET, dst, src, copyCond).catch((e) => {
console.error(`MinIO Error: ${e}`);
@ -577,7 +648,7 @@ export class StorageController extends Controller {
id: id,
doc: {
...metadata,
path: to.path.join("/") + "/",
path: stripLeadingSlash(`${to.path.join("/")}/`),
pathname: dst,
fileName: to.file,
...dateMeta,
@ -586,7 +657,10 @@ export class StorageController extends Controller {
})
.then(
async () =>
await minioClient.removeObject(DEFAULT_BUCKET, from.path.join("/") + `/${from.file}`),
await minioClient.removeObject(
DEFAULT_BUCKET,
stripLeadingSlash(`${from.path.join("/")}/${from.file}`),
),
)
.catch((e) => console.error(`ElasticSearch Error: ${e}`));
@ -595,7 +669,7 @@ export class StorageController extends Controller {
to: {
...source,
...metadata,
path: to.path.join("/") + "/",
path: stripLeadingSlash(`${to.path.join("/")}/`),
pathname: dst,
fileName: to.file,
...dateMeta,
@ -633,7 +707,7 @@ export class StorageController extends Controller {
});
if (upload) {
const src = from.path.join("/") + `/${from.file}`;
const src = stripLeadingSlash(`${from.path.join("/")}/${from.file}`);
const presignedUrl = await minioClient.presignedPutObject(DEFAULT_BUCKET, src);
return { uploadUrl: presignedUrl };
}
@ -650,7 +724,7 @@ export class StorageController extends Controller {
@Security("bearerAuth", ["management-role", "admin"])
@SuccessResponse(HttpStatusCode.NO_CONTENT, "สำเร็จ")
public async deleteFile(@Body() body: DeleteFileBody) {
const pathname = body.path.join("/") + `/${body.file}`;
const pathname = stripLeadingSlash(`${body.path.join("/")}/${body.file}`);
await minioClient
.removeObject(DEFAULT_BUCKET, pathname)
@ -671,8 +745,26 @@ export class StorageController extends Controller {
@Tags("Download")
@Security("bearerAuth", ["management-role", "admin"])
@SuccessResponse(HttpStatusCode.OK, "สำเร็จ")
@Example({
pathname: "ตู้เอกสาร 1/ลิ้นชัก 1/แฟ้ม 1/เอกสาร 1.pdf",
path: "ตู้เอกสาร 1/ลิ้นชัก 1/แฟ้ม 1/",
title: "เอกสาร",
description: "เอกสารการเงิน",
category: ["บัญชี"],
keyword: ["เงิน", "บัญชี", "รายจ่าย", "รายรับ"],
upload: false,
hidden: false,
fileName: "เอกสาร 1.pdf",
fileSize: 10240,
fileType: "application/pdf",
createdAt: "2021-07-20T12:33:13.018Z",
createdBy: "admin",
updatedAt: "2021-07-20T12:33:13.018Z",
updatedBy: "admin",
downloadUrl: "s3.storage.download",
})
public async downloadFile(@Body() body: DownloadFileBody) {
const pathname = body.path.join("/") + `/${body.file}`;
const pathname = stripLeadingSlash(`${body.path.join("/")}/${body.file}`);
const search = await esClient.search<StorageFile & { attachment: Record<string, string> }>({
index: DEFAULT_INDEX,

View file

@ -1,270 +0,0 @@
import {
Body,
Controller,
Delete,
Get,
Path,
Post,
Put,
Route,
Security,
SuccessResponse,
Tags,
Request,
Response,
Example,
} from "tsoa";
import minioClient from "../minio";
import esClient from "../elasticsearch";
import { copyCond, listFolder, listItem, pathExist, replaceIllegalChars } from "../utils/minio";
import HttpStatusCode from "../interfaces/http-status";
import { StorageFile, StorageFolder } from "../interfaces/storage-fs";
import HttpError from "../interfaces/http-error";
import { getInstance } from "../lib/websocket";
const DEFAULT_BUCKET = process.env.MINIO_BUCKET;
const DEFAULT_INDEX = process.env.ELASTICSEARCH_INDEX;
if (!DEFAULT_BUCKET) throw Error("Default MinIO bucket must be specified.");
if (!DEFAULT_INDEX) throw Error("Default ElasticSearch index must be specified.");
@Route("/cabinet/{cabinetName}/drawer/{drawerName}/folder/{folderName}/subfolder")
export class SubFolderController extends Controller {
/**
* @example cabinetName "ตู้เอกสาร 1"
* @example drawerName "ลิ้นชัก 1"
* @example folderName "แฟ้ม 1"
*/
@Get("/")
@Tags("แฟ้มย่อย")
@Security("bearerAuth")
@Response(
HttpStatusCode.INTERNAL_SERVER_ERROR,
"เกิดข้อผิดพลาด ไม่สามารถแสดงรายการแฟ้มได้ กรุณาลองใหม่ในภายหลัง",
)
@SuccessResponse(HttpStatusCode.OK, "สำเร็จ")
@Example([
{
path: "ตู้เอกสาร 1/ลิ้นชัก 1/แฟ้ม 1/แฟ้มย่อย 1",
name: "แฟ้มย่อย 1",
createdAt: "2021-07-20T12:33:13.018Z",
createdBy: "admin",
},
{
path: "ตู้เอกสาร 1/ลิ้นชัก 1/แฟ้ม 1/แฟ้มย่อย 2",
name: "แฟ้มย่อย 2",
createdAt: "2022-01-23T16:05:02.114Z",
createdBy: "admin",
},
])
public async listFolder(
@Path() cabinetName: string,
@Path() drawerName: string,
@Path() folderName: string,
): Promise<StorageFolder[]> {
const list = await listFolder(
DEFAULT_BUCKET!,
`${cabinetName}/${drawerName}/${folderName}`,
).catch((e) => console.error(`Error List Folder: ${e}`));
if (!list) throw new Error("เกิดข้อผิดพลาด ไม่สามารถแสดงรายการแฟ้มได้ กรุณาลองใหม่ในภายหลัง");
return list;
}
/**
* @example cabinetName "ตู้เอกสาร 1"
* @example drawerName "ลิ้นชัก 1"
* @example folderName "แฟ้ม 1"
*/
@Post("/")
@Tags("แฟ้มย่อย")
@Security("bearerAuth", ["admin", "management-role"])
@Response(HttpStatusCode.NOT_FOUND, "ไม่พบของแฟ้ม")
@Response(HttpStatusCode.INTERNAL_SERVER_ERROR, "เกิดข้อผิดพลาดกับระบบจัดการไฟล์")
@SuccessResponse(HttpStatusCode.CREATED, "สำเร็จ")
public async createFolder(
@Request() request: { user: { preferred_username: string } },
@Body() body: { name: string },
@Path() cabinetName: string,
@Path() drawerName: string,
@Path() folderName: string,
) {
const basePath = `${cabinetName}/${drawerName}/${folderName}/`;
if (!(await pathExist(DEFAULT_BUCKET!, basePath))) {
throw new HttpError(HttpStatusCode.NOT_FOUND, "ไม่พบตำแหน่งที่ต้องการสร้างลิ้นชัก");
}
const meta = {
createdAt: new Date().toISOString(),
createdBy: request.user.preferred_username,
};
const created = await minioClient
.putObject(DEFAULT_BUCKET!, `${basePath}${replaceIllegalChars(body.name)}/.keep`, "", 0, meta)
.catch((e) => console.error(e));
if (!created) throw new Error("เกิดข้อผิดพลาดกับระบบจัดการไฟล์");
const io = getInstance();
io?.emit("CreateFolder", {
pathname: `${basePath}${replaceIllegalChars(body.name)}/`,
name: replaceIllegalChars(body.name),
...meta,
});
return this.setStatus(HttpStatusCode.CREATED);
}
/**
* @example cabinetName "ตู้เอกสาร 1"
* @example drawerName "ลิ้นชัก 1"
* @example folderName "แฟ้ม 1"
* @example subFolderName "แฟ้มย่อย 1"
*/
@Put("/{subFolderName}")
@Tags("แฟ้มย่อย")
@Security("bearerAuth", ["admin", "management-role"])
@Response(HttpStatusCode.INTERNAL_SERVER_ERROR, "เกิดข้อผิดพลาดไม่สามารถย้ายไฟล์ได้")
@SuccessResponse(HttpStatusCode.NO_CONTENT, "สำเร็จ")
public async editFolder(
@Body()
body: {
/**
* @example "แฟ้มใหม่"
*/
name: string;
},
@Path() cabinetName: string,
@Path() drawerName: string,
@Path() folderName: string,
@Path() subFolderName: string,
) {
const path = `${cabinetName}/${drawerName}/${folderName}/${subFolderName}/`;
const list = await listItem(DEFAULT_BUCKET!, path, true);
await Promise.all(
list.map(async (current) => {
if (!current.name) return;
const base = `${cabinetName}/${drawerName}/${folderName}/${replaceIllegalChars(
body.name,
)}/`;
const destination = `${base}${current.name.slice(path.length)}`;
const source = `/${DEFAULT_BUCKET}/${current.name}`;
return await minioClient
.copyObject(DEFAULT_BUCKET!, destination, source, copyCond)
.then(async () => {
if (current.name.includes(".keep")) {
return await minioClient.removeObject(DEFAULT_BUCKET!, current.name);
}
const search = await esClient.search<
StorageFile & { attachment: Record<string, string> }
>({
index: DEFAULT_INDEX!,
query: { match: { pathname: current.name } },
});
if (search && search.hits.hits.length === 0) throw new Error("ไม่พบข้อมูลในฐานข้อมูล");
const data = search.hits.hits[0];
await esClient.update({
index: DEFAULT_INDEX!,
id: data._id,
doc: {
pathname: destination,
path: destination.split("/").slice(0, -1).join("/") + "/",
},
refresh: "wait_for",
});
await minioClient.removeObject(DEFAULT_BUCKET!, current.name);
})
.catch((e) => {
console.error(e);
throw new Error("เกิดข้อผิดพลาด ไม่สามารถย้ายไฟล์ได้");
});
}),
);
const io = getInstance();
io?.emit("EditFolder", {
from: `${cabinetName}/${drawerName}/${folderName}/${subFolderName}/`,
to: `${cabinetName}/${drawerName}/${folderName}/${replaceIllegalChars(body.name)}/`,
});
return this.setStatus(HttpStatusCode.NO_CONTENT);
}
/**
* @example cabinetName "ตู้เอกสาร 1"
* @example drawerName "ลิ้นชัก 1"
* @example folderName "แฟ้ม 1"
* @example subFolderName "แฟ้มย่อย 1"
*/
@Delete("/{subFolderName}")
@Tags("แฟ้มย่อย")
@Security("bearerAuth", ["admin", "management-role"])
@SuccessResponse(HttpStatusCode.NO_CONTENT, "สำเร็จ")
public async deleteFolder(
@Path() cabinetName: string,
@Path() drawerName: string,
@Path() folderName: string,
@Path() subFolderName: string,
) {
await new Promise<void>((resolve, reject) => {
const objects: string[] = [];
const stream = minioClient.listObjectsV2(
DEFAULT_BUCKET!,
`${cabinetName}/${drawerName}/${folderName}/${subFolderName}`,
true,
);
stream.on("data", (v) => {
if (v && v.name) objects.push(v.name);
});
stream.on("close", async () =>
resolve(await minioClient.removeObjects(DEFAULT_BUCKET!, objects)),
);
stream.on("error", () => reject(new Error("เกิดข้อผิดพลาด ไม่สามารถลบไฟล์ได้")));
});
const io = getInstance();
io?.emit("DeleteFolder", {
pathname: `${cabinetName}/${drawerName}/${folderName}/${subFolderName}/`,
});
return this.setStatus(HttpStatusCode.NO_CONTENT);
}
/**
* @example cabinetName "ตู้เอกสาร 1"
* @example drawerName "ลิ้นชัก 1"
* @example folderName "แฟ้ม 1"
* @example subFolderName "แฟ้มย่อย 1"
*/
@Get("/{subFolderName}/size")
@Tags("แฟ้มย่อย")
@Security("bearerAuth")
@SuccessResponse(HttpStatusCode.OK, "สำเร็จ")
public async calc(
@Path() cabinetName: string,
@Path() drawerName: string,
@Path() folderName: string,
@Path() subFolderName: string,
) {
const list = await listItem(
DEFAULT_BUCKET!,
`${cabinetName}/${drawerName}/${folderName}/${subFolderName}`,
true,
).catch((e) => console.error(`Error List Folder: ${e}`));
if (!list) throw new Error("เกิดข้อผิดพลาด ไม่สามารถแสดงรายการแฟ้มได้ กรุณาลองใหม่ในภายหลัง");
return { size: list.reduce<number>((a, c) => a + c.size, 0) };
}
}

View file

@ -1,455 +0,0 @@
import {
Body,
Controller,
Delete,
Example,
Get,
Patch,
Path,
Post,
Request,
Response,
Route,
Security,
SuccessResponse,
Tags,
} from "tsoa";
import esClient from "../elasticsearch";
import minioClient from "../minio";
import HttpStatusCode from "../interfaces/http-status";
import { StorageFile } from "../interfaces/storage-fs";
import HttpError from "../interfaces/http-error";
import { copyCond, pathExist, replaceIllegalChars } from "../utils/minio";
import { getInstance } from "../lib/websocket";
const DEFAULT_BUCKET = process.env.MINIO_BUCKET;
const DEFAULT_INDEX = process.env.ELASTICSEARCH_INDEX;
if (!DEFAULT_BUCKET) throw Error("Default MinIO bucket must be specified.");
if (!DEFAULT_INDEX) throw Error("Default ElasticSearch index must be specified.");
@Route(
"/cabinet/{cabinetName}/drawer/{drawerName}/folder/{folderName}/subfolder/{subFolderName}/file",
)
export class SubFolderFileController extends Controller {
/**
* @example cabinetName "ตู้เอกสาร 1"
* @example drawerName "ลิ้นชัก 1"
* @example folderName "แฟ้ม 1"
* @example subFolderName "แฟ้มย่อย 1"
*/
@Get("/")
@Tags("ไฟล์")
@Security("bearerAuth")
@SuccessResponse(HttpStatusCode.OK, "สำเร็จ")
@Example([
{
pathname: "ตู้เอกสาร 1/ลิ้นชัก 1/แฟ้ม 1/แฟ้มย่อย 1/เอกสาร 1",
path: "ตู้เอกสาร 1/ลิ้นชัก 1/แฟ้ม 1/แฟ้มย่อย 1/",
title: "เอกสาร",
description: "เอกสารการเงิน",
category: ["บัญชี"],
keyword: ["เงิน", "บัญชี", "รายจ่าย", "รายรับ"],
upload: false,
fileName: "เอกสาร 1",
fileSize: 10240,
fileType: "application/pdf",
createdAt: "2021-07-20T12:33:13.018Z",
createdBy: "admin",
updatedAt: "2021-07-20T12:33:13.018Z",
updatedBy: "admin",
},
])
public async getFile(
@Path() cabinetName: string,
@Path() drawerName: string,
@Path() folderName: string,
@Path() subFolderName: string,
): Promise<StorageFile[]> {
const search = await esClient.search<StorageFile & { attachment: Record<string, string> }>({
index: DEFAULT_INDEX!,
query: {
match: {
path: `${cabinetName}/${drawerName}/${folderName}/${subFolderName}/`,
},
},
size: 10000,
});
const records = search.hits.hits
.map((v) => {
if (v._source) {
const { attachment, ...rest } = v._source;
return rest satisfies StorageFile;
}
})
.filter((v: StorageFile | undefined): v is StorageFile => !!v);
return records;
}
/**
* @example cabinetName "ตู้เอกสาร 1"
* @example drawerName "ลิ้นชัก 1"
* @example folderName "แฟ้ม 1"
* @example subFolderName "แฟ้มย่อย 1"
*/
@Post("/")
@Tags("ไฟล์")
@Security("bearerAuth", ["admin", "management-role"])
@Response(
HttpStatusCode.NOT_FOUND,
"ตำแหน่งที่ระบุไม่พบ กรุณาเตรียมตำแหน่งที่ต้องการก่อนดำเนินการ",
)
@SuccessResponse(HttpStatusCode.CREATED, "สำเร็จ")
@Example({
pathname: "ตู้เอกสาร 1/ลิ้นชัก 1/แฟ้ม 1/แฟ้มย่อย 1/เอกสาร 1",
path: "ตู้เอกสาร 1/ลิ้นชัก 1/แฟ้ม 1/แฟ้มย่อย 1/",
title: "เอกสาร",
description: "เอกสารการเงิน",
category: ["บัญชี"],
keyword: ["เงิน", "บัญชี", "รายจ่าย", "รายรับ"],
upload: false,
fileName: "เอกสาร 1",
fileSize: 10240,
fileType: "application/pdf",
createdAt: "2021-07-20T12:33:13.018Z",
createdBy: "admin",
updatedAt: "2021-07-20T12:33:13.018Z",
updatedBy: "admin",
})
public async uploadFile(
@Request() request: { user: { preferred_username: string } },
@Body()
body: {
/**
* @example "เอกสาร 1"
*/
file: string;
/**
* @example "เอกสาร"
*/
title?: string;
/**
* @example "เอกสารการเงิน"
*/
description?: string;
/**
* @example ["บัญชี"]
*/
category?: string[];
/**
* @example ["เงิน", "บัญชี", "รายจ่าย", "รายรับ"]
*/
keyword?: string[];
},
@Path() cabinetName: string,
@Path() drawerName: string,
@Path() folderName: string,
@Path() subFolderName: string,
) {
if (body.file && body.file.length > 85) {
throw new HttpError(HttpStatusCode.BAD_REQUEST, "ชื่อไฟล์ยาวเกินกำหนด");
}
const basePath = `${cabinetName}/${drawerName}/${folderName}/${subFolderName}/`;
const pathname = `${basePath}${replaceIllegalChars(body.file)}`;
if (!(await pathExist(DEFAULT_BUCKET!, basePath))) {
throw new HttpError(
HttpStatusCode.NOT_FOUND,
"ตำแหน่งที่ระบุไม่พบ กรุณาเตรียมตำแหน่งที่ต้องการก่อนดำเนินการ",
);
}
const result = await esClient
.search<StorageFile & { attachment?: Record<string, unknown> }>({
index: DEFAULT_INDEX!,
query: { match: { pathname } },
})
.catch((e) => console.error(e));
// pathname is unique and should not have multiple record with same path
if (result && result.hits.hits.length > 0 && result.hits.hits[0]._source) {
await esClient
.delete({
index: DEFAULT_INDEX!,
id: result.hits.hits[0]._id,
})
.catch((e) => console.error(e));
}
const rec = result && result.hits.hits.length > 0 ? result.hits.hits[0]._source : false;
const metadata: Partial<StorageFile> = {
pathname,
path: basePath,
fileName: replaceIllegalChars(body.file),
fileSize: 0,
fileType: "",
title: body.title ?? "",
description: body.description ?? "",
category: body.category ?? [],
keyword: body.keyword ?? [],
upload: false,
createdAt: new Date().toISOString(),
createdBy: rec ? rec.createdBy : "n/a",
updatedAt: new Date().toISOString(),
updatedBy: request.user.preferred_username ?? "n/a",
};
await esClient.index({
index: DEFAULT_INDEX!,
document: metadata,
refresh: "wait_for",
});
const io = getInstance();
io?.emit("FileUploadRequest", metadata);
return {
...body,
createdAt: metadata.createdAt,
createdBy: metadata.createdBy,
updatedAt: metadata.updatedAt,
updatedBy: metadata.updatedBy,
upload: await minioClient.presignedPutObject(DEFAULT_BUCKET!, pathname),
};
}
/**
* @example cabinetName "ตู้เอกสาร 1"
* @example drawerName "ลิ้นชัก 1"
* @example folderName "แฟ้ม 1"
* @example subFolderName "แฟ้มย่อย 1"
* @example fileName "เอกสาร 1"
*/
@Patch("/{fileName}")
@Tags("ไฟล์")
@Security("bearerAuth", ["admin", "management-role"])
@Response(HttpStatusCode.NOT_FOUND, "ไม่พบตำแหน่งที่ต้องการสร้างแฟ้ม")
@SuccessResponse(HttpStatusCode.OK, "สำเร็จ")
public async updateFile(
@Request() request: { user: { preferred_username: string } },
@Path() cabinetName: string,
@Path() drawerName: string,
@Path() folderName: string,
@Path() subFolderName: string,
@Path() fileName: string,
@Body()
body: {
/**
* @example "เอกสารใหม่"
*/
file?: string;
/**
* @example "เอกสารการเงิน"
*/
title?: string;
/**
* @example "เอกสารการเงินฉบับใหม่"
*/
description?: string;
/**
* @example ["บัญชี"]
*/
category?: string[];
/**
* @example ["เงิน", "บัญชี", "รายจ่าย", "รายรับ"]
*/
keyword?: string[];
},
) {
if (body.file && body.file.length > 85) {
throw new HttpError(HttpStatusCode.BAD_REQUEST, "ชื่อไฟล์ยาวเกินกำหนด");
}
const basePath = `${cabinetName}/${drawerName}/${folderName}/${subFolderName}/`;
const pathname = `${basePath}${fileName}`;
if (
!Boolean(
await minioClient.statObject(DEFAULT_BUCKET!, `${pathname}`).catch((e) => {
if (e.code === "NotFound") return false;
throw new Error("เกิดข้อผิดพลาดกับระบบจัดการไฟล์");
}),
)
) {
throw new HttpError(HttpStatusCode.NOT_FOUND, "ไม่พบไฟล์");
}
const { file, ...metadata } = body;
// assume user will probably replace file by re-upload but maybe just rename
if (file) {
const destination = `${basePath}${replaceIllegalChars(file)}`;
const source = `/${DEFAULT_BUCKET}/${basePath}${fileName}`;
const copy = await minioClient.copyObject(DEFAULT_BUCKET!, destination, source, copyCond);
if (copy) {
const search = await esClient
.search<StorageFile & { attachment?: Record<string, unknown> }>({
index: DEFAULT_INDEX!,
query: { match: { pathname } },
})
.catch((e) => console.error(e));
const meta = {
updatedAt: new Date().toISOString(),
updatedBy: request.user.preferred_username ?? "n/a",
};
if (search && search.hits.hits.length > 0 && search.hits.hits[0]._source) {
const { _index: index, _id: id } = search.hits.hits[0];
await esClient
.update({
index,
id,
doc: {
...metadata,
pathname: destination,
path: basePath,
fileName: replaceIllegalChars(file),
...meta,
},
refresh: "wait_for",
})
.then(() => minioClient.removeObject(DEFAULT_BUCKET!, pathname));
const io = getInstance();
io?.emit("FileUpdateMove", {
from: search.hits.hits[0]._source,
to: {
...search.hits.hits[0]._source,
...metadata,
pathname: destination,
path: basePath,
fileName: replaceIllegalChars(file),
...meta,
},
});
} else {
await minioClient.removeObject(DEFAULT_BUCKET!, pathname);
}
}
} else {
const search = await esClient
.search<StorageFile & { attachment?: Record<string, unknown> }>({
index: DEFAULT_INDEX!,
query: { match: { pathname } },
})
.catch((e) => console.error(e));
if (search && search.hits.hits.length > 0 && search.hits.hits[0]._source) {
const { _index: index, _id: id } = search.hits.hits[0];
const meta = {
updatedAt: new Date().toISOString(),
updatedBy: request.user.preferred_username ?? "n/a",
};
await esClient.update({
index,
id,
doc: {
...metadata,
updatedAt: new Date().toISOString(),
updatedBy: request.user.preferred_username ?? "n/a",
},
refresh: "wait_for",
});
const updated: StorageFile = {
...search.hits.hits[0]._source,
...metadata,
...meta,
};
const io = getInstance();
io?.emit("FileUpdate", updated);
}
}
return body.file
? {
upload: await minioClient.presignedPutObject(
DEFAULT_BUCKET!,
`${basePath}${replaceIllegalChars(body.file) ?? fileName}`,
),
}
: this.setStatus(HttpStatusCode.NO_CONTENT);
}
/**
* @example cabinetName "ตู้เอกสาร 1"
* @example drawerName "ลิ้นชัก 1"
* @example folderName "แฟ้ม 1"
* @example subFolderName "แฟ้มย่อย 1"
* @example fileName "เอกสาร 1"
*/
@Delete("/{fileName}")
@Tags("ไฟล์")
@Security("bearerAuth", ["admin", "management-role"])
@SuccessResponse(HttpStatusCode.OK, "สำเร็จ")
public async deleteFile(
@Path() cabinetName: string,
@Path() drawerName: string,
@Path() folderName: string,
@Path() subFolderName: string,
@Path() fileName: string,
) {
await minioClient.removeObject(
DEFAULT_BUCKET!,
`${cabinetName}/${drawerName}/${folderName}/${subFolderName}/${fileName}`,
);
await esClient.deleteByQuery({
index: DEFAULT_INDEX!,
query: {
match: {
pathname: `${cabinetName}/${drawerName}/${folderName}/${subFolderName}/${fileName}`,
},
},
});
return this.setStatus(HttpStatusCode.NO_CONTENT);
}
/**
* @example cabinetName "ตู้เอกสาร 1"
* @example drawerName "ลิ้นชัก 1"
* @example folderName "แฟ้ม 1"
* @example folderName "แฟ้มย่อย 1"
* @example fileName "เอกสาร 1"
*/
@Get("/{fileName}")
@Tags("ดาวน์โหลด")
@Security("bearerAuth")
@SuccessResponse(HttpStatusCode.OK)
public async downloadFile(
@Path() cabinetName: string,
@Path() drawerName: string,
@Path() folderName: string,
@Path() subFolderName: string,
@Path() fileName: string,
) {
const search = await esClient.search<StorageFile & { attachment: Record<string, string> }>({
index: DEFAULT_INDEX!,
query: {
match: {
pathname: `${cabinetName}/${drawerName}/${folderName}/${subFolderName}/${fileName}`,
},
},
});
if (search && search.hits.hits.length === 0) {
throw new HttpError(HttpStatusCode.NOT_FOUND, "Not found");
}
const { attachment, ...rest } = search.hits.hits[0]._source!;
return {
...rest,
download: await minioClient.presignedGetObject(
DEFAULT_BUCKET!,
`${cabinetName}/${drawerName}/${folderName}/${subFolderName}/${fileName}`,
),
};
}
}

View file

@ -111,7 +111,7 @@ async function handleNotFoundRecord(
buffer: Buffer,
stat: { size: number; type: string },
) {
const path = pathname.split("/").slice(0, -1).join("/") + "/";
const path = stripLeadingSlash(pathname.split("/").slice(0, -1).join("/") + "/");
const filename = pathname.split("/").at(-1);
const base64 = Buffer.from(buffer).toString("base64");
@ -175,3 +175,7 @@ async function handleFoundRecord(
return true;
}
function stripLeadingSlash(str: string) {
return str.replace(/^\//, "");
}

View file

@ -3,22 +3,10 @@
// WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa
import { Controller, ValidationService, FieldErrors, ValidateError, TsoaRoute, HttpStatusCodeLiteral, TsoaResponse, fetchMiddlewares } from '@tsoa/runtime';
// WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa
import { CabinetController } from './controllers/cabinetController';
// WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa
import { DrawerController } from './controllers/drawerController';
// WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa
import { FileController } from './controllers/fileController';
// WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa
import { FolderController } from './controllers/folderController';
// WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa
import { SearchController } from './controllers/searchController';
// WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa
import { StorageController } from './controllers/storageController';
// WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa
import { SubFolderController } from './controllers/subFolderController';
// WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa
import { SubFolderFileController } from './controllers/subFolderFileController';
// WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa
import { VersionController } from './controllers/versionController';
import { expressAuthentication } from './utils/auth';
// @ts-ignore - no great way to install types from subpackage
@ -28,17 +16,6 @@ import type { RequestHandler, Router } from 'express';
// WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa
const models: TsoaRoute.Models = {
"StorageFolder": {
"dataType": "refObject",
"properties": {
"pathname": {"dataType":"string","required":true},
"name": {"dataType":"string","required":true},
"createdAt": {"dataType":"union","subSchemas":[{"dataType":"string"},{"dataType":"datetime"}],"required":true},
"createdBy": {"dataType":"union","subSchemas":[{"dataType":"string"},{"dataType":"datetime"}],"required":true},
},
"additionalProperties": false,
},
// WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa
"StorageFile": {
"dataType": "refObject",
"properties": {
@ -71,6 +48,17 @@ const models: TsoaRoute.Models = {
"additionalProperties": false,
},
// WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa
"StorageFolder": {
"dataType": "refObject",
"properties": {
"pathname": {"dataType":"string","required":true},
"name": {"dataType":"string","required":true},
"createdAt": {"dataType":"union","subSchemas":[{"dataType":"string"},{"dataType":"datetime"}],"required":true},
"createdBy": {"dataType":"union","subSchemas":[{"dataType":"string"},{"dataType":"datetime"}],"required":true},
},
"additionalProperties": false,
},
// WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa
"ListRequestBody": {
"dataType": "refObject",
"properties": {
@ -124,11 +112,11 @@ const models: TsoaRoute.Models = {
"PutFileBody": {
"dataType": "refObject",
"properties": {
"hidden": {"dataType":"boolean"},
"keyword": {"dataType":"array","array":{"dataType":"string"}},
"title": {"dataType":"string"},
"description": {"dataType":"string"},
"category": {"dataType":"array","array":{"dataType":"string"}},
"keyword": {"dataType":"array","array":{"dataType":"string"}},
"hidden": {"dataType":"boolean"},
"from": {"dataType":"nestedObjectLiteral","nestedProperties":{"file":{"dataType":"string","required":true},"path":{"dataType":"array","array":{"dataType":"string"},"required":true}},"required":true},
"to": {"dataType":"nestedObjectLiteral","nestedProperties":{"file":{"dataType":"string","required":true},"path":{"dataType":"array","array":{"dataType":"string"},"required":true}}},
"upload": {"dataType":"boolean"},
@ -164,561 +152,6 @@ export function RegisterRoutes(app: Router) {
// NOTE: If you do not see routes for all of your controllers in this file, then you might not have informed tsoa of where to look
// Please look into the "controllerPathGlobs" config option described in the readme: https://github.com/lukeautry/tsoa
// ###########################################################################################################
app.get('/cabinet',
authenticateMiddleware([{"bearerAuth":[]}]),
...(fetchMiddlewares<RequestHandler>(CabinetController)),
...(fetchMiddlewares<RequestHandler>(CabinetController.prototype.listCabinet)),
function CabinetController_listCabinet(request: any, response: any, next: any) {
const args = {
};
// WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa
let validatedArgs: any[] = [];
try {
validatedArgs = getValidatedArgs(args, request, response);
const controller = new CabinetController();
const promise = controller.listCabinet.apply(controller, validatedArgs as any);
promiseHandler(controller, promise, response, 200, next);
} catch (err) {
return next(err);
}
});
// WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa
app.post('/cabinet',
authenticateMiddleware([{"bearerAuth":["admin","management-role"]}]),
...(fetchMiddlewares<RequestHandler>(CabinetController)),
...(fetchMiddlewares<RequestHandler>(CabinetController.prototype.createCabinet)),
function CabinetController_createCabinet(request: any, response: any, next: any) {
const args = {
request: {"in":"request","name":"request","required":true,"dataType":"object"},
body: {"in":"body","name":"body","required":true,"dataType":"nestedObjectLiteral","nestedProperties":{"name":{"dataType":"string","required":true}}},
};
// WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa
let validatedArgs: any[] = [];
try {
validatedArgs = getValidatedArgs(args, request, response);
const controller = new CabinetController();
const promise = controller.createCabinet.apply(controller, validatedArgs as any);
promiseHandler(controller, promise, response, 201, next);
} catch (err) {
return next(err);
}
});
// WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa
app.put('/cabinet/:cabinetName',
authenticateMiddleware([{"bearerAuth":["admin","management-role"]}]),
...(fetchMiddlewares<RequestHandler>(CabinetController)),
...(fetchMiddlewares<RequestHandler>(CabinetController.prototype.editCabinet)),
function CabinetController_editCabinet(request: any, response: any, next: any) {
const args = {
cabinetName: {"in":"path","name":"cabinetName","required":true,"dataType":"string"},
body: {"in":"body","name":"body","required":true,"dataType":"nestedObjectLiteral","nestedProperties":{"name":{"dataType":"string","required":true}}},
};
// WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa
let validatedArgs: any[] = [];
try {
validatedArgs = getValidatedArgs(args, request, response);
const controller = new CabinetController();
const promise = controller.editCabinet.apply(controller, validatedArgs as any);
promiseHandler(controller, promise, response, 204, next);
} catch (err) {
return next(err);
}
});
// WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa
app.delete('/cabinet/:cabinetName',
authenticateMiddleware([{"bearerAuth":["admin","management-role"]}]),
...(fetchMiddlewares<RequestHandler>(CabinetController)),
...(fetchMiddlewares<RequestHandler>(CabinetController.prototype.deleteCabinet)),
function CabinetController_deleteCabinet(request: any, response: any, next: any) {
const args = {
cabinetName: {"in":"path","name":"cabinetName","required":true,"dataType":"string"},
};
// WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa
let validatedArgs: any[] = [];
try {
validatedArgs = getValidatedArgs(args, request, response);
const controller = new CabinetController();
const promise = controller.deleteCabinet.apply(controller, validatedArgs as any);
promiseHandler(controller, promise, response, 204, next);
} catch (err) {
return next(err);
}
});
// WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa
app.get('/cabinet/:cabinetName/size',
authenticateMiddleware([{"bearerAuth":[]}]),
...(fetchMiddlewares<RequestHandler>(CabinetController)),
...(fetchMiddlewares<RequestHandler>(CabinetController.prototype.calc)),
function CabinetController_calc(request: any, response: any, next: any) {
const args = {
cabinetName: {"in":"path","name":"cabinetName","required":true,"dataType":"string"},
};
// WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa
let validatedArgs: any[] = [];
try {
validatedArgs = getValidatedArgs(args, request, response);
const controller = new CabinetController();
const promise = controller.calc.apply(controller, validatedArgs as any);
promiseHandler(controller, promise, response, 200, next);
} catch (err) {
return next(err);
}
});
// WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa
app.get('/cabinet/:cabinetName/drawer',
authenticateMiddleware([{"bearerAuth":[]}]),
...(fetchMiddlewares<RequestHandler>(DrawerController)),
...(fetchMiddlewares<RequestHandler>(DrawerController.prototype.listDrawer)),
function DrawerController_listDrawer(request: any, response: any, next: any) {
const args = {
cabinetName: {"in":"path","name":"cabinetName","required":true,"dataType":"string"},
};
// WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa
let validatedArgs: any[] = [];
try {
validatedArgs = getValidatedArgs(args, request, response);
const controller = new DrawerController();
const promise = controller.listDrawer.apply(controller, validatedArgs as any);
promiseHandler(controller, promise, response, 200, next);
} catch (err) {
return next(err);
}
});
// WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa
app.post('/cabinet/:cabinetName/drawer',
authenticateMiddleware([{"bearerAuth":["admin","management-role"]}]),
...(fetchMiddlewares<RequestHandler>(DrawerController)),
...(fetchMiddlewares<RequestHandler>(DrawerController.prototype.createDrawer)),
function DrawerController_createDrawer(request: any, response: any, next: any) {
const args = {
request: {"in":"request","name":"request","required":true,"dataType":"object"},
cabinetName: {"in":"path","name":"cabinetName","required":true,"dataType":"string"},
body: {"in":"body","name":"body","required":true,"dataType":"nestedObjectLiteral","nestedProperties":{"name":{"dataType":"string","required":true}}},
};
// WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa
let validatedArgs: any[] = [];
try {
validatedArgs = getValidatedArgs(args, request, response);
const controller = new DrawerController();
const promise = controller.createDrawer.apply(controller, validatedArgs as any);
promiseHandler(controller, promise, response, 201, next);
} catch (err) {
return next(err);
}
});
// WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa
app.put('/cabinet/:cabinetName/drawer/:drawerName',
authenticateMiddleware([{"bearerAuth":["admin","management-role"]}]),
...(fetchMiddlewares<RequestHandler>(DrawerController)),
...(fetchMiddlewares<RequestHandler>(DrawerController.prototype.editDrawer)),
function DrawerController_editDrawer(request: any, response: any, next: any) {
const args = {
cabinetName: {"in":"path","name":"cabinetName","required":true,"dataType":"string"},
drawerName: {"in":"path","name":"drawerName","required":true,"dataType":"string"},
body: {"in":"body","name":"body","required":true,"dataType":"nestedObjectLiteral","nestedProperties":{"name":{"dataType":"string","required":true}}},
};
// WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa
let validatedArgs: any[] = [];
try {
validatedArgs = getValidatedArgs(args, request, response);
const controller = new DrawerController();
const promise = controller.editDrawer.apply(controller, validatedArgs as any);
promiseHandler(controller, promise, response, 204, next);
} catch (err) {
return next(err);
}
});
// WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa
app.delete('/cabinet/:cabinetName/drawer/:drawerName',
authenticateMiddleware([{"bearerAuth":["admin","management-role"]}]),
...(fetchMiddlewares<RequestHandler>(DrawerController)),
...(fetchMiddlewares<RequestHandler>(DrawerController.prototype.deleteDrawer)),
function DrawerController_deleteDrawer(request: any, response: any, next: any) {
const args = {
cabinetName: {"in":"path","name":"cabinetName","required":true,"dataType":"string"},
drawerName: {"in":"path","name":"drawerName","required":true,"dataType":"string"},
};
// WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa
let validatedArgs: any[] = [];
try {
validatedArgs = getValidatedArgs(args, request, response);
const controller = new DrawerController();
const promise = controller.deleteDrawer.apply(controller, validatedArgs as any);
promiseHandler(controller, promise, response, 204, next);
} catch (err) {
return next(err);
}
});
// WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa
app.get('/cabinet/:cabinetName/drawer/:drawerName/size',
authenticateMiddleware([{"bearerAuth":[]}]),
...(fetchMiddlewares<RequestHandler>(DrawerController)),
...(fetchMiddlewares<RequestHandler>(DrawerController.prototype.calc)),
function DrawerController_calc(request: any, response: any, next: any) {
const args = {
cabinetName: {"in":"path","name":"cabinetName","required":true,"dataType":"string"},
drawerName: {"in":"path","name":"drawerName","required":true,"dataType":"string"},
};
// WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa
let validatedArgs: any[] = [];
try {
validatedArgs = getValidatedArgs(args, request, response);
const controller = new DrawerController();
const promise = controller.calc.apply(controller, validatedArgs as any);
promiseHandler(controller, promise, response, 200, next);
} catch (err) {
return next(err);
}
});
// WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa
app.get('/cabinet/:cabinetName/drawer/:drawerName/folder/:folderName/file',
authenticateMiddleware([{"bearerAuth":[]}]),
...(fetchMiddlewares<RequestHandler>(FileController)),
...(fetchMiddlewares<RequestHandler>(FileController.prototype.getFile)),
function FileController_getFile(request: any, response: any, next: any) {
const args = {
cabinetName: {"in":"path","name":"cabinetName","required":true,"dataType":"string"},
drawerName: {"in":"path","name":"drawerName","required":true,"dataType":"string"},
folderName: {"in":"path","name":"folderName","required":true,"dataType":"string"},
};
// WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa
let validatedArgs: any[] = [];
try {
validatedArgs = getValidatedArgs(args, request, response);
const controller = new FileController();
const promise = controller.getFile.apply(controller, validatedArgs as any);
promiseHandler(controller, promise, response, 200, next);
} catch (err) {
return next(err);
}
});
// WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa
app.post('/cabinet/:cabinetName/drawer/:drawerName/folder/:folderName/file',
authenticateMiddleware([{"bearerAuth":["admin","management-role"]}]),
...(fetchMiddlewares<RequestHandler>(FileController)),
...(fetchMiddlewares<RequestHandler>(FileController.prototype.uploadFile)),
function FileController_uploadFile(request: any, response: any, next: any) {
const args = {
request: {"in":"request","name":"request","required":true,"dataType":"object"},
body: {"in":"body","name":"body","required":true,"dataType":"nestedObjectLiteral","nestedProperties":{"keyword":{"dataType":"array","array":{"dataType":"string"}},"category":{"dataType":"array","array":{"dataType":"string"}},"description":{"dataType":"string"},"title":{"dataType":"string"},"file":{"dataType":"string","required":true}}},
cabinetName: {"in":"path","name":"cabinetName","required":true,"dataType":"string"},
drawerName: {"in":"path","name":"drawerName","required":true,"dataType":"string"},
folderName: {"in":"path","name":"folderName","required":true,"dataType":"string"},
};
// WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa
let validatedArgs: any[] = [];
try {
validatedArgs = getValidatedArgs(args, request, response);
const controller = new FileController();
const promise = controller.uploadFile.apply(controller, validatedArgs as any);
promiseHandler(controller, promise, response, 201, next);
} catch (err) {
return next(err);
}
});
// WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa
app.patch('/cabinet/:cabinetName/drawer/:drawerName/folder/:folderName/file/:fileName',
authenticateMiddleware([{"bearerAuth":["admin","management-role"]}]),
...(fetchMiddlewares<RequestHandler>(FileController)),
...(fetchMiddlewares<RequestHandler>(FileController.prototype.updateFile)),
function FileController_updateFile(request: any, response: any, next: any) {
const args = {
request: {"in":"request","name":"request","required":true,"dataType":"object"},
cabinetName: {"in":"path","name":"cabinetName","required":true,"dataType":"string"},
drawerName: {"in":"path","name":"drawerName","required":true,"dataType":"string"},
folderName: {"in":"path","name":"folderName","required":true,"dataType":"string"},
fileName: {"in":"path","name":"fileName","required":true,"dataType":"string"},
body: {"in":"body","name":"body","required":true,"dataType":"nestedObjectLiteral","nestedProperties":{"keyword":{"dataType":"array","array":{"dataType":"string"}},"category":{"dataType":"array","array":{"dataType":"string"}},"description":{"dataType":"string"},"title":{"dataType":"string"},"file":{"dataType":"string"}}},
};
// WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa
let validatedArgs: any[] = [];
try {
validatedArgs = getValidatedArgs(args, request, response);
const controller = new FileController();
const promise = controller.updateFile.apply(controller, validatedArgs as any);
promiseHandler(controller, promise, response, 200, next);
} catch (err) {
return next(err);
}
});
// WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa
app.delete('/cabinet/:cabinetName/drawer/:drawerName/folder/:folderName/file/:fileName',
authenticateMiddleware([{"bearerAuth":["admin","management-role"]}]),
...(fetchMiddlewares<RequestHandler>(FileController)),
...(fetchMiddlewares<RequestHandler>(FileController.prototype.deleteFile)),
function FileController_deleteFile(request: any, response: any, next: any) {
const args = {
cabinetName: {"in":"path","name":"cabinetName","required":true,"dataType":"string"},
drawerName: {"in":"path","name":"drawerName","required":true,"dataType":"string"},
folderName: {"in":"path","name":"folderName","required":true,"dataType":"string"},
fileName: {"in":"path","name":"fileName","required":true,"dataType":"string"},
};
// WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa
let validatedArgs: any[] = [];
try {
validatedArgs = getValidatedArgs(args, request, response);
const controller = new FileController();
const promise = controller.deleteFile.apply(controller, validatedArgs as any);
promiseHandler(controller, promise, response, 200, next);
} catch (err) {
return next(err);
}
});
// WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa
app.get('/cabinet/:cabinetName/drawer/:drawerName/folder/:folderName/file/:fileName',
authenticateMiddleware([{"bearerAuth":[]}]),
...(fetchMiddlewares<RequestHandler>(FileController)),
...(fetchMiddlewares<RequestHandler>(FileController.prototype.downloadFile)),
function FileController_downloadFile(request: any, response: any, next: any) {
const args = {
cabinetName: {"in":"path","name":"cabinetName","required":true,"dataType":"string"},
drawerName: {"in":"path","name":"drawerName","required":true,"dataType":"string"},
folderName: {"in":"path","name":"folderName","required":true,"dataType":"string"},
fileName: {"in":"path","name":"fileName","required":true,"dataType":"string"},
};
// WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa
let validatedArgs: any[] = [];
try {
validatedArgs = getValidatedArgs(args, request, response);
const controller = new FileController();
const promise = controller.downloadFile.apply(controller, validatedArgs as any);
promiseHandler(controller, promise, response, 200, next);
} catch (err) {
return next(err);
}
});
// WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa
app.get('/cabinet/:cabinetName/drawer/:drawerName/folder',
authenticateMiddleware([{"bearerAuth":[]}]),
...(fetchMiddlewares<RequestHandler>(FolderController)),
...(fetchMiddlewares<RequestHandler>(FolderController.prototype.listFolder)),
function FolderController_listFolder(request: any, response: any, next: any) {
const args = {
cabinetName: {"in":"path","name":"cabinetName","required":true,"dataType":"string"},
drawerName: {"in":"path","name":"drawerName","required":true,"dataType":"string"},
};
// WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa
let validatedArgs: any[] = [];
try {
validatedArgs = getValidatedArgs(args, request, response);
const controller = new FolderController();
const promise = controller.listFolder.apply(controller, validatedArgs as any);
promiseHandler(controller, promise, response, 200, next);
} catch (err) {
return next(err);
}
});
// WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa
app.post('/cabinet/:cabinetName/drawer/:drawerName/folder',
authenticateMiddleware([{"bearerAuth":["admin","management-role"]}]),
...(fetchMiddlewares<RequestHandler>(FolderController)),
...(fetchMiddlewares<RequestHandler>(FolderController.prototype.createFolder)),
function FolderController_createFolder(request: any, response: any, next: any) {
const args = {
request: {"in":"request","name":"request","required":true,"dataType":"object"},
body: {"in":"body","name":"body","required":true,"dataType":"nestedObjectLiteral","nestedProperties":{"name":{"dataType":"string","required":true}}},
cabinetName: {"in":"path","name":"cabinetName","required":true,"dataType":"string"},
drawerName: {"in":"path","name":"drawerName","required":true,"dataType":"string"},
};
// WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa
let validatedArgs: any[] = [];
try {
validatedArgs = getValidatedArgs(args, request, response);
const controller = new FolderController();
const promise = controller.createFolder.apply(controller, validatedArgs as any);
promiseHandler(controller, promise, response, 201, next);
} catch (err) {
return next(err);
}
});
// WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa
app.put('/cabinet/:cabinetName/drawer/:drawerName/folder/:folderName',
authenticateMiddleware([{"bearerAuth":["admin","management-role"]}]),
...(fetchMiddlewares<RequestHandler>(FolderController)),
...(fetchMiddlewares<RequestHandler>(FolderController.prototype.editFolder)),
function FolderController_editFolder(request: any, response: any, next: any) {
const args = {
body: {"in":"body","name":"body","required":true,"dataType":"nestedObjectLiteral","nestedProperties":{"name":{"dataType":"string","required":true}}},
cabinetName: {"in":"path","name":"cabinetName","required":true,"dataType":"string"},
drawerName: {"in":"path","name":"drawerName","required":true,"dataType":"string"},
folderName: {"in":"path","name":"folderName","required":true,"dataType":"string"},
};
// WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa
let validatedArgs: any[] = [];
try {
validatedArgs = getValidatedArgs(args, request, response);
const controller = new FolderController();
const promise = controller.editFolder.apply(controller, validatedArgs as any);
promiseHandler(controller, promise, response, 204, next);
} catch (err) {
return next(err);
}
});
// WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa
app.delete('/cabinet/:cabinetName/drawer/:drawerName/folder/:folderName',
authenticateMiddleware([{"bearerAuth":["admin","management-role"]}]),
...(fetchMiddlewares<RequestHandler>(FolderController)),
...(fetchMiddlewares<RequestHandler>(FolderController.prototype.deleteFolder)),
function FolderController_deleteFolder(request: any, response: any, next: any) {
const args = {
cabinetName: {"in":"path","name":"cabinetName","required":true,"dataType":"string"},
drawerName: {"in":"path","name":"drawerName","required":true,"dataType":"string"},
folderName: {"in":"path","name":"folderName","required":true,"dataType":"string"},
};
// WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa
let validatedArgs: any[] = [];
try {
validatedArgs = getValidatedArgs(args, request, response);
const controller = new FolderController();
const promise = controller.deleteFolder.apply(controller, validatedArgs as any);
promiseHandler(controller, promise, response, 204, next);
} catch (err) {
return next(err);
}
});
// WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa
app.get('/cabinet/:cabinetName/drawer/:drawerName/folder/:folderName/size',
authenticateMiddleware([{"bearerAuth":[]}]),
...(fetchMiddlewares<RequestHandler>(FolderController)),
...(fetchMiddlewares<RequestHandler>(FolderController.prototype.calc)),
function FolderController_calc(request: any, response: any, next: any) {
const args = {
cabinetName: {"in":"path","name":"cabinetName","required":true,"dataType":"string"},
drawerName: {"in":"path","name":"drawerName","required":true,"dataType":"string"},
folderName: {"in":"path","name":"folderName","required":true,"dataType":"string"},
};
// WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa
let validatedArgs: any[] = [];
try {
validatedArgs = getValidatedArgs(args, request, response);
const controller = new FolderController();
const promise = controller.calc.apply(controller, validatedArgs as any);
promiseHandler(controller, promise, response, 200, next);
} catch (err) {
return next(err);
}
});
// WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa
app.post('/search',
authenticateMiddleware([{"bearerAuth":[]}]),
...(fetchMiddlewares<RequestHandler>(SearchController)),
@ -765,7 +198,7 @@ export function RegisterRoutes(app: Router) {
const promise = controller.getList.apply(controller, validatedArgs as any);
promiseHandler(controller, promise, response, undefined, next);
promiseHandler(controller, promise, response, 200, next);
} catch (err) {
return next(err);
}
@ -844,7 +277,7 @@ export function RegisterRoutes(app: Router) {
const promise = controller.folderSize.apply(controller, validatedArgs as any);
promiseHandler(controller, promise, response, undefined, next);
promiseHandler(controller, promise, response, 200, next);
} catch (err) {
return next(err);
}
@ -975,304 +408,6 @@ export function RegisterRoutes(app: Router) {
const controller = new StorageController();
const promise = controller.downloadFile.apply(controller, validatedArgs as any);
promiseHandler(controller, promise, response, 200, next);
} catch (err) {
return next(err);
}
});
// WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa
app.get('/cabinet/:cabinetName/drawer/:drawerName/folder/:folderName/subfolder',
authenticateMiddleware([{"bearerAuth":[]}]),
...(fetchMiddlewares<RequestHandler>(SubFolderController)),
...(fetchMiddlewares<RequestHandler>(SubFolderController.prototype.listFolder)),
function SubFolderController_listFolder(request: any, response: any, next: any) {
const args = {
cabinetName: {"in":"path","name":"cabinetName","required":true,"dataType":"string"},
drawerName: {"in":"path","name":"drawerName","required":true,"dataType":"string"},
folderName: {"in":"path","name":"folderName","required":true,"dataType":"string"},
};
// WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa
let validatedArgs: any[] = [];
try {
validatedArgs = getValidatedArgs(args, request, response);
const controller = new SubFolderController();
const promise = controller.listFolder.apply(controller, validatedArgs as any);
promiseHandler(controller, promise, response, 200, next);
} catch (err) {
return next(err);
}
});
// WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa
app.post('/cabinet/:cabinetName/drawer/:drawerName/folder/:folderName/subfolder',
authenticateMiddleware([{"bearerAuth":["admin","management-role"]}]),
...(fetchMiddlewares<RequestHandler>(SubFolderController)),
...(fetchMiddlewares<RequestHandler>(SubFolderController.prototype.createFolder)),
function SubFolderController_createFolder(request: any, response: any, next: any) {
const args = {
request: {"in":"request","name":"request","required":true,"dataType":"object"},
body: {"in":"body","name":"body","required":true,"dataType":"nestedObjectLiteral","nestedProperties":{"name":{"dataType":"string","required":true}}},
cabinetName: {"in":"path","name":"cabinetName","required":true,"dataType":"string"},
drawerName: {"in":"path","name":"drawerName","required":true,"dataType":"string"},
folderName: {"in":"path","name":"folderName","required":true,"dataType":"string"},
};
// WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa
let validatedArgs: any[] = [];
try {
validatedArgs = getValidatedArgs(args, request, response);
const controller = new SubFolderController();
const promise = controller.createFolder.apply(controller, validatedArgs as any);
promiseHandler(controller, promise, response, 201, next);
} catch (err) {
return next(err);
}
});
// WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa
app.put('/cabinet/:cabinetName/drawer/:drawerName/folder/:folderName/subfolder/:subFolderName',
authenticateMiddleware([{"bearerAuth":["admin","management-role"]}]),
...(fetchMiddlewares<RequestHandler>(SubFolderController)),
...(fetchMiddlewares<RequestHandler>(SubFolderController.prototype.editFolder)),
function SubFolderController_editFolder(request: any, response: any, next: any) {
const args = {
body: {"in":"body","name":"body","required":true,"dataType":"nestedObjectLiteral","nestedProperties":{"name":{"dataType":"string","required":true}}},
cabinetName: {"in":"path","name":"cabinetName","required":true,"dataType":"string"},
drawerName: {"in":"path","name":"drawerName","required":true,"dataType":"string"},
folderName: {"in":"path","name":"folderName","required":true,"dataType":"string"},
subFolderName: {"in":"path","name":"subFolderName","required":true,"dataType":"string"},
};
// WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa
let validatedArgs: any[] = [];
try {
validatedArgs = getValidatedArgs(args, request, response);
const controller = new SubFolderController();
const promise = controller.editFolder.apply(controller, validatedArgs as any);
promiseHandler(controller, promise, response, 204, next);
} catch (err) {
return next(err);
}
});
// WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa
app.delete('/cabinet/:cabinetName/drawer/:drawerName/folder/:folderName/subfolder/:subFolderName',
authenticateMiddleware([{"bearerAuth":["admin","management-role"]}]),
...(fetchMiddlewares<RequestHandler>(SubFolderController)),
...(fetchMiddlewares<RequestHandler>(SubFolderController.prototype.deleteFolder)),
function SubFolderController_deleteFolder(request: any, response: any, next: any) {
const args = {
cabinetName: {"in":"path","name":"cabinetName","required":true,"dataType":"string"},
drawerName: {"in":"path","name":"drawerName","required":true,"dataType":"string"},
folderName: {"in":"path","name":"folderName","required":true,"dataType":"string"},
subFolderName: {"in":"path","name":"subFolderName","required":true,"dataType":"string"},
};
// WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa
let validatedArgs: any[] = [];
try {
validatedArgs = getValidatedArgs(args, request, response);
const controller = new SubFolderController();
const promise = controller.deleteFolder.apply(controller, validatedArgs as any);
promiseHandler(controller, promise, response, 204, next);
} catch (err) {
return next(err);
}
});
// WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa
app.get('/cabinet/:cabinetName/drawer/:drawerName/folder/:folderName/subfolder/:subFolderName/size',
authenticateMiddleware([{"bearerAuth":[]}]),
...(fetchMiddlewares<RequestHandler>(SubFolderController)),
...(fetchMiddlewares<RequestHandler>(SubFolderController.prototype.calc)),
function SubFolderController_calc(request: any, response: any, next: any) {
const args = {
cabinetName: {"in":"path","name":"cabinetName","required":true,"dataType":"string"},
drawerName: {"in":"path","name":"drawerName","required":true,"dataType":"string"},
folderName: {"in":"path","name":"folderName","required":true,"dataType":"string"},
subFolderName: {"in":"path","name":"subFolderName","required":true,"dataType":"string"},
};
// WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa
let validatedArgs: any[] = [];
try {
validatedArgs = getValidatedArgs(args, request, response);
const controller = new SubFolderController();
const promise = controller.calc.apply(controller, validatedArgs as any);
promiseHandler(controller, promise, response, 200, next);
} catch (err) {
return next(err);
}
});
// WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa
app.get('/cabinet/:cabinetName/drawer/:drawerName/folder/:folderName/subfolder/:subFolderName/file',
authenticateMiddleware([{"bearerAuth":[]}]),
...(fetchMiddlewares<RequestHandler>(SubFolderFileController)),
...(fetchMiddlewares<RequestHandler>(SubFolderFileController.prototype.getFile)),
function SubFolderFileController_getFile(request: any, response: any, next: any) {
const args = {
cabinetName: {"in":"path","name":"cabinetName","required":true,"dataType":"string"},
drawerName: {"in":"path","name":"drawerName","required":true,"dataType":"string"},
folderName: {"in":"path","name":"folderName","required":true,"dataType":"string"},
subFolderName: {"in":"path","name":"subFolderName","required":true,"dataType":"string"},
};
// WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa
let validatedArgs: any[] = [];
try {
validatedArgs = getValidatedArgs(args, request, response);
const controller = new SubFolderFileController();
const promise = controller.getFile.apply(controller, validatedArgs as any);
promiseHandler(controller, promise, response, 200, next);
} catch (err) {
return next(err);
}
});
// WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa
app.post('/cabinet/:cabinetName/drawer/:drawerName/folder/:folderName/subfolder/:subFolderName/file',
authenticateMiddleware([{"bearerAuth":["admin","management-role"]}]),
...(fetchMiddlewares<RequestHandler>(SubFolderFileController)),
...(fetchMiddlewares<RequestHandler>(SubFolderFileController.prototype.uploadFile)),
function SubFolderFileController_uploadFile(request: any, response: any, next: any) {
const args = {
request: {"in":"request","name":"request","required":true,"dataType":"object"},
body: {"in":"body","name":"body","required":true,"dataType":"nestedObjectLiteral","nestedProperties":{"keyword":{"dataType":"array","array":{"dataType":"string"}},"category":{"dataType":"array","array":{"dataType":"string"}},"description":{"dataType":"string"},"title":{"dataType":"string"},"file":{"dataType":"string","required":true}}},
cabinetName: {"in":"path","name":"cabinetName","required":true,"dataType":"string"},
drawerName: {"in":"path","name":"drawerName","required":true,"dataType":"string"},
folderName: {"in":"path","name":"folderName","required":true,"dataType":"string"},
subFolderName: {"in":"path","name":"subFolderName","required":true,"dataType":"string"},
};
// WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa
let validatedArgs: any[] = [];
try {
validatedArgs = getValidatedArgs(args, request, response);
const controller = new SubFolderFileController();
const promise = controller.uploadFile.apply(controller, validatedArgs as any);
promiseHandler(controller, promise, response, 201, next);
} catch (err) {
return next(err);
}
});
// WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa
app.patch('/cabinet/:cabinetName/drawer/:drawerName/folder/:folderName/subfolder/:subFolderName/file/:fileName',
authenticateMiddleware([{"bearerAuth":["admin","management-role"]}]),
...(fetchMiddlewares<RequestHandler>(SubFolderFileController)),
...(fetchMiddlewares<RequestHandler>(SubFolderFileController.prototype.updateFile)),
function SubFolderFileController_updateFile(request: any, response: any, next: any) {
const args = {
request: {"in":"request","name":"request","required":true,"dataType":"object"},
cabinetName: {"in":"path","name":"cabinetName","required":true,"dataType":"string"},
drawerName: {"in":"path","name":"drawerName","required":true,"dataType":"string"},
folderName: {"in":"path","name":"folderName","required":true,"dataType":"string"},
subFolderName: {"in":"path","name":"subFolderName","required":true,"dataType":"string"},
fileName: {"in":"path","name":"fileName","required":true,"dataType":"string"},
body: {"in":"body","name":"body","required":true,"dataType":"nestedObjectLiteral","nestedProperties":{"keyword":{"dataType":"array","array":{"dataType":"string"}},"category":{"dataType":"array","array":{"dataType":"string"}},"description":{"dataType":"string"},"title":{"dataType":"string"},"file":{"dataType":"string"}}},
};
// WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa
let validatedArgs: any[] = [];
try {
validatedArgs = getValidatedArgs(args, request, response);
const controller = new SubFolderFileController();
const promise = controller.updateFile.apply(controller, validatedArgs as any);
promiseHandler(controller, promise, response, 200, next);
} catch (err) {
return next(err);
}
});
// WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa
app.delete('/cabinet/:cabinetName/drawer/:drawerName/folder/:folderName/subfolder/:subFolderName/file/:fileName',
authenticateMiddleware([{"bearerAuth":["admin","management-role"]}]),
...(fetchMiddlewares<RequestHandler>(SubFolderFileController)),
...(fetchMiddlewares<RequestHandler>(SubFolderFileController.prototype.deleteFile)),
function SubFolderFileController_deleteFile(request: any, response: any, next: any) {
const args = {
cabinetName: {"in":"path","name":"cabinetName","required":true,"dataType":"string"},
drawerName: {"in":"path","name":"drawerName","required":true,"dataType":"string"},
folderName: {"in":"path","name":"folderName","required":true,"dataType":"string"},
subFolderName: {"in":"path","name":"subFolderName","required":true,"dataType":"string"},
fileName: {"in":"path","name":"fileName","required":true,"dataType":"string"},
};
// WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa
let validatedArgs: any[] = [];
try {
validatedArgs = getValidatedArgs(args, request, response);
const controller = new SubFolderFileController();
const promise = controller.deleteFile.apply(controller, validatedArgs as any);
promiseHandler(controller, promise, response, 200, next);
} catch (err) {
return next(err);
}
});
// WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa
app.get('/cabinet/:cabinetName/drawer/:drawerName/folder/:folderName/subfolder/:subFolderName/file/:fileName',
authenticateMiddleware([{"bearerAuth":[]}]),
...(fetchMiddlewares<RequestHandler>(SubFolderFileController)),
...(fetchMiddlewares<RequestHandler>(SubFolderFileController.prototype.downloadFile)),
function SubFolderFileController_downloadFile(request: any, response: any, next: any) {
const args = {
cabinetName: {"in":"path","name":"cabinetName","required":true,"dataType":"string"},
drawerName: {"in":"path","name":"drawerName","required":true,"dataType":"string"},
folderName: {"in":"path","name":"folderName","required":true,"dataType":"string"},
subFolderName: {"in":"path","name":"subFolderName","required":true,"dataType":"string"},
fileName: {"in":"path","name":"fileName","required":true,"dataType":"string"},
};
// WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa
let validatedArgs: any[] = [];
try {
validatedArgs = getValidatedArgs(args, request, response);
const controller = new SubFolderFileController();
const promise = controller.downloadFile.apply(controller, validatedArgs as any);
promiseHandler(controller, promise, response, 200, next);
} catch (err) {

File diff suppressed because it is too large Load diff

View file

@ -25,16 +25,7 @@
"description": "Keycloak Bearer Token",
"in": "header"
}
},
"tags": [
{ "name": "ตู้เอกสาร" },
{ "name": "ลิ้นชัก" },
{ "name": "แฟ้ม" },
{ "name": "แฟ้มย่อย" },
{ "name": "ไฟล์" },
{ "name": "ดาวน์โหลด" },
{ "name": "ค้นหา" }
]
}
},
"routes": {
"routesDir": "src",