Skip to content

Commit 47c52e1

Browse files
authored
Merge pull request #83 from Cod-e-Codes/feat/server-multi-db-durable-state
feat(server): multi-DB backends with durable reactions/read receipts/channel state
2 parents 66d7d47 + 6d287a8 commit 47c52e1

17 files changed

Lines changed: 1035 additions & 183 deletions

ARCHITECTURE.md

Lines changed: 30 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,9 @@ Marchat is a self-hosted, terminal-based chat application built in Go with a cli
2020
▼ ▼ ▼
2121
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
2222
│ Configuration │ │ Shared Types │ │ Database Layer │
23-
│ • Profiles │ │ • Message Types │ │ • SQLite
24-
│ • Encryption │ │ • Crypto Utils │ │ • Persistence
25-
│ • Themes │ │ • Protocols │ │ • State Mgmt
23+
│ • Profiles │ │ • Message Types │ │ • SQL Backends
24+
│ • Encryption │ │ • Crypto Utils │ │ • Postgres/MySQL
25+
│ • Themes │ │ • Protocols │ │ • Dialect State
2626
└─────────────────┘ └─────────────────┘ └─────────────────┘
2727
```
2828

@@ -108,7 +108,7 @@ The server package contains the core server logic and components that are used b
108108
#### Core Components
109109

110110
- **WebSocket Handlers**: Connection management and message routing
111-
- **Database Layer**: SQLite integration with message persistence
111+
- **Database Layer**: Pluggable SQL backends (SQLite/PostgreSQL/MySQL) with dialect-aware schema and query helpers
112112
- **Admin Interfaces**: Both TUI and web-based administrative panels
113113
- **Plugin Integration**: Plugin command handling and execution
114114
- **Health Monitoring**: System metrics and health check endpoints
@@ -201,15 +201,15 @@ The **client** stores `config.json`, `profiles.json`, keystore (unless legacy `k
201201
#### Key Settings (server-oriented)
202202

203203
- **Server Configuration**: Port, TLS certificates, admin authentication
204-
- **Database Settings**: SQLite file path and connection parameters
204+
- **Database Settings**: `MARCHAT_DB_PATH` selects backend and connection details (SQLite path or Postgres/MySQL DSN)
205205
- **Plugin Configuration**: Registry URL and installation directories
206206
- **Security Settings**: Admin keys, encryption keys, and authentication
207207
- **File Transfer**: Size limits and allowed file types
208208
- **Logging**: Log levels and output destinations
209209

210210
### Diagnostics (`internal/doctor`)
211211

212-
Shared package invoked by **`marchat-client`** and **`marchat-server`** when passed **`-doctor`** (human-readable report) or **`-doctor-json`** (JSON on stdout). It summarizes Go/OS, resolved config directories, known `MARCHAT_*` variables with secrets masked, role-specific checks (client: profiles, clipboard, TTY; server: `.env`, validation, DB/TLS/sqlite ping), and optionally compares the embedded version to the latest GitHub release. Set **`MARCHAT_DOCTOR_NO_NETWORK=1`** to skip the release check (e.g. air-gapped environments).
212+
Shared package invoked by **`marchat-client`** and **`marchat-server`** when passed **`-doctor`** (human-readable report) or **`-doctor-json`** (JSON on stdout). It summarizes Go/OS, resolved config directories, known `MARCHAT_*` variables with secrets masked, role-specific checks (client: profiles, clipboard, TTY; server: `.env`, validation, detected DB dialect, DB connection-string format validation, DB/TLS ping checks), and optionally compares the embedded version to the latest GitHub release. Set **`MARCHAT_DOCTOR_NO_NETWORK=1`** to skip the release check (e.g. air-gapped environments).
213213

214214
### Command Line Tools (`cmd/`)
215215

@@ -235,11 +235,26 @@ Both **`marchat-client`** and **`marchat-server`** embed diagnostics via **`inte
235235

236236
```
237237
Client: Input → Encrypt → WebSocket Send
238-
Server: WebSocket Receive → Hub → Plugin Processing → Database
239-
Server: Database → Hub → WebSocket Broadcast
238+
Server: WebSocket Receive → Hub → Plugin Processing → SQL Backend
239+
Server: SQL Backend → Hub → WebSocket Broadcast
240240
Client: WebSocket Receive → Decrypt → Display
241241
```
242242

243+
### Database Backends and Durability
244+
245+
- The server chooses a backend at runtime using `MARCHAT_DB_PATH`:
246+
- SQLite path (default local setup)
247+
- PostgreSQL DSN (`postgres://` / `postgresql://`)
248+
- MySQL DSN (`mysql:` / `mysql://`)
249+
- Schema creation and upsert/insert-ignore SQL are dialect-aware.
250+
- Placeholder rebinding keeps shared query callsites portable across backends.
251+
- SQLite-specific optimizations (for example WAL mode) are applied only when the selected backend is SQLite.
252+
- Durable state includes:
253+
- message history
254+
- reactions
255+
- read receipts
256+
- last channel per user
257+
243258
### WebSocket Protocol
244259

245260
The WebSocket communication uses JSON messages with the following structure:
@@ -304,14 +319,15 @@ CREATE TABLE ban_history (
304319

305320
### Key Features
306321

307-
- **WAL Mode**: Write-Ahead Logging for better concurrency and crash recovery
308-
- **Database Files**: Creates three files - `marchat.db` (main), `marchat.db-wal` (write-ahead log), `marchat.db-shm` (shared memory)
322+
- **Backend Selection**: `MARCHAT_DB_PATH` chooses SQLite/PostgreSQL/MySQL at runtime
323+
- **WAL Mode (SQLite only)**: Write-Ahead Logging for better concurrency and crash recovery when SQLite is selected
324+
- **SQLite Database Files**: `marchat.db` (main), `marchat.db-wal` (write-ahead log), `marchat.db-shm` (shared memory)
309325
- **Message ID Tracking**: Sequential message IDs for user state management
310326
- **Encryption Support**: Binary storage for encrypted message data
311327
- **Performance Indexes**: Optimized queries for message retrieval and user state
312328
- **Message Cap**: Automatic cleanup maintaining 1000 most recent messages
313329
- **Ban History**: Comprehensive tracking of user moderation actions
314-
- **Performance Tuning**: Optimized SQLite settings for chat workloads
330+
- **Performance Tuning**: Backend-aware optimizations (SQLite pragmas when SQLite is selected)
315331

316332
## Administrative Interfaces
317333

@@ -384,13 +400,13 @@ The web-based interface provides the same functionality through a browser:
384400

385401
### Database Optimization
386402

387-
- **WAL Mode**: Write-Ahead Logging enabled for improved concurrency and performance
403+
- **WAL Mode (SQLite only)**: Write-Ahead Logging enabled for improved concurrency and performance
388404
- **Indexed Queries**: Performance indexes on frequently queried columns
389405
- **Batch Operations**: Efficient bulk message operations
390406
- **Connection Reuse**: Persistent database connections
391407
- **Query Optimization**: Prepared statements for common operations
392-
- **Performance Tuning**: Optimized SQLite pragmas for chat workloads
393-
- **Backup Considerations**: WAL mode creates additional files; backups may miss recent uncommitted data if taken while server is running
408+
- **Performance Tuning**: SQLite-specific pragmas are applied only on SQLite; Postgres/MySQL use driver/backend defaults
409+
- **Backup Considerations**: SQLite WAL mode creates additional files; backups may miss recent uncommitted data if taken while server is running
394410

395411
## Development Patterns
396412

PROTOCOL.md

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -158,9 +158,10 @@ Messages initiated by the server to update client state.
158158
- On user connect/disconnect:
159159
- Broadcasts updated user list.
160160
- On message send:
161-
- Persists eligible messages to SQLite.
161+
- Persists eligible messages to the configured SQL backend selected by `MARCHAT_DB_PATH` (SQLite path, PostgreSQL DSN, or MySQL DSN).
162162
- Delivers to all connected clients **or** only to members of a channel when `channel` is non-empty and `sender` is not `System` (see [Channels](#channels)). Direct messages use a separate path (sender and recipient only).
163-
- Messages are saved to SQLite and capped at 1000 messages.
163+
- Reactions, read receipts, and last channel per user may be persisted server-side and replayed to reconnecting clients.
164+
- Message history is capped at 1000 messages.
164165

165166
---
166167

@@ -186,7 +187,7 @@ Exceeded messages are dropped silently from the client’s perspective (the serv
186187

187188
## Message Retention
188189

189-
- Messages are stored in SQLite.
190+
- Messages are stored in the backend configured by `MARCHAT_DB_PATH` (SQLite/PostgreSQL/MySQL).
190191
- The most recent 1000 messages are retained.
191192
- Older messages are deleted automatically.
192193

@@ -206,7 +207,7 @@ If `admin` is not requested, `admin_key` is not required.
206207

207208
## Configuration
208209

209-
Client settings are usually stored in **`config.json`** under the **client configuration directory** (per-user application data, or `MARCHAT_CONFIG_DIR`). That directory is separate from the **server** directory (`.env`, SQLite DB). Use **`marchat-client -doctor`** / **`marchat-server -doctor`** to print resolved paths.
210+
Client settings are usually stored in **`config.json`** under the **client configuration directory** (per-user application data, or `MARCHAT_CONFIG_DIR`). That directory is separate from the **server** directory (`.env` plus local DB file when SQLite is used). Use **`marchat-client -doctor`** / **`marchat-server -doctor`** to print resolved paths.
210211

211212
Example `config.json` shape:
212213

README.md

Lines changed: 29 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ Full changelog on [GitHub releases](https://github.com/Cod-e-Codes/marchat/relea
5454
## Features
5555

5656
- **Terminal UI** - Beautiful TUI built with Bubble Tea
57-
- **Real-time Chat** - Fast WebSocket messaging with SQLite backend (PostgreSQL/MySQL planned)
57+
- **Real-time Chat** - Fast WebSocket messaging with SQLite, PostgreSQL, or MySQL backends
5858
- **Message Management** - Edit, delete, pin, react to, and search messages
5959
- **Direct Messages** - Private DM conversations between users
6060
- **Channels** - Multiple chat rooms with join/leave and per-channel messaging
@@ -75,7 +75,7 @@ Full changelog on [GitHub releases](https://github.com/Cod-e-Codes/marchat/relea
7575

7676
## Overview
7777

78-
marchat started as a fun weekend project for father-son coding sessions and has evolved into a lightweight, self-hosted terminal chat application designed specifically for developers who love the command line. Currently runs with SQLite, with PostgreSQL and MySQL support planned for greater scalability.
78+
marchat started as a fun weekend project for father-son coding sessions and has evolved into a lightweight, self-hosted terminal chat application designed specifically for developers who love the command line. It supports SQLite by default and can also run against PostgreSQL or MySQL for larger deployments.
7979

8080
**Key Benefits:**
8181
- **Self-hosted**: No external services required
@@ -205,7 +205,7 @@ go build -o marchat-client ./client
205205
| `MARCHAT_ADMIN_KEY` | Yes | - | Admin authentication key |
206206
| `MARCHAT_USERS` | Yes | - | Comma-separated admin usernames |
207207
| `MARCHAT_PORT` | No | `8080` | Server port |
208-
| `MARCHAT_DB_PATH` | No | `./config/marchat.db` | Database file path |
208+
| `MARCHAT_DB_PATH` | No | `./config/marchat.db` | Database path/DSN. Supports SQLite file path, `postgres://...`, or `mysql:...` |
209209
| `MARCHAT_TLS_CERT_FILE` | No | - | TLS certificate (enables wss://) |
210210
| `MARCHAT_TLS_KEY_FILE` | No | - | TLS private key |
211211
| `MARCHAT_GLOBAL_E2E_KEY` | No | - | Base64 32-byte global encryption key |
@@ -215,6 +215,28 @@ go build -o marchat-client ./client
215215

216216
**Additional variables:** `MARCHAT_LOG_LEVEL`, `MARCHAT_CONFIG_DIR`, `MARCHAT_BAN_HISTORY_GAPS`, `MARCHAT_PLUGIN_REGISTRY_URL`
217217

218+
### Database backend setup
219+
220+
`MARCHAT_DB_PATH` accepts either a SQLite path or a DSN-style backend URL/prefix:
221+
222+
```bash
223+
# SQLite (default)
224+
export MARCHAT_DB_PATH=./config/marchat.db
225+
226+
# PostgreSQL
227+
export MARCHAT_DB_PATH='postgres://marchat:marchat@127.0.0.1:5432/marchat?sslmode=disable'
228+
229+
# MySQL
230+
export MARCHAT_DB_PATH='mysql:marchat:marchat@tcp(127.0.0.1:3306)/marchat?parseTime=true'
231+
```
232+
233+
Notes:
234+
- PostgreSQL requires a reachable database and credentials with schema/table create permissions.
235+
- MySQL DSNs should include `parseTime=true` so timestamp fields decode correctly.
236+
- **MariaDB** generally works with the same `mysql:` DSN shape and `parseTime=true` as MySQL.
237+
- The server creates **dialect-specific DDL** (for example, MySQL/MariaDB use fixed-width strings where indexes, primary keys, or unique constraints apply, because full `TEXT` keys are rejected). Long message bodies still use a large text type.
238+
- SQLite remains the easiest local development option.
239+
218240
**Doctor / diagnostics:** Set `MARCHAT_DOCTOR_NO_NETWORK` to `1` to skip the GitHub latest-release check in `-doctor` / `-doctor-json`.
219241

220242
**File Size Configuration:** Use either `MARCHAT_MAX_FILE_BYTES` (exact bytes) or `MARCHAT_MAX_FILE_MB` (megabytes). If both are set, `MARCHAT_MAX_FILE_BYTES` takes priority.
@@ -248,7 +270,7 @@ The repository’s `config/` directory holds **server** runtime files and the **
248270

249271
### Diagnostics (`-doctor`)
250272

251-
Run **`./marchat-client -doctor`** or **`./marchat-server -doctor`** for a text report (paths, masked `MARCHAT_*` env, sanity checks). Use **`-doctor-json`** for machine-readable output. If both flags were passed, `-doctor-json` wins. Exits without starting the TUI or listening on a port. See [ARCHITECTURE.md](ARCHITECTURE.md) for details.
273+
Run **`./marchat-client -doctor`** or **`./marchat-server -doctor`** for a text report (paths, masked `MARCHAT_*` env, sanity checks). Server doctor also reports the detected DB dialect, validates the configured DB connection string format, and attempts a DB ping. Use **`-doctor-json`** for machine-readable output. If both flags were passed, `-doctor-json` wins. Exits without starting the TUI or listening on a port. See [ARCHITECTURE.md](ARCHITECTURE.md) for details.
252274

253275
## Admin Commands
254276

@@ -681,6 +703,9 @@ Profiles stored in platform-appropriate locations:
681703
| Clipboard issues (Linux) | Install xclip: `sudo apt install xclip` |
682704
| Port in use | Change port: `export MARCHAT_PORT=8081` |
683705
| Database migration fails | Check file permissions, backup before source build |
706+
| PostgreSQL connection fails | Verify URL format: `postgres://user:pass@host:5432/db?sslmode=disable`; test with `psql` using same creds |
707+
| MySQL connection fails | Verify DSN prefix `mysql:` and DSN body `user:pass@tcp(host:3306)/db?parseTime=true`; test with `mysql` CLI |
708+
| SQL syntax error after backend switch | Ensure tables were created by the current server version and restart after changing `MARCHAT_DB_PATH` |
684709
| Message history missing | Expected after updates - user states reset for ban/unban improvements |
685710
| Ban history gaps not working | Ensure `MARCHAT_BAN_HISTORY_GAPS=true` (disabled by default) and `ban_history` table exists |
686711
| TLS certificate errors | Use `--skip-tls-verify` for dev with self-signed certs |

ROADMAP.md

Lines changed: 36 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -1,55 +1,54 @@
11
## Roadmap
22

3+
This file tracks implementation status. Completed items use `- [x]`; future ideas are plain bullets without a checkbox.
4+
35
### Recently Completed
4-
- CLI diagnostics (`-doctor`, `-doctor-json`) on client and server binaries for env/config/update checks
5-
- Message editing, deletion, and pinning
6-
- Message reactions (client-session scoped)
7-
- Direct messages between users
8-
- Chat channels (join/leave with per-channel messaging)
9-
- Typing indicators and read receipts
10-
- Message search (server-side)
11-
- E2E encryption for file transfers
12-
- Connection status indicator, tab completion, unread count
13-
- Multi-line input (Alt+Enter / Ctrl+J)
14-
- Chat history export
15-
- WebSocket rate limiting
16-
- Docker Compose for local development
6+
- [x] CLI diagnostics (`-doctor`, `-doctor-json`) on client and server binaries for env/config/update checks
7+
- [x] Message editing, deletion, and pinning
8+
- [x] Message reactions
9+
- [x] Direct messages between users
10+
- [x] Chat channels (join/leave with per-channel messaging)
11+
- [x] Typing indicators and read receipts
12+
- [x] Message search (server-side)
13+
- [x] E2E encryption for file transfers
14+
- [x] Connection status indicator, @mention tab completion, unread count
15+
- [x] Multi-line input (Alt+Enter / Ctrl+J)
16+
- [x] Chat history export
17+
- [x] WebSocket rate limiting
18+
- [x] Docker Compose for local development
1719

1820
### Phase 1: DB Abstraction Layer
19-
- Refactor database connection and initialization logic into a unified function.
20-
- Dynamically select DB driver and connection string at runtime.
21-
- Add support for PostgreSQL and MySQL in addition to SQLite.
21+
- [x] Refactor database connection and initialization logic into a unified function.
22+
- [x] Dynamically select DB driver and connection string at runtime.
23+
- [x] Add support for PostgreSQL and MySQL in addition to SQLite.
2224

2325
### Phase 2: Multi-Backend Compatibility
24-
- Ensure schema and queries work with:
25-
- SQLite (existing)
26-
- PostgreSQL
27-
- MySQL
28-
- Adjust types where necessary (BOOLEAN, TIMESTAMP/DATETIME).
29-
- Consider a unified schema that works across all backends.
26+
- [x] Ensure schema and queries work with SQLite, PostgreSQL, and MySQL.
27+
- [x] Adjust types where necessary (BOOLEAN, TIMESTAMP/DATETIME).
28+
- [x] Maintain a unified schema that works across all backends.
3029

3130
### Phase 3: Schema & Query Adaptation
32-
- Add conditional logic for DB-specific schema tweaks.
33-
- Validate CREATE TABLE statements in all target backends.
34-
- Test queries for compatibility and performance.
31+
- [x] Add conditional logic for DB-specific schema tweaks.
32+
- [x] Validate CREATE TABLE statements in all target backends.
33+
- [x] Test queries for compatibility and performance.
3534

3635
### Phase 4: Performance Enhancements
37-
- Enable SQLite Write-Ahead Logging (WAL) mode for performance gains.
38-
- Implement batch TTL-based message deletion.
39-
- Add indexing for frequently queried columns.
40-
- Cache displayed messages in the terminal to reduce DB queries.
36+
- [x] Enable SQLite Write-Ahead Logging (WAL) mode for performance gains.
37+
- [x] Implement batch TTL-based message deletion.
38+
- [x] Add indexing for frequently queried columns.
39+
- [x] Cache displayed/recent messages in server memory to reduce repeated DB reads.
4140

4241
### Phase 5: Persistence & Durability
43-
- Persist reactions to the database (currently session-only).
44-
- Persist channel membership across reconnects.
45-
- Add read receipt state tracking per user.
42+
- [x] Persist reactions to the database.
43+
- [x] Persist last channel per user across reconnects (`user_channels`).
44+
- [x] Add read receipt state tracking per user.
4645

4746
### Phase 6: Testing & Documentation
48-
- Write unit/integration tests for all supported backends.
49-
- Document setup steps for PostgreSQL and MySQL.
50-
- Provide working connection string examples.
51-
- Include troubleshooting tips for common DB connection issues.
52-
- Increase test coverage for client and server packages.
47+
- [x] Dialect tests (DSN detection, placeholder rebinding) and SQLite-backed integration tests; exercise PostgreSQL/MySQL against live databases separately.
48+
- [x] Document setup steps for PostgreSQL and MySQL.
49+
- [x] Provide working connection string examples.
50+
- [x] Include troubleshooting tips for common DB connection issues.
51+
- [x] Increase test coverage for client and server packages.
5352

5453
### Phase 7: Future Improvements
5554
- Consider using `sqlx` or a lightweight ORM to reduce SQL dialect handling.

0 commit comments

Comments
 (0)