feat(api): expose board automation endpoints (#6213)
* feat(api): expose board automation endpoints * style: auto-fix lint and format * test(api): cover board settings updates * Delete .opencode/changelog/2026-07-06.md
This commit is contained in:
1 parent
718f095cb7
commit
7c152cf4fe
9 files changed
+303
-40
No files matched your search
@@ -1,6 +1,14 @@
|
||||
import { TRPCError } from "@trpc/server";
|
||||
import { z } from "zod/v4";
|
||||
|
||||
import { getServerSettingByKeyAsync, getServerSettingsAsync, updateServerSettingByKeyAsync } from "@homarr/db/queries";
|
||||
import { and, eq, inArray } from "@homarr/db";
|
||||
import {
|
||||
getServerSettingByKeyAsync,
|
||||
getServerSettingsAsync,
|
||||
insertServerSettingByKeyAsync,
|
||||
updateServerSettingByKeyAsync,
|
||||
} from "@homarr/db/queries";
|
||||
import { boards, serverSettings } from "@homarr/db/schema";
|
||||
import type { ServerSettings } from "@homarr/server-settings";
|
||||
import { defaultServerSettingsKeys } from "@homarr/server-settings";
|
||||
import { settingsInitSchema } from "@homarr/validation/settings";
|
||||
@@ -8,6 +16,15 @@ import { settingsInitSchema } from "@homarr/validation/settings";
|
||||
import { createTRPCRouter, onboardingProcedure, permissionRequiredProcedure, publicProcedure } from "../trpc";
|
||||
import { nextOnboardingStepAsync } from "./onboard/onboard-queries";
|
||||
|
||||
const boardServerSettingsSchema = z.object({
|
||||
homeBoardId: z.string().nullable(),
|
||||
mobileHomeBoardId: z.string().nullable(),
|
||||
enableStatusByDefault: z.boolean(),
|
||||
forceDisableStatus: z.boolean(),
|
||||
}) satisfies z.ZodType<ServerSettings["board"]>;
|
||||
|
||||
const boardServerSettingsUpdateSchema = boardServerSettingsSchema.partial();
|
||||
|
||||
export const serverSettingsRouter = createTRPCRouter({
|
||||
getCulture: publicProcedure.query(async ({ ctx }) => {
|
||||
return await getServerSettingByKeyAsync(ctx.db, "culture");
|
||||
@@ -15,6 +32,67 @@ export const serverSettingsRouter = createTRPCRouter({
|
||||
getAll: permissionRequiredProcedure.requiresPermission("admin").query(async ({ ctx }) => {
|
||||
return await getServerSettingsAsync(ctx.db);
|
||||
}),
|
||||
getBoardSettings: permissionRequiredProcedure
|
||||
.requiresPermission("admin")
|
||||
.meta({
|
||||
openapi: { method: "GET", path: "/api/settings/board", tags: ["settings"], protect: true },
|
||||
mcp: {
|
||||
enabled: true,
|
||||
description:
|
||||
"Get global board defaults, including desktop/mobile home board IDs and status behavior. Requires admin permission",
|
||||
},
|
||||
})
|
||||
.input(z.void())
|
||||
.output(boardServerSettingsSchema)
|
||||
.query(async ({ ctx }) => {
|
||||
return await getServerSettingByKeyAsync(ctx.db, "board");
|
||||
}),
|
||||
updateBoardSettings: permissionRequiredProcedure
|
||||
.requiresPermission("admin")
|
||||
.meta({
|
||||
openapi: { method: "PATCH", path: "/api/settings/board", tags: ["settings"], protect: true },
|
||||
mcp: {
|
||||
enabled: true,
|
||||
description:
|
||||
"Update global board defaults. Requires admin permission. Optional fields: homeBoardId, mobileHomeBoardId, enableStatusByDefault, forceDisableStatus. Home board IDs must reference public boards or be null",
|
||||
},
|
||||
})
|
||||
.input(boardServerSettingsUpdateSchema)
|
||||
.output(boardServerSettingsSchema)
|
||||
.mutation(async ({ ctx, input }) => {
|
||||
const inputBoardIds = [input.homeBoardId, input.mobileHomeBoardId].filter(
|
||||
(id) => id !== undefined && id !== null,
|
||||
);
|
||||
|
||||
if (inputBoardIds.length > 0) {
|
||||
const publicBoards = await ctx.db.query.boards.findMany({
|
||||
columns: { id: true },
|
||||
where: and(inArray(boards.id, inputBoardIds), eq(boards.isPublic, true)),
|
||||
});
|
||||
const publicBoardIds = new Set(publicBoards.map((board) => board.id));
|
||||
const invalidBoardIds = inputBoardIds.filter((id) => !publicBoardIds.has(id));
|
||||
if (invalidBoardIds.length > 0) {
|
||||
throw new TRPCError({
|
||||
code: "BAD_REQUEST",
|
||||
message: `Board settings home board IDs must reference public boards: ${invalidBoardIds.join(", ")}`,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const current = await getServerSettingByKeyAsync(ctx.db, "board");
|
||||
const next = { ...current, ...input };
|
||||
const existing = await ctx.db.query.serverSettings.findFirst({
|
||||
where: eq(serverSettings.settingKey, "board"),
|
||||
});
|
||||
|
||||
if (existing) {
|
||||
await updateServerSettingByKeyAsync(ctx.db, "board", next);
|
||||
} else {
|
||||
await insertServerSettingByKeyAsync(ctx.db, "board", next);
|
||||
}
|
||||
|
||||
return next;
|
||||
}),
|
||||
saveSettings: permissionRequiredProcedure
|
||||
.requiresPermission("admin")
|
||||
.input(
|
||||
|
||||
Reference in new issue
Block a user