Skip to content

refactor: simplify invoice tags API to single PUT endpoint, improve O…#80

Merged
emilwojtaszek merged 5 commits into
mainfrom
refactor/simplify-invoice-tags-api
Mar 8, 2026
Merged

refactor: simplify invoice tags API to single PUT endpoint, improve O…#80
emilwojtaszek merged 5 commits into
mainfrom
refactor/simplify-invoice-tags-api

Conversation

@emilwojtaszek

@emilwojtaszek emilwojtaszek commented Mar 7, 2026

Copy link
Copy Markdown
Member

…penAPI docs

Remove POST /invoices/:id/tags (add_tags) and DELETE /invoices/:id/tags/:tag_id (remove_tag) endpoints, keeping only PUT /invoices/:id/tags (set_tags). The frontend always has the full tag list, so a single replace-all endpoint is sufficient.

Also improve OpenAPI response descriptions across all API controllers to describe specific error conditions instead of generic "Unauthorized" / "Validation error".

Replace manually-maintained endpoint list in README with pointer to SwaggerUI.

Summary by CodeRabbit

  • Documentation

    • Consolidated API docs to reference the OpenAPI 3.0 spec and interactive SwaggerUI (dev URL); removed static endpoint listings and streamlined usage guidance. Response descriptions for invoices, categories, tags, and tokens are clearer and more informative.
  • Breaking Changes

    • Removed add/remove tag endpoints; invoice tag management is now a single replace ("set tags") operation.
  • Tests

    • Updated tests to cover replacement-style tag operations, clearing tags, and revised validation/error expectations.

…penAPI docs

Remove POST /invoices/:id/tags (add_tags) and DELETE /invoices/:id/tags/:tag_id
(remove_tag) endpoints, keeping only PUT /invoices/:id/tags (set_tags). The frontend
always has the full tag list, so a single replace-all endpoint is sufficient.

Also improve OpenAPI response descriptions across all API controllers to describe
specific error conditions instead of generic "Unauthorized" / "Validation error".

Replace manually-maintained endpoint list in README with pointer to SwaggerUI.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Mar 7, 2026

Copy link
Copy Markdown

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

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 8274b19b-0f72-4fe0-9cf0-3c6e10ab99a8

📥 Commits

Reviewing files that changed from the base of the PR and between 19b0cd1 and 73dc6c7.

📒 Files selected for processing (1)
  • lib/ksef_hub_web/controllers/api/invoice_controller.ex

📝 Walkthrough

Walkthrough

Consolidated invoice tag operations to a single PUT-based replacement flow; removed POST/DELETE invoice tag routes and corresponding controller methods; updated README to point to OpenAPI/SwaggerUI; clarified OpenAPI response descriptions across invoice, category, tag, and token controllers; tests adjusted for set_tags semantics.

Changes

Cohort / File(s) Summary
README / Docs
README.md
Replaced explicit Invoices/Categories/Tags/API Tokens endpoint lists with OpenAPI references (GET /api/openapi, GET /dev/swaggerui) and a single SwaggerUI usage instruction.
Invoice controller
lib/ksef_hub_web/controllers/api/invoice_controller.ex
Removed add_tags/2 and remove_tag/2 and related private helpers; consolidated tagging to set_tags; expanded and clarified OpenAPI response and error descriptions (no runtime behavior changes beyond removed methods).
Router
lib/ksef_hub_web/router.ex
Deleted POST /invoices/:id/tags and DELETE /invoices/:id/tags/:tag_id routes; PUT /invoices/:id/tags retained as canonical tag update endpoint.
Tag / Category / Token controllers
lib/ksef_hub_web/controllers/api/{tag_controller.ex,category_controller.ex,token_controller.ex}
Refined OpenAPI operation/response description texts (more explicit 200/201/401/404/422 messages); no control-flow or signature changes.
Tests
test/ksef_hub_web/controllers/api/invoice_controller_test.exs
Removed add_tags tests; added/updated tests for set_tags replacement semantics (clearing tags with empty list, invalid payload shapes, invalid UUIDs, cross-company 422 validations).
Schemas / Docstrings
lib/ksef_hub_web/schemas/invoice_tags_request.ex
Docstring changed to "setting tags" (from "adding or setting tags"); no schema/type changes.

Sequence Diagram(s)

sequenceDiagram
  rect rgba(220,230,240,0.5)
    participant Client
    participant Router
    participant InvoiceController
    participant TagService
    participant DB
  end

  Client->>Router: PUT /api/invoices/:id/tags (tag_ids list)
  Router->>InvoiceController: dispatch set_tags/2
  InvoiceController->>TagService: validate tag_ids, company lookup
  TagService->>DB: fetch tags by ids
  DB-->>TagService: tags (or missing)
  TagService-->>InvoiceController: validated tags or error
  InvoiceController->>DB: update invoice tag associations (replace)
  DB-->>InvoiceController: updated invoice
  InvoiceController-->>Client: 200 OK with updated invoice payload
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

Poem

🐇 I hopped through specs with bright, quick paws,

PUT replaced the scatter, tidy were the laws.
Swagger blooms by the dev server light,
Old routes tucked in burrows out of sight.
I nibble docs and dance — the API feels right.

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main change: simplifying the invoice tags API to a single PUT endpoint and improving OpenAPI documentation.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

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

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch refactor/simplify-invoice-tags-api

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

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 4

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

Inline comments:
In `@lib/ksef_hub_web/controllers/api/category_controller.ex`:
- Around line 27-28: The file fails CI formatting; run mix format on the module
(CategoryController) source and commit the changes so the map entries like the
response tuple lines (200 => {"Category list ordered by sort_order then name",
"application/json", Schemas.CategoryListResponse}, 401 => {"Unauthorized —
missing or invalid API token", "application/json", Schemas.ErrorResponse})
conform to project’s formatter rules; simply run `mix format` (or apply the
formatter to this file), verify formatting, and push the formatted file.

In `@lib/ksef_hub_web/controllers/api/invoice_controller.ex`:
- Around line 103-104: The file fails the project's formatter; run mix format to
correct formatting issues so the map entries (e.g. the 200 => {"Paginated
invoice list with metadata", "application/json", Schemas.InvoiceListResponse}
and 401 => {"Unauthorized — missing or invalid API token", "application/json",
Schemas.ErrorResponse}) and the surrounding InvoiceController module conform to
mix format rules; simply run mix format (or format this file) and commit the
resulting changes to resolve the CI formatting failure.

In `@lib/ksef_hub_web/controllers/api/tag_controller.ex`:
- Around line 28-29: The CI failure is due to formatting issues in the
TagController module (e.g., the spec lines referencing Schemas.TagListResponse
and Schemas.ErrorResponse); run mix format on the codebase (or format this file)
to correct indentation and spacing so the lines like the 200/401 response map
conform to Elixir formatting rules, then commit the formatted file.

In `@lib/ksef_hub_web/controllers/api/token_controller.ex`:
- Around line 26-27: The file has formatting issues causing the CI mix format
check to fail; run the Elixir formatter and commit the changes for
lib/ksef_hub_web/controllers/api/token_controller.ex (ensure the module
TokenController and the map entries referencing Schemas.TokenListResponse and
Schemas.ErrorResponse are formatted by running mix format or formatting the file
in your editor), then re-run the pipeline so the formatted file passes the CI
check.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: fc6e65cd-f1f9-460c-92e8-cc7b9470bad7

📥 Commits

Reviewing files that changed from the base of the PR and between cebc01e and 9c147cd.

📒 Files selected for processing (7)
  • README.md
  • lib/ksef_hub_web/controllers/api/category_controller.ex
  • lib/ksef_hub_web/controllers/api/invoice_controller.ex
  • lib/ksef_hub_web/controllers/api/tag_controller.ex
  • lib/ksef_hub_web/controllers/api/token_controller.ex
  • lib/ksef_hub_web/router.ex
  • test/ksef_hub_web/controllers/api/invoice_controller_test.exs
💤 Files with no reviewable changes (1)
  • lib/ksef_hub_web/router.ex

Comment thread lib/ksef_hub_web/controllers/api/category_controller.ex Outdated
Comment thread lib/ksef_hub_web/controllers/api/invoice_controller.ex Outdated
Comment thread lib/ksef_hub_web/controllers/api/tag_controller.ex Outdated
Comment thread lib/ksef_hub_web/controllers/api/token_controller.ex Outdated
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

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

Inline comments:
In `@lib/ksef_hub_web/controllers/api/invoice_controller.ex`:
- Around line 172-174: The OpenAPI 415 response text claims "accepted: PDF, PNG,
JPG, HEIC, WEBP" but the actual validator validate_content_type/1 only checks
for PDF magic bytes; either extend validate_content_type/1 to detect
PNG/JPG/HEIC/WEBP by adding their respective magic-byte checks (and update
tests) or change the 415 mapping's message (the tuple at the 415 key in the
response map) to accurately state "accepted: PDF" so docs match implementation;
reference validate_content_type/1 and the 415 response tuple to locate the
changes.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: ffe29403-9ab6-435e-bd23-88599934a424

📥 Commits

Reviewing files that changed from the base of the PR and between 9c147cd and f420be6.

📒 Files selected for processing (4)
  • lib/ksef_hub_web/controllers/api/category_controller.ex
  • lib/ksef_hub_web/controllers/api/invoice_controller.ex
  • lib/ksef_hub_web/controllers/api/tag_controller.ex
  • lib/ksef_hub_web/controllers/api/token_controller.ex
✅ Files skipped from review due to trivial changes (1)
  • lib/ksef_hub_web/controllers/api/token_controller.ex

Comment thread lib/ksef_hub_web/controllers/api/invoice_controller.ex
emilwojtaszek and others added 2 commits March 7, 2026 09:35
…ests

- Fix InvoiceTagsRequest moduledoc (no longer mentions "adding")
- Remove dead render_tag_error(:tag_not_in_company) clause
- Clarify set_tags description: empty list clears all tags
- Add tests for invalid tag_ids payloads (non-list, invalid UUIDs)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The upload endpoint only accepts PDF files (magic byte check), but the
description incorrectly listed PNG, JPG, HEIC, WEBP as accepted types.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 3

♻️ Duplicate comments (1)
lib/ksef_hub_web/controllers/api/invoice_controller.ex (1)

172-174: ⚠️ Potential issue | 🟡 Minor

The 415 description still overstates accepted upload formats.

Line 172 says PNG/JPG/HEIC/WEBP are accepted, but validate_content_type/1 at Lines 920-927 only accepts PDF magic bytes and the runtime error at Line 232 also says the file must be a PDF. Either expand the validator/tests or narrow the OpenAPI text to PDF only.

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

In `@lib/ksef_hub_web/controllers/api/invoice_controller.ex` around lines 172 -
174, The OpenAPI 415 response text in the invoice_controller mapping overstates
accepted formats while validate_content_type/1 and the runtime error only allow
PDF; update the 415 description to state only PDF is accepted (replace "PDF,
PNG, JPG, HEIC, WEBP" with "PDF") so the controller's error message and the
validate_content_type/1 behavior (and the runtime error thrown at the invoice
upload path) are consistent; alternatively, if you intend to accept other
formats, extend validate_content_type/1 and related tests to recognize those
magic bytes and adjust the runtime error accordingly — but by default change the
description to "PDF" to match validate_content_type/1 and the runtime error.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@lib/ksef_hub_web/controllers/api/invoice_controller.ex`:
- Around line 482-484: The 422 response description for dismiss_duplicate is
incorrect: update the 422 entry in dismiss_duplicate's response map (the tuple
currently containing "Invoice is not flagged as a suspected duplicate",
"application/json", Schemas.ErrorResponse) to accurately list the real failure
cases ("invoice is not a suspected duplicate" and "invoice is already
dismissed") or replace the single message with a neutral phrase like "invoice is
not a duplicate or already dismissed" so it matches the behavior asserted by
test/ksef_hub_web/controllers/api/invoice_controller_test.exs and the
controller's dismiss_duplicate logic.
- Around line 178-180: The API contract currently documents extractor outages as
returning 502 (the tuple keyed by 502), but the tests
(test/ksef_hub_web/controllers/api/invoice_controller_test.exs lines ~579-603)
exercise that path and the controller actually returns 201 with
extraction_status == "failed"; update the contract to reflect the real behavior
by replacing (or adding) the 502 entry so the response for extractor failures is
a 201 response using the InvoiceResponse schema (with extraction_status
"failed") instead of claiming a 502 Schema/ErrorResponse; ensure the tuple that
currently reads 502 => {"Extraction service unavailable or returned an error",
"application/json", Schemas.ErrorResponse} is changed to the 201 form matching
the controller behavior.
- Around line 745-746: The current validate_tag_ids/1 helper returns {:ok, []}
for nil which lets omitted tag_ids act like an explicit clear; change
validate_tag_ids(nil) to return an error tuple (e.g. {:error, :missing_tag_ids}
or {:error, "tag_ids required"}) so the controller will translate that into a
422, and update the controller branch that calls validate_tag_ids (the call
around Line 853 in invoice_controller.ex) to handle this error case and return a
422 response instead of proceeding to clear tags.

---

Duplicate comments:
In `@lib/ksef_hub_web/controllers/api/invoice_controller.ex`:
- Around line 172-174: The OpenAPI 415 response text in the invoice_controller
mapping overstates accepted formats while validate_content_type/1 and the
runtime error only allow PDF; update the 415 description to state only PDF is
accepted (replace "PDF, PNG, JPG, HEIC, WEBP" with "PDF") so the controller's
error message and the validate_content_type/1 behavior (and the runtime error
thrown at the invoice upload path) are consistent; alternatively, if you intend
to accept other formats, extend validate_content_type/1 and related tests to
recognize those magic bytes and adjust the runtime error accordingly — but by
default change the description to "PDF" to match validate_content_type/1 and the
runtime error.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 37025238-4bab-4705-bac5-7b3240c7d6cf

📥 Commits

Reviewing files that changed from the base of the PR and between f420be6 and c739a0c.

📒 Files selected for processing (3)
  • lib/ksef_hub_web/controllers/api/invoice_controller.ex
  • lib/ksef_hub_web/schemas/invoice_tags_request.ex
  • test/ksef_hub_web/controllers/api/invoice_controller_test.exs
✅ Files skipped from review due to trivial changes (1)
  • lib/ksef_hub_web/schemas/invoice_tags_request.ex

Comment thread lib/ksef_hub_web/controllers/api/invoice_controller.ex Outdated
Comment thread lib/ksef_hub_web/controllers/api/invoice_controller.ex
Comment on lines +745 to +746
description:
"Replaces all tags on an invoice with the given list. Pass an empty list to clear all tags.",

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Missing tag_ids still behaves like “clear all tags”.

Line 746 makes empty-list clearing explicit, but validate_tag_ids(nil) at Line 853 still returns {:ok, []}. A client sending {} or omitting tag_ids will therefore wipe all tags instead of getting a 422, which makes this endpoint unexpectedly destructive.

💡 Suggested fix
-  `@spec` validate_tag_ids(term()) :: {:ok, [String.t()]} | {:error, :invalid_tag_ids}
-  defp validate_tag_ids(nil), do: {:ok, []}
+  `@spec` validate_tag_ids(term()) ::
+          {:ok, [String.t()]} | {:error, :missing_tag_ids | :invalid_tag_ids}
+  defp validate_tag_ids(nil), do: {:error, :missing_tag_ids}
+  defp render_tag_error(conn, _id, {:error, :missing_tag_ids}) do
+    conn
+    |> put_status(:unprocessable_entity)
+    |> json(%{error: "Missing required tag_ids payload"})
+  end
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@lib/ksef_hub_web/controllers/api/invoice_controller.ex` around lines 745 -
746, The current validate_tag_ids/1 helper returns {:ok, []} for nil which lets
omitted tag_ids act like an explicit clear; change validate_tag_ids(nil) to
return an error tuple (e.g. {:error, :missing_tag_ids} or {:error, "tag_ids
required"}) so the controller will translate that into a 422, and update the
controller branch that calls validate_tag_ids (the call around Line 853 in
invoice_controller.ex) to handle this error case and return a 422 response
instead of proceeding to clear tags.

…e 422

- Remove 502 from upload spec: extractor failures return 201 with
  extraction_status 'failed', not 502. Update 201 description to mention
  extraction_status (complete/partial/failed).
- Fix dismiss_duplicate 422: now mentions both error cases (not a duplicate,
  or already dismissed).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@emilwojtaszek emilwojtaszek merged commit 08b8290 into main Mar 8, 2026
3 checks passed
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.

1 participant