mirror of
https://github.com/aaronpo97/the-biergarten-app.git
synced 2026-02-16 10:42:08 +00:00
365 lines
10 KiB
Markdown
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
|