migrated to fireo ORM

This commit is contained in:
aj 2020-04-30 14:54:05 +01:00
parent a47c1556f9
commit bc1b59c7bd
22 changed files with 439 additions and 1461 deletions

View File

@ -3,13 +3,11 @@ from flask import Blueprint, jsonify
import logging
from datetime import datetime
from google.cloud import firestore
from google.cloud import tasks_v2
from music.api.decorators import login_or_basic_auth, admin_required
blueprint = Blueprint('admin-api', __name__)
db = firestore.Client()
tasker = tasks_v2.CloudTasksClient()
task_path = tasker.queue_path('sarsooxyz', 'europe-west2', 'spotify-executions')
@ -20,7 +18,7 @@ logger = logging.getLogger(__name__)
@blueprint.route('/tasks', methods=['GET'])
@login_or_basic_auth
@admin_required
def get_tasks(username=None):
def get_tasks(user=None):
tasks = [i for i in tasker.list_tasks(task_path)]

View File

@ -1,58 +1,55 @@
from flask import Blueprint, request, jsonify
from google.cloud import firestore
from werkzeug.security import generate_password_hash
import os
import json
import logging
from datetime import datetime
from google.cloud import firestore
from music.api.decorators import login_required, login_or_basic_auth, admin_required, gae_cron, cloud_task
from music.cloud.tasks import execute_all_user_playlists, execute_user_playlists, create_run_user_playlist_task, \
create_play_user_playlist_task
from music.tasks.run_user_playlist import run_user_playlist as run_user_playlist
from music.tasks.play_user_playlist import play_user_playlist as play_user_playlist
from music.model.user import User
from music.model.playlist import Playlist
import music.db.database as database
blueprint = Blueprint('api', __name__)
db = firestore.Client()
logger = logging.getLogger(__name__)
@blueprint.route('/playlists', methods=['GET'])
@login_or_basic_auth
def get_playlists(username=None):
def get_playlists(user=None):
assert user is not None
return jsonify({
'playlists': [i.to_dict() for i in database.get_user_playlists(username)]
'playlists': [i.to_dict() for i in Playlist.collection.parent(user.key).fetch()]
}), 200
@blueprint.route('/playlist', methods=['GET', 'POST', 'PUT', 'DELETE'])
@login_or_basic_auth
def playlist(username=None):
user_playlists = database.get_user_playlists(username)
user_ref = database.get_user(username).db_ref
playlists = user_ref.collection(u'playlists')
def playlist_route(user=None):
if request.method == 'GET' or request.method == 'DELETE':
playlist_name = request.args.get('name', None)
if playlist_name:
playlist = Playlist.collection.parent(user.key).filter('name', '==', playlist_name).get()
queried_playlist = next((i for i in user_playlists if i.name == playlist_name), None)
if queried_playlist is None:
return jsonify({'error': 'no playlist found'}), 404
if playlist is None:
return jsonify({'error': f'playlist {playlist_name} not found'}), 404
if request.method == "GET":
return jsonify(queried_playlist.to_dict()), 200
return jsonify(playlist.to_dict()), 200
elif request.method == 'DELETE':
database.delete_playlist(username=username, name=playlist_name)
Playlist.collection.parent(user.key).delete(key=playlist.key)
return jsonify({"message": 'playlist deleted', "status": "success"}), 200
else:
@ -75,9 +72,9 @@ def playlist(username=None):
if request_json['playlist_references'] != -1:
for i in request_json['playlist_references']:
updating_playlist = database.get_playlist(username=username, name=i)
updating_playlist = Playlist.collection.parent(user.key).filter('name', '==', i).get()
if updating_playlist is not None:
playlist_references.append(updating_playlist.db_ref)
playlist_references.append(db.document(updating_playlist.key))
else:
return jsonify({"message": f'managed playlist {i} not found', "status": "error"}), 400
@ -100,168 +97,143 @@ def playlist(username=None):
playlist_chart_range = request_json.get('chart_range', None)
playlist_chart_limit = request_json.get('chart_limit', None)
queried_playlist = [i for i in playlists.where(u'name', u'==', playlist_name).stream()]
playlist = Playlist.collection.parent(user.key).filter('name', '==', playlist_name).get()
if request.method == 'PUT':
if len(queried_playlist) != 0:
if playlist is not None:
return jsonify({'error': 'playlist already exists'}), 400
from music.tasks.create_playlist import create_playlist as create_playlist
to_add = {
'name': playlist_name,
'parts': playlist_parts if playlist_parts is not None else [],
'playlist_references': playlist_references if playlist_references is not None else [],
'include_library_tracks': playlist_library_tracks if playlist_library_tracks is not None else False,
'include_recommendations': playlist_recommendation if playlist_recommendation is not None else False,
'recommendation_sample': playlist_recommendation_sample if playlist_recommendation_sample is not None else 10,
'uri': None,
'shuffle': playlist_shuffle if playlist_shuffle is not None else False,
'type': playlist_type if playlist_type is not None else 'default',
'last_updated': datetime.utcnow(),
new_db_playlist = Playlist(parent=user.key)
'lastfm_stat_count': 0,
'lastfm_stat_album_count': 0,
'lastfm_stat_artist_count': 0,
new_db_playlist.name = playlist_name
new_db_playlist.parts = playlist_parts
new_db_playlist.playlist_references = playlist_references
'lastfm_stat_percent': 0,
'lastfm_stat_album_percent': 0,
'lastfm_stat_artist_percent': 0,
new_db_playlist.include_library_tracks = playlist_library_tracks
new_db_playlist.include_recommendations = playlist_recommendation
new_db_playlist.recommendation_sample = playlist_recommendation_sample
'lastfm_stat_last_refresh': datetime.utcnow(),
}
new_db_playlist.shuffle = playlist_shuffle
if user_ref.get().to_dict()['spotify_linked']:
new_playlist = create_playlist(username, playlist_name)
to_add['uri'] = str(new_playlist.uri) if new_playlist is not None else None
new_db_playlist.type = playlist_type
new_db_playlist.last_updated = datetime.utcnow()
new_db_playlist.lastfm_stat_last_refresh = datetime.utcnow()
if playlist_type == 'recents':
to_add['day_boundary'] = playlist_day_boundary if playlist_day_boundary is not None else 21
to_add['add_this_month'] = playlist_add_this_month if playlist_add_this_month is not None else False
to_add['add_last_month'] = playlist_add_last_month if playlist_add_last_month is not None else False
new_db_playlist.day_boundary = playlist_day_boundary
new_db_playlist.add_this_month = playlist_add_this_month
new_db_playlist.add_last_month = playlist_add_last_month
if playlist_type == 'fmchart':
to_add['chart_range'] = playlist_chart_range
to_add['chart_limit'] = playlist_chart_limit if playlist_chart_limit is not None else 50
new_db_playlist.chart_range = playlist_chart_range
new_db_playlist.chart_limit = playlist_chart_limit
playlists.document().set(to_add)
logger.info(f'added {username} / {playlist_name}')
if user.spotify_linked:
new_playlist = create_playlist(user, playlist_name)
new_db_playlist.uri = str(new_playlist.uri)
new_db_playlist.save()
logger.info(f'added {user.username} / {playlist_name}')
return jsonify({"message": 'playlist added', "status": "success"}), 201
elif request.method == 'POST':
if len(queried_playlist) == 0:
if playlist is None:
return jsonify({'error': "playlist doesn't exist"}), 400
if len(queried_playlist) > 1:
return jsonify({'error': "multiple playlists exist"}), 500
updating_playlist = database.get_playlist(username=username, name=playlist_name)
dic = {}
updating_playlist = Playlist.collection.parent(user.key).filter('name', '==', playlist_name).get()
if playlist_parts is not None:
if playlist_parts == -1:
dic['parts'] = []
updating_playlist.parts = []
else:
dic['parts'] = playlist_parts
updating_playlist.parts = playlist_parts
if playlist_references is not None:
if playlist_references == -1:
dic['playlist_references'] = []
updating_playlist.playlist_references = []
else:
dic['playlist_references'] = playlist_references
updating_playlist.playlist_references = playlist_references
if playlist_uri is not None:
dic['uri'] = playlist_uri
updating_playlist.uri = playlist_uri
if playlist_shuffle is not None:
dic['shuffle'] = playlist_shuffle
updating_playlist.shuffle = playlist_shuffle
if playlist_day_boundary is not None:
dic['day_boundary'] = playlist_day_boundary
updating_playlist.day_boundary = playlist_day_boundary
if playlist_add_this_month is not None:
dic['add_this_month'] = playlist_add_this_month
updating_playlist.add_this_month = playlist_add_this_month
if playlist_add_last_month is not None:
dic['add_last_month'] = playlist_add_last_month
updating_playlist.add_last_month = playlist_add_last_month
if playlist_library_tracks is not None:
dic['include_library_tracks'] = playlist_library_tracks
updating_playlist.include_library_tracks = playlist_library_tracks
if playlist_recommendation is not None:
dic['include_recommendations'] = playlist_recommendation
updating_playlist.include_recommendations = playlist_recommendation
if playlist_recommendation_sample is not None:
dic['recommendation_sample'] = playlist_recommendation_sample
updating_playlist.recommendation_sample = playlist_recommendation_sample
if playlist_chart_range is not None:
dic['chart_range'] = playlist_chart_range
updating_playlist.chart_range = playlist_chart_range
if playlist_chart_limit is not None:
dic['chart_limit'] = playlist_chart_limit
updating_playlist.chart_limit = playlist_chart_limit
if playlist_type is not None:
dic['type'] = playlist_type
# TODO check acceptable value
updating_playlist.type = playlist_type
if playlist_type == 'fmchart':
dic['chart_range'] = 'YEAR'
dic['chart_limit'] = 50
if len(dic) == 0:
logger.warning(f'no changes to make for {username} / {playlist_name}')
return jsonify({"message": 'no changes to make', "status": "error"}), 400
updating_playlist.update_database(dic)
logger.info(f'updated {username} / {playlist_name}')
updating_playlist.update()
logger.info(f'updated {user.username} / {playlist_name}')
return jsonify({"message": 'playlist updated', "status": "success"}), 200
@blueprint.route('/user', methods=['GET', 'POST'])
@login_or_basic_auth
def user(username=None):
def user_route(user=None):
assert user is not None
if request.method == 'GET':
return jsonify(user.to_dict()), 200
database_user = database.get_user(username)
return jsonify(database_user.to_dict()), 200
else:
db_user = database.get_user(username)
if db_user.user_type != db_user.Type.admin:
return jsonify({'status': 'error', 'message': 'unauthorized'}), 401
else: # POST
request_json = request.get_json()
if 'username' in request_json:
username = request_json['username']
if request_json['username'].strip().lower() != user.username:
if user.type != "admin":
return jsonify({'status': 'error', 'message': 'unauthorized'}), 401
actionable_user = database.get_user(username)
user = User.collection.filter('username', '==', request_json['username'].strip().lower()).get()
if 'locked' in request_json:
logger.info(f'updating lock {username} / {request_json["locked"]}')
actionable_user.locked = request_json['locked']
if user.type == "admin":
logger.info(f'updating lock {user.username} / {request_json["locked"]}')
user.locked = request_json['locked']
if 'spotify_linked' in request_json:
logger.info(f'deauthing {username}')
logger.info(f'deauthing {user.username}')
if request_json['spotify_linked'] is False:
actionable_user.update_database({
'access_token': None,
'refresh_token': None,
'spotify_linked': False
})
user.access_token = None
user.refresh_token = None
user.spotify_linked = False
if 'lastfm_username' in request_json:
logger.info(f'updating lastfm username {username} -> {request_json["lastfm_username"]}')
actionable_user.lastfm_username = request_json['lastfm_username']
logger.info(f'updating lastfm username {user.username} -> {request_json["lastfm_username"]}')
user.lastfm_username = request_json['lastfm_username']
logger.info(f'updated {username}')
user.update()
logger.info(f'updated {user.username}')
return jsonify({'message': 'account updated', 'status': 'succeeded'}), 200
@ -269,16 +241,15 @@ def user(username=None):
@blueprint.route('/users', methods=['GET'])
@login_or_basic_auth
@admin_required
def users(username=None):
def users(user=None):
return jsonify({
'accounts': [i.to_dict() for i in database.get_users()]
'accounts': [i.to_dict() for i in User.collection.fetch()]
}), 200
@blueprint.route('/user/password', methods=['POST'])
@login_required
def change_password(username=None):
def change_password(user=None):
request_json = request.get_json()
if 'new_password' in request_json and 'current_password' in request_json:
@ -289,14 +260,14 @@ def change_password(username=None):
if len(request_json['new_password']) > 30:
return jsonify({"error": 'password too long'}), 400
db_user = database.get_user(username)
if db_user.check_password(request_json['current_password']):
db_user.password = request_json['new_password']
logger.info(f'password udpated {username}')
if user.check_password(request_json['current_password']):
user.password = generate_password_hash(request_json['new_password'])
user.update()
logger.info(f'password udpated {user.username}')
return jsonify({"message": 'password changed', "status": "success"}), 200
else:
logger.warning(f"incorrect password {username}")
logger.warning(f"incorrect password {user.username}")
return jsonify({'error': 'wrong password provided'}), 401
else:
@ -305,7 +276,7 @@ def change_password(username=None):
@blueprint.route('/playlist/play', methods=['POST'])
@login_or_basic_auth
def play_playlist(username=None):
def play_playlist(user=None):
request_json = request.get_json()
@ -321,12 +292,12 @@ def play_playlist(username=None):
request_device_name = request_json.get('device_name', None)
logger.info(f'playing {username}')
logger.info(f'playing {user.username}')
if (request_parts and len(request_parts) > 0) or (request_playlists and len(request_playlists) > 0):
if os.environ.get('DEPLOY_DESTINATION', None) == 'PROD':
create_play_user_playlist_task(username,
create_play_user_playlist_task(user.username,
parts=request_parts,
playlist_type=request_playlist_type,
playlists=request_playlists,
@ -338,7 +309,7 @@ def play_playlist(username=None):
add_last_month=request_add_last_month,
device_name=request_device_name)
else:
play_user_playlist(username,
play_user_playlist(user.username,
parts=request_parts,
playlist_type=request_playlist_type,
playlists=request_playlists,
@ -352,7 +323,7 @@ def play_playlist(username=None):
return jsonify({'message': 'execution requested', 'status': 'success'}), 200
else:
logger.error(f'no playlists/parts {username}')
logger.error(f'no playlists/parts {user.username}')
return jsonify({'error': 'insufficient playlist sources'}), 400
@ -381,16 +352,16 @@ def play_playlist_task():
@blueprint.route('/playlist/run', methods=['GET'])
@login_or_basic_auth
def run_playlist(username=None):
def run_playlist(user=None):
playlist_name = request.args.get('name', None)
if playlist_name:
if os.environ.get('DEPLOY_DESTINATION', None) == 'PROD':
create_run_user_playlist_task(username, playlist_name)
create_run_user_playlist_task(user.username, playlist_name)
else:
run_user_playlist(username, playlist_name)
run_user_playlist(user.username, playlist_name)
return jsonify({'message': 'execution requested', 'status': 'success'}), 200
@ -416,13 +387,12 @@ def run_playlist_task():
@blueprint.route('/playlist/run/user', methods=['GET'])
@login_or_basic_auth
def run_user(username=None):
def run_user(user=None):
db_user = database.get_user(username)
if db_user.user_type == db_user.Type.admin:
user_name = request.args.get('username', username)
if user.type == 'admin':
user_name = request.args.get('username', user.username)
else:
user_name = username
user_name = user.username
execute_user_playlists(user_name)
@ -442,7 +412,7 @@ def run_user_task():
@blueprint.route('/playlist/run/users', methods=['GET'])
@login_or_basic_auth
@admin_required
def run_users(username=None):
def run_users(user=None):
execute_all_user_playlists()
return jsonify({'message': 'executed all users', 'status': 'success'}), 200
@ -458,18 +428,18 @@ def run_users_cron():
@blueprint.route('/playlist/image', methods=['GET'])
@login_or_basic_auth
def image(username=None):
def image(user=None):
name = request.args.get('name', None)
if name is None:
return jsonify({'error': "no name provided"}), 400
_playlist = database.get_playlist(name=name, username=username)
_playlist = Playlist.collection.parent(user.key).filter('name', '==', name).get()
if _playlist is None:
return jsonify({'error': "playlist not found"}), 404
net = database.get_authed_spotify_network(username=username)
net = database.get_authed_spotify_network(user)
spotify_playlist = net.get_playlist(uri_string=_playlist.uri)

View File

@ -3,7 +3,7 @@ import logging
from flask import session, request, jsonify
from music.db import database as database
from music.model.user import User
logger = logging.getLogger(__name__)
@ -18,17 +18,24 @@ def is_logged_in():
def is_basic_authed():
if request.authorization:
if request.authorization.get('username', None) and request.authorization.get('password', None):
if database.get_user(request.authorization.username).check_password(request.authorization.password):
return True
user = User.collection.filter('username', '==', request.authorization.username.strip().lower()).get()
if user is None:
return False, None
return False
if user.check_password(request.authorization.password):
return True, user
else:
return False, user
return False, None
def login_required(func):
@functools.wraps(func)
def login_required_wrapper(*args, **kwargs):
if is_logged_in():
return func(username=session['username'], *args, **kwargs)
user = User.collection.filter('username', '==', session['username'].strip().lower()).get()
return func(user=user, *args, **kwargs)
else:
logger.warning('user not logged in')
return jsonify({'error': 'not logged in'}), 401
@ -39,12 +46,15 @@ def login_or_basic_auth(func):
@functools.wraps(func)
def login_or_basic_auth_wrapper(*args, **kwargs):
if is_logged_in():
return func(username=session['username'], *args, **kwargs)
elif is_basic_authed():
return func(username=request.authorization.username, *args, **kwargs)
user = User.collection.filter('username', '==', session['username'].strip().lower()).get()
return func(user=user, *args, **kwargs)
else:
logger.warning('user not logged in')
return jsonify({'error': 'not logged in'}), 401
check, user = is_basic_authed()
if check:
return func(user=user, *args, **kwargs)
else:
logger.warning('user not logged in')
return jsonify({'error': 'not logged in'}), 401
return login_or_basic_auth_wrapper
@ -52,10 +62,10 @@ def login_or_basic_auth(func):
def admin_required(func):
@functools.wraps(func)
def admin_required_wrapper(*args, **kwargs):
db_user = database.get_user(kwargs.get('username'))
db_user = kwargs.get('user')
if db_user is not None:
if db_user.user_type == db_user.Type.admin:
if db_user.type == 'admin':
return func(*args, **kwargs)
else:
logger.warning(f'{db_user.username} not authorized')
@ -70,7 +80,7 @@ def admin_required(func):
def spotify_link_required(func):
@functools.wraps(func)
def spotify_link_required_wrapper(*args, **kwargs):
db_user = database.get_user(kwargs.get('username'))
db_user = kwargs.get('user')
if db_user is not None:
if db_user.spotify_linked:
@ -88,7 +98,7 @@ def spotify_link_required(func):
def lastfm_username_required(func):
@functools.wraps(func)
def lastfm_username_required_wrapper(*args, **kwargs):
db_user = database.get_user(kwargs.get('username'))
db_user = kwargs.get('user')
if db_user is not None:
if db_user.lastfm_username and len(db_user.lastfm_username) > 0:

View File

@ -13,9 +13,9 @@ logger = logging.getLogger(__name__)
@blueprint.route('/today', methods=['GET'])
@login_or_basic_auth
@lastfm_username_required
def daily_scrobbles(username=None):
def daily_scrobbles(user=None):
net = database.get_authed_lastfm_network(username)
net = database.get_authed_lastfm_network(user)
total = net.get_scrobble_count_from_date(input_date=date.today())

View File

@ -18,7 +18,7 @@ logger = logging.getLogger(__name__)
@blueprint.route('/play', methods=['POST'])
@login_or_basic_auth
@spotify_link_required
def play(username=None):
def play(user=None):
request_json = request.get_json()
if 'uri' in request_json:
@ -27,7 +27,7 @@ def play(username=None):
if uri.object_type in [Uri.ObjectType.album, Uri.ObjectType.artist, Uri.ObjectType.playlist]:
context = Context(uri)
net = database.get_authed_spotify_network(username)
net = database.get_authed_spotify_network(user)
player = Player(net)
player.play(context=context, device_name=request_json.get('device_name', None))
@ -39,7 +39,7 @@ def play(username=None):
except ValueError:
return jsonify({'error': "malformed uri provided"}), 400
elif 'playlist_name' in request_json:
net = database.get_authed_spotify_network(username)
net = database.get_authed_spotify_network(user)
playlists = net.get_playlists()
if playlists is not None:
playlist_to_play = next((i for i in playlists if i.name == request_json['playlist_name']), None)
@ -60,7 +60,7 @@ def play(username=None):
uris = [SpotifyTrack.wrap(uri=i) for i in uris if i.object_type == Uri.ObjectType.track]
if len(uris) > 0:
net = database.get_authed_spotify_network(username)
net = database.get_authed_spotify_network(user)
player = Player(net)
player.play(tracks=uris, device_name=request_json.get('device_name', None))
@ -78,8 +78,8 @@ def play(username=None):
@blueprint.route('/next', methods=['POST'])
@login_or_basic_auth
@spotify_link_required
def next_track(username=None):
net = database.get_authed_spotify_network(username)
def next_track(user=None):
net = database.get_authed_spotify_network(user)
player = Player(net)
player.next()
@ -89,12 +89,12 @@ def next_track(username=None):
@blueprint.route('/shuffle', methods=['POST'])
@login_or_basic_auth
@spotify_link_required
def shuffle(username=None):
def shuffle(user=None):
request_json = request.get_json()
if 'state' in request_json:
if isinstance(request_json['state'], bool):
net = database.get_authed_spotify_network(username)
net = database.get_authed_spotify_network(user)
player = Player(net)
player.shuffle(state=request_json['state'])
@ -108,13 +108,13 @@ def shuffle(username=None):
@blueprint.route('/volume', methods=['POST'])
@login_or_basic_auth
@spotify_link_required
def volume(username=None):
def volume(user=None):
request_json = request.get_json()
if 'volume' in request_json:
if isinstance(request_json['volume'], int):
if 0 <= request_json['volume'] <= 100:
net = database.get_authed_spotify_network(username)
net = database.get_authed_spotify_network(user)
player = Player(net)
player.volume(value=request_json['volume'])

View File

@ -21,7 +21,7 @@ logger = logging.getLogger(__name__)
@login_or_basic_auth
@spotify_link_required
@lastfm_username_required
def count(username=None):
def count(user=None):
uri = request.args.get('uri', None)
playlist_name = request.args.get('playlist_name', None)
@ -35,8 +35,8 @@ def count(username=None):
except ValueError:
return jsonify({'error': 'malformed uri provided'}), 401
spotnet = database.get_authed_spotify_network(username)
fmnet = database.get_authed_lastfm_network(username)
spotnet = database.get_authed_spotify_network(user)
fmnet = database.get_authed_lastfm_network(user)
counter = Counter(fmnet=fmnet, spotnet=spotnet)
if uri:
@ -67,18 +67,18 @@ def count(username=None):
@login_or_basic_auth
@spotify_link_required
@lastfm_username_required
def playlist_refresh(username=None):
def playlist_refresh(user=None):
playlist_name = request.args.get('name', None)
if playlist_name:
if os.environ.get('DEPLOY_DESTINATION', None) == 'PROD':
create_refresh_playlist_task(username, playlist_name)
create_refresh_playlist_task(user.username, playlist_name)
else:
refresh_lastfm_track_stats(username, playlist_name)
refresh_lastfm_album_stats(username, playlist_name)
refresh_lastfm_artist_stats(username, playlist_name)
refresh_lastfm_track_stats(user.username, playlist_name)
refresh_lastfm_album_stats(user.username, playlist_name)
refresh_lastfm_artist_stats(user.username, playlist_name)
return jsonify({'message': 'execution requested', 'status': 'success'}), 200
@ -135,7 +135,7 @@ def run_playlist_artist_task():
@blueprint.route('/playlist/refresh/users', methods=['GET'])
@login_or_basic_auth
@admin_required
def run_users(username=None):
def run_users(user=None):
execute_all_user_playlist_stats()
return jsonify({'message': 'executed all users', 'status': 'success'}), 200
@ -149,13 +149,12 @@ def run_users_task():
@blueprint.route('/playlist/refresh/user', methods=['GET'])
@login_or_basic_auth
def run_user(username=None):
def run_user(user=None):
db_user = database.get_user(username)
if db_user.type == db_user.Type.admin:
user_name = request.args.get('username', username)
if user.type == 'admin':
user_name = request.args.get('username', user.username)
else:
user_name = username
user_name = user.username
execute_user_playlist_stats(user_name)

View File

@ -14,10 +14,10 @@ logger = logging.getLogger(__name__)
@blueprint.route('/sort', methods=['POST'])
@login_or_basic_auth
@spotify_link_required
def play(username=None):
def play(user=None):
request_json = request.get_json()
net = database.get_authed_spotify_network(username)
net = database.get_authed_spotify_network(user)
engine = PlaylistEngine(net)
reverse = request_json.get('reverse', False)

View File

@ -2,40 +2,41 @@ from flask import Blueprint, jsonify, request
import logging
import music.db.database as database
from music.api.decorators import login_or_basic_auth
from music.cloud.function import update_tag
from music.model.tag import Tag
blueprint = Blueprint('task', __name__)
logger = logging.getLogger(__name__)
@blueprint.route('/tag', methods=['GET'])
@login_or_basic_auth
def tags(username=None):
logger.info(f'retrieving tags for {username}')
def tags(user=None):
logger.info(f'retrieving tags for {user.username}')
return jsonify({
'tags': [i.to_dict() for i in database.get_user_tags(username)]
'tags': [i.to_dict() for i in Tag.collection.parent(user.key).fetch()]
}), 200
@blueprint.route('/tag/<tag_id>', methods=['GET', 'PUT', 'POST', "DELETE"])
@login_or_basic_auth
def tag(tag_id, username=None):
def tag_route(tag_id, user=None):
if request.method == 'GET':
return get_tag(tag_id, username)
return get_tag(tag_id, user)
elif request.method == 'PUT':
return put_tag(tag_id, username)
return put_tag(tag_id, user)
elif request.method == 'POST':
return post_tag(tag_id, username)
return post_tag(tag_id, user)
elif request.method == 'DELETE':
return delete_tag(tag_id, username)
return delete_tag(tag_id, user)
def get_tag(tag_id, username):
logger.info(f'retriving {tag_id} for {username}')
def get_tag(tag_id, user):
logger.info(f'retriving {tag_id} for {user.username}')
db_tag = database.get_tag(username=username, tag_id=tag_id)
db_tag = Tag.collection.parent(user.key).filter('tag_id', '==', tag_id).get()
if db_tag is not None:
return jsonify({
'tag': db_tag.to_dict()
@ -44,10 +45,10 @@ def get_tag(tag_id, username):
return jsonify({"error": 'tag not found'}), 404
def put_tag(tag_id, username):
logger.info(f'updating {tag_id} for {username}')
def put_tag(tag_id, user):
logger.info(f'updating {tag_id} for {user.username}')
db_tag = database.get_tag(username=username, tag_id=tag_id)
db_tag = Tag.collection.parent(user.key).filter('tag_id', '==', tag_id).get()
if db_tag is None:
return jsonify({"error": 'tag not found'}), 404
@ -92,34 +93,38 @@ def put_tag(tag_id, username):
db_tag.artists = artists
if update_required:
update_tag(username=username, tag_id=tag_id)
update_tag(username=user.username, tag_id=tag_id)
db_tag.update()
return jsonify({"message": 'tag updated', "status": "success"}), 200
def post_tag(tag_id, username):
logger.info(f'creating {tag_id} for {username}')
def post_tag(tag_id, user):
logger.info(f'creating {tag_id} for {user.username}')
tag_id = tag_id.replace(' ', '_')
database.create_tag(username=username, tag_id=tag_id)
tag = Tag(parent=user.key)
tag.tag_id = tag_id
tag.name = tag_id
tag.username = user.username
tag.save()
return jsonify({"message": 'tag added', "status": "success"}), 201
def delete_tag(tag_id, username):
logger.info(f'deleting {tag_id} for {username}')
def delete_tag(tag_id, user):
logger.info(f'deleting {tag_id} for {user.username}')
response = database.delete_tag(username=username, tag_id=tag_id)
db_tag = Tag.collection.parent(user.key).filter('tag_id', '==', tag_id).get()
Tag.collection.parent(user.key).delete(key=db_tag.key)
if response is not None:
return jsonify({"message": 'tag deleted', "status": "success"}), 201
else:
return jsonify({"error": 'tag not deleted'}), 400
return jsonify({"message": 'tag deleted', "status": "success"}), 201
@blueprint.route('/tag/<tag_id>/update', methods=['GET'])
@login_or_basic_auth
def tag_refresh(tag_id, username=None):
logger.info(f'updating {tag_id} tag for {username}')
update_tag(username=username, tag_id=tag_id)
def tag_refresh(tag_id, user=None):
logger.info(f'updating {tag_id} tag for {user.username}')
update_tag(username=user.username, tag_id=tag_id)
return jsonify({"message": 'tag updated', "status": "success"}), 200

View File

@ -1,6 +1,7 @@
from flask import Blueprint, session, flash, request, redirect, url_for, render_template
from google.cloud import firestore
from werkzeug.security import check_password_hash, generate_password_hash
from werkzeug.security import generate_password_hash
from music.model.user import User
import urllib
import datetime
@ -8,8 +9,6 @@ import logging
from base64 import b64encode
import requests
import music.db.database as database
blueprint = Blueprint('authapi', __name__)
db = firestore.Client()
@ -31,8 +30,7 @@ def login():
flash('malformed request')
return redirect(url_for('index'))
username = username.lower()
user = database.get_user(username)
user = User.collection.filter('username', '==', username.strip().lower()).get()
if user is None:
flash('user not found')
@ -45,6 +43,7 @@ def login():
return redirect(url_for('index'))
user.last_login = datetime.datetime.utcnow()
user.update()
logger.info(f'success {username}')
session['username'] = username
@ -91,12 +90,17 @@ def register():
flash('password mismatch')
return redirect('authapi.register')
if username in [i.to_dict()['username'] for i in
db.collection(u'spotify_users').where(u'username', u'==', username).stream()]:
if username in [i.username for i in
User.collection.fetch()]:
flash('username already registered')
return redirect('authapi.register')
database.create_user(username=username, password=password)
user = User()
user.username = username
user.password = generate_password_hash(password)
user.last_login = datetime.utcnow()
user.save()
logger.info(f'new user {username}')
session['username'] = username
@ -150,15 +154,15 @@ def token():
resp = req.json()
user = database.get_user(session['username'])
user = User.collection.filter('username', '==', session['username'].strip().lower()).get()
user.update_database({
'access_token': resp['access_token'],
'refresh_token': resp['refresh_token'],
'last_refreshed': datetime.datetime.now(datetime.timezone.utc),
'token_expiry': resp['expires_in'],
'spotify_linked': True
})
user.access_token = resp['access_token']
user.refresh_token = resp['refresh_token']
user.last_refreshed = datetime.datetime.now(datetime.timezone.utc)
user.token_expiry = resp['expires_in']
user.spotify_linked = True
user.update()
else:
flash('http error on token request')
@ -174,15 +178,15 @@ def deauth():
if 'username' in session:
user = database.get_user(session['username'])
user = User.collection.filter('username', '==', session['username'].strip().lower()).get()
user.update_database({
'access_token': None,
'refresh_token': None,
'last_refreshed': datetime.datetime.now(datetime.timezone.utc),
'token_expiry': None,
'spotify_linked': False
})
user.access_token = None
user.refresh_token = None
user.last_refreshed = datetime.datetime.now(datetime.timezone.utc)
user.token_expiry = None
user.spotify_linked = False
user.update()
return redirect('/app/settings/spotify')

View File

@ -10,6 +10,9 @@ from music.db import database as database
from music.tasks.run_user_playlist import run_user_playlist
from music.tasks.refresh_lastfm_stats import refresh_lastfm_track_stats
from music.model.user import User
from music.model.playlist import Playlist
tasker = tasks_v2.CloudTasksClient()
task_path = tasker.queue_path('sarsooxyz', 'europe-west2', 'spotify-executions')
@ -21,7 +24,7 @@ def execute_all_user_playlists():
seconds_delay = 0
logger.info('running')
for iter_user in database.get_users():
for iter_user in User.collection.fetch():
if iter_user.spotify_linked and not iter_user.locked:
@ -45,14 +48,18 @@ def execute_all_user_playlists():
def execute_user_playlists(username):
user = User.collection.filter('username', '==', username.strip().lower()).get()
playlists = database.get_user_playlists(username)
if user is None:
logger.error(f'user {username} not found')
playlists = Playlist.collection.parent(user.key).fetch()
seconds_delay = 0
logger.info(f'running {username}')
for iterate_playlist in playlists:
if iterate_playlist.uri:
if iterate_playlist.uri is not None:
if os.environ.get('DEPLOY_DESTINATION', None) == 'PROD':
create_run_user_playlist_task(username, iterate_playlist.name, seconds_delay)
@ -139,7 +146,7 @@ def execute_all_user_playlist_stats():
seconds_delay = 0
logger.info('running')
for iter_user in database.get_users():
for iter_user in User.collection.fetch():
if iter_user.spotify_linked and iter_user.lastfm_username and \
len(iter_user.lastfm_username) > 0 and not iter_user.locked:
@ -157,15 +164,18 @@ def execute_all_user_playlist_stats():
def execute_user_playlist_stats(username):
playlists = database.get_user_playlists(username)
user = database.get_user(username)
user = User.collection.filter('username', '==', username.strip().lower()).get()
if user is None:
logger.error(f'user {username} not found')
playlists = Playlist.collection.parent(user.key).fetch()
seconds_delay = 0
logger.info(f'running {username}')
if user.lastfm_username and len(user.lastfm_username) > 0:
for playlist in playlists:
if playlist.uri:
if playlist.uri is not None:
if os.environ.get('DEPLOY_DESTINATION', None) == 'PROD':
create_refresh_playlist_task(username, playlist.name, seconds_delay)

View File

@ -1,15 +1,11 @@
from google.cloud import firestore
import logging
from datetime import timedelta, datetime, timezone
from typing import List, Optional
from werkzeug.security import generate_password_hash
from spotframework.net.network import Network as SpotifyNetwork
from fmframework.net.network import Network as FmNetwork
from music.db.user import DatabaseUser
from music.model.user import User
from music.model.playlist import Playlist, RecentsPlaylist, LastFMChartPlaylist, Sort
from music.model.tag import Tag
db = firestore.Client()
@ -18,22 +14,23 @@ logger = logging.getLogger(__name__)
def refresh_token_database_callback(user):
if isinstance(user, DatabaseUser):
user_obj = get_user(user.user_id)
user_obj = User.collection.filter('username', '==', user.user_id.strip().lower()).get()
if user_obj is None:
logger.error(f'user {user} not found')
user_obj.access_token = user.access_token
user_obj.refresh_token = user.refresh_token
user_obj.last_refreshed = user.last_refreshed
user_obj.token_expiry = user.token_expiry
user_obj.update()
user_obj.update_database({
'access_token': user.access_token,
'refresh_token': user.refresh_token,
'last_refreshed': user.last_refreshed,
'token_expiry': user.token_expiry
})
logger.debug(f'{user.user_id} database entry updated')
else:
logger.error('user has no attached id')
def get_authed_spotify_network(username):
user = get_user(username)
def get_authed_spotify_network(user):
if user is not None:
if user.spotify_linked:
spotify_keys = db.document('key/spotify').get().to_dict()
@ -41,7 +38,7 @@ def get_authed_spotify_network(username):
user_obj = DatabaseUser(client_id=spotify_keys['clientid'],
client_secret=spotify_keys['clientsecret'],
refresh_token=user.refresh_token,
user_id=username,
user_id=user.username,
access_token=user.access_token)
user_obj.on_refresh.append(refresh_token_database_callback)
@ -54,403 +51,15 @@ def get_authed_spotify_network(username):
else:
logger.error('user spotify not linked')
else:
logger.error(f'user {username} not found')
logger.error(f'user {user.username} not found')
def get_authed_lastfm_network(username):
user = get_user(username)
def get_authed_lastfm_network(user):
if user:
if user.lastfm_username:
fm_keys = db.document('key/fm').get().to_dict()
return FmNetwork(username=user.lastfm_username, api_key=fm_keys['clientid'])
else:
logger.error(f'{username} has no last.fm username')
logger.error(f'{user.username} has no last.fm username')
else:
logger.error(f'user {username} not found')
def get_users() -> List[User]:
logger.info('retrieving users')
return [parse_user_reference(user_snapshot=i) for i in db.collection(u'spotify_users').stream()]
def get_user(username: str) -> Optional[User]:
logger.info(f'retrieving {username}')
users = [i for i in db.collection(u'spotify_users').where(u'username', u'==', username).stream()]
if len(users) == 0:
logger.error(f'user {username} not found')
return None
if len(users) > 1:
logger.critical(f"multiple {username}'s found")
return None
return parse_user_reference(user_snapshot=users[0])
def parse_user_reference(user_ref=None, user_snapshot=None) -> User:
if user_ref is None and user_snapshot is None:
raise ValueError('no user object supplied')
if user_ref is None:
user_ref = user_snapshot.reference
if user_snapshot is None:
user_snapshot = user_ref.get()
user_dict = user_snapshot.to_dict()
return User(username=user_dict.get('username'),
password=user_dict.get('password'),
db_ref=user_ref,
email=user_dict.get('email'),
user_type=User.Type[user_dict.get('type')],
last_login=user_dict.get('last_login'),
last_refreshed=user_dict.get('last_refreshed'),
locked=user_dict.get('locked'),
validated=user_dict.get('validated'),
spotify_linked=user_dict.get('spotify_linked'),
access_token=user_dict.get('access_token'),
refresh_token=user_dict.get('refresh_token'),
token_expiry=user_dict.get('token_expiry'),
lastfm_username=user_dict.get('lastfm_username'))
def update_user(username: str, updates: dict) -> None:
logger.debug(f'updating {username}')
users = [i for i in db.collection(u'spotify_users').where(u'username', u'==', username).stream()]
if len(users) == 0:
logger.error(f'user {username} not found')
return None
if len(users) > 1:
logger.critical(f"multiple {username}'s found")
return None
user = users[0].reference
user.update(updates)
def create_user(username: str, password: str):
db.collection(u'spotify_users').add({
'access_token': None,
'email': None,
'last_login': datetime.utcnow(),
'last_refreshed': None,
'locked': False,
'password': generate_password_hash(password),
'refresh_token': None,
'spotify_linked': False,
'type': 'user',
'username': username,
'validated': True
})
def get_user_playlists(username: str) -> List[Playlist]:
logger.info(f'getting playlists for {username}')
user = get_user(username)
if user:
playlist_refs = [i for i in user.db_ref.collection(u'playlists').stream()]
return [parse_playlist_reference(username=username, playlist_snapshot=i) for i in playlist_refs]
else:
logger.error(f'user {username} not found')
def get_playlist(username: str = None, name: str = None) -> Optional[Playlist]:
logger.info(f'retrieving {name} for {username}')
user = get_user(username)
if user:
playlists = [i for i in user.db_ref.collection(u'playlists').where(u'name', u'==', name).stream()]
if len(playlists) == 0:
logger.error(f'playlist {name} for {user} not found')
return None
if len(playlists) > 1:
logger.critical(f"multiple {name}'s for {user} found")
return None
return parse_playlist_reference(username=username, playlist_snapshot=playlists[0])
else:
logger.error(f'user {username} not found')
def parse_playlist_reference(username, playlist_ref=None, playlist_snapshot=None) -> Playlist:
if playlist_ref is None and playlist_snapshot is None:
raise ValueError('no playlist object supplied')
if playlist_ref is None:
playlist_ref = playlist_snapshot.reference
if playlist_snapshot is None:
playlist_snapshot = playlist_ref.get()
playlist_dict = playlist_snapshot.to_dict()
if playlist_dict.get('type') == 'default':
return Playlist(uri=playlist_dict.get('uri'),
name=playlist_dict.get('name'),
username=username,
db_ref=playlist_ref,
include_recommendations=playlist_dict.get('include_recommendations', False),
recommendation_sample=playlist_dict.get('recommendation_sample', 0),
include_library_tracks=playlist_dict.get('include_library_tracks', False),
parts=playlist_dict.get('parts'),
playlist_references=playlist_dict.get('playlist_references'),
shuffle=playlist_dict.get('shuffle'),
sort=Sort[playlist_dict.get('sort', 'release_date')],
description_overwrite=playlist_dict.get('description_overwrite'),
description_suffix=playlist_dict.get('description_suffix'),
last_updated=playlist_dict.get('last_updated'),
lastfm_stat_count=playlist_dict.get('lastfm_stat_count', 0),
lastfm_stat_album_count=playlist_dict.get('lastfm_stat_album_count', 0),
lastfm_stat_artist_count=playlist_dict.get('lastfm_stat_artist_count', 0),
lastfm_stat_percent=playlist_dict.get('lastfm_stat_percent', 0),
lastfm_stat_album_percent=playlist_dict.get('lastfm_stat_album_percent', 0),
lastfm_stat_artist_percent=playlist_dict.get('lastfm_stat_artist_percent', 0),
lastfm_stat_last_refresh=playlist_dict.get('lastfm_stat_last_refresh'))
elif playlist_dict.get('type') == 'recents':
return RecentsPlaylist(uri=playlist_dict.get('uri'),
name=playlist_dict.get('name'),
username=username,
db_ref=playlist_ref,
include_recommendations=playlist_dict.get('include_recommendations', False),
recommendation_sample=playlist_dict.get('recommendation_sample', 0),
include_library_tracks=playlist_dict.get('include_library_tracks', False),
parts=playlist_dict.get('parts'),
playlist_references=playlist_dict.get('playlist_references'),
shuffle=playlist_dict.get('shuffle'),
sort=Sort[playlist_dict.get('sort', 'release_date')],
description_overwrite=playlist_dict.get('description_overwrite'),
description_suffix=playlist_dict.get('description_suffix'),
last_updated=playlist_dict.get('last_updated'),
lastfm_stat_count=playlist_dict.get('lastfm_stat_count', 0),
lastfm_stat_album_count=playlist_dict.get('lastfm_stat_album_count', 0),
lastfm_stat_artist_count=playlist_dict.get('lastfm_stat_artist_count', 0),
lastfm_stat_percent=playlist_dict.get('lastfm_stat_percent', 0),
lastfm_stat_album_percent=playlist_dict.get('lastfm_stat_album_percent', 0),
lastfm_stat_artist_percent=playlist_dict.get('lastfm_stat_artist_percent', 0),
lastfm_stat_last_refresh=playlist_dict.get('lastfm_stat_last_refresh'),
add_last_month=playlist_dict.get('add_last_month'),
add_this_month=playlist_dict.get('add_this_month'),
day_boundary=playlist_dict.get('day_boundary'))
elif playlist_dict.get('type') == 'fmchart':
return LastFMChartPlaylist(uri=playlist_dict.get('uri'),
name=playlist_dict.get('name'),
username=username,
db_ref=playlist_ref,
include_recommendations=playlist_dict.get('include_recommendations', False),
recommendation_sample=playlist_dict.get('recommendation_sample', 0),
include_library_tracks=playlist_dict.get('include_library_tracks', False),
parts=playlist_dict.get('parts'),
playlist_references=playlist_dict.get('playlist_references'),
shuffle=playlist_dict.get('shuffle'),
sort=Sort[playlist_dict.get('sort', 'release_date')],
description_overwrite=playlist_dict.get('description_overwrite'),
description_suffix=playlist_dict.get('description_suffix'),
last_updated=playlist_dict.get('last_updated'),
lastfm_stat_count=playlist_dict.get('lastfm_stat_count', 0),
lastfm_stat_album_count=playlist_dict.get('lastfm_stat_album_count', 0),
lastfm_stat_artist_count=playlist_dict.get('lastfm_stat_artist_count', 0),
lastfm_stat_percent=playlist_dict.get('lastfm_stat_percent', 0),
lastfm_stat_album_percent=playlist_dict.get('lastfm_stat_album_percent', 0),
lastfm_stat_artist_percent=playlist_dict.get('lastfm_stat_artist_percent', 0),
lastfm_stat_last_refresh=playlist_dict.get('lastfm_stat_last_refresh'),
chart_limit=playlist_dict.get('chart_limit'),
chart_range=FmNetwork.Range[playlist_dict.get('chart_range')])
def update_playlist(username: str, name: str, updates: dict) -> None:
if len(updates) > 0:
logger.debug(f'updating {name} for {username}')
user = get_user(username)
playlists = [i for i in user.db_ref.collection(u'playlists').where(u'name', u'==', name).stream()]
if len(playlists) == 0:
logger.error(f'playlist {name} for {username} not found')
return None
if len(playlists) > 1:
logger.critical(f"multiple {name}'s for {username} found")
return None
playlist = playlists[0].reference
playlist.update(updates)
else:
logger.debug(f'nothing to update for {name} for {username}')
def delete_playlist(username: str, name: str) -> None:
logger.info(f'deleting {name} for {username}')
playlist = get_playlist(username=username, name=name)
if playlist:
playlist.db_ref.delete()
else:
logger.error(f'playlist {name} not found for {username}')
def get_user_tags(username: str) -> List[Tag]:
logger.info(f'getting tags for {username}')
user = get_user(username)
if user:
tag_refs = [i for i in user.db_ref.collection(u'tags').stream()]
return [parse_tag_reference(username=username, tag_snapshot=i) for i in tag_refs]
else:
logger.error(f'user {username} not found')
def get_tag(username: str = None, tag_id: str = None) -> Optional[Tag]:
logger.info(f'retrieving {tag_id} for {username}')
user = get_user(username)
if user:
tags = [i for i in user.db_ref.collection(u'tags').where(u'tag_id', u'==', tag_id).stream()]
if len(tags) == 0:
logger.error(f'tag {tag_id} for {user} not found')
return None
if len(tags) > 1:
logger.critical(f"multiple {tag_id}'s for {user} found")
return None
return parse_tag_reference(username=username, tag_snapshot=tags[0])
else:
logger.error(f'user {username} not found')
def parse_tag_reference(username, tag_ref=None, tag_snapshot=None) -> Tag:
if tag_ref is None and tag_snapshot is None:
raise ValueError('no tag object supplied')
if tag_ref is None:
tag_ref = tag_snapshot.reference
if tag_snapshot is None:
tag_snapshot = tag_ref.get()
tag_dict = tag_snapshot.to_dict()
return Tag(tag_id=tag_dict['tag_id'],
name=tag_dict.get('name', 'n/a'),
username=username,
db_ref=tag_ref,
tracks=tag_dict.get('tracks', []),
albums=tag_dict.get('albums', []),
artists=tag_dict.get('artists', []),
count=tag_dict.get('count', 0),
proportion=tag_dict.get('proportion', 0.0),
total_user_scrobbles=tag_dict.get('total_user_scrobbles', 0),
last_updated=tag_dict.get('last_updated'))
def update_tag(username: str, tag_id: str, updates: dict) -> None:
if len(updates) > 0:
logger.debug(f'updating {tag_id} for {username}')
user = get_user(username)
tags = [i for i in user.db_ref.collection(u'tags').where(u'tag_id', u'==', tag_id).stream()]
if len(tags) == 0:
logger.error(f'tag {tag_id} for {username} not found')
return None
if len(tags) > 1:
logger.critical(f"multiple {tag_id}'s for {username} found")
return None
tag = tags[0].reference
tag.update(updates)
else:
logger.debug(f'nothing to update for {tag_id} for {username}')
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
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
})
logger.error(f'user {user.username} not found')

View File

@ -1,9 +1,7 @@
from google.cloud import firestore
import music.db.database as database
from music.model.user import User
from music.model.playlist import Playlist
import logging
db = firestore.Client()
logger = logging.getLogger(__name__)
@ -16,7 +14,7 @@ class PartGenerator:
if user:
self.user = user
elif username:
pulled_user = database.get_user(username)
pulled_user = User.collection.filter('username', '==', username.strip().lower()).get()
if pulled_user:
self.user = pulled_user
else:
@ -38,13 +36,14 @@ class PartGenerator:
def process_reference_by_name(self, name):
playlist = database.get_playlist(username=self.user.username, name=name)
playlist = Playlist.collection.parent(self.user.key).filter('name', '==', name).get()
if playlist is not None:
if playlist.db_ref.id not in self.queried_playlists:
if playlist.id not in self.queried_playlists:
self.parts += playlist.parts
self.queried_playlists.append(playlist.id)
for i in playlist.playlist_references:
if i.id not in self.queried_playlists:
@ -61,6 +60,7 @@ class PartGenerator:
if ref.id not in self.queried_playlists:
playlist_reference_object = ref.get().to_dict()
self.parts += playlist_reference_object['parts']
self.queried_playlists.append(ref.id)
for i in playlist_reference_object['playlist_references']:
self.process_reference_by_reference(i)

View File

@ -1,11 +1,7 @@
from typing import List
from enum import Enum
from datetime import datetime
from google.cloud.firestore import DocumentReference
from fmframework.net.network import Network
import music.db.database as database
from fireo.models import Model
from fireo.fields import TextField, BooleanField, DateTime, NumberField, ListField
class Sort(Enum):
@ -14,466 +10,52 @@ class Sort(Enum):
release_date = 3
class Playlist:
def __init__(self,
uri: str,
name: str,
username: str,
class Playlist(Model):
class Meta:
collection_name = 'playlists'
db_ref: DocumentReference,
uri = TextField()
name = TextField(required=True)
type = TextField(required=True)
include_recommendations: bool,
recommendation_sample: int,
include_library_tracks: bool,
include_recommendations = BooleanField(default=False)
recommendation_sample = NumberField(default=10)
include_library_tracks = BooleanField(default=False)
parts: List[str],
playlist_references: List[DocumentReference],
shuffle: bool,
parts = ListField(default=[])
playlist_references = ListField(default=[])
shuffle = BooleanField(default=False)
sort: Sort = None,
sort = TextField(default='release_date')
description_overwrite = TextField()
description_suffix = TextField()
description_overwrite: str = None,
description_suffix: str = None,
last_updated = DateTime()
last_updated: datetime = None,
lastfm_stat_count = NumberField(default=0)
lastfm_stat_album_count = NumberField(default=0)
lastfm_stat_artist_count = NumberField(default=0)
lastfm_stat_count: int = None,
lastfm_stat_album_count: int = None,
lastfm_stat_artist_count: int = None,
lastfm_stat_percent = NumberField(default=0)
lastfm_stat_album_percent = NumberField(default=0)
lastfm_stat_artist_percent = NumberField(default=0)
lastfm_stat_percent: int = None,
lastfm_stat_album_percent: int = None,
lastfm_stat_artist_percent: int = None,
lastfm_stat_last_refresh = DateTime()
lastfm_stat_last_refresh: datetime = None):
self._uri = uri
self.name = name
self.username = username
add_last_month = BooleanField(default=False)
add_this_month = BooleanField(default=False)
day_boundary = NumberField(default=21)
self.db_ref = db_ref
self._include_recommendations = include_recommendations
self._recommendation_sample = recommendation_sample
self._include_library_tracks = include_library_tracks
self._parts = parts
self._playlist_references = playlist_references
self._shuffle = shuffle
self._sort = sort
self._description_overwrite = description_overwrite
self._description_suffix = description_suffix
self._last_updated = last_updated
self._lastfm_stat_count = lastfm_stat_count
self._lastfm_stat_album_count = lastfm_stat_album_count
self._lastfm_stat_artist_count = lastfm_stat_artist_count
self._lastfm_stat_percent = lastfm_stat_percent
self._lastfm_stat_album_percent = lastfm_stat_album_percent
self._lastfm_stat_artist_percent = lastfm_stat_artist_percent
self._lastfm_stat_last_refresh = lastfm_stat_last_refresh
chart_range = TextField(default='1month')
chart_limit = NumberField(default=50)
def to_dict(self):
return {
'uri': self.uri,
'name': self.name,
'type': 'default',
to_return = super().to_dict()
'include_recommendations': self.include_recommendations,
'recommendation_sample': self.recommendation_sample,
'include_library_tracks': self.include_library_tracks,
to_return["playlist_references"] = [i.get().to_dict().get('name') for i in to_return['playlist_references']]
'parts': self.parts,
'playlist_references': [i.get().to_dict().get('name') for i in self.playlist_references],
'shuffle': self.shuffle,
# remove unnecessary and sensitive fields
to_return.pop('id', None)
to_return.pop('key', None)
'sort': self.sort.name,
'description_overwrite': self.description_overwrite,
'description_suffix': self.description_suffix,
'last_updated': self.last_updated,
'lastfm_stat_count': self.lastfm_stat_count,
'lastfm_stat_album_count': self.lastfm_stat_album_count,
'lastfm_stat_artist_count': self.lastfm_stat_artist_count,
'lastfm_stat_percent': self.lastfm_stat_percent,
'lastfm_stat_album_percent': self.lastfm_stat_album_percent,
'lastfm_stat_artist_percent': self.lastfm_stat_artist_percent,
'lastfm_stat_last_refresh': self.lastfm_stat_last_refresh
}
def update_database(self, updates):
database.update_playlist(username=self.username, name=self.name, updates=updates)
@property
def uri(self):
return self._uri
@uri.setter
def uri(self, value):
database.update_playlist(self.username, self.name, {'uri': value})
self._uri = value
@property
def include_recommendations(self):
return self._include_recommendations
@include_recommendations.setter
def include_recommendations(self, value):
database.update_playlist(self.username, self.name, {'include_recommendations': value})
self._include_recommendations = value
@property
def recommendation_sample(self):
return self._recommendation_sample
@recommendation_sample.setter
def recommendation_sample(self, value):
database.update_playlist(self.username, self.name, {'recommendation_sample': value})
self._recommendation_sample = value
@property
def include_library_tracks(self):
return self._include_library_tracks
@include_library_tracks.setter
def include_library_tracks(self, value):
database.update_playlist(self.username, self.name, {'include_library_tracks': value})
self._include_library_tracks = value
@property
def parts(self):
return self._parts
@parts.setter
def parts(self, value):
database.update_playlist(self.username, self.name, {'parts': value})
self._parts = value
@property
def playlist_references(self):
return self._playlist_references
@playlist_references.setter
def playlist_references(self, value):
database.update_playlist(self.username, self.name, {'playlist_references': value})
self._playlist_references = value
@property
def shuffle(self):
return self._shuffle
@shuffle.setter
def shuffle(self, value):
database.update_playlist(self.username, self.name, {'shuffle': value})
self._shuffle = value
@property
def sort(self):
return self._sort
@sort.setter
def sort(self, value):
database.update_playlist(self.username, self.name, {'sort': value.name})
self._sort = value
@property
def description_overwrite(self):
return self._description_overwrite
@description_overwrite.setter
def description_overwrite(self, value):
database.update_playlist(self.username, self.name, {'description_overwrite': value})
self._description_overwrite = value
@property
def description_suffix(self):
return self._description_suffix
@description_suffix.setter
def description_suffix(self, value):
database.update_playlist(self.username, self.name, {'description_suffix': value})
self._description_suffix = value
@property
def last_updated(self):
return self._last_updated
@last_updated.setter
def last_updated(self, value):
database.update_playlist(self.username, self.name, {'last_updated': value})
self._last_updated = value
@property
def lastfm_stat_count(self):
return self._lastfm_stat_count
@lastfm_stat_count.setter
def lastfm_stat_count(self, value):
database.update_playlist(self.username, self.name, {'lastfm_stat_count': value})
self._lastfm_stat_count = value
@property
def lastfm_stat_album_count(self):
return self._lastfm_stat_album_count
@lastfm_stat_album_count.setter
def lastfm_stat_album_count(self, value):
database.update_playlist(self.username, self.name, {'lastfm_stat_album_count': value})
self._lastfm_stat_album_count = value
@property
def lastfm_stat_artist_count(self):
return self._lastfm_stat_artist_count
@lastfm_stat_artist_count.setter
def lastfm_stat_artist_count(self, value):
database.update_playlist(self.username, self.name, {'lastfm_stat_artist_count': value})
self._lastfm_stat_artist_count = value
@property
def lastfm_stat_percent(self):
return self._lastfm_stat_percent
@lastfm_stat_percent.setter
def lastfm_stat_percent(self, value):
database.update_playlist(self.username, self.name, {'lastfm_stat_percent': value})
self._lastfm_stat_percent = value
@property
def lastfm_stat_album_percent(self):
return self._lastfm_stat_album_percent
@lastfm_stat_album_percent.setter
def lastfm_stat_album_percent(self, value):
database.update_playlist(self.username, self.name, {'lastfm_stat_album_percent': value})
self._lastfm_stat_album_percent = value
@property
def lastfm_stat_artist_percent(self):
return self._lastfm_stat_artist_percent
@lastfm_stat_artist_percent.setter
def lastfm_stat_artist_percent(self, value):
database.update_playlist(self.username, self.name, {'lastfm_stat_artist_percent': value})
self._lastfm_stat_artist_percent = value
@property
def lastfm_stat_last_refresh(self):
return self._lastfm_stat_last_refresh
@lastfm_stat_last_refresh.setter
def lastfm_stat_last_refresh(self, value):
database.update_playlist(self.username, self.name, {'lastfm_stat_last_refresh': value})
self._lastfm_stat_last_refresh = value
class RecentsPlaylist(Playlist):
def __init__(self,
uri: str,
name: str,
username: str,
db_ref: DocumentReference,
include_recommendations: bool,
recommendation_sample: int,
include_library_tracks: bool,
parts: List[str],
playlist_references: List[DocumentReference],
shuffle: bool,
sort: Sort = None,
description_overwrite: str = None,
description_suffix: str = None,
last_updated: datetime = None,
lastfm_stat_count: int = None,
lastfm_stat_album_count: int = None,
lastfm_stat_artist_count: int = None,
lastfm_stat_percent: int = None,
lastfm_stat_album_percent: int = None,
lastfm_stat_artist_percent: int = None,
lastfm_stat_last_refresh: datetime = None,
add_last_month: bool = False,
add_this_month: bool = False,
day_boundary: int = 7):
super().__init__(uri=uri,
name=name,
username=username,
db_ref=db_ref,
include_recommendations=include_recommendations,
recommendation_sample=recommendation_sample,
include_library_tracks=include_library_tracks,
parts=parts,
playlist_references=playlist_references,
shuffle=shuffle,
sort=sort,
description_overwrite=description_overwrite,
description_suffix=description_suffix,
last_updated=last_updated,
lastfm_stat_count=lastfm_stat_count,
lastfm_stat_album_count=lastfm_stat_album_count,
lastfm_stat_artist_count=lastfm_stat_artist_count,
lastfm_stat_percent=lastfm_stat_percent,
lastfm_stat_album_percent=lastfm_stat_album_percent,
lastfm_stat_artist_percent=lastfm_stat_artist_percent,
lastfm_stat_last_refresh=lastfm_stat_last_refresh)
self._add_last_month = add_last_month
self._add_this_month = add_this_month
self._day_boundary = day_boundary
def to_dict(self):
response = super().to_dict()
response.update({
'add_last_month': self.add_last_month,
'add_this_month': self.add_this_month,
'day_boundary': self.day_boundary,
'type': 'recents'
})
return response
@property
def add_last_month(self):
return self._add_last_month
@add_last_month.setter
def add_last_month(self, value):
database.update_playlist(self.username, self.name, {'add_last_month': value})
self._add_last_month = value
@property
def add_this_month(self):
return self._add_this_month
@add_this_month.setter
def add_this_month(self, value):
database.update_playlist(self.username, self.name, {'add_this_month': value})
self._add_this_month = value
@property
def day_boundary(self):
return self._day_boundary
@day_boundary.setter
def day_boundary(self, value):
database.update_playlist(self.username, self.name, {'day_boundary': value})
self._day_boundary = value
class LastFMChartPlaylist(Playlist):
def __init__(self,
uri: str,
name: str,
username: str,
chart_range: Network.Range,
db_ref: DocumentReference,
include_recommendations: bool,
recommendation_sample: int,
include_library_tracks: bool,
parts: List[str],
playlist_references: List[DocumentReference],
shuffle: bool,
chart_limit: int = 50,
sort: Sort = None,
description_overwrite: str = None,
description_suffix: str = None,
last_updated: datetime = None,
lastfm_stat_count: int = None,
lastfm_stat_album_count: int = None,
lastfm_stat_artist_count: int = None,
lastfm_stat_percent: int = None,
lastfm_stat_album_percent: int = None,
lastfm_stat_artist_percent: int = None,
lastfm_stat_last_refresh: datetime = None):
super().__init__(uri=uri,
name=name,
username=username,
db_ref=db_ref,
include_recommendations=include_recommendations,
recommendation_sample=recommendation_sample,
include_library_tracks=include_library_tracks,
parts=parts,
playlist_references=playlist_references,
shuffle=shuffle,
sort=sort,
description_overwrite=description_overwrite,
description_suffix=description_suffix,
last_updated=last_updated,
lastfm_stat_count=lastfm_stat_count,
lastfm_stat_album_count=lastfm_stat_album_count,
lastfm_stat_artist_count=lastfm_stat_artist_count,
lastfm_stat_percent=lastfm_stat_percent,
lastfm_stat_album_percent=lastfm_stat_album_percent,
lastfm_stat_artist_percent=lastfm_stat_artist_percent,
lastfm_stat_last_refresh=lastfm_stat_last_refresh)
self._chart_range = chart_range
self._chart_limit = chart_limit
def to_dict(self):
response = super().to_dict()
response.update({
'chart_limit': self.chart_limit,
'chart_range': self.chart_range.name,
'type': 'fmchart'
})
return response
@property
def chart_range(self):
return self._chart_range
@chart_range.setter
def chart_range(self, value):
database.update_playlist(self.username, self.name, {'chart_range': value.name})
self._chart_range = value
@property
def chart_limit(self):
return self._chart_limit
@chart_limit.setter
def chart_limit(self, value):
database.update_playlist(self.username, self.name, {'chart_limit': value})
self._chart_limit = value
return to_return

View File

@ -1,129 +1,30 @@
from datetime import datetime
import music.db.database as db
from fireo.models import Model
from fireo.fields import TextField, DateTime, NumberField, ListField
class Tag:
class Tag(Model):
class Meta:
collection_name = 'tags'
def __init__(self,
tag_id: str,
name: str,
username: str,
tag_id = TextField(required=True)
name = TextField(required=True)
username = TextField(required=True)
db_ref,
tracks = ListField(default=[])
albums = ListField(default=[])
artists = ListField(default=[])
tracks,
albums,
artists,
count = NumberField(default=0)
proportion = NumberField(default=0)
total_user_scrobbles = NumberField(default=0)
count: int,
proportion: float,
total_user_scrobbles: int,
last_updated: datetime):
self.tag_id = tag_id
self._name = name
self.username = username
self.db_ref = db_ref
self._tracks = tracks
self._albums = albums
self._artists = artists
self._count = count
self._proportion = proportion
self._total_user_scrobbles = total_user_scrobbles
self._last_updated = last_updated
last_updated = DateTime()
def to_dict(self):
return {
'tag_id': self.tag_id,
'name': self.name,
'username': self.username,
to_return = super().to_dict()
'tracks': self.tracks,
'albums': self.albums,
'artists': self.artists,
# remove unnecessary and sensitive fields
to_return.pop('id', None)
to_return.pop('key', None)
'count': self.count,
'proportion': self.proportion,
'total_user_scrobbles': self.total_user_scrobbles,
'last_updated': self.last_updated
}
def update_database(self, updates):
db.update_tag(username=self.username, tag_id=self.tag_id, updates=updates)
@property
def name(self):
return self._name
@name.setter
def name(self, value):
db.update_tag(username=self.username, tag_id=self.tag_id, updates={'name': value})
self._name = value
@property
def tracks(self):
return self._tracks
@tracks.setter
def tracks(self, value):
db.update_tag(username=self.username, tag_id=self.tag_id, updates={'tracks': value})
self._tracks = value
@property
def albums(self):
return self._albums
@albums.setter
def albums(self, value):
db.update_tag(username=self.username, tag_id=self.tag_id, updates={'albums': value})
self._albums = value
@property
def artists(self):
return self._artists
@artists.setter
def artists(self, value):
db.update_tag(username=self.username, tag_id=self.tag_id, updates={'artists': value})
self._artists = value
@property
def count(self):
return self._count
@count.setter
def count(self, value):
db.update_tag(username=self.username, tag_id=self.tag_id, updates={'count': value})
self._count = value
@property
def proportion(self):
return self._proportion
@proportion.setter
def proportion(self, value):
db.update_tag(username=self.username, tag_id=self.tag_id, updates={'proportion': value})
self._proportion = value
@property
def total_user_scrobbles(self):
return self._total_user_scrobbles
@total_user_scrobbles.setter
def total_user_scrobbles(self, value):
db.update_tag(username=self.username, tag_id=self.tag_id, updates={'total_user_scrobbles': value})
self._total_user_scrobbles = value
@property
def last_updated(self):
return self._last_updated
@last_updated.setter
def last_updated(self, value):
db.update_tag(username=self.username, tag_id=self.tag_id, updates={'last_updated': value})
self._last_updated = value
return to_return

View File

@ -1,172 +1,42 @@
from datetime import datetime
from enum import Enum
from fireo.models import Model
from fireo.fields import TextField, BooleanField, DateTime, NumberField
from werkzeug.security import generate_password_hash, check_password_hash
import music.db.database as database
from werkzeug.security import check_password_hash
class User:
class Type(Enum):
user = 1
admin = 2
class User(Model):
class Meta:
collection_name = 'spotify_users'
def __init__(self,
username: str,
password: str,
db_ref,
email: str,
user_type: Type,
last_login: datetime,
last_refreshed: datetime,
locked: bool,
validated: bool,
username = TextField(required=True)
password = TextField(required=True)
email = TextField()
type = TextField(default="user")
spotify_linked: bool,
access_token: str,
refresh_token: str,
token_expiry: int,
last_login = DateTime()
last_refreshed = DateTime()
locked = BooleanField(default=False, required=True)
validated = BooleanField(default=True, required=True)
lastfm_username: str = None):
self.username = username
self._password = password
self.db_ref = db_ref
self._email = email
self._type = user_type
spotify_linked = BooleanField(default=False, required=True)
access_token = TextField()
refresh_token = TextField()
token_expiry = NumberField()
self._last_login = last_login
self._last_refreshed = last_refreshed
self._locked = locked
self._validated = validated
self._spotify_linked = spotify_linked
self._access_token = access_token
self._refresh_token = refresh_token
self._token_expiry = token_expiry
self._lastfm_username = lastfm_username
lastfm_username = TextField()
def check_password(self, password):
return check_password_hash(self.password, password)
def to_dict(self):
return {
'username': self.username,
'email': self.email,
'type': self.user_type.name,
'last_login': self.last_login,
'spotify_linked': self.spotify_linked,
'lastfm_username': self.lastfm_username
}
to_return = super().to_dict()
def update_database(self, updates):
database.update_user(username=self.username, updates=updates)
# remove unnecessary and sensitive fields
to_return.pop('password', None)
to_return.pop('access_token', None)
to_return.pop('refresh_token', None)
to_return.pop('token_expiry', None)
to_return.pop('id', None)
to_return.pop('key', None)
@property
def password(self):
return self._password
@password.setter
def password(self, value):
pw_hash = generate_password_hash(value)
database.update_user(self.username, {'password': pw_hash})
self._password = pw_hash
@property
def email(self):
return self._email
@email.setter
def email(self, value):
database.update_user(self.username, {'email': value})
self._email = value
@property
def user_type(self):
return self._type
@user_type.setter
def user_type(self, value):
database.update_user(self.username, {'type': value})
self._type = value
@property
def last_login(self):
return self._last_login
@last_login.setter
def last_login(self, value):
database.update_user(self.username, {'last_login': value})
self._last_login = value
@property
def last_refreshed(self):
return self._last_refreshed
@last_refreshed.setter
def last_refreshed(self, value):
database.update_user(self.username, {'last_refreshed': value})
self._last_refreshed = value
@property
def locked(self):
return self._locked
@locked.setter
def locked(self, value):
database.update_user(self.username, {'locked': value})
self._locked = value
@property
def validated(self):
return self._validated
@validated.setter
def validated(self, value):
database.update_user(self.username, {'validated': value})
self._validated = value
@property
def spotify_linked(self):
return self._spotify_linked
@spotify_linked.setter
def spotify_linked(self, value):
database.update_user(self.username, {'spotify_linked': value})
self._spotify_linked = value
@property
def access_token(self):
return self._access_token
@access_token.setter
def access_token(self, value):
database.update_user(self.username, {'access_token': value})
self._access_token = value
@property
def refresh_token(self):
return self._refresh_token
@refresh_token.setter
def refresh_token(self, value):
database.update_user(self.username, {'refresh_token': value})
self._refresh_token = value
@property
def token_expiry(self):
return self._token_expiry
@token_expiry.setter
def token_expiry(self, value):
database.update_user(self.username, {'refresh_token': value})
self._token_expiry = value
@property
def lastfm_username(self):
return self._lastfm_username
@lastfm_username.setter
def lastfm_username(self, value):
database.update_user(self.username, {'lastfm_username': value})
self._lastfm_username = value
return to_return

View File

@ -9,21 +9,19 @@ db = firestore.Client()
logger = logging.getLogger(__name__)
def create_playlist(username, name):
logger.info(f'creating spotify playlist for {username} / {name}')
def create_playlist(user, name):
logger.info(f'creating spotify playlist for {user.username} / {name}')
user = database.get_user(username)
if user is not None:
net = database.get_authed_spotify_network(username)
net = database.get_authed_spotify_network(user)
playlist = net.create_playlist(net.user.username, name)
if playlist is not None:
return playlist
else:
logger.error(f'no response received {username} / {name}')
logger.error(f'no response received {user.username} / {name}')
return
else:
logger.error(f'{username} not found')
return
logger.error(f'{user.username} not provided')

View File

@ -1,5 +1,3 @@
from google.cloud import firestore
import datetime
import logging
@ -11,8 +9,7 @@ from spotframework.engine.processor.deduplicate import DeduplicateByID
from spotframework.player.player import Player
import music.db.database as database
from music.db.part_generator import PartGenerator
db = firestore.Client()
from music.model.user import User
logger = logging.getLogger(__name__)
@ -29,7 +26,11 @@ def play_user_playlist(username,
add_last_month=False,
device_name=None):
user = database.get_user(username)
user = User.collection.filter('username', '==', username.strip().lower()).get()
if user is None:
logger.error(f'user {username} not found')
return
logger.info(f'playing for {username}')
@ -51,7 +52,7 @@ def play_user_playlist(username,
logger.critical(f'no playlists to use for creation ({username})')
return None
net = database.get_authed_spotify_network(username)
net = database.get_authed_spotify_network(user)
device = None
if device_name:

View File

@ -4,6 +4,8 @@ import logging
from datetime import datetime
import music.db.database as database
from music.model.user import User
from music.model.playlist import Playlist
from spotfm.maths.counter import Counter
from spotframework.model.uri import Uri
@ -17,11 +19,15 @@ def refresh_lastfm_track_stats(username, playlist_name):
logger.info(f'refreshing {playlist_name} stats for {username}')
fmnet = database.get_authed_lastfm_network(username=username)
spotnet = database.get_authed_spotify_network(username=username)
user = User.collection.filter('username', '==', username.strip().lower()).get()
if user is None:
logger.error(f'user {username} not found')
fmnet = database.get_authed_lastfm_network(user)
spotnet = database.get_authed_spotify_network(user)
counter = Counter(fmnet=fmnet, spotnet=spotnet)
playlist = database.get_playlist(username=username, name=playlist_name)
playlist = Playlist.collection.parent(user.key).filter('name', '==', playlist_name).get()
if playlist is None:
logger.critical(f'playlist {playlist_name} for {username} not found')
@ -40,23 +46,26 @@ def refresh_lastfm_track_stats(username, playlist_name):
else:
percent = 0
playlist.update_database({
'lastfm_stat_count': track_count,
'lastfm_stat_percent': percent,
playlist.lastfm_stat_count = track_count
playlist.lastfm_stat_percent = percent
playlist.lastfm_stat_last_refresh = datetime.utcnow()
'lastfm_stat_last_refresh': datetime.utcnow()
})
playlist.update()
def refresh_lastfm_album_stats(username, playlist_name):
logger.info(f'refreshing {playlist_name} stats for {username}')
fmnet = database.get_authed_lastfm_network(username=username)
spotnet = database.get_authed_spotify_network(username=username)
user = User.collection.filter('username', '==', username.strip().lower()).get()
if user is None:
logger.error(f'user {username} not found')
fmnet = database.get_authed_lastfm_network(user)
spotnet = database.get_authed_spotify_network(user)
counter = Counter(fmnet=fmnet, spotnet=spotnet)
playlist = database.get_playlist(username=username, name=playlist_name)
playlist = Playlist.collection.parent(user.key).filter('name', '==', playlist_name).get()
if playlist is None:
logger.critical(f'playlist {playlist_name} for {username} not found')
@ -75,23 +84,26 @@ def refresh_lastfm_album_stats(username, playlist_name):
else:
album_percent = 0
playlist.update_database({
'lastfm_stat_album_count': album_count,
'lastfm_stat_album_percent': album_percent,
playlist.lastfm_stat_album_count = album_count
playlist.lastfm_stat_album_percent = album_percent
playlist.lastfm_stat_last_refresh = datetime.utcnow()
'lastfm_stat_last_refresh': datetime.utcnow()
})
playlist.update()
def refresh_lastfm_artist_stats(username, playlist_name):
logger.info(f'refreshing {playlist_name} stats for {username}')
fmnet = database.get_authed_lastfm_network(username=username)
spotnet = database.get_authed_spotify_network(username=username)
user = User.collection.filter('username', '==', username.strip().lower()).get()
if user is None:
logger.error(f'user {username} not found')
fmnet = database.get_authed_lastfm_network(user)
spotnet = database.get_authed_spotify_network(user)
counter = Counter(fmnet=fmnet, spotnet=spotnet)
playlist = database.get_playlist(username=username, name=playlist_name)
playlist = Playlist.collection.parent(user.key).filter('name', '==', playlist_name).get()
if playlist is None:
logger.critical(f'playlist {playlist_name} for {username} not found')
@ -110,9 +122,8 @@ def refresh_lastfm_artist_stats(username, playlist_name):
else:
artist_percent = 0
playlist.update_database({
'lastfm_stat_artist_count': artist_count,
'lastfm_stat_artist_percent': artist_percent,
playlist.lastfm_stat_artist_count = artist_count
playlist.lastfm_stat_artist_percent = artist_percent
playlist.lastfm_stat_last_refresh = datetime.utcnow()
'lastfm_stat_last_refresh': datetime.utcnow()
})
playlist.update()

View File

@ -14,7 +14,8 @@ from spotfm.engine.chart_source import ChartSource
import music.db.database as database
from music.db.part_generator import PartGenerator
from music.model.playlist import RecentsPlaylist, LastFMChartPlaylist
from music.model.user import User
from music.model.playlist import Playlist
db = firestore.Client()
@ -23,7 +24,9 @@ logger = logging.getLogger(__name__)
def run_user_playlist(username, playlist_name):
"""Generate and upadate a user's playlist"""
user = database.get_user(username)
user = User.collection.filter('username', '==', username.strip().lower()).get()
if user is None:
logger.error(f'user {username} not found')
logger.info(f'running {username} / {playlist_name}')
@ -32,7 +35,7 @@ def run_user_playlist(username, playlist_name):
logger.critical(f'{username} not found')
return
playlist = database.get_playlist(username=username, name=playlist_name)
playlist = Playlist.collection.parent(user.key).filter('name', '==', playlist_name).get()
if playlist is None:
logger.critical(f'playlist not found ({username}/{playlist_name})')
@ -44,7 +47,7 @@ def run_user_playlist(username, playlist_name):
# END CHECKS
net = database.get_authed_spotify_network(username)
net = database.get_authed_spotify_network(user)
engine = PlaylistEngine(net)
part_generator = PartGenerator(user=user)
@ -63,11 +66,11 @@ def run_user_playlist(username, playlist_name):
params.append(LibraryTrackSource.Params())
# END OPTIONS
if isinstance(playlist, LastFMChartPlaylist):
if playlist.type == 'fmchart':
if user.lastfm_username is None:
logger.error(f'{username} has no associated last.fm username, chart source skipped')
else:
engine.sources.append(ChartSource(spotnet=net, fmnet=database.get_authed_lastfm_network(user.username)))
engine.sources.append(ChartSource(spotnet=net, fmnet=database.get_authed_lastfm_network(user)))
params.append(ChartSource.Params(chart_range=playlist.chart_range, limit=playlist.chart_limit))
else:
@ -78,7 +81,7 @@ def run_user_playlist(username, playlist_name):
processors.append(SortReleaseDate(reverse=True))
# GENERATE TRACKS
if isinstance(playlist, RecentsPlaylist):
if playlist.type == 'recents':
boundary_date = datetime.datetime.now(datetime.timezone.utc) - \
datetime.timedelta(days=int(playlist.day_boundary))
tracks = engine.get_recent_playlist(params=params,
@ -101,3 +104,4 @@ def run_user_playlist(username, playlist_name):
overwrite=overwrite,
suffix=suffix)
playlist.last_updated = datetime.datetime.utcnow()
playlist.update()

View File

@ -2,6 +2,8 @@ import logging
from datetime import datetime
import music.db.database as database
from music.model.user import User
from music.model.tag import Tag
logger = logging.getLogger(__name__)
@ -9,19 +11,20 @@ logger = logging.getLogger(__name__)
def update_tag(username, tag_id):
logger.info(f'updating {username} / {tag_id}')
tag = database.get_tag(username=username, tag_id=tag_id)
user = User.collection.filter('username', '==', username.strip().lower()).get()
if user is None:
logger.error(f'user {username} not found')
tag = Tag.collection.parent(user.key).filter('tag_id', '==', tag_id).get()
if tag is None:
logger.error(f'{tag_id} for {username} not found')
return
user = database.get_user(username)
if user.lastfm_username is None or len(user.lastfm_username) == 0:
logger.error(f'{username} has no last.fm username')
return
net = database.get_authed_lastfm_network(username=username)
net = database.get_authed_lastfm_network(user)
tag_count = 0
user_scrobbles = net.get_user_scrobble_count()
@ -60,13 +63,13 @@ def update_tag(username, tag_id):
tracks.append(track)
tag.update_database({
'tracks': tracks,
'albums': albums,
'artists': artists,
tag.tracks = tracks
tag.albums = albums
tag.artists = artists
'total_user_scrobbles': user_scrobbles,
'count': tag_count,
'proportion': (tag_count / user_scrobbles) * 100,
'last_updated': datetime.utcnow()
})
tag.total_user_scrobbles = user_scrobbles
tag.count = tag_count
tag.proportion = (tag_count / user_scrobbles) * 100
tag.last_updated = datetime.utcnow()
tag.update()

View File

@ -1,13 +1,14 @@
import music.db.database as database
from music.model.user import User
from music.model.playlist import Playlist
playlists = database.get_user_playlists('andy')
user = User.collection.filter('username', '==', 'andy').get()
name = input('enter playlist name: ')
playlist = next((i for i in playlists if i.name == name), None)
playlist = Playlist.collection.parent(user.key).filter('name', '==', name).get()
if playlist is not None:
new_name = input('enter new name: ')
playlist.update_database({'name': new_name})
playlist.name = new_name
playlist.update()
else:
print('playlist not found')

View File

@ -1,37 +1,39 @@
astroid==2.3.3
cachetools==4.0.0
certifi==2019.11.28
astroid==2.4.0
cachetools==4.1.0
certifi==2020.4.5.1
chardet==3.0.4
click==7.1.1
Flask==1.1.1
google-api-core==1.16.0
google-auth==1.12.0
click==7.1.2
fireo==1.2.4
Flask==1.1.2
google-api-core==1.17.0
google-auth==1.14.1
google-cloud-core==1.3.0
google-cloud-firestore==1.6.2
google-cloud-logging==1.15.0
google-cloud-pubsub==1.4.1
google-cloud-pubsub==1.4.3
google-cloud-tasks==1.5.0
googleapis-common-protos==1.51.0
grpc-google-iam-v1==0.12.3
grpcio==1.27.2
grpcio==1.28.1
idna==2.9
isort==4.3.21
itsdangerous==1.1.0
Jinja2==2.11.1
Jinja2==2.11.2
lazy-object-proxy==1.4.3
MarkupSafe==1.1.1
mccabe==0.6.1
numpy==1.18.2
opencv-python==4.2.0.32
numpy==1.18.3
opencv-python==4.2.0.34
protobuf==3.11.3
pyasn1==0.4.8
pyasn1-modules==0.2.8
pylint==2.4.4
pytz==2019.3
pylint==2.5.0
pytz==2020.1
requests==2.23.0
rsa==4.0
six==1.14.0
tabulate==0.8.7
urllib3==1.25.8
Werkzeug==1.0.0
toml==0.10.0
urllib3==1.25.9
Werkzeug==1.0.1
wrapt==1.12.1