feat: initial KosmoConnect platform v0.1
Some checks failed
CI / lint-docs (push) Has been cancelled
CI / build-firmware (push) Has been cancelled
CI / test-backend (push) Has been cancelled
CI / test-web (push) Has been cancelled

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:
2026-04-12 17:30:15 +02:00
commit 0a4fb7b55e
95 changed files with 9903 additions and 0 deletions

View 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)
);

View File

@@ -0,0 +1,20 @@
-- Seed test users and subscriptions for local gateway development
-- Run manually when setting up a fresh dev environment
INSERT INTO users (id, email)
VALUES
('11111111-1111-1111-1111-111111111111', 'test@kosmoconnect.local'),
('22222222-2222-2222-2222-222222222222', 'guardian@kosmoconnect.local')
ON CONFLICT (id) DO NOTHING;
INSERT INTO subscriptions (id, user_id, plan_type, message_quota, valid_from, valid_until, is_active)
VALUES
('aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa', '11111111-1111-1111-1111-111111111111', 'wanderer', 50, NOW(), NOW() + INTERVAL '1 year', true),
('bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb', '22222222-2222-2222-2222-222222222222', 'guardian', 500, NOW(), NOW() + INTERVAL '1 year', true)
ON CONFLICT DO NOTHING;
INSERT INTO allowed_nodes (id, user_id, mesh_node_id, nickname)
VALUES
('cccccccc-cccc-cccc-cccc-cccccccccccc', '22222222-2222-2222-2222-222222222222', '!a1b2c3d4', 'Base Camp'),
('dddddddd-dddd-dddd-dddd-dddddddddddd', '22222222-2222-2222-2222-222222222222', '!b2c3d4e5', 'Lookout')
ON CONFLICT DO NOTHING;

View File

@@ -0,0 +1,24 @@
-- BTCPay Server Billing Schema
CREATE TABLE IF NOT EXISTS btcpay_invoices (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
btcpay_invoice_id TEXT UNIQUE,
store_id TEXT NOT NULL,
plan_type TEXT NOT NULL CHECK (plan_type IN ('free', 'wanderer', 'guardian', 'sanctuary')),
amount DECIMAL(16, 8) NOT NULL,
currency TEXT NOT NULL DEFAULT 'USD',
status TEXT DEFAULT 'Pending' CHECK (status IN ('Pending', 'Processing', 'Settled', 'Invalid', 'Expired')),
checkout_url TEXT,
created_at TIMESTAMPTZ DEFAULT NOW(),
settled_at TIMESTAMPTZ,
metadata JSONB DEFAULT '{}'
);
CREATE INDEX IF NOT EXISTS idx_btcpay_invoices_user ON btcpay_invoices(user_id);
CREATE INDEX IF NOT EXISTS idx_btcpay_invoices_btcpay_id ON btcpay_invoices(btcpay_invoice_id);
-- Add btcpay metadata to subscriptions for traceability
ALTER TABLE subscriptions
ADD COLUMN IF NOT EXISTS btcpay_invoice_id TEXT,
ADD COLUMN IF NOT EXISTS payment_method TEXT;