From d0540dca55c8beee598e2b010409cc69fdcd7591 Mon Sep 17 00:00:00 2001 From: Matt Ciaccio Date: Wed, 29 Apr 2026 15:14:40 +0200 Subject: [PATCH] fix(build): extract route.ts handlers to handlers.ts (CLAUDE.md convention) 8 API route files were exporting handler functions directly from route.ts, which Next.js 15 rejects with "$NAME is not a valid Route export field". Per CLAUDE.md convention, service-tested handler functions live in sibling handlers.ts files and route.ts only re-exports the GET/POST/etc. wrapped in withAuth(withPermission(...)). Discovered during the mobile-foundation Task 24 build validation; the route files predate this branch but the build was never re-run on data-model. Files: - berth-reservations/[id], companies/autocomplete, companies/[id]/members + nested mid/set-primary, yachts/autocomplete, yachts/[id]/transfer, yachts/[id]/ownership-history - Integration tests updated to import from handlers.ts (companies, memberships, reservations, yachts-detail) Co-Authored-By: Claude Opus 4.7 (1M context) --- .../v1/berth-reservations/[id]/handlers.ts | 107 +++++++++++++++++ .../api/v1/berth-reservations/[id]/route.ts | 108 +----------------- .../companies/[id]/members/[mid]/handlers.ts | 47 ++++++++ .../v1/companies/[id]/members/[mid]/route.ts | 48 +------- .../members/[mid]/set-primary/handlers.ts | 19 +++ .../[id]/members/[mid]/set-primary/route.ts | 20 +--- .../api/v1/companies/[id]/members/handlers.ts | 40 +++++++ .../api/v1/companies/[id]/members/route.ts | 41 +------ .../api/v1/companies/autocomplete/handlers.ts | 18 +++ .../api/v1/companies/autocomplete/route.ts | 19 +-- .../yachts/[id]/ownership-history/handlers.ts | 14 +++ .../v1/yachts/[id]/ownership-history/route.ts | 15 +-- .../api/v1/yachts/[id]/transfer/handlers.ts | 22 ++++ src/app/api/v1/yachts/[id]/transfer/route.ts | 23 +--- .../api/v1/yachts/autocomplete/handlers.ts | 18 +++ src/app/api/v1/yachts/autocomplete/route.ts | 19 +-- tests/integration/api/companies.test.ts | 2 +- tests/integration/api/memberships.test.ts | 6 +- tests/integration/api/reservations.test.ts | 2 +- tests/integration/api/yachts-detail.test.ts | 6 +- 20 files changed, 309 insertions(+), 285 deletions(-) create mode 100644 src/app/api/v1/berth-reservations/[id]/handlers.ts create mode 100644 src/app/api/v1/companies/[id]/members/[mid]/handlers.ts create mode 100644 src/app/api/v1/companies/[id]/members/[mid]/set-primary/handlers.ts create mode 100644 src/app/api/v1/companies/[id]/members/handlers.ts create mode 100644 src/app/api/v1/companies/autocomplete/handlers.ts create mode 100644 src/app/api/v1/yachts/[id]/ownership-history/handlers.ts create mode 100644 src/app/api/v1/yachts/[id]/transfer/handlers.ts create mode 100644 src/app/api/v1/yachts/autocomplete/handlers.ts diff --git a/src/app/api/v1/berth-reservations/[id]/handlers.ts b/src/app/api/v1/berth-reservations/[id]/handlers.ts new file mode 100644 index 0000000..46d51a0 --- /dev/null +++ b/src/app/api/v1/berth-reservations/[id]/handlers.ts @@ -0,0 +1,107 @@ +import { NextResponse } from 'next/server'; +import { z } from 'zod'; + +import { type RouteHandler } from '@/lib/api/helpers'; +import { parseBody } from '@/lib/api/route-helpers'; +import { requirePermission } from '@/lib/auth/permissions'; +import { errorResponse } from '@/lib/errors'; +import { + activate, + cancel, + endReservation, + getById, +} from '@/lib/services/berth-reservations.service'; + +// ─── PATCH body schema (action-based discriminated union) ──────────────────── + +const patchBodySchema = z.discriminatedUnion('action', [ + z.object({ + action: z.literal('activate'), + contractFileId: z.string().optional(), + effectiveDate: z.coerce.date().optional(), + }), + z.object({ + action: z.literal('end'), + endDate: z.coerce.date(), + notes: z.string().optional(), + }), + z.object({ + action: z.literal('cancel'), + reason: z.string().optional(), + }), +]); + +// ─── Handlers ──────────────────────────────────────────────────────────────── + +export const getHandler: RouteHandler = async (_req, ctx, params) => { + try { + const reservation = await getById(params.id!, ctx.portId); + return NextResponse.json({ data: reservation }); + } catch (error) { + return errorResponse(error); + } +}; + +export const patchHandler: RouteHandler = async (req, ctx, params) => { + try { + const body = await parseBody(req, patchBodySchema); + const meta = { + userId: ctx.userId, + portId: ctx.portId, + ipAddress: ctx.ipAddress, + userAgent: ctx.userAgent, + }; + + if (body.action === 'activate') { + requirePermission(ctx, 'reservations', 'activate'); + const result = await activate( + params.id!, + ctx.portId, + { + contractFileId: body.contractFileId, + effectiveDate: body.effectiveDate, + }, + meta, + ); + return NextResponse.json({ data: result }); + } + + if (body.action === 'end') { + // `end` is lifecycle progression; same privilege as activate. + requirePermission(ctx, 'reservations', 'activate'); + const result = await endReservation( + params.id!, + ctx.portId, + { endDate: body.endDate, notes: body.notes }, + meta, + ); + return NextResponse.json({ data: result }); + } + + // action === 'cancel' + requirePermission(ctx, 'reservations', 'cancel'); + const result = await cancel(params.id!, ctx.portId, { reason: body.reason }, meta); + return NextResponse.json({ data: result }); + } catch (error) { + return errorResponse(error); + } +}; + +export const deleteHandler: RouteHandler = async (_req, ctx, params) => { + try { + await cancel( + params.id!, + ctx.portId, + {}, + { + userId: ctx.userId, + portId: ctx.portId, + ipAddress: ctx.ipAddress, + userAgent: ctx.userAgent, + }, + ); + return new NextResponse(null, { status: 204 }); + } catch (error) { + return errorResponse(error); + } +}; diff --git a/src/app/api/v1/berth-reservations/[id]/route.ts b/src/app/api/v1/berth-reservations/[id]/route.ts index 6b0d042..b157490 100644 --- a/src/app/api/v1/berth-reservations/[id]/route.ts +++ b/src/app/api/v1/berth-reservations/[id]/route.ts @@ -1,110 +1,6 @@ -import { NextResponse } from 'next/server'; -import { z } from 'zod'; +import { withAuth, withPermission } from '@/lib/api/helpers'; -import { withAuth, withPermission, type RouteHandler } from '@/lib/api/helpers'; -import { parseBody } from '@/lib/api/route-helpers'; -import { requirePermission } from '@/lib/auth/permissions'; -import { errorResponse } from '@/lib/errors'; -import { - activate, - cancel, - endReservation, - getById, -} from '@/lib/services/berth-reservations.service'; - -// ─── PATCH body schema (action-based discriminated union) ──────────────────── - -const patchBodySchema = z.discriminatedUnion('action', [ - z.object({ - action: z.literal('activate'), - contractFileId: z.string().optional(), - effectiveDate: z.coerce.date().optional(), - }), - z.object({ - action: z.literal('end'), - endDate: z.coerce.date(), - notes: z.string().optional(), - }), - z.object({ - action: z.literal('cancel'), - reason: z.string().optional(), - }), -]); - -// ─── Handlers ──────────────────────────────────────────────────────────────── - -export const getHandler: RouteHandler = async (_req, ctx, params) => { - try { - const reservation = await getById(params.id!, ctx.portId); - return NextResponse.json({ data: reservation }); - } catch (error) { - return errorResponse(error); - } -}; - -export const patchHandler: RouteHandler = async (req, ctx, params) => { - try { - const body = await parseBody(req, patchBodySchema); - const meta = { - userId: ctx.userId, - portId: ctx.portId, - ipAddress: ctx.ipAddress, - userAgent: ctx.userAgent, - }; - - if (body.action === 'activate') { - requirePermission(ctx, 'reservations', 'activate'); - const result = await activate( - params.id!, - ctx.portId, - { - contractFileId: body.contractFileId, - effectiveDate: body.effectiveDate, - }, - meta, - ); - return NextResponse.json({ data: result }); - } - - if (body.action === 'end') { - // `end` is lifecycle progression; same privilege as activate. - requirePermission(ctx, 'reservations', 'activate'); - const result = await endReservation( - params.id!, - ctx.portId, - { endDate: body.endDate, notes: body.notes }, - meta, - ); - return NextResponse.json({ data: result }); - } - - // action === 'cancel' - requirePermission(ctx, 'reservations', 'cancel'); - const result = await cancel(params.id!, ctx.portId, { reason: body.reason }, meta); - return NextResponse.json({ data: result }); - } catch (error) { - return errorResponse(error); - } -}; - -export const deleteHandler: RouteHandler = async (_req, ctx, params) => { - try { - await cancel( - params.id!, - ctx.portId, - {}, - { - userId: ctx.userId, - portId: ctx.portId, - ipAddress: ctx.ipAddress, - userAgent: ctx.userAgent, - }, - ); - return new NextResponse(null, { status: 204 }); - } catch (error) { - return errorResponse(error); - } -}; +import { getHandler, patchHandler, deleteHandler } from './handlers'; export const GET = withAuth(withPermission('reservations', 'view', getHandler)); // PATCH cannot use `withPermission` wrapper — the required permission depends diff --git a/src/app/api/v1/companies/[id]/members/[mid]/handlers.ts b/src/app/api/v1/companies/[id]/members/[mid]/handlers.ts new file mode 100644 index 0000000..b4b6d07 --- /dev/null +++ b/src/app/api/v1/companies/[id]/members/[mid]/handlers.ts @@ -0,0 +1,47 @@ +import { NextResponse } from 'next/server'; + +import { type RouteHandler } from '@/lib/api/helpers'; +import { parseBody } from '@/lib/api/route-helpers'; +import { errorResponse } from '@/lib/errors'; +import { endMembership, updateMembership } from '@/lib/services/company-memberships.service'; +import { endMembershipSchema, updateMembershipSchema } from '@/lib/validators/company-memberships'; + +export const patchHandler: RouteHandler = async (req, ctx, params) => { + try { + const body = await parseBody(req, updateMembershipSchema); + const updated = await updateMembership(params.mid!, ctx.portId, body, { + userId: ctx.userId, + portId: ctx.portId, + ipAddress: ctx.ipAddress, + userAgent: ctx.userAgent, + }); + return NextResponse.json({ data: updated }); + } catch (error) { + return errorResponse(error); + } +}; + +export const deleteHandler: RouteHandler = async (req, ctx, params) => { + try { + let endDate = new Date(); + const text = await req.text(); + if (text.length > 0) { + const parsed = endMembershipSchema.parse(JSON.parse(text)); + endDate = parsed.endDate; + } + await endMembership( + params.mid!, + ctx.portId, + { endDate }, + { + userId: ctx.userId, + portId: ctx.portId, + ipAddress: ctx.ipAddress, + userAgent: ctx.userAgent, + }, + ); + return new NextResponse(null, { status: 204 }); + } catch (error) { + return errorResponse(error); + } +}; diff --git a/src/app/api/v1/companies/[id]/members/[mid]/route.ts b/src/app/api/v1/companies/[id]/members/[mid]/route.ts index bf854f7..0d8a2e8 100644 --- a/src/app/api/v1/companies/[id]/members/[mid]/route.ts +++ b/src/app/api/v1/companies/[id]/members/[mid]/route.ts @@ -1,50 +1,6 @@ -import { NextResponse } from 'next/server'; +import { withAuth, withPermission } from '@/lib/api/helpers'; -import { withAuth, withPermission, type RouteHandler } from '@/lib/api/helpers'; -import { parseBody } from '@/lib/api/route-helpers'; -import { errorResponse } from '@/lib/errors'; -import { endMembership, updateMembership } from '@/lib/services/company-memberships.service'; -import { endMembershipSchema, updateMembershipSchema } from '@/lib/validators/company-memberships'; - -export const patchHandler: RouteHandler = async (req, ctx, params) => { - try { - const body = await parseBody(req, updateMembershipSchema); - const updated = await updateMembership(params.mid!, ctx.portId, body, { - userId: ctx.userId, - portId: ctx.portId, - ipAddress: ctx.ipAddress, - userAgent: ctx.userAgent, - }); - return NextResponse.json({ data: updated }); - } catch (error) { - return errorResponse(error); - } -}; - -export const deleteHandler: RouteHandler = async (req, ctx, params) => { - try { - let endDate = new Date(); - const text = await req.text(); - if (text.length > 0) { - const parsed = endMembershipSchema.parse(JSON.parse(text)); - endDate = parsed.endDate; - } - await endMembership( - params.mid!, - ctx.portId, - { endDate }, - { - userId: ctx.userId, - portId: ctx.portId, - ipAddress: ctx.ipAddress, - userAgent: ctx.userAgent, - }, - ); - return new NextResponse(null, { status: 204 }); - } catch (error) { - return errorResponse(error); - } -}; +import { patchHandler, deleteHandler } from './handlers'; export const PATCH = withAuth(withPermission('memberships', 'manage', patchHandler)); export const DELETE = withAuth(withPermission('memberships', 'manage', deleteHandler)); diff --git a/src/app/api/v1/companies/[id]/members/[mid]/set-primary/handlers.ts b/src/app/api/v1/companies/[id]/members/[mid]/set-primary/handlers.ts new file mode 100644 index 0000000..21917c4 --- /dev/null +++ b/src/app/api/v1/companies/[id]/members/[mid]/set-primary/handlers.ts @@ -0,0 +1,19 @@ +import { NextResponse } from 'next/server'; + +import { type RouteHandler } from '@/lib/api/helpers'; +import { errorResponse } from '@/lib/errors'; +import { setPrimary } from '@/lib/services/company-memberships.service'; + +export const setPrimaryHandler: RouteHandler = async (_req, ctx, params) => { + try { + const membership = await setPrimary(params.mid!, ctx.portId, { + userId: ctx.userId, + portId: ctx.portId, + ipAddress: ctx.ipAddress, + userAgent: ctx.userAgent, + }); + return NextResponse.json({ data: membership }); + } catch (error) { + return errorResponse(error); + } +}; diff --git a/src/app/api/v1/companies/[id]/members/[mid]/set-primary/route.ts b/src/app/api/v1/companies/[id]/members/[mid]/set-primary/route.ts index 287c9a2..0a0dfae 100644 --- a/src/app/api/v1/companies/[id]/members/[mid]/set-primary/route.ts +++ b/src/app/api/v1/companies/[id]/members/[mid]/set-primary/route.ts @@ -1,21 +1,5 @@ -import { NextResponse } from 'next/server'; +import { withAuth, withPermission } from '@/lib/api/helpers'; -import { withAuth, withPermission, type RouteHandler } from '@/lib/api/helpers'; -import { errorResponse } from '@/lib/errors'; -import { setPrimary } from '@/lib/services/company-memberships.service'; - -export const setPrimaryHandler: RouteHandler = async (_req, ctx, params) => { - try { - const membership = await setPrimary(params.mid!, ctx.portId, { - userId: ctx.userId, - portId: ctx.portId, - ipAddress: ctx.ipAddress, - userAgent: ctx.userAgent, - }); - return NextResponse.json({ data: membership }); - } catch (error) { - return errorResponse(error); - } -}; +import { setPrimaryHandler } from './handlers'; export const POST = withAuth(withPermission('memberships', 'manage', setPrimaryHandler)); diff --git a/src/app/api/v1/companies/[id]/members/handlers.ts b/src/app/api/v1/companies/[id]/members/handlers.ts new file mode 100644 index 0000000..658b59a --- /dev/null +++ b/src/app/api/v1/companies/[id]/members/handlers.ts @@ -0,0 +1,40 @@ +import { NextResponse } from 'next/server'; +import { z } from 'zod'; + +import { type RouteHandler } from '@/lib/api/helpers'; +import { parseBody, parseQuery } from '@/lib/api/route-helpers'; +import { errorResponse } from '@/lib/errors'; +import { addMembership, listByCompany } from '@/lib/services/company-memberships.service'; +import { addMembershipSchema } from '@/lib/validators/company-memberships'; + +const listQuerySchema = z.object({ + activeOnly: z + .enum(['true', 'false']) + .transform((v) => v === 'true') + .default('true'), +}); + +export const listHandler: RouteHandler = async (req, ctx, params) => { + try { + const { activeOnly } = parseQuery(req, listQuerySchema); + const memberships = await listByCompany(params.id!, ctx.portId, { activeOnly }); + return NextResponse.json({ data: memberships }); + } catch (error) { + return errorResponse(error); + } +}; + +export const createHandler: RouteHandler = async (req, ctx, params) => { + try { + const body = await parseBody(req, addMembershipSchema); + const membership = await addMembership(params.id!, ctx.portId, body, { + userId: ctx.userId, + portId: ctx.portId, + ipAddress: ctx.ipAddress, + userAgent: ctx.userAgent, + }); + return NextResponse.json({ data: membership }, { status: 201 }); + } catch (error) { + return errorResponse(error); + } +}; diff --git a/src/app/api/v1/companies/[id]/members/route.ts b/src/app/api/v1/companies/[id]/members/route.ts index 88cb847..fd22462 100644 --- a/src/app/api/v1/companies/[id]/members/route.ts +++ b/src/app/api/v1/companies/[id]/members/route.ts @@ -1,43 +1,6 @@ -import { NextResponse } from 'next/server'; -import { z } from 'zod'; +import { withAuth, withPermission } from '@/lib/api/helpers'; -import { withAuth, withPermission, type RouteHandler } from '@/lib/api/helpers'; -import { parseBody, parseQuery } from '@/lib/api/route-helpers'; -import { errorResponse } from '@/lib/errors'; -import { addMembership, listByCompany } from '@/lib/services/company-memberships.service'; -import { addMembershipSchema } from '@/lib/validators/company-memberships'; - -const listQuerySchema = z.object({ - activeOnly: z - .enum(['true', 'false']) - .transform((v) => v === 'true') - .default('true'), -}); - -export const listHandler: RouteHandler = async (req, ctx, params) => { - try { - const { activeOnly } = parseQuery(req, listQuerySchema); - const memberships = await listByCompany(params.id!, ctx.portId, { activeOnly }); - return NextResponse.json({ data: memberships }); - } catch (error) { - return errorResponse(error); - } -}; - -export const createHandler: RouteHandler = async (req, ctx, params) => { - try { - const body = await parseBody(req, addMembershipSchema); - const membership = await addMembership(params.id!, ctx.portId, body, { - userId: ctx.userId, - portId: ctx.portId, - ipAddress: ctx.ipAddress, - userAgent: ctx.userAgent, - }); - return NextResponse.json({ data: membership }, { status: 201 }); - } catch (error) { - return errorResponse(error); - } -}; +import { listHandler, createHandler } from './handlers'; export const GET = withAuth(withPermission('memberships', 'view', listHandler)); export const POST = withAuth(withPermission('memberships', 'manage', createHandler)); diff --git a/src/app/api/v1/companies/autocomplete/handlers.ts b/src/app/api/v1/companies/autocomplete/handlers.ts new file mode 100644 index 0000000..f08b3e7 --- /dev/null +++ b/src/app/api/v1/companies/autocomplete/handlers.ts @@ -0,0 +1,18 @@ +import { NextResponse } from 'next/server'; + +import { type RouteHandler } from '@/lib/api/helpers'; +import { errorResponse } from '@/lib/errors'; +import { autocomplete } from '@/lib/services/companies.service'; + +export const autocompleteHandler: RouteHandler = async (req, ctx) => { + try { + const q = req.nextUrl.searchParams.get('q'); + if (!q) { + return NextResponse.json({ data: [] }); + } + const companies = await autocomplete(ctx.portId, q); + return NextResponse.json({ data: companies }); + } catch (error) { + return errorResponse(error); + } +}; diff --git a/src/app/api/v1/companies/autocomplete/route.ts b/src/app/api/v1/companies/autocomplete/route.ts index 070112f..b5bf565 100644 --- a/src/app/api/v1/companies/autocomplete/route.ts +++ b/src/app/api/v1/companies/autocomplete/route.ts @@ -1,20 +1,5 @@ -import { NextResponse } from 'next/server'; +import { withAuth, withPermission } from '@/lib/api/helpers'; -import { withAuth, withPermission, type RouteHandler } from '@/lib/api/helpers'; -import { errorResponse } from '@/lib/errors'; -import { autocomplete } from '@/lib/services/companies.service'; - -export const autocompleteHandler: RouteHandler = async (req, ctx) => { - try { - const q = req.nextUrl.searchParams.get('q'); - if (!q) { - return NextResponse.json({ data: [] }); - } - const companies = await autocomplete(ctx.portId, q); - return NextResponse.json({ data: companies }); - } catch (error) { - return errorResponse(error); - } -}; +import { autocompleteHandler } from './handlers'; export const GET = withAuth(withPermission('companies', 'view', autocompleteHandler)); diff --git a/src/app/api/v1/yachts/[id]/ownership-history/handlers.ts b/src/app/api/v1/yachts/[id]/ownership-history/handlers.ts new file mode 100644 index 0000000..7831131 --- /dev/null +++ b/src/app/api/v1/yachts/[id]/ownership-history/handlers.ts @@ -0,0 +1,14 @@ +import { NextResponse } from 'next/server'; + +import { type RouteHandler } from '@/lib/api/helpers'; +import { errorResponse } from '@/lib/errors'; +import { listOwnershipHistory } from '@/lib/services/yachts.service'; + +export const historyHandler: RouteHandler = async (_req, ctx, params) => { + try { + const history = await listOwnershipHistory(params.id!, ctx.portId); + return NextResponse.json({ data: history }); + } catch (error) { + return errorResponse(error); + } +}; diff --git a/src/app/api/v1/yachts/[id]/ownership-history/route.ts b/src/app/api/v1/yachts/[id]/ownership-history/route.ts index 957ff15..bc40226 100644 --- a/src/app/api/v1/yachts/[id]/ownership-history/route.ts +++ b/src/app/api/v1/yachts/[id]/ownership-history/route.ts @@ -1,16 +1,5 @@ -import { NextResponse } from 'next/server'; +import { withAuth, withPermission } from '@/lib/api/helpers'; -import { withAuth, withPermission, type RouteHandler } from '@/lib/api/helpers'; -import { errorResponse } from '@/lib/errors'; -import { listOwnershipHistory } from '@/lib/services/yachts.service'; - -export const historyHandler: RouteHandler = async (req, ctx, params) => { - try { - const history = await listOwnershipHistory(params.id!, ctx.portId); - return NextResponse.json({ data: history }); - } catch (error) { - return errorResponse(error); - } -}; +import { historyHandler } from './handlers'; export const GET = withAuth(withPermission('yachts', 'view', historyHandler)); diff --git a/src/app/api/v1/yachts/[id]/transfer/handlers.ts b/src/app/api/v1/yachts/[id]/transfer/handlers.ts new file mode 100644 index 0000000..b02c22f --- /dev/null +++ b/src/app/api/v1/yachts/[id]/transfer/handlers.ts @@ -0,0 +1,22 @@ +import { NextResponse } from 'next/server'; + +import { type RouteHandler } from '@/lib/api/helpers'; +import { parseBody } from '@/lib/api/route-helpers'; +import { errorResponse } from '@/lib/errors'; +import { transferOwnership } from '@/lib/services/yachts.service'; +import { transferOwnershipSchema } from '@/lib/validators/yachts'; + +export const transferHandler: RouteHandler = async (req, ctx, params) => { + try { + const body = await parseBody(req, transferOwnershipSchema); + const yacht = await transferOwnership(params.id!, ctx.portId, body, { + userId: ctx.userId, + portId: ctx.portId, + ipAddress: ctx.ipAddress, + userAgent: ctx.userAgent, + }); + return NextResponse.json({ data: yacht }); + } catch (error) { + return errorResponse(error); + } +}; diff --git a/src/app/api/v1/yachts/[id]/transfer/route.ts b/src/app/api/v1/yachts/[id]/transfer/route.ts index 1a89985..63050e0 100644 --- a/src/app/api/v1/yachts/[id]/transfer/route.ts +++ b/src/app/api/v1/yachts/[id]/transfer/route.ts @@ -1,24 +1,5 @@ -import { NextResponse } from 'next/server'; +import { withAuth, withPermission } from '@/lib/api/helpers'; -import { withAuth, withPermission, type RouteHandler } from '@/lib/api/helpers'; -import { parseBody } from '@/lib/api/route-helpers'; -import { errorResponse } from '@/lib/errors'; -import { transferOwnership } from '@/lib/services/yachts.service'; -import { transferOwnershipSchema } from '@/lib/validators/yachts'; - -export const transferHandler: RouteHandler = async (req, ctx, params) => { - try { - const body = await parseBody(req, transferOwnershipSchema); - const yacht = await transferOwnership(params.id!, ctx.portId, body, { - userId: ctx.userId, - portId: ctx.portId, - ipAddress: ctx.ipAddress, - userAgent: ctx.userAgent, - }); - return NextResponse.json({ data: yacht }); - } catch (error) { - return errorResponse(error); - } -}; +import { transferHandler } from './handlers'; export const POST = withAuth(withPermission('yachts', 'transfer', transferHandler)); diff --git a/src/app/api/v1/yachts/autocomplete/handlers.ts b/src/app/api/v1/yachts/autocomplete/handlers.ts new file mode 100644 index 0000000..48d6166 --- /dev/null +++ b/src/app/api/v1/yachts/autocomplete/handlers.ts @@ -0,0 +1,18 @@ +import { NextResponse } from 'next/server'; + +import { type RouteHandler } from '@/lib/api/helpers'; +import { errorResponse } from '@/lib/errors'; +import { autocomplete } from '@/lib/services/yachts.service'; + +export const autocompleteHandler: RouteHandler = async (req, ctx) => { + try { + const q = req.nextUrl.searchParams.get('q'); + if (!q) { + return NextResponse.json({ data: [] }); + } + const yachts = await autocomplete(ctx.portId, q); + return NextResponse.json({ data: yachts }); + } catch (error) { + return errorResponse(error); + } +}; diff --git a/src/app/api/v1/yachts/autocomplete/route.ts b/src/app/api/v1/yachts/autocomplete/route.ts index 992bbdf..4c515b0 100644 --- a/src/app/api/v1/yachts/autocomplete/route.ts +++ b/src/app/api/v1/yachts/autocomplete/route.ts @@ -1,20 +1,5 @@ -import { NextResponse } from 'next/server'; +import { withAuth, withPermission } from '@/lib/api/helpers'; -import { withAuth, withPermission, type RouteHandler } from '@/lib/api/helpers'; -import { errorResponse } from '@/lib/errors'; -import { autocomplete } from '@/lib/services/yachts.service'; - -export const autocompleteHandler: RouteHandler = async (req, ctx) => { - try { - const q = req.nextUrl.searchParams.get('q'); - if (!q) { - return NextResponse.json({ data: [] }); - } - const yachts = await autocomplete(ctx.portId, q); - return NextResponse.json({ data: yachts }); - } catch (error) { - return errorResponse(error); - } -}; +import { autocompleteHandler } from './handlers'; export const GET = withAuth(withPermission('yachts', 'view', autocompleteHandler)); diff --git a/tests/integration/api/companies.test.ts b/tests/integration/api/companies.test.ts index 8a849db..27d40d6 100644 --- a/tests/integration/api/companies.test.ts +++ b/tests/integration/api/companies.test.ts @@ -2,7 +2,7 @@ import { describe, it, expect } from 'vitest'; import { listHandler, createHandler } from '@/app/api/v1/companies/handlers'; import { getHandler, patchHandler, deleteHandler } from '@/app/api/v1/companies/[id]/handlers'; -import { autocompleteHandler } from '@/app/api/v1/companies/autocomplete/route'; +import { autocompleteHandler } from '@/app/api/v1/companies/autocomplete/handlers'; import { db } from '@/lib/db'; import { companies } from '@/lib/db/schema'; import { eq } from 'drizzle-orm'; diff --git a/tests/integration/api/memberships.test.ts b/tests/integration/api/memberships.test.ts index fd201ba..f2ad47b 100644 --- a/tests/integration/api/memberships.test.ts +++ b/tests/integration/api/memberships.test.ts @@ -1,9 +1,9 @@ import { describe, it, expect } from 'vitest'; import { eq } from 'drizzle-orm'; -import { listHandler, createHandler } from '@/app/api/v1/companies/[id]/members/route'; -import { patchHandler, deleteHandler } from '@/app/api/v1/companies/[id]/members/[mid]/route'; -import { setPrimaryHandler } from '@/app/api/v1/companies/[id]/members/[mid]/set-primary/route'; +import { listHandler, createHandler } from '@/app/api/v1/companies/[id]/members/handlers'; +import { patchHandler, deleteHandler } from '@/app/api/v1/companies/[id]/members/[mid]/handlers'; +import { setPrimaryHandler } from '@/app/api/v1/companies/[id]/members/[mid]/set-primary/handlers'; import { db } from '@/lib/db'; import { companyMemberships } from '@/lib/db/schema'; import { makeMockCtx, makeMockRequest } from '../../helpers/route-tester'; diff --git a/tests/integration/api/reservations.test.ts b/tests/integration/api/reservations.test.ts index a1214ba..9ef21b3 100644 --- a/tests/integration/api/reservations.test.ts +++ b/tests/integration/api/reservations.test.ts @@ -9,7 +9,7 @@ import { getHandler as getReservationHandler, patchHandler as patchReservationHandler, deleteHandler as deleteReservationHandler, -} from '@/app/api/v1/berth-reservations/[id]/route'; +} from '@/app/api/v1/berth-reservations/[id]/handlers'; import { db } from '@/lib/db'; import { berthReservations } from '@/lib/db/schema/reservations'; import { makeMockCtx, makeMockRequest } from '../../helpers/route-tester'; diff --git a/tests/integration/api/yachts-detail.test.ts b/tests/integration/api/yachts-detail.test.ts index 61d8089..7e07f5f 100644 --- a/tests/integration/api/yachts-detail.test.ts +++ b/tests/integration/api/yachts-detail.test.ts @@ -1,9 +1,9 @@ import { describe, it, expect } from 'vitest'; import { getHandler, patchHandler, deleteHandler } from '@/app/api/v1/yachts/[id]/handlers'; -import { transferHandler } from '@/app/api/v1/yachts/[id]/transfer/route'; -import { historyHandler } from '@/app/api/v1/yachts/[id]/ownership-history/route'; -import { autocompleteHandler } from '@/app/api/v1/yachts/autocomplete/route'; +import { transferHandler } from '@/app/api/v1/yachts/[id]/transfer/handlers'; +import { historyHandler } from '@/app/api/v1/yachts/[id]/ownership-history/handlers'; +import { autocompleteHandler } from '@/app/api/v1/yachts/autocomplete/handlers'; import { withPermission } from '@/lib/api/helpers'; import { db } from '@/lib/db'; import { yachts } from '@/lib/db/schema';