Welcome to the A1 Academy project repository!
/frontend: React.js (Vite) application/backend/A1Academy.API: ASP.NET Core 10 Web API (Configured for PostgreSQL & Kafka)/backend/A1Academy.Tests: xUnit automated tests/.github/workflows: GitHub Actions CI/CD pipelines (Azure deployment & automated tests)
- Node.js (v20+ recommended) for the frontend.
- .NET 10 SDK for the backend API and testing.
- Docker Desktop (Optional, but recommended for spinning up PostgreSQL and Kafka easily).
The backend requires configuration through appsettings.Development.json. This file is excluded from version control for security reasons.
- Copy the template file to create your local development settings:
cd backend/A1Academy.API
cp appsettings.Development.json.template appsettings.Development.json- Update the placeholders in
appsettings.Development.jsonwith your actual credentials:<YOUR_DB_PASSWORD>- Database password (from docker-compose.yml)<YOUR_JWT_SECRET_KEY>- A secure random key for JWT authentication<YOUR_GOOGLE_CLIENT_ID>- Your Google OAuth client ID<YOUR_SENDER_EMAIL>- Email address for SMTP<YOUR_SMTP_PASSWORD>- SMTP password (e.g., Gmail app password)<YOUR_APPLICATION_INSIGHTS_CONNECTION_STRING>- Application Insights key (optional)<YOUR_ADMIN_EMAIL>/<YOUR_ADMIN_PASSWORD>- credentials for the bootstrap Administrator account, created automatically on first run (there's no self-registration path for Admin - see Admin User Directory evidence)
appsettings.Development.json with real credentials to version control.
If you have Docker installed, you can spin up the required PostgreSQL database and Kafka instance automatically:
docker-compose up -d- Navigate to the API directory:
cd backend/A1Academy.API - Install dependencies/restore:
dotnet restore - Run the backend server:
dotnet run
- Open a new terminal and navigate to the frontend:
cd frontend - Install Node dependencies:
npm install - Start the Vite development server:
npm run dev
The A1 Academy backend uses Docker Compose to run the required local infrastructure services.
| Service | Image | Port | Purpose |
|---|---|---|---|
| PostgreSQL | postgres:16-alpine | 5432 | Application database |
| ZooKeeper | confluentinc/cp-zookeeper:7.5.0 | 2181 | Kafka coordination |
| Kafka | confluentinc/cp-kafka:7.5.0 | 9092 | Event streaming & messaging |
Start all services with:
docker compose up -dVerify services are running:
docker compose psVerify PostgreSQL connectivity:
docker exec local_postgres pg_isready -U appuser -d appdbExpected output:
/var/run/postgresql:5432 - accepting connections
Verify Kafka is healthy by checking container logs:
docker logs local_kafka --tail 20The backend will output on startup:
SUCCESS: Kafka Consumer connected & listening!
The broker has two listeners: containers in the compose network use kafka:29092 (set via
Kafka__BootstrapServers in docker-compose.yml), apps started with dotnet run on your machine
use localhost:9092. Each service consumes in its own consumer group (its assembly name, e.g.
A1Academy.AdminService), so every service receives every event.
End-to-end check - publish from inside the broker and watch all four services log it:
docker exec -it local_kafka bash -c "echo hello-sprint4 | kafka-console-producer --bootstrap-server localhost:29092 --topic test-topic"
docker compose logs auth admin teacher student | Select-String "KAFKA RECEIVED"In Azure, the services connect to an Azure Event Hubs namespace through its Kafka endpoint
(SASL_SSL). scripts/devops-azure-setup.sh provisions the
namespace and topics and sets the Kafka__* env vars on each Container App. Event Hubs does
not auto-create topics, so add any new topic to KAFKA_TOPICS in that script.
The ASP.NET Core backend uses the connection string configured in appsettings.Development.json:
Host=localhost
Port=5432
Database=appdb
Username=appuser
On successful startup, you should see:
SUCCESS: Backend connected to PostgreSQL
Once the backend is running on http://localhost:5123, verify the API is accessible:
(Invoke-WebRequest http://localhost:5123/swagger/index.html -UseBasicParsing).StatusCodeExpected output: 200
Visit http://localhost:5123/swagger/index.html in your browser to explore the API.
To run the unit tests locally, navigate to the tests folder and execute them:
cd backend/A1Academy.Tests
dotnet test --filter "Category!=E2E"AuthenticationFlowE2ETests drives a real Chrome browser through Student signup (including
OTP verification) followed by login, and checks that an authenticated session with the correct
role is reached. It runs against a live local environment rather than starting one itself:
docker-compose up -d postgres kafka zookeeper # from the repo root
dotnet run --project backend/A1Academy.API # http://localhost:5123, ASPNETCORE_ENVIRONMENT=Development
npm run dev --prefix frontend # http://localhost:5173
cd backend/A1Academy.Tests
dotnet test --filter Category=E2EA Chromium-based browser must be installed - Chrome, Brave, or Edge are all auto-detected from
their usual install locations (override with E2E_BROWSER_BINARY if yours lives elsewhere);
Selenium downloads a matching chromedriver for it automatically. Override E2E_FRONTEND_URL /
E2E_API_URL if your servers run elsewhere, and set E2E_HEADLESS=false to watch the browser
drive itself. This suite relies on a
Development/Testing-only endpoint (GET /api/auth/debug-otp) to read the signup OTP instead of
a real mailbox, and only covers the Student role — Teacher signups start unapproved and can't
log in until an admin approves them. It's excluded from the default dotnet test run and from
CI (see .github/workflows/ci.yml).
performance/jmeter/login-load-test.jmx drives
concurrent POST /api/auth/login requests against a running API instance to check it holds up
under load:
docker-compose up -d postgres kafka zookeeper
dotnet run --project backend/A1Academy.API
cd performance/jmeter
./seed-load-test-user.sh # once, creates the test account
jmeter -n -t login-load-test.jmx -l results.jtl -Jusers=50 -JrampUp=10 -Jloops=10
jmeter -g results.jtl -o report/ # HTML dashboard with percentilesSee performance/jmeter/README.md for the full profile list
(baseline/stress/higher-load) and docs/evidence/Login-Load-Testing.md
for the locally verified results. Apache JMeter 5.6.3 executed all three profiles against the
running API, with 0% errors and response times under the ticket's 5-second threshold.
The following screenshots provide evidence that the local infrastructure and backend integration were successfully verified.
The Selenium flow completed Student registration, email verification, and login successfully.
The JMeter execution covered 50, 100, and 200 concurrent users with 0% errors and response times
under the 5-second threshold. Full numbers and analysis are in
docs/evidence/Login-Load-Testing.md.
GET /api/users is restricted server-side to the Admin role ([Authorize(Roles = "Admin")]) —
verified with 200/403/403/401 for Admin/Student/Teacher/unauthenticated requests, both in
automated tests and against the live running API. Full writeup in
docs/evidence/Admin-User-Directory.md.
GET /api/users paginates (?page=/?pageSize=) and the directory table uses a windowed
Previous/page-numbers/Next control. Verified against 21 real seeded users (3 pages) by actually
clicking Next in a live browser session. Full writeup in
docs/evidence/Directory-Pagination.md.
GET /api/users now takes ?role=/?status= filters, and the directory has a one-click
"Pending Teacher Applications" button (role=Teacher + status=Pending) alongside generic Role/
Status dropdowns. Verified against real seeded data. Full writeup in
docs/evidence/Filter-Pending-Teachers.md.
Admins can now act on what the pending-teacher filter finds - PATCH /api/users/{id}/approve
flips a Teacher from Pending to Active, and the directory shows an "Approve" button on any
Pending row. Verified end to end with a real registered teacher: blocked from logging in while
Pending, able to log in immediately after approval, plus the 400/404/403 error paths. Full
writeup in docs/evidence/Teacher-Approval.md.
Accounts now move through a real state machine - Pending/Active/Rejected/Deactivated - instead
of a single approved bool, with a dedicated, pure-C# rules module deciding which moves are legal
(AccountStatusTransitions). Backed by PATCH /api/users/{id}/{approve|reject|deactivate|reactivate},
each enforced the same way, and AuthController.Login blocking anyone whose account isn't
Active with a status-specific message. Verified with real registered accounts end to end -
deactivation blocks login, reactivation unblocks it again - plus every invalid transition (like
re-approving an already-Active account) checked live. Full writeup in
docs/evidence/User-Deactivation.md.
PostgreSQL, Kafka, and ZooKeeper were successfully started using Docker Compose.
Backend connectivity to PostgreSQL was successfully verified.
Kafka consumer successfully connected and listened for messages.
The backend successfully connected to the required infrastructure services.
Swagger was successfully served by the ASP.NET Core backend with HTTP 200.
The automated integration test suite was executed successfully.
Test result:
- Total: 12
- Passed: 12
- Failed: 0
- Skipped: 0
- Build: Successful
During Sprint 2, the core DevOps infrastructure was successfully provisioned and integrated. The primary objectives were to eliminate manual deployment overhead, establish secure cloud communication, and implement a scalable event-driven architecture. The application is now fully supported by automated CI/CD pipelines via GitHub Actions and is successfully deployed to the Azure cloud ecosystem.
To optimize the developer experience and reduce build times, a granular, microservice-specific CI strategy was implemented using GitHub Actions.
- Decoupled Workflows: Rather than a monolithic pipeline, distinct CI workflows were engineered for each backend service (Admin, Auth, Gateway, Student, Teacher) as well as the Frontend.
- Path-Based Triggers: Workflows are configured with path filtering, ensuring that builds and automated tests are only triggered for the specific microservice that was modified. This drastically reduces compute waste and accelerates feedback loops for developers.
- Build and Validation: The CI pipelines automatically provision the .NET environment, restore dependencies, compile the application, and execute unit testing frameworks to prevent regressions from merging into the main branch.
The transition from local development to a live cloud environment was finalized, establishing a seamless Continuous Deployment pipeline to Azure.
- Automated Azure Deployments: The CD pipeline is configured to securely package and promote validated code directly to the live Azure environment upon successful merge to the production branch.
- Centralized Cloud Database: Refactored the appsettings.json configurations across all microservices to deprecate local database dependencies. All services are now securely authenticated and connected to a centralized Azure PostgreSQL Flexible Server, ensuring data consistency across the distributed system.
- API Gateway and CORS Remediation: Resolved cross-origin blocking issues in the live environment by reconfiguring the A1Academy.Gateway (Program.cs). The Gateway now properly routes external requests and manages CORS policies, allowing the live React frontend to successfully consume backend APIs.
- Environment Variable Management: Updated frontend production environments (.env.production) to dynamically point to the live Azure Gateway URL during the build phase.
To support highly scalable, asynchronous communication between microservices, Apache Kafka was implemented as the central message broker.
- Shared Kafka Infrastructure: Engineered centralized KafkaProducerService and KafkaConsumerService abstractions within the A1Academy.Shared library utilizing the Confluent.Kafka SDK.
- Service Injection: Integrated Kafka via Dependency Injection into the Program.cs lifecycle of all microservices. This empowers any service to act as an event publisher or subscriber without tight coupling.
- Containerized Local Development: Orchestrated Kafka and Zookeeper within the docker-compose.yml stack. This allows the engineering team to spin up the entire event-driven messaging topology locally with a single Docker command, ensuring development parity with production.
- Pipeline Unblocking: Identified an issue where failing frontend unit tests were actively blocking the CD pipeline from deploying critical backend infrastructure. To unblock the release, the failing test suite was temporarily bypassed in .github/workflows/main_a1-academy-frontend.yml.
- Action Item: A task has been allocated to the frontend engineering team for Sprint 3 to resolve the broken tests and re-enable strict CI validation.
- Security and Compliance: Integrate Static Application Security Testing (SAST) and dependency vulnerability scanning into the CI pipelines.
- Environment Promotion: Establish staging environments with manual approval gates before pushing to production.
- Observability: Implement centralized logging and application performance monitoring (APM) to track the health of the deployed microservices.
This project utilizes a modern cloud-native deployment strategy on Microsoft Azure, fully automated via GitHub Actions.
- Automated Frontend Deployment: Pushes to the main branch automatically build and deploy the React application directly to Azure App Service using Publish Profiles.
- Automated Backend Builds: Microservices are automatically built into Docker containers and pushed to Docker Hub upon code changes.
- Azure Container Apps: The backend is hosted on Azure Container Apps, ensuring each microservice (Gateway, Auth, Admin, Student, Teacher) runs independently in a highly scalable environment.
- Secure Networking: The API Gateway is configured to route traffic internally using secure HTTPS endpoints, complying with Azure's strict "Express Environment" security policies.
- Secrets Management: Sensitive data, such as SMTP App Passwords and JWT Secret Keys, are securely injected at runtime using Azure Environment Variables.
- CORS & Region Routing: Configured strict CORS policies in the API Gateway to securely accept requests strictly from our live Azure frontend domain in the Malaysia West region.
As the DevOps Engineer for this project, the goal was to ensure a seamless transition from local development to a production-grade cloud environment.
- Local Dev: Developers use docker-compose to spin up PostgreSQL, Kafka, Zookeeper, and the .NET microservices locally.
- Version Control: Code is pushed to GitHub, requiring PR reviews before merging into the main branch.
- Continuous Integration: GitHub Actions automatically builds the code, packages the microservices into Docker Images, and pushes them to Docker Hub.
- Continuous Deployment: The React frontend is automatically built and deployed to Azure App Service, while Azure Container Apps pull the latest backend images to serve live traffic.
Deploying a distributed system to the cloud introduced several complex infrastructure challenges that were successfully resolved:
- Azure Region Migration & CORS: Due to Azure quota limits, the entire cloud infrastructure was migrated to the Malaysia West region. This caused URL changes that triggered strict CORS blocks. This was resolved by dynamically updating the API Gateway CORS policies to accept traffic from the new regional frontend URLs.
- Azure "Express Environment" Security: Azure's new Express Environments actively block unencrypted internal HTTP traffic, causing the API Gateway to return 502 Bad Gateway errors. This was resolved by overriding the YARP proxy environment variables to enforce strict, secure HTTPS routing between all internal containers.
- Cloud Database SSL Connectivity: Migrating from a local Docker database to a managed Azure PostgreSQL database resulted in connection rejections. This was fixed by configuring strict SSL modes (SslMode=Require) and securely injecting the new cloud connection strings into the containers at runtime.



















