#nullable enable using System; using System.Threading; using System.Threading.Tasks; using Serilog; using Wino.Core.Domain.Entities.Shared; using Wino.Core.Domain.Interfaces; using Wino.Core.Domain.Models.Accounts; using Wino.Mail.Api.Contracts.Auth; using Wino.Mail.Api.Contracts.Common; using Wino.Messaging.UI; namespace Wino.Services; public sealed class WinoAccountProfileService : BaseDatabaseService, IWinoAccountProfileService { private readonly IWinoAccountApiClient _apiClient; private readonly ILogger _logger = Log.ForContext(); public WinoAccountProfileService(IDatabaseService databaseService, IWinoAccountApiClient apiClient) : base(databaseService) { _apiClient = apiClient; } public async Task RegisterAsync(string email, string password, CancellationToken cancellationToken = default) { var response = await _apiClient.RegisterAsync(email, password, cancellationToken).ConfigureAwait(false); var result = await PersistResponseAsync(response).ConfigureAwait(false); if (result.IsSuccess && result.Account != null) { ReportUIChange(new WinoAccountSignedInMessage(result.Account)); } return result; } public async Task LoginAsync(string email, string password, CancellationToken cancellationToken = default) { var response = await _apiClient.LoginAsync(email, password, cancellationToken).ConfigureAwait(false); var result = await PersistResponseAsync(response).ConfigureAwait(false); if (result.IsSuccess && result.Account != null) { ReportUIChange(new WinoAccountSignedInMessage(result.Account)); } return result; } public async Task RefreshAsync(CancellationToken cancellationToken = default) { var account = await GetActiveAccountAsync().ConfigureAwait(false); if (account == null || string.IsNullOrWhiteSpace(account.RefreshToken)) { return WinoAccountOperationResult.Failure(ApiErrorCodes.RefreshTokenInvalid); } var response = await _apiClient.RefreshAsync(account.RefreshToken, cancellationToken).ConfigureAwait(false); return await PersistResponseAsync(response).ConfigureAwait(false); } public async Task GetActiveAccountAsync() { var account = await Connection.Table().FirstOrDefaultAsync().ConfigureAwait(false); return account; } public async Task HasActiveAccountAsync() => await Connection.Table().CountAsync().ConfigureAwait(false) > 0; public async Task SignOutAsync(CancellationToken cancellationToken = default) { var account = await GetActiveAccountAsync().ConfigureAwait(false); if (account != null && !string.IsNullOrWhiteSpace(account.RefreshToken)) { try { var result = await _apiClient.LogoutAsync(account.RefreshToken, cancellationToken).ConfigureAwait(false); if (!result.IsSuccess && !string.IsNullOrWhiteSpace(result.ErrorCode)) { _logger.Warning("Wino account remote sign-out failed with error code {ErrorCode}", result.ErrorCode); } } catch (Exception ex) { _logger.Warning(ex, "Wino account remote sign-out failed."); } } await Connection.DeleteAllAsync().ConfigureAwait(false); if (account != null) { ReportUIChange(new WinoAccountSignedOutMessage(account)); } } private async Task PersistResponseAsync(ApiEnvelope response) { if (!response.IsSuccess || response.Result == null) { return WinoAccountOperationResult.Failure(response.ErrorCode); } var account = Map(response.Result); await Connection.DeleteAllAsync().ConfigureAwait(false); await Connection.InsertOrReplaceAsync(account, typeof(WinoAccount)).ConfigureAwait(false); return WinoAccountOperationResult.Success(account); } private static WinoAccount Map(AuthResultDto result) => new() { Id = result.User.UserId, Email = result.User.Email, AccountStatus = result.User.AccountStatus, HasPassword = result.User.HasPassword, HasGoogleLogin = result.User.HasGoogleLogin, HasFacebookLogin = result.User.HasFacebookLogin, AccessToken = result.AccessToken, AccessTokenExpiresAtUtc = result.AccessTokenExpiresAtUtc.UtcDateTime, RefreshToken = result.RefreshToken, RefreshTokenExpiresAtUtc = result.RefreshTokenExpiresAtUtc.UtcDateTime, LastAuthenticatedUtc = DateTime.UtcNow }; }