1- from fastapi import APIRouter , Depends
1+ from datetime import datetime
2+ from pathlib import Path
3+
4+ from fastapi import APIRouter , Depends , File , Form , HTTPException , Query , UploadFile
5+ from fastapi .responses import FileResponse
26from sqlmodel import Session
37from api .deps import get_db
4- from api .schemas .templates import TemplateCreate , TemplateResponse
5- from api .db .repositories import create_template
8+ from api .schemas .templates import (
9+ TemplateCreate ,
10+ TemplateResponse ,
11+ TemplateUploadResponse ,
12+ )
13+ from api .db .repositories import create_template , list_templates
614from api .db .models import Template
715from src .controller import Controller
816
917router = APIRouter (prefix = "/templates" , tags = ["templates" ])
18+ PROJECT_ROOT = Path (__file__ ).resolve ().parents [2 ]
19+ DEFAULT_TEMPLATE_DIR = "src/inputs"
20+
21+
22+ def _resolve_target_directory (directory : str ) -> Path :
23+ dir_value = (directory or DEFAULT_TEMPLATE_DIR ).strip ()
24+ if not dir_value :
25+ raise HTTPException (status_code = 400 , detail = "Directory is required." )
26+
27+ candidate = Path (dir_value )
28+ if not candidate .is_absolute ():
29+ candidate = (PROJECT_ROOT / candidate ).resolve ()
30+ else :
31+ candidate = candidate .resolve ()
32+
33+ if candidate != PROJECT_ROOT and PROJECT_ROOT not in candidate .parents :
34+ raise HTTPException (status_code = 400 , detail = "Directory must be inside the project." )
35+
36+ return candidate
37+
38+
39+ def _resolve_project_file (file_path : str ) -> Path :
40+ raw_path = (file_path or "" ).strip ()
41+ if not raw_path :
42+ raise HTTPException (status_code = 400 , detail = "Path is required." )
43+
44+ candidate = Path (raw_path )
45+ if not candidate .is_absolute ():
46+ candidate = (PROJECT_ROOT / candidate ).resolve ()
47+ else :
48+ candidate = candidate .resolve ()
49+
50+ if candidate != PROJECT_ROOT and PROJECT_ROOT not in candidate .parents :
51+ raise HTTPException (status_code = 400 , detail = "Path must be inside the project." )
52+
53+ return candidate
54+
55+
56+ @router .post ("/upload" , response_model = TemplateUploadResponse )
57+ async def upload_template_pdf (
58+ file : UploadFile = File (...),
59+ directory : str = Form (DEFAULT_TEMPLATE_DIR ),
60+ ):
61+ filename = Path (file .filename or "" ).name
62+ if not filename :
63+ raise HTTPException (status_code = 400 , detail = "A PDF filename is required." )
64+
65+ if not filename .lower ().endswith (".pdf" ):
66+ raise HTTPException (status_code = 400 , detail = "Only PDF files are supported." )
67+
68+ target_dir = _resolve_target_directory (directory )
69+ target_dir .mkdir (parents = True , exist_ok = True )
70+
71+ target_path = target_dir / filename
72+ if target_path .exists ():
73+ timestamp = datetime .utcnow ().strftime ("%Y%m%d_%H%M%S" )
74+ target_path = target_dir / f"{ target_path .stem } _{ timestamp } { target_path .suffix } "
75+
76+ content = await file .read ()
77+ with target_path .open ("wb" ) as output_file :
78+ output_file .write (content )
79+
80+ return TemplateUploadResponse (
81+ filename = target_path .name ,
82+ pdf_path = target_path .relative_to (PROJECT_ROOT ).as_posix (),
83+ )
84+
85+
86+ @router .get ("" , response_model = list [TemplateResponse ])
87+ def get_templates (db : Session = Depends (get_db )):
88+ return list_templates (db )
89+
90+
91+ @router .get ("/preview" )
92+ def preview_template_pdf (path : str = Query (..., description = "Project-relative PDF path" )):
93+ resolved_path = _resolve_project_file (path )
94+
95+ if not resolved_path .exists () or not resolved_path .is_file ():
96+ raise HTTPException (status_code = 404 , detail = "PDF file not found." )
97+
98+ if resolved_path .suffix .lower () != ".pdf" :
99+ raise HTTPException (status_code = 400 , detail = "Only PDF files can be previewed." )
100+
101+ return FileResponse (resolved_path , media_type = "application/pdf" , filename = resolved_path .name )
102+
10103
11104@router .post ("/create" , response_model = TemplateResponse )
12105def create (template : TemplateCreate , db : Session = Depends (get_db )):
13106 controller = Controller ()
14107 template_path = controller .create_template (template .pdf_path )
15108 tpl = Template (** template .model_dump (exclude = {"pdf_path" }), pdf_path = template_path )
16- return create_template (db , tpl )
109+ return create_template (db , tpl )
0 commit comments