Add user avatar and profile feature, consolidate Auth and UserManagement features (#257)

This commit is contained in:
Aaron Po
2026-08-31 00:11:48 -04:00
committed by GitHub
parent ade2128a73
commit 39c5a89ca7
130 changed files with 1005 additions and 613 deletions

View File

@@ -19,17 +19,22 @@ package "API.Core (thin host)" #E3F2FD {
}
package "Feature Slices" #F3E5F5 {
[Features.Auth] as AuthSlice
[Features.Users] as UsersSlice
[Features.Breweries] as BrewerySlice
[Features.UserManagement] as UserSlice
[Features.Emails] as EmailsSlice
[Features.Locations] as LocationsSlice
note right of AuthSlice
note right of UsersSlice
Each slice owns its own:
- Controller (HTTP endpoints)
- Commands/Queries + Handlers
- Validators
- Repository (Dapper)
Features.Users covers auth
(Commands/Authentication),
account management
(Commands/Account), and
profile/avatar (Commands/Profile)
end note
note right of EmailsSlice
No controller - invoked only
@@ -70,6 +75,7 @@ package "Domain Layer" #E8F5E9 {
[Domain.Exceptions] as DomainExceptions
note right of Domain
- UserAccount, UserCredential, UserVerification
- UserProfile, UserAvatar
- BreweryPost, BreweryPostLocation
- City, StateProvince, Country
- BeerPost, BeerStyle
@@ -84,29 +90,24 @@ database "SQL Server" {
node "Database.Seed\n(separate console app)" as Seed
' Relationships
API ..> AuthSlice : AddApplicationPart\n+ MediatR scan
API ..> UsersSlice : AddApplicationPart\n+ MediatR scan
API ..> BrewerySlice : AddApplicationPart\n+ MediatR scan
API ..> UserSlice : AddApplicationPart\n+ MediatR scan
API ..> EmailsSlice : MediatR scan only\n(no controller)
Seed ..> AuthSlice : AddFeaturesAuth()\n(DI only, no API/MediatR)
Seed ..> UsersSlice : AddFeaturesUsers()\n(DI only, no API/MediatR)
Seed ..> BrewerySlice : AddFeaturesBreweries()
Seed ..> LocationsSlice : AddFeaturesLocations()
AuthSlice --> Sql
AuthSlice --> JWT
AuthSlice --> PwdHash
AuthSlice --> SharedContracts
AuthSlice --> SharedApp
UsersSlice --> Sql
UsersSlice --> JWT
UsersSlice --> PwdHash
UsersSlice --> SharedContracts
UsersSlice --> SharedApp
BrewerySlice --> Sql
BrewerySlice --> SharedContracts
BrewerySlice --> SharedApp
UserSlice --> Sql
UserSlice --> SharedContracts
UserSlice --> SharedApp
LocationsSlice --> Sql
EmailsSlice --> Email
@@ -114,21 +115,19 @@ EmailsSlice --> EmailTemplates
EmailsSlice --> SharedApp
' The one cross-slice interaction: a MediatR command contract, not a project reference
AuthSlice ..> SharedApp : sends\nSendRegistrationEmailCommand
UsersSlice ..> SharedApp : sends\nSendRegistrationEmailCommand
EmailsSlice ..> SharedApp : handles\nSendRegistrationEmailCommand
Sql --> Domain
AuthSlice --> Tables : Dapper SQL
UsersSlice --> Tables : Dapper SQL
BrewerySlice --> Tables : Dapper SQL
UserSlice --> Tables : Dapper SQL
LocationsSlice --> Tables : Dapper SQL
AuthSlice --> Domain
UsersSlice --> Domain
BrewerySlice --> Domain
UserSlice --> Domain
LocationsSlice --> Domain
AuthSlice --> DomainExceptions
UsersSlice --> DomainExceptions
' Notes
note left of Sql
@@ -138,7 +137,7 @@ note left of Sql
no stored procedures
end note
note bottom of AuthSlice
note bottom of UsersSlice
No Features.* project
ever references another
Features.* project directly

View File

@@ -39,11 +39,23 @@ package "User & Auth" {
* VerificationDateTime: DATETIME
}
entity "UserAvatar (*)" as Avatar {
* UserAvatarID: UNIQUEIDENTIFIER <<PK>>
entity "UserProfile" as Profile {
* UserProfileID: UNIQUEIDENTIFIER <<PK>>
--
* UserAccountID: UNIQUEIDENTIFIER <<FK, UNIQUE>>
* Biography: NVARCHAR(MAX)
}
entity "UserAvatar" as Avatar {
* UserAvatarID: UNIQUEIDENTIFIER <<PK>>
--
* UserProfileID: UNIQUEIDENTIFIER <<FK>>
* PhotoID: UNIQUEIDENTIFIER <<FK>>
* ValidFrom: DATETIME2(3)
ValidTo: DATETIME2(3)
..
At most one row per UserProfileID
has ValidTo IS NULL (active avatar)
}
entity "UserFollow (*)" as Follow {
@@ -163,7 +175,8 @@ package "Beer (*)" {
' Relationships: User & Auth
User ||--o{ Cred : "has"
User ||--o| Verify : "has"
User ||--o| Avatar : "has"
User ||--o| Profile : "has"
Profile ||--o{ Avatar : "has (history)"
Avatar }o--|| Photo : "refers to"
User ||--o{ Follow : "follows (UserAccountID)"
User ||--o{ Follow : "followed by (FollowingID)"
@@ -193,9 +206,9 @@ note bottom of User
Auth reads/writes (registration, login, credential
rotation, verification) go through ASP.NET Core
Identity's UserManager<ApplicationUser>, backed by
Features.Auth's DapperUserStore - inline SQL, no
stored procedures. Profile data still goes through
Features.UserManagement's UserAccountRepository.
Features.Users' DapperUserStore - inline SQL, no
stored procedures. Profile/avatar data goes through
Features.Users' UserProfileRepository.
end note
note bottom of Brewery

View File

@@ -130,9 +130,8 @@ package "Test Environment\n(docker-compose.test.yaml)" #FFF3E0 {
restores/builds against Core.slnx
then runs `dotnet test` for every
Features/*.Tests project in turn:
- Features.Auth.Tests
- Features.Users.Tests
- Features.Breweries.Tests
- Features.UserManagement.Tests
- Features.Emails.Tests
Uses: Moq to mock each handler's
@@ -261,9 +260,8 @@ package "Minimal Environment\n(docker-compose.min.yaml)" #FFF9C4 {
folder "test-results/\n(mounted volume)" as Results {
file "api-specs/\n results.trx" as Result1
file "Features.Auth.Tests.trx" as Result2
file "Features.Users.Tests.trx" as Result2
file "Features.Breweries.Tests.trx" as Result3
file "Features.UserManagement.Tests.trx" as Result4
file "Features.Emails.Tests.trx" as Result5
note bottom