from typing import Optional, List, Dict, Generic, TypeVar from sqlmodel import SQLModel from datetime import datetime from pydantic import ConfigDict class UserCreate(SQLModel): email: str password: str username: str class UserRead(SQLModel): id: int email: str is_active: bool is_superuser: bool role: str = "user" avatar_bg_color: Optional[str] = "#3B82F6" avatar_text: Optional[str] = None profile_public: bool = True show_attendance_public: bool = True appear_in_leaderboards: bool = True bio: Optional[str] = None username: Optional[str] = None joined_at: Optional[datetime] = None # Social handles bluesky_handle: Optional[str] = None mastodon_handle: Optional[str] = None instagram_handle: Optional[str] = None location: Optional[str] = None class Token(SQLModel): access_token: str token_type: str class TokenData(SQLModel): email: Optional[str] = None T = TypeVar("T") class PaginationMeta(SQLModel): total: int limit: int offset: int class PaginatedResponse(SQLModel, Generic[T]): data: List[T] meta: PaginationMeta # --- Venue Schemas --- class VenueBase(SQLModel): name: str city: str state: Optional[str] = None country: str capacity: Optional[int] = None notes: Optional[str] = None class VenueCreate(VenueBase): pass class VenueRead(VenueBase): model_config = ConfigDict(from_attributes=True) id: int slug: Optional[str] = None class VenueUpdate(SQLModel): name: Optional[str] = None city: Optional[str] = None state: Optional[str] = None country: Optional[str] = None capacity: Optional[int] = None notes: Optional[str] = None # --- Song Schemas --- class SongBase(SQLModel): title: str original_artist: Optional[str] = None vertical_id: int notes: Optional[str] = None class SongCreate(SongBase): pass class SongRead(SongBase): id: int slug: Optional[str] = None tags: List["TagRead"] = [] artist: Optional["ArtistRead"] = None vertical: Optional["VerticalSimple"] = None times_played: Optional[int] = 0 class SongUpdate(SQLModel): title: Optional[str] = None original_artist: Optional[str] = None notes: Optional[str] = None # --- Vertical Schema (simple for embedding) --- class VerticalSimple(SQLModel): model_config = ConfigDict(from_attributes=True) id: int name: str slug: str description: Optional[str] = None logo_url: Optional[str] = None accent_color: Optional[str] = None # --- Show Schemas --- class ShowBase(SQLModel): date: datetime vertical_id: int venue_id: Optional[int] = None tour_id: Optional[int] = None notes: Optional[str] = None class ShowCreate(ShowBase): pass # --- Performance Schemas --- class PerformanceBase(SQLModel): show_id: int song_id: int position: int set_name: Optional[str] = None segue: bool = False notes: Optional[str] = None class PerformanceRead(PerformanceBase): id: int slug: Optional[str] = None song: Optional["SongRead"] = None nicknames: List["PerformanceNicknameRead"] = [] youtube_link: Optional[str] = None class PerformanceReadWithShow(PerformanceRead): show_date: datetime show_slug: Optional[str] = None venue_name: str venue_city: str venue_state: Optional[str] = None artist_name: Optional[str] = None artist_slug: Optional[str] = None avg_rating: Optional[float] = 0.0 total_reviews: Optional[int] = 0 class SongReadWithStats(SongRead): times_played: int gap: int last_played: Optional[datetime] = None set_breakdown: Dict[str, int] = {} artist_distribution: Dict[str, int] = {} performances: List[PerformanceReadWithShow] = [] class PerformanceDetailRead(PerformanceRead): show: Optional["ShowRead"] = None previous_performance_id: Optional[int] = None previous_performance_slug: Optional[str] = None next_performance_id: Optional[int] = None next_performance_slug: Optional[str] = None gap: Optional[int] = 0 times_played: Optional[int] = 0 other_performances: List[PerformanceReadWithShow] = [] # Ranking fields rank: Optional[int] = None total_versions: Optional[int] = None avg_rating: Optional[float] = None rating_count: Optional[int] = None is_heady: Optional[bool] = False # --- Groups --- class GroupBase(SQLModel): name: str description: Optional[str] = None privacy: str = "public" class GroupCreate(GroupBase): pass class GroupRead(GroupBase): id: int created_by: int created_at: datetime member_count: Optional[int] = 0 class GroupPostBase(SQLModel): content: str class GroupPostCreate(GroupPostBase): group_id: int class GroupPostRead(GroupPostBase): id: int group_id: int user_id: int created_at: datetime song: Optional["SongRead"] = None nicknames: List["PerformanceNicknameRead"] = [] class ShowRead(ShowBase): model_config = ConfigDict(from_attributes=True) id: int slug: Optional[str] = None vertical: Optional[VerticalSimple] = None venue: Optional[VenueRead] = None tour: Optional["TourRead"] = None tags: List["TagRead"] = [] performances: List["PerformanceRead"] = [] notes: Optional[str] = None youtube_link: Optional[str] = None nugs_link: Optional[str] = None bandcamp_link: Optional[str] = None class ShowUpdate(SQLModel): date: Optional[datetime] = None venue_id: Optional[int] = None tour_id: Optional[int] = None notes: Optional[str] = None # --- Tour Schemas --- class TourBase(SQLModel): name: str start_date: Optional[datetime] = None end_date: Optional[datetime] = None notes: Optional[str] = None class TourCreate(TourBase): pass class TourRead(TourBase): model_config = ConfigDict(from_attributes=True) id: int slug: Optional[str] = None class TourUpdate(SQLModel): name: Optional[str] = None start_date: Optional[datetime] = None end_date: Optional[datetime] = None notes: Optional[str] = None # --- Artist Schemas --- class ArtistBase(SQLModel): name: str instrument: Optional[str] = None notes: Optional[str] = None class ArtistCreate(ArtistBase): pass class ArtistRead(ArtistBase): id: int class ArtistUpdate(SQLModel): name: Optional[str] = None instrument: Optional[str] = None notes: Optional[str] = None # --- Attendance Schemas --- class AttendanceBase(SQLModel): show_id: int notes: Optional[str] = None class AttendanceCreate(AttendanceBase): pass class AttendanceRead(AttendanceBase): id: int user_id: int created_at: datetime # --- Social Schemas --- class CommentBase(SQLModel): content: str show_id: Optional[int] = None venue_id: Optional[int] = None song_id: Optional[int] = None parent_id: Optional[int] = None class CommentCreate(CommentBase): pass class CommentRead(CommentBase): id: int user_id: int created_at: datetime # We might want to include the username here later class RatingBase(SQLModel): score: Optional[float] = None # 1-5 stars, optional show_id: Optional[int] = None song_id: Optional[int] = None performance_id: Optional[int] = None venue_id: Optional[int] = None tour_id: Optional[int] = None class RatingCreate(RatingBase): pass class RatingRead(RatingBase): id: int user_id: int created_at: datetime class ReviewBase(SQLModel): blurb: Optional[str] = None # Short tagline/summary content: Optional[str] = None # Full review text score: Optional[float] = None # Optional rating with review show_id: Optional[int] = None venue_id: Optional[int] = None song_id: Optional[int] = None performance_id: Optional[int] = None tour_id: Optional[int] = None year: Optional[int] = None class ReviewCreate(ReviewBase): pass class ReviewRead(ReviewBase): id: int user_id: int created_at: datetime # --- Badge Schemas --- class BadgeBase(SQLModel): name: str description: str icon: str slug: str class BadgeCreate(BadgeBase): pass class BadgeRead(BadgeBase): id: int class UserBadgeRead(SQLModel): id: int user_id: int badge: BadgeRead awarded_at: datetime # --- Nickname Schemas --- class PerformanceNicknameBase(SQLModel): performance_id: int nickname: str description: Optional[str] = None class PerformanceNicknameCreate(PerformanceNicknameBase): pass class PerformanceNicknameRead(PerformanceNicknameBase): id: int status: str suggested_by: int created_at: datetime # --- Report Schemas --- class ReportBase(SQLModel): entity_type: str entity_id: int reason: str class ReportCreate(ReportBase): pass class ReportRead(ReportBase): id: int user_id: int status: str created_at: datetime # --- User Preferences Schemas --- class UserPreferencesBase(SQLModel): wiki_mode: bool = False show_ratings: bool = True show_comments: bool = True theme: str = "system" email_on_reply: bool = True email_on_chase: bool = True email_digest: bool = False class UserPreferencesCreate(UserPreferencesBase): pass class UserPreferencesUpdate(SQLModel): wiki_mode: Optional[bool] = None show_ratings: Optional[bool] = None show_comments: Optional[bool] = None theme: Optional[str] = None email_on_reply: Optional[bool] = None email_on_chase: Optional[bool] = None email_digest: Optional[bool] = None class UserPreferencesRead(UserPreferencesBase): user_id: int # --- Notification Schemas --- class NotificationBase(SQLModel): type: str title: str message: str link: Optional[str] = None is_read: bool = False class NotificationCreate(NotificationBase): user_id: int class NotificationRead(NotificationBase): id: int created_at: datetime # --- Tag Schemas --- class TagBase(SQLModel): name: str slug: str class TagCreate(TagBase): pass class TagRead(TagBase): id: int slug: str # Circular refs ShowRead.model_rebuild() PerformanceDetailRead.model_rebuild() # --- Reaction Schemas --- class ReactionBase(SQLModel): entity_type: str entity_id: int emoji: str class ReactionCreate(ReactionBase): pass class ReactionRead(ReactionBase): id: int user_id: int created_at: datetime # --- Profile Schemas --- class SocialHandles(SQLModel): bluesky: Optional[str] = None mastodon: Optional[str] = None instagram: Optional[str] = None class HeadlinerBand(SQLModel): name: str slug: str tier: str # headliner, main_stage, supporting logo_url: Optional[str] = None class PublicProfileRead(SQLModel): id: int username: str display_name: str bio: Optional[str] = None avatar: Optional[str] = None avatar_bg_color: Optional[str] = None avatar_text: Optional[str] = None location: Optional[str] = None # Socials social_handles: SocialHandles # The Lineup headliners: List[HeadlinerBand] supporting_acts: List[HeadlinerBand] # Stats stats: Dict[str, int] joined_at: datetime # --- Pagination --- T = TypeVar('T') class PaginationMeta(SQLModel): total: int limit: int offset: int class PaginatedResponse(SQLModel, Generic[T]): data: List[T] meta: PaginationMeta