diff --git a/app/api/accounts/artists/__tests__/route.test.ts b/app/api/accounts/artists/__tests__/route.test.ts new file mode 100644 index 000000000..e7c22cda7 --- /dev/null +++ b/app/api/accounts/artists/__tests__/route.test.ts @@ -0,0 +1,36 @@ +import { describe, it, expect, vi, beforeEach } from "vitest"; +import { NextRequest, NextResponse } from "next/server"; +import { POST } from "../route"; + +vi.mock("@/lib/accounts/addArtistToAccountHandler", () => ({ + addArtistToAccountHandler: vi.fn(), +})); + +const { addArtistToAccountHandler } = await import("@/lib/accounts/addArtistToAccountHandler"); + +const ARTIST_ID = "11111111-1111-4111-8111-111111111111"; + +function makeReq(body: Record): NextRequest { + return new NextRequest("https://example.com/api/accounts/artists", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify(body), + }); +} + +describe("POST /api/accounts/artists", () => { + beforeEach(() => { + vi.clearAllMocks(); + }); + + it("delegates to addArtistToAccountHandler and returns its response", async () => { + const handlerResponse = NextResponse.json({ success: true }, { status: 200 }); + vi.mocked(addArtistToAccountHandler).mockResolvedValue(handlerResponse); + + const req = makeReq({ artistId: ARTIST_ID }); + const res = await POST(req); + + expect(addArtistToAccountHandler).toHaveBeenCalledWith(req); + expect(res).toBe(handlerResponse); + }); +}); diff --git a/app/api/accounts/artists/route.ts b/app/api/accounts/artists/route.ts index eef8b0e6f..ee0125069 100644 --- a/app/api/accounts/artists/route.ts +++ b/app/api/accounts/artists/route.ts @@ -1,27 +1,21 @@ import { NextRequest, NextResponse } from "next/server"; import { getCorsHeaders } from "@/lib/networking/getCorsHeaders"; -import { safeParseJson } from "@/lib/networking/safeParseJson"; -import { validateAddArtistBody, type AddArtistBody } from "@/lib/accounts/validateAddArtistBody"; import { addArtistToAccountHandler } from "@/lib/accounts/addArtistToAccountHandler"; /** * POST /api/accounts/artists * - * Add an artist to an account's list of associated artists. + * Add an artist to the authenticated account's list of associated artists. + * Requires authentication (x-api-key or Authorization bearer token); the + * target account is derived from the credential. An optional email override + * is allowed only when the caller has access to that account. * If the artist is already associated with the account, returns success. * - * @param req - The incoming request with email and artistId in body + * @param req - The incoming request with artistId (and optional email) in body * @returns NextResponse with success status or error */ export async function POST(req: NextRequest) { - const body = await safeParseJson(req); - - const validated = validateAddArtistBody(body); - if (validated instanceof NextResponse) { - return validated; - } - - return addArtistToAccountHandler(validated as AddArtistBody); + return addArtistToAccountHandler(req); } /** diff --git a/lib/accounts/__tests__/addArtistToAccountHandler.test.ts b/lib/accounts/__tests__/addArtistToAccountHandler.test.ts new file mode 100644 index 000000000..7bac04565 --- /dev/null +++ b/lib/accounts/__tests__/addArtistToAccountHandler.test.ts @@ -0,0 +1,60 @@ +import { describe, it, expect, vi, beforeEach } from "vitest"; +import { NextRequest, NextResponse } from "next/server"; +import { addArtistToAccountHandler } from "../addArtistToAccountHandler"; + +vi.mock("@/lib/accounts/validateAddArtistRequest", () => ({ + validateAddArtistRequest: vi.fn(), +})); + +vi.mock("@/lib/accounts/linkArtistToAccount", () => ({ + linkArtistToAccount: vi.fn(), +})); + +const { validateAddArtistRequest } = await import("@/lib/accounts/validateAddArtistRequest"); +const { linkArtistToAccount } = await import("@/lib/accounts/linkArtistToAccount"); + +const ARTIST_ID = "11111111-1111-4111-8111-111111111111"; +const ACCOUNT_ID = "22222222-2222-4222-8222-222222222222"; + +function makeReq(body: Record): NextRequest { + return new NextRequest("https://example.com/api/accounts/artists", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify(body), + }); +} + +describe("addArtistToAccountHandler", () => { + beforeEach(() => { + vi.clearAllMocks(); + }); + + it("returns the validation error and does not link when validation fails", async () => { + vi.mocked(validateAddArtistRequest).mockResolvedValue( + NextResponse.json({ status: "error" }, { status: 401 }), + ); + + const res = await addArtistToAccountHandler(makeReq({ artistId: ARTIST_ID })); + + expect(res.status).toBe(401); + expect(linkArtistToAccount).not.toHaveBeenCalled(); + }); + + it("links the resolved account and artist when validation passes", async () => { + vi.mocked(validateAddArtistRequest).mockResolvedValue({ + accountId: ACCOUNT_ID, + artistId: ARTIST_ID, + }); + vi.mocked(linkArtistToAccount).mockResolvedValue( + NextResponse.json({ success: true }, { status: 200 }), + ); + + const res = await addArtistToAccountHandler(makeReq({ artistId: ARTIST_ID })); + + expect(res.status).toBe(200); + expect(linkArtistToAccount).toHaveBeenCalledWith({ + accountId: ACCOUNT_ID, + artistId: ARTIST_ID, + }); + }); +}); diff --git a/lib/accounts/__tests__/linkArtistToAccount.test.ts b/lib/accounts/__tests__/linkArtistToAccount.test.ts new file mode 100644 index 000000000..2ce055b05 --- /dev/null +++ b/lib/accounts/__tests__/linkArtistToAccount.test.ts @@ -0,0 +1,63 @@ +import { describe, it, expect, vi, beforeEach } from "vitest"; +import { linkArtistToAccount } from "../linkArtistToAccount"; + +vi.mock("@/lib/supabase/account_artist_ids/getAccountArtistIds", () => ({ + getAccountArtistIds: vi.fn(), +})); + +vi.mock("@/lib/supabase/account_artist_ids/insertAccountArtistId", () => ({ + insertAccountArtistId: vi.fn(), +})); + +vi.mock("@/lib/networking/getCorsHeaders", () => ({ + getCorsHeaders: vi.fn(() => ({ "Access-Control-Allow-Origin": "*" })), +})); + +const { getAccountArtistIds } = await import( + "@/lib/supabase/account_artist_ids/getAccountArtistIds" +); +const { insertAccountArtistId } = await import( + "@/lib/supabase/account_artist_ids/insertAccountArtistId" +); + +const ACCOUNT_ID = "22222222-2222-4222-8222-222222222222"; +const ARTIST_ID = "11111111-1111-4111-8111-111111111111"; + +describe("linkArtistToAccount", () => { + beforeEach(() => { + vi.clearAllMocks(); + }); + + it("inserts the artist and returns success when not already linked", async () => { + vi.mocked(getAccountArtistIds).mockResolvedValue([]); + vi.mocked(insertAccountArtistId).mockResolvedValue(undefined); + + const res = await linkArtistToAccount({ accountId: ACCOUNT_ID, artistId: ARTIST_ID }); + + expect(res.status).toBe(200); + expect(insertAccountArtistId).toHaveBeenCalledWith(ACCOUNT_ID, ARTIST_ID); + }); + + it("returns success without inserting when the artist is already linked", async () => { + vi.mocked(getAccountArtistIds).mockResolvedValue([ + { artist_id: ARTIST_ID, account_id: ACCOUNT_ID }, + ] as never); + + const res = await linkArtistToAccount({ accountId: ACCOUNT_ID, artistId: ARTIST_ID }); + + expect(res.status).toBe(200); + expect(insertAccountArtistId).not.toHaveBeenCalled(); + }); + + it("returns 400 with a generic message (no raw error text) when the write fails", async () => { + vi.mocked(getAccountArtistIds).mockResolvedValue([]); + vi.mocked(insertAccountArtistId).mockRejectedValue(new Error("boom")); + + const res = await linkArtistToAccount({ accountId: ACCOUNT_ID, artistId: ARTIST_ID }); + const body = await res.json(); + + expect(res.status).toBe(400); + expect(body.message).toBe("Failed to add artist to account"); + expect(JSON.stringify(body)).not.toContain("boom"); + }); +}); diff --git a/lib/accounts/__tests__/resolveAddArtistAccountId.test.ts b/lib/accounts/__tests__/resolveAddArtistAccountId.test.ts new file mode 100644 index 000000000..aff59c3d2 --- /dev/null +++ b/lib/accounts/__tests__/resolveAddArtistAccountId.test.ts @@ -0,0 +1,74 @@ +import { describe, it, expect, vi, beforeEach } from "vitest"; +import { NextResponse } from "next/server"; +import { resolveAddArtistAccountId } from "../resolveAddArtistAccountId"; + +vi.mock("@/lib/accounts/resolveAccountIdByEmail", () => ({ + resolveAccountIdByEmail: vi.fn(), +})); + +vi.mock("@/lib/auth/checkAccountAccess", () => ({ + checkAccountAccess: vi.fn(), +})); + +vi.mock("@/lib/networking/getCorsHeaders", () => ({ + getCorsHeaders: vi.fn(() => ({ "Access-Control-Allow-Origin": "*" })), +})); + +const { resolveAccountIdByEmail } = await import("@/lib/accounts/resolveAccountIdByEmail"); +const { checkAccountAccess } = await import("@/lib/auth/checkAccountAccess"); + +const AUTH_ACCOUNT_ID = "22222222-2222-4222-8222-222222222222"; +const TARGET_ACCOUNT_ID = "33333333-3333-4333-8333-333333333333"; + +describe("resolveAddArtistAccountId", () => { + beforeEach(() => { + vi.clearAllMocks(); + }); + + it("returns the authenticated account when no email is provided", async () => { + const result = await resolveAddArtistAccountId(AUTH_ACCOUNT_ID, undefined); + + expect(result).toBe(AUTH_ACCOUNT_ID); + expect(resolveAccountIdByEmail).not.toHaveBeenCalled(); + expect(checkAccountAccess).not.toHaveBeenCalled(); + }); + + it("returns the account when email resolves to the caller's own account", async () => { + vi.mocked(resolveAccountIdByEmail).mockResolvedValue(AUTH_ACCOUNT_ID); + + const result = await resolveAddArtistAccountId(AUTH_ACCOUNT_ID, "me@example.com"); + + expect(result).toBe(AUTH_ACCOUNT_ID); + expect(checkAccountAccess).not.toHaveBeenCalled(); + }); + + it("returns the target account when the caller has access", async () => { + vi.mocked(resolveAccountIdByEmail).mockResolvedValue(TARGET_ACCOUNT_ID); + vi.mocked(checkAccountAccess).mockResolvedValue({ hasAccess: true, entityType: "artist" }); + + const result = await resolveAddArtistAccountId(AUTH_ACCOUNT_ID, "other@example.com"); + + expect(result).toBe(TARGET_ACCOUNT_ID); + expect(checkAccountAccess).toHaveBeenCalledWith(AUTH_ACCOUNT_ID, TARGET_ACCOUNT_ID); + }); + + it("returns 403 when the caller lacks access to the target account", async () => { + vi.mocked(resolveAccountIdByEmail).mockResolvedValue(TARGET_ACCOUNT_ID); + vi.mocked(checkAccountAccess).mockResolvedValue({ hasAccess: false }); + + const result = await resolveAddArtistAccountId(AUTH_ACCOUNT_ID, "other@example.com"); + + expect(result).toBeInstanceOf(NextResponse); + expect((result as NextResponse).status).toBe(403); + }); + + it("propagates the error response when the email is not found", async () => { + const notFound = NextResponse.json({ status: "error" }, { status: 404 }); + vi.mocked(resolveAccountIdByEmail).mockResolvedValue(notFound); + + const result = await resolveAddArtistAccountId(AUTH_ACCOUNT_ID, "missing@example.com"); + + expect(result).toBe(notFound); + expect(checkAccountAccess).not.toHaveBeenCalled(); + }); +}); diff --git a/lib/accounts/__tests__/validateAddArtistRequest.test.ts b/lib/accounts/__tests__/validateAddArtistRequest.test.ts new file mode 100644 index 000000000..42d7d7bfa --- /dev/null +++ b/lib/accounts/__tests__/validateAddArtistRequest.test.ts @@ -0,0 +1,104 @@ +import { describe, it, expect, vi, beforeEach } from "vitest"; +import { NextRequest, NextResponse } from "next/server"; +import { validateAddArtistRequest } from "../validateAddArtistRequest"; + +vi.mock("@/lib/auth/validateAuthContext", () => ({ + validateAuthContext: vi.fn(), +})); + +vi.mock("@/lib/accounts/resolveAddArtistAccountId", () => ({ + resolveAddArtistAccountId: vi.fn(), +})); + +vi.mock("@/lib/networking/getCorsHeaders", () => ({ + getCorsHeaders: vi.fn(() => ({ "Access-Control-Allow-Origin": "*" })), +})); + +const { validateAuthContext } = await import("@/lib/auth/validateAuthContext"); +const { resolveAddArtistAccountId } = await import("@/lib/accounts/resolveAddArtistAccountId"); + +const ARTIST_ID = "11111111-1111-4111-8111-111111111111"; +const AUTH_ACCOUNT_ID = "22222222-2222-4222-8222-222222222222"; +const TARGET_ACCOUNT_ID = "33333333-3333-4333-8333-333333333333"; + +function makeReq(body: Record): NextRequest { + return new NextRequest("https://example.com/api/accounts/artists", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify(body), + }); +} + +describe("validateAddArtistRequest", () => { + beforeEach(() => { + vi.clearAllMocks(); + }); + + it("returns a 400 response when artistId is missing (before auth)", async () => { + const result = await validateAddArtistRequest(makeReq({})); + + expect(result).toBeInstanceOf(NextResponse); + expect((result as NextResponse).status).toBe(400); + expect(validateAuthContext).not.toHaveBeenCalled(); + }); + + it("returns the auth error when no credential is provided", async () => { + vi.mocked(validateAuthContext).mockResolvedValue( + NextResponse.json({ status: "error" }, { status: 401 }), + ); + + const result = await validateAddArtistRequest(makeReq({ artistId: ARTIST_ID })); + + expect(result).toBeInstanceOf(NextResponse); + expect((result as NextResponse).status).toBe(401); + expect(resolveAddArtistAccountId).not.toHaveBeenCalled(); + }); + + it("derives the account from the authenticated credential when no email is given", async () => { + vi.mocked(validateAuthContext).mockResolvedValue({ + accountId: AUTH_ACCOUNT_ID, + orgId: null, + authToken: "token", + }); + vi.mocked(resolveAddArtistAccountId).mockResolvedValue(AUTH_ACCOUNT_ID); + + const result = await validateAddArtistRequest(makeReq({ artistId: ARTIST_ID })); + + expect(resolveAddArtistAccountId).toHaveBeenCalledWith(AUTH_ACCOUNT_ID, undefined); + expect(result).toEqual({ accountId: AUTH_ACCOUNT_ID, artistId: ARTIST_ID }); + }); + + it("passes through the resolved target account when an email override is accepted", async () => { + vi.mocked(validateAuthContext).mockResolvedValue({ + accountId: AUTH_ACCOUNT_ID, + orgId: null, + authToken: "token", + }); + vi.mocked(resolveAddArtistAccountId).mockResolvedValue(TARGET_ACCOUNT_ID); + + const result = await validateAddArtistRequest( + makeReq({ artistId: ARTIST_ID, email: "other@example.com" }), + ); + + expect(resolveAddArtistAccountId).toHaveBeenCalledWith(AUTH_ACCOUNT_ID, "other@example.com"); + expect(result).toEqual({ accountId: TARGET_ACCOUNT_ID, artistId: ARTIST_ID }); + }); + + it("returns the resolver error when an email override is denied", async () => { + vi.mocked(validateAuthContext).mockResolvedValue({ + accountId: AUTH_ACCOUNT_ID, + orgId: null, + authToken: "token", + }); + vi.mocked(resolveAddArtistAccountId).mockResolvedValue( + NextResponse.json({ status: "error" }, { status: 403 }), + ); + + const result = await validateAddArtistRequest( + makeReq({ artistId: ARTIST_ID, email: "other@example.com" }), + ); + + expect(result).toBeInstanceOf(NextResponse); + expect((result as NextResponse).status).toBe(403); + }); +}); diff --git a/lib/accounts/addArtistToAccountHandler.ts b/lib/accounts/addArtistToAccountHandler.ts index 88de76502..28a568c2c 100644 --- a/lib/accounts/addArtistToAccountHandler.ts +++ b/lib/accounts/addArtistToAccountHandler.ts @@ -1,45 +1,24 @@ +import type { NextRequest } from "next/server"; import { NextResponse } from "next/server"; -import { getCorsHeaders } from "@/lib/networking/getCorsHeaders"; -import { selectAccountByEmail } from "@/lib/supabase/account_emails/selectAccountByEmail"; -import { getAccountArtistIds } from "@/lib/supabase/account_artist_ids/getAccountArtistIds"; -import { insertAccountArtistId } from "@/lib/supabase/account_artist_ids/insertAccountArtistId"; -import type { AddArtistBody } from "./validateAddArtistBody"; +import { validateAddArtistRequest } from "@/lib/accounts/validateAddArtistRequest"; +import { linkArtistToAccount } from "@/lib/accounts/linkArtistToAccount"; /** - * Handles POST /api/accounts/artists - Add artist to account's artist list. + * Handler for POST /api/accounts/artists. * - * @param body - Validated request body with email and artistId - * @returns NextResponse with success status + * Validates the request (body + authentication + target-account resolution), + * then links the artist to the resolved account. The target account is derived + * from the credential; an optional `email` override is honored only when the + * caller has access to that account. + * + * @param request - The incoming request + * @returns NextResponse with success status or a validation error */ -export async function addArtistToAccountHandler(body: AddArtistBody): Promise { - const { email, artistId } = body; - - try { - // Find account by email - const accountEmail = await selectAccountByEmail(email); - if (!accountEmail?.account_id) { - return NextResponse.json( - { message: "Not found account." }, - { status: 400, headers: getCorsHeaders() }, - ); - } - - const accountId = accountEmail.account_id; - - // Check if artist is already associated with account - const existingArtists = await getAccountArtistIds({ accountIds: [accountId] }); - const alreadyExists = existingArtists.some(a => a.artist_id === artistId); - - if (alreadyExists) { - return NextResponse.json({ success: true }, { status: 200, headers: getCorsHeaders() }); - } - - // Add artist to account - await insertAccountArtistId(accountId, artistId); - - return NextResponse.json({ success: true }, { status: 200, headers: getCorsHeaders() }); - } catch (error) { - const message = error instanceof Error ? error.message : "failed"; - return NextResponse.json({ message }, { status: 400, headers: getCorsHeaders() }); +export async function addArtistToAccountHandler(request: NextRequest): Promise { + const validated = await validateAddArtistRequest(request); + if (validated instanceof NextResponse) { + return validated; } + + return linkArtistToAccount(validated); } diff --git a/lib/accounts/linkArtistToAccount.ts b/lib/accounts/linkArtistToAccount.ts new file mode 100644 index 000000000..269b63aed --- /dev/null +++ b/lib/accounts/linkArtistToAccount.ts @@ -0,0 +1,42 @@ +import { NextResponse } from "next/server"; +import { getCorsHeaders } from "@/lib/networking/getCorsHeaders"; +import { getAccountArtistIds } from "@/lib/supabase/account_artist_ids/getAccountArtistIds"; +import { insertAccountArtistId } from "@/lib/supabase/account_artist_ids/insertAccountArtistId"; +import type { AddArtistParams } from "@/lib/accounts/validateAddArtistRequest"; + +/** + * Links an artist to an account's artist list (the business step of + * POST /api/accounts/artists). Idempotent: returns success without inserting + * when the link already exists. + * + * The account ID must already be resolved from the authenticated credential + * (see validateAddArtistRequest) — never from unauthenticated user input. + * + * @param params - The resolved account ID and artist ID to link + * @returns NextResponse with success status + */ +export async function linkArtistToAccount({ + accountId, + artistId, +}: AddArtistParams): Promise { + try { + // Check if artist is already associated with account + const existingArtists = await getAccountArtistIds({ accountIds: [accountId] }); + const alreadyExists = existingArtists.some(a => a.artist_id === artistId); + + if (alreadyExists) { + return NextResponse.json({ success: true }, { status: 200, headers: getCorsHeaders() }); + } + + // Add artist to account + await insertAccountArtistId(accountId, artistId); + + return NextResponse.json({ success: true }, { status: 200, headers: getCorsHeaders() }); + } catch (error) { + console.error("[ERROR] Failed to link artist to account:", error); + return NextResponse.json( + { message: "Failed to add artist to account" }, + { status: 400, headers: getCorsHeaders() }, + ); + } +} diff --git a/lib/accounts/resolveAddArtistAccountId.ts b/lib/accounts/resolveAddArtistAccountId.ts new file mode 100644 index 000000000..eb3623aa3 --- /dev/null +++ b/lib/accounts/resolveAddArtistAccountId.ts @@ -0,0 +1,43 @@ +import { NextResponse } from "next/server"; +import { getCorsHeaders } from "@/lib/networking/getCorsHeaders"; +import { resolveAccountIdByEmail } from "@/lib/accounts/resolveAccountIdByEmail"; +import { checkAccountAccess } from "@/lib/auth/checkAccountAccess"; + +/** + * Resolves the target account for POST /api/accounts/artists. + * + * Defaults to the authenticated account. When an email override is provided, + * the email is resolved to an account and the caller must have access to it + * (self, managed artist, owned workspace, or member organization). + * + * @param authenticatedAccountId - The account ID derived from the credential + * @param email - Optional email override for the target account + * @returns The target account ID, or a NextResponse error (404/403) + */ +export async function resolveAddArtistAccountId( + authenticatedAccountId: string, + email?: string, +): Promise { + if (!email) { + return authenticatedAccountId; + } + + const targetAccountId = await resolveAccountIdByEmail(email); + if (targetAccountId instanceof NextResponse) { + return targetAccountId; + } + + if (targetAccountId === authenticatedAccountId) { + return targetAccountId; + } + + const { hasAccess } = await checkAccountAccess(authenticatedAccountId, targetAccountId); + if (!hasAccess) { + return NextResponse.json( + { status: "error", error: "Access denied to the account for the provided email" }, + { status: 403, headers: getCorsHeaders() }, + ); + } + + return targetAccountId; +} diff --git a/lib/accounts/validateAddArtistBody.ts b/lib/accounts/validateAddArtistBody.ts index 5f3e599b9..58477b66c 100644 --- a/lib/accounts/validateAddArtistBody.ts +++ b/lib/accounts/validateAddArtistBody.ts @@ -3,7 +3,7 @@ import { getCorsHeaders } from "@/lib/networking/getCorsHeaders"; import { z } from "zod"; export const addArtistBodySchema = z.object({ - email: z.string().email("email must be a valid email address"), + email: z.string().email("email must be a valid email address").optional(), artistId: z.string().uuid("artistId must be a valid UUID"), }); diff --git a/lib/accounts/validateAddArtistRequest.ts b/lib/accounts/validateAddArtistRequest.ts new file mode 100644 index 000000000..dd2c96783 --- /dev/null +++ b/lib/accounts/validateAddArtistRequest.ts @@ -0,0 +1,51 @@ +import type { NextRequest } from "next/server"; +import { NextResponse } from "next/server"; +import { safeParseJson } from "@/lib/networking/safeParseJson"; +import { validateAddArtistBody, type AddArtistBody } from "@/lib/accounts/validateAddArtistBody"; +import { validateAuthContext } from "@/lib/auth/validateAuthContext"; +import { resolveAddArtistAccountId } from "@/lib/accounts/resolveAddArtistAccountId"; + +/** + * Validated params for adding an artist to an account. + */ +export interface AddArtistParams { + accountId: string; + artistId: string; +} + +/** + * Validates the full POST /api/accounts/artists request. + * + * Handles, in order: + * 1. Body validation (`artistId` required, optional `email`) — a malformed + * body 400s before authentication is checked. + * 2. Authentication (x-api-key or Bearer token). + * 3. Target account resolution — defaults to the authenticated account; an + * `email` override is honored only when the caller has access to it. + * + * @param request - The incoming request + * @returns A NextResponse error (400/401/403/404) or the validated params + */ +export async function validateAddArtistRequest( + request: NextRequest, +): Promise { + const body = await safeParseJson(request); + + const validated = validateAddArtistBody(body); + if (validated instanceof NextResponse) { + return validated; + } + const { email, artistId } = validated as AddArtistBody; + + const authResult = await validateAuthContext(request); + if (authResult instanceof NextResponse) { + return authResult; + } + + const accountIdOrError = await resolveAddArtistAccountId(authResult.accountId, email); + if (accountIdOrError instanceof NextResponse) { + return accountIdOrError; + } + + return { accountId: accountIdOrError, artistId }; +}