added tag routes and js template, fixed faas trigger
This commit is contained in:
parent
1aee2feb16
commit
97ffc1f141
17
main.py
17
main.py
@ -5,22 +5,17 @@ app = app
|
|||||||
|
|
||||||
|
|
||||||
def update_tag(event, context):
|
def update_tag(event, context):
|
||||||
import base64
|
|
||||||
import logging
|
import logging
|
||||||
import json
|
|
||||||
|
|
||||||
logger = logging.getLogger('music')
|
logger = logging.getLogger('music')
|
||||||
|
|
||||||
if 'data' in event:
|
if event.get('attributes'):
|
||||||
body = json.loads(base64.b64decode(event['data']).decode('utf-8'))
|
if 'username' in event['attributes'] and 'tag_id' in event['attributes']:
|
||||||
|
do_update_tag(username=event['attributes']['username'], tag_id=event['attributes']["tag_id"])
|
||||||
if 'username' not in body or 'tag_id' not in body:
|
else:
|
||||||
logger.error('malformed body')
|
logger.error('no parameters in event attributes')
|
||||||
return
|
|
||||||
|
|
||||||
do_update_tag(username=body["username"], tag_id=body["tag_id"])
|
|
||||||
else:
|
else:
|
||||||
logger.error('no data in event')
|
logger.error('no attributes in event')
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
|
@ -4,3 +4,4 @@ from .fm import blueprint as fm_blueprint
|
|||||||
from .spotfm import blueprint as spotfm_blueprint
|
from .spotfm import blueprint as spotfm_blueprint
|
||||||
from .spotify import blueprint as spotify_blueprint
|
from .spotify import blueprint as spotify_blueprint
|
||||||
from .admin import blueprint as admin_blueprint
|
from .admin import blueprint as admin_blueprint
|
||||||
|
from .tag import blueprint as tag_blueprint
|
||||||
|
127
music/api/tag.py
Normal file
127
music/api/tag.py
Normal file
@ -0,0 +1,127 @@
|
|||||||
|
from flask import Blueprint, jsonify, request
|
||||||
|
|
||||||
|
import logging
|
||||||
|
|
||||||
|
from google.cloud import pubsub_v1
|
||||||
|
|
||||||
|
import music.db.database as database
|
||||||
|
from music.api.decorators import login_or_basic_auth
|
||||||
|
|
||||||
|
blueprint = Blueprint('task', __name__)
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
publisher = pubsub_v1.PublisherClient()
|
||||||
|
|
||||||
|
|
||||||
|
@blueprint.route('/tag', methods=['GET'])
|
||||||
|
@login_or_basic_auth
|
||||||
|
def tags(username=None):
|
||||||
|
logger.info(f'retrieving tags for {username}')
|
||||||
|
return jsonify({
|
||||||
|
'tags': [i.to_dict() for i in database.get_user_tags(username)]
|
||||||
|
}), 200
|
||||||
|
|
||||||
|
|
||||||
|
@blueprint.route('/tag/<tag_id>', methods=['GET', 'PUT', 'POST', "DELETE"])
|
||||||
|
@login_or_basic_auth
|
||||||
|
def tag(tag_id, username=None):
|
||||||
|
if request.method == 'GET':
|
||||||
|
return put_tag(tag_id, username)
|
||||||
|
elif request.method == 'PUT':
|
||||||
|
return put_tag(tag_id, username)
|
||||||
|
elif request.method == 'POST':
|
||||||
|
return post_tag(tag_id, username)
|
||||||
|
elif request.method == 'DELETE':
|
||||||
|
return delete_tag(tag_id, username)
|
||||||
|
|
||||||
|
|
||||||
|
def get_tag(tag_id, username):
|
||||||
|
logger.info(f'retriving {tag_id} for {username}')
|
||||||
|
|
||||||
|
db_tag = database.get_tag(username=username, tag_id=tag_id)
|
||||||
|
if db_tag is not None:
|
||||||
|
return jsonify({
|
||||||
|
'tag': db_tag.to_dict()
|
||||||
|
}), 200
|
||||||
|
else:
|
||||||
|
return jsonify({"error": 'tag not found'}), 404
|
||||||
|
|
||||||
|
|
||||||
|
def put_tag(tag_id, username):
|
||||||
|
logger.info(f'updating {tag_id} for {username}')
|
||||||
|
|
||||||
|
db_tag = database.get_tag(username=username, tag_id=tag_id)
|
||||||
|
|
||||||
|
if db_tag is None:
|
||||||
|
return jsonify({"error": 'tag not found'}), 404
|
||||||
|
|
||||||
|
request_json = request.get_json()
|
||||||
|
|
||||||
|
if request_json.get('name'):
|
||||||
|
db_tag.name = request_json['name']
|
||||||
|
|
||||||
|
update_required = False
|
||||||
|
|
||||||
|
tracks = []
|
||||||
|
if request_json.get('tracks'):
|
||||||
|
update_required = True
|
||||||
|
for track in request_json['tracks']:
|
||||||
|
if track.get('name') and track.get('artist'):
|
||||||
|
tracks.append({
|
||||||
|
'name': track['name'],
|
||||||
|
'artist': track['artist']
|
||||||
|
})
|
||||||
|
db_tag.tracks = tracks
|
||||||
|
|
||||||
|
albums = []
|
||||||
|
if request_json.get('albums'):
|
||||||
|
update_required = True
|
||||||
|
for album in request_json['albums']:
|
||||||
|
if album.get('name') and album.get('artist'):
|
||||||
|
albums.append({
|
||||||
|
'name': album['name'],
|
||||||
|
'artist': album['artist']
|
||||||
|
})
|
||||||
|
db_tag.album = albums
|
||||||
|
|
||||||
|
artists = []
|
||||||
|
if request_json.get('artists'):
|
||||||
|
update_required = True
|
||||||
|
for artist in request_json['tracks']:
|
||||||
|
if artist.get('name') and artist.get('artist'):
|
||||||
|
artists.append({
|
||||||
|
'name': artist['name']
|
||||||
|
})
|
||||||
|
db_tag.artists = artists
|
||||||
|
|
||||||
|
if update_required:
|
||||||
|
update_tag(username=username, tag_id=tag_id)
|
||||||
|
|
||||||
|
return jsonify({"message": 'tag updated', "status": "success"}), 200
|
||||||
|
|
||||||
|
|
||||||
|
def post_tag(tag_id, username):
|
||||||
|
logger.info(f'creating {tag_id} for {username}')
|
||||||
|
|
||||||
|
new_tag = database.create_tag(username=username, tag_id=tag_id)
|
||||||
|
if new_tag is not None:
|
||||||
|
return jsonify({"message": 'tag added', "status": "success"}), 201
|
||||||
|
else:
|
||||||
|
return jsonify({"error": 'tag not created'}), 400
|
||||||
|
|
||||||
|
|
||||||
|
def delete_tag(tag_id, username):
|
||||||
|
logger.info(f'deleting {tag_id} for {username}')
|
||||||
|
|
||||||
|
response = database.delete_tag(username=username, tag_id=tag_id)
|
||||||
|
|
||||||
|
if response is not None:
|
||||||
|
return jsonify({"message": 'tag deleted', "status": "success"}), 201
|
||||||
|
else:
|
||||||
|
return jsonify({"error": 'tag not deleted'}), 400
|
||||||
|
|
||||||
|
|
||||||
|
def update_tag(username, tag_id):
|
||||||
|
logger.info(f'queuing {tag_id} update for {username}')
|
||||||
|
|
||||||
|
publisher.publish('projects/sarsooxyz/topics/update_tag', b'', tag_id=tag_id, username=username)
|
@ -411,12 +411,40 @@ def update_tag(username: str, tag_id: str, updates: dict) -> None:
|
|||||||
logger.debug(f'nothing to update for {tag_id} for {username}')
|
logger.debug(f'nothing to update for {tag_id} for {username}')
|
||||||
|
|
||||||
|
|
||||||
def delete_tag(username: str, tag_id: str) -> None:
|
def delete_tag(username: str, tag_id: str) -> bool:
|
||||||
logger.info(f'deleting {tag_id} for {username}')
|
logger.info(f'deleting {tag_id} for {username}')
|
||||||
|
|
||||||
tag = get_tag(username=username, tag_id=tag_id)
|
tag = get_tag(username=username, tag_id=tag_id)
|
||||||
|
|
||||||
if tag:
|
if tag:
|
||||||
tag.db_ref.delete()
|
tag.db_ref.delete()
|
||||||
|
return True
|
||||||
else:
|
else:
|
||||||
logger.error(f'playlist {tag_id} not found for {username}')
|
logger.error(f'playlist {tag_id} not found for {username}')
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def create_tag(username: str, tag_id: str):
|
||||||
|
user = get_user(username)
|
||||||
|
|
||||||
|
if user is None:
|
||||||
|
logger.error(f'{username} not found')
|
||||||
|
return None
|
||||||
|
|
||||||
|
if tag_id in [i.tag_id for i in get_user_tags(username)]:
|
||||||
|
logger.error(f'{tag_id} already exists for {username}')
|
||||||
|
return None
|
||||||
|
|
||||||
|
return parse_tag_reference(user.db_ref.collection(u'tags').add({
|
||||||
|
'tag_id': tag_id,
|
||||||
|
'name': tag_id,
|
||||||
|
|
||||||
|
'tracks': [],
|
||||||
|
'albums': [],
|
||||||
|
'artists': [],
|
||||||
|
|
||||||
|
'count': 0,
|
||||||
|
'proportion': 0.0,
|
||||||
|
'total_user_scrobbles': 0,
|
||||||
|
'last_updated': None
|
||||||
|
})[1])
|
||||||
|
@ -5,7 +5,7 @@ import os
|
|||||||
|
|
||||||
from music.auth import auth_blueprint
|
from music.auth import auth_blueprint
|
||||||
from music.api import api_blueprint, player_blueprint, fm_blueprint, \
|
from music.api import api_blueprint, player_blueprint, fm_blueprint, \
|
||||||
spotfm_blueprint, spotify_blueprint, admin_blueprint
|
spotfm_blueprint, spotify_blueprint, admin_blueprint, tag_blueprint
|
||||||
|
|
||||||
db = firestore.Client()
|
db = firestore.Client()
|
||||||
|
|
||||||
@ -18,6 +18,7 @@ app.register_blueprint(fm_blueprint, url_prefix='/api/fm')
|
|||||||
app.register_blueprint(spotfm_blueprint, url_prefix='/api/spotfm')
|
app.register_blueprint(spotfm_blueprint, url_prefix='/api/spotfm')
|
||||||
app.register_blueprint(spotify_blueprint, url_prefix='/api/spotify')
|
app.register_blueprint(spotify_blueprint, url_prefix='/api/spotify')
|
||||||
app.register_blueprint(admin_blueprint, url_prefix='/api/admin')
|
app.register_blueprint(admin_blueprint, url_prefix='/api/admin')
|
||||||
|
app.register_blueprint(tag_blueprint, url_prefix='/api')
|
||||||
|
|
||||||
|
|
||||||
@app.route('/')
|
@app.route('/')
|
||||||
|
@ -5,16 +5,17 @@ Click==7.0
|
|||||||
Flask==1.1.1
|
Flask==1.1.1
|
||||||
google-api-core==1.16.0
|
google-api-core==1.16.0
|
||||||
google-auth==1.11.0
|
google-auth==1.11.0
|
||||||
google-cloud-core==1.2.0
|
google-cloud-core==1.3.0
|
||||||
google-cloud-firestore==1.6.1
|
google-cloud-firestore==1.6.1
|
||||||
google-cloud-logging==1.14.0
|
google-cloud-logging==1.14.0
|
||||||
|
google-cloud-pubsub==1.1.0
|
||||||
google-cloud-tasks==1.3.0
|
google-cloud-tasks==1.3.0
|
||||||
googleapis-common-protos==1.51.0
|
googleapis-common-protos==1.51.0
|
||||||
grpc-google-iam-v1==0.12.3
|
grpc-google-iam-v1==0.12.3
|
||||||
grpcio==1.26.0
|
grpcio==1.26.0
|
||||||
idna==2.8
|
idna==2.8
|
||||||
itsdangerous==1.1.0
|
itsdangerous==1.1.0
|
||||||
Jinja2==2.10.3
|
Jinja2==2.11.1
|
||||||
MarkupSafe==1.1.1
|
MarkupSafe==1.1.1
|
||||||
numpy==1.18.1
|
numpy==1.18.1
|
||||||
opencv-python==4.1.2.30
|
opencv-python==4.1.2.30
|
||||||
@ -27,4 +28,4 @@ rsa==4.0
|
|||||||
six==1.14.0
|
six==1.14.0
|
||||||
tabulate==0.8.6
|
tabulate==0.8.6
|
||||||
urllib3==1.25.8
|
urllib3==1.25.8
|
||||||
Werkzeug==0.16.0
|
Werkzeug==0.16.1
|
||||||
|
163
src/js/Tag/TagList.js
Normal file
163
src/js/Tag/TagList.js
Normal file
@ -0,0 +1,163 @@
|
|||||||
|
import React, { Component } from "react";
|
||||||
|
import { Link } from "react-router-dom";
|
||||||
|
import { Button, ButtonGroup, Typography, Card, Grid, CircularProgress } from '@material-ui/core';
|
||||||
|
import CardActions from '@material-ui/core/CardActions';
|
||||||
|
import CardContent from '@material-ui/core/CardContent';
|
||||||
|
const axios = require('axios');
|
||||||
|
|
||||||
|
import showMessage from "../Toast.js"
|
||||||
|
|
||||||
|
class TagList extends Component {
|
||||||
|
|
||||||
|
constructor(props){
|
||||||
|
super(props);
|
||||||
|
this.state = {
|
||||||
|
isLoading: true
|
||||||
|
}
|
||||||
|
this.getTags();
|
||||||
|
this.handleRunTag = this.handleRunTag.bind(this);
|
||||||
|
this.handleDeleteTag = this.handleDeleteTag.bind(this);
|
||||||
|
this.handleRunAll = this.handleRunAll.bind(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
getTags(){
|
||||||
|
var self = this;
|
||||||
|
axios.get('/api/tags')
|
||||||
|
.then((response) => {
|
||||||
|
|
||||||
|
var tags = response.data.tags.slice();
|
||||||
|
|
||||||
|
tags.sort(function(a, b){
|
||||||
|
if(a.name.toLowerCase() < b.name.toLowerCase()) { return -1; }
|
||||||
|
if(a.name.toLowerCase() > b.name.toLowerCase()) { return 1; }
|
||||||
|
return 0;
|
||||||
|
});
|
||||||
|
|
||||||
|
self.setState({
|
||||||
|
playlists: tags,
|
||||||
|
isLoading: false
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
showMessage(`Error Getting Playlists (${error.response.status})`);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
handleRunTag(name, event){
|
||||||
|
axios.get('/api/user')
|
||||||
|
.then((response) => {
|
||||||
|
if(response.data.spotify_linked == true){
|
||||||
|
axios.get('/api/tag/run', {params: {name: name}})
|
||||||
|
.then((response) => {
|
||||||
|
showMessage(`${name} ran`);
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
showMessage(`Error Running ${name} (${error.response.status})`);
|
||||||
|
});
|
||||||
|
}else{
|
||||||
|
showMessage(`Link Spotify Before Running`);
|
||||||
|
}
|
||||||
|
}).catch((error) => {
|
||||||
|
showMessage(`Error Running ${this.state.name} (${error.response.status})`);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
handleDeleteTag(name, event){
|
||||||
|
axios.delete('/api/playlist', { params: { name: name } })
|
||||||
|
.then((response) => {
|
||||||
|
showMessage(`${name} Deleted`);
|
||||||
|
this.getTags();
|
||||||
|
}).catch((error) => {
|
||||||
|
showMessage(`Error Deleting ${name} (${error.response.status})`);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
handleRunAll(event){
|
||||||
|
axios.get('/api/user')
|
||||||
|
.then((response) => {
|
||||||
|
if(response.data.spotify_linked == true){
|
||||||
|
axios.get('/api/tag/run/user')
|
||||||
|
.then((response) => {
|
||||||
|
showMessage("All Tags Ran");
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
showMessage(`Error Running All (${error.response.status})`);
|
||||||
|
});
|
||||||
|
}else{
|
||||||
|
showMessage(`Link Spotify Before Running`);
|
||||||
|
}
|
||||||
|
}).catch((error) => {
|
||||||
|
showMessage(`Error Running ${this.state.name} (${error.response.status})`);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
|
||||||
|
const grid = <TagGrid tags={this.state.tags}
|
||||||
|
handleRunTag={this.handleRunTag}
|
||||||
|
handleDeleteTag={this.handleDeleteTag}
|
||||||
|
handleRunAll={this.handleRunAll}/>;
|
||||||
|
|
||||||
|
return this.state.isLoading ? <CircularProgress /> : grid;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function TagGrid(props){
|
||||||
|
return (
|
||||||
|
<Grid container
|
||||||
|
spacing={3}
|
||||||
|
direction="row"
|
||||||
|
justify="flex-start"
|
||||||
|
alignItems="flex-start"
|
||||||
|
style={{padding: '24px'}}>
|
||||||
|
<Grid item xs>
|
||||||
|
<ButtonGroup
|
||||||
|
color="primary"
|
||||||
|
orientation="vertical"
|
||||||
|
className="full-width">
|
||||||
|
<Button component={Link} to='tags/new' >New</Button>
|
||||||
|
<Button onClick={props.handleRunAll}>Run All</Button>
|
||||||
|
</ButtonGroup>
|
||||||
|
</Grid>
|
||||||
|
{ props.tags.length == 0 ? (
|
||||||
|
<Grid item xs={12} sm={6} md={3}>
|
||||||
|
<Typography variant="h5" component="h2">No Tags</Typography>
|
||||||
|
</Grid>
|
||||||
|
) : (
|
||||||
|
props.tags.map((tag) => <TagCard tag={ tag }
|
||||||
|
handleRunTag={props.handleRunTag}
|
||||||
|
handleDeleteTag={props.handleDeleteTag}
|
||||||
|
key={ tag.name }/>)
|
||||||
|
)}
|
||||||
|
</Grid>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function TagCard(props){
|
||||||
|
return (
|
||||||
|
<Grid item xs>
|
||||||
|
<Card>
|
||||||
|
<CardContent>
|
||||||
|
<Typography variant="h5" component="h2">
|
||||||
|
{ props.tag.name }
|
||||||
|
</Typography>
|
||||||
|
</CardContent>
|
||||||
|
<CardActions>
|
||||||
|
<ButtonGroup
|
||||||
|
color="primary"
|
||||||
|
variant="contained">
|
||||||
|
<Button component={Link} to={getTagLink(props.tag.name)}>View</Button>
|
||||||
|
<Button onClick={(e) => props.handleRunTag(props.tag.name, e)}>Update</Button>
|
||||||
|
<Button onClick={(e) => props.handleDeleteTag(props.tag.name, e)}>Delete</Button>
|
||||||
|
</ButtonGroup>
|
||||||
|
</CardActions>
|
||||||
|
</Card>
|
||||||
|
</Grid>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getTagLink(tagName){
|
||||||
|
return `/app/tag/${tagName}/edit`;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default TagList;
|
Loading…
Reference in New Issue
Block a user