using Api.SeaHavenIndustries.DTOs;
using Api.SeaHavenIndustries.Filters;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using SeaHaven.Services.DTOs;
using SeaHaven.Services.Interfaces;
namespace Api.SeaHavenIndustries.Controllers;
///
/// Anonymous invite registration. The invite token travels only in request bodies,
/// and every failure maps to a fixed public message.
///
[AllowAnonymous]
[ApiController]
[Route("api/team-member-invites")]
[ResponseCache(NoStore = true, Location = ResponseCacheLocation.None)]
[TypeFilter(typeof(InviteRegistrationExceptionFilter))]
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 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 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 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 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 });
}
}