fleshed out subsonic more
This commit is contained in:
@@ -37,6 +37,7 @@ pub fn library_page() -> Html {
|
|||||||
|
|
||||||
// Measure DOM heights after render and set spacer flex-grow values directly.
|
// Measure DOM heights after render and set spacer flex-grow values directly.
|
||||||
// Must be called before any early returns to maintain consistent hook order.
|
// Must be called before any early returns to maintain consistent hook order.
|
||||||
|
const SCROLL_TRACK_FONT_PX: f64 = 18.0;
|
||||||
{
|
{
|
||||||
let artist_count = artists.as_ref().map(|a| a.len()).unwrap_or(0);
|
let artist_count = artists.as_ref().map(|a| a.len()).unwrap_or(0);
|
||||||
use_effect_with(artist_count, move |_| {
|
use_effect_with(artist_count, move |_| {
|
||||||
@@ -126,7 +127,7 @@ pub fn library_page() -> Html {
|
|||||||
.style()
|
.style()
|
||||||
.set_property("height", &format!("{pct:.2}%"));
|
.set_property("height", &format!("{pct:.2}%"));
|
||||||
// Hide text if cell is too short to fit it
|
// Hide text if cell is too short to fit it
|
||||||
if px < 18.0 {
|
if px < SCROLL_TRACK_FONT_PX {
|
||||||
let _ = el.style().set_property("font-size", "0");
|
let _ = el.style().set_property("font-size", "0");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -462,7 +462,7 @@ tr[draggable="true"]:active { cursor: grabbing; }
|
|||||||
}
|
}
|
||||||
.scroll-track-letter {
|
.scroll-track-letter {
|
||||||
pointer-events: auto;
|
pointer-events: auto;
|
||||||
font-size: 1.2rem;
|
font-size: 18px;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
color: var(--text-secondary);
|
color: var(--text-secondary);
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
|||||||
@@ -40,3 +40,42 @@ pub async fn scrobble(req: HttpRequest, state: web::Data<AppState>) -> HttpRespo
|
|||||||
|
|
||||||
response::ok(¶ms.format, serde_json::json!({}))
|
response::ok(¶ms.format, serde_json::json!({}))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// GET /rest/star[.view] — no-op stub (returns OK without persisting).
|
||||||
|
pub async fn star(req: HttpRequest, state: web::Data<AppState>) -> HttpResponse {
|
||||||
|
let (params, _user) = match authenticate(&req, &state).await {
|
||||||
|
Ok(v) => v,
|
||||||
|
Err(resp) => return resp,
|
||||||
|
};
|
||||||
|
|
||||||
|
response::ok(¶ms.format, serde_json::json!({}))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// GET /rest/unstar[.view] — no-op stub (returns OK without persisting).
|
||||||
|
pub async fn unstar(req: HttpRequest, state: web::Data<AppState>) -> HttpResponse {
|
||||||
|
let (params, _user) = match authenticate(&req, &state).await {
|
||||||
|
Ok(v) => v,
|
||||||
|
Err(resp) => return resp,
|
||||||
|
};
|
||||||
|
|
||||||
|
response::ok(¶ms.format, serde_json::json!({}))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// GET /rest/getStarred2[.view] — returns empty starred lists.
|
||||||
|
pub async fn get_starred2(req: HttpRequest, state: web::Data<AppState>) -> HttpResponse {
|
||||||
|
let (params, _user) = match authenticate(&req, &state).await {
|
||||||
|
Ok(v) => v,
|
||||||
|
Err(resp) => return resp,
|
||||||
|
};
|
||||||
|
|
||||||
|
response::ok(
|
||||||
|
¶ms.format,
|
||||||
|
serde_json::json!({
|
||||||
|
"starred2": {
|
||||||
|
"artist": [],
|
||||||
|
"album": [],
|
||||||
|
"song": [],
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|||||||
@@ -587,3 +587,411 @@ pub async fn get_music_directory(req: HttpRequest, state: web::Data<AppState>) -
|
|||||||
_ => response::error(¶ms.format, response::ERROR_NOT_FOUND, "unknown id type"),
|
_ => response::error(¶ms.format, response::ERROR_NOT_FOUND, "unknown id type"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// GET /rest/getRandomSongs[.view]
|
||||||
|
pub async fn get_random_songs(req: HttpRequest, state: web::Data<AppState>) -> HttpResponse {
|
||||||
|
let (params, _user) = match authenticate(&req, &state).await {
|
||||||
|
Ok(v) => v,
|
||||||
|
Err(resp) => return resp,
|
||||||
|
};
|
||||||
|
|
||||||
|
let size: u64 = get_query_param(&req, "size")
|
||||||
|
.and_then(|v| v.parse().ok())
|
||||||
|
.unwrap_or(10)
|
||||||
|
.min(500);
|
||||||
|
let genre = get_query_param(&req, "genre");
|
||||||
|
let from_year: Option<i32> = get_query_param(&req, "fromYear").and_then(|v| v.parse().ok());
|
||||||
|
let to_year: Option<i32> = get_query_param(&req, "toYear").and_then(|v| v.parse().ok());
|
||||||
|
|
||||||
|
let tracks = queries::tracks::get_random_filtered(
|
||||||
|
state.db.conn(),
|
||||||
|
size,
|
||||||
|
genre.as_deref(),
|
||||||
|
from_year,
|
||||||
|
to_year,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.unwrap_or_default();
|
||||||
|
|
||||||
|
let songs: Vec<serde_json::Value> = tracks
|
||||||
|
.iter()
|
||||||
|
.map(|t| serde_json::to_value(SubsonicChild::from_track(t)).unwrap_or_default())
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
response::ok(
|
||||||
|
¶ms.format,
|
||||||
|
serde_json::json!({
|
||||||
|
"randomSongs": {
|
||||||
|
"song": songs,
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// GET /rest/getSongsByGenre[.view]
|
||||||
|
pub async fn get_songs_by_genre(req: HttpRequest, state: web::Data<AppState>) -> HttpResponse {
|
||||||
|
let (params, _user) = match authenticate(&req, &state).await {
|
||||||
|
Ok(v) => v,
|
||||||
|
Err(resp) => return resp,
|
||||||
|
};
|
||||||
|
|
||||||
|
let genre = match get_query_param(&req, "genre") {
|
||||||
|
Some(g) => g,
|
||||||
|
None => {
|
||||||
|
return response::error(
|
||||||
|
¶ms.format,
|
||||||
|
response::ERROR_MISSING_PARAM,
|
||||||
|
"missing required parameter: genre",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let count: u64 = get_query_param(&req, "count")
|
||||||
|
.and_then(|v| v.parse().ok())
|
||||||
|
.unwrap_or(10)
|
||||||
|
.min(500);
|
||||||
|
let offset: u64 = get_query_param(&req, "offset")
|
||||||
|
.and_then(|v| v.parse().ok())
|
||||||
|
.unwrap_or(0);
|
||||||
|
|
||||||
|
let tracks = queries::tracks::get_by_genre_paginated(state.db.conn(), &genre, count, offset)
|
||||||
|
.await
|
||||||
|
.unwrap_or_default();
|
||||||
|
|
||||||
|
let songs: Vec<serde_json::Value> = tracks
|
||||||
|
.iter()
|
||||||
|
.map(|t| serde_json::to_value(SubsonicChild::from_track(t)).unwrap_or_default())
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
response::ok(
|
||||||
|
¶ms.format,
|
||||||
|
serde_json::json!({
|
||||||
|
"songsByGenre": {
|
||||||
|
"song": songs,
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// GET /rest/getAlbumList2[.view]
|
||||||
|
pub async fn get_album_list2(req: HttpRequest, state: web::Data<AppState>) -> HttpResponse {
|
||||||
|
let (params, _user) = match authenticate(&req, &state).await {
|
||||||
|
Ok(v) => v,
|
||||||
|
Err(resp) => return resp,
|
||||||
|
};
|
||||||
|
|
||||||
|
let list_type = match get_query_param(&req, "type") {
|
||||||
|
Some(t) => t,
|
||||||
|
None => {
|
||||||
|
return response::error(
|
||||||
|
¶ms.format,
|
||||||
|
response::ERROR_MISSING_PARAM,
|
||||||
|
"missing required parameter: type",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let size: u64 = get_query_param(&req, "size")
|
||||||
|
.and_then(|v| v.parse().ok())
|
||||||
|
.unwrap_or(10)
|
||||||
|
.min(500);
|
||||||
|
let offset: u64 = get_query_param(&req, "offset")
|
||||||
|
.and_then(|v| v.parse().ok())
|
||||||
|
.unwrap_or(0);
|
||||||
|
|
||||||
|
let albums = match list_type.as_str() {
|
||||||
|
"alphabeticalByName" => queries::albums::list(state.db.conn(), size, offset)
|
||||||
|
.await
|
||||||
|
.unwrap_or_default(),
|
||||||
|
"alphabeticalByArtist" => {
|
||||||
|
queries::albums::list_alphabetical_by_artist(state.db.conn(), size, offset)
|
||||||
|
.await
|
||||||
|
.unwrap_or_default()
|
||||||
|
}
|
||||||
|
"newest" | "frequent" | "highest" => {
|
||||||
|
queries::albums::list_newest(state.db.conn(), size, offset)
|
||||||
|
.await
|
||||||
|
.unwrap_or_default()
|
||||||
|
}
|
||||||
|
"recent" => queries::albums::list_recent(state.db.conn(), size, offset)
|
||||||
|
.await
|
||||||
|
.unwrap_or_default(),
|
||||||
|
"random" => queries::albums::get_random(state.db.conn(), size)
|
||||||
|
.await
|
||||||
|
.unwrap_or_default(),
|
||||||
|
"starred" => vec![],
|
||||||
|
"byYear" => {
|
||||||
|
let from: i32 = get_query_param(&req, "fromYear")
|
||||||
|
.and_then(|v| v.parse().ok())
|
||||||
|
.unwrap_or(0);
|
||||||
|
let to: i32 = get_query_param(&req, "toYear")
|
||||||
|
.and_then(|v| v.parse().ok())
|
||||||
|
.unwrap_or(9999);
|
||||||
|
queries::albums::list_by_year_range(state.db.conn(), from, to, size, offset)
|
||||||
|
.await
|
||||||
|
.unwrap_or_default()
|
||||||
|
}
|
||||||
|
"byGenre" => {
|
||||||
|
let genre = get_query_param(&req, "genre").unwrap_or_default();
|
||||||
|
if genre.is_empty() {
|
||||||
|
return response::error(
|
||||||
|
¶ms.format,
|
||||||
|
response::ERROR_MISSING_PARAM,
|
||||||
|
"missing required parameter: genre",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
queries::albums::list_by_genre(state.db.conn(), &genre, size, offset)
|
||||||
|
.await
|
||||||
|
.unwrap_or_default()
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
return response::error(
|
||||||
|
¶ms.format,
|
||||||
|
response::ERROR_MISSING_PARAM,
|
||||||
|
&format!("unknown list type: {list_type}"),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut album_list: Vec<serde_json::Value> = Vec::new();
|
||||||
|
for album in &albums {
|
||||||
|
let tracks = queries::tracks::get_by_album(state.db.conn(), album.id)
|
||||||
|
.await
|
||||||
|
.unwrap_or_default();
|
||||||
|
let song_count = tracks.len();
|
||||||
|
let duration: i32 = tracks
|
||||||
|
.iter()
|
||||||
|
.filter_map(|t| t.duration.map(|d| d as i32))
|
||||||
|
.sum();
|
||||||
|
|
||||||
|
let mut album_json = serde_json::json!({
|
||||||
|
"id": format!("al-{}", album.id),
|
||||||
|
"name": album.name,
|
||||||
|
"title": album.name,
|
||||||
|
"artist": album.album_artist,
|
||||||
|
"artistId": album.artist_id.map(|id| format!("ar-{id}")),
|
||||||
|
"coverArt": format!("al-{}", album.id),
|
||||||
|
"songCount": song_count,
|
||||||
|
"duration": duration,
|
||||||
|
"created": "2024-01-01T00:00:00",
|
||||||
|
});
|
||||||
|
if let Some(year) = album.year {
|
||||||
|
album_json["year"] = serde_json::json!(year);
|
||||||
|
}
|
||||||
|
if let Some(ref genre) = album.genre {
|
||||||
|
album_json["genre"] = serde_json::json!(genre);
|
||||||
|
}
|
||||||
|
album_list.push(album_json);
|
||||||
|
}
|
||||||
|
|
||||||
|
response::ok(
|
||||||
|
¶ms.format,
|
||||||
|
serde_json::json!({
|
||||||
|
"albumList2": {
|
||||||
|
"album": album_list,
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// GET /rest/getTopSongs[.view]
|
||||||
|
pub async fn get_top_songs(req: HttpRequest, state: web::Data<AppState>) -> HttpResponse {
|
||||||
|
let (params, _user) = match authenticate(&req, &state).await {
|
||||||
|
Ok(v) => v,
|
||||||
|
Err(resp) => return resp,
|
||||||
|
};
|
||||||
|
|
||||||
|
let artist_name = match get_query_param(&req, "artist") {
|
||||||
|
Some(a) => a,
|
||||||
|
None => {
|
||||||
|
return response::error(
|
||||||
|
¶ms.format,
|
||||||
|
response::ERROR_MISSING_PARAM,
|
||||||
|
"missing required parameter: artist",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let count: usize = get_query_param(&req, "count")
|
||||||
|
.and_then(|v| v.parse().ok())
|
||||||
|
.unwrap_or(50);
|
||||||
|
|
||||||
|
// Find the artist in our DB
|
||||||
|
let artist = match queries::artists::find_by_name(state.db.conn(), &artist_name).await {
|
||||||
|
Ok(Some(a)) => a,
|
||||||
|
_ => {
|
||||||
|
return response::ok(
|
||||||
|
¶ms.format,
|
||||||
|
serde_json::json!({ "topSongs": { "song": [] } }),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Parse the top_songs JSON stored on the artist model
|
||||||
|
let popular: Vec<shanty_data::PopularTrack> =
|
||||||
|
serde_json::from_str(&artist.top_songs).unwrap_or_default();
|
||||||
|
|
||||||
|
// Match top songs to local tracks
|
||||||
|
let artist_tracks = queries::tracks::get_by_artist(state.db.conn(), artist.id)
|
||||||
|
.await
|
||||||
|
.unwrap_or_default();
|
||||||
|
|
||||||
|
let mut songs: Vec<serde_json::Value> = Vec::new();
|
||||||
|
for pt in popular.iter().take(count) {
|
||||||
|
// Try MBID match first, then case-insensitive title match
|
||||||
|
let matched = pt
|
||||||
|
.mbid
|
||||||
|
.as_ref()
|
||||||
|
.and_then(|mbid| {
|
||||||
|
artist_tracks
|
||||||
|
.iter()
|
||||||
|
.find(|t| t.musicbrainz_id.as_deref() == Some(mbid))
|
||||||
|
})
|
||||||
|
.or_else(|| {
|
||||||
|
let name_lower = pt.name.to_lowercase();
|
||||||
|
artist_tracks.iter().find(|t| {
|
||||||
|
t.title
|
||||||
|
.as_deref()
|
||||||
|
.is_some_and(|tt| tt.to_lowercase() == name_lower)
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
if let Some(track) = matched {
|
||||||
|
songs.push(serde_json::to_value(SubsonicChild::from_track(track)).unwrap_or_default());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
response::ok(
|
||||||
|
¶ms.format,
|
||||||
|
serde_json::json!({
|
||||||
|
"topSongs": {
|
||||||
|
"song": songs,
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// GET /rest/getArtistInfo2[.view]
|
||||||
|
pub async fn get_artist_info2(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, artist_id) = match parse_subsonic_id(&id_str) {
|
||||||
|
Some(v) => v,
|
||||||
|
None => {
|
||||||
|
return response::error(
|
||||||
|
¶ms.format,
|
||||||
|
response::ERROR_NOT_FOUND,
|
||||||
|
"invalid artist id",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let artist = match queries::artists::get_by_id(state.db.conn(), artist_id).await {
|
||||||
|
Ok(a) => a,
|
||||||
|
Err(_) => {
|
||||||
|
return response::error(
|
||||||
|
¶ms.format,
|
||||||
|
response::ERROR_NOT_FOUND,
|
||||||
|
"artist not found",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let count: usize = get_query_param(&req, "count")
|
||||||
|
.and_then(|v| v.parse().ok())
|
||||||
|
.unwrap_or(20);
|
||||||
|
let include_not_present =
|
||||||
|
get_query_param(&req, "includeNotPresent").is_some_and(|v| v == "true");
|
||||||
|
|
||||||
|
let mbid = artist.musicbrainz_id.as_deref().unwrap_or("");
|
||||||
|
|
||||||
|
// Pull cached image and bio
|
||||||
|
let config = state.config.read().await;
|
||||||
|
let image_source = config.metadata.artist_image_source.clone();
|
||||||
|
let bio_source = config.metadata.artist_bio_source.clone();
|
||||||
|
drop(config);
|
||||||
|
|
||||||
|
let image_url = if !mbid.is_empty() {
|
||||||
|
let key = format!("artist_image:{image_source}:{mbid}");
|
||||||
|
queries::cache::get(state.db.conn(), &key)
|
||||||
|
.await
|
||||||
|
.ok()
|
||||||
|
.flatten()
|
||||||
|
.filter(|s| !s.is_empty())
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
|
let biography = if !mbid.is_empty() {
|
||||||
|
let key = format!("artist_bio:{bio_source}:{mbid}");
|
||||||
|
queries::cache::get(state.db.conn(), &key)
|
||||||
|
.await
|
||||||
|
.ok()
|
||||||
|
.flatten()
|
||||||
|
.filter(|s| !s.is_empty())
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
|
// Build similar artists list from the model's JSON field
|
||||||
|
let similar: Vec<shanty_data::SimilarArtist> =
|
||||||
|
serde_json::from_str(&artist.similar_artists).unwrap_or_default();
|
||||||
|
|
||||||
|
let mut similar_list: Vec<serde_json::Value> = Vec::new();
|
||||||
|
for sa in similar.iter().take(count) {
|
||||||
|
// Check if similar artist exists locally
|
||||||
|
let local = queries::artists::find_by_name(state.db.conn(), &sa.name)
|
||||||
|
.await
|
||||||
|
.ok()
|
||||||
|
.flatten();
|
||||||
|
|
||||||
|
if !include_not_present && local.is_none() {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut entry = serde_json::json!({
|
||||||
|
"name": sa.name,
|
||||||
|
});
|
||||||
|
if let Some(ref local_artist) = local {
|
||||||
|
entry["id"] = serde_json::json!(format!("ar-{}", local_artist.id));
|
||||||
|
let album_count = queries::albums::get_by_artist(state.db.conn(), local_artist.id)
|
||||||
|
.await
|
||||||
|
.map(|a| a.len())
|
||||||
|
.unwrap_or(0);
|
||||||
|
entry["albumCount"] = serde_json::json!(album_count);
|
||||||
|
}
|
||||||
|
similar_list.push(entry);
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut info = serde_json::json!({
|
||||||
|
"biography": biography.unwrap_or_default(),
|
||||||
|
"musicBrainzId": mbid,
|
||||||
|
"similarArtist": similar_list,
|
||||||
|
});
|
||||||
|
if let Some(ref url) = image_url {
|
||||||
|
info["smallImageUrl"] = serde_json::json!(url);
|
||||||
|
info["mediumImageUrl"] = serde_json::json!(url);
|
||||||
|
info["largeImageUrl"] = serde_json::json!(url);
|
||||||
|
}
|
||||||
|
|
||||||
|
response::ok(
|
||||||
|
¶ms.format,
|
||||||
|
serde_json::json!({
|
||||||
|
"artistInfo2": info,
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|||||||
@@ -48,6 +48,31 @@ pub fn configure(cfg: &mut web::ServiceConfig) {
|
|||||||
.route("/getSong.view", web::get().to(browsing::get_song))
|
.route("/getSong.view", web::get().to(browsing::get_song))
|
||||||
.route("/getGenres", web::get().to(browsing::get_genres))
|
.route("/getGenres", web::get().to(browsing::get_genres))
|
||||||
.route("/getGenres.view", web::get().to(browsing::get_genres))
|
.route("/getGenres.view", web::get().to(browsing::get_genres))
|
||||||
|
.route("/getRandomSongs", web::get().to(browsing::get_random_songs))
|
||||||
|
.route(
|
||||||
|
"/getRandomSongs.view",
|
||||||
|
web::get().to(browsing::get_random_songs),
|
||||||
|
)
|
||||||
|
.route(
|
||||||
|
"/getSongsByGenre",
|
||||||
|
web::get().to(browsing::get_songs_by_genre),
|
||||||
|
)
|
||||||
|
.route(
|
||||||
|
"/getSongsByGenre.view",
|
||||||
|
web::get().to(browsing::get_songs_by_genre),
|
||||||
|
)
|
||||||
|
.route("/getAlbumList2", web::get().to(browsing::get_album_list2))
|
||||||
|
.route(
|
||||||
|
"/getAlbumList2.view",
|
||||||
|
web::get().to(browsing::get_album_list2),
|
||||||
|
)
|
||||||
|
.route("/getTopSongs", web::get().to(browsing::get_top_songs))
|
||||||
|
.route("/getTopSongs.view", web::get().to(browsing::get_top_songs))
|
||||||
|
.route("/getArtistInfo2", web::get().to(browsing::get_artist_info2))
|
||||||
|
.route(
|
||||||
|
"/getArtistInfo2.view",
|
||||||
|
web::get().to(browsing::get_artist_info2),
|
||||||
|
)
|
||||||
// Search
|
// Search
|
||||||
.route("/search3", web::get().to(search::search3))
|
.route("/search3", web::get().to(search::search3))
|
||||||
.route("/search3.view", web::get().to(search::search3))
|
.route("/search3.view", web::get().to(search::search3))
|
||||||
@@ -79,6 +104,12 @@ pub fn configure(cfg: &mut web::ServiceConfig) {
|
|||||||
// Annotation
|
// Annotation
|
||||||
.route("/scrobble", web::get().to(annotation::scrobble))
|
.route("/scrobble", web::get().to(annotation::scrobble))
|
||||||
.route("/scrobble.view", web::get().to(annotation::scrobble))
|
.route("/scrobble.view", web::get().to(annotation::scrobble))
|
||||||
|
.route("/star", web::get().to(annotation::star))
|
||||||
|
.route("/star.view", web::get().to(annotation::star))
|
||||||
|
.route("/unstar", web::get().to(annotation::unstar))
|
||||||
|
.route("/unstar.view", web::get().to(annotation::unstar))
|
||||||
|
.route("/getStarred2", web::get().to(annotation::get_starred2))
|
||||||
|
.route("/getStarred2.view", web::get().to(annotation::get_starred2))
|
||||||
// User
|
// User
|
||||||
.route("/getUser", web::get().to(user::get_user))
|
.route("/getUser", web::get().to(user::get_user))
|
||||||
.route("/getUser.view", web::get().to(user::get_user)),
|
.route("/getUser.view", web::get().to(user::get_user)),
|
||||||
|
|||||||
Reference in New Issue
Block a user