Added Polls and locking raids if not enough users are signed up

needs testing
This commit is contained in:
t.ruspekhofer 2023-01-03 13:53:26 +01:00
parent 32af72a262
commit ccb276a265
14 changed files with 289 additions and 8 deletions

View file

@ -8,6 +8,12 @@
public static readonly int RaidEditPowerLevel = Roles.Moderator.PowerLevel; public static readonly int RaidEditPowerLevel = Roles.Moderator.PowerLevel;
public const int REMOVE_MAYBE_MINUTES = 15; 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 class Roles
{ {
public static readonly RoleConstant User = new RoleConstant("user", 20); public static readonly RoleConstant User = new RoleConstant("user", 20);

View file

@ -306,8 +306,15 @@ namespace Lieb.Data
public static ApiRaid ConvertRaid(Raid raid) 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(){ ApiRaid apiRaid = new ApiRaid(){
Title = raid.Title, Title = title,
Description = raid.Description, Description = raid.Description,
Guild = raid.Guild, Guild = raid.Guild,
Organizer = raid.Organizer, Organizer = raid.Organizer,

View file

@ -2,6 +2,7 @@
using Lieb.Models; using Lieb.Models;
using Lieb.Models.GuildWars2; using Lieb.Models.GuildWars2;
using Lieb.Models.GuildWars2.Raid; using Lieb.Models.GuildWars2.Raid;
using Lieb.Models.Poll;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
namespace Lieb.Data namespace Lieb.Data
@ -26,6 +27,9 @@ namespace Lieb.Data
public DbSet<RaidSignUp> RaidSignUps { get; set; } public DbSet<RaidSignUp> RaidSignUps { get; set; }
public DbSet<DiscordRaidMessage> DiscordRaidMessages { get; set; } public DbSet<DiscordRaidMessage> DiscordRaidMessages { get; set; }
public DbSet<DiscordSettings> DiscordSettings { get; set; } public DbSet<DiscordSettings> DiscordSettings { get; set; }
public DbSet<Poll> Polls { get; set; }
public DbSet<PollOption> PollOptions { get; set; }
public DbSet<PollAnswer> PollAnswers { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder) protected override void OnModelCreating(ModelBuilder modelBuilder)
@ -43,6 +47,9 @@ namespace Lieb.Data
modelBuilder.Entity<RaidSignUp>().ToTable("RaidSignUp"); modelBuilder.Entity<RaidSignUp>().ToTable("RaidSignUp");
modelBuilder.Entity<DiscordRaidMessage>().ToTable("DiscordRaidMessage"); modelBuilder.Entity<DiscordRaidMessage>().ToTable("DiscordRaidMessage");
modelBuilder.Entity<DiscordSettings>().ToTable("DiscordSettings"); modelBuilder.Entity<DiscordSettings>().ToTable("DiscordSettings");
modelBuilder.Entity<Poll>().ToTable("Poll");
modelBuilder.Entity<PollOption>().ToTable("PollOption");
modelBuilder.Entity<PollAnswer>().ToTable("PollAnswer");
} }
} }
} }

113
Lieb/Data/PollService.cs Normal file
View file

@ -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<LiebContext> _contextFactory;
private readonly DiscordService _discordService;
public PollService(IDbContextFactory<LiebContext> 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<Poll> 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<int> CreatePoll(string question, List<string> 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<ulong> users = raid.SignUps.Where(s => s.LiebUserId != null).Select(s => (ulong)s.LiebUserId).ToList();
return await CreatePoll(question, options, users, raidId);
}
public async Task<int> CreatePoll(string question, List<string> options, List<ulong> 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;
}
}
}

View file

@ -1,6 +1,7 @@
using Lieb.Models; using Lieb.Models;
using Lieb.Models.GuildWars2; using Lieb.Models.GuildWars2;
using Lieb.Models.GuildWars2.Raid; using Lieb.Models.GuildWars2.Raid;
using Lieb.Models.Poll;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
namespace Lieb.Data namespace Lieb.Data
@ -9,11 +10,13 @@ namespace Lieb.Data
{ {
private readonly IDbContextFactory<LiebContext> _contextFactory; private readonly IDbContextFactory<LiebContext> _contextFactory;
private readonly DiscordService _discordService; private readonly DiscordService _discordService;
private readonly PollService _pollService;
public RaidService(IDbContextFactory<LiebContext> contextFactory, DiscordService discordService) public RaidService(IDbContextFactory<LiebContext> contextFactory, DiscordService discordService, PollService pollService)
{ {
_contextFactory = contextFactory; _contextFactory = contextFactory;
_discordService = discordService; _discordService = discordService;
_pollService = pollService;
} }
public List<Raid> GetRaids() public List<Raid> GetRaids()
@ -444,6 +447,7 @@ namespace Lieb.Data
errorMessage = string.Empty; errorMessage = string.Empty;
using var context = _contextFactory.CreateDbContext(); using var context = _contextFactory.CreateDbContext();
Raid? raid = context.Raids Raid? raid = context.Raids
.Include(r => r.SignUps)
.AsNoTracking() .AsNoTracking()
.FirstOrDefault(r => r.RaidId == raidId); .FirstOrDefault(r => r.RaidId == raidId);
if(raid == null) if(raid == null)
@ -451,6 +455,14 @@ namespace Lieb.Data
errorMessage = "Raid not found."; errorMessage = "Raid not found.";
return false; 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 LiebUser? user = context.LiebUsers
.Include(u => u.RoleAssignments) .Include(u => u.RoleAssignments)
.ThenInclude(a => a.LiebRole) .ThenInclude(a => a.LiebRole)
@ -670,5 +682,49 @@ namespace Lieb.Data
await context.SaveChangesAsync(); await context.SaveChangesAsync();
} }
} }
public async Task CheckMinUsers()
{
using var context = _contextFactory.CreateDbContext();
List<Raid> 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<string>() {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<Raid> 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);
}
}
}
} }
} }

View file

@ -49,6 +49,8 @@ namespace Lieb.Data
.GetRequiredService<RaidService>(); .GetRequiredService<RaidService>();
await raidService.SendReminders(); await raidService.SendReminders();
await raidService.RemoveMaybes(); await raidService.RemoveMaybes();
await raidService.CheckMinUsers();
await raidService.CheckMinUserPollResult();
} }
} }

View file

@ -14,6 +14,10 @@ namespace Lieb.Models.GuildWars2.Raid
public DateTimeOffset FreeForAllTimeUTC { get; set; } public DateTimeOffset FreeForAllTimeUTC { get; set; }
public DateTimeOffset MinUserDeadLineUTC { get; set; }
public int? MinUserPollId { get; set; }
public ICollection<RaidSignUp> SignUps { get; set; } = new HashSet<RaidSignUp>(); public ICollection<RaidSignUp> SignUps { get; set; } = new HashSet<RaidSignUp>();
public Raid() { } public Raid() { }
@ -24,6 +28,7 @@ namespace Lieb.Models.GuildWars2.Raid
StartTimeUTC = TimeZoneInfo.ConvertTimeToUtc(template.StartTime, timeZone); StartTimeUTC = TimeZoneInfo.ConvertTimeToUtc(template.StartTime, timeZone);
EndTimeUTC = TimeZoneInfo.ConvertTimeToUtc(template.EndTime, timeZone); EndTimeUTC = TimeZoneInfo.ConvertTimeToUtc(template.EndTime, timeZone);
FreeForAllTimeUTC = TimeZoneInfo.ConvertTimeToUtc(template.FreeForAllTime, timeZone); FreeForAllTimeUTC = TimeZoneInfo.ConvertTimeToUtc(template.FreeForAllTime, timeZone);
MinUserDeadLineUTC = TimeZoneInfo.ConvertTimeToUtc(template.MinUserDeadLine, timeZone);
} }
} }

View file

@ -52,7 +52,8 @@ namespace Lieb.Models.GuildWars2.Raid
public ulong? RaidOwnerId { get; set; } public ulong? RaidOwnerId { get; set; }
//role name, number of spots public int MinUsers { get; set; } = 0;
public ICollection<RaidRole> Roles { get; set; } = new HashSet<RaidRole>(); public ICollection<RaidRole> Roles { get; set; } = new HashSet<RaidRole>();
public ICollection<RaidReminder> Reminders { get; set; } = new List<RaidReminder>(); public ICollection<RaidReminder> Reminders { get; set; } = new List<RaidReminder>();
@ -73,6 +74,7 @@ namespace Lieb.Models.GuildWars2.Raid
this.MoveFlexUsers = template.MoveFlexUsers; this.MoveFlexUsers = template.MoveFlexUsers;
this.RaidOwnerId = template.RaidOwnerId; this.RaidOwnerId = template.RaidOwnerId;
this.EventType = template.EventType; this.EventType = template.EventType;
this.MinUsers = template.MinUsers;
foreach (RaidRole role in template.Roles) foreach (RaidRole role in template.Roles)
{ {

View file

@ -14,6 +14,8 @@ namespace Lieb.Models.GuildWars2.Raid
public DateTime FreeForAllTime { get; set; } public DateTime FreeForAllTime { get; set; }
public DateTime MinUserDeadLine { get; set; }
public string TimeZone { get; set; } = String.Empty; public string TimeZone { get; set; } = String.Empty;
public int Interval { get; set; } public int Interval { get; set; }

12
Lieb/Models/Poll/Poll.cs Normal file
View file

@ -0,0 +1,12 @@
namespace Lieb.Models.Poll
{
public class Poll
{
public int PollId { get; set; }
public string Question { get; set; }
public ICollection<PollOption> Options { get; set; } = new List<PollOption>();
public ICollection<PollAnswer> Answers { get; set; } = new List<PollAnswer>();
public int? RaidId { get; set; }
}
}

View file

@ -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; }
}
}

View file

@ -0,0 +1,8 @@
namespace Lieb.Models.Poll
{
public class PollOption
{
public int PollOptionId { get; set; }
public string Name { get; set;}
}
}

View file

@ -115,6 +115,26 @@
</label> </label>
</p> </p>
<p>
<label>
Minimal required users:
<InputNumber @bind-Value="_raid.MinUsers" />
</label>
</p>
<p>
<label>
Minimal user deadline date:
<InputDate @bind-Value="_minUserDeadlineDate" />
</label>
</p>
<p>
<label>
Minimal user deadline time:
<input type="time" @bind="_minUserDeadlineTime" />
</label>
</p>
<p> <p>
<label> <label>
Organizer: Organizer:
@ -194,6 +214,8 @@
private DateTimeOffset _endTime; private DateTimeOffset _endTime;
private DateTimeOffset _freeForAllDate = DateTime.Now.Date; private DateTimeOffset _freeForAllDate = DateTime.Now.Date;
private DateTimeOffset _freeForAllTime; private DateTimeOffset _freeForAllTime;
private DateTimeOffset _minUserDeadlineDate = DateTime.Now.Date;
private DateTimeOffset _minUserDeadlineTime;
private List<RaidRole> _rolesToDelete = new List<RaidRole>(); private List<RaidRole> _rolesToDelete = new List<RaidRole>();
private List<RaidReminder> _remindersToDelete = new List<RaidReminder>(); private List<RaidReminder> _remindersToDelete = new List<RaidReminder>();
@ -240,6 +262,8 @@
_raidDate = _startTime.Date; _raidDate = _startTime.Date;
_freeForAllTime = await TimeZoneService.GetLocalDateTime(_raid.FreeForAllTimeUTC); _freeForAllTime = await TimeZoneService.GetLocalDateTime(_raid.FreeForAllTimeUTC);
_freeForAllDate = _freeForAllTime.Date; _freeForAllDate = _freeForAllTime.Date;
_minUserDeadlineTime = await TimeZoneService.GetLocalDateTime(_raid.MinUserDeadLineUTC);
_minUserDeadlineDate = _minUserDeadlineTime.Date;
foreach(RaidReminder reminder in _raid.Reminders) foreach(RaidReminder reminder in _raid.Reminders)
{ {
if(reminder.TimeType == RaidReminder.ReminderTimeType.Static) if(reminder.TimeType == RaidReminder.ReminderTimeType.Static)
@ -337,6 +361,7 @@
_raid.EndTimeUTC = await TimeZoneService.GetUTCDateTime(_raidDate.Date.AddDays(1) + _endTime.TimeOfDay); _raid.EndTimeUTC = await TimeZoneService.GetUTCDateTime(_raidDate.Date.AddDays(1) + _endTime.TimeOfDay);
} }
_raid.FreeForAllTimeUTC = await TimeZoneService.GetUTCDateTime(_freeForAllDate.Date + _freeForAllTime.TimeOfDay); _raid.FreeForAllTimeUTC = await TimeZoneService.GetUTCDateTime(_freeForAllDate.Date + _freeForAllTime.TimeOfDay);
_raid.MinUserDeadLineUTC = await TimeZoneService.GetUTCDateTime(_minUserDeadlineDate.Date + _minUserDeadlineTime.TimeOfDay);
if (!_raid.RaidOwnerId.HasValue) if (!_raid.RaidOwnerId.HasValue)
{ {

View file

@ -130,6 +130,26 @@
<input type="time" @bind="_freeForAllTime" /> <input type="time" @bind="_freeForAllTime" />
</label> </label>
</p> </p>
<p>
<label>
Minimal required users:
<InputNumber @bind-Value="_template.MinUsers" />
</label>
</p>
<p>
<label>
Minimal user deadline date:
<InputDate @bind-Value="_minUserDeadlineDate" />
</label>
</p>
<p>
<label>
Minimal user deadline time:
<input type="time" @bind="_minUserDeadlineTime" />
</label>
</p>
<p> <p>
<label> <label>
Organizer: Organizer:
@ -222,6 +242,8 @@
private DateTimeOffset _endTime; private DateTimeOffset _endTime;
private DateTimeOffset _freeForAllDate = DateTime.Now.Date; private DateTimeOffset _freeForAllDate = DateTime.Now.Date;
private DateTimeOffset _freeForAllTime; private DateTimeOffset _freeForAllTime;
private DateTimeOffset _minUserDeadlineDate = DateTime.Now.Date;
private DateTimeOffset _minUserDeadlineTime;
private string _userTimeZone = string.Empty; private string _userTimeZone = string.Empty;
private List<RaidRole> _rolesToDelete = new List<RaidRole>(); private List<RaidRole> _rolesToDelete = new List<RaidRole>();
@ -274,6 +296,8 @@
_raidDate = _startTime.Date; _raidDate = _startTime.Date;
_freeForAllTime = _template.FreeForAllTime; _freeForAllTime = _template.FreeForAllTime;
_freeForAllDate = _freeForAllTime.Date; _freeForAllDate = _freeForAllTime.Date;
_minUserDeadlineTime = _template.MinUserDeadLine;
_minUserDeadlineDate = _minUserDeadlineTime.Date;
foreach(RaidReminder reminder in _template.Reminders) foreach(RaidReminder reminder in _template.Reminders)
{ {
if(reminder.TimeType == RaidReminder.ReminderTimeType.Static) if(reminder.TimeType == RaidReminder.ReminderTimeType.Static)
@ -290,8 +314,8 @@
} }
else else
{ {
_template = new RaidTemplate(); _template = new RaidTemplate();
_dynamicReminders.Add(DynamicRaidReminder.Create30MinReminder()); _dynamicReminders.Add(DynamicRaidReminder.Create30MinReminder());
} }
} }
else else
@ -366,6 +390,7 @@
_template.EndTime = _raidDate.Date.AddDays(1) + _endTime.TimeOfDay; _template.EndTime = _raidDate.Date.AddDays(1) + _endTime.TimeOfDay;
} }
_template.FreeForAllTime = _freeForAllDate.Date + _freeForAllTime.TimeOfDay; _template.FreeForAllTime = _freeForAllDate.Date + _freeForAllTime.TimeOfDay;
_template.MinUserDeadLine = _minUserDeadlineDate.Date + _minUserDeadlineTime.TimeOfDay;
if (!_template.RaidOwnerId.HasValue) if (!_template.RaidOwnerId.HasValue)
{ {