From 1912feb9cfe51deaedbe3abe9239fd1bcf2b37f8 Mon Sep 17 00:00:00 2001 From: Paweł Bernaciak Date: Wed, 25 Oct 2023 20:28:48 +0200 Subject: Make voting work --- .../Controllers/SuggestionController.cs | 2 +- backend/Elements.Backend/Program.cs | 6 ++ .../Elements.Backend/Services/FinishVoteService.cs | 95 ++++++++++++++++++++++ .../Elements.Backend/Services/PeriodicService.cs | 38 +++++++++ 4 files changed, 140 insertions(+), 1 deletion(-) create mode 100644 backend/Elements.Backend/Services/FinishVoteService.cs create mode 100644 backend/Elements.Backend/Services/PeriodicService.cs (limited to 'backend') diff --git a/backend/Elements.Backend/Controllers/SuggestionController.cs b/backend/Elements.Backend/Controllers/SuggestionController.cs index 040b19b..932e774 100644 --- a/backend/Elements.Backend/Controllers/SuggestionController.cs +++ b/backend/Elements.Backend/Controllers/SuggestionController.cs @@ -123,7 +123,7 @@ public class SuggestionController : ControllerBase Icon = ConvertBitmapToPng(Convert.FromBase64String(suggestion.IconBitmap)), FirstElementId = suggestion.FirstElementId, SecondElementId = suggestion.SecondElementId, - VotingEnd = DateTime.UtcNow + TimeSpan.FromSeconds(10), + VotingEnd = DateTime.UtcNow + TimeSpan.FromMinutes(5), UserId = int.Parse(currentUserId) }; await _dbContext.Suggestions.AddAsync(newSuggestion); diff --git a/backend/Elements.Backend/Program.cs b/backend/Elements.Backend/Program.cs index 50e5255..f7b9b4b 100644 --- a/backend/Elements.Backend/Program.cs +++ b/backend/Elements.Backend/Program.cs @@ -1,4 +1,5 @@ using System.Text.Json; +using Elements.Backend.Services; using Elements.Data; using Microsoft.AspNetCore.Authentication.Cookies; using Microsoft.AspNetCore.Authentication.Google; @@ -44,6 +45,11 @@ builder.Services }; }); +builder.Services.AddScoped(); +builder.Services.AddSingleton(); +builder.Services.AddHostedService( + provider => provider.GetRequiredService()); + var app = builder.Build(); app.UseHttpsRedirection(); diff --git a/backend/Elements.Backend/Services/FinishVoteService.cs b/backend/Elements.Backend/Services/FinishVoteService.cs new file mode 100644 index 0000000..8486036 --- /dev/null +++ b/backend/Elements.Backend/Services/FinishVoteService.cs @@ -0,0 +1,95 @@ +using Elements.Data; +using Elements.Data.Models; +using Microsoft.EntityFrameworkCore; + +namespace Elements.Backend.Services; + +public class FinishVoteService +{ + private readonly ApplicationDbContext _dbContext; + private readonly ILogger _logger; + + public FinishVoteService(ApplicationDbContext dbContext, ILogger logger) + { + _dbContext = dbContext; + _logger = logger; + } + + public async Task DoSomethingAsync() + { + _logger.LogDebug("Starting finalizing votes"); + List finishedSuggestions = _dbContext.Suggestions + .Include(s => s.Votes) + .Where(s => DateTime.UtcNow > s.VotingEnd) + .ToList(); + + if (finishedSuggestions.Count == 0) + return; + + bool finished = false; + while (!finished) + { + Suggestion currentSuggestion = finishedSuggestions.First(); + int firstElementId = currentSuggestion.FirstElementId; + int secondElementId = currentSuggestion.SecondElementId; + //Get all suggestions with same recipe as current suggestion + List sameRecipeSuggestions = finishedSuggestions.Where(s => + (s.FirstElementId == firstElementId && s.SecondElementId == secondElementId) || + (s.FirstElementId == secondElementId && s.SecondElementId == firstElementId)) + .ToList(); + sameRecipeSuggestions.Sort((s1, s2) => s2.Votes.Count.CompareTo(s1.Votes.Count)); + + Suggestion winner; + if (sameRecipeSuggestions.Count > 1 && + sameRecipeSuggestions[0].Votes.Count == sameRecipeSuggestions[1].Votes.Count) + { + List sameAmountOfVotes = sameRecipeSuggestions + .Where(s => s.Votes.Count == sameRecipeSuggestions[0].Votes.Count) + .OrderBy(s => s.CreationDate) + .ToList(); + + winner = sameAmountOfVotes[0]; + } + else + { + winner = sameRecipeSuggestions[0]; + } + + Element newElement = new() + { + CreationDate = winner.CreationDate, + UserId = winner.UserId, + Name = winner.Name, + State = ElementState.HasColor, + IconPng = winner.Icon + }; + await _dbContext.Elements.AddAsync(newElement); + await _dbContext.SaveChangesAsync(); + + await _dbContext.Recipes.AddAsync(new Recipe + { + FirstElementId = winner.FirstElementId, + SecondElementId = winner.SecondElementId, + ResultElementId = newElement.Id, + }); + + //Delete votes + foreach (Vote vote in sameRecipeSuggestions.SelectMany(suggestion => suggestion.Votes)) + _dbContext.Entry(vote).State = EntityState.Deleted; + + //Delete suggestions + foreach (Suggestion suggestion in sameRecipeSuggestions) + _dbContext.Entry(suggestion).State = EntityState.Deleted; + + await _dbContext.SaveChangesAsync(); + + finishedSuggestions = finishedSuggestions + .Where(s => !(s.FirstElementId == firstElementId && s.SecondElementId == secondElementId) && + !(s.FirstElementId == secondElementId && s.SecondElementId == firstElementId)) + .ToList(); + + if (finishedSuggestions.Count == 0) + finished = true; + } + } +} \ No newline at end of file diff --git a/backend/Elements.Backend/Services/PeriodicService.cs b/backend/Elements.Backend/Services/PeriodicService.cs new file mode 100644 index 0000000..48cdf2f --- /dev/null +++ b/backend/Elements.Backend/Services/PeriodicService.cs @@ -0,0 +1,38 @@ +namespace Elements.Backend.Services; + +class PeriodicService : BackgroundService +{ + private readonly TimeSpan _period = TimeSpan.FromMinutes(1); + private readonly ILogger _logger; + private readonly IServiceScopeFactory _factory; + public bool IsEnabled { get; set; } = true; + + public PeriodicService( + ILogger logger, + IServiceScopeFactory factory) + { + _logger = logger; + _factory = factory; + } + + protected override async Task ExecuteAsync(CancellationToken stoppingToken) + { + using PeriodicTimer timer = new PeriodicTimer(_period); + while ( + !stoppingToken.IsCancellationRequested && + await timer.WaitForNextTickAsync(stoppingToken)) + { + try + { + if (!IsEnabled) continue; + await using AsyncServiceScope asyncScope = _factory.CreateAsyncScope(); + FinishVoteService finishVoteService = asyncScope.ServiceProvider.GetRequiredService(); + await finishVoteService.DoSomethingAsync(); + } + catch (Exception ex) + { + _logger.LogInformation($"Failed to execute periodic service, Error: {ex.Message}."); + } + } + } +} \ No newline at end of file -- cgit v1.2.3