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:
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user