sp3-verify-1770325830/.sdlc/features/websocket-chat/tasks.md
rdev-worker 42c1444274
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
build: /implement-feature websocket-chat --requirements 'GET /ws upgrades to...
2026-02-05 21:50:17 +00:00

7.8 KiB

Implementation Tasks: WebSocket Chat

Task Overview

ID Task Status Blocked By
WS-1 Extend config with Redis URL pending -
WS-2 Wire realtime components in main.go pending WS-1
WS-3 Mount WebSocket handler in routes.go pending WS-2
WS-4 Add WebSocket documentation to spec.go pending WS-3
WS-5 Add integration tests for WebSocket chat pending WS-3

WS-1: Extend config with Redis URL

Status: pending

Description

Add Redis URL configuration to the chat-api service config.

Files to Modify

  • services/chat-api/internal/config/config.go

Implementation Details

Add to the Config struct:

// Redis configuration for realtime pub/sub
RedisURL string

Add to the Load() function:

RedisURL: os.Getenv("REDIS_URL"),

Acceptance Criteria

  • Config struct has RedisURL field
  • Load() reads from REDIS_URL environment variable
  • Empty string is valid (disables Redis, uses local-only broadcast)

WS-2: Wire realtime components in main.go

Status: pending Blocked By: WS-1

Description

Initialize the realtime hub and Redis broadcaster in the service entry point.

Files to Modify

  • services/chat-api/cmd/server/main.go

Implementation Details

  1. Import required packages:
import (
    "context"
    "github.com/redis/go-redis/v9"
    "git.threesix.ai/jordan/sp3-verify-1770325830/pkg/realtime"
    "git.threesix.ai/jordan/sp3-verify-1770325830/services/chat-api/internal/config"
)
  1. Load config and create context:
cfg := config.Load()
ctx := context.Background()
  1. Create hub:
hub := realtime.NewHub(logger)
go hub.Run(ctx)
  1. Create Redis broadcaster (optional):
var broadcaster realtime.Broadcaster
if cfg.RedisURL != "" {
    opts, err := redis.ParseURL(cfg.RedisURL)
    if err != nil {
        logger.Error("invalid redis url", "error", err)
    } else {
        redisClient := redis.NewClient(opts)
        broadcaster = realtime.NewRedisBroadcaster(redisClient, hub, logger)
        go broadcaster.Run(ctx)
    }
}
  1. Pass hub and broadcaster to route registration:
api.RegisterRoutes(application, exampleService, hub, broadcaster)

Acceptance Criteria

  • Hub is created and running in a goroutine
  • Redis broadcaster is created when REDIS_URL is set
  • Redis broadcaster is nil when REDIS_URL is empty
  • Hub and broadcaster are passed to route registration

WS-3: Mount WebSocket handler in routes.go

Status: pending Blocked By: WS-2

Description

Update the route registration to accept realtime components and mount the WebSocket handler.

Files to Modify

  • services/chat-api/internal/api/routes.go

Implementation Details

  1. Update function signature:
func RegisterRoutes(
    application *app.App,
    exampleService *service.ExampleService,
    hub realtime.Hub,
    broadcaster realtime.Broadcaster,
) {
  1. Import realtime package:
import "git.threesix.ai/jordan/sp3-verify-1770325830/pkg/realtime"
  1. Create and configure WebSocket handler:
wsHandler := realtime.NewHandler(hub, logger, realtime.HandlerConfig{
    Broadcaster: broadcaster,
    OnConnect: func(conn realtime.Connection) {
        logger.Info("websocket client connected",
            "client_id", conn.ID(),
            "user_id", conn.UserID(),
        )
    },
    OnDisconnect: func(conn realtime.Connection) {
        logger.Info("websocket client disconnected",
            "client_id", conn.ID(),
        )
    },
    OnMessage: func(conn realtime.Connection, msg *realtime.Message) *realtime.Message {
        if msg.Type == "" {
            msg.Type = realtime.MessageTypeChat
        }
        return msg
    },
    AuthRequired: cfg.AuthEnabled,
})
  1. Mount WebSocket routes:
application.Mount("/api/chat-api/ws", wsHandler.Routes())

Acceptance Criteria

  • Function signature accepts hub and broadcaster parameters
  • WebSocket handler is created with appropriate callbacks
  • Handler is mounted at /api/chat-api/ws
  • Auth requirement respects AUTH_ENABLED config

WS-4: Add WebSocket documentation to spec.go

Status: pending Blocked By: WS-3

Description

Document the WebSocket endpoint in the OpenAPI specification.

Files to Modify

  • services/chat-api/internal/api/spec.go

Implementation Details

Add WebSocket tag:

spec.WithTag("WebSocket", "Real-time WebSocket endpoints")

Add WebSocket path documentation:

spec.AddPath("/api/chat-api/ws", "get", map[string]any{
    "summary":     "WebSocket connection",
    "description": "Upgrades to WebSocket for real-time chat. Messages are broadcast to all connected clients via Redis pub/sub.",
    "tags":        []string{"WebSocket"},
    "responses": map[string]any{
        "101": map[string]any{
            "description": "Switching Protocols - WebSocket upgrade successful",
        },
        "401": openapi.OpResponse("Unauthorized - authentication required", nil),
    },
})

spec.AddPath("/api/chat-api/ws/{room}", "get", map[string]any{
    "summary":     "WebSocket connection to room",
    "description": "Upgrades to WebSocket and joins the specified room. Messages are broadcast only to clients in the same room.",
    "tags":        []string{"WebSocket"},
    "parameters": []map[string]any{
        {
            "name":        "room",
            "in":          "path",
            "required":    true,
            "description": "Room identifier to join",
            "schema":      map[string]any{"type": "string"},
        },
    },
    "responses": map[string]any{
        "101": map[string]any{
            "description": "Switching Protocols - WebSocket upgrade successful",
        },
        "401": openapi.OpResponse("Unauthorized - authentication required", nil),
    },
})

Acceptance Criteria

  • WebSocket tag added to spec
  • /api/chat-api/ws endpoint documented
  • /api/chat-api/ws/{room} endpoint documented with room parameter
  • Response codes documented (101, 401)

WS-5: Add integration tests for WebSocket chat

Status: pending Blocked By: WS-3

Description

Add tests to verify WebSocket functionality.

Files to Create

  • services/chat-api/internal/api/handlers/websocket_test.go

Implementation Details

Test cases:

  1. Connection upgrade - Verify WebSocket upgrade succeeds
  2. Message broadcast - Verify messages sent by one client reach others
  3. Room isolation - Verify room messages don't leak to other rooms
  4. Ping/pong - Verify heartbeat mechanism

Example test structure:

package handlers_test

import (
    "net/http"
    "net/http/httptest"
    "testing"
    "time"

    "github.com/gorilla/websocket"
    "git.threesix.ai/jordan/sp3-verify-1770325830/pkg/logging"
    "git.threesix.ai/jordan/sp3-verify-1770325830/pkg/realtime"
)

func TestWebSocket_Connection(t *testing.T) {
    // Create hub
    hub := realtime.NewHub(logging.Nop())
    ctx, cancel := context.WithCancel(context.Background())
    defer cancel()
    go hub.Run(ctx)

    // Create handler
    handler := realtime.NewHandler(hub, logging.Nop(), realtime.HandlerConfig{})

    // Create test server
    server := httptest.NewServer(handler.Routes())
    defer server.Close()

    // Connect WebSocket
    wsURL := "ws" + server.URL[4:] + "/"
    conn, _, err := websocket.DefaultDialer.Dial(wsURL, nil)
    if err != nil {
        t.Fatalf("dial failed: %v", err)
    }
    defer conn.Close()

    // Verify connection count
    time.Sleep(50 * time.Millisecond)
    if hub.ConnectionCount() != 1 {
        t.Errorf("expected 1 connection, got %d", hub.ConnectionCount())
    }
}

Acceptance Criteria

  • Test WebSocket connection upgrade works
  • Test message broadcast to multiple clients
  • Test room-based message isolation
  • All tests pass