게시물 Export 기간 선택과 삭제 추가 v1.5.23
This commit is contained in:
@@ -21,6 +21,12 @@ const uploadingLogo = ref(false)
|
||||
const uploadingHomeCover = ref(false)
|
||||
const uploadingHomeCoverDark = ref(false)
|
||||
const requestingPostExport = ref(false)
|
||||
const deletingPostExportJobIds = ref([])
|
||||
const postExportDateRangeMode = ref('all')
|
||||
const postExportYear = ref(new Date().getFullYear())
|
||||
const postExportMonth = ref(new Date().getMonth() + 1)
|
||||
const postExportDateFrom = ref('')
|
||||
const postExportDateTo = ref('')
|
||||
const errorMessage = ref('')
|
||||
const toast = ref(null)
|
||||
const logoInputRef = ref(null)
|
||||
@@ -180,11 +186,40 @@ const hasActivePostExportJobs = computed(() => normalizedPostExportJobs.value.so
|
||||
job.status === 'queued' || job.status === 'processing'
|
||||
)))
|
||||
|
||||
/**
|
||||
* Export 연도 선택지
|
||||
* @returns {Array<number>} 연도 목록
|
||||
*/
|
||||
const postExportYearOptions = computed(() => {
|
||||
const currentYear = new Date().getFullYear()
|
||||
return Array.from({ length: 10 }, (_, index) => currentYear - index)
|
||||
})
|
||||
|
||||
/**
|
||||
* Export 월 선택지
|
||||
* @type {ReadonlyArray<number>}
|
||||
*/
|
||||
const postExportMonthOptions = Array.from({ length: 12 }, (_, index) => index + 1)
|
||||
|
||||
/**
|
||||
* 게시물 export 요청 버튼 활성 가능 여부
|
||||
* @returns {boolean} 활성 가능 여부
|
||||
*/
|
||||
const canRequestPostExport = computed(() => !requestingPostExport.value && !hasActivePostExportJobs.value)
|
||||
const canRequestPostExport = computed(() => {
|
||||
if (requestingPostExport.value || hasActivePostExportJobs.value) {
|
||||
return false
|
||||
}
|
||||
|
||||
if (postExportDateRangeMode.value === 'custom') {
|
||||
return Boolean(
|
||||
postExportDateFrom.value
|
||||
&& postExportDateTo.value
|
||||
&& postExportDateFrom.value <= postExportDateTo.value
|
||||
)
|
||||
}
|
||||
|
||||
return true
|
||||
})
|
||||
|
||||
/**
|
||||
* 게시물 export 요청 버튼 안내 문구
|
||||
@@ -195,9 +230,50 @@ const postExportRequestTitle = computed(() => {
|
||||
return '진행 중인 Export 작업이 끝난 뒤 새 요청을 만들 수 있습니다.'
|
||||
}
|
||||
|
||||
if (postExportDateRangeMode.value === 'custom' && !canRequestPostExport.value) {
|
||||
return '올바른 시작일과 종료일을 선택해 주세요.'
|
||||
}
|
||||
|
||||
return '게시물 Export 작업을 요청합니다.'
|
||||
})
|
||||
|
||||
/**
|
||||
* Export 요청 범위 입력을 만든다.
|
||||
* @returns {Object} Export 범위 입력
|
||||
*/
|
||||
const createPostExportRequestBody = () => {
|
||||
const base = {
|
||||
chunkSize: 100,
|
||||
retentionDays: 100,
|
||||
dateRangeMode: postExportDateRangeMode.value
|
||||
}
|
||||
|
||||
if (postExportDateRangeMode.value === 'year') {
|
||||
return {
|
||||
...base,
|
||||
year: Number(postExportYear.value)
|
||||
}
|
||||
}
|
||||
|
||||
if (postExportDateRangeMode.value === 'month') {
|
||||
return {
|
||||
...base,
|
||||
year: Number(postExportYear.value),
|
||||
month: Number(postExportMonth.value)
|
||||
}
|
||||
}
|
||||
|
||||
if (postExportDateRangeMode.value === 'custom') {
|
||||
return {
|
||||
...base,
|
||||
dateFrom: postExportDateFrom.value,
|
||||
dateTo: postExportDateTo.value
|
||||
}
|
||||
}
|
||||
|
||||
return base
|
||||
}
|
||||
|
||||
/**
|
||||
* export 상태 라벨 조회
|
||||
* @param {string} status - export 상태
|
||||
@@ -490,10 +566,7 @@ const requestPostExport = async () => {
|
||||
try {
|
||||
await $fetch('/admin/api/posts/export-jobs', {
|
||||
method: 'POST',
|
||||
body: {
|
||||
chunkSize: 100,
|
||||
retentionDays: 100
|
||||
}
|
||||
body: createPostExportRequestBody()
|
||||
})
|
||||
await refreshPostExportJobs()
|
||||
showToast('success', '게시물 Export 작업이 등록되었습니다.')
|
||||
@@ -512,6 +585,40 @@ const requestPostExport = async () => {
|
||||
*/
|
||||
const getPostExportDownloadUrl = (file) => `/admin/api/posts/export-jobs/${file.id}/download`
|
||||
|
||||
/**
|
||||
* Export 작업 삭제 중 여부
|
||||
* @param {string} jobId - 작업 ID
|
||||
* @returns {boolean} 삭제 중 여부
|
||||
*/
|
||||
const isDeletingPostExportJob = (jobId) => deletingPostExportJobIds.value.includes(jobId)
|
||||
|
||||
/**
|
||||
* Export 작업을 삭제한다.
|
||||
* @param {Object} job - Export 작업
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
const deletePostExportJob = async (job) => {
|
||||
if (!job?.id || isDeletingPostExportJob(job.id)) {
|
||||
return
|
||||
}
|
||||
|
||||
deletingPostExportJobIds.value = [...deletingPostExportJobIds.value, job.id]
|
||||
showToast('info', 'Export 백업 파일을 삭제하는 중입니다.')
|
||||
|
||||
try {
|
||||
await $fetch(`/admin/api/posts/export-jobs/${job.id}`, {
|
||||
method: 'DELETE'
|
||||
})
|
||||
await refreshPostExportJobs()
|
||||
showToast('success', 'Export 백업 파일을 삭제했습니다.')
|
||||
} catch (error) {
|
||||
errorMessage.value = error?.data?.message || 'Export 백업 파일을 삭제하지 못했습니다.'
|
||||
showToast('error', errorMessage.value)
|
||||
} finally {
|
||||
deletingPostExportJobIds.value = deletingPostExportJobIds.value.filter((id) => id !== job.id)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 로고 파일 선택창을 연다.
|
||||
* @returns {void}
|
||||
@@ -1826,24 +1933,100 @@ onBeforeUnmount(() => {
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="admin-settings-screen__export-actions mt-5 flex flex-col gap-3 rounded-lg border border-[#e6e8eb] bg-[#fbfcfd] p-4 md:flex-row md:items-center md:justify-between">
|
||||
<div class="admin-settings-screen__export-actions mt-5 grid gap-4 rounded-lg border border-[#e6e8eb] bg-[#fbfcfd] p-4">
|
||||
<div class="min-w-0">
|
||||
<p class="text-sm font-semibold text-[#15171a]">
|
||||
Obsidian 호환 백업 준비
|
||||
</p>
|
||||
<p class="mt-1 text-sm leading-relaxed text-[#657080]">
|
||||
100개 단위 분할 zip 계획을 만들고, 산출물은 최대 100일 동안 보관합니다.
|
||||
전체·연도·월·직접 범위로 게시물을 골라 100개 단위 ZIP 백업을 만듭니다.
|
||||
</p>
|
||||
</div>
|
||||
<button
|
||||
class="admin-settings-screen__export-request inline-flex h-10 shrink-0 cursor-pointer items-center justify-center rounded-md bg-[#15171a] px-4 text-sm font-semibold text-white transition hover:bg-black disabled:cursor-not-allowed disabled:bg-[#c7cdd4]"
|
||||
type="button"
|
||||
:disabled="!canRequestPostExport"
|
||||
:title="postExportRequestTitle"
|
||||
@click="requestPostExport"
|
||||
>
|
||||
{{ requestingPostExport ? '요청 중...' : hasActivePostExportJobs ? 'Export 진행 중' : 'Export 요청' }}
|
||||
</button>
|
||||
<div class="grid gap-3">
|
||||
<div class="admin-settings-screen__export-range grid gap-2 md:grid-cols-4">
|
||||
<label class="grid gap-1 text-xs font-semibold text-[#5d6673]">
|
||||
범위
|
||||
<select
|
||||
v-model="postExportDateRangeMode"
|
||||
class="h-10 rounded-md border border-[#dce0e5] bg-white px-3 text-sm font-semibold text-[#15171a] outline-none focus:border-[#15171a] focus:ring-1 focus:ring-[#15171a]"
|
||||
>
|
||||
<option value="all">전체</option>
|
||||
<option value="year">특정년</option>
|
||||
<option value="month">특정월</option>
|
||||
<option value="custom">직접 지정</option>
|
||||
</select>
|
||||
</label>
|
||||
<label
|
||||
v-if="postExportDateRangeMode === 'year' || postExportDateRangeMode === 'month'"
|
||||
class="grid gap-1 text-xs font-semibold text-[#5d6673]"
|
||||
>
|
||||
연도
|
||||
<select
|
||||
v-model.number="postExportYear"
|
||||
class="h-10 rounded-md border border-[#dce0e5] bg-white px-3 text-sm font-semibold text-[#15171a] outline-none focus:border-[#15171a] focus:ring-1 focus:ring-[#15171a]"
|
||||
>
|
||||
<option
|
||||
v-for="year in postExportYearOptions"
|
||||
:key="year"
|
||||
:value="year"
|
||||
>
|
||||
{{ year }}
|
||||
</option>
|
||||
</select>
|
||||
</label>
|
||||
<label
|
||||
v-if="postExportDateRangeMode === 'month'"
|
||||
class="grid gap-1 text-xs font-semibold text-[#5d6673]"
|
||||
>
|
||||
월
|
||||
<select
|
||||
v-model.number="postExportMonth"
|
||||
class="h-10 rounded-md border border-[#dce0e5] bg-white px-3 text-sm font-semibold text-[#15171a] outline-none focus:border-[#15171a] focus:ring-1 focus:ring-[#15171a]"
|
||||
>
|
||||
<option
|
||||
v-for="month in postExportMonthOptions"
|
||||
:key="month"
|
||||
:value="month"
|
||||
>
|
||||
{{ month }}월
|
||||
</option>
|
||||
</select>
|
||||
</label>
|
||||
<label
|
||||
v-if="postExportDateRangeMode === 'custom'"
|
||||
class="grid gap-1 text-xs font-semibold text-[#5d6673]"
|
||||
>
|
||||
시작일
|
||||
<input
|
||||
v-model="postExportDateFrom"
|
||||
class="h-10 rounded-md border border-[#dce0e5] bg-white px-3 text-sm font-semibold text-[#15171a] outline-none focus:border-[#15171a] focus:ring-1 focus:ring-[#15171a]"
|
||||
type="date"
|
||||
>
|
||||
</label>
|
||||
<label
|
||||
v-if="postExportDateRangeMode === 'custom'"
|
||||
class="grid gap-1 text-xs font-semibold text-[#5d6673]"
|
||||
>
|
||||
종료일
|
||||
<input
|
||||
v-model="postExportDateTo"
|
||||
class="h-10 rounded-md border border-[#dce0e5] bg-white px-3 text-sm font-semibold text-[#15171a] outline-none focus:border-[#15171a] focus:ring-1 focus:ring-[#15171a]"
|
||||
type="date"
|
||||
>
|
||||
</label>
|
||||
</div>
|
||||
<div class="flex items-center justify-end">
|
||||
<button
|
||||
class="admin-settings-screen__export-request inline-flex h-10 shrink-0 cursor-pointer items-center justify-center rounded-md bg-[#15171a] px-4 text-sm font-semibold text-white transition hover:bg-black disabled:cursor-not-allowed disabled:bg-[#c7cdd4]"
|
||||
type="button"
|
||||
:disabled="!canRequestPostExport"
|
||||
:title="postExportRequestTitle"
|
||||
@click="requestPostExport"
|
||||
>
|
||||
{{ requestingPostExport ? '요청 중...' : hasActivePostExportJobs ? 'Export 진행 중' : 'Export 요청' }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="admin-settings-screen__export-list mt-5">
|
||||
@@ -1886,18 +2069,31 @@ onBeforeUnmount(() => {
|
||||
<span class="text-sm text-[#9aa3ad]">
|
||||
{{ job.files.length }}개 파일
|
||||
</span>
|
||||
<span class="text-sm text-[#657080]">
|
||||
{{ job.rangeLabel || '전체' }}
|
||||
</span>
|
||||
</div>
|
||||
<p class="mt-2 text-xs text-[#657080]">
|
||||
요청: {{ formatPostDateTime(job.createdAt) }} · 만료: {{ formatPostDateTime(job.expiresAt) }}
|
||||
</p>
|
||||
</div>
|
||||
<button
|
||||
class="inline-flex h-9 shrink-0 cursor-not-allowed items-center justify-center rounded-md border border-[#dce0e5] px-3 text-xs font-semibold text-[#9aa3ad]"
|
||||
type="button"
|
||||
disabled
|
||||
>
|
||||
일괄 다운로드 준비 중
|
||||
</button>
|
||||
<div class="flex shrink-0 items-center gap-2">
|
||||
<button
|
||||
class="inline-flex h-9 shrink-0 cursor-not-allowed items-center justify-center rounded-md border border-[#dce0e5] px-3 text-xs font-semibold text-[#9aa3ad]"
|
||||
type="button"
|
||||
disabled
|
||||
>
|
||||
일괄 다운로드 준비 중
|
||||
</button>
|
||||
<button
|
||||
class="inline-flex h-9 shrink-0 cursor-pointer items-center justify-center rounded-md border border-[#ffd5d5] px-3 text-xs font-semibold text-[#d64545] transition hover:bg-[#fff3f3] disabled:cursor-not-allowed disabled:border-[#e1e5ea] disabled:text-[#a6b0bb]"
|
||||
type="button"
|
||||
:disabled="job.status === 'queued' || job.status === 'processing' || isDeletingPostExportJob(job.id)"
|
||||
@click="deletePostExportJob(job)"
|
||||
>
|
||||
{{ isDeletingPostExportJob(job.id) ? '삭제 중' : '삭제' }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="admin-settings-screen__export-progress mt-4 rounded-lg border border-[#edf0f3] bg-[#fbfcfd] p-3">
|
||||
|
||||
Reference in New Issue
Block a user