Skip to content

Commit

Permalink
Dev (#54)
Browse files Browse the repository at this point in the history
* Add points to creator of vote when that vote has been voted on

* Check if poll has ended when voting

* Add assignpoints to assign points when a poll has ended

* rename x to suggestion

* Add poll end timer

* Update migrations

* add HasPointsAssigned to Poll

* Create timers for polls that haven't been assigned points yet.

* update swashbuckle

* check if tag already exists

* rename SuggestionsIds to SuggestionIds

* feat: SimilarityCheck.cs added to Tags

* Update PollController.cs

---------

Co-authored-by: KaanSecen <[email protected]>
Co-authored-by: Kaan Secen <[email protected]>
  • Loading branch information
3 people authored Jun 13, 2024
1 parent 99acde9 commit 308b143
Show file tree
Hide file tree
Showing 19 changed files with 968 additions and 105 deletions.
74 changes: 74 additions & 0 deletions Talpa Api/Algorithms/SimilarityCheck.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Internal;
using Talpa_Api.Models;
using Talpa_Api.Contexts;

namespace Talpa_Api.Algorithms
{
public abstract class SimilarityCheck
{
public readonly struct ObjectWithSimilarity(int suggestionId, string title, double similarity)
{
public int Id { get; init; } = suggestionId;
public string Title { get; init; } = title;
public double Similarity { get; init; } = similarity;
}

private static double CalculateSimilarityPercentage(string string1, string string2)
{
var pairs1 = WordLetterPairs(string1.ToUpper());
var pairs2 = WordLetterPairs(string2.ToUpper());

var intersection = 0;
var union = pairs1.Count + pairs2.Count;

foreach (var pair1 in pairs1)
{
for (var number = 0; number < pairs2.Count; number++)
{
if (pair1 != pairs2[number]) continue;

intersection++;
pairs2.RemoveAt(number);
break;
}
}

// return the percentage of similarity
return 2.0 * intersection * 100 / union;
}

private static List<string> WordLetterPairs(string str)
{
var allPairs = new List<string>();
var words = str.Split(' ');

foreach (var word in words)
{
for (var number = 0; number < word.Length - 1; number++)
{
allPairs.Add(word.Substring(number, 2));
}
}

return allPairs;
}

public static (List<ObjectWithSimilarity> objects, double max) GetObjectWithSimilarity(string title, dynamic dbSet)
{
var objectWithSimilarity = new List<ObjectWithSimilarity>();
var maxSimilarity = 0.0;

foreach (var obj in dbSet)
{
var sim = CalculateSimilarityPercentage(obj.Title, title);

if (sim > maxSimilarity) maxSimilarity = sim;

if (sim > 50) objectWithSimilarity.Add(new ObjectWithSimilarity(obj.Id, obj.Title, sim));
}

return (objectWithSimilarity, maxSimilarity);
}
}
}
49 changes: 42 additions & 7 deletions Talpa Api/Controllers/Api/PollController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Localization;
using Talpa_Api.Contexts;
using Talpa_Api.Events;
using Talpa_Api.Localization;
using Talpa_Api.Models;

Expand All @@ -15,7 +16,7 @@ public readonly struct PollData
{
public List<DateTime> Dates { get; init; }

public List<int> SuggestionsIds { get; init; }
public List<int> SuggestionIds { get; init; }
}

[HttpPost]
Expand All @@ -25,19 +26,53 @@ public async Task<ActionResult> CreatePoll(string teamId, DateTime endDate, [Fro

if (team is null)
return NotFound(localizer["TeamNotFound"].Value);
if (team.Poll is not null && team.Poll.EndDate > DateTime.Now)
if (team.Poll is not null && !team.Poll.HasEnded)
return Conflict(localizer["TeamAlreadyActivePoll"].Value);
if (data.Dates.Count < 1)
return BadRequest(localizer["CreatePollDateCountWrong"].Value);
if (data.SuggestionsIds.Count is < 1 or > 3)
if (data.SuggestionIds.Count is < 1 or > 3)
return BadRequest(localizer["CreatePollSuggestionCountWrong"].Value);
if (data.SuggestionsIds.Any(id => context.Suggestions.Find(id) is null))
if (data.SuggestionIds.Any(id => context.Suggestions.Find(id) is null))
return NotFound(localizer["SuggestionNotFound"].Value);

team.Poll = new Poll(endDate, data.Dates, data.SuggestionsIds.Select(id => context.Suggestions.Find(id)).ToList()!, team);
team.Poll = new Poll(endDate.ToUniversalTime(), data.Dates, data.SuggestionIds.Select(id => context.Suggestions.Find(id)).ToList()!, team);

await context.SaveChangesAsync();

return Created();
PollEndTimer.CreateTimer(team.Poll);

return Created();
}
}

[NonAction]
public static void AssignPoints(int pollId)
{
var scope = Program.Application.Services.CreateScope();
var context = scope.ServiceProvider.GetRequiredService<Context>();

var poll = context.Polls
.Include(poll => poll.Suggestions)
.ThenInclude(suggestion => suggestion.Creator)
.Include(poll => poll.Suggestions)
.ThenInclude(suggestion => suggestion.Votes)
.ToList()
.Find(x => x.Id == pollId);

if (poll is null || poll.HasPointsAssigned || poll.Suggestions.Count < 1) return;

var winningSuggestion = poll.Suggestions.MaxBy(x => x.Votes.Count(vote => poll.Id == vote.Poll.Id));

if (winningSuggestion?.Creator is null || winningSuggestion.Votes.All(vote => poll.Id != vote.Poll.Id))
{
poll.HasPointsAssigned = true;
context.SaveChanges();
return;
}

winningSuggestion.Creator.Points += 10;

poll.HasPointsAssigned = true;

context.SaveChanges();
}
}
92 changes: 16 additions & 76 deletions Talpa Api/Controllers/Api/SuggestionsController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
using Talpa_Api.Contexts;
using Talpa_Api.Localization;
using Talpa_Api.Models;
using Talpa_Api.Algorithms;

namespace Talpa_Api.Controllers.Api;

Expand All @@ -12,18 +13,18 @@ namespace Talpa_Api.Controllers.Api;
public class SuggestionsController(Context context, IStringLocalizer<LocalizationStrings> localizer) : ControllerBase
{
private static readonly string[] AllowedExtensions = [".jpg", ".jpeg", ".png", ".gif", ".bmp", ".webp"];

[HttpGet]
public async Task<ActionResult<List<Suggestion>>> GetSuggestions()
{
return await context.Suggestions
.Include(x => x.Creator)
.Include(x => x.Tags)
.Include(suggestion => suggestion.Creator)
.Include(suggestion => suggestion.Tags)
.ToListAsync();
}

[HttpPost]
public async Task<ActionResult<List<SuggestionWithSimilarity>>> CreateSuggestion(string title, string description, string creatorId, IFormFile? image, bool overrideSimilarity = false)
public async Task<ActionResult<List<SimilarityCheck.ObjectWithSimilarity>>> CreateSuggestion(string title, string description, string creatorId, IFormFile? image, bool overrideSimilarity = false)
{
var user = await context.Users.FindAsync(creatorId);
if (user is null) return NotFound(localizer["UserNotFound"].Value);
Expand All @@ -40,26 +41,24 @@ public async Task<ActionResult<List<SuggestionWithSimilarity>>> CreateSuggestion
return BadRequest(localizer["ImageInvalid"].Value);
}


var (suggestionsWithSimilarity, maxSimilarity) = GetSuggestionsWithSimilarity(title);
var similarity = SimilarityCheck.GetObjectWithSimilarity(title, context.Suggestions);

if (maxSimilarity >= 90)
if (similarity.max >= 90)
return Conflict(localizer["SuggestionTooSimilar"].Value);

if (!overrideSimilarity && suggestionsWithSimilarity.Count > 0 && maxSimilarity > 70)
return Accepted(suggestionsWithSimilarity.OrderByDescending(x => x.Similarity).ToList());


if (!overrideSimilarity && similarity.objects.Count > 0 && similarity.max > 70)
return Accepted(similarity.objects.OrderByDescending(x => x.Similarity).ToList());

context.Suggestions.Add(new Suggestion
{
Title = title,
Title = title,
Description = description,
ImagePath = imagePath,
Creator = user
ImagePath = imagePath,
Creator = user
});

await context.SaveChangesAsync();

return Created();
}

Expand All @@ -73,7 +72,7 @@ public async Task<ActionResult> ChangeSuggestion(int id, string description)
suggestion.Description = description;

await context.SaveChangesAsync();

return Created();
}

Expand All @@ -94,65 +93,6 @@ public async Task<ActionResult> ChangeSuggestion(int id, string description)

await image.CopyToAsync(stream);


return Path.Combine("images", fileName);
}

private (List<SuggestionWithSimilarity>, double) GetSuggestionsWithSimilarity(string title)
{
var suggestionsWithSimilarity = new List<SuggestionWithSimilarity>();
var maxSimilarity = 0.0;

foreach (var sug in context.Suggestions)
{
var sim = CalculateSimilarityPercentage(sug.Title, title);

if (sim > maxSimilarity) maxSimilarity = sim;

if (sim > 50) suggestionsWithSimilarity.Add(new SuggestionWithSimilarity(sug.Id, sug.Title, sim));
}

return (suggestionsWithSimilarity, maxSimilarity);
}

private double CalculateSimilarityPercentage(string string1, string string2)
{
var pairs1 = WordLetterPairs(string1.ToUpper());
var pairs2 = WordLetterPairs(string2.ToUpper());

var intersection = 0;
var union = pairs1.Count + pairs2.Count;

foreach (var pair1 in pairs1)
{
for (var number = 0; number < pairs2.Count; number++)
{
if (pair1 != pairs2[number]) continue;

intersection++;
pairs2.RemoveAt(number);
break;
}
}

// return the percentage of similarity
return 2.0 * intersection * 100 / union;
}

// Required for the CalculateSimilarityPercentage method.
private List<string> WordLetterPairs(string str)
{
var allPairs = new List<string>();
var words = str.Split(' ');

foreach (var word in words)
{
for (var number = 0; number < word.Length - 1; number++)
{
allPairs.Add(word.Substring(number, 2));
}
}

return allPairs;
}
}
13 changes: 9 additions & 4 deletions Talpa Api/Controllers/Api/TagsController.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Localization;
using Talpa_Api.Algorithms;
using Talpa_Api.Contexts;
using Talpa_Api.Localization;
using Talpa_Api.Models;
Expand All @@ -11,16 +12,20 @@ namespace Talpa_Api.Controllers.Api;
public class TagsController(Context context, IStringLocalizer<LocalizationStrings> localizer) : ControllerBase
{
[HttpPost]
public async Task<ActionResult> CreateTag(string title, bool restrictive, int suggestionId)
public async Task<ActionResult<List<SimilarityCheck.ObjectWithSimilarity>>> CreateTag(string title, bool restrictive, bool overrideSimilarity = false)
{
var suggestion = await context.Suggestions.FindAsync(suggestionId);
if (suggestion is null) return NotFound(localizer["SuggestionNotFound"].Value);
var similarity = SimilarityCheck.GetObjectWithSimilarity(title, context);

if (similarity.max >= 90) return Conflict(localizer["TagAlreadyExists"].Value);

if (similarity.objects.Count > 0 && similarity.max > 70 && !overrideSimilarity)
return Accepted(similarity.objects.OrderByDescending(x => x.Similarity).ToList());

var tag = new Tag
{
Title = title,
Restrictive = restrictive,
Suggestions = [suggestion]
Suggestions = []
};

await context.Tags.AddAsync(tag);
Expand Down
9 changes: 8 additions & 1 deletion Talpa Api/Controllers/Api/VotesController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -55,11 +55,16 @@ public async Task<ActionResult> CreateVote(string userId, int pollId, int sugges
.Find(x => x.Id == userId);

var poll = await context.Polls.FindAsync(pollId);
var suggestion = await context.Suggestions.FindAsync(suggestionId);

var suggestion = context.Suggestions
.Include(suggestion => suggestion.Creator)
.ToList()
.Find(x => x.Id == suggestionId);

if (user is null) return NotFound(localizer["UserNotFound"].Value);
if (poll is null) return NotFound(localizer["PollNotFound"].Value);
if (suggestion is null) return NotFound(localizer["SuggestionNotFound"].Value);
if (poll.HasEnded) return Conflict(localizer["PollHasEnded"].Value);

if (user.Votes.Any(x => x.Poll.Id == pollId)) return Conflict(localizer["UserAlreadyVoted"].Value);

Expand All @@ -69,6 +74,8 @@ await context.Votes.AddAsync(new Vote
Poll = poll,
Suggestion = suggestion
});

if (suggestion.Creator is not null) suggestion.Creator.Points += 1;

await context.SaveChangesAsync();
return Created();
Expand Down
40 changes: 40 additions & 0 deletions Talpa Api/Events/PollEndTimer.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
using Microsoft.Extensions.Localization;
using Talpa_Api.Contexts;
using Talpa_Api.Controllers.Api;
using Talpa_Api.Localization;
using Talpa_Api.Models;
using Timer = System.Timers.Timer;

namespace Talpa_Api.Events;

public static class PollEndTimer
{
private static readonly List<Timer> Timers = [];

public static void CreateTimer(Poll poll)
{
switch (poll)
{
case { HasEnded: true, HasPointsAssigned: true }:
return;
case { HasEnded: true, HasPointsAssigned: false }:
PollController.AssignPoints(poll.Id);
return;
default:
{
var timer = new Timer
{
Interval = poll.EndDate.Subtract(DateTime.UtcNow).TotalMilliseconds,
AutoReset = false,
};

timer.Elapsed += (_, _) => PollController.AssignPoints(poll.Id);

timer.Start();

Timers.Add(timer);
break;
}
}
}
}
Loading

0 comments on commit 308b143

Please sign in to comment.