diff --git a/apps/aelis-backend/src/session/user-session-manager.ts b/apps/aelis-backend/src/session/user-session-manager.ts index f28ee2a..cf9500b 100644 --- a/apps/aelis-backend/src/session/user-session-manager.ts +++ b/apps/aelis-backend/src/session/user-session-manager.ts @@ -38,6 +38,27 @@ export class UserSessionManager { return this.providers.get(sourceId) } + /** + * Returns the user's config for a source, or defaults if no row exists. + * + * @throws {SourceNotFoundError} if the sourceId has no registered provider + */ + async fetchSourceConfig( + userId: string, + sourceId: string, + ): Promise<{ enabled: boolean; config: unknown }> { + const provider = this.providers.get(sourceId) + if (!provider) { + throw new SourceNotFoundError(sourceId, userId) + } + + const row = await sources(this.db, userId).find(sourceId) + return { + enabled: row?.enabled ?? false, + config: row?.config ?? {}, + } + } + async getOrCreate(userId: string): Promise { const existing = this.sessions.get(userId) if (existing) return existing diff --git a/apps/aelis-backend/src/sources/http.test.ts b/apps/aelis-backend/src/sources/http.test.ts index bbe5103..2b70b49 100644 --- a/apps/aelis-backend/src/sources/http.test.ts +++ b/apps/aelis-backend/src/sources/http.test.ts @@ -138,6 +138,10 @@ function patch(app: Hono, sourceId: string, body: unknown) { }) } +function get(app: Hono, sourceId: string) { + return app.request(`/api/sources/${sourceId}`, { method: "GET" }) +} + function put(app: Hono, sourceId: string, body: unknown) { return app.request(`/api/sources/${sourceId}`, { method: "PUT", @@ -150,6 +154,72 @@ function put(app: Hono, sourceId: string, body: unknown) { // Tests // --------------------------------------------------------------------------- +describe("GET /api/sources/:sourceId", () => { + test("returns 401 without auth", async () => { + activeStore = createInMemoryStore() + const { app } = createApp([createStubProvider("aelis.weather", weatherConfig)]) + + const res = await get(app, "aelis.weather") + + expect(res.status).toBe(401) + }) + + test("returns 404 for unknown source", async () => { + activeStore = createInMemoryStore() + const { app } = createApp([createStubProvider("aelis.weather", weatherConfig)], MOCK_USER_ID) + + const res = await get(app, "unknown.source") + + expect(res.status).toBe(404) + const body = (await res.json()) as { error: string } + expect(body.error).toContain("not found") + }) + + test("returns enabled and config for existing source", async () => { + activeStore = createInMemoryStore() + activeStore.seed(MOCK_USER_ID, "aelis.weather", { + enabled: true, + config: { units: "metric" }, + }) + const { app } = createApp([createStubProvider("aelis.weather", weatherConfig)], MOCK_USER_ID) + + const res = await get(app, "aelis.weather") + + expect(res.status).toBe(200) + const body = (await res.json()) as { enabled: boolean; config: unknown } + expect(body.enabled).toBe(true) + expect(body.config).toEqual({ units: "metric" }) + }) + + test("returns defaults when user has no row for source", async () => { + activeStore = createInMemoryStore() + const { app } = createApp([createStubProvider("aelis.weather", weatherConfig)], MOCK_USER_ID) + + const res = await get(app, "aelis.weather") + + expect(res.status).toBe(200) + const body = (await res.json()) as { enabled: boolean; config: unknown } + expect(body.enabled).toBe(false) + expect(body.config).toEqual({}) + }) + + test("returns disabled source", async () => { + activeStore = createInMemoryStore() + activeStore.seed(MOCK_USER_ID, "aelis.weather", { + enabled: false, + config: { units: "imperial" }, + }) + const { app } = createApp([createStubProvider("aelis.weather", weatherConfig)], MOCK_USER_ID) + + const res = await get(app, "aelis.weather") + + expect(res.status).toBe(200) + const body = (await res.json()) as { enabled: boolean; config: unknown } + expect(body.enabled).toBe(false) + expect(body.config).toEqual({ units: "imperial" }) + }) +}) + describe("PATCH /api/sources/:sourceId", () => { test("returns 401 without auth", async () => { activeStore = createInMemoryStore() diff --git a/apps/aelis-backend/src/sources/http.ts b/apps/aelis-backend/src/sources/http.ts index 2d122eb..570a628 100644 --- a/apps/aelis-backend/src/sources/http.ts +++ b/apps/aelis-backend/src/sources/http.ts @@ -38,10 +38,31 @@ export function registerSourcesHttpHandlers( await next() }) + app.get("/api/sources/:sourceId", inject, authSessionMiddleware, handleGetSource) app.patch("/api/sources/:sourceId", inject, authSessionMiddleware, handleUpdateSource) app.put("/api/sources/:sourceId", inject, authSessionMiddleware, handleReplaceSource) } +async function handleGetSource(c: Context) { + const sourceId = c.req.param("sourceId") + if (!sourceId) { + return c.body(null, 404) + } + + const sessionManager = c.get("sessionManager") + const user = c.get("user")! + + try { + const result = await sessionManager.fetchSourceConfig(user.id, sourceId) + return c.json(result) + } catch (err) { + if (err instanceof SourceNotFoundError) { + return c.json({ error: err.message }, 404) + } + throw err + } +} + async function handleUpdateSource(c: Context) { const sourceId = c.req.param("sourceId") if (!sourceId) {