diff --git a/app/__init__.py b/app/__init__.py index 4b8525b..f494729 100644 --- a/app/__init__.py +++ b/app/__init__.py @@ -100,7 +100,6 @@ def create_app(): auth, main, player, - playlist, soundboard, sounds, stream, @@ -114,7 +113,6 @@ def create_app(): app.register_blueprint(sounds.bp, url_prefix="/api/sounds") app.register_blueprint(stream.bp, url_prefix="/api/stream") app.register_blueprint(player.bp, url_prefix="/api/player") - app.register_blueprint(playlist.bp, url_prefix="/api/playlists") # Shutdown services when app is torn down @app.teardown_appcontext diff --git a/app/models/playlist.py b/app/models/playlist.py index 864dc90..dd5e8a6 100644 --- a/app/models/playlist.py +++ b/app/models/playlist.py @@ -81,19 +81,6 @@ class Playlist(db.Model): ), } - def to_detailed_dict(self) -> dict: - """Convert playlist to detailed dictionary with sounds.""" - playlist_dict = self.to_dict() - playlist_dict["sounds"] = [ - { - "sound": ps.sound.to_dict() if ps.sound else None, - "order": ps.order, - "added_at": ps.added_at.isoformat() if ps.added_at else None, - } - for ps in sorted(self.playlist_sounds, key=lambda x: x.order) - ] - return playlist_dict - @classmethod def create_playlist( cls, @@ -123,26 +110,6 @@ class Playlist(db.Model): return playlist - @classmethod - def find_by_name( - cls, name: str, user_id: Optional[int] = None - ) -> Optional["Playlist"]: - """Find playlist by name, optionally filtered by user.""" - query = cls.query.filter_by(name=name) - if user_id is not None: - query = query.filter_by(user_id=user_id) - return query.first() - - @classmethod - def find_by_user(cls, user_id: int) -> list["Playlist"]: - """Find all playlists for a user.""" - return cls.query.filter_by(user_id=user_id).order_by(cls.name).all() - - @classmethod - def find_system_playlists(cls) -> list["Playlist"]: - """Find all system playlists (user_id is None).""" - return cls.query.filter_by(user_id=None).order_by(cls.name).all() - @classmethod def find_current_playlist( cls, user_id: Optional[int] = None @@ -163,22 +130,6 @@ class Playlist(db.Model): query = query.filter_by(user_id=user_id) return query.first() - def set_as_current(self, commit: bool = True) -> None: - """Set this playlist as the current one and unset others.""" - # Unset other current playlists for the same user/system - if self.user_id is not None: - Playlist.query.filter_by( - user_id=self.user_id, is_current=True - ).update({"is_current": False}) - else: - Playlist.query.filter_by(user_id=None, is_current=True).update( - {"is_current": False} - ) - - self.is_current = True - if commit: - db.session.commit() - def add_sound( self, sound_id: int, order: Optional[int] = None, commit: bool = True ) -> "PlaylistSound": @@ -203,87 +154,3 @@ class Playlist(db.Model): db.session.commit() return playlist_sound - - def remove_sound(self, sound_id: int, commit: bool = True) -> bool: - """Remove a sound from the playlist.""" - from app.models.playlist_sound import PlaylistSound - - playlist_sound = PlaylistSound.query.filter_by( - playlist_id=self.id, sound_id=sound_id - ).first() - - if playlist_sound: - db.session.delete(playlist_sound) - if commit: - db.session.commit() - return True - - return False - - def reorder_sounds( - self, sound_orders: list[dict], commit: bool = True - ) -> None: - """Reorder sounds in the playlist. - - Args: - sound_orders: List of dicts with 'sound_id' and 'order' keys - """ - from app.models.playlist_sound import PlaylistSound - - for item in sound_orders: - playlist_sound = PlaylistSound.query.filter_by( - playlist_id=self.id, sound_id=item["sound_id"] - ).first() - if playlist_sound: - playlist_sound.order = item["order"] - - if commit: - db.session.commit() - - def get_total_duration(self) -> int: - """Get total duration of all sounds in the playlist in milliseconds.""" - from app.models.sound import Sound - - total = ( - db.session.query(db.func.sum(Sound.duration)) - .join(self.playlist_sounds) - .filter(Sound.id.in_([ps.sound_id for ps in self.playlist_sounds])) - .scalar() - ) - - return total or 0 - - def duplicate( - self, new_name: str, user_id: Optional[int] = None, commit: bool = True - ) -> "Playlist": - """Create a duplicate of this playlist.""" - new_playlist = Playlist.create_playlist( - name=new_name, - description=self.description, - genre=self.genre, - user_id=user_id, - is_main=False, - is_deletable=True, - is_current=False, - commit=commit, - ) - - # Copy all sounds with their order - for ps in self.playlist_sounds: - new_playlist.add_sound(ps.sound_id, ps.order, commit=False) - - if commit: - db.session.commit() - - return new_playlist - - def save(self, commit: bool = True) -> None: - """Save changes to the playlist.""" - if commit: - db.session.commit() - - def delete(self, commit: bool = True) -> None: - """Delete the playlist.""" - db.session.delete(self) - if commit: - db.session.commit() diff --git a/app/models/playlist_sound.py b/app/models/playlist_sound.py index eaa89ff..a864dc4 100644 --- a/app/models/playlist_sound.py +++ b/app/models/playlist_sound.py @@ -63,126 +63,3 @@ class PlaylistSound(db.Model): "added_at": self.added_at.isoformat() if self.added_at else None, "sound": self.sound.to_dict() if self.sound else None, } - - @classmethod - def create_playlist_sound( - cls, - playlist_id: int, - sound_id: int, - order: int, - commit: bool = True, - ) -> "PlaylistSound": - """Create a new playlist-sound relationship.""" - playlist_sound = cls( - playlist_id=playlist_id, - sound_id=sound_id, - order=order, - ) - - db.session.add(playlist_sound) - if commit: - db.session.commit() - - return playlist_sound - - @classmethod - def find_by_playlist(cls, playlist_id: int) -> list["PlaylistSound"]: - """Find all sounds in a playlist ordered by their position.""" - return ( - cls.query.filter_by(playlist_id=playlist_id) - .order_by(cls.order) - .all() - ) - - @classmethod - def find_by_sound(cls, sound_id: int) -> list["PlaylistSound"]: - """Find all playlists containing a specific sound.""" - return cls.query.filter_by(sound_id=sound_id).all() - - @classmethod - def find_by_playlist_and_sound( - cls, playlist_id: int, sound_id: int - ) -> Optional["PlaylistSound"]: - """Find a specific playlist-sound relationship.""" - return cls.query.filter_by( - playlist_id=playlist_id, sound_id=sound_id - ).first() - - @classmethod - def get_next_order(cls, playlist_id: int) -> int: - """Get the next order number for a playlist.""" - max_order = ( - db.session.query(db.func.max(cls.order)) - .filter_by(playlist_id=playlist_id) - .scalar() - ) - return (max_order or 0) + 1 - - @classmethod - def reorder_playlist( - cls, playlist_id: int, sound_orders: list[dict], commit: bool = True - ) -> None: - """Reorder all sounds in a playlist. - - Args: - playlist_id: ID of the playlist - sound_orders: List of dicts with 'sound_id' and 'order' keys - """ - for item in sound_orders: - playlist_sound = cls.query.filter_by( - playlist_id=playlist_id, sound_id=item["sound_id"] - ).first() - if playlist_sound: - playlist_sound.order = item["order"] - - if commit: - db.session.commit() - - def move_to_position(self, new_order: int, commit: bool = True) -> None: - """Move this sound to a new position in the playlist.""" - old_order = self.order - - if new_order == old_order: - return - - # Get all other sounds in the playlist - other_sounds = ( - PlaylistSound.query.filter_by(playlist_id=self.playlist_id) - .filter(PlaylistSound.id != self.id) - .order_by(PlaylistSound.order) - .all() - ) - - # Remove this sound from its current position - remaining_sounds = [ps for ps in other_sounds if ps.order != old_order] - - # Insert at new position - if new_order <= len(remaining_sounds): - remaining_sounds.insert(new_order - 1, self) - else: - remaining_sounds.append(self) - - # Update all order values - for i, ps in enumerate(remaining_sounds, 1): - ps.order = i - - if commit: - db.session.commit() - - def get_previous_sound(self) -> Optional["PlaylistSound"]: - """Get the previous sound in the playlist.""" - return ( - PlaylistSound.query.filter_by(playlist_id=self.playlist_id) - .filter(PlaylistSound.order < self.order) - .order_by(PlaylistSound.order.desc()) - .first() - ) - - def get_next_sound(self) -> Optional["PlaylistSound"]: - """Get the next sound in the playlist.""" - return ( - PlaylistSound.query.filter_by(playlist_id=self.playlist_id) - .filter(PlaylistSound.order > self.order) - .order_by(PlaylistSound.order.asc()) - .first() - ) diff --git a/app/models/sound.py b/app/models/sound.py index a4139f4..6e817d1 100644 --- a/app/models/sound.py +++ b/app/models/sound.py @@ -197,21 +197,6 @@ class Sound(db.Model): """Find all sounds by type.""" return cls.query.filter_by(type=sound_type).all() - @classmethod - def get_most_played(cls, limit: int = 10) -> list["Sound"]: - """Get the most played sounds.""" - return cls.query.order_by(cls.play_count.desc()).limit(limit).all() - - @classmethod - def get_music_sounds(cls) -> list["Sound"]: - """Get all music sounds.""" - return cls.query.filter_by(is_music=True).all() - - @classmethod - def get_deletable_sounds(cls) -> list["Sound"]: - """Get all deletable sounds.""" - return cls.query.filter_by(is_deletable=True).all() - @classmethod def create_sound( cls, diff --git a/app/models/sound_played.py b/app/models/sound_played.py index c4a471d..d20c8db 100644 --- a/app/models/sound_played.py +++ b/app/models/sound_played.py @@ -92,173 +92,3 @@ class SoundPlayed(db.Model): if commit: db.session.commit() return play_record - - @classmethod - def get_user_plays( - cls, - user_id: int, - limit: int = 50, - offset: int = 0, - ) -> list["SoundPlayed"]: - """Get recent plays for a specific user.""" - return ( - cls.query.filter_by(user_id=user_id) - .order_by(cls.played_at.desc()) - .offset(offset) - .limit(limit) - .all() - ) - - @classmethod - def get_sound_plays( - cls, - sound_id: int, - limit: int = 50, - offset: int = 0, - ) -> list["SoundPlayed"]: - """Get recent plays for a specific sound.""" - return ( - cls.query.filter_by(sound_id=sound_id) - .order_by(cls.played_at.desc()) - .offset(offset) - .limit(limit) - .all() - ) - - @classmethod - def get_recent_plays( - cls, - limit: int = 100, - offset: int = 0, - ) -> list["SoundPlayed"]: - """Get recent plays across all users and sounds.""" - return ( - cls.query.order_by(cls.played_at.desc()) - .offset(offset) - .limit(limit) - .all() - ) - - @classmethod - def get_user_play_count(cls, user_id: int) -> int: - """Get total play count for a user.""" - return cls.query.filter_by(user_id=user_id).count() - - @classmethod - def get_sound_play_count(cls, sound_id: int) -> int: - """Get total play count for a sound.""" - return cls.query.filter_by(sound_id=sound_id).count() - - @classmethod - def get_popular_sounds( - cls, - limit: int = 10, - days: int | None = None, - ) -> list[dict]: - """Get most popular sounds with play counts.""" - from app.models.sound import Sound - - query = ( - db.session.query( - cls.sound_id, - func.count(cls.id).label("play_count"), - func.max(cls.played_at).label("last_played"), - ) - .group_by(cls.sound_id) - .order_by(func.count(cls.id).desc()) - ) - - if days: - query = query.filter( - cls.played_at >= text(f"datetime('now', '-{days} days')"), - ) - - results = query.limit(limit).all() - - # Get sound details - popular_sounds = [] - for result in results: - sound = Sound.query.get(result.sound_id) - if sound: - popular_sounds.append( - { - "sound": sound.to_dict(), - "play_count": result.play_count, - "last_played": ( - result.last_played.isoformat() - if result.last_played - else None - ), - }, - ) - - return popular_sounds - - @classmethod - def get_user_stats(cls, user_id: int) -> dict: - """Get comprehensive stats for a user.""" - from app.models.sound import Sound - - total_plays = cls.query.filter_by(user_id=user_id).count() - - if total_plays == 0: - return { - "total_plays": 0, - "unique_sounds": 0, - "favorite_sound": None, - "first_play": None, - "last_play": None, - } - - # Get unique sounds count - unique_sounds = ( - db.session.query(cls.sound_id) - .filter_by(user_id=user_id) - .distinct() - .count() - ) - - # Get favorite sound - favorite_query = ( - db.session.query( - cls.sound_id, - func.count(cls.id).label("play_count"), - ) - .filter_by(user_id=user_id) - .group_by(cls.sound_id) - .order_by(func.count(cls.id).desc()) - .first() - ) - - favorite_sound = None - if favorite_query: - sound = Sound.query.get(favorite_query.sound_id) - if sound: - favorite_sound = { - "sound": sound.to_dict(), - "play_count": favorite_query.play_count, - } - - # Get first and last play dates - first_play = ( - cls.query.filter_by(user_id=user_id) - .order_by(cls.played_at.asc()) - .first() - ) - last_play = ( - cls.query.filter_by(user_id=user_id) - .order_by(cls.played_at.desc()) - .first() - ) - - return { - "total_plays": total_plays, - "unique_sounds": unique_sounds, - "favorite_sound": favorite_sound, - "first_play": ( - first_play.played_at.isoformat() if first_play else None - ), - "last_play": ( - last_play.played_at.isoformat() if last_play else None - ), - } diff --git a/app/models/stream.py b/app/models/stream.py index 27f266f..a62a66d 100644 --- a/app/models/stream.py +++ b/app/models/stream.py @@ -4,7 +4,14 @@ from datetime import datetime from typing import TYPE_CHECKING, Optional from zoneinfo import ZoneInfo -from sqlalchemy import DateTime, ForeignKey, Integer, String, Text, UniqueConstraint +from sqlalchemy import ( + DateTime, + ForeignKey, + Integer, + String, + Text, + UniqueConstraint, +) from sqlalchemy.orm import Mapped, mapped_column, relationship from app.database import db @@ -51,9 +58,7 @@ class Stream(db.Model): # Constraints __table_args__ = ( - UniqueConstraint( - "service", "service_id", name="unique_service_stream" - ), + UniqueConstraint("service", "service_id", name="unique_service_stream"), ) def __repr__(self) -> str: @@ -117,70 +122,3 @@ class Stream(db.Model): db.session.commit() return stream - - @classmethod - def find_by_service_and_id( - cls, service: str, service_id: str - ) -> Optional["Stream"]: - """Find stream by service and service_id.""" - return cls.query.filter_by( - service=service, service_id=service_id - ).first() - - @classmethod - def find_by_sound(cls, sound_id: int) -> list["Stream"]: - """Find all streams for a specific sound.""" - return cls.query.filter_by(sound_id=sound_id).all() - - @classmethod - def find_by_service(cls, service: str) -> list["Stream"]: - """Find all streams for a specific service.""" - return cls.query.filter_by(service=service).all() - - @classmethod - def find_by_status(cls, status: str) -> list["Stream"]: - """Find all streams with a specific status.""" - return cls.query.filter_by(status=status).all() - - @classmethod - def find_active_streams(cls) -> list["Stream"]: - """Find all active streams.""" - return cls.query.filter_by(status="active").all() - - def update_metadata( - self, - title: Optional[str] = None, - track: Optional[str] = None, - artist: Optional[str] = None, - album: Optional[str] = None, - genre: Optional[str] = None, - commit: bool = True, - ) -> None: - """Update stream metadata.""" - if title is not None: - self.title = title - if track is not None: - self.track = track - if artist is not None: - self.artist = artist - if album is not None: - self.album = album - if genre is not None: - self.genre = genre - - if commit: - db.session.commit() - - def set_status(self, status: str, commit: bool = True) -> None: - """Update stream status.""" - self.status = status - if commit: - db.session.commit() - - def is_active(self) -> bool: - """Check if stream is active.""" - return self.status == "active" - - def get_display_name(self) -> str: - """Get a display name for the stream (title or track or service_id).""" - return self.title or self.track or self.service_id diff --git a/app/routes/admin_sounds.py b/app/routes/admin_sounds.py index 9752978..6ff19bb 100644 --- a/app/routes/admin_sounds.py +++ b/app/routes/admin_sounds.py @@ -82,53 +82,3 @@ def check_ffmpeg(): return jsonify(ffmpeg_status), 200 except Exception as e: return jsonify({"error": str(e)}), 500 - - -@bp.route("/list", methods=["GET"]) -@require_admin -def list_sounds(): - """Get detailed list of all sounds with normalization status.""" - from app.services.sound_management_service import SoundManagementService - - return ErrorHandlingService.wrap_service_call( - SoundManagementService.get_sounds_with_file_status, - request.args.get("type", "SDB"), - int(request.args.get("page", 1)), - int(request.args.get("per_page", 50)), - ) - - -@bp.route("/", methods=["DELETE"]) -@require_admin -def delete_sound(sound_id: int): - """Delete a sound and its files.""" - from app.services.sound_management_service import SoundManagementService - - return ErrorHandlingService.wrap_service_call( - SoundManagementService.delete_sound_with_files, - sound_id, - ) - - -@bp.route("//normalize", methods=["POST"]) -@require_admin -def normalize_single_sound(sound_id: int): - """Normalize a specific sound.""" - try: - from app.services.sound_management_service import SoundManagementService - - data = request.get_json() or {} - overwrite = data.get("overwrite", False) - two_pass = data.get("two_pass", True) - - result = SoundManagementService.normalize_sound( - sound_id, - overwrite, - two_pass, - ) - - if result["success"]: - return jsonify(result), 200 - return jsonify(result), 400 - except Exception as e: - return jsonify({"error": str(e)}), 500 diff --git a/app/routes/main.py b/app/routes/main.py index 85e57f9..09511e1 100644 --- a/app/routes/main.py +++ b/app/routes/main.py @@ -2,71 +2,10 @@ from flask import Blueprint -from app.services.decorators import ( - get_current_user, - require_auth, - require_credits, -) - bp = Blueprint("main", __name__) -@bp.route("/") -def index() -> dict[str, str]: - """Root endpoint that returns API status.""" - return {"message": "API is running", "status": "ok"} - - -@bp.route("/protected") -@require_auth -def protected() -> dict[str, str]: - """Protected endpoint that requires authentication.""" - user = get_current_user() - return { - "message": f"Hello {user['name']}, this is a protected endpoint!", - "user": user, - } - - -@bp.route("/api-protected") -@require_auth -def api_protected() -> dict[str, str]: - """Protected endpoint that accepts JWT or API token authentication.""" - user = get_current_user() - return { - "message": f"Hello {user['name']}, you accessed this via {user['provider']}!", - "user": user, - } - - @bp.route("/health") def health() -> dict[str, str]: """Health check endpoint.""" return {"status": "ok"} - - -@bp.route("/use-credits/") -@require_auth -@require_credits(5) -def use_credits(amount: int) -> dict[str, str]: - """Test endpoint that costs 5 credits to use.""" - user = get_current_user() - return { - "message": f"Successfully used endpoint! You requested amount: {amount}", - "user": user["email"], - "remaining_credits": user["credits"] - - 5, # Note: credits already deducted by decorator - } - - -@bp.route("/expensive-operation") -@require_auth -@require_credits(10) -def expensive_operation() -> dict[str, str]: - """Test endpoint that costs 10 credits to use.""" - user = get_current_user() - return { - "message": "Expensive operation completed successfully!", - "user": user["email"], - "operation_cost": 10, - } diff --git a/app/routes/player.py b/app/routes/player.py index 7bd1b7f..3687b74 100644 --- a/app/routes/player.py +++ b/app/routes/player.py @@ -93,11 +93,14 @@ def seek(): data = request.get_json() if not data or "position" not in data: return jsonify({"error": "Position required"}), 400 - + position = float(data["position"]) if not 0.0 <= position <= 1.0: - return jsonify({"error": "Position must be between 0.0 and 1.0"}), 400 - + return ( + jsonify({"error": "Position must be between 0.0 and 1.0"}), + 400, + ) + success = music_player_service.seek(position) if success: return jsonify({"message": "Seek successful"}), 200 @@ -116,11 +119,11 @@ def set_volume(): data = request.get_json() if not data or "volume" not in data: return jsonify({"error": "Volume required"}), 400 - + volume = int(data["volume"]) if not 0 <= volume <= 100: return jsonify({"error": "Volume must be between 0 and 100"}), 400 - + success = music_player_service.set_volume(volume) if success: return jsonify({"message": "Volume set successfully"}), 200 @@ -139,12 +142,23 @@ def set_play_mode(): data = request.get_json() if not data or "mode" not in data: return jsonify({"error": "Mode required"}), 400 - + mode = data["mode"] - valid_modes = ["continuous", "loop-playlist", "loop-one", "random", "single"] + valid_modes = [ + "continuous", + "loop-playlist", + "loop-one", + "random", + "single", + ] if mode not in valid_modes: - return jsonify({"error": f"Mode must be one of: {', '.join(valid_modes)}"}), 400 - + return ( + jsonify( + {"error": f"Mode must be one of: {', '.join(valid_modes)}"} + ), + 400, + ) + success = music_player_service.set_play_mode(mode) if success: return jsonify({"message": "Play mode set successfully"}), 200 @@ -161,7 +175,7 @@ def load_playlist(): data = request.get_json() if not data or "playlist_id" not in data: return jsonify({"error": "Playlist ID required"}), 400 - + playlist_id = int(data["playlist_id"]) success = music_player_service.load_playlist(playlist_id) if success: @@ -181,7 +195,7 @@ def play_track(): data = request.get_json() if not data or "index" not in data: return jsonify({"error": "Track index required"}), 400 - + index = int(data["index"]) success = music_player_service.play_track_at_index(index) if success: @@ -191,42 +205,3 @@ def play_track(): return jsonify({"error": "Invalid track index"}), 400 except Exception as e: return ErrorHandlingService.handle_generic_error(e) - - -@bp.route("/start-instance", methods=["POST"]) -@require_auth -def start_vlc_instance(): - """Start the VLC player instance.""" - try: - success = music_player_service.start_vlc_instance() - if success: - return jsonify({"message": "VLC instance started successfully"}), 200 - return jsonify({"error": "Failed to start VLC instance"}), 500 - except Exception as e: - return ErrorHandlingService.handle_generic_error(e) - - -@bp.route("/stop-instance", methods=["POST"]) -@require_auth -def stop_vlc_instance(): - """Stop the VLC player instance.""" - try: - success = music_player_service.stop_vlc_instance() - if success: - return jsonify({"message": "VLC instance stopped successfully"}), 200 - return jsonify({"error": "Failed to stop VLC instance"}), 500 - except Exception as e: - return ErrorHandlingService.handle_generic_error(e) - - - -@bp.route("/test-emit", methods=["POST"]) -@require_auth -def test_emit(): - """Test SocketIO emission manually.""" - try: - # Force emit player state - music_player_service._emit_player_state() - return jsonify({"message": "Test emission sent"}), 200 - except Exception as e: - return ErrorHandlingService.handle_generic_error(e) \ No newline at end of file diff --git a/app/routes/playlist.py b/app/routes/playlist.py deleted file mode 100644 index 7e21b49..0000000 --- a/app/routes/playlist.py +++ /dev/null @@ -1,250 +0,0 @@ -"""Playlist management routes.""" - -from flask import Blueprint, jsonify, request -from app.models.playlist import Playlist -from app.models.sound import Sound -from app.services.decorators import require_auth -from app.services.music_player_service import music_player_service -from app.services.logging_service import LoggingService - -logger = LoggingService.get_logger(__name__) - -bp = Blueprint("playlist", __name__) - - -@bp.route("/", methods=["GET"]) -@require_auth -def get_playlists(): - """Get all playlists.""" - try: - # Get system playlists and user playlists - system_playlists = Playlist.find_system_playlists() - user_playlists = [] # TODO: Add user-specific playlists when user auth is implemented - - playlists = system_playlists + user_playlists - - return jsonify({ - "playlists": [playlist.to_dict() for playlist in playlists] - }) - except Exception as e: - logger.error(f"Error getting playlists: {e}") - return jsonify({"error": "Failed to get playlists"}), 500 - - -@bp.route("/", methods=["GET"]) -@require_auth -def get_playlist(playlist_id): - """Get a specific playlist with sounds.""" - try: - playlist = Playlist.query.get_or_404(playlist_id) - return jsonify({"playlist": playlist.to_detailed_dict()}) - except Exception as e: - logger.error(f"Error getting playlist {playlist_id}: {e}") - return jsonify({"error": "Failed to get playlist"}), 500 - - -@bp.route("/", methods=["POST"]) -@require_auth -def create_playlist(): - """Create a new playlist.""" - try: - data = request.get_json() - name = data.get("name") - description = data.get("description") - genre = data.get("genre") - - if not name: - return jsonify({"error": "Playlist name is required"}), 400 - - # Check if playlist with same name already exists - existing = Playlist.find_by_name(name) - if existing: - return jsonify({"error": "Playlist with this name already exists"}), 400 - - playlist = Playlist.create_playlist( - name=name, - description=description, - genre=genre, - user_id=None, # System playlist for now - is_deletable=True - ) - - return jsonify({"playlist": playlist.to_dict()}), 201 - except Exception as e: - logger.error(f"Error creating playlist: {e}") - return jsonify({"error": "Failed to create playlist"}), 500 - - -@bp.route("/", methods=["PUT"]) -@require_auth -def update_playlist(playlist_id): - """Update a playlist.""" - try: - playlist = Playlist.query.get_or_404(playlist_id) - data = request.get_json() - - if "name" in data: - playlist.name = data["name"] - if "description" in data: - playlist.description = data["description"] - if "genre" in data: - playlist.genre = data["genre"] - - playlist.save() - - return jsonify({"playlist": playlist.to_dict()}) - except Exception as e: - logger.error(f"Error updating playlist {playlist_id}: {e}") - return jsonify({"error": "Failed to update playlist"}), 500 - - -@bp.route("/", methods=["DELETE"]) -@require_auth -def delete_playlist(playlist_id): - """Delete a playlist.""" - try: - playlist = Playlist.query.get_or_404(playlist_id) - - if not playlist.is_deletable: - return jsonify({"error": "This playlist cannot be deleted"}), 400 - - # If this is the current playlist, clear it from the player - current_playlist = Playlist.find_current_playlist() - if current_playlist and current_playlist.id == playlist_id: - # Set main playlist as current if it exists - main_playlist = Playlist.find_main_playlist() - if main_playlist: - main_playlist.set_as_current() - music_player_service.reload_current_playlist_if_modified(main_playlist.id) - - playlist.delete() - - return jsonify({"message": "Playlist deleted successfully"}) - except Exception as e: - logger.error(f"Error deleting playlist {playlist_id}: {e}") - return jsonify({"error": "Failed to delete playlist"}), 500 - - -@bp.route("//set-current", methods=["POST"]) -@require_auth -def set_current_playlist(playlist_id): - """Set a playlist as the current one.""" - try: - playlist = Playlist.query.get_or_404(playlist_id) - playlist.set_as_current() - - # Reload the playlist in the music player - music_player_service.reload_current_playlist_if_modified(playlist_id) - - return jsonify({"message": "Playlist set as current"}) - except Exception as e: - logger.error(f"Error setting current playlist {playlist_id}: {e}") - return jsonify({"error": "Failed to set current playlist"}), 500 - - -@bp.route("//sounds", methods=["POST"]) -@require_auth -def add_sound_to_playlist(playlist_id): - """Add a sound to a playlist.""" - try: - playlist = Playlist.query.get_or_404(playlist_id) - data = request.get_json() - sound_id = data.get("sound_id") - order = data.get("order") - - if not sound_id: - return jsonify({"error": "Sound ID is required"}), 400 - - # Verify sound exists - sound = Sound.query.get_or_404(sound_id) - - # Add sound to playlist - playlist_sound = playlist.add_sound(sound_id, order) - - # Reload playlist in music player if it's the current one - music_player_service.reload_current_playlist_if_modified(playlist_id) - - return jsonify({ - "message": "Sound added to playlist", - "playlist_sound": { - "sound_id": playlist_sound.sound_id, - "order": playlist_sound.order - } - }), 201 - except Exception as e: - logger.error(f"Error adding sound to playlist {playlist_id}: {e}") - return jsonify({"error": "Failed to add sound to playlist"}), 500 - - -@bp.route("//sounds/", methods=["DELETE"]) -@require_auth -def remove_sound_from_playlist(playlist_id, sound_id): - """Remove a sound from a playlist.""" - try: - playlist = Playlist.query.get_or_404(playlist_id) - - success = playlist.remove_sound(sound_id) - if not success: - return jsonify({"error": "Sound not found in playlist"}), 404 - - # Reload playlist in music player if it's the current one - music_player_service.reload_current_playlist_if_modified(playlist_id) - - return jsonify({"message": "Sound removed from playlist"}) - except Exception as e: - logger.error(f"Error removing sound from playlist {playlist_id}: {e}") - return jsonify({"error": "Failed to remove sound from playlist"}), 500 - - -@bp.route("//sounds/reorder", methods=["PUT"]) -@require_auth -def reorder_playlist_sounds(playlist_id): - """Reorder sounds in a playlist.""" - try: - playlist = Playlist.query.get_or_404(playlist_id) - data = request.get_json() - sound_orders = data.get("sound_orders", []) - - if not sound_orders: - return jsonify({"error": "Sound orders are required"}), 400 - - # Validate sound_orders format - for item in sound_orders: - if not isinstance(item, dict) or "sound_id" not in item or "order" not in item: - return jsonify({"error": "Invalid sound_orders format"}), 400 - - # Reorder sounds - playlist.reorder_sounds(sound_orders) - - # Reload playlist in music player if it's the current one - music_player_service.reload_current_playlist_if_modified(playlist_id) - - return jsonify({"message": "Playlist sounds reordered"}) - except Exception as e: - logger.error(f"Error reordering playlist sounds {playlist_id}: {e}") - return jsonify({"error": "Failed to reorder playlist sounds"}), 500 - - -@bp.route("//duplicate", methods=["POST"]) -@require_auth -def duplicate_playlist(playlist_id): - """Duplicate a playlist.""" - try: - playlist = Playlist.query.get_or_404(playlist_id) - data = request.get_json() - new_name = data.get("name") - - if not new_name: - return jsonify({"error": "New playlist name is required"}), 400 - - # Check if playlist with same name already exists - existing = Playlist.find_by_name(new_name) - if existing: - return jsonify({"error": "Playlist with this name already exists"}), 400 - - new_playlist = playlist.duplicate(new_name) - - return jsonify({"playlist": new_playlist.to_dict()}), 201 - except Exception as e: - logger.error(f"Error duplicating playlist {playlist_id}: {e}") - return jsonify({"error": "Failed to duplicate playlist"}), 500 \ No newline at end of file diff --git a/app/routes/soundboard.py b/app/routes/soundboard.py index 215664b..2f777a1 100644 --- a/app/routes/soundboard.py +++ b/app/routes/soundboard.py @@ -140,99 +140,3 @@ def get_status(): ) except Exception as e: return jsonify({"error": str(e)}), 500 - - -@bp.route("/history", methods=["GET"]) -@require_auth -def get_play_history(): - """Get recent play history.""" - try: - page = int(request.args.get("page", 1)) - per_page = min(int(request.args.get("per_page", 50)), 100) - offset = (page - 1) * per_page - - recent_plays = SoundPlayed.get_recent_plays( - limit=per_page, - offset=offset, - ) - - return jsonify( - { - "plays": [play.to_dict() for play in recent_plays], - "page": page, - "per_page": per_page, - }, - ) - except Exception as e: - return jsonify({"error": str(e)}), 500 - - -@bp.route("/my-history", methods=["GET"]) -@require_auth -def get_my_play_history(): - """Get current user's play history.""" - try: - user = get_current_user() - if not user: - return jsonify({"error": "User not found"}), 404 - - user_id = int(user["id"]) - page = int(request.args.get("page", 1)) - per_page = min(int(request.args.get("per_page", 50)), 100) - offset = (page - 1) * per_page - - user_plays = SoundPlayed.get_user_plays( - user_id=user_id, - limit=per_page, - offset=offset, - ) - - return jsonify( - { - "plays": [play.to_dict() for play in user_plays], - "page": page, - "per_page": per_page, - "user_id": user_id, - }, - ) - except Exception as e: - return jsonify({"error": str(e)}), 500 - - -@bp.route("/my-stats", methods=["GET"]) -@require_auth -def get_my_stats(): - """Get current user's play statistics.""" - try: - user = get_current_user() - if not user: - return jsonify({"error": "User not found"}), 404 - - user_id = int(user["id"]) - stats = SoundPlayed.get_user_stats(user_id) - - return jsonify(stats) - except Exception as e: - return jsonify({"error": str(e)}), 500 - - -@bp.route("/popular", methods=["GET"]) -@require_auth -def get_popular_sounds(): - """Get most popular sounds.""" - try: - limit = min(int(request.args.get("limit", 10)), 50) - days = request.args.get("days") - days = int(days) if days and days.isdigit() else None - - popular_sounds = SoundPlayed.get_popular_sounds(limit=limit, days=days) - - return jsonify( - { - "popular_sounds": popular_sounds, - "limit": limit, - "days": days, - }, - ) - except Exception as e: - return jsonify({"error": str(e)}), 500 diff --git a/app/routes/stream.py b/app/routes/stream.py index 095c14b..8ccc700 100644 --- a/app/routes/stream.py +++ b/app/routes/stream.py @@ -90,121 +90,3 @@ def add_url(): except Exception as e: db.session.rollback() return jsonify({"error": str(e)}), 500 - - -@bp.route("/", methods=["GET"]) -@require_auth -def list_streams(): - """List all streams with optional filtering.""" - try: - status = request.args.get("status") - service = request.args.get("service") - - query = Stream.query - - if status: - query = query.filter_by(status=status) - if service: - query = query.filter_by(service=service) - - streams = query.order_by(Stream.created_at.desc()).all() - - return ( - jsonify({"streams": [stream.to_dict() for stream in streams]}), - 200, - ) - - except Exception as e: - return jsonify({"error": str(e)}), 500 - - -@bp.route("/", methods=["GET"]) -@require_auth -def get_stream(stream_id): - """Get a specific stream by ID.""" - try: - stream = Stream.query.get_or_404(stream_id) - return jsonify({"stream": stream.to_dict()}), 200 - - except Exception as e: - return jsonify({"error": str(e)}), 500 - - -@bp.route("/", methods=["PUT"]) -@require_auth -def update_stream(stream_id): - """Update stream metadata.""" - try: - stream = Stream.query.get_or_404(stream_id) - data = request.get_json() - - if not data: - return jsonify({"error": "No data provided"}), 400 - - # Update allowed fields - updatable_fields = [ - "title", - "track", - "artist", - "album", - "genre", - "status", - ] - for field in updatable_fields: - if field in data: - setattr(stream, field, data[field]) - - db.session.commit() - - return ( - jsonify( - { - "message": "Stream updated successfully", - "stream": stream.to_dict(), - } - ), - 200, - ) - - except Exception as e: - db.session.rollback() - return jsonify({"error": str(e)}), 500 - - -@bp.route("/", methods=["DELETE"]) -@require_auth -def delete_stream(stream_id): - """Delete a stream.""" - try: - stream = Stream.query.get_or_404(stream_id) - - # If stream is being processed, mark for deletion instead - if stream.status == "processing": - stream.status = "cancelled" - db.session.commit() - return jsonify({"message": "Stream marked for cancellation"}), 200 - - db.session.delete(stream) - db.session.commit() - - return jsonify({"message": "Stream deleted successfully"}), 200 - - except Exception as e: - db.session.rollback() - return jsonify({"error": str(e)}), 500 - - -@bp.route("/queue/status", methods=["GET"]) -@require_auth -def queue_status(): - """Get the current processing queue status.""" - try: - from app.services.stream_processing_service import ( - StreamProcessingService, - ) - - status = StreamProcessingService.get_queue_status() - return jsonify(status), 200 - - except Exception as e: - return jsonify({"error": str(e)}), 500