added register, admin lock functions
This commit is contained in:
parent
ccc98c0f3e
commit
79392fd34d
@ -90,20 +90,22 @@ def playlist():
|
||||
|
||||
from spotify.api.spotify import create_playlist as create_playlist
|
||||
|
||||
new_playlist_id = create_playlist(session['username'], playlist_name)
|
||||
|
||||
to_add = {
|
||||
'name': playlist_name,
|
||||
'parts': playlist_parts,
|
||||
'playlist_id': new_playlist_id,
|
||||
'playlist_id': None,
|
||||
'shuffle': playlist_shuffle,
|
||||
'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':
|
||||
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
|
||||
|
||||
@ -148,21 +150,75 @@ def playlist():
|
||||
return jsonify({'error': 'not logged in'}), 401
|
||||
|
||||
|
||||
@blueprint.route('/user', methods=['GET'])
|
||||
@blueprint.route('/user', methods=['GET', 'POST'])
|
||||
def user():
|
||||
|
||||
if 'username' in session:
|
||||
|
||||
pulled_user = database.get_user_doc_ref(session['username']).get().to_dict()
|
||||
if request.method == 'GET':
|
||||
|
||||
response = {
|
||||
'username': pulled_user['username'],
|
||||
'type': pulled_user['type'],
|
||||
'spotify_linked': pulled_user['spotify_linked'],
|
||||
'validated': pulled_user['validated']
|
||||
pulled_user = database.get_user_doc_ref(session['username']).get().to_dict()
|
||||
|
||||
response = {
|
||||
'username': pulled_user['username'],
|
||||
'type': pulled_user['type'],
|
||||
'spotify_linked': pulled_user['spotify_linked'],
|
||||
'validated': pulled_user['validated']
|
||||
}
|
||||
|
||||
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': []
|
||||
}
|
||||
|
||||
return jsonify(response), 200
|
||||
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:
|
||||
return jsonify({'error': 'not logged in'}), 401
|
||||
|
@ -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 werkzeug.security import check_password_hash
|
||||
from werkzeug.security import check_password_hash, generate_password_hash
|
||||
|
||||
import urllib
|
||||
import datetime
|
||||
@ -41,6 +41,10 @@ def login():
|
||||
|
||||
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.update({'last_login': datetime.datetime.utcnow()})
|
||||
|
||||
@ -61,6 +65,46 @@ def logout():
|
||||
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')
|
||||
def auth():
|
||||
|
||||
@ -89,7 +133,6 @@ 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()
|
||||
|
||||
|
@ -17,7 +17,13 @@ app.register_blueprint(api_blueprint, url_prefix='/api')
|
||||
|
||||
@app.route('/')
|
||||
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': ''})
|
||||
|
@ -15,24 +15,20 @@
|
||||
{% endwith %}
|
||||
|
||||
<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>
|
||||
|
||||
<p class="center-text">create "super-playlists" of smaller modular playlists</p>
|
||||
|
||||
<a class="button full-width" href="/app">launch</a>
|
||||
{% if logged_in %}
|
||||
<a class="button full-width" href="/app">launch</a>
|
||||
{% endif %}
|
||||
</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>
|
||||
{% if not logged_in %}
|
||||
|
||||
<button class="button full-width" onclick="handleLogin()" type="submit">go</button>
|
||||
</form>
|
||||
<script src="{{ url_for('static', filename='js/login.bundle.js') }}"></script>
|
||||
</div>
|
||||
{% block form %}{% endblock %}
|
||||
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
21
spotify/templates/login.html
Normal file
21
spotify/templates/login.html
Normal 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 %}
|
20
spotify/templates/register.html
Normal file
20
spotify/templates/register.html
Normal 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 %}
|
@ -2,10 +2,19 @@ import React, { Component } from "react";
|
||||
import { BrowserRouter as Router, Route, Link } from "react-router-dom";
|
||||
const axios = require('axios');
|
||||
|
||||
import Lock from "./Lock.js";
|
||||
|
||||
class Admin extends Component {
|
||||
render(){
|
||||
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
83
src/js/Admin/Lock.js
Normal 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;
|
@ -44,7 +44,6 @@ class NewPlaylist extends Component {
|
||||
});
|
||||
}
|
||||
});
|
||||
console.log(this.state);
|
||||
}
|
||||
|
||||
render(){
|
||||
|
@ -21,6 +21,8 @@ class PlaylistView extends Component{
|
||||
this.handleInputChange = this.handleInputChange.bind(this);
|
||||
this.handleRemoveRow = this.handleRemoveRow.bind(this);
|
||||
|
||||
this.handleRun = this.handleRun.bind(this);
|
||||
|
||||
this.handleShuffleChange = this.handleShuffleChange.bind(this);
|
||||
}
|
||||
|
||||
@ -53,7 +55,7 @@ class PlaylistView extends Component{
|
||||
handleDayBoundaryChange(boundary) {
|
||||
axios.post('/api/playlist', {
|
||||
name: this.state.name,
|
||||
day_boundary: boundary
|
||||
day_boundary: parseInt(boundary)
|
||||
}).catch((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(){
|
||||
|
||||
const table = (
|
||||
@ -145,7 +154,7 @@ class PlaylistView extends Component{
|
||||
day boundary
|
||||
</td>
|
||||
<td>
|
||||
<input type="text"
|
||||
<input type="number"
|
||||
name="day_boundary"
|
||||
className="full-width"
|
||||
value={this.state.day_boundary}
|
||||
@ -153,6 +162,11 @@ class PlaylistView extends Component{
|
||||
</td>
|
||||
</tr>
|
||||
}
|
||||
<tr>
|
||||
<td colspan="2">
|
||||
<button className="button full-width" onClick={this.handleRun}>run</button>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
);
|
||||
|
@ -27,9 +27,7 @@ class PlaylistsView extends Component {
|
||||
|
||||
handleRunPlaylist(name, event){
|
||||
axios.get('/api/playlist/run', {params: {name: name}})
|
||||
.then((response) => {
|
||||
console.log(response);
|
||||
}).catch((error) => {
|
||||
.catch((error) => {
|
||||
console.log(error);
|
||||
});
|
||||
}
|
||||
|
29
src/js/register.js
Normal file
29
src/js/register.js
Normal 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;
|
@ -43,6 +43,10 @@ p {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.half-width {
|
||||
width: 50%;
|
||||
}
|
||||
|
||||
.button {
|
||||
background-color: #505050;
|
||||
color: $text-colour;
|
||||
@ -54,6 +58,8 @@ p {
|
||||
margin: 4px auto;
|
||||
padding: 15px;
|
||||
|
||||
font-size: 15px;
|
||||
|
||||
cursor: pointer;
|
||||
|
||||
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;
|
||||
background-color: #505050;
|
||||
border: black;
|
||||
|
@ -5,7 +5,8 @@ const { CleanWebpackPlugin } = require('clean-webpack-plugin');
|
||||
module.exports = {
|
||||
entry: {
|
||||
app: './src/js/app.js',
|
||||
login: './src/js/login.js'
|
||||
login: './src/js/login.js',
|
||||
register: './src/js/register.js'
|
||||
},
|
||||
module: {
|
||||
rules: [
|
||||
|
Loading…
Reference in New Issue
Block a user