Fix config overwrite on login/navigation; use production DB for meal schedule CRUD, remove default SiteId normalization, add config load/save logging, and support legacy SiteId format.

pull/1/head
SYED MUSTUFA AHMED NAQVI 2026-03-10 10:40:51 +05:00
parent a42f43b2cc
commit 4d2433fa28
4 changed files with 88 additions and 68 deletions

View File

@ -92,44 +92,16 @@ public class ConfigService : IConfigService
public string GetSiteId() public string GetSiteId()
{ {
// Normalize to a 2-digit string (e.g. "02", "07", "12"). // Return the configured SiteId as-is. No normalization.
// Accepts legacy values like "SITE : 2" or "2" and converts them. // Returns empty string if not configured.
var raw = _config.SiteId; return _config.SiteId ?? string.Empty;
if (string.IsNullOrWhiteSpace(raw))
return "01";
var digits = new string(raw.Where(char.IsDigit).ToArray());
if (string.IsNullOrWhiteSpace(digits))
return "01";
if (!int.TryParse(digits, out var numeric))
return "01";
return Math.Clamp(numeric, 0, 99).ToString("D2");
} }
public void SetSiteId(string siteId) public void SetSiteId(string siteId)
{ {
// Store the normalized 2-digit site code. // Store the SiteId exactly as provided. No automatic normalization.
var normalized = siteId; // Caller is responsible for formatting.
if (string.IsNullOrWhiteSpace(normalized)) _config.SiteId = siteId ?? string.Empty;
{
normalized = "01";
}
else
{
var digits = new string(normalized.Where(char.IsDigit).ToArray());
if (string.IsNullOrWhiteSpace(digits) || !int.TryParse(digits, out var numeric))
{
normalized = "01";
}
else
{
normalized = Math.Clamp(numeric, 0, 99).ToString("D2");
}
}
_config.SiteId = normalized;
SaveConfig(); SaveConfig();
} }
@ -248,6 +220,7 @@ public class ConfigService : IConfigService
{ {
if (!File.Exists(_configPath)) if (!File.Exists(_configPath))
{ {
System.Diagnostics.Debug.WriteLine("[ConfigService] Config file not found. Creating with defaults.");
var defaults = new AppConfig(); var defaults = new AppConfig();
SaveConfig(defaults); SaveConfig(defaults);
return defaults; return defaults;
@ -257,10 +230,17 @@ public class ConfigService : IConfigService
var config = JsonSerializer.Deserialize<AppConfig>(json); var config = JsonSerializer.Deserialize<AppConfig>(json);
config ??= new AppConfig(); config ??= new AppConfig();
MigrateScanIntervalFromSecondsIfNeeded(config); MigrateScanIntervalFromSecondsIfNeeded(config);
System.Diagnostics.Debug.WriteLine($"[ConfigService] Config loaded from {_configPath}");
System.Diagnostics.Debug.WriteLine($"[ConfigService] SiteId: '{config.SiteId}' (empty=not configured)");
System.Diagnostics.Debug.WriteLine($"[ConfigService] MySqlConnectionString: {(string.IsNullOrWhiteSpace(config.MySqlConnectionString) ? "NOT CONFIGURED" : MaskConnectionString(config.MySqlConnectionString))}");
System.Diagnostics.Debug.WriteLine($"[ConfigService] HrmsLookupConnectionString: {(string.IsNullOrWhiteSpace(config.HrmsLookupConnectionString) ? "NOT CONFIGURED" : MaskConnectionString(config.HrmsLookupConnectionString))}");
return config; return config;
} }
catch catch (Exception ex)
{ {
System.Diagnostics.Debug.WriteLine($"[ConfigService] ERROR loading config: {ex.Message}");
return new AppConfig(); return new AppConfig();
} }
} }
@ -308,18 +288,49 @@ public class ConfigService : IConfigService
{ {
try try
{ {
// Log what's being saved
System.Diagnostics.Debug.WriteLine($"[ConfigService] Saving config...");
System.Diagnostics.Debug.WriteLine($"[ConfigService] SiteId: '{_config.SiteId}'");
System.Diagnostics.Debug.WriteLine($"[ConfigService] MySqlConnectionString: {(string.IsNullOrWhiteSpace(_config.MySqlConnectionString) ? "NOT CONFIGURED" : MaskConnectionString(_config.MySqlConnectionString))}");
System.Diagnostics.Debug.WriteLine($"[ConfigService] HrmsLookupConnectionString: {(string.IsNullOrWhiteSpace(_config.HrmsLookupConnectionString) ? "NOT CONFIGURED" : MaskConnectionString(_config.HrmsLookupConnectionString))}");
var json = JsonSerializer.Serialize(config, new JsonSerializerOptions var json = JsonSerializer.Serialize(config, new JsonSerializerOptions
{ {
WriteIndented = true WriteIndented = true
}); });
File.WriteAllText(_configPath, json); File.WriteAllText(_configPath, json);
System.Diagnostics.Debug.WriteLine($"[ConfigService] Config saved to {_configPath}");
} }
catch catch (Exception ex)
{ {
// Ignore write failures (e.g., read-only location). System.Diagnostics.Debug.WriteLine($"[ConfigService] ERROR saving config: {ex.Message}");
} }
} }
/// <summary>
/// Masks sensitive connection string values for logging.
/// Shows server and database but hides password.
/// </summary>
private static string MaskConnectionString(string connectionString)
{
if (string.IsNullOrWhiteSpace(connectionString))
return string.Empty;
// Simple masking: show everything before 'Password=' and mask the rest
var passwordIndex = connectionString.IndexOf("Password=", StringComparison.OrdinalIgnoreCase);
if (passwordIndex >= 0)
{
var semicolonAfterPassword = connectionString.IndexOf(';', passwordIndex);
if (semicolonAfterPassword >= 0)
{
return connectionString.Substring(0, passwordIndex) + "Password=***" + connectionString.Substring(semicolonAfterPassword);
}
return connectionString.Substring(0, passwordIndex) + "Password=***";
}
return connectionString; // No password found, return as-is
}
private sealed class AppConfig private sealed class AppConfig
{ {
public string SyncApiEndpoint { get; set; } = "https://api.example.com/uind/sync"; public string SyncApiEndpoint { get; set; } = "https://api.example.com/uind/sync";
@ -328,10 +339,10 @@ public class ConfigService : IConfigService
public int ScanTimeoutSeconds { get; set; } = 0; public int ScanTimeoutSeconds { get; set; } = 0;
public int ScanIntervalDays { get; set; } = 0; public int ScanIntervalDays { get; set; } = 0;
public int ScanIntervalHours { get; set; } = 0; public int ScanIntervalHours { get; set; } = 0;
public int ScanIntervalMinutes { get; set; } = 1; public int ScanIntervalMinutes { get; set; } = 0;
public int ScanIntervalSeconds { get; set; } = 0; public int ScanIntervalSeconds { get; set; } = 2;
public string AdminCardId { get; set; } = "ADMIN"; public string AdminCardId { get; set; } = "ADMIN";
public string SiteId { get; set; } = "SITE : 1"; public string SiteId { get; set; } = string.Empty; // No default - must be explicitly configured
public string DeviceId { get; set; } = string.Empty; public string DeviceId { get; set; } = string.Empty;
// Admin credential persistence (optional). // Admin credential persistence (optional).
@ -343,6 +354,6 @@ public class ConfigService : IConfigService
public string MySqlConnectionString { get; set; } = string.Empty; public string MySqlConnectionString { get; set; } = string.Empty;
// Local HRMS MySQL for employee lookup by RFID. Separate from production sync. // Local HRMS MySQL for employee lookup by RFID. Separate from production sync.
public string HrmsLookupConnectionString { get; set; } = "Server=192.168.90.147;Port=3306;Database=hrms;User Id=utopia;Password=Utopia01;SslMode=None;"; public string HrmsLookupConnectionString { get; set; } = string.Empty;
} }
} }

View File

@ -5,7 +5,7 @@ namespace UtopiaCanteenSystem.Services;
/// <summary> /// <summary>
/// Meal schedule CRUD against production HRMS MySQL table hrms.meal_schedule. /// Meal schedule CRUD against production HRMS MySQL table hrms.meal_schedule.
/// Uses IConfigService.GetHrmsLookupConnectionString() (same DB as employee lookup). /// Uses IConfigService.GetMySqlConnectionString() (production database connection).
/// Schema: id (bigint), meal_name (varchar), start_time (time), end_time (time), created_at, updated_at, location_site_id (int). /// Schema: id (bigint), meal_name (varchar), start_time (time), end_time (time), created_at, updated_at, location_site_id (int).
/// No is_active column; all rows are treated as active. /// No is_active column; all rows are treated as active.
/// </summary> /// </summary>
@ -22,7 +22,7 @@ public class ProductionMealScheduleService : IMealScheduleService
/// <inheritdoc /> /// <inheritdoc />
public IReadOnlyList<MealSchedule> GetActiveSchedulesForSite(string siteId) public IReadOnlyList<MealSchedule> GetActiveSchedulesForSite(string siteId)
{ {
var connStr = _configService.GetHrmsLookupConnectionString(); var connStr = _configService.GetMySqlConnectionString();
if (string.IsNullOrWhiteSpace(connStr)) if (string.IsNullOrWhiteSpace(connStr))
return Array.Empty<MealSchedule>(); return Array.Empty<MealSchedule>();
@ -49,7 +49,7 @@ public class ProductionMealScheduleService : IMealScheduleService
/// <inheritdoc /> /// <inheritdoc />
public async Task<IReadOnlyList<MealSchedule>> GetAllSchedulesAsync(CancellationToken cancellationToken = default) public async Task<IReadOnlyList<MealSchedule>> GetAllSchedulesAsync(CancellationToken cancellationToken = default)
{ {
var connStr = _configService.GetHrmsLookupConnectionString(); var connStr = _configService.GetMySqlConnectionString();
if (string.IsNullOrWhiteSpace(connStr)) if (string.IsNullOrWhiteSpace(connStr))
return Array.Empty<MealSchedule>(); return Array.Empty<MealSchedule>();
@ -67,9 +67,9 @@ public class ProductionMealScheduleService : IMealScheduleService
/// <inheritdoc /> /// <inheritdoc />
public async Task<long> CreateAsync(MealSchedule schedule, CancellationToken cancellationToken = default) public async Task<long> CreateAsync(MealSchedule schedule, CancellationToken cancellationToken = default)
{ {
var connStr = _configService.GetHrmsLookupConnectionString(); var connStr = _configService.GetMySqlConnectionString();
if (string.IsNullOrWhiteSpace(connStr)) if (string.IsNullOrWhiteSpace(connStr))
throw new InvalidOperationException("HRMS MySQL connection string not configured."); throw new InvalidOperationException("Production MySQL connection string not configured.");
var utc = DateTime.UtcNow; var utc = DateTime.UtcNow;
var mealName = (schedule.MealName ?? string.Empty).Trim(); var mealName = (schedule.MealName ?? string.Empty).Trim();
@ -93,9 +93,9 @@ public class ProductionMealScheduleService : IMealScheduleService
/// <inheritdoc /> /// <inheritdoc />
public async Task UpdateAsync(MealSchedule schedule, CancellationToken cancellationToken = default) public async Task UpdateAsync(MealSchedule schedule, CancellationToken cancellationToken = default)
{ {
var connStr = _configService.GetHrmsLookupConnectionString(); var connStr = _configService.GetMySqlConnectionString();
if (string.IsNullOrWhiteSpace(connStr)) if (string.IsNullOrWhiteSpace(connStr))
throw new InvalidOperationException("HRMS MySQL connection string not configured."); throw new InvalidOperationException("Production MySQL connection string not configured.");
var utc = DateTime.UtcNow; var utc = DateTime.UtcNow;
var mealName = (schedule.MealName ?? string.Empty).Trim(); var mealName = (schedule.MealName ?? string.Empty).Trim();
@ -117,9 +117,9 @@ public class ProductionMealScheduleService : IMealScheduleService
/// <inheritdoc /> /// <inheritdoc />
public async Task DeleteAsync(long id, CancellationToken cancellationToken = default) public async Task DeleteAsync(long id, CancellationToken cancellationToken = default)
{ {
var connStr = _configService.GetHrmsLookupConnectionString(); var connStr = _configService.GetMySqlConnectionString();
if (string.IsNullOrWhiteSpace(connStr)) if (string.IsNullOrWhiteSpace(connStr))
throw new InvalidOperationException("HRMS MySQL connection string not configured."); throw new InvalidOperationException("Production MySQL connection string not configured.");
await using var conn = new MySqlConnection(connStr); await using var conn = new MySqlConnection(connStr);
await conn.OpenAsync(cancellationToken).ConfigureAwait(false); await conn.OpenAsync(cancellationToken).ConfigureAwait(false);

View File

@ -215,14 +215,22 @@ public partial class ScannerDashboardViewModel : ObservableObject
_clockTimer.Tick += (_, _) => CurrentTime = DateTime.Now.ToString("MM/dd/yyyy, hh:mm:ss tt"); _clockTimer.Tick += (_, _) => CurrentTime = DateTime.Now.ToString("MM/dd/yyyy, hh:mm:ss tt");
_clockTimer.Start(); _clockTimer.Start();
// Load initial site from config (e.g. "SITE : 1" -> "1"). // Load initial site from config.
var siteId = _configService.GetSiteId(); var siteId = _configService.GetSiteId();
if (!string.IsNullOrWhiteSpace(siteId) && siteId.StartsWith("SITE : ", StringComparison.OrdinalIgnoreCase)) if (!string.IsNullOrWhiteSpace(siteId))
{
// Extract digits from legacy format "SITE : X"or use value directly if already numeric
if (siteId.StartsWith("SITE : ", StringComparison.OrdinalIgnoreCase))
{ {
var num = siteId.Substring("SITE : ".Length).Trim(); var num = siteId.Substring("SITE : ".Length).Trim();
if (num.Length > 0 && num.All(char.IsDigit)) if (num.Length > 0 && num.All(char.IsDigit))
SiteNumber = num; SiteNumber = num;
} }
else if (siteId.All(char.IsDigit))
{
SiteNumber = siteId;
}
}
RefreshScannerStatus(); RefreshScannerStatus();
_ = RefreshDashboardAsync(); _ = RefreshDashboardAsync();

View File

@ -3,13 +3,14 @@
"ScannerConnected": false, "ScannerConnected": false,
"ScanIntervalDays": 0, "ScanIntervalDays": 0,
"ScanIntervalHours": 0, "ScanIntervalHours": 0,
"ScanIntervalMinutes": 1, "ScanIntervalMinutes": 0,
"ScanIntervalSeconds": 2,
"AdminCardId": "ADMIN", "AdminCardId": "ADMIN",
"SiteId": "02", "SiteId": "02",
"DeviceId": "", "DeviceId": "",
"RememberAdminCredentials": false, "RememberAdminCredentials": false,
"SavedAdminUsername": "", "SavedAdminUsername": "",
"SavedAdminPasswordProtected": "", "SavedAdminPasswordProtected": "",
"MySqlConnectionString": "Server=localhost;Database=hrms;User=root;Password=CHANGE_ME;Port=3306;" "MySqlConnectionString": "SERVER=utopia-industries-rr.c5qech8o9lgg.us-east-1.rds.amazonaws.com;DATABASE=hrms;UID=uind_hrms_user;PASSWORD=UINDHRMS01;"
} }