using SurrealDb.Net; using TarninaHub.Application.Abstractions; using TarninaHub.Application.Dtos; using TarninaHub.Application.Models; namespace TarninaHub.Infrastructure.Persistence; public sealed class UserService : IUserService { private const string SelectUserFields = "type::string(id) AS id, phone, email, role, type::string(organization) AS organization"; private readonly ISurrealDbClient _client; public UserService(ISurrealDbClient client) { _client = client; } public async Task FindByPhoneAsync(string phone, CancellationToken cancellationToken) { var response = await _client.RawQuery( $"SELECT {SelectUserFields} FROM user WHERE phone = $phone LIMIT 1;", new Dictionary { ["phone"] = phone }, cancellationToken); response.EnsureAllOks(); var rows = response.GetValue>(0) ?? []; return rows.Count > 0 ? Map(rows[0]) : null; } public async Task GetByIdAsync(string userId, CancellationToken cancellationToken) { var (table, id) = SurrealRecordId.Split(userId); var response = await _client.RawQuery( $"SELECT {SelectUserFields} FROM type::record($table, $id) LIMIT 1;", new Dictionary { ["table"] = table, ["id"] = id }, cancellationToken); response.EnsureAllOks(); var rows = response.GetValue>(0) ?? []; return rows.Count > 0 ? Map(rows[0]) : null; } public async Task CreateAsync( RegisterRequest request, string role, CancellationToken cancellationToken) { var emailClause = string.IsNullOrWhiteSpace(request.Email) ? string.Empty : ", email = $email"; var parameters = new Dictionary { ["phone"] = request.Phone, ["password"] = request.Password, ["role"] = role, }; if (!string.IsNullOrWhiteSpace(request.Email)) { parameters["email"] = request.Email; } var response = await _client.RawQuery( "CREATE user SET phone = $phone" + emailClause + ", " + "password = crypto::argon2::generate($password), role = $role; " + $"SELECT {SelectUserFields} FROM user WHERE phone = $phone LIMIT 1;", parameters, cancellationToken); response.EnsureAllOks(); var rows = response.GetValue>(1) ?? []; return rows.Count > 0 ? Map(rows[0]) : null; } public async Task VerifyCredentialsAsync( string phone, string password, CancellationToken cancellationToken) { var response = await _client.RawQuery( "SELECT crypto::argon2::compare(password, $password) AS password_ok " + "FROM user WHERE phone = $phone LIMIT 1;", new Dictionary { ["phone"] = phone, ["password"] = password, }, cancellationToken); response.EnsureAllOks(); var rows = response.GetValue>(0) ?? []; return rows.Count > 0 && rows[0].PasswordOk; } private static UserAccount Map(UserRow row) => new( row.Id, row.Phone, string.IsNullOrWhiteSpace(row.Email) ? null : row.Email, row.Role, NormalizeRecordId(row.Organization)); private static string? NormalizeRecordId(string? value) => string.IsNullOrWhiteSpace(value) || value is "NONE" or "NULL" ? null : value; }