Generated by AurionDocs
Job ID: 934f8c39-8082-4942-8d17-72ed8f5f8d50
Source commit: 40aea9a
6.6 KiB
UsersController
File:
src/EchoHub.Server/Controllers/UsersController.cs
Kind: class
Figure: How UsersController works.
%%{init: {'theme':'base','themeVariables':{'background':'#faf7ef','primaryColor':'#f0e2c2','primaryTextColor':'#1f2840','primaryBorderColor':'#8a7548','secondaryColor':'#d9efec','secondaryBorderColor':'#1d8a80','secondaryTextColor':'#1f2840','tertiaryColor':'#f2ebd8','tertiaryBorderColor':'#8a7548','tertiaryTextColor':'#1f2840','lineColor':'#1d8a80','titleColor':'#1f2840','fontSize':'14px','edgeLabelBackground':'#faf7ef','clusterBkg':'#f2ebd8','clusterBorder':'#8a7548','actorBkg':'#f0e2c2','actorBorder':'#8a7548','actorTextColor':'#1f2840','actorLineColor':'#8a7548','signalColor':'#1d8a80','signalTextColor':'#1f2840','activationBkgColor':'#d9efec','activationBorderColor':'#1d8a80','noteBkgColor':'#f2ebd8','noteBorderColor':'#8a7548','noteTextColor':'#1f2840','labelBoxBkgColor':'#f0e2c2','labelBoxBorderColor':'#8a7548','labelTextColor':'#1f2840','transitionColor':'#1d8a80','transitionLabelColor':'#1f2840','stateLabelColor':'#1f2840','altBackground':'#f2ebd8'}}}%%
flowchart TB
start["UsersController receives HTTP request"]
route{"Route: which endpoint?"}
start --> route
%% GET profile flow
route -->|"GET {username}/profile"| gpCall["Call IUserService.GetUserProfileAsync(username)"]
gpCall --> profileNull{"Profile is null?"}
profileNull -- Yes --> notFound["Return 404 NotFound(ErrorResponse)"]
profileNull -- No --> okProfile["Return 200 Ok(profile)"]
%% Update profile flow
route -->|"PUT profile"| updAuth["Extract userIdClaim from User"]
updAuth --> updAuthNull{"userIdClaim is null?"}
updAuthNull -- Yes --> updUnauthorized["Return 401 Unauthorized(ErrorResponse)"]
updAuthNull -- No --> updCall["Call IUserService.UpdateProfileAsync(Guid.Parse(userIdClaim), UpdateProfileRequest fields)"]
updCall --> updResult{"result.IsSuccess?"}
updResult -- No --> updError["Return mapped UserError response (UserError)"]
updResult -- Yes --> updOk["Return 200 Ok(result.User)"]
%% Upload avatar flow (truncated)
route -->|"POST avatar"| uploadAuth["Extract userIdClaim from User"]
uploadAuth --> uploadAuthNull{"userIdClaim is null?"}
uploadAuthNull -- Yes --> uploadUnauthorized["Return 401 Unauthorized(ErrorResponse)"]
uploadAuthNull -- No --> formCheck{"Request.HasFormContentType && Request.Form.Files.Count > 0?"}
formCheck -- No --> noFile["Return 400 BadRequest(ErrorResponse: No file uploaded.)"]
formCheck -- Yes --> fileAssign["Select first file from Request.Form.Files (file)"]
fileAssign --> sizeCheck{"file.Length > UploadLimits.MaxAvatarSizeBytes?"}
sizeCheck -- Yes --> tooLarge["Return 400 BadRequest(ErrorResponse: File size exceeds maximum)"]
sizeCheck -- No --> continue["Proceed with avatar processing (truncated)"]
[ApiController]
[Route("api/users")]
[Authorize]
[EnableRateLimiting("general")]
public class UsersController : ControllerBase
Controller that exposes the HTTP surface for user profile and account-related operations under api/users, including profile retrieval, profile updates and avatar upload. Reach for UsersController when you need to translate authenticated HTTP requests into calls to the user, storage and broadcasting services (for example, calling IUserService to update a profile or ImageToAsciiService to convert an uploaded avatar).
Remarks
UsersController is an orchestration layer: it validates and normalizes incoming HTTP requests, enforces authentication and rate-limiting policies, performs lightweight validation (for example file size and image format checks), and delegates the domain work to collaborators such as IUserService, ImageToAsciiService, FileStorageService, PresenceTracker and the collection of IChatBroadcaster implementations. The controller centralizes common web concerns (claim extraction via User.FindFirstValue, mapping service results to HTTP responses with MapUserError, and producing ErrorResponse/AvatarUploadResponse payloads) so the underlying services can remain focused on business logic. The DeletedUserName constant is a reserved tombstone username: UserService will refuse to register it, and it is used when re-attributing messages after account deletion (messages re-attributed to DeletedUserName). Note also that exported account data includes stored messages but that any end-to-end encrypted room content remains ciphertext on the server (the controller preserves what the server stores, it does not decrypt client-side E2E content).
Notes
- The controller is annotated with
Authorize, so every action requires authentication by default. An action must be decorated withAllowAnonymousto be reachable without credentials. UploadAvatarexpects a multipart/form-data request and will returnBadRequestwhenRequest.Form.Filesis empty. It also enforces size limits using_uploadLimits.MaxAvatarSizeBytesand reports the limit in MB in the error text.FileValidationHelper.IsValidImageis used to allow only images (it recognizes JPEG, PNG, GIF and WebP). Because both validation and ASCII conversion operate on the sameStream(file.OpenReadStream()), ensure the validation method does not consume the stream or that the stream position is reset before callingImageToAsciiService.ConvertToAscii— otherwise the conversion may see an empty stream.- The controller extracts the caller identity using
User.FindFirstValue(ClaimTypes.NameIdentifier)and then callsGuid.Parse(...). If the claim is present but not a valid GUID this will throw; callers should ensure the claim is a GUID or the parsing should be hardened (for example withGuid.TryParse). - Upload endpoints have a more specific rate limit: the controller-level
[EnableRateLimiting("general")]applies broadly whileUploadAvataradditionally uses[EnableRateLimiting("upload")], so be aware of which policy will throttle a client. - Many methods rely on
MapUserErrorto convert domain errors into HTTP responses; consumers of these endpoints should expect standardizedErrorResponsepayloads for error cases.