Merge pull request #136 from RaidMax/feature/issue-135-enhanced-search
[issue 135] enhanced search
This commit is contained in:
commit
4afd1f3cdc
@ -225,7 +225,8 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
|
||||
Port = sv.Port,
|
||||
EndPoint = sv.ToString(),
|
||||
ServerId = serverId,
|
||||
GameName = sv.GameName
|
||||
GameName = sv.GameName,
|
||||
HostName = sv.Hostname
|
||||
};
|
||||
|
||||
server = serverSet.Add(server).Entity;
|
||||
@ -240,6 +241,13 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
|
||||
ctx.Entry(server).Property(_prop => _prop.GameName).IsModified = true;
|
||||
ctx.SaveChanges();
|
||||
}
|
||||
|
||||
if (server.HostName == null || server.HostName != sv.Hostname)
|
||||
{
|
||||
server.HostName = sv.Hostname;
|
||||
ctx.Entry(server).Property(_prop => _prop.HostName).IsModified = true;
|
||||
ctx.SaveChanges();
|
||||
}
|
||||
}
|
||||
|
||||
// check to see if the stats have ever been initialized
|
||||
|
@ -15,5 +15,6 @@ namespace IW4MAdmin.Plugins.Stats.Models
|
||||
public int Port { get; set; }
|
||||
public string EndPoint { get; set; }
|
||||
public Game? GameName { get; set; }
|
||||
public string HostName { get; set; }
|
||||
}
|
||||
}
|
||||
|
88
Plugins/Web/StatsWeb/ChatResourceQueryHelper.cs
Normal file
88
Plugins/Web/StatsWeb/ChatResourceQueryHelper.cs
Normal file
@ -0,0 +1,88 @@
|
||||
using IW4MAdmin.Plugins.Stats.Models;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using SharedLibraryCore.Helpers;
|
||||
using SharedLibraryCore.Interfaces;
|
||||
using StatsWeb.Dtos;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace StatsWeb
|
||||
{
|
||||
/// <summary>
|
||||
/// implementation of IResourceQueryHelper
|
||||
/// </summary>
|
||||
public class ChatResourceQueryHelper : IResourceQueryHelper<ChatSearchQuery, ChatSearchResult>
|
||||
{
|
||||
private readonly IDatabaseContextFactory _contextFactory;
|
||||
private readonly ILogger _logger;
|
||||
|
||||
public ChatResourceQueryHelper(ILogger logger, IDatabaseContextFactory contextFactory)
|
||||
{
|
||||
_contextFactory = contextFactory;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async Task<ResourceQueryHelperResult<ChatSearchResult>> QueryResource(ChatSearchQuery query)
|
||||
{
|
||||
if (query == null)
|
||||
{
|
||||
throw new ArgumentException("Query must be specified");
|
||||
}
|
||||
|
||||
var result = new ResourceQueryHelperResult<ChatSearchResult>();
|
||||
using var context = _contextFactory.CreateContext(enableTracking: false);
|
||||
|
||||
var iqMessages = context.Set<EFClientMessage>()
|
||||
.Where(_message => _message.TimeSent >= query.SentAfter)
|
||||
.Where(_message => _message.TimeSent <= query.SentBefore);
|
||||
|
||||
if (query.ClientId != null)
|
||||
{
|
||||
iqMessages = iqMessages.Where(_message => _message.ClientId == query.ClientId.Value);
|
||||
}
|
||||
|
||||
if (query.ServerId != null)
|
||||
{
|
||||
iqMessages = iqMessages.Where(_message => _message.Server.EndPoint == query.ServerId);
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(query.MessageContains))
|
||||
{
|
||||
iqMessages = iqMessages.Where(_message => EF.Functions.Like(_message.Message, $"%{query.MessageContains}%"));
|
||||
}
|
||||
|
||||
var iqResponse = iqMessages
|
||||
.Select(_message => new ChatSearchResult
|
||||
{
|
||||
ClientId = _message.ClientId,
|
||||
ClientName = _message.Client.CurrentAlias.Name,
|
||||
Date = _message.TimeSent,
|
||||
Message = _message.Message,
|
||||
ServerName = _message.Server.HostName
|
||||
});
|
||||
|
||||
if (query.Direction == SharedLibraryCore.Dtos.SortDirection.Descending)
|
||||
{
|
||||
iqResponse = iqResponse.OrderByDescending(_message => _message.Date);
|
||||
}
|
||||
|
||||
else
|
||||
{
|
||||
iqResponse = iqResponse.OrderBy(_message => _message.Date);
|
||||
}
|
||||
|
||||
var resultList = await iqResponse
|
||||
.Skip(query.Offset)
|
||||
.Take(query.Count)
|
||||
.ToListAsync();
|
||||
|
||||
result.TotalResultCount = await iqResponse.CountAsync();
|
||||
result.Results = resultList;
|
||||
result.RetrievedResultCount = resultList.Count;
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
@ -6,6 +6,8 @@ using Microsoft.EntityFrameworkCore;
|
||||
using SharedLibraryCore;
|
||||
using SharedLibraryCore.Dtos;
|
||||
using SharedLibraryCore.Interfaces;
|
||||
using StatsWeb.Dtos;
|
||||
using StatsWeb.Extensions;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
@ -14,11 +16,18 @@ namespace IW4MAdmin.Plugins.Web.StatsWeb.Controllers
|
||||
{
|
||||
public class StatsController : BaseController
|
||||
{
|
||||
private readonly ILogger _logger;
|
||||
private readonly IManager _manager;
|
||||
private readonly IResourceQueryHelper<ChatSearchQuery, ChatSearchResult> _chatResourceQueryHelper;
|
||||
private readonly ITranslationLookup _translationLookup;
|
||||
|
||||
public StatsController(IManager manager) : base(manager)
|
||||
public StatsController(ILogger logger, IManager manager, IResourceQueryHelper<ChatSearchQuery, ChatSearchResult> resourceQueryHelper,
|
||||
ITranslationLookup translationLookup) : base(manager)
|
||||
{
|
||||
_logger = logger;
|
||||
_manager = manager;
|
||||
_chatResourceQueryHelper = resourceQueryHelper;
|
||||
_translationLookup = translationLookup;
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
@ -105,6 +114,69 @@ namespace IW4MAdmin.Plugins.Web.StatsWeb.Controllers
|
||||
}
|
||||
}
|
||||
|
||||
[HttpGet("Message/Find")]
|
||||
public async Task<IActionResult> 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.WriteWarning($"Could not parse chat message search query - {query}");
|
||||
_logger.WriteDebug(e.GetExceptionInfo());
|
||||
ViewBag.Error = e;
|
||||
}
|
||||
|
||||
catch (FormatException e)
|
||||
{
|
||||
_logger.WriteWarning($"Could not parse chat message search query filter format - {query}");
|
||||
_logger.WriteDebug(e.GetExceptionInfo());
|
||||
ViewBag.Error = e;
|
||||
}
|
||||
|
||||
var result = searchRequest != null ? await _chatResourceQueryHelper.QueryResource(searchRequest) : null;
|
||||
return View("Message/Find", result);
|
||||
}
|
||||
|
||||
[HttpGet("Message/FindNext")]
|
||||
public async Task<IActionResult> FindNextMessages([FromQuery]string query, [FromQuery]int count, [FromQuery]int offset)
|
||||
{
|
||||
ChatSearchQuery searchRequest;
|
||||
|
||||
try
|
||||
{
|
||||
searchRequest = query.ParseSearchInfo(count, offset);
|
||||
}
|
||||
|
||||
catch (ArgumentException e)
|
||||
{
|
||||
_logger.WriteWarning($"Could not parse chat message search query - {query}");
|
||||
_logger.WriteDebug(e.GetExceptionInfo());
|
||||
throw;
|
||||
}
|
||||
|
||||
catch (FormatException e)
|
||||
{
|
||||
_logger.WriteWarning($"Could not parse chat message search query filter format - {query}");
|
||||
_logger.WriteDebug(e.GetExceptionInfo());
|
||||
throw;
|
||||
}
|
||||
|
||||
var result = await _chatResourceQueryHelper.QueryResource(searchRequest);
|
||||
return PartialView("Message/_Item", result.Results);
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
[Authorize]
|
||||
public async Task<IActionResult> GetAutomatedPenaltyInfoAsync(int penaltyId)
|
||||
|
33
Plugins/Web/StatsWeb/Dtos/ChatSearchQuery.cs
Normal file
33
Plugins/Web/StatsWeb/Dtos/ChatSearchQuery.cs
Normal file
@ -0,0 +1,33 @@
|
||||
using SharedLibraryCore.Dtos;
|
||||
using System;
|
||||
|
||||
namespace StatsWeb.Dtos
|
||||
{
|
||||
public class ChatSearchQuery : PaginationInfo
|
||||
{
|
||||
/// <summary>
|
||||
/// specifies the partial content of the message to search for
|
||||
/// </summary>
|
||||
public string MessageContains { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// identifier for the server
|
||||
/// </summary>
|
||||
public string ServerId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// identifier for the client
|
||||
/// </summary>
|
||||
public int? ClientId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// only look for messages sent after this date
|
||||
/// </summary>
|
||||
public DateTime SentAfter { get; set; } = DateTime.UtcNow.AddYears(-100);
|
||||
|
||||
/// <summary>
|
||||
/// only look for messages sent before this date0
|
||||
/// </summary>
|
||||
public DateTime SentBefore { get; set; } = DateTime.UtcNow;
|
||||
}
|
||||
}
|
32
Plugins/Web/StatsWeb/Dtos/ChatSearchResult.cs
Normal file
32
Plugins/Web/StatsWeb/Dtos/ChatSearchResult.cs
Normal file
@ -0,0 +1,32 @@
|
||||
using System;
|
||||
|
||||
namespace StatsWeb.Dtos
|
||||
{
|
||||
public class ChatSearchResult
|
||||
{
|
||||
/// <summary>
|
||||
/// name of the client
|
||||
/// </summary>
|
||||
public string ClientName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// client id
|
||||
/// </summary>
|
||||
public int ClientId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// hostname of the server
|
||||
/// </summary>
|
||||
public string ServerName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// chat message
|
||||
/// </summary>
|
||||
public string Message { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// date the chat occured on
|
||||
/// </summary>
|
||||
public DateTime Date { get; set; }
|
||||
}
|
||||
}
|
77
Plugins/Web/StatsWeb/Extensions/SearchQueryExtensions.cs
Normal file
77
Plugins/Web/StatsWeb/Extensions/SearchQueryExtensions.cs
Normal file
@ -0,0 +1,77 @@
|
||||
using SharedLibraryCore.Dtos;
|
||||
using StatsWeb.Dtos;
|
||||
using System;
|
||||
using System.Linq;
|
||||
|
||||
namespace StatsWeb.Extensions
|
||||
{
|
||||
public static class SearchQueryExtensions
|
||||
{
|
||||
private const int MAX_MESSAGES = 100;
|
||||
|
||||
/// <summary>
|
||||
/// todo: lets abstract this out to a generic buildable query
|
||||
/// this is just a dirty PoC
|
||||
/// </summary>
|
||||
/// <param name="query"></param>
|
||||
/// <returns></returns>
|
||||
public static ChatSearchQuery ParseSearchInfo(this string query, int count, int offset)
|
||||
{
|
||||
string[] filters = query.Split('|');
|
||||
var searchRequest = new ChatSearchQuery
|
||||
{
|
||||
Filter = query,
|
||||
Count = count,
|
||||
Offset = offset
|
||||
};
|
||||
|
||||
// sanity checks
|
||||
searchRequest.Count = Math.Min(searchRequest.Count, MAX_MESSAGES);
|
||||
searchRequest.Count = Math.Max(searchRequest.Count, 0);
|
||||
searchRequest.Offset = Math.Max(searchRequest.Offset, 0);
|
||||
|
||||
if (filters.Length > 1)
|
||||
{
|
||||
if (filters[0].ToLower() != "chat")
|
||||
{
|
||||
throw new ArgumentException("Query is not compatible with chat");
|
||||
}
|
||||
|
||||
foreach (string filter in filters.Skip(1))
|
||||
{
|
||||
string[] args = filter.Split(' ');
|
||||
|
||||
if (args.Length > 1)
|
||||
{
|
||||
string recombinedArgs = string.Join(' ', args.Skip(1));
|
||||
switch (args[0].ToLower())
|
||||
{
|
||||
case "before":
|
||||
searchRequest.SentBefore = DateTime.Parse(recombinedArgs);
|
||||
break;
|
||||
case "after":
|
||||
searchRequest.SentAfter = DateTime.Parse(recombinedArgs);
|
||||
break;
|
||||
case "server":
|
||||
searchRequest.ServerId = args[1];
|
||||
break;
|
||||
case "client":
|
||||
searchRequest.ClientId = int.Parse(args[1]);
|
||||
break;
|
||||
case "contains":
|
||||
searchRequest.MessageContains = string.Join(' ', args.Skip(1));
|
||||
break;
|
||||
case "sort":
|
||||
searchRequest.Direction = Enum.Parse<SortDirection>(args[1], ignoreCase: true);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return searchRequest;
|
||||
}
|
||||
|
||||
throw new ArgumentException("No filters specified for chat search");
|
||||
}
|
||||
}
|
||||
}
|
@ -7,14 +7,14 @@
|
||||
<CopyLocalLockFileAssemblies>false</CopyLocalLockFileAssemblies>
|
||||
<AddRazorSupportForMvc>true</AddRazorSupportForMvc>
|
||||
<Configurations>Debug;Release;Prerelease</Configurations>
|
||||
<LangVersion>7.1</LangVersion>
|
||||
<LangVersion>8.0</LangVersion>
|
||||
<ApplicationIcon />
|
||||
<OutputType>Library</OutputType>
|
||||
<StartupObject />
|
||||
<RunPostBuildEvent>Always</RunPostBuildEvent>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="RaidMax.IW4MAdmin.SharedLibraryCore" Version="2.2.11" PrivateAssets="All" />
|
||||
<PackageReference Include="RaidMax.IW4MAdmin.SharedLibraryCore" Version="2.4.0" PrivateAssets="All" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
38
Plugins/Web/StatsWeb/Views/Stats/Message/Find.cshtml
Normal file
38
Plugins/Web/StatsWeb/Views/Stats/Message/Find.cshtml
Normal file
@ -0,0 +1,38 @@
|
||||
@model SharedLibraryCore.Helpers.ResourceQueryHelperResult<StatsWeb.Dtos.ChatSearchResult>
|
||||
|
||||
@if (ViewBag.Error != null)
|
||||
{
|
||||
<h4 class="text-red">@SharedLibraryCore.Utilities.FormatExt(ViewBag.Localization["WEBFRONT_INVALID_QUERY"], ViewBag.Error.Message)</h4>
|
||||
}
|
||||
|
||||
else
|
||||
{
|
||||
<h4 class="pb-3 text-center">@SharedLibraryCore.Utilities.FormatExt(ViewBag.Localization["WEBFRONT_STATS_MESSAGES_FOUND"], Model.TotalResultCount.ToString("N0"))</h4>
|
||||
|
||||
<table class="table table-striped table-hover">
|
||||
<thead class="d-none d-lg-table-header-group">
|
||||
<tr class="bg-primary pt-2 pb-2">
|
||||
<th scope="col">@ViewBag.Localization["WEBFRONT_PENALTY_TEMPLATE_ADMIN"]</th>
|
||||
<th scope="col">@ViewBag.Localization["WEBFRONT_ACTION_LABEL_MESSAGE"]</th>
|
||||
<th scope="col">@ViewBag.Localization["WEBFRONT_STATS_MESSAGE_SERVER_NAME"]</th>
|
||||
<th scope="col" class="text-right">@ViewBag.Localization["WEBFRONT_ADMIN_AUDIT_LOG_TIME"]</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="message_table_body" class="border-bottom bg-dark">
|
||||
<partial name="Message/_Item" model="@Model.Results" />
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<span id="load_more_messages_button" class="loader-load-more oi oi-chevron-bottom text-center text-primary w-100 h3 pb-0 mb-0 d-none d-lg-block"></span>
|
||||
|
||||
@section scripts {
|
||||
<environment include="Development">
|
||||
<script type="text/javascript" src="~/js/loader.js"></script>
|
||||
</environment>
|
||||
<script>
|
||||
$(document).ready(function () {
|
||||
initLoader('/Message/FindNext?query=@ViewBag.Query', '#message_table_body', @Model.RetrievedResultCount, @ViewBag.QueryLimit);
|
||||
});
|
||||
</script>
|
||||
}
|
||||
}
|
53
Plugins/Web/StatsWeb/Views/Stats/Message/_Item.cshtml
Normal file
53
Plugins/Web/StatsWeb/Views/Stats/Message/_Item.cshtml
Normal file
@ -0,0 +1,53 @@
|
||||
@model IEnumerable<StatsWeb.Dtos.ChatSearchResult>
|
||||
|
||||
@foreach (var message in Model)
|
||||
{
|
||||
<!-- desktop -->
|
||||
<tr class="d-none d-lg-table-row">
|
||||
<td>
|
||||
<a asp-controller="Client" asp-action="ProfileAsync" asp-route-id="@message.ClientId" class="link-inverse">
|
||||
<color-code value="@message.ClientName" allow="@ViewBag.EnableColorCodes"></color-code>
|
||||
</a>
|
||||
</td>
|
||||
<td class="text-light w-50 text-break">
|
||||
<color-code value="@message.Message" allow="@ViewBag.EnableColorCodes"></color-code>
|
||||
</td>
|
||||
<td class="text-light">
|
||||
<color-code value="@(message.ServerName ?? "--")" allow="@ViewBag.EnableColorCodes"></color-code>
|
||||
</td>
|
||||
<td class="text-right text-light">
|
||||
@message.Date
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<!-- mobile -->
|
||||
<tr class="d-table-row d-lg-none bg-dark">
|
||||
<th scope="row" class="bg-primary">@ViewBag.Localization["WEBFRONT_PENALTY_TEMPLATE_ADMIN"]</th>
|
||||
<td class="text-light">
|
||||
<a asp-controller="Client" asp-action="ProfileAsync" asp-route-id="@message.ClientId" class="link-inverse">
|
||||
<color-code value="@message.ClientName" allow="@ViewBag.EnableColorCodes"></color-code>
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr class="d-table-row d-lg-none bg-dark">
|
||||
<th scope="row" class="bg-primary">@ViewBag.Localization["WEBFRONT_ACTION_LABEL_MESSAGE"]</th>
|
||||
<td class="text-light">
|
||||
<color-code value="@message.Message" allow="@ViewBag.EnableColorCodes"></color-code>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr class="d-table-row d-lg-none bg-dark">
|
||||
<th scope="row" class="bg-primary">@ViewBag.Localization["WEBFRONT_STATS_MESSAGE_SERVER_NAME"]</th>
|
||||
<td class="text-light">
|
||||
<color-code value="@(message.ServerName ?? "--")" allow="@ViewBag.EnableColorCodes"></color-code>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr class="d-table-row d-lg-none bg-dark">
|
||||
<th scope="row" class="bg-primary" style="border-bottom: 1px solid #222">@ViewBag.Localization["WEBFRONT_ADMIN_AUDIT_LOG_TIME"]</th>
|
||||
<td class="text-light mb-2 border-bottom">
|
||||
@message.Date
|
||||
</td>
|
||||
</tr>
|
||||
}
|
19
README.md
19
README.md
@ -348,7 +348,24 @@ ___
|
||||
* Shows a client's information and history
|
||||
|
||||
`Web Console`
|
||||
* Allows logged in privileged users to execute commands as if they are in-game
|
||||
* Allows logged in privileged users to execute commands as if they are in-
|
||||
|
||||
`Search`
|
||||
* Query clients and messages
|
||||
|
||||
Advanced filters can be constructed to search for resources using the following filter table.
|
||||
| Filter | Description | Format | Example |
|
||||
|-----------|--------------------------------------------------------|-----------------------|---------------------|
|
||||
| before | include items occurring on or before the provided date | YYYY-MM-DD hh:mm:ss (UTC inferred) | 2020-05-21 23:00:00 |
|
||||
| after | include items occurring on or after the provided date | YYYY-MM-DD hh:mm:ss (UTC inferred) | 2015-01-01 |
|
||||
| server | include items matching the server id | ip:port | 127.0.0.1:28960 |
|
||||
| client | include items matching the client id | integer | 8947 |
|
||||
| contains | include items containing this substring | string | hack |
|
||||
| sort | display results in this order | ascending\|descending | descending |
|
||||
|
||||
Any number of filters can be combined in any order.
|
||||
Example — `chat|before 2020-05-21|after 2020-05-01|server 127.0.0.1:28960|client 444|contains cheating|sort descending`
|
||||
|
||||
---
|
||||
### Game Log Server
|
||||
The game log server provides a way to remotely host your server's log over a http rest-ful api.
|
||||
|
26
SharedLibraryCore/Helpers/ResourceQueryHelperResult.cs
Normal file
26
SharedLibraryCore/Helpers/ResourceQueryHelperResult.cs
Normal file
@ -0,0 +1,26 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace SharedLibraryCore.Helpers
|
||||
{
|
||||
/// <summary>
|
||||
/// generic class for passing information about a resource query
|
||||
/// </summary>
|
||||
/// <typeparam name="QueryResultType">Type of query result</typeparam>
|
||||
public class ResourceQueryHelperResult<QueryResultType>
|
||||
{
|
||||
/// <summary>
|
||||
/// indicates the total number of results found
|
||||
/// </summary>
|
||||
public long TotalResultCount { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// indicates the total number of results retrieved
|
||||
/// </summary>
|
||||
public int RetrievedResultCount { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// collection of results
|
||||
/// </summary>
|
||||
public IEnumerable<QueryResultType> Results { get; set; }
|
||||
}
|
||||
}
|
20
SharedLibraryCore/Interfaces/IResourceQueryHelper.cs
Normal file
20
SharedLibraryCore/Interfaces/IResourceQueryHelper.cs
Normal file
@ -0,0 +1,20 @@
|
||||
using SharedLibraryCore.Helpers;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace SharedLibraryCore.Interfaces
|
||||
{
|
||||
/// <summary>
|
||||
/// defines the capabilities of a resource queryier
|
||||
/// </summary>
|
||||
/// <typeparam name="QueryType">Type of query</typeparam>
|
||||
/// <typeparam name="ResultType">Type of result</typeparam>
|
||||
public interface IResourceQueryHelper<QueryType, ResultType>
|
||||
{
|
||||
/// <summary>
|
||||
/// queries a resource and returns the result of the query
|
||||
/// </summary>
|
||||
/// <param name="query">query params</param>
|
||||
/// <returns></returns>
|
||||
Task<ResourceQueryHelperResult<ResultType>> QueryResource(QueryType query);
|
||||
}
|
||||
}
|
922
SharedLibraryCore/Migrations/20200521203304_AddHostnameToEFServer.Designer.cs
generated
Normal file
922
SharedLibraryCore/Migrations/20200521203304_AddHostnameToEFServer.Designer.cs
generated
Normal file
@ -0,0 +1,922 @@
|
||||
// <auto-generated />
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||
using SharedLibraryCore.Database;
|
||||
|
||||
namespace SharedLibraryCore.Migrations
|
||||
{
|
||||
[DbContext(typeof(DatabaseContext))]
|
||||
[Migration("20200521203304_AddHostnameToEFServer")]
|
||||
partial class AddHostnameToEFServer
|
||||
{
|
||||
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||
{
|
||||
#pragma warning disable 612, 618
|
||||
modelBuilder
|
||||
.HasAnnotation("ProductVersion", "3.1.3");
|
||||
|
||||
modelBuilder.Entity("IW4MAdmin.Plugins.Stats.Models.EFACSnapshot", b =>
|
||||
{
|
||||
b.Property<int>("SnapshotId")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<bool>("Active")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("ClientId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("CurrentSessionLength")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<double>("CurrentStrain")
|
||||
.HasColumnType("REAL");
|
||||
|
||||
b.Property<int>("CurrentViewAngleId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("Deaths")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<double>("Distance")
|
||||
.HasColumnType("REAL");
|
||||
|
||||
b.Property<double>("EloRating")
|
||||
.HasColumnType("REAL");
|
||||
|
||||
b.Property<int>("HitDestinationId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("HitLocation")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("HitOriginId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("HitType")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("Hits")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("Kills")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("LastStrainAngleId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<double>("RecoilOffset")
|
||||
.HasColumnType("REAL");
|
||||
|
||||
b.Property<double>("SessionAngleOffset")
|
||||
.HasColumnType("REAL");
|
||||
|
||||
b.Property<double>("SessionAverageSnapValue")
|
||||
.HasColumnType("REAL");
|
||||
|
||||
b.Property<double>("SessionSPM")
|
||||
.HasColumnType("REAL");
|
||||
|
||||
b.Property<int>("SessionScore")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("SessionSnapHits")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<double>("StrainAngleBetween")
|
||||
.HasColumnType("REAL");
|
||||
|
||||
b.Property<int>("TimeSinceLastEvent")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("WeaponId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<DateTime>("When")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("SnapshotId");
|
||||
|
||||
b.HasIndex("ClientId");
|
||||
|
||||
b.HasIndex("CurrentViewAngleId");
|
||||
|
||||
b.HasIndex("HitDestinationId");
|
||||
|
||||
b.HasIndex("HitOriginId");
|
||||
|
||||
b.HasIndex("LastStrainAngleId");
|
||||
|
||||
b.ToTable("EFACSnapshot");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("IW4MAdmin.Plugins.Stats.Models.EFACSnapshotVector3", b =>
|
||||
{
|
||||
b.Property<int>("ACSnapshotVector3Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<bool>("Active")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("SnapshotId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("Vector3Id")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.HasKey("ACSnapshotVector3Id");
|
||||
|
||||
b.HasIndex("SnapshotId");
|
||||
|
||||
b.HasIndex("Vector3Id");
|
||||
|
||||
b.ToTable("EFACSnapshotVector3");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("IW4MAdmin.Plugins.Stats.Models.EFClientKill", b =>
|
||||
{
|
||||
b.Property<long>("KillId")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<bool>("Active")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("AttackerId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("Damage")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int?>("DeathOriginVector3Id")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("DeathType")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<double>("Fraction")
|
||||
.HasColumnType("REAL");
|
||||
|
||||
b.Property<int>("HitLoc")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<bool>("IsKill")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int?>("KillOriginVector3Id")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("Map")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<long>("ServerId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("VictimId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int?>("ViewAnglesVector3Id")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<double>("VisibilityPercentage")
|
||||
.HasColumnType("REAL");
|
||||
|
||||
b.Property<int>("Weapon")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<DateTime>("When")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("KillId");
|
||||
|
||||
b.HasIndex("AttackerId");
|
||||
|
||||
b.HasIndex("DeathOriginVector3Id");
|
||||
|
||||
b.HasIndex("KillOriginVector3Id");
|
||||
|
||||
b.HasIndex("ServerId");
|
||||
|
||||
b.HasIndex("VictimId");
|
||||
|
||||
b.HasIndex("ViewAnglesVector3Id");
|
||||
|
||||
b.ToTable("EFClientKills");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("IW4MAdmin.Plugins.Stats.Models.EFClientMessage", b =>
|
||||
{
|
||||
b.Property<long>("MessageId")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<bool>("Active")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("ClientId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("Message")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<long>("ServerId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<DateTime>("TimeSent")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("MessageId");
|
||||
|
||||
b.HasIndex("ClientId");
|
||||
|
||||
b.HasIndex("ServerId");
|
||||
|
||||
b.HasIndex("TimeSent");
|
||||
|
||||
b.ToTable("EFClientMessages");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("IW4MAdmin.Plugins.Stats.Models.EFClientRatingHistory", b =>
|
||||
{
|
||||
b.Property<int>("RatingHistoryId")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<bool>("Active")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("ClientId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.HasKey("RatingHistoryId");
|
||||
|
||||
b.HasIndex("ClientId");
|
||||
|
||||
b.ToTable("EFClientRatingHistory");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("IW4MAdmin.Plugins.Stats.Models.EFClientStatistics", b =>
|
||||
{
|
||||
b.Property<int>("ClientId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<long>("ServerId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<bool>("Active")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<double>("AverageRecoilOffset")
|
||||
.HasColumnType("REAL");
|
||||
|
||||
b.Property<double>("AverageSnapValue")
|
||||
.HasColumnType("REAL");
|
||||
|
||||
b.Property<int>("Deaths")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<double>("EloRating")
|
||||
.HasColumnType("REAL");
|
||||
|
||||
b.Property<int>("Kills")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<double>("MaxStrain")
|
||||
.HasColumnType("REAL");
|
||||
|
||||
b.Property<double>("RollingWeightedKDR")
|
||||
.HasColumnType("REAL");
|
||||
|
||||
b.Property<double>("SPM")
|
||||
.HasColumnType("REAL");
|
||||
|
||||
b.Property<double>("Skill")
|
||||
.HasColumnType("REAL");
|
||||
|
||||
b.Property<int>("SnapHitCount")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("TimePlayed")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<double>("VisionAverage")
|
||||
.HasColumnType("REAL");
|
||||
|
||||
b.HasKey("ClientId", "ServerId");
|
||||
|
||||
b.HasIndex("ServerId");
|
||||
|
||||
b.ToTable("EFClientStatistics");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("IW4MAdmin.Plugins.Stats.Models.EFHitLocationCount", b =>
|
||||
{
|
||||
b.Property<int>("HitLocationCountId")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<bool>("Active")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("EFClientStatisticsClientId")
|
||||
.HasColumnName("EFClientStatisticsClientId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<long>("EFClientStatisticsServerId")
|
||||
.HasColumnName("EFClientStatisticsServerId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("HitCount")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<float>("HitOffsetAverage")
|
||||
.HasColumnType("REAL");
|
||||
|
||||
b.Property<int>("Location")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<float>("MaxAngleDistance")
|
||||
.HasColumnType("REAL");
|
||||
|
||||
b.HasKey("HitLocationCountId");
|
||||
|
||||
b.HasIndex("EFClientStatisticsServerId");
|
||||
|
||||
b.HasIndex("EFClientStatisticsClientId", "EFClientStatisticsServerId");
|
||||
|
||||
b.ToTable("EFHitLocationCounts");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("IW4MAdmin.Plugins.Stats.Models.EFRating", b =>
|
||||
{
|
||||
b.Property<int>("RatingId")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<bool>("Active")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("ActivityAmount")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<bool>("Newest")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<double>("Performance")
|
||||
.HasColumnType("REAL");
|
||||
|
||||
b.Property<int>("Ranking")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("RatingHistoryId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<long?>("ServerId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<DateTime>("When")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("RatingId");
|
||||
|
||||
b.HasIndex("RatingHistoryId");
|
||||
|
||||
b.HasIndex("ServerId");
|
||||
|
||||
b.HasIndex("Performance", "Ranking", "When");
|
||||
|
||||
b.ToTable("EFRating");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("IW4MAdmin.Plugins.Stats.Models.EFServer", b =>
|
||||
{
|
||||
b.Property<long>("ServerId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<bool>("Active")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("EndPoint")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<int?>("GameName")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("HostName")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<int>("Port")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.HasKey("ServerId");
|
||||
|
||||
b.ToTable("EFServers");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("IW4MAdmin.Plugins.Stats.Models.EFServerStatistics", b =>
|
||||
{
|
||||
b.Property<int>("StatisticId")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<bool>("Active")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<long>("ServerId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<long>("TotalKills")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<long>("TotalPlayTime")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.HasKey("StatisticId");
|
||||
|
||||
b.HasIndex("ServerId");
|
||||
|
||||
b.ToTable("EFServerStatistics");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("SharedLibraryCore.Database.Models.EFAlias", b =>
|
||||
{
|
||||
b.Property<int>("AliasId")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<bool>("Active")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<DateTime>("DateAdded")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<int?>("IPAddress")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("LinkId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT")
|
||||
.HasMaxLength(24);
|
||||
|
||||
b.Property<string>("SearchableName")
|
||||
.HasColumnType("TEXT")
|
||||
.HasMaxLength(24);
|
||||
|
||||
b.HasKey("AliasId");
|
||||
|
||||
b.HasIndex("IPAddress");
|
||||
|
||||
b.HasIndex("LinkId");
|
||||
|
||||
b.HasIndex("Name");
|
||||
|
||||
b.HasIndex("SearchableName");
|
||||
|
||||
b.HasIndex("Name", "IPAddress")
|
||||
.IsUnique();
|
||||
|
||||
b.ToTable("EFAlias");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("SharedLibraryCore.Database.Models.EFAliasLink", b =>
|
||||
{
|
||||
b.Property<int>("AliasLinkId")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<bool>("Active")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.HasKey("AliasLinkId");
|
||||
|
||||
b.ToTable("EFAliasLinks");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("SharedLibraryCore.Database.Models.EFChangeHistory", b =>
|
||||
{
|
||||
b.Property<int>("ChangeHistoryId")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<bool>("Active")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("Comment")
|
||||
.HasColumnType("TEXT")
|
||||
.HasMaxLength(128);
|
||||
|
||||
b.Property<string>("CurrentValue")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<int?>("ImpersonationEntityId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("OriginEntityId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("PreviousValue")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<int>("TargetEntityId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<DateTime>("TimeChanged")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<int>("TypeOfChange")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.HasKey("ChangeHistoryId");
|
||||
|
||||
b.ToTable("EFChangeHistory");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("SharedLibraryCore.Database.Models.EFClient", b =>
|
||||
{
|
||||
b.Property<int>("ClientId")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<bool>("Active")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("AliasLinkId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("Connections")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("CurrentAliasId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<DateTime>("FirstConnection")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<DateTime>("LastConnection")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<int>("Level")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<bool>("Masked")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<long>("NetworkId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("Password")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("PasswordSalt")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<int>("TotalConnectionTime")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.HasKey("ClientId");
|
||||
|
||||
b.HasIndex("AliasLinkId");
|
||||
|
||||
b.HasIndex("CurrentAliasId");
|
||||
|
||||
b.HasIndex("NetworkId")
|
||||
.IsUnique();
|
||||
|
||||
b.ToTable("EFClients");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("SharedLibraryCore.Database.Models.EFMeta", b =>
|
||||
{
|
||||
b.Property<int>("MetaId")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<bool>("Active")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("ClientId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<DateTime>("Created")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Extra")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Key")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT")
|
||||
.HasMaxLength(32);
|
||||
|
||||
b.Property<DateTime>("Updated")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Value")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("MetaId");
|
||||
|
||||
b.HasIndex("ClientId");
|
||||
|
||||
b.HasIndex("Key");
|
||||
|
||||
b.ToTable("EFMeta");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("SharedLibraryCore.Database.Models.EFPenalty", b =>
|
||||
{
|
||||
b.Property<int>("PenaltyId")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<bool>("Active")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("AutomatedOffense")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<DateTime?>("Expires")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<bool>("IsEvadedOffense")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("LinkId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("OffenderId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("Offense")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<int>("PunisherId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("Type")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<DateTime>("When")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("PenaltyId");
|
||||
|
||||
b.HasIndex("LinkId");
|
||||
|
||||
b.HasIndex("OffenderId");
|
||||
|
||||
b.HasIndex("PunisherId");
|
||||
|
||||
b.ToTable("EFPenalties");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("SharedLibraryCore.Helpers.Vector3", b =>
|
||||
{
|
||||
b.Property<int>("Vector3Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<float>("X")
|
||||
.HasColumnType("REAL");
|
||||
|
||||
b.Property<float>("Y")
|
||||
.HasColumnType("REAL");
|
||||
|
||||
b.Property<float>("Z")
|
||||
.HasColumnType("REAL");
|
||||
|
||||
b.HasKey("Vector3Id");
|
||||
|
||||
b.ToTable("Vector3");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("IW4MAdmin.Plugins.Stats.Models.EFACSnapshot", b =>
|
||||
{
|
||||
b.HasOne("SharedLibraryCore.Database.Models.EFClient", "Client")
|
||||
.WithMany()
|
||||
.HasForeignKey("ClientId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("SharedLibraryCore.Helpers.Vector3", "CurrentViewAngle")
|
||||
.WithMany()
|
||||
.HasForeignKey("CurrentViewAngleId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("SharedLibraryCore.Helpers.Vector3", "HitDestination")
|
||||
.WithMany()
|
||||
.HasForeignKey("HitDestinationId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("SharedLibraryCore.Helpers.Vector3", "HitOrigin")
|
||||
.WithMany()
|
||||
.HasForeignKey("HitOriginId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("SharedLibraryCore.Helpers.Vector3", "LastStrainAngle")
|
||||
.WithMany()
|
||||
.HasForeignKey("LastStrainAngleId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
});
|
||||
|
||||
modelBuilder.Entity("IW4MAdmin.Plugins.Stats.Models.EFACSnapshotVector3", b =>
|
||||
{
|
||||
b.HasOne("IW4MAdmin.Plugins.Stats.Models.EFACSnapshot", "Snapshot")
|
||||
.WithMany("PredictedViewAngles")
|
||||
.HasForeignKey("SnapshotId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("SharedLibraryCore.Helpers.Vector3", "Vector")
|
||||
.WithMany()
|
||||
.HasForeignKey("Vector3Id")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
});
|
||||
|
||||
modelBuilder.Entity("IW4MAdmin.Plugins.Stats.Models.EFClientKill", b =>
|
||||
{
|
||||
b.HasOne("SharedLibraryCore.Database.Models.EFClient", "Attacker")
|
||||
.WithMany()
|
||||
.HasForeignKey("AttackerId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("SharedLibraryCore.Helpers.Vector3", "DeathOrigin")
|
||||
.WithMany()
|
||||
.HasForeignKey("DeathOriginVector3Id");
|
||||
|
||||
b.HasOne("SharedLibraryCore.Helpers.Vector3", "KillOrigin")
|
||||
.WithMany()
|
||||
.HasForeignKey("KillOriginVector3Id");
|
||||
|
||||
b.HasOne("IW4MAdmin.Plugins.Stats.Models.EFServer", "Server")
|
||||
.WithMany()
|
||||
.HasForeignKey("ServerId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("SharedLibraryCore.Database.Models.EFClient", "Victim")
|
||||
.WithMany()
|
||||
.HasForeignKey("VictimId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("SharedLibraryCore.Helpers.Vector3", "ViewAngles")
|
||||
.WithMany()
|
||||
.HasForeignKey("ViewAnglesVector3Id");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("IW4MAdmin.Plugins.Stats.Models.EFClientMessage", b =>
|
||||
{
|
||||
b.HasOne("SharedLibraryCore.Database.Models.EFClient", "Client")
|
||||
.WithMany()
|
||||
.HasForeignKey("ClientId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("IW4MAdmin.Plugins.Stats.Models.EFServer", "Server")
|
||||
.WithMany()
|
||||
.HasForeignKey("ServerId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
});
|
||||
|
||||
modelBuilder.Entity("IW4MAdmin.Plugins.Stats.Models.EFClientRatingHistory", b =>
|
||||
{
|
||||
b.HasOne("SharedLibraryCore.Database.Models.EFClient", "Client")
|
||||
.WithMany()
|
||||
.HasForeignKey("ClientId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
});
|
||||
|
||||
modelBuilder.Entity("IW4MAdmin.Plugins.Stats.Models.EFClientStatistics", b =>
|
||||
{
|
||||
b.HasOne("SharedLibraryCore.Database.Models.EFClient", "Client")
|
||||
.WithMany()
|
||||
.HasForeignKey("ClientId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("IW4MAdmin.Plugins.Stats.Models.EFServer", "Server")
|
||||
.WithMany()
|
||||
.HasForeignKey("ServerId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
});
|
||||
|
||||
modelBuilder.Entity("IW4MAdmin.Plugins.Stats.Models.EFHitLocationCount", b =>
|
||||
{
|
||||
b.HasOne("SharedLibraryCore.Database.Models.EFClient", "Client")
|
||||
.WithMany()
|
||||
.HasForeignKey("EFClientStatisticsClientId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("IW4MAdmin.Plugins.Stats.Models.EFServer", "Server")
|
||||
.WithMany()
|
||||
.HasForeignKey("EFClientStatisticsServerId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("IW4MAdmin.Plugins.Stats.Models.EFClientStatistics", null)
|
||||
.WithMany("HitLocations")
|
||||
.HasForeignKey("EFClientStatisticsClientId", "EFClientStatisticsServerId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
});
|
||||
|
||||
modelBuilder.Entity("IW4MAdmin.Plugins.Stats.Models.EFRating", b =>
|
||||
{
|
||||
b.HasOne("IW4MAdmin.Plugins.Stats.Models.EFClientRatingHistory", "RatingHistory")
|
||||
.WithMany("Ratings")
|
||||
.HasForeignKey("RatingHistoryId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("IW4MAdmin.Plugins.Stats.Models.EFServer", "Server")
|
||||
.WithMany()
|
||||
.HasForeignKey("ServerId");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("IW4MAdmin.Plugins.Stats.Models.EFServerStatistics", b =>
|
||||
{
|
||||
b.HasOne("IW4MAdmin.Plugins.Stats.Models.EFServer", "Server")
|
||||
.WithMany()
|
||||
.HasForeignKey("ServerId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
});
|
||||
|
||||
modelBuilder.Entity("SharedLibraryCore.Database.Models.EFAlias", b =>
|
||||
{
|
||||
b.HasOne("SharedLibraryCore.Database.Models.EFAliasLink", "Link")
|
||||
.WithMany("Children")
|
||||
.HasForeignKey("LinkId")
|
||||
.OnDelete(DeleteBehavior.Restrict)
|
||||
.IsRequired();
|
||||
});
|
||||
|
||||
modelBuilder.Entity("SharedLibraryCore.Database.Models.EFClient", b =>
|
||||
{
|
||||
b.HasOne("SharedLibraryCore.Database.Models.EFAliasLink", "AliasLink")
|
||||
.WithMany()
|
||||
.HasForeignKey("AliasLinkId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("SharedLibraryCore.Database.Models.EFAlias", "CurrentAlias")
|
||||
.WithMany()
|
||||
.HasForeignKey("CurrentAliasId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
});
|
||||
|
||||
modelBuilder.Entity("SharedLibraryCore.Database.Models.EFMeta", b =>
|
||||
{
|
||||
b.HasOne("SharedLibraryCore.Database.Models.EFClient", "Client")
|
||||
.WithMany("Meta")
|
||||
.HasForeignKey("ClientId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
});
|
||||
|
||||
modelBuilder.Entity("SharedLibraryCore.Database.Models.EFPenalty", b =>
|
||||
{
|
||||
b.HasOne("SharedLibraryCore.Database.Models.EFAliasLink", "Link")
|
||||
.WithMany("ReceivedPenalties")
|
||||
.HasForeignKey("LinkId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("SharedLibraryCore.Database.Models.EFClient", "Offender")
|
||||
.WithMany("ReceivedPenalties")
|
||||
.HasForeignKey("OffenderId")
|
||||
.OnDelete(DeleteBehavior.Restrict)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("SharedLibraryCore.Database.Models.EFClient", "Punisher")
|
||||
.WithMany("AdministeredPenalties")
|
||||
.HasForeignKey("PunisherId")
|
||||
.OnDelete(DeleteBehavior.Restrict)
|
||||
.IsRequired();
|
||||
});
|
||||
#pragma warning restore 612, 618
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,22 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
namespace SharedLibraryCore.Migrations
|
||||
{
|
||||
public partial class AddHostnameToEFServer : Migration
|
||||
{
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AddColumn<string>(
|
||||
name: "HostName",
|
||||
table: "EFServers",
|
||||
nullable: true);
|
||||
}
|
||||
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropColumn(
|
||||
name: "HostName",
|
||||
table: "EFServers");
|
||||
}
|
||||
}
|
||||
}
|
@ -405,6 +405,9 @@ namespace SharedLibraryCore.Migrations
|
||||
b.Property<int?>("GameName")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("HostName")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<int>("Port")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
|
@ -6,7 +6,7 @@
|
||||
<ApplicationIcon />
|
||||
<StartupObject />
|
||||
<PackageId>RaidMax.IW4MAdmin.SharedLibraryCore</PackageId>
|
||||
<Version>2.2.12</Version>
|
||||
<Version>2.4.0</Version>
|
||||
<Authors>RaidMax</Authors>
|
||||
<Company>Forever None</Company>
|
||||
<Configurations>Debug;Release;Prerelease</Configurations>
|
||||
@ -20,8 +20,8 @@
|
||||
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
|
||||
<PackageLicenseExpression>MIT</PackageLicenseExpression>
|
||||
<Description>Shared Library for IW4MAdmin</Description>
|
||||
<AssemblyVersion>2.2.12.0</AssemblyVersion>
|
||||
<FileVersion>2.2.12.0</FileVersion>
|
||||
<AssemblyVersion>2.4.0.0</AssemblyVersion>
|
||||
<FileVersion>2.4.0.0</FileVersion>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Prerelease|AnyCPU'">
|
||||
|
@ -17,6 +17,7 @@
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\Application\Application.csproj" />
|
||||
<ProjectReference Include="..\..\Plugins\Stats\Stats.csproj" />
|
||||
<ProjectReference Include="..\..\Plugins\Web\StatsWeb\StatsWeb.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
40
Tests/ApplicationTests/Fixtures/MessageGenerators.cs
Normal file
40
Tests/ApplicationTests/Fixtures/MessageGenerators.cs
Normal file
@ -0,0 +1,40 @@
|
||||
using IW4MAdmin.Plugins.Stats.Models;
|
||||
using SharedLibraryCore.Database.Models;
|
||||
using System;
|
||||
|
||||
namespace ApplicationTests.Fixtures
|
||||
{
|
||||
public class MessageGenerators
|
||||
{
|
||||
public static EFClientMessage GenerateMessage(string content = null, DateTime? sent = null)
|
||||
{
|
||||
if (!sent.HasValue)
|
||||
{
|
||||
sent = DateTime.Now;
|
||||
}
|
||||
|
||||
var rand = new Random();
|
||||
string endPoint = $"127.0.0.1:{rand.Next(1000, short.MaxValue)}";
|
||||
|
||||
return new EFClientMessage()
|
||||
{
|
||||
Active = true,
|
||||
Message = content,
|
||||
TimeSent = sent.Value,
|
||||
Client = new EFClient()
|
||||
{
|
||||
NetworkId = -1,
|
||||
CurrentAlias = new EFAlias()
|
||||
{
|
||||
Name = "test"
|
||||
}
|
||||
},
|
||||
Server = new EFServer()
|
||||
{
|
||||
EndPoint = endPoint,
|
||||
ServerId = long.Parse(endPoint.Replace(".", "").Replace(":", ""))
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
269
Tests/ApplicationTests/StatsWebTests.cs
Normal file
269
Tests/ApplicationTests/StatsWebTests.cs
Normal file
@ -0,0 +1,269 @@
|
||||
using ApplicationTests.Fixtures;
|
||||
using IW4MAdmin.Plugins.Stats.Models;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using NUnit.Framework;
|
||||
using SharedLibraryCore.Database;
|
||||
using SharedLibraryCore.Dtos;
|
||||
using SharedLibraryCore.Interfaces;
|
||||
using StatsWeb;
|
||||
using StatsWeb.Extensions;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace ApplicationTests
|
||||
{
|
||||
[TestFixture]
|
||||
public class StatsWebTests
|
||||
{
|
||||
private IServiceProvider serviceProvider;
|
||||
private DatabaseContext dbContext;
|
||||
private ChatResourceQueryHelper queryHelper;
|
||||
|
||||
~StatsWebTests()
|
||||
{
|
||||
dbContext.Dispose();
|
||||
}
|
||||
|
||||
[SetUp]
|
||||
public void Setup()
|
||||
{
|
||||
serviceProvider = new ServiceCollection()
|
||||
.AddSingleton<ChatResourceQueryHelper>()
|
||||
.BuildBase()
|
||||
.BuildServiceProvider();
|
||||
|
||||
SetupDatabase();
|
||||
|
||||
queryHelper = serviceProvider.GetRequiredService<ChatResourceQueryHelper>();
|
||||
}
|
||||
|
||||
private void SetupDatabase()
|
||||
{
|
||||
var contextFactory = serviceProvider.GetRequiredService<IDatabaseContextFactory>();
|
||||
dbContext = contextFactory.CreateContext();
|
||||
}
|
||||
|
||||
#region PARSE_SEARCH_INFO
|
||||
[Test]
|
||||
public void Test_ParseSearchInfo_SanityChecks()
|
||||
{
|
||||
var query = "chat|".ParseSearchInfo(-1, -1);
|
||||
|
||||
Assert.AreEqual(0, query.Count);
|
||||
Assert.AreEqual(0, query.Offset);
|
||||
|
||||
query = "chat|".ParseSearchInfo(int.MaxValue, int.MaxValue);
|
||||
|
||||
Assert.Greater(int.MaxValue, query.Count);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Test_ParseSearchInfo_BeforeFilter_Happy()
|
||||
{
|
||||
var now = DateTime.Now;
|
||||
var date = new DateTime(now.Year, now.Month, now.Day, now.Hour, now.Minute, now.Second);
|
||||
var query = $"chat|before {date.ToString()}".ParseSearchInfo(0, 0);
|
||||
|
||||
Assert.AreEqual(date, query.SentBefore);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Test_ParseSearchInfo_AfterFilter_Happy()
|
||||
{
|
||||
var now = DateTime.Now;
|
||||
var date = new DateTime(now.Year, now.Month, now.Day, now.Hour, now.Minute, now.Second);
|
||||
var query = $"chat|after {date.ToString()}".ParseSearchInfo(0, 0);
|
||||
|
||||
Assert.AreEqual(date, query.SentAfter);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Test_ParseSearchInfo_ServerFilter_Happy()
|
||||
{
|
||||
string serverId = "127.0.0.1:28960";
|
||||
var query = $"chat|server {serverId}".ParseSearchInfo(0, 0);
|
||||
|
||||
Assert.AreEqual(serverId, query.ServerId);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Test_ParseSearchInfo_ClientFilter_Happy()
|
||||
{
|
||||
int clientId = 123;
|
||||
var query = $"chat|client {clientId.ToString()}".ParseSearchInfo(0, 0);
|
||||
|
||||
Assert.AreEqual(clientId, query.ClientId);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Test_ParseSearchInfo_ContainsFilter_Happy()
|
||||
{
|
||||
string content = "test";
|
||||
var query = $"chat|contains {content}".ParseSearchInfo(0, 0);
|
||||
|
||||
Assert.AreEqual(content, query.MessageContains);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Test_ParseSearchInfo_SortFilter_Happy()
|
||||
{
|
||||
var direction = SortDirection.Ascending;
|
||||
var query = $"chat|sort {direction.ToString().ToLower()}".ParseSearchInfo(0, 0);
|
||||
|
||||
Assert.AreEqual(direction, query.Direction);
|
||||
|
||||
direction = SortDirection.Descending;
|
||||
query = $"chat|sort {direction.ToString().ToLower()}".ParseSearchInfo(0, 0);
|
||||
|
||||
Assert.AreEqual(direction, query.Direction);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Test_ParseSearchInfo_InvalidQueryType()
|
||||
{
|
||||
Assert.Throws<ArgumentException>(() => "player|test".ParseSearchInfo(0, 0));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Test_ParseSearchInfo_NoQueryType()
|
||||
{
|
||||
Assert.Throws<ArgumentException>(() => "".ParseSearchInfo(0, 0));
|
||||
}
|
||||
#endregion]
|
||||
|
||||
#region CHAT_RESOURCE_QUERY_HELPER
|
||||
[Test]
|
||||
public void Test_ChatResourceQueryHelper_Invalid()
|
||||
{
|
||||
var helper = serviceProvider.GetRequiredService<ChatResourceQueryHelper>();
|
||||
|
||||
Assert.ThrowsAsync<ArgumentException>(() => helper.QueryResource(null));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task Test_ChatResourceQueryHelper_SentAfter()
|
||||
{
|
||||
var oneHourAhead = DateTime.Now.AddHours(1);
|
||||
var msg = MessageGenerators.GenerateMessage(sent: oneHourAhead);
|
||||
|
||||
dbContext.Set<EFClientMessage>()
|
||||
.Add(msg);
|
||||
await dbContext.SaveChangesAsync();
|
||||
|
||||
var query = $"chat|after {DateTime.Now.ToString()}".ParseSearchInfo(1, 0);
|
||||
var result = await queryHelper.QueryResource(query);
|
||||
|
||||
Assert.AreEqual(oneHourAhead, result.Results.First().Date);
|
||||
|
||||
dbContext.Remove(msg);
|
||||
await dbContext.SaveChangesAsync();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task Test_ChatResourceQueryHelper_SentBefore()
|
||||
{
|
||||
var oneHourAgo = DateTime.Now.AddHours(-1);
|
||||
var msg = MessageGenerators.GenerateMessage(sent: oneHourAgo);
|
||||
|
||||
dbContext.Set<EFClientMessage>()
|
||||
.Add(msg);
|
||||
await dbContext.SaveChangesAsync();
|
||||
|
||||
var query = $"chat|before {DateTime.Now.ToString()}".ParseSearchInfo(1, 0);
|
||||
var result = await queryHelper.QueryResource(query);
|
||||
|
||||
Assert.AreEqual(oneHourAgo, result.Results.First().Date);
|
||||
|
||||
dbContext.Remove(msg);
|
||||
await dbContext.SaveChangesAsync();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task Test_ChatResourceQueryHelper_Server()
|
||||
{
|
||||
var msg = MessageGenerators.GenerateMessage(sent: DateTime.Now);
|
||||
|
||||
dbContext.Set<EFClientMessage>()
|
||||
.Add(msg);
|
||||
await dbContext.SaveChangesAsync();
|
||||
|
||||
string serverId = msg.Server.EndPoint;
|
||||
var query = $"chat|server {serverId}".ParseSearchInfo(1, 0);
|
||||
var result = await queryHelper.QueryResource(query);
|
||||
|
||||
Assert.IsNotEmpty(result.Results);
|
||||
|
||||
dbContext.Remove(msg);
|
||||
await dbContext.SaveChangesAsync();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task Test_ChatResourceQueryHelper_Client()
|
||||
{
|
||||
var msg = MessageGenerators.GenerateMessage(sent: DateTime.Now);
|
||||
|
||||
dbContext.Set<EFClientMessage>()
|
||||
.Add(msg);
|
||||
await dbContext.SaveChangesAsync();
|
||||
|
||||
int clientId = msg.Client.ClientId;
|
||||
var query = $"chat|client {clientId}".ParseSearchInfo(1, 0);
|
||||
var result = await queryHelper.QueryResource(query);
|
||||
|
||||
Assert.AreEqual(clientId, result.Results.First().ClientId);
|
||||
|
||||
dbContext.Remove(msg);
|
||||
await dbContext.SaveChangesAsync();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task Test_ChatResourceQueryHelper_Contains()
|
||||
{
|
||||
var msg = MessageGenerators.GenerateMessage(sent: DateTime.Now);
|
||||
msg.Message = "this is a test";
|
||||
|
||||
dbContext.Set<EFClientMessage>()
|
||||
.Add(msg);
|
||||
await dbContext.SaveChangesAsync();
|
||||
|
||||
var query = $"chat|contains {msg.Message}".ParseSearchInfo(1, 0);
|
||||
var result = await queryHelper.QueryResource(query);
|
||||
|
||||
Assert.AreEqual(msg.Message, result.Results.First().Message);
|
||||
|
||||
dbContext.Remove(msg);
|
||||
await dbContext.SaveChangesAsync();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task Test_ChatResourceQueryHelper_Sort()
|
||||
{
|
||||
var firstMessage = MessageGenerators.GenerateMessage(sent: DateTime.Now.AddHours(-1));
|
||||
var secondMessage = MessageGenerators.GenerateMessage(sent: DateTime.Now);
|
||||
|
||||
dbContext.Set<EFClientMessage>()
|
||||
.Add(firstMessage);
|
||||
dbContext.Set<EFClientMessage>()
|
||||
.Add(secondMessage);
|
||||
await dbContext.SaveChangesAsync();
|
||||
|
||||
var query = $"chat|sort {SortDirection.Ascending}".ParseSearchInfo(2, 0);
|
||||
var result = await queryHelper.QueryResource(query);
|
||||
|
||||
Assert.AreEqual(firstMessage.TimeSent, result.Results.First().Date);
|
||||
Assert.AreEqual(secondMessage.TimeSent, result.Results.Last().Date);
|
||||
|
||||
query = $"chat|sort {SortDirection.Descending}".ParseSearchInfo(2, 0);
|
||||
result = await queryHelper.QueryResource(query);
|
||||
|
||||
Assert.AreEqual(firstMessage.TimeSent, result.Results.Last().Date);
|
||||
Assert.AreEqual(secondMessage.TimeSent, result.Results.First().Date);
|
||||
|
||||
dbContext.Remove(firstMessage);
|
||||
dbContext.Remove(secondMessage);
|
||||
await dbContext.SaveChangesAsync();
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
}
|
@ -9,7 +9,10 @@ using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using SharedLibraryCore;
|
||||
using SharedLibraryCore.Database;
|
||||
using SharedLibraryCore.Helpers;
|
||||
using SharedLibraryCore.Interfaces;
|
||||
using StatsWeb;
|
||||
using StatsWeb.Dtos;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
@ -101,12 +104,14 @@ namespace WebfrontCore
|
||||
#endif
|
||||
|
||||
services.AddSingleton(Program.Manager);
|
||||
services.AddSingleton<IResourceQueryHelper<ChatSearchQuery, ChatSearchResult>, ChatResourceQueryHelper>();
|
||||
|
||||
// todo: this needs to be handled more gracefully
|
||||
services.AddSingleton(Program.ApplicationServiceProvider.GetService<IConfigurationHandlerFactory>());
|
||||
services.AddSingleton(Program.ApplicationServiceProvider.GetService<IDatabaseContextFactory>());
|
||||
services.AddSingleton(Program.ApplicationServiceProvider.GetService<IAuditInformationRepository>());
|
||||
services.AddSingleton(Program.ApplicationServiceProvider.GetService<ITranslationLookup>());
|
||||
services.AddSingleton(Program.ApplicationServiceProvider.GetService<SharedLibraryCore.Interfaces.ILogger>());
|
||||
}
|
||||
|
||||
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
|
||||
|
@ -78,6 +78,7 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Plugins\Web\StatsWeb\StatsWeb.csproj" />
|
||||
<ProjectReference Include="..\SharedLibraryCore\SharedLibraryCore.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
|
@ -5,18 +5,22 @@
|
||||
$('#client_search')
|
||||
.addClass('input-text-danger')
|
||||
.delay(25)
|
||||
.queue(function(){
|
||||
.queue(function () {
|
||||
$(this).addClass('input-border-transition').dequeue();
|
||||
})
|
||||
.delay(1000)
|
||||
.queue(function() {
|
||||
.queue(function () {
|
||||
$(this).removeClass('input-text-danger').dequeue();
|
||||
})
|
||||
.delay(500)
|
||||
.queue(function() {
|
||||
.queue(function () {
|
||||
$(this).removeClass('input-border-transition').dequeue();
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
else if ($('#client_search').val().startsWith("chat|")) {
|
||||
e.preventDefault();
|
||||
window.location = "/Message/Find?query=" + $('#client_search').val();
|
||||
}
|
||||
});
|
||||
});
|
Loading…
Reference in New Issue
Block a user