Moved listenbrainz api in as its tiny. Add initial schema

This commit is contained in:
2023-08-16 15:50:31 +01:00
parent 41fcffb43d
commit d217dd0b81
10 changed files with 657 additions and 35 deletions

36
src/daemon.rs Normal file
View File

@ -0,0 +1,36 @@
use crate::models::TrackedPlaylist;
use sqlx::postgres::PgPool;
use std::env;
use std::time::Duration;
use tokio::time::{interval, MissedTickBehavior};
pub async fn update_playlists_daemon() -> Result<(), Box<dyn std::error::Error>> {
let database = PgPool::connect(&env::var("DATABASE_URL").unwrap())
.await
.unwrap();
let mut ticker = interval(Duration::from_secs(1800));
ticker.set_missed_tick_behavior(MissedTickBehavior::Skip);
loop {
let records_res = sqlx::query_as!(TrackedPlaylist, "SELECT * FROM \"tracked_playlist\"")
.fetch_all(&database)
.await;
match records_res {
Ok(records) => {
for record in records {
update_playlist(record).await;
}
}
Err(e) => {
println!("Could not fetch tracked_playlists: {:?}", e);
}
}
ticker.tick().await;
}
}
async fn update_playlist(playlist: TrackedPlaylist) {}

93
src/listenbrainz.rs Normal file
View File

@ -0,0 +1,93 @@
use serde::Deserialize;
const BASE: &'static str = "https://api.listenbrainz.org/1";
enum StatsType {
Artists,
Recordings,
Releases,
ReleaseGroups,
}
impl StatsType {
fn stub(&self) -> &'static str {
match self {
StatsType::Artists => "artists",
StatsType::Recordings => "recordings",
StatsType::Releases => "releases",
StatsType::ReleaseGroups => "release-groups",
}
}
}
pub enum StatsRange {
ThisWeek,
ThisMonth,
ThisYear,
Week,
Month,
Quarter,
HalfYear,
Year,
All,
}
impl StatsRange {
fn param(&self) -> &'static str {
match self {
StatsRange::ThisWeek => "this_week",
StatsRange::ThisMonth => "this_month",
StatsRange::ThisYear => "this_year",
StatsRange::Week => "week",
StatsRange::Month => "month",
StatsRange::Quarter => "quarter",
StatsRange::HalfYear => "half_yearly",
StatsRange::Year => "year",
StatsRange::All => "all_time",
}
}
}
#[derive(Deserialize)]
struct RecordingsResponse {
payload: RecordingsPayload,
}
#[derive(Deserialize)]
pub struct RecordingsPayload {
count: u64,
from_ts: u64,
to_ts: u64,
last_updated: u64,
total_recording_count: u64,
user_id: String,
offset: u64,
range: String,
pub recordings: Vec<RecordingsEntry>,
}
#[derive(Deserialize)]
pub struct RecordingsEntry {
pub track_name: String,
pub recording_mbid: String,
pub release_mbid: String,
}
pub async fn recordings(
user: &str,
range: StatsRange,
) -> Result<RecordingsPayload, reqwest::Error> {
let url = format!(
"{}/stats/user/{}/{}?range={}",
BASE,
user,
StatsType::Recordings.stub(),
range.param()
);
Ok(reqwest::get(url)
.await?
.json::<RecordingsResponse>()
.await?
.payload)
}

View File

@ -1,14 +1,44 @@
mod daemon;
mod listenbrainz;
mod models;
use crate::daemon::update_playlists_daemon;
use crate::listenbrainz::StatsRange;
use axum::extract::State;
use axum::routing::get;
use axum::Router;
use sqlx::postgres::PgPool;
use std::env;
use std::net::SocketAddr;
#[tokio::main]
fn main() {
let app = Router::new().route("/", get(index));
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let database = PgPool::connect(&env::var("DATABASE_URL")?).await?;
sqlx::migrate!().run(&database).await?;
let app = Router::new().route("/", get(index)).with_state(database);
let addr = SocketAddr::from(([127, 0, 0, 1], 3000));
axum::Server::bind(&addr)
.serve()
.serve(app.into_make_service())
.await?;
tokio::spawn(async move {
update_playlists_daemon().await.unwrap();
});
Ok(())
}
async fn index() {
async fn index(State(pool): State<PgPool>) -> String {
let response = listenbrainz::recordings("jellywx", StatsRange::Week)
.await
.unwrap();
response
.recordings
.iter()
.map(|a| a.track_name.clone())
.collect::<Vec<String>>()
.join(", ")
}

11
src/models.rs Normal file
View File

@ -0,0 +1,11 @@
use sqlx::types::Uuid;
pub struct TrackedPlaylist {
pub rule_id: i64,
pub playlist_id: Option<Uuid>,
pub playlist_name: Option<String>,
pub playlist_size: i32,
pub tracking_user: Option<String>,
pub tracking_type: Option<String>,
pub reduce_duplication_on: Option<String>,
}