# 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.