refactor: consolidate custom widget admin routes
This commit is contained in:
1 parent
48438fc003
commit
74fa51a461
8 files changed
+72
-73
No files matched your search
@@ -0,0 +1,42 @@
|
||||
import type { NextRequest } from "next/server";
|
||||
import { NextResponse } from "next/server";
|
||||
|
||||
import { CUSTOM_WIDGET_MCP_AUTHORING_PROMPT } from "@homarr/custom-widgets/authoring-prompt";
|
||||
import { getCustomWidgetComponentCatalog, getCustomWidgetSkill } from "@homarr/custom-widgets/authoring-resources";
|
||||
import { getCustomWidgetJsonSchema } from "@homarr/custom-widgets/core";
|
||||
|
||||
import { adminRoute } from "../admin";
|
||||
|
||||
interface RouteContext {
|
||||
params: Promise<{ resource: string }>;
|
||||
}
|
||||
|
||||
const resources = new Map<string, () => unknown>([
|
||||
["schema", getCustomWidgetJsonSchema],
|
||||
["components", getCustomWidgetComponentCatalog],
|
||||
["skill", getCustomWidgetSkill],
|
||||
[
|
||||
"authoring-prompt",
|
||||
() => ({
|
||||
version: 2,
|
||||
prompt: CUSTOM_WIDGET_MCP_AUTHORING_PROMPT,
|
||||
resources: [
|
||||
"homarr://custom-widgets/schema",
|
||||
"homarr://custom-widgets/components",
|
||||
"homarr://custom-widgets/skill",
|
||||
],
|
||||
httpResources: ["/api/custom-widgets/schema", "/api/custom-widgets/components", "/api/custom-widgets/skill"],
|
||||
}),
|
||||
],
|
||||
]);
|
||||
|
||||
const getAuthoringResource = async (_request: NextRequest, context: RouteContext): Promise<Response> => {
|
||||
const { resource } = await context.params;
|
||||
const loadResource = resources.get(resource);
|
||||
if (!loadResource) {
|
||||
return NextResponse.json({ error: "Custom Widget authoring resource not found." }, { status: 404 });
|
||||
}
|
||||
return NextResponse.json(loadResource());
|
||||
};
|
||||
|
||||
export const GET = adminRoute(getAuthoringResource);
|
||||
@@ -8,7 +8,7 @@ const mocks = vi.hoisted(() => ({
|
||||
|
||||
vi.mock("@homarr/auth/next", () => ({ auth: mocks.auth }));
|
||||
|
||||
import { requireCustomWidgetAdmin } from "./admin";
|
||||
import { adminRoute } from "./admin";
|
||||
|
||||
beforeEach(() => {
|
||||
mocks.auth.mockResolvedValue({ user: { permissions: ["admin"] } });
|
||||
@@ -16,7 +16,11 @@ beforeEach(() => {
|
||||
|
||||
describe("Custom Widget HTTP resource access", () => {
|
||||
test("allows authenticated administrators", async () => {
|
||||
await expect(requireCustomWidgetAdmin()).resolves.toBeNull();
|
||||
const handler = vi.fn(async () => Response.json({ ok: true }));
|
||||
const response = await adminRoute(handler)();
|
||||
|
||||
expect(response.status).toBe(200);
|
||||
expect(handler).toHaveBeenCalledOnce();
|
||||
});
|
||||
|
||||
test.each([
|
||||
@@ -24,7 +28,10 @@ describe("Custom Widget HTTP resource access", () => {
|
||||
["authenticated non-admins", { user: { permissions: ["board-modify-all"] } }],
|
||||
])("denies %s", async (_label, session) => {
|
||||
mocks.auth.mockResolvedValue(session);
|
||||
const response = await requireCustomWidgetAdmin();
|
||||
expect(response?.status).toBe(403);
|
||||
const handler = vi.fn(async () => Response.json({ ok: true }));
|
||||
const response = await adminRoute(handler)();
|
||||
|
||||
expect(response.status).toBe(403);
|
||||
expect(handler).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
@@ -2,10 +2,14 @@ import { NextResponse } from "next/server";
|
||||
|
||||
import { auth } from "@homarr/auth/next";
|
||||
|
||||
export const requireCustomWidgetAdmin = async (): Promise<NextResponse | null> => {
|
||||
const session = await auth();
|
||||
if (!session?.user.permissions.includes("admin")) {
|
||||
return NextResponse.json({ error: "Administrator access is required." }, { status: 403 });
|
||||
}
|
||||
return null;
|
||||
};
|
||||
type RouteHandler<Arguments extends unknown[]> = (...args: Arguments) => Response | Promise<Response>;
|
||||
|
||||
export const adminRoute =
|
||||
<Arguments extends unknown[]>(handler: RouteHandler<Arguments>) =>
|
||||
async (...args: Arguments): Promise<Response> => {
|
||||
const session = await auth();
|
||||
if (!session?.user.permissions.includes("admin")) {
|
||||
return NextResponse.json({ error: "Administrator access is required." }, { status: 403 });
|
||||
}
|
||||
return handler(...args);
|
||||
};
|
||||
@@ -1,20 +0,0 @@
|
||||
import { NextResponse } from "next/server";
|
||||
|
||||
import { CUSTOM_WIDGET_MCP_AUTHORING_PROMPT } from "@homarr/custom-widgets/authoring-prompt";
|
||||
|
||||
import { requireCustomWidgetAdmin } from "../admin";
|
||||
|
||||
export async function GET() {
|
||||
const denied = await requireCustomWidgetAdmin();
|
||||
if (denied) return denied;
|
||||
return NextResponse.json({
|
||||
version: 2,
|
||||
prompt: CUSTOM_WIDGET_MCP_AUTHORING_PROMPT,
|
||||
resources: [
|
||||
"homarr://custom-widgets/schema",
|
||||
"homarr://custom-widgets/components",
|
||||
"homarr://custom-widgets/skill",
|
||||
],
|
||||
httpResources: ["/api/custom-widgets/schema", "/api/custom-widgets/components", "/api/custom-widgets/skill"],
|
||||
});
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
import { NextResponse } from "next/server";
|
||||
|
||||
import { getCustomWidgetComponentCatalog } from "@homarr/custom-widgets/authoring-resources";
|
||||
|
||||
import { requireCustomWidgetAdmin } from "../admin";
|
||||
|
||||
export async function GET() {
|
||||
const denied = await requireCustomWidgetAdmin();
|
||||
if (denied) return denied;
|
||||
return NextResponse.json(getCustomWidgetComponentCatalog());
|
||||
}
|
||||
@@ -13,16 +13,14 @@ import { customWidgetSourceSchema } from "@homarr/custom-widgets/core";
|
||||
import { invalidateCustomWidgetResponseCache } from "@homarr/custom-widgets/server";
|
||||
import { db } from "@homarr/db";
|
||||
|
||||
import { requireCustomWidgetAdmin } from "../../admin";
|
||||
import { adminRoute } from "../../admin";
|
||||
import { readConfigurationRequestBody } from "../body";
|
||||
|
||||
interface RouteContext {
|
||||
params: Promise<{ token: string }>;
|
||||
}
|
||||
|
||||
export async function GET(_request: NextRequest, context: RouteContext) {
|
||||
const denied = await requireCustomWidgetAdmin();
|
||||
if (denied) return denied;
|
||||
const getConfigurationRequest = async (_request: NextRequest, context: RouteContext): Promise<Response> => {
|
||||
const { token } = await context.params;
|
||||
const request = await getCustomWidgetConfigurationRequest(token);
|
||||
if (!request)
|
||||
@@ -38,11 +36,9 @@ export async function GET(_request: NextRequest, context: RouteContext) {
|
||||
},
|
||||
{ headers: { "Cache-Control": "no-store" } },
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
export async function POST(request: NextRequest, context: RouteContext) {
|
||||
const denied = await requireCustomWidgetAdmin();
|
||||
if (denied) return denied;
|
||||
const completeConfigurationRequest = async (request: NextRequest, context: RouteContext): Promise<Response> => {
|
||||
const { token } = await context.params;
|
||||
const pending = await getCustomWidgetConfigurationRequest(token);
|
||||
if (!pending || pending.status !== "pending") {
|
||||
@@ -128,4 +124,7 @@ export async function POST(request: NextRequest, context: RouteContext) {
|
||||
} finally {
|
||||
await releaseCustomWidgetConfigurationRequest(token);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export const GET = adminRoute(getConfigurationRequest);
|
||||
export const POST = adminRoute(completeConfigurationRequest);
|
||||
@@ -1,11 +0,0 @@
|
||||
import { NextResponse } from "next/server";
|
||||
|
||||
import { getCustomWidgetJsonSchema } from "@homarr/custom-widgets/core";
|
||||
|
||||
import { requireCustomWidgetAdmin } from "../admin";
|
||||
|
||||
export async function GET() {
|
||||
const denied = await requireCustomWidgetAdmin();
|
||||
if (denied) return denied;
|
||||
return NextResponse.json(getCustomWidgetJsonSchema());
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
import { NextResponse } from "next/server";
|
||||
|
||||
import { getCustomWidgetSkill } from "@homarr/custom-widgets/authoring-resources";
|
||||
|
||||
import { requireCustomWidgetAdmin } from "../admin";
|
||||
|
||||
export async function GET() {
|
||||
const denied = await requireCustomWidgetAdmin();
|
||||
if (denied) return denied;
|
||||
return NextResponse.json(getCustomWidgetSkill());
|
||||
}
|
||||
Reference in new issue
Block a user