Features/public biography endpoint (#316)

This commit is contained in:
Aaron Po
2026-09-26 19:51:10 -04:00
committed by GitHub
parent e1b6cba1d4
commit c5972b9f04
20 changed files with 158 additions and 104 deletions

View File

@@ -176,7 +176,9 @@ brewery photo upload command) rather than bound to an HTTP route directly.
- All business logic for that feature lives in its command/query handlers
- No direct controller-to-repository calls; everything flows through MediatR
- Read endpoints return a dedicated `Dto`, never the domain entity directly
- Read endpoints return a dedicated `Dto` rather than the domain entity
directly. The one exception is `UserController.GetAuthenticated`, which
returns the caller's own `UserAccount` entity
#### Shared projects (`Shared.Contracts`, `Shared.Application`)
@@ -187,7 +189,7 @@ needs it, or because duplicating it four times would be worse than sharing it
| Project | Purpose |
| -------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `Shared.Contracts` | `ResponseBody<T>`/`ResponseBody`, the API response envelope every controller returns |
| `Shared.Contracts` | `ResponseBody<T>`/`ResponseBody`, the API response envelope `AuthController`, `BreweryController`, and `CityController` return; `UserController`'s read endpoints return their DTOs unwrapped |
| `Shared.Application` | `ValidationBehavior<TRequest,TResponse>`, the MediatR pipeline behavior that runs FluentValidation before a handler executes |
**Rules**:

View File

@@ -76,7 +76,7 @@ updates (`UPDATE ... WHERE ... AND RowVersion = @RowVersion`).
| `UserAccount` | `Domain.Entities.UserAccount`, `Features.Users` | `Username` and `Email` are unique |
| `UserCredential` | `Domain.Entities.UserCredential`, `Features.Users` | Argon2id password hash; `IsRevoked`/`RevokedAt` track credential rotation, not JWTs |
| `UserVerification` | `Domain.Entities.UserVerification`, `Features.Users` | Written when a user confirms their email |
| `UserProfile` | `Domain.Entities.UserProfile`, `Features.Users` | 1:1 with `UserAccount`; holds the user's biography; written via `IUserProfileRepository` |
| `UserProfile` | `Domain.Entities.UserProfile`, `Features.Users` | 1:1 with `UserAccount`; holds the user's biography; written via `IUserProfileRepository`, read via `IUserListRepository.GetByIdAsync`'s left join |
| `UserAvatar` | `Domain.Entities.UserAvatar`, `Features.Users` | Links a `UserProfile` to a `Photo` as its avatar; written via `IUserProfileRepository.SaveAvatarAsync` |
| `BreweryPost` | `Domain.Entities.BreweryPost`, `Features.Breweries` | A user-submitted brewery listing |
| `BreweryPostLocation` | `Domain.Entities.BreweryPostLocation`, `Features.Breweries` | 1:1 with `BreweryPost`; `Coordinates` is a `GEOGRAPHY` column, read via a plain Dapper query that selects `CONVERT(varbinary(max), Coordinates)`, since Dapper can't deserialize SQL Server UDTs directly |

View File

@@ -250,7 +250,7 @@ API.Specs/
│ ├── TokenRefresh.feature # Refresh token scenarios
│ ├── AccessTokenValidation.feature # Protected endpoint access scenarios
│ ├── NotFound.feature # 404 handling
│ └── PublicUserProfile.feature # Public profile & locked-down account scenarios
│ └── PublicUserProfile.feature # Public profile & authenticated account scenarios
├── Steps/
│ ├── AuthSteps.cs # Step definitions for the Auth features
│ ├── UserSteps.cs # Step definitions for the public profile & user-account features
@@ -282,17 +282,25 @@ Scenario: Successful user registration
Features.Users.Tests/
├── Commands/
│ ├── RegisterUserHandlerTests.cs
│ ├── RegisterUserValidatorTests.cs
│ ├── ConfirmUserHandlerTests.cs
│ ├── ResendConfirmationEmailHandlerTests.cs
│ ├── RefreshTokenHandlerTests.cs
│ ├── RefreshTokenValidatorTests.cs
│ ├── LoginValidatorTests.cs # tests LoginCommand's validator
│ ├── UpdateUsernameHandlerTests.cs
│ ├── UpdateUsernameValidatorTests.cs
│ ├── UpdateEmailHandlerTests.cs
│ ├── UpdateEmailValidatorTests.cs
│ ├── UpdatePasswordHandlerTests.cs
│ ├── UpdatePasswordValidatorTests.cs
│ ├── UpdateProfileHandlerTests.cs
│ ├── UpdateProfileValidatorTests.cs
│ └── DeleteAccountHandlerTests.cs
├── Queries/
│ ├── LoginHandlerTests.cs # tests LoginCommand, despite the folder name
│ ├── GetAllUsersHandlerTests.cs
│ ├── GetPublicUserProfileByIdHandlerTests.cs
│ └── GetUserByIdHandlerTests.cs
├── Services/
│ ├── TokenServiceRefreshTests.cs
@@ -300,10 +308,13 @@ Features.Users.Tests/
└── TestSupport/ # shared test fixtures/helpers
```
Each of the other slices (`Features.Breweries.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).
`Features.Breweries.Tests` and `Features.PhotoUpload.Tests` follow the same
shape: a `Commands/`/`Queries/` folder with one test file per handler, plus a
`*ValidatorTests.cs` file beside each handler whose command or query has a
FluentValidation validator (`Features.Breweries.Tests` has one of these in
`Queries/` as well, for the nearby-search query). `Features.Emails.Tests`
differs: its handlers respond to MediatR notifications, so its test files live
under `Notifications/`.
## Writing tests