From ccb276a2659623b14cb769318cc8d452688d422e Mon Sep 17 00:00:00 2001
From: "t.ruspekhofer"
Date: Tue, 3 Jan 2023 13:53:26 +0100
Subject: [PATCH] Added Polls and locking raids if not enough users are signed
up needs testing
---
Lieb/Data/Constants.cs | 6 +
Lieb/Data/DiscordService.cs | 9 +-
Lieb/Data/LiebContext.cs | 7 ++
Lieb/Data/PollService.cs | 113 ++++++++++++++++++
Lieb/Data/RaidService.cs | 58 ++++++++-
Lieb/Data/TimerService.cs | 2 +
Lieb/Models/GuildWars2/Raid/Raid.cs | 5 +
Lieb/Models/GuildWars2/Raid/RaidBase.cs | 4 +-
Lieb/Models/GuildWars2/Raid/RaidTemplate.cs | 2 +
Lieb/Models/Poll/Poll.cs | 12 ++
Lieb/Models/Poll/PollAnswer.cs | 11 ++
Lieb/Models/Poll/PollOption.cs | 8 ++
Lieb/Pages/Raids/RaidEdit/RaidEdit.razor | 25 ++++
.../Raids/RaidEdit/RaidTemplateEdit.razor | 35 +++++-
14 files changed, 289 insertions(+), 8 deletions(-)
create mode 100644 Lieb/Data/PollService.cs
create mode 100644 Lieb/Models/Poll/Poll.cs
create mode 100644 Lieb/Models/Poll/PollAnswer.cs
create mode 100644 Lieb/Models/Poll/PollOption.cs
diff --git a/Lieb/Data/Constants.cs b/Lieb/Data/Constants.cs
index f1e6df1..fc58894 100644
--- a/Lieb/Data/Constants.cs
+++ b/Lieb/Data/Constants.cs
@@ -8,6 +8,12 @@
public static readonly int RaidEditPowerLevel = Roles.Moderator.PowerLevel;
public const int REMOVE_MAYBE_MINUTES = 15;
+ public static class Polls
+ {
+ public const string YES = "yes";
+ public const string NO = "no";
+ }
+
public static class Roles
{
public static readonly RoleConstant User = new RoleConstant("user", 20);
diff --git a/Lieb/Data/DiscordService.cs b/Lieb/Data/DiscordService.cs
index 96201fe..80110c0 100644
--- a/Lieb/Data/DiscordService.cs
+++ b/Lieb/Data/DiscordService.cs
@@ -306,8 +306,15 @@ namespace Lieb.Data
public static ApiRaid ConvertRaid(Raid raid)
{
+ string title = raid.Title;
+ if (raid.SignUps.Count < raid.MinUsers
+ && raid.MinUserDeadLineUTC.UtcDateTime > DateTimeOffset.UtcNow)
+ {
+ title = $"The raid was canceled because of not enough sign ups.\n\n{raid.Title}";
+ }
+
ApiRaid apiRaid = new ApiRaid(){
- Title = raid.Title,
+ Title = title,
Description = raid.Description,
Guild = raid.Guild,
Organizer = raid.Organizer,
diff --git a/Lieb/Data/LiebContext.cs b/Lieb/Data/LiebContext.cs
index 1d0a1ce..fc11641 100644
--- a/Lieb/Data/LiebContext.cs
+++ b/Lieb/Data/LiebContext.cs
@@ -2,6 +2,7 @@
using Lieb.Models;
using Lieb.Models.GuildWars2;
using Lieb.Models.GuildWars2.Raid;
+using Lieb.Models.Poll;
using Microsoft.EntityFrameworkCore;
namespace Lieb.Data
@@ -26,6 +27,9 @@ namespace Lieb.Data
public DbSet RaidSignUps { get; set; }
public DbSet DiscordRaidMessages { get; set; }
public DbSet DiscordSettings { get; set; }
+ public DbSet Polls { get; set; }
+ public DbSet PollOptions { get; set; }
+ public DbSet PollAnswers { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
@@ -43,6 +47,9 @@ namespace Lieb.Data
modelBuilder.Entity().ToTable("RaidSignUp");
modelBuilder.Entity().ToTable("DiscordRaidMessage");
modelBuilder.Entity().ToTable("DiscordSettings");
+ modelBuilder.Entity().ToTable("Poll");
+ modelBuilder.Entity().ToTable("PollOption");
+ modelBuilder.Entity().ToTable("PollAnswer");
}
}
}
diff --git a/Lieb/Data/PollService.cs b/Lieb/Data/PollService.cs
new file mode 100644
index 0000000..d8864be
--- /dev/null
+++ b/Lieb/Data/PollService.cs
@@ -0,0 +1,113 @@
+using Lieb.Models.GuildWars2.Raid;
+using Lieb.Models.Poll;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.EntityFrameworkCore.Internal;
+
+namespace Lieb.Data
+{
+ public class PollService
+ {
+ private readonly IDbContextFactory _contextFactory;
+ private readonly DiscordService _discordService;
+ public PollService(IDbContextFactory contextFactory, DiscordService discordService)
+ {
+ _contextFactory = contextFactory;
+ _discordService = discordService;
+ }
+
+ public Poll GetPoll(int pollId)
+ {
+ using var context = _contextFactory.CreateDbContext();
+ Poll? poll = context.Polls
+ .Include(p => p.Options)
+ .Include(p => p.Answers)
+ .FirstOrDefault(p => p.PollId == pollId);
+
+ if (poll != null) return poll;
+
+ return new Poll();
+ }
+
+ public List GetPollsByRaidId(int raidId)
+ {
+ using var context = _contextFactory.CreateDbContext();
+ return context.Polls
+ .Include(p => p.Options)
+ .Include(p => p.Answers)
+ .Where(p => p.RaidId == raidId).ToList();
+ }
+
+ public async Task CreatePoll(string question, List options, int raidId)
+ {
+ using var context = _contextFactory.CreateDbContext();
+ Raid? raid = context.Raids
+ .Include(r => r.SignUps)
+ .FirstOrDefault(r => r.RaidId == raidId);
+
+ if (raid == null) return 0;
+ List users = raid.SignUps.Where(s => s.LiebUserId != null).Select(s => (ulong)s.LiebUserId).ToList();
+ return await CreatePoll(question, options, users, raidId);
+ }
+
+ public async Task CreatePoll(string question, List options, List users, int? raidId = null)
+ {
+ Poll poll = new Poll()
+ {
+ Question = question,
+ RaidId = raidId
+ };
+ foreach(string option in options)
+ {
+ poll.Options.Add(new PollOption()
+ {
+ Name = option
+ });
+ }
+ foreach(ulong user in users)
+ {
+ poll.Answers.Add(new PollAnswer()
+ {
+ UserId = user
+ });
+ }
+
+ using var context = _contextFactory.CreateDbContext();
+ context.Polls.Add(poll);
+ await context.SaveChangesAsync();
+ return poll.PollId;
+ }
+
+ public async Task DeletePoll(int pollId)
+ {
+ using var context = _contextFactory.CreateDbContext();
+ Poll? poll = context.Polls
+ .Include(p => p.Options)
+ .Include(p => p.Answers)
+ .FirstOrDefault(p => p.PollId == pollId);
+
+ if(poll == null) return;
+
+ context.PollOptions.RemoveRange(poll.Options);
+ context.PollAnswers.RemoveRange(poll.Answers);
+ await context.SaveChangesAsync();
+
+ poll.Options.Clear();
+ poll.Answers.Clear();
+ context.Polls.Remove(poll);
+ await context.SaveChangesAsync();
+ }
+
+ public async Task UpdateAnswer(int pollId, int pollOptionId, ulong userId)
+ {
+ using var context = _contextFactory.CreateDbContext();
+ Poll? poll = context.Polls
+ .Include(p => p.Answers)
+ .FirstOrDefault(p => p.PollId == pollId && p.Answers.Where(a => a.UserId == userId).Any());
+
+ if (poll == null) return;
+
+ PollAnswer answer = poll.Answers.First(a => a.UserId == userId);
+ answer.PollOptionId = pollOptionId;
+ }
+ }
+}
diff --git a/Lieb/Data/RaidService.cs b/Lieb/Data/RaidService.cs
index 678cabb..eff9c8b 100644
--- a/Lieb/Data/RaidService.cs
+++ b/Lieb/Data/RaidService.cs
@@ -1,6 +1,7 @@
using Lieb.Models;
using Lieb.Models.GuildWars2;
using Lieb.Models.GuildWars2.Raid;
+using Lieb.Models.Poll;
using Microsoft.EntityFrameworkCore;
namespace Lieb.Data
@@ -9,11 +10,13 @@ namespace Lieb.Data
{
private readonly IDbContextFactory _contextFactory;
private readonly DiscordService _discordService;
+ private readonly PollService _pollService;
- public RaidService(IDbContextFactory contextFactory, DiscordService discordService)
+ public RaidService(IDbContextFactory contextFactory, DiscordService discordService, PollService pollService)
{
_contextFactory = contextFactory;
_discordService = discordService;
+ _pollService = pollService;
}
public List GetRaids()
@@ -444,6 +447,7 @@ namespace Lieb.Data
errorMessage = string.Empty;
using var context = _contextFactory.CreateDbContext();
Raid? raid = context.Raids
+ .Include(r => r.SignUps)
.AsNoTracking()
.FirstOrDefault(r => r.RaidId == raidId);
if(raid == null)
@@ -451,6 +455,14 @@ namespace Lieb.Data
errorMessage = "Raid not found.";
return false;
}
+
+ if (raid.SignUps.Count < raid.MinUsers
+ && raid.MinUserDeadLineUTC.UtcDateTime > DateTimeOffset.UtcNow)
+ {
+ errorMessage = $"The raid was canceled because of not enough sign ups.";
+ return false;
+ }
+
LiebUser? user = context.LiebUsers
.Include(u => u.RoleAssignments)
.ThenInclude(a => a.LiebRole)
@@ -670,5 +682,49 @@ namespace Lieb.Data
await context.SaveChangesAsync();
}
}
+
+ public async Task CheckMinUsers()
+ {
+ using var context = _contextFactory.CreateDbContext();
+ List raids = context.Raids
+ .Include(r => r.SignUps)
+ .Where(r => r.SignUps.Count < r.MinUsers && r.MinUserPollId == null).ToList();
+ foreach (Raid raid in raids.Where(r => r.MinUserDeadLineUTC < DateTimeOffset.UtcNow && r.StartTimeUTC > DateTimeOffset.UtcNow))
+ {
+ raid.MinUserPollId = await _pollService.CreatePoll(
+ "The raid has not the required users, do you want to raid anyway?",
+ new List() {Constants.Polls.YES, Constants.Polls.NO }, raid.RaidId);
+ await context.SaveChangesAsync();
+ await _discordService.PostRaidMessage(raid.RaidId);
+ }
+ }
+
+ public async Task CheckMinUserPollResult()
+ {
+ using var context = _contextFactory.CreateDbContext();
+ List raids = context.Raids
+ .Include(r => r.SignUps)
+ .Where(r => r.SignUps.Count < r.MinUsers && r.MinUserPollId != null).ToList();
+ foreach (Raid raid in raids.Where(r => r.MinUserDeadLineUTC < DateTimeOffset.UtcNow && r.StartTimeUTC > DateTimeOffset.UtcNow))
+ {
+ Poll poll = _pollService.GetPoll(raid.MinUserPollId.Value);
+
+ if (poll.Answers.Count == 0) continue;
+ if (poll.Answers.Where(a => a.PollOptionId == null).Any()) continue;
+
+ int noOptionId = poll.Options.First(o => o.Name == Constants.Polls.NO).PollOptionId;
+ if(poll.Answers.Where(a => a.PollOptionId == noOptionId).Any())
+ {
+ await _discordService.SendMessageToRaidUsers("The raid is canceled.", raid);
+ }
+ else
+ {
+ raid.MinUsers = 0;
+ await context.SaveChangesAsync();
+ await _discordService.SendMessageToRaidUsers("The raid will take place. Signing up is allowed again.", raid);
+ await _discordService.PostRaidMessage(raid.RaidId);
+ }
+ }
+ }
}
}
diff --git a/Lieb/Data/TimerService.cs b/Lieb/Data/TimerService.cs
index d1814fe..4d6d6f5 100644
--- a/Lieb/Data/TimerService.cs
+++ b/Lieb/Data/TimerService.cs
@@ -49,6 +49,8 @@ namespace Lieb.Data
.GetRequiredService();
await raidService.SendReminders();
await raidService.RemoveMaybes();
+ await raidService.CheckMinUsers();
+ await raidService.CheckMinUserPollResult();
}
}
diff --git a/Lieb/Models/GuildWars2/Raid/Raid.cs b/Lieb/Models/GuildWars2/Raid/Raid.cs
index de8eed1..126f522 100644
--- a/Lieb/Models/GuildWars2/Raid/Raid.cs
+++ b/Lieb/Models/GuildWars2/Raid/Raid.cs
@@ -14,6 +14,10 @@ namespace Lieb.Models.GuildWars2.Raid
public DateTimeOffset FreeForAllTimeUTC { get; set; }
+ public DateTimeOffset MinUserDeadLineUTC { get; set; }
+
+ public int? MinUserPollId { get; set; }
+
public ICollection SignUps { get; set; } = new HashSet();
public Raid() { }
@@ -24,6 +28,7 @@ namespace Lieb.Models.GuildWars2.Raid
StartTimeUTC = TimeZoneInfo.ConvertTimeToUtc(template.StartTime, timeZone);
EndTimeUTC = TimeZoneInfo.ConvertTimeToUtc(template.EndTime, timeZone);
FreeForAllTimeUTC = TimeZoneInfo.ConvertTimeToUtc(template.FreeForAllTime, timeZone);
+ MinUserDeadLineUTC = TimeZoneInfo.ConvertTimeToUtc(template.MinUserDeadLine, timeZone);
}
}
diff --git a/Lieb/Models/GuildWars2/Raid/RaidBase.cs b/Lieb/Models/GuildWars2/Raid/RaidBase.cs
index 228e0e3..f4375f7 100644
--- a/Lieb/Models/GuildWars2/Raid/RaidBase.cs
+++ b/Lieb/Models/GuildWars2/Raid/RaidBase.cs
@@ -52,7 +52,8 @@ namespace Lieb.Models.GuildWars2.Raid
public ulong? RaidOwnerId { get; set; }
- //role name, number of spots
+ public int MinUsers { get; set; } = 0;
+
public ICollection Roles { get; set; } = new HashSet();
public ICollection Reminders { get; set; } = new List();
@@ -73,6 +74,7 @@ namespace Lieb.Models.GuildWars2.Raid
this.MoveFlexUsers = template.MoveFlexUsers;
this.RaidOwnerId = template.RaidOwnerId;
this.EventType = template.EventType;
+ this.MinUsers = template.MinUsers;
foreach (RaidRole role in template.Roles)
{
diff --git a/Lieb/Models/GuildWars2/Raid/RaidTemplate.cs b/Lieb/Models/GuildWars2/Raid/RaidTemplate.cs
index a2b3f45..7c51a28 100644
--- a/Lieb/Models/GuildWars2/Raid/RaidTemplate.cs
+++ b/Lieb/Models/GuildWars2/Raid/RaidTemplate.cs
@@ -14,6 +14,8 @@ namespace Lieb.Models.GuildWars2.Raid
public DateTime FreeForAllTime { get; set; }
+ public DateTime MinUserDeadLine { get; set; }
+
public string TimeZone { get; set; } = String.Empty;
public int Interval { get; set; }
diff --git a/Lieb/Models/Poll/Poll.cs b/Lieb/Models/Poll/Poll.cs
new file mode 100644
index 0000000..c11eb4d
--- /dev/null
+++ b/Lieb/Models/Poll/Poll.cs
@@ -0,0 +1,12 @@
+namespace Lieb.Models.Poll
+{
+ public class Poll
+ {
+ public int PollId { get; set; }
+ public string Question { get; set; }
+ public ICollection Options { get; set; } = new List();
+ public ICollection Answers { get; set; } = new List();
+ public int? RaidId { get; set; }
+
+ }
+}
diff --git a/Lieb/Models/Poll/PollAnswer.cs b/Lieb/Models/Poll/PollAnswer.cs
new file mode 100644
index 0000000..3f8be58
--- /dev/null
+++ b/Lieb/Models/Poll/PollAnswer.cs
@@ -0,0 +1,11 @@
+namespace Lieb.Models.Poll
+{
+ public class PollAnswer
+ {
+ public int PollAnswerId { get; set; }
+
+ public int? PollOptionId { get; set; }
+
+ public ulong UserId { get; set; }
+ }
+}
diff --git a/Lieb/Models/Poll/PollOption.cs b/Lieb/Models/Poll/PollOption.cs
new file mode 100644
index 0000000..2d5161b
--- /dev/null
+++ b/Lieb/Models/Poll/PollOption.cs
@@ -0,0 +1,8 @@
+namespace Lieb.Models.Poll
+{
+ public class PollOption
+ {
+ public int PollOptionId { get; set; }
+ public string Name { get; set;}
+ }
+}
diff --git a/Lieb/Pages/Raids/RaidEdit/RaidEdit.razor b/Lieb/Pages/Raids/RaidEdit/RaidEdit.razor
index 8e643a2..36b2595 100644
--- a/Lieb/Pages/Raids/RaidEdit/RaidEdit.razor
+++ b/Lieb/Pages/Raids/RaidEdit/RaidEdit.razor
@@ -115,6 +115,26 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+