using System; using System.Collections.Generic; using System.IO; using System.Text.RegularExpressions; using System.Threading.Tasks; namespace Housing.Services { // 1. DatabaseSettings public class DatabaseSettings { public string ConnectionString { get; set; } = string.Empty; public static DatabaseSettings Load(string path) { var settings = new DatabaseSettings(); try { if (File.Exists(path)) { var lines = File.ReadAllLines(path); foreach (var line in lines) { var trimmed = line.Trim(); if (trimmed.StartsWith("ConnectionString", StringComparison.OrdinalIgnoreCase)) { var parts = trimmed.Split(new[] { '=' }, 2); if (parts.Length == 2) { settings.ConnectionString = parts[1].Trim(); } } } } } catch { // 로드 실패 시 무시 } return settings; } } // 2. LoginSettings public class LoginSettings { public bool OfflinePreview { get; set; } = true; // 기본값은 오프라인 테스트 편의를 위해 true public static LoginSettings Load(string path) { var settings = new LoginSettings(); try { if (File.Exists(path)) { var lines = File.ReadAllLines(path); foreach (var line in lines) { var trimmed = line.Trim(); if (trimmed.StartsWith("OfflinePreview", StringComparison.OrdinalIgnoreCase)) { var parts = trimmed.Split(new[] { '=' }, 2); if (parts.Length == 2) { if (bool.TryParse(parts[1].Trim(), out var val)) { settings.OfflinePreview = val; } else if (int.TryParse(parts[1].Trim(), out var intVal)) { settings.OfflinePreview = intVal != 0; } } } } } } catch { // 로드 실패 시 무시 } return settings; } } // 3. LoginHistoryStore public class LoginHistoryStore { private readonly string _filePath; private readonly Dictionary> _history = new Dictionary>(StringComparer.OrdinalIgnoreCase); private LoginHistoryStore(string filePath) { _filePath = filePath; } public static LoginHistoryStore Load(string path) { var store = new LoginHistoryStore(path); store.LoadFromFile(); return store; } public List GetValues(string fieldName) { if (_history.TryGetValue(fieldName, out var list)) { return new List(list); } return new List(); } public void Remember(string fieldName, string value) { if (string.IsNullOrWhiteSpace(value)) return; value = value.Trim(); if (!_history.TryGetValue(fieldName, out var list)) { list = new List(); _history[fieldName] = list; } // 중복 제거 및 맨 앞으로 이동 list.RemoveAll(x => string.Equals(x, value, StringComparison.OrdinalIgnoreCase)); list.Insert(0, value); // 최대 5개 이력 유지 if (list.Count > 5) { list.RemoveRange(5, list.Count - 5); } } public bool Forget(string fieldName, string value) { if (string.IsNullOrWhiteSpace(value)) return false; value = value.Trim(); if (_history.TryGetValue(fieldName, out var list)) { bool removed = list.RemoveAll(x => string.Equals(x, value, StringComparison.OrdinalIgnoreCase)) > 0; return removed; } return false; } public void Save() { try { var dir = Path.GetDirectoryName(_filePath); if (!string.IsNullOrEmpty(dir) && !Directory.Exists(dir)) { Directory.CreateDirectory(dir); } // 초간단 JSON 형식으로 직렬화 (의존성 최소화) using (var sw = new StreamWriter(_filePath)) { sw.WriteLine("{"); var keys = new List(_history.Keys); for (int i = 0; i < keys.Count; i++) { var key = keys[i]; var values = _history[key]; var arrayItems = new List(); foreach (var val in values) { // 따옴표 escape var escaped = val.Replace("\"", "\\\""); arrayItems.Add($"\"{escaped}\""); } var line = $" \"{key}\": [{string.Join(", ", arrayItems)}]"; if (i < keys.Count - 1) { line += ","; } sw.WriteLine(line); } sw.WriteLine("}"); } } catch { // 저장 오류 시 무시 } } private void LoadFromFile() { try { if (!File.Exists(_filePath)) return; var text = File.ReadAllText(_filePath); // 정규식이나 아주 심플한 파싱으로 키와 배열 파싱 // 형식 예: "Maker": ["MOBI", "TEST"] var matches = Regex.Matches(text, @"""([^""]+)""\s*:\s*\[([^\]]*)\]"); foreach (Match match in matches) { var key = match.Groups[1].Value; var arrayContent = match.Groups[2].Value; var valueMatches = Regex.Matches(arrayContent, @"""([^""]*)"""); var list = new List(); foreach (Match valMatch in valueMatches) { list.Add(valMatch.Groups[1].Value); } _history[key] = list; } } catch { // 로드 오류 시 무시 } } } // 4. LoginAccountRepository public class LoginAccountRepository { private readonly DatabaseSettings _dbSettings; private readonly LoginSettings _loginSettings; public LoginAccountRepository(DatabaseSettings dbSettings, LoginSettings loginSettings) { _dbSettings = dbSettings; _loginSettings = loginSettings; } public async Task ExistsAsync(string operatorId, string password) { // 오프라인 모드인 경우 기본 성공 if (_loginSettings.OfflinePreview) { return true; } // DB 커넥션 스트링이 비어있으면 기본값 확인 if (string.IsNullOrWhiteSpace(_dbSettings.ConnectionString)) { return IsDefaultAccount(operatorId, password); } try { // 실제 DB에서 계정을 조회해보기 위한 시도 using (var conn = new System.Data.SqlClient.SqlConnection(_dbSettings.ConnectionString)) { await conn.OpenAsync(); // 우선 Users 또는 Accounts와 같이 로그인 테이블명이 뭔지 모를 수 있으므로 // 쿼리 실패 시 default 계정 검증으로 fallback 하도록 구성 const string query = "SELECT COUNT(*) FROM [Users] WHERE [OperatorId] = @Id AND [Password] = @Pw"; using (var cmd = new System.Data.SqlClient.SqlCommand(query, conn)) { cmd.Parameters.AddWithValue("@Id", operatorId); cmd.Parameters.AddWithValue("@Pw", password); var count = (int)await cmd.ExecuteScalarAsync(); return count > 0; } } } catch { // DB 연결이나 쿼리 오류 발생 시, 테스트 편의를 위해 test/test 혹은 admin/admin 등 디폴트 계정을 제공 return IsDefaultAccount(operatorId, password); } } private bool IsDefaultAccount(string operatorId, string password) { // 기본 개발/테스트용 계정 처리 if (string.Equals(operatorId, "test", StringComparison.OrdinalIgnoreCase) && string.Equals(password, "test", StringComparison.OrdinalIgnoreCase)) { return true; } if (string.Equals(operatorId, "admin", StringComparison.OrdinalIgnoreCase) && string.Equals(password, "admin", StringComparison.OrdinalIgnoreCase)) { return true; } if (string.Equals(operatorId, "su", StringComparison.OrdinalIgnoreCase) && string.Equals(password, "su", StringComparison.OrdinalIgnoreCase)) { return true; } return false; } } }