diff --git a/.context/AI-LOG.md b/.context/AI-LOG.md new file mode 100644 index 0000000..f1c01e2 --- /dev/null +++ b/.context/AI-LOG.md @@ -0,0 +1,113 @@ +# AI Log + +This log captures user-identified problems and the user instructions for fixes, along with the actual fixes applied to the codebase. This is not an exhaustive list of all changes, but highlights key issues and resolutions during development. + +## AppCore + +- Problem: TaskStatus ambiguity between AppCore enum and System.Threading.Tasks.TaskStatus in Task events. + - Instruction: fix the error and commit. + - Fix: add an alias for the domain TaskStatus in TaskEvents. +- Problem: TaskStatus ambiguity in TaskQuery. + - Instruction: fix the error and commit. + - Fix: add an alias for the domain TaskStatus in TaskQuery. +- Problem: TaskStatus ambiguity in TaskRules. + - Instruction: fix the error on the reported line. + - Fix: add an alias for the domain TaskStatus in TaskRules. +- Problem: question about unit of work and EF Core; user wanted no infra-specific names in AppCore. + - Instruction: rename unit of work, then keep AppCore free of the concept. + - Fix: removed IUnitOfWork (and removed the temporary ITransactionCoordinator) from AppCore. + +## Infrastructure and Docker + +- Problem: errors in multiple files including docker-compose.yml. + - Instruction: fix the errors. + - Fix: replaced tabs with spaces in docker-compose.yml so YAML parses. +- Problem: build errors for missing packages and extensions in Infrastructure projects. + - Instruction: fix the errors. + - Fix: added missing Microsoft.Extensions packages and aligned versions to 10.0.2; set Npgsql provider to 10.0.0 (available in restore). +- Problem: Mongo serialization and query errors (DateOnly serializer, SortDirection ambiguity, nullable return). + - Instruction: fix the errors. + - Fix: added custom DateOnly serializer, aliased SortDirection to AppCore enum, adjusted DashboardSummaryRepository to return nullable properly. +- Problem: SQL UseNpgsql extension not found due to package mismatch. + - Instruction: fix the error. + - Fix: aligned packages and restored UseNpgsql via correct package version. +- Problem: missing indexes and performance issues at scale. + - Instruction: add SQL and Mongo indexes, run migrations, build and test, then commit. + - Fix: added SQL composite indexes for employee/task/project queries, created EF migration, added Mongo index initializer (audit, leaves, dashboard), applied migration, built solution, and ran tests. + +## Testing and build verification + +- Instruction: write tests first, then run tests and build. + - Fix: added AppCore unit tests and ran dotnet test; then built the solution successfully. + +## Audit worker reliability + +- Problem: audit worker lacked idempotency, retries with backoff, DLQ, and a health endpoint. + - Instruction: add exponential backoff (5/10/20), max 3 retries, upsert to avoid duplicates, retry/DLQ queues, and health endpoint. + - Fix: added retry + DLQ queues, x-retry-count header tracking, exponential backoff, audit upsert, and a /health endpoint. + +## Configuration + +- Problem: RabbitMQ username/password were hardcoded in options. + - Instruction: require credentials from config (.env). + - Fix: removed defaults and validated that credentials are provided during connection setup. +- Problem: needed .env support with a tracked template and compose defaults. + - Instruction: ignore .env, keep .env.example, use docker-compose interpolation with fallback values. + - Fix: added .env.example, ignored .env, and switched docker-compose to ${VAR:-default} interpolation. + +## Migrations and seeding + +- Problem: need initial SQL migrations plus seed data (50 employees with related records). + - Instruction: generate migrations and seed data using Bogus; keep related data only. + - Fix: added initial migration, SQL seeder for departments/designations/employees/projects/members/tasks, and startup hook to migrate + seed. +- Problem: need Mongo leave requests seeded to match SQL employees. + - Instruction: seed MongoDB with matching leave requests. + - Fix: added Mongo seeder that creates leave requests for seeded employees and runs after SQL seeding. + +## Testing + +- Problem: API tests failing because migrations run on startup without a DB. + - Instruction: keep migrations on startup but make tests skip them. + - Fix: introduced SkipMigrationsOnStartup and set it in ApiTestFactory. + +## Node dashboard worker + +- Problem: need a Node worker to generate dashboard summaries from SQL + Mongo. + - Instruction: build a TypeScript worker with ES modules, clean architecture, Prisma for SQL, MongoDB driver, and hourly scheduling. + - Fix: added a dashboard worker service with Prisma schema, Mongo aggregation, and node-cron scheduling; wired Dockerfile, compose service, and env template. +- Problem: dashboard worker should be idempotent across instances. + - Instruction: make dashboard summary writes idempotent. + - Fix: added hourly summaryKey and upserted summaries in Mongo by summaryKey. +- Problem: dashboard worker should consume domain events and not run periodically. + - Instruction: remove cron scheduling, subscribe to RabbitMQ event exchanges, and rebuild container for testing. + - Fix: added RabbitMQ consumer with queue bindings to existing domain event exchanges, switched scheduler to per-event summary generation, updated config/compose env, and rebuilt the dashboard worker container. + +## API documentation + +- Problem: need Swagger UI with docs; build errors on OpenApiInfo. + - Instruction: add Swagger UI and fix the error. + - Fix: added Swashbuckle + XML docs, configured Swagger UI, and pinned Microsoft.OpenApi 1.6.23 with explicit OpenApiInfo usage. + +## API health + +- Problem: API needed a health endpoint. + - Instruction: add API health endpoint. + - Fix: registered health checks and exposed /health. + +## API responses + +- Problem: dashboard summary endpoint returned 404 when no summaries existed. + - Instruction: return a usable response instead of not_found. + - Fix: return an empty summary payload with id "latest" and zeroed counts. +- Problem: task detail response only returned assignedEmployeeId. + - Instruction: include assigned employee name, department, and designation. + - Fix: enriched task responses with assigned employee details and batch lookups. + +## Docker runtime + +- Problem: docker builds failed restoring .NET solution and Prisma failed due to missing libssl. + - Instruction: make docker builds and runtime healthy. + - Fix: switched Dockerfiles to restore per-project and moved dashboard worker to a glibc-based Node image with libssl support. +- Problem: audit worker failed to start due to missing Microsoft.AspNetCore.App framework. + - Instruction: fix audit worker runtime. + - Fix: switched audit worker runtime image to dotnet/aspnet. diff --git a/.context/README.md b/.context/README.md new file mode 100644 index 0000000..cf01069 --- /dev/null +++ b/.context/README.md @@ -0,0 +1,34 @@ +# Default Instructions for Context + +We are working in a distributed system using Clean Architecture. + +Rules: + +- Business logic lives in AppCore only +- No DbContext, Mongo client, or broker SDK in AppCore +- API controllers must be thin +- Workers consume events, not HTTP +- Publish domain events on successful state changes only +- Prefer explicit code over clever abstractions +- All DB changes must be a transaction across SQL + Mongo + +If unsure, ask for clarification instead of inventing architecture. + +## Backend Folder Structure: + +``` +backend/dotnet/ +├─ Workforce.slnx +├─ src/ +│ ├─ Core/ +│ │ └─ Workforce.AppCore +│ ├─ Infrastructure/ +│ │ ├─ Workforce.Infrastructure.Sql +│ │ ├─ Workforce.Infrastructure.Mongo +│ │ └─ Workforce.Infrastructure.Messaging +│ └─ Hosts/ +│ ├─ Workforce.Api +│ └─ Workforce.AuditWorker +└─ tests/ +└─ Workforce.AppCore.Tests +``` diff --git a/.context/architecture.md b/.context/architecture.md new file mode 100644 index 0000000..49ac842 --- /dev/null +++ b/.context/architecture.md @@ -0,0 +1,308 @@ +# 1. SQL DOMAIN + +## SQL Entities (relational, strict) + +### Employee + +```text +Employee +- Id (int, PK) +- FirstName (string, required) +- LastName (string, required) +- Email (string, required, unique) +- IsActive (bool) +- DepartmentId (FK) +- DesignationId (FK) +- Salary (decimal) +- JoiningDate (date) +- Phone (string, nullable) +- Address (string, nullable) +- City (string, nullable) +- Country (string, nullable) +``` + +--- + +### Department + +```text +Department +- Id (int, PK) +- Name (string, unique) +``` + +--- + +### Designation + +```text +Designation +- Id (int, PK) +- Name (string, unique) +``` + +--- + +### Project + +```text +Project +- Id (int, PK) +- Name (string) +- Description (string, nullable) +- Status (enum: Active | Completed | OnHold) +- StartDate (date) +- EndDate (date, nullable) +``` + +--- + +### ProjectMember (join table) + +```text +ProjectMember +- ProjectId (FK) +- EmployeeId (FK) +``` + +--- + +### Task + +```text +Task +- Id (int, PK) +- ProjectId (FK) +- AssignedEmployeeId (FK, nullable) +- Title (string) +- Description (string, nullable) +- Status (enum: Todo | InProgress | Review | Done) +- Priority (enum or int) +- DueDate (date) +``` + +--- + +## SQL API Endpoints (MANDATORY) + +### Employees + +```http +GET /api/v1/employees +POST /api/v1/employees +GET /api/v1/employees/{id} +PUT /api/v1/employees/{id} +DELETE /api/v1/employees/{id} // soft delete +``` + +Query params (must): + +```text +?page +?pageSize +&departmentId +&isActive +&search +&sort +``` + +Events: + +- EmployeeCreated +- EmployeeUpdated +- EmployeeDeactivated + +--- + +### Departments & Designations + +```http +GET /api/v1/departments +GET /api/v1/designations +``` + +Read-only. Seeded. + +--- + +### Projects + +```http +GET /api/v1/projects +POST /api/v1/projects +GET /api/v1/projects/{id} +``` + +Events: + +- ProjectCreated +- ProjectUpdated +- ProjectStatusChanged + +--- + +### Project Members (2nd order mandatory) + +```http +POST /api/v1/projects/{id}/members +DELETE /api/v1/projects/{id}/members/{employeeId} +``` + +--- + +### Tasks + +```http +POST /api/v1/projects/{projectId}/tasks +GET /api/v1/projects/{projectId}/tasks +PUT /api/v1/tasks/{taskId} +POST /api/v1/tasks/{taskId}/transition +``` + +Transition payload: + +```json +{ + "toStatus": "InProgress" +} +``` + +Events: + +- TaskCreated +- TaskAssigned +- TaskStatusChanged + +--- + +# 2. MONGO DOMAIN + +## Mongo Documents (document-oriented) + +### LeaveRequest + +```text +LeaveRequest +- _id (ObjectId) +- employeeId (int) // SQL reference +- employeeName (string) // denormalized +- leaveType (string: Sick | Casual | Annual | Unpaid) +- startDate (date) +- endDate (date) +- status (string: Pending | Approved | Rejected | Cancelled) +- reason (string, nullable) +- approvalHistory: [ + { + status (string) + changedBy (string) + changedAt (date) + comment (string, nullable) + } + ] +- createdAt (date) +``` + +--- + +### AuditLog + +```text +AuditLog +- _id (ObjectId) +- eventType (string) +- entityType (string) +- entityId (string) +- timestamp (date) +- actor (string) +- before (object) +- after (object) +``` + +Generated **only by .NET worker**. + +--- + +### DashboardSummary + +```text +DashboardSummary +- _id (ObjectId) +- generatedAt (date) +- headcountByDepartment [{ department, count }] +- activeProjectsCount (number) +- tasksByStatus [{ status, count }] +- leaveStats [{ type, status, count }] +``` + +Generated **only by Node worker**. + +--- + +## Mongo API Endpoints (MANDATORY) + +### Leave Requests + +```http +GET /api/v1/leaves +POST /api/v1/leaves +GET /api/v1/leaves/{id} +``` + +Filters (2nd order): + +```text +?status +&leaveType +``` + +--- + +### Leave Approval Flow + +```http +POST /api/v1/leaves/{id}/approve +POST /api/v1/leaves/{id}/reject +POST /api/v1/leaves/{id}/cancel +``` + +Events: + +- LeaveRequested +- LeaveApproved +- LeaveRejected +- LeaveCancelled + +--- + +### Audit Logs + +```http +GET /api/v1/audit +GET /api/v1/audit/entity/{entityType}/{entityId} +``` + +Read-only. Immutable. + +--- + +### Dashboard + +```http +GET /api/v1/dashboard/summary +``` + +Reads **precomputed Mongo doc only**. +No live aggregation. Ever. + +--- + +# 3. Non-negotiable rules (put this at the bottom of the context file) + +```text +- API never calls workers directly +- Workers never expose business APIs +- All cross-service communication is event-driven +- SQL is source of truth for org + work +- Mongo is source of truth for history + reporting +- Domain events are published only after successful commits +``` diff --git a/.gitignore b/.gitignore index d1be991..32f611b 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,4 @@ -.context/ +**.pdf # .NET build artifacts **/bin/ diff --git a/AI-WORKFLOW.md b/AI-WORKFLOW.md new file mode 100644 index 0000000..046e41b --- /dev/null +++ b/AI-WORKFLOW.md @@ -0,0 +1,42 @@ +# AI Workflow + +This document captures how AI assistance was used in this repository and how outputs were reviewed. + +## Tools Used + +- I used ChatGPT for initial planning of the architecture and to validate my understanding of the requirements and constraints, and the minimal folder structure needed to implement the features. +- For 90-95% tasks GitHub Copilot (GPT-5.2-Codex) for code generation, code review support, and troubleshooting guidance. +- Local codebase inspection to verify architecture rules and required API surface. +- In some cases Claude Sonnet 4.6 was used to solve problems that GPT-5.2-Codex struggled with, such as detecting the issue with Swagger and OpenAPI compatibility in the API project. + +## Planning + +- I conversed with ChatGPT to validate my initial understanding of the architecture of the project and the infrastructure components like DB, message broker, and workers. +- Then I used the files in .context/ to prime the Copilot models to generate project structure. +- After every step of generation, I reviewed the generated content to ensure it aligned with the principles and architecture rules defined in the .context/ files and to my knowledge of .NET, Node.js and system design best practices. + +## Code Generation + +- I gave the AI model definitions, architecture rules, and examples of existing code to generate new code for the API, workers, and documentation. +- I intervened where the AI drifted from the rules, and corrected the generated code iteratively until it met the requirements. +- I had the AI write tests, and verify code correctness by verifying build and test pass. + +## Debugging and Iteration + +- When AI generated code that did not meet the constraints, or did not compile or pass tests, I pointed out key issues and had it regenerate the code. +- Documentation was iterated to align with Docker and manual setup paths defined in [docker-compose.yml](docker-compose.yml). + +## Model Behaviour + +- GPT-5.2-Codex produced consistent code, but sometimes missed specific constraints around scale and failure modes, which required me to explicitly prompt for those details. +- It required explicit guidance on what to prioritize (limitations and scale constraints) to avoid overly generic content. +- GPT-5.2 Codex has a larger context window than Sonnet 4.6, which made it more effective for generating code that needed to adhere to specific architecture rules and patterns defined in the .context/ files. +- AI generated code failed to meet requirements on multiple occasions. One time it generated code with a hardcoded RabbitMQ username and password, which I had to point out and have it fix by removing defaults and validating config values. Another time it generated code that did not compile due to OpenAPI version compatibility issues, which I had to solve by switching to Sonnet 4.6 for better troubleshooting guidance. Another example is, for audit worker operations, it initially generated code without idempotency, retries, DLQ, or health endpoint, which I had to explicitly instruct it to add. + +## Reflection + +- AI assistance definitely works very well for generating boilerplate code, documentation and even for straightforward implementation of features that are well within the model's training data and capabilities. +- However, for key architectural level constraints, human intervention is still required to ensure that the generated content aligns with the intended design and operational requirements, especially around scale and failure modes. +- Future runs should include a short checklist of scale and failure-mode constraints to avoid omissions. +- For complex business logic, I would advise thorough review of generated code and tests, as the AI may not always capture edge cases or specific requirements without explicit prompting. +- Copilot's agentic behavior such as terminal access and file editing saved hours of manual work, but it also requires careful inspection, manual run of tests and builds, and iterative prompting to ensure the generated code is correct and meets the requirements. diff --git a/KNOWN-ISSUES.md b/KNOWN-ISSUES.md index e69de29..dc4c1b1 100644 --- a/KNOWN-ISSUES.md +++ b/KNOWN-ISSUES.md @@ -0,0 +1,64 @@ +# Known Issues and Limitations + +This file tracks unresolved issues, operational limitations, and scale constraints. It includes select items from [.context/AI-LOG.md](.context/AI-LOG.md) that remain relevant. + +## Operational Limitations + +- Event-driven consistency: reads from Mongo (audit, dashboard) are eventually consistent with SQL writes; transient delays are expected. +- Dashboard summaries are updated only on event consumption. Missed events or consumer downtime can lead to stale summaries until new events arrive. +- RabbitMQ is a hard dependency for worker updates; if unavailable, audit and dashboard updates pause and retry behavior can amplify backlog. +- Cross-store transactions: no distributed transaction coordinator is present, so SQL and Mongo writes are not truly atomic across stores. +- No authentication or authorization is implemented on the API endpoints. + +## Failure Modes (Unaddressed) + +- **Partial failure during API writes**: If Postgres commits succeed but MongoDB writes fail, the transaction may roll back SQL changes, but the domain event may already be in RabbitMQ. This can lead to inconsistent state where workers process events for rolled-back transactions. +- **Postgres down, Mongo up**: API requests fail immediately with 500 errors. No graceful degradation or circuit breaker implemented. +- **Mongo down, Postgres up**: Leave request endpoints and audit/dashboard queries fail. Write operations to SQL may succeed but events cannot be stored in Mongo, leading to data loss for audit and leave tracking. +- **RabbitMQ down**: API continues to function for read operations, but write operations that publish events will fail or hang depending on RabbitMQ client timeout settings. Workers cannot consume events and fall behind. +- **Event ordering violations**: RabbitMQ does not guarantee cross-exchange message ordering. If `EmployeeUpdated` arrives at dashboard worker before `EmployeeCreated`, the worker may fail or produce incorrect aggregations. No sequence number or causal ordering implemented. +- **Concurrent dashboard worker instances**: Multiple dashboard worker instances may race to write the same hourly summary. MongoDB upsert by `summaryKey` provides last-write-wins semantics, but could lead to lost updates if events arrive out of order or near hour boundaries. +- **Migration safety in multi-instance deployments**: Migrations run on API startup. If multiple API replicas start simultaneously, concurrent schema migrations may conflict or corrupt database state. No distributed lock or single-instance migration coordinator implemented. + +## Scale Constraints + +- **Event burst handling**: Burst traffic can trigger repeated dashboard summary recomputation (one per event). Rough ceiling is undefined but estimated at 100-500 events/sec before dashboard worker CPU saturation occurs. +- **Audit retry and DLQ growth**: Audit retry and DLQ queues can grow quickly under sustained failures; operational monitoring and cleanup are required. +- **Worker DB call frequency**: Burst events may cause workers to make frequent DB calls, potentially leading to increased latency; rate limiting or backoff strategies may be needed for high-throughput scenarios. +- **Dashboard aggregation cost**: Dashboard worker performs full SQL aggregations (headcount, task counts, leave stats) on every event. No incremental update strategy. This does not scale beyond moderate workloads (estimated <10K employees, <100K tasks). +- **Audit log storage growth**: Audit logs are append-only with no TTL, archival, or partitioning strategy. Storage will grow unbounded and eventually exhaust disk or MongoDB limits. +- **RabbitMQ queue depth**: No limit on queue depth for audit, retry, or dashboard queues. Prolonged worker downtime can cause memory exhaustion in RabbitMQ broker. + +## Retry and DLQ Operational Gaps + +- **DLQ consumption strategy**: Dead-letter queue exists for audit worker but no automated or manual replay mechanism is documented. Operators must manually inspect and republish messages. +- **DLQ alerting**: No monitoring or alerting configured for DLQ message arrival. Failed events may go unnoticed until manual inspection. +- **Retry exhaustion tracking**: After 3 retries, messages move to DLQ but no metrics or logs aggregate failure rate or causes. +- **Dashboard worker DLQ**: Dashboard worker has retry logic but no dedicated DLQ. Failed events after max retries are logged and discarded, leading to permanent data loss for dashboard summaries. + +## Known Issues from AI-LOG + +- Migrations run on API startup by default. Tests bypass this with `SkipMigrationsOnStartup`, but production startup time can increase with large datasets. +- Default credentials are present in configuration templates. These must be replaced in production to avoid insecure defaults. + +## Data Retention and Archival + +- **No TTL policy**: Audit logs, leave requests, and dashboard summaries have no expiration or archival strategy. Historical data will accumulate indefinitely. +- **No partitioning**: MongoDB collections are not partitioned by date or entity type. Query performance will degrade as collections grow beyond millions of documents. +- **No cold storage migration**: No mechanism to move old audit logs or completed leave requests to cheaper cold storage (e.g., S3, archival databases). + +## Capacity Planning Guidance (Missing) + +- **Throughput ceiling**: No documented maximum events/sec, API requests/sec, or worker processing rate. +- **Scaling triggers**: No guidance on when to scale API replicas, worker instances, or database resources. +- **Resource sizing**: No baseline CPU, memory, or storage recommendations for production deployments. +- **Load testing results**: No load test data or performance benchmarks to validate system behavior under stress. + +## What I Would Do With More Time + +- Complete the frontend feature set and verify all user flows against the API contract. +- Add a DLQ workflow and alerting to make failed event recovery operationally safe. Tradeoff: operational complexity increases and replay can introduce duplicate processing risk. +- Add worker rate limiting for burst traffic to avoid DB overload. Tradeoff: higher event latency and slower dashboard freshness under spikes. +- Switch workers to batch reads and writes where possible to reduce per-event query cost. Tradeoff: batching adds memory usage and delays visibility of recent changes. +- Add caching for hot reads (dashboard, lookups) with explicit invalidation on event processing. Tradeoff: cache invalidation complexity and risk of serving stale data. +- Implement distributed locks or coordination to reduce concurrent summary recomputation. Tradeoff: coordination can reduce throughput and create a new dependency surface. diff --git a/README.md b/README.md index e69de29..554f244 100644 --- a/README.md +++ b/README.md @@ -0,0 +1,165 @@ +# Workforce Engine + +Workforce Engine is a distributed, event-driven HR and work management system. It combines a .NET API, a .NET audit worker, and a Node.js dashboard worker with Postgres, MongoDB, and RabbitMQ to manage operational data and reporting. + +## Architecture (Quick View) + +[View Architecture Diagram](https://mermaid.live/edit#pako:eNp1U01v4jAQ_SuWT60EUUNoE3JYiZKCesiK0qqVmvRgksFEgJ21HWlbxH-vPwINu-0lmXnz3sz4Jd7jgpeAY0wFqdfoKckZQrJZunSyrYApaTCEpnfZVHCmgJWoj54rBWgBpFDo6fHNMDSeszP5LSk2FjXy8fw-e-Fis-KiAG9cV28tXtcTLuCsZpG2fs9Wgjz-2WY2kEo0hWoEeBrqMlLOKP-XY8EzlqT_cUBKQitGfziE2QtE68G4KSvVXdXkjtGOSYhcZ-ax5ESUrVr79Vvb_MOEhCjixPNZdjHnUlEB8rJtmGrMniO5PUKL9CG7WJDlslLpw-V51-kd6vd_GbdNpl8udZZayIUWPnr7LW6Hfl-R1A076m1pPjtBVmrBtANKaiG9vVPrwC1nTOwCxj5HsaVOI1PpDDul6Qz39C9clTheka2EHt6B2BGT471h5litYQc5jnVYErHJcc4OWlQT9sr5Dsf6h9AywRu6PjVp6pIoSCqiP9QXRXsNYsIbpnA8GAS2B473-C-Oo6EXDfxoFA2vRkHgX_fwO459bxSEw9AP_WF0cx1GN8Ghhz_s0CsvCjUJ9Cm5SN1NtBfy8AmUOSe0) + +### Component Responsibilities + +- **Frontend**: React SPA served by Nginx. Sends HTTP requests to the API and displays workforce data. Acts only when users interact with the UI. +- **Workforce API**: .NET 10 REST service. Handles all business logic, validates commands, writes to Postgres and MongoDB transactionally, and publishes domain events to RabbitMQ on successful commits. Runs migrations and seeds data on startup (configurable). +- **Postgres**: Relational database storing employees, departments, projects, tasks, and related org/work data. Acts as the source of truth for operational state. +- **MongoDB**: Document database storing audit logs, leave requests, and precomputed dashboard summaries. Acts as the append-only history and reporting store. +- **RabbitMQ**: Message broker routing domain events from the API to workers. Decouples synchronous API writes from asynchronous worker processing. +- **Audit Worker**: .NET background service consuming all domain events. Writes immutable audit logs to MongoDB with retry logic (exponential backoff, DLQ). Runs continuously and processes events as they arrive. +- **Dashboard Worker**: Node.js service consuming domain events. Rebuilds dashboard summaries in MongoDB on each event with retry logic. Runs continuously and responds to event-driven triggers. + +For authoritative architecture rules and required API surface, see [.context/architecture.md](.context/architecture.md). For overall Clean Architecture conventions, see [.context/README.md](.context/README.md). + +## Services + +- API: .NET 10 REST API that owns business workflows and publishes domain events. +- Audit worker: .NET background service that consumes events and writes immutable audit logs to Mongo. +- Dashboard worker: Node.js worker that consumes events and materializes dashboard summaries in Mongo. +- Frontend: Vite + React app served as static assets behind Nginx. + +## Tech Choices (Justification) + +- .NET 10: strong typing and high throughput for domain-heavy API and workers. +- Postgres: relational source of truth for org/work data (employees, projects, tasks). +- MongoDB: document store for audit history and precomputed dashboard summaries. This is a good fit for flexible schema. +- RabbitMQ: event bus for decoupling API from workers and enabling async processing. RabbitMQ's documentation, and support for .NET and Node.js made it a good choice for this use case. +- Node.js + Prisma: quick SQL access and efficient aggregation logic for dashboard worker. +- Vite + React + Tailwind: fast frontend iteration and modern UI tooling. Minimal setup friction for building a simple SPA. + +## Third-Party Libraries (and Why) + +- Bogus: realistic seed data generation for SQL and Mongo development datasets. +- Microsoft.EntityFrameworkCore: ORM for relational data access in the API and infrastructure layers. +- Microsoft.EntityFrameworkCore.Relational: shared relational EF Core features used by the Postgres provider. +- Microsoft.EntityFrameworkCore.Design: design-time migrations and tooling support. +- Npgsql.EntityFrameworkCore.PostgreSQL: EF Core provider for Postgres. +- Microsoft.Extensions.Configuration: configuration loading and binding from appsettings and environment variables. +- Microsoft.Extensions.Configuration.Abstractions: shared config contracts for library reuse. +- Microsoft.Extensions.DependencyInjection: DI container integration across services. +- Microsoft.Extensions.DependencyInjection.Abstractions: DI abstractions to keep infrastructure decoupled. +- Microsoft.Extensions.Hosting.Abstractions: background service hosting contracts for workers. +- Microsoft.Extensions.Options: options pattern for typed configuration. +- Microsoft.Extensions.Options.ConfigurationExtensions: binds configuration to options classes. +- MongoDB.Driver: MongoDB access for audit, leave, and dashboard storage. +- RabbitMQ.Client: AMQP client for publishing and consuming domain events. +- Microsoft.OpenApi: OpenAPI models used by Swagger tooling. +- Swashbuckle.AspNetCore: Swagger generation and UI for API docs. +- Microsoft.AspNetCore.Mvc.Testing: in-memory API test server for integration tests. +- Microsoft.NET.Test.Sdk: test discovery and execution for .NET. +- xunit: unit testing framework. +- xunit.runner.visualstudio: VS test runner integration. +- coverlet.collector: code coverage collection during test runs. +- @prisma/client: typed SQL access in the dashboard worker. +- prisma: schema management and client generation for the dashboard worker. +- amqplib: RabbitMQ client for Node.js. +- mongodb: MongoDB driver for the dashboard worker. +- dotenv: environment variable loading for local worker development. +- tsx: TypeScript runtime for local dashboard worker development. +- typescript: TypeScript compiler for frontend and worker builds. +- @types/amqplib: TypeScript types for RabbitMQ client. +- @types/node: TypeScript types for Node.js runtime. +- react: UI library for the frontend. +- react-dom: DOM bindings for React. +- vite: frontend dev server and build tooling. +- @vitejs/plugin-react: React Fast Refresh and JSX support in Vite. +- tailwindcss: utility-first CSS framework for UI styling. +- @tailwindcss/postcss: Tailwind integration with PostCSS. +- postcss: CSS processing pipeline. +- autoprefixer: vendor prefixing for CSS compatibility. +- eslint: linting for JavaScript and TypeScript. +- @eslint/js: base ESLint rules. +- eslint-plugin-react-hooks: React hooks lint rules. +- eslint-plugin-react-refresh: lint rules for React Fast Refresh. +- typescript-eslint: TypeScript-aware linting rules and parser. +- globals: shared global definitions for ESLint. +- @types/react: TypeScript types for React. +- @types/react-dom: TypeScript types for React DOM. + +## Setup (Docker) + +1. Copy [.env.example](.env.example) to `.env` and adjust values as needed. +2. From the repo root, run: + +```bash +docker compose up --build +``` + +### Exposed Ports (defaults) + +- API: 8080 +- Audit worker: 8081 +- Dashboard worker health: 8090 +- Frontend: 5173 +- Postgres: 5432 +- MongoDB: 27017 +- RabbitMQ: 5672 (management: 15672) + +## API Docs and Health Checks + +- Swagger UI: `http://localhost:8080/swagger` +- API health: `http://localhost:8080/health` +- Audit worker health: `http://localhost:8081/health` +- Dashboard worker health: `http://localhost:8090/health` + +## Setup (Manual / Local Dev) + +### Backend (.NET) + +```bash +cd backend/dotnet +dotnet restore +dotnet build +dotnet run --project src/Hosts/Workforce.Api/Workforce.Api.csproj +dotnet run --project src/Hosts/Workforce.AuditWorker/Workforce.AuditWorker.csproj +``` + +Key configuration is in [backend/dotnet/src/Hosts/Workforce.Api/appsettings.json](backend/dotnet/src/Hosts/Workforce.Api/appsettings.json) and [backend/dotnet/src/Hosts/Workforce.AuditWorker/appsettings.json](backend/dotnet/src/Hosts/Workforce.AuditWorker/appsettings.json). Use environment variables from [.env.example](.env.example) to override. + +### Dashboard Worker (Node) + +```bash +cd backend/node/dashboard-worker +npm install +npm run generate +npm run build +npm run dev +``` + +### Frontend + +```bash +cd frontend +npm install +npm run dev +``` + +## Environment Variables + +The complete list is in [.env.example](.env.example). The most important values are: + +- `POSTGRES_*` and `MONGO_*` for database connectivity +- `RABBITMQ_*` for event bus connectivity +- `API_PORT`, `AUDIT_WORKER_PORT`, `FRONTEND_PORT`, `DASHBOARD_HEALTH_PORT` + +## Tests + +```bash +cd backend/dotnet +dotnet test +``` + +## Known Limitations + +See [KNOWN-ISSUES.md](KNOWN-ISSUES.md) for current limitations, scale constraints, and unresolved items. + +## AI Workflow + +See [AI-WORKFLOW.md](AI-WORKFLOW.md) for how AI was used during development and validation.