Files
leopost-full/backend/app/models.py
Michele befa8b4adc feat: rich character profiles — brand voice, target, rules, hashtag profiles
Backend:
- Character model: add brand_voice, target_audience, business_goals,
  products_services, content_rules (JSON do/dont), hashtag_profiles (JSON)
- Content generation: inject full character context into LLM system prompt
  (voice, audience, goals, products, rules)
- Hashtag generation: merge always-on tags from profile with AI-generated tags
- Schema: update CharacterBase and CharacterUpdate with new fields

Frontend:
- CharacterForm: new sections "Identità e Voce", "Regole Contenuti",
  "Profili Hashtag" with dedicated editors
- RulesEditor: do/don't list with add/remove
- HashtagProfileEditor: per-platform tabs, fixed hashtags + max generated count
- All fields loaded on edit, saved on submit

DB migration: 6 new columns added to characters table

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-04 16:34:25 +02:00

195 lines
8.1 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)
# Rich profile fields
brand_voice = Column(Text, nullable=True) # how the character communicates (long description)
target_audience = Column(Text, nullable=True) # who reads the content
business_goals = Column(Text, nullable=True) # why they create content
products_services = Column(Text, nullable=True) # what they offer
content_rules = Column(JSON, default=dict) # {"do": [...], "dont": [...]}
hashtag_profiles = Column(JSON, default=dict) # per-platform hashtag config
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)
batch_id = Column(String(36), nullable=True, index=True) # groups posts generated together
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)