diff --git a/Intersect (Core)/Security/PasswordUtils.cs b/Intersect (Core)/Security/PasswordUtils.cs index 75df90c743..c513ec197f 100644 --- a/Intersect (Core)/Security/PasswordUtils.cs +++ b/Intersect (Core)/Security/PasswordUtils.cs @@ -9,4 +9,7 @@ public static string ComputePasswordHash(string password) { return BitConverter.ToString(SHA256.HashData(Encoding.UTF8.GetBytes(password ?? string.Empty))).Replace("-", string.Empty); } + + public static bool IsValidClientPasswordHash(string? hashToValidate) => + hashToValidate is { Length: 64 } && hashToValidate.All(char.IsAsciiHexDigit); } diff --git a/Intersect.Server.Core/Collections/Indexing/LookupKey.cs b/Intersect.Server.Core/Collections/Indexing/LookupKey.cs index e81a94a398..e9d874fad7 100644 --- a/Intersect.Server.Core/Collections/Indexing/LookupKey.cs +++ b/Intersect.Server.Core/Collections/Indexing/LookupKey.cs @@ -1,9 +1,9 @@ -using System.ComponentModel; +using System.ComponentModel; using System.Globalization; using Intersect.Server.Localization; using Intersect.Utilities; -namespace Intersect.Server.Web.RestApi.Payloads; +namespace Intersect.Server.Collections.Indexing; // TODO: Figure out how to get LookupKey to show up in swagger.json components/schemas despite being a "string", one or more of the following commented out attributes may help // [SwaggerSubType(typeof(Guid))] @@ -14,15 +14,15 @@ namespace Intersect.Server.Web.RestApi.Payloads; public partial struct LookupKey { - public bool HasName => !string.IsNullOrWhiteSpace(Name); + public readonly bool HasName => !string.IsNullOrWhiteSpace(Name); - public bool HasId => Guid.Empty != Id; + public readonly bool HasId => Guid.Empty != Id; - public bool IsNameInvalid => !HasId && Name != null; + public readonly bool IsNameInvalid => !HasId && Name != null; - public bool IsIdInvalid => !HasId && Name == null; + public readonly bool IsIdInvalid => !HasId && Name == null; - public bool IsInvalid => !HasId && !HasName; + public readonly bool IsInvalid => !HasId && !HasName; public Guid Id { get; private set; } diff --git a/Intersect.Server.Core/Collections/Sorting/Sort.cs b/Intersect.Server.Core/Collections/Sorting/Sort.cs index a4149316d1..59cf841dec 100644 --- a/Intersect.Server.Core/Collections/Sorting/Sort.cs +++ b/Intersect.Server.Core/Collections/Sorting/Sort.cs @@ -1,9 +1,7 @@ -namespace Intersect.Server.Web.RestApi.Payloads; - +namespace Intersect.Server.Collections.Sorting; public partial struct Sort { - public string[] By { get; set; } public SortDirection Direction { get; set; } @@ -21,14 +19,13 @@ public static Sort[] From(string[] sortBy, SortDirection[] sortDirections) { var filteredBy = sortBy?.Where(by => !string.IsNullOrWhiteSpace(by)).Take(sortDirections?.Length ?? 0).ToList() ?? - new List(); + []; - var filteredDirections = sortDirections?.Take(filteredBy.Count).ToList() ?? new List(); + var filteredDirections = sortDirections?.Take(filteredBy.Count).ToList() ?? []; return filteredBy.Select( (by, index) => From(by ?? throw new InvalidOperationException(), filteredDirections[index]) ) .ToArray(); } - } diff --git a/Intersect.Server.Core/Collections/Sorting/SortDirection.cs b/Intersect.Server.Core/Collections/Sorting/SortDirection.cs index 14f8cfdd46..482562e196 100644 --- a/Intersect.Server.Core/Collections/Sorting/SortDirection.cs +++ b/Intersect.Server.Core/Collections/Sorting/SortDirection.cs @@ -1,11 +1,8 @@ -namespace Intersect.Server.Web.RestApi.Payloads; - +namespace Intersect.Server.Collections.Sorting; public enum SortDirection { - Ascending, - Descending - + Descending, } diff --git a/Intersect.Server.Core/Database/PlayerData/Players/Guild.cs b/Intersect.Server.Core/Database/PlayerData/Players/Guild.cs index 67f750f1b0..360736ba6e 100644 --- a/Intersect.Server.Core/Database/PlayerData/Players/Guild.cs +++ b/Intersect.Server.Core/Database/PlayerData/Players/Guild.cs @@ -15,8 +15,8 @@ using Intersect.Logging; using Intersect.Utilities; using Intersect.Server.Localization; -using Intersect.Server.Web.RestApi.Payloads; using static Intersect.Server.Database.Logging.Entities.GuildHistory; +using Intersect.Server.Collections.Sorting; namespace Intersect.Server.Database.PlayerData.Players; diff --git a/Intersect.Server.Core/Database/PlayerData/User.cs b/Intersect.Server.Core/Database/PlayerData/User.cs index 6b5e427eaf..62e16b77f9 100644 --- a/Intersect.Server.Core/Database/PlayerData/User.cs +++ b/Intersect.Server.Core/Database/PlayerData/User.cs @@ -10,6 +10,8 @@ using Intersect.Logging; using Intersect.Reflection; using Intersect.Security; +using Intersect.Server.Collections.Indexing; +using Intersect.Server.Collections.Sorting; using Intersect.Server.Core; using Intersect.Server.Database.Logging.Entities; using Intersect.Server.Database.PlayerData.Api; @@ -19,7 +21,6 @@ using Intersect.Server.General; using Intersect.Server.Localization; using Intersect.Server.Networking; -using Intersect.Server.Web.RestApi.Payloads; using Intersect.Utilities; using Microsoft.EntityFrameworkCore; using Newtonsoft.Json; @@ -529,18 +530,33 @@ public UserSaveResult Save(PlayerContext? playerContext, bool force = false, boo return user; } - public static Tuple Fetch(Guid userId) + public static bool TryFind(LookupKey lookupKey, [NotNullWhen(true)] out User? user) { - var client = Globals.Clients.Find(queryClient => userId == queryClient?.User?.Id); - - return new Tuple(client, client?.User ?? FindById(userId)); + using var playerContext = DbInterface.CreatePlayerContext(); + return TryFind(lookupKey, playerContext, out user); } - public static Tuple Fetch(string userName) + public static bool TryFetch(LookupKey lookupKey, [NotNullWhen(true)] out User? user) => TryFetch(lookupKey, out user, out _); + + public static bool TryFetch(LookupKey lookupKey, [NotNullWhen(true)] out User? user, out Client? client) { - var client = Globals.Clients.Find(queryClient => Entity.CompareName(userName, queryClient?.User?.Name)); + if (lookupKey.IsInvalid) + { + user = default; + client = default; + return false; + } - return new Tuple(client, client?.User ?? Find(userName)); + if (lookupKey.HasId) + { + client = Globals.Clients.Find(queryClient => lookupKey.Id == queryClient?.User?.Id); + user = client?.User ?? FindById(lookupKey.Id); + return user != default; + } + + client = Globals.Clients.Find(queryClient => Entity.CompareName(lookupKey.Name, queryClient?.User?.Name)); + user = client?.User ?? Find(lookupKey.Name); + return user != default; } public static bool TryLogin( @@ -620,28 +636,6 @@ out LoginFailureReason failureReason } } - public static bool TryFetch(LookupKey lookupKey, [NotNullWhen(true)] out User? user) => - TryFetch(lookupKey, out user, out _); - - public static bool TryFetch(LookupKey lookupKey, [NotNullWhen(true)] out User? user, out Client? client) - { - if (lookupKey is { HasName: false, HasId: false }) - { - user = default; - client = default; - return false; - } - - (client, user) = lookupKey.HasId ? Fetch(lookupKey.Id) : Fetch(lookupKey.Name); - return user != default; - } - - public static bool TryFind(LookupKey lookupKey, [NotNullWhen(true)] out User? user) - { - using var playerContext = DbInterface.CreatePlayerContext(); - return TryFind(lookupKey, playerContext, out user); - } - public static bool TryFind(LookupKey lookupKey, PlayerContext playerContext, [NotNullWhen(true)] out User? user) { if (lookupKey.HasId) diff --git a/Intersect.Server.Core/Entities/Player.Database.cs b/Intersect.Server.Core/Entities/Player.Database.cs index a996b449df..70db26a82e 100644 --- a/Intersect.Server.Core/Entities/Player.Database.cs +++ b/Intersect.Server.Core/Entities/Player.Database.cs @@ -1,17 +1,18 @@ -using System.ComponentModel.DataAnnotations.Schema; +using System.ComponentModel.DataAnnotations.Schema; using System.Diagnostics.CodeAnalysis; using Intersect.Logging; using Intersect.Server.Database; using Intersect.Server.Database.PlayerData; using Intersect.Server.General; using Intersect.Server.Networking; -using Intersect.Server.Web.RestApi.Payloads; using Intersect.Server.Database.PlayerData.Players; using Intersect.Utilities; using Microsoft.EntityFrameworkCore; using Newtonsoft.Json; +using Intersect.Server.Collections.Indexing; +using Intersect.Server.Collections.Sorting; namespace Intersect.Server.Entities; @@ -38,42 +39,39 @@ public partial class Player #region Lookup - public static bool TryFetch(LookupKey lookupKey, [NotNullWhen(true)] out Tuple? tuple) - { - tuple = Fetch(lookupKey); - return tuple != default; - } - - public static Tuple Fetch(LookupKey lookupKey, bool loadRelationships = false, - bool loadBags = false) + public static bool TryFetch( + LookupKey lookupKey, + out Player? player, + bool loadRelationships = false, + bool loadBags = false + ) + => TryFetch(lookupKey, out _, out player, loadRelationships, loadBags); + + public static bool TryFetch( + LookupKey lookupKey, + [NotNullWhen(true)] out Client? client, + out Player? player, + bool loadRelationships = false, + bool loadBags = false + ) { - if (lookupKey is { HasName: false, HasId: false }) + if (lookupKey.IsInvalid) { - return new Tuple(null, null); + client = default; + player = default; + return false; } - // HasName checks if null or empty - // ReSharper disable once AssignNullToNotNullAttribute - return lookupKey.HasId - ? Fetch(lookupKey.Id) - : Fetch(lookupKey.Name, loadRelationships: loadRelationships, loadBags: loadBags); - } - - public static Tuple Fetch(string playerName, bool loadRelationships = false, bool loadBags = false) - { - var client = Globals.Clients.Find(queryClient => Entity.CompareName(playerName, queryClient?.Entity?.Name)); - - return new Tuple( - client, - client?.Entity ?? Find(playerName, loadRelationships: loadRelationships, loadBags: loadBags) - ); - } - - public static Tuple Fetch(Guid playerId) - { - var client = Globals.Clients.Find(queryClient => playerId == queryClient?.Entity?.Id); + if (lookupKey.HasId) + { + client = Globals.Clients.Find(queryClient => lookupKey.Id == queryClient?.Entity?.Id); + player = client?.Entity ?? Find(lookupKey.Id); + return player != default; + } - return new Tuple(client, client?.Entity ?? Player.Find(playerId)); + client = Globals.Clients.Find(queryClient => CompareName(lookupKey.Name, queryClient?.Entity?.Name)); + player = client?.Entity ?? Find(lookupKey.Name, loadRelationships: loadRelationships, loadBags: loadBags); + return player != default; } public static Player Find(Guid playerId) diff --git a/Intersect.Server.Core/Extensions/EnumerableExtensions.cs b/Intersect.Server.Core/Extensions/EnumerableExtensions.cs index 621549bd10..ac14d7c32b 100644 --- a/Intersect.Server.Core/Extensions/EnumerableExtensions.cs +++ b/Intersect.Server.Core/Extensions/EnumerableExtensions.cs @@ -1,5 +1,4 @@ -using Intersect.Server.Web.RestApi.Payloads; - +using Intersect.Server.Collections.Sorting; using Microsoft.EntityFrameworkCore; namespace Intersect.Server.Extensions; diff --git a/Intersect.Server.Core/Extensions/QueryableExtensions.cs b/Intersect.Server.Core/Extensions/QueryableExtensions.cs index 0f409bb6a8..502b972c84 100644 --- a/Intersect.Server.Core/Extensions/QueryableExtensions.cs +++ b/Intersect.Server.Core/Extensions/QueryableExtensions.cs @@ -1,6 +1,5 @@ -using System.Linq.Expressions; - -using Intersect.Server.Web.RestApi.Payloads; +using System.Linq.Expressions; +using Intersect.Server.Collections.Sorting; using Microsoft.EntityFrameworkCore; diff --git a/Intersect.Server/Web/Net7/ApiService.cs b/Intersect.Server/Web/Net7/ApiService.cs index 5955904eca..0e8e78ead5 100644 --- a/Intersect.Server/Web/Net7/ApiService.cs +++ b/Intersect.Server/Web/Net7/ApiService.cs @@ -10,7 +10,7 @@ using Intersect.Server.Web.Configuration; using Intersect.Server.Web.Constraints; using Intersect.Server.Web.Middleware; -using Intersect.Server.Web.RestApi.Payloads; +using Intersect.Server.Web.RestApi.Types.Chat; using Intersect.Server.Web.RestApi.Routes; using Intersect.Server.Web.Serialization; using Intersect.Server.Web.Swagger.Filters; @@ -32,6 +32,7 @@ using Microsoft.IdentityModel.Tokens; using Microsoft.OpenApi.Models; using Newtonsoft.Json.Converters; +using Intersect.Server.Collections.Indexing; #pragma warning disable CS1998 // Async method lacks 'await' operators and will run synchronously namespace Intersect.Server.Web; @@ -251,10 +252,10 @@ internal partial class ApiService : ApplicationService {}, - OnChallenge = async _ => {}, - OnMessageReceived = async _ => {}, - OnTokenValidated = async _ => {}, + OnAuthenticationFailed = async _ => { }, + OnChallenge = async _ => { }, + OnMessageReceived = async _ => { }, + OnTokenValidated = async _ => { }, }; SymmetricSecurityKey issuerKey = new(tokenGenerationOptions.SecretData); options.TokenValidationParameters.IssuerSigningKey = issuerKey; diff --git a/Intersect.Server/Web/Net7/Swagger/Filters/LookupKeySchemaFilter.cs b/Intersect.Server/Web/Net7/Swagger/Filters/LookupKeySchemaFilter.cs index ac675d91b9..1c9e197af8 100644 --- a/Intersect.Server/Web/Net7/Swagger/Filters/LookupKeySchemaFilter.cs +++ b/Intersect.Server/Web/Net7/Swagger/Filters/LookupKeySchemaFilter.cs @@ -1,5 +1,5 @@ +using Intersect.Server.Collections.Indexing; using Intersect.Server.Localization; -using Intersect.Server.Web.RestApi.Payloads; using Microsoft.OpenApi.Any; using Microsoft.OpenApi.Models; using Swashbuckle.AspNetCore.SwaggerGen; diff --git a/Intersect.Server/Web/Net7/Swagger/Filters/MetadataOperationFilter.cs b/Intersect.Server/Web/Net7/Swagger/Filters/MetadataOperationFilter.cs index 27aef4a7c9..a3a4e8fdc6 100644 --- a/Intersect.Server/Web/Net7/Swagger/Filters/MetadataOperationFilter.cs +++ b/Intersect.Server/Web/Net7/Swagger/Filters/MetadataOperationFilter.cs @@ -1,4 +1,5 @@ -using Intersect.Server.Web.RestApi.Payloads; +using Intersect.Server.Collections.Indexing; +using Intersect.Server.Web.RestApi.Types; using Intersect.Server.Web.Swagger.Extensions; using Microsoft.AspNetCore.Mvc.Controllers; using Microsoft.OpenApi.Models; diff --git a/Intersect.Server/Web/RestApi/Payloads/AdminChange.cs b/Intersect.Server/Web/RestApi/Payloads/AdminChange.cs deleted file mode 100644 index 34f0965636..0000000000 --- a/Intersect.Server/Web/RestApi/Payloads/AdminChange.cs +++ /dev/null @@ -1,18 +0,0 @@ -using System.ComponentModel.DataAnnotations.Schema; - -using Newtonsoft.Json; - -namespace Intersect.Server.Web.RestApi.Payloads -{ - - public partial struct AdminChange - { - - [JsonIgnore, NotMapped] - public bool IsValid => !string.IsNullOrWhiteSpace(New); - - public string New { get; set; } - - } - -} diff --git a/Intersect.Server/Web/RestApi/Payloads/AuthorizedChange.cs b/Intersect.Server/Web/RestApi/Payloads/AuthorizedChange.cs deleted file mode 100644 index c55a4cf158..0000000000 --- a/Intersect.Server/Web/RestApi/Payloads/AuthorizedChange.cs +++ /dev/null @@ -1,20 +0,0 @@ -using System.ComponentModel.DataAnnotations.Schema; - -using Newtonsoft.Json; - -namespace Intersect.Server.Web.RestApi.Payloads -{ - - public partial struct AuthorizedChange - { - - [JsonIgnore, NotMapped] - public bool IsValid => !string.IsNullOrWhiteSpace(Authorization) && !string.IsNullOrWhiteSpace(New); - - public string Authorization { get; set; } - - public string New { get; set; } - - } - -} diff --git a/Intersect.Server/Web/RestApi/Payloads/ChatMessage.cs b/Intersect.Server/Web/RestApi/Payloads/ChatMessage.cs deleted file mode 100644 index 450e3a9fa7..0000000000 --- a/Intersect.Server/Web/RestApi/Payloads/ChatMessage.cs +++ /dev/null @@ -1,61 +0,0 @@ -using System.ComponentModel; -using System.Globalization; -using Microsoft.AspNetCore.Http; -using Microsoft.AspNetCore.Routing; -using Newtonsoft.Json; - -namespace Intersect.Server.Web.RestApi.Payloads -{ - - //[TypeConverter(typeof(Converter))] - public partial struct ChatMessage - { - - public string Message { get; set; } - - public Color Color { get; set; } - - public string Target { get; set; } - - public partial class Converter : TypeConverter - { - - public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType) - { - return typeof(string) == sourceType; - } - - public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value) - { - if (value == null) - { - return default(ChatMessage); - } - - if (typeof(string) != value.GetType()) - { - throw new ArgumentException(); - } - - return JsonConvert.DeserializeObject(value as string); - } - - } - - internal sealed partial class RouteConstraint : IRouteConstraint - { - public bool Match( - HttpContext httpContext, - IRouter route, - string routeKey, - RouteValueDictionary values, - RouteDirection routeDirection - ) - { - return values.TryGetValue(routeKey, out var value) && value != null; - } - } - - } - -} diff --git a/Intersect.Server/Web/RestApi/Payloads/ClassChange.cs b/Intersect.Server/Web/RestApi/Payloads/ClassChange.cs deleted file mode 100644 index c08f0cefdc..0000000000 --- a/Intersect.Server/Web/RestApi/Payloads/ClassChange.cs +++ /dev/null @@ -1,13 +0,0 @@ -using System; - -namespace Intersect.Server.Web.RestApi.Payloads -{ - - public partial struct ClassChange - { - - public Guid ClassId { get; set; } - - } - -} diff --git a/Intersect.Server/Web/RestApi/Payloads/GuildRank.cs b/Intersect.Server/Web/RestApi/Payloads/GuildRank.cs deleted file mode 100644 index a3436ddc9f..0000000000 --- a/Intersect.Server/Web/RestApi/Payloads/GuildRank.cs +++ /dev/null @@ -1,13 +0,0 @@ -using System; - -namespace Intersect.Server.Web.RestApi.Payloads -{ - - public partial struct GuildRank - { - - public int Rank { get; set; } - - } - -} diff --git a/Intersect.Server/Web/RestApi/Payloads/IpAddress.cs b/Intersect.Server/Web/RestApi/Payloads/IpAddress.cs deleted file mode 100644 index dc16bc92f3..0000000000 --- a/Intersect.Server/Web/RestApi/Payloads/IpAddress.cs +++ /dev/null @@ -1,17 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace Intersect.Server.Web.RestApi.Payloads -{ - public partial class IpAddress - { - public string Ip { get; set; } - - public DateTime LastUsed { get; set; } - - public Dictionary OtherUsers { get; set; } = new Dictionary(); - } -} diff --git a/Intersect.Server/Web/RestApi/Payloads/ItemInfo.cs b/Intersect.Server/Web/RestApi/Payloads/ItemInfo.cs deleted file mode 100644 index 206d68756b..0000000000 --- a/Intersect.Server/Web/RestApi/Payloads/ItemInfo.cs +++ /dev/null @@ -1,17 +0,0 @@ -using System; - -namespace Intersect.Server.Web.RestApi.Payloads -{ - - public partial struct ItemInfo - { - - public Guid ItemId { get; set; } - - public int Quantity { get; set; } - - public bool BankOverflow { get; set; } - - } - -} diff --git a/Intersect.Server/Web/RestApi/Payloads/LevelChange.cs b/Intersect.Server/Web/RestApi/Payloads/LevelChange.cs deleted file mode 100644 index 8a2532d8c1..0000000000 --- a/Intersect.Server/Web/RestApi/Payloads/LevelChange.cs +++ /dev/null @@ -1,13 +0,0 @@ -using System; - -namespace Intersect.Server.Web.RestApi.Payloads -{ - - public partial struct LevelChange - { - - public int Level { get; set; } - - } - -} diff --git a/Intersect.Server/Web/RestApi/Payloads/NameChange.cs b/Intersect.Server/Web/RestApi/Payloads/NameChange.cs deleted file mode 100644 index 1dbfdf0ec0..0000000000 --- a/Intersect.Server/Web/RestApi/Payloads/NameChange.cs +++ /dev/null @@ -1,13 +0,0 @@ -using System; - -namespace Intersect.Server.Web.RestApi.Payloads -{ - - public partial struct NameChange - { - - public string Name { get; set; } - - } - -} diff --git a/Intersect.Server/Web/RestApi/Payloads/PasswordValidation.cs b/Intersect.Server/Web/RestApi/Payloads/PasswordValidation.cs deleted file mode 100644 index 6896ae2db5..0000000000 --- a/Intersect.Server/Web/RestApi/Payloads/PasswordValidation.cs +++ /dev/null @@ -1,11 +0,0 @@ -namespace Intersect.Server.Web.RestApi.Payloads -{ - - public partial struct PasswordValidation - { - - public string Password { get; set; } - - } - -} diff --git a/Intersect.Server/Web/RestApi/Payloads/SpellInfo.cs b/Intersect.Server/Web/RestApi/Payloads/SpellInfo.cs deleted file mode 100644 index 4b52ac7a1c..0000000000 --- a/Intersect.Server/Web/RestApi/Payloads/SpellInfo.cs +++ /dev/null @@ -1,13 +0,0 @@ -using System; - -namespace Intersect.Server.Web.RestApi.Payloads -{ - - public partial struct SpellInfo - { - - public Guid SpellId { get; set; } - - } - -} diff --git a/Intersect.Server/Web/RestApi/Payloads/UserInfo.cs b/Intersect.Server/Web/RestApi/Payloads/UserInfo.cs deleted file mode 100644 index 04d8ffe193..0000000000 --- a/Intersect.Server/Web/RestApi/Payloads/UserInfo.cs +++ /dev/null @@ -1,15 +0,0 @@ -namespace Intersect.Server.Web.RestApi.Payloads -{ - - public partial struct UserInfo - { - - public string Username { get; set; } - - public string Password { get; set; } - - public string Email { get; set; } - - } - -} diff --git a/Intersect.Server/Web/RestApi/Payloads/VariableValueBody.cs b/Intersect.Server/Web/RestApi/Payloads/VariableValueBody.cs deleted file mode 100644 index e90c201524..0000000000 --- a/Intersect.Server/Web/RestApi/Payloads/VariableValueBody.cs +++ /dev/null @@ -1,11 +0,0 @@ -namespace Intersect.Server.Web.RestApi.Payloads -{ - - public partial struct VariableValueBody - { - - public dynamic Value { get; set; } - - } - -} diff --git a/Intersect.Server/Web/RestApi/Routes/V1/ChatController.cs b/Intersect.Server/Web/RestApi/Routes/V1/ChatController.cs index 5558f55207..f9e8546945 100644 --- a/Intersect.Server/Web/RestApi/Routes/V1/ChatController.cs +++ b/Intersect.Server/Web/RestApi/Routes/V1/ChatController.cs @@ -1,6 +1,10 @@ -using Intersect.Server.General; +using System.Net; +using Intersect.Server.Collections.Indexing; +using Intersect.Server.General; using Intersect.Server.Networking; -using Intersect.Server.Web.RestApi.Payloads; +using Intersect.Server.Web.Http; +using Intersect.Server.Web.RestApi.Types; +using Intersect.Server.Web.RestApi.Types.Chat; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; @@ -11,29 +15,18 @@ namespace Intersect.Server.Web.RestApi.Routes.V1 public sealed partial class ChatController : IntersectController { [HttpPost] - [HttpPost("global")] - public object SendGlobal([FromBody] ChatMessage chatMessage) + [ProducesResponseType(typeof(ChatMessageResponseBody), (int)HttpStatusCode.OK, ContentTypes.Json)] + public IActionResult SendGlobal([FromBody] ChatMessage chatMessage) { - try - { - PacketSender.SendGlobalMsg( - chatMessage.Message, chatMessage.Color ?? CustomColors.Chat.AnnouncementChat, chatMessage.Target - ); - - return new - { - success = true, - chatMessage - }; - } - catch (Exception exception) - { - return InternalServerError(exception.Message); - } + PacketSender.SendGlobalMsg(chatMessage.Message, chatMessage.Color ?? CustomColors.Chat.AnnouncementChat, chatMessage.Target); + return Ok(new ChatMessageResponseBody(true, chatMessage)); } [HttpPost("direct/{lookupKey:LookupKey}")] - public object SendDirect(LookupKey lookupKey, [FromBody] ChatMessage chatMessage) + [ProducesResponseType(typeof(StatusMessageResponseBody), (int)HttpStatusCode.BadRequest, ContentTypes.Json)] + [ProducesResponseType(typeof(StatusMessageResponseBody), (int)HttpStatusCode.NotFound, ContentTypes.Json)] + [ProducesResponseType(typeof(ChatMessageLookupKeyResponseBody), (int)HttpStatusCode.OK, ContentTypes.Json)] + public IActionResult SendDirect(LookupKey lookupKey, [FromBody] ChatMessage chatMessage) { if (lookupKey.IsInvalid) { @@ -51,56 +44,42 @@ public object SendDirect(LookupKey lookupKey, [FromBody] ChatMessage chatMessage return NotFound($@"No player found for '{lookupKey}'."); } - try - { - PacketSender.SendChatMsg( - client.Entity, chatMessage.Message, Enums.ChatMessageType.PM, chatMessage.Color ?? CustomColors.Chat.PlayerMsg, - chatMessage.Target - ); + PacketSender.SendChatMsg( + client.Entity, + chatMessage.Message, + Enums.ChatMessageType.PM, + chatMessage.Color ?? CustomColors.Chat.PlayerMsg, + chatMessage.Target + ); - return new - { - success = true, - lookupKey, - chatMessage - }; - } - catch (Exception exception) - { - return InternalServerError(exception.Message); - } + return Ok(new ChatMessageLookupKeyResponseBody(lookupKey, true, chatMessage)); } [HttpPost("proximity/{mapId:guid}")] - public object SendProximity(Guid mapId, [FromBody] ChatMessage chatMessage) + [ProducesResponseType(typeof(StatusMessageResponseBody), (int)HttpStatusCode.BadRequest, ContentTypes.Json)] + [ProducesResponseType(typeof(StatusMessageResponseBody), (int)HttpStatusCode.NotFound, ContentTypes.Json)] + [ProducesResponseType(typeof(ChatMessageMapResponseBody), (int)HttpStatusCode.OK, ContentTypes.Json)] + public IActionResult SendProximity(Guid mapId, [FromBody] ChatMessage chatMessage) { if (Guid.Empty == mapId) { return BadRequest($@"Invalid map id '{mapId}'."); } - try - { - if (PacketSender.SendProximityMsg( - chatMessage.Message, Enums.ChatMessageType.Local, mapId, chatMessage.Color ?? CustomColors.Chat.ProximityMsg, chatMessage.Target - )) - { - return new - { - success = true, - mapId, - chatMessage - }; - } + bool success = PacketSender.SendProximityMsg( + chatMessage.Message, + Enums.ChatMessageType.Local, + mapId, + chatMessage.Color ?? CustomColors.Chat.ProximityMsg, + chatMessage.Target + ); - return NotFound($@"No map found for '{mapId}'."); - } - catch (Exception exception) + if (!success) { - return InternalServerError(exception.Message); + return NotFound($@"No map found for '{mapId}'."); } - } - // TODO: "party" message endpoint? + return Ok(new ChatMessageMapResponseBody(mapId, true, chatMessage)); + } } } diff --git a/Intersect.Server/Web/RestApi/Routes/V1/DemoController.cs b/Intersect.Server/Web/RestApi/Routes/V1/DemoController.cs index 6fcb164073..3f4e040428 100644 --- a/Intersect.Server/Web/RestApi/Routes/V1/DemoController.cs +++ b/Intersect.Server/Web/RestApi/Routes/V1/DemoController.cs @@ -1,4 +1,7 @@ -using Microsoft.AspNetCore.Authorization; +using System.Net; +using Intersect.Server.Web.Http; +using Intersect.Server.Web.RestApi.Types.Demo; +using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; namespace Intersect.Server.Web.RestApi.Routes.V1 @@ -9,16 +12,12 @@ public sealed partial class DemoController : IntersectController { [HttpGet] [AllowAnonymous] - public string Default() - { - return "GET:demo"; - } + [ProducesResponseType(typeof(DemoResponseBody), (int)HttpStatusCode.OK, ContentTypes.Json)] + public IActionResult Default() => Ok(new DemoResponseBody("GET:demo")); [HttpGet("authorize")] [Authorize] - public object Authorize() - { - return "GET:demo/authorize"; - } + [ProducesResponseType(typeof(DemoResponseBody), (int)HttpStatusCode.OK, ContentTypes.Json)] + public IActionResult Authorize() => Ok(new DemoResponseBody("GET:demo/authorize")); } } diff --git a/Intersect.Server/Web/RestApi/Routes/V1/GameObjectController.cs b/Intersect.Server/Web/RestApi/Routes/V1/GameObjectController.cs index c3824ec29a..5ed58e94ec 100644 --- a/Intersect.Server/Web/RestApi/Routes/V1/GameObjectController.cs +++ b/Intersect.Server/Web/RestApi/Routes/V1/GameObjectController.cs @@ -1,10 +1,9 @@ -using System.Net; +using System.Net; using Intersect.Enums; using Intersect.GameObjects; using Intersect.GameObjects.Events; using Intersect.Models; using Intersect.Server.Web.Http; -using Intersect.Server.Web.RestApi.Payloads; using Intersect.Server.Web.RestApi.Types; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; @@ -17,7 +16,6 @@ public sealed partial class GameObjectController : IntersectController { [HttpGet("{gameObjectType}")] [ProducesResponseType(typeof(StatusMessageResponseBody), (int)HttpStatusCode.BadRequest, ContentTypes.Json)] - [ProducesResponseType(typeof(StatusMessageResponseBody), (int)HttpStatusCode.NotFound, ContentTypes.Json)] [ProducesResponseType(typeof(DataPage), (int)HttpStatusCode.OK, ContentTypes.Json)] public IActionResult List(GameObjectType gameObjectType, [FromQuery] PagingInfo pageInfo) { @@ -41,6 +39,7 @@ public IActionResult List(GameObjectType gameObjectType, [FromQuery] PagingInfo var total = gameObjectType == GameObjectType.Event ? lookup.Count(obj => ((EventBase)obj.Value).CommonEvent) : lookup.Count; + return Ok( new DataPage( Total: total, @@ -53,6 +52,8 @@ public IActionResult List(GameObjectType gameObjectType, [FromQuery] PagingInfo } [HttpGet("{gameObjectType}/names")] + [ProducesResponseType(typeof(StatusMessageResponseBody), (int)HttpStatusCode.BadRequest, ContentTypes.Json)] + [ProducesResponseType(typeof(DataPage), (int)HttpStatusCode.OK, ContentTypes.Json)] public IActionResult Names(GameObjectType gameObjectType) { if (!gameObjectType.TryGetLookup(out var lookup)) @@ -80,7 +81,10 @@ public IActionResult Names(GameObjectType gameObjectType) } [HttpGet("{gameObjectType}/{objectId:guid}")] - public object GameObjectById(GameObjectType gameObjectType, Guid objectId) + [ProducesResponseType(typeof(StatusMessageResponseBody), (int)HttpStatusCode.BadRequest, ContentTypes.Json)] + [ProducesResponseType(typeof(StatusMessageResponseBody), (int)HttpStatusCode.NotFound, ContentTypes.Json)] + [ProducesResponseType(typeof(IDatabaseObject), (int)HttpStatusCode.OK, ContentTypes.Json)] + public IActionResult GameObjectById(GameObjectType gameObjectType, Guid objectId) { if (objectId == default) { @@ -98,6 +102,7 @@ public object GameObjectById(GameObjectType gameObjectType, Guid objectId) } [HttpGet("time")] + [ProducesResponseType(typeof(TimeBase), (int)HttpStatusCode.OK, ContentTypes.Json)] public IActionResult Time() => Ok(TimeBase.GetTimeBase()); } } diff --git a/Intersect.Server/Web/RestApi/Routes/V1/GuildController.cs b/Intersect.Server/Web/RestApi/Routes/V1/GuildController.cs index 0f4fb550e9..828e58a1e8 100644 --- a/Intersect.Server/Web/RestApi/Routes/V1/GuildController.cs +++ b/Intersect.Server/Web/RestApi/Routes/V1/GuildController.cs @@ -1,12 +1,14 @@ -using System.Net; +using System.Net; using Intersect.Enums; using Intersect.GameObjects; +using Intersect.Server.Collections.Indexing; +using Intersect.Server.Collections.Sorting; using Intersect.Server.Database.PlayerData.Players; using Intersect.Server.Entities; using Intersect.Server.Localization; using Intersect.Server.Web.Http; -using Intersect.Server.Web.RestApi.Payloads; using Intersect.Server.Web.RestApi.Types; +using Intersect.Server.Web.RestApi.Types.Guild; using Intersect.Utilities; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; @@ -17,26 +19,11 @@ namespace Intersect.Server.Web.RestApi.Routes.V1 [Authorize] public sealed partial class GuildController : IntersectController { - [HttpPost] - [Obsolete("The appropriate verb for retrieving a list of records is GET not POST")] - public object ListPost([FromBody] PagingInfo pageInfo) - { - pageInfo.Page = Math.Max(pageInfo.Page, 0); - pageInfo.PageSize = Math.Max(Math.Min(pageInfo.PageSize, 100), 5); - - var entries = Guild.List(null, null, SortDirection.Ascending, pageInfo.Page * pageInfo.PageSize, pageInfo.PageSize, out int entryTotal); - - return Ok(new DataPage>( - Total: entryTotal, - Page: pageInfo.Page, - PageSize: pageInfo.PageSize, - Count: entries.Count, - Values: entries - )); - } + #region Lookup [HttpGet] - public DataPage> List( + [ProducesResponseType(typeof(DataPage>), (int)HttpStatusCode.OK, ContentTypes.Json)] + public IActionResult List( [FromQuery] int page = 0, [FromQuery] int pageSize = 0, [FromQuery] int limit = PagingInfo.MaxPageSize, @@ -56,17 +43,19 @@ public DataPage> List( values = values.Take(limit).ToList(); } - return new DataPage>( + return Ok(new DataPage>( Total: total, Page: page, PageSize: pageSize, Count: values.Count, Values: values - ); + )); } [HttpGet("{guildId:guid}")] - public object GuildGet(Guid guildId) + [ProducesResponseType(typeof(StatusMessageResponseBody), (int)HttpStatusCode.BadRequest, ContentTypes.Json)] + [ProducesResponseType(typeof(Guild), (int)HttpStatusCode.OK, ContentTypes.Json)] + public IActionResult GuildGet(Guid guildId) { var guild = Guild.LoadGuild(guildId); @@ -75,11 +64,18 @@ public object GuildGet(Guid guildId) return BadRequest($@"Guild not found: ${guildId}."); } - return guild; + return Ok(guild); } + #endregion + + #region Basic Operations + [HttpPost("{guildId:guid}/name")] - public object ChangeName(Guid guildId, [FromBody] NameChange change) + [ProducesResponseType(typeof(StatusMessageResponseBody), (int)HttpStatusCode.BadRequest, ContentTypes.Json)] + [ProducesResponseType(typeof(StatusMessageResponseBody), (int)HttpStatusCode.NotFound, ContentTypes.Json)] + [ProducesResponseType(typeof(Guild), (int)HttpStatusCode.OK, ContentTypes.Json)] + public IActionResult ChangeName(Guid guildId, [FromBody] NameChangePayload change) { if (!FieldChecking.IsValidGuildName(change.Name, Strings.Regex.GuildName)) { @@ -94,7 +90,7 @@ public object ChangeName(Guid guildId, [FromBody] NameChange change) if (guild.Rename(change.Name)) { - return guild; + return Ok(guild); } return BadRequest($@"Invalid name, or name already taken."); @@ -102,7 +98,9 @@ public object ChangeName(Guid guildId, [FromBody] NameChange change) [Route("{guildId:guid}")] [HttpDelete] - public object DisbandGuild(Guid guildId) + [ProducesResponseType(typeof(StatusMessageResponseBody), (int)HttpStatusCode.NotFound, ContentTypes.Json)] + [ProducesResponseType(typeof(Guild), (int)HttpStatusCode.OK, ContentTypes.Json)] + public IActionResult DisbandGuild(Guid guildId) { var guild = Guild.LoadGuild(guildId); if (guild == null) @@ -111,12 +109,12 @@ public object DisbandGuild(Guid guildId) } Guild.DeleteGuild(guild); - - return guild; + return Ok(guild); } [HttpGet("{guildId:guid}/members")] - public DataPage Members( + [ProducesResponseType(typeof(DataPage), (int)HttpStatusCode.OK, ContentTypes.Json)] + public IActionResult Members( Guid guildId, [FromQuery] int page = 0, [FromQuery] int pageSize = 0, @@ -137,58 +135,56 @@ public DataPage Members( values = values.Take(limit).ToList(); } - return new DataPage( + return Ok(new DataPage( Total: total, Page: page, PageSize: pageSize, Count: values.Count, Values: values - ); + )); } [HttpPost("{guildId:guid}/kick/{lookupKey:LookupKey}")] - public object Kick(Guid guildId, LookupKey lookupKey) + [ProducesResponseType(typeof(StatusMessageResponseBody), (int)HttpStatusCode.BadRequest, ContentTypes.Json)] + [ProducesResponseType(typeof(StatusMessageResponseBody), (int)HttpStatusCode.NotFound, ContentTypes.Json)] + [ProducesResponseType(typeof(Player), (int)HttpStatusCode.OK, ContentTypes.Json)] + public IActionResult Kick(Guid guildId, LookupKey lookupKey) { if (lookupKey.IsInvalid) { return BadRequest(lookupKey.IsIdInvalid ? @"Invalid player id." : @"Invalid player name."); } - var guild = Guild.LoadGuild(guildId); - if (guild == null) { - return BadRequest($@"Guild does not exist."); + return NotFound($@"Guild does not exist with id {guildId}."); } - var (client, player) = Player.Fetch(lookupKey); - - //Player not found - if (player == null) + if (!Player.TryFetch(lookupKey, out var player)) { - return BadRequest($@"Player not found."); + return NotFound($@"Player not found with lookup key {lookupKey}."); } - //Player is not a member of this guild if (!guild.IsMember(player.Id)) { return BadRequest($@"{player.Name} is not a member of {guild.Name}."); } - //Cannot kick the owner! if (player.GuildRank == 0) { return BadRequest($@"Cannot kick a guild owner, transfer ownership first."); } guild.TryRemoveMember(player.Id, player, default, Database.Logging.Entities.GuildHistory.GuildActivityType.Kicked); - - return player; + return Ok(player); } [HttpPost("{guildId:guid}/rank/{lookupKey:LookupKey}")] - public object SetRank(Guid guildId, LookupKey lookupKey, [FromBody] GuildRank guildRank) + [ProducesResponseType(typeof(StatusMessageResponseBody), (int)HttpStatusCode.BadRequest, ContentTypes.Json)] + [ProducesResponseType(typeof(StatusMessageResponseBody), (int)HttpStatusCode.NotFound, ContentTypes.Json)] + [ProducesResponseType(typeof(Player), (int)HttpStatusCode.OK, ContentTypes.Json)] + public IActionResult SetRank(Guid guildId, LookupKey lookupKey, [FromBody] GuildRankPayload guildRank) { if (lookupKey.IsInvalid) { @@ -201,17 +197,14 @@ public object SetRank(Guid guildId, LookupKey lookupKey, [FromBody] GuildRank gu } var guild = Guild.LoadGuild(guildId); - if (guild == null) { - return BadRequest($@"Guild does not exist."); + return NotFound($@"Guild does not exist with id {guildId}."); } - var (_, player) = Player.Fetch(lookupKey); - - if (player == null) + if (!Player.TryFetch(lookupKey, out var player)) { - return BadRequest($@"Player not found."); + return NotFound($@"Player not found with lookup key {lookupKey}."); } if (!guild.IsMember(player.Id)) @@ -219,19 +212,20 @@ public object SetRank(Guid guildId, LookupKey lookupKey, [FromBody] GuildRank gu return BadRequest($@"{player.Name} is not a member of {guild.Name}."); } - // Cannot kick the owner! if (player.GuildRank == 0) { return BadRequest($@"Cannot change rank of the guild owner, transfer ownership first!"); } guild.SetPlayerRank(player.Id, player, guildRank.Rank, default); - - return player; + return Ok(player); } [HttpPost("{guildId:guid}/transfer/{lookupKey:LookupKey}")] - public object Transfer(Guid guildId, LookupKey lookupKey) + [ProducesResponseType(typeof(StatusMessageResponseBody), (int)HttpStatusCode.BadRequest, ContentTypes.Json)] + [ProducesResponseType(typeof(StatusMessageResponseBody), (int)HttpStatusCode.NotFound, ContentTypes.Json)] + [ProducesResponseType(typeof(Player), (int)HttpStatusCode.OK, ContentTypes.Json)] + public IActionResult Transfer(Guid guildId, LookupKey lookupKey) { if (lookupKey.IsInvalid) { @@ -239,17 +233,14 @@ public object Transfer(Guid guildId, LookupKey lookupKey) } var guild = Guild.LoadGuild(guildId); - if (guild == null) { return NotFound($@"No guild found with id {guildId}"); } - var (_, player) = Player.Fetch(lookupKey); - - if (player == null) + if (!Player.TryFetch(lookupKey, out var player)) { - return BadRequest($@"Player not found."); + return BadRequest($@"Player not found with lookup key {lookupKey}."); } if (!guild.IsMember(player.Id)) @@ -257,15 +248,17 @@ public object Transfer(Guid guildId, LookupKey lookupKey) return BadRequest($@"{player.Name} is not a member of {guild.Name}."); } - // Cannot kick the owner! if (player.GuildRank == 0) { return BadRequest($@"Cannot transfer ownership of a guild to ones self."); } - guild.TransferOwnership(player); + if (!guild.TransferOwnership(player)) + { + return BadRequest($@"Failed to transfer ownership."); + } - return player; + return Ok(player); } [HttpGet("{guildId:guid}/bank")] @@ -282,6 +275,10 @@ public IActionResult ItemsListBank(Guid guildId) return Ok(guild.Bank.Where(slot => !slot.IsEmpty)); } + #endregion + + #region Variables + [HttpGet("{guildId:guid}/variables")] [ProducesResponseType(typeof(StatusMessageResponseBody), (int)HttpStatusCode.NotFound, ContentTypes.Json)] [ProducesResponseType(typeof(IEnumerable), (int)HttpStatusCode.OK, ContentTypes.Json)] @@ -385,7 +382,7 @@ public IActionResult GuildVariableSet(Guid guildId, Guid variableId, [FromBody] if (changed) { guild.StartCommonEventsWithTriggerForAll(CommonEventTrigger.GuildVariableChange, string.Empty, variableId.ToString()); - guild.UpdatedVariables.AddOrUpdate( + _ = guild.UpdatedVariables.AddOrUpdate( variableId, variableDescriptor, (_, _) => variableDescriptor @@ -394,5 +391,29 @@ public IActionResult GuildVariableSet(Guid guildId, Guid variableId, [FromBody] return Ok(variable); } + + #endregion + + #region Obsolete + + [HttpPost] + [Obsolete("The appropriate verb for retrieving a list of records is GET not POST")] + public IActionResult ListPost([FromBody] PagingInfo pageInfo) + { + pageInfo.Page = Math.Max(pageInfo.Page, 0); + pageInfo.PageSize = Math.Max(Math.Min(pageInfo.PageSize, 100), 5); + + var entries = Guild.List(null, null, SortDirection.Ascending, pageInfo.Page * pageInfo.PageSize, pageInfo.PageSize, out int entryTotal); + + return Ok(new DataPage>( + Total: entryTotal, + Page: pageInfo.Page, + PageSize: pageInfo.PageSize, + Count: entries.Count, + Values: entries + )); + } + + #endregion } } diff --git a/Intersect.Server/Web/RestApi/Routes/V1/InfoController.cs b/Intersect.Server/Web/RestApi/Routes/V1/InfoController.cs index b2cd3dc4f4..62cbea8c42 100644 --- a/Intersect.Server/Web/RestApi/Routes/V1/InfoController.cs +++ b/Intersect.Server/Web/RestApi/Routes/V1/InfoController.cs @@ -1,65 +1,53 @@ -using Intersect.Enums; +using System.Net; +using Intersect.Enums; using Intersect.Server.General; using Intersect.Server.Metrics; using Intersect.Server.Web.Http; +using Intersect.Server.Web.RestApi.Types; +using Intersect.Server.Web.RestApi.Types.Info; using Intersect.Utilities; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; namespace Intersect.Server.Web.RestApi.Routes.V1 { - [Route("api/v1/info")] [Authorize] public sealed partial class InfoController : Controller { + [HttpGet] + [AllowAnonymous] + [ProducesResponseType(typeof(InfoResponseBody), (int)HttpStatusCode.OK, ContentTypes.Json)] + public IActionResult Default() => Ok(new InfoResponseBody(Options.Instance.GameName, Options.ServerPort)); [HttpGet("authorized")] [Authorize] - public object Authorized() - { - return new - { - authorized = true, - }; - } - - [HttpGet] - [AllowAnonymous] - public object Default() - { - return new - { - name = Options.Instance.GameName, - port = Options.ServerPort, - }; - } + [ProducesResponseType(typeof(AuthorizedResponseBody), (int)HttpStatusCode.OK, ContentTypes.Json)] + public IActionResult Authorized() => Ok(new AuthorizedResponseBody()); [HttpGet("config")] - public object Config() - { - return Options.Instance; - } + [ProducesResponseType(typeof(Options), (int)HttpStatusCode.OK, ContentTypes.Json)] + public IActionResult Config() => Ok(Options.Instance); [HttpGet("config/stats")] - public object CombatStats() => Enum.GetNames(); + [ProducesResponseType(typeof(IEnumerable), (int)HttpStatusCode.OK, ContentTypes.Json)] + public IActionResult CombatStats() => Ok(Enum.GetNames()); [HttpGet("stats")] - public object Stats() - { - return new - { - uptime = Timing.Global.Milliseconds, - cps = Globals.Cps, - connectedClients = Globals.Clients?.Count, - onlineCount = Globals.OnlineList?.Count - }; - } + [ProducesResponseType(typeof(InfoStatsResponseBody), (int)HttpStatusCode.OK, ContentTypes.Json)] + public IActionResult Stats() => Ok(new InfoStatsResponseBody(Timing.Global.Milliseconds, Globals.Cps, Globals.Clients?.Count, Globals.OnlineList?.Count)); [HttpGet("metrics")] - public object StatsMetrics() + [ProducesResponseType(typeof(StatusMessageResponseBody), (int)HttpStatusCode.NotFound, ContentTypes.Json)] + [ProducesResponseType(typeof(ContentResult), (int)HttpStatusCode.OK, ContentTypes.Json)] + public IActionResult StatsMetrics() { - return Content(MetricsRoot.Instance.Metrics, ContentTypes.Json); + if (MetricsRoot.Instance == default) + { + return NotFound("Metrics not found or they're disabled."); + } + + return Ok(Content(MetricsRoot.Instance?.Metrics, ContentTypes.Json)); } } } diff --git a/Intersect.Server/Web/RestApi/Routes/V1/LogsController.cs b/Intersect.Server/Web/RestApi/Routes/V1/LogsController.cs index a6491f9b03..8406efa42d 100644 --- a/Intersect.Server/Web/RestApi/Routes/V1/LogsController.cs +++ b/Intersect.Server/Web/RestApi/Routes/V1/LogsController.cs @@ -1,8 +1,11 @@ -using Intersect.Enums; +using System.Net; +using Intersect.Enums; +using Intersect.Server.Collections.Sorting; using Intersect.Server.Database; using Intersect.Server.Database.Logging.Entities; -using Intersect.Server.Web.RestApi.Payloads; +using Intersect.Server.Web.Http; using Intersect.Server.Web.RestApi.Types; +using Intersect.Server.Web.RestApi.Types.Logs; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; @@ -13,9 +16,9 @@ namespace Intersect.Server.Web.RestApi.Routes.V1 [Authorize] public sealed partial class LogsController : IntersectController { - [HttpGet("chat")] - public DataPage ListChat( + [ProducesResponseType(typeof(DataPage), (int)HttpStatusCode.OK, ContentTypes.Json)] + public IActionResult ListChat( [FromQuery] int page = 0, [FromQuery] int pageSize = 0, [FromQuery] int limit = PagingInfo.MaxPageSize, @@ -31,72 +34,71 @@ public DataPage ListChat( pageSize = Math.Max(Math.Min(pageSize, PagingInfo.MaxPageSize), PagingInfo.MinPageSize); limit = Math.Max(Math.Min(limit, pageSize), 1); - using (var context = DbInterface.CreateLoggingContext()) - { - var messages = context.ChatHistory.AsQueryable(); + using var context = DbInterface.CreateLoggingContext(); + var messages = context.ChatHistory.AsQueryable(); - if (messageType > -1) - { - messages = messages.Where(m => m.MessageType == (ChatMessageType)messageType); - } + if (messageType > -1) + { + messages = messages.Where(m => m.MessageType == (ChatMessageType)messageType); + } - if (userId != Guid.Empty) - { - messages = messages.Where(m => m.UserId == userId); - } + if (userId != Guid.Empty) + { + messages = messages.Where(m => m.UserId == userId); + } - if (playerId != Guid.Empty) + if (playerId != Guid.Empty) + { + if (messageType == (int)ChatMessageType.PM) { - if (messageType == (int)ChatMessageType.PM) - { - messages = messages.Where(m => m.PlayerId == playerId || m.TargetId == playerId); - } - else - { - messages = messages.Where(m => m.PlayerId == playerId); - } + messages = messages.Where(m => m.PlayerId == playerId || m.TargetId == playerId); } - - if (guildId != Guid.Empty) + else { - messages = messages.Where(m => m.MessageType == ChatMessageType.Guild && m.TargetId == guildId); + messages = messages.Where(m => m.PlayerId == playerId); } + } - if (!string.IsNullOrWhiteSpace(search)) - { - messages = messages.Where(m => EF.Functions.Like(m.MessageText, $"%{search}%")); - } + if (guildId != Guid.Empty) + { + messages = messages.Where(m => m.MessageType == ChatMessageType.Guild && m.TargetId == guildId); + } - if (sortDirection == SortDirection.Ascending) - { - messages = messages.OrderBy(m => m.TimeStamp); - } - else - { - messages = messages.OrderByDescending(m => m.TimeStamp); - } + if (!string.IsNullOrWhiteSpace(search)) + { + messages = messages.Where(m => EF.Functions.Like(m.MessageText, $"%{search}%")); + } - var values = messages.Skip(page * pageSize).Take(pageSize).ToList(); + if (sortDirection == SortDirection.Ascending) + { + messages = messages.OrderBy(m => m.TimeStamp); + } + else + { + messages = messages.OrderByDescending(m => m.TimeStamp); + } - PopulateMessageNames(values); + var values = messages.Skip(page * pageSize).Take(pageSize).ToList(); - if (limit != pageSize) - { - values = values.Take(limit).ToList(); - } + PopulateMessageNames(values); - return new DataPage( - Total: messages.Count(), - Page: page, - PageSize: pageSize, - Count: values.Count, - Values: values - ); + if (limit != pageSize) + { + values = values.Take(limit).ToList(); } + + return Ok(new DataPage( + Total: messages.Count(), + Page: page, + PageSize: pageSize, + Count: values.Count, + Values: values + )); } [HttpGet("pm")] - public DataPage ListPMs( + [ProducesResponseType(typeof(DataPage), (int)HttpStatusCode.OK, ContentTypes.Json)] + public IActionResult ListPMs( [FromQuery] Guid player1Id, [FromQuery] Guid player2Id, [FromQuery] int page = 0, @@ -110,84 +112,95 @@ public DataPage ListPMs( pageSize = Math.Max(Math.Min(pageSize, PagingInfo.MaxPageSize), PagingInfo.MinPageSize); limit = Math.Max(Math.Min(limit, pageSize), 1); - using (var context = DbInterface.CreateLoggingContext()) - { - var messages = context.ChatHistory.AsQueryable(); - - messages = messages.Where(m => m.MessageType == ChatMessageType.PM); - - messages = messages.Where(m => (m.PlayerId == player1Id && m.TargetId == player2Id) || (m.PlayerId == player2Id && m.TargetId == player1Id)); + using var context = DbInterface.CreateLoggingContext(); + var messages = context.ChatHistory.AsQueryable(); - if (!string.IsNullOrWhiteSpace(search)) - { - messages = messages.Where(m => EF.Functions.Like(m.MessageText, $"%{search}%")); - } + messages = messages.Where(m => m.MessageType == ChatMessageType.PM); + messages = messages.Where(m => (m.PlayerId == player1Id && m.TargetId == player2Id) || (m.PlayerId == player2Id && m.TargetId == player1Id)); - if (sortDirection == SortDirection.Ascending) - { - messages = messages.OrderBy(m => m.TimeStamp); - } - else - { - messages = messages.OrderByDescending(m => m.TimeStamp); - } + if (!string.IsNullOrWhiteSpace(search)) + { + messages = messages.Where(m => EF.Functions.Like(m.MessageText, $"%{search}%")); + } - var values = messages.Skip(page * pageSize).Take(pageSize).ToList(); + if (sortDirection == SortDirection.Ascending) + { + messages = messages.OrderBy(m => m.TimeStamp); + } + else + { + messages = messages.OrderByDescending(m => m.TimeStamp); + } - PopulateMessageNames(values); + var values = messages.Skip(page * pageSize).Take(pageSize).ToList(); - if (limit != pageSize) - { - values = values.Take(limit).ToList(); - } + PopulateMessageNames(values); - return new DataPage( - Total: messages.Count(), - Page: page, - PageSize: pageSize, - Count: values.Count, - Values: values - ); + if (limit != pageSize) + { + values = values.Take(limit).ToList(); } + + return Ok(new DataPage( + Total: messages.Count(), + Page: page, + PageSize: pageSize, + Count: values.Count, + Values: values + )); } private void PopulateMessageNames(List messages) { - var userIds = messages.Where(m => m.UserId != Guid.Empty).GroupBy(m => m.UserId).Select(m => m.First().UserId).ToList(); - var playerIds = messages.Where(m => m.PlayerId != Guid.Empty).GroupBy(m => m.PlayerId).Select(m => m.First().PlayerId).ToList(); - var targetIds = messages.Where(m => m.TargetId != Guid.Empty && m.MessageType == ChatMessageType.PM).GroupBy(m => m.TargetId).Select(m => m.First().TargetId).ToList(); + var userIds = messages.Where(m => m.UserId != Guid.Empty) + .GroupBy(m => m.UserId) + .Select(m => m.First().UserId) + .ToList(); + + var playerIds = messages.Where(m => m.PlayerId != Guid.Empty) + .GroupBy(m => m.PlayerId) + .Select(m => m.First().PlayerId) + .ToList(); + + var targetIds = messages.Where(m => m.TargetId != Guid.Empty && m.MessageType == ChatMessageType.PM) + .GroupBy(m => m.TargetId) + .Select(m => m.First().TargetId) + .ToList(); var playerSet = new HashSet(playerIds); playerSet.UnionWith(targetIds); - using (var db = DbInterface.CreatePlayerContext(true)) - { - var users = db.Users.Where(u => userIds.Contains(u.Id)).Select(u => new KeyValuePair(u.Id, u.Name)).ToDictionary(p => p.Key, p => p.Value); - var players = db.Players.Where(p => playerSet.Contains(p.Id)).Select(p => new KeyValuePair(p.Id, p.Name)).ToDictionary(p => p.Key, p => p.Value); + using var db = DbInterface.CreatePlayerContext(true); + var users = db.Users.Where(u => userIds.Contains(u.Id)) + .Select(u => new KeyValuePair(u.Id, u.Name)) + .ToDictionary(p => p.Key, p => p.Value); + + var players = db.Players.Where(p => playerSet.Contains(p.Id)) + .Select(p => new KeyValuePair(p.Id, p.Name)) + .ToDictionary(p => p.Key, p => p.Value); - foreach (var msg in messages) + foreach (var msg in messages) + { + if (users.TryGetValue(msg.UserId, out var usernameValue)) { - if (users.ContainsKey(msg.UserId)) - { - msg.Username = users[msg.UserId]; - } + msg.Username = usernameValue; + } - if (players.ContainsKey(msg.PlayerId)) - { - msg.PlayerName = players[msg.PlayerId]; - } + if (players.TryGetValue(msg.PlayerId, out var playerNameValue)) + { + msg.PlayerName = playerNameValue; + } - if (players.ContainsKey(msg.TargetId)) - { - msg.TargetName = players[msg.TargetId]; - } + if (players.TryGetValue(msg.TargetId, out var targetNameValue)) + { + msg.TargetName = targetNameValue; } } - } [HttpGet("user/{userId:guid}/ip")] - public DataPage ListIpHistory( + [ProducesResponseType(typeof(DataPage), (int)HttpStatusCode.OK, ContentTypes.Json)] + public IActionResult ListIpHistory( Guid userId, [FromQuery] int page = 0, [FromQuery] int pageSize = 0, @@ -198,43 +211,47 @@ public DataPage ListIpHistory( pageSize = Math.Max(Math.Min(pageSize, PagingInfo.MaxPageSize), PagingInfo.MinPageSize); limit = Math.Max(Math.Min(limit, pageSize), 1); - using (var context = DbInterface.CreateLoggingContext()) - { - var history = context.UserActivityHistory.AsQueryable(); - var ipAddresses = history.Where(m => m.UserId == userId && m.UserId != Guid.Empty && !string.IsNullOrWhiteSpace(m.Ip)).OrderByDescending(m => m.TimeStamp).GroupBy(m => m.Ip).Select(m => new IpAddress { Ip = m.First().Ip, LastUsed = m.First().TimeStamp }); - var addresses = ipAddresses.Skip(page * pageSize).Take(pageSize).ToList(); + using var context = DbInterface.CreateLoggingContext(); + var history = context.UserActivityHistory.AsQueryable(); + var ipAddresses = history.Where(m => m.UserId == userId && m.UserId != Guid.Empty && !string.IsNullOrWhiteSpace(m.Ip)) + .OrderByDescending(m => m.TimeStamp) + .GroupBy(m => m.Ip) + .Select(m => new IpAddressResponseBody { Ip = m.First().Ip, LastUsed = m.First().TimeStamp }); - //Foreach IP Address Find Other Users - using (var ctx = DbInterface.CreatePlayerContext(true)) + var addresses = ipAddresses.Skip(page * pageSize).Take(pageSize).ToList(); + + using (var ctx = DbInterface.CreatePlayerContext(true)) + { + foreach (var addr in addresses) { - foreach (var addr in addresses) + var otherUsers = context.UserActivityHistory.Where(m => m.Ip == addr.Ip && m.UserId != userId && !string.IsNullOrWhiteSpace(m.Ip)) + .GroupBy(m => m.UserId) + .Select(m => m.First().UserId) + .ToList(); + + foreach (var usr in otherUsers) { - var otherUsers = context.UserActivityHistory.Where(m => m.Ip == addr.Ip && m.UserId != userId && !string.IsNullOrWhiteSpace(m.Ip)).GroupBy(m => m.UserId).Select(m => m.First().UserId).ToList(); - foreach (var usr in otherUsers) + var name = ctx.Users.Where(u => u.Id == usr).Select(u => u.Name).FirstOrDefault(); + if (!string.IsNullOrWhiteSpace(name)) { - var name = ctx.Users.Where(u => u.Id == usr).Select(u => u.Name).FirstOrDefault(); - if (!string.IsNullOrWhiteSpace(name)) - { - addr.OtherUsers.Add(usr, name); - } + addr.OtherUsers.Add(usr, name); } } } - - return new DataPage( - Total: ipAddresses.Count(), - Page: page, - PageSize: pageSize, - Count: addresses.Count, - Values: addresses - ); - } - } + return Ok(new DataPage( + Total: ipAddresses.Count(), + Page: page, + PageSize: pageSize, + Count: addresses.Count, + Values: addresses + )); + } [HttpGet("user/{userId:guid}/activity")] - public DataPage ListUserActivity( + [ProducesResponseType(typeof(DataPage), (int)HttpStatusCode.OK, ContentTypes.Json)] + public IActionResult ListUserActivity( Guid userId, [FromQuery] int page = 0, [FromQuery] int pageSize = 0, @@ -246,35 +263,32 @@ public DataPage ListUserActivity( pageSize = Math.Max(Math.Min(pageSize, PagingInfo.MaxPageSize), PagingInfo.MinPageSize); limit = Math.Max(Math.Min(limit, pageSize), 1); - using (var context = DbInterface.CreateLoggingContext()) - { - var activity = context.UserActivityHistory.Where(m => m.UserId == userId); - - if (sortDirection == SortDirection.Ascending) - { - activity = activity.OrderBy(m => m.TimeStamp); - } - else - { - activity = activity.OrderByDescending(m => m.TimeStamp); - } - - var values = activity.Skip(page * pageSize).Take(pageSize).ToList(); - - return new DataPage( - Total: activity.Count(), - Page: page, - PageSize: pageSize, - Count: values.Count, - Values: values - ); + using var context = DbInterface.CreateLoggingContext(); + var activity = context.UserActivityHistory.Where(m => m.UserId == userId); + if (sortDirection == SortDirection.Ascending) + { + activity = activity.OrderBy(m => m.TimeStamp); } + else + { + activity = activity.OrderByDescending(m => m.TimeStamp); + } + + var values = activity.Skip(page * pageSize).Take(pageSize).ToList(); + return Ok(new DataPage( + Total: activity.Count(), + Page: page, + PageSize: pageSize, + Count: values.Count, + Values: values + )); } [HttpGet("player/{playerId:guid}/activity")] - public DataPage ListPlayerActivity( + [ProducesResponseType(typeof(DataPage), (int)HttpStatusCode.OK, ContentTypes.Json)] + public IActionResult ListPlayerActivity( Guid playerId, [FromQuery] int page = 0, [FromQuery] int pageSize = 0, @@ -286,35 +300,32 @@ public DataPage ListPlayerActivity( pageSize = Math.Max(Math.Min(pageSize, PagingInfo.MaxPageSize), PagingInfo.MinPageSize); limit = Math.Max(Math.Min(limit, pageSize), 1); - using (var context = DbInterface.CreateLoggingContext()) - { - var activity = context.UserActivityHistory.Where(m => m.PlayerId == playerId); - - if (sortDirection == SortDirection.Ascending) - { - activity = activity.OrderBy(m => m.TimeStamp); - } - else - { - activity = activity.OrderByDescending(m => m.TimeStamp); - } - - var values = activity.Skip(page * pageSize).Take(pageSize).ToList(); - - return new DataPage( - Total: activity.Count(), - Page: page, - PageSize: pageSize, - Count: values.Count, - Values: values - ); + using var context = DbInterface.CreateLoggingContext(); + var activity = context.UserActivityHistory.Where(m => m.PlayerId == playerId); + if (sortDirection == SortDirection.Ascending) + { + activity = activity.OrderBy(m => m.TimeStamp); + } + else + { + activity = activity.OrderByDescending(m => m.TimeStamp); } + var values = activity.Skip(page * pageSize).Take(pageSize).ToList(); + + return Ok(new DataPage( + Total: activity.Count(), + Page: page, + PageSize: pageSize, + Count: values.Count, + Values: values + )); } [HttpGet("trade")] - public DataPage ListTrades( + [ProducesResponseType(typeof(DataPage), (int)HttpStatusCode.OK, ContentTypes.Json)] + public IActionResult ListTrades( [FromQuery] int page = 0, [FromQuery] int pageSize = 0, [FromQuery] int limit = PagingInfo.MaxPageSize, @@ -372,7 +383,7 @@ public DataPage ListTrades( var delta = DateTime.UtcNow - start; - return new DataPage + return Ok(new DataPage { Total = trades.Count(), Page = page, @@ -383,48 +394,61 @@ public DataPage ListTrades( #if DEBUG Extra = delta, #endif - }; + }); } } private void PopulateTradeNames(List trades) { - var userIds = trades.Where(m => m.UserId != Guid.Empty).GroupBy(m => m.UserId).Select(m => m.First().UserId).ToList(); - var playerIds = trades.Where(m => m.PlayerId != Guid.Empty).GroupBy(m => m.PlayerId).Select(m => m.First().PlayerId).ToList(); - var targetIds = trades.Where(m => m.TargetId != Guid.Empty).GroupBy(m => m.TargetId).Select(m => m.First().TargetId).ToList(); + var userIds = trades.Where(m => m.UserId != Guid.Empty) + .GroupBy(m => m.UserId) + .Select(m => m.First().UserId) + .ToList(); + + var playerIds = trades.Where(m => m.PlayerId != Guid.Empty) + .GroupBy(m => m.PlayerId) + .Select(m => m.First().PlayerId) + .ToList(); + + var targetIds = trades.Where(m => m.TargetId != Guid.Empty) + .GroupBy(m => m.TargetId) + .Select(m => m.First().TargetId) + .ToList(); var playerSet = new HashSet(playerIds); playerSet.UnionWith(targetIds); - using (var db = DbInterface.CreatePlayerContext(true)) - { - var users = db.Users.Where(u => userIds.Contains(u.Id)).Select(u => new KeyValuePair(u.Id, u.Name)).ToDictionary(p => p.Key, p => p.Value); - var players = db.Players.Where(p => playerSet.Contains(p.Id)).Select(p => new KeyValuePair(p.Id, p.Name)).ToDictionary(p => p.Key, p => p.Value); + using var db = DbInterface.CreatePlayerContext(true); + var users = db.Users.Where(u => userIds.Contains(u.Id)) + .Select(u => new KeyValuePair(u.Id, u.Name)) + .ToDictionary(p => p.Key, p => p.Value); + + var players = db.Players.Where(p => playerSet.Contains(p.Id)) + .Select(p => new KeyValuePair(p.Id, p.Name)) + .ToDictionary(p => p.Key, p => p.Value); - foreach (var msg in trades) + foreach (var msg in trades) + { + if (users.TryGetValue(msg.UserId, out var usernameValue)) { - if (users.ContainsKey(msg.UserId)) - { - msg.Username = users[msg.UserId]; - } + msg.Username = usernameValue; + } - if (players.ContainsKey(msg.PlayerId)) - { - msg.PlayerName = players[msg.PlayerId]; - } + if (players.TryGetValue(msg.PlayerId, out var playerNameValue)) + { + msg.PlayerName = playerNameValue; + } - if (players.ContainsKey(msg.TargetId)) - { - msg.TargetName = players[msg.TargetId]; - } + if (players.TryGetValue(msg.TargetId, out var targetNameValue)) + { + msg.TargetName = targetNameValue; } } - } - [HttpGet("guild/{guildId:guid}/activity")] - public DataPage ListGuildActivity( + [ProducesResponseType(typeof(DataPage), (int)HttpStatusCode.OK, ContentTypes.Json)] + public IActionResult ListGuildActivity( Guid guildId, [FromQuery] int page = 0, [FromQuery] int pageSize = 0, @@ -436,76 +460,93 @@ public DataPage ListGuildActivity( pageSize = Math.Max(Math.Min(pageSize, PagingInfo.MaxPageSize), PagingInfo.MinPageSize); limit = Math.Max(Math.Min(limit, pageSize), 1); - using (var context = DbInterface.CreateLoggingContext()) - { - var guildActivity = context.GuildHistory.AsQueryable(); - - if (guildId != Guid.Empty) - { - guildActivity = guildActivity.Where(m => m.GuildId == guildId); - } + using var context = DbInterface.CreateLoggingContext(); + var guildActivity = context.GuildHistory.AsQueryable(); - if (sortDirection == SortDirection.Ascending) - { - guildActivity = guildActivity.OrderBy(m => m.TimeStamp); - } - else - { - guildActivity = guildActivity.OrderByDescending(m => m.TimeStamp); - } + if (guildId != Guid.Empty) + { + guildActivity = guildActivity.Where(m => m.GuildId == guildId); + } - var values = guildActivity.Skip(page * pageSize).Take(pageSize).ToList(); + if (sortDirection == SortDirection.Ascending) + { + guildActivity = guildActivity.OrderBy(m => m.TimeStamp); + } + else + { + guildActivity = guildActivity.OrderByDescending(m => m.TimeStamp); + } - PopulateGuildActivityNames(values); + var values = guildActivity.Skip(page * pageSize).Take(pageSize).ToList(); - if (limit != pageSize) - { - values = values.Take(limit).ToList(); - } + PopulateGuildActivityNames(values); - return new DataPage( - Total: guildActivity.Count(), - Page: page, - PageSize: pageSize, - Count: values.Count, - Values: values - ); + if (limit != pageSize) + { + values = values.Take(limit).ToList(); } + + return Ok(new DataPage( + Total: guildActivity.Count(), + Page: page, + PageSize: pageSize, + Count: values.Count, + Values: values + )); } private void PopulateGuildActivityNames(List guildActivity) { - var userIds = guildActivity.Where(m => m.UserId != Guid.Empty).GroupBy(m => m.UserId).Select(m => m.First().UserId).ToList(); - var playerIds = guildActivity.Where(m => m.PlayerId != Guid.Empty).GroupBy(m => m.PlayerId).Select(m => m.First().PlayerId).ToList(); - var targetIds = guildActivity.Where(m => m.InitiatorId != Guid.Empty).GroupBy(m => m.InitiatorId).Select(m => m.First().InitiatorId).ToList(); + var userIds = guildActivity + .Where(m => m.UserId != Guid.Empty) + .GroupBy(m => m.UserId) + .Select(m => m.First().UserId) + .ToList(); + + var playerIds = guildActivity + .Where(m => m.PlayerId != Guid.Empty) + .GroupBy(m => m.PlayerId) + .Select(m => m.First().PlayerId) + .ToList(); + + var targetIds = guildActivity + .Where(m => m.InitiatorId != Guid.Empty) + .GroupBy(m => m.InitiatorId) + .Select(m => m.First().InitiatorId) + .ToList(); var playerSet = new HashSet(playerIds); playerSet.UnionWith(targetIds); - using (var db = DbInterface.CreatePlayerContext(true)) - { - var users = db.Users.Where(u => userIds.Contains(u.Id)).Select(u => new KeyValuePair(u.Id, u.Name)).ToDictionary(p => p.Key, p => p.Value); - var players = db.Players.Where(p => playerSet.Contains(p.Id)).Select(p => new KeyValuePair(p.Id, p.Name)).ToDictionary(p => p.Key, p => p.Value); + using var db = DbInterface.CreatePlayerContext(true); + + var users = db.Users + .Where(u => userIds.Contains(u.Id)) + .Select(u => new KeyValuePair(u.Id, u.Name)) + .ToDictionary(p => p.Key, p => p.Value); + + var players = db.Players + .Where(p => playerSet.Contains(p.Id)) + .Select(p => new KeyValuePair(p.Id, p.Name)) + .ToDictionary(p => p.Key, p => p.Value); - foreach (var msg in guildActivity) + foreach (var msg in guildActivity) + { + if (users.TryGetValue(msg.UserId, out var usernameValue)) { - if (users.ContainsKey(msg.UserId)) - { - msg.Username = users[msg.UserId]; - } + msg.Username = usernameValue; + } - if (players.ContainsKey(msg.PlayerId)) - { - msg.PlayerName = players[msg.PlayerId]; - } + if (players.TryGetValue(msg.PlayerId, out var playerNameValue)) + { + msg.PlayerName = playerNameValue; + } - if (players.ContainsKey(msg.InitiatorId)) - { - msg.InitiatorName = players[msg.InitiatorId]; - } + if (players.TryGetValue(msg.InitiatorId, out var initiatorNameValue)) + { + msg.InitiatorName = initiatorNameValue; } } - } } } \ No newline at end of file diff --git a/Intersect.Server/Web/RestApi/Routes/V1/PlayerController.cs b/Intersect.Server/Web/RestApi/Routes/V1/PlayerController.cs index c38e1066bd..c1e803cc10 100644 --- a/Intersect.Server/Web/RestApi/Routes/V1/PlayerController.cs +++ b/Intersect.Server/Web/RestApi/Routes/V1/PlayerController.cs @@ -1,6 +1,8 @@ using System.Net; using Intersect.Enums; using Intersect.GameObjects; +using Intersect.Server.Collections.Indexing; +using Intersect.Server.Collections.Sorting; using Intersect.Server.Database; using Intersect.Server.Database.PlayerData; using Intersect.Server.Database.PlayerData.Players; @@ -9,65 +11,22 @@ using Intersect.Server.Localization; using Intersect.Server.Networking; using Intersect.Server.Web.Http; -using Intersect.Server.Web.RestApi.Payloads; using Intersect.Server.Web.RestApi.Types; +using Intersect.Server.Web.RestApi.Types.Player; using Intersect.Utilities; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; namespace Intersect.Server.Web.RestApi.Routes.V1 { - public partial struct AdminActionParameters - { - public string Moderator { get; set; } - - public int Duration { get; set; } - - public bool Ip { get; set; } - - public string Reason { get; set; } - - public byte X { get; set; } - - public byte Y { get; set; } - - public Guid MapId { get; set; } - } - [Route("api/v1/players")] [Authorize] public sealed partial class PlayerController : IntersectController { - - [HttpPost] - [Obsolete("The appropriate verb for retrieving a list of records is GET not POST")] - public IActionResult ListPost([FromBody] PagingInfo pageInfo) - { - pageInfo.Page = Math.Max(pageInfo.Page, 0); - pageInfo.PageSize = Math.Max(Math.Min(pageInfo.PageSize, 100), 5); - - var values = Player.List( - null, - null, - SortDirection.Ascending, - pageInfo.Page * pageInfo.PageSize, - pageInfo.PageSize, - out var entryTotal - ); - - return Ok( - new DataPage - { - Total = entryTotal, - Page = pageInfo.Page, - PageSize = pageInfo.PageSize, - Count = values.Count, - Values = values, - } - ); - } + #region Lookup [HttpGet] + [ProducesResponseType(typeof(DataPage), (int)HttpStatusCode.OK, ContentTypes.Json)] public IActionResult List( [FromQuery] int page = 0, [FromQuery] int pageSize = 0, @@ -103,7 +62,8 @@ out var total } [HttpGet("rank")] - public DataPage Rank( + [ProducesResponseType(typeof(DataPage), (int)HttpStatusCode.OK, ContentTypes.Json)] + public IActionResult Rank( [FromQuery] int page = 0, [FromQuery] int pageSize = 0, [FromQuery] int limit = PagingInfo.MaxPageSize, @@ -120,7 +80,7 @@ public DataPage Rank( values = values.Take(limit).ToList(); } - return new DataPage + return Ok(new DataPage { Total = Player.Count(), Page = page, @@ -135,30 +95,12 @@ public DataPage Rank( ], Direction = sortDirection, }], - }; - } - - [HttpPost("online")] - [Obsolete("The appropriate verb for retrieving a list of records is GET not POST")] - public object OnlinePost([FromBody] PagingInfo pageInfo) - { - pageInfo.Page = Math.Max(pageInfo.Page, 0); - pageInfo.PageSize = Math.Max(Math.Min(pageInfo.PageSize, 100), 5); - - var entries = Globals.OnlineList?.Skip(pageInfo.Page * pageInfo.PageSize).Take(pageInfo.PageSize).ToList(); - - return new DataPage - { - Total = Globals.OnlineList?.Count ?? 0, - Page = pageInfo.Page, - PageSize = pageInfo.PageSize, - Count = entries?.Count ?? 0, - Values = entries, - }; + }); } [HttpGet("online")] - public DataPage Online( + [ProducesResponseType(typeof(DataPage), (int)HttpStatusCode.OK, ContentTypes.Json)] + public IActionResult Online( [FromQuery] int page = 0, [FromQuery] int pageSize = 0, [FromQuery] int limit = PagingInfo.MaxPageSize, @@ -171,7 +113,7 @@ public DataPage Online( pageSize = Math.Max(Math.Min(pageSize, PagingInfo.MaxPageSize), PagingInfo.MinPageSize); limit = Math.Max(Math.Min(limit, pageSize), 1); - Sort.From(sortBy, sortDirection); + _ = Sort.From(sortBy, sortDirection); IEnumerable enumerable = Globals.OnlineList ?? new List(); if (!string.IsNullOrWhiteSpace(search)) @@ -181,19 +123,18 @@ public DataPage Online( var total = enumerable.Count(); - switch (sortBy?.ToLower() ?? "") - { - case "level": - enumerable = sortDirection == SortDirection.Ascending ? enumerable.OrderBy(u => u.Level).ThenBy(u => u.Exp) : enumerable.OrderByDescending(u => u.Level).ThenByDescending(u => u.Exp); - break; - case "onlinetime": - enumerable = sortDirection == SortDirection.Ascending ? enumerable.OrderBy(u => u.OnlineTime) : enumerable.OrderByDescending(u => u.OnlineTime); - break; - case "name": - default: - enumerable = sortDirection == SortDirection.Ascending ? enumerable.OrderBy(u => u.Name.ToUpper()) : enumerable.OrderByDescending(u => u.Name.ToUpper()); - break; - } + enumerable = (sortBy?.ToLower() ?? "") switch + { + "level" => sortDirection == SortDirection.Ascending + ? enumerable.OrderBy(u => u.Level).ThenBy(u => u.Exp) + : enumerable.OrderByDescending(u => u.Level).ThenByDescending(u => u.Exp), + "onlinetime" => sortDirection == SortDirection.Ascending + ? enumerable.OrderBy(u => u.OnlineTime) + : enumerable.OrderByDescending(u => u.OnlineTime), + _ => sortDirection == SortDirection.Ascending + ? enumerable.OrderBy(u => u.Name.ToUpper()) + : enumerable.OrderByDescending(u => u.Name.ToUpper()), + }; var values = enumerable.Skip(page * pageSize).ToList() ?? new List(); @@ -202,25 +143,27 @@ public DataPage Online( values = values.Take(limit).ToList(); } - return new DataPage( + return Ok(new DataPage( Total: total, Page: page, PageSize: pageSize, Count: values.Count, Values: values - ); + )); } [HttpGet("online/count")] - public object OnlineCount() - { - return new - { - onlineCount = Globals.OnlineList?.Count ?? 0, - }; - } + [ProducesResponseType(typeof(OnlineCountResponseBody), (int)HttpStatusCode.OK, ContentTypes.Json)] + public IActionResult OnlineCount() => Ok(new OnlineCountResponseBody(Globals.OnlineList?.Count ?? 0)); + + #endregion + + #region Player Basic Operations [HttpGet("{lookupKey:LookupKey}")] + [ProducesResponseType(typeof(StatusMessageResponseBody), (int)HttpStatusCode.BadRequest, ContentTypes.Json)] + [ProducesResponseType(typeof(StatusMessageResponseBody), (int)HttpStatusCode.NotFound, ContentTypes.Json)] + [ProducesResponseType(typeof(Player), (int)HttpStatusCode.OK, ContentTypes.Json)] public IActionResult LookupPlayer(LookupKey lookupKey) { if (lookupKey.IsInvalid) @@ -228,330 +171,186 @@ public IActionResult LookupPlayer(LookupKey lookupKey) return BadRequest(lookupKey.IsIdInvalid ? @"Invalid player id." : @"Invalid player name."); } - var (_, player) = Player.Fetch(lookupKey, loadRelationships: true, loadBags: true); - if (player != null) + if (!Player.TryFetch(lookupKey, out var player, loadRelationships: true, loadBags: true)) { - return Ok(player); + return NotFound($@"No player found for lookup key '{lookupKey}'"); } - return NotFound(lookupKey.HasId ? $@"No player with id '{lookupKey.Id}'." : $@"No player with name '{lookupKey.Name}'."); + return Ok(player); } [HttpDelete("{lookupKey:LookupKey}")] - public object DeletePlayer(LookupKey lookupKey) + [ProducesResponseType(typeof(StatusMessageResponseBody), (int)HttpStatusCode.BadRequest, ContentTypes.Json)] + [ProducesResponseType(typeof(StatusMessageResponseBody), (int)HttpStatusCode.NotFound, ContentTypes.Json)] + [ProducesResponseType(typeof(StatusMessageResponseBody), (int)HttpStatusCode.InternalServerError, ContentTypes.Json)] + [ProducesResponseType(typeof(Player), (int)HttpStatusCode.OK, ContentTypes.Json)] + public IActionResult DeletePlayer(LookupKey lookupKey) { if (lookupKey.IsInvalid) { return BadRequest(lookupKey.IsIdInvalid ? @"Invalid player id." : @"Invalid player name."); } - var (client, player) = Player.Fetch(lookupKey); - if (player != null) + if (!Player.TryFetch(lookupKey, out var client, out var player)) { - if (Player.FindOnline(player.Id) != null) - { - return InternalServerError("Failed to delete player because they are online!"); - } - - var user = Database.PlayerData.User.FindById(player.UserId); - if (user == null) - { - return BadRequest($@"Failed to load user for {player.Name}!"); - } - - var matchingPlayer = user.Players.FirstOrDefault(p => p.Id == player.Id); - if (matchingPlayer != default) - { - if (!user.TryDeleteCharacter(matchingPlayer)) - { - if (client.User == user) - { - client.LogAndDisconnect(player.Id, nameof(Database.PlayerData.User.TryDeleteCharacter)); - } - } - } - - return player; - } - - return NotFound(lookupKey.HasId ? $@"No player with id '{lookupKey.Id}'." : $@"No player with name '{lookupKey.Name}'."); - } - - [HttpPost("{lookupKey:LookupKey}/name")] - public object ChangeName(LookupKey lookupKey, [FromBody] NameChange change) - { - if (lookupKey.IsInvalid) - { - return BadRequest(lookupKey.IsIdInvalid ? @"Invalid player id." : @"Invalid player name."); + return NotFound($@"No player found for lookup key '{lookupKey}'"); } - if (!FieldChecking.IsValidUsername(change.Name, Strings.Regex.Username)) + if (Player.FindOnline(player.Id) != null) { - return BadRequest($@"Invalid name."); + return InternalServerError("Failed to delete player because they are online!"); } - if (Player.PlayerExists(change.Name)) + var user = Database.PlayerData.User.FindById(player.UserId); + if (user == null) { - return BadRequest($@"Name already taken."); + return BadRequest($@"Failed to load user for {player.Name}!"); } - var (_, player) = Player.Fetch(lookupKey); - if (player != null) + var matchingPlayer = user.Players.FirstOrDefault(p => p.Id == player.Id); + if (matchingPlayer != default && !user.TryDeleteCharacter(matchingPlayer) && client.User == user) { - player.Name = change.Name; - if (player.Online) - { - PacketSender.SendEntityDataToProximity(player); - } - - using (var context = DbInterface.CreatePlayerContext(false)) - { - context.Update(player); - context.SaveChanges(); - } - - return player; + _ = client.LogAndDisconnect(player.Id, nameof(Database.PlayerData.User.TryDeleteCharacter)); } - return NotFound(lookupKey.HasId ? $@"No player with id '{lookupKey.Id}'." : $@"No player with name '{lookupKey.Name}'."); + return Ok(player); } - [HttpGet("{lookupKey:LookupKey}/variables")] + [HttpPost("{lookupKey:LookupKey}/name")] [ProducesResponseType(typeof(StatusMessageResponseBody), (int)HttpStatusCode.BadRequest, ContentTypes.Json)] [ProducesResponseType(typeof(StatusMessageResponseBody), (int)HttpStatusCode.NotFound, ContentTypes.Json)] - [ProducesResponseType(typeof(IEnumerable), (int)HttpStatusCode.OK, ContentTypes.Json)] - public IActionResult PlayerVariablesList(LookupKey lookupKey) + [ProducesResponseType(typeof(Player), (int)HttpStatusCode.OK, ContentTypes.Json)] + public IActionResult ChangeName(LookupKey lookupKey, [FromBody] NameChangePayload change) { if (lookupKey.IsInvalid) { - return BadRequest(lookupKey.IsIdInvalid ? @"Invalid id." : @"Invalid name."); - } - - if (!Player.TryFetch(lookupKey, out var fetchResult)) - { - return NotFound( - lookupKey.HasId - ? $@"No player with id '{lookupKey.Id}'." - : $@"No player with name '{lookupKey.Name}'." - ); + return BadRequest(lookupKey.IsIdInvalid ? @"Invalid player id." : @"Invalid player name."); } - var (_, player) = fetchResult; - - return Ok(player.Variables); - } - - [HttpGet("{lookupKey:LookupKey}/variables/{variableId:guid}")] - [ProducesResponseType(typeof(StatusMessageResponseBody), (int)HttpStatusCode.BadRequest, ContentTypes.Json)] - [ProducesResponseType(typeof(StatusMessageResponseBody), (int)HttpStatusCode.NotFound, ContentTypes.Json)] - [ProducesResponseType(typeof(PlayerVariable), (int)HttpStatusCode.OK, ContentTypes.Json)] - public IActionResult PlayerVariableGet(LookupKey lookupKey, Guid variableId) - { - if (lookupKey.IsInvalid) + if (!FieldChecking.IsValidUsername(change.Name, Strings.Regex.Username)) { - return BadRequest(lookupKey.IsIdInvalid ? @"Invalid id." : @"Invalid name."); + return BadRequest($@"Invalid name."); } - if (!Player.TryFetch(lookupKey, out var fetchResult)) + if (Player.PlayerExists(change.Name)) { - return NotFound( - lookupKey.HasId - ? $@"No player with id '{lookupKey.Id}'." - : $@"No player with name '{lookupKey.Name}'." - ); + return BadRequest($@"Name already taken."); } - var (_, player) = fetchResult; - - if (!PlayerVariableBase.TryGet(variableId, out var variableDescriptor)) + if (!Player.TryFetch(lookupKey, out var player)) { - return NotFound($@"Variable not found for id {variableId}"); + return NotFound($@"No player found for lookup key '{lookupKey}'"); } - var variable = player.GetVariable(variableDescriptor.Id, true); - return Ok(variable); - } - - [HttpGet("{lookupKey:LookupKey}/variables/{variableId:guid}/value")] - [ProducesResponseType(typeof(StatusMessageResponseBody), (int)HttpStatusCode.BadRequest, ContentTypes.Json)] - [ProducesResponseType(typeof(StatusMessageResponseBody), (int)HttpStatusCode.NotFound, ContentTypes.Json)] - [ProducesResponseType(typeof(VariableValueBody), (int)HttpStatusCode.OK, ContentTypes.Json)] - public IActionResult PlayerVariableValueGet(LookupKey lookupKey, Guid variableId) - { - if (lookupKey.IsInvalid) + player.Name = change.Name; + if (player.Online) { - return BadRequest(lookupKey.IsIdInvalid ? @"Invalid id." : @"Invalid name."); + PacketSender.SendEntityDataToProximity(player); } - if (!Player.TryFetch(lookupKey, out var fetchResult)) + using (var context = DbInterface.CreatePlayerContext(false)) { - return NotFound( - lookupKey.HasId - ? $@"No player with id '{lookupKey.Id}'." - : $@"No player with name '{lookupKey.Name}'." - ); + _ = context.Update(player); + _ = context.SaveChanges(); } - var (_, player) = fetchResult; + return Ok(player); + } - if (!PlayerVariableBase.TryGet(variableId, out var variableDescriptor)) - { - return NotFound($@"Variable not found for id {variableId}"); - } + #endregion - var variable = player.GetVariable(variableDescriptor.Id, true); - return Ok(new VariableValueBody - { - Value = variable.Value.Value, - }); - } + #region Player Properties Change - [HttpPost("{lookupKey:LookupKey}/variables/{variableId:guid}")] + [HttpPost("{lookupKey:LookupKey}/class")] [ProducesResponseType(typeof(StatusMessageResponseBody), (int)HttpStatusCode.BadRequest, ContentTypes.Json)] [ProducesResponseType(typeof(StatusMessageResponseBody), (int)HttpStatusCode.NotFound, ContentTypes.Json)] - [ProducesResponseType(typeof(PlayerVariable), (int)HttpStatusCode.OK, ContentTypes.Json)] - public IActionResult PlayerVariableSet(LookupKey lookupKey, Guid variableId, [FromBody] VariableValueBody valueBody) + [ProducesResponseType(typeof(Player), (int)HttpStatusCode.OK, ContentTypes.Json)] + public IActionResult PlayerClassSet(LookupKey lookupKey, [FromBody] IdPayload change) { if (lookupKey.IsInvalid) { - return BadRequest(lookupKey.IsIdInvalid ? @"Invalid id." : @"Invalid name."); + return BadRequest(lookupKey.IsIdInvalid ? @"Invalid player id." : @"Invalid player name."); } - if (!Player.TryFetch(lookupKey, out var fetchResult)) + if (!ClassBase.TryGet(change.Id, out var _)) { - return NotFound( - lookupKey.HasId - ? $@"No player with id '{lookupKey.Id}'." - : $@"No player with name '{lookupKey.Name}'." - ); + return BadRequest($@"Invalid class id {change.Id}."); } - var (_, player) = fetchResult; - - if (!PlayerVariableBase.TryGet(variableId, out var variableDescriptor)) + if (!Player.TryFetch(lookupKey, out var player)) { - return NotFound($@"Variable not found for id {variableId}"); + return NotFound($@"No player found for lookup key '{lookupKey}'"); } - var variable = player.GetVariable(variableDescriptor.Id, true); - - var changed = false; - if (variable?.Value != null) + player.ClassId = change.Id; + player.RecalculateStatsAndPoints(); + player.UnequipInvalidItems(); + if (player.Online) { - if (variable.Value.Value != valueBody.Value) - { - variable.Value.Value = valueBody.Value; - changed = true; - } + PacketSender.SendEntityDataToProximity(player); } - // ReSharper disable once InvertIf - if (changed) + using (var context = DbInterface.CreatePlayerContext(false)) { - player.StartCommonEventsWithTrigger(CommonEventTrigger.PlayerVariableChange, string.Empty, variableId.ToString()); - using var context = DbInterface.CreatePlayerContext(false); - context.Update(player); - context.SaveChanges(); + _ = context.Update(player); + _ = context.SaveChanges(); } - return Ok(variable); + return Ok(player); } - [HttpPost("{lookupKey:LookupKey}/class")] - public object PlayerClassSet(LookupKey lookupKey, [FromBody] ClassChange change) + [HttpPost("{lookupKey:LookupKey}/level")] + [ProducesResponseType(typeof(StatusMessageResponseBody), (int)HttpStatusCode.BadRequest, ContentTypes.Json)] + [ProducesResponseType(typeof(StatusMessageResponseBody), (int)HttpStatusCode.NotFound, ContentTypes.Json)] + [ProducesResponseType(typeof(Player), (int)HttpStatusCode.OK, ContentTypes.Json)] + public IActionResult PlayerLevelSet(LookupKey lookupKey, [FromBody] LevelChangeRequestBody change) { if (lookupKey.IsInvalid) { return BadRequest(lookupKey.IsIdInvalid ? @"Invalid player id." : @"Invalid player name."); } - if (change.ClassId == Guid.Empty || ClassBase.Get(change.ClassId) == null) + if (!Player.TryFetch(lookupKey, out var player)) { - return BadRequest($@"Invalid class id ${change.ClassId}."); + return NotFound($@"No player found for lookup key '{lookupKey}'"); } - var (_, player) = Player.Fetch(lookupKey); - if (player != null) - { - player.ClassId = change.ClassId; - player.RecalculateStatsAndPoints(); - player.UnequipInvalidItems(); - if (player.Online) - { - PacketSender.SendEntityDataToProximity(player); - } - - using (var context = DbInterface.CreatePlayerContext(false)) - { - context.Update(player); - context.SaveChanges(); - } - - return player; - } - - return NotFound(lookupKey.HasId ? $@"No player with id '{lookupKey.Id}'." : $@"No player with name '{lookupKey.Name}'."); - } - - [HttpPost("{lookupKey:LookupKey}/level")] - public object PlayerLevelSet(LookupKey lookupKey, [FromBody] LevelChange change) - { - if (lookupKey.IsInvalid) + player.SetLevel(change.Level, true); + if (player.Online) { - return BadRequest(lookupKey.IsIdInvalid ? @"Invalid player id." : @"Invalid player name."); + PacketSender.SendEntityDataToProximity(player); } - var (_, player) = Player.Fetch(lookupKey); - if (player != null) + using (var context = DbInterface.CreatePlayerContext(false)) { - player.SetLevel(change.Level, true); - if (player.Online) - { - PacketSender.SendEntityDataToProximity(player); - } - - using (var context = DbInterface.CreatePlayerContext(false)) - { - context.Update(player); - context.SaveChanges(); - } - - return player; + _ = context.Update(player); + _ = context.SaveChanges(); } - return NotFound(lookupKey.HasId ? $@"No player with id '{lookupKey.Id}'." : $@"No player with name '{lookupKey.Name}'."); + return Ok(player); } - public struct ItemListResponse - { - public IEnumerable Bank { get; init; } + #endregion - public IEnumerable Inventory { get; init; } - } + #region Player Items Management [HttpGet("{lookupKey:LookupKey}/items")] + [ProducesResponseType(typeof(StatusMessageResponseBody), (int)HttpStatusCode.NotFound, ContentTypes.Json)] + [ProducesResponseType(typeof(ItemListResponse), (int)HttpStatusCode.OK, ContentTypes.Json)] public IActionResult ItemsList(LookupKey lookupKey) { - var (_, player) = Player.Fetch(lookupKey); - if (player == null) + if (!Player.TryFetch(lookupKey, out var player)) { - return NotFound( - lookupKey.HasId - ? $@"No player with id '{lookupKey.Id}'." - : $@"No player with name '{lookupKey.Name}'." - ); + return NotFound($@"No player found for lookup key '{lookupKey}'"); } - return Ok( - new ItemListResponse - { - Bank = player.Bank.Where(slot => !slot.IsEmpty), - Inventory = player.Items.Where(slot => !slot.IsEmpty), - } - ); + return Ok(new ItemListResponse(player.Bank.Where(slot => !slot.IsEmpty), player.Items.Where(slot => !slot.IsEmpty))); } [HttpGet("{lookupKey:LookupKey}/items/bank")] + [ProducesResponseType(typeof(StatusMessageResponseBody), (int)HttpStatusCode.BadRequest, ContentTypes.Json)] + [ProducesResponseType(typeof(StatusMessageResponseBody), (int)HttpStatusCode.NotFound, ContentTypes.Json)] + [ProducesResponseType(typeof(IEnumerable), (int)HttpStatusCode.OK, ContentTypes.Json)] public IActionResult ItemsListBank(LookupKey lookupKey) { if (lookupKey.IsInvalid) @@ -559,20 +358,18 @@ public IActionResult ItemsListBank(LookupKey lookupKey) return BadRequest(lookupKey.IsIdInvalid ? @"Invalid player id." : @"Invalid player name."); } - var (_, player) = Player.Fetch(lookupKey); - if (player == null) + if (!Player.TryFetch(lookupKey, out var player)) { - return NotFound( - lookupKey.HasId - ? $@"No player with id '{lookupKey.Id}'." - : $@"No player with name '{lookupKey.Name}'." - ); + return NotFound($@"No player found for lookup key '{lookupKey}'"); } return Ok(player.Bank.Where(slot => !slot.IsEmpty)); } [HttpGet("bag/{bagId:guid}")] + [ProducesResponseType(typeof(StatusMessageResponseBody), (int)HttpStatusCode.BadRequest, ContentTypes.Json)] + [ProducesResponseType(typeof(StatusMessageResponseBody), (int)HttpStatusCode.NotFound, ContentTypes.Json)] + [ProducesResponseType(typeof(Bag), (int)HttpStatusCode.OK, ContentTypes.Json)] public IActionResult GetPlayerBag(Guid bagId) { if (bagId == Guid.Empty) @@ -580,15 +377,18 @@ public IActionResult GetPlayerBag(Guid bagId) return BadRequest(@"Invalid bag id."); } - if (Bag.TryGetBag(bagId, out var bag)) + if (!Bag.TryGetBag(bagId, out var bag)) { - return Ok(bag); + return NotFound(@"Bag does not exist."); } - return NotFound(@"Bag does not exist."); + return Ok(bag); } [HttpGet("{lookupKey:LookupKey}/items/inventory")] + [ProducesResponseType(typeof(StatusMessageResponseBody), (int)HttpStatusCode.BadRequest, ContentTypes.Json)] + [ProducesResponseType(typeof(StatusMessageResponseBody), (int)HttpStatusCode.NotFound, ContentTypes.Json)] + [ProducesResponseType(typeof(IEnumerable), (int)HttpStatusCode.OK, ContentTypes.Json)] public IActionResult ItemsListInventory(LookupKey lookupKey) { if (lookupKey.IsInvalid) @@ -596,21 +396,20 @@ public IActionResult ItemsListInventory(LookupKey lookupKey) return BadRequest(lookupKey.IsIdInvalid ? @"Invalid player id." : @"Invalid player name."); } - var (_, player) = Player.Fetch(lookupKey); - if (player == null) + if (!Player.TryFetch(lookupKey, out var player)) { - return NotFound( - lookupKey.HasId - ? $@"No player with id '{lookupKey.Id}'." - : $@"No player with name '{lookupKey.Name}'." - ); + return NotFound($@"No player found for lookup key '{lookupKey}'"); } return Ok(player.Items.Where(slot => !slot.IsEmpty)); } [HttpPost("{lookupKey:LookupKey}/items/give")] - public object ItemsGive(LookupKey lookupKey, [FromBody] ItemInfo itemInfo) + [ProducesResponseType(typeof(StatusMessageResponseBody), (int)HttpStatusCode.BadRequest, ContentTypes.Json)] + [ProducesResponseType(typeof(StatusMessageResponseBody), (int)HttpStatusCode.NotFound, ContentTypes.Json)] + [ProducesResponseType(typeof(StatusMessageResponseBody), (int)HttpStatusCode.InternalServerError, ContentTypes.Json)] + [ProducesResponseType(typeof(ItemsGiveResponseBody), (int)HttpStatusCode.OK, ContentTypes.Json)] + public IActionResult ItemsGive(LookupKey lookupKey, [FromBody] ItemInfoRequestBody itemInfo) { if (lookupKey.IsInvalid) { @@ -627,14 +426,9 @@ public object ItemsGive(LookupKey lookupKey, [FromBody] ItemInfo itemInfo) return BadRequest("Cannot give 0, or a negative amount of an item."); } - var (_, player) = Player.Fetch(lookupKey); - if (player == null) + if (!Player.TryFetch(lookupKey, out var player)) { - return NotFound( - lookupKey.HasId - ? $@"No player with id '{lookupKey.Id}'." - : $@"No player with name '{lookupKey.Name}'." - ); + return NotFound($@"No player found for lookup key '{lookupKey}'"); } if (!player.TryGiveItem(itemInfo.ItemId, itemInfo.Quantity, ItemHandling.Normal, itemInfo.BankOverflow, -1, true)) @@ -644,27 +438,27 @@ public object ItemsGive(LookupKey lookupKey, [FromBody] ItemInfo itemInfo) using (var context = DbInterface.CreatePlayerContext(false)) { - context.Update(player); - context.SaveChanges(); + _ = context.Update(player); + _ = context.SaveChanges(); } var quantityBank = player.CountItems(itemInfo.ItemId, false, true); var quantityInventory = player.CountItems(itemInfo.ItemId, true, false); - return new - { - id = itemInfo.ItemId, - quantity = new - { - total = quantityBank + quantityInventory, - bank = quantityBank, - inventory = quantityInventory - } - }; + return Ok( + new ItemsGiveResponseBody( + itemInfo.ItemId, + new ItemsGiveQuantityData(quantityBank + quantityInventory, quantityBank, quantityInventory) + ) + ); } [HttpPost("{lookupKey:LookupKey}/items/take")] - public object ItemsTake(LookupKey lookupKey, [FromBody] ItemInfo itemInfo) + [ProducesResponseType(typeof(StatusMessageResponseBody), (int)HttpStatusCode.BadRequest, ContentTypes.Json)] + [ProducesResponseType(typeof(StatusMessageResponseBody), (int)HttpStatusCode.NotFound, ContentTypes.Json)] + [ProducesResponseType(typeof(StatusMessageResponseBody), (int)HttpStatusCode.InternalServerError, ContentTypes.Json)] + [ProducesResponseType(typeof(ItemsTakeResponseBody), (int)HttpStatusCode.OK, ContentTypes.Json)] + public IActionResult ItemsTake(LookupKey lookupKey, [FromBody] ItemInfoRequestBody itemInfo) { if (lookupKey.IsInvalid) { @@ -676,35 +470,33 @@ public object ItemsTake(LookupKey lookupKey, [FromBody] ItemInfo itemInfo) return BadRequest("Cannot take 0, or a negative amount of an item."); } - var (_, player) = Player.Fetch(lookupKey); - if (player == null) + if (!Player.TryFetch(lookupKey, out var player)) { - return NotFound( - lookupKey.HasId - ? $@"No player with id '{lookupKey.Id}'." - : $@"No player with name '{lookupKey.Name}'." - ); + return NotFound($@"No player found for lookup key '{lookupKey}'"); } - if (player.TryTakeItem(itemInfo.ItemId, itemInfo.Quantity)) + if (!player.TryTakeItem(itemInfo.ItemId, itemInfo.Quantity)) { - using (var context = DbInterface.CreatePlayerContext(false)) - { - context.Update(player); - context.SaveChanges(); - } + return InternalServerError($@"Failed to take {itemInfo.Quantity} of '{itemInfo.ItemId}' from player."); + } - return new - { - itemInfo.ItemId, - itemInfo.Quantity - }; + using (var context = DbInterface.CreatePlayerContext(false)) + { + _ = context.Update(player); + _ = context.SaveChanges(); } - return InternalServerError($@"Failed to take {itemInfo.Quantity} of '{itemInfo.ItemId}' from player."); + return Ok(new ItemsTakeResponseBody(itemInfo.ItemId, itemInfo.Quantity)); } + #endregion + + #region Player Spells Management + [HttpGet("{lookupKey:LookupKey}/spells")] + [ProducesResponseType(typeof(StatusMessageResponseBody), (int)HttpStatusCode.BadRequest, ContentTypes.Json)] + [ProducesResponseType(typeof(StatusMessageResponseBody), (int)HttpStatusCode.NotFound, ContentTypes.Json)] + [ProducesResponseType(typeof(IEnumerable), (int)HttpStatusCode.OK, ContentTypes.Json)] public IActionResult SpellsList(LookupKey lookupKey) { if (lookupKey.IsInvalid) @@ -712,95 +504,218 @@ public IActionResult SpellsList(LookupKey lookupKey) return BadRequest(lookupKey.IsIdInvalid ? @"Invalid player id." : @"Invalid player name."); } - var (_, player) = Player.Fetch(lookupKey); - if (player == null) + if (!Player.TryFetch(lookupKey, out var player)) { - return NotFound( - lookupKey.HasId - ? $@"No player with id '{lookupKey.Id}'." - : $@"No player with name '{lookupKey.Name}'." - ); + return NotFound($@"No player found for lookup key '{lookupKey}'"); } return Ok(player.Spells.Where(s => !s.IsEmpty)); } [HttpPost("{lookupKey:LookupKey}/spells/teach")] - public object SpellsTeach(LookupKey lookupKey, [FromBody] SpellInfo spell) + [ProducesResponseType(typeof(StatusMessageResponseBody), (int)HttpStatusCode.BadRequest, ContentTypes.Json)] + [ProducesResponseType(typeof(StatusMessageResponseBody), (int)HttpStatusCode.NotFound, ContentTypes.Json)] + [ProducesResponseType(typeof(StatusMessageResponseBody), (int)HttpStatusCode.InternalServerError, ContentTypes.Json)] + [ProducesResponseType(typeof(IdPayload), (int)HttpStatusCode.OK, ContentTypes.Json)] + public IActionResult SpellsTeach(LookupKey lookupKey, [FromBody] IdPayload spell) { if (lookupKey.IsInvalid) { return BadRequest(lookupKey.IsIdInvalid ? @"Invalid player id." : @"Invalid player name."); } - if (SpellBase.Get(spell.SpellId) == null) + if (SpellBase.Get(spell.Id) == null) { return BadRequest(@"Invalid spell id."); } - var (_, player) = Player.Fetch(lookupKey); - if (player == null) + if (!Player.TryFetch(lookupKey, out var player)) { - return NotFound( - lookupKey.HasId - ? $@"No player with id '{lookupKey.Id}'." - : $@"No player with name '{lookupKey.Name}'." - ); + return NotFound($@"No player found for lookup key '{lookupKey}'"); } - if (player.TryTeachSpell(new Spell(spell.SpellId), true)) + if (!player.TryTeachSpell(new Spell(spell.Id), true)) { - using (var context = DbInterface.CreatePlayerContext(false)) - { - context.Update(player); - context.SaveChanges(); - } + return InternalServerError($@"Failed to teach player spell with id '{spell.Id}'. They might already know it!"); + } - return spell; + using (var context = DbInterface.CreatePlayerContext(false)) + { + _ = context.Update(player); + _ = context.SaveChanges(); } - return InternalServerError($@"Failed to teach player spell with id '{spell.SpellId}'. They might already know it!"); + return Ok(spell); } [HttpPost("{lookupKey:LookupKey}/spells/forget")] - public object SpellsTake(LookupKey lookupKey, [FromBody] SpellInfo spell) + [ProducesResponseType(typeof(StatusMessageResponseBody), (int)HttpStatusCode.BadRequest, ContentTypes.Json)] + [ProducesResponseType(typeof(StatusMessageResponseBody), (int)HttpStatusCode.NotFound, ContentTypes.Json)] + [ProducesResponseType(typeof(StatusMessageResponseBody), (int)HttpStatusCode.InternalServerError, ContentTypes.Json)] + [ProducesResponseType(typeof(IdPayload), (int)HttpStatusCode.OK, ContentTypes.Json)] + public IActionResult SpellsTake(LookupKey lookupKey, [FromBody] IdPayload spell) { if (lookupKey.IsInvalid) { return BadRequest(lookupKey.IsIdInvalid ? @"Invalid player id." : @"Invalid player name."); } - if (SpellBase.Get(spell.SpellId) == null) + if (SpellBase.Get(spell.Id) == null) { return BadRequest(@"Invalid spell id."); } - var (_, player) = Player.Fetch(lookupKey); - if (player == null) + if (!Player.TryFetch(lookupKey, out var player)) + { + return NotFound($@"No player found for lookup key '{lookupKey}'"); + } + + if (!player.TryForgetSpell(new Spell(spell.Id), true)) + { + return InternalServerError($@"Failed to remove player spell with id '{spell.Id}'."); + } + + using (var context = DbInterface.CreatePlayerContext(false)) + { + _ = context.Update(player); + _ = context.SaveChanges(); + } + + return Ok(spell); + } + + #endregion + + #region Player Variables + + [HttpGet("{lookupKey:LookupKey}/variables")] + [ProducesResponseType(typeof(StatusMessageResponseBody), (int)HttpStatusCode.BadRequest, ContentTypes.Json)] + [ProducesResponseType(typeof(StatusMessageResponseBody), (int)HttpStatusCode.NotFound, ContentTypes.Json)] + [ProducesResponseType(typeof(IEnumerable), (int)HttpStatusCode.OK, ContentTypes.Json)] + public IActionResult PlayerVariablesList(LookupKey lookupKey) + { + if (lookupKey.IsInvalid) { - return NotFound( - lookupKey.HasId - ? $@"No player with id '{lookupKey.Id}'." - : $@"No player with name '{lookupKey.Name}'." - ); + return BadRequest(lookupKey.IsIdInvalid ? @"Invalid id." : @"Invalid name."); + } + + if (!Player.TryFetch(lookupKey, out var player)) + { + return NotFound($@"No player found for lookup key '{lookupKey}'"); + } + + return Ok(player.Variables); + } + + [HttpGet("{lookupKey:LookupKey}/variables/{variableId:guid}")] + [ProducesResponseType(typeof(StatusMessageResponseBody), (int)HttpStatusCode.BadRequest, ContentTypes.Json)] + [ProducesResponseType(typeof(StatusMessageResponseBody), (int)HttpStatusCode.NotFound, ContentTypes.Json)] + [ProducesResponseType(typeof(PlayerVariable), (int)HttpStatusCode.OK, ContentTypes.Json)] + public IActionResult PlayerVariableGet(LookupKey lookupKey, Guid variableId) + { + if (lookupKey.IsInvalid) + { + return BadRequest(lookupKey.IsIdInvalid ? @"Invalid id." : @"Invalid name."); + } + + if (!Player.TryFetch(lookupKey, out var player)) + { + return NotFound($@"No player found for lookup key '{lookupKey}'"); + } + + if (!PlayerVariableBase.TryGet(variableId, out var variableDescriptor)) + { + return NotFound($@"Variable not found for id {variableId}"); + } + + var variable = player.GetVariable(variableDescriptor.Id, true); + return Ok(variable); + } + + [HttpGet("{lookupKey:LookupKey}/variables/{variableId:guid}/value")] + [ProducesResponseType(typeof(StatusMessageResponseBody), (int)HttpStatusCode.BadRequest, ContentTypes.Json)] + [ProducesResponseType(typeof(StatusMessageResponseBody), (int)HttpStatusCode.NotFound, ContentTypes.Json)] + [ProducesResponseType(typeof(VariableValueBody), (int)HttpStatusCode.OK, ContentTypes.Json)] + public IActionResult PlayerVariableValueGet(LookupKey lookupKey, Guid variableId) + { + if (lookupKey.IsInvalid) + { + return BadRequest(lookupKey.IsIdInvalid ? @"Invalid id." : @"Invalid name."); } - if (player.TryForgetSpell(new Spell(spell.SpellId), true)) + if (!Player.TryFetch(lookupKey, out var player)) { - using (var context = DbInterface.CreatePlayerContext(false)) + return NotFound($@"No player found for lookup key '{lookupKey}'"); + } + + if (!PlayerVariableBase.TryGet(variableId, out var variableDescriptor)) + { + return NotFound($@"Variable not found for id {variableId}"); + } + + var variable = player.GetVariable(variableDescriptor.Id, true); + return Ok(new VariableValueBody + { + Value = variable.Value.Value, + }); + } + + [HttpPost("{lookupKey:LookupKey}/variables/{variableId:guid}")] + [ProducesResponseType(typeof(StatusMessageResponseBody), (int)HttpStatusCode.BadRequest, ContentTypes.Json)] + [ProducesResponseType(typeof(StatusMessageResponseBody), (int)HttpStatusCode.NotFound, ContentTypes.Json)] + [ProducesResponseType(typeof(PlayerVariable), (int)HttpStatusCode.OK, ContentTypes.Json)] + public IActionResult PlayerVariableSet(LookupKey lookupKey, Guid variableId, [FromBody] VariableValueBody valueBody) + { + if (lookupKey.IsInvalid) + { + return BadRequest(lookupKey.IsIdInvalid ? @"Invalid id." : @"Invalid name."); + } + + if (!Player.TryFetch(lookupKey, out var player)) + { + return NotFound($@"No player found for lookup key '{lookupKey}'"); + } + + if (!PlayerVariableBase.TryGet(variableId, out var variableDescriptor)) + { + return NotFound($@"Variable not found for id {variableId}"); + } + + var variable = player.GetVariable(variableDescriptor.Id, true); + + var changed = false; + if (variable?.Value != null) + { + if (variable.Value.Value != valueBody.Value) { - context.Update(player); - context.SaveChanges(); + variable.Value.Value = valueBody.Value; + changed = true; } + } - return spell; + // ReSharper disable once InvertIf + if (changed) + { + player.StartCommonEventsWithTrigger(CommonEventTrigger.PlayerVariableChange, string.Empty, variableId.ToString()); + using var context = DbInterface.CreatePlayerContext(false); + _ = context.Update(player); + _ = context.SaveChanges(); } - return InternalServerError($@"Failed to remove player spell with id '{spell.SpellId}'."); + return Ok(variable); } + #endregion + + #region Admin Actions + [HttpPost("{lookupKey:LookupKey}/admin/{adminAction:AdminAction}")] - public object DoAdminActionOnPlayerByLookupKey( + [ProducesResponseType(typeof(StatusMessageResponseBody), (int)HttpStatusCode.BadRequest, ContentTypes.Json)] + [ProducesResponseType(typeof(StatusMessageResponseBody), (int)HttpStatusCode.NotFound, ContentTypes.Json)] + [ProducesResponseType(typeof(StatusMessageResponseBody), (int)HttpStatusCode.InternalServerError, ContentTypes.Json)] + [ProducesResponseType(typeof(StatusMessageResponseBody), (int)HttpStatusCode.NotImplemented, ContentTypes.Json)] + [ProducesResponseType(typeof(StatusMessageResponseBody), (int)HttpStatusCode.OK, ContentTypes.Json)] + public IActionResult DoAdminActionOnPlayerByLookupKey( LookupKey lookupKey, AdminAction adminAction, [FromBody] AdminActionParameters actionParameters @@ -811,29 +726,29 @@ [FromBody] AdminActionParameters actionParameters return BadRequest(lookupKey.IsIdInvalid ? @"Invalid id." : @"Invalid name."); } - Tuple fetchResult; - fetchResult = Player.Fetch(lookupKey); + if (!Player.TryFetch(lookupKey, out var client, out var player)) + { + return NotFound($@"No player found for lookup key '{lookupKey}'"); + } return DoAdminActionOnPlayer( - () => fetchResult, - () => NotFound( - lookupKey.HasId - ? $@"No player with id '{lookupKey.Id}'." - : $@"No player with name '{lookupKey.Name}'." - ), adminAction, actionParameters + client, + player, + () => NotFound($@"No player found for lookup key '{lookupKey}'"), + adminAction, + actionParameters ); } - private object DoAdminActionOnPlayer( - Func> fetch, + private IActionResult DoAdminActionOnPlayer( + Client client, + Player player, Func onError, AdminAction adminAction, AdminActionParameters actionParameters ) { - var (client, player) = fetch(); - - if (player == null) + if (player == default) { return onError(); } @@ -866,7 +781,7 @@ AdminActionParameters actionParameters else { // Add ban - Ban.Add( + _ = Ban.Add( userId, actionParameters.Duration, actionParameters.Reason ?? string.Empty, actionParameters.Moderator ?? @"api", actionParameters.Ip ? targetIp : string.Empty ); @@ -882,7 +797,7 @@ AdminActionParameters actionParameters } case AdminAction.UnBan: - Ban.Remove(userId); + _ = Ban.Remove(userId); PacketSender.SendGlobalMsg(Strings.Account.UnbanSuccess.ToString(player.Name)); return Ok(Strings.Account.UnbanSuccess.ToString(player.Name)); @@ -904,14 +819,14 @@ AdminActionParameters actionParameters { if (user == null) { - Mute.Add( + _ = Mute.Add( userId, actionParameters.Duration, actionParameters.Reason ?? string.Empty, actionParameters.Moderator ?? @"api", actionParameters.Ip ? targetIp : string.Empty ); } else { - Mute.Add( + _ = Mute.Add( user, actionParameters.Duration, actionParameters.Reason ?? string.Empty, actionParameters.Moderator ?? @"api", actionParameters.Ip ? targetIp : string.Empty ); @@ -925,11 +840,11 @@ AdminActionParameters actionParameters case AdminAction.UnMute: if (user == null) { - Mute.Remove(userId); + _ = Mute.Remove(userId); } else { - Mute.Remove(user); + _ = Mute.Remove(user); } PacketSender.SendGlobalMsg(Strings.Account.UnmuteSuccess.ToString(player.Name)); @@ -940,7 +855,7 @@ AdminActionParameters actionParameters if (client?.Entity != null) { var mapId = actionParameters.MapId == Guid.Empty ? client.Entity.MapId : actionParameters.MapId; - client.Entity.Warp(mapId, (byte) client.Entity.X, (byte) client.Entity.Y); + client.Entity.Warp(mapId, (byte)client.Entity.X, (byte)client.Entity.Y); return Ok($@"Warped '{player.Name}' to {mapId} ({client.Entity.X}, {client.Entity.Y})."); } @@ -1015,5 +930,58 @@ AdminActionParameters actionParameters return NotFound(Strings.Player.Offline); } + + #endregion + + #region Obsolete + + [HttpPost] + [Obsolete("The appropriate verb for retrieving a list of records is GET not POST")] + public IActionResult ListPost([FromBody] PagingInfo pageInfo) + { + pageInfo.Page = Math.Max(pageInfo.Page, 0); + pageInfo.PageSize = Math.Max(Math.Min(pageInfo.PageSize, 100), 5); + + var values = Player.List( + null, + null, + SortDirection.Ascending, + pageInfo.Page * pageInfo.PageSize, + pageInfo.PageSize, + out var entryTotal + ); + + return Ok( + new DataPage + { + Total = entryTotal, + Page = pageInfo.Page, + PageSize = pageInfo.PageSize, + Count = values.Count, + Values = values, + } + ); + } + + [HttpPost("online")] + [Obsolete("The appropriate verb for retrieving a list of records is GET not POST")] + public object OnlinePost([FromBody] PagingInfo pageInfo) + { + pageInfo.Page = Math.Max(pageInfo.Page, 0); + pageInfo.PageSize = Math.Max(Math.Min(pageInfo.PageSize, 100), 5); + + var entries = Globals.OnlineList?.Skip(pageInfo.Page * pageInfo.PageSize).Take(pageInfo.PageSize).ToList(); + + return new DataPage + { + Total = Globals.OnlineList?.Count ?? 0, + Page = pageInfo.Page, + PageSize = pageInfo.PageSize, + Count = entries?.Count ?? 0, + Values = entries, + }; + } + + #endregion } } diff --git a/Intersect.Server/Web/RestApi/Routes/V1/UserController.cs b/Intersect.Server/Web/RestApi/Routes/V1/UserController.cs index 10b10b8f99..9ec3b5e8bc 100644 --- a/Intersect.Server/Web/RestApi/Routes/V1/UserController.cs +++ b/Intersect.Server/Web/RestApi/Routes/V1/UserController.cs @@ -1,8 +1,10 @@ using System.Net; using System.Text.RegularExpressions; - using Intersect.Enums; using Intersect.GameObjects; +using Intersect.Security; +using Intersect.Server.Collections.Indexing; +using Intersect.Server.Collections.Sorting; using Intersect.Server.Database; using Intersect.Server.Database.PlayerData; using Intersect.Server.Database.PlayerData.Players; @@ -13,40 +15,23 @@ using Intersect.Server.Networking; using Intersect.Server.Notifications; using Intersect.Server.Web.Http; -using Intersect.Server.Web.RestApi.Payloads; using Intersect.Server.Web.RestApi.Types; +using Intersect.Server.Web.RestApi.Types.User; using Intersect.Utilities; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; namespace Intersect.Server.Web.RestApi.Routes.V1 { - [Route("api/v1/users")] [Authorize(Roles = nameof(ApiRoles.UserQuery))] public sealed partial class UserController : IntersectController { - - [HttpPost] - [Obsolete("The appropriate verb for retrieving a list of records is GET not POST")] - public DataPage ListPost([FromBody] PagingInfo pageInfo) - { - var page = Math.Max(pageInfo.Page, 0); - var pageSize = Math.Max(Math.Min(pageInfo.PageSize, PagingInfo.MaxPageSize), PagingInfo.MinPageSize); - - var values = Database.PlayerData.User.List(null, null, SortDirection.Ascending, pageInfo.Page * pageInfo.PageSize, pageInfo.PageSize, out var total); - - return new DataPage( - Total: total, - Page: page, - PageSize: pageSize, - Count: values.Count, - Values: values - ); - } + #region "Basic CRUD Operations" [HttpGet] - public DataPage List( + [ProducesResponseType(typeof(DataPage), (int)HttpStatusCode.OK, ContentTypes.Json)] + public IActionResult List( [FromQuery] int page = 0, [FromQuery] int pageSize = 0, [FromQuery] int limit = PagingInfo.MaxPageSize, @@ -58,7 +43,6 @@ public DataPage List( page = Math.Max(page, 0); pageSize = Math.Max(Math.Min(pageSize, PagingInfo.MaxPageSize), PagingInfo.MinPageSize); limit = Math.Max(Math.Min(limit, pageSize), 1); - var values = Database.PlayerData.User.List(search?.Length > 2 ? search : null, sortBy, sortDirection, page * pageSize, pageSize, out var total); if (limit != pageSize) @@ -66,53 +50,38 @@ public DataPage List( values = values.Take(limit).ToList(); } - return new DataPage( + return Ok(new DataPage( Total: total, Page: page, PageSize: pageSize, Count: values.Count, Values: values - ); + )); } - [HttpGet("{userId:guid}")] - public object UserById(Guid userId) - { - if (Guid.Empty == userId) - { - return BadRequest($@"Invalid user id '{userId}'."); - } - - var user = Database.PlayerData.User.FindById(userId); - - if (user == null) - { - return NotFound($@"No user with id {userId}."); - } - - return user; - } - - [HttpGet("{userName}")] - public object UserByName(string userName) + [HttpGet("{lookupKey:LookupKey}")] + [ProducesResponseType(typeof(StatusMessageResponseBody), (int)HttpStatusCode.BadRequest, ContentTypes.Json)] + [ProducesResponseType(typeof(StatusMessageResponseBody), (int)HttpStatusCode.NotFound, ContentTypes.Json)] + [ProducesResponseType(typeof(User), (int)HttpStatusCode.OK, ContentTypes.Json)] + public IActionResult GetUser(LookupKey lookupKey) { - if (string.IsNullOrWhiteSpace(userName)) + if (lookupKey.IsInvalid) { - return BadRequest("Invalid user name."); + return BadRequest(lookupKey.IsIdInvalid ? @"Invalid user id." : @"Invalid username."); } - var user = Database.PlayerData.User.Find(userName); - - if (user == null) + if (!Database.PlayerData.User.TryFetch(lookupKey, out var user)) { - return NotFound($@"No user with name '{userName}'."); + return NotFound($@"No user found for lookup key '{lookupKey}'."); } - return user; + return Ok(user); } [HttpPost("register")] - public IActionResult RegisterUser([FromBody] UserInfo user) + [ProducesResponseType(typeof(StatusMessageResponseBody), (int)HttpStatusCode.BadRequest, ContentTypes.Json)] + [ProducesResponseType(typeof(RegisterResponseBody), (int)HttpStatusCode.OK, ContentTypes.Json)] + public IActionResult RegisterUser([FromBody] UserInfoRequestBody user) { if (string.IsNullOrEmpty(user.Username) || string.IsNullOrEmpty(user.Email) || @@ -131,6 +100,12 @@ public IActionResult RegisterUser([FromBody] UserInfo user) return BadRequest($@"Invalid username '{user.Username}'."); } + var cleanedPassword = user.Password?.Trim(); + if (!PasswordUtils.IsValidClientPasswordHash(cleanedPassword)) + { + return BadRequest(@"Did not receive a valid password."); + } + if (Database.PlayerData.User.UserExists(user.Username)) { return BadRequest($@"Account already exists with username '{user.Username}'."); @@ -141,61 +116,24 @@ public IActionResult RegisterUser([FromBody] UserInfo user) return BadRequest($@"Account already with email '{user.Email}'."); } - DbInterface.CreateAccount(null, user.Username, user.Password?.ToUpperInvariant()?.Trim(), user.Email); - - return Ok( - new - { - user.Username, user.Email, - } - ); + DbInterface.CreateAccount(null, user.Username, cleanedPassword, user.Email); + return Ok(new RegisterResponseBody(user.Username, user.Email)); } - [HttpDelete("{username}")] - public object DeleteUserByUsername(string userName) - { - if (string.IsNullOrWhiteSpace(userName)) - { - return BadRequest("Invalid user name."); - } - - var user = Database.PlayerData.User.Find(userName); - - if (user == null) - { - return NotFound($@"No user with name '{userName}'."); - } - - foreach (var plyr in user.Players) - { - if (Player.FindOnline(plyr.Id) != null) - { - return BadRequest($@"Cannot delete a user is currently online."); - } - } - - if (!user.TryDelete()) - { - var client = Globals.ClientLookup.Values.FirstOrDefault(c => c.User.Id == user.Id); - client?.LogAndDisconnect(default, nameof(DeleteUserByUsername)); - } - - return user; - } - - [HttpDelete("{userId:guid}")] - public object DeleteUserById(Guid userId) + [HttpDelete("{lookupKey:LookupKey}")] + [ProducesResponseType(typeof(StatusMessageResponseBody), (int)HttpStatusCode.BadRequest, ContentTypes.Json)] + [ProducesResponseType(typeof(StatusMessageResponseBody), (int)HttpStatusCode.NotFound, ContentTypes.Json)] + [ProducesResponseType(typeof(User), (int)HttpStatusCode.OK, ContentTypes.Json)] + public IActionResult DeleteUser(LookupKey lookupKey) { - if (Guid.Empty == userId) + if (lookupKey.IsInvalid) { - return BadRequest($@"Invalid user id '{userId}'."); + return BadRequest(lookupKey.IsIdInvalid ? @"Invalid user id." : @"Invalid username."); } - var user = Database.PlayerData.User.FindById(userId); - - if (user == null) + if (!Database.PlayerData.User.TryFetch(lookupKey, out var user)) { - return NotFound($@"No user with id '{userId}'."); + return NotFound($@"No user found for lookup key '{lookupKey}'."); } foreach (var plyr in user.Players) @@ -209,111 +147,32 @@ public object DeleteUserById(Guid userId) if (!user.TryDelete()) { var client = Globals.ClientLookup.Values.FirstOrDefault(c => c.User.Id == user.Id); - client?.LogAndDisconnect(default, nameof(DeleteUserById)); + _ = client?.LogAndDisconnect(default, nameof(DeleteUser)); } - return user; + return Ok(user); } - [HttpPost("{username}/name")] - public object ChangeNameByUsername(string userName, [FromBody] NameChange change) - { - if (string.IsNullOrWhiteSpace(userName)) - { - return BadRequest("Invalid user name."); - } - - if (!FieldChecking.IsValidUsername(change.Name, Strings.Regex.Username)) - { - return BadRequest($@"Invalid name."); - } - - if (Database.PlayerData.User.UserExists(change.Name)) - { - return BadRequest($@"Name already taken."); - } - - var user = Database.PlayerData.User.Find(userName); - - if (user == null) - { - return NotFound($@"No user with name '{userName}'."); - } - - user.Name = change.Name; - user.Save(); - - return user; - } - - [HttpPost("{userId:guid}/name")] - public object ChangeNameById(Guid userId, [FromBody] NameChange change) - { - if (Guid.Empty == userId) - { - return BadRequest($@"Invalid user id '{userId}'."); - } - - if (!FieldChecking.IsValidUsername(change.Name, Strings.Regex.Username)) - { - return BadRequest($@"Invalid name."); - } - - if (Database.PlayerData.User.UserExists(change.Name)) - { - return BadRequest($@"Name already taken."); - } - - var user = Database.PlayerData.User.FindById(userId); - - if (user == null) - { - return NotFound($@"No user with id {userId}."); - } - - user.Name = change.Name; - user.Save(); - - return user; - } - - [HttpGet("{userName}/players")] - [Authorize] - public object PlayersByUserName(string userName) - { - if (string.IsNullOrWhiteSpace(userName)) - { - return BadRequest("Invalid user name."); - } - - var user = Database.PlayerData.User.Find(userName); - if (user == default) - { - return NotFound($@"No user with name '{userName}'."); - } - - var players = user.Players; - if (players == default) - { - return InternalServerError("Unknown error occurred loading players for user."); - } + #endregion - return players; - } + #region "Player CRUD Operations" - [HttpGet("{userId:guid}/players")] + [HttpGet("{lookupKey:LookupKey}/players")] [Authorize] - public object PlayersByUserId(Guid userId) + [ProducesResponseType(typeof(StatusMessageResponseBody), (int)HttpStatusCode.BadRequest, ContentTypes.Json)] + [ProducesResponseType(typeof(StatusMessageResponseBody), (int)HttpStatusCode.NotFound, ContentTypes.Json)] + [ProducesResponseType(typeof(StatusMessageResponseBody), (int)HttpStatusCode.InternalServerError, ContentTypes.Json)] + [ProducesResponseType(typeof(IEnumerable), (int)HttpStatusCode.OK, ContentTypes.Json)] + public IActionResult GetPlayers(LookupKey lookupKey) { - if (userId == Guid.Empty) + if (lookupKey.IsInvalid) { - return BadRequest("Invalid user id."); + return BadRequest(lookupKey.IsIdInvalid ? @"Invalid user id." : @"Invalid username."); } - var user = Database.PlayerData.User.FindById(userId); - if (user == default) + if (!Database.PlayerData.User.TryFetch(lookupKey, out var user)) { - return NotFound($@"No user with id {userId}."); + return NotFound($@"No user found for lookup key '{lookupKey}'."); } var players = user.Players; @@ -322,16 +181,19 @@ public object PlayersByUserId(Guid userId) return InternalServerError("Unknown error occurred loading players for user."); } - return players; + return Ok(players); } - [HttpGet("{userName}/players/{playerName}")] + [HttpGet("{lookupKey:LookupKey}/players/{playerName}")] [Authorize] - public object PlayerByNameForUserByName(string userName, string playerName) + [ProducesResponseType(typeof(StatusMessageResponseBody), (int)HttpStatusCode.BadRequest, ContentTypes.Json)] + [ProducesResponseType(typeof(StatusMessageResponseBody), (int)HttpStatusCode.NotFound, ContentTypes.Json)] + [ProducesResponseType(typeof(Player), (int)HttpStatusCode.OK, ContentTypes.Json)] + public IActionResult GetPlayer(LookupKey lookupKey, string playerName) { - if (string.IsNullOrWhiteSpace(userName)) + if (lookupKey.IsInvalid) { - return BadRequest("Invalid user name."); + return BadRequest(lookupKey.IsIdInvalid ? @"Invalid user id." : @"Invalid username."); } if (string.IsNullOrWhiteSpace(playerName)) @@ -339,65 +201,31 @@ public object PlayerByNameForUserByName(string userName, string playerName) return BadRequest("Invalid player name."); } - var user = Database.PlayerData.User.Find(userName); - if (user == default) + if (!Database.PlayerData.User.TryFetch(lookupKey, out var user)) { - return NotFound($@"No user with name '{userName}'."); + return NotFound($@"No user found for lookup key '{lookupKey}'."); } var player = user.Players?.FirstOrDefault(p => string.Equals(p?.Name, playerName, StringComparison.Ordinal)); if (player == default) { - return NotFound($@"No player exists for '{userName}' with name '{playerName}'."); - } - - return player; - } - - [HttpGet("{userId:guid}/players/{playerId:guid}")] - [Authorize] - public object PlayerByIdForUserById(Guid userId, Guid playerId) - { - if (Guid.Empty == userId) - { - return BadRequest($@"Invalid user id '{userId}'."); - } - - if (Guid.Empty == playerId) - { - return BadRequest($@"Invalid player id '{playerId}'."); - } - - var user = Database.PlayerData.User.FindById(userId); - - if (user == default) - { - return NotFound($@"No user with id '{userId}'."); - } - - var players = user.Players; - if (players == default) - { - return InternalServerError("Unknown error occurred loading players for user."); - } - - var player = players.FirstOrDefault(p => p?.Id == playerId); - - if (player == default) - { - return NotFound($@"No player found on user {userId} with id {playerId}."); + return NotFound($@"No player exists with name '{playerName}' for user with lookup key '{lookupKey}'"); } - return player; + return Ok(player); } - [HttpGet("{userId:guid}/players/{index:int}")] + [HttpGet("{lookupKey:LookupKey}/players/{index:int}")] [Authorize] - public object PlayerByIndexForUserById(Guid userId, int index) + [ProducesResponseType(typeof(StatusMessageResponseBody), (int)HttpStatusCode.BadRequest, ContentTypes.Json)] + [ProducesResponseType(typeof(StatusMessageResponseBody), (int)HttpStatusCode.NotFound, ContentTypes.Json)] + [ProducesResponseType(typeof(StatusMessageResponseBody), (int)HttpStatusCode.InternalServerError, ContentTypes.Json)] + [ProducesResponseType(typeof(Player), (int)HttpStatusCode.OK, ContentTypes.Json)] + public IActionResult GetPlayerByIndex(LookupKey lookupKey, int index) { - if (Guid.Empty == userId) + if (lookupKey.IsInvalid) { - return BadRequest($@"Invalid user id '{userId}'."); + return BadRequest(lookupKey.IsIdInvalid ? @"Invalid user id." : @"Invalid username."); } if (index < 0) @@ -405,11 +233,9 @@ public object PlayerByIndexForUserById(Guid userId, int index) return BadRequest($@"Invalid player index {index}."); } - var user = Database.PlayerData.User.FindById(userId); - - if (user == null) + if (!Database.PlayerData.User.TryFetch(lookupKey, out var user)) { - return NotFound($@"No user with id '{userId}'."); + return NotFound($@"No user found for lookup key '{lookupKey}'."); } var players = user.Players; @@ -424,105 +250,63 @@ public object PlayerByIndexForUserById(Guid userId, int index) } var player = players.Skip(index).FirstOrDefault(); - if (player == default) { - return NotFound($@"No player found on user {userId} with index {index}."); + return NotFound($@"No player found for user with lookup key {lookupKey} with index {index}."); } - return player; + return Ok(player); } - #region "Change Email" - - [HttpPost("{userName}/manage/email/change")] - [Authorize(Roles = nameof(ApiRoles.UserManage))] - public object UserChangeEmailByName(string userName, [FromBody] AdminChange authorizedChange) - { - if (string.IsNullOrWhiteSpace(userName)) - { - return BadRequest("Invalid user name."); - } - - var email = authorizedChange.New; - - if (string.IsNullOrWhiteSpace(email)) - { - return BadRequest($@"Malformed email address '{email}'."); - } - - if (!FieldChecking.IsWellformedEmailAddress(email, Strings.Regex.Email)) - { - return BadRequest($@"Malformed email address '{email}'."); - } - - var user = Database.PlayerData.User.Find(userName); - - if (user == null) - { - return NotFound($@"No user with name '{userName}'."); - } - - if (Database.PlayerData.User.UserExists(email)) - { - return Conflict(@"Email address already in use."); - } - - user.Email = email; - user.Save(); + #endregion - return user; - } + #region "Account Management" - [HttpPost("{userId:guid}/manage/email/change")] - [Authorize(Roles = nameof(ApiRoles.UserManage))] - public object UserChangeEmailById(Guid userId, [FromBody] AdminChange authorizedChange) + [HttpPost("{lookupKey:LookupKey}/name")] + [ProducesResponseType(typeof(StatusMessageResponseBody), (int)HttpStatusCode.BadRequest, ContentTypes.Json)] + [ProducesResponseType(typeof(StatusMessageResponseBody), (int)HttpStatusCode.NotFound, ContentTypes.Json)] + [ProducesResponseType(typeof(User), (int)HttpStatusCode.OK, ContentTypes.Json)] + public IActionResult ChangeUsername(LookupKey lookupKey, [FromBody] NameChangePayload change) { - if (Guid.Empty == userId) + if (lookupKey.IsInvalid) { - return BadRequest($@"Invalid user id '{userId}'."); + return BadRequest(lookupKey.IsIdInvalid ? @"Invalid user id." : @"Invalid username."); } - var email = authorizedChange.New; - - if (string.IsNullOrWhiteSpace(email)) - { - return BadRequest($@"Malformed email address '{email}'."); - } - - if (!FieldChecking.IsWellformedEmailAddress(email, Strings.Regex.Email)) + if (!FieldChecking.IsValidUsername(change.Name, Strings.Regex.Username)) { - return BadRequest($@"Malformed email address '{email}'."); + return BadRequest($@"Invalid name."); } - var user = Database.PlayerData.User.FindById(userId); - - if (user == null) + if (Database.PlayerData.User.UserExists(change.Name)) { - return NotFound($@"No user with id '{userId}'."); + return BadRequest($@"Name already taken."); } - if (Database.PlayerData.User.UserExists(email)) + if (!Database.PlayerData.User.TryFetch(lookupKey, out var user)) { - return Conflict(@"Email address already in use."); + return NotFound($@"No user found for lookup key '{lookupKey}'."); } - user.Email = email; - user.Save(); - - return user; + user.Name = change.Name; + _ = user.Save(); + return Ok(user); } - [HttpPost("{userName}/email/change")] - public object UserChangeEmailByName(string userName, [FromBody] AuthorizedChange authorizedChange) + [HttpPost("{lookupKey:LookupKey}/manage/email/change")] + [Authorize(Roles = nameof(ApiRoles.UserManage))] + [ProducesResponseType(typeof(StatusMessageResponseBody), (int)HttpStatusCode.BadRequest, ContentTypes.Json)] + [ProducesResponseType(typeof(StatusMessageResponseBody), (int)HttpStatusCode.NotFound, ContentTypes.Json)] + [ProducesResponseType(typeof(StatusMessageResponseBody), (int)HttpStatusCode.Conflict, ContentTypes.Json)] + [ProducesResponseType(typeof(User), (int)HttpStatusCode.OK, ContentTypes.Json)] + public IActionResult ChangeEmail(LookupKey lookupKey, [FromBody] AdminChangeRequestBody authorizedChange) { - if (string.IsNullOrWhiteSpace(userName)) + if (lookupKey.IsInvalid) { - return BadRequest("Invalid user name."); + return BadRequest(lookupKey.IsIdInvalid ? @"Invalid user id." : @"Invalid username."); } var email = authorizedChange.New; - if (string.IsNullOrWhiteSpace(email)) { return BadRequest($@"Malformed email address '{email}'."); @@ -533,16 +317,9 @@ public object UserChangeEmailByName(string userName, [FromBody] AuthorizedChange return BadRequest($@"Malformed email address '{email}'."); } - var user = Database.PlayerData.User.Find(userName); - - if (user == null) + if (!Database.PlayerData.User.TryFetch(lookupKey, out var user)) { - return NotFound($@"No user with name '{userName}'."); - } - - if (!user.IsPasswordValid(authorizedChange.Authorization?.ToUpperInvariant()?.Trim())) - { - return Forbidden(@"Invalid credentials."); + return NotFound($@"No user found for lookup key '{lookupKey}'."); } if (Database.PlayerData.User.UserExists(email)) @@ -551,21 +328,24 @@ public object UserChangeEmailByName(string userName, [FromBody] AuthorizedChange } user.Email = email; - user.Save(); - - return user; + _ = user.Save(); + return Ok(user); } - [HttpPost("{userId:guid}/email/change")] - public object UserChangeEmailById(Guid userId, [FromBody] AuthorizedChange authorizedChange) + [HttpPost("{lookupKey:LookupKey}/email/change")] + [ProducesResponseType(typeof(StatusMessageResponseBody), (int)HttpStatusCode.BadRequest, ContentTypes.Json)] + [ProducesResponseType(typeof(StatusMessageResponseBody), (int)HttpStatusCode.NotFound, ContentTypes.Json)] + [ProducesResponseType(typeof(StatusMessageResponseBody), (int)HttpStatusCode.Conflict, ContentTypes.Json)] + [ProducesResponseType(typeof(StatusMessageResponseBody), (int)HttpStatusCode.Forbidden, ContentTypes.Json)] + [ProducesResponseType(typeof(User), (int)HttpStatusCode.OK, ContentTypes.Json)] + public IActionResult UserChangeEmail(LookupKey lookupKey, [FromBody] AuthorizedChangeRequestBody authorizedChange) { - if (Guid.Empty == userId) + if (lookupKey.IsInvalid) { - return BadRequest($@"Invalid user id '{userId}'."); + return BadRequest(lookupKey.IsIdInvalid ? @"Invalid user id." : @"Invalid username."); } var email = authorizedChange.New; - if (string.IsNullOrWhiteSpace(email)) { return BadRequest($@"Malformed email address '{email}'."); @@ -576,11 +356,9 @@ public object UserChangeEmailById(Guid userId, [FromBody] AuthorizedChange autho return BadRequest($@"Malformed email address '{email}'."); } - var user = Database.PlayerData.User.FindById(userId); - - if (user == null) + if (!Database.PlayerData.User.TryFetch(lookupKey, out var user)) { - return NotFound($@"No user with id '{userId}'."); + return NotFound($@"No user found for lookup key '{lookupKey}'."); } if (!user.IsPasswordValid(authorizedChange.Authorization?.ToUpperInvariant()?.Trim())) @@ -594,104 +372,66 @@ public object UserChangeEmailById(Guid userId, [FromBody] AuthorizedChange autho } user.Email = email; - user.Save(); - - return user; - } - - #endregion - - #region "Validate Password" - - [HttpPost("{userName}/password/validate")] - public object UserValidatePasswordByName(string userName, [FromBody] PasswordValidation data) - { - if (string.IsNullOrWhiteSpace(userName)) - { - return BadRequest("Invalid user name."); - } - - if (string.IsNullOrWhiteSpace(data.Password)) - { - return BadRequest(@"No password provided."); - } - - if (!Regex.IsMatch(data.Password?.ToUpperInvariant()?.Trim(), "^[0-9A-Fa-f]{64}$", RegexOptions.Compiled)) - { - return BadRequest(@"Did not receive a valid password."); - } - - var user = Database.PlayerData.User.Find(userName); - - if (user == null) - { - return NotFound($@"No user with name '{userName}'."); - } - - if (user.IsPasswordValid(data.Password?.ToUpperInvariant()?.Trim())) - { - return Ok("Password Correct"); - } - - return BadRequest(@"Invalid credentials."); + _ = user.Save(); + return Ok(user); } - [HttpPost("{userId:guid}/password/validate")] - public object UserValidatePasswordById(Guid userId, [FromBody] PasswordValidation data) + [HttpPost("{lookupKey:LookupKey}/password/validate")] + [ProducesResponseType(typeof(StatusMessageResponseBody), (int)HttpStatusCode.BadRequest, ContentTypes.Json)] + [ProducesResponseType(typeof(StatusMessageResponseBody), (int)HttpStatusCode.NotFound, ContentTypes.Json)] + [ProducesResponseType(typeof(StatusMessageResponseBody), (int)HttpStatusCode.OK, ContentTypes.Json)] + public IActionResult ValidatePassword(LookupKey lookupKey, [FromBody] PasswordValidationRequestBody data) { - if (Guid.Empty == userId) + if (lookupKey.IsInvalid) { - return BadRequest($@"Invalid user id '{userId}'."); + return BadRequest(lookupKey.IsIdInvalid ? @"Invalid user id." : @"Invalid username."); } - if (string.IsNullOrWhiteSpace(data.Password)) + var cleanedPassword = data.Password?.Trim(); + if (string.IsNullOrWhiteSpace(cleanedPassword)) { return BadRequest(@"No password provided."); } - if (!Regex.IsMatch(data.Password?.ToUpperInvariant()?.Trim(), "^[0-9A-Fa-f]{64}$", RegexOptions.Compiled)) + if (!PasswordUtils.IsValidClientPasswordHash(cleanedPassword)) { return BadRequest(@"Did not receive a valid password."); } - var user = Database.PlayerData.User.FindById(userId); - - if (user == null) + if (!Database.PlayerData.User.TryFetch(lookupKey, out var user)) { - return NotFound($@"No user with name '{userId}'."); + return NotFound($@"No user found for lookup key '{lookupKey}'."); } - if (user.IsPasswordValid(data.Password?.ToUpperInvariant()?.Trim())) + if (!user.IsPasswordValid(cleanedPassword)) { - return Ok("Password Correct"); + return BadRequest(@"Invalid credentials."); } - return BadRequest(@"Invalid credentials."); + return Ok("Password Correct"); } - #endregion - - #region "Change Password" - - [HttpPost("{userName}/manage/password/change")] + [HttpPost("{lookupKey:LookupKey}/manage/password/change")] [Authorize(Roles = nameof(ApiRoles.UserManage))] - public object UserChangePassword(string userName, [FromBody] AdminChange authorizedChange) + [ProducesResponseType(typeof(StatusMessageResponseBody), (int)HttpStatusCode.BadRequest, ContentTypes.Json)] + [ProducesResponseType(typeof(StatusMessageResponseBody), (int)HttpStatusCode.NotFound, ContentTypes.Json)] + [ProducesResponseType(typeof(StatusMessageResponseBody), (int)HttpStatusCode.Forbidden, ContentTypes.Json)] + [ProducesResponseType(typeof(StatusMessageResponseBody), (int)HttpStatusCode.OK, ContentTypes.Json)] + public IActionResult ChangePassword(LookupKey lookupKey, [FromBody] AdminChangeRequestBody authorizedChange) { - if (string.IsNullOrWhiteSpace(userName)) + if (lookupKey.IsInvalid) { - return BadRequest("Invalid user name."); + return BadRequest(lookupKey.IsIdInvalid ? @"Invalid user id." : @"Invalid username."); } - if (!authorizedChange.IsValid) + if (string.IsNullOrEmpty(authorizedChange.New)) { return BadRequest(@"Invalid payload"); } - var user = Database.PlayerData.User.Find(userName); - - if (user == null) + if (!Database.PlayerData.User.TryFetch(lookupKey, out var user)) { - return NotFound($@"No user with name '{userName}'."); + return NotFound($@"No user found for lookup key '{lookupKey}'."); } if (!user.TrySetPassword(authorizedChange.New?.ToUpperInvariant()?.Trim())) @@ -699,170 +439,77 @@ public object UserChangePassword(string userName, [FromBody] AdminChange authori return Forbidden(@"Failed to update password."); } - user.Save(); - + _ = user.Save(); return Ok("Password updated."); } - [HttpPost("{userId:guid}/manage/password/change")] - [Authorize(Roles = nameof(ApiRoles.UserManage))] - public object UserChangePassword(Guid userId, [FromBody] AdminChange authorizedChange) - { - if (Guid.Empty == userId) - { - return BadRequest($@"Invalid user id '{userId}'."); - } - - if (!authorizedChange.IsValid) - { - return BadRequest(@"Invalid payload"); - } - - var user = Database.PlayerData.User.FindById(userId); - - if (user == null) - { - return NotFound($@"No user with name '{userId}'."); - } - - if (!user.TrySetPassword(authorizedChange.New?.ToUpperInvariant()?.Trim())) - { - return Forbidden(@"Failed to update password."); - } - - user.Save(); - - return Ok("Password Updated."); - } - - [HttpPost("{userName}/password/change")] - public object UserChangePassword(string userName, [FromBody] AuthorizedChange authorizedChange) + [HttpPost("{lookupKey:LookupKey}/password/change")] + [ProducesResponseType(typeof(StatusMessageResponseBody), (int)HttpStatusCode.BadRequest, ContentTypes.Json)] + [ProducesResponseType(typeof(StatusMessageResponseBody), (int)HttpStatusCode.NotFound, ContentTypes.Json)] + [ProducesResponseType(typeof(StatusMessageResponseBody), (int)HttpStatusCode.Forbidden, ContentTypes.Json)] + [ProducesResponseType(typeof(StatusMessageResponseBody), (int)HttpStatusCode.OK, ContentTypes.Json)] + public IActionResult UserChangePassword(LookupKey lookupKey, [FromBody] AuthorizedChangeRequestBody authorizedChange) { - if (string.IsNullOrWhiteSpace(userName)) + if (lookupKey.IsInvalid) { - return BadRequest("Invalid user name."); + return BadRequest(lookupKey.IsIdInvalid ? @"Invalid user id." : @"Invalid username."); } - if (!authorizedChange.IsValid) + if (string.IsNullOrWhiteSpace(authorizedChange.Authorization) || string.IsNullOrWhiteSpace(authorizedChange.New)) { return BadRequest(@"Invalid payload"); } - var user = Database.PlayerData.User.Find(userName); - - if (user == null) + if (!Database.PlayerData.User.TryFetch(lookupKey, out var user)) { - return NotFound($@"No user with name '{userName}'."); + return NotFound($@"No user found for lookup key '{lookupKey}'."); } - if (!user.TryChangePassword( - authorizedChange.Authorization?.ToUpperInvariant()?.Trim(), authorizedChange.New?.ToUpperInvariant()?.Trim() - )) + var oldPassword = authorizedChange.Authorization?.ToUpperInvariant()?.Trim(); + var newPassword = authorizedChange.New?.ToUpperInvariant()?.Trim(); + if (!user.TryChangePassword(oldPassword, newPassword)) { return Forbidden(@"Invalid credentials."); } - user.Save(); - + _ = user.Save(); return Ok("Password Updated."); } - [HttpPost("{userId:guid}/password/change")] - public object UserChangePassword(Guid userId, [FromBody] AuthorizedChange authorizedChange) - { - if (Guid.Empty == userId) - { - return BadRequest($@"Invalid user id '{userId}'."); - } - - if (!authorizedChange.IsValid) - { - return BadRequest(@"Invalid payload"); - } - - var user = Database.PlayerData.User.FindById(userId); - - if (user == null) - { - return NotFound($@"No user with name '{userId}'."); - } - - if (!user.TryChangePassword( - authorizedChange.Authorization?.ToUpperInvariant()?.Trim(), authorizedChange.New?.ToUpperInvariant()?.Trim() - )) - { - return Forbidden(@"Invalid credentials."); - } - - user.Save(); - - return "Password updated."; - } - - #endregion - - #region "Request Reset Email Password" - - [HttpGet("{userName}/password/reset")] - public object UserSendPasswordResetEmailByName(string userName) + [HttpGet("{lookupKey:LookupKey}/password/reset")] + [ProducesResponseType(typeof(StatusMessageResponseBody), (int)HttpStatusCode.BadRequest, ContentTypes.Json)] + [ProducesResponseType(typeof(StatusMessageResponseBody), (int)HttpStatusCode.NotFound, ContentTypes.Json)] + [ProducesResponseType(typeof(StatusMessageResponseBody), (int)HttpStatusCode.InternalServerError, ContentTypes.Json)] + [ProducesResponseType(typeof(StatusMessageResponseBody), (int)HttpStatusCode.OK, ContentTypes.Json)] + public IActionResult UserSendPasswordResetEmail(LookupKey lookupKey) { - if (string.IsNullOrWhiteSpace(userName)) + if (lookupKey.IsInvalid) { - return BadRequest("Invalid user name."); + return BadRequest(lookupKey.IsIdInvalid ? @"Invalid user id." : @"Invalid username."); } - var user = Database.PlayerData.User.Find(userName); - - if (user == null) + if (!Database.PlayerData.User.TryFetch(lookupKey, out var user)) { - return NotFound($@"No user with name '{userName}'."); + return NotFound($@"No user found for lookup key '{lookupKey}'."); } - if (Options.Smtp.IsValid()) - { - var email = new PasswordResetEmail(user); - if (email.Send()) - { - return Ok("Password reset email sent."); - } - - return InternalServerError("Failed to send reset email."); - } - else + if (!Options.Smtp.IsValid()) { return NotFound("Could not send password reset email, SMTP settings on the server are not configured!"); } - } - [HttpGet("{userId:guid}/password/reset")] - public object UserSendPasswordResetEmailById(Guid userId) - { - var user = Database.PlayerData.User.FindById(userId); - - if (user == null) - { - return NotFound($@"No user with name '{userId}'."); - } - - if (Options.Smtp.IsValid()) + var email = new PasswordResetEmail(user); + if (!email.Send()) { - var email = new PasswordResetEmail(user); - if (email.Send()) - { - return Ok("Password reset email sent."); - } - return InternalServerError("Failed to send reset email."); } - else - { - return NotFound("Could not send password reset email, SMTP settings on the server are not configured!"); - } + + return Ok("Password reset email sent."); } #endregion - #region Variables + #region User Variables [HttpGet("{lookupKey:LookupKey}/variables")] [ProducesResponseType(typeof(StatusMessageResponseBody), (int)HttpStatusCode.BadRequest, ContentTypes.Json)] @@ -988,7 +635,7 @@ public IActionResult UserVariableSet(LookupKey lookupKey, Guid variableId, [From if (changed) { user.StartCommonEventsWithTriggerForAll(CommonEventTrigger.UserVariableChange, string.Empty, variableId.ToString()); - user.UpdatedVariables.AddOrUpdate( + _ = user.UpdatedVariables.AddOrUpdate( variableId, variableDescriptor, (_, _) => variableDescriptor @@ -1003,7 +650,11 @@ public IActionResult UserVariableSet(LookupKey lookupKey, Guid variableId, [From #region "Admin Action" [HttpPost("{lookupKey:LookupKey}/admin/{adminAction:AdminAction}")] - public object DoAdminActionOnUserByLookupKey( + [ProducesResponseType(typeof(StatusMessageResponseBody), (int)HttpStatusCode.BadRequest, ContentTypes.Json)] + [ProducesResponseType(typeof(StatusMessageResponseBody), (int)HttpStatusCode.NotFound, ContentTypes.Json)] + [ProducesResponseType(typeof(StatusMessageResponseBody), (int)HttpStatusCode.NotImplemented, ContentTypes.Json)] + [ProducesResponseType(typeof(StatusMessageResponseBody), (int)HttpStatusCode.OK, ContentTypes.Json)] + public IActionResult DoAdminActionOnUserByLookupKey( LookupKey lookupKey, AdminAction adminAction, [FromBody] AdminActionParameters actionParameters @@ -1020,22 +671,23 @@ [FromBody] AdminActionParameters actionParameters } return DoAdminActionOnUser( - () => (client, user), + client, + user, () => NotFound($@"No user found for lookup key '{lookupKey}'."), - adminAction, actionParameters + adminAction, + actionParameters ); } private IActionResult DoAdminActionOnUser( - Func> fetch, + Client client, + User user, Func onError, AdminAction adminAction, AdminActionParameters actionParameters ) { - var (client, user) = fetch(); - - if (user == null) + if (user == default) { return onError(); } @@ -1066,7 +718,7 @@ AdminActionParameters actionParameters else { // Add ban - Ban.Add( + _ = Ban.Add( user.Id, actionParameters.Duration, actionParameters.Reason ?? string.Empty, actionPerformer.Name, actionParameters.Ip ? targetIp : string.Empty ); @@ -1082,7 +734,7 @@ AdminActionParameters actionParameters } case AdminAction.UnBan: - Ban.Remove(user.Id, false); + _ = Ban.Remove(user.Id, false); PacketSender.SendGlobalMsg(Strings.Account.UnbanSuccess.ToString(user.Name)); return Ok(Strings.Account.UnbanSuccess.ToString(user.Name)); @@ -1102,7 +754,7 @@ AdminActionParameters actionParameters // If target is online, not yet muted and the action performer has the authority to mute. else { - Mute.Add( + _ = Mute.Add( user, actionParameters.Duration, actionParameters.Reason ?? string.Empty, actionPerformer.Name, actionParameters.Ip ? targetIp : string.Empty ); @@ -1113,7 +765,7 @@ AdminActionParameters actionParameters } case AdminAction.UnMute: - Mute.Remove(user); + _ = Mute.Remove(user); PacketSender.SendGlobalMsg(Strings.Account.UnmuteSuccess.ToString(user.Name)); return Ok(Strings.Account.UnmuteSuccess.ToString(user.Name)); @@ -1127,7 +779,7 @@ AdminActionParameters actionParameters } var mapId = actionParameters.MapId == Guid.Empty ? player.MapId : actionParameters.MapId; - player.Warp(mapId, (byte) player.X, (byte) player.Y); + player.Warp(mapId, (byte)player.X, (byte)player.Y); return Ok($@"Warped '{player.Name}' to {mapId} ({player.X}, {player.Y})."); } @@ -1191,7 +843,7 @@ AdminActionParameters actionParameters case AdminAction.WarpMeTo: case AdminAction.WarpToMe: - return BadRequest($@"'{adminAction.ToString()}' not supported by the API."); + return BadRequest($@"'{adminAction}' not supported by the API."); case AdminAction.SetSprite: case AdminAction.SetFace: @@ -1205,6 +857,26 @@ AdminActionParameters actionParameters #endregion - } + #region Obsolete + + [HttpPost] + [Obsolete("The appropriate verb for retrieving a list of records is GET not POST")] + public IActionResult ListPost([FromBody] PagingInfo pageInfo) + { + var page = Math.Max(pageInfo.Page, 0); + var pageSize = Math.Max(Math.Min(pageInfo.PageSize, PagingInfo.MaxPageSize), PagingInfo.MinPageSize); + var values = Database.PlayerData.User.List(null, null, SortDirection.Ascending, pageInfo.Page * pageInfo.PageSize, pageInfo.PageSize, out var total); + + return Ok(new DataPage( + Total: total, + Page: page, + PageSize: pageSize, + Count: values.Count, + Values: values + )); + } + + #endregion + } } diff --git a/Intersect.Server/Web/RestApi/Routes/V1/VariablesController.cs b/Intersect.Server/Web/RestApi/Routes/V1/VariablesController.cs index a131b55184..d6c170d34b 100644 --- a/Intersect.Server/Web/RestApi/Routes/V1/VariablesController.cs +++ b/Intersect.Server/Web/RestApi/Routes/V1/VariablesController.cs @@ -1,25 +1,26 @@ -using Intersect.Enums; +using System.Net; +using Intersect.Enums; using Intersect.GameObjects; using Intersect.Server.Database; using Intersect.Server.Database.GameData; using Intersect.Server.Entities; -using Intersect.Server.Web.RestApi.Payloads; +using Intersect.Server.Web.Http; using Intersect.Server.Web.RestApi.Types; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; namespace Intersect.Server.Web.RestApi.Routes.V1; -[Route("api/v1/variables/global")] +[Route("api/v1/variables")] [Authorize] public sealed partial class VariablesController : IntersectController { [HttpGet] - public IActionResult GlobalVariablesGet([FromQuery] PagingInfo pageInfo) + [ProducesResponseType(typeof(DataPage), (int)HttpStatusCode.OK, ContentTypes.Json)] + public IActionResult ServerVariablesGet([FromQuery] PagingInfo pageInfo) { pageInfo.Page = Math.Max(pageInfo.Page, 0); pageInfo.PageSize = Math.Max(Math.Min(pageInfo.PageSize, 100), 5); - var entries = GameContext.Queries.ServerVariables(pageInfo.Page, pageInfo.PageSize)?.ToList(); return Ok( @@ -35,7 +36,10 @@ public IActionResult GlobalVariablesGet([FromQuery] PagingInfo pageInfo) } [HttpGet("{variableId:guid}")] - public IActionResult GlobalVariableGet(Guid variableId) + [ProducesResponseType(typeof(StatusMessageResponseBody), (int)HttpStatusCode.BadRequest, ContentTypes.Json)] + [ProducesResponseType(typeof(StatusMessageResponseBody), (int)HttpStatusCode.NotFound, ContentTypes.Json)] + [ProducesResponseType(typeof(ServerVariableBase), (int)HttpStatusCode.OK, ContentTypes.Json)] + public IActionResult ServerVariableGet(Guid variableId) { if (variableId == default) { @@ -47,14 +51,17 @@ public IActionResult GlobalVariableGet(Guid variableId) // ReSharper disable once ConvertIfStatementToReturnStatement if (variable == null) { - return NotFound($@"No global variable with id '{variableId}'."); + return NotFound($@"No server variable with id '{variableId}'."); } return Ok(variable); } [HttpGet("{variableId:guid}/value")] - public IActionResult GlobalVariableGetValue(Guid variableId) + [ProducesResponseType(typeof(StatusMessageResponseBody), (int)HttpStatusCode.BadRequest, ContentTypes.Json)] + [ProducesResponseType(typeof(StatusMessageResponseBody), (int)HttpStatusCode.NotFound, ContentTypes.Json)] + [ProducesResponseType(typeof(VariableValueBody), (int)HttpStatusCode.OK, ContentTypes.Json)] + public IActionResult ServerVariableGetValue(Guid variableId) { if (variableId == default) { @@ -65,7 +72,7 @@ public IActionResult GlobalVariableGetValue(Guid variableId) if (variable == null) { - return NotFound($@"No global variable with id '{variableId}'."); + return NotFound($@"No server variable with id '{variableId}'."); } return Ok( @@ -77,7 +84,10 @@ public IActionResult GlobalVariableGetValue(Guid variableId) } [HttpPost("{variableId:guid}")] - public IActionResult GlobalVariableSet(Guid variableId, [FromBody] VariableValueBody variableValue) + [ProducesResponseType(typeof(StatusMessageResponseBody), (int)HttpStatusCode.BadRequest, ContentTypes.Json)] + [ProducesResponseType(typeof(StatusMessageResponseBody), (int)HttpStatusCode.NotFound, ContentTypes.Json)] + [ProducesResponseType(typeof(ServerVariableBase), (int)HttpStatusCode.OK, ContentTypes.Json)] + public IActionResult ServerVariableSet(Guid variableId, [FromBody] VariableValueBody variableValue) { if (variableId == default) { @@ -88,17 +98,14 @@ public IActionResult GlobalVariableSet(Guid variableId, [FromBody] VariableValue if (variable == null) { - return NotFound($@"No global variable with id '{variableId}'."); + return NotFound($@"No server variable with id '{variableId}'."); } var changed = false; - if (variable.Value != null) + if (variable.Value != null && variable.Value.Value != variableValue.Value) { - if (variable.Value.Value != variableValue.Value) - { - variable.Value.Value = variableValue.Value; - changed = true; - } + variable.Value.Value = variableValue.Value; + changed = true; } // ReSharper disable once InvertIf @@ -109,7 +116,8 @@ public IActionResult GlobalVariableSet(Guid variableId, [FromBody] VariableValue "", variableId.ToString() ); - DbInterface.UpdatedServerVariables.AddOrUpdate(variable.Id, variable, (_, _) => variable); + + _ = DbInterface.UpdatedServerVariables.AddOrUpdate(variable.Id, variable, (_, _) => variable); } return Ok(variable); diff --git a/Intersect.Server/Web/RestApi/Types/AdminActionParameters.cs b/Intersect.Server/Web/RestApi/Types/AdminActionParameters.cs new file mode 100644 index 0000000000..0f4095349f --- /dev/null +++ b/Intersect.Server/Web/RestApi/Types/AdminActionParameters.cs @@ -0,0 +1,18 @@ +namespace Intersect.Server.Web.RestApi.Types; + +public partial struct AdminActionParameters +{ + public string Moderator { get; set; } + + public int Duration { get; set; } + + public bool Ip { get; set; } + + public string Reason { get; set; } + + public byte X { get; set; } + + public byte Y { get; set; } + + public Guid MapId { get; set; } +} \ No newline at end of file diff --git a/Intersect.Server/Web/RestApi/Types/Chat/ChatMessage.cs b/Intersect.Server/Web/RestApi/Types/Chat/ChatMessage.cs new file mode 100644 index 0000000000..0ff56ed05f --- /dev/null +++ b/Intersect.Server/Web/RestApi/Types/Chat/ChatMessage.cs @@ -0,0 +1,53 @@ +using System.ComponentModel; +using System.Globalization; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Routing; +using Newtonsoft.Json; + +namespace Intersect.Server.Web.RestApi.Types.Chat; + +public partial struct ChatMessage +{ + public string Message { get; set; } + + public Color Color { get; set; } + + public string Target { get; set; } + + public partial class Converter : TypeConverter + { + public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType) + { + return typeof(string) == sourceType; + } + + public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value) + { + if (value == null) + { + return default(ChatMessage); + } + + if (typeof(string) != value.GetType()) + { + throw new ArgumentException(); + } + + return JsonConvert.DeserializeObject(value as string); + } + } + + internal sealed partial class RouteConstraint : IRouteConstraint + { + public bool Match( + HttpContext httpContext, + IRouter route, + string routeKey, + RouteValueDictionary values, + RouteDirection routeDirection + ) + { + return values.TryGetValue(routeKey, out var value) && value != null; + } + } +} diff --git a/Intersect.Server/Web/RestApi/Types/Chat/ChatMessageLookupKeyResponseBody.cs b/Intersect.Server/Web/RestApi/Types/Chat/ChatMessageLookupKeyResponseBody.cs new file mode 100644 index 0000000000..63144394fa --- /dev/null +++ b/Intersect.Server/Web/RestApi/Types/Chat/ChatMessageLookupKeyResponseBody.cs @@ -0,0 +1,5 @@ +using Intersect.Server.Collections.Indexing; + +namespace Intersect.Server.Web.RestApi.Types.Chat; + +public record ChatMessageLookupKeyResponseBody(LookupKey LookupKey, bool Success, ChatMessage ChatMessage) : ChatMessageResponseBody(Success, ChatMessage); diff --git a/Intersect.Server/Web/RestApi/Types/Chat/ChatMessageMapResponseBody.cs b/Intersect.Server/Web/RestApi/Types/Chat/ChatMessageMapResponseBody.cs new file mode 100644 index 0000000000..e56f60d118 --- /dev/null +++ b/Intersect.Server/Web/RestApi/Types/Chat/ChatMessageMapResponseBody.cs @@ -0,0 +1,3 @@ +namespace Intersect.Server.Web.RestApi.Types.Chat; + +public record ChatMessageMapResponseBody(Guid MapId, bool Success, ChatMessage ChatMessage) : ChatMessageResponseBody(Success, ChatMessage); \ No newline at end of file diff --git a/Intersect.Server/Web/RestApi/Types/Chat/ChatMessageResponseBody.cs b/Intersect.Server/Web/RestApi/Types/Chat/ChatMessageResponseBody.cs new file mode 100644 index 0000000000..c83b75c14f --- /dev/null +++ b/Intersect.Server/Web/RestApi/Types/Chat/ChatMessageResponseBody.cs @@ -0,0 +1,3 @@ +namespace Intersect.Server.Web.RestApi.Types.Chat; + +public record ChatMessageResponseBody(bool Success, ChatMessage ChatMessage); \ No newline at end of file diff --git a/Intersect.Server/Web/RestApi/Types/DataPage.cs b/Intersect.Server/Web/RestApi/Types/DataPage.cs index 4a07057671..0dc95f18a0 100644 --- a/Intersect.Server/Web/RestApi/Types/DataPage.cs +++ b/Intersect.Server/Web/RestApi/Types/DataPage.cs @@ -1,12 +1,10 @@ -using Intersect.Framework.Core.Serialization; -using Intersect.Framework.Reflection; -using Intersect.Server.Web.RestApi.Payloads; +using Intersect.Server.Collections.Sorting; using Newtonsoft.Json; using Newtonsoft.Json.Serialization; namespace Intersect.Server.Web.RestApi.Types; -public struct DataPage( +public readonly struct DataPage( int Total, int Page, int PageSize, diff --git a/Intersect.Server/Web/RestApi/Types/Demo/DemoResponseBody.cs b/Intersect.Server/Web/RestApi/Types/Demo/DemoResponseBody.cs new file mode 100644 index 0000000000..9e44c574c3 --- /dev/null +++ b/Intersect.Server/Web/RestApi/Types/Demo/DemoResponseBody.cs @@ -0,0 +1,3 @@ +namespace Intersect.Server.Web.RestApi.Types.Demo; + +public record struct DemoResponseBody(string Value); diff --git a/Intersect.Server/Web/RestApi/Types/Guild/GuildRankPayload.cs b/Intersect.Server/Web/RestApi/Types/Guild/GuildRankPayload.cs new file mode 100644 index 0000000000..a544c34595 --- /dev/null +++ b/Intersect.Server/Web/RestApi/Types/Guild/GuildRankPayload.cs @@ -0,0 +1,6 @@ +namespace Intersect.Server.Web.RestApi.Types.Guild; + +public partial struct GuildRankPayload +{ + public int Rank { get; set; } +} diff --git a/Intersect.Server/Web/RestApi/Types/IDataPage.cs b/Intersect.Server/Web/RestApi/Types/IDataPage.cs index 893830137b..ccf4ab8e92 100644 --- a/Intersect.Server/Web/RestApi/Types/IDataPage.cs +++ b/Intersect.Server/Web/RestApi/Types/IDataPage.cs @@ -1,4 +1,4 @@ -using Intersect.Server.Web.RestApi.Payloads; +using Intersect.Server.Collections.Sorting; namespace Intersect.Server.Web.RestApi.Types; diff --git a/Intersect.Server/Web/RestApi/Types/IdPayload.cs b/Intersect.Server/Web/RestApi/Types/IdPayload.cs new file mode 100644 index 0000000000..1ceed7d7ed --- /dev/null +++ b/Intersect.Server/Web/RestApi/Types/IdPayload.cs @@ -0,0 +1,3 @@ +namespace Intersect.Server.Web.RestApi.Types; + +public record struct IdPayload(Guid Id); \ No newline at end of file diff --git a/Intersect.Server/Web/RestApi/Types/Info/AuthorizedResponseBody.cs b/Intersect.Server/Web/RestApi/Types/Info/AuthorizedResponseBody.cs new file mode 100644 index 0000000000..ef57f8238f --- /dev/null +++ b/Intersect.Server/Web/RestApi/Types/Info/AuthorizedResponseBody.cs @@ -0,0 +1,3 @@ +namespace Intersect.Server.Web.RestApi.Types.Info; + +public record struct AuthorizedResponseBody(bool authorized = true); diff --git a/Intersect.Server/Web/RestApi/Types/Info/InfoResponseBody.cs b/Intersect.Server/Web/RestApi/Types/Info/InfoResponseBody.cs new file mode 100644 index 0000000000..070f8758f4 --- /dev/null +++ b/Intersect.Server/Web/RestApi/Types/Info/InfoResponseBody.cs @@ -0,0 +1,8 @@ +namespace Intersect.Server.Web.RestApi.Types.Info; + +public readonly struct InfoResponseBody(string name, int port) +{ + public string Name { get; } = name; + + public int Port { get; } = port; +} diff --git a/Intersect.Server/Web/RestApi/Types/Info/InfoStatsResponseBody.cs b/Intersect.Server/Web/RestApi/Types/Info/InfoStatsResponseBody.cs new file mode 100644 index 0000000000..83298e7d16 --- /dev/null +++ b/Intersect.Server/Web/RestApi/Types/Info/InfoStatsResponseBody.cs @@ -0,0 +1,12 @@ +namespace Intersect.Server.Web.RestApi.Types.Info; + +public struct InfoStatsResponseBody(long uptime, long cps, int? connectedClients, int? onlineCount) +{ + public long Uptime { get; set; } = uptime; + + public long Cps { get; set; } = cps; + + public int? ConnectedClients { get; set; } = connectedClients; + + public int? OnlineCount { get; set; } = onlineCount; +} diff --git a/Intersect.Server/Web/RestApi/Types/Logs/IpAddressResponseBody.cs b/Intersect.Server/Web/RestApi/Types/Logs/IpAddressResponseBody.cs new file mode 100644 index 0000000000..a8c82bd1a1 --- /dev/null +++ b/Intersect.Server/Web/RestApi/Types/Logs/IpAddressResponseBody.cs @@ -0,0 +1,10 @@ +namespace Intersect.Server.Web.RestApi.Types.Logs; + +public partial class IpAddressResponseBody +{ + public string Ip { get; set; } + + public DateTime LastUsed { get; set; } + + public Dictionary OtherUsers { get; set; } = []; +} diff --git a/Intersect.Server/Web/RestApi/Payloads/ModelBinderProvider.cs b/Intersect.Server/Web/RestApi/Types/ModelBinderProvider.cs similarity index 91% rename from Intersect.Server/Web/RestApi/Payloads/ModelBinderProvider.cs rename to Intersect.Server/Web/RestApi/Types/ModelBinderProvider.cs index 0224e71b27..bd2565482d 100644 --- a/Intersect.Server/Web/RestApi/Payloads/ModelBinderProvider.cs +++ b/Intersect.Server/Web/RestApi/Types/ModelBinderProvider.cs @@ -1,7 +1,7 @@ using Microsoft.AspNetCore.Mvc.ModelBinding; using Microsoft.AspNetCore.Mvc.ModelBinding.Binders; -namespace Intersect.Server.Web.RestApi.Payloads; +namespace Intersect.Server.Web.RestApi.Types; public partial class ModelBinderProvider : IModelBinderProvider { diff --git a/Intersect.Server/Web/RestApi/Types/NameChangePayload.cs b/Intersect.Server/Web/RestApi/Types/NameChangePayload.cs new file mode 100644 index 0000000000..5631733f85 --- /dev/null +++ b/Intersect.Server/Web/RestApi/Types/NameChangePayload.cs @@ -0,0 +1,6 @@ +namespace Intersect.Server.Web.RestApi.Types; + +public partial struct NameChangePayload +{ + public string Name { get; set; } +} diff --git a/Intersect.Server/Web/RestApi/Payloads/PagingInfo.cs b/Intersect.Server/Web/RestApi/Types/PagingInfo.cs similarity index 86% rename from Intersect.Server/Web/RestApi/Payloads/PagingInfo.cs rename to Intersect.Server/Web/RestApi/Types/PagingInfo.cs index 11cd12668f..ef88b38ab7 100644 --- a/Intersect.Server/Web/RestApi/Payloads/PagingInfo.cs +++ b/Intersect.Server/Web/RestApi/Types/PagingInfo.cs @@ -1,4 +1,4 @@ -namespace Intersect.Server.Web.RestApi.Payloads; +namespace Intersect.Server.Web.RestApi.Types; // TODO: Struct, right now there's an exception when it's a struct and I'm not sure how to fix it public class PagingInfo diff --git a/Intersect.Server/Web/RestApi/Types/Player/ItemInfoRequestBody.cs b/Intersect.Server/Web/RestApi/Types/Player/ItemInfoRequestBody.cs new file mode 100644 index 0000000000..0f217f360f --- /dev/null +++ b/Intersect.Server/Web/RestApi/Types/Player/ItemInfoRequestBody.cs @@ -0,0 +1,10 @@ +namespace Intersect.Server.Web.RestApi.Types.Player; + +public partial struct ItemInfoRequestBody +{ + public Guid ItemId { get; set; } + + public int Quantity { get; set; } + + public bool BankOverflow { get; set; } +} diff --git a/Intersect.Server/Web/RestApi/Types/Player/ItemListResponseBody.cs b/Intersect.Server/Web/RestApi/Types/Player/ItemListResponseBody.cs new file mode 100644 index 0000000000..a238b45ced --- /dev/null +++ b/Intersect.Server/Web/RestApi/Types/Player/ItemListResponseBody.cs @@ -0,0 +1,10 @@ +using Intersect.Server.Database.PlayerData.Players; + +namespace Intersect.Server.Web.RestApi.Types.Player; + +public readonly struct ItemListResponse(IEnumerable bankItems, IEnumerable inventoryItems) +{ + public IEnumerable Bank { get; init; } = bankItems; + + public IEnumerable Inventory { get; init; } = inventoryItems; +} \ No newline at end of file diff --git a/Intersect.Server/Web/RestApi/Types/Player/ItemsGiveQuantityData.cs b/Intersect.Server/Web/RestApi/Types/Player/ItemsGiveQuantityData.cs new file mode 100644 index 0000000000..5e3388a96a --- /dev/null +++ b/Intersect.Server/Web/RestApi/Types/Player/ItemsGiveQuantityData.cs @@ -0,0 +1,10 @@ +namespace Intersect.Server.Web.RestApi.Types.Player; + +public readonly struct ItemsGiveQuantityData(int total, int bank, int inventory) +{ + public int Total { get; init; } = total; + + public int Bank { get; init; } = bank; + + public int Inventory { get; init; } = inventory; +} \ No newline at end of file diff --git a/Intersect.Server/Web/RestApi/Types/Player/ItemsGiveResponseBody.cs b/Intersect.Server/Web/RestApi/Types/Player/ItemsGiveResponseBody.cs new file mode 100644 index 0000000000..94d7527eb7 --- /dev/null +++ b/Intersect.Server/Web/RestApi/Types/Player/ItemsGiveResponseBody.cs @@ -0,0 +1,8 @@ +namespace Intersect.Server.Web.RestApi.Types.Player; + +public readonly struct ItemsGiveResponseBody(Guid id, ItemsGiveQuantityData items) +{ + public Guid Id { get; init; } = id; + + public ItemsGiveQuantityData Quantity { get; init; } = items; +} diff --git a/Intersect.Server/Web/RestApi/Types/Player/ItemsTakeResponseBody.cs b/Intersect.Server/Web/RestApi/Types/Player/ItemsTakeResponseBody.cs new file mode 100644 index 0000000000..39b847968c --- /dev/null +++ b/Intersect.Server/Web/RestApi/Types/Player/ItemsTakeResponseBody.cs @@ -0,0 +1,8 @@ +namespace Intersect.Server.Web.RestApi.Types.Player; + +public readonly struct ItemsTakeResponseBody(Guid itemId, int quantity) +{ + public Guid ItemId { get; } = itemId; + + public int Quantity { get; } = quantity; +} diff --git a/Intersect.Server/Web/RestApi/Types/Player/LevelChangeRequestBody.cs b/Intersect.Server/Web/RestApi/Types/Player/LevelChangeRequestBody.cs new file mode 100644 index 0000000000..ad41fa3332 --- /dev/null +++ b/Intersect.Server/Web/RestApi/Types/Player/LevelChangeRequestBody.cs @@ -0,0 +1,3 @@ +namespace Intersect.Server.Web.RestApi.Types.Player; + +public record struct LevelChangeRequestBody(int Level); \ No newline at end of file diff --git a/Intersect.Server/Web/RestApi/Types/Player/OnlineCountResponseBody.cs b/Intersect.Server/Web/RestApi/Types/Player/OnlineCountResponseBody.cs new file mode 100644 index 0000000000..4ba136fb54 --- /dev/null +++ b/Intersect.Server/Web/RestApi/Types/Player/OnlineCountResponseBody.cs @@ -0,0 +1,3 @@ +namespace Intersect.Server.Web.RestApi.Types.Player; + +public record struct OnlineCountResponseBody(int OnlineCount); diff --git a/Intersect.Server/Web/RestApi/Payloads/QueryCollectionExtensions.cs b/Intersect.Server/Web/RestApi/Types/QueryCollectionExtensions.cs similarity index 93% rename from Intersect.Server/Web/RestApi/Payloads/QueryCollectionExtensions.cs rename to Intersect.Server/Web/RestApi/Types/QueryCollectionExtensions.cs index 625a04be1c..4ce5a3a7a7 100644 --- a/Intersect.Server/Web/RestApi/Payloads/QueryCollectionExtensions.cs +++ b/Intersect.Server/Web/RestApi/Types/QueryCollectionExtensions.cs @@ -1,6 +1,6 @@ using Microsoft.AspNetCore.Http; -namespace Intersect.Server.Web.RestApi.Payloads; +namespace Intersect.Server.Web.RestApi.Types; public static class QueryCollectionExtensions { diff --git a/Intersect.Server/Web/RestApi/Types/User/AdminChangeRequestBody.cs b/Intersect.Server/Web/RestApi/Types/User/AdminChangeRequestBody.cs new file mode 100644 index 0000000000..ba16e90dfa --- /dev/null +++ b/Intersect.Server/Web/RestApi/Types/User/AdminChangeRequestBody.cs @@ -0,0 +1,3 @@ +namespace Intersect.Server.Web.RestApi.Types.User; + +public record struct AdminChangeRequestBody(string New); diff --git a/Intersect.Server/Web/RestApi/Types/User/AuthorizedChangeRequestBody.cs b/Intersect.Server/Web/RestApi/Types/User/AuthorizedChangeRequestBody.cs new file mode 100644 index 0000000000..789a5548bb --- /dev/null +++ b/Intersect.Server/Web/RestApi/Types/User/AuthorizedChangeRequestBody.cs @@ -0,0 +1,3 @@ +namespace Intersect.Server.Web.RestApi.Types.User; + +public record struct AuthorizedChangeRequestBody(string Authorization, string New); diff --git a/Intersect.Server/Web/RestApi/Types/User/PasswordValidationRequestBody.cs b/Intersect.Server/Web/RestApi/Types/User/PasswordValidationRequestBody.cs new file mode 100644 index 0000000000..377797eef1 --- /dev/null +++ b/Intersect.Server/Web/RestApi/Types/User/PasswordValidationRequestBody.cs @@ -0,0 +1,3 @@ +namespace Intersect.Server.Web.RestApi.Types.User; + +public record struct PasswordValidationRequestBody(string Password); \ No newline at end of file diff --git a/Intersect.Server/Web/RestApi/Types/User/RegisterResponseBody.cs b/Intersect.Server/Web/RestApi/Types/User/RegisterResponseBody.cs new file mode 100644 index 0000000000..360153ed81 --- /dev/null +++ b/Intersect.Server/Web/RestApi/Types/User/RegisterResponseBody.cs @@ -0,0 +1,8 @@ +namespace Intersect.Server.Web.RestApi.Types.User; + +public struct RegisterResponseBody(string username, string email) +{ + public string Username { get; set; } = username; + + public string Email { get; set; } = email; +} diff --git a/Intersect.Server/Web/RestApi/Types/User/UserInfoRequestBody.cs b/Intersect.Server/Web/RestApi/Types/User/UserInfoRequestBody.cs new file mode 100644 index 0000000000..50f70c5463 --- /dev/null +++ b/Intersect.Server/Web/RestApi/Types/User/UserInfoRequestBody.cs @@ -0,0 +1,10 @@ +namespace Intersect.Server.Web.RestApi.Types.User; + +public partial struct UserInfoRequestBody +{ + public string Username { get; set; } + + public string Password { get; set; } + + public string Email { get; set; } +} diff --git a/Intersect.Server/Web/RestApi/Types/VariableValueBody.cs b/Intersect.Server/Web/RestApi/Types/VariableValueBody.cs new file mode 100644 index 0000000000..6ce10df16e --- /dev/null +++ b/Intersect.Server/Web/RestApi/Types/VariableValueBody.cs @@ -0,0 +1,6 @@ +namespace Intersect.Server.Web.RestApi.Types; + +public partial struct VariableValueBody +{ + public dynamic Value { get; set; } +}