Smart Tasks is a full-stack task-management application for teams. It is a modular monolith: a .NET 10 API owns the domain, persistence, authentication, authorization, and AI integration; an Angular 21 application provides the user interface.
The project is designed around Clean/Onion Architecture, explicit command/query handling, and safe operational defaults such as optimistic concurrency, soft deletion, refresh-token rotation, rate limiting, and a transactional outbox.
Admin, ProjectManager, and TeamMember roles.| Area | Technology |
|---|---|
| API | .NET 10, ASP.NET Core Minimal APIs |
| Data | EF Core 10, SQL Server, ASP.NET Core Identity |
| Frontend | Angular 21, TypeScript, standalone components, ng-zorro-antd |
| Tests | xUnit (.NET), Vitest (Angular) |
| Logging | Serilog console and file sink |
| API documentation | OpenAPI and Swagger UI in Development |
src/
SmartTasks.Domain/ Business aggregates, value rules, domain events; no framework packages
SmartTasks.Application/ Commands, queries, validation, use cases, ports, authorization rules
SmartTasks.Infrastructure/ EF Core, Identity, repositories, AI clients, outbox/inbox implementations
SmartTasks.Api/ Minimal API endpoints, middleware, configuration, composition root
tests/
SmartTasks.*.Tests/ Domain, application, architecture, and infrastructure integration tests
web/smarttasks-web/ Angular application
docs/ Architecture, requirements and implementation plans
The root solution file is SmartTasks.slnx. The Angular application is intentionally a separate Node workspace in web/smarttasks-web.
Dependency direction is strict:
Domain -> Application -> Infrastructure -> API
The architecture test project enforces these boundaries. The important responsibilities are:
| Layer | Responsibility |
|---|---|
| Domain | Project and TaskItem aggregates; task lifecycle and business-rule enforcement through domain exceptions. A project owns its members; a task is a separate aggregate. |
| Application | Hand-rolled CQRS contracts and dispatchers, FluentValidation pipeline behavior, authorization matrix, read-model contracts, and persistence/security abstractions. |
| Infrastructure | SQL Server ApplicationDbContext, Identity, repositories, audit/outbox interceptors, read-model projections, token persistence, AI HTTP clients, and background outbox processing. |
| API | Endpoint mapping, authentication, authorization, CORS, antiforgery, rate limits, exception-to-HTTP mapping, OpenAPI, and DI composition. |
| Web | Lazy feature routes, API services, authentication state/interceptors, layouts, and ng-zorro UI components. |
For a write request, the normal path is:
HTTP endpoint -> command dispatcher -> validation -> application handler
-> aggregate/business rules -> EF Core unit of work
-> audit + domain-event-to-outbox interceptors -> SQL Server transaction
Reads use no-tracking projections through IReadModelService. Data access is authorized before filtering, counting, or paging.
Install the following before running the project:
SQL Server or SQL Server LocalDB
Node.js compatible with npm 11.9.0 (the frontend declares this version)
The EF Core CLI, if you will create or apply migrations:
dotnet tool install --global dotnet-ef
These steps start the API and web application locally.
The API requires ConnectionStrings:DefaultConnection. Set it outside source control. In PowerShell, for the current terminal session:
$env:ConnectionStrings__DefaultConnection = 'Server=localhost;Database=SmartTasks;Trusted_Connection=True;TrustServerCertificate=True'
$env:Jwt__Issuer = 'SmartTasks.Api'
$env:Jwt__Audience = 'SmartTasks.Web'
$env:Jwt__SigningKey = '<a-random-secret-at-least-32-characters-long>'
For a development administrator, also set:
$env:AdminSeed__Enabled = 'true'
$env:AdminSeed__Email = 'admin@example.test'
$env:AdminSeed__Password = '<a-strong-development-password>'
$env:AdminSeed__DisplayName = 'Development Administrator'
Environment-variable names use double underscores in place of configuration colons. Do not commit connection strings, JWT signing keys, API keys, or admin passwords. See Configuration for the complete list.
From the repository root:
dotnet restore
dotnet ef database update --project src/SmartTasks.Infrastructure --startup-project src/SmartTasks.Api
The repository contains the initial SQL Server migration. The API deliberately does not apply migrations on startup.
dotnet run --project src/SmartTasks.Api --launch-profile https
The default project profile serves the API at https://localhost:7019 (and http://localhost:5186). In Development, OpenAPI is available at https://localhost:7019/openapi/v1.json and Swagger UI is available at https://localhost:7019/swagger.
If HTTPS is not trusted on your workstation, run dotnet dev-certs https --trust and restart the API.
In a second terminal:
Set-Location web/smarttasks-web
npm ci
npm start
Open http://localhost:4200.
The development server uses proxy.conf.json for /api calls. Its checked-in target is the IIS Express HTTPS port (https://localhost:44305). If you use the dotnet run --launch-profile https command above, change the local proxy target to https://localhost:7019, or run the API through its IIS Express profile. Keep this a local environment choice; the frontend sends API requests to /api/v1.
Configuration follows standard ASP.NET Core precedence. Use environment variables, your secret store, or an untracked local settings file in your environment; never place real secrets in committed JSON.
| Key | Required | Purpose |
|---|---|---|
ConnectionStrings:DefaultConnection | Yes | SQL Server connection string. |
Jwt:Issuer | Yes | JWT issuer. |
Jwt:Audience | Yes | JWT audience. |
Jwt:SigningKey | Yes | Signing key; authentication is only registered when it has at least 32 characters. |
Jwt:AccessTokenMinutes | No | Access-token lifetime; default configuration uses 15 minutes. |
Jwt:RefreshTokenDays | No | Refresh-token lifetime; default configuration uses 7 days. |
Cors:AllowedOrigins | Yes | Browser origins permitted to make credentialed API requests. Each must be an origin only (no path, query, or fragment). |
| `RateLimiting:Api | Auth | Ai:PermitLimit` |
| `RateLimiting:Api | Auth | Ai:WindowSeconds` |
| `RateLimiting:Api | Auth | Ai:QueueLimit` |
AI:Provider | Optional | Groq, or HuggingFace; the value selects the matching strategy. |
AI:<Provider>:BaseUrl | Required when that provider is used | AI provider rest base URI. |
AI:<Provider>:ApiKey | Required when that provider is used | AI provider credential. |
AI:<Provider>:Model | Required when that provider is used | Model identifier sent to the selected provider. |
AI:<Provider>:TimeoutSeconds | No | Per-request timeout, constrained to 1–120 seconds. |
AdminSeed:* | Development only | Optional idempotent initial administrator seed. |
The development admin seed creates a user only when one with the configured email does not exist. It ensures that user has the Admin role, but does not reset an existing password.
Select a provider and supply only its credentials through your secret store:
$env:AI__Provider = 'HuggingFace'
$env:AI__HuggingFace__BaseUrl = '<provider-base-url>'
$env:AI__HuggingFace__ApiKey = '<provider-api-key>'
$env:AI__HuggingFace__Model = '<model-id>'
$env:AI__HuggingFace__TimeoutSeconds = '20'
An unsupported provider or an unavailable AI service returns an ai.* error through the API. It does not save a changed task description.
EF Core uses SQL Server and ApplicationDbContext combines Identity tables with the application tables:
The model uses soft deletion for projects and tasks and RowVersion values for optimistic concurrency. A project deletion also soft-deletes its tasks.
Useful commands from the repository root:
# Apply existing migrations
dotnet ef database update --project src/SmartTasks.Infrastructure --startup-project src/SmartTasks.Api
# Create a migration after a deliberate model change
dotnet ef migrations add <DescriptiveName> --project src/SmartTasks.Infrastructure --startup-project src/SmartTasks.Api
# Generate a deployment-friendly idempotent script
dotnet ef migrations script --idempotent --project src/SmartTasks.Infrastructure --startup-project src/SmartTasks.Api --output artifacts/migrations.sql
Review generated migrations and deployment scripts before applying them to shared or production databases.
Authorization: Bearer <token>./api/v1/auth, with HttpOnly, Secure, and SameSite=Strict settings.GET /api/v1/auth/antiforgery-token, then send the value in X-XSRF-TOKEN for those requests.| Role | Main capabilities |
|---|---|
Admin | Global access; can list users and assign ProjectManager or TeamMember to non-admin users. |
ProjectManager | Creates and manages projects they own, membership, tasks, assignments, and task statuses within their authorized projects. |
TeamMember | Reads projects/tasks where they are members; can change the status of tasks assigned to them. |
The role matrix is only the first check. Resource ownership, membership, and assignment checks add the final scope. For the detailed policy, see docs/role-permission-matrix.md.
429 and include Retry-After.409.All application endpoints are rooted at /api/v1; JSON enum values are serialized as strings. Protected endpoints require a Bearer access token.
| Area | Endpoints |
|---|---|
| Health | GET /health |
| Auth | POST /auth/register, POST /auth/login, GET /auth/antiforgery-token, POST /auth/refresh, POST /auth/logout |
| Users (Admin) | GET /users, PUT /users/{userId}/role |
| Projects | POST /projects, GET /projects, GET /projects/{projectId}, PUT /projects/{projectId}, DELETE /projects/{projectId} |
| Project membership | POST /projects/{projectId}/members, DELETE /projects/{projectId}/members/{userId}, GET /projects/{projectId}/available-members |
| Tasks | POST /tasks/projects/{projectId}, GET /tasks/projects/{projectId}, GET /tasks/{taskId}, PUT /tasks/{taskId}, PATCH /tasks/{taskId}/assignee, PATCH /tasks/{taskId}/status, DELETE /tasks/{taskId} |
| Dashboard | GET /dashboard/summary, GET /dashboard/recent-projects, GET /dashboard/recent-tasks |
| AI | POST /ai/task-description/improve |
For exact schemas, validation details, status codes, and live authorization support, use Swagger UI in Development. Updates, member changes, and deletes require the current RowVersion in the request body. List endpoints accept query, pageNumber, pageSize, sortBy, and descending; task lists also accept status, priority, and assigneeId. Page sizes must be between 1 and 100.
The Angular app uses standalone components and lazy-loaded feature routes. The main folders are:
web/smarttasks-web/src/app/
core/ Authentication state, HTTP interceptors, route guards, application layout
features/ Auth, projects, tasks, dashboard, and administration screens
shared/ API types, enums, and reusable models
Feature route files export *_ROUTES. Keep API access in feature/core services rather than components, and keep styles component-scoped where practical. The UI uses ng-zorro-antd and SCSS.
Frontend commands:
Set-Location web/smarttasks-web
npm ci
npm start # Development server at http://localhost:4200
npm run build # Production build to dist/smarttasks-web
npm test -- --watch=false # One test run
npx prettier --check "src/**/*.{ts,html,scss}" "*.json" ".vscode/*.json"
From the repository root:
dotnet build
dotnet test
dotnet format --verify-no-changes
From web/smarttasks-web:
npm run build
npm test -- --watch=false
npx prettier --write "src/**/*.{ts,html,scss}" "*.json" ".vscode/*.json"
The backend treats compiler/analyzer warnings as errors. Before opening a pull request, run the applicable commands above and inspect git diff --check for whitespace errors.
src/SmartTasks.Api/Endpoints without changing established contracts unintentionally.web/smarttasks-web/src/app/features..Result, .Wait()) or wrap normal I/O in Task.Run.The AI prompt contract is documented in PROMPTS.md.
| Symptom | Check |
|---|---|
| API refuses to start | ConnectionStrings:DefaultConnection, CORS origins, and the JWT signing key are configured. |
| Database command fails | SQL Server is running, the connection string is reachable, and dotnet-ef is installed. |
| Browser sees CORS or network errors | The API origin matches Cors:AllowedOrigins; the Angular proxy target matches the active API HTTPS port. |
Refresh/logout returns 400 | Obtain an antiforgery token and send it in X-XSRF-TOKEN; ensure HTTPS and cookies are available. |
Protected endpoint returns 401 or 403 | Verify the Bearer token, role, and project membership/ownership/assignment scope. |
AI improvement returns 503 | Verify the selected provider, API key, model, network access, and timeout. |
Update returns 409 | Reload the project/task and retry using its latest RowVersion; another user changed it first. |
C#
69.0%
TypeScript
19.8%
HTML
8.1%
SCSS
3.1%
Smart Tasks is a full-stack task-management application for teams. It is a modular monolith: a .NET 10 API owns the domain, persistence, authentication, authorization, and AI integration; an Angular 21 application provides the user interface.
The project is designed around Clean/Onion Architecture, explicit command/query handling, and safe operational defaults such as optimistic concurrency, soft deletion, refresh-token rotation, rate limiting, and a transactional outbox.
Admin, ProjectManager, and TeamMember roles.| Area | Technology |
|---|---|
| API | .NET 10, ASP.NET Core Minimal APIs |
| Data | EF Core 10, SQL Server, ASP.NET Core Identity |
| Frontend | Angular 21, TypeScript, standalone components, ng-zorro-antd |
| Tests | xUnit (.NET), Vitest (Angular) |
| Logging | Serilog console and file sink |
| API documentation | OpenAPI and Swagger UI in Development |
src/
SmartTasks.Domain/ Business aggregates, value rules, domain events; no framework packages
SmartTasks.Application/ Commands, queries, validation, use cases, ports, authorization rules
SmartTasks.Infrastructure/ EF Core, Identity, repositories, AI clients, outbox/inbox implementations
SmartTasks.Api/ Minimal API endpoints, middleware, configuration, composition root
tests/
SmartTasks.*.Tests/ Domain, application, architecture, and infrastructure integration tests
web/smarttasks-web/ Angular application
docs/ Architecture, requirements and implementation plans
The root solution file is SmartTasks.slnx. The Angular application is intentionally a separate Node workspace in web/smarttasks-web.
Dependency direction is strict:
Domain -> Application -> Infrastructure -> API
The architecture test project enforces these boundaries. The important responsibilities are:
| Layer | Responsibility |
|---|---|
| Domain | Project and TaskItem aggregates; task lifecycle and business-rule enforcement through domain exceptions. A project owns its members; a task is a separate aggregate. |
| Application | Hand-rolled CQRS contracts and dispatchers, FluentValidation pipeline behavior, authorization matrix, read-model contracts, and persistence/security abstractions. |
| Infrastructure | SQL Server ApplicationDbContext, Identity, repositories, audit/outbox interceptors, read-model projections, token persistence, AI HTTP clients, and background outbox processing. |
| API | Endpoint mapping, authentication, authorization, CORS, antiforgery, rate limits, exception-to-HTTP mapping, OpenAPI, and DI composition. |
| Web | Lazy feature routes, API services, authentication state/interceptors, layouts, and ng-zorro UI components. |
For a write request, the normal path is:
HTTP endpoint -> command dispatcher -> validation -> application handler
-> aggregate/business rules -> EF Core unit of work
-> audit + domain-event-to-outbox interceptors -> SQL Server transaction
Reads use no-tracking projections through IReadModelService. Data access is authorized before filtering, counting, or paging.
Install the following before running the project:
SQL Server or SQL Server LocalDB
Node.js compatible with npm 11.9.0 (the frontend declares this version)
The EF Core CLI, if you will create or apply migrations:
dotnet tool install --global dotnet-ef
These steps start the API and web application locally.
The API requires ConnectionStrings:DefaultConnection. Set it outside source control. In PowerShell, for the current terminal session:
$env:ConnectionStrings__DefaultConnection = 'Server=localhost;Database=SmartTasks;Trusted_Connection=True;TrustServerCertificate=True'
$env:Jwt__Issuer = 'SmartTasks.Api'
$env:Jwt__Audience = 'SmartTasks.Web'
$env:Jwt__SigningKey = '<a-random-secret-at-least-32-characters-long>'
For a development administrator, also set:
$env:AdminSeed__Enabled = 'true'
$env:AdminSeed__Email = 'admin@example.test'
$env:AdminSeed__Password = '<a-strong-development-password>'
$env:AdminSeed__DisplayName = 'Development Administrator'
Environment-variable names use double underscores in place of configuration colons. Do not commit connection strings, JWT signing keys, API keys, or admin passwords. See Configuration for the complete list.
From the repository root:
dotnet restore
dotnet ef database update --project src/SmartTasks.Infrastructure --startup-project src/SmartTasks.Api
The repository contains the initial SQL Server migration. The API deliberately does not apply migrations on startup.
dotnet run --project src/SmartTasks.Api --launch-profile https
The default project profile serves the API at https://localhost:7019 (and http://localhost:5186). In Development, OpenAPI is available at https://localhost:7019/openapi/v1.json and Swagger UI is available at https://localhost:7019/swagger.
If HTTPS is not trusted on your workstation, run dotnet dev-certs https --trust and restart the API.
In a second terminal:
Set-Location web/smarttasks-web
npm ci
npm start
Open http://localhost:4200.
The development server uses proxy.conf.json for /api calls. Its checked-in target is the IIS Express HTTPS port (https://localhost:44305). If you use the dotnet run --launch-profile https command above, change the local proxy target to https://localhost:7019, or run the API through its IIS Express profile. Keep this a local environment choice; the frontend sends API requests to /api/v1.
Configuration follows standard ASP.NET Core precedence. Use environment variables, your secret store, or an untracked local settings file in your environment; never place real secrets in committed JSON.
| Key | Required | Purpose |
|---|---|---|
ConnectionStrings:DefaultConnection | Yes | SQL Server connection string. |
Jwt:Issuer | Yes | JWT issuer. |
Jwt:Audience | Yes | JWT audience. |
Jwt:SigningKey | Yes | Signing key; authentication is only registered when it has at least 32 characters. |
Jwt:AccessTokenMinutes | No | Access-token lifetime; default configuration uses 15 minutes. |
Jwt:RefreshTokenDays | No | Refresh-token lifetime; default configuration uses 7 days. |
Cors:AllowedOrigins | Yes | Browser origins permitted to make credentialed API requests. Each must be an origin only (no path, query, or fragment). |
| `RateLimiting:Api | Auth | Ai:PermitLimit` |
| `RateLimiting:Api | Auth | Ai:WindowSeconds` |
| `RateLimiting:Api | Auth | Ai:QueueLimit` |
AI:Provider | Optional | Groq, or HuggingFace; the value selects the matching strategy. |
AI:<Provider>:BaseUrl | Required when that provider is used | AI provider rest base URI. |
AI:<Provider>:ApiKey | Required when that provider is used | AI provider credential. |
AI:<Provider>:Model | Required when that provider is used | Model identifier sent to the selected provider. |
AI:<Provider>:TimeoutSeconds | No | Per-request timeout, constrained to 1–120 seconds. |
AdminSeed:* | Development only | Optional idempotent initial administrator seed. |
The development admin seed creates a user only when one with the configured email does not exist. It ensures that user has the Admin role, but does not reset an existing password.
Select a provider and supply only its credentials through your secret store:
$env:AI__Provider = 'HuggingFace'
$env:AI__HuggingFace__BaseUrl = '<provider-base-url>'
$env:AI__HuggingFace__ApiKey = '<provider-api-key>'
$env:AI__HuggingFace__Model = '<model-id>'
$env:AI__HuggingFace__TimeoutSeconds = '20'
An unsupported provider or an unavailable AI service returns an ai.* error through the API. It does not save a changed task description.
EF Core uses SQL Server and ApplicationDbContext combines Identity tables with the application tables:
The model uses soft deletion for projects and tasks and RowVersion values for optimistic concurrency. A project deletion also soft-deletes its tasks.
Useful commands from the repository root:
# Apply existing migrations
dotnet ef database update --project src/SmartTasks.Infrastructure --startup-project src/SmartTasks.Api
# Create a migration after a deliberate model change
dotnet ef migrations add <DescriptiveName> --project src/SmartTasks.Infrastructure --startup-project src/SmartTasks.Api
# Generate a deployment-friendly idempotent script
dotnet ef migrations script --idempotent --project src/SmartTasks.Infrastructure --startup-project src/SmartTasks.Api --output artifacts/migrations.sql
Review generated migrations and deployment scripts before applying them to shared or production databases.
Authorization: Bearer <token>./api/v1/auth, with HttpOnly, Secure, and SameSite=Strict settings.GET /api/v1/auth/antiforgery-token, then send the value in X-XSRF-TOKEN for those requests.| Role | Main capabilities |
|---|---|
Admin | Global access; can list users and assign ProjectManager or TeamMember to non-admin users. |
ProjectManager | Creates and manages projects they own, membership, tasks, assignments, and task statuses within their authorized projects. |
TeamMember | Reads projects/tasks where they are members; can change the status of tasks assigned to them. |
The role matrix is only the first check. Resource ownership, membership, and assignment checks add the final scope. For the detailed policy, see docs/role-permission-matrix.md.
429 and include Retry-After.409.All application endpoints are rooted at /api/v1; JSON enum values are serialized as strings. Protected endpoints require a Bearer access token.
| Area | Endpoints |
|---|---|
| Health | GET /health |
| Auth | POST /auth/register, POST /auth/login, GET /auth/antiforgery-token, POST /auth/refresh, POST /auth/logout |
| Users (Admin) | GET /users, PUT /users/{userId}/role |
| Projects | POST /projects, GET /projects, GET /projects/{projectId}, PUT /projects/{projectId}, DELETE /projects/{projectId} |
| Project membership | POST /projects/{projectId}/members, DELETE /projects/{projectId}/members/{userId}, GET /projects/{projectId}/available-members |
| Tasks | POST /tasks/projects/{projectId}, GET /tasks/projects/{projectId}, GET /tasks/{taskId}, PUT /tasks/{taskId}, PATCH /tasks/{taskId}/assignee, PATCH /tasks/{taskId}/status, DELETE /tasks/{taskId} |
| Dashboard | GET /dashboard/summary, GET /dashboard/recent-projects, GET /dashboard/recent-tasks |
| AI | POST /ai/task-description/improve |
For exact schemas, validation details, status codes, and live authorization support, use Swagger UI in Development. Updates, member changes, and deletes require the current RowVersion in the request body. List endpoints accept query, pageNumber, pageSize, sortBy, and descending; task lists also accept status, priority, and assigneeId. Page sizes must be between 1 and 100.
The Angular app uses standalone components and lazy-loaded feature routes. The main folders are:
web/smarttasks-web/src/app/
core/ Authentication state, HTTP interceptors, route guards, application layout
features/ Auth, projects, tasks, dashboard, and administration screens
shared/ API types, enums, and reusable models
Feature route files export *_ROUTES. Keep API access in feature/core services rather than components, and keep styles component-scoped where practical. The UI uses ng-zorro-antd and SCSS.
Frontend commands:
Set-Location web/smarttasks-web
npm ci
npm start # Development server at http://localhost:4200
npm run build # Production build to dist/smarttasks-web
npm test -- --watch=false # One test run
npx prettier --check "src/**/*.{ts,html,scss}" "*.json" ".vscode/*.json"
From the repository root:
dotnet build
dotnet test
dotnet format --verify-no-changes
From web/smarttasks-web:
npm run build
npm test -- --watch=false
npx prettier --write "src/**/*.{ts,html,scss}" "*.json" ".vscode/*.json"
The backend treats compiler/analyzer warnings as errors. Before opening a pull request, run the applicable commands above and inspect git diff --check for whitespace errors.
src/SmartTasks.Api/Endpoints without changing established contracts unintentionally.web/smarttasks-web/src/app/features..Result, .Wait()) or wrap normal I/O in Task.Run.The AI prompt contract is documented in PROMPTS.md.
| Symptom | Check |
|---|---|
| API refuses to start | ConnectionStrings:DefaultConnection, CORS origins, and the JWT signing key are configured. |
| Database command fails | SQL Server is running, the connection string is reachable, and dotnet-ef is installed. |
| Browser sees CORS or network errors | The API origin matches Cors:AllowedOrigins; the Angular proxy target matches the active API HTTPS port. |
Refresh/logout returns 400 | Obtain an antiforgery token and send it in X-XSRF-TOKEN; ensure HTTPS and cookies are available. |
Protected endpoint returns 401 or 403 | Verify the Bearer token, role, and project membership/ownership/assignment scope. |
AI improvement returns 503 | Verify the selected provider, API key, model, network access, and timeout. |
Update returns 409 | Reload the project/task and retry using its latest RowVersion; another user changed it first. |
C#
69.0%
TypeScript
19.8%
HTML
8.1%
SCSS
3.1%