diff --git a/main.py b/main.py index 9b349e8..083c8dd 100644 --- a/main.py +++ b/main.py @@ -5,22 +5,17 @@ app = app def update_tag(event, context): - import base64 import logging - import json logger = logging.getLogger('music') - if 'data' in event: - body = json.loads(base64.b64decode(event['data']).decode('utf-8')) - - if 'username' not in body or 'tag_id' not in body: - logger.error('malformed body') - return - - do_update_tag(username=body["username"], tag_id=body["tag_id"]) + if event.get('attributes'): + if 'username' in event['attributes'] and 'tag_id' in event['attributes']: + do_update_tag(username=event['attributes']['username'], tag_id=event['attributes']["tag_id"]) + else: + logger.error('no parameters in event attributes') else: - logger.error('no data in event') + logger.error('no attributes in event') if __name__ == '__main__': diff --git a/music/api/__init__.py b/music/api/__init__.py index 67d1484..8904ebe 100644 --- a/music/api/__init__.py +++ b/music/api/__init__.py @@ -4,3 +4,4 @@ from .fm import blueprint as fm_blueprint from .spotfm import blueprint as spotfm_blueprint from .spotify import blueprint as spotify_blueprint from .admin import blueprint as admin_blueprint +from .tag import blueprint as tag_blueprint diff --git a/music/api/tag.py b/music/api/tag.py new file mode 100644 index 0000000..c2fe469 --- /dev/null +++ b/music/api/tag.py @@ -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/', 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) diff --git a/music/db/database.py b/music/db/database.py index cf6dc93..a1209cb 100644 --- a/music/db/database.py +++ b/music/db/database.py @@ -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}') -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}') tag = get_tag(username=username, tag_id=tag_id) if tag: tag.db_ref.delete() + return True else: 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]) diff --git a/music/music.py b/music/music.py index 0322789..e625544 100644 --- a/music/music.py +++ b/music/music.py @@ -5,7 +5,7 @@ import os from music.auth import auth_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() @@ -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(spotify_blueprint, url_prefix='/api/spotify') app.register_blueprint(admin_blueprint, url_prefix='/api/admin') +app.register_blueprint(tag_blueprint, url_prefix='/api') @app.route('/') diff --git a/requirements.txt b/requirements.txt index d44718b..78c054c 100644 --- a/requirements.txt +++ b/requirements.txt @@ -5,16 +5,17 @@ Click==7.0 Flask==1.1.1 google-api-core==1.16.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-logging==1.14.0 +google-cloud-pubsub==1.1.0 google-cloud-tasks==1.3.0 googleapis-common-protos==1.51.0 grpc-google-iam-v1==0.12.3 grpcio==1.26.0 idna==2.8 itsdangerous==1.1.0 -Jinja2==2.10.3 +Jinja2==2.11.1 MarkupSafe==1.1.1 numpy==1.18.1 opencv-python==4.1.2.30 @@ -27,4 +28,4 @@ rsa==4.0 six==1.14.0 tabulate==0.8.6 urllib3==1.25.8 -Werkzeug==0.16.0 +Werkzeug==0.16.1 diff --git a/src/js/Tag/TagList.js b/src/js/Tag/TagList.js new file mode 100644 index 0000000..3237fc1 --- /dev/null +++ b/src/js/Tag/TagList.js @@ -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 = ; + + return this.state.isLoading ? : grid; + } +} + +function TagGrid(props){ + return ( + + + + + + + + { props.tags.length == 0 ? ( + + No Tags + + ) : ( + props.tags.map((tag) => ) + )} + + ); +} + +function TagCard(props){ + return ( + + + + + { props.tag.name } + + + + + + + + + + + + ); +} + +function getTagLink(tagName){ + return `/app/tag/${tagName}/edit`; +} + +export default TagList; \ No newline at end of file