fix: address remaining widget review feedback
This commit is contained in:
1 parent
5553c4e0c5
commit
f00f5b83b5
13 files changed
+149
-26
No files matched your search
@@ -45,14 +45,14 @@ export const calendarRouter = createTRPCRouter({
|
||||
};
|
||||
},
|
||||
{
|
||||
fallback: (integration, error) => ({
|
||||
fallback: (integration) => ({
|
||||
events: [],
|
||||
integration: {
|
||||
id: integration.id,
|
||||
name: integration.name,
|
||||
kind: integration.kind,
|
||||
},
|
||||
error: error instanceof Error ? error.message : String(error),
|
||||
error: "Integration request failed",
|
||||
}),
|
||||
throwOnAllFailures: true,
|
||||
},
|
||||
|
||||
@@ -44,10 +44,10 @@ export const mediaRequestsRouter = createTRPCRouter({
|
||||
};
|
||||
},
|
||||
{
|
||||
fallback: (integration, error) => ({
|
||||
fallback: (integration) => ({
|
||||
integration: { id: integration.id, name: integration.name, kind: integration.kind },
|
||||
data: [],
|
||||
error: error instanceof Error ? error.message : String(error),
|
||||
error: "Integration request failed",
|
||||
}),
|
||||
throwOnAllFailures: true,
|
||||
},
|
||||
@@ -101,10 +101,10 @@ export const mediaRequestsRouter = createTRPCRouter({
|
||||
};
|
||||
},
|
||||
{
|
||||
fallback: (integration, error) => ({
|
||||
fallback: (integration) => ({
|
||||
integration: { id: integration.id, name: integration.name, kind: integration.kind },
|
||||
data: { stats: null, users: [] },
|
||||
error: error instanceof Error ? error.message : String(error),
|
||||
error: "Integration request failed",
|
||||
}),
|
||||
throwOnAllFailures: true,
|
||||
},
|
||||
|
||||
@@ -38,12 +38,12 @@ export const mediaServerRouter = createTRPCRouter({
|
||||
};
|
||||
},
|
||||
{
|
||||
fallback: (integration, error) => ({
|
||||
fallback: (integration) => ({
|
||||
integrationId: integration.id,
|
||||
integrationName: integration.name,
|
||||
integrationKind: integration.kind,
|
||||
sessions: [],
|
||||
error: error instanceof Error ? error.message : String(error),
|
||||
error: "Integration request failed",
|
||||
}),
|
||||
throwOnAllFailures: true,
|
||||
},
|
||||
|
||||
@@ -9,6 +9,7 @@ import { boards, items } from "@homarr/db/schema";
|
||||
import type { WidgetOptionsSettings } from "../../../../widgets/src";
|
||||
import { createTRPCRouter, protectedProcedure, publicProcedure } from "../../trpc";
|
||||
import { throwIfActionForbiddenAsync } from "../board/board-access";
|
||||
import { validateTimetableOptionsChangeAsync } from "./timetable";
|
||||
import { throwIfCustomWidgetPlacementChangeForbidden } from "../board/custom-widget-placement-access";
|
||||
|
||||
export const optionsRouter = createTRPCRouter({
|
||||
@@ -46,12 +47,15 @@ export const optionsRouter = createTRPCRouter({
|
||||
});
|
||||
}
|
||||
|
||||
const options = SuperJSON.parse<Record<string, unknown>>(item.options);
|
||||
const updatedOptions = { ...options, ...input.newOptions };
|
||||
const previousOptions = SuperJSON.parse<Record<string, unknown>>(item.options);
|
||||
const updatedOptions = { ...previousOptions, ...input.newOptions };
|
||||
if (item.kind === "timetable") {
|
||||
await validateTimetableOptionsChangeAsync(updatedOptions, previousOptions);
|
||||
}
|
||||
throwIfCustomWidgetPlacementChangeForbidden({
|
||||
isAdmin: ctx.session.user.permissions.includes("admin"),
|
||||
submittedItems: [{ id: item.id, kind: item.kind, options: updatedOptions }],
|
||||
storedItems: [{ id: item.id, kind: item.kind, options }],
|
||||
storedItems: [{ id: item.id, kind: item.kind, options: previousOptions }],
|
||||
});
|
||||
await ctx.db
|
||||
.update(items)
|
||||
|
||||
@@ -156,6 +156,34 @@ const resolvePublicTimetableAddressesAsync = async (baseUrl: string): Promise<Ti
|
||||
return addresses;
|
||||
};
|
||||
|
||||
const normalizeTimetableOptionsBaseUrlOrThrowBadRequest = (options: Record<string, unknown>) => {
|
||||
const parsedOptions = timetableOptionsSchema.safeParse(options);
|
||||
if (!parsedOptions.success) {
|
||||
throw new TRPCError({ code: "BAD_REQUEST", message: "Invalid timetable widget options" });
|
||||
}
|
||||
return normalizeBaseUrlOrThrowBadRequest(parsedOptions.data.baseUrl ?? DEFAULT_TIMETABLE_BASE_URL);
|
||||
};
|
||||
|
||||
export const validateTimetableOptionsChangeAsync = async (
|
||||
nextOptions: Record<string, unknown>,
|
||||
previousOptions?: Record<string, unknown>,
|
||||
) => {
|
||||
const nextBaseUrl = normalizeTimetableOptionsBaseUrlOrThrowBadRequest(nextOptions);
|
||||
if (nextBaseUrl === DEFAULT_TIMETABLE_BASE_URL) return;
|
||||
|
||||
let previousBaseUrl: string | undefined;
|
||||
if (previousOptions) {
|
||||
try {
|
||||
previousBaseUrl = normalizeTimetableOptionsBaseUrlOrThrowBadRequest(previousOptions);
|
||||
} catch {
|
||||
// Invalid legacy options must not prevent replacing them with a valid configuration.
|
||||
}
|
||||
}
|
||||
if (nextBaseUrl === previousBaseUrl) return;
|
||||
|
||||
await resolvePublicTimetableAddressesAsync(nextBaseUrl);
|
||||
};
|
||||
|
||||
const getSavedTimetableBaseUrl = (serializedOptions: string) => {
|
||||
const options = parseSavedTimetableOptions(serializedOptions);
|
||||
return normalizeSavedTimetableBaseUrl(options.baseUrl ?? DEFAULT_TIMETABLE_BASE_URL);
|
||||
@@ -174,6 +202,7 @@ const resolveTimetableBaseUrlAsync = async (
|
||||
const requestedBaseUrl = normalizeBaseUrlOrThrowBadRequest(input.baseUrl);
|
||||
if (allowBoardConfiguration) {
|
||||
if (input.boardId) {
|
||||
await throwIfActionForbiddenAsync(ctx, eq(boards.id, input.boardId), "modify");
|
||||
let savedOptions: string | undefined;
|
||||
if (input.itemId) {
|
||||
const item = await ctx.db.query.items.findFirst({
|
||||
@@ -182,8 +211,6 @@ const resolveTimetableBaseUrlAsync = async (
|
||||
if (!item) throw new TRPCError({ code: "NOT_FOUND", message: "Timetable widget not found" });
|
||||
savedOptions = item.options;
|
||||
}
|
||||
|
||||
await throwIfActionForbiddenAsync(ctx, eq(boards.id, input.boardId), "modify");
|
||||
const savedBaseUrl = savedOptions === undefined ? undefined : getSavedTimetableBaseUrl(savedOptions);
|
||||
if (requestedBaseUrl === savedBaseUrl || requestedBaseUrl === DEFAULT_TIMETABLE_BASE_URL) {
|
||||
return { baseUrl: requestedBaseUrl };
|
||||
|
||||
Reference in new issue
Block a user