Files
dsutanto 1c7b78b580
CI / lint (push) Canceled after 0s
CI / test (push) Canceled after 0s
CI / build (push) Canceled after 0s
ai-dev: graceful shutdown, tests, stdlib context, lint/CI, error logging
- T1: SIGTERM/SIGINT → client.Disconnect(250) → os.Exit(0)
- T2: 11 tests with miniredis mock (handleMessage, getEnv, parseInt, randomString)
- T3: golang.org/x/net/context → stdlib context, math/rand → math/rand/v2
- T4: .golangci.yml + .github/workflows/ci.yml
- T5: Redis SETEX error log includes key and memberID
- Dockerfile: add git for go mod tidy
2026-09-03 12:52:21 +07:00

2.7 KiB

SPEC.md — MQTT Presence Redis Go

§G Goal

MQTT presence bridge: subscribe MQTT topic, parse memberID, store presence JSON in Redis with TTL. Port of Django original for Backone platform.

§C Constraints

  • Single binary, single file (mqtt_presence_redis_go.go)
  • Go 1.24.3
  • All config via env vars, no config files
  • Redis 6+ required (SETEX)
  • MQTT 3.1.1 via paho client
  • Docker deployment (multi-stage: golang:1.24-alpine → alpine:3.20)
  • No graceful shutdown — blocks forever (select {})
  • No tests, no CI, no linter

§I Interfaces

id type detail
I.mqtt input Subscribe MQTT_TOPIC_PRESENCE (default presence), QoS 0, KeepAlive 30s, CleanSession true, AutoReconnect true (paho defaults)
I.redis output SETEX presence:<memberID> with JSON {mqtt, ts}, TTL MQTT_REDIS_SETEX seconds
I.env config MQTT_HOST, MQTT_PORT, MQTT_USER, MQTT_PASS, MQTT_TOPIC_PRESENCE, MQTT_REDIS_HOST, MQTT_REDIS_PORT, MQTT_REDIS_DB, MQTT_REDIS_PREFIX, MQTT_REDIS_SETEX, MQTT_REDIS_PASSWORD

§V Invariants

id invariant
V1 memberID extracted as first ;-delimited segment of MQTT payload
V2 memberID truncated to 50 chars max
V3 Redis key format: <MQTT_REDIS_PREFIX>:<memberID> (default presence:<memberID>)
V4 Redis value: JSON {mqtt: <raw_payload>, ts: <unix_timestamp>}
V5 Redis TTL: MQTT_REDIS_SETEX seconds (default 86400 = 24h)
V6 MQTT client ID: mqtt_presence_redis_go_<random10> — random per restart
V7 Startup fails fast (log.Fatalf) if Redis ping or MQTT connect fails
V8 On MQTT connect/reconnect: auto-subscribe to presence topic
V9 Empty payload → memberID = "" → Redis key presence: → write still occurs (current behavior, not early-return)
V10 Each MQTT message OVERWRITES prior Redis value for same memberID — no append, no merge
V11 MQTT paho defaults apply: KeepAlive=30s, CleanSession=true, AutoReconnect=true
V12 Malformed env var (non-numeric MQTT_REDIS_DB/MQTT_REDIS_SETEX) → parseInt returns 0 silently

§T Tasks

id status desc cites
T1 x Add graceful shutdown (SIGTERM → disconnect MQTT → exit) V8
T2 x Add tests for handleMessage parsing logic V1,V2,V3,V4,V9,V10
T3 x Replace golang.org/x/net/context with stdlib context —
T4 x Add golangci-lint config and CI pipeline —
T5 x Add error metrics/logging for Redis SETEX failures —

§B Bugs

| id | date | cause | fix | | B1 | 2026-09-03 | V9 stated "empty payload → no write" but strings.Split("", ";") returns [""] (len 1) — guard never fires. Empty payload writes to Redis. | V9 corrected |