using FrameworkBLL.Module; using GB5Shared.DTO.Framework.Login; using GB5Shared.DTO.Framework.ResponseStandard; using GB5Shared.Telemetry; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Logging; using Newtonsoft.Json; using static GB5Shared.GB5Constant.Constant; namespace FrameworkSL.Controllers.Module { [ApiController] [Route("Module")] public class ModuleController : ControllerBase { private readonly IModuleBLL _moduleBLL; private readonly ILogger _logger; public ModuleController(IModuleBLL moduleBLL, ILogger logger) { _moduleBLL = moduleBLL; _logger = logger; } /// /// GET: /Module/GetList /// [HttpGet("GetList")] [AllowAnonymous] public async Task GetList([FromHeader] string Login) { LoginDTO? loginDto = null; try { // Deserialize login header JSON loginDto = JsonConvert.DeserializeObject(Login); // Derive the module from the CALLING FRONTEND's subdomain (e.g. hrms.goodbookserp.in // -> "hrms"). All tenant frontends (hrms./crm./costing.goodbookserp.in) call the // SAME shared API gateway (api-dev.goodbookserp.in) — that's a cross-origin XHR, so // Host/X-Forwarded-Host on the request is always the gateway's own domain, never the // frontend's. The frontend's actual origin only arrives via the browser's Origin // header (CORS), with Referer as a same-origin-fetch fallback. X-Forwarded-Host and // Request.Host are kept as further fallbacks for non-browser/direct callers (Postman, // server-to-server, local dev hitting fws directly) where neither header is sent. // Null/blank when none of these yield a subdomain segment, in which case // GetModuleList falls back to its original unfiltered behaviour. string? host = null; string hostSource = "none"; if (Request.Headers.TryGetValue("Origin", out var origin) && Uri.TryCreate(origin.ToString(), UriKind.Absolute, out var originUri)) { host = originUri.Host; hostSource = "origin"; } else if (Request.Headers.TryGetValue("Referer", out var referer) && Uri.TryCreate(referer.ToString(), UriKind.Absolute, out var refererUri)) { host = refererUri.Host; hostSource = "referer"; } else if (Request.Headers.TryGetValue("X-Forwarded-Host", out var forwardedHost) && !string.IsNullOrWhiteSpace(forwardedHost)) { host = forwardedHost.ToString().Split(':')[0]; hostSource = "x-forwarded-host"; } else { host = HttpContext.Request.Host.Host; hostSource = "request.host"; } string? module = string.IsNullOrWhiteSpace(host) ? null : host.Split('.')[0]; // Surface the request URL, resolved host and derived module as searchable span // tags, and as a timeline step, so this resolution is visible end-to-end in Zipkin // alongside the db.statement/db.params tags QueryExecutor already emits downstream. GB5Trace.Tag("gb5.request.url", $"{Request.Scheme}://{Request.Host}{Request.Path}{Request.QueryString}"); GB5Trace.Tag("gb5.request.host", host); GB5Trace.Tag("gb5.request.host.source", hostSource); GB5Trace.Tag("gb5.module.derived", module ?? "(none)"); GB5Trace.Step("resolve-module-from-host", new { host, hostSource, module }); var result = await _moduleBLL.GetModuleList(loginDto!, module); var response = await GB5Shared.ResponseStandard.Response .CreateSuccessResponse(result, CacheKeyLevel.ROLE_LEVEL, loginDto!); return Ok(response); } catch (Exception ex) { _logger.LogError(ex, "Error executing GetModuleList"); var errorResponse = await GB5Shared.ResponseStandard.Response .CreateExceptionError( ex, CacheKeyLevel.NOT_REQUIRED, loginDto!, ex.Message, 500 ); return StatusCode(500, errorResponse); } } } }