chore: seminarhof customer project
This commit is contained in:
138
packages/functions/src/errors.ts
Normal file
138
packages/functions/src/errors.ts
Normal file
@@ -0,0 +1,138 @@
|
||||
/**
|
||||
* Domain-specific error classes for the Seminarhof API.
|
||||
*
|
||||
* Convention (see /CLAUDE.md): caught / expected exceptions must throw one of
|
||||
* these defined errors — never a raw `new Error('string')`. Each extends a
|
||||
* Pikku core error and is registered with `addErrors` below so it maps to the
|
||||
* right HTTP status and a stable, client-facing message.
|
||||
*
|
||||
* This module registers its errors as an import side-effect; importing it from
|
||||
* any function that throws is enough to wire the mapping at startup.
|
||||
*/
|
||||
import {
|
||||
BadRequestError,
|
||||
ConflictError,
|
||||
UnauthorizedError,
|
||||
addErrors,
|
||||
} from '@pikku/core/errors'
|
||||
|
||||
// ─── Auth ────────────────────────────────────────────────────────────
|
||||
export class InvalidCredentialsError extends UnauthorizedError {
|
||||
constructor(message = 'Invalid email or password.') {
|
||||
super(message)
|
||||
}
|
||||
}
|
||||
|
||||
// ─── Bookings / scheduling ───────────────────────────────────────────
|
||||
export class InvalidDateRangeError extends BadRequestError {
|
||||
constructor(message = 'Start date must be on or before the end date.') {
|
||||
super(message)
|
||||
}
|
||||
}
|
||||
|
||||
export class InvalidBookingTransitionError extends ConflictError {
|
||||
constructor(from: string, to: string) {
|
||||
super(`Cannot move a booking from "${from}" to "${to}".`)
|
||||
}
|
||||
}
|
||||
|
||||
// ─── Contract lifecycle ──────────────────────────────────────────────
|
||||
export class ContractRequiresReservedError extends ConflictError {
|
||||
constructor(status: string) {
|
||||
super(`A contract can only be sent for a reserved booking (was "${status}").`)
|
||||
}
|
||||
}
|
||||
|
||||
export class ContractAlreadySentError extends ConflictError {
|
||||
constructor(message = 'A contract has already been sent for this booking.') {
|
||||
super(message)
|
||||
}
|
||||
}
|
||||
|
||||
export class ContractNotSentError extends ConflictError {
|
||||
constructor(message = 'No contract has been sent for this booking yet.') {
|
||||
super(message)
|
||||
}
|
||||
}
|
||||
|
||||
export class ContractResponseRequiresReservedError extends ConflictError {
|
||||
constructor(status: string) {
|
||||
super(`A contract response can only be recorded for a reserved booking (was "${status}").`)
|
||||
}
|
||||
}
|
||||
|
||||
export class DepositRequiresReservedError extends ConflictError {
|
||||
constructor(status: string) {
|
||||
super(`A deposit can only be recorded for a reserved booking (was "${status}").`)
|
||||
}
|
||||
}
|
||||
|
||||
// ─── Enquiries ───────────────────────────────────────────────────────
|
||||
export class EnquiryNotPendingError extends ConflictError {
|
||||
constructor(status: string) {
|
||||
super(`This enquiry is "${status}" and can no longer be approved, waitlisted or declined.`)
|
||||
}
|
||||
}
|
||||
|
||||
export class EnquiryDatesRequiredError extends BadRequestError {
|
||||
constructor(message = 'Start and end dates are required for this action.') {
|
||||
super(message)
|
||||
}
|
||||
}
|
||||
|
||||
export class ClientWithEmailExistsError extends ConflictError {
|
||||
constructor(email: string) {
|
||||
super(`A client with the email "${email}" already exists — select it instead of creating a new one.`)
|
||||
}
|
||||
}
|
||||
|
||||
// ─── Booking form / magic link ───────────────────────────────────────
|
||||
export class InvalidFormLinkError extends UnauthorizedError {
|
||||
constructor(message = 'This booking-form link is invalid or has expired.') {
|
||||
super(message)
|
||||
}
|
||||
}
|
||||
|
||||
export class BookingFormUnavailableError extends ConflictError {
|
||||
constructor(status: string) {
|
||||
super(`This booking can no longer be completed via the form (status "${status}").`)
|
||||
}
|
||||
}
|
||||
|
||||
export class ConsentRequiredError extends BadRequestError {
|
||||
constructor(message = 'You must accept the terms and the privacy policy to submit.') {
|
||||
super(message)
|
||||
}
|
||||
}
|
||||
|
||||
// ─── Invoicing ───────────────────────────────────────────────────────
|
||||
export class InvoiceAlreadyPaidError extends ConflictError {
|
||||
constructor(message = 'This invoice has already been paid.') {
|
||||
super(message)
|
||||
}
|
||||
}
|
||||
|
||||
export class InvoiceCancelledError extends ConflictError {
|
||||
constructor(message = 'This invoice has been cancelled.') {
|
||||
super(message)
|
||||
}
|
||||
}
|
||||
|
||||
addErrors([
|
||||
[InvalidCredentialsError, { status: 401, message: 'Invalid email or password.' }],
|
||||
[InvalidDateRangeError, { status: 400, message: 'Start date must be on or before the end date.' }],
|
||||
[InvalidBookingTransitionError, { status: 409, message: 'Illegal booking status transition.' }],
|
||||
[ContractRequiresReservedError, { status: 409, message: 'A contract can only be sent for a reserved booking.' }],
|
||||
[ContractAlreadySentError, { status: 409, message: 'A contract has already been sent for this booking.' }],
|
||||
[ContractNotSentError, { status: 409, message: 'No contract has been sent for this booking yet.' }],
|
||||
[ContractResponseRequiresReservedError, { status: 409, message: 'A contract response can only be recorded for a reserved booking.' }],
|
||||
[DepositRequiresReservedError, { status: 409, message: 'A deposit can only be recorded for a reserved booking.' }],
|
||||
[EnquiryNotPendingError, { status: 409, message: 'This enquiry can no longer be acted on.' }],
|
||||
[EnquiryDatesRequiredError, { status: 400, message: 'Start and end dates are required for this action.' }],
|
||||
[ClientWithEmailExistsError, { status: 409, message: 'A client with this email already exists.' }],
|
||||
[InvalidFormLinkError, { status: 401, message: 'This booking-form link is invalid or has expired.' }],
|
||||
[BookingFormUnavailableError, { status: 409, message: 'This booking can no longer be completed via the form.' }],
|
||||
[ConsentRequiredError, { status: 400, message: 'You must accept the terms and the privacy policy to submit.' }],
|
||||
[InvoiceAlreadyPaidError, { status: 409, message: 'This invoice has already been paid.' }],
|
||||
[InvoiceCancelledError, { status: 409, message: 'This invoice has been cancelled.' }],
|
||||
])
|
||||
Reference in New Issue
Block a user