fix(custom-widgets): require explicit request bindings
This commit is contained in:
23 files changed
+363
-52
No files matched your search
@@ -107,7 +107,7 @@ export const managementQueryProcedures = {
|
||||
),
|
||||
optionRequests: widget.requests
|
||||
.filter((request) => request.kind === "query")
|
||||
.map(({ id, parameters }) => ({ id, parameters })),
|
||||
.map(({ id, parameters, optionsBinding }) => ({ id, parameters, optionsBinding })),
|
||||
updatedAt: definition.updatedAt,
|
||||
};
|
||||
});
|
||||
|
||||
@@ -89,13 +89,24 @@ export const previewBaseProcedures = {
|
||||
optionsSchema: session.optionsSchema,
|
||||
defaultOptions: session.defaultOptions,
|
||||
requests: session.requests.map(
|
||||
({ id, kind, method, minimumBoardPermission, trigger, parameters, confirmation, invalidates }) => ({
|
||||
({
|
||||
id,
|
||||
kind,
|
||||
method,
|
||||
minimumBoardPermission,
|
||||
trigger,
|
||||
parameters,
|
||||
optionsBinding,
|
||||
confirmation,
|
||||
invalidates,
|
||||
}) => ({
|
||||
id,
|
||||
kind,
|
||||
method,
|
||||
minimumBoardPermission,
|
||||
trigger,
|
||||
parameters,
|
||||
optionsBinding,
|
||||
confirmation,
|
||||
invalidates,
|
||||
}),
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import { TRPCError } from "@trpc/server";
|
||||
import { z } from "zod/v4";
|
||||
|
||||
import { resolveCustomWidgetOptionsBinding } from "@homarr/custom-widgets/core";
|
||||
|
||||
import { permissionRequiredProcedure } from "../../trpc";
|
||||
import {
|
||||
getPreviewRequestSource,
|
||||
@@ -31,8 +33,10 @@ export const previewQueryProcedures = {
|
||||
if (!request) throw new TRPCError({ code: "NOT_FOUND", message: "Preview query was not found" });
|
||||
const resolved = getPreviewRequestSource(session, request.sourceId);
|
||||
if (!resolved) throw new TRPCError({ code: "NOT_FOUND", message: "Preview source was not found" });
|
||||
const targetUrl = renderRequestTarget(resolved.source.baseUrl, request, input.params);
|
||||
const body = renderRequestBody(request.bodyTemplate, input.params);
|
||||
const params =
|
||||
request.trigger === "load" ? resolveCustomWidgetOptionsBinding(request, session.defaultOptions) : input.params;
|
||||
const targetUrl = renderRequestTarget(resolved.source.baseUrl, request, params);
|
||||
const body = renderRequestBody(request.bodyTemplate, params);
|
||||
const release = await acquireCustomWidgetRequestLimit({
|
||||
category: "query",
|
||||
userId: ctx.session.user.id,
|
||||
@@ -50,7 +54,7 @@ export const previewQueryProcedures = {
|
||||
auth: request.auth === "none" ? undefined : resolved.auth,
|
||||
networkScope: resolved.source.networkScope,
|
||||
kind: "query",
|
||||
cacheKey: `custom-jsx:preview:${session.id}:${request.id}:${hashRuntimeParams(input.params)}`,
|
||||
cacheKey: `custom-jsx:preview:${session.id}:${request.id}:${hashRuntimeParams(params)}`,
|
||||
cacheTtlSeconds: request.cacheTtlSeconds,
|
||||
});
|
||||
await recordPreviewJournal(session, {
|
||||
|
||||
@@ -7,6 +7,7 @@ import { eq } from "@homarr/db";
|
||||
import { boards, customWidgetDefinitions, items } from "@homarr/db/schema";
|
||||
import type { BoardPermission } from "@homarr/definitions";
|
||||
import { validateCustomWidgetOptions } from "@homarr/custom-widgets/core";
|
||||
import { resolveCustomWidgetOptionsBinding } from "@homarr/custom-widgets/core";
|
||||
import type { CustomJsxRequest, CustomWidgetSource } from "@homarr/custom-widgets/core";
|
||||
|
||||
import { createTRPCRouter, protectedProcedure, publicProcedure } from "../../trpc";
|
||||
@@ -173,21 +174,6 @@ const executeRequest = async (
|
||||
);
|
||||
};
|
||||
|
||||
const getLoadParams = (resolved: ResolvedDefinition, request: CustomJsxRequest) => {
|
||||
const params: Record<string, string | number | boolean> = {};
|
||||
for (const [name, type] of Object.entries(request.parameters)) {
|
||||
const value = resolved.configuration[name];
|
||||
if (typeof value !== type) {
|
||||
throw new TRPCError({
|
||||
code: "PRECONDITION_FAILED",
|
||||
message: `Load request '${request.id}' needs option '${name}' (${type})`,
|
||||
});
|
||||
}
|
||||
params[name] = value as string | number | boolean;
|
||||
}
|
||||
return params;
|
||||
};
|
||||
|
||||
export const customApiRouter = createTRPCRouter({
|
||||
getData: publicProcedure.input(itemInputSchema).query(async ({ ctx, input }) => {
|
||||
const resolved = await resolvePlacedDefinitionAsync(ctx, input.itemId);
|
||||
@@ -197,7 +183,8 @@ export const customApiRouter = createTRPCRouter({
|
||||
const entries = await Promise.all(
|
||||
loadRequests.map(async (request) => {
|
||||
try {
|
||||
const response = await executeRequest(ctx, resolved, request, getLoadParams(resolved, request));
|
||||
const params = resolveCustomWidgetOptionsBinding(request, resolved.configuration);
|
||||
const response = await executeRequest(ctx, resolved, request, params);
|
||||
return [
|
||||
request.id,
|
||||
{
|
||||
@@ -249,6 +236,9 @@ export const customApiRouter = createTRPCRouter({
|
||||
queryRequest: publicProcedure.input(namedRequestInputSchema).query(async ({ ctx, input }) => {
|
||||
const resolved = await resolvePlacedDefinitionAsync(ctx, input.itemId);
|
||||
const request = findRequest(resolved, input.requestId, "query");
|
||||
if (request.trigger !== "manual") {
|
||||
throw new TRPCError({ code: "BAD_REQUEST", message: "Load queries cannot be invoked manually" });
|
||||
}
|
||||
const response = await executeRequest(ctx, resolved, request, input.params);
|
||||
return {
|
||||
ok: response.ok,
|
||||
|
||||
Reference in new issue
Block a user