prod: UI Optimisée mise en production
- Documentation archivée et réorganisée - Backend: Ajout tests, migrations, library service, rate limiting - Frontend: Suppression Flutter, focus sur interface web HTML/JS - Tailwind CSS ajouté pour le style - Améliorations UX et corrections bugs Generated with [Claude Code](https://claude.com/claude-code) via [Happy](https://happy.engineering) Co-Authored-By: Claude <noreply@anthropic.com> Co-Authored-By: Happy <yesreply@happy.engineering>
This commit is contained in:
@@ -1,14 +1,21 @@
|
||||
"""SQLAlchemy models."""
|
||||
from app.core.database import Base
|
||||
|
||||
from app.models.album import Album
|
||||
from app.models.artist import Artist
|
||||
from app.models.liked_track import LikedTrack
|
||||
from app.models.listening_history import ListeningHistory
|
||||
from app.models.playlist import Playlist
|
||||
from app.models.playlist_track import PlaylistTrack
|
||||
from app.models.track import Track
|
||||
from app.models.user import User
|
||||
|
||||
__all__ = [
|
||||
"Base",
|
||||
"Album",
|
||||
"Artist",
|
||||
"LikedTrack",
|
||||
"ListeningHistory",
|
||||
"Playlist",
|
||||
"PlaylistTrack",
|
||||
"Track",
|
||||
|
||||
@@ -0,0 +1,90 @@
|
||||
"""Liked Track model."""
|
||||
import uuid
|
||||
from datetime import datetime
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from sqlalchemy import String, ForeignKey, Index
|
||||
from sqlalchemy.dialects.postgresql import UUID
|
||||
from sqlalchemy.orm import Mapped, mapped_column, relationship
|
||||
|
||||
from app.core.database import Base
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from app.models.user import User
|
||||
from app.models.track import Track
|
||||
|
||||
|
||||
class LikedTrack(Base):
|
||||
"""Liked Track model representing user's liked/favorited tracks."""
|
||||
|
||||
__tablename__ = "liked_tracks"
|
||||
|
||||
# Primary key
|
||||
id: Mapped[uuid.UUID] = mapped_column(
|
||||
UUID(as_uuid=True),
|
||||
primary_key=True,
|
||||
default=uuid.uuid4,
|
||||
index=True,
|
||||
)
|
||||
|
||||
# Foreign keys
|
||||
user_id: Mapped[uuid.UUID] = mapped_column(
|
||||
UUID(as_uuid=True),
|
||||
ForeignKey("users.id", ondelete="CASCADE"),
|
||||
nullable=False,
|
||||
index=True,
|
||||
)
|
||||
track_id: Mapped[uuid.UUID] = mapped_column(
|
||||
UUID(as_uuid=True),
|
||||
ForeignKey("tracks.id", ondelete="CASCADE"),
|
||||
nullable=False,
|
||||
index=True,
|
||||
)
|
||||
|
||||
# Additional metadata
|
||||
notes: Mapped[str | None] = mapped_column(
|
||||
String(1000),
|
||||
nullable=True,
|
||||
comment="User notes about the track",
|
||||
)
|
||||
|
||||
# Timestamps
|
||||
created_at: Mapped[datetime] = mapped_column(
|
||||
default=datetime.utcnow,
|
||||
nullable=False,
|
||||
)
|
||||
updated_at: Mapped[datetime] = mapped_column(
|
||||
default=datetime.utcnow,
|
||||
onupdate=datetime.utcnow,
|
||||
nullable=False,
|
||||
)
|
||||
|
||||
# Relationships
|
||||
user: Mapped["User"] = relationship(
|
||||
"User",
|
||||
back_populates="liked_tracks",
|
||||
lazy="selectin",
|
||||
)
|
||||
track: Mapped["Track"] = relationship(
|
||||
"Track",
|
||||
lazy="selectin",
|
||||
)
|
||||
|
||||
# Table indices for optimal queries and uniqueness constraint
|
||||
__table_args__ = (
|
||||
Index("ix_liked_tracks_user_track", "user_id", "track_id", unique=True),
|
||||
)
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f"<LikedTrack user={self.user_id} track={self.track_id}>"
|
||||
|
||||
def to_dict(self) -> dict:
|
||||
"""Convert liked track model to dictionary."""
|
||||
return {
|
||||
"id": str(self.id),
|
||||
"user_id": str(self.user_id),
|
||||
"track_id": str(self.track_id),
|
||||
"notes": self.notes,
|
||||
"created_at": self.created_at.isoformat(),
|
||||
"updated_at": self.updated_at.isoformat(),
|
||||
}
|
||||
@@ -0,0 +1,105 @@
|
||||
"""Listening History model."""
|
||||
import uuid
|
||||
from datetime import datetime
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from sqlalchemy import Integer, String, Boolean, ForeignKey, Index
|
||||
from sqlalchemy.dialects.postgresql import UUID
|
||||
from sqlalchemy.orm import Mapped, mapped_column, relationship
|
||||
|
||||
from app.core.database import Base
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from app.models.user import User
|
||||
from app.models.track import Track
|
||||
|
||||
|
||||
class ListeningHistory(Base):
|
||||
"""Listening History model representing user's track listening history."""
|
||||
|
||||
__tablename__ = "listening_history"
|
||||
|
||||
# Primary key
|
||||
id: Mapped[uuid.UUID] = mapped_column(
|
||||
UUID(as_uuid=True),
|
||||
primary_key=True,
|
||||
default=uuid.uuid4,
|
||||
index=True,
|
||||
)
|
||||
|
||||
# Foreign keys
|
||||
user_id: Mapped[uuid.UUID] = mapped_column(
|
||||
UUID(as_uuid=True),
|
||||
ForeignKey("users.id", ondelete="CASCADE"),
|
||||
nullable=False,
|
||||
index=True,
|
||||
)
|
||||
track_id: Mapped[uuid.UUID] = mapped_column(
|
||||
UUID(as_uuid=True),
|
||||
ForeignKey("tracks.id", ondelete="CASCADE"),
|
||||
nullable=False,
|
||||
index=True,
|
||||
)
|
||||
|
||||
# Playback details
|
||||
played_for: Mapped[int] = mapped_column(
|
||||
Integer,
|
||||
nullable=False,
|
||||
default=0,
|
||||
comment="Duration played in seconds",
|
||||
)
|
||||
completed: Mapped[bool] = mapped_column(
|
||||
Boolean,
|
||||
default=False,
|
||||
comment="Whether the track was played to completion",
|
||||
)
|
||||
|
||||
# Source information
|
||||
source: Mapped[str | None] = mapped_column(
|
||||
String(50),
|
||||
comment="Playback source (library, playlist, search, etc.)",
|
||||
)
|
||||
|
||||
# Timestamps
|
||||
played_at: Mapped[datetime] = mapped_column(
|
||||
default=datetime.utcnow,
|
||||
nullable=False,
|
||||
index=True,
|
||||
)
|
||||
created_at: Mapped[datetime] = mapped_column(
|
||||
default=datetime.utcnow,
|
||||
nullable=False,
|
||||
)
|
||||
|
||||
# Relationships
|
||||
user: Mapped["User"] = relationship(
|
||||
"User",
|
||||
back_populates="listening_history",
|
||||
lazy="selectin",
|
||||
)
|
||||
track: Mapped["Track"] = relationship(
|
||||
"Track",
|
||||
lazy="selectin",
|
||||
)
|
||||
|
||||
# Table indices for optimal queries
|
||||
__table_args__ = (
|
||||
Index("ix_listening_history_user_played", "user_id", "played_at"),
|
||||
Index("ix_listening_history_user_track", "user_id", "track_id"),
|
||||
)
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f"<ListeningHistory user={self.user_id} track={self.track_id} at={self.played_at}>"
|
||||
|
||||
def to_dict(self) -> dict:
|
||||
"""Convert listening history model to dictionary."""
|
||||
return {
|
||||
"id": str(self.id),
|
||||
"user_id": str(self.user_id),
|
||||
"track_id": str(self.track_id),
|
||||
"played_for": self.played_for,
|
||||
"completed": bool(self.completed),
|
||||
"source": self.source,
|
||||
"played_at": self.played_at.isoformat(),
|
||||
"created_at": self.created_at.isoformat(),
|
||||
}
|
||||
@@ -12,6 +12,8 @@ from app.core.database import Base
|
||||
if TYPE_CHECKING:
|
||||
from app.models.playlist import Playlist
|
||||
from app.models.playlist_track import PlaylistTrack
|
||||
from app.models.listening_history import ListeningHistory
|
||||
from app.models.liked_track import LikedTrack
|
||||
|
||||
|
||||
class User(Base):
|
||||
@@ -100,6 +102,20 @@ class User(Base):
|
||||
lazy="selectin",
|
||||
)
|
||||
|
||||
listening_history: Mapped[list["ListeningHistory"]] = relationship(
|
||||
"ListeningHistory",
|
||||
back_populates="user",
|
||||
cascade="all, delete-orphan",
|
||||
lazy="selectin",
|
||||
)
|
||||
|
||||
liked_tracks: Mapped[list["LikedTrack"]] = relationship(
|
||||
"LikedTrack",
|
||||
back_populates="user",
|
||||
cascade="all, delete-orphan",
|
||||
lazy="selectin",
|
||||
)
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f"<User {self.username} ({self.email})>"
|
||||
|
||||
|
||||
Reference in New Issue
Block a user