added log in, session and started on api

This commit is contained in:
aj 2019-07-29 11:44:10 +01:00
parent aaebf3a6e9
commit 14a3c0bab1
17 changed files with 400 additions and 27 deletions

37
package-lock.json generated
View File

@ -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",

View File

@ -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"

1
spotify/api/__init__.py Normal file
View File

@ -0,0 +1 @@
from .api import blueprint as api_blueprint

67
spotify/api/api.py Normal file
View File

@ -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

1
spotify/auth/__init__.py Normal file
View File

@ -0,0 +1 @@
from .auth import blueprint as auth_blueprint

48
spotify/auth/auth.py Normal file
View File

@ -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'))

View File

@ -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/<path>')
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]

View File

@ -16,7 +16,7 @@
</head>
<body>
<div>
<h1 class="title">andy</h1>
<h1 class="title">sarsoo</h1>
</div>
<br><br>
@ -25,7 +25,7 @@
<script src="{{ url_for('static', filename='js/app.bundle.js') }}"></script>
<footer>
<a href="https://github.com/Sarsoo/sarsoo.xyz">view source code</a>
<a href="https://github.com/Sarsoo/spotify-web">view source code</a>
</footer>
</body>
</html>

View File

@ -18,16 +18,17 @@
</head>
<body>
<div>
<h1 class="title">andy</h1>
<h1 class="title">sarsoo</h1>
</div>
<br><br>
<ul class="navbar">
<li><a href="/">home</a></li>
<li><a href="https://sarsoo.xyz">sarsoo.xyz</a></li>
</ul>
{% block content %}{% endblock %}
<footer>
<a href="https://github.com/Sarsoo/sarsoo.xyz">view source code</a>
<a href="https://github.com/Sarsoo/spotify-web">view source code</a>
</footer>
</body>
</html>

View File

@ -4,13 +4,35 @@
{% block content %}
{% with messages = get_flashed_messages() %}
{% if messages %}
{% for message in messages %}
<div class="row card pad-12">
<p class="center-text" style="color: red">{{ message }}</p>
</div>
{% endfor %}
{% endif %}
{% endwith %}
<div class="row">
<div class="pad-12 card">
<div class="pad-9 card">
<h1>Spotify Playlist Manager</h1>
<p class="center-text">create "super-playlists" of smaller modular playlists</p>
<a class="button full-width" href="/auth">launch</a>
<a class="button full-width" href="/app">launch</a>
</div>
<div class="pad-3 card">
<h1 class="sectiontitle">login</h1>
<form name="login" action="/auth/login" method="POST" onsubmit="return handleLogin()">
<p class="center-text">username<br><input type="text" name="username" class="full-width"></p>
<p class="center-text">password<br><input type="password" name="password" class="full-width"></p>
<p id="status" style="display: none; color: red" class="center-text"></p>
<button class="full-width" onclick="handleLogin()" type="submit">go</button>
</form>
<script src="{{ url_for('static', filename='js/login.bundle.js') }}"></script>
</div>
</div>
{% endblock %}

View File

@ -1,8 +1,31 @@
import React, { Component } from "react";
const axios = require('axios');
function Index(props){
class Index extends Component{
return <p>index</p>;
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 <p>index</p>;
}
}
export default Index;

View File

@ -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 <p>{this.state.name}</p>;
}
}
export default PlaylistView

View File

@ -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 = <div><Table playlists={this.state.playlists}/></div>;
const loadingMessage = <p className="center-text">loading...</p>;
return this.state.isLoading ? loadingMessage : table;
}
}
function Table(props){
return (
<div>
{ props.playlists.map((playlist) => <Row playlist={ playlist } key={ playlist.name }/>) }
</div>
);
}
function Row(props){
return (
<PlaylistLink playlist={props.playlist}/>
);
}
function PlaylistLink(props){
return (
<Link to={ getPlaylistLink(props.playlist.name) }>{ props.playlist.name }</Link>
);
}
function getPlaylistLink(playlistName){
return '/app/playlist/' + playlistName;
}
export default Playlists;

View File

@ -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 (
<Router>
<ul className="navbar">
<li><Link to="/app">home</Link></li>
<li><Link to="/app/settings">settings</Link></li>
</ul>
<div className="card pad-12">
<ul className="sidebar pad-3">
<li><Link to="/app">home</Link></li>
<li><Link to="/app/playlists">playlists</Link></li>
<li><Link to="/app/settings">settings</Link></li>
<li><a href="/auth/logout">logout</a></li>
<li><a href="https://sarsoo.xyz">sarsoo.xyz</a></li>
</ul>
<div className="pad-9">
<Route path="/app" exact component={Index} />
<Route path="/app/playlists" exact component={Playlists} />
<Route path="/app/settings" component={Settings} />
</div>
</div>
<Route path="/app" exact component={Index} />
<Route path="/app/settings" component={Settings} />
</Router>
);
}

22
src/js/login.js Normal file
View File

@ -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;

View File

@ -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;

View File

@ -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: [