OUT-3574: daily cron to auto-refresh QBO refresh tokens - #239
Conversation
…expiry Idle portals previously needed manual re-auth once their ~100-day refresh token lapsed. New daily cron at /api/quickbooks/refresh-tokens sweeps any portal with sync_flag=true whose refresh token expires within 14 days and rotates it via the existing race-aware getRefreshedQbTokenInfo helper. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
Greptile SummaryThis PR introduces a daily Vercel cron ( Confidence Score: 4/5Safe to merge; two P2 findings worth addressing but neither blocks correctness under current Intuit TTL behaviour. Only P2 findings present: ORDER BY uses tokenSetTime as a proxy for expiry order (works because Intuit uses a uniform TTL today, but would break if TTLs ever diverge), and sequential processing of 150 portals at ~2 s each sits right at the 300 s Vercel timeout with no safety margin. No P0/P1 issues found. src/db/service/token.service.ts (ORDER BY semantics) and src/app/api/quickbooks/refresh-tokens/refresh-tokens.service.ts (batch timeout headroom) Important Files Changed
Sequence DiagramsequenceDiagram
participant Vercel as Vercel Scheduler (06:00 UTC)
participant Route as GET /api/quickbooks/refresh-tokens
participant Controller as refreshExpiringTokensCron
participant Service as refreshExpiringTokens
participant DB as PostgreSQL
participant Intuit as Intuit OAuth API
Vercel->>Route: GET (Authorization: Bearer CRON_SECRET)
Route->>Controller: withErrorHandler(refreshExpiringTokensCron)
Controller->>Controller: Validate Bearer token
Controller->>Service: refreshExpiringTokens()
Service->>DB: getPortalsWithExpiringRefreshTokens(14 days, 150)
DB-->>Service: portals[] (expiring soonest-first, up to 150)
loop for each portal
Service->>Intuit: getRefreshedQbTokenInfo(portalId, row)
Intuit-->>Service: new access_token + refresh_token
Service->>DB: UPDATE qb_portal_connections SET tokens, tokenSetTime
alt QBReconnectRequiredError
Service->>Service: reconnectRequired++, Sentry.captureException
else other error
Service->>Service: errored++, Sentry.captureException
end
end
Service-->>Controller: scanned/refreshed/reconnectRequired/errored
Controller-->>Route: NextResponse.json success + summary
Route-->>Vercel: 200 OK
Reviews (1): Last reviewed commit: "feat(OUT-3574): daily cron to auto-refre..." | Re-trigger Greptile |
Intuit calls go through withRetry, so a flaky/rate-limited window can push per-portal latency to 5+ s. 150 × 2 s sat exactly on the 300 s Vercel cap with no room for jitter. 120 finishes in ~120 s at typical 1 s/portal and still gets ~60 portals through at a pessimistic 5 s/portal before timeout. Any deferred portals roll into the 14-day lead window, no token expires. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Summary
GET /api/quickbooks/refresh-tokens(06:00 UTC) sweeps any portal withqb_settings.sync_flag = truewhose refresh token expires within 14 days and rotates it via the existing race-awaregetRefreshedQbTokenInfohelper.Code sites
src/db/service/token.service.ts→getPortalsWithExpiringRefreshTokens(INNER JOIN onqb_settings.sync_flag = true)src/app/api/quickbooks/refresh-tokens/{route.ts, refresh-tokens.controller.ts, refresh-tokens.service.ts}vercel.json→0 6 * * *Out of scope (intentional)
isSuspendedis dead code today; revocation is logged + Sentry-captured + counted but doesn't mutate state.Test plan
yarn tsc --noEmitclean;yarn lint:checkshows only pre-existing warningsscanned/refreshedsummary)🤖 Generated with Claude Code