Skip to content

Commit

Permalink
Create API Tracks Endpoints (#890)
Browse files Browse the repository at this point in the history
* Combine regular and unlisted track endpoints

* Caching

* Cleanup
  • Loading branch information
piazzatron committed Oct 7, 2020
1 parent 5b1c957 commit 4acc7fe
Show file tree
Hide file tree
Showing 4 changed files with 212 additions and 117 deletions.
51 changes: 46 additions & 5 deletions discovery-provider/src/api/v1/tracks.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
from src.api.v1.models.users import user_model_full
from src.queries.get_reposters_for_track import get_reposters_for_track
from src.queries.get_savers_for_track import get_savers_for_track
from src.queries.get_tracks_including_unlisted import get_tracks_including_unlisted

logger = logging.getLogger(__name__)

Expand All @@ -38,15 +39,36 @@

# Get single track

def get_single_track(track_id, endpoint_ns):
decoded_id = decode_with_abort(track_id, endpoint_ns)
args = {"id": [decoded_id], "with_users": True, "filter_deleted": True}
def get_single_track(track_id, current_user_id, endpoint_ns):
args = {
"id": [track_id],
"with_users": True,
"filter_deleted": True,
"current_user_id": current_user_id
}
tracks = get_tracks(args)
if not tracks:
abort_not_found(track_id, endpoint_ns)
single_track = extend_track(tracks[0])
return success_response(single_track)

def get_unlisted_track(track_id, url_title, handle, current_user_id, endpoint_ns):
args = {
"identifiers": [{
"handle": handle,
"url_title": url_title,
"id": track_id
}],
"filter_deleted": False,
"with_users": True,
"current_user_id": current_user_id
}
tracks = get_tracks_including_unlisted(args)
if not tracks:
abort_not_found(track_id, endpoint_ns)
single_track = extend_track(tracks[0])
return success_response(single_track)

TRACK_ROUTE = '/<string:track_id>'
@ns.route(TRACK_ROUTE)
class Track(Resource):
Expand All @@ -64,15 +86,34 @@ class Track(Resource):
@cache(ttl_sec=5)
def get(self, track_id):
"""Fetch a track."""
return get_single_track(track_id, ns)
decoded_id = decode_with_abort(track_id, ns)
return get_single_track(decoded_id, None, ns)

full_track_parser = reqparse.RequestParser()
full_track_parser.add_argument('handle')
full_track_parser.add_argument('url_title')
full_track_parser.add_argument('show_unlisted', type=bool)
full_track_parser.add_argument('user_id')

@full_ns.route(TRACK_ROUTE)
class FullTrack(Resource):
@record_metrics
@full_ns.marshal_with(full_track_response)
@cache(ttl_sec=5)
def get(self, track_id):
return get_single_track(track_id, full_ns)
args = full_track_parser.parse_args()
decoded_id = decode_with_abort(track_id, full_ns)
current_user_id = args.get("user_id")
if current_user_id:
current_user_id = decode_string_id(current_user_id)

if args.get("show_unlisted"):
url_title, handle = args.get("url_title"), args.get("handle")
if not (url_title and handle):
full_ns.abort(400, "Unlisted tracks require url_title and handle")
return get_unlisted_track(decoded_id, url_title, handle, current_user_id, full_ns)

return get_single_track(decoded_id, current_user_id, full_ns)

# Stream

Expand Down
129 changes: 76 additions & 53 deletions discovery-provider/src/queries/get_tracks.py
Original file line number Diff line number Diff line change
@@ -1,78 +1,101 @@
import logging # pylint: disable=C0302

from flask.globals import request
from src.models import AggregatePlays, Track, User
from src.utils import helpers
from src.utils.db_session import get_db_read_replica
from src.queries.query_helpers import paginate_query, parse_sort_param, \
populate_track_metadata, get_users_ids, get_users_by_id
from src.utils.redis_cache import extract_key, use_redis_cache

logger = logging.getLogger(__name__)

UNPOPULATED_TRACK_CACHE_DURATION_SEC = 10

def make_cache_key(args):
cache_keys = {
"handle": args.get("handle"),
"id": args.get("id"),
"user_id": args.get("user_id"),
"filter_deleted": args.get("filter_deleted"),
"min_block_number": args.get("min_block_number"),
"sort": args.get("sort"),
"with_users": args.get("with_users")
}
key = extract_key(f"unpopulated-tracks:{request.path}", cache_keys.items())
return key

def get_tracks(args):
tracks = []
current_user_id = args.get("current_user_id")

db = get_db_read_replica()
with db.scoped_session() as session:
if "handle" in args:
handle = args.get("handle")
user_id = session.query(User.user_id).filter(User.handle_lc == handle.lower()).first()
args["user_id"] = user_id

# Create initial query
base_query = session.query(Track)
base_query = base_query.filter(Track.is_current == True, Track.is_unlisted == False, Track.stem_of == None)

# Conditionally process an array of tracks
if "id" in args:
track_id_list = args.get("id")
try:
# Update query with track_id list
base_query = base_query.filter(Track.track_id.in_(track_id_list))
except ValueError as e:
logger.error("Invalid value found in track id list", exc_info=True)
raise e

# Allow filtering of tracks by a certain creator
if "user_id" in args:
user_id = args.get("user_id")
base_query = base_query.filter(
Track.owner_id == user_id
)

# Allow filtering of deletes
if "filter_deleted" in args:
filter_deleted = args.get("filter_deleted")
if filter_deleted:
def get_unpopulated_track():
if "handle" in args:
handle = args.get("handle")
user_id = session.query(User.user_id).filter(User.handle_lc == handle.lower()).first()
args["user_id"] = user_id

# Create initial query
base_query = session.query(Track)
base_query = base_query.filter(Track.is_current == True, Track.is_unlisted == False, Track.stem_of == None)

# Conditionally process an array of tracks
if "id" in args:
track_id_list = args.get("id")
try:
# Update query with track_id list
base_query = base_query.filter(Track.track_id.in_(track_id_list))
except ValueError as e:
logger.error("Invalid value found in track id list", exc_info=True)
raise e

# Allow filtering of tracks by a certain creator
if "user_id" in args:
user_id = args.get("user_id")
base_query = base_query.filter(
Track.is_delete == False
Track.owner_id == user_id
)

if "min_block_number" in args:
min_block_number = args.get("min_block_number")
base_query = base_query.filter(
Track.blocknumber >= min_block_number
)

if "sort" in args:
if args["sort"] == "date":
base_query = base_query.order_by(Track.created_at.desc(), Track.track_id.desc())
elif args["sort"] == "plays":
base_query = base_query.join(
AggregatePlays,
AggregatePlays.play_item_id == Track.track_id
).order_by(
AggregatePlays.count.desc()
# Allow filtering of deletes
if "filter_deleted" in args:
filter_deleted = args.get("filter_deleted")
if filter_deleted:
base_query = base_query.filter(
Track.is_delete == False
)

if "min_block_number" in args:
min_block_number = args.get("min_block_number")
base_query = base_query.filter(
Track.blocknumber >= min_block_number
)
else:
whitelist_params = ['created_at', 'create_date', 'release_date', 'blocknumber', 'track_id']
base_query = parse_sort_param(base_query, Track, whitelist_params)
query_results = paginate_query(base_query).all()
tracks = helpers.query_result_to_list(query_results)

track_ids = list(map(lambda track: track["track_id"], tracks))
if "sort" in args:
if args["sort"] == "date":
base_query = base_query.order_by(Track.created_at.desc(), Track.track_id.desc())
elif args["sort"] == "plays":
base_query = base_query.join(
AggregatePlays,
AggregatePlays.play_item_id == Track.track_id
).order_by(
AggregatePlays.count.desc()
)
else:
whitelist_params = ['created_at', 'create_date', 'release_date', 'blocknumber', 'track_id']
base_query = parse_sort_param(base_query, Track, whitelist_params)
query_results = paginate_query(base_query).all()
tracks = helpers.query_result_to_list(query_results)

track_ids = list(map(lambda track: track["track_id"], tracks))

return (tracks, track_ids)

key = make_cache_key(args)
(tracks, track_ids) = use_redis_cache(key, UNPOPULATED_TRACK_CACHE_DURATION_SEC, get_unpopulated_track)

# bundle peripheral info into track results
current_user_id = args.get("current_user_id")
tracks = populate_track_metadata(session, track_ids, tracks, current_user_id)

if args.get("with_users", False):
Expand Down

0 comments on commit 4acc7fe

Please sign in to comment.