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>
187 lines
7.5 KiB
Python
187 lines
7.5 KiB
Python
from datetime import datetime
|
|
|
|
from sqlalchemy import Boolean, Column, Date, DateTime, ForeignKey, Integer, JSON, String, Text
|
|
|
|
from .database import Base
|
|
|
|
|
|
# === Phase 1: Core ===
|
|
|
|
class User(Base):
|
|
__tablename__ = "users"
|
|
|
|
id = Column(Integer, primary_key=True, index=True)
|
|
username = Column(String(50), unique=True, nullable=False)
|
|
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"
|
|
|
|
id = Column(Integer, primary_key=True, index=True)
|
|
name = Column(String(100), nullable=False)
|
|
niche = Column(String(200), nullable=False)
|
|
topics = Column(JSON, default=list)
|
|
tone = Column(Text)
|
|
visual_style = Column(JSON, default=dict)
|
|
social_accounts = Column(JSON, default=dict)
|
|
affiliate_links = Column(JSON, default=list)
|
|
avatar_url = Column(String(500))
|
|
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 ===
|
|
|
|
class Post(Base):
|
|
__tablename__ = "posts"
|
|
|
|
id = Column(Integer, primary_key=True, index=True)
|
|
character_id = Column(Integer, ForeignKey("characters.id"), nullable=False)
|
|
content_type = Column(String(20), default="text") # text, image, video, carousel
|
|
text_content = Column(Text)
|
|
hashtags = Column(JSON, default=list)
|
|
image_url = Column(String(500))
|
|
video_url = Column(String(500))
|
|
media_urls = Column(JSON, default=list)
|
|
affiliate_links_used = Column(JSON, default=list)
|
|
llm_provider = Column(String(50))
|
|
llm_model = Column(String(100))
|
|
platform_hint = Column(String(20)) # which platform this was generated for
|
|
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 ===
|
|
|
|
class AffiliateLink(Base):
|
|
__tablename__ = "affiliate_links"
|
|
|
|
id = Column(Integer, primary_key=True, index=True)
|
|
character_id = Column(Integer, ForeignKey("characters.id"), nullable=True) # null = global
|
|
network = Column(String(100), nullable=False) # amazon, clickbank, etc.
|
|
name = Column(String(200), nullable=False)
|
|
url = Column(String(1000), nullable=False)
|
|
tag = Column(String(100))
|
|
topics = Column(JSON, default=list) # relevant topics for auto-insertion
|
|
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 ===
|
|
|
|
class EditorialPlan(Base):
|
|
__tablename__ = "editorial_plans"
|
|
|
|
id = Column(Integer, primary_key=True, index=True)
|
|
character_id = Column(Integer, ForeignKey("characters.id"), nullable=False)
|
|
name = Column(String(200), nullable=False)
|
|
frequency = Column(String(20), default="daily") # daily, twice_daily, weekly, custom
|
|
posts_per_day = Column(Integer, default=1)
|
|
platforms = Column(JSON, default=list) # ["facebook", "instagram", "youtube"]
|
|
content_types = Column(JSON, default=list) # ["text", "image", "video"]
|
|
posting_times = Column(JSON, default=list) # ["09:00", "18:00"]
|
|
start_date = Column(DateTime)
|
|
end_date = Column(DateTime, nullable=True)
|
|
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):
|
|
__tablename__ = "scheduled_posts"
|
|
|
|
id = Column(Integer, primary_key=True, index=True)
|
|
plan_id = Column(Integer, ForeignKey("editorial_plans.id"), nullable=True)
|
|
post_id = Column(Integer, ForeignKey("posts.id"), nullable=False)
|
|
platform = Column(String(20), nullable=False)
|
|
scheduled_at = Column(DateTime, nullable=False)
|
|
published_at = Column(DateTime, nullable=True)
|
|
status = Column(String(20), default="pending") # pending, publishing, published, failed
|
|
error_message = Column(Text, nullable=True)
|
|
external_post_id = Column(String(200), nullable=True)
|
|
created_at = Column(DateTime, default=datetime.utcnow)
|
|
|
|
|
|
# === Phase 6-10: Social Accounts ===
|
|
|
|
class SocialAccount(Base):
|
|
__tablename__ = "social_accounts"
|
|
|
|
id = Column(Integer, primary_key=True, index=True)
|
|
character_id = Column(Integer, ForeignKey("characters.id"), nullable=False)
|
|
platform = Column(String(20), nullable=False) # facebook, instagram, youtube, tiktok
|
|
account_name = Column(String(200))
|
|
account_id = Column(String(200))
|
|
access_token = Column(Text)
|
|
refresh_token = Column(Text, nullable=True)
|
|
token_expires_at = Column(DateTime, nullable=True)
|
|
page_id = Column(String(200), nullable=True) # Facebook page ID
|
|
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 ===
|
|
|
|
class Comment(Base):
|
|
__tablename__ = "comments"
|
|
|
|
id = Column(Integer, primary_key=True, index=True)
|
|
scheduled_post_id = Column(Integer, ForeignKey("scheduled_posts.id"), nullable=True)
|
|
platform = Column(String(20), nullable=False)
|
|
external_comment_id = Column(String(200))
|
|
author_name = Column(String(200))
|
|
author_id = Column(String(200))
|
|
content = Column(Text)
|
|
ai_suggested_reply = Column(Text, nullable=True)
|
|
approved_reply = Column(Text, nullable=True)
|
|
reply_status = Column(String(20), default="pending") # pending, approved, replied, ignored
|
|
replied_at = Column(DateTime, nullable=True)
|
|
created_at = Column(DateTime, default=datetime.utcnow)
|
|
|
|
|
|
# === System Settings ===
|
|
|
|
class SystemSetting(Base):
|
|
__tablename__ = "system_settings"
|
|
|
|
id = Column(Integer, primary_key=True, index=True)
|
|
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)
|