586 lines
25 KiB
C#

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.Json;
using System.Threading.Tasks;
using gehGassi.Dto;
using gehGassi.Dto.Common;
using gehGassi.Dto.Dogs;
using gehGassi.Dto.Ratings;
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;
namespace gehGassiApp.Core.Services
{
public class RatingService : ServiceBase<Rating>, IRatingService
{
private readonly ICommunicationService _communicationService;
private readonly ISyncInfoPullService _pullService;
private readonly ISyncInfoPushService _pushService;
private readonly ILocationService _locationService;
private readonly IRepository<AppUser> _appUserRepository;
/// <summary>
/// Erstellt eine Instanz
/// </summary>
/// <param name="unitOfWork">Instanz eines IUnitOfWork</param>
/// <param name="communicationService">Instanz eines ICommunicationService</param>
/// <param name="syncInfoPullService">Instanz eines ISyncInfoPullService</param>
/// <param name="syncInfoPushService">Instanz eines ISyncInfoPushService</param>
/// <param name="locationService">Instanz eines ILocationService</param>
public RatingService(IUnitOfWork unitOfWork, ICommunicationService communicationService, ISyncInfoPullService syncInfoPullService, ISyncInfoPushService syncInfoPushService, ILocationService locationService) : base(unitOfWork)
{
_communicationService = communicationService;
_pullService = syncInfoPullService;
_pushService = syncInfoPushService;
_locationService = locationService;
_appUserRepository = unitOfWork.GetRepository<AppUser>();
}
public override Rating Get(object id, bool noTracking = true)
{
throw new NotImplementedException();
}
public override async Task<Rating> GetAsync(object id, bool noTracking = true)
{
await Task.Delay(1);
throw new NotImplementedException();
}
/// <summary>
/// Erstellen eines Ratings
/// </summary>
/// <returns></returns>
public Rating Create()
{
var rating = new Rating()
{
Id = Guid.NewGuid().ToString("N"),
Created = DateTimeOffset.UtcNow
};
return rating;
}
/// <summary>
/// Gibt ein Rating basierend auf Abfragekriterien zurück
/// </summary>
/// <param name="fromId">Id des Bewerters</param>
/// <param name="fromType">Typ des Bewertes</param>
/// <param name="targetId">Id des Bewertungsziels</param>
/// <param name="targetType">Typ des Bewertungsziels</param>
/// <param name="accessToken">Aktuelles Accesstoken</param>
/// <param name="token">CancellationToken</param>
/// <returns>true wenn bereits bewertet, false sonst</returns>
public async Task<Rating> GetRatingEx(string fromId, AppUserType fromType, string targetId, RatingTarget targetType, string accessToken, CancellationToken token)
{
//zuerst lokal prüfen, sonst auch online nachsehen
var rating = await Repository.FirstOrDefaultAsync(c => c.FromId == fromId && c.FromType == fromType && c.TargetId == targetId && c.TargetType == targetType).ConfigureAwait(false);
if (rating != null)
return rating;
//Nun online prüfen
var isConnected = await _communicationService.IsConnected();
if (isConnected)
{
var query = new RatingCheckQueryDto()
{
FromId = fromId,
FromType = (AppUserTypeDto)fromType,
TargetId = targetId,
TargetType = (RatingTargetDto)targetType
};
var queryResult = await _communicationService.GetRatingExAsync(query, accessToken, token);
if (queryResult.Success)
return queryResult.Value;
}
return null;
}
/// <summary>
/// Gibt ein Rating basierend auf der ID zurück
/// </summary>
/// <param name="ratingId">Id des Ratings</param>
/// <param name="accessToken">Aktuelles Accesstoken</param>
/// <param name="token">CancellationToken</param>
/// <returns>CommunicationResult</returns>
public async Task<RatingWithNames> GetRatingAsync(string ratingId, string accessToken, CancellationToken token)
{
//Nun online prüfen
var isConnected = await _communicationService.IsConnected();
if (isConnected)
{
var queryResult = await _communicationService.GetRatingAsync(ratingId, accessToken, token);
if (queryResult.Success)
return queryResult.Value;
}
return null;
}
/// <summary>
/// Hinzufügen eines Ratings
/// </summary>
/// <param name="rating">Rating</param>
/// <param name="accessToken">Aktuelles Accesstoken</param>
/// <param name="token">CancellationToken</param>
/// <returns>Angelegtes Rating</returns>
public async Task<Rating> AddAsync(Rating rating, string accessToken, CancellationToken token)
{
var existingRating = await Repository.GetAsync(rating.Id);
if (existingRating == null)
{
Repository.Add(rating);
await CommitAsync();
//Jetzt das Rating an der Server übertragen oder ein Delta erstellen
var ratingDto = rating.ToDto();
var isConnected = await _communicationService.IsConnected();
var createDelta = true;
if (isConnected)
{
var createResult = await _communicationService.CreateRatingAsync(ratingDto, accessToken, token);
if (createResult.Success && createResult.Value.Status != CreateStatus.Error)
{
createDelta = false;
}
}
if (createDelta)
{
await _pushService.AddAsync(nameof(Rating), rating.Id, SyncOperation.Create, rating).ConfigureAwait(false);
}
return rating;
}
return null;
}
/// <summary>
/// Aktualisieren eines Ratings
/// </summary>
/// <param name="rating">Rating</param>
/// <param name="accessToken">Aktuelles Accesstoken</param>
/// <param name="token">CancellationToken</param>
/// <returns>true wenn erfolgreich, false sonst</returns>
public async Task<bool> UpdateAsync(Rating rating, string accessToken, CancellationToken token)
{
var localRating = await Repository.FirstOrDefaultAsync(c => c.Id == rating.Id, false).ConfigureAwait(false);
if (localRating != null && !localRating.Deleted)
{
localRating.FromId = rating.FromId;
localRating.FromType = rating.FromType;
localRating.TargetId = rating.TargetId;
localRating.TargetType = rating.TargetType;
localRating.Points = rating.Points;
localRating.Info = rating.Info;
localRating.UpdatedAt = DateTimeOffset.UtcNow;
Repository.Update(localRating);
await CommitAsync().ConfigureAwait(false);
var ratingDto = localRating.ToDto();
var isConnected = await _communicationService.IsConnected();
var createDelta = true;
if (isConnected)
{
var updateResult = await _communicationService.UpdateRatingAsync(ratingDto, accessToken, token);
if (updateResult.Success && updateResult.Value)
createDelta = false;
}
if (createDelta)
{
await _pushService.AddAsync(nameof(Rating), localRating.Id, SyncOperation.Edit, localRating).ConfigureAwait(false);
}
return true;
}
return false;
}
/// <summary>
/// Echtes Löschen eines Ratings - wird derzeit nicht verwendet!
/// Bitte Rating auf deleted setzen und UPDATE
/// </summary>
/// <param name="ratingId">Id des Ratings</param>
/// <param name="appUserId">Id des AppBenutzers</param>
/// <param name="accessToken">Aktuelles Accesstoken</param>
/// <param name="token">CancellationToken</param>
/// <returns>true wenn erfolgreich, false sonst</returns>
public async Task<bool> DeleteAsync(string ratingId, string appUserId, string accessToken, CancellationToken token)
{
var localRating = await Repository.FirstOrDefaultAsync(c => c.Id == ratingId, false).ConfigureAwait(false);
if (localRating != null && localRating.FromId == appUserId)
{
Repository.Remove(localRating);
await CommitAsync().ConfigureAwait(false);
var ratingDto = localRating.ToDto();
var isConnected = await _communicationService.IsConnected();
var createDelta = true;
if (isConnected)
{
var createResult = await _communicationService.DeleteRatingAsync(ratingDto, appUserId, accessToken, token);
if (createResult.Success && createResult.Value)
createDelta = false;
}
if (createDelta)
{
await _pushService.AddAsync(nameof(Rating), localRating.Id, SyncOperation.Delete, localRating).ConfigureAwait(false);
}
return true;
}
return false;
}
/// <summary>
/// Wurde bereits eine Bewertung vorgenommen?
/// </summary>
/// <param name="fromId">Id des Bewerters</param>
/// <param name="fromType">Typ des Bewertes</param>
/// <param name="targetId">Id des Bewertungsziels</param>
/// <param name="targetType">Typ des Bewertungsziels</param>
/// <param name="accessToken">Aktuelles Accesstoken</param>
/// <param name="token">CancellationToken</param>
/// <returns>true wenn bereits bewertet, false sonst</returns>
public async Task<bool> HasRatedAsync(string fromId, AppUserType fromType, string targetId, RatingTarget targetType, string accessToken, CancellationToken token)
{
//zuerst lokal prüfen, sonst auch online nachsehen
var rating = await Repository.FirstOrDefaultAsync(c => c.FromId == fromId && c.FromType == fromType && c.TargetId == targetId && c.TargetType == targetType).ConfigureAwait(false);
if (rating != null)
return true;
//Nun online prüfen
var isConnected = await _communicationService.IsConnected();
if (isConnected)
{
var query = new RatingCheckQueryDto()
{
FromId = fromId,
FromType = (AppUserTypeDto)fromType,
TargetId = targetId,
TargetType = (RatingTargetDto)targetType
};
var queryResult = await _communicationService.HasRatedAsync(query, accessToken, token);
if (queryResult.Success)
return queryResult.Value;
}
return false;
}
/// <summary>
/// Kann eine Bewertung vorgenommen werden?
/// </summary>
/// <param name="fromId">Id des Bewerters</param>
/// <param name="fromType">Typ des Bewertes</param>
/// <param name="targetId">Id des Bewertungsziels</param>
/// <param name="targetType">Typ des Bewertungsziels</param>
/// <param name="accessToken">Aktuelles Accesstoken</param>
/// <param name="token">CancellationToken</param>
/// <returns>true wennmöglich, false sonst</returns>
public async Task<bool> CanRateAsync(string fromId, AppUserType fromType, string targetId, RatingTarget targetType, string accessToken, CancellationToken token)
{
//Nur online prüfen
var isConnected = await _communicationService.IsConnected();
if (isConnected)
{
var query = new RatingCheckQueryDto()
{
FromId = fromId,
FromType = (AppUserTypeDto)fromType,
TargetId = targetId,
TargetType = (RatingTargetDto)targetType
};
var queryResult = await _communicationService.CanRateAsync(query, accessToken, token);
if (queryResult.Success)
return queryResult.Value;
}
return false;
}
/// <summary>
/// Abrufen einer Liste von Ratings
/// </summary>
/// <param name="fromId">Quelle Id</param>
/// <param name="fromType">Quelle Typ</param>
/// <param name="targetId">Ziel Id</param>
/// <param name="targetType">Ziel Typ</param>
/// <param name="sortOrders">Liste von Sortierangaben</param>
/// <param name="appMode">Aktueller appMode</param>
/// <param name="language">Gewünschte Sprache</param>
/// <param name="take">Wie viele Walks sollen abgerufen werden? -1 Wenn nicht anwenden.</param>
/// <param name="skip">Wie viele Walks sollen ausgelassen werden? -1 Wenn nicht anwenden.</param>
/// <param name="accessToken">Aktuelles Accesstoken</param>
/// <param name="token">CancellationToken</param>
/// <returns>ListCommunicationResult</returns>
public async Task<ListCommunicationResult<List<RatingWithNames>>> GetRatingsAsync(string fromId, AppUserType? fromType, string targetId, RatingTarget? targetType, List<DynamicSortOrder> sortOrders, string language, AppMode appMode, LocationDto locaton, int take, int skip, string accessToken, CancellationToken token)
{
var result = new ListCommunicationResult<List<RatingWithNames>>() { Value = new List<RatingWithNames>() };
var isConnected = await _communicationService.IsConnected();
if (isConnected)
{
var query = new RatingsQueryDto()
{
Location = locaton,
Language = language,
AppMode = (AppModeDto)appMode,
Take = take,
Skip = skip,
LastUpdate = null,
FromId = fromId,
FromType = (AppUserTypeDto?)fromType,
TargetId = targetId,
TargetType = (RatingTargetDto?)targetType
};
query.DynamicSortOrder = sortOrders.ToDto();
var queryResult = await _communicationService.GetRatingsWithNamesAsync(query, accessToken, token);
if (queryResult.Success && queryResult.Value.Count >= 1)
{
return queryResult;
}
}
else
{
result.Success = false;
result.ErrorCode = CommunicationErrors.ServerNoConnection;
result.ErrorMessage = Errors.Server_NoConnection;
}
return result;
}
#region Pull-Push Implementation
/// <summary>
/// Holen der letzten Daten vom Server und Synchronisieren mit den lokalen Daten
/// </summary>
/// <param name="language">Sprache</param>
/// <param name="accessToken">Aktuelles Accesstoken</param>
/// <param name="token">CancellationToken</param>
/// <returns>Task</returns>
public async Task<SyncResult<Rating>> PullAsync(string language, string accessToken, CancellationToken token)
{
var result = new SyncResult<Rating>();
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(Rating));
if (lastSyncInfo != null)
lastUpdate = lastSyncInfo.LastUpdate;
var requestResult = await _communicationService.GetRatingsForSyncAsync(user.Id, lastUpdate, accessToken, token);
if (requestResult.Success)
{
result = await HandleChangesAsync(requestResult.Value);
if (result.LastUpdate != null)
await _pullService.AddOrUpddateAsync(nameof(Rating), result.LastUpdate.Value).ConfigureAwait(false);
}
result.LastUpdate ??= lastUpdate;
}
return result;
}
/// <summary>
/// Holen der Daten vom lokalen Speicher die noch nicht synchronisiert wurden und senden an den Server
/// </summary>
/// <param name="accessToken">Aktuelles Accesstoken</param>
/// <param name="token">CancellationToken</param>
/// <returns>Task</returns>
public async Task<SyncResult<Rating>> PushAsync(string accessToken, CancellationToken token)
{
var result = new SyncResult<Rating>();
if (await _pushService.HasOpenAsync(nameof(Rating)) > 0)
{
var deltas = await _pushService.GetAllAsync(nameof(Rating));
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<Rating>(syncInfoPush.Value, new JsonSerializerOptions(JsonSerializerDefaults.Web));
if (syncInfoPush.Operation == SyncOperation.Create)
{
var createDto = request.ToDto();
var createResult = await _communicationService.CreateRatingAsync(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.UpdateRatingAsync(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.DeleteRatingAsync(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;
}
#endregion
#region Private
/// <summary>
/// Behandeln der Liste von Ratings wenn welche vom Online-Store geholt werden.
/// </summary>
/// <param name="ratings">Liste der Ratings</param>
/// <returns>Task</returns>
private async Task<SyncResult<Rating>> HandleChangesAsync(List<Rating> ratings)
{
var result = new SyncResult<Rating>();
//Je Rasse durchgehen ob was gemacht werden soll
foreach (var rating in ratings)
{
var localRating = await Repository.FirstOrDefaultAsync(c => c.Id == rating.Id, false);
if (localRating != null && localRating.UpdatedAt < rating.UpdatedAt)
{
if (localRating.UpdatedAt >= rating.UpdatedAt)
{
if (result.LastUpdate == null || result.LastUpdate < localRating.UpdatedAt)
result.LastUpdate = localRating.UpdatedAt;
continue;
}
var deleted = localRating.Deleted == false && rating.Deleted;
localRating.Version = rating.Version;
localRating.UpdatedAt = rating.UpdatedAt;
localRating.Deleted = rating.Deleted;
localRating.FromId = rating.FromId;
localRating.FromType = rating.FromType;
localRating.TargetId = rating.TargetId;
localRating.TargetType = rating.TargetType;
localRating.Points = rating.Points;
localRating.Info = rating.Info;
localRating.Created = rating.Created;
if (result.LastUpdate == null || result.LastUpdate < localRating.UpdatedAt)
result.LastUpdate = localRating.UpdatedAt;
if (!deleted)
result.Updated.Add(localRating);
else
result.Deleted.Add(localRating);
Repository.Update(localRating);
}
if (localRating == null)
{
localRating = new Rating()
{
Id = rating.Id,
Version = rating.Version,
UpdatedAt = rating.UpdatedAt,
Deleted = rating.Deleted,
FromId = rating.FromId,
FromType = rating.FromType,
TargetId = rating.TargetId,
TargetType = rating.TargetType,
Points = rating.Points,
Info = rating.Info,
Created = rating.Created
};
Repository.Add(localRating);
if (result.LastUpdate == null || result.LastUpdate < localRating.UpdatedAt)
result.LastUpdate = localRating.UpdatedAt;
result.Added.Add(localRating);
}
}
if (result.HasChanges)
{
try
{
await CommitAsync().ConfigureAwait(false);
}
catch (Exception ex)
{
var err = ex.Message;
}
}
return result;
}
#endregion
}
}