fix: bootstrap campaigns.type column on startup#472
Conversation
The welcome campaign cron's resolveRecipients anti-join references `c.type = 'automated'` but pre-existing campaigns tables (created before the type column landed) hit `column "type" does not exist` and the scheduled run aborts. Add ensureCampaignTypeColumn that runs the idempotent ALTER TABLE on startup, gate /api with SCHEMA_NOT_READY when the migration fails, and run it before ensureCampaignPartialIndexes since the partial index predicate also depends on the column.
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: ASSERTIVE Plan: Pro Run ID: 📒 Files selected for processing (4)
📝 WalkthroughWalkthroughThis PR introduces ChangesCampaign Type Column Migration
Sequence DiagramsequenceDiagram
participant Server as Server Startup
participant Routes as registerRoutes()
participant EnsureFn as ensureCampaignTypeColumn()
participant DB as Database
Server->>Routes: Initialize routes
Routes->>EnsureFn: Call ensureCampaignTypeColumn()
EnsureFn->>DB: ALTER TABLE campaigns ADD COLUMN IF NOT EXISTS type...
alt Column creation succeeds
DB-->>EnsureFn: Success
EnsureFn-->>Routes: Return true
Routes->>Routes: Column ready ✓
Routes-->>Server: Routes registered
else Column creation fails
DB-->>EnsureFn: Error
EnsureFn->>EnsureFn: Log error, return false
EnsureFn-->>Routes: Return false
Routes->>Routes: Add "campaigns.type column" to criticalSchemaFailures
Routes-->>Server: All /api/* return 503 SCHEMA_NOT_READY
end
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes Possibly related PRs
Suggested labels
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Review rate limit: 4/5 reviews remaining, refill in 12 minutes. Comment |
Summary
The scheduled welcome campaign was crashing every run with
column "c.type" does not exist, blocking the only enabled automated campaign. Thecampaigns.typecolumn was added toshared/schema.tswhen automated campaigns shipped, but production databases provisioned before that change never got the column — and there was noensureXxx()migration to backfill it on boot.This PR adds the missing migration.
ensureCampaignTypeColumn()runs an idempotentALTER TABLE campaigns ADD COLUMN IF NOT EXISTS type TEXT NOT NULL DEFAULT 'manual'on every startup, gates/apibehind the existingSCHEMA_NOT_READY503 if the migration fails, and runs beforeensureCampaignPartialIndexesbecause the partial-index predicate (WHERE type = 'automated') also depends on the column.Changes
server/services/ensureTables.tsensureCampaignTypeColumn()exported function. Returnstrueon success,false(withconsole.error) on failure — same shape as the other column-bootstrap helpers (ensureMonitorHealthColumns,ensureMonitorPendingRetryColumn,ensureNotificationQueueColumns).server/routes.tsensureMonitorChangesIndexes()and beforeensureCampaignPartialIndexes()."campaigns.type column"ontocriticalSchemaFailuresif the migration returnsfalse, so a failed migration triggers the existing global/api → 503 SCHEMA_NOT_READYgate rather than letting the cron silently throwcolumn "c.type" does not existon every tick.server/services/ensureTables.test.tsensureCampaignTypeColumn:true.ALTER TABLE campaigns,ADD COLUMN IF NOT EXISTS,type,'manual',NOT NULL).falseand logs the expected error message when ALTER TABLE rejects.server/routes.conditions.test.tsensureCampaignTypeColumn: vi.fn().mockResolvedValue(true)to the./services/ensureTablesmock so the routes module under test can be loaded without unmocked exports throwing.How to test
Reproduce on a stale schema (skip if you trust the unit tests):
Then boot the server. With
mainas it stands, the next welcome cron tick logserror: column c.type does not existand the campaign aborts. With this PR, startup runs the ALTER, the column is restored with default'manual', and the cron run completes (or returns{ skipped: true }if no recipients).Verify idempotency: Boot the server twice in a row against a DB that already has the column. The
ALTER TABLE ... ADD COLUMN IF NOT EXISTSis a no-op the second time and emits no warnings.Verify the failure gate: Temporarily simulate a permission error (e.g. revoke ALTER privileges from the connection role). Boot the server.
/api/*should return503 { code: "SCHEMA_NOT_READY", message: "Schema not ready: …, campaigns.type column" }.Run the suite:
npm run check npm run testAll 2485 tests pass locally, including the 3 new
ensureCampaignTypeColumncases and the existingpartial-index-invariantsparity tests.Confirm ordering:
ensureCampaignTypeColumnis called beforeensureCampaignPartialIndexesinserver/routes.ts:120-124. If you swap the order back, the partial-indexCREATE INDEX … WHERE type = 'automated'will fail on stale schemas (silently, because that helper onlyconsole.warns on error).Generated by Claude Code
Summary by CodeRabbit
New Features
Tests