using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Text.Json; using System.Threading.Tasks; using gehGassi.Dto.Common; using gehGassi.Dto; using gehGassi.Dto.Walks; using gehGassiApp.Core.Data; using gehGassiApp.Core.Interfaces; using gehGassiApp.Core.Interfaces.Synchronization; using gehGassiApp.Core.Mapper; using gehGassiApp.Core.Resources; using gehGassiApp.Domain.Common; using gehGassiApp.Domain.Users; using gehGassiApp.Domain.Walks; using Microsoft.EntityFrameworkCore; namespace gehGassiApp.Core.Services { /// /// Service der die Verwaltung von Antworten auf öffentlichen Anfragen ermöglicht /// public class PublicWalkResponseService : ServiceBase, IPublicWalkResponseService { private readonly ICommunicationService _communicationService; private readonly ISyncInfoPullService _pullService; private readonly ISyncInfoPushService _pushService; private readonly ILocationService _locationService; private readonly IRepository _appUserRepository; private readonly IRepository _namesRepository; /// /// Erstellt eine Instanz /// /// Instanz eines IUnitOfWork /// Instanz eines ICommunicationService /// Instanz eines ISyncInfoPullService /// Instanz eines ISyncInfoPushService /// Instanz eines ILocationService public PublicWalkResponseService(IUnitOfWork unitOfWork, ICommunicationService communicationService, ISyncInfoPullService syncInfoPullService, ISyncInfoPushService syncInfoPushService, ILocationService locationService) : base(unitOfWork) { _communicationService = communicationService; _pullService = syncInfoPullService; _pushService = syncInfoPushService; _locationService = locationService; _appUserRepository = unitOfWork.GetRepository(); _namesRepository = unitOfWork.GetRepository(); } /// /// Gibt eine Entität anhand der eindeutigen Id zurück /// /// Id der Entität /// Gibt an ob NoTRacking verwendet werden soll. Es werden keine Entitäten im EF-Speicher gehalten /// Entität oder null, wenn nicht gefunden public override PublicWalkResponse Get(object id, bool noTracking = true) { throw new NotImplementedException(); } /// /// Gibt eine Entität anhand der eindeutigen Id zurück /// /// Id der Entität /// Gibt an ob NoTRacking verwendet werden soll. Es werden keine Entitäten im EF-Speicher gehalten /// Entität oder null, wenn nicht gefunden public override async Task GetAsync(object id, bool noTracking = true) { await Task.Delay(1); throw new NotImplementedException(); } /// /// Gibt eine Liste von Antworten zu einer öffentlichen Anfragen zurück /// /// Id der öffentlichen Anfrage /// Aktuelle Position /// Wie viele Listungen sollen abgerufen werden? -1 Wenn nicht anwenden. /// Wie viele Listungen sollen ausgelassen werden? -1 Wenn nicht anwenden. /// Akteuller Modus der App /// Gewählte Sprache /// Aktuelles Accesstoken /// CancellationToken /// ListCommunicationResult public async Task>> GetListAsync(string publicWalkRequestId, LocationDto location, int take, int skip, AppMode appMode, string language, string accessToken, CancellationToken token) { var result = new ListCommunicationResult>() { Value = new List() }; var isConnected = await _communicationService.IsConnected(); if (isConnected) { var query = new PublicWalkResponseQueryDto() { Location = location, Language = language, AppMode = (AppModeDto)appMode, Take = take, Skip = skip, PublicWalkRequestId = publicWalkRequestId, }; var queryResult = await _communicationService.GetPublicWalkresponsesAsync(query, accessToken, token); if (queryResult.Success) return queryResult; } else { result.Success = false; result.ErrorCode = CommunicationErrors.ServerNoConnection; result.ErrorMessage = Errors.Server_NoConnection; } return result; } /// /// Holen einer Antworten zu öffentlichen Anfragen /// /// Id der öffentlichen Anfrage /// Aktuelles Accesstoken /// CancellationToken /// CommunicationResult public async Task GetAsync(string publicWalkResponseId, string accessToken, CancellationToken token) { var isConnected = await _communicationService.IsConnected(); if (isConnected) { var requestResult = await _communicationService.GetPublicWalkResponseAsync(publicWalkResponseId, accessToken, token); if (requestResult.Success) return requestResult.Value; } return null; } /// /// Holen einer Antworten zu öffentlichen Anfragen /// /// Id der öffentlichen Anfrage /// Aktuelles Accesstoken /// CancellationToken /// CommunicationResult public async Task GetWithNamesAsync(string publicWalkResponseId, string accessToken, CancellationToken token) { var isConnected = await _communicationService.IsConnected(); if (isConnected) { var requestResult = await _communicationService.GetPublicWalkResponseWithNamesAsync(publicWalkResponseId, accessToken, token); if (requestResult.Success) return requestResult.Value; } return null; } /// /// Holen einer Antworten zu öffentlichen Anfragen /// /// Id des AppUsers /// Id der öffentlichen Anfrage /// Aktuelles Accesstoken /// CancellationToken /// CommunicationResult public async Task GetForWalkerAsync(string appUserId, string publicWalkRequestId, string accessToken, CancellationToken token) { var isConnected = await _communicationService.IsConnected(); if (isConnected) { var requestResult = await _communicationService.GetPublicWalkResponseForWalkerAsync(appUserId, publicWalkRequestId, accessToken, token); if (requestResult.Success) return requestResult.Value; } var response = await Repository.FirstOrDefaultAsync(c => c.DogWalkerId == appUserId && c.PublicWalkRequestId == publicWalkRequestId && c.Deleted).ConfigureAwait(false); return response; } /// /// Hinzufügen einer Antwort zu einer öffentlichen Anfrage /// /// Antwort zur Öffentliche Anfrage /// Aktuelles Accesstoken /// CancellationToken /// PublicWalkRequest public async Task> AddAsync(PublicWalkResponse response, string accessToken, CancellationToken token) { //Kann nur online erfolgen var result = new CommunicationResult() { Success = false, Value = null, ErrorCode = CommunicationErrors.Undefined }; var isConnected = await _communicationService.IsConnected(); if (isConnected) { var dto = response.ToDto(); var createResult = await _communicationService.CreatePublicWalkResponseAsync(dto, accessToken, token); if (createResult.Success) { if (createResult.Value.Status == CreateStatus.Success && createResult.Value.Value != null) { Repository.Add(response); await CommitAsync(); result.Success = true; result.ErrorCode = CommunicationErrors.None; result.Value = createResult.Value.Value; } } else { if (createResult.Value.Status == CreateStatus.Error) result.ErrorCode = CommunicationErrors.Common_Relation_Changed; else result.ErrorCode = CommunicationErrors.Common_Exists; } } else { result.ErrorCode = CommunicationErrors.ServerNoConnection; } return result; } /// /// Prüft in der lokalen DB ob ein Walker für eine öffentliche Anfrage ein Angebot abgegeben hat /// /// Id des Dogwalkers /// Id der öffentlichen Anfrage /// true wenn es ein Angebot gibt, false sonst public async Task HasResponseAsync(string appUserId, string publicWalkRequestId) { var response = await Repository.FirstOrDefaultAsync(c => c.DogWalkerId == appUserId && c.PublicWalkRequestId == publicWalkRequestId).ConfigureAwait(false); return response != null; } /// /// Aktualisieren einer Antwort zu einer öffentlichen Anfrage /// /// Antwort zur Öffentliche Anfrage /// Soll der Status aktualisiert werden? /// Aktuelles Accesstoken /// CancellationToken /// PublicWalkRequest public async Task UpdateAsync(PublicWalkResponse response, bool updateStatus, string accessToken, CancellationToken token) { var localResponse = await Repository.FirstOrDefaultAsync(c => c.Id == response.Id, false).ConfigureAwait(false); //Es kann nach neuinstallation vorkommen, dass die lokale Version nicht mehr da ist... if (localResponse == null) { Repository.Add(response); await CommitAsync(); } localResponse = await Repository.FirstOrDefaultAsync(c => c.Id == response.Id, false).ConfigureAwait(false); if (localResponse != null && !localResponse.Deleted) { localResponse.Price = response.Price; localResponse.Info = response.Info; if(updateStatus) localResponse.Status = response.Status; localResponse.UpdatedAt = DateTimeOffset.UtcNow; Repository.Update(localResponse); await CommitAsync().ConfigureAwait(false); var responseDto = localResponse.ToDto(); var isConnected = await _communicationService.IsConnected(); var createDelta = true; if (isConnected) { var updateResult = await _communicationService.UpdatePublicWalkResponseAsync(responseDto, accessToken, token); if (updateResult.Success && updateResult.Value) createDelta = false; } if (createDelta) { await _pushService.AddAsync(nameof(PublicWalkResponse), localResponse.Id, SyncOperation.Edit, localResponse).ConfigureAwait(false); } return true; } return false; } /// /// Ablehnen eines Angebotes zu einer öffentlichen Anfrage /// /// Id der Antwort /// Id der öffentlichen Anfrage /// Aktuelles Accesstoken /// CancellationToken /// true wenn erfolgreich, false sonst public async Task DeclineAsync(string responseId, string publicWalkRequestId, string accessToken, CancellationToken token) { var dto = new PublicWalkResponseDeclineDto() { PublicWalkRequestId = publicWalkRequestId, PublicWalkResponseId = responseId, UpdatedAt = DateTimeOffset.UtcNow }; //Das hier muss online funktionieren, damit es lokal gespeichert wird var isConnected = await _communicationService.IsConnected(); if (isConnected) { var declinedResult = await _communicationService.DeclinePublicWalkResponseAsync(dto, accessToken, token); if (declinedResult.Success) { return true; } } return false; } /// /// Bearbeiten eines bereits gestellten Angebotes zu einer öffentlichen Anfrage /// /// Id der Antwort /// Id der öffentlichen Anfrage /// Neuer Preis /// Info zum Angebot /// Aktuelles Accesstoken /// CancellationToken /// true wenn erfolgreich, false sonst public async Task> EditAsync(string responseId, string publicWalkRequestId, decimal price, string info, string accessToken, CancellationToken token) { //Kann nur online erfolgen var result = new CommunicationResult() { Success = false, Value = false, ErrorCode = CommunicationErrors.Undefined }; var isConnected = await _communicationService.IsConnected(); if (isConnected) { var dto = new PublicWalkResponseEditDto() { PublicWalkResponseId = responseId, PublicWalkRequestId = publicWalkRequestId, Price = price, Info = info, UpdatedAt = DateTimeOffset.UtcNow }; var updateResult = await _communicationService.EditPublicWalkResponseAsync(dto, accessToken, token); if (updateResult.Success) { if (updateResult.Value) { var response = await Repository.GetAsync(responseId); if (response != null) { response.Price = price; response.Info = info; response.UpdatedAt = dto.UpdatedAt; Repository.Update(response); await CommitAsync(); } result.Success = true; result.ErrorCode = CommunicationErrors.None; result.Value = true; } } else { result.Success = false; result.ErrorCode = updateResult.ErrorCode; result.ErrorMessage = updateResult.ErrorMessage; } } else { result.ErrorCode = CommunicationErrors.ServerNoConnection; } return result; } /// /// Bearbeiten eines bereits gestellten Angebotes zu einer öffentlichen Anfrage /// /// Id der Antwort /// Id der öffentlichen Anfrage /// Grund für die Stornierung /// Aktuelles Accesstoken /// CancellationToken /// true wenn erfolgreich, false sonst public async Task> CancelAsync(string responseId, string publicWalkRequestId, string cancellationReason, string accessToken, CancellationToken token) { //Kann nur online erfolgen var result = new CommunicationResult() { Success = false, Value = false, ErrorCode = CommunicationErrors.Undefined }; var isConnected = await _communicationService.IsConnected(); if (isConnected) { var dto = new PublicWalkResponseCancelDto() { PublicWalkResponseId = responseId, PublicWalkRequestId = publicWalkRequestId, CancellationReason = cancellationReason, UpdatedAt = DateTimeOffset.UtcNow }; var updateResult = await _communicationService.CancelPublicWalkResponseAsync(dto, accessToken, token); if (updateResult.Success) { if (updateResult.Value) { var response = await Repository.GetAsync(responseId); if (response != null) { response.Status = PublicWalkResponseStatus.Cancelled; response.CancellationReason = cancellationReason; response.UpdatedAt = dto.UpdatedAt; Repository.Update(response); await CommitAsync(); } result.Success = true; result.ErrorCode = CommunicationErrors.None; result.Value = true; } } else { result.Success = false; result.ErrorCode = updateResult.ErrorCode; result.ErrorMessage = updateResult.ErrorMessage; } } else { result.ErrorCode = CommunicationErrors.ServerNoConnection; } return result; } /// /// Holen der Daten vom lokalen Speicher die noch nicht synchronisiert wurden und senden an den Server /// /// Aktuelles Accesstoken /// CancellationToken /// Task public async Task> PushAsync(string accessToken, CancellationToken token) { var result = new SyncResult(); if (await _pushService.HasOpenAsync(nameof(PublicWalkResponse)) > 0) { var deltas = await _pushService.GetAllAsync(nameof(PublicWalkResponse)); if (deltas.Any()) { var user = await _appUserRepository.FirstOrDefaultAsync(c => c.Id != "", false).ConfigureAwait(false); deltas = deltas.OrderBy(c => c.DateTime).ToList(); var isConnected = await _communicationService.IsConnected(); if (!isConnected) return result; foreach (var syncInfoPush in deltas) { try { if (!string.IsNullOrWhiteSpace(syncInfoPush.Value)) { var request = JsonSerializer.Deserialize(syncInfoPush.Value, new JsonSerializerOptions(JsonSerializerDefaults.Web)); if (syncInfoPush.Operation == SyncOperation.Create) { var createDto = request.ToDto(); var createResult = await _communicationService.CreatePublicWalkResponseAsync(createDto, accessToken, token); if (createResult.Success && createResult.Value.Status != CreateStatus.Error) { await _pushService.RemoveAsync(syncInfoPush.Id).ConfigureAwait(false); } //TODO: Was tun wenn Fehler? } else if (syncInfoPush.Operation == SyncOperation.Edit) { var updateDto = request.ToDto(); var updateResult = await _communicationService.UpdatePublicWalkResponseAsync(updateDto, accessToken, token); if (updateResult.Success && updateResult.Value) await _pushService.RemoveAsync(syncInfoPush.Id).ConfigureAwait(false); //TODO: Was tun wenn Fehler? } else { var deleteDto = request.ToDto(); var deleteResult = await _communicationService.DeletePublicWalkResponseAsync(deleteDto, user.Id, accessToken, token); if (deleteResult.Success && deleteResult.Value) await _pushService.RemoveAsync(syncInfoPush.Id).ConfigureAwait(false); //TODO: Was tun wenn Fehler? } } else { await _pushService.RemoveAsync(syncInfoPush.Id).ConfigureAwait(false); } } catch (Exception ex) { System.Diagnostics.Debug.WriteLine(ex.Message); await _pushService.RemoveAsync(syncInfoPush.Id).ConfigureAwait(false); } } //Else ist nichts tun, konnte nicht übertragen werden. } } return result; } /// /// Holen der letzten Daten vom Server und Synchronisieren mit den lokalen Daten /// /// Sprache /// Aktuelles Accesstoken /// CancellationToken /// Task public async Task> PullAsync(string language, string accessToken, CancellationToken token) { var result = new SyncResult(); var isConnected = await _communicationService.IsConnected(); if (isConnected) { var user = await _appUserRepository.FirstOrDefaultAsync(c => c.Id != "", false).ConfigureAwait(false); //Zuerst lezte Aktivität holen... DateTimeOffset? lastUpdate = null; var lastSyncInfo = await _pullService.GetAsync(nameof(PublicWalkResponse)); if (lastSyncInfo != null) lastUpdate = lastSyncInfo.LastUpdate; var requestResult = await _communicationService.GetPublicWalkResponsesForSyncAsync(user.Id, lastUpdate, accessToken, token); if (requestResult.Success) { result = await HandleChangesAsync(requestResult.Value); if (result.LastUpdate != null) await _pullService.AddOrUpddateAsync(nameof(PublicWalkResponse), result.LastUpdate.Value).ConfigureAwait(false); } result.LastUpdate ??= lastUpdate; } return result; } #region Private /// /// Behandeln der Liste von öffentlichern Anfragen wenn welche vom Online-Store geholt werden. /// /// Liste der anfragen /// Task private async Task> HandleChangesAsync(List responses) { var result = new SyncResult(); //Je Rasse durchgehen ob was gemacht werden soll foreach (var response in responses) { var localResponse = await Repository.FirstOrDefaultAsync(c => c.Id == response.Id, false); if (localResponse != null && localResponse.UpdatedAt < response.UpdatedAt) { if (localResponse.UpdatedAt >= response.UpdatedAt) { if (result.LastUpdate == null || result.LastUpdate < localResponse.UpdatedAt) result.LastUpdate = localResponse.UpdatedAt; continue; } var deleted = localResponse.Deleted == false && response.Deleted; localResponse.Version = response.Version; localResponse.UpdatedAt = response.UpdatedAt; localResponse.Deleted = response.Deleted; localResponse.PublicWalkRequestId = response.PublicWalkRequestId; localResponse.DogWalkerId = response.DogWalkerId; localResponse.Price = response.Price; localResponse.Info = response.Info; localResponse.Answer = response.Answer; localResponse.Status = response.Status; localResponse.CancellationReason = response.CancellationReason; localResponse.Created = response.Created; if (result.LastUpdate == null || result.LastUpdate < localResponse.UpdatedAt) result.LastUpdate = localResponse.UpdatedAt; if (!deleted) result.Updated.Add(localResponse); else result.Deleted.Add(localResponse); Repository.Update(localResponse); } if (localResponse == null && !response.Deleted) { localResponse = new PublicWalkResponse() { Id = response.Id, Version = response.Version, UpdatedAt = response.UpdatedAt, Deleted = response.Deleted, PublicWalkRequestId = response.PublicWalkRequestId, DogWalkerId = response.DogWalkerId, Price = response.Price, Info = response.Info, Answer = response.Answer, Status = response.Status, CancellationReason = response.CancellationReason, Created = response.Created, }; Repository.Add(localResponse); if (result.LastUpdate == null || result.LastUpdate < localResponse.UpdatedAt) result.LastUpdate = localResponse.UpdatedAt; result.Added.Add(localResponse); } } if (result.HasChanges) { try { await CommitAsync().ConfigureAwait(false); } catch (Exception ex) { var err = ex.Message; } } return result; } #endregion } }