|
| 1 | +from datetime import datetime, timedelta, timezone |
| 2 | +from pathlib import Path |
1 | 3 | import requests |
2 | | -from fastapi import APIRouter, Depends, File, UploadFile |
3 | | -from sqlmodel import Session |
| 4 | +from fastapi import APIRouter, Depends, File, UploadFile, Query |
| 5 | +from sqlmodel import Session, select |
4 | 6 |
|
5 | | -from app.api.deps import get_db |
| 7 | +from app.api.deps import get_db, verify_api_key |
6 | 8 | from app.api.schemas.forms import ( |
7 | 9 | FormFill, |
8 | 10 | FormFillResponse, |
9 | 11 | ModelsResponse, |
10 | 12 | TranscriptionResponse, |
11 | 13 | ) |
12 | | -from app.core.config import OLLAMA_HOST, OLLAMA_MODEL, WHISPER_HOST |
| 14 | +from app.core.config import OLLAMA_HOST, OLLAMA_MODEL, WHISPER_HOST, BASE_DIR, RETENTION_PERIOD_DAYS |
13 | 15 | from app.core.errors.base import AppError |
14 | | -from app.db.repositories import create_form, get_template |
15 | | -from app.models import FormSubmission, Template |
| 16 | +from app.db.repositories import create_form, get_template, get_form_submission, delete_form_submission |
| 17 | +from app.models import FormSubmission |
16 | 18 | from app.services.controller import Controller |
17 | 19 |
|
| 20 | +PROJECT_ROOT = BASE_DIR |
| 21 | + |
| 22 | +def _resolve_project_file(file_path: str) -> Path: |
| 23 | + raw_path = (file_path or "").strip() |
| 24 | + if not raw_path: |
| 25 | + raise AppError("Path is required", status_code=400) |
| 26 | + |
| 27 | + candidate = Path(raw_path) |
| 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 AppError("Path must be inside the project", status_code=400) |
| 35 | + |
| 36 | + return candidate |
| 37 | + |
18 | 38 | router = APIRouter(prefix="/forms", tags=["forms"]) |
19 | 39 |
|
20 | 40 |
|
@@ -106,6 +126,48 @@ def transcribe(audio: UploadFile = File(...)): |
106 | 126 | return TranscriptionResponse(text=text) |
107 | 127 |
|
108 | 128 |
|
| 129 | +@router.delete("/{submission_id}", dependencies=[Depends(verify_api_key)]) |
| 130 | +def delete_submission_endpoint(submission_id: int, db: Session = Depends(get_db)): |
| 131 | + sub = get_form_submission(db, submission_id) |
| 132 | + if not sub: |
| 133 | + raise AppError("Submission not found", status_code=404, error_code="SUBMISSION_NOT_FOUND") |
| 134 | + |
| 135 | + if sub.output_pdf_path: |
| 136 | + try: |
| 137 | + resolved_out = _resolve_project_file(sub.output_pdf_path) |
| 138 | + if resolved_out.exists() and resolved_out.is_file(): |
| 139 | + resolved_out.unlink() |
| 140 | + except Exception: |
| 141 | + pass |
| 142 | + |
| 143 | + delete_form_submission(db, sub) |
| 144 | + return {"status": "success", "message": "Submission and associated output file deleted"} |
| 145 | + |
| 146 | + |
| 147 | +@router.post("/purge", dependencies=[Depends(verify_api_key)]) |
| 148 | +def purge_submissions_endpoint(days: int = Query(default=None), db: Session = Depends(get_db)): |
| 149 | + retention_days = days if days is not None else RETENTION_PERIOD_DAYS |
| 150 | + cutoff_date = datetime.now(timezone.utc) - timedelta(days=retention_days) |
| 151 | + |
| 152 | + statement = select(FormSubmission).where(FormSubmission.created_at < cutoff_date) |
| 153 | + submissions = list(db.exec(statement)) |
| 154 | + |
| 155 | + purged_count = 0 |
| 156 | + for sub in submissions: |
| 157 | + if sub.output_pdf_path: |
| 158 | + try: |
| 159 | + resolved_out = _resolve_project_file(sub.output_pdf_path) |
| 160 | + if resolved_out.exists() and resolved_out.is_file(): |
| 161 | + resolved_out.unlink() |
| 162 | + except Exception: |
| 163 | + pass |
| 164 | + delete_form_submission(db, sub) |
| 165 | + purged_count += 1 |
| 166 | + |
| 167 | + return {"status": "success", "purged_count": purged_count, "retention_days_used": retention_days} |
| 168 | + |
| 169 | + |
| 170 | + |
109 | 171 | @router.get("/submissions") |
110 | 172 | def get_submissions(db: Session = Depends(get_db)): |
111 | 173 | from sqlmodel import select |
|
0 commit comments