Skip to content
Closed
40 changes: 20 additions & 20 deletions .github/copilot-instructions.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@

## Architecture Overview

This is a **full-stack learning project** for managing reminders with multiple client interfaces (React, ASP.NET MVC), a load-balanced .NET API, and blockchain integration.
This is a **full-stack learning project** for managing reminders with multiple client interfaces (React, ASP.NET MVC), three load-balanced API backends (.NET, Go, C++), and blockchain integration.

### Tech Stack
- **Backend**: ASP.NET Core 8.0 Web API + Go Gin API (2 instances behind Nginx load balancer)
- **Backend**: ASP.NET Core 8.0 Web API + Go (Gin) API + C++ API (3 instances behind Nginx load balancer)
- **Frontend**: Next.js 15 (React 19) + ASP.NET MVC
- **Database**: PostgreSQL (primary) or SQL Server (legacy support)
- **Blockchain**: Hardhat + Solidity smart contracts on Ganache
Expand All @@ -28,18 +28,17 @@ src/
│ │ │ │ │ └── SqlServer/Migrations/ # SQL Server migrations (legacy)
│ │ │ │ └── CrossCutting/ # Shared infrastructure
│ │ │ └── Extensions/ # Middleware & DI extensions
│ │ └── go/reminders-api/ # Go (Gin) API - lightweight implementation
│ │ ├── cmd/app/ # Application entry point
│ │ ├── internal/ # Internal packages
│ │ │ ├── handlers/ # HTTP handlers
│ │ │ ├── repository/ # Data access
│ │ │ └── models/ # Domain models
│ │ └── wait-for-dotnet.sh # Startup helper script
│ │ ├── go/reminders-api/ # Go (Gin) API - lightweight implementation
│ │ │ ├── cmd/app/ # Application entry point (main.go)
│ │ │ ├── pkg/api/ # HTTP handlers + repository (Postgres)
│ │ │ ├── pkg/models/ # Domain models
│ │ │ └── wait-for-dotnet.sh # Startup helper script
│ │ └── cpp/reminders-api/ # C++ API - third load-balanced instance
├── app/
│ ├── reactjs/reminders-app/ # Next.js App Router app
│ └── dotnet/Reminders.Mvc/ # ASP.NET MVC app
blockchain/ # Hardhat smart contracts
test/cypress/ # E2E tests
└── test/cypress/ # E2E tests
blockchain/ # Hardhat smart contracts (repo root, alongside src/)
```

## Critical Workflows
Expand Down Expand Up @@ -70,7 +69,7 @@ docker compose --profile production -f docker-compose.yml -f docker-compose.prod
**Architecture**:
- **Execution Model**: Runs once per deployment, exits after completion (not long-running)
- **Orchestration**: Docker Compose ensures `migrations` service completes successfully before starting `dotnet-api` and `go-api`
- **Health Endpoint**: Exposes `/healthz` on port 8081 (development only)
- **Health Endpoint**: Exposes `GET /healthz` on port 8081 (development only) - this is the migration runner's own health check, distinct from the Go API's `GET /health` described under "Go API Specifics" below
- HTTP 500: Migrations pending, running, or failed
- HTTP 200: Migrations completed successfully
- **Retry Logic**: Exponential backoff with jitter (5 attempts, 2s base delay)
Expand Down Expand Up @@ -125,12 +124,12 @@ cd src/app/reactjs/reminders-app && npm test
cd blockchain && npm test

# Run Cypress E2E tests
cd test/cypress && npm run cy:run
cd src/test/cypress && npm run cy:run

# Run .NET API tests (requires running API)
docker compose up postgres ganache -d
docker compose --profile api up postgres ganache -d
cd src/server/api/dotnet/Reminders.Api && dotnet run &
cd test/server/dotnet/Reminders.Api.Test && dotnet test
cd src/test/server/dotnet/Reminders.Api.Test && dotnet test
```

### Blockchain Development
Expand Down Expand Up @@ -195,27 +194,28 @@ Uses **Next.js App Router** with:
DATABASE_PROVIDER=Postgres
CONNECTION_STRING=Host=reminders-postgres;Database=Reminders;Username=postgres;Password=YOUR_PASSWORD_HERE
BLOCKCHAIN_NODE_URL=http://reminders-blockchain:8545
BLOCKCHAIN_PRIVATE_KEY=0xc87509a1c067bbde78beb793e6fa76530b6382a4c0241e5e4a9ec0a0f44dc0d3
BLOCKCHAIN_PRIVATE_KEY=<test-account-private-key-from-.env.example>
CORS_ORIGINS=http://localhost:3000
API_BASE_URL=http://reminders-nginx:9999
```

## Integration Points

### Load Balancing
Two API instances (`dotnet-api` running .NET, `go-api` running Go) behind Nginx ([infrastructure/nginx.conf](../infrastructure/nginx.conf)). Requests to port 9999 are load-balanced round-robin between both backends.
Three API instances (`dotnet-api`, `go-api`, `cpp-api`) behind Nginx ([infrastructure/nginx.conf](../infrastructure/nginx.conf)). Requests to port 9999 are load-balanced round-robin across all three backends.

**Architecture Notes**:
- Both APIs implement the same REST endpoints for reminders CRUD
- All three APIs implement the same REST endpoints for reminders CRUD
- .NET API: Layered architecture with full blockchain integration
- Go API: Lightweight Gin-based implementation, shares same PostgreSQL database
- Both APIs add `X-Server` header to responses (`dotnet` or `go`) for identification
- C++ API: Third load-balanced instance, shares the same PostgreSQL database
- All three APIs add an `X-Server` response header for identification

**Go API Specifics**:
- Uses Gin web framework
- Repository pattern with direct PostgreSQL access
- Environment: `PostgresDefaultConnection` connection string
- Healthcheck: `GET /health` returns `"Healthy"`
- Healthcheck: `GET /health` returns `"Healthy"` (container port 8080, published as host port 5001 via Docker Compose)
- No blockchain integration (API-only service)

See [src/server/api/go/reminders-api/README.md](../src/server/api/go/reminders-api/README.md) for details.
Expand Down
1 change: 1 addition & 0 deletions .github/workflows/blockchain-pull-request.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ on:
- main
paths:
- 'blockchain/**'
- '!blockchain/**/*.md'

permissions:
contents: read
Expand Down
1 change: 1 addition & 0 deletions .github/workflows/cypress-e2e.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ on:
- main
paths:
- 'src/test/cypress/**'
- '!src/test/cypress/**/*.md'
# Allow manual trigger for debugging
workflow_dispatch:

Expand Down
1 change: 1 addition & 0 deletions .github/workflows/deploy-pages.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ on:
- main
paths:
- 'src/app/reactjs/reminders-app/**'
- '!src/app/reactjs/reminders-app/**/*.md'
workflow_dispatch:

# Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages
Expand Down
1 change: 1 addition & 0 deletions .github/workflows/dotnet-code-coverage.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ on:
- 'src/app/dotnet/**'
- 'src/server/services/dotnet/**'
- 'src/test/server/dotnet/**'
- '!**/*.md'

permissions:
contents: read
Expand Down
1 change: 1 addition & 0 deletions .github/workflows/dotnet-pull-request.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ on:
- 'src/app/dotnet/**'
- 'src/server/services/dotnet/**'
- 'src/test/server/dotnet/**'
- '!**/*.md'

permissions:
contents: read
Expand Down
1 change: 1 addition & 0 deletions .github/workflows/go-pull-request.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ on:
- main
paths:
- 'src/server/api/go/reminders-api/**'
- '!src/server/api/go/reminders-api/**/*.md'

permissions:
contents: read
Expand Down
1 change: 1 addition & 0 deletions .github/workflows/react-pull-request.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ on:
- main
paths:
- 'src/app/reactjs/reminders-app/**'
- '!src/app/reactjs/reminders-app/**/*.md'

permissions:
contents: read
Expand Down
125 changes: 102 additions & 23 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ cp .env.example .env
# Edit .env and configure your settings

# Start infrastructure services
docker compose up postgres ganache -d
docker compose --profile api up postgres ganache -d

# Build the API
cd src/server/api/dotnet/Reminders.Api
Expand All @@ -62,22 +62,25 @@ npm install

```text
Reminders/
├── blockchain/ # Hardhat smart contracts
│ ├── contracts/ # Solidity contracts
│ ├── scripts/ # Deployment scripts
│ └── test/ # Contract tests
├── docs/ # GitHub Pages documentation
├── infrastructure/ # Nginx configs, k6 tests
├── blockchain/ # Hardhat smart contracts
│ ├── contracts/ # Solidity contracts
│ ├── scripts/ # Deployment scripts
│ └── test/ # Contract tests
├── docs/ # GitHub Pages documentation
├── infrastructure/ # Nginx configs, k6 tests
├── src/
│ ├── app/
│ │ ├── dotnet/ # ASP.NET MVC application
│ │ └── reactjs/ # Next.js React application
│ └── server/
│ ├── api/dotnet/ # ASP.NET Core Web API
│ └── data/ # Data access layers
└── test/ # Test projects
├── cypress/ # E2E tests
└── server/dotnet/ # API unit tests
│ │ ├── dotnet/ # ASP.NET MVC application
│ │ └── reactjs/ # Next.js React application
│ ├── server/
│ │ ├── api/
│ │ │ ├── dotnet/ # ASP.NET Core Web API
│ │ │ ├── go/ # Go (Gin) API
│ │ │ └── cpp/ # C++ API
│ │ └── services/dotnet/ # Migration runner service
│ └── test/
│ ├── cypress/ # E2E tests
│ └── server/dotnet/ # API unit tests
```

## Database Migrations
Expand All @@ -91,7 +94,65 @@ The project supports both **PostgreSQL** (default) and **SQL Server**. Migration

### Expected Behavior

When starting the API with PostgreSQL (default configuration), you may see a migration error message for SQL Server migrations. **This is expected and harmless**. The PostgreSQL migrations apply successfully, and the SQL Server migration fails because you're using PostgreSQL.
Migrations are applied by a **dedicated migration runner service** (`src/server/services/dotnet/Reminders.MigrationsRunner/`), not by the API itself. Docker Compose starts `postgres`, waits for it to report healthy, then runs `migrations` to completion before either API instance starts. When PostgreSQL is the active provider (default), the runner also attempts the SQL Server migration set and logs an expected failure for it - **this is expected and harmless**. Only the PostgreSQL migrations are actually applied.

### How the Runner Works

- **Technology**: .NET 8.0 console app with an embedded HTTP health endpoint (`http://localhost:8081/healthz`, development mode only). Runs once per deployment and exits, it is not a long-running service.
- **Order**: `postgres` starts and reports healthy, then `migrations` runs with retry logic (exponential backoff, 5 attempts, 2s base delay), and once it exits with code 0 both `dotnet-api` and `go-api` start (each depends on `migrations` with `condition: service_completed_successfully`).
- **Health signal**: the runner exposes HTTP 500 while running and HTTP 200 once migrations succeed.

### Running Migrations Manually

**Local development (without Docker):**

```bash
cd src/server/services/dotnet/Reminders.MigrationsRunner

export ConnectionStrings__DefaultConnection="Host=localhost;Database=Reminders;Username=postgres;Password=yourpassword"
export DatabaseProvider="Postgres"

dotnet run

# check health endpoint in another terminal
curl http://localhost:8081/healthz
```

**Via Docker Compose (recommended):**

```bash
docker compose --profile all up -d

# check migration runner logs
docker compose logs migrations

# verify it completed successfully - should show "Exited (0)"
docker compose ps migrations
```

### Runner Configuration

Settings live in `appsettings.json`, or override via environment variables:

```json
{
"ConnectionStrings": {
"DefaultConnection": "Host=postgres;Database=Reminders;..."
},
"DatabaseProvider": "Postgres",
"MigrationRunner": {
"MaxRetryAttempts": 5,
"RetryBaseDelaySeconds": 2
}
}
```

```bash
ConnectionStrings__DefaultConnection="..."
DatabaseProvider="Postgres"
MigrationRunner__MaxRetryAttempts=5
MigrationRunner__RetryBaseDelaySeconds=2
```

### Creating New Migrations

Expand All @@ -115,6 +176,12 @@ dotnet ef migrations add MigrationName \
--output-dir Layers/Data/EntityFramework/SqlServer/Migrations
```

### Troubleshooting Migrations

- **Runner fails to start**: check `docker compose logs migrations`. Usual causes are the database not being ready yet, an invalid connection string, or missing environment variables.
- **Migrations fail to apply**: check the logs for the detailed error, then test the connection directly with `docker compose exec postgres psql -U root -d Reminders -c "\dt"`. As a last resort, `docker compose down -v && docker compose --profile all up -d` resets the database (this deletes all data).
- **API won't start after a migration failure**: the runner must exit with code 0 for the APIs to start. Check `docker compose ps migrations`; if the exit code is non-zero, fix the underlying issue and run `docker compose up migrations -d --force-recreate`.

## Making Changes

### Branching Strategy
Expand Down Expand Up @@ -150,7 +217,7 @@ refactor: Improve error handling
```bash
# .NET API Integration Tests (requires running API)
# Start API first:
docker compose up postgres ganache -d
docker compose --profile api up postgres ganache -d
cd src/server/api/dotnet/Reminders.Api
dotnet run &

Expand All @@ -163,7 +230,7 @@ cd src/app/reactjs/reminders-app
npm test

# Cypress E2E Tests
cd test/cypress
cd src/test/cypress
npm run cy:run

# Blockchain Tests (all passing ✅)
Expand Down Expand Up @@ -223,7 +290,7 @@ Example: [feat] Add email notification for reminders

```bash
# Just the database
docker compose up postgres -d
docker compose --profile api up postgres -d

# Just the blockchain
docker compose up ganache -d
Expand All @@ -248,11 +315,17 @@ npm run dev
#### Port Already in Use

```bash
# Find and kill process using port 5000
# Find and kill process using a port (example: 5000)
lsof -ti:5000 | xargs kill -9

# Or stop all containers and try again
docker compose down
docker compose --profile all up -d
```

#### Docker Build Fails
#### Docker Build Fails or Takes Too Long

The first build can take 10+ minutes since Docker has to download base images (.NET, Node.js, PostgreSQL, Nginx, Ganache) and install all dependencies. Subsequent builds are much faster thanks to layer caching. If a build is actually failing:

```bash
# Clean Docker cache
Expand All @@ -262,9 +335,15 @@ docker compose down -v

#### Database Connection Issues

- Ensure PostgreSQL container is running: `docker compose ps`
- Ensure PostgreSQL container is running and healthy: `docker compose ps` / `docker logs reminders-postgres`
- Check `.env` file has correct credentials
- Verify port 5432 is not blocked
- Verify port 5432 is not blocked, and give PostgreSQL a few seconds to finish initializing

#### React App Shows API Connection Error

- Ensure the API is reachable: `curl http://localhost:9999/health`
- Check CORS configuration in your `.env` file
- Verify Nginx is running: `docker compose ps reminders-nginx`

## Questions or Need Help?

Expand Down
Loading