Files
homarr/packages/validation/src/board.ts
T
Thomas a42af0e678 fix(boards): add permissions to board summary schema to fix manage page crash (#5917)
* fix(boards): add permissions to board summary schema to fix manage page crash

The getAllBoards query fetches userPermissions and groupPermissions
from the DB, but boardSummarySchema (the output validator) did not
include them. tRPC stripped the fields, causing a TypeError when
getBoardPermissionsAsync called .some() on undefined.

* fix(backup): cast Buffer to Uint8Array for NextResponse body

Node 24 narrowed the BodyInit type, making Buffer incompatible.
Wrapping in Uint8Array satisfies the type constraint.

* fix(lint): remove forbidden non-null assertions across backup and board code

Replace non-null assertions with safe guards, optional chaining, and
for-of iteration so oxlint passes in CI. Also remove unused Collapse import.
2026-06-10 19:51:56 +02:00

128 lines
3.5 KiB
TypeScript

import { z } from "zod/v4";
import {
backgroundImageAttachments,
backgroundImageRepeats,
backgroundImageSizes,
boardPermissions,
widgetKinds,
} from "@homarr/definitions";
import { zodEnumFromArray } from "./enums";
import { createSavePermissionsSchema } from "./permissions";
import { commonItemSchema, sectionSchema } from "./shared";
const hexColorSchema = z.string().regex(/^#[0-9A-Fa-f]{6}$/);
const hexColorNullableSchema = hexColorSchema
.or(z.literal(""))
.nullable()
.transform((value) => (value?.trim().length === 0 ? null : value));
export const boardNameSchema = z
.string()
.min(1)
.max(255)
.regex(/^[A-Za-z0-9-_]*$/);
export const boardColumnCountSchema = z.number().min(1).max(24);
export const boardByNameSchema = z.object({
name: boardNameSchema,
});
export const boardRenameSchema = z.object({
id: z.string(),
name: boardNameSchema,
});
export const boardDuplicateSchema = z.object({
id: z.string(),
name: boardNameSchema,
});
export const boardChangeVisibilitySchema = z.object({
id: z.string(),
visibility: z.enum(["public", "private"]),
});
const trimmedNullableString = z
.string()
.nullable()
.transform((value) => (value?.trim().length === 0 ? null : value));
export const boardSavePartialSettingsSchema = z
.object({
pageTitle: trimmedNullableString,
metaTitle: trimmedNullableString,
logoImageUrl: trimmedNullableString,
faviconImageUrl: trimmedNullableString,
backgroundImageUrl: trimmedNullableString,
backgroundImageAttachment: z.enum(backgroundImageAttachments.values),
backgroundImageRepeat: z.enum(backgroundImageRepeats.values),
backgroundImageSize: z.enum(backgroundImageSizes.values),
primaryColor: hexColorSchema,
secondaryColor: hexColorSchema,
opacity: z.number().min(0).max(100),
customCss: z.string().max(16384),
iconColor: hexColorNullableSchema,
itemRadius: z.union([z.literal("xs"), z.literal("sm"), z.literal("md"), z.literal("lg"), z.literal("xl")]),
disableStatus: z.boolean(),
})
.partial();
export const boardSaveLayoutsSchema = z.object({
id: z.string(),
layouts: z.array(
z.object({
id: z.string(),
name: z.string().trim().nonempty().max(32),
columnCount: boardColumnCountSchema,
breakpoint: z.number().min(0).max(32767),
}),
),
});
export const boardSaveSchema = z.object({
id: z.string(),
sections: z.array(sectionSchema),
items: z.array(commonItemSchema),
});
export const boardCreateSchema = z.object({
name: boardNameSchema,
columnCount: boardColumnCountSchema,
isPublic: z.boolean(),
});
export const boardSavePermissionsSchema = createSavePermissionsSchema(zodEnumFromArray(boardPermissions));
const boardPermissionEntrySchema = z.object({
permission: z.enum(boardPermissions),
});
export const boardSummarySchema = z.object({
id: z.string(),
name: z.string(),
logoImageUrl: z.string().nullable(),
isPublic: z.boolean(),
creator: z
.object({
id: z.string(),
name: z.string().nullable(),
image: z.string().nullable(),
email: z.string().nullable(),
})
.nullable(),
isHome: z.boolean(),
isMobileHome: z.boolean(),
userPermissions: z.array(boardPermissionEntrySchema),
groupPermissions: z.array(boardPermissionEntrySchema),
});
export const addItemToBoardSchema = z.object({
boardId: z.string(),
kind: zodEnumFromArray(widgetKinds),
options: z.record(z.string(), z.unknown()).default({}),
integrationIds: z.array(z.string()).default([]),
});