A production-ready, multi-tenant Expense Analytics & Reporting Platform built with .NET 8, Angular 17, and SQL Server.
┌─────────────────────────────────────────────────────────────────┐
│ CloudFront CDN │
│ (Angular 17 SPA on S3) │
└──────────────────────────┬──────────────────────────────────────┘
│ HTTPS
┌──────────────────────────▼──────────────────────────────────────┐
│ AWS Elastic Beanstalk (.NET 8 API) │
│ ┌──────────┐ ┌──────────┐ ┌────────────┐ ┌─────────────────┐ │
│ │ Auth │ │ Expenses │ │ Dashboard │ │ Reports │ │
│ │Controller│ │Controller│ │ Controller │ │ Controller │ │
│ └────┬─────┘ └────┬─────┘ └─────┬──────┘ └───────┬─────────┘ │
│ └─────────────┴─────────────┴────────────────┘ │
│ MediatR (CQRS) │
│ ┌─────────────┴─────────────┴────────────────┐ │
│ │ Application Layer │ │
│ │ Commands │ Queries │ Handlers │ DTOs │ │
│ └─────────────┬─────────────┬────────────────┘ │
│ │ │ │
│ ┌─────────────▼─────────────▼────────────────┐ │
│ │ Infrastructure Layer │ │
│ │ EF Core │ Repositories │ Dapper │ S3 │ │
│ └─────────────┬─────────────┬────────────────┘ │
└──────────────────────┼─────────────┼────────────────────────────┘
│ │
┌────────────▼──┐ ┌──────▼───────┐
│ RDS SQL Server│ │ AWS Redshift │
│ (Multi-AZ) │ │ (Analytics) │
└───────────────┘ └──────────────┘
| Layer | Technology |
|---|---|
| Backend API | .NET 8, ASP.NET Core Web API, C# |
| Backend MVC | ASP.NET Core MVC (admin views) |
| ORM | Entity Framework Core 8, Dapper (stored procedures) |
| Database | SQL Server (LocalDB dev / RDS prod) |
| Auth | JWT Bearer + Refresh Token Rotation |
| Architecture | CQRS (MediatR), AutoMapper, FluentValidation |
| Logging | Serilog (structured, console + file sinks) |
| Frontend | Angular 17 (standalone components) |
| UI Framework | Angular Material + custom SCSS |
| State Mgmt | NgRx (Store, Effects) |
| Charts | ApexCharts (ng-apexcharts) |
| Containerization | Docker + docker-compose |
| Cloud | AWS (EB, RDS, S3, CloudFront, Redshift) |
| IaC | Terraform |
| CI/CD | GitHub Actions + Jenkinsfile |
| Security Scan | SonarQube (SAST), Veracode (config) |
| Testing | xUnit, Moq, FluentAssertions |
- .NET 8 SDK
- Node.js 20+
- SQL Server (LocalDB or Docker)
- Docker & Docker Compose (optional)
- Angular CLI:
npm i -g @angular/cli@17
cd infrastructure
docker-compose up -dAPI: http://localhost:5000 | Angular: http://localhost:4200
# Backend
dotnet restore
dotnet build
dotnet run --project src/ExpensoTrack.API
# Frontend (the development proxy forwards /api to http://localhost:5045)
cd frontend
npm install
npm run lint
npm run build
npm startThe Development API profile uses the checked-in SQLite database. Production SQL Server deployments keep using the stored-procedure analytics path; local SQLite runs use the equivalent EF Core analytics queries.
dotnet restore ExpensoTrack.sln
docker-compose -f infrastructure/docker-compose.yml up -d
# Visit http://localhost:4200| Variable | Description | Required |
|---|---|---|
ConnectionStrings__DefaultConnection |
SQL Server connection string | Yes |
Jwt__Key |
JWT signing key (min 32 chars) | Yes |
Jwt__Issuer |
JWT issuer | Yes |
Jwt__Audience |
JWT audience | Yes |
AWS__Region |
AWS region | Prod |
AWS__S3__BucketName |
S3 bucket for receipts | Prod |
SONAR_TOKEN |
SonarQube auth token | CI/CD |
SONAR_HOST_URL |
SonarQube server URL | CI/CD |
VERACODE_API_ID |
Veracode API ID | CI/CD |
VERACODE_API_KEY |
Veracode API Key | CI/CD |
AWS_ACCESS_KEY_ID |
AWS access key | Deploy |
AWS_SECRET_ACCESS_KEY |
AWS secret key | Deploy |
CLOUDFRONT_DISTRIBUTION_ID |
CloudFront dist ID for invalidation | Deploy |
| Method | Route | Description | Auth |
|---|---|---|---|
| POST | /api/auth/login |
Login, get JWT | Public |
| POST | /api/auth/refresh |
Refresh token | Public |
| Method | Route | Description | Auth |
|---|---|---|---|
| GET | /api/expenses |
Get all expenses | Admin, Manager |
| GET | /api/expenses/{id} |
Get expense by ID | Authenticated |
| GET | /api/expenses/my |
Get current user expenses | Authenticated |
| POST | /api/expenses |
Create expense | Authenticated |
| PUT | /api/expenses/{id} |
Update expense | Authenticated |
| DELETE | /api/expenses/{id} |
Delete expense | Authenticated |
| POST | /api/expenses/{id}/approve |
Approve expense | Admin, Manager |
| POST | /api/expenses/{id}/reject |
Reject expense | Admin, Manager |
| Method | Route | Description | Auth |
|---|---|---|---|
| GET | /api/dashboard/department-summary |
Dept-wise totals | Admin, Manager |
| GET | /api/dashboard/monthly-trends |
Monthly expense trends | Authenticated |
| GET | /api/dashboard/pending-approvals/{id} |
Manager's approval queue | Admin, Manager |
| GET | /api/dashboard/top-spenders |
Top spenders leaderboard | Admin, Manager |
| Method | Route | Description | Auth |
|---|---|---|---|
| GET | /api/reports/expense-summary |
Dept expense summary | Admin, Manager |
| GET | /api/reports/monthly |
Monthly report | Admin, Manager |
| GET | /api/reports/top-spenders |
Top spenders report | Admin, Manager |
| Method | Route | Description | Auth |
|---|---|---|---|
| GET | /api/users |
List all users | Admin |
| GET | /api/users/{id} |
Get user | Admin |
| POST | /api/users |
Create user | Admin |
| DELETE | /api/users/{id} |
Delete user | Admin |
- Users — Id, Email, PasswordHash, Role, DepartmentId, CreatedAt, RefreshToken
- Departments — Id, Name, BudgetLimit, ManagerId
- Expenses — Id, Title, Amount, CategoryId, UserId, Status, SubmittedAt, ApprovedAt, ReceiptUrl, Description
- ExpenseCategories — Id, Name, MaxLimit
- ApprovalWorkflows — Id, ExpenseId, ApproverId, Status, Comments, ActionedAt
- AuditLogs — Id, UserId, Action, EntityType, EntityId, Timestamp, IpAddress
All stored procedures are in src/ExpensoTrack.Infrastructure/Data/StoredProcedures/ and called via Dapper:
- sp_GetExpenseSummaryByDepartment — Returns dept-wise totals, averages, counts with date range filter
- sp_GetMonthlyTrends — Month-over-month expense trends for 12 months
- sp_GetPendingApprovals — Manager's approval queue with aging days
- sp_GetTopSpenders — Leaderboard of top N spenders
- sp_ApproveExpense — Transactional approval with audit log insert
- 3 departments (Engineering, Marketing, Finance)
- 10 users (1 Admin, 3 Managers, 6 Employees)
- 5 expense categories
- 55 expenses across all statuses
| Role | Password | |
|---|---|---|
| Admin | admin@expensotrack.com | Password123! |
| Manager | john.manager@expensotrack.com | Password123! |
| Manager | sarah.manager@expensotrack.com | Password123! |
| Manager | david.manager@expensotrack.com | Password123! |
| Employee | alice@expensotrack.com | Password123! |
| Employee | bob@expensotrack.com | Password123! |
cd infrastructure/terraform
terraform init
terraform plan -var="db_username=admin" -var="db_password=YourSecurePass"
terraform applyResources provisioned:
- VPC with public/private subnets
- Elastic Beanstalk (.NET 8) for API
- RDS SQL Server (Multi-AZ, db.t3.medium)
- S3 + CloudFront for Angular SPA
- Redshift cluster (dc2.large) for analytics
- IAM roles and policies
Add these in Settings > Secrets and variables > Actions:
SONAR_TOKEN— SonarQube authentication tokenSONAR_HOST_URL— SonarQube server URLVERACODE_API_ID— Veracode API IDVERACODE_API_KEY— Veracode API KeyAWS_ACCESS_KEY_ID— AWS IAM access keyAWS_SECRET_ACCESS_KEY— AWS IAM secret keyCLOUDFRONT_DISTRIBUTION_ID— CloudFront distribution ID
The workflow always runs the .NET and Angular build/test checks and publishes the Docker image on pushes. SonarQube, Veracode, and AWS deployment are optional integrations: each is enabled only when its required secrets are present. The workflow summary reports which integrations were enabled or skipped, so a local development repository can validate the application without placeholder secrets.
- Build .NET solution and Angular frontend
- Run xUnit tests + OpenCover coverage
- SonarQube SAST analysis (when configured)
- Veracode security scan upload (when configured)
- Docker build & push to GHCR
- Deploy API to Elastic Beanstalk (when AWS secrets are configured)
- Deploy Angular to S3 (when AWS secrets are configured)
- Invalidate CloudFront cache (when AWS secrets are configured)
- JWT with refresh token rotation (7-day expiry)
- Role-based authorization on all endpoints (Admin/Manager/Employee)
- OWASP-aligned: parameterized queries via EF Core, input validation via FluentValidation
- Rate limiting: 100 requests/minute per IP (AspNetCoreRateLimit)
- Security headers: X-Content-Type-Options, X-Frame-Options, HSTS, CSP, Referrer-Policy
- SonarQube: configured via
sonar-project.properties, quality gate enforced - Veracode: SAST config in
.veracode.yml - HTTPS redirect middleware enabled
dotnet test ExpensoTrack.sln --collect:"XPlat Code Coverage" --results-directory ./coverage -- DataCollectionRunSettings.DataCollectors.DataCollector.Configuration.Format=opencoverTest categories:
- Unit tests: Handler tests, Validator tests, Mapping tests (Moq + FluentAssertions)
- Integration tests: Repository tests (EF Core InMemory provider)
Set up a GitHub Projects board with columns:
- Backlog | Sprint | In Progress | Review | Done
Suggested initial issues:
- Setup CI/CD pipeline
- Configure AWS infrastructure
- Add integration tests
- Performance testing & optimization
- Fork the repository
- Create a feature branch (
git checkout -b feature/amazing-feature) - Commit changes (
git commit -m 'Add amazing feature') - Push to branch (
git push origin feature/amazing-feature) - Open a Pull Request
MIT