use lazy_static::lazy_static; use rocket::http::Status; use rocket::response::{status, Redirect}; use rspotify::spotify::client::{ApiError, Spotify}; use rspotify::spotify::oauth2::{SpotifyClientCredentials, SpotifyOAuth}; use rspotify::spotify::util::process_token; use std::collections::HashMap; use std::sync::{Arc, Mutex}; lazy_static! { static ref CACHE: Arc>> = Arc::new(Mutex::new(HashMap::new())); } #[get("/callback//")] pub fn get_tracks(name: String, url: String) -> Result<(), status::Custom> { let mut guard = CACHE.lock().or(Err(status::Custom( Status::InternalServerError, String::from("failed to lock cache mutex"), )))?; let mut oauth = guard.remove(&name).ok_or(status::Custom( Status::NotFound, String::from("uuid not found in cache"), ))?; println!("auth: {:?} url: {}", oauth, url); let token_info = process_token(&mut oauth, &mut ("?code=".to_owned() + url.as_ref())); let client_credential = SpotifyClientCredentials::default() .token_info(token_info.unwrap()) .build(); let spotify = Spotify::default() .client_credentials_manager(client_credential) .build(); let user_id = spotify.current_user().unwrap().id; let mut client = crate::CLIENT.lock().or(Err(status::Custom( Status::InternalServerError, String::from("failed to lock cache mutex"), )))?; client .execute( "INSERT INTO suser (user_name) VALUES ($1) ON CONFLICT (user_name) DO NOTHING;", &[&user_id], ) .unwrap(); let uid = get_uid(user_id.as_ref(), &mut client).ok_or(status::Custom( Status::NotFound, format!("username {} not found", user_id), ))?; client .execute("DELETE FROM user_track WHERE user_id = $1;", &[&uid]) .unwrap(); drop(client); let chunk_size = 50; let mut playlist_index = 0; loop { match spotify.user_playlists(user_id.as_ref(), Some(chunk_size), Some(playlist_index)) { Ok(playlists) => { playlist_index += chunk_size; if playlists.items.is_empty() { break; } for playlist in playlists.items { println!("playlist: {:?}", playlist.name); let mut track_index = 0; loop { match spotify.user_playlist_tracks( user_id.as_ref(), &playlist.id, None, Some(chunk_size), Some(track_index), None, ) { Ok(tracks) => { track_index += chunk_size; if tracks.items.is_empty() { break; } for track in tracks.items { //println!("{:?}", track.track.name); let mut client = crate::CLIENT.lock().or(Err( status::Custom(Status::InternalServerError, String::new()), ))?; if track.track.id.is_none() { println!("{:#?}", track); continue; } print!(" {} ", track.track.id.clone().unwrap()); client .execute( "INSERT INTO track (track_code, name, artist, popularity) VALUES ($1, $2, $3, $4) ON CONFLICT DO NOTHING ", &[&(track.track.id.clone().unwrap()), &track.track.name, &track.track.artists[0].name, &(track.track.popularity as i32)], ) .unwrap(); let tid: i32 = client .query( "SELECT track_id FROM track where track_code = $1;", &[&(track.track.id.clone().unwrap())], ) .unwrap()[0] .get(0); println!("uid: {} tid: {}", uid, tid); client .execute( "INSERT INTO user_track (track_id, user_id, count) VALUES ($1, $2, $3) ON CONFLICT ON CONSTRAINT track_user_pkey DO NOTHING;", &[&tid, &uid, &0], ) .unwrap(); client .execute( "UPDATE user_track SET count = count + 1 WHERE track_id = $1 AND user_id = $2;", &[&tid, &uid], ) .unwrap(); } } Err(e) => match e.downcast::() { Ok(ApiError::RateLimited(x)) => std::thread::sleep( std::time::Duration::from_secs(x.unwrap_or(5) as u64), ), cause => { println!("Error: {:?}", cause); break; } }, } } } } Err(e) => match e.downcast::() { Ok(ApiError::RateLimited(x)) => { std::thread::sleep(std::time::Duration::from_secs(x.unwrap_or(5) as u64)) } cause => { println!("Error: {:?}", cause); break; } }, } } Ok(()) } #[get("/token/")] pub fn token(name: String) -> Result> { let state = rspotify::spotify::util::generate_random_string(16); let oauth = SpotifyOAuth::default(); //let callback = oauth.redirect_uri.clone(); let oauth = oauth .scope("playlist-read-private, playlist-read-collaborative, user-read-private, user-follow-read, user-library-read") //.redirect_uri(format!("{}/{}", callback, &state).as_ref()) .build(); let auth_url = oauth.get_authorize_url(Some(&state), None); match CACHE.lock() { Ok(mut guard) => { guard.insert(name, oauth); Ok(Redirect::to(auth_url)) } Err(_) => Err(status::Custom( Status::ImATeapot, "Internal Server Error".to_owned(), )), } } fn get_uid(name: &str, client: &mut postgres::Client) -> Option { match client.query("SELECT user_id FROM suser where user_name = $1;", &[&name]) { Ok(rows) => match rows.len() { 0 => None, x => { let x: i32 = rows[0].get(0); Some(x) } }, _ => None, } } #[get("/match//")] pub fn match_users(name1: String, name2: String) -> Result> { let mut client = crate::CLIENT.lock().unwrap(); let uid1 = get_uid(name1.as_ref(), &mut client) .ok_or(status::NotFound(format!("username {} not found", name1)))?; let uid2 = get_uid(name2.as_ref(), &mut client) .ok_or(status::NotFound(format!("username {} not found", name2)))?; let mut songs = String::new(); for row in client .query( " WITH users AS ( SELECT * FROM ( VALUES ($1), ($2) ) AS _ (user_name) ) --SELECT track_id, SUM(score) / (SELECT COUNT(*) FROM users) AS score SELECT track_id, name, artist FROM track JOIN ( SELECT track_id FROM user_track JOIN suser USING (user_id) JOIN track USING (track_id) WHERE suser.user_name IN (SELECT * FROM users) GROUP BY track_id HAVING COUNT(track_id) = (SELECT COUNT(*) FROM users) ORDER BY SUM(score) DESC ) AS _ USING (track_id) ; ", &[name1.as_ref(), name2.as_ref()], ) .unwrap() { //let song = row.get(0); println!("{:?}", row.columns()); let name: String = row.get(1); let artist: String = row.get(2); songs = format!("{}{} by {}\n", songs, name, artist); } Ok(songs) } #[get("/user")] pub fn get_users() -> Result> { let mut client = crate::CLIENT.lock().unwrap(); let mut users = String::new(); for row in client.query("SELECT user_name FROM suser", &[]).unwrap() { let user: String = row.get(0); users = format!("{}{}\n", users, user); } Ok(users) }