Class AdminController
- Namespace
- Builvero.Api.Controllers
- Assembly
- Builvero.Api.dll
Provides administrative API endpoints for user management, invitation management, email testing, and bulk communications.
[ApiController]
[Route("api/admin")]
[Authorize(Policy = "AdminRead")]
public class AdminController : ControllerBase
- Inheritance
-
AdminController
- Inherited Members
Remarks
This controller requires authorization via the "AdminRead" policy for read operations and "AdminWrite" policy for write operations.
All endpoints are prefixed with /api/admin.
The controller handles:
- User management (listing, blocking, unblocking, role changes, profile retrieval)
- Invitation code creation and management
- Email configuration diagnostics and test email sending
- Bulk email updates to users by role (Admin Updates feature)
- Role enumeration for UI dropdowns
Error responses follow a consistent format: { "error": "error message" } for client errors (400, 404)
and { "status": "status", "message": "message" } for success responses.
Constructors
AdminController(IAdminService, IConfiguration, IEmailSender, ILogger<AdminController>)
Initializes a new instance of the AdminController class.
public AdminController(IAdminService adminService, IConfiguration configuration, IEmailSender emailSender, ILogger<AdminController> logger)
Parameters
adminServiceIAdminServiceService for administrative operations.
configurationIConfigurationApplication configuration for accessing settings.
emailSenderIEmailSenderService for sending emails via SendGrid.
loggerILogger<AdminController>Logger for recording controller operations and errors.
Methods
BlockUser(Guid, CancellationToken)
Blocks a user account, preventing login and platform access.
[HttpPatch("users/{id}/block")]
[Authorize(Policy = "AdminWrite")]
public Task<IActionResult> BlockUser(Guid id, CancellationToken cancellationToken)
Parameters
idGuidThe unique identifier of the user to block.
cancellationTokenCancellationTokenCancellation token to cancel the operation.
Returns
- Task<IActionResult>
200 OK: Returns
{ "message": "User blocked successfully" }400 Bad Request: User not found or other error
Remarks
Requires "AdminWrite" policy. Sets user status to Blocked. Blocked users cannot authenticate.
ChangeUserRole(Guid, ChangeRoleRequest, CancellationToken)
Changes a user's role, affecting their permissions and access level.
[HttpPatch("users/{id}/role")]
[Authorize(Policy = "AdminWrite")]
public Task<IActionResult> ChangeUserRole(Guid id, ChangeRoleRequest request, CancellationToken cancellationToken)
Parameters
idGuidThe unique identifier of the user whose role should be changed.
requestChangeRoleRequestRequest containing the new role string (e.g., "Admin", "Moderator", "User").
cancellationTokenCancellationTokenCancellation token to cancel the operation.
Returns
- Task<IActionResult>
200 OK: Returns
{ "message": "User role updated successfully" }400 Bad Request: Invalid role or user not found
Remarks
Requires "AdminWrite" policy. Role string must exactly match a UserRole enum value name (case-sensitive). Role changes take effect immediately.
CreateInvitation(CreateInvitationRequest, CancellationToken)
Creates a new invitation code for user registration.
[HttpPost("invitations")]
[Authorize(Policy = "AdminWrite")]
public Task<ActionResult<InvitationDto>> CreateInvitation(CreateInvitationRequest request, CancellationToken cancellationToken)
Parameters
requestCreateInvitationRequestInvitation creation request containing max uses, expiration date, and optional label.
cancellationTokenCancellationTokenCancellation token to cancel the operation.
Returns
- Task<ActionResult<InvitationDto>>
200 OK: Returns created InvitationDto with generated code and full invitation link
400 Bad Request: Invalid request data
Remarks
Requires "AdminWrite" policy. A unique invitation code is automatically generated. The invitation link is constructed
as: {frontendBaseUrl}/auth/signup.html?invite={code}. The creator is determined from the authenticated user's ID.
GetEmailConfigStatus(CancellationToken)
Retrieves diagnostic information about the SendGrid email service configuration.
[HttpGet("email-config-status")]
public Task<ActionResult<object>> GetEmailConfigStatus(CancellationToken cancellationToken)
Parameters
cancellationTokenCancellationTokenCancellation token to cancel the operation.
Returns
- Task<ActionResult<object>>
200 OK: Returns configuration status object with API key resolution, format validation, template IDs, and SSM parameter name
500 Internal Server Error: Failed to retrieve configuration status
Remarks
Requires "AdminRead" policy. This endpoint helps diagnose email sending issues without sending actual emails. Returns information about:
- Environment variable presence (SENDGRID_API_KEY)
- SSM parameter name configuration
- From email address
- Template IDs (Hello template)
- API key format validation (must start with "SG.")
- API key resolution status
- API key length and prefix (for verification)
GetInvitations(int, int, CancellationToken)
Retrieves a paginated list of all invitation codes in the system.
[HttpGet("invitations")]
public Task<ActionResult<object>> GetInvitations(int page = 1, int pageSize = 20, CancellationToken cancellationToken = default)
Parameters
pageintPage number (1-based). Defaults to 1.
pageSizeintNumber of invitations per page. Defaults to 20.
cancellationTokenCancellationTokenCancellation token to cancel the operation.
Returns
- Task<ActionResult<object>>
200 OK: Returns
{ "invitations": [...], "totalCount": int, "page": int, "pageSize": int }400 Bad Request: Invalid request parameters
Remarks
Requires "AdminRead" policy. Each invitation includes a full invitation link constructed from the frontend base URL. Invitation status is calculated based on expiration date, max uses, and current usage.
GetRoles()
Retrieves a list of all available user roles for UI dropdowns.
[HttpGet("roles")]
public ActionResult<object> GetRoles()
Returns
- ActionResult<object>
200 OK: Returns
{ "roles": ["User", "Admin", "Moderator", ...] }(sorted alphabetically)
Remarks
Requires "AdminRead" policy. Returns all UserRole enum values sorted alphabetically. Used by the admin UI to populate role selection dropdowns.
GetUser(Guid, CancellationToken)
Retrieves the complete profile information for a user, including profile photo with presigned URL.
[HttpGet("users/{id}")]
public Task<ActionResult<ProfileDto>> GetUser(Guid id, CancellationToken cancellationToken)
Parameters
idGuidThe unique identifier of the user whose profile should be retrieved.
cancellationTokenCancellationTokenCancellation token to cancel the operation.
Returns
- Task<ActionResult<ProfileDto>>
200 OK: Returns ProfileDto with complete profile information
404 Not Found: User or profile not found
Remarks
Requires "AdminRead" policy. Profile photo URLs are automatically converted to presigned URLs (15-minute TTL) to prevent plain S3 URL exposure. Includes education, experience, skills, and builder tags.
GetUserProjects(Guid, CancellationToken)
Retrieves all projects associated with a user (owned and member projects).
[HttpGet("users/{id}/projects")]
public Task<ActionResult<List<object>>> GetUserProjects(Guid id, CancellationToken cancellationToken)
Parameters
idGuidThe unique identifier of the user whose projects should be retrieved.
cancellationTokenCancellationTokenCancellation token to cancel the operation.
Returns
- Task<ActionResult<List<object>>>
200 OK: Returns list of ProjectDto objects
400 Bad Request: User not found or other error
Remarks
Requires "AdminRead" policy. For each project, includes the user's role (Owner or Member), pending invitation counts, and join request counts. Profile photo URLs for owners and members are converted to presigned URLs.
GetUsers(int, int, string?, string?, string?, CancellationToken)
Retrieves a paginated list of users with optional filtering.
[HttpGet("users")]
public Task<ActionResult<object>> GetUsers(int page = 1, int pageSize = 20, string? search = null, string? status = null, string? role = null, CancellationToken cancellationToken = default)
Parameters
pageintPage number (1-based). Defaults to 1.
pageSizeintNumber of users per page. Defaults to 20.
searchstringOptional search term to filter by email or name.
statusstringOptional status filter (e.g., "Active", "Blocked"). Must match UserStatus enum values.
rolestringOptional role filter (e.g., "User", "Admin"). Must match UserRole enum values.
cancellationTokenCancellationTokenCancellation token to cancel the operation.
Returns
- Task<ActionResult<object>>
200 OK: Returns
{ "users": [...], "totalCount": int, "page": int, "pageSize": int }400 Bad Request: Invalid request parameters
Remarks
Requires "AdminRead" policy. Invalid status or role values are ignored. Search is applied to both email and profile names.
SendAdminUpdates(SendAdminUpdateRequest, CancellationToken)
Sends bulk email updates to all active users matching the specified roles using the SendGrid AdminUpdates template.
[HttpPost("updates/send")]
[Authorize(Policy = "AdminWrite")]
public Task<ActionResult<AdminUpdateSendResult>> SendAdminUpdates(SendAdminUpdateRequest request, CancellationToken cancellationToken)
Parameters
requestSendAdminUpdateRequestRequest containing list of roles to target and HTML content for the email body.
cancellationTokenCancellationTokenCancellation token to cancel the operation.
Returns
- Task<ActionResult<AdminUpdateSendResult>>
200 OK: Returns AdminUpdateSendResult with detailed statistics (total matched, skipped, attempted, succeeded, failed, failed emails)
400 Bad Request: Request body is null, no roles specified, HTML content is empty/whitespace, or invalid role names
500 Internal Server Error: Unexpected error during email sending
Remarks
Requires "AdminWrite" policy. This endpoint:
- Parses role strings (case-insensitive) to UserRole enum values
- Queries all active users matching the specified roles
- Automatically excludes users with "demo.builvero.local" email addresses
- Sends emails in batches with concurrency control (see SendAdminUpdatesAsync(List<UserRole>, string, CancellationToken) for details)
- Returns comprehensive statistics about the operation
- See Also
SendInvitation(Guid, SendInvitationRequest, CancellationToken)
Sends invitation emails to multiple recipients for a specific invitation code.
[HttpPost("invitations/{id}/send")]
[Authorize(Policy = "AdminWrite")]
public Task<ActionResult<SendInvitationResponse>> SendInvitation(Guid id, SendInvitationRequest request, CancellationToken cancellationToken)
Parameters
idGuidThe unique identifier of the invitation to send.
requestSendInvitationRequestRequest containing list of email addresses to send invitations to.
cancellationTokenCancellationTokenCancellation token to cancel the operation.
Returns
- Task<ActionResult<SendInvitationResponse>>
200 OK: Returns SendInvitationResponse with per-email results (sent and failed)
400 Bad Request: Invalid request data or invitation not found
404 Not Found: Invitation not found
Remarks
Requires "AdminWrite" policy. Sends a separate email to each recipient. Each email is sent independently, so partial failures are handled gracefully. The response includes detailed per-email results. Email addresses are normalized (lowercase, trimmed) and deduplicated before sending.
SetModeratorRole(Guid, SetModeratorRoleRequest, CancellationToken)
Promotes or demotes a user to/from the Moderator role.
[HttpPost("users/{userId}/role/moderator")]
[Authorize(Policy = "AdminWrite")]
public Task<IActionResult> SetModeratorRole(Guid userId, SetModeratorRoleRequest request, CancellationToken cancellationToken)
Parameters
userIdGuidThe unique identifier of the user whose moderator status should be changed.
requestSetModeratorRoleRequestRequest containing a boolean flag indicating whether the user should be a moderator.
cancellationTokenCancellationTokenCancellation token to cancel the operation.
Returns
- Task<IActionResult>
200 OK: Returns
{ "message": "User promoted to moderator role" }or{ "message": "User removed from moderator role" }400 Bad Request: User not found or invalid role
Remarks
Requires "AdminWrite" policy. If IsModerator is true, sets role to "Moderator"; otherwise sets to "User".
Role changes take effect immediately.
TestEmail(TestEmailRequest, CancellationToken)
Sends a test email via SendGrid to verify email service configuration.
[HttpPost("test-email")]
[Authorize(Policy = "AdminWrite")]
public Task<ActionResult<object>> TestEmail(TestEmailRequest request, CancellationToken cancellationToken)
Parameters
requestTestEmailRequestRequest containing the recipient email address.
cancellationTokenCancellationTokenCancellation token to cancel the operation.
Returns
- Task<ActionResult<object>>
200 OK: Returns
{ "status": "sent", "message": "Test email sent successfully" }400 Bad Request: Email address is required, empty, or invalid format
502 Bad Gateway: Email service configuration error, API key issues, or SendGrid API errors
Remarks
Requires "AdminWrite" policy. Uses the SendGrid "Hello" template (configured via SendGrid:HelloTemplateId).
Email format is validated using the same regex as SendGridEmailSender.
Error responses are user-friendly and do not expose internal configuration details.
Specific error codes:
- 502: API key configuration errors, authentication failures, or template configuration errors
- 400: Invalid email format or malformed request
Exceptions
- ArgumentException
Thrown when email format is invalid (validated using regex:
^[^@\s]+@[^@\s]+.[^@\s]+$).- InvalidOperationException
Thrown when SendGrid API key cannot be resolved, API key format is invalid, or SendGrid API returns errors (401, 403, 400).
UnblockUser(Guid, CancellationToken)
Unblocks a user account, restoring login and platform access.
[HttpPatch("users/{id}/unblock")]
[Authorize(Policy = "AdminWrite")]
public Task<IActionResult> UnblockUser(Guid id, CancellationToken cancellationToken)
Parameters
idGuidThe unique identifier of the user to unblock.
cancellationTokenCancellationTokenCancellation token to cancel the operation.
Returns
- Task<IActionResult>
200 OK: Returns
{ "message": "User unblocked successfully" }400 Bad Request: User not found or other error
Remarks
Requires "AdminWrite" policy. Sets user status to Active. User can immediately log in after unblocking.
UpdateInvitation(Guid, UpdateInvitationRequest, CancellationToken)
Updates an existing invitation code's properties (max uses, expiration date, label).
[HttpPut("invitations/{id}")]
[Authorize(Policy = "AdminWrite")]
public Task<ActionResult<InvitationDto>> UpdateInvitation(Guid id, UpdateInvitationRequest request, CancellationToken cancellationToken)
Parameters
idGuidThe unique identifier of the invitation to update.
requestUpdateInvitationRequestUpdate request containing fields to modify. Only provided fields are updated.
cancellationTokenCancellationTokenCancellation token to cancel the operation.
Returns
- Task<ActionResult<InvitationDto>>
200 OK: Returns updated InvitationDto with full invitation link
400 Bad Request: Invalid data, max uses less than current usage, or invitation not found
Remarks
Requires "AdminWrite" policy. Performs partial updates. Max uses cannot be reduced below current usage count. The invitation code itself cannot be changed after creation. The invitation link is regenerated in the response.
UpdateUser(Guid, UpdateUserRequest, CancellationToken)
Updates user account information (email, password, role, status).
[HttpPut("users/{id}")]
[Authorize(Policy = "AdminWrite")]
public Task<ActionResult<UserListDto>> UpdateUser(Guid id, UpdateUserRequest request, CancellationToken cancellationToken)
Parameters
idGuidThe unique identifier of the user to update.
requestUpdateUserRequestUpdate request containing fields to modify. Only provided fields are updated.
cancellationTokenCancellationTokenCancellation token to cancel the operation.
Returns
- Task<ActionResult<UserListDto>>
200 OK: Returns updated UserListDto
400 Bad Request: Invalid data, email already in use, or user not found
Remarks
Requires "AdminWrite" policy. Performs partial updates - only fields provided in the request are modified. Email uniqueness is enforced. Passwords are hashed using BCrypt before storage.