Backend API for the Shelton Tool-Hire Review Portal. This repository contains the ASP.NET Core Web API, Clean Architecture application layers, EF Core migrations, SQL scripts, automated tests, GitHub Actions workflows, Azure deployment configuration, and supporting project documentation.
- Platform: ASP.NET Core Web API on .NET 8
- Architecture: Clean Architecture with Domain, Application, Infrastructure, and API layers
- Database: SQL Server or Azure SQL with EF Core code-first migrations
- Authentication: Custom JWT bearer authentication with ASP.NET Core password hashing
- Authorization: Role-based access for Customer, Moderator, and Admin users
- Image storage: Azure Blob Storage for uploaded tool/service images
- Observability: Optional Azure Application Insights telemetry and logging
- API docs: Swagger UI and OpenAPI JSON, including multipart upload endpoints
- Testing: xUnit unit tests and Windows LocalDB integration tests
- CI/CD: GitHub Actions runs unit and integration tests on
developmentandmain; Azure App Service deployment is restricted tomain - Dependency updates: Dependabot opens NuGet and GitHub Actions PRs into
development
ReviewPortal-API/
|-- src/
| |-- ReviewPortal.Domain/ # Entities, enums, and domain contracts
| |-- ReviewPortal.Application/ # DTOs, services, validators, and use-case logic
| |-- ReviewPortal.Infrastructure/ # EF Core, repositories, authentication, Blob storage, migrations
| `-- ReviewPortal.API/ # Controllers, middleware, DI, Swagger, hosting configuration
|-- tests/
| |-- ReviewPortal.UnitTests/ # Fast unit and controller tests
| `-- ReviewPortal.IntegrationTests/ # WebApplicationFactory and SQL Server LocalDB tests
|-- docs/ # Architecture, deployment, testing, security, and agile documents
|-- scripts/
| |-- local/ # Local run and Azure SQL migration helpers
| |-- security/ # Secret scan helper
| `-- sql/ # Checked-in migration and seed scripts
|-- .github/
| |-- dependabot.yml # Weekly dependency update configuration
| `-- workflows/
| |-- ci.yml # Unit tests, integration tests, main-only publish/deploy
| `-- trigger-qa-automation.yml # Triggers Playwright QA after successful main workflow
|-- AGENTS.md
|-- CLAUDE.md
|-- README.md
`-- ReviewPortal.slnx
The solution follows a four-layer Clean Architecture structure:
Domain <- Application <- Infrastructure
<- API
Key rules:
ReviewPortal.Domainhas no dependency on outer layers.ReviewPortal.Applicationdepends only on the domain layer.ReviewPortal.Infrastructureimplements persistence, JWT generation, repositories, and Azure Blob image storage.ReviewPortal.APIwires dependency injection, authentication, authorization, Swagger, Application Insights, controllers, and HTTP middleware.
- Public category and tool catalogue browsing
- Tool search, tool details, and rental cost calculation
- Customer registration, login, current-user lookup, password change, forgot password, and password reset
- JWT-secured customer review submission and personal review history
- Review comments and company responses
- Moderator/Admin review and comment moderation
- Admin category create, update, and delete operations
- Admin tool create, update, status update, image upload, and image delete operations
- Admin dashboard statistics
- Uploaded image validation, Azure Blob upload/delete, and persisted public Blob image URLs
- Swagger/OpenAPI document generation for local and deployed inspection
- Health endpoint for smoke checks
| Area | Endpoint examples |
|---|---|
| Auth | POST /api/auth/register, POST /api/auth/login, GET /api/auth/me, POST /api/auth/change-password |
| Categories | GET /api/categories, GET /api/categories/featured, GET /api/categories/{id}, GET /api/categories/{id}/tools |
| Tools | GET /api/tools/search, GET /api/tools/{id}, POST /api/tools/{id}/rental-calculation |
| Reviews | GET /api/tools/{toolId}/reviews, POST /api/tools/{toolId}/reviews, GET /api/users/me/reviews |
| Comments | GET /api/reviews/{reviewId}/comments, POST /api/reviews/{reviewId}/comments |
| Responses | POST /api/reviews/{reviewId}/response, PUT /api/reviews/{reviewId}/response, DELETE /api/reviews/{reviewId}/response |
| Admin tools | GET /api/admin/tools, POST /api/admin/tools, PUT /api/admin/tools/{id}, PATCH /api/admin/tools/{id}/status, POST /api/admin/tools/{id}/images |
| Admin categories | POST /api/admin/categories, PUT /api/admin/categories/{id}, DELETE /api/admin/categories/{id} |
| Admin moderation | GET /api/admin/moderation/pending, PUT /api/admin/moderation/reviews/{id}, PUT /api/admin/moderation/comments/{id} |
| Admin dashboard | GET /api/admin/dashboard/stats |
| Diagnostics | GET /health, /swagger, /swagger/v1/swagger.json |
Run all commands from the repository root.
- .NET SDK 8
- SQL Server, SQL Server LocalDB, or Azure SQL Database
- Azure Blob Storage container for uploaded tool/service images
- Optional Azure Application Insights resource
- Optional Azure CLI or Visual Studio sign-in for passwordless Blob Storage access with
DefaultAzureCredential
Secrets are not stored in source control. Use .NET user secrets for local development:
dotnet user-secrets set "ConnectionStrings:DefaultConnection" "<your-sql-connection-string>" --project src/ReviewPortal.API
dotnet user-secrets set "Jwt:Secret" "<your-32-plus-character-secret>" --project src/ReviewPortal.API
dotnet user-secrets set "Jwt:Issuer" "ReviewPortalAPI" --project src/ReviewPortal.API
dotnet user-secrets set "Jwt:Audience" "ReviewPortalClient" --project src/ReviewPortal.API
dotnet user-secrets set "Jwt:ExpiryMinutes" "60" --project src/ReviewPortal.APIConfigure Azure Blob Storage with managed identity or a local development connection string:
dotnet user-secrets set "ImageStorage:ServiceUri" "https://<storage-account-name>.blob.core.windows.net" --project src/ReviewPortal.API
dotnet user-secrets set "ImageStorage:ContainerName" "tool-images" --project src/ReviewPortal.API
dotnet user-secrets set "ImageStorage:PublicBaseUrl" "https://<storage-account-name>.blob.core.windows.net/tool-images" --project src/ReviewPortal.API
dotnet user-secrets set "ImageStorage:BlobNamePrefix" "tools" --project src/ReviewPortal.APIIf you are not using Azure identity locally, use this fallback instead of ImageStorage:ServiceUri:
dotnet user-secrets set "ImageStorage:ConnectionString" "<your-storage-connection-string>" --project src/ReviewPortal.APIApplication Insights is optional locally and enabled only when a connection string is present:
dotnet user-secrets set "ApplicationInsights:ConnectionString" "<your-application-insights-connection-string>" --project src/ReviewPortal.APIDo not commit real SQL passwords, JWT secrets, storage keys, publish profiles, or Application Insights connection strings.
For Azure SQL migration smoke tests or local API runs against Azure resources, copy the ignored local settings template:
Copy-Item src/ReviewPortal.API/appsettings.Local.example.json src/ReviewPortal.API/appsettings.Local.json
notepad src/ReviewPortal.API/appsettings.Local.jsonappsettings.Local.json is ignored by git and loads after checked-in settings files.
dotnet restore ReviewPortal.slnx
dotnet build ReviewPortal.slnxdotnet ef database update --project src/ReviewPortal.Infrastructure --startup-project src/ReviewPortal.APITo apply migrations to Azure SQL using appsettings.Local.json:
.\scripts\local\Update-AzureDatabase.ps1The EF migration applies the core schema and seeded identity/reference data. Extra catalogue and relational demo data scripts are in scripts/sql/.
sqlcmd -S "<server>" -d "<database>" -U "<username>" -P "<password>" -i "scripts/sql/SeedTestUsers.sql"
sqlcmd -S "<server>" -d "<database>" -U "<username>" -P "<password>" -i "scripts/sql/SeedFullTestData.sql"dotnet run --project src/ReviewPortal.APITo run using the ignored local settings file:
.\scripts\local\Run-ApiLocal.ps1Useful development endpoints:
/swagger/swagger/v1/swagger.json/health
Application Insights support is implemented in src/ReviewPortal.API/Program.cs and uses Microsoft.ApplicationInsights.AspNetCore.
- Local setting:
ApplicationInsights:ConnectionString - Azure App Service setting:
APPLICATIONINSIGHTS_CONNECTION_STRING - Checked-in appsettings files contain blank placeholders only.
- When the setting is missing, the API starts normally and logs that telemetry is disabled.
- When the setting is present, request telemetry, dependency telemetry, exceptions, and configured logs are sent to the Application Insights resource.
In Azure Portal, add the connection string under:
reviewportal-api -> Settings -> Environment variables
Then restart the App Service and check Application Insights Logs, Failures, Performance, and Live Metrics.
Tool and service images are stored in Azure Blob Storage instead of the App Service filesystem.
Required production settings:
ImageStorage__ServiceUri = https://<storage-account-name>.blob.core.windows.net
ImageStorage__ContainerName = tool-images
ImageStorage__PublicBaseUrl = https://<storage-account-name>.blob.core.windows.net/tool-images
ImageStorage__BlobNamePrefix = tools
Recommended Azure permission:
- Assign the App Service managed identity the
Storage Blob Data Contributorrole on the storage account.
Use ImageStorage__ConnectionString only as a local development fallback. Full setup steps are documented in docs/azure-blob-storage/README.md.
Run all tests:
dotnet test ReviewPortal.slnxRun the suites separately:
dotnet test tests/ReviewPortal.UnitTests/ReviewPortal.UnitTests.csproj
dotnet test tests/ReviewPortal.IntegrationTests/ReviewPortal.IntegrationTests.csprojNotes:
- Unit tests are fast and do not require SQL Server.
- Integration tests use
WebApplicationFactory<Program>and SQL Server LocalDB, so CI runs them onwindows-latest. - Integration tests replace Azure Blob access with an in-memory test Blob storage double.
- Swagger JSON is covered by an integration test so
/swagger/v1/swagger.jsondoes not regress to a 500 error.
The main workflow is .github/workflows/ci.yml.
| Trigger | Branch | What happens |
|---|---|---|
| Pull request | development |
Unit tests and integration tests run |
| Pull request | main |
Unit tests and integration tests run |
| Push | development |
Unit tests and integration tests run only |
| Push | main |
Unit tests and integration tests run, API is published, then deployment waits for production approval |
Important deployment rule:
- The
developmentbranch never publishes or deploys to Azure App Service. - Azure App Service deployment happens only after changes reach
main, tests pass, and theproductionGitHub environment approves the deploy job.
The deploy job uses:
- Azure App Service name:
reviewportal-api - Production URL:
https://reviewportal-api-escdb3f2epg8eeha.southeastasia-01.azurewebsites.net - GitHub environment:
production - Required secret:
AZURE_WEBAPP_PUBLISH_PROFILE
The QA trigger workflow is .github/workflows/trigger-qa-automation.yml. It dispatches the Playwright QA automation repository only after a successful main workflow, or when manually dispatched from main.
Detailed setup is in docs/DEPLOYMENT-TO-AZURE-APP-SERVICE.md.
Set these in Azure Portal under reviewportal-api -> Settings -> Environment variables:
ASPNETCORE_ENVIRONMENT = Production
Jwt__Secret = <strong-random-secret>
Jwt__Issuer = ReviewPortalAPI
Jwt__Audience = ReviewPortalClient
Jwt__ExpiryMinutes = 60
APPLICATIONINSIGHTS_CONNECTION_STRING = <connection-string-from-application-insights>
ImageStorage__ServiceUri = https://<storage-account-name>.blob.core.windows.net
ImageStorage__ContainerName = tool-images
ImageStorage__PublicBaseUrl = https://<storage-account-name>.blob.core.windows.net/tool-images
ImageStorage__BlobNamePrefix = tools
ConnectionStrings__DefaultConnection = Server=tcp:<azure-sql-server>.database.windows.net,1433;Initial Catalog=<database-name>;User ID=<azure-sql-user>;Password=<rotated-password>;Encrypt=True;TrustServerCertificate=False;Connection Timeout=30;
Optional frontend CORS settings:
Cors__AllowedOrigins__0 = https://reviewportal-frontend-dccvarataff4a8hg.southeastasia-01.azurewebsites.net
Cors__AllowedOrigins__1 = http://localhost:3000
Restart the App Service after changing settings.
In the API repository:
- Create a GitHub environment named
production. - Enable required reviewers for the
productionenvironment. - Restrict the environment to branch
main. - Add the
AZURE_WEBAPP_PUBLISH_PROFILEsecret to theproductionenvironment. - Add
QA_AUTOMATION_TRIGGER_TOKENas an Actions secret if the API workflow should trigger the Playwright QA repository.
Recommended release path:
- Work on a feature branch.
- Open a PR into
development. - Merge only after unit and integration tests pass.
- Open a PR from
developmenttomain. - Merge to
mainonly after approval and passing checks. - Approve the
productiondeployment job. - Confirm Azure App Service health and Playwright QA results.
Dependabot is configured in .github/dependabot.yml.
- NuGet package updates are checked weekly for the API, Application, Infrastructure, UnitTests, and IntegrationTests projects.
- GitHub Actions updates are checked weekly.
- PRs target the
developmentbranch. - Minor and patch updates are grouped to reduce PR noise.
| Role | Password | |
|---|---|---|
| Customer | customer.test@reviewportal.local |
Customer123! |
| Admin | admin.test@reviewportal.local |
Admin123! |
| Moderator | moderator.test@reviewportal.local |
Moderator123! |
dotnet restore ReviewPortal.slnx
dotnet build ReviewPortal.slnx
dotnet test ReviewPortal.slnx
dotnet test tests/ReviewPortal.UnitTests/ReviewPortal.UnitTests.csproj
dotnet test tests/ReviewPortal.IntegrationTests/ReviewPortal.IntegrationTests.csproj
dotnet run --project src/ReviewPortal.API
.\scripts\local\Run-ApiLocal.ps1
.\scripts\local\Update-AzureDatabase.ps1
.\scripts\security\scan-secrets.ps1
dotnet ef migrations add <MigrationName> --project src/ReviewPortal.Infrastructure --startup-project src/ReviewPortal.API
dotnet ef migrations script <FromMigration> <ToMigration> --idempotent --output scripts/sql/<MigrationName>.sql --project src/ReviewPortal.Infrastructure --startup-project src/ReviewPortal.API
dotnet ef database update --project src/ReviewPortal.Infrastructure --startup-project src/ReviewPortal.APIRun the local secret scan before opening a PR:
.\scripts\security\scan-secrets.ps1Check that no real values are committed for:
- Azure SQL passwords
- JWT secrets
- Azure publish profiles
- Storage account keys or connection strings
- Application Insights connection strings
- GitHub tokens
| Document | Path |
|---|---|
| Architecture and coding conventions | CLAUDE.md |
| AI agent working instructions | AGENTS.md |
| Azure App Service deployment guide | docs/DEPLOYMENT-TO-AZURE-APP-SERVICE.md |
| Azure Blob Storage setup | docs/azure-blob-storage/README.md |
| Functional design diagrams | docs/FUNCTIONAL-DESIGN-DIAGRAMS.md |
| Database design | docs/DATABASE-DESIGN.md |
| Entity relationship diagram | docs/ERD.md |
| Requirements specification | docs/REQUIREMENTS-SPECIFICATION.md |
| Non-functional requirements | docs/NON-FUNCTIONAL-REQUIREMENTS.md |
| Testing strategy | docs/TESTING-STRATEGY.md |
| Test plan | docs/TEST-PLAN.md |
| Security scan notes | docs/security/BACKEND-SECURITY-SCAN-2026-05-07.md |
| Agile backlog and sprint artefacts | docs/agile/ |
| SQL script usage notes | scripts/sql/README.md |
- This repository is the backend API package for the Review Portal.
- Some planning documents still describe the wider full-stack product because they capture the original project context.
- The active code, tests, workflows, deployment setup, and README instructions are backend API specific.