from datetime import datetime from typing import List, Optional from pydantic import BaseModel # === Auth === class LoginRequest(BaseModel): username: str password: str class Token(BaseModel): access_token: str token_type: str = "bearer" user: Optional[dict] = None # === 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" # legacy single-platform (kept for compat) content_type: str = "text" # legacy single type (kept for compat) platforms: List[str] = [] # new: multi-platform (overrides platform if non-empty) content_types: List[str] = [] # new: multi-type (overrides content_type if non-empty) topic_hint: Optional[str] = None include_affiliates: bool = True provider: Optional[str] = None model: Optional[str] = None @property def effective_platform(self) -> str: return self.platforms[0] if self.platforms else self.platform @property def effective_content_type(self) -> str: return self.content_types[0] if self.content_types else self.content_type 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): 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