fix: resolve remaining format and test failures

Co-authored-by: claw <stanislavkalishin+claw@gmail.com>
This commit is contained in:
2026-03-30 16:01:06 +02:00
parent ed2fbac284
commit 160d922b8b
4 changed files with 29 additions and 8 deletions

View File

@@ -20,11 +20,7 @@ function parsePositiveInteger(name: string, fallback: number): number {
return parsed return parsed
} }
async function runOnce(input: { async function runOnce(input: { baseUrl: string; schedulerSecret: string; dueScanLimit: number }) {
baseUrl: string
schedulerSecret: string
dueScanLimit: number
}) {
const response = await fetch(`${input.baseUrl}/jobs/dispatch-due?limit=${input.dueScanLimit}`, { const response = await fetch(`${input.baseUrl}/jobs/dispatch-due?limit=${input.dueScanLimit}`, {
method: 'POST', method: 'POST',
headers: { headers: {

View File

@@ -5,6 +5,7 @@
Make the VPS deployment path first-class without removing the existing Cloud Run / AWS paths. Make the VPS deployment path first-class without removing the existing Cloud Run / AWS paths.
Primary target: Primary target:
- bot API on Docker Compose - bot API on Docker Compose
- mini app on Docker Compose - mini app on Docker Compose
- reverse proxy with HTTPS on the VPS - reverse proxy with HTTPS on the VPS
@@ -12,18 +13,21 @@ Primary target:
- GitHub Actions CD that deploys to the VPS - GitHub Actions CD that deploys to the VPS
Compatibility requirement: Compatibility requirement:
- keep existing cloud deployment code and workflows available - keep existing cloud deployment code and workflows available
- avoid deleting GCP/AWS-specific adapters unless they are clearly dead and isolated - avoid deleting GCP/AWS-specific adapters unless they are clearly dead and isolated
## Deployment Shape ## Deployment Shape
Recommended production services: Recommended production services:
- `bot` - Bun runtime for Telegram webhook/API - `bot` - Bun runtime for Telegram webhook/API
- `miniapp` - static assets served behind reverse proxy - `miniapp` - static assets served behind reverse proxy
- `scheduler` - separate service that periodically triggers due scheduled dispatch processing - `scheduler` - separate service that periodically triggers due scheduled dispatch processing
- `caddy` - TLS + reverse proxy for `bot.<domain>` and `app.<domain>` - `caddy` - TLS + reverse proxy for `bot.<domain>` and `app.<domain>`
Database: Database:
- keep Supabase / managed Postgres external - keep Supabase / managed Postgres external
- do not move Postgres onto the VPS in this phase - 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. Current app logic already stores scheduled dispatches in Postgres and uses provider adapters for one-shot execution.
For VPS: For VPS:
1. Add a self-hosted scheduled dispatch provider. 1. Add a self-hosted scheduled dispatch provider.
2. Keep dispatch records in the database as before. 2. Keep dispatch records in the database as before.
3. Add a due-dispatch scan endpoint/handler in the bot runtime. 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 ## Image / Runtime Plan
### Bot image ### Bot image
- keep multi-stage build - keep multi-stage build
- build runtime entrypoints for: - build runtime entrypoints for:
- bot server - bot server
@@ -51,14 +57,17 @@ This keeps reminder behavior deterministic while removing dependency on cloud sc
- keep runtime image lean - keep runtime image lean
### Mini app image ### Mini app image
- keep static build + nginx/alpine runtime - keep static build + nginx/alpine runtime
### Reverse proxy image ### Reverse proxy image
- use an off-the-shelf slim image (Caddy) - use an off-the-shelf slim image (Caddy)
## CD Plan ## CD Plan
Add a separate GitHub Actions workflow for VPS deploy: Add a separate GitHub Actions workflow for VPS deploy:
1. run on successful `main` CI and manual dispatch 1. run on successful `main` CI and manual dispatch
2. build/push bot and miniapp images to GHCR 2. build/push bot and miniapp images to GHCR
3. SSH into VPS 3. SSH into VPS
@@ -73,35 +82,42 @@ Keep existing GCP and AWS workflows untouched.
## Secrets / Env Plan ## Secrets / Env Plan
Phase 1: Phase 1:
- keep runtime env files on the VPS outside the repo - keep runtime env files on the VPS outside the repo
- Compose loads env files from a deploy directory - Compose loads env files from a deploy directory
Optional later upgrade: Optional later upgrade:
- add 1Password-backed rendering or injection without changing app runtime contracts - 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 - keep the runtime contract env-file-based so 1Password remains an overlay, not a hard dependency
Compatibility rule: Compatibility rule:
- do not remove existing env vars for GCP/AWS paths - do not remove existing env vars for GCP/AWS paths
- only add new VPS/self-hosted vars where needed - only add new VPS/self-hosted vars where needed
## Expected Repo Changes ## Expected Repo Changes
### App/runtime ### App/runtime
- add self-hosted scheduler adapter - add self-hosted scheduler adapter
- add due-dispatch scan support - add due-dispatch scan support
- add scheduler runner entrypoint - add scheduler runner entrypoint
- extend config parsing with VPS/self-hosted provider - extend config parsing with VPS/self-hosted provider
### Docker / deploy ### Docker / deploy
- add production compose file - add production compose file
- add Caddy config - add Caddy config
- add VPS deploy helper scripts - add VPS deploy helper scripts
### CI/CD ### CI/CD
- add VPS deploy workflow - add VPS deploy workflow
- keep `cd.yml` and `cd-aws.yml` - keep `cd.yml` and `cd-aws.yml`
### Docs ### Docs
- add VPS deployment runbook - add VPS deployment runbook
- document required env files and domains - document required env files and domains
@@ -110,6 +126,7 @@ Compatibility rule:
Base domain: `whekin.dev` Base domain: `whekin.dev`
Suggested hostnames: Suggested hostnames:
- `household-bot.whekin.dev` for bot API / webhook - `household-bot.whekin.dev` for bot API / webhook
- `household.whekin.dev` for mini app - `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 ## Runtime Env Files on VPS
Expected files under `/opt/household-bot/env`: Expected files under `/opt/household-bot/env`:
- `bot.env` - `bot.env`
- `miniapp.env` - `miniapp.env`
- `caddy.env` - `caddy.env`
Templates live in `deploy/vps/*.env.example`. Templates live in `deploy/vps/*.env.example`.
- `miniapp.env` should set `VITE_BOT_API_URL` for the frontend build/runtime config. - `miniapp.env` should set `VITE_BOT_API_URL` for the frontend build/runtime config.
## GitHub Actions Inputs / Secrets ## GitHub Actions Inputs / Secrets
Recommended repository variables: Recommended repository variables:
- `VPS_HOST` - `VPS_HOST`
- `VPS_USER` (default `root`) - `VPS_USER` (default `root`)
- `VPS_PORT` (default `22`) - `VPS_PORT` (default `22`)
@@ -146,10 +166,12 @@ Recommended repository variables:
- `VPS_MINIAPP_URL` (default `https://household.whekin.dev`) - `VPS_MINIAPP_URL` (default `https://household.whekin.dev`)
Required repository secrets: Required repository secrets:
- `VPS_SSH_KEY` - `VPS_SSH_KEY`
- `VPS_KNOWN_HOSTS` - `VPS_KNOWN_HOSTS`
Optional for webhook sync and smoke verification: Optional for webhook sync and smoke verification:
- `TELEGRAM_BOT_TOKEN` - `TELEGRAM_BOT_TOKEN`
- `TELEGRAM_WEBHOOK_SECRET` - `TELEGRAM_WEBHOOK_SECRET`

View File

@@ -351,7 +351,7 @@ describe('createAdHocNotificationService', () => {
const result = await service.updateNotification({ const result = await service.updateNotification({
notificationId: created.id, notificationId: created.id,
viewerMemberId: 'creator', viewerMemberId: 'creator',
scheduledFor: Temporal.Instant.from('2026-03-25T09:00:00Z'), scheduledFor: Temporal.Instant.from('2099-03-25T09:00:00Z'),
timePrecision: 'exact', timePrecision: 'exact',
deliveryMode: 'dm_selected', deliveryMode: 'dm_selected',
dmRecipientMemberIds: ['alice', 'bob'], dmRecipientMemberIds: ['alice', 'bob'],
@@ -360,7 +360,7 @@ describe('createAdHocNotificationService', () => {
expect(result.status).toBe('updated') expect(result.status).toBe('updated')
if (result.status === '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.deliveryMode).toBe('dm_selected')
expect(result.notification.dmRecipientMemberIds).toEqual(['alice', 'bob']) expect(result.notification.dmRecipientMemberIds).toEqual(['alice', 'bob'])
} }

View File

@@ -97,7 +97,10 @@ export interface ScheduledDispatchService {
cancelAdHocNotification(notificationId: string, cancelledAt?: Instant): Promise<void> cancelAdHocNotification(notificationId: string, cancelledAt?: Instant): Promise<void>
reconcileHouseholdBuiltInDispatches(householdId: string, asOf?: Instant): Promise<void> reconcileHouseholdBuiltInDispatches(householdId: string, asOf?: Instant): Promise<void>
reconcileAllBuiltInDispatches(asOf?: Instant): Promise<void> reconcileAllBuiltInDispatches(asOf?: Instant): Promise<void>
listDueDispatches(input?: { asOf?: Instant; limit?: number }): Promise<readonly ScheduledDispatchRecord[]> listDueDispatches(input?: {
asOf?: Instant
limit?: number
}): Promise<readonly ScheduledDispatchRecord[]>
getDispatchById(dispatchId: string): Promise<ScheduledDispatchRecord | null> getDispatchById(dispatchId: string): Promise<ScheduledDispatchRecord | null>
claimDispatch(dispatchId: string): Promise<boolean> claimDispatch(dispatchId: string): Promise<boolean>
releaseDispatch(dispatchId: string): Promise<void> releaseDispatch(dispatchId: string): Promise<void>