# 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