feat: add record keyed settings

- Separate managed record settings from actor identity\n- Limit integration reads to the active managed record
This commit is contained in:
Marc-André Bombeck committed 2026-08-06 02:26:44 +02:00
1 parent 1eb0d22285
commit 009de7cb5f
7 files changed
+198

No files matched your search

@@ -0,0 +1,51 @@
import {
apiHandler,
requireGuardianAuth,
SharingAccessDeniedError,
} from "@/lib/api-handler";
import { apiSuccess } from "@/lib/api-response";
import {
getIntegrationStatus,
type IntegrationKey,
} from "@/lib/integrations/status";
import { annotate } from "@/lib/logging/context";
import { resolveGuardianRecordSettingsAccess } from "@/lib/record-settings";
export const dynamic = "force-dynamic";
const MANAGED_INTEGRATIONS: readonly IntegrationKey[] = [
"withings",
"whoop",
"fitbit",
"nightscout",
"polar",
"oura",
"google-health",
"strava",
];
/**
* A managed profile may disclose whether its existing integrations are
* connected, but this endpoint deliberately has no control or credential
* projection. OAuth and provider state cannot safely carry a record selector.
*/
export const GET = apiHandler(async () => {
const context = await requireGuardianAuth();
const access = resolveGuardianRecordSettingsAccess(context);
if (!access) throw new SharingAccessDeniedError();
const integrations = await Promise.all(
MANAGED_INTEGRATIONS.map(async (integration) => {
const status = await getIntegrationStatus(access.recordId, integration);
return {
integration: status.integration,
state: status.state,
lastSuccessAt: status.lastSuccessAt,
lastAttemptAt: status.lastAttemptAt,
};
}),
);
annotate({ action: { name: "record-settings.integrations.get" } });
return apiSuccess({ recordId: access.recordId, integrations });
});
+35
View File
@@ -0,0 +1,35 @@
import {
apiHandler,
requireGuardianAuth,
SharingAccessDeniedError,
} from "@/lib/api-handler";
import { apiSuccess } from "@/lib/api-response";
import { annotate } from "@/lib/logging/context";
import {
resolveGuardianRecordSettingsAccess,
toRecordSettingsDto,
} from "@/lib/record-settings";
export const dynamic = "force-dynamic";
/**
* The active managed record's configuration identity. This is intentionally a
* distinct record DTO rather than another actor field on `/api/auth/me`.
*/
export const GET = apiHandler(async () => {
const context = await requireGuardianAuth();
const access = resolveGuardianRecordSettingsAccess(context);
if (!access) throw new SharingAccessDeniedError();
annotate({ action: { name: "record-settings.get" } });
return apiSuccess(
toRecordSettingsDto({
id: access.recordId,
name: context.user.displayName ?? context.user.username,
locale: context.user.locale,
timezone: context.user.timezone,
recordKind: "managed",
}),
);
});