Repository navigation
fix: one /v1, owned by the base URL - #200
Merged
Merged
Conversation
`NEXT_PUBLIC_API_URL` meant two different things. The dashboard read it as an origin and appended `/v1`; checkout read it as already including `/v1` and appended nothing. Both were self-consistent, which is why neither looked wrong on its own — but the variable is set once per deployment, so no value could satisfy both. ci.yml builds both apps with `NEXT_PUBLIC_API_URL: https://api.useroutr.com`, so production checkout was being built with no version segment at all. On top of that, four call sites in checkout wrote `/v1/payments/...` against a base that already ended in `/v1`, requesting `/v1/v1/payments/...`: the bank session, its regenerate, bank-sent, and the card session. Those are the card and bank rails. `@useroutr/types` now owns the contract, since both apps already depend on it: resolveApiBaseUrl(origin, fallback) → always exactly one /v1, accepting an origin written either way, and collapsing one that already doubled assertVersionlessPath(path) → throws outside production if a caller re-adds the version The rule is now stated in one place: the base URL owns the version, call sites never write it. A future /v2 is a one-line change here instead of a sweep. The guard matters more than the resolver. A doubled prefix produces no type error and no lint error — it 404s at runtime and nowhere else, and the mocked hook tests happily assert the wrong string alongside it. That is exactly how twenty call sites accumulated. Also here: the four `usePayouts` test assertions still pinning the old `/v1/payouts` paths, and `useInvoiceCheckout.ts`, which someone had already corrected in the working tree — carried in because the new guard would now throw on it rather than let it 404 quietly. CI gains a Test Packages job. Nothing outside apps/api and the contracts was ever run there, which is why the stale assertions went unnoticed; 12 tests cover both helpers, including the case where someone pastes the already-doubled URL into the env var. Not fixed here: apps/dashboard's vitest suite is red for unrelated reasons (@useroutr/ui does not resolve under vitest; a jsdom container issue), and apps/checkout has a test file with no runner wired at all. Both need their own change before they can join CI. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
This branch was successfully deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Follow-up to #199. That PR fixed 20 call sites; this one removes the reason they kept appearing.
The root cause
NEXT_PUBLIC_API_URLmeant two different things:/v1/v1Both were self-consistent, which is why neither looked wrong in isolation. But the variable is set once per deployment, so no value could satisfy both — and
ci.ymlbuilds both apps withNEXT_PUBLIC_API_URL: https://api.useroutr.com, meaning production checkout was built with no version segment at all.Separately, four checkout call sites wrote
/v1/payments/…on top of a base that already ended in/v1, requesting/v1/v1/payments/…. Those are the card and bank rails:bank-session,bank-session/regenerate,bank-sent,card-session.The fix
@useroutr/typesnow owns the contract — both apps already depend on it:One rule, stated once: the base URL owns the version, call sites never write it. All three clients (dashboard
api.ts, dashboardauth.ts, checkoutapi.ts) now go through it, so a future/v2is a one-line change rather than a sweep.The guard matters more than the resolver. A doubled prefix produces no type error and no lint error — it 404s at runtime and nowhere else, and the mocked hook tests assert the wrong string right alongside it. That is precisely how twenty call sites accumulated without anyone noticing.
Server side
Already correct and unchanged: one
setGlobalPrefix('v1'), and no controller repeats it (the one that did,notifications, was fixed in #199).Verified
Guard:
/v1/payoutsand/v1/invoices/abcthrow with the corrected path named in the message;/payoutsand/v1beta/experimentspass (it does not false-positive on a resource whose name merely starts with the version string).In the browser, checkout now requests
GET /v1/checkout/<id>— single prefix.Tests
packages/types, including the case where someone who has already been bitten pastes the doubled URL into the env varapps/apiand the contracts was ever run there, which is why the stale assertions survivedusePayoutsassertions still pinning/v1/payouts. Dashboard suite goes from 14 failed / 6 passed to 10 failed / 10 passed — the remaining 10 fail on cleanmaintoo, for unrelated reasons (see below)Deliberately not fixed here
apps/dashboard's vitest suite is red independently of this change:@useroutr/uidoesn't resolve under vitest (the package ships raw TSX with no build, handled in Next bytranspilePackagesbut not configured for vitest), plus a jsdom container issue. Needs its own change before it can join CI.apps/checkout/__tests__/crypto-payment.test.tsxexists but the app has no test script, no test deps and no config — it has never run once.fetch()and no auth header, already tracked separately.One carried-in change worth naming:
useInvoiceCheckout.tshad already been corrected in the working tree by someone else. I included it because the new guard would now throw on those paths rather than let them 404 quietly.