Files
the-biergarten-app/docs/database.md
2026-02-15 21:13:07 -05:00

365 lines
10 KiB
Markdown

# 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