using IW4MAdmin.Plugins.Stats; using IW4MAdmin.Plugins.Stats.Helpers; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; using SharedLibraryCore; using SharedLibraryCore.Dtos; using SharedLibraryCore.Dtos.Meta.Responses; using SharedLibraryCore.Interfaces; using Stats.Dtos; using System; using System.Collections.Generic; using System.Linq; using System.Threading; using System.Threading.Tasks; using Microsoft.Extensions.Logging; using ILogger = Microsoft.Extensions.Logging.ILogger; using Data.Abstractions; using Stats.Config; namespace IW4MAdmin.Plugins.Web.StatsWeb.Controllers { public class StatsController : BaseController { private readonly ILogger _logger; private readonly IManager _manager; private readonly IResourceQueryHelper _chatResourceQueryHelper; private readonly ITranslationLookup _translationLookup; private readonly IDatabaseContextFactory _contextFactory; private readonly StatsConfiguration _config; private readonly IServerDataViewer _serverDataViewer; private readonly StatManager _statManager; public StatsController(ILogger logger, IManager manager, IResourceQueryHelper resourceQueryHelper, ITranslationLookup translationLookup, IDatabaseContextFactory contextFactory, StatsConfiguration config, IServerDataViewer serverDataViewer, StatManager statManager) : base(manager) { _logger = logger; _manager = manager; _chatResourceQueryHelper = resourceQueryHelper; _translationLookup = translationLookup; _contextFactory = contextFactory; _config = config; _serverDataViewer = serverDataViewer; _statManager = statManager; } [HttpGet] public async Task TopPlayers(string serverId = null, CancellationToken token = default) { ViewBag.Title = Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_STATS_INDEX_TITLE"]; ViewBag.Description = Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_STATS_INDEX_DESC"]; ViewBag.Localization = _translationLookup; ViewBag.SelectedServerId = serverId; var server = _manager.GetServers().FirstOrDefault(server => server.Id == serverId) as IGameServer; long? matchedServerId = null; if (server != null) { matchedServerId = server.LegacyDatabaseId; } ViewBag.TotalRankedClients = await _serverDataViewer.RankedClientsCountAsync(matchedServerId, token); ViewBag.ServerId = matchedServerId; return View("~/Views/Client/Statistics/Index.cshtml", _manager.GetServers() .Select(selectedServer => new ServerInfo { Name = selectedServer.Hostname, IPAddress = selectedServer.ListenAddress, Port = selectedServer.ListenPort, Game = selectedServer.GameCode })); } [HttpGet] public async Task GetTopPlayersAsync(int count, int offset, long? serverId = null) { // this prevents empty results when we really want aggregate if (serverId == 0) { serverId = null; } if (_manager.GetServers().FirstOrDefault(activeServer => activeServer.EndPoint == serverId) is IGameServer server) { serverId = server.LegacyDatabaseId; } var results = _config?.EnableAdvancedMetrics ?? true ? await _statManager.GetNewTopStats(offset, count, serverId) : await _statManager.GetTopStats(offset, count, serverId); // this returns an empty result so we know to stale the loader if (results.Count == 0 && offset > 0) { return Ok(); } ViewBag.UseNewStats = _config?.EnableAdvancedMetrics; return View("~/Views/Client/Statistics/Components/TopPlayers/_List.cshtml", results); } [HttpGet] public async Task GetMessageAsync(string serverId, long when) { var whenTime = DateTime.FromFileTimeUtc(when); var whenUpper = whenTime.AddMinutes(5); var whenLower = whenTime.AddMinutes(-5); var messages = await _chatResourceQueryHelper.QueryResource(new ChatSearchQuery { ServerId = serverId, SentBefore = whenUpper, SentAfter = whenLower }); return View("~/Views/Client/_MessageContext.cshtml", messages.Results.OrderBy(message => message.When).ToList()); } [HttpGet("Message/Find")] public async Task FindMessage([FromQuery] string query) { ViewBag.Localization = _translationLookup; ViewBag.EnableColorCodes = _manager.GetApplicationSettings().Configuration().EnableColorCodes; ViewBag.Query = query; ViewBag.QueryLimit = 100; ViewBag.Title = _translationLookup["WEBFRONT_STATS_MESSAGES_TITLE"]; ViewBag.Error = null; ViewBag.IsFluid = true; ChatSearchQuery searchRequest = null; try { searchRequest = query.ParseSearchInfo(int.MaxValue, 0); } catch (ArgumentException e) { _logger.LogWarning(e, "Could not parse chat message search query {query}", query); ViewBag.Error = e; } catch (FormatException e) { _logger.LogWarning(e, "Could not parse chat message search query filter format {query}", query); ViewBag.Error = e; } var result = searchRequest != null ? await _chatResourceQueryHelper.QueryResource(searchRequest) : null; return View("~/Views/Client/Message/Find.cshtml", result); } [HttpGet("Message/FindNext")] public async Task FindNextMessages([FromQuery] string query, [FromQuery] int count, [FromQuery] int offset) { ChatSearchQuery searchRequest; try { searchRequest = query.ParseSearchInfo(count, offset); } catch (ArgumentException e) { _logger.LogWarning(e, "Could not parse chat message search query {query}", query); throw; } catch (FormatException e) { _logger.LogWarning(e, "Could not parse chat message search query filter format {query}", query); throw; } var result = await _chatResourceQueryHelper.QueryResource(searchRequest); return PartialView("~/Views/Client/Message/_Item.cshtml", result.Results); } [HttpGet] [Authorize] public async Task GetAutomatedPenaltyInfoAsync(int penaltyId) { await using var context = _contextFactory.CreateContext(false); var penalty = await context.Penalties .Select(_penalty => new { _penalty.OffenderId, _penalty.PenaltyId, _penalty.When, _penalty.AutomatedOffense }) .FirstOrDefaultAsync(_penalty => _penalty.PenaltyId == penaltyId); if (penalty == null) { return NotFound(); } // todo: this can be optimized var iqSnapshotInfo = context.ACSnapshots .Where(s => s.ClientId == penalty.OffenderId) .Include(s => s.LastStrainAngle) .Include(s => s.HitOrigin) .Include(s => s.HitDestination) .Include(s => s.CurrentViewAngle) .Include(s => s.Server) .Include(s => s.PredictedViewAngles) .ThenInclude(angles => angles.Vector) .OrderBy(s => s.When) .ThenBy(s => s.Hits); var penaltyInfo = await iqSnapshotInfo.ToListAsync(); if (penaltyInfo.Count > 0) { return View("~/Views/Client/_PenaltyInfo.cshtml", penaltyInfo); } // we want to show anything related to the automated offense else { return View("~/Views/Client/_MessageContext.cshtml", new List { new() { ClientId = penalty.OffenderId, Message = penalty.AutomatedOffense, When = penalty.When } }); } } } }