/* ============================================================================= * B01_Dashboard_Api_Temp.ts * 임시 보관함(프로젝트 생성 전 업로드) API 클라이언트 * * 백엔드 계약 (B03_FileInput_Router_Temp.py): * POST /api/temp-uploads 묶음 생성 * GET /api/temp-uploads 내 보관함 목록 * DELETE /api/temp-uploads/{batch_id} 묶음 삭제 * POST /api/temp-uploads/{batch_id}/files 작은 파일 저장 * DELETE /api/temp-uploads/{batch_id}/files/{type} 파일 1건 삭제 * POST /api/temp-uploads/{batch_id}/upload-sessions 청크 세션 생성 * POST /api/temp-uploads/{batch_id}/chunks 청크 저장 * POST /api/temp-uploads/{batch_id}/finalize 병합·완료 * GET /api/temp-uploads/{batch_id}/upload-status/{s} 이어올리기 조회 * POST /api/projects/{id}/temp-uploads/{batch_id}/attach 프로젝트로 이동 * ========================================================================== */ import { API_BASE_URL } from "@config/config_frontend"; export interface TempBatchFile { file_type: string; original_filename: string; file_size_bytes: number; crs_epsg: number | null; } export interface TempBatchPendingSession { upload_session_id: string; original_filename: string; file_size_bytes: number; total_chunks: number; completed_chunks: number; progress_percent: number; } export interface TempBatchItem { batch_id: string; name: string; memo: string | null; status: string; files: TempBatchFile[]; pending_sessions: TempBatchPendingSession[]; total_size_bytes: number; required_complete: boolean; completed_at: string | null; expires_at: string | null; linked_project_id: string | null; created_at: string | null; } export interface TempBatchListResponse { status: string; batches: TempBatchItem[]; retention_days: number; } async function requestJson(path: string, init: RequestInit = {}): Promise { const response = await fetch(`${API_BASE_URL}${path}`, { ...init, credentials: "include", headers: init.body instanceof FormData ? init.headers : { "Content-Type": "application/json", ...(init.headers ?? {}), }, }); const payload = await response.json().catch(() => ({})); if (!response.ok || payload.status === "error") { throw new Error(payload.message ?? `HTTP ${response.status}`); } return payload as T; } export async function createTempBatch( name: string, memo?: string, ): Promise<{ batch_id: string; name: string }> { return requestJson("/temp-uploads", { method: "POST", body: JSON.stringify({ name, memo: memo || null }), }); } export async function fetchTempBatches(): Promise { return requestJson("/temp-uploads", { method: "GET" }); } export async function deleteTempBatch(batchId: string): Promise { await requestJson(`/temp-uploads/${encodeURIComponent(batchId)}`, { method: "DELETE" }); } /** 묶음 안 파일 1건 삭제 — 임시 저장소의 실제 파일도 함께 지워진다. */ export async function deleteTempBatchFile(batchId: string, fileType: string): Promise { await requestJson( `/temp-uploads/${encodeURIComponent(batchId)}/files/${encodeURIComponent(fileType)}`, { method: "DELETE" }, ); } /** 작은 파일(csv·prj·tfw·tif)은 한 번에 보낸다. */ export async function uploadTempBatchFiles( batchId: string, files: readonly File[], ): Promise<{ required_complete: boolean }> { const form = new FormData(); for (const file of files) form.append("files", file, file.name); return requestJson(`/temp-uploads/${encodeURIComponent(batchId)}/files`, { method: "POST", body: form, }); } export async function createTempUploadSession( batchId: string, file: File, chunkSizeBytes: number, ): Promise<{ upload_session_id: string; total_chunks: number }> { return requestJson(`/temp-uploads/${encodeURIComponent(batchId)}/upload-sessions`, { method: "POST", body: JSON.stringify({ original_filename: file.name, size_bytes: file.size, chunk_size_bytes: chunkSizeBytes, }), }); } export async function uploadTempChunk( batchId: string, sessionId: string, chunkIndex: number, chunk: Blob, ): Promise<{ completed_chunks: number }> { const form = new FormData(); form.append("session_id", sessionId); form.append("chunk_index", String(chunkIndex)); form.append("chunk_data", chunk, `chunk_${chunkIndex}`); return requestJson(`/temp-uploads/${encodeURIComponent(batchId)}/chunks`, { method: "POST", body: form, }); } export async function finalizeTempUpload( batchId: string, sessionId: string, totalChunks: number, ): Promise<{ required_complete: boolean }> { return requestJson(`/temp-uploads/${encodeURIComponent(batchId)}/finalize`, { method: "POST", body: JSON.stringify({ session_id: sessionId, total_chunks: totalChunks, complete_upload: true, }), }); } /** 이어올리기 — 이미 서버에 올라간 청크 번호. */ export async function fetchTempUploadStatus( batchId: string, sessionId: string, ): Promise<{ completed_chunk_indexes: number[]; total_chunks: number }> { return requestJson( `/temp-uploads/${encodeURIComponent(batchId)}/upload-status/${encodeURIComponent(sessionId)}`, { method: "GET" }, ); } export interface TempBatchAttachResponse { status: string; project_id: string; batch_id: string; moved_files: number; analysis_started: boolean; } /** 보관함 자료를 프로젝트 영구저장소로 옮기고 초기 분석을 시작한다. */ export async function attachTempBatch( projectId: string, batchId: string, ): Promise { return requestJson( `/projects/${encodeURIComponent(projectId)}/temp-uploads/${encodeURIComponent(batchId)}/attach`, { method: "POST" }, ); }