An enterprise-grade web platform for TechMove Logistics — managing international freight contracts, driver schedules, service requests, and financial reporting with automatic currency conversion.
Built with ASP.NET Core 8, Entity Framework Core, SQL Server, and Docker.
┌─────────────────┐ HTTP ┌─────────────────┐ EF Core ┌──────────────┐
│ GLMS.Web │ ──────────► │ GLMS.API │ ──────────────► │ SQL Server │
│ (MVC Client) │ HttpClient │ (Web API) │ Repository │ (Azure SQL │
│ Port 5000 │ ◄────────── │ Port 5001 │ ◄────────────── │ Edge) │
└─────────────────┘ JSON └─────────────────┘ └──────────────┘
│
│ HttpClient
▼
┌──────────────────┐
│ ExchangeRate API │
│ (USD → ZAR) │
└──────────────────┘
The system follows a Service-Oriented Architecture (SOA):
- GLMS.Web — ASP.NET Core MVC frontend. Zero database dependency. Communicates with the API via
HttpClient. - GLMS.API — ASP.NET Core Web API backend. Owns all business logic, database access, and external integrations. Exposes RESTful endpoints with Swagger/OpenAPI documentation.
- GLMS.Core — Shared class library containing models, DTOs, interfaces, services, and design pattern implementations.
- GLMS.Infrastructure — Entity Framework Core data access layer with repository implementations.
- GLMS.Tests — xUnit test project with 49 tests (35 unit + 14 integration).
- Contract Management — Full CRUD with status workflow (Draft → Active → Expired / On Hold)
- Service Request Processing — Create requests against active contracts with automatic validation
- Currency Conversion — Live USD-to-ZAR exchange rates from ExchangeRate-API, with caching and fallback
- PDF File Handling — Upload signed agreements (PDF only, UUID-named), download via UI
- JWT Authentication — Secure API endpoints with token-based auth
- Search & Filter — LINQ-powered contract filtering by status, date range, and client name
- Automated Testing — Unit tests (currency math, file validation, workflow rules, factory) and integration tests (API roundtrips, auth, data integrity)
- Docker Deployment — Full containerisation with Docker Compose (3 containers)
| Pattern | Implementation | Purpose |
|---|---|---|
| Factory Method | IContractFactory / ContractFactory |
Creates contracts with correct defaults based on service level |
| Strategy | IValidationStrategy<T> with 3 concrete strategies |
Decouples validation logic (status transitions, file types, currency) from controllers |
| Observer | IContractObserver with ServiceRequestStatusObserver, AuditLogObserver |
Cascades contract status changes to dependent service requests and audit logs |
| Layer | Technology |
|---|---|
| Frontend | ASP.NET Core 8 MVC, Razor Views, Bootstrap 5, jQuery Validation |
| Backend API | ASP.NET Core 8 Web API, Swagger/OpenAPI |
| Database | SQL Server (Azure SQL Edge for ARM Mac), Entity Framework Core 8 |
| Authentication | JWT Bearer Tokens (BCrypt password hashing) |
| Testing | xUnit, Moq, WebApplicationFactory, EF Core InMemory |
| Containerisation | Docker, Docker Compose |
| External API | open.er-api.com (no API key required) |
proj.net/
├── GLMS.sln
├── docker-compose.yml
├── docs/
│ ├── Part1-Architecture-Report.md # TOGAF, design patterns, UML, scalability
│ └── Part3-Technical-Reflection.md # CI/CD and containerisation reflection
├── src/
│ ├── GLMS.API/ # Web API (backend)
│ │ ├── Controllers/ # Auth, Clients, Contracts, ServiceRequests, Currency
│ │ ├── Dockerfile
│ │ └── Program.cs # DI, JWT, Swagger, EF Core setup
│ ├── GLMS.Core/ # Shared library
│ │ ├── DTOs/ # Request/Response objects
│ │ ├── Interfaces/ # Repository, Service, Pattern interfaces
│ │ ├── Models/ # Client, Contract, ServiceRequest, User, Enums
│ │ └── Services/ # Factory, Strategy, Observer implementations
│ ├── GLMS.Infrastructure/ # Data access
│ │ ├── Data/GlmsDbContext.cs # EF Core context with Fluent API + seed data
│ │ └── Repositories/ # Contract, Client, ServiceRequest repos
│ └── GLMS.Web/ # MVC frontend
│ ├── Controllers/ # Home, Account, Clients, Contracts, ServiceRequests
│ ├── Dockerfile
│ ├── Services/GlmsApiClient.cs # HttpClient wrapper for API calls
│ └── Views/ # Razor views with Bootstrap 5
├── tests/
│ └── GLMS.Tests/
│ ├── Unit/ # Currency, FileValidation, Workflow, Factory tests
│ └── Integration/ # API endpoint tests with WebApplicationFactory
└── uploads/ # Simulated file server for PDF agreements
- .NET 8 SDK
- Docker Desktop (only needed for Docker deployment)
The API reads the connection string in this priority order:
- Machine environment variable
GLMS_CONNECTION_STRING(if set) - appsettings.json
ConnectionStrings:DefaultConnection(fallback — defaults to LocalDB)
The database is auto-created on first run via EnsureCreated() — no manual SQL scripts needed.
To set the environment variable (Windows):
# System-level (persists across reboots, requires admin PowerShell)
[System.Environment]::SetEnvironmentVariable("GLMS_CONNECTION_STRING", "Server=(localdb)\MSSQLLocalDB;Database=GLMSDB;Trusted_Connection=True;TrustServerCertificate=True", "Machine")
# Or User-level
setx GLMS_CONNECTION_STRING "Server=(localdb)\MSSQLLocalDB;Database=GLMSDB;Trusted_Connection=True;TrustServerCertificate=True"To set the environment variable (macOS/Linux):
export GLMS_CONNECTION_STRING="Server=localhost,1433;Database=GLMSDB;User Id=sa;Password=YourPassword;TrustServerCertificate=True"If no environment variable is set, the app uses the LocalDB connection string from appsettings.json, which works out-of-the-box on any Windows machine with SQL Server LocalDB installed (included with Visual Studio).
Runs everything in containers — no local SQL Server or .NET runtime needed beyond Docker.
cp .env.example .env # then set SA_PASSWORD and JWT_KEY
docker-compose up --build| Service | URL |
|---|---|
| Frontend | http://localhost:5000 |
| API Swagger | http://localhost:5001/swagger |
# Start SQL Server in Docker
docker run -e "ACCEPT_EULA=Y" -e "MSSQL_SA_PASSWORD=YourStrong@Passw0rd" -p 1433:1433 -d mcr.microsoft.com/azure-sql-edge
# Build and run the API
cd src/GLMS.API
dotnet run
# In a separate terminal, run the Web frontend
cd src/GLMS.Web
dotnet run| Username | Password | Role |
|---|---|---|
admin |
Admin@123 |
Admin |
# Run all tests (unit + integration)
dotnet test
# Run only unit tests
dotnet test --filter "FullyQualifiedName~Unit"
# Run only integration tests
dotnet test --filter "FullyQualifiedName~Integration"Test Summary (49 tests):
| Category | Tests | What's Covered |
|---|---|---|
| Currency Calculation | 6 | Conversion math, rounding, zero/negative amounts, API fallback |
| File Validation | 9 | PDF accepted, exe/docx/null/empty/oversized rejected, Theory with multiple types |
| Contract Workflow | 12 | Status transitions (valid + invalid), service request blocking, observer notification |
| Contract Factory | 8 | Standard/Premium defaults, title, clientId, dates, auto-correction |
| Contract API | 6 | GET list, POST create, create-then-read integrity, PATCH status, 404, invalid transition |
| Service Request API | 2 | Create on active contract (201), create on expired contract (409) |
| Auth API | 2 | Valid login returns token, invalid login returns 401 |
| Method | Endpoint | Description |
|---|---|---|
POST |
/api/auth/login |
Authenticate and receive JWT token |
GET |
/api/contracts |
List contracts (filterable by status, dates, client) |
GET |
/api/contracts/{id} |
Get single contract |
POST |
/api/contracts |
Create new contract |
PATCH |
/api/contracts/{id}/status |
Update contract status |
POST |
/api/contracts/{id}/upload |
Upload signed agreement PDF |
GET |
/api/contracts/{id}/download |
Download signed agreement |
GET |
/api/clients |
List all clients |
POST |
/api/clients |
Create new client |
GET |
/api/servicerequests?contractId={id} |
List service requests for a contract |
POST |
/api/servicerequests |
Create service request (auto-converts USD to ZAR) |
GET |
/api/currency/rate?from=USD&to=ZAR |
Get current exchange rate |
All endpoints except /api/auth/login require a Bearer token in the Authorization header.
The database is pre-populated with realistic logistics data for immediate exploration:
- 8 clients — Maersk, DHL, Cape Town Port Authority, Shanghai Express, Amazon SA, FedEx SA, Transnet, MSC
- 12 contracts — Across all statuses (5 Active, 3 Draft, 2 Expired, 1 On Hold) with Standard and Premium service levels
- 14 service requests — Freight shipments, container storage, courier services with USD/ZAR costs
I designed and built GLMS end to end as my Programming 3A (PROG7311) portfolio project in my final year of the Bachelor of Computer and Information Sciences (Application Development) at The IIE's Varsity College. The client, TechMove Logistics, is a fictional company from the brief. The project covers:
- Architecture: splitting a monolith into a service-oriented Web + API design, documented with TOGAF and UML in
docs/ - Design patterns: Factory Method, Strategy and Observer, each solving a concrete problem in the domain
- Security: JWT bearer auth, BCrypt password hashing, PDF-only upload validation with UUID file names
- Testing: 49 xUnit tests, including integration tests with
WebApplicationFactoryand EF Core InMemory - DevOps: multi-container Docker Compose deployment and GitHub Actions CI
Seeded credentials and keys are for local demo use only.
Author: Botho Kakula · GitHub