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>
This commit is contained in:
Michele
2026-03-31 20:01:07 +02:00
parent 2c16407f96
commit 77ca70cd48
31 changed files with 2818 additions and 449 deletions

View File

@@ -1,6 +1,6 @@
from datetime import datetime
from sqlalchemy import Boolean, Column, DateTime, ForeignKey, Integer, JSON, String, Text
from sqlalchemy import Boolean, Column, Date, DateTime, ForeignKey, Integer, JSON, String, Text
from .database import Base
@@ -15,6 +15,30 @@ class User(Base):
hashed_password = Column(String(255), nullable=False)
created_at = Column(DateTime, default=datetime.utcnow)
# Multi-user SaaS fields
email = Column(String(255), unique=True, nullable=True)
display_name = Column(String(100), nullable=True)
avatar_url = Column(String(500), nullable=True)
auth_provider = Column(String(50), default="local")
google_id = Column(String(200), unique=True, nullable=True)
subscription_plan = Column(String(50), default="freemium")
subscription_expires_at = Column(DateTime, nullable=True)
is_admin = Column(Boolean, default=False)
posts_generated_this_month = Column(Integer, default=0)
posts_reset_date = Column(Date, nullable=True)
class SubscriptionCode(Base):
__tablename__ = "subscription_codes"
id = Column(Integer, primary_key=True)
code = Column(String(100), unique=True, nullable=False)
duration_months = Column(Integer, nullable=False) # 1, 3, 6, 12
created_by_admin_id = Column(Integer, ForeignKey("users.id"))
used_by_user_id = Column(Integer, ForeignKey("users.id"), nullable=True)
used_at = Column(DateTime, nullable=True)
created_at = Column(DateTime, default=datetime.utcnow)
class Character(Base):
__tablename__ = "characters"
@@ -31,6 +55,7 @@ class Character(Base):
is_active = Column(Boolean, default=True)
created_at = Column(DateTime, default=datetime.utcnow)
updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
user_id = Column(Integer, ForeignKey("users.id"), nullable=True)
# === Phase 2: Content Generation ===
@@ -53,6 +78,7 @@ class Post(Base):
status = Column(String(20), default="draft") # draft, approved, scheduled, published, failed
created_at = Column(DateTime, default=datetime.utcnow)
updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
user_id = Column(Integer, ForeignKey("users.id"), nullable=True)
# === Phase 4: Affiliate Links ===
@@ -70,6 +96,7 @@ class AffiliateLink(Base):
is_active = Column(Boolean, default=True)
click_count = Column(Integer, default=0)
created_at = Column(DateTime, default=datetime.utcnow)
user_id = Column(Integer, ForeignKey("users.id"), nullable=True)
# === Phase 5: Scheduling ===
@@ -90,6 +117,7 @@ class EditorialPlan(Base):
is_active = Column(Boolean, default=False)
created_at = Column(DateTime, default=datetime.utcnow)
updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
user_id = Column(Integer, ForeignKey("users.id"), nullable=True)
class ScheduledPost(Base):
@@ -124,6 +152,7 @@ class SocialAccount(Base):
extra_data = Column(JSON, default=dict) # platform-specific data
is_active = Column(Boolean, default=True)
created_at = Column(DateTime, default=datetime.utcnow)
user_id = Column(Integer, ForeignKey("users.id"), nullable=True)
# === Phase 11: Comment Management ===
@@ -151,6 +180,7 @@ class SystemSetting(Base):
__tablename__ = "system_settings"
id = Column(Integer, primary_key=True, index=True)
key = Column(String(100), unique=True, nullable=False)
key = Column(String(100), nullable=False)
value = Column(JSON)
updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
user_id = Column(Integer, ForeignKey("users.id"), nullable=True)