Skip to content

Commit

Permalink
Add DP v1 user follows & track/playlist repost/saves (#875)
Browse files Browse the repository at this point in the history
* Add full user follows and following routes to DP v1 api

* Remove logs

* Fix switched queries

* Fix lint errors

* Update endpoint doc

* Add queries for track/playlist save repost
  • Loading branch information
jowlee committed Oct 2, 2020
1 parent 240f7a3 commit dad9683
Show file tree
Hide file tree
Showing 13 changed files with 426 additions and 83 deletions.
3 changes: 2 additions & 1 deletion discovery-provider/src/api/v1/api.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
from flask import Flask, Blueprint
from flask.helpers import url_for
from flask_restx import Resource, Api
from src.api.v1.users import ns as users_ns
from src.api.v1.users import ns as users_ns, full_ns as full_users_ns
from src.api.v1.playlists import ns as playlists_ns, full_ns as full_playlists_ns
from src.api.v1.tracks import ns as tracks_ns, full_ns as full_tracks_ns
from src.api.v1.metrics import ns as metrics_ns
Expand Down Expand Up @@ -32,3 +32,4 @@ def specs_url(self):
api_v1_full = ApiWithHTTPS(bp_full, version='1.0')
api_v1_full.add_namespace(full_tracks_ns)
api_v1_full.add_namespace(full_playlists_ns)
api_v1_full.add_namespace(full_users_ns)
10 changes: 10 additions & 0 deletions discovery-provider/src/api/v1/helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -224,3 +224,13 @@ def format_offset(args, max_offset=MAX_LIMIT):
if offset is None:
return DEFAULT_OFFSET
return max(min(int(offset), max_offset), MIN_OFFSET)


def get_default_max(value, default, max=None):
if not isinstance(value, int):
return default
elif max is None:
return value
else:
return min(value, max)

91 changes: 90 additions & 1 deletion discovery-provider/src/api/v1/playlists.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,19 @@
import logging
from src.queries.get_top_playlists import get_top_playlists # pylint: disable=C0302
from src.api.v1.models.playlists import playlist_model, full_playlist_model
from src.api.v1.models.users import user_model_full
from src.queries.get_playlists import get_playlists
from flask_restx import Resource, Namespace, fields, reqparse
from src.queries.get_playlist_tracks import get_playlist_tracks
from src.api.v1.helpers import abort_not_found, decode_with_abort, extend_playlist, extend_track,\
make_response, success_response, search_parser, abort_bad_request_param, decode_string_id
make_response, success_response, search_parser, abort_bad_request_param, decode_string_id, \
extend_user, get_default_max
from .models.tracks import track
from src.queries.search_queries import SearchKind, search
from src.utils.redis_cache import cache
from src.utils.redis_metrics import record_metrics
from src.queries.get_reposters_for_playlist import get_reposters_for_playlist
from src.queries.get_savers_for_playlist import get_savers_for_playlist

logger = logging.getLogger(__name__)

Expand Down Expand Up @@ -177,3 +181,88 @@ def get(self):

playlists = list(map(extend_playlist, response))
return success_response(playlists)

playlist_favorites_route_parser = reqparse.RequestParser()
playlist_favorites_route_parser.add_argument('user_id', required=False)
playlist_favorites_route_parser.add_argument('limit', required=False, type=int)
playlist_favorites_route_parser.add_argument('offset', required=False, type=int)
playlist_favorites_response = make_response("following_response", full_ns, fields.List(fields.Nested(user_model_full)))
@full_ns.route("/<string:playlist_id>/favorites")
class FullTrackFavorites(Resource):
@full_ns.expect(playlist_favorites_route_parser)
@full_ns.doc(
id="""Get Users that Favorited a Playlist""",
params={
'user_id': 'A User ID',
'limit': 'Limit',
'offset': 'Offset'
},
responses={
200: 'Success',
400: 'Bad request',
500: 'Server error'
}
)
@full_ns.marshal_with(playlist_favorites_response)
@cache(ttl_sec=5)
def get(self, playlist_id):
args = playlist_favorites_route_parser.parse_args()
decoded_id = decode_with_abort(playlist_id, full_ns)
limit = get_default_max(args.get('limit'), 10, 100)
offset = get_default_max(args.get('offset'), 0)

current_user_id = None
if args.get("user_id"):
current_user_id = decode_string_id(args["user_id"])
args = {
'save_playlist_id': decoded_id,
'current_user_id': current_user_id,
'limit': limit,
'offset': offset
}
users = get_savers_for_playlist(args)
users = list(map(extend_user, users))

return success_response(users)

playlist_reposts_route_parser = reqparse.RequestParser()
playlist_reposts_route_parser.add_argument('user_id', required=False)
playlist_reposts_route_parser.add_argument('limit', required=False, type=int)
playlist_reposts_route_parser.add_argument('offset', required=False, type=int)
playlist_reposts_response = make_response("following_response", full_ns, fields.List(fields.Nested(user_model_full)))
@full_ns.route("/<string:playlist_id>/reposts")
class FullTrackReposts(Resource):
@full_ns.expect(playlist_reposts_route_parser)
@full_ns.doc(
id="""Get Users that Reposted a Playlist""",
params={
'user_id': 'A User ID',
'limit': 'Limit',
'offset': 'Offset'
},
responses={
200: 'Success',
400: 'Bad request',
500: 'Server error'
}
)
@full_ns.marshal_with(playlist_reposts_response)
@cache(ttl_sec=5)
def get(self, playlist_id):
args = playlist_reposts_route_parser.parse_args()
decoded_id = decode_with_abort(playlist_id, full_ns)
limit = get_default_max(args.get('limit'), 10, 100)
offset = get_default_max(args.get('offset'), 0)

current_user_id = None
if args.get("user_id"):
current_user_id = decode_string_id(args["user_id"])
args = {
'repost_playlist_id': decoded_id,
'current_user_id': current_user_id,
'limit': limit,
'offset': offset
}
users = get_reposters_for_playlist(args)
users = list(map(extend_user, users))
return success_response(users)
91 changes: 89 additions & 2 deletions discovery-provider/src/api/v1/tracks.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
from urllib.parse import urljoin
import logging # pylint: disable=C0302
from flask import redirect
from flask_restx import Resource, Namespace, fields
from flask_restx import Resource, Namespace, fields, reqparse
from flask_restx import resource
from src.queries.get_tracks import get_tracks
from src.queries.get_track_user_creator_node import get_track_user_creator_node
from src.api.v1.helpers import abort_not_found, decode_with_abort, \
extend_track, make_response, search_parser, \
extend_track, make_response, search_parser, extend_user, get_default_max, \
trending_parser, full_trending_parser, success_response, abort_bad_request_param, to_dict, \
format_offset, format_limit, decode_string_id
from .models.tracks import track, track_full
Expand All @@ -16,6 +16,9 @@
from src.utils.redis_cache import cache, extract_key, use_redis_cache
from flask.globals import request
from src.utils.redis_metrics import record_metrics
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

logger = logging.getLogger(__name__)

Expand Down Expand Up @@ -245,3 +248,87 @@ def get(self):
trending = full_trending[offset: limit + offset]
return success_response(trending)

track_favorites_route_parser = reqparse.RequestParser()
track_favorites_route_parser.add_argument('user_id', required=False)
track_favorites_route_parser.add_argument('limit', required=False, type=int)
track_favorites_route_parser.add_argument('offset', required=False, type=int)
track_favorites_response = make_response("following_response", full_ns, fields.List(fields.Nested(user_model_full)))
@full_ns.route("/<string:track_id>/favorites")
class FullTrackFavorites(Resource):
@full_ns.expect(track_favorites_route_parser)
@full_ns.doc(
id="""Get Users that Favorited a Track""",
params={
'user_id': 'A User ID',
'limit': 'Limit',
'offset': 'Offset'
},
responses={
200: 'Success',
400: 'Bad request',
500: 'Server error'
}
)
@full_ns.marshal_with(track_favorites_response)
@cache(ttl_sec=5)
def get(self, track_id):
args = track_favorites_route_parser.parse_args()
decoded_id = decode_with_abort(track_id, full_ns)
limit = get_default_max(args.get('limit'), 10, 100)
offset = get_default_max(args.get('offset'), 0)

current_user_id = None
if args.get("user_id"):
current_user_id = decode_string_id(args["user_id"])
args = {
'save_track_id': decoded_id,
'current_user_id': current_user_id,
'limit': limit,
'offset': offset
}
users = get_savers_for_track(args)
users = list(map(extend_user, users))

return success_response(users)

track_reposts_route_parser = reqparse.RequestParser()
track_reposts_route_parser.add_argument('user_id', required=False)
track_reposts_route_parser.add_argument('limit', required=False, type=int)
track_reposts_route_parser.add_argument('offset', required=False, type=int)
track_reposts_response = make_response("following_response", full_ns, fields.List(fields.Nested(user_model_full)))
@full_ns.route("/<string:track_id>/reposts")
class FullTrackReposts(Resource):
@full_ns.expect(track_reposts_route_parser)
@full_ns.doc(
id="""Get Users that Reposted a Track""",
params={
'user_id': 'A User ID',
'limit': 'Limit',
'offset': 'Offset'
},
responses={
200: 'Success',
400: 'Bad request',
500: 'Server error'
}
)
@full_ns.marshal_with(track_reposts_response)
@cache(ttl_sec=5)
def get(self, track_id):
args = track_reposts_route_parser.parse_args()
decoded_id = decode_with_abort(track_id, full_ns)
limit = get_default_max(args.get('limit'), 10, 100)
offset = get_default_max(args.get('offset'), 0)

current_user_id = None
if args.get("user_id"):
current_user_id = decode_string_id(args["user_id"])
args = {
'repost_track_id': decoded_id,
'current_user_id': current_user_id,
'limit': limit,
'offset': offset
}
users = get_reposters_for_track(args)
users = list(map(extend_user, users))
return success_response(users)
101 changes: 98 additions & 3 deletions discovery-provider/src/api/v1/users.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,18 @@
import logging
from flask_restx import Resource, Namespace, fields, reqparse
from src.api.v1.models.common import favorite
from src.api.v1.models.users import user_model
from src.api.v1.models.users import user_model, user_model_full

from src.queries.get_saves import get_saves
from src.queries.get_users import get_users
from src.queries.search_queries import SearchKind, search
from flask_restx import Resource, Namespace, fields
from src.queries.get_tracks import get_tracks
from src.queries.get_followees_for_user import get_followees_for_user
from src.queries.get_followers_for_user import get_followers_for_user

from src.api.v1.helpers import abort_not_found, decode_with_abort, extend_favorite, extend_track, \
extend_user, make_response, search_parser, success_response, abort_bad_request_param
extend_user, make_response, search_parser, success_response, abort_bad_request_param, \
get_default_max, encode_int_id, decode_string_id
from .models.tracks import track
from src.utils.redis_cache import cache
from src.utils.redis_metrics import record_metrics
Expand All @@ -16,6 +21,7 @@
logger = logging.getLogger(__name__)

ns = Namespace('users', description='User related operations')
full_ns = Namespace('users', description='Full user operations')

user_response = make_response("user_response", ns, fields.Nested(user_model))
@ns.route("/<string:user_id>")
Expand Down Expand Up @@ -122,3 +128,92 @@ def get(self):
users = response["users"]
users = list(map(extend_user, users))
return success_response(users)


followers_route_parser = reqparse.RequestParser()
followers_route_parser.add_argument('user_id', required=False)
followers_route_parser.add_argument('limit', required=False, type=int)
followers_route_parser.add_argument('offset', required=False, type=int)

followers_response = make_response("followers_response", full_ns, fields.List(fields.Nested(user_model_full)))
@full_ns.route("/<string:user_id>/followers")
class FollowerUsers(Resource):
@record_metrics
@ns.expect(followers_route_parser)
@ns.doc(
id="""All users that follow the provided user""",
params={
'user_id': 'A User ID',
'limit': 'Limit',
'offset': 'Offset'
},
responses={
200: 'Success',
400: 'Bad request',
500: 'Server error'
}
)
@full_ns.marshal_with(followers_response)
@cache(ttl_sec=5)
def get(self, user_id):
decoded_id = decode_with_abort(user_id, full_ns)
args = followers_route_parser.parse_args()
limit = get_default_max(args.get('limit'), 10, 100)
offset = get_default_max(args.get('offset'), 0)

current_user_id = None
if args.get("user_id"):
current_user_id = decode_string_id(args["user_id"])
args = {
'followee_user_id': decoded_id,
'current_user_id': current_user_id,
'limit': limit,
'offset': offset
}
users = get_followers_for_user(args)
users = list(map(extend_user, users))
return success_response(users)


following_route_parser = reqparse.RequestParser()
following_route_parser.add_argument('user_id', required=False)
following_route_parser.add_argument('limit', required=False, type=int)
following_route_parser.add_argument('offset', required=False, type=int)
following_response = make_response("following_response", full_ns, fields.List(fields.Nested(user_model_full)))
@full_ns.route("/<string:user_id>/following")
class FollowingUsers(Resource):
@record_metrics
@full_ns.expect(following_route_parser)
@full_ns.doc(
id="""All users that the provided user follows""",
params={
'user_id': 'A User ID',
'limit': 'Limit',
'offset': 'Offset'
},
responses={
200: 'Success',
400: 'Bad request',
500: 'Server error'
}
)
@full_ns.marshal_with(following_response)
@cache(ttl_sec=5)
def get(self, user_id):
decoded_id = decode_with_abort(user_id, full_ns)
args = following_route_parser.parse_args()
limit = get_default_max(args.get('limit'), 10, 100)
offset = get_default_max(args.get('offset'), 0)

current_user_id = None
if args.get("user_id"):
current_user_id = decode_string_id(args["user_id"])
args = {
'follower_user_id': decoded_id,
'current_user_id': current_user_id,
'limit': limit,
'offset': offset
}
users = get_followees_for_user(args)
users = list(map(extend_user, users))
return success_response(users)

0 comments on commit dad9683

Please sign in to comment.