mirror of
https://github.com/aaronpo97/the-biergarten-app.git
synced 2026-10-14 17:54:42 +00:00
Update outdated documentation (#255)
This commit is contained in:
@@ -33,6 +33,7 @@ updates (`UPDATE ... WHERE ... AND RowVersion = @RowVersion`).
|
||||
| `City` | `Domain.Entities.City`, `Features.Locations` | Referenced by `BreweryPostLocation.CityID`; written via `ILocationRepository`, read via `ILocationRepository` and `IBreweryRepository`'s location joins, currently written only by `Database.Seed` |
|
||||
| `StateProvince` | `Domain.Entities.StateProvince`, `Features.Locations` | Referenced by `City.StateProvinceID`; created on demand by `ILocationRepository.GetOrCreateCityIdAsync`, read via `IBreweryRepository`'s location joins |
|
||||
| `Country` | `Domain.Entities.Country`, `Features.Locations` | Referenced by `StateProvince.CountryID`; created on demand by `ILocationRepository.GetOrCreateCityIdAsync`, read via `IBreweryRepository`'s location joins |
|
||||
| `Photo` | `Domain.Entities.Photo`, `Features.PhotoUpload` | Written via `IPhotoUploadRepository.CreateAsync`, sent as an `UploadPhotoCommand` from other features' handlers; not yet seeded by `Database.Seed` |
|
||||
|
||||
### Tables defined in the schema but not yet wired to a feature slice
|
||||
|
||||
@@ -41,7 +42,6 @@ repository or seed data yet:
|
||||
|
||||
| Table | Purpose (from schema) |
|
||||
| ------------------ | -------------------------------------------------------------------------------------------------------- |
|
||||
| `Photo` | A photo uploaded by a user account |
|
||||
| `UserAvatar` | Links a `UserAccount` to a `Photo` as its avatar |
|
||||
| `UserFollow` | One user following another (`CannotFollowOwnAccount` check constraint) |
|
||||
| `BeerStyle` | A beer style/category; has a `Domain.Entities.BeerStyle` type, but no repository yet |
|
||||
@@ -66,8 +66,9 @@ an already-applied script means it won't re-run against existing databases.
|
||||
`Database.Seed` populates a target database from data produced by the C++
|
||||
pipeline (see [Pipeline README](../pipeline/README.md)): countries,
|
||||
state/provinces, and cities (created on demand while resolving each brewery's
|
||||
location), user accounts, and brewery posts with locations. It only seeds
|
||||
tables that have a feature slice to write through (see the table above).
|
||||
location), user accounts, and brewery posts with locations. It doesn't call
|
||||
every feature slice that has a write path — `Photo`, for example, has a
|
||||
repository (`Features.PhotoUpload`) but no seed data.
|
||||
|
||||
## Database error handling
|
||||
|
||||
|
||||
@@ -29,6 +29,7 @@ representation.
|
||||
- Swagger UI enabled
|
||||
- Seed data included
|
||||
- Local Mailpit SMTP server for dev email testing
|
||||
- Local SeaweedFS S3-compatible object storage for photo uploads
|
||||
- `CLEAR_DATABASE=true` (drops and recreates schema)
|
||||
|
||||
**Services**:
|
||||
@@ -39,6 +40,7 @@ database.migrations # DbUp migrations
|
||||
database.seed # Seed initial data
|
||||
api.core # Web API (ports 8080, 8081)
|
||||
mailpit # Local dev SMTP server + UI (port 8025)
|
||||
seaweedfs # S3-compatible object storage (ports 8333, 8888, 9333, 23646)
|
||||
```
|
||||
|
||||
**Start Development Environment**:
|
||||
@@ -53,6 +55,10 @@ docker compose --env-file web/.env.dev -f web/docker-compose.dev.yaml up -d
|
||||
- Health Check: http://localhost:8080/health
|
||||
- Mailpit UI: http://localhost:8025
|
||||
- SQL Server: localhost:1434 (sa credentials from .env.dev)
|
||||
- SeaweedFS S3 API: http://localhost:8333 (credentials from
|
||||
`SEAWEEDFS_ACCESS_KEY_ID`/`SEAWEEDFS_SECRET_ACCESS_KEY` in .env.dev)
|
||||
- SeaweedFS Filer UI: http://localhost:8888
|
||||
- SeaweedFS Master UI: http://localhost:9333
|
||||
|
||||
**Stop Environment**:
|
||||
|
||||
@@ -182,8 +188,12 @@ 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]
|
||||
E[seaweedfs: container started] --> D
|
||||
```
|
||||
|
||||
In development, `api.core` also depends on `seaweedfs` (`condition:
|
||||
service_started`, not a health check) alongside `database.seed`.
|
||||
|
||||
**Health Check Example** (SQL Server):
|
||||
|
||||
```yaml
|
||||
@@ -216,6 +226,8 @@ api.core:
|
||||
|
||||
- `sqlserverdata-dev` - Database files persist between restarts
|
||||
- `nuget-cache-dev` - NuGet package cache (speeds up builds)
|
||||
- `seaweedfsdata-dev` - SeaweedFS object storage data persists between
|
||||
restarts
|
||||
|
||||
**Testing**:
|
||||
|
||||
@@ -262,6 +274,10 @@ environment:
|
||||
ACCESS_TOKEN_SECRET: "${ACCESS_TOKEN_SECRET}"
|
||||
REFRESH_TOKEN_SECRET: "${REFRESH_TOKEN_SECRET}"
|
||||
CONFIRMATION_TOKEN_SECRET: "${CONFIRMATION_TOKEN_SECRET}"
|
||||
SEAWEEDFS_SERVICE_URL: "${SEAWEEDFS_SERVICE_URL}"
|
||||
SEAWEEDFS_ACCESS_KEY_ID: "${SEAWEEDFS_ACCESS_KEY_ID}"
|
||||
SEAWEEDFS_SECRET_ACCESS_KEY: "${SEAWEEDFS_SECRET_ACCESS_KEY}"
|
||||
SEAWEEDFS_BUCKET: "${SEAWEEDFS_BUCKET}"
|
||||
```
|
||||
|
||||
For complete list, see [Environment Variables](environment-variables.md).
|
||||
|
||||
@@ -35,8 +35,9 @@ Environment-specific `.env` files loaded via `env_file:` in docker-compose.yaml:
|
||||
`web/generate-env.sh` copies `.env.example` to a target file and replaces the
|
||||
secret-bearing values (`DB_PASSWORD`, `ACCESS_TOKEN_SECRET`,
|
||||
`REFRESH_TOKEN_SECRET`, `CONFIRMATION_TOKEN_SECRET`, `SMTP_USERNAME`,
|
||||
`SMTP_PASSWORD`) with freshly randomized ones via `openssl`. Non-secret values
|
||||
are left untouched. The output file is written with `600` permissions.
|
||||
`SMTP_PASSWORD`, `SEAWEEDFS_ACCESS_KEY_ID`, `SEAWEEDFS_SECRET_ACCESS_KEY`)
|
||||
with freshly randomized ones via `openssl`. Non-secret values are left
|
||||
untouched. The output file is written with `600` permissions.
|
||||
|
||||
```bash
|
||||
cd web
|
||||
@@ -221,6 +222,25 @@ SMTP_FROM_NAME=The Biergarten App # Optional, defaults to "The Biergarten
|
||||
- **Production**: point at a real provider (SendGrid, Mailgun, Amazon SES, and
|
||||
so on)
|
||||
|
||||
### File storage configuration (backend)
|
||||
|
||||
Read by `Infrastructure.FileUpload/S3FileStorageProvider.cs` for uploading and
|
||||
retrieving photos via an S3-compatible object store.
|
||||
|
||||
```bash
|
||||
SEAWEEDFS_SERVICE_URL=http://seaweedfs:8333 # Required, no default
|
||||
SEAWEEDFS_ACCESS_KEY_ID=admin # Required, no default
|
||||
SEAWEEDFS_SECRET_ACCESS_KEY=secret # Required, no default
|
||||
SEAWEEDFS_BUCKET=my-bucket # Required, no default
|
||||
```
|
||||
|
||||
- **Implementation**: `S3FileStorageProvider` throws on construction if any of
|
||||
these four values is missing
|
||||
- **Local dev**: point at the `seaweedfs` Docker service (S3 API on port 8333)
|
||||
- **Production**: point at any S3-compatible endpoint (AWS S3, a hosted
|
||||
SeaweedFS cluster, and so on); `ForcePathStyle` is always enabled, which
|
||||
SeaweedFS requires and AWS S3 also accepts
|
||||
|
||||
## Docker-specific variables
|
||||
|
||||
### SQL Server container
|
||||
@@ -288,6 +308,10 @@ reference only and is not run as part of the active stack.
|
||||
| `SMTP_USE_SSL` | ✓ | | ✓ | No | Defaults to `true` |
|
||||
| `SMTP_FROM_EMAIL` | ✓ | | ✓ | Yes | Email sender address |
|
||||
| `SMTP_FROM_NAME` | ✓ | | ✓ | No | Defaults to `The Biergarten` |
|
||||
| `SEAWEEDFS_SERVICE_URL` | ✓ | | ✓ | Yes | S3-compatible endpoint URL |
|
||||
| `SEAWEEDFS_ACCESS_KEY_ID` | ✓ | | ✓ | Yes | S3 access key |
|
||||
| `SEAWEEDFS_SECRET_ACCESS_KEY` | ✓ | | ✓ | Yes | S3 secret key |
|
||||
| `SEAWEEDFS_BUCKET` | ✓ | | ✓ | Yes | S3 bucket name |
|
||||
| `API_BASE_URL` | | ✓ | | Yes | Website-to-API base URL |
|
||||
| `SESSION_SECRET` | | ✓ | | Yes | Website session signing |
|
||||
| `NODE_ENV` | | ✓ | | No | Runtime mode |
|
||||
@@ -309,8 +333,10 @@ reference only and is not run as part of the active stack.
|
||||
Variables are validated at startup:
|
||||
|
||||
- Missing required variables (`ACCESS_TOKEN_SECRET`, `REFRESH_TOKEN_SECRET`,
|
||||
`CONFIRMATION_TOKEN_SECRET`, `SMTP_HOST`, `SMTP_FROM_EMAIL`, DB connection
|
||||
values) cause the application to fail with an `InvalidOperationException`
|
||||
`CONFIRMATION_TOKEN_SECRET`, `SMTP_HOST`, `SMTP_FROM_EMAIL`,
|
||||
`SEAWEEDFS_SERVICE_URL`, `SEAWEEDFS_ACCESS_KEY_ID`,
|
||||
`SEAWEEDFS_SECRET_ACCESS_KEY`, `SEAWEEDFS_BUCKET`, DB connection values)
|
||||
cause the application to fail with an `InvalidOperationException`
|
||||
- No minimum length is enforced on the token secrets in code; the "minimum 32
|
||||
characters" guidance above is a recommendation, not an enforced check
|
||||
|
||||
@@ -347,6 +373,12 @@ SMTP_USE_SSL=false
|
||||
SMTP_FROM_EMAIL=noreply@thebiergarten.app
|
||||
SMTP_FROM_NAME=The Biergarten App
|
||||
|
||||
# SeaweedFS (S3-compatible storage)
|
||||
SEAWEEDFS_SERVICE_URL=http://seaweedfs:8333
|
||||
SEAWEEDFS_ACCESS_KEY_ID=<generated-with-openssl>
|
||||
SEAWEEDFS_SECRET_ACCESS_KEY=<generated-with-openssl>
|
||||
SEAWEEDFS_BUCKET=my-bucket
|
||||
|
||||
# Migration
|
||||
CLEAR_DATABASE=true
|
||||
|
||||
|
||||
@@ -58,14 +58,16 @@ section for details on `generate-env.sh`.
|
||||
docker compose --env-file web/.env.dev -f web/docker-compose.dev.yaml up -d
|
||||
```
|
||||
|
||||
This starts SQL Server, migrations, seeding, the API, and Mailpit (local dev
|
||||
SMTP).
|
||||
This starts SQL Server, migrations, seeding, the API, Mailpit (local dev
|
||||
SMTP), and SeaweedFS (local dev S3-compatible object storage for photo
|
||||
uploads).
|
||||
|
||||
Available endpoints:
|
||||
|
||||
- API Swagger: http://localhost:8080/swagger
|
||||
- Health Check: http://localhost:8080/health
|
||||
- Mailpit UI: http://localhost:8025
|
||||
- SeaweedFS S3 API: http://localhost:8333
|
||||
|
||||
### 4. Start the active frontend
|
||||
|
||||
@@ -124,6 +126,10 @@ export ACCESS_TOKEN_SECRET="<generated>"
|
||||
export REFRESH_TOKEN_SECRET="<generated>"
|
||||
export CONFIRMATION_TOKEN_SECRET="<generated>"
|
||||
export WEBSITE_BASE_URL="http://localhost:3000"
|
||||
export SEAWEEDFS_SERVICE_URL="http://localhost:8333"
|
||||
export SEAWEEDFS_ACCESS_KEY_ID="<generated>"
|
||||
export SEAWEEDFS_SECRET_ACCESS_KEY="<generated>"
|
||||
export SEAWEEDFS_BUCKET="my-bucket"
|
||||
```
|
||||
|
||||
### 2. Run migrations and seed
|
||||
|
||||
@@ -11,9 +11,9 @@ The project uses a multi-layered testing approach across backend and frontend:
|
||||
live, seeded database
|
||||
- **Features.\*.Tests** - One unit test project per backend feature slice
|
||||
(`Features.Auth.Tests`, `Features.Breweries.Tests`,
|
||||
`Features.UserManagement.Tests`, `Features.Emails.Tests`), covering that
|
||||
slice's command/query handlers, with dependencies mocked via Moq (no real
|
||||
database required)
|
||||
`Features.UserManagement.Tests`, `Features.Emails.Tests`,
|
||||
`Features.PhotoUpload.Tests`), covering that slice's command/query
|
||||
handlers, with dependencies mocked via Moq (no real database required)
|
||||
- **Storybook Vitest project** - Browser-based interaction tests for shared
|
||||
website stories
|
||||
- **Storybook Playwright suite** - Browser checks against Storybook-rendered
|
||||
@@ -49,6 +49,7 @@ cat test-results/Features.Auth.Tests.trx
|
||||
cat test-results/Features.Breweries.Tests.trx
|
||||
cat test-results/Features.UserManagement.Tests.trx
|
||||
cat test-results/Features.Emails.Tests.trx
|
||||
cat test-results/Features.PhotoUpload.Tests.trx
|
||||
```
|
||||
|
||||
### Clean up
|
||||
@@ -86,6 +87,7 @@ dotnet test Features/Features.Auth.Tests/Features.Auth.Tests.csproj
|
||||
dotnet test Features/Features.Breweries.Tests/Features.Breweries.Tests.csproj
|
||||
dotnet test Features/Features.UserManagement.Tests/Features.UserManagement.Tests.csproj
|
||||
dotnet test Features/Features.Emails.Tests/Features.Emails.Tests.csproj
|
||||
dotnet test Features/Features.PhotoUpload.Tests/Features.PhotoUpload.Tests.csproj
|
||||
|
||||
# Or run all of them at once via the solution:
|
||||
for proj in Features/*.Tests; do dotnet test "$proj"; done
|
||||
@@ -175,6 +177,14 @@ The default (`headless: true`) is set in `web/frontend/vite.config.ts`.
|
||||
|
||||
- Registration and resend-confirmation email dispatch handlers
|
||||
|
||||
**Features.PhotoUpload.Tests**:
|
||||
|
||||
- Upload command handler: storage key construction, content type passthrough,
|
||||
and photo persistence via the mocked storage provider and repository
|
||||
- Upload validator: min/max file size bounds, and PNG/JPEG/WebP signature
|
||||
checks (including rejection of files with a mismatched or missing
|
||||
signature)
|
||||
|
||||
**Frontend UI Coverage**:
|
||||
|
||||
- Shared submit button states
|
||||
@@ -188,7 +198,6 @@ The default (`headless: true`) is set in `web/frontend/vite.config.ts`.
|
||||
- [ ] Password reset functionality
|
||||
- [ ] Beer post operations
|
||||
- [ ] User follow/unfollow
|
||||
- [ ] Image upload service
|
||||
- [ ] Frontend route integration coverage beyond Storybook stories
|
||||
|
||||
## Testing frameworks and tools
|
||||
@@ -276,9 +285,11 @@ Features.Auth.Tests/
|
||||
└── TokenServiceValidationTests.cs
|
||||
```
|
||||
|
||||
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.
|
||||
Each of the other slices (`Features.Breweries.Tests`,
|
||||
`Features.UserManagement.Tests`, `Features.Emails.Tests`,
|
||||
`Features.PhotoUpload.Tests`) follows the same shape: a `Commands/`/`Queries/`
|
||||
folder with one test file per handler (`Features.PhotoUpload.Tests` also adds
|
||||
a validator test file alongside its handler test).
|
||||
|
||||
## Writing tests
|
||||
|
||||
|
||||
Reference in New Issue
Block a user