1- # ~/noteflow/Backend/routers/file.py
2-
31import os
42from datetime import datetime
53from typing import Optional , List
@@ -71,11 +69,12 @@ def run(cmd: list[str]):
7169
7270@router .post (
7371 "/upload" ,
74- summary = "폴더에 파일 업로드" ,
72+ summary = "폴더/노트에 파일 업로드 (note_id 있으면 노트 본문에도 삽입) " ,
7573 status_code = status .HTTP_201_CREATED
7674)
7775async def upload_file (
7876 folder_id : Optional [int ] = Form (None ),
77+ note_id : Optional [int ] = Form (None ),
7978 upload_file : UploadFile = File (...),
8079 db : Session = Depends (get_db ),
8180 current_user = Depends (get_current_user )
@@ -87,7 +86,7 @@ async def upload_file(
8786 user_dir = os .path .join (BASE_UPLOAD_DIR , str (current_user .u_id ))
8887 os .makedirs (user_dir , exist_ok = True )
8988
90- # 원본 파일명 그대로 저장 (동명이인 방지 )
89+ # 원본 파일명 그대로 저장 (중복 시 _1, _2 붙임 )
9190 saved_filename = orig_filename
9291 saved_path = os .path .join (user_dir , saved_filename )
9392 if os .path .exists (saved_path ):
@@ -110,10 +109,22 @@ async def upload_file(
110109 except Exception as e :
111110 raise HTTPException (status_code = 500 , detail = f"파일 저장 실패: { e } " )
112111
112+ # note_id가 있으면 해당 노트 확인
113+ note_obj = None
114+ if note_id is not None :
115+ note_obj = (
116+ db .query (NoteModel )
117+ .filter (NoteModel .id == note_id , NoteModel .user_id == current_user .u_id )
118+ .first ()
119+ )
120+ if not note_obj :
121+ raise HTTPException (status_code = 404 , detail = "해당 노트를 찾을 수 없습니다." )
122+
113123 # DB에 메타데이터 기록
114124 new_file = FileModel (
115125 user_id = current_user .u_id ,
116- folder_id = folder_id ,
126+ folder_id = None if note_id else folder_id ,
127+ note_id = note_id ,
117128 original_name = orig_filename ,
118129 saved_path = saved_path ,
119130 content_type = content_type
@@ -125,11 +136,25 @@ async def upload_file(
125136 base_url = os .getenv ("BASE_API_URL" , "http://localhost:8000" )
126137 download_url = f"{ base_url } /api/v1/files/download/{ new_file .id } "
127138
139+ # note_id가 있으면 content에도 삽입
140+ if note_obj :
141+ if content_type .startswith ("image/" ):
142+ embed = f"\n \n \n \n "
143+ elif content_type == "application/pdf" :
144+ embed = f"\n \n [{ new_file .original_name } ]({ download_url } ) (PDF 보기)\n \n "
145+ else :
146+ embed = f"\n \n [{ new_file .original_name } ]({ download_url } )\n \n "
147+
148+ note_obj .content = (note_obj .content or "" ) + embed
149+ db .commit ()
150+ db .refresh (note_obj )
151+
128152 return {
129153 "file_id" : new_file .id ,
130154 "url" : download_url ,
131155 "original_name" : new_file .original_name ,
132156 "folder_id" : new_file .folder_id ,
157+ "note_id" : new_file .note_id ,
133158 "content_type" : new_file .content_type ,
134159 "created_at" : new_file .created_at
135160 }
@@ -180,13 +205,6 @@ def download_file(
180205 if not os .path .exists (file_path ):
181206 raise HTTPException (status_code = 404 , detail = "서버에 파일이 존재하지 않습니다." )
182207
183- # filename_star = file_obj.original_name
184- # return FileResponse(
185- # path=file_path,
186- # media_type=file_obj.content_type,
187- # headers={"Content-Disposition": f"inline; filename*=UTF-8''{filename_star}"}
188- # )
189- # FastAPI가 내부에서 UTF-8로 인코딩된 Content-Disposition 헤더를 생성해 줌
190208 return FileResponse (
191209 path = file_path ,
192210 media_type = file_obj .content_type ,
@@ -201,7 +219,6 @@ def download_file(
201219 response_model = OCRResponse
202220)
203221async def ocr_and_create_note (
204- # 변경: 업로드 필드명 'file' 기본 + 과거 호환 'ocr_file' 동시 허용
205222 file : Optional [UploadFile ] = File (None , description = "기본 업로드 필드명" ),
206223 ocr_file : Optional [UploadFile ] = File (None , description = "과거 호환 업로드 필드명" ),
207224 folder_id : Optional [int ] = Form (None ),
@@ -210,21 +227,13 @@ async def ocr_and_create_note(
210227 db : Session = Depends (get_db ),
211228 current_user = Depends (get_current_user )
212229):
213- """
214- 변경 전: 이미지 전용 EasyOCR/TrOCR로 텍스트 추출 후 노트 생성.
215- 변경 후(추가/변경): 공통 파이프라인(utils.ocr.run_pipeline)으로 이미지/PDF/DOC/DOCX/HWP 처리.
216- - 예외는 200으로 내려가며, results=[] + warnings에 사유 기입.
217- - 결과 텍스트를 합쳐 비어있지 않으면 기존과 동일하게 노트를 생성.
218- """
219- # 업로드 파일 결정
220230 upload = file or ocr_file
221231 if upload is None :
222232 raise HTTPException (status_code = 400 , detail = "업로드 파일이 필요합니다. 필드명은 'file' 또는 'ocr_file'을 사용하세요." )
223233
224234 filename = upload .filename or "uploaded"
225235 mime = upload .content_type
226236
227- # 허용 확장자 확인 (불일치 시 200 + warnings)
228237 _ , ext = os .path .splitext (filename )
229238 ext = ext .lower ()
230239 if ext and ext not in ALLOWED_ALL_EXTS :
@@ -238,7 +247,6 @@ async def ocr_and_create_note(
238247 text = None ,
239248 )
240249
241- # 타입 판별 (보조적으로 unknown 방지)
242250 ftype = detect_type (filename , mime )
243251 if ftype == "unknown" :
244252 return OCRResponse (
@@ -268,7 +276,6 @@ async def ocr_and_create_note(
268276 note_id : Optional [int ] = None
269277 if merged_text :
270278 try :
271- # 추가/변경: 노트 제목을 업로드한 파일 이름으로 설정 (확장자 제거)
272279 base_title = os .path .splitext (filename )[0 ].strip () or "OCR 결과"
273280 new_note = NoteModel (
274281 user_id = current_user .u_id ,
@@ -297,24 +304,21 @@ async def upload_audio_and_transcribe(
297304 db : Session = Depends (get_db ),
298305 user = Depends (get_current_user )
299306):
300- # 📁 저장 경로 생성
301307 timestamp = datetime .now ().strftime ("%Y%m%d_%H%M%S" )
302308 filename = f"user{ user .u_id } _{ timestamp } _{ file .filename } "
303309 save_dir = os .path .join (BASE_UPLOAD_DIR , str (user .u_id ))
304310 os .makedirs (save_dir , exist_ok = True )
305311 save_path = os .path .join (save_dir , filename )
306312
307- # 📥 파일 저장
308313 with open (save_path , "wb" ) as f :
309314 f .write (await file .read ())
310315
311- # ✅ note_id가 있으면 folder_id는 무시
312316 folder_id_to_use = folder_id if note_id is None else None
313317
314- # 📦 files 테이블에 기록
315318 new_file = FileModel (
316319 user_id = user .u_id ,
317320 folder_id = folder_id_to_use ,
321+ note_id = note_id ,
318322 original_name = filename ,
319323 saved_path = save_path ,
320324 content_type = "audio"
@@ -323,7 +327,6 @@ async def upload_audio_and_transcribe(
323327 db .commit ()
324328 db .refresh (new_file )
325329
326- # 🧠 STT 처리
327330 try :
328331 import whisper
329332 model = whisper .load_model ("base" )
@@ -332,9 +335,7 @@ async def upload_audio_and_transcribe(
332335 except Exception as e :
333336 raise HTTPException (status_code = 500 , detail = f"STT 처리 실패: { e } " )
334337
335- # 📝 노트 처리
336338 if note_id :
337- # 기존 노트에 텍스트 추가
338339 note = db .query (NoteModel ).filter (
339340 NoteModel .id == note_id ,
340341 NoteModel .user_id == user .u_id
@@ -349,7 +350,6 @@ async def upload_audio_and_transcribe(
349350 db .refresh (note )
350351
351352 else :
352- # 새 노트 생성
353353 new_note = NoteModel (
354354 user_id = user .u_id ,
355355 folder_id = folder_id_to_use ,
@@ -364,10 +364,7 @@ async def upload_audio_and_transcribe(
364364 "message" : "STT 및 노트 저장 완료" ,
365365 "transcript" : transcript
366366 }
367+
367368@router .options ("/ocr" )
368369def ocr_cors_preflight () -> Response :
369- """CORS preflight용 OPTIONS 응답. 일부 프록시/클라이언트에서 405 회피.
370- 변경 전: 별도 OPTIONS 라우트 없음(미들웨어에 의존)
371- 변경 후(추가): 명시적으로 200을 반환
372- """
373370 return Response (status_code = 200 )
0 commit comments