Merge branch 'development'
This commit is contained in:
commit
5e74817f59
16 changed files with 475 additions and 199 deletions
|
|
@ -10,9 +10,18 @@ const props = withDefaults(
|
||||||
defineProps<{
|
defineProps<{
|
||||||
open: boolean
|
open: boolean
|
||||||
error: {
|
error: {
|
||||||
|
externalFileExist?: boolean
|
||||||
fileExist?: boolean
|
fileExist?: boolean
|
||||||
fileName2Long?: boolean
|
fileName2Long?: boolean
|
||||||
}
|
}
|
||||||
|
disableFields?: (
|
||||||
|
| 'file'
|
||||||
|
| 'title'
|
||||||
|
| 'description'
|
||||||
|
| 'keyword'
|
||||||
|
| 'category'
|
||||||
|
| 'author'
|
||||||
|
)[]
|
||||||
mode: 'create' | 'edit'
|
mode: 'create' | 'edit'
|
||||||
fileNameLabel?: string
|
fileNameLabel?: string
|
||||||
title?: string
|
title?: string
|
||||||
|
|
@ -63,6 +72,25 @@ function reset() {
|
||||||
}
|
}
|
||||||
|
|
||||||
function submit() {
|
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', {
|
emit('submit', {
|
||||||
mode: props.mode,
|
mode: props.mode,
|
||||||
file: file.value,
|
file: file.value,
|
||||||
|
|
@ -146,12 +174,13 @@ const file = ref<File | undefined>()
|
||||||
/>
|
/>
|
||||||
</q-toolbar>
|
</q-toolbar>
|
||||||
|
|
||||||
<section class="q-mb-md">
|
<section class="q-pb-xs">
|
||||||
<span class="text-weight-bold q-mb-sm block">อัปโหลดไฟล์</span>
|
<span class="text-weight-bold q-mb-sm block">อัปโหลดไฟล์</span>
|
||||||
<q-file
|
<q-file
|
||||||
dense
|
dense
|
||||||
outlined
|
outlined
|
||||||
v-model="file"
|
v-model="file"
|
||||||
|
:disable="disableFields?.includes('file')"
|
||||||
@update:model-value="(v) => $emit('filechange', v.name)"
|
@update:model-value="(v) => $emit('filechange', v.name)"
|
||||||
:label="
|
:label="
|
||||||
mode === 'edit' && fileNameLabel && !file?.name
|
mode === 'edit' && fileNameLabel && !file?.name
|
||||||
|
|
@ -160,13 +189,19 @@ const file = ref<File | undefined>()
|
||||||
? undefined
|
? undefined
|
||||||
: 'เลือกไฟล์'
|
: 'เลือกไฟล์'
|
||||||
"
|
"
|
||||||
:error="!!error.fileExist || !!error.fileName2Long"
|
:error="
|
||||||
|
!!error.externalFileExist ||
|
||||||
|
!!error.fileExist ||
|
||||||
|
!!error.fileName2Long
|
||||||
|
"
|
||||||
:error-message="
|
:error-message="
|
||||||
error.fileExist
|
error.externalFileExist
|
||||||
? 'พบไฟล์ชื่อซ้ำในระบบ ไฟล์ชื่อนี้ภายในระบบจะถูกเขียนทับ'
|
? 'พบไฟล์ชื่อซ้ำในระบบอื่น'
|
||||||
: error.fileName2Long
|
: error.fileExist
|
||||||
? 'ไม่สามารถเพิ่มไฟล์ที่ชื่อยาวเกิน 85 ตัวอักษรได้'
|
? 'พบไฟล์ชื่อซ้ำในระบบ ไฟล์ชื่อนี้ภายในระบบจะถูกเขียนทับ'
|
||||||
: ''
|
: error.fileName2Long
|
||||||
|
? 'ไม่สามารถเพิ่มไฟล์ที่ชื่อยาวเกิน 85 ตัวอักษรได้'
|
||||||
|
: ''
|
||||||
"
|
"
|
||||||
id="inputFile"
|
id="inputFile"
|
||||||
:rules="[
|
:rules="[
|
||||||
|
|
@ -179,13 +214,14 @@ const file = ref<File | undefined>()
|
||||||
</q-file>
|
</q-file>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section class="q-mb-md">
|
<section class="q-pb-xs">
|
||||||
<span class="text-weight-bold">ชื่อเรื่อง</span>
|
<span class="text-weight-bold">ชื่อเรื่อง</span>
|
||||||
<q-input
|
<q-input
|
||||||
outlined
|
outlined
|
||||||
dense
|
dense
|
||||||
class="q-my-sm"
|
class="q-mt-sm"
|
||||||
placeholder="กรอกชื่อเรื่อง"
|
placeholder="กรอกชื่อเรื่อง"
|
||||||
|
:disable="disableFields?.includes('title')"
|
||||||
:model-value="title"
|
:model-value="title"
|
||||||
:rules="[
|
:rules="[
|
||||||
(v) =>
|
(v) =>
|
||||||
|
|
@ -196,7 +232,7 @@ const file = ref<File | undefined>()
|
||||||
/>
|
/>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section class="q-mb-md">
|
<section class="q-pb-lg">
|
||||||
<span class="text-weight-bold">รายละเอียดของเอกสาร</span>
|
<span class="text-weight-bold">รายละเอียดของเอกสาร</span>
|
||||||
<q-input
|
<q-input
|
||||||
outlined
|
outlined
|
||||||
|
|
@ -204,63 +240,21 @@ const file = ref<File | undefined>()
|
||||||
class="q-mt-sm no-resize"
|
class="q-mt-sm no-resize"
|
||||||
type="textarea"
|
type="textarea"
|
||||||
placeholder="กรอกรายละเอียด"
|
placeholder="กรอกรายละเอียด"
|
||||||
|
:disable="disableFields?.includes('description')"
|
||||||
:model-value="description"
|
:model-value="description"
|
||||||
@update:model-value="(v) => $emit('update:description', v)"
|
@update:model-value="(v) => $emit('update:description', v)"
|
||||||
id="inputDescription"
|
id="inputDescription"
|
||||||
/>
|
/>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section class="q-mb-md">
|
<section class="q-pb-xs">
|
||||||
<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">
|
|
||||||
<span class="text-weight-bold">เจ้าของผลงาน</span>
|
<span class="text-weight-bold">เจ้าของผลงาน</span>
|
||||||
<q-input
|
<q-input
|
||||||
outlined
|
outlined
|
||||||
dense
|
dense
|
||||||
class="q-my-sm"
|
class="q-mt-sm"
|
||||||
placeholder="เจ้าของผลงาน"
|
placeholder="เจ้าของผลงาน"
|
||||||
|
:disable="disableFields?.includes('author')"
|
||||||
:model-value="author"
|
:model-value="author"
|
||||||
:rules="[
|
:rules="[
|
||||||
(v) =>
|
(v) =>
|
||||||
|
|
@ -271,6 +265,50 @@ const file = ref<File | undefined>()
|
||||||
/>
|
/>
|
||||||
</section>
|
</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' }">
|
<section :style="{ display: 'flex', gap: '.5rem' }">
|
||||||
<q-btn
|
<q-btn
|
||||||
label="บันทึก"
|
label="บันทึก"
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@ import UploadExistDialog from './UploadExistDialog.vue'
|
||||||
|
|
||||||
const storageStore = useStorage()
|
const storageStore = useStorage()
|
||||||
const { file, currentInfo } = storeToRefs(storageStore)
|
const { file, currentInfo } = storeToRefs(storageStore)
|
||||||
const { createFile, updateFile } = storageStore
|
const { createFile, updateFile, goto } = storageStore
|
||||||
|
|
||||||
const fileFormState = ref<boolean>(false)
|
const fileFormState = ref<boolean>(false)
|
||||||
const fileFormPath = ref<string>('')
|
const fileFormPath = ref<string>('')
|
||||||
|
|
@ -18,10 +18,16 @@ const fileFormData = ref<{
|
||||||
keyword?: string[]
|
keyword?: string[]
|
||||||
category?: string[]
|
category?: string[]
|
||||||
author?: string
|
author?: string
|
||||||
|
metadata?: Record<string, unknown>
|
||||||
}>({})
|
}>({})
|
||||||
const fileFormType = ref<'edit' | 'create'>('create')
|
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 fileExistNotification = ref<boolean>(false)
|
||||||
|
const errorState = ref<'fileExist' | 'externalFileExist'>('fileExist')
|
||||||
const fileFormComponent = ref<InstanceType<typeof FileForm>>()
|
const fileFormComponent = ref<InstanceType<typeof FileForm>>()
|
||||||
const fileNameLabel = ref<string>()
|
const fileNameLabel = ref<string>()
|
||||||
|
|
||||||
|
|
@ -38,6 +44,7 @@ function triggerFileEdit(
|
||||||
keyword: string[]
|
keyword: string[]
|
||||||
category: string[]
|
category: string[]
|
||||||
author: string
|
author: string
|
||||||
|
metadata?: Record<string, unknown>
|
||||||
},
|
},
|
||||||
pathname: string,
|
pathname: string,
|
||||||
file?: string,
|
file?: string,
|
||||||
|
|
@ -46,13 +53,18 @@ function triggerFileEdit(
|
||||||
fileFormType.value = 'edit'
|
fileFormType.value = 'edit'
|
||||||
fileFormPath.value = pathname
|
fileFormPath.value = pathname
|
||||||
fileFormData.value = {
|
fileFormData.value = {
|
||||||
title: value.title,
|
title: (value.metadata?.subject as string) ?? value.title,
|
||||||
description: value.description,
|
description: value.description,
|
||||||
keyword: value.keyword,
|
keyword: value.keyword,
|
||||||
category: value.category,
|
category: value.category,
|
||||||
author: value.author,
|
author: (value.metadata?.author as string) ?? value.author,
|
||||||
|
metadata: value.metadata,
|
||||||
}
|
}
|
||||||
fileNameLabel.value = file
|
fileNameLabel.value = file
|
||||||
|
|
||||||
|
const arr = pathname.split('/')
|
||||||
|
arr[arr.length - 1] = ''
|
||||||
|
goto(arr.join('/'))
|
||||||
}
|
}
|
||||||
|
|
||||||
defineExpose({
|
defineExpose({
|
||||||
|
|
@ -62,6 +74,18 @@ defineExpose({
|
||||||
|
|
||||||
const currentParam = ref<Parameters<typeof submitFileForm>[0]>()
|
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) {
|
function checkFileExist(name: string, except?: string) {
|
||||||
return Boolean(
|
return Boolean(
|
||||||
file.value[currentInfo.value.path].find(
|
file.value[currentInfo.value.path].find(
|
||||||
|
|
@ -88,6 +112,16 @@ async function submitFileForm(
|
||||||
) {
|
) {
|
||||||
currentParam.value = value
|
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 (
|
if (
|
||||||
value.file &&
|
value.file &&
|
||||||
file.value[currentInfo.value.path].find(
|
file.value[currentInfo.value.path].find(
|
||||||
|
|
@ -96,6 +130,7 @@ async function submitFileForm(
|
||||||
!force
|
!force
|
||||||
) {
|
) {
|
||||||
fileExistNotification.value = true
|
fileExistNotification.value = true
|
||||||
|
errorState.value = 'fileExist'
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -133,6 +168,11 @@ async function submitFileForm(
|
||||||
:mode="fileFormType"
|
:mode="fileFormType"
|
||||||
:error="fileFormError"
|
:error="fileFormError"
|
||||||
:fileNameLabel="fileNameLabel"
|
:fileNameLabel="fileNameLabel"
|
||||||
|
:disableFields="
|
||||||
|
fileFormData.metadata && Object.keys(fileFormData.metadata).length > 0
|
||||||
|
? ['file', 'title', 'author']
|
||||||
|
: []
|
||||||
|
"
|
||||||
v-model:open="fileFormState"
|
v-model:open="fileFormState"
|
||||||
v-model:title="fileFormData.title"
|
v-model:title="fileFormData.title"
|
||||||
v-model:description="fileFormData.description"
|
v-model:description="fileFormData.description"
|
||||||
|
|
@ -142,6 +182,7 @@ async function submitFileForm(
|
||||||
@reset="() => (fileFormError = {})"
|
@reset="() => (fileFormError = {})"
|
||||||
@filechange="
|
@filechange="
|
||||||
(name: string) => {
|
(name: string) => {
|
||||||
|
fileFormError.externalFileExist = checkFileExistMetadata(name)
|
||||||
;(fileFormError.fileExist = checkFileExist(name, fileNameLabel)),
|
;(fileFormError.fileExist = checkFileExist(name, fileNameLabel)),
|
||||||
(fileFormError.fileName2Long = checkFileName2Long(
|
(fileFormError.fileName2Long = checkFileName2Long(
|
||||||
name,
|
name,
|
||||||
|
|
@ -153,6 +194,7 @@ async function submitFileForm(
|
||||||
/>
|
/>
|
||||||
<upload-exist-dialog
|
<upload-exist-dialog
|
||||||
v-model:notification="fileExistNotification"
|
v-model:notification="fileExistNotification"
|
||||||
|
v-model:errorState="errorState"
|
||||||
@confirm="() => currentParam && submitFileForm(currentParam, true)"
|
@confirm="() => currentParam && submitFileForm(currentParam, true)"
|
||||||
@cancel="() => (currentParam = undefined)"
|
@cancel="() => (currentParam = undefined)"
|
||||||
/>
|
/>
|
||||||
|
|
|
||||||
|
|
@ -117,6 +117,7 @@ function triggerFileDelete(pathname: string) {
|
||||||
>
|
>
|
||||||
<file-item-action
|
<file-item-action
|
||||||
:nameId="value.pathname"
|
:nameId="value.pathname"
|
||||||
|
:have-meta="false"
|
||||||
@delete="() => triggerFolderDelete(value.pathname)"
|
@delete="() => triggerFolderDelete(value.pathname)"
|
||||||
@edit="
|
@edit="
|
||||||
() =>
|
() =>
|
||||||
|
|
@ -238,6 +239,9 @@ function triggerFileDelete(pathname: string) {
|
||||||
>
|
>
|
||||||
<file-item-action
|
<file-item-action
|
||||||
:nameId="value.pathname"
|
:nameId="value.pathname"
|
||||||
|
:have-meta="
|
||||||
|
!!(value.metadata && Object.keys(value.metadata).length > 0)
|
||||||
|
"
|
||||||
@edit="
|
@edit="
|
||||||
() =>
|
() =>
|
||||||
fileFormComponent?.triggerFileEdit(
|
fileFormComponent?.triggerFileEdit(
|
||||||
|
|
@ -247,6 +251,7 @@ function triggerFileDelete(pathname: string) {
|
||||||
keyword: value.keyword,
|
keyword: value.keyword,
|
||||||
category: value.category,
|
category: value.category,
|
||||||
author: value.author,
|
author: value.author,
|
||||||
|
metadata: value.metadata,
|
||||||
},
|
},
|
||||||
value.pathname,
|
value.pathname,
|
||||||
value.fileName,
|
value.fileName,
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ defineEmits(['edit', 'delete'])
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
nameId: string
|
nameId: string
|
||||||
|
haveMeta: boolean
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
const open = ref(false)
|
const open = ref(false)
|
||||||
|
|
@ -39,6 +40,7 @@ const open = ref(false)
|
||||||
</q-item-section>
|
</q-item-section>
|
||||||
</q-item>
|
</q-item>
|
||||||
<q-item
|
<q-item
|
||||||
|
v-if="!haveMeta"
|
||||||
clickable
|
clickable
|
||||||
@click.stop="
|
@click.stop="
|
||||||
() => {
|
() => {
|
||||||
|
|
|
||||||
|
|
@ -226,6 +226,9 @@ onMounted(() => {
|
||||||
>
|
>
|
||||||
<file-item-action
|
<file-item-action
|
||||||
:nameId="value.pathname"
|
:nameId="value.pathname"
|
||||||
|
:have-meta="
|
||||||
|
!!(value.metadata && Object.keys(value.metadata).length > 0)
|
||||||
|
"
|
||||||
@edit="
|
@edit="
|
||||||
() =>
|
() =>
|
||||||
fileFormComponent?.triggerFileEdit(
|
fileFormComponent?.triggerFileEdit(
|
||||||
|
|
@ -235,6 +238,7 @@ onMounted(() => {
|
||||||
keyword: value.keyword,
|
keyword: value.keyword,
|
||||||
category: value.category,
|
category: value.category,
|
||||||
author: value.author,
|
author: value.author,
|
||||||
|
metadata: value.metadata,
|
||||||
},
|
},
|
||||||
value.pathname,
|
value.pathname,
|
||||||
value.fileName,
|
value.fileName,
|
||||||
|
|
@ -353,8 +357,21 @@ onMounted(() => {
|
||||||
dense
|
dense
|
||||||
icon="mdi-trash-can-outline"
|
icon="mdi-trash-can-outline"
|
||||||
@click="() => triggerFileDelete(actionData.row.pathname)"
|
@click="() => triggerFileDelete(actionData.row.pathname)"
|
||||||
|
v-if="
|
||||||
|
!(
|
||||||
|
actionData.row.metadata &&
|
||||||
|
Object.keys(actionData.row.metadata).length > 0
|
||||||
|
)
|
||||||
|
"
|
||||||
id="listViewFileDelete"
|
id="listViewFileDelete"
|
||||||
/>
|
/>
|
||||||
|
<div
|
||||||
|
style="width: 64px"
|
||||||
|
v-if="
|
||||||
|
actionData.row.metadata &&
|
||||||
|
Object.keys(actionData.row.metadata).length > 0
|
||||||
|
"
|
||||||
|
></div>
|
||||||
</div>
|
</div>
|
||||||
</q-td>
|
</q-td>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
||||||
|
|
@ -379,7 +379,7 @@ const onRowClick = ((_, row) => {
|
||||||
fileFormComponent?.triggerFileEdit(
|
fileFormComponent?.triggerFileEdit(
|
||||||
data.row,
|
data.row,
|
||||||
data.row.pathname,
|
data.row.pathname,
|
||||||
data.row.filename,
|
data.row.fileName,
|
||||||
)
|
)
|
||||||
"
|
"
|
||||||
id="listViewFileEdit"
|
id="listViewFileEdit"
|
||||||
|
|
@ -391,8 +391,20 @@ const onRowClick = ((_, row) => {
|
||||||
color="negative"
|
color="negative"
|
||||||
icon="mdi-trash-can-outline"
|
icon="mdi-trash-can-outline"
|
||||||
@click.stop="() => triggerFileDelete(data.row.pathname)"
|
@click.stop="() => triggerFileDelete(data.row.pathname)"
|
||||||
|
v-if="
|
||||||
|
!(
|
||||||
|
data.row.metadata &&
|
||||||
|
Object.keys(data.row.metadata).length > 0
|
||||||
|
)
|
||||||
|
"
|
||||||
data-testid="listViewFileDelete"
|
data-testid="listViewFileDelete"
|
||||||
/>
|
/>
|
||||||
|
<div
|
||||||
|
style="width: 64px"
|
||||||
|
v-if="
|
||||||
|
data.row.metadata && Object.keys(data.row.metadata).length > 0
|
||||||
|
"
|
||||||
|
></div>
|
||||||
</div>
|
</div>
|
||||||
</q-td>
|
</q-td>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
defineProps<{
|
defineProps<{
|
||||||
notification: boolean
|
notification: boolean
|
||||||
|
errorState: 'fileExist' | 'externalFileExist'
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
defineEmits(['update:notification', 'confirm', 'cancel'])
|
defineEmits(['update:notification', 'confirm', 'cancel'])
|
||||||
|
|
@ -22,11 +23,19 @@ defineEmits(['update:notification', 'confirm', 'cancel'])
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<h6 class="q-my-none">ยืนยันการเพิ่มข้อมูล</h6>
|
<h6 class="q-my-none">
|
||||||
|
{{
|
||||||
|
errorState === 'externalFileExist'
|
||||||
|
? 'พบไฟล์ชื่อช้ำในระบบอื่น'
|
||||||
|
: 'ยืนยันการเพิ่มข้อมูล'
|
||||||
|
}}
|
||||||
|
</h6>
|
||||||
<p class="q-my-none">
|
<p class="q-my-none">
|
||||||
พบไฟล์ชื่อซ้ำในระบบ หากดำเนินการต่อ
|
{{
|
||||||
ไฟล์ที่มีอยู่จะถูกแทนที่ด้วยไฟล์ใหม่
|
errorState === 'externalFileExist'
|
||||||
ต้องการยืนยันการอัปโหลดไฟล์นี้หรือไม่
|
? 'พบไฟล์ชื่อช้ำในระบบอื่น จะไม่สามารถอัปโหลดไฟล์นี้ได้'
|
||||||
|
: 'ไฟล์ที่มีอยู่จะถูกแทนที่ด้วยไฟล์ใหม่ต้องการยืนยันการอัปโหลดไฟล์นี้หรือไม่'
|
||||||
|
}}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -44,7 +53,12 @@ defineEmits(['update:notification', 'confirm', 'cancel'])
|
||||||
label="ดำเนินการต่อ"
|
label="ดำเนินการต่อ"
|
||||||
v-close-popup
|
v-close-popup
|
||||||
color="warning"
|
color="warning"
|
||||||
@click="() => $emit('confirm')"
|
@click="
|
||||||
|
() =>
|
||||||
|
errorState === 'externalFileExist'
|
||||||
|
? $emit('cancel')
|
||||||
|
: $emit('confirm')
|
||||||
|
"
|
||||||
/>
|
/>
|
||||||
</q-card-actions>
|
</q-card-actions>
|
||||||
</q-card>
|
</q-card>
|
||||||
|
|
|
||||||
|
|
@ -132,7 +132,9 @@ async function downloadSubmit(path: string | undefined) {
|
||||||
<span>ชื่อเรื่อง</span>
|
<span>ชื่อเรื่อง</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-grow">
|
<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>
|
||||||
</div>
|
</div>
|
||||||
<q-separator />
|
<q-separator />
|
||||||
|
|
@ -150,7 +152,9 @@ async function downloadSubmit(path: string | undefined) {
|
||||||
<span>เจ้าของผลงาน</span>
|
<span>เจ้าของผลงาน</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-grow">
|
<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>
|
||||||
</div>
|
</div>
|
||||||
<q-separator />
|
<q-separator />
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,21 @@ import { useFileInfoStore } from '@/stores/file-info-data'
|
||||||
import AdvancedSearch from '@/modules/01_user/components/AdvancedSearch.vue'
|
import AdvancedSearch from '@/modules/01_user/components/AdvancedSearch.vue'
|
||||||
import useStorage from '@/stores/storage'
|
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 loaderStore = useLoader()
|
||||||
const storageStore = useStorage()
|
const storageStore = useStorage()
|
||||||
const { currentInfo } = storeToRefs(storageStore)
|
const { currentInfo } = storeToRefs(storageStore)
|
||||||
|
|
@ -26,21 +41,12 @@ const {
|
||||||
advSearchDataRow,
|
advSearchDataRow,
|
||||||
} = storeToRefs(useSearchDataStore())
|
} = storeToRefs(useSearchDataStore())
|
||||||
const { getFoundFile } = useSearchDataStore()
|
const { getFoundFile } = useSearchDataStore()
|
||||||
const submitSearchData = ref<{
|
|
||||||
AND: {
|
const submitSearchData = ref<Search>({
|
||||||
field: string
|
|
||||||
value: string
|
|
||||||
exact?: boolean
|
|
||||||
}[]
|
|
||||||
OR: {
|
|
||||||
field: string
|
|
||||||
value: string
|
|
||||||
exact?: boolean
|
|
||||||
}[]
|
|
||||||
}>({
|
|
||||||
AND: [],
|
AND: [],
|
||||||
OR: [],
|
OR: [],
|
||||||
})
|
})
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
mode: 'admin' | 'user'
|
mode: 'admin' | 'user'
|
||||||
}>()
|
}>()
|
||||||
|
|
@ -50,55 +56,50 @@ async function submitSearch() {
|
||||||
if (searchData.value.value.trim() !== '') {
|
if (searchData.value.value.trim() !== '') {
|
||||||
submitSearchData.value = { AND: [], OR: [] }
|
submitSearchData.value = { AND: [], OR: [] }
|
||||||
if (props.mode === 'admin') {
|
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) => {
|
optionsField.value.forEach((option) => {
|
||||||
submitSearchData.value.OR.push({
|
submitSearchData.value.OR?.push({
|
||||||
field: option.value,
|
field: option.value,
|
||||||
value: searchData.value.value,
|
value: searchData.value.value,
|
||||||
exact: true,
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
submitSearchData.value.OR.push({
|
submitSearchData.value.OR?.push({
|
||||||
field: 'fileName',
|
field: 'fileName',
|
||||||
value: searchData.value.value,
|
value: searchData.value.value,
|
||||||
exact: true,
|
|
||||||
})
|
})
|
||||||
submitSearchData.value.OR.push({
|
submitSearchData.value.OR?.push({
|
||||||
field: 'metadata.author',
|
field: 'metadata.author',
|
||||||
value: searchData.value.value,
|
value: searchData.value.value,
|
||||||
exact: true,
|
|
||||||
})
|
})
|
||||||
submitSearchData.value.OR.push({
|
submitSearchData.value.OR?.push({
|
||||||
field: 'metadata.subject',
|
field: 'metadata.subject',
|
||||||
value: searchData.value.value,
|
value: searchData.value.value,
|
||||||
exact: true,
|
|
||||||
})
|
})
|
||||||
submitSearchData.value.OR.push({
|
submitSearchData.value.OR?.push({
|
||||||
field: 'fileType',
|
field: 'fileType',
|
||||||
value: mime.getType(searchData.value.value) || '',
|
value: mime.getType(searchData.value.value) || '',
|
||||||
exact: true,
|
|
||||||
})
|
})
|
||||||
if (currentInfo.value.path !== '/') {
|
|
||||||
submitSearchData.value.AND.push({
|
|
||||||
field: 'path',
|
|
||||||
value: currentInfo.value.path,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
|
submitSearchData.value.recursive = true
|
||||||
|
submitSearchData.value.path = []
|
||||||
if (searchData.value.field == 'title') {
|
if (searchData.value.field == 'title') {
|
||||||
submitSearchData.value.OR.push({
|
submitSearchData.value.OR?.push({
|
||||||
field: 'metadata.subject',
|
field: 'metadata.subject',
|
||||||
value: searchData.value.value,
|
value: searchData.value.value,
|
||||||
exact: isExact.value,
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
if (searchData.value.field == 'author') {
|
if (searchData.value.field == 'author') {
|
||||||
submitSearchData.value.OR.push({
|
submitSearchData.value.OR?.push({
|
||||||
field: 'metadata.author',
|
field: 'metadata.author',
|
||||||
value: searchData.value.value,
|
value: searchData.value.value,
|
||||||
exact: isExact.value,
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
submitSearchData.value.OR.push({
|
submitSearchData.value.OR?.push({
|
||||||
field: searchData.value.field,
|
field: searchData.value.field,
|
||||||
value: searchData.value.value,
|
value: searchData.value.value,
|
||||||
exact: isExact.value,
|
exact: isExact.value,
|
||||||
|
|
@ -111,31 +112,56 @@ async function submitSearch() {
|
||||||
(d: { field: string; value: string; op: string; exact: boolean }) => {
|
(d: { field: string; value: string; op: string; exact: boolean }) => {
|
||||||
if (d.field && d.value.trim() !== '') {
|
if (d.field && d.value.trim() !== '') {
|
||||||
const op = d.op === 'AND' ? 'AND' : 'OR'
|
const op = d.op === 'AND' ? 'AND' : 'OR'
|
||||||
if (d.field == 'title') {
|
if (
|
||||||
submitSearchData.value[op].push({
|
(op === 'AND' && d.field === 'title') ||
|
||||||
field: 'metadata.subject',
|
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,
|
value: d.value,
|
||||||
exact: d.exact,
|
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) {
|
if (advField.keyword.length > 0) {
|
||||||
for (let i = 0; i < advField.keyword.length; i++) {
|
for (let i = 0; i < advField.keyword.length; i++) {
|
||||||
submitSearchData.value.AND.push({
|
submitSearchData.value.AND?.push({
|
||||||
field: 'keyword',
|
field: 'keyword',
|
||||||
value: advField.keyword[i],
|
value: advField.keyword[i],
|
||||||
exact: true,
|
exact: true,
|
||||||
|
|
@ -143,7 +169,7 @@ async function submitSearch() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (advField.description.trim() !== '') {
|
if (advField.description.trim() !== '') {
|
||||||
submitSearchData.value.AND.push({
|
submitSearchData.value.AND?.push({
|
||||||
field: 'description',
|
field: 'description',
|
||||||
value: advField.description,
|
value: advField.description,
|
||||||
exact: true,
|
exact: true,
|
||||||
|
|
|
||||||
|
|
@ -69,7 +69,7 @@ export const useFileInfoStore = defineStore('info', () => {
|
||||||
|
|
||||||
if (extension) return extension
|
if (extension) return extension
|
||||||
if (fileName && fileName.includes('.')) {
|
if (fileName && fileName.includes('.')) {
|
||||||
return fileName.substring(fileName.lastIndexOf('.'))
|
return fileName.substring(fileName.lastIndexOf('.') + 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
return 'ไม่ทราบประเภท'
|
return 'ไม่ทราบประเภท'
|
||||||
|
|
|
||||||
|
|
@ -27,7 +27,7 @@ export interface StorageFile {
|
||||||
title: string
|
title: string
|
||||||
description: string
|
description: string
|
||||||
author: string
|
author: string
|
||||||
metadata: Record<string, unknown>
|
metadata?: Record<string, unknown>
|
||||||
category: string[]
|
category: string[]
|
||||||
keyword: string[]
|
keyword: string[]
|
||||||
updatedAt: string
|
updatedAt: string
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,10 @@
|
||||||
import { Body, Controller, Post, Route, Security, SuccessResponse, Tags } from "tsoa";
|
import { Body, Controller, Post, Route, Security, SuccessResponse, Tags } from "tsoa";
|
||||||
import HttpStatusCode from "../interfaces/http-status";
|
import HttpStatusCode from "../interfaces/http-status";
|
||||||
import esClient from "../elasticsearch";
|
import esClient from "../elasticsearch";
|
||||||
import { Search } from "../interfaces/search";
|
import { Search, SearchInfo, SearchOperator, SearchOptions } from "../interfaces/search";
|
||||||
import { StorageFile } from "../interfaces/storage-fs";
|
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;
|
const DEFAULT_INDEX = process.env.ELASTICSEARCH_INDEX;
|
||||||
|
|
||||||
|
|
@ -14,22 +16,57 @@ export class SearchController extends Controller {
|
||||||
@Tags("Search")
|
@Tags("Search")
|
||||||
@Security("bearerAuth")
|
@Security("bearerAuth")
|
||||||
@SuccessResponse(HttpStatusCode.OK, "สำเร็จ")
|
@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 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> }>({
|
const result = await esClient.search<StorageFile & { attachment: Record<string, string> }>({
|
||||||
index: DEFAULT_INDEX,
|
index: DEFAULT_INDEX,
|
||||||
query: {
|
query: {
|
||||||
bool: {
|
bool: {
|
||||||
must: search.AND?.map((v) => ({
|
must: search.AND?.map(searchMapCallback),
|
||||||
[type[search.exact || v.exact ? 1 : 0]]: { [v.field]: v.value },
|
should: search.OR?.map(searchMapCallback),
|
||||||
})),
|
must_not: { match: { hidden: true } },
|
||||||
should: search.OR?.map((v) => ({
|
},
|
||||||
[type[search.exact || v.exact ? 1 : 0]]: { [v.field]: v.value },
|
},
|
||||||
})),
|
post_filter: {
|
||||||
must_not: {
|
[["match", "prefix"][search.recursive ? 1 : 0]]: {
|
||||||
match: { hidden: true },
|
path: `${search.path?.join("/")}/`.replace(/^\//, "") || "",
|
||||||
},
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
size: 10000,
|
size: 10000,
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,34 @@
|
||||||
export interface Search {
|
import { QueryDslQueryContainer } from "@elastic/elasticsearch/lib/api/types";
|
||||||
AND?: {
|
|
||||||
field: string;
|
export interface SearchOptions {
|
||||||
value: string;
|
recursive?: boolean;
|
||||||
exact?: boolean;
|
path?: string[];
|
||||||
}[];
|
|
||||||
OR?: {
|
|
||||||
field: string;
|
|
||||||
value: string;
|
|
||||||
exact?: boolean;
|
|
||||||
}[];
|
|
||||||
exact?: boolean;
|
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)),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -27,6 +27,8 @@ if (!DEFAULT_INDEX) throw Error("Default ElasticSearch index must be specified."
|
||||||
// for failed queue that will come later
|
// for failed queue that will come later
|
||||||
const cachedBuffer: Record<string, Buffer> = {};
|
const cachedBuffer: Record<string, Buffer> = {};
|
||||||
const cachedMetadata: Record<string, { size: number; type: string }> = {};
|
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> {
|
export async function handler(key: string, event: string): Promise<boolean> {
|
||||||
console.info(`[AMQ] Messages received - key: ${key}, event: ${event}`);
|
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);
|
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
|
console.info(`[AMQ] Key: ${key} - ${JSON.stringify(cachedRecord[key] ?? "Not Found", null, 2)}`);
|
||||||
? await handleFoundRecord(rec, cachedBuffer[key], cachedMetadata[key])
|
|
||||||
: await handleNotFoundRecord(pathname, cachedBuffer[key], cachedMetadata[key]);
|
const result = cachedRecord[key]
|
||||||
|
? await handleFoundRecord(cachedRecord[key], cachedBuffer[key], cachedMetadata[key], key)
|
||||||
|
: await handleNotFoundRecord(pathname, cachedBuffer[key], cachedMetadata[key], key);
|
||||||
|
|
||||||
if (result) {
|
if (result) {
|
||||||
delete cachedBuffer[key];
|
delete cachedBuffer[key];
|
||||||
delete cachedMetadata[key];
|
delete cachedMetadata[key];
|
||||||
|
delete cachedRecord[key];
|
||||||
|
errorKey = errorKey.filter((v) => v !== key);
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
|
|
@ -96,7 +102,7 @@ async function popInfo(pathname: string) {
|
||||||
return result.hits.hits[0]._source;
|
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;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
@ -126,10 +132,11 @@ async function handleNotFoundRecord(
|
||||||
pathname: string,
|
pathname: string,
|
||||||
buffer: Buffer,
|
buffer: Buffer,
|
||||||
stat: { size: number; type: string },
|
stat: { size: number; type: string },
|
||||||
|
key: string,
|
||||||
) {
|
) {
|
||||||
const path = stripLeadingSlash(pathname.split("/").slice(0, -1).join("/") + "/");
|
const path = stripLeadingSlash(pathname.split("/").slice(0, -1).join("/") + "/");
|
||||||
const filename = pathname.split("/").at(-1);
|
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 = {
|
const metadata = {
|
||||||
pathname,
|
pathname,
|
||||||
|
|
@ -158,7 +165,12 @@ async function handleNotFoundRecord(
|
||||||
document: { data: base64, ...metadata },
|
document: { data: base64, ...metadata },
|
||||||
refresh: "wait_for",
|
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;
|
if (!result) return false;
|
||||||
|
|
||||||
|
|
@ -173,6 +185,7 @@ async function handleFoundRecord(
|
||||||
metadata: StorageFile,
|
metadata: StorageFile,
|
||||||
buffer: Buffer,
|
buffer: Buffer,
|
||||||
stat: { size: number; type: string },
|
stat: { size: number; type: string },
|
||||||
|
key: string,
|
||||||
) {
|
) {
|
||||||
metadata.fileSize = stat.size;
|
metadata.fileSize = stat.size;
|
||||||
metadata.fileType = stat.type;
|
metadata.fileType = stat.type;
|
||||||
|
|
@ -182,10 +195,19 @@ async function handleFoundRecord(
|
||||||
.index({
|
.index({
|
||||||
pipeline: "attachment",
|
pipeline: "attachment",
|
||||||
index: DEFAULT_INDEX!,
|
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",
|
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;
|
if (!result) return false;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -45,11 +45,21 @@ const models: TsoaRoute.Models = {
|
||||||
"additionalProperties": false,
|
"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
|
// 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",
|
"dataType": "refObject",
|
||||||
"properties": {
|
"properties": {
|
||||||
"AND": {"dataType":"array","array":{"dataType":"nestedObjectLiteral","nestedProperties":{"exact":{"dataType":"boolean"},"value":{"dataType":"string","required":true},"field":{"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}}}},
|
"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"},
|
"exact": {"dataType":"boolean"},
|
||||||
},
|
},
|
||||||
"additionalProperties": false,
|
"additionalProperties": false,
|
||||||
|
|
@ -175,7 +185,7 @@ export function RegisterRoutes(app: Router) {
|
||||||
|
|
||||||
function SearchController_searchFile(request: any, response: any, next: any) {
|
function SearchController_searchFile(request: any, response: any, next: any) {
|
||||||
const args = {
|
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
|
// 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
|
// 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',
|
app.post('/storage/folder/size',
|
||||||
authenticateMiddleware([{"bearerAuth":["management-role","admin"]}]),
|
authenticateMiddleware([{"bearerAuth":[]}]),
|
||||||
...(fetchMiddlewares<RequestHandler>(StorageController)),
|
...(fetchMiddlewares<RequestHandler>(StorageController)),
|
||||||
...(fetchMiddlewares<RequestHandler>(StorageController.prototype.folderSize)),
|
...(fetchMiddlewares<RequestHandler>(StorageController.prototype.folderSize)),
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -110,47 +110,33 @@
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"additionalProperties": false
|
"additionalProperties": false
|
||||||
},
|
},
|
||||||
"Search": {
|
"SearchInfo": {
|
||||||
"properties": {
|
"properties": {
|
||||||
"AND": {
|
"field": {
|
||||||
"items": {
|
"type": "string"
|
||||||
"properties": {
|
|
||||||
"exact": {
|
|
||||||
"type": "boolean"
|
|
||||||
},
|
|
||||||
"value": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"field": {
|
|
||||||
"type": "string"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"required": [
|
|
||||||
"value",
|
|
||||||
"field"
|
|
||||||
],
|
|
||||||
"type": "object"
|
|
||||||
},
|
|
||||||
"type": "array"
|
|
||||||
},
|
},
|
||||||
"OR": {
|
"value": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"exact": {
|
||||||
|
"type": "boolean"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"field",
|
||||||
|
"value"
|
||||||
|
],
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": false
|
||||||
|
},
|
||||||
|
"SearchOptions": {
|
||||||
|
"properties": {
|
||||||
|
"recursive": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"path": {
|
||||||
"items": {
|
"items": {
|
||||||
"properties": {
|
"type": "string"
|
||||||
"exact": {
|
|
||||||
"type": "boolean"
|
|
||||||
},
|
|
||||||
"value": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"field": {
|
|
||||||
"type": "string"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"required": [
|
|
||||||
"value",
|
|
||||||
"field"
|
|
||||||
],
|
|
||||||
"type": "object"
|
|
||||||
},
|
},
|
||||||
"type": "array"
|
"type": "array"
|
||||||
},
|
},
|
||||||
|
|
@ -608,7 +594,50 @@
|
||||||
"content": {
|
"content": {
|
||||||
"application/json": {
|
"application/json": {
|
||||||
"schema": {
|
"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": [
|
"security": [
|
||||||
{
|
{
|
||||||
"bearerAuth": [
|
"bearerAuth": []
|
||||||
"management-role",
|
|
||||||
"admin"
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"parameters": [],
|
"parameters": [],
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue