OmniDesk is a production-style multi-tenant Helpdesk SaaS built to showcase real backend engineering: authentication, org-level RBAC, tenant isolation, integration testing with Testcontainers, and (next) tickets, audit logs, caching, rate limiting, and background jobs.
- ASP.NET Core (net9.0)
- EF Core + PostgreSQL
- ASP.NET Core Identity (GUID keys)
- JWT Authentication
- Multi-tenancy via
OrganizationId+X-Org-Idrequest header - Policy-based Authorization (RBAC): Owner/Admin/Member/Viewer
- Swagger (Swashbuckle) + Serilog
- Integration Tests: xUnit + Testcontainers PostgreSQL + Respawn
- Next.js + React + TypeScript + Tailwind CSS
/src
/OmniDesk.Api
/OmniDesk.Application
/OmniDesk.Domain
/OmniDesk.Infrastructure
/tests
/OmniDesk.Api.IntegrationTests
/infra
docker-compose.yml
/docs
Uses Docker Compose. Your local dev DB can be separate from the integration test DB.
docker compose -f infra/docker-compose.yml up -d
docker psdotnet ef database update --project src/OmniDesk.Infrastructure --startup-project src/OmniDesk.Apidotnet run --project src/OmniDesk.ApiOpen:
- Swagger:
http://localhost:<port>/swagger - Health:
http://localhost:<port>/health
Integration tests run against a fresh PostgreSQL container automatically:
dotnet testPOST /auth/registerPOST /auth/loginGET /auth/me
GET /orgs(list orgs where current user is a member)POST /orgs(create org and make creator Owner)
GET /orgs/{orgId}/members(Admin+)PATCH /orgs/{orgId}/members/{userId}(Owner only)
Tenant context is resolved from:
X-Org-Id: <org-guid>
For tenant-scoped endpoints, clients should send:
Authorization: Bearer <token>X-Org-Id: <orgId>
- Missing or mismatched
X-Org-Idmay currently yield 403 Forbidden (authorization fails before MVC filters run).- We can later adjust pipeline to return 400 BadRequest for tenant header errors.
- FluentAssertions prints a license warning; we may replace it with built-in xUnit asserts or another assertion library later.
Portfolio project (no production license currently).