Files
leopost-full/backend/app/plan_limits.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

50 lines
1.6 KiB
Python

"""Piano di abbonamento e limiti per feature."""
PLAN_LIMITS = {
"freemium": {
"characters_max": 1,
"posts_per_month": 15,
"platforms": ["facebook", "instagram"],
"auto_plans": False,
"comments_management": False,
"affiliate_links": False,
"editorial_calendar_max": 5,
},
"pro": {
"characters_max": None,
"posts_per_month": None,
"platforms": ["facebook", "instagram", "youtube", "tiktok"],
"auto_plans": True,
"comments_management": True,
"affiliate_links": True,
"editorial_calendar_max": None,
},
}
def get_plan(user) -> dict:
"""Returns the effective plan for a user (checks expiry for pro)."""
from datetime import datetime
plan = getattr(user, "subscription_plan", "freemium") or "freemium"
if plan == "pro":
expires = getattr(user, "subscription_expires_at", None)
if expires and expires < datetime.utcnow():
return PLAN_LIMITS["freemium"]
return PLAN_LIMITS.get(plan, PLAN_LIMITS["freemium"])
def check_limit(user, feature: str, current_count: int = 0) -> tuple[bool, str]:
"""Returns (allowed, error_message)."""
limits = get_plan(user)
limit = limits.get(feature)
if limit is None:
return True, ""
if isinstance(limit, bool):
if not limit:
return False, f"Feature '{feature}' non disponibile nel piano Freemium. Passa a Pro."
return True, ""
if isinstance(limit, int):
if current_count >= limit:
return False, f"Hai raggiunto il limite del piano Freemium ({limit} {feature}). Passa a Pro."
return True, ""