## Changes - Add `theme` (refined | modern | bold | editorial, default "refined") and `themeMode` (auto | light | dark, default "auto") fields to the StatusPage Mongoose model and the shared `StatusPage` type. - Extend the create/update Zod validator to accept both fields optionally. - Map both fields in the repository's `toEntity` so GET responses include them. - Document both fields in the create and update endpoints in `openapi.json`. - Add `STATUS_PAGE_THEMES_ENABLED` env var (default `true`) wired through `SettingsService` for ops-level rollback. When disabled, the controller strips theme fields on write and forces defaults on read. ## Notes - No migration needed; Mongoose applies defaults for legacy documents. - Controller short-circuits to zero-cost when the feature flag is on. - `DEFAULT_STATUS_PAGE_THEME` / `DEFAULT_STATUS_PAGE_THEME_MODE` constants exported so the frontend PR can consume the same values.
58 lines
2.3 KiB
TypeScript
58 lines
2.3 KiB
TypeScript
import { z } from "zod";
|
|
import { booleanCoercion } from "./shared.js";
|
|
import { StatusPageTypes, StatusPageThemes, StatusPageThemeModes } from "@/types/statusPage.js";
|
|
|
|
//****************************************
|
|
// Status Page Validations
|
|
//****************************************
|
|
|
|
export const getStatusPageParamValidation = z.object({
|
|
url: z.string().min(1, "URL is required"),
|
|
});
|
|
|
|
export const getStatusPageQueryValidation = z.object({
|
|
type: z.union([z.enum(StatusPageTypes), z.array(z.enum(StatusPageTypes))]).transform((val) => (Array.isArray(val) ? val : [val])),
|
|
timeFrame: z.coerce.number().optional(),
|
|
});
|
|
|
|
export const createStatusPageBodyValidation = z
|
|
.object({
|
|
type: z.union([z.enum(StatusPageTypes), z.array(z.enum(StatusPageTypes))]).transform((val) => (Array.isArray(val) ? val : [val])),
|
|
companyName: z.string().min(1, "Company name is required"),
|
|
url: z.string().regex(/^[a-zA-Z0-9_-]+$/, {
|
|
message: "URL can only contain letters, numbers, underscores, and hyphens",
|
|
}),
|
|
timezone: z.string().optional(),
|
|
color: z.string().optional(),
|
|
monitors: z.array(z.string().regex(/^[0-9a-fA-F]{24}$/, "Must be a valid monitor ID")).min(1, "At least one monitor is required"),
|
|
subMonitors: z.array(z.string().regex(/^[0-9a-fA-F]{24}$/)).optional(),
|
|
deleteSubmonitors: z.boolean().optional(),
|
|
isPublished: booleanCoercion,
|
|
showCharts: booleanCoercion.optional(),
|
|
showUptimePercentage: booleanCoercion,
|
|
showAdminLoginLink: booleanCoercion.optional(),
|
|
showInfrastructure: booleanCoercion.optional(),
|
|
removeLogo: z.union([z.literal("true"), z.literal("false")]).optional(),
|
|
theme: z.enum(StatusPageThemes).optional(),
|
|
themeMode: z.enum(StatusPageThemeModes).optional(),
|
|
})
|
|
.strip();
|
|
|
|
export const imageValidation = z
|
|
.object({
|
|
fieldname: z.string().min(1, "Field name is required"),
|
|
originalname: z.string().min(1, "Original name is required"),
|
|
encoding: z.string().min(1, "Encoding is required"),
|
|
mimetype: z.enum(["image/jpeg", "image/png", "image/jpg"], {
|
|
message: "File must be a valid image (jpeg, jpg, or png)",
|
|
}),
|
|
size: z.number().max(3145728, "File size must be less than 3MB"),
|
|
buffer: z.instanceof(Buffer, { message: "Buffer is required" }),
|
|
destination: z.string().optional(),
|
|
filename: z.string().optional(),
|
|
path: z.string().optional(),
|
|
})
|
|
.refine((data) => data.buffer, {
|
|
message: "Image file is required",
|
|
});
|