Skip to content

feat(release): launch public landing page and enforce usage billing - #17

Merged
TradingGoose-Dev merged 7 commits into
mainfrom
staging
Jul 17, 2026
Merged

feat(release): launch public landing page and enforce usage billing#17
TradingGoose-Dev merged 7 commits into
mainfrom
staging

Conversation

@TradingGoose-Dev

Copy link
Copy Markdown
Contributor

Summary

  • Launch a responsive public TradingGoose Market landing page with an animated market globe, new branding assets and fonts, SEO metadata, structured data, and cached GitHub star counts.
  • Post billable Market API usage synchronously and return an error when usage reporting fails.
  • Remove the billing outbox implementation and generated database schema, including its table.
  • Adopt ESLint flat configuration and update landing-page dependencies and Tailwind tokens.
  • Add manual release generation and automatic staging reset 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 staging and reset staging to main after a release PR is merged.

Affected Areas

  • Admin UI / components
  • Auth / invitations / team management
  • Public API routes
  • Market API core / cache / Redis / rate limits / billing
  • Database / Drizzle / migrations
  • Uploads / storage providers
  • Plugins / install tooling
  • Email / external integrations
  • Config / env / deployment
  • Docs / repository metadata
  • Other: Public landing page, branding, metadata, and release automation

Validation

git diff --check main...HEAD
# Passed

bun run type-check
# Could not run: bun is not installed in the validation environment.

ESLINT_USE_FLAT_CONFIG=false npx eslint .
# Could not run: npx is not installed in the validation environment.

bun run lint
# Could not run: bun is not installed in the validation environment.

Rollout Notes

  • Run bun run db:migrate during deployment. Migration 0006_early_gargoyle drops market_billing_outbox and permanently removes any queued billing events.
  • Set NEXT_PUBLIC_APP_URL to the public origin at build time. Without it, canonical URLs, og:url, and JSON-LD structured data are intentionally omitted.
  • GITHUB_TOKEN or GITHUB_PAT is optional for the GitHub star endpoint but increases API reliability and rate limits. Failures fall back to 0.
  • Billable Market API requests now depend on successful usage reporting. A reporting failure returns the upstream status or 502 instead of billable response data.
  • The release workflow requires repository contents write permission.
  • After a PR into main is merged, the reset workflow force-pushes main to staging.
  • Application and workflow changes can be backed out by reverting this PR. Restoring the dropped billing outbox or its queued data requires a database backup or a new forward migration.

Screenshots / Video

Checklist

  • I reviewed my own diff
  • I documented validation results
  • I called out env, schema, cache, provider, and rollout impact
  • I did not manually edit generated files under */migrations/
  • I did not include secrets or private credentials

BruzWJ and others added 7 commits July 7, 2026 20:00
@greptile-apps

greptile-apps Bot commented Jul 17, 2026

Copy link
Copy Markdown

Greptile Summary

This PR launches the public TradingGoose Market landing page, hardens the Market API billing flow by switching from fire-and-forget to synchronous fail-closed usage reporting, drops the billing outbox table (with a destructive migration), and adds date-based release and staging-reset GitHub workflows.

  • Billing change (handler.ts): usage is now posted synchronously before the market data response is returned; a billing failure yields a 502 instead of data, closing the window where billable responses could be served without recording usage.
  • Landing page (app/(landing)/): full responsive page with animated globe, SEO metadata, JSON-LD structured data, and cached GitHub star counts; metadata tags are conditionally omitted when NEXT_PUBLIC_APP_URL is not a public origin.
  • Release workflow (.github/workflows/release.yml): generates date-tagged GitHub releases with categorised changelogs from merged staging PRs; missing --limit on gh pr list caps results at 30 and will silently truncate notes for larger releases.

Confidence Score: 3/5

Safe to merge the landing page and billing changes; the release workflow has a defect that will produce incomplete changelogs once PR volume exceeds 30 per release cycle.

The billing and landing-page changes look correct and well-guarded. The release workflow is new and self-contained, but the missing --limit flag means changelog generation is already wrong for any release with more than 30 merged PRs — the team won't notice until the notes are visibly short. The destructive migration is intentional and documented but irreversible, which warrants care at deploy time.

release.yml needs the --limit fix before the workflow is used for a busy release. packages/db/migrations/0006_early_gargoyle.sql should be verified against a backup plan before running in production.

Important Files Changed

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
Loading
%%{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
Loading

Fix All in Codex Fix All in Claude Code Fix All in Cursor

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

Comment on lines +41 to +47
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

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 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.

Fix in Codex Fix in Claude Code Fix in Cursor

Comment on lines +24 to +25
next: { revalidate: 3600 },
cache: "force-cache"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 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.

Suggested change
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!

Fix in Codex Fix in Claude Code Fix in Cursor

Comment on lines +5 to +9
const response = await fetch("/api/github-stars", {
headers: {
"Cache-Control": "max-age=3600"
}
});

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 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.

Fix in Codex Fix in Claude Code Fix in Cursor

@TradingGoose-Dev
TradingGoose-Dev merged commit fd46b37 into main Jul 17, 2026
6 of 7 checks passed
@TradingGoose-Dev
TradingGoose-Dev deleted the staging branch July 17, 2026 15:35
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants