From c475f46f33323c3e7b3e65ff2b33c2a68ed0673f Mon Sep 17 00:00:00 2001 From: ramvignesh-b Date: Mon, 11 May 2026 16:33:14 +0530 Subject: [PATCH 1/3] chore: init OpenAPI documentation and Scalar API --- bun.lock | 24 ++++++++++++++++++++++++ package.json | 2 ++ 2 files changed, 26 insertions(+) diff --git a/bun.lock b/bun.lock index 920faa9..027ce16 100644 --- a/bun.lock +++ b/bun.lock @@ -5,7 +5,9 @@ "": { "name": "auth-server", "dependencies": { + "@hono/zod-openapi": "^1.4.0", "@phosphor-icons/core": "^2.1.1", + "@scalar/hono-api-reference": "^0.10.14", "hono": "^4.12.18", "ioredis": "^5.10.1", "zod": "^4.4.3", @@ -20,6 +22,8 @@ }, }, "packages": { + "@asteasolutions/zod-to-openapi": ["@asteasolutions/zod-to-openapi@8.5.0", "", { "dependencies": { "openapi3-ts": "^4.1.2" }, "peerDependencies": { "zod": "^4.0.0" } }, "sha512-SABbKiObg5dLRiTFnqiW1WWwGcg1BJfmHtT2asIBnBHg6Smy/Ms2KHc650+JI4Hw7lSkdiNebEGXpwoxfben8Q=="], + "@biomejs/biome": ["@biomejs/biome@2.4.15", "", { "optionalDependencies": { "@biomejs/cli-darwin-arm64": "2.4.15", "@biomejs/cli-darwin-x64": "2.4.15", "@biomejs/cli-linux-arm64": "2.4.15", "@biomejs/cli-linux-arm64-musl": "2.4.15", "@biomejs/cli-linux-x64": "2.4.15", "@biomejs/cli-linux-x64-musl": "2.4.15", "@biomejs/cli-win32-arm64": "2.4.15", "@biomejs/cli-win32-x64": "2.4.15" }, "bin": { "biome": "bin/biome" } }, "sha512-j5VH3a/h/HXTKBM50MDMxRCzkeLv9S2XJcW2WgnZT1+xyisi+0bISrXR82gCX+8S9lvK0skEvHJRN+3Ktr2hlw=="], "@biomejs/cli-darwin-arm64": ["@biomejs/cli-darwin-arm64@2.4.15", "", { "os": "darwin", "cpu": "arm64" }, "sha512-rF3PPqLq1yoST79zaQbDjVJwsuIeci/O+9bgNmC5QpgOqz6aqYuzA4abyAGx+mgyiDXn4A049xAN8gijbuR1Qg=="], @@ -38,10 +42,22 @@ "@biomejs/cli-win32-x64": ["@biomejs/cli-win32-x64@2.4.15", "", { "os": "win32", "cpu": "x64" }, "sha512-zBrGq5mx5wwpnow4+2BxUvleDM+GNd4sLbPaMapsSLQLD0NGRCquqPBTgN+7XkUteHvj7M+BstuI8tmnV7+HgQ=="], + "@hono/zod-openapi": ["@hono/zod-openapi@1.4.0", "", { "dependencies": { "@asteasolutions/zod-to-openapi": "^8.5.0", "@hono/zod-validator": "^0.8.0", "openapi3-ts": "^4.5.0" }, "peerDependencies": { "hono": ">=4.10.0", "zod": "^4.0.0" } }, "sha512-AFchqR1N/NxfI4hUOSGI2/g8zLROxA1OE7Oh5JJFlTaGxhrdRyH+93gd0tIBpb0z8s9r8hUoNnaOBfHbdb4NMw=="], + + "@hono/zod-validator": ["@hono/zod-validator@0.8.0", "", { "peerDependencies": { "hono": ">=4.10.0", "zod": "^3.25.0 || ^4.0.0" } }, "sha512-5uS4S1/LKtZQYvD4BtpPUFkOv8d1wNxHHrChm26buMiEYc1FrHWvDUaKVBwkiVtvSExHSpLGDvcnpI2Copyj9w=="], + "@ioredis/commands": ["@ioredis/commands@1.5.1", "", {}, "sha512-JH8ZL/ywcJyR9MmJ5BNqZllXNZQqQbnVZOqpPQqE1vHiFgAw4NHbvE0FOduNU8IX9babitBT46571OnPTT0Zcw=="], "@phosphor-icons/core": ["@phosphor-icons/core@2.1.1", "", {}, "sha512-v4ARvrip4qBCImOE5rmPUylOEK4iiED9ZyKjcvzuezqMaiRASCHKcRIuvvxL/twvLpkfnEODCOJp5dM4eZilxQ=="], + "@scalar/client-side-rendering": ["@scalar/client-side-rendering@0.1.7", "", { "dependencies": { "@scalar/types": "0.9.6" } }, "sha512-IDzjKF93jrOljlvKBsLHXT1FPWgz56jFrMPC+iLihREp1qH8wF92mG8Zpakw8cURkEuw5WijRk0xNBP2moGyuw=="], + + "@scalar/helpers": ["@scalar/helpers@0.6.0", "", {}, "sha512-pfSamAgBxqFeE8IpEG6uGkHlnPhY1CLeOTttV9+vKQbrBk5b7vvyTsUXv0Hz4kNU1TFrxcTTPE+Akn5S+jlTtQ=="], + + "@scalar/hono-api-reference": ["@scalar/hono-api-reference@0.10.14", "", { "dependencies": { "@scalar/client-side-rendering": "0.1.7" }, "peerDependencies": { "hono": "^4.12.5" } }, "sha512-LCIT4ul3c4MyD7shhxsWcvvOABt0fEHNQID2n+2TPeItc/MR2qCjjp/QfqD+JoQ7zbc0nnzh1kwRR06MVBmnUA=="], + + "@scalar/types": ["@scalar/types@0.9.6", "", { "dependencies": { "@scalar/helpers": "0.6.0", "nanoid": "^5.1.6", "type-fest": "^5.3.1", "zod": "^4.3.5" } }, "sha512-UaCQQcscFTJdxZREE8KhUdSJgaDlc44TZbmWcZffs4m1hzqOvEI7lEBS13iBpLq7/cxUXFgyJdecywvNqJ0PkA=="], + "@types/node": ["@types/node@22.19.18", "", { "dependencies": { "undici-types": "~6.21.0" } }, "sha512-9v00a+dn2yWVsYDEunWC4g/TcRKVq3r8N5FuZp7u0SGrPvdN9c2yXI9bBuf5Fl0hNCb+QTIePTn5pJs2pwBOQQ=="], "ansi-escapes": ["ansi-escapes@7.3.0", "", { "dependencies": { "environment": "^1.0.0" } }, "sha512-BvU8nYgGQBxcmMuEeUEmNTvrMVjJNSH7RgW24vXexN4Ven6qCvy4TntnvlnwnMLTVlcRQQdbRY8NKnaIoeWDNg=="], @@ -90,8 +106,12 @@ "ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="], + "nanoid": ["nanoid@5.1.11", "", { "bin": { "nanoid": "bin/nanoid.js" } }, "sha512-v+KEsUv2ps74PaSKv0gHTxTCgMXOIfBEbaqa6w6ISIGC7ZsvHN4N9oJ8d4cmf0n5oTzQz2SLmThbQWhjd/8eKg=="], + "onetime": ["onetime@7.0.0", "", { "dependencies": { "mimic-function": "^5.0.0" } }, "sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ=="], + "openapi3-ts": ["openapi3-ts@4.5.0", "", { "dependencies": { "yaml": "^2.8.0" } }, "sha512-jaL+HgTq2Gj5jRcfdutgRGLosCy/hT8sQf6VOy+P+g36cZOjI1iukdPnijC+4CmeRzg/jEllJUboEic2FhxhtQ=="], + "picomatch": ["picomatch@4.0.4", "", {}, "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A=="], "redis-errors": ["redis-errors@1.2.0", "", {}, "sha512-1qny3OExCf0UvUV/5wpYKf2YwPcOqXzkwKKSmKHiE6ZMQs5heeE/c8eXK+PNllPvmjgAbfnsbpkGZWy8cBpn9w=="], @@ -114,8 +134,12 @@ "strip-ansi": ["strip-ansi@7.2.0", "", { "dependencies": { "ansi-regex": "^6.2.2" } }, "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w=="], + "tagged-tag": ["tagged-tag@1.0.0", "", {}, "sha512-yEFYrVhod+hdNyx7g5Bnkkb0G6si8HJurOoOEgC8B/O0uXLHlaey/65KRv6cuWBNhBgHKAROVpc7QyYqE5gFng=="], + "tinyexec": ["tinyexec@1.1.2", "", {}, "sha512-dAqSqE/RabpBKI8+h26GfLq6Vb3JVXs30XYQjdMjaj/c2tS8IYYMbIzP599KtRj7c57/wYApb3QjgRgXmrCukA=="], + "type-fest": ["type-fest@5.6.0", "", { "dependencies": { "tagged-tag": "^1.0.0" } }, "sha512-8ZiHFm91orbSAe2PSAiSVBVko18pbhbiB3U9GglSzF/zCGkR+rxpHx6sEMCUm4kxY4LjDIUGgCfUMtwfZfjfUA=="], + "typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="], "undici-types": ["undici-types@6.21.0", "", {}, "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ=="], diff --git a/package.json b/package.json index 7c8f112..484e00b 100644 --- a/package.json +++ b/package.json @@ -10,7 +10,9 @@ "prepare": "husky" }, "dependencies": { + "@hono/zod-openapi": "^1.4.0", "@phosphor-icons/core": "^2.1.1", + "@scalar/hono-api-reference": "^0.10.14", "hono": "^4.12.18", "ioredis": "^5.10.1", "zod": "^4.4.3" -- 2.52.0 From d6f13ec332ef492a2054c48a4c4ea9425ef235c3 Mon Sep 17 00:00:00 2001 From: ramvignesh-b Date: Mon, 11 May 2026 16:38:05 +0530 Subject: [PATCH 2/3] feat: migrate API to @hono/zod-openapi and add Scalar API documentation UI --- src/index.ts | 29 ++++++++++- src/routes/api.ts | 124 ++++++++++++++++++++++++++++++++++++++++------ 2 files changed, 136 insertions(+), 17 deletions(-) diff --git a/src/index.ts b/src/index.ts index 13b6a6d..2acc239 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,4 +1,5 @@ -import { Hono } from "hono"; +import { OpenAPIHono } from "@hono/zod-openapi"; +import { Scalar } from "@scalar/hono-api-reference"; import { logger } from "hono/logger"; import { prettyJSON } from "hono/pretty-json"; import { config } from "./config"; @@ -7,7 +8,31 @@ import { authRoutes } from "./routes/auth"; import { configRoutes } from "./routes/config"; import { dashboardRoutes } from "./routes/dashboard"; -const app = new Hono({ strict: false }); +const app = new OpenAPIHono({ strict: false }); + +// OpenAPI specs +app.doc("/doc", { + openapi: "3.0.0", + info: { + version: "1.0.0", + title: "toknd — Auth Broker API", + description: "Centralized token management and OAuth2 broker service.", + }, +}); + +app.openAPIRegistry.registerComponent("securitySchemes", "API_KEY", { + type: "http", + scheme: "bearer", +}); + +// Scalar API +app.get( + "/ui", + Scalar({ + theme: "solarized", + url: "/doc", + }), +); app.use("*", logger()); app.use("*", prettyJSON()); diff --git a/src/routes/api.ts b/src/routes/api.ts index 698f91b..3aa28dd 100644 --- a/src/routes/api.ts +++ b/src/routes/api.ts @@ -1,31 +1,122 @@ -import { Hono } from "hono"; +import { createRoute, OpenAPIHono, z } from "@hono/zod-openapi"; import { ConfigManager } from "../core/ConfigManager"; import { redis } from "../core/RedisClient"; import { TokenManager } from "../core/TokenManager"; import { authMiddleware } from "../middleware/auth"; import { GenericProvider } from "../providers/GenericProvider"; -const apiRoutes = new Hono({ strict: false }); +const apiRoutes = new OpenAPIHono(); +// Schemas +const StatusResponseSchema = z + .record( + z.string(), + z.object({ + accessToken: z.string().nullable(), + refreshToken: z.string().nullable(), + lastUpdated: z.string().nullable(), + }), + ) + .openapi("StatusResponse"); + +const TokenResponseSchema = z + .object({ + access_token: z.string(), + }) + .openapi("TokenResponse"); + +const RefreshResponseSchema = z + .object({ + success: z.boolean(), + status: z.object({ + accessToken: z.string().nullable(), + refreshToken: z.string().nullable(), + lastUpdated: z.string().nullable(), + }), + }) + .openapi("RefreshResponse"); + +const ErrorSchema = z + .object({ + error: z.string(), + }) + .openapi("Error"); + +// Routes +const statusRoute = createRoute({ + method: "get", + path: "/status", + security: [{ API_KEY: [] }], + responses: { + 200: { + content: { "application/json": { schema: StatusResponseSchema } }, + description: "Retrieve the status of all configured providers", + }, + }, +}); + +const tokenRoute = createRoute({ + method: "get", + path: "/token/{provider}", + security: [{ API_KEY: [] }], + request: { + params: z.object({ + provider: z.string().openapi({ example: "trakt" }), + }), + }, + responses: { + 200: { + content: { "application/json": { schema: TokenResponseSchema } }, + description: "Retrieve a valid access token for a specific provider", + }, + 404: { + content: { "application/json": { schema: ErrorSchema } }, + description: "Provider not configured or tokens not found", + }, + }, +}); + +const refreshRoute = createRoute({ + method: "post", + path: "/refresh/{provider}", + security: [{ API_KEY: [] }], + request: { + params: z.object({ + provider: z.string().openapi({ example: "trakt" }), + }), + }, + responses: { + 200: { + content: { "application/json": { schema: RefreshResponseSchema } }, + description: "Manually force a token refresh for a specific provider", + }, + 404: { + content: { "application/json": { schema: ErrorSchema } }, + description: "Provider not configured", + }, + }, +}); + +// Implementations apiRoutes.use("*", authMiddleware); -apiRoutes.get("/status", async (c) => { +apiRoutes.openapi(statusRoute, async (c) => { const configManager = new ConfigManager(redis); const providers = await configManager.getAllProviders(); - const status: Record = {}; + const status: z.infer = {}; for (const provider of Object.keys(providers)) { const accessToken = await redis.get(`provider:${provider}:access_token`); const refreshToken = await redis.get(`provider:${provider}:refresh_token`); const lastUpdated = await redis.get(`provider:${provider}:last_updated`); - status[provider] = { accessToken, refreshToken, lastUpdated } as any; + status[provider] = { accessToken, refreshToken, lastUpdated }; } - return c.json(status); + return c.json(status, 200); }); -apiRoutes.get("/token/:provider", async (c) => { - const providerName = c.req.param("provider"); +apiRoutes.openapi(tokenRoute, async (c) => { + const providerName = c.req.valid("param").provider; const configManager = new ConfigManager(redis); const providerConfig = await configManager.getProviderConfig(providerName); @@ -40,11 +131,11 @@ apiRoutes.get("/token/:provider", async (c) => { if (!accessToken) { return c.json({ error: "No tokens found for provider" }, 404); } - return c.json({ access_token: accessToken }); + return c.json({ access_token: accessToken }, 200); }); -apiRoutes.post("/refresh/:provider", async (c) => { - const providerName = c.req.param("provider"); +apiRoutes.openapi(refreshRoute, async (c) => { + const providerName = c.req.valid("param").provider; const configManager = new ConfigManager(redis); const providerConfig = await configManager.getProviderConfig(providerName); @@ -60,10 +151,13 @@ apiRoutes.post("/refresh/:provider", async (c) => { const refreshToken = await redis.get(`provider:${providerName}:refresh_token`); const lastUpdated = await redis.get(`provider:${providerName}:last_updated`); - return c.json({ - success: true, - status: { accessToken, refreshToken, lastUpdated }, - }); + return c.json( + { + success: true, + status: { accessToken, refreshToken, lastUpdated }, + }, + 200, + ); }); export { apiRoutes }; -- 2.52.0 From 327822141bf31c3d3b2f738abec88fe1560b62e4 Mon Sep 17 00:00:00 2001 From: ramvignesh-b Date: Mon, 11 May 2026 16:38:57 +0530 Subject: [PATCH 3/3] feat: update documentation endpoint path --- src/index.ts | 5 ++- src/routes/auth.ts | 83 +++++++++++++++++++++++++++-------- src/routes/config.ts | 100 ++++++++++++++++++++++++++++++++++--------- 3 files changed, 147 insertions(+), 41 deletions(-) diff --git a/src/index.ts b/src/index.ts index 2acc239..ea4dbc1 100644 --- a/src/index.ts +++ b/src/index.ts @@ -25,14 +25,15 @@ app.openAPIRegistry.registerComponent("securitySchemes", "API_KEY", { scheme: "bearer", }); -// Scalar API +// Scalar API Reference app.get( - "/ui", + "/api", Scalar({ theme: "solarized", url: "/doc", }), ); +app.get("/docs", (c) => c.redirect("/api")); app.use("*", logger()); app.use("*", prettyJSON()); diff --git a/src/routes/auth.ts b/src/routes/auth.ts index 01bf5a4..45fc314 100644 --- a/src/routes/auth.ts +++ b/src/routes/auth.ts @@ -1,15 +1,74 @@ import { readFile } from "node:fs/promises"; import { join } from "node:path"; -import { Hono } from "hono"; +import { createRoute, OpenAPIHono, z } from "@hono/zod-openapi"; import { ConfigManager } from "../core/ConfigManager"; import { redis } from "../core/RedisClient"; import { TokenManager } from "../core/TokenManager"; import { GenericProvider } from "../providers/GenericProvider"; -const authRoutes = new Hono({ strict: false }); +const authRoutes = new OpenAPIHono(); -authRoutes.get("/:provider/login", async (c) => { - const providerName = c.req.param("provider"); +const AuthErrorResponse = z + .object({ + error: z.string(), + message: z.string(), + }) + .openapi("AuthError"); + +// Routes +const loginRoute = createRoute({ + method: "get", + path: "/{provider}/login", + request: { + params: z.object({ + provider: z.string().openapi({ example: "trakt" }), + }), + }, + responses: { + 302: { + description: "Redirect to the provider's OAuth2 authorization page", + }, + 404: { + content: { "application/json": { schema: AuthErrorResponse } }, + description: "Provider not configured", + }, + }, +}); + +const callbackRoute = createRoute({ + method: "get", + path: "/callback", + request: { + query: z.object({ + state: z + .string() + .openapi({ description: "The provider name (passed as state during login)" }), + code: z.string().openapi({ description: "The authorization code from the provider" }), + }), + }, + responses: { + 200: { + description: "Success page indicating successful token exchange", + content: { "text/html": { schema: { type: "string" } } }, + }, + 400: { + content: { "application/json": { schema: AuthErrorResponse } }, + description: "Invalid request (missing state or code)", + }, + 404: { + content: { "application/json": { schema: AuthErrorResponse } }, + description: "Provider configuration not found", + }, + 500: { + content: { "application/json": { schema: AuthErrorResponse } }, + description: "Token exchange failure", + }, + }, +}); + +// Implementations +authRoutes.openapi(loginRoute, async (c) => { + const providerName = c.req.valid("param").provider; const configManager = new ConfigManager(redis); const providerConfig = await configManager.getProviderConfig(providerName); @@ -24,26 +83,14 @@ authRoutes.get("/:provider/login", async (c) => { } const provider = new GenericProvider(providerName, providerConfig); - const url = new URL(c.req.url); const redirectUri = providerConfig.redirectUri || `${url.origin}/auth/callback`; return c.redirect(provider.getAuthUrl(providerName, redirectUri)); }); -authRoutes.get("/callback", async (c) => { - const providerName = c.req.query("state"); - const code = c.req.query("code"); - - if (!providerName || !code) { - return c.json( - { - error: "Invalid Request", - message: "Missing state (provider) or authorization code.", - }, - 400, - ); - } +authRoutes.openapi(callbackRoute, async (c) => { + const { state: providerName, code } = c.req.valid("query"); const configManager = new ConfigManager(redis); const providerConfig = await configManager.getProviderConfig(providerName); diff --git a/src/routes/config.ts b/src/routes/config.ts index 8053d4c..d8d3b6d 100644 --- a/src/routes/config.ts +++ b/src/routes/config.ts @@ -1,33 +1,91 @@ -import { Hono } from "hono"; -import { ConfigManager, ProviderConfigSchema } from "../core/ConfigManager"; +import { createRoute, OpenAPIHono, z } from "@hono/zod-openapi"; +import { ConfigManager } from "../core/ConfigManager"; import { redis } from "../core/RedisClient"; import { authMiddleware } from "../middleware/auth"; -const configRoutes = new Hono({ strict: false }); +const configRoutes = new OpenAPIHono(); -configRoutes.use("*", authMiddleware); +// Schemas +const ProviderConfigSpec = z + .object({ + authUrl: z.url().openapi({ example: "https://trakt.tv/oauth/authorize" }), + tokenUrl: z.url().openapi({ example: "https://api.trakt.tv/oauth/token" }), + clientId: z.string().openapi({ example: "your_client_id" }), + clientSecret: z.string().openapi({ example: "your_client_secret" }), + scope: z.string().openapi({ example: "public" }), + redirectUri: z.url().optional().openapi({ example: "http://localhost:3000/auth/callback" }), + }) + .openapi("ProviderConfig"); -configRoutes.get("/", async (c) => { - const configManager = new ConfigManager(redis); - const providers = await configManager.getAllProviders(); - return c.json(providers); +const AllProvidersResponse = z + .record(z.string(), ProviderConfigSpec) + .openapi("AllProvidersResponse"); + +const SuccessMessage = z + .object({ + message: z.string(), + }) + .openapi("SuccessMessage"); + +const ErrorResponse = z + .object({ + error: z.string(), + }) + .openapi("ErrorResponse"); + +// Routes +const listConfigRoute = createRoute({ + method: "get", + path: "/", + security: [{ API_KEY: [] }], + responses: { + 200: { + content: { "application/json": { schema: AllProvidersResponse } }, + description: "Retrieve all registered provider configurations", + }, + }, }); -configRoutes.post("/:provider", async (c) => { - const provider = c.req.param("provider"); +const setConfigRoute = createRoute({ + method: "post", + path: "/{provider}", + security: [{ API_KEY: [] }], + request: { + params: z.object({ + provider: z.string().openapi({ example: "trakt" }), + }), + body: { + content: { "application/json": { schema: ProviderConfigSpec } }, + }, + }, + responses: { + 200: { + content: { "application/json": { schema: SuccessMessage } }, + description: "Save or update a provider configuration", + }, + 400: { + content: { "application/json": { schema: ErrorResponse } }, + description: "Invalid configuration data", + }, + }, +}); + +// Implementations +configRoutes.use("*", authMiddleware); + +configRoutes.openapi(listConfigRoute, async (c) => { + const configManager = new ConfigManager(redis); + const providers = await configManager.getAllProviders(); + return c.json(providers, 200); +}); + +configRoutes.openapi(setConfigRoute, async (c) => { + const provider = c.req.valid("param").provider; + const body = c.req.valid("json"); const configManager = new ConfigManager(redis); - try { - const body = await c.req.json(); - const validatedConfig = ProviderConfigSchema.parse(body); - await configManager.setProviderConfig(provider, validatedConfig); - return c.json({ message: `Config for ${provider} saved successfully` }); - } catch (error) { - if (error instanceof Error) { - return c.json({ error: error.message }, 400); - } - return c.json({ error: "Internal Server Error" }, 500); - } + await configManager.setProviderConfig(provider, body); + return c.json({ message: `Config for ${provider} saved successfully` }, 200); }); export { configRoutes }; -- 2.52.0