-- KosmoConnect Initial Schema -- Runs automatically when the TimescaleDB container starts for the first time -- Enable TimescaleDB extension CREATE EXTENSION IF NOT EXISTS timescaledb; -- ============================================================ -- Nodes Registry -- ============================================================ CREATE TABLE IF NOT EXISTS nodes ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), mesh_node_id TEXT UNIQUE NOT NULL, name TEXT, lat DOUBLE PRECISION, lon DOUBLE PRECISION, hardware_revision TEXT DEFAULT 'v1.0', installed_at TIMESTAMPTZ DEFAULT NOW(), last_seen TIMESTAMPTZ, is_active BOOLEAN DEFAULT true, metadata JSONB DEFAULT '{}' ); CREATE INDEX IF NOT EXISTS idx_nodes_mesh_node_id ON nodes(mesh_node_id); CREATE INDEX IF NOT EXISTS idx_nodes_last_seen ON nodes(last_seen); -- ============================================================ -- Environmental Readings (Time-series) -- ============================================================ CREATE TABLE IF NOT EXISTS enviro_readings ( time TIMESTAMPTZ NOT NULL, node_id TEXT NOT NULL REFERENCES nodes(mesh_node_id) ON DELETE CASCADE, temperature_c DOUBLE PRECISION, humidity_percent DOUBLE PRECISION, pressure_pa DOUBLE PRECISION, wind_speed_ms DOUBLE PRECISION, wind_direction SMALLINT, pm25_ugm3 DOUBLE PRECISION, pm10_ugm3 DOUBLE PRECISION, gas_resistance_kohm DOUBLE PRECISION, battery_voltage DOUBLE PRECISION, solar_voltage DOUBLE PRECISION ); -- Convert to hypertable for automatic time-based partitioning SELECT create_hypertable('enviro_readings', 'time', if_not_exists => TRUE); CREATE INDEX IF NOT EXISTS idx_enviro_node_id_time ON enviro_readings(node_id, time DESC); -- ============================================================ -- Mesh Messages (for gateway delivery tracking) -- ============================================================ CREATE TABLE IF NOT EXISTS mesh_messages ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), user_id UUID REFERENCES users(id) ON DELETE SET NULL, direction TEXT NOT NULL CHECK (direction IN ('inbound', 'outbound')), sender_node_id TEXT, target_node_id TEXT, gateway_node_id TEXT, text TEXT, status TEXT DEFAULT 'pending' CHECK (status IN ('pending', 'queued', 'transmitted', 'delivered', 'failed')), hop_count INTEGER, rssi INTEGER, snr DOUBLE PRECISION, created_at TIMESTAMPTZ DEFAULT NOW(), updated_at TIMESTAMPTZ DEFAULT NOW() ); CREATE INDEX IF NOT EXISTS idx_mesh_messages_status ON mesh_messages(status); CREATE INDEX IF NOT EXISTS idx_mesh_messages_target ON mesh_messages(target_node_id, created_at DESC); -- ============================================================ -- Users & Subscriptions (minimal schema for Phase 1/2) -- ============================================================ CREATE TABLE IF NOT EXISTS users ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), email TEXT UNIQUE NOT NULL, stripe_customer_id TEXT, created_at TIMESTAMPTZ DEFAULT NOW() ); CREATE TABLE IF NOT EXISTS subscriptions ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE, plan_type TEXT NOT NULL CHECK (plan_type IN ('free', 'wanderer', 'guardian', 'sanctuary')), stripe_subscription_id TEXT, message_quota INTEGER, messages_used INTEGER DEFAULT 0, valid_from TIMESTAMPTZ DEFAULT NOW(), valid_until TIMESTAMPTZ, is_active BOOLEAN DEFAULT true ); CREATE INDEX IF NOT EXISTS idx_subscriptions_user ON subscriptions(user_id); CREATE TABLE IF NOT EXISTS allowed_nodes ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE, mesh_node_id TEXT NOT NULL, nickname TEXT, created_at TIMESTAMPTZ DEFAULT NOW(), UNIQUE(user_id, mesh_node_id) );