use std::sync::Arc; use axum::{ Json, extract::{Path, Query, State}, http::StatusCode, response::IntoResponse, }; use serde::Deserialize; use crate::application::dtos::playlist_dto::{ AddTracksDto, CreatePlaylistDto, PlaylistQueryDto, ReorderTracksDto, SharePlaylistDto, UpdatePlaylistDto, }; use crate::application::ports::music_ports::MusicUseCase; use crate::application::services::music_service::MusicService; use crate::interfaces::errors::AppError; use crate::interfaces::middleware::auth::AuthUser; #[derive(Debug, Deserialize)] pub struct PaginationQuery { pub limit: Option, pub offset: Option, } #[utoipa::path( post, path = "/api/playlists", responses( (status = 201, description = "Playlist created"), (status = 400, description = "Bad request"), (status = 401, description = "Unauthorized") ), security(("bearerAuth" = [])), tag = "playlists" )] pub async fn create_playlist( State(music_service): State>, auth_user: AuthUser, Json(dto): Json, ) -> impl IntoResponse { match music_service.create_playlist(dto, auth_user.id).await { Ok(playlist) => (StatusCode::CREATED, Json(playlist)).into_response(), Err(err) => AppError::from(err).into_response(), } } #[utoipa::path( get, path = "/api/playlists/{playlist_id}", params(("playlist_id" = String, Path, description = "Playlist ID")), responses( (status = 200, description = "Playlist details"), (status = 401, description = "Unauthorized"), (status = 404, description = "Playlist not found") ), security(("bearerAuth" = [])), tag = "playlists" )] pub async fn get_playlist( State(music_service): State>, auth_user: AuthUser, Path(playlist_id): Path, ) -> impl IntoResponse { match music_service.get_playlist(&playlist_id, auth_user.id).await { Ok(playlist) => (StatusCode::OK, Json(playlist)).into_response(), Err(err) => AppError::from(err).into_response(), } } #[utoipa::path( get, path = "/api/playlists", responses( (status = 200, description = "List of playlists"), (status = 401, description = "Unauthorized") ), security(("bearerAuth" = [])), tag = "playlists" )] pub async fn list_playlists( State(music_service): State>, auth_user: AuthUser, Query(query): Query, ) -> impl IntoResponse { match music_service.list_playlists(query, auth_user.id).await { Ok(playlists) => (StatusCode::OK, Json(playlists)).into_response(), Err(err) => AppError::from(err).into_response(), } } #[derive(Debug, Deserialize)] pub struct IncludeSharedQuery { pub include_shared: Option, pub include_public: Option, } #[utoipa::path( put, path = "/api/playlists/{playlist_id}", params(("playlist_id" = String, Path, description = "Playlist ID")), responses( (status = 200, description = "Playlist updated"), (status = 401, description = "Unauthorized"), (status = 404, description = "Playlist not found") ), security(("bearerAuth" = [])), tag = "playlists" )] pub async fn update_playlist( State(music_service): State>, auth_user: AuthUser, Path(playlist_id): Path, Json(dto): Json, ) -> impl IntoResponse { match music_service .update_playlist(&playlist_id, dto, auth_user.id) .await { Ok(playlist) => (StatusCode::OK, Json(playlist)).into_response(), Err(err) => AppError::from(err).into_response(), } } #[utoipa::path( delete, path = "/api/playlists/{playlist_id}", params(("playlist_id" = String, Path, description = "Playlist ID")), responses( (status = 204, description = "Playlist deleted"), (status = 401, description = "Unauthorized"), (status = 404, description = "Playlist not found") ), security(("bearerAuth" = [])), tag = "playlists" )] pub async fn delete_playlist( State(music_service): State>, auth_user: AuthUser, Path(playlist_id): Path, ) -> impl IntoResponse { match music_service .delete_playlist(&playlist_id, auth_user.id) .await { Ok(()) => StatusCode::NO_CONTENT.into_response(), Err(err) => AppError::from(err).into_response(), } } #[utoipa::path( post, path = "/api/playlists/{playlist_id}/tracks", params(("playlist_id" = String, Path, description = "Playlist ID")), responses( (status = 201, description = "Tracks added"), (status = 401, description = "Unauthorized"), (status = 404, description = "Playlist not found") ), security(("bearerAuth" = [])), tag = "playlists" )] pub async fn add_tracks( State(music_service): State>, auth_user: AuthUser, Path(playlist_id): Path, Json(dto): Json, ) -> impl IntoResponse { match music_service .add_tracks(&playlist_id, dto, auth_user.id) .await { Ok(tracks) => (StatusCode::CREATED, Json(tracks)).into_response(), Err(err) => AppError::from(err).into_response(), } } #[utoipa::path( delete, path = "/api/playlists/{playlist_id}/tracks/{file_id}", params( ("playlist_id" = String, Path, description = "Playlist ID"), ("file_id" = String, Path, description = "File ID to remove") ), responses( (status = 204, description = "Track removed"), (status = 401, description = "Unauthorized"), (status = 404, description = "Playlist or track not found") ), security(("bearerAuth" = [])), tag = "playlists" )] pub async fn remove_track( State(music_service): State>, auth_user: AuthUser, Path((playlist_id, file_id)): Path<(String, String)>, ) -> impl IntoResponse { match music_service .remove_track(&playlist_id, &file_id, auth_user.id) .await { Ok(()) => StatusCode::NO_CONTENT.into_response(), Err(err) => AppError::from(err).into_response(), } } #[utoipa::path( put, path = "/api/playlists/{playlist_id}/reorder", params(("playlist_id" = String, Path, description = "Playlist ID")), responses( (status = 204, description = "Tracks reordered"), (status = 401, description = "Unauthorized"), (status = 404, description = "Playlist not found") ), security(("bearerAuth" = [])), tag = "playlists" )] pub async fn reorder_tracks( State(music_service): State>, auth_user: AuthUser, Path(playlist_id): Path, Json(dto): Json, ) -> impl IntoResponse { match music_service .reorder_tracks(&playlist_id, dto, auth_user.id) .await { Ok(()) => StatusCode::NO_CONTENT.into_response(), Err(err) => AppError::from(err).into_response(), } } #[utoipa::path( get, path = "/api/playlists/{playlist_id}/tracks", params(("playlist_id" = String, Path, description = "Playlist ID")), responses( (status = 200, description = "List of playlist tracks"), (status = 401, description = "Unauthorized"), (status = 404, description = "Playlist not found") ), security(("bearerAuth" = [])), tag = "playlists" )] pub async fn list_playlist_tracks( State(music_service): State>, auth_user: AuthUser, Path(playlist_id): Path, ) -> impl IntoResponse { match music_service .list_playlist_tracks(&playlist_id, auth_user.id) .await { Ok(tracks) => (StatusCode::OK, Json(tracks)).into_response(), Err(err) => AppError::from(err).into_response(), } } #[utoipa::path( post, path = "/api/playlists/{playlist_id}/share", params(("playlist_id" = String, Path, description = "Playlist ID")), responses( (status = 204, description = "Playlist shared"), (status = 401, description = "Unauthorized"), (status = 404, description = "Playlist not found") ), security(("bearerAuth" = [])), tag = "playlists" )] pub async fn share_playlist( State(music_service): State>, auth_user: AuthUser, Path(playlist_id): Path, Json(dto): Json, ) -> impl IntoResponse { match music_service .share_playlist(&playlist_id, dto, auth_user.id) .await { Ok(()) => StatusCode::NO_CONTENT.into_response(), Err(err) => AppError::from(err).into_response(), } } #[utoipa::path( delete, path = "/api/playlists/{playlist_id}/share/{user_id}", params( ("playlist_id" = String, Path, description = "Playlist ID"), ("user_id" = String, Path, description = "User ID to remove share") ), responses( (status = 204, description = "Share removed"), (status = 401, description = "Unauthorized"), (status = 404, description = "Playlist or share not found") ), security(("bearerAuth" = [])), tag = "playlists" )] pub async fn remove_share( State(music_service): State>, auth_user: AuthUser, Path((playlist_id, user_id)): Path<(String, String)>, ) -> impl IntoResponse { match music_service .remove_share(&playlist_id, &user_id, auth_user.id) .await { Ok(()) => StatusCode::NO_CONTENT.into_response(), Err(err) => AppError::from(err).into_response(), } } #[utoipa::path( get, path = "/api/playlists/{playlist_id}/shares", params(("playlist_id" = String, Path, description = "Playlist ID")), responses( (status = 200, description = "List of playlist shares"), (status = 401, description = "Unauthorized"), (status = 404, description = "Playlist not found") ), security(("bearerAuth" = [])), tag = "playlists" )] pub async fn get_playlist_shares( State(music_service): State>, auth_user: AuthUser, Path(playlist_id): Path, ) -> impl IntoResponse { match music_service .get_playlist_shares(&playlist_id, auth_user.id) .await { Ok(shares) => (StatusCode::OK, Json(shares)).into_response(), Err(err) => AppError::from(err).into_response(), } } #[utoipa::path( get, path = "/api/playlists/audio-metadata/{file_id}", params(("file_id" = String, Path, description = "Audio file ID")), responses( (status = 200, description = "Audio metadata"), (status = 401, description = "Unauthorized"), (status = 404, description = "File not found") ), security(("bearerAuth" = [])), tag = "playlists" )] pub async fn get_audio_metadata( State(music_service): State>, auth_user: AuthUser, Path(file_id): Path, ) -> impl IntoResponse { match music_service .get_audio_metadata(&file_id, auth_user.id) .await { Ok(metadata) => (StatusCode::OK, Json(metadata)).into_response(), Err(err) => AppError::from(err).into_response(), } }