Add VAPID config and the push subscription API
CI / ci (push) Successful in 6m3s
CI / ci (pull_request) Successful in 5m17s

Require MEALQUEST_VAPID_PUBLIC_KEY, MEALQUEST_VAPID_PRIVATE_KEY_FILE and
MEALQUEST_VAPID_SUBJECT at boot. Both keys are unpadded base64url P-256
keys, the encoding webpush-go reads; boot derives the public point from the
private key and refuses a pair that does not match. The subject must be
mailto:<address> or an https:// URL.

internal/push stores subscriptions by endpoint: GET
/api/v1/push/vapid-public-key serves only the public key, POST
/api/v1/push/subscriptions upserts {endpoint, keys.p256dh, keys.auth,
user_agent}, and DELETE removes by endpoint, answering 204 whether or not
it was stored. cmd/vapidgen prints a new key pair. deploy/compose.yaml
mounts the vapid_private_key secret.

Maestro task e8a7b1f4-327a-478b-88c7-5d265afda153.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-09 08:03:59 -04:00
co-authored by Claude Opus 5.5
parent cb451baed4
commit f1e7503f94
30 changed files with 1526 additions and 36 deletions
+1 -1
View File
@@ -69,7 +69,7 @@ func newGoogleEnv(t *testing.T) *googleEnv {
}
logger := slog.New(slog.NewJSONHandler(e.logs, &slog.HandlerOptions{Level: slog.LevelDebug}))
e.h, err = NewRouter(logger, "dev", nil, stubAuth{}, stubSessions{}, newTestSettings(t), newTestSearch(t), newTestAI(t),
newTestDinners(t), newTestSuggest(t), newTestPlan(t), newTestShopping(t), svc, newTestRunner(t), newTestPhotos(t),
newTestDinners(t), newTestSuggest(t), newTestPlan(t), newTestShopping(t), svc, newTestPush(t), newTestRunner(t), newTestPhotos(t),
fstest.MapFS{"index.html": {Data: []byte(fixtureIndex)}})
if err != nil {
t.Fatal(err)