fix: repair restored board layouts

This commit is contained in:
ajnart committed 2026-08-08 18:44:32 +02:00
1 parent bc41125b7f
commit 2657ff4384
4 files changed
+54 -8

No files matched your search

+38 -1
View File
@@ -10,6 +10,7 @@ import { createLogger } from "@homarr/core/infrastructure/logs";
import type { Database, InferInsertModel, InferSelectModel, SQL } from "@homarr/db";
import { and, asc, eq, handleTransactionsAsync, inArray, isNull, like, not, or, sql } from "@homarr/db";
import { createDbInsertCollectionWithoutTransaction } from "@homarr/db/collection";
import { seedProtectedBoardLayoutsAsync } from "@homarr/db/migrations/seed";
import { getServerSettingByKeyAsync } from "@homarr/db/queries";
import {
boardGroupPermissions,
@@ -2037,7 +2038,14 @@ const getElementsForLayout = (board: BoardForLayoutProjection, layoutId: string)
return [...itemElements, ...sectionElements];
};
const getFullBoardWithWhereAsync = async (db: Database, where: SQL<unknown>, userId: string | null) => {
const protectedLayoutRepairPromises = new Map<string, Promise<void>>();
const getFullBoardWithWhereAsync = async (
db: Database,
where: SQL<unknown>,
userId: string | null,
repairProtectedLayouts = true,
) => {
const groupPermissionWhere = userId
? inArray(
boardGroupPermissions.groupId,
@@ -2093,6 +2101,18 @@ const getFullBoardWithWhereAsync = async (db: Database, where: SQL<unknown>, use
});
}
if (repairProtectedLayouts && boardLayoutsNeedRepair(board.layouts)) {
let repairPromise = protectedLayoutRepairPromises.get(board.id);
if (!repairPromise) {
repairPromise = seedProtectedBoardLayoutsAsync(db, board.id).finally(() => {
protectedLayoutRepairPromises.delete(board.id);
});
protectedLayoutRepairPromises.set(board.id, repairPromise);
}
await repairPromise;
return getFullBoardWithWhereAsync(db, where, userId, false);
}
const { sections, items, layouts, ...otherBoardProperties } = board;
return {
@@ -2138,6 +2158,23 @@ const getFullBoardWithWhereAsync = async (db: Database, where: SQL<unknown>, use
};
};
export const boardLayoutsNeedRepair = (
boardLayouts: Array<{ id: string; breakpoint: number; role: "mobile" | "base" | "custom" }>,
) => {
const mobileLayouts = boardLayouts.filter((layout) => layout.role === "mobile");
const baseLayouts = boardLayouts.filter((layout) => layout.role === "base");
const baseLayout = baseLayouts.at(0);
return (
mobileLayouts.length !== 1 ||
baseLayouts.length !== 1 ||
mobileLayouts.at(0)?.breakpoint !== 0 ||
!baseLayout ||
boardLayouts.some((layout) => layout.id !== baseLayout.id && layout.breakpoint >= baseLayout.breakpoint) ||
new Set(boardLayouts.map((layout) => layout.breakpoint)).size !== boardLayouts.length
);
};
const forKind = <T extends WidgetKind>(kind: T) =>
z.object({
kind: z.literal(kind),
+13 -6
View File
@@ -602,6 +602,13 @@ describe("getBoardByName should return board by name", () => {
name,
...fullBoardProps,
});
expect(result.layouts).toEqual(
expect.arrayContaining([
expect.objectContaining({ role: "mobile", breakpoint: 0, columnCount: 3 }),
expect.objectContaining({ id: fullBoardProps.layoutId, role: "base", breakpoint: 768 }),
]),
);
expect(result.items.at(0)?.layouts).toHaveLength(2);
expect(spy).toHaveBeenCalledWith(expect.anything(), expect.anything(), "view");
});
@@ -1171,12 +1178,12 @@ describe("saveBoard should save full board", () => {
expect(firstItem.id).toBe(itemId);
expect(firstItem.kind).toBe("clock");
expect(SuperJSON.parse<{ is24HourFormat: boolean }>(firstItem.options).is24HourFormat).toBe(false);
const firstLayout = expectToBeDefined(firstItem.layouts[0]);
expect(firstLayout.sectionId).toBe(sectionId);
expect(firstLayout.height).toBe(3);
expect(firstLayout.width).toBe(2);
expect(firstLayout.xOffset).toBe(7);
expect(firstLayout.yOffset).toBe(5);
const updatedLayout = expectToBeDefined(firstItem.layouts.find((layout) => layout.layoutId === layoutId));
expect(updatedLayout.sectionId).toBe(sectionId);
expect(updatedLayout.height).toBe(3);
expect(updatedLayout.width).toBe(2);
expect(updatedLayout.xOffset).toBe(7);
expect(updatedLayout.yOffset).toBe(5);
expect(spy).toHaveBeenCalledWith(expect.anything(), expect.anything(), "modify");
});
it("should fail when board not found", async () => {
+2 -1
View File
@@ -161,8 +161,9 @@ export const seedDataAsync = async (db: Database) => {
await seedProtectedBoardLayoutsAsync(db);
};
export const seedProtectedBoardLayoutsAsync = async (db: Database) => {
export const seedProtectedBoardLayoutsAsync = async (db: Database, boardId?: string) => {
const dbBoards = await db.query.boards.findMany({
where: boardId ? eq(boards.id, boardId) : undefined,
with: {
layouts: true,
items: { with: { layouts: true } },
+1
View File
@@ -9,6 +9,7 @@
"exports": {
".": "./index.ts",
"./collection": "./collection.ts",
"./migrations/seed": "./migrations/seed.ts",
"./proxy-reader": "./proxy-reader.ts",
"./schema": "./schema/index.ts",
"./test": "./test/index.ts",