"""Application settings (admin): random amount/shipping ranges, invoice number format, default per-account limits, send pacing, attachment filename prefixes, compliance toggles.""" from typing import Optional from fastapi import APIRouter, Depends, HTTPException from pydantic import BaseModel, Field from sqlalchemy import select from sqlalchemy.ext.asyncio import AsyncSession from app.api.deps import get_current_user, get_db, require_csrf from app.core.config import settings as cfg from app.core.security import log_audit from app.models import ApplicationSettings, User from app.services.campaign_engine import get_setting router = APIRouter(prefix="/settings", tags=["settings"]) class SettingIn(BaseModel): key: str = Field(min_length=1, max_length=150) value: object = None category: str = "general" description: str = "" class BatchSettingsIn(BaseModel): values: dict # Known settings keys with validation constraints SETTING_DEFS = { "amount_min": {"type": "float", "min": 0, "max": 100000, "default": cfg.AMOUNT_MIN, "label": "Random amount minimum ($)", "category": "financial"}, "amount_max": {"type": "float", "min": 0, "max": 100000, "default": cfg.AMOUNT_MAX, "label": "Random amount maximum ($)", "category": "financial"}, "shipping_min": {"type": "float", "min": 0, "max": 10000, "default": cfg.SHIPPING_MIN, "label": "Random shipping minimum ($)", "category": "financial"}, "shipping_max": {"type": "float", "min": 0, "max": 10000, "default": cfg.SHIPPING_MAX, "label": "Random shipping maximum ($)", "category": "financial"}, "invoice_format": {"type": "str", "max_len": 80, "default": cfg.INVOICE_NUMBER_FORMAT, "label": "Invoice number format (tokens: {prefix},{R#},{r#},{d#},{seq4},{date},{hash})", "category": "financial"}, "invoice_prefix": {"type": "str", "max_len": 12, "default": "INV", "label": "Invoice prefix", "category": "financial"}, "default_daily_limit": {"type": "int", "min": 1, "max": 2000, "default": 500, "label": "Default per-account daily send limit", "category": "limits"}, "default_campaign_limit": {"type": "int", "min": 1, "max": 2000, "default": 200, "label": "Default per-account per-campaign limit", "category": "limits"}, "default_max_per_account": {"type": "int", "min": 1, "max": 2000, "default": 200, "label": "Default max sends per account per campaign", "category": "limits"}, "default_send_interval": {"type": "float", "min": 0.5, "max": 3600, "default": 1.0, "label": "Default seconds between sends per account", "category": "limits"}, "default_rate_per_minute": {"type": "int", "min": 1, "max": 600, "default": 60, "label": "Default max sends per minute (per account)", "category": "limits"}, "attachment_kind": {"type": "str", "max_len": 20, "default": "invoice", "label": "Attachment filename prefix (invoice|order|receipt)", "category": "attachments"}, "unsubscribe_enabled": {"type": "bool", "default": True, "label": "Enable unsubscribe links globally", "category": "compliance"}, "unsubscribe_base_url": {"type": "str", "max_len": 300, "default": "", ====== if applied: await log_audit(db, user.id, "settings.save_batch", "application_setting", None, {"keys": list(applied.keys())}) return {"applied": applied, "errors": errors} @router.post("/reset") async def reset_setting(payload: SettingIn, db: AsyncSession = Depends(get_db), user: User = Depends(get_current_user), _: None = Depends(require_csrf)): """Reset one setting to its default value (deletes the DB row).""" if payload.key not in SETTING_DEFS: raise HTTPException(status_code=400, detail="Unknown setting key") row = await db.execute( select(ApplicationSettings).where(ApplicationSettings.key == payload.key)) row = row.scalar() if row: await db.delete(row) await db.commit() await log_audit(db, user.id, "settings.reset", "application_setting", None, {"key": payload.key}) return {"key": payload.key, "value": SETTING_DEFS[payload.key]["default"]} @router.post("/reset-all") async def reset_all_settings(db: AsyncSession = Depends(get_db), user: User = Depends(get_current_user), _: None = Depends(require_csrf)): rows = (await db.execute( select(ApplicationSettings).where(ApplicationSettings.key.in_(SETTING_DEFS.keys())) )).scalars().all() for r in rows: await db.delete(r) await db.commit() await log_audit(db, user.id, "settings.reset_all", "application_setting", None, {"reset": len(rows)}) return {"reset": len(rows)}