From 99245b9ead0add5fd861216e6c2a5d5925f4984d Mon Sep 17 00:00:00 2001 From: runebaas Date: Wed, 5 Sep 2018 22:55:45 +0200 Subject: [PATCH] Refactor !rank, add HighscoreManager and add /v1/highscore to the api --- Geekbot.net/Commands/User/Ranking/Rank.cs | 112 +++--------------- Geekbot.net/Commands/User/Ranking/RankType.cs | 9 -- Geekbot.net/Database/RedisMigration.cs | 2 +- .../Highscores/HighscoreListEmptyException.cs | 13 ++ .../Lib/Highscores/HighscoreManager.cs | 106 +++++++++++++++++ Geekbot.net/Lib/Highscores/HighscoreTypes.cs | 9 ++ .../Highscores/HighscoreUserDto.cs} | 5 +- .../Lib/Highscores/IHighscoreManager.cs | 12 ++ Geekbot.net/Lib/Logger/LogSource.cs | 1 + Geekbot.net/Program.cs | 7 +- Geekbot.net/WebApi/ApiError.cs | 7 ++ .../Highscores/HighscoreController.cs | 56 +++++++++ .../HighscoreControllerPostBodyDto.cs | 16 +++ .../HighscoreControllerReponseBody.cs | 11 ++ Geekbot.net/WebApi/WebApiStartup.cs | 4 +- 15 files changed, 261 insertions(+), 109 deletions(-) delete mode 100644 Geekbot.net/Commands/User/Ranking/RankType.cs create mode 100644 Geekbot.net/Lib/Highscores/HighscoreListEmptyException.cs create mode 100644 Geekbot.net/Lib/Highscores/HighscoreManager.cs create mode 100644 Geekbot.net/Lib/Highscores/HighscoreTypes.cs rename Geekbot.net/{Commands/User/Ranking/RankUserDto.cs => Lib/Highscores/HighscoreUserDto.cs} (54%) create mode 100644 Geekbot.net/Lib/Highscores/IHighscoreManager.cs create mode 100644 Geekbot.net/WebApi/ApiError.cs create mode 100644 Geekbot.net/WebApi/Controllers/Highscores/HighscoreController.cs create mode 100644 Geekbot.net/WebApi/Controllers/Highscores/HighscoreControllerPostBodyDto.cs create mode 100644 Geekbot.net/WebApi/Controllers/Highscores/HighscoreControllerReponseBody.cs diff --git a/Geekbot.net/Commands/User/Ranking/Rank.cs b/Geekbot.net/Commands/User/Ranking/Rank.cs index 08fd384..9c37a4a 100644 --- a/Geekbot.net/Commands/User/Ranking/Rank.cs +++ b/Geekbot.net/Commands/User/Ranking/Rank.cs @@ -5,31 +5,30 @@ using System.Text; using System.Threading.Tasks; using Discord.Commands; using Geekbot.net.Database; -using Geekbot.net.Lib.AlmostRedis; using Geekbot.net.Lib.Converters; using Geekbot.net.Lib.ErrorHandling; using Geekbot.net.Lib.Extensions; +using Geekbot.net.Lib.Highscores; using Geekbot.net.Lib.UserRepository; -using StackExchange.Redis; namespace Geekbot.net.Commands.User.Ranking { public class Rank : ModuleBase { private readonly IEmojiConverter _emojiConverter; + private readonly IHighscoreManager _highscoreManager; private readonly IErrorHandler _errorHandler; private readonly DatabaseContext _database; private readonly IUserRepository _userRepository; - private readonly IAlmostRedis _redis; public Rank(DatabaseContext database, IErrorHandler errorHandler, IUserRepository userRepository, - IEmojiConverter emojiConverter, IAlmostRedis redis) + IEmojiConverter emojiConverter, IHighscoreManager highscoreManager) { _database = database; _errorHandler = errorHandler; _userRepository = userRepository; _emojiConverter = emojiConverter; - _redis = redis; + _highscoreManager = highscoreManager; } [Command("rank", RunMode = RunMode.Async)] @@ -38,10 +37,10 @@ namespace Geekbot.net.Commands.User.Ranking { try { - RankType type; + HighscoreTypes type; try { - type = Enum.Parse(typeUnformated.ToLower()); + type = Enum.Parse(typeUnformated.ToLower()); } catch { @@ -49,7 +48,6 @@ namespace Geekbot.net.Commands.User.Ranking return; } - var replyBuilder = new StringBuilder(); if (amount > 20) { @@ -57,69 +55,28 @@ namespace Geekbot.net.Commands.User.Ranking amount = 20; } - Dictionary list; - - switch (type) + var guildId = Context.Guild.Id; + Dictionary highscoreUsers; + try { - case RankType.messages: - list = GetMessageList(amount); - break; - case RankType.karma: - list = GetKarmaList(amount); - break; - case RankType.rolls: - list = GetRollsList(amount); - break; - default: - await ReplyAsync("Valid types are '`messages`' '`karma`', '`rolls`'"); - return; + highscoreUsers = _highscoreManager.GetHighscoresWithUserData(type, guildId, amount); } - - if (!list.Any()) + catch (HighscoreListEmptyException) { await ReplyAsync($"No {type} found on this server"); return; } int guildMessages = 0; - if (type == RankType.messages) + if (type == HighscoreTypes.messages) { -// guildMessages = _database.Messages -// .Where(e => e.GuildId.Equals(Context.Guild.Id.AsLong())) -// .Select(e => e.MessageCount) -// .Sum(); - guildMessages = (int) _redis.Db.HashGet($"{Context.Guild.Id}:Messages", 0.ToString()); + guildMessages = _database.Messages + .Where(e => e.GuildId.Equals(Context.Guild.Id.AsLong())) + .Select(e => e.MessageCount) + .Sum(); } - var highscoreUsers = new Dictionary(); - var failedToRetrieveUser = false; - foreach (var user in list) - { - try - { - var guildUser = _userRepository.Get(user.Key); - if (guildUser?.Username != null) - { - highscoreUsers.Add(new RankUserDto - { - Username = guildUser.Username, - Discriminator = guildUser.Discriminator - }, user.Value); - } - else - { - highscoreUsers.Add(new RankUserDto - { - Id = user.Key.ToString() - }, user.Value); - failedToRetrieveUser = true; - } - } - catch - { - // ignore - } - } + var failedToRetrieveUser = highscoreUsers.Any(e => string.IsNullOrEmpty(e.Key.Username)); if (failedToRetrieveUser) replyBuilder.AppendLine(":warning: I couldn't find all usernames. Maybe they left the server?\n"); replyBuilder.AppendLine($":bar_chart: **{type.ToString().CapitalizeFirst()} Highscore for {Context.Guild.Name}**"); @@ -134,7 +91,7 @@ namespace Geekbot.net.Commands.User.Ranking ? $"**{user.Key.Username}#{user.Key.Discriminator}**" : $"**{user.Key.Id}**"); - replyBuilder.Append(type == RankType.messages + replyBuilder.Append(type == HighscoreTypes.messages ? $" - {user.Value} {type} - {Math.Round((double) (100 * user.Value) / guildMessages, digits: 2)}%\n" : $" - {user.Value} {type}\n"); @@ -148,38 +105,5 @@ namespace Geekbot.net.Commands.User.Ranking await _errorHandler.HandleCommandException(e, Context); } } - - private Dictionary GetMessageList(int amount) - { - return _database.Messages - .Where(k => k.GuildId.Equals(Context.Guild.Id.AsLong())) - .OrderByDescending(o => o.MessageCount) - .Take(amount) - .ToDictionary(key => key.UserId.AsUlong(), key => key.MessageCount); -// return _redis.Db -// .HashGetAll($"{Context.Guild.Id}:Messages") -// .Where(user => !user.Name.Equals(0)) -// .OrderByDescending(s => s.Value) -// .Take(amount) -// .ToDictionary(user => ulong.Parse(user.Name), user => int.Parse(user.Value)); - } - - private Dictionary GetKarmaList(int amount) - { - return _database.Karma - .Where(k => k.GuildId.Equals(Context.Guild.Id.AsLong())) - .OrderByDescending(o => o.Karma) - .Take(amount) - .ToDictionary(key => key.UserId.AsUlong(), key => key.Karma); - } - - private Dictionary GetRollsList(int amount) - { - return _database.Rolls - .Where(k => k.GuildId.Equals(Context.Guild.Id.AsLong())) - .OrderByDescending(o => o.Rolls) - .Take(amount) - .ToDictionary(key => key.UserId.AsUlong(), key => key.Rolls); - } } } \ No newline at end of file diff --git a/Geekbot.net/Commands/User/Ranking/RankType.cs b/Geekbot.net/Commands/User/Ranking/RankType.cs deleted file mode 100644 index 1e88ef0..0000000 --- a/Geekbot.net/Commands/User/Ranking/RankType.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace Geekbot.net.Commands.User.Ranking -{ - public enum RankType - { - messages, - karma, - rolls - } -} \ No newline at end of file diff --git a/Geekbot.net/Database/RedisMigration.cs b/Geekbot.net/Database/RedisMigration.cs index 92ee555..74d97d2 100644 --- a/Geekbot.net/Database/RedisMigration.cs +++ b/Geekbot.net/Database/RedisMigration.cs @@ -43,7 +43,7 @@ namespace Geekbot.net.Database Console.WriteLine(g.Name); allGuilds.Add(g); } - catch (Exception e) + catch (Exception) { // ignore } diff --git a/Geekbot.net/Lib/Highscores/HighscoreListEmptyException.cs b/Geekbot.net/Lib/Highscores/HighscoreListEmptyException.cs new file mode 100644 index 0000000..8c05ca9 --- /dev/null +++ b/Geekbot.net/Lib/Highscores/HighscoreListEmptyException.cs @@ -0,0 +1,13 @@ +using System; + +namespace Geekbot.net.Lib.Highscores +{ + public class HighscoreListEmptyException : Exception + { + public HighscoreListEmptyException() {} + + public HighscoreListEmptyException(string message) : base(message) {} + + public HighscoreListEmptyException(string message, Exception inner) : base(message, inner) {} + } +} \ No newline at end of file diff --git a/Geekbot.net/Lib/Highscores/HighscoreManager.cs b/Geekbot.net/Lib/Highscores/HighscoreManager.cs new file mode 100644 index 0000000..c684254 --- /dev/null +++ b/Geekbot.net/Lib/Highscores/HighscoreManager.cs @@ -0,0 +1,106 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Geekbot.net.Database; +using Geekbot.net.Lib.Extensions; +using Geekbot.net.Lib.Logger; +using Geekbot.net.Lib.UserRepository; + +namespace Geekbot.net.Lib.Highscores +{ + public class HighscoreManager : IHighscoreManager + { + private readonly DatabaseContext _database; + private readonly IUserRepository _userRepository; + + public HighscoreManager(DatabaseContext databaseContext, IUserRepository userRepository) + { + _database = databaseContext; + _userRepository = userRepository; + + } + + public Dictionary GetHighscoresWithUserData(HighscoreTypes type, ulong guildId, int amount) + { + Dictionary list; + switch (type) + { + case HighscoreTypes.messages: + list = GetMessageList(guildId, amount); + break; + case HighscoreTypes.karma: + list = GetKarmaList(guildId, amount); + break; + case HighscoreTypes.rolls: + list = GetRollsList(guildId, amount); + break; + default: + list = new Dictionary(); + break; + } + + if (!list.Any()) + { + throw new HighscoreListEmptyException($"No {type} found for guild {guildId}"); + } + + var highscoreUsers = new Dictionary(); + foreach (var user in list) + { + try + { + var guildUser = _userRepository.Get(user.Key); + if (guildUser?.Username != null) + { + highscoreUsers.Add(new HighscoreUserDto + { + Username = guildUser.Username, + Discriminator = guildUser.Discriminator, + Avatar = guildUser.AvatarUrl + }, user.Value); + } + else + { + highscoreUsers.Add(new HighscoreUserDto + { + Id = user.Key.ToString() + }, user.Value); + } + } + catch + { + // ignore + } + } + + return highscoreUsers; + } + + public Dictionary GetMessageList(ulong guildId, int amount) + { + return _database.Messages + .Where(k => k.GuildId.Equals(guildId.AsLong())) + .OrderByDescending(o => o.MessageCount) + .Take(amount) + .ToDictionary(key => key.UserId.AsUlong(), key => key.MessageCount); + } + + public Dictionary GetKarmaList(ulong guildId, int amount) + { + return _database.Karma + .Where(k => k.GuildId.Equals(guildId.AsLong())) + .OrderByDescending(o => o.Karma) + .Take(amount) + .ToDictionary(key => key.UserId.AsUlong(), key => key.Karma); + } + + public Dictionary GetRollsList(ulong guildId, int amount) + { + return _database.Rolls + .Where(k => k.GuildId.Equals(guildId.AsLong())) + .OrderByDescending(o => o.Rolls) + .Take(amount) + .ToDictionary(key => key.UserId.AsUlong(), key => key.Rolls); + } + } +} \ No newline at end of file diff --git a/Geekbot.net/Lib/Highscores/HighscoreTypes.cs b/Geekbot.net/Lib/Highscores/HighscoreTypes.cs new file mode 100644 index 0000000..492d08d --- /dev/null +++ b/Geekbot.net/Lib/Highscores/HighscoreTypes.cs @@ -0,0 +1,9 @@ +namespace Geekbot.net.Lib.Highscores +{ + public enum HighscoreTypes + { + messages, + karma, + rolls + } +} \ No newline at end of file diff --git a/Geekbot.net/Commands/User/Ranking/RankUserDto.cs b/Geekbot.net/Lib/Highscores/HighscoreUserDto.cs similarity index 54% rename from Geekbot.net/Commands/User/Ranking/RankUserDto.cs rename to Geekbot.net/Lib/Highscores/HighscoreUserDto.cs index 2ec518e..7abb352 100644 --- a/Geekbot.net/Commands/User/Ranking/RankUserDto.cs +++ b/Geekbot.net/Lib/Highscores/HighscoreUserDto.cs @@ -1,8 +1,9 @@ -namespace Geekbot.net.Commands.User.Ranking +namespace Geekbot.net.Lib.Highscores { - internal class RankUserDto + public class HighscoreUserDto { public string Username { get; set; } + public string Avatar { get; set; } public string Discriminator { get; set; } public string Id { get; set; } } diff --git a/Geekbot.net/Lib/Highscores/IHighscoreManager.cs b/Geekbot.net/Lib/Highscores/IHighscoreManager.cs new file mode 100644 index 0000000..a09b07e --- /dev/null +++ b/Geekbot.net/Lib/Highscores/IHighscoreManager.cs @@ -0,0 +1,12 @@ +using System.Collections.Generic; + +namespace Geekbot.net.Lib.Highscores +{ + public interface IHighscoreManager + { + Dictionary GetHighscoresWithUserData(HighscoreTypes type, ulong guildId, int amount); + Dictionary GetMessageList(ulong guildId, int amount); + Dictionary GetKarmaList(ulong guildId, int amount); + Dictionary GetRollsList(ulong guildId, int amount); + } +} \ No newline at end of file diff --git a/Geekbot.net/Lib/Logger/LogSource.cs b/Geekbot.net/Lib/Logger/LogSource.cs index 42c1877..7cd92ff 100644 --- a/Geekbot.net/Lib/Logger/LogSource.cs +++ b/Geekbot.net/Lib/Logger/LogSource.cs @@ -17,6 +17,7 @@ namespace Geekbot.net.Lib.Logger Command, Api, Migration, + HighscoreManager, Other } } \ No newline at end of file diff --git a/Geekbot.net/Program.cs b/Geekbot.net/Program.cs index f2a6291..9af8879 100755 --- a/Geekbot.net/Program.cs +++ b/Geekbot.net/Program.cs @@ -7,7 +7,6 @@ using Discord; using Discord.Commands; using Discord.WebSocket; using Geekbot.net.Database; -using Geekbot.net.Database.LoggingAdapter; using Geekbot.net.Lib; using Geekbot.net.Lib.AlmostRedis; using Geekbot.net.Lib.Audio; @@ -15,6 +14,7 @@ using Geekbot.net.Lib.Clients; using Geekbot.net.Lib.Converters; using Geekbot.net.Lib.ErrorHandling; using Geekbot.net.Lib.GlobalSettings; +using Geekbot.net.Lib.Highscores; using Geekbot.net.Lib.Levels; using Geekbot.net.Lib.Localization; using Geekbot.net.Lib.Logger; @@ -40,6 +40,7 @@ namespace Geekbot.net private IUserRepository _userRepository; private RunParameters _runParameters; private IAlmostRedis _redis; + private IHighscoreManager _highscoreManager; private static void Main(string[] args) { @@ -123,6 +124,7 @@ namespace Geekbot.net var mtgManaConverter = new MtgManaConverter(); var wikipediaClient = new WikipediaClient(); var audioUtils = new AudioUtils(); + _highscoreManager = new HighscoreManager(_databaseInitializer.Initialize(), _userRepository); _services.AddSingleton(_redis); _services.AddSingleton(_userRepository); @@ -135,6 +137,7 @@ namespace Geekbot.net _services.AddSingleton(mtgManaConverter); _services.AddSingleton(wikipediaClient); _services.AddSingleton(audioUtils); + _services.AddSingleton(_highscoreManager); _services.AddSingleton(_globalSettings); _services.AddTransient((e) => _databaseInitializer.Initialize()); @@ -203,7 +206,7 @@ namespace Geekbot.net private Task StartWebApi() { _logger.Information(LogSource.Api, "Starting Webserver"); - WebApi.WebApiStartup.StartWebApi(_logger, _runParameters, _commands, _databaseInitializer.Initialize(), _client, _globalSettings); + WebApi.WebApiStartup.StartWebApi(_logger, _runParameters, _commands, _databaseInitializer.Initialize(), _client, _globalSettings, _highscoreManager); return Task.CompletedTask; } } diff --git a/Geekbot.net/WebApi/ApiError.cs b/Geekbot.net/WebApi/ApiError.cs new file mode 100644 index 0000000..182518e --- /dev/null +++ b/Geekbot.net/WebApi/ApiError.cs @@ -0,0 +1,7 @@ +namespace Geekbot.net.WebApi +{ + public class ApiError + { + public string Message { get; set; } + } +} \ No newline at end of file diff --git a/Geekbot.net/WebApi/Controllers/Highscores/HighscoreController.cs b/Geekbot.net/WebApi/Controllers/Highscores/HighscoreController.cs new file mode 100644 index 0000000..3cccb0e --- /dev/null +++ b/Geekbot.net/WebApi/Controllers/Highscores/HighscoreController.cs @@ -0,0 +1,56 @@ +using System.Collections.Generic; +using Geekbot.net.Lib.Highscores; +using Microsoft.AspNetCore.Cors; +using Microsoft.AspNetCore.Mvc; + +namespace Geekbot.net.WebApi.Controllers.Highscores +{ + [EnableCors("AllowSpecificOrigin")] + public class HighscoreController : Controller + { + private readonly IHighscoreManager _highscoreManager; + + public HighscoreController(IHighscoreManager highscoreManager) + { + _highscoreManager = highscoreManager; + } + + [HttpPost] + [Route("/v1/highscore")] + public IActionResult GetHighscores([FromBody] HighscoreControllerPostBodyDto body) + { + if (!ModelState.IsValid || body == null) + { + var error = new SerializableError(ModelState); + return BadRequest(error); + } + + Dictionary list; + try + { + list = _highscoreManager.GetHighscoresWithUserData(body.Type, body.GuildId, body.Amount); + } + catch (HighscoreListEmptyException) + { + return NotFound(new ApiError + { + Message = $"No {body.Type} found on this server" + }); + } + + var response = new List(); + var counter = 1; + foreach (var item in list) + { + response.Add(new HighscoreControllerReponseBody + { + count = item.Value, + rank = counter, + user = item.Key + }); + counter++; + } + return Ok(response); + } + } +} \ No newline at end of file diff --git a/Geekbot.net/WebApi/Controllers/Highscores/HighscoreControllerPostBodyDto.cs b/Geekbot.net/WebApi/Controllers/Highscores/HighscoreControllerPostBodyDto.cs new file mode 100644 index 0000000..22da3c7 --- /dev/null +++ b/Geekbot.net/WebApi/Controllers/Highscores/HighscoreControllerPostBodyDto.cs @@ -0,0 +1,16 @@ +using System.ComponentModel.DataAnnotations; +using Geekbot.net.Lib.Highscores; + +namespace Geekbot.net.WebApi.Controllers.Highscores +{ + public class HighscoreControllerPostBodyDto + { + [Required] + public ulong GuildId { get; set; } + + public HighscoreTypes Type { get; set; } = HighscoreTypes.messages; + + [Range(1, 150)] + public int Amount { get; set; } = 50; + } +} \ No newline at end of file diff --git a/Geekbot.net/WebApi/Controllers/Highscores/HighscoreControllerReponseBody.cs b/Geekbot.net/WebApi/Controllers/Highscores/HighscoreControllerReponseBody.cs new file mode 100644 index 0000000..0e59880 --- /dev/null +++ b/Geekbot.net/WebApi/Controllers/Highscores/HighscoreControllerReponseBody.cs @@ -0,0 +1,11 @@ +using Geekbot.net.Lib.Highscores; + +namespace Geekbot.net.WebApi.Controllers.Highscores +{ + public class HighscoreControllerReponseBody + { + public int rank { get; set; } + public HighscoreUserDto user { get; set; } + public int count { get; set; } + } +} \ No newline at end of file diff --git a/Geekbot.net/WebApi/WebApiStartup.cs b/Geekbot.net/WebApi/WebApiStartup.cs index e93a8d5..2459362 100644 --- a/Geekbot.net/WebApi/WebApiStartup.cs +++ b/Geekbot.net/WebApi/WebApiStartup.cs @@ -5,6 +5,7 @@ using Discord.WebSocket; using Geekbot.net.Database; using Geekbot.net.Lib; using Geekbot.net.Lib.GlobalSettings; +using Geekbot.net.Lib.Highscores; using Geekbot.net.Lib.Logger; using Geekbot.net.WebApi.Logging; using Microsoft.AspNetCore; @@ -18,7 +19,7 @@ namespace Geekbot.net.WebApi public static class WebApiStartup { public static void StartWebApi(IGeekbotLogger logger, RunParameters runParameters, CommandService commandService, - DatabaseContext databaseContext, DiscordSocketClient client, IGlobalSettings globalSettings) + DatabaseContext databaseContext, DiscordSocketClient client, IGlobalSettings globalSettings, IHighscoreManager highscoreManager) { WebHost.CreateDefaultBuilder() .UseKestrel(options => @@ -32,6 +33,7 @@ namespace Geekbot.net.WebApi services.AddSingleton(databaseContext); services.AddSingleton(client); services.AddSingleton(globalSettings); + services.AddSingleton(highscoreManager); services.AddCors(options => { options.AddPolicy("AllowSpecificOrigin",