Backend system for a mechanic shop — built as the Tech Challenge for FIAP POS Tech (15SOAT), covering Phase 1 and Phase 2.
A RESTful API that manages the full lifecycle of service orders, customers, vehicles, parts, and inventory for a medium-sized auto repair shop. On every service order status change, the customer receives an automatic email notification.
Architecture: Clean Architecture + DDD Domain
Stack: C# 12 · ASP.NET Core 8 · PostgreSQL 16 · Entity Framework Core 8
Infra: Docker · Kubernetes (AWS EKS) · Terraform
Docs: /docs
Fase 2 expands Fase 1 with Clean Architecture, automated email notifications, Kubernetes on AWS EKS, Terraform IaC, and a GitHub Actions CI/CD pipeline — turning the original monolith into a production-ready, scalable system.
GitHub Actions (CI/CD)
│
├─ coverage.yml ──→ unit tests · 80% coverage gate · GitHub Pages report
│
└─ deploy.yml ──→ docker build · push GHCR · EF migrations · kubectl apply
│
▼
AWS EKS Cluster (Terraform-provisioned VPC + EKS)
┌─────────────────────────────────────────────┐
│ Namespace: mechanics-software │
│ │
│ ┌─────────────────────┐ │
│ │ API Deployment │◄── HPA │
│ │ ASP.NET Core 8 │ (auto-scale) │
│ │ N replicas │ │
│ └──────────┬──────────┘ │
│ │ ClusterIP │
│ ▼ │
│ ┌─────────────────────┐ │
│ │ PostgreSQL 16 │◄── PVC │
│ └─────────────────────┘ (persistent) │
│ │
│ LoadBalancer ──────────────────► Internet │
└─────────────────────────────────────────────┘
git push → main
│
├── coverage.yml → dotnet test → enforce 80% line coverage → publish HTML report
│
└── deploy.yml → docker build → push to GHCR
│
└─→ kubectl apply (initContainer runs EF migrations first)
- .NET 8 SDK
- Docker + Docker Compose
docker compose up --buildAPI available at: http://localhost:8080
Swagger UI: http://localhost:8080/swagger
Migrations and the default admin user are applied automatically on startup.
1. Start the database
docker compose up db -dPostgreSQL will be available at localhost:5435.
2. Run the API
dotnet run --project src/MechanicsSoftware.API/MechanicsSoftware.API.csprojSwagger UI: http://localhost:5066/swagger
On first startup the application automatically applies all pending migrations and creates a default admin user.
| Field | Value |
|---|---|
admin@mechanics.local |
|
| Password | Admin@123 |
Override with SEED_ADMIN_EMAIL and SEED_ADMIN_PASSWORD environment variables.
Call POST /api/auth/login with the credentials above:
{
"email": "admin@mechanics.local",
"password": "Admin@123"
}Copy the token from the response.
- Open
http://localhost:5066/swagger - Click the Authorize button (🔒)
- Paste the token value and click Authorize
All protected endpoints will now work.
| Variable | Required | Description | Default |
|---|---|---|---|
JWT_SECRET |
Yes (production) | Secret key for JWT signing (min 32 chars) | pre-configured in appsettings.Development.json |
DATABASE_URL |
No | PostgreSQL connection string | see appsettings.Development.json |
JWT_EXPIRATION_MINUTES |
No | Token expiration in minutes | 60 |
BCRYPT_SALT_ROUNDS |
No | BCrypt cost factor | 12 |
SEED_ADMIN_EMAIL |
No | Default admin email | admin@mechanics.local |
SEED_ADMIN_PASSWORD |
No | Default admin password | Admin@123 |
SMTP_HOST |
Yes (production) | SMTP server hostname | — |
SMTP_PORT |
Yes (production) | SMTP server port (e.g. 587) |
— |
SMTP_USER |
Yes (production) | SMTP username | — |
SMTP_PASS |
Yes (production) | SMTP password | — |
SMTP_FROM |
Yes (production) | Sender address for notification emails | — |
Every service order status change triggers an automatic email to the customer. The email is sent by SmtpEmailNotifier (Infrastructure layer), which implements the IEmailNotifier abstraction defined in the Application layer.
Notifications are sent on the following transitions:
| Transition | Handler |
|---|---|
| RECEIVED → IN_DIAGNOSIS | StartDiagnosisHandler |
| IN_DIAGNOSIS → AWAITING_APPROVAL | SendBudgetHandler |
| AWAITING_APPROVAL → IN_EXECUTION | ApproveServiceOrderHandler |
| AWAITING_APPROVAL → CANCELLED | RejectServiceOrderHandler |
| IN_EXECUTION → COMPLETED | CompleteServiceOrderHandler |
| COMPLETED → DELIVERED | DeliverServiceOrderHandler |
The project uses a local dotnet-ef tool pinned in .config/dotnet-tools.json. Run dotnet tool restore once to install it, then use dotnet dotnet-ef instead of dotnet ef.
dotnet dotnet-ef database update \
--project src/MechanicsSoftware.Infrastructure/MechanicsSoftware.Infrastructure.csproj \
--startup-project src/MechanicsSoftware.API/MechanicsSoftware.API.csprojdotnet dotnet-ef migrations add <MigrationName> \
--project src/MechanicsSoftware.Infrastructure/MechanicsSoftware.Infrastructure.csproj \
--startup-project src/MechanicsSoftware.API/MechanicsSoftware.API.csproj \
--output-dir Persistence/Migrationsdotnet dotnet-ef migrations remove \
--project src/MechanicsSoftware.Infrastructure/MechanicsSoftware.Infrastructure.csproj \
--startup-project src/MechanicsSoftware.API/MechanicsSoftware.API.csproj| Method | Endpoint | Description |
|---|---|---|
POST |
/api/auth/login |
Authenticate and receive JWT |
GET |
/api/service-orders/{id}/status |
Check service order status (for customers) |
GET |
/health |
Liveness/readiness probe |
| Resource | Endpoints |
|---|---|
| Customers | GET/POST /api/customers · GET/PUT/DELETE /api/customers/{id} |
| Vehicles | GET/POST /api/vehicles · GET/PUT/DELETE /api/vehicles/{id} |
| Parts | GET/POST /api/parts · GET/PUT/DELETE /api/parts/{id} · PATCH /api/parts/{id}/stock |
| Services | GET/POST /api/services · GET/PUT/DELETE /api/services/{id} |
| Service Orders | GET/POST /api/service-orders · full lifecycle via action endpoints |
Full documentation and request/response schemas available at /swagger when running.
POST /api/auth/login
Content-Type: application/json
{
"email": "admin@mechanics.local",
"password": "Admin@123"
}{ "token": "<JWT>" }Use the token as Authorization: Bearer <JWT> on all protected requests.
POST /api/customers
Authorization: Bearer <JWT>
Content-Type: application/json
{
"name": "João da Silva",
"documentValue": "123.456.789-09",
"personType": "INDIVIDUAL",
"email": "joao@example.com",
"phone": "(11) 99999-0001"
}{ "id": "3fa85f64-...", "name": "João da Silva", ... }POST /api/service-orders
Authorization: Bearer <JWT>
Content-Type: application/json
{
"customerId": "<customer-id>",
"vehicleId": "<vehicle-id>"
}{ "id": "<order-id>", "status": "RECEIVED", ... }# Move to diagnosis
POST /api/service-orders/{id}/start-diagnosis
# Add a service and a part
POST /api/service-orders/{id}/services
{ "serviceId": "<id>", "quantity": 1 }
POST /api/service-orders/{id}/parts
{ "partId": "<id>", "quantity": 2 }
# Generate and send budget to customer (triggers email)
POST /api/service-orders/{id}/budget
POST /api/service-orders/{id}/send-budget
# Customer approves or rejects
POST /api/service-orders/{id}/budget-decision
{ "decision": "approve" } # or "reject"
# Complete and deliver
POST /api/service-orders/{id}/start-execution
POST /api/service-orders/{id}/complete
POST /api/service-orders/{id}/deliverGET /api/service-orders/{id}/status{ "status": "IN_EXECUTION" }RECEIVED → IN_DIAGNOSIS → AWAITING_APPROVAL → IN_EXECUTION → COMPLETED → DELIVERED
↓
CANCELLED
src/
MechanicsSoftware.Domain/ # Entities, value objects, domain rules (innermost layer)
MechanicsSoftware.Application/ # Use cases, command/query handlers, abstractions (IEmailNotifier, IAppDbContext)
MechanicsSoftware.Infrastructure/ # EF Core, JWT, BCrypt, SmtpEmailNotifier
MechanicsSoftware.API/ # Controllers, middleware, Swagger, DI composition root
tests/
MechanicsSoftware.UnitTests/ # Domain + Application + Infrastructure unit tests
MechanicsSoftware.IntegrationTests/ # Full HTTP integration tests (WebApplicationFactory)
k8s/ # Kubernetes manifests (AWS EKS)
infra/ # Terraform IaC (VPC + EKS cluster)
See docs/architecture/overview.md for full details.
Manifests live in k8s/ and target an AWS EKS cluster provisioned by Terraform.
| File | Purpose |
|---|---|
namespace.yaml |
Dedicated namespace |
configmap.yaml |
Non-secret configuration |
secret.yaml |
JWT secret, DB credentials, SMTP credentials |
deployment-api.yaml |
API deployment (runs EF migrations via initContainer) |
deployment-db.yaml |
PostgreSQL deployment |
service-api.yaml |
LoadBalancer service for the API |
service-db.yaml |
ClusterIP service for PostgreSQL |
pvc.yaml |
Persistent volume claim for PostgreSQL data |
hpa.yaml |
Horizontal Pod Autoscaler |
kubectl apply -f k8s/Infrastructure is defined in infra/ using the terraform-aws-modules/eks/aws and terraform-aws-modules/vpc/aws modules.
cd infra
terraform init
terraform plan
terraform applySee infra/README.md for variable descriptions and prerequisites.
Two GitHub Actions workflows run on every push/PR to main:
| Workflow | File | What it does |
|---|---|---|
| Coverage Report | .github/workflows/coverage.yml |
Builds, runs unit tests, enforces 80% line coverage, publishes HTML report to GitHub Pages |
| Deploy | .github/workflows/deploy.yml |
Builds & pushes Docker image to GHCR, runs EF Core migrations via initContainer, applies K8s manifests to EKS |
Coverage report: rnataoliveira.github.io/mechanics-software
# Unit tests
dotnet test tests/MechanicsSoftware.UnitTests
# Unit tests with coverage report
dotnet test tests/MechanicsSoftware.UnitTests \
--collect:"XPlat Code Coverage" \
--settings coverlet.runsettings \
--results-directory ./coverage-results
# Integration tests (requires Docker — starts PostgreSQL automatically)
dotnet test tests/MechanicsSoftware.IntegrationTests| Document | Path |
|---|---|
| Architecture Overview | docs/architecture/overview.md |
| Event Storming | docs/domain/event-storming.md |
| Ubiquitous Language | docs/domain/ubiquitous-language.md |
| Aggregates & Entities | docs/domain/aggregates-and-entities.md |
| Bounded Contexts | docs/domain/bounded-contexts.md |
| ADR-001 Tech Stack | docs/decisions/ADR-001-tech-stack.md |
| ADR-002 Architecture | docs/decisions/ADR-002-architecture.md |
| ADR-003 Database | docs/decisions/ADR-003-database.md |
| ADR-004 Application Layer | docs/decisions/ADR-004-application-layer-conventions.md |
| ADR-005 Clean Architecture Migration | docs/decisions/ADR-005-clean-architecture-migration.md |
| ADR-006 Database Migration Strategy | docs/decisions/ADR-006-database-migration-strategy.md |
See CONTRIBUTING.md for project conventions, commit message format, and branch naming rules.