chore(release): v1.4.27

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.
This commit is contained in:
Marc-André Bombeck authored and GitHub committed 2026-05-15 22:59:10 +02:00
1 parent d98e85cd03
commit 4f09c3f862
314 files changed
+26801 -4419

No files matched your search

+5
View File
@@ -136,6 +136,11 @@ export const GET = apiHandler(async (request: NextRequest) => {
action: true,
ipAddress: true,
location: true,
// v1.4.27 B3 — surface the carrier chip data alongside the
// existing location string. Both nullable so older rows render
// with the same dash that `location` falls back to.
asn: true,
carrier: true,
details: true,
createdAt: true,
user: {
@@ -3,7 +3,7 @@ import { apiHandler, requireAdmin } from "@/lib/api-handler";
import { annotate, getEvent } from "@/lib/logging/context";
import { apiSuccess } from "@/lib/api-response";
import { parseScheduleRecurrence } from "@/lib/medication-schedule";
import { dispatchNotification } from "@/lib/notifications/dispatcher";
import { dispatchLocalisedNotification } from "@/lib/notifications/dispatch-localised";
import { getUserTodayBounds, getDayOfWeekInTz } from "@/lib/timezone";
export const dynamic = "force-dynamic";
@@ -138,20 +138,36 @@ export const POST = apiHandler(async () => {
const timeWindow = `${schedule.windowStart}–${schedule.windowEnd}`;
try {
// v1.4.27 F21 — each notification renders in the affected
// user's `User.locale` (the recipient is the user whose dose
// is overdue, not the admin running the check).
// `dispatchLocalisedNotification` resolves the locale from
// the userId inside the helper.
if (status === "missed") {
await dispatchNotification({
eventType: "MEDICATION_REMINDER",
await dispatchLocalisedNotification({
userId: med.user.id,
title: `Verpasst: ${med.name}`,
message: `<b>${med.name}</b> (${doseInfo}, ${timeWindow}) wurde als verpasst markiert.`,
eventType: "MEDICATION_REMINDER",
titleKey: "notifications.admin.reminderCheckMissedTitle",
messageKey: "notifications.admin.reminderCheckMissedBody",
params: {
medication: med.name,
dose: doseInfo,
window: timeWindow,
},
metadata: { medicationId: med.id },
});
} else {
await dispatchNotification({
eventType: "MEDICATION_REMINDER",
await dispatchLocalisedNotification({
userId: med.user.id,
title: `Erinnerung: ${med.name}`,
message: `Erinnerung: <b>${med.name}</b> (${doseInfo}, ${timeWindow}) wurde noch nicht eingenommen. Seit ${minutesPastEnd} Min überfällig.`,
eventType: "MEDICATION_REMINDER",
titleKey: "notifications.admin.reminderCheckOverdueTitle",
messageKey: "notifications.admin.reminderCheckOverdueBody",
params: {
medication: med.name,
dose: doseInfo,
window: timeWindow,
minutes: minutesPastEnd,
},
metadata: { medicationId: med.id },
});
}
+24 -3
View File
@@ -12,6 +12,8 @@ import type {
ChannelType,
NotificationPayload,
} from "@/lib/notifications/types";
import { defaultLocale, type Locale } from "@/lib/i18n/config";
import { getServerTranslator } from "@/lib/i18n/server-translator";
export const dynamic = "force-dynamic";
@@ -59,6 +61,11 @@ async function ensureTelegramChannel(userId: string): Promise<boolean> {
return true;
}
function isLocale(value: string | null | undefined): value is Locale {
return value === "de" || value === "en" || value === "fr" ||
value === "es" || value === "it" || value === "pl";
}
export const POST = apiHandler(async () => {
const { user } = await requireAdmin();
annotate({ action: { name: "admin.notifications.test" } });
@@ -84,12 +91,26 @@ export const POST = apiHandler(async () => {
});
}
// v1.4.27 F21 — resolve the admin's persisted locale so the test
// notification arrives in their preferred language. The test route
// talks to the senders directly (not the dispatcher) because it
// needs per-channel success / error feedback for the UI, so we
// compose the payload locally rather than via
// `dispatchLocalisedNotification`.
const adminLocaleRow = await prisma.user.findUnique({
where: { id: user.id },
select: { locale: true },
});
const adminLocale: Locale = isLocale(adminLocaleRow?.locale)
? adminLocaleRow.locale
: defaultLocale;
const t = getServerTranslator(adminLocale).t;
const payload: NotificationPayload = {
eventType: "SYSTEM_ALERT",
userId: user.id,
title: "Test-Notification",
message:
"<b>HealthLog Test:</b> If you see this message, your notifications are working!",
title: t("notifications.admin.testNotificationTitle"),
message: t("notifications.admin.testNotificationBody"),
};
const results: Array<{