feat(billing): expand schema — add BillingProvider, DevApiProject, nullable columns#133
Conversation
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>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
Note Reviews pausedIt 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 Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
📝 WalkthroughWalkthroughAdds 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
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
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Possibly related PRs
Suggested labels
Suggested reviewers
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches
🧪 Generate unit tests (beta)
Comment |
|
🗃️ Database Migration Detected This PR includes changes to Prisma schema files. Please ensure:
Requesting review from the core team: @livepeer/core |
|
|
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>
There was a problem hiding this comment.
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 | 🟡 MinorStale documentation: comment claims
prisma db pushalways 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 | 🟠 MajorValidate billingProviderId and projectId before persisting.
These IDs come from the client and are stored without confirming provider existence/enabled state or project ownership. Unlike
modelIdandgatewayId(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 | 🟡 MinorIn-memory key creation missing new fields.
The Prisma path stores
keyLookupId,billingProviderId, andprojectId, 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
billingProviderIdandprojectIdintentional 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.
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.
…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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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 | 🟠 MajorValidate 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, andbillingProviderIdentirely.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.
…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.
There was a problem hiding this comment.
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 | 🟠 MajorMissing validation for
projectIdownership.The
projectIdis 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 | 🟡 MinorIn-memory fallback missing new key fields.
The in-memory key object doesn't include
keyLookupId,billingProviderId, orprojectId, 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 validatingbillingProviderIdexists.While less critical than
projectId, storing an invalidbillingProviderIdcould cause downstream issues. SinceBillingProvideris 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.
|
@seanhanca This is ready for review now, includes all db changes, the only UI changes included are for the billing provider |
seanhanca
left a comment
There was a problem hiding this comment.
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

Summary
This PR expands the billing provider migration in an additive, non-breaking way. Existing code paths ignore all new fields, and
prisma db pushshould produce only additive SQL (CREATE TABLE, ADD COLUMN, CREATE INDEX).Changes
Schema (additive only)
public.BillingProvider(catalog of supported billing providers)plugin_developer_api.DevApiProjectDevApiKey:projectIdbillingProviderIdkeyLookupIdlabelCode
billing-providers.ts) + seed + plugin-registry sync integrationGET /api/v1/billing-providersGET /api/v1/developer/projectsPOST /api/v1/developer/projectskeyLookupIdbillingProviderId/projectIdlabelSafety / Compatibility
projectName,keyHash,modelId,gatewayId) are unchangedTest plan
prisma db pushlocally; confirm only additive SQL (CREATE TABLE / ADD COLUMN / CREATE INDEX)GET /api/v1/billing-providersreturns the daydream provider after seedGET/POST /api/v1/developer/projectsbehaves correctly (web-next + plugin backend)Summary by CodeRabbit
New Features
Chores