A multi-tenant construction management system built with Rust, Axum, and PostgreSQL.
- Backend: Rust with Axum web framework
- Database: PostgreSQL with SQLx
- Auth: Keycloak JWT validation
- Architecture: Modular Monolith with DDD Bounded Contexts
- Rust 1.70+ (edition 2021)
- PostgreSQL 14+
- Keycloak instance (for production auth)
git clone <repo-url>
cd schreinerei
cp .env.example .envCreate .env file:
DATABASE_URL=postgres://user:password@localhost:5432/schreinerei
KEYCLOAK_URL=https://auth.jakob-lingel.dev
KEYCLOAK_REALM=schreinerei
JWT_ISSUER=https://auth.jakob-lingel.dev/realms/schreinerei
APP_HOST=0.0.0.0
APP_PORT=3000
RUST_LOG=debug# Create database
createdb schreinereiMigrations run automatically on startup (only unapplied migrations).
Or run manually:
cargo sqlx migrate runcargo runServer starts at http://localhost:3000
cargo testcargo export-typesThis writes the committed Rust-to-TypeScript contract file at frontend/src/types/generated.ts.
Integration tests require a running PostgreSQL database. They use sqlx::test which creates isolated test databases.
# Ensure DATABASE_URL is set
export DATABASE_URL=postgres://user:password@localhost:5432/schreinerei
# Run all tests including integration tests
cargo test --all
# Run specific integration test file
cargo test --test tenant_isolation_testThe sqlx::test macro automatically creates a temporary database for each test. No manual setup required beyond having PostgreSQL running.
src/
├── main.rs # Application entry point
├── lib.rs # Library root with AppState
├── config.rs # Configuration loading
├── common/ # Shared types and utilities
│ ├── types.rs # TenantId, UserId, Role
│ ├── error.rs # Error types
│ └── db.rs # Database utilities
├── auth/ # Authentication
│ ├── jwt.rs # JWT validation
│ ├── jwks.rs # JWKS client
│ ├── extractor.rs # Axum extractors
│ └── middleware.rs # Auth middleware
└── modules/ # Business modules (DDD)
└── iam/ # Identity & Access Management
├── domain/ # Entities, value objects
├── application/ # Services, use cases
├── infrastructure/ # Repositories
└── api/ # HTTP routes
GET /health- Health check
GET /api/v1/auth/me- Get current userPATCH /api/v1/users/me- Update own profileGET /api/v1/users- List users in tenantPOST /api/v1/users/invite- Invite user (admin only)PATCH /api/v1/users/:id/role- Update user role (admin only)
GET /api/v1/inventory/categories- List categoriesPOST /api/v1/inventory/categories- Create categoryGET /api/v1/inventory/materials- List materialsPOST /api/v1/inventory/materials- Create materialPOST /api/v1/inventory/materials/:id/withdraw- Withdraw stockPOST /api/v1/inventory/materials/:id/adjust- Adjust stockPOST /api/v1/inventory/materials/:id/qr- Generate QR codeGET /api/v1/inventory/low-stock- List low stock itemsGET /api/v1/inventory/orders- List order requestsPOST /api/v1/inventory/orders- Create order requestPOST /api/v1/inventory/orders/:id/approve- Approve order
# Setup test tenant
./scripts/setup-test-data.sh
# Run API tests (requires Keycloak user with tenant_id attribute)
[email protected] TEST_PASSWORD=yourpassword ./scripts/test-api.shSee docs/API-TESTING.md for detailed setup instructions.
# Create new migration
cargo sqlx migrate add <migration_name>
# Run migrations
cargo sqlx migrate run
# Revert last migration
cargo sqlx migrate revert# Format code
cargo fmt
# Lint
cargo clippy -- -D warnings
# Check compilation
cargo checkUse the selective runner to mirror CI without paying for unrelated checks:
# Fast staged checks, intended for pre-commit
./scripts/ci-selective.sh --staged --fast
# CI-equivalent checks for everything on your branch since it diverged from main
./scripts/ci-selective.sh --branch
# Force both backend and frontend checks
./scripts/ci-selective.sh --allThe runner uses these scopes:
frontend:frontend/**,dockerfile.frontend,nginx.confbackend:src/**,migrations/**,Cargo.toml,Cargo.lock,dockerfile.backendcontract: backend files that can changefrontend/src/types/generated.tsshared: workflow files and.dockerignore, which fan out to both backend and frontend checks
That means frontend-only changes skip Rust checks locally and in GitHub Actions, while backend API or ts-rs changes still force the frontend build path.
When Docker-related files change, the full runner also validates the affected image build locally:
dockerfile.backend-> backend image builddockerfile.frontendornginx.conf-> frontend image build.dockerignore-> both image builds
To install the versioned hooks for this worktree:
./scripts/install-git-hooks.shThe installer chains the repo hooks after any existing Beads-managed hooks in the active worktree hook directory.
Installed hooks:
pre-commit: runs full selective checks for staged changespre-push: runs CI-equivalent checks for the current branch range