refactor(upload): 프론트 pre-check 가 서버 공개 설정을 구독하도록 전환
프론트의 `MAX_UPLOAD_BYTES = 100 * 1000 * 1000` 하드코딩 상수를 제거하고 서버 `GET /api/config/public` 응답을 단일 진실 공급원으로 사용. pre-check 자체는 그대로 유지 (UX 개선 — 대용량 파일을 edge proxy 까지 올리기 전 클라이언트에서 즉시 차단). 값의 출처만 서버로 이동. 변경: - frontend/src/lib/stores/config.ts 신규 — publicConfig readable store * 첫 구독 시 `/config/public` 1회 fetch * fetch 실패 시 fallback 100MB 유지 (서버 enforcement 가 본선이라 안전) - +layout.svelte onMount 에서 prewarm refresh() 호출 - UploadDropzone.svelte 에서 `$derived` 로 store 값을 반응형 구독 * `maxBytes` / `maxBytesLabel` 을 파생 * 에러 토스트 문구도 동적 라벨 사용 (`100MB` 하드코딩 제거) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -2,13 +2,15 @@
|
||||
import { onMount } from 'svelte';
|
||||
import { api } from '$lib/api';
|
||||
import { addToast } from '$lib/stores/toast';
|
||||
import { publicConfig } from '$lib/stores/config';
|
||||
import { Upload } from 'lucide-svelte';
|
||||
|
||||
let { onupload = () => {} } = $props();
|
||||
|
||||
// 업로드 크기 한도 (SI 기준 100MB). 백엔드 설정과 동기화 필요 — 추후 서버에서 내려받는 구조로 전환 예정.
|
||||
// 100MB 초과 파일은 NAS 의 PKM/Inbox 폴더에 직접 두면 file_watcher 가 감시 경로로 수집.
|
||||
const MAX_UPLOAD_BYTES = 100 * 1000 * 1000;
|
||||
// 업로드 크기 한도는 서버 `upload.max_bytes` 가 authoritative. 여기서는 pre-check UX 용으로만 사용.
|
||||
// 실제 enforcement 는 /documents/ POST 413 응답 (서버 스트리밍 검증) 이 담당.
|
||||
const maxBytes = $derived($publicConfig.upload.max_bytes);
|
||||
const maxBytesLabel = $derived(`${Math.round($publicConfig.upload.max_bytes / 1_000_000)}MB`);
|
||||
const NAS_FALLBACK_HINT = '대용량 파일은 NAS의 PKM/Inbox 폴더에 두면 자동 수집 대상이 됩니다. 감시 주기와 처리 대기열 상황에 따라 반영 시점은 달라질 수 있습니다.';
|
||||
|
||||
let dragging = $state(false);
|
||||
@@ -64,9 +66,9 @@
|
||||
const allFiles = Array.from(fileList || []);
|
||||
if (allFiles.length === 0) return;
|
||||
|
||||
// 사전 크기 검사 — 100MB 초과는 즉시 차단 + NAS file_watcher 안내
|
||||
const tooLarge = allFiles.filter(f => f.size > MAX_UPLOAD_BYTES);
|
||||
const files = allFiles.filter(f => f.size <= MAX_UPLOAD_BYTES);
|
||||
// 사전 크기 검사 — 서버 한도(maxBytes) 초과는 즉시 차단 + NAS file_watcher 안내
|
||||
const tooLarge = allFiles.filter(f => f.size > maxBytes);
|
||||
const files = allFiles.filter(f => f.size <= maxBytes);
|
||||
|
||||
if (tooLarge.length > 0) {
|
||||
const names = tooLarge
|
||||
@@ -74,7 +76,7 @@
|
||||
.join(', ');
|
||||
addToast(
|
||||
'error',
|
||||
`100MB 초과 파일은 업로드 불가 (${tooLarge.length}건: ${names}). ${NAS_FALLBACK_HINT}`,
|
||||
`${maxBytesLabel} 초과 파일은 업로드 불가 (${tooLarge.length}건: ${names}). ${NAS_FALLBACK_HINT}`,
|
||||
10000
|
||||
);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
// 공개 서버 설정 store.
|
||||
//
|
||||
// /api/config/public 을 초기 1회 fetch 해서 upload.max_bytes 등 프론트 UX 에
|
||||
// 필요한 값을 공급. 서버가 authoritative 소스이며, 여기서는 캐시만 제공.
|
||||
//
|
||||
// fetch 실패 시 하드코딩 fallback 값을 유지해 초기 렌더 깜빡임과 offline UX
|
||||
// 퇴행을 방지. 실제 size enforcement 는 서버 (/documents/ POST 413) 가 담당
|
||||
// 하므로 fallback 값이 일시적으로 서버와 불일치해도 보안적 영향 없음.
|
||||
//
|
||||
// API 응답 shape: app/api/config.py PublicConfigResponse 참조
|
||||
|
||||
import { writable } from 'svelte/store';
|
||||
import { api } from '$lib/api';
|
||||
|
||||
export interface PublicConfig {
|
||||
upload: {
|
||||
max_bytes: number;
|
||||
};
|
||||
}
|
||||
|
||||
// 서버 fetch 전 fallback. 실제 정책값은 서버 config.yaml `upload.max_bytes`.
|
||||
const FALLBACK_CONFIG: PublicConfig = {
|
||||
upload: {
|
||||
max_bytes: 100_000_000,
|
||||
},
|
||||
};
|
||||
|
||||
let fetched = false;
|
||||
let inFlight: Promise<void> | null = null;
|
||||
|
||||
const internal = writable<PublicConfig>(FALLBACK_CONFIG, (_set) => {
|
||||
// 첫 구독 시 1회만 fetch
|
||||
if (!fetched) {
|
||||
void refresh();
|
||||
}
|
||||
});
|
||||
|
||||
export const publicConfig = { subscribe: internal.subscribe };
|
||||
|
||||
export async function refresh(): Promise<void> {
|
||||
if (inFlight) return inFlight;
|
||||
inFlight = (async () => {
|
||||
try {
|
||||
const data = await api<PublicConfig>('/config/public');
|
||||
internal.set(data);
|
||||
fetched = true;
|
||||
} catch (err) {
|
||||
// fallback 유지. 서버 enforcement 가 본선이므로 UI fallback 은 안전.
|
||||
console.error('공개 설정 fetch 실패, fallback 사용:', err);
|
||||
} finally {
|
||||
inFlight = null;
|
||||
}
|
||||
})();
|
||||
return inFlight;
|
||||
}
|
||||
@@ -6,6 +6,7 @@
|
||||
import { Menu, EllipsisVertical } from 'lucide-svelte';
|
||||
import { isAuthenticated, user, tryRefresh, logout } from '$lib/stores/auth';
|
||||
import { toasts, removeToast } from '$lib/stores/toast';
|
||||
import { refresh as refreshPublicConfig } from '$lib/stores/config';
|
||||
import { ui } from '$lib/stores/uiState.svelte';
|
||||
import Sidebar from '$lib/components/Sidebar.svelte';
|
||||
import SystemStatusDot from '$lib/components/SystemStatusDot.svelte';
|
||||
@@ -38,6 +39,8 @@
|
||||
await tryRefresh();
|
||||
}
|
||||
authChecked = true;
|
||||
// 공개 설정 prewarm (인증 상태와 독립적). 실패 시 fallback 유지.
|
||||
void refreshPublicConfig();
|
||||
});
|
||||
|
||||
$effect(() => {
|
||||
|
||||
Reference in New Issue
Block a user