mirror of
https://github.com/aaronpo97/the-biergarten-app.git
synced 2026-10-14 17:54:42 +00:00
Documentation updates (#236)
Remove vague or flowery language, use Simplified Technical English whenever possible
This commit is contained in:
@@ -2,7 +2,7 @@
|
||||
|
||||
This document describes the active architecture of The Biergarten App.
|
||||
|
||||
## High-Level Overview
|
||||
## High-level overview
|
||||
|
||||
The Biergarten App is a monorepo with a clear split between the backend and the
|
||||
active website:
|
||||
@@ -29,9 +29,9 @@ For visual representations, see:
|
||||
- [database-schema.svg](website/diagrams-out/database-schema.svg) - Database
|
||||
relationships
|
||||
|
||||
## Backend Architecture
|
||||
## Backend architecture
|
||||
|
||||
### Vertical Slice Architecture Pattern
|
||||
### Vertical-slice architecture pattern
|
||||
|
||||
The backend organizes business capabilities as feature slices instead of
|
||||
technical layers. Each feature (`Features.Auth`, `Features.Breweries`,
|
||||
@@ -80,9 +80,9 @@ interaction (`Features.Auth` triggering a confirmation email handled by
|
||||
whose contract lives in `Shared.Application`, so neither slice takes a
|
||||
project reference on the other.
|
||||
|
||||
### Layer Responsibilities
|
||||
### Layer responsibilities
|
||||
|
||||
#### API Layer (`API.Core`)
|
||||
#### API layer (`API.Core`)
|
||||
|
||||
**Purpose**: Thin ASP.NET Core host: no business logic, no controllers of
|
||||
its own
|
||||
@@ -108,7 +108,7 @@ its own
|
||||
- No controllers, no business logic, no feature-specific contracts
|
||||
- Exists purely to host and wire up the feature slices
|
||||
|
||||
#### Feature Slices (`Features.Auth`, `Features.Breweries`, `Features.UserManagement`, `Features.Emails`)
|
||||
#### Feature slices (`Features.Auth`, `Features.Breweries`, `Features.UserManagement`, `Features.Emails`)
|
||||
|
||||
**Purpose**: Each slice is the complete vertical for one business capability
|
||||
|
||||
@@ -146,7 +146,7 @@ MediatR commands sent from other slices, never over HTTP.
|
||||
MediatR
|
||||
- Read endpoints return a dedicated `Dto`, never the domain entity directly
|
||||
|
||||
#### Shared Projects (`Shared.Contracts`, `Shared.Application`)
|
||||
#### Shared projects (`Shared.Contracts`, `Shared.Application`)
|
||||
|
||||
**Purpose**: The minimum cross-slice surface area required because every
|
||||
slice needs it, or because duplicating it four times would be worse than
|
||||
@@ -166,7 +166,7 @@ sharing it
|
||||
- Kept deliberately small: this is the exception to "no slice depends on
|
||||
another slice," not a general-purpose dumping ground
|
||||
|
||||
#### Infrastructure Layer
|
||||
#### Infrastructure layer
|
||||
|
||||
**Purpose**: Technical capabilities and external integrations, shared by
|
||||
whichever slices need them
|
||||
@@ -185,14 +185,14 @@ whichever slices need them
|
||||
**Dependencies**:
|
||||
|
||||
- Domain entities
|
||||
- External libraries (ADO.NET, JWT, Argon2, MailKit, etc.)
|
||||
- External libraries (ADO.NET, JWT, Argon2, MailKit, and so on)
|
||||
|
||||
**Rules**:
|
||||
|
||||
- Implements technical concerns only, no business logic
|
||||
- Reusable across slices
|
||||
|
||||
#### Domain Layer (`Domain.Entities`)
|
||||
#### Domain layer (`Domain.Entities`)
|
||||
|
||||
**Purpose**: Core business entities and models
|
||||
|
||||
@@ -213,9 +213,9 @@ whichever slices need them
|
||||
- No infrastructure references
|
||||
- Represents business concepts
|
||||
|
||||
### Design Patterns
|
||||
### Design patterns
|
||||
|
||||
#### Vertical Slice + MediatR
|
||||
#### Vertical slice + MediatR
|
||||
|
||||
**Purpose**: Organize code by feature instead of by technical layer, so
|
||||
everything needed to understand or change one capability lives in one
|
||||
@@ -223,9 +223,9 @@ project
|
||||
|
||||
**Implementation**:
|
||||
|
||||
- Each HTTP write operation is a `Command` (e.g. `CreateBreweryCommand` in
|
||||
`Features.Breweries/Commands/CreateBrewery/`); each read operation is a
|
||||
`Query` (e.g. `GetBreweryByIdQuery`)
|
||||
- Each HTTP write operation is a `Command` (for example, `CreateBreweryCommand`
|
||||
in `Features.Breweries/Commands/CreateBrewery/`); each read operation is a
|
||||
`Query` (for example, `GetBreweryByIdQuery`)
|
||||
- Controllers bind directly to the Command/Query as the request body;
|
||||
there is no separate request DTO + mapping step for writes
|
||||
- A single shared `ValidationBehavior<TRequest,TResponse>`
|
||||
@@ -246,7 +246,7 @@ public class CreateBreweryHandler(IBreweryRepository repository) : IRequestHandl
|
||||
}
|
||||
```
|
||||
|
||||
#### Repository Pattern
|
||||
#### Repository pattern
|
||||
|
||||
**Purpose**: Abstract database access behind interfaces
|
||||
|
||||
@@ -275,7 +275,7 @@ public interface IAuthRepository
|
||||
}
|
||||
```
|
||||
|
||||
#### Dependency Injection
|
||||
#### Dependency injection
|
||||
|
||||
**Purpose**: Loose coupling and testability
|
||||
|
||||
@@ -289,7 +289,7 @@ extension method that registers its repository and slice-internal services
|
||||
- Singleton: `ISqlConnectionFactory`
|
||||
- Transient: Utilities, helpers
|
||||
|
||||
#### SQL-First Approach
|
||||
#### SQL-first approach
|
||||
|
||||
**Purpose**: Push complex logic into the database
|
||||
|
||||
@@ -306,9 +306,9 @@ extension method that registers its repository and slice-internal services
|
||||
- `USP_GetUserAccountByUsername` - User lookup
|
||||
- `USP_RotateUserCredential` - Password update
|
||||
|
||||
## Frontend Architecture
|
||||
## Frontend architecture
|
||||
|
||||
### Active Website (`web/frontend`)
|
||||
### Active website (`web/frontend`)
|
||||
|
||||
The current website is a React Router 7 application with server-side rendering
|
||||
enabled.
|
||||
@@ -327,7 +327,7 @@ web/frontend/
|
||||
└── package.json Frontend scripts and dependencies
|
||||
```
|
||||
|
||||
### Frontend Responsibilities
|
||||
### Frontend responsibilities
|
||||
|
||||
- Render the auth demo and theme guide routes
|
||||
- Manage cookie-backed website session state
|
||||
@@ -335,7 +335,7 @@ web/frontend/
|
||||
- Provide shared UI building blocks for forms, navigation, themes, and toasts
|
||||
- Supply Storybook documentation and browser-based component verification
|
||||
|
||||
### Theme System
|
||||
### Theme system
|
||||
|
||||
The active website uses semantic DaisyUI theme tokens backed by four Biergarten
|
||||
themes:
|
||||
@@ -348,15 +348,15 @@ themes:
|
||||
All component styling should prefer semantic tokens such as `primary`,
|
||||
`success`, `surface`, and `highlight` instead of hard-coded color values.
|
||||
|
||||
### Legacy Frontend
|
||||
### Legacy frontend
|
||||
|
||||
The previous Next.js frontend has been archived at `archive/next-js-web-app/`
|
||||
for reference only. Active product and engineering documentation should point
|
||||
to `web/frontend`.
|
||||
|
||||
## Security Architecture
|
||||
## Security architecture
|
||||
|
||||
### Authentication Flow
|
||||
### Authentication flow
|
||||
|
||||
1. **Registration**:
|
||||
- User submits credentials
|
||||
@@ -375,7 +375,7 @@ to `web/frontend`.
|
||||
- Middleware validates token
|
||||
- Request proceeds if valid
|
||||
|
||||
### Password Security
|
||||
### Password security
|
||||
|
||||
**Algorithm**: Argon2id
|
||||
|
||||
@@ -385,7 +385,7 @@ to `web/frontend`.
|
||||
- Salt: 128-bit (16 bytes)
|
||||
- Hash: 256-bit (32 bytes)
|
||||
|
||||
### JWT Tokens
|
||||
### JWT tokens
|
||||
|
||||
**Algorithm**: HS256 (HMAC-SHA256)
|
||||
|
||||
@@ -409,9 +409,9 @@ to `web/frontend`.
|
||||
}
|
||||
```
|
||||
|
||||
## Database Architecture
|
||||
## Database architecture
|
||||
|
||||
### SQL-First Philosophy
|
||||
### SQL-first philosophy
|
||||
|
||||
**Principles**:
|
||||
|
||||
@@ -427,7 +427,7 @@ to `web/frontend`.
|
||||
- Version-controlled schema (migrations)
|
||||
- Easier query profiling and tuning
|
||||
|
||||
### Migration Strategy
|
||||
### Migration strategy
|
||||
|
||||
**Tool**: DbUp
|
||||
|
||||
@@ -448,7 +448,7 @@ scripts/
|
||||
└── ...
|
||||
```
|
||||
|
||||
### Data Seeding
|
||||
### Data seeding
|
||||
|
||||
**Purpose**: Populate development/test databases
|
||||
|
||||
@@ -460,9 +460,9 @@ scripts/
|
||||
- Test user accounts
|
||||
- Sample breweries (future)
|
||||
|
||||
## Deployment Architecture
|
||||
## Deployment architecture
|
||||
|
||||
### Docker Containerization
|
||||
### Docker containerization
|
||||
|
||||
**Container Structure**:
|
||||
|
||||
@@ -479,10 +479,10 @@ scripts/
|
||||
|
||||
For details, see [Docker Guide](website/docker.md).
|
||||
|
||||
### Health Checks
|
||||
### Health checks
|
||||
|
||||
**SQL Server**: Validates database connectivity **API**: Checks service health
|
||||
and dependencies
|
||||
- **SQL Server**: Validates database connectivity
|
||||
- **API**: Checks service health and dependencies
|
||||
|
||||
**Configuration**:
|
||||
|
||||
@@ -494,9 +494,9 @@ healthcheck:
|
||||
start_period: 30s
|
||||
```
|
||||
|
||||
## Testing Architecture
|
||||
## Testing architecture
|
||||
|
||||
### Test Pyramid
|
||||
### Test pyramid
|
||||
|
||||
```
|
||||
┌──────────────┐
|
||||
|
||||
@@ -1,28 +1,28 @@
|
||||
# Ethics, Bias, and Known Issues
|
||||
# Ethics, bias, and known issues
|
||||
|
||||
This document covers the ethical context of the Biergarten Pipeline's output,
|
||||
the model's biases, and known issues including hallucinated brewing science and
|
||||
low-resource language failures.
|
||||
|
||||
> Note that all testing was used using `google_gemma-4-E4B-it-Q6_K.gguf`.
|
||||
> All testing described below used `google_gemma-4-E4B-it-Q6_K.gguf`.
|
||||
|
||||
## Table of Contents
|
||||
## Table of contents
|
||||
|
||||
- [What This Dataset Is](#what-this-dataset-is)
|
||||
- [What This Dataset Is Not](#what-this-dataset-is-not)
|
||||
- [Model Bias and Language Quality](#model-bias-and-language-quality)
|
||||
- [Western and Eurocentric Lens](#western-and-eurocentric-lens)
|
||||
- [Wikipedia Enrichment](#wikipedia-enrichment)
|
||||
- [Names-by-Country Dataset](#names-by-country-dataset)
|
||||
- [The "Avoid AI Phrases" Prompt Instruction](#the-avoid-ai-phrases-prompt-instruction)
|
||||
- [Known Issues](#known-issues)
|
||||
- [Hallucinated Brewing Techniques](#hallucinated-brewing-techniques)
|
||||
- [Low-Resource Language Hallucination](#low-resource-language-hallucination)
|
||||
- [Synthetic Postal Codes](#synthetic-postal-codes)
|
||||
- [What this dataset is](#what-this-dataset-is)
|
||||
- [What this dataset is not](#what-this-dataset-is-not)
|
||||
- [Model bias and language quality](#model-bias-and-language-quality)
|
||||
- [Western and Eurocentric lens](#western-and-eurocentric-lens)
|
||||
- [Wikipedia enrichment](#wikipedia-enrichment)
|
||||
- [Names-by-country dataset](#names-by-country-dataset)
|
||||
- [The "avoid AI phrases" prompt instruction](#the-avoid-ai-phrases-prompt-instruction)
|
||||
- [Known issues](#known-issues)
|
||||
- [Hallucinated brewing techniques](#hallucinated-brewing-techniques)
|
||||
- [Low-resource language hallucination](#low-resource-language-hallucination)
|
||||
- [Synthetic postal codes](#synthetic-postal-codes)
|
||||
|
||||
---
|
||||
|
||||
## What This Dataset Is
|
||||
## What this dataset is
|
||||
|
||||
This is AI-generated fixture data for a proof-of-concept version of The
|
||||
Biergarten App. Anyone who interacts with an application seeded from this
|
||||
@@ -30,29 +30,29 @@ pipeline must be told upfront that the content is AI-generated.
|
||||
|
||||
---
|
||||
|
||||
## What This Dataset Is Not
|
||||
## What this dataset is not
|
||||
|
||||
The pipeline is not intended to produce accurate brewing science, faithful
|
||||
cultural representation, or reliable local-language text. Hallucinations such as
|
||||
invented fermentation techniques, or incoherent local-language prose, are
|
||||
expected, observed, and partially documented in [Known Issues](#known-issues)
|
||||
expected, observed, and partially documented in [Known issues](#known-issues)
|
||||
below.
|
||||
|
||||
Human control sits at the context layer (i.e. prompt design, Wikipedia
|
||||
Human control sits at the context layer (that is, prompt design, Wikipedia
|
||||
enrichment). Statistical output shapes in future pipeline stages (check-in
|
||||
distributions, rating skews, activity profiles) will be handled the same way.
|
||||
|
||||
**Treat this data as an exercise in prompt engineering and model behaviour, not
|
||||
as a source of truth for brewing techniques or cultural representation.**
|
||||
|
||||
**Natural language processing, although a powerful tool for data analysis and
|
||||
generation is to be taken with scrutiny. Human language is not simply just data
|
||||
points to be analyzed, but it also carries deep cultural and human meaning that
|
||||
artificial intelligence is incapable of.**
|
||||
**Natural language processing is a powerful tool for data analysis and
|
||||
generation, but its output should be treated with scrutiny. Human language is
|
||||
not simply data points to be analyzed — it carries cultural and human meaning
|
||||
that artificial intelligence cannot capture.**
|
||||
|
||||
---
|
||||
|
||||
## Model Bias and Language Quality
|
||||
## Model bias and language quality
|
||||
|
||||
The underlying model's training biases surface within this pipeline. Output
|
||||
quality tracks with how well a language is represented in the training corpus:
|
||||
@@ -67,9 +67,9 @@ large language models trained predominantly on English-language
|
||||
material.[^llm-bias]
|
||||
|
||||
Mitigations are documented in
|
||||
[Known Issues: Low-Resource Language Hallucination](#low-resource-language-hallucination).
|
||||
[Known issues: Low-resource language hallucination](#low-resource-language-hallucination).
|
||||
|
||||
### Western and Eurocentric Lens
|
||||
### Western and Eurocentric lens
|
||||
|
||||
The model's training data skews heavily Western and North American. When
|
||||
generating brewery descriptions for Kinshasa, Abidjan, or Osaka, for example, it
|
||||
@@ -81,19 +81,19 @@ some generation in city-specific material, but it does not eliminate the skew.
|
||||
|
||||
---
|
||||
|
||||
## Wikipedia Enrichment
|
||||
## Wikipedia enrichment
|
||||
|
||||
City and beer context is fetched from the Wikipedia API. Wikipedia text is
|
||||
co-licensed under the **Creative Commons Attribution-ShareAlike 4.0
|
||||
International License (CC BY-SA 4.0)** and the **GNU Free Documentation License
|
||||
(GFDL)**.[^wp-license]
|
||||
International License (CC BY-SA 4.0)**[^cc-sa] and the **GNU Free Documentation
|
||||
License (GFDL)**.[^wp-license]
|
||||
|
||||
Wikipedia's own accuracy limitations and editorial biases can propagate into
|
||||
generated descriptions.
|
||||
|
||||
---
|
||||
|
||||
## Names-by-Country Dataset
|
||||
## Names-by-country dataset
|
||||
|
||||
`tooling/pipeline/forenames-by-country.json` and `surnames-by-country.json`
|
||||
(used to sample a `Name` per ISO 3166-1 country code for user generation) are
|
||||
@@ -118,7 +118,7 @@ same way brewery generation skips cities whose enrichment lookup fails.
|
||||
|
||||
---
|
||||
|
||||
## The "Avoid AI Phrases" Prompt Instruction
|
||||
## The "avoid AI phrases" prompt instruction
|
||||
|
||||
The system prompt instructs the model to avoid common AI-generated phrasing
|
||||
patterns. This is a prompt engineering experiment:
|
||||
@@ -130,14 +130,14 @@ consumers are informed of the AI-generated origin before engagement.
|
||||
|
||||
---
|
||||
|
||||
## Known Issues
|
||||
## Known issues
|
||||
|
||||
### Hallucinated Brewing Techniques
|
||||
### Hallucinated brewing techniques
|
||||
|
||||
When forced by the system prompt to generate a "highly specialized technical
|
||||
brewing detail," the model frequently hallucinates fermentation science and
|
||||
brewing chemistry. While the resulting sounds confident, it will be nonsensical
|
||||
to reader with brewing and/or scientific expertise.
|
||||
brewing chemistry. While the resulting text sounds confident, it is
|
||||
nonsensical to a reader with brewing or scientific expertise.
|
||||
|
||||
Small-parameter models such as Gemma 4 E4B cannot reliably satisfy prompts
|
||||
calling for specialist brewing detail. This is consistent with the **CHOKE**
|
||||
@@ -155,9 +155,9 @@ and factually wrong output.
|
||||
[2026-04-21 15:04:40.258] [info] brewery_description_local="早朝の、たこ焼きや香ばしいイカ焼きの湿った匂いは、いつも乾燥小屋の外にある使用済み麦芽の金属的な匂いと奇妙に混ざり合って近隣に漂います。私たちはこの場所に店を構えることを決めたのです。なぜなら、大阪は決して止まることのない都市であり、商品と味が絶え間なく交換されることで築かれた場所だからです。地元の多くの方々は、信頼できる大規模な淡麗ラガーの味が習慣になっていますが、私たちは発酵の、より深く、より複雑な表現を探求することに関心があります。私たちのベースモルトは、山田錦のような地元の米の品種を意図的に組み込んで作られています。この米を酒ではなく、麦芽として、仕込みの最中にその複雑でバターのような風味を引き出すために使用しています。この組み合わせを、ゆっくりとした嫌気的な熟成プロセスに続けることで、私たちのビールは、慌ただしい市場のスタイルとは一線を画す、深みのある、満足感のある複雑さを持っています。オリジナルの倉庫の重く深紅のレンガ壁は、関西特有の湿気と季節の雨を何十年も吸収し、この地区の絶え間ない動きの正確な物語を語るような古色を帯びています。私たちはこの構造物を、その魅力のためではなく、その回復力とモルタルに込められた地域の歴史の密度ゆえに選びました。私たちの目標は、ただこの力強い交易都市に値する飲み物を提供することだけです。もしメインの通りから離れた静かな場所をお探しなら、新世界近くの脇道にある私たちを探してください。"
|
||||
```
|
||||
|
||||
A review of the following text for brewing techniques reveals several
|
||||
inaccuracies, and no comments could be made on the local-language version due to
|
||||
my own lack of proficiency in Japanese:
|
||||
A review of the following text for brewing techniques reveals three
|
||||
inaccuracies. No comments are made on the local-language version, since its
|
||||
reviewer does not read Japanese:
|
||||
|
||||
#### 1. "Buttery flavours" framed as a desirable malt-derived flavour
|
||||
|
||||
@@ -191,7 +191,7 @@ fermentable sugars.[^yn-sakeonline]
|
||||
|
||||
**Misleading**
|
||||
|
||||
Anaerobic conditions during packaging and aging are not differentiating
|
||||
Anaerobic conditions during packaging and aging are not a differentiating
|
||||
technique. Anaerobic conditions are the standard baseline for all commercial
|
||||
beer production. Breweries exclude oxygen as a top priority for packaging and
|
||||
shelf stability; published research in _Microbiology Spectrum_ confirms that
|
||||
@@ -200,7 +200,7 @@ Professional packaging lines use CO_2 purges and closed transfers specifically
|
||||
to maintain this state.[^packaging] Framing anaerobic aging as a distinctive
|
||||
practice is misleading and suggests hallucinated output.
|
||||
|
||||
### Low-Resource Language Hallucination
|
||||
### Low-resource language hallucination
|
||||
|
||||
The generation pipeline passes local language codes to the model to retrieve a
|
||||
translated `description_local`. Output quality is reliable for high-resource
|
||||
@@ -262,9 +262,9 @@ idiomatic phrasing.
|
||||
]
|
||||
```
|
||||
|
||||
This dataset, when fed into the pipeline will often times reason that a local
|
||||
variant of French is needed, but will often times just default to a standardized
|
||||
dialect of French, devoid of any cultural or linguistic nuance.
|
||||
When fed into the pipeline, this dataset often causes the model to reason that
|
||||
a local variant of French is needed, but it often defaults to a standardized
|
||||
dialect of French, without cultural or linguistic nuance.
|
||||
|
||||
For languages such as Welsh (Wales), Māori (Aotearoa/New Zealand), or Sicilian
|
||||
(Sicily, Italy), the model can generate text that looks syntactically plausible
|
||||
@@ -273,7 +273,7 @@ rather than prompt engineering.
|
||||
|
||||
Output sample: [./french-cities.example](french-cities.example)
|
||||
|
||||
#### Proposed Mitigations
|
||||
#### Proposed mitigations
|
||||
|
||||
- **Prevention via allowlist:** introduce a high-resource language allowlist. If
|
||||
a location's code is unlisted, skip `description_local` generation and fall
|
||||
@@ -284,7 +284,7 @@ Output sample: [./french-cities.example](french-cities.example)
|
||||
SQLite schema so downstream applications can filter or flag potentially
|
||||
hallucinated text by language tier.
|
||||
|
||||
### Synthetic Postal Codes
|
||||
### Synthetic postal codes
|
||||
|
||||
When the pipeline is run without `--mocked`, `XegerPostalCodeService` generates
|
||||
a fresh postal code per brewery/user address by reverse-matching one of the
|
||||
@@ -312,7 +312,7 @@ not a source of truth.
|
||||
Simhi, Itay Itzhak, Fazl Barez, Gabriel Stanovsky, Yonatan Belinkov.
|
||||
|
||||
[^llm-bias]:
|
||||
e.g., Blasi et al. (2022), "Systematic Inequalities in Language Technology
|
||||
For example, Blasi et al. (2022), "Systematic Inequalities in Language Technology
|
||||
Performance across the World's Languages," _ACL Anthology_. The pattern is
|
||||
consistent with models trained predominantly on English-language web
|
||||
corpora.
|
||||
|
||||
@@ -14,8 +14,8 @@ deterministic mock.
|
||||
|
||||
## Table of Contents
|
||||
|
||||
- [How It Fits The Main App](#how-it-fits-the-main-app)
|
||||
- [Quick Start](#quick-start)
|
||||
- [How it fits the main app](#how-it-fits-the-main-app)
|
||||
- [Quick start](#quick-start)
|
||||
- [Build](#build)
|
||||
- [Model](#model)
|
||||
- [Run](#run)
|
||||
@@ -24,17 +24,17 @@ deterministic mock.
|
||||
- [Pipeline Stages](#pipeline-stages)
|
||||
- [Key Components](#key-components)
|
||||
- [Runtime Behaviour](#runtime-behaviour)
|
||||
- [Generated Output](#generated-output)
|
||||
- [Tech Stack](#tech-stack)
|
||||
- [Tested Hardware](#tested-hardware)
|
||||
- [Fixture Strategy](#fixture-strategy)
|
||||
- [Repo Layout](#repo-layout)
|
||||
- [Code Tour](#code-tour)
|
||||
- [Next Steps](#next-steps)
|
||||
- [Generated output](#generated-output)
|
||||
- [Tech stack](#tech-stack)
|
||||
- [Tested hardware](#tested-hardware)
|
||||
- [Fixture strategy](#fixture-strategy)
|
||||
- [Repo layout](#repo-layout)
|
||||
- [Code tour](#code-tour)
|
||||
- [Next steps](#next-steps)
|
||||
|
||||
---
|
||||
|
||||
## How It Fits The Main App
|
||||
## How it fits the main app
|
||||
|
||||
The pipeline is a data ingestion layer. It sits outside the web app runtime and
|
||||
produces seed records the app imports at startup or during a dedicated seed
|
||||
@@ -50,7 +50,7 @@ step.
|
||||
|
||||
---
|
||||
|
||||
## Quick Start
|
||||
## Quick start
|
||||
|
||||
### Build
|
||||
|
||||
@@ -99,13 +99,13 @@ Each run writes a fresh dated SQLite file such as
|
||||
--temperature 1.0 --top-p 0.95 --top-k 64 --n-ctx 8192 --seed -1
|
||||
```
|
||||
|
||||
#### CLI Flags
|
||||
#### CLI flags
|
||||
|
||||
| Flag | Purpose |
|
||||
| ------------------ | ---------------------------------------------------------------------------------------------------- |
|
||||
| `--mocked` | Deterministic mock generator, no model required. |
|
||||
| `--model, -m` | Path to a GGUF file. Required unless `--mocked` is set. |
|
||||
| `--prompt-dir` | Directory containing prompt files (e.g. `BREWERY_GENERATION.md`). Required unless `--mocked` is set. |
|
||||
| `--prompt-dir` | Directory containing prompt files (for example, `BREWERY_GENERATION.md`). Required unless `--mocked` is set. |
|
||||
| `--output, -o` | Directory for generated SQLite artifacts. Default: `output`. |
|
||||
| `--log-path` | Path for application logs. Default: `pipeline.log`. |
|
||||
| `--location-count` | Number of cities to sample from `locations.json` per run. Default: `10`. |
|
||||
@@ -176,7 +176,7 @@ output to confirm the fast path was taken.
|
||||
### Run the container
|
||||
|
||||
The container always runs the model-backed path; there is no `--mocked`
|
||||
container mode (use a native build for that — see [Quick Start](#quick-start)).
|
||||
container mode (use a native build for that — see [Quick start](#quick-start)).
|
||||
The entrypoint, `runpod/start.sh`, downloads the GGUF model automatically if it
|
||||
is not already present at the configured path.
|
||||
|
||||
@@ -218,7 +218,7 @@ environment variables listed above to match your run.
|
||||
|
||||
## Architecture
|
||||
|
||||
### Pipeline Stages
|
||||
### Pipeline stages
|
||||
|
||||
| Stage | Implementation |
|
||||
| ------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
@@ -234,7 +234,7 @@ If name sampling, enrichment, or generation fails for a city, that city is
|
||||
skipped and the pipeline continues. `GenerateUsers()` runs before
|
||||
`GenerateBreweries()` in `BiergartenPipelineOrchestrator::Run()`.
|
||||
|
||||
### Key Components
|
||||
### Key components
|
||||
|
||||
- `src/main.cc` — argument parsing and Boost.DI composition root.
|
||||
- `CuratedJsonDataService` — implements `ICuratedDataService`; takes a
|
||||
@@ -260,7 +260,7 @@ skipped and the pipeline continues. `GenerateUsers()` runs before
|
||||
LLM-generated username/bio/activity weight, and a programmatically generated
|
||||
(not LLM-authored) unique email and date of birth.
|
||||
|
||||
### Runtime Behaviour
|
||||
### Runtime behaviour
|
||||
|
||||
`WikipediaEnrichmentService` fetches two Wikipedia extracts per city: a generic
|
||||
"brewing" extract and a "beer in `{country}`" extract. It does not currently
|
||||
@@ -290,17 +290,17 @@ absent from either name fixture (currently `KE`, `SE`, `SG`, `TH`, `VN`, `ZA`)
|
||||
are skipped, the same way a failed enrichment or generation call skips a city —
|
||||
see ETHICS-AND-KNOWN-ISSUES.md's Names-by-Country Dataset section.
|
||||
|
||||
### Process Flow - Activity Diagram
|
||||
### Process flow - activity diagram
|
||||
|
||||

|
||||
|
||||
### Architectural Overview - Class Diagram
|
||||
### Architectural overview - class diagram
|
||||
|
||||

|
||||
|
||||
---
|
||||
|
||||
## Generated Output
|
||||
## Generated output
|
||||
|
||||
Each successful run stores a `BreweryRecord` pair with the source location and a
|
||||
`BreweryResult` payload, and a `UserRecord` pair with the source location and a
|
||||
@@ -328,7 +328,7 @@ SQLite export file named with the current UTC timestamp.
|
||||
The log dump also includes city, country, state or province, ISO subdivision
|
||||
code, latitude, and longitude for each entry.
|
||||
|
||||
### Consumer Data Shape
|
||||
### Consumer data shape
|
||||
|
||||
| Field | Why it matters |
|
||||
| ----------------------------------- | ------------------------------------------------ |
|
||||
@@ -341,7 +341,7 @@ code, latitude, and longitude for each entry.
|
||||
|
||||
---
|
||||
|
||||
## Tech Stack
|
||||
## Tech stack
|
||||
|
||||
- C++20
|
||||
- CMake 3.31+
|
||||
@@ -363,7 +363,7 @@ Silicon; CUDA or HIP/ROCm is detected on Linux when the toolkit is present.
|
||||
|
||||
---
|
||||
|
||||
## Tested Hardware
|
||||
## Tested hardware
|
||||
|
||||
### ARM macOS — M1 Pro
|
||||
|
||||
@@ -398,7 +398,7 @@ Silicon; CUDA or HIP/ROCm is detected on Linux when the toolkit is present.
|
||||
|
||||
---
|
||||
|
||||
## Fixture Strategy
|
||||
## Fixture strategy
|
||||
|
||||
- `--mocked` for stable fixtures, repeatable screenshots, and Storybook runs.
|
||||
`MockCuratedDataService` swaps in for `CuratedJsonDataService`, so no fixture
|
||||
@@ -414,7 +414,7 @@ Silicon; CUDA or HIP/ROCm is detected on Linux when the toolkit is present.
|
||||
|
||||
---
|
||||
|
||||
## Repo Layout
|
||||
## Repo layout
|
||||
|
||||
| Path | Purpose |
|
||||
| -------------------------------------------- | ----------------------------------------------------------- |
|
||||
@@ -431,7 +431,7 @@ Silicon; CUDA or HIP/ROCm is detected on Linux when the toolkit is present.
|
||||
|
||||
---
|
||||
|
||||
## Code Tour
|
||||
## Code tour
|
||||
|
||||
Paths below are relative to `tooling/pipeline/`.
|
||||
|
||||
@@ -450,7 +450,7 @@ Paths below are relative to `tooling/pipeline/`.
|
||||
|
||||
---
|
||||
|
||||
## Next Steps
|
||||
## Next steps
|
||||
|
||||
The pipeline currently produces city-aware brewery and user records and dated
|
||||
SQLite exports. The next passes add additional fixture types so the app can
|
||||
@@ -458,7 +458,7 @@ exercise the full brewery and social domains without live data. For the detailed
|
||||
engineering breakdown of what's needed to reach the architecture in
|
||||
[`diagrams/planned/`](./diagrams/planned/), see [ROADMAP.md](./ROADMAP.md).
|
||||
|
||||
### Testing — Very High Priority
|
||||
### Testing — very high priority
|
||||
|
||||
- Unit test JSON validation and retry logic against malformed, truncated, and
|
||||
empty model outputs.
|
||||
@@ -472,19 +472,19 @@ engineering breakdown of what's needed to reach the architecture in
|
||||
- Confirm the retry path is reachable when the reasoning block consumes
|
||||
available token budget.
|
||||
|
||||
### Beer Generation
|
||||
### Beer generation
|
||||
|
||||
Generate catalog entries with style, ABV, IBU, color, aroma notes, and food
|
||||
pairing hints. Link beers back to breweries and cities. Keep style coverage wide
|
||||
enough to exercise search, sort, and category filters.
|
||||
|
||||
### Check-In System
|
||||
### Check-in system
|
||||
|
||||
Produce timestamped check-in events between users and breweries. Use a J-curve
|
||||
activity profile — a small set of users accounts for most check-ins, the rest
|
||||
appear occasionally. Add bursty behaviour around weekends and travel periods.
|
||||
|
||||
### Beer Ratings
|
||||
### Beer ratings
|
||||
|
||||
Generate rating events with a strong positive skew and a long tail of lower
|
||||
scores. Avoid uniform distributions. Attach timestamps and user IDs so the app
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# Docker Guide
|
||||
# Docker guide
|
||||
|
||||
This document covers Docker deployment, configuration, and troubleshooting for
|
||||
The Biergarten App.
|
||||
@@ -16,7 +16,7 @@ The project uses Docker Compose to orchestrate multiple services:
|
||||
See the [deployment diagram](diagrams-out/deployment.svg) for visual
|
||||
representation.
|
||||
|
||||
## Docker Compose Environments
|
||||
## Docker Compose environments
|
||||
|
||||
### 1. Development (`docker-compose.dev.yaml`)
|
||||
|
||||
@@ -66,7 +66,7 @@ docker compose --env-file web/.env.dev -f web/docker-compose.dev.yaml down -v
|
||||
|
||||
### 2. Testing (`docker-compose.test.yaml`)
|
||||
|
||||
**Purpose**: Automated CI/CD testing in isolated environment
|
||||
**Purpose**: Automated CI/CD testing in an isolated environment
|
||||
|
||||
**Features**:
|
||||
|
||||
@@ -128,18 +128,15 @@ api.core # Production API
|
||||
docker compose --env-file web/.env.prod -f web/docker-compose.prod.yaml up -d
|
||||
```
|
||||
|
||||
## Service Dependencies
|
||||
## Service dependencies
|
||||
|
||||
Docker Compose manages startup order using health checks:
|
||||
|
||||
```mermaid
|
||||
sqlserver (health check)
|
||||
↓
|
||||
database.migrations (completes successfully)
|
||||
↓
|
||||
database.seed (completes successfully)
|
||||
↓
|
||||
api.core / tests (start when ready)
|
||||
graph TD
|
||||
A[sqlserver: health check passes] --> B[database.migrations: completes successfully]
|
||||
B --> C[database.seed: completes successfully]
|
||||
C --> D[api.core / tests: start when ready]
|
||||
```
|
||||
|
||||
**Health Check Example** (SQL Server):
|
||||
@@ -168,7 +165,7 @@ api.core:
|
||||
|
||||
## Volumes
|
||||
|
||||
### Persistent Volumes
|
||||
### Persistent volumes
|
||||
|
||||
**Development**:
|
||||
|
||||
@@ -184,7 +181,7 @@ api.core:
|
||||
- `sqlserverdata-prod` - Production database files
|
||||
- `nuget-cache-prod` - Production NuGet cache
|
||||
|
||||
### Mounted Volumes
|
||||
### Mounted volumes
|
||||
|
||||
**Test Results**:
|
||||
|
||||
@@ -203,7 +200,7 @@ Each environment uses isolated bridge networks:
|
||||
- `testnet` - Testing network (fully isolated)
|
||||
- `prodnet` - Production network
|
||||
|
||||
## Environment Variables
|
||||
## Environment variables
|
||||
|
||||
All containers are configured via environment variables from `.env` files:
|
||||
|
||||
@@ -224,9 +221,9 @@ environment:
|
||||
|
||||
For complete list, see [Environment Variables](environment-variables.md).
|
||||
|
||||
## Common Commands
|
||||
## Common commands
|
||||
|
||||
### View Services
|
||||
### View services
|
||||
|
||||
```bash
|
||||
# Running services
|
||||
@@ -236,7 +233,7 @@ docker compose --env-file web/.env.dev -f web/docker-compose.dev.yaml ps
|
||||
docker ps -a
|
||||
```
|
||||
|
||||
### View Logs
|
||||
### View logs
|
||||
|
||||
```bash
|
||||
# All services
|
||||
@@ -249,17 +246,17 @@ docker compose --env-file web/.env.dev -f web/docker-compose.dev.yaml logs -f ap
|
||||
docker compose --env-file web/.env.dev -f web/docker-compose.dev.yaml logs --tail=100 api.core
|
||||
```
|
||||
|
||||
### Execute Commands in Container
|
||||
### Execute commands in container
|
||||
|
||||
```bash
|
||||
# Interactive shell
|
||||
docker exec -it dev-env-api-core bash
|
||||
|
||||
# Run command
|
||||
docker exec dev-env-sqlserver /opt/mssql-tools18/bin/sqlcmd -S localhost -U sa -P 'password' -C
|
||||
# Run command (replace <db-password> with the value of DB_PASSWORD from .env.dev)
|
||||
docker exec dev-env-sqlserver /opt/mssql-tools18/bin/sqlcmd -S localhost -U sa -P '<db-password>' -C
|
||||
```
|
||||
|
||||
### Restart Services
|
||||
### Restart services
|
||||
|
||||
```bash
|
||||
# Restart all services
|
||||
@@ -272,7 +269,7 @@ docker compose --env-file web/.env.dev -f web/docker-compose.dev.yaml restart ap
|
||||
docker compose --env-file web/.env.dev -f web/docker-compose.dev.yaml up -d --build api.core
|
||||
```
|
||||
|
||||
### Build Images
|
||||
### Build images
|
||||
|
||||
```bash
|
||||
# Build all images
|
||||
@@ -285,7 +282,7 @@ docker compose --env-file web/.env.dev -f web/docker-compose.dev.yaml build api.
|
||||
docker compose --env-file web/.env.dev -f web/docker-compose.dev.yaml build --no-cache
|
||||
```
|
||||
|
||||
### Clean Up
|
||||
### Clean up
|
||||
|
||||
```bash
|
||||
# Stop and remove containers
|
||||
@@ -301,9 +298,9 @@ docker compose --env-file web/.env.dev -f web/docker-compose.dev.yaml down -v --
|
||||
docker system prune -af --volumes
|
||||
```
|
||||
|
||||
## Dockerfile Structure
|
||||
## Dockerfile structure
|
||||
|
||||
### Multi-Stage Build
|
||||
### Multi-stage build
|
||||
|
||||
```dockerfile
|
||||
# Stage 1: Build
|
||||
@@ -321,7 +318,7 @@ COPY --from=build /app/build .
|
||||
ENTRYPOINT ["dotnet", "Project.dll"]
|
||||
```
|
||||
|
||||
## Additional Resources
|
||||
## Additional resources
|
||||
|
||||
- [Docker Compose Documentation](https://docs.docker.com/compose/)
|
||||
- [.NET Docker Images](https://hub.docker.com/_/microsoft-dotnet)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# Environment Variables
|
||||
# Environment variables
|
||||
|
||||
This document covers the active environment variables used by the current
|
||||
Biergarten stack.
|
||||
@@ -11,7 +11,7 @@ The application uses environment variables for:
|
||||
- **React Router website** - API base URL and session signing
|
||||
- **Docker containers** - environment-specific orchestration
|
||||
|
||||
## Configuration Patterns
|
||||
## Configuration patterns
|
||||
|
||||
### Backend (.NET API)
|
||||
|
||||
@@ -30,9 +30,9 @@ Environment-specific `.env` files loaded via `env_file:` in docker-compose.yaml:
|
||||
- `.env.test` - Testing
|
||||
- `.env.prod` - Production
|
||||
|
||||
## Backend Variables (.NET API)
|
||||
## Backend variables (.NET API)
|
||||
|
||||
### Database Connection
|
||||
### Database connection
|
||||
|
||||
**Option 1: Component-Based (Recommended for Docker)**
|
||||
|
||||
@@ -59,7 +59,7 @@ string is built from components.
|
||||
|
||||
**Implementation**: See `DefaultSqlConnectionFactory.cs`
|
||||
|
||||
### JWT Authentication Secrets (Backend)
|
||||
### JSON Web Token (JWT) authentication secrets (backend)
|
||||
|
||||
The backend uses separate secrets for different token types to enable
|
||||
independent key rotation and validation isolation.
|
||||
@@ -80,9 +80,9 @@ WEBSITE_BASE_URL=https://thebiergarten.app # Base URL for the website
|
||||
|
||||
**Security Requirements**:
|
||||
|
||||
- Each secret should be minimum 32 characters
|
||||
- Recommend 127+ characters for production
|
||||
- Generate using cryptographically secure random functions
|
||||
- Use at least 32 characters per secret
|
||||
- Use 127 or more characters in production
|
||||
- Generate secrets with a cryptographically secure random function
|
||||
- Never reuse secrets across token types or environments
|
||||
- Rotate secrets periodically in production
|
||||
|
||||
@@ -110,19 +110,20 @@ openssl rand -base64 127
|
||||
- **Handler**: Microsoft.IdentityModel.JsonWebTokens.JsonWebTokenHandler
|
||||
- **Validation**: Token signature, expiration, and malformed token checks
|
||||
|
||||
### Migration Control
|
||||
### Migration control
|
||||
|
||||
```bash
|
||||
CLEAR_DATABASE=true
|
||||
```
|
||||
|
||||
- **Required**: No
|
||||
- **Default**: false
|
||||
- **Effect**: If "true", drops and recreates database during migrations
|
||||
- **Usage**: Development and testing environments ONLY
|
||||
- **Warning**: NEVER use in production
|
||||
- **Default**: `false`
|
||||
- **Effect**: If set to `true`, drops and recreates the database during
|
||||
migrations
|
||||
- **Usage**: Development and testing environments only
|
||||
- **Warning**: Never use in production
|
||||
|
||||
### ASP.NET Core Configuration
|
||||
### ASP.NET Core configuration
|
||||
|
||||
```bash
|
||||
ASPNETCORE_ENVIRONMENT=Development # Development, Production, Staging
|
||||
@@ -130,7 +131,7 @@ ASPNETCORE_URLS=http://0.0.0.0:8080 # Binding address and port
|
||||
DOTNET_RUNNING_IN_CONTAINER=true # Flag for container execution
|
||||
```
|
||||
|
||||
## Frontend Variables (`web/frontend`)
|
||||
## Frontend variables (`web/frontend`)
|
||||
|
||||
The active website does not use the old Next.js/Prisma environment model. Its
|
||||
core runtime variables are:
|
||||
@@ -141,7 +142,7 @@ SESSION_SECRET=<generated-secret> # Cookie session signing secret
|
||||
NODE_ENV=development # Standard Node runtime mode
|
||||
```
|
||||
|
||||
### Frontend Variable Details
|
||||
### Frontend variable details
|
||||
|
||||
#### `API_BASE_URL`
|
||||
|
||||
@@ -163,7 +164,7 @@ NODE_ENV=development # Standard Node runtime mode
|
||||
- **Typical values**: `development`, `production`, `test`
|
||||
- **Purpose**: Controls secure cookie behavior and runtime mode
|
||||
|
||||
### SMTP Configuration (Backend)
|
||||
### SMTP configuration (backend)
|
||||
|
||||
Read by `Infrastructure.Email/SmtpEmailProvider.cs` for sending confirmation
|
||||
and account emails.
|
||||
@@ -183,11 +184,11 @@ SMTP_FROM_NAME=The Biergarten App # Optional, defaults to "The Biergarten
|
||||
- **Local dev**: point at the `mailpit` Docker service (SMTP on port 1025, web
|
||||
UI on http://localhost:8025)
|
||||
- **Production**: point at a real provider (SendGrid, Mailgun, Amazon SES,
|
||||
etc.)
|
||||
and so on)
|
||||
|
||||
## Docker-Specific Variables
|
||||
## Docker-specific variables
|
||||
|
||||
### SQL Server Container
|
||||
### SQL Server container
|
||||
|
||||
```bash
|
||||
SA_PASSWORD=YourStrong!Passw0rd # SQL Server SA password
|
||||
@@ -201,9 +202,9 @@ MSSQL_PID=Express # SQL Server edition (Express, Developer, Ente
|
||||
- Uppercase, lowercase, digits, and special characters
|
||||
- Maps to `DB_PASSWORD` for application containers
|
||||
|
||||
## Environment File Structure
|
||||
## Environment file structure
|
||||
|
||||
### Backend/Docker (`web/` Directory)
|
||||
### Backend/Docker (`web/` directory)
|
||||
|
||||
```
|
||||
web/.env.example # Template (tracked in Git)
|
||||
@@ -219,7 +220,7 @@ cp web/.env.example web/.env.dev
|
||||
# Edit web/.env.dev with your values
|
||||
```
|
||||
|
||||
## Legacy Frontend Variables
|
||||
## 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
|
||||
@@ -231,7 +232,7 @@ reference only and is not run as part of the active stack.
|
||||
- `web/docker-compose.test.yaml` → `web/.env.test`
|
||||
- `web/docker-compose.prod.yaml` → `web/.env.prod`
|
||||
|
||||
## Variable Reference Table
|
||||
## Variable reference table
|
||||
|
||||
| Variable | Backend | Frontend | Docker | Required | Notes |
|
||||
| ----------------------------- | :-----: | :------: | :----: | :------: | -------------------------- |
|
||||
@@ -268,7 +269,7 @@ reference only and is not run as part of the active stack.
|
||||
|
||||
## Validation
|
||||
|
||||
### Backend Validation
|
||||
### Backend validation
|
||||
|
||||
Variables are validated at startup:
|
||||
|
||||
@@ -279,7 +280,7 @@ Variables are validated at startup:
|
||||
"minimum 32 characters" guidance above is a recommendation, not an
|
||||
enforced check
|
||||
|
||||
### Frontend Validation
|
||||
### Frontend validation
|
||||
|
||||
The active website relies on runtime defaults for local development and the
|
||||
surrounding server environment in deployed environments.
|
||||
@@ -288,9 +289,9 @@ surrounding server environment in deployed environments.
|
||||
- `SESSION_SECRET` falls back to a development-only local secret
|
||||
- `NODE_ENV` controls secure cookie behavior
|
||||
|
||||
## Example Configuration Files
|
||||
## Example configuration files
|
||||
|
||||
### `.env.dev` (Backend/Docker)
|
||||
### `.env.dev` (backend/Docker)
|
||||
|
||||
```bash
|
||||
# Database
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# Getting Started
|
||||
# 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`.
|
||||
@@ -10,16 +10,16 @@ in `web/backend` and the active React Router frontend in `web/frontend`.
|
||||
- **Docker Desktop** or equivalent Docker Engine setup
|
||||
- **Java 8+** if you want to regenerate PlantUML diagrams
|
||||
|
||||
## Recommended Path: Docker for Backend, Node for Frontend
|
||||
## Recommended path: Docker for backend, Node for frontend
|
||||
|
||||
### 1. Clone the Repository
|
||||
### 1. Clone the repository
|
||||
|
||||
```bash
|
||||
git clone <repository-url>
|
||||
git clone https://github.com/aaronpo97/the-biergarten-app
|
||||
cd the-biergarten-app
|
||||
```
|
||||
|
||||
### 2. Configure Backend Environment Variables
|
||||
### 2. Configure backend environment variables
|
||||
|
||||
```bash
|
||||
cp web/.env.example web/.env.dev
|
||||
@@ -40,7 +40,7 @@ WEBSITE_BASE_URL=http://localhost:3000
|
||||
|
||||
See [Environment Variables](environment-variables.md) for the full list.
|
||||
|
||||
### 3. Start the Backend Stack
|
||||
### 3. Start the backend stack
|
||||
|
||||
```bash
|
||||
docker compose --env-file web/.env.dev -f web/docker-compose.dev.yaml up -d
|
||||
@@ -54,7 +54,7 @@ Available endpoints:
|
||||
- Health Check: http://localhost:8080/health
|
||||
- Mailpit UI: http://localhost:8025
|
||||
|
||||
### 4. Start the Active Frontend
|
||||
### 4. Start the active frontend
|
||||
|
||||
```bash
|
||||
cd web/frontend
|
||||
@@ -69,7 +69,7 @@ Required frontend runtime variables for local work:
|
||||
- `API_BASE_URL` - Base URL for the .NET API
|
||||
- `SESSION_SECRET` - Cookie session signing secret for the website server
|
||||
|
||||
### 5. Optional: Run Storybook
|
||||
### 5. Optional: run Storybook
|
||||
|
||||
```bash
|
||||
cd web/frontend
|
||||
@@ -78,7 +78,7 @@ npm run storybook
|
||||
|
||||
Storybook runs at http://localhost:6006 by default.
|
||||
|
||||
## Useful Commands
|
||||
## Useful commands
|
||||
|
||||
### Backend
|
||||
|
||||
@@ -99,11 +99,11 @@ npm run test:storybook
|
||||
npm run test:storybook:playwright
|
||||
```
|
||||
|
||||
## Manual Backend Setup
|
||||
## Manual backend setup
|
||||
|
||||
If you do not want to use Docker, you can run the backend locally.
|
||||
|
||||
### 1. Set Environment Variables
|
||||
### 1. Set environment variables
|
||||
|
||||
```bash
|
||||
export DB_CONNECTION_STRING="Server=localhost,1433;Database=Biergarten;User Id=sa;Password=YourStrong!Passw0rd;TrustServerCertificate=True;"
|
||||
@@ -113,7 +113,7 @@ export CONFIRMATION_TOKEN_SECRET="<generated>"
|
||||
export WEBSITE_BASE_URL="http://localhost:3000"
|
||||
```
|
||||
|
||||
### 2. Run Migrations and Seed
|
||||
### 2. Run migrations and seed
|
||||
|
||||
```bash
|
||||
cd web/backend
|
||||
@@ -127,12 +127,12 @@ dotnet run --project Database/Database.Seed/Database.Seed.csproj
|
||||
dotnet run --project API/API.Core/API.Core.csproj
|
||||
```
|
||||
|
||||
## Legacy Frontend Note
|
||||
## Legacy frontend note
|
||||
|
||||
The previous Next.js frontend lives in `archive/next-js-web-app/` for reference
|
||||
only and is not the active website.
|
||||
|
||||
## Next Steps
|
||||
## Next steps
|
||||
|
||||
- Review [Architecture](../architecture.md)
|
||||
- Run backend and frontend checks from [Testing](testing.md)
|
||||
|
||||
@@ -19,7 +19,7 @@ The project uses a multi-layered testing approach across backend and frontend:
|
||||
- **Storybook Playwright suite** - Browser checks against Storybook-rendered
|
||||
components
|
||||
|
||||
## Running Tests with Docker (Recommended)
|
||||
## Running tests with Docker (recommended)
|
||||
|
||||
The easiest way to run all tests is using Docker Compose, which sets up an
|
||||
isolated test environment:
|
||||
@@ -37,7 +37,7 @@ This command:
|
||||
5. Exports results to `./test-results/`
|
||||
6. Exits when tests complete
|
||||
|
||||
### View Test Results
|
||||
### View test results
|
||||
|
||||
```bash
|
||||
# List test result files
|
||||
@@ -51,18 +51,18 @@ cat test-results/Features.UserManagement.Tests.trx
|
||||
cat test-results/Features.Emails.Tests.trx
|
||||
```
|
||||
|
||||
### Clean Up
|
||||
### Clean up
|
||||
|
||||
```bash
|
||||
# Remove test containers and volumes
|
||||
docker compose --env-file web/.env.test -f web/docker-compose.test.yaml down -v
|
||||
```
|
||||
|
||||
## Running Tests Locally
|
||||
## Running tests locally
|
||||
|
||||
You can run individual test projects locally without Docker:
|
||||
|
||||
### Integration Tests (API.Specs)
|
||||
### Integration tests (API.Specs)
|
||||
|
||||
```bash
|
||||
cd web/backend
|
||||
@@ -75,7 +75,7 @@ dotnet test API/API.Specs/API.Specs.csproj
|
||||
- Database migrated and seeded
|
||||
- Environment variables set (DB connection, JWT secret)
|
||||
|
||||
### Feature Slice Unit Tests
|
||||
### Feature slice unit tests
|
||||
|
||||
Each feature slice has its own test project, covering its command/query
|
||||
handlers and repository:
|
||||
@@ -96,7 +96,7 @@ for proj in Features/*.Tests; do dotnet test "$proj"; done
|
||||
- No database required (handlers use Moq; repository tests use DbMocker to
|
||||
simulate SQL Server responses)
|
||||
|
||||
### Frontend Storybook Tests
|
||||
### Frontend Storybook tests
|
||||
|
||||
```bash
|
||||
cd web/frontend
|
||||
@@ -110,7 +110,7 @@ npm run test:storybook
|
||||
toasts, and the theme gallery
|
||||
- Runs in browser mode via Vitest and Storybook integration
|
||||
|
||||
### Frontend Playwright Storybook Tests
|
||||
### Frontend Playwright Storybook tests
|
||||
|
||||
```bash
|
||||
cd web/frontend
|
||||
@@ -125,9 +125,9 @@ npm run test:storybook:playwright
|
||||
- The command will start or reuse the Storybook server defined in
|
||||
`playwright.storybook.config.ts`
|
||||
|
||||
## Test Coverage
|
||||
## Test coverage
|
||||
|
||||
### Current Coverage
|
||||
### Current coverage
|
||||
|
||||
**Features.Auth.Tests**:
|
||||
|
||||
@@ -160,7 +160,7 @@ npm run test:storybook:playwright
|
||||
- Theme gallery rendering across Biergarten themes
|
||||
- Toast interactions and themed notification display
|
||||
|
||||
### Planned Coverage
|
||||
### Planned coverage
|
||||
|
||||
- [ ] Password reset functionality
|
||||
- [ ] Beer post operations
|
||||
@@ -168,7 +168,7 @@ npm run test:storybook:playwright
|
||||
- [ ] Image upload service
|
||||
- [ ] Frontend route integration coverage beyond Storybook stories
|
||||
|
||||
## Testing Frameworks & Tools
|
||||
## Testing frameworks and tools
|
||||
|
||||
### xUnit
|
||||
|
||||
@@ -200,9 +200,9 @@ npm run test:storybook:playwright
|
||||
- Simulates SQL Server responses
|
||||
- No real database required for unit tests
|
||||
|
||||
## Test Structure
|
||||
## Test structure
|
||||
|
||||
### API.Specs (Integration Tests)
|
||||
### API.Specs (integration tests)
|
||||
|
||||
```
|
||||
API.Specs/
|
||||
@@ -260,9 +260,9 @@ Each of the other three slices (`Features.Breweries.Tests`,
|
||||
`Features.UserManagement.Tests`, `Features.Emails.Tests`) follows the same
|
||||
shape: a `Commands/`/`Queries/` folder with one test file per handler.
|
||||
|
||||
## Writing Tests
|
||||
## Writing tests
|
||||
|
||||
### Unit Test Example (xUnit)
|
||||
### Unit test example (xUnit)
|
||||
|
||||
```csharp
|
||||
public class LoginHandlerTests
|
||||
@@ -287,7 +287,7 @@ public class LoginHandlerTests
|
||||
}
|
||||
```
|
||||
|
||||
### Integration Test Example (Reqnroll)
|
||||
### Integration test example (Reqnroll)
|
||||
|
||||
```gherkin
|
||||
Scenario: User login with valid credentials
|
||||
@@ -297,7 +297,7 @@ Scenario: User login with valid credentials
|
||||
And the response should contain a JWT token
|
||||
```
|
||||
|
||||
## Continuous Integration
|
||||
## Continuous integration
|
||||
|
||||
Tests run automatically in CI/CD pipelines using the test Docker Compose
|
||||
configuration:
|
||||
@@ -326,7 +326,7 @@ npm run test:storybook:playwright
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Tests Failing Due to Database Connection
|
||||
### Tests failing due to database connection
|
||||
|
||||
Ensure SQL Server is running and environment variables are set:
|
||||
|
||||
@@ -334,12 +334,12 @@ Ensure SQL Server is running and environment variables are set:
|
||||
docker compose --env-file web/.env.test -f web/docker-compose.test.yaml ps
|
||||
```
|
||||
|
||||
### Port Conflicts
|
||||
### Port conflicts
|
||||
|
||||
If port 1433 is in use, stop other SQL Server instances or modify the port in
|
||||
`docker-compose.test.yaml`.
|
||||
|
||||
### Stale Test Data
|
||||
### Stale test data
|
||||
|
||||
Clean up test database:
|
||||
|
||||
@@ -347,13 +347,13 @@ Clean up test database:
|
||||
docker compose --env-file web/.env.test -f web/docker-compose.test.yaml down -v
|
||||
```
|
||||
|
||||
### View Container Logs
|
||||
### View container logs
|
||||
|
||||
```bash
|
||||
docker compose --env-file web/.env.test -f web/docker-compose.test.yaml logs <service-name>
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
## Best practices
|
||||
|
||||
1. **Isolation**: Each test should be independent and not rely on other tests
|
||||
2. **Cleanup**: Use fixtures and dispose patterns for resource cleanup
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
# Token Validation Architecture
|
||||
# Token validation architecture
|
||||
|
||||
## Overview
|
||||
|
||||
The Core project validates JWTs across three token types:
|
||||
The Core project validates JSON Web Tokens (JWTs) across three token types:
|
||||
|
||||
- **Access Tokens**: Short-lived (1 hour) tokens for API authentication
|
||||
- **Refresh Tokens**: Long-lived (21 days) tokens for obtaining new access
|
||||
@@ -12,7 +12,7 @@ The Core project validates JWTs across three token types:
|
||||
|
||||
## Components
|
||||
|
||||
### Infrastructure Layer
|
||||
### Infrastructure layer
|
||||
|
||||
#### [ITokenInfrastructure](../../web/backend/Infrastructure/Infrastructure.Jwt/ITokenInfrastructure.cs)
|
||||
|
||||
@@ -30,7 +30,7 @@ Low-level JWT operations.
|
||||
- Algorithm: HS256 (HMAC-SHA256)
|
||||
- Validates token lifetime, signature, and well-formedness
|
||||
|
||||
### Features.Auth Slice
|
||||
### Features.Auth slice
|
||||
|
||||
#### [ITokenService](../../web/backend/Features/Features.Auth/Services/ITokenService.cs)
|
||||
|
||||
@@ -69,7 +69,7 @@ registered by the host (`API.Core/Program.cs`) since
|
||||
`JwtAuthenticationHandler` (host-level auth middleware) also depends on
|
||||
it directly.
|
||||
|
||||
### Integration Points
|
||||
### Integration points
|
||||
|
||||
#### [ConfirmUserHandler](../../web/backend/Features/Features.Auth/Commands/ConfirmUser/ConfirmUserHandler.cs)
|
||||
|
||||
@@ -112,9 +112,9 @@ it directly.
|
||||
- `POST /api/auth/confirm/resend?userId=...` - Resend the confirmation email
|
||||
- `POST /api/auth/refresh` - Refresh access token
|
||||
|
||||
## Validation Security
|
||||
## Validation security
|
||||
|
||||
### Token Secrets
|
||||
### Token secrets
|
||||
|
||||
Three independent secrets enable:
|
||||
|
||||
@@ -130,7 +130,7 @@ REFRESH_TOKEN_SECRET=... # Signs 21-day refresh tokens
|
||||
CONFIRMATION_TOKEN_SECRET=... # Signs 30-minute confirmation tokens
|
||||
```
|
||||
|
||||
### Validation Checks
|
||||
### Validation checks
|
||||
|
||||
Each token is validated for:
|
||||
|
||||
@@ -139,7 +139,7 @@ Each token is validated for:
|
||||
3. **Claims Presence** - Required claims (Sub, UniqueName) must be present
|
||||
4. **Claims Format** - UserId claim must be a valid GUID
|
||||
|
||||
### Error Handling
|
||||
### Error handling
|
||||
|
||||
Validation failures return HTTP 401 Unauthorized:
|
||||
|
||||
@@ -148,9 +148,9 @@ Validation failures return HTTP 401 Unauthorized:
|
||||
- Missing claims → "Invalid token"
|
||||
- Malformed claims → "Invalid token"
|
||||
|
||||
## Token Lifecycle
|
||||
## Token lifecycle
|
||||
|
||||
### Access Token Lifecycle
|
||||
### Access token lifecycle
|
||||
|
||||
1. **Generation**: During login (1-hour validity)
|
||||
2. **Usage**: Included in Authorization header on API requests
|
||||
@@ -158,16 +158,16 @@ Validation failures return HTTP 401 Unauthorized:
|
||||
4. **Expiration**: Token becomes invalid after 1 hour
|
||||
5. **Refresh**: Use refresh token to obtain new access token
|
||||
|
||||
### Refresh Token Lifecycle
|
||||
### Refresh token lifecycle
|
||||
|
||||
1. **Generation**: During login (21-day validity)
|
||||
2. **Storage**: Client-side (secure storage)
|
||||
3. **Usage**: Posted to `/api/auth/refresh` endpoint
|
||||
4. **Validation**: Validated by RefreshTokenService
|
||||
4. **Validation**: Validated by `ITokenService.RefreshTokenAsync()`
|
||||
5. **Rotation**: New refresh token issued on successful refresh
|
||||
6. **Expiration**: Token becomes invalid after 21 days
|
||||
|
||||
### Confirmation Token Lifecycle
|
||||
### Confirmation token lifecycle
|
||||
|
||||
1. **Generation**: During user registration (30-minute validity)
|
||||
2. **Delivery**: Emailed to user in confirmation link
|
||||
@@ -180,7 +180,7 @@ Validation failures return HTTP 401 Unauthorized:
|
||||
|
||||
All of the following live in `Features.Auth.Tests`.
|
||||
|
||||
### Unit Tests
|
||||
### Unit tests
|
||||
|
||||
**Services/TokenServiceValidationTests.cs**
|
||||
|
||||
@@ -210,7 +210,7 @@ All of the following live in `Features.Auth.Tests`.
|
||||
- Sends a fresh confirmation email when the user exists and is unverified
|
||||
- No-ops when the user doesn't exist or is already verified
|
||||
|
||||
### BDD Tests (Reqnroll)
|
||||
### BDD tests (Reqnroll)
|
||||
|
||||
**TokenRefresh.feature**
|
||||
|
||||
@@ -230,19 +230,19 @@ All of the following live in `Features.Auth.Tests`.
|
||||
- Invalid/expired access token rejection
|
||||
- Token type mismatch (refresh used as access token)
|
||||
|
||||
## Future Enhancements
|
||||
## Future enhancements
|
||||
|
||||
### Stretch Goals
|
||||
### Stretch goals
|
||||
|
||||
1. **Middleware for Access Token Validation**
|
||||
- Automatically validate access tokens on protected routes
|
||||
- Populate HttpContext.User from token claims
|
||||
- Return 401 for invalid/missing tokens
|
||||
|
||||
2. **Token Blacklisting**
|
||||
- Implement token revocation (e.g., on logout)
|
||||
- Store blacklisted tokens in cache/database
|
||||
- Check blacklist during validation
|
||||
2. **Token Denylisting**
|
||||
- Implement token revocation (for example, on logout)
|
||||
- Store denylisted tokens in cache/database
|
||||
- Check the denylist during validation
|
||||
|
||||
3. **Refresh Token Rotation Strategy**
|
||||
- Detect token reuse (replay attacks)
|
||||
|
||||
Reference in New Issue
Block a user