Daniel Volz d0a40bde88 feat: Nagging reminders with max limit + ENV defaults for settings (#18)
* ci: prevent duplicate test runs - tests only on PRs, inline tests for builds

* docs: add testing and CI/CD documentation

* security: fix CodeQL vulnerabilities (SSRF, XSS, rate limiting)

- Add URL validation to prevent SSRF attacks on notification endpoints
  - Block private IPs (10.x, 172.16-31.x, 192.168.x, 169.254.x)
  - Block localhost and internal hostnames
  - Only allow HTTP/HTTPS protocols
- Add HTML escaping for medication names in email templates (XSS)
- Add stricter rate limiting for auth routes (5 req/15min for login/register)
- Add SSRF protection tests (405 tests total)

* security: add rate limiting to remaining auth routes

* chore: add CodeQL config to suppress rate-limit false positives

Rate limiting IS implemented via @fastify/rate-limit plugin:
- Global: 100 req/min (index.ts)
- Auth routes: 5-10 req/min via config.rateLimit option

CodeQL doesn't recognize Fastify's plugin-based rate limiting pattern.

* ci: switch to CodeQL Advanced Setup

- Add custom codeql.yml workflow
- Configure to use codeql-config.yml
- Exclude js/missing-rate-limiting rule (false positive)
  Rate limiting is implemented via @fastify/rate-limit plugin

* ci: add explicit permissions to workflows

Fixes CodeQL 'Workflow does not contain permissions' warnings.
Sets minimal 'contents: read' at top level.

* ci: add manual trigger to CodeQL workflow

* ci: add explicit permissions to all workflow jobs

* build(deps): bump esbuild, @vitest/coverage-v8 and vitest in /backend

Bumps [esbuild](https://github.com/evanw/esbuild) to 0.27.2 and updates ancestor dependencies [esbuild](https://github.com/evanw/esbuild), [@vitest/coverage-v8](https://github.com/vitest-dev/vitest/tree/HEAD/packages/coverage-v8) and [vitest](https://github.com/vitest-dev/vitest/tree/HEAD/packages/vitest). These dependencies need to be updated together.


Updates `esbuild` from 0.21.5 to 0.27.2
- [Release notes](https://github.com/evanw/esbuild/releases)
- [Changelog](https://github.com/evanw/esbuild/blob/main/CHANGELOG-2024.md)
- [Commits](https://github.com/evanw/esbuild/compare/v0.21.5...v0.27.2)

Updates `@vitest/coverage-v8` from 2.1.9 to 4.0.16
- [Release notes](https://github.com/vitest-dev/vitest/releases)
- [Commits](https://github.com/vitest-dev/vitest/commits/v4.0.16/packages/coverage-v8)

Updates `vitest` from 2.1.9 to 4.0.16
- [Release notes](https://github.com/vitest-dev/vitest/releases)
- [Commits](https://github.com/vitest-dev/vitest/commits/v4.0.16/packages/vitest)

---
updated-dependencies:
- dependency-name: esbuild
  dependency-version: 0.27.2
  dependency-type: indirect
- dependency-name: "@vitest/coverage-v8"
  dependency-version: 4.0.16
  dependency-type: direct:development
- dependency-name: vitest
  dependency-version: 4.0.16
  dependency-type: direct:development
...

Signed-off-by: dependabot[bot] <support@github.com>

* docs: add GitHub issue templates

- Bug report template with deployment type, browser info, logs
- Feature request template with affected area, priority
- Config with link to discussions and README
- Optimize test.yml to skip tests for non-code changes

* Initial plan

* Remove database schema duplication by creating shared schema-sql.ts module

Co-authored-by: DanielVolz <3275994+DanielVolz@users.noreply.github.com>

* Refactor frontend date formatting to eliminate duplication

Co-authored-by: DanielVolz <3275994+DanielVolz@users.noreply.github.com>

* docs: Add branch protection warning and PR workflow to instructions

* ci: remove paths filter from test workflow to fix branch protection

* fix: add .js extension to schema-sql imports for ESM compatibility (#15)

* feat: add setting to skip reminders for taken doses

- Add skipRemindersForTakenDoses setting to database schema
- Extend settings API to save and load new setting
- Update intake reminder scheduler to filter taken doses
- Add frontend toggle in settings with i18n (EN/DE)
- Only check doses from today (timezone-aware)
- Update all test schemas with new field
- All 405 tests passing

* feat: add repeat reminders for missed doses

- Add repeatRemindersEnabled and reminderRepeatIntervalMinutes settings
- Refactor intake reminder state from array to object with sendCount tracking
- Update scheduler to send repeated reminders at configurable intervals
- Only remind for today's doses (timezone-aware filtering)
- Add frontend toggle and interval input (5-480 minutes) in settings
- Maintain backward compatibility for old state file format
- Update all test schemas and assertions
- All 406 tests passing

* feat: add nagging reminders with max limit and ENV defaults

- Add maxNaggingReminders setting to limit repeat reminders (1-20)
- Add ENV defaults for all user settings (DEFAULT_*)
- Add ALTER TABLE migrations for backward compatibility
- Add smtpConfigured/shoutrrrConfigured to health endpoint
- Fix Push toggle to allow enabling without existing URL
- Disable skip/repeat toggles when no notifications enabled
- Add Pocket ID button to registration page
- Add getTodaysIntakes() for repeat reminder logic
- Update translations (en/de) for new settings
- Add comprehensive tests for new features

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: DanielVolz <3275994+DanielVolz@users.noreply.github.com>
2026-01-10 21:05:44 +01:00
2025-12-19 13:09:53 +01:00
2025-12-19 13:09:53 +01:00
2025-12-19 13:09:53 +01:00

MedAssist-ng Logo

MedAssist-ng

Never run out of your medications again.
A medication tracking and planning app with stock monitoring, intake schedules, and reminder notifications.

React 18 TypeScript Fastify SQLite Docker

🤖 AI-Generated Code

This app was 100% coded with Claude Opus 4.5. Use at your own risk.

⚠️ Disclaimer

Your health is your responsibility. This app may contain bugs. Follow your doctor's instructions closely, keep track of your medication supply, and plan ahead for reordering.

Think of this app as a helpful tool, but make all health decisions independently!

Features

MedAssist-ng Dashboard

Smart Inventory

  • Track exact stock: packs, blisters, and loose pills
  • Display remaining days of supply
  • Automatic calculation based on intake schedule

Flexible Schedules

  • Daily, weekly, or custom intervals per medication
  • Independent schedules for each medication

Stock Alerts & Reminders

  • Notifications before stock runs out
  • Configurable warning thresholds
  • Intake reminders via push notifications

Trip Planner

  • Calculate how many pills you need for a trip or date range
  • Plan ahead for vacations, business trips, or hospital stays

Multi-Person Support

  • Manage medications for multiple people
  • Share schedules via link. Recipients can mark doses as taken, you see it live

Notifications

  • Email via SMTP
  • Push notifications via ntfy, Gotify, Telegram, Discord (Shoutrrr)
  • Supports both stock warnings and intake reminders

Privacy & Security

  • Fully self-hosted
  • SSO via OIDC (Authelia, Authentik, Pocket ID, Keycloak)
  • Non-root containers
  • Dark mode included 😎

Getting Started

The easiest way to deploy MedAssist-ng is with Docker Compose:

git clone https://github.com/DanielVolz/medassist-ng.git
cd medassist-ng
cp .env.example .env
docker compose up -d

Open http://localhost:4174 and start tracking your medications.

Configuration

All configuration is done via environment variables in .env. Copy .env.example to get started.

General

Variable Default Description
PUID 1000 User ID for container file permissions
PGID 1000 Group ID for container file permissions
PORT 3000 Backend API port
CORS_ORIGINS http://localhost:4174 Allowed origins for CORS
LOG_LEVEL info Log verbosity (debug, info, warn, error)
TZ Europe/Berlin Timezone for scheduled reminders

Authentication

Variable Default Description
AUTH_ENABLED false Enable user authentication
REGISTRATION_ENABLED false Allow new user registrations
JWT_SECRET Access token signing key (required if auth enabled)
REFRESH_SECRET Refresh token signing key (required if auth enabled)
COOKIE_SECRET Cookie signing key (required if auth enabled)
ACCESS_TOKEN_TTL_MINUTES 15 Access token lifetime
REFRESH_TOKEN_TTL_DAYS 7 Refresh token lifetime

Generate secrets with: openssl rand -hex 32

OIDC / SSO

Variable Default Description
OIDC_ENABLED false Enable OIDC authentication
OIDC_ISSUER_URL OIDC provider URL
OIDC_CLIENT_ID Client ID from OIDC provider
OIDC_CLIENT_SECRET Client secret from OIDC provider
OIDC_REDIRECT_URI Callback URL
OIDC_SCOPES openid profile email Scopes to request
OIDC_USERNAME_CLAIM preferred_username Claim for username
OIDC_AUTO_CREATE_USERS true Auto-create users on first SSO login
OIDC_PROVIDER_NAME SSO Name shown on login button

Email (SMTP)

Variable Default Description
SMTP_HOST SMTP server hostname
SMTP_PORT 587 SMTP server port
SMTP_USER SMTP username
SMTP_PASS SMTP password
SMTP_TOKEN OAuth2/App token (takes precedence over password)
SMTP_FROM Sender email address
SMTP_SECURE false Use TLS

Reminders

Variable Default Description
REMINDER_DAYS_BEFORE 7 Days before stock runs out to send reminder
REMINDER_HOUR 6 Hour to send daily reminders (24h format)
REMINDER_MINUTES_BEFORE 15 Minutes before intake to send reminder
EXPIRY_WARNING_DAYS 30 Days before expiry to show warning

Development

docker compose -f docker-compose.dev.yml up
  • Frontend: http://localhost:5173 (hot reload)
  • Backend: http://localhost:3000

Acknowledgements

This project was inspired by MedAssist by njic.

Languages
TypeScript 94.4%
CSS 5.4%
Dockerfile 0.1%