using CommunityToolkit.Mvvm.Messaging; using gehGassi.Dto; using gehGassiApp.Core.Data; using gehGassiApp.Core.Interfaces; using gehGassiApp.Core.Interfaces.Synchronization; using gehGassiApp.Domain.Common; using gehGassiApp.Domain.Messages; using gehGassiApp.Domain.Users; using gehGassiApp.Domain.Walks; using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using gehGassi.Dto.Walks; using System.Text.Json; using gehGassi.Dto.Common; using gehGassiApp.Core.Mapper; using gehGassiApp.Core.Resources; namespace gehGassiApp.Core.Services { /// /// Service der die Verwaltung von öffentlichen Anfragen ermöglicht /// public class PublicWalkRequestService : ServiceBase, IPublicWalkRequestService { private readonly ICommunicationService _communicationService; private readonly ISyncInfoPullService _pullService; private readonly ISyncInfoPushService _pushService; private readonly ILocationService _locationService; private readonly IRepository _appUserRepository; private readonly IRepository _namesRepository; private readonly IRepository _walksRepository; /// /// Erstellt eine Instanz /// /// Instanz eines IUnitOfWork /// Instanz eines ICommunicationService /// Instanz eines ISyncInfoPullService /// Instanz eines ISyncInfoPushService /// Instanz eines ILocationService public PublicWalkRequestService(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(); _walksRepository = 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 PublicWalkRequest 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(); } /// /// Zählt die Anzahl der öffentlichen Anfragen für den aktuellen Benutzer /// /// Aktuelles Accesstoken /// CancellationToken /// Anzahl öffentliche Anfragen public async Task> CountPublicWalkRequestsAsync(string accessToken, CancellationToken token) { var result = new CommunicationResult() { Value = 0 }; var isConnected = await _communicationService.IsConnected(); if (isConnected) { var queryResult = await _communicationService.CountPublicWalkRequestsAsync(accessToken, token); if (queryResult.Success) return queryResult; } else { result.Success = false; result.ErrorCode = CommunicationErrors.ServerNoConnection; result.ErrorMessage = Errors.Server_NoConnection; } return result; } /// /// Gibt eine Liste von öffentlichen Anfragen zurück /// /// Id die ignoriert werden soll (DogOwnerId) /// Optional: Id des Hundebesitzers /// Optional: Id des Hundeausführers /// Status der Ausschreibung oder NULL für alle /// Liste von Sortierangaben /// 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>> GetAsync(string ignoreId, string dogOwnerId, string dogWalkerId, PublicWalkRequestStatus? status, List sortOrders, 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 PublicWalkRequestQueryDto { Location = location, Language = language, AppMode = (AppModeDto)appMode, Take = take, Skip = skip, IgnoreId = ignoreId, DogOwnerId = dogOwnerId, DogWalkerId = dogWalkerId, Status = (PublicWalkRequestStatusDto?)status, }; query.DynamicSortOrder = sortOrders.ToDto(); var queryResult = await _communicationService.GetPublicWalkRequestsAsync(query, accessToken, token); if (queryResult.Success) return queryResult; } else { result.Success = false; result.ErrorCode = CommunicationErrors.ServerNoConnection; result.ErrorMessage = Errors.Server_NoConnection; } return result; } /// /// Gibt eine Liste von öffentlichen Anfragen zurück erweitert /// /// Id die ignoriert werden soll (DogOwnerId) /// Optional: Id des Hundebesitzers /// Optional: Id des Hundeausführers /// Status der Ausschreibung oder NULL für alle /// Maximaler Radius /// Gesuchter Typ oder NULL für alle /// Mindestpreis /// Liste von Sortierangaben /// 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>> GetExAsync(string ignoreId, string dogOwnerId, string dogWalkerId, PublicWalkRequestStatus? status, double maxRadius, PublicWalkRequestType? type, decimal minPrice, List sortOrders, 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 PublicWalkRequestQueryExDto() { Location = location, Language = language, AppMode = (AppModeDto)appMode, Take = take, Skip = skip, IgnoreId = ignoreId, DogOwnerId = dogOwnerId, DogWalkerId = dogWalkerId, Status = (PublicWalkRequestStatusDto?)status, MaxRadius = maxRadius, Type = (PublicWalkRequestTypeDto?)type, MinPrice = minPrice }; query.DynamicSortOrder = sortOrders.ToDto(); var queryResult = await _communicationService.GetPublicWalkRequestsExAsync(query, accessToken, token); if (queryResult.Success) return queryResult; } else { result.Success = false; result.ErrorCode = CommunicationErrors.ServerNoConnection; result.ErrorMessage = Errors.Server_NoConnection; } return result; } /// /// Gibt eine Liste von öffentlichen Anfragen zurück mit einem Antwort-Status für einen DogWalker /// /// Id die ignoriert werden soll (DogOwnerId) /// Optional: Id des Hundebesitzers /// Optional: Id des Hundeausführers /// Status der Ausschreibung oder null für alle /// es muss ein Angebot vom Dogwalker vorhanden sein /// Liste von Sortierangaben /// Aktuelle Position /// Sollten blockierte unnd/oder gesperrte Benutzer in die Liste eingefügt werden? /// 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 /// Id des Dogwalkers der die Anfrage absetzt /// Ziel Response-Status oder Null für alle /// ListCommunicationResult public async Task>> GetWithResponseStatusAsync(string ignoreId, string dogOwnerId, string dogWalkerId, PublicWalkRequestStatus? status, string targetDogWalkerId, PublicWalkResponseStatus? responseStatus, bool mustHaveResponse, List sortOrders, LocationDto location, bool includeBlockedAndLocked, 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 PublicWalkRequestAndResponseStatusQueryDto { Location = location, Language = language, AppMode = (AppModeDto)appMode, IncludeBlockedAndLocked = includeBlockedAndLocked, Take = take, Skip = skip, IgnoreId = ignoreId, DogOwnerId = dogOwnerId, DogWalkerId = dogWalkerId, Status = (PublicWalkRequestStatusDto?)status, TargetDogWalkerId = targetDogWalkerId, ResponseStatus = (PublicWalkResponseStatusDto?)responseStatus, MustHaveResponse = mustHaveResponse }; query.DynamicSortOrder = sortOrders.ToDto(); var queryResult = await _communicationService.GetPublicWalkRequestsWithResponseStatusAsync(query, accessToken, token); if (queryResult.Success) return queryResult; } else { result.Success = false; result.ErrorCode = CommunicationErrors.ServerNoConnection; result.ErrorMessage = Errors.Server_NoConnection; } return result; } /// /// Gibt eine Liste von öffentlichen Anfragen zurück mit einem Antwort-Status für einen DogWalker mit erweiterten Suchparametern /// /// Id die ignoriert werden soll (DogOwnerId) /// Optional: Id des Hundebesitzers /// Optional: Id des Hundeausführers /// Status der Ausschreibung oder null für alle /// Es muss ein Angebot vom DogWalker vorhanden sein /// Suchtext in Hundename, Besitzername oder Stadt /// Liste von Sortierangaben /// Aktuelle Position /// Sollten blockierte unnd/oder gesperrte Benutzer in die Liste eingefügt werden? /// 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 /// Id des Dogwalkers der die Anfrage absetzt /// Ziel Response-Status oder Null für alle /// Maximaler Radius /// Gesuchter Typ oder null für alle /// Mindestpreis /// ListCommunicationResult public async Task>> GetWithResponseStatusExAsync(string ignoreId, string dogOwnerId, string dogWalkerId, PublicWalkRequestStatus? status, string targetDogWalkerId, PublicWalkResponseStatus? responseStatus, double maxRadius, PublicWalkRequestType? type, decimal minPrice, bool mustHaveResponse, string searchText, List sortOrders, LocationDto location, bool includeBlockedAndLocked, 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 PublicWalkRequestAndResponseStatusQueryExDto() { Location = location, Language = language, AppMode = (AppModeDto)appMode, IncludeBlockedAndLocked = includeBlockedAndLocked, Take = take, Skip = skip, IgnoreId = ignoreId, DogOwnerId = dogOwnerId, DogWalkerId = dogWalkerId, Status = (PublicWalkRequestStatusDto?)status, TargetDogWalkerId = targetDogWalkerId, ResponseStatus = (PublicWalkResponseStatusDto?)responseStatus, MaxRadius = maxRadius, Type = (PublicWalkRequestTypeDto?)type, MinPrice = minPrice, MustHaveResponse = mustHaveResponse, SearchText = searchText }; query.DynamicSortOrder = sortOrders.ToDto(); var queryResult = await _communicationService.GetPublicWalkRequestsWithResponseStatusExAsync(query, accessToken, token); if (queryResult.Success) return queryResult; } else { result.Success = false; result.ErrorCode = CommunicationErrors.ServerNoConnection; result.ErrorMessage = Errors.Server_NoConnection; } return result; } /// /// Gibt eine Anzahl von öffentlichen Anfragen zurück, absteigend nach Datum sortiert /// /// Wie viele sollen genommen werden? /// Liste von öffntlichen Anfragen public async Task> GetLatestAsync(int take) { //var requests = await Repository.Query(c => c.Deleted == false).OrderByDescending(c => c.UpdatedAt).Take(take).ToListAsync(); var requests = (await Repository.FindAsync(c => c.Deleted == false, c => c.StartDate, 0, take, false)).ToList(); return requests; } /// /// Gibt eine Anzahl von öffentlichen Anfragen zurück, absteigend nach Datum sortiert /// /// Id des App-Users /// Gewünschte Sprache /// Wie viele sollen genommen werden? /// Aktuelles Accesstoken /// CancellationToken /// Liste von öffntlichen Anfragen public async Task> GetLatestWithNamesAsync(string appUserId, string language, int take, string accessToken, CancellationToken token) { var isConnected = await _communicationService.IsConnected(); if (isConnected) { var requestResult = await _communicationService.GetPublicWalkRequestsLatestAsync(appUserId, language, take, accessToken, token); if (requestResult.Success) return requestResult.Value; } //var requests = await _namesRepository.Query(c => c.Deleted == false).OrderBy(c => c.StartDate).Take(take).ToListAsync(); var requests = (await _namesRepository.FindAsync(c => c.Deleted == false, c => c.StartDate, 0, take, true)).ToList(); return requests; } /// /// Gibt eine öffentliche Anfrage zurück /// /// Id der Anfrage /// Gewünschte Sprache /// Aktuelles Accesstoken /// CancellationToken /// Öffentliche Anfrage oder null, wenn nicht gefunden public async Task GetWithNamesAsync(string id, string language, string accessToken, CancellationToken token) { var isConnected = await _communicationService.IsConnected(); if (isConnected) { var requestResult = await _communicationService.GetPublicWalkRequestWithNamesAsync(id, language, accessToken, token); if (requestResult.Success) return requestResult.Value; } var request = await _namesRepository.FirstOrDefaultAsync(c => c.Id == id).ConfigureAwait(false); return request; } /// /// Hinzufügen einer öffentlichen Anfrage /// /// Öffentliche Anfrage /// Aktuelles Accesstoken /// CancellationToken /// PublicWalkRequest public async Task AddAsync(PublicWalkRequest request, string accessToken, CancellationToken token) { //Zuerst in der Datenbank Repository.Add(request); await CommitAsync(); //Jetzt am Server oder Delta erstellen var createDelta = true; var isConnected = await _communicationService.IsConnected(); if (isConnected) { var dto = request.ToCreateDto(); var createResult = await _communicationService.CreatePublicWalkRequestAsync(dto, accessToken, token); if (createResult.Success) { if (createResult.Value.Status == CreateStatus.Success) { createDelta = false; } } } if (createDelta) { await _pushService.AddAsync(nameof(PublicWalkRequest), request.Id, SyncOperation.Create, request).ConfigureAwait(false); } return request; } /// /// Stornieren einer öffentlichen Anfrage /// /// Id der Öffentliche Anfrage /// Aktuelles Accesstoken /// CancellationToken /// PublicWalkRequest public async Task CancelAsync(string publicWalkRequestId, string accessToken, CancellationToken token) { var localRequest = await Repository.FirstOrDefaultAsync(c => c.Id == publicWalkRequestId, false); if (localRequest != null) { localRequest.Status = PublicWalkRequestStatus.Cancelled; localRequest.UpdatedAt = DateTimeOffset.UtcNow; Repository.Update(localRequest); await CommitAsync(); //Jetzt am Server oder Delta erstellen var createDelta = true; var isConnected = await _communicationService.IsConnected(); if (isConnected) { var dto = new PublicWalkRequestCancelDto() { PublicWalkRequestId = publicWalkRequestId, UpdatedAt = localRequest.UpdatedAt }; var cancelResult = await _communicationService.CancelPublicWalkRequestAsync(dto, accessToken, token); if (cancelResult.Success) { if (cancelResult.Value) { createDelta = false; } } } if (createDelta) { await _pushService.AddAsync(nameof(PublicWalkRequest), localRequest.Id, SyncOperation.Edit, localRequest).ConfigureAwait(false); } } return false; } /// /// Akzeptieren eines Angebotes zu einer öffnentlichen Anfrage /// /// Id der öffentlichen Anfrage /// Id des akzeptierten Angebotes /// Id des Hundebesitzers /// Aktuelles Accesstoken /// CancellationToken /// Erstellter Walk wenn erfolgreich, false sonst public async Task AcceptResponseAsync(string publicWalkRequestId, string publicWalkResponseId, string appUserId, string accessToken, CancellationToken token) { var dto = new PublicWalkRequestAcceptDto() { PublicWalkRequestId = publicWalkRequestId, PublicWalkResponseId = publicWalkResponseId, DogOwnerId = appUserId, UpdatedAt = DateTimeOffset.UtcNow }; //Das hier muss online funktionieren, damit es lokal gespeichert wird var isConnected = await _communicationService.IsConnected(); if (isConnected) { var acceptResult = await _communicationService.AcceptPublicWalkRequestAsync(dto, accessToken, token); if (acceptResult.Success) { //Nun lokal aktualisieren? var localRequest = await Repository.FirstOrDefaultAsync(c => c.Id == publicWalkRequestId, false); if (localRequest != null) { localRequest.PublicWalkResponseId = publicWalkResponseId; localRequest.Status = PublicWalkRequestStatus.Placed; localRequest.UpdatedAt = dto.UpdatedAt; localRequest.PlacedDate = dto.UpdatedAt; Repository.Update(localRequest); await CommitAsync(); //Walk lokal anlegen wenn nicht vorhanden var existingWalk = _walksRepository.FirstOrDefaultAsync(c => c.Id == acceptResult.Value.Id); if (existingWalk == null) { _walksRepository.Add(acceptResult.Value); await CommitAsync(); } return acceptResult.Value; } } } return null; } #region Pull-Push Implementation /// /// 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(PublicWalkRequest)) > 0) { var deltas = await _pushService.GetAllAsync(nameof(PublicWalkRequest)); 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.ToCreateDto(); var createResult = await _communicationService.CreatePublicWalkRequestAsync(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.UpdatePublicWalkRequestAsync(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.DeletePublicWalkRequestAsync(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(PublicWalkRequest)); if (lastSyncInfo != null) lastUpdate = lastSyncInfo.LastUpdate; var requestResult = await _communicationService.GetPublicWalkRequestsForSyncAsync(user.Id, lastUpdate, accessToken, token); if (requestResult.Success) { result = await HandleChangesAsync(requestResult.Value); if (result.LastUpdate != null) await _pullService.AddOrUpddateAsync(nameof(PublicWalkRequest), result.LastUpdate.Value).ConfigureAwait(false); } result.LastUpdate ??= lastUpdate; } return result; } #endregion #region Private /// /// Behandeln der Liste von öffentlichern Anfragen wenn welche vom Online-Store geholt werden. /// /// Liste der anfragen /// Task private async Task> HandleChangesAsync(List requests) { var result = new SyncResult(); //Je Rasse durchgehen ob was gemacht werden soll foreach (var request in requests) { var localRequest = await Repository.FirstOrDefaultAsync(c => c.Id == request.Id, false); if (localRequest != null && localRequest.UpdatedAt < request.UpdatedAt) { if (localRequest.UpdatedAt >= request.UpdatedAt) { if (result.LastUpdate == null || result.LastUpdate < localRequest.UpdatedAt) result.LastUpdate = localRequest.UpdatedAt; continue; } var deleted = localRequest.Deleted == false && request.Deleted; localRequest.Version = request.Version; localRequest.UpdatedAt = request.UpdatedAt; localRequest.Deleted = request.Deleted; localRequest.PickupAddress = request.PickupAddress; localRequest.Lat = request.Lat; localRequest.Lng = request.Lng; localRequest.RequestType = request.RequestType; localRequest.StartDate = request.StartDate; localRequest.EndDate = request.EndDate; localRequest.DogId = request.DogId; localRequest.Exclusive = request.Exclusive; localRequest.Price = request.Price; localRequest.Info = request.Info; localRequest.ExpirationDate = request.ExpirationDate; localRequest.DogWalkerId = request.DogWalkerId; localRequest.PublicWalkResponseId = request.PublicWalkResponseId; localRequest.ResponseCount = request.ResponseCount; localRequest.Status = request.Status; localRequest.PlacedDate = request.PlacedDate; localRequest.Created = request.Created; if (result.LastUpdate == null || result.LastUpdate < localRequest.UpdatedAt) result.LastUpdate = localRequest.UpdatedAt; if (!deleted) result.Updated.Add(localRequest); else result.Deleted.Add(localRequest); Repository.Update(localRequest); } if (localRequest == null) { localRequest = new PublicWalkRequest() { Id = request.Id, Version = request.Version, UpdatedAt = request.UpdatedAt, Deleted = request.Deleted, PickupAddress = request.PickupAddress, Lat = request.Lat, Lng = request.Lng, RequestType = request.RequestType, StartDate = request.StartDate, EndDate = request.EndDate, DogId = request.DogId, Exclusive = request.Exclusive, Price = request.Price, Info = request.Info, ExpirationDate = request.ExpirationDate, DogWalkerId = request.DogWalkerId, PublicWalkResponseId = request.PublicWalkResponseId, ResponseCount = request.ResponseCount, Status = request.Status, PlacedDate = request.PlacedDate, Created = request.Created, }; Repository.Add(localRequest); if (result.LastUpdate == null || result.LastUpdate < localRequest.UpdatedAt) result.LastUpdate = localRequest.UpdatedAt; result.Added.Add(localRequest); } } if (result.HasChanges) { try { await CommitAsync().ConfigureAwait(false); } catch (Exception ex) { var err = ex.Message; } } return result; } #endregion } }