Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/0Crazy-0/ClinicFlow/llms.txt

Use this file to discover all available pages before exploring further.

User management in ClinicFlow is handled entirely through MediatR commands and queries. There is no REST layer; commands are dispatched directly via IMediator.Send() in the presentation layer. All commands use FluentValidation pipelines that run before the handler, and all write operations are wrapped in a IUnitOfWork that commits atomically.

Registration Commands

RegisterUserCommand

Registers a new Patient user account. Returns: IRequest<Guid> — the newly created user’s Id.
public sealed record RegisterUserCommand(string Email, string Password, string PhoneNumber)
    : IRequest<Guid>;
Email
string
required
Valid email address. Must be unique across all users. Maximum length is defined by EmailAddress.MaximumLength.
Password
string
required
Plain-text password to hash and store. Minimum 8 characters.
PhoneNumber
string
required
Phone number in any format accepted by the PhoneNumber value object. Minimum/maximum length enforced.
Handler behaviour:
  • Checks IUserRepository.ExistsByEmailAsync and throws BusinessRuleValidationException (DomainErrors.User.EmailAlreadyExists) if the email is taken.
  • Creates EmailAddress and PhoneNumber value objects (format-validated by their constructors).
  • Hashes the password via IPasswordHasherService.
  • Calls User.Create(email, hash, phone, UserRole.Patient) and persists via IUnitOfWork.
Returns the new user’s Guid Id.

RegisterDoctorUserCommand

Registers a new Doctor user account. Identical field structure to RegisterUserCommand; the handler assigns UserRole.Doctor. Returns: IRequest<Guid>
public sealed record RegisterDoctorUserCommand(string Email, string Password, string PhoneNumber)
    : IRequest<Guid>;
Email
string
required
Unique, valid email address.
Password
string
required
Plain-text password. Minimum 8 characters.
PhoneNumber
string
required
Phone number within PhoneNumber.MinimumLengthPhoneNumber.MaximumLength.
Handler behaviour: Same pipeline as RegisterUserCommand except UserRole.Doctor is assigned.

RegisterReceptionistUserCommand

Registers a new Receptionist user account. Returns: IRequest<Guid>
public sealed record RegisterReceptionistUserCommand(
    string Email,
    string Password,
    string PhoneNumber
) : IRequest<Guid>;
Same field validation rules as RegisterUserCommand. Handler assigns UserRole.Receptionist.

RegisterAdminUserCommand

Registers a new Admin user account. Returns: IRequest<Guid>
public sealed record RegisterAdminUserCommand(string Email, string Password, string PhoneNumber)
    : IRequest<Guid>;
Same field validation rules as RegisterUserCommand. Handler assigns UserRole.Admin.
Admin accounts have unrestricted access to all system operations. Restrict who can dispatch this command at the presentation layer.

Account Lifecycle Commands

DeactivateUserCommand

Soft-deactivates a user account, preventing login without deleting data. Returns: IRequest (no return value)
public sealed record DeactivateUserCommand(Guid UserId) : IRequest;
UserId
Guid
required
The Id of the user to deactivate. Must be a non-empty Guid.
Handler behaviour:
  • Fetches the user or throws EntityNotFoundException.
  • Calls user.Deactivate() which throws BusinessRuleValidationException (DomainErrors.User.AlreadyInactive) if already inactive.
  • Sets IsActive = false on the domain entity.

ReactivateUserCommand

Restores a deactivated user account and resets the lockout state. Returns: IRequest (no return value)
public sealed record ReactivateUserCommand(Guid UserId) : IRequest;
UserId
Guid
required
The Id of the user to reactivate. Must be a non-empty Guid.
Handler behaviour:
  • Fetches the user or throws EntityNotFoundException.
  • Calls user.Reactivate() which throws BusinessRuleValidationException (DomainErrors.User.AlreadyActive) if already active.
  • Sets IsActive = true, resets FailedLoginAttempts = 0, and clears LockoutEnd.

Query: GetUsers

Retrieves a paginated, filterable list of all users. Returns: IRequest<PaginatedList<UserDto>>
public sealed record GetUsersQuery(
    int PageNumber,
    int PageSize,
    UserRole? Role,
    bool? IsActive,
    string? SearchTerm
) : IRequest<PaginatedList<UserDto>>;
PageNumber
int
required
1-based page index.
PageSize
int
required
Number of records per page.
Role
UserRole?
Optional role filter. One of Patient (1), Doctor (2), Receptionist (3), Admin (4).
IsActive
bool?
Optional active-status filter. null returns all users.
SearchTerm
string?
Optional free-text search applied by the repository against email or phone.
Response shape: PaginatedList<UserDto> — see UserDto below.

Query: GetUserById

Retrieves a single user by their primary key. Returns: IRequest<UserDto>
public sealed record GetUserByIdQuery(Guid UserId) : IRequest<UserDto>;
UserId
Guid
required
The user’s primary key.
Throws EntityNotFoundException if no user with the given Id exists.

Query: GetLockedOutUsers

Retrieves a paginated list of users whose accounts are currently locked out (i.e., LockoutEnd is in the future). Returns: IRequest<PaginatedList<UserDto>>
public sealed record GetLockedOutUsersQuery(int PageNumber, int PageSize)
    : IRequest<PaginatedList<UserDto>>;
PageNumber
int
required
1-based page index.
PageSize
int
required
Number of records per page.
Handler behaviour: Uses TimeProvider.GetUtcNow() as the reference time to determine which accounts are still locked. Accounts become locked after User.MaxFailedLoginAttempts (5) failed attempts; lockout lasts 15 minutes.

Query: CheckEmailUniqueness

Returns true if the given email is not already registered. Useful for real-time form validation. Returns: IRequest<bool>
public sealed record CheckEmailUniquenessQuery(string Email) : IRequest<bool>;
Email
string
required
The email address to check.
Returns true when the email is available; false when it is already in use.

Query: CheckPhoneUniqueness

Returns true if the given phone number is not already registered. Returns: IRequest<bool>
public sealed record CheckPhoneUniquenessQuery(string PhoneNumber) : IRequest<bool>;
PhoneNumber
string
required
The phone number to check.
Returns true when the phone number is available; false when it is already in use.

UserDto Response Shape

All user queries return UserDto or PaginatedList<UserDto>.
public sealed record UserDto(
    Guid Id,
    string Email,
    string PhoneNumber,
    UserRole Role,
    bool IsActive,
    bool IsPhoneVerified,
    DateTime? LastLoginAt,
    int FailedLoginAttempts,
    DateTime? LockoutEnd
);
Id
Guid
The user’s primary key.
Email
string
The user’s email address (unwrapped from the EmailAddress value object).
PhoneNumber
string
The user’s phone number (unwrapped from the PhoneNumber value object).
Role
UserRole
Integer-backed enum: Patient = 1, Doctor = 2, Receptionist = 3, Admin = 4.
IsActive
bool
true if the account is active and can authenticate.
IsPhoneVerified
bool
true if the phone number has been verified via OTP. Required before a patient can schedule appointments.
LastLoginAt
DateTime?
UTC timestamp of the most recent successful login. null if never logged in.
FailedLoginAttempts
int
Number of consecutive failed login attempts since the last successful login or password change.
LockoutEnd
DateTime?
UTC timestamp when the current lockout expires. null if the account is not locked.

PaginatedList Wrapper

Paginated queries wrap results in PaginatedList<T>:
Items
IReadOnlyCollection<T>
The page’s records.
TotalCount
int
Total number of matching records across all pages.
PageNumber
int
Current 1-based page number.
TotalPages
int
Computed as ceil(TotalCount / PageSize).
HasPreviousPage
bool
true when PageNumber > 1.
HasNextPage
bool
true when PageNumber < TotalPages.

UserRole Enum

public enum UserRole
{
    Patient      = 1,  // Can book and manage their own appointments
    Doctor       = 2,  // Clinical staff
    Receptionist = 3,  // Front-desk scheduling and admin tasks
    Admin        = 4,  // Full system access
}

Build docs developers (and LLMs) love