Table of Contents

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

adminService IAdminService

Service for administrative operations.

configuration IConfiguration

Application configuration for accessing settings.

emailSender IEmailSender

Service for sending emails via SendGrid.

logger ILogger<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

id Guid

The unique identifier of the user to block.

cancellationToken CancellationToken

Cancellation 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

id Guid

The unique identifier of the user whose role should be changed.

request ChangeRoleRequest

Request containing the new role string (e.g., "Admin", "Moderator", "User").

cancellationToken CancellationToken

Cancellation 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

request CreateInvitationRequest

Invitation creation request containing max uses, expiration date, and optional label.

cancellationToken CancellationToken

Cancellation 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

cancellationToken CancellationToken

Cancellation 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

page int

Page number (1-based). Defaults to 1.

pageSize int

Number of invitations per page. Defaults to 20.

cancellationToken CancellationToken

Cancellation 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

id Guid

The unique identifier of the user whose profile should be retrieved.

cancellationToken CancellationToken

Cancellation 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

id Guid

The unique identifier of the user whose projects should be retrieved.

cancellationToken CancellationToken

Cancellation 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

page int

Page number (1-based). Defaults to 1.

pageSize int

Number of users per page. Defaults to 20.

search string

Optional search term to filter by email or name.

status string

Optional status filter (e.g., "Active", "Blocked"). Must match UserStatus enum values.

role string

Optional role filter (e.g., "User", "Admin"). Must match UserRole enum values.

cancellationToken CancellationToken

Cancellation 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

request SendAdminUpdateRequest

Request containing list of roles to target and HTML content for the email body.

cancellationToken CancellationToken

Cancellation 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:

  1. Parses role strings (case-insensitive) to UserRole enum values
  2. Queries all active users matching the specified roles
  3. Automatically excludes users with "demo.builvero.local" email addresses
  4. Sends emails in batches with concurrency control (see SendAdminUpdatesAsync(List<UserRole>, string, CancellationToken) for details)
  5. Returns comprehensive statistics about the operation
The email subject is defined in the SendGrid template and cannot be overridden. HTML content is sent as-is to the template.
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

id Guid

The unique identifier of the invitation to send.

request SendInvitationRequest

Request containing list of email addresses to send invitations to.

cancellationToken CancellationToken

Cancellation 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

userId Guid

The unique identifier of the user whose moderator status should be changed.

request SetModeratorRoleRequest

Request containing a boolean flag indicating whether the user should be a moderator.

cancellationToken CancellationToken

Cancellation 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

request TestEmailRequest

Request containing the recipient email address.

cancellationToken CancellationToken

Cancellation 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

id Guid

The unique identifier of the user to unblock.

cancellationToken CancellationToken

Cancellation 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

id Guid

The unique identifier of the invitation to update.

request UpdateInvitationRequest

Update request containing fields to modify. Only provided fields are updated.

cancellationToken CancellationToken

Cancellation 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

id Guid

The unique identifier of the user to update.

request UpdateUserRequest

Update request containing fields to modify. Only provided fields are updated.

cancellationToken CancellationToken

Cancellation 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.