diff --git a/README.md b/README.md index 5304939..54dab25 100644 --- a/README.md +++ b/README.md @@ -2,917 +2,292 @@ A social platform for craft beer enthusiasts to discover breweries, share reviews, and connect with fellow beer lovers. -## Project Status +## Quick Links -This project is in active development, transitioning from a full-stack Next.js application to a **multi-project monorepo** with: -- **Backend**: .NET 10 Web API with SQL Server -- **Frontend**: Next.js with TypeScript -- **Architecture**: SQL-first approach using stored procedures +📚 **Documentation** +- [Getting Started](docs/getting-started.md) - Setup and installation +- [Architecture](docs/architecture.md) - System design and patterns +- [Database](docs/database.md) - Schema and stored procedures +- [Docker Guide](docs/docker.md) - Container deployment +- [Testing](docs/testing.md) - Test strategy and commands +- [Environment Variables](docs/environment-variables.md) - Configuration reference -**Current State** (February 2026): -- Core authentication and user management APIs functional -- Database schema and migrations established -- Domain, Infrastructure, Repository, and Service layers implemented -- Frontend integration with .NET API in progress -- Migrating remaining features from Next.js serverless functions +📊 **Diagrams** (Generate with `make`) +- [Architecture](docs/diagrams/pdf/architecture.pdf) - Layered architecture +- [Deployment](docs/diagrams/pdf/deployment.pdf) - Docker topology +- [Authentication Flow](docs/diagrams/pdf/authentication-flow.pdf) - Auth sequence +- [Database Schema](docs/diagrams/pdf/database-schema.pdf) - Entity relationships -**The Next.js app currently runs standalone with its original Prisma/Neon Postgres backend. It will be fully integrated with the .NET API once feature parity is achieved.** +## Project Status + +**Active Development** - Transitioning from full-stack Next.js to multi-project monorepo + +- ✅ Core authentication and user management APIs +- ✅ Database schema with migrations and seeding +- ✅ Layered architecture (Domain, Service, Infrastructure, Repository, API) +- ✅ Comprehensive test suite (unit + integration) +- 🚧 Frontend integration with .NET API (in progress) +- 🚧 Migration from Next.js serverless functions --- -## Repository Structure +## Tech Stack -``` -src/Core/ -├── API/ -│ ├── API.Core/ # ASP.NET Core Web API with Swagger/OpenAPI -│ └── API.Specs/ # Integration tests using Reqnroll (BDD) -├── Database/ -│ ├── Database.Migrations/ # DbUp migrations (embedded SQL scripts) -│ └── Database.Seed/ # Database seeding for development/testing -├── Domain/ -│ └── Domain.csproj # Domain entities and models -│ └── Entities/ # Core domain entities (UserAccount, UserCredential, etc.) -├── Infrastructure/ -│ ├── Infrastructure.Jwt/ # JWT token generation and validation -│ ├── Infrastructure.PasswordHashing/ # Argon2id password hashing -│ └── Infrastructure.Repository/ -│ ├── Infrastructure.Repository/ # Data access layer (stored procedure-based) -│ └── Infrastructure.Repository.Tests/ # Unit tests for repositories -└── Service/ - └── Service.Core/ # Business logic layer - -Website/ # Next.js frontend application -misc/ -└── raw-data/ # Sample data files (breweries, beers) -``` - -### Key Components - -**API Layer** (`API.Core`) -- RESTful endpoints for authentication, users, and breweries -- Controllers: `AuthController`, `UserController` -- Configured with Swagger UI for API exploration -- Health checks and structured logging -- Middleware for error handling and request processing - -**Database Layer** -- SQL Server with stored procedures for all data operations -- DbUp for version-controlled migrations -- Comprehensive schema including users, breweries, beers, locations, and social features -- Seeders for development data (users, locations across US/Canada/Mexico) - -**Domain Layer** (`Domain`) -- Core business entities and models -- Entities: `UserAccount`, `UserCredential`, `UserVerification` -- Shared domain logic and value objects -- No external dependencies - pure domain model - -**Infrastructure Layer** -- **Infrastructure.Jwt**: JWT token generation, validation, and configuration -- **Infrastructure.PasswordHashing**: Argon2id password hashing with configurable parameters -- **Infrastructure.Password**: Password utilities and validation -- **Infrastructure.Repository**: Repository pattern infrastructure and base classes - -**Repository Layer** (`Infrastructure.Repository`) -- Abstraction over SQL Server using ADO.NET -- `ISqlConnectionFactory` for connection management -- Repositories: `AuthRepository`, `UserAccountRepository` -- All data access via stored procedures (no inline SQL) - -**Service Layer** (`Service.Core`) -- Business logic and orchestration -- Services: `AuthService`, `UserService` -- Integration with infrastructure components -- Transaction management and business rule enforcement - -**Frontend** (`Website`) -- Next.js 14+ with TypeScript -- TailwindCSS, Headless UI, DaisyUI for UI components -- Integrations: Mapbox (maps), Cloudinary (image hosting) -- Progressive migration from serverless API routes to .NET API +**Backend**: .NET 10, ASP.NET Core, SQL Server 2022, DbUp +**Frontend**: Next.js 14+, TypeScript, TailwindCSS +**Testing**: xUnit, Reqnroll (BDD), FluentAssertions, Moq +**Infrastructure**: Docker, Docker Compose +**Security**: Argon2id password hashing, JWT (HS256) --- -## Technology Stack - -### Backend -- **.NET 10** - Latest C# and runtime features -- **ASP.NET Core** - Web API framework -- **SQL Server 2022** - Primary database -- **DbUp** - Database migration tool -- **Argon2id** - Password hashing -- **JWT** - Authentication tokens - -### Frontend -- **Next.js 14+** - React framework -- **TypeScript** - Type safety -- **TailwindCSS** - Utility-first CSS -- **Mapbox GL** - Interactive maps -- **Cloudinary** - Image management - -### Testing -- **xUnit** - Unit testing framework -- **Reqnroll** - BDD/Gherkin integration testing -- **FluentAssertions** - Assertion library -- **DbMocker** - Database mocking - -### DevOps & Infrastructure -- **Docker** - Containerization for all services -- **Docker Compose** - Multi-container orchestration -- **Multi-stage builds** - Optimized image sizes -- **Health checks** - Container readiness and liveness probes -- **Separate environments** - Development, testing, and production configurations - ---- - -## Getting Started +## Quick Start ### Prerequisites -- **.NET SDK 10+** ([Download](https://dotnet.microsoft.com/download)) -- **Node.js 18+** ([Download](https://nodejs.org/)) -- **Docker Desktop** ([Download](https://www.docker.com/products/docker-desktop)) +- [.NET SDK 10+](https://dotnet.microsoft.com/download) +- [Docker Desktop](https://www.docker.com/products/docker-desktop) +- [Node.js 18+](https://nodejs.org/) (for frontend) -### Quick Start (Development Environment) - -1. **Clone the repository** - ```bash - git clone - cd biergarten-app - ``` - -2. **Configure environment variables** - - Copy the example file and customize: - ```bash - cp .env.example .env.dev - ``` - - Required variables in `.env.dev`: - ```bash - # Database (component-based for Docker) - DB_SERVER=sqlserver,1433 - DB_NAME=Biergarten - DB_USER=sa - DB_PASSWORD=YourStrong!Passw0rd - - # JWT Authentication - JWT_SECRET=your-secret-key-minimum-32-characters-required - ``` - - For a complete list of all backend and frontend environment variables, see the [Environment Variables](#environment-variables) section. - -3. **Start the development environment** - ```bash - docker compose -f docker-compose.dev.yaml up -d - ``` - - This will: - - Start SQL Server - - Run database migrations - - Seed initial data - - Start the API on http://localhost:8080 - -4. **Access Swagger UI** - - Navigate to http://localhost:8080/swagger to explore and test API endpoints. - -5. **Run the frontend** (optional) - - The frontend requires additional environment variables. See [Frontend Variables](#frontend-variables-nextjs) section. - - ```bash - cd Website - - # Create .env.local with frontend variables - # (see Environment Variables section) - - npm install - npm run dev - ``` - - For complete environment variable documentation, see the [Environment Variables](#environment-variables) section below. - -### Manual Setup (Without Docker) - -#### Backend Setup - -1. **Start SQL Server locally** or use a hosted instance - -2. **Set environment variables** - - See [Backend Variables](#backend-variables-net-api) for details. - - ```bash - # macOS/Linux - export DB_CONNECTION_STRING="Server=localhost,1433;Database=Biergarten;User Id=sa;Password=YourStrong!Passw0rd;TrustServerCertificate=True;" - export JWT_SECRET="your-secret-key-minimum-32-characters-required" - - # Windows PowerShell - $env:DB_CONNECTION_STRING="Server=localhost,1433;Database=Biergarten;User Id=sa;Password=YourStrong!Passw0rd;TrustServerCertificate=True;" - $env:JWT_SECRET="your-secret-key-minimum-32-characters-required" - ``` - -3. **Run migrations** - ```bash - cd src/Core - dotnet run --project Database/Database.Migrations/Database.Migrations.csproj - ``` - -4. **Seed the database** - ```bash - dotnet run --project Database/Database.Seed/Database.Seed.csproj - ``` - -5. **Start the API** - ```bash - dotnet run --project API/API.Core/API.Core.csproj - ``` - -#### Frontend Setup - -1. **Navigate to Website directory** - ```bash - cd Website - ``` - -2. **Create environment file** - - Create `.env.local` with required frontend variables. See [Frontend Variables](#frontend-variables-nextjs) for the complete list. - - ```bash - # Example minimal setup - BASE_URL=http://localhost:3000 - NODE_ENV=development - - # Generate secrets - CONFIRMATION_TOKEN_SECRET=$(openssl rand -base64 127) - RESET_PASSWORD_TOKEN_SECRET=$(openssl rand -base64 127) - SESSION_SECRET=$(openssl rand -base64 127) - - # Add external service credentials - NEXT_PUBLIC_CLOUDINARY_CLOUD_NAME=your-cloud-name - CLOUDINARY_KEY=your-api-key - CLOUDINARY_SECRET=your-api-secret - # ... (see Environment Variables section for complete list) - ``` - -3. **Install dependencies** - ```bash - npm install - ``` - -4. **Run Prisma migrations** (current frontend database) - ```bash - npx prisma generate - npx prisma migrate dev - ``` - -5. **Start the development server** - ```bash - npm run dev - ``` - - The frontend will be available at http://localhost:3000 - ---- - -## Environment Variables - -### Overview - -The Biergarten App uses environment variables for configuration across both backend (.NET API) and frontend (Next.js) services. This section provides complete documentation for all required and optional variables. - -**Configuration Patterns:** -- **Backend**: Direct environment variable access via `Environment.GetEnvironmentVariable()` -- **Frontend**: Centralized configuration module at [src/Website/src/config/env/index.ts](src/Website/src/config/env/index.ts) with Zod validation -- **Docker**: Environment-specific `.env` files (`.env.dev`, `.env.test`, `.env.prod`) - -### Backend Variables (.NET API) - -The .NET API requires environment variables for database connectivity and JWT authentication. These can be set directly in your shell or via `.env` files when using Docker. - -#### Database Connection - -**Option 1: Component-Based (Recommended for Docker)** - -Use individual components to build the connection string: +### Start Development Environment ```bash -DB_SERVER=sqlserver,1433 # SQL Server address and port -DB_NAME=Biergarten # Database name -DB_USER=sa # SQL Server username -DB_PASSWORD=YourStrong!Passw0rd # SQL Server password -DB_TRUST_SERVER_CERTIFICATE=True # Optional, defaults to True -``` +# Clone repository +git clone +cd the-biergarten-app -**Option 2: Full Connection String (Local Development)** - -Provide a complete SQL Server connection string: - -```bash -DB_CONNECTION_STRING="Server=localhost,1433;Database=Biergarten;User Id=sa;Password=YourStrong!Passw0rd;TrustServerCertificate=True;" -``` - -The connection factory checks for `DB_CONNECTION_STRING` first, then falls back to building from components. See [DefaultSqlConnectionFactory.cs](src/Core/Infrastructure/Infrastructure.Repository/Infrastructure.Repository/Sql/DefaultSqlConnectionFactory.cs). - -#### JWT Authentication - -```bash -JWT_SECRET=your-secret-key-minimum-32-characters-required -``` - -- **Required**: Yes -- **Minimum Length**: 32 characters -- **Used For**: Signing JWT tokens for user authentication -- **Location**: [JwtService.cs](src/Core/Service/Service.Core/Services/JwtService.cs) - -**Additional JWT Configuration** (in `appsettings.json`): -- `Jwt:ExpirationMinutes` - Token lifetime (default: 60) -- `Jwt:Issuer` - Token issuer (default: "biergarten-api") -- `Jwt:Audience` - Token audience (default: "biergarten-users") - -#### Migration Control - -```bash -CLEAR_DATABASE=true # Development/Testing only -``` - -- **Required**: No -- **Effect**: If set to "true", drops and recreates the database during migrations -- **Usage**: Development and testing environments only -- **Warning**: Never use in production - -### Frontend Variables (Next.js) - -The Next.js frontend requires environment variables for external services, authentication, and database connectivity. Create a `.env` or `.env.local` file in the `Website/` directory. - -All variables are validated at runtime using Zod schemas. See [src/Website/src/config/env/index.ts](src/Website/src/config/env/index.ts). - -#### Base Configuration - -```bash -BASE_URL=http://localhost:3000 # Application base URL -NODE_ENV=development # Environment: development, production, test -``` - -#### Authentication & Sessions - -```bash -# Token signing secrets (generate with: openssl rand -base64 127) -CONFIRMATION_TOKEN_SECRET= # Email confirmation tokens -RESET_PASSWORD_TOKEN_SECRET= # Password reset tokens -SESSION_SECRET= # Session cookie signing - -# Session configuration -SESSION_TOKEN_NAME=biergarten # Cookie name -SESSION_MAX_AGE=604800 # Cookie max age in seconds (604800 = 1 week) -``` - -#### Database (Prisma/Postgres) - -**Current State**: The frontend currently uses Neon Postgres with Prisma. This will migrate to the SQL Server backend once feature parity is achieved. - -```bash -POSTGRES_PRISMA_URL=postgresql://user:pass@host/db?pgbouncer=true # Pooled connection -POSTGRES_URL_NON_POOLING=postgresql://user:pass@host/db # Direct connection (migrations) -SHADOW_DATABASE_URL=postgresql://user:pass@host/shadow_db # Prisma shadow DB -``` - -#### Admin Account - -```bash -ADMIN_PASSWORD=SecureAdminPassword123! # Initial admin account password for seeding -``` - -### Docker Variables - -When running services in Docker, additional environment variables control container behavior: - -#### ASP.NET Core - -```bash -ASPNETCORE_ENVIRONMENT=Development # Development, Production -ASPNETCORE_URLS=http://0.0.0.0:8080 # Binding address -DOTNET_RUNNING_IN_CONTAINER=true # Container execution flag -``` - -#### SQL Server (Docker Container) - -```bash -SA_PASSWORD=YourStrong!Passw0rd # SQL Server SA password (maps to DB_PASSWORD) -ACCEPT_EULA=Y # Accept SQL Server EULA -MSSQL_PID=Express # SQL Server edition (Express, Developer, etc.) -``` - -**Note**: `SA_PASSWORD` in the SQL Server container maps to `DB_PASSWORD` for the API application. - -### External Services - -The frontend integrates with several third-party services. Sign up for accounts and retrieve API credentials: - -#### Cloudinary (Image Hosting) - -```bash -NEXT_PUBLIC_CLOUDINARY_CLOUD_NAME=your-cloud-name # Public, client-accessible -CLOUDINARY_KEY=your-api-key # Server-side API key -CLOUDINARY_SECRET=your-api-secret # Server-side secret -``` - -**Setup**: -1. Sign up at [cloudinary.com](https://cloudinary.com) -2. Navigate to Dashboard -3. Copy Cloud Name, API Key, and API Secret - -**Note**: The `NEXT_PUBLIC_` prefix makes the cloud name accessible in client-side code. - -#### Mapbox (Maps & Geocoding) - -```bash -MAPBOX_ACCESS_TOKEN=pk.your-public-token -``` - -**Setup**: -1. Create account at [mapbox.com](https://mapbox.com) -2. Navigate to Account → Tokens -3. Create a new token with public scopes -4. Copy the access token - -#### SparkPost (Email Service) - -```bash -SPARKPOST_API_KEY=your-api-key -SPARKPOST_SENDER_ADDRESS=noreply@yourdomain.com -``` - -**Setup**: -1. Sign up at [sparkpost.com](https://sparkpost.com) -2. Verify your sending domain or use sandbox -3. Create an API key with "Send via SMTP" permission -4. Configure sender address (must match verified domain) - -### Generating Secrets - -For authentication secrets (`JWT_SECRET`, `CONFIRMATION_TOKEN_SECRET`, etc.), generate cryptographically secure random values: - -**macOS/Linux:** -```bash -openssl rand -base64 127 -``` - -**Windows PowerShell:** -```powershell -[Convert]::ToBase64String((1..127 | ForEach-Object { Get-Random -Maximum 256 })) -``` - -**Requirements**: -- `JWT_SECRET`: Minimum 32 characters -- Session/token secrets: Recommend 127+ characters for maximum security - -### Environment File Structure - -The project uses multiple environment files depending on the context: - -#### Backend/Docker (Root Directory) - -- **`.env.example`** - Template file (tracked in Git) -- **`.env.dev`** - Development environment (gitignored) -- **`.env.test`** - Testing environment (gitignored) -- **`.env.prod`** - Production environment (gitignored) - -**Setup**: -```bash -# Copy template and customize +# Configure environment cp .env.example .env.dev -# Edit .env.dev with your values -``` +# Edit .env.dev with your settings -Docker Compose files reference these: -- `docker-compose.dev.yaml` → `.env.dev` -- `docker-compose.test.yaml` → `.env.test` -- `docker-compose.prod.yaml` → `.env.prod` - -#### Frontend (Website Directory) - -- **`.env`** or **`.env.local`** - Local development (gitignored) - -**Setup**: -```bash -cd Website -# Create .env file with frontend variables -touch .env.local -``` - -### Variable Reference Table - -| Variable | Backend | Frontend | Docker | Required | Notes | -|----------|---------|----------|--------|----------|-------| -| **Database** | -| `DB_SERVER` | ✓ | | ✓ | Yes* | SQL Server address | -| `DB_NAME` | ✓ | | ✓ | Yes* | Database name | -| `DB_USER` | ✓ | | ✓ | Yes* | SQL username | -| `DB_PASSWORD` | ✓ | | ✓ | Yes* | SQL password | -| `DB_CONNECTION_STRING` | ✓ | | | Yes* | Alternative to components | -| `DB_TRUST_SERVER_CERTIFICATE` | ✓ | | ✓ | No | Defaults to True | -| `SA_PASSWORD` | | | ✓ | Yes | SQL Server container only | -| **Authentication (Backend)** | -| `JWT_SECRET` | ✓ | | ✓ | Yes | Min 32 chars | -| **Authentication (Frontend)** | -| `CONFIRMATION_TOKEN_SECRET` | | ✓ | | Yes | Email confirmation | -| `RESET_PASSWORD_TOKEN_SECRET` | | ✓ | | Yes | Password reset | -| `SESSION_SECRET` | | ✓ | | Yes | Session signing | -| `SESSION_TOKEN_NAME` | | ✓ | | No | Default: "biergarten" | -| `SESSION_MAX_AGE` | | ✓ | | No | Default: 604800 | -| **Base Configuration** | -| `BASE_URL` | | ✓ | | Yes | App base URL | -| `NODE_ENV` | | ✓ | | Yes | development/production | -| `ASPNETCORE_ENVIRONMENT` | ✓ | | ✓ | Yes | Development/Production | -| `ASPNETCORE_URLS` | ✓ | | ✓ | Yes | Binding address | -| **Database (Frontend - Current)** | -| `POSTGRES_PRISMA_URL` | | ✓ | | Yes | Pooled connection | -| `POSTGRES_URL_NON_POOLING` | | ✓ | | Yes | Direct connection | -| `SHADOW_DATABASE_URL` | | ✓ | | No | Prisma shadow DB | -| **External Services** | -| `NEXT_PUBLIC_CLOUDINARY_CLOUD_NAME` | | ✓ | | Yes | Public, client-side | -| `CLOUDINARY_KEY` | | ✓ | | Yes | Server-side | -| `CLOUDINARY_SECRET` | | ✓ | | Yes | Server-side | -| `MAPBOX_ACCESS_TOKEN` | | ✓ | | Yes | Maps/geocoding | -| `SPARKPOST_API_KEY` | | ✓ | | Yes | Email service | -| `SPARKPOST_SENDER_ADDRESS` | | ✓ | | Yes | From address | -| **Other** | -| `ADMIN_PASSWORD` | | ✓ | | No | Seeding only | -| `CLEAR_DATABASE` | ✓ | | ✓ | No | Dev/test only | -| `ACCEPT_EULA` | | | ✓ | Yes | SQL Server EULA | -| `MSSQL_PID` | | | ✓ | No | SQL Server edition | - -\* Either `DB_CONNECTION_STRING` OR the four component variables (`DB_SERVER`, `DB_NAME`, `DB_USER`, `DB_PASSWORD`) are required. - ---- - -## Testing - -### Run All Tests (Docker) -```bash -docker compose -f docker-compose.test.yaml up --abort-on-container-exit -``` - -This runs: -- **API.Specs** - BDD integration tests -- **Infrastructure.Repository.Tests** - Unit tests for data access - -Test results are output to `./test-results/`. - -### Run Tests Locally - -**Integration Tests (API.Specs)** -```bash -cd src/Core -dotnet test API/API.Specs/API.Specs.csproj -``` - -**Unit Tests (Infrastructure.Repository.Tests)** -```bash -cd src/Core -dotnet test Infrastructure/Infrastructure.Repository/Infrastructure.Repository.Tests/Repository.Tests.csproj -``` - -### Test Features - -Current test coverage includes: -- User authentication (login, registration) -- JWT token generation -- Password validation -- 404 error handling -- User repository operations - ---- - -## Database Schema - -The database uses a SQL-first approach with comprehensive normalization and referential integrity. - -### Key Tables - -**User Management** -- `UserAccount` - User profiles -- `UserCredential` - Password hashes (Argon2id) -- `UserVerification` - Account verification status -- `UserAvatar` - Profile pictures -- `UserFollow` - Social following relationships - -**Location Data** -- `Country` - ISO 3166-1 country codes -- `StateProvince` - ISO 3166-2 subdivisions -- `City` - City/municipality data - -**Content** -- `BreweryPost` - Brewery information -- `BreweryPostLocation` - Geographic data with `GEOGRAPHY` type -- `BeerStyle` - Beer style taxonomy -- `BeerPost` - Individual beers with ABV/IBU -- `BeerPostComment` - User reviews and ratings -- `Photo` - Image metadata - -**Stored Procedures** (examples) -- `USP_RegisterUser` - Create user account with credential -- `USP_GetUserAccountByUsername` - Retrieve user by username -- `USP_RotateUserCredential` - Update password -- `USP_CreateCountry/StateProvince/City` - Location management - ---- - -## Authentication & Security - -- **Password Hashing**: Argon2id with configurable parameters - - Salt: 128-bit (16 bytes) - - Hash: 256-bit (32 bytes) - - Memory: 64MB - - Iterations: 4 - - Parallelism: Based on CPU cores - -- **JWT Tokens**: HS256 signing - - Claims: User ID (sub), Username (unique_name), JTI - - Configurable expiration (60-120 minutes) - - Secret key from environment variable - -- **Credential Management**: - - Credential rotation/invalidation supported - - Expiry tracking (90-day default) - - Revocation timestamps - ---- - -## Architecture Patterns - -### Layered Architecture -``` -API (Controllers) - | -Service Layer (Business Logic) - | -Repository Layer (Data Access) - | -Database (SQL Server) -``` - -### Design Patterns -- **Repository Pattern**: Abstraction over data access -- **Dependency Injection**: Constructor injection throughout -- **Factory Pattern**: `ISqlConnectionFactory` for database connections -- **Service Pattern**: Encapsulated business logic - -### SQL-First Approach -- All CRUD operations via stored procedures -- No ORM (Entity Framework, Dapper, etc.) -- Direct ADO.NET for maximum control -- Version-controlled schema via DbUp - ---- - -## Docker & Containerization - - -### Container Architecture - -### Docker Compose Environments - -Three separate compose files manage different environments: - -#### 1. **Development** (`docker-compose.dev.yaml`) -- **Purpose**: Local development with live data -- **Features**: - - SQL Server with persistent volume - - Database migrations with `CLEAR_DATABASE=true` (drops/recreates schema) - - Seed data for testing - - API accessible on `localhost:8080` - - Hot reload support via volume mounts - -**Services**: -```yaml -sqlserver # SQL Server 2022 (port 1433) -database.migrations # Runs DbUp migrations -database.seed # Seeds initial data -api.core # Web API (ports 8080, 8081) -``` - -**Usage**: -```bash -docker compose -f docker-compose.dev.yaml up -d -docker compose -f docker-compose.dev.yaml logs -f # View logs -docker compose -f docker-compose.dev.yaml down # Stop all services -``` - -#### 2. **Testing** (`docker-compose.test.yaml`) -- **Purpose**: Automated CI/CD testing -- **Features**: - - Isolated test database - - Runs integration and unit tests - - Test results exported to `./test-results/` - - Containers exit after tests complete - -**Services**: -```yaml -sqlserver # Test database instance -database.migrations # Fresh schema each run -database.seed # Test data -api.specs # Integration tests (Reqnroll) -repository.tests # Unit tests (xUnit) -``` - -**Usage**: -```bash -docker compose -f docker-compose.test.yaml up --abort-on-container-exit; -docker compose -f docker-compose.test.yaml down -v; -# View results in ./test-results/ -``` - -#### 3. **Production** (`docker-compose.prod.yaml`) -- **Purpose**: Production-like deployment -- **Features**: - - Production logging levels - - No database clearing - - Optimized builds - - Health checks enabled - - Restart policies configured - -**Services**: -```yaml -sqlserver # Production SQL Server -database.migrations # Schema updates only (no drops) -api.core # Production API -``` - -### Service Dependencies - -Docker Compose manages service startup order using **health checks** and **depends_on** conditions: - -```yaml -database.migrations: - depends_on: - sqlserver: - condition: service_healthy # Waits for SQL Server to be ready - -database.seed: - depends_on: - database.migrations: - condition: service_completed_successfully # Waits for migrations -``` - -**Flow**: -1. `sqlserver` starts and runs health check (SQL query) -2. `database.migrations` starts when SQL Server is healthy -3. `database.seed` starts when migrations complete successfully -4. `api.core` starts when seeding completes - -### Health Checks - -SQL Server container includes a health check to ensure it's ready: - -```yaml -healthcheck: - test: ["CMD-SHELL", "/opt/mssql-tools18/bin/sqlcmd -S localhost -U sa -P '${SA_PASSWORD}' -C -Q 'SELECT 1' || exit 1"] - interval: 10s - timeout: 5s - retries: 12 - start_period: 30s -``` - -This prevents downstream services from attempting connections before SQL Server is ready. - -### Volumes - -**Persistent Storage**: -- `sqlserverdata-dev` - Development database data -- `sqlserverdata-test` - Test database data -- `sqlserverdata-prod` - Production database data -- `nuget-cache-dev/prod` - NuGet package cache (speeds up builds) - -**Mounted Volumes**: -```yaml -volumes: - - ./test-results:/app/test-results # Export test results to host - - nuget-cache-dev:/root/.nuget/packages # Cache dependencies -``` - -### Networks - -Each environment uses isolated bridge networks: -- `devnet` - Development network -- `testnet` - Testing network (fully isolated) -- `prodnet` - Production network - -This prevents cross-environment communication and enhances security. - -### Environment Variables - -All containers receive configuration via environment variables: - -```yaml -environment: - ASPNETCORE_ENVIRONMENT: "Development" - DOTNET_RUNNING_IN_CONTAINER: "true" - DB_CONNECTION_STRING: "${DB_CONNECTION_STRING}" - JWT_SECRET: "${JWT_SECRET}" -``` - -Values are populated from the `.env` file in the project root. - -### Container Lifecycle - -**Development Workflow**: -```bash -# Start environment +# Start all services docker compose -f docker-compose.dev.yaml up -d # View logs -docker compose -f docker-compose.dev.yaml logs -f api.core - -# Restart a service -docker compose -f docker-compose.dev.yaml restart api.core - -# Rebuild after code changes -docker compose -f docker-compose.dev.yaml up -d --build api.core - -# Clean shutdown -docker compose -f docker-compose.dev.yaml down - -# Remove volumes (fresh start) -docker compose -f docker-compose.dev.yaml down -v +docker compose -f docker-compose.dev.yaml logs -f ``` -**Testing Workflow**: +**Access**: +- API: http://localhost:8080/swagger +- Health: http://localhost:8080/health + +### Run Tests + ```bash -# Run tests (containers auto-exit) docker compose -f docker-compose.test.yaml up --abort-on-container-exit - -# Check test results -cat test-results/test-results.trx -cat test-results/repository-tests.trx - -# Clean up -docker compose -f docker-compose.test.yaml down -v ``` -## Docker Tips & Troubleshooting +Results are in `./test-results/` -### Common Commands +### Generate Diagrams -**View running containers**: ```bash -docker ps +make # Generate PDFs +make pngs # Generate PNGs +make help # View options ``` -**View all containers (including stopped)**: -```bash -docker ps -a +Requires Java 8+. See [Getting Started](docs/getting-started.md) for details. + +--- + +## Repository Structure + +``` +src/Core/ # Backend (.NET) +├── API/ +│ ├── API.Core/ # ASP.NET Core Web API +│ └── API.Specs/ # Integration tests (Reqnroll) +├── Database/ +│ ├── Database.Migrations/ # DbUp migrations +│ └── Database.Seed/ # Data seeding +├── Domain.Entities/ # Domain models +├── Infrastructure/ # Cross-cutting concerns +│ ├── Infrastructure.Jwt/ +│ ├── Infrastructure.PasswordHashing/ +│ ├── Infrastructure.Email/ +│ ├── Infrastructure.Repository/ +│ └── Infrastructure.Repository.Tests/ +└── Service/ # Business logic + ├── Service.Auth/ + ├── Service.Auth.Tests/ + └── Service.UserManagement/ + +Website/ # Frontend (Next.js) +docs/ # Documentation +docs/diagrams/ # PlantUML diagrams ``` -**View logs for a specific service**: +--- + +## Key Features + +### Implemented +- ✅ User registration and authentication +- ✅ JWT token-based auth +- ✅ Argon2id password hashing +- ✅ SQL Server with stored procedures +- ✅ Database migrations (DbUp) +- ✅ Docker containerization +- ✅ Comprehensive test suite +- ✅ Swagger/OpenAPI documentation +- ✅ Health checks + +### Planned +- [ ] Brewery discovery and management +- [ ] Beer reviews and ratings +- [ ] Social following/followers +- [ ] Geospatial brewery search +- [ ] Image upload (Cloudinary) +- [ ] Email notifications +- [ ] OAuth integration + +--- + +## Architecture Highlights + +### Layered Architecture +``` +API Layer (Controllers) + ↓ +Service Layer (Business Logic) + ↓ +Infrastructure Layer (Repositories, JWT, Email) + ↓ +Domain Layer (Entities) + ↓ +Database (SQL Server + Stored Procedures) +``` + +### SQL-First Approach +- All queries via stored procedures +- No ORM (no Entity Framework) +- Database handles business logic +- Version-controlled schema + +### Security +- **Password Hashing**: Argon2id (64MB memory, 4 iterations) +- **JWT Tokens**: HS256 with configurable expiration +- **Credential Rotation**: Built-in password change support + +See [Architecture Guide](docs/architecture.md) for details. + +--- + +## Testing + +The project includes three test suites: + +| Suite | Type | Framework | Purpose | +|-------|------|-----------|---------| +| **API.Specs** | Integration | Reqnroll (BDD) | End-to-end API testing | +| **Repository.Tests** | Unit | xUnit | Data access layer | +| **Service.Auth.Tests** | Unit | xUnit + Moq | Business logic | + +**Run All Tests**: ```bash +docker compose -f docker-compose.test.yaml up --abort-on-container-exit +``` + +**Run Individual Test Suite**: +```bash +cd src/Core +dotnet test API/API.Specs/API.Specs.csproj +dotnet test Infrastructure/Infrastructure.Repository.Tests/Infrastructure.Repository.Tests.csproj +dotnet test Service/Service.Auth.Tests/Service.Auth.Tests.csproj +``` + +See [Testing Guide](docs/testing.md) for more information. + +--- + +## Docker Environments + +The project uses three Docker Compose configurations: + +| File | Purpose | Features | +|------|---------|----------| +| **docker-compose.dev.yaml** | Development | Persistent data, hot reload, Swagger UI | +| **docker-compose.test.yaml** | CI/CD Testing | Isolated DB, auto-exit, test results export | +| **docker-compose.prod.yaml** | Production | Optimized builds, health checks, restart policies | + +**Common Commands**: +```bash +# Development +docker compose -f docker-compose.dev.yaml up -d docker compose -f docker-compose.dev.yaml logs -f api.core +docker compose -f docker-compose.dev.yaml down -v + +# Testing +docker compose -f docker-compose.test.yaml up --abort-on-container-exit +docker compose -f docker-compose.test.yaml down -v + +# Build +docker compose -f docker-compose.dev.yaml build +docker compose -f docker-compose.dev.yaml build --no-cache ``` -**Execute commands in a running container**: +See [Docker Guide](docs/docker.md) for troubleshooting and advanced usage. + +--- + +## Configuration + +### Required Environment Variables + +**Backend** (`.env.dev`): ```bash -docker exec -it dev-env-api-core bash +DB_SERVER=sqlserver,1433 +DB_NAME=Biergarten +DB_USER=sa +DB_PASSWORD=YourStrong!Passw0rd +JWT_SECRET= ``` -**Connect to SQL Server from host**: +**Frontend** (`.env.local`): ```bash -# Using sqlcmd (if installed) -sqlcmd -S localhost,1433 -U sa -P 'YourStrong!Passw0rd' -C - -# Config -Server: localhost,1433 -Authentication: SQL Login -Username: sa -Password: (from .env) +BASE_URL=http://localhost:3000 +NODE_ENV=development +CONFIRMATION_TOKEN_SECRET= +RESET_PASSWORD_TOKEN_SECRET= +SESSION_SECRET= +# + External services (Cloudinary, Mapbox, SparkPost) ``` ---- - -## Roadmap - -### Near-term -- [ ] Complete API endpoints for breweries and beers -- [ ] Integrate frontend with .NET API -- [ ] Implement image upload service -- [ ] Add comprehensive API documentation - -### Medium-term -- [ ] Geospatial queries for nearby breweries -- [ ] Advanced authentication (OAuth, 2FA) ---- - -## License - -See [LICENSE.md](LICENSE.md) for details. +See [Environment Variables Guide](docs/environment-variables.md) for complete reference. --- -## Contact & Support +## Contributing -For questions about this project, please open an issue in the repository. +1. Fork the repository +2. Create a feature branch (`git checkout -b feature/amazing-feature`) +3. Commit your changes (`git commit -m 'Add amazing feature'`) +4. Push to the branch (`git push origin feature/amazing-feature`) +5. Open a Pull Request +### Development Workflow + +1. Start development environment: `docker compose -f docker-compose.dev.yaml up -d` +2. Make changes to code +3. Run tests: `docker compose -f docker-compose.test.yaml up --abort-on-container-exit` +4. Rebuild if needed: `docker compose -f docker-compose.dev.yaml up -d --build api.core` + +--- + +## License + +This project is licensed under the MIT License - see the [LICENSE.md](LICENSE.md) file for details. + +--- + +## Support + +- **Issues**: [GitHub Issues](https://github.com/yourusername/the-biergarten-app/issues) +- **Documentation**: [docs/](docs/) +- **Architecture**: See [Architecture Guide](docs/architecture.md) + +--- + +**Built with ❤️ for craft beer enthusiasts** diff --git a/docs/architecture.md b/docs/architecture.md new file mode 100644 index 0000000..3519b0f --- /dev/null +++ b/docs/architecture.md @@ -0,0 +1,404 @@ +# Architecture + +This document describes the architecture patterns and design decisions for The Biergarten App. + +## High-Level Overview + +The Biergarten App follows a **multi-project monorepo** architecture with clear separation between backend and frontend: + +- **Backend**: .NET 10 Web API with SQL Server +- **Frontend**: Next.js with TypeScript +- **Architecture Style**: Layered architecture with SQL-first approach + +## Diagrams + +For visual representations, see: +- [architecture.pdf](diagrams/pdf/architecture.pdf) - Layered architecture diagram +- [deployment.pdf](diagrams/pdf/deployment.pdf) - Docker deployment diagram +- [authentication-flow.pdf](diagrams/pdf/authentication-flow.pdf) - Authentication workflow +- [database-schema.pdf](diagrams/pdf/database-schema.pdf) - Database relationships + +Generate diagrams with: `make diagrams` + +## Backend Architecture + +### Layered Architecture Pattern + +The backend follows a strict layered architecture: + +``` +┌─────────────────────────────────────┐ +│ API Layer (Controllers) │ +│ - HTTP Endpoints │ +│ - Request/Response mapping │ +│ - Swagger/OpenAPI │ +└─────────────────────────────────────┘ + ↓ +┌─────────────────────────────────────┐ +│ Service Layer (Business Logic) │ +│ - Authentication logic │ +│ - User management │ +│ - Validation & orchestration │ +└─────────────────────────────────────┘ + ↓ +┌─────────────────────────────────────┐ +│ Infrastructure Layer (Tools) │ +│ - JWT token generation │ +│ - Password hashing (Argon2id) │ +│ - Email services │ +│ - Repository implementations │ +└─────────────────────────────────────┘ + ↓ +┌─────────────────────────────────────┐ +│ Domain Layer (Entities) │ +│ - UserAccount, UserCredential │ +│ - Pure POCO classes │ +│ - No external dependencies │ +└─────────────────────────────────────┘ + ↓ +┌─────────────────────────────────────┐ +│ Database (SQL Server) │ +│ - Stored procedures │ +│ - Tables & constraints │ +└─────────────────────────────────────┘ +``` + +### Layer Responsibilities + +#### API Layer (`API.Core`) + +**Purpose**: HTTP interface and request handling + +**Components**: +- Controllers (`AuthController`, `UserController`) +- Middleware for error handling +- Swagger/OpenAPI documentation +- Health check endpoints + +**Dependencies**: +- Service layer +- ASP.NET Core framework + +**Rules**: +- No business logic +- Only request/response transformation +- Delegates all work to Service layer + +#### Service Layer (`Service.Auth`, `Service.UserManagement`) + +**Purpose**: Business logic and orchestration + +**Components**: +- Authentication services (login, registration) +- User management services +- Business rule validation +- Transaction coordination + +**Dependencies**: +- Infrastructure layer (repositories, JWT, password hashing) +- Domain entities + +**Rules**: +- Contains all business logic +- Coordinates multiple infrastructure components +- No direct database access (uses repositories) +- Returns domain models, not DTOs + +#### Infrastructure Layer + +**Purpose**: Technical capabilities and external integrations + +**Components**: +- **Infrastructure.Repository**: Data access via stored procedures +- **Infrastructure.Jwt**: JWT token generation and validation +- **Infrastructure.PasswordHashing**: Argon2id password hashing +- **Infrastructure.Email**: Email sending capabilities +- **Infrastructure.Email.Templates**: Email template rendering + +**Dependencies**: +- Domain entities +- External libraries (ADO.NET, JWT, Argon2, etc.) + +**Rules**: +- Implements technical concerns +- No business logic +- Reusable across services + +#### Domain Layer (`Domain.Entities`) + +**Purpose**: Core business entities and models + +**Components**: +- `UserAccount` - User profile data +- `UserCredential` - Authentication credentials +- `UserVerification` - Account verification state + +**Dependencies**: +- None (pure domain) + +**Rules**: +- Plain Old CLR Objects (POCOs) +- No framework dependencies +- No infrastructure references +- Represents business concepts + +### Design Patterns + +#### Repository Pattern + +**Purpose**: Abstract database access behind interfaces + +**Implementation**: +- `IAuthRepository` - Authentication queries +- `IUserAccountRepository` - User account queries +- `DefaultSqlConnectionFactory` - Connection management + +**Benefits**: +- Testable (easy to mock) +- SQL-first approach (stored procedures) +- Centralized data access logic + +**Example**: +```csharp +public interface IAuthRepository +{ + Task GetUserCredentialAsync(string username); + Task CreateUserAccountAsync(UserAccount user, UserCredential credential); +} +``` + +#### Dependency Injection + +**Purpose**: Loose coupling and testability + +**Configuration**: `Program.cs` registers all services + +**Lifetimes**: +- Scoped: Repositories, Services (per request) +- Singleton: Connection factories, JWT configuration +- Transient: Utilities, helpers + +#### SQL-First Approach + +**Purpose**: Leverage database capabilities + +**Strategy**: +- All queries via stored procedures +- No ORM (Entity Framework not used) +- Database handles complex logic +- Application focuses on orchestration + +**Stored Procedure Examples**: +- `USP_RegisterUser` - User registration +- `USP_GetUserAccountByUsername` - User lookup +- `USP_RotateUserCredential` - Password update + +## Frontend Architecture + +### Next.js Application Structure + +``` +Website/src/ +├── components/ # React components +├── pages/ # Next.js routes +├── contexts/ # React context providers +├── hooks/ # Custom React hooks +├── controllers/ # Business logic layer +├── services/ # API communication +├── requests/ # API request builders +├── validation/ # Form validation schemas +├── config/ # Configuration & env vars +└── prisma/ # Database schema (current) +``` + +### Migration Strategy + +The frontend is **transitioning** from a standalone architecture to integrate with the .NET API: + +**Current State**: +- Uses Prisma ORM with Postgres (Neon) +- Has its own server-side API routes +- Direct database access from Next.js + +**Target State**: +- Pure client-side Next.js app +- All data via .NET API +- No server-side database access +- JWT-based authentication + +## Security Architecture + +### Authentication Flow + +1. **Registration**: + - User submits credentials + - Password hashed with Argon2id + - User account created + - JWT token issued + +2. **Login**: + - User submits credentials + - Password verified against hash + - JWT token issued + - Token stored client-side + +3. **API Requests**: + - Client sends JWT in Authorization header + - Middleware validates token + - Request proceeds if valid + +### Password Security + +**Algorithm**: Argon2id +- Memory: 64MB +- Iterations: 4 +- Parallelism: CPU core count +- Salt: 128-bit (16 bytes) +- Hash: 256-bit (32 bytes) + +**Rationale**: +- Argon2 winner of Password Hashing Competition (2015) +- Memory-hard (resistant to GPU/ASIC attacks) +- Configurable resource usage + +### JWT Tokens + +**Algorithm**: HS256 (HMAC-SHA256) + +**Claims**: +- `sub` - User ID +- `unique_name` - Username +- `jti` - Unique token ID +- `iat` - Issued at timestamp +- `exp` - Expiration timestamp + +**Configuration** (appsettings.json): +```json +{ + "Jwt": { + "ExpirationMinutes": 60, + "Issuer": "biergarten-api", + "Audience": "biergarten-users" + } +} +``` + +## Database Architecture + +### SQL-First Philosophy + +**Principles**: +1. Database is source of truth +2. Complex queries in stored procedures +3. Database handles referential integrity +4. Application orchestrates, database executes + +**Benefits**: +- Performance optimization via execution plans +- Centralized query logic +- Version-controlled schema (migrations) +- Easier query profiling and tuning + +### Migration Strategy + +**Tool**: DbUp + +**Process**: +1. Write SQL migration script +2. Embed in `Database.Migrations` project +3. Run migrations on startup +4. Idempotent and versioned + +**Migration Files**: +``` +scripts/ +├── 001-CreateUserTables.sql +├── 002-CreateLocationTables.sql +├── 003-CreateBreweryTables.sql +└── ... +``` + +### Data Seeding + +**Purpose**: Populate development/test databases + +**Implementation**: `Database.Seed` project + +**Seed Data**: +- Countries, states/provinces, cities +- Test user accounts +- Sample breweries (future) + +## Deployment Architecture + +### Docker Containerization + +**Container Structure**: +- `sqlserver` - SQL Server 2022 +- `database.migrations` - Schema migration runner +- `database.seed` - Data seeder +- `api.core` - ASP.NET Core Web API + +**Environments**: +- Development (`docker-compose.dev.yaml`) +- Testing (`docker-compose.test.yaml`) +- Production (`docker-compose.prod.yaml`) + +For details, see [Docker Guide](docker.md). + +### Health Checks + +**SQL Server**: Validates database connectivity +**API**: Checks service health and dependencies + +**Configuration**: +```yaml +healthcheck: + test: ["CMD-SHELL", "sqlcmd health check"] + interval: 10s + retries: 12 + start_period: 30s +``` + +## Testing Architecture + +### Test Pyramid + +``` + ┌──────────────┐ + │ Integration │ ← API.Specs (Reqnroll) + │ Tests │ + ├──────────────┤ + │ Unit Tests │ ← Service.Auth.Tests + │ (Service) │ Repository.Tests + ├──────────────┤ + │ Unit Tests │ + │ (Repository) │ + └──────────────┘ +``` + +**Strategy**: +- Many unit tests (fast, isolated) +- Fewer integration tests (slower, e2e) +- Mock external dependencies +- Test database for integration tests + +For details, see [Testing Guide](testing.md). + +## Future Enhancements + +### Planned Architecture Changes + +- [ ] **CQRS Pattern**: Separate read and write models +- [ ] **Event Sourcing**: Audit trail for critical operations +- [ ] **Caching Layer**: Redis for frequently accessed data +- [ ] **Message Queue**: Background job processing +- [ ] **API Gateway**: Centralized routing and auth +- [ ] **Microservices**: Break into bounded contexts + +### Scalability Considerations + +- **Horizontal Scaling**: Stateless API design allows multiple instances +- **Database Scaling**: Read replicas for query load +- **CDN**: Static asset delivery via CDN +- **Load Balancing**: Distribute traffic across API instances diff --git a/docs/database.md b/docs/database.md new file mode 100644 index 0000000..825c667 --- /dev/null +++ b/docs/database.md @@ -0,0 +1,364 @@ +# Database Schema + +This document describes the database schema, stored procedures, and data access patterns for The Biergarten App. + +## Overview + +The Biergarten App uses **SQL Server 2022** with a **SQL-first approach**: +- All queries via stored procedures +- No ORM (no Entity Framework) +- Database-driven validation and integrity +- Version-controlled migrations using DbUp + +See the [database-schema diagram](diagrams/pdf/database-schema.pdf) for visual representation. + +## Schema Overview + +The database is organized into several logical domains: + +### User Management + +Core user account and authentication tables. + +#### UserAccount +Primary user profile table. + +| Column | Type | Constraints | Description | +|--------|------|-------------|-------------| +| UserAccountId | INT | PK, IDENTITY | Primary key | +| Username | NVARCHAR(30) | UNIQUE, NOT NULL | Unique username | +| Email | NVARCHAR(255) | UNIQUE, NOT NULL | Email address | +| FirstName | NVARCHAR(50) | NOT NULL | First name | +| LastName | NVARCHAR(50) | NOT NULL | Last name | +| Bio | NVARCHAR(500) | NULL | User bio | +| CreatedAt | DATETIME2 | NOT NULL | Account creation timestamp | +| UpdatedAt | DATETIME2 | NOT NULL | Last update timestamp | +| LastLoginAt | DATETIME2 | NULL | Last successful login | + +**Indexes**: +- Unique index on `Username` +- Unique index on `Email` +- Index on `CreatedAt` for sorting + +#### UserCredential +Authentication credentials with password hashes. + +| Column | Type | Constraints | Description | +|--------|------|-------------|-------------| +| UserCredentialId | INT | PK, IDENTITY | Primary key | +| UserAccountId | INT | FK → UserAccount | User reference | +| PasswordHash | VARBINARY(32) | NOT NULL | Argon2id hash (256-bit) | +| PasswordSalt | VARBINARY(16) | NOT NULL | Random salt (128-bit) | +| IsActive | BIT | NOT NULL | Active credential flag | +| CredentialRotatedAt | DATETIME2 | NULL | Last password change | +| CredentialExpiresAt | DATETIME2 | NULL | Password expiration | +| CredentialRevokedAt | DATETIME2 | NULL | Revocation timestamp | +| CreatedAt | DATETIME2 | NOT NULL | Creation timestamp | + +**Notes**: +- Users can have multiple credentials (rotation support) +- Only one active credential per user +- Expired/revoked credentials retained for audit + +**Password Hashing**: +- Algorithm: Argon2id +- Memory: 64MB +- Iterations: 4 +- Parallelism: CPU core count +- Salt: Cryptographically random 128-bit +- Hash: 256-bit output + +#### UserVerification +Email verification and account activation. + +| Column | Type | Constraints | Description | +|--------|------|-------------|-------------| +| UserVerificationId | INT | PK, IDENTITY | Primary key | +| UserAccountId | INT | FK → UserAccount | User reference | +| IsVerified | BIT | NOT NULL, DEFAULT 0 | Verification status | +| VerifiedAt | DATETIME2 | NULL | Verification timestamp | +| VerificationToken | NVARCHAR(255) | NULL | Email token | +| TokenExpiresAt | DATETIME2 | NULL | Token expiration | + +#### UserAvatar +User profile pictures. + +| Column | Type | Constraints | Description | +|--------|------|-------------|-------------| +| UserAvatarId | INT | PK, IDENTITY | Primary key | +| UserAccountId | INT | FK → UserAccount | User reference | +| PhotoId | INT | FK → Photo | Image reference | +| IsActive | BIT | NOT NULL | Active avatar flag | +| CreatedAt | DATETIME2 | NOT NULL | Upload timestamp | + +#### UserFollow +Social following relationships. + +| Column | Type | Constraints | Description | +|--------|------|-------------|-------------| +| UserFollowId | INT | PK, IDENTITY | Primary key | +| FollowerUserId | INT | FK → UserAccount | User who follows | +| FollowedUserId | INT | FK → UserAccount | User being followed | +| CreatedAt | DATETIME2 | NOT NULL | Follow timestamp | + +**Constraints**: +- Unique constraint on (FollowerUserId, FollowedUserId) +- Check constraint: FollowerUserId ≠ FollowedUserId + +### Location Data + +Geographic reference data for breweries and users. + +#### Country +ISO 3166-1 country codes. + +| Column | Type | Constraints | Description | +|--------|------|-------------|-------------| +| CountryId | INT | PK, IDENTITY | Primary key | +| CountryCode | CHAR(2) | UNIQUE, NOT NULL | ISO 3166-1 alpha-2 | +| CountryName | NVARCHAR(100) | NOT NULL | Country name | + +#### StateProvince +States, provinces, and regions (ISO 3166-2). + +| Column | Type | Constraints | Description | +|--------|------|-------------|-------------| +| StateProvinceId | INT | PK, IDENTITY | Primary key | +| CountryId | INT | FK → Country | Country reference | +| StateProvinceCode | NVARCHAR(10) | NOT NULL | ISO 3166-2 code | +| StateProvinceName | NVARCHAR(100) | NOT NULL | State/province name | + +#### City +Cities and municipalities. + +| Column | Type | Constraints | Description | +|--------|------|-------------|-------------| +| CityId | INT | PK, IDENTITY | Primary key | +| StateProvinceId | INT | FK → StateProvince | State/province ref | +| CityName | NVARCHAR(100) | NOT NULL | City name | +| Latitude | DECIMAL(9,6) | NULL | Latitude coordinate | +| Longitude | DECIMAL(9,6) | NULL | Longitude coordinate | + +### Content + +Brewery and beer content tables. + +#### BreweryPost +Brewery information posts. + +| Column | Type | Constraints | Description | +|--------|------|-------------|-------------| +| BreweryPostId | INT | PK, IDENTITY | Primary key | +| UserAccountId | INT | FK → UserAccount | Author | +| BreweryName | NVARCHAR(200) | NOT NULL | Brewery name | +| Description | NVARCHAR(MAX) | NULL | Description | +| CreatedAt | DATETIME2 | NOT NULL | Creation timestamp | +| UpdatedAt | DATETIME2 | NOT NULL | Last update | + +#### BreweryPostLocation +Geospatial data for breweries. + +| Column | Type | Constraints | Description | +|--------|------|-------------|-------------| +| BreweryPostLocationId | INT | PK, IDENTITY | Primary key | +| BreweryPostId | INT | FK → BreweryPost | Brewery reference | +| CityId | INT | FK → City | City reference | +| Address | NVARCHAR(255) | NULL | Street address | +| Location | GEOGRAPHY | NULL | Spatial data | + +**Spatial Index**: On `Location` column for geospatial queries. + +#### BeerPost +Individual beer posts. + +| Column | Type | Constraints | Description | +|--------|------|-------------|-------------| +| BeerPostId | INT | PK, IDENTITY | Primary key | +| BreweryPostId | INT | FK → BreweryPost | Brewery ref | +| UserAccountId | INT | FK → UserAccount | Author | +| BeerName | NVARCHAR(200) | NOT NULL | Beer name | +| Description | NVARCHAR(MAX) | NULL | Description | +| ABV | DECIMAL(4,2) | NULL | Alcohol by volume | +| IBU | INT | NULL | International Bitterness Units | +| BeerStyleId | INT | FK → BeerStyle | Style category | +| CreatedAt | DATETIME2 | NOT NULL | Creation timestamp | + +#### Photo +Image metadata. + +| Column | Type | Constraints | Description | +|--------|------|-------------|-------------| +| PhotoId | INT | PK, IDENTITY | Primary key | +| Url | NVARCHAR(500) | NOT NULL | Image URL | +| CloudinaryPublicId | NVARCHAR(255) | NULL | Cloudinary ID | +| Width | INT | NULL | Image width | +| Height | INT | NULL | Image height | +| Format | NVARCHAR(10) | NULL | Image format | +| CreatedAt | DATETIME2 | NOT NULL | Upload timestamp | + +## Stored Procedures + +All data access is performed through stored procedures for: +- Performance optimization +- Centralized business logic +- Enhanced security +- Better query plan caching + +### User Management Procedures + +#### USP_RegisterUser +Creates a new user account with credentials. + +**Parameters**: +- `@Username` - Unique username +- `@Email` - Email address +- `@FirstName` - First name +- `@LastName` - Last name +- `@PasswordHash` - Argon2id hash +- `@PasswordSalt` - Random salt + +**Returns**: `UserAccountId` of created user + +**Logic**: +1. Validates username and email uniqueness +2. Creates UserAccount record +3. Creates UserCredential record +4. Creates UserVerification record (unverified) +5. Returns user ID + +#### USP_GetUserAccountByUsername +Retrieves user account and credential by username. + +**Parameters**: +- `@Username` - Username to look up + +**Returns**: User account details + active credential + +#### USP_RotateUserCredential +Updates user password (credential rotation). + +**Parameters**: +- `@UserAccountId` - User ID +- `@NewPasswordHash` - New password hash +- `@NewPasswordSalt` - New salt + +**Logic**: +1. Deactivates current credential +2. Creates new active credential +3. Updates CredentialRotatedAt timestamp + +### Location Procedures + +#### USP_CreateCountry +Inserts a new country or retrieves existing. + +#### USP_CreateStateProvince +Inserts a state/province or retrieves existing. + +#### USP_CreateCity +Inserts a city or retrieves existing. + +## Data Seeding + +The `Database.Seed` project populates development/test databases with: + +### Location Data +- 3 countries (US, Canada, Mexico) +- 50 US states + DC +- 13 Canadian provinces/territories +- 32 Mexican states +- Major cities for each region + +### Test Users +- Admin account +- Sample user accounts with verified emails + +### Sample Data (Future) +- Breweries +- Beer styles +- Sample posts + +## Migrations + +### DbUp Migration Strategy + +**Migration Files**: `src/Core/Database/Database.Migrations/scripts/` + +**Naming Convention**: `###-DescriptiveName.sql` +- `001-CreateUserTables.sql` +- `002-CreateLocationTables.sql` +- `003-CreateContentTables.sql` + +**Execution**: +- Migrations run on container startup +- Tracked in `SchemaVersions` table +- Idempotent (safe to run multiple times) +- Sequential execution order + +**Development Mode**: +Set `CLEAR_DATABASE=true` to drop and recreate (dev/test only). + +### Adding New Migrations + +1. Create new SQL file in `scripts/` folder +2. Use next sequential number +3. Write idempotent SQL +4. Test locally before committing +5. Rebuild migration container + +**Example Migration**: +```sql +-- 004-AddUserBio.sql +IF NOT EXISTS ( + SELECT * FROM sys.columns + WHERE object_id = OBJECT_ID('UserAccount') + AND name = 'Bio' +) +BEGIN + ALTER TABLE UserAccount + ADD Bio NVARCHAR(500) NULL; +END +GO +``` + +## Query Performance + +### Indexes + +Strategic indexes for common queries: +- Username lookup (unique index) +- Email lookup (unique index) +- User followers (composite index) +- Brewery location (spatial index) +- Created date sorting + +###Execution Plans + +Stored procedures allow SQL Server to: +- Cache execution plans +- Optimize query performance +- Reduce compilation overhead +- Enable query hints when needed + +## Backup & Recovery + +### Development +No backup strategy (fresh database from migrations). + +### Production (Future) +- Automated daily backups +- Point-in-time recovery +- Geo-redundant storage +- Backup retention policy + +## Monitoring + +### Queries to Monitor +- User login frequency +- Slow-running procedures +- Lock contention +- Index fragmentation + +### Tools +- SQL Server Management Studio (SSMS) +- JetBrains Rider diff --git a/docs/diagrams/architecture.puml b/docs/diagrams/architecture.puml new file mode 100644 index 0000000..ebb79db --- /dev/null +++ b/docs/diagrams/architecture.puml @@ -0,0 +1,75 @@ +@startuml architecture +!theme plain +skinparam backgroundColor #FFFFFF +skinparam defaultFontName Arial +skinparam packageStyle rectangle + +title The Biergarten App - Layered Architecture + +package "API Layer" #E3F2FD { + [API.Core\nASP.NET Core Web API] as API + note right of API + - Controllers (Auth, User) + - Swagger/OpenAPI + - Middleware + - Health Checks + end note +} + +package "Service Layer" #F3E5F5 { + [Service.Auth] as AuthSvc + [Service.UserManagement] as UserSvc + note right of AuthSvc + - Business Logic + - Validation + - Orchestration + end note +} + +package "Infrastructure Layer" #FFF3E0 { + [Infrastructure.Repository] as Repo + [Infrastructure.Jwt] as JWT + [Infrastructure.PasswordHashing] as PwdHash + [Infrastructure.Email] as Email +} + +package "Domain Layer" #E8F5E9 { + [Domain.Entities] as Domain + note right of Domain + - UserAccount + - UserCredential + - UserVerification + end note +} + +database "SQL Server" { + [Stored Procedures] as SP + [Tables] as Tables +} + +' Relationships +API --> AuthSvc +API --> UserSvc + +AuthSvc --> Repo +AuthSvc --> JWT +AuthSvc --> PwdHash +AuthSvc --> Email + +UserSvc --> Repo + +Repo --> SP +Repo --> Domain +SP --> Tables + +AuthSvc --> Domain +UserSvc --> Domain + +' Notes +note left of Repo + SQL-first approach + All queries via + stored procedures +end note + +@enduml diff --git a/docs/diagrams/authentication-flow.puml b/docs/diagrams/authentication-flow.puml new file mode 100644 index 0000000..17f908c --- /dev/null +++ b/docs/diagrams/authentication-flow.puml @@ -0,0 +1,72 @@ +@startuml authentication-flow +!theme plain +skinparam backgroundColor #FFFFFF +skinparam defaultFontName Arial + +title User Authentication Flow + +actor User +participant "API\nController" as API +participant "Service.Auth" as AuthSvc +participant "Password\nHasher" as PwdHash +participant "Repository" as Repo +participant "JWT\nProvider" as JWT +database "SQL Server\nStored Procedures" as DB + +== Registration == +User -> API: POST /api/auth/register +activate API +API -> AuthSvc: RegisterAsync(username, email, password) +activate AuthSvc +AuthSvc -> AuthSvc: Validate input +AuthSvc -> PwdHash: HashPassword(password) +activate PwdHash +PwdHash -> PwdHash: Argon2id\n(64MB, 4 iterations) +return hash + salt +AuthSvc -> Repo: CreateUserWithCredential(user, hash) +activate Repo +Repo -> DB: EXEC USP_RegisterUser +activate DB +DB -> DB: Create UserAccount\nCreate UserCredential\nCreate UserVerification +return userId +return userId +AuthSvc -> JWT: GenerateToken(userId, username) +activate JWT +JWT -> JWT: HS256 signing\nInclude claims +return JWT token +return RegisterResult{token, userId} +API -> User: 201 Created + JWT +deactivate API + +== Login == +User -> API: POST /api/auth/login +activate API +API -> AuthSvc: LoginAsync(username, password) +activate AuthSvc +AuthSvc -> Repo: GetUserCredential(username) +activate Repo +Repo -> DB: EXEC USP_GetUserAccountByUsername +activate DB +return user + credential +return UserCredential +AuthSvc -> PwdHash: VerifyPassword(password, hash, salt) +activate PwdHash +PwdHash -> PwdHash: Argon2id verify +return isValid +alt Password Valid + AuthSvc -> JWT: GenerateToken(userId, username) + activate JWT + return JWT token + AuthSvc -> Repo: UpdateLastLogin(userId) + activate Repo + Repo -> DB: Update LastLogin + return + return LoginResult{token, userId} + API -> User: 200 OK + JWT +else Invalid Credentials + return AuthenticationException + API -> User: 401 Unauthorized +end +deactivate API + +@enduml diff --git a/docs/diagrams/database-schema.puml b/docs/diagrams/database-schema.puml new file mode 100644 index 0000000..9c75257 --- /dev/null +++ b/docs/diagrams/database-schema.puml @@ -0,0 +1,104 @@ +@startuml database-schema +!theme plain +skinparam backgroundColor #FFFFFF +skinparam defaultFontName Arial +skinparam linetype ortho + +title Key Database Schema - User & Authentication + +entity "UserAccount" as User { + * UserAccountId: INT <> + -- + * Username: NVARCHAR(30) <> + * Email: NVARCHAR(255) <> + * FirstName: NVARCHAR(50) + * LastName: NVARCHAR(50) + Bio: NVARCHAR(500) + CreatedAt: DATETIME2 + UpdatedAt: DATETIME2 + LastLoginAt: DATETIME2 +} + +entity "UserCredential" as Cred { + * UserCredentialId: INT <> + -- + * UserAccountId: INT <> + * PasswordHash: VARBINARY(32) + * PasswordSalt: VARBINARY(16) + CredentialRotatedAt: DATETIME2 + CredentialExpiresAt: DATETIME2 + CredentialRevokedAt: DATETIME2 + * IsActive: BIT + CreatedAt: DATETIME2 +} + +entity "UserVerification" as Verify { + * UserVerificationId: INT <> + -- + * UserAccountId: INT <> + * IsVerified: BIT + VerifiedAt: DATETIME2 + VerificationToken: NVARCHAR(255) + TokenExpiresAt: DATETIME2 +} + +entity "UserAvatar" as Avatar { + * UserAvatarId: INT <> + -- + * UserAccountId: INT <> + PhotoId: INT <> + * IsActive: BIT + CreatedAt: DATETIME2 +} + +entity "UserFollow" as Follow { + * UserFollowId: INT <> + -- + * FollowerUserId: INT <> + * FollowedUserId: INT <> + CreatedAt: DATETIME2 +} + +entity "Photo" as Photo { + * PhotoId: INT <> + -- + * Url: NVARCHAR(500) + * CloudinaryPublicId: NVARCHAR(255) + Width: INT + Height: INT + Format: NVARCHAR(10) + CreatedAt: DATETIME2 +} + +' Relationships +User ||--o{ Cred : "has" +User ||--o| Verify : "has" +User ||--o{ Avatar : "has" +User ||--o{ Follow : "follows" +User ||--o{ Follow : "followed by" +Avatar }o--|| Photo : "refers to" + +note right of Cred + Password hashing: + - Algorithm: Argon2id + - Memory: 64MB + - Iterations: 4 + - Salt: 128-bit + - Hash: 256-bit +end note + +note right of Verify + Account verification + via email token + with expiry +end note + +note bottom of User + Core stored procedures: + - USP_RegisterUser + - USP_GetUserAccountByUsername + - USP_RotateUserCredential + - USP_UpdateUserAccount +end note + +@enduml diff --git a/docs/diagrams/deployment.puml b/docs/diagrams/deployment.puml new file mode 100644 index 0000000..e178b69 --- /dev/null +++ b/docs/diagrams/deployment.puml @@ -0,0 +1,94 @@ +@startuml deployment +!theme plain +skinparam backgroundColor #FFFFFF +skinparam defaultFontName Arial + +title Docker Deployment Architecture + + + + package "Development Environment\n(docker-compose.dev.yaml)" #E3F2FD { + node "SQL Server" as DevDB { + database "Biergarten\nDatabase" + } + + node "API Container" as DevAPI { + component "API.Core" as API1 + component "Port: 8080" + } + + node "Migrations" as DevMig { + component "Database.Migrations" + } + + node "Seed" as DevSeed { + component "Database.Seed" + } + } + + package "Test Environment\n(docker-compose.test.yaml)" #FFF3E0 { + node "SQL Server" as TestDB { + database "Test\nDatabase" + } + + node "Migrations" as TestMig { + component "Database.Migrations" + } + + node "Seed" as TestSeed { + component "Database.Seed" + } + + node "API.Specs" as Specs { + component "Reqnroll\nIntegration Tests" + } + + node "Repository Tests" as RepoTests { + component "xUnit\nData Access Tests" + } + + node "Service Tests" as SvcTests { + component "xUnit\nAuth Service Tests" + } + } + + folder "test-results/" as Results { + file "api-specs/results.trx" + file "repository-tests/results.trx" + file "service-auth-tests/results.trx" + } + + +' Development flow +DevMig --> DevDB : migrate +DevSeed --> DevDB : seed +DevAPI --> DevDB : connect + +' Test flow +TestMig --> TestDB : migrate +TestSeed --> TestDB : seed +Specs --> TestDB : test +RepoTests --> TestDB : test +SvcTests ..> TestDB : mock + +Specs --> Results : export +RepoTests --> Results : export +SvcTests --> Results : export + +' Notes +note right of DevAPI + Swagger UI: + http://localhost:8080/swagger +end note + +note right of Results + Test results + exported to host +end note + +note bottom of TestDB + Isolated environment + Fresh database each run +end note + +@enduml diff --git a/docs/docker.md b/docs/docker.md new file mode 100644 index 0000000..480f113 --- /dev/null +++ b/docs/docker.md @@ -0,0 +1,510 @@ +# Docker Guide + +This document covers Docker deployment, configuration, and troubleshooting for The Biergarten App. + +## Overview + +The project uses Docker Compose to orchestrate multiple services: +- SQL Server 2022 database +- Database migrations runner (DbUp) +- Database seeder +- .NET API +- Test runners + +See the [deployment diagram](diagrams/pdf/deployment.pdf) for visual representation. + +## Docker Compose Environments + +### 1. Development (`docker-compose.dev.yaml`) + +**Purpose**: Local development with persistent data + +**Features**: +- Persistent SQL Server volume +- Hot reload support +- Swagger UI enabled +- Seed data included +- `CLEAR_DATABASE=true` (drops and recreates schema) + +**Services**: +```yaml +sqlserver # SQL Server 2022 (port 1433) +database.migrations # DbUp migrations +database.seed # Seed initial data +api.core # Web API (ports 8080, 8081) +``` + +**Start Development Environment**: +```bash +docker compose -f docker-compose.dev.yaml up -d +``` + +**Access**: +- API Swagger: http://localhost:8080/swagger +- Health Check: http://localhost:8080/health +- SQL Server: localhost:1433 (sa credentials from .env.dev) + +**Stop Environment**: +```bash +# Stop services (keep volumes) +docker compose -f docker-compose.dev.yaml down + +# Stop and remove volumes (fresh start) +docker compose -f docker-compose.dev.yaml down -v +``` + +### 2. Testing (`docker-compose.test.yaml`) + +**Purpose**: Automated CI/CD testing in isolated environment + +**Features**: +- Fresh database each run +- All test suites execute in parallel +- Test results exported to `./test-results/` +- Containers auto-exit after completion +- Fully isolated testnet network + +**Services**: +```yaml +sqlserver # Test database +database.migrations # Fresh schema +database.seed # Test data +api.specs # Reqnroll BDD tests +repository.tests # Repository unit tests +service.auth.tests # Service unit tests +``` + +**Run Tests**: +```bash +# Run all tests +docker compose -f docker-compose.test.yaml up --abort-on-container-exit + +# View results +ls -la test-results/ +cat test-results/api-specs/results.trx +cat test-results/repository-tests/results.trx +cat test-results/service-auth-tests/results.trx + +# Clean up +docker compose -f docker-compose.test.yaml down -v +``` + +### 3. Production (`docker-compose.prod.yaml`) + +**Purpose**: Production-ready deployment + +**Features**: +- Production logging levels +- No database clearing +- Optimized build configurations +- Health checks enabled +- Restart policies (unless-stopped) +- Security hardening + +**Services**: +```yaml +sqlserver # Production SQL Server +database.migrations # Schema updates only +api.core # Production API +``` + +**Deploy Production**: +```bash +docker compose -f docker-compose.prod.yaml up -d +``` + +**Note**: In real production, use orchestration platforms (Kubernetes, ECS, etc.). + +## Service Dependencies + +Docker Compose manages startup order using health checks: + +```mermaid +sqlserver (health check) + ↓ +database.migrations (completes successfully) + ↓ +database.seed (completes successfully) + ↓ +api.core / tests (start when ready) +``` + +**Health Check Example** (SQL Server): +```yaml +healthcheck: + test: ["CMD-SHELL", "sqlcmd -S localhost -U sa -P '${DB_PASSWORD}' -C -Q 'SELECT 1'"] + interval: 10s + timeout: 5s + retries: 12 + start_period: 30s +``` + +**Dependency Configuration**: +```yaml +api.core: + depends_on: + database.seed: + condition: service_completed_successfully +``` + +## Volumes + +### Persistent Volumes + +**Development**: +- `sqlserverdata-dev` - Database files persist between restarts +- `nuget-cache-dev` - NuGet package cache (speeds up builds) + +**Testing**: +- `sqlserverdata-test` - Temporary, typically removed after tests + +**Production**: +- `sqlserverdata-prod` - Production database files +- `nuget-cache-prod` - Production NuGet cache + +### Mounted Volumes + +**Test Results**: +```yaml +volumes: + - ./test-results:/app/test-results +``` +Test results are written to host filesystem for CI/CD integration. + +**Code Volumes** (development only): +```yaml +volumes: + - ./src:/app/src # Hot reload for development +``` + +## Networks + +Each environment uses isolated bridge networks: + +- `devnet` - Development network +- `testnet` - Testing network (fully isolated) +- `prodnet` - Production network + +This prevents cross-environment communication and improves security. + +## Environment Variables + +All containers are configured via environment variables from `.env` files: + +```yaml +env_file: ".env.dev" # or .env.test, .env.prod + +environment: + ASPNETCORE_ENVIRONMENT: "Development" + DOTNET_RUNNING_IN_CONTAINER: "true" + DB_SERVER: "${DB_SERVER}" + DB_NAME: "${DB_NAME}" + DB_USER: "${DB_USER}" + DB_PASSWORD: "${DB_PASSWORD}" + JWT_SECRET: "${JWT_SECRET}" +``` + +For complete list, see [Environment Variables](environment-variables.md). + +## Common Commands + +### View Services + +```bash +# Running services +docker compose -f docker-compose.dev.yaml ps + +# All containers (including stopped) +docker ps -a +``` + +### View Logs + +```bash +# All services +docker compose -f docker-compose.dev.yaml logs -f + +# Specific service +docker compose -f docker-compose.dev.yaml logs -f api.core + +# Last 100 lines +docker compose -f docker-compose.dev.yaml logs --tail=100 api.core +``` + +### Execute Commands in Container + +```bash +# Interactive shell +docker exec -it dev-env-api-core bash + +# Run command +docker exec dev-env-sqlserver /opt/mssql-tools18/bin/sqlcmd -S localhost -U sa -P 'password' -C +``` + +### Restart Services + +```bash +# Restart all services +docker compose -f docker-compose.dev.yaml restart + +# Restart specific service +docker compose -f docker-compose.dev.yaml restart api.core + +# Rebuild and restart +docker compose -f docker-compose.dev.yaml up -d --build api.core +``` + +### Build Images + +```bash +# Build all images +docker compose -f docker-compose.dev.yaml build + +# Build specific service +docker compose -f docker-compose.dev.yaml build api.core + +# Build without cache +docker compose -f docker-compose.dev.yaml build --no-cache +``` + +### Clean Up + +```bash +# Stop and remove containers +docker compose -f docker-compose.dev.yaml down + +# Remove containers and volumes +docker compose -f docker-compose.dev.yaml down -v + +# Remove containers, volumes, and images +docker compose -f docker-compose.dev.yaml down -v --rmi all + +# System-wide cleanup +docker system prune -af --volumes +``` + +## Dockerfile Structure + +### Multi-Stage Build + +All service Dockerfiles use multi-stage builds: + +```dockerfile +# Stage 1: Build +FROM mcr.microsoft.com/dotnet/sdk:10.0 AS build +WORKDIR /src +COPY ["Project/Project.csproj", "Project/"] +RUN dotnet restore +COPY . . +RUN dotnet build -c Release + +# Stage 2: Runtime +FROM mcr.microsoft.com/dotnet/aspnet:10.0 AS final +WORKDIR /app +COPY --from=build /app/build . +ENTRYPOINT ["dotnet", "Project.dll"] +``` + +**Benefits**: +- Smaller final images (no SDK) +- Cached layers speed up builds +- Separation of build and runtime dependencies + +## Troubleshooting + +### Port Already in Use + +**Problem**: Port 8080 or 1433 already bound + +**Solution**: +```bash +# Find process using port +lsof -ti:8080 +lsof -ti:1433 + +# Kill process +kill -9 $(lsof -ti:8080) + +# Or change port in docker-compose.yaml +ports: + - "8081:8080" # Map to different host port +``` + +### Container Won't Start + +**Problem**: Container exits immediately + +**Solution**: +```bash +# View logs +docker compose -f docker-compose.dev.yaml logs + +# Check container status +docker compose -f docker-compose.dev.yaml ps + +# Inspect container +docker inspect +``` + +### Database Connection Failed + +**Problem**: API can't connect to SQL Server + +**Check**: +1. SQL Server container is running: + ```bash + docker compose -f docker-compose.dev.yaml ps sqlserver + ``` + +2. Health check is passing: + ```bash + docker inspect dev-env-sqlserver | grep -A 10 Health + ``` + +3. Connection string is correct in `.env` file + +4. SQL Server is accepting connections: + ```bash + docker exec dev-env-sqlserver /opt/mssql-tools18/bin/sqlcmd -S localhost -U sa -P 'password' -C -Q "SELECT 1" + ``` + +### Out of Disk Space + +**Problem**: No space left on device + +**Solution**: +```bash +# Check Docker disk usage +docker system df + +# Remove unused data +docker system prune -af --volumes + +# Remove specific volumes +docker volume ls +docker volume rm +``` + +### SQL Server Container Unhealthy + +**Problem**: Health check failing + +**Reasons**: +- Incorrect password +- Container still starting up +- Insufficient memory + +**Solution**: +```bash +# Check logs +docker compose -f docker-compose.dev.yaml logs sqlserver + +# Verify password matches .env file +grep DB_PASSWORD .env.dev + +# Increase memory in Docker Desktop settings (min 4GB recommended) +``` + +### Build Failures + +**Problem**: Docker build fails + +**Common Causes**: +- Missing project references +- Incorrect COPY paths +- Network issues during restore + +**Solution**: +```bash +# Build without cache +docker compose -f docker-compose.dev.yaml build --no-cache + +# Check Dockerfile COPY paths match project structure +# Ensure all .csproj files are in correct locations +``` + +### Test Results Not Appearing + +**Problem**: `test-results/` folder is empty + +**Solution**: +```bash +# Ensure folder has write permissions +chmod -R 755 test-results/ + +# Check test container logs +docker compose -f docker-compose.test.yaml logs api.specs + +# Verify volume mount +docker compose -f docker-compose.test.yaml config | grep -A 5 volumes +``` + +## Performance Optimization + +### Build Cache + +Leverage Docker layer caching: +1. Copy `.csproj` files first +2. Run `dotnet restore` +3. Copy source code +4. Build application + +This allows dependency layer caching when only source changes. + +### Volume Mounts for Development + +Use NuGet cache volume for faster rebuilds: +```yaml +volumes: + - nuget-cache-dev:/root/.nuget/packages +``` + +### Resource Limits + +Set memory/CPU limits for containers: +```yaml +deploy: + resources: + limits: + memory: 2GB + cpus: '1.0' +``` + +## CI/CD Integration + +### GitHub Actions Example + +```yaml +name: Run Tests +on: [push] +jobs: + test: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - name: Run tests + run: | + docker compose -f docker-compose.test.yaml build + docker compose -f docker-compose.test.yaml up --abort-on-container-exit + - name: Upload test results + uses: actions/upload-artifact@v3 + with: + name: test-results + path: test-results/ +``` + +## Security Best Practices + +1. **Don't commit `.env` files** - Use `.env.example` as template +2. **Use secrets management** - For production credentials +3. **Run as non-root user** - Configure USER in Dockerfile +4. **Scan images** - Use `docker scan` or Trivy +5. **Keep base images updated** - Regularly update FROM images +6. **Minimize installed packages** - Only install what's needed +7. **Use specific tags** - Avoid `latest` tag + +## Additional Resources + +- [Docker Compose Documentation](https://docs.docker.com/compose/) +- [.NET Docker Images](https://hub.docker.com/_/microsoft-dotnet) +- [SQL Server Docker Images](https://hub.docker.com/_/microsoft-mssql-server) diff --git a/docs/environment-variables.md b/docs/environment-variables.md new file mode 100644 index 0000000..56d739a --- /dev/null +++ b/docs/environment-variables.md @@ -0,0 +1,408 @@ +# Environment Variables + +Complete documentation for all environment variables used in The Biergarten App. + +## Overview + +The application uses environment variables for configuration across: +- **.NET API Backend** - Database connections, JWT secrets +- **Next.js Frontend** - External services, authentication +- **Docker Containers** - Runtime configuration + +## Configuration Patterns + +### Backend (.NET API) +Direct environment variable access via `Environment.GetEnvironmentVariable()`. + +### Frontend (Next.js) +Centralized configuration module at `src/Website/src/config/env/index.ts` with Zod validation. + +### Docker +Environment-specific `.env` files loaded via `env_file:` in docker-compose.yaml: +- `.env.dev` - Development +- `.env.test` - Testing +- `.env.prod` - Production + +## Backend Variables (.NET API) + +### Database Connection + +**Option 1: Component-Based (Recommended for Docker)** + +Build connection string from individual components: + +```bash +DB_SERVER=sqlserver,1433 # SQL Server host and port +DB_NAME=Biergarten # Database name +DB_USER=sa # SQL Server username +DB_PASSWORD=YourStrong!Passw0rd # SQL Server password +DB_TRUST_SERVER_CERTIFICATE=True # Optional, defaults to True +``` + +**Option 2: Full Connection String (Local Development)** + +Provide complete connection string: + +```bash +DB_CONNECTION_STRING="Server=localhost,1433;Database=Biergarten;User Id=sa;Password=YourStrong!Passw0rd;TrustServerCertificate=True;" +``` + +**Priority**: `DB_CONNECTION_STRING` is checked first. If not found, connection string is built from components. + +**Implementation**: See `DefaultSqlConnectionFactory.cs` + +### JWT Authentication + +```bash +JWT_SECRET=your-secret-key-minimum-32-characters-required +``` + +- **Required**: Yes +- **Minimum Length**: 32 characters (enforced) +- **Purpose**: Signs JWT tokens for user authentication +- **Algorithm**: HS256 (HMAC-SHA256) + +**Generate Secret**: +```bash +# macOS/Linux +openssl rand -base64 127 + +# Windows PowerShell +[Convert]::ToBase64String((1..127 | %{Get-Random -Max 256})) +``` + +**Additional JWT Settings** (appsettings.json): +```json +{ + "Jwt": { + "ExpirationMinutes": 60, + "Issuer": "biergarten-api", + "Audience": "biergarten-users" + } +} +``` + +### Migration Control + +```bash +CLEAR_DATABASE=true +``` + +- **Required**: No +- **Default**: false +- **Effect**: If "true", drops and recreates database during migrations +- **Usage**: Development and testing environments ONLY +- **Warning**: NEVER use in production + +### ASP.NET Core Configuration + +```bash +ASPNETCORE_ENVIRONMENT=Development # Development, Production, Staging +ASPNETCORE_URLS=http://0.0.0.0:8080 # Binding address and port +DOTNET_RUNNING_IN_CONTAINER=true # Flag for container execution +``` + +## Frontend Variables (Next.js) + +Create `.env.local` in the `Website/` directory. + +### Base Configuration + +```bash +BASE_URL=http://localhost:3000 # Application base URL +NODE_ENV=development # Environment: development, production, test +``` + +### Authentication & Sessions + +```bash +# Token signing secrets (use openssl rand -base64 127) +CONFIRMATION_TOKEN_SECRET= # Email confirmation tokens +RESET_PASSWORD_TOKEN_SECRET= # Password reset tokens +SESSION_SECRET= # Session cookie signing + +# Session configuration +SESSION_TOKEN_NAME=biergarten # Cookie name (optional) +SESSION_MAX_AGE=604800 # Cookie max age in seconds (optional, default: 1 week) +``` + +**Security Requirements**: +- All secrets should be 127+ characters +- Generate using cryptographically secure random functions +- Never reuse secrets across environments +- Rotate secrets periodically in production + +### Database (Current - Prisma/Postgres) + +**Note**: Frontend currently uses Neon Postgres. Will migrate to .NET API. + +```bash +POSTGRES_PRISMA_URL=postgresql://user:pass@host/db?pgbouncer=true # Pooled connection +POSTGRES_URL_NON_POOLING=postgresql://user:pass@host/db # Direct connection (migrations) +SHADOW_DATABASE_URL=postgresql://user:pass@host/shadow_db # Prisma shadow DB (optional) +``` + +### External Services + +#### Cloudinary (Image Hosting) + +```bash +NEXT_PUBLIC_CLOUDINARY_CLOUD_NAME=your-cloud-name # Public, client-accessible +CLOUDINARY_KEY=your-api-key # Server-side API key +CLOUDINARY_SECRET=your-api-secret # Server-side secret +``` + +**Setup Steps**: +1. Sign up at [cloudinary.com](https://cloudinary.com) +2. Navigate to Dashboard +3. Copy Cloud Name, API Key, and API Secret + +**Note**: `NEXT_PUBLIC_` prefix makes variable accessible in client-side code. + +#### Mapbox (Maps & Geocoding) + +```bash +MAPBOX_ACCESS_TOKEN=pk.your-public-token +``` + +**Setup Steps**: +1. Create account at [mapbox.com](https://mapbox.com) +2. Navigate to Account → Tokens +3. Create new token with public scopes +4. Copy access token + +#### SparkPost (Email Service) + +```bash +SPARKPOST_API_KEY=your-api-key +SPARKPOST_SENDER_ADDRESS=noreply@yourdomain.com +``` + +**Setup Steps**: +1. Sign up at [sparkpost.com](https://sparkpost.com) +2. Verify sending domain or use sandbox +3. Create API key with "Send via SMTP" permission +4. Configure sender address (must match verified domain) + +### Admin Account (Seeding) + +```bash +ADMIN_PASSWORD=SecureAdminPassword123! # Initial admin password for seeding +``` + +- **Required**: No (only needed for seeding) +- **Purpose**: Sets admin account password during database seeding +- **Security**: Use strong password, change immediately in production + +## Docker-Specific Variables + +### SQL Server Container + +```bash +SA_PASSWORD=YourStrong!Passw0rd # SQL Server SA password +ACCEPT_EULA=Y # Accept SQL Server EULA (required) +MSSQL_PID=Express # SQL Server edition (Express, Developer, Enterprise) +``` + +**Password Requirements**: +- Minimum 8 characters +- Uppercase, lowercase, digits, and special characters +- Maps to `DB_PASSWORD` for application containers + +## Environment File Structure + +### Backend/Docker (Root Directory) + +``` +.env.example # Template (tracked in Git) +.env.dev # Development config (gitignored) +.env.test # Testing config (gitignored) +.env.prod # Production config (gitignored) +``` + +**Setup**: +```bash +cp .env.example .env.dev +# Edit .env.dev with your values +``` + +**Docker Compose Mapping**: +- `docker-compose.dev.yaml` → `.env.dev` +- `docker-compose.test.yaml` → `.env.test` +- `docker-compose.prod.yaml` → `.env.prod` + +### Frontend (Website Directory) + +``` +.env.local # Local development (gitignored) +.env.production # Production (gitignored) +``` + +**Setup**: +```bash +cd Website +touch .env.local +# Add frontend variables +``` + +## Variable Reference Table + +| Variable | Backend | Frontend | Docker | Required | Notes | +|----------|:-------:|:--------:|:------:|:--------:|-------| +| **Database** | +| `DB_SERVER` | ✓ | | ✓ | Yes* | SQL Server address | +| `DB_NAME` | ✓ | | ✓ | Yes* | Database name | +| `DB_USER` | ✓ | | ✓ | Yes* | SQL username | +| `DB_PASSWORD` | ✓ | | ✓ | Yes* | SQL password | +| `DB_CONNECTION_STRING` | ✓ | | | Yes* | Alternative to components | +| `DB_TRUST_SERVER_CERTIFICATE` | ✓ | | ✓ | No | Defaults to True | +| `SA_PASSWORD` | | | ✓ | Yes | SQL Server container | +| **Authentication (Backend)** | +| `JWT_SECRET` | ✓ | | ✓ | Yes | Min 32 chars | +| **Authentication (Frontend)** | +| `CONFIRMATION_TOKEN_SECRET` | | ✓ | | Yes | Email confirmation | +| `RESET_PASSWORD_TOKEN_SECRET` | | ✓ | | Yes | Password reset | +| `SESSION_SECRET` | | ✓ | | Yes | Session signing | +| `SESSION_TOKEN_NAME` | | ✓ | | No | Default: "biergarten" | +| `SESSION_MAX_AGE` | | ✓ | | No | Default: 604800 | +| **Base Configuration** | +| `BASE_URL` | | ✓ | | Yes | App base URL | +| `NODE_ENV` | | ✓ | | Yes | Node environment | +| `ASPNETCORE_ENVIRONMENT` | ✓ | | ✓ | Yes | ASP.NET environment | +| `ASPNETCORE_URLS` | ✓ | | ✓ | Yes | API binding address | +| **Database (Frontend - Current)** | +| `POSTGRES_PRISMA_URL` | | ✓ | | Yes | Pooled connection | +| `POSTGRES_URL_NON_POOLING` | | ✓ | | Yes | Direct connection | +| `SHADOW_DATABASE_URL` | | ✓ | | No | Prisma shadow DB | +| **External Services** | +| `NEXT_PUBLIC_CLOUDINARY_CLOUD_NAME` | | ✓ | | Yes | Public, client-side | +| `CLOUDINARY_KEY` | | ✓ | | Yes | Server-side | +| `CLOUDINARY_SECRET` | | ✓ | | Yes | Server-side | +| `MAPBOX_ACCESS_TOKEN` | | ✓ | | Yes | Maps/geocoding | +| `SPARKPOST_API_KEY` | | ✓ | | Yes | Email service | +| `SPARKPOST_SENDER_ADDRESS` | | ✓ | | Yes | From address | +| **Other** | +| `ADMIN_PASSWORD` | | ✓ | | No | Seeding only | +| `CLEAR_DATABASE` | ✓ | | ✓ | No | Dev/test only | +| `ACCEPT_EULA` | | | ✓ | Yes | SQL Server EULA | +| `MSSQL_PID` | | | ✓ | No | SQL Server edition | +| `DOTNET_RUNNING_IN_CONTAINER` | ✓ | | ✓ | No | Container flag | + +\* Either `DB_CONNECTION_STRING` OR the component variables (`DB_SERVER`, `DB_NAME`, `DB_USER`, `DB_PASSWORD`) must be provided. + +## Validation + +### Backend Validation + +Variables are validated at startup: +- Missing required variables cause application to fail +- JWT_SECRET length is enforced (min 32 chars) +- Connection string format is validated + +### Frontend Validation + +Zod schemas validate variables at runtime: +- Type checking (string, number, URL, etc.) +- Format validation (email, URL patterns) +- Required vs optional enforcement + +**Location**: `src/Website/src/config/env/index.ts` + +## Security Best Practices + +1. **Never commit `.env` files** - Add to `.gitignore` +2. **Use `.env.example` as template** - Track in Git without sensitive values +3. **Generate strong secrets** - Use cryptographically secure random generators +4. **Rotate secrets regularly** - Especially after team member changes +5. **Use different secrets per environment** - Production ≠ Development +6. **Restrict access** - Limit who can view production secrets +7. **Use secret management** - Consider HashiCorp Vault, AWS Secrets Manager for production +8. **Audit secret access** - Log when secrets are accessed or rotated + +## Troubleshooting + +### Variable Not Loading + +**Check**: +1. Variable is defined in correct `.env` file +2. No typos in variable name +3. No extra spaces around `=` +4. Quotes used correctly (bash: no quotes for simple values) +5. Docker Compose file references correct env_file + +**Debug**: +```bash +# Print environment variables in container +docker exec env | grep DB_ +``` + +### Connection String Issues + +**Test connection string format**: +```bash +# Ensure semicolons separate components +# Ensure no trailing/leading spaces +# Ensure password special characters are not causing issues +``` + +### Frontend Variables Not Accessible + +**Remember**: +- Only `NEXT_PUBLIC_*` variables are accessible in browser +- Server-side variables require getServerSideProps or API routes +- Variables must be defined at build time for static pages + +## Example Configuration Files + +### `.env.dev` (Backend/Docker) + +```bash +# Database +DB_SERVER=sqlserver,1433 +DB_NAME=Biergarten +DB_USER=sa +DB_PASSWORD=Dev_Password_123! + +# JWT +JWT_SECRET=development-secret-key-at-least-32-characters-long-recommended-longer + +# Migration +CLEAR_DATABASE=true + +# ASP.NET Core +ASPNETCORE_ENVIRONMENT=Development +ASPNETCORE_URLS=http://0.0.0.0:8080 + +# SQL Server Container +SA_PASSWORD=Dev_Password_123! +ACCEPT_EULA=Y +MSSQL_PID=Express +``` + +### `.env.local` (Frontend) + +```bash +# Base +BASE_URL=http://localhost:3000 +NODE_ENV=development + +# Authentication +CONFIRMATION_TOKEN_SECRET= +RESET_PASSWORD_TOKEN_SECRET= +SESSION_SECRET= + +# Database (current Prisma setup) +POSTGRES_PRISMA_URL=postgresql://user:pass@db.neon.tech/biergarten?pgbouncer=true +POSTGRES_URL_NON_POOLING=postgresql://user:pass@db.neon.tech/biergarten + +# External Services +NEXT_PUBLIC_CLOUDINARY_CLOUD_NAME=my-cloud +CLOUDINARY_KEY=123456789012345 +CLOUDINARY_SECRET=abcdefghijklmnopqrstuvwxyz +MAPBOX_ACCESS_TOKEN=pk.eyJ... +SPARKPOST_API_KEY=abc123... +SPARKPOST_SENDER_ADDRESS=noreply@biergarten.app + +# Admin (for seeding) +ADMIN_PASSWORD=Admin_Dev_Password_123! +``` diff --git a/docs/getting-started.md b/docs/getting-started.md new file mode 100644 index 0000000..63e64a8 --- /dev/null +++ b/docs/getting-started.md @@ -0,0 +1,248 @@ +# Getting Started + +This guide will help you set up and run The Biergarten App in your development environment. + +## Prerequisites + +Before you begin, ensure you have the following installed: + +- **.NET SDK 10+** - [Download](https://dotnet.microsoft.com/download) +- **Node.js 18+** - [Download](https://nodejs.org/) +- **Docker Desktop** - [Download](https://www.docker.com/products/docker-desktop) (recommended) +- **Java 8+** - Required for generating diagrams from PlantUML (optional) + +## Quick Start with Docker (Recommended) + +### 1. Clone the Repository + +```bash +git clone +cd the-biergarten-app +``` + +### 2. Configure Environment Variables + +Copy the example environment file: + +```bash +cp .env.example .env.dev +``` + +Edit `.env.dev` with your configuration: + +```bash +# Database (component-based for Docker) +DB_SERVER=sqlserver,1433 +DB_NAME=Biergarten +DB_USER=sa +DB_PASSWORD=YourStrong!Passw0rd + +# JWT Authentication +JWT_SECRET=your-secret-key-minimum-32-characters-required +``` + +> For a complete list of environment variables, see [Environment Variables](environment-variables.md). + +### 3. Start the Development Environment + +```bash +docker compose -f docker-compose.dev.yaml up -d +``` + +This command will: +- Start SQL Server container +- Run database migrations +- Seed initial data +- Start the API on http://localhost:8080 + +### 4. Access the API + +- **Swagger UI**: http://localhost:8080/swagger +- **Health Check**: http://localhost:8080/health + +### 5. View Logs + +```bash +# All services +docker compose -f docker-compose.dev.yaml logs -f + +# Specific service +docker compose -f docker-compose.dev.yaml logs -f api.core +``` + +### 6. Stop the Environment + +```bash +docker compose -f docker-compose.dev.yaml down + +# Remove volumes (fresh start) +docker compose -f docker-compose.dev.yaml down -v +``` + +## Manual Setup (Without Docker) + +If you prefer to run services locally without Docker: + +### Backend Setup + +#### 1. Start SQL Server + +You can use a local SQL Server instance or a cloud-hosted one. Ensure it's accessible and you have the connection details. + +#### 2. Set Environment Variables + +```bash +# macOS/Linux +export DB_CONNECTION_STRING="Server=localhost,1433;Database=Biergarten;User Id=sa;Password=YourStrong!Passw0rd;TrustServerCertificate=True;" +export JWT_SECRET="your-secret-key-minimum-32-characters-required" + +# Windows PowerShell +$env:DB_CONNECTION_STRING="Server=localhost,1433;Database=Biergarten;User Id=sa;Password=YourStrong!Passw0rd;TrustServerCertificate=True;" +$env:JWT_SECRET="your-secret-key-minimum-32-characters-required" +``` + +#### 3. Run Database Migrations + +```bash +cd src/Core +dotnet run --project Database/Database.Migrations/Database.Migrations.csproj +``` + +#### 4. Seed the Database + +```bash +dotnet run --project Database/Database.Seed/Database.Seed.csproj +``` + +#### 5. Start the API + +```bash +dotnet run --project API/API.Core/API.Core.csproj +``` + +The API will be available at http://localhost:5000 (or the port specified in launchSettings.json). + +### Frontend Setup + +> **Note**: The frontend is currently transitioning from its standalone Prisma/Postgres backend to the .NET API. Some features may still use the old backend. + +#### 1. Navigate to Website Directory + +```bash +cd Website +``` + +#### 2. Create Environment File + +Create `.env.local` with frontend variables. See [Environment Variables - Frontend](environment-variables.md#frontend-variables) for the complete list. + +```bash +BASE_URL=http://localhost:3000 +NODE_ENV=development + +# Generate secrets +CONFIRMATION_TOKEN_SECRET=$(openssl rand -base64 127) +RESET_PASSWORD_TOKEN_SECRET=$(openssl rand -base64 127) +SESSION_SECRET=$(openssl rand -base64 127) + +# External services (you'll need to register for these) +NEXT_PUBLIC_CLOUDINARY_CLOUD_NAME=your-cloud-name +CLOUDINARY_KEY=your-api-key +CLOUDINARY_SECRET=your-api-secret +NEXT_PUBLIC_MAPBOX_KEY=your-mapbox-token + +# Database URL (current Prisma setup) +DATABASE_URL=your-postgres-connection-string +``` + +#### 3. Install Dependencies + +```bash +npm install +``` + +#### 4. Run Prisma Migrations + +```bash +npx prisma generate +npx prisma migrate dev +``` + +#### 5. Start Development Server + +```bash +npm run dev +``` + +The frontend will be available at http://localhost:3000. + +## Generate Diagrams (Optional) + +The project includes PlantUML diagrams that can be converted to PDF or PNG: + +### Install Java + +Make sure Java 8+ is installed: + +```bash +# Check Java version +java -version +``` + +### Generate Diagrams + +```bash +# Generate all PDFs +make + +# Generate PNGs +make pngs + +# Generate both +make diagrams + +# View help +make help +``` + +Generated diagrams will be in `docs/diagrams/pdf/`. + +## Next Steps + +- **Test the API**: Visit http://localhost:8080/swagger and try the endpoints +- **Run Tests**: See [Testing Guide](testing.md) +- **Learn the Architecture**: Read [Architecture Overview](architecture.md) +- **Understand Docker Setup**: See [Docker Guide](docker.md) +- **Database Details**: Check [Database Schema](database.md) + +## Troubleshooting + +### Port Already in Use + +If port 8080 or 1433 is already in use, you can either: +- Stop the service using that port +- Change the port mapping in `docker-compose.dev.yaml` + +### Database Connection Issues + +Check that: +- SQL Server container is running: `docker ps` +- Connection string is correct in `.env.dev` +- Health check is passing: `docker compose -f docker-compose.dev.yaml ps` + +### Container Won't Start + +View container logs: +```bash +docker compose -f docker-compose.dev.yaml logs +``` + +### Fresh Start + +Remove all containers and volumes: +```bash +docker compose -f docker-compose.dev.yaml down -v +docker system prune -f +``` + +For more troubleshooting, see the [Docker Guide](docker.md#troubleshooting). diff --git a/docs/testing.md b/docs/testing.md new file mode 100644 index 0000000..ffa9236 --- /dev/null +++ b/docs/testing.md @@ -0,0 +1,275 @@ +# Testing + +This document describes the testing strategy and how to run tests for The Biergarten App. + +## Overview + +The project uses a multi-layered testing approach: + +- **API.Specs** - BDD integration tests using Reqnroll (Gherkin) +- **Infrastructure.Repository.Tests** - Unit tests for data access layer +- **Service.Auth.Tests** - Unit tests for authentication business logic + +## Running Tests with Docker (Recommended) + +The easiest way to run all tests is using Docker Compose, which sets up an isolated test environment: + +```bash +docker compose -f docker-compose.test.yaml up --abort-on-container-exit +``` + +This command: +1. Starts a fresh SQL Server instance +2. Runs database migrations +3. Seeds test data +4. Executes all test suites in parallel +5. Exports results to `./test-results/` +6. Exits when tests complete + +### View Test Results + +```bash +# List test result files +ls -la test-results/ + +# View specific test results +cat test-results/api-specs/results.trx +cat test-results/repository-tests/results.trx +cat test-results/service-auth-tests/results.trx +``` + +### Clean Up + +```bash +# Remove test containers and volumes +docker compose -f docker-compose.test.yaml down -v +``` + +## Running Tests Locally + +You can run individual test projects locally without Docker: + +### Integration Tests (API.Specs) + +```bash +cd src/Core +dotnet test API/API.Specs/API.Specs.csproj +``` + +**Requirements**: +- SQL Server instance running +- Database migrated and seeded +- Environment variables set (DB connection, JWT secret) + +### Repository Tests + +```bash +cd src/Core +dotnet test Infrastructure/Infrastructure.Repository.Tests/Infrastructure.Repository.Tests.csproj +``` + +**Requirements**: +- SQL Server instance running (uses mock data) + +### Service Tests + +```bash +cd src/Core +dotnet test Service/Service.Auth.Tests/Service.Auth.Tests.csproj +``` + +**Requirements**: +- No database required (uses Moq for mocking) + +## Test Coverage + +### Current Coverage + +**Authentication & User Management**: +- ✅ User registration with validation +- ✅ User login with JWT token generation +- ✅ Password hashing and verification (Argon2id) +- ✅ JWT token generation and claims +- ✅ Invalid credentials handling +- ✅ 404 error responses + +**Repository Layer**: +- ✅ User account creation +- ✅ User credential management +- ✅ GetUserByUsername queries +- ✅ Stored procedure execution + +**Service Layer**: +- ✅ Login service with password verification +- ✅ Register service with validation +- ✅ Business logic for authentication flow + +### Planned Coverage + +- [ ] Email verification workflow +- [ ] Password reset functionality +- [ ] Token refresh mechanism +- [ ] Brewery data management +- [ ] Beer post operations +- [ ] User follow/unfollow +- [ ] Image upload service + +## Testing Frameworks & Tools + +### xUnit +- Primary unit testing framework +- Used for Repository and Service layer tests +- Supports parallel test execution + +### Reqnroll (Gherkin/BDD) +- Behavior-driven development framework +- Used for API integration tests +- Human-readable test scenarios in `.feature` files + +### FluentAssertions +- Expressive assertion library +- Makes test assertions more readable +- Used across all test projects + +### Moq +- Mocking framework for .NET +- Used in Service layer tests +- Enables isolated unit testing + +### DbMocker +- Database mocking for repository tests +- Simulates SQL Server responses +- No real database required for unit tests + +## Test Structure + +### API.Specs (Integration Tests) + +``` +API.Specs/ +├── Features/ +│ ├── Authentication.feature # Login/register scenarios +│ └── UserManagement.feature # User CRUD scenarios +├── Steps/ +│ ├── AuthenticationSteps.cs # Step definitions +│ └── UserManagementSteps.cs +└── Mocks/ + └── TestApiFactory.cs # Test server setup +``` + +**Example Feature**: +```gherkin +Feature: User Authentication + As a user + I want to register and login + So that I can access the platform + +Scenario: Successful user registration + Given I have valid registration details + When I register a new account + Then I should receive a JWT token + And my account should be created +``` + +### Infrastructure.Repository.Tests + +``` +Infrastructure.Repository.Tests/ +├── AuthRepositoryTests.cs # Auth repository tests +├── UserAccountRepositoryTests.cs # User account tests +└── TestFixtures/ + └── DatabaseFixture.cs # Shared test setup +``` + +### Service.Auth.Tests + +``` +Service.Auth.Tests/ +├── LoginService.test.cs # Login business logic tests +└── RegisterService.test.cs # Registration business logic tests +``` + +## Writing Tests + +### Unit Test Example (xUnit) + +```csharp +public class LoginServiceTests +{ + [Fact] + public async Task LoginAsync_ValidCredentials_ReturnsToken() + { + // Arrange + var mockRepo = new Mock(); + var mockJwt = new Mock(); + var service = new AuthService(mockRepo.Object, mockJwt.Object); + + // Act + var result = await service.LoginAsync("testuser", "password123"); + + // Assert + result.Should().NotBeNull(); + result.Token.Should().NotBeNullOrEmpty(); + } +} +``` + +### Integration Test Example (Reqnroll) + +```gherkin +Scenario: User login with valid credentials + Given a registered user with username "testuser" + When I POST to "/api/auth/login" with valid credentials + Then the response status should be 200 + And the response should contain a JWT token +``` + +## Continuous Integration + +Tests run automatically in CI/CD pipelines using the test Docker Compose configuration: + +```bash +# CI/CD command +docker compose -f docker-compose.test.yaml build +docker compose -f docker-compose.test.yaml up --abort-on-container-exit +docker compose -f docker-compose.test.yaml down -v +``` + +Exit codes: +- `0` - All tests passed +- Non-zero - Test failures occurred + +## Troubleshooting + +### Tests Failing Due to Database Connection + +Ensure SQL Server is running and environment variables are set: +```bash +docker compose -f docker-compose.test.yaml ps +``` + +### Port Conflicts + +If port 1433 is in use, stop other SQL Server instances or modify the port in `docker-compose.test.yaml`. + +### Stale Test Data + +Clean up test database: +```bash +docker compose -f docker-compose.test.yaml down -v +``` + +### View Container Logs + +```bash +docker compose -f docker-compose.test.yaml logs +``` + +## Best Practices + +1. **Isolation**: Each test should be independent and not rely on other tests +2. **Cleanup**: Use fixtures and dispose patterns for resource cleanup +3. **Mocking**: Mock external dependencies in unit tests +4. **Descriptive Names**: Use clear, descriptive test method names +5. **Arrange-Act-Assert**: Follow AAA pattern in unit tests +6. **Given-When-Then**: Follow GWT pattern in BDD scenarios