Skip to content

Commit

Permalink
Merge pull request #422 from code4romania/hotfix/fix-answers-value-an…
Browse files Browse the repository at this point in the history
…d-add-corrupted-answers

Hotfixes
  • Loading branch information
idormenco authored Oct 15, 2023
2 parents 2b6a72a + beb0d8c commit 1974aff
Show file tree
Hide file tree
Showing 16 changed files with 1,726 additions and 53 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,14 @@ namespace VoteMonitor.Api.Answer.Commands;

public record FillInAnswerCommand : IRequest<int>
{
public FillInAnswerCommand(int observerId, IEnumerable<AnswerDto> answers)
public FillInAnswerCommand(int observerId, IEnumerable<AnswerDto> answers, IEnumerable<CorruptedAnswerDto> corruptedAnswers)
{
ObserverId = observerId;
Answers = answers.ToList().AsReadOnly();
CorruptedAnswers = corruptedAnswers.ToList().AsReadOnly();
}

public int ObserverId { get; }
public IReadOnlyCollection<AnswerDto> Answers { get; }
public IReadOnlyCollection<CorruptedAnswerDto> CorruptedAnswers { get; }
}
Original file line number Diff line number Diff line change
Expand Up @@ -86,10 +86,6 @@ public async Task<IActionResult> PostAnswer([FromBody] BulkAnswersRequest answer
{
// TODO[DH] use a pipeline instead of separate Send commands
var command = await _mediator.Send(new Commands.BulkAnswers(this.GetIdObserver(), answerModel.Answers));
if (!command.Answers.Any())
{
return NotFound();
}

var result = await _mediator.Send(command);

Expand Down
14 changes: 13 additions & 1 deletion src/api/VoteMonitor.Api.Answer/Handlers/AnswerQueryHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ public AnswerQueryHandler(IPollingStationService pollingPollingStationService, V
public async Task<FillInAnswerCommand> Handle(BulkAnswers message, CancellationToken cancellationToken)
{
var answersBuilder = new List<AnswerDto>();
var corruptedAnswersBuilder = new List<CorruptedAnswerDto>();

foreach (var answer in message.Answers)
{
Expand All @@ -37,8 +38,19 @@ public async Task<FillInAnswerCommand> Handle(BulkAnswers message, CancellationT
CountyCode = answer.CountyCode,
});
}
else
{
corruptedAnswersBuilder.Add(new CorruptedAnswerDto()
{
QuestionId = answer.QuestionId,
Options = answer.Options,
PollingStationNumber = answer.PollingStationNumber,
CountyCode = answer.CountyCode,
MunicipalityCode = answer.MunicipalityCode,
});
}
}
var command = new FillInAnswerCommand(message.ObserverId, answersBuilder);
var command = new FillInAnswerCommand(message.ObserverId, answersBuilder, corruptedAnswersBuilder);
return command;
}
}
141 changes: 107 additions & 34 deletions src/api/VoteMonitor.Api.Answer/Handlers/FillInAnswerQueryHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
using VoteMonitor.Api.Answer.Commands;
using VoteMonitor.Api.Answer.Models;
using VoteMonitor.Entities;

namespace VoteMonitor.Api.Answer.Handlers;
Expand All @@ -22,30 +23,12 @@ public async Task<int> Handle(FillInAnswerCommand message, CancellationToken can
try
{
var lastModified = DateTime.UtcNow;
var newAnswers = GetFlatListOfAnswers(message, lastModified);

var pollingStationIds = message.Answers.Select(a => a.PollingStationId).Distinct().ToList();

using (var tran = await _context.Database.BeginTransactionAsync(cancellationToken))
{
foreach (var pollingStationId in pollingStationIds)
{
var questionIds = message.Answers.Select(a => a.QuestionId).Distinct().ToList();

var oldAnswersToBeDeleted = _context.Answers
.Include(a => a.OptionAnswered)
.Where(
a =>
a.IdObserver == message.ObserverId &&
a.IdPollingStation == pollingStationId)
.WhereRaspunsContains(questionIds)
;
_context.Answers.RemoveRange(oldAnswersToBeDeleted);

await _context.SaveChangesAsync(cancellationToken);
}

await _context.Answers.AddRangeAsync(newAnswers, cancellationToken);
await WriteAnswers(message.ObserverId, message.Answers, lastModified, cancellationToken);
await WriteCorruptedAnswers(message.ObserverId, message.CorruptedAnswers, lastModified, cancellationToken);

var result = await _context.SaveChangesAsync(cancellationToken);

Expand All @@ -56,27 +39,79 @@ public async Task<int> Handle(FillInAnswerCommand message, CancellationToken can
}
catch (Exception ex)
{
_logger.LogError(typeof(FillInAnswerCommand).GetHashCode(), ex, ex.Message);
_logger.LogError(ex, ex.Message);
}

return await Task.FromResult(-1);
}

public static List<Entities.Answer> GetFlatListOfAnswers(FillInAnswerCommand command, DateTime lastModified)
private async Task WriteAnswers(int observerId, IReadOnlyCollection<AnswerDto> answers, DateTime lastModified, CancellationToken cancellationToken)
{
var newAnswers = GetFlatListOfAnswers(observerId, answers, lastModified);

var pollingStationIds = answers.Select(a => a.PollingStationId).Distinct().ToList();

foreach (var pollingStationId in pollingStationIds)
{
var questionIds = answers.Select(a => a.QuestionId).Distinct().ToList();

var oldAnswersToBeDeleted = _context.Answers
.Include(a => a.OptionAnswered)
.Where(
a =>
a.IdObserver == observerId &&
a.IdPollingStation == pollingStationId)
.WhereRaspunsContains(questionIds)
;
_context.Answers.RemoveRange(oldAnswersToBeDeleted);

await _context.SaveChangesAsync(cancellationToken);
}

await _context.Answers.AddRangeAsync(newAnswers, cancellationToken);
}
private async Task WriteCorruptedAnswers(int observerId, IReadOnlyCollection<CorruptedAnswerDto> corruptedAnswers, DateTime lastModified, CancellationToken cancellationToken)
{
var newCorruptedAnswers = GetFlatListOfCorruptedAnswers(observerId, corruptedAnswers, lastModified);
var pollingStationIds = corruptedAnswers.Select(a => (a.CountyCode, a.MunicipalityCode, a.PollingStationNumber)).Distinct().ToList();

foreach (var pollingStationId in pollingStationIds)
{
var questionIds = corruptedAnswers.Select(a => a.QuestionId).Distinct().ToList();

var oldAnswersToBeDeleted = _context.CorruptedAnswers
.Include(a => a.OptionAnswered)
.Where(
a =>
a.IdObserver == observerId &&
a.CountyCode == pollingStationId.CountyCode &&
a.MunicipalityCode == pollingStationId.MunicipalityCode &&
a.PollingStationNumber == pollingStationId.PollingStationNumber)
.WhereRaspunsContains(questionIds);

_context.CorruptedAnswers.RemoveRange(oldAnswersToBeDeleted);

await _context.SaveChangesAsync(cancellationToken);
}

await _context.CorruptedAnswers.AddRangeAsync(newCorruptedAnswers, cancellationToken);
}

public static List<Entities.Answer> GetFlatListOfAnswers(int observerId, IReadOnlyCollection<AnswerDto> answers, DateTime lastModified)
{
var list = command.Answers.Select(a => new
var list = answers.Select(a => new
{
flat = a.Options.Select(o => new Entities.Answer
{
flat = a.Options.Select(o => new Entities.Answer
{
IdObserver = command.ObserverId,
IdPollingStation = a.PollingStationId,
IdOptionToQuestion = o.OptionId,
Value = o.Value,
CountyCode = a.CountyCode,
PollingStationNumber = a.PollingStationNumber,
LastModified = lastModified
})
IdObserver = observerId,
IdPollingStation = a.PollingStationId,
IdOptionToQuestion = o.OptionId,
Value = o.Value,
CountyCode = a.CountyCode,
PollingStationNumber = a.PollingStationNumber,
LastModified = lastModified
})
})
.SelectMany(a => a.flat)
.GroupBy(k => k.IdOptionToQuestion,
(g, o) =>
Expand All @@ -85,7 +120,7 @@ public async Task<int> Handle(FillInAnswerCommand message, CancellationToken can

return new Entities.Answer
{
IdObserver = command.ObserverId,
IdObserver = observerId,
IdPollingStation = enumerable.Last().IdPollingStation,
IdOptionToQuestion = g,
Value = enumerable.Last().Value,
Expand All @@ -99,4 +134,42 @@ public async Task<int> Handle(FillInAnswerCommand message, CancellationToken can

return list;
}

public static List<Entities.AnswerCorrupted> GetFlatListOfCorruptedAnswers(int observerId, IReadOnlyCollection<CorruptedAnswerDto> corruptedAnswers, DateTime lastModified)
{
var list = corruptedAnswers.Select(a => new
{
flat = a.Options.Select(o => new Entities.AnswerCorrupted()
{
IdObserver = observerId,
IdOptionToQuestion = o.OptionId,
Value = o.Value,
CountyCode = a.CountyCode,
MunicipalityCode = a.MunicipalityCode,
PollingStationNumber = a.PollingStationNumber,
LastModified = lastModified
})
})
.SelectMany(a => a.flat)
.GroupBy(k => k.IdOptionToQuestion,
(g, o) =>
{
var enumerable = o as Entities.AnswerCorrupted[] ?? o.ToArray();

return new Entities.AnswerCorrupted
{
IdObserver = observerId,
IdOptionToQuestion = g,
Value = enumerable.Last().Value,
CountyCode = enumerable.Last().CountyCode,
MunicipalityCode = enumerable.Last().MunicipalityCode,
PollingStationNumber = enumerable.Last().PollingStationNumber,
LastModified = lastModified
};
})
.Distinct()
.ToList();

return list;
}
}
10 changes: 10 additions & 0 deletions src/api/VoteMonitor.Api.Answer/Models/CorruptedAnswerDto.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
namespace VoteMonitor.Api.Answer.Models;

public class CorruptedAnswerDto
{
public int QuestionId { get; set; }
public string CountyCode { get; set; }
public string MunicipalityCode { get; set; }
public int PollingStationNumber { get; set; }
public List<SelectedOptionDto> Options { get; set; }
}
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,7 @@ private async Task<int> SaveToPollingStationInfoCorruptedData(RegisterPollingSta
IdObserver = message.IdObserver,
CountyCode = message.CountyCode,
MunicipalityCode = message.MunicipalityCode,
PollingStationNumber = message.PollingStationNumber,

LastModified = DateTime.UtcNow,
ObserverArrivalTime = message.ObserverArrivalTime.AsUtc(),
Expand Down
15 changes: 15 additions & 0 deletions src/api/VoteMonitor.Entities/AnswerCorrupted.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
namespace VoteMonitor.Entities;

public class AnswerCorrupted
{
public int IdObserver { get; set; }
public int IdOptionToQuestion { get; set; }
public DateTime LastModified { get; set; }
public string Value { get; set; }
public string CountyCode { get; set; }
public string MunicipalityCode { get; set; }
public int PollingStationNumber { get; set; }

public virtual Observer Observer { get; set; }
public virtual OptionToQuestion OptionAnswered { get; set; }
}
21 changes: 19 additions & 2 deletions src/api/VoteMonitor.Entities/EfBuilderExtensions.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
using LinqKit;
using LinqKit;
using System.Linq.Expressions;

namespace VoteMonitor.Entities;
Expand All @@ -20,6 +20,23 @@ public static IQueryable<Answer> WhereRaspunsContains(this IQueryable<Answer> so
? (a => a.OptionAnswered.IdQuestion == id)
: expression.Or(a => a.OptionAnswered.IdQuestion == id));

return source.Where(ors);
}
/// <summary>
/// super simple and dumb translation of .Contains because is not supported pe EF plus
/// this translates to contains in EF SQL
/// </summary>
/// <param name="source"></param>
/// <param name="contains"></param>
/// <returns></returns>
public static IQueryable<AnswerCorrupted> WhereRaspunsContains(this IQueryable<AnswerCorrupted> source, IList<int> contains)
{
var ors = contains
.Aggregate<int, Expression<Func<AnswerCorrupted, bool>>>(null, (expression, id) =>
expression == null
? (a => a.OptionAnswered.IdQuestion == id)
: expression.Or(a => a.OptionAnswered.IdQuestion == id));

return source.Where(ors);
}
}
}
1 change: 1 addition & 0 deletions src/api/VoteMonitor.Entities/Observer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ public class Observer : IIdentifiableEntity
public ICollection<Note> Notes { get; set; } = new HashSet<Note>();
public ICollection<NoteCorrupted> NotesCorrupted { get; set; } = new HashSet<NoteCorrupted>();
public ICollection<Answer> Answers { get; set; } = new HashSet<Answer>();
public ICollection<AnswerCorrupted> CorruptedAnswers { get; set; } = new HashSet<AnswerCorrupted>();
public ICollection<PollingStationInfo> PollingStationInfos { get; set; } = new HashSet<PollingStationInfo>();
public ICollection<PollingStationInfoCorrupted> PollingStationInfosCorrupted { get; set; } = new HashSet<PollingStationInfoCorrupted>();
public Ngo Ngo { get; set; }
Expand Down
1 change: 1 addition & 0 deletions src/api/VoteMonitor.Entities/OptionToQuestion.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ public class OptionToQuestion : IIdentifiableEntity
public bool Flagged { get; set; }

public virtual ICollection<Answer> Answers { get; } = new HashSet<Answer>();
public virtual ICollection<AnswerCorrupted> CorruptedAnswers { get; } = new HashSet<AnswerCorrupted>();
public virtual Question Question { get; set; }
public virtual Option Option { get; set; }
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,4 +24,5 @@ public class PollingStationInfoCorrupted
public virtual Observer Observer { get; set; }
public string CountyCode { get; set; }
public string MunicipalityCode { get; set; }
public int PollingStationNumber { get; set; }
}
Loading

0 comments on commit 1974aff

Please sign in to comment.