275 lines
12 KiB
Python
275 lines
12 KiB
Python
from typing import List, Optional
|
|
from sqlmodel import Field, Relationship, SQLModel
|
|
from datetime import datetime
|
|
|
|
# --- Join Tables ---
|
|
class Performance(SQLModel, table=True):
|
|
"""Link table between Show and Song (Many-to-Many with extra data)"""
|
|
id: Optional[int] = Field(default=None, primary_key=True)
|
|
show_id: int = Field(foreign_key="show.id")
|
|
song_id: int = Field(foreign_key="song.id")
|
|
position: int = Field(description="Order in the setlist")
|
|
set_name: Optional[str] = Field(default=None, description="e.g., Set 1, Encore")
|
|
segue: bool = Field(default=False, description="Transition to next song >")
|
|
notes: Optional[str] = Field(default=None)
|
|
|
|
nicknames: List["PerformanceNickname"] = Relationship(back_populates="performance")
|
|
show: "Show" = Relationship(back_populates="performances")
|
|
song: "Song" = Relationship()
|
|
|
|
class ShowArtist(SQLModel, table=True):
|
|
id: Optional[int] = Field(default=None, primary_key=True)
|
|
show_id: int = Field(foreign_key="show.id")
|
|
artist_id: int = Field(foreign_key="artist.id")
|
|
notes: Optional[str] = Field(default=None, description="Role e.g. Guest")
|
|
|
|
class PerformanceArtist(SQLModel, table=True):
|
|
id: Optional[int] = Field(default=None, primary_key=True)
|
|
performance_id: int = Field(foreign_key="performance.id")
|
|
artist_id: int = Field(foreign_key="artist.id")
|
|
notes: Optional[str] = Field(default=None, description="Role e.g. Guest")
|
|
|
|
class PerformanceNickname(SQLModel, table=True):
|
|
id: Optional[int] = Field(default=None, primary_key=True)
|
|
performance_id: int = Field(foreign_key="performance.id")
|
|
nickname: str = Field(index=True)
|
|
description: Optional[str] = Field(default=None)
|
|
status: str = Field(default="pending", index=True) # pending, approved, rejected
|
|
suggested_by: int = Field(foreign_key="user.id")
|
|
created_at: datetime = Field(default_factory=datetime.utcnow)
|
|
|
|
performance: "Performance" = Relationship(back_populates="nicknames")
|
|
user: "User" = Relationship()
|
|
|
|
class EntityTag(SQLModel, table=True):
|
|
id: Optional[int] = Field(default=None, primary_key=True)
|
|
tag_id: int = Field(foreign_key="tag.id")
|
|
entity_type: str = Field(index=True) # "show", "song", "venue"
|
|
entity_id: int = Field(index=True)
|
|
|
|
# --- Core Entities ---
|
|
|
|
class Vertical(SQLModel, table=True):
|
|
"""Represents a Fandom Vertical (e.g., 'Phish', 'Goose', 'Star Wars')"""
|
|
id: Optional[int] = Field(default=None, primary_key=True)
|
|
name: str = Field(index=True)
|
|
slug: str = Field(unique=True, index=True)
|
|
description: Optional[str] = Field(default=None)
|
|
|
|
shows: List["Show"] = Relationship(back_populates="vertical")
|
|
songs: List["Song"] = Relationship(back_populates="vertical")
|
|
|
|
class Venue(SQLModel, table=True):
|
|
id: Optional[int] = Field(default=None, primary_key=True)
|
|
name: str = Field(index=True)
|
|
city: str
|
|
state: Optional[str] = Field(default=None)
|
|
country: str
|
|
capacity: Optional[int] = Field(default=None)
|
|
notes: Optional[str] = Field(default=None)
|
|
|
|
shows: List["Show"] = Relationship(back_populates="venue")
|
|
|
|
class Tour(SQLModel, table=True):
|
|
id: Optional[int] = Field(default=None, primary_key=True)
|
|
name: str = Field(index=True)
|
|
start_date: Optional[datetime] = None
|
|
end_date: Optional[datetime] = None
|
|
notes: Optional[str] = Field(default=None)
|
|
|
|
shows: List["Show"] = Relationship(back_populates="tour")
|
|
|
|
class Artist(SQLModel, table=True):
|
|
id: Optional[int] = Field(default=None, primary_key=True)
|
|
name: str = Field(index=True)
|
|
instrument: Optional[str] = Field(default=None)
|
|
notes: Optional[str] = Field(default=None)
|
|
|
|
class Show(SQLModel, table=True):
|
|
id: Optional[int] = Field(default=None, primary_key=True)
|
|
date: datetime = Field(index=True)
|
|
vertical_id: int = Field(foreign_key="vertical.id")
|
|
venue_id: Optional[int] = Field(default=None, foreign_key="venue.id")
|
|
tour_id: Optional[int] = Field(default=None, foreign_key="tour.id")
|
|
notes: Optional[str] = Field(default=None)
|
|
|
|
vertical: Vertical = Relationship(back_populates="shows")
|
|
venue: Optional[Venue] = Relationship(back_populates="shows")
|
|
tour: Optional[Tour] = Relationship(back_populates="shows")
|
|
attendances: List["Attendance"] = Relationship(back_populates="show")
|
|
performances: List["Performance"] = Relationship(back_populates="show")
|
|
|
|
class Song(SQLModel, table=True):
|
|
id: Optional[int] = Field(default=None, primary_key=True)
|
|
title: str = Field(index=True)
|
|
original_artist: Optional[str] = Field(default=None)
|
|
vertical_id: int = Field(foreign_key="vertical.id")
|
|
notes: Optional[str] = Field(default=None)
|
|
|
|
vertical: Vertical = Relationship(back_populates="songs")
|
|
|
|
class Tag(SQLModel, table=True):
|
|
id: Optional[int] = Field(default=None, primary_key=True)
|
|
name: str = Field(unique=True, index=True)
|
|
slug: str = Field(unique=True, index=True)
|
|
|
|
class Attendance(SQLModel, table=True):
|
|
id: Optional[int] = Field(default=None, primary_key=True)
|
|
user_id: int = Field(foreign_key="user.id")
|
|
show_id: int = Field(foreign_key="show.id")
|
|
notes: Optional[str] = Field(default=None)
|
|
created_at: datetime = Field(default_factory=datetime.utcnow)
|
|
|
|
user: "User" = Relationship(back_populates="attendances")
|
|
show: "Show" = Relationship(back_populates="attendances")
|
|
|
|
class Comment(SQLModel, table=True):
|
|
id: Optional[int] = Field(default=None, primary_key=True)
|
|
user_id: int = Field(foreign_key="user.id")
|
|
content: str
|
|
created_at: datetime = Field(default_factory=datetime.utcnow)
|
|
|
|
# Polymorphic-ish associations (nullable FKs)
|
|
show_id: Optional[int] = Field(default=None, foreign_key="show.id")
|
|
venue_id: Optional[int] = Field(default=None, foreign_key="venue.id")
|
|
song_id: Optional[int] = Field(default=None, foreign_key="song.id")
|
|
|
|
user: "User" = Relationship(back_populates="comments")
|
|
|
|
class Rating(SQLModel, table=True):
|
|
id: Optional[int] = Field(default=None, primary_key=True)
|
|
user_id: int = Field(foreign_key="user.id")
|
|
score: int = Field(ge=1, le=10, description="Rating from 1 to 10")
|
|
created_at: datetime = Field(default_factory=datetime.utcnow)
|
|
|
|
show_id: Optional[int] = Field(default=None, foreign_key="show.id")
|
|
song_id: Optional[int] = Field(default=None, foreign_key="song.id")
|
|
|
|
user: "User" = Relationship(back_populates="ratings")
|
|
|
|
class User(SQLModel, table=True):
|
|
id: Optional[int] = Field(default=None, primary_key=True)
|
|
email: str = Field(unique=True, index=True)
|
|
hashed_password: str
|
|
is_active: bool = Field(default=True)
|
|
is_superuser: bool = Field(default=False)
|
|
role: str = Field(default="user") # user, moderator, admin
|
|
bio: Optional[str] = Field(default=None)
|
|
avatar: Optional[str] = Field(default=None)
|
|
|
|
# Multi-identity support: A user can have multiple Profiles
|
|
profiles: List["Profile"] = Relationship(back_populates="user")
|
|
comments: List["Comment"] = Relationship(back_populates="user")
|
|
ratings: List["Rating"] = Relationship(back_populates="user")
|
|
reviews: List["Review"] = Relationship(back_populates="user")
|
|
attendances: List["Attendance"] = Relationship(back_populates="user")
|
|
badges: List["UserBadge"] = Relationship(back_populates="user")
|
|
preferences: Optional["UserPreferences"] = Relationship(back_populates="user", sa_relationship_kwargs={"uselist": False})
|
|
reports: List["Report"] = Relationship(back_populates="user")
|
|
notifications: List["Notification"] = Relationship(back_populates="user")
|
|
|
|
class Report(SQLModel, table=True):
|
|
id: Optional[int] = Field(default=None, primary_key=True)
|
|
user_id: int = Field(foreign_key="user.id")
|
|
entity_type: str = Field(index=True) # comment, review, nickname
|
|
entity_id: int = Field(index=True)
|
|
reason: str
|
|
details: str = Field(default="")
|
|
status: str = Field(default="pending", index=True) # pending, resolved, dismissed
|
|
created_at: datetime = Field(default_factory=datetime.utcnow)
|
|
|
|
user: "User" = Relationship(back_populates="reports")
|
|
|
|
class Badge(SQLModel, table=True):
|
|
id: Optional[int] = Field(default=None, primary_key=True)
|
|
name: str = Field(unique=True, index=True)
|
|
description: str
|
|
icon: str = Field(description="Lucide icon name or image URL")
|
|
slug: str = Field(unique=True, index=True)
|
|
|
|
class UserBadge(SQLModel, table=True):
|
|
id: Optional[int] = Field(default=None, primary_key=True)
|
|
user_id: int = Field(foreign_key="user.id")
|
|
badge_id: int = Field(foreign_key="badge.id")
|
|
awarded_at: datetime = Field(default_factory=datetime.utcnow)
|
|
|
|
user: "User" = Relationship(back_populates="badges")
|
|
badge: "Badge" = Relationship()
|
|
|
|
class Review(SQLModel, table=True):
|
|
id: Optional[int] = Field(default=None, primary_key=True)
|
|
user_id: int = Field(foreign_key="user.id")
|
|
blurb: str = Field(description="One-liner/pullquote")
|
|
content: str = Field(description="Full review text")
|
|
score: int = Field(ge=1, le=10)
|
|
show_id: Optional[int] = Field(default=None, foreign_key="show.id")
|
|
venue_id: Optional[int] = Field(default=None, foreign_key="venue.id")
|
|
song_id: Optional[int] = Field(default=None, foreign_key="song.id")
|
|
performance_id: Optional[int] = Field(default=None, foreign_key="performance.id")
|
|
tour_id: Optional[int] = Field(default=None, foreign_key="tour.id")
|
|
year: Optional[int] = Field(default=None, description="For reviewing a specific year")
|
|
created_at: datetime = Field(default_factory=datetime.utcnow)
|
|
|
|
user: "User" = Relationship(back_populates="reviews")
|
|
|
|
class UserPreferences(SQLModel, table=True):
|
|
id: Optional[int] = Field(default=None, primary_key=True)
|
|
user_id: int = Field(foreign_key="user.id", unique=True)
|
|
wiki_mode: bool = Field(default=False, description="Disable social features")
|
|
show_ratings: bool = Field(default=True)
|
|
show_comments: bool = Field(default=True)
|
|
|
|
user: User = Relationship(back_populates="preferences")
|
|
|
|
class Profile(SQLModel, table=True):
|
|
"""A user's identity within a specific context or global"""
|
|
id: Optional[int] = Field(default=None, primary_key=True)
|
|
user_id: int = Field(foreign_key="user.id")
|
|
username: str = Field(index=True)
|
|
display_name: Optional[str] = Field(default=None)
|
|
|
|
user: User = Relationship(back_populates="profiles")
|
|
|
|
# --- Groups ---
|
|
class Group(SQLModel, table=True):
|
|
id: Optional[int] = Field(default=None, primary_key=True)
|
|
name: str = Field(index=True, unique=True)
|
|
description: Optional[str] = None
|
|
privacy: str = Field(default="public") # public, private
|
|
created_by: int = Field(foreign_key="user.id")
|
|
created_at: datetime = Field(default_factory=datetime.utcnow)
|
|
|
|
members: List["GroupMember"] = Relationship(back_populates="group")
|
|
posts: List["GroupPost"] = Relationship(back_populates="group")
|
|
|
|
class GroupMember(SQLModel, table=True):
|
|
id: Optional[int] = Field(default=None, primary_key=True)
|
|
group_id: int = Field(foreign_key="group.id")
|
|
user_id: int = Field(foreign_key="user.id")
|
|
role: str = Field(default="member") # member, admin
|
|
joined_at: datetime = Field(default_factory=datetime.utcnow)
|
|
|
|
group: Group = Relationship(back_populates="members")
|
|
user: User = Relationship()
|
|
|
|
class GroupPost(SQLModel, table=True):
|
|
id: Optional[int] = Field(default=None, primary_key=True)
|
|
group_id: int = Field(foreign_key="group.id")
|
|
user_id: int = Field(foreign_key="user.id")
|
|
content: str
|
|
created_at: datetime = Field(default_factory=datetime.utcnow)
|
|
|
|
group: Group = Relationship(back_populates="posts")
|
|
user: User = Relationship()
|
|
|
|
class Notification(SQLModel, table=True):
|
|
id: Optional[int] = Field(default=None, primary_key=True)
|
|
user_id: int = Field(foreign_key="user.id", index=True)
|
|
type: str = Field(description="reply, mention, system")
|
|
title: str
|
|
message: str
|
|
link: Optional[str] = None
|
|
is_read: bool = Field(default=False)
|
|
created_at: datetime = Field(default_factory=datetime.utcnow)
|
|
|
|
user: User = Relationship(back_populates="notifications")
|