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:
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
|
||||
Reference in New Issue
Block a user