Skip to content

Commit d69a33d

Browse files
patrickrbclaude
andauthored
chore(db): add migration for missing propagation tables (#204)
The install-flow backfill marks 0000_baseline_canonical_schema as already-applied on existing installs without verifying every table exists. Pre-baseline installs are missing the four propagation tables that exist in drizzle/schema.ts. Add an idempotent 0002 migration to fill the gap. Tables created: - propagation_alerts - propagation_forecasts - solar_activity - user_propagation_preferences All CREATE TABLE / CREATE INDEX statements use IF NOT EXISTS; foreign keys are added inside DO blocks that check pg_constraint. Safe to re-run; no-op on installs that already have these tables. Tested against a postgres container loaded with a snapshot of the production schema (with drizzle.__drizzle_migrations populated to simulate the install-backfill state). All four tables + six indexes created; second run is a no-op. Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent d2c99e7 commit d69a33d

3 files changed

Lines changed: 3712 additions & 0 deletions

File tree

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
-- Adds the propagation feature tables that exist in drizzle/schema.ts but
2+
-- are missing from installs that were bootstrapped before the canonical
3+
-- baseline (or that ran the installer's "existing-install" backfill, which
4+
-- marks 0000_baseline_canonical_schema as already-applied without verifying
5+
-- every table exists).
6+
--
7+
-- Safe to run on:
8+
-- - existing installs missing these tables (creates them)
9+
-- - fresh installs that already got them from the baseline (no-op)
10+
-- - re-runs (no-op)
11+
--
12+
-- Each statement is idempotent: CREATE TABLE IF NOT EXISTS, CREATE INDEX IF
13+
-- NOT EXISTS, and DO-block guards around ADD CONSTRAINT.
14+
15+
CREATE TABLE IF NOT EXISTS "propagation_alerts" (
16+
"id" serial PRIMARY KEY NOT NULL,
17+
"user_id" integer NOT NULL,
18+
"alert_type" varchar(30) NOT NULL,
19+
"severity" varchar(10) NOT NULL,
20+
"title" varchar(255) NOT NULL,
21+
"message" text NOT NULL,
22+
"is_active" boolean DEFAULT true,
23+
"expires_at" timestamp,
24+
"created_at" timestamp DEFAULT CURRENT_TIMESTAMP,
25+
CONSTRAINT "propagation_alerts_alert_type_check" CHECK ((alert_type)::text = ANY ((ARRAY['solar_storm'::character varying, 'enhanced_propagation'::character varying, 'band_opening'::character varying, 'aurora'::character varying])::text[])),
26+
CONSTRAINT "propagation_alerts_severity_check" CHECK ((severity)::text = ANY ((ARRAY['low'::character varying, 'medium'::character varying, 'high'::character varying])::text[])),
27+
CONSTRAINT "valid_alert_type" CHECK ((alert_type)::text = ANY ((ARRAY['solar_storm'::character varying, 'enhanced_propagation'::character varying, 'band_opening'::character varying, 'aurora'::character varying])::text[])),
28+
CONSTRAINT "valid_severity" CHECK ((severity)::text = ANY ((ARRAY['low'::character varying, 'medium'::character varying, 'high'::character varying])::text[]))
29+
);
30+
--> statement-breakpoint
31+
CREATE TABLE IF NOT EXISTS "propagation_forecasts" (
32+
"id" serial PRIMARY KEY NOT NULL,
33+
"timestamp" timestamp NOT NULL,
34+
"forecast_for" timestamp NOT NULL,
35+
"band_conditions" jsonb NOT NULL,
36+
"general_conditions" varchar(20) NOT NULL,
37+
"notes" text,
38+
"source" varchar(50) NOT NULL,
39+
"created_at" timestamp DEFAULT CURRENT_TIMESTAMP,
40+
CONSTRAINT "propagation_forecasts_general_conditions_check" CHECK ((general_conditions)::text = ANY ((ARRAY['poor'::character varying, 'fair'::character varying, 'good'::character varying, 'excellent'::character varying])::text[])),
41+
CONSTRAINT "valid_general_conditions" CHECK ((general_conditions)::text = ANY ((ARRAY['poor'::character varying, 'fair'::character varying, 'good'::character varying, 'excellent'::character varying])::text[]))
42+
);
43+
--> statement-breakpoint
44+
CREATE TABLE IF NOT EXISTS "solar_activity" (
45+
"id" serial PRIMARY KEY NOT NULL,
46+
"timestamp" timestamp NOT NULL,
47+
"solar_flux_index" numeric(6, 2) NOT NULL,
48+
"a_index" numeric(5, 2) NOT NULL,
49+
"k_index" numeric(3, 1) NOT NULL,
50+
"solar_wind_speed" numeric(7, 2),
51+
"solar_wind_density" numeric(6, 3),
52+
"xray_class" varchar(5),
53+
"created_at" timestamp DEFAULT CURRENT_TIMESTAMP,
54+
"updated_at" timestamp DEFAULT CURRENT_TIMESTAMP,
55+
CONSTRAINT "solar_activity_timestamp_key" UNIQUE("timestamp")
56+
);
57+
--> statement-breakpoint
58+
CREATE TABLE IF NOT EXISTS "user_propagation_preferences" (
59+
"id" serial PRIMARY KEY NOT NULL,
60+
"user_id" integer NOT NULL,
61+
"home_grid_locator" varchar(10),
62+
"preferred_bands" text[],
63+
"alert_solar_storms" boolean DEFAULT true,
64+
"alert_enhanced_propagation" boolean DEFAULT true,
65+
"alert_band_openings" boolean DEFAULT false,
66+
"alert_aurora" boolean DEFAULT false,
67+
"update_interval_minutes" integer DEFAULT 60,
68+
"created_at" timestamp DEFAULT CURRENT_TIMESTAMP,
69+
"updated_at" timestamp DEFAULT CURRENT_TIMESTAMP,
70+
CONSTRAINT "user_propagation_preferences_user_id_key" UNIQUE("user_id"),
71+
CONSTRAINT "user_propagation_preferences_update_interval_minutes_check" CHECK (update_interval_minutes >= 15)
72+
);
73+
--> statement-breakpoint
74+
DO $$
75+
BEGIN
76+
IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'propagation_alerts_user_id_fkey') THEN
77+
ALTER TABLE "propagation_alerts" ADD CONSTRAINT "propagation_alerts_user_id_fkey"
78+
FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE cascade ON UPDATE no action;
79+
END IF;
80+
IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'user_propagation_preferences_user_id_fkey') THEN
81+
ALTER TABLE "user_propagation_preferences" ADD CONSTRAINT "user_propagation_preferences_user_id_fkey"
82+
FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE cascade ON UPDATE no action;
83+
END IF;
84+
END $$;
85+
--> statement-breakpoint
86+
CREATE INDEX IF NOT EXISTS "idx_propagation_alerts_active" ON "propagation_alerts" USING btree ("is_active" bool_ops) WHERE (is_active = true);
87+
--> statement-breakpoint
88+
CREATE INDEX IF NOT EXISTS "idx_propagation_alerts_expires" ON "propagation_alerts" USING btree ("expires_at" timestamp_ops) WHERE (expires_at IS NOT NULL);
89+
--> statement-breakpoint
90+
CREATE INDEX IF NOT EXISTS "idx_propagation_alerts_user_id" ON "propagation_alerts" USING btree ("user_id" int4_ops);
91+
--> statement-breakpoint
92+
CREATE INDEX IF NOT EXISTS "idx_propagation_forecasts_forecast_for" ON "propagation_forecasts" USING btree ("forecast_for" timestamp_ops);
93+
--> statement-breakpoint
94+
CREATE INDEX IF NOT EXISTS "idx_propagation_forecasts_timestamp" ON "propagation_forecasts" USING btree ("timestamp" timestamp_ops);
95+
--> statement-breakpoint
96+
CREATE INDEX IF NOT EXISTS "idx_solar_activity_timestamp" ON "solar_activity" USING btree ("timestamp" timestamp_ops);

0 commit comments

Comments
 (0)