A decentralized music tracking and discovery platform built on AT Protocol 🎵 rocksky.app
spotify atproto lastfm musicbrainz scrobbling listenbrainz
98
fork

Configure Feed

Select the types of activity you want to include in your feed.

feat: add repository structure for DuckDB and Postgres with async support

+361 -44
+1
Cargo.lock
··· 5362 5362 version = "0.1.0" 5363 5363 dependencies = [ 5364 5364 "anyhow", 5365 + "async-trait", 5365 5366 "atrium-api", 5366 5367 "atrium-xrpc-client", 5367 5368 "chrono",
+1
crates/feed/Cargo.toml
··· 38 38 "rustls-tls-webpki-roots", 39 39 ] } 40 40 tokio-stream = { version = "0.1.17", features = ["full"] } 41 + async-trait = "0.1.89"
+13
crates/feed/src/repo/duckdb/album.rs
··· 1 + use std::sync::Arc; 2 + 3 + use tokio::sync::Mutex; 4 + 5 + pub struct AlbumRepo { 6 + pub conn: Arc<Mutex<duckdb::Connection>>, 7 + } 8 + 9 + impl AlbumRepo { 10 + pub fn new(conn: Arc<Mutex<duckdb::Connection>>) -> Self { 11 + Self { conn } 12 + } 13 + }
+13
crates/feed/src/repo/duckdb/artist.rs
··· 1 + use std::sync::Arc; 2 + 3 + use tokio::sync::Mutex; 4 + 5 + pub struct ArtistRepo { 6 + pub conn: Arc<Mutex<duckdb::Connection>>, 7 + } 8 + 9 + impl ArtistRepo { 10 + pub fn new(conn: Arc<Mutex<duckdb::Connection>>) -> Self { 11 + Self { conn } 12 + } 13 + }
+43 -15
crates/feed/src/repo/duckdb/mod.rs
··· 1 + use std::sync::Arc; 2 + 3 + use crate::repo::duckdb::{ 4 + album::AlbumRepo, artist::ArtistRepo, scrobble::ScrobbleRepo, track::TrackRepo, 5 + }; 6 + 1 7 use super::Repo; 8 + use anyhow::Error; 9 + use async_trait::async_trait; 10 + use tokio::sync::Mutex; 2 11 3 12 pub mod album; 4 13 pub mod artist; ··· 6 15 pub mod track; 7 16 pub mod user; 8 17 9 - pub struct DuckdbRepo {} 18 + pub struct DuckdbRepo { 19 + pub album: AlbumRepo, 20 + pub atist: ArtistRepo, 21 + pub scrobble: ScrobbleRepo, 22 + pub track: TrackRepo, 23 + } 24 + 25 + impl DuckdbRepo { 26 + pub async fn new() -> Result<Self, Error> { 27 + let conn = duckdb::Connection::open("./rocksky-seed.ddb")?; 28 + let conn = Arc::new(Mutex::new(conn)); 29 + Ok(Self { 30 + album: AlbumRepo::new(conn.clone()), 31 + atist: ArtistRepo::new(conn.clone()), 32 + scrobble: ScrobbleRepo::new(conn.clone()), 33 + track: TrackRepo::new(conn.clone()), 34 + }) 35 + } 36 + } 10 37 38 + #[async_trait] 11 39 impl Repo for DuckdbRepo { 12 - fn insert_album() -> Result<(), anyhow::Error> { 40 + async fn insert_album(self) -> Result<(), anyhow::Error> { 13 41 todo!() 14 42 } 15 43 16 - fn insert_artist() -> Result<(), anyhow::Error> { 44 + async fn insert_artist(self) -> Result<(), anyhow::Error> { 17 45 todo!() 18 46 } 19 47 20 - fn insert_scrobble() -> Result<(), anyhow::Error> { 48 + async fn insert_scrobble(self) -> Result<(), anyhow::Error> { 21 49 todo!() 22 50 } 23 51 24 - fn insert_track() -> Result<(), anyhow::Error> { 52 + async fn insert_track(self) -> Result<(), anyhow::Error> { 25 53 todo!() 26 54 } 27 55 28 - fn insert_user() -> Result<(), anyhow::Error> { 56 + async fn insert_user(self) -> Result<(), anyhow::Error> { 29 57 todo!() 30 58 } 31 59 32 - fn get_albums() -> Result<(), anyhow::Error> { 60 + async fn get_albums(self) -> Result<(), anyhow::Error> { 33 61 todo!() 34 62 } 35 63 36 - fn get_artists() -> Result<(), anyhow::Error> { 64 + async fn get_artists(self) -> Result<(), anyhow::Error> { 37 65 todo!() 38 66 } 39 67 40 - fn get_scrobbles() -> Result<(), anyhow::Error> { 68 + async fn get_scrobbles(self) -> Result<(), anyhow::Error> { 41 69 todo!() 42 70 } 43 71 44 - fn get_tracks() -> Result<(), anyhow::Error> { 72 + async fn get_tracks(self) -> Result<(), anyhow::Error> { 45 73 todo!() 46 74 } 47 75 48 - fn get_users() -> Result<(), anyhow::Error> { 76 + async fn get_users(self) -> Result<(), anyhow::Error> { 49 77 todo!() 50 78 } 51 79 52 - fn get_album() -> Result<(), anyhow::Error> { 80 + async fn get_album(self) -> Result<(), anyhow::Error> { 53 81 todo!() 54 82 } 55 83 56 - fn get_artist() -> Result<(), anyhow::Error> { 84 + async fn get_artist(self) -> Result<(), anyhow::Error> { 57 85 todo!() 58 86 } 59 87 60 - fn get_track() -> Result<(), anyhow::Error> { 88 + async fn get_track(self) -> Result<(), anyhow::Error> { 61 89 todo!() 62 90 } 63 91 64 - fn get_user() -> Result<(), anyhow::Error> { 92 + async fn get_user(self) -> Result<(), anyhow::Error> { 65 93 todo!() 66 94 } 67 95 }
+13
crates/feed/src/repo/duckdb/scrobble.rs
··· 1 + use std::sync::Arc; 2 + 3 + use tokio::sync::Mutex; 4 + 5 + pub struct ScrobbleRepo { 6 + pub conn: Arc<Mutex<duckdb::Connection>>, 7 + } 8 + 9 + impl ScrobbleRepo { 10 + pub fn new(conn: Arc<Mutex<duckdb::Connection>>) -> Self { 11 + Self { conn } 12 + } 13 + }
+13
crates/feed/src/repo/duckdb/track.rs
··· 1 + use std::sync::Arc; 2 + 3 + use tokio::sync::Mutex; 4 + 5 + pub struct TrackRepo { 6 + pub conn: Arc<Mutex<duckdb::Connection>>, 7 + } 8 + 9 + impl TrackRepo { 10 + pub fn new(conn: Arc<Mutex<duckdb::Connection>>) -> Self { 11 + Self { conn } 12 + } 13 + }
+13
crates/feed/src/repo/duckdb/user.rs
··· 1 + use std::sync::Arc; 2 + 3 + use tokio::sync::Mutex; 4 + 5 + pub struct UserRepo { 6 + pub conn: Arc<Mutex<duckdb::Connection>>, 7 + } 8 + 9 + impl UserRepo { 10 + pub fn new(conn: Arc<Mutex<duckdb::Connection>>) -> Self { 11 + Self { conn } 12 + } 13 + }
+16 -14
crates/feed/src/repo/mod.rs
··· 1 1 use anyhow::Error; 2 + use async_trait::async_trait; 2 3 3 4 pub mod duckdb; 4 5 pub mod postgres; 5 6 7 + #[async_trait] 6 8 pub trait Repo { 7 - fn insert_album() -> Result<(), Error>; 8 - fn insert_artist() -> Result<(), Error>; 9 - fn insert_scrobble() -> Result<(), Error>; 10 - fn insert_track() -> Result<(), Error>; 11 - fn insert_user() -> Result<(), Error>; 12 - fn get_albums() -> Result<(), Error>; 13 - fn get_artists() -> Result<(), Error>; 14 - fn get_scrobbles() -> Result<(), Error>; 15 - fn get_tracks() -> Result<(), Error>; 16 - fn get_users() -> Result<(), Error>; 17 - fn get_album() -> Result<(), Error>; 18 - fn get_artist() -> Result<(), Error>; 19 - fn get_track() -> Result<(), Error>; 20 - fn get_user() -> Result<(), Error>; 9 + async fn insert_album(self) -> Result<(), Error>; 10 + async fn insert_artist(self) -> Result<(), Error>; 11 + async fn insert_scrobble(self) -> Result<(), Error>; 12 + async fn insert_track(self) -> Result<(), Error>; 13 + async fn insert_user(self) -> Result<(), Error>; 14 + async fn get_albums(self) -> Result<(), Error>; 15 + async fn get_artists(self) -> Result<(), Error>; 16 + async fn get_scrobbles(self) -> Result<(), Error>; 17 + async fn get_tracks(self) -> Result<(), Error>; 18 + async fn get_users(self) -> Result<(), Error>; 19 + async fn get_album(self) -> Result<(), Error>; 20 + async fn get_artist(self) -> Result<(), Error>; 21 + async fn get_track(self) -> Result<(), Error>; 22 + async fn get_user(self) -> Result<(), Error>; 21 23 }
+14
crates/feed/src/repo/postgres/album.rs
··· 1 + use std::sync::Arc; 2 + 3 + use sqlx::{Pool, Postgres}; 4 + use tokio::sync::Mutex; 5 + 6 + pub struct AlbumRepo { 7 + pub pool: Arc<Mutex<Pool<Postgres>>>, 8 + } 9 + 10 + impl AlbumRepo { 11 + pub fn new(pool: Arc<Mutex<Pool<Postgres>>>) -> Self { 12 + Self { pool } 13 + } 14 + }
+14
crates/feed/src/repo/postgres/artist.rs
··· 1 + use std::sync::Arc; 2 + 3 + use sqlx::{Pool, Postgres}; 4 + use tokio::sync::Mutex; 5 + 6 + pub struct ArtistRepo { 7 + pub pool: Arc<Mutex<Pool<Postgres>>>, 8 + } 9 + 10 + impl ArtistRepo { 11 + pub fn new(pool: Arc<Mutex<Pool<Postgres>>>) -> Self { 12 + Self { pool } 13 + } 14 + }
+50 -15
crates/feed/src/repo/postgres/mod.rs
··· 1 + use std::{env, sync::Arc}; 2 + 3 + use anyhow::Error; 4 + use async_trait::async_trait; 5 + use sqlx::postgres::PgPoolOptions; 6 + use tokio::sync::Mutex; 7 + 8 + use crate::repo::postgres::{ 9 + album::AlbumRepo, artist::ArtistRepo, scrobble::ScrobbleRepo, track::TrackRepo, user::UserRepo, 10 + }; 11 + 1 12 use super::Repo; 2 13 3 14 pub mod album; ··· 6 17 pub mod track; 7 18 pub mod user; 8 19 9 - pub struct PostgresRepo {} 20 + pub struct PostgresRepo { 21 + pub album: AlbumRepo, 22 + pub artist: ArtistRepo, 23 + pub scrobble: ScrobbleRepo, 24 + pub track: TrackRepo, 25 + pub user: UserRepo, 26 + } 27 + 28 + impl PostgresRepo { 29 + pub async fn new() -> Result<Self, Error> { 30 + let pool = PgPoolOptions::new() 31 + .max_connections(5) 32 + .connect(&env::var("XATA_POSTGRES_URL")?) 33 + .await?; 34 + let pool = Arc::new(Mutex::new(pool)); 35 + Ok(Self { 36 + album: AlbumRepo::new(pool.clone()), 37 + artist: ArtistRepo::new(pool.clone()), 38 + scrobble: ScrobbleRepo::new(pool.clone()), 39 + track: TrackRepo::new(pool.clone()), 40 + user: UserRepo::new(pool.clone()), 41 + }) 42 + } 43 + } 10 44 45 + #[async_trait] 11 46 impl Repo for PostgresRepo { 12 - fn insert_album() -> Result<(), anyhow::Error> { 47 + async fn insert_album(self) -> Result<(), anyhow::Error> { 13 48 todo!() 14 49 } 15 50 16 - fn insert_artist() -> Result<(), anyhow::Error> { 51 + async fn insert_artist(self) -> Result<(), anyhow::Error> { 17 52 todo!() 18 53 } 19 54 20 - fn insert_scrobble() -> Result<(), anyhow::Error> { 55 + async fn insert_scrobble(self) -> Result<(), anyhow::Error> { 21 56 todo!() 22 57 } 23 58 24 - fn insert_track() -> Result<(), anyhow::Error> { 59 + async fn insert_track(self) -> Result<(), anyhow::Error> { 25 60 todo!() 26 61 } 27 62 28 - fn insert_user() -> Result<(), anyhow::Error> { 63 + async fn insert_user(self) -> Result<(), anyhow::Error> { 29 64 todo!() 30 65 } 31 66 32 - fn get_albums() -> Result<(), anyhow::Error> { 67 + async fn get_albums(self) -> Result<(), anyhow::Error> { 33 68 todo!() 34 69 } 35 70 36 - fn get_artists() -> Result<(), anyhow::Error> { 71 + async fn get_artists(self) -> Result<(), anyhow::Error> { 37 72 todo!() 38 73 } 39 74 40 - fn get_scrobbles() -> Result<(), anyhow::Error> { 75 + async fn get_scrobbles(self) -> Result<(), anyhow::Error> { 41 76 todo!() 42 77 } 43 78 44 - fn get_tracks() -> Result<(), anyhow::Error> { 79 + async fn get_tracks(self) -> Result<(), anyhow::Error> { 45 80 todo!() 46 81 } 47 82 48 - fn get_users() -> Result<(), anyhow::Error> { 83 + async fn get_users(self) -> Result<(), anyhow::Error> { 49 84 todo!() 50 85 } 51 86 52 - fn get_album() -> Result<(), anyhow::Error> { 87 + async fn get_album(self) -> Result<(), anyhow::Error> { 53 88 todo!() 54 89 } 55 90 56 - fn get_artist() -> Result<(), anyhow::Error> { 91 + async fn get_artist(self) -> Result<(), anyhow::Error> { 57 92 todo!() 58 93 } 59 94 60 - fn get_track() -> Result<(), anyhow::Error> { 95 + async fn get_track(self) -> Result<(), anyhow::Error> { 61 96 todo!() 62 97 } 63 98 64 - fn get_user() -> Result<(), anyhow::Error> { 99 + async fn get_user(self) -> Result<(), anyhow::Error> { 65 100 todo!() 66 101 } 67 102 }
+13
crates/feed/src/repo/postgres/scrobble.rs
··· 1 + use std::sync::Arc; 1 2 3 + use sqlx::{Pool, Postgres}; 4 + use tokio::sync::Mutex; 5 + 6 + pub struct ScrobbleRepo { 7 + pub pool: Arc<Mutex<Pool<Postgres>>>, 8 + } 9 + 10 + impl ScrobbleRepo { 11 + pub fn new(pool: Arc<Mutex<Pool<Postgres>>>) -> Self { 12 + Self { pool } 13 + } 14 + }
+14
crates/feed/src/repo/postgres/track.rs
··· 1 + use std::sync::Arc; 2 + 3 + use sqlx::{Pool, Postgres}; 4 + use tokio::sync::Mutex; 5 + 6 + pub struct TrackRepo { 7 + pub pool: Arc<Mutex<Pool<Postgres>>>, 8 + } 9 + 10 + impl TrackRepo { 11 + pub fn new(pool: Arc<Mutex<Pool<Postgres>>>) -> Self { 12 + Self { pool } 13 + } 14 + }
+14
crates/feed/src/repo/postgres/user.rs
··· 1 + use std::sync::Arc; 2 + 3 + use sqlx::{Pool, Postgres}; 4 + use tokio::sync::Mutex; 5 + 6 + pub struct UserRepo { 7 + pub pool: Arc<Mutex<Pool<Postgres>>>, 8 + } 9 + 10 + impl UserRepo { 11 + pub fn new(pool: Arc<Mutex<Pool<Postgres>>>) -> Self { 12 + Self { pool } 13 + } 14 + }
+21
crates/feed/src/xata/album.rs
··· 1 + use chrono::{DateTime, Utc}; 2 + use serde::Deserialize; 3 + 4 + #[derive(Debug, sqlx::FromRow, Deserialize, Clone)] 5 + pub struct Album { 6 + pub xata_id: String, 7 + pub title: String, 8 + pub artist: String, 9 + pub release_date: Option<String>, 10 + pub album_art: Option<String>, 11 + pub year: Option<i32>, 12 + pub spotify_link: Option<String>, 13 + pub tidal_link: Option<String>, 14 + pub youtube_link: Option<String>, 15 + pub apple_music_link: Option<String>, 16 + pub sha256: String, 17 + pub uri: Option<String>, 18 + pub artist_uri: Option<String>, 19 + #[serde(with = "chrono::serde::ts_seconds")] 20 + pub xata_createdat: DateTime<Utc>, 21 + }
+8
crates/feed/src/xata/album_track.rs
··· 1 + use serde::Deserialize; 2 + 3 + #[derive(Debug, sqlx::FromRow, Deserialize, Clone)] 4 + pub struct AlbumTrack { 5 + pub xata_id: String, 6 + pub album_id: String, 7 + pub track_id: String, 8 + }
+24
crates/feed/src/xata/artist.rs
··· 1 + use chrono::{DateTime, Utc}; 2 + use serde::Deserialize; 3 + 4 + #[derive(Debug, sqlx::FromRow, Deserialize, Clone)] 5 + pub struct Artist { 6 + pub xata_id: String, 7 + pub name: String, 8 + pub biography: Option<String>, 9 + #[serde(with = "chrono::serde::ts_seconds_option")] 10 + pub born: Option<DateTime<Utc>>, 11 + pub born_in: Option<String>, 12 + #[serde(with = "chrono::serde::ts_seconds_option")] 13 + pub died: Option<DateTime<Utc>>, 14 + pub picture: Option<String>, 15 + pub sha256: String, 16 + pub spotify_link: Option<String>, 17 + pub tidal_link: Option<String>, 18 + pub youtube_link: Option<String>, 19 + pub apple_music_link: Option<String>, 20 + pub uri: Option<String>, 21 + pub genres: Option<Vec<String>>, 22 + #[serde(with = "chrono::serde::ts_seconds")] 23 + pub xata_createdat: DateTime<Utc>, 24 + }
+10
crates/feed/src/xata/artist_track.rs
··· 1 + use serde::Deserialize; 2 + 3 + #[derive(Debug, sqlx::FromRow, Deserialize, Clone)] 4 + pub struct ArtistTrack { 5 + pub xata_id: String, 6 + pub artist_id: String, 7 + pub track_id: String, 8 + #[serde(with = "chrono::serde::ts_seconds")] 9 + pub xata_createdat: chrono::DateTime<chrono::Utc>, 10 + }
+6
crates/feed/src/xata/mod.rs
··· 1 + pub mod album; 2 + pub mod album_track; 3 + pub mod artist; 4 + pub mod artist_track; 5 + pub mod scrobble; 6 + pub mod track; 1 7 pub mod user;
+16
crates/feed/src/xata/scrobble.rs
··· 1 + use chrono::{DateTime, Utc}; 2 + use serde::Deserialize; 3 + 4 + #[derive(Debug, sqlx::FromRow, Deserialize, Clone)] 5 + pub struct Scrobble { 6 + pub xata_id: String, 7 + pub user_id: String, 8 + pub track_id: String, 9 + pub album_id: Option<String>, 10 + pub artist_id: Option<String>, 11 + pub uri: Option<String>, 12 + #[serde(with = "chrono::serde::ts_seconds")] 13 + pub xata_createdat: DateTime<Utc>, 14 + #[serde(with = "chrono::serde::ts_seconds")] 15 + pub timestamp: DateTime<Utc>, 16 + }
+31
crates/feed/src/xata/track.rs
··· 1 + use chrono::{DateTime, Utc}; 2 + use serde::{Deserialize, Serialize}; 3 + 4 + #[derive(Debug, sqlx::FromRow, Serialize, Deserialize, Clone)] 5 + pub struct Track { 6 + pub xata_id: String, 7 + pub title: String, 8 + pub artist: String, 9 + pub album_artist: String, 10 + pub album_art: Option<String>, 11 + pub album: String, 12 + pub track_number: i32, 13 + pub duration: i32, 14 + pub mb_id: Option<String>, 15 + pub youtube_link: Option<String>, 16 + pub spotify_link: Option<String>, 17 + pub tidal_link: Option<String>, 18 + pub apple_music_link: Option<String>, 19 + pub sha256: String, 20 + pub lyrics: Option<String>, 21 + pub composer: Option<String>, 22 + pub genre: Option<String>, 23 + pub disc_number: i32, 24 + pub copyright_message: Option<String>, 25 + pub label: Option<String>, 26 + pub uri: Option<String>, 27 + pub artist_uri: Option<String>, 28 + pub album_uri: Option<String>, 29 + #[serde(with = "chrono::serde::ts_seconds")] 30 + pub xata_createdat: DateTime<Utc>, 31 + }