Skip to content

feat(billing): expand schema — add BillingProvider, DevApiProject, nullable columns#133

Merged
eliteprox merged 12 commits into
mainfrom
pr1/expand-billing-schema
Feb 20, 2026
Merged

feat(billing): expand schema — add BillingProvider, DevApiProject, nullable columns#133
eliteprox merged 12 commits into
mainfrom
pr1/expand-billing-schema

Conversation

@eliteprox
Copy link
Copy Markdown
Contributor

@eliteprox eliteprox commented Feb 20, 2026

Summary

This PR expands the billing provider migration in an additive, non-breaking way. Existing code paths ignore all new fields, and prisma db push should produce only additive SQL (CREATE TABLE, ADD COLUMN, CREATE INDEX).

Changes

Schema (additive only)

  • Create public.BillingProvider (catalog of supported billing providers)
  • Create plugin_developer_api.DevApiProject
  • Add nullable columns to DevApiKey:
    • projectId
    • billingProviderId
    • keyLookupId
    • label
  • Add non-breaking indexes only (no FKs, no UNIQUE, no NOT NULL constraints)

Code

  • Add billing provider catalog data (billing-providers.ts) + seed + plugin-registry sync integration
  • Add read-only catalog endpoint: GET /api/v1/billing-providers
  • Add developer projects routes:
    • GET /api/v1/developer/projects
    • POST /api/v1/developer/projects
    • Implemented in web-next + plugin backend
  • Plugin backend includes in-memory fallback for billing-providers + projects
  • Dual-write during key creation:
    • always writes keyLookupId
    • optionally writes billingProviderId / projectId
    • optionally writes label
    • keeps all legacy fields intact

Safety / Compatibility

  • Existing required fields (projectName, keyHash, modelId, gatewayId) are unchanged
  • New columns are nullable with no constraints, so old code continues to work
  • Expected DB diff: CREATE TABLE + ADD COLUMN + CREATE INDEX only

Test plan

  • Run the following migration commands:
cd packages/database
npm run db:push
npm run db:seed
  • Run prisma db push locally; confirm only additive SQL (CREATE TABLE / ADD COLUMN / CREATE INDEX)
  • Verify existing key list/create/revoke flows work unchanged
  • GET /api/v1/billing-providers returns the daydream provider after seed
  • GET/POST /api/v1/developer/projects behaves correctly (web-next + plugin backend)

Summary by CodeRabbit

  • New Features

    • View available billing providers in the Developer UI.
    • Create and list developer projects from the Developer area.
    • API keys now include a unique lookup ID and can be associated with a project and billing provider.
  • Chores

    • Database schema extended to support billing providers, developer projects, and key associations; added seed/sync for providers.
    • Build now skips destructive DB sync outside production.

eliteprox and others added 5 commits February 20, 2026 15:48
Preview deployments share the production database. Running db push from
feature branches can apply destructive schema changes (column drops,
renames) to live data. Gate schema pushes on VERCEL_ENV=production so
the expand/contract migration pattern is enforced safely.

Co-authored-by: Cursor <cursoragent@cursor.com>
…expand columns

Expand phase of the billing migration:
- Create BillingProvider catalog table in public schema
- Create DevApiProject table in plugin_developer_api schema
- Add nullable projectId, billingProviderId, keyLookupId to DevApiKey
- Add non-breaking indexes only (no FKs, no UNIQUE, no NOT NULL)

Existing code and data are unaffected — all new columns are nullable
and no relations/constraints reference them yet.

Co-authored-by: Cursor <cursoragent@cursor.com>
- Add billing-providers.ts with provider catalog data (daydream)
- Add seed.ts with idempotent upsert for BillingProvider table
- Add billing provider sync to sync-plugin-registry.ts deploy step
- Export BILLING_PROVIDERS from @naap/database
- Add GET /api/v1/billing-providers read-only catalog endpoint

Co-authored-by: Cursor <cursoragent@cursor.com>
- Add GET/POST /api/v1/developer/projects (web-next route)
- Add GET /api/v1/developer/projects, POST /api/v1/developer/projects
  to plugin backend with in-memory fallback
- Add GET /api/v1/developer/billing-providers to plugin backend
  with in-memory fallback

These are new routes that don't affect existing key operations.

Co-authored-by: Cursor <cursoragent@cursor.com>
During key creation, also write keyLookupId (always generated) and
optionally billingProviderId/projectId if provided by the caller.
Old required fields (projectName, keyHash, modelId, gatewayId) are
still written and validated — this is additive dual-write only.

Co-authored-by: Cursor <cursoragent@cursor.com>
@vercel
Copy link
Copy Markdown
Contributor

vercel Bot commented Feb 20, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
naap-platform Ready Ready Preview, Comment Feb 20, 2026 6:24pm

Request Review

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Feb 20, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds BillingProvider and DevApiProject models, BILLING_PROVIDERS constant plus seed/sync logic, new Next.js API routes for billing providers and developer projects, expands DevApiKey with keyLookupId/billingProviderId/projectId, backend in-memory fallbacks, frontend billing-provider UI, and guards prisma db push in Vercel build.

Changes

Cohort / File(s) Summary
Prisma schema & DB model changes
packages/database/prisma/schema.prisma
Add BillingProvider model, add DevApiProject model, and extend DevApiKey with label, projectId, billingProviderId, keyLookupId plus new indexes.
Billing provider constants & exports
packages/database/src/billing-providers.ts, packages/database/src/index.ts
Add exported BILLING_PROVIDERS readonly constant and re-export from package index.
DB seed & registry sync
packages/database/prisma/seed.ts, bin/sync-plugin-registry.ts
Add seed script to upsert billing providers; extend registry sync to upsert BILLING_PROVIDERS into the DB.
Web API: billing providers
apps/web-next/src/app/api/v1/billing-providers/route.ts
New Next.js GET route returning enabled billing providers ordered by sortOrder.
Developer projects API
apps/web-next/src/app/api/v1/developer/projects/route.ts, plugins/developer-api/backend/src/server.ts
Add GET/POST endpoints to list/create developer projects with validation and uniqueness checks; backend includes in-memory fallback.
Developer API keys: creation flow
apps/web-next/src/app/api/v1/developer/keys/route.ts, plugins/developer-api/backend/src/server.ts
Key creation now generates keyLookupId, accepts optional billingProviderId and projectId, and persists these fields (Prisma and in-memory paths).
Frontend: developer view
plugins/developer-api/frontend/src/pages/DeveloperView.tsx
Load and display billing providers in DeveloperView; add provider list UI and empty-state handling.
Build script
bin/vercel-build.sh
Make prisma db push conditional on VERCEL_ENV=production.
Package metadata
packages/database/package.json
Add devDependencies: @types/node, ts-node.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  participant Client as Client
  participant API as Developer API / Next.js
  participant DB as Prisma DB
  rect rgba(200,200,255,0.5)
    Client->>API: POST /api/v1/developer/keys {payload, billingProviderId?, projectId?}
    API->>API: validate auth, CSRF, and payload
    API->>API: generateKeyLookupId()
    API->>DB: create DevApiKey (keyHash, keyPrefix, keyLookupId, billingProviderId?, projectId?, ...)
    DB-->>API: created key record
    API-->>Client: 200 Created (key info incl. keyLookupId)
  end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Suggested labels

scope/backend

Suggested reviewers

  • seanhanca
🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 13.33% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main schema expansion work: adding BillingProvider and DevApiProject entities and nullable columns to DevApiKey.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch pr1/expand-billing-schema

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions github-actions Bot added size/L Large PR (201-500 lines) scope/shell Shell app changes scope/packages Shared package changes has-migration Includes database migration scope/infra Infrastructure changes plugin/developer-api Developer API plugin and removed size/L Large PR (201-500 lines) labels Feb 20, 2026
@github-actions
Copy link
Copy Markdown

🗃️ Database Migration Detected

This PR includes changes to Prisma schema files. Please ensure:

  • Migration files have been generated (npx prisma migrate dev)
  • Migration is backward-compatible or a rollback plan exists
  • Data migration scripts are included if needed
  • The migration has been tested against a staging database

Requesting review from the core team: @livepeer/core

@github-actions
Copy link
Copy Markdown

github-actions Bot commented Feb 20, 2026

⚠️ This PR is very large (705 lines changed). Please split it into smaller, focused PRs if possible.

Comment thread packages/database/src/index.ts Outdated
Add optional label field for API keys. If no label is provided,
the key prefix will be used as the display name in the UI.

Co-authored-by: Cursor <cursoragent@cursor.com>
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (3)
bin/vercel-build.sh (1)

14-14: ⚠️ Potential issue | 🟡 Minor

Stale documentation: comment claims prisma db push always runs.

Line 14 states "prisma db push ... always run" but the new logic gates it to production only. Update the header comment to reflect the conditional behavior.

📝 Suggested fix
-#   - prisma db push and sync-plugin-registry always run (idempotent, ~5s + ~2s)
+#   - prisma db push runs on production only (idempotent, ~5s); sync-plugin-registry always runs (~2s)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@bin/vercel-build.sh` at line 14, Update the stale header comment in
bin/vercel-build.sh to reflect that "prisma db push" and "sync-plugin-registry"
no longer always run; they are now conditionally executed only in production
(the new logic gates them to production deployment). Edit the comment text to
mention the conditional/production-only behavior and keep the note about
idempotency and approximate runtime.
apps/web-next/src/app/api/v1/developer/keys/route.ts (1)

131-149: ⚠️ Potential issue | 🟠 Major

Validate billingProviderId and projectId before persisting.

These IDs come from the client and are stored without confirming provider existence/enabled state or project ownership. Unlike modelId and gatewayId (which are validated), these optional fields bypass checks entirely. If they're later used for billing or authorization scoping, a user could associate a key with arbitrary or foreign entities, creating data-integrity and authorization risks.

✅ Suggested guardrails
     const billingProviderId = typeof body.billingProviderId === 'string'
       ? body.billingProviderId.trim() || null
       : null;
     const projectId = typeof body.projectId === 'string'
       ? body.projectId.trim() || null
       : null;
+
+    if (billingProviderId) {
+      const provider = await prisma.billingProvider.findUnique({
+        where: { id: billingProviderId },
+        select: { id: true, enabled: true },
+      });
+      if (!provider || !provider.enabled) {
+        return errors.badRequest('Invalid billingProviderId');
+      }
+    }
+
+    if (projectId) {
+      const project = await prisma.devApiProject.findFirst({
+        where: { id: projectId, userId: user.id },
+        select: { id: true },
+      });
+      if (!project) {
+        return errors.badRequest('Invalid projectId');
+      }
+    }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/web-next/src/app/api/v1/developer/keys/route.ts` around lines 131 - 149,
Before calling prisma.devApiKey.create, validate the optional billingProviderId
and projectId the same way modelId and gateway are validated: if
billingProviderId is provided, fetch the corresponding BillingProvider (e.g.,
via prisma.billingProvider.findUnique) and ensure it exists and is
enabled/usable by this user or organization; if projectId is provided, fetch the
Project (e.g., via prisma.project.findUnique) and ensure it exists and is
owned/accessible by user.id (or the relevant org) and is in a valid state; if
validation fails, return an error (or set the field to null) instead of
persisting, and only pass the validated billingProviderId and projectId into
prisma.devApiKey.create.
plugins/developer-api/backend/src/server.ts (1)

432-444: ⚠️ Potential issue | 🟡 Minor

In-memory key creation missing new fields.

The Prisma path stores keyLookupId, billingProviderId, and projectId, but the in-memory fallback omits them. This creates behavioral inconsistency between modes and may cause issues during development/testing when these fields are expected.

🔧 Proposed fix to add missing fields
     const newKey = {
       id: `key-${Date.now()}`,
       userId,
       projectName,
       modelId,
       modelName: model.name,
       gatewayId,
       gatewayName: gateway.gatewayName,
       keyPrefix,
+      keyLookupId,
+      billingProviderId: billingProviderId || null,
+      projectId: projectId || null,
       status: 'active',
       createdAt: new Date().toISOString(),
       lastUsedAt: null,
     };
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@plugins/developer-api/backend/src/server.ts` around lines 432 - 444, The
in-memory API key object created as newKey in server.ts is missing fields that
Prisma stores (keyLookupId, billingProviderId, projectId); update the newKey
construction to include those properties (populate with the same values used for
DB mode or sensible defaults/nulls), ensuring keys returned in-memory mirror the
Prisma shape (reference newKey in server.ts and ensure any callers expecting
keyLookupId, billingProviderId, or projectId receive them).
🧹 Nitpick comments (2)
plugins/developer-api/backend/src/server.ts (2)

87-89: Consider using UUID for lookup IDs at scale.

The 8-byte (64-bit) random ID is sufficient for moderate volumes, but with the birthday paradox, collision probability reaches ~50% at ~4 billion keys. If this system scales significantly, consider using crypto.randomUUID() (128-bit) for stronger collision resistance.

💡 Optional: Use UUID for better collision resistance
 function generateKeyLookupId(): string {
-  return crypto.randomBytes(8).toString('hex');
+  return crypto.randomUUID();
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@plugins/developer-api/backend/src/server.ts` around lines 87 - 89, The
generateKeyLookupId function currently returns an 8-byte hex string via
crypto.randomBytes(8), which has weaker collision resistance at very large
scales; update generateKeyLookupId to use crypto.randomUUID() (or a UUID v4
generator) to produce 128-bit UUIDs for lookup IDs, replacing the
randomBytes-based implementation and keeping the function signature the same so
callers of generateKeyLookupId need no changes.

402-404: No validation for billingProviderId and projectId existence.

These IDs are stored without verifying they reference valid records. Since the PR explicitly avoids foreign keys, this may be intentional, but it allows storing references to non-existent billing providers or projects.

💡 Optional: Add existence checks for data integrity
+      if (billingProviderId) {
+        const provider = await prisma.billingProvider.findUnique({ where: { id: billingProviderId } });
+        if (!provider) return res.status(400).json({ error: 'Invalid billingProviderId' });
+      }
+
+      if (projectId) {
+        const project = await prisma.devApiProject.findFirst({ where: { id: projectId, userId } });
+        if (!project) return res.status(400).json({ error: 'Invalid projectId or project does not belong to user' });
+      }
+
       const newKey = await prisma.devApiKey.create({

Is deferring validation of billingProviderId and projectId intentional for this PR, with plans to add constraints in a subsequent PR?

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@plugins/developer-api/backend/src/server.ts` around lines 402 - 404, The code
is persisting billingProviderId and projectId (assigned alongside keyLookupId)
without verifying they reference existing records; update the flow that writes
these fields (look for the assignment using keyLookupId, billingProviderId,
projectId) to first check the referenced entities exist (e.g., query
BillingProvider and Project by the provided IDs) and reject the request or
return an error if either ID does not exist, or if you intentionally defer
validation add an explicit comment/TODO and a feature flag or request flag that
clearly documents the deliberate omission so callers and reviewers know
validation is postponed.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@plugins/developer-api/backend/src/server.ts`:
- Line 217: The in-memory fallback response currently returns unordered
projects; update the response that uses inMemoryProjects (the array filtered by
userId) so it is sorted the same way as Prisma: first by isDefault descending,
then by name ascending. After filtering by userId, apply a stable sort that
compares p.isDefault (true before false) and then compares p.name
lexicographically (case-insensitive if Prisma does), and return that sorted
array in the res.json call that currently references inMemoryProjects.filter((p:
any) => p.userId === userId).

---

Outside diff comments:
In `@apps/web-next/src/app/api/v1/developer/keys/route.ts`:
- Around line 131-149: Before calling prisma.devApiKey.create, validate the
optional billingProviderId and projectId the same way modelId and gateway are
validated: if billingProviderId is provided, fetch the corresponding
BillingProvider (e.g., via prisma.billingProvider.findUnique) and ensure it
exists and is enabled/usable by this user or organization; if projectId is
provided, fetch the Project (e.g., via prisma.project.findUnique) and ensure it
exists and is owned/accessible by user.id (or the relevant org) and is in a
valid state; if validation fails, return an error (or set the field to null)
instead of persisting, and only pass the validated billingProviderId and
projectId into prisma.devApiKey.create.

In `@bin/vercel-build.sh`:
- Line 14: Update the stale header comment in bin/vercel-build.sh to reflect
that "prisma db push" and "sync-plugin-registry" no longer always run; they are
now conditionally executed only in production (the new logic gates them to
production deployment). Edit the comment text to mention the
conditional/production-only behavior and keep the note about idempotency and
approximate runtime.

In `@plugins/developer-api/backend/src/server.ts`:
- Around line 432-444: The in-memory API key object created as newKey in
server.ts is missing fields that Prisma stores (keyLookupId, billingProviderId,
projectId); update the newKey construction to include those properties (populate
with the same values used for DB mode or sensible defaults/nulls), ensuring keys
returned in-memory mirror the Prisma shape (reference newKey in server.ts and
ensure any callers expecting keyLookupId, billingProviderId, or projectId
receive them).

---

Nitpick comments:
In `@plugins/developer-api/backend/src/server.ts`:
- Around line 87-89: The generateKeyLookupId function currently returns an
8-byte hex string via crypto.randomBytes(8), which has weaker collision
resistance at very large scales; update generateKeyLookupId to use
crypto.randomUUID() (or a UUID v4 generator) to produce 128-bit UUIDs for lookup
IDs, replacing the randomBytes-based implementation and keeping the function
signature the same so callers of generateKeyLookupId need no changes.
- Around line 402-404: The code is persisting billingProviderId and projectId
(assigned alongside keyLookupId) without verifying they reference existing
records; update the flow that writes these fields (look for the assignment using
keyLookupId, billingProviderId, projectId) to first check the referenced
entities exist (e.g., query BillingProvider and Project by the provided IDs) and
reject the request or return an error if either ID does not exist, or if you
intentionally defer validation add an explicit comment/TODO and a feature flag
or request flag that clearly documents the deliberate omission so callers and
reviewers know validation is postponed.

Comment thread plugins/developer-api/backend/src/server.ts Outdated
Removed the file extension from the import statement for BILLING_PROVIDERS in the database index file to align with module resolution best practices.
… in database package.json

- Added new dependencies including @cspotcode/source-map-support, create-require, diff, make-error, ts-node, and v8-compile-cache-lib to package-lock.json.
- Updated database package.json to include @types/node and ts-node as devDependencies, ensuring compatibility with TypeScript and Node.js development.
- Added several new devDependencies including @cspotcode/source-map-support, create-require, diff, make-error, ts-node, v8-compile-cache-lib, and yn to package-lock.json.
- Updated existing dependencies to ensure compatibility and enhance development capabilities.
@github-actions github-actions Bot removed the size/L Large PR (201-500 lines) label Feb 20, 2026
…play

- Added a new section in DeveloperView to display available billing providers for API key creation, including their details and an option to add custom providers.
- Implemented a new state to manage billing provider information and a function to fetch this data from the API.
- Updated the UI to include a usage dashboard alongside the billing providers section, improving the overall user experience.
- Implemented middleware to generate and attach a unique request ID to each request, enhancing traceability in logs.
- Updated the project retrieval endpoint to sort projects by default status and name, ensuring a more user-friendly response.
- Enhanced error handling to include request ID in error logs and responses, improving debugging capabilities.
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@plugins/developer-api/frontend/src/pages/DeveloperView.tsx`:
- Around line 63-70: The fetch in loadBillingProviders uses a relative path;
change it to use the same BASE_URL pattern as the other API calls (e.g., prepend
BASE_URL or getServiceOrigin('developer-api')) so requests go to the correct
developer-api origin; update the fetch in the loadBillingProviders function to
use BASE_URL + '/api/v1/billing-providers' (or the existing
getServiceOrigin('developer-api') value) and leave error handling and
setBillingProviders logic unchanged.

Comment thread plugins/developer-api/frontend/src/pages/DeveloperView.tsx
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
plugins/developer-api/backend/src/server.ts (1)

401-432: ⚠️ Potential issue | 🟠 Major

Validate optional billingProviderId and projectId before persisting.

These optional fields are accepted and stored without checking existence or ownership. The schema defines them as unrelated String fields with no foreign key constraints, allowing invalid references to be created. Additionally, the in-memory fallback diverges from the Prisma path by omitting keyLookupId, projectId, and billingProviderId entirely.

Required fixes:

  • Validate that projectId (if provided) exists and belongs to the caller
  • Validate that billingProviderId (if provided) exists and is enabled
  • Include all three missing fields in the in-memory fallback to maintain consistency
Suggested fix (Prisma path)
    const rawKey = generateApiKey();
    const keyHash = hashApiKey(rawKey);
    const keyPrefix = getKeyPrefix(rawKey);
    const keyLookupId = generateKeyLookupId();

    if (prisma) {
+      if (projectId) {
+        const project = await prisma.devApiProject.findFirst({
+          where: { id: projectId, userId },
+          select: { id: true },
+        });
+        if (!project) return res.status(400).json({ error: 'Invalid projectId' });
+      }
+
+      if (billingProviderId) {
+        const provider = await prisma.billingProvider.findFirst({
+          where: { id: billingProviderId, enabled: true },
+          select: { id: true },
+        });
+        if (!provider) return res.status(400).json({ error: 'Invalid billingProviderId' });
+      }
+
       const model = await prisma.devApiAIModel.findUnique({ where: { id: modelId } });
       if (!model) return res.status(400).json({ error: 'Invalid modelId' });
Suggested fix (in-memory parity)
    const newKey = {
      id: `key-${Date.now()}`,
      userId,
      projectName,
      modelId,
      modelName: model.name,
      gatewayId,
      gatewayName: gateway.gatewayName,
      keyPrefix,
+      keyLookupId,
+      billingProviderId: billingProviderId || null,
+      projectId: projectId || null,
       status: 'active',
       createdAt: new Date().toISOString(),
       lastUsedAt: null,
    };
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@plugins/developer-api/backend/src/server.ts` around lines 401 - 432, Validate
optional projectId and billingProviderId before persisting: when handling the
request in the same block that uses prisma (before prisma.devApiKey.create), if
projectId is provided query the project (e.g., prisma.project.findUnique or
findFirst) and ensure it exists and belongs to userId, otherwise return 400; if
billingProviderId is provided query the billing provider (e.g.,
prisma.billingProvider.findFirst) and ensure it exists and is enabled, otherwise
return 400; then include validated projectId and billingProviderId in the
prisma.devApiKey.create data (they may be null if not provided). Also make the
in-memory fallback store the same fields (keyLookupId, projectId,
billingProviderId) and perform the same validations/ownership checks before
adding to the in-memory store so behavior matches prisma.devApiKey.create and
the generated keyLookupId is persisted in both paths.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Outside diff comments:
In `@plugins/developer-api/backend/src/server.ts`:
- Around line 401-432: Validate optional projectId and billingProviderId before
persisting: when handling the request in the same block that uses prisma (before
prisma.devApiKey.create), if projectId is provided query the project (e.g.,
prisma.project.findUnique or findFirst) and ensure it exists and belongs to
userId, otherwise return 400; if billingProviderId is provided query the billing
provider (e.g., prisma.billingProvider.findFirst) and ensure it exists and is
enabled, otherwise return 400; then include validated projectId and
billingProviderId in the prisma.devApiKey.create data (they may be null if not
provided). Also make the in-memory fallback store the same fields (keyLookupId,
projectId, billingProviderId) and perform the same validations/ownership checks
before adding to the in-memory store so behavior matches prisma.devApiKey.create
and the generated keyLookupId is persisted in both paths.

coderabbitai[bot]
coderabbitai Bot previously approved these changes Feb 20, 2026
…d in-memory data

- Deleted the billing providers endpoint from the API, along with the in-memory billing providers data structure.
- This change streamlines the codebase by removing unused features, preparing for future enhancements.
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
plugins/developer-api/backend/src/server.ts (2)

390-404: ⚠️ Potential issue | 🟠 Major

Missing validation for projectId ownership.

The projectId is stored without verifying it belongs to the requesting user. A user could potentially associate their API key with another user's project by guessing/knowing project IDs.

🛡️ Proposed fix to validate project ownership
       if (!gatewayOffer) return res.status(400).json({ error: 'Gateway does not offer this model' });

+      if (projectId) {
+        const project = await prisma.devApiProject.findFirst({
+          where: { id: projectId, userId },
+        });
+        if (!project) {
+          return res.status(400).json({ error: 'Invalid projectId or project does not belong to user' });
+        }
+      }
+
       const newKey = await prisma.devApiKey.create({
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@plugins/developer-api/backend/src/server.ts` around lines 390 - 404, The code
inserts projectId into prisma.devApiKey.create without verifying ownership;
before calling prisma.devApiKey.create, if projectId is present you must verify
it belongs to the requesting user by querying Prisma (e.g.,
prisma.project.findUnique or prisma.project.findFirst) for the given projectId
and matching owner/userId (userId variable), and reject with an error (400/403)
if not found or not owned; only then call prisma.devApiKey.create with the
validated projectId. Ensure you use the same prisma instance and the existing
userId and projectId variables and fail fast when validation fails.

428-440: ⚠️ Potential issue | 🟡 Minor

In-memory fallback missing new key fields.

The in-memory key object doesn't include keyLookupId, billingProviderId, or projectId, which are persisted in the Prisma path. This creates inconsistent behavior between database and fallback modes.

🔧 Proposed fix to add missing fields
     const newKey = {
       id: `key-${Date.now()}`,
       userId,
       projectName,
       modelId,
       modelName: model.name,
       gatewayId,
       gatewayName: gateway.gatewayName,
       keyPrefix,
+      keyLookupId,
+      billingProviderId: billingProviderId || null,
+      projectId: projectId || null,
       status: 'active',
       createdAt: new Date().toISOString(),
       lastUsedAt: null,
     };
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@plugins/developer-api/backend/src/server.ts` around lines 428 - 440, The
in-memory fallback newKey is missing fields that the Prisma path persists;
update the newKey object (the const newKey = {...} in server.ts) to include
keyLookupId, billingProviderId, and projectId using the same source
variables/values used when creating the DB record (e.g., set keyLookupId to the
keyLookupId value used in the DB create, billingProviderId to billingProviderId,
and projectId to the project.id or projectId variable) so the in-memory and DB
representations are consistent.
🧹 Nitpick comments (2)
plugins/developer-api/backend/src/server.ts (2)

399-399: Consider validating billingProviderId exists.

While less critical than projectId, storing an invalid billingProviderId could cause downstream issues. Since BillingProvider is a catalog table, a quick existence check would improve data integrity.

🔧 Proposed fix to validate billing provider
+      if (billingProviderId) {
+        const provider = await prisma.billingProvider.findUnique({
+          where: { id: billingProviderId },
+        });
+        if (!provider) {
+          return res.status(400).json({ error: 'Invalid billingProviderId' });
+        }
+      }
+
       const newKey = await prisma.devApiKey.create({
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@plugins/developer-api/backend/src/server.ts` at line 399, Validate that
billingProviderId refers to an existing BillingProvider before saving: in the
handler that sets billingProviderId (where you assign billingProviderId:
billingProviderId || null) query the BillingProvider catalog (e.g., via the
BillingProvider repository/model or a findById/findOne method) and if the id
does not exist return a 4xx validation error (or reject the request) rather than
storing an invalid id; only set billingProviderId on the project when the lookup
succeeds. Use the symbols billingProviderId and the BillingProvider
repository/model to locate and implement this check.

284-286: In-memory duplicate check may differ from Prisma behavior.

The in-memory check uses exact string comparison (p.name === trimmedName), while Prisma's uniqueness check depends on database collation (often case-insensitive). This could allow duplicates in-memory that would fail in production.

🔧 Proposed fix for case-insensitive comparison
-    if (inMemoryProjects.find((p: any) => p.userId === userId && p.name === trimmedName)) {
+    if (inMemoryProjects.find((p: any) => p.userId === userId && p.name.toLowerCase() === trimmedName.toLowerCase())) {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@plugins/developer-api/backend/src/server.ts` around lines 284 - 286, The
in-memory duplicate check uses exact string equality (p.name === trimmedName)
which can differ from Prisma's typically case-insensitive behavior; update the
check in the project-creation handler that reads inMemoryProjects to compare
names case-insensitively and consistently normalized (e.g., compare
p.name?.trim().toLocaleLowerCase() === trimmedName.trim().toLocaleLowerCase(),
or use localeCompare with sensitivity: 'accent' set appropriately), and ensure
you guard against null/undefined p.name when referencing these properties.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Outside diff comments:
In `@plugins/developer-api/backend/src/server.ts`:
- Around line 390-404: The code inserts projectId into prisma.devApiKey.create
without verifying ownership; before calling prisma.devApiKey.create, if
projectId is present you must verify it belongs to the requesting user by
querying Prisma (e.g., prisma.project.findUnique or prisma.project.findFirst)
for the given projectId and matching owner/userId (userId variable), and reject
with an error (400/403) if not found or not owned; only then call
prisma.devApiKey.create with the validated projectId. Ensure you use the same
prisma instance and the existing userId and projectId variables and fail fast
when validation fails.
- Around line 428-440: The in-memory fallback newKey is missing fields that the
Prisma path persists; update the newKey object (the const newKey = {...} in
server.ts) to include keyLookupId, billingProviderId, and projectId using the
same source variables/values used when creating the DB record (e.g., set
keyLookupId to the keyLookupId value used in the DB create, billingProviderId to
billingProviderId, and projectId to the project.id or projectId variable) so the
in-memory and DB representations are consistent.

---

Nitpick comments:
In `@plugins/developer-api/backend/src/server.ts`:
- Line 399: Validate that billingProviderId refers to an existing
BillingProvider before saving: in the handler that sets billingProviderId (where
you assign billingProviderId: billingProviderId || null) query the
BillingProvider catalog (e.g., via the BillingProvider repository/model or a
findById/findOne method) and if the id does not exist return a 4xx validation
error (or reject the request) rather than storing an invalid id; only set
billingProviderId on the project when the lookup succeeds. Use the symbols
billingProviderId and the BillingProvider repository/model to locate and
implement this check.
- Around line 284-286: The in-memory duplicate check uses exact string equality
(p.name === trimmedName) which can differ from Prisma's typically
case-insensitive behavior; update the check in the project-creation handler that
reads inMemoryProjects to compare names case-insensitively and consistently
normalized (e.g., compare p.name?.trim().toLocaleLowerCase() ===
trimmedName.trim().toLocaleLowerCase(), or use localeCompare with sensitivity:
'accent' set appropriately), and ensure you guard against null/undefined p.name
when referencing these properties.

@eliteprox eliteprox requested a review from seanhanca February 20, 2026 18:27
@eliteprox
Copy link
Copy Markdown
Contributor Author

eliteprox commented Feb 20, 2026

@seanhanca This is ready for review now, includes all db changes, the only UI changes included are for the billing provider
image

Copy link
Copy Markdown
Contributor

@seanhanca seanhanca left a comment

Choose a reason for hiding this comment

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

approved, without any changes required.

What looks good

  • Schema changes are strictly additive (nullable columns, new tables, new indexes — no FK constraints, no NOT NULL)
  • Billing provider catalog with idempotent upsert in both seed.ts and sync-plugin-registry.ts
  • Project uniqueness enforced via @@unique([userId, name]) composite constraint
  • P2002 race condition handling in the web-next projects route
  • Frontend changes are purely additive (billing providers section in Usage tab)
  • Clear PR description with explicit test plan

@eliteprox eliteprox merged commit 1393de0 into main Feb 20, 2026
27 checks passed
@eliteprox eliteprox deleted the pr1/expand-billing-schema branch February 20, 2026 20:14
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

has-migration Includes database migration plugin/developer-api Developer API plugin scope/infra Infrastructure changes scope/packages Shared package changes scope/shell Shell app changes size/XL Extra large PR (500+ lines)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat(settings): add billing provider linking to NaaP

2 participants