Add playlist building daemon
This creates playlists when the name would change
This commit is contained in:
parent
996504373e
commit
2bd75c1c06
@ -2,5 +2,6 @@
|
|||||||
<project version="4">
|
<project version="4">
|
||||||
<component name="SqlDialectMappings">
|
<component name="SqlDialectMappings">
|
||||||
<file url="file://$PROJECT_DIR$/migrations/20230816135241_initial.sql" dialect="PostgreSQL" />
|
<file url="file://$PROJECT_DIR$/migrations/20230816135241_initial.sql" dialect="PostgreSQL" />
|
||||||
|
<file url="file://$PROJECT_DIR$/migrations/20240418195028_meta_tracker.sql" dialect="PostgreSQL" />
|
||||||
</component>
|
</component>
|
||||||
</project>
|
</project>
|
@ -13,10 +13,14 @@
|
|||||||
</component>
|
</component>
|
||||||
<component name="ChangeListManager">
|
<component name="ChangeListManager">
|
||||||
<list default="true" id="52900e09-9584-4b6c-95ff-fbd4ed5d8b2c" name="Changes" comment="Add interface package. Start adding auth stuff for refreshing tokens.">
|
<list default="true" id="52900e09-9584-4b6c-95ff-fbd4ed5d8b2c" name="Changes" comment="Add interface package. Start adding auth stuff for refreshing tokens.">
|
||||||
|
<change afterPath="$PROJECT_DIR$/src/daemon/create_playlists.rs" afterDir="false" />
|
||||||
|
<change beforePath="$PROJECT_DIR$/.idea/sqldialects.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/sqldialects.xml" afterDir="false" />
|
||||||
<change beforePath="$PROJECT_DIR$/.idea/workspace.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/workspace.xml" afterDir="false" />
|
<change beforePath="$PROJECT_DIR$/.idea/workspace.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/workspace.xml" afterDir="false" />
|
||||||
<change beforePath="$PROJECT_DIR$/Cargo.lock" beforeDir="false" afterPath="$PROJECT_DIR$/Cargo.lock" afterDir="false" />
|
<change beforePath="$PROJECT_DIR$/Cargo.lock" beforeDir="false" afterPath="$PROJECT_DIR$/Cargo.lock" afterDir="false" />
|
||||||
<change beforePath="$PROJECT_DIR$/Cargo.toml" beforeDir="false" afterPath="$PROJECT_DIR$/Cargo.toml" afterDir="false" />
|
<change beforePath="$PROJECT_DIR$/Cargo.toml" beforeDir="false" afterPath="$PROJECT_DIR$/Cargo.toml" afterDir="false" />
|
||||||
|
<change beforePath="$PROJECT_DIR$/src/daemon/mod.rs" beforeDir="false" afterPath="$PROJECT_DIR$/src/daemon/mod.rs" afterDir="false" />
|
||||||
<change beforePath="$PROJECT_DIR$/src/daemon/update_playlists.rs" beforeDir="false" afterPath="$PROJECT_DIR$/src/daemon/update_playlists.rs" afterDir="false" />
|
<change beforePath="$PROJECT_DIR$/src/daemon/update_playlists.rs" beforeDir="false" afterPath="$PROJECT_DIR$/src/daemon/update_playlists.rs" afterDir="false" />
|
||||||
|
<change beforePath="$PROJECT_DIR$/src/listenbrainz.rs" beforeDir="false" afterPath="$PROJECT_DIR$/src/listenbrainz.rs" afterDir="false" />
|
||||||
<change beforePath="$PROJECT_DIR$/src/main.rs" beforeDir="false" afterPath="$PROJECT_DIR$/src/main.rs" afterDir="false" />
|
<change beforePath="$PROJECT_DIR$/src/main.rs" beforeDir="false" afterPath="$PROJECT_DIR$/src/main.rs" afterDir="false" />
|
||||||
<change beforePath="$PROJECT_DIR$/src/models.rs" beforeDir="false" afterPath="$PROJECT_DIR$/src/models.rs" afterDir="false" />
|
<change beforePath="$PROJECT_DIR$/src/models.rs" beforeDir="false" afterPath="$PROJECT_DIR$/src/models.rs" afterDir="false" />
|
||||||
<change beforePath="$PROJECT_DIR$/src/subsonic.rs" beforeDir="false" afterPath="$PROJECT_DIR$/src/subsonic.rs" afterDir="false" />
|
<change beforePath="$PROJECT_DIR$/src/subsonic.rs" beforeDir="false" afterPath="$PROJECT_DIR$/src/subsonic.rs" afterDir="false" />
|
||||||
@ -161,7 +165,8 @@
|
|||||||
<workItem from="1713283935337" duration="13390000" />
|
<workItem from="1713283935337" duration="13390000" />
|
||||||
<workItem from="1713372315382" duration="4924000" />
|
<workItem from="1713372315382" duration="4924000" />
|
||||||
<workItem from="1713385358427" duration="2705000" />
|
<workItem from="1713385358427" duration="2705000" />
|
||||||
<workItem from="1713465993328" duration="1489000" />
|
<workItem from="1713465993328" duration="1750000" />
|
||||||
|
<workItem from="1713469457285" duration="6504000" />
|
||||||
</task>
|
</task>
|
||||||
<task id="LOCAL-00001" summary="Structure">
|
<task id="LOCAL-00001" summary="Structure">
|
||||||
<created>1692008860369</created>
|
<created>1692008860369</created>
|
||||||
|
62
Cargo.lock
generated
62
Cargo.lock
generated
@ -45,6 +45,21 @@ version = "0.2.18"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "5c6cb57a04249c6480766f7f7cef5467412af1490f8d1e243141daddada3264f"
|
checksum = "5c6cb57a04249c6480766f7f7cef5467412af1490f8d1e243141daddada3264f"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "android-tzdata"
|
||||||
|
version = "0.1.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "android_system_properties"
|
||||||
|
version = "0.1.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "async-stream"
|
name = "async-stream"
|
||||||
version = "0.3.5"
|
version = "0.3.5"
|
||||||
@ -201,6 +216,20 @@ version = "1.0.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "chrono"
|
||||||
|
version = "0.4.38"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a21f936df1771bf62b77f047b726c4625ff2e8aa607c01ec06e5a05bd8463401"
|
||||||
|
dependencies = [
|
||||||
|
"android-tzdata",
|
||||||
|
"iana-time-zone",
|
||||||
|
"js-sys",
|
||||||
|
"num-traits",
|
||||||
|
"wasm-bindgen",
|
||||||
|
"windows-targets 0.52.5",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "config"
|
name = "config"
|
||||||
version = "0.14.0"
|
version = "0.14.0"
|
||||||
@ -832,6 +861,29 @@ dependencies = [
|
|||||||
"tokio-native-tls",
|
"tokio-native-tls",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "iana-time-zone"
|
||||||
|
version = "0.1.60"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e7ffbb5a1b541ea2561f8c41c087286cc091e21e556a4f09a8f6cbf17b69b141"
|
||||||
|
dependencies = [
|
||||||
|
"android_system_properties",
|
||||||
|
"core-foundation-sys",
|
||||||
|
"iana-time-zone-haiku",
|
||||||
|
"js-sys",
|
||||||
|
"wasm-bindgen",
|
||||||
|
"windows-core",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "iana-time-zone-haiku"
|
||||||
|
version = "0.1.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f"
|
||||||
|
dependencies = [
|
||||||
|
"cc",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "idna"
|
name = "idna"
|
||||||
version = "0.5.0"
|
version = "0.5.0"
|
||||||
@ -1406,6 +1458,7 @@ checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec"
|
|||||||
name = "playlistd"
|
name = "playlistd"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"chrono",
|
||||||
"config",
|
"config",
|
||||||
"getrandom",
|
"getrandom",
|
||||||
"log",
|
"log",
|
||||||
@ -2781,6 +2834,15 @@ dependencies = [
|
|||||||
"windows-targets 0.48.5",
|
"windows-targets 0.48.5",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows-core"
|
||||||
|
version = "0.52.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9"
|
||||||
|
dependencies = [
|
||||||
|
"windows-targets 0.52.5",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows-sys"
|
name = "windows-sys"
|
||||||
version = "0.48.0"
|
version = "0.48.0"
|
||||||
|
@ -18,6 +18,7 @@ getrandom = "0.2.12"
|
|||||||
thiserror = "1.0.58"
|
thiserror = "1.0.58"
|
||||||
config = "0.14.0"
|
config = "0.14.0"
|
||||||
log = "0.4.21"
|
log = "0.4.21"
|
||||||
|
chrono = "0.4.38"
|
||||||
|
|
||||||
[package.metadata.deb]
|
[package.metadata.deb]
|
||||||
depends = "$auto"
|
depends = "$auto"
|
||||||
|
9
migrations/20240418195028_meta_tracker.sql
Normal file
9
migrations/20240418195028_meta_tracker.sql
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
CREATE TABLE "create_tracked_playlist"
|
||||||
|
(
|
||||||
|
id UUID NOT NULL PRIMARY KEY, -- Internal ID
|
||||||
|
name_template VARCHAR(250) NOT NULL, -- Name of the playlist in subsonic
|
||||||
|
last_name VARCHAR(250), -- Name of last playlist created by this rule
|
||||||
|
playlist_size INT NOT NULL, -- Max number of tracks to populate
|
||||||
|
tracking_user VARCHAR(250) NOT NULL, -- User to track stats for
|
||||||
|
tracking_type VARCHAR(14) NOT NULL -- Matches time period params from listenbrainz
|
||||||
|
);
|
100
src/daemon/create_playlists.rs
Normal file
100
src/daemon/create_playlists.rs
Normal file
@ -0,0 +1,100 @@
|
|||||||
|
use crate::models::CreateTrackedPlaylist;
|
||||||
|
use crate::subsonic::Subsonic;
|
||||||
|
use crate::CONFIG;
|
||||||
|
use chrono::Local;
|
||||||
|
use log::error;
|
||||||
|
use sqlx::postgres::PgPool;
|
||||||
|
use std::str::FromStr;
|
||||||
|
use std::time::Duration;
|
||||||
|
use thiserror::Error;
|
||||||
|
use tokio::time::{interval, MissedTickBehavior};
|
||||||
|
use uuid::Uuid;
|
||||||
|
|
||||||
|
pub async fn create_playlists_daemon(
|
||||||
|
media_client: impl AsRef<Subsonic> + Clone,
|
||||||
|
) -> Result<(), Box<dyn std::error::Error>> {
|
||||||
|
println!("Create playlist daemon starting...");
|
||||||
|
|
||||||
|
let database = PgPool::connect(
|
||||||
|
CONFIG
|
||||||
|
.get()
|
||||||
|
.unwrap()
|
||||||
|
.get::<String>("database.url")?
|
||||||
|
.as_str(),
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let mut ticker = interval(Duration::from_secs(
|
||||||
|
CONFIG.get().unwrap().get::<u64>("daemon.interval")?,
|
||||||
|
));
|
||||||
|
ticker.set_missed_tick_behavior(MissedTickBehavior::Skip);
|
||||||
|
|
||||||
|
loop {
|
||||||
|
let records_res = sqlx::query_as!(
|
||||||
|
CreateTrackedPlaylist,
|
||||||
|
r#"SELECT * FROM "create_tracked_playlist""#
|
||||||
|
)
|
||||||
|
.fetch_all(&database)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
match records_res {
|
||||||
|
Ok(records) => {
|
||||||
|
for record in records {
|
||||||
|
// todo handle me
|
||||||
|
create_playlist(media_client.clone(), record, database.clone())
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Err(e) => {
|
||||||
|
println!("Could not fetch create_tracked_playlist: {:?}", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ticker.tick().await;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn create_playlist(
|
||||||
|
media_client: impl AsRef<Subsonic>,
|
||||||
|
playlist_rule: CreateTrackedPlaylist,
|
||||||
|
database: PgPool,
|
||||||
|
) -> Result<(), Box<dyn std::error::Error>> {
|
||||||
|
let now = Local::now();
|
||||||
|
let new_name = now.format(&playlist_rule.name_template).to_string();
|
||||||
|
|
||||||
|
if Some(&new_name) != playlist_rule.last_name.as_ref() {
|
||||||
|
// Create playlist
|
||||||
|
let playlist = media_client.as_ref().create_playlist(&new_name).await?;
|
||||||
|
let uuid = Uuid::new_v4();
|
||||||
|
let playlist_uuid = Uuid::from_str(&playlist.id)?;
|
||||||
|
|
||||||
|
sqlx::query!(
|
||||||
|
r#"
|
||||||
|
INSERT INTO "tracked_playlist" (id, playlist_id, playlist_size, tracking_user, tracking_type)
|
||||||
|
VALUES ($1, $2, $3, $4, $5)
|
||||||
|
"#,
|
||||||
|
uuid,
|
||||||
|
playlist_uuid,
|
||||||
|
playlist_rule.playlist_size,
|
||||||
|
playlist_rule.tracking_user,
|
||||||
|
playlist_rule.tracking_type,
|
||||||
|
)
|
||||||
|
.execute(&database)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
sqlx::query!(
|
||||||
|
r#"
|
||||||
|
UPDATE "create_tracked_playlist" SET last_name = $1 WHERE id = $2
|
||||||
|
"#,
|
||||||
|
new_name,
|
||||||
|
playlist_rule.id,
|
||||||
|
)
|
||||||
|
.execute(&database)
|
||||||
|
.await?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
@ -1,3 +1,5 @@
|
|||||||
|
pub mod create_playlists;
|
||||||
pub mod update_playlists;
|
pub mod update_playlists;
|
||||||
|
|
||||||
|
pub use create_playlists::create_playlists_daemon;
|
||||||
pub use update_playlists::update_playlists_daemon;
|
pub use update_playlists::update_playlists_daemon;
|
||||||
|
@ -19,7 +19,7 @@ pub enum UpdatePlaylistError {
|
|||||||
pub async fn update_playlists_daemon(
|
pub async fn update_playlists_daemon(
|
||||||
media_client: impl AsRef<Subsonic> + Clone,
|
media_client: impl AsRef<Subsonic> + Clone,
|
||||||
) -> Result<(), Box<dyn std::error::Error>> {
|
) -> Result<(), Box<dyn std::error::Error>> {
|
||||||
println!("Playlist daemon starting...");
|
println!("Track playlist daemon starting...");
|
||||||
|
|
||||||
let database = PgPool::connect(
|
let database = PgPool::connect(
|
||||||
CONFIG
|
CONFIG
|
||||||
@ -67,9 +67,10 @@ async fn update_playlist(
|
|||||||
let range = StatsRange::from_param(&playlist.tracking_type)
|
let range = StatsRange::from_param(&playlist.tracking_type)
|
||||||
.ok_or(UpdatePlaylistError::BadRange)?;
|
.ok_or(UpdatePlaylistError::BadRange)?;
|
||||||
|
|
||||||
let recordings = listenbrainz::recordings(playlist.tracking_user, range)
|
let recordings =
|
||||||
.await?
|
listenbrainz::recordings(playlist.tracking_user, range, playlist.playlist_size)
|
||||||
.recordings;
|
.await?
|
||||||
|
.recordings;
|
||||||
|
|
||||||
let mut tracks = vec![];
|
let mut tracks = vec![];
|
||||||
for recording in recordings {
|
for recording in recordings {
|
||||||
@ -77,7 +78,7 @@ async fn update_playlist(
|
|||||||
.as_ref()
|
.as_ref()
|
||||||
.song_search(format!(
|
.song_search(format!(
|
||||||
"{} {}",
|
"{} {}",
|
||||||
recording.track_name, recording.artist_name
|
recording.track_name, recording.artists[0].artist_credit_name
|
||||||
))
|
))
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
|
@ -82,11 +82,17 @@ pub struct RecordingsPayload {
|
|||||||
pub recordings: Vec<RecordingsEntry>,
|
pub recordings: Vec<RecordingsEntry>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
pub struct Artist {
|
||||||
|
pub artist_credit_name: String,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
#[derive(Deserialize)]
|
||||||
pub struct RecordingsEntry {
|
pub struct RecordingsEntry {
|
||||||
pub track_name: String,
|
pub track_name: String,
|
||||||
pub release_name: String,
|
pub release_name: String,
|
||||||
pub artist_name: String,
|
pub artist_name: String,
|
||||||
|
pub artists: Vec<Artist>,
|
||||||
pub recording_mbid: String,
|
pub recording_mbid: String,
|
||||||
pub release_mbid: String,
|
pub release_mbid: String,
|
||||||
}
|
}
|
||||||
@ -94,13 +100,15 @@ pub struct RecordingsEntry {
|
|||||||
pub async fn recordings(
|
pub async fn recordings(
|
||||||
user: impl Display,
|
user: impl Display,
|
||||||
range: StatsRange,
|
range: StatsRange,
|
||||||
|
count: i32,
|
||||||
) -> Result<RecordingsPayload, reqwest::Error> {
|
) -> Result<RecordingsPayload, reqwest::Error> {
|
||||||
let url = format!(
|
let url = format!(
|
||||||
"{}/stats/user/{}/{}?range={}",
|
"{}/stats/user/{}/{}?range={}&count={}",
|
||||||
BASE,
|
BASE,
|
||||||
user,
|
user,
|
||||||
StatsType::Recordings.stub(),
|
StatsType::Recordings.stub(),
|
||||||
range.param()
|
range.param(),
|
||||||
|
count
|
||||||
);
|
);
|
||||||
|
|
||||||
Ok(reqwest::get(url)
|
Ok(reqwest::get(url)
|
||||||
|
39
src/main.rs
39
src/main.rs
@ -5,13 +5,13 @@ mod playlist;
|
|||||||
mod subsonic;
|
mod subsonic;
|
||||||
mod track;
|
mod track;
|
||||||
|
|
||||||
use crate::daemon::update_playlists_daemon;
|
use crate::daemon::{create_playlists_daemon, update_playlists_daemon};
|
||||||
use crate::models::{CreateTrackedPlaylist, PartialTrackedPlaylist, TrackedPlaylist};
|
use crate::models::{PartialTrackedPlaylist, TrackedPlaylist};
|
||||||
use crate::subsonic::{Subsonic, SubsonicBuilder};
|
use crate::subsonic::{Subsonic, SubsonicBuilder};
|
||||||
use config::Config;
|
use config::Config;
|
||||||
use rocket::serde::json::{json, Json};
|
use rocket::serde::json::{json, Json};
|
||||||
use rocket::{get, post, routes, serde::json::Value as JsonValue, State};
|
use rocket::{get, post, routes, serde::json::Value as JsonValue, State};
|
||||||
use serde::Serialize;
|
use serde::{Deserialize, Serialize};
|
||||||
use sqlx::postgres::PgPool;
|
use sqlx::postgres::PgPool;
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
use std::sync::{Arc, OnceLock};
|
use std::sync::{Arc, OnceLock};
|
||||||
@ -49,16 +49,24 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|||||||
.build()?,
|
.build()?,
|
||||||
);
|
);
|
||||||
|
|
||||||
let _media_client = media_client.clone();
|
{
|
||||||
tokio::spawn(async move {
|
let _media_client = media_client.clone();
|
||||||
update_playlists_daemon(_media_client).await.unwrap();
|
tokio::spawn(async move {
|
||||||
});
|
update_playlists_daemon(_media_client).await.unwrap();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
{
|
||||||
|
let _media_client = media_client.clone();
|
||||||
|
tokio::spawn(async move {
|
||||||
|
create_playlists_daemon(_media_client).await.unwrap();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
rocket::build()
|
rocket::build()
|
||||||
.manage(media_client)
|
.manage(media_client)
|
||||||
.manage(database)
|
.manage(database)
|
||||||
.mount(
|
.mount(
|
||||||
"/",
|
"/api/playlist",
|
||||||
routes![
|
routes![
|
||||||
create_tracking_playlist,
|
create_tracking_playlist,
|
||||||
add_tracking_playlist,
|
add_tracking_playlist,
|
||||||
@ -98,11 +106,20 @@ enum Response<T> {
|
|||||||
Error(JsonError),
|
Error(JsonError),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
pub struct CreatePlaylist {
|
||||||
|
pub playlist_name: String,
|
||||||
|
pub playlist_size: i32,
|
||||||
|
pub tracking_user: Option<String>,
|
||||||
|
pub tracking_type: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create a new playlist and attach a tracker
|
||||||
#[post("/create", data = "<create_playlist>")]
|
#[post("/create", data = "<create_playlist>")]
|
||||||
async fn create_tracking_playlist(
|
async fn create_tracking_playlist(
|
||||||
media_client: &State<Arc<Subsonic>>,
|
media_client: &State<Arc<Subsonic>>,
|
||||||
pool: &State<PgPool>,
|
pool: &State<PgPool>,
|
||||||
create_playlist: Json<CreateTrackedPlaylist>,
|
create_playlist: Json<CreatePlaylist>,
|
||||||
) -> Json<Response<TrackedPlaylist>> {
|
) -> Json<Response<TrackedPlaylist>> {
|
||||||
match media_client
|
match media_client
|
||||||
.create_playlist(create_playlist.playlist_name.clone())
|
.create_playlist(create_playlist.playlist_name.clone())
|
||||||
@ -132,7 +149,8 @@ async fn create_tracking_playlist(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[post("/add", data = "<playlist>")]
|
/// Add tracking to an existing playlist
|
||||||
|
#[post("/track", data = "<playlist>")]
|
||||||
async fn add_tracking_playlist(
|
async fn add_tracking_playlist(
|
||||||
pool: &State<PgPool>,
|
pool: &State<PgPool>,
|
||||||
playlist: Json<PartialTrackedPlaylist>,
|
playlist: Json<PartialTrackedPlaylist>,
|
||||||
@ -143,6 +161,7 @@ async fn add_tracking_playlist(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Get all tracked playlists
|
||||||
#[get("/")]
|
#[get("/")]
|
||||||
async fn all_playlists(pool: &State<PgPool>) -> JsonValue {
|
async fn all_playlists(pool: &State<PgPool>) -> JsonValue {
|
||||||
match TrackedPlaylist::all(pool.inner()).await {
|
match TrackedPlaylist::all(pool.inner()).await {
|
||||||
|
@ -4,14 +4,6 @@ use sqlx::types::Uuid as UuidType;
|
|||||||
use sqlx::{Executor, Postgres};
|
use sqlx::{Executor, Postgres};
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
|
||||||
pub struct CreateTrackedPlaylist {
|
|
||||||
pub playlist_name: String,
|
|
||||||
pub playlist_size: i32,
|
|
||||||
pub tracking_user: Option<String>,
|
|
||||||
pub tracking_type: Option<String>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
#[derive(Deserialize)]
|
||||||
pub struct PartialTrackedPlaylist {
|
pub struct PartialTrackedPlaylist {
|
||||||
pub playlist_id: Uuid,
|
pub playlist_id: Uuid,
|
||||||
@ -75,3 +67,34 @@ impl TrackedPlaylist {
|
|||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize)]
|
||||||
|
pub struct CreateTrackedPlaylist {
|
||||||
|
pub id: Uuid,
|
||||||
|
pub name_template: String,
|
||||||
|
pub last_name: Option<String>,
|
||||||
|
pub playlist_size: i32,
|
||||||
|
pub tracking_user: String,
|
||||||
|
pub tracking_type: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CreateTrackedPlaylist {
|
||||||
|
pub async fn all(pool: impl Executor<'_, Database = Postgres>) -> Result<Vec<Self>, Error> {
|
||||||
|
sqlx::query_as!(Self, r#"SELECT * FROM "create_tracked_playlist""#)
|
||||||
|
.fetch_all(pool)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn rule(
|
||||||
|
pool: impl Executor<'_, Database = Postgres>,
|
||||||
|
uuid: Uuid,
|
||||||
|
) -> Result<Self, Error> {
|
||||||
|
sqlx::query_as!(
|
||||||
|
Self,
|
||||||
|
r#"SELECT * FROM "create_tracked_playlist" WHERE id = $1"#,
|
||||||
|
uuid
|
||||||
|
)
|
||||||
|
.fetch_one(pool)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -6,6 +6,10 @@ use std::fmt::{Display, Formatter};
|
|||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
|
fn default_vec() -> Vec<Track> {
|
||||||
|
Vec::new()
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct Subsonic {
|
pub struct Subsonic {
|
||||||
username: String,
|
username: String,
|
||||||
@ -28,6 +32,7 @@ struct SearchResponse {
|
|||||||
|
|
||||||
#[derive(Serialize, Deserialize)]
|
#[derive(Serialize, Deserialize)]
|
||||||
struct SearchResponseSongs {
|
struct SearchResponseSongs {
|
||||||
|
#[serde(default = "default_vec")]
|
||||||
song: Vec<Track>,
|
song: Vec<Track>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -121,7 +126,10 @@ impl Subsonic {
|
|||||||
.playlist)
|
.playlist)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn create_playlist(&self, playlist_name: String) -> Result<Playlist, SubsonicError> {
|
pub async fn create_playlist(
|
||||||
|
&self,
|
||||||
|
playlist_name: impl ToString,
|
||||||
|
) -> Result<Playlist, SubsonicError> {
|
||||||
let auth_params = self.auth_params();
|
let auth_params = self.auth_params();
|
||||||
|
|
||||||
let get_params = [
|
let get_params = [
|
||||||
@ -131,7 +139,7 @@ impl Subsonic {
|
|||||||
("v", String::from("16")),
|
("v", String::from("16")),
|
||||||
("c", String::from("playlistd")),
|
("c", String::from("playlistd")),
|
||||||
("f", String::from("json")),
|
("f", String::from("json")),
|
||||||
("name", playlist_name),
|
("name", playlist_name.to_string()),
|
||||||
];
|
];
|
||||||
|
|
||||||
Ok(self
|
Ok(self
|
||||||
|
Loading…
Reference in New Issue
Block a user