From 5c370aa0a9489f2fdfc64c5f89c0fdd6717f747d Mon Sep 17 00:00:00 2001 From: alikia2x Date: Mon, 20 Apr 2026 01:14:30 +0800 Subject: [PATCH 1/2] feat(artist): add CRUD endpoints for artists Implement a complete CRUD API for managing artists in the catalog. Changes: - Add GET /v2/artist/:id endpoint for retrieving artist details - Add POST /v2/artist endpoint for creating new artists (auth required) - Add PATCH /v2/artist/:id endpoint for updating artist metadata (auth required) - Add DELETE /v2/artist/:id endpoint for soft-deleting artists (auth required) - Add Zod schemas for request/response validation in packages/core - Add E2E tests covering all endpoints with 15 test cases --- .../src/handlers/catalog/artist/create.ts | 33 ++ .../src/handlers/catalog/artist/delete.ts | 34 ++ .../src/handlers/catalog/artist/get.ts | 29 ++ .../src/handlers/catalog/artist/index.ts | 11 + .../src/handlers/catalog/artist/update.ts | 39 ++ apps/backend/src/handlers/index.ts | 1 + apps/backend/src/index.ts | 3 +- apps/backend/tests/artist.test.ts | 335 ++++++++++++++++++ .../src/modules/catalog/artist/container.ts | 6 + .../core/src/modules/catalog/artist/dto.ts | 31 ++ .../core/src/modules/catalog/artist/index.ts | 5 + .../catalog/artist/repository.interface.ts | 23 ++ .../src/modules/catalog/artist/repository.ts | 64 ++++ .../src/modules/catalog/artist/service.ts | 50 +++ packages/core/src/modules/catalog/index.ts | 1 + 15 files changed, 664 insertions(+), 1 deletion(-) create mode 100644 apps/backend/src/handlers/catalog/artist/create.ts create mode 100644 apps/backend/src/handlers/catalog/artist/delete.ts create mode 100644 apps/backend/src/handlers/catalog/artist/get.ts create mode 100644 apps/backend/src/handlers/catalog/artist/index.ts create mode 100644 apps/backend/src/handlers/catalog/artist/update.ts create mode 100644 apps/backend/tests/artist.test.ts create mode 100644 packages/core/src/modules/catalog/artist/container.ts create mode 100644 packages/core/src/modules/catalog/artist/dto.ts create mode 100644 packages/core/src/modules/catalog/artist/index.ts create mode 100644 packages/core/src/modules/catalog/artist/repository.interface.ts create mode 100644 packages/core/src/modules/catalog/artist/repository.ts create mode 100644 packages/core/src/modules/catalog/artist/service.ts diff --git a/apps/backend/src/handlers/catalog/artist/create.ts b/apps/backend/src/handlers/catalog/artist/create.ts new file mode 100644 index 0000000..a20a2b9 --- /dev/null +++ b/apps/backend/src/handlers/catalog/artist/create.ts @@ -0,0 +1,33 @@ +import { Elysia } from "elysia"; +import { + CreateArtistRequestSchema, + ErrorResponseSchema, + ArtistResponseSchema, + artistService, +} from "@cvsa/core"; +import { authMiddleware } from "@/middlewares"; +import { traceTask } from "@/common/trace"; + +export const artistCreateHandler = new Elysia({ name: "artistCreateHandler" }) + .use(authMiddleware) + .post( + "/artist", + async ({ body, status }) => { + const artist = await traceTask("artistService.create", async () => { + return await artistService.create(body); + }); + return status(201, artist); + }, + { + body: CreateArtistRequestSchema, + detail: { + summary: "Create Artist", + description: + "Create a new artist entry in the catalog. Requires authentication.", + }, + response: { + 201: ArtistResponseSchema, + 401: ErrorResponseSchema, + }, + } + ); diff --git a/apps/backend/src/handlers/catalog/artist/delete.ts b/apps/backend/src/handlers/catalog/artist/delete.ts new file mode 100644 index 0000000..d6abec0 --- /dev/null +++ b/apps/backend/src/handlers/catalog/artist/delete.ts @@ -0,0 +1,34 @@ +import { Elysia } from "elysia"; +import { z } from "zod"; +import { ErrorResponseSchema, artistService } from "@cvsa/core"; +import { authMiddleware } from "@/middlewares"; +import { traceTask } from "@/common/trace"; + +export const artistDeleteHandler = new Elysia({ name: "artistDeleteHandler" }) + .use(authMiddleware) + .delete( + "/artist/:id", + async ({ params, set }) => { + await traceTask("artistService.delete", async () => { + return await artistService.delete(params.id); + }); + set.status = 204; + return null; + }, + { + params: z.object({ + id: z.coerce.number().int().positive(), + }), + detail: { + summary: "Delete Artist", + description: + "Soft delete an artist from the catalog. Requires authentication. The artist record is marked as deleted but retained in the database for data integrity.", + }, + response: { + 204: z.null(), + 400: ErrorResponseSchema, + 401: ErrorResponseSchema, + 404: ErrorResponseSchema, + }, + } + ); diff --git a/apps/backend/src/handlers/catalog/artist/get.ts b/apps/backend/src/handlers/catalog/artist/get.ts new file mode 100644 index 0000000..9e8096f --- /dev/null +++ b/apps/backend/src/handlers/catalog/artist/get.ts @@ -0,0 +1,29 @@ +import { Elysia } from "elysia"; +import { ErrorResponseSchema, ArtistDetailsResponseSchema, artistService } from "@cvsa/core"; +import z from "zod"; +import { traceTask } from "@/common/trace"; + +export const artistDetailsHandler = new Elysia().get( + "/artist/:id", + async ({ params, status }) => { + const artist = await traceTask("artistService.getDetails", async () => { + return await artistService.getDetails(params.id); + }); + return status(200, artist); + }, + { + detail: { + summary: "Artist Details", + description: + "Retrieve detailed information about a specific artist by its ID. Includes name, description, aliases, and metadata.", + }, + response: { + 200: ArtistDetailsResponseSchema, + 400: ErrorResponseSchema, + 404: ErrorResponseSchema, + }, + params: z.object({ + id: z.coerce.number().int().positive(), + }), + } +); diff --git a/apps/backend/src/handlers/catalog/artist/index.ts b/apps/backend/src/handlers/catalog/artist/index.ts new file mode 100644 index 0000000..498c5f5 --- /dev/null +++ b/apps/backend/src/handlers/catalog/artist/index.ts @@ -0,0 +1,11 @@ +import { Elysia } from "elysia"; +import { artistCreateHandler } from "./create"; +import { artistUpdateHandler } from "./update"; +import { artistDeleteHandler } from "./delete"; +import { artistDetailsHandler } from "./get"; + +export const artistHandler = new Elysia({ name: "artistHandler" }) + .use(artistDetailsHandler) + .use(artistCreateHandler) + .use(artistUpdateHandler) + .use(artistDeleteHandler); diff --git a/apps/backend/src/handlers/catalog/artist/update.ts b/apps/backend/src/handlers/catalog/artist/update.ts new file mode 100644 index 0000000..6162564 --- /dev/null +++ b/apps/backend/src/handlers/catalog/artist/update.ts @@ -0,0 +1,39 @@ +import { Elysia } from "elysia"; +import { z } from "zod"; +import { + UpdateArtistRequestSchema, + ErrorResponseSchema, + artistService, + ArtistResponseSchema, +} from "@cvsa/core"; +import { authMiddleware } from "@/middlewares"; +import { traceTask } from "@/common/trace"; + +export const artistUpdateHandler = new Elysia({ name: "artistUpdateHandler" }) + .use(authMiddleware) + .patch( + "/artist/:id", + async ({ params, body, status }) => { + const artist = await traceTask("artistService.update", async () => { + return await artistService.update(params.id, body); + }); + return status(200, artist); + }, + { + body: UpdateArtistRequestSchema, + params: z.object({ + id: z.coerce.number().int().positive(), + }), + detail: { + summary: "Update Artist", + description: + "Update metadata of an existing artist identified by its ID. Requires authentication.", + }, + response: { + 200: ArtistResponseSchema, + 400: ErrorResponseSchema, + 401: ErrorResponseSchema, + 404: ErrorResponseSchema, + }, + } + ); diff --git a/apps/backend/src/handlers/index.ts b/apps/backend/src/handlers/index.ts index 0b8a509..768f891 100644 --- a/apps/backend/src/handlers/index.ts +++ b/apps/backend/src/handlers/index.ts @@ -1,4 +1,5 @@ export * from "./auth"; export * from "./catalog/song"; export * from "./catalog/engine"; +export * from "./catalog/artist"; export * from "./dev"; diff --git a/apps/backend/src/index.ts b/apps/backend/src/index.ts index c4acb23..216dfd7 100644 --- a/apps/backend/src/index.ts +++ b/apps/backend/src/index.ts @@ -2,7 +2,7 @@ import { Elysia } from "elysia"; import { onAfterHandler } from "./onAfterHandle"; import { getBindingInfo, logStartup } from "./startMessage"; import pkg from "../package.json"; -import { authHandler, songHandler, engineHandler } from "@handlers/index"; +import { authHandler, songHandler, engineHandler, artistHandler } from "@handlers/index"; import { errorHandler } from "./errorHandler"; import { openapi } from "@elysiajs/openapi"; import { requestLoggerMiddleware } from "@/middlewares"; @@ -33,6 +33,7 @@ export const app = new Elysia({ .use(authHandler) .use(songHandler) .use(engineHandler) + .use(artistHandler) .use(devHandler) .listen(16412); diff --git a/apps/backend/tests/artist.test.ts b/apps/backend/tests/artist.test.ts new file mode 100644 index 0000000..dd353a2 --- /dev/null +++ b/apps/backend/tests/artist.test.ts @@ -0,0 +1,335 @@ +import { afterAll, beforeAll, describe, expect, test } from "bun:test"; +import { treaty } from "@elysiajs/eden"; +import { app } from "@/index"; +import { prisma } from "@cvsa/db"; + +const api = treaty(app); + +describe("Artist E2E Tests", () => { + beforeAll(async () => { + await prisma.$connect(); + await prisma.session.deleteMany(); + await prisma.user.deleteMany(); + await prisma.artist.deleteMany(); + }); + + afterAll(async () => { + await prisma.session.deleteMany(); + await prisma.user.deleteMany(); + await prisma.$disconnect(); + }); + + async function getAuthToken() { + const signup = await api.v2.user.post({ + username: `${Math.random()}`, + password: "password123", + }); + return signup.data?.data.token; + } + + describe("GET /v2/artist/:id - Get Artist Details", () => { + test("should retrieve an artist", async () => { + const artist = await prisma.artist.create({ + data: { + name: "Test Artist", + description: "Test Description", + language: "zh", + aliases: ["Alias1", "Alias2"], + }, + }); + + const { data, status } = await api.v2.artist({ id: artist.id }).get(); + + expect(status).toBe(200); + expect(data).toMatchObject({ + id: expect.any(Number), + name: "Test Artist", + description: "Test Description", + language: "zh", + aliases: ["Alias1", "Alias2"], + }); + }); + + test("should return 404 for nonexistent artist", async () => { + const { error, status } = await api.v2.artist({ id: 99999 }).get(); + + expect(status).toBe(404); + expect(error?.value).toMatchObject({ + code: "NOT_FOUND", + }); + }); + + test("should not retrieve soft-deleted artist", async () => { + const artist = await prisma.artist.create({ + data: { + name: "Deleted Artist", + deletedAt: new Date(), + }, + }); + + const { error, status } = await api.v2.artist({ id: artist.id }).get(); + + expect(status).toBe(404); + expect(error?.value).toMatchObject({ + code: "NOT_FOUND", + }); + }); + }); + + describe("POST /v2/artist - Create Artist", () => { + test("should create an artist with authentication", async () => { + const token = await getAuthToken(); + + const payload = { + name: "New Artist", + description: "A talented producer", + language: "zh", + aliases: ["Producer X", "PX"], + }; + + const { data, status } = await api.v2.artist.post(payload, { + headers: { + authorization: `Bearer ${token}`, + }, + }); + + expect(status).toBe(201); + expect(data).toMatchObject({ + id: expect.any(Number), + name: "New Artist", + description: "A talented producer", + language: "zh", + aliases: ["Producer X", "PX"], + }); + }); + + test("should return 401 without authentication", async () => { + const payload = { + name: "Test Artist", + }; + + const { error, status } = await api.v2.artist.post(payload); + + expect(status).toBe(401); + expect(error?.value).toMatchObject({ + code: "UNAUTHORIZED", + }); + }); + + test("should create an artist with localized names and descriptions", async () => { + const token = await getAuthToken(); + + const payload = { + name: "Artist Name", + localizedNames: { + zh: "艺术家名称", + ja: "アーティスト名", + }, + description: "Description in English", + localizedDescriptions: { + zh: "中文描述", + ja: "日本語の説明", + }, + language: "zh", + }; + + const { data, status } = await api.v2.artist.post(payload, { + headers: { + authorization: `Bearer ${token}`, + }, + }); + + expect(status).toBe(201); + expect(data).toMatchObject({ + id: expect.any(Number), + name: "Artist Name", + localizedNames: { + zh: "艺术家名称", + ja: "アーティスト名", + }, + localizedDescriptions: { + zh: "中文描述", + ja: "日本語の説明", + }, + }); + }); + + test("should create an artist with minimal fields", async () => { + const token = await getAuthToken(); + + const { data, status } = await api.v2.artist.post({}, { + headers: { + authorization: `Bearer ${token}`, + }, + }); + + expect(status).toBe(201); + expect(data).toMatchObject({ + id: expect.any(Number), + }); + }); + }); + + describe("PATCH /v2/artist/:id - Update Artist", () => { + test("should update an artist with authentication", async () => { + const token = await getAuthToken(); + + const artist = await prisma.artist.create({ + data: { + name: "Original Name", + description: "Original Description", + language: "zh", + }, + }); + + const { data, status } = await api.v2.artist({ id: artist.id }).patch( + { name: "Updated Name", description: "Updated Description", language: "en" }, + { + headers: { + authorization: `Bearer ${token}`, + }, + } + ); + + expect(status).toBe(200); + expect(data?.name).toBe("Updated Name"); + expect(data?.description).toBe("Updated Description"); + expect(data?.language).toBe("en"); + }); + + test("should return 401 without authentication", async () => { + const artist = await prisma.artist.create({ + data: { + name: "Test Artist", + }, + }); + + const { status } = await api.v2.artist({ id: artist.id }).patch({ + name: "Updated Name", + }); + + expect(status).toBe(401); + }); + + test("should return 404 for non-existent artist", async () => { + const token = await getAuthToken(); + + const { error, status } = await api.v2.artist({ id: 999999 }).patch( + { name: "Updated Name" }, + { + headers: { + authorization: `Bearer ${token}`, + }, + } + ); + + expect(status).toBe(404); + expect(error?.value).toMatchObject({ + code: "NOT_FOUND", + }); + }); + + test("should update aliases array", async () => { + const token = await getAuthToken(); + + const artist = await prisma.artist.create({ + data: { + name: "Artist", + aliases: ["Old Alias"], + }, + }); + + const { data, status } = await api.v2.artist({ id: artist.id }).patch( + { aliases: ["New Alias 1", "New Alias 2"] }, + { + headers: { + authorization: `Bearer ${token}`, + }, + } + ); + + expect(status).toBe(200); + expect(data?.aliases).toEqual(["New Alias 1", "New Alias 2"]); + }); + }); + + describe("DELETE /v2/artist/:id - Delete Artist", () => { + test("should soft delete an artist with authentication", async () => { + const token = await getAuthToken(); + + const artist = await prisma.artist.create({ + data: { + name: "Artist to Delete", + }, + }); + + const { status } = await api.v2.artist({ id: artist.id }).delete( + {}, + { + headers: { + authorization: `Bearer ${token}`, + }, + } + ); + + expect(status).toBe(204); + + const deletedArtist = await prisma.artist.findUnique({ + where: { id: artist.id }, + }); + expect(deletedArtist?.deletedAt).not.toBeNull(); + }); + + test("should return 401 without authentication", async () => { + const artist = await prisma.artist.create({ + data: { + name: "Test Artist", + }, + }); + + const { status } = await api.v2.artist({ id: artist.id }).delete({}, {}); + + expect(status).toBe(401); + }); + + test("should return 404 for non-existent artist", async () => { + const token = await getAuthToken(); + + const { status } = await api.v2.artist({ id: 999999 }).delete( + {}, + { + headers: { + authorization: `Bearer ${token}`, + }, + } + ); + + expect(status).toBe(404); + }); + + test("should return 404 when trying to delete already deleted artist", async () => { + const token = await getAuthToken(); + + const artist = await prisma.artist.create({ + data: { + name: "Already Deleted", + deletedAt: new Date(), + }, + }); + + const { error, status } = await api.v2.artist({ id: artist.id }).delete( + {}, + { + headers: { + authorization: `Bearer ${token}`, + }, + } + ); + + expect(status).toBe(404); + expect(error?.value).toMatchObject({ + code: "NOT_FOUND", + }); + }); + }); +}); diff --git a/packages/core/src/modules/catalog/artist/container.ts b/packages/core/src/modules/catalog/artist/container.ts new file mode 100644 index 0000000..f8c2c71 --- /dev/null +++ b/packages/core/src/modules/catalog/artist/container.ts @@ -0,0 +1,6 @@ +import { prisma } from "@cvsa/db"; +import { ArtistRepository } from "./repository"; +import { ArtistService } from "./service"; + +export const artistRepository = new ArtistRepository(prisma); +export const artistService = new ArtistService(artistRepository); diff --git a/packages/core/src/modules/catalog/artist/dto.ts b/packages/core/src/modules/catalog/artist/dto.ts new file mode 100644 index 0000000..2ac8d0d --- /dev/null +++ b/packages/core/src/modules/catalog/artist/dto.ts @@ -0,0 +1,31 @@ +import { z } from "zod"; +import { ArtistSchema, type Serialized } from "@cvsa/db"; + +export type ArtistId = number; + +export const CreateArtistRequestSchema = z.object({ + name: z.string().optional(), + localizedNames: z.record(z.string(), z.string()).optional(), + language: z.string().optional(), + aliases: z.array(z.string()).optional(), + description: z.string().optional(), + localizedDescriptions: z.record(z.string(), z.string()).optional(), +}); + +export const UpdateArtistRequestSchema = z.object({ + name: z.string().optional(), + localizedNames: z.record(z.string(), z.string()).optional(), + language: z.string().optional(), + aliases: z.array(z.string()).optional(), + description: z.string().optional(), + localizedDescriptions: z.record(z.string(), z.string()).optional(), +}); + +export const ArtistResponseSchema = ArtistSchema.omit({ deletedAt: true }); + +export const ArtistDetailsResponseSchema = ArtistResponseSchema; + +export type ArtistResponseDto = Serialized>; +export type ArtistDetailsResponseDto = Serialized>; +export type CreateArtistRequestDto = Serialized>; +export type UpdateArtistRequestDto = Serialized>; diff --git a/packages/core/src/modules/catalog/artist/index.ts b/packages/core/src/modules/catalog/artist/index.ts new file mode 100644 index 0000000..132cf0c --- /dev/null +++ b/packages/core/src/modules/catalog/artist/index.ts @@ -0,0 +1,5 @@ +export * from "./dto"; +export * from "./repository"; +export * from "./service"; +export * from "./repository.interface"; +export * from "./container"; diff --git a/packages/core/src/modules/catalog/artist/repository.interface.ts b/packages/core/src/modules/catalog/artist/repository.interface.ts new file mode 100644 index 0000000..4f286aa --- /dev/null +++ b/packages/core/src/modules/catalog/artist/repository.interface.ts @@ -0,0 +1,23 @@ +import type { TxClient } from "@cvsa/db"; +import type { IRepositoryWithGetDetails } from "@cvsa/core/internal"; +import type { + CreateArtistRequestDto, + ArtistId, + UpdateArtistRequestDto, + ArtistResponseDto, + ArtistDetailsResponseDto, +} from "./dto"; + +export abstract class IArtistRepository + implements IRepositoryWithGetDetails +{ + abstract getById(id: ArtistId, tx?: TxClient): Promise; + abstract getDetailsById(id: ArtistId, tx?: TxClient): Promise; + abstract create(input: CreateArtistRequestDto, tx?: TxClient): Promise; + abstract update( + id: ArtistId, + input: UpdateArtistRequestDto, + tx?: TxClient + ): Promise; + abstract softDelete(id: ArtistId, tx?: TxClient): Promise; +} diff --git a/packages/core/src/modules/catalog/artist/repository.ts b/packages/core/src/modules/catalog/artist/repository.ts new file mode 100644 index 0000000..0897f52 --- /dev/null +++ b/packages/core/src/modules/catalog/artist/repository.ts @@ -0,0 +1,64 @@ +import type { PrismaClient } from "@cvsa/db"; +import type { + CreateArtistRequestDto, + ArtistId, + UpdateArtistRequestDto, + ArtistDetailsResponseDto, +} from "./dto"; +import type { IArtistRepository } from "./repository.interface"; +import { transformPrismaResult, type TxClient } from "@cvsa/db"; + +export class ArtistRepository implements IArtistRepository { + constructor(private readonly prisma: PrismaClient) {} + + async getById(id: ArtistId, tx?: TxClient) { + const client = tx ?? this.prisma; + return transformPrismaResult( + await client.artist.findFirst({ + where: { id, deletedAt: null }, + omit: { deletedAt: true }, + }) + ); + } + + async getDetailsById(id: ArtistId, tx?: TxClient): Promise { + const client = tx ?? this.prisma; + return transformPrismaResult( + await client.artist.findFirst({ + where: { id, deletedAt: null }, + omit: { deletedAt: true }, + }) + ); + } + + async create(input: CreateArtistRequestDto, tx?: TxClient) { + const client = tx ?? this.prisma; + + return transformPrismaResult( + await client.artist.create({ + data: input, + omit: { deletedAt: true }, + }) + ); + } + + async update(id: ArtistId, input: UpdateArtistRequestDto, tx?: TxClient) { + const client = tx ?? this.prisma; + + return transformPrismaResult( + await client.artist.update({ + where: { id }, + data: input, + omit: { deletedAt: true }, + }) + ); + } + + async softDelete(id: ArtistId, tx?: TxClient) { + const client = tx ?? this.prisma; + await client.artist.update({ + where: { id }, + data: { deletedAt: new Date() }, + }); + } +} diff --git a/packages/core/src/modules/catalog/artist/service.ts b/packages/core/src/modules/catalog/artist/service.ts new file mode 100644 index 0000000..d688934 --- /dev/null +++ b/packages/core/src/modules/catalog/artist/service.ts @@ -0,0 +1,50 @@ +import { AppError, type IServiceWithGetDetails } from "@cvsa/core/internal"; +import type { + ArtistDetailsResponseDto, + ArtistId, + CreateArtistRequestDto, + UpdateArtistRequestDto, + ArtistResponseDto, +} from "./dto"; +import type { IArtistRepository } from "./repository.interface"; +import { traceTask } from "@cvsa/observability"; + +export class ArtistService implements IServiceWithGetDetails { + constructor(private readonly repository: IArtistRepository) {} + + async getDetails(id: ArtistId) { + return traceTask("db findOne artist", async () => { + const result = await this.repository.getDetailsById(id); + if (result === null) { + throw new AppError("error.artist.notfound", "NOT_FOUND", 404); + } + return result; + }); + } + + async create(input: CreateArtistRequestDto): Promise { + return traceTask("db create artist", async () => { + return await this.repository.create(input); + }); + } + + async update(id: ArtistId, input: UpdateArtistRequestDto): Promise { + const existing = await this.repository.getById(id); + if (existing === null) { + throw new AppError("error.artist.notfound", "NOT_FOUND", 404); + } + return traceTask("db update artist", async () => { + return await this.repository.update(id, input); + }); + } + + async delete(id: ArtistId): Promise { + const existing = await this.repository.getById(id); + if (existing === null) { + throw new AppError("error.artist.notfound", "NOT_FOUND", 404); + } + await traceTask("db delete artist", async () => { + return await this.repository.softDelete(id); + }); + } +} diff --git a/packages/core/src/modules/catalog/index.ts b/packages/core/src/modules/catalog/index.ts index 69b91cc..6b142ba 100644 --- a/packages/core/src/modules/catalog/index.ts +++ b/packages/core/src/modules/catalog/index.ts @@ -1,2 +1,3 @@ export * from "./song"; export * from "./engine"; +export * from "./artist"; From 2bc4cdb731ed2baa7927b1c112870867dd68561d Mon Sep 17 00:00:00 2001 From: alikia2x Date: Mon, 20 Apr 2026 16:28:26 +0800 Subject: [PATCH 2/2] format: reformat with Biome --- apps/backend/src/handlers/catalog/artist/create.ts | 3 +-- apps/backend/tests/artist.test.ts | 13 ++++++++----- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/apps/backend/src/handlers/catalog/artist/create.ts b/apps/backend/src/handlers/catalog/artist/create.ts index a20a2b9..d4de3d0 100644 --- a/apps/backend/src/handlers/catalog/artist/create.ts +++ b/apps/backend/src/handlers/catalog/artist/create.ts @@ -22,8 +22,7 @@ export const artistCreateHandler = new Elysia({ name: "artistCreateHandler" }) body: CreateArtistRequestSchema, detail: { summary: "Create Artist", - description: - "Create a new artist entry in the catalog. Requires authentication.", + description: "Create a new artist entry in the catalog. Requires authentication.", }, response: { 201: ArtistResponseSchema, diff --git a/apps/backend/tests/artist.test.ts b/apps/backend/tests/artist.test.ts index dd353a2..93f7f6f 100644 --- a/apps/backend/tests/artist.test.ts +++ b/apps/backend/tests/artist.test.ts @@ -157,11 +157,14 @@ describe("Artist E2E Tests", () => { test("should create an artist with minimal fields", async () => { const token = await getAuthToken(); - const { data, status } = await api.v2.artist.post({}, { - headers: { - authorization: `Bearer ${token}`, - }, - }); + const { data, status } = await api.v2.artist.post( + {}, + { + headers: { + authorization: `Bearer ${token}`, + }, + } + ); expect(status).toBe(201); expect(data).toMatchObject({