using FastEndpoints;
using FrameworkBLL.User;
using GB5Shared.DTO.Framework.Login;
using GB5Shared.DTO.Framework.ResponseStandard;
using GB5Shared.FastEndPoint;
using static GB5Shared.GB5Constant.Constant;
namespace FrameworkSL.Endpoints.User
{
///
/// GB4 parity for User.svc's LogOutUser(UserCode) (POST /LogOut/?UserCode={UserCode}).
/// Logs a user out by UserCode rather than by the caller's own session — removes the
/// user's active session row(s) from MSESSIONSTORE so their next API call gets 401
/// from SessionHeartbeatMiddleware.
///
public class LogOutUser : BaseEndpoint>
{
private readonly IUserBLL _UserBLL;
public LogOutUser(IUserBLL UserBLL)
{
_UserBLL = UserBLL;
}
public override void Configure()
{
Post("/User/LogOutUser");
AllowAnonymous();
// This endpoint has no [FromBody] property — everything comes from the
// Login/ConnectionName headers and the UserCode query param. FastEndpoints'
// default binder still tries to JSON-deserialize the body whenever the
// request carries a JSON content-type, even an empty one (Postman's "raw
// JSON" tab sends Content-Type: application/json with 0 bytes by default),
// which throws "input does not contain any JSON tokens". Registering a
// binder with JsonBody excluded from BindingSource stops that attempt.
RequestBinder(new NoBodyRequestBinder());
}
private sealed class NoBodyRequestBinder : RequestBinder
{
public NoBodyRequestBinder() : base(BindingSource.Headers | BindingSource.QueryParams | BindingSource.RouteValues)
{
}
}
public record LogOutUserParameters(
[property: FromHeader] string Login,
[property: FromHeader(null, false)] string? ConnectionName,
[property: QueryParam] string UserCode
);
protected override string? GetCacheKey(LogOutUserParameters req, LoginDTO login) => null;
protected override async Task> ExecuteAsync(LogOutUserParameters req, LoginDTO LoginDTO, CancellationToken ct)
{
try
{
// GB4 read ConnectionName from its own header, but GB5's "Login" header already
// carries DatabaseName inside LoginDTO — fall back to that so callers don't have
// to send ConnectionName twice.
string connectionName = !string.IsNullOrWhiteSpace(req.ConnectionName)
? req.ConnectionName
: LoginDTO!.DatabaseName;
var Result = await _UserBLL.LogOutUser(connectionName, req.UserCode, LoginDTO!);
return await GB5Shared.ResponseStandard.Response.CreateSuccessResponse(Result, CacheKeyLevel.NOT_REQUIRED, LoginDTO);
}
catch (Exception ex)
{
return await GB5Shared.ResponseStandard.Response.CreateExceptionError(ex, CacheKeyLevel.NOT_REQUIRED, LoginDTO, ex.Message, 500);
}
}
}
}