Initial commit: Leopost Full — merge di Leopost, Post Generator e Autopilot OS

- Backend FastAPI con multi-LLM (Claude/OpenAI/Gemini)
- Publishing su Facebook, Instagram, YouTube, TikTok
- Calendario editoriale con awareness levels (PAS, AIDA, BAB...)
- Design system Editorial Fresh (Fraunces + DM Sans)
- Scheduler automatico, gestione commenti AI, affiliate links

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Michele
2026-03-31 17:23:16 +02:00
commit 519a580679
58 changed files with 8348 additions and 0 deletions

320
backend/app/schemas.py Normal file
View File

@@ -0,0 +1,320 @@
from datetime import datetime
from typing import Optional
from pydantic import BaseModel
# === Auth ===
class LoginRequest(BaseModel):
username: str
password: str
class Token(BaseModel):
access_token: str
token_type: str = "bearer"
# === Characters ===
class CharacterBase(BaseModel):
name: str
niche: str
topics: list[str] = []
tone: Optional[str] = None
visual_style: dict = {}
social_accounts: dict = {}
affiliate_links: list[dict] = []
avatar_url: Optional[str] = None
is_active: bool = True
class CharacterCreate(CharacterBase):
pass
class CharacterUpdate(BaseModel):
name: Optional[str] = None
niche: Optional[str] = None
topics: Optional[list[str]] = None
tone: Optional[str] = None
visual_style: Optional[dict] = None
social_accounts: Optional[dict] = None
affiliate_links: Optional[list[dict]] = None
avatar_url: Optional[str] = None
is_active: Optional[bool] = None
class CharacterResponse(CharacterBase):
id: int
created_at: datetime
updated_at: Optional[datetime] = None
class Config:
from_attributes = True
# === Posts / Content ===
class PostCreate(BaseModel):
character_id: int
content_type: str = "text"
platform_hint: str = "instagram"
text_content: Optional[str] = None
hashtags: list[str] = []
image_url: Optional[str] = None
video_url: Optional[str] = None
media_urls: list[str] = []
affiliate_links_used: list[dict] = []
status: str = "draft"
class PostUpdate(BaseModel):
text_content: Optional[str] = None
hashtags: Optional[list[str]] = None
image_url: Optional[str] = None
video_url: Optional[str] = None
status: Optional[str] = None
affiliate_links_used: Optional[list[dict]] = None
class PostResponse(BaseModel):
id: int
character_id: int
content_type: str
text_content: Optional[str] = None
hashtags: list[str] = []
image_url: Optional[str] = None
video_url: Optional[str] = None
media_urls: list[str] = []
affiliate_links_used: list[dict] = []
llm_provider: Optional[str] = None
llm_model: Optional[str] = None
platform_hint: Optional[str] = None
status: str
created_at: datetime
updated_at: Optional[datetime] = None
class Config:
from_attributes = True
class GenerateContentRequest(BaseModel):
character_id: int
platform: str = "instagram"
content_type: str = "text"
topic_hint: Optional[str] = None
include_affiliates: bool = True
provider: Optional[str] = None # override default LLM
model: Optional[str] = None
class GenerateImageRequest(BaseModel):
character_id: int
prompt: Optional[str] = None # auto-generated if not provided
style_hint: Optional[str] = None
size: str = "1024x1024"
provider: Optional[str] = None # dalle, replicate
# === Affiliate Links ===
class AffiliateLinkBase(BaseModel):
character_id: Optional[int] = None
network: str
name: str
url: str
tag: Optional[str] = None
topics: list[str] = []
is_active: bool = True
class AffiliateLinkCreate(AffiliateLinkBase):
pass
class AffiliateLinkUpdate(BaseModel):
network: Optional[str] = None
name: Optional[str] = None
url: Optional[str] = None
tag: Optional[str] = None
topics: Optional[list[str]] = None
is_active: Optional[bool] = None
class AffiliateLinkResponse(AffiliateLinkBase):
id: int
click_count: int = 0
created_at: datetime
class Config:
from_attributes = True
# === Editorial Plans ===
class EditorialPlanBase(BaseModel):
character_id: int
name: str
frequency: str = "daily"
posts_per_day: int = 1
platforms: list[str] = []
content_types: list[str] = ["text"]
posting_times: list[str] = ["09:00"]
start_date: Optional[datetime] = None
end_date: Optional[datetime] = None
is_active: bool = False
class EditorialPlanCreate(EditorialPlanBase):
pass
class EditorialPlanUpdate(BaseModel):
name: Optional[str] = None
frequency: Optional[str] = None
posts_per_day: Optional[int] = None
platforms: Optional[list[str]] = None
content_types: Optional[list[str]] = None
posting_times: Optional[list[str]] = None
start_date: Optional[datetime] = None
end_date: Optional[datetime] = None
is_active: Optional[bool] = None
class EditorialPlanResponse(EditorialPlanBase):
id: int
created_at: datetime
updated_at: Optional[datetime] = None
class Config:
from_attributes = True
# === Scheduled Posts ===
class ScheduledPostCreate(BaseModel):
plan_id: Optional[int] = None
post_id: int
platform: str
scheduled_at: datetime
class ScheduledPostResponse(BaseModel):
id: int
plan_id: Optional[int] = None
post_id: int
platform: str
scheduled_at: datetime
published_at: Optional[datetime] = None
status: str
error_message: Optional[str] = None
external_post_id: Optional[str] = None
created_at: datetime
class Config:
from_attributes = True
# === Social Accounts ===
class SocialAccountCreate(BaseModel):
character_id: int
platform: str
account_name: Optional[str] = None
account_id: Optional[str] = None
access_token: Optional[str] = None
refresh_token: Optional[str] = None
page_id: Optional[str] = None
extra_data: dict = {}
class SocialAccountUpdate(BaseModel):
account_name: Optional[str] = None
access_token: Optional[str] = None
refresh_token: Optional[str] = None
page_id: Optional[str] = None
extra_data: Optional[dict] = None
is_active: Optional[bool] = None
class SocialAccountResponse(BaseModel):
id: int
character_id: int
platform: str
account_name: Optional[str] = None
account_id: Optional[str] = None
page_id: Optional[str] = None
is_active: bool
token_expires_at: Optional[datetime] = None
created_at: datetime
class Config:
from_attributes = True
# === Comments ===
class CommentResponse(BaseModel):
id: int
scheduled_post_id: Optional[int] = None
platform: str
external_comment_id: Optional[str] = None
author_name: Optional[str] = None
content: Optional[str] = None
ai_suggested_reply: Optional[str] = None
approved_reply: Optional[str] = None
reply_status: str
replied_at: Optional[datetime] = None
created_at: datetime
class Config:
from_attributes = True
class CommentAction(BaseModel):
action: str # approve, edit, ignore
reply_text: Optional[str] = None # for edit action
# === System Settings ===
class SettingUpdate(BaseModel):
key: str
value: dict | str | list | int | bool | None
class SettingResponse(BaseModel):
key: str
value: dict | str | list | int | bool | None
updated_at: Optional[datetime] = None
class Config:
from_attributes = True
# === Editorial Calendar (from PostGenerator) ===
class CalendarGenerateRequest(BaseModel):
topics: list[str]
format_narrativo: Optional[str] = None # PAS, AIDA, BAB, Storytelling, Listicle, Dato_Implicazione
awareness_level: Optional[int] = None # 1-5 (Schwartz levels)
num_posts: int = 7
start_date: Optional[str] = None # YYYY-MM-DD
character_id: Optional[int] = None
class CalendarSlotResponse(BaseModel):
indice: int
topic: str
formato_narrativo: str
awareness_level: int
awareness_label: str
data_pubblicazione: str
note: Optional[str] = None
class CalendarResponse(BaseModel):
slots: list[CalendarSlotResponse]
totale_post: int