mirror of
https://github.com/Sea-Haven-Industries/shoc-backend.git
synced 2026-09-30 09:33:13 +00:00
94 lines
4.8 KiB
C#
94 lines
4.8 KiB
C#
|
|
using Api.SeaHavenIndustries.DTOs;
|
||
|
|
using Microsoft.AspNetCore.Authorization;
|
||
|
|
using Microsoft.AspNetCore.Mvc;
|
||
|
|
using SeaHaven.Services.DTOs;
|
||
|
|
using SeaHaven.Services.Interfaces;
|
||
|
|
|
||
|
|
namespace Api.SeaHavenIndustries.Controllers;
|
||
|
|
|
||
|
|
/// <summary>
|
||
|
|
/// Anonymous invite registration. The invite token travels only in request bodies,
|
||
|
|
/// and every failure maps to a fixed public message.
|
||
|
|
/// </summary>
|
||
|
|
[AllowAnonymous]
|
||
|
|
[ApiController]
|
||
|
|
[Route("api/team-member-invites")]
|
||
|
|
[ResponseCache(NoStore = true, Location = ResponseCacheLocation.None)]
|
||
|
|
public sealed class TeamMemberInviteController : ControllerBase
|
||
|
|
{
|
||
|
|
public const string InvalidInviteMessage =
|
||
|
|
"This invite link is invalid or has expired. Ask your admin to send a new invite.";
|
||
|
|
|
||
|
|
private readonly ITeamMemberRegistrationService _registrationService;
|
||
|
|
|
||
|
|
public TeamMemberInviteController(ITeamMemberRegistrationService registrationService)
|
||
|
|
{
|
||
|
|
_registrationService = registrationService;
|
||
|
|
}
|
||
|
|
|
||
|
|
[HttpPost("resolve")]
|
||
|
|
public async Task<IActionResult> Resolve(TeamMemberInviteTokenRequestDTO request, CancellationToken cancellationToken)
|
||
|
|
{
|
||
|
|
var outcome = await _registrationService.ResolveAsync(request.Token, cancellationToken);
|
||
|
|
return outcome.Status == TeamMemberRegistrationStatus.Ok ? Ok(outcome.Details) : Failure(outcome);
|
||
|
|
}
|
||
|
|
|
||
|
|
[HttpPost("send-code")]
|
||
|
|
public async Task<IActionResult> SendCode(TeamMemberInviteTokenRequestDTO request, CancellationToken cancellationToken)
|
||
|
|
{
|
||
|
|
var outcome = await _registrationService.SendCodeAsync(request.Token, cancellationToken);
|
||
|
|
return outcome.Status == TeamMemberRegistrationStatus.Ok
|
||
|
|
? Ok(new { message = "Code sent" })
|
||
|
|
: Failure(outcome);
|
||
|
|
}
|
||
|
|
|
||
|
|
[HttpPost("verify-code")]
|
||
|
|
public async Task<IActionResult> VerifyCode(VerifyTeamMemberInviteCodeRequestDTO request, CancellationToken cancellationToken)
|
||
|
|
{
|
||
|
|
var outcome = await _registrationService.VerifyCodeAsync(request.Token, request.Code, cancellationToken);
|
||
|
|
return outcome.Status == TeamMemberRegistrationStatus.Ok
|
||
|
|
? Ok(new { message = "Email confirmed" })
|
||
|
|
: Failure(outcome);
|
||
|
|
}
|
||
|
|
|
||
|
|
[HttpPost("complete")]
|
||
|
|
public async Task<IActionResult> Complete(CompleteTeamMemberRegistrationRequestDTO request, CancellationToken cancellationToken)
|
||
|
|
{
|
||
|
|
var outcome = await _registrationService.CompleteAsync(request, cancellationToken);
|
||
|
|
return outcome.Status == TeamMemberRegistrationStatus.Ok && outcome.Session is not null
|
||
|
|
? Ok(LoginPayload.From(outcome.Session))
|
||
|
|
: Failure(outcome);
|
||
|
|
}
|
||
|
|
|
||
|
|
private IActionResult Failure(TeamMemberRegistrationOutcomeDTO outcome)
|
||
|
|
{
|
||
|
|
var (statusCode, code, message) = outcome.Status switch
|
||
|
|
{
|
||
|
|
TeamMemberRegistrationStatus.CodeIncorrect =>
|
||
|
|
(StatusCodes.Status400BadRequest, "code_incorrect", "Incorrect code — check your email and try again"),
|
||
|
|
TeamMemberRegistrationStatus.CodeExpired =>
|
||
|
|
(StatusCodes.Status400BadRequest, "code_expired", "This code has expired — request a new code"),
|
||
|
|
TeamMemberRegistrationStatus.CodeLocked =>
|
||
|
|
(StatusCodes.Status400BadRequest, "code_locked", "Too many incorrect attempts — request a new code"),
|
||
|
|
TeamMemberRegistrationStatus.ResendTooSoon =>
|
||
|
|
(StatusCodes.Status429TooManyRequests, "resend_too_soon", "Please wait a moment before requesting another code"),
|
||
|
|
TeamMemberRegistrationStatus.ResendLimitReached =>
|
||
|
|
(StatusCodes.Status429TooManyRequests, "resend_limit_reached", "Too many codes were requested. Ask your admin to send a new invite."),
|
||
|
|
TeamMemberRegistrationStatus.CodeDeliveryFailed =>
|
||
|
|
(StatusCodes.Status503ServiceUnavailable, "code_delivery_failed", "We couldn't send the code. Try again in a minute."),
|
||
|
|
TeamMemberRegistrationStatus.EmailNotConfirmed =>
|
||
|
|
(StatusCodes.Status400BadRequest, "email_not_confirmed", "Confirm your email before finishing registration"),
|
||
|
|
TeamMemberRegistrationStatus.PasswordRejected =>
|
||
|
|
(StatusCodes.Status400BadRequest, "password_rejected", "Password must be at least 6 characters and include one uppercase letter, one number, and one special character."),
|
||
|
|
TeamMemberRegistrationStatus.InvalidPhone =>
|
||
|
|
(StatusCodes.Status400BadRequest, "invalid_phone", "Enter a valid phone number"),
|
||
|
|
_ => (StatusCodes.Status400BadRequest, "invalid_invite", InvalidInviteMessage)
|
||
|
|
};
|
||
|
|
|
||
|
|
if (outcome.RetryAfterSeconds is int retryAfter)
|
||
|
|
Response.Headers.RetryAfter = retryAfter.ToString(System.Globalization.CultureInfo.InvariantCulture);
|
||
|
|
|
||
|
|
return StatusCode(statusCode, new { code, message, retryAfterSeconds = outcome.RetryAfterSeconds });
|
||
|
|
}
|
||
|
|
}
|