Some checks failed
CI / Rust Format (push) Has been cancelled
CI / Clippy (push) Has been cancelled
CI / Test Server (push) Has been cancelled
CI / Frontend Check (push) Has been cancelled
CI / Tauri Client Check (push) Has been cancelled
CI / Docker Build (push) Has been cancelled
CI / Build Tauri (Linux) (push) Has been cancelled
- Fix 14 Clippy warnings across server and bot-sdk - Add 67 unit tests (32 bot-sdk, 34 server, 1 doctest) - Add Prometheus metrics endpoint (/api/metrics) - Add structured JSON logging (EIFELDC_LOG_FORMAT=json) - Add release workflow (Docker push + GitHub Release + Tauri builds) - Add rate limiting middleware (EIFELDC_RATE_LIMIT) - Add CORS restriction (EIFELDC_CORS_ORIGINS) - Add session token expiry (EIFELDC_SESSION_TTL) - Add input validation (username/password/homeserver length limits) - Add upload size limit (EIFELDC_MAX_UPLOAD_MB) - Upgrade Tauri client from v1 to v2 - Add session store with SQLite persistence - Add proper error types and cleanup across all crates - Format all code with cargo fmt - Update CI pipeline with fmt, clippy, test, frontend, and Tauri checks - Add README with full API reference and setup guide
300 lines
9.2 KiB
Markdown
300 lines
9.2 KiB
Markdown
# EifelDC
|
|
|
|
Discord-like Matrix chat platform built with Rust, Svelte, and Tauri.
|
|
|
|
## Architecture
|
|
|
|
| Component | Crate | Description |
|
|
|---|---|---|
|
|
| **Server** | `eifeldc-server` | Axum web server, Matrix SDK, LiveKit voice, SQLite sessions |
|
|
| **Bot SDK** | `eifeldc-bot-sdk` | SDK for building Matrix bots |
|
|
| **Client** | `eifeldc-client` | Tauri desktop app |
|
|
| **Frontend** | — | Svelte + TypeScript UI |
|
|
|
|
## Prerequisites
|
|
|
|
- Rust 1.82+ (stable)
|
|
- Node.js 20+
|
|
- A running Matrix Synapse homeserver
|
|
- LiveKit server (for voice)
|
|
|
|
## Quick Start
|
|
|
|
### Build Server & Bot SDK
|
|
|
|
```bash
|
|
cargo build --release -p eifeldc-server -p eifeldc-bot-sdk
|
|
```
|
|
|
|
### Build Frontend
|
|
|
|
```bash
|
|
cd client/src-ui
|
|
npm ci
|
|
npm run build
|
|
```
|
|
|
|
### Build Tauri Desktop App
|
|
|
|
Requires system dependencies:
|
|
|
|
```bash
|
|
# Ubuntu/Debian
|
|
sudo apt-get install -y libwebkit2gtk-4.1-dev libgtk-3-dev libayatana-appindicator3-dev librsvg2-dev libssl-dev
|
|
```
|
|
|
|
```bash
|
|
cargo build --release -p eifeldc-client
|
|
```
|
|
|
|
### Run the Server
|
|
|
|
```bash
|
|
cargo run -p eifeldc-server
|
|
```
|
|
|
|
The server starts on `http://0.0.0.0:3000` and serves the frontend from `client/src-ui/dist`.
|
|
|
|
## Environment Variables
|
|
|
|
| Variable | Default | Description |
|
|
|---|---|---|
|
|
| `EIFELDC_DB` | `eifeldc.db` | SQLite database path for session storage |
|
|
| `EIFELDC_STATIC_DIR` | `client/src-ui/dist` | Path to frontend static files |
|
|
| `LIVEKIT_API_KEY` | `devkey` | LiveKit API key |
|
|
| `LIVEKIT_API_SECRET` | `devsecret` | LiveKit API secret |
|
|
| `LIVEKIT_URL` | `ws://localhost:7880` | LiveKit server URL |
|
|
| `RUST_LOG` | — | Logging filter (e.g. `eifeldc_server=info`) |
|
|
| `EIFELDC_LOG_FORMAT` | `pretty` | Log format: `pretty` or `json` |
|
|
| `EIFELDC_CORS_ORIGINS` | `*` | Comma-separated allowed origins (e.g. `https://app.example.org,https://dev.example.org`) |
|
|
| `EIFELDC_SESSION_TTL` | — | Session TTL in seconds (no expiry if unset) |
|
|
| `EIFELDC_RATE_LIMIT` | `60` | Max requests per minute per client |
|
|
| `EIFELDC_MAX_UPLOAD_MB` | `50` | Max upload size in MB |
|
|
|
|
## Docker Deployment
|
|
|
|
```bash
|
|
# Configure environment
|
|
cp infra/.env.example infra/.env
|
|
# Edit infra/.env with your domain and secrets
|
|
|
|
# Setup infrastructure (generates secrets, configs, certs)
|
|
bash infra/scripts/setup.sh your.domain.com
|
|
|
|
# Or manually:
|
|
cd infra
|
|
docker compose up -d
|
|
```
|
|
|
|
Services: EifelDC server, Synapse, PostgreSQL, LiveKit, coturn, nginx.
|
|
|
|
### Useful Commands
|
|
|
|
```bash
|
|
make docker-build # Build Docker image
|
|
make docker-up # Start containers
|
|
make docker-down # Stop containers
|
|
make docker-logs # Follow EifelDC logs
|
|
make health # Run healthcheck
|
|
make backup # Backup data
|
|
```
|
|
|
|
## API Reference
|
|
|
|
All endpoints are under `/api`. Public endpoints require no auth. Protected endpoints require a `Bearer` token in the `Authorization` header.
|
|
|
|
### Authentication
|
|
|
|
| Method | Endpoint | Auth | Description |
|
|
|---|---|---|---|
|
|
| POST | `/api/login` | No | Login with Matrix credentials |
|
|
| POST | `/api/register` | No | Register a new Matrix user |
|
|
| GET | `/api/current-user` | No | Get current user ID from token |
|
|
| POST | `/api/logout` | Yes | Logout and invalidate session |
|
|
|
|
**Login Request:**
|
|
```json
|
|
{ "homeserver": "https://matrix.example.org", "username": "alice", "password": "secret" }
|
|
```
|
|
|
|
**Login Response:**
|
|
```json
|
|
{ "success": true, "user_id": "@alice:example.org", "token": "uuid-token", "error": null }
|
|
```
|
|
|
|
### Rooms
|
|
|
|
| Method | Endpoint | Description |
|
|
|---|---|---|
|
|
| GET | `/api/rooms` | List joined rooms |
|
|
| POST | `/api/rooms/create` | Create a new room |
|
|
| POST | `/api/rooms/join` | Join a room by ID or alias |
|
|
| POST | `/api/rooms/{room_id}/leave` | Leave a room |
|
|
| GET | `/api/rooms/{room_id}/members` | List room members |
|
|
| POST | `/api/rooms/{room_id}/name` | Set room name |
|
|
| POST | `/api/rooms/{room_id}/topic` | Set room topic |
|
|
| POST | `/api/rooms/{room_id}/avatar` | Set room avatar (multipart) |
|
|
| GET | `/api/rooms/unread` | Get unread notification counts |
|
|
| POST | `/api/rooms/{room_id}/read` | Mark room as read |
|
|
|
|
### Messages
|
|
|
|
| Method | Endpoint | Description |
|
|
|---|---|---|
|
|
| GET | `/api/rooms/{room_id}/messages` | Get room messages |
|
|
| POST | `/api/rooms/{room_id}/send` | Send a message |
|
|
| POST | `/api/rooms/{room_id}/edit` | Edit a message |
|
|
| POST | `/api/rooms/{room_id}/delete/{event_id}` | Delete a message |
|
|
| POST | `/api/rooms/{room_id}/react` | React to a message |
|
|
| POST | `/api/rooms/{room_id}/typing` | Set typing notification |
|
|
| POST | `/api/rooms/{room_id}/upload` | Upload a file (multipart) |
|
|
|
|
### Threads
|
|
|
|
| Method | Endpoint | Description |
|
|
|---|---|---|
|
|
| GET | `/api/rooms/{room_id}/threads` | List threads in a room |
|
|
| GET | `/api/rooms/{room_id}/threads/{thread_id}` | Get thread messages |
|
|
| POST | `/api/rooms/{room_id}/threads/{thread_id}/reply` | Reply in a thread |
|
|
| POST | `/api/rooms/{room_id}/reply` | Reply to a message |
|
|
|
|
### Voice (LiveKit)
|
|
|
|
| Method | Endpoint | Description |
|
|
|---|---|---|
|
|
| POST | `/api/voice/join` | Join a voice channel |
|
|
| POST | `/api/voice/leave` | Leave voice channel |
|
|
| POST | `/api/voice/toggle-mute` | Toggle mute |
|
|
| POST | `/api/voice/toggle-deafen` | Toggle deafen |
|
|
| GET | `/api/voice/participants` | Get voice participants |
|
|
|
|
### Presence
|
|
|
|
| Method | Endpoint | Description |
|
|
|---|---|---|
|
|
| POST | `/api/presence/set` | Set presence status |
|
|
| GET | `/api/presence/{user_id}` | Get user presence |
|
|
|
|
### Profile
|
|
|
|
| Method | Endpoint | Description |
|
|
|---|---|---|
|
|
| GET | `/api/profile/me` | Get own profile |
|
|
| GET | `/api/profile/{user_id}` | Get user profile |
|
|
| POST | `/api/profile/displayname` | Set display name |
|
|
| POST | `/api/profile/avatar` | Upload avatar (multipart) |
|
|
|
|
### Emoji & Roles
|
|
|
|
| Method | Endpoint | Description |
|
|
|---|---|---|
|
|
| GET | `/api/rooms/{room_id}/emoji` | Get custom emoji |
|
|
| POST | `/api/rooms/{room_id}/emoji/upload` | Upload custom emoji |
|
|
| GET | `/api/rooms/{room_id}/roles` | Get room roles |
|
|
| POST | `/api/rooms/{room_id}/roles/assign` | Assign role to user |
|
|
| POST | `/api/rooms/{room_id}/roles/remove` | Remove role from user |
|
|
| GET | `/api/rooms/{room_id}/permissions/{user_id}` | Get user permissions |
|
|
|
|
### Media & WebSocket
|
|
|
|
| Method | Endpoint | Description |
|
|
|---|---|---|
|
|
| GET | `/api/media/{mxc_path}` | Proxy Matrix media |
|
|
| GET | `/api/metrics` | Prometheus metrics endpoint |
|
|
| GET | `/api/ws?token={token}` | WebSocket for real-time events |
|
|
|
|
### WebSocket Events
|
|
|
|
### Monitoring
|
|
|
|
**Prometheus Metrics** are exposed at `/api/metrics`:
|
|
|
|
| Metric | Type | Description |
|
|
|---|---|---|
|
|
| `eifeldc_http_requests_total` | Counter | Total HTTP requests |
|
|
| `eifeldc_active_sessions` | Gauge | Active user sessions |
|
|
| `eifeldc_active_websockets` | Gauge | Active WebSocket connections |
|
|
| `eifeldc_messages_sent_total` | Counter | Total messages sent |
|
|
| `eifeldc_rooms_joined_total` | Counter | Total room join operations |
|
|
| `eifeldc_uploads_total` | Counter | Total file uploads |
|
|
| `eifeldc_voice_participants` | Gauge | Current voice participants |
|
|
|
|
**Structured Logging** — set `EIFELDC_LOG_FORMAT=json` for JSON output:
|
|
|
|
```json
|
|
{"timestamp":"2026-04-29T10:00:00Z","level":"INFO","target":"eifeldc_server::routes::auth","fields":{"message":"LiveKit URL: ws://localhost:7880"}}
|
|
```
|
|
|
|
Use `RUST_LOG` for filtering: `RUST_LOG=eifeldc_server=debug,tower_http=info`
|
|
|
|
### WebSocket Events
|
|
|
|
```json
|
|
{ "type": "message", "room_id": "...", "event_id": "...", "sender": "...", "body": "...", "timestamp": 0 }
|
|
{ "type": "message_edited", "room_id": "...", "event_id": "...", "new_body": "..." }
|
|
{ "type": "message_deleted", "room_id": "...", "redacts": "..." }
|
|
{ "type": "reaction", "room_id": "...", "event_id": "...", "key": "👍", "sender": "..." }
|
|
{ "type": "room_joined", "room_id": "...", "name": "..." }
|
|
{ "type": "room_left", "room_id": "..." }
|
|
{ "type": "presence", "user_id": "...", "status": "online|idle|offline", "status_msg": "..." }
|
|
{ "type": "typing", "room_id": "...", "user_id": "...", "typing": true }
|
|
{ "type": "voice_state_update", "room_id": "...", "user_id": "...", "muted": false, "deafened": false }
|
|
{ "type": "voice_user_joined", "room_id": "...", "user_id": "..." }
|
|
{ "type": "voice_user_left", "room_id": "...", "user_id": "..." }
|
|
{ "type": "thread_reply", "room_id": "...", "root_event_id": "...", "event_id": "...", "sender": "...", "body": "...", "timestamp": 0 }
|
|
```
|
|
|
|
## Bot SDK
|
|
|
|
```rust
|
|
use std::sync::Arc;
|
|
use eifeldc_bot_sdk::{BotClient, BotEvent, CommandContext};
|
|
|
|
#[tokio::main]
|
|
async fn main() -> anyhow::Result<()> {
|
|
let mut bot = BotClient::new("https://matrix.example.org")
|
|
.with_auth("botuser", "botpassword");
|
|
|
|
bot.on_event(|event| {
|
|
match event {
|
|
BotEvent::Message { room_id, sender, body, .. } => {
|
|
println!("{} in {}: {}", sender, room_id, body);
|
|
}
|
|
_ => {}
|
|
}
|
|
});
|
|
|
|
bot.on_command("hello", Arc::new(|ctx: CommandContext| {
|
|
println!("Hello command from {} in {}!", ctx.sender, ctx.room_id);
|
|
}));
|
|
|
|
bot.start().await?;
|
|
Ok(())
|
|
}
|
|
```
|
|
|
|
## Development
|
|
|
|
```bash
|
|
# Check compilation
|
|
cargo check -p eifeldc-server -p eifeldc-bot-sdk
|
|
|
|
# Run linter
|
|
cargo clippy -p eifeldc-server -p eifeldc-bot-sdk -- -D warnings
|
|
|
|
# Check formatting
|
|
cargo fmt --all -- --check
|
|
|
|
# Run tests
|
|
cargo test -p eifeldc-server -p eifeldc-bot-sdk
|
|
|
|
# Build frontend
|
|
cd client/src-ui && npm ci && npm run build
|
|
|
|
# Run dev server
|
|
cargo run -p eifeldc-server
|
|
```
|
|
|
|
## License
|
|
|
|
All rights reserved. |