from apollo_utils.core.constants import SPOTIFY_PLAYLIST_URI_PREFIX from apollo_utils.core.constants.dsp import DSP, DSP_SPOTIFY_APPLE, DSP_SPOTIFY_APPLE_AMAZON from apollo_utils.service.schemas.base import RequestSchema from apollo_utils.service.schemas.fields.datetime import DateTimeStringField from apollo_utils.service.schemas.fields.enum import EnumListField from apollo_utils.service.schemas.fields.list import SplitUniqueListField from apollo_utils.service.schemas.mixins.pagination import PaginationResponseMixin from marshmallow import Schema, ValidationError, fields, post_load, validate from src.constants.search.recent import RecentSearchOrderBy, RecentSearchType, RecentSearchTypeMapping from src.legacy.core.serializers import PaginationMixin from src.legacy.spotify.constants import SPOTIFY_URI_PREFIX class RecentSearch: """Recent search GET & POST input schema(s).""" # GET api/search/recent/ class Request(RequestSchema, PaginationMixin): """Recent search GET query params input schema""" vendors = SplitUniqueListField( fields.String(validate=validate.OneOf(DSP_SPOTIFY_APPLE_AMAZON.values())), data_key="vendor", missing=DSP_SPOTIFY_APPLE.values(), ) type_list = EnumListField(RecentSearchType, data_key="type", as_enum=True, allow_all=True, missing=None) order_by = SplitUniqueListField( fields.String(validate=validate.OneOf(RecentSearchOrderBy.values())), missing=[RecentSearchOrderBy.TIMESTAMP_DESC.value], ) class Response(Schema, PaginationResponseMixin): """Recent search response schema""" class NestedItem(Schema): uri = fields.String() timestamp = DateTimeStringField() type = fields.Method("get_search_type") vendor = fields.String() id = fields.Function( lambda item: item.uri.replace(SPOTIFY_URI_PREFIX, "").replace(SPOTIFY_PLAYLIST_URI_PREFIX, "") ) country_code = fields.String() def get_search_type(self, item) -> str: type_list = self.context["type_list"] if type_list: if len(type_list) == 1: return type_list[0].value if RecentSearchType.USER not in type_list and item.search_type == RecentSearchType.USER.value: return RecentSearchType.PLAYLIST.value return next(k for k, v in RecentSearchTypeMapping.items() if v == item.search_type) items = fields.Nested(NestedItem(many=True)) # POST api/search/recent/ class PostRequestBody(RequestSchema): """Request body is used for backward compatible to .Net spotifysearchhistory & applemusicsearchhistory for AGO FE easier integration with POST search/recent/ endpoint""" uri = fields.String(required=True) search_term = fields.String(data_key="searchTerm", load_default="") timestamp = fields.Integer(required=True) search_type = fields.String( validate=validate.OneOf(RecentSearchType.values()), load_default=RecentSearchType.PLAYLIST.value, data_key="type", ) dsp = fields.String(validate=validate.OneOf(DSP_SPOTIFY_APPLE_AMAZON.values()), load_default=None) country_code = fields.String(validate=validate.Length(max=10, min=2), missing=None) def _get_dsp(self, data, **kwargs): """Temporary solution to get dsp [One Of "apple", "spotify", "amazon"] as FE don`t pass it Could be removed when FE starts to pass dsp as a param """ if data["dsp"] is None: uri = data["uri"] dsp = DSP.AMAZON.value if uri.startswith("spotify:"): dsp = DSP.SPOTIFY.value if uri.startswith("pl."): dsp = DSP.APPLE.value data["dsp"] = dsp return data @post_load def _track_only_for_spotify(self, data, **kwargs): """Legacy logic -> type="track" works only with dsp="spotify" with uri that starts with spotify:track:<>""" search_type = data["search_type"] uri = data["uri"] dsp = data["dsp"] if search_type == RecentSearchType.TRACK.value and not ( uri.startswith(SPOTIFY_URI_PREFIX) and dsp == DSP.SPOTIFY.value ): raise ValidationError( f"Track instances are available only for DSP = {DSP.SPOTIFY.value}, " f"With uri startswith: {SPOTIFY_URI_PREFIX} " f"passed data = {data}" ) return data @post_load def _check_country_code_for_apple_playlists(self, data, *args, **kwargs): dsp = self._get_dsp(data)["dsp"] entity_type = data["search_type"] country_code = data["country_code"] if dsp == DSP.APPLE.value and entity_type == "playlist" and not country_code: raise ValidationError( f"Saving type = playlists for DSP = {DSP.APPLE.value}, is available with passed country_code only" f"Passed data = {data}" ) return data class PostResponse(Schema): pass