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); } } } }