feat(api): migrate agent templates management endpoints#543
feat(api): migrate agent templates management endpoints#543arpitgupta1214 wants to merge 11 commits into
Conversation
Adds 5 dedicated endpoints for the chat → api migration (Group 7):
- GET /api/agent-templates
- POST /api/agent-templates
- PATCH /api/agent-templates/{id}
- DELETE /api/agent-templates/{id}
- PUT /api/agent-templates/{id}/favorite
Auth via validateAuthContext (x-api-key or Bearer). Ownership enforced
on PATCH/DELETE. GET list embeds the creator block (id/name/image/is_admin)
so callers no longer need a per-card lookup against the old /api/agent-creator
chat route.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.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:
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro Run ID: ⛔ Files ignored due to path filters (5)
📒 Files selected for processing (16)
🚧 Files skipped from review as they are similar to previous changes (11)
📝 WalkthroughWalkthroughThis PR implements a complete REST API for agent templates including list, create, update, delete, favorites management, and email-based sharing. It adds Supabase CRUD helpers, request validation with Zod schemas, authorization checks, handler orchestration, and Next.js route modules with CORS support and dynamic rendering configuration. ChangesAgent Templates CRUD API with Favorites & Sharing
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Possibly related PRs
Suggested reviewers
Poem
🚥 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 docstrings
🧪 Generate unit tests (beta)
Tip 💬 Introducing Slack Agent: The best way for teams to turn conversations into code.Slack Agent is built on CodeRabbit's deep understanding of your code, so your team can collaborate across the entire SDLC without losing context.
Built for teams:
One agent for your entire SDLC. Right inside Slack. Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
15 issues found across 36 files
Confidence score: 2/5
- Multiple high-confidence issues are user-impacting (security/error handling and data consistency), so merge risk is elevated rather than routine.
- The most severe finding is internal error detail leakage via
error.messagein 500 responses inlib/agent_templates/deleteAgentTemplateHandler.ts,lib/agent_templates/updateAgentTemplateHandler.ts,lib/agent_templates/toggleAgentTemplateFavoriteHandler.ts, andlib/agent_templates/createAgentTemplateHandler.ts; this should be replaced with a generic internal-error response. - Failure handling in
lib/supabase/agent_templates/selectAgentTemplate.ts,lib/supabase/agent_template_shares/deleteAgentTemplateShares.ts, andlib/supabase/agent_template_shares/insertAgentTemplateShares.tscan mask DB errors (false 404s/no-op success), and non-atomic share updates inlib/agent_templates/updateAgentTemplateHandler.tscan leave sharing state inconsistent. - Pay close attention to
lib/agent_templates/updateAgentTemplateHandler.ts,lib/supabase/agent_template_shares/deleteAgentTemplateShares.ts, andlib/supabase/agent_template_shares/insertAgentTemplateShares.ts- share updates need atomicity and explicit DB error propagation to avoid inconsistent permissions.
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="lib/supabase/agent_template_shares/insertAgentTemplateShares.ts">
<violation number="1" location="lib/supabase/agent_template_shares/insertAgentTemplateShares.ts:35">
P2: Do not swallow Supabase upsert errors by returning `0`; this masks real write failures as no-op success.</violation>
</file>
<file name="lib/supabase/agent_templates/selectAgentTemplate.ts">
<violation number="1" location="lib/supabase/agent_templates/selectAgentTemplate.ts:19">
P1: Returning `null` on Supabase error conflates DB failures with missing rows, causing PATCH/DELETE validation paths to return false 404s instead of internal errors.</violation>
</file>
<file name="lib/agent_templates/deleteAgentTemplateHandler.ts">
<violation number="1" location="lib/agent_templates/deleteAgentTemplateHandler.ts:39">
P1: Do not expose `error.message` in 500 responses; return a generic "Internal server error" string instead.
(Based on your team's feedback about preventing internal exception details from leaking in 500 responses.) [FEEDBACK_USED]</violation>
</file>
<file name="lib/agent_templates/toggleAgentTemplateFavoriteHandler.ts">
<violation number="1" location="lib/agent_templates/toggleAgentTemplateFavoriteHandler.ts:8">
P2: Custom agent: **API Design Consistency and Maintainability**
Mutation endpoint is exposed/documented as PUT instead of POST, conflicting with the API method convention required by the rule.</violation>
<violation number="2" location="lib/agent_templates/toggleAgentTemplateFavoriteHandler.ts:46">
P1: Do not return raw exception messages in 500 responses; always return a generic "Internal server error" string and keep full details only in server logs.
(Based on your team's feedback about never exposing raw exception text in 500 responses.) [FEEDBACK_USED]</violation>
</file>
<file name="lib/agent_templates/validateToggleFavoriteRequest.ts">
<violation number="1" location="lib/agent_templates/validateToggleFavoriteRequest.ts:35">
P2: Run `validateAuthContext` before body parsing/validation so unauthenticated requests consistently return 401.</violation>
</file>
<file name="lib/supabase/agent_template_shares/deleteAgentTemplateShares.ts">
<violation number="1" location="lib/supabase/agent_template_shares/deleteAgentTemplateShares.ts:17">
P1: Do not swallow delete failures by returning `false`; throw the DB error so callers cannot silently continue with inconsistent share updates.</violation>
</file>
<file name="lib/agent_templates/updateAgentTemplateHandler.ts">
<violation number="1" location="lib/agent_templates/updateAgentTemplateHandler.ts:50">
P1: Updating shares via delete-then-insert without a transaction can leave templates with no shares on partial failure. Make this operation atomic.</violation>
<violation number="2" location="lib/agent_templates/updateAgentTemplateHandler.ts:67">
P1: Do not return `error.message` in 500 responses; always return a generic "Internal server error" string.
(Based on your team's feedback about preventing internal error detail leakage in API 500 responses.) [FEEDBACK_USED]</violation>
</file>
<file name="lib/supabase/agent_templates/getAccessibleAgentTemplates.ts">
<violation number="1" location="lib/supabase/agent_templates/getAccessibleAgentTemplates.ts:95">
P2: Both queries are subject to Supabase's default 1000-row cap with no pagination. As the number of public templates grows, results will be silently truncated. Consider adding DB-side pagination (e.g., offset/limit passed from the caller) or, at minimum, a TODO documenting this constraint.
(Based on your team's feedback about preferring DB-side pagination over raising Supabase query limits.) [FEEDBACK_USED]</violation>
</file>
<file name="lib/supabase/agent_templates/getAgentTemplateWithDetails.ts">
<violation number="1" location="lib/supabase/agent_templates/getAgentTemplateWithDetails.ts:55">
P2: Returning `null` on query error masks DB failures; callers can respond `success` with `template: null` instead of surfacing an error.</violation>
</file>
<file name="lib/agent_templates/createAgentTemplateHandler.ts">
<violation number="1" location="lib/agent_templates/createAgentTemplateHandler.ts:62">
P1: Do not return raw exception messages in 500 responses; return a fixed "Internal server error" string and keep full details only in server logs.
(Based on your team's feedback about preventing internal error detail leakage in 500 responses.) [FEEDBACK_USED]</violation>
</file>
<file name="lib/agent_templates/__tests__/listAgentTemplatesHandler.test.ts">
<violation number="1" location="lib/agent_templates/__tests__/listAgentTemplatesHandler.test.ts:38">
P3: Custom agent: **Enforce Clear Code Style and Maintainability Practices**
Test fixture uses `any` instead of strict typing.</violation>
<violation number="2" location="lib/agent_templates/__tests__/listAgentTemplatesHandler.test.ts:49">
P2: Custom agent: **Flag AI Slop and Fabricated Changes**
New test does not verify the endpoint’s claimed embedded creator-block response shape.</violation>
</file>
<file name="lib/agent_templates/__tests__/createAgentTemplateHandler.test.ts">
<violation number="1" location="lib/agent_templates/__tests__/createAgentTemplateHandler.test.ts:57">
P2: Custom agent: **Enforce Clear Code Style and Maintainability Practices**
New test file exceeds the 100-line limit and uses `as any` casts, violating the repository's maintainability/type-safety rule.</violation>
</file>
Architecture diagram
sequenceDiagram
participant Client as External Client
participant Router as Next.js Router
participant CORS as CORS Middleware
participant Auth as validateAuthContext
participant Validator as Request Validator
participant Handler as Template Handler
participant DB as Supabase DB
Note over Client,DB: NEW: Agent Templates API Flow
alt GET /api/agent-templates
Client->>Router: GET with x-api-key / Bearer token
Router->>Auth: validateAuthContext()
alt Auth success
Auth-->>Router: accountId
Router->>DB: getAccessibleAgentTemplates(accountId)
Note over Router,DB: Fetches own + public + shared templates
DB-->>Router: enriched templates with creator block
Note over Router: Embeds: creator {id, name, image, is_admin}
Router-->>Client: 200 {status, templates}
else Auth failure
Auth-->>Router: 401 response
Router-->>Client: 401 Unauthorized
end
end
alt POST /api/agent-templates
Client->>Router: POST with JSON body
Router->>Validator: validateCreateAgentTemplateBody()
alt Body invalid
Validator-->>Router: 400 response
Router-->>Client: 400 error
else Body valid
Router->>Auth: validateAuthContext()
alt Auth failure
Auth-->>Router: 401 response
Router-->>Client: 401 Unauthorized
else Auth success
Router->>DB: insertAgentTemplate({title, description, prompt, tags, is_private, creator})
DB-->>Router: inserted template
opt is_private and share_emails present
Router->>DB: insertAgentTemplateShares(templateId, share_emails)
Note over Router,DB: Resolves emails to account_ids, upserts
end
Router->>DB: getAgentTemplateWithDetails(templateId, accountId)
DB-->>Router: enriched template
Router-->>Client: 201 {status, template}
end
end
end
alt PATCH /api/agent-templates/{id}
Client->>Router: PATCH with JSON body
Router->>Validator: validateUpdateAgentTemplateRequest()
Note over Validator: Checks: id format, body schema, auth, ownership
alt Validation fails
Validator-->>Router: 400/403/404 response
Router-->>Client: error
else Valid
Router->>DB: updateAgentTemplate(templateId, updates)
DB-->>Router: updated row
opt share_emails provided
Router->>DB: deleteAgentTemplateShares(templateId)
Router->>DB: insertAgentTemplateShares(templateId, share_emails)
end
Router->>DB: getAgentTemplateWithDetails(templateId, accountId)
DB-->>Router: enriched template
Router-->>Client: 200 {status, template}
end
end
alt DELETE /api/agent-templates/{id}
Client->>Router: DELETE
Router->>Validator: validateDeleteAgentTemplateRequest()
Note over Validator: Checks: id format, auth, ownership, existence
alt Validation fails
Validator-->>Router: 400/403/404 response
Router-->>Client: error
else Valid
Router->>DB: deleteAgentTemplate(templateId)
Note over Router,DB: Cascade deletes shares + favorites
DB-->>Router: success
Router-->>Client: 200 {status: "success"}
end
end
alt PUT /api/agent-templates/{id}/favorite
Client->>Router: PUT {is_favourite: boolean}
Router->>Validator: validateToggleFavoriteRequest()
alt Validation fails
Validator-->>Router: 400 response
Router-->>Client: 400 error
else Valid
alt is_favourite = true
Router->>DB: insertAgentTemplateFavorite(templateId, accountId)
Note over Router,DB: Idempotent - handles unique violation
else is_favourite = false
Router->>DB: deleteAgentTemplateFavorite(templateId, accountId)
end
DB-->>Router: success
Router-->>Client: 200 {status: "success"}
end
end
Note over Router: All endpoints include CORS headers via getCorsHeaders()
Note over Router: All handlers wrapped in try/catch returning 500 on error
Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.
| } | ||
|
|
||
| if (typeof body.share_emails !== "undefined") { | ||
| await deleteAgentTemplateShares(templateId); |
There was a problem hiding this comment.
P1: Updating shares via delete-then-insert without a transaction can leave templates with no shares on partial failure. Make this operation atomic.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At lib/agent_templates/updateAgentTemplateHandler.ts, line 50:
<comment>Updating shares via delete-then-insert without a transaction can leave templates with no shares on partial failure. Make this operation atomic.</comment>
<file context>
@@ -0,0 +1,72 @@
+ }
+
+ if (typeof body.share_emails !== "undefined") {
+ await deleteAgentTemplateShares(templateId);
+ if (body.share_emails.length > 0) {
+ await insertAgentTemplateShares(templateId, body.share_emails);
</file context>
| expect(res.status).toBe(200); | ||
| const body = await res.json(); | ||
| expect(body.status).toBe("success"); | ||
| expect(body.templates).toHaveLength(1); |
There was a problem hiding this comment.
P2: Custom agent: Flag AI Slop and Fabricated Changes
New test does not verify the endpoint’s claimed embedded creator-block response shape.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At lib/agent_templates/__tests__/listAgentTemplatesHandler.test.ts, line 49:
<comment>New test does not verify the endpoint’s claimed embedded creator-block response shape.</comment>
<file context>
@@ -0,0 +1,63 @@
+ expect(res.status).toBe(200);
+ const body = await res.json();
+ expect(body.status).toBe("success");
+ expect(body.templates).toHaveLength(1);
+ expect(getAccessibleAgentTemplates).toHaveBeenCalledWith(ACCOUNT_ID);
+ });
</file context>
| import { deleteAgentTemplateFavorite } from "@/lib/supabase/agent_template_favorites/deleteAgentTemplateFavorite"; | ||
|
|
||
| /** | ||
| * Handler for PUT /api/agent-templates/{id}/favorite. |
There was a problem hiding this comment.
P2: Custom agent: API Design Consistency and Maintainability
Mutation endpoint is exposed/documented as PUT instead of POST, conflicting with the API method convention required by the rule.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At lib/agent_templates/toggleAgentTemplateFavoriteHandler.ts, line 8:
<comment>Mutation endpoint is exposed/documented as PUT instead of POST, conflicting with the API method convention required by the rule.</comment>
<file context>
@@ -0,0 +1,51 @@
+import { deleteAgentTemplateFavorite } from "@/lib/supabase/agent_template_favorites/deleteAgentTemplateFavorite";
+
+/**
+ * Handler for PUT /api/agent-templates/{id}/favorite.
+ *
+ * Idempotently toggles the caller's favorite status for the template:
</file context>
| @@ -0,0 +1,107 @@ | |||
| import { describe, it, expect, vi, beforeEach } from "vitest"; | |||
There was a problem hiding this comment.
P2: Custom agent: Enforce Clear Code Style and Maintainability Practices
New test file exceeds the 100-line limit and uses as any casts, violating the repository's maintainability/type-safety rule.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At lib/agent_templates/__tests__/createAgentTemplateHandler.test.ts, line 57:
<comment>New test file exceeds the 100-line limit and uses `as any` casts, violating the repository's maintainability/type-safety rule.</comment>
<file context>
@@ -0,0 +1,107 @@
+ authToken: "k",
+ });
+
+ vi.mocked(insertAgentTemplate).mockResolvedValue({ id: TEMPLATE_ID } as any);
+ vi.mocked(insertAgentTemplateShares).mockResolvedValue(1);
+ vi.mocked(getAgentTemplateWithDetails).mockResolvedValue({
</file context>
| vi.mocked(getAccessibleAgentTemplates).mockResolvedValue([ | ||
| // Cast to match the enriched shape we expect downstream. | ||
|
|
||
| { id: "t1", title: "T", shared_emails: [] } as any, |
There was a problem hiding this comment.
P3: Custom agent: Enforce Clear Code Style and Maintainability Practices
Test fixture uses any instead of strict typing.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At lib/agent_templates/__tests__/listAgentTemplatesHandler.test.ts, line 38:
<comment>Test fixture uses `any` instead of strict typing.</comment>
<file context>
@@ -0,0 +1,63 @@
+ vi.mocked(getAccessibleAgentTemplates).mockResolvedValue([
+ // Cast to match the enriched shape we expect downstream.
+
+ { id: "t1", title: "T", shared_emails: [] } as any,
+ ]);
+
</file context>
Handler tests are the project convention (~62% coverage across the repo); route tests are anomalies (~11%). The route files are thin delegates to handlers — no test value beyond the handler tests already shipped. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Actionable comments posted: 8
🧹 Nitpick comments (8)
lib/supabase/agent_template_favorites/selectAgentTemplateFavorites.ts (1)
6-13: ⚡ Quick winUse
accountIdterminology in helper interfaces instead ofuserId.At Lines [6] and [9], rename
userIdtoaccountIdto stay consistent with the API/domain language. DB columnuser_idcan remain unchanged at Line [13].Proposed fix
-export async function selectAgentTemplateFavorites(userId: string): Promise<Set<string>> { +export async function selectAgentTemplateFavorites(accountId: string): Promise<Set<string>> { @@ - .eq("user_id", userId); + .eq("user_id", accountId);As per coding guidelines, "Use 'account' terminology, never 'entity' or 'user'; use specific names like 'artist', 'workspace', 'organization' when referring to specific types".
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@lib/supabase/agent_template_favorites/selectAgentTemplateFavorites.ts` around lines 6 - 13, Rename the parameter and local usage from userId to accountId in the selectAgentTemplateFavorites function signature and wherever it's referenced inside the function (keeping the return type Promise<Set<string>> unchanged); update the .eq call to pass accountId as the value while leaving the DB column name "user_id" unchanged so only the external API/helper terminology changes.lib/const.ts (1)
57-57: ⚡ Quick winMake
ADMIN_EMAILSimmutable to harden privilege checks.At Line [57], exporting a mutable
string[]allows accidental runtime mutation of admin privileges. Prefer a readonly constant.Proposed fix
-export const ADMIN_EMAILS: string[] = ["sidney+1@recoupable.com"]; +export const ADMIN_EMAILS = ["sidney+1@recoupable.com"] as const;🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@lib/const.ts` at line 57, ADMIN_EMAILS is exported as a mutable string[] which allows runtime mutation of admin privileges; change its declaration to an immutable type such as export const ADMIN_EMAILS: readonly string[] = ["sidney+1@recoupable.com"] or export const ADMIN_EMAILS = ["sidney+1@recoupable.com"] as const, and update any code that attempts to push/pop or reassign items to use non-mutating checks (e.g., includes) so privilege checks remain safe and compile-time readonly.lib/supabase/agent_template_favorites/deleteAgentTemplateFavorite.ts (1)
7-18: ⚡ Quick winAlign parameter naming with account terminology (
accountId).At Lines [7], [12], and [18], rename
userIdtoaccountIdfor consistency with the rest of the API model.Proposed fix
export async function deleteAgentTemplateFavorite( templateId: string, - userId: string, + accountId: string, ): Promise<boolean> { @@ - .eq("user_id", userId); + .eq("user_id", accountId);As per coding guidelines, "Use 'account' terminology, never 'entity' or 'user'; use specific names like 'artist', 'workspace', 'organization' when referring to specific types".
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@lib/supabase/agent_template_favorites/deleteAgentTemplateFavorite.ts` around lines 7 - 18, Rename the parameter and all references from userId to accountId in the deleteAgentTemplateFavorite function: update the JSDoc param name, the function signature (deleteAgentTemplateFavorite(templateId: string, accountId: string)), and replace any uses of userId (e.g. the second argument to .eq(...)) with accountId; also update the .eq column name to "account_id" if your DB uses that column (otherwise keep the existing column name but pass accountId). Ensure all occurrences in deleteAgentTemplateFavorite (including templateId/accountId variable usage) are updated consistently.lib/supabase/agent_template_favorites/insertAgentTemplateFavorite.ts (1)
11-17: ⚡ Quick winUse account terminology in the function contract.
Please rename
userIdtoaccountId(and map it touser_idonly at the DB payload boundary) for naming consistency across the codebase.♻️ Suggested rename
export async function insertAgentTemplateFavorite( templateId: string, - userId: string, + accountId: string, ): Promise<boolean> { const { error } = await supabase .from("agent_template_favorites") - .insert({ template_id: templateId, user_id: userId }) + .insert({ template_id: templateId, user_id: accountId })As per coding guidelines, "Use 'account' terminology, never 'entity' or 'user'".
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@lib/supabase/agent_template_favorites/insertAgentTemplateFavorite.ts` around lines 11 - 17, Rename the function parameter userId to accountId in insertAgentTemplateFavorite and update all internal references to use accountId, but when building the DB payload keep the column name user_id (i.e., insert({ template_id: templateId, user_id: accountId })); ensure the exported function signature and any callers are updated to the new accountId name for consistency with account terminology.lib/supabase/agent_templates/getAccessibleAgentTemplates.ts (2)
82-162: 🏗️ Heavy liftSplit
getAccessibleAgentTemplatesinto smaller focused steps.This function is doing querying, deduplication, enrichment, favorite computation, and response shaping in one block. Please split into smaller helpers to reduce cognitive load and make change-risk lower.
As per coding guidelines, "Flag functions longer than 20 lines" and "Apply Single Responsibility Principle (SRP): one exported function per file; each file should do one thing well".
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@lib/supabase/agent_templates/getAccessibleAgentTemplates.ts` around lines 82 - 162, getAccessibleAgentTemplates is doing multiple responsibilities (DB queries, deduplication, enrichment, and shaping) and should be split into small helpers; extract the owned/public query into fetchOwnedAndPublic(accountId), the share query into fetchSharedTemplates(accountId), the deduplication/merge logic into mergeTemplates(ownedAndPublic, sharedJoin) that returns a Map or array of AgentTemplateRowWithCreator, the enrichment steps into enrichWithFavouritesAndSharedEmails(templates, accountId) which calls selectAgentTemplateFavorites and getSharedEmailsByTemplateId, and a final mapToAgentTemplateWithDetails(templates, favourites, sharedEmails) that returns AgentTemplateWithDetails[]; ensure getAccessibleAgentTemplates orchestrates these helpers and only returns the final mapped array, preserving existing identifiers (ownedAndPublicSelect, selectAgentTemplateFavorites, getSharedEmailsByTemplateId, buildCreator) and error handling behavior.
176-200: ⚡ Quick winUse account terminology for local identifiers.
Please rename
userIds/emailsByUser/userEmailstoaccountIds/emailsByAccount/accountEmailsfor terminology consistency.As per coding guidelines, "Use 'account' terminology, never 'entity' or 'user'".
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@lib/supabase/agent_templates/getAccessibleAgentTemplates.ts` around lines 176 - 200, Rename the local identifiers to use "account" terminology: change userIds to accountIds, emailsByUser to emailsByAccount, and userEmails to accountEmails within the getAccessibleAgentTemplates logic where shares, supabase query, and result assembly occur; update the .in("account_id", userIds) call argument, the Map key/variable creation and lookups (emailsByAccount.get(share.user_id)), and the variable used when pushing into result[share.template_id] so all usages consistently use the new names.lib/agent_templates/validateUpdateAgentTemplateRequest.ts (1)
47-56: 💤 Low valueMisleading property name in error response.
The property
missing_fieldsis confusing because:
- For the refinement error ("At least one field must be provided"),
firstError.pathwill be an empty array[], not a list of missing fields- For field validation errors (e.g., string too short), the path indicates the invalid field, not a missing field
💡 Consider renaming for clarity
return NextResponse.json( { status: "error", - missing_fields: firstError.path, + field_path: firstError.path, error: firstError.message, }, { status: 400, headers: getCorsHeaders() }, );Or use a more descriptive structure:
{ status: "error", error: firstError.message, details: { path: firstError.path, code: firstError.code, } }🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@lib/agent_templates/validateUpdateAgentTemplateRequest.ts` around lines 47 - 56, The response property name `missing_fields` is misleading; update the error payload returned in the block that checks parsedBody.success (where you read parsedBody.error.issues[0]) to return a clearer structure — e.g., replace `missing_fields: firstError.path` with a `details` object that includes `path: firstError.path` and `code: firstError.code` (and keep `error: firstError.message` and status), and continue to use NextResponse.json with getCorsHeaders so callers can distinguish between empty paths (refinement errors) and invalid-field paths.lib/supabase/agent_templates/getAgentTemplateWithDetails.ts (1)
60-60: ⚡ Quick winType assertion bypasses type safety.
The double type assertion
as unknown as AgentTemplateRowWithCreatorcircumvents TypeScript's type checking. If the Supabase join structure changes or the query result differs from expectations, this will fail silently at runtime.🔒 Consider validating the shape before casting
const row = data as unknown as AgentTemplateRowWithCreator; + + // Validate the creator join returned expected structure + if (row.creator && typeof row.creator !== 'object') { + console.error('Unexpected creator structure in agent template query'); + return null; + }Alternatively, consider using Zod to validate the query result shape or use Supabase's type generation to infer the join type correctly.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@lib/supabase/agent_templates/getAgentTemplateWithDetails.ts` at line 60, The current double type assertion "as unknown as AgentTemplateRowWithCreator" in getAgentTemplateWithDetails.ts bypasses TypeScript safety; replace it with an explicit runtime shape check or validation (e.g., a Zod schema or a custom type guard) that verifies required fields exist on the Supabase response before mapping to AgentTemplateRowWithCreator, or switch the Supabase call to use generated types so the result is already correctly typed; update the code that assigns to the row variable to perform validation and only cast after successful validation, returning or throwing a clear error when the shape is invalid.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@lib/agent_templates/deleteAgentTemplateHandler.ts`:
- Around line 34-41: The catch block in deleteAgentTemplateHandler currently
returns the raw exception message to the client via
NextResponse.json(error.message); change this to always return a generic message
(e.g., "Internal server error") to the client and keep full error details in the
server log: ensure console.error (or your logger) logs the full error
object/stack and update the NextResponse.json payload to use a static generic
error string while preserving the 500 status and getCorsHeaders().
In `@lib/agent_templates/toggleAgentTemplateFavoriteHandler.ts`:
- Around line 41-48: In toggleAgentTemplateFavoriteHandler's catch block, stop
returning internal error details to clients: keep the console.error("[ERROR]
toggleAgentTemplateFavoriteHandler:", error) for logs but change the
NextResponse.json payload to use a generic message (e.g., "Internal server
error" or "An unexpected error occurred") instead of error.message; preserve the
500 status and getCorsHeaders() in the response.
In `@lib/agent_templates/updateAgentTemplateHandler.ts`:
- Line 56: The call to getAgentTemplateWithDetails(templateId, accountId) may
return null but the handler currently proceeds and returns template: null;
update the updateAgentTemplateHandler to check the returned value and handle the
null case explicitly (e.g., return a 404/appropriate error response or throw a
well-formed error) so the API contract is preserved; reference
getAgentTemplateWithDetails and the local variable template and ensure the
branch returns a clear error message/status instead of including a null template
in the success response.
- Around line 49-54: The share update block currently ignores return values from
deleteAgentTemplateShares and insertAgentTemplateShares; change it to check
their results (deleteAgentTemplateShares(templateId) returns boolean,
insertAgentTemplateShares(templateId, body.share_emails) returns number) and
fail the handler if either indicates failure: after calling
deleteAgentTemplateShares confirm it returned true (or handle false as an error)
and after insertAgentTemplateShares confirm the inserted count matches
body.share_emails.length (or non-zero as appropriate), and on failure return the
same error response pattern used for updateAgentTemplate earlier (or
throw/return an error status) so the overall handler does not claim success when
share operations failed.
In `@lib/agent_templates/validateToggleFavoriteRequest.ts`:
- Around line 28-57: The validator currently only checks params/body/auth;
modify validateToggleFavoriteRequest to also verify the template exists and the
caller is authorized before returning the validated object: after obtaining
validatedParams (validatedParams.id) and authResult (authResult.accountId) call
the appropriate data-access function (e.g., a repository or service that loads a
template by id) to confirm the template exists and belongs to or is accessible
by authResult.accountId, and if not return a NextResponse with a 404 or 403 JSON
error (with getCorsHeaders()); only return the validated object ({ templateId,
accountId, isFavourite }) when the existence and access checks succeed.
In `@lib/supabase/agent_template_shares/insertAgentTemplateShares.ts`:
- Around line 33-36: The error branch in insertAgentTemplateShares currently
returns 0, conflating DB failure with a legitimate no-op; change the error
handling in the block that checks the Supabase error (the `if (error) { ... }`
in insertAgentTemplateShares) to propagate the failure distinctly—either throw
the caught error (e.g., rethrow or wrap with a descriptive Error) or return a
discriminated result object (e.g., { success: false, inserted: 0, error })
instead of 0 so callers can reliably distinguish an actual failure from "nothing
inserted."
In `@lib/supabase/agent_templates/getAccessibleAgentTemplates.ts`:
- Around line 145-158: The mapping currently exposes shared_emails for any
private template; change it to only include shared_emails when the calling user
is the template owner or has admin rights: compute an isOwner flag (e.g.,
compare t.creator.id to the caller's userId passed into
getAccessibleAgentTemplates) and/or use an isAdmin boolean passed into the
function, then replace the shared_emails assignment so it returns
sharedEmailsByTemplate[t.id] only if t.is_private && (isOwner || isAdmin),
otherwise return [] (and keep non-private templates returning []). Ensure you
propagate the caller's userId/isAdmin into the scope where
privateIds/getSharedEmailsByTemplateId/ templates.map are used and reference
t.id, t.is_private, and t.creator consistently.
In `@lib/supabase/agent_templates/selectAgentTemplate.ts`:
- Around line 17-20: The function selectAgentTemplate currently treats DB query
errors the same as "not found" by returning null; change it to propagate errors
instead: inside selectAgentTemplate where it checks `if (error) { ... }`,
replace the console.error + `return null` with throwing a new Error (or
rethrowing the original) that includes context (e.g., `throw new
Error(\`selectAgentTemplate DB error: ${error.message}\`)`), so callers can
distinguish DB failures from a missing template and return 500 instead of 404.
---
Nitpick comments:
In `@lib/agent_templates/validateUpdateAgentTemplateRequest.ts`:
- Around line 47-56: The response property name `missing_fields` is misleading;
update the error payload returned in the block that checks parsedBody.success
(where you read parsedBody.error.issues[0]) to return a clearer structure —
e.g., replace `missing_fields: firstError.path` with a `details` object that
includes `path: firstError.path` and `code: firstError.code` (and keep `error:
firstError.message` and status), and continue to use NextResponse.json with
getCorsHeaders so callers can distinguish between empty paths (refinement
errors) and invalid-field paths.
In `@lib/const.ts`:
- Line 57: ADMIN_EMAILS is exported as a mutable string[] which allows runtime
mutation of admin privileges; change its declaration to an immutable type such
as export const ADMIN_EMAILS: readonly string[] = ["sidney+1@recoupable.com"] or
export const ADMIN_EMAILS = ["sidney+1@recoupable.com"] as const, and update any
code that attempts to push/pop or reassign items to use non-mutating checks
(e.g., includes) so privilege checks remain safe and compile-time readonly.
In `@lib/supabase/agent_template_favorites/deleteAgentTemplateFavorite.ts`:
- Around line 7-18: Rename the parameter and all references from userId to
accountId in the deleteAgentTemplateFavorite function: update the JSDoc param
name, the function signature (deleteAgentTemplateFavorite(templateId: string,
accountId: string)), and replace any uses of userId (e.g. the second argument to
.eq(...)) with accountId; also update the .eq column name to "account_id" if
your DB uses that column (otherwise keep the existing column name but pass
accountId). Ensure all occurrences in deleteAgentTemplateFavorite (including
templateId/accountId variable usage) are updated consistently.
In `@lib/supabase/agent_template_favorites/insertAgentTemplateFavorite.ts`:
- Around line 11-17: Rename the function parameter userId to accountId in
insertAgentTemplateFavorite and update all internal references to use accountId,
but when building the DB payload keep the column name user_id (i.e., insert({
template_id: templateId, user_id: accountId })); ensure the exported function
signature and any callers are updated to the new accountId name for consistency
with account terminology.
In `@lib/supabase/agent_template_favorites/selectAgentTemplateFavorites.ts`:
- Around line 6-13: Rename the parameter and local usage from userId to
accountId in the selectAgentTemplateFavorites function signature and wherever
it's referenced inside the function (keeping the return type
Promise<Set<string>> unchanged); update the .eq call to pass accountId as the
value while leaving the DB column name "user_id" unchanged so only the external
API/helper terminology changes.
In `@lib/supabase/agent_templates/getAccessibleAgentTemplates.ts`:
- Around line 82-162: getAccessibleAgentTemplates is doing multiple
responsibilities (DB queries, deduplication, enrichment, and shaping) and should
be split into small helpers; extract the owned/public query into
fetchOwnedAndPublic(accountId), the share query into
fetchSharedTemplates(accountId), the deduplication/merge logic into
mergeTemplates(ownedAndPublic, sharedJoin) that returns a Map or array of
AgentTemplateRowWithCreator, the enrichment steps into
enrichWithFavouritesAndSharedEmails(templates, accountId) which calls
selectAgentTemplateFavorites and getSharedEmailsByTemplateId, and a final
mapToAgentTemplateWithDetails(templates, favourites, sharedEmails) that returns
AgentTemplateWithDetails[]; ensure getAccessibleAgentTemplates orchestrates
these helpers and only returns the final mapped array, preserving existing
identifiers (ownedAndPublicSelect, selectAgentTemplateFavorites,
getSharedEmailsByTemplateId, buildCreator) and error handling behavior.
- Around line 176-200: Rename the local identifiers to use "account"
terminology: change userIds to accountIds, emailsByUser to emailsByAccount, and
userEmails to accountEmails within the getAccessibleAgentTemplates logic where
shares, supabase query, and result assembly occur; update the .in("account_id",
userIds) call argument, the Map key/variable creation and lookups
(emailsByAccount.get(share.user_id)), and the variable used when pushing into
result[share.template_id] so all usages consistently use the new names.
In `@lib/supabase/agent_templates/getAgentTemplateWithDetails.ts`:
- Line 60: The current double type assertion "as unknown as
AgentTemplateRowWithCreator" in getAgentTemplateWithDetails.ts bypasses
TypeScript safety; replace it with an explicit runtime shape check or validation
(e.g., a Zod schema or a custom type guard) that verifies required fields exist
on the Supabase response before mapping to AgentTemplateRowWithCreator, or
switch the Supabase call to use generated types so the result is already
correctly typed; update the code that assigns to the row variable to perform
validation and only cast after successful validation, returning or throwing a
clear error when the shape is invalid.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: e6e6d847-4346-4c47-8e4d-3ec7846d8758
⛔ Files ignored due to path filters (5)
lib/agent_templates/__tests__/createAgentTemplateHandler.test.tsis excluded by!**/*.test.*,!**/__tests__/**and included bylib/**lib/agent_templates/__tests__/deleteAgentTemplateHandler.test.tsis excluded by!**/*.test.*,!**/__tests__/**and included bylib/**lib/agent_templates/__tests__/listAgentTemplatesHandler.test.tsis excluded by!**/*.test.*,!**/__tests__/**and included bylib/**lib/agent_templates/__tests__/toggleAgentTemplateFavoriteHandler.test.tsis excluded by!**/*.test.*,!**/__tests__/**and included bylib/**lib/agent_templates/__tests__/updateAgentTemplateHandler.test.tsis excluded by!**/*.test.*,!**/__tests__/**and included bylib/**
📒 Files selected for processing (25)
app/api/agent-templates/[id]/favorite/route.tsapp/api/agent-templates/[id]/route.tsapp/api/agent-templates/route.tslib/agent_templates/createAgentTemplateHandler.tslib/agent_templates/deleteAgentTemplateHandler.tslib/agent_templates/listAgentTemplatesHandler.tslib/agent_templates/toggleAgentTemplateFavoriteHandler.tslib/agent_templates/updateAgentTemplateHandler.tslib/agent_templates/validateCreateAgentTemplateBody.tslib/agent_templates/validateDeleteAgentTemplateRequest.tslib/agent_templates/validateToggleFavoriteRequest.tslib/agent_templates/validateUpdateAgentTemplateRequest.tslib/const.tslib/supabase/agent_template_favorites/deleteAgentTemplateFavorite.tslib/supabase/agent_template_favorites/insertAgentTemplateFavorite.tslib/supabase/agent_template_favorites/selectAgentTemplateFavorites.tslib/supabase/agent_template_shares/deleteAgentTemplateShares.tslib/supabase/agent_template_shares/insertAgentTemplateShares.tslib/supabase/agent_template_shares/selectAgentTemplateShares.tslib/supabase/agent_templates/deleteAgentTemplate.tslib/supabase/agent_templates/getAccessibleAgentTemplates.tslib/supabase/agent_templates/getAgentTemplateWithDetails.tslib/supabase/agent_templates/insertAgentTemplate.tslib/supabase/agent_templates/selectAgentTemplate.tslib/supabase/agent_templates/updateAgentTemplate.ts
| } | ||
| } | ||
|
|
||
| const template = await getAgentTemplateWithDetails(templateId, accountId); |
There was a problem hiding this comment.
Potential null return not handled.
If getAgentTemplateWithDetails returns null (e.g., due to a database error or race condition where the template was deleted), the response will include template: null, which may not match the expected API contract.
🛡️ Handle null template scenario
const template = await getAgentTemplateWithDetails(templateId, accountId);
+
+ if (!template) {
+ return NextResponse.json(
+ { status: "error", error: "Failed to fetch updated template" },
+ { status: 500, headers: getCorsHeaders() },
+ );
+ }
return NextResponse.json(
{ status: "success", template },
{ status: 200, headers: getCorsHeaders() },
);📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| const template = await getAgentTemplateWithDetails(templateId, accountId); | |
| const template = await getAgentTemplateWithDetails(templateId, accountId); | |
| if (!template) { | |
| return NextResponse.json( | |
| { status: "error", error: "Failed to fetch updated template" }, | |
| { status: 500, headers: getCorsHeaders() }, | |
| ); | |
| } | |
| return NextResponse.json( | |
| { status: "success", template }, | |
| { status: 200, headers: getCorsHeaders() }, | |
| ); |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@lib/agent_templates/updateAgentTemplateHandler.ts` at line 56, The call to
getAgentTemplateWithDetails(templateId, accountId) may return null but the
handler currently proceeds and returns template: null; update the
updateAgentTemplateHandler to check the returned value and handle the null case
explicitly (e.g., return a 404/appropriate error response or throw a well-formed
error) so the API contract is preserved; reference getAgentTemplateWithDetails
and the local variable template and ensure the branch returns a clear error
message/status instead of including a null template in the success response.
P1 correctness: - Drop error.message from 500 responses across all 5 handlers; return a generic "Internal server error" string. Full error stays in server logs. - selectAgentTemplate, getAgentTemplateWithDetails, deleteAgentTemplateShares, insertAgentTemplateShares, getAccessibleAgentTemplates now throw on DB error instead of returning null/false/0/[]. Callers no longer conflate "DB failed" with "no row" (which caused false 404s and silent no-op success). - Privacy: shared_emails is now only returned to the template's creator. Previously any sharee could see who else the template was shared with. Acknowledged limitation: - PATCH share-update is still delete-then-insert without a Postgres-level transaction; comment in updateAgentTemplateHandler documents this. The helpers now throw on failure so the client at least gets a 500. Nits: - userId → accountId in the favorite helpers and internal vars in getAccessibleAgentTemplates, matching repo terminology convention. - ADMIN_EMAILS declared as readonly to harden the privilege list. - Added TODO comment for Supabase's 1000-row default cap on the templates query. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
1 issue found across 15 files (changes from recent commits).
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="lib/supabase/agent_templates/getAgentTemplateWithDetails.ts">
<violation number="1" location="lib/supabase/agent_templates/getAgentTemplateWithDetails.ts:55">
P2: Custom agent: **Flag AI Slop and Fabricated Changes**
JSDoc @returns claims null is returned on error, but implementation now throws</violation>
</file>
Tip: Review your code locally with the cubic CLI to iterate faster.
- One canonical SELECT + SDK-derived row type (no hand-written CreatorJoin) - Pure-data supabase functions: selectAgentTemplateById, selectOwnedAndPublicAgentTemplates, selectSharedAgentTemplates - Domain layer owns response shaping: buildAgentTemplateResponse, getAccessibleAgentTemplatesForAccount, getAgentTemplateForAccount, resolveSharedEmailsByTemplateId - Drop getAccessibleAgentTemplates, getAgentTemplateWithDetails, selectAgentTemplate (duplicated types, as-unknown-as casts, parallel buildCreator implementations) - Validators run auth before body parsing; toggle-favorite checks template visibility (own/public/shared) before mutating - Reuse selectAccountEmails instead of inlining account_emails queries - selectAgentTemplateFavorites returns rows; Set construction moves up Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
selectAgentTemplates({ id }) or selectAgentTemplates({ accessibleTo }) —
single function, single SELECT, single SDK-derived row type. Validators
take the [0] slice for single-row use.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
4 issues found across 18 files (changes from recent commits).
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="lib/agent_templates/__tests__/listAgentTemplatesHandler.test.ts">
<violation number="1" location="lib/agent_templates/__tests__/listAgentTemplatesHandler.test.ts:42">
P2: Custom agent: **Flag AI Slop and Fabricated Changes**
Updated test no longer validates the response contract for the migrated list endpoint.</violation>
</file>
<file name="lib/agent_templates/getAccessibleAgentTemplatesForAccount.ts">
<violation number="1" location="lib/agent_templates/getAccessibleAgentTemplatesForAccount.ts:39">
P2: `shared_emails` is still exposed to non-creators as an empty array; it should be omitted for non-owners.</violation>
</file>
<file name="lib/agent_templates/validateToggleFavoriteRequest.ts">
<violation number="1" location="lib/agent_templates/validateToggleFavoriteRequest.ts:65">
P1: Share lookup failures are converted into `403 Forbidden`, so shared users can be wrongly denied when the DB query errors.</violation>
</file>
<file name="lib/supabase/agent_templates/selectAgentTemplates.ts">
<violation number="1" location="lib/supabase/agent_templates/selectAgentTemplates.ts:83">
P2: The merged result loses alphabetical ordering. `ownedAndPublic` is fetched with `.order("title")`, but shared templates are appended after deduplication without re-sorting, so the returned array is only partially ordered. Sort the final array before returning to ensure a consistent response.</violation>
</file>
Tip: Review your code locally with the cubic CLI to iterate faster.
| const isOwner = creator?.id === accountId; | ||
| let canAccess = isOwner || !existing.is_private; | ||
| if (!canAccess) { | ||
| const shares = await selectAgentTemplateShares([templateId]); |
There was a problem hiding this comment.
P1: Share lookup failures are converted into 403 Forbidden, so shared users can be wrongly denied when the DB query errors.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At lib/agent_templates/validateToggleFavoriteRequest.ts, line 65:
<comment>Share lookup failures are converted into `403 Forbidden`, so shared users can be wrongly denied when the DB query errors.</comment>
<file context>
@@ -46,12 +47,34 @@ export async function validateToggleFavoriteRequest(
+ const isOwner = creator?.id === accountId;
+ let canAccess = isOwner || !existing.is_private;
+ if (!canAccess) {
+ const shares = await selectAgentTemplateShares([templateId]);
+ canAccess = shares.some(s => s.user_id === accountId);
+ }
</file context>
Tip: Review your code locally with the cubic CLI to iterate faster.
| if (t && !byId.has(t.id)) byId.set(t.id, t); | ||
| }); | ||
| }); | ||
| return Array.from(byId.values()); |
There was a problem hiding this comment.
P2: The merged result loses alphabetical ordering. ownedAndPublic is fetched with .order("title"), but shared templates are appended after deduplication without re-sorting, so the returned array is only partially ordered. Sort the final array before returning to ensure a consistent response.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At lib/supabase/agent_templates/selectAgentTemplates.ts, line 83:
<comment>The merged result loses alphabetical ordering. `ownedAndPublic` is fetched with `.order("title")`, but shared templates are appended after deduplication without re-sorting, so the returned array is only partially ordered. Sort the final array before returning to ensure a consistent response.</comment>
<file context>
@@ -0,0 +1,84 @@
+ if (t && !byId.has(t.id)) byId.set(t.id, t);
+ });
+ });
+ return Array.from(byId.values());
+}
</file context>
| return Array.from(byId.values()); | |
| return Array.from(byId.values()).sort((a, b) => | |
| (a.title ?? "").localeCompare(b.title ?? ""), | |
| ); |
There was a problem hiding this comment.
Actionable comments posted: 3
🧹 Nitpick comments (3)
lib/agent_templates/validateToggleFavoriteRequest.ts (3)
61-67: 💤 Low valueMinor:
creatornormalization is duplicated withgetAccessibleAgentTemplatesForAccount.ts.The same
Array.isArray(existing.creator) ? existing.creator[0] : existing.creatorshape-handling exists ingetAccessibleAgentTemplatesForAccount.tsascreatorIdOf(row). Worth hoisting that into a tiny shared util (e.g.,lib/agent_templates/normalizeCreator.tsor agetCreatorIdhelper alongsideagentTemplateWithCreatorSelect) so the Supabase relation-shape quirk lives in exactly one place. If the relation shape ever changes, you'll thank yourself.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@lib/agent_templates/validateToggleFavoriteRequest.ts` around lines 61 - 67, The creator normalization is duplicated; add a small shared helper (e.g., lib/agent_templates/getCreatorId.ts or normalizeCreator.ts) that exports a function like getCreatorId(creatorField) or creatorIdOf(row) which returns Array.isArray(x) ? x[0] : x and returns its id, then replace the inline normalization in validateToggleFavoriteRequest.ts (the existing.creator handling used to compute isOwner) with a call to that helper; also update getAccessibleAgentTemplatesForAccount.ts to use the same helper instead of duplicating the Array.isArray(...) logic so the relationship-shape quirk is handled centrally.
53-73: ⚡ Quick winConsider extracting the access-check into a reusable authorization helper (DRY).
The "load by id → ownership check → public check → shares fallback" sequence is exactly the kind of authorization gate that
updateAgentTemplateHandler/deleteAgentTemplateHandler(and any future endpoint) will also need. Per the coding guidelines on DRY and SRP, this is a good candidate for a single domain helper, e.g.lib/agent_templates/assertAccountCanAccessTemplate.ts, returning either the loaded template or aNextResponse404/403. That would also bringvalidateToggleFavoriteRequestback under the 50-line guideline (it currently sits at ~54 lines body), and consolidate theArray.isArray(creator)normalization withcreatorIdOfingetAccessibleAgentTemplatesForAccount.ts.Not blocking — purely a maintainability improvement.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@lib/agent_templates/validateToggleFavoriteRequest.ts` around lines 53 - 73, Extract the repeated "load by id → ownership → public → shares" logic into a reusable helper (e.g. assertAccountCanAccessTemplate) that accepts templateId and accountId, normalizes the creator (use the same creatorIdOf normalization as in getAccessibleAgentTemplatesForAccount), loads the template via selectAgentTemplateById and selectAgentTemplateShares as needed, and returns either the loaded template or a NextResponse (404 or 403) so callers can return it directly; then replace the inline block in validateToggleFavoriteRequest (and mirror in updateAgentTemplateHandler and deleteAgentTemplateHandler) to call assertAccountCanAccessTemplate and use the returned template when allowed.
10-12: ⚡ Quick winMigrate to Zod 4's unified
errorparameter;messageis deprecated.In Zod 4, the
messageparameter is deprecated in favor of a unifiederrorparameter. Additionally, the current message "is_favourite is required" will fire for both missing values and type mismatches (e.g.,is_favourite: "true"), which is misleading. Use the function form to clearly distinguish the two cases:Suggested refactor
export const toggleFavoriteBodySchema = z.object({ - is_favourite: z.boolean({ message: "is_favourite is required" }), + is_favourite: z.boolean({ + error: (issue) => + issue.input === undefined ? "is_favourite is required" : "is_favourite must be a boolean", + }), });🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@lib/agent_templates/validateToggleFavoriteRequest.ts` around lines 10 - 12, Update toggleFavoriteBodySchema to use Zod 4's unified error parameter instead of the deprecated message option: replace z.boolean({ message: ... }) with z.boolean({ error: (value) => { if (value === undefined) return { message: "is_favourite is required" }; return { message: "is_favourite must be a boolean" }; } }) so the schema (toggleFavoriteBodySchema) reports a clear "required" error when is_favourite is missing and a distinct "must be a boolean" error for type-mismatch cases.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@lib/agent_templates/createAgentTemplateHandler.ts`:
- Around line 48-53: The handler currently returns 201 with template possibly
undefined after calling getAgentTemplateForAccount; add a null-check after const
template = await getAgentTemplateForAccount(inserted.id, accountId) and handle
the failed-read case: either return a 500 response using NextResponse.json with
a clear message like "created but failed to fetch template" and include the
created entity (inserted) for debugging, or construct a minimal fallback payload
from inserted (e.g., id plus any known fields) and return that with 201; update
the code around the inserted and template variables and ensure headers use
getCorsHeaders() as before.
In `@lib/supabase/agent_template_favorites/selectAgentTemplateFavorites.ts`:
- Around line 18-21: selectAgentTemplateFavorites currently swallows DB errors
by returning [] which hides failures; change its error handling to log the error
and rethrow (or propagate) the original error instead of returning an empty
array so callers can distinguish DB failures from "no favorites"—modify the
error branch inside selectAgentTemplateFavorites to call process/error logger
with the error and then throw the error (or return a rejected promise)
consistent with other selectors like selectAgentTemplateById,
selectOwnedAndPublicAgentTemplates, and selectSharedAgentTemplates.
In `@lib/supabase/agent_templates/selectOwnedAndPublicAgentTemplates.ts`:
- Around line 11-12: selectOwnedAndPublicAgentTemplates currently fetches all
rows and risks silent truncation by Supabase's default 1000-row cap; change it
to a paginated query: add parameters (pageSize, cursor) to
selectOwnedAndPublicAgentTemplates, perform a cursor-based query ordered by a
stable column (e.g., created_at or id) with limit = pageSize + 1, fetch owned
and public templates using the same ordering, detect and return nextCursor when
more than pageSize rows are returned, and return only pageSize rows; ensure the
function returns { items, nextCursor } so callers can iterate pages.
---
Nitpick comments:
In `@lib/agent_templates/validateToggleFavoriteRequest.ts`:
- Around line 61-67: The creator normalization is duplicated; add a small shared
helper (e.g., lib/agent_templates/getCreatorId.ts or normalizeCreator.ts) that
exports a function like getCreatorId(creatorField) or creatorIdOf(row) which
returns Array.isArray(x) ? x[0] : x and returns its id, then replace the inline
normalization in validateToggleFavoriteRequest.ts (the existing.creator handling
used to compute isOwner) with a call to that helper; also update
getAccessibleAgentTemplatesForAccount.ts to use the same helper instead of
duplicating the Array.isArray(...) logic so the relationship-shape quirk is
handled centrally.
- Around line 53-73: Extract the repeated "load by id → ownership → public →
shares" logic into a reusable helper (e.g. assertAccountCanAccessTemplate) that
accepts templateId and accountId, normalizes the creator (use the same
creatorIdOf normalization as in getAccessibleAgentTemplatesForAccount), loads
the template via selectAgentTemplateById and selectAgentTemplateShares as
needed, and returns either the loaded template or a NextResponse (404 or 403) so
callers can return it directly; then replace the inline block in
validateToggleFavoriteRequest (and mirror in updateAgentTemplateHandler and
deleteAgentTemplateHandler) to call assertAccountCanAccessTemplate and use the
returned template when allowed.
- Around line 10-12: Update toggleFavoriteBodySchema to use Zod 4's unified
error parameter instead of the deprecated message option: replace z.boolean({
message: ... }) with z.boolean({ error: (value) => { if (value === undefined)
return { message: "is_favourite is required" }; return { message: "is_favourite
must be a boolean" }; } }) so the schema (toggleFavoriteBodySchema) reports a
clear "required" error when is_favourite is missing and a distinct "must be a
boolean" error for type-mismatch cases.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: da378481-1bf6-4000-8964-3f1aaeab9b5a
⛔ Files ignored due to path filters (3)
lib/agent_templates/__tests__/createAgentTemplateHandler.test.tsis excluded by!**/*.test.*,!**/__tests__/**and included bylib/**lib/agent_templates/__tests__/listAgentTemplatesHandler.test.tsis excluded by!**/*.test.*,!**/__tests__/**and included bylib/**lib/agent_templates/__tests__/updateAgentTemplateHandler.test.tsis excluded by!**/*.test.*,!**/__tests__/**and included bylib/**
📒 Files selected for processing (22)
lib/agent_templates/buildAgentTemplateResponse.tslib/agent_templates/createAgentTemplateHandler.tslib/agent_templates/deleteAgentTemplateHandler.tslib/agent_templates/getAccessibleAgentTemplatesForAccount.tslib/agent_templates/getAgentTemplateForAccount.tslib/agent_templates/listAgentTemplatesHandler.tslib/agent_templates/resolveSharedEmailsByTemplateId.tslib/agent_templates/toggleAgentTemplateFavoriteHandler.tslib/agent_templates/updateAgentTemplateHandler.tslib/agent_templates/validateDeleteAgentTemplateRequest.tslib/agent_templates/validateToggleFavoriteRequest.tslib/agent_templates/validateUpdateAgentTemplateRequest.tslib/const.tslib/supabase/agent_template_favorites/deleteAgentTemplateFavorite.tslib/supabase/agent_template_favorites/insertAgentTemplateFavorite.tslib/supabase/agent_template_favorites/selectAgentTemplateFavorites.tslib/supabase/agent_template_shares/deleteAgentTemplateShares.tslib/supabase/agent_template_shares/insertAgentTemplateShares.tslib/supabase/agent_templates/agentTemplateWithCreatorSelect.tslib/supabase/agent_templates/selectAgentTemplateById.tslib/supabase/agent_templates/selectOwnedAndPublicAgentTemplates.tslib/supabase/agent_templates/selectSharedAgentTemplates.ts
✅ Files skipped from review due to trivial changes (2)
- lib/supabase/agent_templates/agentTemplateWithCreatorSelect.ts
- lib/const.ts
🚧 Files skipped from review as they are similar to previous changes (7)
- lib/supabase/agent_template_favorites/insertAgentTemplateFavorite.ts
- lib/agent_templates/deleteAgentTemplateHandler.ts
- lib/supabase/agent_template_favorites/deleteAgentTemplateFavorite.ts
- lib/agent_templates/listAgentTemplatesHandler.ts
- lib/agent_templates/updateAgentTemplateHandler.ts
- lib/agent_templates/validateUpdateAgentTemplateRequest.ts
- lib/agent_templates/validateDeleteAgentTemplateRequest.ts
getAccessibleAgentTemplatesForAccount had a single caller and added nothing beyond a function call — fold its composition (selectAgentTemplates accessibleTo + favorites + shared emails for owned-private + buildAgentTemplateResponse) directly into listAgentTemplatesHandler. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
selectAgentTemplates now takes an optional forAccountId and returns rows fully shaped for the API: creator flattened with is_admin, is_favourite marked per the account, and shared_emails populated for owned-private templates. Validators omit the param so they skip the favorites/shares queries they don't need. Drops three helpers — buildAgentTemplateResponse, getAgentTemplateForAccount, resolveSharedEmailsByTemplateId — and the Array.isArray(creator) dance. Handlers shrink to one call each. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
2 issues found across 13 files (changes from recent commits).
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="lib/supabase/agent_templates/selectAgentTemplates.ts">
<violation number="1" location="lib/supabase/agent_templates/selectAgentTemplates.ts:165">
P1: Database failures in favorites/shares enrichment are swallowed, causing incorrect `is_favourite`/`shared_emails` values to be returned as successful data.</violation>
</file>
<file name="lib/agent_templates/__tests__/listAgentTemplatesHandler.test.ts">
<violation number="1" location="lib/agent_templates/__tests__/listAgentTemplatesHandler.test.ts:42">
P2: Custom agent: **Flag AI Slop and Fabricated Changes**
Updated test no longer validates the response shape for the agent-template list contract; it only checks array length, so the behavior change is not actually exercised.</violation>
</file>
Tip: Review your code locally with the cubic CLI to iterate faster.
| .filter(r => r.is_private && r.creator?.id === forAccountId) | ||
| .map(r => r.id); | ||
| const [favorites, sharedEmailsMap] = await Promise.all([ | ||
| selectAgentTemplateFavorites(forAccountId), |
There was a problem hiding this comment.
P1: Database failures in favorites/shares enrichment are swallowed, causing incorrect is_favourite/shared_emails values to be returned as successful data.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At lib/supabase/agent_templates/selectAgentTemplates.ts, line 165:
<comment>Database failures in favorites/shares enrichment are swallowed, causing incorrect `is_favourite`/`shared_emails` values to be returned as successful data.</comment>
<file context>
@@ -82,3 +126,51 @@ export async function selectAgentTemplates(
+ .filter(r => r.is_private && r.creator?.id === forAccountId)
+ .map(r => r.id);
+ const [favorites, sharedEmailsMap] = await Promise.all([
+ selectAgentTemplateFavorites(forAccountId),
+ resolveSharedEmails(ownedPrivateIds),
+ ]);
</file context>
Tip: Review your code locally with the cubic CLI to iterate faster.
| ); | ||
|
|
||
| expect(res.status).toBe(200); | ||
| expect((await res.json()).templates).toHaveLength(1); |
There was a problem hiding this comment.
P2: Custom agent: Flag AI Slop and Fabricated Changes
Updated test no longer validates the response shape for the agent-template list contract; it only checks array length, so the behavior change is not actually exercised.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At lib/agent_templates/__tests__/listAgentTemplatesHandler.test.ts, line 42:
<comment>Updated test no longer validates the response shape for the agent-template list contract; it only checks array length, so the behavior change is not actually exercised.</comment>
<file context>
@@ -13,81 +13,34 @@ vi.mock("@/lib/supabase/agent_templates/selectAgentTemplates", () => ({
- expect(body.templates[1].shared_emails).toEqual(["a@x.com"]);
- expect(selectAgentTemplates).toHaveBeenCalledWith({ accessibleTo: ACCOUNT_ID });
- expect(resolveSharedEmailsByTemplateId).toHaveBeenCalledWith(["t2"]);
+ expect((await res.json()).templates).toHaveLength(1);
+ expect(selectAgentTemplates).toHaveBeenCalledWith({ accessibleTo: ACCOUNT_ID }, ACCOUNT_ID);
});
</file context>
| expect((await res.json()).templates).toHaveLength(1); | |
| const body = await res.json(); | |
| expect(body.templates).toHaveLength(1); | |
| expect(body.templates[0]).toMatchObject({ | |
| id: "t1", | |
| is_favourite: true, | |
| shared_emails: [], | |
| }); |
Smoke test ✅Ran against the Vercel preview with the test API key. All 16 scenarios pass.
Notable behaviors verified end-to-end:
|
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (2)
lib/agent_templates/validateDeleteAgentTemplateRequest.ts (1)
12-19: ⚡ Quick winConsider adding JSDoc comment.
The function would benefit from a JSDoc comment describing its behavior, parameters, and return value pattern (returns validated data or error response). As per coding guidelines, Supabase-related functions should follow documented patterns including JSDoc comments.
📝 Suggested JSDoc
+/** + * Validates DELETE /api/agent-templates/{id} request. + * Checks authentication, validates ID format, verifies template exists, + * and ensures the authenticated account is the template's creator. + * + * `@param` request - The Next.js request object + * `@param` id - The template ID from the route parameter + * `@returns` Validated request data or a NextResponse with error details + */ export async function validateDeleteAgentTemplateRequest( request: NextRequest, id: string,🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@lib/agent_templates/validateDeleteAgentTemplateRequest.ts` around lines 12 - 19, Add a JSDoc block above the validateDeleteAgentTemplateRequest function describing its purpose (validates DELETE /api/agent-templates/{id} including auth, id format, and creator check), list parameters (request: NextRequest, id: string), and the return type behavior (Promise resolving to ValidatedDeleteAgentTemplateRequest on success or NextResponse error on failure); mention any thrown/handled error cases and link to Supabase-related patterns used by this code so callers know it returns either validated data or an error response.lib/agent_templates/validateUpdateAgentTemplateRequest.ts (1)
30-37: ⚡ Quick winConsider adding JSDoc comment.
Following the same pattern as the delete validator, this function would benefit from a JSDoc comment documenting its validation steps and return behavior. As per coding guidelines, functions should be well-documented.
📝 Suggested JSDoc
+/** + * Validates PATCH /api/agent-templates/{id} request. + * Checks authentication, validates ID format and request body, + * verifies template exists, and ensures the authenticated account + * is the template's creator. + * + * `@param` request - The Next.js request object + * `@param` id - The template ID from the route parameter + * `@returns` Validated request data (including parsed body) or a NextResponse with error details + */ export async function validateUpdateAgentTemplateRequest( request: NextRequest, id: string,🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@lib/agent_templates/validateUpdateAgentTemplateRequest.ts` around lines 30 - 37, Add a JSDoc block above the validateUpdateAgentTemplateRequest function that mirrors the style of the delete validator: document the HTTP method and route, list the validation steps (auth check, id format validation, request body validation, and creator authorization), describe the return types (ValidatedUpdateAgentTemplateRequest on success or NextResponse on failure), and note any thrown errors or side effects; reference the function name validateUpdateAgentTemplateRequest and ensure the comment is concise and follows existing project JSDoc conventions.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@lib/agent_templates/validateUpdateAgentTemplateRequest.ts`:
- Around line 46-56: The response key `missing_fields` is misleading for Zod
validation failures; in the error branch where parsedBody.success is false (use
of parsedBody and firstError from parsedBody.error.issues) change the payload
key from `missing_fields` to a clearer name such as `field` or `validation_path`
(e.g., return field: firstError.path or validation_path: firstError.path) so the
API conveys that this is the validation path, not only missing fields; update
the NextResponse.json call accordingly in validateUpdateAgentTemplateRequest.ts.
---
Nitpick comments:
In `@lib/agent_templates/validateDeleteAgentTemplateRequest.ts`:
- Around line 12-19: Add a JSDoc block above the
validateDeleteAgentTemplateRequest function describing its purpose (validates
DELETE /api/agent-templates/{id} including auth, id format, and creator check),
list parameters (request: NextRequest, id: string), and the return type behavior
(Promise resolving to ValidatedDeleteAgentTemplateRequest on success or
NextResponse error on failure); mention any thrown/handled error cases and link
to Supabase-related patterns used by this code so callers know it returns either
validated data or an error response.
In `@lib/agent_templates/validateUpdateAgentTemplateRequest.ts`:
- Around line 30-37: Add a JSDoc block above the
validateUpdateAgentTemplateRequest function that mirrors the style of the delete
validator: document the HTTP method and route, list the validation steps (auth
check, id format validation, request body validation, and creator
authorization), describe the return types (ValidatedUpdateAgentTemplateRequest
on success or NextResponse on failure), and note any thrown errors or side
effects; reference the function name validateUpdateAgentTemplateRequest and
ensure the comment is concise and follows existing project JSDoc conventions.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: 120b0f79-b46a-4745-a5c8-ee4faf31939f
⛔ Files ignored due to path filters (3)
lib/agent_templates/__tests__/createAgentTemplateHandler.test.tsis excluded by!**/*.test.*,!**/__tests__/**and included bylib/**lib/agent_templates/__tests__/listAgentTemplatesHandler.test.tsis excluded by!**/*.test.*,!**/__tests__/**and included bylib/**lib/agent_templates/__tests__/updateAgentTemplateHandler.test.tsis excluded by!**/*.test.*,!**/__tests__/**and included bylib/**
📒 Files selected for processing (7)
lib/agent_templates/createAgentTemplateHandler.tslib/agent_templates/listAgentTemplatesHandler.tslib/agent_templates/updateAgentTemplateHandler.tslib/agent_templates/validateDeleteAgentTemplateRequest.tslib/agent_templates/validateToggleFavoriteRequest.tslib/agent_templates/validateUpdateAgentTemplateRequest.tslib/supabase/agent_templates/selectAgentTemplates.ts
🚧 Files skipped from review as they are similar to previous changes (4)
- lib/agent_templates/listAgentTemplatesHandler.ts
- lib/agent_templates/createAgentTemplateHandler.ts
- lib/agent_templates/validateToggleFavoriteRequest.ts
- lib/agent_templates/updateAgentTemplateHandler.ts
URL: /api/agent-templates → /api/templates
Folders: lib/agent_templates → lib/templates,
lib/supabase/agent_templates → lib/supabase/templates,
lib/supabase/agent_template_{favorites,shares} →
lib/supabase/template_{favorites,shares}
Identifiers: AgentTemplate* → Template*,
selectAgentTemplate* → selectTemplate*, etc.
DB table strings (.from("agent_templates"), Tables<"agent_template_*">)
and FK names (agent_templates_creator_fkey,
agent_template_shares_template_id_fkey) preserved — those mirror the
actual schema in the database submodule.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Folder: api-reference/agent-templates/ → api-reference/templates/
OpenAPI: openapi/agent-templates.json → openapi/templates.json
URL prefix: /api/agents/templates → /api/templates (matches api PR
recoupable/api#543; both use /api/templates throughout)
Schemas: AgentTemplate* → Template*
Page titles + descriptions: "agent template" → "template"
docs.json: navigation paths updated to api-reference/templates/*
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
URL: /api/templates → /api/agents/templates
Route folder: app/api/templates → app/api/agents/templates
Lib structure unchanged (lib/templates, lib/supabase/templates,
lib/supabase/template_{favorites,shares}) — those are code-organization
folders, not URL paths.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
URL: /api/templates → /api/agents/templates in OpenAPI paths and MDX frontmatter. Tracks the api PR (recoupable/api#543). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Actionable comments posted: 4
♻️ Duplicate comments (2)
lib/supabase/template_favorites/selectTemplateFavorites.ts (1)
18-21:⚠️ Potential issue | 🟠 Major | ⚡ Quick winPropagate DB failures instead of returning an empty favorites list.
Returning
[]on Line 20 makes “query failed” indistinguishable from “no favourites,” which can silently produce incorrectis_favouritestates. Throw here (or return a discriminated failure result) so upstream can return 500 consistently.As per coding guidelines, "Handle errors gracefully" and for Supabase operations "Handle errors gracefully".
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@lib/supabase/template_favorites/selectTemplateFavorites.ts` around lines 18 - 21, In selectTemplateFavorites, do not return an empty array when Supabase returns an error; instead propagate the failure by throwing the error (or a wrapped Error with context) from the error-handling block that currently logs "Error selecting template_favorites:" so upstream can respond with 500; locate the if (error) { ... } branch in selectTemplateFavorites, include the original error details in the thrown error (or throw a new Error that includes both a descriptive message and the Supabase error) so callers can distinguish DB failures from an empty favorites list.lib/templates/validateUpdateTemplateRequest.ts (1)
46-56: 🛠️ Refactor suggestion | 🟠 Major | ⚡ Quick winRename
missing_fieldsto better reflect validation error scope.The response key
missing_fieldsis misleading for Zod validation failures, which can include type mismatches, format violations, length constraints, and more—not just missing fields. Consider renaming tofieldorvalidation_pathfor accuracy.🔧 Proposed fix
return NextResponse.json( { status: "error", - missing_fields: firstError.path, + field: firstError.path, error: firstError.message, }, { status: 400, headers: getCorsHeaders() }, );🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@lib/templates/validateUpdateTemplateRequest.ts` around lines 46 - 56, The response currently returns Zod failures under the misleading key missing_fields; update the JSON payload created in the NextResponse.json call to use a clearer name (e.g., validation_path or field) instead of missing_fields so it accurately represents any Zod issue type; modify the object returned in the error branch that references parsedBody, firstError, and NextResponse.json to rename missing_fields to the chosen key and keep the value firstError.path unchanged.
🧹 Nitpick comments (1)
lib/supabase/templates/selectTemplates.ts (1)
80-128: 🏗️ Heavy liftBreak down oversized query/shape functions into smaller units.
fetchRawandselectTemplatesexceed the 20-line guideline and are accumulating multiple responsibilities (querying, deduping, enrichment composition). Consider splitting into narrower helpers (e.g., accessible-query assembly, shared merge, enrichment mapping) to reduce coupling and future regression risk.As per coding guidelines, "Flag functions longer than 20 lines" and "Keep functions small and focused".
Also applies to: 145-176
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@lib/supabase/templates/selectTemplates.ts` around lines 80 - 128, fetchRaw is doing too many things (building queries, executing two different queries, error handling, and deduping) and should be split into smaller focused helpers; extract the accessible-query assembly (e.g., buildAccessibleTemplatesQuery(accountId)), the shared-templates fetch (e.g., fetchSharedTemplates(accountId)), and the merge/dedupe logic (e.g., mergeTemplatesById(ownedAndPublicRows, sharedRows)) and have fetchRaw orchestrate these helpers and return the merged result. Move the SELECT and supabase call details into the two small functions (one that queries agent_templates with .or(...) and .order("title") and one that queries agent_template_shares with the nested template select), centralize repeated error handling into a helper or consistent pattern, and replace the inline dedupe block with mergeTemplatesById to keep fetchRaw under the 20-line guideline while maintaining identical behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@lib/supabase/template_shares/selectTemplateShares.ts`:
- Around line 20-23: The function selectTemplateShares currently swallows
Supabase errors by logging and returning [] when "error" is truthy; instead,
propagate the failure to callers so request handlers can return proper 500s.
Modify selectTemplateShares to throw the Supabase error (or return a
Result/Error object) rather than returning an empty array—use the existing
"error" value from the supabase query, remove the silent fallback that returns
[] and rethrow or reject with that error so callers of selectTemplateShares can
handle and surface a 500 response.
In `@lib/templates/createTemplateHandler.ts`:
- Around line 48-53: After calling selectTemplates and destructuring const
[template], add a null/undefined check for template (in the create template
handler) and if it's missing return an error response instead of a 201: log the
failure (include inserted.id and accountId), and return NextResponse.json({
status: "error", message: "Created template not found" }, { status: 500,
headers: getCorsHeaders() }); otherwise continue to return the 201 success
response with the template. This uses the existing selectTemplates call and the
template variable to detect the race/permission failure and avoids returning a
201 with a null body.
In `@lib/templates/validateCreateTemplateBody.ts`:
- Line 7: The Zod schema in validateCreateTemplateBody.ts uses the deprecated
.string({ message: "..." }) option; update each primitive schema customization
(the .string(...) calls in this file) to use the correct parameter by replacing
message with error (e.g., .string({ error: "title is required" })) or simply
pass the message as a positional argument (e.g., z.string("title is required")),
and apply the same change for all occurrences currently using { message: ... }
so the Zod v4.1.13 API is used correctly.
In `@lib/templates/validateToggleFavoriteRequest.ts`:
- Around line 10-12: The boolean schema in toggleFavoriteBodySchema uses the
removed `message` option; update the z.boolean call to use Zod v4's `error`
option instead (replace the `message` parameter with an `error` object
containing the same message), so the schema becomes valid under Zod v4 while
preserving the "is_favourite is required" text.
---
Duplicate comments:
In `@lib/supabase/template_favorites/selectTemplateFavorites.ts`:
- Around line 18-21: In selectTemplateFavorites, do not return an empty array
when Supabase returns an error; instead propagate the failure by throwing the
error (or a wrapped Error with context) from the error-handling block that
currently logs "Error selecting template_favorites:" so upstream can respond
with 500; locate the if (error) { ... } branch in selectTemplateFavorites,
include the original error details in the thrown error (or throw a new Error
that includes both a descriptive message and the Supabase error) so callers can
distinguish DB failures from an empty favorites list.
In `@lib/templates/validateUpdateTemplateRequest.ts`:
- Around line 46-56: The response currently returns Zod failures under the
misleading key missing_fields; update the JSON payload created in the
NextResponse.json call to use a clearer name (e.g., validation_path or field)
instead of missing_fields so it accurately represents any Zod issue type; modify
the object returned in the error branch that references parsedBody, firstError,
and NextResponse.json to rename missing_fields to the chosen key and keep the
value firstError.path unchanged.
---
Nitpick comments:
In `@lib/supabase/templates/selectTemplates.ts`:
- Around line 80-128: fetchRaw is doing too many things (building queries,
executing two different queries, error handling, and deduping) and should be
split into smaller focused helpers; extract the accessible-query assembly (e.g.,
buildAccessibleTemplatesQuery(accountId)), the shared-templates fetch (e.g.,
fetchSharedTemplates(accountId)), and the merge/dedupe logic (e.g.,
mergeTemplatesById(ownedAndPublicRows, sharedRows)) and have fetchRaw
orchestrate these helpers and return the merged result. Move the SELECT and
supabase call details into the two small functions (one that queries
agent_templates with .or(...) and .order("title") and one that queries
agent_template_shares with the nested template select), centralize repeated
error handling into a helper or consistent pattern, and replace the inline
dedupe block with mergeTemplatesById to keep fetchRaw under the 20-line
guideline while maintaining identical behavior.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: ea0fbfec-b327-4100-8072-3a68fe9a9ca0
⛔ Files ignored due to path filters (5)
lib/templates/__tests__/createTemplateHandler.test.tsis excluded by!**/*.test.*,!**/__tests__/**and included bylib/**lib/templates/__tests__/deleteTemplateHandler.test.tsis excluded by!**/*.test.*,!**/__tests__/**and included bylib/**lib/templates/__tests__/listTemplatesHandler.test.tsis excluded by!**/*.test.*,!**/__tests__/**and included bylib/**lib/templates/__tests__/toggleTemplateFavoriteHandler.test.tsis excluded by!**/*.test.*,!**/__tests__/**and included bylib/**lib/templates/__tests__/updateTemplateHandler.test.tsis excluded by!**/*.test.*,!**/__tests__/**and included bylib/**
📒 Files selected for processing (23)
app/api/templates/[id]/favorite/route.tsapp/api/templates/[id]/route.tsapp/api/templates/route.tslib/const.tslib/supabase/template_favorites/deleteTemplateFavorite.tslib/supabase/template_favorites/insertTemplateFavorite.tslib/supabase/template_favorites/selectTemplateFavorites.tslib/supabase/template_shares/deleteTemplateShares.tslib/supabase/template_shares/insertTemplateShares.tslib/supabase/template_shares/selectTemplateShares.tslib/supabase/templates/deleteTemplate.tslib/supabase/templates/insertTemplate.tslib/supabase/templates/selectTemplates.tslib/supabase/templates/updateTemplate.tslib/templates/createTemplateHandler.tslib/templates/deleteTemplateHandler.tslib/templates/listTemplatesHandler.tslib/templates/toggleTemplateFavoriteHandler.tslib/templates/updateTemplateHandler.tslib/templates/validateCreateTemplateBody.tslib/templates/validateDeleteTemplateRequest.tslib/templates/validateToggleFavoriteRequest.tslib/templates/validateUpdateTemplateRequest.ts
✅ Files skipped from review due to trivial changes (2)
- lib/supabase/template_favorites/deleteTemplateFavorite.ts
- lib/supabase/templates/insertTemplate.ts
🚧 Files skipped from review as they are similar to previous changes (1)
- lib/const.ts
| const [template] = await selectTemplates({ id: inserted.id }, accountId); | ||
|
|
||
| return NextResponse.json( | ||
| { status: "success", template: template ?? null }, | ||
| { status: 201, headers: getCorsHeaders() }, | ||
| ); |
There was a problem hiding this comment.
Add null check after fetching the created template.
If selectTemplates returns an empty array (e.g., due to a race condition or permissions issue), template will be undefined, and the API returns { status: "success", template: null } with a 201 status. This is inconsistent—a 201 status implies the resource was successfully created and should be returned in the response body.
🛡️ Proposed fix to handle missing template
const [template] = await selectTemplates({ id: inserted.id }, accountId);
+ if (!template) {
+ return NextResponse.json(
+ { status: "error", error: "Failed to retrieve created template" },
+ { status: 500, headers: getCorsHeaders() },
+ );
+ }
return NextResponse.json(
- { status: "success", template: template ?? null },
+ { status: "success", template },
{ status: 201, headers: getCorsHeaders() },
);📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| const [template] = await selectTemplates({ id: inserted.id }, accountId); | |
| return NextResponse.json( | |
| { status: "success", template: template ?? null }, | |
| { status: 201, headers: getCorsHeaders() }, | |
| ); | |
| const [template] = await selectTemplates({ id: inserted.id }, accountId); | |
| if (!template) { | |
| return NextResponse.json( | |
| { status: "error", error: "Failed to retrieve created template" }, | |
| { status: 500, headers: getCorsHeaders() }, | |
| ); | |
| } | |
| return NextResponse.json( | |
| { status: "success", template }, | |
| { status: 201, headers: getCorsHeaders() }, | |
| ); |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@lib/templates/createTemplateHandler.ts` around lines 48 - 53, After calling
selectTemplates and destructuring const [template], add a null/undefined check
for template (in the create template handler) and if it's missing return an
error response instead of a 201: log the failure (include inserted.id and
accountId), and return NextResponse.json({ status: "error", message: "Created
template not found" }, { status: 500, headers: getCorsHeaders() }); otherwise
continue to return the 201 success response with the template. This uses the
existing selectTemplates call and the template variable to detect the
race/permission failure and avoids returning a 201 with a null body.
|
|
||
| export const createTemplateBodySchema = z.object({ | ||
| title: z | ||
| .string({ message: "title is required" }) |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Find package.json or lock files to determine Zod version
find . -maxdepth 3 -type f \( -name "package.json" -o -name "package-lock.json" -o -name "yarn.lock" -o -name "pnpm-lock.yaml" \) | head -5Repository: recoupable/api
Length of output: 90
🏁 Script executed:
#!/bin/bash
# Check the validateCreateTemplateBody.ts file
cat -n lib/templates/validateCreateTemplateBody.ts 2>/dev/null || echo "File not found"Repository: recoupable/api
Length of output: 2084
🏁 Script executed:
#!/bin/bash
# Search for Zod imports and usage to understand the version
rg "import.*zod|from.*zod" --type ts --type tsx -A 2 | head -30Repository: recoupable/api
Length of output: 86
🏁 Script executed:
#!/bin/bash
# Check the actual Zod version in package.json
cat -n package.json | grep -A 50 "dependencies\|devDependencies" | grep -i zodRepository: recoupable/api
Length of output: 130
🏁 Script executed:
#!/bin/bash
# Also check pnpm-lock.yaml for exact Zod version
rg "zod" pnpm-lock.yaml | head -20Repository: recoupable/api
Length of output: 1259
🏁 Script executed:
#!/bin/bash
# Check Zod v4.1.13 type definitions to see correct parameter name
if [ -d "node_modules/zod" ]; then
echo "=== Checking Zod type definitions ==="
find node_modules/zod -name "*.d.ts" | head -3 | xargs grep -l "string\|message\|error" | head -1 | xargs head -100
else
echo "node_modules/zod not found"
fiRepository: recoupable/api
Length of output: 85
🌐 Web query:
Zod v4.1.13 z.string error message parameter API documentation
💡 Result:
In Zod v4.1.13, the way to control the error message for z.string(...) is via the optional params argument, using either the message/error string form or the { error: (issue) =>... } error-map function. The customization ultimately sets the validation issue’s message field. For example: z.string({ error: "Bad!" }) and z.string({ error: (iss)=>... }) are documented as supported patterns, including that the error function receives a context object (issue) containing fields like input, code, inst, and path [1][2]. Key documentation points: 1) “Virtually every Zod API accepts an optional error message.” You can pass a string directly as the schema parameter (e.g., z.string("Not a string!")), and that message will appear as the issue.message for validation issues originating from that schema [1][2]. 2) Alternatively, you can pass a params object with an error property: z.string({ error: "Bad!" }) or z.string.min(5, { error: "Too short!" }) [1][2]. 3) The error param can be a function (error map) that runs at parse time when a validation error occurs: z.string({ error: =>... }) [1][2]. The function gets an “iss”/issue context object with properties like input, inst (schema/check), and path [1][2]. 4) If you want to avoid customizing for some cases, return undefined so Zod falls back to the default message / precedence chain [1][2]. So, for your specific query “Zod v4.1.13 z.string error message parameter API documentation”: - Use the params object form: z.string({ error: "..." }) for a static message. - Use the error-map function form: z.string({ error: (iss) => (iss.input === undefined? "Field is required": "Invalid input") }) to branch based on the issue context [1][2]. Note: the “example that checks iss.input === undefined” is explicitly shown in the Zod error customization docs as the pattern for making the message depend on whether the field was undefined [1][2].
Citations:
Update Zod error parameter from message to error.
In Zod v4.1.13, the correct parameter is error, not message. Update all primitive schema error customizations to use z.string({ error: "..." }) or pass the message directly as a positional argument: z.string("title is required").
Affects lines 7, 11, 15, 18, and 19.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@lib/templates/validateCreateTemplateBody.ts` at line 7, The Zod schema in
validateCreateTemplateBody.ts uses the deprecated .string({ message: "..." })
option; update each primitive schema customization (the .string(...) calls in
this file) to use the correct parameter by replacing message with error (e.g.,
.string({ error: "title is required" })) or simply pass the message as a
positional argument (e.g., z.string("title is required")), and apply the same
change for all occurrences currently using { message: ... } so the Zod v4.1.13
API is used correctly.
| export const toggleFavoriteBodySchema = z.object({ | ||
| is_favourite: z.boolean({ message: "is_favourite is required" }), | ||
| }); |
There was a problem hiding this comment.
🧩 Analysis chain
🌐 Web query:
Zod v4 boolean schema custom error message syntax
💡 Result:
In Zod v4, the syntax for creating a boolean schema with a custom error message uses the unified error parameter, which accepts a string or a function that receives an issue object. The old invalid_type_error and required_error parameters from v3 have been removed.[1][2] Basic example with a static message: import { z } from 'zod'; const boolSchema = z.boolean({ error: 'Must be a boolean value' }); For conditional messages (e.g., distinguishing undefined/missing from invalid type): const boolSchema = z.boolean({ error: (issue) => { if (issue.input === undefined) { return 'Boolean is required'; } return 'Expected boolean, received ' + typeof issue.input; } }); This function receives an issue object with properties like code ('invalid_type' for booleans), input, expected ('boolean'), etc.[3][4][5] Example usage: boolSchema.parse(true); // passes boolSchema.parse('yes'); // throws ZodError with custom message boolSchema.parse(undefined); // throws ZodError with custom message (if in object context) For more complex validation, chain .refine(): const requiredTrueSchema = z.boolean().refine((val) => val === true, { error: 'Must be true' }); This is confirmed in the official Zod v4 docs on error customization and schema definition.[6][3][7][1][2][5]
Citations:
- 1: https://v4.zod.dev/v4/changelog?id=changes-zunknown-optionality
- 2: https://www.zod.dev/v4?id=stringbool
- 3: https://v4.zod.dev/error-customization?id=locales
- 4: https://v4.zod.dev/error-customization?id=error-precedence
- 5: https://v4.zod.dev/?id=primitives
- 6: https://v4.zod.dev/?id=booleans
- 7: https://v4.zod.dev/api?id=error
Replace message parameter with Zod v4's error parameter for custom boolean validation messages.
Zod v4 removed support for the message parameter on primitive schemas. Use error instead:
export const toggleFavoriteBodySchema = z.object({
- is_favourite: z.boolean({ message: "is_favourite is required" }),
+ is_favourite: z.boolean({ error: "is_favourite is required" }),
});🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@lib/templates/validateToggleFavoriteRequest.ts` around lines 10 - 12, The
boolean schema in toggleFavoriteBodySchema uses the removed `message` option;
update the z.boolean call to use Zod v4's `error` option instead (replace the
`message` parameter with an `error` object containing the same message), so the
schema becomes valid under Zod v4 while preserving the "is_favourite is
required" text.
There was a problem hiding this comment.
6 issues found across 31 files (changes from recent commits).
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="lib/supabase/templates/deleteTemplate.ts">
<violation number="1" location="lib/supabase/templates/deleteTemplate.ts:4">
P3: Fix the typo in the function docstring (`an template` → `a template`) to keep API documentation clear.</violation>
</file>
<file name="lib/templates/updateTemplateHandler.ts">
<violation number="1" location="lib/templates/updateTemplateHandler.ts:11">
P3: The JSDoc route path is incorrect (`/api/agents/templates/{id}`) and should match the migrated endpoint (`/api/templates/{id}`).</violation>
</file>
<file name="lib/templates/createTemplateHandler.ts">
<violation number="1" location="lib/templates/createTemplateHandler.ts:11">
P3: The handler JSDoc documents the wrong endpoint path (`/api/agents/templates`), which is inconsistent with the migrated `/api/templates` API.</violation>
<violation number="2" location="lib/templates/createTemplateHandler.ts:48">
P2: Add a null check for `template` after the `selectTemplates` call. If the fetch returns empty (e.g. race condition), the handler currently returns `201` with `template: null`, which is inconsistent — a `201` status implies the created resource is present in the response body. Return a `500` error instead when the re-fetch unexpectedly yields nothing.</violation>
</file>
<file name="lib/supabase/template_favorites/selectTemplateFavorites.ts">
<violation number="1" location="lib/supabase/template_favorites/selectTemplateFavorites.ts:19">
P3: The error log message now references `template_favorites`, but this function still queries `agent_template_favorites`. This makes DB failures harder to trace because logs no longer match the actual table being accessed.</violation>
</file>
<file name="app/api/agents/templates/[id]/favorite/route.ts">
<violation number="1" location="app/api/agents/templates/[id]/favorite/route.ts:32">
P1: This change keeps the favorite toggle wired to `/api/agents/templates/{id}/favorite`, so clients calling the migrated `/api/templates/{id}/favorite` endpoint will still fail (404).</violation>
</file>
Tip: Review your code locally with the cubic CLI to iterate faster.
| request: NextRequest, | ||
| context: { params: Promise<{ id: string }> }, | ||
| ): Promise<NextResponse> { | ||
| return toggleTemplateFavoriteHandler(request, context.params); |
There was a problem hiding this comment.
P1: This change keeps the favorite toggle wired to /api/agents/templates/{id}/favorite, so clients calling the migrated /api/templates/{id}/favorite endpoint will still fail (404).
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At app/api/agents/templates/[id]/favorite/route.ts, line 32:
<comment>This change keeps the favorite toggle wired to `/api/agents/templates/{id}/favorite`, so clients calling the migrated `/api/templates/{id}/favorite` endpoint will still fail (404).</comment>
<file context>
@@ -29,7 +29,7 @@ export async function PUT(
context: { params: Promise<{ id: string }> },
): Promise<NextResponse> {
- return toggleAgentTemplateFavoriteHandler(request, context.params);
+ return toggleTemplateFavoriteHandler(request, context.params);
}
</file context>
Tip: Review your code locally with the cubic CLI to iterate faster.
| await insertTemplateShares(inserted.id, parsedBody.share_emails); | ||
| } | ||
|
|
||
| const [template] = await selectTemplates({ id: inserted.id }, accountId); |
There was a problem hiding this comment.
P2: Add a null check for template after the selectTemplates call. If the fetch returns empty (e.g. race condition), the handler currently returns 201 with template: null, which is inconsistent — a 201 status implies the created resource is present in the response body. Return a 500 error instead when the re-fetch unexpectedly yields nothing.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At lib/templates/createTemplateHandler.ts, line 48:
<comment>Add a null check for `template` after the `selectTemplates` call. If the fetch returns empty (e.g. race condition), the handler currently returns `201` with `template: null`, which is inconsistent — a `201` status implies the created resource is present in the response body. Return a `500` error instead when the re-fetch unexpectedly yields nothing.</comment>
<file context>
@@ -36,23 +36,23 @@ export async function createAgentTemplateHandler(request: NextRequest): Promise<
}
- const [template] = await selectAgentTemplates({ id: inserted.id }, accountId);
+ const [template] = await selectTemplates({ id: inserted.id }, accountId);
return NextResponse.json(
</file context>
| const [template] = await selectTemplates({ id: inserted.id }, accountId); | |
| const [template] = await selectTemplates({ id: inserted.id }, accountId); | |
| if (!template) { | |
| return NextResponse.json( | |
| { status: "error", error: "Failed to retrieve created template" }, | |
| { status: 500, headers: getCorsHeaders() }, | |
| ); | |
| } |
| import supabase from "@/lib/supabase/serverClient"; | ||
|
|
||
| /** | ||
| * Deletes an template row by id. Cascades remove dependent shares / |
There was a problem hiding this comment.
P3: Fix the typo in the function docstring (an template → a template) to keep API documentation clear.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At lib/supabase/templates/deleteTemplate.ts, line 4:
<comment>Fix the typo in the function docstring (`an template` → `a template`) to keep API documentation clear.</comment>
<file context>
@@ -1,17 +1,17 @@
/**
- * Deletes an agent template row by id. Cascades remove dependent shares /
+ * Deletes an template row by id. Cascades remove dependent shares /
* favorites at the database level.
*
</file context>
| * Deletes an template row by id. Cascades remove dependent shares / | |
| * Deletes a template row by id. Cascades remove dependent shares / |
| import type { TablesUpdate } from "@/types/database.types"; | ||
|
|
||
| /** | ||
| * Handler for PATCH /api/agents/templates/{id}. |
There was a problem hiding this comment.
P3: The JSDoc route path is incorrect (/api/agents/templates/{id}) and should match the migrated endpoint (/api/templates/{id}).
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At lib/templates/updateTemplateHandler.ts, line 11:
<comment>The JSDoc route path is incorrect (`/api/agents/templates/{id}`) and should match the migrated endpoint (`/api/templates/{id}`).</comment>
<file context>
@@ -1,25 +1,25 @@
/**
- * Handler for PATCH /api/agent-templates/{id}.
+ * Handler for PATCH /api/agents/templates/{id}.
*
- * Applies a partial update to an agent template the caller owns. When
</file context>
| * Handler for PATCH /api/agents/templates/{id}. | |
| * Handler for PATCH /api/templates/{id}. |
| import { selectTemplates } from "@/lib/supabase/templates/selectTemplates"; | ||
|
|
||
| /** | ||
| * Handler for POST /api/agents/templates. |
There was a problem hiding this comment.
P3: The handler JSDoc documents the wrong endpoint path (/api/agents/templates), which is inconsistent with the migrated /api/templates API.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At lib/templates/createTemplateHandler.ts, line 11:
<comment>The handler JSDoc documents the wrong endpoint path (`/api/agents/templates`), which is inconsistent with the migrated `/api/templates` API.</comment>
<file context>
@@ -2,30 +2,30 @@ import { NextRequest, NextResponse } from "next/server";
/**
- * Handler for POST /api/agent-templates.
+ * Handler for POST /api/agents/templates.
*
- * Creates an agent template owned by the authenticated account. When
</file context>
| * Handler for POST /api/agents/templates. | |
| * Handler for POST /api/templates. |
| .eq("user_id", accountId); | ||
|
|
||
| if (error) { | ||
| console.error("Error selecting template_favorites:", error); |
There was a problem hiding this comment.
P3: The error log message now references template_favorites, but this function still queries agent_template_favorites. This makes DB failures harder to trace because logs no longer match the actual table being accessed.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At lib/supabase/template_favorites/selectTemplateFavorites.ts, line 19:
<comment>The error log message now references `template_favorites`, but this function still queries `agent_template_favorites`. This makes DB failures harder to trace because logs no longer match the actual table being accessed.</comment>
<file context>
@@ -16,7 +16,7 @@ export async function selectAgentTemplateFavorites(
if (error) {
- console.error("Error selecting agent_template_favorites:", error);
+ console.error("Error selecting template_favorites:", error);
return [];
}
</file context>
| console.error("Error selecting template_favorites:", error); | |
| console.error("Error selecting agent_template_favorites:", error); |
…ort merged result
Three findings from the PR review, all about silent failure modes:
1. selectTemplateFavorites was swallowing DB errors and returning [],
inconsistent with selectTemplates which throws. Now throws.
2. selectTemplateShares had the same issue, with a worse consequence:
in validateToggleFavoriteRequest a DB error during the visibility
check would set canAccess=false and 403 a legitimate sharee. Now
throws so the handler's outer catch returns a real 500.
3. selectTemplates({accessibleTo}) merges owned/public (.order("title"))
with shared rows via a Map; the dedup interleaving broke alphabetical
order. Re-sort the combined result by title before returning.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* docs: add agent templates endpoints (list/create/update/delete/favorite)
Documents the new dedicated api endpoints for CHAT_API_MIGRATION
Group 7. Adds an "Agent Templates" group under Agents & Sandboxes
with 5 endpoint pages backed by a new agent-templates.json OpenAPI
spec. Endpoints support both x-api-key and Bearer auth; ownership
is enforced server-side on PATCH/DELETE.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* docs(agent-templates): align openapi with post-refactor api shape
- Mark tags / favorites_count / created_at non-nullable (matches DB
schema and the SDK-derived response shape)
- Add tags, creator, favorites_count, created_at, updated_at to
AgentTemplate.required (always returned)
- Add name, image, is_admin to AgentTemplateCreator.required (always
present in the flat block)
- Drop share_emails from CreateAgentTemplateRequest.required (zod
default makes it optional)
- Document 403 and 404 on PUT /api/agent-templates/{id}/favorite —
the visibility check (own / public / shared) was added in the api
refactor
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* docs: rename nav group Agent Templates → Templates per review
Applies sweetmantech's suggestion on PR #199 — the group is already
nested inside the Agents-related tab, so "Templates" alone keeps the
nav scannable. KISS.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* docs: rename routes /api/agent-templates → /api/agents/templates
Aligns with the existing /api/agents/* namespacing convention so the
nav, the spec, and the public URL all read as templates living under
the agents resource — not as a parallel top-level resource.
Updates 3 path keys in agent-templates.json and the openapi:
frontmatter in all 5 MDX pages (list, create, update, delete, favorite).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* docs(templates): finish rename, align with api PR /api/templates URL
Folder: api-reference/agent-templates/ → api-reference/templates/
OpenAPI: openapi/agent-templates.json → openapi/templates.json
URL prefix: /api/agents/templates → /api/templates (matches api PR
recoupable/api#543; both use /api/templates throughout)
Schemas: AgentTemplate* → Template*
Page titles + descriptions: "agent template" → "template"
docs.json: navigation paths updated to api-reference/templates/*
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* docs(templates): nest route under /api/agents/templates
URL: /api/templates → /api/agents/templates in OpenAPI paths and MDX
frontmatter. Tracks the api PR (recoupable/api#543).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* docs(templates): replace OpenAPI 3.1-incompatible "nullable" with type unions
The spec declares openapi: 3.1.0, which removed the "nullable" keyword.
Switch to JSON Schema type arrays:
TemplateCreator object → ["object", "null"]
TemplateCreator.name string → ["string", "null"]
TemplateCreator.image string → ["string", "null"]
Template.updated_at string (date) → ["string", "null"]
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-authored-by: Sweets Sweetman <sweetmantech@gmail.com>
|
|
||
| type SelectTemplatesParams = { id: string } | { accessibleTo: string }; | ||
|
|
||
| function flattenCreator(creator: RawTemplate["creator"]): TemplateCreator | null { |
There was a problem hiding this comment.
SRP - new lib file for flattenCreator.ts
| }; | ||
| } | ||
|
|
||
| async function resolveSharedEmails(templateIds: string[]): Promise<Record<string, string[]>> { |
There was a problem hiding this comment.
SRP - new lib file for resolveSharedEmails
| return result; | ||
| } | ||
|
|
||
| async function fetchRaw(params: SelectTemplatesParams): Promise<RawTemplate[]> { |
There was a problem hiding this comment.
SRP - new lib file for fetchRaw.ts
| const authResult = await validateAuthContext(request); | ||
| if (authResult instanceof NextResponse) return authResult; | ||
|
|
||
| const body = await safeParseJson(request); | ||
| const parsedBody = validateCreateTemplateBody(body); | ||
| if (parsedBody instanceof NextResponse) return parsedBody; |
There was a problem hiding this comment.
SRP
- actual: multiple validate + safeParse called directly in the handler
- required: one, single, validate call with safeParse called inside the validate function
| * Surfaced as `creator.is_admin` in `/api/agents/templates` so clients can flag | ||
| * official Recoup templates. Mirrors `chat/lib/admin.ts`. | ||
| */ | ||
| export const ADMIN_EMAILS: readonly string[] = ["sidney+1@recoupable.com"] as const; |
There was a problem hiding this comment.
DRY - Why is admin_emails required? Admin is already an existing concept with libs, etc.
Adds 5 dedicated endpoints — list/create/update/delete agent templates plus toggle favorite. Auth via
validateAuthContext; ownership enforced on PATCH/DELETE; the list response embeds the creator block (id/name/image/is_admin) so callers don't need a per-template creator lookup.Test plan
🤖 Generated with Claude Code
Summary by cubic
Migrated agent template management to new
/api/agents/templatesroutes with full CRUD and an idempotent favorite toggle. List responses include creator details, per-calleris_favourite, andshared_emailsonly for the creator, sorted alphabetically bytitle.New Features
/api/agents/templates, PATCH/DELETE/api/agents/templates/{id}, PUT/api/agents/templates/{id}/favoritevalidateAuthContext; PATCH/DELETE require creator; 401/403 on failurecreator { id, name, image, is_admin },is_favourite, andshared_emails(creator only)Migration
/api/agents/templates(from/api/agent-templatesand interim/api/templates); snake_case request/responsex-api-keyorAuthorization: Bearer <token>; DB failures return a generic 500 "Internal server error"titleafter dedup for stable orderingselectTemplates({ id | accessibleTo }, forAccountId?)returns fully shaped rows; list composition lives inlistTemplatesHandlerWritten for commit 62ff368. Summary will update on new commits.
Summary by CodeRabbit
Release Notes