import re from apollo_utils.core.constants.dsp import DSP_SPOTIFY_APPLE, DSP_SPOTIFY_APPLE_AMAZON from apollo_utils.core.constants.market import Market from apollo_utils.service.schemas.base import RequestSchema from apollo_utils.service.schemas.fields.enum import EnumField from apollo_utils.service.schemas.fields.isrc import IsrcExtendedField from apollo_utils.service.schemas.fields.list import SplitUniqueListField from apollo_utils.service.schemas.fields.market import MarketField from apollo_utils.service.schemas.mixins.pagination import PaginationResponseMixin from marshmallow import Schema, ValidationError, fields, validates_schema from server.constants.core import ApolloEntityType from server.constants.favorites import FavoritesModifyInclude from server.schemas.accounts.favorites import FavoriteEntityDataNoneMixin, FavoriteEntityIdListNoneMixin, \ FavoriteEntityRequiredMixin, FavoriteEntityTypeListNoneMixin, FavoritesModifyMixin from server.schemas.mixins import CreatedUpdatedAtMixin, IntIdListNoneMixin, IntOrNull from server.schemas.pagination import GetAllPaginationMixin class ApolloFavoriteTrackSchema(RequestSchema): class ApolloFavoriteTrackDataSchema(RequestSchema): _dsp_to_id_type = {DSP_SPOTIFY_APPLE.APPLE.value: int, DSP_SPOTIFY_APPLE.SPOTIFY.value: str} dsp = EnumField(enum=DSP_SPOTIFY_APPLE, load_as="value", required=True) id = fields.Field(required=True) country_code = MarketField(gl=Market.GLOBAL, required=True) @validates_schema def validate_track(self, data, **kwargs): _id, dsp = data["id"], data["dsp"] try: data["id"] = self._dsp_to_id_type[dsp](_id) except ValueError: raise ValidationError(f"Received unsupported id type for dsp={dsp} and id={_id}.") if dsp == DSP_SPOTIFY_APPLE.APPLE.value and data["country_code"] == Market.GLOBAL: data["country_code"] = Market.US return data entity_id = IsrcExtendedField(required=True) entity_type = fields.Str() data = fields.Nested(ApolloFavoriteTrackDataSchema(), required=True) class ApolloFavoritePlaylistSchema(RequestSchema): _dsp_to_entity_id_mask = { DSP_SPOTIFY_APPLE_AMAZON.APPLE.value: "^apple_[a-z]+_pl.[a-z0-9]{32}", DSP_SPOTIFY_APPLE_AMAZON.SPOTIFY.value: "^spotify_None_[A-Za-z0-9]{22}", DSP_SPOTIFY_APPLE_AMAZON.AMAZON.value: "^amazon_None_[A-Z0-9]{10}\:\d+\_\w+", } class ApolloFavoritePlaylistDataSchema(RequestSchema): _amazon_playlist_id_mask = "^[A-Z0-9]{10}\:\d+\_\w+" vendor = EnumField(enum=DSP_SPOTIFY_APPLE_AMAZON, load_as="value", required=True) id = fields.Str(required=True) country_code = MarketField(gl=Market.US, required=False) @validates_schema def validate_playlist(self, data, **kwargs): _id, dsp, country_code = data["id"], data["vendor"], data.get("country_code") if dsp == DSP_SPOTIFY_APPLE_AMAZON.APPLE.value and not country_code: raise ValidationError("'country_code' is required in 'data' for apple playlist.") if dsp == DSP_SPOTIFY_APPLE_AMAZON.AMAZON.value and not re.compile(self._amazon_playlist_id_mask).match( _id ): raise ValidationError( f"Invalid id={_id} for amazon playlist. " f"An id should match the following mask {self._amazon_playlist_id_mask}" ) return data entity_id = fields.Str(required=True) entity_type = fields.Str() data = fields.Nested(ApolloFavoritePlaylistDataSchema(), required=True) @validates_schema def validate_entity_id(self, data, **kwargs): dsp = data["data"]["vendor"] entity_id_mask = self._dsp_to_entity_id_mask[dsp] if not re.compile(entity_id_mask).match(data["entity_id"]): raise ValidationError(f"entity_id={data['entity_id']} does not match mask={entity_id_mask} for dsp={dsp}.") return data class FavoritesCreateRequest(FavoriteEntityRequiredMixin, FavoriteEntityDataNoneMixin, RequestSchema): include = EnumField(FavoritesModifyInclude, required=False, load_as="value") class FavoritesDeleteRequest(IntIdListNoneMixin, FavoriteEntityIdListNoneMixin, FavoritesModifyMixin, RequestSchema): @validates_schema def validate_ids(self, data, **kwargs): if not data.get("entity_id") and not data.get("id"): raise ValidationError(f"At least one of 'entity_id', 'id' should be passed") class FavoritesResponse(Schema): id = fields.Int(data_key="favorites_id") entity_id = fields.String() entity_type = fields.String() tag_id = fields.Int(default=None) data = fields.Dict() count = fields.Function(lambda entity, context: context.get("count")) class FavoritesGet: class Request(GetAllPaginationMixin, FavoriteEntityIdListNoneMixin, FavoriteEntityTypeListNoneMixin, RequestSchema): tag_id = SplitUniqueListField(IntOrNull(allow_none=True, null_to=None, required=True), default=[]) class Response(PaginationResponseMixin, Schema): class ResponseItem(CreatedUpdatedAtMixin, FavoritesResponse): pass items = fields.List(fields.Nested(ResponseItem)) def validate_apollo_entity(data: dict) -> dict: entity_type = data["entity_type"] if entity_type == ApolloEntityType.TRACK.value: return ApolloFavoriteTrackSchema().load(data) if entity_type == ApolloEntityType.PLAYLIST.value: return ApolloFavoritePlaylistSchema().load(data) return data