fix: adding a found subscription sends its membership id (adopt 400 invalid_body)

membershipReconcile.adopt passed { membership_id } as request options, so
no body was sent. useApi now refuses any option other than body, params or
signal, so a payload in the wrong place fails loudly in the browser instead
of as a 400 from the server.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LVjrnpQjRgHdvq95rASoiR
This commit is contained in:
2026-09-24 14:13:12 -04:00
co-authored by Claude Opus 5.5
parent 2f9e35390e
commit 49d18c1757
3 changed files with 54 additions and 2 deletions
+16 -1
View File
@@ -10,7 +10,22 @@ export class ApiError extends Error {
}
}
async function request(method, url, { body, params, signal } = {}) {
const OPTIONS = new Set(['body', 'params', 'signal'])
async function request(method, url, opts = {}) {
// Refuse an option this wrapper does not know. `api.post(url, { id: 1 })`
// reads naturally and sends NO body — the payload lands in the options bag
// and is dropped — so the server answers `invalid_body` and the caller
// looks broken server-side. It shipped exactly that way in the Subscriptions
// "Add subscription" button (2026-09-24). Failing here names the mistake.
const unknown = Object.keys(opts).filter((k) => !OPTIONS.has(k))
if (unknown.length) {
throw new TypeError(
`useApi ${method} ${url}: unknown option(s) ${unknown.join(', ')} — ` +
'a request payload goes under `body`, a query under `params`'
)
}
const { body, params, signal } = opts
let fullUrl = url
if (params) {
const search = new URLSearchParams()
+1 -1
View File
@@ -28,7 +28,7 @@ export const useMembershipReconcileStore = defineStore('membershipReconcile', ()
async function adopt (membershipId) {
try {
const res = await api.post('/api/sources/reconciliation/adopt', {
membership_id: membershipId
body: { membership_id: membershipId }
})
toast({
text: res.already_tracked