From 91f126a174328b6fd788f41bde2c60755dfbbc15 Mon Sep 17 00:00:00 2001 From: Arpit Gupta Date: Thu, 9 Apr 2026 17:37:31 +0530 Subject: [PATCH] Revert "feat: add artist detail read endpoint (#416)" This reverts commit 77053735c932c25b162e9783659f81909a04eb75. --- app/api/artists/[id]/route.ts | 29 ----- .../__tests__/getArtistHandler.test.ts | 103 --------------- .../validateGetArtistRequest.test.ts | 120 ------------------ lib/artists/getArtistById.ts | 42 ------ lib/artists/getArtistHandler.ts | 62 --------- lib/artists/validateGetArtistRequest.ts | 51 -------- .../selectAccountWithArtistDetails.ts | 33 ----- 7 files changed, 440 deletions(-) delete mode 100644 app/api/artists/[id]/route.ts delete mode 100644 lib/artists/__tests__/getArtistHandler.test.ts delete mode 100644 lib/artists/__tests__/validateGetArtistRequest.test.ts delete mode 100644 lib/artists/getArtistById.ts delete mode 100644 lib/artists/getArtistHandler.ts delete mode 100644 lib/artists/validateGetArtistRequest.ts delete mode 100644 lib/supabase/accounts/selectAccountWithArtistDetails.ts diff --git a/app/api/artists/[id]/route.ts b/app/api/artists/[id]/route.ts deleted file mode 100644 index 447ada3d..00000000 --- a/app/api/artists/[id]/route.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { NextRequest, NextResponse } from "next/server"; -import { getCorsHeaders } from "@/lib/networking/getCorsHeaders"; -import { getArtistHandler } from "@/lib/artists/getArtistHandler"; - -/** - * OPTIONS handler for CORS preflight requests. - * - * @returns A NextResponse with CORS headers. - */ -export async function OPTIONS() { - return new NextResponse(null, { - status: 200, - headers: getCorsHeaders(), - }); -} - -/** - * GET /api/artists/[id] - * - * Retrieves a single artist detail payload for an accessible artist account. - * - * @param request - The request object - * @param options - Route options containing params - * @param options.params - Route params containing the artist account ID - * @returns A NextResponse with artist data - */ -export async function GET(request: NextRequest, options: { params: Promise<{ id: string }> }) { - return getArtistHandler(request, options.params); -} diff --git a/lib/artists/__tests__/getArtistHandler.test.ts b/lib/artists/__tests__/getArtistHandler.test.ts deleted file mode 100644 index 4bfe3311..00000000 --- a/lib/artists/__tests__/getArtistHandler.test.ts +++ /dev/null @@ -1,103 +0,0 @@ -import { describe, it, expect, vi, beforeEach } from "vitest"; -import { NextRequest, NextResponse } from "next/server"; -import { getArtistHandler } from "../getArtistHandler"; -import { validateGetArtistRequest } from "../validateGetArtistRequest"; -import { getArtistById } from "../getArtistById"; - -vi.mock("@/lib/networking/getCorsHeaders", () => ({ - getCorsHeaders: vi.fn(() => ({ "Access-Control-Allow-Origin": "*" })), -})); - -vi.mock("../validateGetArtistRequest", () => ({ - validateGetArtistRequest: vi.fn(), -})); - -vi.mock("../getArtistById", () => ({ - getArtistById: vi.fn(), -})); - -const validUuid = "550e8400-e29b-41d4-a716-446655440000"; - -describe("getArtistHandler", () => { - beforeEach(() => { - vi.clearAllMocks(); - }); - - it("returns the auth/validation response when params validation fails", async () => { - vi.mocked(validateGetArtistRequest).mockResolvedValue( - NextResponse.json({ error: "unauthorized" }, { status: 401 }), - ); - - const req = new NextRequest(`http://localhost/api/artists/${validUuid}`); - const res = await getArtistHandler(req, Promise.resolve({ id: validUuid })); - - expect(res.status).toBe(401); - expect(getArtistById).not.toHaveBeenCalled(); - }); - - it("returns 404 when the artist does not exist", async () => { - vi.mocked(validateGetArtistRequest).mockResolvedValue({ - artistId: validUuid, - requesterAccountId: validUuid, - }); - vi.mocked(getArtistById).mockResolvedValue(null); - - const req = new NextRequest(`http://localhost/api/artists/${validUuid}`); - const res = await getArtistHandler(req, Promise.resolve({ id: validUuid })); - const body = await res.json(); - - expect(res.status).toBe(404); - expect(body.error).toBe("Artist not found"); - }); - - it("returns 200 with the merged artist payload", async () => { - const artist = { - account_id: validUuid, - id: validUuid, - name: "Test Artist", - instruction: "Be specific", - knowledges: [], - label: "Indie", - image: "https://example.com/artist.png", - account_socials: [], - }; - - vi.mocked(validateGetArtistRequest).mockResolvedValue({ - artistId: validUuid, - requesterAccountId: validUuid, - }); - vi.mocked(getArtistById).mockResolvedValue(artist as never); - - const req = new NextRequest(`http://localhost/api/artists/${validUuid}`); - const res = await getArtistHandler(req, Promise.resolve({ id: validUuid })); - const body = await res.json(); - - expect(res.status).toBe(200); - expect(body.artist).toEqual(artist); - }); - - it("passes the request and path id to validation", async () => { - vi.mocked(validateGetArtistRequest).mockResolvedValue({ - artistId: validUuid, - requesterAccountId: validUuid, - }); - vi.mocked(getArtistById).mockResolvedValue({ account_id: validUuid } as never); - - const req = new NextRequest(`http://localhost/api/artists/${validUuid}`); - await getArtistHandler(req, Promise.resolve({ id: validUuid })); - - expect(validateGetArtistRequest).toHaveBeenCalledWith(req, validUuid); - }); - - it("returns 403 when the authenticated account cannot access the artist", async () => { - vi.mocked(validateGetArtistRequest).mockResolvedValue( - NextResponse.json({ status: "error", error: "forbidden" }, { status: 403 }), - ); - - const req = new NextRequest(`http://localhost/api/artists/${validUuid}`); - const res = await getArtistHandler(req, Promise.resolve({ id: validUuid })); - - expect(res.status).toBe(403); - expect(getArtistById).not.toHaveBeenCalled(); - }); -}); diff --git a/lib/artists/__tests__/validateGetArtistRequest.test.ts b/lib/artists/__tests__/validateGetArtistRequest.test.ts deleted file mode 100644 index 67bcdab9..00000000 --- a/lib/artists/__tests__/validateGetArtistRequest.test.ts +++ /dev/null @@ -1,120 +0,0 @@ -import { describe, it, expect, vi, beforeEach } from "vitest"; -import { NextRequest, NextResponse } from "next/server"; -import { validateGetArtistRequest } from "../validateGetArtistRequest"; -import { validateAccountParams } from "@/lib/accounts/validateAccountParams"; -import { validateAuthContext } from "@/lib/auth/validateAuthContext"; -import { checkAccountArtistAccess } from "../checkAccountArtistAccess"; - -vi.mock("@/lib/networking/getCorsHeaders", () => ({ - getCorsHeaders: vi.fn(() => ({ "Access-Control-Allow-Origin": "*" })), -})); - -vi.mock("@/lib/accounts/validateAccountParams", () => ({ - validateAccountParams: vi.fn(), -})); - -vi.mock("@/lib/auth/validateAuthContext", () => ({ - validateAuthContext: vi.fn(), -})); - -vi.mock("../checkAccountArtistAccess", () => ({ - checkAccountArtistAccess: vi.fn(), -})); - -const validUuid = "550e8400-e29b-41d4-a716-446655440000"; - -describe("validateGetArtistRequest", () => { - beforeEach(() => { - vi.clearAllMocks(); - }); - - it("returns 400 when the artist id is invalid", async () => { - vi.mocked(validateAccountParams).mockReturnValue( - NextResponse.json({ error: "invalid UUID" }, { status: 400 }), - ); - - const req = new NextRequest("http://localhost/api/artists/not-valid"); - const result = await validateGetArtistRequest(req, "not-valid"); - - expect(result).toBeInstanceOf(NextResponse); - expect((result as NextResponse).status).toBe(400); - expect(validateAuthContext).not.toHaveBeenCalled(); - }); - - it("returns 401 when auth fails", async () => { - vi.mocked(validateAccountParams).mockReturnValue({ id: validUuid }); - vi.mocked(validateAuthContext).mockResolvedValue( - NextResponse.json({ error: "unauthorized" }, { status: 401 }), - ); - - const req = new NextRequest(`http://localhost/api/artists/${validUuid}`); - const result = await validateGetArtistRequest(req, validUuid); - - expect(result).toBeInstanceOf(NextResponse); - expect((result as NextResponse).status).toBe(401); - expect(validateAuthContext).toHaveBeenCalledWith(req); - expect(checkAccountArtistAccess).not.toHaveBeenCalled(); - }); - - it("returns 403 when the authenticated account cannot access the artist", async () => { - vi.mocked(validateAccountParams).mockReturnValue({ id: validUuid }); - vi.mocked(validateAuthContext).mockResolvedValue({ - accountId: "11111111-1111-4111-8111-111111111111", - orgId: null, - authToken: "token", - }); - vi.mocked(checkAccountArtistAccess).mockResolvedValue(false); - - const req = new NextRequest(`http://localhost/api/artists/${validUuid}`); - const result = await validateGetArtistRequest(req, validUuid); - - expect(result).toBeInstanceOf(NextResponse); - expect((result as NextResponse).status).toBe(403); - expect(checkAccountArtistAccess).toHaveBeenCalledWith( - "11111111-1111-4111-8111-111111111111", - validUuid, - ); - }); - - it("returns the validated artist id when auth succeeds", async () => { - vi.mocked(validateAccountParams).mockReturnValue({ id: validUuid }); - vi.mocked(validateAuthContext).mockResolvedValue({ - accountId: validUuid, - orgId: null, - authToken: "token", - }); - - const req = new NextRequest(`http://localhost/api/artists/${validUuid}`); - const result = await validateGetArtistRequest(req, validUuid); - - expect(result).toEqual({ - artistId: validUuid, - requesterAccountId: validUuid, - }); - expect(validateAccountParams).toHaveBeenCalledWith(validUuid); - expect(validateAuthContext).toHaveBeenCalledWith(req); - expect(checkAccountArtistAccess).not.toHaveBeenCalled(); - }); - - it("returns the validated artist id when the authenticated account can access the artist", async () => { - vi.mocked(validateAccountParams).mockReturnValue({ id: validUuid }); - vi.mocked(validateAuthContext).mockResolvedValue({ - accountId: "11111111-1111-4111-8111-111111111111", - orgId: null, - authToken: "token", - }); - vi.mocked(checkAccountArtistAccess).mockResolvedValue(true); - - const req = new NextRequest(`http://localhost/api/artists/${validUuid}`); - const result = await validateGetArtistRequest(req, validUuid); - - expect(result).toEqual({ - artistId: validUuid, - requesterAccountId: "11111111-1111-4111-8111-111111111111", - }); - expect(checkAccountArtistAccess).toHaveBeenCalledWith( - "11111111-1111-4111-8111-111111111111", - validUuid, - ); - }); -}); diff --git a/lib/artists/getArtistById.ts b/lib/artists/getArtistById.ts deleted file mode 100644 index 869c1501..00000000 --- a/lib/artists/getArtistById.ts +++ /dev/null @@ -1,42 +0,0 @@ -import { getFormattedArtist } from "@/lib/artists/getFormattedArtist"; -import { selectAccountWithArtistDetails } from "@/lib/supabase/accounts/selectAccountWithArtistDetails"; - -export interface ArtistDetail { - id: string; - account_id: string; - name: string; - image: string | null; - instruction: string | null; - knowledges: ReturnType["knowledges"]; - label: string | null; - account_socials: ReturnType["account_socials"]; -} - -/** - * Retrieves a single artist by account ID and formats it for the public artist detail route. - * - * @param artistId - The artist account ID - * @returns Artist detail payload or null when not found - */ -export async function getArtistById(artistId: string): Promise { - const account = await selectAccountWithArtistDetails(artistId); - - if (!account) { - return null; - } - - const formattedArtist = getFormattedArtist(account); - const { account_id, name, image, instruction, knowledges, label, account_socials } = - formattedArtist; - - return { - id: artistId, - account_id, - name, - image, - instruction, - knowledges, - label, - account_socials, - }; -} diff --git a/lib/artists/getArtistHandler.ts b/lib/artists/getArtistHandler.ts deleted file mode 100644 index d6ec0bf3..00000000 --- a/lib/artists/getArtistHandler.ts +++ /dev/null @@ -1,62 +0,0 @@ -import { NextRequest, NextResponse } from "next/server"; -import { getCorsHeaders } from "@/lib/networking/getCorsHeaders"; -import { validateGetArtistRequest } from "@/lib/artists/validateGetArtistRequest"; -import { getArtistById } from "@/lib/artists/getArtistById"; - -/** - * Handler for retrieving a single artist detail by account ID. - * - * @param request - The incoming request - * @param params - Route params containing the artist account ID - * @returns A NextResponse with the artist payload or an error - */ -export async function getArtistHandler( - request: NextRequest, - params: Promise<{ id: string }>, -): Promise { - try { - const { id } = await params; - - const validatedRequest = await validateGetArtistRequest(request, id); - if (validatedRequest instanceof NextResponse) { - return validatedRequest; - } - - const artist = await getArtistById(validatedRequest.artistId); - - if (!artist) { - return NextResponse.json( - { - status: "error", - error: "Artist not found", - }, - { - status: 404, - headers: getCorsHeaders(), - }, - ); - } - - return NextResponse.json( - { - artist, - }, - { - status: 200, - headers: getCorsHeaders(), - }, - ); - } catch (error) { - console.error("[ERROR] getArtistHandler:", error); - return NextResponse.json( - { - status: "error", - error: error instanceof Error ? error.message : "Internal server error", - }, - { - status: 500, - headers: getCorsHeaders(), - }, - ); - } -} diff --git a/lib/artists/validateGetArtistRequest.ts b/lib/artists/validateGetArtistRequest.ts deleted file mode 100644 index 43cda908..00000000 --- a/lib/artists/validateGetArtistRequest.ts +++ /dev/null @@ -1,51 +0,0 @@ -import { NextRequest, NextResponse } from "next/server"; -import { validateAccountParams } from "@/lib/accounts/validateAccountParams"; -import { validateAuthContext } from "@/lib/auth/validateAuthContext"; -import { getCorsHeaders } from "@/lib/networking/getCorsHeaders"; -import { checkAccountArtistAccess } from "@/lib/artists/checkAccountArtistAccess"; - -export interface GetArtistRequest { - artistId: string; - requesterAccountId: string; -} - -/** - * Validates GET /api/artists/{id} path params and authentication. - * - * @param request - The incoming request - * @param id - The artist account ID from the route - * @returns The validated artist ID plus requester context, or a NextResponse error - */ -export async function validateGetArtistRequest( - request: NextRequest, - id: string, -): Promise { - const validatedParams = validateAccountParams(id); - if (validatedParams instanceof NextResponse) { - return validatedParams; - } - - const authResult = await validateAuthContext(request); - if (authResult instanceof NextResponse) { - return authResult; - } - - if (validatedParams.id !== authResult.accountId) { - const hasArtistAccess = await checkAccountArtistAccess( - authResult.accountId, - validatedParams.id, - ); - - if (!hasArtistAccess) { - return NextResponse.json( - { status: "error", error: "Access denied to specified artist" }, - { status: 403, headers: getCorsHeaders() }, - ); - } - } - - return { - artistId: validatedParams.id, - requesterAccountId: authResult.accountId, - }; -} diff --git a/lib/supabase/accounts/selectAccountWithArtistDetails.ts b/lib/supabase/accounts/selectAccountWithArtistDetails.ts deleted file mode 100644 index fd39ef3d..00000000 --- a/lib/supabase/accounts/selectAccountWithArtistDetails.ts +++ /dev/null @@ -1,33 +0,0 @@ -import supabase from "../serverClient"; -import type { Tables } from "@/types/database.types"; - -type AccountSocialWithSocial = Tables<"account_socials"> & { - social: Tables<"socials"> | null; -}; - -export type AccountWithArtistDetails = Tables<"accounts"> & { - account_info: Tables<"account_info">[]; - account_socials: AccountSocialWithSocial[]; -}; - -/** - * Retrieves an account with artist-compatible relations for detail responses. - * - * @param accountId - The account ID to fetch - * @returns Account row with account_info and joined socials, or null when missing - */ -export async function selectAccountWithArtistDetails( - accountId: string, -): Promise { - const { data, error } = await supabase - .from("accounts") - .select("*, account_info(*), account_socials(*, social:socials(*))") - .eq("id", accountId) - .single(); - - if (error || !data) { - return null; - } - - return data as AccountWithArtistDetails; -}