feat(B01,B03): 프로젝트 생성 전 임시 보관함 (temp upload)

라이다 원본은 업로드에 오래 걸려 프로젝트 정보 확정 전에 미리 올릴 수 있어야 한다.
계정에 묶인 임시 보관함을 만들고, 나중에 만든 프로젝트로 자료를 옮겨 쓴다.

저장·DB
- storage/tmp/{user_id}/{batch_id}/ 아래에 프로젝트 저장소와 동일한 구조를 써서
  청크 저장·병합 엔진(resolve_upload_destination/merge_upload_chunks)을 그대로 재사용
- 010_temp_upload.sql: temp_upload_batches / temp_upload_files 신설,
  upload_sessions.project_id NULL 허용 + temp_batch_id 추가(FK명 조회 후 재생성)
- config: TEMP_UPLOAD_DIR_NAME / TEMP_UPLOAD_RETENTION_DAYS(30) /
  TEMP_UPLOAD_CLEANUP_INTERVAL_HOURS(6)

백엔드
- B03_FileInput_Router_Temp.py: 묶음 생성·목록·삭제, 일반/청크 업로드, finalize,
  이어올리기 상태 조회, 프로젝트 연결(attach)
- attach: 파일 이동 후 input_files 등록, stage 0 완료, WF1·자동 설계 체인 트리거
- common_util_temp_cleanup.py: 완료 시각 기준 만료분 주기 삭제(서버 시작 시 1회 포함)

프론트엔드
- B01 대시보드 임시 보관함 섹션: 프로젝트 등록과 같은 폼 + 보관 목록.
  진행률은 모달이 아니라 리스트 행에 표시, 새로고침 후 이어올리기 지원
- B03 업로드 컨테이너 내부 불러오기 버튼과 선택 모달.
  완료된 묶음만 노출하고, 선택 후 업로드를 누르면 이동과 분석으로 이어짐

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-08 12:55:14 +09:00
co-authored by Claude Fable 5
parent 336335c611
commit 17834d8189
16 changed files with 2251 additions and 1 deletions
+9
View File
@@ -28,6 +28,10 @@ from A09_Security.A09_Security_Router import router as a09_security_router
from B01_Dashboard.B01_Dashboard_Router import router as b01_dashboard_router
from B02_ProjRegister.B02_ProjRegister_Router import router as b02_proj_register_router
from B03_FileInput.B03_FileInput_Router import router as b03_file_input_router
from B03_FileInput.B03_FileInput_Router_Temp import (
attach_router as b03_temp_attach_router,
)
from B03_FileInput.B03_FileInput_Router_Temp import router as b03_temp_upload_router
from B04_PreProcess.B04_PreProcess_Router import router as b04_surface_router
from B04_PreProcess.B04_PreProcess_Router_Basins import router as b04_basins_router
from B04_PreProcess.B04_PreProcess_Router_Contour import router as b04_surface_contour_router
@@ -44,6 +48,7 @@ from B07_Quantity.B07_Quantity_Router import router as b07_quantity_router
from B08_DesignDetail.B08_DesignDetail_Router import router as b08_design_router
from common_util.common_util_auth import require_company, verify_session
from common_util.common_util_resource_monitor import sample_resources_loop
from common_util.common_util_temp_cleanup import cleanup_expired_temp_uploads_loop
from config.config_db import close_db_pool, get_db_pool, init_db_pool
# 설정 import
@@ -259,6 +264,7 @@ async def lifespan(app: FastAPI):
await connection.commit()
cleanup_task = asyncio.create_task(cleanup_expired_sessions())
resource_task = asyncio.create_task(sample_resources_loop())
temp_cleanup_task = asyncio.create_task(cleanup_expired_temp_uploads_loop())
yield
@@ -266,6 +272,7 @@ async def lifespan(app: FastAPI):
logger.info("앱 종료 중...")
cleanup_task.cancel()
resource_task.cancel()
temp_cleanup_task.cancel()
stop_frontend_dev()
await close_db_pool()
logger.info("✓ DB 풀 종료 완료")
@@ -349,6 +356,8 @@ app.include_router(b02_proj_register_router)
protected = [Depends(verify_session)]
protected_with_company = [Depends(verify_session), Depends(require_company)]
app.include_router(b03_file_input_router, dependencies=protected_with_company)
app.include_router(b03_temp_upload_router, dependencies=protected)
app.include_router(b03_temp_attach_router, dependencies=protected_with_company)
app.include_router(b04_surface_router, dependencies=protected_with_company)
app.include_router(b04_surface_contour_router, dependencies=protected_with_company)
app.include_router(b04_surface_gis_router, dependencies=protected_with_company)