From e3615d0ccfda7d80f0059a86a7034f2dc686edab Mon Sep 17 00:00:00 2001 From: aj Date: Wed, 31 Jul 2019 12:24:10 +0100 Subject: [PATCH] integrated spotify auth/deauth, adding parts to playlist --- spotify/api/api.py | 135 +++++++++++++++--------------- spotify/api/database.py | 32 +++++++ spotify/auth/auth.py | 91 +++++++++++++++++++- spotify/spotify.py | 49 ----------- src/js/Index/Index.js | 2 +- src/js/Playlist/PlaylistView.js | 86 +++++++++++++++++-- src/js/PlaylistManager.js | 8 +- src/js/Settings/ChangePassword.js | 10 ++- src/js/Settings/Settings.js | 5 +- src/js/Settings/SpotifyLink.js | 50 +++++++++++ 10 files changed, 337 insertions(+), 131 deletions(-) create mode 100644 spotify/api/database.py create mode 100644 src/js/Settings/SpotifyLink.js diff --git a/spotify/api/api.py b/spotify/api/api.py index 67fd0cb..6b87edb 100644 --- a/spotify/api/api.py +++ b/spotify/api/api.py @@ -2,6 +2,8 @@ from flask import Blueprint, session, request, jsonify from google.cloud import firestore from werkzeug.security import check_password_hash, generate_password_hash +import spotify.api.database as database + blueprint = Blueprint('api', __name__) db = firestore.Client() @@ -11,15 +13,9 @@ def get_playlists(): if 'username' in session: - users = db.collection(u'spotify_users').where(u'username', u'==', session['username']).stream() + pulled_user = database.get_user_doc_ref(session['username']) - users = [i for i in users] - - if len(users) == 1: - playlists = db.document(u'spotify_users/{}'.format(users[0].id)).collection(u'playlists') - else: - error = {'error': 'multiple usernames?'} - return jsonify(error), 500 + playlists = database.get_user_playlists_collection(pulled_user.id) response = { 'playlists': [i.to_dict() for i in playlists.stream()] @@ -28,8 +24,7 @@ def get_playlists(): return jsonify(response), 200 else: - error = {'error': 'not logged in'} - return jsonify(error), 401 + return jsonify({'error': 'not logged in'}), 401 @blueprint.route('/playlist', methods=['GET', 'POST', 'PUT']) @@ -37,33 +32,25 @@ def get_playlist(): if 'username' in session: - users = db.collection(u'spotify_users').where(u'username', u'==', session['username']).stream() - - users = [i for i in users] - - if len(users) == 1: - playlists = db.document(u'spotify_users/{}'.format(users[0].id)).collection(u'playlists') - else: - error = {'error': 'multiple usernames?'} - return jsonify(error), 500 + user_ref = database.get_user_doc_ref(session['username']) + playlists = database.get_user_playlists_collection(user_ref.id) if request.method == 'GET': playlist_name = request.args.get('name', None) if playlist_name: - playlist = [i for i in playlists.where(u'name', u'==', playlist_name).stream()] + queried_playlist = [i for i in playlists.where(u'name', u'==', playlist_name).stream()] - if len(playlist) == 0: + if len(queried_playlist) == 0: return jsonify({'error': 'no playlist found'}), 404 - elif len(playlist) > 1: + elif len(queried_playlist) > 1: return jsonify({'error': 'multiple playlists found'}), 500 - return jsonify(playlist[0].to_dict()), 200 + return jsonify(queried_playlist[0].to_dict()), 200 else: - response = {"error": 'no name requested'} - return jsonify(response), 400 + return jsonify({"error": 'no name requested'}), 400 elif request.method == 'POST' or request.method == 'PUT': @@ -73,12 +60,54 @@ def get_playlist(): return jsonify({'error': "no name provided"}), 400 playlist_name = request_json['name'] + playlist_parts = request_json.get('parts', None) + playlist_id = request_json.get('id', None) - return 404 + queried_playlist = [i for i in playlists.where(u'name', u'==', playlist_name).stream()] + + if request.method == 'PUT': + + if len(queried_playlist) != 0: + return jsonify({'error': 'playlist already exists'}), 400 + + if playlist_parts is None or playlist_id is None: + return jsonify({'error': 'parts and id required'}), 400 + + playlists.add({ + 'name': playlist_name, + 'parts': playlist_parts, + 'playlist_id': playlist_id + }) + + return jsonify({"message": 'playlist added', "status": "success"}), 200 + + else: + + if len(queried_playlist) == 0: + return jsonify({'error': "playlist doesn't exist"}), 400 + + if len(queried_playlist) > 1: + return jsonify({'error': "multiple playlists exist"}), 500 + + if playlist_parts is None and playlist_id is None: + return jsonify({'error': "no chnages to make"}), 400 + + playlist_doc = playlists.document(queried_playlist[0].id) + + dic = {} + + if playlist_parts: + dic['parts'] = playlist_parts + + if playlist_id: + dic['playlist_id'] = playlist_id + + playlist_doc.update(dic) + + return jsonify({"message": 'playlist updated', "status": "success"}), 200 else: - error = {'error': 'not logged in'} - return jsonify(error), 401 + return jsonify({'error': 'not logged in'}), 401 @blueprint.route('/user', methods=['GET']) @@ -86,28 +115,19 @@ def user(): if 'username' in session: - users = db.collection(u'spotify_users').where(u'username', u'==', session['username']).stream() - users = [i for i in users] - - if len(users) == 1: - pulled_user = db.collection(u'spotify_users').document(u'{}'.format(users[0].id)).get() - else: - error = {'error': 'multiple usernames?'} - return jsonify(error), 500 - - doc = pulled_user.to_dict() + pulled_user = database.get_user_doc_ref(session['username']).get().to_dict() response = { - 'username': doc['username'], - 'type': doc['type'], - 'validated': doc['validated'] + 'username': pulled_user['username'], + 'type': pulled_user['type'], + 'spotify_linked': pulled_user['spotify_linked'], + 'validated': pulled_user['validated'] } return jsonify(response), 200 else: - error = {'error': 'not logged in'} - return jsonify(error), 401 + return jsonify({'error': 'not logged in'}), 401 @blueprint.route('/user/password', methods=['POST']) @@ -120,21 +140,12 @@ def change_password(): if 'new_password' in request_json and 'current_password' in request_json: if len(request_json['new_password']) == 0: - response = {"error": 'zero length password'} - return jsonify(response), 400 + return jsonify({"error": 'zero length password'}), 400 if len(request_json['new_password']) > 30: - response = {"error": 'password too long'} - return jsonify(response), 400 + return jsonify({"error": 'password too long'}), 400 - users = db.collection(u'spotify_users').where(u'username', u'==', session['username']).stream() - users = [i for i in users] - - if len(users) == 1: - current_user = db.collection(u'spotify_users').document(u'{}'.format(users[0].id)) - else: - error = {'error': 'multiple usernames?'} - return jsonify(error), 500 + current_user = database.get_user_doc_ref(session['username']) if check_password_hash(current_user.get().to_dict()['password'], request_json['current_password']): @@ -144,18 +155,10 @@ def change_password(): return jsonify(response), 200 else: - error = {'error': 'wrong password provided'} - return jsonify(error), 403 + return jsonify({'error': 'wrong password provided'}), 403 else: - error = {'error': 'malformed request, no old_password/new_password'} - return jsonify(error), 400 + return jsonify({'error': 'malformed request, no old_password/new_password'}), 400 else: - error = {'error': 'not logged in'} - return jsonify(error), 401 - - -@blueprint.route('/playlist', methods=['GET', 'PUT', 'POST']) -def playlist(): - return 401 + return jsonify({'error': 'not logged in'}), 401 diff --git a/spotify/api/database.py b/spotify/api/database.py new file mode 100644 index 0000000..d0d876d --- /dev/null +++ b/spotify/api/database.py @@ -0,0 +1,32 @@ +from google.cloud import firestore +db = firestore.Client() + + +def get_user_query_stream(user): + + users = db.collection(u'spotify_users').where(u'username', u'==', user).stream() + users = [i for i in users] + + return users + + +def get_user_doc_ref(user): + + users = get_user_query_stream(user) + + if len(users) == 1: + + return db.collection(u'spotify_users').document(u'{}'.format(users[0].id)) + + else: + print(len(users)) + raise ValueError + + +def get_user_playlists_collection(user_id): + + playlists = db.document(u'spotify_users/{}'.format(user_id)).collection(u'playlists') + + return playlists + + diff --git a/spotify/auth/auth.py b/spotify/auth/auth.py index 65c6ada..8ee98f4 100644 --- a/spotify/auth/auth.py +++ b/spotify/auth/auth.py @@ -2,6 +2,13 @@ from flask import Blueprint, session, flash, request, redirect, url_for from google.cloud import firestore from werkzeug.security import check_password_hash +import urllib +import datetime +from base64 import b64encode +import requests + +import spotify.api.database as database + blueprint = Blueprint('authapi', __name__) db = firestore.Client() @@ -17,9 +24,7 @@ def login(): username = request.form['username'].lower() password = request.form['password'] - users = db.collection(u'spotify_users').where(u'username', u'==', username).stream() - - users = [i for i in users] + users = database.get_user_query_stream(username) if len(users) == 0: flash('user not found') @@ -35,6 +40,10 @@ def login(): return redirect(url_for('index')) if check_password_hash(doc['password'], password): + + user_reference = db.collection(u'spotify_users').document(u'{}'.format(users[0].id)) + user_reference.update({'last_login': datetime.datetime.utcnow()}) + session['username'] = username return redirect(url_for('app_route')) else: @@ -50,3 +59,79 @@ def logout(): session.pop('username', None) flash('logged out') return redirect(url_for('index')) + + +@blueprint.route('/spotify') +def auth(): + + if 'username' in session: + + client_id = db.document('key/spotify').get().to_dict()['clientid'] + params = urllib.parse.urlencode( + { + 'client_id': client_id, + 'response_type': 'code', + 'scope': 'playlist-modify-public playlist-modify-private playlist-read-private', + 'redirect_uri': 'https://spotify.sarsoo.xyz/auth/spotify/token' + } + ) + + return redirect(urllib.parse.urlunparse(['https', 'accounts.spotify.com', 'authorize', '', params, ''])) + + return redirect('/') + + +@blueprint.route('/spotify/token') +def token(): + + if 'username' in session: + + code = request.args.get('code', None) + if code is None: + error = request.args.get('error', None) + print('error') + else: + app_credentials = db.document('key/spotify').get().to_dict() + + idsecret = b64encode(bytes(app_credentials['clientid'] + ':' + app_credentials['clientsecret'], "utf-8")).decode("ascii") + headers = {'Authorization': 'Basic %s' % idsecret} + + data = { + 'grant_type': 'authorization_code', + 'code': code, + 'redirect_uri': 'https://spotify.sarsoo.xyz/auth/spotify/token' + } + + req = requests.post('https://accounts.spotify.com/api/token', data=data, headers=headers) + + resp = req.json() + + user_reference = database.get_user_doc_ref(session['username']) + + user_reference.update({ + 'access_token': resp['access_token'], + 'refresh_token': resp['refresh_token'], + 'spotify_linked': True + }) + + return redirect('/app/settings/spotify') + + return redirect('/') + + +@blueprint.route('/spotify/deauth') +def deauth(): + + if 'username' in session: + + user_reference = database.get_user_doc_ref(session['username']) + + user_reference.update({ + 'access_token': None, + 'refresh_token': None, + 'spotify_linked': False + }) + + return redirect('/app/settings/spotify') + + return redirect('/') diff --git a/spotify/spotify.py b/spotify/spotify.py index ae6ccec..91efe9e 100644 --- a/spotify/spotify.py +++ b/spotify/spotify.py @@ -1,11 +1,7 @@ from flask import Flask, render_template, redirect, request, session, flash, url_for from google.cloud import firestore -import requests - -from base64 import b64encode import os -import urllib from spotify.auth import auth_blueprint from spotify.api import api_blueprint @@ -24,51 +20,6 @@ def index(): return render_template('index.html') -@app.route('/spotify/auth') -def auth(): - - client_id = db.document('key/spotify').get().to_dict()['clientid'] - params = urllib.parse.urlencode( - { - 'client_id': client_id, - 'response_type': 'code', - 'scope': 'playlist-modify-public playlist-modify-private playlist-read-private', - 'redirect_uri': 'https://spotify.sarsoo.xyz/token' - } - ) - - return redirect(urllib.parse.urlunparse(['https', 'accounts.spotify.com', 'authorize', '', params, ''])) - - -@app.route('/token') -def token(): - - code = request.args.get('code', None) - if code is None: - error = request.args.get('error', None) - print('error') - else: - app_credentials = db.document('key/spotify').get().to_dict() - - idsecret = b64encode(bytes(app_credentials['clientid'] + ':' + app_credentials['clientsecret'], "utf-8")).decode("ascii") - headers = {'Authorization': 'Basic %s' % idsecret} - - data = { - 'grant_type': 'authorization_code', - 'code': code, - 'redirect_uri': 'https://spotify.sarsoo.xyz/token' - } - - req = requests.post('https://accounts.spotify.com/api/token', data=data, headers=headers) - - resp = req.json() - # print(str(req.status_code) + str(resp)) - - # if 200 <= req.status_code < 300: - - return redirect('/app') - - @app.route('/app', defaults={'path': ''}) @app.route('/app/') def app_route(path): diff --git a/src/js/Index/Index.js b/src/js/Index/Index.js index 50a3f5a..68ea663 100644 --- a/src/js/Index/Index.js +++ b/src/js/Index/Index.js @@ -22,7 +22,7 @@ class Index extends Component{ } render(){ - return

index

; + return

welcome to playlist manager!

; } } diff --git a/src/js/Playlist/PlaylistView.js b/src/js/Playlist/PlaylistView.js index ac5e95a..4b9e4fa 100644 --- a/src/js/Playlist/PlaylistView.js +++ b/src/js/Playlist/PlaylistView.js @@ -1,21 +1,97 @@ import React, { Component } from "react"; -import { BrowserRouter as Router, Route, Link } from "react-router-dom"; const axios = require('axios'); class PlaylistView extends Component{ constructor(props){ super(props); - console.log(this.props); + console.log(this.props.match.params.name); this.state = { - name: this.props.match.name + name: this.props.match.params.name, + parts: [], + error: false, + error_text: null, + add_part_value: '' } + this.handleAddPart = this.handleAddPart.bind(this); + this.handleAddPartChange = this.handleAddPartChange.bind(this); + } + + componentDidMount(){ + this.getPlaylistInfo(); + } + + getPlaylistInfo(){ + axios.get(`/api/playlist?name=${ this.state.name }`) + .then((response) => { + this.setState(response.data); + }).catch((error) => { + this.setState({ + error: true, + error_text: "error pulling playlist info" + }); + }); + } + + handleAddPartChange(event){ + this.setState({ + add_part_value: event.target.value + }); + } + + handleAddPart(event){ + var parts = this.state.parts; + parts.push(this.state.add_part_value); + this.setState({ + parts: parts + }); + axios.post('/api/playlist', { + name: this.state.name, + parts: parts + }).then((response) => { + console.log(reponse); + }).catch((error) => { + console.log(error); + }); } render(){ - return

{this.state.name}

; + + const table = ( + + + + + + + + { this.state.parts.map((part) => ) } + + + + + +

{ this.state.name }

+ + + +
+ ); + + const error =

{ this.state.error_text }

; + + return this.state.error ? error : table; } } -export default PlaylistView \ No newline at end of file +function Row (props) { + return ( + + { props.part } + remove + + ); +} + +export default PlaylistView; \ No newline at end of file diff --git a/src/js/PlaylistManager.js b/src/js/PlaylistManager.js index 77739a8..6e8a46d 100644 --- a/src/js/PlaylistManager.js +++ b/src/js/PlaylistManager.js @@ -16,7 +16,8 @@ class PlaylistManager extends Component { constructor(props){ super(props); this.state = { - type: null + type: null, + spotify_linked: null } } @@ -37,7 +38,8 @@ class PlaylistManager extends Component { }) .then((response) => { self.setState({ - type: response.data.type + type: response.data.type, + spotify_linked: response.data.spotify_linked }) }); } @@ -61,7 +63,7 @@ class PlaylistManager extends Component { - + } /> { this.state.type == 'admin' && } diff --git a/src/js/Settings/ChangePassword.js b/src/js/Settings/ChangePassword.js index 6272d84..ee45edf 100644 --- a/src/js/Settings/ChangePassword.js +++ b/src/js/Settings/ChangePassword.js @@ -79,10 +79,14 @@ class ChangePassword extends Component { render(){ return ( -
-

change password

+
+ + + + + @@ -117,7 +121,7 @@ class ChangePassword extends Component {

change password

current:
- { this.state.error &&

{this.state.errorValue}

} + { this.state.error &&

{this.state.errorValue}

}
); } diff --git a/src/js/Settings/Settings.js b/src/js/Settings/Settings.js index e92f314..e3c6122 100644 --- a/src/js/Settings/Settings.js +++ b/src/js/Settings/Settings.js @@ -1,7 +1,8 @@ import React, { Component } from "react"; import { BrowserRouter as Router, Route, Link, Switch, Redirect} from "react-router-dom"; -import ChangePassword from "./ChangePassword.js" +import ChangePassword from "./ChangePassword.js"; +import SpotifyLink from "./SpotifyLink.js"; class Settings extends Component { @@ -10,9 +11,11 @@ class Settings extends Component {
  • password
  • +
  • spotify
+ } />
); diff --git a/src/js/Settings/SpotifyLink.js b/src/js/Settings/SpotifyLink.js new file mode 100644 index 0000000..e246975 --- /dev/null +++ b/src/js/Settings/SpotifyLink.js @@ -0,0 +1,50 @@ +import React, { Component } from "react"; +const axios = require('axios'); + +class SpotifyLink extends Component { + + constructor(props){ + super(props); + this.state = { + spotify_linked: props.spotify_linked + } + } + + getUserInfo(){ + + } + + render(){ + return ( + + + + + + + + + + + + + + +

spotify link status

+ status: { this.state.spotify_linked ? "linked" : "unlinked" } +
+ { this.state.spotify_linked ? : } +
+ ); + } +} + +function AuthButton(props) { + return auth; +} + +function DeAuthButton(props) { + return de-auth; +} + +export default SpotifyLink; \ No newline at end of file