Update documentation

This commit is contained in:
Aaron Po
2026-02-15 21:13:07 -05:00
parent 0d52c937ce
commit b7bd287c48
11 changed files with 2792 additions and 863 deletions

1077
README.md

File diff suppressed because it is too large Load Diff

404
docs/architecture.md Normal file
View 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
View 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

View 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

View 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

View 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

View 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
View 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)

View 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
View 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
View 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