Added RaidTemplates and TimerService to create weekly raids

This commit is contained in:
t.ruspekhofer 2022-03-12 21:16:45 +01:00
parent c215ed058f
commit ab6602710d
13 changed files with 270 additions and 53 deletions

View file

@ -106,6 +106,31 @@ namespace Lieb.Data
context.Raids.Add(raid); context.Raids.Add(raid);
context.SaveChanges(); context.SaveChanges();
DateTime templateStartTime = DateTime.UtcNow.AddDays(-7).AddMinutes(-117);
RaidTemplate template = new RaidTemplate()
{
Title = "Testraid",
Description = "This is a test raid\nwith multiple lines?",
Guild = "LIEB",
Organizer = "Sarah",
RaidType = RaidType.RandomWithBoons,
StartTime = templateStartTime,
EndTime = templateStartTime.AddHours(2),
FreeForAllTime = templateStartTime.AddHours(-2),
VoiceChat = "ts.lieb.games",
Frequency = 7,
CreateDaysBefore = 7,
TimeZone = "Europe/Vienna",
Roles = new[] { new PlannedRaidRole(){
Description = "WupWup",
Name = "Ups",
Spots = 10
}
}
};
context.RaidTemplates.Add(template);
context.SaveChanges();
var signUps = new RaidSignUp[] var signUps = new RaidSignUp[]
{ {
new RaidSignUp{GuildWars2AccountId = linaith.GuildWars2AccountId, LiebUserId = users[0].LiebUserId, PlannedRaidRoleId = ele.PlannedRaidRoleId, RaidId = raid.RaidId, SignUpType = SignUpType.SignedUp }, new RaidSignUp{GuildWars2AccountId = linaith.GuildWars2AccountId, LiebUserId = users[0].LiebUserId, PlannedRaidRoleId = ele.PlannedRaidRoleId, RaidId = raid.RaidId, SignUpType = SignUpType.SignedUp },

View file

@ -21,6 +21,7 @@ namespace Lieb.Data
public DbSet<GuildWars2Build> GuildWars2Builds { get; set; } public DbSet<GuildWars2Build> GuildWars2Builds { get; set; }
public DbSet<PlannedRaidRole> PlannedRaidRoles { get; set; } public DbSet<PlannedRaidRole> PlannedRaidRoles { get; set; }
public DbSet<Raid> Raids { get; set; } public DbSet<Raid> Raids { get; set; }
public DbSet<RaidTemplate> RaidTemplates { get; set; }
public DbSet<RaidReminder> RaidReminders { get; set; } public DbSet<RaidReminder> RaidReminders { get; set; }
public DbSet<RaidSignUp> RaidSignUps { get; set; } public DbSet<RaidSignUp> RaidSignUps { get; set; }
public DbSet<SignUpHistory> SignUpHistories { get; set; } public DbSet<SignUpHistory> SignUpHistories { get; set; }
@ -36,6 +37,7 @@ namespace Lieb.Data
modelBuilder.Entity<GuildWars2Build>().ToTable("GuildWars2Build"); modelBuilder.Entity<GuildWars2Build>().ToTable("GuildWars2Build");
modelBuilder.Entity<PlannedRaidRole>().ToTable("PlannedRaidRole"); modelBuilder.Entity<PlannedRaidRole>().ToTable("PlannedRaidRole");
modelBuilder.Entity<Raid>().ToTable("Raid"); modelBuilder.Entity<Raid>().ToTable("Raid");
modelBuilder.Entity<RaidTemplate>().ToTable("RaidTemplate");
modelBuilder.Entity<RaidReminder>().ToTable("RaidReminder"); modelBuilder.Entity<RaidReminder>().ToTable("RaidReminder");
modelBuilder.Entity<RaidSignUp>().ToTable("RaidSignUp"); modelBuilder.Entity<RaidSignUp>().ToTable("RaidSignUp");
modelBuilder.Entity<SignUpHistory>().ToTable("SignUpHistory"); modelBuilder.Entity<SignUpHistory>().ToTable("SignUpHistory");

View file

@ -67,12 +67,10 @@ namespace Lieb.Data
raidToChange.Description = raid.Description; raidToChange.Description = raid.Description;
raidToChange.StartTimeUTC = raid.StartTimeUTC; raidToChange.StartTimeUTC = raid.StartTimeUTC;
raidToChange.EndTimeUTC = raid.EndTimeUTC; raidToChange.EndTimeUTC = raid.EndTimeUTC;
raidToChange.TimeZone = raid.TimeZone;
raidToChange.Organizer = raid.Organizer; raidToChange.Organizer = raid.Organizer;
raidToChange.Guild = raid.Guild; raidToChange.Guild = raid.Guild;
raidToChange.VoiceChat = raid.VoiceChat; raidToChange.VoiceChat = raid.VoiceChat;
raidToChange.RaidType = raid.RaidType; raidToChange.RaidType = raid.RaidType;
raidToChange.Frequency = raid.Frequency;
raidToChange.RequiredRole = raid.RequiredRole; raidToChange.RequiredRole = raid.RequiredRole;
raidToChange.FreeForAllTimeUTC = raid.FreeForAllTimeUTC; raidToChange.FreeForAllTimeUTC = raid.FreeForAllTimeUTC;
raidToChange.DiscordMessageId = raid.DiscordMessageId; raidToChange.DiscordMessageId = raid.DiscordMessageId;
@ -337,5 +335,22 @@ namespace Lieb.Data
return true; return true;
} }
public void SendReminders()
{
using var context = _contextFactory.CreateDbContext();
List<Raid> raids = context.Raids
.Include(r => r.Reminders)
.Where(raid => raid.Reminders.Where(reminder => !reminder.Sent && raid.StartTimeUTC.AddHours(-reminder.HoursBeforeRaid) < DateTime.UtcNow).Any())
.ToList();
foreach(Raid raid in raids)
{
foreach(RaidReminder reminder in raid.Reminders.Where(reminder => !reminder.Sent && raid.StartTimeUTC.AddHours(-reminder.HoursBeforeRaid) < DateTime.UtcNow))
{
//TODO send reminders -> this is a Discord Problem...
}
}
}
} }
} }

View file

@ -0,0 +1,49 @@
using Lieb.Models.GuildWars2.Raid;
using Microsoft.EntityFrameworkCore;
namespace Lieb.Data
{
public class RaidTemplateService
{
private readonly IDbContextFactory<LiebContext> _contextFactory;
public RaidTemplateService(IDbContextFactory<LiebContext> contextFactory)
{
_contextFactory = contextFactory;
}
public List<RaidTemplate> GetTemplates()
{
using var context = _contextFactory.CreateDbContext();
return context.RaidTemplates
.Include(r => r.Roles)
.Include(r => r.Reminders)
.ToList();
}
public async Task CreateNewRaid(int raidTempalteId)
{
using var context = _contextFactory.CreateDbContext();
RaidTemplate? template = await context.RaidTemplates
.Include(r => r.Roles)
.Include(r => r.Reminders)
.FirstOrDefaultAsync(t => t.RaidTemplateId == raidTempalteId);
if(template == null)
{
return;
}
Raid raid = new Raid(template);
context.Raids.Add(raid);
MoveTemplate(template);
await context.SaveChangesAsync();
}
private void MoveTemplate(RaidTemplate template)
{
template.StartTime = template.StartTime.AddDays(template.Frequency);
template.EndTime = template.EndTime.AddDays(template.Frequency);
template.FreeForAllTime = template.FreeForAllTime.AddDays(template.Frequency);
}
}
}

60
Lieb/Data/TimerService.cs Normal file
View file

@ -0,0 +1,60 @@
using Lieb.Models.GuildWars2.Raid;
namespace Lieb.Data
{
public class TimerService : IHostedService, IDisposable
{
private Timer _timer = null!;
private IServiceProvider _services;
public TimerService(IServiceProvider services)
{
_services = services;
}
public Task StartAsync(CancellationToken stoppingToken)
{
_timer = new Timer(CheckRaids, null, TimeSpan.Zero,
TimeSpan.FromMinutes(1));
return Task.CompletedTask;
}
private void CheckRaids(object? state)
{
using (var scope = _services.CreateScope())
{
var raidTemplateService =
scope.ServiceProvider
.GetRequiredService<RaidTemplateService>();
foreach(RaidTemplate template in raidTemplateService.GetTemplates())
{
TimeZoneInfo timeZone = TimeZoneInfo.FindSystemTimeZoneById(template.TimeZone);
DateTime UTCStartTime = TimeZoneInfo.ConvertTimeToUtc(template.StartTime, timeZone);
if(UTCStartTime.AddDays(-template.CreateDaysBefore).AddHours(1) < DateTime.UtcNow)
{
raidTemplateService.CreateNewRaid(template.RaidTemplateId).Wait();
}
}
var raidService =
scope.ServiceProvider
.GetRequiredService<RaidService>();
raidService.SendReminders();
}
}
public Task StopAsync(CancellationToken stoppingToken)
{
_timer?.Change(Timeout.Infinite, 0);
return Task.CompletedTask;
}
public void Dispose()
{
_timer?.Dispose();
}
}
}

View file

@ -10,53 +10,19 @@ namespace Lieb.Models.GuildWars2.Raid
RandomEliteSpecialization = 3, RandomEliteSpecialization = 3,
} }
public class Raid public class Raid : RaidBase
{ {
public int RaidId { get; private set; } public int RaidId { get; private set; }
[Required]
[StringLength(100, ErrorMessage = "Title too long (100 character limit).")]
public string Title { get; set; } = String.Empty;
[Required]
[StringLength(1000, ErrorMessage = "Description too long (1000 character limit).")]
public string Description { get; set; } = String.Empty;
[Required] [Required]
public DateTimeOffset StartTimeUTC { get; set; } = DateTime.Now; public DateTimeOffset StartTimeUTC { get; set; } = DateTime.Now;
[Required] [Required]
public DateTimeOffset EndTimeUTC { get; set; } public DateTimeOffset EndTimeUTC { get; set; }
public string TimeZone { get; set; } = String.Empty;
[Required]
[StringLength(50, ErrorMessage = "Organizer too long (50 character limit).")]
public string Organizer { get; set; } = String.Empty;
[Required]
[StringLength(50, ErrorMessage = "Guild too long (50 character limit).")]
public string Guild { get; set; } = String.Empty;
[Required]
[StringLength(50, ErrorMessage = "VoiceChat too long (50 character limit).")]
public string VoiceChat { get; set; } = String.Empty;
[Required]
public RaidType RaidType { get; set; }
public bool IsRandomized { get; set; } = false;
public int Frequency { get; set; }
public string RequiredRole { get; set; } = String.Empty;
public DateTimeOffset FreeForAllTimeUTC { get; set; } public DateTimeOffset FreeForAllTimeUTC { get; set; }
//role name, number of spots public bool IsRandomized { get; set; } = false;
public ICollection<PlannedRaidRole> Roles { get; set; } = new HashSet<PlannedRaidRole>();
public ICollection<RaidReminder> Reminders { get; set; } = new List<RaidReminder>();
public ICollection<RaidSignUp> SignUps { get; set; } = new HashSet<RaidSignUp>(); public ICollection<RaidSignUp> SignUps { get; set; } = new HashSet<RaidSignUp>();
@ -65,8 +31,46 @@ namespace Lieb.Models.GuildWars2.Raid
//used to edit the Discord message //used to edit the Discord message
public ulong DiscordMessageId { get; set; } public ulong DiscordMessageId { get; set; }
public ulong DiscordChannelId { get; set; } public Raid() { }
public Raid(RaidTemplate template)
{
this.Title = template.Title;
this.Description = template.Description;
this.Organizer = template.Organizer;
this.Guild = template.Guild;
this.VoiceChat = template.VoiceChat;
this.RaidType = template.RaidType;
this.RequiredRole = template.RequiredRole;
this.DiscordChannelId = template.DiscordChannelId;
this.DiscordGuildId = template.DiscordGuildId;
foreach(PlannedRaidRole role in template.Roles)
{
this.Roles.Add(new PlannedRaidRole()
{
Description = role.Description,
Name = role.Name,
Spots = role.Spots
});
}
foreach(RaidReminder reminder in template.Reminders)
{
this.Reminders.Add(new RaidReminder()
{
ChannelId = reminder.ChannelId,
HoursBeforeRaid = reminder.HoursBeforeRaid,
Message = reminder.Message,
Sent = reminder.Sent,
Type = reminder.Type
});
}
TimeZoneInfo timeZone = TimeZoneInfo.FindSystemTimeZoneById(template.TimeZone);
StartTimeUTC = TimeZoneInfo.ConvertTimeToUtc(template.StartTime, timeZone);
EndTimeUTC = TimeZoneInfo.ConvertTimeToUtc(template.EndTime, timeZone);
FreeForAllTimeUTC = TimeZoneInfo.ConvertTimeToUtc(template.FreeForAllTime, timeZone);
}
public ulong DiscordGuildId { get; set; }
} }
} }

View file

@ -0,0 +1,42 @@
using System.ComponentModel.DataAnnotations;
namespace Lieb.Models.GuildWars2.Raid
{
public class RaidBase
{
[Required]
[StringLength(100, ErrorMessage = "Title too long (100 character limit).")]
public string Title { get; set; } = String.Empty;
[Required]
[StringLength(1000, ErrorMessage = "Description too long (1000 character limit).")]
public string Description { get; set; } = String.Empty;
[Required]
[StringLength(50, ErrorMessage = "Organizer too long (50 character limit).")]
public string Organizer { get; set; } = String.Empty;
[Required]
[StringLength(50, ErrorMessage = "Guild too long (50 character limit).")]
public string Guild { get; set; } = String.Empty;
[Required]
[StringLength(50, ErrorMessage = "VoiceChat too long (50 character limit).")]
public string VoiceChat { get; set; } = String.Empty;
[Required]
public RaidType RaidType { get; set; }
public string RequiredRole { get; set; } = String.Empty;
//role name, number of spots
public ICollection<PlannedRaidRole> Roles { get; set; } = new HashSet<PlannedRaidRole>();
public ICollection<RaidReminder> Reminders { get; set; } = new List<RaidReminder>();
//used to edit the Discord message
public ulong DiscordChannelId { get; set; }
public ulong DiscordGuildId { get; set; }
}
}

View file

@ -6,26 +6,21 @@ namespace Lieb.Models.GuildWars2.Raid
{ {
public enum ReminderType public enum ReminderType
{ {
User = 0, User = 1,
Channel = 1 Channel = 2
}
public RaidReminder(ReminderType type, string message, double hoursBeforeRaid, ulong channelId = 0)
{
Type = type;
Message = message;
HoursBeforeRaid = hoursBeforeRaid;
ChannelId = channelId;
} }
public int RaidReminderId { get; set; } public int RaidReminderId { get; set; }
[Required]
[Range(1, 2, ErrorMessage = "Please select a reminder type")]
public ReminderType Type { get; set; } public ReminderType Type { get; set; }
[Required] [Required]
[StringLength(1000, ErrorMessage = "Message too long (1000 character limit).")] [StringLength(1000, ErrorMessage = "Message too long (1000 character limit).")]
public string Message { get; set; } public string Message { get; set; }
[Required]
public double HoursBeforeRaid { get; set; } public double HoursBeforeRaid { get; set; }
public ulong ChannelId { get; set; } public ulong ChannelId { get; set; }

View file

@ -0,0 +1,23 @@
using System.ComponentModel.DataAnnotations;
namespace Lieb.Models.GuildWars2.Raid
{
public class RaidTemplate : RaidBase
{
public int RaidTemplateId { get; private set; }
[Required]
public DateTime StartTime { get; set; } = DateTime.Now;
[Required]
public DateTime EndTime { get; set; }
public DateTime FreeForAllTime { get; set; }
public string TimeZone { get; set; } = String.Empty;
public int Frequency { get; set; }
public int CreateDaysBefore { get; set; }
}
}

View file

@ -193,7 +193,7 @@
return; return;
} }
_raid.TimeZone = await TimeZoneService.GetUserTimeZone(); //_raid.TimeZone = await TimeZoneService.GetUserTimeZone();
_raid.StartTimeUTC = await TimeZoneService.GetUTCDateTime(_raidDate.Date + _startTime.TimeOfDay); _raid.StartTimeUTC = await TimeZoneService.GetUTCDateTime(_raidDate.Date + _startTime.TimeOfDay);
if(_startTime.TimeOfDay > _endTime.TimeOfDay) if(_startTime.TimeOfDay > _endTime.TimeOfDay)

View file

@ -1,7 +1,7 @@
@using Lieb.Models.GuildWars2.Raid @using Lieb.Models.GuildWars2.Raid
<div> <div>
<table class="roleTable"> <table class="table">
<tbody> <tbody>
@foreach (var role in _raid.Roles) @foreach (var role in _raid.Roles)
{ {

View file

@ -56,7 +56,7 @@
@{string signUpStatus = string.Empty;} @{string signUpStatus = string.Empty;}
@if (signUp.SignUpType != SignUpType.SignedUp) signUpStatus = $" - {signUp.SignUpType}"; @if (signUp.SignUpType != SignUpType.SignedUp) signUpStatus = $" - {signUp.SignUpType}";
@if (isUser) @if (isUser && _user.GuildWars2Accounts.Count > 1)
{ {
<td>@signUp.LiebUser.Name <td>@signUp.LiebUser.Name
<select value=@signUp.GuildWars2AccountId @onchange="args => ChangeAccount(_user, args)"> <select value=@signUp.GuildWars2AccountId @onchange="args => ChangeAccount(_user, args)">

View file

@ -21,11 +21,13 @@ builder.Services.AddDatabaseDeveloperPageExceptionFilter();
builder.Services.AddServerSideBlazor(); builder.Services.AddServerSideBlazor();
builder.Services.AddScoped<RaidService>(); builder.Services.AddScoped<RaidService>();
builder.Services.AddScoped<RaidTemplateService>();
builder.Services.AddScoped<UserService>(); builder.Services.AddScoped<UserService>();
builder.Services.AddScoped<GuildWars2AccountService>(); builder.Services.AddScoped<GuildWars2AccountService>();
builder.Services.AddScoped<GuildWars2BuildService>(); builder.Services.AddScoped<GuildWars2BuildService>();
builder.Services.AddScoped<RaidRandomizerService>(); builder.Services.AddScoped<RaidRandomizerService>();
builder.Services.AddScoped<TimeZoneService>(); builder.Services.AddScoped<TimeZoneService>();
builder.Services.AddHostedService<TimerService>();
builder.Services.AddAuthentication(opt => builder.Services.AddAuthentication(opt =>