using gehGassi.Core.Interfaces; using gehGassi.Domain.Walks; using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using gehGassi.Domain.Common; using gehGassi.Domain.Dogs; using gehGassi.Domain.Ratings; using Microsoft.EntityFrameworkCore.Metadata.Internal; using Microsoft.EntityFrameworkCore; using gehGassi.Common.Extensions; namespace gehGassi.Core.Services { /// /// Service der die Verwaltung von Ratings ermöglicht /// public class RatingService : ServiceBase, IRatingService { private readonly IRepository _namesRepository; private readonly IRepository _namesExRepository; private readonly IRepository _dogsRepository; private readonly IRepository _appUserRepository; private readonly IRepository _walksRepository; /// /// Erstellt eine Instanz /// /// Instanz eines IUnitOfWork public RatingService(IUnitOfWork unitOfWork) : base(unitOfWork) { _namesRepository = unitOfWork.GetRepository(); _namesExRepository = unitOfWork.GetRepository(); _dogsRepository = unitOfWork.GetRepository(); _appUserRepository = unitOfWork.GetRepository(); _walksRepository = unitOfWork.GetRepository(); } /// /// Erstellen eines Ratings /// /// Rating public Rating Create() { var rating = new Rating() { Id = Guid.NewGuid().ToString("N"), Created = DateTimeOffset.UtcNow }; return rating; } /// /// Erstellen eines Ratings /// /// Id der Rating Quelle /// Typ der Rating Quelle /// Id des Ziels des Ratings /// Typ des Rating Ziels /// Vergebene Punkte /// Zusatzinfo zum Rating /// Rating public Rating Create(string fromId, AppUserType fromType, string targetId, RatingTarget targetType, decimal points, string info) { var rating = new Rating() { Id = Guid.NewGuid().ToString("N"), FromId = fromId, FromType = fromType, TargetId = targetId, TargetType = targetType, Points = points, Info = info, Created = DateTimeOffset.UtcNow }; return rating; } /// /// Gibt eine Liste der Ratings eines Benutzers zurück. Kann Owner und Walker sein /// /// Id des AppUsers /// Letztes Update /// Liste der öffentlichen Anfragen public async Task> GetForSyncAppAsync(string appUserId, DateTimeOffset? lastUpdate) { var query = Repository.Query(c => c.FromId == appUserId); if (lastUpdate.HasValue) query = query.Where(c => c.UpdatedAt > lastUpdate); return await query.ToListAsync().ConfigureAwait(false); } /// /// Gibt ein Rating basierend auf Abfragekriterien zurück /// /// Id der Rating Quelle /// Typ der Rating Quelle /// Id des Ziels des Ratings /// Typ des Rating Ziels /// Rating wenn gefunden, null sonst public async Task GetRatingExAsync(string fromId, AppUserType fromType, string targetId, RatingTarget targetType) { var rating = await Repository.FirstOrDefaultAsync(c => c.FromId == fromId && c.FromType == fromType && c.TargetId == targetId && c.TargetType == targetType).ConfigureAwait(false); return rating; } /// /// Neu berechnen der Ratings-Statistics für ein Ziel /// /// Id des Ziels des Ratings /// Typ des Rating Ziels /// true wenn erfolgreich, false sonst public async Task UpdateRatingStatisticsAsync(string targetId, RatingTarget targetType) { if (targetType == RatingTarget.Dog || targetType == RatingTarget.Owner || targetType == RatingTarget.Walker) { var totalRatings = await Repository.CountAsync(c => c.TargetId == targetId && c.Deleted == false).ConfigureAwait(false); var totalPoints = (await Repository.FindAsync(c => c.TargetId == targetId && c.Deleted == false).ConfigureAwait(false)).Sum(c => c.Points); var averageRating = 0m; if (totalRatings > 0) averageRating = totalPoints / totalRatings; if (targetType == RatingTarget.Dog) { var dog = await _dogsRepository.FirstOrDefaultAsync(c => c.Id == targetId); if (dog != null) { dog.RatingStatistics.TotalRatings = totalRatings; dog.RatingStatistics.TotalPoints = totalPoints; dog.RatingStatistics.AverageRating = averageRating; dog.UpdatedAt = DateTimeOffset.UtcNow; return true; } } if (targetType == RatingTarget.Owner) { var appUser = await _appUserRepository.FirstOrDefaultAsync(c => c.Id == targetId); if (appUser != null) { appUser.RatingStatistics.TotalRatings = totalRatings; appUser.RatingStatistics.TotalPoints = totalPoints; appUser.RatingStatistics.AverageRating = averageRating; appUser.UpdatedAt = DateTimeOffset.UtcNow; return true; } } if (targetType == RatingTarget.Walker) { var appUser = await _appUserRepository.FirstOrDefaultAsync(c => c.Id == targetId); if (appUser != null) { appUser.RatingStatisticsWalker.TotalRatings = totalRatings; appUser.RatingStatisticsWalker.TotalPoints = totalPoints; appUser.RatingStatisticsWalker.AverageRating = averageRating; appUser.UpdatedAt = DateTimeOffset.UtcNow; return true; } } } return false; } /// /// Gibt an ob ein Bewerter ein Ziel bereits bewertet hat /// /// Id der Rating Quelle /// Typ der Rating Quelle /// Id des Ziels des Ratings /// Typ des Rating Ziels /// true wenn bereits bewertet, false sonst public async Task HasRatedAsync(string fromId, AppUserType fromType, string targetId, RatingTarget targetType) { var rating = await Repository.FirstOrDefaultAsync(c => c.FromId == fromId && c.FromType == fromType && c.TargetId == targetId && c.TargetType == targetType).ConfigureAwait(false); return rating != null; } /// /// Gibt zurück ob ein Bewerter ein Ziel bewerten darf. /// z.B. ein Walker darf einen hund nur bewerten, wenn mindestens ein Walk mit dem hund stattgefunden hat. /// Ein Owner darf einen Walker nur bewerten, wenn dieser mindestens einen Walk für den Owen gemacht hat usw. /// /// Id der Rating Quelle /// Typ der Rating Quelle /// Id des Ziels des Ratings /// Typ des Rating Ziels /// true wenn eine Bewertung stattfinden darf, false sonst public async Task CanRateAsync(string fromId, AppUserType fromType, string targetId, RatingTarget targetType) { if (targetType == RatingTarget.Dog || targetType == RatingTarget.Owner || targetType == RatingTarget.Walker) { //1. Darf ein Walker einen Hund bewerten? if ((fromType == AppUserType.DogWalker || fromType == AppUserType.Both) && targetType == RatingTarget.Dog) { var existingWalk = await _walksRepository.FirstOrDefaultAsync(c => c.DogsJson.Contains(targetId) && c.DogWalkerId == fromId && c.Completed.HasValue).ConfigureAwait(false); return existingWalk != null; } //2. Darf ein Owner einen Walker bewerten? if ((fromType == AppUserType.DogOwner || fromType == AppUserType.Both) && targetType == RatingTarget.Walker) { var existingWalk = await _walksRepository.FirstOrDefaultAsync(c => c.DogOwnerId == fromId && c.DogWalkerId == targetId && c.Completed.HasValue).ConfigureAwait(false); return existingWalk != null; } //2. Darf ein Walker einen Owner bewerten? if ((fromType == AppUserType.DogWalker || fromType == AppUserType.Both) && targetType == RatingTarget.Owner) { var existingWalk = await _walksRepository.FirstOrDefaultAsync(c => c.DogOwnerId == targetId && c.DogWalkerId == fromId && c.Completed.HasValue).ConfigureAwait(false); return existingWalk != null; } } return false; } /// /// Alle Ratings eines AppUsers und für einen AppUser löschen /// /// Id des AppUsers /// Anzahl gelöscht public async Task>> DeleteAllForAppUserAsync(string appUserId) { var targetsToUpdate = new List>(); //Zuerst werden alle Ratings gelöscht, die an den AppUser gestellt wurden var ratings = await Repository.FindAsync(c => c.TargetId == appUserId).ConfigureAwait(false); foreach (var rating in ratings) { Repository.Remove(rating); } //Nun alle suchen die von dem Benutzer kommen und diese löschen. //Achutung: bei den Zielbenutzern muss dann die Statistik aktualisiert werden! var ratingsFromUser = await Repository.FindAsync(c => c.FromId == appUserId).ConfigureAwait(false); foreach (var rating in ratingsFromUser) { if (rating.TargetType == RatingTarget.Dog || rating.TargetType == RatingTarget.Owner || rating.TargetType == RatingTarget.Walker) { targetsToUpdate.Add(new Tuple(rating.TargetId, rating.TargetType)); } Repository.Remove(rating); } return targetsToUpdate; } /// /// Gibt eine Liste von Ratings mit Namen aufgelöst zurück /// /// Id der Rating Quelle /// Typ der Rating Quelle /// Id des Ziels des Ratings /// Typ des Rating Ziels /// Optional: Liste der sortierung /// Datensätze auslassen /// Datensätze nehmen /// Liste gefudene Walks public async Task<(int total, List list)> GetWithNamesAsync(string fromId, AppUserType? fromType, string targetId, RatingTarget? targetType, List sortList, int skip, int take) { var baseQuery = _namesRepository.QueryStoredProcedure("SELECT * FROM vw_RatingsWithNames", c => c.Deleted == false); if (!string.IsNullOrWhiteSpace(fromId)) baseQuery = baseQuery.Where(c => c.FromId == fromId); if (fromType.HasValue) baseQuery = baseQuery.Where(c => c.FromType == fromType); if (!string.IsNullOrWhiteSpace(targetId)) baseQuery = baseQuery.Where(c => c.TargetId == targetId); if (targetType.HasValue) baseQuery = baseQuery.Where(c => c.TargetType == targetType); var total = await baseQuery.CountAsync().ConfigureAwait(false); if (sortList.Any()) { var isFirst = true; var orderedBaseQuery = baseQuery.SpecialOrderBy(sortList.First().Property, sortList.First().SortDirection); ; foreach (var sortOrder in sortList) { if (isFirst) { isFirst = false; continue; } else { orderedBaseQuery = orderedBaseQuery.SpecialThenBy(sortOrder.Property, sortOrder.SortDirection); } } var list = await orderedBaseQuery.Skip(skip).Take(take).ToListAsync().ConfigureAwait(false); return (total, list); } else { var list = await baseQuery.OrderByDescending(c => c.UpdatedAt).Skip(skip).Take(take).ToListAsync().ConfigureAwait(false); return (total, list); } } /// /// Gibt eine Liste von Ratings mit Namen aufgelöst und mit lock und block Info zurück /// /// Id des Benutzers der abfrägt und für den Blocking geprüft wird /// Id der Rating Quelle /// Typ der Rating Quelle /// Id des Ziels des Ratings /// Typ des Rating Ziels /// Optional: Liste der sortierung /// Datensätze auslassen /// Datensätze nehmen /// Liste gefudene Walks public async Task<(int total, List list)> GetWithNamesExAsync(string blockingAppUserId, string fromId, AppUserType? fromType, string targetId, RatingTarget? targetType, List sortList, int skip, int take) { var baseQuery = _namesExRepository.QueryStoredProcedure("SELECT * FROM tv_RatingsWithNamesEx({0})", c => c.Deleted == false, blockingAppUserId); if (!string.IsNullOrWhiteSpace(fromId)) baseQuery = baseQuery.Where(c => c.FromId == fromId); if (fromType.HasValue) baseQuery = baseQuery.Where(c => c.FromType == fromType); if (!string.IsNullOrWhiteSpace(targetId)) baseQuery = baseQuery.Where(c => c.TargetId == targetId); if (targetType.HasValue) baseQuery = baseQuery.Where(c => c.TargetType == targetType); var total = await baseQuery.CountAsync().ConfigureAwait(false); if (sortList.Any()) { var isFirst = true; var orderedBaseQuery = baseQuery.SpecialOrderBy(sortList.First().Property, sortList.First().SortDirection); ; foreach (var sortOrder in sortList) { if (isFirst) { isFirst = false; continue; } else { orderedBaseQuery = orderedBaseQuery.SpecialThenBy(sortOrder.Property, sortOrder.SortDirection); } } var list = await orderedBaseQuery.Skip(skip).Take(take).ToListAsync().ConfigureAwait(false); return (total, list); } else { var list = await baseQuery.OrderByDescending(c => c.UpdatedAt).Skip(skip).Take(take).ToListAsync().ConfigureAwait(false); return (total, list); } } /// /// Gibt ein Rating mit Namen aufgelöst zurück /// /// Id des Ratings /// Rating oder null, wenn nicht gefunden public async Task GetWithNamesAsync(string ratingId) { return await _namesRepository.FirstOrDefaultAsync(c => c.Id == ratingId).ConfigureAwait(false); } } }