373 lines
17 KiB
C#

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
{
/// <summary>
/// Service der die Verwaltung von Ratings ermöglicht
/// </summary>
public class RatingService : ServiceBase<Rating>, IRatingService
{
private readonly IRepository<RatingWithNames> _namesRepository;
private readonly IRepository<RatingWithNamesEx> _namesExRepository;
private readonly IRepository<Dog> _dogsRepository;
private readonly IRepository<AppUser> _appUserRepository;
private readonly IRepository<Walk> _walksRepository;
/// <summary>
/// Erstellt eine Instanz
/// </summary>
/// <param name="unitOfWork">Instanz eines IUnitOfWork</param>
public RatingService(IUnitOfWork unitOfWork) : base(unitOfWork)
{
_namesRepository = unitOfWork.GetRepository<RatingWithNames>();
_namesExRepository = unitOfWork.GetRepository<RatingWithNamesEx>();
_dogsRepository = unitOfWork.GetRepository<Dog>();
_appUserRepository = unitOfWork.GetRepository<AppUser>();
_walksRepository = unitOfWork.GetRepository<Walk>();
}
/// <summary>
/// Erstellen eines Ratings
/// </summary>
/// <returns>Rating</returns>
public Rating Create()
{
var rating = new Rating()
{
Id = Guid.NewGuid().ToString("N"),
Created = DateTimeOffset.UtcNow
};
return rating;
}
/// <summary>
/// Erstellen eines Ratings
/// </summary>
/// <param name="fromId">Id der Rating Quelle</param>
/// <param name="fromType">Typ der Rating Quelle</param>
/// <param name="targetId">Id des Ziels des Ratings</param>
/// <param name="targetType">Typ des Rating Ziels</param>
/// <param name="points">Vergebene Punkte</param>
/// <param name="info">Zusatzinfo zum Rating</param>
/// <returns>Rating</returns>
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;
}
/// <summary>
/// Gibt eine Liste der Ratings eines Benutzers zurück. Kann Owner und Walker sein
/// </summary>
/// <param name="appUserId">Id des AppUsers</param>
/// <param name="lastUpdate">Letztes Update</param>
/// <returns>Liste der öffentlichen Anfragen</returns>
public async Task<List<Rating>> 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);
}
/// <summary>
/// Gibt ein Rating basierend auf Abfragekriterien zurück
/// </summary>
/// <param name="fromId">Id der Rating Quelle</param>
/// <param name="fromType">Typ der Rating Quelle</param>
/// <param name="targetId">Id des Ziels des Ratings</param>
/// <param name="targetType">Typ des Rating Ziels</param>
/// <returns>Rating wenn gefunden, null sonst</returns>
public async Task<Rating> 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;
}
/// <summary>
/// Neu berechnen der Ratings-Statistics für ein Ziel
/// </summary>
/// <param name="targetId">Id des Ziels des Ratings</param>
/// <param name="targetType">Typ des Rating Ziels</param>
/// <returns>true wenn erfolgreich, false sonst</returns>
public async Task<bool> 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;
}
/// <summary>
/// Gibt an ob ein Bewerter ein Ziel bereits bewertet hat
/// </summary>
/// <param name="fromId">Id der Rating Quelle</param>
/// <param name="fromType">Typ der Rating Quelle</param>
/// <param name="targetId">Id des Ziels des Ratings</param>
/// <param name="targetType">Typ des Rating Ziels</param>
/// <returns>true wenn bereits bewertet, false sonst</returns>
public async Task<bool> 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;
}
/// <summary>
/// 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.
/// </summary>
/// <param name="fromId">Id der Rating Quelle</param>
/// <param name="fromType">Typ der Rating Quelle</param>
/// <param name="targetId">Id des Ziels des Ratings</param>
/// <param name="targetType">Typ des Rating Ziels</param>
/// <returns>true wenn eine Bewertung stattfinden darf, false sonst</returns>
public async Task<bool> 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;
}
/// <summary>
/// Alle Ratings eines AppUsers und für einen AppUser löschen
/// </summary>
/// <param name="appUserId">Id des AppUsers</param>
/// <returns>Anzahl gelöscht</returns>
public async Task<List<Tuple<string, RatingTarget>>> DeleteAllForAppUserAsync(string appUserId)
{
var targetsToUpdate = new List<Tuple<string, RatingTarget>>();
//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<string, RatingTarget>(rating.TargetId, rating.TargetType));
}
Repository.Remove(rating);
}
return targetsToUpdate;
}
/// <summary>
/// Gibt eine Liste von Ratings mit Namen aufgelöst zurück
/// </summary>
/// <param name="fromId">Id der Rating Quelle</param>
/// <param name="fromType">Typ der Rating Quelle</param>
/// <param name="targetId">Id des Ziels des Ratings</param>
/// <param name="targetType">Typ des Rating Ziels</param>
/// <param name="sortList">Optional: Liste der sortierung</param>
/// <param name="skip">Datensätze auslassen</param>
/// <param name="take">Datensätze nehmen</param>
/// <returns>Liste gefudene Walks</returns>
public async Task<(int total, List<RatingWithNames> list)> GetWithNamesAsync(string fromId, AppUserType? fromType, string targetId, RatingTarget? targetType, List<DynamicSortOrder> 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);
}
}
/// <summary>
/// Gibt eine Liste von Ratings mit Namen aufgelöst und mit lock und block Info zurück
/// </summary>
/// <param name="blockingAppUserId">Id des Benutzers der abfrägt und für den Blocking geprüft wird</param>
/// <param name="fromId">Id der Rating Quelle</param>
/// <param name="fromType">Typ der Rating Quelle</param>
/// <param name="targetId">Id des Ziels des Ratings</param>
/// <param name="targetType">Typ des Rating Ziels</param>
/// <param name="sortList">Optional: Liste der sortierung</param>
/// <param name="skip">Datensätze auslassen</param>
/// <param name="take">Datensätze nehmen</param>
/// <returns>Liste gefudene Walks</returns>
public async Task<(int total, List<RatingWithNamesEx> list)> GetWithNamesExAsync(string blockingAppUserId, string fromId, AppUserType? fromType, string targetId, RatingTarget? targetType, List<DynamicSortOrder> 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);
}
}
/// <summary>
/// Gibt ein Rating mit Namen aufgelöst zurück
/// </summary>
/// <param name="ratingId">Id des Ratings</param>
/// <returns>Rating oder null, wenn nicht gefunden</returns>
public async Task<RatingWithNames> GetWithNamesAsync(string ratingId)
{
return await _namesRepository.FirstOrDefaultAsync(c => c.Id == ratingId).ConfigureAwait(false);
}
}
}