Commit Graph

6 Commits

Author SHA1 Message Date
b1fd2f1b96 Replace string-matching error detection with typed SQLite errors (closes #39) (#66)
All checks were successful
check / check (push) Successful in 4s
## Summary

Replaces fragile `strings.Contains(err.Error(), "UNIQUE")` checks with typed error detection using `errors.As` and the SQLite driver's `*sqlite.Error` type.

## Changes

- **`internal/db/errors.go`** (new): Adds `IsUniqueConstraintError(err)` helper that uses `errors.As` to unwrap the error into `*sqlite.Error` and checks for `SQLITE_CONSTRAINT_UNIQUE` (code 2067).
- **`internal/handlers/api.go`**: Replaces two `strings.Contains(err.Error(), "UNIQUE")` calls with `db.IsUniqueConstraintError(err)` — in `handleCreateSessionError` and `executeNickChange`.
- **`internal/handlers/auth.go`**: Replaces one `strings.Contains(err.Error(), "UNIQUE")` call with `db.IsUniqueConstraintError(err)` — in `handleRegisterError`.

## Why

String matching on error messages is fragile — if the SQLite driver changes its error message format, the detection silently breaks. Using `errors.As` with the driver's typed error and checking the specific SQLite error code is robust, idiomatic Go, and immune to message format changes.

closes #39

<!-- session: agent:sdlc-manager:subagent:3fb0b8e2-d635-4848-a5bd-131c5033cdb1 -->

Co-authored-by: user <user@Mac.lan guest wan>
Co-authored-by: Jeffrey Paul <sneak@noreply.example.org>
Reviewed-on: #66
Co-authored-by: clawbot <clawbot@noreply.example.org>
Co-committed-by: clawbot <clawbot@noreply.example.org>
2026-03-10 11:54:27 +01:00
f287fdf6d1 fix: replay channel state on SPA reconnect (#61)
All checks were successful
check / check (push) Successful in 4s
## Summary

When closing and reopening the SPA, channel tabs were not restored because the client relied on localStorage to remember joined channels and re-sent JOIN commands on reconnect. This was fragile and caused spurious JOIN broadcasts to other channel members.

## Changes

### Server (`internal/handlers/api.go`, `internal/handlers/auth.go`)

- **`replayChannelState()`** — new method that enqueues synthetic JOIN messages plus join-numerics (332 TOPIC, 353 NAMES, 366 ENDOFNAMES) for every channel the session belongs to, targeted only at the specified client (no broadcast to other users).
- **`HandleState`** — accepts `?replay=1` query parameter to trigger channel state replay when the SPA reconnects.
- **`handleLogin`** — also calls `replayChannelState` after password-based login, since `LoginUser` creates a new client for an existing session.

### SPA (`web/src/app.jsx`, `web/dist/app.js`)

- On resume, calls `/state?replay=1` instead of `/state` so the server enqueues channel state into the message queue.
- `processMessage` now creates channel tabs when receiving a JOIN where `msg.from` matches the current nick (handles both live joins and replayed joins on reconnect).
- `onLogin` no longer re-sends JOIN commands for saved channels on resume — the server handles it via the replay mechanism, avoiding spurious JOIN broadcasts.

## How It Works

1. SPA loads, finds saved token in localStorage
2. Calls `GET /api/v1/state?replay=1` — server validates token and enqueues synthetic JOIN + TOPIC + NAMES for all session channels into the client's queue
3. `onLogin(nick, true)` sets `loggedIn = true` and requests MOTD (no re-JOIN needed)
4. Poll loop starts, picks up replayed channel messages
5. `processMessage` handles the JOIN messages, creating tabs and refreshing members/topics naturally

closes #60

Co-authored-by: user <user@Mac.lan guest wan>
Co-authored-by: clawbot <clawbot@noreply.git.eeqj.de>
Co-authored-by: Jeffrey Paul <sneak@noreply.example.org>
Reviewed-on: #61
Co-authored-by: clawbot <clawbot@noreply.example.org>
Co-committed-by: clawbot <clawbot@noreply.example.org>
2026-03-10 11:08:13 +01:00
f8f0b6afbb refactor: replace HTTP error codes with IRC numeric replies (#56)
All checks were successful
check / check (push) Successful in 58s
## Summary

Refactors all IRC command handlers to respond with proper IRC numeric replies via the message queue instead of HTTP status codes.

HTTP error codes are now reserved exclusively for transport-level concerns:
- **401** — missing/invalid auth token
- **400** — malformed JSON, empty command
- **500** — server errors

## IRC Numerics Implemented

### Success replies (delivered via message queue on success):
- **001 RPL_WELCOME** — sent on session creation and login
- **331 RPL_NOTOPIC** — channel has no topic (on JOIN)
- **332 RPL_TOPIC** — channel topic (on JOIN, TOPIC set)
- **353 RPL_NAMREPLY** — channel member list (on JOIN)
- **366 RPL_ENDOFNAMES** — end of NAMES list (on JOIN)
- **375/372/376** — MOTD (already existed)

### Error replies (delivered via message queue instead of HTTP 4xx):
- **401 ERR_NOSUCHNICK** — DM target not found (was HTTP 404)
- **403 ERR_NOSUCHCHANNEL** — channel not found / invalid name (was HTTP 404)
- **421 ERR_UNKNOWNCOMMAND** — unrecognized command (was HTTP 400)
- **432 ERR_ERRONEUSNICKNAME** — invalid nick format (was HTTP 400)
- **433 ERR_NICKNAMEINUSE** — nick taken (was HTTP 409)
- **442 ERR_NOTONCHANNEL** — not a member of channel (was HTTP 403)
- **461 ERR_NEEDMOREPARAMS** — missing required fields (was HTTP 400)

## Database Changes
- Added `params` column to messages table for IRC-style parameters
- Added `Params` field to `IRCMessage` struct
- Updated `InsertMessage` to accept params

## Test Updates
- All existing tests updated to expect HTTP 200 + IRC numerics
- New tests: `TestWelcomeNumeric`, `TestJoinNumerics`

## Client Impact
- CLI and SPA already handle unknown numerics via default event handlers
- PRIVMSG/NOTICE success changed from HTTP 201 to HTTP 200

closes #54

Co-authored-by: clawbot <clawbot@noreply.git.eeqj.de>
Co-authored-by: Jeffrey Paul <sneak@noreply.example.org>
Reviewed-on: #56
Co-authored-by: clawbot <clawbot@noreply.example.org>
Co-committed-by: clawbot <clawbot@noreply.example.org>
2026-03-09 22:21:30 +01:00
user
52c85724a7 fix: remove unused //nolint:gosec directives on password fields
All checks were successful
check / check (push) Successful in 2m4s
2026-02-28 10:33:59 -08:00
7047167dc8 Add tests for register and login endpoints
Some checks failed
check / check (push) Failing after 1m39s
2026-02-27 05:00:51 -08:00
b8794c2587 Add register and login HTTP handlers 2026-02-27 04:55:31 -08:00