- Adds archive.org/Relisten deep link support - Complements existing nugs_link, youtube_link, bandcamp_link
486 lines
23 KiB
Python
486 lines
23 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)
|
|
slug: Optional[str] = Field(default=None, unique=True, index=True, description="songslug-YYYY-MM-DD")
|
|
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)
|
|
track_url: Optional[str] = Field(default=None, description="Deep link to track audio")
|
|
youtube_link: Optional[str] = Field(default=None, description="YouTube video URL")
|
|
bandcamp_link: Optional[str] = Field(default=None, description="Bandcamp track URL")
|
|
nugs_link: Optional[str] = Field(default=None, description="Nugs.net track URL")
|
|
|
|
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 VerticalScene(SQLModel, table=True):
|
|
"""Join table linking verticals to scenes (many-to-many)"""
|
|
vertical_id: int = Field(foreign_key="vertical.id", primary_key=True)
|
|
scene_id: int = Field(foreign_key="scene.id", primary_key=True)
|
|
|
|
|
|
class Scene(SQLModel, table=True):
|
|
"""Genre/scene categorization for bands (e.g., 'Jam', 'Bluegrass', 'Dead Family')"""
|
|
id: Optional[int] = Field(default=None, primary_key=True)
|
|
name: str = Field(unique=True, index=True)
|
|
slug: str = Field(unique=True, index=True)
|
|
description: Optional[str] = Field(default=None)
|
|
|
|
# Relationships
|
|
verticals: List["Vertical"] = Relationship(back_populates="scenes", link_model=VerticalScene)
|
|
|
|
|
|
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)
|
|
|
|
# Link to primary artist/band for this vertical
|
|
primary_artist_id: Optional[int] = Field(default=None, foreign_key="artist.id")
|
|
|
|
# Setlist.fm integration for universal import
|
|
setlistfm_mbid: Optional[str] = Field(default=None, description="MusicBrainz ID for Setlist.fm")
|
|
|
|
# Admin/status fields
|
|
is_active: bool = Field(default=True, description="Show in band selector")
|
|
is_featured: bool = Field(default=False, description="Highlight in discovery")
|
|
|
|
# Relationships
|
|
shows: List["Show"] = Relationship(back_populates="vertical")
|
|
songs: List["Song"] = Relationship(back_populates="vertical")
|
|
scenes: List["Scene"] = Relationship(back_populates="verticals", link_model=VerticalScene)
|
|
|
|
class Venue(SQLModel, table=True):
|
|
id: Optional[int] = Field(default=None, primary_key=True)
|
|
name: str = Field(index=True)
|
|
slug: Optional[str] = Field(default=None, unique=True, 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)
|
|
slug: Optional[str] = Field(default=None, unique=True, 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)
|
|
slug: str = Field(unique=True, index=True)
|
|
bio: Optional[str] = Field(default=None)
|
|
image_url: Optional[str] = Field(default=None)
|
|
instrument: Optional[str] = Field(default=None)
|
|
notes: Optional[str] = Field(default=None)
|
|
|
|
songs: List["Song"] = Relationship(back_populates="artist")
|
|
|
|
class Musician(SQLModel, table=True):
|
|
"""Individual human musicians (for tracking sit-ins and band membership)"""
|
|
id: Optional[int] = Field(default=None, primary_key=True)
|
|
name: str = Field(index=True)
|
|
slug: str = Field(unique=True, index=True)
|
|
bio: Optional[str] = Field(default=None)
|
|
image_url: Optional[str] = Field(default=None)
|
|
primary_instrument: Optional[str] = Field(default=None)
|
|
notes: Optional[str] = Field(default=None)
|
|
|
|
# Relationships
|
|
memberships: List["BandMembership"] = Relationship(back_populates="musician")
|
|
guest_appearances: List["PerformanceGuest"] = Relationship(back_populates="musician")
|
|
|
|
class BandMembership(SQLModel, table=True):
|
|
"""Link between Musician and Band/Artist with role and dates"""
|
|
id: Optional[int] = Field(default=None, primary_key=True)
|
|
musician_id: int = Field(foreign_key="musician.id")
|
|
artist_id: int = Field(foreign_key="artist.id", description="The band/group")
|
|
role: Optional[str] = Field(default=None, description="e.g., Keyboards, Rhythm Guitar")
|
|
start_date: Optional[datetime] = Field(default=None)
|
|
end_date: Optional[datetime] = Field(default=None, description="Null = current member")
|
|
notes: Optional[str] = Field(default=None)
|
|
|
|
musician: Musician = Relationship(back_populates="memberships")
|
|
artist: Artist = Relationship()
|
|
|
|
class PerformanceGuest(SQLModel, table=True):
|
|
"""Link between Performance and Musician for sit-ins/guest appearances"""
|
|
id: Optional[int] = Field(default=None, primary_key=True)
|
|
performance_id: int = Field(foreign_key="performance.id")
|
|
musician_id: int = Field(foreign_key="musician.id")
|
|
instrument: Optional[str] = Field(default=None, description="What they played on this track")
|
|
notes: Optional[str] = Field(default=None)
|
|
|
|
musician: Musician = Relationship(back_populates="guest_appearances")
|
|
|
|
|
|
class Show(SQLModel, table=True):
|
|
id: Optional[int] = Field(default=None, primary_key=True)
|
|
date: datetime = Field(index=True)
|
|
slug: Optional[str] = Field(default=None, unique=True, 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)
|
|
|
|
# External Links
|
|
bandcamp_link: Optional[str] = Field(default=None)
|
|
nugs_link: Optional[str] = Field(default=None)
|
|
youtube_link: Optional[str] = Field(default=None)
|
|
relisten_link: Optional[str] = Field(default=None, description="Link to Relisten.net or archive.org")
|
|
|
|
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 SongCanon(SQLModel, table=True):
|
|
"""Canonical 'master' song independent of band - enables cross-band song linking"""
|
|
id: Optional[int] = Field(default=None, primary_key=True)
|
|
title: str = Field(index=True)
|
|
slug: str = Field(unique=True, index=True)
|
|
original_artist: Optional[str] = Field(default=None, description="Original songwriter/band")
|
|
original_artist_id: Optional[int] = Field(default=None, foreign_key="artist.id")
|
|
notes: Optional[str] = Field(default=None)
|
|
|
|
# All vertical-specific versions of this song
|
|
versions: List["Song"] = Relationship(back_populates="canon")
|
|
|
|
class Song(SQLModel, table=True):
|
|
id: Optional[int] = Field(default=None, primary_key=True)
|
|
title: str = Field(index=True)
|
|
slug: Optional[str] = Field(default=None, unique=True, index=True)
|
|
original_artist: Optional[str] = Field(default=None)
|
|
vertical_id: int = Field(foreign_key="vertical.id")
|
|
notes: Optional[str] = Field(default=None)
|
|
youtube_link: Optional[str] = Field(default=None)
|
|
|
|
# Link to canonical song for cross-band tracking
|
|
canon_id: Optional[int] = Field(default=None, foreign_key="songcanon.id")
|
|
canon: Optional[SongCanon] = Relationship(back_populates="versions")
|
|
|
|
# Artist who wrote/performs this version
|
|
artist_id: Optional[int] = Field(default=None, foreign_key="artist.id")
|
|
artist: Optional["Artist"] = Relationship(back_populates="songs")
|
|
|
|
vertical: "Vertical" = Relationship(back_populates="songs")
|
|
|
|
class Sequence(SQLModel, table=True):
|
|
"""Named groupings of consecutive songs, e.g. 'Autumn Crossing' = Travelers > Elmeg the Wise"""
|
|
id: Optional[int] = Field(default=None, primary_key=True)
|
|
name: str = Field(index=True, description="Human-readable name like 'Autumn Crossing'")
|
|
slug: str = Field(unique=True, index=True)
|
|
description: Optional[str] = Field(default=None)
|
|
notes: Optional[str] = Field(default=None)
|
|
|
|
# Relationship to songs that make up this sequence
|
|
songs: List["SequenceSong"] = Relationship(back_populates="sequence")
|
|
|
|
class SequenceSong(SQLModel, table=True):
|
|
"""Join table linking songs to sequences with ordering"""
|
|
id: Optional[int] = Field(default=None, primary_key=True)
|
|
sequence_id: int = Field(foreign_key="sequence.id")
|
|
song_id: int = Field(foreign_key="song.id")
|
|
position: int = Field(description="Order in sequence, 1-indexed")
|
|
|
|
sequence: Sequence = 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")
|
|
parent_id: Optional[int] = Field(default=None, foreign_key="comment.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: float = Field(ge=1.0, le=10.0, description="Rating from 1.0 to 10.0")
|
|
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")
|
|
performance_id: Optional[int] = Field(default=None, foreign_key="performance.id")
|
|
venue_id: Optional[int] = Field(default=None, foreign_key="venue.id")
|
|
tour_id: Optional[int] = Field(default=None, foreign_key="tour.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)
|
|
avatar_bg_color: Optional[str] = Field(default="#3B82F6", description="Hex color for avatar background")
|
|
avatar_text: Optional[str] = Field(default=None, description="1-3 character text overlay on avatar")
|
|
|
|
# Privacy settings
|
|
profile_public: bool = Field(default=True, description="Allow others to view profile")
|
|
show_attendance_public: bool = Field(default=True, description="Show attended shows on profile")
|
|
appear_in_leaderboards: bool = Field(default=True, description="Appear in community leaderboards")
|
|
|
|
# Gamification
|
|
xp: int = Field(default=0, description="Experience points")
|
|
level: int = Field(default=1, description="User level based on XP")
|
|
streak_days: int = Field(default=0, description="Consecutive days active")
|
|
last_activity: Optional[datetime] = Field(default=None)
|
|
|
|
# Custom Titles & Flair (tracker forum style)
|
|
custom_title: Optional[str] = Field(default=None, description="Custom title chosen by user")
|
|
title_color: Optional[str] = Field(default=None, description="Hex color for username display")
|
|
flair: Optional[str] = Field(default=None, description="Small text/emoji beside name")
|
|
is_early_adopter: bool = Field(default=False, description="First 100 users get special perks")
|
|
is_supporter: bool = Field(default=False, description="Donated/supported the platform")
|
|
joined_at: datetime = Field(default_factory=datetime.utcnow)
|
|
|
|
# Email verification
|
|
email_verified: bool = Field(default=False)
|
|
verification_token: Optional[str] = Field(default=None)
|
|
verification_token_expires: Optional[datetime] = Field(default=None)
|
|
|
|
# Password reset
|
|
reset_token: Optional[str] = Field(default=None)
|
|
reset_token_expires: Optional[datetime] = 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)
|
|
tier: str = Field(default="bronze", description="bronze, silver, gold, platinum, diamond")
|
|
category: str = Field(default="general", description="attendance, ratings, social, milestones")
|
|
xp_reward: int = Field(default=50, description="XP awarded when badge is earned")
|
|
|
|
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: float = Field(ge=1.0, le=10.0)
|
|
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)
|
|
|
|
# Theme preference (synced from frontend)
|
|
theme: str = Field(default="system", description="light, dark, or system")
|
|
|
|
# Email notification preferences
|
|
email_on_reply: bool = Field(default=True, description="Email when someone replies to your review")
|
|
email_on_chase: bool = Field(default=True, description="Email when your chase song is played")
|
|
email_digest: bool = Field(default=False, description="Weekly digest email")
|
|
|
|
user: "User" = Relationship(back_populates="preferences")
|
|
|
|
class UserVerticalPreference(SQLModel, table=True):
|
|
"""User preferences for which bands to display prominently vs. attribution-only"""
|
|
id: Optional[int] = Field(default=None, primary_key=True)
|
|
user_id: int = Field(foreign_key="user.id", index=True)
|
|
vertical_id: int = Field(foreign_key="vertical.id", index=True)
|
|
display_mode: str = Field(default="primary", description="primary, secondary, attribution_only, hidden")
|
|
priority: int = Field(default=0, description="Sort order - lower = higher priority")
|
|
notify_on_show: bool = Field(default=True, description="Notify when this band plays a show")
|
|
created_at: datetime = Field(default_factory=datetime.utcnow)
|
|
|
|
user: "User" = Relationship()
|
|
vertical: "Vertical" = Relationship()
|
|
|
|
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")
|
|
|
|
class Reaction(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) # "review", "comment"
|
|
entity_id: int = Field(index=True)
|
|
emoji: str # "❤️", "🔥", etc.
|
|
created_at: datetime = Field(default_factory=datetime.utcnow)
|
|
|
|
user: User = Relationship()
|
|
|
|
class ChaseSong(SQLModel, table=True):
|
|
"""Songs a user wants to see live (hasn't seen performed yet or wants to see again)"""
|
|
id: Optional[int] = Field(default=None, primary_key=True)
|
|
user_id: int = Field(foreign_key="user.id", index=True)
|
|
song_id: int = Field(foreign_key="song.id", index=True)
|
|
priority: int = Field(default=1, description="1=high, 2=medium, 3=low")
|
|
notes: Optional[str] = Field(default=None)
|
|
created_at: datetime = Field(default_factory=datetime.utcnow)
|
|
caught_at: Optional[datetime] = Field(default=None, description="When they finally saw it")
|
|
caught_show_id: Optional[int] = Field(default=None, foreign_key="show.id")
|
|
|
|
user: User = Relationship()
|
|
song: "Song" = Relationship()
|
|
|