generated from coulomb/repo-seed
init: documentation and prototypes
This commit is contained in:
132
prototype-grok4.1/direktvermittlungde-backend/main.py
Normal file
132
prototype-grok4.1/direktvermittlungde-backend/main.py
Normal file
@@ -0,0 +1,132 @@
|
||||
import asyncio
|
||||
from concurrent.futures import ProcessPoolExecutor
|
||||
from fastapi import APIRouter, BackgroundTasks, Depends, FastAPI, File, HTTPException, Query, UploadFile, status
|
||||
from fastapi.security.utils import get_authorization_scheme_param
|
||||
|
||||
from .models import (
|
||||
DocumentEnvelope, ThreadType, InteractionThread, Message, ExportRequest, ExportJob,
|
||||
Document, Thread, Message as DBMessage, Metadata
|
||||
)
|
||||
from .database import async_session, init_db
|
||||
from .auth import get_current_user
|
||||
from .services import route_document, rate_limit, validate_encrypted_payload, get_s3_client, redis_settings
|
||||
from .workers import export_worker
|
||||
|
||||
# Router
|
||||
router = APIRouter()
|
||||
|
||||
@router.post("/documents", response_model=DocumentEnvelope, status_code=201)
|
||||
async def upload_document(
|
||||
background_tasks: BackgroundTasks,
|
||||
metadata: Metadata,
|
||||
file: UploadFile = File(...),
|
||||
current_user=Depends(get_current_user),
|
||||
db: AsyncSession = Depends(async_session),
|
||||
redis=Depends(redis_settings.connection)
|
||||
):
|
||||
if current_user["role"] != "CITIZEN":
|
||||
raise HTTPException(403, "Only citizens can submit")
|
||||
|
||||
await rate_limit(current_user["user_id"], redis)
|
||||
|
||||
content = await file.read()
|
||||
encrypted_payload = base64.b64encode(content).decode('utf-8')
|
||||
|
||||
loop = asyncio.get_running_loop()
|
||||
is_valid = await loop.run_in_executor(ProcessPoolExecutor(), validate_encrypted_payload, encrypted_payload)
|
||||
if not is_valid:
|
||||
raise HTTPException(400, "Invalid encrypted payload")
|
||||
|
||||
assigned_unit = route_document(metadata)
|
||||
|
||||
s3 = await get_s3_client()
|
||||
storage_path = f"blobs/{uuid.uuid4()}.enc"
|
||||
await s3.put_object(Bucket="dvd-blobs", Key=storage_path, Body=content)
|
||||
|
||||
doc = Document(
|
||||
reference_number=metadata.reference_number,
|
||||
authority_id=metadata.authority_id,
|
||||
doc_type=metadata.doc_type,
|
||||
status="ROUTED" if assigned_unit != "Fallback-Team" else "RECEIVED",
|
||||
storage_path=storage_path,
|
||||
issued_at=metadata.issued_at,
|
||||
retention_date=datetime.now() + timedelta(days=30)
|
||||
)
|
||||
db.add(doc)
|
||||
await db.commit()
|
||||
await db.refresh(doc)
|
||||
|
||||
background_tasks.add_task(lambda: print(f"Notify {assigned_unit} about {doc.id}"))
|
||||
|
||||
return DocumentEnvelope(
|
||||
id=doc.id,
|
||||
metadata=metadata,
|
||||
encrypted_payload=encrypted_payload,
|
||||
status=doc.status,
|
||||
retention_date=doc.retention_date
|
||||
)
|
||||
|
||||
@router.post("/documents/{doc_id}/threads", response_model=InteractionThread, status_code=201)
|
||||
async def start_thread(
|
||||
doc_id: str,
|
||||
thread_data: ThreadType,
|
||||
current_user=Depends(get_current_user),
|
||||
db: AsyncSession = Depends(async_session)
|
||||
):
|
||||
if current_user["role"] != "CITIZEN":
|
||||
raise HTTPException(403)
|
||||
|
||||
doc = await db.get(Document, doc_id)
|
||||
if not doc:
|
||||
raise HTTPException(404, "Document not found")
|
||||
|
||||
thread = Thread(document_id=doc_id, type=thread_data.type, status="PENDING_OFFICIAL")
|
||||
db.add(thread)
|
||||
await db.commit()
|
||||
await db.refresh(thread)
|
||||
|
||||
if thread_data.initial_message:
|
||||
msg = DBMessage(thread_id=thread.id, sender_role="CITIZEN", content=base64.b64encode(thread_data.initial_message.encode()).decode())
|
||||
db.add(msg)
|
||||
await db.commit()
|
||||
|
||||
return InteractionThread(id=thread.id, document_id=doc_id, type=thread.type, status=thread.status)
|
||||
|
||||
@router.get("/threads/{thread_id}/messages", response_model=List[Message])
|
||||
async def get_messages(
|
||||
thread_id: str,
|
||||
limit: int = Query(default=20, ge=1, le=100),
|
||||
before=None,
|
||||
current_user=Depends(get_current_user),
|
||||
db: AsyncSession = Depends(async_session)
|
||||
):
|
||||
query = db.query(DBMessage).filter(DBMessage.thread_id == thread_id).order_by(DBMessage.timestamp.desc())
|
||||
if before:
|
||||
query = query.filter(DBMessage.timestamp < before)
|
||||
messages = await query.limit(limit).all()
|
||||
return [Message.model_validate(m) for m in reversed(messages)]
|
||||
|
||||
@router.post("/exports", response_model=ExportJob, status_code=202)
|
||||
async def request_export(
|
||||
export_data: ExportRequest,
|
||||
current_user=Depends(get_current_user),
|
||||
arq=Depends(redis_settings.worker)
|
||||
):
|
||||
if "official" not in current_user["scopes"]:
|
||||
raise HTTPException(403)
|
||||
|
||||
job_id = str(uuid.uuid4())
|
||||
await arq.enqueue_job("export_worker", export_data.dict(), job_id=job_id)
|
||||
return ExportJob(job_id=job_id)
|
||||
|
||||
# App
|
||||
app = FastAPI(title="DirektVermittlungDe API", version="1.0.0")
|
||||
app.include_router(router)
|
||||
|
||||
@app.on_event("startup")
|
||||
async def startup():
|
||||
await init_db()
|
||||
|
||||
@app.get("/")
|
||||
async def root():
|
||||
return {"message": "DirektVermittlungDe API"}
|
||||
Reference in New Issue
Block a user