Each SERVICE_DOCS.md documents: Overview, API Endpoints, Commands, Queries, Domain Model, Database Schema, Integration Events, Dependencies, Configuration. Generated by 23 parallel audit agents reading actual source code. Key corrections from audit: - inventory-service: 12 commands/6 queries (was listed as scaffold) - promotion-service: 12 commands/10 queries (was listed as 0) - mission-service: 4 commands/7 queries (was listed as 0) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
26 KiB
SocialService - Service Documentation
1. Overview
The Social Service manages user-to-user social relationships within the GoodGo platform. It handles friendships (bidirectional, requires acceptance), following (unidirectional, auto-accepted), user blocking, and graph-based queries such as mutual friends and friend suggestions.
- Service Name: social-service-net
- Port: 5009 (Development, HTTP)
- Docker Port: 8080
- Database: PostgreSQL -
social_service(Neon cloud) - Database (local): configurable via
DATABASE_URLenv var - Framework: .NET 10.0, C# 14
- Architecture: Clean Architecture + CQRS (MediatR)
- Solution File:
SocialService.slnx - Migration:
20260112164356_InitialCreate(auto-applied on startup)
2. API Endpoints
2.1 RelationshipsController
Base Route: api/v1/relationships
| Method | Route | Description | Request Body | Response |
|---|---|---|---|---|
| GET | /users/{userId}/friends?skip=0&take=20 |
Get friends of a user (paginated) | - | GetFriendsResult |
| POST | /friend-requests |
Send a friend request | SendFriendRequestRequest |
SendFriendRequestResult (201) |
| PUT | /friend-requests/{relationshipId} |
Accept or reject a friend request | RespondToFriendRequestRequest |
{ success: bool } |
| GET | /users/{userId1}/mutual-friends/{userId2} |
Get mutual friends between two users | - | GetMutualFriendsResult |
| GET | /users/{userId}/suggestions?limit=10 |
Get friend suggestions for a user | - | GetFriendSuggestionsResult |
| POST | /follow |
Follow a user | FollowUserRequest |
FollowUserResult (201) |
| DELETE | /follow |
Unfollow a user | UnfollowUserRequest |
{ success: bool } |
Request DTOs (defined in RelationshipsController.cs):
SendFriendRequestRequest(Guid RequesterId, Guid AddresseeId)RespondToFriendRequestRequest(Guid UserId, bool Accept)FollowUserRequest(Guid FollowerId, Guid FolloweeId)UnfollowUserRequest(Guid FollowerId, Guid FolloweeId)
2.2 BlocksController
Base Route: api/v1/blocks
| Method | Route | Description | Request Body | Response |
|---|---|---|---|---|
| POST | / |
Block a user | BlockUserRequest |
BlockUserResult (201) |
| DELETE | / |
Unblock a user | UnblockUserRequest |
{ success: bool } |
| GET | /users/{userId}?skip=0&take=20 |
Get list of blocked users (paginated) | - | GetBlockedUsersResult |
Request DTOs (defined in BlocksController.cs):
BlockUserRequest(Guid BlockerId, Guid BlockedId, string? Reason = null)UnblockUserRequest(Guid BlockerId, Guid BlockedId)
2.3 AdminController
Base Route: api/v1/admin/social
| Method | Route | Description | Query Params | Response |
|---|---|---|---|---|
| GET | /relationships?skip=0&take=20&status=&type= |
Get all relationships (filtered, paginated) | skip, take, status (pending/accepted/rejected/cancelled), type (friendship/following) |
GetAllRelationshipsResult |
| GET | /relationships/{id:guid} |
Get relationship by ID | - | RelationshipDetailDto or 404 |
| DELETE | /relationships/{id:guid} |
Admin delete a relationship | - | { success, message } or 404 |
| GET | /blocks?skip=0&take=20 |
Get all blocks (paginated) | skip, take |
GetAllBlocksResult |
| DELETE | /blocks/{id:guid} |
Admin delete a block | - | { success, message } or 404 |
| GET | /statistics |
Get social statistics overview | - | SocialStatisticsDto |
2.4 Health Check Endpoints
| Route | Description |
|---|---|
/health |
Full health check (includes PostgreSQL) |
/health/live |
Liveness probe (app is running, no dependency checks) |
/health/ready |
Readiness probe (includes PostgreSQL) |
3. Commands
All commands are MediatR IRequest<T> records. The MediatR pipeline wraps commands in transactions automatically (via TransactionBehavior, which skips requests ending in "Query").
3.1 SendFriendRequestCommand
- File:
Application/Commands/SendFriendRequestCommand.cs - Parameters:
Guid RequesterId,Guid AddresseeId - Returns:
SendFriendRequestResult(Guid RelationshipId, string Status) - Behavior:
- Checks for blocks between users (throws if blocked)
- Checks for existing friendship (throws if already friends or pending)
- If reverse pending request exists, auto-accepts it (mutual request)
- Otherwise creates new Relationship with
Pendingstatus
- Dependencies:
IRelationshipRepository,IUserBlockRepository
3.2 RespondToFriendRequestCommand
- File:
Application/Commands/RespondToFriendRequestCommand.cs - Parameters:
Guid RelationshipId,Guid UserId,bool Accept - Returns:
bool - Behavior:
- Verifies relationship exists (throws if not found)
- Verifies user is the addressee (throws if not)
- Calls
Accept()orReject()on the Relationship entity
- Dependencies:
IRelationshipRepository
3.3 FollowUserCommand
- File:
Application/Commands/FollowUserCommand.cs - Parameters:
Guid FollowerId,Guid FolloweeId - Returns:
FollowUserResult(Guid RelationshipId, bool Success) - Behavior:
- Prevents self-follow
- Checks for blocks between users
- Checks for existing follow (throws if already following)
- Creates Relationship with
Followingtype, callsAccept()(auto-accepted)
- Dependencies:
IRelationshipRepository,IUserBlockRepository
3.4 UnfollowUserCommand
- File:
Application/Commands/UnfollowUserCommand.cs - Parameters:
Guid FollowerId,Guid FolloweeId - Returns:
bool - Behavior:
- Finds existing Following relationship (throws if not following)
- Calls
Remove()on the Relationship entity (sets status to Cancelled)
- Dependencies:
IRelationshipRepository
3.5 BlockUserCommand
- File:
Application/Commands/BlockUserCommand.cs - Parameters:
Guid BlockerId,Guid BlockedId,string? Reason = null - Returns:
BlockUserResult(Guid BlockId, bool Success) - Behavior:
- Prevents self-block
- Checks for existing block (throws if already blocked)
- Creates UserBlock entity
- Removes all existing relationships between the two users (friendships and followings in both directions) by calling
Remove()on each
- Dependencies:
IUserBlockRepository,IRelationshipRepository
3.6 UnblockUserCommand
- File:
Application/Commands/UnblockUserCommand.cs - Parameters:
Guid BlockerId,Guid BlockedId - Returns:
bool - Behavior:
- Finds existing block (throws if not blocked)
- Removes the UserBlock entity
- Dependencies:
IUserBlockRepository
3.7 AdminDeleteRelationshipCommand
- File:
Application/Commands/AdminDeleteRelationshipCommand.cs - Parameters:
Guid RelationshipId - Returns:
bool - Behavior: Hard-deletes a relationship from the database. Returns false if not found.
- Dependencies:
IRelationshipRepository
3.8 AdminDeleteBlockCommand
- File:
Application/Commands/AdminDeleteBlockCommand.cs - Parameters:
Guid BlockId - Returns:
bool - Behavior: Hard-deletes a block from the database. Returns false if not found.
- Dependencies:
IUserBlockRepository
4. Queries
4.1 GetFriendsQuery
- File:
Application/Queries/GetFriendsQuery.cs - Parameters:
Guid UserId,int Skip = 0,int Take = 20 - Returns:
GetFriendsResult(IEnumerable<FriendDto> Friends, int TotalCount) - FriendDto:
(Guid UserId, string DisplayName, string? AvatarUrl, DateTime FriendsSince) - Behavior: Gets accepted friendships where user is requester or addressee, enriches with UserProfile data.
4.2 GetMutualFriendsQuery
- File:
Application/Queries/GetMutualFriendsQuery.cs - Parameters:
Guid UserId1,Guid UserId2 - Returns:
GetMutualFriendsResult(IEnumerable<FriendDto> MutualFriends, int Count) - Behavior: Uses
IGraphQueryServicewith raw SQL (CTE) to find users who are friends with both users. Enriches with UserProfile data.
4.3 GetFriendSuggestionsQuery
- File:
Application/Queries/GetFriendSuggestionsQuery.cs - Parameters:
Guid UserId,int Limit = 10 - Returns:
GetFriendSuggestionsResult(IEnumerable<FriendSuggestionDto> Suggestions) - FriendSuggestionDto:
(Guid UserId, string DisplayName, string? AvatarUrl, int MutualFriendsCount) - Behavior: Uses
IGraphQueryServicewith raw SQL (friends-of-friends CTE). Excludes already-friends and blocked users. Orders by mutual friend count descending.
4.4 GetBlockedUsersQuery
- File: Defined in
Controllers/BlocksController.cs - Parameters:
Guid UserId,int Skip = 0,int Take = 20 - Returns:
GetBlockedUsersResult(IEnumerable<BlockedUserDto> BlockedUsers, int TotalCount) - BlockedUserDto:
(Guid UserId, string DisplayName, string? AvatarUrl, DateTime BlockedAt, string? Reason) - Behavior: Gets blocks by blocker user ID, enriches with UserProfile data.
4.5 GetAllRelationshipsQuery (Admin)
- File:
Application/Queries/GetAllRelationshipsQuery.cs - Parameters:
int Skip,int Take,string? StatusFilter,string? TypeFilter - Returns:
GetAllRelationshipsResult(IEnumerable<RelationshipAdminDto> Relationships, int TotalCount) - RelationshipAdminDto:
(Guid Id, Guid RequesterId, Guid AddresseeId, string Type, string Status, DateTime CreatedAt, DateTime? UpdatedAt)
4.6 GetRelationshipByIdQuery (Admin)
- File:
Application/Queries/GetRelationshipByIdQuery.cs - Parameters:
Guid Id - Returns:
RelationshipDetailDto?(nullable) - RelationshipDetailDto:
(Guid Id, Guid RequesterId, Guid AddresseeId, string Type, string Status, DateTime CreatedAt, DateTime? UpdatedAt)
4.7 GetAllBlocksQuery (Admin)
- File:
Application/Queries/GetAllBlocksQuery.cs - Parameters:
int Skip,int Take - Returns:
GetAllBlocksResult(IEnumerable<BlockAdminDto> Blocks, int TotalCount) - BlockAdminDto:
(Guid Id, Guid BlockerId, Guid BlockedId, string? Reason, DateTime CreatedAt)
4.8 GetSocialStatisticsQuery (Admin)
- File:
Application/Queries/GetSocialStatisticsQuery.cs - Parameters: none
- Returns:
SocialStatisticsDto(int TotalFriendships, int TotalFollowings, int TotalBlocks, int PendingFriendRequests, int AcceptedFriendships, int TotalRelationships)
5. Domain Model
5.1 Aggregates
Relationship (Aggregate Root)
- File:
Domain/AggregatesModel/RelationshipAggregate/Relationship.cs - Extends:
Entity, implementsIAggregateRoot - Private Fields:
_requesterId,_addresseeId,_type,_status,_createdAt,_updatedAt - Public Properties:
RequesterId,AddresseeId,Type,TypeId,Status,StatusId,CreatedAt,UpdatedAt - Constructor:
Relationship(Guid requesterId, Guid addresseeId, RelationshipType type)- Validates: no empty GUIDs, no self-relationship
- Following type: auto-sets status to
Accepted, raisesUserFollowedDomainEvent - Friendship type: sets status to
Pending, raisesFriendRequestSentDomainEvent
- Behavior Methods:
Accept()- Only for pending friendships. Sets status to Accepted. RaisesFriendshipCreatedDomainEventandRelationshipStatusChangedDomainEvent.Reject()- Only for pending friendships. Sets status to Rejected. RaisesRelationshipStatusChangedDomainEvent.Cancel()- Only for pending requests. Sets status to Cancelled. RaisesRelationshipStatusChangedDomainEvent.Remove()- Only for accepted relationships. Sets status to Cancelled. RaisesRelationshipRemovedDomainEventandRelationshipStatusChangedDomainEvent.
UserBlock (Aggregate Root)
- File:
Domain/AggregatesModel/UserBlockAggregate/UserBlock.cs - Extends:
Entity, implementsIAggregateRoot - Private Fields:
_blockerId,_blockedId,_reason,_createdAt - Public Properties:
BlockerId,BlockedId,Reason,CreatedAt - Constructor:
UserBlock(Guid blockerId, Guid blockedId, string? reason = null)- Validates: no empty GUIDs, no self-block
- Raises
UserBlockedDomainEvent
- No behavior methods (immutable once created; removal is done via repository
Remove())
UserProfile (Aggregate Root)
- File:
Domain/AggregatesModel/UserProfileAggregate/UserProfile.cs - Extends:
Entity, implementsIAggregateRoot - Private Fields:
_userId,_displayName,_avatarUrl,_bio,_lastSyncedAt,_createdAt - Public Properties:
UserId,DisplayName,AvatarUrl,Bio,LastSyncedAt,CreatedAt - Constructor:
UserProfile(Guid userId, string displayName, string? avatarUrl = null, string? bio = null) - Behavior Methods:
UpdateFromEvent(string displayName, string? avatarUrl, string? bio)- Updates profile data from IAM integration event
5.2 Enumerations (Type-Safe Enum Pattern)
RelationshipType
- File:
Domain/AggregatesModel/RelationshipAggregate/RelationshipType.cs - Values:
Friendship(Id: 1) - Bidirectional, requires acceptanceFollowing(Id: 2) - Unidirectional, auto-accepted
RelationshipStatus
- File:
Domain/AggregatesModel/RelationshipAggregate/RelationshipStatus.cs - Values:
Pending(Id: 1) - Waiting for acceptanceAccepted(Id: 2) - Active relationshipRejected(Id: 3) - Request was rejectedCancelled(Id: 4) - Cancelled by requester or removed
5.3 Domain Services
IGraphQueryService
- File:
Domain/Services/IGraphQueryService.cs - Implementation:
Infrastructure/Services/GraphQueryService.cs(PostgreSQL raw SQL with CTEs) - Methods:
GetMutualFriendsAsync(Guid userId1, Guid userId2)- Returns mutual friend IDs using SQL CTE joinsGetFriendSuggestionsAsync(Guid userId, int limit)- Friends-of-friends who aren't already friends, excludes blocked users, ordered by mutual countGetConnectionDegreeAsync(Guid userId1, Guid userId2)- Recursive CTE BFS, max depth 6, returns degrees of separation (-1 if not connected)GetMutualFriendsCountAsync(Guid userId1, Guid userId2)- Count of mutual friends
- Helper DTO:
FriendSuggestion(Guid UserId, int MutualFriendsCount)
5.4 Domain Events
Relationship Events (Domain/Events/RelationshipDomainEvents.cs):
FriendRequestSentDomainEvent- Raised when a friendship request is createdFriendshipCreatedDomainEvent- Raised when a friendship request is acceptedUserFollowedDomainEvent- Raised when a follow relationship is createdRelationshipStatusChangedDomainEvent- Raised on any status transition (includes PreviousStatus and NewStatus)RelationshipRemovedDomainEvent- Raised when an accepted relationship is removed
Block Events (Domain/Events/UserBlockDomainEvents.cs):
UserBlockedDomainEvent- Raised when a user block is createdUserUnblockedDomainEvent- Defined but not currently raised in code (the UnblockUserCommandHandler does not add this event)
5.5 Exceptions
DomainException- Base exception classSocialDomainException- Extends DomainException, used for all social domain rule violationsSampleDomainException- Leftover from template, extends DomainException (unused)
5.6 SeedWork
Base classes shared across the domain:
Entity- Base entity withId(Guid),DomainEventscollection, equality by IDIAggregateRoot- Marker interfaceIRepository<T>- Generic repository withIUnitOfWork UnitOfWorkIUnitOfWork-SaveChangesAsync(),SaveEntitiesAsync()(dispatches domain events)Enumeration- Type-safe enum base class withId,Name,GetAll<T>(),FromValue<T>(),FromDisplayName<T>()ValueObject- Immutable value comparison base class (not currently used by any entity)
6. Database Schema
Database: social_service (PostgreSQL)
Migration: 20260112164356_InitialCreate
6.1 Table: relationships
| Column | Type | Nullable | Constraints |
|---|---|---|---|
id |
uuid | NO | PK, ValueGeneratedNever |
requester_id |
uuid | NO | |
addressee_id |
uuid | NO | |
type_id |
integer | NO | FK -> relationship_types.id (RESTRICT) |
status_id |
integer | NO | FK -> relationship_statuses.id (RESTRICT) |
created_at |
timestamp with time zone | NO | |
updated_at |
timestamp with time zone | YES |
Indexes:
ix_relationships_requester_addressee_type- UNIQUE on (requester_id, addressee_id, type_id)ix_relationships_requester- on (requester_id)ix_relationships_addressee- on (addressee_id)ix_relationships_type_status- on (type_id, status_id)IX_relationships_status_id- on (status_id) (auto-generated by EF for FK)
6.2 Table: relationship_types (Enumeration)
| Column | Type | Nullable | Constraints |
|---|---|---|---|
id |
integer | NO | PK, ValueGeneratedNever |
name |
varchar(50) | NO |
Seed Data: (1, "Friendship"), (2, "Following")
6.3 Table: relationship_statuses (Enumeration)
| Column | Type | Nullable | Constraints |
|---|---|---|---|
id |
integer | NO | PK, ValueGeneratedNever |
name |
varchar(50) | NO |
Seed Data: (1, "Pending"), (2, "Accepted"), (3, "Rejected"), (4, "Cancelled")
6.4 Table: user_blocks
| Column | Type | Nullable | Constraints |
|---|---|---|---|
id |
uuid | NO | PK, ValueGeneratedNever |
blocker_id |
uuid | NO | |
blocked_id |
uuid | NO | |
reason |
varchar(500) | YES | |
created_at |
timestamp with time zone | NO |
Indexes:
ix_user_blocks_blocker_blocked- UNIQUE on (blocker_id, blocked_id)ix_user_blocks_blocker- on (blocker_id)ix_user_blocks_blocked- on (blocked_id)
6.5 Table: user_profiles
| Column | Type | Nullable | Constraints |
|---|---|---|---|
id |
uuid | NO | PK, ValueGeneratedNever |
user_id |
uuid | NO | |
display_name |
varchar(255) | NO | |
avatar_url |
varchar(500) | YES | |
bio |
varchar(500) | YES | |
last_synced_at |
timestamp with time zone | NO | |
created_at |
timestamp with time zone | NO |
Indexes:
ix_user_profiles_user_id- UNIQUE on (user_id)ix_user_profiles_display_name- on (display_name)
7. Integration Events
7.1 Domain Events (Internal)
The following domain events are raised within the service and dispatched via MediatR INotification through the SocialServiceContext.DispatchDomainEventsAsync() method before SaveChanges. Currently, there are no domain event handlers registered in the service -- the events are raised but not consumed.
FriendRequestSentDomainEventFriendshipCreatedDomainEventUserFollowedDomainEventRelationshipStatusChangedDomainEventRelationshipRemovedDomainEventUserBlockedDomainEvent
7.2 Cross-Service Integration
- Inbound (Expected):
UserProfile.UpdateFromEvent()method exists for receiving IAM profile update events. However, there is no integration event handler or RabbitMQ consumer implemented -- the UserProfile entity and repository exist but have no controllers or commands for creating/updating profiles. Profiles must currently be populated directly in the database. - Outbound: No integration events are published to RabbitMQ or any message broker. Domain events remain internal only.
8. Dependencies
8.1 NuGet Packages
SocialService.API:
- MediatR 12.4.1
- FluentValidation 11.11.0
- FluentValidation.DependencyInjectionExtensions 11.11.0
- Microsoft.EntityFrameworkCore.Design 10.0.1
- Swashbuckle.AspNetCore 7.2.0
- Asp.Versioning.Mvc 8.1.0
- Asp.Versioning.Mvc.ApiExplorer 8.1.0
- AspNetCore.HealthChecks.NpgSql 8.0.2
- AspNetCore.HealthChecks.Redis 8.0.1
- Hellang.Middleware.ProblemDetails 6.5.1
- Serilog.AspNetCore 8.0.3
- Serilog.Sinks.Console 6.0.0
- Serilog.Sinks.Seq 8.0.0
SocialService.Domain:
- MediatR.Contracts 2.0.1
SocialService.Infrastructure:
- Microsoft.EntityFrameworkCore 10.0.0
- Npgsql.EntityFrameworkCore.PostgreSQL 10.0.0
- Microsoft.EntityFrameworkCore.Tools 10.0.0
- MediatR 12.4.1
- Dapper 2.1.35
- Microsoft.Extensions.Http.Polly 9.0.0
- Polly 8.5.0
- StackExchange.Redis 2.8.16
Shared (Directory.Build.props):
- Microsoft.SourceLink.GitHub 8.0.0
- Target: net10.0, LangVersion 14.0, Nullable enabled, TreatWarningsAsErrors
Unit Tests:
- Microsoft.NET.Test.Sdk 17.12.0
- xunit 2.9.2
- FluentAssertions 6.12.2
- NSubstitute 5.3.0
- coverlet.collector 6.0.2
Functional Tests:
- Microsoft.AspNetCore.Mvc.Testing 10.0.0
- Microsoft.EntityFrameworkCore.InMemory 10.0.0
- FluentAssertions 6.12.2
- Testcontainers.PostgreSql 4.1.0
8.2 External Service Dependencies
- PostgreSQL (Neon cloud or local) - primary data store
- Redis (referenced in health checks and .env.example, but not actively used in code -- no Redis caching is implemented)
- IAM Service - UserProfile entity is designed to sync from IAM, but no integration is implemented
9. Configuration
9.1 appsettings.json
{
"ConnectionStrings": {
"DefaultConnection": "Host=...neon.tech;Database=social_service;..."
},
"Redis": {
"ConnectionString": "localhost:6379"
},
"Jwt": {
"Secret": "your-super-secret-key-min-32-characters",
"Issuer": "goodgo-platform",
"Audience": "goodgo-services",
"AccessTokenExpiryMinutes": 15,
"RefreshTokenExpiryDays": 7
}
}
Note: JWT configuration is present in settings but JWT authentication middleware is NOT configured in Program.cs. There is no AddAuthentication() or UseAuthentication() call. All endpoints are currently unauthenticated.
9.2 Environment Variables (.env.example)
| Variable | Description | Default |
|---|---|---|
ASPNETCORE_ENVIRONMENT |
Environment name | Development |
DATABASE_URL |
PostgreSQL connection string | localhost:5432 |
REDIS_URL |
Redis connection string | localhost:6379 |
REDIS_PASSWORD |
Redis password | (empty) |
JWT_SECRET |
JWT signing key | (placeholder) |
JWT_ISSUER |
JWT issuer | goodgo-platform |
JWT_AUDIENCE |
JWT audience | goodgo-services |
JWT_ACCESS_TOKEN_EXPIRY_MINUTES |
Access token TTL | 15 |
JWT_REFRESH_TOKEN_EXPIRY_DAYS |
Refresh token TTL | 7 |
API_PORT |
API port | 5000 |
OTEL_EXPORTER_OTLP_ENDPOINT |
OpenTelemetry endpoint | localhost:4317 |
OTEL_SERVICE_NAME |
OpenTelemetry service name | socialservice |
SEQ_URL |
Seq logging endpoint | localhost:5341 |
RATE_LIMIT_PERMITS_PER_MINUTE |
Rate limit | 100 |
RATE_LIMIT_QUEUE_LIMIT |
Rate limit queue | 10 |
HEALTHCHECK_TIMEOUT_SECONDS |
Health check timeout | 5 |
9.3 MediatR Pipeline (Behavior Order)
LoggingBehavior- Logs request name, measures execution time with StopwatchValidatorBehavior- Runs FluentValidation validators (none currently registered)TransactionBehavior- Wraps commands in a database transaction (skips requests ending in "Query"), usesCreateExecutionStrategy()for retry support
9.4 DI Registration (Infrastructure)
Registered in DependencyInjection.AddInfrastructure():
SocialServiceContext(DbContext with Npgsql, retry on failure: 5 retries, 30s delay)IRelationshipRepository->RelationshipRepository(Scoped)IUserProfileRepository->UserProfileRepository(Scoped)IUserBlockRepository->UserBlockRepository(Scoped)IGraphQueryService->GraphQueryService(Scoped)IRequestManager->RequestManager(Scoped)
9.5 Docker Configuration
- Multi-stage build:
sdk:10.0(build) ->aspnet:10.0(runtime) - Non-root user:
dotnetuser(UID 1001, GID 1001) - Port: 8080 (
ASPNETCORE_URLS=http://+:8080) - Health check:
curl -f http://localhost:8080/health/live(30s interval, 3 retries)
9.6 SDK Version
- .NET SDK: 10.0.101 (rollForward: latestMinor)
10. Test Coverage
10.1 Unit Tests (SocialService.UnitTests)
Handler Tests:
-
SendFriendRequestCommandHandlerTests(4 tests):- Valid request creates new relationship
- Block exists throws exception
- Already friends throws exception
- Request already pending throws exception
- Mutual request auto-accepts
-
BlockUserCommandHandlerTests(5 tests):- Valid request creates block
- Self-block throws exception
- Already blocked throws exception
- With existing friendship removes friendship
- No reason creates block without reason
Domain Tests:
RelationshipAggregateTests(14 tests): Constructor, Accept, Reject, Cancel, Remove with valid and invalid state transitionsUserBlockAggregateTests(6 tests): Constructor with valid/invalid inputs, domain event verification
10.2 Functional Tests (SocialService.FunctionalTests)
Uses CustomWebApplicationFactory with InMemoryDatabase.
RelationshipsControllerTests(4 tests): GET friends, mutual friends, suggestions, health checkBlocksControllerTests(2 tests): GET blocked users, with paginationAdminControllerTests(6 tests): GET all relationships (with/without filter), GET by ID (404), DELETE relationship (404), GET all blocks, DELETE block (404), GET statistics
11. Known Gaps and Notes
- No Authentication: JWT config exists in settings but authentication middleware is not wired up. All endpoints are public.
- No FluentValidation Validators: The
ValidatorBehavioris in the pipeline but noAbstractValidator<T>classes exist for any command. - No UserProfile CRUD API: The
UserProfileentity and repository exist, but there are no controllers or commands to create/update profiles. TheGetFriendsQueryand other queries reference UserProfile for display names, but profiles must be populated externally. - No IAM Integration Event Consumer:
UserProfile.UpdateFromEvent()method exists but no RabbitMQ consumer or integration event handler is implemented. - No Redis Usage: Redis package is referenced and health check is configured, but no caching logic exists.
- UserUnblockedDomainEvent: Defined but never raised -- the
UnblockUserCommandHandlerdoes not add this domain event before removing the block. - SampleDomainException: Leftover from template, unused.
- Idempotency:
IRequestManager/RequestManagerandClientRequestare registered but not used by any command handler. - CORS: Configured to allow any origin/method/header (development-only concern).
- Swagger: Only enabled in Development environment.