From 14a3c0bab1787b688ee8652c674cfa16e748a723 Mon Sep 17 00:00:00 2001 From: aj Date: Mon, 29 Jul 2019 11:44:10 +0100 Subject: [PATCH] added log in, session and started on api --- package-lock.json | 37 +++++++++++++++++- package.json | 1 + spotify/api/__init__.py | 1 + spotify/api/api.py | 67 +++++++++++++++++++++++++++++++++ spotify/auth/__init__.py | 1 + spotify/auth/auth.py | 48 +++++++++++++++++++++++ spotify/spotify.py | 28 ++++++++++---- spotify/templates/app.html | 4 +- spotify/templates/base.html | 5 ++- spotify/templates/index.html | 26 ++++++++++++- src/js/Index.js | 27 ++++++++++++- src/js/Playlist/PlaylistView.js | 20 ++++++++++ src/js/Playlist/Playlists.js | 59 +++++++++++++++++++++++++++++ src/js/PlaylistManager.js | 23 ++++++++--- src/js/login.js | 22 +++++++++++ src/scss/style.scss | 55 ++++++++++++++++++++++++++- webpack.common.js | 3 +- 17 files changed, 400 insertions(+), 27 deletions(-) create mode 100644 spotify/api/__init__.py create mode 100644 spotify/api/api.py create mode 100644 spotify/auth/__init__.py create mode 100644 spotify/auth/auth.py create mode 100644 src/js/Playlist/PlaylistView.js create mode 100644 src/js/Playlist/Playlists.js create mode 100644 src/js/login.js diff --git a/package-lock.json b/package-lock.json index 79fb327..98f9890 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1346,6 +1346,22 @@ "integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==", "dev": true }, + "axios": { + "version": "0.19.0", + "resolved": "https://registry.npmjs.org/axios/-/axios-0.19.0.tgz", + "integrity": "sha512-1uvKqKQta3KBxIz14F2v06AEHZ/dIoeKfbTRkK1E5oqjDnuEerLmYTgJB5AiQZHJcljpg1TuRzdjDR06qNk0DQ==", + "requires": { + "follow-redirects": "1.5.10", + "is-buffer": "^2.0.2" + }, + "dependencies": { + "is-buffer": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-2.0.3.tgz", + "integrity": "sha512-U15Q7MXTuZlrbymiz95PJpZxu8IlipAp4dtS3wOdgPXx3mqBnslrWU14kxfHB+Py/+2PVKSr37dMAgM2A4uArw==" + } + } + }, "babel-loader": { "version": "8.0.6", "resolved": "https://registry.npmjs.org/babel-loader/-/babel-loader-8.0.6.tgz", @@ -2482,6 +2498,24 @@ "readable-stream": "^2.3.6" } }, + "follow-redirects": { + "version": "1.5.10", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.5.10.tgz", + "integrity": "sha512-0V5l4Cizzvqt5D44aTXbFZz+FtyXV1vrDN6qrelxtfYQKW0KO0W2T/hkE8xvGa/540LkZlkaUjO4ailYTFtHVQ==", + "requires": { + "debug": "=3.1.0" + }, + "dependencies": { + "debug": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", + "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", + "requires": { + "ms": "2.0.0" + } + } + } + }, "for-in": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz", @@ -3946,8 +3980,7 @@ "ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", - "dev": true + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" }, "nan": { "version": "2.14.0", diff --git a/package.json b/package.json index 30c0f50..0cdc06e 100644 --- a/package.json +++ b/package.json @@ -19,6 +19,7 @@ }, "homepage": "https://github.com/Sarsoo/spotify-web#readme", "dependencies": { + "axios": "^0.19.0", "react": "^16.8.6", "react-dom": "^16.8.6", "react-router-dom": "^5.0.1" diff --git a/spotify/api/__init__.py b/spotify/api/__init__.py new file mode 100644 index 0000000..fce51c0 --- /dev/null +++ b/spotify/api/__init__.py @@ -0,0 +1 @@ +from .api import blueprint as api_blueprint \ No newline at end of file diff --git a/spotify/api/api.py b/spotify/api/api.py new file mode 100644 index 0000000..f551e04 --- /dev/null +++ b/spotify/api/api.py @@ -0,0 +1,67 @@ +from flask import Blueprint, session, request, jsonify +from google.cloud import firestore + +blueprint = Blueprint('api', __name__) +db = firestore.Client() + + +@blueprint.route('/playlists', methods=['GET']) +def get_playlists(): + + 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 + + docs = playlists.stream() + + response = { + 'playlists': [i.to_dict() for i in docs] + } + + return jsonify(response) + + else: + error = {'error': 'username not in session, not logged in?'} + return jsonify(error), 500 + + +@blueprint.route('/user', methods=['GET']) +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() + + response = { + 'username': doc['username'], + 'type': doc['type'], + 'validated': doc['validated'] + } + + return jsonify(response) + + else: + error = {'error': 'username not in session, not logged in?'} + return jsonify(error), 404 + + +@blueprint.route('/playlist', methods=['GET', 'PUT', 'POST']) +def playlist(): + return 404 diff --git a/spotify/auth/__init__.py b/spotify/auth/__init__.py new file mode 100644 index 0000000..8368808 --- /dev/null +++ b/spotify/auth/__init__.py @@ -0,0 +1 @@ +from .auth import blueprint as auth_blueprint \ No newline at end of file diff --git a/spotify/auth/auth.py b/spotify/auth/auth.py new file mode 100644 index 0000000..967c259 --- /dev/null +++ b/spotify/auth/auth.py @@ -0,0 +1,48 @@ +from flask import Blueprint, session, flash, request, redirect, url_for +from google.cloud import firestore +from werkzeug.security import check_password_hash + +blueprint = Blueprint('authapi', __name__) + +db = firestore.Client() + + +@blueprint.route('/login', methods=['GET', 'POST']) +def login(): + + if request.method == 'POST': + + session.pop('username', None) + + 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] + + if len(users) != 1: + flash('multiple users found') + return redirect(url_for('index')) + + doc = users[0].to_dict() + if doc is None: + flash('username not found') + return redirect(url_for('index')) + + if check_password_hash(doc['password'], password): + session['username'] = username + return redirect(url_for('app_route')) + else: + flash('incorrect password') + return redirect(url_for('index')) + + else: + return redirect(url_for('index')) + + +@blueprint.route('/logout', methods=['GET', 'POST']) +def logout(): + session.pop('username', None) + flash('logged out') + return redirect(url_for('index')) diff --git a/spotify/spotify.py b/spotify/spotify.py index 1c3c2d4..a95d2b1 100644 --- a/spotify/spotify.py +++ b/spotify/spotify.py @@ -1,4 +1,4 @@ -from flask import Flask, render_template, redirect, request +from flask import Flask, render_template, redirect, request, session, flash, url_for from google.cloud import firestore import requests @@ -7,21 +7,26 @@ from base64 import b64encode import os import urllib +from spotify.auth import auth_blueprint +from spotify.api import api_blueprint + # Project ID is determined by the GCLOUD_PROJECT environment variable db = firestore.Client() app = Flask(__name__, static_folder=os.path.join(os.path.dirname(__file__), '..', 'build'), template_folder="templates") - -staticbucketurl = 'https://storage.googleapis.com/sarsooxyzstatic/' +app.secret_key = db.collection(u'spotify').document(u'config').get().to_dict()['secret_key'] +app.register_blueprint(auth_blueprint, url_prefix='/auth') +app.register_blueprint(api_blueprint, url_prefix='/api') @app.route('/') -def main(): +def index(): return render_template('index.html') -@app.route('/auth') +@app.route('/spotify/auth') def auth(): + client_id = db.document('key/spotify').get().to_dict()['clientid'] params = urllib.parse.urlencode( { @@ -41,6 +46,7 @@ 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() @@ -55,9 +61,10 @@ def token(): req = requests.post('https://accounts.spotify.com/api/token', data=data, headers=headers) - if 200 <= req.status_code < 300: - resp = req.json() - print(resp) + resp = req.json() + # print(str(req.status_code) + str(resp)) + + # if 200 <= req.status_code < 300: return redirect('/app') @@ -65,6 +72,11 @@ def token(): @app.route('/app') @app.route('/app/') def app_route(path = None): + + if 'username' not in session: + flash('please log in') + return redirect(url_for('index')) + return render_template('app.html') # [END gae_python37_app] diff --git a/spotify/templates/app.html b/spotify/templates/app.html index 2de467a..5cd6e30 100644 --- a/spotify/templates/app.html +++ b/spotify/templates/app.html @@ -16,7 +16,7 @@
-

andy

+

sarsoo



@@ -25,7 +25,7 @@ \ No newline at end of file diff --git a/spotify/templates/base.html b/spotify/templates/base.html index e677928..18e97f9 100644 --- a/spotify/templates/base.html +++ b/spotify/templates/base.html @@ -18,16 +18,17 @@
-

andy

+

sarsoo



{% block content %}{% endblock %} diff --git a/spotify/templates/index.html b/spotify/templates/index.html index 6125dc9..612e649 100644 --- a/spotify/templates/index.html +++ b/spotify/templates/index.html @@ -4,13 +4,35 @@ {% block content %} + {% with messages = get_flashed_messages() %} + {% if messages %} + {% for message in messages %} +
+

{{ message }}

+
+ {% endfor %} + {% endif %} + {% endwith %} +
-
+

Spotify Playlist Manager

create "super-playlists" of smaller modular playlists

- launch + launch +
+
+

login

+ +
+

username

+

password

+ + + +
+
{% endblock %} diff --git a/src/js/Index.js b/src/js/Index.js index d29871d..360752c 100644 --- a/src/js/Index.js +++ b/src/js/Index.js @@ -1,8 +1,31 @@ import React, { Component } from "react"; +const axios = require('axios'); -function Index(props){ +class Index extends Component{ - return

index

; + constructor(props){ + super(props); + this.state = { + + } + this.pingPlaylists(); + } + + pingPlaylists(){ + var self = this; + axios.get('/api/playlists') + .then((response) => { + console.log(response) + }); + axios.get('/api/user') + .then((response) => { + console.log(response) + }); + } + + render(){ + return

index

; + } } export default Index; \ No newline at end of file diff --git a/src/js/Playlist/PlaylistView.js b/src/js/Playlist/PlaylistView.js new file mode 100644 index 0000000..5b95f0d --- /dev/null +++ b/src/js/Playlist/PlaylistView.js @@ -0,0 +1,20 @@ +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); + this.state = { + name: props.name + } + } + + render(){ + return

{this.state.name}

; + } + +} + +export default PlaylistView \ No newline at end of file diff --git a/src/js/Playlist/Playlists.js b/src/js/Playlist/Playlists.js new file mode 100644 index 0000000..8e56858 --- /dev/null +++ b/src/js/Playlist/Playlists.js @@ -0,0 +1,59 @@ +import React, { Component } from "react"; +import { BrowserRouter as Router, Route, Link } from "react-router-dom"; +const axios = require('axios'); + +class Playlists extends Component { + + constructor(props){ + super(props); + this.state = { + isLoading: true + } + this.getPlaylists(); + } + + getPlaylists(){ + var self = this; + axios.get('/api/playlists') + .then((response) => { + self.setState({ + playlists: response.data.playlists, + isLoading: false + }); + }); + } + + render() { + + const table =
; + const loadingMessage =

loading...

; + + return this.state.isLoading ? loadingMessage : table; + } +} + +function Table(props){ + return ( +
+ { props.playlists.map((playlist) => ) } +
+ ); +} + +function Row(props){ + return ( + + ); +} + +function PlaylistLink(props){ + return ( + { props.playlist.name } + ); +} + +function getPlaylistLink(playlistName){ + return '/app/playlist/' + playlistName; +} + +export default Playlists; \ No newline at end of file diff --git a/src/js/PlaylistManager.js b/src/js/PlaylistManager.js index ed7a614..564acba 100644 --- a/src/js/PlaylistManager.js +++ b/src/js/PlaylistManager.js @@ -2,6 +2,8 @@ import React, { Component } from "react"; import { BrowserRouter as Router, Route, Link } from "react-router-dom"; import Index from "./Index.js"; +import Playlists from "./Playlist/Playlists.js"; +import PlaylistView from "./Playlist/PlaylistView.js"; import Settings from "./Settings.js"; class PlaylistManager extends Component { @@ -9,13 +11,22 @@ class PlaylistManager extends Component { render(){ return ( -
    -
  • home
  • -
  • settings
  • -
+
+ + +
+ + + +
+
- -
); } diff --git a/src/js/login.js b/src/js/login.js new file mode 100644 index 0000000..ccd55c2 --- /dev/null +++ b/src/js/login.js @@ -0,0 +1,22 @@ + +function handleLogin(){ + var username = document.forms['login']['username'].value; + var password = document.forms['login']['password'].value; + + if (username.length == 0) { + var status = document.getElementById("status"); + status.innerHTML = "enter username"; + status.style.display = "block"; + return false; + } + if (password.length == 0) { + var status = document.getElementById("status"); + status.innerHTML = "enter password"; + status.style.display = "block"; + return false; + } + + return true; +} + +window.handleLogin = handleLogin; \ No newline at end of file diff --git a/src/scss/style.scss b/src/scss/style.scss index d3355b4..7bbdf6e 100644 --- a/src/scss/style.scss +++ b/src/scss/style.scss @@ -20,6 +20,7 @@ body { a { color: $text-colour; font-size: 20px; + outline: 0; /*text-shadow: 1px 1px 1px #aaa;*/ } @@ -39,7 +40,29 @@ p { .button { background-color: #505050; - color: black; + color: white; + border-radius: 10px; + display: inline-block; + margin: 4px auto; + cursor: pointer; + padding: 15px; + box-shadow: 2px 2px 4px black; + /*-webkit-transition-duration: 0.4s; + transition-duration: 0.4s;*/ + + text: { + align: center; + decoration: none; + } + + &:hover { + box-shadow: 0 12px 16px 0 rgba(0,0,0,0.24),0 17px 50px 0 rgba(0,0,0,0.19); + } +} + +button { + background-color: #505050; + color: white; border-radius: 10px; display: inline-block; margin: 4px auto; @@ -111,7 +134,7 @@ h1.title { background-color: white; padding: 0px; /*font-size: 16em;*/ - width: 2.5em; + width: 3.5em; height: 1.3em; text-align: center; margin: auto; @@ -190,6 +213,34 @@ ul.navbar { li.right {float: right;} } +.sidebar { + li { + // float: left; + position: -webkit-sticky; + position: sticky; + top: 0; + height: 50px; + + + a { + // display: block; + color: white; + text-align: center; + padding: 14px 16px; + + text-decoration: none; + text-shadow: 1px 1px 2px black; + -webkit-transition: background-color 0.4s; + transition: background-color 0.4s; + + + &:hover { + background-color: #080808; + } + } + } +} + footer { p { text-align: right; diff --git a/webpack.common.js b/webpack.common.js index 1a40070..c3e6749 100644 --- a/webpack.common.js +++ b/webpack.common.js @@ -4,7 +4,8 @@ const { CleanWebpackPlugin } = require('clean-webpack-plugin'); module.exports = { entry: { - app: './src/js/app.js' + app: './src/js/app.js', + login: './src/js/login.js' }, module: { rules: [