Merge remote-tracking branch 'origin/release/v1.46.0' into fix/v146-sec-review

This commit is contained in:
Marc-André Bombeck committed 2026-10-25 18:19:39 +01:00
commit 722c6d1db5
15 files changed
+367 -245

No files matched your search

+2 -2
View File
@@ -60,7 +60,7 @@ jobs:
# minutes and can produce nothing actionable, because nobody edits
# it and the fix for anything found there is `pnpm prisma generate`.
- name: Initialise CodeQL
uses: github/codeql-action/init@6f2ac81f5cd81fe351a197ba0b00d91d03cd2aee # v4.39.0
uses: github/codeql-action/init@9b3254085494b55a0f7c4c2b3d2d52cc712ce370 # v4.39.1
with:
languages: javascript-typescript
build-mode: none
@@ -75,6 +75,6 @@ jobs:
- public
- name: Analyse
uses: github/codeql-action/analyze@6f2ac81f5cd81fe351a197ba0b00d91d03cd2aee # v4.39.0
uses: github/codeql-action/analyze@9b3254085494b55a0f7c4c2b3d2d52cc712ce370 # v4.39.1
with:
category: "/language:javascript-typescript"
+1 -1
View File
@@ -54,7 +54,7 @@ jobs:
publish_results: true
- name: Upload results
uses: github/codeql-action/upload-sarif@6f2ac81f5cd81fe351a197ba0b00d91d03cd2aee # v4.39.0
uses: github/codeql-action/upload-sarif@9b3254085494b55a0f7c4c2b3d2d52cc712ce370 # v4.39.1
with:
sarif_file: scorecard-results.sarif
category: scorecard
+2 -2
View File
@@ -88,7 +88,7 @@ jobs:
skip-dirs: src/generated
- name: Upload results
uses: github/codeql-action/upload-sarif@6f2ac81f5cd81fe351a197ba0b00d91d03cd2aee # v4.39.0
uses: github/codeql-action/upload-sarif@9b3254085494b55a0f7c4c2b3d2d52cc712ce370 # v4.39.1
with:
sarif_file: trivy-filesystem.sarif
category: trivy-filesystem
@@ -145,7 +145,7 @@ jobs:
exit-code: "0"
- name: Upload results
uses: github/codeql-action/upload-sarif@6f2ac81f5cd81fe351a197ba0b00d91d03cd2aee # v4.39.0
uses: github/codeql-action/upload-sarif@9b3254085494b55a0f7c4c2b3d2d52cc712ce370 # v4.39.1
with:
sarif_file: trivy-published-image.sarif
category: trivy-published-image
+1 -1
View File
@@ -87,7 +87,7 @@ curl -s http://localhost:3000/api/version # returns version + buildSha + built
`SESSION_COOKIE_SECURE=false` is the one setting plain-HTTP self-hosts must set: without it the session cookie carries the `Secure` flag, the browser drops it over HTTP, and login silently fails. Leave it unset (or `true`) only when a TLS reverse proxy serves HTTPS — and then set `NEXT_PUBLIC_APP_URL` and `APP_URL` to the public URL first (see the [self-hosting guide](https://docs.healthlog.dev/getting-started/self-host-in-10-minutes/)).
Persistent state lives in exactly one named volume (`postgres-data`, the Postgres data); the app container is stateless, so backing up the database backs up everything. An install from v1.45 or earlier moves its Postgres 16 database into it on the first start ([details](docs/ops/postgres-major-upgrade.md)).
Persistent state lives in the database volume (`postgres-data`) and in `backups`, which holds the nightly database dumps; the app container is stateless. An install from v1.45 or earlier moves its Postgres 16 database from `pgdata` into the new volume on the first start ([details](docs/ops/postgres-major-upgrade.md)); recovering a lost server is in [disaster-recovery.md](docs/ops/disaster-recovery.md).
NAS and homelab users have ready-made paths: [Unraid Community Applications](docs/self-hosting/unraid.md), [Portainer app templates](docs/self-hosting/portainer/README.md) and [TrueNAS](docs/self-hosting/truenas.md) (the Community train app is pending catalog review).
+33 -4
View File
@@ -695,6 +695,10 @@ services:
#
# On every start it decides in a second and exits:
# - no `pgdata` data (a new install): nothing to do;
# - the database service runs Postgres 16 (after going back): nothing to
# do, the live 16 volume is left alone;
# - 16 was used again after an earlier move: refuses, so the app does not
# start on the move-day copy;
# - Postgres 18 already holds the database: nothing to do;
# - otherwise it starts Postgres 16 inside this container on the old
# volume, streams a dump into the new database in one transaction,
@@ -752,14 +756,35 @@ services:
log "no Postgres 16 database to move, nothing to do"
exit 0
fi
# The database service must be the new major. After going back to 16 the
# service runs on this very volume: touching it then would wipe the live data.
i=0
until NEWVER=$$(new "SHOW server_version_num" 2>/dev/null); do
i=$$((i + 1))
[ "$$i" -lt 60 ] || { log "the database service did not answer for a minute"; exit 1; }
sleep 1
done
if [ "$$NEWVER" -lt 180000 ]; then
log "the database service runs Postgres $$((NEWVER / 10000)), not 18: nothing to move, the old volume is left alone"
exit 0
fi
checkpoint() { pg_controldata "$$OLD" | awk -F': *' '/^Latest checkpoint location/ { print $$2 }'; }
USER_TABLES="FROM pg_tables WHERE schemaname NOT IN ('pg_catalog', 'information_schema')"
TABLES=$$(new "SELECT count(*) $$USER_TABLES")
if [ -e "$$MARK" ]; then
MOVED_ON=$$(sed -n 1p "$$MARK")
MOVED_CHECKPOINT=$$(sed -n 2p "$$MARK")
if [ -n "$$MOVED_CHECKPOINT" ] && [ "$$(checkpoint)" != "$$MOVED_CHECKPOINT" ]; then
log "the Postgres 16 database changed after it was moved on $$MOVED_ON: it was used again after going back to 16."
log "Postgres 18 still holds the state of that day, so it is not started on. To move the current 16 database,"
log "see docs/ops/postgres-major-upgrade.md, \"Updating again after going back\"."
exit 1
fi
if [ "$$TABLES" != 0 ]; then
log "the Postgres 16 database was moved on $$(cat "$$MARK"), nothing to do"
log "the Postgres 16 database was moved on $$MOVED_ON, nothing to do"
exit 0
fi
log "the Postgres 16 database was moved on $$(cat "$$MARK"), but the Postgres 18 database is empty now."
log "the Postgres 16 database was moved on $$MOVED_ON, but the Postgres 18 database is empty now."
log "Not importing the old state again by itself. See docs/ops/postgres-major-upgrade.md, \"Starting over\"."
exit 1
fi
@@ -778,8 +803,9 @@ services:
fi
NEED=$$(du -sk "$$OLD" | cut -f1)
FREE=$$(df -Pk /var/lib/postgresql/next | awk 'NR == 2 { print $$4 }')
if [ "$$FREE" -le "$$NEED" ]; then
log "the move needs about $$((NEED / 1024)) MiB free for the new database, the disk has $$((FREE / 1024)) MiB."
# Room for the copy, and for the dump the app takes before its first migration.
if [ "$$FREE" -le $$((NEED * 3 / 2)) ]; then
log "the move needs about $$((NEED * 3 / 2 / 1024)) MiB free for the new database and its first dump, the disk has $$((FREE / 1024)) MiB."
log "Nothing was changed. Free some space and start the stack again."
exit 1
fi
@@ -813,6 +839,9 @@ services:
vacuumdb -h db -U healthlog -d healthlog --analyze-in-stages --quiet
kill -INT "$$PID"
wait "$$PID" || true
# The marker keeps the old database's last checkpoint: a later start can tell
# whether 16 was used again after the move.
{ date -u +%Y-%m-%dT%H:%M:%SZ; checkpoint; } > "$$MOVING"
mv "$$MOVING" "$$MARK"
log "done. The old volume stays as it was, as a way back; see docs/ops/postgres-major-upgrade.md"
+30 -3
View File
@@ -17,7 +17,9 @@ Postgres 18 database is up and before the app, and it exits in a second when
there is nothing to move. When the old `pgdata` volume holds a Postgres 16
database and the new one is empty, it:
1. checks there is room on the disk for a second copy;
1. checks that the database service runs Postgres 18, and that there is room
on the disk for a second copy and for the dump the app takes before its
first migration;
2. starts Postgres 16 inside its own container on the old volume, reachable
only from inside that container;
3. streams a dump into Postgres 18 in a single transaction;
@@ -34,8 +36,10 @@ If any step fails, the new database stays empty, the app does not start, and
the log says what happened. A move cut short by a restart or a full disk is
run again from scratch on the next `docker compose up -d`.
The old `pgdata` volume is read and never changed, apart from the marker
file. That is the way back.
The old `pgdata` volume keeps the database as it was. Postgres 16 runs on it
during the move, so it writes a checkpoint there (and recovers first after an
unclean stop), and the marker file is added; no data in it changes. That is
the way back.
## Going back
@@ -55,6 +59,29 @@ HEALTHLOG_IMAGE_REF=:v1.45.0 docker compose up -d
```
Anything written after the move lives only in the Postgres 18 volume.
`db-upgrade` can stay in the file: it sees that the database service runs
Postgres 16 and leaves that volume alone. This also holds after a move that
stopped half-way.
## Updating again after going back
If you used Postgres 16 again after going back, the Postgres 18 volume holds
the state of the day of the move, not what you wrote since. `db-upgrade`
notices that the old database changed after the move and refuses to start on
the old copy. To move the current 16 database, remove the old copy and the
marker, then start again:
```bash
docker compose down
docker volume rm <project>_postgres-data
docker compose run --rm --entrypoint rm db-upgrade \
/var/lib/postgresql/data/HEALTHLOG_MOVED_TO_POSTGRES_18
docker compose up -d
```
`docker volume ls` shows the volume's full name. Removing it deletes what was
written on Postgres 18 before you went back; take a dump of it first if any
of that matters.
## Afterwards
+3 -1
View File
@@ -434,7 +434,9 @@ Keep the dump until the new version has run for a few days, and keep your
encryption key with it: a dump without the key cannot be read.
The database image is a separate matter: a new Postgres minor is a change of
the `image:` line, a new major (16 to 18) is a dump and restore, described in
the `image:` line. The move from 16 to 18 runs by itself on the first start
of the compose file from v1.46; a database you run yourself moves by dump
and restore. Both are in
[`docs/ops/postgres-major-upgrade.md`](../ops/postgres-major-upgrade.md).
The start log says what is about to happen before the migrations run:
+2 -2
View File
@@ -5,7 +5,7 @@
"type": 3,
"title": "HealthLog",
"name": "healthlog",
"description": "Self-hosted personal health tracker (PWA + native iOS client): weight, blood pressure, pulse, blood glucose, body composition, sleep, mood, menstrual cycle, and medications on one encrypted timeline. AES-256-GCM at rest, no vendor cloud, no telemetry. Ships app + PostgreSQL 16 as one stack.",
"description": "Self-hosted personal health tracker (PWA + native iOS client): weight, blood pressure, pulse, blood glucose, body composition, sleep, mood, menstrual cycle, and medications on one encrypted timeline. AES-256-GCM at rest, no vendor cloud, no telemetry. Ships app + PostgreSQL 18 as one stack.",
"note": "Generate each secret with: openssl rand -hex 32 (a 64-char hex string). For plain-HTTP / LAN access set SESSION_COOKIE_SECURE to false, or login fails. Set NEXT_PUBLIC_APP_URL and APP_URL to how you reach the app. Full guide: https://github.com/MBombeck/HealthLog/blob/main/docs/self-hosting/getting-started.md",
"categories": ["Tools", "Other"],
"platform": "linux",
@@ -33,7 +33,7 @@
{
"name": "HEALTHLOG_PLATFORM",
"label": "Platform hint",
"description": "Opens the admin's encryption key backup step on the Portainer instructions.",
"description": "Opens the admin's recovery kit on the Portainer instructions.",
"default": "portainer"
},
{
+22 -10
View File
@@ -95,19 +95,31 @@ different things depending on which button you press.
two actions below, the row stays. A start that was folded into a later
cycle stays for as long as that cycle does, because deleting the later
cycle gives it back.
- **Settings → Advanced → Delete all data** removes the rows outright, tombstones
- **Settings → Advanced → Delete all data** removes the rows, tombstones
included. Everything the account owns goes except what you sign in
with; the confirmation dialog lists it.
- **Deleting the account** removes the account row, and the database
cascades every record with it.
with; the confirmation dialog lists it. Before it does, it keeps a copy
of the account on the server for the deletion grace window (14 days by
default), so a mistaken wipe can be restored; the copy is erased when
the window ends.
- **Deleting the account** locks it at once and erases it, with every
record, when the grace window ends. Until then an admin can undo it.
Two consequences follow, and they matter for the seizure threat model
`ACCOUNT_DELETION_GRACE_DAYS=0` makes both immediate again, without the
copy and without an undo.
Three consequences follow, and they matter for the seizure threat model
above. A host imaged while a per-entry deletion is the only deletion you
have performed still holds that entry. And a backup taken before any of
these actions still holds whatever it captured, because a backup is a
copy of a moment. If your reason for deleting is that the data must not
be recoverable from the machine, use one of the two whole-record actions
and rotate your backups.
have performed still holds that entry; so does a host imaged inside the
grace window. A backup taken before any of these actions still holds
whatever it captured, because a backup is a copy of a moment: the nightly
database dumps keep a deleted record until they age out (`DB_BACKUP_KEEP`
nights on the server, and your bucket's lifecycle rule for `database/`).
And after the move to Postgres 18 the old `pgdata` volume is a full copy
of the database from that day until you remove it. If your reason for
deleting is that the data must not be recoverable from the machine, set
the grace window to 0 or wait it out, remove the old volume, and let the
dumps age out or delete them
([backup-restore.md](../ops/backup-restore.md)).
## Practical hardening
+2 -2
View File
@@ -46,14 +46,14 @@
<Config Name="WebUI Port" Target="3000" Default="3000" Mode="tcp" Description="HTTP port HealthLog listens on." Type="Port" Display="always" Required="true" Mask="false">3000</Config>
<Config Name="DATABASE_URL" Target="DATABASE_URL" Default="postgresql://healthlog:CHANGE-ME@POSTGRES-HOST:5432/healthlog?schema=public" Mode="" Description="Connection string for your separately-installed PostgreSQL 16. Replace CHANGE-ME with the DB password and POSTGRES-HOST with the Postgres container IP or name." Type="Variable" Display="always" Required="true" Mask="true"/>
<Config Name="DATABASE_URL" Target="DATABASE_URL" Default="postgresql://healthlog:CHANGE-ME@POSTGRES-HOST:5432/healthlog?schema=public" Mode="" Description="Connection string for your separately-installed PostgreSQL (18, or 16). Replace CHANGE-ME with the DB password and POSTGRES-HOST with the Postgres container IP or name." Type="Variable" Display="always" Required="true" Mask="true"/>
<Config Name="ENCRYPTION_KEY" Target="ENCRYPTION_KEY" Default="" Mode="" Description="AES-256-GCM key for data at rest. Generate with: openssl rand -hex 32 (64-char hex). Back this up — losing it makes encrypted data unrecoverable." Type="Variable" Display="always" Required="true" Mask="true"/>
<Config Name="API_TOKEN_HMAC_KEY" Target="API_TOKEN_HMAC_KEY" Default="" Mode="" Description="HMAC key for API token hashing. Generate with: openssl rand -hex 32 (64-char hex)." Type="Variable" Display="always" Required="true" Mask="true"/>
<Config Name="SESSION_COOKIE_SECURE" Target="SESSION_COOKIE_SECURE" Default="false" Mode="" Description="Set to false for LAN / plain-HTTP installs (http://tower:3000) or login fails. Set to true only behind an HTTPS reverse proxy." Type="Variable" Display="always" Required="true" Mask="false">false</Config>
<Config Name="HEALTHLOG_PLATFORM" Target="HEALTHLOG_PLATFORM" Default="unraid" Mode="" Description="Where this server is configured. Opens the admin's encryption key backup step on the Unraid instructions." Type="Variable" Display="advanced" Required="false" Mask="false">unraid</Config>
<Config Name="HEALTHLOG_PLATFORM" Target="HEALTHLOG_PLATFORM" Default="unraid" Mode="" Description="Where this server is configured. Opens the admin's recovery kit on the Unraid instructions." Type="Variable" Display="advanced" Required="false" Mask="false">unraid</Config>
<Config Name="ENCRYPTION_KEY_CHECK" Target="ENCRYPTION_KEY_CHECK" Default="enforce" Mode="" Description="Start-up encryption key check. enforce refuses to serve when the key does not open the stored data. warn only logs it: a last resort for a refusal you are certain is false, and it disables a safety check." Type="Variable" Display="advanced" Required="false" Mask="false">enforce</Config>
<Config Name="NEXT_PUBLIC_APP_URL" Target="NEXT_PUBLIC_APP_URL" Default="http://tower:3000" Mode="" Description="Public URL users reach HealthLog at. Use the LAN URL (e.g. http://tower:3000) or your HTTPS domain behind a reverse proxy." Type="Variable" Display="always" Required="true" Mask="false"/>
+7 -7
View File
@@ -54,13 +54,13 @@
"mcp:stdio": "npx tsx src/cli/mcp-stdio.ts"
},
"dependencies": {
"@aws-sdk/client-s3": "^3.1153.0",
"@aws-sdk/lib-storage": "3.1153.0",
"@aws-sdk/client-s3": "^3.1155.0",
"@aws-sdk/lib-storage": "3.1155.0",
"@dnd-kit/core": "^6.3.1",
"@dnd-kit/sortable": "^10.0.0",
"@dnd-kit/utilities": "^3.2.2",
"@modelcontextprotocol/sdk": "^1.32.1",
"@napi-rs/canvas": "1.0.11",
"@napi-rs/canvas": "1.0.13",
"@node-rs/argon2": "^2.2.2",
"@parse/node-apn": "^8.1.0",
"@prisma/adapter-pg": "^7.10.1",
@@ -68,20 +68,20 @@
"@simplewebauthn/browser": "^14.0.0",
"@simplewebauthn/server": "^14.0.3",
"@tanstack/react-query": "5.104.5",
"@tanstack/react-virtual": "^3.14.21",
"@tanstack/react-virtual": "^3.14.22",
"@types/sax": "^1.2.7",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"date-fns": "^4.4.0",
"dotenv": "^18.0.8",
"fflate": "^0.8.3",
"jose": "^6.2.12",
"jose": "^6.2.13",
"jspdf": "^4.2.1",
"jspdf-autotable": "^5.0.9",
"lucide-react": "^1.59.0",
"lucide-react": "^1.61.0",
"mmdb-lib": "^3.0.3",
"next": "16.4.2",
"nodemailer": "10.1.7",
"nodemailer": "10.2.1",
"otpauth": "^9.5.2",
"p-limit": "^7.3.3",
"pg": "^8.23.1",
+205 -205
View File
@@ -45,11 +45,11 @@ importers:
.:
dependencies:
'@aws-sdk/client-s3':
specifier: ^3.1153.0
version: 3.1153.0
specifier: ^3.1155.0
version: 3.1155.0
'@aws-sdk/lib-storage':
specifier: 3.1153.0
version: 3.1153.0(@aws-sdk/client-s3@3.1153.0)
specifier: 3.1155.0
version: 3.1155.0(@aws-sdk/client-s3@3.1155.0)
'@dnd-kit/core':
specifier: ^6.3.1
version: 6.3.1(react-dom@19.3.1(react@19.3.1))(react@19.3.1)
@@ -63,8 +63,8 @@ importers:
specifier: ^1.32.1
version: 1.32.1(@cfworker/json-schema@4.1.1)(supports-color@7.2.0)(zod@4.6.7)
'@napi-rs/canvas':
specifier: 1.0.11
version: 1.0.11
specifier: 1.0.13
version: 1.0.13
'@node-rs/argon2':
specifier: ^2.2.2
version: 2.2.2
@@ -87,8 +87,8 @@ importers:
specifier: 5.104.5
version: 5.104.5(@types/react@19.3.0)(react@19.3.1)
'@tanstack/react-virtual':
specifier: ^3.14.21
version: 3.14.21(react-dom@19.3.1(react@19.3.1))(react@19.3.1)
specifier: ^3.14.22
version: 3.14.22(react-dom@19.3.1(react@19.3.1))(react@19.3.1)
'@types/sax':
specifier: ^1.2.7
version: 1.2.7
@@ -108,8 +108,8 @@ importers:
specifier: ^0.8.3
version: 0.8.3
jose:
specifier: ^6.2.12
version: 6.2.12
specifier: ^6.2.13
version: 6.2.13
jspdf:
specifier: ^4.2.1
version: 4.2.1
@@ -117,8 +117,8 @@ importers:
specifier: ^5.0.9
version: 5.0.9(jspdf@4.2.1)
lucide-react:
specifier: ^1.59.0
version: 1.59.0(@types/react@19.3.0)(react@19.3.1)
specifier: ^1.61.0
version: 1.61.0(@types/react@19.3.0)(react@19.3.1)
mmdb-lib:
specifier: ^3.0.3
version: 3.0.3
@@ -126,8 +126,8 @@ importers:
specifier: 16.4.2
version: 16.4.2(@babel/core@7.29.7(supports-color@7.2.0))(@opentelemetry/api@1.9.1)(@playwright/test@1.64.1)(@types/node@26.6.5)(react-dom@19.3.1(react@19.3.1))(react@19.3.1)
nodemailer:
specifier: 10.1.7
version: 10.1.7
specifier: 10.2.1
version: 10.2.1
otpauth:
specifier: ^9.5.2
version: 9.5.2
@@ -301,74 +301,74 @@ packages:
resolution: {integrity: sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==}
engines: {node: '>=10'}
'@aws-sdk/checksums@3.1001.4':
resolution: {integrity: sha512-H8BiG/2DnfwydVxD1uDPLa1s1sfhRnVTbD2OwzfTr3WlJzgbIzICDXxBTlTMjbWNNCe4VS9X55fco+JI34L9kw==}
'@aws-sdk/checksums@3.1001.5':
resolution: {integrity: sha512-6erto9nK2OZO/woEgmFIaPh5gChgdNd7nL6jgAf8TAXz3/QHC8Pan2TFe9GYp3IxOIKCCGWPylSjtOHKfNzQlw==}
engines: {node: '>=20.0.0'}
'@aws-sdk/client-s3@3.1153.0':
resolution: {integrity: sha512-h0VScQZPfBApg1Qi4AedsOUSMZ7m758kZIg0JO0B5HtwSzQhB7Tgh6h1JoGEXELgrWADPusYMpMBDmrRAEx2yw==}
'@aws-sdk/client-s3@3.1155.0':
resolution: {integrity: sha512-l3uMeIlzWVgj/RTJTh5sKYKtQz1la3cvWLUPn1cwFH4UZpcqTH66SgJHCeB2FOE9bEAjOdbmCyPKt9aw867Fng==}
engines: {node: '>=20.0.0'}
'@aws-sdk/core@3.978.4':
resolution: {integrity: sha512-JvGp6y9sPX8FSs2K/EmFMZcUfdkHxqGE45+oxwGC75OIh50j2e4cbx7zcR3BwNs2DSGqJdBja5h6Vy89iIDmRQ==}
'@aws-sdk/core@3.978.5':
resolution: {integrity: sha512-ruI0AWqcrfL20XDS6SwZIJFPOBZ6N0ADa9nLzWlBhxk1aoUjbUX3JkTR9vEmk7o35XpAHi68ahZ4T6RE2U9XyA==}
engines: {node: '>=20.0.0'}
'@aws-sdk/credential-provider-env@3.972.75':
resolution: {integrity: sha512-nc8Epg98PcYoKNx0VFlwtmZSCP+tuJ1FLWqpslET/gp4h/WYuULSJqtxppM6ywKKDdzNJPvLD7ceRfmxm3Qdxw==}
'@aws-sdk/credential-provider-env@3.972.76':
resolution: {integrity: sha512-YxmL1Ar7bV9ZkJvtv+lj+MF6le76NnZnbmgySyZuxpZaCv0/1lZ3KxKy6RIj1h9ui2hWnVjZxbmTMeO0y/NrQA==}
engines: {node: '>=20.0.0'}
'@aws-sdk/credential-provider-http@3.972.77':
resolution: {integrity: sha512-KkYFdIQwuw9IOHfEzK78slxmVWuhcm1w6YgE3V3WVxAeRRj9q5J9jlwzpY/oszWxd4SD96FTFkDdRGBdMFF2Mw==}
'@aws-sdk/credential-provider-http@3.972.78':
resolution: {integrity: sha512-3VHknDRngoebsaqyT1iR75b5cBT2zfEtODK6yYmYltljkK1HEAQLs2FEd1IpWqAeOYdMGaG9wikngWLTufY7RA==}
engines: {node: '>=20.0.0'}
'@aws-sdk/credential-provider-ini@3.973.20':
resolution: {integrity: sha512-OSl2R/0zLkoeKEIy6oP2tu/bsSdY+DGgro2eOffMD2CcP8POv7rS14FY5Jzc5bjnqB1G9wv/aM1akHC2mdlhZw==}
'@aws-sdk/credential-provider-ini@3.973.21':
resolution: {integrity: sha512-aLx9hbCX6mGAyv4ZZd8UagJ4RASHVJw6BFDSIyuKmLkqNL6H47EjBPK6qaJ4B5TDV6mc+vMa0SJFYyo4mkvVQg==}
engines: {node: '>=20.0.0'}
'@aws-sdk/credential-provider-login@3.972.82':
resolution: {integrity: sha512-8jyDPtf1WPxeQ/5xlNpyxAD6P7YKwkYeNk8B2vFEDwkPWDvb7c3tQJG5/jbNWbF2yTMIH9tDTueBkdfovYIAPw==}
'@aws-sdk/credential-provider-login@3.972.83':
resolution: {integrity: sha512-9bJU6FjRacF4FK/QHf8Dyxhur7B1grRj8C1Yh4IkpoFW2exQflPdCv0VrbemLW9ZctLNle+AE7TETs9KNvuLYg==}
engines: {node: '>=20.0.0'}
'@aws-sdk/credential-provider-node@3.972.87':
resolution: {integrity: sha512-TKj2C0x4IMPSSOAlmeryX8fCPvvPS2pFJz2ikV9IGqyh6Ehsg4leMalCTVfV2rvMTrlGAe7X7nK0HZ3P0KZR9g==}
'@aws-sdk/credential-provider-node@3.972.88':
resolution: {integrity: sha512-AG0KID5qhQPzrFkh5b0Cws1Amytvzo0L0Xj6hMgJXEwD6ZXAqninDhSNAWJiN5zU4YVVZnuidnqwPwYJ04IamA==}
engines: {node: '>=20.0.0'}
'@aws-sdk/credential-provider-process@3.972.75':
resolution: {integrity: sha512-ZNm8T/HJNhpS8MikDVAsym8QyUSUuOUGiCkmI3TTya7G6xhOgfRLd2iK91HhQQyvRM36eo6Xm845hHVyfToA2w==}
'@aws-sdk/credential-provider-process@3.972.76':
resolution: {integrity: sha512-pdhKFTXv1zPYhh92MTlp0jVbLWvUEV9v+rziuEkoskGGQ2iJskxHTO38nA8UAbT/N4IM3n8OZJ9rIY5IBO15/A==}
engines: {node: '>=20.0.0'}
'@aws-sdk/credential-provider-sso@3.973.19':
resolution: {integrity: sha512-qHGDAGesyWtRw3BapyFkYSUzVNrC1CWPS6+S2lf/MLMEkWB2gR3Bl6imVHy1nhImXKS+gcuUDzbu39BnsHJc1Q==}
'@aws-sdk/credential-provider-sso@3.973.20':
resolution: {integrity: sha512-DlxrAB3MnjNLx/da7b89W+3DyJQYso/q7Ns15MsST2IV2nkAYQOYTUCaffNSjgrpMP99Dm2mV2jxzcbyF94ZfA==}
engines: {node: '>=20.0.0'}
'@aws-sdk/credential-provider-web-identity@3.972.81':
resolution: {integrity: sha512-nHkgkMIC6NHgqfI6UAQq5cCS4bUkNxkT+r9fXCq/X4IKfX/DHGZ8UM3T4b78OXpWK5OlzEzN4kxwyax0W/MUCA==}
'@aws-sdk/credential-provider-web-identity@3.972.82':
resolution: {integrity: sha512-LUo46IqPTJ4xKBsrLuoaxiv/D2GTBCcFDMD1y3jKIqPR/p7Rw14RJFZ0iRWftqZtyLPQX9JMGnSi3mBu4y/pNA==}
engines: {node: '>=20.0.0'}
'@aws-sdk/lib-storage@3.1153.0':
resolution: {integrity: sha512-zshL4tYVwTXfEOQ0iqNsXUPq/0aoZwOHrAsqlBmGcAij1+8qc79rm9LpzBuEV7KWiSMetZ+89n7mSYgcJKFgyA==}
'@aws-sdk/lib-storage@3.1155.0':
resolution: {integrity: sha512-ThvepKZILBOGCO1rsRsuAWps6hwfMfFVVhRpNrxup+o0q/Hyi50IaDSGZYxrZMPbWH/8pG2LFld//OYDmyr48A==}
engines: {node: '>=20.0.0'}
peerDependencies:
'@aws-sdk/client-s3': ^3.1153.0
'@aws-sdk/client-s3': ^3.1155.0
'@aws-sdk/middleware-sdk-s3@3.972.80':
resolution: {integrity: sha512-zX3lXN/9XOUAuGpxgt3wXwZrXhTjQYEgRsqa856PbZWpOtleUWmCMbhlkLfusPuhW24cOC/8JYR8hnRalrrIkQ==}
'@aws-sdk/middleware-sdk-s3@3.972.81':
resolution: {integrity: sha512-jfanv1pV0sJFZw6/DPiNjvK7B478god0zOum5yIWo+/+1Gy+CSXWpGBWx+3nBJyDvjcu/2kUMr4ADUp9BHH34Q==}
engines: {node: '>=20.0.0'}
'@aws-sdk/nested-clients@3.997.49':
resolution: {integrity: sha512-zjqIqzTYLRKhT7TPJDUreK9vG9PjhmnfLCaTaqArkRT7j+Ogl0tuXB7MpSd8rlRfRT+xTOeyNFWaOfyNcf51hg==}
'@aws-sdk/nested-clients@3.997.50':
resolution: {integrity: sha512-nKb8qu4QsnpZTq/6JIXc72mzuzfh7BK4sUQkpQznPec2z4W7Ob2xYkcM4i5e1yGTBnbJVeFv/0sf3jcz85hclA==}
engines: {node: '>=20.0.0'}
'@aws-sdk/signature-v4-multi-region@3.996.48':
resolution: {integrity: sha512-cFDXfEbXaGwbnpvR/DyqfxfjxpjXVRVthQLgkwvcfDS5u3+8G9WSj1X1Ag/pTRg/QOeOno1Tq3ycEdrKsCUcOA==}
'@aws-sdk/signature-v4-multi-region@3.996.49':
resolution: {integrity: sha512-nvuAZNRrRRVCiARRKsYlQ0VnXiEQiq7vXPH+UA+LSo+kN3Djl9eo/V2r0udkw85PnsNshmv2Em1clld5M3j67w==}
engines: {node: '>=20.0.0'}
'@aws-sdk/token-providers@3.1153.0':
resolution: {integrity: sha512-6bKJ2OfLboxhzeJ3hBDeB8L6emCYf9DfdrjBAharLrY42J1RV/6jA6G8jvfNghzID8Yb40BdSY7jQMtWx9Ot0Q==}
'@aws-sdk/token-providers@3.1155.0':
resolution: {integrity: sha512-iIcQnYLhWuq9qOLQbIWFrxF3PF0svVeZvHnUS4DUnO4zUFmFHIyGyjqrntXG+e/5PUgfavV0pYfDIKHxTzN2LA==}
engines: {node: '>=20.0.0'}
'@aws-sdk/types@3.974.6':
resolution: {integrity: sha512-v/clNZzZnDxGyvpHMOGpJKVXFAExJzUNAAjaWGdcx8QAcXLGwTaOkw33p5SHAi0YAioK32xB3hWwOekRVfmfKg==}
'@aws-sdk/types@3.975.0':
resolution: {integrity: sha512-WQlhs6juByzWOzj5fwxOx0mPJaEqMcG/sDpl3Id61B3XhKE63xY+0QWcnG+sWR/sCfprmreyUo1lM7IKZmHMzw==}
engines: {node: '>=20.0.0'}
'@aws-sdk/xml-builder@3.972.41':
@@ -1237,79 +1237,79 @@ packages:
resolution: {integrity: sha512-VVPPgHyQ6ShqnrmDWuxjmUIsO9gWyOZFmuOfLd9LfBGQJwZfy0gvv9pbHSJuoFNIYC7ZDX9aoFwowjcdSC4E8w==}
engines: {node: '>=18'}
'@napi-rs/canvas-android-arm64@1.0.11':
resolution: {integrity: sha512-dtJS4FQEfH9OwSYLtMrEnj7T8DzBbJc/97LI/OkiWOYpW/iW5t6R/gcxVrUXS8l45gCVpwMufgucRe/pe0ISqA==}
'@napi-rs/canvas-android-arm64@1.0.13':
resolution: {integrity: sha512-trn7AaDzQ+yeeA0ezhkC7cB1ZDPtp4UYk0EvB5F8qtYL9jEVWtVrU30WOpI52DszoTxaTsCgf5PP4ZVaOlOIHQ==}
engines: {node: '>= 10'}
cpu: [arm64]
os: [android]
'@napi-rs/canvas-darwin-arm64@1.0.11':
resolution: {integrity: sha512-J/JylWoJTG8OQqZaiqly4IODyAxNofUlZo0wtSBJtHk6AdymbdIkPynAM1JvQaZpPaG5h+msk9QuK8y4i1ooqw==}
'@napi-rs/canvas-darwin-arm64@1.0.13':
resolution: {integrity: sha512-6Y2djEetL8copxfNK6hg7oig/xqTwOchv19hBOm+zBNdKc/02Z3kjZN5wrLzv/l5O04/bAQS6I/AlQpwBlXgPg==}
engines: {node: '>= 10'}
cpu: [arm64]
os: [darwin]
'@napi-rs/canvas-darwin-x64@1.0.11':
resolution: {integrity: sha512-nlecCk3xy06eAGC7krZtzNDm9UNTHKuHQYK8ZYegdjzgSL8anGt/eLJZm86OWaNgZkwQwvDqfSmG8Nqe6MPZNQ==}
'@napi-rs/canvas-darwin-x64@1.0.13':
resolution: {integrity: sha512-OZoPwwu4TMMg8GTCFBrg8UFExwQTU/55i3uTC/izDat6PHE+ro9dimyQoOboFf91+NY7Pnn70ScR8f8I8+eZXw==}
engines: {node: '>= 10'}
cpu: [x64]
os: [darwin]
'@napi-rs/canvas-linux-arm-gnueabihf@1.0.11':
resolution: {integrity: sha512-DKImmG+sTPolE0kGPsYPBxTtOjb3INALp32O2lW2CfSr0bAzSEKKuu+GrQbtilClyoPOahUErS3OrznFDraoWA==}
'@napi-rs/canvas-linux-arm-gnueabihf@1.0.13':
resolution: {integrity: sha512-dZSYaKROuswU2eUWG5aBKndNVe68elo1YX93xtRax7q658tj8rYE+HwduovWJgs6qwv1r1/CLR60nDeBbyjvyg==}
engines: {node: '>= 10'}
cpu: [arm]
os: [linux]
'@napi-rs/canvas-linux-arm64-gnu@1.0.11':
resolution: {integrity: sha512-55sykACGENj5j1rhj+81AO8JbR9HYgGjRZ7/H+pNr4VE1DvINKYNMTcutk8ZmWxk276jAQvUKJ3+Kdj9GXU3lQ==}
'@napi-rs/canvas-linux-arm64-gnu@1.0.13':
resolution: {integrity: sha512-W6FzYE8ADHI93In1vi80KBL6btDtAMuj50Ch5ptkWS8xuntyzbR4h5JDUmXoakxZn2yMInksudTEqVSWoFqFGg==}
engines: {node: '>= 10'}
cpu: [arm64]
os: [linux]
libc: [glibc]
'@napi-rs/canvas-linux-arm64-musl@1.0.11':
resolution: {integrity: sha512-6Jz6jys17yXydO+WdJgu7JhoB4K7/w9sWHByzzTmLjQghUx3TDMWvLv+UcCdnWhg4oprET67EMymnrETdeGNAg==}
'@napi-rs/canvas-linux-arm64-musl@1.0.13':
resolution: {integrity: sha512-+6fQ+rR49hkAa4voh2stLIwQ+4t+gmoseM8Bc53/DlMXb73IKymDPWK9A/NtWeqTW4ijWppYuyuxS2hPI8tzNA==}
engines: {node: '>= 10'}
cpu: [arm64]
os: [linux]
libc: [musl]
'@napi-rs/canvas-linux-riscv64-gnu@1.0.11':
resolution: {integrity: sha512-lf2XeEebWiUYVOVEjLXxF7dfXrBBSEOBn9AqBh39VYXrPt5cDqI+1t58IFSjn8npqkg5jIOs8TQbjKABbFW1gQ==}
'@napi-rs/canvas-linux-riscv64-gnu@1.0.13':
resolution: {integrity: sha512-17XEMO3t9RW/TtHtSFf2AkaSWuCWdzbPeidz7zrOFawHxoLSJCnpTI1hN5aUvrZNnkNNmt/vZVJCh0ryUaik3A==}
engines: {node: '>= 10'}
cpu: [riscv64]
os: [linux]
libc: [glibc]
'@napi-rs/canvas-linux-x64-gnu@1.0.11':
resolution: {integrity: sha512-iDapw7nFSE5tsmQxcH2+KotJhyfVPmHd6vUOQN/Em+8WNbkZJwJ/rDowlJMFnMl0SYSDSfgKCNUGi6Sn5++Smg==}
'@napi-rs/canvas-linux-x64-gnu@1.0.13':
resolution: {integrity: sha512-lc48IwknVHBsNzXn9Xs5OkUsRX38Ml3Z1aOybQ51mMEzZckm8+4A0qROlyYHEoBPzr1kL3IU8ZUAqDb7wtdX8w==}
engines: {node: '>= 10'}
cpu: [x64]
os: [linux]
libc: [glibc]
'@napi-rs/canvas-linux-x64-musl@1.0.11':
resolution: {integrity: sha512-26q26AyITo3CGZX4HWFIMgBMA1lu3YN07gxzBm4tW6Hglq/OVyTP50kDrmWuKybCX6dh3m8Ly+c7YhI+sqCgCQ==}
'@napi-rs/canvas-linux-x64-musl@1.0.13':
resolution: {integrity: sha512-bGY1r26S2bs8UNFJcPWcCSNdCFbKep5PATsvtgtC9A5u6SRgr2/jJE/RlhHL5m9ZpixNDhVkLY/5h16Kx5V9YA==}
engines: {node: '>= 10'}
cpu: [x64]
os: [linux]
libc: [musl]
'@napi-rs/canvas-win32-arm64-msvc@1.0.11':
resolution: {integrity: sha512-cUO96IdsfvIj2S+2PNcDSkTcLhjPj8pIOEWRV1Kj70MOP4wnE9zSk7N4C1EKHlFGRcgdR5AkbnBZj0dfHK9nbQ==}
'@napi-rs/canvas-win32-arm64-msvc@1.0.13':
resolution: {integrity: sha512-2vRPBZoaIvJrUkDhZTE2Hn0YrnpJRCWVM3EJACoBKjuWI6WgPxGtUoRMsxRviCsyks8bKTSFdyw14CellnG2qA==}
engines: {node: '>= 10'}
cpu: [arm64]
os: [win32]
'@napi-rs/canvas-win32-x64-msvc@1.0.11':
resolution: {integrity: sha512-DFYp/ZQRdKunI/LPc0UkRXG1vlNjJKOJf8O8Ctb8+wm2Nt2ELg9Eamc31D2VoakkfGo8L+z0Iejz6WtFbQwyGw==}
'@napi-rs/canvas-win32-x64-msvc@1.0.13':
resolution: {integrity: sha512-DUlW3lZPz63kYsf8mxgyyZuevhlH0T3/BGP9l+NazhB0F1+gL6D4hqZdnhZpw24DlWmTpATDcyYSZlJOi+DdfQ==}
engines: {node: '>= 10'}
cpu: [x64]
os: [win32]
'@napi-rs/canvas@1.0.11':
resolution: {integrity: sha512-vt5tog1cr+QKadIddGe+3LrcX0EmjEd0izF717Uwf92mBmgYfq2XRmL2r4bZU40spB69exxhrBn9I91HYEr/7w==}
'@napi-rs/canvas@1.0.13':
resolution: {integrity: sha512-b87ugZpQ5ViwqVBkjervxpwa0GmJPsKHLBNoZN6GpjWeHzTbDUGryriq+2DYW2XbB+xCqSIINQysC0/I87nPbw==}
engines: {node: '>= 10'}
'@napi-rs/wasm-runtime@1.2.5':
@@ -2792,8 +2792,8 @@ packages:
resolution: {integrity: sha512-tlqY9xq5ukxTUZBmoOp+m61cqwQD5pHJtFY3Mn8CA8ps6yghLH/Hw8UPdqg4OLmFW3IFlcXnQNmo/dh8HzXYIQ==}
engines: {node: '>=18'}
'@smithy/core@3.36.0':
resolution: {integrity: sha512-3hJiwG1F5t1SoXqp/yOCmYXziyx7ZVqF63S5W6YU201e/qwVaHPex+E/3vvyY9a3ORq/b+BydeNZpP2saQ9X2g==}
'@smithy/core@3.36.2':
resolution: {integrity: sha512-oRw0rB9QSUiCZfEs23G5F4Af+b2rQt1lItIICWu5IejFNd+uPM8SWqiKLMpQ6bX1Q7slsLbjuoE2niErFtkVPQ==}
engines: {node: '>=18.0.0'}
'@smithy/credential-provider-imds@4.5.2':
@@ -2952,14 +2952,14 @@ packages:
'@types/react':
optional: true
'@tanstack/react-virtual@3.14.21':
resolution: {integrity: sha512-wWE/ZJDNNDp18JHWy7n5PQooqerHePVkEEu6Op0twKQ2InF9DPhYMQptQY0syDEviMd3/r8D0OUvKJibDGm9HQ==}
'@tanstack/react-virtual@3.14.22':
resolution: {integrity: sha512-zNiB9IF3prySTfAUrj9tPHjD/lonBYkfxrPP4v4ITUx+qwHajJR5cY/xirjvwc+KWkur10GX3TpsbKh7JkD8Lw==}
peerDependencies:
react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
'@tanstack/virtual-core@3.19.6':
resolution: {integrity: sha512-CsTW892z23gy7IyxYTTorVu1uZWB+0N349rSMgKz4rFLPpLza7F7TpcCKeI5iUAwYQCt8QUlOU67J+KXCxtpGw==}
'@tanstack/virtual-core@3.19.7':
resolution: {integrity: sha512-gxy5SvH0mFRJ0YujtrflB/1HAu9Wn1KGvL0bjZfQkYVVMsIpQiwcf1mR06/ViN4NjllUb4HNCjqE9FGwgTvMCQ==}
'@tesseract.js-data/deu@1.0.0':
resolution: {integrity: sha512-3ter1p2gFAiCd5CMC+PodZ9YY2UDomkKj+ytlKeeZqOUmhLx1SDnYPf5zog0g5MBbxYveW+8omI3PUflqHfZ4Q==}
@@ -5040,8 +5040,8 @@ packages:
resolution: {integrity: sha512-AC/7JofJvZGrrneWNaEnJeOLUx+JlGt7tNa0wZiRPT4MY1wmfKjt2+6O2p2uz2+skll8OZZmJMNqeke7kKbNgQ==}
hasBin: true
jose@6.2.12:
resolution: {integrity: sha512-9NiFmJEex0sy2Dk58j2UGBSHgUs2ypF9eZSu4L6vjOX3Dp96Sw1F3uL+H+D1sx02jZZdzUT0HgvCy59CuvXcWw==}
jose@6.2.13:
resolution: {integrity: sha512-7TIg10ZE1x82YediN+lcrPNvGMKge9yFHQD+Qho/b8y2adfugU3AsvvHfxV0fzyFllT6l9xCjf4RWY9g/Z9PJQ==}
js-md4@0.3.2:
resolution: {integrity: sha512-/GDnfQYsltsjRswQhN9fhv3EMw2sCpUdrdxyWDOUK7eyD++r3gRhzgiQgc/x4MAv2i1iuQ4lxO5mvqM3vj4bwA==}
@@ -5420,8 +5420,8 @@ packages:
resolution: {integrity: sha512-5J9ysMYUpYIg9RF2vJpy9SinEmSviFSe0GyPpCQ4L5QSkLAgeLXlTAOu2ZwWUU5m+0SBl6gUU1R1ZQB3aKypfA==}
engines: {bun: '>=1.0.0', deno: '>=1.30.0', node: '>=8.0.0'}
lucide-react@1.59.0:
resolution: {integrity: sha512-5qgMyyCPU6OvnKpLS2t693+ZtiqA8oaSweWoUows0BLFKFf6V75m4+CptpmohZHpD5IuOVIBXHBBiv2vpLJmtw==}
lucide-react@1.61.0:
resolution: {integrity: sha512-v2SQPPPNjnr+BlhL9Spuzmo5yR+hLkftfuE4E3jaxdNPgSH0hJKPcLJBP7yAta87jQNPsSERS59PJjCJ/CorBA==}
peerDependencies:
'@types/react': '*'
react: ^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0
@@ -5633,8 +5633,8 @@ packages:
resolution: {integrity: sha512-BdiKAgbfRlvfMq/XzO/AF9NPmaA8a+YpLAyAFTVFBj3gARrAIfWvNq7Q7NE4se3KgFu0KkrvQMxTS0XVnsGzHg==}
engines: {node: '>=18'}
nodemailer@10.1.7:
resolution: {integrity: sha512-OQa6FDln5fLttmqSbjDiRYvFDOFNrBqfiOwHPlEbURil3b+vPB1v8xpi+Sp46drzNDlsTnNsS502vthmz56r4w==}
nodemailer@10.2.1:
resolution: {integrity: sha512-ZrDNsPFoDMwefNs7sJwrYiEJ/CG4ClJWDZv05iNJsEBWa8GRDry1QJHTJxsuTAY9eDDKjX4k+q3SxslXJOao7w==}
engines: {node: '>=20.0.0'}
non-error@0.1.0:
@@ -7268,170 +7268,170 @@ snapshots:
'@alloc/quick-lru@5.2.0': {}
'@aws-sdk/checksums@3.1001.4':
'@aws-sdk/checksums@3.1001.5':
dependencies:
'@aws-sdk/core': 3.978.4
'@aws-sdk/types': 3.974.6
'@smithy/core': 3.36.0
'@aws-sdk/core': 3.978.5
'@aws-sdk/types': 3.975.0
'@smithy/core': 3.36.2
'@smithy/types': 4.19.0
tslib: 2.8.1
'@aws-sdk/client-s3@3.1153.0':
'@aws-sdk/client-s3@3.1155.0':
dependencies:
'@aws-sdk/checksums': 3.1001.4
'@aws-sdk/core': 3.978.4
'@aws-sdk/credential-provider-node': 3.972.87
'@aws-sdk/middleware-sdk-s3': 3.972.80
'@aws-sdk/signature-v4-multi-region': 3.996.48
'@aws-sdk/types': 3.974.6
'@smithy/core': 3.36.0
'@aws-sdk/checksums': 3.1001.5
'@aws-sdk/core': 3.978.5
'@aws-sdk/credential-provider-node': 3.972.88
'@aws-sdk/middleware-sdk-s3': 3.972.81
'@aws-sdk/signature-v4-multi-region': 3.996.49
'@aws-sdk/types': 3.975.0
'@smithy/core': 3.36.2
'@smithy/fetch-http-handler': 5.8.0
'@smithy/node-http-handler': 4.12.1
'@smithy/types': 4.19.0
tslib: 2.8.1
'@aws-sdk/core@3.978.4':
'@aws-sdk/core@3.978.5':
dependencies:
'@aws-sdk/types': 3.974.6
'@aws-sdk/types': 3.975.0
'@aws-sdk/xml-builder': 3.972.41
'@aws/lambda-invoke-store': 0.3.0
'@smithy/core': 3.36.0
'@smithy/core': 3.36.2
'@smithy/signature-v4': 5.7.4
'@smithy/types': 4.19.0
bowser: 2.14.1
tslib: 2.8.1
'@aws-sdk/credential-provider-env@3.972.75':
'@aws-sdk/credential-provider-env@3.972.76':
dependencies:
'@aws-sdk/core': 3.978.4
'@aws-sdk/types': 3.974.6
'@smithy/core': 3.36.0
'@aws-sdk/core': 3.978.5
'@aws-sdk/types': 3.975.0
'@smithy/core': 3.36.2
'@smithy/types': 4.19.0
tslib: 2.8.1
'@aws-sdk/credential-provider-http@3.972.77':
'@aws-sdk/credential-provider-http@3.972.78':
dependencies:
'@aws-sdk/core': 3.978.4
'@aws-sdk/types': 3.974.6
'@smithy/core': 3.36.0
'@aws-sdk/core': 3.978.5
'@aws-sdk/types': 3.975.0
'@smithy/core': 3.36.2
'@smithy/fetch-http-handler': 5.8.0
'@smithy/node-http-handler': 4.12.1
'@smithy/types': 4.19.0
tslib: 2.8.1
'@aws-sdk/credential-provider-ini@3.973.20':
'@aws-sdk/credential-provider-ini@3.973.21':
dependencies:
'@aws-sdk/core': 3.978.4
'@aws-sdk/credential-provider-env': 3.972.75
'@aws-sdk/credential-provider-http': 3.972.77
'@aws-sdk/credential-provider-login': 3.972.82
'@aws-sdk/credential-provider-process': 3.972.75
'@aws-sdk/credential-provider-sso': 3.973.19
'@aws-sdk/credential-provider-web-identity': 3.972.81
'@aws-sdk/nested-clients': 3.997.49
'@aws-sdk/types': 3.974.6
'@smithy/core': 3.36.0
'@aws-sdk/core': 3.978.5
'@aws-sdk/credential-provider-env': 3.972.76
'@aws-sdk/credential-provider-http': 3.972.78
'@aws-sdk/credential-provider-login': 3.972.83
'@aws-sdk/credential-provider-process': 3.972.76
'@aws-sdk/credential-provider-sso': 3.973.20
'@aws-sdk/credential-provider-web-identity': 3.972.82
'@aws-sdk/nested-clients': 3.997.50
'@aws-sdk/types': 3.975.0
'@smithy/core': 3.36.2
'@smithy/credential-provider-imds': 4.5.2
'@smithy/types': 4.19.0
tslib: 2.8.1
'@aws-sdk/credential-provider-login@3.972.82':
'@aws-sdk/credential-provider-login@3.972.83':
dependencies:
'@aws-sdk/core': 3.978.4
'@aws-sdk/nested-clients': 3.997.49
'@aws-sdk/types': 3.974.6
'@smithy/core': 3.36.0
'@aws-sdk/core': 3.978.5
'@aws-sdk/nested-clients': 3.997.50
'@aws-sdk/types': 3.975.0
'@smithy/core': 3.36.2
'@smithy/types': 4.19.0
tslib: 2.8.1
'@aws-sdk/credential-provider-node@3.972.87':
'@aws-sdk/credential-provider-node@3.972.88':
dependencies:
'@aws-sdk/credential-provider-env': 3.972.75
'@aws-sdk/credential-provider-http': 3.972.77
'@aws-sdk/credential-provider-ini': 3.973.20
'@aws-sdk/credential-provider-process': 3.972.75
'@aws-sdk/credential-provider-sso': 3.973.19
'@aws-sdk/credential-provider-web-identity': 3.972.81
'@aws-sdk/types': 3.974.6
'@smithy/core': 3.36.0
'@aws-sdk/credential-provider-env': 3.972.76
'@aws-sdk/credential-provider-http': 3.972.78
'@aws-sdk/credential-provider-ini': 3.973.21
'@aws-sdk/credential-provider-process': 3.972.76
'@aws-sdk/credential-provider-sso': 3.973.20
'@aws-sdk/credential-provider-web-identity': 3.972.82
'@aws-sdk/types': 3.975.0
'@smithy/core': 3.36.2
'@smithy/credential-provider-imds': 4.5.2
'@smithy/types': 4.19.0
tslib: 2.8.1
'@aws-sdk/credential-provider-process@3.972.75':
'@aws-sdk/credential-provider-process@3.972.76':
dependencies:
'@aws-sdk/core': 3.978.4
'@aws-sdk/types': 3.974.6
'@smithy/core': 3.36.0
'@aws-sdk/core': 3.978.5
'@aws-sdk/types': 3.975.0
'@smithy/core': 3.36.2
'@smithy/types': 4.19.0
tslib: 2.8.1
'@aws-sdk/credential-provider-sso@3.973.19':
'@aws-sdk/credential-provider-sso@3.973.20':
dependencies:
'@aws-sdk/core': 3.978.4
'@aws-sdk/nested-clients': 3.997.49
'@aws-sdk/token-providers': 3.1153.0
'@aws-sdk/types': 3.974.6
'@smithy/core': 3.36.0
'@aws-sdk/core': 3.978.5
'@aws-sdk/nested-clients': 3.997.50
'@aws-sdk/token-providers': 3.1155.0
'@aws-sdk/types': 3.975.0
'@smithy/core': 3.36.2
'@smithy/types': 4.19.0
tslib: 2.8.1
'@aws-sdk/credential-provider-web-identity@3.972.81':
'@aws-sdk/credential-provider-web-identity@3.972.82':
dependencies:
'@aws-sdk/core': 3.978.4
'@aws-sdk/nested-clients': 3.997.49
'@aws-sdk/types': 3.974.6
'@smithy/core': 3.36.0
'@aws-sdk/core': 3.978.5
'@aws-sdk/nested-clients': 3.997.50
'@aws-sdk/types': 3.975.0
'@smithy/core': 3.36.2
'@smithy/types': 4.19.0
tslib: 2.8.1
'@aws-sdk/lib-storage@3.1153.0(@aws-sdk/client-s3@3.1153.0)':
'@aws-sdk/lib-storage@3.1155.0(@aws-sdk/client-s3@3.1155.0)':
dependencies:
'@aws-sdk/client-s3': 3.1153.0
'@smithy/core': 3.36.0
'@aws-sdk/client-s3': 3.1155.0
'@smithy/core': 3.36.2
'@smithy/types': 4.19.0
buffer: 5.6.0
events: 3.3.0
stream-browserify: 3.0.0
tslib: 2.8.1
'@aws-sdk/middleware-sdk-s3@3.972.80':
'@aws-sdk/middleware-sdk-s3@3.972.81':
dependencies:
'@aws-sdk/core': 3.978.4
'@aws-sdk/signature-v4-multi-region': 3.996.48
'@aws-sdk/types': 3.974.6
'@smithy/core': 3.36.0
'@aws-sdk/core': 3.978.5
'@aws-sdk/signature-v4-multi-region': 3.996.49
'@aws-sdk/types': 3.975.0
'@smithy/core': 3.36.2
'@smithy/types': 4.19.0
tslib: 2.8.1
'@aws-sdk/nested-clients@3.997.49':
'@aws-sdk/nested-clients@3.997.50':
dependencies:
'@aws-sdk/core': 3.978.4
'@aws-sdk/signature-v4-multi-region': 3.996.48
'@aws-sdk/types': 3.974.6
'@smithy/core': 3.36.0
'@aws-sdk/core': 3.978.5
'@aws-sdk/signature-v4-multi-region': 3.996.49
'@aws-sdk/types': 3.975.0
'@smithy/core': 3.36.2
'@smithy/fetch-http-handler': 5.8.0
'@smithy/node-http-handler': 4.12.1
'@smithy/types': 4.19.0
tslib: 2.8.1
'@aws-sdk/signature-v4-multi-region@3.996.48':
'@aws-sdk/signature-v4-multi-region@3.996.49':
dependencies:
'@aws-sdk/types': 3.974.6
'@aws-sdk/types': 3.975.0
'@smithy/signature-v4': 5.7.4
'@smithy/types': 4.19.0
tslib: 2.8.1
'@aws-sdk/token-providers@3.1153.0':
'@aws-sdk/token-providers@3.1155.0':
dependencies:
'@aws-sdk/core': 3.978.4
'@aws-sdk/nested-clients': 3.997.49
'@aws-sdk/types': 3.974.6
'@smithy/core': 3.36.0
'@aws-sdk/core': 3.978.5
'@aws-sdk/nested-clients': 3.997.50
'@aws-sdk/types': 3.975.0
'@smithy/core': 3.36.2
'@smithy/types': 4.19.0
tslib: 2.8.1
'@aws-sdk/types@3.974.6':
'@aws-sdk/types@3.975.0':
dependencies:
'@smithy/types': 4.19.0
tslib: 2.8.1
@@ -8368,7 +8368,7 @@ snapshots:
express: 5.2.1(supports-color@7.2.0)
express-rate-limit: 8.6.2(express@5.2.1(supports-color@7.2.0))(supports-color@7.2.0)
hono: 4.13.13
jose: 6.2.12
jose: 6.2.13
json-schema-typed: 8.0.2
pkce-challenge: 5.0.1
raw-body: 3.0.2
@@ -8389,52 +8389,52 @@ snapshots:
strict-event-emitter: 0.5.1
optional: true
'@napi-rs/canvas-android-arm64@1.0.11':
'@napi-rs/canvas-android-arm64@1.0.13':
optional: true
'@napi-rs/canvas-darwin-arm64@1.0.11':
'@napi-rs/canvas-darwin-arm64@1.0.13':
optional: true
'@napi-rs/canvas-darwin-x64@1.0.11':
'@napi-rs/canvas-darwin-x64@1.0.13':
optional: true
'@napi-rs/canvas-linux-arm-gnueabihf@1.0.11':
'@napi-rs/canvas-linux-arm-gnueabihf@1.0.13':
optional: true
'@napi-rs/canvas-linux-arm64-gnu@1.0.11':
'@napi-rs/canvas-linux-arm64-gnu@1.0.13':
optional: true
'@napi-rs/canvas-linux-arm64-musl@1.0.11':
'@napi-rs/canvas-linux-arm64-musl@1.0.13':
optional: true
'@napi-rs/canvas-linux-riscv64-gnu@1.0.11':
'@napi-rs/canvas-linux-riscv64-gnu@1.0.13':
optional: true
'@napi-rs/canvas-linux-x64-gnu@1.0.11':
'@napi-rs/canvas-linux-x64-gnu@1.0.13':
optional: true
'@napi-rs/canvas-linux-x64-musl@1.0.11':
'@napi-rs/canvas-linux-x64-musl@1.0.13':
optional: true
'@napi-rs/canvas-win32-arm64-msvc@1.0.11':
'@napi-rs/canvas-win32-arm64-msvc@1.0.13':
optional: true
'@napi-rs/canvas-win32-x64-msvc@1.0.11':
'@napi-rs/canvas-win32-x64-msvc@1.0.13':
optional: true
'@napi-rs/canvas@1.0.11':
'@napi-rs/canvas@1.0.13':
optionalDependencies:
'@napi-rs/canvas-android-arm64': 1.0.11
'@napi-rs/canvas-darwin-arm64': 1.0.11
'@napi-rs/canvas-darwin-x64': 1.0.11
'@napi-rs/canvas-linux-arm-gnueabihf': 1.0.11
'@napi-rs/canvas-linux-arm64-gnu': 1.0.11
'@napi-rs/canvas-linux-arm64-musl': 1.0.11
'@napi-rs/canvas-linux-riscv64-gnu': 1.0.11
'@napi-rs/canvas-linux-x64-gnu': 1.0.11
'@napi-rs/canvas-linux-x64-musl': 1.0.11
'@napi-rs/canvas-win32-arm64-msvc': 1.0.11
'@napi-rs/canvas-win32-x64-msvc': 1.0.11
'@napi-rs/canvas-android-arm64': 1.0.13
'@napi-rs/canvas-darwin-arm64': 1.0.13
'@napi-rs/canvas-darwin-x64': 1.0.13
'@napi-rs/canvas-linux-arm-gnueabihf': 1.0.13
'@napi-rs/canvas-linux-arm64-gnu': 1.0.13
'@napi-rs/canvas-linux-arm64-musl': 1.0.13
'@napi-rs/canvas-linux-riscv64-gnu': 1.0.13
'@napi-rs/canvas-linux-x64-gnu': 1.0.13
'@napi-rs/canvas-linux-x64-musl': 1.0.13
'@napi-rs/canvas-win32-arm64-msvc': 1.0.13
'@napi-rs/canvas-win32-x64-msvc': 1.0.13
'@napi-rs/wasm-runtime@1.2.5(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)':
dependencies:
@@ -9887,32 +9887,32 @@ snapshots:
'@sindresorhus/merge-streams@4.0.0': {}
'@smithy/core@3.36.0':
'@smithy/core@3.36.2':
dependencies:
'@smithy/types': 4.19.0
tslib: 2.8.1
'@smithy/credential-provider-imds@4.5.2':
dependencies:
'@smithy/core': 3.36.0
'@smithy/core': 3.36.2
'@smithy/types': 4.19.0
tslib: 2.8.1
'@smithy/fetch-http-handler@5.8.0':
dependencies:
'@smithy/core': 3.36.0
'@smithy/core': 3.36.2
'@smithy/types': 4.19.0
tslib: 2.8.1
'@smithy/node-http-handler@4.12.1':
dependencies:
'@smithy/core': 3.36.0
'@smithy/core': 3.36.2
'@smithy/types': 4.19.0
tslib: 2.8.1
'@smithy/signature-v4@5.7.4':
dependencies:
'@smithy/core': 3.36.0
'@smithy/core': 3.36.2
'@smithy/types': 4.19.0
tslib: 2.8.1
@@ -10072,13 +10072,13 @@ snapshots:
optionalDependencies:
'@types/react': 19.3.0
'@tanstack/react-virtual@3.14.21(react-dom@19.3.1(react@19.3.1))(react@19.3.1)':
'@tanstack/react-virtual@3.14.22(react-dom@19.3.1(react@19.3.1))(react@19.3.1)':
dependencies:
'@tanstack/virtual-core': 3.19.6
'@tanstack/virtual-core': 3.19.7
react: 19.3.1
react-dom: 19.3.1(react@19.3.1)
'@tanstack/virtual-core@3.19.6': {}
'@tanstack/virtual-core@3.19.7': {}
'@tesseract.js-data/deu@1.0.0': {}
@@ -12354,7 +12354,7 @@ snapshots:
jiti@2.7.0: {}
jose@6.2.12: {}
jose@6.2.13: {}
js-md4@0.3.2: {}
@@ -12677,7 +12677,7 @@ snapshots:
lru.min@1.1.5: {}
lucide-react@1.59.0(@types/react@19.3.0)(react@19.3.1):
lucide-react@1.61.0(@types/react@19.3.0)(react@19.3.1):
dependencies:
react: 19.3.1
optionalDependencies:
@@ -12868,7 +12868,7 @@ snapshots:
node-releases@2.0.58: {}
nodemailer@10.1.7: {}
nodemailer@10.2.1: {}
non-error@0.1.0: {}
@@ -13113,7 +13113,7 @@ snapshots:
pdfjs-dist@6.4.299:
optionalDependencies:
'@napi-rs/canvas': 1.0.11
'@napi-rs/canvas': 1.0.13
perfect-debounce@2.1.0: {}
+51 -3
View File
@@ -11,7 +11,11 @@
# empty the new database and run the move again.
# 5. Empty the new volume: it must refuse to import the old state again,
# and move it once more after the marker is removed as the guide says.
# 6. A new install with no old volume: nothing to do.
# 6. Going back to 16 after a move that did not finish: db-upgrade leaves
# the live 16 database alone.
# 7. Using 16 again after a move, then updating: refused, not the old copy.
# 8. Updating again as the guide says: the current 16 database moves.
# 9. A new install with no old volume: nothing to do.
#
# Needs Docker and the repo's node_modules (for the migrations). Run it from
# anywhere in the repo: `bash scripts/compose-db-upgrade-check.sh`.
@@ -85,7 +89,7 @@ wait_for_port() {
migrate_old() {
local started=$SECONDS
until DATABASE_URL="postgresql://healthlog:$POSTGRES_PASSWORD@127.0.0.1:$PORT/healthlog?schema=public" \
node "$ROOT/node_modules/prisma/build/index.js" migrate deploy --schema "$ROOT/prisma/schema.prisma" >"$WORK/migrate.log" 2>&1; do
node "$ROOT/node_modules/prisma/build/index.js" migrate deploy --schema "$WORK/old-prisma/prisma/schema.prisma" >"$WORK/migrate.log" 2>&1; do
if [ $((SECONDS - started)) -ge 30 ] || ! grep -q "P1001" "$WORK/migrate.log"; then
cat "$WORK/migrate.log" >&2
return 1
@@ -94,6 +98,18 @@ migrate_old() {
done
}
# The v1.45 schema, migrated with v1.45's own migrations.
mkdir -p "$WORK/old-prisma"
git -C "$ROOT" archive "$OLD_REF" prisma | tar -x -C "$WORK/old-prisma"
# The current compose file with its database pointed back at Postgres 16, the
# way docs/ops/postgres-major-upgrade.md "Going back" describes it.
sed -e 's|image: postgres:18\.[0-9]*-alpine|image: postgres:16.15-alpine|' \
-e 's|- postgres-data:/var/lib/postgresql$|- pgdata:/var/lib/postgresql/data|' \
"$ROOT/docker-compose.yml" >"$WORK/back.yml"
grep -q 'image: postgres:16.15-alpine' "$WORK/back.yml" && grep -q -- '- pgdata:/var/lib/postgresql/data' "$WORK/back.yml" ||
{ echo "FAIL: could not derive the going-back compose file" >&2; exit 1; }
back() { docker compose -p "$PROJECT" -f "$WORK/back.yml" "$@"; }
down_all
step "a v1.45 database on Postgres 16"
old up -d --wait db >"$WORK/up.log" 2>&1 || { cat "$WORK/up.log" >&2; fail "the v1.45 database did not start"; }
@@ -157,10 +173,42 @@ CODE=$(run_upgrade)
[ "$CODE" = 0 ] || fail "db-upgrade exited $CODE after the marker was removed"
[ "$(probe docker exec healthlog-db)" = "$PROBE" ] || fail "the probe rows differ after starting over"
step "going back to 16 after a move that did not finish leaves 16 alone"
new down >/dev/null 2>&1
docker volume rm "${PROJECT}_postgres-data" >/dev/null
docker run --rm -v "${PROJECT}_pgdata:/old" postgres:16.15-alpine \
mv /old/HEALTHLOG_MOVED_TO_POSTGRES_18 /old/HEALTHLOG_MOVING_TO_POSTGRES_18
back up -d db-upgrade >/dev/null 2>&1 || true
CODE=$(docker wait healthlog-db-upgrade)
upgrade_log
[ "$CODE" = 0 ] || fail "db-upgrade exited $CODE beside a Postgres 16 database service"
upgrade_log | grep -q 'not 18: nothing to move' || fail "db-upgrade did not recognise the Postgres 16 service"
[ "$(probe docker exec healthlog-db)" = "$PROBE" ] || fail "the live Postgres 16 data changed beside db-upgrade"
step "using 16 again after a move, then updating, is refused instead of serving the old copy"
back down >/dev/null 2>&1
CODE=$(run_upgrade)
[ "$CODE" = 0 ] || fail "the move after going back exited $CODE"
back up -d db >/dev/null 2>&1
back exec -T db psql -U healthlog -d healthlog -v ON_ERROR_STOP=1 -qc \
"INSERT INTO check_probe.rows VALUES (50001, 'written on 16 after the move')"
back down >/dev/null 2>&1
CODE=$(run_upgrade)
[ "$CODE" = 1 ] || fail "db-upgrade exited $CODE after 16 was used again, expected 1"
upgrade_log | grep -q 'changed after it was moved' || fail "the refusal did not say that 16 changed"
step "updating again as the guide says moves the current 16 database"
new down >/dev/null 2>&1
docker volume rm "${PROJECT}_postgres-data" >/dev/null
new run --rm --entrypoint rm db-upgrade /var/lib/postgresql/data/HEALTHLOG_MOVED_TO_POSTGRES_18 >/dev/null 2>&1
CODE=$(run_upgrade)
[ "$CODE" = 0 ] || fail "db-upgrade exited $CODE when moving the current 16 database"
[ "$(sql "SELECT count(*) FROM check_probe.rows")" = 50001 ] || fail "the row written on 16 did not arrive"
step "a new install: nothing to do"
new down -v >/dev/null 2>&1
CODE=$(run_upgrade)
[ "$CODE" = 0 ] || fail "db-upgrade exited $CODE on a new install"
upgrade_log | tail -1 | grep -q 'no Postgres 16 database to move' || fail "the new install did not say there was nothing to move"
echo "OK: the Postgres 16 -> 18 move behaves in all six cases"
echo "OK: the Postgres 16 -> 18 move behaves in all nine cases"
@@ -77,6 +77,8 @@ describe("the Postgres 16 -> 18 move in docker-compose.yml", () => {
// The checks run before the restore, in this order.
const order = [
"PG_VERSION",
// Never touch the old volume while the database service runs on it.
'if [ "$$NEWVER" -lt 180000 ]',
"$$MARK",
"$$MOVING",
'elif [ "$$TABLES" != 0 ]',
+4 -2
View File
@@ -105,7 +105,9 @@ describe("demo seed, short-window shape", () => {
it("leaves the median of the window in the settled band", () => {
// The usual range is the median ± 3 MAD of the last 30 days. A stretch
// that fills half the window moves that median onto the stretch and
// widens the band to something like 87-181 mmHg.
// widens the band to something like 87-181 mmHg. The seed's own spread
// reaches about ±22 in one run of 150, so the bound sits at 24, still far
// below what a stretch that long produces.
const sorted = [...systolic].sort((a, b) => a - b);
const median = sorted[Math.floor(sorted.length / 2)];
expect(median).toBeLessThanOrEqual(124);
@@ -113,7 +115,7 @@ describe("demo seed, short-window shape", () => {
.map((v) => Math.abs(v - median))
.sort((a, b) => a - b);
const mad = deviations[Math.floor(deviations.length / 2)];
expect(3 * 1.4826 * mad).toBeLessThanOrEqual(20);
expect(3 * 1.4826 * mad).toBeLessThanOrEqual(24);
});
it("raises ramipril on the day the stretch turns", () => {