Basisimplementierung für Mailversandt.
This commit is contained in:
parent
8505e9217a
commit
531b9cb10e
14 changed files with 520 additions and 41 deletions
79
app/routers/mail.py
Normal file
79
app/routers/mail.py
Normal file
|
|
@ -0,0 +1,79 @@
|
|||
# app/routers/mail.py
|
||||
from __future__ import annotations
|
||||
import base64
|
||||
from uuid import UUID
|
||||
from fastapi import APIRouter, UploadFile, File, Form, Depends, status, HTTPException
|
||||
from fastapi.encoders import jsonable_encoder
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from app.db import get_session
|
||||
from app.schemas import SendTestMailRequest, UploadMailResponse
|
||||
from app.schema_utils import job_to_schema
|
||||
from app.task_enqueue import enqueue_job_with_single_task
|
||||
from app.services import create_job, get_job_with_tasks
|
||||
from app.job_registry import routing_key
|
||||
from email import policy
|
||||
from email.parser import BytesParser
|
||||
|
||||
router = APIRouter(prefix="/api/v1/mail", tags=["mail"])
|
||||
|
||||
# TODO: vorhandene Auth-Dependency einhängen
|
||||
async def api_key_auth():
|
||||
return True
|
||||
|
||||
def _guess_domain_from_addresses(addresses: str | None) -> str:
|
||||
if not addresses:
|
||||
return "unknown.local"
|
||||
# nimmt erste Adresse, extrahiert Domain
|
||||
first = addresses.split(",")[0].strip()
|
||||
if "@" in first:
|
||||
return first.split("@")[-1].strip().lower()
|
||||
return "unknown.local"
|
||||
|
||||
@router.post("/send", status_code=status.HTTP_202_ACCEPTED)
|
||||
async def send_test_mail(
|
||||
req: SendTestMailRequest,
|
||||
_=Depends(api_key_auth),
|
||||
session: AsyncSession = Depends(get_session),
|
||||
):
|
||||
if not req.recipients:
|
||||
raise HTTPException(status_code=400, detail="At least one recipient required.")
|
||||
domain = req.recipients[0].split("@")[-1].lower()
|
||||
job = await create_job(
|
||||
session,
|
||||
job_type="MailSend",
|
||||
domain=domain,
|
||||
payload=req.model_dump(mode="json")
|
||||
)
|
||||
job = await get_job_with_tasks(session, job.id)
|
||||
return job_to_schema(job)
|
||||
|
||||
@router.post("/upload", response_model=UploadMailResponse, status_code=status.HTTP_202_ACCEPTED)
|
||||
async def upload_mail(
|
||||
mailbox_id: UUID = Form(...),
|
||||
eml: UploadFile = File(...),
|
||||
_=Depends(api_key_auth),
|
||||
session: AsyncSession = Depends(get_session),
|
||||
):
|
||||
raw = await eml.read()
|
||||
# Domain für Job.domain aus To/From ableiten
|
||||
try:
|
||||
msg = BytesParser(policy=policy.default).parsebytes(raw)
|
||||
domain = _guess_domain_from_addresses(msg.get("To")) or _guess_domain_from_addresses(msg.get("From"))
|
||||
except Exception:
|
||||
domain = "unknown.local"
|
||||
|
||||
payload = {
|
||||
"mailbox_id": str(mailbox_id),
|
||||
"raw_eml_b64": base64.b64encode(raw).decode("ascii"),
|
||||
}
|
||||
job_id, task_id = await enqueue_job_with_single_task(
|
||||
session,
|
||||
job_type="mail.store",
|
||||
domain=domain,
|
||||
payload=payload,
|
||||
task_name="mail.store",
|
||||
routing_key="mail.store",
|
||||
)
|
||||
return UploadMailResponse(id=UUID(task_id), message_id=None)
|
||||
|
||||
|
||||
Loading…
Add table
Add a link
Reference in a new issue