feat(board): add mobile home board (#1910)

* feat(board): add mobile home board

* fix: add missing translations

* fix: mysql key reference with other datatype

* fix: format issue

* fix: missing trpc context arguments in tests

* fix: missing trpc context arguments in tests
This commit is contained in:
Meier Lukas
2025-01-14 19:54:55 +01:00
committed by GitHub
parent ec3bda34e0
commit e01d74f4f8
32 changed files with 3634 additions and 90 deletions

View File

@@ -2,7 +2,8 @@ import { TRPCError } from "@trpc/server";
import superjson from "superjson";
import { constructBoardPermissions } from "@homarr/auth/shared";
import type { Database, InferInsertModel, SQL } from "@homarr/db";
import type { DeviceType } from "@homarr/common/server";
import type { Database, InferInsertModel, InferSelectModel, SQL } from "@homarr/db";
import { and, createId, eq, inArray, like, or } from "@homarr/db";
import { getServerSettingByKeyAsync } from "@homarr/db/queries";
import {
@@ -121,6 +122,7 @@ export const boardRouter = createTRPCRouter({
return dbBoards.map((board) => ({
...board,
isHome: currentUserWhenPresent?.homeBoardId === board.id,
isMobileHome: currentUserWhenPresent?.mobileHomeBoardId === board.id,
}));
}),
search: publicProcedure
@@ -194,6 +196,7 @@ export const boardRouter = createTRPCRouter({
logoImageUrl: board.logoImageUrl,
permissions: constructBoardPermissions(board, ctx.session),
isHome: currentUserWhenPresent?.homeBoardId === board.id,
isMobileHome: currentUserWhenPresent?.mobileHomeBoardId === board.id,
}));
}),
createBoard: permissionRequiredProcedure
@@ -336,7 +339,10 @@ export const boardRouter = createTRPCRouter({
await throwIfActionForbiddenAsync(ctx, eq(boards.id, input.id), "full");
const boardSettings = await getServerSettingByKeyAsync(ctx.db, "board");
if (input.visibility !== "public" && boardSettings.homeBoardId === input.id) {
if (
input.visibility !== "public" &&
(boardSettings.homeBoardId === input.id || boardSettings.mobileHomeBoardId === input.id)
) {
throw new TRPCError({
code: "BAD_REQUEST",
message: "Cannot make home board private",
@@ -358,30 +364,30 @@ export const boardRouter = createTRPCRouter({
await ctx.db.update(users).set({ homeBoardId: input.id }).where(eq(users.id, ctx.session.user.id));
}),
setMobileHomeBoard: protectedProcedure.input(z.object({ id: z.string() })).mutation(async ({ ctx, input }) => {
await throwIfActionForbiddenAsync(ctx, eq(boards.id, input.id), "view");
await ctx.db.update(users).set({ mobileHomeBoardId: input.id }).where(eq(users.id, ctx.session.user.id));
}),
getHomeBoard: publicProcedure.query(async ({ ctx }) => {
const userId = ctx.session?.user.id;
const user = userId
? await ctx.db.query.users.findFirst({
? ((await ctx.db.query.users.findFirst({
where: eq(users.id, userId),
})
})) ?? null)
: null;
// 1. user home board, 2. home board, 3. not found
let boardWhere: SQL<unknown> | null = null;
if (user?.homeBoardId) {
boardWhere = eq(boards.id, user.homeBoardId);
} else {
const boardSettings = await getServerSettingByKeyAsync(ctx.db, "board");
boardWhere = boardSettings.homeBoardId ? eq(boards.id, boardSettings.homeBoardId) : null;
}
const homeBoardId = await getHomeIdBoardAsync(ctx.db, user, ctx.deviceType);
if (!boardWhere) {
if (!homeBoardId) {
throw new TRPCError({
code: "NOT_FOUND",
message: "No home board found",
});
}
const boardWhere = eq(boards.id, homeBoardId);
await throwIfActionForbiddenAsync(ctx, boardWhere, "view");
return await getFullBoardWithWhereAsync(ctx.db, boardWhere, ctx.session?.user.id ?? null);
@@ -692,6 +698,29 @@ export const boardRouter = createTRPCRouter({
}),
});
/**
* Get the home board id of the user with the given device type
* For an example of a user with deviceType = 'mobile' it would go through the following order:
* 1. user.mobileHomeBoardId
* 2. user.homeBoardId
* 3. serverSettings.mobileHomeBoardId
* 4. serverSettings.homeBoardId
* 5. show NOT_FOUND error
*/
const getHomeIdBoardAsync = async (
db: Database,
user: InferSelectModel<typeof users> | null,
deviceType: DeviceType,
) => {
const settingKey = deviceType === "mobile" ? "mobileHomeBoardId" : "homeBoardId";
if (user?.[settingKey] || user?.homeBoardId) {
return user[settingKey] ?? user.homeBoardId;
} else {
const boardSettings = await getServerSettingByKeyAsync(db, "board");
return boardSettings[settingKey] ?? boardSettings.homeBoardId;
}
};
const noBoardWithSimilarNameAsync = async (db: Database, name: string, ignoredIds: string[] = []) => {
const boards = await db.query.boards.findMany({
columns: {