mirror of
https://github.com/aaronpo97/the-biergarten-app.git
synced 2026-02-16 10:42:08 +00:00
Update documentation
This commit is contained in:
404
docs/architecture.md
Normal file
404
docs/architecture.md
Normal file
@@ -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<UserCredential> GetUserCredentialAsync(string username);
|
||||||
|
Task<int> 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
|
||||||
364
docs/database.md
Normal file
364
docs/database.md
Normal file
@@ -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
|
||||||
75
docs/diagrams/architecture.puml
Normal file
75
docs/diagrams/architecture.puml
Normal file
@@ -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
|
||||||
72
docs/diagrams/authentication-flow.puml
Normal file
72
docs/diagrams/authentication-flow.puml
Normal file
@@ -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
|
||||||
104
docs/diagrams/database-schema.puml
Normal file
104
docs/diagrams/database-schema.puml
Normal file
@@ -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 <<PK>>
|
||||||
|
--
|
||||||
|
* Username: NVARCHAR(30) <<UNIQUE>>
|
||||||
|
* Email: NVARCHAR(255) <<UNIQUE>>
|
||||||
|
* FirstName: NVARCHAR(50)
|
||||||
|
* LastName: NVARCHAR(50)
|
||||||
|
Bio: NVARCHAR(500)
|
||||||
|
CreatedAt: DATETIME2
|
||||||
|
UpdatedAt: DATETIME2
|
||||||
|
LastLoginAt: DATETIME2
|
||||||
|
}
|
||||||
|
|
||||||
|
entity "UserCredential" as Cred {
|
||||||
|
* UserCredentialId: INT <<PK>>
|
||||||
|
--
|
||||||
|
* UserAccountId: INT <<FK>>
|
||||||
|
* PasswordHash: VARBINARY(32)
|
||||||
|
* PasswordSalt: VARBINARY(16)
|
||||||
|
CredentialRotatedAt: DATETIME2
|
||||||
|
CredentialExpiresAt: DATETIME2
|
||||||
|
CredentialRevokedAt: DATETIME2
|
||||||
|
* IsActive: BIT
|
||||||
|
CreatedAt: DATETIME2
|
||||||
|
}
|
||||||
|
|
||||||
|
entity "UserVerification" as Verify {
|
||||||
|
* UserVerificationId: INT <<PK>>
|
||||||
|
--
|
||||||
|
* UserAccountId: INT <<FK>>
|
||||||
|
* IsVerified: BIT
|
||||||
|
VerifiedAt: DATETIME2
|
||||||
|
VerificationToken: NVARCHAR(255)
|
||||||
|
TokenExpiresAt: DATETIME2
|
||||||
|
}
|
||||||
|
|
||||||
|
entity "UserAvatar" as Avatar {
|
||||||
|
* UserAvatarId: INT <<PK>>
|
||||||
|
--
|
||||||
|
* UserAccountId: INT <<FK>>
|
||||||
|
PhotoId: INT <<FK>>
|
||||||
|
* IsActive: BIT
|
||||||
|
CreatedAt: DATETIME2
|
||||||
|
}
|
||||||
|
|
||||||
|
entity "UserFollow" as Follow {
|
||||||
|
* UserFollowId: INT <<PK>>
|
||||||
|
--
|
||||||
|
* FollowerUserId: INT <<FK>>
|
||||||
|
* FollowedUserId: INT <<FK>>
|
||||||
|
CreatedAt: DATETIME2
|
||||||
|
}
|
||||||
|
|
||||||
|
entity "Photo" as Photo {
|
||||||
|
* PhotoId: INT <<PK>>
|
||||||
|
--
|
||||||
|
* 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
|
||||||
94
docs/diagrams/deployment.puml
Normal file
94
docs/diagrams/deployment.puml
Normal file
@@ -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
|
||||||
510
docs/docker.md
Normal file
510
docs/docker.md
Normal file
@@ -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 <service-name>
|
||||||
|
|
||||||
|
# Check container status
|
||||||
|
docker compose -f docker-compose.dev.yaml ps
|
||||||
|
|
||||||
|
# Inspect container
|
||||||
|
docker inspect <container-name>
|
||||||
|
```
|
||||||
|
|
||||||
|
### 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 <volume-name>
|
||||||
|
```
|
||||||
|
|
||||||
|
### 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)
|
||||||
408
docs/environment-variables.md
Normal file
408
docs/environment-variables.md
Normal file
@@ -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=<generated-secret> # Email confirmation tokens
|
||||||
|
RESET_PASSWORD_TOKEN_SECRET=<generated-secret> # Password reset tokens
|
||||||
|
SESSION_SECRET=<generated-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 <container> 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=<generated-with-openssl>
|
||||||
|
RESET_PASSWORD_TOKEN_SECRET=<generated-with-openssl>
|
||||||
|
SESSION_SECRET=<generated-with-openssl>
|
||||||
|
|
||||||
|
# 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!
|
||||||
|
```
|
||||||
248
docs/getting-started.md
Normal file
248
docs/getting-started.md
Normal file
@@ -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 <repository-url>
|
||||||
|
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 <service-name>
|
||||||
|
```
|
||||||
|
|
||||||
|
### 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).
|
||||||
275
docs/testing.md
Normal file
275
docs/testing.md
Normal file
@@ -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<IAuthRepository>();
|
||||||
|
var mockJwt = new Mock<IJwtService>();
|
||||||
|
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 <service-name>
|
||||||
|
```
|
||||||
|
|
||||||
|
## 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
|
||||||
Reference in New Issue
Block a user