Files
leopost-full/backend/app/routers/settings.py
Michele 77ca70cd48 feat: multi-user SaaS, piani Freemium/Pro, Google OAuth, admin panel
BLOCCO 1 - Multi-user data model:
- User: email, display_name, avatar_url, auth_provider, google_id
- User: subscription_plan, subscription_expires_at, is_admin, post counters
- SubscriptionCode table per redeem codes
- user_id FK su Character, Post, AffiliateLink, EditorialPlan, SocialAccount, SystemSetting
- Migrazione SQLite-safe (ALTER TABLE) + preserva dati esistenti

BLOCCO 2 - Auth completo:
- Registrazione email/password + login multi-user
- Google OAuth 2.0 (httpx, no deps esterne)
- Callback flow: Google -> /auth/callback?token=JWT -> frontend
- Backward compat login admin con username

BLOCCO 3 - Piani e abbonamenti:
- Freemium: 1 character, 15 post/mese, FB+IG only, no auto-plans
- Pro: illimitato, tutte le piattaforme, tutte le feature
- Enforcement automatico in tutti i router
- Redeem codes con durate 1/3/6/12 mesi
- Admin panel: genera codici, lista utenti

BLOCCO 4 - Frontend completo:
- Login page design Leopost (split coral/cream, Google, social coming soon)
- AuthCallback per OAuth redirect
- PlanBanner, UpgradeModal con pricing
- AdminSettings per generazione codici
- CharacterForm con tab Account Social + guide setup

Deploy:
- Dockerfile con ARG VITE_BASE_PATH/VITE_API_BASE
- docker-compose.prod.yml per leopost.it (no subpath)
- docker-compose.yml aggiornato per lab

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-31 20:01:07 +02:00

156 lines
4.6 KiB
Python

"""System settings router.
Manages key-value system settings including API provider configuration.
Each user has their own private settings.
"""
from datetime import datetime
from fastapi import APIRouter, Depends, HTTPException
from sqlalchemy.orm import Session
from ..auth import get_current_user
from ..database import get_db
from ..models import SystemSetting, User
from ..schemas import SettingResponse, SettingUpdate
router = APIRouter(
prefix="/api/settings",
tags=["settings"],
)
@router.get("/", response_model=list[SettingResponse])
def list_settings(
db: Session = Depends(get_db),
current_user: User = Depends(get_current_user),
):
"""Get all system settings for the current user."""
settings = (
db.query(SystemSetting)
.filter(SystemSetting.user_id == current_user.id)
.order_by(SystemSetting.key)
.all()
)
return settings
@router.get("/providers/status")
def get_providers_status(
db: Session = Depends(get_db),
current_user: User = Depends(get_current_user),
):
"""Check which API providers are configured (have API keys set)."""
def _has_setting(key: str) -> str | None:
# User-specific first
setting = (
db.query(SystemSetting)
.filter(SystemSetting.key == key, SystemSetting.user_id == current_user.id)
.first()
)
if not setting:
# Global fallback
setting = db.query(SystemSetting).filter(
SystemSetting.key == key, SystemSetting.user_id == None
).first()
if setting and setting.value:
return setting.value if isinstance(setting.value, str) else str(setting.value)
return None
llm_provider = _has_setting("llm_provider")
llm_key = _has_setting("llm_api_key")
image_provider = _has_setting("image_provider")
image_key = _has_setting("image_api_key")
voice_provider = _has_setting("voice_provider")
voice_key = _has_setting("voice_api_key")
from ..models import SocialAccount
social_platforms = {}
for platform in ("facebook", "instagram", "youtube", "tiktok"):
has_account = (
db.query(SocialAccount)
.filter(
SocialAccount.platform == platform,
SocialAccount.is_active == True,
SocialAccount.access_token != None,
SocialAccount.user_id == current_user.id,
)
.first()
)
social_platforms[platform] = has_account is not None
return {
"llm": {
"configured": bool(llm_provider and llm_key),
"provider": llm_provider,
},
"image": {
"configured": bool(image_provider and image_key),
"provider": image_provider,
},
"voice": {
"configured": bool(voice_provider and voice_key),
"provider": voice_provider,
},
"social": social_platforms,
}
@router.get("/{key}", response_model=SettingResponse)
def get_setting(
key: str,
db: Session = Depends(get_db),
current_user: User = Depends(get_current_user),
):
"""Get a single setting by key (user-specific)."""
setting = (
db.query(SystemSetting)
.filter(SystemSetting.key == key, SystemSetting.user_id == current_user.id)
.first()
)
if not setting:
raise HTTPException(status_code=404, detail=f"Setting '{key}' not found")
return setting
@router.put("/{key}", response_model=SettingResponse)
def upsert_setting(
key: str,
data: SettingUpdate,
db: Session = Depends(get_db),
current_user: User = Depends(get_current_user),
):
"""Create or update a setting by key (user-specific)."""
setting = (
db.query(SystemSetting)
.filter(SystemSetting.key == key, SystemSetting.user_id == current_user.id)
.first()
)
if setting:
setting.value = data.value
setting.updated_at = datetime.utcnow()
else:
setting = SystemSetting(key=key, value=data.value, user_id=current_user.id)
db.add(setting)
db.commit()
db.refresh(setting)
return setting
@router.delete("/{key}", status_code=204)
def delete_setting(
key: str,
db: Session = Depends(get_db),
current_user: User = Depends(get_current_user),
):
"""Delete a setting by key (user-specific)."""
setting = (
db.query(SystemSetting)
.filter(SystemSetting.key == key, SystemSetting.user_id == current_user.id)
.first()
)
if not setting:
raise HTTPException(status_code=404, detail=f"Setting '{key}' not found")
db.delete(setting)
db.commit()