mirror of
https://github.com/moshix/mvs.git
synced 2026-03-04 18:14:15 +00:00
Create FederatedChat.md
This commit is contained in:
582
FederatedChat.md
Normal file
582
FederatedChat.md
Normal file
@@ -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.
|
||||
Reference in New Issue
Block a user