HR-ATS-Portal/backend/s3/app.py

110 lines
3.8 KiB
Python

from fastapi import APIRouter,Depends,File,Form,HTTPException,Query,UploadFile
from fastapi.responses import JSONResponse
from pydantic import BaseModel
from s3.views import S3Storage
from users.permissions import PermissionTag,require_permission
from dotenv import load_dotenv
load_dotenv()
router=APIRouter()
class DeleteObjectBody(BaseModel):
key: str
@router.get("/s3/health")
async def s3_health():
"""Bucket reachability — unauthenticated like GET /sheet/health."""
try:
service=S3Storage()
data=await service.health_check()
return JSONResponse(content={"data":data,"total":1,"status_code":200})
except HTTPException:
raise
except Exception as e:
raise HTTPException(status_code=500,detail=str(e))
@router.post("/s3/upload")
async def upload_s3_file(
file: UploadFile=File(...),
source: str=Form(...),
record_id: str=Form(...),
owner_id: str=Form(...),
current_user: dict=Depends(require_permission(PermissionTag.CANDIDATES_CREATE,PermissionTag.SETTINGS_EDIT,require_all=False)),
):
"""PDF only. Private PutObject under {source}/{record_id}/{owner_id}/{file}.pdf."""
try:
service=S3Storage()
data=await service.upload_for_record(file,source=source,record_id=record_id,owner_id=owner_id)
return JSONResponse(content={"data":data,"total":1,"status_code":200})
except HTTPException:
raise
except Exception as e:
raise HTTPException(status_code=500,detail=str(e))
@router.get("/s3/url")
async def fetch_s3_url(
key: str=Query(...),
current_user: dict=Depends(require_permission(PermissionTag.CANDIDATES_VIEW,PermissionTag.SETTINGS_VIEW,require_all=False)),
):
"""Stable private object address stored in file_path (not anonymously openable)."""
try:
service=S3Storage()
data=await service.object_url(key)
return JSONResponse(content={"data":data,"total":1,"status_code":200})
except HTTPException:
raise
except Exception as e:
raise HTTPException(status_code=500,detail=str(e))
@router.get("/s3/open")
async def open_s3_file(
key: str=Query(...,description="S3 key or stored file_path URL"),
expires_in: int | None=Query(None,ge=60,le=604800),
current_user: dict=Depends(require_permission(PermissionTag.CANDIDATES_VIEW,PermissionTag.SETTINGS_VIEW,PermissionTag.INBOX_VIEW,require_all=False)),
):
"""Short-lived presigned GET for a private CV — open this URL in the browser."""
try:
service=S3Storage()
data=await service.open_url(key,expires_in=expires_in)
return JSONResponse(content={"data":data,"total":1,"status_code":200})
except HTTPException:
raise
except Exception as e:
raise HTTPException(status_code=500,detail=str(e))
@router.get("/s3/download")
async def download_s3_file(
key: str=Query(...,description="S3 key or stored file_path URL"),
current_user: dict=Depends(require_permission(PermissionTag.CANDIDATES_VIEW,PermissionTag.SETTINGS_VIEW,require_all=False)),
):
"""Stream a private PDF through the API (IAM GetObject — no public bucket)."""
try:
service=S3Storage()
return await service.download_file(key)
except HTTPException:
raise
except Exception as e:
raise HTTPException(status_code=500,detail=str(e))
@router.post("/s3/delete")
async def delete_s3_file(
payload: DeleteObjectBody,
current_user: dict=Depends(require_permission(PermissionTag.CANDIDATES_DELETE,PermissionTag.SETTINGS_DELETE,require_all=False)),
):
try:
service=S3Storage()
data=await service.delete_file(payload.key)
return JSONResponse(content={"data":data,"total":1,"status_code":200})
except HTTPException:
raise
except Exception as e:
raise HTTPException(status_code=500,detail=str(e))