clawbot 8bb083a7f8 Add project scaffolding with fx DI, SQLite migrations, and healthcheck
- go.mod with git.eeqj.de/sneak/chat module
- internal packages: globals, logger, config, db, healthcheck, middleware, handlers, server
- SQLite database with embedded migration system (schema_migrations tracking)
- Migration 001: schema_migrations table
- Migration 002: channels table
- Config with chat-specific vars (MAX_HISTORY, SESSION_TIMEOUT, MAX_MESSAGE_SIZE, MOTD, SERVER_NAME, FEDERATION_KEY)
- Healthcheck endpoint at /.well-known/healthcheck.json
- Makefile, .gitignore
- cmd/chatd/main.go entry point
2026-02-09 12:22:28 -08:00

chat

A modern IRC-inspired chat server written in Go. Decouples session state from transport connections, enabling mobile-friendly persistent sessions over HTTP.

Motivation

IRC is in decline because session state is tied to the TCP connection. In a mobile-first world, that's a nonstarter. Not everyone wants to run a bouncer or pay for IRCCloud.

This project builds a chat server that:

  • Holds session state server-side (message queues, presence, channel membership)
  • Delivers messages over HTTP (JSON-RPC style)
  • Supports multiple concurrent connections per user session
  • Provides IRC-like semantics: channels, nicks, topics, modes
  • Uses structured JSON messages with arbitrary extensibility

Architecture

Transport: HTTP only

All client↔server and server↔server communication uses HTTP/1.1+ with JSON request/response bodies. No WebSockets, no raw TCP, no gRPC — just plain HTTP.

  • Client polling: Clients poll for new messages via GET with long-polling support (server holds the connection open until messages arrive or timeout)
  • Client sending: Clients send messages/commands via POST
  • Server federation: Servers exchange messages via HTTP to enable multi-server networks (like IRC server linking)

Core Concepts

Users

  • Identified by a unique user ID (UUID)
  • Authenticate via token (issued at registration or login)
  • Have a nick (changeable, unique per server at any point in time)
  • Maintain a persistent message queue on the server

Sessions

  • A session represents an authenticated user's connection context
  • Session state is server-held, not connection-bound
  • Multiple devices can share a session (messages delivered to all)
  • Sessions persist across disconnects — messages queue until retrieved
  • Sessions expire after a configurable idle timeout (default 24h)

Channels

  • Named with # prefix (e.g. #general)
  • Have a topic, mode flags, and member list
  • Messages to a channel are queued for all members
  • Channel history is stored server-side (configurable depth)
  • No eternal logging by default — history rotates

Messages

Every message is a structured JSON object:

{
  "id": "550e8400-e29b-41d4-a716-446655440000",
  "ts": "2026-02-09T20:00:00.000Z",
  "from": "nick",
  "to": "#channel",
  "type": "message",
  "body": "Hello, world!",
  "meta": {}
}

Fields:

  • id — Server-assigned UUID, globally unique
  • ts — Server-assigned timestamp (ISO 8601)
  • from — Sender nick
  • to — Destination: channel name (#foo) or nick (for DMs)
  • type — Message type: message, action, notice, join, part, quit, topic, mode, nick, system
  • body — Message content (UTF-8 text)
  • meta — Arbitrary extensible metadata (JSON object). Can carry:
    • Cryptographic signatures
    • Rich content hints (URLs, embeds)
    • Client-specific extensions
    • Reactions, edits, threading references

API Endpoints

All endpoints accept and return application/json.

Authentication

POST /api/v1/register      — Create account (nick, password) → token
POST /api/v1/login          — Authenticate → token
POST /api/v1/logout         — Invalidate token

Session & Messages

GET  /api/v1/messages       — Retrieve queued messages (long-poll supported)
                               Query params: ?after=<message-id>&timeout=30
POST /api/v1/messages       — Send a message or command
GET  /api/v1/history        — Retrieve channel/DM history
                               Query params: ?target=#channel&before=<id>&limit=50

Channels

GET  /api/v1/channels                — List joined channels
POST /api/v1/channels/join           — Join a channel
POST /api/v1/channels/part           — Leave a channel
GET  /api/v1/channels/{name}         — Channel info (topic, members, modes)
POST /api/v1/channels/{name}/topic   — Set channel topic

Users

GET  /api/v1/users/me        — Current user info
POST /api/v1/users/nick      — Change nick
GET  /api/v1/users/{nick}    — User info (online status, idle time)

Server Info

GET  /api/v1/server          — Server info (name, version, MOTD, user count)
GET  /.well-known/healthcheck.json  — Health check

Federation (Server-to-Server)

Servers can link to form a network, similar to IRC server linking:

POST /api/v1/federation/link     — Establish server link (mutual auth via shared key)
POST /api/v1/federation/relay    — Relay messages between linked servers
GET  /api/v1/federation/status   — Link status

Federation uses the same HTTP+JSON transport. Messages are relayed between servers so users on different servers can share channels.

Channel Modes

Inspired by IRC but simplified:

Mode Meaning
+i Invite-only
+m Moderated (only voiced users can send)
+s Secret (hidden from channel list)
+t Topic locked (only ops can change)
+n No external messages

User channel modes: +o (operator), +v (voice)

Configuration

Via environment variables (Viper), following gohttpserver conventions:

Variable Default Description
PORT 8080 Listen port
DBURL "" SQLite/Postgres connection string
DEBUG false Debug mode
MAX_HISTORY 10000 Max messages per channel history
SESSION_TIMEOUT 86400 Session idle timeout (seconds)
MAX_MESSAGE_SIZE 4096 Max message body size (bytes)
MOTD "" Message of the day
SERVER_NAME hostname Server display name
FEDERATION_KEY "" Shared key for server linking

Storage

SQLite by default (single-file, zero-config), with Postgres support for larger deployments. Tables:

  • users — accounts and auth tokens
  • channels — channel metadata and modes
  • channel_members — membership and user modes
  • messages — message history (rotated per MAX_HISTORY)
  • message_queue — per-user pending delivery queue
  • server_links — federation peer configuration

Project Structure

Following gohttpserver CONVENTIONS.md:

chat/
├── cmd/
│   └── chat/
│       └── main.go
├── internal/
│   ├── config/
│   │   └── config.go
│   ├── database/
│   │   └── database.go
│   ├── globals/
│   │   └── globals.go
│   ├── handlers/
│   │   ├── handlers.go
│   │   ├── auth.go
│   │   ├── channels.go
│   │   ├── federation.go
│   │   ├── healthcheck.go
│   │   ├── messages.go
│   │   └── users.go
│   ├── healthcheck/
│   │   └── healthcheck.go
│   ├── logger/
│   │   └── logger.go
│   ├── middleware/
│   │   └── middleware.go
│   ├── models/
│   │   ├── channel.go
│   │   ├── message.go
│   │   └── user.go
│   ├── queue/
│   │   └── queue.go
│   └── server/
│       ├── server.go
│       ├── http.go
│       └── routes.go
├── go.mod
├── go.sum
├── Makefile
├── Dockerfile
├── CONVENTIONS.md → (copy from gohttpserver)
└── README.md

Required Libraries

Per gohttpserver conventions:

Purpose Library
DI go.uber.org/fx
Router github.com/go-chi/chi
Logging log/slog (stdlib)
Config github.com/spf13/viper
Env github.com/joho/godotenv/autoload
CORS github.com/go-chi/cors
Metrics github.com/prometheus/client_golang
DB modernc.org/sqlite + database/sql

Design Principles

  1. HTTP is the only transport — no WebSockets, no raw TCP, no protocol negotiation. HTTP is universal, proxy-friendly, and works everywhere.
  2. Server holds state — clients are stateless. Reconnect, switch devices, lose connectivity — your messages are waiting.
  3. Structured messages — JSON with extensible metadata. Enables signatures, rich content, client extensions without protocol changes.
  4. Simple deployment — single binary, SQLite default, zero mandatory external dependencies.
  5. No eternal logs — history rotates. Chat should be ephemeral by default.
  6. Federation optional — single server works standalone. Linking is opt-in.

Status

Design phase. This README is the spec. Implementation has not started.

License

MIT

Description
No description provided
Readme 1.2 MiB
Languages
Go 86.1%
JavaScript 9.1%
CSS 2.8%
Dockerfile 0.7%
Makefile 0.7%
Other 0.6%