251 lines
7.7 KiB
Rust
251 lines
7.7 KiB
Rust
use actix_web::{HttpRequest, HttpResponse, web};
|
|
|
|
use shanty_db::queries;
|
|
|
|
use crate::state::AppState;
|
|
|
|
use super::helpers::{authenticate, get_query_param, parse_subsonic_id};
|
|
use super::response::{self, SubsonicChild};
|
|
|
|
/// GET /rest/getPlaylists[.view]
|
|
pub async fn get_playlists(req: HttpRequest, state: web::Data<AppState>) -> HttpResponse {
|
|
let (params, user) = match authenticate(&req, &state).await {
|
|
Ok(v) => v,
|
|
Err(resp) => return resp,
|
|
};
|
|
|
|
let playlists = queries::playlists::list(state.db.conn(), Some(user.id))
|
|
.await
|
|
.unwrap_or_default();
|
|
|
|
let mut playlist_list: Vec<serde_json::Value> = Vec::new();
|
|
for pl in &playlists {
|
|
let track_count = queries::playlists::get_track_count(state.db.conn(), pl.id)
|
|
.await
|
|
.unwrap_or(0);
|
|
|
|
// Calculate total duration
|
|
let tracks = queries::playlists::get_tracks(state.db.conn(), pl.id)
|
|
.await
|
|
.unwrap_or_default();
|
|
let duration: i32 = tracks
|
|
.iter()
|
|
.filter_map(|t| t.duration.map(|d| d as i32))
|
|
.sum();
|
|
|
|
let mut pl_json = serde_json::json!({
|
|
"id": format!("pl-{}", pl.id),
|
|
"name": pl.name,
|
|
"owner": user.username,
|
|
"public": false,
|
|
"songCount": track_count,
|
|
"duration": duration,
|
|
"created": pl.created_at.format("%Y-%m-%dT%H:%M:%S").to_string(),
|
|
"changed": pl.updated_at.format("%Y-%m-%dT%H:%M:%S").to_string(),
|
|
});
|
|
if let Some(ref desc) = pl.description {
|
|
pl_json["comment"] = serde_json::json!(desc);
|
|
}
|
|
playlist_list.push(pl_json);
|
|
}
|
|
|
|
response::ok(
|
|
¶ms.format,
|
|
serde_json::json!({
|
|
"playlists": {
|
|
"playlist": playlist_list,
|
|
}
|
|
}),
|
|
)
|
|
}
|
|
|
|
/// GET /rest/getPlaylist[.view]
|
|
pub async fn get_playlist(req: HttpRequest, state: web::Data<AppState>) -> HttpResponse {
|
|
let (params, user) = match authenticate(&req, &state).await {
|
|
Ok(v) => v,
|
|
Err(resp) => return resp,
|
|
};
|
|
|
|
let id_str = match get_query_param(&req, "id") {
|
|
Some(id) => id,
|
|
None => {
|
|
return response::error(
|
|
¶ms.format,
|
|
response::ERROR_MISSING_PARAM,
|
|
"missing required parameter: id",
|
|
);
|
|
}
|
|
};
|
|
|
|
let (_prefix, playlist_id) = match parse_subsonic_id(&id_str) {
|
|
Some(v) => v,
|
|
None => {
|
|
return response::error(
|
|
¶ms.format,
|
|
response::ERROR_NOT_FOUND,
|
|
"invalid playlist id",
|
|
);
|
|
}
|
|
};
|
|
|
|
let playlist = match queries::playlists::get_by_id(state.db.conn(), playlist_id).await {
|
|
Ok(p) => p,
|
|
Err(_) => {
|
|
return response::error(
|
|
¶ms.format,
|
|
response::ERROR_NOT_FOUND,
|
|
"playlist not found",
|
|
);
|
|
}
|
|
};
|
|
|
|
let tracks = queries::playlists::get_tracks(state.db.conn(), playlist_id)
|
|
.await
|
|
.unwrap_or_default();
|
|
|
|
let duration: i32 = tracks
|
|
.iter()
|
|
.filter_map(|t| t.duration.map(|d| d as i32))
|
|
.sum();
|
|
|
|
let entry_list: Vec<serde_json::Value> = tracks
|
|
.iter()
|
|
.map(|track| serde_json::to_value(SubsonicChild::from_track(track)).unwrap_or_default())
|
|
.collect();
|
|
|
|
let mut pl_json = serde_json::json!({
|
|
"id": format!("pl-{}", playlist.id),
|
|
"name": playlist.name,
|
|
"owner": user.username,
|
|
"public": false,
|
|
"songCount": tracks.len(),
|
|
"duration": duration,
|
|
"created": playlist.created_at.format("%Y-%m-%dT%H:%M:%S").to_string(),
|
|
"changed": playlist.updated_at.format("%Y-%m-%dT%H:%M:%S").to_string(),
|
|
"entry": entry_list,
|
|
});
|
|
if let Some(ref desc) = playlist.description {
|
|
pl_json["comment"] = serde_json::json!(desc);
|
|
}
|
|
|
|
response::ok(
|
|
¶ms.format,
|
|
serde_json::json!({
|
|
"playlist": pl_json,
|
|
}),
|
|
)
|
|
}
|
|
|
|
/// GET /rest/createPlaylist[.view]
|
|
pub async fn create_playlist(req: HttpRequest, state: web::Data<AppState>) -> HttpResponse {
|
|
let (params, user) = match authenticate(&req, &state).await {
|
|
Ok(v) => v,
|
|
Err(resp) => return resp,
|
|
};
|
|
|
|
let name = match get_query_param(&req, "name") {
|
|
Some(n) => n,
|
|
None => {
|
|
return response::error(
|
|
¶ms.format,
|
|
response::ERROR_MISSING_PARAM,
|
|
"missing required parameter: name",
|
|
);
|
|
}
|
|
};
|
|
|
|
// Collect songId params (can be repeated)
|
|
let qs = req.query_string();
|
|
let query_params: Vec<(String, String)> = serde_urlencoded::from_str(qs).unwrap_or_default();
|
|
let track_ids: Vec<i32> = query_params
|
|
.iter()
|
|
.filter(|(k, _)| k == "songId")
|
|
.filter_map(|(_, v)| parse_subsonic_id(v).map(|(_, id)| id))
|
|
.collect();
|
|
|
|
match queries::playlists::create(state.db.conn(), &name, None, Some(user.id), &track_ids).await
|
|
{
|
|
Ok(playlist) => {
|
|
let tracks = queries::playlists::get_tracks(state.db.conn(), playlist.id)
|
|
.await
|
|
.unwrap_or_default();
|
|
let duration: i32 = tracks
|
|
.iter()
|
|
.filter_map(|t| t.duration.map(|d| d as i32))
|
|
.sum();
|
|
let entry_list: Vec<serde_json::Value> = tracks
|
|
.iter()
|
|
.map(|track| {
|
|
serde_json::to_value(SubsonicChild::from_track(track)).unwrap_or_default()
|
|
})
|
|
.collect();
|
|
|
|
let mut pl_json = serde_json::json!({
|
|
"id": format!("pl-{}", playlist.id),
|
|
"name": playlist.name,
|
|
"owner": user.username,
|
|
"public": false,
|
|
"songCount": tracks.len(),
|
|
"duration": duration,
|
|
"created": playlist.created_at.format("%Y-%m-%dT%H:%M:%S").to_string(),
|
|
"changed": playlist.updated_at.format("%Y-%m-%dT%H:%M:%S").to_string(),
|
|
"entry": entry_list,
|
|
});
|
|
if let Some(ref desc) = playlist.description {
|
|
pl_json["comment"] = serde_json::json!(desc);
|
|
}
|
|
|
|
response::ok(
|
|
¶ms.format,
|
|
serde_json::json!({
|
|
"playlist": pl_json,
|
|
}),
|
|
)
|
|
}
|
|
Err(e) => response::error(
|
|
¶ms.format,
|
|
response::ERROR_GENERIC,
|
|
&format!("failed to create playlist: {e}"),
|
|
),
|
|
}
|
|
}
|
|
|
|
/// GET /rest/deletePlaylist[.view]
|
|
pub async fn delete_playlist(req: HttpRequest, state: web::Data<AppState>) -> HttpResponse {
|
|
let (params, _user) = match authenticate(&req, &state).await {
|
|
Ok(v) => v,
|
|
Err(resp) => return resp,
|
|
};
|
|
|
|
let id_str = match get_query_param(&req, "id") {
|
|
Some(id) => id,
|
|
None => {
|
|
return response::error(
|
|
¶ms.format,
|
|
response::ERROR_MISSING_PARAM,
|
|
"missing required parameter: id",
|
|
);
|
|
}
|
|
};
|
|
|
|
let (_prefix, playlist_id) = match parse_subsonic_id(&id_str) {
|
|
Some(v) => v,
|
|
None => {
|
|
return response::error(
|
|
¶ms.format,
|
|
response::ERROR_NOT_FOUND,
|
|
"invalid playlist id",
|
|
);
|
|
}
|
|
};
|
|
|
|
match queries::playlists::delete(state.db.conn(), playlist_id).await {
|
|
Ok(()) => response::ok(¶ms.format, serde_json::json!({})),
|
|
Err(e) => response::error(
|
|
¶ms.format,
|
|
response::ERROR_GENERIC,
|
|
&format!("failed to delete playlist: {e}"),
|
|
),
|
|
}
|
|
}
|