b77b192b56
- fix: Starlette 1.0 changed TemplateResponse signature — request is now the first positional argument, not nested inside the context dict. All HTML routes returned 500 (unhashable dict in jinja2 template cache) after the image rebuild picked up the new starlette version. - fix: processed_mails (1.7M rows in production: 1 account × 12 rules × 141k inbox mails) made backup export hit 558 MB / 90s. Moved to opt-in checkbox in the UI alongside the logs option, default off. - yield_per for streaming the processed_mails query when included. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
130 lines
4.1 KiB
Python
130 lines
4.1 KiB
Python
import logging
|
|
from contextlib import asynccontextmanager
|
|
|
|
from fastapi import FastAPI
|
|
from fastapi.staticfiles import StaticFiles
|
|
from fastapi.templating import Jinja2Templates
|
|
|
|
from app.config import settings
|
|
from app.database import Base, engine
|
|
|
|
logging.basicConfig(
|
|
level=getattr(logging, settings.log_level.upper(), logging.INFO),
|
|
format="%(asctime)s [%(levelname)s] %(name)s: %(message)s",
|
|
)
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
@asynccontextmanager
|
|
async def lifespan(app: FastAPI):
|
|
logger.info("Starte IMAP Mail Filter Service...")
|
|
# Datenbank-Migration mit Alembic
|
|
from alembic.config import Config
|
|
from alembic import command
|
|
alembic_cfg = Config("alembic.ini")
|
|
command.upgrade(alembic_cfg, "head")
|
|
logger.info("Datenbank-Migration abgeschlossen.")
|
|
if settings.yaml_sync_on_startup:
|
|
from app.services.yaml_service import import_from_file
|
|
result = import_from_file()
|
|
logger.info("YAML-Startup-Import: %s", result)
|
|
from app.services.scheduler import start_scheduler, stop_scheduler
|
|
start_scheduler()
|
|
yield
|
|
stop_scheduler()
|
|
logger.info("Service wird beendet.")
|
|
|
|
|
|
app = FastAPI(title="IMAP Mail Filter Service", lifespan=lifespan)
|
|
app.mount("/static", StaticFiles(directory="app/static"), name="static")
|
|
templates = Jinja2Templates(directory="app/templates")
|
|
|
|
from fastapi import Depends, Request # noqa: E402
|
|
from sqlalchemy.orm import Session # noqa: E402
|
|
|
|
from app.database import get_db # noqa: E402
|
|
from app.models.db_models import Account # noqa: E402
|
|
from app.routers import accounts, filters, logs, yaml_sync # noqa: E402
|
|
|
|
app.include_router(accounts.router)
|
|
app.include_router(filters.router)
|
|
app.include_router(yaml_sync.router)
|
|
app.include_router(logs.router)
|
|
|
|
|
|
# --- API: Scheduler-Status ---
|
|
|
|
|
|
@app.get("/api/scheduler/status")
|
|
def scheduler_status():
|
|
from app.services.scheduler import scheduler
|
|
jobs = []
|
|
for job in scheduler.get_jobs():
|
|
jobs.append({
|
|
"id": job.id,
|
|
"next_run": job.next_run_time.isoformat() if job.next_run_time else None,
|
|
"interval": str(job.trigger),
|
|
})
|
|
return {
|
|
"running": scheduler.running,
|
|
"jobs": jobs,
|
|
}
|
|
|
|
|
|
# --- Web-UI Routen ---
|
|
|
|
|
|
@app.get("/")
|
|
def dashboard(request: Request, db: Session = Depends(get_db)):
|
|
accs = db.query(Account).order_by(Account.name).all()
|
|
account_list = []
|
|
for acc in accs:
|
|
account_list.append({
|
|
"id": acc.id,
|
|
"name": acc.name,
|
|
"username": acc.username,
|
|
"imap_host": acc.imap_host,
|
|
"enabled": acc.enabled,
|
|
"poll_interval_seconds": acc.poll_interval_seconds,
|
|
"last_poll_at": acc.last_poll_at,
|
|
"filter_rule_count": len(acc.filter_rules),
|
|
})
|
|
return templates.TemplateResponse(request, "dashboard.html", {"accounts": account_list})
|
|
|
|
|
|
@app.get("/accounts")
|
|
def accounts_page(request: Request, db: Session = Depends(get_db)):
|
|
accs = db.query(Account).order_by(Account.name).all()
|
|
return templates.TemplateResponse(request, "accounts.html", {"accounts": accs})
|
|
|
|
|
|
@app.get("/accounts/new")
|
|
def new_account_page(request: Request):
|
|
return templates.TemplateResponse(request, "account_form.html", {"account": None})
|
|
|
|
|
|
@app.get("/accounts/{account_id}/edit")
|
|
def edit_account_page(account_id: int, request: Request, db: Session = Depends(get_db)):
|
|
account = db.get(Account, account_id)
|
|
return templates.TemplateResponse(request, "account_form.html", {"account": account})
|
|
|
|
|
|
@app.get("/filters")
|
|
def filters_page(request: Request, account_id: int = 0, db: Session = Depends(get_db)):
|
|
accs = db.query(Account).order_by(Account.name).all()
|
|
return templates.TemplateResponse(request, "filters.html", {
|
|
"accounts": accs,
|
|
"selected_account_id": account_id,
|
|
})
|
|
|
|
|
|
@app.get("/yaml")
|
|
def yaml_page(request: Request):
|
|
return templates.TemplateResponse(request, "yaml.html", {})
|
|
|
|
|
|
@app.get("/logs")
|
|
def logs_page(request: Request, db: Session = Depends(get_db)):
|
|
accs = db.query(Account).order_by(Account.name).all()
|
|
return templates.TemplateResponse(request, "logs.html", {"accounts": accs})
|