Imported from SkyUOI/OurChat (
server/AGENTS.md). Install upstream withnpx skills add SkyUOI/OurChat --skill server. Copyright stays with the author.
AGENTS.md - Server
This file provides guidance to AI coding agents (Claude Code, opencode, etc.) when working with the server portion of this repository.
⚠️ MANDATORY DATABASE WORKFLOW — READ THIS FIRST
Whenever you add, remove, or rename a column/table, you MUST follow these steps in order:
- Write the migration (
server/migration/src/m*.rs) — add the column/enum variant there. Also updateserver/migration/src/enums.rsif adding to an existing table. - Run the migration against the real database (so SeaORM sees the actual schema):
cd server && python ../script/db_migration.py - Regenerate entities — this reads the live database schema and writes
server/entities/src/entities/*.rs:cd server && python ../script/regenerate_entity.py
🚫 NEVER hand-edit server/entities/src/entities/*.rs. The entity files are automatically generated from the live database schema. Any manual edit will be overwritten the next time someone runs the regeneration script. If a field is missing from the entity, it means you skipped step 2 or 3 — go back and run them.
🚫 Skipping steps leads to: mysterious compilation errors (missing fields in ActiveModel, mismatched column types), test failures, and drift between the entity code and the actual database.
Summary checklist for every schema change:
-
migration/src/m*.rs— migration written -
migration/src/enums.rs— column ident added (for existing tables) -
migration/src/lib.rs— migration registered -
python script/db_migration.py— migration applied to DB -
python script/regenerate_entity.py— entities regenerated from DB -
cargo check --workspace --all-targets— everything compiles
Project Overview
OurChat server is a cross-platform chat application backend built with Rust. It uses a modern async architecture with gRPC and HTTP APIs, supporting real-time messaging, group chats, end-to-end encryption, and self-hosting capabilities.
Quick Reference
Critical Notes
- SeaORM for database operations, Axum for HTTP, Tonic for gRPC
- Configuration files in
docker/config/(Docker) orconfig/(non-Docker), see "Configuration Files" section
Development Environment
Rust Toolchain
- Uses nightly Rust toolchain (
rust-toolchain.toml) - Rust edition 2024
- Minimum Rust version: 1.91
Build Commands
Workspace Structure
The server is a Rust workspace with multiple crates:
server/- Main server applicationserver/entities/- SeaORM database entitiesserver/migration/- Database migrationsserver/pb/- Protobuf code generationserver/derive/- Custom derive macrosserver/base/- Base libraryserver/client/- Client libraryserver/utils/- Utility functionsserver/stress_test/- Server stress testingserver/web-panel/- Web administration panel
Key source directories in server/src/:
process/- Business logic (authentication, messaging, sessions)db/- Database operationsmatrix/- Matrix protocol integration
Architecture Overview
Server Structure
- Main entry:
server/src/main.rs- Application bootstrap - Core:
server/src/lib.rs- Application struct and shared state - HTTP API:
server/src/httpserver.rs- Axum-based REST endpoints - gRPC API:
server/src/server.rs- Tonic-based gRPC services - Business Logic:
server/src/process/- Authentication, messaging, etc.
Database Layer
- PostgreSQL: Primary data storage with SeaORM
- Redis: Caching and session management
- RabbitMQ: Real-time message queue
- Entities:
server/entities/src/entities/- Database models - Migrations:
server/migration/src/- Database schema management
API Architecture
- gRPC Services: Core chat functionality via Tonic
- HTTP Endpoints: REST API under
/v1/with Axum - Authentication: JWT tokens with Argon2 password hashing
- OAuth: GitHub OAuth integration
Key Configuration
Configuration Files
Configuration files are located in two main directories:
- Docker deployment:
docker/config/(primary configs for containerized deployment) - Non-Docker deployment:
config/(configs for non-containerized deployment)
Main configuration files:
ourchat.toml- Main server configurationdatabase.toml- PostgreSQL database configurationredis.toml- Redis configurationrabbitmq.toml- RabbitMQ configurationuser_setting.toml- User settings and policieshttp.toml- HTTP server configurationemail.toml- Email configuration (optional)
Configuration System
- Hierarchical config with environment variable fallback (
OURCHAT_CONFIG_FILE) - Multiple file support with merging capability
- Configuration inheritance via
inheritfield - Located in
server/src/config.rs - Uses the
configcrate for TOML parsing - Default values defined in
server/base/src/constants.rs - Environment variable:
OURCHAT_LOGfor log level (instead ofRUST_LOG)
Key structs:
MainCfg- Primary configuration struct with all server settings (server/src/config.rs)RawMainCfg- Raw deserialization struct with serde attributesCfg- Aggregated configuration containing all sub-configsHttpCfg- HTTP server configuration (server/src/config/http.rs)
Loading order:
- Default values from constants (
server/base/src/constants.rs) - Config file values (with inheritance via
inheritfield) - Environment variables (limited)
- Command line arguments (for specific overrides)
Adding New Configuration Entries
Follow this pattern to add new configuration entries:
Step 1: Add default constant (server/base/src/constants.rs)
pub const fn default_enable_metrics() -> bool {
true
}
pub const fn default_metrics_snapshot_interval() -> Duration {
Duration::from_mins(1)
}
Step 2: Add field to MainCfg struct (server/src/config.rs)
pub struct MainCfg {
// ... existing fields
pub enable_metrics: bool,
pub metrics_snapshot_interval: Duration,
// ... more fields
}
Step 3: Add field to RawMainCfg with serde attributes (server/src/config.rs)
pub struct RawMainCfg {
// ... existing fields
#[serde(default = "constants::default_enable_metrics")]
pub enable_metrics: bool,
#[serde(
default = "constants::default_metrics_snapshot_interval",
with = "humantime_serde"
)]
pub metrics_snapshot_interval: Duration,
// ... more fields
}
Step 4: Update Deserialize implementation (server/src/config.rs)
Update the impl<'de> Deserialize<'de> for MainCfg to map from RawMainCfg to MainCfg:
impl<'de> Deserialize<'de> for MainCfg {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> {
let raw = RawMainCfg::deserialize(deserializer)?;
// ... validation
Ok(MainCfg {
// ... existing mappings
enable_metrics: raw.enable_metrics,
metrics_snapshot_interval: raw.metrics_snapshot_interval,
// ... more mappings
})
}
}
Step 5: Add validation if needed
Add validation logic in the Deserialize implementation for the new field:
if raw.metrics_snapshot_interval.is_zero() {
return Err(D::Error::custom("metrics_snapshot_interval cannot be zero"));
}
Step 6: Add to config file (docker/config/ourchat.toml)
# If record the metrics of the server, which will be used for monitoring and debugging
enable_metrics = true
metrics_snapshot_interval = "1m"
Notes:
- Use
#[serde(with = "humantime_serde")]forDurationfields - Use
#[serde(default = "constants::default_*")]to reference default functions - Use
size::Sizefor file size fields with human-readable string parsing - Add comprehensive validation in the
Deserializeimplementation
Development Workflow
Testing
The server uses a comprehensive test architecture with both unit and integration tests.
Test Directory Structure
Main test directories:
server/tests/- Primary test directoryserver/- Integration tests for server functionalitybasic_services/- Database and RabbitMQ testssession/- Session management testsfriend/- Friend management testsserver_manage/- Server management tests (including metrics)voip/- VoIP functionality testswebrtc/- WebRTC tests
http_test/- HTTP API testsmatrix_test/- Matrix protocol integration tests
Test data directory:
server/test_data/- Test certificates and filescerts/- TLS certificates for testingprivate/- Private keys- Test files like
test_avatar.png
Test Organization
Integration Tests:
- Located in
server/tests/ - Use
TestApphelper to launch full server instances - Test real server functionality with databases and message queues
- Organized by feature areas (auth, session, friend, etc.)
Test Helpers and Common Patterns
Key Test Helpers:
TestApp(server/client/src/oc_helper/client.rs) - Manages server lifecycle, creates isolated databases and RabbitMQ vhostsTestUser(server/client/src/oc_helper/user.rs) - Represents authenticated test users with methods for operations
Common Test Pattern:
#[tokio::test]
async fn test_name() {
let mut app = TestApp::new_with_launching_instance().await.unwrap();
// Test logic
app.async_drop().await;
}
Custom Configuration Pattern:
let (mut config, args) = TestApp::get_test_config().unwrap();
config.main_cfg.some_setting = some_value;
let mut app = TestApp::new_with_launching_instance_custom_cfg((config, args), |_| {})
.await
.unwrap();
Adding New Tests
Adding Integration Tests:
- Create new test file in appropriate subdirectory under
server/tests/server/ - Import dependencies:
use client::TestApp;,use claims::assert_*; - Write test function with
#[tokio::test]attribute - Use
TestAppto launch server instance - Create test users with
app.new_user().await.unwrap() - Perform operations using user methods
- Assert results using
claimsmacros or standard assertions - Clean up with
app.async_drop().await
Example authentication test:
#[tokio::test]
async fn auth_token() {
let mut app = TestApp::new_with_launching_instance().await.unwrap();
let user = app.new_user().await.unwrap();
assert_ok!(user.lock().await.ocid_auth().await);
app.async_drop().await;
}
Test Configuration and Setup
Test Dependencies (from Cargo.toml):
client- Test client crate withTestAppandTestUsertempfile- Temporary file management
Test Execution:
- Each test launches its own server instance on random port
- Database migrations run automatically for each test
- Cleanup happens via
async_drop()method
Running Tests
Test Utilities and Macros
Assertion Macros (from claims crate):
assert_ok!- Assert result is Okassert_err!- Assert result is Errassert_ge!,assert_gt!,assert_le!,assert_lt!- Comparison assertionsassert_some!,assert_none!- Option assertions
Test Helper Functions:
TestApp::new_session_db_level()- Create test session with usersTestUser::random_readable()- Create user with readable namesTestUser::random_unreadable()- Create user with random strings- Various assertion helpers in test files
Code Quality
- Structured logging with
tracingcrate
Database Operations
- Entity definitions in
server/entities/src/entities/ - Migrations managed via SeaORM
Common Development Tasks
Adding New API Endpoints
- Add gRPC service definition in
service/*.proto - Implement service in
server/src/process/ - Add HTTP route in
server/src/httpserver.rsif needed
Setting Logs
- Use OURCHAT_LOG instead of RUST_LOG, for example, OURCHAT_LOG=trace
Authentication Flow
- JWT tokens with 5-day expiration
- Password hashing with Argon2
- OAuth support via GitHub
- Session management with Redis
Important Notes
- Rate limiting is implemented with
tower-governor - Don't use mod.rs
Troubleshooting
Common Issues
Database migration failures:
- Check
docker/config/database.tomlcredentials - Run
python scripts/db_migration.py down -n 100to rollback migrations, then re-apply
Server fails to start:
- Check
log/directory for error logs