From 08393a48dcf8276803db286b73e2b7bfa09a5848 Mon Sep 17 00:00:00 2001 From: Donchess1 Date: Sun, 2 Mar 2025 13:07:19 +0100 Subject: [PATCH 1/3] soft delete retested getblog adjusted with filter --- api/utils/pagination.py | 7 ++++++- api/v1/routes/blog.py | 2 +- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/api/utils/pagination.py b/api/utils/pagination.py index 0fab89c9b..b40eb0a31 100644 --- a/api/utils/pagination.py +++ b/api/utils/pagination.py @@ -69,8 +69,13 @@ def paginated_response( if filters and join is None: # Apply filters for attr, value in filters.items(): + if value is not None: - query = query.filter(getattr(model, attr).like(f"%{value}%")) + column = getattr(model, attr) + if isinstance(value, bool): + query = query.filter(column == value) + else: + query = query.filter(column.like(f"%{value}%")) elif filters and join is not None: # Apply filters diff --git a/api/v1/routes/blog.py b/api/v1/routes/blog.py index e8bd9bb09..20d72bf8e 100644 --- a/api/v1/routes/blog.py +++ b/api/v1/routes/blog.py @@ -224,7 +224,7 @@ async def archive_blog_post( """Endpoint to archive/soft-delete a blog post""" blog_service = BlogService(db=db) - blog_post = blog_service.fetch(blog_id=id) + blog_post = blog_service.fetch(blog_id=blog_id) if not blog_post: raise HTTPException(status_code=404, detail="Post not found") #check if admin/ authorized user From ea7d5a611d87268b88638916b2d3dba59eac654c Mon Sep 17 00:00:00 2001 From: Donchess1 Date: Sun, 2 Mar 2025 13:37:03 +0100 Subject: [PATCH 2/3] all soft_delete cases fixed --- et --hard 08393a48 | 225 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 225 insertions(+) create mode 100644 et --hard 08393a48 diff --git a/et --hard 08393a48 b/et --hard 08393a48 new file mode 100644 index 000000000..f74dbd088 --- /dev/null +++ b/et --hard 08393a48 @@ -0,0 +1,225 @@ +238a8609 (HEAD -> feat/soft_delete, origin/feat/soft_delete) Merge branch 'feat/soft_delete' of https://github.com/Donchess1/hng_boilerplate_python_fastapi_web into feat/soft_delete +08393a48 soft delete retested getblog adjusted with filter +2a48bec3 Merge branch 'dev' into feat/soft_delete +e15acc8e Merge pull request #1139 from Faith-K-commits/feature/add-database-indexing +8f91c67b Merge branch 'dev' into feat/soft_delete +2721e828 Merge branch 'dev' into feature/add-database-indexing +d21d556e Merge pull request #1164 from 0xNuru/refactor/urls +cb8f2cc0 Merge branch 'dev' into feature/add-database-indexing +125d8731 Merge branch 'dev' into refactor/urls +3ad1b4af Merge pull request #1178 from jeffmaine240/feat/email_verification +a93cb0d8 Merge branch 'dev' into refactor/urls +7670fe5c Merge branch 'dev' into feat/email_verification +d04685e9 Merge pull request #1141 from DavidIfebueme/feat/delete-user-endpoint +136a42b2 Merge branch 'dev' into feat/delete-user-endpoint +9c295ff4 Merge pull request #1155 from EmmyAnieDev/feature/login-email-notification +b24370ec Merge branch 'dev' into feat/email_verification +c46fa4ea Merge branch 'dev' into feat/delete-user-endpoint +15ebc91d Merge branch 'dev' into feature/login-email-notification +f36b5127 Merge pull request #1147 from codeWithGodstime/feat/filter_faq_by_category +d242cd15 Merge branch 'dev' into feat/filter_faq_by_category +e07aa133 Merge branch 'dev' into feature/login-email-notification +b27e3547 Merge pull request #1192 from eniiku/feat/update_squeezepage_endpoint +adf721d4 Merge branch 'dev' into feat/update_squeezepage_endpoint +3d058e9f Merge branch 'dev' into feat/email_verification +4ec6e3fc Merge branch 'dev' into feature/login-email-notification +3407c894 Merge pull request #1190 from ObiFaith/fix/send-notification +9da99a41 Merge branch 'dev' into feat/update_squeezepage_endpoint +e204407b Merge branch 'dev' into feat/email_verification +b3b78741 Merge branch 'dev' into feature/login-email-notification +e7dccd6b Merge branch 'dev' into fix/send-notification +4aaa3327 Merge branch 'dev' into refactor/urls +07d2b5df Merge pull request #1185 from Staneering/chore/blog-service-refactor +a1084320 chore: change all urls that are using settings.base urls to settings.anchor_base_url +264a4382 Merge branch 'dev' into feature/login-email-notification +65ce298c Merge branch 'dev' into chore/blog-service-refactor +87785eb1 Merge branch 'dev' into feat/delete-user-endpoint +1d243050 Merge branch 'dev' into fix/send-notification +3229c4f5 Merge pull request #1174 from LmOpe/feat/reply-to-blog-comment +b9eec093 Merge branch 'dev' into feat/delete-user-endpoint +7a2d3b0d Merge branch 'dev' into fix/send-notification +e25d2453 chore: change all urls that are using settings.base urls to settings.anchor_base_url +0d4dc21b Merge branch 'dev' into feat/email_verification +2875dced Merge branch 'dev' into feat/reply-to-blog-comment +3ea9c534 Merge branch 'dev' into feature/login-email-notification +450f4c59 Merge pull request #1168 from Lamido-stack/Total-Blog-Reactions +b5dcab88 fix(auth): resolve merge conflict in authentication tests +726581ed feat(auth): email verification test added +2af3491b Merge branch 'dev' into Total-Blog-Reactions +50e8ebb0 feat(auth): email verification test added +0df52c09 test: add authentication checks for send_notification +2ec8a683 refactor: update send_notification service to attach user_id +f74140e3 fix: enforce authentication on send_notification endpoint +48010aae Merge branch 'dev' into chore/blog-service-refactor +e9f6b117 Merge branch 'dev' into feat/email_verification +596b3647 Merge branch 'dev' into feat/delete-user-endpoint +f61f847c Merge branch 'dev' into feat/reply-to-blog-comment +e0ac8b98 Merge pull request #1160 from PreciousEzeigbo/backend +845e1d75 chore(blog): move fetch_and_increment_view to BlogService +16149d71 Merge branch 'dev' into backend +3e2baab9 fix: fix UTC import error in python 3.10 version to use datetime timezone module +d8fabe26 test(blog): add test cases for reply blog post comments endpoint +2ea02d0a feat(blog): add post endpoint for replying to blog post comments +359ec16b Merge branch 'dev' into refactor/urls +820ab9e6 Merge branch 'dev' into feature/add-database-indexing +2a575429 Merge pull request #1158 from Ayobamidele/chore/readme_update +4ed4ef21 Merge branch 'dev' into refactor/urls +de8b3d2a Merge branch 'dev' into chore/readme_update +307ceac9 fix(email): resolve merge conflict in email templates setup +5fb87068 fix(models): adjust indentation in Blog model +d6109fd5 Merge pull request #1140 from 0xNuru/fix/login-error-handling +ef04fb7b Merge branch 'dev' into feat/delete-user-endpoint +abc8255a fix(models): correct indentation in Blog model +46122f32 Merge branch 'dev' into fix/login-error-handling +a4f4267b Merge pull request #1125 from johnafariogun/feat/telex-integration-for-enhanced-logging +72663867 Merge branch 'dev' into fix/login-error-handling +5fb8df6b Merge branch 'dev' into feature/add-database-indexing +b1df4d0b Merge branch 'dev' into feat/delete-user-endpoint +1cbcec91 Merge branch 'dev' into feat/telex-integration-for-enhanced-logging +01b25020 Merge branch 'dev' into feature/login-email-notification +c1f42258 Merge branch 'dev' into chore/blog-service-refactor +58037817 chore: removed telex channel webhook +0b109a62 Merge pull request #1170 from Joe-encodes/chore/cleanup-blogservice +a2160fcc Merge branch 'dev' into feature/add-database-indexing +1bddd7f6 Merge branch 'dev' into feat/delete-user-endpoint +bf1c7864 Merge branch 'dev' into feature/login-email-notification +61ce8be9 chore: added telex channel webhook +81a12885 Merge branch 'dev' into chore/cleanup-blogservice +5c7800fe Merge branch 'dev' into feat/telex-integration-for-enhanced-logging +9027b260 Merge pull request #1149 from Magnus984/bookmark-feature +d8f6e7c0 feat(tests): create test cases against updating squeeze page impl +d1f71112 feat(routes): create route to handle updating squeeze page details +2f6ccaa0 feat(services): create business logic to handle updating squeeze page details +9b005807 feat(schemas): create schema for updating squeeze page +74a4eadd Merge branch 'dev' into bookmark-feature +4e3a1de1 Merge branch 'dev' into chore/cleanup-blogservice +f7ad467a Merge branch 'dev' into feat/delete-user-endpoint +23cc2edc Merge branch 'dev' into feat/telex-integration-for-enhanced-logging +9634f02c Merge branch 'dev' into feat/email_verification +2793610c Merge pull request #1095 from Afeh/feat/add-wishlist +da48bfe7 Merge branch 'dev' into chore/blog-service-refactor +885f03bc Merge branch 'dev' into bookmark-feature +81f6441e Merge branch 'dev' into feat/add-wishlist +9deaf625 Merge branch 'dev' into feat/telex-integration-for-enhanced-logging +97d1401f Merge branch 'dev' into feat/delete-user-endpoint +6cd8e1c7 Merge pull request #1105 from Tonyjr7/feat/confirmpassword +56e7f2de Merge branch 'dev' into feat/confirmpassword +312c4ec3 Merge branch 'dev' into feat/telex-integration-for-enhanced-logging +57ee1aef Merge branch 'dev' into feat/add-wishlist +d696e57f Merge branch 'dev' into feat/delete-user-endpoint +72bb4926 Merge pull request #1083 from Marlinekhavele/feat/remove_member_from_organization +ed7f371e Merge branch 'dev' into feat/telex-integration-for-enhanced-logging +d79dddfe Merge branch 'dev' into chore/cleanup-blogservice +2233505d Merge branch 'dev' into backend +ae62790e Merge branch 'dev' into feat/remove_member_from_organization +15ea4f15 Merge branch 'dev' into feat/delete-user-endpoint +e05956dc Merge pull request #1097 from simplicityf/feat/delete_blog_like +ad5746a5 Merge branch 'dev' into feat/delete_blog_like +d23fe94a Merge branch 'dev' into feat/confirmpassword +4a7caf96 Merge branch 'dev' into chore/cleanup-blogservice +2850c3c4 resolved conflicts +c826e2a1 resolved conflicts +7f6b1525 Merge branch 'dev' into feat/telex-integration-for-enhanced-logging +898d18b9 resolved conflicts +c64a0b41 Remove .DS_Store files and add them to .gitignore +d5e2ea1c Merge branch 'dev' into feat/delete-user-endpoint +4931e718 resolved conflicts +6acbf90d Merge branch 'dev' into feat/remove_member_from_organization +73645573 resolved conflicts +17ca6a7f chore(blog): implement base interaction service for likes/dislikes +959d59c9 resolved conflicts +54dcdf74 Merge pull request #1066 from CynthiaWahome/feature/blog-view-count +27dc54cc resolved conflicts +084efcb1 resolved conflicts +386e2460 resolved conflicts +2309a237 resolved conflicts +092725f0 Merge branch 'dev' into feature/login-email-notification +0d3c59f2 resolved conflicts +49103061 resolved conflicts +83a1c02e fix(auth): resolve merge conflicts with dev branch in login tests +de7934ab resolved conflicts +fe96d758 Merge branch 'dev' into chore/cleanup-blogservice +69ee0069 fix: update test to expect 200 status code for user deletion +c14355b8 Merge branch 'dev' into feat/delete-user-endpoint +99674be4 Merge branch 'dev' into feat/telex-integration-for-enhanced-logging +2d5d0259 Merge branch 'dev' into feature/blog-view-count +358704f5 refactor: Enhance blog view counter with improved error handling +e86a48db Merge branch 'dev' into feat/remove_member_from_organization +7ca8f7a8 Merge pull request #1162 from osadeleke/feat/paginate_newsletter_subscribers +5827ed88 feat(auth): email verification added +15bf1a78 Deleted alembic/.DS_Store file +032115cd Deleted tests/.DS_Store file +bc6903d8 Deleted an api\.DS_Store file +c5ee4e86 Deleted a.DS_Store file +a1f46d88 feat(auth): email verification added +23701153 feat(auth): email verification added +c2f5f023 feat(auth): email verification added +2fb0e89a fix: added status code in response json as per new response standards +98aebfb6 Merge branch 'dev' into chore/cleanup-blogservice +29963657 chore: Removed outdated setup instructions to follow current standards +5113c0df fix: removed success field from response json as per new response standards +a3c2f23d fix: Updated files +d20267d5 fix: implemented fix for missing , in test_delelte_user.py +6345911b fix: Updated files +3996087b Merge branch 'chore/cleanup-blogservice' of https://github.com/Joe-encodes/hng_boilerplate_python_fastapi_web into chore/cleanup-blogservice +bab12300 fix base: Added .DS_Store to .gitignore +b72cb7ca Merge remote-tracking branch 'refs/remotes/origin/chore/readme_update' into chore/readme_update +cdf5351d Delete .DS_Store +f4bb1d20 chore: refactored BlogService methods to use instance db and improve authentication checks +93b89c3b chore: refactored BlogService methods to use instance db and improve authentication checks +29fb6b16 chore: Removed outdated setup instructions to follow current standards +5459f36d Merge branch 'dev' into feat/add-wishlist +1796f8d3 fix: Delete alembic/versions/a860d2b54034_added_wishlist_model.py +99e25f8f fix: Delete alembic/versions/66d2371b0b34_added_wishlist_model.py +73d370c5 fix: Delete alembic/versions/51c061a73b85_initial_migration.py +5c032033 Update test_delete_user.py +38d1324f Merge branch 'dev' into Total-Blog-Reactions +49ac4f6d Merge branch 'dev' into feature/blog-view-count +6239b229 fix: remove unneccessary commented code +fb7fc35d Merge branch 'dev' into feat/delete-user-endpoint +028cdb4b Merge branch 'dev' into feat/paginate_newsletter_subscribers +f42cf35e Merge branch 'feat/remove_member_from_organization' of github.com:Marlinekhavele/hng_boilerplate_python_fastapi_web into feat/remove_member_from_organization +5ccc4884 fix: forgot to add status code on the endpoint causing datamismatch with the test +0835bf86 refactor: move hardcoded URLs to configuration settings +367ab2fa Merge branch 'dev' into fix/login-error-handling +5a9e83e5 feat : Retrieves the Total Number of Likes and Dislikes for a Specific Blog Post +43ed97b7 Merge branch 'dev' into feat/remove_member_from_organization +283303b3 fix: fixed CI/CD fail by removing alembic migrations +633b36de chore: pushing alembic migration files +bba086d0 test: add test for subscriber retrieval on nil subs and with some subscribers +19ee81d6 Merge branch 'dev' into feat/telex-integration-for-enhanced-logging +fafccdee refactor(tests): removed redundant comments +9317fc3e Merge branch 'dev' into chore/readme_update +207a110b Merge pull request #1082 from Ayobamidele/feat/response +534182dd fix: standardize user deletion responses and add proper error handling +d4641f62 ci: Removed branch restriction for CI testing +21c63761 chore: Removed outdated setup instructions to follow current standards +68d7a514 fix(auth): handle login notification API errors and improve test coverage +5ec934dc Merge branch 'dev' into feature/add-database-indexing +978e29ef refactor(tests): replace with-patch syntax with decorator for mocking +ea2a5e48 Merge branch 'dev' into feat/response +ee318845 fix: updated tests to folow changes +59a3f0b3 fix: updated test code to remove phantom assert +5167e0fc fix: updated test code to remove phantom assert +5b98345d test: added mock webhook properly +1c9e33c0 fix: updated tests to folow changes +5ec90963 refactor(tests): simplify database fixture documentation and cleanup +ac35e5fe fix: removed prints from tests +a595466b Merge branch 'feat/add-wishlist' of https://github.com/Afeh/hng_boilerplate_python_fastapi_web into feat/add-wishlist +0feb59c8 fix: added logic in wishlist routes to check if a user ia authenticated before they can add a product to wishlist, added authentication headers to tests as a result, wrote two new tests; test to check if the user adding the wish list is unauthorized and test to check for unprocessable entity when a user sends an empty request. +c1bf55c5 fix: updated tests to folow changes +14ea25d2 feat: modified the query parameter to all filtering faq by category and wrote tests for that functionality +1b613e12 feat: add pagination to newsletter subscribers fetch +001ebf55 feat(models): add indexing to Blog model +7ceaa997 test: fix assertion errors and ensure correct mocking +1c5797ed test: added unit tests to test endpoint +2a63bfb1 Merge branch 'feat/remove_member_from_organization' of github.com:Marlinekhavele/hng_boilerplate_python_fastapi_web into feat/remove_member_from_organization +38a8d858 fix: rewrite tests for test_delete_user_from_organisation +8ab36038 chore(env): add MAIL_USERNAME and MAIL_PASSWORD to .env.sample +bec309ac rename blog_id, refactor error 403 +04131699 fix: extract status code dynamically and handle missing webhook URL +e72a91a8 rename blog_id, refactor error 403 +08032bd4 test: add test for Swagger UI auth form handling +20d170ff tests: added unittests for the delete user endpoint +c1c79f4d revert(test): restore original test setup for user login +e081299e feat(models): add indexin \ No newline at end of file From 0cff91a8921b06889e3623cf9382cd3c029369d5 Mon Sep 17 00:00:00 2001 From: Donchess1 Date: Sun, 2 Mar 2025 18:34:36 +0100 Subject: [PATCH 3/3] implement blog restore after soft-delete --- api/utils/pagination.py | 7 +--- api/v1/routes/blog.py | 46 +++++++++++++++++++- tests/v1/blog/test_restore_blog.py | 67 ++++++++++++++++++++++++++++++ 3 files changed, 114 insertions(+), 6 deletions(-) create mode 100644 tests/v1/blog/test_restore_blog.py diff --git a/api/utils/pagination.py b/api/utils/pagination.py index b40eb0a31..c6213aef5 100644 --- a/api/utils/pagination.py +++ b/api/utils/pagination.py @@ -89,10 +89,7 @@ def paginated_response( results = jsonable_encoder(query.offset(skip).limit(limit).all()) total_pages = int(total / limit) + (total % limit > 0) - return success_response( - status_code=200, - message="Successfully fetched items", - data={ + return { "pages": total_pages, "total": total, "skip": skip, @@ -107,4 +104,4 @@ def paginated_response( } ) } - ) + diff --git a/api/v1/routes/blog.py b/api/v1/routes/blog.py index 20d72bf8e..cf8a27066 100644 --- a/api/v1/routes/blog.py +++ b/api/v1/routes/blog.py @@ -52,7 +52,20 @@ def create_blog( def get_all_blogs(db: Session = Depends(get_db), limit: int = 10, skip: int = 0): """Endpoint to get all blogs""" - return paginated_response( + blog = paginated_response( + db=db, + model=Blog, + limit=limit, + skip=skip, + ) + + return success_response(200, message="Successfully fetched all blogs", data=blog) + +@blog.get("/active", response_model=success_response) +def get_all_active_blogs(db: Session = Depends(get_db), limit: int = 10, skip: int = 0): + """Endpoint to get all active blogs""" + + blog = paginated_response( db=db, model=Blog, limit=limit, @@ -60,6 +73,7 @@ def get_all_blogs(db: Session = Depends(get_db), limit: int = 10, skip: int = 0) filters={"is_deleted": False} #filter out soft-deleted blogs ) + return success_response(200, message="Successfully fetched active blogs", data=blog) @blog.get("/{id}", response_model=BlogPostResponse) def get_blog_by_id(id: str, db: Session = Depends(get_db)): @@ -111,6 +125,8 @@ async def update_blog( ) + + @blog.post("/{blog_id}/like", response_model=BlogLikeDislikeResponse) def like_blog_post( blog_id: str, @@ -241,6 +257,34 @@ async def archive_blog_post( data=jsonable_encoder(blog_post), ) +@blog.put("/{blog_id}/restore") +async def restore_blog_post( + blog_id: str, + db: Session = Depends(get_db), + current_user: User = Depends(user_service.get_current_super_admin), +): + + """Endpoint to restore a soft-deleted blog post""" + + blog_service = BlogService(db=db) + blog_post = blog_service.fetch(blog_id=blog_id) + if not blog_post: + raise HTTPException(status_code=404, detail="Post not found") + #check if admin/ authorized user + if not (blog_post.author_id != current_user.id or current_user.is_superadmin): + raise HTTPException(status_code=403, detail="You don't have permission to perform this action") + if not blog_post.is_deleted: + raise HTTPException(status_code=400, detail="Blog post is already active") + blog_post.is_deleted = False + db.commit() + db.refresh(blog_post) + + return success_response( + message="Blog post restored successfully!", + status_code=200, + data=jsonable_encoder(blog_post), + ) + # Post a comment to a blog diff --git a/tests/v1/blog/test_restore_blog.py b/tests/v1/blog/test_restore_blog.py new file mode 100644 index 000000000..8aeb363f6 --- /dev/null +++ b/tests/v1/blog/test_restore_blog.py @@ -0,0 +1,67 @@ +from unittest.mock import MagicMock, patch + +import pytest +from fastapi.testclient import TestClient +from sqlalchemy.orm import Session +from uuid_extensions import uuid7 + +from api.db.database import get_db +from api.v1.services.user import user_service +from api.v1.models import User +from api.v1.models.blog import Blog +from main import app + + +def mock_get_db(): + db_session = MagicMock() + yield db_session + + +def mock_get_current_super_admin(): + return User(id="1", is_superadmin=True) + +@pytest.fixture +def db_session_mock(): + db_session = MagicMock(spec=Session) + return db_session + +@pytest.fixture +def client(db_session_mock): + app.dependency_overrides[get_db] = lambda: db_session_mock + client = TestClient(app) + yield client + + +def test_restore_blog_success(client, db_session_mock): + '''Test for success in blog archiving''' + + app.dependency_overrides[get_db] = lambda: db_session_mock + app.dependency_overrides[user_service.get_current_super_admin] = lambda: mock_get_current_super_admin() + blog_id = uuid7() + mock_blog = Blog(id=blog_id, title="Test Blog", + content="Test Content", is_deleted=True) + + db_session_mock.query(Blog).filter(Blog.id==blog_id).first.return_value = mock_blog + + response = client.put(f"/api/v1/blogs/{mock_blog.id}/restore", headers={'Authorization': 'Bearer token'}) + + assert response.status_code == 200 + assert response.json()["message"] == "Blog post restored successfully!" + + +def test_restore_blog_not_found(client, db_session_mock): + '''test for blog not found''' + + db_session_mock.query(Blog).filter(Blog.id == f'{uuid7()}').first.return_value = None + + app.dependency_overrides[get_db] = lambda: db_session_mock + app.dependency_overrides[user_service.get_current_super_admin] = lambda: mock_get_current_super_admin + + response = client.put(f"/api/v1/blogs/{uuid7()}/restore", headers={'Authorization': 'Bearer token'}) + + assert response.status_code == 404 + assert response.json()["message"] == "Post not found" + + +if __name__ == "__main__": + pytest.main()