Wrap the eight invitation-key downloads
`GOALS.md` listed these as "eight routes that take no auth token, so they do
not fit the authenticated request path and need a different entry point."
That was wrong on every count, and wrong in the way goal 6 exists to catch:
the premise came from the route names rather than the route file.
All eight sit inside `routes/api.php`'s `api/v1` group, under its
`token_auth` middleware, and the controllers' own OpenAPI blocks declare
`X-API-TOKEN`. So they needed no new entry point — just a macro, alongside
`entity_action_methods!` and for the same four entities. What the item had
actually described is the client-portal family in `routes/client.php`, which
is genuinely token-less, is not under `/api/v1`, and stays unwrapped for
that reason; it is now the gap entry in `COVERAGE.md`.
`download_{invoice,quote,credit,purchase_order}_by_invitation_key` returns
the PDF and `download_e_*_by_invitation_key` the XML e-document. These take
a contact's invitation key rather than a record id, so the server renders in
that contact's locale — a client with contacts in two languages gets a
different document per key, which the id-based `download_*` cannot express.
Two things here are easy to get wrong, so both are pinned by tests. The
entity segment is singular (`invoice/{key}/download`), the only route family
in the API that departs from the plural CRUD path. And an unknown key is a
400 carrying `no record found`: the controllers return that directly instead
of raising `ModelNotFoundException`, so the status matches the rule for a
missing record but the message does not.
The smoke test's read tier now downloads an invoice by invitation key,
taking the key from the `invitations` array the list route already returns.
It is there to re-check the token claim against a real server rather than
against a second reading of the source. The `download_e_*` four are left
out: they render from e-invoice settings a test instance need not have.
Also in here, unrelated: a rustfmt fix to a `match` arm in
`examples/smoke_test.rs` that was already off-format on main, so
`cargo fmt --check` — and with it CI's `test` job — was failing before this
change. It could not be split out cleanly, being in a file this change also
touches.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
+51
-8
@@ -18,7 +18,7 @@ declares 34 `Route::resource` groups and 274 explicit route declarations
|
||||
> exists or what it returns.
|
||||
|
||||
**All 34 `Route::resource` groups are now wrapped**, plus `users` (which is
|
||||
not a resource group). 325 methods in total.
|
||||
not a resource group). 333 methods in total.
|
||||
|
||||
## Resources with full CRUD + bulk
|
||||
|
||||
@@ -127,6 +127,47 @@ same reason: the response isn't JSON.
|
||||
|
||||
Convenience wrappers: `mark_invoice_sent` and `mark_invoice_paid`.
|
||||
|
||||
## Invitation-key downloads
|
||||
|
||||
The same four resources also answer two downloads keyed by **invitation key**
|
||||
rather than record id — the per-contact token that appears in a client-portal
|
||||
link. Eight routes, all wrapped:
|
||||
|
||||
| Route | Method | Returns |
|
||||
| --- | --- | --- |
|
||||
| `GET /invoice/{key}/download` | `download_invoice_by_invitation_key` | `application/pdf` |
|
||||
| `GET /invoice/{key}/download_e_invoice` | `download_e_invoice_by_invitation_key` | `application/xml` |
|
||||
| `GET /quote/{key}/download` | `download_quote_by_invitation_key` | `application/pdf` |
|
||||
| `GET /quote/{key}/download_e_quote` | `download_e_quote_by_invitation_key` | `application/xml` |
|
||||
| `GET /credit/{key}/download` | `download_credit_by_invitation_key` | `application/pdf` |
|
||||
| `GET /credit/{key}/download_e_credit` | `download_e_credit_by_invitation_key` | `application/xml` |
|
||||
| `GET /purchase_order/{key}/download` | `download_purchase_order_by_invitation_key` | `application/pdf` |
|
||||
| `GET /purchase_order/{key}/download_e_purchase_order` | `download_e_purchase_order_by_invitation_key` | `application/xml` |
|
||||
|
||||
Two things about these are easy to get wrong, and both are pinned by tests.
|
||||
The entity segment is **singular** — `invoice/…`, not `invoices/…` — which
|
||||
is the only place in the API that departs from the plural CRUD path. And
|
||||
despite being the download a *client* follows, they are **authenticated**:
|
||||
the routes sit inside `routes/api.php`'s `api/v1` group under its
|
||||
`token_auth` middleware, so they need the API token like any other call.
|
||||
|
||||
This file previously listed them as unwrapped on the grounds that they take
|
||||
no auth token. That was wrong — it conflated them with the client-portal
|
||||
routes in `routes/client.php` (`/client/invoice/{key}/download_pdf` and
|
||||
friends), which are a separate route family, are not under `/api/v1`, and
|
||||
remain unwrapped for that reason.
|
||||
|
||||
The smoke test's read tier exercises the invoice PDF, taking a key from the
|
||||
`invitations` array the list route already returns. The `download_e_*` four
|
||||
are not exercised: they render from the company's e-invoice settings, which a
|
||||
test instance need not have configured.
|
||||
|
||||
An unknown or deleted key is a `400` carrying `no record found`. The
|
||||
controllers return that directly rather than raising
|
||||
`ModelNotFoundException`, so the message differs from the `No query results
|
||||
for model` text a missing record produces elsewhere, but the status is the
|
||||
same 400.
|
||||
|
||||
## Document uploads
|
||||
|
||||
`PUT /{resource}/{id}/upload` exists for 16 resources, each getting
|
||||
@@ -346,12 +387,13 @@ check `routes/api.php`).
|
||||
`/migration/start`
|
||||
- **Invoice extras** — `delivery_note`, `payment_schedule`,
|
||||
`update_reminders`
|
||||
- **Public invitation-key downloads** — `GET /{entity}/{invitation_key}/download`
|
||||
for `invoice`, `quote`, `credit`, and `purchase_order` (note the singular
|
||||
path segment), plus the matching e-invoice variants `download_e_invoice`,
|
||||
`download_e_quote`, `download_e_credit`,
|
||||
`download_e_purchase_order`. These are invitation-key based and take no
|
||||
auth token, so they don't fit the client's authenticated request path.
|
||||
- **Client-portal invitation downloads** — the `routes/client.php` family:
|
||||
`/client/invoice/{key}/download_pdf`, the `download_e_*` variants for
|
||||
quotes and credits, `/client/{entity}/{key}/download`, and
|
||||
`/client/documents/{hash}/hashed`. These are not under `/api/v1`, so they
|
||||
don't fit a `base_url` that ends there. Note these are *not* the
|
||||
invitation-key downloads documented above, which are authenticated
|
||||
`/api/v1` routes and are wrapped.
|
||||
- **Integrations** — Stripe, QuickBooks, Apple in-app purchase, PostMark,
|
||||
einvoice (17 routes), calendar_connection, self-update, templates, hooks,
|
||||
Yodlee (`/yodlee/*`), Nordigen (`/nordigen/institutions`)
|
||||
@@ -406,7 +448,8 @@ top of a shared `BaseEntity` offering `all`, `get`, `create`, `update`,
|
||||
`recurring_quotes`, `recurring_expenses`, `bank_transaction_rules`,
|
||||
`company_gateways`, `designs`, `locations`, `tags`, `task_schedulers`,
|
||||
`tokens`, `system_logs`, documents, per-entity actions (`mark_sent`,
|
||||
`mark_paid`, `clone_to_*`, …), per-entity PDF download, reports, charts,
|
||||
`mark_paid`, `clone_to_*`, …), per-entity PDF download by id and by
|
||||
invitation key, the XML e-documents, reports, charts,
|
||||
search, `send_email`, `refund_payment`, `ping`, `health_check`, automatic
|
||||
pagination (`list_all_*`), and `X-API-PASSWORD` support for the
|
||||
password-protected routes.
|
||||
|
||||
Reference in New Issue
Block a user