using SwDAL.CustomCode.ServerConfig;
using SwDAL.DTO.ServerConfig;
using GB5Shared.Connection;
using GB5Shared.DTO.Framework.Login;
using GB5Shared.GenerateAutoNumber;
using Microsoft.Extensions.Logging;
using static GB5Shared.GB5Constant.Constant;
namespace SwBLL.ServerConfig;
/// Everything a caller supplies to register a newly-provisioned physical database in
/// MSERVER/MSERVERCONFIG. DatabasePassword is plaintext IN (encrypted internally before storage,
/// via IApplicationConnection.Encrypt — the same convention ServerConfigCache already decrypts
/// with on read) — never log or persist it anywhere else.
public class RegisterServerConfigRequest
{
public string ConnectionName { get; set; } = string.Empty;
public string ServerMachineName { get; set; } = string.Empty;
public string? ServerName { get; set; }
public string? ServerIp { get; set; }
/// The IIS/service server that answers requests for this connection (drives
/// VersionDAL.VersionUrlIdentifier's PRIMARYBASEURL/SECONDORYBASEURL resolution) — a
/// DIFFERENT MSERVER row than (the DB server) whenever
/// app and database run on separate boxes. Defaults to
/// when omitted, i.e. a single-box install where one machine is both. Leaving this unset
/// with no default was the exact bug that left every row this endpoint ever wrote with a
/// NULL/-1 SERVICESERVERID, matching the identical defect fixed in
/// ClientDetailQB.CREATE_SERVER_CONFIG.
public string? ServiceServerMachineName { get; set; }
public byte DatabaseType { get; set; } // 0=SqlServer,1=PostgreSQL,2=MySQL,3=Oracle
public string DatabaseName { get; set; } = string.Empty;
public string DatabaseUserName { get; set; } = string.Empty;
public string DatabasePassword { get; set; } = string.Empty; // plaintext in, encrypted before storage
public string DatabasePort { get; set; } = string.Empty;
/// 0=Production/OLTP, 2=Report, 3=Archive — see MServerConfigWriteDTO's doc
/// comment. Never 1 (TCMS-owned, TEST-only).
public byte DbInstanceType { get; set; }
public int ClientId { get; set; }
}
/// Registers physical databases in the shared MSERVER/MSERVERCONFIG tables so
/// GB5Framework/FrameworkBLL/ReportOrchestration/ReportConnectionResolver can route to them at
/// runtime — the write-side counterpart to that resolver's read-only ServerConfigCache. No
/// module (SqlWorkbench included) owns this table; this is the first real write path for it.
public interface IServerConfigBLL
{
/// Finds-or-creates the MSERVER row for request.ServerMachineName, then inserts a
/// new MSERVERCONFIG row pointing at it. Returns the new ServerConfigId.
Task RegisterDatabaseAsync(RegisterServerConfigRequest request, LoginDTO login, CancellationToken ct);
/// Points mainServerConfigId's ReportServerConfigId or ArchiveServerConfigId (per
/// isArchive) at linkedServerConfigId — the step that makes ReportConnectionResolver actually
/// pick up a newly-registered Report/Archive database for that client.
Task LinkReportOrArchiveAsync(int mainServerConfigId, int linkedServerConfigId, bool isArchive, CancellationToken ct);
}
public class ServerConfigBLL : IServerConfigBLL
{
private readonly IServerConfigDAL _dal;
private readonly IApplicationConnection _appConnection;
private readonly AutoNumber _autoNumber;
private readonly ILogger _logger;
public ServerConfigBLL(IServerConfigDAL dal, IApplicationConnection appConnection, AutoNumber autoNumber, ILogger logger)
{
_dal = dal;
_appConnection = appConnection;
_autoNumber = autoNumber;
_logger = logger;
}
public async Task RegisterDatabaseAsync(RegisterServerConfigRequest request, LoginDTO login, CancellationToken ct)
{
var serverId = await _dal.FindServerIdByMachineNameAsync(request.ServerMachineName, ct).ConfigureAwait(false);
if (serverId is null)
{
var now = DateTime.UtcNow;
var auto = await _autoNumber.GetNumberAsync(1, AUTONUMBERCONSTANT.MSERVER, login).ConfigureAwait(false);
var serverDto = new MServerDTO
{
ServerId = auto.StartNumber,
ServerName = request.ServerName ?? request.ServerMachineName,
ServerIp = request.ServerIp,
ServerMachineName = request.ServerMachineName,
CreatedById = login.UserId,
CreatedOn = now,
ModifiedById = login.UserId,
ModifiedOn = now
};
serverId = await _dal.InsertServerAsync(serverDto, ct).ConfigureAwait(false);
_logger.LogInformation("Registered new MSERVER row {ServerId} for machine {ServerMachineName}",
serverId, request.ServerMachineName);
}
// Resolve the service (IIS) server the same find-or-create way as the DB server above.
// Defaults to the DB server's own machine name for a single-box install; a genuinely
// separate app tier passes ServiceServerMachineName explicitly. This is the fix for
// the confirmed bug where this path always left SERVICESERVERID at its DTO default of
// -1, reproducing VersionDAL.VersionUrlIdentifier's "Service server is not correctly
// assigned" failure for every connection ever registered here.
var serviceServerMachineName = string.IsNullOrWhiteSpace(request.ServiceServerMachineName)
? request.ServerMachineName
: request.ServiceServerMachineName;
var serviceServerId = serviceServerMachineName == request.ServerMachineName
? serverId.Value
: await _dal.FindServerIdByMachineNameAsync(serviceServerMachineName, ct).ConfigureAwait(false);
if (serviceServerId is null)
{
var now = DateTime.UtcNow;
var svcAuto = await _autoNumber.GetNumberAsync(1, AUTONUMBERCONSTANT.MSERVER, login).ConfigureAwait(false);
serviceServerId = await _dal.InsertServerAsync(new MServerDTO
{
ServerId = svcAuto.StartNumber,
ServerName = serviceServerMachineName,
ServerMachineName = serviceServerMachineName,
CreatedById = login.UserId,
CreatedOn = now,
ModifiedById = login.UserId,
ModifiedOn = now
}, ct).ConfigureAwait(false);
_logger.LogInformation("Registered new MSERVER row {ServerId} for service-server machine {ServiceServerMachineName}",
serviceServerId, serviceServerMachineName);
}
// Never persist a plaintext password — same encryption convention ServerConfigCache
// already decrypts with on read (IApplicationConnection.Decrypt(value, "GB5")).
var encryptedPassword = _appConnection.Encrypt(request.DatabasePassword, "GB5");
var nowConfig = DateTime.UtcNow;
var configAuto = await _autoNumber.GetNumberAsync(1, AUTONUMBERCONSTANT.MSERVERCONFIG, login).ConfigureAwait(false);
var configDto = new MServerConfigWriteDTO
{
ServerConfigId = configAuto.StartNumber,
ConnectionName = request.ConnectionName,
ServerId = serverId.Value,
DatabaseType = request.DatabaseType,
DatabaseName = request.DatabaseName,
DatabaseUserName = request.DatabaseUserName,
DatabasePassword = encryptedPassword,
DatabasePort = request.DatabasePort,
DbInstanceType = request.DbInstanceType,
ClientId = request.ClientId,
CreatedById = login.UserId,
CreatedOn = nowConfig,
ModifiedById = login.UserId,
ModifiedOn = nowConfig,
ServiceServerId = serviceServerId.Value
};
var serverConfigId = await _dal.InsertServerConfigAsync(configDto, ct).ConfigureAwait(false);
_logger.LogInformation(
"Registered new MSERVERCONFIG row {ServerConfigId} for database {DatabaseName} (DbInstanceType {DbInstanceType})",
serverConfigId, request.DatabaseName, request.DbInstanceType);
return serverConfigId;
}
public async Task LinkReportOrArchiveAsync(int mainServerConfigId, int linkedServerConfigId, bool isArchive, CancellationToken ct)
{
if (isArchive)
await _dal.UpdateArchiveServerConfigIdAsync(mainServerConfigId, linkedServerConfigId, ct).ConfigureAwait(false);
else
await _dal.UpdateReportServerConfigIdAsync(mainServerConfigId, linkedServerConfigId, ct).ConfigureAwait(false);
_logger.LogInformation(
"Linked ServerConfigId {LinkedServerConfigId} as {Kind} for main ServerConfigId {MainServerConfigId}",
linkedServerConfigId, isArchive ? "Archive" : "Report", mainServerConfigId);
}
}