Skip to content

Fix/toggle favourite 404#1210

Open
Ujjwal-Gowda wants to merge 7 commits intoAOSSIE-Org:mainfrom
Ujjwal-Gowda:fix/toggle-favourite-404
Open

Fix/toggle favourite 404#1210
Ujjwal-Gowda wants to merge 7 commits intoAOSSIE-Org:mainfrom
Ujjwal-Gowda:fix/toggle-favourite-404

Conversation

@Ujjwal-Gowda
Copy link

@Ujjwal-Gowda Ujjwal-Gowda commented Mar 3, 2026

Addressed Issues:

BUG: /images/toggle-favourite returns 500 instead of 404 for unknown image_id
Fixes #1179

Screenshots/Recordings:

Screenshot from 2026-03-03 21-27-25

Additional Notes:

This PR fixes issue #1179, where the /images/toggle-favourite endpoint was incorrectly returning a 500 Internal Server Error instead of a 404 Not Found when an invalid image_id was provided now it returns a 404 when an invalid image_id is provided.

Checklist

  • [ x] My PR addresses a single issue, fixes a single bug or makes a single improvement.
  • [ x] My code follows the project's code style and conventions
  • [ x] If applicable, I have made corresponding changes or additions to the documentation
  • [ x] If applicable, I have made corresponding changes or additions to tests
  • [ x] My changes generate no new warnings or errors
  • [ x] I have joined the Discord server and I will share a link to this PR with the project maintainers there
  • [ x] I have read the Contribution Guidelines
  • [ x] Once I submit my PR, CodeRabbit AI will automatically review it and I will address CodeRabbit's comments.

AI Usage Disclosure

Check one of the checkboxes below:

  • This PR does not contain AI-generated code at all.
  • [ x] This PR contains AI-generated code. I have tested the code locally and I am responsible for it.

I have used the following AI models and tools: i have used gemini cli for test generation

⚠️ AI Notice - Important!

We encourage contributors to use AI tools responsibly when creating Pull Requests. While AI can be a valuable aid, it is essential to ensure that your contributions meet the task requirements, build successfully, include relevant tests, and pass all linters. Submissions that do not meet these standards may be closed without warning to maintain the quality and integrity of the project. Please take the time to understand the changes you are proposing and their impact.

Summary by CodeRabbit

  • Bug Fixes

    • Error responses are now structured (404/500) and HTTP errors are propagated correctly.
    • Toggle-favourite verifies image existence after toggling and returns not-found if absent.
  • Improvements

    • Updated image retrieval to fetch the updated image directly.
    • Image metadata is always returned with required fields and sensible defaults.
  • Tests

    • Added tests for toggle-favourite, empty metadata handling, and not-found responses.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Mar 3, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

Preserves HTTPExceptions in the toggle-favourite route, returns structured ErrorResponse on 404s, adds db_get_image_by_id for single-image retrieval, ensures metadata parsing always merges defaults, and adds tests for empty metadata, nonexistent image 404, and toggle behaviour.

Changes

Cohort / File(s) Summary
Routes / Toggle handling
backend/app/routes/images.py
Propagate HTTPException (except HTTPException: raise), return structured ErrorResponse for missing/failed toggles, fetch updated image via db_get_image_by_id instead of scanning all images, and map other exceptions to 500 with structured payload.
Database / Image retrieval
backend/app/database/images.py
Added db_get_image_by_id(image_id: str) -> Optional[dict] to retrieve a single image (joined tags, parsed metadata, flags, coords, captured_at); db_toggle_image_favourite_status now returns Optional[bool] and re-raises exceptions after logging.
Utils / Metadata parsing
backend/app/utils/images.py
image_util_parse_metadata now merges parsed metadata with defaults, handles falsy and JSON-string inputs, and always returns a complete metadata dict with required fields.
Tests
backend/tests/test_toggle_favourite.py
New tests: fixture inserts a dummy image; asserts GET /images/ handles empty metadata; verifies POST /images/toggle-favourite returns structured 404 for unknown image_id; verifies toggling flips isFavourite twice.
Manifest / Misc
manifest_file
Manifest entries updated to reflect added/changed files and lines (non-functional).

Sequence Diagram(s)

sequenceDiagram
  participant Client
  participant API as ImagesRoute
  participant DB as ImagesDB
  participant Utils as MetadataUtil

  Client->>API: POST /images/toggle-favourite { image_id }
  API->>DB: db_toggle_image_favourite_status(image_id)
  alt toggle returned None (not found / failed)
    DB-->>API: None
    API-->>Client: 404 ErrorResponse ("Image not found or failed to toggle")
  else toggle succeeded (bool)
    DB-->>API: True/False
    API->>DB: db_get_image_by_id(image_id)
    DB-->>API: image_row or None
    alt image_row found
      API->>Utils: image_util_parse_metadata(image_row.metadata)
      Utils-->>API: merged metadata dict
      API-->>Client: 200 { image with updated isFavourite }
    else image_row missing
      API-->>Client: 404 ErrorResponse ("Image not found after toggling")
    end
  end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Suggested labels

Python

Poem

A rabbit nudged code with a careful paw,
Kept 404s honest and tidy the law,
Defaults stitched where metadata slept,
Tests hopped through toggles and promises kept,
🐇✨

🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'Fix/toggle favourite 404' directly addresses the main objective of fixing the toggle-favourite endpoint to return 404 instead of 500 for unknown image IDs.
Linked Issues check ✅ Passed The PR comprehensively addresses issue #1179 by fixing the broad exception handler, restructuring error responses, adding explicit HTTPException propagation, and implementing tests validating 404 behavior.
Out of Scope Changes check ✅ Passed All changes are in-scope: the database function additions and signature updates support the core fix, the utility function improvements ensure complete metadata handling, and the new tests validate the corrected behavior.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Tip

Try Coding Plans. Let us write the prompt for your AI agent so you can ship faster (with fewer bugs).
Share your feedback on Discord.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🧹 Nitpick comments (1)
backend/app/routes/images.py (1)

105-110: Extract duplicated 404 error payload construction.

The same ErrorResponse(...).model_dump() block is repeated in two places. Consider a small helper (or constant factory) to keep error shape/messages consistent and easier to maintain.
As per coding guidelines, "Look for code duplication".

Also applies to: 118-123

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@backend/app/routes/images.py` around lines 105 - 110, Create a small helper
to produce the 404 error payload and replace the duplicated
ErrorResponse(...).model_dump() usages: add a function like
build_image_not_found_error() that returns ErrorResponse(success=False,
error="Image Not Found", message="Image not found or failed to
toggle").model_dump(), then call this helper from the two places currently
constructing that payload (the HTTPException raises in routes/images.py around
the toggling logic). Update both occurrences to use
build_image_not_found_error() so the shape/messages are consistent and
duplication is removed.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@backend/app/routes/images.py`:
- Around line 116-124: The current post-toggle read maps any failure to a 404 by
checking `image is None` after calling the generic read; instead call a DB
method that clearly distinguishes “not found” from internal errors (e.g., use or
add a `db_get_image_by_id(image_id)` or similar in
`backend/app/database/images.py`) and propagate DB exceptions rather than
treating them as empty results; change the logic around the `image` check in the
toggle handler to catch and re-raise DB errors (500) while only returning the
HTTP 404 when the DB call returns a definitive not-found result (None),
referencing the toggle handler's `image` variable and the new/updated
`db_get_image_by_id` function to locate the change.

In `@backend/app/utils/images.py`:
- Around line 553-562: The code parses db_metadata with json.loads and then
merges it into defaults, but json.loads can return non-dict values
(list/str/number) causing a TypeError when performing {**defaults, **parsed};
modify the parsing branch around db_metadata so that after json.loads you verify
parsed is a dict (e.g., isinstance(parsed, dict)) and if not, set parsed = {}
(or coerce only expected keys), ensuring the merge {**defaults, **parsed} only
receives a mapping; references: the db_metadata variable and the return merge
expression that currently does "return {**defaults, **parsed}"—add the dict-type
guard immediately after json.loads and before the merge.

In `@backend/tests/test_toggle_favourite.py`:
- Around line 24-31: The test test_get_all_images_crash currently only checks
for a 200 and success flag; update it to also validate that the first returned
image includes required metadata keys by inspecting response.json() (use the
existing response variable) and asserting that the first image's metadata
contains "name", "width", "height", "file_location", and "item_type" (and
optionally their default non-null/expected default values) so the contract about
metadata defaults is enforced.
- Around line 9-23: The dummy_image fixture inserts a test row into the images
table but never removes it; change the fixture to perform teardown by yielding
the image_id (use the fixture named dummy_image) and after the yield delete the
inserted row (DELETE FROM images WHERE id = ?) using the same DATABASE_PATH
connection and commit/close the connection to ensure no persistent DB state
leaks between tests; ensure the fixture still returns the image_id to callers
and that cleanup runs even if tests fail.

---

Nitpick comments:
In `@backend/app/routes/images.py`:
- Around line 105-110: Create a small helper to produce the 404 error payload
and replace the duplicated ErrorResponse(...).model_dump() usages: add a
function like build_image_not_found_error() that returns
ErrorResponse(success=False, error="Image Not Found", message="Image not found
or failed to toggle").model_dump(), then call this helper from the two places
currently constructing that payload (the HTTPException raises in
routes/images.py around the toggling logic). Update both occurrences to use
build_image_not_found_error() so the shape/messages are consistent and
duplication is removed.

ℹ️ Review info

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 8274ebd and 08ed7d3.

📒 Files selected for processing (3)
  • backend/app/routes/images.py
  • backend/app/utils/images.py
  • backend/tests/test_toggle_favourite.py

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@backend/app/database/images.py`:
- Line 291: The mapping for "folder_id" treats 0 as falsey; update the
conditional to explicitly check for None so a folder_id of 0 is
preserved—replace the check in the mapping (the line that currently reads
"folder_id": str(row[2]) if row[2] else None) with an explicit None check (e.g.,
"folder_id": str(row[2]) if row[2] is not None else None) so row[2]==0 is
handled correctly.

In `@backend/app/routes/images.py`:
- Around line 131-133: The catch block in the /toggle-favourite route currently
returns the raw exception message in the HTTPException detail and leaks
internals; instead, keep the logger.error call but include exception context
(e.g., pass exc_info=True or log the stack) and change the HTTPException to
return a generic, structured error (matching other handlers' format) such as a
fixed message like "Internal server error" or {"detail":"Internal server error"}
without including {e}; update the block around the logger.error and
HTTPException instantiation to only log the exception details and return the
sanitized generic response.

ℹ️ Review info

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 8a13496 and d770584.

📒 Files selected for processing (2)
  • backend/app/database/images.py
  • backend/app/routes/images.py

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
backend/app/routes/images.py (1)

133-140: Use ErrorResponse in the 500 branch for consistency.

This block already returns a safe message; consider serializing with ErrorResponse(...).model_dump() like other branches to keep one error shape source.

Consistency-only refactor
-        raise HTTPException(
-            status_code=500,
-            detail={
-                "success": False,
-                "error": "Internal Server Error",
-                "message": "An unexpected error occurred while toggling favourite",
-            },
-        )
+        raise HTTPException(
+            status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
+            detail=ErrorResponse(
+                success=False,
+                error="Internal Server Error",
+                message="An unexpected error occurred while toggling favourite",
+            ).model_dump(),
+        )
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@backend/app/routes/images.py` around lines 133 - 140, Replace the raw dict
used in the HTTPException for the 500 branch with a serialized ErrorResponse to
match other error branches: construct ErrorResponse(success=False,
error="Internal Server Error", message="An unexpected error occurred while
toggling favourite") and pass ErrorResponse(...).model_dump() as the detail when
raising HTTPException(status_code=500, ...); keep the same status code and
message text but use ErrorResponse.model_dump() for consistency with other error
responses.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@backend/app/routes/images.py`:
- Around line 102-111: db_toggle_image_favourite_status currently conflates "not
found" and internal DB errors by returning False for both; update the route
handler to distinguish outcomes: call db_toggle_image_favourite_status(image_id)
and treat three cases — a not-found sentinel (e.g., None or a specific enum)
should raise HTTP 404 with "Image Not Found", a DB failure (False or an
exception) should raise HTTP 500 with an internal error message, and success
should continue as normal; reference db_toggle_image_favourite_status
(backend/app/database/images.py) and the route in images.py to implement the
explicit branching and convert DB exceptions to 500 (or propagate) instead of
mapping them to 404.

---

Nitpick comments:
In `@backend/app/routes/images.py`:
- Around line 133-140: Replace the raw dict used in the HTTPException for the
500 branch with a serialized ErrorResponse to match other error branches:
construct ErrorResponse(success=False, error="Internal Server Error",
message="An unexpected error occurred while toggling favourite") and pass
ErrorResponse(...).model_dump() as the detail when raising
HTTPException(status_code=500, ...); keep the same status code and message text
but use ErrorResponse.model_dump() for consistency with other error responses.

ℹ️ Review info

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d770584 and f11378d.

📒 Files selected for processing (2)
  • backend/app/database/images.py
  • backend/app/routes/images.py

Comment on lines 102 to 111
success = db_toggle_image_favourite_status(image_id)
if not success:
raise HTTPException(
status_code=404, detail="Image not found or failed to toggle"
status_code=status.HTTP_404_NOT_FOUND,
detail=ErrorResponse(
success=False,
error="Image Not Found",
message="Image not found or failed to toggle",
).model_dump(),
)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Don’t map DB exceptions to 404 in this branch.

At Line 103, not success currently returns 404, but db_toggle_image_favourite_status also returns False on internal DB exceptions (backend/app/database/images.py Line 504-Line 507). That can still mislabel server failures as “Image Not Found.”

As per coding guidelines, "Confirm that the code meets the project's requirements and objectives."

Suggested fix (separate not-found from DB failure)
# backend/app/database/images.py
-def db_toggle_image_favourite_status(image_id: str) -> bool:
+def db_toggle_image_favourite_status(image_id: str) -> Optional[bool]:
@@
-        if not cursor.fetchone():
-            return False
+        if not cursor.fetchone():
+            return None
@@
-    except Exception as e:
-        logger.error(f"Database error: {e}")
-        conn.rollback()
-        return False
+    except Exception:
+        logger.exception("Database error while toggling favourite")
+        conn.rollback()
+        raise
# backend/app/routes/images.py
-        success = db_toggle_image_favourite_status(image_id)
-        if not success:
+        result = db_toggle_image_favourite_status(image_id)
+        if result is None:
             raise HTTPException(
                 status_code=status.HTTP_404_NOT_FOUND,
                 detail=ErrorResponse(
                     success=False,
                     error="Image Not Found",
                     message="Image not found or failed to toggle",
                 ).model_dump(),
             )
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@backend/app/routes/images.py` around lines 102 - 111,
db_toggle_image_favourite_status currently conflates "not found" and internal DB
errors by returning False for both; update the route handler to distinguish
outcomes: call db_toggle_image_favourite_status(image_id) and treat three cases
— a not-found sentinel (e.g., None or a specific enum) should raise HTTP 404
with "Image Not Found", a DB failure (False or an exception) should raise HTTP
500 with an internal error message, and success should continue as normal;
reference db_toggle_image_favourite_status (backend/app/database/images.py) and
the route in images.py to implement the explicit branching and convert DB
exceptions to 500 (or propagate) instead of mapping them to 404.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
backend/app/database/images.py (1)

487-489: Use _connect() helper instead of direct sqlite3.connect() for consistency.

This function uses sqlite3.connect(DATABASE_PATH) directly, while all other functions in the file use the _connect() helper. The helper also enables foreign key constraints (PRAGMA foreign_keys = ON), so bypassing it introduces inconsistency.

♻️ Proposed fix
 def db_toggle_image_favourite_status(image_id: str) -> Optional[bool]:
-    conn = sqlite3.connect(DATABASE_PATH)
+    conn = _connect()
     cursor = conn.cursor()
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@backend/app/database/images.py` around lines 487 - 489, The function
db_toggle_image_favourite_status uses sqlite3.connect(DATABASE_PATH) directly;
replace that call with the module's _connect() helper and use the returned
connection/cursor (as other DB functions do) so foreign key PRAGMA and
consistent connection handling are preserved; update any variable assignments
(conn, cursor) and ensure the rest of db_toggle_image_favourite_status uses
those objects and closes/commits the connection unchanged.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@backend/app/database/images.py`:
- Around line 487-489: The function db_toggle_image_favourite_status uses
sqlite3.connect(DATABASE_PATH) directly; replace that call with the module's
_connect() helper and use the returned connection/cursor (as other DB functions
do) so foreign key PRAGMA and consistent connection handling are preserved;
update any variable assignments (conn, cursor) and ensure the rest of
db_toggle_image_favourite_status uses those objects and closes/commits the
connection unchanged.

ℹ️ Review info
Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: f2e98d0d-5b5d-45be-a9df-ab94e2ccbfa3

📥 Commits

Reviewing files that changed from the base of the PR and between f11378d and cabaacc.

📒 Files selected for processing (2)
  • backend/app/database/images.py
  • backend/app/routes/images.py
🚧 Files skipped from review as they are similar to previous changes (1)
  • backend/app/routes/images.py

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

BUG: /images/toggle-favourite returns 500 instead of 404 for unknown image_id

2 participants