diff --git a/src/lambdas/api/episode_by_collection_item/__init__.py b/src/lambdas/api/episode_by_collection_item/__init__.py index babf24f5..50d2284c 100644 --- a/src/lambdas/api/episode_by_collection_item/__init__.py +++ b/src/lambdas/api/episode_by_collection_item/__init__.py @@ -160,6 +160,14 @@ def _post_episode(username, collection_name, item_id, body, token): body ) + watch_history_db.change_watched_eps( + username, + collection_name, + item_id, + 1, + special=res["is_special"] + ) + if "dates_watched" not in body: return { "statusCode": 200, diff --git a/src/lambdas/api/episode_by_id/__init__.py b/src/lambdas/api/episode_by_id/__init__.py index c400e52a..bf567063 100644 --- a/src/lambdas/api/episode_by_id/__init__.py +++ b/src/lambdas/api/episode_by_id/__init__.py @@ -39,9 +39,10 @@ def handle(event, context): elif method == "PUT": body = event.get("body") return _put_episode(username, collection_name, item_id, episode_id, - body, auth_header) + body, auth_header) elif method == "DELETE": - return _delete_episode(username, collection_name, episode_id) + return _delete_episode(username, collection_name, item_id, episode_id, + auth_header) def _get_episode(username, collection_name, item_id, episode_id, token): @@ -125,6 +126,27 @@ def _put_episode(username, collection_name, item_id, episode_id, body, token): return {"statusCode": 204} -def _delete_episode(username, collection_name, episode_id): +def _delete_episode(username, collection_name, item_id, episode_id, token): + res = None + try: + if collection_name == "anime": + res = anime_api.get_episode(item_id, episode_id, token) + elif collection_name == "show": + res = shows_api.get_episode(item_id, episode_id) + except utils.HttpError as e: + err_msg = f"Could not get {collection_name} episode for " \ + f"item: {item_id} and episode_id: {episode_id}" + log.error(f"{err_msg}. Error: {str(e)}") + return {"statusCode": e.status_code, + "body": json.dumps({"message": err_msg}), "error": str(e)} + episodes_db.delete_episode(username, collection_name, episode_id) + + watch_history_db.change_watched_eps( + username, + collection_name, + item_id, + -1, + special=res["is_special"] + ) return {"statusCode": 204} diff --git a/src/lambdas/subscribers/show_updates/__init__.py b/src/lambdas/subscribers/show_updates/__init__.py index 9fc32bcf..3a8afcb7 100644 --- a/src/lambdas/subscribers/show_updates/__init__.py +++ b/src/lambdas/subscribers/show_updates/__init__.py @@ -16,11 +16,10 @@ def handle(event, context): for item in items: print(f"Updating item: {item}") - if "ep_progress" not in item: - item["ep_progress"] = 0 + if "watched_eps" not in item: item["watched_eps"] = 0 - item["special_progress"] = 0 - item["watched_special"] = 0 + if "watched_specials" not in item: + item["watched_specials"] = 0 item["ep_count"] = show["ep_count"] if item["ep_count"] == 0: @@ -33,7 +32,7 @@ def handle(event, context): if item["special_count"] == 0: special_progress = 0 else: - special_progress = item["watched_special"] / item["special_count"] + special_progress = item["watched_specials"] / item["special_count"] item["special_progress"] = str(round(special_progress * 100, 2)) watch_history_db.put_item(item) diff --git a/src/layers/databases/python/watch_history_db.py b/src/layers/databases/python/watch_history_db.py index 354957a5..0a48c638 100644 --- a/src/layers/databases/python/watch_history_db.py +++ b/src/layers/databases/python/watch_history_db.py @@ -62,7 +62,8 @@ def add_item(username, collection_name, item_id, data=None): except NotFoundError: data["created_at"] = datetime.now().strftime("%Y-%m-%d %H:%M:%S") - update_item(username, collection_name, item_id, data, clean_whitelist=["deleted_at"]) + update_item(username, collection_name, item_id, data, + clean_whitelist=["deleted_at"]) def delete_item(username, collection_name, item_id): @@ -128,6 +129,35 @@ def update_item(username, collection_name, item_id, data, ) +def change_watched_eps(username, collection_name, item_id, change, special=False): + field_name = "ep" + if special: + field_name = "special" + + item = get_item(username, collection_name, item_id) + if item[f"{field_name}_count"] == 0: + ep_progress = 0 + else: + ep_progress = (item[f"watched_{field_name}s"] + (change)) / item[f"{field_name}_count"] + ep_progress = str(round(ep_progress * 100, 2)) + + _get_table().update_item( + Key={ + "username": username, + "item_id": item_id, + }, + UpdateExpression="SET #w=#w+:i, #p=:p", + ExpressionAttributeNames={ + "#w": f"watched_{field_name}s", + "#e": f"{field_name}_progress", + }, + ExpressionAttributeValues={ + ":p": ep_progress, + ":i": change, + } + ) + + def get_watch_history(username, collection_name=None, index_name=None, status_filter=None): paginator = _get_client().get_paginator('query') diff --git a/test/unittest/test_episode_by_collection.py b/test/unittest/test_episode_by_collection.py index 6e1b3f6e..3f74a62a 100644 --- a/test/unittest/test_episode_by_collection.py +++ b/test/unittest/test_episode_by_collection.py @@ -203,10 +203,12 @@ class TestPost: @patch("api.episode_by_collection_item.watch_history_db.get_item") @patch("api.episode_by_collection_item.watch_history_db.update_item") @patch("api.episode_by_collection_item.episodes_db.update_episode") - def test_success_anime(self, mocked_update_episode, mocked_update_item, mocked_get_item, mocked_post_episode, mocked_post): + @patch("api.episode_by_collection_item.watch_history_db.change_watched_eps") + def test_success_anime(self, mocked_change_watched_eps, mocked_update_episode, mocked_update_item, mocked_get_item, mocked_post_episode, mocked_post): mocked_post.return_value = True mocked_post_episode.return_value = { - "id": "123" + "id": "123", + "is_special": False, } mocked_get_item.return_value = { "latest_watch_date": "2030-01-01 10:00:00" @@ -223,10 +225,12 @@ def test_success_anime(self, mocked_update_episode, mocked_update_item, mocked_g @patch("api.episode_by_collection_item.watch_history_db.get_item") @patch("api.episode_by_collection_item.watch_history_db.update_item") @patch("api.episode_by_collection_item.episodes_db.update_episode") - def test_success_show(self, mocked_update_episode, mocked_update_item, mocked_get_item, mocked_post_episode, mocked_post): + @patch("api.episode_by_collection_item.watch_history_db.change_watched_eps") + def test_success_show(self, mocked_change_watched_eps, mocked_update_episode, mocked_update_item, mocked_get_item, mocked_post_episode, mocked_post): mocked_post.return_value = True mocked_post_episode.return_value = { - "id": "123" + "id": "123", + "is_special": False, } mocked_get_item.return_value = { "latest_watch_date": "2030-01-01 10:00:00" diff --git a/test/unittest/test_episode_by_id.py b/test/unittest/test_episode_by_id.py index b53a2874..d3955973 100644 --- a/test/unittest/test_episode_by_id.py +++ b/test/unittest/test_episode_by_id.py @@ -99,7 +99,9 @@ class TestDelete: } @patch("api.episode_by_id.episodes_db.delete_episode") - def test_success(self, mocked_delete): + @patch("api.episode_by_id.anime_api.get_episode") + @patch("api.episode_by_id.watch_history_db.change_watched_eps") + def test_success(self, mocked_change_watched_eps, mocked_anime_api, mocked_delete): mocked_delete.return_value = True ret = handle(self.event, None) diff --git a/test/unittest/test_watch_history_db.py b/test/unittest/test_watch_history_db.py index f2b82ff3..83a89c5b 100644 --- a/test/unittest/test_watch_history_db.py +++ b/test/unittest/test_watch_history_db.py @@ -40,7 +40,7 @@ def test_get_watch_history(mocked_watch_history_db): "KeyConditionExpression": "username = :username", "ExpressionAttributeValues": {":username": {"S": "TEST_USERNAME"}}, "ScanIndexForward": False, - 'FilterExpression': 'attribute_not_exists(deleted_at)', + "FilterExpression": "attribute_not_exists(deleted_at)", } assert res == mock_func.exp_res @@ -78,8 +78,8 @@ def test_get_watch_history_by_collection_and_index(mocked_watch_history_db): m.paginate = mock_func.f res = mocked_watch_history_db.get_watch_history(TEST_USERNAME, - collection_name="ANIME", - index_name="test_index") + collection_name="ANIME", + index_name="test_index") assert mock_func.update_values == { "ExpressionAttributeValues": { @@ -103,24 +103,24 @@ def test_add_item(mocked_watch_history_db): mocked_watch_history_db.add_item(TEST_USERNAME, "MOVIE", "123123") assert mock_func.update_values == { - 'ExpressionAttributeNames': { - '#collection_name': 'collection_name', - '#created_at': 'created_at', - '#deleted_at': 'deleted_at', - '#latest_watch_date': 'latest_watch_date', - '#updated_at': 'updated_at', + "ExpressionAttributeNames": { + "#collection_name": "collection_name", + "#created_at": "created_at", + "#deleted_at": "deleted_at", + "#latest_watch_date": "latest_watch_date", + "#updated_at": "updated_at", }, - 'ExpressionAttributeValues': { - ':collection_name': 'MOVIE', + "ExpressionAttributeValues": { + ":collection_name": "MOVIE", ":created_at": datetime.now().strftime("%Y-%m-%d %H:%M:%S"), - ':latest_watch_date': '0', + ":latest_watch_date": "0", ":updated_at": datetime.now().strftime("%Y-%m-%d %H:%M:%S") }, - 'Key': { - 'username': TEST_USERNAME, - 'item_id': '123123'}, - 'UpdateExpression': 'SET #latest_watch_date=:latest_watch_date,#created_at=:created_at,#collection_name=:collection_name,#updated_at=:updated_at ' - 'REMOVE #deleted_at' + "Key": { + "username": TEST_USERNAME, + "item_id": "123123"}, + "UpdateExpression": "SET #latest_watch_date=:latest_watch_date,#created_at=:created_at,#collection_name=:collection_name,#updated_at=:updated_at " + "REMOVE #deleted_at" } @@ -328,3 +328,72 @@ def test_get_item_not_found(mocked_watch_history_db): with pytest.raises(mocked_watch_history_db.NotFoundError): mocked_watch_history_db.get_item(TEST_USERNAME, "MOVIE", "123123") + + +def test_change_watched_eps(mocked_watch_history_db): + mock_func = MockFunc() + mocked_watch_history_db.table.update_item = mock_func.f + mocked_watch_history_db.table.query.return_value = { + "Items": [ + { + "ep_count": 10, + "watched_eps": 1, + } + ] + } + + mocked_watch_history_db.change_watched_eps(TEST_USERNAME, "MOVIE", "123123", + 1) + + assert mock_func.update_values == { + 'ExpressionAttributeNames': {'#e': 'ep_progress', '#w': 'watched_eps'}, + 'ExpressionAttributeValues': {':i': 1, ':p': '20.0'}, + 'Key': {'item_id': '123123', 'username': 'TEST_USERNAME'}, + 'UpdateExpression': 'SET #w=#w+:i, #p=:p' + } + + +def test_change_watched_eps_removal(mocked_watch_history_db): + mock_func = MockFunc() + mocked_watch_history_db.table.update_item = mock_func.f + mocked_watch_history_db.table.query.return_value = { + "Items": [ + { + "ep_count": 10, + "watched_eps": 1, + } + ] + } + + mocked_watch_history_db.change_watched_eps(TEST_USERNAME, "MOVIE", "123123", + -1) + + assert mock_func.update_values == { + 'ExpressionAttributeNames': {'#e': 'ep_progress', '#w': 'watched_eps'}, + 'ExpressionAttributeValues': {':i': -1, ':p': '0.0'}, + 'Key': {'item_id': '123123', 'username': 'TEST_USERNAME'}, + 'UpdateExpression': 'SET #w=#w+:i, #p=:p' + } + + +def test_change_watched_specials(mocked_watch_history_db): + mock_func = MockFunc() + mocked_watch_history_db.table.update_item = mock_func.f + mocked_watch_history_db.table.query.return_value = { + "Items": [ + { + "special_count": 20, + "watched_specials": 2, + } + ] + } + + mocked_watch_history_db.change_watched_eps(TEST_USERNAME, "MOVIE", "123123", + 1, special=True) + + assert mock_func.update_values == { + 'ExpressionAttributeNames': {'#e': 'special_progress', '#w': 'watched_specials'}, + 'ExpressionAttributeValues': {':i': 1, ':p': '15.0'}, + 'Key': {'item_id': '123123', 'username': 'TEST_USERNAME'}, + 'UpdateExpression': 'SET #w=#w+:i, #p=:p' + }