Implement the dynamic crud endpoint
This commit is contained in:
parent
e78c0d8746
commit
8d072d48eb
@ -1,6 +1,7 @@
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Dynamic;
|
using System.Dynamic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using System.Text.Json;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using ChaosBot.Models;
|
using ChaosBot.Models;
|
||||||
using ChaosBot.WebServer.Services;
|
using ChaosBot.WebServer.Services;
|
||||||
@ -15,12 +16,15 @@ namespace ChaosBot.WebServer.App.ApiControllers
|
|||||||
public abstract class BaseApiController<T, TDeleteParameter> : Controller where T : class, new()
|
public abstract class BaseApiController<T, TDeleteParameter> : Controller where T : class, new()
|
||||||
{
|
{
|
||||||
protected readonly AccessTokenCache AccessTokenCache;
|
protected readonly AccessTokenCache AccessTokenCache;
|
||||||
|
protected readonly ValidationService ValidationService;
|
||||||
|
|
||||||
protected BaseApiController(
|
protected BaseApiController(
|
||||||
AccessTokenCache accessTokenCache
|
AccessTokenCache accessTokenCache,
|
||||||
|
ValidationService validationService
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
AccessTokenCache = accessTokenCache;
|
AccessTokenCache = accessTokenCache;
|
||||||
|
ValidationService = validationService;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<IActionResult> Index(ulong guildId)
|
public async Task<IActionResult> Index(ulong guildId)
|
||||||
@ -50,10 +54,13 @@ namespace ChaosBot.WebServer.App.ApiControllers
|
|||||||
return Content(JsonConvert.SerializeObject(response), new MediaTypeHeaderValue("application/json"));
|
return Content(JsonConvert.SerializeObject(response), new MediaTypeHeaderValue("application/json"));
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<IActionResult> Upsert(ulong guildId, dynamic requestBody)
|
public async Task<IActionResult> Upsert(ulong guildId, JsonElement requestBody)
|
||||||
{
|
{
|
||||||
if (!CheckPermissions.GetResult(AccessTokenCache, Request, guildId, out IActionResult result))
|
if (!CheckPermissions.GetResult(AccessTokenCache, Request, guildId, out IActionResult result))
|
||||||
return result;
|
return result;
|
||||||
|
|
||||||
|
if (!ValidationService.Validate(requestBody, GetValidationRules(), out string errors))
|
||||||
|
return BadRequest(errors);
|
||||||
|
|
||||||
await using ChaosbotContext dbContext = new ChaosbotContext();
|
await using ChaosbotContext dbContext = new ChaosbotContext();
|
||||||
|
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using System.Text.Json;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using ChaosBot.Models;
|
using ChaosBot.Models;
|
||||||
using ChaosBot.WebServer.Models;
|
using ChaosBot.WebServer.Models;
|
||||||
@ -15,9 +16,11 @@ namespace ChaosBot.WebServer.App.ApiControllers
|
|||||||
public class CustomCommandController : BaseApiController<CustomCommand, string>
|
public class CustomCommandController : BaseApiController<CustomCommand, string>
|
||||||
{
|
{
|
||||||
public CustomCommandController(
|
public CustomCommandController(
|
||||||
AccessTokenCache accessTokenCache
|
AccessTokenCache accessTokenCache,
|
||||||
|
ValidationService validationService
|
||||||
) : base(
|
) : base(
|
||||||
accessTokenCache
|
accessTokenCache,
|
||||||
|
validationService
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
[HttpGet]
|
[HttpGet]
|
||||||
@ -31,7 +34,7 @@ namespace ChaosBot.WebServer.App.ApiControllers
|
|||||||
[Route("{guildId}")]
|
[Route("{guildId}")]
|
||||||
public async Task<IActionResult> UpsertAction(
|
public async Task<IActionResult> UpsertAction(
|
||||||
[FromRoute] ulong guildId,
|
[FromRoute] ulong guildId,
|
||||||
[FromBody] dynamic requestBody)
|
[FromBody] JsonElement requestBody)
|
||||||
{
|
{
|
||||||
return await Upsert(guildId, requestBody);
|
return await Upsert(guildId, requestBody);
|
||||||
}
|
}
|
||||||
@ -64,7 +67,12 @@ namespace ChaosBot.WebServer.App.ApiControllers
|
|||||||
|
|
||||||
protected override Dictionary<string, List<string>> GetValidationRules()
|
protected override Dictionary<string, List<string>> GetValidationRules()
|
||||||
{
|
{
|
||||||
return new Dictionary<string, List<string>>();
|
return new Dictionary<string, List<string>>
|
||||||
|
{
|
||||||
|
{"Command", new List<string>{"required", "type:string", "min:1", "max:128"}},
|
||||||
|
{"Type", new List<string>{"required", "type:integer", "in:CustomCommandType"}},
|
||||||
|
{"Content", new List<string>{"required", "type:string"}},
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override UpsertCommandBuilder<CustomCommand> ApplyFilterForUpsert(
|
protected override UpsertCommandBuilder<CustomCommand> ApplyFilterForUpsert(
|
||||||
|
|||||||
198
ChaosBot/WebServer/Services/ValidationService.cs
Normal file
198
ChaosBot/WebServer/Services/ValidationService.cs
Normal file
@ -0,0 +1,198 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Text.Json;
|
||||||
|
|
||||||
|
namespace ChaosBot.WebServer.Services
|
||||||
|
{
|
||||||
|
public class ValidationService
|
||||||
|
{
|
||||||
|
public bool Validate(JsonElement requestBody, Dictionary<string,List<string>> getValidationRules, out string error)
|
||||||
|
{
|
||||||
|
StringBuilder errorBuilder = new StringBuilder();
|
||||||
|
|
||||||
|
foreach (KeyValuePair<string,List<string>> validationRule in getValidationRules)
|
||||||
|
{
|
||||||
|
string key = validationRule.Key;
|
||||||
|
dynamic value = GetValueFromRequest(requestBody, key);
|
||||||
|
|
||||||
|
List<string> rules = validationRule.Value;
|
||||||
|
|
||||||
|
foreach (string rule in rules)
|
||||||
|
{
|
||||||
|
string[] ruleParts = rule.Split(':');
|
||||||
|
string ruleType = ruleParts.First();
|
||||||
|
|
||||||
|
if (ruleType == null)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
ValidationResult result = ruleType switch
|
||||||
|
{
|
||||||
|
"required" => CheckRequired(key, value),
|
||||||
|
"type" => CheckType(key, value, ruleParts),
|
||||||
|
"min" => CheckMin(key, value, ruleParts),
|
||||||
|
"max" => CheckMax(key, value, ruleParts),
|
||||||
|
_ => new ValidationResult.Unknown()
|
||||||
|
};
|
||||||
|
|
||||||
|
if (result.GetError() != null)
|
||||||
|
{
|
||||||
|
errorBuilder.AppendLine($"[{result.GetKey()}] {result.GetError()}");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
error = errorBuilder.ToString();
|
||||||
|
return error.Length == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static dynamic GetValueFromRequest(JsonElement requestBody, string key)
|
||||||
|
{
|
||||||
|
JsonElement prop;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
prop = requestBody.GetProperty(key);
|
||||||
|
}
|
||||||
|
catch (KeyNotFoundException)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return prop.ValueKind switch
|
||||||
|
{
|
||||||
|
JsonValueKind.String => prop.GetString(),
|
||||||
|
JsonValueKind.Number => prop.GetInt64(),
|
||||||
|
JsonValueKind.True => prop.GetBoolean(),
|
||||||
|
JsonValueKind.False => prop.GetBoolean(),
|
||||||
|
_ => null
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private ValidationResult CheckRequired(string key, dynamic value)
|
||||||
|
{
|
||||||
|
if (value != null)
|
||||||
|
return new ValidationResult.Success();
|
||||||
|
return new ValidationResult.Failure(key, $"{key} is required");
|
||||||
|
}
|
||||||
|
|
||||||
|
private ValidationResult CheckType(string key, dynamic value, string[] ruleParts)
|
||||||
|
{
|
||||||
|
if (ruleParts.Length != 2) return new ValidationResult.Unknown();
|
||||||
|
|
||||||
|
Type type = ruleParts[1] switch
|
||||||
|
{
|
||||||
|
"string" => typeof(string),
|
||||||
|
"boolean" => typeof(bool),
|
||||||
|
"short" => typeof(short),
|
||||||
|
"ushort" => typeof(ushort),
|
||||||
|
"int" => typeof(int),
|
||||||
|
"uint" => typeof(uint),
|
||||||
|
"long" => typeof(long),
|
||||||
|
"ulong" => typeof(ulong),
|
||||||
|
_ => null
|
||||||
|
};
|
||||||
|
|
||||||
|
if (type == null)
|
||||||
|
return new ValidationResult.Unknown();
|
||||||
|
|
||||||
|
dynamic val = Convert.ChangeType(value, type);
|
||||||
|
if (val != null)
|
||||||
|
return new ValidationResult.Success();
|
||||||
|
return new ValidationResult.Failure(key, $"{key} could not be interpreted as {ruleParts[1]}");
|
||||||
|
}
|
||||||
|
|
||||||
|
private ValidationResult CheckMin(string key, dynamic value, string[] ruleParts)
|
||||||
|
{
|
||||||
|
if (ruleParts.Length != 2) return new ValidationResult.Unknown();
|
||||||
|
|
||||||
|
if (value is string stringValue)
|
||||||
|
{
|
||||||
|
int minLength = Convert.ToInt32(ruleParts[1]);
|
||||||
|
if (stringValue.Length < minLength)
|
||||||
|
return new ValidationResult.Failure(key, $"{key} cannot be shorter than {minLength} characters");
|
||||||
|
} else if (value is ulong intValue)
|
||||||
|
{
|
||||||
|
ulong minSize = Convert.ToUInt64(ruleParts[1]);
|
||||||
|
if (intValue < minSize)
|
||||||
|
return new ValidationResult.Failure(key, $"{key} must be greater than or equal to {minSize}");
|
||||||
|
} else if (value is double floatValue)
|
||||||
|
{
|
||||||
|
double minSize = Convert.ToDouble(ruleParts[1]);
|
||||||
|
if (floatValue < minSize)
|
||||||
|
return new ValidationResult.Failure(key, $"{key} must be greater than or equal to {minSize}");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return new ValidationResult.Unknown();
|
||||||
|
}
|
||||||
|
|
||||||
|
return new ValidationResult.Success();
|
||||||
|
}
|
||||||
|
|
||||||
|
private ValidationResult CheckMax(string key, dynamic value, string[] ruleParts)
|
||||||
|
{
|
||||||
|
if (ruleParts.Length != 2) return new ValidationResult.Unknown();
|
||||||
|
|
||||||
|
if (value is string stringValue)
|
||||||
|
{
|
||||||
|
int maxLength = Convert.ToInt32(ruleParts[1]);
|
||||||
|
if (stringValue.Length > maxLength)
|
||||||
|
return new ValidationResult.Failure(key, $"{key} cannot be longer than {maxLength} characters");
|
||||||
|
} else if (value is ulong intValue)
|
||||||
|
{
|
||||||
|
ulong maxSize = Convert.ToUInt64(ruleParts[1]);
|
||||||
|
if (intValue > maxSize)
|
||||||
|
return new ValidationResult.Failure(key, $"{key} must be less than or equal to {maxSize}");
|
||||||
|
} else if (value is double floatValue)
|
||||||
|
{
|
||||||
|
double maxSize = Convert.ToDouble(ruleParts[1]);
|
||||||
|
if (floatValue > maxSize)
|
||||||
|
return new ValidationResult.Failure(key, $"{key} must be less than or equal to {maxSize}");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return new ValidationResult.Unknown();
|
||||||
|
}
|
||||||
|
|
||||||
|
return new ValidationResult.Success();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal class ValidationResult
|
||||||
|
{
|
||||||
|
private readonly string _errorMessage;
|
||||||
|
private readonly string _key;
|
||||||
|
|
||||||
|
private ValidationResult()
|
||||||
|
{
|
||||||
|
_errorMessage = null;
|
||||||
|
_key = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private ValidationResult(string key, string errorMessage)
|
||||||
|
{
|
||||||
|
_errorMessage = errorMessage;
|
||||||
|
_key = key;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal class Success : ValidationResult {}
|
||||||
|
|
||||||
|
internal class Failure : ValidationResult
|
||||||
|
{
|
||||||
|
public Failure(string key, string message) : base(key, message) {}
|
||||||
|
}
|
||||||
|
internal class Unknown : ValidationResult {}
|
||||||
|
|
||||||
|
public string GetError()
|
||||||
|
{
|
||||||
|
return this._errorMessage;
|
||||||
|
}
|
||||||
|
|
||||||
|
public string GetKey()
|
||||||
|
{
|
||||||
|
return this._key;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -33,6 +33,7 @@ namespace ChaosBot.WebServer
|
|||||||
.AddSingleton(sp => new AccessTokenCache())
|
.AddSingleton(sp => new AccessTokenCache())
|
||||||
.AddSingleton(sp => new DiscordInviteGenerator())
|
.AddSingleton(sp => new DiscordInviteGenerator())
|
||||||
.AddSingleton(sp => new HttpClient())
|
.AddSingleton(sp => new HttpClient())
|
||||||
|
.AddSingleton(sp => new ValidationService())
|
||||||
;
|
;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1 +1 @@
|
|||||||
Subproject commit 07d2c3dc0e7ba5bfaa8eac94dad814f1a333a6eb
|
Subproject commit 56e8caf708d791a2b05b12e82fd407905147fafd
|
||||||
Loading…
Reference in New Issue
Block a user