Core multi-tenant SaaS foundation for a marketing management platform.
- .NET SDK 10.x
- Docker Desktop (or Docker Engine with Compose)
- PostgreSQL client tools (optional, for manual checks)
docker compose up -d
This starts PostgreSQL on localhost:5432 with:
- user:
postgres - password:
postgres
dotnet restore
dotnet build PostyLand.slndotnet ef database update --context MainDbContext --project src/PostyLand.Persistence --startup-project src/PostyLand.APIIf dotnet ef is missing:
dotnet tool install --global dotnet-efdotnet run --project src/PostyLand.API/PostyLand.API.csprojDefault development URL is:
http://localhost:5166
Optional background start script:
.\scripts\start-api.ps1Invoke-RestMethod -Method Get -Uri "http://localhost:5166/health"$body = @{
name = "Acme Inc"
subdomain = "acme"
adminEmail = "owner@acme.com"
adminPassword = "StrongPass123!"
plan = "Pro"
employeeLimit = 25
renewalDate = "2030-01-01T00:00:00Z"
} | ConvertTo-Json
$register = Invoke-RestMethod -Method Post -Uri "http://localhost:5166/api/tenants/register" -ContentType "application/json" -Body $body
$registerExpected response includes:
tenantIdonboardingJobIdstatus
Generate token:
$tenantId = $register.tenantId
$userId = [guid]::NewGuid().ToString()
$token = .\scripts\gen-jwt.ps1 -TenantId $tenantId -UserId $userId -Role "Owner" -Scope "tenant.api"Call tenant diagnostics endpoint:
Invoke-RestMethod -Method Get -Uri "http://localhost:5166/api/tenant/ping" -Headers @{
Authorization = "Bearer $token"
"X-Tenant-Subdomain" = "acme"
}dotnet test PostyLand.sln- Health:
GET /health - Tenant registration:
POST /api/tenants/register - Tenant diagnostic:
GET /api/tenant/ping(JWT + tenant required) - Hangfire dashboard:
/hangfire(platform admin policy) - Tenant migration endpoint:
POST /api/admin/tenants/{tenantId}/migrations/run(platform admin policy)
- Tenant resolution for local development should use header
X-Tenant-Subdomain. - External provisioning (Route53/S3) is disabled in development by default (
Provisioning:DisableExternalProvisioning=true).