33 changed files with 3160 additions and 299 deletions
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
File diff suppressed because it is too large
@ -0,0 +1,295 @@ |
|||||
|
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<string, List<string>> _history = new Dictionary<string, List<string>>(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<string> GetValues(string fieldName) |
||||
|
{ |
||||
|
if (_history.TryGetValue(fieldName, out var list)) |
||||
|
{ |
||||
|
return new List<string>(list); |
||||
|
} |
||||
|
return new List<string>(); |
||||
|
} |
||||
|
|
||||
|
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<string>(); |
||||
|
_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<string>(_history.Keys); |
||||
|
for (int i = 0; i < keys.Count; i++) |
||||
|
{ |
||||
|
var key = keys[i]; |
||||
|
var values = _history[key]; |
||||
|
var arrayItems = new List<string>(); |
||||
|
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<string>(); |
||||
|
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<bool> 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; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -1 +1 @@ |
|||||
ec6dd6e1649354a88820f29fa99f449377f51e4d5e8d8707b3c7326a682c80e1 |
786424e28f57daf62f6180d8da5730790c336df832876ad7e4dec61838b897e5 |
||||
|
|||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
File diff suppressed because it is too large
Loading…
Reference in new issue