diff --git a/Cargo.lock b/Cargo.lock index 79e1fbf..d37a210 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -65,7 +65,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e01ed3140b2f8d422c68afa1ed2e85d996ea619c988ac834d255db32138655cb" dependencies = [ "quote", - "syn 2.0.46", + "syn 2.0.48", ] [[package]] @@ -179,7 +179,7 @@ dependencies = [ "actix-router", "proc-macro2", "quote", - "syn 2.0.46", + "syn 2.0.48", ] [[package]] @@ -257,7 +257,7 @@ checksum = "c980ee35e870bd1a4d2c8294d4c04d0499e67bca1e4b5cefcc693c2fa00caea9" dependencies = [ "proc-macro2", "quote", - "syn 2.0.46", + "syn 2.0.48", ] [[package]] @@ -455,9 +455,9 @@ checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f" [[package]] name = "cpufeatures" -version = "0.2.11" +version = "0.2.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce420fe07aecd3e67c5f910618fe65e94158f6dcc0adf44e00d69ce2bdfe0fd0" +checksum = "53fe5e26ff1b7aef8bca9c6080520cfb8d9333c7568e1829cef191a9723e5504" dependencies = [ "libc", ] @@ -527,7 +527,7 @@ dependencies = [ "diesel_table_macro_syntax", "proc-macro2", "quote", - "syn 2.0.46", + "syn 2.0.48", ] [[package]] @@ -547,7 +547,7 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc5557efc453706fed5e4fa85006fe9817c224c3f480a34c7e5959fd700921c5" dependencies = [ - "syn 2.0.46", + "syn 2.0.48", ] [[package]] @@ -967,9 +967,9 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "libc" -version = "0.2.151" +version = "0.2.152" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "302d7ab3130588088d277783b1e2d2e10c9e9e4a16dd9050e6ec93fb3e7048f4" +checksum = "13e3bf6590cbc649f4d1a3eefc9d5d6eb746f5200ffb04e5e142700b8faa56e7" [[package]] name = "linux-raw-sys" @@ -1160,7 +1160,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.46", + "syn 2.0.48", ] [[package]] @@ -1267,9 +1267,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.74" +version = "1.0.76" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2de98502f212cfcea8d0bb305bd0f49d7ebdd75b64ba0a68f937d888f4e0d6db" +checksum = "95fc56cda0b5c3325f5fbbd7ff9fda9e02bb00bb3dac51252d2f1bfa1cb8cc8c" dependencies = [ "unicode-ident", ] @@ -1497,35 +1497,35 @@ dependencies = [ [[package]] name = "semver" -version = "1.0.20" +version = "1.0.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "836fa6a3e1e547f9a2c4040802ec865b5d85f4014efe00555d7090a3dcaa1090" +checksum = "b97ed7a9823b74f99c7742f5336af7be5ecd3eeafcb1507d1fa93347b1d589b0" [[package]] name = "serde" -version = "1.0.194" +version = "1.0.195" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b114498256798c94a0689e1a15fec6005dee8ac1f41de56404b67afc2a4b773" +checksum = "63261df402c67811e9ac6def069e4786148c4563f4b50fd4bf30aa370d626b02" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.194" +version = "1.0.195" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3385e45322e8f9931410f01b3031ec534c3947d0e94c18049af4d9f9907d4e0" +checksum = "46fe8f8603d81ba86327b23a2e9cdf49e1255fb94a4c5f297f6ee0547178ea2c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.46", + "syn 2.0.48", ] [[package]] name = "serde_json" -version = "1.0.110" +version = "1.0.111" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6fbd975230bada99c8bb618e0c365c2eefa219158d5c6c29610fd09ff1833257" +checksum = "176e46fa42316f18edd598015a5166857fc835ec732f5215eac6b7bdbf0a84f4" dependencies = [ "itoa", "ryu", @@ -1646,9 +1646,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.46" +version = "2.0.48" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89456b690ff72fddcecf231caedbe615c59480c93358a93dfae7fc29e3ebbf0e" +checksum = "0f3531638e407dfc0814761abb7c00a5b54992b849452a0646b7f65c9f770f3f" dependencies = [ "proc-macro2", "quote", @@ -1715,7 +1715,7 @@ checksum = "fa0faa943b50f3db30a20aa7e265dbc66076993efed8463e8de414e5d06d3471" dependencies = [ "proc-macro2", "quote", - "syn 2.0.46", + "syn 2.0.48", ] [[package]] @@ -1931,6 +1931,7 @@ name = "vybr-api" version = "0.0.1" dependencies = [ "actix-rt", + "actix-service", "actix-web", "async-trait", "bcrypt", @@ -1985,7 +1986,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn 2.0.46", + "syn 2.0.48", "wasm-bindgen-shared", ] @@ -2019,7 +2020,7 @@ checksum = "f0eb82fcb7930ae6219a7ecfd55b217f5f0893484b7a13022ebb2b2bf20b5283" dependencies = [ "proc-macro2", "quote", - "syn 2.0.46", + "syn 2.0.48", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -2214,9 +2215,9 @@ checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04" [[package]] name = "winnow" -version = "0.5.31" +version = "0.5.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97a4882e6b134d6c28953a387571f1acdd3496830d5e36c5e3a1075580ea641c" +checksum = "b7520bbdec7211caa7c4e682eb1fbe07abe20cee6756b6e00f537c82c11816aa" dependencies = [ "memchr", ] @@ -2248,7 +2249,7 @@ checksum = "9ce1b18ccd8e73a9321186f97e46f9f04b778851177567b1975109d26a08d2a6" dependencies = [ "proc-macro2", "quote", - "syn 2.0.46", + "syn 2.0.48", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index bb3a36a..920e9a5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -23,3 +23,4 @@ lazy_static = "1" jsonwebtoken = "9" serde = "1" serde_json = "1" +actix-service = "2.0.2" diff --git a/migrations/00000000000000_diesel_initial_setup/up.sql b/migrations/00000000000000_diesel_initial_setup/up.sql index 7e95698..fa90f26 100644 --- a/migrations/00000000000000_diesel_initial_setup/up.sql +++ b/migrations/00000000000000_diesel_initial_setup/up.sql @@ -45,7 +45,7 @@ CREATE EXTENSION IF NOT EXISTS pgcrypto; -- allowing for customization to meet specific needs. DROP FUNCTION IF EXISTS nanoid(int, text, float); CREATE OR REPLACE FUNCTION nanoid( - size int DEFAULT 21, -- The number of symbols in the NanoId String. Must be greater than 0. + size int DEFAULT 24, -- The number of symbols in the NanoId String. Must be greater than 0. alphabet text DEFAULT '_-0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ', -- The symbols used in the NanoId String. Must contain between 1 and 255 symbols. additionalBytesFactor float DEFAULT 1.6 -- The additional bytes factor used for calculating the step size. Must be equal or greater then 1. ) diff --git a/migrations/2023-12-30-162032_playlists/up.sql b/migrations/2023-12-30-162032_playlists/up.sql index 6f496d7..3adc732 100644 --- a/migrations/2023-12-30-162032_playlists/up.sql +++ b/migrations/2023-12-30-162032_playlists/up.sql @@ -1,10 +1,14 @@ +create type playlist_type as enum ('playlist', 'folder'); + create table if not exists playlists ( id varchar(24) default nanoid(24), name varchar(255) not null, public bool default false not null, + type playlist_type default 'playlist' not null, creator_id varchar(24) not null, + parent_id varchar(24) references playlists (id) on delete set null, created_at timestamp default now(), updated_at timestamp, diff --git a/migrations/2023-12-30-214938_playlists_tracks/up.sql b/migrations/2023-12-30-214938_playlists_tracks/up.sql index 927a236..bfe4f82 100644 --- a/migrations/2023-12-30-214938_playlists_tracks/up.sql +++ b/migrations/2023-12-30-214938_playlists_tracks/up.sql @@ -2,5 +2,6 @@ create table if not exists playlists_tracks ( playlist_id varchar(24) references playlists (id) on delete cascade, track_id varchar(24) references tracks (id) on delete cascade, + added_at timestamp default now() not null, primary key (playlist_id, track_id) ); diff --git a/src/main.rs b/src/main.rs index abc0a34..d3f1dc8 100644 --- a/src/main.rs +++ b/src/main.rs @@ -12,10 +12,10 @@ use crate::helpers::db; #[actix_web::main] async fn main() -> std::io::Result<()> { - use actix_web::{web, App, HttpServer}; + use actix_web::{App, HttpServer}; dotenvy::dotenv().expect("No .env file found"); - std::env::set_var("RUST_LOG", "debug"); + env_logger::init(); // Register services. let _ = services::spotify::instance().await; @@ -24,9 +24,11 @@ async fn main() -> std::io::Result<()> { HttpServer::new(move || { App::new() - .service(web::scope("/playlists").service(routes::playlists::get_playlist)) .service(routes::auth::routes()) + // .wrap(middlewares::auth::auth()) + .service(routes::playlists::routes()) .service(routes::me::routes()) + .service(routes::import::routes()) .service(routes::users::routes()) .service(routes::search::routes()) }) diff --git a/src/middlewares/user.rs b/src/middlewares/user.rs index 84c229f..5aafd9f 100644 --- a/src/middlewares/user.rs +++ b/src/middlewares/user.rs @@ -1,6 +1,6 @@ use crate::helpers::jwt::get_token; use crate::middlewares::error::ErrorResponse; -use crate::models::user::Users; +use crate::models::users::Users; use actix_web::http::{header, StatusCode}; use actix_web::HttpRequest; @@ -36,3 +36,12 @@ pub fn get_user(req: HttpRequest) -> Result { )), } } + +pub fn get_user_option(req: HttpRequest) -> Option { + let user = get_user(req); + + match user { + Ok(user) => Some(user), + Err(_) => None, + } +} diff --git a/src/models/artists.rs b/src/models/artists.rs index d931681..6605cb6 100644 --- a/src/models/artists.rs +++ b/src/models/artists.rs @@ -1,16 +1,13 @@ use crate::helpers::db; use crate::schema::{artists, artists_tracks}; use chrono::NaiveDateTime; +use diesel::prelude::*; use diesel::result::Error; -use diesel::{ - AsChangeset, ExpressionMethods, Insertable, QueryDsl, Queryable, RunQueryDsl, Selectable, -}; use serde::{Deserialize, Serialize}; -#[derive(AsChangeset, Insertable, Queryable, Selectable, Deserialize, Serialize)] -#[diesel(table_name = crate::schema::artists)] -#[diesel(check_for_backend(diesel::pg::Pg))] -pub struct Artist { +#[derive(AsChangeset, Identifiable, Insertable, Queryable, Selectable, Deserialize, Serialize)] +#[diesel(table_name = artists)] +pub struct Artists { pub id: String, pub name: String, pub slug: String, @@ -22,14 +19,11 @@ pub struct Artist { pub tidal_id: Option, } -#[derive(Debug, Deserialize, Queryable, Serialize)] -pub struct Artists { +#[derive(Debug, Insertable, Deserialize, Serialize)] +#[diesel(table_name = artists)] +pub struct NewArtist { pub id: String, - pub title: String, - pub slug: String, - - pub created_at: Option, - pub updated_at: Option, + pub name: String, pub spotify_id: Option, pub tidal_id: Option, @@ -38,14 +32,14 @@ pub struct Artists { impl Artists { pub fn find(id: String) -> Result { let conn = &mut db::connection()?; - let playlist = artists::table.filter(artists::id.eq(id)).first(conn)?; - Ok(playlist) + let artist = artists::table.filter(artists::id.eq(id)).first(conn)?; + Ok(artist) } - pub fn create(artist: Artist) -> Result { + pub fn create(artist: NewArtist) -> Result { let conn = &mut db::connection()?; let playlist = diesel::insert_into(artists::table) - .values(Artist::from(artist)) + .values(artist) .get_result(conn)?; Ok(playlist) } @@ -64,19 +58,3 @@ impl Artists { Ok(artists) } } - -impl Artist { - fn from(artist: Artist) -> Artist { - Artist { - id: artist.id, - name: artist.name, - slug: artist.slug, - - created_at: artist.created_at, - updated_at: artist.updated_at, - - spotify_id: artist.spotify_id, - tidal_id: artist.tidal_id, - } - } -} diff --git a/src/models/artists_tracks.rs b/src/models/artists_tracks.rs new file mode 100644 index 0000000..9fb94c7 --- /dev/null +++ b/src/models/artists_tracks.rs @@ -0,0 +1,52 @@ +use diesel::prelude::*; +use diesel::result::Error; +use serde::{Deserialize, Serialize}; + +use crate::helpers::db; + +use super::artists::Artists; +use super::tracks::Tracks; + +use crate::schema::{artists, artists_tracks, tracks}; + +#[derive(Identifiable, Selectable, Queryable, Associations, Debug)] +#[diesel(belongs_to(Artists, foreign_key = artist_id))] +#[diesel(belongs_to(Tracks, foreign_key = track_id))] +#[diesel(table_name = artists_tracks)] +#[diesel(primary_key(artist_id, track_id))] +pub struct ArtistTracks { + pub artist_id: String, + pub track_id: String, +} + +impl ArtistTracks { + pub fn get_tracks(artist: &Artists) -> Result, Error> { + let conn = &mut db::connection()?; + let track_ids = ArtistTracks::belonging_to(artist).select(artists_tracks::track_id); + + let tracks: Vec = tracks::table + .filter(tracks::id.eq_any(track_ids)) + .load::(conn)?; + + Ok(tracks) + } + + pub fn get_artists(track: &Tracks) -> Result, Error> { + let conn = &mut db::connection()?; + let artist_ids = ArtistTracks::belonging_to(track).select(artists_tracks::artist_id); + + let artists = artists::table + .select((artists::id, artists::name)) + .filter(artists::id.eq_any(artist_ids)) + .load::(conn)?; + + Ok(artists) + } +} + +#[derive(Debug, Deserialize, Serialize, Queryable)] +#[diesel(table_name = artists)] +pub struct TrackArtist { + pub id: String, + pub name: String, +} diff --git a/src/models/mod.rs b/src/models/mod.rs index 62087e1..eadd19f 100644 --- a/src/models/mod.rs +++ b/src/models/mod.rs @@ -1,5 +1,7 @@ pub mod artists; +pub mod artists_tracks; pub mod playlists; +pub mod playlists_tracks; pub mod spotify; pub mod tracks; -pub mod user; +pub mod users; diff --git a/src/models/playlists.rs b/src/models/playlists.rs index 22a7982..bdb05af 100644 --- a/src/models/playlists.rs +++ b/src/models/playlists.rs @@ -1,43 +1,43 @@ use crate::helpers::db; -use crate::models::tracks::{Tracks, TracksWithArtists}; -use crate::models::user::Users; -use crate::schema::playlists; +use crate::schema::{playlists, playlists_tracks}; use chrono::NaiveDateTime; -use diesel::result::Error; -use diesel::{ - AsChangeset, ExpressionMethods, Insertable, QueryDsl, Queryable, RunQueryDsl, Selectable, -}; +use diesel::{prelude::*, result::Error}; use serde::{Deserialize, Serialize}; -#[derive(Serialize, Deserialize)] +use super::artists_tracks::{ArtistTracks, TrackArtist}; +use super::{playlists_tracks::PlaylistTracks, tracks::Tracks, users::Users}; + +#[derive(Debug, Deserialize, Serialize)] pub struct PlaylistCreator { pub id: String, pub name: String, } -#[derive(AsChangeset, Insertable, Queryable, Selectable, Deserialize, Serialize)] -#[diesel(table_name = crate::schema::playlists)] -#[diesel(belongs_to(Users))] -#[diesel(check_for_backend(diesel::pg::Pg))] -pub struct Playlist { +#[derive( + AsChangeset, Debug, Deserialize, Identifiable, Queryable, Selectable, Serialize, PartialEq, +)] +#[diesel(table_name = playlists)] +pub struct Playlists { pub id: String, pub name: String, pub public: bool, + + pub playlist_type: String, + pub parent_id: Option, + pub creator_id: String, pub created_at: Option, pub updated_at: Option, } -#[derive(Debug, Deserialize, Queryable, Serialize)] -pub struct Playlists { - pub id: String, +#[derive(Debug, Deserialize, Insertable)] +#[diesel(table_name = playlists)] +pub struct NewPlaylist { pub name: String, pub public: bool, + // pub playlist_type: String, pub creator_id: String, - - pub created_at: Option, - pub updated_at: Option, } impl Playlists { @@ -59,23 +59,26 @@ impl Playlists { let conn = &mut db::connection()?; let mut playlists = playlists::table + .distinct() + .select(Playlists::as_select()) + .order((playlists::playlist_type.desc(), playlists::parent_id.desc())) .filter(playlists::creator_id.eq(user_id)) .into_boxed(); if filter_public { - playlists = playlists.filter(playlists::public.eq(true)); + playlists = playlists.filter( + playlists::public + .eq(true) + .and(playlists::playlist_type.ne("folder")), + ); } let playlists = playlists.get_results(conn)?; Ok(playlists) } - pub fn get_tracks(&self) -> Result, Error> { - let tracks = Tracks::find_by_playlist(&self.id)?; - let tracks: Vec = tracks - .into_iter() - .map(Tracks::with_artists) - .collect::, _>>()?; + pub fn get_tracks(&self) -> Result, Error> { + let tracks: Vec = PlaylistTracks::get_tracks(self)?; Ok(tracks) } @@ -84,34 +87,69 @@ impl Playlists { let creator = Users::find(&self.creator_id)?; Ok(creator) } -} -#[derive(Debug, Insertable, Deserialize)] -#[diesel(table_name = playlists)] -pub struct NewPlaylist { - pub name: String, - pub public: bool, - pub creator_id: Option, -} - -impl Playlist { - pub fn create(name: &str, public: bool, creator_id: &str) -> NewPlaylist { - NewPlaylist { - name: name.to_string(), - public, - creator_id: Some(creator_id.to_string()), + pub fn get_data(&self, tracks: &[PlaylistTrack]) -> (usize, usize) { + if tracks.is_empty() { + return (0, 0); } + + let duration = tracks + .iter() + .map(|track| track.duration_ms) + .reduce(|a, b| a + b) + .unwrap() as usize; + + (duration, tracks.len()) } - fn from(playlist: Playlist) -> Playlist { - Playlist { - id: playlist.id, - name: playlist.name, - public: playlist.public, - creator_id: playlist.creator_id, + pub fn can_see(&self, creator: Option) -> bool { + self.public || creator.map_or(false, |user| user.id == self.creator_id) + } +} - created_at: playlist.created_at, - updated_at: playlist.updated_at, +// // Folder +// struct Folder { +// id: String, +// name: String, +// playlists: Vec, +// } + +// impl Folder { +// pub fn get_playlists(&self) -> Result, Error> { +// let playlists = playlists::table +// } +// } + +#[derive(Deserialize, Serialize)] +pub struct PlaylistTrack { + pub id: String, + pub title: String, + pub duration_ms: i32, + + pub artists: Vec, + pub added_at: NaiveDateTime, + pub spotify_id: Option, + pub tidal_id: Option, +} + +impl From for PlaylistTrack { + fn from(track: Tracks) -> Self { + let artists = ArtistTracks::get_artists(&track).unwrap(); + + let added_at = playlists_tracks::table + .filter(playlists_tracks::track_id.eq(&track.id)) + .select(playlists_tracks::added_at) + .first::(&mut db::connection().unwrap()) + .unwrap(); + + PlaylistTrack { + id: track.id, + title: track.title, + duration_ms: track.duration_ms, + artists, + added_at, + spotify_id: track.spotify_id, + tidal_id: track.tidal_id, } } } diff --git a/src/models/playlists_tracks.rs b/src/models/playlists_tracks.rs new file mode 100644 index 0000000..bc98b23 --- /dev/null +++ b/src/models/playlists_tracks.rs @@ -0,0 +1,38 @@ +use diesel::prelude::*; +use diesel::result::Error; + +use crate::helpers::db; + +use super::playlists::{PlaylistTrack, Playlists}; +use super::tracks::Tracks; + +use crate::schema::{playlists_tracks, tracks}; + +#[derive(Identifiable, Selectable, Queryable, Associations, Debug)] +#[diesel(belongs_to(Playlists, foreign_key = playlist_id))] +#[diesel(belongs_to(Tracks, foreign_key = track_id))] +#[diesel(table_name = playlists_tracks)] +#[diesel(primary_key(playlist_id, track_id))] +pub struct PlaylistTracks { + pub playlist_id: String, + pub track_id: String, + pub added_at: String, +} + +impl PlaylistTracks { + pub fn get_tracks(playlist: &Playlists) -> Result, Error> { + let conn = &mut db::connection()?; + let track_ids = PlaylistTracks::belonging_to(playlist).select(playlists_tracks::track_id); + + let tracks: Vec = tracks::table + .filter(tracks::id.eq_any(track_ids)) + .load::(conn)?; + + let tracks = tracks + .into_iter() + .map(PlaylistTrack::from) + .collect::>(); + + Ok(tracks) + } +} diff --git a/src/models/tracks.rs b/src/models/tracks.rs index aa4e0bd..21507c3 100644 --- a/src/models/tracks.rs +++ b/src/models/tracks.rs @@ -1,18 +1,15 @@ use crate::helpers::db; use crate::models::{artists::Artists, spotify}; -use crate::schema::tracks; +use crate::schema::{playlists_tracks, tracks}; use chrono::NaiveDateTime; +use diesel::prelude::*; use diesel::result::Error; -use diesel::{ - AsChangeset, ExpressionMethods, Insertable, QueryDsl, Queryable, RunQueryDsl, Selectable, -}; use serde::{Deserialize, Serialize}; -use crate::schema::playlists_tracks; - -#[derive(AsChangeset, Clone, Insertable, Queryable, Selectable, Deserialize, Serialize)] -#[diesel(table_name = crate::schema::tracks)] -#[diesel(check_for_backend(diesel::pg::Pg))] +#[derive( + AsChangeset, Debug, Deserialize, Serialize, Queryable, Identifiable, Selectable, Clone, +)] +#[diesel(table_name = tracks)] pub struct Tracks { pub id: String, pub title: String, @@ -25,6 +22,17 @@ pub struct Tracks { pub tidal_id: Option, } +#[derive(AsChangeset, Clone, Debug, Deserialize, Insertable)] +#[diesel(table_name = tracks)] +pub struct NewTrack { + pub id: String, + pub title: String, + pub duration_ms: i32, + + pub spotify_id: Option, + pub tidal_id: Option, +} + impl Tracks { pub fn find(id: String) -> Result { let conn = &mut db::connection()?; @@ -32,29 +40,15 @@ impl Tracks { Ok(playlist) } - pub fn create(track: Tracks) -> Result { + pub fn create(track: NewTrack) -> Result { let conn = &mut db::connection()?; let playlist = diesel::insert_into(tracks::table) - .values(Tracks::from(track)) + .values(track) .get_result(conn)?; Ok(playlist) } - pub fn find_by_playlist(playlist_id: &str) -> Result, Error> { - let conn = &mut db::connection()?; - let tracks: Vec<(String, String)> = playlists_tracks::table - .filter(playlists_tracks::playlist_id.eq(playlist_id)) - .get_results::<(String, String)>(conn)?; - - let tracks = tracks - .into_iter() - .map(|(_, track_id)| Tracks::find(track_id).unwrap()) - .collect::>(); - - Ok(tracks) - } - - pub fn create_or_update(track: Tracks) -> Result { + pub fn create_or_update(track: NewTrack) -> Result { let conn = &mut db::connection()?; let playlist = diesel::insert_into(tracks::table) .values(track.clone()) @@ -70,20 +64,6 @@ impl Tracks { Ok(artists) } - fn from(track: Tracks) -> Self { - Tracks { - id: track.id, - title: track.title, - duration_ms: track.duration_ms, - - created_at: track.created_at, - updated_at: track.updated_at, - - spotify_id: track.spotify_id, - tidal_id: track.tidal_id, - } - } - pub fn with_artists(track: Tracks) -> Result { let artists = track.get_artists()?; diff --git a/src/models/user.rs b/src/models/users.rs similarity index 60% rename from src/models/user.rs rename to src/models/users.rs index 99d242c..33c1427 100644 --- a/src/models/user.rs +++ b/src/models/users.rs @@ -11,18 +11,6 @@ use serde::{Deserialize, Serialize}; #[derive(AsChangeset, Insertable, Queryable, Selectable, Deserialize, Serialize)] #[diesel(table_name = crate::schema::users)] #[diesel(check_for_backend(diesel::pg::Pg))] -pub struct User { - pub id: String, - pub name: String, - pub email: String, - - pub password: String, - - pub created_at: Option, - pub updated_at: Option, -} - -#[derive(Debug, Deserialize, Queryable, Serialize)] pub struct Users { pub id: String, pub name: String, @@ -41,14 +29,6 @@ impl Users { Ok(user) } - pub fn create(user: User) -> Result { - let conn = &mut db::connection()?; - let user = diesel::insert_into(users::table) - .values(User::from(user)) - .get_result(conn)?; - Ok(user) - } - pub fn find_by_email(email: &str) -> Result { let conn = &mut db::connection()?; let user = users::table.filter(users::email.eq(email)).first(conn)?; @@ -59,16 +39,3 @@ impl Users { bcrypt::verify(password, hash) } } - -impl User { - fn from(user: User) -> User { - User { - id: user.id, - name: user.name, - email: user.email, - password: user.password, - created_at: user.created_at, - updated_at: user.updated_at, - } - } -} diff --git a/src/routes/auth.rs b/src/routes/auth.rs index f376a69..00d0739 100644 --- a/src/routes/auth.rs +++ b/src/routes/auth.rs @@ -1,6 +1,6 @@ use crate::helpers::jwt::get_encoding_key; use crate::middlewares::error::ErrorResponse; -use crate::models::user::Users; +use crate::models::users::Users; use actix_web::http::StatusCode; use actix_web::{post, web, HttpResponse, Scope}; use chrono::{Days, Utc}; @@ -17,7 +17,7 @@ pub struct JWTClaims { pub exp: i64, } -#[post("/auth/login")] +#[post("/login")] async fn login(body: web::Json) -> Result { #[derive(Deserialize, Serialize)] struct Response { diff --git a/src/routes/me.rs b/src/routes/me.rs index 4a63bf3..660c772 100644 --- a/src/routes/me.rs +++ b/src/routes/me.rs @@ -1,6 +1,7 @@ use crate::middlewares::error::ErrorResponse; use crate::middlewares::user::get_user; -use crate::models::playlists::{NewPlaylist, Playlists}; +use crate::models::playlists::{NewPlaylist, PlaylistCreator, Playlists}; +use crate::routes::playlists::GetPlaylistResponse; use actix_web::{get, post, web, HttpRequest, HttpResponse, Scope}; use serde::Serialize; @@ -33,14 +34,32 @@ async fn me(req: HttpRequest) -> Result { async fn me_playlists(req: HttpRequest) -> Result { let user = get_user(req)?; - #[derive(Serialize)] - struct Response { - playlists: Vec, - } + let playlists = Playlists::find_for_user(&user.id, false)?; + let playlists = playlists + .into_iter() + .map(|playlist| { + let creator = playlist.get_creator().unwrap(); - Ok(HttpResponse::Ok().json(Response { - playlists: Playlists::find_for_user(&user.id, false)?, - })) + let tracks = playlist.get_tracks().unwrap(); + let (duration, tracks_count) = playlist.get_data(&tracks); + + GetPlaylistResponse { + id: playlist.id.to_string(), + name: playlist.name.to_string(), + playlist_type: playlist.playlist_type.to_string(), + parent_id: playlist.parent_id, + creator: PlaylistCreator { + id: creator.id, + name: creator.name, + }, + tracks, + tracks_count, + duration, + } + }) + .collect::>(); + + Ok(HttpResponse::Ok().json(playlists)) } #[post("/playlists")] @@ -50,7 +69,7 @@ async fn create_playlist( ) -> Result { let user = get_user(req)?; - playlist.creator_id = Option::from(user.id); + playlist.creator_id = user.id; Playlists::create(playlist.into_inner())?; diff --git a/src/routes/playlists.rs b/src/routes/playlists.rs index d49aed9..4106c97 100644 --- a/src/routes/playlists.rs +++ b/src/routes/playlists.rs @@ -1,7 +1,8 @@ use crate::middlewares::error::ErrorResponse; -use crate::models::playlists::{PlaylistCreator, Playlists}; -use crate::models::tracks::TracksWithArtists; -use actix_web::{get, web, HttpResponse, Scope}; +use crate::middlewares::user::get_user_option; +use crate::models::playlists::{PlaylistCreator, PlaylistTrack, Playlists}; +use actix_web::{get, web, HttpRequest, HttpResponse, Scope}; +use reqwest::StatusCode; use serde::{Deserialize, Serialize}; pub fn routes() -> Scope { @@ -9,33 +10,42 @@ pub fn routes() -> Scope { } #[derive(Deserialize, Serialize)] -struct GetPlaylistResponse { +pub struct GetPlaylistResponse { pub id: String, pub name: String, + pub playlist_type: String, + pub parent_id: Option, pub creator: PlaylistCreator, - pub tracks: Vec, + pub tracks: Vec, pub tracks_count: usize, pub duration: usize, } #[get("/{playlist_id}")] -async fn get_playlist(path: web::Path) -> Result { +async fn get_playlist( + req: HttpRequest, + path: web::Path, +) -> Result { let playlist_id = path.into_inner(); let playlist = Playlists::find(playlist_id.as_str())?; + if !playlist.can_see(get_user_option(req)) { + return Err(ErrorResponse::new( + "Playlist not found", + StatusCode::NOT_FOUND, + )); + } + let creator = playlist.get_creator()?; let tracks = playlist.get_tracks()?; - let tracks_count = tracks.len(); - let duration = tracks - .iter() - .map(|track| track.duration_ms) - .reduce(|a, b| a + b) - .unwrap() as usize; + let (duration, tracks_count) = playlist.get_data(&tracks); Ok(HttpResponse::Ok().json(GetPlaylistResponse { id: playlist.id.to_string(), name: playlist.name.to_string(), + playlist_type: playlist.playlist_type.to_string(), + parent_id: playlist.parent_id, creator: PlaylistCreator { id: creator.id, name: creator.name, diff --git a/src/routes/users.rs b/src/routes/users.rs index 85fa1d7..ee1db79 100644 --- a/src/routes/users.rs +++ b/src/routes/users.rs @@ -1,6 +1,9 @@ use crate::middlewares::error::ErrorResponse; -use crate::models::playlists::Playlists; -use crate::models::user::Users; +use crate::models::{ + playlists::{PlaylistCreator, Playlists}, + users::Users, +}; +use crate::routes::playlists::GetPlaylistResponse; use actix_web::http::StatusCode; use actix_web::{get, web, HttpResponse, Result, Scope}; use diesel::result::Error as DBError; @@ -49,6 +52,29 @@ async fn get_user_playlists(path: web::Path) -> Result>(); Ok(HttpResponse::Ok().json(playlists)) } diff --git a/src/schema.rs b/src/schema.rs index 17fb37c..ebc6b6b 100644 --- a/src/schema.rs +++ b/src/schema.rs @@ -33,6 +33,10 @@ diesel::table! { name -> Varchar, public -> Bool, #[max_length = 24] + playlist_type -> Varchar, + #[max_length = 24] + parent_id -> Nullable, + #[max_length = 24] creator_id -> Varchar, created_at -> Nullable, updated_at -> Nullable, @@ -45,6 +49,7 @@ diesel::table! { playlist_id -> Varchar, #[max_length = 24] track_id -> Varchar, + added_at -> Timestamp, } }