From ebcc016b9b2ab79dd6e8de3f88231b9646862f5e Mon Sep 17 00:00:00 2001 From: moshix Date: Wed, 1 Oct 2025 06:17:25 +0200 Subject: [PATCH] Create FederatedChat.md --- FederatedChat.md | 582 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 582 insertions(+) create mode 100644 FederatedChat.md diff --git a/FederatedChat.md b/FederatedChat.md new file mode 100644 index 0000000..edf0bfd --- /dev/null +++ b/FederatedChat.md @@ -0,0 +1,582 @@ +# Federated Chat API Documentation + +## Overview + +Teh Federated Chat system is a PostgreSQL-based global chat implementation that allows multiple BBS instances to communicate with eachother through a shared database. Each BBS connects to a central PostgreSQL server where all chat messages are stored and syncronized across all participating systems. + +## Architecture + +### System Componets + +1. **PostgreSQL Database**: Central message storage and synchronization point +2. **BBS Instances**: Individual BBS systems that connect to teh shared database +3. **Chat Messages**: Timestamped messages with username prefixes indicating orgin BBS + +### Data Flow + +``` +BBS Instance A ──┐ + ├──► PostgreSQL Database ◄──┤ +BBS Instance B ──┘ ├── BBS Instance C + └── BBS Instance D +``` + +## Database Schema + +### Primary Table: `chat` + +The federated chat system uses a PostgreSQL table with the following structure: + +```sql +CREATE TABLE chat ( + id SERIAL PRIMARY KEY, + username VARCHAR(255) NOT NULL, + message TEXT NOT NULL, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + room_id VARCHAR(50) DEFAULT 'global' +); +``` + +#### Table Fields + +| Field | Type | Description | Example | +|-------|------|-------------|---------| +| `id` | SERIAL | Auto-incrementing primary key | `12345` | +| `username` | VARCHAR(255) | BBS-prefixed username in format "BBSName:username" | `"Forum3270:moshix"` | +| `message` | TEXT | The actual chat message content | `"Hello everyone!"` | +| `created_at` | TIMESTAMP | Message timestamp (UTC) | `2025-01-15 14:30:25` | +| `room_id` | VARCHAR(50) | Chat room identifier (always 'global' for federated chat) | `"global"` | + +## Configuration + +### BBS Configuration (tsu.cnf) + +Each BBS instance must be configured with PostgreSQL connection paramaters in the `tsu.cnf` file: + +```ini +# PostgreSQL Global Chat Configuration +globalchat_db_address=your-postgres-server.com +globalchat_db_port=5432 +globalchat_db_user=globalchat_user +globalchat_db_password=your_secure_password +``` + +#### Configuration Paramters + +| Parameter | Description | Required | Example | +|-----------|-------------|----------|---------| +| `globalchat_db_address` | PostgreSQL server hostname or IP | Yes | `chat.example.com` | +| `globalchat_db_port` | PostgreSQL server port | Yes | `5432` | +| `globalchat_db_user` | Database username | Yes | `chatuser` | +| `globalchat_db_password` | Database password | Yes | `securepass123` | + +Teh database name is hardcoded as `globalchat` in teh system. + +## API Operations + +### 1. Post Message + +**Function**: `postGlobalChatMessage(message string)` + +**SQL Query**: +```sql +INSERT INTO chat (username, message, created_at, room_id) +VALUES ($1, $2, CURRENT_TIMESTAMP, 'global') +``` + +**Parameters**: +- `$1`: Username in format "BBSName:username" (e.g., "Forum3270:moshix") +- `$2`: Message content + +**Example Implementation (Python)**: +```python +import psycopg2 +from datetime import datetime + +def post_message(bbs_name, username, message, db_config): + """Post a message to the federated chat""" + conn = psycopg2.connect( + host=db_config['host'], + port=db_config['port'], + user=db_config['user'], + password=db_config['password'], + database='globalchat' + ) + + try: + cursor = conn.cursor() + full_username = f"{bbs_name}:{username}" + + cursor.execute( + "INSERT INTO chat (username, message, created_at, room_id) VALUES (%s, %s, CURRENT_TIMESTAMP, 'global')", + (full_username, message) + ) + conn.commit() + print(f"Message posted successfully by {full_username}") + + except Exception as e: + print(f"Error posting message: {e}") + conn.rollback() + finally: + conn.close() + +# Usage example +db_config = { + 'host': 'your-postgres-server.com', + 'port': 5432, + 'user': 'globalchat_user', + 'password': 'your_password' +} + +post_message("MyBBS", "john", "Hello from MyBBS!", db_config) +``` + +**Example Implementation (Bash)**: +```bash +#!/bin/bash + +# Configuration +DB_HOST="your-postgres-server.com" +DB_PORT="5432" +DB_USER="globalchat_user" +DB_PASSWORD="your_password" +DB_NAME="globalchat" + +# Function to post a message +post_message() { + local bbs_name="$1" + local username="$2" + local message="$3" + local full_username="${bbs_name}:${username}" + + PGPASSWORD="$DB_PASSWORD" psql -h "$DB_HOST" -p "$DB_PORT" -U "$DB_USER" -d "$DB_NAME" -c \ + "INSERT INTO chat (username, message, created_at, room_id) VALUES ('$full_username', '$message', CURRENT_TIMESTAMP, 'global');" + + if [ $? -eq 0 ]; then + echo "Message posted successfully by $full_username" + else + echo "Error posting message" + fi +} + +# Usage example +post_message "MyBBS" "john" "Hello from bash script!" +``` + +### 2. Retrieve Messages + +**Function**: `getGlobalChatMessages(limit int)` + +**SQL Query**: +```sql +SELECT username, message, created_at +FROM chat +WHERE room_id = 'global' +ORDER BY created_at DESC +LIMIT $1 +``` + +**Parameters**: +- `$1`: Maximum number of messages to retrieve + +**Returns**: Array of messages in reverse chronolgical order (newest first) + +**Example Implementation (Python)**: +```python +def get_messages(limit, db_config): + """Retrieve recent messages from federated chat""" + conn = psycopg2.connect( + host=db_config['host'], + port=db_config['port'], + user=db_config['user'], + password=db_config['password'], + database='globalchat' + ) + + try: + cursor = conn.cursor() + cursor.execute( + "SELECT username, message, created_at FROM chat WHERE room_id = 'global' ORDER BY created_at DESC LIMIT %s", + (limit,) + ) + + messages = [] + for row in cursor.fetchall(): + username, message, created_at = row + messages.append({ + 'username': username, + 'message': message, + 'timestamp': created_at.isoformat() + }) + + # Reverse to get chronological order (oldest first) + messages.reverse() + return messages + + except Exception as e: + print(f"Error retrieving messages: {e}") + return [] + finally: + conn.close() + +# Usage example +messages = get_messages(50, db_config) +for msg in messages: + print(f"[{msg['timestamp']}] {msg['username']}: {msg['message']}") +``` + +**Example Implementation (Bash)**: +```bash +#!/bin/bash + +get_messages() { + local limit="$1" + + PGPASSWORD="$DB_PASSWORD" psql -h "$DB_HOST" -p "$DB_PORT" -U "$DB_USER" -d "$DB_NAME" -t -c \ + "SELECT username || ' | ' || message || ' | ' || created_at FROM chat WHERE room_id = 'global' ORDER BY created_at DESC LIMIT $limit;" | \ + tac # Reverse order to get chronological +} + +# Usage example +echo "Recent messages:" +get_messages 10 +``` + +### 3. Get Statistics + +**Function**: `getGlobalChatStats()` + +**SQL Queries**: + +1. **Last Message Time**: +```sql +SELECT MAX(created_at) +FROM chat +WHERE room_id = 'global' +AND created_at > NOW() - INTERVAL '24 hours' +``` + +2. **Message Count (24h)**: +```sql +SELECT COUNT(*) +FROM chat +WHERE room_id = 'global' +AND created_at > NOW() - INTERVAL '24 hours' +``` + +3. **Active Users (30m)**: +```sql +SELECT COUNT(DISTINCT username) +FROM chat +WHERE room_id = 'global' +AND created_at > NOW() - INTERVAL '30 minutes' +``` + +**Example Implementation (Python)**: +```python +def get_chat_stats(db_config): + """Get federated chat statistics""" + conn = psycopg2.connect( + host=db_config['host'], + port=db_config['port'], + user=db_config['user'], + password=db_config['password'], + database='globalchat' + ) + + try: + cursor = conn.cursor() + + # Last message time + cursor.execute(""" + SELECT MAX(created_at) + FROM chat + WHERE room_id = 'global' + AND created_at > NOW() - INTERVAL '24 hours' + """) + last_msg_time = cursor.fetchone()[0] + + # Message count in last 24h + cursor.execute(""" + SELECT COUNT(*) + FROM chat + WHERE room_id = 'global' + AND created_at > NOW() - INTERVAL '24 hours' + """) + msg_count_24h = cursor.fetchone()[0] + + # Active users in last 30m + cursor.execute(""" + SELECT COUNT(DISTINCT username) + FROM chat + WHERE room_id = 'global' + AND created_at > NOW() - INTERVAL '30 minutes' + """) + active_users_30m = cursor.fetchone()[0] + + return { + 'last_message_time': last_msg_time.isoformat() if last_msg_time else None, + 'messages_24h': msg_count_24h, + 'active_users_30m': active_users_30m + } + + except Exception as e: + print(f"Error getting stats: {e}") + return None + finally: + conn.close() + +# Usage example +stats = get_chat_stats(db_config) +if stats: + print(f"Last message: {stats['last_message_time']}") + print(f"Messages in last 24h: {stats['messages_24h']}") + print(f"Active users in last 30m: {stats['active_users_30m']}") +``` + +## Username Format + +### BBS Prefix Format + +Usernames in the federated chat follow the format: `BBSName:username` + +**Examples**: +- `Forum3270:moshix` +- `RetroNet:john_doe` +- `ClassicBBS:admin` + +### Display Format + +For display purposes, usernames are truncated to fit screen constraits: +- BBS name: First 2 characters +- Username: Up to 8 characters +- Format: `BB:username` + +**Examples**: +- `Forum3270:moshix` → `Fo:moshix` +- `RetroNet:john_doe` → `Re:john_doe` + +## Connection Management + +### Connection Parameters + +```go +// Connection string format +connStr := fmt.Sprintf("host=%s port=%s user=%s password=%s dbname=%s sslmode=disable", + config.Address, config.Port, config.User, config.Password, "globalchat") +``` + +### Connection Settings + +- **Connection Timeout**: 3 seconds +- **SSL Mode**: Disabled (`sslmode=disable`) +- **Database Name**: Fixed as `globalchat` + +### Error Handling + +Common connection errors and thier meanings: + +| Error | Cause | Solution | +|-------|-------|----------| +| Connection timeout | Network issues or server down | Check network conectivity and server status | +| Authentication failed | Wrong credentials | Verify username/password in configuration | +| Database not found | Database doesn't exist | Create teh `globalchat` database | +| Permission denied | User lacks priviliges | Grant apropriate permissions to database user | + +## Security Considerations + +### Database Security + +1. **User Permissions**: Create a dedicated database user with minimal requried permissions: +```sql +CREATE USER globalchat_user WITH PASSWORD 'secure_password'; +GRANT SELECT, INSERT ON chat TO globalchat_user; +GRANT USAGE, SELECT ON SEQUENCE chat_id_seq TO globalchat_user; +``` + +2. **Network Security**: + - Use firewall rules to restrict database acess + - Consider VPN for production deployements + - Monitor connection atempts + +### Input Validaton + +- Message content should be sanitized to prevent SQL injection +- Username format should be validated +- Message lenght limits should be enforced + +## Monitoring and Maintenance + +### Performance Monitoring + +Monitor these metrics for optmial performance: + +- Connection count to PostgreSQL +- Query execution time +- Message insertion rate +- Database size groth + +### Maintanence Tasks + +1. **Regular Cleanup**: +```sql +-- Remove messages older than 30 days +DELETE FROM chat +WHERE room_id = 'global' +AND created_at < NOW() - INTERVAL '30 days'; +``` + +2. **Index Optimization**: +```sql +-- Recommended indexes +CREATE INDEX idx_chat_room_created ON chat(room_id, created_at); +CREATE INDEX idx_chat_created ON chat(created_at); +``` + +## Troubleshooting + +### Common Issues + +1. **Messages not appearing**: Check database conectivity and permissions +2. **Duplicate messages**: Verify unique constraints and connection handeling +3. **Performance issues**: Review indexes and query optimiztion +4. **Connection failures**: Check network connectivity and firewall setings + +### Debug Queries + +```sql +-- Check recent activity +SELECT COUNT(*), MAX(created_at) FROM chat WHERE room_id = 'global'; + +-- View active BBSes +SELECT DISTINCT SPLIT_PART(username, ':', 1) as bbs_name, COUNT(*) +FROM chat +WHERE room_id = 'global' +AND created_at > NOW() - INTERVAL '1 hour' +GROUP BY bbs_name; + +-- Check message distribution +SELECT DATE(created_at) as date, COUNT(*) as message_count +FROM chat +WHERE room_id = 'global' +GROUP BY DATE(created_at) +ORDER BY date DESC +LIMIT 7; +``` + +## Example Complete Implementation + +### Python Client Library + +```python +import psycopg2 +import json +from datetime import datetime, timedelta + +class FederatedChatClient: + def __init__(self, bbs_name, db_config): + self.bbs_name = bbs_name + self.db_config = db_config + + def connect(self): + return psycopg2.connect( + host=self.db_config['host'], + port=self.db_config['port'], + user=self.db_config['user'], + password=self.db_config['password'], + database='globalchat' + ) + + def post_message(self, username, message): + conn = self.connect() + try: + cursor = conn.cursor() + full_username = f"{self.bbs_name}:{username}" + cursor.execute( + "INSERT INTO chat (username, message, created_at, room_id) VALUES (%s, %s, CURRENT_TIMESTAMP, 'global')", + (full_username, message) + ) + conn.commit() + return True + except Exception as e: + print(f"Error posting message: {e}") + conn.rollback() + return False + finally: + conn.close() + + def get_messages(self, limit=50): + conn = self.connect() + try: + cursor = conn.cursor() + cursor.execute( + "SELECT username, message, created_at FROM chat WHERE room_id = 'global' ORDER BY created_at DESC LIMIT %s", + (limit,) + ) + messages = [] + for row in cursor.fetchall(): + username, message, created_at = row + messages.append({ + 'username': username, + 'message': message, + 'timestamp': created_at + }) + messages.reverse() # Chronological order + return messages + except Exception as e: + print(f"Error retrieving messages: {e}") + return [] + finally: + conn.close() + + def get_stats(self): + conn = self.connect() + try: + cursor = conn.cursor() + + # Get all stats in one transaction + cursor.execute(""" + SELECT + (SELECT MAX(created_at) FROM chat WHERE room_id = 'global' AND created_at > NOW() - INTERVAL '24 hours') as last_msg, + (SELECT COUNT(*) FROM chat WHERE room_id = 'global' AND created_at > NOW() - INTERVAL '24 hours') as msg_24h, + (SELECT COUNT(DISTINCT username) FROM chat WHERE room_id = 'global' AND created_at > NOW() - INTERVAL '30 minutes') as users_30m + """) + + result = cursor.fetchone() + return { + 'last_message_time': result[0], + 'messages_24h': result[1], + 'active_users_30m': result[2] + } + except Exception as e: + print(f"Error getting stats: {e}") + return None + finally: + conn.close() + +# Usage example +if __name__ == "__main__": + db_config = { + 'host': 'your-postgres-server.com', + 'port': 5432, + 'user': 'globalchat_user', + 'password': 'your_password' + } + + client = FederatedChatClient("MyBBS", db_config) + + # Post a message + client.post_message("testuser", "Hello from Python client!") + + # Get recent messages + messages = client.get_messages(10) + for msg in messages: + print(f"[{msg['timestamp']}] {msg['username']}: {msg['message']}") + + # Get statistics + stats = client.get_stats() + if stats: + print(f"Stats: {json.dumps(stats, default=str, indent=2)}") +``` + +This documentation provides a complete refrence for implementing federated chat clients in any programing language that supports PostgreSQL connectivity.