The final functional web release before the iOS native client lands.
Headline work: a streaming Apple Health `export.zip` importer for
prospective iOS-migrating accounts, a server-side aggregation cache
that collapses the three hottest dashboard reads onto in-process LRU
slots with single-flight coalescing and per-user invalidation, a
broader compliance classifier with a dedicated `early` bucket so
ahead-of-window doses count as compliant, the Coach launch surface
hoisted onto every authed page with a dashboard-hero CTA, and a
trimmer Settings shelf that folds Sources into Targets. The release
also lights up the `cache.<name>.outcome` annotation on the active
wide event so production logs carry the cache hit-ratio signal
without leaking userIds.
### Added
- **Server-side aggregation cache.** A new `ServerCache<T>` primitive
(`src/lib/cache/server-cache.ts`) extends the v1.4.33 Coach snapshot
LRU shape into a reusable per-route layer with TTL expiry,
capacity-bounded LRU eviction, single-flight read coalescing, and
per-instance hit / miss / eviction / stampede counters. Wires the
three hottest dashboard reads — `/api/analytics` (slim + default),
`/api/gamification/achievements`, `/api/medications/intake?scope=compliance`
— and bolts per-user invalidation onto every measurement / mood /
medication / workout / dashboard-widget write endpoint so the next
read paints fresh data instead of waiting out the TTL. Every cache
hit / miss surfaces on the active wide event as
`cache.<name>.outcome` + `cache.<name>.key_hash` so production logs
carry the hit-ratio signal without leaking userIds.
- **Apple Health `export.zip` import.** New endpoint
`POST /api/import/apple-health-export` (synchronous multipart
upload, asynchronous ingest via a dedicated pg-boss
`apple-health-import` worker, per-`MeasurementType` ingestion
stats). Streams the upload straight to disk so a multi-gigabyte
export never lands in V8 heap, hashes the bytes inline for
content-based idempotency, and unpacks `apple_health_export/export.xml`
with a hand-rolled ZIP central-directory walker that handles Zip64
for archives past the 4 GB barrier. The parser folds every
`<Record>`, `<Workout>`, `<Correlation>`, and `<ClinicalRecord>`
into the existing `Measurement` and `Workout` row shapes — spot
rows keyed by `HKMetadataKeyExternalUUID` (or a deterministic
`sample:<sha256>` fallback), cumulative HK types collapsed into
one `stats:<HKType>:<YYYY-MM-DD>` row per user-local day to match
the iOS daily-aggregation convention. Live per-stage progress
surfaces through `GET /api/import/apple-health-export/{jobId}/status`.
An admin variant at `POST /api/admin/import-apple-health-export`
imports on behalf of a target user (cookie-only `requireAdmin()`
gate; Bearer tokens never elevate).
- **`ImportJob` schema model + migration.** New Prisma model captures
the per-upload state machine (`queued | unpacking | parsing |
upserting | done | failed`), the content-hash for idempotency
short-circuits, and the per-`MeasurementType` ingestion counters
the status route surfaces back to the client.
- **`lastSeenByType` on `/api/analytics`.** Both the slim
(`?slice=summaries`) and default slices return a per-type
`{ lastSeenAt, daysAgo }` map. The dashboard trend tiles wire 12
mounts to a new `tileStaleDays()` helper so an "X days / weeks /
months ago" hint paints under any per-metric tile whose last
sample crosses the 7-day floor. Six locales gained additive
week / month plural keys; Polish uses the genitive-plural form
that covers every non-1 count the bucket math can produce.
- **Dashboard "Ask the coach" hero CTA.** A new pill next to the
existing "Hinzufügen" / "Add" launches the Coach drawer directly
from the dashboard hero. The `<CoachLaunchProvider>` hoisted from
the insights layout to the auth shell so the drawer is reachable
from every authed route; the floating action button stays scoped
to `/insights/**` so it cannot distract on the dashboard.
- **Shared achievements query hook.** `useAchievementsQuery()`
centralises the three previous consumers — recent-achievements
card, the `/achievements` mother page, and the unlock notifier —
onto one TanStack queryKey + cache slot so dashboard cold mount
fires the endpoint once instead of twice.
- **Typed authed Cache-Control presets.** New
`src/lib/http/cache-headers.ts` module exports
`NO_STORE_BUT_BFCACHE = "private, max-age=0, must-revalidate"`,
`SHORT_LIVED_PUBLIC`, and an `applyAuthedHeaders()` helper. The
presets land at the framework level via a `next.config.ts`
`headers()` rule that stamps the bfcache-friendly directive on
every authed HTML response, restoring Chromium bfcache eligibility
for in-app back / forward navigation.
- **`scripts/print-bundle-report.mjs`.** Restores the at-a-glance
"top client chunks by parsed size" signal Turbopack dropped from
`next build`; reads `.next/analyze/client.json` (written by
`@next/bundle-analyzer` when `pnpm analyze` runs with `ANALYZE=1`)
and prints a sorted table plus totals.
### Changed
- **Settings sidebar: "Sources" folded into "Targets & Sources".**
Per-metric threshold ranges and per-metric source priority now
share one `/settings/thresholds` page so the same metric's
threshold and device-source preference sit together instead of
one sidebar entry away. `/settings/sources` keeps a
`permanentRedirect` so external bookmarks and docs links follow
through unchanged. Section count: 11 → 10.
- **Insights tab strip: vital pills collapse under a "Vitals"
parent.** Five vital pills (HRV, Resting HR, Oxygen, Body
Temperature, Active Energy) hide behind one parent pill that
opens a popover sub-list. Parent-pill active state mirrors the
URL so spatial orientation survives the collapse; each sub-page
keeps its own URL and bookmark resolves unchanged. Strip
footprint: 14 → 10 entries when every metric has data.
- **Coolify env-var audit.** `mcp__coolify-apps01__env_vars`
inspection captured the section-1 / section-2 duplicates that
have accumulated under apps-01 since v1.3.1. Audit pinned at
`.planning/round-v1434-iwa-coolify-env-audit.md`; no env-var
deletes performed (operator action).
### Fixed
- **Compliance classifier: early intakes count as compliant.** The
classifier picked up a dedicated `early` bucket (window-start
minus three hours through window-start), the `on_time` post-window
tolerance widened from one hour to three hours, and the `late`
band sits between the new `on_time` ceiling and the configurable
`lateMinutes` knob. Heatmap consumers route the new bucket
through the compliant path; the dedicated `early` counter rides
on `DailyComplianceEntry` for downstream consumers that want to
distinguish ahead-of-window from on-window intakes.
- **Compliance heatmap fallback retired.** The v1.4.33 defensive
`looksClassifierBug` fallthrough is gone now that the underlying
classifier widening removes the every-dose-very-late mode the
fallback covered.
### Performance
- **`/api/gamification/achievements` consumer collapse.** Two of
three previous consumers shared the same TanStack queryKey
literal; the third carried a per-user discriminator so the cache
treated it as a fresh slot. Dashboard cold mount fired the
endpoint twice. Collapsing onto the shared hook trims that to a
single request; with the new server-side cache slot warm, the
duplicate-mount worst case rides single-flight coalescing.
- **GHCR build fires once per release tag.** The
`docker-publish.yml` workflow lost its `push.branches: [main]`
trigger; the `:latest` raw-tag enable rule moved onto the tag
ref so each release produces exactly one multi-arch build that
refreshes both the semver tags and the `:latest` alias.
- **NFT-trace warnings silenced.** `next.config.ts` gained an
`outputFileTracingExcludes` block that narrows the Turbopack
tracer away from the `MAXMIND_LICENSE_KEY` env-access path so
the trace-report no longer warns on every dependent route file.
### Refactor
- **Dashboard analytics consumers.** The two cards that previously
decided their own freshness copy now lean on the typed
`tileStaleDays()` helper and the additive `lastSeenByType` field
on the analytics response. Mood and BD-Zielbereich tiles keep the
default `null` because they have no underlying per-type freshness
signal.
### Accessibility
- **Dashboard hero CTA hits the WCAG 2.5.5 touch-target floor.**
The new "Ask the coach" pill carries `min-h-11` on mobile and
the matching `sm:min-h-9` desktop floor so the touch target
stays above 44 px on the Pixel 5 boundary.
- **Insights tab-strip Vitals parent reads correctly to assistive
tech.** Parent pill carries `aria-current="page"` whenever the
current URL matches one of its child vital sub-pages, plus a
`data-slot="insights-tab-strip-group"` hook for visual-regression
testing.
### Internal
- **Two e2e flake windows tightened.** `onboarding-flicker` swapped
its 12-sample 50 ms poll loop for a single Playwright
auto-retrying `toBeHidden({ timeout: 700 })` assertion so the
1-2 ms race window between the analytics-pending shell and
`useAuth().user` resolving collapses to a single retry slot.
`mobile-viewport` dropped `nav a[href]` from the touch-target
sweep (the bottom-nav owns its own WCAG spec) and gated the
44-px floor on `matchMedia('(min-width: 640px)').matches === false`
so the Pixel 5 viewport never tripped into the `sm:` tier during
WebKit render commits.
- **Server-cache observability.** Every `cached()` invocation passes
an `annotate` callback that lands two keys on the wide event:
`cache.<name>.outcome` ∈ `{ hit | miss | stampede }` and
`cache.<name>.key_hash` (non-reversible djb2 32-bit hash). Ops
can grep `cache.analytics.outcome` over any time window to
compute the hit ratio per deployment.
- **AASA followups verified.** The v1.4.33 `/.well-known/apple-app-site-association`
handler serves a direct 200 on every fronting origin; Apple's
CDN has ingested matching bodies for all three domains.
### Web freeze
v1.4.34 marks the last functional release of the web codebase before
the iOS native client launches. Subsequent v1.4.x tags carry security
fixes, dependency bumps, and hotfix-only corrections. New feature work
is paused until the iOS app clears Apple review, at which point a
v1.5.0 version-bump-only release tags the milestone. The Prisma
schema head comment and `.planning/v15-strategic-plan.md` §5
decision-log row pin the freeze trigger in-tree.
HealthLog
Your health data belongs to you. Track it on your own terms.
A self-hosted, privacy-first health tracking PWA.
Weight, blood pressure, mood, medications, AI insights -- all under your control.
Website · Live Demo · Documentation
What it is
HealthLog is a self-hosted personal health tracker that runs from a single docker compose up. It covers the metrics most people actually log -- weight, blood pressure, pulse, body composition, blood glucose, sleep, mood, and medication compliance -- and brings them together in one dashboard with reference ranges from ESC/ESH 2018, ADA 2024, and NICE NG115. Withings devices sync automatically; multi-provider AI Insights (BYOK or local) explain what the numbers mean; a doctor-report PDF generates client-side. EN/DE end-to-end. AGPL-3.0.
Built for people who want their health data on their own server -- whether that's a NAS, a homelab, or a small VPS -- and who don't want to hand it to a US cloud to read a 7-day weight trend. Try the live demo to see what a working install looks like, or skip to Quick Start below.
Why HealthLog?
Most health apps lock your data behind proprietary clouds, push subscriptions, and sell your metrics to advertisers. HealthLog takes a different approach: your data stays on your server, encrypted at rest, accessible only to you.
Key Features
Health Metrics -- Track weight, blood pressure, pulse, body fat, sleep, steps, blood glucose (fasting/postprandial/random/bedtime, mg/dL ↔ mmol/L), total body water, bone mass, and pulse oximetry (SpO₂) with interactive trend charts, moving averages, and traffic-light ranges based on ESC/ESH 2018, ADA 2024, and consensus pulse-oximeter guidance (NICE NG115). Body-composition + SpO₂ metrics sync automatically from Withings Body+ scales and ScanWatch devices.
Custom Thresholds -- Override the computed default ranges per metric with the targets your clinician set. Audit-logged. Doctor Report PDF prints both your target and the standard reference.
Customizable Dashboard -- Show, hide, and drag-to-reorder every widget. Per-user layout with reset-to-defaults.
Mood Logging -- 5-point scale with tags, notes, and trend analytics. Syncs automatically from moodLog.app via webhook.
Medication Compliance -- Flexible scheduling with time windows, recurrence patterns, intake logging (take/skip/snooze), and compliance heatmaps. External API for iOS Shortcuts integration.
Withings Integration -- OAuth2 device sync for scales, blood pressure monitors, and activity trackers with automatic deduplication.
Multi-Provider AI Insights -- Pick OpenAI, Anthropic Claude, or any local OpenAI-compatible endpoint (Ollama, LM Studio, vLLM). BYOK or admin-shared key. Cached daily. Local endpoints keep all data on your network.
Doctor Report PDF Export -- Generate professional medical reports client-side. Locale-aware (English/German), with vital sign summaries, BP/BMI/glucose classification, compliance rates, custom-threshold badges, and optional AI analysis.
Built-in Feedback -- Send bug reports and feature requests from inside the app. Stored in your HealthLog database — no GitHub config required. Optional GitHub escalation for admins.
PWA with Offline Support -- Installable on iOS and Android. Service worker with intelligent caching strategies for reliable offline access.
Multi-Channel Notifications -- Telegram (with inline action buttons), ntfy (self-hostable), and Web Push. Medication reminders with late/missed escalation.
Gamification -- 38+ persistent achievements across intake streaks, compliance milestones, and healthy metric streaks.
Internationalization -- English (default) and German UI with 1500+ translation keys, guarded by a CI integrity test that fails the build on duplicate keys or drift between locales. Numbers, dates, units, and AI prompts all locale-aware via useFormatters(). Browser-based detection with per-user override.
Multi-tenant ready (v1.4) — Off-host AES-GCM-encrypted weekly backups to any S3-compatible bucket (R2, B2, MinIO, AWS), encryption-key versioning + zero-downtime rotation CLI, optional HEALTHLOG_PROCESS_TYPE=web|worker|all so HTTP and pg-boss can scale independently, and short-lived 24h access tokens with refresh-token rotation for native API clients. The browser cookie session is unchanged.
Test connection buttons (v1.4) — One-click probes for Withings, moodLog.app, Web Push, Glitchtip, and Umami in addition to the existing AI / Telegram / ntfy tests. Each one rate-limited, sanitised against SSRF redirects, and surfaces a localisable errorCode so the UI can render the failure in the user's language.
Quick Start
3 minutes from git clone to a working install. The bundled docker-compose.yml pulls a pre-built multi-arch image (linux/amd64 + linux/arm64) from GitHub Container Registry; no build step required for self-hosters. Contributors who want to test local changes can docker compose up --build.
git clone https://github.com/MBombeck/HealthLog.git
cd HealthLog
cp .env.example .env
Generate the four required secrets and paste them into .env:
echo "POSTGRES_PASSWORD=$(openssl rand -base64 24)" >> .env
echo "SESSION_SECRET=$(openssl rand -hex 32)" >> .env
echo "ENCRYPTION_KEY=$(openssl rand -hex 32)" >> .env
echo "API_TOKEN_HMAC_KEY=$(openssl rand -hex 32)" >> .env
Then bring the stack up:
docker compose up -d
Open http://localhost:3000. The first registered user becomes admin.
Behind a reverse proxy (Caddy / Traefik / Nginx) for TLS, set
NEXT_PUBLIC_APP_URLandAPP_URLto your public URL in.envbefore starting. See Self-Hosting → Reverse Proxy for examples.
Tech Stack
| Layer | Technology |
|---|---|
| Framework | Next.js 16 (App Router, React Server Components) |
| Language | TypeScript (strict mode) |
| Database | PostgreSQL 16 + Prisma 7 (26 models) |
| Job Queue | pg-boss 12 (reminders, insights, backups) |
| UI | shadcn/ui, Tailwind CSS 4, Radix UI, Lucide Icons |
| Charts | Recharts 3 |
| Data Fetching | TanStack Query 5 |
| Forms | React Hook Form 7 + Zod 4 |
| Auth | SimpleWebAuthn 13, Argon2id |
| Notifications | Telegram Bot API, ntfy, Web Push (VAPID) |
| jsPDF (client-side generation) | |
| Testing | Vitest 4 |
| Deployment | Docker (multi-stage Alpine) |
Security and Privacy
HealthLog is designed for people who take data ownership seriously.
- Self-hosted -- Your data never leaves your server. No telemetry, no third-party tracking.
- AES-256-GCM encryption -- All stored secrets (OAuth tokens, API keys, VAPID keys) are encrypted at rest.
- Passkey authentication -- WebAuthn as primary auth with password fallback (Argon2id + zxcvbn strength validation).
- Server-side sessions -- PostgreSQL-backed with 30-day sliding expiry, HttpOnly/SameSite=Strict cookies.
- Security headers -- CSP with nonces, HSTS, X-Frame-Options DENY, Permissions-Policy, Referrer-Policy.
- Rate limiting -- Sliding window on auth and API endpoints.
- HMAC-SHA256 API tokens -- Bearer tokens are hashed before storage.
- Audit logging -- All sensitive operations tracked with IP addresses.
Environment Variables
Required
| Variable | Description |
|---|---|
POSTGRES_PASSWORD |
Password for the bundled Postgres service (Docker Compose) |
DATABASE_URL |
PostgreSQL connection string (uses POSTGRES_PASSWORD above) |
SESSION_SECRET |
64-char hex string for session signing |
ENCRYPTION_KEY |
64-char hex string for AES-256-GCM |
API_TOKEN_HMAC_KEY |
64-char hex string for API token hashing |
Optional
| Variable | Description |
|---|---|
NEXT_PUBLIC_APP_URL |
Public-facing URL (default: http://localhost:3000) |
WITHINGS_CLIENT_ID |
Withings OAuth2 client ID |
WITHINGS_CLIENT_SECRET |
Withings OAuth2 client secret |
WITHINGS_REDIRECT_URI |
OAuth callback URL |
WITHINGS_WEBHOOK_SECRET |
Webhook URL hardening secret |
TELEGRAM_WEBHOOK_SECRET |
Telegram bot webhook secret |
Telegram bot token, ntfy settings, Web Push VAPID keys, Umami, and GlitchTip URLs are configured in the Admin Panel and stored encrypted in the database.
Architecture
src/
├── app/ # Next.js App Router pages & API routes
│ ├── api/ # REST API endpoints (100+ route files)
│ ├── admin/ # Admin panel
│ ├── auth/ # Login, register, passkey enrolment
│ ├── medications/ # Medication management
│ ├── measurements/ # Health metric entry
│ ├── mood/ # Mood log
│ ├── insights/ # AI-powered analytics
│ ├── charts/ # Long-form charts
│ ├── achievements/ # Gamification page
│ ├── targets/ # Custom thresholds dashboard
│ ├── notifications/ # Notification preferences matrix
│ ├── bugreport/ # Built-in feedback / bug report
│ ├── onboarding/ # 4-step guided setup
│ └── settings/ # User preferences (8 top-level sections)
├── components/
│ ├── ui/ # shadcn/ui primitives
│ ├── layout/ # Shell (sidebar, topbar, bottom nav)
│ ├── medications/ # Medication cards, forms, timeline
│ ├── measurements/ # Measurement form, list
│ ├── mood/ # Mood form, mood list
│ ├── charts/ # Recharts wrappers
│ ├── insights/ # AI insight status / advisor cards
│ ├── gamification/ # Achievement cards, progress
│ ├── monitoring/ # Umami / GlitchTip bootstrap
│ └── settings/ # Settings-page section components
├── lib/
│ ├── auth/ # Session, audit, passkey logic
│ ├── notifications/ # Dispatcher + channel senders
│ ├── jobs/ # pg-boss worker (reminders, insights, backups)
│ ├── analytics/ # Trend calculations, compliance, correlations
│ ├── ai/ # Multi-provider client (OpenAI, Anthropic, local)
│ ├── insights/ # Insight pipeline + medical prompts
│ ├── gamification/ # Achievement definitions
│ ├── feedback/ # Built-in feedback + GitHub escalation
│ ├── moodlog/ # moodLog.app webhook + sync
│ ├── monitoring/ # Umami / GlitchTip server-side hooks
│ ├── withings/ # OAuth client, sync service
│ ├── logging/ # Wide Events: builder, context, transports
│ ├── i18n/ # Translations context & config
│ ├── validations/ # Shared Zod schemas
│ ├── api-handler.ts # apiHandler wrapper, requireAuth/requireAdmin
│ ├── api-response.ts # { data, error } envelope helpers
│ ├── crypto.ts # AES-256-GCM encrypt/decrypt
│ ├── rate-limit.ts # Sliding-window rate limiter
│ ├── db.ts # Prisma singleton
│ └── doctor-report-pdf.ts # Client-side PDF generation
├── hooks/ # React hooks
└── generated/prisma/ # Generated Prisma client
Key Patterns
- RSC by default --
"use client"only for interactive components - API envelope -- All responses follow
{ data, error }shape viaapiSuccess()/apiError()insrc/lib/api-response.ts - apiHandler wrapper -- Every API route wraps its handler in
apiHandler()(src/lib/api-handler.ts) for consistent error handling, Wide-Event structured logging, andx-request-idpropagation - Encrypted secrets -- Withings tokens, API keys, VAPID keys, notification credentials
- Timezone-aware --
Europe/Berlinfor display, UTC in database - Route protection --
proxy.ts(Next.js 16's renamed middleware) checks session cookie, redirects unauthenticated requests - Client-side PDF -- Doctor reports generated in browser via jsPDF
API Reference
All mutations require authentication via session cookie. External ingest uses Bearer tokens.
Health Data
| Method | Endpoint | Description |
|---|---|---|
GET |
/api/measurements |
List measurements (paginated, filterable) |
POST |
/api/measurements |
Create measurement |
DELETE |
/api/measurements/:id |
Delete measurement |
GET |
/api/analytics |
Trend summaries (7d/30d) |
GET |
/api/export |
Export as CSV or JSON |
POST |
/api/import |
Import from JSON |
POST |
/api/doctor-report |
Aggregated data for PDF |
Mood
| Method | Endpoint | Description |
|---|---|---|
GET |
/api/mood-entries |
List mood entries |
POST |
/api/mood-entries |
Create mood entry |
DELETE |
/api/mood-entries/:id |
Delete mood entry |
GET |
/api/mood/analytics |
Mood trend analytics |
POST |
/api/integrations/moodlog/webhook |
moodLog.app webhook |
Medications
| Method | Endpoint | Description |
|---|---|---|
GET |
/api/medications |
List all medications |
POST |
/api/medications |
Create medication |
PUT |
/api/medications/:id |
Update medication |
DELETE |
/api/medications/:id |
Delete medication |
POST |
/api/medications/:id/intake |
Log intake event |
GET |
/api/medications/:id/compliance |
Compliance stats |
POST |
/api/ingest/medication |
External intake (Bearer) |
Auth and Integrations
| Method | Endpoint | Description |
|---|---|---|
POST |
/api/auth/register |
Create account |
POST |
/api/auth/login |
Password login |
POST |
/api/auth/logout |
Destroy session |
GET |
/api/auth/me |
Current user profile + Gravatar URL |
POST |
/api/auth/password |
Change password |
PATCH |
/api/auth/profile |
Update profile fields |
POST |
/api/auth/passkey/* |
WebAuthn flows (4 sub-routes) |
GET |
/api/auth/passkeys |
List enrolled passkeys |
GET |
/api/auth/codex/authorize |
ChatGPT (codex) OAuth start |
GET |
/api/withings/connect |
Initiate Withings OAuth |
POST |
/api/withings/sync |
Trigger manual Withings sync |
POST |
/api/withings/webhook |
Withings notification webhook |
POST |
/api/insights/generate |
Regenerate AI insights |
GET |
/api/insights/comprehensive |
Aggregated insight payload |
GET |
/api/gamification/achievements |
Achievement progress |
GET |
/api/health |
Docker health check |
Personalization (Thresholds + Dashboard)
| Method | Endpoint | Description |
|---|---|---|
GET |
/api/user/thresholds |
Read per-user threshold overrides |
PUT |
/api/user/thresholds |
Upsert thresholds (rate-limited, audit-logged) |
GET |
/api/insights/targets |
Effective ranges (defaults + overrides merged) |
GET |
/api/dashboard/widgets |
Read dashboard layout |
PUT |
/api/dashboard/widgets |
Persist dashboard layout (show/hide/reorder) |
POST |
/api/onboarding/complete |
Mark onboarding finished |
Feedback + API Tokens
| Method | Endpoint | Description |
|---|---|---|
POST |
/api/feedback |
Submit in-app feedback |
GET |
/api/bugreport/status |
Check published GitHub issue state |
GET |
/api/tokens |
List own API tokens |
POST |
/api/tokens |
Mint new API token (Bearer, hashed) |
DELETE |
/api/tokens/:id |
Revoke API token |
Notifications
| Method | Endpoint | Description |
|---|---|---|
GET |
/api/notifications/preferences |
Read per-channel × per-event matrix |
PUT |
/api/notifications/preferences |
Update preferences |
GET |
/api/notifications/vapid |
VAPID public key for Web Push |
POST |
/api/notifications/web-push |
Register Web Push subscription |
POST |
/api/telegram/webhook |
Telegram bot inline-button callback |
Admin (admin role required)
| Method | Endpoint | Description |
|---|---|---|
GET |
/api/admin/status |
System + integration status |
GET |
/api/admin/users |
List users |
POST |
/api/admin/users/:id/reset-password |
Force password reset |
GET |
/api/admin/feedback |
All feedback / bug reports |
POST |
/api/admin/feedback/:id/github |
Escalate feedback to GitHub issue |
GET |
/api/admin/audit-log |
Audit-log viewer |
GET |
/api/admin/ai-settings |
Read shared AI provider config |
PUT |
/api/admin/ai-settings |
Update shared AI provider config |
GET |
/api/admin/tokens |
All issued API tokens |
POST |
/api/admin/notifications/test |
Send test notification |
GET |
/api/admin/data |
Data backups + counts |
GET |
/api/admin/status-overview |
Aggregated status for the 6-card grid |
POST |
/api/admin/backup/test |
Probe S3-compatible backup target |
Public + v1.4 additions
| Method | Endpoint | Description |
|---|---|---|
GET |
/api/version |
Public — version + build SHA + license, no auth |
POST |
/api/integrations/withings/test |
Probe a saved Withings connection |
POST |
/api/integrations/moodlog/test |
Probe moodLog.app webhook reachability |
POST |
/api/notifications/web-push/test |
Send a test Web Push to the current user |
POST |
/api/monitoring/glitchtip/test |
Trigger a Glitchtip ingest probe |
POST |
/api/monitoring/umami/test |
Verify Umami script + website ID resolve |
POST |
/api/auth/refresh |
Native client refresh-token rotation |
POST |
/api/auth/refresh/revoke |
Revoke an issued refresh token |
Integrations
| Integration | Setup | Purpose |
|---|---|---|
| Withings | Env vars | Auto-sync weight, BP, and activity |
| Telegram | Admin Panel | Medication reminders with inline buttons |
| ntfy | User Settings | Self-hosted push notifications |
| Web Push | Admin Panel | Browser-native VAPID notifications |
| OpenAI | User Settings | AI health insights (BYOK) |
| moodLog.app | User Settings | Mood tracking sync |
| Umami | Admin Panel | Privacy-friendly analytics |
| GlitchTip | Admin Panel | Sentry-compatible error tracking |
Local Development
# Prerequisites: Node.js 20+, pnpm, PostgreSQL
cp .env.example .env
pnpm install
pnpm db:generate
pnpm db:migrate
pnpm dev
Scripts
pnpm dev # Development server
pnpm build # Production build
pnpm lint # ESLint
pnpm typecheck # TypeScript strict check
pnpm test # Vitest
pnpm format # Prettier
pnpm db:generate # Generate Prisma client
pnpm db:migrate # Create & apply migration
pnpm db:migrate:deploy # Apply migrations (production)
pnpm db:studio # Prisma Studio GUI
Deployment
The included docker-compose.yml runs the app and PostgreSQL. The entrypoint automatically waits for the database, runs pending migrations, and starts the server.
The app listens on port 3000. Place it behind Nginx, Caddy, or Traefik for TLS termination. Works out of the box with Coolify.
Contributing
Contributions are welcome. See CONTRIBUTING.md for guidelines.
- Code style:
pnpm format && pnpm lint - Type safety:
pnpm typecheckmust pass - Tests:
pnpm test - UI language: English by default, German selectable per user. Code, comments, and commits: English.
License
HealthLog is licensed under the GNU Affero General Public License v3.0.