Files
you-music/backend/app/models/models.py
T
junv f7e4686816 Implement the following:* Search function should time out in 1 min if something goes wrong
* Add function tofull-size current playing song through a button in the bottom control panel
* Add an API also a feature in the home page, to allow user type a song name, then create a backend job to auto search and download song in the background, when pick the song from the results, put the result into priority candicdate if the name either has “official song” or “官方”, if the same exact song has been downloaded before, then just put the job as pending to confirm, and let user to confirm, once user confirmed, then the system can download the duplicate song, we need to persist the those jobs, if a job fails, then we mark it failed, and user can retry it later through UI, also add an summary about how to trigger this function through API
* Add API keys section in settings, so we need to evaluate api keys for public APIs
    * Now we only put the auto search and download song into the pubic API
* Add feature in search to search artist, which would return all the matched artists,
* Add sort feature to the artists and library page, like the one we have in playlist detail page
* By default sort the song in the library page by added at desc
2025-10-31 09:17:44 +11:00

108 lines
4.9 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")
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 song" 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
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)