diff --git a/.env.example b/.env.example index b71a721..5edcc1c 100644 --- a/.env.example +++ b/.env.example @@ -11,42 +11,31 @@ PGID=1000 PORT=3000 CORS_ORIGINS=http://localhost:4174 -LOG_LEVEL=warn + +# Server default timezone for scheduled reminders. +# Users can override this in Settings -> Timezone. +TZ=Europe/Berlin # Public base URL used for notification action links. -# Required for intake reminder action buttons/links. +# Required for intake reminder action buttons. # PUBLIC_APP_URL=https://medassist.example.com -# For mobile testing on the same LAN, use your laptop IP instead of localhost, -# e.g. PUBLIC_APP_URL=http://192.168.0.113:5173 and add that origin to CORS_ORIGINS. +# If this uses a non-local host, include that origin in CORS_ORIGINS. -# Levels: debug, info, warn, error, silent -# Controls: backend Fastify logging, frontend nginx access logs (Docker), -# and frontend browser console (via build-time injection) -# -# Behavior per level: -# debug — all app logs + all HTTP request logs (including polling endpoints) -# info — all app logs + HTTP request logs, EXCEPT high-frequency polling -# (GET /doses/taken, GET /share/:token/doses, GET /health are hidden) -# warn — only warnings and errors -# error — only errors -# silent — no logs +# Log level: debug, info, warn, error, silent +LOG_LEVEL=info # Rate limit: max requests per minute per IP (default: 100) # Increase for development/testing environments # RATE_LIMIT_MAX=100 # API documentation UI + OpenAPI JSON -# Default behavior: enabled outside production, disabled in production -# When enabled, docs are available on /docs and /docs/json. +# Docs are served on /docs and /docs/json. +# Default behavior: enabled outside production, disabled in production. # Recommended: -# development/staging: OPENAPI_DOCS_ENABLED=true -# production: leave unset, or set OPENAPI_DOCS_ENABLED=false +# development, staging: OPENAPI_DOCS_ENABLED=true +# production: leave unset or set OPENAPI_DOCS_ENABLED=false # OPENAPI_DOCS_ENABLED=true -# Server default timezone for scheduled reminders (e.g., Europe/Berlin, America/New_York). -# Users can override this per account in Settings -> Timezone. -TZ=Europe/Berlin - # ============================================================================= # Authentication (optional - disabled by default for easy setup) # ============================================================================= @@ -131,7 +120,7 @@ EXPIRY_WARNING_DAYS=30 # Days before expiry to show yellow warning # DEFAULT_EMAIL_INTAKE_REMINDERS=true # DEFAULT_EMAIL_PRESCRIPTION_REMINDERS=true -# Push notifications (ntfy/gotify via Shoutrrr) +# Push notifications (Shoutrrr URL) # DEFAULT_SHOUTRRR_ENABLED=false # DEFAULT_SHOUTRRR_URL= # DEFAULT_SHOUTRRR_STOCK_REMINDERS=true @@ -155,6 +144,6 @@ EXPIRY_WARNING_DAYS=30 # Days before expiry to show yellow warning # UI defaults # DEFAULT_LANGUAGE=en # en or de # DEFAULT_STOCK_CALCULATION_MODE=automatic # automatic or manual -# DEFAULT_SHARE_MEDICATION_OVERVIEW=false # Show medication overview section on shared schedule links +# DEFAULT_SHARE_MEDICATION_OVERVIEW=false # Show medication overview on shared schedule links # DEFAULT_UPCOMING_TODAY_ONLY=false -# DEFAULT_SHARE_SCHEDULE_TODAY_ONLY=false \ No newline at end of file +# DEFAULT_SHARE_SCHEDULE_TODAY_ONLY=false diff --git a/README.md b/README.md index bba0161..ea69b80 100644 --- a/README.md +++ b/README.md @@ -120,10 +120,10 @@ Share your medication schedule with others via a public link. ### Medication Setup -- Optional multi-source lookup inside the medication editor on desktop and mobile, prioritizing `RxNorm` and `openFDA` before `EMA`, including package-size suggestions when the source exposes them -- Explicit review-and-apply flow with low-risk suggestions only -- Additional lookup results can be revealed on demand instead of being hard-cut at the initial small result set -- Honest incomplete-coverage messaging with source labels; manual entry always remains available +- Optional medication lookup in the editor on desktop and mobile +- Supports `RxNorm`, `openFDA`, and `EMA` with source labels +- Review-and-apply flow with package-size suggestions when available +- Manual entry remains available ### Smart Inventory - Track exact stock with package profiles (blister, bottle, tube, liquid container) @@ -148,7 +148,6 @@ Share your medication schedule with others via a public link. ### Trip Planner - Calculate medication demand for a trip or date range with package-aware units -- Plan ahead for vacations, business trips, or hospital stays - Send demand reports via email or push notification ### Reports @@ -168,7 +167,7 @@ Share your medication schedule with others via a public link. ### Notifications - Email via SMTP - Push notifications via ntfy, Pushover, Gotify, Telegram, Discord & more ([Shoutrrr](https://containrrr.dev/shoutrrr/)) -- Supports both stock warnings and intake reminders +- Supports stock warnings and intake reminders ### Privacy & Security - Fully self-hosted @@ -191,208 +190,33 @@ 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. +Configure the application with environment variables in `.env`. Keep the basic container settings in the README and use the dedicated docs for the full reference. -### General +### Initial Configuration | 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`, `silent`). At `info` (default), high-frequency polling endpoints are suppressed. Set `debug` to see all requests. | -| `OPENAPI_DOCS_ENABLED` | `auto` | Enables API docs in non-production by default. Set explicitly to `true`/`false` to override. | -| `TZ` | `Europe/Berlin` | Server default timezone for scheduled reminders (can be overridden per user in Settings) | +| `CORS_ORIGINS` | `http://localhost:4174` | Allowed frontend origins | +| `TZ` | `Europe/Berlin` | Default timezone for reminders | -Recommended values for API docs by environment: - -| Environment | Recommendation | -|-------------|----------------| -| Development | `OPENAPI_DOCS_ENABLED=true` | -| Staging/Test | `OPENAPI_DOCS_ENABLED=true` | -| Production | leave it unset, or set `OPENAPI_DOCS_ENABLED=false` | - -Notes: - -- If `OPENAPI_DOCS_ENABLED` is not set, docs are enabled outside production and disabled in production. -- If `OPENAPI_DOCS_ENABLED=true`, docs are available on `/docs` and `/docs/json`. -- If `OPENAPI_DOCS_ENABLED=false`, only the docs are disabled. The API still works normally. - -### Authentication +Optional but commonly needed: | 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 | +| `PUBLIC_APP_URL` | — | Public base URL for notification action links | -Generate secrets with: `openssl rand -hex 32` +Detailed configuration references: -### API Keys (Programmatic API Access) - -When `AUTH_ENABLED=true`, you can create personal API keys and call protected endpoints with: - -```bash -Authorization: Bearer ma_... -``` - -Available scopes: - -- `read`: read-only access (`GET`, `HEAD`, `OPTIONS`) -- `write`: read + write access - -Essential notes: - -- Create keys in the app when authentication is enabled. -- The token is shown only once after creation. -- Creating a new key automatically deactivates previously active keys for the same user. -- API keys are stored hashed in the database. - -Example usage: - -```bash -curl http://localhost:3000/settings \ - -H "Authorization: Bearer ma_..." -``` - -API reference: - -- Interactive docs: `/docs` -- OpenAPI JSON: `/docs/json` -- With the bundled frontend ingress, these paths work on the normal app URL as well, for example `http://localhost:4174/docs` when docs are enabled. -- Key management endpoints for authenticated users: - - `GET /auth/api-keys` - - `POST /auth/api-keys` - - `DELETE /auth/api-keys/:id` - -### 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` | — | Full callback URL (e.g., `https://your-domain.com/api/auth/oidc/callback`) | -| `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 | - -Intake reminder timing uses IANA timezones. The server uses `TZ` as default, and each user can set an override in Settings. If no user timezone is set, reminders continue using the server default. - -### Push Notifications (Shoutrrr) - -MedAssist uses [Shoutrrr](https://containrrr.dev/shoutrrr/) for push notifications, supporting many services with a single URL format. - -**Implemented URL schemes in MedAssist:** `ntfy://`, `discord://`, `pushover://`, `gotify://`, `telegram://`, plus direct `https://` webhooks. - -This covers common providers like ntfy, Discord, Pushover, Gotify, Telegram, Slack webhooks, and many others via webhook URLs. - -Configure push notifications in Settings → Push, or set defaults via environment variables: - -| Variable | Default | Description | -|----------|---------|-------------| -| `DEFAULT_SHOUTRRR_ENABLED` | `false` | Enable push notifications by default | -| `DEFAULT_SHOUTRRR_URL` | — | Shoutrrr URL (see examples below) | -| `DEFAULT_SHOUTRRR_STOCK_REMINDERS` | `true` | Send stock warnings via push | -| `DEFAULT_SHOUTRRR_INTAKE_REMINDERS` | `true` | Send intake reminders via push | - -### Default User Settings - -These defaults are applied when a new user is created. Once a user saves settings in the app, their values take precedence. - -Complete list and details: - -- [docs/DEFAULT_USER_SETTINGS.md](docs/DEFAULT_USER_SETTINGS.md) - -#### URL Examples - -**ntfy** (free, self-hostable): -``` -ntfy://ntfy.sh/your-topic -ntfy://user:password@your-server.com/topic -``` - -**Pushover** (free app for iOS/Android): -``` -pushover://shoutrrr:API_TOKEN@USER_KEY/ -``` -Get your keys at [pushover.net](https://pushover.net/): -- **User Key**: Shown on your dashboard (top right) -- **API Token**: Create an application → copy the API Token - -**Gotify** (self-hosted): -``` -gotify://your-server.com/TOKEN -gotify://your-server.com:443/path/to/gotify/TOKEN?priority=1 -``` - -**Discord**: -``` -discord://TOKEN@WEBHOOK_ID -``` - -**Telegram**: -``` -telegram://TOKEN@telegram?chats=CHAT_ID -telegram://TOKEN@telegram?chats=@your_channel,-1001234567890 -``` - -For all services and options, see the [Shoutrrr documentation](https://containrrr.dev/shoutrrr/v0.8/services/overview/). +- Full configuration reference: [docs/CONFIGURATION.md](docs/CONFIGURATION.md) +- Push notifications: [docs/PUSH_NOTIFICATIONS.md](docs/PUSH_NOTIFICATIONS.md) +- Default user settings: [docs/DEFAULT_USER_SETTINGS.md](docs/DEFAULT_USER_SETTINGS.md) # Development -```bash -docker compose -p medassist-dev -f docker-compose.dev.yml up -``` - -- Frontend: `http://localhost:5173` (hot reload) -- Backend: `http://localhost:3000` -- API docs UI: `http://localhost:3000/docs` (when docs are enabled) -- OpenAPI JSON: `http://localhost:3000/docs/json` (when docs are enabled) - -If you run the frontend dev server behind a reverse proxy or on a remote host, you can optionally set these frontend-only environment variables before starting Vite: - -- `VITE_ALLOWED_HOSTS`: comma-separated hostnames allowed to connect to the dev server; defaults to `localhost,127.0.0.1` -- `VITE_HMR_HOST`: public hostname used for HMR websocket connections -- `VITE_HMR_PROTOCOL`: optional websocket protocol override (`ws` or `wss`) -- `VITE_HMR_CLIENT_PORT`: optional public websocket port exposed to the browser -- `VITE_HMR_PORT`: optional server-side websocket port for the Vite process - -Useful local commands: - -```bash -npm run lint -cd backend && npm run test:run -cd frontend && npm run test:run -``` +Development setup and local commands are documented in [docs/DEVELOPMENT.md](docs/DEVELOPMENT.md). # Acknowledgements diff --git a/docs/CONFIGURATION.md b/docs/CONFIGURATION.md new file mode 100644 index 0000000..a92f85c --- /dev/null +++ b/docs/CONFIGURATION.md @@ -0,0 +1,113 @@ +# Configuration + +Configure MedAssist with environment variables in `.env`. Start from `.env.example`. + +## 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 | +| `TZ` | `Europe/Berlin` | Server default timezone for scheduled reminders | +| `PUBLIC_APP_URL` | — | Public base URL for notification action links | +| `LOG_LEVEL` | `info` | Log level: `debug`, `info`, `warn`, `error`, or `silent` | +| `RATE_LIMIT_MAX` | `100` | Maximum requests per minute per IP | +| `OPENAPI_DOCS_ENABLED` | `auto` | Explicitly enable or disable `/docs` and `/docs/json` | + +API docs behavior: + +- If `OPENAPI_DOCS_ENABLED` is unset, docs are enabled outside production and disabled in production. +- `OPENAPI_DOCS_ENABLED=true` enables `/docs` and `/docs/json`. +- `OPENAPI_DOCS_ENABLED=false` disables the docs only. + +## Authentication + +| Variable | Default | Description | +|----------|---------|-------------| +| `AUTH_ENABLED` | `false` | Enable user authentication | +| `REGISTRATION_ENABLED` | `false` | Allow new user registrations | +| `FORM_LOGIN_ENABLED` | `true` | Enable username/password login | +| `JWT_SECRET` | — | Access token signing key; required when auth is enabled | +| `REFRESH_SECRET` | — | Refresh token signing key; required when auth is enabled | +| `COOKIE_SECRET` | — | Cookie signing key; required when auth is enabled | +| `ACCESS_TOKEN_TTL_MINUTES` | `15` | Access token lifetime | +| `REFRESH_TOKEN_TTL_DAYS` | `7` | Refresh token lifetime | + +Generate secrets with `openssl rand -hex 32`. + +## API Keys + +When `AUTH_ENABLED=true`, authenticated users can create API keys and call protected endpoints with: + +```text +Authorization: Bearer ma_... +``` + +Available scopes: + +- `read`: read-only access (`GET`, `HEAD`, `OPTIONS`) +- `write`: read and write access + +Notes: + +- The token is shown only once after creation. +- Creating a new key deactivates previously active keys for the same user. +- API keys are stored hashed in the database. + +API reference: + +- Interactive docs: `/docs` +- OpenAPI JSON: `/docs/json` +- Key management endpoints: + - `GET /auth/api-keys` + - `POST /auth/api-keys` + - `DELETE /auth/api-keys/:id` + +## OIDC / SSO + +| Variable | Default | Description | +|----------|---------|-------------| +| `OIDC_ENABLED` | `false` | Enable OIDC authentication | +| `OIDC_ISSUER_URL` | — | OIDC provider URL | +| `OIDC_CLIENT_ID` | — | OIDC client ID | +| `OIDC_CLIENT_SECRET` | — | OIDC client secret | +| `OIDC_REDIRECT_URI` | — | OIDC callback URL | +| `OIDC_SCOPES` | `openid profile email` | Requested scopes | +| `OIDC_USERNAME_CLAIM` | `preferred_username` | Username claim | +| `OIDC_AUTO_CREATE_USERS` | `true` | Auto-create users on first SSO login | +| `OIDC_PROVIDER_NAME` | `SSO` | Login button label | + +## 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 or app token; takes precedence over `SMTP_PASS` | +| `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 warning | + +Reminder timing uses IANA timezones. `TZ` is the server default. Users can override it in Settings. + +## Push Notifications + +Push notification setup, provider support, and URL examples are documented in [PUSH_NOTIFICATIONS.md](PUSH_NOTIFICATIONS.md). + +Recommended provider: `ntfy`, especially for intake reminders with direct actions. + +## Default User Settings + +Default values for newly created users are documented in [DEFAULT_USER_SETTINGS.md](DEFAULT_USER_SETTINGS.md). diff --git a/docs/DEFAULT_USER_SETTINGS.md b/docs/DEFAULT_USER_SETTINGS.md index 1fe47db..328429d 100644 --- a/docs/DEFAULT_USER_SETTINGS.md +++ b/docs/DEFAULT_USER_SETTINGS.md @@ -6,6 +6,9 @@ Scope and behavior: - These values are applied only when a user's settings are created for the first time. - After that, values stored in the database are used and take precedence. +- This document only covers settings that have an environment-backed default. +- It is not intended to be a full inventory of every setting shown in the UI. +- UI-only settings without a `DEFAULT_*` variable, for example the dashboard section order toggle, are intentionally excluded. ## Email Defaults diff --git a/docs/DEVELOPMENT.md b/docs/DEVELOPMENT.md new file mode 100644 index 0000000..6b2f520 --- /dev/null +++ b/docs/DEVELOPMENT.md @@ -0,0 +1,35 @@ +# Development + +## Start the Development Stack + +```bash +docker compose -p medassist-dev -f docker-compose.dev.yml up +``` + +## Service Endpoints + +- Frontend: `http://localhost:5173` +- Backend: `http://localhost:3000` +- API docs UI: `http://localhost:3000/docs` when docs are enabled +- OpenAPI JSON: `http://localhost:3000/docs/json` when docs are enabled + +## Frontend Dev Server Behind a Proxy + +If the frontend dev server runs behind a reverse proxy or on a remote host, set these frontend-only environment variables before starting Vite: + +These development overrides are documented here intentionally and are not part of the standard operator-focused `.env.example` surface. + +- `BACKEND_URL`: backend target used by the Vite `/api` proxy; default `http://localhost:3000` outside Docker and `http://backend-dev:3000` in Docker +- `VITE_ALLOWED_HOSTS`: comma-separated hostnames allowed to connect to the dev server; default `localhost,127.0.0.1` +- `VITE_HMR_HOST`: public hostname for HMR websocket connections +- `VITE_HMR_PROTOCOL`: websocket protocol override (`ws` or `wss`) +- `VITE_HMR_CLIENT_PORT`: public websocket port exposed to the browser +- `VITE_HMR_PORT`: server-side websocket port for the Vite process + +## Useful Commands + +```bash +npm run lint +cd backend && npm run test:run +cd frontend && npm run test:run +``` \ No newline at end of file diff --git a/docs/PUSH_NOTIFICATIONS.md b/docs/PUSH_NOTIFICATIONS.md new file mode 100644 index 0000000..b9cddc9 --- /dev/null +++ b/docs/PUSH_NOTIFICATIONS.md @@ -0,0 +1,71 @@ +# Push Notifications + +MedAssist uses [Shoutrrr](https://containrrr.dev/shoutrrr/) for push notifications. + +## Recommendation + +Recommended provider: `ntfy`. + +Use `ntfy` when you want the best-supported MedAssist notification flow, especially for intake reminders with direct actions such as `Take`, `Skip`, and `View`. + +## Supported URL Schemes + +- `ntfy://` +- `discord://` +- `pushover://` +- `gotify://` +- `telegram://` +- direct `https://` webhooks + +## Configuration + +Configure push notifications in the app under `Settings -> Push`, or set defaults for new users with environment variables. + +Push-related default variables: + +| Variable | Default | Description | +|----------|---------|-------------| +| `DEFAULT_SHOUTRRR_ENABLED` | `false` | Enable push notifications by default | +| `DEFAULT_SHOUTRRR_URL` | — | Default Shoutrrr URL | +| `DEFAULT_SHOUTRRR_STOCK_REMINDERS` | `true` | Send stock reminders via push | +| `DEFAULT_SHOUTRRR_INTAKE_REMINDERS` | `true` | Send intake reminders via push | +| `DEFAULT_SHOUTRRR_PRESCRIPTION_REMINDERS` | `true` | Send prescription reminders via push | + +For the full default-user-settings reference, see [DEFAULT_USER_SETTINGS.md](DEFAULT_USER_SETTINGS.md). + +## URL Examples + +### ntfy + +```text +ntfy://ntfy.sh/your-topic +ntfy://user:password@your-server.com/topic +``` + +### Pushover + +```text +pushover://shoutrrr:API_TOKEN@USER_KEY/ +``` + +### Gotify + +```text +gotify://your-server.com/TOKEN +gotify://your-server.com:443/path/to/gotify/TOKEN?priority=1 +``` + +### Discord + +```text +discord://TOKEN@WEBHOOK_ID +``` + +### Telegram + +```text +telegram://TOKEN@telegram?chats=CHAT_ID +telegram://TOKEN@telegram?chats=@your_channel,-1001234567890 +``` + +For all supported services and options, see the [Shoutrrr documentation](https://containrrr.dev/shoutrrr/v0.8/services/overview/). \ No newline at end of file