From b68d70448f9983287744b701ac576be44b72e5fa Mon Sep 17 00:00:00 2001 From: Arpit Gupta Date: Thu, 16 Apr 2026 02:02:22 +0530 Subject: [PATCH 1/4] feat(api): add POST /api/artists/{id}/segments for manual segment creation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds a dedicated REST endpoint for manual segment generation that reuses the existing MCP-backed `createSegments` handler with no duplication of generation or insert logic. - Route: `app/api/artists/[id]/segments/route.ts` (POST, OPTIONS) - Thin delegation to `lib/artists/segments/postArtistSegmentsHandler.ts` - Body validator in `lib/artists/segments/validatePostSegmentsBody.ts` accepting `{ prompt: string }` (non-empty) - Auth via `validateAuthContext()`; per-artist access enforced via `checkAccountArtistAccess`, matching existing `DELETE /api/artists/{id}` - Error envelope mapping: 400 (validation), 401 (auth), 403 (access), 404 (artist not found), 409 (no socials/no fans — preserves `feedback` from `createSegments`), 500 (other) - Success envelope: `{ status, segments_created, message }` - 11 handler tests cover success, 400, 401, 403, 404, 409 (both no-socials and no-fans paths with feedback), 500 (error envelope and thrown) Part of the Segments Surface Migration plan (Create → api PR). --- app/api/artists/[id]/segments/route.ts | 30 +++ .../postArtistSegmentsHandler.test.ts | 245 ++++++++++++++++++ .../segments/postArtistSegmentsHandler.ts | 128 +++++++++ .../segments/validatePostSegmentsBody.ts | 36 +++ 4 files changed, 439 insertions(+) create mode 100644 app/api/artists/[id]/segments/route.ts create mode 100644 lib/artists/segments/__tests__/postArtistSegmentsHandler.test.ts create mode 100644 lib/artists/segments/postArtistSegmentsHandler.ts create mode 100644 lib/artists/segments/validatePostSegmentsBody.ts diff --git a/app/api/artists/[id]/segments/route.ts b/app/api/artists/[id]/segments/route.ts new file mode 100644 index 000000000..29c49f1c0 --- /dev/null +++ b/app/api/artists/[id]/segments/route.ts @@ -0,0 +1,30 @@ +import { NextRequest, NextResponse } from "next/server"; +import { getCorsHeaders } from "@/lib/networking/getCorsHeaders"; +import { postArtistSegmentsHandler } from "@/lib/artists/segments/postArtistSegmentsHandler"; + +/** + * OPTIONS handler for CORS preflight requests. + * + * @returns A NextResponse with CORS headers. + */ +export async function OPTIONS() { + return new NextResponse(null, { + status: 200, + headers: getCorsHeaders(), + }); +} + +/** + * POST /api/artists/{id}/segments + * + * Manually creates segments for the specified artist by delegating to the shared + * `createSegments` handler (also exposed via the MCP `create_segments` tool). + * + * @param request - The incoming request object + * @param options - Route options containing params + * @param options.params - Route params containing the artist account ID + * @returns A NextResponse with the segment creation envelope + */ +export async function POST(request: NextRequest, options: { params: Promise<{ id: string }> }) { + return postArtistSegmentsHandler(request, options.params); +} diff --git a/lib/artists/segments/__tests__/postArtistSegmentsHandler.test.ts b/lib/artists/segments/__tests__/postArtistSegmentsHandler.test.ts new file mode 100644 index 000000000..5cbae481e --- /dev/null +++ b/lib/artists/segments/__tests__/postArtistSegmentsHandler.test.ts @@ -0,0 +1,245 @@ +import { describe, it, expect, vi, beforeEach } from "vitest"; +import { NextRequest, NextResponse } from "next/server"; + +import { postArtistSegmentsHandler } from "../postArtistSegmentsHandler"; + +vi.mock("@/lib/networking/getCorsHeaders", () => ({ + getCorsHeaders: vi.fn(() => ({ "Access-Control-Allow-Origin": "*" })), +})); + +const mockValidateAuthContext = vi.fn(); +vi.mock("@/lib/auth/validateAuthContext", () => ({ + validateAuthContext: (...args: unknown[]) => mockValidateAuthContext(...args), +})); + +const mockSelectAccounts = vi.fn(); +vi.mock("@/lib/supabase/accounts/selectAccounts", () => ({ + selectAccounts: (...args: unknown[]) => mockSelectAccounts(...args), +})); + +const mockCheckAccountArtistAccess = vi.fn(); +vi.mock("@/lib/artists/checkAccountArtistAccess", () => ({ + checkAccountArtistAccess: (...args: unknown[]) => mockCheckAccountArtistAccess(...args), +})); + +const mockCreateSegments = vi.fn(); +vi.mock("@/lib/segments/createSegments", () => ({ + createSegments: (...args: unknown[]) => mockCreateSegments(...args), +})); + +const ARTIST_ID = "550e8400-e29b-41d4-a716-446655440000"; +const REQUESTER_ACCOUNT_ID = "660e8400-e29b-41d4-a716-446655440000"; + +function createRequest(body: unknown, headers: Record = {}): NextRequest { + const defaultHeaders: Record = { + "Content-Type": "application/json", + "x-api-key": "test-api-key", + }; + return new NextRequest(`http://localhost/api/artists/${ARTIST_ID}/segments`, { + method: "POST", + headers: { ...defaultHeaders, ...headers }, + body: JSON.stringify(body), + }); +} + +describe("postArtistSegmentsHandler", () => { + beforeEach(() => { + vi.clearAllMocks(); + mockValidateAuthContext.mockResolvedValue({ + accountId: REQUESTER_ACCOUNT_ID, + orgId: null, + authToken: "test-api-key", + }); + mockSelectAccounts.mockResolvedValue([{ id: ARTIST_ID, name: "Test Artist" }]); + mockCheckAccountArtistAccess.mockResolvedValue(true); + }); + + it("returns 200 with success envelope when segments are created", async () => { + mockCreateSegments.mockResolvedValue({ + success: true, + status: "success", + message: "Successfully created 5 segments for artist", + data: { + supabase_segments: [], + supabase_artist_segments: [], + supabase_fan_segments: [], + segments: [], + }, + count: 5, + }); + + const request = createRequest({ prompt: "Segment my fans" }); + const response = await postArtistSegmentsHandler(request, Promise.resolve({ id: ARTIST_ID })); + const body = await response.json(); + + expect(response.status).toBe(200); + expect(body).toEqual({ + status: "success", + segments_created: 5, + message: "Segments generated successfully.", + }); + expect(mockCreateSegments).toHaveBeenCalledWith({ + artist_account_id: ARTIST_ID, + prompt: "Segment my fans", + }); + }); + + it("returns 400 when prompt is missing", async () => { + const request = createRequest({}); + const response = await postArtistSegmentsHandler(request, Promise.resolve({ id: ARTIST_ID })); + const body = await response.json(); + + expect(response.status).toBe(400); + expect(body.status).toBe("error"); + expect(body.error).toBe("prompt is required"); + expect(mockCreateSegments).not.toHaveBeenCalled(); + }); + + it("returns 400 when prompt is empty", async () => { + const request = createRequest({ prompt: "" }); + const response = await postArtistSegmentsHandler(request, Promise.resolve({ id: ARTIST_ID })); + const body = await response.json(); + + expect(response.status).toBe(400); + expect(body.error).toBe("prompt cannot be empty"); + expect(mockCreateSegments).not.toHaveBeenCalled(); + }); + + it("returns 400 when path id is not a valid UUID", async () => { + const request = createRequest({ prompt: "Segment my fans" }); + const response = await postArtistSegmentsHandler( + request, + Promise.resolve({ id: "not-a-uuid" }), + ); + const body = await response.json(); + + expect(response.status).toBe(400); + expect(body.status).toBe("error"); + expect(mockCreateSegments).not.toHaveBeenCalled(); + }); + + it("returns 401 when auth context fails", async () => { + mockValidateAuthContext.mockResolvedValue( + NextResponse.json( + { status: "error", error: "Exactly one of x-api-key or Authorization must be provided" }, + { status: 401 }, + ), + ); + + const request = new NextRequest(`http://localhost/api/artists/${ARTIST_ID}/segments`, { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ prompt: "Segment my fans" }), + }); + + const response = await postArtistSegmentsHandler(request, Promise.resolve({ id: ARTIST_ID })); + const body = await response.json(); + + expect(response.status).toBe(401); + expect(body.error).toBe("Exactly one of x-api-key or Authorization must be provided"); + expect(mockCreateSegments).not.toHaveBeenCalled(); + }); + + it("returns 403 when caller lacks access to artist", async () => { + mockCheckAccountArtistAccess.mockResolvedValue(false); + + const request = createRequest({ prompt: "Segment my fans" }); + const response = await postArtistSegmentsHandler(request, Promise.resolve({ id: ARTIST_ID })); + const body = await response.json(); + + expect(response.status).toBe(403); + expect(body.status).toBe("error"); + expect(body.error).toBe("Unauthorized segment creation attempt"); + expect(mockCreateSegments).not.toHaveBeenCalled(); + }); + + it("returns 404 when artist does not exist", async () => { + mockSelectAccounts.mockResolvedValue([]); + + const request = createRequest({ prompt: "Segment my fans" }); + const response = await postArtistSegmentsHandler(request, Promise.resolve({ id: ARTIST_ID })); + const body = await response.json(); + + expect(response.status).toBe(404); + expect(body.error).toBe("Artist not found"); + expect(mockCheckAccountArtistAccess).not.toHaveBeenCalled(); + expect(mockCreateSegments).not.toHaveBeenCalled(); + }); + + it("returns 409 when createSegments reports no social account with feedback", async () => { + const feedback = "No Instagram accounts found for Test Artist. Follow these steps..."; + mockCreateSegments.mockResolvedValue({ + success: false, + status: "error", + message: "No social account found for this artist", + data: [], + count: 0, + feedback, + }); + + const request = createRequest({ prompt: "Segment my fans" }); + const response = await postArtistSegmentsHandler(request, Promise.resolve({ id: ARTIST_ID })); + const body = await response.json(); + + expect(response.status).toBe(409); + expect(body).toEqual({ + status: "error", + error: "No social account found for this artist", + feedback, + }); + }); + + it("returns 409 when createSegments reports no fans with feedback", async () => { + const feedback = "No social_fans records found for Test Artist. Follow these steps..."; + mockCreateSegments.mockResolvedValue({ + success: false, + status: "error", + message: "No fans found for this artist", + data: [], + count: 0, + feedback, + }); + + const request = createRequest({ prompt: "Segment my fans" }); + const response = await postArtistSegmentsHandler(request, Promise.resolve({ id: ARTIST_ID })); + const body = await response.json(); + + expect(response.status).toBe(409); + expect(body).toEqual({ + status: "error", + error: "No fans found for this artist", + feedback, + }); + }); + + it("returns 500 when createSegments reports a generic failure", async () => { + mockCreateSegments.mockResolvedValue({ + success: false, + status: "error", + message: "Failed to generate segment names", + data: [], + count: 0, + }); + + const request = createRequest({ prompt: "Segment my fans" }); + const response = await postArtistSegmentsHandler(request, Promise.resolve({ id: ARTIST_ID })); + const body = await response.json(); + + expect(response.status).toBe(500); + expect(body).toEqual({ + status: "error", + error: "Failed to generate segment names", + }); + }); + + it("returns 500 when createSegments throws", async () => { + mockCreateSegments.mockRejectedValue(new Error("Database exploded")); + + const request = createRequest({ prompt: "Segment my fans" }); + const response = await postArtistSegmentsHandler(request, Promise.resolve({ id: ARTIST_ID })); + const body = await response.json(); + + expect(response.status).toBe(500); + expect(body.error).toBe("Database exploded"); + }); +}); diff --git a/lib/artists/segments/postArtistSegmentsHandler.ts b/lib/artists/segments/postArtistSegmentsHandler.ts new file mode 100644 index 000000000..4d86db140 --- /dev/null +++ b/lib/artists/segments/postArtistSegmentsHandler.ts @@ -0,0 +1,128 @@ +import { NextRequest, NextResponse } from "next/server"; +import { getCorsHeaders } from "@/lib/networking/getCorsHeaders"; +import { validateAccountParams } from "@/lib/accounts/validateAccountParams"; +import { validateAuthContext } from "@/lib/auth/validateAuthContext"; +import { safeParseJson } from "@/lib/networking/safeParseJson"; +import { checkAccountArtistAccess } from "@/lib/artists/checkAccountArtistAccess"; +import { selectAccounts } from "@/lib/supabase/accounts/selectAccounts"; +import { createSegments } from "@/lib/segments/createSegments"; +import { validatePostSegmentsBody } from "@/lib/artists/segments/validatePostSegmentsBody"; + +const NO_RESOURCE_ERROR_MESSAGES = new Set([ + "No social account found for this artist", + "No fans found for this artist", +]); + +/** + * Handler for POST /api/artists/{id}/segments. + * + * Validates authentication and per-artist access, then delegates to the shared + * `createSegments` handler that also powers the MCP `create_segments` tool. + * + * @param request - The incoming request object. + * @param params - The route params containing the artist account ID. + * @returns A NextResponse with the segment generation result envelope. + */ +export async function postArtistSegmentsHandler( + request: NextRequest, + params: Promise<{ id: string }>, +): Promise { + try { + const { id } = await params; + + const validatedParams = validateAccountParams(id); + if (validatedParams instanceof NextResponse) { + return validatedParams; + } + + const body = await safeParseJson(request); + const validatedBody = validatePostSegmentsBody(body); + if (validatedBody instanceof NextResponse) { + return validatedBody; + } + + const authResult = await validateAuthContext(request); + if (authResult instanceof NextResponse) { + return authResult; + } + + const artistId = validatedParams.id; + const requesterAccountId = authResult.accountId; + + const existingArtist = await selectAccounts(artistId); + if (!existingArtist.length) { + return NextResponse.json( + { + status: "error", + error: "Artist not found", + }, + { + status: 404, + headers: getCorsHeaders(), + }, + ); + } + + const hasAccess = await checkAccountArtistAccess(requesterAccountId, artistId); + if (!hasAccess) { + return NextResponse.json( + { + status: "error", + error: "Unauthorized segment creation attempt", + }, + { + status: 403, + headers: getCorsHeaders(), + }, + ); + } + + const result = await createSegments({ + artist_account_id: artistId, + prompt: validatedBody.prompt, + }); + + if (result.success) { + return NextResponse.json( + { + status: "success", + segments_created: result.count, + message: "Segments generated successfully.", + }, + { + status: 200, + headers: getCorsHeaders(), + }, + ); + } + + const message = result.message ?? "Failed to create segments"; + const feedback = "feedback" in result ? result.feedback : undefined; + const isNoResourceError = NO_RESOURCE_ERROR_MESSAGES.has(message); + const statusCode = isNoResourceError ? 409 : 500; + + return NextResponse.json( + { + status: "error", + error: message, + ...(feedback ? { feedback } : {}), + }, + { + status: statusCode, + headers: getCorsHeaders(), + }, + ); + } catch (error) { + console.error("[ERROR] postArtistSegmentsHandler:", error); + return NextResponse.json( + { + status: "error", + error: error instanceof Error ? error.message : "Internal server error", + }, + { + status: 500, + headers: getCorsHeaders(), + }, + ); + } +} diff --git a/lib/artists/segments/validatePostSegmentsBody.ts b/lib/artists/segments/validatePostSegmentsBody.ts new file mode 100644 index 000000000..2fde3e2bd --- /dev/null +++ b/lib/artists/segments/validatePostSegmentsBody.ts @@ -0,0 +1,36 @@ +import { NextResponse } from "next/server"; +import { z } from "zod"; +import { getCorsHeaders } from "@/lib/networking/getCorsHeaders"; + +export const postSegmentsBodySchema = z.object({ + prompt: z.string({ message: "prompt is required" }).min(1, "prompt cannot be empty"), +}); + +export type PostSegmentsBody = z.infer; + +/** + * Validates the request body for POST /api/artists/{id}/segments. + * + * @param body - The parsed request body to validate. + * @returns A NextResponse with an error when validation fails, or the validated body when it passes. + */ +export function validatePostSegmentsBody(body: unknown): NextResponse | PostSegmentsBody { + const result = postSegmentsBodySchema.safeParse(body); + + if (!result.success) { + const firstError = result.error.issues[0]; + return NextResponse.json( + { + status: "error", + missing_fields: firstError.path, + error: firstError.message, + }, + { + status: 400, + headers: getCorsHeaders(), + }, + ); + } + + return result.data; +} From b31faf7e524a0a18c36cb7a202d6f482e3a07c3f Mon Sep 17 00:00:00 2001 From: Arpit Gupta Date: Thu, 16 Apr 2026 02:59:45 +0530 Subject: [PATCH 2/4] refactor(api): bundle POST segments validations into validatePostArtistSegmentsRequest Matches the project validateRequest pattern (see validateDeleteArtistRequest). Handler now calls one validator that covers path id, body, auth, artist existence, and per-artist access. --- .../segments/postArtistSegmentsHandler.ts | 63 ++------------ .../validatePostArtistSegmentsRequest.ts | 84 +++++++++++++++++++ 2 files changed, 92 insertions(+), 55 deletions(-) create mode 100644 lib/artists/segments/validatePostArtistSegmentsRequest.ts diff --git a/lib/artists/segments/postArtistSegmentsHandler.ts b/lib/artists/segments/postArtistSegmentsHandler.ts index 4d86db140..08ee4c282 100644 --- a/lib/artists/segments/postArtistSegmentsHandler.ts +++ b/lib/artists/segments/postArtistSegmentsHandler.ts @@ -1,12 +1,7 @@ import { NextRequest, NextResponse } from "next/server"; import { getCorsHeaders } from "@/lib/networking/getCorsHeaders"; -import { validateAccountParams } from "@/lib/accounts/validateAccountParams"; -import { validateAuthContext } from "@/lib/auth/validateAuthContext"; -import { safeParseJson } from "@/lib/networking/safeParseJson"; -import { checkAccountArtistAccess } from "@/lib/artists/checkAccountArtistAccess"; -import { selectAccounts } from "@/lib/supabase/accounts/selectAccounts"; import { createSegments } from "@/lib/segments/createSegments"; -import { validatePostSegmentsBody } from "@/lib/artists/segments/validatePostSegmentsBody"; +import { validatePostArtistSegmentsRequest } from "@/lib/artists/segments/validatePostArtistSegmentsRequest"; const NO_RESOURCE_ERROR_MESSAGES = new Set([ "No social account found for this artist", @@ -16,8 +11,8 @@ const NO_RESOURCE_ERROR_MESSAGES = new Set([ /** * Handler for POST /api/artists/{id}/segments. * - * Validates authentication and per-artist access, then delegates to the shared - * `createSegments` handler that also powers the MCP `create_segments` tool. + * Validates the request then delegates to the shared `createSegments` handler + * that also powers the MCP `create_segments` tool. * * @param request - The incoming request object. * @param params - The route params containing the artist account ID. @@ -30,56 +25,14 @@ export async function postArtistSegmentsHandler( try { const { id } = await params; - const validatedParams = validateAccountParams(id); - if (validatedParams instanceof NextResponse) { - return validatedParams; - } - - const body = await safeParseJson(request); - const validatedBody = validatePostSegmentsBody(body); - if (validatedBody instanceof NextResponse) { - return validatedBody; - } - - const authResult = await validateAuthContext(request); - if (authResult instanceof NextResponse) { - return authResult; - } - - const artistId = validatedParams.id; - const requesterAccountId = authResult.accountId; - - const existingArtist = await selectAccounts(artistId); - if (!existingArtist.length) { - return NextResponse.json( - { - status: "error", - error: "Artist not found", - }, - { - status: 404, - headers: getCorsHeaders(), - }, - ); - } - - const hasAccess = await checkAccountArtistAccess(requesterAccountId, artistId); - if (!hasAccess) { - return NextResponse.json( - { - status: "error", - error: "Unauthorized segment creation attempt", - }, - { - status: 403, - headers: getCorsHeaders(), - }, - ); + const validated = await validatePostArtistSegmentsRequest(request, id); + if (validated instanceof NextResponse) { + return validated; } const result = await createSegments({ - artist_account_id: artistId, - prompt: validatedBody.prompt, + artist_account_id: validated.artistId, + prompt: validated.body.prompt, }); if (result.success) { diff --git a/lib/artists/segments/validatePostArtistSegmentsRequest.ts b/lib/artists/segments/validatePostArtistSegmentsRequest.ts new file mode 100644 index 000000000..27035840f --- /dev/null +++ b/lib/artists/segments/validatePostArtistSegmentsRequest.ts @@ -0,0 +1,84 @@ +import type { NextRequest } from "next/server"; +import { NextResponse } from "next/server"; +import { validateAccountParams } from "@/lib/accounts/validateAccountParams"; +import { validateAuthContext } from "@/lib/auth/validateAuthContext"; +import { safeParseJson } from "@/lib/networking/safeParseJson"; +import { getCorsHeaders } from "@/lib/networking/getCorsHeaders"; +import { checkAccountArtistAccess } from "@/lib/artists/checkAccountArtistAccess"; +import { selectAccounts } from "@/lib/supabase/accounts/selectAccounts"; +import { + validatePostSegmentsBody, + type PostSegmentsBody, +} from "@/lib/artists/segments/validatePostSegmentsBody"; + +export interface PostArtistSegmentsRequest { + artistId: string; + requesterAccountId: string; + body: PostSegmentsBody; +} + +/** + * Validates POST /api/artists/{id}/segments: path id, authentication, body, + * artist existence, and per-artist access. + * + * @param request - The incoming request + * @param id - The artist account ID from the route + * @returns The validated request context, or a NextResponse error + */ +export async function validatePostArtistSegmentsRequest( + request: NextRequest, + id: string, +): Promise { + const validatedParams = validateAccountParams(id); + if (validatedParams instanceof NextResponse) { + return validatedParams; + } + + const rawBody = await safeParseJson(request); + const validatedBody = validatePostSegmentsBody(rawBody); + if (validatedBody instanceof NextResponse) { + return validatedBody; + } + + const authResult = await validateAuthContext(request); + if (authResult instanceof NextResponse) { + return authResult; + } + + const artistId = validatedParams.id; + const requesterAccountId = authResult.accountId; + + const existingArtist = await selectAccounts(artistId); + if (!existingArtist.length) { + return NextResponse.json( + { + status: "error", + error: "Artist not found", + }, + { + status: 404, + headers: getCorsHeaders(), + }, + ); + } + + const hasAccess = await checkAccountArtistAccess(requesterAccountId, artistId); + if (!hasAccess) { + return NextResponse.json( + { + status: "error", + error: "Unauthorized segment creation attempt", + }, + { + status: 403, + headers: getCorsHeaders(), + }, + ); + } + + return { + artistId, + requesterAccountId, + body: validatedBody, + }; +} From 806c6cefe9f46f6deb451b59cc7e926f70790a34 Mon Sep 17 00:00:00 2001 From: Arpit Gupta Date: Thu, 16 Apr 2026 03:05:28 +0530 Subject: [PATCH 3/4] refactor(api): prefix validator return types with Validated Co-Authored-By: Claude Opus 4.6 --- lib/artists/segments/validatePostArtistSegmentsRequest.ts | 8 ++++---- lib/artists/segments/validatePostSegmentsBody.ts | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/lib/artists/segments/validatePostArtistSegmentsRequest.ts b/lib/artists/segments/validatePostArtistSegmentsRequest.ts index 27035840f..134b55452 100644 --- a/lib/artists/segments/validatePostArtistSegmentsRequest.ts +++ b/lib/artists/segments/validatePostArtistSegmentsRequest.ts @@ -8,13 +8,13 @@ import { checkAccountArtistAccess } from "@/lib/artists/checkAccountArtistAccess import { selectAccounts } from "@/lib/supabase/accounts/selectAccounts"; import { validatePostSegmentsBody, - type PostSegmentsBody, + type ValidatedPostSegmentsBody, } from "@/lib/artists/segments/validatePostSegmentsBody"; -export interface PostArtistSegmentsRequest { +export interface ValidatedPostArtistSegmentsRequest { artistId: string; requesterAccountId: string; - body: PostSegmentsBody; + body: ValidatedPostSegmentsBody; } /** @@ -28,7 +28,7 @@ export interface PostArtistSegmentsRequest { export async function validatePostArtistSegmentsRequest( request: NextRequest, id: string, -): Promise { +): Promise { const validatedParams = validateAccountParams(id); if (validatedParams instanceof NextResponse) { return validatedParams; diff --git a/lib/artists/segments/validatePostSegmentsBody.ts b/lib/artists/segments/validatePostSegmentsBody.ts index 2fde3e2bd..14bdf5601 100644 --- a/lib/artists/segments/validatePostSegmentsBody.ts +++ b/lib/artists/segments/validatePostSegmentsBody.ts @@ -6,7 +6,7 @@ export const postSegmentsBodySchema = z.object({ prompt: z.string({ message: "prompt is required" }).min(1, "prompt cannot be empty"), }); -export type PostSegmentsBody = z.infer; +export type ValidatedPostSegmentsBody = z.infer; /** * Validates the request body for POST /api/artists/{id}/segments. @@ -14,7 +14,7 @@ export type PostSegmentsBody = z.infer; * @param body - The parsed request body to validate. * @returns A NextResponse with an error when validation fails, or the validated body when it passes. */ -export function validatePostSegmentsBody(body: unknown): NextResponse | PostSegmentsBody { +export function validatePostSegmentsBody(body: unknown): NextResponse | ValidatedPostSegmentsBody { const result = postSegmentsBodySchema.safeParse(body); if (!result.success) { From eeff5138b663b255a1ca242934309838336bc387 Mon Sep 17 00:00:00 2001 From: Arpit Gupta Date: Thu, 16 Apr 2026 03:10:19 +0530 Subject: [PATCH 4/4] fix(api): return 201 Created for POST /api/artists/{id}/segments Co-Authored-By: Claude Opus 4.6 --- .../segments/__tests__/postArtistSegmentsHandler.test.ts | 2 +- lib/artists/segments/postArtistSegmentsHandler.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/artists/segments/__tests__/postArtistSegmentsHandler.test.ts b/lib/artists/segments/__tests__/postArtistSegmentsHandler.test.ts index 5cbae481e..b4091b311 100644 --- a/lib/artists/segments/__tests__/postArtistSegmentsHandler.test.ts +++ b/lib/artists/segments/__tests__/postArtistSegmentsHandler.test.ts @@ -72,7 +72,7 @@ describe("postArtistSegmentsHandler", () => { const response = await postArtistSegmentsHandler(request, Promise.resolve({ id: ARTIST_ID })); const body = await response.json(); - expect(response.status).toBe(200); + expect(response.status).toBe(201); expect(body).toEqual({ status: "success", segments_created: 5, diff --git a/lib/artists/segments/postArtistSegmentsHandler.ts b/lib/artists/segments/postArtistSegmentsHandler.ts index 08ee4c282..2113b7e0a 100644 --- a/lib/artists/segments/postArtistSegmentsHandler.ts +++ b/lib/artists/segments/postArtistSegmentsHandler.ts @@ -43,7 +43,7 @@ export async function postArtistSegmentsHandler( message: "Segments generated successfully.", }, { - status: 200, + status: 201, headers: getCorsHeaders(), }, );