mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-10 05:12:14 +00:00
v1.4.27 — Mobile capability and maintainer-finding cleanup. ResponsiveSheet primitive (bottom-sheet on narrow viewports, centred dialog on desktop) applied to BD, Weight, Glucose, Mood, Activity forms. Maintainer-finding cleanups across Coach, Insights, Reports, Privacy. GeoLite2 offline geocoding with resilience layer (graceful fallback when EULA not yet propagated). E2E gap-fill coverage. 450-line release notes attached to the GitHub Release.
63 lines
1.8 KiB
TypeScript
63 lines
1.8 KiB
TypeScript
import { prisma } from "@/lib/db";
|
|
import { lookupIpAsn, lookupIpLocation } from "@/lib/geo";
|
|
|
|
export async function auditLog(
|
|
action: string,
|
|
opts: {
|
|
userId?: string | null;
|
|
details?: Record<string, unknown>;
|
|
ipAddress?: string | null;
|
|
} = {},
|
|
) {
|
|
const entry = await prisma.auditLog.create({
|
|
data: {
|
|
action,
|
|
userId: opts.userId ?? null,
|
|
details: opts.details ? JSON.stringify(opts.details) : null,
|
|
ipAddress: opts.ipAddress ?? null,
|
|
},
|
|
});
|
|
|
|
// Fire-and-forget: resolve IP location + carrier and update the entry
|
|
// with whichever fields the resolver could fill. v1.4.27 B3 — carrier
|
|
// resolution is synchronous offline MMDB read so it cannot outrun the
|
|
// location-lookup 3 s race; we run both inside the same race so a
|
|
// hung online provider still bounds the await window.
|
|
if (opts.ipAddress && action.startsWith("auth.")) {
|
|
const ipAddress = opts.ipAddress;
|
|
const timeout = new Promise<null>((resolve) =>
|
|
setTimeout(() => resolve(null), 3000),
|
|
);
|
|
|
|
Promise.race([
|
|
Promise.all([
|
|
lookupIpLocation(ipAddress),
|
|
Promise.resolve(lookupIpAsn(ipAddress)),
|
|
]),
|
|
timeout,
|
|
])
|
|
.then((result) => {
|
|
if (!result) return;
|
|
const [location, asnRow] = result;
|
|
const data: {
|
|
location?: string;
|
|
asn?: number;
|
|
carrier?: string | null;
|
|
} = {};
|
|
if (location) data.location = location;
|
|
if (asnRow) {
|
|
data.asn = asnRow.asn;
|
|
data.carrier = asnRow.carrier;
|
|
}
|
|
if (Object.keys(data).length === 0) return;
|
|
return prisma.auditLog.update({
|
|
where: { id: entry.id },
|
|
data,
|
|
});
|
|
})
|
|
.catch(() => {
|
|
// Silently ignore geo lookup failures
|
|
});
|
|
}
|
|
}
|