feat: initial KosmoConnect platform v0.1
Includes: - Backend services: ingestion (:8001), weather API (:8002), gateway (:8003), billing (:8004) with BTCPay integration - Shared asyncpg pool, TimescaleDB hypertable, Redis, Mosquitto MQTT - React frontend: Dashboard (MapLibre) and Messaging (chat UI) - Bridge daemon for Pi + Meshtastic (Serial/TCP T-Deck support) - Production Docker Compose, Nginx reverse proxy, ops scripts - DEPLOY.md with step-by-step deployment guide
This commit is contained in:
102
backend/migrations/001_initial_schema.sql
Normal file
102
backend/migrations/001_initial_schema.sql
Normal file
@@ -0,0 +1,102 @@
|
||||
-- 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)
|
||||
);
|
||||
Reference in New Issue
Block a user