mirror of
https://github.com/aaronpo97/the-biergarten-app.git
synced 2026-10-15 01:54:43 +00:00
79 lines
6.1 KiB
Markdown
79 lines
6.1 KiB
Markdown
# Database
|
|
|
|
SQL Server 2022 schema, migration approach, and the app's SQL error-handling
|
|
convention. For the vertical-slice code that talks to this schema, see
|
|
[Architecture](../architecture.md).
|
|
|
|
## Schema
|
|
|
|
The full schema lives in one script:
|
|
`web/backend/Database/Database.Migrations/scripts/01-schema/schema.sql`, run by
|
|
`Database.Migrations` (DbUp) on startup. Each table is queried directly by the
|
|
owning feature slice's repository (see
|
|
[Direct SQL via Dapper/ADO.NET](../architecture.md#direct-sql-via-dapperadonet)).
|
|
|
|
All tables use a `UNIQUEIDENTIFIER` primary key defaulting to `NEWID()`, and
|
|
most carry a `Timer ROWVERSION` column used for optimistic concurrency on
|
|
updates (`UPDATE ... WHERE ... AND Timer = @Timer`).
|
|
|
|
### Tables backed by the API today
|
|
|
|
| Table | Backed by | Notes |
|
|
| --------------------- | ------------------------------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
| `UserAccount` | `Domain.Entities.UserAccount`, `Features.Auth`/`Features.UserManagement` | `Username` and `Email` are unique |
|
|
| `UserCredential` | `Domain.Entities.UserCredential`, `Features.Auth` | Argon2id password hash; `IsRevoked`/`RevokedAt` track credential rotation, not JWTs |
|
|
| `UserVerification` | `Domain.Entities.UserVerification`, `Features.Auth` | Written when a user confirms their email |
|
|
| `BreweryPost` | `Domain.Entities.BreweryPost`, `Features.Breweries` | A user-submitted brewery listing |
|
|
| `BreweryPostLocation` | `Domain.Entities.BreweryPostLocation`, `Features.Breweries` | 1:1 with `BreweryPost`; `Coordinates` is a `GEOGRAPHY` column, read manually via `DbDataReader.GetFieldValue<byte[]>` because Dapper can't deserialize SQL Server UDTs |
|
|
| `City` | `Domain.Entities.City`, `Features.Locations` | Referenced by `BreweryPostLocation.CityID`; looked up via `ILocationRepository`, currently consumed only by `Database.Seed` |
|
|
|
|
### Tables defined in the schema but not yet wired to a feature slice
|
|
|
|
These exist so the schema can support planned functionality, but have no
|
|
`Domain.Entities` type, repository, or seed data yet:
|
|
|
|
| Table | Purpose (from schema) |
|
|
| ------------------ | -------------------------------------------------------------------------------------------------------- |
|
|
| `Photo` | A photo uploaded by a user account |
|
|
| `UserAvatar` | Links a `UserAccount` to a `Photo` as its avatar |
|
|
| `UserFollow` | One user following another (`CannotFollowOwnAccount` check constraint) |
|
|
| `StateProvince` | Referenced by `City.StateProvinceID` |
|
|
| `Country` | Referenced by `StateProvince.CountryID` |
|
|
| `BeerStyle` | A beer style/category |
|
|
| `BeerPost` | A beer listing, linked to a `BreweryPost` and `BeerStyle`; `ABV`/`IBU` are constrained (`0-67`, `0-120`) |
|
|
| `BeerPostPhoto` | Links a `BeerPost` to a `Photo` |
|
|
| `BeerPostComment` | A comment + 1-5 `Rating` on a `BeerPost` |
|
|
| `BreweryPostPhoto` | Links a `BreweryPost` to a `Photo` |
|
|
|
|
## Migrations
|
|
|
|
**Tool**: DbUp, run by the `Database.Migrations` project/container.
|
|
|
|
Scripts live under `scripts/`, applied in order and tracked so a script never
|
|
re-runs once it has succeeded. Today that's a single `01-schema/schema.sql`.
|
|
When the schema needs to change, add a new numbered script rather than editing
|
|
`schema.sql` in place — DbUp's tracking is per-script, so editing an
|
|
already-applied script means it won't re-run against existing databases.
|
|
|
|
## Data seeding
|
|
|
|
`Database.Seed` populates a target database from data produced by the C++
|
|
pipeline (see [Pipeline README](../pipeline/README.md)): cities, user accounts,
|
|
and brewery posts with locations. It only seeds tables that have a feature slice
|
|
to write through (see the table above).
|
|
|
|
## Database error handling
|
|
|
|
- Optimistic-concurrency conflicts are detected by an `UPDATE` affecting zero
|
|
rows against the `Timer` column.
|
|
- On failure, the repository throws a `Domain.Exceptions` type directly
|
|
(`NotFoundException`, `ConflictException`, `ArgumentException`, ...).
|
|
`API.Core`'s `GlobalExceptionFilter` maps these to HTTP status codes (404,
|
|
409, 400, ...); an unmapped `SqlException` (e.g. a genuine connectivity or
|
|
constraint-violation failure) maps to 503.
|
|
|
|
- One repository-level exception: `Features.Auth`'s `AuthRepository` does
|
|
catch `SqlException` directly, to detect a duplicate-key race (SQL Server
|
|
error 2601 or 2627) when two requests try to verify the same account
|
|
concurrently — see `Auth Repository.IsDuplicateVerificationViolation`.
|