from aiohttp import web from aiohttp_apispec import docs, response_schema, json_schema, querystring_schema, match_info_schema from sqlalchemy import func from typing import Dict, Any, Mapping from server.core.helpers.discovery import DiscoveryHelper from server.artist.decorators import Helper from server.artist.helpers.discovery import ArtistDiscoveryHelper from server.artist.schemas import ArtistDiscoveryResponseSchema from server.artist.utils import ImageService as ArtistImageService from server.core.repositories import ESRepo from server.db.constants import ALL_SEARCH_INDEXES from server.dna.schemas import ErrorResponseSchema from server.dna.constants import ARTIST, TRACK, LIKES, VIEWS, FOLLOWERS from server.track.schemas import TrackDiscoveryResponseSchema from server.track.helpers.discovery import TrackDiscoveryHelper from server.dna.category.errors import HOMEPAGE_CATEGORY_EXISTS_ERR from server.dna.category.decorators import CategoryValidator from server.dna.category.repositories import CategoryRepository from server.dna.category.models import Category from server.dna.category.schemas import ( CategorySchema, CategoryEntitySchema, CreatedUpdatedResponseSchema, LimitOffsetSchema, EntityCategoriesQuerystringSchema, EntityTypeURLSchema, CategoryEntityResponseSchema, ) from server.dna.category.utils import make_cover_image from server.utils import ( make_contry_obj_from_results, make_genres_obj_from_results, ) TYPE_SCHEMAS = {ARTIST: ArtistDiscoveryResponseSchema, TRACK: TrackDiscoveryResponseSchema} CHART_MAPPING: Mapping[str, str] = { LIKES: "likes_chart", FOLLOWERS: "followers_chart", VIEWS: "views_chart", } TYPE_HELPER: Mapping[str, DiscoveryHelper] = {ARTIST: ArtistDiscoveryHelper, TRACK: TrackDiscoveryHelper} @docs(tags=["category"], description="Get user categories") @querystring_schema(LimitOffsetSchema) @response_schema(CategorySchema.ResponseSchema) @Helper.auth_with_user_id async def get_categories(request: web.Request) -> web.Response: user_id = request["user_id"] params: dict = LimitOffsetSchema().load(request.rel_url.query) limit = params.get("limit") offset = params.get("offset") filters = {"user_id": user_id, "is_deleted": None} results = await CategoryRepository.get_categories_data(filters, limit, offset) if results: results = [r.__dict__ for r in results] categories_id = [i.get("id") for i in results] # counters: count_results = await CategoryRepository.get_category_entities_counters(categories_id) counters: Dict[Any, Any] = {} for i in count_results: category_id = i.get("category_id") if category_id in counters.keys(): data: dict = counters.get(category_id, {}) data.update({i.get("entity_type"): i.get("count")}) counters[i.get("category_id")] = data else: counters[i.get("category_id")] = {i.get("entity_type"): i.get("count")} # cover image: entities = await CategoryRepository.get_category_entities(categories_id) for res in results: category_id = res.get("id") res["counters"] = {"artist": 0, "track": 0} res["cover_image"] = None if category_id in counters.keys(): res["counters"].update(counters.get(category_id)) res["cover_image"] = await make_cover_image(category_id, entities) return web.json_response(CategorySchema.ResponseSchema().dump(results, many=True)) @docs(tags=["category"], description="Get specific user category") @response_schema(CategorySchema.ResponseSchema) @Helper.args_decompose() @Helper.auth_with_user_id # TODO: add tests for non exist category id @CategoryValidator.category_user_restriction async def get_category(request: web.Request, id: int) -> web.Response: user_id: str = request["user_id"] result_counter = await CategoryRepository.get_category_data_by_id(id, user_id) entities = await CategoryRepository.get_category_entities(categories_id=[id]) result = {} if result_counter: category: Category = result_counter[0].get("Category") result = category.__dict__ counter: Dict[Any, Any] = {} result["counters"] = {"artist": 0, "track": 0} for i in result_counter: counter[i.get("entity_type")] = i.get("count") result["counters"].update(counter) result["cover_image"] = await make_cover_image(id, entities) return web.json_response(CategorySchema.ResponseSchema().dump(result)) @docs(tags=["category"], description="Create user category") @json_schema(CategorySchema.CreateRequestSchema) @response_schema(CreatedUpdatedResponseSchema) @Helper.auth_with_user_id async def create_category(request: web.Request) -> web.Response: data: dict = await request.json() data = CategorySchema.CreateRequestSchema().load(data) user_id = request["user_id"] data["user_id"] = user_id filters = {"is_homepage": True, "user_id": user_id, "is_deleted": None} user_homepage_categories_count = await CategoryRepository.get_categories_count(filters) if user_homepage_categories_count == 0: # user should have at least 1 homepage category: data["is_homepage"] = True elif user_homepage_categories_count == 1 and data.get("is_homepage"): # user want to create more than 1 homepage category: result = {"error": HOMEPAGE_CATEGORY_EXISTS_ERR} return web.json_response(status=web.HTTPBadRequest.status_code, data=ErrorResponseSchema().dump(result)) result = await CategoryRepository.create_category(data) if "error" in result: return web.json_response(status=web.HTTPBadRequest.status_code, data=ErrorResponseSchema().dump(result)) return web.json_response(status=web.HTTPCreated.status_code, data=CreatedUpdatedResponseSchema().dump(result)) @docs(tags=["category"], description="Update user category") @json_schema(CategorySchema.UpdateRequestSchema) @response_schema(CreatedUpdatedResponseSchema) @Helper.args_decompose() @Helper.auth_with_user_id @CategoryValidator.category_user_restriction async def update_category(request: web.Request, id: int) -> web.Response: data: dict = await request.json() data = CategorySchema.UpdateRequestSchema().load(data) user_id = request["user_id"] data["id"] = id data["user_id"] = user_id is_homepage = data.get("is_homepage") if is_homepage: # make previous homepage category not homepage await CategoryRepository.set_all_user_categories_homepage_false(user_id) filters = {"id": id, "user_id": user_id} result = await CategoryRepository.update_category(values=data, filters=filters) if "error" in result: return web.json_response(status=web.HTTPBadRequest.status_code, data=ErrorResponseSchema().dump(result)) return web.json_response(CreatedUpdatedResponseSchema().dump(result)) @docs(tags=["category"], description="Delete user category") @Helper.args_decompose() @Helper.auth_with_user_id @CategoryValidator.category_user_restriction async def delete_category(request: web.Request, id: int) -> web.Response: user_id = request["user_id"] data: dict = {"id": id, "user_id": user_id, "is_deleted": func.now()} filters: dict = {"id": id, "is_deleted": None, "user_id": user_id} await CategoryRepository.update_category(values=data, filters=filters) await CategoryRepository.delete_categories_entities(filters={"category_id": id}) return web.json_response({}, status=web.HTTPNoContent.status_code) @docs(tags=["category entity"], description="Get all category entities") @querystring_schema(CategoryEntitySchema.QuerystringSchema) @response_schema(CategoryEntitySchema.ResponseSchema) @Helper.auth_with_user_id @Helper.args_decompose() @CategoryValidator.category_user_restriction async def get_category_entities(request: web.Request, id: int) -> web.Response: async def get_entity_results(results: list, entity_type: str, params: Dict[str, Any]) -> list: entities_ids = [str(item["id"]) for item in results if item.get("entity_type") == entity_type] return await TYPE_HELPER[entity_type].get_data(entities_ids, params=params) def parse_entity_result(entities_results, entity_item): entity_type = entity_item.get("entity_type") entity_id = entity_item.get("id") for entity in entities_results: if entity_id == int(entity["id"]): entity["country"] = make_contry_obj_from_results(entity) entity["genres"] = make_genres_obj_from_results(entity) entity_item["data"] = TYPE_SCHEMAS[entity_type]().dump(entity) entity_item["is_signed"] = False break else: entity_item["is_signed"] = True return entity_item params: dict = CategoryEntitySchema.QuerystringSchema().load(request.rel_url.query) limit = params.pop("limit", None) offset = params.pop("offset", None) filters = {"category_id": id} if params: filters.update(params) results = await CategoryRepository.get_categories_entities(filters, limit, offset) results = CategoryEntitySchema.ResponseSchema().dump(results, many=True) entities_result_artists: list = await get_entity_results(results, entity_type=ARTIST, params=params) entities_result_tracks: list = await get_entity_results(results, entity_type=TRACK, params=params) for item in results: entity_type = item.get("entity_type") parse_entity_result( entities_results=entities_result_artists if entity_type == ARTIST else entities_result_tracks, entity_item=item, ) return web.json_response(results) @docs(tags=["category entity"], description="Create new category entities") @json_schema(CategoryEntitySchema.CreateRequestSchema) @response_schema(CreatedUpdatedResponseSchema) @Helper.auth_with_user_id @Helper.args_decompose() @CategoryValidator.category_user_restriction async def create_category_entities(request: web.Request, id: int) -> web.Response: data: dict = await request.json() data = CategoryEntitySchema.CreateRequestSchema().load(data) data["category_id"] = id entity_id = data.get("entity_id") entity_type = data.get("entity_type") category_id = data.get("category_id") index = ALL_SEARCH_INDEXES[entity_type] # type: ignore entity_result: dict = await ESRepo.get( id=str(entity_id), index=index, ) if not entity_result: err = {"error": "not correct id"} return web.json_response(status=web.HTTPBadRequest.status_code, data=ErrorResponseSchema().dump(err)) cover_image = None if entity_result: if entity_type == ARTIST: # get image from spotify id spotify_artist_id = str(entity_result.get("spotify_artist_id")) cover_image = ArtistImageService.get_img_url(spotify_artist_id) elif entity_type == TRACK: cover_image = entity_result.get("artwork_url") data["cover_image"] = cover_image result = await CategoryRepository.create_category_entity(values=data) error_msg = result.get("error") if error_msg: result = {"error": error_msg} return web.json_response(status=web.HTTPBadRequest.status_code, data=ErrorResponseSchema().dump(result)) await CategoryRepository.update_category(values={}, filters={"id": category_id}) return web.json_response(status=web.HTTPCreated.status_code, data=CreatedUpdatedResponseSchema().dump(result)) @docs(tags=["category entity"], description="Delete category entity") @match_info_schema(CategoryEntitySchema.DeleteRequestSchema) @Helper.auth_with_user_id @Helper.args_decompose() @CategoryValidator.category_user_restriction async def delete_category_entities(request: web.Request, id: int, entity_id: int) -> web.Response: filters = {"category_id": id, "entity_id": int(entity_id)} await CategoryRepository.delete_categories_entities(filters) return web.json_response({}, status=web.HTTPNoContent.status_code) @docs(tags=["artist categories"], description="Get categories for artist where their being saved") @querystring_schema(EntityCategoriesQuerystringSchema) @response_schema(CategoryEntityResponseSchema) @Helper.args_decompose() @Helper.auth_with_user_id async def get_artist_categories(request: web.Request, entity_type) -> web.Response: params: dict = EntityCategoriesQuerystringSchema().load(request.query) EntityTypeURLSchema().load({"entity_type": entity_type}) user_id = request["user_id"] ids: list = [int(id) for id in params["ids"].split(",")] results = await CategoryRepository.get_entity_categories(user_id, ids, entity_type) return web.json_response(CategoryEntityResponseSchema().dump(results, many=True))