From 160d922b8b3c01355cbb3e696ef9b31ef0438278 Mon Sep 17 00:00:00 2001 From: claw Date: Mon, 30 Mar 2026 16:01:06 +0200 Subject: [PATCH] fix: resolve remaining format and test failures Co-authored-by: claw --- apps/bot/src/scheduler-runner.ts | 6 +---- docs/runbooks/vps-compose-deploy.md | 22 +++++++++++++++++++ .../src/ad-hoc-notification-service.test.ts | 4 ++-- .../src/scheduled-dispatch-service.ts | 5 ++++- 4 files changed, 29 insertions(+), 8 deletions(-) diff --git a/apps/bot/src/scheduler-runner.ts b/apps/bot/src/scheduler-runner.ts index 2f8b922..2007880 100644 --- a/apps/bot/src/scheduler-runner.ts +++ b/apps/bot/src/scheduler-runner.ts @@ -20,11 +20,7 @@ function parsePositiveInteger(name: string, fallback: number): number { return parsed } -async function runOnce(input: { - baseUrl: string - schedulerSecret: string - dueScanLimit: number -}) { +async function runOnce(input: { baseUrl: string; schedulerSecret: string; dueScanLimit: number }) { const response = await fetch(`${input.baseUrl}/jobs/dispatch-due?limit=${input.dueScanLimit}`, { method: 'POST', headers: { diff --git a/docs/runbooks/vps-compose-deploy.md b/docs/runbooks/vps-compose-deploy.md index 901483f..6ecaab8 100644 --- a/docs/runbooks/vps-compose-deploy.md +++ b/docs/runbooks/vps-compose-deploy.md @@ -5,6 +5,7 @@ Make the VPS deployment path first-class without removing the existing Cloud Run / AWS paths. Primary target: + - bot API on Docker Compose - mini app on Docker Compose - reverse proxy with HTTPS on the VPS @@ -12,18 +13,21 @@ Primary target: - GitHub Actions CD that deploys to the VPS Compatibility requirement: + - keep existing cloud deployment code and workflows available - avoid deleting GCP/AWS-specific adapters unless they are clearly dead and isolated ## Deployment Shape Recommended production services: + - `bot` - Bun runtime for Telegram webhook/API - `miniapp` - static assets served behind reverse proxy - `scheduler` - separate service that periodically triggers due scheduled dispatch processing - `caddy` - TLS + reverse proxy for `bot.` and `app.` Database: + - keep Supabase / managed Postgres external - do not move Postgres onto the VPS in this phase @@ -32,6 +36,7 @@ Database: Current app logic already stores scheduled dispatches in Postgres and uses provider adapters for one-shot execution. For VPS: + 1. Add a self-hosted scheduled dispatch provider. 2. Keep dispatch records in the database as before. 3. Add a due-dispatch scan endpoint/handler in the bot runtime. @@ -43,6 +48,7 @@ This keeps reminder behavior deterministic while removing dependency on cloud sc ## Image / Runtime Plan ### Bot image + - keep multi-stage build - build runtime entrypoints for: - bot server @@ -51,14 +57,17 @@ This keeps reminder behavior deterministic while removing dependency on cloud sc - keep runtime image lean ### Mini app image + - keep static build + nginx/alpine runtime ### Reverse proxy image + - use an off-the-shelf slim image (Caddy) ## CD Plan Add a separate GitHub Actions workflow for VPS deploy: + 1. run on successful `main` CI and manual dispatch 2. build/push bot and miniapp images to GHCR 3. SSH into VPS @@ -73,35 +82,42 @@ Keep existing GCP and AWS workflows untouched. ## Secrets / Env Plan Phase 1: + - keep runtime env files on the VPS outside the repo - Compose loads env files from a deploy directory Optional later upgrade: + - add 1Password-backed rendering or injection without changing app runtime contracts - keep the runtime contract env-file-based so 1Password remains an overlay, not a hard dependency Compatibility rule: + - do not remove existing env vars for GCP/AWS paths - only add new VPS/self-hosted vars where needed ## Expected Repo Changes ### App/runtime + - add self-hosted scheduler adapter - add due-dispatch scan support - add scheduler runner entrypoint - extend config parsing with VPS/self-hosted provider ### Docker / deploy + - add production compose file - add Caddy config - add VPS deploy helper scripts ### CI/CD + - add VPS deploy workflow - keep `cd.yml` and `cd-aws.yml` ### Docs + - add VPS deployment runbook - document required env files and domains @@ -110,6 +126,7 @@ Compatibility rule: Base domain: `whekin.dev` Suggested hostnames: + - `household-bot.whekin.dev` for bot API / webhook - `household.whekin.dev` for mini app @@ -128,16 +145,19 @@ These can be adjusted later without changing the deployment shape. ## Runtime Env Files on VPS Expected files under `/opt/household-bot/env`: + - `bot.env` - `miniapp.env` - `caddy.env` Templates live in `deploy/vps/*.env.example`. + - `miniapp.env` should set `VITE_BOT_API_URL` for the frontend build/runtime config. ## GitHub Actions Inputs / Secrets Recommended repository variables: + - `VPS_HOST` - `VPS_USER` (default `root`) - `VPS_PORT` (default `22`) @@ -146,10 +166,12 @@ Recommended repository variables: - `VPS_MINIAPP_URL` (default `https://household.whekin.dev`) Required repository secrets: + - `VPS_SSH_KEY` - `VPS_KNOWN_HOSTS` Optional for webhook sync and smoke verification: + - `TELEGRAM_BOT_TOKEN` - `TELEGRAM_WEBHOOK_SECRET` diff --git a/packages/application/src/ad-hoc-notification-service.test.ts b/packages/application/src/ad-hoc-notification-service.test.ts index 6a02af6..a58931c 100644 --- a/packages/application/src/ad-hoc-notification-service.test.ts +++ b/packages/application/src/ad-hoc-notification-service.test.ts @@ -351,7 +351,7 @@ describe('createAdHocNotificationService', () => { const result = await service.updateNotification({ notificationId: created.id, viewerMemberId: 'creator', - scheduledFor: Temporal.Instant.from('2026-03-25T09:00:00Z'), + scheduledFor: Temporal.Instant.from('2099-03-25T09:00:00Z'), timePrecision: 'exact', deliveryMode: 'dm_selected', dmRecipientMemberIds: ['alice', 'bob'], @@ -360,7 +360,7 @@ describe('createAdHocNotificationService', () => { expect(result.status).toBe('updated') if (result.status === 'updated') { - expect(result.notification.scheduledFor.toString()).toBe('2026-03-25T09:00:00Z') + expect(result.notification.scheduledFor.toString()).toBe('2099-03-25T09:00:00Z') expect(result.notification.deliveryMode).toBe('dm_selected') expect(result.notification.dmRecipientMemberIds).toEqual(['alice', 'bob']) } diff --git a/packages/application/src/scheduled-dispatch-service.ts b/packages/application/src/scheduled-dispatch-service.ts index 177f369..e485133 100644 --- a/packages/application/src/scheduled-dispatch-service.ts +++ b/packages/application/src/scheduled-dispatch-service.ts @@ -97,7 +97,10 @@ export interface ScheduledDispatchService { cancelAdHocNotification(notificationId: string, cancelledAt?: Instant): Promise reconcileHouseholdBuiltInDispatches(householdId: string, asOf?: Instant): Promise reconcileAllBuiltInDispatches(asOf?: Instant): Promise - listDueDispatches(input?: { asOf?: Instant; limit?: number }): Promise + listDueDispatches(input?: { + asOf?: Instant + limit?: number + }): Promise getDispatchById(dispatchId: string): Promise claimDispatch(dispatchId: string): Promise releaseDispatch(dispatchId: string): Promise