diff --git a/src/HonzaBotner.Database/HonzaBotnerDbContext.cs b/src/HonzaBotner.Database/HonzaBotnerDbContext.cs index bbc34d90..7618f703 100644 --- a/src/HonzaBotner.Database/HonzaBotnerDbContext.cs +++ b/src/HonzaBotner.Database/HonzaBotnerDbContext.cs @@ -27,4 +27,5 @@ protected override void OnModelCreating(ModelBuilder builder) public DbSet RoleBindings { get; set; } public DbSet Warnings { get; set; } public DbSet Reminders { get; set; } + public DbSet StandUpStats { get; set; } } diff --git a/src/HonzaBotner.Database/StandUpStat.cs b/src/HonzaBotner.Database/StandUpStat.cs new file mode 100644 index 00000000..5ff7687f --- /dev/null +++ b/src/HonzaBotner.Database/StandUpStat.cs @@ -0,0 +1,17 @@ +using System; +using System.ComponentModel.DataAnnotations; + +namespace HonzaBotner.Database; + +public class StandUpStat +{ + [Key] public ulong UserId { get; set; } + public int Streak { get; set; } + public int LongestStreak { get; set; } + public int Freezes { get; set; } + public DateTime LastDayOfStreak { get; set; } + public int LastDayCompleted { get; set; } + public int LastDayTasks { get; set; } + public int TotalCompleted { get; set; } + public int TotalTasks { get; set; } +} diff --git a/src/HonzaBotner.Discord.Services/EventHandlers/StandUpHandler.cs b/src/HonzaBotner.Discord.Services/EventHandlers/StandUpHandler.cs new file mode 100644 index 00000000..ac199c8a --- /dev/null +++ b/src/HonzaBotner.Discord.Services/EventHandlers/StandUpHandler.cs @@ -0,0 +1,71 @@ +using System; +using System.Threading.Tasks; +using DSharpPlus; +using DSharpPlus.Entities; +using DSharpPlus.EventArgs; +using HonzaBotner.Discord.EventHandler; +using HonzaBotner.Discord.Services.Options; +using HonzaBotner.Services.Contract; +using HonzaBotner.Services.Contract.Dto; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; + +namespace HonzaBotner.Discord.Services.EventHandlers; + +public class StandUpHandler : IEventHandler +{ + + private readonly ButtonOptions _buttonOptions; + private readonly IStandUpStatsService _standUpStats; + private readonly ILogger _logger; + + public StandUpHandler( + IOptions buttonOptions, + IStandUpStatsService standUpStats, + ILogger logger) + { + _buttonOptions = buttonOptions.Value; + _standUpStats = standUpStats; + _logger = logger; + } + + public async Task Handle(ComponentInteractionCreateEventArgs args) + { + if (args.Id != _buttonOptions.StandUpStatsId) + { + return EventHandlerResult.Continue; + } + + _logger.LogDebug("{User} requested stats info", args.User.Username); + + StandUpStat? stats = await _standUpStats.GetStreak(args.User.Id); + + DiscordInteractionResponseBuilder response = new(); + response.AsEphemeral(true); + DiscordEmbedBuilder embed = new DiscordEmbedBuilder().WithAuthor("Stats", iconUrl: args.User.AvatarUrl); + + if (stats is null) + { + embed.Description = "Unfortunately you are not in the database yet.\nDatabase updates daily at 8 am"; + embed.Color = new Optional(DiscordColor.Gold); + response.AddEmbed(embed.Build()); + + await args.Interaction.CreateResponseAsync(InteractionResponseType.ChannelMessageWithSource, response); + return EventHandlerResult.Stop; + } + + embed.Description = $"Cool stats for {args.User.Mention}"; + embed.AddField("Current streak", stats.Streak.ToString(), true); + embed.AddField("Available freezes", stats.Freezes.ToString(), true); + embed.AddField("Total tasks", stats.TotalTasks.ToString(), true); + embed.AddField("Total completed tasks", stats.TotalCompleted.ToString(), true); + embed.AddField("Last streak update", + ""); + embed.Color = new Optional(DiscordColor.Wheat); + + response.AddEmbed(embed.Build()); + await args.Interaction.CreateResponseAsync(InteractionResponseType.ChannelMessageWithSource, response); + return EventHandlerResult.Stop; + } + +} diff --git a/src/HonzaBotner.Discord.Services/Jobs/StandUpJobProvider.cs b/src/HonzaBotner.Discord.Services/Jobs/StandUpJobProvider.cs index 9b65f47b..bd83e91b 100644 --- a/src/HonzaBotner.Discord.Services/Jobs/StandUpJobProvider.cs +++ b/src/HonzaBotner.Discord.Services/Jobs/StandUpJobProvider.cs @@ -4,10 +4,12 @@ using System.Text.RegularExpressions; using System.Threading; using System.Threading.Tasks; +using DSharpPlus; using DSharpPlus.Entities; using HonzaBotner.Discord.Services.Helpers; using HonzaBotner.Discord.Services.Options; using HonzaBotner.Scheduler.Contract; +using HonzaBotner.Services.Contract; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; @@ -20,17 +22,24 @@ public class StandUpJobProvider : IJob private readonly DiscordWrapper _discord; - private readonly CommonCommandOptions _commonOptions; + private readonly StandUpOptions _standUpOptions; + private readonly ButtonOptions _buttonOptions; + + private readonly IStandUpStatsService _statsService; public StandUpJobProvider( ILogger logger, DiscordWrapper discord, - IOptions commonOptions + IOptions standUpOptions, + IStandUpStatsService statsService, + IOptions buttonOptions ) { _logger = logger; _discord = discord; - _commonOptions = commonOptions.Value; + _standUpOptions = standUpOptions.Value; + _statsService = statsService; + _buttonOptions = buttonOptions.Value; } /// @@ -44,7 +53,8 @@ IOptions commonOptions /// [] - normal /// []! - critical /// - private static readonly Regex Regex = new(@"^ *\[ *(?\S*) *\] *(?[!])?", RegexOptions.Multiline); + private static readonly Regex TaskRegex = new(@"^ *\[ *(?\S*) *\] *(?[!])?", + RegexOptions.Multiline); private static readonly List OkList = new() { "check", "done", "ok", "✅", "x" }; @@ -52,24 +62,23 @@ IOptions commonOptions public async Task ExecuteAsync(CancellationToken cancellationToken) { - var today = DateTime.Today; // Fix one point in time. - DateTime yesterday = today.AddDays(-1); + DateTime yesterday = DateTime.Today.AddDays(-1); try { - DiscordChannel channel = await _discord.Client.GetChannelAsync(_commonOptions.StandUpChannelId); + DiscordChannel channel = await _discord.Client.GetChannelAsync(_standUpOptions.StandUpChannelId); var ok = new StandUpStats(); var fail = new StandUpStats(); - List messageList = new(); - messageList.AddRange(await channel.GetMessagesAsync()); + List messageList = (await channel.GetMessagesAsync()) + .Where(msg => !msg.Author.IsBot).ToList(); while (messageList.LastOrDefault()?.Timestamp.Date == yesterday) { int messagesCount = messageList.Count; messageList.AddRange( - await channel.GetMessagesBeforeAsync(messageList.Last().Id) + (await channel.GetMessagesBeforeAsync(messageList.Last().Id)).Where(msg => !msg.Author.IsBot) ); // No new data. @@ -79,34 +88,58 @@ await channel.GetMessagesBeforeAsync(messageList.Last().Id) } } - foreach (DiscordMessage msg in messageList.Where(msg => msg.Timestamp.Date == yesterday)) + foreach (var authorGrouped in messageList.Where(msg => msg.Timestamp.Date == yesterday) + .GroupBy(msg => msg.Author.Id)) { - foreach (Match match in Regex.Matches(msg.Content)) + int total = 0; + int completed = 0; + + foreach (var msg in authorGrouped) { - string state = match.Groups["State"].ToString(); - string priority = match.Groups["Priority"].ToString(); - if (OkList.Any(s => state.Contains(s))) + foreach (Match match in TaskRegex.Matches(msg.Content)) { - ok.Increment(priority); - } - else - { - fail.Increment(priority); + total++; + string state = match.Groups["State"].Value; + string priority = match.Groups["Priority"].Value; + + if (OkList.Any(s => state.Contains(s))) + { + completed++; + ok.Increment(priority); + } + else + { + fail.Increment(priority); + } } } + + // Update DB. + if (total != 0) + { + await _statsService.UpdateStats(authorGrouped.Key, completed, total); + } } - await channel.SendMessageAsync($@" -Stand-up time, <@&{_commonOptions.StandUpRoleId}>! + // Send stats message to channel. + DiscordMessageBuilder message = new DiscordMessageBuilder().WithContent($@" +Stand-up time, <@&{_standUpOptions.StandUpRoleId}>! -Results from : +Results from : ``` all: {ok.Add(fail)} completed: {ok} failed: {fail} ``` "); + if (_buttonOptions.StandUpStatsId is not null) + { + message.AddComponents(new DiscordButtonComponent(ButtonStyle.Primary, _buttonOptions.StandUpStatsId, + "Get your stats", false, new DiscordComponentEmoji(DiscordEmoji.FromUnicode("🐸")))); + } + + await message.SendAsync(channel); } catch (Exception e) { diff --git a/src/HonzaBotner.Discord.Services/Options/ButtonOptions.cs b/src/HonzaBotner.Discord.Services/Options/ButtonOptions.cs index 4980f914..8befad64 100644 --- a/src/HonzaBotner.Discord.Services/Options/ButtonOptions.cs +++ b/src/HonzaBotner.Discord.Services/Options/ButtonOptions.cs @@ -7,5 +7,6 @@ public class ButtonOptions public string? VerificationId { get; set; } public string? StaffVerificationId { get; set; } public string? StaffRemoveRoleId { get; set; } + public string? StandUpStatsId { get; set; } public ulong[]? CzechChannelsIds { get; set; } } diff --git a/src/HonzaBotner.Discord.Services/Options/CommonCommandOptions.cs b/src/HonzaBotner.Discord.Services/Options/CommonCommandOptions.cs index 6642e8c7..e6bd1bb7 100644 --- a/src/HonzaBotner.Discord.Services/Options/CommonCommandOptions.cs +++ b/src/HonzaBotner.Discord.Services/Options/CommonCommandOptions.cs @@ -21,8 +21,5 @@ public class CommonCommandOptions public ulong[]? ReactionIgnoreChannels { get; set; } - public ulong StandUpRoleId { get; set; } - public ulong StandUpChannelId { get; set; } - public ulong[] MemberCountAllowlistIds { get; set; } = System.Array.Empty(); } diff --git a/src/HonzaBotner.Discord.Services/Options/StandUpOptions.cs b/src/HonzaBotner.Discord.Services/Options/StandUpOptions.cs new file mode 100644 index 00000000..4d7bbb53 --- /dev/null +++ b/src/HonzaBotner.Discord.Services/Options/StandUpOptions.cs @@ -0,0 +1,12 @@ +namespace HonzaBotner.Discord.Services.Options; + +public class StandUpOptions +{ + public static string ConfigName => "StandUpOptions"; + + public ulong StandUpRoleId { get; set; } + public ulong StandUpChannelId { get; set; } + + public int DaysToAcquireFreeze { get; set; } + public int TasksCompletedThreshold { get; set; } +} diff --git a/src/HonzaBotner.Discord.Services/ServiceCollectionExtensions.cs b/src/HonzaBotner.Discord.Services/ServiceCollectionExtensions.cs index 2ebd1992..01cc675d 100644 --- a/src/HonzaBotner.Discord.Services/ServiceCollectionExtensions.cs +++ b/src/HonzaBotner.Discord.Services/ServiceCollectionExtensions.cs @@ -14,6 +14,7 @@ public static IServiceCollection AddCommandOptions(this IServiceCollection servi services.Configure(configuration.GetSection(PinOptions.ConfigName)); services.Configure(configuration.GetSection(InfoOptions.ConfigName)); services.Configure(configuration.GetSection(ReminderOptions.ConfigName)); + services.Configure(configuration.GetSection(StandUpOptions.ConfigName)); services.Configure(configuration.GetSection(ButtonOptions.ConfigName)); services.Configure(configuration.GetSection(BadgeRoleOptions.ConfigName)); diff --git a/src/HonzaBotner.Services.Contract/Dto/StandUpStat.cs b/src/HonzaBotner.Services.Contract/Dto/StandUpStat.cs new file mode 100644 index 00000000..4a2aa3bf --- /dev/null +++ b/src/HonzaBotner.Services.Contract/Dto/StandUpStat.cs @@ -0,0 +1,11 @@ +using System; + +namespace HonzaBotner.Services.Contract.Dto; + +public record StandUpStat( + ulong UserId, int Streak, + int LongestStreak, int Freezes, + DateTime LastDayOfStreak, int TotalCompleted, + int TotalTasks, int LastDayCompleted, + int LastDayTasks +); diff --git a/src/HonzaBotner.Services.Contract/IStandUpStatsService.cs b/src/HonzaBotner.Services.Contract/IStandUpStatsService.cs new file mode 100644 index 00000000..d51e3114 --- /dev/null +++ b/src/HonzaBotner.Services.Contract/IStandUpStatsService.cs @@ -0,0 +1,21 @@ +using System.Threading.Tasks; +using HonzaBotner.Services.Contract.Dto; +namespace HonzaBotner.Services.Contract; + +public interface IStandUpStatsService +{ + /// + /// Get StandUp stats for a user with a given userId + /// + /// Id of the user + /// Null if user not in database, else his stats + Task GetStreak(ulong userId); + + /// + /// Update database record of given user regarding StandUp stats. Should be called just once per day per user. + /// + /// Id of the user + /// Amount of completed tasks yesterday + /// Total amount of tasks yesterday + Task UpdateStats(ulong userId, int completed, int total); +} diff --git a/src/HonzaBotner.Services.Test/HonzaBotner.Services.Test.csproj b/src/HonzaBotner.Services.Test/HonzaBotner.Services.Test.csproj index da3b6f92..82ffde28 100644 --- a/src/HonzaBotner.Services.Test/HonzaBotner.Services.Test.csproj +++ b/src/HonzaBotner.Services.Test/HonzaBotner.Services.Test.csproj @@ -18,7 +18,6 @@ - diff --git a/src/HonzaBotner.Services/HonzaBotner.Services.csproj b/src/HonzaBotner.Services/HonzaBotner.Services.csproj index 9429c890..108134e7 100644 --- a/src/HonzaBotner.Services/HonzaBotner.Services.csproj +++ b/src/HonzaBotner.Services/HonzaBotner.Services.csproj @@ -8,6 +8,7 @@ + diff --git a/src/HonzaBotner.Services/ServiceCollectionExtensions.cs b/src/HonzaBotner.Services/ServiceCollectionExtensions.cs index a4a84f76..3ef779a9 100644 --- a/src/HonzaBotner.Services/ServiceCollectionExtensions.cs +++ b/src/HonzaBotner.Services/ServiceCollectionExtensions.cs @@ -31,6 +31,7 @@ public static IServiceCollection AddBotnerServices(this IServiceCollection servi serviceCollection.AddScoped(); serviceCollection.AddScoped(); serviceCollection.AddScoped(); + serviceCollection.AddScoped(); return serviceCollection; } diff --git a/src/HonzaBotner.Services/StandUpStatsService.cs b/src/HonzaBotner.Services/StandUpStatsService.cs new file mode 100644 index 00000000..23de6b4c --- /dev/null +++ b/src/HonzaBotner.Services/StandUpStatsService.cs @@ -0,0 +1,142 @@ +using System; +using System.Threading.Tasks; +using HonzaBotner.Database; +using HonzaBotner.Discord.Services.Options; +using HonzaBotner.Services.Contract; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; +using StandUpStat = HonzaBotner.Services.Contract.Dto.StandUpStat; + +namespace HonzaBotner.Services; + +public class StandUpStatsService : IStandUpStatsService +{ + private readonly HonzaBotnerDbContext _dbContext; + private readonly ILogger _logger; + private readonly StandUpOptions _standUpOptions; + + public StandUpStatsService( + HonzaBotnerDbContext dbContext, + ILogger logger, + IOptions standUpOptions + ) + { + _dbContext = dbContext; + _logger = logger; + _standUpOptions = standUpOptions.Value; + } + + public async Task GetStreak(ulong userId) + { + Database.StandUpStat? standUpStat = await _dbContext.StandUpStats + .FirstOrDefaultAsync(streak => streak.UserId == userId); + + return standUpStat == null ? null : GetDto(standUpStat); + } + + private Database.StandUpStat UpdateStreak(Database.StandUpStat streak, bool streakMaintained) + { + int days = (DateTime.Today.AddDays(-2) - streak.LastDayOfStreak).Days; + + // Tasks completed and on time + if (streakMaintained && days <= streak.Freezes) + { + streak.Streak++; + // streak.Streak += days + 1; // Alternative in case we want to count frozen days in streak + streak.LastDayOfStreak = DateTime.Today.AddDays(-1).ToUniversalTime(); + streak.Freezes -= days; + + if (streak.Streak > streak.LongestStreak) + { + streak.LongestStreak = streak.Streak; + } + } + // Tasks completed, but reset streak + else if (streakMaintained) + { + streak.Streak = 1; + streak.LastDayOfStreak = DateTime.Today.AddDays(-1).ToUniversalTime(); + streak.Freezes = 0; + } + // Not valid, and lateeeeeeeeeee + else if (days >= streak.Freezes) + { + streak.Streak = 0; + streak.Freezes = 0; + } + // Not valid && on time is ignored, in that case nothing happens + + return streak; + } + + public async Task UpdateStats(ulong userId, int completed, int total) + { + bool streakMaintained = completed >= _standUpOptions.TasksCompletedThreshold; + + Database.StandUpStat? stat = await _dbContext.StandUpStats + .FirstOrDefaultAsync(streak => streak.UserId == userId); + + if (stat is null) + { + Database.StandUpStat newStat = new() + { + UserId = userId, + Freezes = 0, + LastDayOfStreak = + streakMaintained + ? DateTime.Today.AddDays(-1).ToUniversalTime() + : DateTime.UnixEpoch.ToUniversalTime(), + Streak = streakMaintained ? 1 : 0, + LongestStreak = streakMaintained ? 1 : 0, + LastDayCompleted = completed, + LastDayTasks = total, + TotalCompleted = completed, + TotalTasks = total + }; + + await _dbContext.StandUpStats.AddAsync(newStat); + + try + { + await _dbContext.SaveChangesAsync(); + } + catch (Exception e) + { + _logger.LogError(e, "Couldn't add streak {@Stat}", stat); + } + + return; + } + + stat = UpdateStreak(stat, streakMaintained); + + stat.LastDayCompleted = completed; + stat.LastDayTasks = total; + stat.TotalCompleted += completed; + stat.TotalTasks += total; + + try + { + _dbContext.StandUpStats.Update(stat); + await _dbContext.SaveChangesAsync(); + } + catch (Exception e) + { + _logger.LogError(e, "Couldn't update streak {@Stat}", stat); + } + } + + static StandUpStat GetDto(Database.StandUpStat standUpStat) => + new( + standUpStat.UserId, + standUpStat.Streak, + standUpStat.LongestStreak, + standUpStat.Freezes, + standUpStat.LastDayOfStreak, + standUpStat.TotalCompleted, + standUpStat.TotalTasks, + standUpStat.LastDayCompleted, + standUpStat.LastDayTasks + ); +} diff --git a/src/HonzaBotner/Migrations/20220521212254_StandUpStats.Designer.cs b/src/HonzaBotner/Migrations/20220521212254_StandUpStats.Designer.cs new file mode 100644 index 00000000..66e2a474 --- /dev/null +++ b/src/HonzaBotner/Migrations/20220521212254_StandUpStats.Designer.cs @@ -0,0 +1,172 @@ +// +using System; +using HonzaBotner.Database; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +#nullable disable + +namespace HonzaBotner.Migrations +{ + [DbContext(typeof(HonzaBotnerDbContext))] + [Migration("20220521212254_StandUpStats")] + partial class StandUpStats + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "6.0.5") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("HonzaBotner.Database.CountedEmoji", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("numeric(20,0)"); + + b.Property("FirstUsedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Times") + .HasColumnType("numeric(20,0)"); + + b.HasKey("Id"); + + b.ToTable("CountedEmojis"); + }); + + modelBuilder.Entity("HonzaBotner.Database.Reminder", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("ChannelId") + .HasColumnType("numeric(20,0)"); + + b.Property("Content") + .HasColumnType("text"); + + b.Property("DateTime") + .HasColumnType("timestamp with time zone"); + + b.Property("MessageId") + .HasColumnType("numeric(20,0)"); + + b.Property("OwnerId") + .HasColumnType("numeric(20,0)"); + + b.HasKey("Id"); + + b.HasIndex("DateTime"); + + b.ToTable("Reminders"); + }); + + modelBuilder.Entity("HonzaBotner.Database.RoleBinding", b => + { + b.Property("Emoji") + .HasColumnType("text"); + + b.Property("ChannelId") + .HasColumnType("numeric(20,0)"); + + b.Property("MessageId") + .HasColumnType("numeric(20,0)"); + + b.Property("RoleId") + .HasColumnType("numeric(20,0)"); + + b.HasKey("Emoji", "ChannelId", "MessageId", "RoleId"); + + b.ToTable("RoleBindings"); + }); + + modelBuilder.Entity("HonzaBotner.Database.StandUpStat", b => + { + b.Property("UserId") + .ValueGeneratedOnAdd() + .HasColumnType("numeric(20,0)"); + + b.Property("Freezes") + .HasColumnType("integer"); + + b.Property("LastDayCompleted") + .HasColumnType("integer"); + + b.Property("LastDayOfStreak") + .HasColumnType("timestamp with time zone"); + + b.Property("LastDayTasks") + .HasColumnType("integer"); + + b.Property("LongestStreak") + .HasColumnType("integer"); + + b.Property("Streak") + .HasColumnType("integer"); + + b.Property("TotalCompleted") + .HasColumnType("integer"); + + b.Property("TotalTasks") + .HasColumnType("integer"); + + b.HasKey("UserId"); + + b.ToTable("StandUpStats"); + }); + + modelBuilder.Entity("HonzaBotner.Database.Verification", b => + { + b.Property("UserId") + .ValueGeneratedOnAdd() + .HasColumnType("numeric(20,0)"); + + b.Property("AuthId") + .HasColumnType("text"); + + b.HasKey("UserId"); + + b.HasIndex("AuthId") + .IsUnique(); + + b.ToTable("Verifications"); + }); + + modelBuilder.Entity("HonzaBotner.Database.Warning", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("IssuedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("IssuerId") + .HasColumnType("numeric(20,0)"); + + b.Property("Reason") + .HasColumnType("text"); + + b.Property("UserId") + .HasColumnType("numeric(20,0)"); + + b.HasKey("Id"); + + b.ToTable("Warnings"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/HonzaBotner/Migrations/20220521212254_StandUpStats.cs b/src/HonzaBotner/Migrations/20220521212254_StandUpStats.cs new file mode 100644 index 00000000..9d3b5316 --- /dev/null +++ b/src/HonzaBotner/Migrations/20220521212254_StandUpStats.cs @@ -0,0 +1,38 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace HonzaBotner.Migrations +{ + public partial class StandUpStats : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "StandUpStats", + columns: table => new + { + UserId = table.Column(type: "numeric(20,0)", nullable: false), + Streak = table.Column(type: "integer", nullable: false), + LongestStreak = table.Column(type: "integer", nullable: false), + Freezes = table.Column(type: "integer", nullable: false), + LastDayOfStreak = table.Column(type: "timestamp with time zone", nullable: false), + LastDayCompleted = table.Column(type: "integer", nullable: false), + LastDayTasks = table.Column(type: "integer", nullable: false), + TotalCompleted = table.Column(type: "integer", nullable: false), + TotalTasks = table.Column(type: "integer", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_StandUpStats", x => x.UserId); + }); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "StandUpStats"); + } + } +} diff --git a/src/HonzaBotner/Migrations/ApplicationDbContextModelSnapshot.cs b/src/HonzaBotner/Migrations/ApplicationDbContextModelSnapshot.cs index 47f71d51..6177f2f8 100644 --- a/src/HonzaBotner/Migrations/ApplicationDbContextModelSnapshot.cs +++ b/src/HonzaBotner/Migrations/ApplicationDbContextModelSnapshot.cs @@ -17,7 +17,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) { #pragma warning disable 612, 618 modelBuilder - .HasAnnotation("ProductVersion", "6.0.0") + .HasAnnotation("ProductVersion", "6.0.5") .HasAnnotation("Relational:MaxIdentifierLength", 63); NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); @@ -88,6 +88,41 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.ToTable("RoleBindings"); }); + modelBuilder.Entity("HonzaBotner.Database.StandUpStat", b => + { + b.Property("UserId") + .ValueGeneratedOnAdd() + .HasColumnType("numeric(20,0)"); + + b.Property("Freezes") + .HasColumnType("integer"); + + b.Property("LastDayCompleted") + .HasColumnType("integer"); + + b.Property("LastDayOfStreak") + .HasColumnType("timestamp with time zone"); + + b.Property("LastDayTasks") + .HasColumnType("integer"); + + b.Property("LongestStreak") + .HasColumnType("integer"); + + b.Property("Streak") + .HasColumnType("integer"); + + b.Property("TotalCompleted") + .HasColumnType("integer"); + + b.Property("TotalTasks") + .HasColumnType("integer"); + + b.HasKey("UserId"); + + b.ToTable("StandUpStats"); + }); + modelBuilder.Entity("HonzaBotner.Database.Verification", b => { b.Property("UserId") diff --git a/src/HonzaBotner/Startup.cs b/src/HonzaBotner/Startup.cs index 877b897a..614ed42b 100644 --- a/src/HonzaBotner/Startup.cs +++ b/src/HonzaBotner/Startup.cs @@ -84,6 +84,7 @@ public void ConfigureServices(IServiceCollection services) .AddEventHandler(EventHandlerPriority.Urgent) .AddEventHandler() .AddEventHandler() + .AddEventHandler() ; } ) diff --git a/src/HonzaBotner/appsettings.BotDev.json b/src/HonzaBotner/appsettings.BotDev.json index 37c5a305..62dfa477 100644 --- a/src/HonzaBotner/appsettings.BotDev.json +++ b/src/HonzaBotner/appsettings.BotDev.json @@ -26,9 +26,13 @@ "GentlemenFilePath": "Static/gentleman.gif", "HornyJailRoleId": 0, "HornyJailChannelId": 0, - "HornyJailFilePath": "Static/hornyjail.mp4", + "HornyJailFilePath": "Static/hornyjail.mp4" + }, + "StandUpOptions": { "StandUpRoleId": 0, - "StandUpChannelId": 0 + "StandUpChannelId": 0, + "TasksCompletedThreshold": 1, + "DaysToAcquireFreeze": 6 }, "CommandAllowlistsOptions": { "MemberCount": [ @@ -70,7 +74,8 @@ "ButtonOptions": { "VerificationId": "verification-user", "StaffVerificationId": "verification-staff", - "StaffRemoveRoleId": "verification-remove-staff" + "StaffRemoveRoleId": "verification-remove-staff", + "StandUpStatsId": "standup-stats" }, "BadgeRoleOptions": { "TriggerRoles": [ diff --git a/src/HonzaBotner/appsettings.CvutFit.json b/src/HonzaBotner/appsettings.CvutFit.json index 3db7a58b..7695e612 100644 --- a/src/HonzaBotner/appsettings.CvutFit.json +++ b/src/HonzaBotner/appsettings.CvutFit.json @@ -33,9 +33,13 @@ "ReactionIgnoreChannels": [ 691998530151120936, 774247506669600798 - ], + ] + }, + "StandUpOptions": { "StandUpRoleId": 972019490818768927, - "StandUpChannelId": 972019138446897162 + "StandUpChannelId": 972019138446897162, + "TasksCompletedThreshold": 1, + "DaysToAcquireFreeze": 6 }, "CommandAllowlistsOptions": { "MemberCount": [ @@ -110,6 +114,7 @@ "VerificationId": "verification-user", "StaffVerificationId": "verification-staff", "StaffRemoveRoleId": "verification-remove-staff", + "StandUpStatsId": "standup-stats", "CzechChannelsIds": [507515506073403402] }, "BadgeRoleOptions": { diff --git a/src/HonzaBotner/appsettings.Development.json b/src/HonzaBotner/appsettings.Development.json index da4a2734..70221566 100644 --- a/src/HonzaBotner/appsettings.Development.json +++ b/src/HonzaBotner/appsettings.Development.json @@ -29,9 +29,13 @@ "GentlemenFilePath": "Static/gentleman.gif", "HornyJailRoleId": 760050336366329856, "HornyJailChannelId": 819986928928686080, - "HornyJailFilePath": "Static/hornyjail.mp4", + "HornyJailFilePath": "Static/hornyjail.mp4" + }, + "StandUpOptions": { "StandUpRoleId": 972123386094440498, - "StandUpChannelId": 750108543125946448 + "StandUpChannelId": 750108543125946448, + "TasksCompletedThreshold": 1, + "DaysToAcquireFreeze": 6 }, "CommandAllowlistsOptions": { "MemberCount": [ @@ -81,6 +85,7 @@ "VerificationId": "verification-user", "StaffVerificationId": "verification-staff", "StaffRemoveRoleId": "verification-remove-staff", + "StandUpStatsId": "standup-stats", "CzechChannelsIds": [750108543125946448] }, "BadgeRoleOptions": {