From 24b9c8cd758aff1242a896f4201c60a72f17cdd2 Mon Sep 17 00:00:00 2001 From: Ansh Goyal Date: Thu, 3 Mar 2022 19:04:01 +0000 Subject: [PATCH 01/15] Add /artist endpoint --- critiquebrainz/db/rating_stats.py | 48 ++++++ critiquebrainz/ws/__init__.py | 2 + critiquebrainz/ws/artist/__init__.py | 0 critiquebrainz/ws/artist/views.py | 229 +++++++++++++++++++++++++++ critiquebrainz/ws/constants.py | 1 + 5 files changed, 280 insertions(+) create mode 100644 critiquebrainz/db/rating_stats.py create mode 100644 critiquebrainz/ws/artist/__init__.py create mode 100644 critiquebrainz/ws/artist/views.py diff --git a/critiquebrainz/db/rating_stats.py b/critiquebrainz/db/rating_stats.py new file mode 100644 index 000000000..ee4f0803c --- /dev/null +++ b/critiquebrainz/db/rating_stats.py @@ -0,0 +1,48 @@ +import sqlalchemy + +from critiquebrainz import db + +def get_stats(entity_id, entity_type): + """Gets the average rating and the rating statistics of the entity + + It is done by selecting ratings from the latest revisions of all reviews + for a given entity. + + Args: + entity_id (uuid): ID of the entity + entity_type (str): Type of the entity + """ + with db.engine.connect() as connection: + result = connection.execute(sqlalchemy.text(""" + WITH LatestRevisions AS ( + SELECT review_id, + MAX("timestamp") created_at + FROM revision + WHERE review_id in ( + SELECT id + FROM review + WHERE entity_id = :entity_id + AND entity_type = :entity_type + AND is_hidden = 'f') + GROUP BY review_id + ) + SELECT rating + FROM revision + INNER JOIN LatestRevisions + ON revision.review_id = LatestRevisions.review_id + AND revision.timestamp = LatestRevisions.created_at + """), { + "entity_id": entity_id, + "entity_type": entity_type, + }) + row = result.fetchall() + + ratings = [r[0]/20 for r in row] + ratings_stats = {1: 0, 2: 0, 3: 0, 4:0, 5:0} + + for rating in ratings: + ratings_stats[rating] += 1 + + average_rating = sum(ratings)/len(ratings) + + return ratings_stats, average_rating \ No newline at end of file diff --git a/critiquebrainz/ws/__init__.py b/critiquebrainz/ws/__init__.py index a91ca10eb..78cd20300 100644 --- a/critiquebrainz/ws/__init__.py +++ b/critiquebrainz/ws/__init__.py @@ -108,7 +108,9 @@ def _register_blueprints(app): from critiquebrainz.ws.review.views import review_bp from critiquebrainz.ws.user.views import user_bp from critiquebrainz.ws.review.bulk import bulk_review_bp + from critiquebrainz.ws.artist.views import artist_bp app.register_blueprint(oauth_bp, url_prefix="/oauth") app.register_blueprint(review_bp, url_prefix="/review") app.register_blueprint(user_bp, url_prefix="/user") app.register_blueprint(bulk_review_bp, url_prefix="/reviews") + app.register_blueprint(artist_bp, url_prefix="/artist") diff --git a/critiquebrainz/ws/artist/__init__.py b/critiquebrainz/ws/artist/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/critiquebrainz/ws/artist/views.py b/critiquebrainz/ws/artist/views.py new file mode 100644 index 000000000..4d9503c77 --- /dev/null +++ b/critiquebrainz/ws/artist/views.py @@ -0,0 +1,229 @@ +from flask import Blueprint, jsonify +import critiquebrainz.db.review as db_review +import critiquebrainz.db.rating_stats as db_rating_stats +from critiquebrainz.db import exceptions as db_exceptions +import critiquebrainz.frontend.external.musicbrainz_db.artist as mb_artist +from brainzutils.musicbrainz_db import artist as db_artist +from critiquebrainz.decorators import crossdomain +from critiquebrainz.ws.exceptions import NotFound + +artist_bp = Blueprint('ws_artist', __name__) + + +@artist_bp.route('/', methods=['GET', 'OPTIONS']) +@crossdomain(headers="Authorization, Content-Type") +def artist_entity_handler(artist_mbid): + """Get list of reviews. + + **Request Example:** + + .. code-block:: bash + + $ curl "https://critiquebrainz.org/ws/1/artist/df602ea4-c143-425d-a235-d7641f7634fd" \\ + -X GET + + **Response Example:** + + .. code-block:: json + + { + "artist": { + "id": "b7d92248-97e3-4450-8057-6fe06738f735", + "name": "Shawn Mendes", + "sort_name": "Mendes, Shawn", + "type": "Person" + }, + "avg_rating": 5.0, + "latest_reviews": [ + { + "created": "Mon, 24 Jan 2022 18:11:59 GMT", + "edits": 0, + "entity_id": "b7d92248-97e3-4450-8057-6fe06738f735", + "entity_type": "artist", + "full_name": "Creative Commons Attribution-ShareAlike 3.0 Unported", + "id": "89fb94fd-3e65-4e52-8d16-5bdca4572883", + "info_url": "https://creativecommons.org/licenses/by-sa/3.0/", + "is_draft": false, + "is_hidden": false, + "language": "en", + "last_revision": { + "id": 11258, + "rating": 5, + "review_id": "89fb94fd-3e65-4e52-8d16-5bdca4572883", + "text": null, + "timestamp": "Mon, 24 Jan 2022 18:11:59 GMT" + }, + "last_updated": "Mon, 24 Jan 2022 18:11:59 GMT", + "license_id": "CC BY-SA 3.0", + "popularity": 0, + "published_on": "Mon, 24 Jan 2022 18:11:59 GMT", + "rating": 5, + "source": null, + "source_url": null, + "text": null, + "user": { + "created": "Sun, 23 Jan 2022 15:17:48 GMT", + "display_name": "Ashutosh Aswal", + "id": "36b854f0-8601-4eab-8e59-3733e7f8de24", + "karma": 0, + "user_type": "Noob" + }, + "votes_negative_count": 0, + "votes_positive_count": 0 + }, + { + "created": "Thu, 27 Jan 2022 16:54:09 GMT", + "edits": 0, + "entity_id": "b7d92248-97e3-4450-8057-6fe06738f735", + "entity_type": "artist", + "full_name": "Creative Commons Attribution-ShareAlike 3.0 Unported", + "id": "d326c9d2-1e4c-448d-be05-5f6cd872de0d", + "info_url": "https://creativecommons.org/licenses/by-sa/3.0/", + "is_draft": false, + "is_hidden": false, + "language": "en", + "last_revision": { + "id": 11265, + "rating": 5, + "review_id": "d326c9d2-1e4c-448d-be05-5f6cd872de0d", + "text": null, + "timestamp": "Thu, 27 Jan 2022 16:54:09 GMT" + }, + "last_updated": "Thu, 27 Jan 2022 16:54:09 GMT", + "license_id": "CC BY-SA 3.0", + "popularity": 0, + "published_on": "Thu, 27 Jan 2022 16:54:09 GMT", + "rating": 5, + "source": null, + "source_url": null, + "text": null, + "user": { + "created": "Sat, 09 Feb 2019 13:06:27 GMT", + "display_name": "amCap1712", + "id": "23f97b7f-3ea3-4557-b808-1cb08474b28b", + "karma": 0, + "user_type": "Noob" + }, + "votes_negative_count": 0, + "votes_positive_count": 0 + } + ], + "rating_stats": { + "1": 0, + "2": 0, + "3": 0, + "4": 0, + "5": 2 + }, + "reviews_count": 2, + "top_reviews": [ + { + "created": "Mon, 24 Jan 2022 18:11:59 GMT", + "edits": 0, + "entity_id": "b7d92248-97e3-4450-8057-6fe06738f735", + "entity_type": "artist", + "full_name": "Creative Commons Attribution-ShareAlike 3.0 Unported", + "id": "89fb94fd-3e65-4e52-8d16-5bdca4572883", + "info_url": "https://creativecommons.org/licenses/by-sa/3.0/", + "is_draft": false, + "is_hidden": false, + "language": "en", + "last_revision": { + "id": 11258, + "rating": 5, + "review_id": "89fb94fd-3e65-4e52-8d16-5bdca4572883", + "text": null, + "timestamp": "Mon, 24 Jan 2022 18:11:59 GMT" + }, + "last_updated": "Mon, 24 Jan 2022 18:11:59 GMT", + "license_id": "CC BY-SA 3.0", + "popularity": 0, + "published_on": "Mon, 24 Jan 2022 18:11:59 GMT", + "rating": 5, + "source": null, + "source_url": null, + "text": null, + "user": { + "created": "Sun, 23 Jan 2022 15:17:48 GMT", + "display_name": "Ashutosh Aswal", + "id": "36b854f0-8601-4eab-8e59-3733e7f8de24", + "karma": 0, + "user_type": "Noob" + }, + "votes_negative_count": 0, + "votes_positive_count": 0 + }, + { + "created": "Thu, 27 Jan 2022 16:54:09 GMT", + "edits": 0, + "entity_id": "b7d92248-97e3-4450-8057-6fe06738f735", + "entity_type": "artist", + "full_name": "Creative Commons Attribution-ShareAlike 3.0 Unported", + "id": "d326c9d2-1e4c-448d-be05-5f6cd872de0d", + "info_url": "https://creativecommons.org/licenses/by-sa/3.0/", + "is_draft": false, + "is_hidden": false, + "language": "en", + "last_revision": { + "id": 11265, + "rating": 5, + "review_id": "d326c9d2-1e4c-448d-be05-5f6cd872de0d", + "text": null, + "timestamp": "Thu, 27 Jan 2022 16:54:09 GMT" + }, + "last_updated": "Thu, 27 Jan 2022 16:54:09 GMT", + "license_id": "CC BY-SA 3.0", + "popularity": 0, + "published_on": "Thu, 27 Jan 2022 16:54:09 GMT", + "rating": 5, + "source": null, + "source_url": null, + "text": null, + "user": { + "created": "Sat, 09 Feb 2019 13:06:27 GMT", + "display_name": "amCap1712", + "id": "23f97b7f-3ea3-4557-b808-1cb08474b28b", + "karma": 0, + "user_type": "Noob" + }, + "votes_negative_count": 0, + "votes_positive_count": 0 + } + ] + } + + :statuscode 200: no error + :statuscode 404: review not found + + :resheader Content-Type: *application/json* + """ + + try: + artist = db_artist.get_artist_by_id(str(artist_mbid)) + except db_exceptions.NoDataFoundException: + raise NotFound("Can't find an artist with ID: {artist_mbid}".format(artist_mbid=artist_mbid)) + + ratings_stats, average_rating = db_rating_stats.get_stats(artist_mbid, "artist") + + reviews_limit = 5 + + top_reviews, reviews_count = db_review.list_reviews( + entity_id=artist['id'], + entity_type='artist', + sort='popularity', + limit=reviews_limit, + offset=0, + ) + + latest_reviews, reviews_count = db_review.list_reviews( + entity_id=artist['id'], + entity_type='artist', + sort='popularity', + limit=reviews_limit, + offset=0, + ) + + top_reviews = [db_review.to_dict(p) for p in top_reviews] + latest_reviews = [db_review.to_dict(p) for p in latest_reviews] + + return jsonify(artist=artist, avg_rating=average_rating, rating_stats=ratings_stats, reviews_count=reviews_count, top_reviews=top_reviews, latest_reviews=latest_reviews) diff --git a/critiquebrainz/ws/constants.py b/critiquebrainz/ws/constants.py index c7645f6ff..29d1d46af 100644 --- a/critiquebrainz/ws/constants.py +++ b/critiquebrainz/ws/constants.py @@ -2,4 +2,5 @@ 'user', 'review', 'vote', + 'artist', ) From a6eb2cc0170f76954f1f9a5455daa2b9aa9360c6 Mon Sep 17 00:00:00 2001 From: Ansh Goyal Date: Tue, 15 Mar 2022 19:47:43 +0000 Subject: [PATCH 02/15] Fix edge cases --- critiquebrainz/db/rating_stats.py | 5 ++++- critiquebrainz/ws/artist/views.py | 7 +++---- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/critiquebrainz/db/rating_stats.py b/critiquebrainz/db/rating_stats.py index ee4f0803c..091692b77 100644 --- a/critiquebrainz/db/rating_stats.py +++ b/critiquebrainz/db/rating_stats.py @@ -37,8 +37,11 @@ def get_stats(entity_id, entity_type): }) row = result.fetchall() - ratings = [r[0]/20 for r in row] ratings_stats = {1: 0, 2: 0, 3: 0, 4:0, 5:0} + if row == []: + return ratings_stats, 0 + + ratings = [r[0]/20 for r in row if r[0] is not None] for rating in ratings: ratings_stats[rating] += 1 diff --git a/critiquebrainz/ws/artist/views.py b/critiquebrainz/ws/artist/views.py index 4d9503c77..35d71ffa3 100644 --- a/critiquebrainz/ws/artist/views.py +++ b/critiquebrainz/ws/artist/views.py @@ -1,8 +1,7 @@ from flask import Blueprint, jsonify import critiquebrainz.db.review as db_review import critiquebrainz.db.rating_stats as db_rating_stats -from critiquebrainz.db import exceptions as db_exceptions -import critiquebrainz.frontend.external.musicbrainz_db.artist as mb_artist +import brainzutils.musicbrainz_db.exceptions as db_exceptions from brainzutils.musicbrainz_db import artist as db_artist from critiquebrainz.decorators import crossdomain from critiquebrainz.ws.exceptions import NotFound @@ -193,7 +192,7 @@ def artist_entity_handler(artist_mbid): } :statuscode 200: no error - :statuscode 404: review not found + :statuscode 404: artist not found :resheader Content-Type: *application/json* """ @@ -218,7 +217,7 @@ def artist_entity_handler(artist_mbid): latest_reviews, reviews_count = db_review.list_reviews( entity_id=artist['id'], entity_type='artist', - sort='popularity', + sort='published_on', limit=reviews_limit, offset=0, ) From cc7868d66f82d527d92fed89faa433ba3cf7c2f0 Mon Sep 17 00:00:00 2001 From: Ansh Goyal Date: Tue, 15 Mar 2022 20:01:00 +0000 Subject: [PATCH 03/15] add /release-group endpoint --- critiquebrainz/ws/__init__.py | 3 + critiquebrainz/ws/constants.py | 1 + critiquebrainz/ws/release_group/__init__.py | 0 critiquebrainz/ws/release_group/views.py | 155 ++++++++++++++++++++ 4 files changed, 159 insertions(+) create mode 100644 critiquebrainz/ws/release_group/__init__.py create mode 100644 critiquebrainz/ws/release_group/views.py diff --git a/critiquebrainz/ws/__init__.py b/critiquebrainz/ws/__init__.py index 78cd20300..790f569b6 100644 --- a/critiquebrainz/ws/__init__.py +++ b/critiquebrainz/ws/__init__.py @@ -109,8 +109,11 @@ def _register_blueprints(app): from critiquebrainz.ws.user.views import user_bp from critiquebrainz.ws.review.bulk import bulk_review_bp from critiquebrainz.ws.artist.views import artist_bp + from critiquebrainz.ws.release_group.views import release_group_bp app.register_blueprint(oauth_bp, url_prefix="/oauth") app.register_blueprint(review_bp, url_prefix="/review") app.register_blueprint(user_bp, url_prefix="/user") app.register_blueprint(bulk_review_bp, url_prefix="/reviews") app.register_blueprint(artist_bp, url_prefix="/artist") + app.register_blueprint(release_group_bp, url_prefix="/release-group") + diff --git a/critiquebrainz/ws/constants.py b/critiquebrainz/ws/constants.py index 29d1d46af..cd6f76298 100644 --- a/critiquebrainz/ws/constants.py +++ b/critiquebrainz/ws/constants.py @@ -3,4 +3,5 @@ 'review', 'vote', 'artist', + 'release_group', ) diff --git a/critiquebrainz/ws/release_group/__init__.py b/critiquebrainz/ws/release_group/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/critiquebrainz/ws/release_group/views.py b/critiquebrainz/ws/release_group/views.py new file mode 100644 index 000000000..01068140d --- /dev/null +++ b/critiquebrainz/ws/release_group/views.py @@ -0,0 +1,155 @@ +from flask import Blueprint, jsonify +import critiquebrainz.db.review as db_review +import critiquebrainz.db.rating_stats as db_rating_stats +from brainzutils.musicbrainz_db import release_group as db_release_group +import brainzutils.musicbrainz_db.exceptions as db_exceptions +from critiquebrainz.decorators import crossdomain +from critiquebrainz.ws.exceptions import NotFound + +release_group_bp = Blueprint('ws_release_group', __name__) + + +@release_group_bp.route('/', methods=['GET', 'OPTIONS']) +@crossdomain(headers="Authorization, Content-Type") +def release_group_entity_handler(release_group_mbid): + """Get list of reviews. + + **Request Example:** + + .. code-block:: bash + + $ curl "https://critiquebrainz.org/ws/1/release-group/f0bd8ae8-321f-43d8-af87-e2f90d1b3817" \\ + -X GET + + **Response Example:** + + .. code-block:: json + + { + "avg_rating": 4.0, + "latest_reviews": [ + { + "created": "Wed, 01 Dec 2021 16:28:50 GMT", + "edits": 0, + "entity_id": "f0bd8ae8-321f-43d8-af87-e2f90d1b3817", + "entity_type": "release_group", + "full_name": "Creative Commons Attribution-ShareAlike 3.0 Unported", + "id": "b08b37f6-c644-470d-af48-e77b14331e09", + "info_url": "https://creativecommons.org/licenses/by-sa/3.0/", + "is_draft": false, + "is_hidden": false, + "language": "en", + "last_revision": { + "id": 11167, + "rating": 4, + "review_id": "b08b37f6-c644-470d-af48-e77b14331e09", + "text": "Hollywood Greats, a cover mount from The Daily Mirror, was one of my first ever newspaper cover mounts I got - and sadly it led me to believe that all newspaper covermounts were free goldmines ready to be plundered; how wrong was I!\r\n\r\n10 great original studio recordings, and 5 so-so tracks from \"underground\" artists, however those 5 artists were so underground that even the internet knows nothing of them.\r\n\r\nI'm probably biased with this one as it was my first - but a good collection either way.", + "timestamp": "Wed, 01 Dec 2021 16:28:50 GMT" + }, + "last_updated": "Wed, 01 Dec 2021 16:28:50 GMT", + "license_id": "CC BY-SA 3.0", + "popularity": 1, + "published_on": "Wed, 01 Dec 2021 16:28:50 GMT", + "rating": 4, + "source": null, + "source_url": null, + "text": "Hollywood Greats, a cover mount from The Daily Mirror, was one of my first ever newspaper cover mounts I got - and sadly it led me to believe that all newspaper covermounts were free goldmines ready to be plundered; how wrong was I!\r\n\r\n10 great original studio recordings, and 5 so-so tracks from \"underground\" artists, however those 5 artists were so underground that even the internet knows nothing of them.\r\n\r\nI'm probably biased with this one as it was my first - but a good collection either way.", + "user": { + "created": "Sat, 15 Aug 2020 15:48:39 GMT", + "display_name": "sound.and.vision", + "id": "dfdae69f-275f-41a2-82c7-ac5d1f9c8129", + "karma": 16, + "user_type": "Noob" + }, + "votes_negative_count": 0, + "votes_positive_count": 1 + } + ], + "rating_stats": { + "1": 0, + "2": 0, + "3": 0, + "4": 1, + "5": 0 + }, + "release_group": { + "id": "f0bd8ae8-321f-43d8-af87-e2f90d1b3817", + "title": "Hollywood Greats", + "type": "Album" + }, + "reviews_count": 1, + "top_reviews": [ + { + "created": "Wed, 01 Dec 2021 16:28:50 GMT", + "edits": 0, + "entity_id": "f0bd8ae8-321f-43d8-af87-e2f90d1b3817", + "entity_type": "release_group", + "full_name": "Creative Commons Attribution-ShareAlike 3.0 Unported", + "id": "b08b37f6-c644-470d-af48-e77b14331e09", + "info_url": "https://creativecommons.org/licenses/by-sa/3.0/", + "is_draft": false, + "is_hidden": false, + "language": "en", + "last_revision": { + "id": 11167, + "rating": 4, + "review_id": "b08b37f6-c644-470d-af48-e77b14331e09", + "text": "Hollywood Greats, a cover mount from The Daily Mirror, was one of my first ever newspaper cover mounts I got - and sadly it led me to believe that all newspaper covermounts were free goldmines ready to be plundered; how wrong was I!\r\n\r\n10 great original studio recordings, and 5 so-so tracks from \"underground\" artists, however those 5 artists were so underground that even the internet knows nothing of them.\r\n\r\nI'm probably biased with this one as it was my first - but a good collection either way.", + "timestamp": "Wed, 01 Dec 2021 16:28:50 GMT" + }, + "last_updated": "Wed, 01 Dec 2021 16:28:50 GMT", + "license_id": "CC BY-SA 3.0", + "popularity": 1, + "published_on": "Wed, 01 Dec 2021 16:28:50 GMT", + "rating": 4, + "source": null, + "source_url": null, + "text": "Hollywood Greats, a cover mount from The Daily Mirror, was one of my first ever newspaper cover mounts I got - and sadly it led me to believe that all newspaper covermounts were free goldmines ready to be plundered; how wrong was I!\r\n\r\n10 great original studio recordings, and 5 so-so tracks from \"underground\" artists, however those 5 artists were so underground that even the internet knows nothing of them.\r\n\r\nI'm probably biased with this one as it was my first - but a good collection either way.", + "user": { + "created": "Sat, 15 Aug 2020 15:48:39 GMT", + "display_name": "sound.and.vision", + "id": "dfdae69f-275f-41a2-82c7-ac5d1f9c8129", + "karma": 16, + "user_type": "Noob" + }, + "votes_negative_count": 0, + "votes_positive_count": 1 + } + ] + } + :statuscode 200: no error + :statuscode 404: release group not found + + :resheader Content-Type: *application/json* + """ + + + try: + release_group = db_release_group.get_release_group_by_id(str(release_group_mbid)) + except db_exceptions.NoDataFoundException: + raise NotFound("Can't find an release group with ID: {release_group_mbid}".format(release_group_mbid=release_group_mbid)) + + ratings_stats, average_rating = db_rating_stats.get_stats(release_group_mbid, "release_group") + + reviews_limit = 5 + + top_reviews, reviews_count = db_review.list_reviews( + entity_id=release_group_mbid, + entity_type='release_group', + sort='popularity', + limit=reviews_limit, + offset=0, + ) + + latest_reviews, reviews_count = db_review.list_reviews( + entity_id=release_group_mbid, + entity_type='release_group', + sort='published_on', + limit=reviews_limit, + offset=0, + ) + + top_reviews = [db_review.to_dict(p) for p in top_reviews] + latest_reviews = [db_review.to_dict(p) for p in latest_reviews] + + return jsonify(release_group=release_group, avg_rating=average_rating, rating_stats=ratings_stats, reviews_count=reviews_count, top_reviews=top_reviews, latest_reviews=latest_reviews) From d06a946cfecec97940d4cb5251f893eebbd6a2e6 Mon Sep 17 00:00:00 2001 From: Ansh Goyal Date: Thu, 1 Sep 2022 18:09:32 +0000 Subject: [PATCH 04/15] feat: Add endpoints for MB entities --- critiquebrainz/db/rating_stats.py | 5 +- critiquebrainz/ws/__init__.py | 11 + critiquebrainz/ws/artist/views.py | 390 +++++++++++++---------- critiquebrainz/ws/event/__init__.py | 0 critiquebrainz/ws/event/views.py | 302 ++++++++++++++++++ critiquebrainz/ws/label/__init__.py | 0 critiquebrainz/ws/label/views.py | 217 +++++++++++++ critiquebrainz/ws/place/__init__.py | 0 critiquebrainz/ws/place/views.py | 372 +++++++++++++++++++++ critiquebrainz/ws/recording/__init__.py | 0 critiquebrainz/ws/recording/views.py | 186 +++++++++++ critiquebrainz/ws/release_group/views.py | 88 +++-- critiquebrainz/ws/work/__init__.py | 0 critiquebrainz/ws/work/views.py | 261 +++++++++++++++ 14 files changed, 1638 insertions(+), 194 deletions(-) create mode 100644 critiquebrainz/ws/event/__init__.py create mode 100644 critiquebrainz/ws/event/views.py create mode 100644 critiquebrainz/ws/label/__init__.py create mode 100644 critiquebrainz/ws/label/views.py create mode 100644 critiquebrainz/ws/place/__init__.py create mode 100644 critiquebrainz/ws/place/views.py create mode 100644 critiquebrainz/ws/recording/__init__.py create mode 100644 critiquebrainz/ws/recording/views.py create mode 100644 critiquebrainz/ws/work/__init__.py create mode 100644 critiquebrainz/ws/work/views.py diff --git a/critiquebrainz/db/rating_stats.py b/critiquebrainz/db/rating_stats.py index 091692b77..dca3fd771 100644 --- a/critiquebrainz/db/rating_stats.py +++ b/critiquebrainz/db/rating_stats.py @@ -46,6 +46,9 @@ def get_stats(entity_id, entity_type): for rating in ratings: ratings_stats[rating] += 1 - average_rating = sum(ratings)/len(ratings) + if ratings: + average_rating = sum(ratings)/len(ratings) + else: + average_rating = 0 return ratings_stats, average_rating \ No newline at end of file diff --git a/critiquebrainz/ws/__init__.py b/critiquebrainz/ws/__init__.py index 790f569b6..3eab53294 100644 --- a/critiquebrainz/ws/__init__.py +++ b/critiquebrainz/ws/__init__.py @@ -7,6 +7,7 @@ deploy_env = os.environ.get('DEPLOY_ENV', '') CONSUL_CONFIG_FILE_RETRY_COUNT = 10 +REVIEWS_LIMIT = 5 def create_app(debug=None, config_path=None): @@ -109,11 +110,21 @@ def _register_blueprints(app): from critiquebrainz.ws.user.views import user_bp from critiquebrainz.ws.review.bulk import bulk_review_bp from critiquebrainz.ws.artist.views import artist_bp + from critiquebrainz.ws.label.views import label_bp + from critiquebrainz.ws.event.views import event_bp + from critiquebrainz.ws.place.views import place_bp + from critiquebrainz.ws.recording.views import recording_bp from critiquebrainz.ws.release_group.views import release_group_bp + from critiquebrainz.ws.work.views import work_bp app.register_blueprint(oauth_bp, url_prefix="/oauth") app.register_blueprint(review_bp, url_prefix="/review") app.register_blueprint(user_bp, url_prefix="/user") app.register_blueprint(bulk_review_bp, url_prefix="/reviews") app.register_blueprint(artist_bp, url_prefix="/artist") + app.register_blueprint(label_bp, url_prefix="/label") + app.register_blueprint(event_bp, url_prefix="/event") + app.register_blueprint(place_bp, url_prefix="/place") + app.register_blueprint(recording_bp, url_prefix="/recording") app.register_blueprint(release_group_bp, url_prefix="/release-group") + app.register_blueprint(work_bp, url_prefix="/work") diff --git a/critiquebrainz/ws/artist/views.py b/critiquebrainz/ws/artist/views.py index 35d71ffa3..058fe66d1 100644 --- a/critiquebrainz/ws/artist/views.py +++ b/critiquebrainz/ws/artist/views.py @@ -1,10 +1,11 @@ from flask import Blueprint, jsonify import critiquebrainz.db.review as db_review import critiquebrainz.db.rating_stats as db_rating_stats -import brainzutils.musicbrainz_db.exceptions as db_exceptions -from brainzutils.musicbrainz_db import artist as db_artist +from critiquebrainz.frontend.external.musicbrainz_db import artist as db_artist from critiquebrainz.decorators import crossdomain from critiquebrainz.ws.exceptions import NotFound +from critiquebrainz.ws.parser import Parser +from critiquebrainz.ws import REVIEWS_LIMIT artist_bp = Blueprint('ws_artist', __name__) @@ -27,202 +28,249 @@ def artist_entity_handler(artist_mbid): { "artist": { - "id": "b7d92248-97e3-4450-8057-6fe06738f735", - "name": "Shawn Mendes", - "sort_name": "Mendes, Shawn", - "type": "Person" - }, - "avg_rating": 5.0, + "artist-rels": [ + { + "artist": { + "life-span": { + "begin": "1981-07-17" + }, + "mbid": "60bc1e7d-d974-4205-bc9f-9dd3dba93534", + "name": "Sergey Semenov", + "sort_name": "Sergey Semenov", + "type": "Person" + }, + "begin-year": 2011, + "direction": "backward", + "end-year": null, + "type": "member of band", + "type-id": "5be4c609-9afa-4ea0-910b-12ffb71e3821" + }, + { + "artist": { + "life-span": { + "begin": "2020-10-01" + }, + "mbid": "ba2f69b0-dcda-4f81-b324-17f73f36980c", + "name": "DJ Repeet", + "sort_name": "Repeet, DJ", + "type": "Character" + }, + "begin-year": 2020, + "direction": "backward", + "end-year": null, + "type": "member of band", + "type-id": "5be4c609-9afa-4ea0-910b-12ffb71e3821" + } + ], + "band-members": [ + { + "artist": { + "life-span": { + "begin": "1981-07-17" + }, + "mbid": "60bc1e7d-d974-4205-bc9f-9dd3dba93534", + "name": "Sergey Semenov", + "sort_name": "Sergey Semenov", + "type": "Person" + }, + "begin-year": 2011, + "direction": "backward", + "end-year": null, + "type": "member of band", + "type-id": "5be4c609-9afa-4ea0-910b-12ffb71e3821" + }, + { + "artist": { + "life-span": { + "begin": "2020-10-01" + }, + "mbid": "ba2f69b0-dcda-4f81-b324-17f73f36980c", + "name": "DJ Repeet", + "sort_name": "Repeet, DJ", + "type": "Character" + }, + "begin-year": 2020, + "direction": "backward", + "end-year": null, + "type": "member of band", + "type-id": "5be4c609-9afa-4ea0-910b-12ffb71e3821" + } + ], + "external-urls": [ + { + "begin-year": null, + "direction": "forward", + "end-year": null, + "icon": "discogs-16.png", + "name": "Discogs", + "type": "discogs", + "type-id": "04a5b104-a4c2-4bac-99a1-7b837c37d9e4", + "url": { + "mbid": "694f02d8-1be0-4c69-ae04-4c63686d3fdb", + "url": "https://www.discogs.com/artist/10091563" + } + } + { + "begin-year": null, + "direction": "forward", + "end-year": null, + "icon": "wikidata-16.png", + "name": "Wikidata", + "type": "wikidata", + "type-id": "689870a4-a1e4-4912-b17f-7b2664215698", + "url": { + "mbid": "bddc058d-4cb6-4ced-a05c-de3bf2b60692", + "url": "https://www.wikidata.org/wiki/Q109645393" + } + } + ], + "life-span": { + "begin": "2011-07-17" + }, + "mbid": "df602ea4-c143-425d-a235-d7641f7634fd", + "name": "Senkino", + "sort_name": "Senkino", + "type": "Group" + }, + "average_rating": 5.0, "latest_reviews": [ { - "created": "Mon, 24 Jan 2022 18:11:59 GMT", - "edits": 0, - "entity_id": "b7d92248-97e3-4450-8057-6fe06738f735", - "entity_type": "artist", - "full_name": "Creative Commons Attribution-ShareAlike 3.0 Unported", - "id": "89fb94fd-3e65-4e52-8d16-5bdca4572883", - "info_url": "https://creativecommons.org/licenses/by-sa/3.0/", - "is_draft": false, - "is_hidden": false, - "language": "en", - "last_revision": { - "id": 11258, - "rating": 5, - "review_id": "89fb94fd-3e65-4e52-8d16-5bdca4572883", - "text": null, - "timestamp": "Mon, 24 Jan 2022 18:11:59 GMT" - }, - "last_updated": "Mon, 24 Jan 2022 18:11:59 GMT", - "license_id": "CC BY-SA 3.0", - "popularity": 0, - "published_on": "Mon, 24 Jan 2022 18:11:59 GMT", - "rating": 5, - "source": null, - "source_url": null, - "text": null, - "user": { - "created": "Sun, 23 Jan 2022 15:17:48 GMT", - "display_name": "Ashutosh Aswal", - "id": "36b854f0-8601-4eab-8e59-3733e7f8de24", - "karma": 0, - "user_type": "Noob" - }, - "votes_negative_count": 0, - "votes_positive_count": 0 - }, - { - "created": "Thu, 27 Jan 2022 16:54:09 GMT", - "edits": 0, - "entity_id": "b7d92248-97e3-4450-8057-6fe06738f735", - "entity_type": "artist", - "full_name": "Creative Commons Attribution-ShareAlike 3.0 Unported", - "id": "d326c9d2-1e4c-448d-be05-5f6cd872de0d", - "info_url": "https://creativecommons.org/licenses/by-sa/3.0/", - "is_draft": false, - "is_hidden": false, - "language": "en", - "last_revision": { - "id": 11265, - "rating": 5, - "review_id": "d326c9d2-1e4c-448d-be05-5f6cd872de0d", - "text": null, - "timestamp": "Thu, 27 Jan 2022 16:54:09 GMT" - }, - "last_updated": "Thu, 27 Jan 2022 16:54:09 GMT", - "license_id": "CC BY-SA 3.0", - "popularity": 0, - "published_on": "Thu, 27 Jan 2022 16:54:09 GMT", - "rating": 5, - "source": null, - "source_url": null, - "text": null, - "user": { - "created": "Sat, 09 Feb 2019 13:06:27 GMT", - "display_name": "amCap1712", - "id": "23f97b7f-3ea3-4557-b808-1cb08474b28b", - "karma": 0, - "user_type": "Noob" - }, - "votes_negative_count": 0, - "votes_positive_count": 0 + "created": "Wed, 24 Nov 2021 02:59:15 GMT", + "edits": 0, + "entity_id": "df602ea4-c143-425d-a235-d7641f7634fd", + "entity_type": "artist", + "full_name": "Creative Commons Attribution-ShareAlike 3.0 Unported", + "id": "0a3548a7-a014-4e71-a65b-d45fce677cf0", + "info_url": "https://creativecommons.org/licenses/by-sa/3.0/", + "is_draft": false, + "is_hidden": false, + "language": "en", + "last_revision": { + "id": 11139, + "rating": 5, + "review_id": "0a3548a7-a014-4e71-a65b-d45fce677cf0", + "text": null, + "timestamp": "Wed, 24 Nov 2021 02:59:15 GMT" + }, + "last_updated": "Wed, 24 Nov 2021 02:59:15 GMT", + "license_id": "CC BY-SA 3.0", + "popularity": 0, + "published_on": "Wed, 24 Nov 2021 02:59:15 GMT", + "rating": 5, + "source": null, + "source_url": null, + "text": null, + "user": { + "created": "Wed, 24 Nov 2021 02:56:16 GMT", + "display_name": "Rada87", + "id": "5e784575-4bb8-4a9f-8334-2cb87f073cf0", + "karma": 0, + "user_type": "Noob" + }, + "votes_negative_count": 0, + "votes_positive_count": 0 } - ], - "rating_stats": { - "1": 0, - "2": 0, - "3": 0, - "4": 0, - "5": 2 - }, - "reviews_count": 2, + ], + "ratings_stats": { + "1": 0, + "2": 0, + "3": 0, + "4": 0, + "5": 1 + }, + "reviews_count": 1, "top_reviews": [ { - "created": "Mon, 24 Jan 2022 18:11:59 GMT", - "edits": 0, - "entity_id": "b7d92248-97e3-4450-8057-6fe06738f735", - "entity_type": "artist", - "full_name": "Creative Commons Attribution-ShareAlike 3.0 Unported", - "id": "89fb94fd-3e65-4e52-8d16-5bdca4572883", - "info_url": "https://creativecommons.org/licenses/by-sa/3.0/", - "is_draft": false, - "is_hidden": false, - "language": "en", - "last_revision": { - "id": 11258, - "rating": 5, - "review_id": "89fb94fd-3e65-4e52-8d16-5bdca4572883", - "text": null, - "timestamp": "Mon, 24 Jan 2022 18:11:59 GMT" - }, - "last_updated": "Mon, 24 Jan 2022 18:11:59 GMT", - "license_id": "CC BY-SA 3.0", - "popularity": 0, - "published_on": "Mon, 24 Jan 2022 18:11:59 GMT", - "rating": 5, - "source": null, - "source_url": null, - "text": null, - "user": { - "created": "Sun, 23 Jan 2022 15:17:48 GMT", - "display_name": "Ashutosh Aswal", - "id": "36b854f0-8601-4eab-8e59-3733e7f8de24", - "karma": 0, - "user_type": "Noob" - }, - "votes_negative_count": 0, - "votes_positive_count": 0 - }, - { - "created": "Thu, 27 Jan 2022 16:54:09 GMT", - "edits": 0, - "entity_id": "b7d92248-97e3-4450-8057-6fe06738f735", - "entity_type": "artist", - "full_name": "Creative Commons Attribution-ShareAlike 3.0 Unported", - "id": "d326c9d2-1e4c-448d-be05-5f6cd872de0d", - "info_url": "https://creativecommons.org/licenses/by-sa/3.0/", - "is_draft": false, - "is_hidden": false, - "language": "en", - "last_revision": { - "id": 11265, - "rating": 5, - "review_id": "d326c9d2-1e4c-448d-be05-5f6cd872de0d", - "text": null, - "timestamp": "Thu, 27 Jan 2022 16:54:09 GMT" - }, - "last_updated": "Thu, 27 Jan 2022 16:54:09 GMT", - "license_id": "CC BY-SA 3.0", - "popularity": 0, - "published_on": "Thu, 27 Jan 2022 16:54:09 GMT", - "rating": 5, - "source": null, - "source_url": null, - "text": null, - "user": { - "created": "Sat, 09 Feb 2019 13:06:27 GMT", - "display_name": "amCap1712", - "id": "23f97b7f-3ea3-4557-b808-1cb08474b28b", - "karma": 0, - "user_type": "Noob" - }, - "votes_negative_count": 0, - "votes_positive_count": 0 + "created": "Wed, 24 Nov 2021 02:59:15 GMT", + "edits": 0, + "entity_id": "df602ea4-c143-425d-a235-d7641f7634fd", + "entity_type": "artist", + "full_name": "Creative Commons Attribution-ShareAlike 3.0 Unported", + "id": "0a3548a7-a014-4e71-a65b-d45fce677cf0", + "info_url": "https://creativecommons.org/licenses/by-sa/3.0/", + "is_draft": false, + "is_hidden": false, + "language": "en", + "last_revision": { + "id": 11139, + "rating": 5, + "review_id": "0a3548a7-a014-4e71-a65b-d45fce677cf0", + "text": null, + "timestamp": "Wed, 24 Nov 2021 02:59:15 GMT" + }, + "last_updated": "Wed, 24 Nov 2021 02:59:15 GMT", + "license_id": "CC BY-SA 3.0", + "popularity": 0, + "published_on": "Wed, 24 Nov 2021 02:59:15 GMT", + "rating": 5, + "source": null, + "source_url": null, + "text": null, + "user": { + "created": "Wed, 24 Nov 2021 02:56:16 GMT", + "display_name": "Rada87", + "id": "5e784575-4bb8-4a9f-8334-2cb87f073cf0", + "karma": 0, + "user_type": "Noob" + }, + "votes_negative_count": 0, + "votes_positive_count": 0 } ] } :statuscode 200: no error :statuscode 404: artist not found - + :resheader Content-Type: *application/json* """ - try: - artist = db_artist.get_artist_by_id(str(artist_mbid)) - except db_exceptions.NoDataFoundException: + artist = db_artist.get_artist_by_mbid(str(artist_mbid)) + if not artist: raise NotFound("Can't find an artist with ID: {artist_mbid}".format(artist_mbid=artist_mbid)) - ratings_stats, average_rating = db_rating_stats.get_stats(artist_mbid, "artist") + user_id = Parser.uuid('uri', 'user_id', optional=True) + if user_id: + user_review, _ = db_review.list_reviews( + entity_id=artist['mbid'], + entity_type='artist', + user_id=user_id + ) + if user_review: + user_review = db_review.to_dict(user_review[0]) + else: + user_review = None - reviews_limit = 5 + ratings_stats, average_rating = db_rating_stats.get_stats(artist_mbid, "artist") top_reviews, reviews_count = db_review.list_reviews( - entity_id=artist['id'], + entity_id=artist['mbid'], entity_type='artist', sort='popularity', - limit=reviews_limit, + limit=REVIEWS_LIMIT, offset=0, ) latest_reviews, reviews_count = db_review.list_reviews( - entity_id=artist['id'], + entity_id=artist['mbid'], entity_type='artist', sort='published_on', - limit=reviews_limit, + limit=REVIEWS_LIMIT, offset=0, ) - - top_reviews = [db_review.to_dict(p) for p in top_reviews] - latest_reviews = [db_review.to_dict(p) for p in latest_reviews] - return jsonify(artist=artist, avg_rating=average_rating, rating_stats=ratings_stats, reviews_count=reviews_count, top_reviews=top_reviews, latest_reviews=latest_reviews) + top_reviews = [db_review.to_dict(review) for review in top_reviews] + latest_reviews = [db_review.to_dict(review) for review in latest_reviews] + + result = { + "artist": artist, + "average_rating": average_rating, + "ratings_stats": ratings_stats, + "reviews_count": reviews_count, + "top_reviews": top_reviews, + "latest_reviews": latest_reviews + } + if user_id: + result['user_review'] = user_review + + return jsonify(**result) diff --git a/critiquebrainz/ws/event/__init__.py b/critiquebrainz/ws/event/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/critiquebrainz/ws/event/views.py b/critiquebrainz/ws/event/views.py new file mode 100644 index 000000000..1404e51a4 --- /dev/null +++ b/critiquebrainz/ws/event/views.py @@ -0,0 +1,302 @@ +from flask import Blueprint, jsonify +import critiquebrainz.db.review as db_review +import critiquebrainz.db.rating_stats as db_rating_stats +from critiquebrainz.frontend.external.musicbrainz_db import event as db_event +from critiquebrainz.decorators import crossdomain +from critiquebrainz.ws.exceptions import NotFound +from critiquebrainz.ws.parser import Parser +from critiquebrainz.ws import REVIEWS_LIMIT + +event_bp = Blueprint('ws_event', __name__) + + +@event_bp.route('/', methods=['GET', 'OPTIONS']) +@crossdomain(headers="Authorization, Content-Type") +def event_entity_handler(event_mbid): + """Get list of reviews. + + **Request Example:** + + .. code-block:: bash + + $ curl "https://critiquebrainz.org/ws/1/event/3f0b7c79-1d66-40d2-a8a0-54be9ca2b18c" \\ + -X GET + + **Response Example:** + + .. code-block:: json + + { + "average_rating": 0, + "event": { + "artist-rels": [ + { + "artist": { + "life-span": { + "begin": "1989-12-13" + }, + "mbid": "20244d07-534f-4eff-b4d4-930878889970", + "name": "Taylor Swift", + "sort_name": "Swift, Taylor", + "type": "Person" + }, + "begin-year": null, + "direction": "backward", + "end-year": null, + "type": "main performer", + "type-id": "936c7c95-3156-3889-a062-8a0cd57f8946" + }, + { + "artist": { + "life-span": { + "begin": "1989-12-13" + }, + "mbid": "20244d07-534f-4eff-b4d4-930878889970", + "name": "Taylor Swift", + "sort_name": "Swift, Taylor", + "type": "Person" + }, + "begin-year": null, + "direction": "backward", + "end-year": null, + "type": "main performer", + "type-id": "936c7c95-3156-3889-a062-8a0cd57f8946" + }, + { + "artist": { + "life-span": { + "begin": "1989-12-13" + }, + "mbid": "20244d07-534f-4eff-b4d4-930878889970", + "name": "Taylor Swift", + "sort_name": "Swift, Taylor", + "type": "Person" + }, + "begin-year": null, + "direction": "backward", + "end-year": null, + "type": "main performer", + "type-id": "936c7c95-3156-3889-a062-8a0cd57f8946" + } + ], + "life-span": { + "begin": "2015-05-05", + "end": "2015-12-12" + }, + "mbid": "3f0b7c79-1d66-40d2-a8a0-54be9ca2b18c", + "name": "The 1989 World Tour by Taylor Swift", + "type": "Concert" + }, + "latest_reviews": [ + { + "created": "Sun, 24 Jan 2016 16:46:47 GMT", + "edits": 0, + "entity_id": "3f0b7c79-1d66-40d2-a8a0-54be9ca2b18c", + "entity_type": "event", + "full_name": "Creative Commons Attribution-ShareAlike 3.0 Unported", + "id": "1da0c290-2c8c-47b4-9703-b172aaf9576f", + "info_url": "https://creativecommons.org/licenses/by-sa/3.0/", + "is_draft": false, + "is_hidden": false, + "language": "en", + "last_revision": { + "id": 9753, + "rating": null, + "review_id": "1da0c290-2c8c-47b4-9703-b172aaf9576f", + "text": "I had never been to a concert before, so when my mom told me that she had snagged tickets to see Taylor Swift's 1989 tour, I was speechless, anxious, overwhelmingly excited, and eager. I didn't know what to expect, but I knew Taylor was known for singing amazingly live and the numerous guest stars and the pretty costumes, lights, and scenes.\r\n\r\nBoy, was I right. The night was surely a night that I will never forget. Taylor started off with \"Welcome to New York\", one of my favorites, and continued onto some other gems of her album such as \"The New Romantics\" and \"Blank Space. I definitely was one of the embarrassing screaming girls that you always see in the audience. But everyone in the crowd was singing along to the lyrics of her new platinum album (how could you not?) and danced as she belted and strutted around the stage.\r\n\r\nTaylor Swift continued onto some of her older songs of previous years like \"I Know You Were Trouble\" and \"Mean.\" I loved them just as much! Her voice was so beautiful as the night continued. I wonder how someone could be so talented of that caliber - to be able to sing and dance for hours on end. In between tracks, she would thank the crowd and express her gratitude of being able to perform for us all. She ended the night with a bang, singing some of her classic hits like \"Love Story\" and \"We Are Never Getting Back Together.\"\r\n\r\nUltimately, one of the most momentous occasions I have ever experienced. I can't speak for other artists, but Taylor really felt the energy of the crowd and maintained that vigorous happy energy and she smiled and sang and just looked like she was having so much fun.", + "timestamp": "Sun, 24 Jan 2016 16:46:47 GMT" + }, + "last_updated": "Sun, 24 Jan 2016 16:46:47 GMT", + "license_id": "CC BY-SA 3.0", + "popularity": 1, + "published_on": "Sun, 24 Jan 2016 16:46:47 GMT", + "rating": null, + "source": null, + "source_url": null, + "text": "I had never been to a concert before, so when my mom told me that she had snagged tickets to see Taylor Swift's 1989 tour, I was speechless, anxious, overwhelmingly excited, and eager. I didn't know what to expect, but I knew Taylor was known for singing amazingly live and the numerous guest stars and the pretty costumes, lights, and scenes.\r\n\r\nBoy, was I right. The night was surely a night that I will never forget. Taylor started off with \"Welcome to New York\", one of my favorites, and continued onto some other gems of her album such as \"The New Romantics\" and \"Blank Space. I definitely was one of the embarrassing screaming girls that you always see in the audience. But everyone in the crowd was singing along to the lyrics of her new platinum album (how could you not?) and danced as she belted and strutted around the stage.\r\n\r\nTaylor Swift continued onto some of her older songs of previous years like \"I Know You Were Trouble\" and \"Mean.\" I loved them just as much! Her voice was so beautiful as the night continued. I wonder how someone could be so talented of that caliber - to be able to sing and dance for hours on end. In between tracks, she would thank the crowd and express her gratitude of being able to perform for us all. She ended the night with a bang, singing some of her classic hits like \"Love Story\" and \"We Are Never Getting Back Together.\"\r\n\r\nUltimately, one of the most momentous occasions I have ever experienced. I can't speak for other artists, but Taylor really felt the energy of the crowd and maintained that vigorous happy energy and she smiled and sang and just looked like she was having so much fun.", + "user": { + "created": "Sun, 24 Jan 2016 16:33:34 GMT", + "display_name": "michelletu", + "id": "8f5deaf3-84b9-4024-8751-6dbc0f9cf4ef", + "karma": 1, + "user_type": "Noob" + }, + "votes_negative_count": 0, + "votes_positive_count": 1 + }, + { + "created": "Sun, 17 Jan 2016 10:02:06 GMT", + "edits": 0, + "entity_id": "3f0b7c79-1d66-40d2-a8a0-54be9ca2b18c", + "entity_type": "event", + "full_name": "Creative Commons Attribution-ShareAlike 3.0 Unported", + "id": "b02b8836-d7e2-4077-9443-38c0c0f6725f", + "info_url": "https://creativecommons.org/licenses/by-sa/3.0/", + "is_draft": false, + "is_hidden": false, + "language": "en", + "last_revision": { + "id": 9636, + "rating": null, + "review_id": "b02b8836-d7e2-4077-9443-38c0c0f6725f", + "text": "It was one of the most hyped up music event of the year, and it certainly lived up to expectations. When it was announced that Taylor Swift would be bringing her 1989 World Tour to Singapore in November, I, among many others, made a mad rush to grab at whatever tickets we could. On the grand day itself, we were treated to a musical feast at the Singapore Indoor Stadium, which was truly an unforgettable experience. \r\n\r\nTaylor Swift started off her concert with a rousing medley of songs (\"Welcome to New York\", \"The New Romantics\" and \"Blank Space) from her platinum release album 1989, which certainly did not fail to wow the crowd. However, what made Taylor Swift's songs different this time round was that she added a Singapore touch to them, thus resonating with Singaporeans at her concert. For instance, during her dynamic single \"Blank Space\", Taylor used a metal rod to strike a pole situated in the middle of the runway, looping her saying \"Singapore\" into the backing track. \r\n\r\nNext, Taylor belted out classics from her previous albums, such as \"I Knew You Were Trouble\" and \"I Wish You Would\". In between the singles, Taylor thanked everyone at the concert for supporting her latest album \"1989\", which brought the crowd roaring with excitement. \"How You Get the Girl\" and \"You Belong With Me\" were up next, with the audience singing along to the classics. \r\n\r\nSome of her biggest hits were also subjected to makeovers. With Taylor strumming a white electric guitar, \"We Are Never Ever Getting Back Together\" sounded much heftier than the recorded version. Taylor also slowed down classics \"I Knew You Were Trouble\" and \"Love Story\" by a notch, creating a new dimension for familiar tunes. \r\n\r\nHowever, there were a couple of minor disappointments at the concert. Prior to the concert, many made wild guesses as to who the surprise guests at the Singapore concert would be - after all, Taylor had been making headlines for inviting her celebrity friends to join her in the 1989 World Tour. Hence, when we realised that there were not going to be any surprise guests, we could not help but feel a little shortchanged. But Taylor's last song for the concert, \"Shake It Off\", immediately helped us feel much better, with almost everyone jumping to their feet to dance along with Taylor, who was strutting her moves on the runway. \r\n\r\nAll in all, it was a dynamic night at the Singapore Indoor Stadium, with Taylor Swift belting out song after song, from classics to singles from her latest album. \r\n", + "timestamp": "Mon, 18 Jan 2016 22:52:28 GMT" + }, + "last_updated": "Mon, 18 Jan 2016 22:52:28 GMT", + "license_id": "CC BY-SA 3.0", + "popularity": 0, + "published_on": "Mon, 18 Jan 2016 22:52:28 GMT", + "rating": null, + "source": null, + "source_url": null, + "text": "It was one of the most hyped up music event of the year, and it certainly lived up to expectations. When it was announced that Taylor Swift would be bringing her 1989 World Tour to Singapore in November, I, among many others, made a mad rush to grab at whatever tickets we could. On the grand day itself, we were treated to a musical feast at the Singapore Indoor Stadium, which was truly an unforgettable experience. \r\n\r\nTaylor Swift started off her concert with a rousing medley of songs (\"Welcome to New York\", \"The New Romantics\" and \"Blank Space) from her platinum release album 1989, which certainly did not fail to wow the crowd. However, what made Taylor Swift's songs different this time round was that she added a Singapore touch to them, thus resonating with Singaporeans at her concert. For instance, during her dynamic single \"Blank Space\", Taylor used a metal rod to strike a pole situated in the middle of the runway, looping her saying \"Singapore\" into the backing track. \r\n\r\nNext, Taylor belted out classics from her previous albums, such as \"I Knew You Were Trouble\" and \"I Wish You Would\". In between the singles, Taylor thanked everyone at the concert for supporting her latest album \"1989\", which brought the crowd roaring with excitement. \"How You Get the Girl\" and \"You Belong With Me\" were up next, with the audience singing along to the classics. \r\n\r\nSome of her biggest hits were also subjected to makeovers. With Taylor strumming a white electric guitar, \"We Are Never Ever Getting Back Together\" sounded much heftier than the recorded version. Taylor also slowed down classics \"I Knew You Were Trouble\" and \"Love Story\" by a notch, creating a new dimension for familiar tunes. \r\n\r\nHowever, there were a couple of minor disappointments at the concert. Prior to the concert, many made wild guesses as to who the surprise guests at the Singapore concert would be - after all, Taylor had been making headlines for inviting her celebrity friends to join her in the 1989 World Tour. Hence, when we realised that there were not going to be any surprise guests, we could not help but feel a little shortchanged. But Taylor's last song for the concert, \"Shake It Off\", immediately helped us feel much better, with almost everyone jumping to their feet to dance along with Taylor, who was strutting her moves on the runway. \r\n\r\nAll in all, it was a dynamic night at the Singapore Indoor Stadium, with Taylor Swift belting out song after song, from classics to singles from her latest album. \r\n", + "user": { + "created": "Sun, 17 Jan 2016 06:49:23 GMT", + "display_name": "gabriellee", + "id": "172631a3-8338-4aaf-84c8-1ed0f02c56c1", + "karma": 0, + "user_type": "Noob" + }, + "votes_negative_count": 0, + "votes_positive_count": 0 + } + ], + "ratings_stats": { + "1": 0, + "2": 0, + "3": 0, + "4": 0, + "5": 0 + }, + "reviews_count": 2, + "top_reviews": [ + { + "created": "Sun, 24 Jan 2016 16:46:47 GMT", + "edits": 0, + "entity_id": "3f0b7c79-1d66-40d2-a8a0-54be9ca2b18c", + "entity_type": "event", + "full_name": "Creative Commons Attribution-ShareAlike 3.0 Unported", + "id": "1da0c290-2c8c-47b4-9703-b172aaf9576f", + "info_url": "https://creativecommons.org/licenses/by-sa/3.0/", + "is_draft": false, + "is_hidden": false, + "language": "en", + "last_revision": { + "id": 9753, + "rating": null, + "review_id": "1da0c290-2c8c-47b4-9703-b172aaf9576f", + "text": "I had never been to a concert before, so when my mom told me that she had snagged tickets to see Taylor Swift's 1989 tour, I was speechless, anxious, overwhelmingly excited, and eager. I didn't know what to expect, but I knew Taylor was known for singing amazingly live and the numerous guest stars and the pretty costumes, lights, and scenes.\r\n\r\nBoy, was I right. The night was surely a night that I will never forget. Taylor started off with \"Welcome to New York\", one of my favorites, and continued onto some other gems of her album such as \"The New Romantics\" and \"Blank Space. I definitely was one of the embarrassing screaming girls that you always see in the audience. But everyone in the crowd was singing along to the lyrics of her new platinum album (how could you not?) and danced as she belted and strutted around the stage.\r\n\r\nTaylor Swift continued onto some of her older songs of previous years like \"I Know You Were Trouble\" and \"Mean.\" I loved them just as much! Her voice was so beautiful as the night continued. I wonder how someone could be so talented of that caliber - to be able to sing and dance for hours on end. In between tracks, she would thank the crowd and express her gratitude of being able to perform for us all. She ended the night with a bang, singing some of her classic hits like \"Love Story\" and \"We Are Never Getting Back Together.\"\r\n\r\nUltimately, one of the most momentous occasions I have ever experienced. I can't speak for other artists, but Taylor really felt the energy of the crowd and maintained that vigorous happy energy and she smiled and sang and just looked like she was having so much fun.", + "timestamp": "Sun, 24 Jan 2016 16:46:47 GMT" + }, + "last_updated": "Sun, 24 Jan 2016 16:46:47 GMT", + "license_id": "CC BY-SA 3.0", + "popularity": 1, + "published_on": "Sun, 24 Jan 2016 16:46:47 GMT", + "rating": null, + "source": null, + "source_url": null, + "text": "I had never been to a concert before, so when my mom told me that she had snagged tickets to see Taylor Swift's 1989 tour, I was speechless, anxious, overwhelmingly excited, and eager. I didn't know what to expect, but I knew Taylor was known for singing amazingly live and the numerous guest stars and the pretty costumes, lights, and scenes.\r\n\r\nBoy, was I right. The night was surely a night that I will never forget. Taylor started off with \"Welcome to New York\", one of my favorites, and continued onto some other gems of her album such as \"The New Romantics\" and \"Blank Space. I definitely was one of the embarrassing screaming girls that you always see in the audience. But everyone in the crowd was singing along to the lyrics of her new platinum album (how could you not?) and danced as she belted and strutted around the stage.\r\n\r\nTaylor Swift continued onto some of her older songs of previous years like \"I Know You Were Trouble\" and \"Mean.\" I loved them just as much! Her voice was so beautiful as the night continued. I wonder how someone could be so talented of that caliber - to be able to sing and dance for hours on end. In between tracks, she would thank the crowd and express her gratitude of being able to perform for us all. She ended the night with a bang, singing some of her classic hits like \"Love Story\" and \"We Are Never Getting Back Together.\"\r\n\r\nUltimately, one of the most momentous occasions I have ever experienced. I can't speak for other artists, but Taylor really felt the energy of the crowd and maintained that vigorous happy energy and she smiled and sang and just looked like she was having so much fun.", + "user": { + "created": "Sun, 24 Jan 2016 16:33:34 GMT", + "display_name": "michelletu", + "id": "8f5deaf3-84b9-4024-8751-6dbc0f9cf4ef", + "karma": 1, + "user_type": "Noob" + }, + "votes_negative_count": 0, + "votes_positive_count": 1 + }, + { + "created": "Sun, 17 Jan 2016 10:02:06 GMT", + "edits": 0, + "entity_id": "3f0b7c79-1d66-40d2-a8a0-54be9ca2b18c", + "entity_type": "event", + "full_name": "Creative Commons Attribution-ShareAlike 3.0 Unported", + "id": "b02b8836-d7e2-4077-9443-38c0c0f6725f", + "info_url": "https://creativecommons.org/licenses/by-sa/3.0/", + "is_draft": false, + "is_hidden": false, + "language": "en", + "last_revision": { + "id": 9636, + "rating": null, + "review_id": "b02b8836-d7e2-4077-9443-38c0c0f6725f", + "text": "It was one of the most hyped up music event of the year, and it certainly lived up to expectations. When it was announced that Taylor Swift would be bringing her 1989 World Tour to Singapore in November, I, among many others, made a mad rush to grab at whatever tickets we could. On the grand day itself, we were treated to a musical feast at the Singapore Indoor Stadium, which was truly an unforgettable experience. \r\n\r\nTaylor Swift started off her concert with a rousing medley of songs (\"Welcome to New York\", \"The New Romantics\" and \"Blank Space) from her platinum release album 1989, which certainly did not fail to wow the crowd. However, what made Taylor Swift's songs different this time round was that she added a Singapore touch to them, thus resonating with Singaporeans at her concert. For instance, during her dynamic single \"Blank Space\", Taylor used a metal rod to strike a pole situated in the middle of the runway, looping her saying \"Singapore\" into the backing track. \r\n\r\nNext, Taylor belted out classics from her previous albums, such as \"I Knew You Were Trouble\" and \"I Wish You Would\". In between the singles, Taylor thanked everyone at the concert for supporting her latest album \"1989\", which brought the crowd roaring with excitement. \"How You Get the Girl\" and \"You Belong With Me\" were up next, with the audience singing along to the classics. \r\n\r\nSome of her biggest hits were also subjected to makeovers. With Taylor strumming a white electric guitar, \"We Are Never Ever Getting Back Together\" sounded much heftier than the recorded version. Taylor also slowed down classics \"I Knew You Were Trouble\" and \"Love Story\" by a notch, creating a new dimension for familiar tunes. \r\n\r\nHowever, there were a couple of minor disappointments at the concert. Prior to the concert, many made wild guesses as to who the surprise guests at the Singapore concert would be - after all, Taylor had been making headlines for inviting her celebrity friends to join her in the 1989 World Tour. Hence, when we realised that there were not going to be any surprise guests, we could not help but feel a little shortchanged. But Taylor's last song for the concert, \"Shake It Off\", immediately helped us feel much better, with almost everyone jumping to their feet to dance along with Taylor, who was strutting her moves on the runway. \r\n\r\nAll in all, it was a dynamic night at the Singapore Indoor Stadium, with Taylor Swift belting out song after song, from classics to singles from her latest album. \r\n", + "timestamp": "Mon, 18 Jan 2016 22:52:28 GMT" + }, + "last_updated": "Mon, 18 Jan 2016 22:52:28 GMT", + "license_id": "CC BY-SA 3.0", + "popularity": 0, + "published_on": "Mon, 18 Jan 2016 22:52:28 GMT", + "rating": null, + "source": null, + "source_url": null, + "text": "It was one of the most hyped up music event of the year, and it certainly lived up to expectations. When it was announced that Taylor Swift would be bringing her 1989 World Tour to Singapore in November, I, among many others, made a mad rush to grab at whatever tickets we could. On the grand day itself, we were treated to a musical feast at the Singapore Indoor Stadium, which was truly an unforgettable experience. \r\n\r\nTaylor Swift started off her concert with a rousing medley of songs (\"Welcome to New York\", \"The New Romantics\" and \"Blank Space) from her platinum release album 1989, which certainly did not fail to wow the crowd. However, what made Taylor Swift's songs different this time round was that she added a Singapore touch to them, thus resonating with Singaporeans at her concert. For instance, during her dynamic single \"Blank Space\", Taylor used a metal rod to strike a pole situated in the middle of the runway, looping her saying \"Singapore\" into the backing track. \r\n\r\nNext, Taylor belted out classics from her previous albums, such as \"I Knew You Were Trouble\" and \"I Wish You Would\". In between the singles, Taylor thanked everyone at the concert for supporting her latest album \"1989\", which brought the crowd roaring with excitement. \"How You Get the Girl\" and \"You Belong With Me\" were up next, with the audience singing along to the classics. \r\n\r\nSome of her biggest hits were also subjected to makeovers. With Taylor strumming a white electric guitar, \"We Are Never Ever Getting Back Together\" sounded much heftier than the recorded version. Taylor also slowed down classics \"I Knew You Were Trouble\" and \"Love Story\" by a notch, creating a new dimension for familiar tunes. \r\n\r\nHowever, there were a couple of minor disappointments at the concert. Prior to the concert, many made wild guesses as to who the surprise guests at the Singapore concert would be - after all, Taylor had been making headlines for inviting her celebrity friends to join her in the 1989 World Tour. Hence, when we realised that there were not going to be any surprise guests, we could not help but feel a little shortchanged. But Taylor's last song for the concert, \"Shake It Off\", immediately helped us feel much better, with almost everyone jumping to their feet to dance along with Taylor, who was strutting her moves on the runway. \r\n\r\nAll in all, it was a dynamic night at the Singapore Indoor Stadium, with Taylor Swift belting out song after song, from classics to singles from her latest album. \r\n", + "user": { + "created": "Sun, 17 Jan 2016 06:49:23 GMT", + "display_name": "gabriellee", + "id": "172631a3-8338-4aaf-84c8-1ed0f02c56c1", + "karma": 0, + "user_type": "Noob" + }, + "votes_negative_count": 0, + "votes_positive_count": 0 + } + ] + } + :statuscode 200: no error + :statuscode 404: label not found + + :resheader Content-Type: *application/json* + """ + + event = db_event.get_event_by_mbid(str(event_mbid)) + + if not event: + raise NotFound("Can't find an event with ID: {event_mbid}".format(event_mbid=event_mbid)) + + user_id = Parser.uuid('uri', 'user_id', optional=True) + if user_id: + user_review, _ = db_review.list_reviews( + entity_id=event['mbid'], + entity_type='event', + user_id=user_id + ) + if user_review: + user_review = db_review.to_dict(user_review[0]) + else: + user_review = None + + ratings_stats, average_rating = db_rating_stats.get_stats(event_mbid, "event") + + top_reviews, reviews_count = db_review.list_reviews( + entity_id=event['mbid'], + entity_type='event', + sort='popularity', + limit=REVIEWS_LIMIT, + offset=0, + ) + + latest_reviews, reviews_count = db_review.list_reviews( + entity_id=event['mbid'], + entity_type='event', + sort='published_on', + limit=REVIEWS_LIMIT, + offset=0, + ) + + top_reviews = [db_review.to_dict(review) for review in top_reviews] + latest_reviews = [db_review.to_dict(review) for review in latest_reviews] + + result = { + "event": event, + "average_rating": average_rating, + "ratings_stats": ratings_stats, + "reviews_count": reviews_count, + "top_reviews": top_reviews, + "latest_reviews": latest_reviews + } + if user_id: + result['user_review'] = user_review + + return jsonify(**result) diff --git a/critiquebrainz/ws/label/__init__.py b/critiquebrainz/ws/label/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/critiquebrainz/ws/label/views.py b/critiquebrainz/ws/label/views.py new file mode 100644 index 000000000..aed63948d --- /dev/null +++ b/critiquebrainz/ws/label/views.py @@ -0,0 +1,217 @@ +from flask import Blueprint, jsonify +import critiquebrainz.db.review as db_review +import critiquebrainz.db.rating_stats as db_rating_stats +from critiquebrainz.frontend.external.musicbrainz_db import label as db_label +from critiquebrainz.decorators import crossdomain +from critiquebrainz.ws.exceptions import NotFound +from critiquebrainz.ws.parser import Parser +from critiquebrainz.ws import REVIEWS_LIMIT + +label_bp = Blueprint('ws_label', __name__) + + +@label_bp.route('/', methods=['GET', 'OPTIONS']) +@crossdomain(headers="Authorization, Content-Type") +def label_entity_handler(label_mbid): + """Get list of reviews. + + **Request Example:** + + .. code-block:: bash + + $ curl "https://critiquebrainz.org/ws/1/label/e268deeb-31bc-4428-9caf-c7e2726cd496" \\ + -X GET + + **Response Example:** + + .. code-block:: json + + { + "average_rating": 5.0, + "label": { + "area": "Dakar", + "artist-rels": [ + { + "artist": { + "comment": "Songwriter / Producer / CEO", + "life-span": { + "begin": "1992-06-07" + }, + "mbid": "547bb51b-a016-402e-8120-50c3e8def75b", + "name": "Med Mouha", + "sort_name": "Mouha, Med", + "type": "Person" + }, + "begin-year": 2019, + "direction": "backward", + "end-year": null, + "type": "label founder", + "type-id": "577996f3-7ff9-45cf-877e-740fb1267a63" + } + ], + "comment": "Records, Dabass", + "external-urls": [ + { + "begin-year": null, + "direction": "forward", + "end-year": null, + "icon": "discogs-16.png", + "name": "Discogs", + "type": "discogs", + "type-id": "5b987f87-25bc-4a2d-b3f1-3618795b8207", + "url": { + "mbid": "e810e5d5-d2cc-406a-9365-a2bc84e35754", + "url": "https://www.discogs.com/label/2385910" + } + } + ], + "life-span": { + "begin": "2019" + }, + "mbid": "e268deeb-31bc-4428-9caf-c7e2726cd496", + "name": "Dabass Records", + "rating": 100, + "type": "Production" + }, + "latest_reviews": [ + { + "created": "Sun, 05 Sep 2021 16:57:39 GMT", + "edits": 0, + "entity_id": "e268deeb-31bc-4428-9caf-c7e2726cd496", + "entity_type": "label", + "full_name": "Creative Commons Attribution-ShareAlike 3.0 Unported", + "id": "5d941575-29ff-4dde-a48a-ad0ba5daec8c", + "info_url": "https://creativecommons.org/licenses/by-sa/3.0/", + "is_draft": false, + "is_hidden": false, + "language": "en", + "last_revision": { + "id": 10991, + "rating": 5, + "review_id": "5d941575-29ff-4dde-a48a-ad0ba5daec8c", + "text": null, + "timestamp": "Sun, 05 Sep 2021 16:57:39 GMT" + }, + "last_updated": "Sun, 05 Sep 2021 16:57:39 GMT", + "license_id": "CC BY-SA 3.0", + "popularity": 0, + "published_on": "Sun, 05 Sep 2021 16:57:39 GMT", + "rating": 5, + "source": null, + "source_url": null, + "text": null, + "user": { + "created": "Sun, 05 Sep 2021 14:20:33 GMT", + "display_name": "Medmouha", + "id": "20d9fb34-dc1f-4aa4-8082-c69e4b20f660", + "karma": 0, + "user_type": "Noob" + }, + "votes_negative_count": 0, + "votes_positive_count": 0 + } + ], + "ratings_stats": { + "1": 0, + "2": 0, + "3": 0, + "4": 0, + "5": 1 + }, + "reviews_count": 1, + "top_reviews": [ + { + "created": "Sun, 05 Sep 2021 16:57:39 GMT", + "edits": 0, + "entity_id": "e268deeb-31bc-4428-9caf-c7e2726cd496", + "entity_type": "label", + "full_name": "Creative Commons Attribution-ShareAlike 3.0 Unported", + "id": "5d941575-29ff-4dde-a48a-ad0ba5daec8c", + "info_url": "https://creativecommons.org/licenses/by-sa/3.0/", + "is_draft": false, + "is_hidden": false, + "language": "en", + "last_revision": { + "id": 10991, + "rating": 5, + "review_id": "5d941575-29ff-4dde-a48a-ad0ba5daec8c", + "text": null, + "timestamp": "Sun, 05 Sep 2021 16:57:39 GMT" + }, + "last_updated": "Sun, 05 Sep 2021 16:57:39 GMT", + "license_id": "CC BY-SA 3.0", + "popularity": 0, + "published_on": "Sun, 05 Sep 2021 16:57:39 GMT", + "rating": 5, + "source": null, + "source_url": null, + "text": null, + "user": { + "created": "Sun, 05 Sep 2021 14:20:33 GMT", + "display_name": "Medmouha", + "id": "20d9fb34-dc1f-4aa4-8082-c69e4b20f660", + "karma": 0, + "user_type": "Noob" + }, + "votes_negative_count": 0, + "votes_positive_count": 0 + } + ] + } + + :statuscode 200: no error + :statuscode 404: label not found + + :resheader Content-Type: *application/json* + """ + + label = db_label.get_label_by_mbid(str(label_mbid)) + + if not label: + raise NotFound("Can't find a label with ID: {label_mbid}".format(label_mbid=label_mbid)) + + user_id = Parser.uuid('uri', 'user_id', optional=True) + if user_id: + user_review, _ = db_review.list_reviews( + entity_id=label['mbid'], + entity_type='label', + user_id=user_id + ) + if user_review: + user_review = db_review.to_dict(user_review[0]) + else: + user_review = None + + ratings_stats, average_rating = db_rating_stats.get_stats(label_mbid, "label") + + top_reviews, reviews_count = db_review.list_reviews( + entity_id=label['mbid'], + entity_type='label', + sort='popularity', + limit=REVIEWS_LIMIT, + offset=0, + ) + + latest_reviews, reviews_count = db_review.list_reviews( + entity_id=label['mbid'], + entity_type='label', + sort='published_on', + limit=REVIEWS_LIMIT, + offset=0, + ) + + top_reviews = [db_review.to_dict(review) for review in top_reviews] + latest_reviews = [db_review.to_dict(review) for review in latest_reviews] + + result = { + "label": label, + "average_rating": average_rating, + "ratings_stats": ratings_stats, + "reviews_count": reviews_count, + "top_reviews": top_reviews, + "latest_reviews": latest_reviews + } + if user_id: + result['user_review'] = user_review + + return jsonify(**result) diff --git a/critiquebrainz/ws/place/__init__.py b/critiquebrainz/ws/place/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/critiquebrainz/ws/place/views.py b/critiquebrainz/ws/place/views.py new file mode 100644 index 000000000..cd2a0cf1f --- /dev/null +++ b/critiquebrainz/ws/place/views.py @@ -0,0 +1,372 @@ +from flask import Blueprint, jsonify +import critiquebrainz.db.review as db_review +import critiquebrainz.db.rating_stats as db_rating_stats +from critiquebrainz.frontend.external.musicbrainz_db import place as db_place +from critiquebrainz.decorators import crossdomain +from critiquebrainz.ws.exceptions import NotFound +from critiquebrainz.ws.parser import Parser +from critiquebrainz.ws import REVIEWS_LIMIT + +place_bp = Blueprint('ws_place', __name__) + + +@place_bp.route('/', methods=['GET', 'OPTIONS']) +@crossdomain(headers="Authorization, Content-Type") +def place_entity_handler(place_mbid): + """Get list of reviews. + + **Request Example:** + + .. code-block:: bash + + $ curl "https://critiquebrainz.org/ws/1/place/8da8cd3d-6162-4714-ad96-6d7c276f4f8a" \\ + -X GET + + **Response Example:** + + .. code-block:: json + + { + "average_rating": 0, + "latest_reviews": [ + { + "created": "Sun, 13 Mar 2016 12:16:52 GMT", + "edits": 0, + "entity_id": "8da8cd3d-6162-4714-ad96-6d7c276f4f8a", + "entity_type": "place", + "full_name": "Creative Commons Attribution-ShareAlike 3.0 Unported", + "id": "dd4b5498-ee04-4663-80f1-22eb5601c68b", + "info_url": "https://creativecommons.org/licenses/by-sa/3.0/", + "is_draft": false, + "is_hidden": false, + "language": "en", + "last_revision": { + "id": 9796, + "rating": null, + "review_id": "dd4b5498-ee04-4663-80f1-22eb5601c68b", + "text": "The Elbphilharmonie was hyped a lot but so far has failed lived up to everyone's expectations.\r\nThe delay of seven years only proves that large prestige projects in Germany do not tend to go well while costs skyrocket.\r\n\r\nI am though intrigued by the acoustics of the main concert hall, which will hopefully be as good as advertised.", + "timestamp": "Sun, 13 Mar 2016 12:17:09 GMT" + }, + "last_updated": "Sun, 13 Mar 2016 12:17:09 GMT", + "license_id": "CC BY-SA 3.0", + "popularity": -3, + "published_on": "Sun, 13 Mar 2016 12:17:09 GMT", + "rating": null, + "source": null, + "source_url": null, + "text": "The Elbphilharmonie was hyped a lot but so far has failed lived up to everyone's expectations.\r\nThe delay of seven years only proves that large prestige projects in Germany do not tend to go well while costs skyrocket.\r\n\r\nI am though intrigued by the acoustics of the main concert hall, which will hopefully be as good as advertised.", + "user": { + "created": "Thu, 19 Feb 2015 15:05:21 GMT", + "display_name": "Leo Verto", + "id": "f0aeeddc-0682-42bb-a340-b5773ca6f2c8", + "karma": -3, + "user_type": "Noob" + }, + "votes_negative_count": 3, + "votes_positive_count": 0 + } + ], + "place": { + "address": "Am Kaiserkai 1, 20457 Hamburg", + "area": { + "mbid": "11a44e18-a2e5-43a9-bee9-aa4f7c83f967", + "name": "Hamburg" + }, + "artist-rels": [ + { + "artist": { + "life-span": { + "begin": "1976-11-30" + }, + "mbid": "7d98417c-60b0-4567-b807-a250c25261ef", + "name": "Iveta Apkalna", + "sort_name": "Apkalna, Iveta", + "type": "Person" + }, + "begin-year": 2017, + "direction": "backward", + "end-year": null, + "type": "organist", + "type-id": "cad0dbab-c711-442a-a91c-05359f0228ce" + }, + { + "artist": { + "comment": "1945\u20132016: NDR Sinfonieorchester", + "life-span": { + "begin": "1945" + }, + "mbid": "2688f91d-dfdd-4d67-94df-0f1a02140c62", + "name": "NDR Elbphilharmonie Orchester", + "sort_name": "NDR Elbphilharmonie Orchester", + "type": "Orchestra" + }, + "begin-year": 2017, + "direction": "backward", + "end-year": null, + "type": "primary concert venue", + "type-id": "fff4640a-0819-49e9-92c5-1e3b5134fd95" + } + ], + "comment": "concert hall in Hamburg", + "coordinates": { + "latitude": 53.541238, + "longitude": 9.984255 + }, + "external-urls": [ + { + "begin-year": null, + "direction": "forward", + "end-year": null, + "icon": "discogs-16.png", + "name": "Discogs", + "type": "discogs", + "type-id": "1c140ac8-8dc2-449e-92cb-52c90d525640", + "url": { + "mbid": "96b51b85-0780-488a-bc71-902fb473595d", + "url": "https://www.discogs.com/label/1153802" + } + }, + { + "begin-year": null, + "direction": "forward", + "end-year": null, + "icon": "home-16.png", + "name": "Official homepage", + "type": "official homepage", + "type-id": "696b79da-7e45-40e6-a9d4-b31438eb7e5d", + "url": { + "mbid": "551b47c5-0b3b-49d2-aa5a-e3a5fc76c0c9", + "url": "http://www.elbphilharmonie.de/" + } + }, + { + "begin-year": null, + "direction": "forward", + "end-year": null, + "icon": "wikidata-16.png", + "name": "Wikidata", + "type": "wikidata", + "type-id": "e6826618-b410-4b8d-b3b5-52e29eac5e1f", + "url": { + "mbid": "7add6c0d-c644-4565-bb4e-09ad6f147568", + "url": "https://www.wikidata.org/wiki/Q673223" + } + } + ], + "life-span": { + "begin": "2017-01-11" + }, + "mbid": "8da8cd3d-6162-4714-ad96-6d7c276f4f8a", + "name": "Elbphilharmonie", + "parts": [ + { + "begin-year": null, + "direction": "forward", + "end-year": null, + "place": { + "address": "Am Kaiserkai 1, 20457 Hamburg", + "area": { + "mbid": "11a44e18-a2e5-43a9-bee9-aa4f7c83f967", + "name": "Hamburg" + }, + "coordinates": { + "latitude": 53.541238, + "longitude": 9.984255 + }, + "mbid": "b3aa8167-f92f-48d6-a3bf-e20c8e20c6b5", + "name": "Elbphilharmonie: Kaistudio 1" + }, + "type": "parts", + "type-id": "ff683f48-eff1-40ab-a58f-b128098ffe92" + }, + { + "begin-year": null, + "direction": "forward", + "end-year": null, + "place": { + "address": "Platz der Deutschen Einheit 1, 20457 Hamburg", + "area": { + "mbid": "11a44e18-a2e5-43a9-bee9-aa4f7c83f967", + "name": "Hamburg" + }, + "coordinates": { + "latitude": 53.54133, + "longitude": 9.98447 + }, + "mbid": "9fc8d3da-e7f1-404c-a615-e9794d57ddb6", + "name": "Elbphilharmonie: Gro\u00dfer Saal", + "type": "Venue" + }, + "type": "parts", + "type-id": "ff683f48-eff1-40ab-a58f-b128098ffe92" + }, + { + "begin-year": null, + "direction": "forward", + "end-year": null, + "place": { + "address": "", + "area": { + "mbid": "11a44e18-a2e5-43a9-bee9-aa4f7c83f967", + "name": "Hamburg" + }, + "mbid": "e810f624-7423-4efa-9cb4-de2e081cc755", + "name": "Elbphilharmonie: Kleiner Saal", + "type": "Venue" + }, + "type": "parts", + "type-id": "ff683f48-eff1-40ab-a58f-b128098ffe92" + } + ], + "type": "Venue", + "url-rels": [ + { + "begin-year": null, + "direction": "forward", + "end-year": null, + "type": "official homepage", + "type-id": "696b79da-7e45-40e6-a9d4-b31438eb7e5d", + "url": { + "mbid": "551b47c5-0b3b-49d2-aa5a-e3a5fc76c0c9", + "url": "http://www.elbphilharmonie.de/" + } + }, + { + "begin-year": null, + "direction": "forward", + "end-year": null, + "type": "discogs", + "type-id": "1c140ac8-8dc2-449e-92cb-52c90d525640", + "url": { + "mbid": "96b51b85-0780-488a-bc71-902fb473595d", + "url": "https://www.discogs.com/label/1153802" + } + }, + { + "begin-year": null, + "direction": "forward", + "end-year": null, + "type": "songkick", + "type-id": "3eb58d3e-6f00-36a8-a115-3dad616b7391", + "url": { + "mbid": "97b4b49f-aa9c-4809-ae63-6184896655df", + "url": "https://www.songkick.com/venues/914796" + } + }, + { + "begin-year": null, + "direction": "forward", + "end-year": null, + "type": "image", + "type-id": "68a4537c-f2a6-49b8-81c5-82a62b0976b7", + "url": { + "mbid": "cfdaf6e4-7e68-4aa1-9053-211afaf4bbd3", + "url": "https://commons.wikimedia.org/wiki/File:Elbphilharmonie,_Hamburg.jpg" + } + } + ] + }, + "ratings_stats": { + "1": 0, + "2": 0, + "3": 0, + "4": 0, + "5": 0 + }, + "reviews_count": 1, + "top_reviews": [ + { + "created": "Sun, 13 Mar 2016 12:16:52 GMT", + "edits": 0, + "entity_id": "8da8cd3d-6162-4714-ad96-6d7c276f4f8a", + "entity_type": "place", + "full_name": "Creative Commons Attribution-ShareAlike 3.0 Unported", + "id": "dd4b5498-ee04-4663-80f1-22eb5601c68b", + "info_url": "https://creativecommons.org/licenses/by-sa/3.0/", + "is_draft": false, + "is_hidden": false, + "language": "en", + "last_revision": { + "id": 9796, + "rating": null, + "review_id": "dd4b5498-ee04-4663-80f1-22eb5601c68b", + "text": "The Elbphilharmonie was hyped a lot but so far has failed lived up to everyone's expectations.\r\nThe delay of seven years only proves that large prestige projects in Germany do not tend to go well while costs skyrocket.\r\n\r\nI am though intrigued by the acoustics of the main concert hall, which will hopefully be as good as advertised.", + "timestamp": "Sun, 13 Mar 2016 12:17:09 GMT" + }, + "last_updated": "Sun, 13 Mar 2016 12:17:09 GMT", + "license_id": "CC BY-SA 3.0", + "popularity": -3, + "published_on": "Sun, 13 Mar 2016 12:17:09 GMT", + "rating": null, + "source": null, + "source_url": null, + "text": "The Elbphilharmonie was hyped a lot but so far has failed lived up to everyone's expectations.\r\nThe delay of seven years only proves that large prestige projects in Germany do not tend to go well while costs skyrocket.\r\n\r\nI am though intrigued by the acoustics of the main concert hall, which will hopefully be as good as advertised.", + "user": { + "created": "Thu, 19 Feb 2015 15:05:21 GMT", + "display_name": "Leo Verto", + "id": "f0aeeddc-0682-42bb-a340-b5773ca6f2c8", + "karma": -3, + "user_type": "Noob" + }, + "votes_negative_count": 3, + "votes_positive_count": 0 + } + ] + } + + :statuscode 200: no error + :statuscode 404: place not found + + :resheader Content-Type: *application/json* + """ + + place = db_place.get_place_by_mbid(str(place_mbid)) + + if not place: + raise NotFound("Can't find a place with ID: {place_mbid}".format(place_mbid=place_mbid)) + + user_id = Parser.uuid('uri', 'user_id', optional=True) + if user_id: + user_review, _ = db_review.list_reviews( + entity_id=place['mbid'], + entity_type='place', + user_id=user_id + ) + if user_review: + user_review = db_review.to_dict(user_review[0]) + else: + user_review = None + + ratings_stats, average_rating = db_rating_stats.get_stats(place_mbid, "place") + + top_reviews, reviews_count = db_review.list_reviews( + entity_id=place['mbid'], + entity_type='place', + sort='popularity', + limit=REVIEWS_LIMIT, + offset=0, + ) + + latest_reviews, reviews_count = db_review.list_reviews( + entity_id=place['mbid'], + entity_type='place', + sort='published_on', + limit=REVIEWS_LIMIT, + offset=0, + ) + + top_reviews = [db_review.to_dict(review) for review in top_reviews] + latest_reviews = [db_review.to_dict(review) for review in latest_reviews] + + result = { + "place": place, + "average_rating": average_rating, + "ratings_stats": ratings_stats, + "reviews_count": reviews_count, + "top_reviews": top_reviews, + "latest_reviews": latest_reviews + } + if user_id: + result['user_review'] = user_review + + return jsonify(**result) diff --git a/critiquebrainz/ws/recording/__init__.py b/critiquebrainz/ws/recording/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/critiquebrainz/ws/recording/views.py b/critiquebrainz/ws/recording/views.py new file mode 100644 index 000000000..28faa6d81 --- /dev/null +++ b/critiquebrainz/ws/recording/views.py @@ -0,0 +1,186 @@ +from flask import Blueprint, jsonify +import critiquebrainz.db.review as db_review +import critiquebrainz.db.rating_stats as db_rating_stats +from critiquebrainz.frontend.external.musicbrainz_db import recording as db_recording +from critiquebrainz.decorators import crossdomain +from critiquebrainz.ws.exceptions import NotFound +from critiquebrainz.ws.parser import Parser +from critiquebrainz.ws import REVIEWS_LIMIT + +recording_bp = Blueprint('ws_recording', __name__) + +@recording_bp.route('/', methods=['GET', 'OPTIONS']) +@crossdomain(headers="Authorization, Content-Type") +def recording_entity_handler(recording_mbid): + """Get list of reviews. + + **Request Example:** + + .. code-block:: bash + + $ curl "https://critiquebrainz.org/ws/1/recording/299e8f6d-957d-43fd-a724-ebfd4c570bbc" \\ + -X GET + + **Response Example:** + + .. code-block:: json + + { + "average_rating": 5.0, + "latest_reviews": [ + { + "created": "Sat, 04 Dec 2021 13:17:15 GMT", + "edits": 0, + "entity_id": "299e8f6d-957d-43fd-a724-ebfd4c570bbc", + "entity_type": "recording", + "full_name": "Creative Commons Attribution-ShareAlike 3.0 Unported", + "id": "5f98882a-86c0-41b6-b828-59c3886adf6b", + "info_url": "https://creativecommons.org/licenses/by-sa/3.0/", + "is_draft": false, + "is_hidden": false, + "language": "en", + "last_revision": { + "id": 11177, + "rating": 5, + "review_id": "5f98882a-86c0-41b6-b828-59c3886adf6b", + "text": null, + "timestamp": "Sat, 04 Dec 2021 13:17:15 GMT" + }, + "last_updated": "Sat, 04 Dec 2021 13:17:15 GMT", + "license_id": "CC BY-SA 3.0", + "popularity": 0, + "published_on": "Sat, 04 Dec 2021 13:17:15 GMT", + "rating": 5, + "source": null, + "source_url": null, + "text": null, + "user": { + "created": "Wed, 26 May 2021 13:20:30 GMT", + "display_name": "akshaaatt", + "id": "7a98bc0a-1e40-4cd6-b36d-8c25931533af", + "karma": 1, + "user_type": "Noob" + }, + "votes_negative_count": 0, + "votes_positive_count": 0 + } + ], + "ratings_stats": { + "1": 0, + "2": 0, + "3": 0, + "4": 0, + "5": 1 + }, + "recording": { + "artist-credit-phrase": "Linkin Park", + "artists": [ + { + "mbid": "f59c5520-5f46-4d2c-b2c4-822eabf53419", + "name": "Linkin Park" + } + ], + "comment": "live, 2012-06-05: Admiralspalast, Berlin, Germany", + "length": 301.0, + "mbid": "299e8f6d-957d-43fd-a724-ebfd4c570bbc", + "name": "New Divide", + "rating": 100, + "video": true + }, + "reviews_count": 1, + "top_reviews": [ + { + "created": "Sat, 04 Dec 2021 13:17:15 GMT", + "edits": 0, + "entity_id": "299e8f6d-957d-43fd-a724-ebfd4c570bbc", + "entity_type": "recording", + "full_name": "Creative Commons Attribution-ShareAlike 3.0 Unported", + "id": "5f98882a-86c0-41b6-b828-59c3886adf6b", + "info_url": "https://creativecommons.org/licenses/by-sa/3.0/", + "is_draft": false, + "is_hidden": false, + "language": "en", + "last_revision": { + "id": 11177, + "rating": 5, + "review_id": "5f98882a-86c0-41b6-b828-59c3886adf6b", + "text": null, + "timestamp": "Sat, 04 Dec 2021 13:17:15 GMT" + }, + "last_updated": "Sat, 04 Dec 2021 13:17:15 GMT", + "license_id": "CC BY-SA 3.0", + "popularity": 0, + "published_on": "Sat, 04 Dec 2021 13:17:15 GMT", + "rating": 5, + "source": null, + "source_url": null, + "text": null, + "user": { + "created": "Wed, 26 May 2021 13:20:30 GMT", + "display_name": "akshaaatt", + "id": "7a98bc0a-1e40-4cd6-b36d-8c25931533af", + "karma": 1, + "user_type": "Noob" + }, + "votes_negative_count": 0, + "votes_positive_count": 0 + } + ] + } + + :statuscode 200: no error + :statuscode 404: recording not found + + :resheader Content-Type: *application/json* + """ + + recording = db_recording.get_recording_by_mbid(str(recording_mbid)) + + if not recording: + raise NotFound("Can't find a recording with ID: {recording_mbid}".format(recording_mbid=recording_mbid)) + + user_id = Parser.uuid('uri', 'user_id', optional=True) + if user_id: + user_review, _ = db_review.list_reviews( + entity_id=recording['mbid'], + entity_type='recording', + user_id=user_id + ) + if user_review: + user_review = db_review.to_dict(user_review[0]) + else: + user_review = None + + ratings_stats, average_rating = db_rating_stats.get_stats(recording_mbid, "recording") + + top_reviews, reviews_count = db_review.list_reviews( + entity_id=recording['mbid'], + entity_type='recording', + sort='popularity', + limit=REVIEWS_LIMIT, + offset=0, + ) + + latest_reviews, reviews_count = db_review.list_reviews( + entity_id=recording['mbid'], + entity_type='recording', + sort='published_on', + limit=REVIEWS_LIMIT, + offset=0, + ) + + top_reviews = [db_review.to_dict(review) for review in top_reviews] + latest_reviews = [db_review.to_dict(review) for review in latest_reviews] + + result = { + "recording": recording, + "average_rating": average_rating, + "ratings_stats": ratings_stats, + "reviews_count": reviews_count, + "top_reviews": top_reviews, + "latest_reviews": latest_reviews + } + if user_id: + result['user_review'] = user_review + + return jsonify(**result) diff --git a/critiquebrainz/ws/release_group/views.py b/critiquebrainz/ws/release_group/views.py index 01068140d..3c2485a81 100644 --- a/critiquebrainz/ws/release_group/views.py +++ b/critiquebrainz/ws/release_group/views.py @@ -1,10 +1,12 @@ from flask import Blueprint, jsonify import critiquebrainz.db.review as db_review import critiquebrainz.db.rating_stats as db_rating_stats -from brainzutils.musicbrainz_db import release_group as db_release_group -import brainzutils.musicbrainz_db.exceptions as db_exceptions +from critiquebrainz.frontend.external.musicbrainz_db import release_group as db_release_group from critiquebrainz.decorators import crossdomain from critiquebrainz.ws.exceptions import NotFound +from critiquebrainz.ws.parser import Parser +from critiquebrainz.ws import REVIEWS_LIMIT + release_group_bp = Blueprint('ws_release_group', __name__) @@ -26,7 +28,7 @@ def release_group_entity_handler(release_group_mbid): .. code-block:: json { - "avg_rating": 4.0, + "average_rating": 4.0, "latest_reviews": [ { "created": "Wed, 01 Dec 2021 16:28:50 GMT", @@ -58,14 +60,14 @@ def release_group_entity_handler(release_group_mbid): "created": "Sat, 15 Aug 2020 15:48:39 GMT", "display_name": "sound.and.vision", "id": "dfdae69f-275f-41a2-82c7-ac5d1f9c8129", - "karma": 16, + "karma": 27, "user_type": "Noob" }, "votes_negative_count": 0, "votes_positive_count": 1 } ], - "rating_stats": { + "ratings_stats": { "1": 0, "2": 0, "3": 0, @@ -73,7 +75,28 @@ def release_group_entity_handler(release_group_mbid): "5": 0 }, "release_group": { - "id": "f0bd8ae8-321f-43d8-af87-e2f90d1b3817", + "artist-credit": [ + { + "artist": { + "comment": "add compilations to this artist", + "mbid": "89ad4ac3-39f7-470e-963a-56509c546377", + "name": "Various Artists", + "sort_name": "Various Artists", + "type": "Other" + }, + "name": "Various Artists" + } + ], + "artist-credit-phrase": "Various Artists", + "first-release-year": 2004, + "mbid": "f0bd8ae8-321f-43d8-af87-e2f90d1b3817", + "rating": 60, + "release-list": [ + { + "mbid": "f51d400c-3303-447f-8f3c-595e1b1352bf", + "name": "Hollywood Greats" + } + ], "title": "Hollywood Greats", "type": "Album" }, @@ -109,7 +132,7 @@ def release_group_entity_handler(release_group_mbid): "created": "Sat, 15 Aug 2020 15:48:39 GMT", "display_name": "sound.and.vision", "id": "dfdae69f-275f-41a2-82c7-ac5d1f9c8129", - "karma": 16, + "karma": 27, "user_type": "Noob" }, "votes_negative_count": 0, @@ -117,39 +140,60 @@ def release_group_entity_handler(release_group_mbid): } ] } + :statuscode 200: no error :statuscode 404: release group not found - + :resheader Content-Type: *application/json* """ - - try: - release_group = db_release_group.get_release_group_by_id(str(release_group_mbid)) - except db_exceptions.NoDataFoundException: - raise NotFound("Can't find an release group with ID: {release_group_mbid}".format(release_group_mbid=release_group_mbid)) + release_group = db_release_group.get_release_group_by_mbid(str(release_group_mbid)) - ratings_stats, average_rating = db_rating_stats.get_stats(release_group_mbid, "release_group") + if not release_group: + raise NotFound("Can't find a release group with ID: {release_group_mbid}".format(release_group_mbid=release_group_mbid)) - reviews_limit = 5 + user_id = Parser.uuid('uri', 'user_id', optional=True) + if user_id: + user_review, _ = db_review.list_reviews( + entity_id=release_group['mbid'], + entity_type='release_group', + user_id=user_id + ) + if user_review: + user_review = db_review.to_dict(user_review[0]) + else: + user_review = None + + ratings_stats, average_rating = db_rating_stats.get_stats(release_group_mbid, "release_group") top_reviews, reviews_count = db_review.list_reviews( - entity_id=release_group_mbid, + entity_id=release_group['mbid'], entity_type='release_group', sort='popularity', - limit=reviews_limit, + limit=REVIEWS_LIMIT, offset=0, ) latest_reviews, reviews_count = db_review.list_reviews( - entity_id=release_group_mbid, + entity_id=release_group['mbid'], entity_type='release_group', sort='published_on', - limit=reviews_limit, + limit=REVIEWS_LIMIT, offset=0, ) - top_reviews = [db_review.to_dict(p) for p in top_reviews] - latest_reviews = [db_review.to_dict(p) for p in latest_reviews] + top_reviews = [db_review.to_dict(review) for review in top_reviews] + latest_reviews = [db_review.to_dict(review) for review in latest_reviews] + + result = { + "release_group": release_group, + "average_rating": average_rating, + "ratings_stats": ratings_stats, + "reviews_count": reviews_count, + "top_reviews": top_reviews, + "latest_reviews": latest_reviews + } + if user_id: + result['user_review'] = user_review - return jsonify(release_group=release_group, avg_rating=average_rating, rating_stats=ratings_stats, reviews_count=reviews_count, top_reviews=top_reviews, latest_reviews=latest_reviews) + return jsonify(**result) diff --git a/critiquebrainz/ws/work/__init__.py b/critiquebrainz/ws/work/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/critiquebrainz/ws/work/views.py b/critiquebrainz/ws/work/views.py new file mode 100644 index 000000000..af9784b71 --- /dev/null +++ b/critiquebrainz/ws/work/views.py @@ -0,0 +1,261 @@ +from flask import Blueprint, jsonify +import critiquebrainz.db.review as db_review +import critiquebrainz.db.rating_stats as db_rating_stats +from critiquebrainz.frontend.external.musicbrainz_db import work as db_work +from critiquebrainz.decorators import crossdomain +from critiquebrainz.ws.exceptions import NotFound +from critiquebrainz.ws.parser import Parser +from critiquebrainz.ws import REVIEWS_LIMIT + +work_bp = Blueprint('ws_work', __name__) + + +@work_bp.route('/', methods=['GET', 'OPTIONS']) +@crossdomain(headers="Authorization, Content-Type") +def work_entity_handler(work_mbid): + """Get list of reviews. + + **Request Example:** + + .. code-block:: bash + + $ curl "https://critiquebrainz.org/ws/1/work/233d58f7-aba4-405a-8cd3-4adb344bd333" \\ + -X GET + + **Response Example:** + + .. code-block:: json + + { + "average_rating": 5.0, + "latest_reviews": [ + { + "created": "Thu, 01 Sep 2022 14:06:33 GMT", + "edits": 0, + "entity_id": "233d58f7-aba4-405a-8cd3-4adb344bd333", + "entity_type": "work", + "full_name": "Creative Commons Attribution-ShareAlike 3.0 Unported", + "id": "f8278997-bfd5-4d0e-8b9a-afb575bb1080", + "info_url": "https://creativecommons.org/licenses/by-sa/3.0/", + "is_draft": false, + "is_hidden": false, + "language": "en", + "last_revision": { + "id": 11784, + "rating": 5, + "review_id": "f8278997-bfd5-4d0e-8b9a-afb575bb1080", + "text": null, + "timestamp": "Thu, 01 Sep 2022 14:06:33 GMT" + }, + "last_updated": "Thu, 01 Sep 2022 14:06:33 GMT", + "license_id": "CC BY-SA 3.0", + "popularity": 0, + "published_on": "Thu, 01 Sep 2022 14:06:33 GMT", + "rating": 5, + "source": null, + "source_url": null, + "text": null, + "user": { + "created": "Tue, 18 Jan 2022 09:53:49 GMT", + "display_name": "Ansh Goyal", + "id": "11a1160e-d607-4882-8a82-e2e800f664fe", + "karma": 0, + "user_type": "Noob" + }, + "votes_negative_count": 0, + "votes_positive_count": 0 + } + ], + "ratings_stats": { + "1": 0, + "2": 0, + "3": 0, + "4": 0, + "5": 1 + }, + "reviews_count": 1, + "top_reviews": [ + { + "created": "Thu, 01 Sep 2022 14:06:33 GMT", + "edits": 0, + "entity_id": "233d58f7-aba4-405a-8cd3-4adb344bd333", + "entity_type": "work", + "full_name": "Creative Commons Attribution-ShareAlike 3.0 Unported", + "id": "f8278997-bfd5-4d0e-8b9a-afb575bb1080", + "info_url": "https://creativecommons.org/licenses/by-sa/3.0/", + "is_draft": false, + "is_hidden": false, + "language": "en", + "last_revision": { + "id": 11784, + "rating": 5, + "review_id": "f8278997-bfd5-4d0e-8b9a-afb575bb1080", + "text": null, + "timestamp": "Thu, 01 Sep 2022 14:06:33 GMT" + }, + "last_updated": "Thu, 01 Sep 2022 14:06:33 GMT", + "license_id": "CC BY-SA 3.0", + "popularity": 0, + "published_on": "Thu, 01 Sep 2022 14:06:33 GMT", + "rating": 5, + "source": null, + "source_url": null, + "text": null, + "user": { + "created": "Tue, 18 Jan 2022 09:53:49 GMT", + "display_name": "Ansh Goyal", + "id": "11a1160e-d607-4882-8a82-e2e800f664fe", + "karma": 0, + "user_type": "Noob" + }, + "votes_negative_count": 0, + "votes_positive_count": 0 + } + ], + "work": { + "artist-rels": [ + { + "artist": { + "life-span": { + "begin": "1998" + }, + "mbid": "663f33f4-8e5f-4773-93ea-493a1135de9a", + "name": "Tones and I", + "sort_name": "Tones and I", + "type": "Person" + }, + "begin-year": null, + "direction": "backward", + "end-year": null, + "type": "composer", + "type-id": "d59d99ea-23d4-4a80-b066-edca32ee158f" + }, + { + "artist": { + "life-span": { + "begin": "1998" + }, + "mbid": "663f33f4-8e5f-4773-93ea-493a1135de9a", + "name": "Tones and I", + "sort_name": "Tones and I", + "type": "Person" + }, + "begin-year": null, + "direction": "backward", + "end-year": null, + "type": "lyricist", + "type-id": "3e48faba-ec01-47fd-8e89-30e81161661c" + } + ], + "mbid": "233d58f7-aba4-405a-8cd3-4adb344bd333", + "name": "Dance Monkey", + "recording-rels": [ + { + "begin-year": null, + "direction": "backward", + "end-year": null, + "recording": { + "length": 208.823, + "mbid": "a24895fe-c0a1-41eb-b695-3dbddbe2cb36", + "name": "Dance Monkey" + }, + "type": "performance", + "type-id": "a3005666-a872-32c3-ad06-98af558e99b0" + }, + { + "begin-year": null, + "direction": "backward", + "end-year": null, + "recording": { + "comment": "Nath Jennings bootleg", + "mbid": "fceef8ab-f844-45e8-822a-12d0ed536a4c", + "name": "Dance Monkey" + }, + "type": "performance", + "type-id": "a3005666-a872-32c3-ad06-98af558e99b0" + }, + { + "begin-year": null, + "direction": "backward", + "end-year": null, + "recording": { + "length": 234.0, + "mbid": "0ae5d3b3-2756-40b0-98cd-4a913351a42f", + "name": "Dance Monkey" + }, + "type": "performance", + "type-id": "a3005666-a872-32c3-ad06-98af558e99b0" + }, + { + "begin-year": null, + "direction": "backward", + "end-year": null, + "recording": { + "length": 177.449, + "mbid": "c73f942a-1c06-4034-86a2-d9f2090ada18", + "name": "Dance Monkey" + }, + "type": "performance", + "type-id": "a3005666-a872-32c3-ad06-98af558e99b0" + } + ], + "type": "Song" + } + } + + :statuscode 200: no error + :statuscode 404: work not found + + :resheader Content-Type: *application/json* + """ + + work = db_work.get_work_by_mbid(str(work_mbid)) + + if not work: + raise NotFound("Can't find a work with ID: {work_mbid}".format(work_mbid=work_mbid)) + + user_id = Parser.uuid('uri', 'user_id', optional=True) + if user_id: + user_review, _ = db_review.list_reviews( + entity_id=work['mbid'], + entity_type='work', + user_id=user_id + ) + if user_review: + user_review = db_review.to_dict(user_review[0]) + else: + user_review = None + + ratings_stats, average_rating = db_rating_stats.get_stats(work_mbid, "work") + + top_reviews, reviews_count = db_review.list_reviews( + entity_id=work['mbid'], + entity_type='work', + sort='popularity', + limit=REVIEWS_LIMIT, + offset=0, + ) + + latest_reviews, reviews_count = db_review.list_reviews( + entity_id=work['mbid'], + entity_type='work', + sort='published_on', + limit=REVIEWS_LIMIT, + offset=0, + ) + + top_reviews = [db_review.to_dict(review) for review in top_reviews] + latest_reviews = [db_review.to_dict(review) for review in latest_reviews] + + result = { + "work": work, + "average_rating": average_rating, + "ratings_stats": ratings_stats, + "reviews_count": reviews_count, + "top_reviews": top_reviews, + "latest_reviews": latest_reviews + } + if user_id: + result['user_review'] = user_review + + return jsonify(**result) From bb1995e2045bea5cb6153434fb91073a76574469 Mon Sep 17 00:00:00 2001 From: Ansh Goyal Date: Fri, 2 Sep 2022 10:04:00 +0000 Subject: [PATCH 05/15] feat: Add endpoints for BB entities --- critiquebrainz/ws/__init__.py | 8 + critiquebrainz/ws/bb_author/__init__.py | 0 critiquebrainz/ws/bb_author/views.py | 213 ++++++++++++++++++ .../ws/bb_edition_group/__init__.py | 0 critiquebrainz/ws/bb_edition_group/views.py | 191 ++++++++++++++++ .../ws/bb_literary_work/__init__.py | 0 critiquebrainz/ws/bb_literary_work/views.py | 211 +++++++++++++++++ critiquebrainz/ws/bb_series/__init__.py | 0 critiquebrainz/ws/bb_series/views.py | 183 +++++++++++++++ 9 files changed, 806 insertions(+) create mode 100644 critiquebrainz/ws/bb_author/__init__.py create mode 100644 critiquebrainz/ws/bb_author/views.py create mode 100644 critiquebrainz/ws/bb_edition_group/__init__.py create mode 100644 critiquebrainz/ws/bb_edition_group/views.py create mode 100644 critiquebrainz/ws/bb_literary_work/__init__.py create mode 100644 critiquebrainz/ws/bb_literary_work/views.py create mode 100644 critiquebrainz/ws/bb_series/__init__.py create mode 100644 critiquebrainz/ws/bb_series/views.py diff --git a/critiquebrainz/ws/__init__.py b/critiquebrainz/ws/__init__.py index 3eab53294..1bc0afc49 100644 --- a/critiquebrainz/ws/__init__.py +++ b/critiquebrainz/ws/__init__.py @@ -116,6 +116,10 @@ def _register_blueprints(app): from critiquebrainz.ws.recording.views import recording_bp from critiquebrainz.ws.release_group.views import release_group_bp from critiquebrainz.ws.work.views import work_bp + from critiquebrainz.ws.bb_author.views import author_bp + from critiquebrainz.ws.bb_edition_group.views import edition_group_bp + from critiquebrainz.ws.bb_literary_work.views import literary_work_bp + from critiquebrainz.ws.bb_series.views import series_bp app.register_blueprint(oauth_bp, url_prefix="/oauth") app.register_blueprint(review_bp, url_prefix="/review") app.register_blueprint(user_bp, url_prefix="/user") @@ -127,4 +131,8 @@ def _register_blueprints(app): app.register_blueprint(recording_bp, url_prefix="/recording") app.register_blueprint(release_group_bp, url_prefix="/release-group") app.register_blueprint(work_bp, url_prefix="/work") + app.register_blueprint(author_bp, url_prefix="/author") + app.register_blueprint(edition_group_bp, url_prefix="/edition-group") + app.register_blueprint(literary_work_bp, url_prefix="/literary-work") + app.register_blueprint(series_bp, url_prefix="/series") diff --git a/critiquebrainz/ws/bb_author/__init__.py b/critiquebrainz/ws/bb_author/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/critiquebrainz/ws/bb_author/views.py b/critiquebrainz/ws/bb_author/views.py new file mode 100644 index 000000000..1a9554a7b --- /dev/null +++ b/critiquebrainz/ws/bb_author/views.py @@ -0,0 +1,213 @@ +from flask import Blueprint, jsonify +import critiquebrainz.db.review as db_review +import critiquebrainz.db.rating_stats as db_rating_stats +from critiquebrainz.frontend.external.bookbrainz_db import author as db_author +from critiquebrainz.decorators import crossdomain +from critiquebrainz.ws.exceptions import NotFound +from critiquebrainz.ws.parser import Parser +from critiquebrainz.ws import REVIEWS_LIMIT + +author_bp = Blueprint('ws_author', __name__) + + +@author_bp.route('/', methods=['GET', 'OPTIONS']) +@crossdomain(headers="Authorization, Content-Type") +def author_entity_handler(author_bbid): + """Get list of reviews. + + **Request Example:** + + .. code-block:: bash + + $ curl "https://critiquebrainz.org/ws/1/author/e5c4e68b-bfce-4c77-9ca2-0f0a2d4d09f0" \\ + -X GET + + **Response Example:** + + .. code-block:: json + + { + "author": { + "area_id": null, + "area_info": [], + "author_type": "Person", + "bbid": "e5c4e68b-bfce-4c77-9ca2-0f0a2d4d09f0", + "begin_area_id": 221, + "begin_day": 31, + "begin_month": 7, + "begin_year": 1965, + "disambiguation": null, + "end_area_id": null, + "end_day": null, + "end_month": null, + "end_year": null, + "ended": false, + "gender": "Female", + "identifier_set_id": 7401, + "identifiers": [ + { + "icon": "wikidata-16.png", + "name": "Wikidata ID", + "url": "https://www.wikidata.org/wiki/Q34660", + "value": "Q34660" + }, + { + "icon": "viaf-16.png", + "name": "VIAF", + "url": "https://viaf.org/viaf/116796842", + "value": "116796842" + }, + { + "icon": "wikidata-16.png", + "name": "Wikidata ID", + "url": "https://www.wikidata.org/wiki/Q1190608", + "value": "Q1190608" + } + ], + "name": "J. K. Rowling", + "relationship_set_id": 151715, + "sort_name": "Rowling, J. K." + }, + "average_rating": 5.0, + "latest_reviews": [ + { + "created": "Tue, 16 Aug 2022 11:25:44 GMT", + "edits": 0, + "entity_id": "e5c4e68b-bfce-4c77-9ca2-0f0a2d4d09f0", + "entity_type": "bb_author", + "full_name": "Creative Commons Attribution-ShareAlike 3.0 Unported", + "id": "326e702a-020f-40e7-b369-9142a7af4315", + "info_url": "https://creativecommons.org/licenses/by-sa/3.0/", + "is_draft": false, + "is_hidden": false, + "language": "en", + "last_revision": { + "id": 11785, + "rating": 5, + "review_id": "326e702a-020f-40e7-b369-9142a7af4315", + "text": null, + "timestamp": "Fri, 02 Sep 2022 09:54:13 GMT" + }, + "last_updated": "Fri, 02 Sep 2022 09:54:13 GMT", + "license_id": "CC BY-SA 3.0", + "popularity": 0, + "published_on": "Tue, 16 Aug 2022 11:25:44 GMT", + "rating": 5, + "source": null, + "source_url": null, + "text": null, + "user": { + "created": "Tue, 18 Jan 2022 09:53:49 GMT", + "display_name": "Ansh Goyal", + "id": "11a1160e-d607-4882-8a82-e2e800f664fe", + "karma": 0, + "user_type": "Noob" + }, + "votes_negative_count": 0, + "votes_positive_count": 0 + } + ], + "ratings_stats": { + "1": 0, + "2": 0, + "3": 0, + "4": 0, + "5": 1 + }, + "reviews_count": 1, + "top_reviews": [ + { + "created": "Tue, 16 Aug 2022 11:25:44 GMT", + "edits": 0, + "entity_id": "e5c4e68b-bfce-4c77-9ca2-0f0a2d4d09f0", + "entity_type": "bb_author", + "full_name": "Creative Commons Attribution-ShareAlike 3.0 Unported", + "id": "326e702a-020f-40e7-b369-9142a7af4315", + "info_url": "https://creativecommons.org/licenses/by-sa/3.0/", + "is_draft": false, + "is_hidden": false, + "language": "en", + "last_revision": { + "id": 11785, + "rating": 5, + "review_id": "326e702a-020f-40e7-b369-9142a7af4315", + "text": null, + "timestamp": "Fri, 02 Sep 2022 09:54:13 GMT" + }, + "last_updated": "Fri, 02 Sep 2022 09:54:13 GMT", + "license_id": "CC BY-SA 3.0", + "popularity": 0, + "published_on": "Tue, 16 Aug 2022 11:25:44 GMT", + "rating": 5, + "source": null, + "source_url": null, + "text": null, + "user": { + "created": "Tue, 18 Jan 2022 09:53:49 GMT", + "display_name": "Ansh Goyal", + "id": "11a1160e-d607-4882-8a82-e2e800f664fe", + "karma": 0, + "user_type": "Noob" + }, + "votes_negative_count": 0, + "votes_positive_count": 0 + } + ] + } + + :statuscode 200: no error + :statuscode 404: author not found + + :resheader Content-Type: *application/json* + """ + + author = db_author.get_author_by_bbid(str(author_bbid)) + + if not author: + raise NotFound("Can't find an author with ID: {author_bbid}".format(author_bbid=author_bbid)) + + user_id = Parser.uuid('uri', 'user_id', optional=True) + if user_id: + user_review, _ = db_review.list_reviews( + entity_id=author['bbid'], + entity_type='bb_author', + user_id=user_id + ) + if user_review: + user_review = db_review.to_dict(user_review[0]) + else: + user_review = None + + ratings_stats, average_rating = db_rating_stats.get_stats(author_bbid, "bb_author") + + top_reviews, reviews_count = db_review.list_reviews( + entity_id=author['bbid'], + entity_type='bb_author', + sort='popularity', + limit=REVIEWS_LIMIT, + offset=0, + ) + + latest_reviews, reviews_count = db_review.list_reviews( + entity_id=author['bbid'], + entity_type='bb_author', + sort='published_on', + limit=REVIEWS_LIMIT, + offset=0, + ) + + top_reviews = [db_review.to_dict(review) for review in top_reviews] + latest_reviews = [db_review.to_dict(review) for review in latest_reviews] + + result = { + "author": author, + "average_rating": average_rating, + "ratings_stats": ratings_stats, + "reviews_count": reviews_count, + "top_reviews": top_reviews, + "latest_reviews": latest_reviews + } + if user_id: + result['user_review'] = user_review + + return jsonify(**result) diff --git a/critiquebrainz/ws/bb_edition_group/__init__.py b/critiquebrainz/ws/bb_edition_group/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/critiquebrainz/ws/bb_edition_group/views.py b/critiquebrainz/ws/bb_edition_group/views.py new file mode 100644 index 000000000..54edee1d9 --- /dev/null +++ b/critiquebrainz/ws/bb_edition_group/views.py @@ -0,0 +1,191 @@ +from flask import Blueprint, jsonify +import critiquebrainz.db.review as db_review +import critiquebrainz.db.rating_stats as db_rating_stats +from critiquebrainz.frontend.external.bookbrainz_db import edition_group as db_edition_group +from critiquebrainz.decorators import crossdomain +from critiquebrainz.ws.exceptions import NotFound +from critiquebrainz.ws.parser import Parser +from critiquebrainz.ws import REVIEWS_LIMIT + +edition_group_bp = Blueprint('ws_edition_group', __name__) + + +@edition_group_bp.route('/', methods=['GET', 'OPTIONS']) +@crossdomain(headers="Authorization, Content-Type") +def edition_group_entity_handler(edition_group_bbid): + """Get list of reviews. + + **Request Example:** + + .. code-block:: bash + + $ curl "https://critiquebrainz.org/ws/1/edition-group/b2a5f76e-91c6-46bb-a618-3ee5ebc6dd6c" \\ + -X GET + + **Response Example:** + + .. code-block:: json + + { + "average_rating": 5.0, + "edition_group": { + "author_credits": [], + "bbid": "b2a5f76e-91c6-46bb-a618-3ee5ebc6dd6c", + "disambiguation": null, + "edition_group_type": "Book", + "identifier_set_id": 1272, + "identifiers": [ + { + "icon": "wikidata-16.png", + "name": "Wikidata ID", + "url": "https://www.wikidata.org/wiki/Q47209", + "value": "Q47209" + } + ], + "name": "Harry Potter and the Chamber of Secrets", + "relationship_set_id": null, + "rels": null, + "sort_name": "Chamber of Secrets, Harry Potter and the" + }, + "latest_reviews": [ + { + "created": "Thu, 28 Jul 2022 07:03:41 GMT", + "edits": 0, + "entity_id": "b2a5f76e-91c6-46bb-a618-3ee5ebc6dd6c", + "entity_type": "bb_edition_group", + "full_name": "Creative Commons Attribution-ShareAlike 3.0 Unported", + "id": "84b01e18-50ac-484e-9e48-41d05250db76", + "info_url": "https://creativecommons.org/licenses/by-sa/3.0/", + "is_draft": false, + "is_hidden": false, + "language": "en", + "last_revision": { + "id": 11786, + "rating": 5, + "review_id": "84b01e18-50ac-484e-9e48-41d05250db76", + "text": null, + "timestamp": "Fri, 02 Sep 2022 09:58:44 GMT" + }, + "last_updated": "Fri, 02 Sep 2022 09:58:44 GMT", + "license_id": "CC BY-SA 3.0", + "popularity": 0, + "published_on": "Thu, 28 Jul 2022 07:03:41 GMT", + "rating": 5, + "source": null, + "source_url": null, + "text": null, + "user": { + "created": "Tue, 18 Jan 2022 09:53:49 GMT", + "display_name": "Ansh Goyal", + "id": "11a1160e-d607-4882-8a82-e2e800f664fe", + "karma": 0, + "user_type": "Noob" + }, + "votes_negative_count": 0, + "votes_positive_count": 0 + } + ], + "ratings_stats": { + "1": 0, + "2": 0, + "3": 0, + "4": 0, + "5": 1 + }, + "reviews_count": 1, + "top_reviews": [ + { + "created": "Thu, 28 Jul 2022 07:03:41 GMT", + "edits": 0, + "entity_id": "b2a5f76e-91c6-46bb-a618-3ee5ebc6dd6c", + "entity_type": "bb_edition_group", + "full_name": "Creative Commons Attribution-ShareAlike 3.0 Unported", + "id": "84b01e18-50ac-484e-9e48-41d05250db76", + "info_url": "https://creativecommons.org/licenses/by-sa/3.0/", + "is_draft": false, + "is_hidden": false, + "language": "en", + "last_revision": { + "id": 11786, + "rating": 5, + "review_id": "84b01e18-50ac-484e-9e48-41d05250db76", + "text": null, + "timestamp": "Fri, 02 Sep 2022 09:58:44 GMT" + }, + "last_updated": "Fri, 02 Sep 2022 09:58:44 GMT", + "license_id": "CC BY-SA 3.0", + "popularity": 0, + "published_on": "Thu, 28 Jul 2022 07:03:41 GMT", + "rating": 5, + "source": null, + "source_url": null, + "text": null, + "user": { + "created": "Tue, 18 Jan 2022 09:53:49 GMT", + "display_name": "Ansh Goyal", + "id": "11a1160e-d607-4882-8a82-e2e800f664fe", + "karma": 0, + "user_type": "Noob" + }, + "votes_negative_count": 0, + "votes_positive_count": 0 + } + ] + } + + :statuscode 200: no error + :statuscode 404: edition group not found + + :resheader Content-Type: *application/json* + """ + + edition_group = db_edition_group.get_edition_group_by_bbid(str(edition_group_bbid)) + + if not edition_group: + raise NotFound("Can't find an edition_group with ID: {edition_group_bbid}".format(edition_group_bbid=edition_group_bbid)) + + user_id = Parser.uuid('uri', 'user_id', optional=True) + if user_id: + user_review, _ = db_review.list_reviews( + entity_id=edition_group['bbid'], + entity_type='bb_edition_group', + user_id=user_id + ) + if user_review: + user_review = db_review.to_dict(user_review[0]) + else: + user_review = None + + ratings_stats, average_rating = db_rating_stats.get_stats(edition_group_bbid, "bb_edition_group") + + top_reviews, reviews_count = db_review.list_reviews( + entity_id=edition_group['bbid'], + entity_type='bb_edition_group', + sort='popularity', + limit=REVIEWS_LIMIT, + offset=0, + ) + + latest_reviews, reviews_count = db_review.list_reviews( + entity_id=edition_group['bbid'], + entity_type='bb_edition_group', + sort='published_on', + limit=REVIEWS_LIMIT, + offset=0, + ) + + top_reviews = [db_review.to_dict(review) for review in top_reviews] + latest_reviews = [db_review.to_dict(review) for review in latest_reviews] + + result = { + "edition_group": edition_group, + "average_rating": average_rating, + "ratings_stats": ratings_stats, + "reviews_count": reviews_count, + "top_reviews": top_reviews, + "latest_reviews": latest_reviews + } + if user_id: + result['user_review'] = user_review + + return jsonify(**result) diff --git a/critiquebrainz/ws/bb_literary_work/__init__.py b/critiquebrainz/ws/bb_literary_work/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/critiquebrainz/ws/bb_literary_work/views.py b/critiquebrainz/ws/bb_literary_work/views.py new file mode 100644 index 000000000..1c1d006d3 --- /dev/null +++ b/critiquebrainz/ws/bb_literary_work/views.py @@ -0,0 +1,211 @@ +from flask import Blueprint, jsonify +import critiquebrainz.db.review as db_review +import critiquebrainz.db.rating_stats as db_rating_stats +from critiquebrainz.frontend.external.bookbrainz_db import literary_work as db_literary_work +from critiquebrainz.decorators import crossdomain +from critiquebrainz.ws.exceptions import NotFound +from critiquebrainz.ws.parser import Parser +from critiquebrainz.ws import REVIEWS_LIMIT + +literary_work_bp = Blueprint('ws_literary_work', __name__) + + +@literary_work_bp.route('/', methods=['GET', 'OPTIONS']) +@crossdomain(headers="Authorization, Content-Type") +def literary_work_entity_handler(literary_work_bbid): + """Get list of reviews. + + **Request Example:** + + .. code-block:: bash + + $ curl "https://critiquebrainz.org/ws/1/literary-work/b2d19b45-117d-437d-b55b-7fff01e29603" \\ + -X GET + + **Response Example:** + + .. code-block:: json + + { + "average_rating": 5.0, + "latest_reviews": [ + { + "created": "Thu, 18 Aug 2022 09:21:37 GMT", + "edits": 0, + "entity_id": "b2d19b45-117d-437d-b55b-7fff01e29603", + "entity_type": "bb_literary_work", + "full_name": "Creative Commons Attribution-ShareAlike 3.0 Unported", + "id": "178b5ef1-ec96-40b8-a08c-b11b01550efd", + "info_url": "https://creativecommons.org/licenses/by-sa/3.0/", + "is_draft": false, + "is_hidden": false, + "language": "en", + "last_revision": { + "id": 11775, + "rating": 5, + "review_id": "178b5ef1-ec96-40b8-a08c-b11b01550efd", + "text": null, + "timestamp": "Thu, 18 Aug 2022 09:21:37 GMT" + }, + "last_updated": "Thu, 18 Aug 2022 09:21:37 GMT", + "license_id": "CC BY-SA 3.0", + "popularity": 0, + "published_on": "Thu, 18 Aug 2022 09:21:37 GMT", + "rating": 5, + "source": null, + "source_url": null, + "text": null, + "user": { + "created": "Tue, 18 Jan 2022 09:53:49 GMT", + "display_name": "Ansh Goyal", + "id": "11a1160e-d607-4882-8a82-e2e800f664fe", + "karma": 0, + "user_type": "Noob" + }, + "votes_negative_count": 0, + "votes_positive_count": 0 + } + ], + "literary_work": { + "bbid": "b2d19b45-117d-437d-b55b-7fff01e29603", + "disambiguation": null, + "identifier_set_id": 2831, + "identifiers": [ + { + "icon": "wikidata-16.png", + "name": "Wikidata ID", + "url": "https://www.wikidata.org/wiki/Q47209", + "value": "Q47209" + }, + { + "icon": "viaf-16.png", + "name": "VIAF", + "url": "https://viaf.org/viaf/190455963", + "value": "190455963" + }, + { + "icon": null, + "name": "OpenLibrary Work ID", + "url": "https://openlibrary.org/works/OL16313124W", + "value": "OL16313124W" + }, + { + "icon": null, + "name": "OpenLibrary Work ID", + "url": "https://openlibrary.org/works/OL16313123W", + "value": "OL16313123W" + } + ], + "languages": [ + "English" + ], + "name": "Harry Potter and the Chamber of Secrets", + "relationship_set_id": 136021, + "rels": null, + "sort_name": "Chamber of Secrets, Harry Potter and the", + "work_type": "Novel" + }, + "ratings_stats": { + "1": 0, + "2": 0, + "3": 0, + "4": 0, + "5": 1 + }, + "reviews_count": 1, + "top_reviews": [ + { + "created": "Thu, 18 Aug 2022 09:21:37 GMT", + "edits": 0, + "entity_id": "b2d19b45-117d-437d-b55b-7fff01e29603", + "entity_type": "bb_literary_work", + "full_name": "Creative Commons Attribution-ShareAlike 3.0 Unported", + "id": "178b5ef1-ec96-40b8-a08c-b11b01550efd", + "info_url": "https://creativecommons.org/licenses/by-sa/3.0/", + "is_draft": false, + "is_hidden": false, + "language": "en", + "last_revision": { + "id": 11775, + "rating": 5, + "review_id": "178b5ef1-ec96-40b8-a08c-b11b01550efd", + "text": null, + "timestamp": "Thu, 18 Aug 2022 09:21:37 GMT" + }, + "last_updated": "Thu, 18 Aug 2022 09:21:37 GMT", + "license_id": "CC BY-SA 3.0", + "popularity": 0, + "published_on": "Thu, 18 Aug 2022 09:21:37 GMT", + "rating": 5, + "source": null, + "source_url": null, + "text": null, + "user": { + "created": "Tue, 18 Jan 2022 09:53:49 GMT", + "display_name": "Ansh Goyal", + "id": "11a1160e-d607-4882-8a82-e2e800f664fe", + "karma": 0, + "user_type": "Noob" + }, + "votes_negative_count": 0, + "votes_positive_count": 0 + } + ] + } + + :statuscode 200: no error + :statuscode 404: literary work not found + + :resheader Content-Type: *application/json* + """ + + literary_work = db_literary_work.get_literary_work_by_bbid(str(literary_work_bbid)) + + if not literary_work: + raise NotFound("Can't find a literary_work with ID: {literary_work_bbid}".format(literary_work_bbid=literary_work_bbid)) + + user_id = Parser.uuid('uri', 'user_id', optional=True) + if user_id: + user_review, _ = db_review.list_reviews( + entity_id=literary_work['bbid'], + entity_type='bb_literary_work', + user_id=user_id + ) + if user_review: + user_review = db_review.to_dict(user_review[0]) + else: + user_review = None + + ratings_stats, average_rating = db_rating_stats.get_stats(literary_work_bbid, "bb_literary_work") + + top_reviews, reviews_count = db_review.list_reviews( + entity_id=literary_work['bbid'], + entity_type='bb_literary_work', + sort='popularity', + limit=REVIEWS_LIMIT, + offset=0, + ) + + latest_reviews, reviews_count = db_review.list_reviews( + entity_id=literary_work['bbid'], + entity_type='bb_literary_work', + sort='published_on', + limit=REVIEWS_LIMIT, + offset=0, + ) + + top_reviews = [db_review.to_dict(review) for review in top_reviews] + latest_reviews = [db_review.to_dict(review) for review in latest_reviews] + + result = { + "literary_work": literary_work, + "average_rating": average_rating, + "ratings_stats": ratings_stats, + "reviews_count": reviews_count, + "top_reviews": top_reviews, + "latest_reviews": latest_reviews + } + if user_id: + result['user_review'] = user_review + + return jsonify(**result) diff --git a/critiquebrainz/ws/bb_series/__init__.py b/critiquebrainz/ws/bb_series/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/critiquebrainz/ws/bb_series/views.py b/critiquebrainz/ws/bb_series/views.py new file mode 100644 index 000000000..da24edea9 --- /dev/null +++ b/critiquebrainz/ws/bb_series/views.py @@ -0,0 +1,183 @@ +from flask import Blueprint, jsonify +import critiquebrainz.db.review as db_review +import critiquebrainz.db.rating_stats as db_rating_stats +from critiquebrainz.frontend.external.bookbrainz_db import series as db_series +from critiquebrainz.decorators import crossdomain +from critiquebrainz.ws.exceptions import NotFound +from critiquebrainz.ws.parser import Parser +from critiquebrainz.ws import REVIEWS_LIMIT + +series_bp = Blueprint('ws_series', __name__) + + +@series_bp.route('/', methods=['GET', 'OPTIONS']) +@crossdomain(headers="Authorization, Content-Type") +def series_entity_handler(series_bbid): + """Get list of reviews. + + **Request Example:** + + .. code-block:: bash + + $ curl "https://critiquebrainz.org/ws/1/series/e6f48cbd-26de-4c2e-a24a-29892f9eb3be" \\ + -X GET + + **Response Example:** + + .. code-block:: json + + { + "average_rating": 4.0, + "latest_reviews": [ + { + "created": "Tue, 16 Aug 2022 11:26:58 GMT", + "edits": 0, + "entity_id": "e6f48cbd-26de-4c2e-a24a-29892f9eb3be", + "entity_type": "bb_series", + "full_name": "Creative Commons Attribution-ShareAlike 3.0 Unported", + "id": "998512d8-0d6b-4c76-bfb7-0ec666e3fa0a", + "info_url": "https://creativecommons.org/licenses/by-sa/3.0/", + "is_draft": false, + "is_hidden": false, + "language": "en", + "last_revision": { + "id": 11773, + "rating": 4, + "review_id": "998512d8-0d6b-4c76-bfb7-0ec666e3fa0a", + "text": null, + "timestamp": "Tue, 16 Aug 2022 11:26:58 GMT" + }, + "last_updated": "Tue, 16 Aug 2022 11:26:58 GMT", + "license_id": "CC BY-SA 3.0", + "popularity": 0, + "published_on": "Tue, 16 Aug 2022 11:26:58 GMT", + "rating": 4, + "source": null, + "source_url": null, + "text": null, + "user": { + "created": "Tue, 18 Jan 2022 09:53:49 GMT", + "display_name": "Ansh Goyal", + "id": "11a1160e-d607-4882-8a82-e2e800f664fe", + "karma": 0, + "user_type": "Noob" + }, + "votes_negative_count": 0, + "votes_positive_count": 0 + } + ], + "ratings_stats": { + "1": 0, + "2": 0, + "3": 0, + "4": 1, + "5": 0 + }, + "reviews_count": 1, + "series": { + "bbid": "e6f48cbd-26de-4c2e-a24a-29892f9eb3be", + "disambiguation": "English", + "identifier_set_id": null, + "identifiers": null, + "name": "Harry Potter", + "relationship_set_id": 151767, + "series_ordering_type": "Automatic", + "series_type": "Work", + "sort_name": "Harry Potter" + }, + "top_reviews": [ + { + "created": "Tue, 16 Aug 2022 11:26:58 GMT", + "edits": 0, + "entity_id": "e6f48cbd-26de-4c2e-a24a-29892f9eb3be", + "entity_type": "bb_series", + "full_name": "Creative Commons Attribution-ShareAlike 3.0 Unported", + "id": "998512d8-0d6b-4c76-bfb7-0ec666e3fa0a", + "info_url": "https://creativecommons.org/licenses/by-sa/3.0/", + "is_draft": false, + "is_hidden": false, + "language": "en", + "last_revision": { + "id": 11773, + "rating": 4, + "review_id": "998512d8-0d6b-4c76-bfb7-0ec666e3fa0a", + "text": null, + "timestamp": "Tue, 16 Aug 2022 11:26:58 GMT" + }, + "last_updated": "Tue, 16 Aug 2022 11:26:58 GMT", + "license_id": "CC BY-SA 3.0", + "popularity": 0, + "published_on": "Tue, 16 Aug 2022 11:26:58 GMT", + "rating": 4, + "source": null, + "source_url": null, + "text": null, + "user": { + "created": "Tue, 18 Jan 2022 09:53:49 GMT", + "display_name": "Ansh Goyal", + "id": "11a1160e-d607-4882-8a82-e2e800f664fe", + "karma": 0, + "user_type": "Noob" + }, + "votes_negative_count": 0, + "votes_positive_count": 0 + } + ] + } + + :statuscode 200: no error + :statuscode 404: series not found + + :resheader Content-Type: *application/json* + """ + + series = db_series.get_series_by_bbid(str(series_bbid)) + + if not series: + raise NotFound("Can't find a series with ID: {series_bbid}".format(series_bbid=series_bbid)) + + user_id = Parser.uuid('uri', 'user_id', optional=True) + if user_id: + user_review, _ = db_review.list_reviews( + entity_id=series['bbid'], + entity_type='bb_series', + user_id=user_id + ) + if user_review: + user_review = db_review.to_dict(user_review[0]) + else: + user_review = None + + ratings_stats, average_rating = db_rating_stats.get_stats(series_bbid, "bb_series") + + top_reviews, reviews_count = db_review.list_reviews( + entity_id=series['bbid'], + entity_type='bb_series', + sort='popularity', + limit=REVIEWS_LIMIT, + offset=0, + ) + + latest_reviews, reviews_count = db_review.list_reviews( + entity_id=series['bbid'], + entity_type='bb_series', + sort='published_on', + limit=REVIEWS_LIMIT, + offset=0, + ) + + top_reviews = [db_review.to_dict(review) for review in top_reviews] + latest_reviews = [db_review.to_dict(review) for review in latest_reviews] + + result = { + "series": series, + "average_rating": average_rating, + "ratings_stats": ratings_stats, + "reviews_count": reviews_count, + "top_reviews": top_reviews, + "latest_reviews": latest_reviews + } + if user_id: + result['user_review'] = user_review + + return jsonify(**result) From a36cad50939b7f6603937ad83b13a5a097bbdbdc Mon Sep 17 00:00:00 2001 From: Ansh Goyal Date: Mon, 5 Sep 2022 12:22:33 +0000 Subject: [PATCH 06/15] feat: Cache reviews --- critiquebrainz/db/review.py | 17 +++-- critiquebrainz/ws/__init__.py | 2 + critiquebrainz/ws/artist/views.py | 74 ++++++++++++------- critiquebrainz/ws/bb_author/views.py | 78 ++++++++++++++------- critiquebrainz/ws/bb_edition_group/views.py | 78 ++++++++++++++------- critiquebrainz/ws/bb_literary_work/views.py | 78 ++++++++++++++------- critiquebrainz/ws/bb_series/views.py | 78 ++++++++++++++------- critiquebrainz/ws/event/views.py | 73 +++++++++++++------ critiquebrainz/ws/label/views.py | 77 +++++++++++++------- critiquebrainz/ws/place/views.py | 74 ++++++++++++------- critiquebrainz/ws/recording/views.py | 78 ++++++++++++++------- critiquebrainz/ws/release_group/views.py | 78 ++++++++++++++------- critiquebrainz/ws/work/views.py | 75 +++++++++++++------- 13 files changed, 580 insertions(+), 280 deletions(-) diff --git a/critiquebrainz/db/review.py b/critiquebrainz/db/review.py index 8b8433c8d..ac9b45810 100644 --- a/critiquebrainz/db/review.py +++ b/critiquebrainz/db/review.py @@ -296,14 +296,16 @@ def update(review_id, *, drafted, text=None, rating=None, license_id=None, langu db_revision.update_rating(review_id) result = connection.execute(sqlalchemy.text(""" - SELECT review.entity_id + SELECT review.entity_id, + review.entity_type, + review.user_id FROM review WHERE review.id = :review_id """), { "review_id": review_id, }) review = dict(result.fetchone()) - invalidate_ws_entity_cache(review["entity_id"]) + invalidate_ws_entity_cache(review["entity_id"], review["entity_type"], review["user_id"]) def create(*, entity_id, entity_type, user_id, is_draft, text=None, rating=None, @@ -385,11 +387,11 @@ def create(*, entity_id, entity_type, user_id, is_draft, text=None, rating=None, if rating: db_revision.update_rating(review_id) - invalidate_ws_entity_cache(entity_id) + invalidate_ws_entity_cache(entity_id, entity_type, user_id) return get_by_id(review_id) -def invalidate_ws_entity_cache(entity_id): +def invalidate_ws_entity_cache(entity_id, entity_type, user_id): cache_keys_for_entity_id_key = cache.gen_key('ws_cache', entity_id) cache_keys_to_delete = cache.smembers(cache_keys_for_entity_id_key, namespace=REVIEW_CACHE_NAMESPACE) if cache_keys_to_delete: @@ -402,6 +404,13 @@ def invalidate_ws_entity_cache(entity_id): cache.delete_many(cache_keys_to_delete, namespace=REVIEW_CACHE_NAMESPACE) cache.delete(cache_keys_for_no_entity_id_key, namespace=REVIEW_CACHE_NAMESPACE) + cache_keys_for_top_reviews_key = cache.gen_key('entity_api', entity_type, entity_id, "top_reviews") + cache_keys_for_latest_reviews_key = cache.gen_key('entity_api', entity_type, entity_id, "latest_reviews") + cache_keys_for_user_reviews_key = cache.gen_key('entity_api', entity_id, user_id, "user_reviews") + cache.delete(cache_keys_for_top_reviews_key, namespace=REVIEW_CACHE_NAMESPACE) + cache.delete(cache_keys_for_latest_reviews_key, namespace=REVIEW_CACHE_NAMESPACE) + cache.delete(cache_keys_for_user_reviews_key, namespace=REVIEW_CACHE_NAMESPACE) + # pylint: disable=too-many-branches def get_reviews_list(connection, *, inc_drafts=False, inc_hidden=False, entity_id=None, diff --git a/critiquebrainz/ws/__init__.py b/critiquebrainz/ws/__init__.py index 1bc0afc49..1a4014d69 100644 --- a/critiquebrainz/ws/__init__.py +++ b/critiquebrainz/ws/__init__.py @@ -8,6 +8,8 @@ deploy_env = os.environ.get('DEPLOY_ENV', '') CONSUL_CONFIG_FILE_RETRY_COUNT = 10 REVIEWS_LIMIT = 5 +REVIEW_CACHE_NAMESPACE = "Review" +REVIEW_CACHE_TIMEOUT = 30 * 60 # 30 minutes def create_app(debug=None, config_path=None): diff --git a/critiquebrainz/ws/artist/views.py b/critiquebrainz/ws/artist/views.py index 058fe66d1..8197389d5 100644 --- a/critiquebrainz/ws/artist/views.py +++ b/critiquebrainz/ws/artist/views.py @@ -5,7 +5,8 @@ from critiquebrainz.decorators import crossdomain from critiquebrainz.ws.exceptions import NotFound from critiquebrainz.ws.parser import Parser -from critiquebrainz.ws import REVIEWS_LIMIT +from critiquebrainz.ws import REVIEWS_LIMIT, REVIEW_CACHE_NAMESPACE, REVIEW_CACHE_TIMEOUT +from brainzutils import cache artist_bp = Blueprint('ws_artist', __name__) @@ -229,38 +230,63 @@ def artist_entity_handler(artist_mbid): if not artist: raise NotFound("Can't find an artist with ID: {artist_mbid}".format(artist_mbid=artist_mbid)) + user_review = None + user_id = Parser.uuid('uri', 'user_id', optional=True) if user_id: - user_review, _ = db_review.list_reviews( + user_review_cache_key = cache.gen_key('entity_api', artist['mbid'], user_id, "user_review") + user_review = cache.get(user_review_cache_key) + if not user_review: + user_review, _ = db_review.list_reviews( + entity_id=artist['mbid'], + entity_type='artist', + user_id=user_id + ) + if user_review: + user_review = db_review.to_dict(user_review[0]) + else: + user_review = None + + cache.set(user_review_cache_key, user_review, + expirein=REVIEW_CACHE_TIMEOUT, namespace=REVIEW_CACHE_NAMESPACE) + + ratings_stats, average_rating = db_rating_stats.get_stats(artist_mbid, "artist") + + top_reviews_cache_key = cache.gen_key("entity_api_artist", artist['mbid'], "top_reviews") + top_reviews_cached_result = cache.get(top_reviews_cache_key, REVIEW_CACHE_NAMESPACE) + + if top_reviews_cached_result: + top_reviews, reviews_count = top_reviews_cached_result + else: + top_reviews, reviews_count = db_review.list_reviews( entity_id=artist['mbid'], entity_type='artist', - user_id=user_id + sort='popularity', + limit=REVIEWS_LIMIT, + offset=0, ) - if user_review: - user_review = db_review.to_dict(user_review[0]) - else: - user_review = None + top_reviews = [db_review.to_dict(review) for review in top_reviews] - ratings_stats, average_rating = db_rating_stats.get_stats(artist_mbid, "artist") + cache.set(top_reviews_cache_key, (top_reviews, reviews_count), + expirein=REVIEW_CACHE_TIMEOUT, namespace=REVIEW_CACHE_NAMESPACE) - top_reviews, reviews_count = db_review.list_reviews( - entity_id=artist['mbid'], - entity_type='artist', - sort='popularity', - limit=REVIEWS_LIMIT, - offset=0, - ) + latest_reviews_cache_key = cache.gen_key("entity_api_artist", artist['mbid'], "latest_reviews") + latest_reviews_cached_result = cache.get(latest_reviews_cache_key, REVIEW_CACHE_NAMESPACE) - latest_reviews, reviews_count = db_review.list_reviews( - entity_id=artist['mbid'], - entity_type='artist', - sort='published_on', - limit=REVIEWS_LIMIT, - offset=0, - ) + if latest_reviews_cached_result: + latest_reviews, reviews_count = latest_reviews_cached_result + else: + latest_reviews, reviews_count = db_review.list_reviews( + entity_id=artist['mbid'], + entity_type='artist', + sort='published_on', + limit=REVIEWS_LIMIT, + offset=0, + ) + latest_reviews = [db_review.to_dict(review) for review in latest_reviews] - top_reviews = [db_review.to_dict(review) for review in top_reviews] - latest_reviews = [db_review.to_dict(review) for review in latest_reviews] + cache.set(latest_reviews_cache_key, (latest_reviews, reviews_count), + expirein=REVIEW_CACHE_TIMEOUT, namespace=REVIEW_CACHE_NAMESPACE) result = { "artist": artist, diff --git a/critiquebrainz/ws/bb_author/views.py b/critiquebrainz/ws/bb_author/views.py index 1a9554a7b..26dda70cf 100644 --- a/critiquebrainz/ws/bb_author/views.py +++ b/critiquebrainz/ws/bb_author/views.py @@ -5,7 +5,8 @@ from critiquebrainz.decorators import crossdomain from critiquebrainz.ws.exceptions import NotFound from critiquebrainz.ws.parser import Parser -from critiquebrainz.ws import REVIEWS_LIMIT +from critiquebrainz.ws import REVIEWS_LIMIT, REVIEW_CACHE_NAMESPACE, REVIEW_CACHE_TIMEOUT +from brainzutils import cache author_bp = Blueprint('ws_author', __name__) @@ -166,38 +167,63 @@ def author_entity_handler(author_bbid): if not author: raise NotFound("Can't find an author with ID: {author_bbid}".format(author_bbid=author_bbid)) + user_review = [] + user_id = Parser.uuid('uri', 'user_id', optional=True) if user_id: - user_review, _ = db_review.list_reviews( + user_review_cache_key = cache.gen_key('entity_api', author['bbid'], user_id, "user_review") + user_review = cache.get(user_review_cache_key) + if not user_review: + user_review, _ = db_review.list_reviews( + entity_id=author['bbid'], + entity_type='bb_author', + user_id=user_id + ) + if user_review: + user_review = db_review.to_dict(user_review[0]) + else: + user_review = [] + + cache.set(user_review_cache_key, user_review, + expirein=REVIEW_CACHE_TIMEOUT, namespace=REVIEW_CACHE_NAMESPACE) + + ratings_stats, average_rating = db_rating_stats.get_stats(author_bbid, "bb_author") + + top_reviews_cache_key = cache.gen_key("entity_api_bb_author", author['bbid'], "top_reviews") + top_reviews_cached_result = cache.get(top_reviews_cache_key, REVIEW_CACHE_NAMESPACE) + + if top_reviews_cached_result: + top_reviews, reviews_count = top_reviews_cached_result + else: + top_reviews, reviews_count = db_review.list_reviews( entity_id=author['bbid'], entity_type='bb_author', - user_id=user_id + sort='popularity', + limit=REVIEWS_LIMIT, + offset=0, ) - if user_review: - user_review = db_review.to_dict(user_review[0]) - else: - user_review = None + top_reviews = [db_review.to_dict(review) for review in top_reviews] - ratings_stats, average_rating = db_rating_stats.get_stats(author_bbid, "bb_author") + cache.set(top_reviews_cache_key, (top_reviews, reviews_count), + expirein=REVIEW_CACHE_TIMEOUT, namespace=REVIEW_CACHE_NAMESPACE) + + latest_reviews_cache_key = cache.gen_key("entity_api_bb_author", author['bbid'], "latest_reviews") + latest_reviews_cached_result = cache.get(latest_reviews_cache_key, REVIEW_CACHE_NAMESPACE) + + if latest_reviews_cached_result: + latest_reviews, reviews_count = latest_reviews_cached_result + else: + latest_reviews, reviews_count = db_review.list_reviews( + entity_id=author['bbid'], + entity_type='bb_author', + sort='published_on', + limit=REVIEWS_LIMIT, + offset=0, + ) + latest_reviews = [db_review.to_dict(review) for review in latest_reviews] - top_reviews, reviews_count = db_review.list_reviews( - entity_id=author['bbid'], - entity_type='bb_author', - sort='popularity', - limit=REVIEWS_LIMIT, - offset=0, - ) - - latest_reviews, reviews_count = db_review.list_reviews( - entity_id=author['bbid'], - entity_type='bb_author', - sort='published_on', - limit=REVIEWS_LIMIT, - offset=0, - ) - - top_reviews = [db_review.to_dict(review) for review in top_reviews] - latest_reviews = [db_review.to_dict(review) for review in latest_reviews] + cache.set(latest_reviews_cache_key, (latest_reviews, reviews_count), + expirein=REVIEW_CACHE_TIMEOUT, namespace=REVIEW_CACHE_NAMESPACE) result = { "author": author, diff --git a/critiquebrainz/ws/bb_edition_group/views.py b/critiquebrainz/ws/bb_edition_group/views.py index 54edee1d9..ef12b15cd 100644 --- a/critiquebrainz/ws/bb_edition_group/views.py +++ b/critiquebrainz/ws/bb_edition_group/views.py @@ -5,7 +5,8 @@ from critiquebrainz.decorators import crossdomain from critiquebrainz.ws.exceptions import NotFound from critiquebrainz.ws.parser import Parser -from critiquebrainz.ws import REVIEWS_LIMIT +from critiquebrainz.ws import REVIEWS_LIMIT, REVIEW_CACHE_NAMESPACE, REVIEW_CACHE_TIMEOUT +from brainzutils import cache edition_group_bp = Blueprint('ws_edition_group', __name__) @@ -144,38 +145,63 @@ def edition_group_entity_handler(edition_group_bbid): if not edition_group: raise NotFound("Can't find an edition_group with ID: {edition_group_bbid}".format(edition_group_bbid=edition_group_bbid)) + user_review = [] + user_id = Parser.uuid('uri', 'user_id', optional=True) if user_id: - user_review, _ = db_review.list_reviews( + user_review_cache_key = cache.gen_key('entity_api', edition_group['bbid'], user_id, "user_review") + user_review = cache.get(user_review_cache_key) + if not user_review: + user_review, _ = db_review.list_reviews( + entity_id=edition_group['bbid'], + entity_type='bb_edition_group', + user_id=user_id + ) + if user_review: + user_review = db_review.to_dict(user_review[0]) + else: + user_review = [] + + cache.set(user_review_cache_key, user_review, + expirein=REVIEW_CACHE_TIMEOUT, namespace=REVIEW_CACHE_NAMESPACE) + + ratings_stats, average_rating = db_rating_stats.get_stats(edition_group_bbid, "bb_edition_group") + + top_reviews_cache_key = cache.gen_key("entity_api_bb_edition_group", edition_group['bbid'], "top_reviews") + top_reviews_cached_result = cache.get(top_reviews_cache_key, REVIEW_CACHE_NAMESPACE) + + if top_reviews_cached_result: + top_reviews, reviews_count = top_reviews_cached_result + else: + top_reviews, reviews_count = db_review.list_reviews( entity_id=edition_group['bbid'], entity_type='bb_edition_group', - user_id=user_id + sort='popularity', + limit=REVIEWS_LIMIT, + offset=0, ) - if user_review: - user_review = db_review.to_dict(user_review[0]) - else: - user_review = None + top_reviews = [db_review.to_dict(review) for review in top_reviews] - ratings_stats, average_rating = db_rating_stats.get_stats(edition_group_bbid, "bb_edition_group") + cache.set(top_reviews_cache_key, (top_reviews, reviews_count), + expirein=REVIEW_CACHE_TIMEOUT, namespace=REVIEW_CACHE_NAMESPACE) + + latest_reviews_cache_key = cache.gen_key("entity_api_bb_edition_group", edition_group['bbid'], "latest_reviews") + latest_reviews_cached_result = cache.get(latest_reviews_cache_key, REVIEW_CACHE_NAMESPACE) + + if latest_reviews_cached_result: + latest_reviews, reviews_count = latest_reviews_cached_result + else: + latest_reviews, reviews_count = db_review.list_reviews( + entity_id=edition_group['bbid'], + entity_type='bb_edition_group', + sort='published_on', + limit=REVIEWS_LIMIT, + offset=0, + ) + latest_reviews = [db_review.to_dict(review) for review in latest_reviews] - top_reviews, reviews_count = db_review.list_reviews( - entity_id=edition_group['bbid'], - entity_type='bb_edition_group', - sort='popularity', - limit=REVIEWS_LIMIT, - offset=0, - ) - - latest_reviews, reviews_count = db_review.list_reviews( - entity_id=edition_group['bbid'], - entity_type='bb_edition_group', - sort='published_on', - limit=REVIEWS_LIMIT, - offset=0, - ) - - top_reviews = [db_review.to_dict(review) for review in top_reviews] - latest_reviews = [db_review.to_dict(review) for review in latest_reviews] + cache.set(latest_reviews_cache_key, (latest_reviews, reviews_count), + expirein=REVIEW_CACHE_TIMEOUT, namespace=REVIEW_CACHE_NAMESPACE) result = { "edition_group": edition_group, diff --git a/critiquebrainz/ws/bb_literary_work/views.py b/critiquebrainz/ws/bb_literary_work/views.py index 1c1d006d3..ba0c5991b 100644 --- a/critiquebrainz/ws/bb_literary_work/views.py +++ b/critiquebrainz/ws/bb_literary_work/views.py @@ -5,7 +5,8 @@ from critiquebrainz.decorators import crossdomain from critiquebrainz.ws.exceptions import NotFound from critiquebrainz.ws.parser import Parser -from critiquebrainz.ws import REVIEWS_LIMIT +from critiquebrainz.ws import REVIEWS_LIMIT, REVIEW_CACHE_NAMESPACE, REVIEW_CACHE_TIMEOUT +from brainzutils import cache literary_work_bp = Blueprint('ws_literary_work', __name__) @@ -164,38 +165,63 @@ def literary_work_entity_handler(literary_work_bbid): if not literary_work: raise NotFound("Can't find a literary_work with ID: {literary_work_bbid}".format(literary_work_bbid=literary_work_bbid)) + user_review = [] + user_id = Parser.uuid('uri', 'user_id', optional=True) if user_id: - user_review, _ = db_review.list_reviews( + user_review_cache_key = cache.gen_key('entity_api', literary_work['bbid'], user_id, "user_review") + user_review = cache.get(user_review_cache_key) + if not user_review: + user_review, _ = db_review.list_reviews( + entity_id=literary_work['bbid'], + entity_type='bb_literary_work', + user_id=user_id + ) + if user_review: + user_review = db_review.to_dict(user_review[0]) + else: + user_review = [] + + cache.set(user_review_cache_key, user_review, + expirein=REVIEW_CACHE_TIMEOUT, namespace=REVIEW_CACHE_NAMESPACE) + + ratings_stats, average_rating = db_rating_stats.get_stats(literary_work_bbid, "bb_literary_work") + + top_reviews_cache_key = cache.gen_key("entity_api_bb_literary_work", literary_work['bbid'], "top_reviews") + top_reviews_cached_result = cache.get(top_reviews_cache_key, REVIEW_CACHE_NAMESPACE) + + if top_reviews_cached_result: + top_reviews, reviews_count = top_reviews_cached_result + else: + top_reviews, reviews_count = db_review.list_reviews( entity_id=literary_work['bbid'], entity_type='bb_literary_work', - user_id=user_id + sort='popularity', + limit=REVIEWS_LIMIT, + offset=0, ) - if user_review: - user_review = db_review.to_dict(user_review[0]) - else: - user_review = None + top_reviews = [db_review.to_dict(review) for review in top_reviews] - ratings_stats, average_rating = db_rating_stats.get_stats(literary_work_bbid, "bb_literary_work") + cache.set(top_reviews_cache_key, (top_reviews, reviews_count), + expirein=REVIEW_CACHE_TIMEOUT, namespace=REVIEW_CACHE_NAMESPACE) + + latest_reviews_cache_key = cache.gen_key("entity_api_bb_literary_work", literary_work['bbid'], "latest_reviews") + latest_reviews_cached_result = cache.get(latest_reviews_cache_key, REVIEW_CACHE_NAMESPACE) + + if latest_reviews_cached_result: + latest_reviews, reviews_count = latest_reviews_cached_result + else: + latest_reviews, reviews_count = db_review.list_reviews( + entity_id=literary_work['bbid'], + entity_type='bb_literary_work', + sort='published_on', + limit=REVIEWS_LIMIT, + offset=0, + ) + latest_reviews = [db_review.to_dict(review) for review in latest_reviews] - top_reviews, reviews_count = db_review.list_reviews( - entity_id=literary_work['bbid'], - entity_type='bb_literary_work', - sort='popularity', - limit=REVIEWS_LIMIT, - offset=0, - ) - - latest_reviews, reviews_count = db_review.list_reviews( - entity_id=literary_work['bbid'], - entity_type='bb_literary_work', - sort='published_on', - limit=REVIEWS_LIMIT, - offset=0, - ) - - top_reviews = [db_review.to_dict(review) for review in top_reviews] - latest_reviews = [db_review.to_dict(review) for review in latest_reviews] + cache.set(latest_reviews_cache_key, (latest_reviews, reviews_count), + expirein=REVIEW_CACHE_TIMEOUT, namespace=REVIEW_CACHE_NAMESPACE) result = { "literary_work": literary_work, diff --git a/critiquebrainz/ws/bb_series/views.py b/critiquebrainz/ws/bb_series/views.py index da24edea9..0c20a40c1 100644 --- a/critiquebrainz/ws/bb_series/views.py +++ b/critiquebrainz/ws/bb_series/views.py @@ -5,7 +5,8 @@ from critiquebrainz.decorators import crossdomain from critiquebrainz.ws.exceptions import NotFound from critiquebrainz.ws.parser import Parser -from critiquebrainz.ws import REVIEWS_LIMIT +from critiquebrainz.ws import REVIEWS_LIMIT, REVIEW_CACHE_NAMESPACE, REVIEW_CACHE_TIMEOUT +from brainzutils import cache series_bp = Blueprint('ws_series', __name__) @@ -136,38 +137,63 @@ def series_entity_handler(series_bbid): if not series: raise NotFound("Can't find a series with ID: {series_bbid}".format(series_bbid=series_bbid)) + user_review = [] + user_id = Parser.uuid('uri', 'user_id', optional=True) if user_id: - user_review, _ = db_review.list_reviews( + user_review_cache_key = cache.gen_key('entity_api', series['bbid'], user_id, "user_review") + user_review = cache.get(user_review_cache_key) + if not user_review: + user_review, _ = db_review.list_reviews( + entity_id=series['bbid'], + entity_type='bb_series', + user_id=user_id + ) + if user_review: + user_review = db_review.to_dict(user_review[0]) + else: + user_review = [] + + cache.set(user_review_cache_key, user_review, + expirein=REVIEW_CACHE_TIMEOUT, namespace=REVIEW_CACHE_NAMESPACE) + + ratings_stats, average_rating = db_rating_stats.get_stats(series_bbid, "bb_series") + + top_reviews_cache_key = cache.gen_key("entity_api_bb_series", series['bbid'], "top_reviews") + top_reviews_cached_result = cache.get(top_reviews_cache_key, REVIEW_CACHE_NAMESPACE) + + if top_reviews_cached_result: + top_reviews, reviews_count = top_reviews_cached_result + else: + top_reviews, reviews_count = db_review.list_reviews( entity_id=series['bbid'], entity_type='bb_series', - user_id=user_id + sort='popularity', + limit=REVIEWS_LIMIT, + offset=0, ) - if user_review: - user_review = db_review.to_dict(user_review[0]) - else: - user_review = None + top_reviews = [db_review.to_dict(review) for review in top_reviews] - ratings_stats, average_rating = db_rating_stats.get_stats(series_bbid, "bb_series") + cache.set(top_reviews_cache_key, (top_reviews, reviews_count), + expirein=REVIEW_CACHE_TIMEOUT, namespace=REVIEW_CACHE_NAMESPACE) + + latest_reviews_cache_key = cache.gen_key("entity_api_bb_series", series['bbid'], "latest_reviews") + latest_reviews_cached_result = cache.get(latest_reviews_cache_key, REVIEW_CACHE_NAMESPACE) + + if latest_reviews_cached_result: + latest_reviews, reviews_count = latest_reviews_cached_result + else: + latest_reviews, reviews_count = db_review.list_reviews( + entity_id=series['bbid'], + entity_type='bb_series', + sort='published_on', + limit=REVIEWS_LIMIT, + offset=0, + ) + latest_reviews = [db_review.to_dict(review) for review in latest_reviews] - top_reviews, reviews_count = db_review.list_reviews( - entity_id=series['bbid'], - entity_type='bb_series', - sort='popularity', - limit=REVIEWS_LIMIT, - offset=0, - ) - - latest_reviews, reviews_count = db_review.list_reviews( - entity_id=series['bbid'], - entity_type='bb_series', - sort='published_on', - limit=REVIEWS_LIMIT, - offset=0, - ) - - top_reviews = [db_review.to_dict(review) for review in top_reviews] - latest_reviews = [db_review.to_dict(review) for review in latest_reviews] + cache.set(latest_reviews_cache_key, (latest_reviews, reviews_count), + expirein=REVIEW_CACHE_TIMEOUT, namespace=REVIEW_CACHE_NAMESPACE) result = { "series": series, diff --git a/critiquebrainz/ws/event/views.py b/critiquebrainz/ws/event/views.py index 1404e51a4..6e199dacd 100644 --- a/critiquebrainz/ws/event/views.py +++ b/critiquebrainz/ws/event/views.py @@ -6,6 +6,8 @@ from critiquebrainz.ws.exceptions import NotFound from critiquebrainz.ws.parser import Parser from critiquebrainz.ws import REVIEWS_LIMIT +from critiquebrainz.ws import REVIEWS_LIMIT, REVIEW_CACHE_NAMESPACE, REVIEW_CACHE_TIMEOUT +from brainzutils import cache event_bp = Blueprint('ws_event', __name__) @@ -255,38 +257,63 @@ def event_entity_handler(event_mbid): if not event: raise NotFound("Can't find an event with ID: {event_mbid}".format(event_mbid=event_mbid)) + user_review = None + user_id = Parser.uuid('uri', 'user_id', optional=True) if user_id: - user_review, _ = db_review.list_reviews( + user_review_cache_key = cache.gen_key('entity_api', event['mbid'], user_id, "user_review") + user_review = cache.get(user_review_cache_key) + if not user_review: + user_review, _ = db_review.list_reviews( + entity_id=event['mbid'], + entity_type='event', + user_id=user_id + ) + if user_review: + user_review = db_review.to_dict(user_review[0]) + else: + user_review = None + + cache.set(user_review_cache_key, user_review, + expirein=REVIEW_CACHE_TIMEOUT, namespace=REVIEW_CACHE_NAMESPACE) + + ratings_stats, average_rating = db_rating_stats.get_stats(event_mbid, "event") + + top_reviews_cache_key = cache.gen_key("entity_api_event", event['mbid'], "top_reviews") + top_reviews_cached_result = cache.get(top_reviews_cache_key, REVIEW_CACHE_NAMESPACE) + + if top_reviews_cached_result: + top_reviews, reviews_count = top_reviews_cached_result + else: + top_reviews, reviews_count = db_review.list_reviews( entity_id=event['mbid'], entity_type='event', - user_id=user_id + sort='popularity', + limit=REVIEWS_LIMIT, + offset=0, ) - if user_review: - user_review = db_review.to_dict(user_review[0]) - else: - user_review = None + top_reviews = [db_review.to_dict(review) for review in top_reviews] - ratings_stats, average_rating = db_rating_stats.get_stats(event_mbid, "event") + cache.set(top_reviews_cache_key, (top_reviews, reviews_count), + expirein=REVIEW_CACHE_TIMEOUT, namespace=REVIEW_CACHE_NAMESPACE) - top_reviews, reviews_count = db_review.list_reviews( - entity_id=event['mbid'], - entity_type='event', - sort='popularity', - limit=REVIEWS_LIMIT, - offset=0, - ) + latest_reviews_cache_key = cache.gen_key("entity_api_event", event['mbid'], "latest_reviews") + latest_reviews_cached_result = cache.get(latest_reviews_cache_key, REVIEW_CACHE_NAMESPACE) - latest_reviews, reviews_count = db_review.list_reviews( - entity_id=event['mbid'], - entity_type='event', - sort='published_on', - limit=REVIEWS_LIMIT, - offset=0, - ) + if latest_reviews_cached_result: + latest_reviews, reviews_count = latest_reviews_cached_result + else: + latest_reviews, reviews_count = db_review.list_reviews( + entity_id=event['mbid'], + entity_type='event', + sort='published_on', + limit=REVIEWS_LIMIT, + offset=0, + ) + latest_reviews = [db_review.to_dict(review) for review in latest_reviews] - top_reviews = [db_review.to_dict(review) for review in top_reviews] - latest_reviews = [db_review.to_dict(review) for review in latest_reviews] + cache.set(latest_reviews_cache_key, (latest_reviews, reviews_count), + expirein=REVIEW_CACHE_TIMEOUT, namespace=REVIEW_CACHE_NAMESPACE) result = { "event": event, diff --git a/critiquebrainz/ws/label/views.py b/critiquebrainz/ws/label/views.py index aed63948d..95dd6cf1e 100644 --- a/critiquebrainz/ws/label/views.py +++ b/critiquebrainz/ws/label/views.py @@ -6,6 +6,8 @@ from critiquebrainz.ws.exceptions import NotFound from critiquebrainz.ws.parser import Parser from critiquebrainz.ws import REVIEWS_LIMIT +from critiquebrainz.ws import REVIEWS_LIMIT, REVIEW_CACHE_NAMESPACE, REVIEW_CACHE_TIMEOUT +from brainzutils import cache label_bp = Blueprint('ws_label', __name__) @@ -170,38 +172,63 @@ def label_entity_handler(label_mbid): if not label: raise NotFound("Can't find a label with ID: {label_mbid}".format(label_mbid=label_mbid)) + user_review = None + user_id = Parser.uuid('uri', 'user_id', optional=True) if user_id: - user_review, _ = db_review.list_reviews( + user_review_cache_key = cache.gen_key('entity_api', label['mbid'], user_id, "user_review") + user_review = cache.get(user_review_cache_key) + if not user_review: + user_review, _ = db_review.list_reviews( + entity_id=label['mbid'], + entity_type='label', + user_id=user_id + ) + if user_review: + user_review = db_review.to_dict(user_review[0]) + else: + user_review = None + + cache.set(user_review_cache_key, user_review, + expirein=REVIEW_CACHE_TIMEOUT, namespace=REVIEW_CACHE_NAMESPACE) + + ratings_stats, average_rating = db_rating_stats.get_stats(label_mbid, "label") + + top_reviews_cache_key = cache.gen_key("entity_api_label", label['mbid'], "top_reviews") + top_reviews_cached_result = cache.get(top_reviews_cache_key, REVIEW_CACHE_NAMESPACE) + + if top_reviews_cached_result: + top_reviews, reviews_count = top_reviews_cached_result + else: + top_reviews, reviews_count = db_review.list_reviews( entity_id=label['mbid'], entity_type='label', - user_id=user_id + sort='popularity', + limit=REVIEWS_LIMIT, + offset=0, ) - if user_review: - user_review = db_review.to_dict(user_review[0]) - else: - user_review = None + top_reviews = [db_review.to_dict(review) for review in top_reviews] - ratings_stats, average_rating = db_rating_stats.get_stats(label_mbid, "label") + cache.set(top_reviews_cache_key, (top_reviews, reviews_count), + expirein=REVIEW_CACHE_TIMEOUT, namespace=REVIEW_CACHE_NAMESPACE) + + latest_reviews_cache_key = cache.gen_key("entity_api_label", label['mbid'], "latest_reviews") + latest_reviews_cached_result = cache.get(latest_reviews_cache_key, REVIEW_CACHE_NAMESPACE) + + if latest_reviews_cached_result: + latest_reviews, reviews_count = latest_reviews_cached_result + else: + latest_reviews, reviews_count = db_review.list_reviews( + entity_id=label['mbid'], + entity_type='label', + sort='published_on', + limit=REVIEWS_LIMIT, + offset=0, + ) + latest_reviews = [db_review.to_dict(review) for review in latest_reviews] - top_reviews, reviews_count = db_review.list_reviews( - entity_id=label['mbid'], - entity_type='label', - sort='popularity', - limit=REVIEWS_LIMIT, - offset=0, - ) - - latest_reviews, reviews_count = db_review.list_reviews( - entity_id=label['mbid'], - entity_type='label', - sort='published_on', - limit=REVIEWS_LIMIT, - offset=0, - ) - - top_reviews = [db_review.to_dict(review) for review in top_reviews] - latest_reviews = [db_review.to_dict(review) for review in latest_reviews] + cache.set(latest_reviews_cache_key, (latest_reviews, reviews_count), + expirein=REVIEW_CACHE_TIMEOUT, namespace=REVIEW_CACHE_NAMESPACE) result = { "label": label, diff --git a/critiquebrainz/ws/place/views.py b/critiquebrainz/ws/place/views.py index cd2a0cf1f..ee6d9f909 100644 --- a/critiquebrainz/ws/place/views.py +++ b/critiquebrainz/ws/place/views.py @@ -5,7 +5,8 @@ from critiquebrainz.decorators import crossdomain from critiquebrainz.ws.exceptions import NotFound from critiquebrainz.ws.parser import Parser -from critiquebrainz.ws import REVIEWS_LIMIT +from critiquebrainz.ws import REVIEWS_LIMIT, REVIEW_CACHE_NAMESPACE, REVIEW_CACHE_TIMEOUT +from brainzutils import cache place_bp = Blueprint('ws_place', __name__) @@ -325,38 +326,63 @@ def place_entity_handler(place_mbid): if not place: raise NotFound("Can't find a place with ID: {place_mbid}".format(place_mbid=place_mbid)) + user_review = None + user_id = Parser.uuid('uri', 'user_id', optional=True) if user_id: - user_review, _ = db_review.list_reviews( + user_review_cache_key = cache.gen_key('entity_api', place['mbid'], user_id, "user_review") + user_review = cache.get(user_review_cache_key) + if not user_review: + user_review, _ = db_review.list_reviews( + entity_id=place['mbid'], + entity_type='place', + user_id=user_id + ) + if user_review: + user_review = db_review.to_dict(user_review[0]) + else: + user_review = None + + cache.set(user_review_cache_key, user_review, + expirein=REVIEW_CACHE_TIMEOUT, namespace=REVIEW_CACHE_NAMESPACE) + + ratings_stats, average_rating = db_rating_stats.get_stats(place_mbid, "place") + + top_reviews_cache_key = cache.gen_key("entity_api_place", place['mbid'], "top_reviews") + top_reviews_cached_result = cache.get(top_reviews_cache_key, REVIEW_CACHE_NAMESPACE) + + if top_reviews_cached_result: + top_reviews, reviews_count = top_reviews_cached_result + else: + top_reviews, reviews_count = db_review.list_reviews( entity_id=place['mbid'], entity_type='place', - user_id=user_id + sort='popularity', + limit=REVIEWS_LIMIT, + offset=0, ) - if user_review: - user_review = db_review.to_dict(user_review[0]) - else: - user_review = None + top_reviews = [db_review.to_dict(review) for review in top_reviews] - ratings_stats, average_rating = db_rating_stats.get_stats(place_mbid, "place") + cache.set(top_reviews_cache_key, (top_reviews, reviews_count), + expirein=REVIEW_CACHE_TIMEOUT, namespace=REVIEW_CACHE_NAMESPACE) - top_reviews, reviews_count = db_review.list_reviews( - entity_id=place['mbid'], - entity_type='place', - sort='popularity', - limit=REVIEWS_LIMIT, - offset=0, - ) + latest_reviews_cache_key = cache.gen_key("entity_api_place", place['mbid'], "latest_reviews") + latest_reviews_cached_result = cache.get(latest_reviews_cache_key, REVIEW_CACHE_NAMESPACE) - latest_reviews, reviews_count = db_review.list_reviews( - entity_id=place['mbid'], - entity_type='place', - sort='published_on', - limit=REVIEWS_LIMIT, - offset=0, - ) + if latest_reviews_cached_result: + latest_reviews, reviews_count = latest_reviews_cached_result + else: + latest_reviews, reviews_count = db_review.list_reviews( + entity_id=place['mbid'], + entity_type='place', + sort='published_on', + limit=REVIEWS_LIMIT, + offset=0, + ) + latest_reviews = [db_review.to_dict(review) for review in latest_reviews] - top_reviews = [db_review.to_dict(review) for review in top_reviews] - latest_reviews = [db_review.to_dict(review) for review in latest_reviews] + cache.set(latest_reviews_cache_key, (latest_reviews, reviews_count), + expirein=REVIEW_CACHE_TIMEOUT, namespace=REVIEW_CACHE_NAMESPACE) result = { "place": place, diff --git a/critiquebrainz/ws/recording/views.py b/critiquebrainz/ws/recording/views.py index 28faa6d81..eb2865deb 100644 --- a/critiquebrainz/ws/recording/views.py +++ b/critiquebrainz/ws/recording/views.py @@ -5,7 +5,8 @@ from critiquebrainz.decorators import crossdomain from critiquebrainz.ws.exceptions import NotFound from critiquebrainz.ws.parser import Parser -from critiquebrainz.ws import REVIEWS_LIMIT +from critiquebrainz.ws import REVIEWS_LIMIT, REVIEW_CACHE_NAMESPACE, REVIEW_CACHE_TIMEOUT +from brainzutils import cache recording_bp = Blueprint('ws_recording', __name__) @@ -139,38 +140,63 @@ def recording_entity_handler(recording_mbid): if not recording: raise NotFound("Can't find a recording with ID: {recording_mbid}".format(recording_mbid=recording_mbid)) + user_review = None + user_id = Parser.uuid('uri', 'user_id', optional=True) if user_id: - user_review, _ = db_review.list_reviews( + user_review_cache_key = cache.gen_key('entity_api', recording['mbid'], user_id, "user_review") + user_review = cache.get(user_review_cache_key) + if not user_review: + user_review, _ = db_review.list_reviews( + entity_id=recording['mbid'], + entity_type='recording', + user_id=user_id + ) + if user_review: + user_review = db_review.to_dict(user_review[0]) + else: + user_review = None + + cache.set(user_review_cache_key, user_review, + expirein=REVIEW_CACHE_TIMEOUT, namespace=REVIEW_CACHE_NAMESPACE) + + ratings_stats, average_rating = db_rating_stats.get_stats(recording_mbid, "recording") + + top_reviews_cache_key = cache.gen_key("entity_api_recording", recording['mbid'], "top_reviews") + top_reviews_cached_result = cache.get(top_reviews_cache_key, REVIEW_CACHE_NAMESPACE) + + if top_reviews_cached_result: + top_reviews, reviews_count = top_reviews_cached_result + else: + top_reviews, reviews_count = db_review.list_reviews( entity_id=recording['mbid'], entity_type='recording', - user_id=user_id + sort='popularity', + limit=REVIEWS_LIMIT, + offset=0, ) - if user_review: - user_review = db_review.to_dict(user_review[0]) - else: - user_review = None + top_reviews = [db_review.to_dict(review) for review in top_reviews] - ratings_stats, average_rating = db_rating_stats.get_stats(recording_mbid, "recording") + cache.set(top_reviews_cache_key, (top_reviews, reviews_count), + expirein=REVIEW_CACHE_TIMEOUT, namespace=REVIEW_CACHE_NAMESPACE) + + latest_reviews_cache_key = cache.gen_key("entity_api_recording", recording['mbid'], "latest_reviews") + latest_reviews_cached_result = cache.get(latest_reviews_cache_key, REVIEW_CACHE_NAMESPACE) + + if latest_reviews_cached_result: + latest_reviews, reviews_count = latest_reviews_cached_result + else: + latest_reviews, reviews_count = db_review.list_reviews( + entity_id=recording['mbid'], + entity_type='recording', + sort='published_on', + limit=REVIEWS_LIMIT, + offset=0, + ) + latest_reviews = [db_review.to_dict(review) for review in latest_reviews] - top_reviews, reviews_count = db_review.list_reviews( - entity_id=recording['mbid'], - entity_type='recording', - sort='popularity', - limit=REVIEWS_LIMIT, - offset=0, - ) - - latest_reviews, reviews_count = db_review.list_reviews( - entity_id=recording['mbid'], - entity_type='recording', - sort='published_on', - limit=REVIEWS_LIMIT, - offset=0, - ) - - top_reviews = [db_review.to_dict(review) for review in top_reviews] - latest_reviews = [db_review.to_dict(review) for review in latest_reviews] + cache.set(latest_reviews_cache_key, (latest_reviews, reviews_count), + expirein=REVIEW_CACHE_TIMEOUT, namespace=REVIEW_CACHE_NAMESPACE) result = { "recording": recording, diff --git a/critiquebrainz/ws/release_group/views.py b/critiquebrainz/ws/release_group/views.py index 3c2485a81..062e38b9c 100644 --- a/critiquebrainz/ws/release_group/views.py +++ b/critiquebrainz/ws/release_group/views.py @@ -5,7 +5,8 @@ from critiquebrainz.decorators import crossdomain from critiquebrainz.ws.exceptions import NotFound from critiquebrainz.ws.parser import Parser -from critiquebrainz.ws import REVIEWS_LIMIT +from critiquebrainz.ws import REVIEWS_LIMIT, REVIEW_CACHE_NAMESPACE, REVIEW_CACHE_TIMEOUT +from brainzutils import cache release_group_bp = Blueprint('ws_release_group', __name__) @@ -152,38 +153,63 @@ def release_group_entity_handler(release_group_mbid): if not release_group: raise NotFound("Can't find a release group with ID: {release_group_mbid}".format(release_group_mbid=release_group_mbid)) + user_review = None + user_id = Parser.uuid('uri', 'user_id', optional=True) if user_id: - user_review, _ = db_review.list_reviews( + user_review_cache_key = cache.gen_key('entity_api', release_group['mbid'], user_id, "user_review") + user_review = cache.get(user_review_cache_key) + if not user_review: + user_review, _ = db_review.list_reviews( + entity_id=release_group['mbid'], + entity_type='release_group', + user_id=user_id + ) + if user_review: + user_review = db_review.to_dict(user_review[0]) + else: + user_review = None + + cache.set(user_review_cache_key, user_review, + expirein=REVIEW_CACHE_TIMEOUT, namespace=REVIEW_CACHE_NAMESPACE) + + ratings_stats, average_rating = db_rating_stats.get_stats(release_group_mbid, "release_group") + + top_reviews_cache_key = cache.gen_key("entity_api_release_group", release_group['mbid'], "top_reviews") + top_reviews_cached_result = cache.get(top_reviews_cache_key, REVIEW_CACHE_NAMESPACE) + + if top_reviews_cached_result: + top_reviews, reviews_count = top_reviews_cached_result + else: + top_reviews, reviews_count = db_review.list_reviews( entity_id=release_group['mbid'], entity_type='release_group', - user_id=user_id + sort='popularity', + limit=REVIEWS_LIMIT, + offset=0, ) - if user_review: - user_review = db_review.to_dict(user_review[0]) - else: - user_review = None + top_reviews = [db_review.to_dict(review) for review in top_reviews] - ratings_stats, average_rating = db_rating_stats.get_stats(release_group_mbid, "release_group") + cache.set(top_reviews_cache_key, (top_reviews, reviews_count), + expirein=REVIEW_CACHE_TIMEOUT, namespace=REVIEW_CACHE_NAMESPACE) + + latest_reviews_cache_key = cache.gen_key("entity_api_release_group", release_group['mbid'], "latest_reviews") + latest_reviews_cached_result = cache.get(latest_reviews_cache_key, REVIEW_CACHE_NAMESPACE) + + if latest_reviews_cached_result: + latest_reviews, reviews_count = latest_reviews_cached_result + else: + latest_reviews, reviews_count = db_review.list_reviews( + entity_id=release_group['mbid'], + entity_type='release_group', + sort='published_on', + limit=REVIEWS_LIMIT, + offset=0, + ) + latest_reviews = [db_review.to_dict(review) for review in latest_reviews] - top_reviews, reviews_count = db_review.list_reviews( - entity_id=release_group['mbid'], - entity_type='release_group', - sort='popularity', - limit=REVIEWS_LIMIT, - offset=0, - ) - - latest_reviews, reviews_count = db_review.list_reviews( - entity_id=release_group['mbid'], - entity_type='release_group', - sort='published_on', - limit=REVIEWS_LIMIT, - offset=0, - ) - - top_reviews = [db_review.to_dict(review) for review in top_reviews] - latest_reviews = [db_review.to_dict(review) for review in latest_reviews] + cache.set(latest_reviews_cache_key, (latest_reviews, reviews_count), + expirein=REVIEW_CACHE_TIMEOUT, namespace=REVIEW_CACHE_NAMESPACE) result = { "release_group": release_group, diff --git a/critiquebrainz/ws/work/views.py b/critiquebrainz/ws/work/views.py index af9784b71..510a36e85 100644 --- a/critiquebrainz/ws/work/views.py +++ b/critiquebrainz/ws/work/views.py @@ -5,7 +5,9 @@ from critiquebrainz.decorators import crossdomain from critiquebrainz.ws.exceptions import NotFound from critiquebrainz.ws.parser import Parser -from critiquebrainz.ws import REVIEWS_LIMIT +from critiquebrainz.ws import REVIEWS_LIMIT, REVIEW_CACHE_NAMESPACE, REVIEW_CACHE_TIMEOUT +from brainzutils import cache + work_bp = Blueprint('ws_work', __name__) @@ -214,38 +216,63 @@ def work_entity_handler(work_mbid): if not work: raise NotFound("Can't find a work with ID: {work_mbid}".format(work_mbid=work_mbid)) + user_review = None + user_id = Parser.uuid('uri', 'user_id', optional=True) if user_id: - user_review, _ = db_review.list_reviews( + user_review_cache_key = cache.gen_key('entity_api', work['mbid'], user_id, "user_review") + user_review = cache.get(user_review_cache_key) + if not user_review: + user_review, _ = db_review.list_reviews( + entity_id=work['mbid'], + entity_type='work', + user_id=user_id + ) + if user_review: + user_review = db_review.to_dict(user_review[0]) + else: + user_review = None + + cache.set(user_review_cache_key, user_review, + expirein=REVIEW_CACHE_TIMEOUT, namespace=REVIEW_CACHE_NAMESPACE) + + ratings_stats, average_rating = db_rating_stats.get_stats(work_mbid, "work") + + top_reviews_cache_key = cache.gen_key("entity_api_work", work['mbid'], "top_reviews") + top_reviews_cached_result = cache.get(top_reviews_cache_key, REVIEW_CACHE_NAMESPACE) + + if top_reviews_cached_result: + top_reviews, reviews_count = top_reviews_cached_result + else: + top_reviews, reviews_count = db_review.list_reviews( entity_id=work['mbid'], entity_type='work', - user_id=user_id + sort='popularity', + limit=REVIEWS_LIMIT, + offset=0, ) - if user_review: - user_review = db_review.to_dict(user_review[0]) - else: - user_review = None + top_reviews = [db_review.to_dict(review) for review in top_reviews] - ratings_stats, average_rating = db_rating_stats.get_stats(work_mbid, "work") + cache.set(top_reviews_cache_key, (top_reviews, reviews_count), + expirein=REVIEW_CACHE_TIMEOUT, namespace=REVIEW_CACHE_NAMESPACE) - top_reviews, reviews_count = db_review.list_reviews( - entity_id=work['mbid'], - entity_type='work', - sort='popularity', - limit=REVIEWS_LIMIT, - offset=0, - ) + latest_reviews_cache_key = cache.gen_key("entity_api_work", work['mbid'], "latest_reviews") + latest_reviews_cached_result = cache.get(latest_reviews_cache_key, REVIEW_CACHE_NAMESPACE) - latest_reviews, reviews_count = db_review.list_reviews( - entity_id=work['mbid'], - entity_type='work', - sort='published_on', - limit=REVIEWS_LIMIT, - offset=0, - ) + if latest_reviews_cached_result: + latest_reviews, reviews_count = latest_reviews_cached_result + else: + latest_reviews, reviews_count = db_review.list_reviews( + entity_id=work['mbid'], + entity_type='work', + sort='published_on', + limit=REVIEWS_LIMIT, + offset=0, + ) + latest_reviews = [db_review.to_dict(review) for review in latest_reviews] - top_reviews = [db_review.to_dict(review) for review in top_reviews] - latest_reviews = [db_review.to_dict(review) for review in latest_reviews] + cache.set(latest_reviews_cache_key, (latest_reviews, reviews_count), + expirein=REVIEW_CACHE_TIMEOUT, namespace=REVIEW_CACHE_NAMESPACE) result = { "work": work, From 492021579bdaa4860e3716d7f7ff6f1276ea4ba9 Mon Sep 17 00:00:00 2001 From: Ansh Goyal Date: Fri, 9 Sep 2022 09:09:03 +0000 Subject: [PATCH 07/15] feat: Use username to get user's review --- critiquebrainz/db/review.py | 8 +++-- critiquebrainz/ws/artist/views.py | 39 +++++++++++++-------- critiquebrainz/ws/bb_author/views.py | 35 +++++++++++------- critiquebrainz/ws/bb_edition_group/views.py | 37 +++++++++++-------- critiquebrainz/ws/bb_literary_work/views.py | 37 +++++++++++-------- critiquebrainz/ws/bb_series/views.py | 36 ++++++++++++------- critiquebrainz/ws/event/views.py | 39 +++++++++++++-------- critiquebrainz/ws/label/views.py | 39 +++++++++++++-------- critiquebrainz/ws/place/views.py | 39 +++++++++++++-------- critiquebrainz/ws/recording/views.py | 39 +++++++++++++-------- critiquebrainz/ws/release_group/views.py | 39 +++++++++++++-------- critiquebrainz/ws/work/views.py | 39 +++++++++++++-------- 12 files changed, 265 insertions(+), 161 deletions(-) diff --git a/critiquebrainz/db/review.py b/critiquebrainz/db/review.py index ac9b45810..b7f8805bc 100644 --- a/critiquebrainz/db/review.py +++ b/critiquebrainz/db/review.py @@ -406,10 +406,14 @@ def invalidate_ws_entity_cache(entity_id, entity_type, user_id): cache_keys_for_top_reviews_key = cache.gen_key('entity_api', entity_type, entity_id, "top_reviews") cache_keys_for_latest_reviews_key = cache.gen_key('entity_api', entity_type, entity_id, "latest_reviews") - cache_keys_for_user_reviews_key = cache.gen_key('entity_api', entity_id, user_id, "user_reviews") cache.delete(cache_keys_for_top_reviews_key, namespace=REVIEW_CACHE_NAMESPACE) cache.delete(cache_keys_for_latest_reviews_key, namespace=REVIEW_CACHE_NAMESPACE) - cache.delete(cache_keys_for_user_reviews_key, namespace=REVIEW_CACHE_NAMESPACE) + + user = db_users.get_by_id(user_id) + username = user["musicbrainz_username"] + if username: + cache_keys_for_user_reviews_key = cache.gen_key('entity_api', entity_id, username, "user_reviews") + cache.delete(cache_keys_for_user_reviews_key, namespace=REVIEW_CACHE_NAMESPACE) # pylint: disable=too-many-branches diff --git a/critiquebrainz/ws/artist/views.py b/critiquebrainz/ws/artist/views.py index 8197389d5..44c3233be 100644 --- a/critiquebrainz/ws/artist/views.py +++ b/critiquebrainz/ws/artist/views.py @@ -1,5 +1,6 @@ from flask import Blueprint, jsonify import critiquebrainz.db.review as db_review +import critiquebrainz.db.users as db_users import critiquebrainz.db.rating_stats as db_rating_stats from critiquebrainz.frontend.external.musicbrainz_db import artist as db_artist from critiquebrainz.decorators import crossdomain @@ -223,6 +224,8 @@ def artist_entity_handler(artist_mbid): :statuscode 200: no error :statuscode 404: artist not found + :query username: User's username **(optional)** + :resheader Content-Type: *application/json* """ @@ -232,23 +235,28 @@ def artist_entity_handler(artist_mbid): user_review = None - user_id = Parser.uuid('uri', 'user_id', optional=True) - if user_id: - user_review_cache_key = cache.gen_key('entity_api', artist['mbid'], user_id, "user_review") + username = Parser.string('uri', 'username', optional=True) + if username: + user_review_cache_key = cache.gen_key('entity_api', artist['mbid'], username, "user_review") user_review = cache.get(user_review_cache_key) if not user_review: - user_review, _ = db_review.list_reviews( - entity_id=artist['mbid'], - entity_type='artist', - user_id=user_id - ) - if user_review: - user_review = db_review.to_dict(user_review[0]) - else: - user_review = None + user = db_users.get_by_mbid(username) + if user: + user_id = user['id'] + + user_review, _ = db_review.list_reviews( + entity_id=artist['mbid'], + entity_type='artist', + user_id=user_id + ) + if user_review: + user_review = db_review.to_dict(user_review[0]) - cache.set(user_review_cache_key, user_review, - expirein=REVIEW_CACHE_TIMEOUT, namespace=REVIEW_CACHE_NAMESPACE) + cache.set(user_review_cache_key, user_review, + expirein=REVIEW_CACHE_TIMEOUT, namespace=REVIEW_CACHE_NAMESPACE) + + else: + user_review = [] ratings_stats, average_rating = db_rating_stats.get_stats(artist_mbid, "artist") @@ -296,7 +304,8 @@ def artist_entity_handler(artist_mbid): "top_reviews": top_reviews, "latest_reviews": latest_reviews } - if user_id: + + if username: result['user_review'] = user_review return jsonify(**result) diff --git a/critiquebrainz/ws/bb_author/views.py b/critiquebrainz/ws/bb_author/views.py index 26dda70cf..43c088c3a 100644 --- a/critiquebrainz/ws/bb_author/views.py +++ b/critiquebrainz/ws/bb_author/views.py @@ -1,5 +1,6 @@ from flask import Blueprint, jsonify import critiquebrainz.db.review as db_review +import critiquebrainz.db.users as db_users import critiquebrainz.db.rating_stats as db_rating_stats from critiquebrainz.frontend.external.bookbrainz_db import author as db_author from critiquebrainz.decorators import crossdomain @@ -159,6 +160,8 @@ def author_entity_handler(author_bbid): :statuscode 200: no error :statuscode 404: author not found + :query username: User's username **(optional)** + :resheader Content-Type: *application/json* """ @@ -169,23 +172,29 @@ def author_entity_handler(author_bbid): user_review = [] - user_id = Parser.uuid('uri', 'user_id', optional=True) - if user_id: - user_review_cache_key = cache.gen_key('entity_api', author['bbid'], user_id, "user_review") + username = Parser.string('uri', 'username', optional=True) + if username: + user_review_cache_key = cache.gen_key('entity_api', author['bbid'], username, "user_review") user_review = cache.get(user_review_cache_key) if not user_review: - user_review, _ = db_review.list_reviews( - entity_id=author['bbid'], - entity_type='bb_author', - user_id=user_id - ) - if user_review: - user_review = db_review.to_dict(user_review[0]) + user = db_users.get_by_mbid(username) + if user: + user_id = user['id'] + + user_review, _ = db_review.list_reviews( + entity_id=author['bbid'], + entity_type='bb_author', + user_id=user_id + ) + if user_review: + user_review = db_review.to_dict(user_review[0]) + + cache.set(user_review_cache_key, user_review, + expirein=REVIEW_CACHE_TIMEOUT, namespace=REVIEW_CACHE_NAMESPACE) + else: user_review = [] - cache.set(user_review_cache_key, user_review, - expirein=REVIEW_CACHE_TIMEOUT, namespace=REVIEW_CACHE_NAMESPACE) ratings_stats, average_rating = db_rating_stats.get_stats(author_bbid, "bb_author") @@ -233,7 +242,7 @@ def author_entity_handler(author_bbid): "top_reviews": top_reviews, "latest_reviews": latest_reviews } - if user_id: + if username: result['user_review'] = user_review return jsonify(**result) diff --git a/critiquebrainz/ws/bb_edition_group/views.py b/critiquebrainz/ws/bb_edition_group/views.py index ef12b15cd..64bc8a77a 100644 --- a/critiquebrainz/ws/bb_edition_group/views.py +++ b/critiquebrainz/ws/bb_edition_group/views.py @@ -1,5 +1,6 @@ from flask import Blueprint, jsonify import critiquebrainz.db.review as db_review +import critiquebrainz.db.users as db_users import critiquebrainz.db.rating_stats as db_rating_stats from critiquebrainz.frontend.external.bookbrainz_db import edition_group as db_edition_group from critiquebrainz.decorators import crossdomain @@ -137,6 +138,8 @@ def edition_group_entity_handler(edition_group_bbid): :statuscode 200: no error :statuscode 404: edition group not found + :query username: User's username **(optional)** + :resheader Content-Type: *application/json* """ @@ -147,24 +150,29 @@ def edition_group_entity_handler(edition_group_bbid): user_review = [] - user_id = Parser.uuid('uri', 'user_id', optional=True) - if user_id: - user_review_cache_key = cache.gen_key('entity_api', edition_group['bbid'], user_id, "user_review") + username = Parser.string('uri', 'username', optional=True) + if username: + user_review_cache_key = cache.gen_key('entity_api', edition_group['bbid'], username, "user_review") user_review = cache.get(user_review_cache_key) if not user_review: - user_review, _ = db_review.list_reviews( - entity_id=edition_group['bbid'], - entity_type='bb_edition_group', - user_id=user_id - ) - if user_review: - user_review = db_review.to_dict(user_review[0]) + user = db_users.get_by_mbid(username) + if user: + user_id = user['id'] + + user_review, _ = db_review.list_reviews( + entity_id=edition_group['bbid'], + entity_type='bb_edition_group', + user_id=user_id + ) + if user_review: + user_review = db_review.to_dict(user_review[0]) + + cache.set(user_review_cache_key, user_review, + expirein=REVIEW_CACHE_TIMEOUT, namespace=REVIEW_CACHE_NAMESPACE) + else: user_review = [] - cache.set(user_review_cache_key, user_review, - expirein=REVIEW_CACHE_TIMEOUT, namespace=REVIEW_CACHE_NAMESPACE) - ratings_stats, average_rating = db_rating_stats.get_stats(edition_group_bbid, "bb_edition_group") top_reviews_cache_key = cache.gen_key("entity_api_bb_edition_group", edition_group['bbid'], "top_reviews") @@ -211,7 +219,8 @@ def edition_group_entity_handler(edition_group_bbid): "top_reviews": top_reviews, "latest_reviews": latest_reviews } - if user_id: + + if username: result['user_review'] = user_review return jsonify(**result) diff --git a/critiquebrainz/ws/bb_literary_work/views.py b/critiquebrainz/ws/bb_literary_work/views.py index ba0c5991b..2ea81848f 100644 --- a/critiquebrainz/ws/bb_literary_work/views.py +++ b/critiquebrainz/ws/bb_literary_work/views.py @@ -1,5 +1,6 @@ from flask import Blueprint, jsonify import critiquebrainz.db.review as db_review +import critiquebrainz.db.users as db_users import critiquebrainz.db.rating_stats as db_rating_stats from critiquebrainz.frontend.external.bookbrainz_db import literary_work as db_literary_work from critiquebrainz.decorators import crossdomain @@ -157,6 +158,8 @@ def literary_work_entity_handler(literary_work_bbid): :statuscode 200: no error :statuscode 404: literary work not found + :query username: User's username **(optional)** + :resheader Content-Type: *application/json* """ @@ -167,24 +170,29 @@ def literary_work_entity_handler(literary_work_bbid): user_review = [] - user_id = Parser.uuid('uri', 'user_id', optional=True) - if user_id: - user_review_cache_key = cache.gen_key('entity_api', literary_work['bbid'], user_id, "user_review") + username = Parser.string('uri', 'username', optional=True) + if username: + user_review_cache_key = cache.gen_key('entity_api', literary_work['bbid'], username, "user_review") user_review = cache.get(user_review_cache_key) if not user_review: - user_review, _ = db_review.list_reviews( - entity_id=literary_work['bbid'], - entity_type='bb_literary_work', - user_id=user_id - ) - if user_review: - user_review = db_review.to_dict(user_review[0]) + user = db_users.get_by_mbid(username) + if user: + user_id = user['id'] + + user_review, _ = db_review.list_reviews( + entity_id=literary_work['bbid'], + entity_type='bb_literary_work', + user_id=user_id + ) + if user_review: + user_review = db_review.to_dict(user_review[0]) + + cache.set(user_review_cache_key, user_review, + expirein=REVIEW_CACHE_TIMEOUT, namespace=REVIEW_CACHE_NAMESPACE) + else: user_review = [] - cache.set(user_review_cache_key, user_review, - expirein=REVIEW_CACHE_TIMEOUT, namespace=REVIEW_CACHE_NAMESPACE) - ratings_stats, average_rating = db_rating_stats.get_stats(literary_work_bbid, "bb_literary_work") top_reviews_cache_key = cache.gen_key("entity_api_bb_literary_work", literary_work['bbid'], "top_reviews") @@ -231,7 +239,8 @@ def literary_work_entity_handler(literary_work_bbid): "top_reviews": top_reviews, "latest_reviews": latest_reviews } - if user_id: + + if username: result['user_review'] = user_review return jsonify(**result) diff --git a/critiquebrainz/ws/bb_series/views.py b/critiquebrainz/ws/bb_series/views.py index 0c20a40c1..807e6f277 100644 --- a/critiquebrainz/ws/bb_series/views.py +++ b/critiquebrainz/ws/bb_series/views.py @@ -1,5 +1,6 @@ from flask import Blueprint, jsonify import critiquebrainz.db.review as db_review +import critiquebrainz.db.users as db_users import critiquebrainz.db.rating_stats as db_rating_stats from critiquebrainz.frontend.external.bookbrainz_db import series as db_series from critiquebrainz.decorators import crossdomain @@ -129,6 +130,8 @@ def series_entity_handler(series_bbid): :statuscode 200: no error :statuscode 404: series not found + :query username: User's username **(optional)** + :resheader Content-Type: *application/json* """ @@ -139,23 +142,29 @@ def series_entity_handler(series_bbid): user_review = [] - user_id = Parser.uuid('uri', 'user_id', optional=True) - if user_id: - user_review_cache_key = cache.gen_key('entity_api', series['bbid'], user_id, "user_review") + username = Parser.string('uri', 'username', optional=True) + if username: + user_review_cache_key = cache.gen_key('entity_api', series['bbid'], username, "user_review") user_review = cache.get(user_review_cache_key) if not user_review: - user_review, _ = db_review.list_reviews( - entity_id=series['bbid'], - entity_type='bb_series', - user_id=user_id - ) - if user_review: - user_review = db_review.to_dict(user_review[0]) + user = db_users.get_by_mbid(username) + if user: + user_id = user['id'] + + user_review, _ = db_review.list_reviews( + entity_id=series['bbid'], + entity_type='bb_series', + user_id=user_id + ) + if user_review: + user_review = db_review.to_dict(user_review[0]) + + cache.set(user_review_cache_key, user_review, + expirein=REVIEW_CACHE_TIMEOUT, namespace=REVIEW_CACHE_NAMESPACE) + else: user_review = [] - cache.set(user_review_cache_key, user_review, - expirein=REVIEW_CACHE_TIMEOUT, namespace=REVIEW_CACHE_NAMESPACE) ratings_stats, average_rating = db_rating_stats.get_stats(series_bbid, "bb_series") @@ -203,7 +212,8 @@ def series_entity_handler(series_bbid): "top_reviews": top_reviews, "latest_reviews": latest_reviews } - if user_id: + + if username: result['user_review'] = user_review return jsonify(**result) diff --git a/critiquebrainz/ws/event/views.py b/critiquebrainz/ws/event/views.py index 6e199dacd..e0a304b50 100644 --- a/critiquebrainz/ws/event/views.py +++ b/critiquebrainz/ws/event/views.py @@ -1,5 +1,6 @@ from flask import Blueprint, jsonify import critiquebrainz.db.review as db_review +import critiquebrainz.db.users as db_users import critiquebrainz.db.rating_stats as db_rating_stats from critiquebrainz.frontend.external.musicbrainz_db import event as db_event from critiquebrainz.decorators import crossdomain @@ -249,6 +250,8 @@ def event_entity_handler(event_mbid): :statuscode 200: no error :statuscode 404: label not found + :query username: User's username **(optional)** + :resheader Content-Type: *application/json* """ @@ -259,23 +262,28 @@ def event_entity_handler(event_mbid): user_review = None - user_id = Parser.uuid('uri', 'user_id', optional=True) - if user_id: - user_review_cache_key = cache.gen_key('entity_api', event['mbid'], user_id, "user_review") + username = Parser.string('uri', 'username', optional=True) + if username: + user_review_cache_key = cache.gen_key('entity_api', event['mbid'], username, "user_review") user_review = cache.get(user_review_cache_key) if not user_review: - user_review, _ = db_review.list_reviews( - entity_id=event['mbid'], - entity_type='event', - user_id=user_id - ) - if user_review: - user_review = db_review.to_dict(user_review[0]) - else: - user_review = None + user = db_users.get_by_mbid(username) + if user: + user_id = user['id'] + + user_review, _ = db_review.list_reviews( + entity_id=event['mbid'], + entity_type='event', + user_id=user_id + ) + if user_review: + user_review = db_review.to_dict(user_review[0]) - cache.set(user_review_cache_key, user_review, - expirein=REVIEW_CACHE_TIMEOUT, namespace=REVIEW_CACHE_NAMESPACE) + cache.set(user_review_cache_key, user_review, + expirein=REVIEW_CACHE_TIMEOUT, namespace=REVIEW_CACHE_NAMESPACE) + + else: + user_review = [] ratings_stats, average_rating = db_rating_stats.get_stats(event_mbid, "event") @@ -323,7 +331,8 @@ def event_entity_handler(event_mbid): "top_reviews": top_reviews, "latest_reviews": latest_reviews } - if user_id: + + if username: result['user_review'] = user_review return jsonify(**result) diff --git a/critiquebrainz/ws/label/views.py b/critiquebrainz/ws/label/views.py index 95dd6cf1e..e91737828 100644 --- a/critiquebrainz/ws/label/views.py +++ b/critiquebrainz/ws/label/views.py @@ -1,5 +1,6 @@ from flask import Blueprint, jsonify import critiquebrainz.db.review as db_review +import critiquebrainz.db.users as db_users import critiquebrainz.db.rating_stats as db_rating_stats from critiquebrainz.frontend.external.musicbrainz_db import label as db_label from critiquebrainz.decorators import crossdomain @@ -164,6 +165,8 @@ def label_entity_handler(label_mbid): :statuscode 200: no error :statuscode 404: label not found + :query username: User's username **(optional)** + :resheader Content-Type: *application/json* """ @@ -174,23 +177,28 @@ def label_entity_handler(label_mbid): user_review = None - user_id = Parser.uuid('uri', 'user_id', optional=True) - if user_id: - user_review_cache_key = cache.gen_key('entity_api', label['mbid'], user_id, "user_review") + username = Parser.string('uri', 'username', optional=True) + if username: + user_review_cache_key = cache.gen_key('entity_api', label['mbid'], username, "user_review") user_review = cache.get(user_review_cache_key) if not user_review: - user_review, _ = db_review.list_reviews( - entity_id=label['mbid'], - entity_type='label', - user_id=user_id - ) - if user_review: - user_review = db_review.to_dict(user_review[0]) - else: - user_review = None + user = db_users.get_by_mbid(username) + if user: + user_id = user['id'] + + user_review, _ = db_review.list_reviews( + entity_id=label['mbid'], + entity_type='label', + user_id=user_id + ) + if user_review: + user_review = db_review.to_dict(user_review[0]) - cache.set(user_review_cache_key, user_review, - expirein=REVIEW_CACHE_TIMEOUT, namespace=REVIEW_CACHE_NAMESPACE) + cache.set(user_review_cache_key, user_review, + expirein=REVIEW_CACHE_TIMEOUT, namespace=REVIEW_CACHE_NAMESPACE) + + else: + user_review = [] ratings_stats, average_rating = db_rating_stats.get_stats(label_mbid, "label") @@ -238,7 +246,8 @@ def label_entity_handler(label_mbid): "top_reviews": top_reviews, "latest_reviews": latest_reviews } - if user_id: + + if username: result['user_review'] = user_review return jsonify(**result) diff --git a/critiquebrainz/ws/place/views.py b/critiquebrainz/ws/place/views.py index ee6d9f909..45f4f2e82 100644 --- a/critiquebrainz/ws/place/views.py +++ b/critiquebrainz/ws/place/views.py @@ -1,5 +1,6 @@ from flask import Blueprint, jsonify import critiquebrainz.db.review as db_review +import critiquebrainz.db.users as db_users import critiquebrainz.db.rating_stats as db_rating_stats from critiquebrainz.frontend.external.musicbrainz_db import place as db_place from critiquebrainz.decorators import crossdomain @@ -318,6 +319,8 @@ def place_entity_handler(place_mbid): :statuscode 200: no error :statuscode 404: place not found + :query username: User's username **(optional)** + :resheader Content-Type: *application/json* """ @@ -328,23 +331,28 @@ def place_entity_handler(place_mbid): user_review = None - user_id = Parser.uuid('uri', 'user_id', optional=True) - if user_id: - user_review_cache_key = cache.gen_key('entity_api', place['mbid'], user_id, "user_review") + username = Parser.string('uri', 'username', optional=True) + if username: + user_review_cache_key = cache.gen_key('entity_api', place['mbid'], username, "user_review") user_review = cache.get(user_review_cache_key) if not user_review: - user_review, _ = db_review.list_reviews( - entity_id=place['mbid'], - entity_type='place', - user_id=user_id - ) - if user_review: - user_review = db_review.to_dict(user_review[0]) - else: - user_review = None + user = db_users.get_by_mbid(username) + if user: + user_id = user['id'] + + user_review, _ = db_review.list_reviews( + entity_id=place['mbid'], + entity_type='place', + user_id=user_id + ) + if user_review: + user_review = db_review.to_dict(user_review[0]) - cache.set(user_review_cache_key, user_review, - expirein=REVIEW_CACHE_TIMEOUT, namespace=REVIEW_CACHE_NAMESPACE) + cache.set(user_review_cache_key, user_review, + expirein=REVIEW_CACHE_TIMEOUT, namespace=REVIEW_CACHE_NAMESPACE) + + else: + user_review = [] ratings_stats, average_rating = db_rating_stats.get_stats(place_mbid, "place") @@ -392,7 +400,8 @@ def place_entity_handler(place_mbid): "top_reviews": top_reviews, "latest_reviews": latest_reviews } - if user_id: + + if username: result['user_review'] = user_review return jsonify(**result) diff --git a/critiquebrainz/ws/recording/views.py b/critiquebrainz/ws/recording/views.py index eb2865deb..5e0924211 100644 --- a/critiquebrainz/ws/recording/views.py +++ b/critiquebrainz/ws/recording/views.py @@ -1,5 +1,6 @@ from flask import Blueprint, jsonify import critiquebrainz.db.review as db_review +import critiquebrainz.db.users as db_users import critiquebrainz.db.rating_stats as db_rating_stats from critiquebrainz.frontend.external.musicbrainz_db import recording as db_recording from critiquebrainz.decorators import crossdomain @@ -132,6 +133,8 @@ def recording_entity_handler(recording_mbid): :statuscode 200: no error :statuscode 404: recording not found + :query username: User's username **(optional)** + :resheader Content-Type: *application/json* """ @@ -142,23 +145,28 @@ def recording_entity_handler(recording_mbid): user_review = None - user_id = Parser.uuid('uri', 'user_id', optional=True) - if user_id: - user_review_cache_key = cache.gen_key('entity_api', recording['mbid'], user_id, "user_review") + username = Parser.string('uri', 'username', optional=True) + if username: + user_review_cache_key = cache.gen_key('entity_api', recording['mbid'], username, "user_review") user_review = cache.get(user_review_cache_key) if not user_review: - user_review, _ = db_review.list_reviews( - entity_id=recording['mbid'], - entity_type='recording', - user_id=user_id - ) - if user_review: - user_review = db_review.to_dict(user_review[0]) - else: - user_review = None + user = db_users.get_by_mbid(username) + if user: + user_id = user['id'] + + user_review, _ = db_review.list_reviews( + entity_id=recording['mbid'], + entity_type='label', + user_id=user_id + ) + if user_review: + user_review = db_review.to_dict(user_review[0]) - cache.set(user_review_cache_key, user_review, - expirein=REVIEW_CACHE_TIMEOUT, namespace=REVIEW_CACHE_NAMESPACE) + cache.set(user_review_cache_key, user_review, + expirein=REVIEW_CACHE_TIMEOUT, namespace=REVIEW_CACHE_NAMESPACE) + + else: + user_review = [] ratings_stats, average_rating = db_rating_stats.get_stats(recording_mbid, "recording") @@ -206,7 +214,8 @@ def recording_entity_handler(recording_mbid): "top_reviews": top_reviews, "latest_reviews": latest_reviews } - if user_id: + + if username: result['user_review'] = user_review return jsonify(**result) diff --git a/critiquebrainz/ws/release_group/views.py b/critiquebrainz/ws/release_group/views.py index 062e38b9c..84f1e3168 100644 --- a/critiquebrainz/ws/release_group/views.py +++ b/critiquebrainz/ws/release_group/views.py @@ -1,5 +1,6 @@ from flask import Blueprint, jsonify import critiquebrainz.db.review as db_review +import critiquebrainz.db.users as db_users import critiquebrainz.db.rating_stats as db_rating_stats from critiquebrainz.frontend.external.musicbrainz_db import release_group as db_release_group from critiquebrainz.decorators import crossdomain @@ -145,6 +146,8 @@ def release_group_entity_handler(release_group_mbid): :statuscode 200: no error :statuscode 404: release group not found + :query username: User's username **(optional)** + :resheader Content-Type: *application/json* """ @@ -155,23 +158,28 @@ def release_group_entity_handler(release_group_mbid): user_review = None - user_id = Parser.uuid('uri', 'user_id', optional=True) - if user_id: - user_review_cache_key = cache.gen_key('entity_api', release_group['mbid'], user_id, "user_review") + username = Parser.string('uri', 'username', optional=True) + if username: + user_review_cache_key = cache.gen_key('entity_api', release_group['mbid'], username, "user_review") user_review = cache.get(user_review_cache_key) if not user_review: - user_review, _ = db_review.list_reviews( - entity_id=release_group['mbid'], - entity_type='release_group', - user_id=user_id - ) - if user_review: - user_review = db_review.to_dict(user_review[0]) - else: - user_review = None + user = db_users.get_by_mbid(username) + if user: + user_id = user['id'] + + user_review, _ = db_review.list_reviews( + entity_id=release_group['mbid'], + entity_type='release_group', + user_id=user_id + ) + if user_review: + user_review = db_review.to_dict(user_review[0]) - cache.set(user_review_cache_key, user_review, - expirein=REVIEW_CACHE_TIMEOUT, namespace=REVIEW_CACHE_NAMESPACE) + cache.set(user_review_cache_key, user_review, + expirein=REVIEW_CACHE_TIMEOUT, namespace=REVIEW_CACHE_NAMESPACE) + + else: + user_review = [] ratings_stats, average_rating = db_rating_stats.get_stats(release_group_mbid, "release_group") @@ -219,7 +227,8 @@ def release_group_entity_handler(release_group_mbid): "top_reviews": top_reviews, "latest_reviews": latest_reviews } - if user_id: + + if username: result['user_review'] = user_review return jsonify(**result) diff --git a/critiquebrainz/ws/work/views.py b/critiquebrainz/ws/work/views.py index 510a36e85..a1a9bb0d6 100644 --- a/critiquebrainz/ws/work/views.py +++ b/critiquebrainz/ws/work/views.py @@ -1,5 +1,6 @@ from flask import Blueprint, jsonify import critiquebrainz.db.review as db_review +import critiquebrainz.db.users as db_users import critiquebrainz.db.rating_stats as db_rating_stats from critiquebrainz.frontend.external.musicbrainz_db import work as db_work from critiquebrainz.decorators import crossdomain @@ -208,6 +209,8 @@ def work_entity_handler(work_mbid): :statuscode 200: no error :statuscode 404: work not found + :query username: User's username **(optional)** + :resheader Content-Type: *application/json* """ @@ -218,23 +221,28 @@ def work_entity_handler(work_mbid): user_review = None - user_id = Parser.uuid('uri', 'user_id', optional=True) - if user_id: - user_review_cache_key = cache.gen_key('entity_api', work['mbid'], user_id, "user_review") + username = Parser.string('uri', 'username', optional=True) + if username: + user_review_cache_key = cache.gen_key('entity_api', work['mbid'], username, "user_review") user_review = cache.get(user_review_cache_key) if not user_review: - user_review, _ = db_review.list_reviews( - entity_id=work['mbid'], - entity_type='work', - user_id=user_id - ) - if user_review: - user_review = db_review.to_dict(user_review[0]) - else: - user_review = None + user = db_users.get_by_mbid(username) + if user: + user_id = user['id'] + + user_review, _ = db_review.list_reviews( + entity_id=work['mbid'], + entity_type='work', + user_id=user_id + ) + if user_review: + user_review = db_review.to_dict(user_review[0]) - cache.set(user_review_cache_key, user_review, - expirein=REVIEW_CACHE_TIMEOUT, namespace=REVIEW_CACHE_NAMESPACE) + cache.set(user_review_cache_key, user_review, + expirein=REVIEW_CACHE_TIMEOUT, namespace=REVIEW_CACHE_NAMESPACE) + + else: + user_review = [] ratings_stats, average_rating = db_rating_stats.get_stats(work_mbid, "work") @@ -282,7 +290,8 @@ def work_entity_handler(work_mbid): "top_reviews": top_reviews, "latest_reviews": latest_reviews } - if user_id: + + if username: result['user_review'] = user_review return jsonify(**result) From e3f1f82fe8068c1e0da42fb01a19b2ab36279a42 Mon Sep 17 00:00:00 2001 From: Ansh Goyal Date: Sat, 17 Sep 2022 06:49:56 +0000 Subject: [PATCH 08/15] feat: Use Reviews namespace --- critiquebrainz/db/review.py | 6 +++--- critiquebrainz/ws/artist/views.py | 2 +- critiquebrainz/ws/bb_author/views.py | 2 +- critiquebrainz/ws/bb_edition_group/views.py | 2 +- critiquebrainz/ws/bb_literary_work/views.py | 2 +- critiquebrainz/ws/bb_series/views.py | 2 +- critiquebrainz/ws/event/views.py | 2 +- critiquebrainz/ws/label/views.py | 2 +- critiquebrainz/ws/place/views.py | 2 +- critiquebrainz/ws/recording/views.py | 4 ++-- critiquebrainz/ws/release_group/views.py | 2 +- critiquebrainz/ws/work/views.py | 2 +- 12 files changed, 15 insertions(+), 15 deletions(-) diff --git a/critiquebrainz/db/review.py b/critiquebrainz/db/review.py index b7f8805bc..2723bf372 100644 --- a/critiquebrainz/db/review.py +++ b/critiquebrainz/db/review.py @@ -410,9 +410,9 @@ def invalidate_ws_entity_cache(entity_id, entity_type, user_id): cache.delete(cache_keys_for_latest_reviews_key, namespace=REVIEW_CACHE_NAMESPACE) user = db_users.get_by_id(user_id) - username = user["musicbrainz_username"] - if username: - cache_keys_for_user_reviews_key = cache.gen_key('entity_api', entity_id, username, "user_reviews") + if 'musicbrainz_username' in user.keys() and user['musicbrainz_username']: + username = user["musicbrainz_username"] + cache_keys_for_user_reviews_key = cache.gen_key('entity_api', entity_id, username, "user_review") cache.delete(cache_keys_for_user_reviews_key, namespace=REVIEW_CACHE_NAMESPACE) diff --git a/critiquebrainz/ws/artist/views.py b/critiquebrainz/ws/artist/views.py index 44c3233be..a36ed7354 100644 --- a/critiquebrainz/ws/artist/views.py +++ b/critiquebrainz/ws/artist/views.py @@ -238,7 +238,7 @@ def artist_entity_handler(artist_mbid): username = Parser.string('uri', 'username', optional=True) if username: user_review_cache_key = cache.gen_key('entity_api', artist['mbid'], username, "user_review") - user_review = cache.get(user_review_cache_key) + user_review = cache.get(user_review_cache_key, REVIEW_CACHE_NAMESPACE) if not user_review: user = db_users.get_by_mbid(username) if user: diff --git a/critiquebrainz/ws/bb_author/views.py b/critiquebrainz/ws/bb_author/views.py index 43c088c3a..0bdf6ae8f 100644 --- a/critiquebrainz/ws/bb_author/views.py +++ b/critiquebrainz/ws/bb_author/views.py @@ -175,7 +175,7 @@ def author_entity_handler(author_bbid): username = Parser.string('uri', 'username', optional=True) if username: user_review_cache_key = cache.gen_key('entity_api', author['bbid'], username, "user_review") - user_review = cache.get(user_review_cache_key) + user_review = cache.get(user_review_cache_key, namespace=REVIEW_CACHE_NAMESPACE) if not user_review: user = db_users.get_by_mbid(username) if user: diff --git a/critiquebrainz/ws/bb_edition_group/views.py b/critiquebrainz/ws/bb_edition_group/views.py index 64bc8a77a..65c7653c6 100644 --- a/critiquebrainz/ws/bb_edition_group/views.py +++ b/critiquebrainz/ws/bb_edition_group/views.py @@ -153,7 +153,7 @@ def edition_group_entity_handler(edition_group_bbid): username = Parser.string('uri', 'username', optional=True) if username: user_review_cache_key = cache.gen_key('entity_api', edition_group['bbid'], username, "user_review") - user_review = cache.get(user_review_cache_key) + user_review = cache.get(user_review_cache_key, REVIEW_CACHE_NAMESPACE) if not user_review: user = db_users.get_by_mbid(username) if user: diff --git a/critiquebrainz/ws/bb_literary_work/views.py b/critiquebrainz/ws/bb_literary_work/views.py index 2ea81848f..e5037e125 100644 --- a/critiquebrainz/ws/bb_literary_work/views.py +++ b/critiquebrainz/ws/bb_literary_work/views.py @@ -173,7 +173,7 @@ def literary_work_entity_handler(literary_work_bbid): username = Parser.string('uri', 'username', optional=True) if username: user_review_cache_key = cache.gen_key('entity_api', literary_work['bbid'], username, "user_review") - user_review = cache.get(user_review_cache_key) + user_review = cache.get(user_review_cache_key, REVIEW_CACHE_NAMESPACE) if not user_review: user = db_users.get_by_mbid(username) if user: diff --git a/critiquebrainz/ws/bb_series/views.py b/critiquebrainz/ws/bb_series/views.py index 807e6f277..2ffcacf73 100644 --- a/critiquebrainz/ws/bb_series/views.py +++ b/critiquebrainz/ws/bb_series/views.py @@ -145,7 +145,7 @@ def series_entity_handler(series_bbid): username = Parser.string('uri', 'username', optional=True) if username: user_review_cache_key = cache.gen_key('entity_api', series['bbid'], username, "user_review") - user_review = cache.get(user_review_cache_key) + user_review = cache.get(user_review_cache_key, REVIEW_CACHE_NAMESPACE) if not user_review: user = db_users.get_by_mbid(username) if user: diff --git a/critiquebrainz/ws/event/views.py b/critiquebrainz/ws/event/views.py index e0a304b50..75d4cd1e2 100644 --- a/critiquebrainz/ws/event/views.py +++ b/critiquebrainz/ws/event/views.py @@ -265,7 +265,7 @@ def event_entity_handler(event_mbid): username = Parser.string('uri', 'username', optional=True) if username: user_review_cache_key = cache.gen_key('entity_api', event['mbid'], username, "user_review") - user_review = cache.get(user_review_cache_key) + user_review = cache.get(user_review_cache_key, REVIEW_CACHE_NAMESPACE) if not user_review: user = db_users.get_by_mbid(username) if user: diff --git a/critiquebrainz/ws/label/views.py b/critiquebrainz/ws/label/views.py index e91737828..03c3b3d9b 100644 --- a/critiquebrainz/ws/label/views.py +++ b/critiquebrainz/ws/label/views.py @@ -180,7 +180,7 @@ def label_entity_handler(label_mbid): username = Parser.string('uri', 'username', optional=True) if username: user_review_cache_key = cache.gen_key('entity_api', label['mbid'], username, "user_review") - user_review = cache.get(user_review_cache_key) + user_review = cache.get(user_review_cache_key, REVIEW_CACHE_NAMESPACE) if not user_review: user = db_users.get_by_mbid(username) if user: diff --git a/critiquebrainz/ws/place/views.py b/critiquebrainz/ws/place/views.py index 45f4f2e82..bd0bd5b50 100644 --- a/critiquebrainz/ws/place/views.py +++ b/critiquebrainz/ws/place/views.py @@ -334,7 +334,7 @@ def place_entity_handler(place_mbid): username = Parser.string('uri', 'username', optional=True) if username: user_review_cache_key = cache.gen_key('entity_api', place['mbid'], username, "user_review") - user_review = cache.get(user_review_cache_key) + user_review = cache.get(user_review_cache_key, REVIEW_CACHE_NAMESPACE) if not user_review: user = db_users.get_by_mbid(username) if user: diff --git a/critiquebrainz/ws/recording/views.py b/critiquebrainz/ws/recording/views.py index 5e0924211..9964eb013 100644 --- a/critiquebrainz/ws/recording/views.py +++ b/critiquebrainz/ws/recording/views.py @@ -148,7 +148,7 @@ def recording_entity_handler(recording_mbid): username = Parser.string('uri', 'username', optional=True) if username: user_review_cache_key = cache.gen_key('entity_api', recording['mbid'], username, "user_review") - user_review = cache.get(user_review_cache_key) + user_review = cache.get(user_review_cache_key, REVIEW_CACHE_NAMESPACE) if not user_review: user = db_users.get_by_mbid(username) if user: @@ -156,7 +156,7 @@ def recording_entity_handler(recording_mbid): user_review, _ = db_review.list_reviews( entity_id=recording['mbid'], - entity_type='label', + entity_type='recording', user_id=user_id ) if user_review: diff --git a/critiquebrainz/ws/release_group/views.py b/critiquebrainz/ws/release_group/views.py index 84f1e3168..cc9fc63cc 100644 --- a/critiquebrainz/ws/release_group/views.py +++ b/critiquebrainz/ws/release_group/views.py @@ -161,7 +161,7 @@ def release_group_entity_handler(release_group_mbid): username = Parser.string('uri', 'username', optional=True) if username: user_review_cache_key = cache.gen_key('entity_api', release_group['mbid'], username, "user_review") - user_review = cache.get(user_review_cache_key) + user_review = cache.get(user_review_cache_key, REVIEW_CACHE_NAMESPACE) if not user_review: user = db_users.get_by_mbid(username) if user: diff --git a/critiquebrainz/ws/work/views.py b/critiquebrainz/ws/work/views.py index a1a9bb0d6..3f4cd8345 100644 --- a/critiquebrainz/ws/work/views.py +++ b/critiquebrainz/ws/work/views.py @@ -224,7 +224,7 @@ def work_entity_handler(work_mbid): username = Parser.string('uri', 'username', optional=True) if username: user_review_cache_key = cache.gen_key('entity_api', work['mbid'], username, "user_review") - user_review = cache.get(user_review_cache_key) + user_review = cache.get(user_review_cache_key, REVIEW_CACHE_NAMESPACE) if not user_review: user = db_users.get_by_mbid(username) if user: From b389c131056c268f1484300ec842bd43ab2af38b Mon Sep 17 00:00:00 2001 From: Ansh Goyal Date: Sat, 17 Sep 2022 06:50:38 +0000 Subject: [PATCH 09/15] feat: Add Tests for entity endpoints --- critiquebrainz/ws/artist/test/__init__.py | 0 critiquebrainz/ws/artist/test/views_test.py | 89 +++++++++++++++++++ critiquebrainz/ws/bb_author/test/__init__.py | 0 .../ws/bb_author/test/views_test.py | 88 ++++++++++++++++++ .../ws/bb_edition_group/test/__init__.py | 0 .../ws/bb_edition_group/test/views_test.py | 88 ++++++++++++++++++ .../ws/bb_literary_work/test/__init__.py | 0 .../ws/bb_literary_work/test/views_test.py | 89 +++++++++++++++++++ critiquebrainz/ws/bb_series/test/__init__.py | 0 .../ws/bb_series/test/views_test.py | 88 ++++++++++++++++++ critiquebrainz/ws/event/test/__init__.py | 0 critiquebrainz/ws/event/test/views_test.py | 88 ++++++++++++++++++ critiquebrainz/ws/label/test/__init__.py | 0 critiquebrainz/ws/label/test/views_test.py | 88 ++++++++++++++++++ critiquebrainz/ws/place/test/__init__.py | 0 critiquebrainz/ws/place/test/views_test.py | 88 ++++++++++++++++++ critiquebrainz/ws/recording/test/__init__.py | 0 .../ws/recording/test/views_test.py | 88 ++++++++++++++++++ .../ws/release_group/test/__init__.py | 0 .../ws/release_group/test/views_test.py | 88 ++++++++++++++++++ critiquebrainz/ws/work/test/__init__.py | 0 critiquebrainz/ws/work/test/views_test.py | 88 ++++++++++++++++++ 22 files changed, 970 insertions(+) create mode 100644 critiquebrainz/ws/artist/test/__init__.py create mode 100644 critiquebrainz/ws/artist/test/views_test.py create mode 100644 critiquebrainz/ws/bb_author/test/__init__.py create mode 100644 critiquebrainz/ws/bb_author/test/views_test.py create mode 100644 critiquebrainz/ws/bb_edition_group/test/__init__.py create mode 100644 critiquebrainz/ws/bb_edition_group/test/views_test.py create mode 100644 critiquebrainz/ws/bb_literary_work/test/__init__.py create mode 100644 critiquebrainz/ws/bb_literary_work/test/views_test.py create mode 100644 critiquebrainz/ws/bb_series/test/__init__.py create mode 100644 critiquebrainz/ws/bb_series/test/views_test.py create mode 100644 critiquebrainz/ws/event/test/__init__.py create mode 100644 critiquebrainz/ws/event/test/views_test.py create mode 100644 critiquebrainz/ws/label/test/__init__.py create mode 100644 critiquebrainz/ws/label/test/views_test.py create mode 100644 critiquebrainz/ws/place/test/__init__.py create mode 100644 critiquebrainz/ws/place/test/views_test.py create mode 100644 critiquebrainz/ws/recording/test/__init__.py create mode 100644 critiquebrainz/ws/recording/test/views_test.py create mode 100644 critiquebrainz/ws/release_group/test/__init__.py create mode 100644 critiquebrainz/ws/release_group/test/views_test.py create mode 100644 critiquebrainz/ws/work/test/__init__.py create mode 100644 critiquebrainz/ws/work/test/views_test.py diff --git a/critiquebrainz/ws/artist/test/__init__.py b/critiquebrainz/ws/artist/test/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/critiquebrainz/ws/artist/test/views_test.py b/critiquebrainz/ws/artist/test/views_test.py new file mode 100644 index 000000000..ba7915891 --- /dev/null +++ b/critiquebrainz/ws/artist/test/views_test.py @@ -0,0 +1,89 @@ +from brainzutils import cache + +import critiquebrainz.db.license as db_license +import critiquebrainz.db.review as db_review +import critiquebrainz.db.users as db_users +from critiquebrainz.db.user import User +from critiquebrainz.ws.testing import WebServiceTestCase + + +class ArtistViewsTestCase(WebServiceTestCase): + + def setUp(self): + super(ArtistViewsTestCase, self).setUp() + + self.artist_id1 = "f59c5520-5f46-4d2c-b2c4-822eabf53419" + self.artist_id2 = "83d91898-7763-47d7-b03b-b92132375c47" + + self.user = User(db_users.get_or_create(1, "Tester", new_user_data={ + "display_name": "test user", + })) + self.another_user = User(db_users.get_or_create(2, "Hacker!", new_user_data={ + "display_name": "test hacker", + })) + self.license = db_license.create( + id="CC BY-SA 3.0", + full_name="Created so we can fill the form correctly.", + ) + self.review = dict( + entity_id=self.artist_id1, + entity_type='artist', + user_id=self.user.id, + text="Testing! This text should be on the page.", + rating=5, + is_draft=False, + license_id=self.license["id"], + ) + self.review2 = dict( + entity_id=self.artist_id2, + entity_type='artist', + user_id=self.user.id, + text="Testing! This text should be on the page.", + rating=5, + is_draft=False, + license_id=self.license["id"], + ) + + def create_dummy_review(self): + return db_review.create(**self.review) + + def create_dummy_review2(self): + return db_review.create(**self.review2) + + def test_artist_endpoint(self): + review = self.create_dummy_review() + response = self.client.get('/artist/f59c5520-5f46-4d2c-b2c4-822eabf53419') + + self.assert200(response) + self.assertIn(review['text'], response.json['top_reviews'][0]['text']) + + self.assertEqual(5, response.json['average_rating']) + self.assertEqual(1, response.json['reviews_count']) + + # Test for an artist which does not exist + response = self.client.get('/artist/f59c5520-5f46-4d2c-b2c4-822eabf53417') + self.assert404(response) + + def test_artist_user_reviews(self): + review = self.create_dummy_review() + response = self.client.get('/artist/f59c5520-5f46-4d2c-b2c4-822eabf53419?username=%s' % self.user.musicbrainz_username) + + self.assert200(response) + self.assertIn(review['text'], response.json['user_review']['text']) + + def test_user_cache_tracking(self): + track_key = cache.gen_key("entity_api", self.artist_id2, self.user.musicbrainz_username, "user_review") + + # Make sure the cache is empty + self.client.get('/artist/83d91898-7763-47d7-b03b-b92132375c47?username=%s' % self.user.musicbrainz_username) + cache_value = cache.get(track_key, namespace="Review") + self.assertEqual([], cache_value) + + review = self.create_dummy_review2() + + # Check if the cache is populated after the request + self.client.get('/artist/83d91898-7763-47d7-b03b-b92132375c47?username=%s' % self.user.musicbrainz_username) + cache_value = cache.get(track_key, namespace="Review") + self.assertTrue(cache_value is not None) + + self.assertIn(review['text'], cache_value['text']) diff --git a/critiquebrainz/ws/bb_author/test/__init__.py b/critiquebrainz/ws/bb_author/test/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/critiquebrainz/ws/bb_author/test/views_test.py b/critiquebrainz/ws/bb_author/test/views_test.py new file mode 100644 index 000000000..18e4e9ca5 --- /dev/null +++ b/critiquebrainz/ws/bb_author/test/views_test.py @@ -0,0 +1,88 @@ +from brainzutils import cache + +import critiquebrainz.db.license as db_license +import critiquebrainz.db.review as db_review +import critiquebrainz.db.users as db_users +from critiquebrainz.db.user import User +from critiquebrainz.ws.testing import WebServiceTestCase + + +class AuthorViewsTestCase(WebServiceTestCase): + + def setUp(self): + super(AuthorViewsTestCase, self).setUp() + + self.author_id1 = "b8f9bd89-79d1-497e-811f-18576321111c" + self.author_id2 = "b2507eee-1391-47c5-93e6-ca972bd8e0e0" + + self.user = User(db_users.get_or_create(1, "Tester", new_user_data={ + "display_name": "test user", + })) + self.another_user = User(db_users.get_or_create(2, "Hacker!", new_user_data={ + "display_name": "test hacker", + })) + self.license = db_license.create( + id="CC BY-SA 3.0", + full_name="Created so we can fill the form correctly.", + ) + self.review = dict( + entity_id=self.author_id1, + entity_type='bb_author', + user_id=self.user.id, + text="Testing! This text should be on the page.", + rating=5, + is_draft=False, + license_id=self.license["id"], + ) + self.review2 = dict( + entity_id=self.author_id2, + entity_type='bb_author', + user_id=self.user.id, + text="Testing! This text should be on the page.", + rating=5, + is_draft=False, + license_id=self.license["id"], + ) + + def create_dummy_review(self): + return db_review.create(**self.review) + + def create_dummy_review2(self): + return db_review.create(**self.review2) + + def test_author_endpoint(self): + review = self.create_dummy_review() + response = self.client.get('/author/b8f9bd89-79d1-497e-811f-18576321111c') + + self.assert200(response) + self.assertIn(review['text'], response.json['top_reviews'][0]['text']) + + self.assertEqual(5, response.json['average_rating']) + self.assertEqual(1, response.json['reviews_count']) + + # Test for an author which does not exist + response = self.client.get('/author/b8f9bd89-79d1-497e-811f-18576321111g') + self.assert404(response) + + def test_author_user_reviews(self): + review = self.create_dummy_review() + response = self.client.get('/author/b8f9bd89-79d1-497e-811f-18576321111c?username=%s' % self.user.musicbrainz_username) + + self.assert200(response) + self.assertIn(review['text'], response.json['user_review']['text']) + + def test_user_cache_tracking(self): + track_key = cache.gen_key("entity_api", self.author_id2, self.user.musicbrainz_username, "user_review") + + # Make sure the cache is empty + self.client.get('/author/b2507eee-1391-47c5-93e6-ca972bd8e0e0?username=%s' % self.user.musicbrainz_username) + cache_value = cache.get(track_key, namespace="Review") + self.assertEqual([], cache_value) + + review = self.create_dummy_review2() + + # Check if the cache is populated after the request + self.client.get('/author/b2507eee-1391-47c5-93e6-ca972bd8e0e0?username=%s' % self.user.musicbrainz_username) + cache_value = cache.get(track_key, namespace="Review") + + self.assertIn(review['text'], cache_value['text']) diff --git a/critiquebrainz/ws/bb_edition_group/test/__init__.py b/critiquebrainz/ws/bb_edition_group/test/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/critiquebrainz/ws/bb_edition_group/test/views_test.py b/critiquebrainz/ws/bb_edition_group/test/views_test.py new file mode 100644 index 000000000..e66543508 --- /dev/null +++ b/critiquebrainz/ws/bb_edition_group/test/views_test.py @@ -0,0 +1,88 @@ +from brainzutils import cache + +import critiquebrainz.db.license as db_license +import critiquebrainz.db.review as db_review +import critiquebrainz.db.users as db_users +from critiquebrainz.db.user import User +from critiquebrainz.ws.testing import WebServiceTestCase + + +class EditionGroupViewsTestCase(WebServiceTestCase): + + def setUp(self): + super(EditionGroupViewsTestCase, self).setUp() + + self.edition_group_id1 = "ac4ae236-6bee-4335-9e04-21314a478f9f" + self.edition_group_id2 = "ab87aa42-3cb7-478c-bba7-09192d04f252" + + self.user = User(db_users.get_or_create(1, "Tester", new_user_data={ + "display_name": "test user", + })) + self.another_user = User(db_users.get_or_create(2, "Hacker!", new_user_data={ + "display_name": "test hacker", + })) + self.license = db_license.create( + id="CC BY-SA 3.0", + full_name="Created so we can fill the form correctly.", + ) + self.review = dict( + entity_id=self.edition_group_id1, + entity_type='bb_edition_group', + user_id=self.user.id, + text="Testing! This text should be on the page.", + rating=5, + is_draft=False, + license_id=self.license["id"], + ) + self.review2 = dict( + entity_id=self.edition_group_id2, + entity_type='bb_edition_group', + user_id=self.user.id, + text="Testing! This text should be on the page.", + rating=5, + is_draft=False, + license_id=self.license["id"], + ) + + def create_dummy_review(self): + return db_review.create(**self.review) + + def create_dummy_review2(self): + return db_review.create(**self.review2) + + def test_edition_group_endpoint(self): + review = self.create_dummy_review() + response = self.client.get('/edition-group/ac4ae236-6bee-4335-9e04-21314a478f9f') + + self.assert200(response) + self.assertIn(review['text'], response.json['top_reviews'][0]['text']) + + self.assertEqual(5, response.json['average_rating']) + self.assertEqual(1, response.json['reviews_count']) + + # Test for an edition group that doesn't exist + response = self.client.get('/edition-group/ac4ae236-6bee-4335-9e04-21314a478f9d') + self.assert404(response) + + def test_edition_group_user_reviews(self): + review = self.create_dummy_review() + response = self.client.get('/edition-group/ac4ae236-6bee-4335-9e04-21314a478f9f?username=%s' % self.user.musicbrainz_username) + + self.assert200(response) + self.assertIn(review['text'], response.json['user_review']['text']) + + def test_user_cache_tracking(self): + track_key = cache.gen_key("entity_api", self.edition_group_id2, self.user.musicbrainz_username, "user_review") + + # Make sure the cache is empty + self.client.get('/edition-group/ab87aa42-3cb7-478c-bba7-09192d04f252?username=%s' % self.user.musicbrainz_username) + cache_value = cache.get(track_key, namespace="Review") + self.assertEqual([], cache_value) + + review = self.create_dummy_review2() + + # Check if the cache is populated after the request + self.client.get('/edition-group/ab87aa42-3cb7-478c-bba7-09192d04f252?username=%s' % self.user.musicbrainz_username) + cache_value = cache.get(track_key, namespace="Review") + + self.assertIn(review['text'], cache_value['text']) diff --git a/critiquebrainz/ws/bb_literary_work/test/__init__.py b/critiquebrainz/ws/bb_literary_work/test/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/critiquebrainz/ws/bb_literary_work/test/views_test.py b/critiquebrainz/ws/bb_literary_work/test/views_test.py new file mode 100644 index 000000000..e21dca5ba --- /dev/null +++ b/critiquebrainz/ws/bb_literary_work/test/views_test.py @@ -0,0 +1,89 @@ +from brainzutils import cache + +import critiquebrainz.db.license as db_license +import critiquebrainz.db.review as db_review +import critiquebrainz.db.users as db_users +from critiquebrainz.db.user import User +from critiquebrainz.ws.testing import WebServiceTestCase + + +class LiteraryWorkViewsTestCase(WebServiceTestCase): + + def setUp(self): + super(LiteraryWorkViewsTestCase, self).setUp() + + self.literary_work_id1 = "56efa555-abd5-4ccb-89a6-ff9d9021971f" + self.literary_work_id2 = "0e03bc2a-2867-4687-afee-e211ece30772" + + self.user = User(db_users.get_or_create(1, "Tester", new_user_data={ + "display_name": "test user", + })) + self.another_user = User(db_users.get_or_create(2, "Hacker!", new_user_data={ + "display_name": "test hacker", + })) + self.license = db_license.create( + id="CC BY-SA 3.0", + full_name="Created so we can fill the form correctly.", + ) + self.review = dict( + entity_id=self.literary_work_id1, + entity_type='bb_literary_work', + user_id=self.user.id, + text="Testing! This text should be on the page.", + rating=5, + is_draft=False, + license_id=self.license["id"], + ) + self.review2 = dict( + entity_id=self.literary_work_id2, + entity_type='bb_literary_work', + user_id=self.user.id, + text="Testing! This text should be on the page.", + rating=5, + is_draft=False, + license_id=self.license["id"], + ) + + def create_dummy_review(self): + return db_review.create(**self.review) + + def create_dummy_review2(self): + return db_review.create(**self.review2) + + def test_literary_work_endpoint(self): + review = self.create_dummy_review() + response = self.client.get('/literary-work/56efa555-abd5-4ccb-89a6-ff9d9021971f') + + self.assert200(response) + self.assertIn(review['text'], response.json['top_reviews'][0]['text']) + + self.assertEqual(5, response.json['average_rating']) + self.assertEqual(1, response.json['reviews_count']) + + # Test for a literary work that doesn't exist + response = self.client.get('/literary-work/56efa555-abd5-4ccb-89a6-ff9d9021971g') + self.assert404(response) + + def test_literary_work_user_reviews(self): + review = self.create_dummy_review() + response = self.client.get('/literary-work/56efa555-abd5-4ccb-89a6-ff9d9021971f?username=%s' % + self.user.musicbrainz_username) + + self.assert200(response) + self.assertIn(review['text'], response.json['user_review']['text']) + + def test_user_cache_tracking(self): + track_key = cache.gen_key("entity_api", self.literary_work_id2, self.user.musicbrainz_username, "user_review") + + # Make sure the cache is empty + self.client.get('/literary-work/0e03bc2a-2867-4687-afee-e211ece30772?username=%s' % self.user.musicbrainz_username) + cache_value = cache.get(track_key, namespace="Review") + self.assertEqual([], cache_value) + + review = self.create_dummy_review2() + + # Check if the cache is populated after the request + self.client.get('/literary-work/0e03bc2a-2867-4687-afee-e211ece30772?username=%s' % self.user.musicbrainz_username) + cache_value = cache.get(track_key, namespace="Review") + + self.assertIn(review['text'], cache_value['text']) diff --git a/critiquebrainz/ws/bb_series/test/__init__.py b/critiquebrainz/ws/bb_series/test/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/critiquebrainz/ws/bb_series/test/views_test.py b/critiquebrainz/ws/bb_series/test/views_test.py new file mode 100644 index 000000000..e43c0d0c7 --- /dev/null +++ b/critiquebrainz/ws/bb_series/test/views_test.py @@ -0,0 +1,88 @@ +from brainzutils import cache + +import critiquebrainz.db.license as db_license +import critiquebrainz.db.review as db_review +import critiquebrainz.db.users as db_users +from critiquebrainz.db.user import User +from critiquebrainz.ws.testing import WebServiceTestCase + + +class seriesViewsTestCase(WebServiceTestCase): + + def setUp(self): + super(seriesViewsTestCase, self).setUp() + + self.series_id1 = "968ef651-6a70-410f-9b17-f326ee0062c3" + self.series_id2 = "29b7d60f-0be1-428d-8a2d-71f3abb8d218" + + self.user = User(db_users.get_or_create(1, "Tester", new_user_data={ + "display_name": "test user", + })) + self.another_user = User(db_users.get_or_create(2, "Hacker!", new_user_data={ + "display_name": "test hacker", + })) + self.license = db_license.create( + id="CC BY-SA 3.0", + full_name="Created so we can fill the form correctly.", + ) + self.review = dict( + entity_id=self.series_id1, + entity_type='bb_series', + user_id=self.user.id, + text="Testing! This text should be on the page.", + rating=5, + is_draft=False, + license_id=self.license["id"], + ) + self.review2 = dict( + entity_id=self.series_id2, + entity_type='bb_series', + user_id=self.user.id, + text="Testing! This text should be on the page.", + rating=5, + is_draft=False, + license_id=self.license["id"], + ) + + def create_dummy_review(self): + return db_review.create(**self.review) + + def create_dummy_review2(self): + return db_review.create(**self.review2) + + def test_series_endpoint(self): + review = self.create_dummy_review() + response = self.client.get('/series/968ef651-6a70-410f-9b17-f326ee0062c3') + + self.assert200(response) + self.assertIn(review['text'], response.json['top_reviews'][0]['text']) + + self.assertEqual(5, response.json['average_rating']) + self.assertEqual(1, response.json['reviews_count']) + + # Test for a series which does not exist + response = self.client.get('/series/968ef651-6a70-410f-9b17-f326ee0062c1') + self.assert404(response) + + def test_series_user_reviews(self): + review = self.create_dummy_review() + response = self.client.get('/series/968ef651-6a70-410f-9b17-f326ee0062c3?username=%s' % self.user.musicbrainz_username) + + self.assert200(response) + self.assertIn(review['text'], response.json['user_review']['text']) + + def test_user_cache_tracking(self): + track_key = cache.gen_key("entity_api", self.series_id2, self.user.musicbrainz_username, "user_review") + + # Make sure the cache is empty + self.client.get('/series/29b7d60f-0be1-428d-8a2d-71f3abb8d218?username=%s' % self.user.musicbrainz_username) + cache_value = cache.get(track_key, namespace="Review") + self.assertEqual([], cache_value) + + review = self.create_dummy_review2() + + # Check if the cache is populated after the request + self.client.get('/series/29b7d60f-0be1-428d-8a2d-71f3abb8d218?username=%s' % self.user.musicbrainz_username) + cache_value = cache.get(track_key, namespace="Review") + + self.assertIn(review['text'], cache_value['text']) diff --git a/critiquebrainz/ws/event/test/__init__.py b/critiquebrainz/ws/event/test/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/critiquebrainz/ws/event/test/views_test.py b/critiquebrainz/ws/event/test/views_test.py new file mode 100644 index 000000000..a552788ef --- /dev/null +++ b/critiquebrainz/ws/event/test/views_test.py @@ -0,0 +1,88 @@ +from brainzutils import cache + +import critiquebrainz.db.license as db_license +import critiquebrainz.db.review as db_review +import critiquebrainz.db.users as db_users +from critiquebrainz.db.user import User +from critiquebrainz.ws.testing import WebServiceTestCase + + +class EventViewsTestCase(WebServiceTestCase): + + def setUp(self): + super(EventViewsTestCase, self).setUp() + + self.event_id1 = "fe39727a-3d21-4066-9345-3970cbd6cca4" + self.event_id2 = "7f2d3cd9-c2e1-4adc-841f-dab3de070a75" + + self.user = User(db_users.get_or_create(1, "Tester", new_user_data={ + "display_name": "test user", + })) + self.another_user = User(db_users.get_or_create(2, "Hacker!", new_user_data={ + "display_name": "test hacker", + })) + self.license = db_license.create( + id="CC BY-SA 3.0", + full_name="Created so we can fill the form correctly.", + ) + self.review = dict( + entity_id=self.event_id1, + entity_type='event', + user_id=self.user.id, + text="Testing! This text should be on the page.", + rating=5, + is_draft=False, + license_id=self.license["id"], + ) + self.review2 = dict( + entity_id=self.event_id2, + entity_type='event', + user_id=self.user.id, + text="Testing! This text should be on the page.", + rating=5, + is_draft=False, + license_id=self.license["id"], + ) + + def create_dummy_review(self): + return db_review.create(**self.review) + + def create_dummy_review2(self): + return db_review.create(**self.review2) + + def test_event_endpoint(self): + review = self.create_dummy_review() + response = self.client.get('/event/fe39727a-3d21-4066-9345-3970cbd6cca4') + + self.assert200(response) + self.assertIn(review['text'], response.json['top_reviews'][0]['text']) + + self.assertEqual(5, response.json['average_rating']) + self.assertEqual(1, response.json['reviews_count']) + + # Test for an event that doesn't exist + response = self.client.get('/event/fe39727a-3d21-4066-9345-3970cbd6cca1') + self.assert404(response) + + def test_event_user_reviews(self): + review = self.create_dummy_review() + response = self.client.get('/event/fe39727a-3d21-4066-9345-3970cbd6cca4?username=%s' % self.user.musicbrainz_username) + + self.assert200(response) + self.assertIn(review['text'], response.json['user_review']['text']) + + def test_user_cache_tracking(self): + track_key = cache.gen_key("entity_api", self.event_id2, self.user.musicbrainz_username, "user_review") + + # Make sure the cache is empty + self.client.get('/event/7f2d3cd9-c2e1-4adc-841f-dab3de070a75?username=%s' % self.user.musicbrainz_username) + cache_value = cache.get(track_key, namespace="Review") + self.assertEqual([], cache_value) + + review = self.create_dummy_review2() + + # Check if the cache is populated after the request + self.client.get('/event/7f2d3cd9-c2e1-4adc-841f-dab3de070a75?username=%s' % self.user.musicbrainz_username) + cache_value = cache.get(track_key, namespace="Review") + + self.assertIn(review['text'], cache_value['text']) diff --git a/critiquebrainz/ws/label/test/__init__.py b/critiquebrainz/ws/label/test/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/critiquebrainz/ws/label/test/views_test.py b/critiquebrainz/ws/label/test/views_test.py new file mode 100644 index 000000000..300dc6820 --- /dev/null +++ b/critiquebrainz/ws/label/test/views_test.py @@ -0,0 +1,88 @@ +from brainzutils import cache + +import critiquebrainz.db.license as db_license +import critiquebrainz.db.review as db_review +import critiquebrainz.db.users as db_users +from critiquebrainz.db.user import User +from critiquebrainz.ws.testing import WebServiceTestCase + + +class LabelViewsTestCase(WebServiceTestCase): + + def setUp(self): + super(LabelViewsTestCase, self).setUp() + + self.label_id1 = "13a464dc-b9fd-4d16-a4f4-d4316f6a46c7" + self.label_id2 = "d8067fa7-8758-4527-b436-ce5faff3e576" + + self.user = User(db_users.get_or_create(1, "Tester", new_user_data={ + "display_name": "test user", + })) + self.another_user = User(db_users.get_or_create(2, "Hacker!", new_user_data={ + "display_name": "test hacker", + })) + self.license = db_license.create( + id="CC BY-SA 3.0", + full_name="Created so we can fill the form correctly.", + ) + self.review = dict( + entity_id=self.label_id1, + entity_type='label', + user_id=self.user.id, + text="Testing! This text should be on the page.", + rating=5, + is_draft=False, + license_id=self.license["id"], + ) + self.review2 = dict( + entity_id=self.label_id2, + entity_type='label', + user_id=self.user.id, + text="Testing! This text should be on the page.", + rating=5, + is_draft=False, + license_id=self.license["id"], + ) + + def create_dummy_review(self): + return db_review.create(**self.review) + + def create_dummy_review2(self): + return db_review.create(**self.review2) + + def test_label_endpoint(self): + review = self.create_dummy_review() + response = self.client.get('/label/13a464dc-b9fd-4d16-a4f4-d4316f6a46c7') + + self.assert200(response) + self.assertIn(review['text'], response.json['top_reviews'][0]['text']) + + self.assertEqual(5, response.json['average_rating']) + self.assertEqual(1, response.json['reviews_count']) + + # Test for a label that doesn't exist + response = self.client.get('/label/13a464dc-b9fd-4d16-a4f4-d4316f6a46c1') + self.assert404(response) + + def test_label_user_reviews(self): + review = self.create_dummy_review() + response = self.client.get('/label/13a464dc-b9fd-4d16-a4f4-d4316f6a46c7?username=%s' % self.user.musicbrainz_username) + + self.assert200(response) + self.assertIn(review['text'], response.json['user_review']['text']) + + def test_user_cache_tracking(self): + track_key = cache.gen_key("entity_api", self.label_id2, self.user.musicbrainz_username, "user_review") + + # Make sure the cache is empty + self.client.get('/label/d8067fa7-8758-4527-b436-ce5faff3e576?username=%s' % self.user.musicbrainz_username) + cache_value = cache.get(track_key, namespace="Review") + self.assertEqual([], cache_value) + + review = self.create_dummy_review2() + + # Check if the cache is populated after the request + self.client.get('/label/d8067fa7-8758-4527-b436-ce5faff3e576?username=%s' % self.user.musicbrainz_username) + cache_value = cache.get(track_key, namespace="Review") + + self.assertIn(review['text'], cache_value['text']) diff --git a/critiquebrainz/ws/place/test/__init__.py b/critiquebrainz/ws/place/test/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/critiquebrainz/ws/place/test/views_test.py b/critiquebrainz/ws/place/test/views_test.py new file mode 100644 index 000000000..ab0b2e07e --- /dev/null +++ b/critiquebrainz/ws/place/test/views_test.py @@ -0,0 +1,88 @@ +from brainzutils import cache + +import critiquebrainz.db.license as db_license +import critiquebrainz.db.review as db_review +import critiquebrainz.db.users as db_users +from critiquebrainz.db.user import User +from critiquebrainz.ws.testing import WebServiceTestCase + + +class PlaceViewsTestCase(WebServiceTestCase): + + def setUp(self): + super(PlaceViewsTestCase, self).setUp() + + self.place_id1 = "4352063b-a833-421b-a420-e7fb295dece0" + self.place_id2 = "853b36f9-8806-459c-9480-0766b8f9354b" + + self.user = User(db_users.get_or_create(1, "Tester", new_user_data={ + "display_name": "test user", + })) + self.another_user = User(db_users.get_or_create(2, "Hacker!", new_user_data={ + "display_name": "test hacker", + })) + self.license = db_license.create( + id="CC BY-SA 3.0", + full_name="Created so we can fill the form correctly.", + ) + self.review = dict( + entity_id=self.place_id1, + entity_type='place', + user_id=self.user.id, + text="Testing! This text should be on the page.", + rating=5, + is_draft=False, + license_id=self.license["id"], + ) + self.review2 = dict( + entity_id=self.place_id2, + entity_type='place', + user_id=self.user.id, + text="Testing! This text should be on the page.", + rating=5, + is_draft=False, + license_id=self.license["id"], + ) + + def create_dummy_review(self): + return db_review.create(**self.review) + + def create_dummy_review2(self): + return db_review.create(**self.review2) + + def test_place_endpoint(self): + review = self.create_dummy_review() + response = self.client.get('/place/4352063b-a833-421b-a420-e7fb295dece0') + + self.assert200(response) + self.assertIn(review['text'], response.json['top_reviews'][0]['text']) + + self.assertEqual(5, response.json['average_rating']) + self.assertEqual(1, response.json['reviews_count']) + + # Test for a place that doesn't exist + response = self.client.get('/place/4352063b-a833-421b-a420-e7fb295dece9') + self.assert404(response) + + def test_place_user_reviews(self): + review = self.create_dummy_review() + response = self.client.get('/place/4352063b-a833-421b-a420-e7fb295dece0?username=%s' % self.user.musicbrainz_username) + + self.assert200(response) + self.assertIn(review['text'], response.json['user_review']['text']) + + def test_user_cache_tracking(self): + track_key = cache.gen_key("entity_api", self.place_id2, self.user.musicbrainz_username, "user_review") + + # Make sure the cache is empty + self.client.get('/place/853b36f9-8806-459c-9480-0766b8f9354b?username=%s' % self.user.musicbrainz_username) + cache_value = cache.get(track_key, namespace="Review") + self.assertEqual([], cache_value) + + review = self.create_dummy_review2() + + # Check if the cache is populated after the request + self.client.get('/place/853b36f9-8806-459c-9480-0766b8f9354b?username=%s' % self.user.musicbrainz_username) + cache_value = cache.get(track_key, namespace="Review") + + self.assertIn(review['text'], cache_value['text']) diff --git a/critiquebrainz/ws/recording/test/__init__.py b/critiquebrainz/ws/recording/test/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/critiquebrainz/ws/recording/test/views_test.py b/critiquebrainz/ws/recording/test/views_test.py new file mode 100644 index 000000000..2dfa755e5 --- /dev/null +++ b/critiquebrainz/ws/recording/test/views_test.py @@ -0,0 +1,88 @@ +from brainzutils import cache + +import critiquebrainz.db.license as db_license +import critiquebrainz.db.review as db_review +import critiquebrainz.db.users as db_users +from critiquebrainz.db.user import User +from critiquebrainz.ws.testing import WebServiceTestCase + + +class RecordingViewsTestCase(WebServiceTestCase): + + def setUp(self): + super(RecordingViewsTestCase, self).setUp() + + self.recording_id1 = "442ddce2-ffa1-4865-81d2-b42c40fec7c5" + self.recording_id2 = "7a03530e-1018-458c-a254-b814a4b62b72" + + self.user = User(db_users.get_or_create(1, "Tester", new_user_data={ + "display_name": "test user", + })) + self.another_user = User(db_users.get_or_create(2, "Hacker!", new_user_data={ + "display_name": "test hacker", + })) + self.license = db_license.create( + id="CC BY-SA 3.0", + full_name="Created so we can fill the form correctly.", + ) + self.review = dict( + entity_id=self.recording_id1, + entity_type='recording', + user_id=self.user.id, + text="Testing! This text should be on the page.", + rating=5, + is_draft=False, + license_id=self.license["id"], + ) + self.review2 = dict( + entity_id=self.recording_id2, + entity_type='recording', + user_id=self.user.id, + text="Testing! This text should be on the page.", + rating=5, + is_draft=False, + license_id=self.license["id"], + ) + + def create_dummy_review(self): + return db_review.create(**self.review) + + def create_dummy_review2(self): + return db_review.create(**self.review2) + + def test_recording_endpoint(self): + review = self.create_dummy_review() + response = self.client.get('/recording/442ddce2-ffa1-4865-81d2-b42c40fec7c5') + + self.assert200(response) + self.assertIn(review['text'], response.json['top_reviews'][0]['text']) + + self.assertEqual(5, response.json['average_rating']) + self.assertEqual(1, response.json['reviews_count']) + + # Test for a recording that doesn't exist + response = self.client.get('/recording/442ddce2-ffa1-4865-81d2-b42c40fec7c1') + self.assert404(response) + + def test_recording_user_reviews(self): + review = self.create_dummy_review() + response = self.client.get('/recording/442ddce2-ffa1-4865-81d2-b42c40fec7c5?username=%s' % self.user.musicbrainz_username) + + self.assert200(response) + self.assertIn(review['text'], response.json['user_review']['text']) + + def test_user_cache_tracking(self): + track_key = cache.gen_key("entity_api", self.recording_id2, self.user.musicbrainz_username, "user_review") + + # Make sure the cache is empty + self.client.get('/recording/7a03530e-1018-458c-a254-b814a4b62b72?username=%s' % self.user.musicbrainz_username) + cache_value = cache.get(track_key, namespace="Review") + self.assertEqual([], cache_value) + + review = self.create_dummy_review2() + + # Check if the cache is populated after the request + self.client.get('/recording/7a03530e-1018-458c-a254-b814a4b62b72?username=%s' % self.user.musicbrainz_username) + cache_value = cache.get(track_key, namespace="Review") + + self.assertIn(review['text'], cache_value['text']) diff --git a/critiquebrainz/ws/release_group/test/__init__.py b/critiquebrainz/ws/release_group/test/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/critiquebrainz/ws/release_group/test/views_test.py b/critiquebrainz/ws/release_group/test/views_test.py new file mode 100644 index 000000000..9db56b291 --- /dev/null +++ b/critiquebrainz/ws/release_group/test/views_test.py @@ -0,0 +1,88 @@ +from brainzutils import cache + +import critiquebrainz.db.license as db_license +import critiquebrainz.db.review as db_review +import critiquebrainz.db.users as db_users +from critiquebrainz.db.user import User +from critiquebrainz.ws.testing import WebServiceTestCase + + +class ReleaseGroupViewsTestCase(WebServiceTestCase): + + def setUp(self): + super(ReleaseGroupViewsTestCase, self).setUp() + + self.release_group_id1 = "1eff4a06-056e-4dc7-91c4-0cbc5878f3c3" + self.release_group_id2 = "c033d28b-fe43-3744-a5f8-50b30a100dcb" + + self.user = User(db_users.get_or_create(1, "Tester", new_user_data={ + "display_name": "test user", + })) + self.another_user = User(db_users.get_or_create(2, "Hacker!", new_user_data={ + "display_name": "test hacker", + })) + self.license = db_license.create( + id="CC BY-SA 3.0", + full_name="Created so we can fill the form correctly.", + ) + self.review = dict( + entity_id=self.release_group_id1, + entity_type='release_group', + user_id=self.user.id, + text="Testing! This text should be on the page.", + rating=5, + is_draft=False, + license_id=self.license["id"], + ) + self.review2 = dict( + entity_id=self.release_group_id2, + entity_type='release_group', + user_id=self.user.id, + text="Testing! This text should be on the page.", + rating=5, + is_draft=False, + license_id=self.license["id"], + ) + + def create_dummy_review(self): + return db_review.create(**self.review) + + def create_dummy_review2(self): + return db_review.create(**self.review2) + + def test_release_group_endpoint(self): + review = self.create_dummy_review() + response = self.client.get('/release-group/1eff4a06-056e-4dc7-91c4-0cbc5878f3c3') + + self.assert200(response) + self.assertIn(review['text'], response.json['top_reviews'][0]['text']) + + self.assertEqual(5, response.json['average_rating']) + self.assertEqual(1, response.json['reviews_count']) + + # Test for a release_group that doesn't exist + response = self.client.get('/release-group/1eff4a06-056e-4dc7-91c4-0cbc5878f3c1') + self.assert404(response) + + def test_release_group_user_reviews(self): + review = self.create_dummy_review() + response = self.client.get('/release-group/1eff4a06-056e-4dc7-91c4-0cbc5878f3c3?username=%s' % self.user.musicbrainz_username) + + self.assert200(response) + self.assertIn(review['text'], response.json['user_review']['text']) + + def test_user_cache_tracking(self): + track_key = cache.gen_key("entity_api", self.release_group_id2, self.user.musicbrainz_username, "user_review") + + # Make sure the cache is empty + self.client.get('/release-group/c033d28b-fe43-3744-a5f8-50b30a100dcb?username=%s' % self.user.musicbrainz_username) + cache_value = cache.get(track_key, namespace="Review") + self.assertEqual([], cache_value) + + review = self.create_dummy_review2() + + # Check if the cache is populated after the request + self.client.get('/release-group/c033d28b-fe43-3744-a5f8-50b30a100dcb?username=%s' % self.user.musicbrainz_username) + cache_value = cache.get(track_key, namespace="Review") + + self.assertIn(review['text'], cache_value['text']) diff --git a/critiquebrainz/ws/work/test/__init__.py b/critiquebrainz/ws/work/test/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/critiquebrainz/ws/work/test/views_test.py b/critiquebrainz/ws/work/test/views_test.py new file mode 100644 index 000000000..ac45fdfdc --- /dev/null +++ b/critiquebrainz/ws/work/test/views_test.py @@ -0,0 +1,88 @@ +from brainzutils import cache + +import critiquebrainz.db.license as db_license +import critiquebrainz.db.review as db_review +import critiquebrainz.db.users as db_users +from critiquebrainz.db.user import User +from critiquebrainz.ws.testing import WebServiceTestCase + + +class WorkViewsTestCase(WebServiceTestCase): + + def setUp(self): + super(WorkViewsTestCase, self).setUp() + + self.work_id1 = "0e5a48f3-7d21-365c-bfb7-98d9865ea1dd" + self.work_id2 = "fd881e93-c199-4c86-81a7-cabc044948d0" + + self.user = User(db_users.get_or_create(1, "Tester", new_user_data={ + "display_name": "test user", + })) + self.another_user = User(db_users.get_or_create(2, "Hacker!", new_user_data={ + "display_name": "test hacker", + })) + self.license = db_license.create( + id="CC BY-SA 3.0", + full_name="Created so we can fill the form correctly.", + ) + self.review = dict( + entity_id=self.work_id1, + entity_type='work', + user_id=self.user.id, + text="Testing! This text should be on the page.", + rating=5, + is_draft=False, + license_id=self.license["id"], + ) + self.review2 = dict( + entity_id=self.work_id2, + entity_type='work', + user_id=self.user.id, + text="Testing! This text should be on the page.", + rating=5, + is_draft=False, + license_id=self.license["id"], + ) + + def create_dummy_review(self): + return db_review.create(**self.review) + + def create_dummy_review2(self): + return db_review.create(**self.review2) + + def test_work_endpoint(self): + review = self.create_dummy_review() + response = self.client.get('/work/0e5a48f3-7d21-365c-bfb7-98d9865ea1dd') + + self.assert200(response) + self.assertIn(review['text'], response.json['top_reviews'][0]['text']) + + self.assertEqual(5, response.json['average_rating']) + self.assertEqual(1, response.json['reviews_count']) + + # Test for a work that doesn't exist + response = self.client.get('/work/0e5a48f3-7d21-365c-bfb7-98d9865ea1d1') + self.assert404(response) + + def test_work_user_reviews(self): + review = self.create_dummy_review() + response = self.client.get('/work/0e5a48f3-7d21-365c-bfb7-98d9865ea1dd?username=%s' % self.user.musicbrainz_username) + + self.assert200(response) + self.assertIn(review['text'], response.json['user_review']['text']) + + def test_user_cache_tracking(self): + track_key = cache.gen_key("entity_api", self.work_id2, self.user.musicbrainz_username, "user_review") + + # Make sure the cache is empty + self.client.get('/work/fd881e93-c199-4c86-81a7-cabc044948d0?username=%s' % self.user.musicbrainz_username) + cache_value = cache.get(track_key, namespace="Review") + self.assertEqual([], cache_value) + + review = self.create_dummy_review2() + + # Check if the cache is populated after the request + self.client.get('/work/fd881e93-c199-4c86-81a7-cabc044948d0?username=%s' % self.user.musicbrainz_username) + cache_value = cache.get(track_key, namespace="Review") + + self.assertIn(review['text'], cache_value['text']) From a7b39c2e414e2d609144f4de33e29967b129d116 Mon Sep 17 00:00:00 2001 From: anshg1214 Date: Thu, 14 Nov 2024 14:46:52 +0000 Subject: [PATCH 10/15] feat: Add a generic entity endpoint --- critiquebrainz/ws/__init__.py | 29 +--- critiquebrainz/ws/entity/__init__.py | 0 critiquebrainz/ws/entity/views.py | 251 +++++++++++++++++++++++++++ 3 files changed, 258 insertions(+), 22 deletions(-) create mode 100644 critiquebrainz/ws/entity/__init__.py create mode 100644 critiquebrainz/ws/entity/views.py diff --git a/critiquebrainz/ws/__init__.py b/critiquebrainz/ws/__init__.py index 06d8954e7..11b07a8c0 100644 --- a/critiquebrainz/ws/__init__.py +++ b/critiquebrainz/ws/__init__.py @@ -98,6 +98,7 @@ def create_app(debug=None, config_path=None): app.config["JSONIFY_PRETTYPRINT_REGULAR"] = False + _register_converters(app) _register_blueprints(app) return app @@ -114,30 +115,14 @@ def _register_blueprints(app): from critiquebrainz.ws.review.views import review_bp from critiquebrainz.ws.user.views import user_bp from critiquebrainz.ws.review.bulk import bulk_review_bp - from critiquebrainz.ws.artist.views import artist_bp - from critiquebrainz.ws.label.views import label_bp - from critiquebrainz.ws.event.views import event_bp - from critiquebrainz.ws.place.views import place_bp - from critiquebrainz.ws.recording.views import recording_bp - from critiquebrainz.ws.release_group.views import release_group_bp - from critiquebrainz.ws.work.views import work_bp - from critiquebrainz.ws.bb_author.views import author_bp - from critiquebrainz.ws.bb_edition_group.views import edition_group_bp - from critiquebrainz.ws.bb_literary_work.views import literary_work_bp - from critiquebrainz.ws.bb_series.views import series_bp + from critiquebrainz.ws.entity.views import entity_bp app.register_blueprint(oauth_bp, url_prefix="/oauth") app.register_blueprint(review_bp, url_prefix="/review") app.register_blueprint(user_bp, url_prefix="/user") app.register_blueprint(bulk_review_bp, url_prefix="/reviews") - app.register_blueprint(artist_bp, url_prefix="/artist") - app.register_blueprint(label_bp, url_prefix="/label") - app.register_blueprint(event_bp, url_prefix="/event") - app.register_blueprint(place_bp, url_prefix="/place") - app.register_blueprint(recording_bp, url_prefix="/recording") - app.register_blueprint(release_group_bp, url_prefix="/release-group") - app.register_blueprint(work_bp, url_prefix="/work") - app.register_blueprint(author_bp, url_prefix="/author") - app.register_blueprint(edition_group_bp, url_prefix="/edition-group") - app.register_blueprint(literary_work_bp, url_prefix="/literary-work") - app.register_blueprint(series_bp, url_prefix="/series") + app.register_blueprint(entity_bp) + +def _register_converters(app): + from critiquebrainz.ws.entity.views import EntityNameConverter + app.url_map.converters['entity_name'] = EntityNameConverter diff --git a/critiquebrainz/ws/entity/__init__.py b/critiquebrainz/ws/entity/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/critiquebrainz/ws/entity/views.py b/critiquebrainz/ws/entity/views.py new file mode 100644 index 000000000..2a5cb0e47 --- /dev/null +++ b/critiquebrainz/ws/entity/views.py @@ -0,0 +1,251 @@ +from flask import Blueprint, jsonify +import critiquebrainz.db.review as db_review +import critiquebrainz.db.users as db_users +import critiquebrainz.db.rating_stats as db_rating_stats +from critiquebrainz.frontend.external.entities import get_entity_by_id +from critiquebrainz.decorators import crossdomain +from critiquebrainz.ws.exceptions import NotFound +from critiquebrainz.ws.parser import Parser +from critiquebrainz.ws import REVIEWS_LIMIT, REVIEW_CACHE_NAMESPACE, REVIEW_CACHE_TIMEOUT +from brainzutils import cache +from werkzeug.routing import BaseConverter + +entity_bp = Blueprint('ws_entity', __name__) + +ENTITY_URL_TYPE_MAPPING = { + "release-group": "release_group", + "artist": "artist", + "label": "label", + "place": "place", + "event": "event", + "work": "work", + "recording": "recording", + "series": "bb_series", + "edition-proup": "bb_edition_group", + "literary-work": "bb_literary_work", + "author": "bb_author", +} + +class EntityNameConverter(BaseConverter): + """This converter only accepts valid entity names from ENTITY_URL_TYPE_MAPPING + + Rule('/') + """ + + def __init__(self, url_map): + super().__init__(url_map) + # Create regex pattern from valid entity names + entity_names = '|'.join(ENTITY_URL_TYPE_MAPPING.keys()) + self.regex = f"({entity_names})" + + def to_python(self, value): + return value + + def to_url(self, value): + return str(value) + + +@entity_bp.route('//', methods=['GET', 'OPTIONS']) +@crossdomain(headers="Authorization, Content-Type") +def entity_handler(entity_name, entity_id): + """Get list of reviews. + + **Request Example:** + + .. code-block:: bash + + $ curl "https://critiquebrainz.org/ws/1//e6f48cbd-26de-4c2e-a24a-29892f9eb3be" \\ + -X GET + + **Response Example:** + + .. code-block:: json + + { + "average_rating": 4.0, + "latest_reviews": [ + { + "created": "Tue, 16 Aug 2022 11:26:58 GMT", + "edits": 0, + "entity_id": "e6f48cbd-26de-4c2e-a24a-29892f9eb3be", + "entity_type": "bb_series", + "full_name": "Creative Commons Attribution-ShareAlike 3.0 Unported", + "id": "998512d8-0d6b-4c76-bfb7-0ec666e3fa0a", + "info_url": "https://creativecommons.org/licenses/by-sa/3.0/", + "is_draft": false, + "is_hidden": false, + "language": "en", + "last_revision": { + "id": 11773, + "rating": 4, + "review_id": "998512d8-0d6b-4c76-bfb7-0ec666e3fa0a", + "text": null, + "timestamp": "Tue, 16 Aug 2022 11:26:58 GMT" + }, + "last_updated": "Tue, 16 Aug 2022 11:26:58 GMT", + "license_id": "CC BY-SA 3.0", + "popularity": 0, + "published_on": "Tue, 16 Aug 2022 11:26:58 GMT", + "rating": 4, + "source": null, + "source_url": null, + "text": null, + "user": { + "created": "Tue, 18 Jan 2022 09:53:49 GMT", + "display_name": "Ansh Goyal", + "id": "11a1160e-d607-4882-8a82-e2e800f664fe", + "karma": 0, + "user_type": "Noob" + }, + "votes_negative_count": 0, + "votes_positive_count": 0 + } + ], + "ratings_stats": { + "1": 0, + "2": 0, + "3": 0, + "4": 1, + "5": 0 + }, + "reviews_count": 1, + "entity": { + "bbid": "e6f48cbd-26de-4c2e-a24a-29892f9eb3be", + "disambiguation": "English", + "identifier_set_id": null, + "identifiers": null, + "name": "Harry Potter", + "relationship_set_id": 151767, + "series_ordering_type": "Automatic", + "series_type": "Work", + "sort_name": "Harry Potter" + }, + "top_reviews": [ + { + "created": "Tue, 16 Aug 2022 11:26:58 GMT", + "edits": 0, + "entity_id": "e6f48cbd-26de-4c2e-a24a-29892f9eb3be", + "entity_type": "bb_series", + "full_name": "Creative Commons Attribution-ShareAlike 3.0 Unported", + "id": "998512d8-0d6b-4c76-bfb7-0ec666e3fa0a", + "info_url": "https://creativecommons.org/licenses/by-sa/3.0/", + "is_draft": false, + "is_hidden": false, + "language": "en", + "last_revision": { + "id": 11773, + "rating": 4, + "review_id": "998512d8-0d6b-4c76-bfb7-0ec666e3fa0a", + "text": null, + "timestamp": "Tue, 16 Aug 2022 11:26:58 GMT" + }, + "last_updated": "Tue, 16 Aug 2022 11:26:58 GMT", + "license_id": "CC BY-SA 3.0", + "popularity": 0, + "published_on": "Tue, 16 Aug 2022 11:26:58 GMT", + "rating": 4, + "source": null, + "source_url": null, + "text": null, + "user": { + "created": "Tue, 18 Jan 2022 09:53:49 GMT", + "display_name": "Ansh Goyal", + "id": "11a1160e-d607-4882-8a82-e2e800f664fe", + "karma": 0, + "user_type": "Noob" + }, + "votes_negative_count": 0, + "votes_positive_count": 0 + } + ] + } + + :statuscode 200: no error + :statuscode 404: series not found + + :query username: User's username **(optional)** + + :resheader Content-Type: *application/json* + """ + + entity_type = ENTITY_URL_TYPE_MAPPING.get(entity_name) + entity = get_entity_by_id(entity_id, entity_type) + if not entity: + raise NotFound("Can't find a {entity_name} with ID: {entity_id}" + .format(entity_name=entity_name, entity_id=entity_id)) + + user_review = [] + username = Parser.string('uri', 'username', optional=True) + if username: + user_review_cache_key = cache.gen_key('entity_api', entity_id, username, "user_review") + user_review = cache.get(user_review_cache_key, REVIEW_CACHE_NAMESPACE) + if not user_review: + user = db_users.get_by_mbid(username) + if user: + user_id = user['id'] + + user_review, _ = db_review.list_reviews( + entity_id=entity_id, + entity_type=entity_type, + user_id=user_id + ) + if user_review: + user_review = db_review.to_dict(user_review[0]) + + cache.set(user_review_cache_key, user_review, + expirein=REVIEW_CACHE_TIMEOUT, namespace=REVIEW_CACHE_NAMESPACE) + + else: + user_review = [] + + ratings_stats, average_rating = db_rating_stats.get_stats(entity_id, entity_type) + + top_reviews_cache_key = cache.gen_key(f"entity_api_{entity_type}", entity_id, "top_reviews") + top_reviews_cached_result = cache.get(top_reviews_cache_key, REVIEW_CACHE_NAMESPACE) + + if top_reviews_cached_result: + top_reviews, reviews_count = top_reviews_cached_result + else: + top_reviews, reviews_count = db_review.list_reviews( + entity_id=entity_id, + entity_type=entity_type, + sort='popularity', + limit=REVIEWS_LIMIT, + offset=0, + ) + top_reviews = [db_review.to_dict(review) for review in top_reviews] + + cache.set(top_reviews_cache_key, (top_reviews, reviews_count), + expirein=REVIEW_CACHE_TIMEOUT, namespace=REVIEW_CACHE_NAMESPACE) + + latest_reviews_cache_key = cache.gen_key(f"entity_api_{entity_type}", entity_id, "latest_reviews") + latest_reviews_cached_result = cache.get(latest_reviews_cache_key, REVIEW_CACHE_NAMESPACE) + + if latest_reviews_cached_result: + latest_reviews, reviews_count = latest_reviews_cached_result + else: + latest_reviews, reviews_count = db_review.list_reviews( + entity_id=entity_id, + entity_type=entity_type, + sort='published_on', + limit=REVIEWS_LIMIT, + offset=0, + ) + latest_reviews = [db_review.to_dict(review) for review in latest_reviews] + + cache.set(latest_reviews_cache_key, (latest_reviews, reviews_count), + expirein=REVIEW_CACHE_TIMEOUT, namespace=REVIEW_CACHE_NAMESPACE) + + result = { + "entity": entity, + "average_rating": average_rating, + "ratings_stats": ratings_stats, + "reviews_count": reviews_count, + "top_reviews": top_reviews, + "latest_reviews": latest_reviews + } + + if username: + result['user_review'] = user_review + + return jsonify(**result) From 13e0218f1849643aa51ef2484871b825e4cce84c Mon Sep 17 00:00:00 2001 From: anshg1214 Date: Fri, 15 Nov 2024 14:17:39 +0000 Subject: [PATCH 11/15] refactor: Refactor Entity endpoint --- critiquebrainz/db/review.py | 14 ++--- critiquebrainz/ws/entity/views.py | 92 +++++++++++++------------------ 2 files changed, 45 insertions(+), 61 deletions(-) diff --git a/critiquebrainz/db/review.py b/critiquebrainz/db/review.py index 77ca1ed57..e1d72722d 100644 --- a/critiquebrainz/db/review.py +++ b/critiquebrainz/db/review.py @@ -408,16 +408,16 @@ def invalidate_ws_entity_cache(entity_id, entity_type, user_id): cache.delete_many(cache_keys_to_delete, namespace=REVIEW_CACHE_NAMESPACE) cache.delete(cache_keys_for_no_entity_id_key, namespace=REVIEW_CACHE_NAMESPACE) - cache_keys_for_top_reviews_key = cache.gen_key('entity_api', entity_type, entity_id, "top_reviews") - cache_keys_for_latest_reviews_key = cache.gen_key('entity_api', entity_type, entity_id, "latest_reviews") - cache.delete(cache_keys_for_top_reviews_key, namespace=REVIEW_CACHE_NAMESPACE) - cache.delete(cache_keys_for_latest_reviews_key, namespace=REVIEW_CACHE_NAMESPACE) + # Invalidate top and latest reviews caches + for sort_type in ['popularity', 'published_on']: + cache_key = cache.gen_key(f'entity_api_{entity_type}', entity_id, f"{sort_type}_reviews") + cache.delete(cache_key, namespace=REVIEW_CACHE_NAMESPACE) user = db_users.get_by_id(user_id) - if 'musicbrainz_username' in user.keys() and user['musicbrainz_username']: + if user and 'musicbrainz_username' in user.keys() and user['musicbrainz_username']: username = user["musicbrainz_username"] - cache_keys_for_user_reviews_key = cache.gen_key('entity_api', entity_id, username, "user_review") - cache.delete(cache_keys_for_user_reviews_key, namespace=REVIEW_CACHE_NAMESPACE) + cache_key = cache.gen_key('entity_api', entity_id, entity_type, username, "user_review") + cache.delete(cache_key, namespace=REVIEW_CACHE_NAMESPACE) # pylint: disable=too-many-branches diff --git a/critiquebrainz/ws/entity/views.py b/critiquebrainz/ws/entity/views.py index 2a5cb0e47..9cb84912d 100644 --- a/critiquebrainz/ws/entity/views.py +++ b/critiquebrainz/ws/entity/views.py @@ -45,9 +45,32 @@ def to_url(self, value): return str(value) +def _get_cached_reviews(entity_id: str, entity_type: str, sort_type: str, + cache_namespace: str = REVIEW_CACHE_NAMESPACE) -> tuple[list, int]: + """Helper function to fetch and cache reviews.""" + cache_key = cache.gen_key(f"entity_api_{entity_type}", entity_id, f"{sort_type}_reviews") + cached_result = cache.get(cache_key, cache_namespace) + + if cached_result: + return cached_result + + reviews, count = db_review.list_reviews( + entity_id=entity_id, + entity_type=entity_type, + sort=sort_type, + limit=REVIEWS_LIMIT, + offset=0, + ) + reviews = [db_review.to_dict(review) for review in reviews] + + cache.set(cache_key, (reviews, count), + expirein=REVIEW_CACHE_TIMEOUT, namespace=cache_namespace) + return reviews, count + + @entity_bp.route('//', methods=['GET', 'OPTIONS']) @crossdomain(headers="Authorization, Content-Type") -def entity_handler(entity_name, entity_id): +def entity_handler(entity_name: str, entity_id: str): """Get list of reviews. **Request Example:** @@ -171,70 +194,31 @@ def entity_handler(entity_name, entity_id): entity_type = ENTITY_URL_TYPE_MAPPING.get(entity_name) entity = get_entity_by_id(entity_id, entity_type) if not entity: - raise NotFound("Can't find a {entity_name} with ID: {entity_id}" - .format(entity_name=entity_name, entity_id=entity_id)) + raise NotFound(f"Can't find a {entity_name} with ID: {entity_id}") + # Get user review if username provided user_review = [] username = Parser.string('uri', 'username', optional=True) if username: - user_review_cache_key = cache.gen_key('entity_api', entity_id, username, "user_review") - user_review = cache.get(user_review_cache_key, REVIEW_CACHE_NAMESPACE) + cache_key = cache.gen_key('entity_api', entity_id, entity_type, username, "user_review") + user_review = cache.get(cache_key, REVIEW_CACHE_NAMESPACE) + if not user_review: user = db_users.get_by_mbid(username) if user: - user_id = user['id'] - - user_review, _ = db_review.list_reviews( + reviews, _ = db_review.list_reviews( entity_id=entity_id, entity_type=entity_type, - user_id=user_id + user_id=user['id'] ) - if user_review: - user_review = db_review.to_dict(user_review[0]) - - cache.set(user_review_cache_key, user_review, - expirein=REVIEW_CACHE_TIMEOUT, namespace=REVIEW_CACHE_NAMESPACE) - - else: - user_review = [] + user_review = db_review.to_dict(reviews[0]) if reviews else [] + cache.set(cache_key, user_review, + expirein=REVIEW_CACHE_TIMEOUT, namespace=REVIEW_CACHE_NAMESPACE) + # Get ratings and reviews ratings_stats, average_rating = db_rating_stats.get_stats(entity_id, entity_type) - - top_reviews_cache_key = cache.gen_key(f"entity_api_{entity_type}", entity_id, "top_reviews") - top_reviews_cached_result = cache.get(top_reviews_cache_key, REVIEW_CACHE_NAMESPACE) - - if top_reviews_cached_result: - top_reviews, reviews_count = top_reviews_cached_result - else: - top_reviews, reviews_count = db_review.list_reviews( - entity_id=entity_id, - entity_type=entity_type, - sort='popularity', - limit=REVIEWS_LIMIT, - offset=0, - ) - top_reviews = [db_review.to_dict(review) for review in top_reviews] - - cache.set(top_reviews_cache_key, (top_reviews, reviews_count), - expirein=REVIEW_CACHE_TIMEOUT, namespace=REVIEW_CACHE_NAMESPACE) - - latest_reviews_cache_key = cache.gen_key(f"entity_api_{entity_type}", entity_id, "latest_reviews") - latest_reviews_cached_result = cache.get(latest_reviews_cache_key, REVIEW_CACHE_NAMESPACE) - - if latest_reviews_cached_result: - latest_reviews, reviews_count = latest_reviews_cached_result - else: - latest_reviews, reviews_count = db_review.list_reviews( - entity_id=entity_id, - entity_type=entity_type, - sort='published_on', - limit=REVIEWS_LIMIT, - offset=0, - ) - latest_reviews = [db_review.to_dict(review) for review in latest_reviews] - - cache.set(latest_reviews_cache_key, (latest_reviews, reviews_count), - expirein=REVIEW_CACHE_TIMEOUT, namespace=REVIEW_CACHE_NAMESPACE) + top_reviews, reviews_count = _get_cached_reviews(entity_id, entity_type, 'popularity') + latest_reviews, _ = _get_cached_reviews(entity_id, entity_type, 'published_on') result = { "entity": entity, @@ -248,4 +232,4 @@ def entity_handler(entity_name, entity_id): if username: result['user_review'] = user_review - return jsonify(**result) + return jsonify(result) From 8d9dbf81ecb0f6a1e011f2d2a8f4490952d52222 Mon Sep 17 00:00:00 2001 From: anshg1214 Date: Fri, 15 Nov 2024 14:49:45 +0000 Subject: [PATCH 12/15] feat: Delete endpoints which are not required --- critiquebrainz/ws/artist/__init__.py | 0 critiquebrainz/ws/artist/test/__init__.py | 0 critiquebrainz/ws/artist/test/views_test.py | 89 ---- critiquebrainz/ws/artist/views.py | 311 ------------- critiquebrainz/ws/bb_author/__init__.py | 0 critiquebrainz/ws/bb_author/test/__init__.py | 0 .../ws/bb_author/test/views_test.py | 88 ---- critiquebrainz/ws/bb_author/views.py | 248 ----------- .../ws/bb_edition_group/__init__.py | 0 .../ws/bb_edition_group/test/__init__.py | 0 .../ws/bb_edition_group/test/views_test.py | 88 ---- critiquebrainz/ws/bb_edition_group/views.py | 226 ---------- .../ws/bb_literary_work/__init__.py | 0 .../ws/bb_literary_work/test/__init__.py | 0 .../ws/bb_literary_work/test/views_test.py | 89 ---- critiquebrainz/ws/bb_literary_work/views.py | 246 ----------- critiquebrainz/ws/bb_series/__init__.py | 0 critiquebrainz/ws/bb_series/test/__init__.py | 0 .../ws/bb_series/test/views_test.py | 88 ---- critiquebrainz/ws/bb_series/views.py | 219 ---------- critiquebrainz/ws/event/__init__.py | 0 critiquebrainz/ws/event/test/__init__.py | 0 critiquebrainz/ws/event/test/views_test.py | 88 ---- critiquebrainz/ws/event/views.py | 338 --------------- critiquebrainz/ws/label/__init__.py | 0 critiquebrainz/ws/label/test/__init__.py | 0 critiquebrainz/ws/label/test/views_test.py | 88 ---- critiquebrainz/ws/label/views.py | 253 ----------- critiquebrainz/ws/place/__init__.py | 0 critiquebrainz/ws/place/test/__init__.py | 0 critiquebrainz/ws/place/test/views_test.py | 88 ---- critiquebrainz/ws/place/views.py | 407 ------------------ critiquebrainz/ws/recording/__init__.py | 0 critiquebrainz/ws/recording/test/__init__.py | 0 .../ws/recording/test/views_test.py | 88 ---- critiquebrainz/ws/recording/views.py | 221 ---------- critiquebrainz/ws/release_group/__init__.py | 0 .../ws/release_group/test/__init__.py | 0 .../ws/release_group/test/views_test.py | 88 ---- critiquebrainz/ws/release_group/views.py | 234 ---------- critiquebrainz/ws/work/__init__.py | 0 critiquebrainz/ws/work/test/__init__.py | 0 critiquebrainz/ws/work/test/views_test.py | 88 ---- critiquebrainz/ws/work/views.py | 297 ------------- 44 files changed, 3970 deletions(-) delete mode 100644 critiquebrainz/ws/artist/__init__.py delete mode 100644 critiquebrainz/ws/artist/test/__init__.py delete mode 100644 critiquebrainz/ws/artist/test/views_test.py delete mode 100644 critiquebrainz/ws/artist/views.py delete mode 100644 critiquebrainz/ws/bb_author/__init__.py delete mode 100644 critiquebrainz/ws/bb_author/test/__init__.py delete mode 100644 critiquebrainz/ws/bb_author/test/views_test.py delete mode 100644 critiquebrainz/ws/bb_author/views.py delete mode 100644 critiquebrainz/ws/bb_edition_group/__init__.py delete mode 100644 critiquebrainz/ws/bb_edition_group/test/__init__.py delete mode 100644 critiquebrainz/ws/bb_edition_group/test/views_test.py delete mode 100644 critiquebrainz/ws/bb_edition_group/views.py delete mode 100644 critiquebrainz/ws/bb_literary_work/__init__.py delete mode 100644 critiquebrainz/ws/bb_literary_work/test/__init__.py delete mode 100644 critiquebrainz/ws/bb_literary_work/test/views_test.py delete mode 100644 critiquebrainz/ws/bb_literary_work/views.py delete mode 100644 critiquebrainz/ws/bb_series/__init__.py delete mode 100644 critiquebrainz/ws/bb_series/test/__init__.py delete mode 100644 critiquebrainz/ws/bb_series/test/views_test.py delete mode 100644 critiquebrainz/ws/bb_series/views.py delete mode 100644 critiquebrainz/ws/event/__init__.py delete mode 100644 critiquebrainz/ws/event/test/__init__.py delete mode 100644 critiquebrainz/ws/event/test/views_test.py delete mode 100644 critiquebrainz/ws/event/views.py delete mode 100644 critiquebrainz/ws/label/__init__.py delete mode 100644 critiquebrainz/ws/label/test/__init__.py delete mode 100644 critiquebrainz/ws/label/test/views_test.py delete mode 100644 critiquebrainz/ws/label/views.py delete mode 100644 critiquebrainz/ws/place/__init__.py delete mode 100644 critiquebrainz/ws/place/test/__init__.py delete mode 100644 critiquebrainz/ws/place/test/views_test.py delete mode 100644 critiquebrainz/ws/place/views.py delete mode 100644 critiquebrainz/ws/recording/__init__.py delete mode 100644 critiquebrainz/ws/recording/test/__init__.py delete mode 100644 critiquebrainz/ws/recording/test/views_test.py delete mode 100644 critiquebrainz/ws/recording/views.py delete mode 100644 critiquebrainz/ws/release_group/__init__.py delete mode 100644 critiquebrainz/ws/release_group/test/__init__.py delete mode 100644 critiquebrainz/ws/release_group/test/views_test.py delete mode 100644 critiquebrainz/ws/release_group/views.py delete mode 100644 critiquebrainz/ws/work/__init__.py delete mode 100644 critiquebrainz/ws/work/test/__init__.py delete mode 100644 critiquebrainz/ws/work/test/views_test.py delete mode 100644 critiquebrainz/ws/work/views.py diff --git a/critiquebrainz/ws/artist/__init__.py b/critiquebrainz/ws/artist/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/critiquebrainz/ws/artist/test/__init__.py b/critiquebrainz/ws/artist/test/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/critiquebrainz/ws/artist/test/views_test.py b/critiquebrainz/ws/artist/test/views_test.py deleted file mode 100644 index ba7915891..000000000 --- a/critiquebrainz/ws/artist/test/views_test.py +++ /dev/null @@ -1,89 +0,0 @@ -from brainzutils import cache - -import critiquebrainz.db.license as db_license -import critiquebrainz.db.review as db_review -import critiquebrainz.db.users as db_users -from critiquebrainz.db.user import User -from critiquebrainz.ws.testing import WebServiceTestCase - - -class ArtistViewsTestCase(WebServiceTestCase): - - def setUp(self): - super(ArtistViewsTestCase, self).setUp() - - self.artist_id1 = "f59c5520-5f46-4d2c-b2c4-822eabf53419" - self.artist_id2 = "83d91898-7763-47d7-b03b-b92132375c47" - - self.user = User(db_users.get_or_create(1, "Tester", new_user_data={ - "display_name": "test user", - })) - self.another_user = User(db_users.get_or_create(2, "Hacker!", new_user_data={ - "display_name": "test hacker", - })) - self.license = db_license.create( - id="CC BY-SA 3.0", - full_name="Created so we can fill the form correctly.", - ) - self.review = dict( - entity_id=self.artist_id1, - entity_type='artist', - user_id=self.user.id, - text="Testing! This text should be on the page.", - rating=5, - is_draft=False, - license_id=self.license["id"], - ) - self.review2 = dict( - entity_id=self.artist_id2, - entity_type='artist', - user_id=self.user.id, - text="Testing! This text should be on the page.", - rating=5, - is_draft=False, - license_id=self.license["id"], - ) - - def create_dummy_review(self): - return db_review.create(**self.review) - - def create_dummy_review2(self): - return db_review.create(**self.review2) - - def test_artist_endpoint(self): - review = self.create_dummy_review() - response = self.client.get('/artist/f59c5520-5f46-4d2c-b2c4-822eabf53419') - - self.assert200(response) - self.assertIn(review['text'], response.json['top_reviews'][0]['text']) - - self.assertEqual(5, response.json['average_rating']) - self.assertEqual(1, response.json['reviews_count']) - - # Test for an artist which does not exist - response = self.client.get('/artist/f59c5520-5f46-4d2c-b2c4-822eabf53417') - self.assert404(response) - - def test_artist_user_reviews(self): - review = self.create_dummy_review() - response = self.client.get('/artist/f59c5520-5f46-4d2c-b2c4-822eabf53419?username=%s' % self.user.musicbrainz_username) - - self.assert200(response) - self.assertIn(review['text'], response.json['user_review']['text']) - - def test_user_cache_tracking(self): - track_key = cache.gen_key("entity_api", self.artist_id2, self.user.musicbrainz_username, "user_review") - - # Make sure the cache is empty - self.client.get('/artist/83d91898-7763-47d7-b03b-b92132375c47?username=%s' % self.user.musicbrainz_username) - cache_value = cache.get(track_key, namespace="Review") - self.assertEqual([], cache_value) - - review = self.create_dummy_review2() - - # Check if the cache is populated after the request - self.client.get('/artist/83d91898-7763-47d7-b03b-b92132375c47?username=%s' % self.user.musicbrainz_username) - cache_value = cache.get(track_key, namespace="Review") - self.assertTrue(cache_value is not None) - - self.assertIn(review['text'], cache_value['text']) diff --git a/critiquebrainz/ws/artist/views.py b/critiquebrainz/ws/artist/views.py deleted file mode 100644 index a36ed7354..000000000 --- a/critiquebrainz/ws/artist/views.py +++ /dev/null @@ -1,311 +0,0 @@ -from flask import Blueprint, jsonify -import critiquebrainz.db.review as db_review -import critiquebrainz.db.users as db_users -import critiquebrainz.db.rating_stats as db_rating_stats -from critiquebrainz.frontend.external.musicbrainz_db import artist as db_artist -from critiquebrainz.decorators import crossdomain -from critiquebrainz.ws.exceptions import NotFound -from critiquebrainz.ws.parser import Parser -from critiquebrainz.ws import REVIEWS_LIMIT, REVIEW_CACHE_NAMESPACE, REVIEW_CACHE_TIMEOUT -from brainzutils import cache - -artist_bp = Blueprint('ws_artist', __name__) - - -@artist_bp.route('/', methods=['GET', 'OPTIONS']) -@crossdomain(headers="Authorization, Content-Type") -def artist_entity_handler(artist_mbid): - """Get list of reviews. - - **Request Example:** - - .. code-block:: bash - - $ curl "https://critiquebrainz.org/ws/1/artist/df602ea4-c143-425d-a235-d7641f7634fd" \\ - -X GET - - **Response Example:** - - .. code-block:: json - - { - "artist": { - "artist-rels": [ - { - "artist": { - "life-span": { - "begin": "1981-07-17" - }, - "mbid": "60bc1e7d-d974-4205-bc9f-9dd3dba93534", - "name": "Sergey Semenov", - "sort_name": "Sergey Semenov", - "type": "Person" - }, - "begin-year": 2011, - "direction": "backward", - "end-year": null, - "type": "member of band", - "type-id": "5be4c609-9afa-4ea0-910b-12ffb71e3821" - }, - { - "artist": { - "life-span": { - "begin": "2020-10-01" - }, - "mbid": "ba2f69b0-dcda-4f81-b324-17f73f36980c", - "name": "DJ Repeet", - "sort_name": "Repeet, DJ", - "type": "Character" - }, - "begin-year": 2020, - "direction": "backward", - "end-year": null, - "type": "member of band", - "type-id": "5be4c609-9afa-4ea0-910b-12ffb71e3821" - } - ], - "band-members": [ - { - "artist": { - "life-span": { - "begin": "1981-07-17" - }, - "mbid": "60bc1e7d-d974-4205-bc9f-9dd3dba93534", - "name": "Sergey Semenov", - "sort_name": "Sergey Semenov", - "type": "Person" - }, - "begin-year": 2011, - "direction": "backward", - "end-year": null, - "type": "member of band", - "type-id": "5be4c609-9afa-4ea0-910b-12ffb71e3821" - }, - { - "artist": { - "life-span": { - "begin": "2020-10-01" - }, - "mbid": "ba2f69b0-dcda-4f81-b324-17f73f36980c", - "name": "DJ Repeet", - "sort_name": "Repeet, DJ", - "type": "Character" - }, - "begin-year": 2020, - "direction": "backward", - "end-year": null, - "type": "member of band", - "type-id": "5be4c609-9afa-4ea0-910b-12ffb71e3821" - } - ], - "external-urls": [ - { - "begin-year": null, - "direction": "forward", - "end-year": null, - "icon": "discogs-16.png", - "name": "Discogs", - "type": "discogs", - "type-id": "04a5b104-a4c2-4bac-99a1-7b837c37d9e4", - "url": { - "mbid": "694f02d8-1be0-4c69-ae04-4c63686d3fdb", - "url": "https://www.discogs.com/artist/10091563" - } - } - { - "begin-year": null, - "direction": "forward", - "end-year": null, - "icon": "wikidata-16.png", - "name": "Wikidata", - "type": "wikidata", - "type-id": "689870a4-a1e4-4912-b17f-7b2664215698", - "url": { - "mbid": "bddc058d-4cb6-4ced-a05c-de3bf2b60692", - "url": "https://www.wikidata.org/wiki/Q109645393" - } - } - ], - "life-span": { - "begin": "2011-07-17" - }, - "mbid": "df602ea4-c143-425d-a235-d7641f7634fd", - "name": "Senkino", - "sort_name": "Senkino", - "type": "Group" - }, - "average_rating": 5.0, - "latest_reviews": [ - { - "created": "Wed, 24 Nov 2021 02:59:15 GMT", - "edits": 0, - "entity_id": "df602ea4-c143-425d-a235-d7641f7634fd", - "entity_type": "artist", - "full_name": "Creative Commons Attribution-ShareAlike 3.0 Unported", - "id": "0a3548a7-a014-4e71-a65b-d45fce677cf0", - "info_url": "https://creativecommons.org/licenses/by-sa/3.0/", - "is_draft": false, - "is_hidden": false, - "language": "en", - "last_revision": { - "id": 11139, - "rating": 5, - "review_id": "0a3548a7-a014-4e71-a65b-d45fce677cf0", - "text": null, - "timestamp": "Wed, 24 Nov 2021 02:59:15 GMT" - }, - "last_updated": "Wed, 24 Nov 2021 02:59:15 GMT", - "license_id": "CC BY-SA 3.0", - "popularity": 0, - "published_on": "Wed, 24 Nov 2021 02:59:15 GMT", - "rating": 5, - "source": null, - "source_url": null, - "text": null, - "user": { - "created": "Wed, 24 Nov 2021 02:56:16 GMT", - "display_name": "Rada87", - "id": "5e784575-4bb8-4a9f-8334-2cb87f073cf0", - "karma": 0, - "user_type": "Noob" - }, - "votes_negative_count": 0, - "votes_positive_count": 0 - } - ], - "ratings_stats": { - "1": 0, - "2": 0, - "3": 0, - "4": 0, - "5": 1 - }, - "reviews_count": 1, - "top_reviews": [ - { - "created": "Wed, 24 Nov 2021 02:59:15 GMT", - "edits": 0, - "entity_id": "df602ea4-c143-425d-a235-d7641f7634fd", - "entity_type": "artist", - "full_name": "Creative Commons Attribution-ShareAlike 3.0 Unported", - "id": "0a3548a7-a014-4e71-a65b-d45fce677cf0", - "info_url": "https://creativecommons.org/licenses/by-sa/3.0/", - "is_draft": false, - "is_hidden": false, - "language": "en", - "last_revision": { - "id": 11139, - "rating": 5, - "review_id": "0a3548a7-a014-4e71-a65b-d45fce677cf0", - "text": null, - "timestamp": "Wed, 24 Nov 2021 02:59:15 GMT" - }, - "last_updated": "Wed, 24 Nov 2021 02:59:15 GMT", - "license_id": "CC BY-SA 3.0", - "popularity": 0, - "published_on": "Wed, 24 Nov 2021 02:59:15 GMT", - "rating": 5, - "source": null, - "source_url": null, - "text": null, - "user": { - "created": "Wed, 24 Nov 2021 02:56:16 GMT", - "display_name": "Rada87", - "id": "5e784575-4bb8-4a9f-8334-2cb87f073cf0", - "karma": 0, - "user_type": "Noob" - }, - "votes_negative_count": 0, - "votes_positive_count": 0 - } - ] - } - - :statuscode 200: no error - :statuscode 404: artist not found - - :query username: User's username **(optional)** - - :resheader Content-Type: *application/json* - """ - - artist = db_artist.get_artist_by_mbid(str(artist_mbid)) - if not artist: - raise NotFound("Can't find an artist with ID: {artist_mbid}".format(artist_mbid=artist_mbid)) - - user_review = None - - username = Parser.string('uri', 'username', optional=True) - if username: - user_review_cache_key = cache.gen_key('entity_api', artist['mbid'], username, "user_review") - user_review = cache.get(user_review_cache_key, REVIEW_CACHE_NAMESPACE) - if not user_review: - user = db_users.get_by_mbid(username) - if user: - user_id = user['id'] - - user_review, _ = db_review.list_reviews( - entity_id=artist['mbid'], - entity_type='artist', - user_id=user_id - ) - if user_review: - user_review = db_review.to_dict(user_review[0]) - - cache.set(user_review_cache_key, user_review, - expirein=REVIEW_CACHE_TIMEOUT, namespace=REVIEW_CACHE_NAMESPACE) - - else: - user_review = [] - - ratings_stats, average_rating = db_rating_stats.get_stats(artist_mbid, "artist") - - top_reviews_cache_key = cache.gen_key("entity_api_artist", artist['mbid'], "top_reviews") - top_reviews_cached_result = cache.get(top_reviews_cache_key, REVIEW_CACHE_NAMESPACE) - - if top_reviews_cached_result: - top_reviews, reviews_count = top_reviews_cached_result - else: - top_reviews, reviews_count = db_review.list_reviews( - entity_id=artist['mbid'], - entity_type='artist', - sort='popularity', - limit=REVIEWS_LIMIT, - offset=0, - ) - top_reviews = [db_review.to_dict(review) for review in top_reviews] - - cache.set(top_reviews_cache_key, (top_reviews, reviews_count), - expirein=REVIEW_CACHE_TIMEOUT, namespace=REVIEW_CACHE_NAMESPACE) - - latest_reviews_cache_key = cache.gen_key("entity_api_artist", artist['mbid'], "latest_reviews") - latest_reviews_cached_result = cache.get(latest_reviews_cache_key, REVIEW_CACHE_NAMESPACE) - - if latest_reviews_cached_result: - latest_reviews, reviews_count = latest_reviews_cached_result - else: - latest_reviews, reviews_count = db_review.list_reviews( - entity_id=artist['mbid'], - entity_type='artist', - sort='published_on', - limit=REVIEWS_LIMIT, - offset=0, - ) - latest_reviews = [db_review.to_dict(review) for review in latest_reviews] - - cache.set(latest_reviews_cache_key, (latest_reviews, reviews_count), - expirein=REVIEW_CACHE_TIMEOUT, namespace=REVIEW_CACHE_NAMESPACE) - - result = { - "artist": artist, - "average_rating": average_rating, - "ratings_stats": ratings_stats, - "reviews_count": reviews_count, - "top_reviews": top_reviews, - "latest_reviews": latest_reviews - } - - if username: - result['user_review'] = user_review - - return jsonify(**result) diff --git a/critiquebrainz/ws/bb_author/__init__.py b/critiquebrainz/ws/bb_author/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/critiquebrainz/ws/bb_author/test/__init__.py b/critiquebrainz/ws/bb_author/test/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/critiquebrainz/ws/bb_author/test/views_test.py b/critiquebrainz/ws/bb_author/test/views_test.py deleted file mode 100644 index 18e4e9ca5..000000000 --- a/critiquebrainz/ws/bb_author/test/views_test.py +++ /dev/null @@ -1,88 +0,0 @@ -from brainzutils import cache - -import critiquebrainz.db.license as db_license -import critiquebrainz.db.review as db_review -import critiquebrainz.db.users as db_users -from critiquebrainz.db.user import User -from critiquebrainz.ws.testing import WebServiceTestCase - - -class AuthorViewsTestCase(WebServiceTestCase): - - def setUp(self): - super(AuthorViewsTestCase, self).setUp() - - self.author_id1 = "b8f9bd89-79d1-497e-811f-18576321111c" - self.author_id2 = "b2507eee-1391-47c5-93e6-ca972bd8e0e0" - - self.user = User(db_users.get_or_create(1, "Tester", new_user_data={ - "display_name": "test user", - })) - self.another_user = User(db_users.get_or_create(2, "Hacker!", new_user_data={ - "display_name": "test hacker", - })) - self.license = db_license.create( - id="CC BY-SA 3.0", - full_name="Created so we can fill the form correctly.", - ) - self.review = dict( - entity_id=self.author_id1, - entity_type='bb_author', - user_id=self.user.id, - text="Testing! This text should be on the page.", - rating=5, - is_draft=False, - license_id=self.license["id"], - ) - self.review2 = dict( - entity_id=self.author_id2, - entity_type='bb_author', - user_id=self.user.id, - text="Testing! This text should be on the page.", - rating=5, - is_draft=False, - license_id=self.license["id"], - ) - - def create_dummy_review(self): - return db_review.create(**self.review) - - def create_dummy_review2(self): - return db_review.create(**self.review2) - - def test_author_endpoint(self): - review = self.create_dummy_review() - response = self.client.get('/author/b8f9bd89-79d1-497e-811f-18576321111c') - - self.assert200(response) - self.assertIn(review['text'], response.json['top_reviews'][0]['text']) - - self.assertEqual(5, response.json['average_rating']) - self.assertEqual(1, response.json['reviews_count']) - - # Test for an author which does not exist - response = self.client.get('/author/b8f9bd89-79d1-497e-811f-18576321111g') - self.assert404(response) - - def test_author_user_reviews(self): - review = self.create_dummy_review() - response = self.client.get('/author/b8f9bd89-79d1-497e-811f-18576321111c?username=%s' % self.user.musicbrainz_username) - - self.assert200(response) - self.assertIn(review['text'], response.json['user_review']['text']) - - def test_user_cache_tracking(self): - track_key = cache.gen_key("entity_api", self.author_id2, self.user.musicbrainz_username, "user_review") - - # Make sure the cache is empty - self.client.get('/author/b2507eee-1391-47c5-93e6-ca972bd8e0e0?username=%s' % self.user.musicbrainz_username) - cache_value = cache.get(track_key, namespace="Review") - self.assertEqual([], cache_value) - - review = self.create_dummy_review2() - - # Check if the cache is populated after the request - self.client.get('/author/b2507eee-1391-47c5-93e6-ca972bd8e0e0?username=%s' % self.user.musicbrainz_username) - cache_value = cache.get(track_key, namespace="Review") - - self.assertIn(review['text'], cache_value['text']) diff --git a/critiquebrainz/ws/bb_author/views.py b/critiquebrainz/ws/bb_author/views.py deleted file mode 100644 index 0bdf6ae8f..000000000 --- a/critiquebrainz/ws/bb_author/views.py +++ /dev/null @@ -1,248 +0,0 @@ -from flask import Blueprint, jsonify -import critiquebrainz.db.review as db_review -import critiquebrainz.db.users as db_users -import critiquebrainz.db.rating_stats as db_rating_stats -from critiquebrainz.frontend.external.bookbrainz_db import author as db_author -from critiquebrainz.decorators import crossdomain -from critiquebrainz.ws.exceptions import NotFound -from critiquebrainz.ws.parser import Parser -from critiquebrainz.ws import REVIEWS_LIMIT, REVIEW_CACHE_NAMESPACE, REVIEW_CACHE_TIMEOUT -from brainzutils import cache - -author_bp = Blueprint('ws_author', __name__) - - -@author_bp.route('/', methods=['GET', 'OPTIONS']) -@crossdomain(headers="Authorization, Content-Type") -def author_entity_handler(author_bbid): - """Get list of reviews. - - **Request Example:** - - .. code-block:: bash - - $ curl "https://critiquebrainz.org/ws/1/author/e5c4e68b-bfce-4c77-9ca2-0f0a2d4d09f0" \\ - -X GET - - **Response Example:** - - .. code-block:: json - - { - "author": { - "area_id": null, - "area_info": [], - "author_type": "Person", - "bbid": "e5c4e68b-bfce-4c77-9ca2-0f0a2d4d09f0", - "begin_area_id": 221, - "begin_day": 31, - "begin_month": 7, - "begin_year": 1965, - "disambiguation": null, - "end_area_id": null, - "end_day": null, - "end_month": null, - "end_year": null, - "ended": false, - "gender": "Female", - "identifier_set_id": 7401, - "identifiers": [ - { - "icon": "wikidata-16.png", - "name": "Wikidata ID", - "url": "https://www.wikidata.org/wiki/Q34660", - "value": "Q34660" - }, - { - "icon": "viaf-16.png", - "name": "VIAF", - "url": "https://viaf.org/viaf/116796842", - "value": "116796842" - }, - { - "icon": "wikidata-16.png", - "name": "Wikidata ID", - "url": "https://www.wikidata.org/wiki/Q1190608", - "value": "Q1190608" - } - ], - "name": "J. K. Rowling", - "relationship_set_id": 151715, - "sort_name": "Rowling, J. K." - }, - "average_rating": 5.0, - "latest_reviews": [ - { - "created": "Tue, 16 Aug 2022 11:25:44 GMT", - "edits": 0, - "entity_id": "e5c4e68b-bfce-4c77-9ca2-0f0a2d4d09f0", - "entity_type": "bb_author", - "full_name": "Creative Commons Attribution-ShareAlike 3.0 Unported", - "id": "326e702a-020f-40e7-b369-9142a7af4315", - "info_url": "https://creativecommons.org/licenses/by-sa/3.0/", - "is_draft": false, - "is_hidden": false, - "language": "en", - "last_revision": { - "id": 11785, - "rating": 5, - "review_id": "326e702a-020f-40e7-b369-9142a7af4315", - "text": null, - "timestamp": "Fri, 02 Sep 2022 09:54:13 GMT" - }, - "last_updated": "Fri, 02 Sep 2022 09:54:13 GMT", - "license_id": "CC BY-SA 3.0", - "popularity": 0, - "published_on": "Tue, 16 Aug 2022 11:25:44 GMT", - "rating": 5, - "source": null, - "source_url": null, - "text": null, - "user": { - "created": "Tue, 18 Jan 2022 09:53:49 GMT", - "display_name": "Ansh Goyal", - "id": "11a1160e-d607-4882-8a82-e2e800f664fe", - "karma": 0, - "user_type": "Noob" - }, - "votes_negative_count": 0, - "votes_positive_count": 0 - } - ], - "ratings_stats": { - "1": 0, - "2": 0, - "3": 0, - "4": 0, - "5": 1 - }, - "reviews_count": 1, - "top_reviews": [ - { - "created": "Tue, 16 Aug 2022 11:25:44 GMT", - "edits": 0, - "entity_id": "e5c4e68b-bfce-4c77-9ca2-0f0a2d4d09f0", - "entity_type": "bb_author", - "full_name": "Creative Commons Attribution-ShareAlike 3.0 Unported", - "id": "326e702a-020f-40e7-b369-9142a7af4315", - "info_url": "https://creativecommons.org/licenses/by-sa/3.0/", - "is_draft": false, - "is_hidden": false, - "language": "en", - "last_revision": { - "id": 11785, - "rating": 5, - "review_id": "326e702a-020f-40e7-b369-9142a7af4315", - "text": null, - "timestamp": "Fri, 02 Sep 2022 09:54:13 GMT" - }, - "last_updated": "Fri, 02 Sep 2022 09:54:13 GMT", - "license_id": "CC BY-SA 3.0", - "popularity": 0, - "published_on": "Tue, 16 Aug 2022 11:25:44 GMT", - "rating": 5, - "source": null, - "source_url": null, - "text": null, - "user": { - "created": "Tue, 18 Jan 2022 09:53:49 GMT", - "display_name": "Ansh Goyal", - "id": "11a1160e-d607-4882-8a82-e2e800f664fe", - "karma": 0, - "user_type": "Noob" - }, - "votes_negative_count": 0, - "votes_positive_count": 0 - } - ] - } - - :statuscode 200: no error - :statuscode 404: author not found - - :query username: User's username **(optional)** - - :resheader Content-Type: *application/json* - """ - - author = db_author.get_author_by_bbid(str(author_bbid)) - - if not author: - raise NotFound("Can't find an author with ID: {author_bbid}".format(author_bbid=author_bbid)) - - user_review = [] - - username = Parser.string('uri', 'username', optional=True) - if username: - user_review_cache_key = cache.gen_key('entity_api', author['bbid'], username, "user_review") - user_review = cache.get(user_review_cache_key, namespace=REVIEW_CACHE_NAMESPACE) - if not user_review: - user = db_users.get_by_mbid(username) - if user: - user_id = user['id'] - - user_review, _ = db_review.list_reviews( - entity_id=author['bbid'], - entity_type='bb_author', - user_id=user_id - ) - if user_review: - user_review = db_review.to_dict(user_review[0]) - - cache.set(user_review_cache_key, user_review, - expirein=REVIEW_CACHE_TIMEOUT, namespace=REVIEW_CACHE_NAMESPACE) - - else: - user_review = [] - - - ratings_stats, average_rating = db_rating_stats.get_stats(author_bbid, "bb_author") - - top_reviews_cache_key = cache.gen_key("entity_api_bb_author", author['bbid'], "top_reviews") - top_reviews_cached_result = cache.get(top_reviews_cache_key, REVIEW_CACHE_NAMESPACE) - - if top_reviews_cached_result: - top_reviews, reviews_count = top_reviews_cached_result - else: - top_reviews, reviews_count = db_review.list_reviews( - entity_id=author['bbid'], - entity_type='bb_author', - sort='popularity', - limit=REVIEWS_LIMIT, - offset=0, - ) - top_reviews = [db_review.to_dict(review) for review in top_reviews] - - cache.set(top_reviews_cache_key, (top_reviews, reviews_count), - expirein=REVIEW_CACHE_TIMEOUT, namespace=REVIEW_CACHE_NAMESPACE) - - latest_reviews_cache_key = cache.gen_key("entity_api_bb_author", author['bbid'], "latest_reviews") - latest_reviews_cached_result = cache.get(latest_reviews_cache_key, REVIEW_CACHE_NAMESPACE) - - if latest_reviews_cached_result: - latest_reviews, reviews_count = latest_reviews_cached_result - else: - latest_reviews, reviews_count = db_review.list_reviews( - entity_id=author['bbid'], - entity_type='bb_author', - sort='published_on', - limit=REVIEWS_LIMIT, - offset=0, - ) - latest_reviews = [db_review.to_dict(review) for review in latest_reviews] - - cache.set(latest_reviews_cache_key, (latest_reviews, reviews_count), - expirein=REVIEW_CACHE_TIMEOUT, namespace=REVIEW_CACHE_NAMESPACE) - - result = { - "author": author, - "average_rating": average_rating, - "ratings_stats": ratings_stats, - "reviews_count": reviews_count, - "top_reviews": top_reviews, - "latest_reviews": latest_reviews - } - if username: - result['user_review'] = user_review - - return jsonify(**result) diff --git a/critiquebrainz/ws/bb_edition_group/__init__.py b/critiquebrainz/ws/bb_edition_group/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/critiquebrainz/ws/bb_edition_group/test/__init__.py b/critiquebrainz/ws/bb_edition_group/test/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/critiquebrainz/ws/bb_edition_group/test/views_test.py b/critiquebrainz/ws/bb_edition_group/test/views_test.py deleted file mode 100644 index e66543508..000000000 --- a/critiquebrainz/ws/bb_edition_group/test/views_test.py +++ /dev/null @@ -1,88 +0,0 @@ -from brainzutils import cache - -import critiquebrainz.db.license as db_license -import critiquebrainz.db.review as db_review -import critiquebrainz.db.users as db_users -from critiquebrainz.db.user import User -from critiquebrainz.ws.testing import WebServiceTestCase - - -class EditionGroupViewsTestCase(WebServiceTestCase): - - def setUp(self): - super(EditionGroupViewsTestCase, self).setUp() - - self.edition_group_id1 = "ac4ae236-6bee-4335-9e04-21314a478f9f" - self.edition_group_id2 = "ab87aa42-3cb7-478c-bba7-09192d04f252" - - self.user = User(db_users.get_or_create(1, "Tester", new_user_data={ - "display_name": "test user", - })) - self.another_user = User(db_users.get_or_create(2, "Hacker!", new_user_data={ - "display_name": "test hacker", - })) - self.license = db_license.create( - id="CC BY-SA 3.0", - full_name="Created so we can fill the form correctly.", - ) - self.review = dict( - entity_id=self.edition_group_id1, - entity_type='bb_edition_group', - user_id=self.user.id, - text="Testing! This text should be on the page.", - rating=5, - is_draft=False, - license_id=self.license["id"], - ) - self.review2 = dict( - entity_id=self.edition_group_id2, - entity_type='bb_edition_group', - user_id=self.user.id, - text="Testing! This text should be on the page.", - rating=5, - is_draft=False, - license_id=self.license["id"], - ) - - def create_dummy_review(self): - return db_review.create(**self.review) - - def create_dummy_review2(self): - return db_review.create(**self.review2) - - def test_edition_group_endpoint(self): - review = self.create_dummy_review() - response = self.client.get('/edition-group/ac4ae236-6bee-4335-9e04-21314a478f9f') - - self.assert200(response) - self.assertIn(review['text'], response.json['top_reviews'][0]['text']) - - self.assertEqual(5, response.json['average_rating']) - self.assertEqual(1, response.json['reviews_count']) - - # Test for an edition group that doesn't exist - response = self.client.get('/edition-group/ac4ae236-6bee-4335-9e04-21314a478f9d') - self.assert404(response) - - def test_edition_group_user_reviews(self): - review = self.create_dummy_review() - response = self.client.get('/edition-group/ac4ae236-6bee-4335-9e04-21314a478f9f?username=%s' % self.user.musicbrainz_username) - - self.assert200(response) - self.assertIn(review['text'], response.json['user_review']['text']) - - def test_user_cache_tracking(self): - track_key = cache.gen_key("entity_api", self.edition_group_id2, self.user.musicbrainz_username, "user_review") - - # Make sure the cache is empty - self.client.get('/edition-group/ab87aa42-3cb7-478c-bba7-09192d04f252?username=%s' % self.user.musicbrainz_username) - cache_value = cache.get(track_key, namespace="Review") - self.assertEqual([], cache_value) - - review = self.create_dummy_review2() - - # Check if the cache is populated after the request - self.client.get('/edition-group/ab87aa42-3cb7-478c-bba7-09192d04f252?username=%s' % self.user.musicbrainz_username) - cache_value = cache.get(track_key, namespace="Review") - - self.assertIn(review['text'], cache_value['text']) diff --git a/critiquebrainz/ws/bb_edition_group/views.py b/critiquebrainz/ws/bb_edition_group/views.py deleted file mode 100644 index 65c7653c6..000000000 --- a/critiquebrainz/ws/bb_edition_group/views.py +++ /dev/null @@ -1,226 +0,0 @@ -from flask import Blueprint, jsonify -import critiquebrainz.db.review as db_review -import critiquebrainz.db.users as db_users -import critiquebrainz.db.rating_stats as db_rating_stats -from critiquebrainz.frontend.external.bookbrainz_db import edition_group as db_edition_group -from critiquebrainz.decorators import crossdomain -from critiquebrainz.ws.exceptions import NotFound -from critiquebrainz.ws.parser import Parser -from critiquebrainz.ws import REVIEWS_LIMIT, REVIEW_CACHE_NAMESPACE, REVIEW_CACHE_TIMEOUT -from brainzutils import cache - -edition_group_bp = Blueprint('ws_edition_group', __name__) - - -@edition_group_bp.route('/', methods=['GET', 'OPTIONS']) -@crossdomain(headers="Authorization, Content-Type") -def edition_group_entity_handler(edition_group_bbid): - """Get list of reviews. - - **Request Example:** - - .. code-block:: bash - - $ curl "https://critiquebrainz.org/ws/1/edition-group/b2a5f76e-91c6-46bb-a618-3ee5ebc6dd6c" \\ - -X GET - - **Response Example:** - - .. code-block:: json - - { - "average_rating": 5.0, - "edition_group": { - "author_credits": [], - "bbid": "b2a5f76e-91c6-46bb-a618-3ee5ebc6dd6c", - "disambiguation": null, - "edition_group_type": "Book", - "identifier_set_id": 1272, - "identifiers": [ - { - "icon": "wikidata-16.png", - "name": "Wikidata ID", - "url": "https://www.wikidata.org/wiki/Q47209", - "value": "Q47209" - } - ], - "name": "Harry Potter and the Chamber of Secrets", - "relationship_set_id": null, - "rels": null, - "sort_name": "Chamber of Secrets, Harry Potter and the" - }, - "latest_reviews": [ - { - "created": "Thu, 28 Jul 2022 07:03:41 GMT", - "edits": 0, - "entity_id": "b2a5f76e-91c6-46bb-a618-3ee5ebc6dd6c", - "entity_type": "bb_edition_group", - "full_name": "Creative Commons Attribution-ShareAlike 3.0 Unported", - "id": "84b01e18-50ac-484e-9e48-41d05250db76", - "info_url": "https://creativecommons.org/licenses/by-sa/3.0/", - "is_draft": false, - "is_hidden": false, - "language": "en", - "last_revision": { - "id": 11786, - "rating": 5, - "review_id": "84b01e18-50ac-484e-9e48-41d05250db76", - "text": null, - "timestamp": "Fri, 02 Sep 2022 09:58:44 GMT" - }, - "last_updated": "Fri, 02 Sep 2022 09:58:44 GMT", - "license_id": "CC BY-SA 3.0", - "popularity": 0, - "published_on": "Thu, 28 Jul 2022 07:03:41 GMT", - "rating": 5, - "source": null, - "source_url": null, - "text": null, - "user": { - "created": "Tue, 18 Jan 2022 09:53:49 GMT", - "display_name": "Ansh Goyal", - "id": "11a1160e-d607-4882-8a82-e2e800f664fe", - "karma": 0, - "user_type": "Noob" - }, - "votes_negative_count": 0, - "votes_positive_count": 0 - } - ], - "ratings_stats": { - "1": 0, - "2": 0, - "3": 0, - "4": 0, - "5": 1 - }, - "reviews_count": 1, - "top_reviews": [ - { - "created": "Thu, 28 Jul 2022 07:03:41 GMT", - "edits": 0, - "entity_id": "b2a5f76e-91c6-46bb-a618-3ee5ebc6dd6c", - "entity_type": "bb_edition_group", - "full_name": "Creative Commons Attribution-ShareAlike 3.0 Unported", - "id": "84b01e18-50ac-484e-9e48-41d05250db76", - "info_url": "https://creativecommons.org/licenses/by-sa/3.0/", - "is_draft": false, - "is_hidden": false, - "language": "en", - "last_revision": { - "id": 11786, - "rating": 5, - "review_id": "84b01e18-50ac-484e-9e48-41d05250db76", - "text": null, - "timestamp": "Fri, 02 Sep 2022 09:58:44 GMT" - }, - "last_updated": "Fri, 02 Sep 2022 09:58:44 GMT", - "license_id": "CC BY-SA 3.0", - "popularity": 0, - "published_on": "Thu, 28 Jul 2022 07:03:41 GMT", - "rating": 5, - "source": null, - "source_url": null, - "text": null, - "user": { - "created": "Tue, 18 Jan 2022 09:53:49 GMT", - "display_name": "Ansh Goyal", - "id": "11a1160e-d607-4882-8a82-e2e800f664fe", - "karma": 0, - "user_type": "Noob" - }, - "votes_negative_count": 0, - "votes_positive_count": 0 - } - ] - } - - :statuscode 200: no error - :statuscode 404: edition group not found - - :query username: User's username **(optional)** - - :resheader Content-Type: *application/json* - """ - - edition_group = db_edition_group.get_edition_group_by_bbid(str(edition_group_bbid)) - - if not edition_group: - raise NotFound("Can't find an edition_group with ID: {edition_group_bbid}".format(edition_group_bbid=edition_group_bbid)) - - user_review = [] - - username = Parser.string('uri', 'username', optional=True) - if username: - user_review_cache_key = cache.gen_key('entity_api', edition_group['bbid'], username, "user_review") - user_review = cache.get(user_review_cache_key, REVIEW_CACHE_NAMESPACE) - if not user_review: - user = db_users.get_by_mbid(username) - if user: - user_id = user['id'] - - user_review, _ = db_review.list_reviews( - entity_id=edition_group['bbid'], - entity_type='bb_edition_group', - user_id=user_id - ) - if user_review: - user_review = db_review.to_dict(user_review[0]) - - cache.set(user_review_cache_key, user_review, - expirein=REVIEW_CACHE_TIMEOUT, namespace=REVIEW_CACHE_NAMESPACE) - - else: - user_review = [] - - ratings_stats, average_rating = db_rating_stats.get_stats(edition_group_bbid, "bb_edition_group") - - top_reviews_cache_key = cache.gen_key("entity_api_bb_edition_group", edition_group['bbid'], "top_reviews") - top_reviews_cached_result = cache.get(top_reviews_cache_key, REVIEW_CACHE_NAMESPACE) - - if top_reviews_cached_result: - top_reviews, reviews_count = top_reviews_cached_result - else: - top_reviews, reviews_count = db_review.list_reviews( - entity_id=edition_group['bbid'], - entity_type='bb_edition_group', - sort='popularity', - limit=REVIEWS_LIMIT, - offset=0, - ) - top_reviews = [db_review.to_dict(review) for review in top_reviews] - - cache.set(top_reviews_cache_key, (top_reviews, reviews_count), - expirein=REVIEW_CACHE_TIMEOUT, namespace=REVIEW_CACHE_NAMESPACE) - - latest_reviews_cache_key = cache.gen_key("entity_api_bb_edition_group", edition_group['bbid'], "latest_reviews") - latest_reviews_cached_result = cache.get(latest_reviews_cache_key, REVIEW_CACHE_NAMESPACE) - - if latest_reviews_cached_result: - latest_reviews, reviews_count = latest_reviews_cached_result - else: - latest_reviews, reviews_count = db_review.list_reviews( - entity_id=edition_group['bbid'], - entity_type='bb_edition_group', - sort='published_on', - limit=REVIEWS_LIMIT, - offset=0, - ) - latest_reviews = [db_review.to_dict(review) for review in latest_reviews] - - cache.set(latest_reviews_cache_key, (latest_reviews, reviews_count), - expirein=REVIEW_CACHE_TIMEOUT, namespace=REVIEW_CACHE_NAMESPACE) - - result = { - "edition_group": edition_group, - "average_rating": average_rating, - "ratings_stats": ratings_stats, - "reviews_count": reviews_count, - "top_reviews": top_reviews, - "latest_reviews": latest_reviews - } - - if username: - result['user_review'] = user_review - - return jsonify(**result) diff --git a/critiquebrainz/ws/bb_literary_work/__init__.py b/critiquebrainz/ws/bb_literary_work/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/critiquebrainz/ws/bb_literary_work/test/__init__.py b/critiquebrainz/ws/bb_literary_work/test/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/critiquebrainz/ws/bb_literary_work/test/views_test.py b/critiquebrainz/ws/bb_literary_work/test/views_test.py deleted file mode 100644 index e21dca5ba..000000000 --- a/critiquebrainz/ws/bb_literary_work/test/views_test.py +++ /dev/null @@ -1,89 +0,0 @@ -from brainzutils import cache - -import critiquebrainz.db.license as db_license -import critiquebrainz.db.review as db_review -import critiquebrainz.db.users as db_users -from critiquebrainz.db.user import User -from critiquebrainz.ws.testing import WebServiceTestCase - - -class LiteraryWorkViewsTestCase(WebServiceTestCase): - - def setUp(self): - super(LiteraryWorkViewsTestCase, self).setUp() - - self.literary_work_id1 = "56efa555-abd5-4ccb-89a6-ff9d9021971f" - self.literary_work_id2 = "0e03bc2a-2867-4687-afee-e211ece30772" - - self.user = User(db_users.get_or_create(1, "Tester", new_user_data={ - "display_name": "test user", - })) - self.another_user = User(db_users.get_or_create(2, "Hacker!", new_user_data={ - "display_name": "test hacker", - })) - self.license = db_license.create( - id="CC BY-SA 3.0", - full_name="Created so we can fill the form correctly.", - ) - self.review = dict( - entity_id=self.literary_work_id1, - entity_type='bb_literary_work', - user_id=self.user.id, - text="Testing! This text should be on the page.", - rating=5, - is_draft=False, - license_id=self.license["id"], - ) - self.review2 = dict( - entity_id=self.literary_work_id2, - entity_type='bb_literary_work', - user_id=self.user.id, - text="Testing! This text should be on the page.", - rating=5, - is_draft=False, - license_id=self.license["id"], - ) - - def create_dummy_review(self): - return db_review.create(**self.review) - - def create_dummy_review2(self): - return db_review.create(**self.review2) - - def test_literary_work_endpoint(self): - review = self.create_dummy_review() - response = self.client.get('/literary-work/56efa555-abd5-4ccb-89a6-ff9d9021971f') - - self.assert200(response) - self.assertIn(review['text'], response.json['top_reviews'][0]['text']) - - self.assertEqual(5, response.json['average_rating']) - self.assertEqual(1, response.json['reviews_count']) - - # Test for a literary work that doesn't exist - response = self.client.get('/literary-work/56efa555-abd5-4ccb-89a6-ff9d9021971g') - self.assert404(response) - - def test_literary_work_user_reviews(self): - review = self.create_dummy_review() - response = self.client.get('/literary-work/56efa555-abd5-4ccb-89a6-ff9d9021971f?username=%s' % - self.user.musicbrainz_username) - - self.assert200(response) - self.assertIn(review['text'], response.json['user_review']['text']) - - def test_user_cache_tracking(self): - track_key = cache.gen_key("entity_api", self.literary_work_id2, self.user.musicbrainz_username, "user_review") - - # Make sure the cache is empty - self.client.get('/literary-work/0e03bc2a-2867-4687-afee-e211ece30772?username=%s' % self.user.musicbrainz_username) - cache_value = cache.get(track_key, namespace="Review") - self.assertEqual([], cache_value) - - review = self.create_dummy_review2() - - # Check if the cache is populated after the request - self.client.get('/literary-work/0e03bc2a-2867-4687-afee-e211ece30772?username=%s' % self.user.musicbrainz_username) - cache_value = cache.get(track_key, namespace="Review") - - self.assertIn(review['text'], cache_value['text']) diff --git a/critiquebrainz/ws/bb_literary_work/views.py b/critiquebrainz/ws/bb_literary_work/views.py deleted file mode 100644 index e5037e125..000000000 --- a/critiquebrainz/ws/bb_literary_work/views.py +++ /dev/null @@ -1,246 +0,0 @@ -from flask import Blueprint, jsonify -import critiquebrainz.db.review as db_review -import critiquebrainz.db.users as db_users -import critiquebrainz.db.rating_stats as db_rating_stats -from critiquebrainz.frontend.external.bookbrainz_db import literary_work as db_literary_work -from critiquebrainz.decorators import crossdomain -from critiquebrainz.ws.exceptions import NotFound -from critiquebrainz.ws.parser import Parser -from critiquebrainz.ws import REVIEWS_LIMIT, REVIEW_CACHE_NAMESPACE, REVIEW_CACHE_TIMEOUT -from brainzutils import cache - -literary_work_bp = Blueprint('ws_literary_work', __name__) - - -@literary_work_bp.route('/', methods=['GET', 'OPTIONS']) -@crossdomain(headers="Authorization, Content-Type") -def literary_work_entity_handler(literary_work_bbid): - """Get list of reviews. - - **Request Example:** - - .. code-block:: bash - - $ curl "https://critiquebrainz.org/ws/1/literary-work/b2d19b45-117d-437d-b55b-7fff01e29603" \\ - -X GET - - **Response Example:** - - .. code-block:: json - - { - "average_rating": 5.0, - "latest_reviews": [ - { - "created": "Thu, 18 Aug 2022 09:21:37 GMT", - "edits": 0, - "entity_id": "b2d19b45-117d-437d-b55b-7fff01e29603", - "entity_type": "bb_literary_work", - "full_name": "Creative Commons Attribution-ShareAlike 3.0 Unported", - "id": "178b5ef1-ec96-40b8-a08c-b11b01550efd", - "info_url": "https://creativecommons.org/licenses/by-sa/3.0/", - "is_draft": false, - "is_hidden": false, - "language": "en", - "last_revision": { - "id": 11775, - "rating": 5, - "review_id": "178b5ef1-ec96-40b8-a08c-b11b01550efd", - "text": null, - "timestamp": "Thu, 18 Aug 2022 09:21:37 GMT" - }, - "last_updated": "Thu, 18 Aug 2022 09:21:37 GMT", - "license_id": "CC BY-SA 3.0", - "popularity": 0, - "published_on": "Thu, 18 Aug 2022 09:21:37 GMT", - "rating": 5, - "source": null, - "source_url": null, - "text": null, - "user": { - "created": "Tue, 18 Jan 2022 09:53:49 GMT", - "display_name": "Ansh Goyal", - "id": "11a1160e-d607-4882-8a82-e2e800f664fe", - "karma": 0, - "user_type": "Noob" - }, - "votes_negative_count": 0, - "votes_positive_count": 0 - } - ], - "literary_work": { - "bbid": "b2d19b45-117d-437d-b55b-7fff01e29603", - "disambiguation": null, - "identifier_set_id": 2831, - "identifiers": [ - { - "icon": "wikidata-16.png", - "name": "Wikidata ID", - "url": "https://www.wikidata.org/wiki/Q47209", - "value": "Q47209" - }, - { - "icon": "viaf-16.png", - "name": "VIAF", - "url": "https://viaf.org/viaf/190455963", - "value": "190455963" - }, - { - "icon": null, - "name": "OpenLibrary Work ID", - "url": "https://openlibrary.org/works/OL16313124W", - "value": "OL16313124W" - }, - { - "icon": null, - "name": "OpenLibrary Work ID", - "url": "https://openlibrary.org/works/OL16313123W", - "value": "OL16313123W" - } - ], - "languages": [ - "English" - ], - "name": "Harry Potter and the Chamber of Secrets", - "relationship_set_id": 136021, - "rels": null, - "sort_name": "Chamber of Secrets, Harry Potter and the", - "work_type": "Novel" - }, - "ratings_stats": { - "1": 0, - "2": 0, - "3": 0, - "4": 0, - "5": 1 - }, - "reviews_count": 1, - "top_reviews": [ - { - "created": "Thu, 18 Aug 2022 09:21:37 GMT", - "edits": 0, - "entity_id": "b2d19b45-117d-437d-b55b-7fff01e29603", - "entity_type": "bb_literary_work", - "full_name": "Creative Commons Attribution-ShareAlike 3.0 Unported", - "id": "178b5ef1-ec96-40b8-a08c-b11b01550efd", - "info_url": "https://creativecommons.org/licenses/by-sa/3.0/", - "is_draft": false, - "is_hidden": false, - "language": "en", - "last_revision": { - "id": 11775, - "rating": 5, - "review_id": "178b5ef1-ec96-40b8-a08c-b11b01550efd", - "text": null, - "timestamp": "Thu, 18 Aug 2022 09:21:37 GMT" - }, - "last_updated": "Thu, 18 Aug 2022 09:21:37 GMT", - "license_id": "CC BY-SA 3.0", - "popularity": 0, - "published_on": "Thu, 18 Aug 2022 09:21:37 GMT", - "rating": 5, - "source": null, - "source_url": null, - "text": null, - "user": { - "created": "Tue, 18 Jan 2022 09:53:49 GMT", - "display_name": "Ansh Goyal", - "id": "11a1160e-d607-4882-8a82-e2e800f664fe", - "karma": 0, - "user_type": "Noob" - }, - "votes_negative_count": 0, - "votes_positive_count": 0 - } - ] - } - - :statuscode 200: no error - :statuscode 404: literary work not found - - :query username: User's username **(optional)** - - :resheader Content-Type: *application/json* - """ - - literary_work = db_literary_work.get_literary_work_by_bbid(str(literary_work_bbid)) - - if not literary_work: - raise NotFound("Can't find a literary_work with ID: {literary_work_bbid}".format(literary_work_bbid=literary_work_bbid)) - - user_review = [] - - username = Parser.string('uri', 'username', optional=True) - if username: - user_review_cache_key = cache.gen_key('entity_api', literary_work['bbid'], username, "user_review") - user_review = cache.get(user_review_cache_key, REVIEW_CACHE_NAMESPACE) - if not user_review: - user = db_users.get_by_mbid(username) - if user: - user_id = user['id'] - - user_review, _ = db_review.list_reviews( - entity_id=literary_work['bbid'], - entity_type='bb_literary_work', - user_id=user_id - ) - if user_review: - user_review = db_review.to_dict(user_review[0]) - - cache.set(user_review_cache_key, user_review, - expirein=REVIEW_CACHE_TIMEOUT, namespace=REVIEW_CACHE_NAMESPACE) - - else: - user_review = [] - - ratings_stats, average_rating = db_rating_stats.get_stats(literary_work_bbid, "bb_literary_work") - - top_reviews_cache_key = cache.gen_key("entity_api_bb_literary_work", literary_work['bbid'], "top_reviews") - top_reviews_cached_result = cache.get(top_reviews_cache_key, REVIEW_CACHE_NAMESPACE) - - if top_reviews_cached_result: - top_reviews, reviews_count = top_reviews_cached_result - else: - top_reviews, reviews_count = db_review.list_reviews( - entity_id=literary_work['bbid'], - entity_type='bb_literary_work', - sort='popularity', - limit=REVIEWS_LIMIT, - offset=0, - ) - top_reviews = [db_review.to_dict(review) for review in top_reviews] - - cache.set(top_reviews_cache_key, (top_reviews, reviews_count), - expirein=REVIEW_CACHE_TIMEOUT, namespace=REVIEW_CACHE_NAMESPACE) - - latest_reviews_cache_key = cache.gen_key("entity_api_bb_literary_work", literary_work['bbid'], "latest_reviews") - latest_reviews_cached_result = cache.get(latest_reviews_cache_key, REVIEW_CACHE_NAMESPACE) - - if latest_reviews_cached_result: - latest_reviews, reviews_count = latest_reviews_cached_result - else: - latest_reviews, reviews_count = db_review.list_reviews( - entity_id=literary_work['bbid'], - entity_type='bb_literary_work', - sort='published_on', - limit=REVIEWS_LIMIT, - offset=0, - ) - latest_reviews = [db_review.to_dict(review) for review in latest_reviews] - - cache.set(latest_reviews_cache_key, (latest_reviews, reviews_count), - expirein=REVIEW_CACHE_TIMEOUT, namespace=REVIEW_CACHE_NAMESPACE) - - result = { - "literary_work": literary_work, - "average_rating": average_rating, - "ratings_stats": ratings_stats, - "reviews_count": reviews_count, - "top_reviews": top_reviews, - "latest_reviews": latest_reviews - } - - if username: - result['user_review'] = user_review - - return jsonify(**result) diff --git a/critiquebrainz/ws/bb_series/__init__.py b/critiquebrainz/ws/bb_series/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/critiquebrainz/ws/bb_series/test/__init__.py b/critiquebrainz/ws/bb_series/test/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/critiquebrainz/ws/bb_series/test/views_test.py b/critiquebrainz/ws/bb_series/test/views_test.py deleted file mode 100644 index e43c0d0c7..000000000 --- a/critiquebrainz/ws/bb_series/test/views_test.py +++ /dev/null @@ -1,88 +0,0 @@ -from brainzutils import cache - -import critiquebrainz.db.license as db_license -import critiquebrainz.db.review as db_review -import critiquebrainz.db.users as db_users -from critiquebrainz.db.user import User -from critiquebrainz.ws.testing import WebServiceTestCase - - -class seriesViewsTestCase(WebServiceTestCase): - - def setUp(self): - super(seriesViewsTestCase, self).setUp() - - self.series_id1 = "968ef651-6a70-410f-9b17-f326ee0062c3" - self.series_id2 = "29b7d60f-0be1-428d-8a2d-71f3abb8d218" - - self.user = User(db_users.get_or_create(1, "Tester", new_user_data={ - "display_name": "test user", - })) - self.another_user = User(db_users.get_or_create(2, "Hacker!", new_user_data={ - "display_name": "test hacker", - })) - self.license = db_license.create( - id="CC BY-SA 3.0", - full_name="Created so we can fill the form correctly.", - ) - self.review = dict( - entity_id=self.series_id1, - entity_type='bb_series', - user_id=self.user.id, - text="Testing! This text should be on the page.", - rating=5, - is_draft=False, - license_id=self.license["id"], - ) - self.review2 = dict( - entity_id=self.series_id2, - entity_type='bb_series', - user_id=self.user.id, - text="Testing! This text should be on the page.", - rating=5, - is_draft=False, - license_id=self.license["id"], - ) - - def create_dummy_review(self): - return db_review.create(**self.review) - - def create_dummy_review2(self): - return db_review.create(**self.review2) - - def test_series_endpoint(self): - review = self.create_dummy_review() - response = self.client.get('/series/968ef651-6a70-410f-9b17-f326ee0062c3') - - self.assert200(response) - self.assertIn(review['text'], response.json['top_reviews'][0]['text']) - - self.assertEqual(5, response.json['average_rating']) - self.assertEqual(1, response.json['reviews_count']) - - # Test for a series which does not exist - response = self.client.get('/series/968ef651-6a70-410f-9b17-f326ee0062c1') - self.assert404(response) - - def test_series_user_reviews(self): - review = self.create_dummy_review() - response = self.client.get('/series/968ef651-6a70-410f-9b17-f326ee0062c3?username=%s' % self.user.musicbrainz_username) - - self.assert200(response) - self.assertIn(review['text'], response.json['user_review']['text']) - - def test_user_cache_tracking(self): - track_key = cache.gen_key("entity_api", self.series_id2, self.user.musicbrainz_username, "user_review") - - # Make sure the cache is empty - self.client.get('/series/29b7d60f-0be1-428d-8a2d-71f3abb8d218?username=%s' % self.user.musicbrainz_username) - cache_value = cache.get(track_key, namespace="Review") - self.assertEqual([], cache_value) - - review = self.create_dummy_review2() - - # Check if the cache is populated after the request - self.client.get('/series/29b7d60f-0be1-428d-8a2d-71f3abb8d218?username=%s' % self.user.musicbrainz_username) - cache_value = cache.get(track_key, namespace="Review") - - self.assertIn(review['text'], cache_value['text']) diff --git a/critiquebrainz/ws/bb_series/views.py b/critiquebrainz/ws/bb_series/views.py deleted file mode 100644 index 2ffcacf73..000000000 --- a/critiquebrainz/ws/bb_series/views.py +++ /dev/null @@ -1,219 +0,0 @@ -from flask import Blueprint, jsonify -import critiquebrainz.db.review as db_review -import critiquebrainz.db.users as db_users -import critiquebrainz.db.rating_stats as db_rating_stats -from critiquebrainz.frontend.external.bookbrainz_db import series as db_series -from critiquebrainz.decorators import crossdomain -from critiquebrainz.ws.exceptions import NotFound -from critiquebrainz.ws.parser import Parser -from critiquebrainz.ws import REVIEWS_LIMIT, REVIEW_CACHE_NAMESPACE, REVIEW_CACHE_TIMEOUT -from brainzutils import cache - -series_bp = Blueprint('ws_series', __name__) - - -@series_bp.route('/', methods=['GET', 'OPTIONS']) -@crossdomain(headers="Authorization, Content-Type") -def series_entity_handler(series_bbid): - """Get list of reviews. - - **Request Example:** - - .. code-block:: bash - - $ curl "https://critiquebrainz.org/ws/1/series/e6f48cbd-26de-4c2e-a24a-29892f9eb3be" \\ - -X GET - - **Response Example:** - - .. code-block:: json - - { - "average_rating": 4.0, - "latest_reviews": [ - { - "created": "Tue, 16 Aug 2022 11:26:58 GMT", - "edits": 0, - "entity_id": "e6f48cbd-26de-4c2e-a24a-29892f9eb3be", - "entity_type": "bb_series", - "full_name": "Creative Commons Attribution-ShareAlike 3.0 Unported", - "id": "998512d8-0d6b-4c76-bfb7-0ec666e3fa0a", - "info_url": "https://creativecommons.org/licenses/by-sa/3.0/", - "is_draft": false, - "is_hidden": false, - "language": "en", - "last_revision": { - "id": 11773, - "rating": 4, - "review_id": "998512d8-0d6b-4c76-bfb7-0ec666e3fa0a", - "text": null, - "timestamp": "Tue, 16 Aug 2022 11:26:58 GMT" - }, - "last_updated": "Tue, 16 Aug 2022 11:26:58 GMT", - "license_id": "CC BY-SA 3.0", - "popularity": 0, - "published_on": "Tue, 16 Aug 2022 11:26:58 GMT", - "rating": 4, - "source": null, - "source_url": null, - "text": null, - "user": { - "created": "Tue, 18 Jan 2022 09:53:49 GMT", - "display_name": "Ansh Goyal", - "id": "11a1160e-d607-4882-8a82-e2e800f664fe", - "karma": 0, - "user_type": "Noob" - }, - "votes_negative_count": 0, - "votes_positive_count": 0 - } - ], - "ratings_stats": { - "1": 0, - "2": 0, - "3": 0, - "4": 1, - "5": 0 - }, - "reviews_count": 1, - "series": { - "bbid": "e6f48cbd-26de-4c2e-a24a-29892f9eb3be", - "disambiguation": "English", - "identifier_set_id": null, - "identifiers": null, - "name": "Harry Potter", - "relationship_set_id": 151767, - "series_ordering_type": "Automatic", - "series_type": "Work", - "sort_name": "Harry Potter" - }, - "top_reviews": [ - { - "created": "Tue, 16 Aug 2022 11:26:58 GMT", - "edits": 0, - "entity_id": "e6f48cbd-26de-4c2e-a24a-29892f9eb3be", - "entity_type": "bb_series", - "full_name": "Creative Commons Attribution-ShareAlike 3.0 Unported", - "id": "998512d8-0d6b-4c76-bfb7-0ec666e3fa0a", - "info_url": "https://creativecommons.org/licenses/by-sa/3.0/", - "is_draft": false, - "is_hidden": false, - "language": "en", - "last_revision": { - "id": 11773, - "rating": 4, - "review_id": "998512d8-0d6b-4c76-bfb7-0ec666e3fa0a", - "text": null, - "timestamp": "Tue, 16 Aug 2022 11:26:58 GMT" - }, - "last_updated": "Tue, 16 Aug 2022 11:26:58 GMT", - "license_id": "CC BY-SA 3.0", - "popularity": 0, - "published_on": "Tue, 16 Aug 2022 11:26:58 GMT", - "rating": 4, - "source": null, - "source_url": null, - "text": null, - "user": { - "created": "Tue, 18 Jan 2022 09:53:49 GMT", - "display_name": "Ansh Goyal", - "id": "11a1160e-d607-4882-8a82-e2e800f664fe", - "karma": 0, - "user_type": "Noob" - }, - "votes_negative_count": 0, - "votes_positive_count": 0 - } - ] - } - - :statuscode 200: no error - :statuscode 404: series not found - - :query username: User's username **(optional)** - - :resheader Content-Type: *application/json* - """ - - series = db_series.get_series_by_bbid(str(series_bbid)) - - if not series: - raise NotFound("Can't find a series with ID: {series_bbid}".format(series_bbid=series_bbid)) - - user_review = [] - - username = Parser.string('uri', 'username', optional=True) - if username: - user_review_cache_key = cache.gen_key('entity_api', series['bbid'], username, "user_review") - user_review = cache.get(user_review_cache_key, REVIEW_CACHE_NAMESPACE) - if not user_review: - user = db_users.get_by_mbid(username) - if user: - user_id = user['id'] - - user_review, _ = db_review.list_reviews( - entity_id=series['bbid'], - entity_type='bb_series', - user_id=user_id - ) - if user_review: - user_review = db_review.to_dict(user_review[0]) - - cache.set(user_review_cache_key, user_review, - expirein=REVIEW_CACHE_TIMEOUT, namespace=REVIEW_CACHE_NAMESPACE) - - else: - user_review = [] - - - ratings_stats, average_rating = db_rating_stats.get_stats(series_bbid, "bb_series") - - top_reviews_cache_key = cache.gen_key("entity_api_bb_series", series['bbid'], "top_reviews") - top_reviews_cached_result = cache.get(top_reviews_cache_key, REVIEW_CACHE_NAMESPACE) - - if top_reviews_cached_result: - top_reviews, reviews_count = top_reviews_cached_result - else: - top_reviews, reviews_count = db_review.list_reviews( - entity_id=series['bbid'], - entity_type='bb_series', - sort='popularity', - limit=REVIEWS_LIMIT, - offset=0, - ) - top_reviews = [db_review.to_dict(review) for review in top_reviews] - - cache.set(top_reviews_cache_key, (top_reviews, reviews_count), - expirein=REVIEW_CACHE_TIMEOUT, namespace=REVIEW_CACHE_NAMESPACE) - - latest_reviews_cache_key = cache.gen_key("entity_api_bb_series", series['bbid'], "latest_reviews") - latest_reviews_cached_result = cache.get(latest_reviews_cache_key, REVIEW_CACHE_NAMESPACE) - - if latest_reviews_cached_result: - latest_reviews, reviews_count = latest_reviews_cached_result - else: - latest_reviews, reviews_count = db_review.list_reviews( - entity_id=series['bbid'], - entity_type='bb_series', - sort='published_on', - limit=REVIEWS_LIMIT, - offset=0, - ) - latest_reviews = [db_review.to_dict(review) for review in latest_reviews] - - cache.set(latest_reviews_cache_key, (latest_reviews, reviews_count), - expirein=REVIEW_CACHE_TIMEOUT, namespace=REVIEW_CACHE_NAMESPACE) - - result = { - "series": series, - "average_rating": average_rating, - "ratings_stats": ratings_stats, - "reviews_count": reviews_count, - "top_reviews": top_reviews, - "latest_reviews": latest_reviews - } - - if username: - result['user_review'] = user_review - - return jsonify(**result) diff --git a/critiquebrainz/ws/event/__init__.py b/critiquebrainz/ws/event/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/critiquebrainz/ws/event/test/__init__.py b/critiquebrainz/ws/event/test/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/critiquebrainz/ws/event/test/views_test.py b/critiquebrainz/ws/event/test/views_test.py deleted file mode 100644 index a552788ef..000000000 --- a/critiquebrainz/ws/event/test/views_test.py +++ /dev/null @@ -1,88 +0,0 @@ -from brainzutils import cache - -import critiquebrainz.db.license as db_license -import critiquebrainz.db.review as db_review -import critiquebrainz.db.users as db_users -from critiquebrainz.db.user import User -from critiquebrainz.ws.testing import WebServiceTestCase - - -class EventViewsTestCase(WebServiceTestCase): - - def setUp(self): - super(EventViewsTestCase, self).setUp() - - self.event_id1 = "fe39727a-3d21-4066-9345-3970cbd6cca4" - self.event_id2 = "7f2d3cd9-c2e1-4adc-841f-dab3de070a75" - - self.user = User(db_users.get_or_create(1, "Tester", new_user_data={ - "display_name": "test user", - })) - self.another_user = User(db_users.get_or_create(2, "Hacker!", new_user_data={ - "display_name": "test hacker", - })) - self.license = db_license.create( - id="CC BY-SA 3.0", - full_name="Created so we can fill the form correctly.", - ) - self.review = dict( - entity_id=self.event_id1, - entity_type='event', - user_id=self.user.id, - text="Testing! This text should be on the page.", - rating=5, - is_draft=False, - license_id=self.license["id"], - ) - self.review2 = dict( - entity_id=self.event_id2, - entity_type='event', - user_id=self.user.id, - text="Testing! This text should be on the page.", - rating=5, - is_draft=False, - license_id=self.license["id"], - ) - - def create_dummy_review(self): - return db_review.create(**self.review) - - def create_dummy_review2(self): - return db_review.create(**self.review2) - - def test_event_endpoint(self): - review = self.create_dummy_review() - response = self.client.get('/event/fe39727a-3d21-4066-9345-3970cbd6cca4') - - self.assert200(response) - self.assertIn(review['text'], response.json['top_reviews'][0]['text']) - - self.assertEqual(5, response.json['average_rating']) - self.assertEqual(1, response.json['reviews_count']) - - # Test for an event that doesn't exist - response = self.client.get('/event/fe39727a-3d21-4066-9345-3970cbd6cca1') - self.assert404(response) - - def test_event_user_reviews(self): - review = self.create_dummy_review() - response = self.client.get('/event/fe39727a-3d21-4066-9345-3970cbd6cca4?username=%s' % self.user.musicbrainz_username) - - self.assert200(response) - self.assertIn(review['text'], response.json['user_review']['text']) - - def test_user_cache_tracking(self): - track_key = cache.gen_key("entity_api", self.event_id2, self.user.musicbrainz_username, "user_review") - - # Make sure the cache is empty - self.client.get('/event/7f2d3cd9-c2e1-4adc-841f-dab3de070a75?username=%s' % self.user.musicbrainz_username) - cache_value = cache.get(track_key, namespace="Review") - self.assertEqual([], cache_value) - - review = self.create_dummy_review2() - - # Check if the cache is populated after the request - self.client.get('/event/7f2d3cd9-c2e1-4adc-841f-dab3de070a75?username=%s' % self.user.musicbrainz_username) - cache_value = cache.get(track_key, namespace="Review") - - self.assertIn(review['text'], cache_value['text']) diff --git a/critiquebrainz/ws/event/views.py b/critiquebrainz/ws/event/views.py deleted file mode 100644 index 75d4cd1e2..000000000 --- a/critiquebrainz/ws/event/views.py +++ /dev/null @@ -1,338 +0,0 @@ -from flask import Blueprint, jsonify -import critiquebrainz.db.review as db_review -import critiquebrainz.db.users as db_users -import critiquebrainz.db.rating_stats as db_rating_stats -from critiquebrainz.frontend.external.musicbrainz_db import event as db_event -from critiquebrainz.decorators import crossdomain -from critiquebrainz.ws.exceptions import NotFound -from critiquebrainz.ws.parser import Parser -from critiquebrainz.ws import REVIEWS_LIMIT -from critiquebrainz.ws import REVIEWS_LIMIT, REVIEW_CACHE_NAMESPACE, REVIEW_CACHE_TIMEOUT -from brainzutils import cache - -event_bp = Blueprint('ws_event', __name__) - - -@event_bp.route('/', methods=['GET', 'OPTIONS']) -@crossdomain(headers="Authorization, Content-Type") -def event_entity_handler(event_mbid): - """Get list of reviews. - - **Request Example:** - - .. code-block:: bash - - $ curl "https://critiquebrainz.org/ws/1/event/3f0b7c79-1d66-40d2-a8a0-54be9ca2b18c" \\ - -X GET - - **Response Example:** - - .. code-block:: json - - { - "average_rating": 0, - "event": { - "artist-rels": [ - { - "artist": { - "life-span": { - "begin": "1989-12-13" - }, - "mbid": "20244d07-534f-4eff-b4d4-930878889970", - "name": "Taylor Swift", - "sort_name": "Swift, Taylor", - "type": "Person" - }, - "begin-year": null, - "direction": "backward", - "end-year": null, - "type": "main performer", - "type-id": "936c7c95-3156-3889-a062-8a0cd57f8946" - }, - { - "artist": { - "life-span": { - "begin": "1989-12-13" - }, - "mbid": "20244d07-534f-4eff-b4d4-930878889970", - "name": "Taylor Swift", - "sort_name": "Swift, Taylor", - "type": "Person" - }, - "begin-year": null, - "direction": "backward", - "end-year": null, - "type": "main performer", - "type-id": "936c7c95-3156-3889-a062-8a0cd57f8946" - }, - { - "artist": { - "life-span": { - "begin": "1989-12-13" - }, - "mbid": "20244d07-534f-4eff-b4d4-930878889970", - "name": "Taylor Swift", - "sort_name": "Swift, Taylor", - "type": "Person" - }, - "begin-year": null, - "direction": "backward", - "end-year": null, - "type": "main performer", - "type-id": "936c7c95-3156-3889-a062-8a0cd57f8946" - } - ], - "life-span": { - "begin": "2015-05-05", - "end": "2015-12-12" - }, - "mbid": "3f0b7c79-1d66-40d2-a8a0-54be9ca2b18c", - "name": "The 1989 World Tour by Taylor Swift", - "type": "Concert" - }, - "latest_reviews": [ - { - "created": "Sun, 24 Jan 2016 16:46:47 GMT", - "edits": 0, - "entity_id": "3f0b7c79-1d66-40d2-a8a0-54be9ca2b18c", - "entity_type": "event", - "full_name": "Creative Commons Attribution-ShareAlike 3.0 Unported", - "id": "1da0c290-2c8c-47b4-9703-b172aaf9576f", - "info_url": "https://creativecommons.org/licenses/by-sa/3.0/", - "is_draft": false, - "is_hidden": false, - "language": "en", - "last_revision": { - "id": 9753, - "rating": null, - "review_id": "1da0c290-2c8c-47b4-9703-b172aaf9576f", - "text": "I had never been to a concert before, so when my mom told me that she had snagged tickets to see Taylor Swift's 1989 tour, I was speechless, anxious, overwhelmingly excited, and eager. I didn't know what to expect, but I knew Taylor was known for singing amazingly live and the numerous guest stars and the pretty costumes, lights, and scenes.\r\n\r\nBoy, was I right. The night was surely a night that I will never forget. Taylor started off with \"Welcome to New York\", one of my favorites, and continued onto some other gems of her album such as \"The New Romantics\" and \"Blank Space. I definitely was one of the embarrassing screaming girls that you always see in the audience. But everyone in the crowd was singing along to the lyrics of her new platinum album (how could you not?) and danced as she belted and strutted around the stage.\r\n\r\nTaylor Swift continued onto some of her older songs of previous years like \"I Know You Were Trouble\" and \"Mean.\" I loved them just as much! Her voice was so beautiful as the night continued. I wonder how someone could be so talented of that caliber - to be able to sing and dance for hours on end. In between tracks, she would thank the crowd and express her gratitude of being able to perform for us all. She ended the night with a bang, singing some of her classic hits like \"Love Story\" and \"We Are Never Getting Back Together.\"\r\n\r\nUltimately, one of the most momentous occasions I have ever experienced. I can't speak for other artists, but Taylor really felt the energy of the crowd and maintained that vigorous happy energy and she smiled and sang and just looked like she was having so much fun.", - "timestamp": "Sun, 24 Jan 2016 16:46:47 GMT" - }, - "last_updated": "Sun, 24 Jan 2016 16:46:47 GMT", - "license_id": "CC BY-SA 3.0", - "popularity": 1, - "published_on": "Sun, 24 Jan 2016 16:46:47 GMT", - "rating": null, - "source": null, - "source_url": null, - "text": "I had never been to a concert before, so when my mom told me that she had snagged tickets to see Taylor Swift's 1989 tour, I was speechless, anxious, overwhelmingly excited, and eager. I didn't know what to expect, but I knew Taylor was known for singing amazingly live and the numerous guest stars and the pretty costumes, lights, and scenes.\r\n\r\nBoy, was I right. The night was surely a night that I will never forget. Taylor started off with \"Welcome to New York\", one of my favorites, and continued onto some other gems of her album such as \"The New Romantics\" and \"Blank Space. I definitely was one of the embarrassing screaming girls that you always see in the audience. But everyone in the crowd was singing along to the lyrics of her new platinum album (how could you not?) and danced as she belted and strutted around the stage.\r\n\r\nTaylor Swift continued onto some of her older songs of previous years like \"I Know You Were Trouble\" and \"Mean.\" I loved them just as much! Her voice was so beautiful as the night continued. I wonder how someone could be so talented of that caliber - to be able to sing and dance for hours on end. In between tracks, she would thank the crowd and express her gratitude of being able to perform for us all. She ended the night with a bang, singing some of her classic hits like \"Love Story\" and \"We Are Never Getting Back Together.\"\r\n\r\nUltimately, one of the most momentous occasions I have ever experienced. I can't speak for other artists, but Taylor really felt the energy of the crowd and maintained that vigorous happy energy and she smiled and sang and just looked like she was having so much fun.", - "user": { - "created": "Sun, 24 Jan 2016 16:33:34 GMT", - "display_name": "michelletu", - "id": "8f5deaf3-84b9-4024-8751-6dbc0f9cf4ef", - "karma": 1, - "user_type": "Noob" - }, - "votes_negative_count": 0, - "votes_positive_count": 1 - }, - { - "created": "Sun, 17 Jan 2016 10:02:06 GMT", - "edits": 0, - "entity_id": "3f0b7c79-1d66-40d2-a8a0-54be9ca2b18c", - "entity_type": "event", - "full_name": "Creative Commons Attribution-ShareAlike 3.0 Unported", - "id": "b02b8836-d7e2-4077-9443-38c0c0f6725f", - "info_url": "https://creativecommons.org/licenses/by-sa/3.0/", - "is_draft": false, - "is_hidden": false, - "language": "en", - "last_revision": { - "id": 9636, - "rating": null, - "review_id": "b02b8836-d7e2-4077-9443-38c0c0f6725f", - "text": "It was one of the most hyped up music event of the year, and it certainly lived up to expectations. When it was announced that Taylor Swift would be bringing her 1989 World Tour to Singapore in November, I, among many others, made a mad rush to grab at whatever tickets we could. On the grand day itself, we were treated to a musical feast at the Singapore Indoor Stadium, which was truly an unforgettable experience. \r\n\r\nTaylor Swift started off her concert with a rousing medley of songs (\"Welcome to New York\", \"The New Romantics\" and \"Blank Space) from her platinum release album 1989, which certainly did not fail to wow the crowd. However, what made Taylor Swift's songs different this time round was that she added a Singapore touch to them, thus resonating with Singaporeans at her concert. For instance, during her dynamic single \"Blank Space\", Taylor used a metal rod to strike a pole situated in the middle of the runway, looping her saying \"Singapore\" into the backing track. \r\n\r\nNext, Taylor belted out classics from her previous albums, such as \"I Knew You Were Trouble\" and \"I Wish You Would\". In between the singles, Taylor thanked everyone at the concert for supporting her latest album \"1989\", which brought the crowd roaring with excitement. \"How You Get the Girl\" and \"You Belong With Me\" were up next, with the audience singing along to the classics. \r\n\r\nSome of her biggest hits were also subjected to makeovers. With Taylor strumming a white electric guitar, \"We Are Never Ever Getting Back Together\" sounded much heftier than the recorded version. Taylor also slowed down classics \"I Knew You Were Trouble\" and \"Love Story\" by a notch, creating a new dimension for familiar tunes. \r\n\r\nHowever, there were a couple of minor disappointments at the concert. Prior to the concert, many made wild guesses as to who the surprise guests at the Singapore concert would be - after all, Taylor had been making headlines for inviting her celebrity friends to join her in the 1989 World Tour. Hence, when we realised that there were not going to be any surprise guests, we could not help but feel a little shortchanged. But Taylor's last song for the concert, \"Shake It Off\", immediately helped us feel much better, with almost everyone jumping to their feet to dance along with Taylor, who was strutting her moves on the runway. \r\n\r\nAll in all, it was a dynamic night at the Singapore Indoor Stadium, with Taylor Swift belting out song after song, from classics to singles from her latest album. \r\n", - "timestamp": "Mon, 18 Jan 2016 22:52:28 GMT" - }, - "last_updated": "Mon, 18 Jan 2016 22:52:28 GMT", - "license_id": "CC BY-SA 3.0", - "popularity": 0, - "published_on": "Mon, 18 Jan 2016 22:52:28 GMT", - "rating": null, - "source": null, - "source_url": null, - "text": "It was one of the most hyped up music event of the year, and it certainly lived up to expectations. When it was announced that Taylor Swift would be bringing her 1989 World Tour to Singapore in November, I, among many others, made a mad rush to grab at whatever tickets we could. On the grand day itself, we were treated to a musical feast at the Singapore Indoor Stadium, which was truly an unforgettable experience. \r\n\r\nTaylor Swift started off her concert with a rousing medley of songs (\"Welcome to New York\", \"The New Romantics\" and \"Blank Space) from her platinum release album 1989, which certainly did not fail to wow the crowd. However, what made Taylor Swift's songs different this time round was that she added a Singapore touch to them, thus resonating with Singaporeans at her concert. For instance, during her dynamic single \"Blank Space\", Taylor used a metal rod to strike a pole situated in the middle of the runway, looping her saying \"Singapore\" into the backing track. \r\n\r\nNext, Taylor belted out classics from her previous albums, such as \"I Knew You Were Trouble\" and \"I Wish You Would\". In between the singles, Taylor thanked everyone at the concert for supporting her latest album \"1989\", which brought the crowd roaring with excitement. \"How You Get the Girl\" and \"You Belong With Me\" were up next, with the audience singing along to the classics. \r\n\r\nSome of her biggest hits were also subjected to makeovers. With Taylor strumming a white electric guitar, \"We Are Never Ever Getting Back Together\" sounded much heftier than the recorded version. Taylor also slowed down classics \"I Knew You Were Trouble\" and \"Love Story\" by a notch, creating a new dimension for familiar tunes. \r\n\r\nHowever, there were a couple of minor disappointments at the concert. Prior to the concert, many made wild guesses as to who the surprise guests at the Singapore concert would be - after all, Taylor had been making headlines for inviting her celebrity friends to join her in the 1989 World Tour. Hence, when we realised that there were not going to be any surprise guests, we could not help but feel a little shortchanged. But Taylor's last song for the concert, \"Shake It Off\", immediately helped us feel much better, with almost everyone jumping to their feet to dance along with Taylor, who was strutting her moves on the runway. \r\n\r\nAll in all, it was a dynamic night at the Singapore Indoor Stadium, with Taylor Swift belting out song after song, from classics to singles from her latest album. \r\n", - "user": { - "created": "Sun, 17 Jan 2016 06:49:23 GMT", - "display_name": "gabriellee", - "id": "172631a3-8338-4aaf-84c8-1ed0f02c56c1", - "karma": 0, - "user_type": "Noob" - }, - "votes_negative_count": 0, - "votes_positive_count": 0 - } - ], - "ratings_stats": { - "1": 0, - "2": 0, - "3": 0, - "4": 0, - "5": 0 - }, - "reviews_count": 2, - "top_reviews": [ - { - "created": "Sun, 24 Jan 2016 16:46:47 GMT", - "edits": 0, - "entity_id": "3f0b7c79-1d66-40d2-a8a0-54be9ca2b18c", - "entity_type": "event", - "full_name": "Creative Commons Attribution-ShareAlike 3.0 Unported", - "id": "1da0c290-2c8c-47b4-9703-b172aaf9576f", - "info_url": "https://creativecommons.org/licenses/by-sa/3.0/", - "is_draft": false, - "is_hidden": false, - "language": "en", - "last_revision": { - "id": 9753, - "rating": null, - "review_id": "1da0c290-2c8c-47b4-9703-b172aaf9576f", - "text": "I had never been to a concert before, so when my mom told me that she had snagged tickets to see Taylor Swift's 1989 tour, I was speechless, anxious, overwhelmingly excited, and eager. I didn't know what to expect, but I knew Taylor was known for singing amazingly live and the numerous guest stars and the pretty costumes, lights, and scenes.\r\n\r\nBoy, was I right. The night was surely a night that I will never forget. Taylor started off with \"Welcome to New York\", one of my favorites, and continued onto some other gems of her album such as \"The New Romantics\" and \"Blank Space. I definitely was one of the embarrassing screaming girls that you always see in the audience. But everyone in the crowd was singing along to the lyrics of her new platinum album (how could you not?) and danced as she belted and strutted around the stage.\r\n\r\nTaylor Swift continued onto some of her older songs of previous years like \"I Know You Were Trouble\" and \"Mean.\" I loved them just as much! Her voice was so beautiful as the night continued. I wonder how someone could be so talented of that caliber - to be able to sing and dance for hours on end. In between tracks, she would thank the crowd and express her gratitude of being able to perform for us all. She ended the night with a bang, singing some of her classic hits like \"Love Story\" and \"We Are Never Getting Back Together.\"\r\n\r\nUltimately, one of the most momentous occasions I have ever experienced. I can't speak for other artists, but Taylor really felt the energy of the crowd and maintained that vigorous happy energy and she smiled and sang and just looked like she was having so much fun.", - "timestamp": "Sun, 24 Jan 2016 16:46:47 GMT" - }, - "last_updated": "Sun, 24 Jan 2016 16:46:47 GMT", - "license_id": "CC BY-SA 3.0", - "popularity": 1, - "published_on": "Sun, 24 Jan 2016 16:46:47 GMT", - "rating": null, - "source": null, - "source_url": null, - "text": "I had never been to a concert before, so when my mom told me that she had snagged tickets to see Taylor Swift's 1989 tour, I was speechless, anxious, overwhelmingly excited, and eager. I didn't know what to expect, but I knew Taylor was known for singing amazingly live and the numerous guest stars and the pretty costumes, lights, and scenes.\r\n\r\nBoy, was I right. The night was surely a night that I will never forget. Taylor started off with \"Welcome to New York\", one of my favorites, and continued onto some other gems of her album such as \"The New Romantics\" and \"Blank Space. I definitely was one of the embarrassing screaming girls that you always see in the audience. But everyone in the crowd was singing along to the lyrics of her new platinum album (how could you not?) and danced as she belted and strutted around the stage.\r\n\r\nTaylor Swift continued onto some of her older songs of previous years like \"I Know You Were Trouble\" and \"Mean.\" I loved them just as much! Her voice was so beautiful as the night continued. I wonder how someone could be so talented of that caliber - to be able to sing and dance for hours on end. In between tracks, she would thank the crowd and express her gratitude of being able to perform for us all. She ended the night with a bang, singing some of her classic hits like \"Love Story\" and \"We Are Never Getting Back Together.\"\r\n\r\nUltimately, one of the most momentous occasions I have ever experienced. I can't speak for other artists, but Taylor really felt the energy of the crowd and maintained that vigorous happy energy and she smiled and sang and just looked like she was having so much fun.", - "user": { - "created": "Sun, 24 Jan 2016 16:33:34 GMT", - "display_name": "michelletu", - "id": "8f5deaf3-84b9-4024-8751-6dbc0f9cf4ef", - "karma": 1, - "user_type": "Noob" - }, - "votes_negative_count": 0, - "votes_positive_count": 1 - }, - { - "created": "Sun, 17 Jan 2016 10:02:06 GMT", - "edits": 0, - "entity_id": "3f0b7c79-1d66-40d2-a8a0-54be9ca2b18c", - "entity_type": "event", - "full_name": "Creative Commons Attribution-ShareAlike 3.0 Unported", - "id": "b02b8836-d7e2-4077-9443-38c0c0f6725f", - "info_url": "https://creativecommons.org/licenses/by-sa/3.0/", - "is_draft": false, - "is_hidden": false, - "language": "en", - "last_revision": { - "id": 9636, - "rating": null, - "review_id": "b02b8836-d7e2-4077-9443-38c0c0f6725f", - "text": "It was one of the most hyped up music event of the year, and it certainly lived up to expectations. When it was announced that Taylor Swift would be bringing her 1989 World Tour to Singapore in November, I, among many others, made a mad rush to grab at whatever tickets we could. On the grand day itself, we were treated to a musical feast at the Singapore Indoor Stadium, which was truly an unforgettable experience. \r\n\r\nTaylor Swift started off her concert with a rousing medley of songs (\"Welcome to New York\", \"The New Romantics\" and \"Blank Space) from her platinum release album 1989, which certainly did not fail to wow the crowd. However, what made Taylor Swift's songs different this time round was that she added a Singapore touch to them, thus resonating with Singaporeans at her concert. For instance, during her dynamic single \"Blank Space\", Taylor used a metal rod to strike a pole situated in the middle of the runway, looping her saying \"Singapore\" into the backing track. \r\n\r\nNext, Taylor belted out classics from her previous albums, such as \"I Knew You Were Trouble\" and \"I Wish You Would\". In between the singles, Taylor thanked everyone at the concert for supporting her latest album \"1989\", which brought the crowd roaring with excitement. \"How You Get the Girl\" and \"You Belong With Me\" were up next, with the audience singing along to the classics. \r\n\r\nSome of her biggest hits were also subjected to makeovers. With Taylor strumming a white electric guitar, \"We Are Never Ever Getting Back Together\" sounded much heftier than the recorded version. Taylor also slowed down classics \"I Knew You Were Trouble\" and \"Love Story\" by a notch, creating a new dimension for familiar tunes. \r\n\r\nHowever, there were a couple of minor disappointments at the concert. Prior to the concert, many made wild guesses as to who the surprise guests at the Singapore concert would be - after all, Taylor had been making headlines for inviting her celebrity friends to join her in the 1989 World Tour. Hence, when we realised that there were not going to be any surprise guests, we could not help but feel a little shortchanged. But Taylor's last song for the concert, \"Shake It Off\", immediately helped us feel much better, with almost everyone jumping to their feet to dance along with Taylor, who was strutting her moves on the runway. \r\n\r\nAll in all, it was a dynamic night at the Singapore Indoor Stadium, with Taylor Swift belting out song after song, from classics to singles from her latest album. \r\n", - "timestamp": "Mon, 18 Jan 2016 22:52:28 GMT" - }, - "last_updated": "Mon, 18 Jan 2016 22:52:28 GMT", - "license_id": "CC BY-SA 3.0", - "popularity": 0, - "published_on": "Mon, 18 Jan 2016 22:52:28 GMT", - "rating": null, - "source": null, - "source_url": null, - "text": "It was one of the most hyped up music event of the year, and it certainly lived up to expectations. When it was announced that Taylor Swift would be bringing her 1989 World Tour to Singapore in November, I, among many others, made a mad rush to grab at whatever tickets we could. On the grand day itself, we were treated to a musical feast at the Singapore Indoor Stadium, which was truly an unforgettable experience. \r\n\r\nTaylor Swift started off her concert with a rousing medley of songs (\"Welcome to New York\", \"The New Romantics\" and \"Blank Space) from her platinum release album 1989, which certainly did not fail to wow the crowd. However, what made Taylor Swift's songs different this time round was that she added a Singapore touch to them, thus resonating with Singaporeans at her concert. For instance, during her dynamic single \"Blank Space\", Taylor used a metal rod to strike a pole situated in the middle of the runway, looping her saying \"Singapore\" into the backing track. \r\n\r\nNext, Taylor belted out classics from her previous albums, such as \"I Knew You Were Trouble\" and \"I Wish You Would\". In between the singles, Taylor thanked everyone at the concert for supporting her latest album \"1989\", which brought the crowd roaring with excitement. \"How You Get the Girl\" and \"You Belong With Me\" were up next, with the audience singing along to the classics. \r\n\r\nSome of her biggest hits were also subjected to makeovers. With Taylor strumming a white electric guitar, \"We Are Never Ever Getting Back Together\" sounded much heftier than the recorded version. Taylor also slowed down classics \"I Knew You Were Trouble\" and \"Love Story\" by a notch, creating a new dimension for familiar tunes. \r\n\r\nHowever, there were a couple of minor disappointments at the concert. Prior to the concert, many made wild guesses as to who the surprise guests at the Singapore concert would be - after all, Taylor had been making headlines for inviting her celebrity friends to join her in the 1989 World Tour. Hence, when we realised that there were not going to be any surprise guests, we could not help but feel a little shortchanged. But Taylor's last song for the concert, \"Shake It Off\", immediately helped us feel much better, with almost everyone jumping to their feet to dance along with Taylor, who was strutting her moves on the runway. \r\n\r\nAll in all, it was a dynamic night at the Singapore Indoor Stadium, with Taylor Swift belting out song after song, from classics to singles from her latest album. \r\n", - "user": { - "created": "Sun, 17 Jan 2016 06:49:23 GMT", - "display_name": "gabriellee", - "id": "172631a3-8338-4aaf-84c8-1ed0f02c56c1", - "karma": 0, - "user_type": "Noob" - }, - "votes_negative_count": 0, - "votes_positive_count": 0 - } - ] - } - :statuscode 200: no error - :statuscode 404: label not found - - :query username: User's username **(optional)** - - :resheader Content-Type: *application/json* - """ - - event = db_event.get_event_by_mbid(str(event_mbid)) - - if not event: - raise NotFound("Can't find an event with ID: {event_mbid}".format(event_mbid=event_mbid)) - - user_review = None - - username = Parser.string('uri', 'username', optional=True) - if username: - user_review_cache_key = cache.gen_key('entity_api', event['mbid'], username, "user_review") - user_review = cache.get(user_review_cache_key, REVIEW_CACHE_NAMESPACE) - if not user_review: - user = db_users.get_by_mbid(username) - if user: - user_id = user['id'] - - user_review, _ = db_review.list_reviews( - entity_id=event['mbid'], - entity_type='event', - user_id=user_id - ) - if user_review: - user_review = db_review.to_dict(user_review[0]) - - cache.set(user_review_cache_key, user_review, - expirein=REVIEW_CACHE_TIMEOUT, namespace=REVIEW_CACHE_NAMESPACE) - - else: - user_review = [] - - ratings_stats, average_rating = db_rating_stats.get_stats(event_mbid, "event") - - top_reviews_cache_key = cache.gen_key("entity_api_event", event['mbid'], "top_reviews") - top_reviews_cached_result = cache.get(top_reviews_cache_key, REVIEW_CACHE_NAMESPACE) - - if top_reviews_cached_result: - top_reviews, reviews_count = top_reviews_cached_result - else: - top_reviews, reviews_count = db_review.list_reviews( - entity_id=event['mbid'], - entity_type='event', - sort='popularity', - limit=REVIEWS_LIMIT, - offset=0, - ) - top_reviews = [db_review.to_dict(review) for review in top_reviews] - - cache.set(top_reviews_cache_key, (top_reviews, reviews_count), - expirein=REVIEW_CACHE_TIMEOUT, namespace=REVIEW_CACHE_NAMESPACE) - - latest_reviews_cache_key = cache.gen_key("entity_api_event", event['mbid'], "latest_reviews") - latest_reviews_cached_result = cache.get(latest_reviews_cache_key, REVIEW_CACHE_NAMESPACE) - - if latest_reviews_cached_result: - latest_reviews, reviews_count = latest_reviews_cached_result - else: - latest_reviews, reviews_count = db_review.list_reviews( - entity_id=event['mbid'], - entity_type='event', - sort='published_on', - limit=REVIEWS_LIMIT, - offset=0, - ) - latest_reviews = [db_review.to_dict(review) for review in latest_reviews] - - cache.set(latest_reviews_cache_key, (latest_reviews, reviews_count), - expirein=REVIEW_CACHE_TIMEOUT, namespace=REVIEW_CACHE_NAMESPACE) - - result = { - "event": event, - "average_rating": average_rating, - "ratings_stats": ratings_stats, - "reviews_count": reviews_count, - "top_reviews": top_reviews, - "latest_reviews": latest_reviews - } - - if username: - result['user_review'] = user_review - - return jsonify(**result) diff --git a/critiquebrainz/ws/label/__init__.py b/critiquebrainz/ws/label/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/critiquebrainz/ws/label/test/__init__.py b/critiquebrainz/ws/label/test/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/critiquebrainz/ws/label/test/views_test.py b/critiquebrainz/ws/label/test/views_test.py deleted file mode 100644 index 300dc6820..000000000 --- a/critiquebrainz/ws/label/test/views_test.py +++ /dev/null @@ -1,88 +0,0 @@ -from brainzutils import cache - -import critiquebrainz.db.license as db_license -import critiquebrainz.db.review as db_review -import critiquebrainz.db.users as db_users -from critiquebrainz.db.user import User -from critiquebrainz.ws.testing import WebServiceTestCase - - -class LabelViewsTestCase(WebServiceTestCase): - - def setUp(self): - super(LabelViewsTestCase, self).setUp() - - self.label_id1 = "13a464dc-b9fd-4d16-a4f4-d4316f6a46c7" - self.label_id2 = "d8067fa7-8758-4527-b436-ce5faff3e576" - - self.user = User(db_users.get_or_create(1, "Tester", new_user_data={ - "display_name": "test user", - })) - self.another_user = User(db_users.get_or_create(2, "Hacker!", new_user_data={ - "display_name": "test hacker", - })) - self.license = db_license.create( - id="CC BY-SA 3.0", - full_name="Created so we can fill the form correctly.", - ) - self.review = dict( - entity_id=self.label_id1, - entity_type='label', - user_id=self.user.id, - text="Testing! This text should be on the page.", - rating=5, - is_draft=False, - license_id=self.license["id"], - ) - self.review2 = dict( - entity_id=self.label_id2, - entity_type='label', - user_id=self.user.id, - text="Testing! This text should be on the page.", - rating=5, - is_draft=False, - license_id=self.license["id"], - ) - - def create_dummy_review(self): - return db_review.create(**self.review) - - def create_dummy_review2(self): - return db_review.create(**self.review2) - - def test_label_endpoint(self): - review = self.create_dummy_review() - response = self.client.get('/label/13a464dc-b9fd-4d16-a4f4-d4316f6a46c7') - - self.assert200(response) - self.assertIn(review['text'], response.json['top_reviews'][0]['text']) - - self.assertEqual(5, response.json['average_rating']) - self.assertEqual(1, response.json['reviews_count']) - - # Test for a label that doesn't exist - response = self.client.get('/label/13a464dc-b9fd-4d16-a4f4-d4316f6a46c1') - self.assert404(response) - - def test_label_user_reviews(self): - review = self.create_dummy_review() - response = self.client.get('/label/13a464dc-b9fd-4d16-a4f4-d4316f6a46c7?username=%s' % self.user.musicbrainz_username) - - self.assert200(response) - self.assertIn(review['text'], response.json['user_review']['text']) - - def test_user_cache_tracking(self): - track_key = cache.gen_key("entity_api", self.label_id2, self.user.musicbrainz_username, "user_review") - - # Make sure the cache is empty - self.client.get('/label/d8067fa7-8758-4527-b436-ce5faff3e576?username=%s' % self.user.musicbrainz_username) - cache_value = cache.get(track_key, namespace="Review") - self.assertEqual([], cache_value) - - review = self.create_dummy_review2() - - # Check if the cache is populated after the request - self.client.get('/label/d8067fa7-8758-4527-b436-ce5faff3e576?username=%s' % self.user.musicbrainz_username) - cache_value = cache.get(track_key, namespace="Review") - - self.assertIn(review['text'], cache_value['text']) diff --git a/critiquebrainz/ws/label/views.py b/critiquebrainz/ws/label/views.py deleted file mode 100644 index 03c3b3d9b..000000000 --- a/critiquebrainz/ws/label/views.py +++ /dev/null @@ -1,253 +0,0 @@ -from flask import Blueprint, jsonify -import critiquebrainz.db.review as db_review -import critiquebrainz.db.users as db_users -import critiquebrainz.db.rating_stats as db_rating_stats -from critiquebrainz.frontend.external.musicbrainz_db import label as db_label -from critiquebrainz.decorators import crossdomain -from critiquebrainz.ws.exceptions import NotFound -from critiquebrainz.ws.parser import Parser -from critiquebrainz.ws import REVIEWS_LIMIT -from critiquebrainz.ws import REVIEWS_LIMIT, REVIEW_CACHE_NAMESPACE, REVIEW_CACHE_TIMEOUT -from brainzutils import cache - -label_bp = Blueprint('ws_label', __name__) - - -@label_bp.route('/', methods=['GET', 'OPTIONS']) -@crossdomain(headers="Authorization, Content-Type") -def label_entity_handler(label_mbid): - """Get list of reviews. - - **Request Example:** - - .. code-block:: bash - - $ curl "https://critiquebrainz.org/ws/1/label/e268deeb-31bc-4428-9caf-c7e2726cd496" \\ - -X GET - - **Response Example:** - - .. code-block:: json - - { - "average_rating": 5.0, - "label": { - "area": "Dakar", - "artist-rels": [ - { - "artist": { - "comment": "Songwriter / Producer / CEO", - "life-span": { - "begin": "1992-06-07" - }, - "mbid": "547bb51b-a016-402e-8120-50c3e8def75b", - "name": "Med Mouha", - "sort_name": "Mouha, Med", - "type": "Person" - }, - "begin-year": 2019, - "direction": "backward", - "end-year": null, - "type": "label founder", - "type-id": "577996f3-7ff9-45cf-877e-740fb1267a63" - } - ], - "comment": "Records, Dabass", - "external-urls": [ - { - "begin-year": null, - "direction": "forward", - "end-year": null, - "icon": "discogs-16.png", - "name": "Discogs", - "type": "discogs", - "type-id": "5b987f87-25bc-4a2d-b3f1-3618795b8207", - "url": { - "mbid": "e810e5d5-d2cc-406a-9365-a2bc84e35754", - "url": "https://www.discogs.com/label/2385910" - } - } - ], - "life-span": { - "begin": "2019" - }, - "mbid": "e268deeb-31bc-4428-9caf-c7e2726cd496", - "name": "Dabass Records", - "rating": 100, - "type": "Production" - }, - "latest_reviews": [ - { - "created": "Sun, 05 Sep 2021 16:57:39 GMT", - "edits": 0, - "entity_id": "e268deeb-31bc-4428-9caf-c7e2726cd496", - "entity_type": "label", - "full_name": "Creative Commons Attribution-ShareAlike 3.0 Unported", - "id": "5d941575-29ff-4dde-a48a-ad0ba5daec8c", - "info_url": "https://creativecommons.org/licenses/by-sa/3.0/", - "is_draft": false, - "is_hidden": false, - "language": "en", - "last_revision": { - "id": 10991, - "rating": 5, - "review_id": "5d941575-29ff-4dde-a48a-ad0ba5daec8c", - "text": null, - "timestamp": "Sun, 05 Sep 2021 16:57:39 GMT" - }, - "last_updated": "Sun, 05 Sep 2021 16:57:39 GMT", - "license_id": "CC BY-SA 3.0", - "popularity": 0, - "published_on": "Sun, 05 Sep 2021 16:57:39 GMT", - "rating": 5, - "source": null, - "source_url": null, - "text": null, - "user": { - "created": "Sun, 05 Sep 2021 14:20:33 GMT", - "display_name": "Medmouha", - "id": "20d9fb34-dc1f-4aa4-8082-c69e4b20f660", - "karma": 0, - "user_type": "Noob" - }, - "votes_negative_count": 0, - "votes_positive_count": 0 - } - ], - "ratings_stats": { - "1": 0, - "2": 0, - "3": 0, - "4": 0, - "5": 1 - }, - "reviews_count": 1, - "top_reviews": [ - { - "created": "Sun, 05 Sep 2021 16:57:39 GMT", - "edits": 0, - "entity_id": "e268deeb-31bc-4428-9caf-c7e2726cd496", - "entity_type": "label", - "full_name": "Creative Commons Attribution-ShareAlike 3.0 Unported", - "id": "5d941575-29ff-4dde-a48a-ad0ba5daec8c", - "info_url": "https://creativecommons.org/licenses/by-sa/3.0/", - "is_draft": false, - "is_hidden": false, - "language": "en", - "last_revision": { - "id": 10991, - "rating": 5, - "review_id": "5d941575-29ff-4dde-a48a-ad0ba5daec8c", - "text": null, - "timestamp": "Sun, 05 Sep 2021 16:57:39 GMT" - }, - "last_updated": "Sun, 05 Sep 2021 16:57:39 GMT", - "license_id": "CC BY-SA 3.0", - "popularity": 0, - "published_on": "Sun, 05 Sep 2021 16:57:39 GMT", - "rating": 5, - "source": null, - "source_url": null, - "text": null, - "user": { - "created": "Sun, 05 Sep 2021 14:20:33 GMT", - "display_name": "Medmouha", - "id": "20d9fb34-dc1f-4aa4-8082-c69e4b20f660", - "karma": 0, - "user_type": "Noob" - }, - "votes_negative_count": 0, - "votes_positive_count": 0 - } - ] - } - - :statuscode 200: no error - :statuscode 404: label not found - - :query username: User's username **(optional)** - - :resheader Content-Type: *application/json* - """ - - label = db_label.get_label_by_mbid(str(label_mbid)) - - if not label: - raise NotFound("Can't find a label with ID: {label_mbid}".format(label_mbid=label_mbid)) - - user_review = None - - username = Parser.string('uri', 'username', optional=True) - if username: - user_review_cache_key = cache.gen_key('entity_api', label['mbid'], username, "user_review") - user_review = cache.get(user_review_cache_key, REVIEW_CACHE_NAMESPACE) - if not user_review: - user = db_users.get_by_mbid(username) - if user: - user_id = user['id'] - - user_review, _ = db_review.list_reviews( - entity_id=label['mbid'], - entity_type='label', - user_id=user_id - ) - if user_review: - user_review = db_review.to_dict(user_review[0]) - - cache.set(user_review_cache_key, user_review, - expirein=REVIEW_CACHE_TIMEOUT, namespace=REVIEW_CACHE_NAMESPACE) - - else: - user_review = [] - - ratings_stats, average_rating = db_rating_stats.get_stats(label_mbid, "label") - - top_reviews_cache_key = cache.gen_key("entity_api_label", label['mbid'], "top_reviews") - top_reviews_cached_result = cache.get(top_reviews_cache_key, REVIEW_CACHE_NAMESPACE) - - if top_reviews_cached_result: - top_reviews, reviews_count = top_reviews_cached_result - else: - top_reviews, reviews_count = db_review.list_reviews( - entity_id=label['mbid'], - entity_type='label', - sort='popularity', - limit=REVIEWS_LIMIT, - offset=0, - ) - top_reviews = [db_review.to_dict(review) for review in top_reviews] - - cache.set(top_reviews_cache_key, (top_reviews, reviews_count), - expirein=REVIEW_CACHE_TIMEOUT, namespace=REVIEW_CACHE_NAMESPACE) - - latest_reviews_cache_key = cache.gen_key("entity_api_label", label['mbid'], "latest_reviews") - latest_reviews_cached_result = cache.get(latest_reviews_cache_key, REVIEW_CACHE_NAMESPACE) - - if latest_reviews_cached_result: - latest_reviews, reviews_count = latest_reviews_cached_result - else: - latest_reviews, reviews_count = db_review.list_reviews( - entity_id=label['mbid'], - entity_type='label', - sort='published_on', - limit=REVIEWS_LIMIT, - offset=0, - ) - latest_reviews = [db_review.to_dict(review) for review in latest_reviews] - - cache.set(latest_reviews_cache_key, (latest_reviews, reviews_count), - expirein=REVIEW_CACHE_TIMEOUT, namespace=REVIEW_CACHE_NAMESPACE) - - result = { - "label": label, - "average_rating": average_rating, - "ratings_stats": ratings_stats, - "reviews_count": reviews_count, - "top_reviews": top_reviews, - "latest_reviews": latest_reviews - } - - if username: - result['user_review'] = user_review - - return jsonify(**result) diff --git a/critiquebrainz/ws/place/__init__.py b/critiquebrainz/ws/place/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/critiquebrainz/ws/place/test/__init__.py b/critiquebrainz/ws/place/test/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/critiquebrainz/ws/place/test/views_test.py b/critiquebrainz/ws/place/test/views_test.py deleted file mode 100644 index ab0b2e07e..000000000 --- a/critiquebrainz/ws/place/test/views_test.py +++ /dev/null @@ -1,88 +0,0 @@ -from brainzutils import cache - -import critiquebrainz.db.license as db_license -import critiquebrainz.db.review as db_review -import critiquebrainz.db.users as db_users -from critiquebrainz.db.user import User -from critiquebrainz.ws.testing import WebServiceTestCase - - -class PlaceViewsTestCase(WebServiceTestCase): - - def setUp(self): - super(PlaceViewsTestCase, self).setUp() - - self.place_id1 = "4352063b-a833-421b-a420-e7fb295dece0" - self.place_id2 = "853b36f9-8806-459c-9480-0766b8f9354b" - - self.user = User(db_users.get_or_create(1, "Tester", new_user_data={ - "display_name": "test user", - })) - self.another_user = User(db_users.get_or_create(2, "Hacker!", new_user_data={ - "display_name": "test hacker", - })) - self.license = db_license.create( - id="CC BY-SA 3.0", - full_name="Created so we can fill the form correctly.", - ) - self.review = dict( - entity_id=self.place_id1, - entity_type='place', - user_id=self.user.id, - text="Testing! This text should be on the page.", - rating=5, - is_draft=False, - license_id=self.license["id"], - ) - self.review2 = dict( - entity_id=self.place_id2, - entity_type='place', - user_id=self.user.id, - text="Testing! This text should be on the page.", - rating=5, - is_draft=False, - license_id=self.license["id"], - ) - - def create_dummy_review(self): - return db_review.create(**self.review) - - def create_dummy_review2(self): - return db_review.create(**self.review2) - - def test_place_endpoint(self): - review = self.create_dummy_review() - response = self.client.get('/place/4352063b-a833-421b-a420-e7fb295dece0') - - self.assert200(response) - self.assertIn(review['text'], response.json['top_reviews'][0]['text']) - - self.assertEqual(5, response.json['average_rating']) - self.assertEqual(1, response.json['reviews_count']) - - # Test for a place that doesn't exist - response = self.client.get('/place/4352063b-a833-421b-a420-e7fb295dece9') - self.assert404(response) - - def test_place_user_reviews(self): - review = self.create_dummy_review() - response = self.client.get('/place/4352063b-a833-421b-a420-e7fb295dece0?username=%s' % self.user.musicbrainz_username) - - self.assert200(response) - self.assertIn(review['text'], response.json['user_review']['text']) - - def test_user_cache_tracking(self): - track_key = cache.gen_key("entity_api", self.place_id2, self.user.musicbrainz_username, "user_review") - - # Make sure the cache is empty - self.client.get('/place/853b36f9-8806-459c-9480-0766b8f9354b?username=%s' % self.user.musicbrainz_username) - cache_value = cache.get(track_key, namespace="Review") - self.assertEqual([], cache_value) - - review = self.create_dummy_review2() - - # Check if the cache is populated after the request - self.client.get('/place/853b36f9-8806-459c-9480-0766b8f9354b?username=%s' % self.user.musicbrainz_username) - cache_value = cache.get(track_key, namespace="Review") - - self.assertIn(review['text'], cache_value['text']) diff --git a/critiquebrainz/ws/place/views.py b/critiquebrainz/ws/place/views.py deleted file mode 100644 index bd0bd5b50..000000000 --- a/critiquebrainz/ws/place/views.py +++ /dev/null @@ -1,407 +0,0 @@ -from flask import Blueprint, jsonify -import critiquebrainz.db.review as db_review -import critiquebrainz.db.users as db_users -import critiquebrainz.db.rating_stats as db_rating_stats -from critiquebrainz.frontend.external.musicbrainz_db import place as db_place -from critiquebrainz.decorators import crossdomain -from critiquebrainz.ws.exceptions import NotFound -from critiquebrainz.ws.parser import Parser -from critiquebrainz.ws import REVIEWS_LIMIT, REVIEW_CACHE_NAMESPACE, REVIEW_CACHE_TIMEOUT -from brainzutils import cache - -place_bp = Blueprint('ws_place', __name__) - - -@place_bp.route('/', methods=['GET', 'OPTIONS']) -@crossdomain(headers="Authorization, Content-Type") -def place_entity_handler(place_mbid): - """Get list of reviews. - - **Request Example:** - - .. code-block:: bash - - $ curl "https://critiquebrainz.org/ws/1/place/8da8cd3d-6162-4714-ad96-6d7c276f4f8a" \\ - -X GET - - **Response Example:** - - .. code-block:: json - - { - "average_rating": 0, - "latest_reviews": [ - { - "created": "Sun, 13 Mar 2016 12:16:52 GMT", - "edits": 0, - "entity_id": "8da8cd3d-6162-4714-ad96-6d7c276f4f8a", - "entity_type": "place", - "full_name": "Creative Commons Attribution-ShareAlike 3.0 Unported", - "id": "dd4b5498-ee04-4663-80f1-22eb5601c68b", - "info_url": "https://creativecommons.org/licenses/by-sa/3.0/", - "is_draft": false, - "is_hidden": false, - "language": "en", - "last_revision": { - "id": 9796, - "rating": null, - "review_id": "dd4b5498-ee04-4663-80f1-22eb5601c68b", - "text": "The Elbphilharmonie was hyped a lot but so far has failed lived up to everyone's expectations.\r\nThe delay of seven years only proves that large prestige projects in Germany do not tend to go well while costs skyrocket.\r\n\r\nI am though intrigued by the acoustics of the main concert hall, which will hopefully be as good as advertised.", - "timestamp": "Sun, 13 Mar 2016 12:17:09 GMT" - }, - "last_updated": "Sun, 13 Mar 2016 12:17:09 GMT", - "license_id": "CC BY-SA 3.0", - "popularity": -3, - "published_on": "Sun, 13 Mar 2016 12:17:09 GMT", - "rating": null, - "source": null, - "source_url": null, - "text": "The Elbphilharmonie was hyped a lot but so far has failed lived up to everyone's expectations.\r\nThe delay of seven years only proves that large prestige projects in Germany do not tend to go well while costs skyrocket.\r\n\r\nI am though intrigued by the acoustics of the main concert hall, which will hopefully be as good as advertised.", - "user": { - "created": "Thu, 19 Feb 2015 15:05:21 GMT", - "display_name": "Leo Verto", - "id": "f0aeeddc-0682-42bb-a340-b5773ca6f2c8", - "karma": -3, - "user_type": "Noob" - }, - "votes_negative_count": 3, - "votes_positive_count": 0 - } - ], - "place": { - "address": "Am Kaiserkai 1, 20457 Hamburg", - "area": { - "mbid": "11a44e18-a2e5-43a9-bee9-aa4f7c83f967", - "name": "Hamburg" - }, - "artist-rels": [ - { - "artist": { - "life-span": { - "begin": "1976-11-30" - }, - "mbid": "7d98417c-60b0-4567-b807-a250c25261ef", - "name": "Iveta Apkalna", - "sort_name": "Apkalna, Iveta", - "type": "Person" - }, - "begin-year": 2017, - "direction": "backward", - "end-year": null, - "type": "organist", - "type-id": "cad0dbab-c711-442a-a91c-05359f0228ce" - }, - { - "artist": { - "comment": "1945\u20132016: NDR Sinfonieorchester", - "life-span": { - "begin": "1945" - }, - "mbid": "2688f91d-dfdd-4d67-94df-0f1a02140c62", - "name": "NDR Elbphilharmonie Orchester", - "sort_name": "NDR Elbphilharmonie Orchester", - "type": "Orchestra" - }, - "begin-year": 2017, - "direction": "backward", - "end-year": null, - "type": "primary concert venue", - "type-id": "fff4640a-0819-49e9-92c5-1e3b5134fd95" - } - ], - "comment": "concert hall in Hamburg", - "coordinates": { - "latitude": 53.541238, - "longitude": 9.984255 - }, - "external-urls": [ - { - "begin-year": null, - "direction": "forward", - "end-year": null, - "icon": "discogs-16.png", - "name": "Discogs", - "type": "discogs", - "type-id": "1c140ac8-8dc2-449e-92cb-52c90d525640", - "url": { - "mbid": "96b51b85-0780-488a-bc71-902fb473595d", - "url": "https://www.discogs.com/label/1153802" - } - }, - { - "begin-year": null, - "direction": "forward", - "end-year": null, - "icon": "home-16.png", - "name": "Official homepage", - "type": "official homepage", - "type-id": "696b79da-7e45-40e6-a9d4-b31438eb7e5d", - "url": { - "mbid": "551b47c5-0b3b-49d2-aa5a-e3a5fc76c0c9", - "url": "http://www.elbphilharmonie.de/" - } - }, - { - "begin-year": null, - "direction": "forward", - "end-year": null, - "icon": "wikidata-16.png", - "name": "Wikidata", - "type": "wikidata", - "type-id": "e6826618-b410-4b8d-b3b5-52e29eac5e1f", - "url": { - "mbid": "7add6c0d-c644-4565-bb4e-09ad6f147568", - "url": "https://www.wikidata.org/wiki/Q673223" - } - } - ], - "life-span": { - "begin": "2017-01-11" - }, - "mbid": "8da8cd3d-6162-4714-ad96-6d7c276f4f8a", - "name": "Elbphilharmonie", - "parts": [ - { - "begin-year": null, - "direction": "forward", - "end-year": null, - "place": { - "address": "Am Kaiserkai 1, 20457 Hamburg", - "area": { - "mbid": "11a44e18-a2e5-43a9-bee9-aa4f7c83f967", - "name": "Hamburg" - }, - "coordinates": { - "latitude": 53.541238, - "longitude": 9.984255 - }, - "mbid": "b3aa8167-f92f-48d6-a3bf-e20c8e20c6b5", - "name": "Elbphilharmonie: Kaistudio 1" - }, - "type": "parts", - "type-id": "ff683f48-eff1-40ab-a58f-b128098ffe92" - }, - { - "begin-year": null, - "direction": "forward", - "end-year": null, - "place": { - "address": "Platz der Deutschen Einheit 1, 20457 Hamburg", - "area": { - "mbid": "11a44e18-a2e5-43a9-bee9-aa4f7c83f967", - "name": "Hamburg" - }, - "coordinates": { - "latitude": 53.54133, - "longitude": 9.98447 - }, - "mbid": "9fc8d3da-e7f1-404c-a615-e9794d57ddb6", - "name": "Elbphilharmonie: Gro\u00dfer Saal", - "type": "Venue" - }, - "type": "parts", - "type-id": "ff683f48-eff1-40ab-a58f-b128098ffe92" - }, - { - "begin-year": null, - "direction": "forward", - "end-year": null, - "place": { - "address": "", - "area": { - "mbid": "11a44e18-a2e5-43a9-bee9-aa4f7c83f967", - "name": "Hamburg" - }, - "mbid": "e810f624-7423-4efa-9cb4-de2e081cc755", - "name": "Elbphilharmonie: Kleiner Saal", - "type": "Venue" - }, - "type": "parts", - "type-id": "ff683f48-eff1-40ab-a58f-b128098ffe92" - } - ], - "type": "Venue", - "url-rels": [ - { - "begin-year": null, - "direction": "forward", - "end-year": null, - "type": "official homepage", - "type-id": "696b79da-7e45-40e6-a9d4-b31438eb7e5d", - "url": { - "mbid": "551b47c5-0b3b-49d2-aa5a-e3a5fc76c0c9", - "url": "http://www.elbphilharmonie.de/" - } - }, - { - "begin-year": null, - "direction": "forward", - "end-year": null, - "type": "discogs", - "type-id": "1c140ac8-8dc2-449e-92cb-52c90d525640", - "url": { - "mbid": "96b51b85-0780-488a-bc71-902fb473595d", - "url": "https://www.discogs.com/label/1153802" - } - }, - { - "begin-year": null, - "direction": "forward", - "end-year": null, - "type": "songkick", - "type-id": "3eb58d3e-6f00-36a8-a115-3dad616b7391", - "url": { - "mbid": "97b4b49f-aa9c-4809-ae63-6184896655df", - "url": "https://www.songkick.com/venues/914796" - } - }, - { - "begin-year": null, - "direction": "forward", - "end-year": null, - "type": "image", - "type-id": "68a4537c-f2a6-49b8-81c5-82a62b0976b7", - "url": { - "mbid": "cfdaf6e4-7e68-4aa1-9053-211afaf4bbd3", - "url": "https://commons.wikimedia.org/wiki/File:Elbphilharmonie,_Hamburg.jpg" - } - } - ] - }, - "ratings_stats": { - "1": 0, - "2": 0, - "3": 0, - "4": 0, - "5": 0 - }, - "reviews_count": 1, - "top_reviews": [ - { - "created": "Sun, 13 Mar 2016 12:16:52 GMT", - "edits": 0, - "entity_id": "8da8cd3d-6162-4714-ad96-6d7c276f4f8a", - "entity_type": "place", - "full_name": "Creative Commons Attribution-ShareAlike 3.0 Unported", - "id": "dd4b5498-ee04-4663-80f1-22eb5601c68b", - "info_url": "https://creativecommons.org/licenses/by-sa/3.0/", - "is_draft": false, - "is_hidden": false, - "language": "en", - "last_revision": { - "id": 9796, - "rating": null, - "review_id": "dd4b5498-ee04-4663-80f1-22eb5601c68b", - "text": "The Elbphilharmonie was hyped a lot but so far has failed lived up to everyone's expectations.\r\nThe delay of seven years only proves that large prestige projects in Germany do not tend to go well while costs skyrocket.\r\n\r\nI am though intrigued by the acoustics of the main concert hall, which will hopefully be as good as advertised.", - "timestamp": "Sun, 13 Mar 2016 12:17:09 GMT" - }, - "last_updated": "Sun, 13 Mar 2016 12:17:09 GMT", - "license_id": "CC BY-SA 3.0", - "popularity": -3, - "published_on": "Sun, 13 Mar 2016 12:17:09 GMT", - "rating": null, - "source": null, - "source_url": null, - "text": "The Elbphilharmonie was hyped a lot but so far has failed lived up to everyone's expectations.\r\nThe delay of seven years only proves that large prestige projects in Germany do not tend to go well while costs skyrocket.\r\n\r\nI am though intrigued by the acoustics of the main concert hall, which will hopefully be as good as advertised.", - "user": { - "created": "Thu, 19 Feb 2015 15:05:21 GMT", - "display_name": "Leo Verto", - "id": "f0aeeddc-0682-42bb-a340-b5773ca6f2c8", - "karma": -3, - "user_type": "Noob" - }, - "votes_negative_count": 3, - "votes_positive_count": 0 - } - ] - } - - :statuscode 200: no error - :statuscode 404: place not found - - :query username: User's username **(optional)** - - :resheader Content-Type: *application/json* - """ - - place = db_place.get_place_by_mbid(str(place_mbid)) - - if not place: - raise NotFound("Can't find a place with ID: {place_mbid}".format(place_mbid=place_mbid)) - - user_review = None - - username = Parser.string('uri', 'username', optional=True) - if username: - user_review_cache_key = cache.gen_key('entity_api', place['mbid'], username, "user_review") - user_review = cache.get(user_review_cache_key, REVIEW_CACHE_NAMESPACE) - if not user_review: - user = db_users.get_by_mbid(username) - if user: - user_id = user['id'] - - user_review, _ = db_review.list_reviews( - entity_id=place['mbid'], - entity_type='place', - user_id=user_id - ) - if user_review: - user_review = db_review.to_dict(user_review[0]) - - cache.set(user_review_cache_key, user_review, - expirein=REVIEW_CACHE_TIMEOUT, namespace=REVIEW_CACHE_NAMESPACE) - - else: - user_review = [] - - ratings_stats, average_rating = db_rating_stats.get_stats(place_mbid, "place") - - top_reviews_cache_key = cache.gen_key("entity_api_place", place['mbid'], "top_reviews") - top_reviews_cached_result = cache.get(top_reviews_cache_key, REVIEW_CACHE_NAMESPACE) - - if top_reviews_cached_result: - top_reviews, reviews_count = top_reviews_cached_result - else: - top_reviews, reviews_count = db_review.list_reviews( - entity_id=place['mbid'], - entity_type='place', - sort='popularity', - limit=REVIEWS_LIMIT, - offset=0, - ) - top_reviews = [db_review.to_dict(review) for review in top_reviews] - - cache.set(top_reviews_cache_key, (top_reviews, reviews_count), - expirein=REVIEW_CACHE_TIMEOUT, namespace=REVIEW_CACHE_NAMESPACE) - - latest_reviews_cache_key = cache.gen_key("entity_api_place", place['mbid'], "latest_reviews") - latest_reviews_cached_result = cache.get(latest_reviews_cache_key, REVIEW_CACHE_NAMESPACE) - - if latest_reviews_cached_result: - latest_reviews, reviews_count = latest_reviews_cached_result - else: - latest_reviews, reviews_count = db_review.list_reviews( - entity_id=place['mbid'], - entity_type='place', - sort='published_on', - limit=REVIEWS_LIMIT, - offset=0, - ) - latest_reviews = [db_review.to_dict(review) for review in latest_reviews] - - cache.set(latest_reviews_cache_key, (latest_reviews, reviews_count), - expirein=REVIEW_CACHE_TIMEOUT, namespace=REVIEW_CACHE_NAMESPACE) - - result = { - "place": place, - "average_rating": average_rating, - "ratings_stats": ratings_stats, - "reviews_count": reviews_count, - "top_reviews": top_reviews, - "latest_reviews": latest_reviews - } - - if username: - result['user_review'] = user_review - - return jsonify(**result) diff --git a/critiquebrainz/ws/recording/__init__.py b/critiquebrainz/ws/recording/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/critiquebrainz/ws/recording/test/__init__.py b/critiquebrainz/ws/recording/test/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/critiquebrainz/ws/recording/test/views_test.py b/critiquebrainz/ws/recording/test/views_test.py deleted file mode 100644 index 2dfa755e5..000000000 --- a/critiquebrainz/ws/recording/test/views_test.py +++ /dev/null @@ -1,88 +0,0 @@ -from brainzutils import cache - -import critiquebrainz.db.license as db_license -import critiquebrainz.db.review as db_review -import critiquebrainz.db.users as db_users -from critiquebrainz.db.user import User -from critiquebrainz.ws.testing import WebServiceTestCase - - -class RecordingViewsTestCase(WebServiceTestCase): - - def setUp(self): - super(RecordingViewsTestCase, self).setUp() - - self.recording_id1 = "442ddce2-ffa1-4865-81d2-b42c40fec7c5" - self.recording_id2 = "7a03530e-1018-458c-a254-b814a4b62b72" - - self.user = User(db_users.get_or_create(1, "Tester", new_user_data={ - "display_name": "test user", - })) - self.another_user = User(db_users.get_or_create(2, "Hacker!", new_user_data={ - "display_name": "test hacker", - })) - self.license = db_license.create( - id="CC BY-SA 3.0", - full_name="Created so we can fill the form correctly.", - ) - self.review = dict( - entity_id=self.recording_id1, - entity_type='recording', - user_id=self.user.id, - text="Testing! This text should be on the page.", - rating=5, - is_draft=False, - license_id=self.license["id"], - ) - self.review2 = dict( - entity_id=self.recording_id2, - entity_type='recording', - user_id=self.user.id, - text="Testing! This text should be on the page.", - rating=5, - is_draft=False, - license_id=self.license["id"], - ) - - def create_dummy_review(self): - return db_review.create(**self.review) - - def create_dummy_review2(self): - return db_review.create(**self.review2) - - def test_recording_endpoint(self): - review = self.create_dummy_review() - response = self.client.get('/recording/442ddce2-ffa1-4865-81d2-b42c40fec7c5') - - self.assert200(response) - self.assertIn(review['text'], response.json['top_reviews'][0]['text']) - - self.assertEqual(5, response.json['average_rating']) - self.assertEqual(1, response.json['reviews_count']) - - # Test for a recording that doesn't exist - response = self.client.get('/recording/442ddce2-ffa1-4865-81d2-b42c40fec7c1') - self.assert404(response) - - def test_recording_user_reviews(self): - review = self.create_dummy_review() - response = self.client.get('/recording/442ddce2-ffa1-4865-81d2-b42c40fec7c5?username=%s' % self.user.musicbrainz_username) - - self.assert200(response) - self.assertIn(review['text'], response.json['user_review']['text']) - - def test_user_cache_tracking(self): - track_key = cache.gen_key("entity_api", self.recording_id2, self.user.musicbrainz_username, "user_review") - - # Make sure the cache is empty - self.client.get('/recording/7a03530e-1018-458c-a254-b814a4b62b72?username=%s' % self.user.musicbrainz_username) - cache_value = cache.get(track_key, namespace="Review") - self.assertEqual([], cache_value) - - review = self.create_dummy_review2() - - # Check if the cache is populated after the request - self.client.get('/recording/7a03530e-1018-458c-a254-b814a4b62b72?username=%s' % self.user.musicbrainz_username) - cache_value = cache.get(track_key, namespace="Review") - - self.assertIn(review['text'], cache_value['text']) diff --git a/critiquebrainz/ws/recording/views.py b/critiquebrainz/ws/recording/views.py deleted file mode 100644 index 9964eb013..000000000 --- a/critiquebrainz/ws/recording/views.py +++ /dev/null @@ -1,221 +0,0 @@ -from flask import Blueprint, jsonify -import critiquebrainz.db.review as db_review -import critiquebrainz.db.users as db_users -import critiquebrainz.db.rating_stats as db_rating_stats -from critiquebrainz.frontend.external.musicbrainz_db import recording as db_recording -from critiquebrainz.decorators import crossdomain -from critiquebrainz.ws.exceptions import NotFound -from critiquebrainz.ws.parser import Parser -from critiquebrainz.ws import REVIEWS_LIMIT, REVIEW_CACHE_NAMESPACE, REVIEW_CACHE_TIMEOUT -from brainzutils import cache - -recording_bp = Blueprint('ws_recording', __name__) - -@recording_bp.route('/', methods=['GET', 'OPTIONS']) -@crossdomain(headers="Authorization, Content-Type") -def recording_entity_handler(recording_mbid): - """Get list of reviews. - - **Request Example:** - - .. code-block:: bash - - $ curl "https://critiquebrainz.org/ws/1/recording/299e8f6d-957d-43fd-a724-ebfd4c570bbc" \\ - -X GET - - **Response Example:** - - .. code-block:: json - - { - "average_rating": 5.0, - "latest_reviews": [ - { - "created": "Sat, 04 Dec 2021 13:17:15 GMT", - "edits": 0, - "entity_id": "299e8f6d-957d-43fd-a724-ebfd4c570bbc", - "entity_type": "recording", - "full_name": "Creative Commons Attribution-ShareAlike 3.0 Unported", - "id": "5f98882a-86c0-41b6-b828-59c3886adf6b", - "info_url": "https://creativecommons.org/licenses/by-sa/3.0/", - "is_draft": false, - "is_hidden": false, - "language": "en", - "last_revision": { - "id": 11177, - "rating": 5, - "review_id": "5f98882a-86c0-41b6-b828-59c3886adf6b", - "text": null, - "timestamp": "Sat, 04 Dec 2021 13:17:15 GMT" - }, - "last_updated": "Sat, 04 Dec 2021 13:17:15 GMT", - "license_id": "CC BY-SA 3.0", - "popularity": 0, - "published_on": "Sat, 04 Dec 2021 13:17:15 GMT", - "rating": 5, - "source": null, - "source_url": null, - "text": null, - "user": { - "created": "Wed, 26 May 2021 13:20:30 GMT", - "display_name": "akshaaatt", - "id": "7a98bc0a-1e40-4cd6-b36d-8c25931533af", - "karma": 1, - "user_type": "Noob" - }, - "votes_negative_count": 0, - "votes_positive_count": 0 - } - ], - "ratings_stats": { - "1": 0, - "2": 0, - "3": 0, - "4": 0, - "5": 1 - }, - "recording": { - "artist-credit-phrase": "Linkin Park", - "artists": [ - { - "mbid": "f59c5520-5f46-4d2c-b2c4-822eabf53419", - "name": "Linkin Park" - } - ], - "comment": "live, 2012-06-05: Admiralspalast, Berlin, Germany", - "length": 301.0, - "mbid": "299e8f6d-957d-43fd-a724-ebfd4c570bbc", - "name": "New Divide", - "rating": 100, - "video": true - }, - "reviews_count": 1, - "top_reviews": [ - { - "created": "Sat, 04 Dec 2021 13:17:15 GMT", - "edits": 0, - "entity_id": "299e8f6d-957d-43fd-a724-ebfd4c570bbc", - "entity_type": "recording", - "full_name": "Creative Commons Attribution-ShareAlike 3.0 Unported", - "id": "5f98882a-86c0-41b6-b828-59c3886adf6b", - "info_url": "https://creativecommons.org/licenses/by-sa/3.0/", - "is_draft": false, - "is_hidden": false, - "language": "en", - "last_revision": { - "id": 11177, - "rating": 5, - "review_id": "5f98882a-86c0-41b6-b828-59c3886adf6b", - "text": null, - "timestamp": "Sat, 04 Dec 2021 13:17:15 GMT" - }, - "last_updated": "Sat, 04 Dec 2021 13:17:15 GMT", - "license_id": "CC BY-SA 3.0", - "popularity": 0, - "published_on": "Sat, 04 Dec 2021 13:17:15 GMT", - "rating": 5, - "source": null, - "source_url": null, - "text": null, - "user": { - "created": "Wed, 26 May 2021 13:20:30 GMT", - "display_name": "akshaaatt", - "id": "7a98bc0a-1e40-4cd6-b36d-8c25931533af", - "karma": 1, - "user_type": "Noob" - }, - "votes_negative_count": 0, - "votes_positive_count": 0 - } - ] - } - - :statuscode 200: no error - :statuscode 404: recording not found - - :query username: User's username **(optional)** - - :resheader Content-Type: *application/json* - """ - - recording = db_recording.get_recording_by_mbid(str(recording_mbid)) - - if not recording: - raise NotFound("Can't find a recording with ID: {recording_mbid}".format(recording_mbid=recording_mbid)) - - user_review = None - - username = Parser.string('uri', 'username', optional=True) - if username: - user_review_cache_key = cache.gen_key('entity_api', recording['mbid'], username, "user_review") - user_review = cache.get(user_review_cache_key, REVIEW_CACHE_NAMESPACE) - if not user_review: - user = db_users.get_by_mbid(username) - if user: - user_id = user['id'] - - user_review, _ = db_review.list_reviews( - entity_id=recording['mbid'], - entity_type='recording', - user_id=user_id - ) - if user_review: - user_review = db_review.to_dict(user_review[0]) - - cache.set(user_review_cache_key, user_review, - expirein=REVIEW_CACHE_TIMEOUT, namespace=REVIEW_CACHE_NAMESPACE) - - else: - user_review = [] - - ratings_stats, average_rating = db_rating_stats.get_stats(recording_mbid, "recording") - - top_reviews_cache_key = cache.gen_key("entity_api_recording", recording['mbid'], "top_reviews") - top_reviews_cached_result = cache.get(top_reviews_cache_key, REVIEW_CACHE_NAMESPACE) - - if top_reviews_cached_result: - top_reviews, reviews_count = top_reviews_cached_result - else: - top_reviews, reviews_count = db_review.list_reviews( - entity_id=recording['mbid'], - entity_type='recording', - sort='popularity', - limit=REVIEWS_LIMIT, - offset=0, - ) - top_reviews = [db_review.to_dict(review) for review in top_reviews] - - cache.set(top_reviews_cache_key, (top_reviews, reviews_count), - expirein=REVIEW_CACHE_TIMEOUT, namespace=REVIEW_CACHE_NAMESPACE) - - latest_reviews_cache_key = cache.gen_key("entity_api_recording", recording['mbid'], "latest_reviews") - latest_reviews_cached_result = cache.get(latest_reviews_cache_key, REVIEW_CACHE_NAMESPACE) - - if latest_reviews_cached_result: - latest_reviews, reviews_count = latest_reviews_cached_result - else: - latest_reviews, reviews_count = db_review.list_reviews( - entity_id=recording['mbid'], - entity_type='recording', - sort='published_on', - limit=REVIEWS_LIMIT, - offset=0, - ) - latest_reviews = [db_review.to_dict(review) for review in latest_reviews] - - cache.set(latest_reviews_cache_key, (latest_reviews, reviews_count), - expirein=REVIEW_CACHE_TIMEOUT, namespace=REVIEW_CACHE_NAMESPACE) - - result = { - "recording": recording, - "average_rating": average_rating, - "ratings_stats": ratings_stats, - "reviews_count": reviews_count, - "top_reviews": top_reviews, - "latest_reviews": latest_reviews - } - - if username: - result['user_review'] = user_review - - return jsonify(**result) diff --git a/critiquebrainz/ws/release_group/__init__.py b/critiquebrainz/ws/release_group/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/critiquebrainz/ws/release_group/test/__init__.py b/critiquebrainz/ws/release_group/test/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/critiquebrainz/ws/release_group/test/views_test.py b/critiquebrainz/ws/release_group/test/views_test.py deleted file mode 100644 index 9db56b291..000000000 --- a/critiquebrainz/ws/release_group/test/views_test.py +++ /dev/null @@ -1,88 +0,0 @@ -from brainzutils import cache - -import critiquebrainz.db.license as db_license -import critiquebrainz.db.review as db_review -import critiquebrainz.db.users as db_users -from critiquebrainz.db.user import User -from critiquebrainz.ws.testing import WebServiceTestCase - - -class ReleaseGroupViewsTestCase(WebServiceTestCase): - - def setUp(self): - super(ReleaseGroupViewsTestCase, self).setUp() - - self.release_group_id1 = "1eff4a06-056e-4dc7-91c4-0cbc5878f3c3" - self.release_group_id2 = "c033d28b-fe43-3744-a5f8-50b30a100dcb" - - self.user = User(db_users.get_or_create(1, "Tester", new_user_data={ - "display_name": "test user", - })) - self.another_user = User(db_users.get_or_create(2, "Hacker!", new_user_data={ - "display_name": "test hacker", - })) - self.license = db_license.create( - id="CC BY-SA 3.0", - full_name="Created so we can fill the form correctly.", - ) - self.review = dict( - entity_id=self.release_group_id1, - entity_type='release_group', - user_id=self.user.id, - text="Testing! This text should be on the page.", - rating=5, - is_draft=False, - license_id=self.license["id"], - ) - self.review2 = dict( - entity_id=self.release_group_id2, - entity_type='release_group', - user_id=self.user.id, - text="Testing! This text should be on the page.", - rating=5, - is_draft=False, - license_id=self.license["id"], - ) - - def create_dummy_review(self): - return db_review.create(**self.review) - - def create_dummy_review2(self): - return db_review.create(**self.review2) - - def test_release_group_endpoint(self): - review = self.create_dummy_review() - response = self.client.get('/release-group/1eff4a06-056e-4dc7-91c4-0cbc5878f3c3') - - self.assert200(response) - self.assertIn(review['text'], response.json['top_reviews'][0]['text']) - - self.assertEqual(5, response.json['average_rating']) - self.assertEqual(1, response.json['reviews_count']) - - # Test for a release_group that doesn't exist - response = self.client.get('/release-group/1eff4a06-056e-4dc7-91c4-0cbc5878f3c1') - self.assert404(response) - - def test_release_group_user_reviews(self): - review = self.create_dummy_review() - response = self.client.get('/release-group/1eff4a06-056e-4dc7-91c4-0cbc5878f3c3?username=%s' % self.user.musicbrainz_username) - - self.assert200(response) - self.assertIn(review['text'], response.json['user_review']['text']) - - def test_user_cache_tracking(self): - track_key = cache.gen_key("entity_api", self.release_group_id2, self.user.musicbrainz_username, "user_review") - - # Make sure the cache is empty - self.client.get('/release-group/c033d28b-fe43-3744-a5f8-50b30a100dcb?username=%s' % self.user.musicbrainz_username) - cache_value = cache.get(track_key, namespace="Review") - self.assertEqual([], cache_value) - - review = self.create_dummy_review2() - - # Check if the cache is populated after the request - self.client.get('/release-group/c033d28b-fe43-3744-a5f8-50b30a100dcb?username=%s' % self.user.musicbrainz_username) - cache_value = cache.get(track_key, namespace="Review") - - self.assertIn(review['text'], cache_value['text']) diff --git a/critiquebrainz/ws/release_group/views.py b/critiquebrainz/ws/release_group/views.py deleted file mode 100644 index cc9fc63cc..000000000 --- a/critiquebrainz/ws/release_group/views.py +++ /dev/null @@ -1,234 +0,0 @@ -from flask import Blueprint, jsonify -import critiquebrainz.db.review as db_review -import critiquebrainz.db.users as db_users -import critiquebrainz.db.rating_stats as db_rating_stats -from critiquebrainz.frontend.external.musicbrainz_db import release_group as db_release_group -from critiquebrainz.decorators import crossdomain -from critiquebrainz.ws.exceptions import NotFound -from critiquebrainz.ws.parser import Parser -from critiquebrainz.ws import REVIEWS_LIMIT, REVIEW_CACHE_NAMESPACE, REVIEW_CACHE_TIMEOUT -from brainzutils import cache - - -release_group_bp = Blueprint('ws_release_group', __name__) - - -@release_group_bp.route('/', methods=['GET', 'OPTIONS']) -@crossdomain(headers="Authorization, Content-Type") -def release_group_entity_handler(release_group_mbid): - """Get list of reviews. - - **Request Example:** - - .. code-block:: bash - - $ curl "https://critiquebrainz.org/ws/1/release-group/f0bd8ae8-321f-43d8-af87-e2f90d1b3817" \\ - -X GET - - **Response Example:** - - .. code-block:: json - - { - "average_rating": 4.0, - "latest_reviews": [ - { - "created": "Wed, 01 Dec 2021 16:28:50 GMT", - "edits": 0, - "entity_id": "f0bd8ae8-321f-43d8-af87-e2f90d1b3817", - "entity_type": "release_group", - "full_name": "Creative Commons Attribution-ShareAlike 3.0 Unported", - "id": "b08b37f6-c644-470d-af48-e77b14331e09", - "info_url": "https://creativecommons.org/licenses/by-sa/3.0/", - "is_draft": false, - "is_hidden": false, - "language": "en", - "last_revision": { - "id": 11167, - "rating": 4, - "review_id": "b08b37f6-c644-470d-af48-e77b14331e09", - "text": "Hollywood Greats, a cover mount from The Daily Mirror, was one of my first ever newspaper cover mounts I got - and sadly it led me to believe that all newspaper covermounts were free goldmines ready to be plundered; how wrong was I!\r\n\r\n10 great original studio recordings, and 5 so-so tracks from \"underground\" artists, however those 5 artists were so underground that even the internet knows nothing of them.\r\n\r\nI'm probably biased with this one as it was my first - but a good collection either way.", - "timestamp": "Wed, 01 Dec 2021 16:28:50 GMT" - }, - "last_updated": "Wed, 01 Dec 2021 16:28:50 GMT", - "license_id": "CC BY-SA 3.0", - "popularity": 1, - "published_on": "Wed, 01 Dec 2021 16:28:50 GMT", - "rating": 4, - "source": null, - "source_url": null, - "text": "Hollywood Greats, a cover mount from The Daily Mirror, was one of my first ever newspaper cover mounts I got - and sadly it led me to believe that all newspaper covermounts were free goldmines ready to be plundered; how wrong was I!\r\n\r\n10 great original studio recordings, and 5 so-so tracks from \"underground\" artists, however those 5 artists were so underground that even the internet knows nothing of them.\r\n\r\nI'm probably biased with this one as it was my first - but a good collection either way.", - "user": { - "created": "Sat, 15 Aug 2020 15:48:39 GMT", - "display_name": "sound.and.vision", - "id": "dfdae69f-275f-41a2-82c7-ac5d1f9c8129", - "karma": 27, - "user_type": "Noob" - }, - "votes_negative_count": 0, - "votes_positive_count": 1 - } - ], - "ratings_stats": { - "1": 0, - "2": 0, - "3": 0, - "4": 1, - "5": 0 - }, - "release_group": { - "artist-credit": [ - { - "artist": { - "comment": "add compilations to this artist", - "mbid": "89ad4ac3-39f7-470e-963a-56509c546377", - "name": "Various Artists", - "sort_name": "Various Artists", - "type": "Other" - }, - "name": "Various Artists" - } - ], - "artist-credit-phrase": "Various Artists", - "first-release-year": 2004, - "mbid": "f0bd8ae8-321f-43d8-af87-e2f90d1b3817", - "rating": 60, - "release-list": [ - { - "mbid": "f51d400c-3303-447f-8f3c-595e1b1352bf", - "name": "Hollywood Greats" - } - ], - "title": "Hollywood Greats", - "type": "Album" - }, - "reviews_count": 1, - "top_reviews": [ - { - "created": "Wed, 01 Dec 2021 16:28:50 GMT", - "edits": 0, - "entity_id": "f0bd8ae8-321f-43d8-af87-e2f90d1b3817", - "entity_type": "release_group", - "full_name": "Creative Commons Attribution-ShareAlike 3.0 Unported", - "id": "b08b37f6-c644-470d-af48-e77b14331e09", - "info_url": "https://creativecommons.org/licenses/by-sa/3.0/", - "is_draft": false, - "is_hidden": false, - "language": "en", - "last_revision": { - "id": 11167, - "rating": 4, - "review_id": "b08b37f6-c644-470d-af48-e77b14331e09", - "text": "Hollywood Greats, a cover mount from The Daily Mirror, was one of my first ever newspaper cover mounts I got - and sadly it led me to believe that all newspaper covermounts were free goldmines ready to be plundered; how wrong was I!\r\n\r\n10 great original studio recordings, and 5 so-so tracks from \"underground\" artists, however those 5 artists were so underground that even the internet knows nothing of them.\r\n\r\nI'm probably biased with this one as it was my first - but a good collection either way.", - "timestamp": "Wed, 01 Dec 2021 16:28:50 GMT" - }, - "last_updated": "Wed, 01 Dec 2021 16:28:50 GMT", - "license_id": "CC BY-SA 3.0", - "popularity": 1, - "published_on": "Wed, 01 Dec 2021 16:28:50 GMT", - "rating": 4, - "source": null, - "source_url": null, - "text": "Hollywood Greats, a cover mount from The Daily Mirror, was one of my first ever newspaper cover mounts I got - and sadly it led me to believe that all newspaper covermounts were free goldmines ready to be plundered; how wrong was I!\r\n\r\n10 great original studio recordings, and 5 so-so tracks from \"underground\" artists, however those 5 artists were so underground that even the internet knows nothing of them.\r\n\r\nI'm probably biased with this one as it was my first - but a good collection either way.", - "user": { - "created": "Sat, 15 Aug 2020 15:48:39 GMT", - "display_name": "sound.and.vision", - "id": "dfdae69f-275f-41a2-82c7-ac5d1f9c8129", - "karma": 27, - "user_type": "Noob" - }, - "votes_negative_count": 0, - "votes_positive_count": 1 - } - ] - } - - :statuscode 200: no error - :statuscode 404: release group not found - - :query username: User's username **(optional)** - - :resheader Content-Type: *application/json* - """ - - release_group = db_release_group.get_release_group_by_mbid(str(release_group_mbid)) - - if not release_group: - raise NotFound("Can't find a release group with ID: {release_group_mbid}".format(release_group_mbid=release_group_mbid)) - - user_review = None - - username = Parser.string('uri', 'username', optional=True) - if username: - user_review_cache_key = cache.gen_key('entity_api', release_group['mbid'], username, "user_review") - user_review = cache.get(user_review_cache_key, REVIEW_CACHE_NAMESPACE) - if not user_review: - user = db_users.get_by_mbid(username) - if user: - user_id = user['id'] - - user_review, _ = db_review.list_reviews( - entity_id=release_group['mbid'], - entity_type='release_group', - user_id=user_id - ) - if user_review: - user_review = db_review.to_dict(user_review[0]) - - cache.set(user_review_cache_key, user_review, - expirein=REVIEW_CACHE_TIMEOUT, namespace=REVIEW_CACHE_NAMESPACE) - - else: - user_review = [] - - ratings_stats, average_rating = db_rating_stats.get_stats(release_group_mbid, "release_group") - - top_reviews_cache_key = cache.gen_key("entity_api_release_group", release_group['mbid'], "top_reviews") - top_reviews_cached_result = cache.get(top_reviews_cache_key, REVIEW_CACHE_NAMESPACE) - - if top_reviews_cached_result: - top_reviews, reviews_count = top_reviews_cached_result - else: - top_reviews, reviews_count = db_review.list_reviews( - entity_id=release_group['mbid'], - entity_type='release_group', - sort='popularity', - limit=REVIEWS_LIMIT, - offset=0, - ) - top_reviews = [db_review.to_dict(review) for review in top_reviews] - - cache.set(top_reviews_cache_key, (top_reviews, reviews_count), - expirein=REVIEW_CACHE_TIMEOUT, namespace=REVIEW_CACHE_NAMESPACE) - - latest_reviews_cache_key = cache.gen_key("entity_api_release_group", release_group['mbid'], "latest_reviews") - latest_reviews_cached_result = cache.get(latest_reviews_cache_key, REVIEW_CACHE_NAMESPACE) - - if latest_reviews_cached_result: - latest_reviews, reviews_count = latest_reviews_cached_result - else: - latest_reviews, reviews_count = db_review.list_reviews( - entity_id=release_group['mbid'], - entity_type='release_group', - sort='published_on', - limit=REVIEWS_LIMIT, - offset=0, - ) - latest_reviews = [db_review.to_dict(review) for review in latest_reviews] - - cache.set(latest_reviews_cache_key, (latest_reviews, reviews_count), - expirein=REVIEW_CACHE_TIMEOUT, namespace=REVIEW_CACHE_NAMESPACE) - - result = { - "release_group": release_group, - "average_rating": average_rating, - "ratings_stats": ratings_stats, - "reviews_count": reviews_count, - "top_reviews": top_reviews, - "latest_reviews": latest_reviews - } - - if username: - result['user_review'] = user_review - - return jsonify(**result) diff --git a/critiquebrainz/ws/work/__init__.py b/critiquebrainz/ws/work/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/critiquebrainz/ws/work/test/__init__.py b/critiquebrainz/ws/work/test/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/critiquebrainz/ws/work/test/views_test.py b/critiquebrainz/ws/work/test/views_test.py deleted file mode 100644 index ac45fdfdc..000000000 --- a/critiquebrainz/ws/work/test/views_test.py +++ /dev/null @@ -1,88 +0,0 @@ -from brainzutils import cache - -import critiquebrainz.db.license as db_license -import critiquebrainz.db.review as db_review -import critiquebrainz.db.users as db_users -from critiquebrainz.db.user import User -from critiquebrainz.ws.testing import WebServiceTestCase - - -class WorkViewsTestCase(WebServiceTestCase): - - def setUp(self): - super(WorkViewsTestCase, self).setUp() - - self.work_id1 = "0e5a48f3-7d21-365c-bfb7-98d9865ea1dd" - self.work_id2 = "fd881e93-c199-4c86-81a7-cabc044948d0" - - self.user = User(db_users.get_or_create(1, "Tester", new_user_data={ - "display_name": "test user", - })) - self.another_user = User(db_users.get_or_create(2, "Hacker!", new_user_data={ - "display_name": "test hacker", - })) - self.license = db_license.create( - id="CC BY-SA 3.0", - full_name="Created so we can fill the form correctly.", - ) - self.review = dict( - entity_id=self.work_id1, - entity_type='work', - user_id=self.user.id, - text="Testing! This text should be on the page.", - rating=5, - is_draft=False, - license_id=self.license["id"], - ) - self.review2 = dict( - entity_id=self.work_id2, - entity_type='work', - user_id=self.user.id, - text="Testing! This text should be on the page.", - rating=5, - is_draft=False, - license_id=self.license["id"], - ) - - def create_dummy_review(self): - return db_review.create(**self.review) - - def create_dummy_review2(self): - return db_review.create(**self.review2) - - def test_work_endpoint(self): - review = self.create_dummy_review() - response = self.client.get('/work/0e5a48f3-7d21-365c-bfb7-98d9865ea1dd') - - self.assert200(response) - self.assertIn(review['text'], response.json['top_reviews'][0]['text']) - - self.assertEqual(5, response.json['average_rating']) - self.assertEqual(1, response.json['reviews_count']) - - # Test for a work that doesn't exist - response = self.client.get('/work/0e5a48f3-7d21-365c-bfb7-98d9865ea1d1') - self.assert404(response) - - def test_work_user_reviews(self): - review = self.create_dummy_review() - response = self.client.get('/work/0e5a48f3-7d21-365c-bfb7-98d9865ea1dd?username=%s' % self.user.musicbrainz_username) - - self.assert200(response) - self.assertIn(review['text'], response.json['user_review']['text']) - - def test_user_cache_tracking(self): - track_key = cache.gen_key("entity_api", self.work_id2, self.user.musicbrainz_username, "user_review") - - # Make sure the cache is empty - self.client.get('/work/fd881e93-c199-4c86-81a7-cabc044948d0?username=%s' % self.user.musicbrainz_username) - cache_value = cache.get(track_key, namespace="Review") - self.assertEqual([], cache_value) - - review = self.create_dummy_review2() - - # Check if the cache is populated after the request - self.client.get('/work/fd881e93-c199-4c86-81a7-cabc044948d0?username=%s' % self.user.musicbrainz_username) - cache_value = cache.get(track_key, namespace="Review") - - self.assertIn(review['text'], cache_value['text']) diff --git a/critiquebrainz/ws/work/views.py b/critiquebrainz/ws/work/views.py deleted file mode 100644 index 3f4cd8345..000000000 --- a/critiquebrainz/ws/work/views.py +++ /dev/null @@ -1,297 +0,0 @@ -from flask import Blueprint, jsonify -import critiquebrainz.db.review as db_review -import critiquebrainz.db.users as db_users -import critiquebrainz.db.rating_stats as db_rating_stats -from critiquebrainz.frontend.external.musicbrainz_db import work as db_work -from critiquebrainz.decorators import crossdomain -from critiquebrainz.ws.exceptions import NotFound -from critiquebrainz.ws.parser import Parser -from critiquebrainz.ws import REVIEWS_LIMIT, REVIEW_CACHE_NAMESPACE, REVIEW_CACHE_TIMEOUT -from brainzutils import cache - - -work_bp = Blueprint('ws_work', __name__) - - -@work_bp.route('/', methods=['GET', 'OPTIONS']) -@crossdomain(headers="Authorization, Content-Type") -def work_entity_handler(work_mbid): - """Get list of reviews. - - **Request Example:** - - .. code-block:: bash - - $ curl "https://critiquebrainz.org/ws/1/work/233d58f7-aba4-405a-8cd3-4adb344bd333" \\ - -X GET - - **Response Example:** - - .. code-block:: json - - { - "average_rating": 5.0, - "latest_reviews": [ - { - "created": "Thu, 01 Sep 2022 14:06:33 GMT", - "edits": 0, - "entity_id": "233d58f7-aba4-405a-8cd3-4adb344bd333", - "entity_type": "work", - "full_name": "Creative Commons Attribution-ShareAlike 3.0 Unported", - "id": "f8278997-bfd5-4d0e-8b9a-afb575bb1080", - "info_url": "https://creativecommons.org/licenses/by-sa/3.0/", - "is_draft": false, - "is_hidden": false, - "language": "en", - "last_revision": { - "id": 11784, - "rating": 5, - "review_id": "f8278997-bfd5-4d0e-8b9a-afb575bb1080", - "text": null, - "timestamp": "Thu, 01 Sep 2022 14:06:33 GMT" - }, - "last_updated": "Thu, 01 Sep 2022 14:06:33 GMT", - "license_id": "CC BY-SA 3.0", - "popularity": 0, - "published_on": "Thu, 01 Sep 2022 14:06:33 GMT", - "rating": 5, - "source": null, - "source_url": null, - "text": null, - "user": { - "created": "Tue, 18 Jan 2022 09:53:49 GMT", - "display_name": "Ansh Goyal", - "id": "11a1160e-d607-4882-8a82-e2e800f664fe", - "karma": 0, - "user_type": "Noob" - }, - "votes_negative_count": 0, - "votes_positive_count": 0 - } - ], - "ratings_stats": { - "1": 0, - "2": 0, - "3": 0, - "4": 0, - "5": 1 - }, - "reviews_count": 1, - "top_reviews": [ - { - "created": "Thu, 01 Sep 2022 14:06:33 GMT", - "edits": 0, - "entity_id": "233d58f7-aba4-405a-8cd3-4adb344bd333", - "entity_type": "work", - "full_name": "Creative Commons Attribution-ShareAlike 3.0 Unported", - "id": "f8278997-bfd5-4d0e-8b9a-afb575bb1080", - "info_url": "https://creativecommons.org/licenses/by-sa/3.0/", - "is_draft": false, - "is_hidden": false, - "language": "en", - "last_revision": { - "id": 11784, - "rating": 5, - "review_id": "f8278997-bfd5-4d0e-8b9a-afb575bb1080", - "text": null, - "timestamp": "Thu, 01 Sep 2022 14:06:33 GMT" - }, - "last_updated": "Thu, 01 Sep 2022 14:06:33 GMT", - "license_id": "CC BY-SA 3.0", - "popularity": 0, - "published_on": "Thu, 01 Sep 2022 14:06:33 GMT", - "rating": 5, - "source": null, - "source_url": null, - "text": null, - "user": { - "created": "Tue, 18 Jan 2022 09:53:49 GMT", - "display_name": "Ansh Goyal", - "id": "11a1160e-d607-4882-8a82-e2e800f664fe", - "karma": 0, - "user_type": "Noob" - }, - "votes_negative_count": 0, - "votes_positive_count": 0 - } - ], - "work": { - "artist-rels": [ - { - "artist": { - "life-span": { - "begin": "1998" - }, - "mbid": "663f33f4-8e5f-4773-93ea-493a1135de9a", - "name": "Tones and I", - "sort_name": "Tones and I", - "type": "Person" - }, - "begin-year": null, - "direction": "backward", - "end-year": null, - "type": "composer", - "type-id": "d59d99ea-23d4-4a80-b066-edca32ee158f" - }, - { - "artist": { - "life-span": { - "begin": "1998" - }, - "mbid": "663f33f4-8e5f-4773-93ea-493a1135de9a", - "name": "Tones and I", - "sort_name": "Tones and I", - "type": "Person" - }, - "begin-year": null, - "direction": "backward", - "end-year": null, - "type": "lyricist", - "type-id": "3e48faba-ec01-47fd-8e89-30e81161661c" - } - ], - "mbid": "233d58f7-aba4-405a-8cd3-4adb344bd333", - "name": "Dance Monkey", - "recording-rels": [ - { - "begin-year": null, - "direction": "backward", - "end-year": null, - "recording": { - "length": 208.823, - "mbid": "a24895fe-c0a1-41eb-b695-3dbddbe2cb36", - "name": "Dance Monkey" - }, - "type": "performance", - "type-id": "a3005666-a872-32c3-ad06-98af558e99b0" - }, - { - "begin-year": null, - "direction": "backward", - "end-year": null, - "recording": { - "comment": "Nath Jennings bootleg", - "mbid": "fceef8ab-f844-45e8-822a-12d0ed536a4c", - "name": "Dance Monkey" - }, - "type": "performance", - "type-id": "a3005666-a872-32c3-ad06-98af558e99b0" - }, - { - "begin-year": null, - "direction": "backward", - "end-year": null, - "recording": { - "length": 234.0, - "mbid": "0ae5d3b3-2756-40b0-98cd-4a913351a42f", - "name": "Dance Monkey" - }, - "type": "performance", - "type-id": "a3005666-a872-32c3-ad06-98af558e99b0" - }, - { - "begin-year": null, - "direction": "backward", - "end-year": null, - "recording": { - "length": 177.449, - "mbid": "c73f942a-1c06-4034-86a2-d9f2090ada18", - "name": "Dance Monkey" - }, - "type": "performance", - "type-id": "a3005666-a872-32c3-ad06-98af558e99b0" - } - ], - "type": "Song" - } - } - - :statuscode 200: no error - :statuscode 404: work not found - - :query username: User's username **(optional)** - - :resheader Content-Type: *application/json* - """ - - work = db_work.get_work_by_mbid(str(work_mbid)) - - if not work: - raise NotFound("Can't find a work with ID: {work_mbid}".format(work_mbid=work_mbid)) - - user_review = None - - username = Parser.string('uri', 'username', optional=True) - if username: - user_review_cache_key = cache.gen_key('entity_api', work['mbid'], username, "user_review") - user_review = cache.get(user_review_cache_key, REVIEW_CACHE_NAMESPACE) - if not user_review: - user = db_users.get_by_mbid(username) - if user: - user_id = user['id'] - - user_review, _ = db_review.list_reviews( - entity_id=work['mbid'], - entity_type='work', - user_id=user_id - ) - if user_review: - user_review = db_review.to_dict(user_review[0]) - - cache.set(user_review_cache_key, user_review, - expirein=REVIEW_CACHE_TIMEOUT, namespace=REVIEW_CACHE_NAMESPACE) - - else: - user_review = [] - - ratings_stats, average_rating = db_rating_stats.get_stats(work_mbid, "work") - - top_reviews_cache_key = cache.gen_key("entity_api_work", work['mbid'], "top_reviews") - top_reviews_cached_result = cache.get(top_reviews_cache_key, REVIEW_CACHE_NAMESPACE) - - if top_reviews_cached_result: - top_reviews, reviews_count = top_reviews_cached_result - else: - top_reviews, reviews_count = db_review.list_reviews( - entity_id=work['mbid'], - entity_type='work', - sort='popularity', - limit=REVIEWS_LIMIT, - offset=0, - ) - top_reviews = [db_review.to_dict(review) for review in top_reviews] - - cache.set(top_reviews_cache_key, (top_reviews, reviews_count), - expirein=REVIEW_CACHE_TIMEOUT, namespace=REVIEW_CACHE_NAMESPACE) - - latest_reviews_cache_key = cache.gen_key("entity_api_work", work['mbid'], "latest_reviews") - latest_reviews_cached_result = cache.get(latest_reviews_cache_key, REVIEW_CACHE_NAMESPACE) - - if latest_reviews_cached_result: - latest_reviews, reviews_count = latest_reviews_cached_result - else: - latest_reviews, reviews_count = db_review.list_reviews( - entity_id=work['mbid'], - entity_type='work', - sort='published_on', - limit=REVIEWS_LIMIT, - offset=0, - ) - latest_reviews = [db_review.to_dict(review) for review in latest_reviews] - - cache.set(latest_reviews_cache_key, (latest_reviews, reviews_count), - expirein=REVIEW_CACHE_TIMEOUT, namespace=REVIEW_CACHE_NAMESPACE) - - result = { - "work": work, - "average_rating": average_rating, - "ratings_stats": ratings_stats, - "reviews_count": reviews_count, - "top_reviews": top_reviews, - "latest_reviews": latest_reviews - } - - if username: - result['user_review'] = user_review - - return jsonify(**result) From d57d1bacd5963b1c48de9c1db1f32b4aa0aeb6e1 Mon Sep 17 00:00:00 2001 From: anshg1214 Date: Fri, 15 Nov 2024 17:17:53 +0000 Subject: [PATCH 13/15] test: Add tests for entity route --- critiquebrainz/ws/entity/test/__init__.py | 0 critiquebrainz/ws/entity/test/views_test.py | 89 +++++++++++++++++++++ 2 files changed, 89 insertions(+) create mode 100644 critiquebrainz/ws/entity/test/__init__.py create mode 100644 critiquebrainz/ws/entity/test/views_test.py diff --git a/critiquebrainz/ws/entity/test/__init__.py b/critiquebrainz/ws/entity/test/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/critiquebrainz/ws/entity/test/views_test.py b/critiquebrainz/ws/entity/test/views_test.py new file mode 100644 index 000000000..ab198e94f --- /dev/null +++ b/critiquebrainz/ws/entity/test/views_test.py @@ -0,0 +1,89 @@ +from brainzutils import cache + +import critiquebrainz.db.license as db_license +import critiquebrainz.db.review as db_review +import critiquebrainz.db.users as db_users +from critiquebrainz.db.user import User +from critiquebrainz.ws.testing import WebServiceTestCase + + +class EntityViewsTestCase(WebServiceTestCase): + + def setUp(self): + super(EntityViewsTestCase, self).setUp() + + self.artist_id1 = "f59c5520-5f46-4d2c-b2c4-822eabf53419" + self.artist_id2 = "83d91898-7763-47d7-b03b-b92132375c47" + + self.user = User(db_users.get_or_create(1, "Tester", new_user_data={ + "display_name": "test user", + })) + self.another_user = User(db_users.get_or_create(2, "Hacker!", new_user_data={ + "display_name": "test hacker", + })) + self.license = db_license.create( + id="CC BY-SA 3.0", + full_name="Created so we can fill the form correctly.", + ) + self.review = dict( + entity_id=self.artist_id1, + entity_type='artist', + user_id=self.user.id, + text="Testing! This text should be on the page.", + rating=5, + is_draft=False, + license_id=self.license["id"], + ) + self.review2 = dict( + entity_id=self.artist_id2, + entity_type='artist', + user_id=self.user.id, + text="Testing! This text should be on the page.", + rating=5, + is_draft=False, + license_id=self.license["id"], + ) + + def create_dummy_review(self): + return db_review.create(**self.review) + + def create_dummy_review2(self): + return db_review.create(**self.review2) + + def test_artist_endpoint(self): + review = self.create_dummy_review() + response = self.client.get('/artist/f59c5520-5f46-4d2c-b2c4-822eabf53419') + + self.assert200(response) + self.assertIn(review['text'], response.json['top_reviews'][0]['text']) + + self.assertEqual(5, response.json['average_rating']) + self.assertEqual(1, response.json['reviews_count']) + + # Test for an artist which does not exist + response = self.client.get('/artist/f59c5520-5f46-4d2c-b2c4-822eabf53417') + self.assert404(response) + + def test_artist_user_reviews(self): + review = self.create_dummy_review() + response = self.client.get('/artist/f59c5520-5f46-4d2c-b2c4-822eabf53419?username=%s' % self.user.musicbrainz_username) + + self.assert200(response) + self.assertIn(review['text'], response.json['user_review']['text']) + + def test_user_cache_tracking(self): + track_key = cache.gen_key("entity_api", self.artist_id2, "artist",self.user.musicbrainz_username, "user_review") + + # Make sure the cache is empty + self.client.get('/artist/83d91898-7763-47d7-b03b-b92132375c47?username=%s' % self.user.musicbrainz_username) + cache_value = cache.get(track_key, namespace="Review") + self.assertEqual([], cache_value) + + review = self.create_dummy_review2() + + # Check if the cache is populated after the request + self.client.get('/artist/83d91898-7763-47d7-b03b-b92132375c47?username=%s' % self.user.musicbrainz_username) + cache_value = cache.get(track_key, namespace="Review") + self.assertTrue(cache_value is not None) + + self.assertIn(review['text'], cache_value['text']) From 4621102b0d2916dc0a2d0c86fa36de7d095fb974 Mon Sep 17 00:00:00 2001 From: anshg1214 Date: Sat, 16 Nov 2024 13:28:28 +0000 Subject: [PATCH 14/15] feat: Add Review Type parameter --- critiquebrainz/db/review.py | 9 ++++++--- critiquebrainz/ws/entity/views.py | 12 ++++++++---- 2 files changed, 14 insertions(+), 7 deletions(-) diff --git a/critiquebrainz/db/review.py b/critiquebrainz/db/review.py index e1d72722d..34d59e0a1 100644 --- a/critiquebrainz/db/review.py +++ b/critiquebrainz/db/review.py @@ -409,9 +409,12 @@ def invalidate_ws_entity_cache(entity_id, entity_type, user_id): cache.delete(cache_keys_for_no_entity_id_key, namespace=REVIEW_CACHE_NAMESPACE) # Invalidate top and latest reviews caches - for sort_type in ['popularity', 'published_on']: - cache_key = cache.gen_key(f'entity_api_{entity_type}', entity_id, f"{sort_type}_reviews") - cache.delete(cache_key, namespace=REVIEW_CACHE_NAMESPACE) + cache_keys_to_delete = [ + cache.gen_key(f'entity_api_{entity_type}', entity_id, review_type, f"{sort_type}_reviews") + for sort_type in ('popularity', 'published_on') + for review_type in ('review', 'rating', None) + ] + cache.delete_many(cache_keys_to_delete, namespace=REVIEW_CACHE_NAMESPACE) user = db_users.get_by_id(user_id) if user and 'musicbrainz_username' in user.keys() and user['musicbrainz_username']: diff --git a/critiquebrainz/ws/entity/views.py b/critiquebrainz/ws/entity/views.py index 9cb84912d..85199f829 100644 --- a/critiquebrainz/ws/entity/views.py +++ b/critiquebrainz/ws/entity/views.py @@ -45,10 +45,10 @@ def to_url(self, value): return str(value) -def _get_cached_reviews(entity_id: str, entity_type: str, sort_type: str, +def _get_cached_reviews(entity_id: str, entity_type: str, review_type: str, sort_type: str, cache_namespace: str = REVIEW_CACHE_NAMESPACE) -> tuple[list, int]: """Helper function to fetch and cache reviews.""" - cache_key = cache.gen_key(f"entity_api_{entity_type}", entity_id, f"{sort_type}_reviews") + cache_key = cache.gen_key(f"entity_api_{entity_type}", entity_id, review_type, f"{sort_type}_reviews") cached_result = cache.get(cache_key, cache_namespace) if cached_result: @@ -58,6 +58,7 @@ def _get_cached_reviews(entity_id: str, entity_type: str, sort_type: str, entity_id=entity_id, entity_type=entity_type, sort=sort_type, + review_type=review_type, limit=REVIEWS_LIMIT, offset=0, ) @@ -187,6 +188,7 @@ def entity_handler(entity_name: str, entity_id: str): :statuscode 404: series not found :query username: User's username **(optional)** + :query review_type: ``review`` or ``rating``. If set, only return reviews which have a text review, or a rating **(optional)** :resheader Content-Type: *application/json* """ @@ -196,6 +198,8 @@ def entity_handler(entity_name: str, entity_id: str): if not entity: raise NotFound(f"Can't find a {entity_name} with ID: {entity_id}") + review_type = Parser.string('uri', 'review_type', valid_values=['rating', 'review'], optional=True) + # Get user review if username provided user_review = [] username = Parser.string('uri', 'username', optional=True) @@ -217,8 +221,8 @@ def entity_handler(entity_name: str, entity_id: str): # Get ratings and reviews ratings_stats, average_rating = db_rating_stats.get_stats(entity_id, entity_type) - top_reviews, reviews_count = _get_cached_reviews(entity_id, entity_type, 'popularity') - latest_reviews, _ = _get_cached_reviews(entity_id, entity_type, 'published_on') + top_reviews, reviews_count = _get_cached_reviews(entity_id, entity_type, review_type, 'popularity') + latest_reviews, _ = _get_cached_reviews(entity_id, entity_type, review_type, 'published_on') result = { "entity": entity, From c95f5660db4d83560d11f9d2c0ec3b0a6191388a Mon Sep 17 00:00:00 2001 From: anshg1214 Date: Sat, 16 Nov 2024 13:40:18 +0000 Subject: [PATCH 15/15] feat: Cache Rating Stats too --- critiquebrainz/ws/entity/views.py | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/critiquebrainz/ws/entity/views.py b/critiquebrainz/ws/entity/views.py index 85199f829..9f6101a02 100644 --- a/critiquebrainz/ws/entity/views.py +++ b/critiquebrainz/ws/entity/views.py @@ -69,6 +69,22 @@ def _get_cached_reviews(entity_id: str, entity_type: str, review_type: str, sort return reviews, count +def _get_cached_rating_stats(entity_id: str, entity_type: str, + cache_namespace: str = REVIEW_CACHE_NAMESPACE) -> tuple[dict, float]: + """Helper function to fetch and cache rating statistics.""" + cache_key = cache.gen_key(f"entity_api_{entity_type}", entity_id, "rating_stats") + cached_result = cache.get(cache_key, cache_namespace) + + if cached_result: + return cached_result + + ratings_stats, average_rating = db_rating_stats.get_stats(entity_id, entity_type) + + cache.set(cache_key, (ratings_stats, average_rating), + expirein=REVIEW_CACHE_TIMEOUT, namespace=cache_namespace) + return ratings_stats, average_rating + + @entity_bp.route('//', methods=['GET', 'OPTIONS']) @crossdomain(headers="Authorization, Content-Type") def entity_handler(entity_name: str, entity_id: str): @@ -220,7 +236,7 @@ def entity_handler(entity_name: str, entity_id: str): expirein=REVIEW_CACHE_TIMEOUT, namespace=REVIEW_CACHE_NAMESPACE) # Get ratings and reviews - ratings_stats, average_rating = db_rating_stats.get_stats(entity_id, entity_type) + ratings_stats, average_rating = _get_cached_rating_stats(entity_id, entity_type) top_reviews, reviews_count = _get_cached_reviews(entity_id, entity_type, review_type, 'popularity') latest_reviews, _ = _get_cached_reviews(entity_id, entity_type, review_type, 'published_on')