Cleanup prose in written documentation

This commit is contained in:
Aaron Po
2026-08-31 17:23:46 -04:00
parent f28f188a54
commit 30995f6f40
9 changed files with 141 additions and 113 deletions

View File

@@ -59,8 +59,8 @@ Scripts live under `scripts/`, applied in order and tracked so a script never
re-runs once it has succeeded. Today that's `01-schema/`, with one script per
table, numbered so a table's foreign keys always point at an earlier-numbered
script. When the schema needs to change, add a new numbered script rather than
editing an existing one in place — DbUp's tracking is per-script, so editing
an already-applied script means it won't re-run against existing databases.
editing an existing one in place: DbUp's tracking is per-script, so an edited
already-applied script never re-runs against existing databases.
## Data seeding
@@ -68,7 +68,7 @@ an already-applied script means it won't re-run against existing databases.
pipeline (see [Pipeline README](../pipeline/README.md)): countries,
state/provinces, and cities (created on demand while resolving each brewery's
location), user accounts, and brewery posts with locations. It doesn't call
every feature slice that has a write path — `Photo`, for example, has a
every feature slice that has a write path: `Photo`, for example, has a
repository (`Features.PhotoUpload`) but no seed data.
## Database error handling
@@ -78,10 +78,10 @@ repository (`Features.PhotoUpload`) but no seed data.
- 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.
409, 400, ...); an unmapped `SqlException` (for example, a genuine
connectivity or constraint-violation failure) maps to 503.
- One exception: `Features.Users`' `DapperUserStore` 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 `DapperUserStore.IsDuplicateKeyViolation`.
concurrently; see `DapperUserStore.IsDuplicateKeyViolation`.

View File

@@ -100,7 +100,8 @@ frontend.tests # Storybook Vitest + Playwright suites (own Playwright image)
```bash
# Run all tests
docker compose --env-file web/.env.test -f web/docker-compose.test.yaml up --abort-on-container-exit
docker compose --env-file web/.env.test -f web/docker-compose.test.yaml up -d
docker compose --env-file web/.env.test -f web/docker-compose.test.yaml wait api.specs unit.tests frontend.tests
# View results
ls -la test-results/
@@ -111,6 +112,13 @@ cat test-results/Features.Users.Tests.trx
docker compose --env-file web/.env.test -f web/docker-compose.test.yaml down -v
```
`wait` blocks until `api.specs`, `unit.tests`, and `frontend.tests` have all
stopped, regardless of which one finishes first; `up --abort-on-container-exit`
isn't used here because the one-shot `database.migrations`/`database.seed`
jobs and `frontend.tests` (no database dependency) can exit before the other
test containers, which would end the run prematurely. See
[Testing](testing.md#running-tests-with-docker-recommended) for details.
### 3. Production (`docker-compose.prod.yaml`)
**Purpose**: Production-ready deployment

View File

@@ -1,7 +1,6 @@
# Environment variables
This document covers the active environment variables used by the current
Biergarten stack.
This document covers the environment variables used by the Biergarten stack.
## Overview
@@ -19,10 +18,10 @@ Direct environment variable access via `Environment.GetEnvironmentVariable()`.
### Frontend (`web/frontend`)
The active website reads runtime values from the server environment for its auth
and API integration. In Docker, the `frontend` service is wired into the same
The website reads runtime values from the server environment for its auth and
API integration. In Docker, the `frontend` service is wired into the same
`web/.env.dev` / `.env.test` / `.env.prod` files as the backend; running it
locally with `npm run dev` still reads from the shell environment.
locally with `npm run dev` reads from the shell environment.
### Docker
@@ -104,7 +103,8 @@ WEBSITE_BASE_URL=https://thebiergarten.app # Base URL for the website
**Security Requirements**:
- Use at least 32 characters per secret
- Use 127 or more characters in production
- Use secrets generated from 127 or more random bytes in production (base64
encoding turns 127 bytes into a longer string, well over 127 characters)
- Generate secrets with a cryptographically secure random function
- Never reuse secrets across token types or environments
- Rotate secrets periodically in production
@@ -170,10 +170,17 @@ ASPNETCORE_URLS=http://0.0.0.0:8080 # Binding address and port
DOTNET_RUNNING_IN_CONTAINER=true # Flag for container execution
```
`ASPNETCORE_URLS` is hardcoded directly in `docker-compose.dev.yaml` and
`docker-compose.prod.yaml` (not sourced from the `.env` file), so it's
required only for Docker. Outside Docker, `dotnet run` falls back to the
`http` profile in `API.Core/Properties/launchSettings.json`
(`http://localhost:5069`), so manual backend setup (see
[Getting Started](getting-started.md#manual-backend-setup)) doesn't need to
set it.
## Frontend variables (`web/frontend`)
The active website does not use the old Next.js/Prisma environment model. Its
core runtime variables are:
The website's core runtime variables are:
```bash
API_BASE_URL=http://api.core:8080 # Base URL for the .NET API (container DNS name in Docker)
@@ -291,12 +298,6 @@ cp web/.env.example web/.env.dev
# Edit web/.env.dev with your values
```
## Legacy frontend variables
Variables for the archived Next.js frontend (`archive/next-js-web-app/`) have
been removed from this active reference, since that app is retained for
reference only and is not run as part of the active stack.
**Docker Compose Mapping**:
- `web/docker-compose.dev.yaml` → `web/.env.dev`
@@ -334,7 +335,7 @@ reference only and is not run as part of the active stack.
| `PORT` | | ✓ | ✓ | No | Frontend server port |
| `CLEAR_DATABASE` | ✓ | | ✓ | No | Dev/test reset flag |
| `ASPNETCORE_ENVIRONMENT` | ✓ | | ✓ | Yes | ASP.NET environment |
| `ASPNETCORE_URLS` | ✓ | | ✓ | Yes | API binding address |
| `ASPNETCORE_URLS` | ✓ | | ✓ | Yes\*\* | API binding address |
| `SA_PASSWORD` | | | ✓ | Yes | SQL Server container |
| `ACCEPT_EULA` | | | ✓ | Yes | SQL Server EULA |
| `MSSQL_PID` | | | ✓ | No | SQL Server edition |
@@ -343,6 +344,10 @@ reference only and is not run as part of the active stack.
\* Either `DB_CONNECTION_STRING` OR the component variables (`DB_SERVER`,
`DB_NAME`, `DB_USER`, `DB_PASSWORD`) must be provided.
\*\* Required for Docker, where it's hardcoded in the compose files. Outside
Docker, `dotnet run` falls back to the `applicationUrl` in
`launchSettings.json`.
## Validation
### Backend validation
@@ -359,8 +364,8 @@ Variables are validated at startup:
### Frontend validation
The active website relies on runtime defaults for local development and the
surrounding server environment in deployed environments.
The website relies on runtime defaults for local development and on the server
environment when deployed.
- `API_BASE_URL` defaults to `http://localhost:8080`
- `SESSION_SECRET` falls back to a development-only local secret

View File

@@ -1,7 +1,7 @@
# Getting started
This guide covers local setup for the current Biergarten stack: the .NET backend
in `web/backend` and the active React Router frontend in `web/frontend`.
This guide covers local setup for the Biergarten stack: the .NET backend in
`web/backend` and the React Router frontend in `web/frontend`.
## Prerequisites
@@ -69,7 +69,7 @@ Available endpoints:
- Mailpit UI: http://localhost:8025
- SeaweedFS S3 API: http://localhost:8333
### 4. Start the active frontend
### 4. Start the frontend
```bash
cd web/frontend
@@ -146,10 +146,10 @@ dotnet run --project Database/Database.Seed/Database.Seed.csproj
dotnet run --project API/API.Core/API.Core.csproj
```
## Legacy frontend note
## Archived frontend
The previous Next.js frontend lives in `archive/next-js-web-app/` for reference
only and is not the active website.
`archive/next-js-web-app/` contains an archived Next.js frontend, kept for
reference; it is not part of the running stack.
## Next steps

View File

@@ -21,8 +21,8 @@ The project uses a multi-layered testing approach across backend and frontend:
## Running tests with Docker (recommended)
The easiest way to run all tests is using Docker Compose, which sets up an
isolated test environment:
To run all tests, use Docker Compose, which sets up an isolated test
environment:
```bash
docker compose --env-file web/.env.test -f web/docker-compose.test.yaml up -d
@@ -43,7 +43,7 @@ This command:
stopped, regardless of which one finishes first. Long-running services
(`sqlserver`, `seaweedfs`) and the one-shot `database.migrations` /
`database.seed` jobs keep running in the background alongside them and are
torn down separately (see below) — `wait` doesn't touch them.
torn down separately (see below); `wait` doesn't touch them.
### View test results
@@ -355,15 +355,12 @@ The workflow inspects the exit code of each test container
after `wait` returns and fails the job if any of them is non-zero.
`./test-results/` is uploaded as a build artifact regardless of outcome.
Earlier versions of this workflow used
`up --abort-on-container-exit`, which stops the entire stack the instant
*any* watched container exits. That's wrong here: the one-shot
`database.migrations`/`database.seed` jobs are expected to exit 0 partway
through, and even scoping the flag to just the three test services doesn't
help, since `frontend.tests` (no database dependency) reliably finishes
before `api.specs`/`unit.tests` even start, aborting them prematurely.
`up -d` + `wait` avoids both races by not tying container teardown to any
single container's exit.
`up -d` + `wait` keeps container teardown independent of any single
container's exit. The one-shot `database.migrations`/`database.seed` jobs
exit 0 partway through the run, and `frontend.tests` (which has no database
dependency) reliably finishes before `api.specs`/`unit.tests` start; because
`wait` blocks on all three test containers, neither early exit ends the run
prematurely.
Exit codes:
@@ -414,13 +411,13 @@ act push \
`sqlserver` service's image is amd64-only, and act needs to know to
emulate that platform for the whole job container, not just that one
service.
- No secrets or `.actrc` are needed — `generate-env.sh` creates its own
- No secrets or `.actrc` are needed: `generate-env.sh` creates its own
`.env.test` with freshly randomized values, same as in real CI.
**Known act-only limitation**: the "Upload test results" step
(`actions/upload-artifact@v4`) fails locally with `Unable to get the
ACTIONS_RUNTIME_TOKEN env variable`. act doesn't provide a real Actions
artifact backend, so this step — and only this step — is expected to fail
artifact backend, so this step, and only this step, is expected to fail
under act even when everything else passes. It works normally on
GitHub-hosted runners.
@@ -429,7 +426,7 @@ names and named volumes (`sqlserverdata-test`, `seaweedfsdata-test`) scoped
to the `web` compose project. If a previous local run (via `act` or a
manual `docker compose` invocation) didn't get torn down, its SQL Server
volume can persist with an old `SA_PASSWORD` baked in, while a fresh
`.env.test` generates a new one each run — causing `Login failed for user
`.env.test` generates a new one each run, causing `Login failed for user
'sa'` errors that look like a test bug but are really a leftover volume.
Clear it before re-running:

View File

@@ -34,8 +34,7 @@ Low-level JWT operations.
#### [ITokenService](../../web/backend/Features/Features.Users/Services/ITokenService.cs)
Both token generation and validation live on the same slice-internal service
(there is no separate validation service/class).
Token generation and validation live on the same slice-internal service.
**Generation methods:**
@@ -79,9 +78,17 @@ directly.
1. Receives confirmation token from user via `ConfirmUserCommand`
2. Calls `ITokenService.ValidateConfirmationTokenAsync()`
3. Extracts user ID from validated token and looks it up via `UserManager<ApplicationUser>`
4. Marks the account confirmed via `IUserEmailStore<ApplicationUser>.SetEmailConfirmedAsync()`
4. Marks the account confirmed via `IUserEmailStore<ApplicationUser>.SetEmailConfirmedAsync()`,
then persists it via `UserManager<ApplicationUser>.UpdateAsync()`
5. Returns confirmation result
`UserManager<ApplicationUser>` and `IUserEmailStore<ApplicationUser>` are
ASP.NET Core Identity's abstractions, not EF Core's default Identity store:
the app backs them with its own `DapperUserStore` (see
[Database](database.md#database-error-handling)), which persists against the
existing `UserAccount`/`UserCredential`/`UserVerification` tables via Dapper,
consistent with the rest of the app's repository pattern.
#### [ResendConfirmationEmailHandler](../../web/backend/Features/Features.Users/Commands/Authentication/ResendConfirmationEmail/ResendConfirmationEmailHandler.cs)
**Flow:**