fix: address PR review - atomic upsert, MCP metadata, stale tokens, save UX
- Wrap setSecret delete+insert in a cross-DB transaction - Add MCP metadata to all widget secret procedures and register in mcp.ts - Show configured tokens even when no repos use that provider - Only clear token input after mutation succeeds, with loading state
This commit is contained in:
3 files changed
+62
-28
No files matched your search
@@ -17,6 +17,7 @@ import { healthMonitoringRouter } from "./router/widgets/health-monitoring";
|
||||
import { mediaRequestsRouter } from "./router/widgets/media-requests";
|
||||
import { mediaServerRouter } from "./router/widgets/media-server";
|
||||
import { smartHomeRouter } from "./router/widgets/smart-home";
|
||||
import { widgetSecretsRouter } from "./router/widgets/widget-secrets";
|
||||
|
||||
export const mcpRouter = createTRPCRouter({
|
||||
app: appRouterForApps,
|
||||
@@ -35,4 +36,5 @@ export const mcpRouter = createTRPCRouter({
|
||||
mediaRequests: mediaRequestsRouter,
|
||||
mediaServer: mediaServerRouter,
|
||||
smartHome: smartHomeRouter,
|
||||
widgetSecrets: widgetSecretsRouter,
|
||||
});
|
||||
@@ -2,7 +2,7 @@ import { TRPCError } from "@trpc/server";
|
||||
import { z } from "zod/v4";
|
||||
|
||||
import { encryptSecret } from "@homarr/common/server";
|
||||
import { and, eq } from "@homarr/db";
|
||||
import { and, eq, handleTransactionsAsync } from "@homarr/db";
|
||||
import { boards, items, widgetSecrets } from "@homarr/db/schema";
|
||||
import { releaseProviderKinds } from "@homarr/definitions";
|
||||
|
||||
@@ -20,23 +20,36 @@ const resolveItemWithBoardAccess = async (ctx: { db: any; session: any }, itemId
|
||||
|
||||
export const widgetSecretsRouter = createTRPCRouter({
|
||||
setSecret: protectedProcedure
|
||||
.meta({ mcp: { enabled: true, description: "Set an authentication token for a release provider on a specific releases widget. Requires modify permission on the widget's board. Use widget_secrets_getConfiguredKinds to check existing tokens" } })
|
||||
.input(z.object({ itemId: z.string(), kind: secretKindSchema, value: z.string().min(1).max(4096) }))
|
||||
.mutation(async ({ ctx, input }) => {
|
||||
await resolveItemWithBoardAccess(ctx, input.itemId);
|
||||
|
||||
await ctx.db
|
||||
.delete(widgetSecrets)
|
||||
.where(and(eq(widgetSecrets.itemId, input.itemId), eq(widgetSecrets.kind, input.kind)));
|
||||
const encrypted = encryptSecret(input.value);
|
||||
const row = { itemId: input.itemId, kind: input.kind, value: encrypted, updatedAt: new Date() };
|
||||
|
||||
await ctx.db.insert(widgetSecrets).values({
|
||||
itemId: input.itemId,
|
||||
kind: input.kind,
|
||||
value: encryptSecret(input.value),
|
||||
updatedAt: new Date(),
|
||||
await handleTransactionsAsync(ctx.db, {
|
||||
async handleAsync(db, schema) {
|
||||
await db.transaction(async (tx) => {
|
||||
await tx
|
||||
.delete(schema.widgetSecrets)
|
||||
.where(and(eq(schema.widgetSecrets.itemId, input.itemId), eq(schema.widgetSecrets.kind, input.kind)));
|
||||
await tx.insert(schema.widgetSecrets).values(row);
|
||||
});
|
||||
},
|
||||
handleSync(db) {
|
||||
db.transaction((tx) => {
|
||||
tx.delete(widgetSecrets)
|
||||
.where(and(eq(widgetSecrets.itemId, input.itemId), eq(widgetSecrets.kind, input.kind)))
|
||||
.run();
|
||||
tx.insert(widgetSecrets).values(row).run();
|
||||
});
|
||||
},
|
||||
});
|
||||
}),
|
||||
|
||||
deleteSecret: protectedProcedure
|
||||
.meta({ mcp: { enabled: true, description: "Remove an authentication token for a release provider from a specific releases widget. Requires modify permission on the widget's board" } })
|
||||
.input(z.object({ itemId: z.string(), kind: secretKindSchema }))
|
||||
.mutation(async ({ ctx, input }) => {
|
||||
await resolveItemWithBoardAccess(ctx, input.itemId);
|
||||
@@ -46,7 +59,10 @@ export const widgetSecretsRouter = createTRPCRouter({
|
||||
.where(and(eq(widgetSecrets.itemId, input.itemId), eq(widgetSecrets.kind, input.kind)));
|
||||
}),
|
||||
|
||||
getConfiguredKinds: protectedProcedure.input(z.object({ itemId: z.string() })).query(async ({ ctx, input }) => {
|
||||
getConfiguredKinds: protectedProcedure
|
||||
.meta({ mcp: { enabled: true, description: "List which release provider kinds have tokens configured for a releases widget. Returns an array of provider kind strings (e.g. github, gitlab, dockerHub). Requires modify permission on the widget's board" } })
|
||||
.input(z.object({ itemId: z.string() }))
|
||||
.query(async ({ ctx, input }) => {
|
||||
await resolveItemWithBoardAccess(ctx, input.itemId);
|
||||
|
||||
const secrets = await ctx.db.query.widgetSecrets.findMany({
|
||||
|
||||
Reference in new issue
Block a user