mirror of
https://github.com/wahyd4/you-music.git
synced 2026-08-08 20:59:47 +10:00
155 lines
6.6 KiB
Python
155 lines
6.6 KiB
Python
from sqlalchemy import Column, Integer, String, Float, DateTime, ForeignKey, Table, Text, Boolean
|
|
from sqlalchemy.orm import relationship
|
|
from datetime import datetime, timedelta
|
|
from app.db.session import Base
|
|
import secrets
|
|
|
|
# Association table for playlist-music many-to-many relationship
|
|
playlist_music = Table(
|
|
'playlist_music',
|
|
Base.metadata,
|
|
Column('playlist_id', Integer, ForeignKey('playlists.id'), primary_key=True),
|
|
Column('music_id', Integer, ForeignKey('music.id'), primary_key=True),
|
|
Column('position', Integer, default=0),
|
|
Column('added_at', DateTime, default=datetime.utcnow),
|
|
)
|
|
|
|
|
|
class Music(Base):
|
|
__tablename__ = "music"
|
|
|
|
id = Column(Integer, primary_key=True, index=True)
|
|
title = Column(String, index=True)
|
|
artist = Column(String, index=True, nullable=True)
|
|
album = Column(String, nullable=True)
|
|
duration = Column(Float, nullable=True)
|
|
file_path = Column(String, unique=True)
|
|
file_size = Column(Integer, nullable=True)
|
|
file_format = Column(String, nullable=True) # mp3, flac, m4a, wav, ogg, etc.
|
|
file_location = Column(String, nullable=True) # Full absolute path for user reference
|
|
file_exists = Column(Boolean, default=True) # Track if file still exists on disk
|
|
source_url = Column(String, nullable=True)
|
|
source_type = Column(String, nullable=True) # local, youtube, bilibili, etc.
|
|
thumbnail = Column(String, nullable=True)
|
|
lyrics = Column(Text, nullable=True)
|
|
share_token = Column(String, unique=True, index=True, nullable=True) # Secure share token
|
|
share_token_expires_at = Column(DateTime, nullable=True) # Expiration timestamp
|
|
created_at = Column(DateTime, default=datetime.utcnow)
|
|
updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
|
|
last_scanned_at = Column(DateTime, nullable=True) # Last time file was verified
|
|
|
|
playlists = relationship("Playlist", secondary=playlist_music, back_populates="music_items")
|
|
|
|
@property
|
|
def artist_list(self) -> list:
|
|
"""Get list of individual artists from the artist field"""
|
|
if not self.artist or self.artist == "Unknown":
|
|
return ["Unknown"]
|
|
|
|
import re
|
|
# Split by / or , and clean up whitespace
|
|
artists = re.split(r'[/,]', self.artist)
|
|
return [a.strip() for a in artists if a.strip()]
|
|
|
|
@property
|
|
def display_artist(self) -> str:
|
|
"""Get display-friendly artist string"""
|
|
artists = self.artist_list
|
|
if not artists or artists == ["Unknown"]:
|
|
return "Unknown"
|
|
return ", ".join(artists)
|
|
|
|
def generate_share_token(self, expiration_days: int = 14):
|
|
"""Generate a secure random share token with expiration"""
|
|
if not self.share_token:
|
|
self.share_token = secrets.token_urlsafe(16)
|
|
self.share_token_expires_at = datetime.utcnow() + timedelta(days=expiration_days)
|
|
|
|
def is_share_token_valid(self) -> bool:
|
|
"""Check if share token is still valid"""
|
|
if not self.share_token or not self.share_token_expires_at:
|
|
return False
|
|
return datetime.utcnow() < self.share_token_expires_at
|
|
|
|
|
|
class Playlist(Base):
|
|
__tablename__ = "playlists"
|
|
|
|
id = Column(Integer, primary_key=True, index=True)
|
|
name = Column(String, unique=True, index=True)
|
|
description = Column(Text, nullable=True)
|
|
thumbnail = Column(String, nullable=True)
|
|
created_at = Column(DateTime, default=datetime.utcnow)
|
|
updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
|
|
|
|
music_items = relationship("Music", secondary=playlist_music, back_populates="playlists")
|
|
|
|
|
|
class AppSettings(Base):
|
|
__tablename__ = "app_settings"
|
|
|
|
id = Column(Integer, primary_key=True, index=True)
|
|
key = Column(String, unique=True, index=True)
|
|
value = Column(Text)
|
|
updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
|
|
|
|
|
|
class DownloadJob(Base):
|
|
__tablename__ = "download_jobs"
|
|
|
|
id = Column(Integer, primary_key=True, index=True)
|
|
song_name = Column(String, index=True)
|
|
status = Column(String, index=True) # pending, searching, downloading, completed, failed, waiting_confirmation
|
|
search_results = Column(Text, nullable=True) # JSON string of search results
|
|
selected_result = Column(Text, nullable=True) # JSON string of selected result
|
|
selected_result_index = Column(Integer, nullable=True) # Index of selected result
|
|
priority = Column(Boolean, default=False) # True if contains "official" or "官方"
|
|
error_message = Column(Text, nullable=True)
|
|
music_id = Column(Integer, ForeignKey('music.id'), nullable=True) # Reference to downloaded music
|
|
confirmed = Column(Boolean, default=False) # Whether user confirmed duplicate download
|
|
is_duplicate = Column(Boolean, default=False) # Whether song already exists
|
|
duplicate_music_id = Column(Integer, nullable=True) # ID of existing duplicate
|
|
thumbnail = Column(String, nullable=True) # Thumbnail URL from search results
|
|
created_at = Column(DateTime, default=datetime.utcnow, index=True)
|
|
updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
|
|
|
|
|
|
class APIKey(Base):
|
|
__tablename__ = "api_keys"
|
|
|
|
id = Column(Integer, primary_key=True, index=True)
|
|
key = Column(String, unique=True, index=True)
|
|
name = Column(String)
|
|
description = Column(Text, nullable=True)
|
|
is_active = Column(Boolean, default=True)
|
|
created_at = Column(DateTime, default=datetime.utcnow)
|
|
expires_at = Column(DateTime, nullable=True)
|
|
last_used_at = Column(DateTime, nullable=True)
|
|
|
|
|
|
class ScanHistory(Base):
|
|
__tablename__ = "scan_history"
|
|
|
|
id = Column(Integer, primary_key=True, index=True)
|
|
started_at = Column(DateTime, default=datetime.utcnow, index=True)
|
|
completed_at = Column(DateTime, nullable=True)
|
|
files_scanned = Column(Integer, default=0)
|
|
files_added = Column(Integer, default=0)
|
|
files_updated = Column(Integer, default=0)
|
|
files_missing = Column(Integer, default=0)
|
|
errors_count = Column(Integer, default=0)
|
|
status = Column(String, default="in_progress") # in_progress, completed, failed
|
|
created_at = Column(DateTime, default=datetime.utcnow)
|
|
|
|
|
|
class ArtistAlias(Base):
|
|
"""Artist alias management for merging artists with different names"""
|
|
__tablename__ = "artist_aliases"
|
|
|
|
id = Column(Integer, primary_key=True, index=True)
|
|
display_name = Column(String, index=True) # The main name to display
|
|
alias_name = Column(String, index=True, unique=True) # An alias (could be English/Chinese name, etc.)
|
|
created_at = Column(DateTime, default=datetime.utcnow)
|
|
updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
|
|
|