From 90a4cfacbb64750a6779995e91509588f78e9802 Mon Sep 17 00:00:00 2001 From: Dennis Kobert Date: Wed, 17 Feb 2021 21:22:58 +0100 Subject: Add lobby concept --- src/database.rs | 98 ++++++++++++++++++++++++++++++++++++++++----------------- src/errors.rs | 8 ++--- src/main.rs | 10 ++++-- src/serve.rs | 31 +++++++++++------- src/spotify.rs | 45 ++++++++++++++------------ 5 files changed, 126 insertions(+), 66 deletions(-) (limited to 'src') diff --git a/src/database.rs b/src/database.rs index 94a07d6..b2cd323 100644 --- a/src/database.rs +++ b/src/database.rs @@ -1,11 +1,15 @@ use crate::errors::Error; use postgres::{Client, NoTls}; +use rand::prelude::*; use std::sync::{Arc, Mutex}; lazy_static! { static ref CLIENT: Arc> = Arc::new(Mutex::new( - Client::connect("host=track_db user=postgres password=example", NoTls) - .expect("failed to connect to database") + Client::connect( + "host=127.0.0.1 user=spotify_intersect password=example dbname=track_db", + NoTls + ) + .expect("failed to connect to database") )); } @@ -14,15 +18,22 @@ pub fn initialize_db() -> Result<(), Error> { client.batch_execute( r#" CREATE TABLE IF NOT EXISTS track ( - track_id SERIAL PRIMARY KEY, + track_id SERIAL PRIMARY KEY, track_code TEXT NOT NULL UNIQUE, name TEXT NOT NULL, artist TEXT NOT NULL, - popularity int DEFAULT 50 + popularity int DEFAULT 50 + ); + CREATE TABLE IF NOT EXISTS lobby ( + lobby_id SERIAL PRIMARY KEY, + token TEXT NOT NULL UNIQUE, + lobby_name TEXT NOT NULL ); CREATE TABLE IF NOT EXISTS suser ( user_id SERIAL PRIMARY KEY, - user_name TEXT NOT NULL UNIQUE + user_name TEXT NOT NULL, + lobby_id int REFERENCES lobby (lobby_id) ON UPDATE CASCADE ON DELETE CASCADE, + CONSTRAINT suser_name_lobby UNIQUE (lobby_id, user_name) ); CREATE TABLE IF NOT EXISTS user_track_raw ( track_id int REFERENCES track (track_id) ON UPDATE CASCADE ON DELETE CASCADE, @@ -44,7 +55,7 @@ pub fn initialize_db() -> Result<(), Error> { Ok(()) } -use rspotify::spotify::model::track::FullTrack; +use rspotify::model::track::FullTrack; pub fn insert_track(user_id: i32, track: FullTrack, weight: i32) -> Result<(), Error> { let mut client = CLIENT.lock()?; @@ -54,8 +65,8 @@ pub fn insert_track(user_id: i32, track: FullTrack, weight: i32) -> Result<(), E } print!(" {} ", track.id.clone()?); client.execute( - "INSERT INTO track (track_code, name, artist, popularity) - VALUES ($1, $2, $3, $4) + "INSERT INTO track (track_code, name, artist, popularity) + VALUES ($1, $2, $3, $4) ON CONFLICT DO NOTHING", &[ &(track.id.clone()?), @@ -72,10 +83,10 @@ pub fn insert_track(user_id: i32, track: FullTrack, weight: i32) -> Result<(), E println!("uid: {} tid: {}", user_id, track_id); client.execute( " - INSERT INTO user_track_raw (track_id, user_id, count) - VALUES ($1, $2, $3) - ON CONFLICT - ON CONSTRAINT track_user_pkey + INSERT INTO user_track_raw (track_id, user_id, count) + VALUES ($1, $2, $3) + ON CONFLICT + ON CONSTRAINT track_user_pkey DO NOTHING; ", &[&track_id, &user_id, &0], @@ -87,36 +98,45 @@ pub fn insert_track(user_id: i32, track: FullTrack, weight: i32) -> Result<(), E Ok(()) } -pub fn insert_user(name: &str) -> Result { +pub fn insert_user(name: &str, lobby: &str) -> Result { let mut client = CLIENT.lock()?; + let x = get_lid(lobby, &mut *client)?; client.execute( - "INSERT INTO suser (user_name) VALUES ($1) ON CONFLICT (user_name) DO NOTHING;", - &[&name], + "INSERT INTO suser (user_name, lobby_id) VALUES ($1, $2) ON CONFLICT (user_name, lobby_id) DO NOTHING;", + &[&name, &x], )?; - let db_user_id = get_uid(name, &mut *client)?; + let db_user_id = get_uid(name, lobby, &mut *client)?; client.execute("DELETE FROM user_track WHERE user_id = $1;", &[&db_user_id])?; Ok(db_user_id) } -fn get_uid(name: &str, client: &mut postgres::Client) -> Result { +fn get_lid(lobby: &str, client: &mut postgres::Client) -> Result { let x: i32 = client - .query_one("SELECT user_id FROM suser where user_name = $1;", &[&name])? + .query_one("SELECT lobby_id FROM lobby WHERE token = $1;", &[&lobby])? .get(0); Ok(x) } -pub fn match_users(name1: String, name2: String) -> Result { +fn get_uid(name: &str, lobby: &str, client: &mut postgres::Client) -> Result { + let x: i32 = client + .query_one("SELECT user_id FROM suser JOIN lobby USING (lobby_id) WHERE user_name = $1 AND token = $2;", &[&name, &lobby])? + .get(0); + Ok(x) +} + +pub fn match_users(lobby: String, names: &[&str]) -> Result { let mut client = CLIENT.lock()?; let mut songs = String::new(); + let names: Vec = names.iter().map(|x| x.to_string()).collect(); + let users = names + .iter() + .fold(String::new(), |a, name| format!("{}, ({})", a, name)); + let users: String = users.chars().skip(2).collect(); for row in client.query( " WITH users AS ( SELECT * - FROM ( VALUES - - ($1), ($2) - - ) AS _ (user_id) + FROM ( VALUES $1 AS _ (user_id) ) SELECT track_id, name, artist FROM track @@ -125,14 +145,15 @@ pub fn match_users(name1: String, name2: String) -> Result { FROM user_track JOIN suser USING (user_id) JOIN track USING (track_id) - WHERE suser.user_name IN (SELECT * FROM users) + JOIN lobby USING (lobby_id) + WHERE suser.user_name IN (SELECT * FROM users) AND track = $2 GROUP BY track_id HAVING COUNT(track_id) = (SELECT COUNT(*) FROM users) ORDER BY SUM(score) DESC ) AS _ USING (track_id) ; ", - &[&name1.as_str(), &name2.as_str()], + &[&users, &lobby], )? { let name: String = row.get(1); let artist: String = row.get(2); @@ -141,12 +162,33 @@ pub fn match_users(name1: String, name2: String) -> Result { Ok(songs) } -pub fn get_users() -> Result { +pub fn get_users(lobby: &str) -> Result { let mut client = CLIENT.lock()?; let mut users = String::new(); - for row in client.query("SELECT user_name FROM suser", &[])? { + for row in client.query( + "SELECT user_name FROM suser JOIN lobby USING (lobby_id) WHERE token = $1", + &[&lobby], + )? { let user: String = row.get(0); users = format!("{}{}\n", users, user); } Ok(users) } + +pub fn create_lobby(name: &str) -> Result { + let mut client = CLIENT.lock()?; + let mut token = String::new(); + while token.is_empty() + || client + .query_one("SELECT lobby_id FROM lobby WHERE token = $1", &[&token]) + .is_ok() + { + let rand: [u8; 20] = rand::thread_rng().gen(); + token = base64::encode_config(&rand, base64::URL_SAFE); + } + client.execute( + "INSERT INTO lobby (token, lobby_name) VALUES ($1, $2);", + &[&token, &name], + )?; + Ok(token) +} diff --git a/src/errors.rs b/src/errors.rs index 8fb2793..544c391 100644 --- a/src/errors.rs +++ b/src/errors.rs @@ -1,8 +1,8 @@ use rocket::http::ContentType; use rocket::request::Request; use rocket::response::{self, Responder, Response}; -use rspotify::spotify::client::ApiError; -use rspotify::spotify::oauth2::SpotifyOAuth; +use rspotify::client::ApiError; +use rspotify::oauth2::SpotifyOAuth; use std::collections::HashMap; use std::io::Cursor; use std::sync::{MutexGuard, PoisonError}; @@ -49,7 +49,7 @@ impl<'a> From for Error { Error::Misc(format!("tried to unwrap none at: {:?}", error)) } } -impl<'a> Responder<'a> for Error { +impl<'a> Responder<'a, 'a> for Error { fn respond_to(self, _: &Request) -> response::Result<'a> { let response = match self { Error::Postgres(e) => format!("DB Error: {:?}", e), @@ -59,7 +59,7 @@ impl<'a> Responder<'a> for Error { Response::build() .header(ContentType::Plain) .status(rocket::http::Status::raw(500)) - .sized_body(Cursor::new(response)) + .sized_body(response.len(), Cursor::new(response)) .ok() } } diff --git a/src/main.rs b/src/main.rs index 9dd4551..eb5af4c 100644 --- a/src/main.rs +++ b/src/main.rs @@ -10,7 +10,8 @@ mod errors; mod serve; mod spotify; -fn main() { +#[rocket::main] +async fn main() { database::initialize_db().expect("failed to initialize_db"); println!("connected with db"); rocket::ignite() @@ -20,8 +21,11 @@ fn main() { serve::token, serve::get_tracks, serve::match_users, - serve::get_users + serve::get_users, + serve::create_lobby ], ) - .launch(); + .launch() + .await + .unwrap(); } diff --git a/src/serve.rs b/src/serve.rs index 53a3cac..ee0e5ff 100644 --- a/src/serve.rs +++ b/src/serve.rs @@ -3,11 +3,11 @@ use crate::errors::Error; use crate::spotify; use rocket::response::Redirect; -#[get("/callback//")] -pub fn get_tracks(name: String, url: String) -> Result<(), Error> { - let (spotify_uid, spotify_client) = spotify::auth_user(name.as_ref(), url)?; - let uid = database::insert_user(spotify_uid.as_ref())?; - spotify::load_profile(uid, spotify_uid.as_ref(), spotify_client) +#[get("/callback///")] +pub async fn get_tracks(name: String, lobby: String, url: String) -> Result<(), Error> { + let (spotify_uid, spotify_client) = spotify::auth_user(name.as_ref(), url).await?; + let uid = database::insert_user(spotify_uid.as_ref(), lobby.as_ref())?; + spotify::load_profile(uid, spotify_uid.as_ref(), spotify_client).await } #[get("/token/")] @@ -15,12 +15,21 @@ pub fn token(name: String) -> Result { Ok(Redirect::to(spotify::token(name)?)) } -#[get("/match//")] -pub fn match_users(name1: String, name2: String) -> Result { - database::match_users(name1, name2) +#[get("/match//")] +pub fn match_users(lobby: String, names: String) -> Result { + //let bytes = base64::decode_config(names, base64::URL_SAFE).unwrap(); + //let names = String::from_utf8(bytes).unwrap(); + let names: Vec<&str> = names.split(',').collect(); + database::match_users(lobby, names.as_slice()) } -#[get("/user")] -pub fn get_users() -> Result { - database::get_users() +#[get("/users/")] +pub fn get_users(lobby: String) -> Result { + database::get_users(lobby.as_str()) +} + +//#[post("/lobby", format = "application/json", data = "")] +#[post("/lobby/")] +pub fn create_lobby(name: String) -> Result { + database::create_lobby(name.as_str()) } diff --git a/src/spotify.rs b/src/spotify.rs index 596dec3..0e18c43 100644 --- a/src/spotify.rs +++ b/src/spotify.rs @@ -1,9 +1,13 @@ use crate::database; use crate::errors::Error; use lazy_static::lazy_static; -use rspotify::spotify::client::{ApiError, Spotify}; -use rspotify::spotify::oauth2::{SpotifyClientCredentials, SpotifyOAuth}; -use rspotify::spotify::util::process_token; +use rspotify::client::{ApiError, Spotify}; +use rspotify::model::page::Page; +use rspotify::model::playlist::*; +use rspotify::model::track::*; +use rspotify::oauth2::{SpotifyClientCredentials, SpotifyOAuth}; +use rspotify::senum::TimeRange; +use rspotify::util::process_token; use std::collections::HashMap; use std::sync::{Arc, Mutex}; @@ -19,13 +23,14 @@ macro_rules! get_items { $index = 0; let mut result: Vec<$t> = Vec::new(); loop { - match $spotify_call { - Ok(mut items) => { + let res: Result, failure::Error> = $spotify_call.await; + match res { + Ok(mut page) => { $index += CHUNK_SIZE; - if items.items.is_empty() { + if page.items.is_empty() { break; } - result.append(&mut items.items); + result.append(&mut page.items); } Err(e) => match e.downcast::() { Ok(ApiError::RateLimited(x)) => { @@ -43,10 +48,7 @@ macro_rules! get_items { }}; } -use rspotify::spotify::model::playlist::*; -use rspotify::spotify::model::track::*; -use rspotify::spotify::senum::TimeRange; -pub fn load_profile(db_uid: i32, spotify_uid: &str, spotify: Spotify) -> Result<(), Error> { +pub async fn load_profile(db_uid: i32, spotify_uid: &str, spotify: Spotify) -> Result<(), Error> { let mut index; let playlists = get_items!( SimplifiedPlaylist, @@ -87,8 +89,8 @@ pub fn load_profile(db_uid: i32, spotify_uid: &str, spotify: Spotify) -> Result< None, ) ); - for track in tracks { - if let Err(e) = database::insert_track(db_uid, track.track, 1) { + for track in tracks.iter().map(|x| x.track.clone()).flatten() { + if let Err(e) = database::insert_track(db_uid, track, 1) { println!("failed to load track to db: {:?}", e) }; } @@ -96,13 +98,16 @@ pub fn load_profile(db_uid: i32, spotify_uid: &str, spotify: Spotify) -> Result< Ok(()) } -pub fn auth_user(name: &str, url: String) -> Result<(String, Spotify), Error> { - let mut guard = CACHE.lock()?; - let mut oauth = guard.remove(name)?; +pub async fn auth_user(name: &str, url: String) -> Result<(String, Spotify), Error> { + let mut oauth = { + let mut guard = CACHE.lock()?; + guard.remove(name)? + }; println!("auth: {:?} url: {}", oauth, url); - let token_info = process_token(&mut oauth, &mut ("?code=".to_owned() + url.as_ref())); + let mut token_string = format!("?code={}", url); + let token_info = process_token(&mut oauth, &mut token_string); let client_credential = SpotifyClientCredentials::default() - .token_info(token_info?) + .token_info(token_info.await?) .build(); let spotify = Spotify::default() @@ -110,14 +115,14 @@ pub fn auth_user(name: &str, url: String) -> Result<(String, Spotify), Error> { .build(); let user_id = spotify .current_user() + .await .map_err(|e| format!("failed to load currentuser {:?}", e))? .id; Ok((user_id, spotify)) } -#[get("/token/")] pub fn token(name: String) -> Result { - let state = rspotify::spotify::util::generate_random_string(16); + let state = rspotify::util::generate_random_string(16); let oauth = SpotifyOAuth::default(); let oauth = oauth .scope("playlist-read-private, playlist-read-collaborative, user-read-private, user-follow-read, user-library-read") -- cgit v1.2.3-70-g09d2