Merge branch 'development'

This commit is contained in:
Methapon2001 2024-02-09 09:39:14 +07:00
commit 5e74817f59
No known key found for this signature in database
GPG key ID: 849924FEF46BD132
16 changed files with 475 additions and 199 deletions

View file

@ -10,9 +10,18 @@ const props = withDefaults(
defineProps<{
open: boolean
error: {
externalFileExist?: boolean
fileExist?: boolean
fileName2Long?: boolean
}
disableFields?: (
| 'file'
| 'title'
| 'description'
| 'keyword'
| 'category'
| 'author'
)[]
mode: 'create' | 'edit'
fileNameLabel?: string
title?: string
@ -63,6 +72,25 @@ function reset() {
}
function submit() {
if (props.mode === 'edit') {
return emit('submit', {
mode: props.mode,
file: props.disableFields?.includes('file') ? undefined : file.value,
title: props.disableFields?.includes('title') ? undefined : props.title,
description: props.disableFields?.includes('description')
? undefined
: props.description ?? '',
keyword: props.disableFields?.includes('keyword')
? undefined
: props.keyword,
category: props.disableFields?.includes('category')
? undefined
: props.category,
author: props.disableFields?.includes('author')
? undefined
: props.author,
})
}
emit('submit', {
mode: props.mode,
file: file.value,
@ -146,12 +174,13 @@ const file = ref<File | undefined>()
/>
</q-toolbar>
<section class="q-mb-md">
<section class="q-pb-xs">
<span class="text-weight-bold q-mb-sm block">ปโหลดไฟล</span>
<q-file
dense
outlined
v-model="file"
:disable="disableFields?.includes('file')"
@update:model-value="(v) => $emit('filechange', v.name)"
:label="
mode === 'edit' && fileNameLabel && !file?.name
@ -160,13 +189,19 @@ const file = ref<File | undefined>()
? undefined
: 'เลือกไฟล์'
"
:error="!!error.fileExist || !!error.fileName2Long"
:error="
!!error.externalFileExist ||
!!error.fileExist ||
!!error.fileName2Long
"
:error-message="
error.fileExist
? 'พบไฟล์ชื่อซ้ำในระบบ ไฟล์ชื่อนี้ภายในระบบจะถูกเขียนทับ'
: error.fileName2Long
? 'ไม่สามารถเพิ่มไฟล์ที่ชื่อยาวเกิน 85 ตัวอักษรได้'
: ''
error.externalFileExist
? 'พบไฟล์ชื่อซ้ำในระบบอื่น'
: error.fileExist
? 'พบไฟล์ชื่อซ้ำในระบบ ไฟล์ชื่อนี้ภายในระบบจะถูกเขียนทับ'
: error.fileName2Long
? 'ไม่สามารถเพิ่มไฟล์ที่ชื่อยาวเกิน 85 ตัวอักษรได้'
: ''
"
id="inputFile"
:rules="[
@ -179,13 +214,14 @@ const file = ref<File | undefined>()
</q-file>
</section>
<section class="q-mb-md">
<section class="q-pb-xs">
<span class="text-weight-bold">อเรอง</span>
<q-input
outlined
dense
class="q-my-sm"
class="q-mt-sm"
placeholder="กรอกชื่อเรื่อง"
:disable="disableFields?.includes('title')"
:model-value="title"
:rules="[
(v) =>
@ -196,7 +232,7 @@ const file = ref<File | undefined>()
/>
</section>
<section class="q-mb-md">
<section class="q-pb-lg">
<span class="text-weight-bold">รายละเอยดของเอกสาร</span>
<q-input
outlined
@ -204,63 +240,21 @@ const file = ref<File | undefined>()
class="q-mt-sm no-resize"
type="textarea"
placeholder="กรอกรายละเอียด"
:disable="disableFields?.includes('description')"
:model-value="description"
@update:model-value="(v) => $emit('update:description', v)"
id="inputDescription"
/>
</section>
<section class="q-mb-md">
<span class="text-weight-bold">กล/หมวดหม</span>
<div class="q-mt-md">
<q-select
outlined
dense
:model-value="category"
label="กดปุ่มEnterเพื่อเพิ่ม"
use-input
use-chips
hide-dropdown-icon
multiple
input-debounce="0"
@new-value="createCategory"
:options="filterDataCategory"
@filter="filterCategory"
@update:model-value="(v) => $emit('update:category', v)"
data-testid="filterDataCategory"
>
</q-select>
</div>
</section>
<section class="q-mb-md">
<span class="text-weight-bold">คำสำค</span>
<div class="q-mt-md">
<q-select
outlined
dense
label="กดปุ่มEnterเพื่อเพิ่ม"
use-input
use-chips
multiple
hide-dropdown-icon
input-debounce="0"
:model-value="props.keyword"
@update:model-value="(v) => $emit('update:keyword', v)"
@new-value="createKeyword"
data-testid="filterDataKeyWord"
>
</q-select>
</div>
</section>
<section class="q-mb-md">
<section class="q-pb-xs">
<span class="text-weight-bold">เจาของผลงาน</span>
<q-input
outlined
dense
class="q-my-sm"
class="q-mt-sm"
placeholder="เจ้าของผลงาน"
:disable="disableFields?.includes('author')"
:model-value="author"
:rules="[
(v) =>
@ -271,6 +265,50 @@ const file = ref<File | undefined>()
/>
</section>
<section class="q-pb-md">
<span class="text-weight-bold">กล/หมวดหม</span>
<q-select
outlined
dense
:model-value="category"
label="กดปุ่มEnterเพื่อเพิ่ม"
use-input
use-chips
hide-dropdown-icon
multiple
class="q-my-sm"
input-debounce="0"
@new-value="createCategory"
:disable="disableFields?.includes('category')"
:options="filterDataCategory"
@filter="filterCategory"
@update:model-value="(v) => $emit('update:category', v)"
data-testid="filterDataCategory"
>
</q-select>
</section>
<section class="q-pb-lg">
<span class="text-weight-bold">คำสำค</span>
<q-select
outlined
dense
label="กดปุ่มEnterเพื่อเพิ่ม"
use-input
use-chips
multiple
hide-dropdown-icon
class="q-my-sm"
input-debounce="0"
:disable="disableFields?.includes('keyword')"
:model-value="props.keyword"
@update:model-value="(v) => $emit('update:keyword', v)"
@new-value="createKeyword"
data-testid="filterDataKeyWord"
>
</q-select>
</section>
<section :style="{ display: 'flex', gap: '.5rem' }">
<q-btn
label="บันทึก"

View file

@ -7,7 +7,7 @@ import UploadExistDialog from './UploadExistDialog.vue'
const storageStore = useStorage()
const { file, currentInfo } = storeToRefs(storageStore)
const { createFile, updateFile } = storageStore
const { createFile, updateFile, goto } = storageStore
const fileFormState = ref<boolean>(false)
const fileFormPath = ref<string>('')
@ -18,10 +18,16 @@ const fileFormData = ref<{
keyword?: string[]
category?: string[]
author?: string
metadata?: Record<string, unknown>
}>({})
const fileFormType = ref<'edit' | 'create'>('create')
const fileFormError = ref<{ fileExist?: boolean; fileName2Long?: boolean }>({})
const fileFormError = ref<{
externalFileExist?: boolean
fileExist?: boolean
fileName2Long?: boolean
}>({})
const fileExistNotification = ref<boolean>(false)
const errorState = ref<'fileExist' | 'externalFileExist'>('fileExist')
const fileFormComponent = ref<InstanceType<typeof FileForm>>()
const fileNameLabel = ref<string>()
@ -38,6 +44,7 @@ function triggerFileEdit(
keyword: string[]
category: string[]
author: string
metadata?: Record<string, unknown>
},
pathname: string,
file?: string,
@ -46,13 +53,18 @@ function triggerFileEdit(
fileFormType.value = 'edit'
fileFormPath.value = pathname
fileFormData.value = {
title: value.title,
title: (value.metadata?.subject as string) ?? value.title,
description: value.description,
keyword: value.keyword,
category: value.category,
author: value.author,
author: (value.metadata?.author as string) ?? value.author,
metadata: value.metadata,
}
fileNameLabel.value = file
const arr = pathname.split('/')
arr[arr.length - 1] = ''
goto(arr.join('/'))
}
defineExpose({
@ -62,6 +74,18 @@ defineExpose({
const currentParam = ref<Parameters<typeof submitFileForm>[0]>()
function checkFileExistMetadata(name: string) {
const fileInfo = file.value[currentInfo.value.path].find(
(v) => v.fileName === name,
)
if (fileInfo?.metadata && Object.keys(fileInfo?.metadata).length > 0) {
return Boolean(true)
}
return Boolean(false)
}
function checkFileExist(name: string, except?: string) {
return Boolean(
file.value[currentInfo.value.path].find(
@ -88,6 +112,16 @@ async function submitFileForm(
) {
currentParam.value = value
const fileInfo = file.value[currentInfo.value.path]?.find(
(v) => v.fileName === value.file?.name,
)
if (fileInfo?.metadata && Object.keys(fileInfo.metadata).length > 0) {
fileExistNotification.value = true
errorState.value = 'externalFileExist'
return
}
if (
value.file &&
file.value[currentInfo.value.path].find(
@ -96,6 +130,7 @@ async function submitFileForm(
!force
) {
fileExistNotification.value = true
errorState.value = 'fileExist'
return
}
@ -133,6 +168,11 @@ async function submitFileForm(
:mode="fileFormType"
:error="fileFormError"
:fileNameLabel="fileNameLabel"
:disableFields="
fileFormData.metadata && Object.keys(fileFormData.metadata).length > 0
? ['file', 'title', 'author']
: []
"
v-model:open="fileFormState"
v-model:title="fileFormData.title"
v-model:description="fileFormData.description"
@ -142,6 +182,7 @@ async function submitFileForm(
@reset="() => (fileFormError = {})"
@filechange="
(name: string) => {
fileFormError.externalFileExist = checkFileExistMetadata(name)
;(fileFormError.fileExist = checkFileExist(name, fileNameLabel)),
(fileFormError.fileName2Long = checkFileName2Long(
name,
@ -153,6 +194,7 @@ async function submitFileForm(
/>
<upload-exist-dialog
v-model:notification="fileExistNotification"
v-model:errorState="errorState"
@confirm="() => currentParam && submitFileForm(currentParam, true)"
@cancel="() => (currentParam = undefined)"
/>

View file

@ -117,6 +117,7 @@ function triggerFileDelete(pathname: string) {
>
<file-item-action
:nameId="value.pathname"
:have-meta="false"
@delete="() => triggerFolderDelete(value.pathname)"
@edit="
() =>
@ -238,6 +239,9 @@ function triggerFileDelete(pathname: string) {
>
<file-item-action
:nameId="value.pathname"
:have-meta="
!!(value.metadata && Object.keys(value.metadata).length > 0)
"
@edit="
() =>
fileFormComponent?.triggerFileEdit(
@ -247,6 +251,7 @@ function triggerFileDelete(pathname: string) {
keyword: value.keyword,
category: value.category,
author: value.author,
metadata: value.metadata,
},
value.pathname,
value.fileName,

View file

@ -5,6 +5,7 @@ defineEmits(['edit', 'delete'])
const props = defineProps<{
nameId: string
haveMeta: boolean
}>()
const open = ref(false)
@ -39,6 +40,7 @@ const open = ref(false)
</q-item-section>
</q-item>
<q-item
v-if="!haveMeta"
clickable
@click.stop="
() => {

View file

@ -226,6 +226,9 @@ onMounted(() => {
>
<file-item-action
:nameId="value.pathname"
:have-meta="
!!(value.metadata && Object.keys(value.metadata).length > 0)
"
@edit="
() =>
fileFormComponent?.triggerFileEdit(
@ -235,6 +238,7 @@ onMounted(() => {
keyword: value.keyword,
category: value.category,
author: value.author,
metadata: value.metadata,
},
value.pathname,
value.fileName,
@ -353,8 +357,21 @@ onMounted(() => {
dense
icon="mdi-trash-can-outline"
@click="() => triggerFileDelete(actionData.row.pathname)"
v-if="
!(
actionData.row.metadata &&
Object.keys(actionData.row.metadata).length > 0
)
"
id="listViewFileDelete"
/>
<div
style="width: 64px"
v-if="
actionData.row.metadata &&
Object.keys(actionData.row.metadata).length > 0
"
></div>
</div>
</q-td>
</template>

View file

@ -379,7 +379,7 @@ const onRowClick = ((_, row) => {
fileFormComponent?.triggerFileEdit(
data.row,
data.row.pathname,
data.row.filename,
data.row.fileName,
)
"
id="listViewFileEdit"
@ -391,8 +391,20 @@ const onRowClick = ((_, row) => {
color="negative"
icon="mdi-trash-can-outline"
@click.stop="() => triggerFileDelete(data.row.pathname)"
v-if="
!(
data.row.metadata &&
Object.keys(data.row.metadata).length > 0
)
"
data-testid="listViewFileDelete"
/>
<div
style="width: 64px"
v-if="
data.row.metadata && Object.keys(data.row.metadata).length > 0
"
></div>
</div>
</q-td>
</template>

View file

@ -1,6 +1,7 @@
<script lang="ts" setup>
defineProps<{
notification: boolean
errorState: 'fileExist' | 'externalFileExist'
}>()
defineEmits(['update:notification', 'confirm', 'cancel'])
@ -22,11 +23,19 @@ defineEmits(['update:notification', 'confirm', 'cancel'])
</div>
</div>
<div>
<h6 class="q-my-none">นยนการเพมขอม</h6>
<h6 class="q-my-none">
{{
errorState === 'externalFileExist'
? 'พบไฟล์ชื่อช้ำในระบบอื่น'
: 'ยืนยันการเพิ่มข้อมูล'
}}
</h6>
<p class="q-my-none">
พบไฟลอซำในระบบ หากดำเนนการต
ไฟลอยจะถกแทนทวยไฟลใหม
องการยนยนการอปโหลดไฟลหรอไม
{{
errorState === 'externalFileExist'
? 'พบไฟล์ชื่อช้ำในระบบอื่น จะไม่สามารถอัปโหลดไฟล์นี้ได้'
: 'ไฟล์ที่มีอยู่จะถูกแทนที่ด้วยไฟล์ใหม่ต้องการยืนยันการอัปโหลดไฟล์นี้หรือไม่'
}}
</p>
</div>
</div>
@ -44,7 +53,12 @@ defineEmits(['update:notification', 'confirm', 'cancel'])
label="ดำเนินการต่อ"
v-close-popup
color="warning"
@click="() => $emit('confirm')"
@click="
() =>
errorState === 'externalFileExist'
? $emit('cancel')
: $emit('confirm')
"
/>
</q-card-actions>
</q-card>

View file

@ -132,7 +132,9 @@ async function downloadSubmit(path: string | undefined) {
<span>อเรอง</span>
</div>
<div class="col-grow">
<span class="text-grey">{{ fileInfo?.metadata.subject ?? fileInfo?.title }}</span>
<span class="text-grey">{{
fileInfo?.metadata?.subject ?? fileInfo?.title
}}</span>
</div>
</div>
<q-separator />
@ -150,7 +152,9 @@ async function downloadSubmit(path: string | undefined) {
<span>เจาของผลงาน</span>
</div>
<div class="col-grow">
<span class="text-grey">{{ fileInfo?.metadata.author ?? fileInfo?.author }}</span>
<span class="text-grey">{{
fileInfo?.metadata?.author ?? fileInfo?.author
}}</span>
</div>
</div>
<q-separator />

View file

@ -12,6 +12,21 @@ import { useFileInfoStore } from '@/stores/file-info-data'
import AdvancedSearch from '@/modules/01_user/components/AdvancedSearch.vue'
import useStorage from '@/stores/storage'
interface SearchInfo {
field: string
value: string
exact?: boolean
}
interface SearchOperator {
AND?: (SearchInfo | SearchOperator)[]
OR?: (SearchInfo | SearchOperator)[]
}
interface Search extends SearchOperator {
recursive?: boolean
path?: string[]
exact?: boolean
}
const loaderStore = useLoader()
const storageStore = useStorage()
const { currentInfo } = storeToRefs(storageStore)
@ -26,21 +41,12 @@ const {
advSearchDataRow,
} = storeToRefs(useSearchDataStore())
const { getFoundFile } = useSearchDataStore()
const submitSearchData = ref<{
AND: {
field: string
value: string
exact?: boolean
}[]
OR: {
field: string
value: string
exact?: boolean
}[]
}>({
const submitSearchData = ref<Search>({
AND: [],
OR: [],
})
const props = defineProps<{
mode: 'admin' | 'user'
}>()
@ -50,55 +56,50 @@ async function submitSearch() {
if (searchData.value.value.trim() !== '') {
submitSearchData.value = { AND: [], OR: [] }
if (props.mode === 'admin') {
submitSearchData.value.exact = true
submitSearchData.value.recursive = true
submitSearchData.value.path = currentInfo.value.path
.split('/')
.filter(Boolean)
optionsField.value.forEach((option) => {
submitSearchData.value.OR.push({
submitSearchData.value.OR?.push({
field: option.value,
value: searchData.value.value,
exact: true,
})
})
submitSearchData.value.OR.push({
submitSearchData.value.OR?.push({
field: 'fileName',
value: searchData.value.value,
exact: true,
})
submitSearchData.value.OR.push({
submitSearchData.value.OR?.push({
field: 'metadata.author',
value: searchData.value.value,
exact: true,
})
submitSearchData.value.OR.push({
submitSearchData.value.OR?.push({
field: 'metadata.subject',
value: searchData.value.value,
exact: true,
})
submitSearchData.value.OR.push({
submitSearchData.value.OR?.push({
field: 'fileType',
value: mime.getType(searchData.value.value) || '',
exact: true,
})
if (currentInfo.value.path !== '/') {
submitSearchData.value.AND.push({
field: 'path',
value: currentInfo.value.path,
})
}
} else {
submitSearchData.value.recursive = true
submitSearchData.value.path = []
if (searchData.value.field == 'title') {
submitSearchData.value.OR.push({
submitSearchData.value.OR?.push({
field: 'metadata.subject',
value: searchData.value.value,
exact: isExact.value,
})
}
if (searchData.value.field == 'author') {
submitSearchData.value.OR.push({
submitSearchData.value.OR?.push({
field: 'metadata.author',
value: searchData.value.value,
exact: isExact.value,
})
}
submitSearchData.value.OR.push({
submitSearchData.value.OR?.push({
field: searchData.value.field,
value: searchData.value.value,
exact: isExact.value,
@ -111,31 +112,56 @@ async function submitSearch() {
(d: { field: string; value: string; op: string; exact: boolean }) => {
if (d.field && d.value.trim() !== '') {
const op = d.op === 'AND' ? 'AND' : 'OR'
if (d.field == 'title') {
submitSearchData.value[op].push({
field: 'metadata.subject',
if (
(op === 'AND' && d.field === 'title') ||
d.field === 'author'
) {
console.log('and title author')
if (d.field === 'title') {
submitSearchData.value[op]?.push({
OR: [
{
field: d.field,
value: d.value,
exact: d.exact,
},
{
field: 'metadata.subject',
value: d.value,
exact: d.exact,
},
],
})
}
if (d.field === 'author') {
submitSearchData.value[op]?.push({
OR: [
{
field: d.field,
value: d.value,
exact: d.exact,
},
{
field: 'metadata.author',
value: d.value,
exact: d.exact,
},
],
})
}
} else {
submitSearchData.value[op]?.push({
field: d.field,
value: d.value,
exact: d.exact,
})
}
if (d.field == 'author') {
submitSearchData.value[op].push({
field: 'metadata.author',
value: d.value,
exact: d.exact,
})
}
submitSearchData.value[op].push({
field: d.field,
value: d.value,
exact: d.exact,
})
}
},
)
if (advField.keyword.length > 0) {
for (let i = 0; i < advField.keyword.length; i++) {
submitSearchData.value.AND.push({
submitSearchData.value.AND?.push({
field: 'keyword',
value: advField.keyword[i],
exact: true,
@ -143,7 +169,7 @@ async function submitSearch() {
}
}
if (advField.description.trim() !== '') {
submitSearchData.value.AND.push({
submitSearchData.value.AND?.push({
field: 'description',
value: advField.description,
exact: true,

View file

@ -69,7 +69,7 @@ export const useFileInfoStore = defineStore('info', () => {
if (extension) return extension
if (fileName && fileName.includes('.')) {
return fileName.substring(fileName.lastIndexOf('.'))
return fileName.substring(fileName.lastIndexOf('.') + 1)
}
return 'ไม่ทราบประเภท'

View file

@ -27,7 +27,7 @@ export interface StorageFile {
title: string
description: string
author: string
metadata: Record<string, unknown>
metadata?: Record<string, unknown>
category: string[]
keyword: string[]
updatedAt: string

View file

@ -1,8 +1,10 @@
import { Body, Controller, Post, Route, Security, SuccessResponse, Tags } from "tsoa";
import HttpStatusCode from "../interfaces/http-status";
import esClient from "../elasticsearch";
import { Search } from "../interfaces/search";
import { Search, SearchInfo, SearchOperator, SearchOptions } from "../interfaces/search";
import { StorageFile } from "../interfaces/storage-fs";
import { QueryDslQueryContainer } from "@elastic/elasticsearch/lib/api/types";
import HttpError from "../interfaces/http-error";
const DEFAULT_INDEX = process.env.ELASTICSEARCH_INDEX;
@ -14,22 +16,57 @@ export class SearchController extends Controller {
@Tags("Search")
@Security("bearerAuth")
@SuccessResponse(HttpStatusCode.OK, "สำเร็จ")
public async searchFile(@Body() search: Search): Promise<StorageFile[]> {
public async searchFile(
@Body()
body: {
AND?: (
| SearchInfo
| {
AND?: unknown;
OR?: unknown;
}
)[];
OR?: (
| SearchInfo
| {
AND?: unknown;
OR?: unknown;
}
)[];
} & SearchOptions,
): Promise<StorageFile[]> {
const search = body as Search;
const type = ["match", "match_phrase"] as const;
const searchMapCallback = (v: SearchInfo | SearchOperator): QueryDslQueryContainer => {
if ("field" in v && "value" in v) {
return { [type[search.exact || v.exact ? 1 : 0]]: { [v.field]: v.value } };
}
if ("AND" in v || "OR" in v) {
return {
bool: { must: v.AND?.map(searchMapCallback), should: v.OR?.map(searchMapCallback) },
};
}
throw new HttpError(
HttpStatusCode.UNPROCESSABLE_ENTITY,
"ข้อมูลค้นหาไม่ถูกต้อง กรุณาตรวจสอบอีกครั้ง",
);
};
const result = await esClient.search<StorageFile & { attachment: Record<string, string> }>({
index: DEFAULT_INDEX,
query: {
bool: {
must: search.AND?.map((v) => ({
[type[search.exact || v.exact ? 1 : 0]]: { [v.field]: v.value },
})),
should: search.OR?.map((v) => ({
[type[search.exact || v.exact ? 1 : 0]]: { [v.field]: v.value },
})),
must_not: {
match: { hidden: true },
},
must: search.AND?.map(searchMapCallback),
should: search.OR?.map(searchMapCallback),
must_not: { match: { hidden: true } },
},
},
post_filter: {
[["match", "prefix"][search.recursive ? 1 : 0]]: {
path: `${search.path?.join("/")}/`.replace(/^\//, "") || "",
},
},
size: 10000,

View file

@ -1,13 +1,34 @@
export interface Search {
AND?: {
field: string;
value: string;
exact?: boolean;
}[];
OR?: {
field: string;
value: string;
exact?: boolean;
}[];
import { QueryDslQueryContainer } from "@elastic/elasticsearch/lib/api/types";
export interface SearchOptions {
recursive?: boolean;
path?: string[];
exact?: boolean;
}
export interface SearchInfo {
field: string;
value: string;
exact?: boolean;
}
export interface SearchOperator {
AND?: (SearchInfo | SearchOperator)[];
OR?: (SearchInfo | SearchOperator)[];
}
export type Search = SearchOperator & SearchOptions;
export function mapCallback(exact: boolean) {
const type = ["match", "match_phrase"] as const;
return (v: SearchInfo | SearchOperator): QueryDslQueryContainer => {
return "field" in v && "value" in v
? { [type[exact || v.exact ? 1 : 0]]: { [v.field]: v.value } }
: {
bool: {
must: v.AND?.map(mapCallback(exact)),
should: v.OR?.map(mapCallback(exact)),
},
};
};
}

View file

@ -27,6 +27,8 @@ if (!DEFAULT_INDEX) throw Error("Default ElasticSearch index must be specified."
// for failed queue that will come later
const cachedBuffer: Record<string, Buffer> = {};
const cachedMetadata: Record<string, { size: number; type: string }> = {};
const cachedRecord: Record<string, StorageFile> = {};
let errorKey: string[] = [];
export async function handler(key: string, event: string): Promise<boolean> {
console.info(`[AMQ] Messages received - key: ${key}, event: ${event}`);
@ -61,15 +63,19 @@ export async function handler(key: string, event: string): Promise<boolean> {
const rec = await popInfo(pathname);
console.info(`[AMQ] Key: ${key} - ${JSON.stringify(rec, null, 2) ?? "Not Found."}`);
if (rec) cachedRecord[key] = rec;
const result = rec
? await handleFoundRecord(rec, cachedBuffer[key], cachedMetadata[key])
: await handleNotFoundRecord(pathname, cachedBuffer[key], cachedMetadata[key]);
console.info(`[AMQ] Key: ${key} - ${JSON.stringify(cachedRecord[key] ?? "Not Found", null, 2)}`);
const result = cachedRecord[key]
? await handleFoundRecord(cachedRecord[key], cachedBuffer[key], cachedMetadata[key], key)
: await handleNotFoundRecord(pathname, cachedBuffer[key], cachedMetadata[key], key);
if (result) {
delete cachedBuffer[key];
delete cachedMetadata[key];
delete cachedRecord[key];
errorKey = errorKey.filter((v) => v !== key);
}
return result;
@ -96,7 +102,7 @@ async function popInfo(pathname: string) {
return result.hits.hits[0]._source;
}
if (result && result.hits.hits.length === 0) console.info("Index Not Found");
if (result && result.hits.hits.length === 0) console.info("[AMQ] Index Not Found");
return false;
}
@ -126,10 +132,11 @@ async function handleNotFoundRecord(
pathname: string,
buffer: Buffer,
stat: { size: number; type: string },
key: string,
) {
const path = stripLeadingSlash(pathname.split("/").slice(0, -1).join("/") + "/");
const filename = pathname.split("/").at(-1);
const base64 = Buffer.from(buffer).toString("base64");
const base64 = errorKey.includes(key) ? "" : Buffer.from(buffer).toString("base64");
const metadata = {
pathname,
@ -158,7 +165,12 @@ async function handleNotFoundRecord(
document: { data: base64, ...metadata },
refresh: "wait_for",
})
.catch((e) => console.error(e));
.catch((e) => {
if (e.meta.statusCode >= 400 && e.meta.statusCode < 500) {
errorKey.push(key);
}
console.error(e);
});
if (!result) return false;
@ -173,6 +185,7 @@ async function handleFoundRecord(
metadata: StorageFile,
buffer: Buffer,
stat: { size: number; type: string },
key: string,
) {
metadata.fileSize = stat.size;
metadata.fileType = stat.type;
@ -182,10 +195,19 @@ async function handleFoundRecord(
.index({
pipeline: "attachment",
index: DEFAULT_INDEX!,
document: { data: Buffer.from(buffer).toString("base64"), ...metadata },
document: {
data: errorKey.includes(key) ? "" : Buffer.from(buffer).toString("base64"),
...metadata,
},
refresh: "wait_for",
})
.catch((e) => console.error(e));
.catch((e) => {
if (e.meta.statusCode >= 400 && e.meta.statusCode < 500) {
errorKey.push(key);
console.log(cachedRecord);
}
console.error(e);
});
if (!result) return false;

View file

@ -45,11 +45,21 @@ 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
"Search": {
"SearchInfo": {
"dataType": "refObject",
"properties": {
"AND": {"dataType":"array","array":{"dataType":"nestedObjectLiteral","nestedProperties":{"exact":{"dataType":"boolean"},"value":{"dataType":"string","required":true},"field":{"dataType":"string","required":true}}}},
"OR": {"dataType":"array","array":{"dataType":"nestedObjectLiteral","nestedProperties":{"exact":{"dataType":"boolean"},"value":{"dataType":"string","required":true},"field":{"dataType":"string","required":true}}}},
"field": {"dataType":"string","required":true},
"value": {"dataType":"string","required":true},
"exact": {"dataType":"boolean"},
},
"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
"SearchOptions": {
"dataType": "refObject",
"properties": {
"recursive": {"dataType":"boolean"},
"path": {"dataType":"array","array":{"dataType":"string"}},
"exact": {"dataType":"boolean"},
},
"additionalProperties": false,
@ -175,7 +185,7 @@ export function RegisterRoutes(app: Router) {
function SearchController_searchFile(request: any, response: any, next: any) {
const args = {
search: {"in":"body","name":"search","required":true,"ref":"Search"},
body: {"in":"body","name":"body","required":true,"dataType":"intersection","subSchemas":[{"dataType":"nestedObjectLiteral","nestedProperties":{"OR":{"dataType":"array","array":{"dataType":"union","subSchemas":[{"ref":"SearchInfo"},{"dataType":"nestedObjectLiteral","nestedProperties":{"OR":{"dataType":"any"},"AND":{"dataType":"any"}}}]}},"AND":{"dataType":"array","array":{"dataType":"union","subSchemas":[{"ref":"SearchInfo"},{"dataType":"nestedObjectLiteral","nestedProperties":{"OR":{"dataType":"any"},"AND":{"dataType":"any"}}}]}}}},{"ref":"SearchOptions"}]},
};
// 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
@ -274,7 +284,7 @@ export function RegisterRoutes(app: Router) {
});
// 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('/storage/folder/size',
authenticateMiddleware([{"bearerAuth":["management-role","admin"]}]),
authenticateMiddleware([{"bearerAuth":[]}]),
...(fetchMiddlewares<RequestHandler>(StorageController)),
...(fetchMiddlewares<RequestHandler>(StorageController.prototype.folderSize)),

View file

@ -110,47 +110,33 @@
"type": "object",
"additionalProperties": false
},
"Search": {
"SearchInfo": {
"properties": {
"AND": {
"items": {
"properties": {
"exact": {
"type": "boolean"
},
"value": {
"type": "string"
},
"field": {
"type": "string"
}
},
"required": [
"value",
"field"
],
"type": "object"
},
"type": "array"
"field": {
"type": "string"
},
"OR": {
"value": {
"type": "string"
},
"exact": {
"type": "boolean"
}
},
"required": [
"field",
"value"
],
"type": "object",
"additionalProperties": false
},
"SearchOptions": {
"properties": {
"recursive": {
"type": "boolean"
},
"path": {
"items": {
"properties": {
"exact": {
"type": "boolean"
},
"value": {
"type": "string"
},
"field": {
"type": "string"
}
},
"required": [
"value",
"field"
],
"type": "object"
"type": "string"
},
"type": "array"
},
@ -608,7 +594,50 @@
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Search"
"allOf": [
{
"properties": {
"OR": {
"items": {
"anyOf": [
{
"$ref": "#/components/schemas/SearchInfo"
},
{
"properties": {
"OR": {},
"AND": {}
},
"type": "object"
}
]
},
"type": "array"
},
"AND": {
"items": {
"anyOf": [
{
"$ref": "#/components/schemas/SearchInfo"
},
{
"properties": {
"OR": {},
"AND": {}
},
"type": "object"
}
]
},
"type": "array"
}
},
"type": "object"
},
{
"$ref": "#/components/schemas/SearchOptions"
}
]
}
}
}
@ -847,10 +876,7 @@
],
"security": [
{
"bearerAuth": [
"management-role",
"admin"
]
"bearerAuth": []
}
],
"parameters": [],