added register, admin lock functions

This commit is contained in:
aj 2019-08-03 21:35:08 +01:00
parent ccc98c0f3e
commit 79392fd34d
14 changed files with 318 additions and 37 deletions

View File

@ -90,20 +90,22 @@ def playlist():
from spotify.api.spotify import create_playlist as create_playlist from spotify.api.spotify import create_playlist as create_playlist
new_playlist_id = create_playlist(session['username'], playlist_name)
to_add = { to_add = {
'name': playlist_name, 'name': playlist_name,
'parts': playlist_parts, 'parts': playlist_parts,
'playlist_id': new_playlist_id, 'playlist_id': None,
'shuffle': playlist_shuffle, 'shuffle': playlist_shuffle,
'type': playlist_type 'type': playlist_type
} }
if user_ref.get().to_dict()['spotify_linked']:
new_playlist_id = create_playlist(session['username'], playlist_name)
to_add['playlist_id'] = new_playlist_id
if playlist_type == 'recents': if playlist_type == 'recents':
to_add['day_boundary'] = playlist_day_boundary if playlist_day_boundary is not None else 21 to_add['day_boundary'] = playlist_day_boundary if playlist_day_boundary is not None else 21
playlists.add(to_add) playlists.document().set(to_add)
return jsonify({"message": 'playlist added', "status": "success"}), 200 return jsonify({"message": 'playlist added', "status": "success"}), 200
@ -148,11 +150,13 @@ def playlist():
return jsonify({'error': 'not logged in'}), 401 return jsonify({'error': 'not logged in'}), 401
@blueprint.route('/user', methods=['GET']) @blueprint.route('/user', methods=['GET', 'POST'])
def user(): def user():
if 'username' in session: if 'username' in session:
if request.method == 'GET':
pulled_user = database.get_user_doc_ref(session['username']).get().to_dict() pulled_user = database.get_user_doc_ref(session['username']).get().to_dict()
response = { response = {
@ -164,6 +168,58 @@ def user():
return jsonify(response), 200 return jsonify(response), 200
else:
if database.get_user_doc_ref(session['username']).get().to_dict()['type'] != 'admin':
return jsonify({'status': 'error', 'message': 'unauthorized'}), 401
request_json = request.get_json()
if 'username' not in request_json:
return jsonify({'status': 'error', 'message': 'no username provided'}), 400
actionable_user = database.get_user_doc_ref(request_json['username'])
dic = {}
if 'locked' in request_json:
dic['locked'] = request_json['locked']
if len(dic) > 0:
actionable_user.update(dic)
return jsonify({'message': 'account locked', 'status': 'succeeded'}), 200
else:
return jsonify({'error': 'not logged in'}), 401
@blueprint.route('/users', methods=['GET'])
def users():
if 'username' in session:
if database.get_user_doc_ref(session['username']).get().to_dict()['type'] != 'admin':
return jsonify({'status': 'unauthorised'}), 401
dic = {
'accounts': []
}
for account in [i.to_dict() for i in db.collection(u'spotify_users').stream()]:
user_dic = {
'username': account['username'],
'type': account['type'],
'spotify_linked': account['spotify_linked'],
'locked': account['locked'],
'last_login': account['last_login']
}
dic['accounts'].append(user_dic)
return jsonify(dic)
else: else:
return jsonify({'error': 'not logged in'}), 401 return jsonify({'error': 'not logged in'}), 401

View File

@ -1,6 +1,6 @@
from flask import Blueprint, session, flash, request, redirect, url_for from flask import Blueprint, session, flash, request, redirect, url_for, render_template
from google.cloud import firestore from google.cloud import firestore
from werkzeug.security import check_password_hash from werkzeug.security import check_password_hash, generate_password_hash
import urllib import urllib
import datetime import datetime
@ -41,6 +41,10 @@ def login():
if check_password_hash(doc['password'], password): if check_password_hash(doc['password'], password):
if doc['locked']:
flash('account locked')
return redirect(url_for('index'))
user_reference = db.collection(u'spotify_users').document(u'{}'.format(users[0].id)) user_reference = db.collection(u'spotify_users').document(u'{}'.format(users[0].id))
user_reference.update({'last_login': datetime.datetime.utcnow()}) user_reference.update({'last_login': datetime.datetime.utcnow()})
@ -61,6 +65,46 @@ def logout():
return redirect(url_for('index')) return redirect(url_for('index'))
@blueprint.route('/register', methods=['GET', 'POST'])
def register():
if 'username' in session:
return redirect(url_for('index'))
if request.method == 'GET':
return render_template('register.html')
else:
username = request.form['username'].lower()
password = request.form['password']
password_again = request.form['password_again']
if username in [i.to_dict()['username'] for i in
db.collection(u'spotify_users').where(u'username', u'==', username).stream()]:
flash('username already registered')
return redirect('authapi.register')
if password != password_again:
flash('password mismatch')
return redirect('authapi.register')
db.collection(u'spotify_users').add({
'access_token': None,
'email': None,
'last_login': datetime.datetime.utcnow(),
'locked': False,
'password': generate_password_hash(password),
'refresh_token': None,
'spotify_linked': False,
'type': 'user',
'username': username,
'validated': True
})
session['username'] = username
return redirect(url_for('authapi.auth'))
@blueprint.route('/spotify') @blueprint.route('/spotify')
def auth(): def auth():
@ -89,7 +133,6 @@ def token():
code = request.args.get('code', None) code = request.args.get('code', None)
if code is None: if code is None:
error = request.args.get('error', None) error = request.args.get('error', None)
print('error')
else: else:
app_credentials = db.document('key/spotify').get().to_dict() app_credentials = db.document('key/spotify').get().to_dict()

View File

@ -17,7 +17,13 @@ app.register_blueprint(api_blueprint, url_prefix='/api')
@app.route('/') @app.route('/')
def index(): def index():
return render_template('index.html')
if 'username' in session:
logged_in = True
else:
logged_in = False
return render_template('login.html', logged_in=logged_in)
@app.route('/app', defaults={'path': ''}) @app.route('/app', defaults={'path': ''})

View File

@ -15,24 +15,20 @@
{% endwith %} {% endwith %}
<div class="row"> <div class="row">
<div class="pad-9 card"> <div class="{% if logged_in %}pad-12{% else %}pad-9{% endif %} card">
<h1>Spotify Playlist Manager</h1> <h1>Spotify Playlist Manager</h1>
<p class="center-text">create "super-playlists" of smaller modular playlists</p> <p class="center-text">create "super-playlists" of smaller modular playlists</p>
{% if logged_in %}
<a class="button full-width" href="/app">launch</a> <a class="button full-width" href="/app">launch</a>
{% endif %}
</div> </div>
<div class="pad-3 card">
<h1 class="sectiontitle">login</h1>
<form name="login" action="/auth/login" method="POST" onsubmit="return handleLogin()"> {% if not logged_in %}
<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="button full-width" onclick="handleLogin()" type="submit">go</button> {% block form %}{% endblock %}
</form>
<script src="{{ url_for('static', filename='js/login.bundle.js') }}"></script> {% endif %}
</div>
</div> </div>
{% endblock %} {% endblock %}

View File

@ -0,0 +1,21 @@
{% extends 'index.html' %}
{% block form %}
<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="button full-width" onclick="handleLogin()" type="submit">go</button>
<br><br>
<a href="/auth/register" class="button full-width">register</a>
</form>
<script src="{{ url_for('static', filename='js/login.bundle.js') }}"></script>
</div>
{% endblock %}

View File

@ -0,0 +1,20 @@
{% extends 'index.html' %}
{% block form %}
<div class="pad-3 card">
<h1 class="sectiontitle">register</h1>
<form name="register" action="/auth/register" method="POST" onsubmit="return handleRegister()">
<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 class="center-text">password again<br><input type="password" name="password_again" class="full-width"></p>
<p id="status" style="display: none; color: red" class="center-text"></p>
<button class="button full-width" onclick="handleRegister()" type="submit">register</button>
</form>
<script src="{{ url_for('static', filename='js/register.bundle.js') }}"></script>
</div>
{% endblock %}

View File

@ -2,10 +2,19 @@ import React, { Component } from "react";
import { BrowserRouter as Router, Route, Link } from "react-router-dom"; import { BrowserRouter as Router, Route, Link } from "react-router-dom";
const axios = require('axios'); const axios = require('axios');
import Lock from "./Lock.js";
class Admin extends Component { class Admin extends Component {
render(){ render(){
return ( return (
<p>admin</p> <div>
<ul className="navbar" style={{width: "100%"}}>
<li><Link to={`${this.props.match.url}/lock`}>lock accounts</Link></li>
</ul>
<Route path={`${this.props.match.url}/lock`} component={Lock} />
</div>
); );
} }
} }

83
src/js/Admin/Lock.js Normal file
View File

@ -0,0 +1,83 @@
import React, { Component } from "react";
const axios = require('axios');
class Lock extends Component {
constructor(props){
super(props);
this.state = {
isLoading: true,
accounts: []
}
this.getUserInfo();
this.handleLock = this.handleLock.bind(this);
}
getUserInfo(){
axios.get('/api/users')
.then((response) => {
this.setState({
accounts: response.data.accounts,
isLoading: false
})
console.log(response)
});
}
handleLock(event, username, to_state){
axios.post('/api/user', {
username: username,
locked: to_state
}).catch((error) => {
console.log(error);
}).finally(() => {
this.getUserInfo();
});
}
render() {
const loadingMessage = <p className="center-text text-no-select">loading...</p>;
return this.state.isLoading ? loadingMessage :
<div>
<table className="app-table max-width">
<thead>
<tr>
<th colSpan='3'>
<h1 className="text no-select">
account locks
</h1>
</th>
</tr>
</thead>
<tbody>
{ this.state.accounts.map((account) => <Row account={account} handler={this.handleLock}
key= {account.username}/>) }
</tbody>
</table>
</div>;
}
}
function Row(props){
return (
<tr>
<td className="ui-text center-text text-no-select" style={{width: "40%"}}>{ props.account.username }</td>
<td className="ui-text center-text text-no-select" style={{width: "30%"}}>
{ props.account.last_login }
</td>
<td style={{width: "30%"}}>
<button className="button full-width"
onClick={(e) => props.handler(e, props.account.username, !props.account.locked)}>
{props.account.locked ? "unlock" : "lock"}
</button>
</td>
</tr>
);
}
export default Lock;

View File

@ -44,7 +44,6 @@ class NewPlaylist extends Component {
}); });
} }
}); });
console.log(this.state);
} }
render(){ render(){

View File

@ -21,6 +21,8 @@ class PlaylistView extends Component{
this.handleInputChange = this.handleInputChange.bind(this); this.handleInputChange = this.handleInputChange.bind(this);
this.handleRemoveRow = this.handleRemoveRow.bind(this); this.handleRemoveRow = this.handleRemoveRow.bind(this);
this.handleRun = this.handleRun.bind(this);
this.handleShuffleChange = this.handleShuffleChange.bind(this); this.handleShuffleChange = this.handleShuffleChange.bind(this);
} }
@ -53,7 +55,7 @@ class PlaylistView extends Component{
handleDayBoundaryChange(boundary) { handleDayBoundaryChange(boundary) {
axios.post('/api/playlist', { axios.post('/api/playlist', {
name: this.state.name, name: this.state.name,
day_boundary: boundary day_boundary: parseInt(boundary)
}).catch((error) => { }).catch((error) => {
console.log(error); console.log(error);
}); });
@ -105,6 +107,13 @@ class PlaylistView extends Component{
}); });
} }
handleRun(event){
axios.get('/api/playlist/run', {params: {name: this.state.name}})
.catch((error) => {
console.log(error);
});
}
render(){ render(){
const table = ( const table = (
@ -145,7 +154,7 @@ class PlaylistView extends Component{
day boundary day boundary
</td> </td>
<td> <td>
<input type="text" <input type="number"
name="day_boundary" name="day_boundary"
className="full-width" className="full-width"
value={this.state.day_boundary} value={this.state.day_boundary}
@ -153,6 +162,11 @@ class PlaylistView extends Component{
</td> </td>
</tr> </tr>
} }
<tr>
<td colspan="2">
<button className="button full-width" onClick={this.handleRun}>run</button>
</td>
</tr>
</tbody> </tbody>
</table> </table>
); );

View File

@ -27,9 +27,7 @@ class PlaylistsView extends Component {
handleRunPlaylist(name, event){ handleRunPlaylist(name, event){
axios.get('/api/playlist/run', {params: {name: name}}) axios.get('/api/playlist/run', {params: {name: name}})
.then((response) => { .catch((error) => {
console.log(response);
}).catch((error) => {
console.log(error); console.log(error);
}); });
} }

29
src/js/register.js Normal file
View File

@ -0,0 +1,29 @@
function handleRegister(){
var username = document.forms['register']['username'].value;
var password = document.forms['register']['password'].value;
var passwordAgain = document.forms['register']['password_again'].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;
}
if (password != passwordAgain) {
var status = document.getElementById("status");
status.innerHTML = "password mismatch";
status.style.display = "block";
return false;
}
return true;
}
window.handleRegister = handleRegister;

View File

@ -43,6 +43,10 @@ p {
width: 100%; width: 100%;
} }
.half-width {
width: 50%;
}
.button { .button {
background-color: #505050; background-color: #505050;
color: $text-colour; color: $text-colour;
@ -54,6 +58,8 @@ p {
margin: 4px auto; margin: 4px auto;
padding: 15px; padding: 15px;
font-size: 15px;
cursor: pointer; cursor: pointer;
box-shadow: 0 9px #383838; box-shadow: 0 9px #383838;
@ -75,7 +81,7 @@ p {
} }
} }
input[type=text], input[type=password], select { input[type=text], input[type=password], input[type=number], select {
padding: 10px; padding: 10px;
background-color: #505050; background-color: #505050;
border: black; border: black;

View File

@ -5,7 +5,8 @@ const { CleanWebpackPlugin } = require('clean-webpack-plugin');
module.exports = { module.exports = {
entry: { entry: {
app: './src/js/app.js', app: './src/js/app.js',
login: './src/js/login.js' login: './src/js/login.js',
register: './src/js/register.js'
}, },
module: { module: {
rules: [ rules: [