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

10 KiB

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

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