added log in, session and started on api
This commit is contained in:
parent
aaebf3a6e9
commit
14a3c0bab1
37
package-lock.json
generated
37
package-lock.json
generated
@ -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",
|
||||
|
@ -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
1
spotify/api/__init__.py
Normal file
@ -0,0 +1 @@
|
||||
from .api import blueprint as api_blueprint
|
67
spotify/api/api.py
Normal file
67
spotify/api/api.py
Normal 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
1
spotify/auth/__init__.py
Normal file
@ -0,0 +1 @@
|
||||
from .auth import blueprint as auth_blueprint
|
48
spotify/auth/auth.py
Normal file
48
spotify/auth/auth.py
Normal 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'))
|
@ -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)
|
||||
# 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]
|
||||
|
@ -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>
|
@ -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>
|
||||
|
@ -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 %}
|
||||
|
@ -1,8 +1,31 @@
|
||||
import React, { Component } from "react";
|
||||
const axios = require('axios');
|
||||
|
||||
function Index(props){
|
||||
class Index extends Component{
|
||||
|
||||
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;
|
20
src/js/Playlist/PlaylistView.js
Normal file
20
src/js/Playlist/PlaylistView.js
Normal 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
|
59
src/js/Playlist/Playlists.js
Normal file
59
src/js/Playlist/Playlists.js
Normal 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;
|
@ -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">
|
||||
<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>
|
||||
|
||||
</Router>
|
||||
);
|
||||
}
|
||||
|
22
src/js/login.js
Normal file
22
src/js/login.js
Normal 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;
|
@ -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;
|
||||
|
@ -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: [
|
||||
|
Loading…
Reference in New Issue
Block a user