mirror of
https://github.com/aaronpo97/the-biergarten-app.git
synced 2026-10-15 01:54:43 +00:00
Documentation updates (#236)
Remove vague or flowery language, use Simplified Technical English whenever possible
This commit is contained in:
55
README.md
55
README.md
@@ -1,20 +1,24 @@
|
|||||||
# The Biergarten App
|
# The Biergarten App
|
||||||
|
|
||||||
The Biergarten App is a full-stack directory and discovery platform for
|
The Biergarten App is a full-stack directory and discovery platform for
|
||||||
breweries. It features a robust user authentication system, a searchable
|
breweries. It provides:
|
||||||
database of brewery locations, and a custom offline data-generation pipeline
|
|
||||||
that uses LLMs (Llama.cpp) and Wikipedia to synthesize realistic seed data.
|
|
||||||
|
|
||||||
It features:
|
- JWT-based user authentication, with email confirmation and refresh-token
|
||||||
|
rotation
|
||||||
|
- A searchable database of brewery locations
|
||||||
|
- An offline data-generation pipeline that uses large language models (LLMs)
|
||||||
|
via Llama.cpp and Wikipedia to synthesize realistic seed data
|
||||||
|
|
||||||
|
The repository is organized into:
|
||||||
|
|
||||||
- A .NET backend (Web API + database migrations/seed) under `web/backend/`
|
- A .NET backend (Web API + database migrations/seed) under `web/backend/`
|
||||||
- A server-rendered React website (React Router + Vite) under `web/frontend/`
|
- A server-rendered React website (React Router + Vite) under `web/frontend/`
|
||||||
- A C++20 “pipeline” CLI for generating seed data under `tooling/pipeline/`
|
- A C++20 data-generation CLI under `tooling/pipeline/`
|
||||||
|
|
||||||
Specialized documentation (setup, architecture, docker, testing, diagrams, and
|
Specialized documentation (setup, architecture, docker, testing, diagrams, and
|
||||||
pipeline notes) lives under `docs/`.
|
pipeline notes) lives under `docs/`.
|
||||||
|
|
||||||
## Documentation (Start Here)
|
## Documentation
|
||||||
|
|
||||||
Website + backend (active stack):
|
Website + backend (active stack):
|
||||||
|
|
||||||
@@ -37,7 +41,7 @@ Data generation pipeline (C++):
|
|||||||
- [Authentication Flow](docs/website/diagrams-out/authentication-flow.svg)
|
- [Authentication Flow](docs/website/diagrams-out/authentication-flow.svg)
|
||||||
- [Database Schema](docs/website/diagrams-out/database-schema.svg)
|
- [Database Schema](docs/website/diagrams-out/database-schema.svg)
|
||||||
|
|
||||||
## Current Status
|
## Current status
|
||||||
|
|
||||||
Active areas in the repository:
|
Active areas in the repository:
|
||||||
|
|
||||||
@@ -52,18 +56,27 @@ Archived/reference areas:
|
|||||||
- `archive/next-js-web-app/` contains an older Next.js frontend retained for
|
- `archive/next-js-web-app/` contains an older Next.js frontend retained for
|
||||||
reference
|
reference
|
||||||
|
|
||||||
## Tech Stack
|
## Tech stack
|
||||||
|
|
||||||
- **Backend**: .NET 10, ASP.NET Core, SQL Server 2022, DbUp
|
- **Backend**: .NET 10, ASP.NET Core, SQL Server 2022, DbUp
|
||||||
- **Frontend**: React 19, React Router 7, Vite 7, Tailwind CSS 4, DaisyUI 5
|
- **Frontend**: React 19, React Router 7, Vite 7, Tailwind CSS 4, DaisyUI 5
|
||||||
- **UI Documentation**: Storybook 10, Vitest browser mode, Playwright
|
- **UI Documentation**: Storybook 10, Vitest browser mode, Playwright
|
||||||
- **Testing**: xUnit, Reqnroll (BDD), FluentAssertions, Moq
|
- **Testing**: xUnit, Reqnroll (BDD), FluentAssertions, Moq
|
||||||
- **Infrastructure**: Docker, Docker Compose
|
- **Infrastructure**: Docker, Docker Compose
|
||||||
- **Security**: Argon2id password hashing, JWT access/refresh/confirmation
|
- **Security**: Argon2id password hashing, JSON Web Tokens (JWT) for
|
||||||
tokens
|
access/refresh/confirmation
|
||||||
- **Data Pipeline**: C++20, CMake, Boost, libcurl, SQLite, llama.cpp
|
- **Data Pipeline**: C++20, CMake, Boost, libcurl, SQLite, llama.cpp
|
||||||
|
|
||||||
## Quick Start
|
## Requirements
|
||||||
|
|
||||||
|
- .NET SDK 10 or later
|
||||||
|
- Node.js 18 or later
|
||||||
|
- Docker Desktop, or an equivalent Docker Engine setup
|
||||||
|
|
||||||
|
See [Getting Started](docs/website/getting-started.md) for the full
|
||||||
|
prerequisite list, including the pipeline's C++ toolchain.
|
||||||
|
|
||||||
|
## Quick start
|
||||||
|
|
||||||
For full setup details, use [Getting Started](docs/website/getting-started.md).
|
For full setup details, use [Getting Started](docs/website/getting-started.md).
|
||||||
This section is the shortest path to a working dev environment.
|
This section is the shortest path to a working dev environment.
|
||||||
@@ -101,7 +114,7 @@ npm run test:storybook
|
|||||||
npm run test:storybook:playwright
|
npm run test:storybook:playwright
|
||||||
```
|
```
|
||||||
|
|
||||||
## Repository Structure
|
## Repository structure
|
||||||
|
|
||||||
```text
|
```text
|
||||||
web/
|
web/
|
||||||
@@ -123,9 +136,10 @@ archive/
|
|||||||
|
|
||||||
## Testing
|
## Testing
|
||||||
|
|
||||||
Run the backend test stack with Docker:
|
Create a test environment file, then run the backend test stack with Docker:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
|
cp web/.env.example web/.env.test
|
||||||
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 --abort-on-container-exit
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -147,5 +161,16 @@ See [Environment Variables](docs/website/environment-variables.md) for details.
|
|||||||
1. Fork the repository
|
1. Fork the repository
|
||||||
2. Create a feature branch (`git checkout -b feature/amazing-feature`)
|
2. Create a feature branch (`git checkout -b feature/amazing-feature`)
|
||||||
3. Commit your changes (`git commit -m 'Add amazing feature'`)
|
3. Commit your changes (`git commit -m 'Add amazing feature'`)
|
||||||
4. Push to the branch (`git push origin feature/amazing-feature`)
|
4. Run the checks in [Testing](docs/website/testing.md) (backend tests,
|
||||||
5. Open a Pull Request
|
`npm run lint`, `npm run typecheck`)
|
||||||
|
5. Push to the branch (`git push origin feature/amazing-feature`)
|
||||||
|
6. Open a Pull Request
|
||||||
|
|
||||||
|
## Support
|
||||||
|
|
||||||
|
Report bugs and request features via
|
||||||
|
[GitHub Issues](https://github.com/aaronpo97/the-biergarten-app/issues).
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
GPL-3.0. See [LICENSE.md](LICENSE.md).
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
This document describes the active architecture of The Biergarten App.
|
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
|
The Biergarten App is a monorepo with a clear split between the backend and the
|
||||||
active website:
|
active website:
|
||||||
@@ -29,9 +29,9 @@ For visual representations, see:
|
|||||||
- [database-schema.svg](website/diagrams-out/database-schema.svg) - Database
|
- [database-schema.svg](website/diagrams-out/database-schema.svg) - Database
|
||||||
relationships
|
relationships
|
||||||
|
|
||||||
## Backend Architecture
|
## Backend architecture
|
||||||
|
|
||||||
### Vertical Slice Architecture Pattern
|
### Vertical-slice architecture pattern
|
||||||
|
|
||||||
The backend organizes business capabilities as feature slices instead of
|
The backend organizes business capabilities as feature slices instead of
|
||||||
technical layers. Each feature (`Features.Auth`, `Features.Breweries`,
|
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
|
whose contract lives in `Shared.Application`, so neither slice takes a
|
||||||
project reference on the other.
|
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
|
**Purpose**: Thin ASP.NET Core host: no business logic, no controllers of
|
||||||
its own
|
its own
|
||||||
@@ -108,7 +108,7 @@ its own
|
|||||||
- No controllers, no business logic, no feature-specific contracts
|
- No controllers, no business logic, no feature-specific contracts
|
||||||
- Exists purely to host and wire up the feature slices
|
- 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
|
**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
|
MediatR
|
||||||
- Read endpoints return a dedicated `Dto`, never the domain entity directly
|
- 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
|
**Purpose**: The minimum cross-slice surface area required because every
|
||||||
slice needs it, or because duplicating it four times would be worse than
|
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
|
- Kept deliberately small: this is the exception to "no slice depends on
|
||||||
another slice," not a general-purpose dumping ground
|
another slice," not a general-purpose dumping ground
|
||||||
|
|
||||||
#### Infrastructure Layer
|
#### Infrastructure layer
|
||||||
|
|
||||||
**Purpose**: Technical capabilities and external integrations, shared by
|
**Purpose**: Technical capabilities and external integrations, shared by
|
||||||
whichever slices need them
|
whichever slices need them
|
||||||
@@ -185,14 +185,14 @@ whichever slices need them
|
|||||||
**Dependencies**:
|
**Dependencies**:
|
||||||
|
|
||||||
- Domain entities
|
- Domain entities
|
||||||
- External libraries (ADO.NET, JWT, Argon2, MailKit, etc.)
|
- External libraries (ADO.NET, JWT, Argon2, MailKit, and so on)
|
||||||
|
|
||||||
**Rules**:
|
**Rules**:
|
||||||
|
|
||||||
- Implements technical concerns only, no business logic
|
- Implements technical concerns only, no business logic
|
||||||
- Reusable across slices
|
- Reusable across slices
|
||||||
|
|
||||||
#### Domain Layer (`Domain.Entities`)
|
#### Domain layer (`Domain.Entities`)
|
||||||
|
|
||||||
**Purpose**: Core business entities and models
|
**Purpose**: Core business entities and models
|
||||||
|
|
||||||
@@ -213,9 +213,9 @@ whichever slices need them
|
|||||||
- No infrastructure references
|
- No infrastructure references
|
||||||
- Represents business concepts
|
- Represents business concepts
|
||||||
|
|
||||||
### Design Patterns
|
### Design patterns
|
||||||
|
|
||||||
#### Vertical Slice + MediatR
|
#### Vertical slice + MediatR
|
||||||
|
|
||||||
**Purpose**: Organize code by feature instead of by technical layer, so
|
**Purpose**: Organize code by feature instead of by technical layer, so
|
||||||
everything needed to understand or change one capability lives in one
|
everything needed to understand or change one capability lives in one
|
||||||
@@ -223,9 +223,9 @@ project
|
|||||||
|
|
||||||
**Implementation**:
|
**Implementation**:
|
||||||
|
|
||||||
- Each HTTP write operation is a `Command` (e.g. `CreateBreweryCommand` in
|
- Each HTTP write operation is a `Command` (for example, `CreateBreweryCommand`
|
||||||
`Features.Breweries/Commands/CreateBrewery/`); each read operation is a
|
in `Features.Breweries/Commands/CreateBrewery/`); each read operation is a
|
||||||
`Query` (e.g. `GetBreweryByIdQuery`)
|
`Query` (for example, `GetBreweryByIdQuery`)
|
||||||
- Controllers bind directly to the Command/Query as the request body;
|
- Controllers bind directly to the Command/Query as the request body;
|
||||||
there is no separate request DTO + mapping step for writes
|
there is no separate request DTO + mapping step for writes
|
||||||
- A single shared `ValidationBehavior<TRequest,TResponse>`
|
- 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
|
**Purpose**: Abstract database access behind interfaces
|
||||||
|
|
||||||
@@ -275,7 +275,7 @@ public interface IAuthRepository
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
#### Dependency Injection
|
#### Dependency injection
|
||||||
|
|
||||||
**Purpose**: Loose coupling and testability
|
**Purpose**: Loose coupling and testability
|
||||||
|
|
||||||
@@ -289,7 +289,7 @@ extension method that registers its repository and slice-internal services
|
|||||||
- Singleton: `ISqlConnectionFactory`
|
- Singleton: `ISqlConnectionFactory`
|
||||||
- Transient: Utilities, helpers
|
- Transient: Utilities, helpers
|
||||||
|
|
||||||
#### SQL-First Approach
|
#### SQL-first approach
|
||||||
|
|
||||||
**Purpose**: Push complex logic into the database
|
**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_GetUserAccountByUsername` - User lookup
|
||||||
- `USP_RotateUserCredential` - Password update
|
- `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
|
The current website is a React Router 7 application with server-side rendering
|
||||||
enabled.
|
enabled.
|
||||||
@@ -327,7 +327,7 @@ web/frontend/
|
|||||||
└── package.json Frontend scripts and dependencies
|
└── package.json Frontend scripts and dependencies
|
||||||
```
|
```
|
||||||
|
|
||||||
### Frontend Responsibilities
|
### Frontend responsibilities
|
||||||
|
|
||||||
- Render the auth demo and theme guide routes
|
- Render the auth demo and theme guide routes
|
||||||
- Manage cookie-backed website session state
|
- Manage cookie-backed website session state
|
||||||
@@ -335,7 +335,7 @@ web/frontend/
|
|||||||
- Provide shared UI building blocks for forms, navigation, themes, and toasts
|
- Provide shared UI building blocks for forms, navigation, themes, and toasts
|
||||||
- Supply Storybook documentation and browser-based component verification
|
- Supply Storybook documentation and browser-based component verification
|
||||||
|
|
||||||
### Theme System
|
### Theme system
|
||||||
|
|
||||||
The active website uses semantic DaisyUI theme tokens backed by four Biergarten
|
The active website uses semantic DaisyUI theme tokens backed by four Biergarten
|
||||||
themes:
|
themes:
|
||||||
@@ -348,15 +348,15 @@ themes:
|
|||||||
All component styling should prefer semantic tokens such as `primary`,
|
All component styling should prefer semantic tokens such as `primary`,
|
||||||
`success`, `surface`, and `highlight` instead of hard-coded color values.
|
`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/`
|
The previous Next.js frontend has been archived at `archive/next-js-web-app/`
|
||||||
for reference only. Active product and engineering documentation should point
|
for reference only. Active product and engineering documentation should point
|
||||||
to `web/frontend`.
|
to `web/frontend`.
|
||||||
|
|
||||||
## Security Architecture
|
## Security architecture
|
||||||
|
|
||||||
### Authentication Flow
|
### Authentication flow
|
||||||
|
|
||||||
1. **Registration**:
|
1. **Registration**:
|
||||||
- User submits credentials
|
- User submits credentials
|
||||||
@@ -375,7 +375,7 @@ to `web/frontend`.
|
|||||||
- Middleware validates token
|
- Middleware validates token
|
||||||
- Request proceeds if valid
|
- Request proceeds if valid
|
||||||
|
|
||||||
### Password Security
|
### Password security
|
||||||
|
|
||||||
**Algorithm**: Argon2id
|
**Algorithm**: Argon2id
|
||||||
|
|
||||||
@@ -385,7 +385,7 @@ to `web/frontend`.
|
|||||||
- Salt: 128-bit (16 bytes)
|
- Salt: 128-bit (16 bytes)
|
||||||
- Hash: 256-bit (32 bytes)
|
- Hash: 256-bit (32 bytes)
|
||||||
|
|
||||||
### JWT Tokens
|
### JWT tokens
|
||||||
|
|
||||||
**Algorithm**: HS256 (HMAC-SHA256)
|
**Algorithm**: HS256 (HMAC-SHA256)
|
||||||
|
|
||||||
@@ -409,9 +409,9 @@ to `web/frontend`.
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
## Database Architecture
|
## Database architecture
|
||||||
|
|
||||||
### SQL-First Philosophy
|
### SQL-first philosophy
|
||||||
|
|
||||||
**Principles**:
|
**Principles**:
|
||||||
|
|
||||||
@@ -427,7 +427,7 @@ to `web/frontend`.
|
|||||||
- Version-controlled schema (migrations)
|
- Version-controlled schema (migrations)
|
||||||
- Easier query profiling and tuning
|
- Easier query profiling and tuning
|
||||||
|
|
||||||
### Migration Strategy
|
### Migration strategy
|
||||||
|
|
||||||
**Tool**: DbUp
|
**Tool**: DbUp
|
||||||
|
|
||||||
@@ -448,7 +448,7 @@ scripts/
|
|||||||
└── ...
|
└── ...
|
||||||
```
|
```
|
||||||
|
|
||||||
### Data Seeding
|
### Data seeding
|
||||||
|
|
||||||
**Purpose**: Populate development/test databases
|
**Purpose**: Populate development/test databases
|
||||||
|
|
||||||
@@ -460,9 +460,9 @@ scripts/
|
|||||||
- Test user accounts
|
- Test user accounts
|
||||||
- Sample breweries (future)
|
- Sample breweries (future)
|
||||||
|
|
||||||
## Deployment Architecture
|
## Deployment architecture
|
||||||
|
|
||||||
### Docker Containerization
|
### Docker containerization
|
||||||
|
|
||||||
**Container Structure**:
|
**Container Structure**:
|
||||||
|
|
||||||
@@ -479,10 +479,10 @@ scripts/
|
|||||||
|
|
||||||
For details, see [Docker Guide](website/docker.md).
|
For details, see [Docker Guide](website/docker.md).
|
||||||
|
|
||||||
### Health Checks
|
### Health checks
|
||||||
|
|
||||||
**SQL Server**: Validates database connectivity **API**: Checks service health
|
- **SQL Server**: Validates database connectivity
|
||||||
and dependencies
|
- **API**: Checks service health and dependencies
|
||||||
|
|
||||||
**Configuration**:
|
**Configuration**:
|
||||||
|
|
||||||
@@ -494,9 +494,9 @@ healthcheck:
|
|||||||
start_period: 30s
|
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,
|
This document covers the ethical context of the Biergarten Pipeline's output,
|
||||||
the model's biases, and known issues including hallucinated brewing science and
|
the model's biases, and known issues including hallucinated brewing science and
|
||||||
low-resource language failures.
|
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](#what-this-dataset-is)
|
||||||
- [What This Dataset Is Not](#what-this-dataset-is-not)
|
- [What this dataset is not](#what-this-dataset-is-not)
|
||||||
- [Model Bias and Language Quality](#model-bias-and-language-quality)
|
- [Model bias and language quality](#model-bias-and-language-quality)
|
||||||
- [Western and Eurocentric Lens](#western-and-eurocentric-lens)
|
- [Western and Eurocentric lens](#western-and-eurocentric-lens)
|
||||||
- [Wikipedia Enrichment](#wikipedia-enrichment)
|
- [Wikipedia enrichment](#wikipedia-enrichment)
|
||||||
- [Names-by-Country Dataset](#names-by-country-dataset)
|
- [Names-by-country dataset](#names-by-country-dataset)
|
||||||
- [The "Avoid AI Phrases" Prompt Instruction](#the-avoid-ai-phrases-prompt-instruction)
|
- [The "avoid AI phrases" prompt instruction](#the-avoid-ai-phrases-prompt-instruction)
|
||||||
- [Known Issues](#known-issues)
|
- [Known issues](#known-issues)
|
||||||
- [Hallucinated Brewing Techniques](#hallucinated-brewing-techniques)
|
- [Hallucinated brewing techniques](#hallucinated-brewing-techniques)
|
||||||
- [Low-Resource Language Hallucination](#low-resource-language-hallucination)
|
- [Low-resource language hallucination](#low-resource-language-hallucination)
|
||||||
- [Synthetic Postal Codes](#synthetic-postal-codes)
|
- [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
|
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
|
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
|
The pipeline is not intended to produce accurate brewing science, faithful
|
||||||
cultural representation, or reliable local-language text. Hallucinations such as
|
cultural representation, or reliable local-language text. Hallucinations such as
|
||||||
invented fermentation techniques, or incoherent local-language prose, are
|
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.
|
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
|
enrichment). Statistical output shapes in future pipeline stages (check-in
|
||||||
distributions, rating skews, activity profiles) will be handled the same way.
|
distributions, rating skews, activity profiles) will be handled the same way.
|
||||||
|
|
||||||
**Treat this data as an exercise in prompt engineering and model behaviour, not
|
**Treat this data as an exercise in prompt engineering and model behaviour, not
|
||||||
as a source of truth for brewing techniques or cultural representation.**
|
as a source of truth for brewing techniques or cultural representation.**
|
||||||
|
|
||||||
**Natural language processing, although a powerful tool for data analysis and
|
**Natural language processing is a powerful tool for data analysis and
|
||||||
generation is to be taken with scrutiny. Human language is not simply just data
|
generation, but its output should be treated with scrutiny. Human language is
|
||||||
points to be analyzed, but it also carries deep cultural and human meaning that
|
not simply data points to be analyzed — it carries cultural and human meaning
|
||||||
artificial intelligence is incapable of.**
|
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
|
The underlying model's training biases surface within this pipeline. Output
|
||||||
quality tracks with how well a language is represented in the training corpus:
|
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]
|
material.[^llm-bias]
|
||||||
|
|
||||||
Mitigations are documented in
|
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
|
The model's training data skews heavily Western and North American. When
|
||||||
generating brewery descriptions for Kinshasa, Abidjan, or Osaka, for example, it
|
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
|
City and beer context is fetched from the Wikipedia API. Wikipedia text is
|
||||||
co-licensed under the **Creative Commons Attribution-ShareAlike 4.0
|
co-licensed under the **Creative Commons Attribution-ShareAlike 4.0
|
||||||
International License (CC BY-SA 4.0)** and the **GNU Free Documentation License
|
International License (CC BY-SA 4.0)**[^cc-sa] and the **GNU Free Documentation
|
||||||
(GFDL)**.[^wp-license]
|
License (GFDL)**.[^wp-license]
|
||||||
|
|
||||||
Wikipedia's own accuracy limitations and editorial biases can propagate into
|
Wikipedia's own accuracy limitations and editorial biases can propagate into
|
||||||
generated descriptions.
|
generated descriptions.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Names-by-Country Dataset
|
## Names-by-country dataset
|
||||||
|
|
||||||
`tooling/pipeline/forenames-by-country.json` and `surnames-by-country.json`
|
`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
|
(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
|
The system prompt instructs the model to avoid common AI-generated phrasing
|
||||||
patterns. This is a prompt engineering experiment:
|
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
|
When forced by the system prompt to generate a "highly specialized technical
|
||||||
brewing detail," the model frequently hallucinates fermentation science and
|
brewing detail," the model frequently hallucinates fermentation science and
|
||||||
brewing chemistry. While the resulting sounds confident, it will be nonsensical
|
brewing chemistry. While the resulting text sounds confident, it is
|
||||||
to reader with brewing and/or scientific expertise.
|
nonsensical to a reader with brewing or scientific expertise.
|
||||||
|
|
||||||
Small-parameter models such as Gemma 4 E4B cannot reliably satisfy prompts
|
Small-parameter models such as Gemma 4 E4B cannot reliably satisfy prompts
|
||||||
calling for specialist brewing detail. This is consistent with the **CHOKE**
|
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="早朝の、たこ焼きや香ばしいイカ焼きの湿った匂いは、いつも乾燥小屋の外にある使用済み麦芽の金属的な匂いと奇妙に混ざり合って近隣に漂います。私たちはこの場所に店を構えることを決めたのです。なぜなら、大阪は決して止まることのない都市であり、商品と味が絶え間なく交換されることで築かれた場所だからです。地元の多くの方々は、信頼できる大規模な淡麗ラガーの味が習慣になっていますが、私たちは発酵の、より深く、より複雑な表現を探求することに関心があります。私たちのベースモルトは、山田錦のような地元の米の品種を意図的に組み込んで作られています。この米を酒ではなく、麦芽として、仕込みの最中にその複雑でバターのような風味を引き出すために使用しています。この組み合わせを、ゆっくりとした嫌気的な熟成プロセスに続けることで、私たちのビールは、慌ただしい市場のスタイルとは一線を画す、深みのある、満足感のある複雑さを持っています。オリジナルの倉庫の重く深紅のレンガ壁は、関西特有の湿気と季節の雨を何十年も吸収し、この地区の絶え間ない動きの正確な物語を語るような古色を帯びています。私たちはこの構造物を、その魅力のためではなく、その回復力とモルタルに込められた地域の歴史の密度ゆえに選びました。私たちの目標は、ただこの力強い交易都市に値する飲み物を提供することだけです。もしメインの通りから離れた静かな場所をお探しなら、新世界近くの脇道にある私たちを探してください。"
|
[2026-04-21 15:04:40.258] [info] brewery_description_local="早朝の、たこ焼きや香ばしいイカ焼きの湿った匂いは、いつも乾燥小屋の外にある使用済み麦芽の金属的な匂いと奇妙に混ざり合って近隣に漂います。私たちはこの場所に店を構えることを決めたのです。なぜなら、大阪は決して止まることのない都市であり、商品と味が絶え間なく交換されることで築かれた場所だからです。地元の多くの方々は、信頼できる大規模な淡麗ラガーの味が習慣になっていますが、私たちは発酵の、より深く、より複雑な表現を探求することに関心があります。私たちのベースモルトは、山田錦のような地元の米の品種を意図的に組み込んで作られています。この米を酒ではなく、麦芽として、仕込みの最中にその複雑でバターのような風味を引き出すために使用しています。この組み合わせを、ゆっくりとした嫌気的な熟成プロセスに続けることで、私たちのビールは、慌ただしい市場のスタイルとは一線を画す、深みのある、満足感のある複雑さを持っています。オリジナルの倉庫の重く深紅のレンガ壁は、関西特有の湿気と季節の雨を何十年も吸収し、この地区の絶え間ない動きの正確な物語を語るような古色を帯びています。私たちはこの構造物を、その魅力のためではなく、その回復力とモルタルに込められた地域の歴史の密度ゆえに選びました。私たちの目標は、ただこの力強い交易都市に値する飲み物を提供することだけです。もしメインの通りから離れた静かな場所をお探しなら、新世界近くの脇道にある私たちを探してください。"
|
||||||
```
|
```
|
||||||
|
|
||||||
A review of the following text for brewing techniques reveals several
|
A review of the following text for brewing techniques reveals three
|
||||||
inaccuracies, and no comments could be made on the local-language version due to
|
inaccuracies. No comments are made on the local-language version, since its
|
||||||
my own lack of proficiency in Japanese:
|
reviewer does not read Japanese:
|
||||||
|
|
||||||
#### 1. "Buttery flavours" framed as a desirable malt-derived flavour
|
#### 1. "Buttery flavours" framed as a desirable malt-derived flavour
|
||||||
|
|
||||||
@@ -191,7 +191,7 @@ fermentable sugars.[^yn-sakeonline]
|
|||||||
|
|
||||||
**Misleading**
|
**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
|
technique. Anaerobic conditions are the standard baseline for all commercial
|
||||||
beer production. Breweries exclude oxygen as a top priority for packaging and
|
beer production. Breweries exclude oxygen as a top priority for packaging and
|
||||||
shelf stability; published research in _Microbiology Spectrum_ confirms that
|
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
|
to maintain this state.[^packaging] Framing anaerobic aging as a distinctive
|
||||||
practice is misleading and suggests hallucinated output.
|
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
|
The generation pipeline passes local language codes to the model to retrieve a
|
||||||
translated `description_local`. Output quality is reliable for high-resource
|
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
|
When fed into the pipeline, this dataset often causes the model to reason that
|
||||||
variant of French is needed, but will often times just default to a standardized
|
a local variant of French is needed, but it often defaults to a standardized
|
||||||
dialect of French, devoid of any cultural or linguistic nuance.
|
dialect of French, without cultural or linguistic nuance.
|
||||||
|
|
||||||
For languages such as Welsh (Wales), Māori (Aotearoa/New Zealand), or Sicilian
|
For languages such as Welsh (Wales), Māori (Aotearoa/New Zealand), or Sicilian
|
||||||
(Sicily, Italy), the model can generate text that looks syntactically plausible
|
(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)
|
Output sample: [./french-cities.example](french-cities.example)
|
||||||
|
|
||||||
#### Proposed Mitigations
|
#### Proposed mitigations
|
||||||
|
|
||||||
- **Prevention via allowlist:** introduce a high-resource language allowlist. If
|
- **Prevention via allowlist:** introduce a high-resource language allowlist. If
|
||||||
a location's code is unlisted, skip `description_local` generation and fall
|
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
|
SQLite schema so downstream applications can filter or flag potentially
|
||||||
hallucinated text by language tier.
|
hallucinated text by language tier.
|
||||||
|
|
||||||
### Synthetic Postal Codes
|
### Synthetic postal codes
|
||||||
|
|
||||||
When the pipeline is run without `--mocked`, `XegerPostalCodeService` generates
|
When the pipeline is run without `--mocked`, `XegerPostalCodeService` generates
|
||||||
a fresh postal code per brewery/user address by reverse-matching one of the
|
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.
|
Simhi, Itay Itzhak, Fazl Barez, Gabriel Stanovsky, Yonatan Belinkov.
|
||||||
|
|
||||||
[^llm-bias]:
|
[^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
|
Performance across the World's Languages," _ACL Anthology_. The pattern is
|
||||||
consistent with models trained predominantly on English-language web
|
consistent with models trained predominantly on English-language web
|
||||||
corpora.
|
corpora.
|
||||||
|
|||||||
@@ -14,8 +14,8 @@ deterministic mock.
|
|||||||
|
|
||||||
## Table of Contents
|
## Table of Contents
|
||||||
|
|
||||||
- [How It Fits The Main App](#how-it-fits-the-main-app)
|
- [How it fits the main app](#how-it-fits-the-main-app)
|
||||||
- [Quick Start](#quick-start)
|
- [Quick start](#quick-start)
|
||||||
- [Build](#build)
|
- [Build](#build)
|
||||||
- [Model](#model)
|
- [Model](#model)
|
||||||
- [Run](#run)
|
- [Run](#run)
|
||||||
@@ -24,17 +24,17 @@ deterministic mock.
|
|||||||
- [Pipeline Stages](#pipeline-stages)
|
- [Pipeline Stages](#pipeline-stages)
|
||||||
- [Key Components](#key-components)
|
- [Key Components](#key-components)
|
||||||
- [Runtime Behaviour](#runtime-behaviour)
|
- [Runtime Behaviour](#runtime-behaviour)
|
||||||
- [Generated Output](#generated-output)
|
- [Generated output](#generated-output)
|
||||||
- [Tech Stack](#tech-stack)
|
- [Tech stack](#tech-stack)
|
||||||
- [Tested Hardware](#tested-hardware)
|
- [Tested hardware](#tested-hardware)
|
||||||
- [Fixture Strategy](#fixture-strategy)
|
- [Fixture strategy](#fixture-strategy)
|
||||||
- [Repo Layout](#repo-layout)
|
- [Repo layout](#repo-layout)
|
||||||
- [Code Tour](#code-tour)
|
- [Code tour](#code-tour)
|
||||||
- [Next Steps](#next-steps)
|
- [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
|
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
|
produces seed records the app imports at startup or during a dedicated seed
|
||||||
@@ -50,7 +50,7 @@ step.
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Quick Start
|
## Quick start
|
||||||
|
|
||||||
### Build
|
### 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
|
--temperature 1.0 --top-p 0.95 --top-k 64 --n-ctx 8192 --seed -1
|
||||||
```
|
```
|
||||||
|
|
||||||
#### CLI Flags
|
#### CLI flags
|
||||||
|
|
||||||
| Flag | Purpose |
|
| Flag | Purpose |
|
||||||
| ------------------ | ---------------------------------------------------------------------------------------------------- |
|
| ------------------ | ---------------------------------------------------------------------------------------------------- |
|
||||||
| `--mocked` | Deterministic mock generator, no model required. |
|
| `--mocked` | Deterministic mock generator, no model required. |
|
||||||
| `--model, -m` | Path to a GGUF file. Required unless `--mocked` is set. |
|
| `--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`. |
|
| `--output, -o` | Directory for generated SQLite artifacts. Default: `output`. |
|
||||||
| `--log-path` | Path for application logs. Default: `pipeline.log`. |
|
| `--log-path` | Path for application logs. Default: `pipeline.log`. |
|
||||||
| `--location-count` | Number of cities to sample from `locations.json` per run. Default: `10`. |
|
| `--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
|
### Run the container
|
||||||
|
|
||||||
The container always runs the model-backed path; there is no `--mocked`
|
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
|
The entrypoint, `runpod/start.sh`, downloads the GGUF model automatically if it
|
||||||
is not already present at the configured path.
|
is not already present at the configured path.
|
||||||
|
|
||||||
@@ -218,7 +218,7 @@ environment variables listed above to match your run.
|
|||||||
|
|
||||||
## Architecture
|
## Architecture
|
||||||
|
|
||||||
### Pipeline Stages
|
### Pipeline stages
|
||||||
|
|
||||||
| Stage | Implementation |
|
| 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
|
skipped and the pipeline continues. `GenerateUsers()` runs before
|
||||||
`GenerateBreweries()` in `BiergartenPipelineOrchestrator::Run()`.
|
`GenerateBreweries()` in `BiergartenPipelineOrchestrator::Run()`.
|
||||||
|
|
||||||
### Key Components
|
### Key components
|
||||||
|
|
||||||
- `src/main.cc` — argument parsing and Boost.DI composition root.
|
- `src/main.cc` — argument parsing and Boost.DI composition root.
|
||||||
- `CuratedJsonDataService` — implements `ICuratedDataService`; takes a
|
- `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
|
LLM-generated username/bio/activity weight, and a programmatically generated
|
||||||
(not LLM-authored) unique email and date of birth.
|
(not LLM-authored) unique email and date of birth.
|
||||||
|
|
||||||
### Runtime Behaviour
|
### Runtime behaviour
|
||||||
|
|
||||||
`WikipediaEnrichmentService` fetches two Wikipedia extracts per city: a generic
|
`WikipediaEnrichmentService` fetches two Wikipedia extracts per city: a generic
|
||||||
"brewing" extract and a "beer in `{country}`" extract. It does not currently
|
"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 —
|
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.
|
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
|
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
|
`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
|
The log dump also includes city, country, state or province, ISO subdivision
|
||||||
code, latitude, and longitude for each entry.
|
code, latitude, and longitude for each entry.
|
||||||
|
|
||||||
### Consumer Data Shape
|
### Consumer data shape
|
||||||
|
|
||||||
| Field | Why it matters |
|
| Field | Why it matters |
|
||||||
| ----------------------------------- | ------------------------------------------------ |
|
| ----------------------------------- | ------------------------------------------------ |
|
||||||
@@ -341,7 +341,7 @@ code, latitude, and longitude for each entry.
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Tech Stack
|
## Tech stack
|
||||||
|
|
||||||
- C++20
|
- C++20
|
||||||
- CMake 3.31+
|
- 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
|
### 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.
|
- `--mocked` for stable fixtures, repeatable screenshots, and Storybook runs.
|
||||||
`MockCuratedDataService` swaps in for `CuratedJsonDataService`, so no fixture
|
`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 |
|
| 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/`.
|
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
|
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
|
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
|
engineering breakdown of what's needed to reach the architecture in
|
||||||
[`diagrams/planned/`](./diagrams/planned/), see [ROADMAP.md](./ROADMAP.md).
|
[`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
|
- Unit test JSON validation and retry logic against malformed, truncated, and
|
||||||
empty model outputs.
|
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
|
- Confirm the retry path is reachable when the reasoning block consumes
|
||||||
available token budget.
|
available token budget.
|
||||||
|
|
||||||
### Beer Generation
|
### Beer generation
|
||||||
|
|
||||||
Generate catalog entries with style, ABV, IBU, color, aroma notes, and food
|
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
|
pairing hints. Link beers back to breweries and cities. Keep style coverage wide
|
||||||
enough to exercise search, sort, and category filters.
|
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
|
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
|
activity profile — a small set of users accounts for most check-ins, the rest
|
||||||
appear occasionally. Add bursty behaviour around weekends and travel periods.
|
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
|
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
|
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
|
This document covers Docker deployment, configuration, and troubleshooting for
|
||||||
The Biergarten App.
|
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
|
See the [deployment diagram](diagrams-out/deployment.svg) for visual
|
||||||
representation.
|
representation.
|
||||||
|
|
||||||
## Docker Compose Environments
|
## Docker Compose environments
|
||||||
|
|
||||||
### 1. Development (`docker-compose.dev.yaml`)
|
### 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`)
|
### 2. Testing (`docker-compose.test.yaml`)
|
||||||
|
|
||||||
**Purpose**: Automated CI/CD testing in isolated environment
|
**Purpose**: Automated CI/CD testing in an isolated environment
|
||||||
|
|
||||||
**Features**:
|
**Features**:
|
||||||
|
|
||||||
@@ -128,18 +128,15 @@ api.core # Production API
|
|||||||
docker compose --env-file web/.env.prod -f web/docker-compose.prod.yaml up -d
|
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:
|
Docker Compose manages startup order using health checks:
|
||||||
|
|
||||||
```mermaid
|
```mermaid
|
||||||
sqlserver (health check)
|
graph TD
|
||||||
↓
|
A[sqlserver: health check passes] --> B[database.migrations: completes successfully]
|
||||||
database.migrations (completes successfully)
|
B --> C[database.seed: completes successfully]
|
||||||
↓
|
C --> D[api.core / tests: start when ready]
|
||||||
database.seed (completes successfully)
|
|
||||||
↓
|
|
||||||
api.core / tests (start when ready)
|
|
||||||
```
|
```
|
||||||
|
|
||||||
**Health Check Example** (SQL Server):
|
**Health Check Example** (SQL Server):
|
||||||
@@ -168,7 +165,7 @@ api.core:
|
|||||||
|
|
||||||
## Volumes
|
## Volumes
|
||||||
|
|
||||||
### Persistent Volumes
|
### Persistent volumes
|
||||||
|
|
||||||
**Development**:
|
**Development**:
|
||||||
|
|
||||||
@@ -184,7 +181,7 @@ api.core:
|
|||||||
- `sqlserverdata-prod` - Production database files
|
- `sqlserverdata-prod` - Production database files
|
||||||
- `nuget-cache-prod` - Production NuGet cache
|
- `nuget-cache-prod` - Production NuGet cache
|
||||||
|
|
||||||
### Mounted Volumes
|
### Mounted volumes
|
||||||
|
|
||||||
**Test Results**:
|
**Test Results**:
|
||||||
|
|
||||||
@@ -203,7 +200,7 @@ Each environment uses isolated bridge networks:
|
|||||||
- `testnet` - Testing network (fully isolated)
|
- `testnet` - Testing network (fully isolated)
|
||||||
- `prodnet` - Production network
|
- `prodnet` - Production network
|
||||||
|
|
||||||
## Environment Variables
|
## Environment variables
|
||||||
|
|
||||||
All containers are configured via environment variables from `.env` files:
|
All containers are configured via environment variables from `.env` files:
|
||||||
|
|
||||||
@@ -224,9 +221,9 @@ environment:
|
|||||||
|
|
||||||
For complete list, see [Environment Variables](environment-variables.md).
|
For complete list, see [Environment Variables](environment-variables.md).
|
||||||
|
|
||||||
## Common Commands
|
## Common commands
|
||||||
|
|
||||||
### View Services
|
### View services
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Running services
|
# Running services
|
||||||
@@ -236,7 +233,7 @@ docker compose --env-file web/.env.dev -f web/docker-compose.dev.yaml ps
|
|||||||
docker ps -a
|
docker ps -a
|
||||||
```
|
```
|
||||||
|
|
||||||
### View Logs
|
### View logs
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# All services
|
# 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
|
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
|
```bash
|
||||||
# Interactive shell
|
# Interactive shell
|
||||||
docker exec -it dev-env-api-core bash
|
docker exec -it dev-env-api-core bash
|
||||||
|
|
||||||
# Run command
|
# 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 'password' -C
|
docker exec dev-env-sqlserver /opt/mssql-tools18/bin/sqlcmd -S localhost -U sa -P '<db-password>' -C
|
||||||
```
|
```
|
||||||
|
|
||||||
### Restart Services
|
### Restart services
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Restart all services
|
# 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
|
docker compose --env-file web/.env.dev -f web/docker-compose.dev.yaml up -d --build api.core
|
||||||
```
|
```
|
||||||
|
|
||||||
### Build Images
|
### Build images
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Build all images
|
# 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
|
docker compose --env-file web/.env.dev -f web/docker-compose.dev.yaml build --no-cache
|
||||||
```
|
```
|
||||||
|
|
||||||
### Clean Up
|
### Clean up
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Stop and remove containers
|
# 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
|
docker system prune -af --volumes
|
||||||
```
|
```
|
||||||
|
|
||||||
## Dockerfile Structure
|
## Dockerfile structure
|
||||||
|
|
||||||
### Multi-Stage Build
|
### Multi-stage build
|
||||||
|
|
||||||
```dockerfile
|
```dockerfile
|
||||||
# Stage 1: Build
|
# Stage 1: Build
|
||||||
@@ -321,7 +318,7 @@ COPY --from=build /app/build .
|
|||||||
ENTRYPOINT ["dotnet", "Project.dll"]
|
ENTRYPOINT ["dotnet", "Project.dll"]
|
||||||
```
|
```
|
||||||
|
|
||||||
## Additional Resources
|
## Additional resources
|
||||||
|
|
||||||
- [Docker Compose Documentation](https://docs.docker.com/compose/)
|
- [Docker Compose Documentation](https://docs.docker.com/compose/)
|
||||||
- [.NET Docker Images](https://hub.docker.com/_/microsoft-dotnet)
|
- [.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
|
This document covers the active environment variables used by the current
|
||||||
Biergarten stack.
|
Biergarten stack.
|
||||||
@@ -11,7 +11,7 @@ The application uses environment variables for:
|
|||||||
- **React Router website** - API base URL and session signing
|
- **React Router website** - API base URL and session signing
|
||||||
- **Docker containers** - environment-specific orchestration
|
- **Docker containers** - environment-specific orchestration
|
||||||
|
|
||||||
## Configuration Patterns
|
## Configuration patterns
|
||||||
|
|
||||||
### Backend (.NET API)
|
### Backend (.NET API)
|
||||||
|
|
||||||
@@ -30,9 +30,9 @@ Environment-specific `.env` files loaded via `env_file:` in docker-compose.yaml:
|
|||||||
- `.env.test` - Testing
|
- `.env.test` - Testing
|
||||||
- `.env.prod` - Production
|
- `.env.prod` - Production
|
||||||
|
|
||||||
## Backend Variables (.NET API)
|
## Backend variables (.NET API)
|
||||||
|
|
||||||
### Database Connection
|
### Database connection
|
||||||
|
|
||||||
**Option 1: Component-Based (Recommended for Docker)**
|
**Option 1: Component-Based (Recommended for Docker)**
|
||||||
|
|
||||||
@@ -59,7 +59,7 @@ string is built from components.
|
|||||||
|
|
||||||
**Implementation**: See `DefaultSqlConnectionFactory.cs`
|
**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
|
The backend uses separate secrets for different token types to enable
|
||||||
independent key rotation and validation isolation.
|
independent key rotation and validation isolation.
|
||||||
@@ -80,9 +80,9 @@ WEBSITE_BASE_URL=https://thebiergarten.app # Base URL for the website
|
|||||||
|
|
||||||
**Security Requirements**:
|
**Security Requirements**:
|
||||||
|
|
||||||
- Each secret should be minimum 32 characters
|
- Use at least 32 characters per secret
|
||||||
- Recommend 127+ characters for production
|
- Use 127 or more characters in production
|
||||||
- Generate using cryptographically secure random functions
|
- Generate secrets with a cryptographically secure random function
|
||||||
- Never reuse secrets across token types or environments
|
- Never reuse secrets across token types or environments
|
||||||
- Rotate secrets periodically in production
|
- Rotate secrets periodically in production
|
||||||
|
|
||||||
@@ -110,19 +110,20 @@ openssl rand -base64 127
|
|||||||
- **Handler**: Microsoft.IdentityModel.JsonWebTokens.JsonWebTokenHandler
|
- **Handler**: Microsoft.IdentityModel.JsonWebTokens.JsonWebTokenHandler
|
||||||
- **Validation**: Token signature, expiration, and malformed token checks
|
- **Validation**: Token signature, expiration, and malformed token checks
|
||||||
|
|
||||||
### Migration Control
|
### Migration control
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
CLEAR_DATABASE=true
|
CLEAR_DATABASE=true
|
||||||
```
|
```
|
||||||
|
|
||||||
- **Required**: No
|
- **Required**: No
|
||||||
- **Default**: false
|
- **Default**: `false`
|
||||||
- **Effect**: If "true", drops and recreates database during migrations
|
- **Effect**: If set to `true`, drops and recreates the database during
|
||||||
- **Usage**: Development and testing environments ONLY
|
migrations
|
||||||
- **Warning**: NEVER use in production
|
- **Usage**: Development and testing environments only
|
||||||
|
- **Warning**: Never use in production
|
||||||
|
|
||||||
### ASP.NET Core Configuration
|
### ASP.NET Core configuration
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
ASPNETCORE_ENVIRONMENT=Development # Development, Production, Staging
|
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
|
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
|
The active website does not use the old Next.js/Prisma environment model. Its
|
||||||
core runtime variables are:
|
core runtime variables are:
|
||||||
@@ -141,7 +142,7 @@ SESSION_SECRET=<generated-secret> # Cookie session signing secret
|
|||||||
NODE_ENV=development # Standard Node runtime mode
|
NODE_ENV=development # Standard Node runtime mode
|
||||||
```
|
```
|
||||||
|
|
||||||
### Frontend Variable Details
|
### Frontend variable details
|
||||||
|
|
||||||
#### `API_BASE_URL`
|
#### `API_BASE_URL`
|
||||||
|
|
||||||
@@ -163,7 +164,7 @@ NODE_ENV=development # Standard Node runtime mode
|
|||||||
- **Typical values**: `development`, `production`, `test`
|
- **Typical values**: `development`, `production`, `test`
|
||||||
- **Purpose**: Controls secure cookie behavior and runtime mode
|
- **Purpose**: Controls secure cookie behavior and runtime mode
|
||||||
|
|
||||||
### SMTP Configuration (Backend)
|
### SMTP configuration (backend)
|
||||||
|
|
||||||
Read by `Infrastructure.Email/SmtpEmailProvider.cs` for sending confirmation
|
Read by `Infrastructure.Email/SmtpEmailProvider.cs` for sending confirmation
|
||||||
and account emails.
|
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
|
- **Local dev**: point at the `mailpit` Docker service (SMTP on port 1025, web
|
||||||
UI on http://localhost:8025)
|
UI on http://localhost:8025)
|
||||||
- **Production**: point at a real provider (SendGrid, Mailgun, Amazon SES,
|
- **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
|
```bash
|
||||||
SA_PASSWORD=YourStrong!Passw0rd # SQL Server SA password
|
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
|
- Uppercase, lowercase, digits, and special characters
|
||||||
- Maps to `DB_PASSWORD` for application containers
|
- 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)
|
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
|
# 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
|
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
|
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.test.yaml` → `web/.env.test`
|
||||||
- `web/docker-compose.prod.yaml` → `web/.env.prod`
|
- `web/docker-compose.prod.yaml` → `web/.env.prod`
|
||||||
|
|
||||||
## Variable Reference Table
|
## Variable reference table
|
||||||
|
|
||||||
| Variable | Backend | Frontend | Docker | Required | Notes |
|
| Variable | Backend | Frontend | Docker | Required | Notes |
|
||||||
| ----------------------------- | :-----: | :------: | :----: | :------: | -------------------------- |
|
| ----------------------------- | :-----: | :------: | :----: | :------: | -------------------------- |
|
||||||
@@ -268,7 +269,7 @@ reference only and is not run as part of the active stack.
|
|||||||
|
|
||||||
## Validation
|
## Validation
|
||||||
|
|
||||||
### Backend Validation
|
### Backend validation
|
||||||
|
|
||||||
Variables are validated at startup:
|
Variables are validated at startup:
|
||||||
|
|
||||||
@@ -279,7 +280,7 @@ Variables are validated at startup:
|
|||||||
"minimum 32 characters" guidance above is a recommendation, not an
|
"minimum 32 characters" guidance above is a recommendation, not an
|
||||||
enforced check
|
enforced check
|
||||||
|
|
||||||
### Frontend Validation
|
### Frontend validation
|
||||||
|
|
||||||
The active website relies on runtime defaults for local development and the
|
The active website relies on runtime defaults for local development and the
|
||||||
surrounding server environment in deployed environments.
|
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
|
- `SESSION_SECRET` falls back to a development-only local secret
|
||||||
- `NODE_ENV` controls secure cookie behavior
|
- `NODE_ENV` controls secure cookie behavior
|
||||||
|
|
||||||
## Example Configuration Files
|
## Example configuration files
|
||||||
|
|
||||||
### `.env.dev` (Backend/Docker)
|
### `.env.dev` (backend/Docker)
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Database
|
# Database
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
# Getting Started
|
# Getting started
|
||||||
|
|
||||||
This guide covers local setup for the current Biergarten stack: the .NET backend
|
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`.
|
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
|
- **Docker Desktop** or equivalent Docker Engine setup
|
||||||
- **Java 8+** if you want to regenerate PlantUML diagrams
|
- **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
|
```bash
|
||||||
git clone <repository-url>
|
git clone https://github.com/aaronpo97/the-biergarten-app
|
||||||
cd the-biergarten-app
|
cd the-biergarten-app
|
||||||
```
|
```
|
||||||
|
|
||||||
### 2. Configure Backend Environment Variables
|
### 2. Configure backend environment variables
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
cp web/.env.example web/.env.dev
|
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.
|
See [Environment Variables](environment-variables.md) for the full list.
|
||||||
|
|
||||||
### 3. Start the Backend Stack
|
### 3. Start the backend stack
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
docker compose --env-file web/.env.dev -f web/docker-compose.dev.yaml up -d
|
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
|
- Health Check: http://localhost:8080/health
|
||||||
- Mailpit UI: http://localhost:8025
|
- Mailpit UI: http://localhost:8025
|
||||||
|
|
||||||
### 4. Start the Active Frontend
|
### 4. Start the active frontend
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
cd web/frontend
|
cd web/frontend
|
||||||
@@ -69,7 +69,7 @@ Required frontend runtime variables for local work:
|
|||||||
- `API_BASE_URL` - Base URL for the .NET API
|
- `API_BASE_URL` - Base URL for the .NET API
|
||||||
- `SESSION_SECRET` - Cookie session signing secret for the website server
|
- `SESSION_SECRET` - Cookie session signing secret for the website server
|
||||||
|
|
||||||
### 5. Optional: Run Storybook
|
### 5. Optional: run Storybook
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
cd web/frontend
|
cd web/frontend
|
||||||
@@ -78,7 +78,7 @@ npm run storybook
|
|||||||
|
|
||||||
Storybook runs at http://localhost:6006 by default.
|
Storybook runs at http://localhost:6006 by default.
|
||||||
|
|
||||||
## Useful Commands
|
## Useful commands
|
||||||
|
|
||||||
### Backend
|
### Backend
|
||||||
|
|
||||||
@@ -99,11 +99,11 @@ npm run test:storybook
|
|||||||
npm run test:storybook:playwright
|
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.
|
If you do not want to use Docker, you can run the backend locally.
|
||||||
|
|
||||||
### 1. Set Environment Variables
|
### 1. Set environment variables
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
export DB_CONNECTION_STRING="Server=localhost,1433;Database=Biergarten;User Id=sa;Password=YourStrong!Passw0rd;TrustServerCertificate=True;"
|
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"
|
export WEBSITE_BASE_URL="http://localhost:3000"
|
||||||
```
|
```
|
||||||
|
|
||||||
### 2. Run Migrations and Seed
|
### 2. Run migrations and seed
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
cd web/backend
|
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
|
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
|
The previous Next.js frontend lives in `archive/next-js-web-app/` for reference
|
||||||
only and is not the active website.
|
only and is not the active website.
|
||||||
|
|
||||||
## Next Steps
|
## Next steps
|
||||||
|
|
||||||
- Review [Architecture](../architecture.md)
|
- Review [Architecture](../architecture.md)
|
||||||
- Run backend and frontend checks from [Testing](testing.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
|
- **Storybook Playwright suite** - Browser checks against Storybook-rendered
|
||||||
components
|
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
|
The easiest way to run all tests is using Docker Compose, which sets up an
|
||||||
isolated test environment:
|
isolated test environment:
|
||||||
@@ -37,7 +37,7 @@ This command:
|
|||||||
5. Exports results to `./test-results/`
|
5. Exports results to `./test-results/`
|
||||||
6. Exits when tests complete
|
6. Exits when tests complete
|
||||||
|
|
||||||
### View Test Results
|
### View test results
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# List test result files
|
# List test result files
|
||||||
@@ -51,18 +51,18 @@ cat test-results/Features.UserManagement.Tests.trx
|
|||||||
cat test-results/Features.Emails.Tests.trx
|
cat test-results/Features.Emails.Tests.trx
|
||||||
```
|
```
|
||||||
|
|
||||||
### Clean Up
|
### Clean up
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Remove test containers and volumes
|
# Remove test containers and volumes
|
||||||
docker compose --env-file web/.env.test -f web/docker-compose.test.yaml down -v
|
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:
|
You can run individual test projects locally without Docker:
|
||||||
|
|
||||||
### Integration Tests (API.Specs)
|
### Integration tests (API.Specs)
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
cd web/backend
|
cd web/backend
|
||||||
@@ -75,7 +75,7 @@ dotnet test API/API.Specs/API.Specs.csproj
|
|||||||
- Database migrated and seeded
|
- Database migrated and seeded
|
||||||
- Environment variables set (DB connection, JWT secret)
|
- 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
|
Each feature slice has its own test project, covering its command/query
|
||||||
handlers and repository:
|
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
|
- No database required (handlers use Moq; repository tests use DbMocker to
|
||||||
simulate SQL Server responses)
|
simulate SQL Server responses)
|
||||||
|
|
||||||
### Frontend Storybook Tests
|
### Frontend Storybook tests
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
cd web/frontend
|
cd web/frontend
|
||||||
@@ -110,7 +110,7 @@ npm run test:storybook
|
|||||||
toasts, and the theme gallery
|
toasts, and the theme gallery
|
||||||
- Runs in browser mode via Vitest and Storybook integration
|
- Runs in browser mode via Vitest and Storybook integration
|
||||||
|
|
||||||
### Frontend Playwright Storybook Tests
|
### Frontend Playwright Storybook tests
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
cd web/frontend
|
cd web/frontend
|
||||||
@@ -125,9 +125,9 @@ npm run test:storybook:playwright
|
|||||||
- The command will start or reuse the Storybook server defined in
|
- The command will start or reuse the Storybook server defined in
|
||||||
`playwright.storybook.config.ts`
|
`playwright.storybook.config.ts`
|
||||||
|
|
||||||
## Test Coverage
|
## Test coverage
|
||||||
|
|
||||||
### Current Coverage
|
### Current coverage
|
||||||
|
|
||||||
**Features.Auth.Tests**:
|
**Features.Auth.Tests**:
|
||||||
|
|
||||||
@@ -160,7 +160,7 @@ npm run test:storybook:playwright
|
|||||||
- Theme gallery rendering across Biergarten themes
|
- Theme gallery rendering across Biergarten themes
|
||||||
- Toast interactions and themed notification display
|
- Toast interactions and themed notification display
|
||||||
|
|
||||||
### Planned Coverage
|
### Planned coverage
|
||||||
|
|
||||||
- [ ] Password reset functionality
|
- [ ] Password reset functionality
|
||||||
- [ ] Beer post operations
|
- [ ] Beer post operations
|
||||||
@@ -168,7 +168,7 @@ npm run test:storybook:playwright
|
|||||||
- [ ] Image upload service
|
- [ ] Image upload service
|
||||||
- [ ] Frontend route integration coverage beyond Storybook stories
|
- [ ] Frontend route integration coverage beyond Storybook stories
|
||||||
|
|
||||||
## Testing Frameworks & Tools
|
## Testing frameworks and tools
|
||||||
|
|
||||||
### xUnit
|
### xUnit
|
||||||
|
|
||||||
@@ -200,9 +200,9 @@ npm run test:storybook:playwright
|
|||||||
- Simulates SQL Server responses
|
- Simulates SQL Server responses
|
||||||
- No real database required for unit tests
|
- No real database required for unit tests
|
||||||
|
|
||||||
## Test Structure
|
## Test structure
|
||||||
|
|
||||||
### API.Specs (Integration Tests)
|
### API.Specs (integration tests)
|
||||||
|
|
||||||
```
|
```
|
||||||
API.Specs/
|
API.Specs/
|
||||||
@@ -260,9 +260,9 @@ Each of the other three slices (`Features.Breweries.Tests`,
|
|||||||
`Features.UserManagement.Tests`, `Features.Emails.Tests`) follows the same
|
`Features.UserManagement.Tests`, `Features.Emails.Tests`) follows the same
|
||||||
shape: a `Commands/`/`Queries/` folder with one test file per handler.
|
shape: a `Commands/`/`Queries/` folder with one test file per handler.
|
||||||
|
|
||||||
## Writing Tests
|
## Writing tests
|
||||||
|
|
||||||
### Unit Test Example (xUnit)
|
### Unit test example (xUnit)
|
||||||
|
|
||||||
```csharp
|
```csharp
|
||||||
public class LoginHandlerTests
|
public class LoginHandlerTests
|
||||||
@@ -287,7 +287,7 @@ public class LoginHandlerTests
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
### Integration Test Example (Reqnroll)
|
### Integration test example (Reqnroll)
|
||||||
|
|
||||||
```gherkin
|
```gherkin
|
||||||
Scenario: User login with valid credentials
|
Scenario: User login with valid credentials
|
||||||
@@ -297,7 +297,7 @@ Scenario: User login with valid credentials
|
|||||||
And the response should contain a JWT token
|
And the response should contain a JWT token
|
||||||
```
|
```
|
||||||
|
|
||||||
## Continuous Integration
|
## Continuous integration
|
||||||
|
|
||||||
Tests run automatically in CI/CD pipelines using the test Docker Compose
|
Tests run automatically in CI/CD pipelines using the test Docker Compose
|
||||||
configuration:
|
configuration:
|
||||||
@@ -326,7 +326,7 @@ npm run test:storybook:playwright
|
|||||||
|
|
||||||
## Troubleshooting
|
## Troubleshooting
|
||||||
|
|
||||||
### Tests Failing Due to Database Connection
|
### Tests failing due to database connection
|
||||||
|
|
||||||
Ensure SQL Server is running and environment variables are set:
|
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
|
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
|
If port 1433 is in use, stop other SQL Server instances or modify the port in
|
||||||
`docker-compose.test.yaml`.
|
`docker-compose.test.yaml`.
|
||||||
|
|
||||||
### Stale Test Data
|
### Stale test data
|
||||||
|
|
||||||
Clean up test database:
|
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
|
docker compose --env-file web/.env.test -f web/docker-compose.test.yaml down -v
|
||||||
```
|
```
|
||||||
|
|
||||||
### View Container Logs
|
### View container logs
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
docker compose --env-file web/.env.test -f web/docker-compose.test.yaml logs <service-name>
|
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
|
1. **Isolation**: Each test should be independent and not rely on other tests
|
||||||
2. **Cleanup**: Use fixtures and dispose patterns for resource cleanup
|
2. **Cleanup**: Use fixtures and dispose patterns for resource cleanup
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
# Token Validation Architecture
|
# Token validation architecture
|
||||||
|
|
||||||
## Overview
|
## 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
|
- **Access Tokens**: Short-lived (1 hour) tokens for API authentication
|
||||||
- **Refresh Tokens**: Long-lived (21 days) tokens for obtaining new access
|
- **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
|
## Components
|
||||||
|
|
||||||
### Infrastructure Layer
|
### Infrastructure layer
|
||||||
|
|
||||||
#### [ITokenInfrastructure](../../web/backend/Infrastructure/Infrastructure.Jwt/ITokenInfrastructure.cs)
|
#### [ITokenInfrastructure](../../web/backend/Infrastructure/Infrastructure.Jwt/ITokenInfrastructure.cs)
|
||||||
|
|
||||||
@@ -30,7 +30,7 @@ Low-level JWT operations.
|
|||||||
- Algorithm: HS256 (HMAC-SHA256)
|
- Algorithm: HS256 (HMAC-SHA256)
|
||||||
- Validates token lifetime, signature, and well-formedness
|
- Validates token lifetime, signature, and well-formedness
|
||||||
|
|
||||||
### Features.Auth Slice
|
### Features.Auth slice
|
||||||
|
|
||||||
#### [ITokenService](../../web/backend/Features/Features.Auth/Services/ITokenService.cs)
|
#### [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
|
`JwtAuthenticationHandler` (host-level auth middleware) also depends on
|
||||||
it directly.
|
it directly.
|
||||||
|
|
||||||
### Integration Points
|
### Integration points
|
||||||
|
|
||||||
#### [ConfirmUserHandler](../../web/backend/Features/Features.Auth/Commands/ConfirmUser/ConfirmUserHandler.cs)
|
#### [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/confirm/resend?userId=...` - Resend the confirmation email
|
||||||
- `POST /api/auth/refresh` - Refresh access token
|
- `POST /api/auth/refresh` - Refresh access token
|
||||||
|
|
||||||
## Validation Security
|
## Validation security
|
||||||
|
|
||||||
### Token Secrets
|
### Token secrets
|
||||||
|
|
||||||
Three independent secrets enable:
|
Three independent secrets enable:
|
||||||
|
|
||||||
@@ -130,7 +130,7 @@ REFRESH_TOKEN_SECRET=... # Signs 21-day refresh tokens
|
|||||||
CONFIRMATION_TOKEN_SECRET=... # Signs 30-minute confirmation tokens
|
CONFIRMATION_TOKEN_SECRET=... # Signs 30-minute confirmation tokens
|
||||||
```
|
```
|
||||||
|
|
||||||
### Validation Checks
|
### Validation checks
|
||||||
|
|
||||||
Each token is validated for:
|
Each token is validated for:
|
||||||
|
|
||||||
@@ -139,7 +139,7 @@ Each token is validated for:
|
|||||||
3. **Claims Presence** - Required claims (Sub, UniqueName) must be present
|
3. **Claims Presence** - Required claims (Sub, UniqueName) must be present
|
||||||
4. **Claims Format** - UserId claim must be a valid GUID
|
4. **Claims Format** - UserId claim must be a valid GUID
|
||||||
|
|
||||||
### Error Handling
|
### Error handling
|
||||||
|
|
||||||
Validation failures return HTTP 401 Unauthorized:
|
Validation failures return HTTP 401 Unauthorized:
|
||||||
|
|
||||||
@@ -148,9 +148,9 @@ Validation failures return HTTP 401 Unauthorized:
|
|||||||
- Missing claims → "Invalid token"
|
- Missing claims → "Invalid token"
|
||||||
- Malformed claims → "Invalid token"
|
- Malformed claims → "Invalid token"
|
||||||
|
|
||||||
## Token Lifecycle
|
## Token lifecycle
|
||||||
|
|
||||||
### Access Token Lifecycle
|
### Access token lifecycle
|
||||||
|
|
||||||
1. **Generation**: During login (1-hour validity)
|
1. **Generation**: During login (1-hour validity)
|
||||||
2. **Usage**: Included in Authorization header on API requests
|
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
|
4. **Expiration**: Token becomes invalid after 1 hour
|
||||||
5. **Refresh**: Use refresh token to obtain new access token
|
5. **Refresh**: Use refresh token to obtain new access token
|
||||||
|
|
||||||
### Refresh Token Lifecycle
|
### Refresh token lifecycle
|
||||||
|
|
||||||
1. **Generation**: During login (21-day validity)
|
1. **Generation**: During login (21-day validity)
|
||||||
2. **Storage**: Client-side (secure storage)
|
2. **Storage**: Client-side (secure storage)
|
||||||
3. **Usage**: Posted to `/api/auth/refresh` endpoint
|
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
|
5. **Rotation**: New refresh token issued on successful refresh
|
||||||
6. **Expiration**: Token becomes invalid after 21 days
|
6. **Expiration**: Token becomes invalid after 21 days
|
||||||
|
|
||||||
### Confirmation Token Lifecycle
|
### Confirmation token lifecycle
|
||||||
|
|
||||||
1. **Generation**: During user registration (30-minute validity)
|
1. **Generation**: During user registration (30-minute validity)
|
||||||
2. **Delivery**: Emailed to user in confirmation link
|
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`.
|
All of the following live in `Features.Auth.Tests`.
|
||||||
|
|
||||||
### Unit Tests
|
### Unit tests
|
||||||
|
|
||||||
**Services/TokenServiceValidationTests.cs**
|
**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
|
- Sends a fresh confirmation email when the user exists and is unverified
|
||||||
- No-ops when the user doesn't exist or is already verified
|
- No-ops when the user doesn't exist or is already verified
|
||||||
|
|
||||||
### BDD Tests (Reqnroll)
|
### BDD tests (Reqnroll)
|
||||||
|
|
||||||
**TokenRefresh.feature**
|
**TokenRefresh.feature**
|
||||||
|
|
||||||
@@ -230,19 +230,19 @@ All of the following live in `Features.Auth.Tests`.
|
|||||||
- Invalid/expired access token rejection
|
- Invalid/expired access token rejection
|
||||||
- Token type mismatch (refresh used as access token)
|
- Token type mismatch (refresh used as access token)
|
||||||
|
|
||||||
## Future Enhancements
|
## Future enhancements
|
||||||
|
|
||||||
### Stretch Goals
|
### Stretch goals
|
||||||
|
|
||||||
1. **Middleware for Access Token Validation**
|
1. **Middleware for Access Token Validation**
|
||||||
- Automatically validate access tokens on protected routes
|
- Automatically validate access tokens on protected routes
|
||||||
- Populate HttpContext.User from token claims
|
- Populate HttpContext.User from token claims
|
||||||
- Return 401 for invalid/missing tokens
|
- Return 401 for invalid/missing tokens
|
||||||
|
|
||||||
2. **Token Blacklisting**
|
2. **Token Denylisting**
|
||||||
- Implement token revocation (e.g., on logout)
|
- Implement token revocation (for example, on logout)
|
||||||
- Store blacklisted tokens in cache/database
|
- Store denylisted tokens in cache/database
|
||||||
- Check blacklist during validation
|
- Check the denylist during validation
|
||||||
|
|
||||||
3. **Refresh Token Rotation Strategy**
|
3. **Refresh Token Rotation Strategy**
|
||||||
- Detect token reuse (replay attacks)
|
- Detect token reuse (replay attacks)
|
||||||
|
|||||||
@@ -1,8 +1,7 @@
|
|||||||
```bash
|
# RunPod container
|
||||||
touch runpod/start.sh
|
|
||||||
docker build \
|
Dockerfile, launcher (`start.sh`), and pod template for running the
|
||||||
--progress=plain \
|
Biergarten pipeline on a GPU host. See
|
||||||
-t biergarten-pipeline:latest \
|
[Docker / RunPod](../../../docs/pipeline/README.md#docker--runpod) in the
|
||||||
-f runpod/Dockerfile \
|
pipeline README for the full build and run instructions, environment
|
||||||
. 2>&1 | tee build.log
|
variable reference, and RunPod deployment notes.
|
||||||
```
|
|
||||||
|
|||||||
Reference in New Issue
Block a user