feat(release): launch public landing page and enforce usage billing - #17
Conversation
Co-authored-by: BWJ2310 <brucewj2310@gmail.com> Co-authored-by: BWJ2310-backup <jun.1216.wei@gmail.com>
Co-authored-by: BWJ2310 <brucewj2310@gmail.com> Co-authored-by: BWJ2310-backup <jun.1216.wei@gmail.com>
feat(landing): add public TradingGoose Market landing page
|
| Filename | Overview |
|---|---|
| lib/market-api/core/handler.ts | Switches billing from fire-and-forget (via after()) to synchronous fail-closed: computes the market response, then posts usage before returning data; a billing failure now returns 502 instead of the market data. |
| lib/market-api/core/billing.ts | Removes the billing outbox (enqueueBillingEvent, flushBillingOutbox, postMarketUsageDurable); retains postMarketUsage and the validation/cache logic unchanged. |
| .github/workflows/release.yml | New manual release workflow that generates date-based tags and changelog from merged staging PRs; missing --limit on gh pr list silently truncates results to 30, producing incomplete release notes for busy releases. |
| .github/workflows/reset-staging.yml | New workflow that force-pushes main to staging on every merged PR to main; intentional behavior documented in rollout notes, uses correct contents: write permission. |
| app/api/github-stars/route.ts | New route handler that fetches and caches GitHub star counts; uses conflicting cache: "force-cache" alongside next: { revalidate: 3600 } — the explicit cache option is redundant. |
| app/(landing)/actions/github.ts | Client-side helper that fetches star count via a relative URL; works correctly today from a useEffect but will break if the file ever gains a "use server" directive. |
| packages/db/migrations/0006_early_gargoyle.sql | Single destructive migration: DROP TABLE "market_billing_outbox" CASCADE — aligns with schema removal; irreversible without a backup or new forward migration as noted in rollout notes. |
| packages/db/schema.ts | Removes the marketBillingOutbox table definition including its two indexes; no other schema changes. |
| app/(landing)/site-url.ts | Utility that reads NEXT_PUBLIC_APP_URL at build time and guards canonical/OG/JSON-LD URLs; correctly falls back and warns when unconfigured in production. |
| app/(landing)/components/structured-data.tsx | Renders JSON-LD structured data only when isLandingSiteUrlConfigured() is true; safely escapes < characters to prevent XSS via dangerouslySetInnerHTML. |
Sequence Diagram
%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
participant Client
participant Handler as handleMarketRequest
participant Auth as requireApiKey
participant Studio as Official TG Studio
Client->>Handler: Market API request
Handler->>Auth: requireApiKey(request)
Auth-->>Handler: "{ auth }"
alt Free tier
Handler-->>Client: enforceFreeTierLimit response
else Authenticated tier
Handler->>Studio: validateUsageLimitCached(userId)
Studio-->>Handler: "{ allowed }"
alt Usage limit exceeded
Handler-->>Client: 402 Usage limit exceeded
else Allowed
Handler->>Handler: Run market data handler → response
alt response.ok and billingEnabled
Handler->>Studio: postMarketUsage(userId, endpoint, method)
Studio-->>Handler: "{ success, status }"
alt Billing succeeded
Handler-->>Client: 200 Market data response
else Billing failed
Handler-->>Client: 502 Usage billing failed
end
else response not ok or billing disabled
Handler-->>Client: Market data response as-is
end
end
end
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
sequenceDiagram
participant Client
participant Handler as handleMarketRequest
participant Auth as requireApiKey
participant Studio as Official TG Studio
Client->>Handler: Market API request
Handler->>Auth: requireApiKey(request)
Auth-->>Handler: "{ auth }"
alt Free tier
Handler-->>Client: enforceFreeTierLimit response
else Authenticated tier
Handler->>Studio: validateUsageLimitCached(userId)
Studio-->>Handler: "{ allowed }"
alt Usage limit exceeded
Handler-->>Client: 402 Usage limit exceeded
else Allowed
Handler->>Handler: Run market data handler → response
alt response.ok and billingEnabled
Handler->>Studio: postMarketUsage(userId, endpoint, method)
Studio-->>Handler: "{ success, status }"
alt Billing succeeded
Handler-->>Client: 200 Market data response
else Billing failed
Handler-->>Client: 502 Usage billing failed
end
else response not ok or billing disabled
Handler-->>Client: Market data response as-is
end
end
end
Prompt To Fix All With AI
Fix the following 3 code review issues. Work through them one at a time, proposing concise fixes.
---
### Issue 1 of 3
.github/workflows/release.yml:41-47
**Missing `--limit` truncates release notes silently**
Both `gh pr list` calls use the CLI's default limit of 30 items. When more than 30 PRs have been merged into `staging` since the previous release, the excess PRs are silently excluded from changelog entries. The jq date filter is applied client-side to whatever the CLI already returned, so filtering doesn't help recover the missing records. Adding `--limit 200` (or a suitably large value) to both invocations prevents this truncation.
### Issue 2 of 3
app/api/github-stars/route.ts:24-25
`cache: "force-cache"` is redundant here. `next: { revalidate: 3600 }` already opts the fetch into the Next.js Data Cache with a 3600-second TTL; `force-cache` alone would cache indefinitely and is superseded. Removing the explicit `cache` option makes the intent clearer.
```suggestion
next: { revalidate: 3600 }
```
### Issue 3 of 3
app/(landing)/actions/github.ts:5-9
**Relative URL only valid in browser context**
`fetch("/api/github-stars")` works today because `getFormattedGitHubStars` is called from inside a `useEffect` in the client component `LandingNav`. However, placing this helper in an `actions/` directory is a common convention for server actions. If a `"use server"` directive is ever added (intentionally or by refactor), this call will throw `TypeError: Failed to parse URL from /api/github-stars` on the server. Replacing the path with an absolute URL constructed from `window.location.origin` (or an env-based base URL) would make the intent unambiguous and safer to refactor later.
Reviews (1): Last reviewed commit: "Merge pull request #15 from TradingGoose..." | Re-trigger Greptile
| if [ -n "$PREV_DATE" ]; then | ||
| PR_NUMS=$(gh pr list --base staging --state merged --json number,mergedAt \ | ||
| --jq "[.[] | select(.mergedAt >= \"$PREV_DATE\")] | .[].number") | ||
| else | ||
| PR_NUMS=$(gh pr list --base staging --state merged --json number \ | ||
| --jq '.[].number') | ||
| fi |
There was a problem hiding this comment.
Missing
--limit truncates release notes silently
Both gh pr list calls use the CLI's default limit of 30 items. When more than 30 PRs have been merged into staging since the previous release, the excess PRs are silently excluded from changelog entries. The jq date filter is applied client-side to whatever the CLI already returned, so filtering doesn't help recover the missing records. Adding --limit 200 (or a suitably large value) to both invocations prevents this truncation.
Prompt To Fix With AI
This is a comment left during a code review.
Path: .github/workflows/release.yml
Line: 41-47
Comment:
**Missing `--limit` truncates release notes silently**
Both `gh pr list` calls use the CLI's default limit of 30 items. When more than 30 PRs have been merged into `staging` since the previous release, the excess PRs are silently excluded from changelog entries. The jq date filter is applied client-side to whatever the CLI already returned, so filtering doesn't help recover the missing records. Adding `--limit 200` (or a suitably large value) to both invocations prevents this truncation.
How can I resolve this? If you propose a fix, please make it concise.| next: { revalidate: 3600 }, | ||
| cache: "force-cache" |
There was a problem hiding this comment.
cache: "force-cache" is redundant here. next: { revalidate: 3600 } already opts the fetch into the Next.js Data Cache with a 3600-second TTL; force-cache alone would cache indefinitely and is superseded. Removing the explicit cache option makes the intent clearer.
| next: { revalidate: 3600 }, | |
| cache: "force-cache" | |
| next: { revalidate: 3600 } |
Prompt To Fix With AI
This is a comment left during a code review.
Path: app/api/github-stars/route.ts
Line: 24-25
Comment:
`cache: "force-cache"` is redundant here. `next: { revalidate: 3600 }` already opts the fetch into the Next.js Data Cache with a 3600-second TTL; `force-cache` alone would cache indefinitely and is superseded. Removing the explicit `cache` option makes the intent clearer.
```suggestion
next: { revalidate: 3600 }
```
How can I resolve this? If you propose a fix, please make it concise.Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!
| const response = await fetch("/api/github-stars", { | ||
| headers: { | ||
| "Cache-Control": "max-age=3600" | ||
| } | ||
| }); |
There was a problem hiding this comment.
Relative URL only valid in browser context
fetch("/api/github-stars") works today because getFormattedGitHubStars is called from inside a useEffect in the client component LandingNav. However, placing this helper in an actions/ directory is a common convention for server actions. If a "use server" directive is ever added (intentionally or by refactor), this call will throw TypeError: Failed to parse URL from /api/github-stars on the server. Replacing the path with an absolute URL constructed from window.location.origin (or an env-based base URL) would make the intent unambiguous and safer to refactor later.
Prompt To Fix With AI
This is a comment left during a code review.
Path: app/(landing)/actions/github.ts
Line: 5-9
Comment:
**Relative URL only valid in browser context**
`fetch("/api/github-stars")` works today because `getFormattedGitHubStars` is called from inside a `useEffect` in the client component `LandingNav`. However, placing this helper in an `actions/` directory is a common convention for server actions. If a `"use server"` directive is ever added (intentionally or by refactor), this call will throw `TypeError: Failed to parse URL from /api/github-stars` on the server. Replacing the path with an absolute URL constructed from `window.location.origin` (or an env-based base URL) would make the intent unambiguous and safer to refactor later.
How can I resolve this? If you propose a fix, please make it concise.
Summary
stagingreset workflows.Why
The landing route previously returned
notFound(), leaving TradingGoose Market without a public product entry point.Usage reporting previously ran after returning billable data and relied on an outbox fallback. This could return a successful response even when billing usage was not recorded. The updated flow records usage before returning billable data and fails closed if reporting is unsuccessful.
The new GitHub workflows formalize date-based releases from
stagingand resetstagingtomainafter a release PR is merged.Affected Areas
Validation
Rollout Notes
bun run db:migrateduring deployment. Migration0006_early_gargoyledropsmarket_billing_outboxand permanently removes any queued billing events.NEXT_PUBLIC_APP_URLto the public origin at build time. Without it, canonical URLs,og:url, and JSON-LD structured data are intentionally omitted.GITHUB_TOKENorGITHUB_PATis optional for the GitHub star endpoint but increases API reliability and rate limits. Failures fall back to0.502instead of billable response data.mainis merged, the reset workflow force-pushesmaintostaging.Screenshots / Video
Checklist
*/migrations/