Skip to content

feat(api): migrate agent templates management endpoints#543

Open
arpitgupta1214 wants to merge 11 commits into
testfrom
feat/migrate-agent-templates
Open

feat(api): migrate agent templates management endpoints#543
arpitgupta1214 wants to merge 11 commits into
testfrom
feat/migrate-agent-templates

Conversation

@arpitgupta1214
Copy link
Copy Markdown
Collaborator

@arpitgupta1214 arpitgupta1214 commented May 9, 2026

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

  • Hit each endpoint against the Vercel preview with a known API key — verify success bodies match the snake_case contract and the embedded creator block is populated on list
  • Verify 401 without auth, and 403 on PATCH/DELETE attempted by a non-creator

🤖 Generated with Claude Code


Summary by cubic

Migrated agent template management to new /api/agents/templates routes with full CRUD and an idempotent favorite toggle. List responses include creator details, per-caller is_favourite, and shared_emails only for the creator, sorted alphabetically by title.

  • New Features

    • Endpoints: GET/POST /api/agents/templates, PATCH/DELETE /api/agents/templates/{id}, PUT /api/agents/templates/{id}/favorite
    • Auth via validateAuthContext; PATCH/DELETE require creator; 401/403 on failure
    • GET returns own, public, and shared templates with creator { id, name, image, is_admin }, is_favourite, and shared_emails (creator only)
  • Migration

    • Surface renamed to /api/agents/templates (from /api/agent-templates and interim /api/templates); snake_case request/response
    • Use x-api-key or Authorization: Bearer <token>; DB failures return a generic 500 "Internal server error"
    • Robustness: favorites/shares selectors now throw on DB errors to avoid false 403s; merged list re-sorted by title after dedup for stable ordering
    • Internals: unified selectTemplates({ id | accessibleTo }, forAccountId?) returns fully shaped rows; list composition lives in listTemplatesHandler

Written for commit 62ff368. Summary will update on new commits.

Summary by CodeRabbit

Release Notes

  • New Features
    • Added comprehensive template management system supporting create, read, update, and delete operations
    • Introduced template favorites feature to bookmark frequently used templates
    • Enabled template sharing with other users via email addresses
    • Added admin email configuration for elevated user permissions

Review Change Stack

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>
@vercel
Copy link
Copy Markdown
Contributor

vercel Bot commented May 9, 2026

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

Project Deployment Actions Updated (UTC)
api Ready Ready Preview May 12, 2026 1:34am

Request Review

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 9, 2026

Note

Reviews paused

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

Use the following commands to manage reviews:

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

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 9c205842-cd74-47f9-b9e1-18193fd2a1c1

📥 Commits

Reviewing files that changed from the base of the PR and between 3837ad8 and 62ff368.

⛔ Files ignored due to path filters (5)
  • lib/templates/__tests__/createTemplateHandler.test.ts is excluded by !**/*.test.*, !**/__tests__/** and included by lib/**
  • lib/templates/__tests__/deleteTemplateHandler.test.ts is excluded by !**/*.test.*, !**/__tests__/** and included by lib/**
  • lib/templates/__tests__/listTemplatesHandler.test.ts is excluded by !**/*.test.*, !**/__tests__/** and included by lib/**
  • lib/templates/__tests__/toggleTemplateFavoriteHandler.test.ts is excluded by !**/*.test.*, !**/__tests__/** and included by lib/**
  • lib/templates/__tests__/updateTemplateHandler.test.ts is excluded by !**/*.test.*, !**/__tests__/** and included by lib/**
📒 Files selected for processing (16)
  • app/api/agents/templates/[id]/favorite/route.ts
  • app/api/agents/templates/[id]/route.ts
  • app/api/agents/templates/route.ts
  • lib/const.ts
  • lib/supabase/template_favorites/selectTemplateFavorites.ts
  • lib/supabase/template_shares/selectTemplateShares.ts
  • lib/supabase/templates/selectTemplates.ts
  • lib/templates/createTemplateHandler.ts
  • lib/templates/deleteTemplateHandler.ts
  • lib/templates/listTemplatesHandler.ts
  • lib/templates/toggleTemplateFavoriteHandler.ts
  • lib/templates/updateTemplateHandler.ts
  • lib/templates/validateCreateTemplateBody.ts
  • lib/templates/validateDeleteTemplateRequest.ts
  • lib/templates/validateToggleFavoriteRequest.ts
  • lib/templates/validateUpdateTemplateRequest.ts
🚧 Files skipped from review as they are similar to previous changes (11)
  • lib/const.ts
  • lib/templates/validateCreateTemplateBody.ts
  • lib/templates/deleteTemplateHandler.ts
  • lib/templates/listTemplatesHandler.ts
  • lib/templates/validateDeleteTemplateRequest.ts
  • lib/templates/validateToggleFavoriteRequest.ts
  • lib/templates/toggleTemplateFavoriteHandler.ts
  • lib/templates/createTemplateHandler.ts
  • lib/templates/updateTemplateHandler.ts
  • lib/templates/validateUpdateTemplateRequest.ts
  • lib/supabase/templates/selectTemplates.ts

📝 Walkthrough

Walkthrough

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

Changes

Agent Templates CRUD API with Favorites & Sharing

Layer / File(s) Summary
Configuration & Request Schemas
lib/const.ts, lib/templates/validateCreateTemplateBody.ts, lib/templates/validateUpdateTemplateRequest.ts, lib/templates/validateToggleFavoriteRequest.ts
Admin email constant and Zod schemas for POST, PATCH, PUT body validation with derived types and error response formatting.
Supabase Types & Template Response Shapes
lib/supabase/templates/selectTemplates.ts
Typed Supabase SELECT projection with joined creator account data; exports TemplateCreator and Template types with computed is_admin (via ADMIN_EMAILS), is_favourite, and shared_emails fields.
Supabase CRUD & Favorites Helpers
lib/supabase/templates/insertTemplate.ts, lib/supabase/templates/updateTemplate.ts, lib/supabase/templates/deleteTemplate.ts, lib/supabase/template_favorites/*, lib/supabase/template_shares/*
Low-level database operations: insert/update/delete templates, idempotent favorite insert/delete, template share management with conflict handling, all with error logging and failure visibility.
Template Query & Enrichment Engine
lib/supabase/templates/selectTemplates.ts
High-level selector supporting direct template lookup and account-scoped retrieval; flattens creator joins, resolves shared emails by querying shares and account lookups, enriches with favorite flags and accessibility for authenticated users.
Request Authorization & Validation
lib/templates/validateDeleteTemplateRequest.ts, lib/templates/validateUpdateTemplateRequest.ts, lib/templates/validateToggleFavoriteRequest.ts
Schema-based body validators and authorization checks for DELETE, PATCH, PUT: enforces creator ownership, allows template creator or non-private templates, queries shares for shared-template access, returns 400/403/404 errors with CORS headers.
Business Logic Handlers
lib/templates/createTemplateHandler.ts, lib/templates/deleteTemplateHandler.ts, lib/templates/listTemplatesHandler.ts, lib/templates/toggleTemplateFavoriteHandler.ts, lib/templates/updateTemplateHandler.ts
Orchestrates validation, CRUD execution, optional share synchronization, template enrichment via selectTemplates, and JSON response building with CORS headers for all five operations.
Next.js HTTP Route Modules
app/api/agents/templates/route.ts, app/api/agents/templates/[id]/route.ts, app/api/agents/templates/[id]/favorite/route.ts
Entry points for base and nested routes with CORS OPTIONS handlers, delegation to business logic handlers, and forced-dynamic/no-cache configuration.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

  • recoupable/api#259: Both PRs add constants to lib/const.ts; this PR adds ADMIN_EMAILS while the linked PR adds CREATE_CONTENT_TASK_ID.

Suggested reviewers

  • sweetmantech

Poem

📋 Templates take shape, favorites find their place,
Shares fly swift through email's embrace,
CRUD ops dance with CORS delight,
Zod validates each request just right,
Admin emails crown the creator's might! ✨

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Solid & Clean Code ⚠️ Warning DRY violation: 23 NextResponse.json calls duplicate error pattern; errorResponse/successResponse utilities exist but unused. SRP violation: selectTemplates.ts (179 lines) mixes 5+ concerns. Use errorResponse/successResponse helpers. Extract selectTemplates helpers into separate modules. Extract creator-only auth check to shared utility.
✅ Passed checks (2 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

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

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/migrate-agent-templates

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.

  • Generate code and open pull requests
  • Plan features and break down work
  • Investigate incidents and troubleshoot customer tickets together
  • Automate recurring tasks and respond to alerts with triggers
  • Summarize progress and report instantly

Built for teams:

  • Shared memory across your entire org—no repeating context
  • Per-thread sandboxes to safely plan and execute work
  • Governance built-in—scoped access, auditability, and budget controls

One agent for your entire SDLC. Right inside Slack.

👉 Get started


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.

❤️ Share

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

Copy link
Copy Markdown

@cubic-dev-ai cubic-dev-ai Bot left a comment

Choose a reason for hiding this comment

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

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.message in 500 responses in lib/agent_templates/deleteAgentTemplateHandler.ts, lib/agent_templates/updateAgentTemplateHandler.ts, lib/agent_templates/toggleAgentTemplateFavoriteHandler.ts, and lib/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, and lib/supabase/agent_template_shares/insertAgentTemplateShares.ts can mask DB errors (false 404s/no-op success), and non-atomic share updates in lib/agent_templates/updateAgentTemplateHandler.ts can leave sharing state inconsistent.
  • Pay close attention to lib/agent_templates/updateAgentTemplateHandler.ts, lib/supabase/agent_template_shares/deleteAgentTemplateShares.ts, and lib/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
Loading

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

Comment thread lib/supabase/agent_templates/selectAgentTemplate.ts Outdated
Comment thread lib/agent_templates/deleteAgentTemplateHandler.ts Outdated
Comment thread lib/agent_templates/toggleAgentTemplateFavoriteHandler.ts Outdated
Comment thread lib/supabase/agent_template_shares/deleteAgentTemplateShares.ts Outdated
}

if (typeof body.share_emails !== "undefined") {
await deleteAgentTemplateShares(templateId);
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

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

Comment thread lib/supabase/agent_templates/getAgentTemplateWithDetails.ts Outdated
expect(res.status).toBe(200);
const body = await res.json();
expect(body.status).toBe("success");
expect(body.templates).toHaveLength(1);
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2: 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.
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2: 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";
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2: 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,
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

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>
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 8

🧹 Nitpick comments (8)
lib/supabase/agent_template_favorites/selectAgentTemplateFavorites.ts (1)

6-13: ⚡ Quick win

Use accountId terminology in helper interfaces instead of userId.

At Lines [6] and [9], rename userId to accountId to stay consistent with the API/domain language. DB column user_id can 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 win

Make ADMIN_EMAILS immutable 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 win

Align parameter naming with account terminology (accountId).

At Lines [7], [12], and [18], rename userId to accountId for 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 win

Use account terminology in the function contract.

Please rename userId to accountId (and map it to user_id only 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 lift

Split getAccessibleAgentTemplates into 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 win

Use account terminology for local identifiers.

Please rename userIds / emailsByUser / userEmails to accountIds / emailsByAccount / accountEmails for 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 value

Misleading property name in error response.

The property missing_fields is confusing because:

  1. For the refinement error ("At least one field must be provided"), firstError.path will be an empty array [], not a list of missing fields
  2. 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 win

Type assertion bypasses type safety.

The double type assertion as unknown as AgentTemplateRowWithCreator circumvents 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

📥 Commits

Reviewing files that changed from the base of the PR and between f677145 and ff5e04f.

⛔ Files ignored due to path filters (5)
  • lib/agent_templates/__tests__/createAgentTemplateHandler.test.ts is excluded by !**/*.test.*, !**/__tests__/** and included by lib/**
  • lib/agent_templates/__tests__/deleteAgentTemplateHandler.test.ts is excluded by !**/*.test.*, !**/__tests__/** and included by lib/**
  • lib/agent_templates/__tests__/listAgentTemplatesHandler.test.ts is excluded by !**/*.test.*, !**/__tests__/** and included by lib/**
  • lib/agent_templates/__tests__/toggleAgentTemplateFavoriteHandler.test.ts is excluded by !**/*.test.*, !**/__tests__/** and included by lib/**
  • lib/agent_templates/__tests__/updateAgentTemplateHandler.test.ts is excluded by !**/*.test.*, !**/__tests__/** and included by lib/**
📒 Files selected for processing (25)
  • app/api/agent-templates/[id]/favorite/route.ts
  • app/api/agent-templates/[id]/route.ts
  • app/api/agent-templates/route.ts
  • lib/agent_templates/createAgentTemplateHandler.ts
  • lib/agent_templates/deleteAgentTemplateHandler.ts
  • lib/agent_templates/listAgentTemplatesHandler.ts
  • lib/agent_templates/toggleAgentTemplateFavoriteHandler.ts
  • lib/agent_templates/updateAgentTemplateHandler.ts
  • lib/agent_templates/validateCreateAgentTemplateBody.ts
  • lib/agent_templates/validateDeleteAgentTemplateRequest.ts
  • lib/agent_templates/validateToggleFavoriteRequest.ts
  • lib/agent_templates/validateUpdateAgentTemplateRequest.ts
  • lib/const.ts
  • lib/supabase/agent_template_favorites/deleteAgentTemplateFavorite.ts
  • lib/supabase/agent_template_favorites/insertAgentTemplateFavorite.ts
  • lib/supabase/agent_template_favorites/selectAgentTemplateFavorites.ts
  • lib/supabase/agent_template_shares/deleteAgentTemplateShares.ts
  • lib/supabase/agent_template_shares/insertAgentTemplateShares.ts
  • lib/supabase/agent_template_shares/selectAgentTemplateShares.ts
  • lib/supabase/agent_templates/deleteAgentTemplate.ts
  • lib/supabase/agent_templates/getAccessibleAgentTemplates.ts
  • lib/supabase/agent_templates/getAgentTemplateWithDetails.ts
  • lib/supabase/agent_templates/insertAgentTemplate.ts
  • lib/supabase/agent_templates/selectAgentTemplate.ts
  • lib/supabase/agent_templates/updateAgentTemplate.ts

Comment thread lib/templates/deleteTemplateHandler.ts
Comment thread lib/templates/toggleTemplateFavoriteHandler.ts
Comment thread lib/templates/updateTemplateHandler.ts
}
}

const template = await getAgentTemplateWithDetails(templateId, accountId);
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

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.

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

Comment thread lib/templates/validateToggleFavoriteRequest.ts
Comment thread lib/supabase/template_shares/insertTemplateShares.ts
Comment thread lib/supabase/agent_templates/getAccessibleAgentTemplates.ts Outdated
Comment thread lib/supabase/agent_templates/selectAgentTemplate.ts Outdated
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>
Copy link
Copy Markdown

@cubic-dev-ai cubic-dev-ai Bot left a comment

Choose a reason for hiding this comment

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

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.

Comment thread lib/supabase/agent_templates/getAgentTemplateWithDetails.ts Outdated
- 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>
Copy link
Copy Markdown

@cubic-dev-ai cubic-dev-ai Bot left a comment

Choose a reason for hiding this comment

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

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]);
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

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

Comment thread lib/agent_templates/__tests__/listAgentTemplatesHandler.test.ts
Comment thread lib/agent_templates/getAccessibleAgentTemplatesForAccount.ts Outdated
if (t && !byId.has(t.id)) byId.set(t.id, t);
});
});
return Array.from(byId.values());
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2: 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>
Suggested change
return Array.from(byId.values());
return Array.from(byId.values()).sort((a, b) =>
(a.title ?? "").localeCompare(b.title ?? ""),
);

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🧹 Nitpick comments (3)
lib/agent_templates/validateToggleFavoriteRequest.ts (3)

61-67: 💤 Low value

Minor: creator normalization is duplicated with getAccessibleAgentTemplatesForAccount.ts.

The same Array.isArray(existing.creator) ? existing.creator[0] : existing.creator shape-handling exists in getAccessibleAgentTemplatesForAccount.ts as creatorIdOf(row). Worth hoisting that into a tiny shared util (e.g., lib/agent_templates/normalizeCreator.ts or a getCreatorId helper alongside agentTemplateWithCreatorSelect) 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 win

Consider 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 a NextResponse 404/403. That would also bring validateToggleFavoriteRequest back under the 50-line guideline (it currently sits at ~54 lines body), and consolidate the Array.isArray(creator) normalization with creatorIdOf in getAccessibleAgentTemplatesForAccount.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 win

Migrate to Zod 4's unified error parameter; message is deprecated.

In Zod 4, the message parameter is deprecated in favor of a unified error parameter. 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

📥 Commits

Reviewing files that changed from the base of the PR and between ff5e04f and ff853b8.

⛔ Files ignored due to path filters (3)
  • lib/agent_templates/__tests__/createAgentTemplateHandler.test.ts is excluded by !**/*.test.*, !**/__tests__/** and included by lib/**
  • lib/agent_templates/__tests__/listAgentTemplatesHandler.test.ts is excluded by !**/*.test.*, !**/__tests__/** and included by lib/**
  • lib/agent_templates/__tests__/updateAgentTemplateHandler.test.ts is excluded by !**/*.test.*, !**/__tests__/** and included by lib/**
📒 Files selected for processing (22)
  • lib/agent_templates/buildAgentTemplateResponse.ts
  • lib/agent_templates/createAgentTemplateHandler.ts
  • lib/agent_templates/deleteAgentTemplateHandler.ts
  • lib/agent_templates/getAccessibleAgentTemplatesForAccount.ts
  • lib/agent_templates/getAgentTemplateForAccount.ts
  • lib/agent_templates/listAgentTemplatesHandler.ts
  • lib/agent_templates/resolveSharedEmailsByTemplateId.ts
  • lib/agent_templates/toggleAgentTemplateFavoriteHandler.ts
  • lib/agent_templates/updateAgentTemplateHandler.ts
  • lib/agent_templates/validateDeleteAgentTemplateRequest.ts
  • lib/agent_templates/validateToggleFavoriteRequest.ts
  • lib/agent_templates/validateUpdateAgentTemplateRequest.ts
  • lib/const.ts
  • lib/supabase/agent_template_favorites/deleteAgentTemplateFavorite.ts
  • lib/supabase/agent_template_favorites/insertAgentTemplateFavorite.ts
  • lib/supabase/agent_template_favorites/selectAgentTemplateFavorites.ts
  • lib/supabase/agent_template_shares/deleteAgentTemplateShares.ts
  • lib/supabase/agent_template_shares/insertAgentTemplateShares.ts
  • lib/supabase/agent_templates/agentTemplateWithCreatorSelect.ts
  • lib/supabase/agent_templates/selectAgentTemplateById.ts
  • lib/supabase/agent_templates/selectOwnedAndPublicAgentTemplates.ts
  • lib/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

Comment thread lib/agent_templates/createAgentTemplateHandler.ts Outdated
Comment thread lib/supabase/template_favorites/selectTemplateFavorites.ts
Comment thread lib/supabase/agent_templates/selectOwnedAndPublicAgentTemplates.ts Outdated
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>
Copy link
Copy Markdown

@cubic-dev-ai cubic-dev-ai Bot left a comment

Choose a reason for hiding this comment

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

0 issues found across 3 files (changes from recent commits).

Requires human review: Auto-approval blocked by 7 unresolved issues from previous reviews.

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>
Copy link
Copy Markdown

@cubic-dev-ai cubic-dev-ai Bot left a comment

Choose a reason for hiding this comment

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

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),
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1: 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);
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2: 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>
Suggested change
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: [],
});

@arpitgupta1214
Copy link
Copy Markdown
Collaborator Author

arpitgupta1214 commented May 11, 2026

Smoke test ✅

Ran against the Vercel preview with the test API key. All 16 scenarios pass.

# Case Expected Got
1 GET list, no auth 401 ✅ 401
2 GET list, with key 200, {status, templates}, flat creator { id, name, image, is_admin } ✅ 200, 44 rows, shape correct
3 is_admin: true for Sidney Swift (configured admin email) true
4 POST create public template 201, response embeds shaped row ✅ creator bound to API key's account
5 PUT favorite 200 {status: "success"}
6 List reflects is_favourite: true for that row true
7 PUT unfavorite 200
8 PATCH title 200, response reflects rename
9 PATCH is_private: true + share_emails: ["sidney+1@recoupable.com"] 200, shared_emails populated
10a POST title too short 400 with missing_fields: ["title"]
10b PATCH non-UUID id 400
10c PATCH someone else's template 403 Forbidden
10d DELETE someone else's template 403 Forbidden
11 PATCH share_emails: [] clears shares (replace semantics) shared_emails: []
12 DELETE own template 200
13 PATCH after delete 404
14 PUT favorite on non-existent template 404 ✅ visibility check fires
15 PUT favorite without auth 401 ✅ auth-before-body
16 OPTIONS preflight on all three routes 200 + Access-Control-Allow-Origin: *

Notable behaviors verified end-to-end:

  • Flat creator block + is_admin derivation working against ADMIN_EMAILS.
  • Auth runs before body validation (api/catalogs/songs - migrate to Vercel API #15: missing auth gets 401 not 400).
  • Toggle-favorite visibility check returns 404 for non-existent templates (was previously inserting/deleting anyway).
  • share_emails replace semantics confirmed on PATCH.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (2)
lib/agent_templates/validateDeleteAgentTemplateRequest.ts (1)

12-19: ⚡ Quick win

Consider 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 win

Consider 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

📥 Commits

Reviewing files that changed from the base of the PR and between ff853b8 and bca13d9.

⛔ Files ignored due to path filters (3)
  • lib/agent_templates/__tests__/createAgentTemplateHandler.test.ts is excluded by !**/*.test.*, !**/__tests__/** and included by lib/**
  • lib/agent_templates/__tests__/listAgentTemplatesHandler.test.ts is excluded by !**/*.test.*, !**/__tests__/** and included by lib/**
  • lib/agent_templates/__tests__/updateAgentTemplateHandler.test.ts is excluded by !**/*.test.*, !**/__tests__/** and included by lib/**
📒 Files selected for processing (7)
  • lib/agent_templates/createAgentTemplateHandler.ts
  • lib/agent_templates/listAgentTemplatesHandler.ts
  • lib/agent_templates/updateAgentTemplateHandler.ts
  • lib/agent_templates/validateDeleteAgentTemplateRequest.ts
  • lib/agent_templates/validateToggleFavoriteRequest.ts
  • lib/agent_templates/validateUpdateAgentTemplateRequest.ts
  • lib/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

Comment thread lib/templates/validateUpdateTemplateRequest.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>
arpitgupta1214 added a commit to recoupable/docs that referenced this pull request May 12, 2026
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>
arpitgupta1214 added a commit to recoupable/docs that referenced this pull request May 12, 2026
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>
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 4

♻️ Duplicate comments (2)
lib/supabase/template_favorites/selectTemplateFavorites.ts (1)

18-21: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Propagate DB failures instead of returning an empty favorites list.

Returning [] on Line 20 makes “query failed” indistinguishable from “no favourites,” which can silently produce incorrect is_favourite states. 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 win

Rename missing_fields to better reflect validation error scope.

The response key missing_fields is misleading for Zod validation failures, which can include type mismatches, format violations, length constraints, and more—not just missing fields. Consider renaming to field or validation_path for 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 lift

Break down oversized query/shape functions into smaller units.

fetchRaw and selectTemplates exceed 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

📥 Commits

Reviewing files that changed from the base of the PR and between bca13d9 and 3837ad8.

⛔ Files ignored due to path filters (5)
  • lib/templates/__tests__/createTemplateHandler.test.ts is excluded by !**/*.test.*, !**/__tests__/** and included by lib/**
  • lib/templates/__tests__/deleteTemplateHandler.test.ts is excluded by !**/*.test.*, !**/__tests__/** and included by lib/**
  • lib/templates/__tests__/listTemplatesHandler.test.ts is excluded by !**/*.test.*, !**/__tests__/** and included by lib/**
  • lib/templates/__tests__/toggleTemplateFavoriteHandler.test.ts is excluded by !**/*.test.*, !**/__tests__/** and included by lib/**
  • lib/templates/__tests__/updateTemplateHandler.test.ts is excluded by !**/*.test.*, !**/__tests__/** and included by lib/**
📒 Files selected for processing (23)
  • app/api/templates/[id]/favorite/route.ts
  • app/api/templates/[id]/route.ts
  • app/api/templates/route.ts
  • lib/const.ts
  • lib/supabase/template_favorites/deleteTemplateFavorite.ts
  • lib/supabase/template_favorites/insertTemplateFavorite.ts
  • lib/supabase/template_favorites/selectTemplateFavorites.ts
  • lib/supabase/template_shares/deleteTemplateShares.ts
  • lib/supabase/template_shares/insertTemplateShares.ts
  • lib/supabase/template_shares/selectTemplateShares.ts
  • lib/supabase/templates/deleteTemplate.ts
  • lib/supabase/templates/insertTemplate.ts
  • lib/supabase/templates/selectTemplates.ts
  • lib/supabase/templates/updateTemplate.ts
  • lib/templates/createTemplateHandler.ts
  • lib/templates/deleteTemplateHandler.ts
  • lib/templates/listTemplatesHandler.ts
  • lib/templates/toggleTemplateFavoriteHandler.ts
  • lib/templates/updateTemplateHandler.ts
  • lib/templates/validateCreateTemplateBody.ts
  • lib/templates/validateDeleteTemplateRequest.ts
  • lib/templates/validateToggleFavoriteRequest.ts
  • lib/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

Comment thread lib/supabase/template_shares/selectTemplateShares.ts
Comment on lines +48 to +53
const [template] = await selectTemplates({ id: inserted.id }, accountId);

return NextResponse.json(
{ status: "success", template: template ?? null },
{ status: 201, headers: getCorsHeaders() },
);
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

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.

Suggested change
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" })
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

🧩 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 -5

Repository: 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 -30

Repository: 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 zod

Repository: 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 -20

Repository: 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"
fi

Repository: 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.

Comment on lines +10 to +12
export const toggleFavoriteBodySchema = z.object({
is_favourite: z.boolean({ message: "is_favourite is required" }),
});
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

🧩 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:


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.

Copy link
Copy Markdown

@cubic-dev-ai cubic-dev-ai Bot left a comment

Choose a reason for hiding this comment

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

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);
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1: 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);
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2: 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>
Suggested change
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 /
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P3: Fix the typo in the function docstring (an templatea 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>
Suggested change
* 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}.
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

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>
Suggested change
* 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.
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

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>
Suggested change
* Handler for POST /api/agents/templates.
* Handler for POST /api/templates.

.eq("user_id", accountId);

if (error) {
console.error("Error selecting template_favorites:", error);
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

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>
Suggested change
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>
Copy link
Copy Markdown

@cubic-dev-ai cubic-dev-ai Bot left a comment

Choose a reason for hiding this comment

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

0 issues found across 3 files (changes from recent commits).

Requires human review: Auto-approval blocked by 15 unresolved issues from previous reviews.

sweetmantech added a commit to recoupable/docs that referenced this pull request May 12, 2026
* 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 {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

SRP - new lib file for flattenCreator.ts

};
}

async function resolveSharedEmails(templateIds: string[]): Promise<Record<string, string[]>> {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

SRP - new lib file for resolveSharedEmails

return result;
}

async function fetchRaw(params: SelectTemplatesParams): Promise<RawTemplate[]> {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

SRP - new lib file for fetchRaw.ts

Comment on lines +19 to +24
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;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

SRP

  • actual: multiple validate + safeParse called directly in the handler
  • required: one, single, validate call with safeParse called inside the validate function

Comment thread lib/const.ts
* 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;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

DRY - Why is admin_emails required? Admin is already an existing concept with libs, etc.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants