diff --git a/src/GrillBot.App/Actions/ActionsExtensions.cs b/src/GrillBot.App/Actions/ActionsExtensions.cs index 82b2b966b..674bc8876 100644 --- a/src/GrillBot.App/Actions/ActionsExtensions.cs +++ b/src/GrillBot.App/Actions/ActionsExtensions.cs @@ -1,4 +1,8 @@ -using Microsoft.Extensions.DependencyInjection; +using GrillBot.Core.Services.AuditLog; +using GrillBot.Core.Services.PointsService; +using GrillBot.Core.Services.RubbergodService; +using GrillBot.Database.Migrations; +using Microsoft.Extensions.DependencyInjection; namespace GrillBot.App.Actions; @@ -7,16 +11,21 @@ public static class ActionsExtensions public static IServiceCollection AddActions(this IServiceCollection services) { return services + .AddServiceBridge() .AddApiActions() .AddCommandsActions(); } - private static IServiceCollection AddApiActions(this IServiceCollection services) + private static IServiceCollection AddServiceBridge(this IServiceCollection services) { - // Common - services - .AddScoped(); + return services + .AddScoped>() + .AddScoped>() + .AddScoped>(); + } + private static IServiceCollection AddApiActions(this IServiceCollection services) + { // V1 // AuditLog services @@ -52,7 +61,7 @@ private static IServiceCollection AddApiActions(this IServiceCollection services .AddScoped() .AddScoped() .AddScoped(); - + // Dashboard services .AddScoped() @@ -154,7 +163,8 @@ private static IServiceCollection AddApiActions(this IServiceCollection services .AddScoped() .AddScoped() .AddScoped() - .AddScoped(); + .AddScoped() + .AddScoped(); // V2 services diff --git a/src/GrillBot.App/Actions/Api/ApiBridgeAction.cs b/src/GrillBot.App/Actions/Api/ApiBridgeAction.cs deleted file mode 100644 index 16359a0af..000000000 --- a/src/GrillBot.App/Actions/Api/ApiBridgeAction.cs +++ /dev/null @@ -1,21 +0,0 @@ -using GrillBot.Common.Models; -using GrillBot.Core.Services.Common; -using Microsoft.Extensions.DependencyInjection; - -namespace GrillBot.App.Actions.Api; - -public class ApiBridgeAction : ApiAction -{ - private IServiceProvider ServiceProvider { get; } - - public ApiBridgeAction(ApiRequestContext apiContext, IServiceProvider serviceProvider) : base(apiContext) - { - ServiceProvider = serviceProvider; - } - - public async Task ExecuteAsync(Func> asyncExecutor) where TServiceClient : IClient - => await asyncExecutor(ServiceProvider.GetRequiredService()); - - public async Task ExecuteAsync(Func asyncExecutor) where TServiceClient : IClient - => await asyncExecutor(ServiceProvider.GetRequiredService()); -} diff --git a/src/GrillBot.App/Actions/Api/ServiceBridgeAction.cs b/src/GrillBot.App/Actions/Api/ServiceBridgeAction.cs new file mode 100644 index 000000000..57221c2d3 --- /dev/null +++ b/src/GrillBot.App/Actions/Api/ServiceBridgeAction.cs @@ -0,0 +1,32 @@ +using GrillBot.Common.Models; +using GrillBot.Core.Infrastructure.Actions; +using GrillBot.Core.Services.Common; + +namespace GrillBot.App.Actions.Api; + +public class ServiceBridgeAction : ApiAction where TServiceClient : IClient +{ + private TServiceClient Client { get; } + + public ServiceBridgeAction(ApiRequestContext apiContext, TServiceClient client) : base(apiContext) + { + Client = client; + } + + public override async Task ProcessAsync() + { + var funcExecutor = Parameters.OfType>>().FirstOrDefault(); + var actionExecutor = Parameters.OfType>().FirstOrDefault(); + + if (funcExecutor is not null) + return ApiResult.Ok(await funcExecutor(Client)); + + if (actionExecutor is not null) + { + await actionExecutor(Client); + return ApiResult.Ok(); + } + + return ApiResult.BadRequest(); + } +} diff --git a/src/GrillBot.App/Actions/Api/V1/AuditLog/CreateLogItem.cs b/src/GrillBot.App/Actions/Api/V1/AuditLog/CreateLogItem.cs index 13e93dab6..0a53808a4 100644 --- a/src/GrillBot.App/Actions/Api/V1/AuditLog/CreateLogItem.cs +++ b/src/GrillBot.App/Actions/Api/V1/AuditLog/CreateLogItem.cs @@ -1,5 +1,7 @@ -using GrillBot.Common.Managers.Localization; +using GrillBot.Common.Extensions; +using GrillBot.Common.Managers.Localization; using GrillBot.Common.Models; +using GrillBot.Core.Infrastructure.Actions; using GrillBot.Core.Services.AuditLog; using GrillBot.Core.Services.AuditLog.Enums; using GrillBot.Core.Services.AuditLog.Models.Request.CreateItems; @@ -18,8 +20,9 @@ public CreateLogItem(ApiRequestContext apiContext, ITextsManager texts, IAuditLo AuditLogServiceClient = auditLogServiceClient; } - public async Task ProcessAsync(ClientLogItemRequest request) + public override async Task ProcessAsync() { + var request = (ClientLogItemRequest)Parameters[0]!; ValidateParameters(request); var logRequest = new LogRequest @@ -50,6 +53,7 @@ public async Task ProcessAsync(ClientLogItemRequest request) } await AuditLogServiceClient.CreateItemsAsync(new List { logRequest }); + return ApiResult.Ok(); } private void ValidateParameters(ClientLogItemRequest request) @@ -57,13 +61,13 @@ private void ValidateParameters(ClientLogItemRequest request) var flags = new[] { request.IsInfo, request.IsWarning, request.IsError }; var names = new[] { nameof(request.IsInfo), nameof(request.IsWarning), nameof(request.IsError) }; - ValidationResult? result = null; + string? errorMessage = null; if (!Array.Exists(flags, o => o)) - result = new ValidationResult(Texts["AuditLog/CreateLogItem/Required", ApiContext.Language], names); + errorMessage = Texts["AuditLog/CreateLogItem/Required", ApiContext.Language]; else if (flags.Count(o => o) > 1) - result = new ValidationResult(Texts["AuditLog/CreateLogItem/MultipleTypes", ApiContext.Language], names); + errorMessage = Texts["AuditLog/CreateLogItem/MultipleTypes", ApiContext.Language]; - if (result is not null) - throw new ValidationException(result, null, request); + if (!string.IsNullOrEmpty(errorMessage)) + throw new ValidationException(errorMessage).ToBadRequestValidation(request, names); } } diff --git a/src/GrillBot.App/Actions/Api/V1/AuditLog/GetAuditLogDetail.cs b/src/GrillBot.App/Actions/Api/V1/AuditLog/GetAuditLogDetail.cs index cfaef22de..cfd733759 100644 --- a/src/GrillBot.App/Actions/Api/V1/AuditLog/GetAuditLogDetail.cs +++ b/src/GrillBot.App/Actions/Api/V1/AuditLog/GetAuditLogDetail.cs @@ -3,6 +3,7 @@ using GrillBot.Common.Extensions.Discord; using GrillBot.Common.Models; using GrillBot.Core.Extensions; +using GrillBot.Core.Infrastructure.Actions; using GrillBot.Core.Services.AuditLog; using GrillBot.Core.Services.AuditLog.Enums; using GrillBot.Core.Services.AuditLog.Models.Response.Detail; @@ -25,11 +26,12 @@ public GetAuditLogDetail(ApiRequestContext apiContext, IAuditLogServiceClient au Mapper = mapper; } - public async Task ProcessAsync(Guid id) + public override async Task ProcessAsync() { + var id = (Guid)Parameters[0]!; var detail = await AuditLogServiceClient.DetailAsync(id); if (detail?.Data is not JsonElement jsonElement) - return null; + return ApiResult.NotFound(); var options = new JsonSerializerOptions { @@ -48,53 +50,53 @@ public GetAuditLogDetail(ApiRequestContext apiContext, IAuditLogServiceClient au detail.Data = jsonElement.Deserialize(options); break; case LogType.OverwriteUpdated: - { - var overwriteUpdated = jsonElement.Deserialize(options)!; - var role = overwriteUpdated.TargetType == PermissionTarget.Role ? await DiscordClient.FindRoleAsync(overwriteUpdated.TargetId.ToUlong()) : null; - var user = overwriteUpdated.TargetType == PermissionTarget.User ? await repository.User.FindUserByIdAsync(overwriteUpdated.TargetId.ToUlong()) : null; - - detail.Data = new Data.Models.API.AuditLog.Detail.OverwriteUpdatedDetail { - User = Mapper.Map(user), - Role = Mapper.Map(role), - Allow = overwriteUpdated.Allow, - Deny = overwriteUpdated.Deny - }; - break; - } - case LogType.MemberUpdated: - { - var memberUpdated = jsonElement.Deserialize(options)!; - var user = await repository.User.FindUserByIdAsync(memberUpdated.UserId.ToUlong()); + var overwriteUpdated = jsonElement.Deserialize(options)!; + var role = overwriteUpdated.TargetType == PermissionTarget.Role ? await DiscordClient.FindRoleAsync(overwriteUpdated.TargetId.ToUlong()) : null; + var user = overwriteUpdated.TargetType == PermissionTarget.User ? await repository.User.FindUserByIdAsync(overwriteUpdated.TargetId.ToUlong()) : null; - detail.Data = new Data.Models.API.AuditLog.Detail.MemberUpdatedDetail + detail.Data = new Data.Models.API.AuditLog.Detail.OverwriteUpdatedDetail + { + User = Mapper.Map(user), + Role = Mapper.Map(role), + Allow = overwriteUpdated.Allow, + Deny = overwriteUpdated.Deny + }; + break; + } + case LogType.MemberUpdated: { - User = Mapper.Map(user), - Flags = memberUpdated.Flags, - Nickname = memberUpdated.Nickname, - IsDeaf = memberUpdated.IsDeaf, - IsMuted = memberUpdated.IsMuted, - SelfUnverifyMinimalTime = memberUpdated.SelfUnverifyMinimalTime - }; - break; - } + var memberUpdated = jsonElement.Deserialize(options)!; + var user = await repository.User.FindUserByIdAsync(memberUpdated.UserId.ToUlong()); + + detail.Data = new Data.Models.API.AuditLog.Detail.MemberUpdatedDetail + { + User = Mapper.Map(user), + Flags = memberUpdated.Flags, + Nickname = memberUpdated.Nickname, + IsDeaf = memberUpdated.IsDeaf, + IsMuted = memberUpdated.IsMuted, + SelfUnverifyMinimalTime = memberUpdated.SelfUnverifyMinimalTime + }; + break; + } case LogType.GuildUpdated: detail.Data = jsonElement.Deserialize(options); break; case LogType.MessageDeleted: - { - var messageDeleted = jsonElement.Deserialize(options)!; - var author = await repository.User.FindUserByIdAsync(messageDeleted.AuthorId.ToUlong()); - - detail.Data = new Data.Models.API.AuditLog.Detail.MessageDeletedDetail { - Author = Mapper.Map(author), - Content = messageDeleted.Content, - Embeds = messageDeleted.Embeds, - MessageCreatedAt = messageDeleted.MessageCreatedAt.ToLocalTime() - }; - break; - } + var messageDeleted = jsonElement.Deserialize(options)!; + var author = await repository.User.FindUserByIdAsync(messageDeleted.AuthorId.ToUlong()); + + detail.Data = new Data.Models.API.AuditLog.Detail.MessageDeletedDetail + { + Author = Mapper.Map(author), + Content = messageDeleted.Content, + Embeds = messageDeleted.Embeds, + MessageCreatedAt = messageDeleted.MessageCreatedAt.ToLocalTime() + }; + break; + } case LogType.InteractionCommand: detail.Data = jsonElement.Deserialize(options); break; @@ -102,21 +104,21 @@ public GetAuditLogDetail(ApiRequestContext apiContext, IAuditLogServiceClient au detail.Data = jsonElement.Deserialize(options); break; case LogType.JobCompleted: - { - var jobCompleted = jsonElement.Deserialize(options)!; - var startUser = string.IsNullOrEmpty(jobCompleted.StartUserId) ? null : await repository.User.FindUserByIdAsync(jobCompleted.StartUserId.ToUlong()); - - detail.Data = new Data.Models.API.AuditLog.Detail.JobExecutionDetail { - Result = jobCompleted.Result, - EndAt = jobCompleted.EndAt.ToLocalTime(), - JobName = jobCompleted.JobName, - StartAt = jobCompleted.StartAt.ToLocalTime(), - StartUser = startUser is null ? null : Mapper.Map(startUser), - WasError = jobCompleted.WasError - }; - break; - } + var jobCompleted = jsonElement.Deserialize(options)!; + var startUser = string.IsNullOrEmpty(jobCompleted.StartUserId) ? null : await repository.User.FindUserByIdAsync(jobCompleted.StartUserId.ToUlong()); + + detail.Data = new Data.Models.API.AuditLog.Detail.JobExecutionDetail + { + Result = jobCompleted.Result, + EndAt = jobCompleted.EndAt.ToLocalTime(), + JobName = jobCompleted.JobName, + StartAt = jobCompleted.StartAt.ToLocalTime(), + StartUser = startUser is null ? null : Mapper.Map(startUser), + WasError = jobCompleted.WasError + }; + break; + } case LogType.Api: detail.Data = jsonElement.Deserialize(options); break; @@ -128,6 +130,6 @@ public GetAuditLogDetail(ApiRequestContext apiContext, IAuditLogServiceClient au break; } - return detail; + return ApiResult.Ok(detail); } } diff --git a/src/GrillBot.App/Actions/Api/V1/AuditLog/GetAuditLogList.cs b/src/GrillBot.App/Actions/Api/V1/AuditLog/GetAuditLogList.cs index 2aa02c4c2..753e9a6bc 100644 --- a/src/GrillBot.App/Actions/Api/V1/AuditLog/GetAuditLogList.cs +++ b/src/GrillBot.App/Actions/Api/V1/AuditLog/GetAuditLogList.cs @@ -5,6 +5,7 @@ using GrillBot.Common.FileStorage; using GrillBot.Common.Models; using GrillBot.Core.Extensions; +using GrillBot.Core.Infrastructure.Actions; using GrillBot.Core.Models.Pagination; using GrillBot.Core.Services.AuditLog; using GrillBot.Core.Services.AuditLog.Enums; @@ -43,9 +44,12 @@ public GetAuditLogList(ApiRequestContext apiContext, GrillBotDatabaseBuilder dat BlobManagerFactoryHelper = blobManagerFactoryHelper; } - public async Task> ProcessAsync(SearchRequest request) + public override async Task ProcessAsync() { - FixDateTimes(request); + var request = (SearchRequest)Parameters[0]!; + + request.CreatedFrom = FixDateTime(request.CreatedFrom); + request.CreatedTo = FixDateTime(request.CreatedTo); var response = await AuditLogServiceClient.SearchItemsAsync(request); if (response.ValidationErrors is not null) @@ -58,13 +62,12 @@ public async Task> ProcessAsync(SearchRequest req } await using var repository = DatabaseBuilder.CreateRepository(); - return await PaginatedResponse.CopyAndMapAsync(response.Response!, async entity => await MapListItemAsync(repository, entity)); - } + var result = await PaginatedResponse.CopyAndMapAsync( + response.Response!, + async entity => await MapListItemAsync(repository, entity) + ); - private static void FixDateTimes(SearchRequest request) - { - request.CreatedFrom = FixDateTime(request.CreatedFrom); - request.CreatedTo = FixDateTime(request.CreatedTo); + return ApiResult.Ok(result); } private static DateTime? FixDateTime(DateTime? dateTime) diff --git a/src/GrillBot.App/Actions/Api/V1/AuditLog/RemoveItem.cs b/src/GrillBot.App/Actions/Api/V1/AuditLog/RemoveItem.cs index 4bbed5485..8464d5b1c 100644 --- a/src/GrillBot.App/Actions/Api/V1/AuditLog/RemoveItem.cs +++ b/src/GrillBot.App/Actions/Api/V1/AuditLog/RemoveItem.cs @@ -3,6 +3,7 @@ using GrillBot.Common.Managers.Localization; using GrillBot.Common.Models; using GrillBot.Core.Exceptions; +using GrillBot.Core.Infrastructure.Actions; using GrillBot.Core.Services.AuditLog; namespace GrillBot.App.Actions.Api.V1.AuditLog; @@ -20,14 +21,15 @@ public RemoveItem(ApiRequestContext apiContext, ITextsManager texts, IAuditLogSe BlobManagerFactoryHelper = blobManagerFactoryHelper; } - public async Task ProcessAsync(Guid id) + public override async Task ProcessAsync() { + var id = (Guid)Parameters[0]!; var response = await AuditLogServiceClient.DeleteItemAsync(id); if (!response.Exists) throw new NotFoundException(Texts["AuditLog/RemoveItem/NotFound", ApiContext.Language]); if (response.FilesToDelete.Count == 0) - return; + return ApiResult.Ok(); var manager = await BlobManagerFactoryHelper.CreateAsync(BlobConstants.AuditLogDeletedAttachments); var legacyManager = await BlobManagerFactoryHelper.CreateLegacyAsync(); @@ -37,5 +39,7 @@ public async Task ProcessAsync(Guid id) await manager.DeleteAsync(filename); await legacyManager.DeleteAsync(filename); } + + return ApiResult.Ok(); } } diff --git a/src/GrillBot.App/Actions/Api/V1/Auth/CreateToken.cs b/src/GrillBot.App/Actions/Api/V1/Auth/CreateToken.cs index 707b6bec3..386937d7a 100644 --- a/src/GrillBot.App/Actions/Api/V1/Auth/CreateToken.cs +++ b/src/GrillBot.App/Actions/Api/V1/Auth/CreateToken.cs @@ -5,6 +5,7 @@ using GrillBot.Common.Extensions.Discord; using GrillBot.Common.Managers.Localization; using GrillBot.Common.Models; +using GrillBot.Core.Infrastructure.Actions; using GrillBot.Data.Models.API.OAuth2; using GrillBot.Database.Enums; using Microsoft.IdentityModel.Tokens; @@ -29,13 +30,26 @@ public CreateToken(ApiRequestContext apiContext, IHttpClientFactory httpClientFa Configuration = configuration.GetRequiredSection("Auth:OAuth2"); } - public async Task ProcessAsync(string sessionId, bool isPublic) + public override async Task ProcessAsync() { - var userId = await GetUserIdAsync(sessionId); - return await ProcessAsync(userId, isPublic); + var isPublic = (bool)Parameters[1]!; + + if (Parameters[0] is string sessionId) + { + var userId = await GetUserIdAsync(sessionId); + var token = await ProcessAsync(userId, isPublic); + return ApiResult.Ok(token); + } + else if (Parameters[0] is ulong userId) + { + var token = await ProcessAsync(userId, isPublic); + return ApiResult.Ok(token); + } + + return ApiResult.BadRequest(); } - public async Task ProcessAsync(ulong? userId, bool isPublic) + private async Task ProcessAsync(ulong? userId, bool isPublic) { var user = await FindUserAsync(userId); diff --git a/src/GrillBot.App/Actions/Api/V1/Auth/GetRedirectLink.cs b/src/GrillBot.App/Actions/Api/V1/Auth/GetRedirectLink.cs index 69265c479..c6b479f5f 100644 --- a/src/GrillBot.App/Actions/Api/V1/Auth/GetRedirectLink.cs +++ b/src/GrillBot.App/Actions/Api/V1/Auth/GetRedirectLink.cs @@ -1,4 +1,5 @@ using GrillBot.Common.Models; +using GrillBot.Core.Infrastructure.Actions; using GrillBot.Data.Models.API; using GrillBot.Data.Models.API.OAuth2; @@ -13,8 +14,9 @@ public GetRedirectLink(ApiRequestContext apiContext, IConfiguration configuratio Configuration = configuration.GetRequiredSection("Auth:OAuth2"); } - public OAuth2GetLink Process(AuthState state) + public override Task ProcessAsync() { + var state = (AuthState)Parameters[0]!; var builder = new UriBuilder("https://discord.com/api/oauth2/authorize") { Query = string.Join( @@ -27,6 +29,6 @@ public OAuth2GetLink Process(AuthState state) ) }; - return new OAuth2GetLink(builder.ToString()); + return Task.FromResult(ApiResult.Ok(new OAuth2GetLink(builder.ToString()))); } } diff --git a/src/GrillBot.App/Actions/Api/V1/Auth/ProcessCallback.cs b/src/GrillBot.App/Actions/Api/V1/Auth/ProcessCallback.cs index 6a61dfb20..eda9b1dfc 100644 --- a/src/GrillBot.App/Actions/Api/V1/Auth/ProcessCallback.cs +++ b/src/GrillBot.App/Actions/Api/V1/Auth/ProcessCallback.cs @@ -1,6 +1,9 @@ using System.Net.Http; using GrillBot.Common.Models; +using GrillBot.Core.Infrastructure.Actions; using GrillBot.Data.Models.API; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; namespace GrillBot.App.Actions.Api.V1.Auth; @@ -15,8 +18,11 @@ public ProcessCallback(ApiRequestContext apiContext, IConfiguration configuratio HttpClient = httpClientFactory.CreateClient(); } - public async Task ProcessAsync(string code, string encodedState) + public override async Task ProcessAsync() { + var code = (string)Parameters[0]!; + var encodedState = (string)Parameters[1]!; + var state = AuthState.Decode(encodedState); var accessToken = await RetrieveAccessTokenAsync(code); var returnUrl = GetReturnUrl(state); @@ -25,7 +31,7 @@ public async Task ProcessAsync(string code, string encodedState) if (!string.IsNullOrEmpty(accessToken)) uriBuilder.Query = string.Join("&", $"sessionId={accessToken}", $"isPublic={state.IsPublic}"); - return uriBuilder.ToString(); + return new ApiResult(StatusCodes.Status302Found, new RedirectResult(uriBuilder.ToString())); } private async Task RetrieveAccessTokenAsync(string code) @@ -63,5 +69,10 @@ public async Task ProcessAsync(string code, string encodedState) } private string GetReturnUrl(AuthState state) - => !string.IsNullOrEmpty(state.ReturnUrl) ? state.ReturnUrl : Configuration[state.IsPublic ? "ClientRedirectUrl" : "AdminRedirectUrl"]!; + { + if (!string.IsNullOrEmpty(state.ReturnUrl)) + return state.ReturnUrl; + else + return Configuration[state.IsPublic ? "ClientRedirectUrl" : "AdminRedirectUrl"]!; + } } diff --git a/src/GrillBot.App/Actions/Api/V1/AutoReply/CreateAutoReplyItem.cs b/src/GrillBot.App/Actions/Api/V1/AutoReply/CreateAutoReplyItem.cs index 4829bf55a..92032270e 100644 --- a/src/GrillBot.App/Actions/Api/V1/AutoReply/CreateAutoReplyItem.cs +++ b/src/GrillBot.App/Actions/Api/V1/AutoReply/CreateAutoReplyItem.cs @@ -1,6 +1,7 @@ using AutoMapper; using GrillBot.App.Managers; using GrillBot.Common.Models; +using GrillBot.Core.Infrastructure.Actions; using GrillBot.Data.Models.API.AutoReply; namespace GrillBot.App.Actions.Api.V1.AutoReply; @@ -18,8 +19,9 @@ public CreateAutoReplyItem(ApiRequestContext apiContext, AutoReplyManager autoRe Mapper = mapper; } - public async Task ProcessAsync(AutoReplyItemParams parameters) + public override async Task ProcessAsync() { + var parameters = (AutoReplyItemParams)Parameters[0]!; var entity = new Database.Entity.AutoReplyItem { Flags = parameters.Flags, @@ -33,6 +35,7 @@ public async Task ProcessAsync(AutoReplyItemParams parameters) await repository.CommitAsync(); await AutoReplyManager.InitAsync(); - return Mapper.Map(entity); + var result = Mapper.Map(entity); + return ApiResult.Ok(result); } } diff --git a/src/GrillBot.App/Actions/Api/V1/AutoReply/GetAutoReplyItem.cs b/src/GrillBot.App/Actions/Api/V1/AutoReply/GetAutoReplyItem.cs index 7f01f377f..bb649bd9f 100644 --- a/src/GrillBot.App/Actions/Api/V1/AutoReply/GetAutoReplyItem.cs +++ b/src/GrillBot.App/Actions/Api/V1/AutoReply/GetAutoReplyItem.cs @@ -2,6 +2,7 @@ using GrillBot.Common.Managers.Localization; using GrillBot.Common.Models; using GrillBot.Core.Exceptions; +using GrillBot.Core.Infrastructure.Actions; using GrillBot.Data.Models.API.AutoReply; namespace GrillBot.App.Actions.Api.V1.AutoReply; @@ -19,14 +20,16 @@ public GetAutoReplyItem(ApiRequestContext apiContext, GrillBotDatabaseBuilder da Texts = texts; } - public async Task ProcessAsync(long id) + public override async Task ProcessAsync() { + var id = (long)Parameters[0]!; + await using var repository = DatabaseBuilder.CreateRepository(); - var entity = await repository.AutoReply.FindReplyByIdAsync(id); - if (entity == null) - throw new NotFoundException(Texts["AutoReply/NotFound", ApiContext.Language].FormatWith(id)); + var entity = await repository.AutoReply.FindReplyByIdAsync(id) + ?? throw new NotFoundException(Texts["AutoReply/NotFound", ApiContext.Language].FormatWith(id)); - return Mapper.Map(entity); + var result = Mapper.Map(entity); + return ApiResult.Ok(result); } } diff --git a/src/GrillBot.App/Actions/Api/V1/AutoReply/GetAutoReplyList.cs b/src/GrillBot.App/Actions/Api/V1/AutoReply/GetAutoReplyList.cs index 30929a625..6b7a8f311 100644 --- a/src/GrillBot.App/Actions/Api/V1/AutoReply/GetAutoReplyList.cs +++ b/src/GrillBot.App/Actions/Api/V1/AutoReply/GetAutoReplyList.cs @@ -1,5 +1,6 @@ using AutoMapper; using GrillBot.Common.Models; +using GrillBot.Core.Infrastructure.Actions; using GrillBot.Data.Models.API.AutoReply; namespace GrillBot.App.Actions.Api.V1.AutoReply; @@ -15,11 +16,13 @@ public GetAutoReplyList(ApiRequestContext apiContext, GrillBotDatabaseBuilder da Mapper = mapper; } - public async Task> ProcessAsync() + public override async Task ProcessAsync() { await using var repository = DatabaseBuilder.CreateRepository(); var items = await repository.AutoReply.GetAllAsync(false); - return Mapper.Map>(items); + var result = Mapper.Map>(items); + + return ApiResult.Ok(result); } } diff --git a/src/GrillBot.App/Actions/Api/V1/AutoReply/RemoveAutoReplyItem.cs b/src/GrillBot.App/Actions/Api/V1/AutoReply/RemoveAutoReplyItem.cs index 31a4ebae5..1484744a9 100644 --- a/src/GrillBot.App/Actions/Api/V1/AutoReply/RemoveAutoReplyItem.cs +++ b/src/GrillBot.App/Actions/Api/V1/AutoReply/RemoveAutoReplyItem.cs @@ -2,6 +2,7 @@ using GrillBot.Common.Managers.Localization; using GrillBot.Common.Models; using GrillBot.Core.Exceptions; +using GrillBot.Core.Infrastructure.Actions; namespace GrillBot.App.Actions.Api.V1.AutoReply; @@ -18,16 +19,19 @@ public RemoveAutoReplyItem(ApiRequestContext apiContext, GrillBotDatabaseBuilder AutoReplyManager = autoReplyManager; } - public async Task ProcessAsync(long id) + public override async Task ProcessAsync() { + var id = (long)Parameters[0]!; + await using var repository = DatabaseBuilder.CreateRepository(); - var entity = await repository.AutoReply.FindReplyByIdAsync(id); - if (entity == null) - throw new NotFoundException(Texts["AutoReply/NotFound", ApiContext.Language]); + var entity = await repository.AutoReply.FindReplyByIdAsync(id) + ?? throw new NotFoundException(Texts["AutoReply/NotFound", ApiContext.Language]); repository.Remove(entity); await repository.CommitAsync(); await AutoReplyManager.InitAsync(); + + return ApiResult.Ok(); } } diff --git a/src/GrillBot.App/Actions/Api/V1/AutoReply/UpdateAutoReplyItem.cs b/src/GrillBot.App/Actions/Api/V1/AutoReply/UpdateAutoReplyItem.cs index 66c8c3f7a..51f2b6f7d 100644 --- a/src/GrillBot.App/Actions/Api/V1/AutoReply/UpdateAutoReplyItem.cs +++ b/src/GrillBot.App/Actions/Api/V1/AutoReply/UpdateAutoReplyItem.cs @@ -3,6 +3,7 @@ using GrillBot.Common.Managers.Localization; using GrillBot.Common.Models; using GrillBot.Core.Exceptions; +using GrillBot.Core.Infrastructure.Actions; using GrillBot.Data.Models.API.AutoReply; namespace GrillBot.App.Actions.Api.V1.AutoReply; @@ -22,13 +23,15 @@ public UpdateAutoReplyItem(ApiRequestContext apiContext, GrillBotDatabaseBuilder AutoReplyManager = autoReplyManager; } - public async Task ProcessAsync(long id, AutoReplyItemParams parameters) + public override async Task ProcessAsync() { + var id = (long)Parameters[0]!; + var parameters = (AutoReplyItemParams)Parameters[0]!; + await using var repository = DatabaseBuilder.CreateRepository(); - var entity = await repository.AutoReply.FindReplyByIdAsync(id); - if (entity == null) - throw new NotFoundException(Texts["AutoReply/NotFound", ApiContext.Language].FormatWith(id)); + var entity = await repository.AutoReply.FindReplyByIdAsync(id) + ?? throw new NotFoundException(Texts["AutoReply/NotFound", ApiContext.Language].FormatWith(id)); entity.Template = parameters.Template; entity.Flags = parameters.Flags; @@ -37,6 +40,7 @@ public async Task ProcessAsync(long id, AutoReplyItemParams param await repository.CommitAsync(); await AutoReplyManager.InitAsync(); - return Mapper.Map(entity); + var result = Mapper.Map(entity); + return ApiResult.Ok(result); } } diff --git a/src/GrillBot.App/Actions/Api/V1/Channel/ClearMessageCache.cs b/src/GrillBot.App/Actions/Api/V1/Channel/ClearMessageCache.cs index 6038b517f..7cc289c71 100644 --- a/src/GrillBot.App/Actions/Api/V1/Channel/ClearMessageCache.cs +++ b/src/GrillBot.App/Actions/Api/V1/Channel/ClearMessageCache.cs @@ -1,5 +1,6 @@ using GrillBot.Cache.Services.Managers.MessageCache; using GrillBot.Common.Models; +using GrillBot.Core.Infrastructure.Actions; using GrillBot.Core.Services.AuditLog; using GrillBot.Core.Services.AuditLog.Enums; using GrillBot.Core.Services.AuditLog.Models.Request.CreateItems; @@ -19,13 +20,18 @@ public ClearMessageCache(ApiRequestContext apiContext, IDiscordClient discordCli AuditLogServiceClient = auditLogServiceClient; } - public async Task ProcessAsync(ulong guildId, ulong channelId) + public override async Task ProcessAsync() { + var guildId = (ulong)Parameters[0]!; + var channelId = (ulong)Parameters[1]!; + var guild = await DiscordClient.GetGuildAsync(guildId, CacheMode.CacheOnly); - if (guild == null) return; + if (guild == null) + return ApiResult.Ok(); var channel = await guild.GetChannelAsync(channelId); - if (channel == null) return; + if (channel == null) + return ApiResult.Ok(); var count = await MessageCache.ClearAllMessagesFromChannelAsync(channel); var logRequest = new LogRequest @@ -45,5 +51,6 @@ public async Task ProcessAsync(ulong guildId, ulong channelId) }; await AuditLogServiceClient.CreateItemsAsync(new List { logRequest }); + return ApiResult.Ok(); } } diff --git a/src/GrillBot.App/Actions/Api/V1/Channel/GetChannelDetail.cs b/src/GrillBot.App/Actions/Api/V1/Channel/GetChannelDetail.cs index 03e3d9202..6f607e763 100644 --- a/src/GrillBot.App/Actions/Api/V1/Channel/GetChannelDetail.cs +++ b/src/GrillBot.App/Actions/Api/V1/Channel/GetChannelDetail.cs @@ -4,6 +4,7 @@ using GrillBot.Common.Models; using GrillBot.Core.Exceptions; using GrillBot.Core.Extensions; +using GrillBot.Core.Infrastructure.Actions; using GrillBot.Data.Models.API.Channels; using GrillBot.Database.Enums; using GrillBot.Database.Enums.Internal; @@ -28,12 +29,14 @@ public GetChannelDetail(ApiRequestContext apiContext, GrillBotDatabaseBuilder da MessageCache = messageCache; } - public async Task ProcessAsync(ulong id) + public override async Task ProcessAsync() { + var id = (ulong)Parameters[0]!; + await using var repository = DatabaseBuilder.CreateRepository(); - var channel = await repository.Channel.FindChannelByIdAsync(id, null, true, ChannelsIncludeUsersMode.IncludeExceptInactive, true, true); - if (channel == null) throw new NotFoundException(Texts["ChannelModule/ChannelDetail/ChannelNotFound", ApiContext.Language]); + var channel = await repository.Channel.FindChannelByIdAsync(id, null, true, ChannelsIncludeUsersMode.IncludeExceptInactive, true, true) + ?? throw new NotFoundException(Texts["ChannelModule/ChannelDetail/ChannelNotFound", ApiContext.Language]); var result = Mapper.Map(channel); if (channel.IsText()) @@ -43,15 +46,17 @@ public async Task ProcessAsync(ulong id) } if (channel.HasFlag(ChannelFlag.Deleted)) - return result; + return ApiResult.Ok(result); var guild = await DiscordClient.GetGuildAsync(channel.GuildId.ToUlong(), CacheMode.CacheOnly); var guildChannel = guild == null ? null : await guild.GetChannelAsync(id); - if (guildChannel == null) return result; + if (guildChannel == null) + return ApiResult.Ok(result); result = Mapper.Map(guildChannel, result); if (channel.IsText() || channel.IsThread() || channel.IsVoice()) result.CachedMessagesCount = await MessageCache.GetCachedMessagesCount(guildChannel); - return result; + + return ApiResult.Ok(result); } } diff --git a/src/GrillBot.App/Actions/Api/V1/Channel/GetChannelList.cs b/src/GrillBot.App/Actions/Api/V1/Channel/GetChannelList.cs index 3588a4569..015cef25f 100644 --- a/src/GrillBot.App/Actions/Api/V1/Channel/GetChannelList.cs +++ b/src/GrillBot.App/Actions/Api/V1/Channel/GetChannelList.cs @@ -2,6 +2,7 @@ using GrillBot.Cache.Services.Managers.MessageCache; using GrillBot.Common.Models; using GrillBot.Core.Extensions; +using GrillBot.Core.Infrastructure.Actions; using GrillBot.Core.Models.Pagination; using GrillBot.Data.Models.API.Channels; @@ -23,12 +24,15 @@ public GetChannelList(ApiRequestContext apiContext, GrillBotDatabaseBuilder data Mapper = mapper; } - public async Task> ProcessAsync(GetChannelListParams parameters) + public override async Task ProcessAsync() { + var parameters = (GetChannelListParams)Parameters[0]!; await using var repository = DatabaseBuilder.CreateRepository(); var channels = await repository.Channel.GetChannelListAsync(parameters, parameters.Pagination); - return await PaginatedResponse.CopyAndMapAsync(channels, MapAsync); + var result = await PaginatedResponse.CopyAndMapAsync(channels, MapAsync); + + return ApiResult.Ok(result); } private async Task MapAsync(Database.Entity.GuildChannel entity) diff --git a/src/GrillBot.App/Actions/Api/V1/Channel/GetChannelUsers.cs b/src/GrillBot.App/Actions/Api/V1/Channel/GetChannelUsers.cs index e89c94401..dea557cd3 100644 --- a/src/GrillBot.App/Actions/Api/V1/Channel/GetChannelUsers.cs +++ b/src/GrillBot.App/Actions/Api/V1/Channel/GetChannelUsers.cs @@ -1,5 +1,6 @@ using AutoMapper; using GrillBot.Common.Models; +using GrillBot.Core.Infrastructure.Actions; using GrillBot.Core.Models.Pagination; using GrillBot.Data.Models.API.Channels; @@ -16,8 +17,11 @@ public GetChannelUsers(ApiRequestContext apiContext, GrillBotDatabaseBuilder dat Mapper = mapper; } - public async Task> ProcessAsync(ulong channelId, PaginatedParams pagination) + public override async Task ProcessAsync() { + var channelId = (ulong)Parameters[0]!; + var pagination = (PaginatedParams)Parameters[1]!; + await using var repository = DatabaseBuilder.CreateRepository(); var data = await repository.Channel.GetUserChannelListAsync(channelId, pagination); @@ -26,6 +30,6 @@ public async Task> ProcessAsync(ulong cha for (var i = 0; i < result.Data.Count; i++) result.Data[i].Position = pagination.Skip() + i + 1; - return result; + return ApiResult.Ok(result); } } diff --git a/src/GrillBot.App/Actions/Api/V1/Channel/GetChannelboard.cs b/src/GrillBot.App/Actions/Api/V1/Channel/GetChannelboard.cs index 8e9f17b10..49c6bcbca 100644 --- a/src/GrillBot.App/Actions/Api/V1/Channel/GetChannelboard.cs +++ b/src/GrillBot.App/Actions/Api/V1/Channel/GetChannelboard.cs @@ -1,6 +1,7 @@ using AutoMapper; using GrillBot.Common.Extensions.Discord; using GrillBot.Common.Models; +using GrillBot.Core.Infrastructure.Actions; using GrillBot.Data.Models.API.Channels; namespace GrillBot.App.Actions.Api.V1.Channel; @@ -18,7 +19,7 @@ public GetChannelboard(ApiRequestContext apiContext, IDiscordClient discordClien Mapper = mapper; } - public async Task> ProcessAsync() + public override async Task ProcessAsync() { var mutualGuilds = await DiscordClient.FindMutualGuildsAsync(ApiContext.GetUserId()); var result = new List(); @@ -26,7 +27,8 @@ public async Task> ProcessAsync() foreach (var guild in mutualGuilds) result.AddRange(await GetChannelboardAsync(guild)); - return result.OrderByDescending(o => o.Count).ThenByDescending(o => o.LastMessageAt).ToList(); + result = result.OrderByDescending(o => o.Count).ThenByDescending(o => o.LastMessageAt).ToList(); + return ApiResult.Ok(result); } private async Task> GetChannelboardAsync(IGuild guild) @@ -44,12 +46,12 @@ private async Task> GetChannelboardAsync(IGuild guild) var channels = await repository.Channel.GetVisibleChannelsAsync(guild.Id, statistics.Keys.ToList(), true, true); foreach (var channel in channels) { - var stats = statistics[channel.ChannelId]; + var (count, firstMessageAt, lastMessageAt) = statistics[channel.ChannelId]; var channelboardItem = Mapper.Map(channel); - channelboardItem.Count = stats.count; - channelboardItem.LastMessageAt = stats.lastMessageAt; - channelboardItem.FirstMessageAt = stats.firstMessageAt; + channelboardItem.Count = count; + channelboardItem.LastMessageAt = lastMessageAt; + channelboardItem.FirstMessageAt = firstMessageAt; result.Add(channelboardItem); } diff --git a/src/GrillBot.App/Actions/Api/V1/Channel/GetPins.cs b/src/GrillBot.App/Actions/Api/V1/Channel/GetPins.cs index fa5f1d027..3c610de6c 100644 --- a/src/GrillBot.App/Actions/Api/V1/Channel/GetPins.cs +++ b/src/GrillBot.App/Actions/Api/V1/Channel/GetPins.cs @@ -3,6 +3,9 @@ using GrillBot.Common.Models; using GrillBot.Core.Services.RubbergodService; using GrillBot.Core.Exceptions; +using GrillBot.Core.Infrastructure.Actions; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Http; namespace GrillBot.App.Actions.Api.V1.Channel; @@ -19,13 +22,22 @@ public GetPins(ApiRequestContext apiContext, ChannelHelper channelHelper, ITexts RubbergodServiceClient = rubbergodServiceClient; } - public async Task ProcessAsync(ulong channelId, bool markdown) + public override async Task ProcessAsync() { - var guild = await ChannelHelper.GetGuildFromChannelAsync(null, channelId); - if (guild is null) - throw new NotFoundException(Texts["ChannelModule/ChannelDetail/ChannelNotFound", ApiContext.Language]); + var channelId = (ulong)Parameters[0]!; + var markdown = (bool)Parameters[1]!; + + var guild = await ChannelHelper.GetGuildFromChannelAsync(null, channelId) + ?? throw new NotFoundException(Texts["ChannelModule/ChannelDetail/ChannelNotFound", ApiContext.Language]); var content = await RubbergodServiceClient.GetPinsAsync(guild.Id, channelId, markdown); - return Encoding.UTF8.GetString(content); + var apiResult = new ContentResult + { + Content = Encoding.UTF8.GetString(content), + ContentType = markdown ? "text/markdown" : "application/json", + StatusCode = StatusCodes.Status200OK + }; + + return ApiResult.Ok(apiResult); } } diff --git a/src/GrillBot.App/Actions/Api/V1/Channel/GetPinsWithAttachments.cs b/src/GrillBot.App/Actions/Api/V1/Channel/GetPinsWithAttachments.cs index b133c5a4f..daefdcd7b 100644 --- a/src/GrillBot.App/Actions/Api/V1/Channel/GetPinsWithAttachments.cs +++ b/src/GrillBot.App/Actions/Api/V1/Channel/GetPinsWithAttachments.cs @@ -5,6 +5,8 @@ using GrillBot.Core.Services.RubbergodService; using GrillBot.Core.Exceptions; using GrillBot.Core.IO; +using GrillBot.Core.Infrastructure.Actions; +using Microsoft.AspNetCore.Mvc; namespace GrillBot.App.Actions.Api.V1.Channel; @@ -24,28 +26,31 @@ public GetPinsWithAttachments(ApiRequestContext apiContext, ChannelHelper channe DownloadHelper = downloadHelper; } - public async Task ProcessAsync(ulong channelId) + public override async Task ProcessAsync() { - var guild = await ChannelHelper.GetGuildFromChannelAsync(null, channelId); - if (guild is null) - throw new NotFoundException(Texts["ChannelModule/ChannelDetail/ChannelNotFound", ApiContext.Language]); + var channelId = (ulong)Parameters[0]!; + + var guild = await ChannelHelper.GetGuildFromChannelAsync(null, channelId) + ?? throw new NotFoundException(Texts["ChannelModule/ChannelDetail/ChannelNotFound", ApiContext.Language]); var markdownContent = await RubbergodService.GetPinsAsync(guild.Id, channelId, true); TemporaryFile? archiveFile = null; - ZipArchive? archive = null; try { + ZipArchive? archive; (archiveFile, archive) = await CreateTemporaryZipAsync(markdownContent); await AppendAttachmentsAsync(guild, channelId, archive); archive.Dispose(); - return await File.ReadAllBytesAsync(archiveFile.Path); + var result = await File.ReadAllBytesAsync(archiveFile.Path); + var apiResult = new FileContentResult(result, "application/zip"); + + return ApiResult.Ok(apiResult); } finally { - archive?.Dispose(); archiveFile?.Dispose(); } } @@ -70,8 +75,8 @@ private async Task AppendAttachmentsAsync(IGuild guild, ulong channelId, ZipArch var filename = attachment["name"]!.Value()!; var attachmentEntry = archive.CreateEntry(filename); - await using (var attachmentEntryStream = attachmentEntry.Open()) - await attachmentEntryStream.WriteAsync(content); + await using var attachmentEntryStream = attachmentEntry.Open(); + await attachmentEntryStream.WriteAsync(content); } } } diff --git a/src/GrillBot.App/Actions/Api/V1/Channel/SendMessageToChannel.cs b/src/GrillBot.App/Actions/Api/V1/Channel/SendMessageToChannel.cs index a4f579088..de150cb2f 100644 --- a/src/GrillBot.App/Actions/Api/V1/Channel/SendMessageToChannel.cs +++ b/src/GrillBot.App/Actions/Api/V1/Channel/SendMessageToChannel.cs @@ -3,6 +3,7 @@ using GrillBot.Common.Models; using GrillBot.Core.Exceptions; using GrillBot.Core.Extensions; +using GrillBot.Core.Infrastructure.Actions; using GrillBot.Data.Models.API.Channels; namespace GrillBot.App.Actions.Api.V1.Channel; @@ -20,6 +21,16 @@ public SendMessageToChannel(ApiRequestContext apiContext, ITextsManager texts, I MessageCache = messageCache; } + public override async Task ProcessAsync() + { + var guildId = (ulong)Parameters[0]!; + var channelId = (ulong)Parameters[1]!; + var parameters = (SendMessageToChannelParams)Parameters[0]!; + + await ProcessAsync(guildId, channelId, parameters); + return ApiResult.Ok(); + } + public async Task ProcessAsync(ulong guildId, ulong channelId, SendMessageToChannelParams parameters) { var channel = await FindChannelAsync(guildId, channelId); diff --git a/src/GrillBot.App/Actions/Api/V1/Channel/SimpleList/GetChannelSimpleList.cs b/src/GrillBot.App/Actions/Api/V1/Channel/SimpleList/GetChannelSimpleList.cs index bc2b59bb3..71af174ce 100644 --- a/src/GrillBot.App/Actions/Api/V1/Channel/SimpleList/GetChannelSimpleList.cs +++ b/src/GrillBot.App/Actions/Api/V1/Channel/SimpleList/GetChannelSimpleList.cs @@ -2,6 +2,7 @@ using AutoMapper; using GrillBot.Common.Managers.Localization; using GrillBot.Common.Models; +using GrillBot.Core.Infrastructure.Actions; namespace GrillBot.App.Actions.Api.V1.Channel.SimpleList; @@ -17,8 +18,11 @@ public GetChannelSimpleList(ApiRequestContext apiContext, IDiscordClient discord Texts = texts; } - public async Task> ProcessAsync(ulong? guildId, bool noThreads) + public override async Task ProcessAsync() { + var noThreads = (bool)Parameters[0]!; + var guildId = (ulong?)Parameters[1]; + var guilds = await GetGuildsAsync(guildId); ValidateParameters(guildId, guilds); @@ -32,7 +36,7 @@ public async Task> ProcessAsync(ulong? guildId, bool await using var repository = DatabaseBuilder.CreateRepository(); var databaseChannels = await repository.Channel.GetAllChannelsAsync(guildIds, noThreads, true); - databaseChannels = databaseChannels.FindAll(o => mappedChannels.All(x => x.Id != o.ChannelId)); + databaseChannels = databaseChannels.FindAll(o => mappedChannels.TrueForAll(x => x.Id != o.ChannelId)); mappedChannels.AddRange(Mapper.Map>(databaseChannels)); return CreateResult(mappedChannels); diff --git a/src/GrillBot.App/Actions/Api/V1/Channel/SimpleList/GetChannelSimpleListWithPins.cs b/src/GrillBot.App/Actions/Api/V1/Channel/SimpleList/GetChannelSimpleListWithPins.cs index 36d6b6d45..06c4dda23 100644 --- a/src/GrillBot.App/Actions/Api/V1/Channel/SimpleList/GetChannelSimpleListWithPins.cs +++ b/src/GrillBot.App/Actions/Api/V1/Channel/SimpleList/GetChannelSimpleListWithPins.cs @@ -1,5 +1,6 @@ using AutoMapper; using GrillBot.Common.Models; +using GrillBot.Core.Infrastructure.Actions; namespace GrillBot.App.Actions.Api.V1.Channel.SimpleList; @@ -15,7 +16,7 @@ public GetChannelSimpleListWithPins(ApiRequestContext apiContext, IDiscordClient DatabaseBuilder = databaseBuilder; } - public async Task> ProcessAsync() + public override async Task ProcessAsync() { var guilds = await GetGuildsAsync(); var availableChannels = await GetAvailableChannelsAsync(guilds, false); diff --git a/src/GrillBot.App/Actions/Api/V1/Channel/SimpleList/SimpleListBase.cs b/src/GrillBot.App/Actions/Api/V1/Channel/SimpleList/SimpleListBase.cs index 57bcb8c74..b812d512a 100644 --- a/src/GrillBot.App/Actions/Api/V1/Channel/SimpleList/SimpleListBase.cs +++ b/src/GrillBot.App/Actions/Api/V1/Channel/SimpleList/SimpleListBase.cs @@ -1,6 +1,7 @@ using AutoMapper; using GrillBot.Common.Extensions.Discord; using GrillBot.Common.Models; +using GrillBot.Core.Infrastructure.Actions; namespace GrillBot.App.Actions.Api.V1.Channel.SimpleList; @@ -45,12 +46,14 @@ private async Task> GetAvailableChannelsAsync(IGuild guild, return (noThreads ? channels.Where(o => o is not IThreadChannel) : channels).ToList(); } - protected static Dictionary CreateResult(IEnumerable channels) + protected static ApiResult CreateResult(IEnumerable channels) { - return channels + var data = channels .DistinctBy(o => o.Id) .OrderBy(o => o.Name) .ToDictionary(o => o.Id, o => $"{o.Name} {(o.Type is ChannelType.PublicThread or ChannelType.PrivateThread or ChannelType.NewsThread ? "(Thread)" : "")}".Trim()); + + return ApiResult.Ok(data); } protected List Map(IEnumerable channels) diff --git a/src/GrillBot.App/Actions/Api/V1/Channel/UpdateChannel.cs b/src/GrillBot.App/Actions/Api/V1/Channel/UpdateChannel.cs index 4c9546379..c1196ea7d 100644 --- a/src/GrillBot.App/Actions/Api/V1/Channel/UpdateChannel.cs +++ b/src/GrillBot.App/Actions/Api/V1/Channel/UpdateChannel.cs @@ -4,6 +4,7 @@ using GrillBot.Common.Models; using GrillBot.Core.Exceptions; using GrillBot.Core.Extensions; +using GrillBot.Core.Infrastructure.Actions; using GrillBot.Core.Services.AuditLog; using GrillBot.Core.Services.AuditLog.Enums; using GrillBot.Core.Services.AuditLog.Models.Request.CreateItems; @@ -34,23 +35,28 @@ public UpdateChannel(ApiRequestContext apiContext, GrillBotDatabaseBuilder datab AuditLogServiceClient = auditLogServiceClient; } - public async Task ProcessAsync(ulong id, UpdateChannelParams parameters) + public override async Task ProcessAsync() { + var id = (ulong)Parameters[0]!; + var parameters = (UpdateChannelParams)Parameters[1]!; + await using var repository = DatabaseBuilder.CreateRepository(); - var channel = await repository.Channel.FindChannelByIdAsync(id); - if (channel == null) - throw new NotFoundException(Texts["ChannelModule/ChannelDetail/ChannelNotFound", ApiContext.Language]); + var channel = await repository.Channel.FindChannelByIdAsync(id) + ?? throw new NotFoundException(Texts["ChannelModule/ChannelDetail/ChannelNotFound", ApiContext.Language]); var before = channel.Clone(); channel.Flags = parameters.Flags; var success = await repository.CommitAsync() > 0; - if (!success) return; + if (!success) + return ApiResult.Ok(); await WriteToAuditLogAsync(id, before, channel); await TryReloadAutoReplyAsync(before, channel); await TrySyncPointsService(before, channel); + + return ApiResult.Ok(); } private async Task TryReloadAutoReplyAsync(Database.Entity.GuildChannel before, Database.Entity.GuildChannel after) diff --git a/src/GrillBot.App/Actions/Api/V1/Dashboard/GetActiveOperations.cs b/src/GrillBot.App/Actions/Api/V1/Dashboard/GetActiveOperations.cs index e8b6b4ebd..5a1ecce9a 100644 --- a/src/GrillBot.App/Actions/Api/V1/Dashboard/GetActiveOperations.cs +++ b/src/GrillBot.App/Actions/Api/V1/Dashboard/GetActiveOperations.cs @@ -1,4 +1,5 @@ using GrillBot.Common.Models; +using GrillBot.Core.Infrastructure.Actions; using GrillBot.Core.Managers.Performance; namespace GrillBot.App.Actions.Api.V1.Dashboard; @@ -12,6 +13,9 @@ public GetActiveOperations(ApiRequestContext apiContext, ICounterManager counter CounterManager = counterManager; } - public Dictionary Process() - => CounterManager.GetActiveCounters(); + public override Task ProcessAsync() + { + var result = CounterManager.GetActiveCounters(); + return Task.FromResult(ApiResult.Ok(result)); + } } diff --git a/src/GrillBot.App/Actions/Api/V1/Dashboard/GetCommonInfo.cs b/src/GrillBot.App/Actions/Api/V1/Dashboard/GetCommonInfo.cs index f43f58f10..3cdc78ece 100644 --- a/src/GrillBot.App/Actions/Api/V1/Dashboard/GetCommonInfo.cs +++ b/src/GrillBot.App/Actions/Api/V1/Dashboard/GetCommonInfo.cs @@ -1,5 +1,6 @@ using GrillBot.Common.Managers; using GrillBot.Common.Models; +using GrillBot.Core.Infrastructure.Actions; using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.Hosting; @@ -18,13 +19,14 @@ public GetCommonInfo(ApiRequestContext apiContext, IDiscordClient discordClient, WebHost = webHost; } - public Data.Models.API.System.DashboardInfo Process() + public override Task ProcessAsync() { var process = global::System.Diagnostics.Process.GetCurrentProcess(); + var now = DateTime.Now; - return new Data.Models.API.System.DashboardInfo + var result = new Data.Models.API.System.DashboardInfo { - Uptime = Convert.ToInt64((DateTime.Now - process.StartTime).TotalMilliseconds), + Uptime = Convert.ToInt64((now - process.StartTime).TotalMilliseconds), ConnectionState = DiscordClient.ConnectionState, UsedMemory = process.WorkingSet64, IsActive = InitManager.Get(), @@ -33,5 +35,7 @@ public Data.Models.API.System.DashboardInfo Process() StartAt = process.StartTime, IsDevelopment = WebHost.IsDevelopment() }; + + return Task.FromResult(ApiResult.Ok(result)); } } diff --git a/src/GrillBot.App/Actions/Api/V1/Dashboard/GetOperationStats.cs b/src/GrillBot.App/Actions/Api/V1/Dashboard/GetOperationStats.cs index c40d961ec..53d72106b 100644 --- a/src/GrillBot.App/Actions/Api/V1/Dashboard/GetOperationStats.cs +++ b/src/GrillBot.App/Actions/Api/V1/Dashboard/GetOperationStats.cs @@ -1,4 +1,5 @@ using GrillBot.Common.Models; +using GrillBot.Core.Infrastructure.Actions; using GrillBot.Core.Managers.Performance; namespace GrillBot.App.Actions.Api.V1.Dashboard; @@ -6,17 +7,16 @@ namespace GrillBot.App.Actions.Api.V1.Dashboard; public class GetOperationStats : ApiAction { private ICounterManager CounterManager { get; } - + public GetOperationStats(ApiRequestContext apiContext, ICounterManager counterManager) : base(apiContext) { CounterManager = counterManager; } - - public List Process() + + public override Task ProcessAsync() { var statistics = CounterManager.GetStatistics(); - - return statistics + var result = statistics .Select(o => new { Key = o.Section.Split('.'), Item = o }) .GroupBy(o => o.Key.Length == 1 ? o.Key[0] : string.Join(".", o.Key.Take(o.Key.Length - 1))) .Select(o => new CounterStats @@ -25,5 +25,7 @@ public List Process() Section = o.Key, TotalTime = o.Sum(x => x.Item.TotalTime) }).ToList(); + + return Task.FromResult(ApiResult.Ok(result)); } } diff --git a/src/GrillBot.App/Actions/Api/V1/Dashboard/GetServicesList.cs b/src/GrillBot.App/Actions/Api/V1/Dashboard/GetServicesList.cs index 001cfa768..d5a272ac3 100644 --- a/src/GrillBot.App/Actions/Api/V1/Dashboard/GetServicesList.cs +++ b/src/GrillBot.App/Actions/Api/V1/Dashboard/GetServicesList.cs @@ -1,5 +1,6 @@ using GrillBot.Common.Managers.Logging; using GrillBot.Common.Models; +using GrillBot.Core.Infrastructure.Actions; using GrillBot.Core.Services.AuditLog; using GrillBot.Core.Services.Common; using GrillBot.Core.Services.Graphics; @@ -32,7 +33,7 @@ public GetServicesList(ApiRequestContext apiContext, LoggingManager logging, IGr LoggingManager = logging; } - public async Task> ProcessAsync() + public override async Task ProcessAsync() { var services = new List(); @@ -43,11 +44,12 @@ public async Task> ProcessAsync() await AddServiceStatusAsync(services, "audit-log", AuditLogServiceClient); if (Errors.Count == 0) - return services; + return ApiResult.Ok(services); var aggregateException = new AggregateException(Errors); await LoggingManager.ErrorAsync("API-Dashboard", aggregateException.Message, aggregateException); - return services; + + return ApiResult.Ok(services); } private async Task AddServiceStatusAsync(ICollection services, string id, IClient client) diff --git a/src/GrillBot.App/Actions/Api/V1/Emote/GetEmoteSuggestionsList.cs b/src/GrillBot.App/Actions/Api/V1/Emote/GetEmoteSuggestionsList.cs index a14b1f146..6c8e76f24 100644 --- a/src/GrillBot.App/Actions/Api/V1/Emote/GetEmoteSuggestionsList.cs +++ b/src/GrillBot.App/Actions/Api/V1/Emote/GetEmoteSuggestionsList.cs @@ -1,5 +1,6 @@ using AutoMapper; using GrillBot.Common.Models; +using GrillBot.Core.Infrastructure.Actions; using GrillBot.Core.Models.Pagination; using GrillBot.Data.Models.API.Suggestions; @@ -16,11 +17,15 @@ public GetEmoteSuggestionsList(ApiRequestContext apiContext, GrillBotDatabaseBui Mapper = mapper; } - public async Task> ProcessAsync(GetSuggestionsListParams parameters) + public override async Task ProcessAsync() { + var parameters = (GetSuggestionsListParams)Parameters[0]!; + await using var repository = DatabaseBuilder.CreateRepository(); var data = await repository.EmoteSuggestion.GetSuggestionListAsync(parameters, parameters.Pagination); - return await PaginatedResponse.CopyAndMapAsync(data, entity => Task.FromResult(Mapper.Map(entity))); + var result = await PaginatedResponse.CopyAndMapAsync(data, entity => Task.FromResult(Mapper.Map(entity))); + + return ApiResult.Ok(result); } } diff --git a/src/GrillBot.App/Actions/Api/V1/Emote/GetStatOfEmote.cs b/src/GrillBot.App/Actions/Api/V1/Emote/GetStatOfEmote.cs index f4ab42d36..2aa4df018 100644 --- a/src/GrillBot.App/Actions/Api/V1/Emote/GetStatOfEmote.cs +++ b/src/GrillBot.App/Actions/Api/V1/Emote/GetStatOfEmote.cs @@ -1,6 +1,7 @@ using AutoMapper; using GrillBot.Common.Models; using GrillBot.Core.Exceptions; +using GrillBot.Core.Infrastructure.Actions; using GrillBot.Data.Models.API.Emotes; namespace GrillBot.App.Actions.Api.V1.Emote; @@ -16,15 +17,17 @@ public GetStatOfEmote(ApiRequestContext apiContext, GrillBotDatabaseBuilder data Mapper = mapper; } - public async Task ProcessAsync(string emoteId) + public override async Task ProcessAsync() { + var emoteId = (string)Parameters[0]!; + await using var repository = DatabaseBuilder.CreateRepository(); var emote = Discord.Emote.Parse(emoteId); - var statistics = await repository.Emote.GetStatisticsOfEmoteAsync(emote); - if (statistics is null) - throw new NotFoundException(); + var statistics = await repository.Emote.GetStatisticsOfEmoteAsync(emote) + ?? throw new NotFoundException(); - return Mapper.Map(statistics); + var result = Mapper.Map(statistics); + return ApiResult.Ok(result); } } diff --git a/src/GrillBot.App/Actions/Api/V1/Emote/GetStatsOfEmotes.cs b/src/GrillBot.App/Actions/Api/V1/Emote/GetStatsOfEmotes.cs index d3a9be9df..2295fbefb 100644 --- a/src/GrillBot.App/Actions/Api/V1/Emote/GetStatsOfEmotes.cs +++ b/src/GrillBot.App/Actions/Api/V1/Emote/GetStatsOfEmotes.cs @@ -1,5 +1,6 @@ using AutoMapper; using GrillBot.Common.Models; +using GrillBot.Core.Infrastructure.Actions; using GrillBot.Core.Models.Pagination; using GrillBot.Data.Models.API.Emotes; @@ -16,6 +17,15 @@ public GetStatsOfEmotes(ApiRequestContext apiContext, GrillBotDatabaseBuilder da Mapper = mapper; } + public override async Task ProcessAsync() + { + var parameters = (EmotesListParams)Parameters[0]!; + var unsupported = (bool)Parameters[1]!; + var result = await ProcessAsync(parameters, unsupported); + + return ApiResult.Ok(result); + } + public async Task> ProcessAsync(EmotesListParams parameters, bool unsupported) { await using var repository = DatabaseBuilder.CreateRepository(); diff --git a/src/GrillBot.App/Actions/Api/V1/Emote/GetSupportedEmotes.cs b/src/GrillBot.App/Actions/Api/V1/Emote/GetSupportedEmotes.cs index c6e1d1fc3..52d6ca1e0 100644 --- a/src/GrillBot.App/Actions/Api/V1/Emote/GetSupportedEmotes.cs +++ b/src/GrillBot.App/Actions/Api/V1/Emote/GetSupportedEmotes.cs @@ -1,5 +1,6 @@ using AutoMapper; using GrillBot.Common.Models; +using GrillBot.Core.Infrastructure.Actions; using GrillBot.Data.Models.API.Emotes; namespace GrillBot.App.Actions.Api.V1.Emote; @@ -15,7 +16,7 @@ public GetSupportedEmotes(ApiRequestContext apiContext, IMapper mapper, IDiscord DiscordClient = discordClient; } - public async Task> ProcessAsync() + public override async Task ProcessAsync() { var guilds = await DiscordClient.GetGuildsAsync(); var result = new List(); @@ -32,8 +33,9 @@ public async Task> ProcessAsync() result.AddRange(mappedEmotes); } - return result + result = result .OrderBy(o => o.Name) .ToList(); + return ApiResult.Ok(result); } } diff --git a/src/GrillBot.App/Actions/Api/V1/Emote/GetUserStatisticsOfEmote.cs b/src/GrillBot.App/Actions/Api/V1/Emote/GetUserStatisticsOfEmote.cs index 866dae2a1..402298920 100644 --- a/src/GrillBot.App/Actions/Api/V1/Emote/GetUserStatisticsOfEmote.cs +++ b/src/GrillBot.App/Actions/Api/V1/Emote/GetUserStatisticsOfEmote.cs @@ -1,5 +1,6 @@ using AutoMapper; using GrillBot.Common.Models; +using GrillBot.Core.Infrastructure.Actions; using GrillBot.Core.Models.Pagination; using GrillBot.Data.Models.API.Emotes; @@ -16,14 +17,18 @@ public GetUserStatisticsOfEmote(ApiRequestContext apiContext, GrillBotDatabaseBu Mapper = mapper; } - public async Task> ProcessAsync(EmoteStatsUserListParams parameters) + public override async Task ProcessAsync() { + var parameters = (EmoteStatsUserListParams)Parameters[0]!; + await using var repository = DatabaseBuilder.CreateRepository(); var statistics = await repository.Emote.GetUserStatisticsOfEmoteAsync(parameters, parameters.Pagination); - return await PaginatedResponse.CopyAndMapAsync( + var result = await PaginatedResponse.CopyAndMapAsync( statistics, entity => Task.FromResult(Mapper.Map(entity)) ); + + return ApiResult.Ok(result); } } diff --git a/src/GrillBot.App/Actions/Api/V1/Emote/MergeStats.cs b/src/GrillBot.App/Actions/Api/V1/Emote/MergeStats.cs index 369eadc41..8c02b4ce1 100644 --- a/src/GrillBot.App/Actions/Api/V1/Emote/MergeStats.cs +++ b/src/GrillBot.App/Actions/Api/V1/Emote/MergeStats.cs @@ -1,5 +1,6 @@ using GrillBot.Common.Extensions; using GrillBot.Common.Models; +using GrillBot.Core.Infrastructure.Actions; using GrillBot.Core.Managers.Discord; using GrillBot.Core.Services.AuditLog; using GrillBot.Core.Services.AuditLog.Enums; @@ -22,13 +23,16 @@ public MergeStats(ApiRequestContext apiContext, GrillBotDatabaseBuilder database AuditLogServiceClient = auditLogServiceClient; } - public async Task ProcessAsync(MergeEmoteStatsParams parameters) + public override async Task ProcessAsync() { + var parameters = (MergeEmoteStatsParams)Parameters[0]!; + await using var repository = DatabaseBuilder.CreateRepository(); await ValidateMergeAsync(repository, parameters); var sourceStats = await repository.Emote.FindStatisticsByEmoteIdAsync(parameters.SourceEmoteId); - if (sourceStats.Count == 0) return 0; + if (sourceStats.Count == 0) + return ApiResult.Ok(0); var destinationStats = await repository.Emote.FindStatisticsByEmoteIdAsync(parameters.DestinationEmoteId); foreach (var item in sourceStats) @@ -58,7 +62,9 @@ public async Task ProcessAsync(MergeEmoteStatsParams parameters) } await WriteToAuditLogAsync(parameters, sourceStats.Count, destinationStats.Count); - return await repository.CommitAsync(); + var result = await repository.CommitAsync(); + + return ApiResult.Ok(result); } private static async Task ValidateMergeAsync(GrillBotRepository repository, MergeEmoteStatsParams @params) diff --git a/src/GrillBot.App/Actions/Api/V1/Emote/RemoveStats.cs b/src/GrillBot.App/Actions/Api/V1/Emote/RemoveStats.cs index f553ef59b..5fef74a2d 100644 --- a/src/GrillBot.App/Actions/Api/V1/Emote/RemoveStats.cs +++ b/src/GrillBot.App/Actions/Api/V1/Emote/RemoveStats.cs @@ -1,4 +1,5 @@ using GrillBot.Common.Models; +using GrillBot.Core.Infrastructure.Actions; using GrillBot.Core.Services.AuditLog; using GrillBot.Core.Services.AuditLog.Enums; using GrillBot.Core.Services.AuditLog.Models.Request.CreateItems; @@ -16,16 +17,21 @@ public RemoveStats(ApiRequestContext apiContext, GrillBotDatabaseBuilder databas AuditLogServiceClient = auditLogServiceClient; } - public async Task ProcessAsync(string emoteId) + public override async Task ProcessAsync() { + var emoteId = (string)Parameters[0]!; + await using var repository = DatabaseBuilder.CreateRepository(); var emotes = await repository.Emote.FindStatisticsByEmoteIdAsync(emoteId); - if (emotes.Count == 0) return 0; + if (emotes.Count == 0) + return ApiResult.Ok(0); await WriteToAuditlogAsync(emoteId, emotes.Count); repository.RemoveCollection(emotes); - return await repository.CommitAsync(); + var result = await repository.CommitAsync(); + + return ApiResult.Ok(result); } private async Task WriteToAuditlogAsync(string emoteId, int emotesCount) diff --git a/src/GrillBot.App/Actions/Api/V1/Guild/GetAvailableGuilds.cs b/src/GrillBot.App/Actions/Api/V1/Guild/GetAvailableGuilds.cs index 3617b9283..822c2da75 100644 --- a/src/GrillBot.App/Actions/Api/V1/Guild/GetAvailableGuilds.cs +++ b/src/GrillBot.App/Actions/Api/V1/Guild/GetAvailableGuilds.cs @@ -1,5 +1,6 @@ using GrillBot.Common.Extensions.Discord; using GrillBot.Common.Models; +using GrillBot.Core.Infrastructure.Actions; using GrillBot.Data.Models.API.Guilds; namespace GrillBot.App.Actions.Api.V1.Guild; @@ -15,10 +16,10 @@ public GetAvailableGuilds(ApiRequestContext apiContext, GrillBotDatabaseBuilder DiscordClient = discordClient; } - public async Task> ProcessAsync() + public override async Task ProcessAsync() { if (ApiContext.IsPublic()) - return await GetMutualGuildsAsync(); + return ApiResult.Ok(await GetMutualGuildsAsync()); var filter = new GetGuildListParams { @@ -28,7 +29,7 @@ public async Task> ProcessAsync() await using var repository = DatabaseBuilder.CreateRepository(); var data = await repository.Guild.GetGuildListAsync(filter, filter.Pagination); - return data.Data.ToDictionary(o => o.Id, o => o.Name); + return ApiResult.Ok(data.Data.ToDictionary(o => o.Id, o => o.Name)); } private async Task> GetMutualGuildsAsync() diff --git a/src/GrillBot.App/Actions/Api/V1/Guild/GetGuildDetail.cs b/src/GrillBot.App/Actions/Api/V1/Guild/GetGuildDetail.cs index ffef2996f..8c9da9a93 100644 --- a/src/GrillBot.App/Actions/Api/V1/Guild/GetGuildDetail.cs +++ b/src/GrillBot.App/Actions/Api/V1/Guild/GetGuildDetail.cs @@ -5,6 +5,7 @@ using GrillBot.Common.Models; using GrillBot.Core.Exceptions; using GrillBot.Core.Extensions; +using GrillBot.Core.Infrastructure.Actions; using GrillBot.Core.Models.Pagination; using GrillBot.Core.Services.AuditLog; using GrillBot.Core.Services.PointsService; @@ -37,8 +38,10 @@ public GetGuildDetail(ApiRequestContext apiContext, GrillBotDatabaseBuilder data AuditLogServiceClient = auditLogServiceClient; } - public async Task ProcessAsync(ulong id) + public override async Task ProcessAsync() { + var id = (ulong)Parameters[0]!; + await using var repository = DatabaseBuilder.CreateRepository(); var dbGuild = await repository.Guild.FindGuildByIdAsync(id, true); @@ -47,7 +50,8 @@ public async Task ProcessAsync(ulong id) var detail = Mapper.Map(dbGuild); var discordGuild = await DiscordClient.GetGuildAsync(id); - if (discordGuild == null) return detail; + if (discordGuild == null) + return ApiResult.Ok(detail); detail.DatabaseReport = await CreateDatabaseReportAsync(id); detail = Mapper.Map(discordGuild, detail); @@ -80,7 +84,7 @@ public async Task ProcessAsync(ulong id) .GroupBy(o => o) .ToDictionary(o => o.Key, o => o.Count()); - return detail; + return ApiResult.Ok(detail); } private async Task CreateDatabaseReportAsync(ulong guildId) diff --git a/src/GrillBot.App/Actions/Api/V1/Guild/GetGuildList.cs b/src/GrillBot.App/Actions/Api/V1/Guild/GetGuildList.cs index 3341fb772..bd3c8d055 100644 --- a/src/GrillBot.App/Actions/Api/V1/Guild/GetGuildList.cs +++ b/src/GrillBot.App/Actions/Api/V1/Guild/GetGuildList.cs @@ -1,6 +1,7 @@ using AutoMapper; using GrillBot.Common.Models; using GrillBot.Core.Extensions; +using GrillBot.Core.Infrastructure.Actions; using GrillBot.Core.Models.Pagination; using GrillBot.Data.Models.API.Guilds; @@ -19,8 +20,10 @@ public GetGuildList(ApiRequestContext apiContext, GrillBotDatabaseBuilder databa DiscordClient = discordClient; } - public async Task> ProcessAsync(GetGuildListParams parameters) + public override async Task ProcessAsync() { + var parameters = (GetGuildListParams)Parameters[0]!; + await using var repository = DatabaseBuilder.CreateRepository(); var data = await repository.Guild.GetGuildListAsync(parameters, parameters.Pagination); @@ -35,6 +38,6 @@ public GetGuildList(ApiRequestContext apiContext, GrillBotDatabaseBuilder databa result.Data[i] = Mapper.Map(guild, result.Data[i]); } - return result; + return ApiResult.Ok(result); } } diff --git a/src/GrillBot.App/Actions/Api/V1/Guild/GetRoles.cs b/src/GrillBot.App/Actions/Api/V1/Guild/GetRoles.cs index 80d13ed43..b794949b5 100644 --- a/src/GrillBot.App/Actions/Api/V1/Guild/GetRoles.cs +++ b/src/GrillBot.App/Actions/Api/V1/Guild/GetRoles.cs @@ -1,5 +1,6 @@ using GrillBot.Common.Extensions.Discord; using GrillBot.Common.Models; +using GrillBot.Core.Infrastructure.Actions; namespace GrillBot.App.Actions.Api.V1.Guild; @@ -12,15 +13,18 @@ public GetRoles(ApiRequestContext apiContext, IDiscordClient discordClient) : ba DiscordClient = discordClient; } - public async Task> ProcessAsync(ulong? guildId) + public override async Task ProcessAsync() { + var guildId = (ulong?)Parameters[0]; + var guilds = await GetGuildsAsync(guildId); - return guilds + var result = guilds .Select(o => o.Roles.Where(x => x.Id != o.EveryoneRole.Id)) .SelectMany(o => o) .OrderBy(o => o.Name) .ToDictionary(o => o.Id.ToString(), o => o.Name); + return ApiResult.Ok(result); } private async Task> GetGuildsAsync(ulong? guildId) diff --git a/src/GrillBot.App/Actions/Api/V1/Guild/UpdateGuild.cs b/src/GrillBot.App/Actions/Api/V1/Guild/UpdateGuild.cs index e2eec8f55..50d629cbd 100644 --- a/src/GrillBot.App/Actions/Api/V1/Guild/UpdateGuild.cs +++ b/src/GrillBot.App/Actions/Api/V1/Guild/UpdateGuild.cs @@ -2,6 +2,7 @@ using GrillBot.Common.Models; using GrillBot.Core.Exceptions; using GrillBot.Core.Extensions; +using GrillBot.Core.Infrastructure.Actions; using GrillBot.Data.Models.API.Guilds; namespace GrillBot.App.Actions.Api.V1.Guild; @@ -21,8 +22,11 @@ public UpdateGuild(ApiRequestContext apiContext, IDiscordClient discordClient, G Texts = texts; } - public async Task ProcessAsync(ulong id, UpdateGuildParams parameters) + public override async Task ProcessAsync() { + var id = (ulong)Parameters[0]!; + var parameters = (UpdateGuildParams)Parameters[1]!; + var guild = await DiscordClient.GetGuildAsync(id); if (guild == null) throw new NotFoundException(Texts["GuildModule/GuildDetail/NotFound", ApiContext.Language]); @@ -59,7 +63,9 @@ public async Task ProcessAsync(ulong id, UpdateGuildParams paramete dbGuild.EmoteSuggestionsTo = parameters.EmoteSuggestionsValidity?.To; await repository.CommitAsync(); - return await GetGuildDetail.ProcessAsync(id); + + GetGuildDetail.Init(HttpContext, new object[] { id }); + return await GetGuildDetail.ProcessAsync(); } private void ThrowValidationException(string errorMessageId, object value, params string[] memberNames) diff --git a/src/GrillBot.App/Actions/Api/V1/Invite/DeleteInvite.cs b/src/GrillBot.App/Actions/Api/V1/Invite/DeleteInvite.cs index 681dcfdad..096b14171 100644 --- a/src/GrillBot.App/Actions/Api/V1/Invite/DeleteInvite.cs +++ b/src/GrillBot.App/Actions/Api/V1/Invite/DeleteInvite.cs @@ -1,6 +1,7 @@ using GrillBot.Common.Managers.Localization; using GrillBot.Common.Models; using GrillBot.Core.Exceptions; +using GrillBot.Core.Infrastructure.Actions; namespace GrillBot.App.Actions.Api.V1.Invite; @@ -15,18 +16,23 @@ public DeleteInvite(ApiRequestContext apiContext, GrillBotDatabaseBuilder databa Texts = texts; } - public async Task ProcessAsync(ulong guildId, string code) + public override async Task ProcessAsync() { + var guildId = (ulong)Parameters[0]!; + var code = (string)Parameters[1]!; + await using var repository = DatabaseBuilder.CreateRepository(); - var invite = await repository.Invite.FindInviteByCodeAsync(guildId, code); - if (invite == null) - throw new NotFoundException(Texts["Invite/NotFound", ApiContext.Language].FormatWith(code)); + var invite = await repository.Invite.FindInviteByCodeAsync(guildId, code) + ?? throw new NotFoundException(Texts["Invite/NotFound", ApiContext.Language].FormatWith(code)); var users = await repository.GuildUser.FindUsersWithInviteCode(guildId, code); - foreach (var user in users) user.UsedInviteCode = null; + foreach (var user in users) + user.UsedInviteCode = null; repository.Remove(invite); await repository.CommitAsync(); + + return ApiResult.Ok(); } } diff --git a/src/GrillBot.App/Actions/Api/V1/Invite/GetInviteList.cs b/src/GrillBot.App/Actions/Api/V1/Invite/GetInviteList.cs index 20236dbc5..ddc7c1d77 100644 --- a/src/GrillBot.App/Actions/Api/V1/Invite/GetInviteList.cs +++ b/src/GrillBot.App/Actions/Api/V1/Invite/GetInviteList.cs @@ -1,5 +1,6 @@ using AutoMapper; using GrillBot.Common.Models; +using GrillBot.Core.Infrastructure.Actions; using GrillBot.Core.Models.Pagination; using GrillBot.Data.Models.API.Invites; @@ -16,11 +17,15 @@ public GetInviteList(ApiRequestContext apiContext, GrillBotDatabaseBuilder datab Mapper = mapper; } - public async Task> ProcessAsync(GetInviteListParams parameters) + public override async Task ProcessAsync() { + var parameters = (GetInviteListParams)Parameters[0]!; + await using var repository = DatabaseBuilder.CreateRepository(); var data = await repository.Invite.GetInviteListAsync(parameters, parameters.Pagination); - return await PaginatedResponse.CopyAndMapAsync(data, entity => Task.FromResult(Mapper.Map(entity))); + var result = await PaginatedResponse.CopyAndMapAsync(data, entity => Task.FromResult(Mapper.Map(entity))); + + return ApiResult.Ok(result); } } diff --git a/src/GrillBot.App/Actions/Api/V1/Invite/GetMetadataCount.cs b/src/GrillBot.App/Actions/Api/V1/Invite/GetMetadataCount.cs index a4fcb199c..cb2d92be5 100644 --- a/src/GrillBot.App/Actions/Api/V1/Invite/GetMetadataCount.cs +++ b/src/GrillBot.App/Actions/Api/V1/Invite/GetMetadataCount.cs @@ -1,5 +1,6 @@ using GrillBot.Cache.Services.Managers; using GrillBot.Common.Models; +using GrillBot.Core.Infrastructure.Actions; namespace GrillBot.App.Actions.Api.V1.Invite; @@ -12,6 +13,9 @@ public GetMetadataCount(ApiRequestContext apiContext, InviteManager inviteManage InviteManager = inviteManager; } - public async Task ProcessAsync() - => await InviteManager.GetMetadataCountAsync(); + public override async Task ProcessAsync() + { + var result = await InviteManager.GetMetadataCountAsync(); + return ApiResult.Ok(result); + } } diff --git a/src/GrillBot.App/Actions/Api/V1/Invite/RefreshMetadata.cs b/src/GrillBot.App/Actions/Api/V1/Invite/RefreshMetadata.cs index e29de9eef..6fc1b84aa 100644 --- a/src/GrillBot.App/Actions/Api/V1/Invite/RefreshMetadata.cs +++ b/src/GrillBot.App/Actions/Api/V1/Invite/RefreshMetadata.cs @@ -1,6 +1,7 @@ using GrillBot.Cache.Services.Managers; using GrillBot.Common.Extensions.Discord; using GrillBot.Common.Models; +using GrillBot.Core.Infrastructure.Actions; using GrillBot.Core.Services.AuditLog; using GrillBot.Core.Services.AuditLog.Enums; using GrillBot.Core.Services.AuditLog.Models.Request.CreateItems; @@ -20,6 +21,14 @@ public RefreshMetadata(ApiRequestContext apiContext, IDiscordClient discordClien AuditLogServiceClient = auditLogServiceClient; } + public override async Task ProcessAsync() + { + var isReload = (bool)Parameters[0]!; + var result = await ProcessAsync(isReload); + + return ApiResult.Ok(result); + } + public async Task> ProcessAsync(bool isReload) { var result = new Dictionary(); diff --git a/src/GrillBot.App/Actions/Api/V1/Points/ComputeUserPoints.cs b/src/GrillBot.App/Actions/Api/V1/Points/ComputeUserPoints.cs index 586100458..493fef45b 100644 --- a/src/GrillBot.App/Actions/Api/V1/Points/ComputeUserPoints.cs +++ b/src/GrillBot.App/Actions/Api/V1/Points/ComputeUserPoints.cs @@ -7,6 +7,7 @@ using GrillBot.Data.Models.API.Users; using GrillBot.Database.Enums.Internal; using GrillBot.Database.Services.Repository; +using GrillBot.Core.Infrastructure.Actions; namespace GrillBot.App.Actions.Api.V1.Points; @@ -26,8 +27,9 @@ public ComputeUserPoints(ApiRequestContext apiContext, GrillBotDatabaseBuilder d PointsServiceClient = pointsServiceClient; } - public async Task> ProcessAsync(ulong? userId) + public override async Task ProcessAsync() { + var userId = Parameters.OfType().FirstOrDefault(); userId ??= ApiContext.GetUserId(); var result = new List(); @@ -39,7 +41,7 @@ public async Task> ProcessAsync(ulong? userId) result.Add(await TransformStatusAsync(repository, guildId, userId.Value, status)); } - return result; + return ApiResult.Ok(result); } private async Task TransformStatusAsync(GrillBotRepository repository, ulong guildId, ulong userId, PointsStatus status) diff --git a/src/GrillBot.App/Actions/Api/V1/Points/GetPointsLeaderboard.cs b/src/GrillBot.App/Actions/Api/V1/Points/GetPointsLeaderboard.cs index 0f69f4e45..aac87ea3f 100644 --- a/src/GrillBot.App/Actions/Api/V1/Points/GetPointsLeaderboard.cs +++ b/src/GrillBot.App/Actions/Api/V1/Points/GetPointsLeaderboard.cs @@ -5,6 +5,7 @@ using GrillBot.Core.Extensions; using GrillBot.Data.Models.API.Users; using GrillBot.Core.Services.PointsService.Enums; +using GrillBot.Core.Infrastructure.Actions; namespace GrillBot.App.Actions.Api.V1.Points; @@ -24,7 +25,7 @@ public GetPointsLeaderboard(ApiRequestContext apiContext, IDiscordClient discord PointsServiceClient = pointsServiceClient; } - public async Task> ProcessAsync() + public override async Task ProcessAsync() { var result = new List(); await using var repository = DatabaseBuilder.CreateRepository(); @@ -59,8 +60,9 @@ public async Task> ProcessAsync() } } - return result + result = result .OrderByDescending(o => o.PointsYearBack) .ToList(); + return ApiResult.Ok(result); } } diff --git a/src/GrillBot.App/Actions/Api/V1/Points/GetTransactionList.cs b/src/GrillBot.App/Actions/Api/V1/Points/GetTransactionList.cs index c7b64312c..c57a50dc3 100644 --- a/src/GrillBot.App/Actions/Api/V1/Points/GetTransactionList.cs +++ b/src/GrillBot.App/Actions/Api/V1/Points/GetTransactionList.cs @@ -1,6 +1,7 @@ using AutoMapper; using GrillBot.Common.Models; using GrillBot.Core.Extensions; +using GrillBot.Core.Infrastructure.Actions; using GrillBot.Core.Models.Pagination; using GrillBot.Core.Services.PointsService; using GrillBot.Core.Services.PointsService.Models; @@ -22,8 +23,10 @@ public GetTransactionList(ApiRequestContext apiContext, GrillBotDatabaseBuilder PointsServiceClient = pointsServiceClient; } - public async Task> ProcessAsync(AdminListRequest request) + public override async Task ProcessAsync() { + var request = (AdminListRequest)Parameters[0]!; + var transactions = await PointsServiceClient.GetTransactionListAsync(request); transactions.ValidationErrors.AggregateAndThrow(); @@ -31,7 +34,8 @@ public async Task> ProcessAsync(AdminListRe var userCache = new Dictionary(); await using var repository = DatabaseBuilder.CreateRepository(); - return await PaginatedResponse.CopyAndMapAsync(transactions.Response!, async entity => + + var result = await PaginatedResponse.CopyAndMapAsync(transactions.Response!, async entity => { if (!guildCache.TryGetValue(entity.GuildId, out var guild)) { @@ -70,5 +74,7 @@ public async Task> ProcessAsync(AdminListRe User = user }; }); + + return ApiResult.Ok(result); } } diff --git a/src/GrillBot.App/Actions/Api/V1/Points/GetUserList.cs b/src/GrillBot.App/Actions/Api/V1/Points/GetUserList.cs index 2f56e0a1d..f4dc694eb 100644 --- a/src/GrillBot.App/Actions/Api/V1/Points/GetUserList.cs +++ b/src/GrillBot.App/Actions/Api/V1/Points/GetUserList.cs @@ -1,6 +1,7 @@ using AutoMapper; using GrillBot.Common.Models; using GrillBot.Core.Extensions; +using GrillBot.Core.Infrastructure.Actions; using GrillBot.Core.Models.Pagination; using GrillBot.Core.Services.PointsService; using GrillBot.Core.Services.PointsService.Models.Users; @@ -25,8 +26,9 @@ public GetUserList(ApiRequestContext apiContext, IPointsServiceClient pointsServ Mapper = mapper; } - public async Task> ProcessAsync(UserListRequest request) + public override async Task ProcessAsync() { + var request = (UserListRequest)Parameters[0]!; var userList = await PointsServiceClient.GetUserListAsync(request); await using var repository = DatabaseBuilder.CreateRepository(); @@ -40,7 +42,9 @@ public GetUserList(ApiRequestContext apiContext, IPointsServiceClient pointsServ foreach (var guild in guilds) CachedGuilds.Add(guild.Id, Mapper.Map(guild)); - return await PaginatedResponse.CopyAndMapAsync(userList, entity => MapItemAsync(entity, repository)); + var result = await PaginatedResponse.CopyAndMapAsync(userList, entity => MapItemAsync(entity, repository)); + + return ApiResult.Ok(result); } private async Task MapItemAsync(UserListItem item, GrillBotRepository repository) diff --git a/src/GrillBot.App/Actions/Api/V1/Points/ServiceIncrementPoints.cs b/src/GrillBot.App/Actions/Api/V1/Points/ServiceIncrementPoints.cs index 3cd4e8a7b..70991b45a 100644 --- a/src/GrillBot.App/Actions/Api/V1/Points/ServiceIncrementPoints.cs +++ b/src/GrillBot.App/Actions/Api/V1/Points/ServiceIncrementPoints.cs @@ -6,6 +6,7 @@ using GrillBot.Core.Services.PointsService.Models; using GrillBot.Core.Exceptions; using Microsoft.AspNetCore.Mvc; +using GrillBot.Core.Infrastructure.Actions; namespace GrillBot.App.Actions.Api.V1.Points; @@ -28,10 +29,15 @@ public ServiceIncrementPoints(ApiRequestContext apiContext, IDiscordClient disco PointsServiceClient = pointsServiceClient; } - public async Task ProcessAsync(ulong guildId, ulong userId, int amount) + public override async Task ProcessAsync() { + var guildId = (ulong)Parameters[0]!; + var userId = (ulong)Parameters[1]!; + var amount = (int)Parameters[2]!; + await InitAsync(guildId, userId); - if (Guild is null || User is null) return; + if (Guild is null || User is null) + return ApiResult.Ok(); var request = new AdminTransactionRequest { @@ -50,6 +56,7 @@ public async Task ProcessAsync(ulong guildId, ulong userId, int amount) var exception = ConvertValidationErrorsToException(validationErrors); if (exception is not null) throw exception; + return ApiResult.Ok(); } private async Task InitAsync(ulong guildId, ulong userId) @@ -70,6 +77,6 @@ private async Task InitAsync(ulong guildId, ulong userId) return new ValidationException(Texts["Points/Service/Increment/NotAcceptable", ApiContext.Language]); var error = details.Errors.First(); - return new ValidationException(error.Value.First()).ToBadRequestValidation(null, error.Key); + return new ValidationException(error.Value[0]).ToBadRequestValidation(null, error.Key); } } diff --git a/src/GrillBot.App/Actions/Api/V1/Points/ServiceTransferPoints.cs b/src/GrillBot.App/Actions/Api/V1/Points/ServiceTransferPoints.cs index 0eb057178..202ad938c 100644 --- a/src/GrillBot.App/Actions/Api/V1/Points/ServiceTransferPoints.cs +++ b/src/GrillBot.App/Actions/Api/V1/Points/ServiceTransferPoints.cs @@ -6,6 +6,7 @@ using GrillBot.Core.Services.PointsService.Models; using GrillBot.Core.Exceptions; using Microsoft.AspNetCore.Mvc; +using GrillBot.Core.Infrastructure.Actions; namespace GrillBot.App.Actions.Api.V1.Points; @@ -27,8 +28,13 @@ public ServiceTransferPoints(ApiRequestContext apiContext, IDiscordClient discor PointsServiceClient = pointsServiceClient; } - public async Task ProcessAsync(ulong guildId, ulong fromUserId, ulong toUserId, int amount) + public override async Task ProcessAsync() { + var guildId = (ulong)Parameters[0]!; + var fromUserId = (ulong)Parameters[1]!; + var toUserId = (ulong)Parameters[2]!; + var amount = (int)Parameters[3]!; + var (from, to) = await GetAndCheckUsersAsync(guildId, fromUserId, toUserId); var request = new TransferPointsRequest @@ -49,6 +55,7 @@ public async Task ProcessAsync(ulong guildId, ulong fromUserId, ulong toUserId, var exception = ConvertValidationErrorsToException(validationErrors); if (exception is not null) throw exception; + return ApiResult.Ok(); } private async Task<(IGuildUser from, IGuildUser to)> GetAndCheckUsersAsync(ulong guildId, ulong fromUserId, ulong toUserId) @@ -71,11 +78,8 @@ await CheckUserAsync(toUserId, false) private async Task CheckUserAsync(ulong userId, bool isSource) { - var user = await Guild.GetUserAsync(userId); - - if (user is null) - throw new NotFoundException(Texts[$"Points/Service/Transfer/{(isSource ? "SourceUserNotFound" : "DestUserNotFound")}", ApiContext.Language]); - return user; + return await Guild.GetUserAsync(userId) + ?? throw new NotFoundException(Texts[$"Points/Service/Transfer/{(isSource ? "SourceUserNotFound" : "DestUserNotFound")}", ApiContext.Language]); } private Exception? ConvertValidationErrorsToException(ValidationProblemDetails? details) @@ -85,11 +89,11 @@ private async Task CheckUserAsync(ulong userId, bool isSource) var error = details.Errors.First(); - if (error.Key.ToLower() == "Amount" && error.Value.First() == "NotEnoughPoints") + if (error.Key.ToLower() == "amount" && error.Value[0] == "NotEnoughPoints") return new ValidationException(Texts["Points/Service/Transfer/InsufficientAmount", ApiContext.Language]); - if (error.Value.First() == "User is bot.") + if (error.Value[0] == "User is bot.") new ValidationException(Texts["Points/Service/Transfer/UserIsBot", ApiContext.Language]).ToBadRequestValidation(null, error.Key); - return new ValidationException(error.Value.First()).ToBadRequestValidation(null, error.Key); + return new ValidationException(error.Value[0]).ToBadRequestValidation(null, error.Key); } } diff --git a/src/GrillBot.App/Actions/Api/V1/PublicApiClients/CreateClient.cs b/src/GrillBot.App/Actions/Api/V1/PublicApiClients/CreateClient.cs index 4bb61d39c..7e1929370 100644 --- a/src/GrillBot.App/Actions/Api/V1/PublicApiClients/CreateClient.cs +++ b/src/GrillBot.App/Actions/Api/V1/PublicApiClients/CreateClient.cs @@ -1,4 +1,5 @@ using GrillBot.Common.Models; +using GrillBot.Core.Infrastructure.Actions; using GrillBot.Data.Models.API.ApiClients; using GrillBot.Database.Entity; @@ -13,8 +14,10 @@ public CreateClient(ApiRequestContext apiContext, GrillBotDatabaseBuilder databa DatabaseBuilder = databaseBuilder; } - public async Task ProcessAsync(ApiClientParams parameters) + public override async Task ProcessAsync() { + var parameters = (ApiClientParams)Parameters[0]!; + var entity = new ApiClient { Id = Guid.NewGuid().ToString(), @@ -27,5 +30,6 @@ public async Task ProcessAsync(ApiClientParams parameters) await repository.AddAsync(entity); await repository.CommitAsync(); + return ApiResult.Ok(); } } diff --git a/src/GrillBot.App/Actions/Api/V1/PublicApiClients/DeleteClient.cs b/src/GrillBot.App/Actions/Api/V1/PublicApiClients/DeleteClient.cs index 7c63e6f09..6dbf55a11 100644 --- a/src/GrillBot.App/Actions/Api/V1/PublicApiClients/DeleteClient.cs +++ b/src/GrillBot.App/Actions/Api/V1/PublicApiClients/DeleteClient.cs @@ -1,6 +1,7 @@ using GrillBot.Common.Managers.Localization; using GrillBot.Common.Models; using GrillBot.Core.Exceptions; +using GrillBot.Core.Infrastructure.Actions; namespace GrillBot.App.Actions.Api.V1.PublicApiClients; @@ -15,15 +16,17 @@ public DeleteClient(ApiRequestContext apiContext, GrillBotDatabaseBuilder databa Texts = texts; } - public async Task ProcessAsync(string id) + public override async Task ProcessAsync() { + var id = (string)Parameters[0]!; + await using var repository = DatabaseBuilder.CreateRepository(); - var apiClient = await repository.ApiClientRepository.FindClientById(id); - if (apiClient == null) - throw new NotFoundException(Texts["PublicApiClients/NotFound", ApiContext.Language]); + var apiClient = await repository.ApiClientRepository.FindClientById(id) + ?? throw new NotFoundException(Texts["PublicApiClients/NotFound", ApiContext.Language]); repository.Remove(apiClient); await repository.CommitAsync(); + return ApiResult.Ok(); } } diff --git a/src/GrillBot.App/Actions/Api/V1/PublicApiClients/GetClient.cs b/src/GrillBot.App/Actions/Api/V1/PublicApiClients/GetClient.cs index 9762ecd65..7966b0080 100644 --- a/src/GrillBot.App/Actions/Api/V1/PublicApiClients/GetClient.cs +++ b/src/GrillBot.App/Actions/Api/V1/PublicApiClients/GetClient.cs @@ -1,5 +1,6 @@ using GrillBot.Common.Models; using GrillBot.Core.Exceptions; +using GrillBot.Core.Infrastructure.Actions; using GrillBot.Database.Entity; namespace GrillBot.App.Actions.Api.V1.PublicApiClients; @@ -7,17 +8,21 @@ namespace GrillBot.App.Actions.Api.V1.PublicApiClients; public class GetClient : ApiAction { private GrillBotDatabaseBuilder DatabaseBuilder { get; } - + public GetClient(ApiRequestContext apiContext, GrillBotDatabaseBuilder databaseBuilder) : base(apiContext) { DatabaseBuilder = databaseBuilder; } - public async Task ProcessAsync(string clientId) + public override async Task ProcessAsync() { + var clientId = (string)Parameters[0]!; + await using var repository = DatabaseBuilder.CreateRepository(); - var client = await repository.ApiClientRepository.FindClientById(clientId); - return client ?? throw new NotFoundException(); + var client = await repository.ApiClientRepository.FindClientById(clientId) + ?? throw new NotFoundException(); + + return ApiResult.Ok(client); } } diff --git a/src/GrillBot.App/Actions/Api/V1/PublicApiClients/GetClientsList.cs b/src/GrillBot.App/Actions/Api/V1/PublicApiClients/GetClientsList.cs index 86215d244..1c197c73a 100644 --- a/src/GrillBot.App/Actions/Api/V1/PublicApiClients/GetClientsList.cs +++ b/src/GrillBot.App/Actions/Api/V1/PublicApiClients/GetClientsList.cs @@ -1,5 +1,5 @@ using GrillBot.Common.Models; -using GrillBot.Database.Entity; +using GrillBot.Core.Infrastructure.Actions; namespace GrillBot.App.Actions.Api.V1.PublicApiClients; @@ -12,9 +12,11 @@ public GetClientsList(ApiRequestContext apiContext, GrillBotDatabaseBuilder data DatabaseBuilder = databaseBuilder; } - public async Task> ProcessAsync() + public override async Task ProcessAsync() { await using var repository = DatabaseBuilder.CreateRepository(); - return await repository.ApiClientRepository.GetClientsAsync(); + var result = await repository.ApiClientRepository.GetClientsAsync(); + + return ApiResult.Ok(result); } } diff --git a/src/GrillBot.App/Actions/Api/V1/PublicApiClients/GetPublicApiMethods.cs b/src/GrillBot.App/Actions/Api/V1/PublicApiClients/GetPublicApiMethods.cs index 526128707..928dd3c38 100644 --- a/src/GrillBot.App/Actions/Api/V1/PublicApiClients/GetPublicApiMethods.cs +++ b/src/GrillBot.App/Actions/Api/V1/PublicApiClients/GetPublicApiMethods.cs @@ -1,5 +1,6 @@ using System.Reflection; using GrillBot.Common.Models; +using GrillBot.Core.Infrastructure.Actions; using Microsoft.AspNetCore.Mvc; namespace GrillBot.App.Actions.Api.V1.PublicApiClients; @@ -10,11 +11,13 @@ public GetPublicApiMethods(ApiRequestContext apiContext) : base(apiContext) { } - public List Process() + public override Task ProcessAsync() { - return GetMethods() + var result = GetMethods() .Select(o => $"{o.DeclaringType!.Name}.{o.Name}") .ToList(); + + return Task.FromResult(ApiResult.Ok(result)); } private IEnumerable GetMethods() diff --git a/src/GrillBot.App/Actions/Api/V1/PublicApiClients/UpdateClient.cs b/src/GrillBot.App/Actions/Api/V1/PublicApiClients/UpdateClient.cs index 5c03cca14..ed6401262 100644 --- a/src/GrillBot.App/Actions/Api/V1/PublicApiClients/UpdateClient.cs +++ b/src/GrillBot.App/Actions/Api/V1/PublicApiClients/UpdateClient.cs @@ -1,6 +1,7 @@ using GrillBot.Common.Managers.Localization; using GrillBot.Common.Models; using GrillBot.Core.Exceptions; +using GrillBot.Core.Infrastructure.Actions; using GrillBot.Data.Models.API.ApiClients; namespace GrillBot.App.Actions.Api.V1.PublicApiClients; @@ -16,17 +17,20 @@ public UpdateClient(ApiRequestContext apiContext, GrillBotDatabaseBuilder databa Texts = texts; } - public async Task ProcessAsync(string id, ApiClientParams parameters) + public override async Task ProcessAsync() { + var id = (string)Parameters[0]!; + var parameters = (ApiClientParams)Parameters[1]!; + await using var repository = DatabaseBuilder.CreateRepository(); - var apiClient = await repository.ApiClientRepository.FindClientById(id); - if (apiClient == null) - throw new NotFoundException(Texts["PublicApiClients/NotFound", ApiContext.Language]); + var apiClient = await repository.ApiClientRepository.FindClientById(id) + ?? throw new NotFoundException(Texts["PublicApiClients/NotFound", ApiContext.Language]); apiClient.AllowedMethods = parameters.AllowedMethods; apiClient.Name = parameters.Name; apiClient.Disabled = parameters.Disabled; await repository.CommitAsync(); + return ApiResult.Ok(); } } diff --git a/src/GrillBot.App/Actions/Api/V1/Reminder/FinishRemind.cs b/src/GrillBot.App/Actions/Api/V1/Reminder/FinishRemind.cs index 3c6bbbd22..4fdec3beb 100644 --- a/src/GrillBot.App/Actions/Api/V1/Reminder/FinishRemind.cs +++ b/src/GrillBot.App/Actions/Api/V1/Reminder/FinishRemind.cs @@ -2,10 +2,13 @@ using GrillBot.Common.Managers.Localization; using GrillBot.Common.Models; using GrillBot.Core.Exceptions; +using GrillBot.Core.Infrastructure.Actions; using GrillBot.Core.Services.AuditLog; using GrillBot.Core.Services.AuditLog.Enums; using GrillBot.Core.Services.AuditLog.Models.Request.CreateItems; +using GrillBot.Data.Models.API; using GrillBot.Database.Entity; +using Microsoft.AspNetCore.Http; namespace GrillBot.App.Actions.Api.V1.Reminder; @@ -43,6 +46,16 @@ public void ResetState() Remind = null; } + public override async Task ProcessAsync() + { + var id = (long)Parameters[0]!; + var notify = (bool)Parameters[1]!; + var isService = (bool)Parameters[2]!; + + await ProcessAsync(id, notify, isService); + return IsGone ? new ApiResult(StatusCodes.Status410Gone, new MessageResponse(ErrorMessage!)) : ApiResult.Ok(); + } + public async Task ProcessAsync(long id, bool notify, bool isService) { await using var repository = DatabaseBuilder.CreateRepository(); diff --git a/src/GrillBot.App/Actions/Api/V1/Reminder/GetReminderList.cs b/src/GrillBot.App/Actions/Api/V1/Reminder/GetReminderList.cs index 5fa995665..045cc1753 100644 --- a/src/GrillBot.App/Actions/Api/V1/Reminder/GetReminderList.cs +++ b/src/GrillBot.App/Actions/Api/V1/Reminder/GetReminderList.cs @@ -1,5 +1,6 @@ using AutoMapper; using GrillBot.Common.Models; +using GrillBot.Core.Infrastructure.Actions; using GrillBot.Core.Models.Pagination; using GrillBot.Data.Models.API.Reminder; @@ -16,6 +17,14 @@ public GetReminderList(ApiRequestContext apiContext, GrillBotDatabaseBuilder dat Mapper = mapper; } + public override async Task ProcessAsync() + { + var parameters = (GetReminderListParams)Parameters[0]!; + var result = await ProcessAsync(parameters); + + return ApiResult.Ok(result); + } + public async Task> ProcessAsync(GetReminderListParams parameters) { CheckAndSetPublicAccess(parameters); diff --git a/src/GrillBot.App/Actions/Api/V1/ScheduledJobs/GetScheduledJobs.cs b/src/GrillBot.App/Actions/Api/V1/ScheduledJobs/GetScheduledJobs.cs index 60aff5908..37f618a9e 100644 --- a/src/GrillBot.App/Actions/Api/V1/ScheduledJobs/GetScheduledJobs.cs +++ b/src/GrillBot.App/Actions/Api/V1/ScheduledJobs/GetScheduledJobs.cs @@ -1,5 +1,6 @@ using GrillBot.Cache.Services.Managers; using GrillBot.Common.Models; +using GrillBot.Core.Infrastructure.Actions; using GrillBot.Core.Services.AuditLog; using GrillBot.Core.Services.AuditLog.Models.Response.Info; using GrillBot.Data.Models.API.Jobs; @@ -21,7 +22,7 @@ public GetScheduledJobs(ApiRequestContext apiContext, ISchedulerFactory schedule AuditLogServiceClient = auditLogServiceClient; } - public async Task> ProcessAsync() + public override async Task ProcessAsync() { var jobInfos = await AuditLogServiceClient.GetJobsInfoAsync(); var scheduler = await SchedulerFactory.GetScheduler(); @@ -36,7 +37,7 @@ public async Task> ProcessAsync() result.Add(await GetJobAsync(jobKey, jobInfo, runningJobs, scheduler, disabledJobs)); } - return result; + return ApiResult.Ok(result); } private static async Task GetJobAsync(JobKey key, JobInfo jobInfo, IEnumerable runningJobs, IScheduler scheduler, ICollection disabledJobs) diff --git a/src/GrillBot.App/Actions/Api/V1/ScheduledJobs/RunScheduledJob.cs b/src/GrillBot.App/Actions/Api/V1/ScheduledJobs/RunScheduledJob.cs index fc3eb7ad4..7f06a6f0f 100644 --- a/src/GrillBot.App/Actions/Api/V1/ScheduledJobs/RunScheduledJob.cs +++ b/src/GrillBot.App/Actions/Api/V1/ScheduledJobs/RunScheduledJob.cs @@ -1,4 +1,5 @@ using GrillBot.Common.Models; +using GrillBot.Core.Infrastructure.Actions; using Quartz; namespace GrillBot.App.Actions.Api.V1.ScheduledJobs; @@ -12,13 +13,15 @@ public RunScheduledJob(ApiRequestContext apiContext, ISchedulerFactory scheduler SchedulerFactory = schedulerFactory; } - public async Task ProcessAsync(string name) + public override async Task ProcessAsync() { + var name = (string)Parameters[0]!; var scheduler = await SchedulerFactory.GetScheduler(); var jobData = new JobDataMap(); jobData.Put("User", ApiContext.LoggedUser!); - + await scheduler.TriggerJob(JobKey.Create(name), jobData); + return ApiResult.Ok(); } } diff --git a/src/GrillBot.App/Actions/Api/V1/ScheduledJobs/UpdateJob.cs b/src/GrillBot.App/Actions/Api/V1/ScheduledJobs/UpdateJob.cs index 5509d7bdc..d5997e097 100644 --- a/src/GrillBot.App/Actions/Api/V1/ScheduledJobs/UpdateJob.cs +++ b/src/GrillBot.App/Actions/Api/V1/ScheduledJobs/UpdateJob.cs @@ -2,6 +2,7 @@ using GrillBot.Common.Managers.Localization; using GrillBot.Common.Models; using GrillBot.Core.Exceptions; +using GrillBot.Core.Infrastructure.Actions; using Quartz; using Quartz.Impl.Matchers; @@ -20,20 +21,26 @@ public UpdateJob(ApiRequestContext apiContext, DataCacheManager dataCacheManager Texts = texts; } - public async Task ProcessAsync(string name, bool enabled) + public override async Task ProcessAsync() { + var name = (string)Parameters[0]!; + var enabled = (bool)Parameters[1]!; + await ValidateJobAsync(name); var data = await DataCacheManager.GetValueAsync("DisabledJobs"); if (string.IsNullOrEmpty(data)) data = "[]"; var disabledJobs = JsonConvert.DeserializeObject>(data)!; - if (enabled) disabledJobs.Remove(name); - else disabledJobs.Add(name); + if (enabled) + disabledJobs.Remove(name); + else + disabledJobs.Add(name); var newData = JsonConvert.SerializeObject(disabledJobs, Formatting.None); if (data != newData) await DataCacheManager.SetValueAsync("DisabledJobs", newData, DateTime.MaxValue); + return ApiResult.Ok(); } private async Task ValidateJobAsync(string name) diff --git a/src/GrillBot.App/Actions/Api/V1/Searching/GetSearchingList.cs b/src/GrillBot.App/Actions/Api/V1/Searching/GetSearchingList.cs index 952d043f5..b05db69bd 100644 --- a/src/GrillBot.App/Actions/Api/V1/Searching/GetSearchingList.cs +++ b/src/GrillBot.App/Actions/Api/V1/Searching/GetSearchingList.cs @@ -1,6 +1,7 @@ using AutoMapper; using GrillBot.Common.Extensions.Discord; using GrillBot.Common.Models; +using GrillBot.Core.Infrastructure.Actions; using GrillBot.Core.Models.Pagination; using GrillBot.Data.Models.API.Searching; @@ -19,6 +20,14 @@ public GetSearchingList(ApiRequestContext apiContext, IDiscordClient discordClie Mapper = mapper; } + public override async Task ProcessAsync() + { + var parameters = (GetSearchingListParams)Parameters[0]!; + var result = await ProcessAsync(parameters); + + return ApiResult.Ok(result); + } + public async Task> ProcessAsync(GetSearchingListParams parameters) { var mutualGuilds = await GetMutualGuildsAsync(); diff --git a/src/GrillBot.App/Actions/Api/V1/Searching/RemoveSearches.cs b/src/GrillBot.App/Actions/Api/V1/Searching/RemoveSearches.cs index 72fe00519..74e14a5a2 100644 --- a/src/GrillBot.App/Actions/Api/V1/Searching/RemoveSearches.cs +++ b/src/GrillBot.App/Actions/Api/V1/Searching/RemoveSearches.cs @@ -1,4 +1,5 @@ using GrillBot.Common.Models; +using GrillBot.Core.Infrastructure.Actions; namespace GrillBot.App.Actions.Api.V1.Searching; @@ -11,14 +12,17 @@ public RemoveSearches(ApiRequestContext apiContext, GrillBotDatabaseBuilder data DatabaseBuilder = databaseBuilder; } - public async Task ProcessAsync(IEnumerable ids) + public override async Task ProcessAsync() { + var ids = (long[])Parameters[0]!; await using var repository = DatabaseBuilder.CreateRepository(); var searches = await repository.Searching.FindSearchesByIdsAsync(ids); - if (searches.Count == 0) return; + if (searches.Count == 0) + return ApiResult.Ok(); repository.RemoveCollection(searches); await repository.CommitAsync(); + return ApiResult.Ok(); } } diff --git a/src/GrillBot.App/Actions/Api/V1/Services/GetServiceInfo.cs b/src/GrillBot.App/Actions/Api/V1/Services/GetServiceInfo.cs index dcfc81579..aa37b41cb 100644 --- a/src/GrillBot.App/Actions/Api/V1/Services/GetServiceInfo.cs +++ b/src/GrillBot.App/Actions/Api/V1/Services/GetServiceInfo.cs @@ -1,5 +1,6 @@ using GrillBot.Common.Managers.Logging; using GrillBot.Common.Models; +using GrillBot.Core.Infrastructure.Actions; using GrillBot.Core.Services.AuditLog; using GrillBot.Core.Services.Common; using GrillBot.Core.Services.Diagnostics.Models; @@ -8,31 +9,24 @@ using GrillBot.Core.Services.PointsService; using GrillBot.Core.Services.RubbergodService; using GrillBot.Data.Models.API.Services; +using Microsoft.Extensions.DependencyInjection; namespace GrillBot.App.Actions.Api.V1.Services; public class GetServiceInfo : ApiAction { - private IGraphicsClient GraphicsClient { get; } - private IRubbergodServiceClient RubbergodServiceClient { get; } private LoggingManager LoggingManager { get; } - private IPointsServiceClient PointsServiceClient { get; } - private IImageProcessingClient ImageProcessingClient { get; } - private IAuditLogServiceClient AuditLogServiceClient { get; } + private IServiceProvider ServiceProvider { get; } - public GetServiceInfo(ApiRequestContext apiContext, IGraphicsClient graphicsClient, IRubbergodServiceClient rubbergodServiceClient, LoggingManager loggingManager, IPointsServiceClient pointsServiceClient, - IImageProcessingClient imageProcessingClient, IAuditLogServiceClient auditLogServiceClient) : base(apiContext) + public GetServiceInfo(ApiRequestContext apiContext, LoggingManager loggingManager, IServiceProvider serviceProvider) : base(apiContext) { - GraphicsClient = graphicsClient; - RubbergodServiceClient = rubbergodServiceClient; LoggingManager = loggingManager; - PointsServiceClient = pointsServiceClient; - ImageProcessingClient = imageProcessingClient; - AuditLogServiceClient = auditLogServiceClient; + ServiceProvider = serviceProvider; } - public async Task ProcessAsync(string id) + public override async Task ProcessAsync() { + var id = (string)Parameters[0]!; var client = PickClient(id); var info = new ServiceInfo @@ -42,18 +36,18 @@ public async Task ProcessAsync(string id) }; await SetDiagnosticsInfo(info, client); - return info; + return ApiResult.Ok(info); } private IClient PickClient(string id) { return id switch { - "rubbergod" => RubbergodServiceClient, - "graphics" => GraphicsClient, - "points" => PointsServiceClient, - "image-processing" => ImageProcessingClient, - "audit-log" => AuditLogServiceClient, + "rubbergod" => ServiceProvider.GetRequiredService(), + "graphics" => ServiceProvider.GetRequiredService(), + "points" => ServiceProvider.GetRequiredService(), + "image-processing" => ServiceProvider.GetRequiredService(), + "audit-log" => ServiceProvider.GetRequiredService(), _ => throw new NotSupportedException($"Unsupported service {id}") }; } @@ -64,11 +58,11 @@ private async Task SetDiagnosticsInfo(ServiceInfo info, IClient client) { info.DiagnosticInfo = client switch { - IRubbergodServiceClient => await RubbergodServiceClient.GetDiagAsync(), - IGraphicsClient => await GetGraphicsServiceInfo(), - IPointsServiceClient => await PointsServiceClient.GetDiagAsync(), - IImageProcessingClient => await ImageProcessingClient.GetDiagAsync(), - IAuditLogServiceClient => await AuditLogServiceClient.GetDiagAsync(), + IRubbergodServiceClient rubbergodServiceClient => await rubbergodServiceClient.GetDiagAsync(), + IGraphicsClient graphicsClient => await GetGraphicsServiceInfo(graphicsClient), + IPointsServiceClient pointsServiceClient => await pointsServiceClient.GetDiagAsync(), + IImageProcessingClient imageProcessingClient => await imageProcessingClient.GetDiagAsync(), + IAuditLogServiceClient auditLogServiceClient => await auditLogServiceClient.GetDiagAsync(), _ => null }; } @@ -79,10 +73,10 @@ private async Task SetDiagnosticsInfo(ServiceInfo info, IClient client) } } - private async Task GetGraphicsServiceInfo() + private static async Task GetGraphicsServiceInfo(IGraphicsClient graphicsClient) { - var stats = await GraphicsClient.GetStatisticsAsync(); - var metrics = await GraphicsClient.GetMetricsAsync(); + var stats = await graphicsClient.GetStatisticsAsync(); + var metrics = await graphicsClient.GetMetricsAsync(); return new DiagnosticInfo { diff --git a/src/GrillBot.App/Actions/Api/V1/Statistics/GetApiUserStatistics.cs b/src/GrillBot.App/Actions/Api/V1/Statistics/GetApiUserStatistics.cs index de92ebb62..559ad5b8e 100644 --- a/src/GrillBot.App/Actions/Api/V1/Statistics/GetApiUserStatistics.cs +++ b/src/GrillBot.App/Actions/Api/V1/Statistics/GetApiUserStatistics.cs @@ -1,4 +1,5 @@ using GrillBot.Common.Models; +using GrillBot.Core.Infrastructure.Actions; using GrillBot.Core.Services.AuditLog; using GrillBot.Data.Models.API.Statistics; @@ -15,8 +16,9 @@ public GetApiUserStatistics(ApiRequestContext apiContext, GrillBotDatabaseBuilde AuditLogServiceClient = auditLogServiceClient; } - public async Task> ProcessAsync(string criteria) + public override async Task ProcessAsync() { + var criteria = (string)Parameters[0]!; var statistics = await AuditLogServiceClient.GetUserApiStatisticsAsync(criteria); var userIds = statistics .Select(o => ulong.TryParse(o.UserId, out _) ? o.UserId : "") @@ -28,11 +30,13 @@ public async Task> ProcessAsync(string criteria) var users = await repository.User.GetUsersByIdsAsync(userIds); var usernames = users.ToDictionary(o => o.Id, o => o.Username); - return statistics.Select(o => new UserActionCountItem + var result = statistics.Select(o => new UserActionCountItem { Username = usernames.TryGetValue(o.UserId, out var username) ? username : o.UserId, Count = o.Count, Action = o.Action }).OrderBy(o => o.Username).ThenBy(o => o.Action).ToList(); + + return ApiResult.Ok(result); } } diff --git a/src/GrillBot.App/Actions/Api/V1/Statistics/GetDatabaseStatus.cs b/src/GrillBot.App/Actions/Api/V1/Statistics/GetDatabaseStatus.cs index e850fffa4..507d6d0eb 100644 --- a/src/GrillBot.App/Actions/Api/V1/Statistics/GetDatabaseStatus.cs +++ b/src/GrillBot.App/Actions/Api/V1/Statistics/GetDatabaseStatus.cs @@ -1,5 +1,6 @@ using GrillBot.Cache.Services; using GrillBot.Common.Models; +using GrillBot.Core.Infrastructure.Actions; using GrillBot.Data.Models.API.Statistics; namespace GrillBot.App.Actions.Api.V1.Statistics; @@ -15,7 +16,7 @@ public GetDatabaseStatus(ApiRequestContext apiContext, GrillBotDatabaseBuilder d CacheBuilder = cacheBuilder; } - public async Task ProcessAsync() + public override async Task ProcessAsync() { var result = new DatabaseStatistics(); @@ -27,6 +28,6 @@ public async Task ProcessAsync() result.Cache = await cache.StatisticsRepository.GetTableStatisticsAsync(); result.Cache = result.Cache.OrderByDescending(o => o.Value).ToDictionary(o => o.Key, o => o.Value); - return result; + return ApiResult.Ok(result); } } diff --git a/src/GrillBot.App/Actions/Api/V1/Statistics/GetEventStatistics.cs b/src/GrillBot.App/Actions/Api/V1/Statistics/GetEventStatistics.cs index 2be55365c..e0825f3dc 100644 --- a/src/GrillBot.App/Actions/Api/V1/Statistics/GetEventStatistics.cs +++ b/src/GrillBot.App/Actions/Api/V1/Statistics/GetEventStatistics.cs @@ -1,5 +1,6 @@ using GrillBot.Common.Managers.Events; using GrillBot.Common.Models; +using GrillBot.Core.Infrastructure.Actions; namespace GrillBot.App.Actions.Api.V1.Statistics; @@ -12,6 +13,9 @@ public GetEventStatistics(ApiRequestContext apiContext, EventLogManager eventLog EventLogManager = eventLogManager; } - public Dictionary Process() - => EventLogManager.GetStatistics(); + public override Task ProcessAsync() + { + var result = EventLogManager.GetStatistics(); + return Task.FromResult(ApiResult.Ok(result)); + } } diff --git a/src/GrillBot.App/Actions/Api/V1/Statistics/GetOperationStats.cs b/src/GrillBot.App/Actions/Api/V1/Statistics/GetOperationStats.cs index b6893832a..2e04f18d9 100644 --- a/src/GrillBot.App/Actions/Api/V1/Statistics/GetOperationStats.cs +++ b/src/GrillBot.App/Actions/Api/V1/Statistics/GetOperationStats.cs @@ -1,4 +1,5 @@ using GrillBot.Common.Models; +using GrillBot.Core.Infrastructure.Actions; using GrillBot.Core.Managers.Performance; using GrillBot.Data.Models.API.Statistics; @@ -13,15 +14,16 @@ public GetOperationStats(ApiRequestContext apiContext, ICounterManager counterMa CounterManager = counterManager; } - public OperationStats Process() + public override Task ProcessAsync() { var statistics = CounterManager.GetStatistics(); - - return new OperationStats + var result = new OperationStats { CountChartData = statistics.ToDictionary(o => o.Section, o => o.Count), TimeChartData = statistics.ToDictionary(o => o.Section, o => o.AverageTime), Statistics = OperationCounterConverter.ComputeTree(statistics) }; + + return Task.FromResult(ApiResult.Ok(result)); } } diff --git a/src/GrillBot.App/Actions/Api/V1/Statistics/GetUnverifyStatistics.cs b/src/GrillBot.App/Actions/Api/V1/Statistics/GetUnverifyStatistics.cs index 7ba426569..63640ebcc 100644 --- a/src/GrillBot.App/Actions/Api/V1/Statistics/GetUnverifyStatistics.cs +++ b/src/GrillBot.App/Actions/Api/V1/Statistics/GetUnverifyStatistics.cs @@ -1,4 +1,5 @@ using GrillBot.Common.Models; +using GrillBot.Core.Infrastructure.Actions; using GrillBot.Database.Enums; namespace GrillBot.App.Actions.Api.V1.Statistics; @@ -12,7 +13,20 @@ public GetUnverifyStatistics(ApiRequestContext apiContext, GrillBotDatabaseBuild DatabaseBuilder = databaseBuilder; } - public async Task> ProcessByOperationAsync() + public override async Task ProcessAsync() + { + switch((string)Parameters[0]!) + { + case "ByOperation": + return ApiResult.Ok(await ProcessByOperationAsync()); + case "ByDate": + return ApiResult.Ok(await ProcessByDateAsync()); + default: + return ApiResult.BadRequest(); + } + } + + private async Task> ProcessByOperationAsync() { await using var repository = DatabaseBuilder.CreateRepository(); var statistics = await repository.Unverify.GetStatisticsByTypeAsync(); @@ -23,7 +37,7 @@ public async Task> ProcessByOperationAsync() .ToDictionary(o => o.Key, o => o.Value); } - public async Task> ProcessByDateAsync() + private async Task> ProcessByDateAsync() { await using var repository = DatabaseBuilder.CreateRepository(); return await repository.Unverify.GetStatisticsByDateAsync(); diff --git a/src/GrillBot.App/Actions/Api/V1/Statistics/GetUserCommandStatistics.cs b/src/GrillBot.App/Actions/Api/V1/Statistics/GetUserCommandStatistics.cs index fc17625e7..01407815c 100644 --- a/src/GrillBot.App/Actions/Api/V1/Statistics/GetUserCommandStatistics.cs +++ b/src/GrillBot.App/Actions/Api/V1/Statistics/GetUserCommandStatistics.cs @@ -1,4 +1,5 @@ using GrillBot.Common.Models; +using GrillBot.Core.Infrastructure.Actions; using GrillBot.Core.Services.AuditLog; using GrillBot.Data.Models.API.Statistics; @@ -15,7 +16,7 @@ public GetUserCommandStatistics(ApiRequestContext apiContext, GrillBotDatabaseBu AuditLogServiceClient = auditLogServiceClient; } - public async Task> ProcessAsync() + public override async Task ProcessAsync() { var statistics = await AuditLogServiceClient.GetUserCommandStatisticsAsync(); var userIds = statistics.Select(o => o.UserId).Distinct().ToList(); @@ -24,11 +25,13 @@ public async Task> ProcessAsync() var users = await repository.User.GetUsersByIdsAsync(userIds); var usernames = users.ToDictionary(o => o.Id, o => o.Username); - return statistics.Select(o => new UserActionCountItem + var result = statistics.Select(o => new UserActionCountItem { Username = usernames.TryGetValue(o.UserId, out var username) ? username : o.UserId, Action = o.Action, Count = o.Count }).OrderBy(o => o.Username).ThenBy(o => o.Action).ToList(); + + return ApiResult.Ok(result); } } diff --git a/src/GrillBot.App/Actions/Api/V1/System/ChangeBotStatus.cs b/src/GrillBot.App/Actions/Api/V1/System/ChangeBotStatus.cs index 94cc53429..823a3e3e8 100644 --- a/src/GrillBot.App/Actions/Api/V1/System/ChangeBotStatus.cs +++ b/src/GrillBot.App/Actions/Api/V1/System/ChangeBotStatus.cs @@ -1,5 +1,6 @@ using GrillBot.Common.Managers; using GrillBot.Common.Models; +using GrillBot.Core.Infrastructure.Actions; namespace GrillBot.App.Actions.Api.V1.System; @@ -12,5 +13,9 @@ public ChangeBotStatus(ApiRequestContext apiContext, InitManager initManager) : InitManager = initManager; } - public void Process(bool isActive) => InitManager.Set(isActive); + public override Task ProcessAsync() + { + InitManager.Set((bool)Parameters[0]!); + return Task.FromResult(ApiResult.Ok()); + } } diff --git a/src/GrillBot.App/Actions/Api/V1/System/GetEventLog.cs b/src/GrillBot.App/Actions/Api/V1/System/GetEventLog.cs index 825e7ff86..0a3d2de80 100644 --- a/src/GrillBot.App/Actions/Api/V1/System/GetEventLog.cs +++ b/src/GrillBot.App/Actions/Api/V1/System/GetEventLog.cs @@ -1,5 +1,6 @@ using GrillBot.Common.Managers.Events; using GrillBot.Common.Models; +using GrillBot.Core.Infrastructure.Actions; namespace GrillBot.App.Actions.Api.V1.System; @@ -12,5 +13,9 @@ public GetEventLog(ApiRequestContext apiContext, EventLogManager eventLogManager EventLogManager = eventLogManager; } - public string[] Process() => EventLogManager.GetEventLog(); + public override Task ProcessAsync() + { + var result = EventLogManager.GetEventLog(); + return Task.FromResult(ApiResult.Ok(result)); + } } diff --git a/src/GrillBot.App/Actions/Api/V1/Unverify/AddKeepables.cs b/src/GrillBot.App/Actions/Api/V1/Unverify/AddKeepables.cs index d379a9acd..9093b91fe 100644 --- a/src/GrillBot.App/Actions/Api/V1/Unverify/AddKeepables.cs +++ b/src/GrillBot.App/Actions/Api/V1/Unverify/AddKeepables.cs @@ -1,5 +1,6 @@ using GrillBot.Common.Managers.Localization; using GrillBot.Common.Models; +using GrillBot.Core.Infrastructure.Actions; using GrillBot.Data.Models.API.Selfunverify; using GrillBot.Database.Entity; using GrillBot.Database.Services.Repository; @@ -17,8 +18,9 @@ public AddKeepables(ApiRequestContext apiContext, GrillBotDatabaseBuilder databa Texts = texts; } - public async Task ProcessAsync(List parameters) + public override async Task ProcessAsync() { + var parameters = (List)Parameters[0]!; await using var repository = DatabaseBuilder.CreateRepository(); await ValidateParameters(parameters, repository); @@ -30,6 +32,7 @@ public async Task ProcessAsync(List parameters) await repository.AddCollectionAsync(entities); await repository.CommitAsync(); + return ApiResult.Ok(); } private async Task ValidateParameters(List parameters, GrillBotRepository repository) diff --git a/src/GrillBot.App/Actions/Api/V1/Unverify/GetCurrentUnverifies.cs b/src/GrillBot.App/Actions/Api/V1/Unverify/GetCurrentUnverifies.cs index e66204c6d..2afc8f588 100644 --- a/src/GrillBot.App/Actions/Api/V1/Unverify/GetCurrentUnverifies.cs +++ b/src/GrillBot.App/Actions/Api/V1/Unverify/GetCurrentUnverifies.cs @@ -3,6 +3,7 @@ using GrillBot.Common.Extensions; using GrillBot.Common.Models; using GrillBot.Core.Extensions; +using GrillBot.Core.Infrastructure.Actions; using GrillBot.Data.Models.API.Unverify; namespace GrillBot.App.Actions.Api.V1.Unverify; @@ -20,7 +21,7 @@ public GetCurrentUnverifies(ApiRequestContext apiContext, IMapper mapper, IDisco DiscordClient = discordClient; } - public async Task> ProcessAsync() + public override async Task ProcessAsync() { var data = await GetAllUnverifiesAsync( ApiContext.IsPublic() ? ApiContext.GetUserId() : null @@ -35,7 +36,7 @@ public async Task> ProcessAsync() result.Add(profile); } - return result; + return ApiResult.Ok(result); } private async Task> GetAllUnverifiesAsync(ulong? userId = null) diff --git a/src/GrillBot.App/Actions/Api/V1/Unverify/GetKeepablesList.cs b/src/GrillBot.App/Actions/Api/V1/Unverify/GetKeepablesList.cs index dd9d600af..c14904297 100644 --- a/src/GrillBot.App/Actions/Api/V1/Unverify/GetKeepablesList.cs +++ b/src/GrillBot.App/Actions/Api/V1/Unverify/GetKeepablesList.cs @@ -1,4 +1,5 @@ using GrillBot.Common.Models; +using GrillBot.Core.Infrastructure.Actions; namespace GrillBot.App.Actions.Api.V1.Unverify; @@ -11,6 +12,14 @@ public GetKeepablesList(ApiRequestContext apiContext, GrillBotDatabaseBuilder da DatabaseBuilder = databaseBuilder; } + public override async Task ProcessAsync() + { + var group = (string?)Parameters[0]; + var result = await ProcessAsync(group); + + return ApiResult.Ok(result); + } + public async Task>> ProcessAsync(string? group) { await using var repository = DatabaseBuilder.CreateRepository(); diff --git a/src/GrillBot.App/Actions/Api/V1/Unverify/GetLogs.cs b/src/GrillBot.App/Actions/Api/V1/Unverify/GetLogs.cs index 5bd26ea34..a1279e1c4 100644 --- a/src/GrillBot.App/Actions/Api/V1/Unverify/GetLogs.cs +++ b/src/GrillBot.App/Actions/Api/V1/Unverify/GetLogs.cs @@ -2,6 +2,7 @@ using GrillBot.Common.Extensions.Discord; using GrillBot.Common.Models; using GrillBot.Core.Extensions; +using GrillBot.Core.Infrastructure.Actions; using GrillBot.Core.Models.Pagination; using GrillBot.Data.Models.API; using GrillBot.Data.Models.API.Unverify; @@ -23,15 +24,17 @@ public GetLogs(ApiRequestContext apiContext, IDiscordClient discordClient, IMapp DatabaseBuilder = databaseBuilder; } - public async Task> ProcessAsync(UnverifyLogParams parameters) + public override async Task ProcessAsync() { + var parameters = (UnverifyLogParams)Parameters[0]!; var mutualGuilds = await GetMutualGuildsAsync(); UpdatePublicAccess(parameters, mutualGuilds); await using var repository = DatabaseBuilder.CreateRepository(); var data = await repository.Unverify.GetLogsAsync(parameters, parameters.Pagination, mutualGuilds); - return await PaginatedResponse.CopyAndMapAsync(data, MapItemAsync); + var result = await PaginatedResponse.CopyAndMapAsync(data, MapItemAsync); + return ApiResult.Ok(result); } private async Task> GetMutualGuildsAsync() diff --git a/src/GrillBot.App/Actions/Api/V1/Unverify/KeepableExists.cs b/src/GrillBot.App/Actions/Api/V1/Unverify/KeepableExists.cs index 725edf3b1..f2cc9e4d6 100644 --- a/src/GrillBot.App/Actions/Api/V1/Unverify/KeepableExists.cs +++ b/src/GrillBot.App/Actions/Api/V1/Unverify/KeepableExists.cs @@ -1,4 +1,5 @@ using GrillBot.Common.Models; +using GrillBot.Core.Infrastructure.Actions; using GrillBot.Data.Models.API.Selfunverify; namespace GrillBot.App.Actions.Api.V1.Unverify; @@ -12,9 +13,13 @@ public KeepableExists(ApiRequestContext apiContext, GrillBotDatabaseBuilder data DatabaseBuilder = databaseBuilder; } - public async Task ProcessAsync(KeepableParams parameters) + public override async Task ProcessAsync() { + var parameters = (KeepableParams)Parameters[0]!; + await using var repository = DatabaseBuilder.CreateRepository(); - return await repository.SelfUnverify.KeepableExistsAsync(parameters.Group, parameters.Name); + + var result = await repository.SelfUnverify.KeepableExistsAsync(parameters.Group, parameters.Name); + return ApiResult.Ok(result); } } diff --git a/src/GrillBot.App/Actions/Api/V1/Unverify/RecoverState.cs b/src/GrillBot.App/Actions/Api/V1/Unverify/RecoverState.cs index 69728fd89..6013f5745 100644 --- a/src/GrillBot.App/Actions/Api/V1/Unverify/RecoverState.cs +++ b/src/GrillBot.App/Actions/Api/V1/Unverify/RecoverState.cs @@ -4,6 +4,7 @@ using GrillBot.Common.Models; using GrillBot.Core.Exceptions; using GrillBot.Core.Extensions; +using GrillBot.Core.Infrastructure.Actions; using GrillBot.Data.Models; using GrillBot.Data.Models.Unverify; using GrillBot.Database.Enums; @@ -25,8 +26,10 @@ public RecoverState(ApiRequestContext apiContext, GrillBotDatabaseBuilder databa LogManager = logManager; } - public async Task ProcessAsync(long logId) + public override async Task ProcessAsync() { + var logId = (long)Parameters[0]!; + await using var repository = DatabaseBuilder.CreateRepository(); var logItem = await repository.Unverify.FindUnverifyLogByIdAsync(logId); @@ -36,11 +39,11 @@ public async Task ProcessAsync(long logId) if (logItem.ToUser!.Unverify != null) throw new ValidationException(Texts["Unverify/Recover/ValidUnverify", ApiContext.Language]).ToBadRequestValidation(logId, nameof(logItem.ToUser.Unverify)); - var guild = await DiscordClient.GetGuildAsync(logItem.GuildId.ToUlong()); - if (guild == null) throw new NotFoundException(Texts["Unverify/Recover/GuildNotFound", ApiContext.Language]); + var guild = await DiscordClient.GetGuildAsync(logItem.GuildId.ToUlong()) + ?? throw new NotFoundException(Texts["Unverify/Recover/GuildNotFound", ApiContext.Language]); - var user = await guild.GetUserAsync(logItem.ToUserId.ToUlong()); - if (user == null) throw new NotFoundException(Texts["Unverify/Recover/MemberNotFound", ApiContext.Language].FormatWith(guild.Name)); + var user = await guild.GetUserAsync(logItem.ToUserId.ToUlong()) + ?? throw new NotFoundException(Texts["Unverify/Recover/MemberNotFound", ApiContext.Language].FormatWith(guild.Name)); var mutedRole = !string.IsNullOrEmpty(logItem.Guild!.MuteRoleId) ? guild.GetRole(logItem.Guild.MuteRoleId.ToUlong()) : null; var data = JsonConvert.DeserializeObject(logItem.Data)!; @@ -72,5 +75,7 @@ public async Task ProcessAsync(long logId) if (mutedRole != null && !data.KeepMutedRole) await user.RemoveRoleAsync(mutedRole); + + return ApiResult.Ok(); } } diff --git a/src/GrillBot.App/Actions/Api/V1/Unverify/RemoveKeepables.cs b/src/GrillBot.App/Actions/Api/V1/Unverify/RemoveKeepables.cs index a5901e99e..46a0dc55e 100644 --- a/src/GrillBot.App/Actions/Api/V1/Unverify/RemoveKeepables.cs +++ b/src/GrillBot.App/Actions/Api/V1/Unverify/RemoveKeepables.cs @@ -1,5 +1,6 @@ using GrillBot.Common.Managers.Localization; using GrillBot.Common.Models; +using GrillBot.Core.Infrastructure.Actions; namespace GrillBot.App.Actions.Api.V1.Unverify; @@ -14,8 +15,11 @@ public RemoveKeepables(ApiRequestContext apiContext, GrillBotDatabaseBuilder dat Texts = texts; } - public async Task ProcessAsync(string group, string? name = null) + public override async Task ProcessAsync() { + var group = (string)Parameters[0]!; + var name = (string?)Parameters[1]; + await using var repository = DatabaseBuilder.CreateRepository(); if (string.IsNullOrEmpty(name)) @@ -38,10 +42,9 @@ public async Task ProcessAsync(string group, string? name = null) } await repository.CommitAsync(); + return ApiResult.Ok(); } private void ThrowValidationException(string errorId, object value, params object[] args) - { - throw new ValidationException(new ValidationResult(Texts[$"Unverify/SelfUnverify/Keepables/{errorId}", ApiContext.Language].FormatWith(args)), null, value); - } + => throw new ValidationException(new ValidationResult(Texts[$"Unverify/SelfUnverify/Keepables/{errorId}", ApiContext.Language].FormatWith(args)), null, value); } diff --git a/src/GrillBot.App/Actions/Api/V1/Unverify/RemoveUnverify.cs b/src/GrillBot.App/Actions/Api/V1/Unverify/RemoveUnverify.cs index 9c5108ac7..a6d0c6698 100644 --- a/src/GrillBot.App/Actions/Api/V1/Unverify/RemoveUnverify.cs +++ b/src/GrillBot.App/Actions/Api/V1/Unverify/RemoveUnverify.cs @@ -5,7 +5,9 @@ using GrillBot.Common.Managers.Logging; using GrillBot.Common.Models; using GrillBot.Core.Exceptions; +using GrillBot.Core.Infrastructure.Actions; using GrillBot.Data.Models; +using GrillBot.Data.Models.API; using Microsoft.EntityFrameworkCore; namespace GrillBot.App.Actions.Api.V1.Unverify; @@ -50,6 +52,16 @@ public async Task ProcessAutoRemoveAsync(ulong guildId, ulong userId) } } + public override async Task ProcessAsync() + { + var guildId = (ulong)Parameters[0]!; + var userId = (ulong)Parameters[1]!; + var force = (bool)Parameters[2]!; + + var result = await ProcessAsync(guildId, userId, force); + return ApiResult.Ok(new MessageResponse(result)); + } + public async Task ProcessAsync(ulong guildId, ulong userId, bool force = false) { IsForceRemove = force; diff --git a/src/GrillBot.App/Actions/Api/V1/Unverify/UpdateUnverify.cs b/src/GrillBot.App/Actions/Api/V1/Unverify/UpdateUnverify.cs index 485531dd6..33908e50d 100644 --- a/src/GrillBot.App/Actions/Api/V1/Unverify/UpdateUnverify.cs +++ b/src/GrillBot.App/Actions/Api/V1/Unverify/UpdateUnverify.cs @@ -4,6 +4,8 @@ using GrillBot.Common.Managers.Localization; using GrillBot.Common.Models; using GrillBot.Core.Exceptions; +using GrillBot.Core.Infrastructure.Actions; +using GrillBot.Data.Models.API; using GrillBot.Data.Models.API.Unverify; namespace GrillBot.App.Actions.Api.V1.Unverify; @@ -26,6 +28,16 @@ public UpdateUnverify(ApiRequestContext apiContext, IDiscordClient discordClient MessageManager = messageManager; } + public override async Task ProcessAsync() + { + var guildId = (ulong)Parameters[0]!; + var userId = (ulong)Parameters[1]!; + var parameters = (UpdateUnverifyParams)Parameters[2]!; + + var result = await ProcessAsync(guildId, userId, parameters); + return ApiResult.Ok(new MessageResponse(result)); + } + public async Task ProcessAsync(ulong guildId, ulong userId, UpdateUnverifyParams parameters) { var (guild, fromUser, toUser) = await InitAsync(guildId, userId); diff --git a/src/GrillBot.App/Actions/Api/V1/User/GetAvailableUsers.cs b/src/GrillBot.App/Actions/Api/V1/User/GetAvailableUsers.cs index 4de11fefc..d65f3b544 100644 --- a/src/GrillBot.App/Actions/Api/V1/User/GetAvailableUsers.cs +++ b/src/GrillBot.App/Actions/Api/V1/User/GetAvailableUsers.cs @@ -1,5 +1,6 @@ using GrillBot.Common.Extensions.Discord; using GrillBot.Common.Models; +using GrillBot.Core.Infrastructure.Actions; namespace GrillBot.App.Actions.Api.V1.User; @@ -14,17 +15,20 @@ public GetAvailableUsers(ApiRequestContext apiContext, IDiscordClient discordCli DatabaseBuilder = databaseBuilder; } - public async Task> ProcessAsync(bool? bots, ulong? guildId) + public override async Task ProcessAsync() { + var bots = (bool?)Parameters[0]!; + var guildId = (ulong?)Parameters[1]!; var mutualGuilds = await GetMutualGuildsAsync(); await using var repository = DatabaseBuilder.CreateRepository(); var data = await repository.User.GetFullListOfUsers(bots, mutualGuilds, guildId); - return data + var result = data .Select(o => new { o.Id, DisplayName = o.GetDisplayName() }) .OrderBy(o => o.DisplayName) .ToDictionary(o => o.Id, o => o.DisplayName); + return ApiResult.Ok(result); } private async Task?> GetMutualGuildsAsync() diff --git a/src/GrillBot.App/Actions/Api/V1/User/GetUserDetail.cs b/src/GrillBot.App/Actions/Api/V1/User/GetUserDetail.cs index 51c20dab0..168d12fa0 100644 --- a/src/GrillBot.App/Actions/Api/V1/User/GetUserDetail.cs +++ b/src/GrillBot.App/Actions/Api/V1/User/GetUserDetail.cs @@ -10,6 +10,7 @@ using GrillBot.Data.Models.API.Unverify; using GrillBot.Data.Models.API.Users; using GrillBot.Database.Enums.Internal; +using GrillBot.Core.Infrastructure.Actions; namespace GrillBot.App.Actions.Api.V1.User; @@ -31,23 +32,23 @@ public GetUserDetail(ApiRequestContext apiContext, GrillBotDatabaseBuilder datab PointsServiceClient = pointsServiceClient; } - public async Task ProcessSelfAsync() + public override async Task ProcessAsync() { - var userId = ApiContext.GetUserId(); + var userId = (ulong?)Parameters.ElementAtOrDefault(0); + userId ??= ApiContext.GetUserId(); - var result = await ProcessAsync(userId); + var result = await ProcessAsync(userId.Value); if (ApiContext.IsPublic()) result.RemoveSecretData(); - return result; + return ApiResult.Ok(result); } public async Task ProcessAsync(ulong id) { await using var repository = DatabaseBuilder.CreateRepository(); - var entity = await repository.User.FindUserByIdAsync(id, UserIncludeOptions.All, true); - if (entity == null) - throw new NotFoundException(Texts["User/NotFound", ApiContext.Language]); + var entity = await repository.User.FindUserByIdAsync(id, UserIncludeOptions.All, true) + ?? throw new NotFoundException(Texts["User/NotFound", ApiContext.Language]); var result = new UserDetail { @@ -121,7 +122,7 @@ private async Task SetVisibleChannelsAsync(GuildUserDetail detail, IGuildUser us { if (ApiContext.IsPublic()) return; - + var visibleChannels = await guild.GetAvailableChannelsAsync(user); detail.VisibleChannels = visibleChannels diff --git a/src/GrillBot.App/Actions/Api/V1/User/GetUserList.cs b/src/GrillBot.App/Actions/Api/V1/User/GetUserList.cs index 34a81b4b9..a1c9805fc 100644 --- a/src/GrillBot.App/Actions/Api/V1/User/GetUserList.cs +++ b/src/GrillBot.App/Actions/Api/V1/User/GetUserList.cs @@ -1,5 +1,6 @@ using GrillBot.Common.Models; using GrillBot.Core.Extensions; +using GrillBot.Core.Infrastructure.Actions; using GrillBot.Core.Models.Pagination; using GrillBot.Data.Models.API.Users; @@ -16,14 +17,17 @@ public GetUserList(ApiRequestContext apiContext, GrillBotDatabaseBuilder databas DiscordClient = discordClient; } - public async Task> ProcessAsync(GetUserListParams parameters) + public override async Task ProcessAsync() { + var parameters = (GetUserListParams)Parameters[0]!; parameters.FixStatus(); await using var repository = DatabaseBuilder.CreateRepository(); var data = await repository.User.GetUsersListAsync(parameters, parameters.Pagination); - return await PaginatedResponse.CopyAndMapAsync(data, MapItemAsync); + var result = await PaginatedResponse.CopyAndMapAsync(data, MapItemAsync); + + return ApiResult.Ok(result); } private async Task MapItemAsync(Database.Entity.User entity) diff --git a/src/GrillBot.App/Actions/Api/V1/User/Hearthbeat.cs b/src/GrillBot.App/Actions/Api/V1/User/Hearthbeat.cs new file mode 100644 index 000000000..89ff00110 --- /dev/null +++ b/src/GrillBot.App/Actions/Api/V1/User/Hearthbeat.cs @@ -0,0 +1,23 @@ +using GrillBot.App.Managers; +using GrillBot.Common.Models; +using GrillBot.Core.Infrastructure.Actions; + +namespace GrillBot.App.Actions.Api.V1.User; + +public class Hearthbeat : ApiAction +{ + private UserManager UserManager { get; } + + public Hearthbeat(ApiRequestContext apiContext, UserManager userManager) : base(apiContext) + { + UserManager = userManager; + } + + public override async Task ProcessAsync() + { + var isActive = (bool)Parameters[0]!; + await UserManager.SetHearthbeatAsync(isActive, ApiContext); + + return ApiResult.Ok(); + } +} diff --git a/src/GrillBot.App/Actions/Api/V1/User/UpdateUser.cs b/src/GrillBot.App/Actions/Api/V1/User/UpdateUser.cs index cbacb644b..3975019bd 100644 --- a/src/GrillBot.App/Actions/Api/V1/User/UpdateUser.cs +++ b/src/GrillBot.App/Actions/Api/V1/User/UpdateUser.cs @@ -8,6 +8,7 @@ using GrillBot.Core.Extensions; using GrillBot.Data.Models.API.Users; using GrillBot.Database.Enums; +using GrillBot.Core.Infrastructure.Actions; namespace GrillBot.App.Actions.Api.V1.User; @@ -29,13 +30,14 @@ public UpdateUser(ApiRequestContext apiContext, GrillBotDatabaseBuilder database AuditLogServiceClient = auditLogServiceClient; } - public async Task ProcessAsync(ulong id, UpdateUserParams parameters) + public override async Task ProcessAsync() { - await using var repository = DatabaseBuilder.CreateRepository(); - var user = await repository.User.FindUserByIdAsync(id); + var id = (ulong)Parameters[0]!; + var parameters = (UpdateUserParams)Parameters[1]!; - if (user == null) - throw new NotFoundException(Texts["User/NotFound", ApiContext.Language]); + await using var repository = DatabaseBuilder.CreateRepository(); + var user = await repository.User.FindUserByIdAsync(id) + ?? throw new NotFoundException(Texts["User/NotFound", ApiContext.Language]); var before = user.Clone(); user.SelfUnverifyMinimalTime = parameters.SelfUnverifyMinimalTime; @@ -44,6 +46,7 @@ public async Task ProcessAsync(ulong id, UpdateUserParams parameters) await repository.CommitAsync(); await WriteToAuditLogAsync(before, user); await TrySyncPointsService(before, user); + return ApiResult.Ok(); } private async Task TrySyncPointsService(Database.Entity.User before, Database.Entity.User after) diff --git a/src/GrillBot.App/Actions/Api/V2/AuditLog/CreateAuditLogMessageAction.cs b/src/GrillBot.App/Actions/Api/V2/AuditLog/CreateAuditLogMessageAction.cs index 4a7ed9d60..e91f0f8c2 100644 --- a/src/GrillBot.App/Actions/Api/V2/AuditLog/CreateAuditLogMessageAction.cs +++ b/src/GrillBot.App/Actions/Api/V2/AuditLog/CreateAuditLogMessageAction.cs @@ -1,4 +1,5 @@ using GrillBot.Common.Models; +using GrillBot.Core.Infrastructure.Actions; using GrillBot.Core.Services.AuditLog; using GrillBot.Core.Services.AuditLog.Enums; using GrillBot.Core.Services.AuditLog.Models.Request.CreateItems; @@ -14,8 +15,9 @@ public CreateAuditLogMessageAction(ApiRequestContext apiContext, IAuditLogServic Client = client; } - public async Task ProcessAsync(Data.Models.API.AuditLog.Public.LogMessageRequest request) + public override async Task ProcessAsync() { + var request = (Data.Models.API.AuditLog.Public.LogMessageRequest)Parameters[0]!; var logRequest = new LogRequest { Type = request.Type.ToLower() switch @@ -45,5 +47,6 @@ public async Task ProcessAsync(Data.Models.API.AuditLog.Public.LogMessageRequest }; await Client.CreateItemsAsync(new List { logRequest }); + return ApiResult.Ok(); } } diff --git a/src/GrillBot.App/Actions/Api/V2/Events/CancelScheduledEvent.cs b/src/GrillBot.App/Actions/Api/V2/Events/CancelScheduledEvent.cs index 29d5f5518..2911c4521 100644 --- a/src/GrillBot.App/Actions/Api/V2/Events/CancelScheduledEvent.cs +++ b/src/GrillBot.App/Actions/Api/V2/Events/CancelScheduledEvent.cs @@ -2,6 +2,7 @@ using GrillBot.Common.Managers.Localization; using GrillBot.Common.Models; using GrillBot.Core.Exceptions; +using GrillBot.Core.Infrastructure.Actions; namespace GrillBot.App.Actions.Api.V2.Events; @@ -16,17 +17,21 @@ public CancelScheduledEvent(ApiRequestContext apiContext, IDiscordClient discord Texts = texts; } - public async Task ProcessAsync(ulong guildId, ulong eventId) + public override async Task ProcessAsync() { + var guildId = (ulong)Parameters[0]!; + var eventId = (ulong)Parameters[1]!; + var @event = await FindAndCheckEventAsync(guildId, eventId); await @event.EndAsync(); + + return ApiResult.Ok(); } private async Task FindAndCheckEventAsync(ulong guildId, ulong eventId) { - var guild = await DiscordClient.GetGuildAsync(guildId); - if (guild == null) throw new NotFoundException(Texts["GuildScheduledEvents/GuildNotFound", ApiContext.Language]); - + var guild = await DiscordClient.GetGuildAsync(guildId) + ?? throw new NotFoundException(Texts["GuildScheduledEvents/GuildNotFound", ApiContext.Language]); var guildEvent = await guild.GetEventAsync(eventId); ValidateCancelation(guildEvent); diff --git a/src/GrillBot.App/Actions/Api/V2/Events/CreateScheduledEvent.cs b/src/GrillBot.App/Actions/Api/V2/Events/CreateScheduledEvent.cs index 878d0d939..87bd377d2 100644 --- a/src/GrillBot.App/Actions/Api/V2/Events/CreateScheduledEvent.cs +++ b/src/GrillBot.App/Actions/Api/V2/Events/CreateScheduledEvent.cs @@ -2,6 +2,7 @@ using GrillBot.Common.Managers.Localization; using GrillBot.Common.Models; using GrillBot.Core.Exceptions; +using GrillBot.Core.Infrastructure.Actions; using GrillBot.Data.Models.API.Guilds.GuildEvents; namespace GrillBot.App.Actions.Api.V2.Events; @@ -17,8 +18,11 @@ public CreateScheduledEvent(ApiRequestContext apiContext, IDiscordClient discord Texts = texts; } - public async Task ProcessAsync(ulong guildId, ScheduledEventParams parameters) + public override async Task ProcessAsync() { + var guildId = (ulong)Parameters[0]!; + var parameters = (ScheduledEventParams)Parameters[1]!; + ValidateInput(parameters); var guild = await FindGuildAsync(guildId); var endTime = new DateTimeOffset(parameters.EndAt); @@ -29,7 +33,7 @@ public async Task ProcessAsync(ulong guildId, ScheduledEventParams parame { var scheduledEvent = await guild.CreateEventAsync(parameters.Name, startTime, GuildScheduledEventType.External, GuildScheduledEventPrivacyLevel.Private, parameters.Description, endTime, null, parameters.Location, image); - return scheduledEvent.Id; + return ApiResult.Ok(scheduledEvent.Id); } finally { diff --git a/src/GrillBot.App/Actions/Api/V2/Events/UpdateScheduledEvent.cs b/src/GrillBot.App/Actions/Api/V2/Events/UpdateScheduledEvent.cs index cd02ecf5a..db68f1274 100644 --- a/src/GrillBot.App/Actions/Api/V2/Events/UpdateScheduledEvent.cs +++ b/src/GrillBot.App/Actions/Api/V2/Events/UpdateScheduledEvent.cs @@ -1,6 +1,7 @@ using GrillBot.Common.Managers.Localization; using GrillBot.Common.Models; using GrillBot.Core.Exceptions; +using GrillBot.Core.Infrastructure.Actions; using GrillBot.Data.Models.API.Guilds.GuildEvents; namespace GrillBot.App.Actions.Api.V2.Events; @@ -16,14 +17,19 @@ public UpdateScheduledEvent(ApiRequestContext apiContext, IDiscordClient discord Texts = texts; } - public async Task ProcessAsync(ulong guildId, ulong eventId, ScheduledEventParams parameters) + public override async Task ProcessAsync() { + var guildId = (ulong)Parameters[0]!; + var eventId = (ulong)Parameters[1]!; + var parameters = (ScheduledEventParams)Parameters[2]!; + var guildEvent = await FindAndCheckEventAsync(guildId, eventId); var image = CreateImage(parameters.Image); try { await guildEvent.ModifyAsync(prop => SetModificationParameters(prop, parameters, image, guildEvent)); + return ApiResult.Ok(); } finally { diff --git a/src/GrillBot.App/Actions/Api/V2/GetTodayBirthdayInfo.cs b/src/GrillBot.App/Actions/Api/V2/GetTodayBirthdayInfo.cs index 43125f58d..c8b6a4cbb 100644 --- a/src/GrillBot.App/Actions/Api/V2/GetTodayBirthdayInfo.cs +++ b/src/GrillBot.App/Actions/Api/V2/GetTodayBirthdayInfo.cs @@ -2,6 +2,7 @@ using GrillBot.Common.Managers.Localization; using GrillBot.Common.Models; using GrillBot.Core.Extensions; +using GrillBot.Core.Infrastructure.Actions; using GrillBot.Data.Models.API; namespace GrillBot.App.Actions.Api.V2; @@ -22,14 +23,16 @@ public GetTodayBirthdayInfo(ApiRequestContext apiContext, GrillBotDatabaseBuilde Texts = texts; } - public async Task ProcessAsync() + public override async Task ProcessAsync() { await using var repository = DatabaseBuilder.CreateRepository(); var todayBirthdayUsers = await repository.User.GetUsersWithTodayBirthday(); var users = await TransformUsersAsync(todayBirthdayUsers); var message = Format(users); - return new MessageResponse(message); + var response = new MessageResponse(message); + + return ApiResult.Ok(response); } private async Task> TransformUsersAsync(List users) @@ -66,14 +69,12 @@ private string Format(IReadOnlyCollection<(IUser user, int? age)> users) if (users.Count > 1) { var withoutLast = string.Join(", ", formatted.Take(formatted.Count - 1)); - result = result.FormatWith(withoutLast, formatted[^1], hypers); + return result.FormatWith(withoutLast, formatted[^1], hypers); } else { - result = result.FormatWith(formatted[0], hypers); + return result.FormatWith(formatted[0], hypers); } - - return result; } private static int ComputeAge(DateTime dateTime) diff --git a/src/GrillBot.App/Actions/Api/V2/User/GetRubbergodUserKarma.cs b/src/GrillBot.App/Actions/Api/V2/User/GetRubbergodUserKarma.cs index 229c97b99..2043bdfaf 100644 --- a/src/GrillBot.App/Actions/Api/V2/User/GetRubbergodUserKarma.cs +++ b/src/GrillBot.App/Actions/Api/V2/User/GetRubbergodUserKarma.cs @@ -4,6 +4,7 @@ using GrillBot.Core.Models.Pagination; using GrillBot.Data.Models.API.Users; using GrillBot.Database.Enums; +using GrillBot.Core.Infrastructure.Actions; namespace GrillBot.App.Actions.Api.V2.User; @@ -18,12 +19,14 @@ public GetRubbergodUserKarma(ApiRequestContext apiContext, IRubbergodServiceClie DatabaseBuilder = databaseBuilder; } - public async Task> ProcessAsync(KarmaListParams parameters) + public override async Task ProcessAsync() { + var parameters = (KarmaListParams)Parameters[0]!; var page = await RubbergodServiceClient.GetKarmaPageAsync(parameters.Pagination); var users = await ReadUsersAsync(page.Data.ConvertAll(o => o.UserId)); + var result = await PaginatedResponse.CopyAndMapAsync(page, entity => Task.FromResult(Map(entity, users))); - return await PaginatedResponse.CopyAndMapAsync(page, entity => Task.FromResult(Map(entity, users))); + return ApiResult.Ok(result); } private async Task> ReadUsersAsync(List userIds) diff --git a/src/GrillBot.App/Actions/ApiAction.cs b/src/GrillBot.App/Actions/ApiAction.cs index 9c84afcee..bd9dcb6b2 100644 --- a/src/GrillBot.App/Actions/ApiAction.cs +++ b/src/GrillBot.App/Actions/ApiAction.cs @@ -7,7 +7,7 @@ namespace GrillBot.App.Actions; -public abstract class ApiAction +public abstract class ApiAction : Core.Infrastructure.Actions.ApiActionBase { protected ApiRequestContext ApiContext { get; } @@ -18,13 +18,13 @@ protected ApiAction(ApiRequestContext apiContext) ApiContext = apiContext; } - public static void Init(Controller controller, IDictionaryObject apiObject) + public static void Init(ControllerBase controller, IDictionaryObject apiObject) { var apiRequestContext = controller.HttpContext.RequestServices.GetRequiredService(); apiRequestContext.LogRequest.AddParameters(apiObject); } - public static void Init(Controller controller, IDictionaryObject[] apiObjects) + public static void Init(ControllerBase controller, IDictionaryObject[] apiObjects) { var apiRequestContext = controller.HttpContext.RequestServices.GetRequiredService(); for (var i = 0; i < apiObjects.Length; i++) diff --git a/src/GrillBot.App/Actions/Commands/EmoteSuggestion/InitSuggestion.cs b/src/GrillBot.App/Actions/Commands/EmoteSuggestion/InitSuggestion.cs index 74c57211c..5e87b38b5 100644 --- a/src/GrillBot.App/Actions/Commands/EmoteSuggestion/InitSuggestion.cs +++ b/src/GrillBot.App/Actions/Commands/EmoteSuggestion/InitSuggestion.cs @@ -40,11 +40,18 @@ private void ValidateData(IEmote? emoteData, IAttachment? attachment) Emote = emoteData as Emote; Attachment = attachment; - if (Emote == null && Attachment == null) + if (Emote is null && Attachment is null) throw new ValidationException(GetText("NoEmoteAndAttachment")); - if (Emote != null && Context.Guild.Emotes.Any(o => o.Id == Emote.Id)) + if (Emote is not null && Context.Guild.Emotes.Any(o => o.Id == Emote.Id)) throw new ValidationException(GetText("EmoteExistsInGuild")); + + if (Attachment is not null) + { + var extension = Path.GetExtension(Attachment.Filename).ToLower(); + if (extension != ".png") + throw new ValidationException(GetText("UnsupportedImageFormat")); + } } private async Task<(string filename, byte[] content)> GetDataAsync() diff --git a/src/GrillBot.App/Actions/Commands/Unverify/SelfUnverifyKeepables.cs b/src/GrillBot.App/Actions/Commands/Unverify/SelfUnverifyKeepables.cs index 8a0c6e796..0a071dcdd 100644 --- a/src/GrillBot.App/Actions/Commands/Unverify/SelfUnverifyKeepables.cs +++ b/src/GrillBot.App/Actions/Commands/Unverify/SelfUnverifyKeepables.cs @@ -41,7 +41,7 @@ public async Task ListAsync(string? group = null) if (fieldGroupBuilder.Length + item.Length >= EmbedFieldBuilder.MaxFieldValueLength) { fieldGroupResult = fieldGroupBuilder.ToString().Trim(); - embed.AddField(keys, fieldGroupResult.EndsWith(",") ? fieldGroupResult[..^1] : fieldGroupResult); + embed.AddField(keys, fieldGroupResult.EndsWith(',') ? fieldGroupResult[..^1] : fieldGroupResult); fieldGroupBuilder.Clear(); } else @@ -54,7 +54,7 @@ public async Task ListAsync(string? group = null) continue; fieldGroupResult = fieldGroupBuilder.ToString().Trim(); - embed.AddField(keys, fieldGroupResult.EndsWith(",") ? fieldGroupResult[..^1] : fieldGroupResult); + embed.AddField(keys, fieldGroupResult.EndsWith(',') ? fieldGroupResult[..^1] : fieldGroupResult); fieldGroupBuilder.Clear(); } diff --git a/src/GrillBot.App/Controllers/AuditLogController.cs b/src/GrillBot.App/Controllers/AuditLogController.cs index 25ca78932..784ee0a3b 100644 --- a/src/GrillBot.App/Controllers/AuditLogController.cs +++ b/src/GrillBot.App/Controllers/AuditLogController.cs @@ -18,7 +18,7 @@ namespace GrillBot.App.Controllers; [ApiController] [Route("api/auditlog")] [ApiExplorerSettings(GroupName = "v1")] -public class AuditLogController : Infrastructure.ControllerBase +public class AuditLogController : Core.Infrastructure.Actions.ControllerBase { public AuditLogController(IServiceProvider serviceProvider) : base(serviceProvider) { @@ -30,14 +30,11 @@ public AuditLogController(IServiceProvider serviceProvider) : base(serviceProvid /// Success. /// Item not found. [HttpDelete("{id:guid}")] - [ProducesResponseType((int)HttpStatusCode.OK)] + [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(typeof(MessageResponse), (int)HttpStatusCode.NotFound)] [Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme, Roles = "Admin")] - public async Task RemoveItemAsync(Guid id) - { - await ProcessActionAsync(action => action.ProcessAsync(id)); - return Ok(); - } + public async Task RemoveItemAsync(Guid id) + => await ProcessAsync(id); /// /// Get paginated list of audit logs. @@ -45,13 +42,13 @@ public async Task RemoveItemAsync(Guid id) /// Returns paginated list of audit log items. /// Validation of parameters failed. [HttpPost("list")] - [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(typeof(PaginatedResponse), StatusCodes.Status200OK)] [ProducesResponseType(typeof(ValidationProblemDetails), StatusCodes.Status400BadRequest)] [Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme, Roles = "Admin")] - public async Task>> SearchAuditLogsAsync([FromBody] SearchRequest request) + public async Task SearchAuditLogsAsync([FromBody] SearchRequest request) { ApiAction.Init(this, request); - return Ok(await ProcessActionAsync>(action => action.ProcessAsync(request))); + return await ProcessAsync(request); } /// @@ -62,22 +59,21 @@ public async Task>> SearchAuditLogsA [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(typeof(ValidationProblemDetails), StatusCodes.Status400BadRequest)] [Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme, Roles = "Admin,User")] - public async Task HandleClientAppMessageAsync([FromBody] ClientLogItemRequest request) + public async Task HandleClientAppMessageAsync([FromBody] ClientLogItemRequest request) { ApiAction.Init(this, request); - - await ProcessActionAsync(action => action.ProcessAsync(request)); - return Ok(); + return await ProcessAsync(request); } /// /// Get detailed information of log item. /// [HttpGet("{id:guid}")] - [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(typeof(Detail), StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status404NotFound)] [Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme, Roles = "Admin")] - public async Task> DetailAsync(Guid id) - => Ok(await ProcessActionAsync(action => action.ProcessAsync(id))); + public async Task DetailAsync(Guid id) + => await ProcessAsync(id); /// /// Create text based log item. @@ -89,11 +85,9 @@ public async Task HandleClientAppMessageAsync([FromBody] ClientLog [ProducesResponseType(typeof(ValidationProblemDetails), StatusCodes.Status400BadRequest)] [ApiKeyAuth] [ApiExplorerSettings(GroupName = "v2")] - public async Task CreateMessageLogItemAsync(LogMessageRequest request) + public async Task CreateMessageLogItemAsync(LogMessageRequest request) { ApiAction.Init(this, request); - - await ProcessActionAsync(action => action.ProcessAsync(request)); - return Ok(); + return await ProcessAsync(request); } } diff --git a/src/GrillBot.App/Controllers/AuthController.cs b/src/GrillBot.App/Controllers/AuthController.cs index a7f068e28..fa2013dac 100644 --- a/src/GrillBot.App/Controllers/AuthController.cs +++ b/src/GrillBot.App/Controllers/AuthController.cs @@ -11,7 +11,7 @@ namespace GrillBot.App.Controllers; [ApiController] [Route("api/auth")] [ApiExplorerSettings(GroupName = "v1")] -public class AuthController : Infrastructure.ControllerBase +public class AuthController : Core.Infrastructure.Actions.ControllerBase { public AuthController(IServiceProvider serviceProvider) : base(serviceProvider) { @@ -23,9 +23,9 @@ public AuthController(IServiceProvider serviceProvider) : base(serviceProvider) /// Success [HttpGet("link")] [AllowAnonymous] - [ProducesResponseType((int)HttpStatusCode.OK)] - public ActionResult GetRedirectLink([FromQuery] AuthState state) - => Ok(ProcessAction(action => action.Process(state))); + [ProducesResponseType(typeof(OAuth2GetLink), StatusCodes.Status200OK)] + public async Task GetRedirectLinkAsync([FromQuery] AuthState state) + => await ProcessAsync(state); /// /// OAuth2 redirect callback. @@ -36,10 +36,10 @@ public ActionResult GetRedirectLink([FromQuery] AuthState state) /// Validation failed [HttpGet("callback")] [AllowAnonymous] - [ProducesResponseType((int)HttpStatusCode.OK)] - [ProducesResponseType(typeof(ValidationProblemDetails), (int)HttpStatusCode.BadRequest)] - public async Task OnOAuth2CallbackAsync([FromQuery, Required] string code, [Required, FromQuery] string state) - => Redirect(await ProcessActionAsync(action => action.ProcessAsync(code, state))); + [ProducesResponseType(StatusCodes.Status302Found)] + [ProducesResponseType(typeof(ValidationProblemDetails), StatusCodes.Status400BadRequest)] + public async Task OnOAuth2CallbackAsync([FromQuery, Required] string code, [Required, FromQuery] string state) + => await ProcessAsync(code, state); /// /// Create auth token from session. @@ -50,10 +50,10 @@ public async Task OnOAuth2CallbackAsync([FromQuery, Required] stri /// Validation failed [HttpGet("token")] [AllowAnonymous] - [ProducesResponseType((int)HttpStatusCode.OK)] - [ProducesResponseType(typeof(ValidationProblemDetails), (int)HttpStatusCode.BadRequest)] - public async Task> CreateLoginTokenAsync([FromQuery, Required] string sessionId, [FromQuery, Required] bool isPublic) - => Ok(await ProcessActionAsync(action => action.ProcessAsync(sessionId, isPublic))); + [ProducesResponseType(typeof(OAuth2LoginToken), StatusCodes.Status200OK)] + [ProducesResponseType(typeof(ValidationProblemDetails), StatusCodes.Status400BadRequest)] + public async Task CreateLoginTokenAsync([FromQuery, Required] string sessionId, [FromQuery, Required] bool isPublic) + => await ProcessAsync(sessionId, isPublic); /// /// Create auth token from user ID. Only for development purposes. @@ -64,9 +64,9 @@ public async Task> CreateLoginTokenAsync([FromQue /// Validation failed or user not found. [HttpGet("token/{userId}")] [AllowAnonymous] - [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(typeof(OAuth2LoginToken), StatusCodes.Status200OK)] [ProducesResponseType(typeof(ValidationProblemDetails), StatusCodes.Status400BadRequest)] [OnlyDevelopment] - public async Task> CreateLoginTokenFromIdAsync(ulong userId, [FromQuery] bool isPublic = false) - => Ok(await ProcessActionAsync(action => action.ProcessAsync(userId, isPublic))); + public async Task CreateLoginTokenFromIdAsync(ulong userId, [FromQuery] bool isPublic = false) + => await ProcessAsync(userId, isPublic); } diff --git a/src/GrillBot.App/Controllers/AutoReplyController.cs b/src/GrillBot.App/Controllers/AutoReplyController.cs index 33f049e14..683ea0abd 100644 --- a/src/GrillBot.App/Controllers/AutoReplyController.cs +++ b/src/GrillBot.App/Controllers/AutoReplyController.cs @@ -13,7 +13,7 @@ namespace GrillBot.App.Controllers; [Route("api/autoreply")] [Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme, Roles = "Admin")] [ApiExplorerSettings(GroupName = "v1")] -public class AutoReplyController : Infrastructure.ControllerBase +public class AutoReplyController : Core.Infrastructure.Actions.ControllerBase { public AutoReplyController(IServiceProvider serviceProvider) : base(serviceProvider) { @@ -24,9 +24,9 @@ public AutoReplyController(IServiceProvider serviceProvider) : base(serviceProvi /// /// Success [HttpGet] - [ProducesResponseType(StatusCodes.Status200OK)] - public async Task>> GetAutoReplyListAsync() - => Ok(await ProcessActionAsync>(action => action.ProcessAsync())); + [ProducesResponseType(typeof(List), StatusCodes.Status200OK)] + public async Task GetAutoReplyListAsync() + => await ProcessAsync(); /// /// Get reply item @@ -35,10 +35,10 @@ public async Task>> GetAutoReplyListAsync() /// Success /// Reply not found [HttpGet("{id:long}")] - [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(typeof(AutoReplyItem), StatusCodes.Status200OK)] [ProducesResponseType(typeof(MessageResponse), StatusCodes.Status404NotFound)] - public async Task> GetItemAsync(long id) - => Ok(await ProcessActionAsync(action => action.ProcessAsync(id))); + public async Task GetItemAsync(long id) + => await ProcessAsync(id); /// /// Create new reply item. @@ -46,13 +46,12 @@ public async Task> GetItemAsync(long id) /// Success /// Validation failed [HttpPost] - [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(typeof(AutoReplyItem), StatusCodes.Status200OK)] [ProducesResponseType(typeof(ValidationProblemDetails), StatusCodes.Status400BadRequest)] - public async Task> CreateItemAsync([FromBody] AutoReplyItemParams parameters) + public async Task CreateItemAsync([FromBody] AutoReplyItemParams parameters) { ApiAction.Init(this, parameters); - - return Ok(await ProcessActionAsync(action => action.ProcessAsync(parameters))); + return await ProcessAsync(parameters); } /// @@ -64,14 +63,13 @@ public async Task> CreateItemAsync([FromBody] AutoRe /// Validation failed /// Item not found [HttpPut("{id:long}")] - [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(typeof(AutoReplyItem), StatusCodes.Status200OK)] [ProducesResponseType(typeof(ValidationProblemDetails), StatusCodes.Status400BadRequest)] [ProducesResponseType(typeof(MessageResponse), StatusCodes.Status404NotFound)] - public async Task> UpdateItemAsync(long id, [FromBody] AutoReplyItemParams parameters) + public async Task UpdateItemAsync(long id, [FromBody] AutoReplyItemParams parameters) { ApiAction.Init(this, parameters); - - return Ok(await ProcessActionAsync(action => action.ProcessAsync(id, parameters))); + return await ProcessAsync(id, parameters); } /// @@ -83,9 +81,6 @@ public async Task> UpdateItemAsync(long id, [FromBod [HttpDelete("{id:long}")] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(typeof(MessageResponse), StatusCodes.Status404NotFound)] - public async Task RemoveItemAsync(long id) - { - await ProcessActionAsync(action => action.ProcessAsync(id)); - return Ok(); - } + public async Task RemoveItemAsync(long id) + => await ProcessAsync(id); } diff --git a/src/GrillBot.App/Controllers/ChannelController.cs b/src/GrillBot.App/Controllers/ChannelController.cs index d9564ecad..f76d4567f 100644 --- a/src/GrillBot.App/Controllers/ChannelController.cs +++ b/src/GrillBot.App/Controllers/ChannelController.cs @@ -13,7 +13,7 @@ namespace GrillBot.App.Controllers; [ApiController] [Route("api/channel")] [ApiExplorerSettings(GroupName = "v1")] -public class ChannelController : Infrastructure.ControllerBase +public class ChannelController : Core.Infrastructure.Actions.ControllerBase { public ChannelController(IServiceProvider serviceProvider) : base(serviceProvider) { @@ -26,13 +26,12 @@ public ChannelController(IServiceProvider serviceProvider) : base(serviceProvide /// Validation failed [HttpPost("{id}/userStats")] [Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme, Roles = "Admin")] - [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(typeof(PaginatedResponse), StatusCodes.Status200OK)] [ProducesResponseType(typeof(ValidationProblemDetails), (int)HttpStatusCode.BadRequest)] - public async Task>> GetChannelUsersAsync(ulong id, [FromBody] PaginatedParams pagination) + public async Task GetChannelUsersAsync(ulong id, [FromBody] PaginatedParams pagination) { ApiAction.Init(this, pagination); - - return Ok(await ProcessActionAsync>(action => action.ProcessAsync(id, pagination))); + return await ProcessAsync(id, pagination); } /// @@ -49,12 +48,10 @@ public async Task>> GetChann [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(typeof(ValidationProblemDetails), StatusCodes.Status400BadRequest)] [ProducesResponseType(typeof(MessageResponse), StatusCodes.Status404NotFound)] - public async Task SendMessageToChannelAsync(ulong guildId, ulong channelId, [FromBody] SendMessageToChannelParams parameters) + public async Task SendMessageToChannelAsync(ulong guildId, ulong channelId, [FromBody] SendMessageToChannelParams parameters) { ApiAction.Init(this, parameters); - - await ProcessActionAsync(action => action.ProcessAsync(guildId, channelId, parameters)); - return Ok(); + return await ProcessAsync(guildId, channelId, parameters); } /// @@ -62,13 +59,12 @@ public async Task SendMessageToChannelAsync(ulong guildId, ulong c /// [HttpPost("list")] [Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme, Roles = "Admin")] - [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(typeof(PaginatedResponse), StatusCodes.Status200OK)] [ProducesResponseType(typeof(ValidationProblemDetails), StatusCodes.Status400BadRequest)] - public async Task>> GetChannelsListAsync([FromBody] GetChannelListParams parameters) + public async Task GetChannelsListAsync([FromBody] GetChannelListParams parameters) { ApiAction.Init(this, parameters); - - return Ok(await ProcessActionAsync>(action => action.ProcessAsync(parameters))); + return await ProcessAsync(parameters); } /// @@ -77,11 +73,8 @@ public async Task>> GetChan [HttpDelete("{guildId}/{channelId}/cache")] [Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme, Roles = "Admin")] [ProducesResponseType(StatusCodes.Status200OK)] - public async Task ClearChannelCacheAsync(ulong guildId, ulong channelId) - { - await ProcessActionAsync(action => action.ProcessAsync(guildId, channelId)); - return Ok(); - } + public async Task ClearChannelCacheAsync(ulong guildId, ulong channelId) + => await ProcessAsync(guildId, channelId); /// /// Get detail of channel. @@ -91,10 +84,10 @@ public async Task ClearChannelCacheAsync(ulong guildId, ulong chan /// Channel not found. [HttpGet("{id}")] [Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme, Roles = "Admin")] - [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(typeof(ChannelDetail), StatusCodes.Status200OK)] [ProducesResponseType(typeof(MessageResponse), StatusCodes.Status404NotFound)] - public async Task> GetChannelDetailAsync(ulong id) - => Ok(await ProcessActionAsync(action => action.ProcessAsync(id))); + public async Task GetChannelDetailAsync(ulong id) + => await ProcessAsync(id); /// /// Update channel @@ -109,12 +102,10 @@ public async Task> GetChannelDetailAsync(ulong id) [ProducesResponseType(typeof(ValidationProblemDetails), StatusCodes.Status400BadRequest)] [ProducesResponseType(typeof(MessageResponse), StatusCodes.Status404NotFound)] [ProducesResponseType(typeof(MessageResponse), StatusCodes.Status500InternalServerError)] - public async Task UpdateChannelAsync(ulong id, [FromBody] UpdateChannelParams parameters) + public async Task UpdateChannelAsync(ulong id, [FromBody] UpdateChannelParams parameters) { ApiAction.Init(this, parameters); - - await ProcessActionAsync(action => action.ProcessAsync(id, parameters)); - return Ok(); + return await ProcessAsync(id, parameters); } /// @@ -123,9 +114,9 @@ public async Task UpdateChannelAsync(ulong id, [FromBody] UpdateCh /// Success [HttpGet("board")] [Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme, Roles = "User")] - [ProducesResponseType(StatusCodes.Status200OK)] - public async Task>> GetChannelboardAsync() - => Ok(await ProcessActionAsync>(action => action.ProcessAsync())); + [ProducesResponseType(typeof(List), StatusCodes.Status200OK)] + public async Task GetChannelboardAsync() + => await ProcessAsync(); /// /// Get all pins from channel. @@ -134,15 +125,10 @@ public async Task>> GetChannelboardAsync() /// Guild wasn't found. [HttpGet("{channelId}/pins")] [Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme, Roles = "User")] - [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(typeof(string), StatusCodes.Status200OK)] [ProducesResponseType(typeof(MessageResponse), StatusCodes.Status404NotFound)] - public async Task> GetChannelPinsAsync(ulong channelId, bool markdown) - { - return Content( - await ProcessActionAsync(action => action.ProcessAsync(channelId, markdown)), - markdown ? "text/markdown" : "application/json" - ); - } + public async Task GetChannelPinsAsync(ulong channelId, bool markdown) + => await ProcessAsync(channelId, markdown); /// /// Get all pins with attachments as zip archive. @@ -154,5 +140,5 @@ await ProcessActionAsync(action => action.ProcessAsync(channelI [ProducesResponseType(typeof(FileContentResult), StatusCodes.Status200OK)] [ProducesResponseType(typeof(MessageResponse), StatusCodes.Status404NotFound)] public async Task GetChannelPinsWithAttachmentsAsync(ulong channelId) - => File(await ProcessActionAsync(action => action.ProcessAsync(channelId)), "application/zip"); + => await ProcessAsync(channelId); } diff --git a/src/GrillBot.App/Controllers/DashboardController.cs b/src/GrillBot.App/Controllers/DashboardController.cs index 01a4ada15..67e833364 100644 --- a/src/GrillBot.App/Controllers/DashboardController.cs +++ b/src/GrillBot.App/Controllers/DashboardController.cs @@ -7,6 +7,7 @@ using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Authentication.JwtBearer; +using GrillBot.App.Actions.Api; namespace GrillBot.App.Controllers; @@ -14,49 +15,61 @@ namespace GrillBot.App.Controllers; [Route("api/dashboard")] [ApiExplorerSettings(GroupName = "v1")] [Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme, Roles = "Admin")] -public class DashboardController : Infrastructure.ControllerBase +public class DashboardController : Core.Infrastructure.Actions.ControllerBase { public DashboardController(IServiceProvider serviceProvider) : base(serviceProvider) { } [HttpGet("api/{apiGroup}")] - [ProducesResponseType(StatusCodes.Status200OK)] - public async Task>> GetApiDashboardAsync(string apiGroup) - => await ProcessBridgeAsync>(client => client.GetApiDashboardAsync(apiGroup)); + [ProducesResponseType(typeof(List), StatusCodes.Status200OK)] + public async Task GetApiDashboardAsync(string apiGroup) + { + var executor = new Func>(async (IAuditLogServiceClient client) => await client.GetApiDashboardAsync(apiGroup)); + return await ProcessAsync>(executor); + } [HttpGet("interactions")] - [ProducesResponseType(StatusCodes.Status200OK)] - public async Task>> GetInteractionsDashboardAsync() - => await ProcessBridgeAsync>(client => client.GetInteractionsDashboardAsync()); + [ProducesResponseType(typeof(List), StatusCodes.Status200OK)] + public async Task GetInteractionsDashboardAsync() + { + var executor = new Func>(async (IAuditLogServiceClient client) => await client.GetInteractionsDashboardAsync()); + return await ProcessAsync>(executor); + } [HttpGet("jobs")] - [ProducesResponseType(StatusCodes.Status200OK)] - public async Task>> GetJobsDashboardAsync() - => await ProcessBridgeAsync>(client => client.GetJobsDashboardAsync()); + [ProducesResponseType(typeof(List), StatusCodes.Status200OK)] + public async Task GetJobsDashboardAsync() + { + var executor = new Func>(async (IAuditLogServiceClient client) => await client.GetJobsDashboardAsync()); + return await ProcessAsync>(executor); + } [HttpGet("todayAvgTimes")] - [ProducesResponseType(StatusCodes.Status200OK)] - public async Task> GetTodayAvgTimesAsync() - => await ProcessBridgeAsync(client => client.GetTodayAvgTimes()); + [ProducesResponseType(typeof(TodayAvgTimes), StatusCodes.Status200OK)] + public async Task GetTodayAvgTimesAsync() + { + var executor = new Func>(async (IAuditLogServiceClient client) => await client.GetTodayAvgTimes()); + return await ProcessAsync>(executor); + } [HttpGet("common")] - [ProducesResponseType(StatusCodes.Status200OK)] - public ActionResult GetCommonInfo() - => ProcessAction(action => action.Process()); + [ProducesResponseType(typeof(DashboardInfo), StatusCodes.Status200OK)] + public async Task GetCommonInfoAsync() + => await ProcessAsync(); [HttpGet("services")] - [ProducesResponseType(StatusCodes.Status200OK)] - public async Task>> GetServicesListAsync() - => await ProcessActionAsync>(action => action.ProcessAsync()); + [ProducesResponseType(typeof(List), StatusCodes.Status200OK)] + public async Task GetServicesListAsync() + => await ProcessAsync(); [HttpGet("operations/active")] - [ProducesResponseType(StatusCodes.Status200OK)] - public ActionResult> GetActiveOperations() - => ProcessAction>(action => action.Process()); + [ProducesResponseType(typeof(Dictionary), StatusCodes.Status200OK)] + public async Task GetActiveOperationsAsync() + => await ProcessAsync(); [HttpGet("operations")] - [ProducesResponseType(StatusCodes.Status200OK)] - public ActionResult> GetOperationStats() - => ProcessAction>(action => action.Process()); + [ProducesResponseType(typeof(List), StatusCodes.Status200OK)] + public async Task GetOperationStatsAsync() + => await ProcessAsync(); } diff --git a/src/GrillBot.App/Controllers/DataController.cs b/src/GrillBot.App/Controllers/DataController.cs index ee5cbe2e0..54f7ca0ae 100644 --- a/src/GrillBot.App/Controllers/DataController.cs +++ b/src/GrillBot.App/Controllers/DataController.cs @@ -14,7 +14,7 @@ namespace GrillBot.App.Controllers; [ApiController] [Route("api/data")] [ApiExplorerSettings(GroupName = "v1")] -public class DataController : Infrastructure.ControllerBase +public class DataController : Core.Infrastructure.Actions.ControllerBase { public DataController(IServiceProvider serviceProvider) : base(serviceProvider) { @@ -25,9 +25,9 @@ public DataController(IServiceProvider serviceProvider) : base(serviceProvider) /// [HttpGet("guilds")] [Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme, Roles = "Admin,User")] - [ProducesResponseType((int)HttpStatusCode.OK)] - public async Task>> GetAvailableGuildsAsync() - => Ok(await ProcessActionAsync>(action => action.ProcessAsync())); + [ProducesResponseType(typeof(Dictionary), StatusCodes.Status200OK)] + public async Task GetAvailableGuildsAsync() + => await ProcessAsync(); /// /// Get non paginated list of channels. @@ -36,9 +36,9 @@ public async Task>> GetAvailableGuildsAs /// Flag that removes threads from list. [HttpGet("channels")] [Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme, Roles = "Admin,User")] - [ProducesResponseType((int)HttpStatusCode.OK)] - public async Task>> GetChannelsAsync(ulong? guildId, bool ignoreThreads = false) - => Ok(await ProcessActionAsync>(action => action.ProcessAsync(guildId, ignoreThreads))); + [ProducesResponseType(typeof(Dictionary), StatusCodes.Status200OK)] + public async Task GetChannelsAsync(ulong? guildId, bool ignoreThreads = false) + => await ProcessAsync(ignoreThreads, guildId); /// /// Get non paginated list of channels that contains some pin. @@ -46,18 +46,18 @@ public async Task>> GetChannelsAsync(ulo /// Returns simple list of channels that contains some pin. [HttpGet("channels/pins")] [Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme, Roles = "User")] - [ProducesResponseType(StatusCodes.Status200OK)] - public async Task>> GetChannelsWithPinsAsync() - => Ok(await ProcessActionAsync>(action => action.ProcessAsync())); + [ProducesResponseType(typeof(Dictionary), StatusCodes.Status200OK)] + public async Task GetChannelsWithPinsAsync() + => await ProcessAsync(); /// /// Get roles /// [HttpGet("roles")] [Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme, Roles = "Admin,User")] - [ProducesResponseType((int)HttpStatusCode.OK)] - public async Task>> GetRolesAsync(ulong? guildId) - => Ok(await ProcessActionAsync>(action => action.ProcessAsync(guildId))); + [ProducesResponseType(typeof(Dictionary), StatusCodes.Status200OK)] + public async Task GetRolesAsync(ulong? guildId) + => await ProcessAsync(guildId); /// /// Gets non-paginated list of users. @@ -65,18 +65,18 @@ public async Task>> GetRolesAsync(ulong? /// Success [HttpGet("users")] [Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme, Roles = "Admin,User")] - [ProducesResponseType((int)HttpStatusCode.OK)] - public async Task>> GetAvailableUsersAsync(bool? bots = null, ulong? guildId = null) - => Ok(await ProcessActionAsync>(action => action.ProcessAsync(bots, guildId))); + [ProducesResponseType(typeof(Dictionary), StatusCodes.Status200OK)] + public async Task GetAvailableUsersAsync(bool? bots = null, ulong? guildId = null) + => await ProcessAsync(bots, guildId); /// /// Get currently supported emotes. /// [HttpGet("emotes")] [Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme, Roles = "Admin")] - [ProducesResponseType(StatusCodes.Status200OK)] - public async Task>> GetSupportedEmotes() - => Ok(await ProcessActionAsync>(action => action.ProcessAsync())); + [ProducesResponseType(typeof(List), StatusCodes.Status200OK)] + public async Task GetSupportedEmotesAsync() + => await ProcessAsync(); /// /// Get list of methods available from public api. @@ -84,7 +84,7 @@ public async Task>> GetSupportedEmotes() /// Returns list of methods available from public api. [HttpGet("publicApi/methods")] [Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme, Roles = "Admin")] - [ProducesResponseType(StatusCodes.Status200OK)] - public ActionResult> GetPublicApiMethods() - => Ok(ProcessAction>(action => action.Process())); + [ProducesResponseType(typeof(List), StatusCodes.Status200OK)] + public async Task GetPublicApiMethodsAsync() + => await ProcessAsync(); } diff --git a/src/GrillBot.App/Controllers/EmoteSuggestionController.cs b/src/GrillBot.App/Controllers/EmoteSuggestionController.cs index 169d1b897..a48a357f9 100644 --- a/src/GrillBot.App/Controllers/EmoteSuggestionController.cs +++ b/src/GrillBot.App/Controllers/EmoteSuggestionController.cs @@ -13,7 +13,7 @@ namespace GrillBot.App.Controllers; [Route("api/emotes/suggestion")] [Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme, Roles = "Admin")] [ApiExplorerSettings(GroupName = "v1")] -public class EmoteSuggestionController : Infrastructure.ControllerBase +public class EmoteSuggestionController : Core.Infrastructure.Actions.ControllerBase { public EmoteSuggestionController(IServiceProvider serviceProvider) : base(serviceProvider) { @@ -25,11 +25,11 @@ public EmoteSuggestionController(IServiceProvider serviceProvider) : base(servic /// Return paginated list of emote suggestions /// Validation of parameters failed [HttpPost("list")] - [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(typeof(PaginatedResponse), StatusCodes.Status200OK)] [ProducesResponseType(typeof(ValidationProblemDetails), StatusCodes.Status400BadRequest)] - public async Task>> GetSuggestionListAsync([FromBody] GetSuggestionsListParams parameters) + public async Task GetSuggestionListAsync([FromBody] GetSuggestionsListParams parameters) { ApiAction.Init(this, parameters); - return Ok(await ProcessActionAsync>(action => action.ProcessAsync(parameters))); + return await ProcessAsync(parameters); } } diff --git a/src/GrillBot.App/Controllers/EmotesController.cs b/src/GrillBot.App/Controllers/EmotesController.cs index 2617e844b..d7bd83860 100644 --- a/src/GrillBot.App/Controllers/EmotesController.cs +++ b/src/GrillBot.App/Controllers/EmotesController.cs @@ -14,7 +14,7 @@ namespace GrillBot.App.Controllers; [Route("api/emotes")] [Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme, Roles = "Admin")] [ApiExplorerSettings(GroupName = "v1")] -public class EmotesController : Infrastructure.ControllerBase +public class EmotesController : Core.Infrastructure.Actions.ControllerBase { public EmotesController(IServiceProvider serviceProvider) : base(serviceProvider) { @@ -26,12 +26,12 @@ public EmotesController(IServiceProvider serviceProvider) : base(serviceProvider /// Return paginated list with statistics of emotes. /// Validation of parameters failed. [HttpPost("stats/list/{unsupported}")] - [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(typeof(PaginatedResponse), StatusCodes.Status200OK)] [ProducesResponseType(typeof(ValidationProblemDetails), StatusCodes.Status400BadRequest)] - public async Task>> GetStatsOfEmotesAsync([FromBody] EmotesListParams @params, bool unsupported) + public async Task GetStatsOfEmotesAsync([FromBody] EmotesListParams @params, bool unsupported) { ApiAction.Init(this, @params); - return Ok(await ProcessActionAsync>(action => action.ProcessAsync(@params, unsupported))); + return await ProcessAsync(@params, unsupported); } /// @@ -40,12 +40,12 @@ public async Task>> GetStatsO /// Returns count of changed rows in the database. /// Validation of parameters failed. [HttpPost("stats/merge")] - [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(typeof(int), StatusCodes.Status200OK)] [ProducesResponseType(typeof(ValidationProblemDetails), StatusCodes.Status400BadRequest)] - public async Task> MergeStatsToAnotherAsync([FromBody] MergeEmoteStatsParams @params) + public async Task MergeStatsToAnotherAsync([FromBody] MergeEmoteStatsParams @params) { ApiAction.Init(this, @params); - return Ok(await ProcessActionAsync(action => action.ProcessAsync(@params))); + return await ProcessAsync(@params); } /// @@ -54,12 +54,12 @@ public async Task> MergeStatsToAnotherAsync([FromBody] MergeEm /// Returns count of changed rows in the database. /// Validation of EmoteId failed. [HttpDelete("stats")] - [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(typeof(int), StatusCodes.Status200OK)] [ProducesResponseType(typeof(ValidationProblemDetails), StatusCodes.Status400BadRequest)] - public async Task> RemoveStatisticsAsync( + public async Task RemoveStatisticsAsync( [Required(ErrorMessage = "Pro smazání je vyžadováno EmoteId.")] [EmoteId(ErrorMessage = "Zadaný vstup není EmoteId.")] string emoteId - ) => Ok(await ProcessActionAsync(action => action.ProcessAsync(emoteId))); + ) => await ProcessAsync(emoteId); /// /// Get a paginated list of users who use a specific emote. @@ -67,10 +67,10 @@ string emoteId /// Returns paginated list of users. /// Validation of parameters failed. [HttpPost("list/users")] - [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(typeof(PaginatedResponse), StatusCodes.Status200OK)] [ProducesResponseType(typeof(ValidationProblemDetails), StatusCodes.Status400BadRequest)] - public async Task>> GetUserStatisticsOfEmoteAsync([FromBody] EmoteStatsUserListParams parameters) - => Ok(await ProcessActionAsync>(action => action.ProcessAsync(parameters))); + public async Task GetUserStatisticsOfEmoteAsync([FromBody] EmoteStatsUserListParams parameters) + => await ProcessAsync(parameters); /// /// Get statistics of one emote. @@ -79,10 +79,10 @@ public async Task>> GetUs /// Validation failed. /// Emote not found. [HttpGet("stats")] - [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(typeof(EmoteStatItem), StatusCodes.Status200OK)] [ProducesResponseType(typeof(ValidationProblemDetails), StatusCodes.Status400BadRequest)] - public async Task> GetStatOfEmoteAsync( + public async Task GetStatOfEmoteAsync( [Required(ErrorMessage = "Je vyžadování EmoteId.")] [EmoteId(ErrorMessage = "Zadaný vstup není EmoteId.")] string emoteId - ) => Ok(await ProcessActionAsync(action => action.ProcessAsync(emoteId))); + ) => await ProcessAsync(emoteId); } diff --git a/src/GrillBot.App/Controllers/GuildController.cs b/src/GrillBot.App/Controllers/GuildController.cs index 0ca9e3bab..51e43d3db 100644 --- a/src/GrillBot.App/Controllers/GuildController.cs +++ b/src/GrillBot.App/Controllers/GuildController.cs @@ -16,7 +16,7 @@ namespace GrillBot.App.Controllers; [ApiController] [Route("api/guild")] [ApiExplorerSettings(GroupName = "v1")] -public class GuildController : Infrastructure.ControllerBase +public class GuildController : Core.Infrastructure.Actions.ControllerBase { public GuildController(IServiceProvider serviceProvider) : base(serviceProvider) { @@ -28,14 +28,13 @@ public GuildController(IServiceProvider serviceProvider) : base(serviceProvider) /// Return paginated list of guilds in DB. /// Validation of parameters failed. [HttpPost("list")] - [ProducesResponseType((int)HttpStatusCode.OK)] - [ProducesResponseType(typeof(ValidationProblemDetails), (int)HttpStatusCode.BadRequest)] + [ProducesResponseType(typeof(PaginatedResponse), StatusCodes.Status200OK)] + [ProducesResponseType(typeof(ValidationProblemDetails), StatusCodes.Status400BadRequest)] [Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme, Roles = "Admin")] - public async Task>> GetGuildListAsync([FromBody] GetGuildListParams parameters) + public async Task GetGuildListAsync([FromBody] GetGuildListParams parameters) { ApiAction.Init(this, parameters); - - return Ok(await ProcessActionAsync>(action => action.ProcessAsync(parameters))); + return await ProcessAsync(parameters); } /// @@ -43,11 +42,11 @@ public async Task>> GetGuildListAsync([Fro /// /// Guild ID [HttpGet("{id}")] - [ProducesResponseType((int)HttpStatusCode.OK)] - [ProducesResponseType(typeof(MessageResponse), (int)HttpStatusCode.NotFound)] + [ProducesResponseType(typeof(GuildDetail), StatusCodes.Status200OK)] + [ProducesResponseType(typeof(MessageResponse), StatusCodes.Status404NotFound)] [Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme, Roles = "Admin")] - public async Task> GetGuildDetailAsync(ulong id) - => Ok(await ProcessActionAsync(action => action.ProcessAsync(id))); + public async Task GetGuildDetailAsync(ulong id) + => await ProcessAsync(id); /// /// Update guild @@ -56,14 +55,14 @@ public async Task> GetGuildDetailAsync(ulong id) /// Validation of parameters failed. /// Guild not found. [HttpPut("{id}")] - [ProducesResponseType((int)HttpStatusCode.OK)] - [ProducesResponseType(typeof(ValidationProblemDetails), (int)HttpStatusCode.BadRequest)] - [ProducesResponseType(typeof(MessageResponse), (int)HttpStatusCode.NotFound)] + [ProducesResponseType(typeof(GuildDetail), StatusCodes.Status200OK)] + [ProducesResponseType(typeof(ValidationProblemDetails), StatusCodes.Status400BadRequest)] + [ProducesResponseType(typeof(MessageResponse), StatusCodes.Status404NotFound)] [Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme, Roles = "Admin")] - public async Task> UpdateGuildAsync(ulong id, [FromBody] UpdateGuildParams parameters) + public async Task UpdateGuildAsync(ulong id, [FromBody] UpdateGuildParams parameters) { ApiAction.Init(this, parameters); - return Ok(await ProcessActionAsync(action => action.ProcessAsync(id, parameters))); + return await ProcessAsync(id, parameters); } /// @@ -77,13 +76,13 @@ public async Task> UpdateGuildAsync(ulong id, [FromBod [ApiKeyAuth] [ApiExplorerSettings(GroupName = "v2")] [HttpPost("{guildId}/event")] - [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(typeof(ulong), StatusCodes.Status200OK)] [ProducesResponseType(typeof(ValidationProblemDetails), StatusCodes.Status400BadRequest)] [ProducesResponseType(typeof(MessageResponse), StatusCodes.Status404NotFound)] - public async Task> CreateScheduledEventAsync(ulong guildId, [FromBody] ScheduledEventParams parameters) + public async Task CreateScheduledEventAsync(ulong guildId, [FromBody] ScheduledEventParams parameters) { ApiAction.Init(this, parameters); - return Ok(await ProcessActionAsync(action => action.ProcessAsync(guildId, parameters))); + return await ProcessAsync(guildId, parameters); } /// @@ -103,12 +102,10 @@ public async Task> CreateScheduledEventAsync(ulong guildId, [ProducesResponseType(typeof(ValidationProblemDetails), StatusCodes.Status400BadRequest)] [ProducesResponseType(typeof(MessageResponse), StatusCodes.Status403Forbidden)] [ProducesResponseType(typeof(MessageResponse), StatusCodes.Status404NotFound)] - public async Task UpdateScheduledEventAsync(ulong guildId, ulong eventId, [FromBody] ScheduledEventParams parameters) + public async Task UpdateScheduledEventAsync(ulong guildId, ulong eventId, [FromBody] ScheduledEventParams parameters) { ApiAction.Init(this, parameters); - - await ProcessActionAsync(action => action.ProcessAsync(guildId, eventId, parameters)); - return Ok(); + return await ProcessAsync(guildId, eventId, parameters); } /// @@ -127,9 +124,6 @@ public async Task UpdateScheduledEventAsync(ulong guildId, ulong e [ProducesResponseType(typeof(ValidationProblemDetails), StatusCodes.Status400BadRequest)] [ProducesResponseType(typeof(MessageResponse), StatusCodes.Status403Forbidden)] [ProducesResponseType(typeof(MessageResponse), StatusCodes.Status404NotFound)] - public async Task CancelScheduledEventAsync(ulong guildId, ulong eventId) - { - await ProcessActionAsync(action => action.ProcessAsync(guildId, eventId)); - return Ok(); - } + public async Task CancelScheduledEventAsync(ulong guildId, ulong eventId) + => await ProcessAsync(guildId, eventId); } diff --git a/src/GrillBot.App/Controllers/InviteController.cs b/src/GrillBot.App/Controllers/InviteController.cs index 75fcf184e..6532b3da0 100644 --- a/src/GrillBot.App/Controllers/InviteController.cs +++ b/src/GrillBot.App/Controllers/InviteController.cs @@ -14,7 +14,7 @@ namespace GrillBot.App.Controllers; [Route("api/invite")] [Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme, Roles = "Admin")] [ApiExplorerSettings(GroupName = "v1")] -public class InviteController : Infrastructure.ControllerBase +public class InviteController : Core.Infrastructure.Actions.ControllerBase { public InviteController(IServiceProvider serviceProvider) : base(serviceProvider) { @@ -26,12 +26,12 @@ public InviteController(IServiceProvider serviceProvider) : base(serviceProvider /// Returns paginated list of created and used invites. /// Validation of parameters failed. [HttpPost("list")] - [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(typeof(PaginatedResponse), StatusCodes.Status200OK)] [ProducesResponseType(typeof(ValidationProblemDetails), StatusCodes.Status400BadRequest)] - public async Task>> GetInviteListAsync([FromBody] GetInviteListParams parameters) + public async Task GetInviteListAsync([FromBody] GetInviteListParams parameters) { ApiAction.Init(this, parameters); - return Ok(await ProcessActionAsync>(action => action.ProcessAsync(parameters))); + return await ProcessAsync(parameters); } /// @@ -39,18 +39,18 @@ public async Task>> GetInviteListAsy /// /// Returns report per server. [HttpPost("metadata/refresh")] - [ProducesResponseType(StatusCodes.Status200OK)] - public async Task>> RefreshMetadataCacheAsync() - => Ok(await ProcessActionAsync>(action => action.ProcessAsync(true))); + [ProducesResponseType(typeof(Dictionary), StatusCodes.Status200OK)] + public async Task RefreshMetadataCacheAsync() + => await ProcessAsync(true); /// /// Get count of items in metadata cache. /// /// Returns count of current items in cache. [HttpGet("metadata/count")] - [ProducesResponseType(StatusCodes.Status200OK)] - public async Task> GetCurrentMetadataCountAsync() - => Ok(await ProcessActionAsync(action => action.ProcessAsync())); + [ProducesResponseType(typeof(int), StatusCodes.Status200OK)] + public async Task GetCurrentMetadataCountAsync() + => await ProcessAsync(); /// /// Delete invite. @@ -60,9 +60,6 @@ public async Task> GetCurrentMetadataCountAsync() [HttpDelete("{guildId}/{code}")] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(typeof(MessageResponse), StatusCodes.Status404NotFound)] - public async Task DeleteInviteAsync(ulong guildId, string code) - { - await ProcessActionAsync(action => action.ProcessAsync(guildId, code)); - return Ok(); - } + public async Task DeleteInviteAsync(ulong guildId, string code) + => await ProcessAsync(guildId, code); } diff --git a/src/GrillBot.App/Controllers/PointsController.cs b/src/GrillBot.App/Controllers/PointsController.cs index 54245ed52..e6d5ce689 100644 --- a/src/GrillBot.App/Controllers/PointsController.cs +++ b/src/GrillBot.App/Controllers/PointsController.cs @@ -18,7 +18,7 @@ namespace GrillBot.App.Controllers; [ApiController] [Route("api/user/points")] [ApiExplorerSettings(GroupName = "v1")] -public class PointsController : Infrastructure.ControllerBase +public class PointsController : Core.Infrastructure.Actions.ControllerBase { public PointsController(IServiceProvider serviceProvider) : base(serviceProvider) { @@ -30,9 +30,9 @@ public PointsController(IServiceProvider serviceProvider) : base(serviceProvider /// Returns full points board. [HttpGet("board")] [Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme, Roles = "User")] - [ProducesResponseType(StatusCodes.Status200OK)] - public async Task>> GetPointsLeaderboardAsync() - => Ok(await ProcessActionAsync>(action => action.ProcessAsync())); + [ProducesResponseType(typeof(List), StatusCodes.Status200OK)] + public async Task GetPointsLeaderboardAsync() + => await ProcessAsync(); /// /// Get paginated list of transactions. @@ -41,12 +41,12 @@ public async Task>> GetPointsLeaderboardAsync( /// Validation failed. [HttpPost("transactions/list")] [Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme, Roles = "Admin")] - [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(typeof(PaginatedResponse), StatusCodes.Status200OK)] [ProducesResponseType(typeof(ValidationProblemDetails), StatusCodes.Status400BadRequest)] - public async Task>> GetTransactionListAsync([FromBody] AdminListRequest request) + public async Task GetTransactionListAsync([FromBody] AdminListRequest request) { ApiAction.Init(this, request); - return Ok(await ProcessActionAsync>(action => action.ProcessAsync(request))); + return await ProcessAsync(request); } /// @@ -56,21 +56,21 @@ public async Task>> GetTransac /// Validation failed. [HttpPost("graph/data")] [Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme, Roles = "Admin")] - [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(typeof(List), StatusCodes.Status200OK)] [ProducesResponseType(typeof(ValidationProblemDetails), StatusCodes.Status400BadRequest)] - public async Task>> GetGraphDataAsync([FromBody] AdminListRequest parameters) + public async Task GetGraphDataAsync([FromBody] AdminListRequest parameters) { ApiAction.Init(this, parameters); - return Ok(await ProcessActionAsync>( - bridge => bridge.ExecuteAsync>(async client => - { - var result = await client.GetChartDataAsync(parameters); - result.ValidationErrors?.AggregateAndThrow(); + var executor = new Func>(async (IPointsServiceClient client) => + { + var result = await client.GetChartDataAsync(parameters); + result.ValidationErrors.AggregateAndThrow(); + + return result.Response!; + }); - return result.Response!; - }) - )); + return await ProcessAsync>(executor); } /// @@ -79,9 +79,9 @@ public async Task>> GetGraphDataAsync([FromBo /// Returns points state of user. Grouped per guilds. [HttpGet("{userId}")] [Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme, Roles = "Admin")] - [ProducesResponseType(StatusCodes.Status200OK)] - public async Task>> ComputeUserPointsAsync(ulong userId) - => Ok(await ProcessActionAsync>(action => action.ProcessAsync(userId))); + [ProducesResponseType(typeof(List), StatusCodes.Status200OK)] + public async Task ComputeUserPointsAsync(ulong userId) + => await ProcessAsync(userId); /// /// Compute current points status of user. @@ -89,9 +89,9 @@ public async Task>> ComputeUserPointsAsync(ulo /// Returns points state of user. Grouped per guilds. [HttpGet("me")] [Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme, Roles = "User")] - [ProducesResponseType(StatusCodes.Status200OK)] - public async Task>> ComputeLoggedUserPointsAsync() - => Ok(await ProcessActionAsync>(action => action.ProcessAsync(null))); + [ProducesResponseType(typeof(List), StatusCodes.Status200OK)] + public async Task ComputeLoggedUserPointsAsync() + => await ProcessAsync(); /// /// Creation of a service transaction by users with bonus points. @@ -99,11 +99,8 @@ public async Task>> ComputeLoggedUserPointsAsy [HttpPut("service/increment/{guildId}/{toUserId}/{amount:int}")] [Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme, Roles = "Admin")] [ProducesResponseType(StatusCodes.Status200OK)] - public async Task ServiceIncrementPointsAsync(ulong guildId, ulong toUserId, int amount) - { - await ProcessActionAsync(action => action.ProcessAsync(guildId, toUserId, amount)); - return Ok(); - } + public async Task ServiceIncrementPointsAsync(ulong guildId, ulong toUserId, int amount) + => await ProcessAsync(guildId, toUserId, amount); /// /// Service transfer of points between accounts. @@ -111,11 +108,8 @@ public async Task ServiceIncrementPointsAsync(ulong guildId, ulong [HttpPut("service/transfer/{guildId}/{fromUserId}/{toUserId}/{amount:int}")] [Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme, Roles = "Admin")] [ProducesResponseType(StatusCodes.Status200OK)] - public async Task ServiceTransferPointsAsync(ulong guildId, ulong fromUserId, ulong toUserId, int amount) - { - await ProcessActionAsync(action => action.ProcessAsync(guildId, fromUserId, toUserId, amount)); - return Ok(); - } + public async Task ServiceTransferPointsAsync(ulong guildId, ulong fromUserId, ulong toUserId, int amount) + => await ProcessAsync(guildId, fromUserId, toUserId, amount); /// /// Remove transaction. @@ -124,17 +118,17 @@ public async Task ServiceTransferPointsAsync(ulong guildId, ulong [HttpDelete("{guildId}/{messageId}")] [Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme, Roles = "Admin")] [ProducesResponseType(StatusCodes.Status200OK)] - public async Task DeleteTransactionAsync(string guildId, string messageId, string? reactionId) + public async Task DeleteTransactionAsync(string guildId, string messageId, string? reactionId) { - await ProcessActionAsync(bridge => bridge.ExecuteAsync(async client => + var executor = new Func(async (IPointsServiceClient client) => { if (!string.IsNullOrEmpty(reactionId)) await client.DeleteTransactionAsync(guildId, messageId, reactionId); else await client.DeleteTransactionAsync(guildId, messageId); - })); + }); - return Ok(); + return await ProcessAsync>(executor); } /// @@ -143,12 +137,12 @@ await ProcessActionAsync(bridge => bridge.ExecuteAsyncReturns paginated list of users from points service. /// Validation failed. [HttpPost("users/list")] - [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(typeof(PaginatedResponse), StatusCodes.Status200OK)] [ProducesResponseType(typeof(ValidationProblemDetails), StatusCodes.Status400BadRequest)] [Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme, Roles = "Admin")] - public async Task>> GetUserListAsync(UserListRequest request) + public async Task GetUserListAsync(UserListRequest request) { ApiAction.Init(this, request); - return await ProcessActionAsync>(action => action.ProcessAsync(request)); + return await ProcessAsync(request); } } diff --git a/src/GrillBot.App/Controllers/PublicApiClientsController.cs b/src/GrillBot.App/Controllers/PublicApiClientsController.cs index ae4d2e443..596736173 100644 --- a/src/GrillBot.App/Controllers/PublicApiClientsController.cs +++ b/src/GrillBot.App/Controllers/PublicApiClientsController.cs @@ -14,7 +14,7 @@ namespace GrillBot.App.Controllers; [Route("api/publicApiClients")] [Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme, Roles = "Admin")] [ApiExplorerSettings(GroupName = "v1")] -public class PublicApiClientsController : Infrastructure.ControllerBase +public class PublicApiClientsController : Core.Infrastructure.Actions.ControllerBase { public PublicApiClientsController(IServiceProvider serviceProvider) : base(serviceProvider) { @@ -25,9 +25,9 @@ public PublicApiClientsController(IServiceProvider serviceProvider) : base(servi /// /// Returns list of clients [HttpGet] - [ProducesResponseType(StatusCodes.Status200OK)] - public async Task>> GetClientsListAsync() - => Ok(await ProcessActionAsync>(action => action.ProcessAsync())); + [ProducesResponseType(typeof(List), StatusCodes.Status200OK)] + public async Task GetClientsListAsync() + => await ProcessAsync(); /// /// Create new client. @@ -37,12 +37,10 @@ public async Task>> GetClientsListAsync() [HttpPost] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(typeof(ValidationProblemDetails), StatusCodes.Status400BadRequest)] - public async Task CreateClientAsync([FromBody] ApiClientParams parameters) + public async Task CreateClientAsync([FromBody] ApiClientParams parameters) { ApiAction.Init(this, parameters); - - await ProcessActionAsync(action => action.ProcessAsync(parameters)); - return Ok(); + return await ProcessAsync(parameters); } /// @@ -53,11 +51,8 @@ public async Task CreateClientAsync([FromBody] ApiClientParams par [HttpDelete("{id}")] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(typeof(MessageResponse), StatusCodes.Status404NotFound)] - public async Task DeleteClientAsync(string id) - { - await ProcessActionAsync(action => action.ProcessAsync(id)); - return Ok(); - } + public async Task DeleteClientAsync(string id) + => await ProcessAsync(id); /// /// Update existing client. @@ -69,12 +64,10 @@ public async Task DeleteClientAsync(string id) [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(typeof(ValidationProblemDetails), StatusCodes.Status400BadRequest)] [ProducesResponseType(typeof(MessageResponse), StatusCodes.Status404NotFound)] - public async Task UpdateClientAsync(string id, [FromBody] ApiClientParams parameters) + public async Task UpdateClientAsync(string id, [FromBody] ApiClientParams parameters) { ApiAction.Init(this, parameters); - - await ProcessActionAsync(action => action.ProcessAsync(id, parameters)); - return Ok(); + return await ProcessAsync(id, parameters); } /// @@ -83,8 +76,8 @@ public async Task UpdateClientAsync(string id, [FromBody] ApiClien /// Returns client data /// Client not found [HttpGet("{id}")] - [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(typeof(ApiClient), StatusCodes.Status200OK)] [ProducesResponseType(typeof(MessageResponse), StatusCodes.Status404NotFound)] - public async Task> GetClientAsync(string id) - => await ProcessActionAsync(action => action.ProcessAsync(id)); + public async Task GetClientAsync(string id) + => await ProcessAsync(id); } diff --git a/src/GrillBot.App/Controllers/ReminderController.cs b/src/GrillBot.App/Controllers/ReminderController.cs index 46aa8f761..fd0e0c6f3 100644 --- a/src/GrillBot.App/Controllers/ReminderController.cs +++ b/src/GrillBot.App/Controllers/ReminderController.cs @@ -7,14 +7,13 @@ using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; -using Microsoft.Extensions.DependencyInjection; namespace GrillBot.App.Controllers; [ApiController] [Route("api/remind")] [ApiExplorerSettings(GroupName = "v1")] -public class ReminderController : Infrastructure.ControllerBase +public class ReminderController : Core.Infrastructure.Actions.ControllerBase { public ReminderController(IServiceProvider serviceProvider) : base(serviceProvider) { @@ -27,12 +26,12 @@ public ReminderController(IServiceProvider serviceProvider) : base(serviceProvid /// Validation of parameters failed. [HttpPost("list")] [Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme, Roles = "Admin,User")] - [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(typeof(PaginatedResponse), StatusCodes.Status200OK)] [ProducesResponseType(typeof(ValidationProblemDetails), StatusCodes.Status400BadRequest)] - public async Task>> GetRemindMessagesListAsync([FromBody] GetReminderListParams parameters) + public async Task GetRemindMessagesListAsync([FromBody] GetReminderListParams parameters) { ApiAction.Init(this, parameters); - return Ok(await ProcessActionAsync>(action => action.ProcessAsync(parameters))); + return await ProcessAsync(parameters); } /// @@ -45,19 +44,9 @@ public async Task>> GetRemindMessa /// Remind was notified or cancelled. [HttpDelete("{id:long}")] [Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme, Roles = "Admin")] - [ProducesResponseType((int)HttpStatusCode.OK)] - [ProducesResponseType(typeof(MessageResponse), (int)HttpStatusCode.NotFound)] - [ProducesResponseType(typeof(MessageResponse), (int)HttpStatusCode.Gone)] - public async Task CancelRemindAsync(long id, [FromQuery] bool notify = false) - { - var (isGone, errorMessage) = await ProcessActionAsync(async action => - { - await action.ProcessAsync(id, notify, true); - return (action.IsGone, action.ErrorMessage); - }); - - if (isGone) - return StatusCode((int)HttpStatusCode.Gone, new MessageResponse(errorMessage!)); - return Ok(); - } + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(typeof(MessageResponse), StatusCodes.Status404NotFound)] + [ProducesResponseType(typeof(MessageResponse), StatusCodes.Status410Gone)] + public async Task CancelRemindAsync(long id, [FromQuery] bool notify = false) + => await ProcessAsync(id, notify, true); } diff --git a/src/GrillBot.App/Controllers/ScheduledJobsController.cs b/src/GrillBot.App/Controllers/ScheduledJobsController.cs index d9809fd90..2f7cbd921 100644 --- a/src/GrillBot.App/Controllers/ScheduledJobsController.cs +++ b/src/GrillBot.App/Controllers/ScheduledJobsController.cs @@ -12,7 +12,7 @@ namespace GrillBot.App.Controllers; [Route("api/jobs")] [ApiExplorerSettings(GroupName = "v1")] [Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme, Roles = "Admin")] -public class ScheduledJobsController : Infrastructure.ControllerBase +public class ScheduledJobsController : Core.Infrastructure.Actions.ControllerBase { public ScheduledJobsController(IServiceProvider serviceProvider) : base(serviceProvider) { @@ -23,9 +23,9 @@ public ScheduledJobsController(IServiceProvider serviceProvider) : base(serviceP /// /// Returns list of scheduled jobs [HttpGet] - [ProducesResponseType(StatusCodes.Status200OK)] - public async Task>> GetScheduledJobsAsync() - => Ok(await ProcessActionAsync>(action => action.ProcessAsync())); + [ProducesResponseType(typeof(List), StatusCodes.Status200OK)] + public async Task GetScheduledJobsAsync() + => await ProcessAsync(); /// /// Trigger a scheduled job. @@ -33,11 +33,8 @@ public async Task>> GetScheduledJobsAsync() /// [HttpPost] [ProducesResponseType(StatusCodes.Status200OK)] - public async Task RunScheduledJobAsync(string jobName) - { - await ProcessActionAsync(action => action.ProcessAsync(jobName)); - return Ok(); - } + public async Task RunScheduledJobAsync(string jobName) + => await ProcessAsync(jobName); /// /// Update an existing job. @@ -47,9 +44,6 @@ public async Task RunScheduledJobAsync(string jobName) [HttpPut] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(typeof(MessageResponse), StatusCodes.Status404NotFound)] - public async Task UpdateJobAsync(string jobName, bool enabled) - { - await ProcessActionAsync(action => action.ProcessAsync(jobName, enabled)); - return Ok(); - } + public async Task UpdateJobAsync(string jobName, bool enabled) + => await ProcessAsync(jobName, enabled); } diff --git a/src/GrillBot.App/Controllers/SearchingController.cs b/src/GrillBot.App/Controllers/SearchingController.cs index de0390422..3378e4aa5 100644 --- a/src/GrillBot.App/Controllers/SearchingController.cs +++ b/src/GrillBot.App/Controllers/SearchingController.cs @@ -5,15 +5,15 @@ using GrillBot.Data.Models.API.Searching; using Microsoft.AspNetCore.Authentication.JwtBearer; using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; -using Microsoft.Extensions.DependencyInjection; namespace GrillBot.App.Controllers; [ApiController] [Route("api/search")] [ApiExplorerSettings(GroupName = "v1")] -public class SearchingController : Infrastructure.ControllerBase +public class SearchingController : Core.Infrastructure.Actions.ControllerBase { public SearchingController(IServiceProvider serviceProvider) : base(serviceProvider) { @@ -26,12 +26,12 @@ public SearchingController(IServiceProvider serviceProvider) : base(serviceProvi /// Validation failed [HttpPost("list")] [Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme, Roles = "Admin, User")] - [ProducesResponseType((int)HttpStatusCode.OK)] - [ProducesResponseType(typeof(ValidationProblemDetails), (int)HttpStatusCode.BadRequest)] - public async Task>> GetSearchListAsync([FromBody] GetSearchingListParams parameters) + [ProducesResponseType(typeof(PaginatedResponse), StatusCodes.Status200OK)] + [ProducesResponseType(typeof(ValidationProblemDetails), StatusCodes.Status400BadRequest)] + public async Task GetSearchListAsync([FromBody] GetSearchingListParams parameters) { ApiAction.Init(this, parameters); - return Ok(await ProcessActionAsync>(action => action.ProcessAsync(parameters))); + return await ProcessAsync(parameters); } /// @@ -40,14 +40,13 @@ public async Task>> GetSearchL /// Success [HttpDelete] [Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme, Roles = "Admin")] - [ProducesResponseType((int)HttpStatusCode.OK)] - public async Task RemoveSearchesAsync([FromQuery(Name = "id")] long[] ids) + [ProducesResponseType(StatusCodes.Status200OK)] + public async Task RemoveSearchesAsync([FromQuery(Name = "id")] long[] ids) { var idsLogData = new DictionaryObject(); idsLogData.FromCollection(ids.Select((id, index) => new KeyValuePair($"[{index}]", id))); ApiAction.Init(this, idsLogData); - await ProcessActionAsync(action => action.ProcessAsync(ids)); - return Ok(); + return await ProcessAsync(ids); } } diff --git a/src/GrillBot.App/Controllers/SelfUnverifyController.cs b/src/GrillBot.App/Controllers/SelfUnverifyController.cs index 55fc94ea5..7e59b4431 100644 --- a/src/GrillBot.App/Controllers/SelfUnverifyController.cs +++ b/src/GrillBot.App/Controllers/SelfUnverifyController.cs @@ -13,7 +13,7 @@ namespace GrillBot.App.Controllers; [Route("api/selfunverify/keep")] [Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme, Roles = "Admin")] [ApiExplorerSettings(GroupName = "v1")] -public class SelfUnverifyController : Infrastructure.ControllerBase +public class SelfUnverifyController : Core.Infrastructure.Actions.ControllerBase { public SelfUnverifyController(IServiceProvider serviceProvider) : base(serviceProvider) { @@ -24,9 +24,9 @@ public SelfUnverifyController(IServiceProvider serviceProvider) : base(servicePr /// /// Success [HttpGet] - [ProducesResponseType(StatusCodes.Status200OK)] - public async Task>>> GetKeepablesListAsync() - => Ok(await ProcessActionAsync>>(action => action.ProcessAsync(null))); + [ProducesResponseType(typeof(Dictionary>), StatusCodes.Status200OK)] + public async Task GetKeepablesListAsync() + => await ProcessAsync((string?)null); /// /// Add new keepable role or channel. @@ -36,12 +36,10 @@ public async Task>>> GetKeepablesLi [HttpPost] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(typeof(ValidationProblemDetails), StatusCodes.Status400BadRequest)] - public async Task AddKeepableAsync([FromBody] List parameters) + public async Task AddKeepableAsync([FromBody] List parameters) { ApiAction.Init(this, parameters.OfType().ToArray()); - - await ProcessActionAsync(action => action.ProcessAsync(parameters)); - return Ok(); + return await ProcessAsync(parameters); } /// @@ -49,9 +47,10 @@ public async Task AddKeepableAsync([FromBody] List /// /// Success [HttpGet("exist")] - [ProducesResponseType(StatusCodes.Status200OK)] - public async Task> KeepableExistsAsync([FromQuery] KeepableParams parameters) - => Ok(await ProcessActionAsync(action => action.ProcessAsync(parameters))); + [ProducesResponseType(typeof(bool), StatusCodes.Status200OK)] + [ProducesResponseType(typeof(ValidationProblemDetails), StatusCodes.Status400BadRequest)] + public async Task KeepableExistsAsync([FromQuery] KeepableParams parameters) + => await ProcessAsync(parameters); /// /// Remove keepable item or group. @@ -61,9 +60,6 @@ public async Task> KeepableExistsAsync([FromQuery] KeepablePa [HttpDelete] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(typeof(ValidationProblemDetails), StatusCodes.Status400BadRequest)] - public async Task KeepableRemoveAsync(string group, string? name = null) - { - await ProcessActionAsync(action => action.ProcessAsync(group, name)); - return Ok(); - } + public async Task KeepableRemoveAsync(string group, string? name = null) + => await ProcessAsync(group, name); } diff --git a/src/GrillBot.App/Controllers/ServiceController.cs b/src/GrillBot.App/Controllers/ServiceController.cs index 704a8c833..d4a74048e 100644 --- a/src/GrillBot.App/Controllers/ServiceController.cs +++ b/src/GrillBot.App/Controllers/ServiceController.cs @@ -1,4 +1,5 @@ -using GrillBot.Core.Services.AuditLog; +using GrillBot.App.Actions.Api; +using GrillBot.Core.Services.AuditLog; using GrillBot.Core.Services.AuditLog.Models.Response.Info; using GrillBot.Core.Services.PointsService; using GrillBot.Data.Models.API.Services; @@ -6,7 +7,6 @@ using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; -using ControllerBase = GrillBot.App.Infrastructure.ControllerBase; namespace GrillBot.App.Controllers; @@ -14,7 +14,7 @@ namespace GrillBot.App.Controllers; [Route("api/service")] [Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme, Roles = "Admin")] [ApiExplorerSettings(GroupName = "v1")] -public class ServiceController : ControllerBase +public class ServiceController : Core.Infrastructure.Actions.ControllerBase { public ServiceController(IServiceProvider serviceProvider) : base(serviceProvider) { @@ -25,25 +25,31 @@ public ServiceController(IServiceProvider serviceProvider) : base(serviceProvide /// /// Returns service info. [HttpGet("{id}")] - [ProducesResponseType(StatusCodes.Status200OK)] - public async Task> GetServiceInfoAsync(string id) - => Ok(await ProcessActionAsync(action => action.ProcessAsync(id))); + [ProducesResponseType(typeof(ServiceInfo), StatusCodes.Status200OK)] + public async Task GetServiceInfoAsync(string id) + => await ProcessAsync(id); /// - /// Get additional status info of AuditLogService. + /// Get additional status info of AuditLogService. /// /// Returns additional status info. [HttpGet("auditLog/status")] - [ProducesResponseType(StatusCodes.Status200OK)] - public async Task> GetAuditLogStatusInfoAsync() - => Ok(await ProcessBridgeAsync(client => client.GetStatusInfoAsync())); + [ProducesResponseType(typeof(StatusInfo), StatusCodes.Status200OK)] + public async Task GetAuditLogStatusInfoAsync() + { + var executor = new Func>(async (IAuditLogServiceClient client) => await client.GetStatusInfoAsync()); + return await ProcessAsync>(executor); + } /// /// Get additional status info of PointsService. /// /// Returns additional status info. [HttpGet("points/status")] - [ProducesResponseType(StatusCodes.Status200OK)] - public async Task> GetPointsServiceSatusInfoAsync() - => Ok(await ProcessBridgeAsync(client => client.GetStatusInfoAsync())); + [ProducesResponseType(typeof(Core.Services.PointsService.Models.StatusInfo), StatusCodes.Status200OK)] + public async Task GetPointsServiceSatusInfoAsync() + { + var executor = new Func>(async (IPointsServiceClient client) => await client.GetStatusInfoAsync()); + return await ProcessAsync>(executor); + } } diff --git a/src/GrillBot.App/Controllers/StatisticsController.cs b/src/GrillBot.App/Controllers/StatisticsController.cs index 0e2e0b979..924f1d663 100644 --- a/src/GrillBot.App/Controllers/StatisticsController.cs +++ b/src/GrillBot.App/Controllers/StatisticsController.cs @@ -1,4 +1,5 @@ -using GrillBot.App.Actions.Api.V1.Statistics; +using GrillBot.App.Actions.Api; +using GrillBot.App.Actions.Api.V1.Statistics; using GrillBot.Core.Services.AuditLog; using GrillBot.Data.Models.API.Statistics; using Microsoft.AspNetCore.Authentication.JwtBearer; @@ -13,7 +14,7 @@ namespace GrillBot.App.Controllers; [Route("api/stats")] [Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme, Roles = "Admin")] [ApiExplorerSettings(GroupName = "v1")] -public class StatisticsController : Infrastructure.ControllerBase +public class StatisticsController : Core.Infrastructure.Actions.ControllerBase { public StatisticsController(IServiceProvider serviceProvider) : base(serviceProvider) { @@ -24,93 +25,105 @@ public StatisticsController(IServiceProvider serviceProvider) : base(serviceProv /// /// Returns statistics about database and cache. [HttpGet("db")] - [ProducesResponseType(StatusCodes.Status200OK)] - public async Task> GetDbStatusAsync() - => Ok(await ProcessActionAsync(action => action.ProcessAsync())); + [ProducesResponseType(typeof(DatabaseStatistics), StatusCodes.Status200OK)] + public async Task GetDbStatusAsync() + => await ProcessAsync(); /// /// Get statistics about audit logs. /// /// Returns statistics about audit log (by type, by date, files by count, files by size) [HttpGet("audit-log")] - [ProducesResponseType(StatusCodes.Status200OK)] - public async Task> GetAuditLogStatisticsAsync() - => Ok(await ProcessBridgeAsync(client => client.GetAuditLogStatisticsAsync())); + [ProducesResponseType(typeof(AuditLog.AuditLogStatistics), StatusCodes.Status200OK)] + public async Task GetAuditLogStatisticsAsync() + { + var executor = new Func>(async (IAuditLogServiceClient client) => await client.GetAuditLogStatisticsAsync()); + return await ProcessAsync>(executor); + } /// /// Gets statistics about interactions. /// /// Returns statistics about interaction commannds [HttpGet("interactions")] - [ProducesResponseType(StatusCodes.Status200OK)] - public async Task> GetInteractionsStatusAsync() - => Ok(await ProcessBridgeAsync(client => client.GetInteractionStatisticsAsync())); + [ProducesResponseType(typeof(AuditLog.InteractionStatistics), StatusCodes.Status200OK)] + public async Task GetInteractionsStatusAsync() + { + var executor = new Func>(async (IAuditLogServiceClient client) => await client.GetInteractionStatisticsAsync()); + return await ProcessAsync>(executor); + } /// /// Get statistics about unverify logs by type. /// /// Returns dictionary of unverify logs statistics per type. (Type, Count) [HttpGet("unverify-logs/type")] - [ProducesResponseType(StatusCodes.Status200OK)] - public async Task>> GetUnverifyLogsStatisticsByOperationAsync() - => Ok(await ProcessActionAsync>(action => action.ProcessByOperationAsync())); + [ProducesResponseType(typeof(Dictionary), StatusCodes.Status200OK)] + public async Task GetUnverifyLogsStatisticsByOperationAsync() + => await ProcessAsync("ByOperation"); /// /// Get statistics about unverify logs by date and year. /// /// Returns dictionary of unverify logs statistics per date (Year-Month, Count) [HttpGet("unverify-logs/date")] - [ProducesResponseType(StatusCodes.Status200OK)] - public async Task>> GetUnverifyLogsStatisticsByDateAsync() - => Ok(await ProcessActionAsync>(action => action.ProcessByDateAsync())); + [ProducesResponseType(typeof(Dictionary), StatusCodes.Status200OK)] + public async Task GetUnverifyLogsStatisticsByDateAsync() + => await ProcessAsync("ByDate"); /// /// Get statistics about API. /// /// [HttpGet("api")] - [ProducesResponseType(StatusCodes.Status200OK)] - public async Task> GetApiStatisticsAsync() - => Ok(await ProcessBridgeAsync(client => client.GetApiStatisticsAsync())); + [ProducesResponseType(typeof(AuditLog.ApiStatistics), StatusCodes.Status200OK)] + public async Task GetApiStatisticsAsync() + { + var executor = new Func>(async (IAuditLogServiceClient client) => await client.GetApiStatisticsAsync()); + return await ProcessAsync>(executor); + } /// /// Get Discord event statistics. /// /// Returns dictionary of Discord event statistics (EventName, Count). [HttpGet("events")] - [ProducesResponseType(StatusCodes.Status200OK)] - public ActionResult> GetEventLogStatistics() - => Ok(ProcessAction>(action => action.Process())); + [ProducesResponseType(typeof(Dictionary), StatusCodes.Status200OK)] + public async Task GetEventLogStatisticsAsync() + => await ProcessAsync(); /// /// Get average execution times. /// [HttpGet("avg-times")] - [ProducesResponseType(StatusCodes.Status200OK)] - public async Task> GetAvgTimesAsync() - => Ok(await ProcessBridgeAsync(client => client.GetAvgTimesAsync())); + [ProducesResponseType(typeof(AuditLog.AvgExecutionTimes), StatusCodes.Status200OK)] + public async Task GetAvgTimesAsync() + { + var executor = new Func>(async (IAuditLogServiceClient client) => await client.GetAvgTimesAsync()); + return await ProcessAsync>(executor); + } /// /// Get full statistics of operations. /// [HttpGet("operations")] - [ProducesResponseType(StatusCodes.Status200OK)] - public ActionResult GetOperationStatistics() - => Ok(ProcessAction(action => action.Process())); + [ProducesResponseType(typeof(OperationStats), StatusCodes.Status200OK)] + public async Task GetOperationStatisticsAsync() + => await ProcessAsync(); /// /// Get statistics of commands cross grouped with users. /// [HttpGet("interactions/users")] - [ProducesResponseType(StatusCodes.Status200OK)] - public async Task>> GetUserCommandStatisticsAsync() - => Ok(await ProcessActionAsync>(action => action.ProcessAsync())); + [ProducesResponseType(typeof(List), StatusCodes.Status200OK)] + public async Task GetUserCommandStatisticsAsync() + => await ProcessAsync(); /// /// Get statistics of api requests cross grouped with users. /// [HttpGet("api/users")] - [ProducesResponseType(StatusCodes.Status200OK)] - public async Task>> GetUserApiStatisticsAsync([Required] string criteria) - => Ok(await ProcessActionAsync>(action => action.ProcessAsync(criteria))); + [ProducesResponseType(typeof(List), StatusCodes.Status200OK)] + public async Task GetUserApiStatisticsAsync([Required] string criteria) + => await ProcessAsync(criteria); } diff --git a/src/GrillBot.App/Controllers/SystemController.cs b/src/GrillBot.App/Controllers/SystemController.cs index 178baa27b..ba6588961 100644 --- a/src/GrillBot.App/Controllers/SystemController.cs +++ b/src/GrillBot.App/Controllers/SystemController.cs @@ -1,4 +1,5 @@ -using Microsoft.AspNetCore.Authentication.JwtBearer; +using GrillBot.App.Actions.Api.V1.System; +using Microsoft.AspNetCore.Authentication.JwtBearer; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; @@ -9,7 +10,7 @@ namespace GrillBot.App.Controllers; [Route("api/system")] [Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme, Roles = "Admin")] [ApiExplorerSettings(GroupName = "v1")] -public class SystemController : Infrastructure.ControllerBase +public class SystemController : Core.Infrastructure.Actions.ControllerBase { public SystemController(IServiceProvider serviceProvider) : base(serviceProvider) { @@ -20,19 +21,16 @@ public SystemController(IServiceProvider serviceProvider) : base(serviceProvider /// /// Success [HttpPut("status")] - [ProducesResponseType((int)HttpStatusCode.OK)] - public ActionResult ChangeBotStatus(bool isActive) - { - ProcessAction(action => action.Process(isActive)); - return Ok(); - } + [ProducesResponseType(StatusCodes.Status200OK)] + public async Task ChangeBotStatusAsync(bool isActive) + => await ProcessAsync(isActive); /// /// Gets list of discord event logs. /// /// Returns last 1000 events from discord. [HttpGet("eventLog")] - [ProducesResponseType(StatusCodes.Status200OK)] - public ActionResult GetEventLog() - => Ok(ProcessAction(action => action.Process())); + [ProducesResponseType(typeof(string[]), StatusCodes.Status200OK)] + public async Task GetEventLogAsync() + => await ProcessAsync(); } diff --git a/src/GrillBot.App/Controllers/UnverifyController.cs b/src/GrillBot.App/Controllers/UnverifyController.cs index 073fe9ccc..16abd7f7d 100644 --- a/src/GrillBot.App/Controllers/UnverifyController.cs +++ b/src/GrillBot.App/Controllers/UnverifyController.cs @@ -6,13 +6,14 @@ using GrillBot.App.Actions; using GrillBot.App.Actions.Api.V1.Unverify; using GrillBot.Core.Models.Pagination; +using Microsoft.AspNetCore.Http; namespace GrillBot.App.Controllers; [ApiController] [Route("api/unverify")] [ApiExplorerSettings(GroupName = "v1")] -public class UnverifyController : Infrastructure.ControllerBase +public class UnverifyController : Core.Infrastructure.Actions.ControllerBase { public UnverifyController(IServiceProvider serviceProvider) : base(serviceProvider) { @@ -24,9 +25,9 @@ public UnverifyController(IServiceProvider serviceProvider) : base(serviceProvid /// Success [HttpGet("current")] [Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme, Roles = "Admin,User")] - [ProducesResponseType((int)HttpStatusCode.OK)] - public async Task>> GetCurrentUnverifiesAsync() - => Ok(await ProcessActionAsync>(action => action.ProcessAsync())); + [ProducesResponseType(typeof(List), StatusCodes.Status200OK)] + public async Task GetCurrentUnverifiesAsync() + => await ProcessAsync(); /// /// Removes unverify @@ -35,29 +36,23 @@ public async Task>> GetCurrentUnverifiesA /// Unverify or guild not found. [HttpDelete("{guildId}/{userId}")] [Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme, Roles = "Admin")] - [ProducesResponseType((int)HttpStatusCode.OK)] - [ProducesResponseType(typeof(MessageResponse), (int)HttpStatusCode.NotFound)] - public async Task> RemoveUnverifyAsync(ulong guildId, ulong userId, bool force = false) - { - return Ok(new MessageResponse( - await ProcessActionAsync(action => action.ProcessAsync(guildId, userId, force)) - )); - } + [ProducesResponseType(typeof(MessageResponse), StatusCodes.Status200OK)] + [ProducesResponseType(typeof(MessageResponse), StatusCodes.Status404NotFound)] + public async Task RemoveUnverifyAsync(ulong guildId, ulong userId, bool force = false) + => await ProcessAsync(guildId, userId, force); /// /// Update unverify time. /// [HttpPut("{guildId}/{userId}")] [Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme, Roles = "Admin")] - [ProducesResponseType((int)HttpStatusCode.OK)] - [ProducesResponseType(typeof(ValidationProblemDetails), (int)HttpStatusCode.BadRequest)] - [ProducesResponseType(typeof(MessageResponse), (int)HttpStatusCode.NotFound)] - public async Task> UpdateUnverifyTimeAsync(ulong guildId, ulong userId, UpdateUnverifyParams parameters) + [ProducesResponseType(typeof(MessageResponse), StatusCodes.Status200OK)] + [ProducesResponseType(typeof(ValidationProblemDetails), StatusCodes.Status400BadRequest)] + [ProducesResponseType(typeof(MessageResponse), StatusCodes.Status404NotFound)] + public async Task UpdateUnverifyTimeAsync(ulong guildId, ulong userId, UpdateUnverifyParams parameters) { ApiAction.Init(this, parameters); - - var result = await ProcessActionAsync(action => action.ProcessAsync(guildId, userId, parameters)); - return Ok(new MessageResponse(result)); + return await ProcessAsync(guildId, userId, parameters); } /// @@ -67,12 +62,12 @@ public async Task> UpdateUnverifyTimeAsync(ulong g /// Validation failed [HttpPost("log")] [Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme, Roles = "Admin,User")] - [ProducesResponseType((int)HttpStatusCode.OK)] - [ProducesResponseType(typeof(ValidationProblemDetails), (int)HttpStatusCode.BadRequest)] - public async Task>> GetUnverifyLogsAsync([FromBody] UnverifyLogParams parameters) + [ProducesResponseType(typeof(PaginatedResponse), StatusCodes.Status200OK)] + [ProducesResponseType(typeof(ValidationProblemDetails), StatusCodes.Status400BadRequest)] + public async Task GetUnverifyLogsAsync([FromBody] UnverifyLogParams parameters) { ApiAction.Init(this, parameters); - return Ok(await ProcessActionAsync>(action => action.ProcessAsync(parameters))); + return await ProcessAsync(parameters); } /// @@ -84,12 +79,9 @@ public async Task>> GetUnverifyL /// Unverify, guild or users not found. [HttpPost("log/{logId:long}/recover")] [Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme, Roles = "Admin")] - [ProducesResponseType((int)HttpStatusCode.OK)] - [ProducesResponseType(typeof(MessageResponse), (int)HttpStatusCode.NotFound)] - [ProducesResponseType(typeof(ValidationProblemDetails), (int)HttpStatusCode.BadRequest)] - public async Task RecoverUnverifyAsync(long logId) - { - await ProcessActionAsync(action => action.ProcessAsync(logId)); - return Ok(); - } + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(typeof(MessageResponse), StatusCodes.Status404NotFound)] + [ProducesResponseType(typeof(ValidationProblemDetails), StatusCodes.Status400BadRequest)] + public async Task RecoverUnverifyAsync(long logId) + => await ProcessAsync(logId); } diff --git a/src/GrillBot.App/Controllers/UserController.cs b/src/GrillBot.App/Controllers/UserController.cs index 8f3ff6b93..4723daf2c 100644 --- a/src/GrillBot.App/Controllers/UserController.cs +++ b/src/GrillBot.App/Controllers/UserController.cs @@ -15,13 +15,14 @@ using GrillBot.Core.Services.RubbergodService.Models.Karma; using GrillBot.Core.Models.Pagination; using Microsoft.Extensions.DependencyInjection; +using GrillBot.App.Actions.Api; namespace GrillBot.App.Controllers; [ApiController] [Route("api/user")] [ApiExplorerSettings(GroupName = "v1")] -public class UsersController : Infrastructure.ControllerBase +public class UsersController : Core.Infrastructure.Actions.ControllerBase { public UsersController(IServiceProvider serviceProvider) : base(serviceProvider) { @@ -34,14 +35,12 @@ public UsersController(IServiceProvider serviceProvider) : base(serviceProvider) /// Validation of parameters failed. [HttpPost("list")] [Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme, Roles = "Admin")] - [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(typeof(PaginatedResponse), StatusCodes.Status200OK)] [ProducesResponseType(typeof(ValidationProblemDetails), StatusCodes.Status400BadRequest)] - public async Task>> GetUsersListAsync([FromBody] GetUserListParams parameters) + public async Task GetUsersListAsync([FromBody] GetUserListParams parameters) { ApiAction.Init(this, parameters); - parameters.FixStatus(); - - return Ok(await ProcessActionAsync>(action => action.ProcessAsync(parameters))); + return await ProcessAsync(parameters); } /// @@ -51,10 +50,10 @@ public async Task>> GetUsersListAsy /// User not found in database. [HttpGet("{id}")] [Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme, Roles = "Admin")] - [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(typeof(UserDetail), StatusCodes.Status200OK)] [ProducesResponseType(typeof(MessageResponse), StatusCodes.Status404NotFound)] - public async Task> GetUserDetailAsync(ulong id) - => Ok(await ProcessActionAsync(action => action.ProcessAsync(id))); + public async Task GetUserDetailAsync(ulong id) + => await ProcessAsync(id); /// /// Get data about currently logged user. @@ -64,10 +63,10 @@ public async Task> GetUserDetailAsync(ulong id) /// Only for users with User permissions. [HttpGet("me")] [Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme, Roles = "User")] - [ProducesResponseType((int)HttpStatusCode.OK)] - [ProducesResponseType(typeof(MessageResponse), (int)HttpStatusCode.NotFound)] - public async Task> GetCurrentUserDetailAsync() - => Ok(await ProcessActionAsync(action => action.ProcessSelfAsync())); + [ProducesResponseType(typeof(UserDetail), StatusCodes.Status200OK)] + [ProducesResponseType(typeof(MessageResponse), StatusCodes.Status404NotFound)] + public async Task GetCurrentUserDetailAsync() + => await ProcessAsync(); /// /// Update user. @@ -77,15 +76,13 @@ public async Task> GetCurrentUserDetailAsync() /// User not found. [HttpPut("{id}")] [Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme, Roles = "Admin")] - [ProducesResponseType((int)HttpStatusCode.OK)] - [ProducesResponseType(typeof(ValidationProblemDetails), (int)HttpStatusCode.BadRequest)] - [ProducesResponseType(typeof(MessageResponse), (int)HttpStatusCode.NotFound)] - public async Task UpdateUserAsync(ulong id, [FromBody] UpdateUserParams parameters) + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(typeof(ValidationProblemDetails), StatusCodes.Status400BadRequest)] + [ProducesResponseType(typeof(MessageResponse), StatusCodes.Status404NotFound)] + public async Task UpdateUserAsync(ulong id, [FromBody] UpdateUserParams parameters) { ApiAction.Init(this, parameters); - - await ProcessActionAsync(action => action.ProcessAsync(id, parameters)); - return Ok(); + return await ProcessAsync(id, parameters); } /// @@ -96,13 +93,9 @@ public async Task UpdateUserAsync(ulong id, [FromBody] UpdateUserP /// [HttpDelete("hearthbeat")] [Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme, Roles = "User,Admin")] - [ProducesResponseType((int)HttpStatusCode.OK)] - public async Task HearthbeatOffAsync() - { - var apiContext = ServiceProvider.GetRequiredService(); - await ProcessActionAsync(manager => manager.SetHearthbeatAsync(false, apiContext)); - return Ok(); - } + [ProducesResponseType(StatusCodes.Status200OK)] + public async Task HearthbeatOffAsync() + => await ProcessAsync(false); /// /// Get rubbergod karma leaderboard. @@ -112,12 +105,12 @@ public async Task HearthbeatOffAsync() [ApiKeyAuth] [ApiExplorerSettings(GroupName = "v2")] [HttpPost("karma")] - [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(typeof(PaginatedResponse), StatusCodes.Status200OK)] [ProducesResponseType(typeof(MessageResponse), StatusCodes.Status500InternalServerError)] - public async Task>> GetRubbergodUserKarmaAsync([FromBody] KarmaListParams parameters) + public async Task GetRubbergodUserKarmaAsync([FromBody] KarmaListParams parameters) { ApiAction.Init(this, parameters); - return Ok(await ProcessActionAsync>(action => action.ProcessAsync(parameters))); + return await ProcessAsync(parameters); } /// @@ -133,12 +126,12 @@ public async Task>> GetRubbergodUserKa [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(typeof(ValidationProblemDetails), StatusCodes.Status400BadRequest)] [ProducesResponseType(typeof(MessageResponse), StatusCodes.Status500InternalServerError)] - public async Task StoreKarmaAsync([FromBody] List items) + public async Task StoreKarmaAsync([FromBody] List items) { ApiAction.Init(this, items.ToArray()); - await ProcessBridgeAsync(client => client.StoreKarmaAsync(items)); - return Ok(); + var executor = new Func(async (IRubbergodServiceClient client) => await client.StoreKarmaAsync(items)); + return await ProcessAsync>(executor); } /// @@ -148,7 +141,7 @@ public async Task StoreKarmaAsync([FromBody] List items [ApiKeyAuth] [ApiExplorerSettings(GroupName = "v2")] [HttpGet("birthday/today")] - [ProducesResponseType(StatusCodes.Status200OK)] - public async Task> GetTodayBirthdayInfoAsync() - => Ok(await ProcessActionAsync(action => action.ProcessAsync())); + [ProducesResponseType(typeof(MessageResponse), StatusCodes.Status200OK)] + public async Task GetTodayBirthdayInfoAsync() + => await ProcessAsync(); } diff --git a/src/GrillBot.App/Infrastructure/ControllerBase.cs b/src/GrillBot.App/Infrastructure/ControllerBase.cs deleted file mode 100644 index f52410461..000000000 --- a/src/GrillBot.App/Infrastructure/ControllerBase.cs +++ /dev/null @@ -1,34 +0,0 @@ -using GrillBot.App.Actions.Api; -using GrillBot.Core.Services.Common; -using Microsoft.AspNetCore.Mvc; -using Microsoft.Extensions.DependencyInjection; - -namespace GrillBot.App.Infrastructure; - -public abstract class ControllerBase : Controller -{ - protected IServiceProvider ServiceProvider { get; } - - protected ControllerBase(IServiceProvider serviceProvider) - { - ServiceProvider = serviceProvider; - } - - protected async Task ProcessActionAsync(Func> asyncExecution) where TAction : notnull - => await asyncExecution(ServiceProvider.GetRequiredService()); - - protected async Task ProcessActionAsync(Func asyncExecution) where TAction : notnull - => await asyncExecution(ServiceProvider.GetRequiredService()); - - protected TData ProcessAction(Func syncExecution) where TAction : notnull - => syncExecution(ServiceProvider.GetRequiredService()); - - protected void ProcessAction(Action syncExecution) where TAction : notnull - => syncExecution(ServiceProvider.GetRequiredService()); - - protected Task ProcessBridgeAsync(Func asyncExecutor) where TService : IClient - => ProcessActionAsync(bridge => bridge.ExecuteAsync(asyncExecutor)); - - protected Task ProcessBridgeAsync(Func> asyncExecutor) where TService : IClient - => ProcessActionAsync(bridge => bridge.ExecuteAsync(asyncExecutor)); -} diff --git a/src/GrillBot.App/Jobs/BirthdayCronJob.cs b/src/GrillBot.App/Jobs/BirthdayCronJob.cs index 69cd8d61b..3f1b29c7b 100644 --- a/src/GrillBot.App/Jobs/BirthdayCronJob.cs +++ b/src/GrillBot.App/Jobs/BirthdayCronJob.cs @@ -1,5 +1,6 @@ using GrillBot.App.Actions.Api.V2; using GrillBot.App.Infrastructure.Jobs; +using GrillBot.Data.Models.API; using Quartz; namespace GrillBot.App.Jobs; @@ -45,8 +46,9 @@ protected override async Task RunAsync(IJobExecutionContext context) } var result = await GetTodayBirthdayInfo.ProcessAsync(); - context.Result = result.Message; + var message = ((MessageResponse)result.Data!).Message; - await channel.SendMessageAsync(result.Message); + context.Result = message; + await channel.SendMessageAsync(message); } } diff --git a/src/GrillBot.App/Managers/UserManager.cs b/src/GrillBot.App/Managers/UserManager.cs index ecd5797ad..6098846ba 100644 --- a/src/GrillBot.App/Managers/UserManager.cs +++ b/src/GrillBot.App/Managers/UserManager.cs @@ -18,9 +18,8 @@ public async Task SetHearthbeatAsync(bool isActive, ApiRequestContext context) var isPublic = context.IsPublic(); await using var repository = DatabaseBuilder.CreateRepository(); - var user = await repository.User.FindUserAsync(context.LoggedUser!); - if (user == null) - throw new NotFoundException(); + var user = await repository.User.FindUserAsync(context.LoggedUser!) + ?? throw new NotFoundException(); if (isActive) user.Flags |= (int)(isPublic ? UserFlags.PublicAdminOnline : UserFlags.WebAdminOnline); diff --git a/src/GrillBot.App/Modules/Interactions/BirthdayModule.cs b/src/GrillBot.App/Modules/Interactions/BirthdayModule.cs index 3771972a5..2e1ef8478 100644 --- a/src/GrillBot.App/Modules/Interactions/BirthdayModule.cs +++ b/src/GrillBot.App/Modules/Interactions/BirthdayModule.cs @@ -3,6 +3,7 @@ using GrillBot.App.Actions.Commands.Birthday; using GrillBot.App.Infrastructure; using GrillBot.App.Infrastructure.Preconditions.Interactions; +using GrillBot.Data.Models.API; namespace GrillBot.App.Modules.Interactions; @@ -19,8 +20,9 @@ public async Task TodayBirthdayAsync() { using var command = GetActionAsCommand(); var result = await command.Command.ProcessAsync(); + var mesasge = ((MessageResponse)result.Data!).Message; - await SetResponseAsync(result.Message); + await SetResponseAsync(mesasge); } [SlashCommand("add", "Adding your date of birth.")] diff --git a/src/GrillBot.Data/Resources/Localization/messages.cs.json b/src/GrillBot.Data/Resources/Localization/messages.cs.json index dc63fe9dd..239ccd208 100644 --- a/src/GrillBot.Data/Resources/Localization/messages.cs.json +++ b/src/GrillBot.Data/Resources/Localization/messages.cs.json @@ -1,666 +1,667 @@ { - "BirthdayModule": { - "Add": { - "Success": "Datum narození bylo úspěšně uloženo." - }, - "Remove": { - "Success": "Datum narození bylo úspěšně odebráno." - }, - "Have": { - "Yes": "Ano. Máš uložené narozeniny.", - "No": "Ne. Nemáš uložené narozeniny." - }, - "Info": { - "NoOneHave": "Dnes nemá nikdo narozeniny. {0}", - "Parts": { - "WithYears": "**{0} ({1} let)**", - "WithoutYears": "**{0}**" - }, - "Template": { - "SingleForm": "Dnes má narozeniny {0} {1}", - "MultipleForm": "Dnes mají narozeniny {0} a {1} {2}" - } - } - }, - "BotModule": { - "BotInfo": { - "CreatedAt": "Vytvořen", - "Uptime": "Uptime", - "Repository": "Repozitář", - "Documentation": "Dokumentace", - "Swagger": "Swagger", - "PrivateAdmin": "Administrace (Vyšší role)", - "PublicAdmin": "Administrace (Pro každého)", - "NavigationPage": "Rozcestník" - } - }, - "ChannelModule": { - "ChannelInfo": { - "NoAccess": "Informace o kanálu ti nemohu dát, protože tam nemáš přístup.", - "CreatedAt": "Vytvořen", - "MemberCount": "Počet členů", - "MemberCountValue": { - "One": "{0:N0} člen", - "TwoToFour": "{0:N0} členové", - "FiveAndMore": "{0:N0} členů" - }, - "NewsChannelTitle": "Informace o kanálu s novinkami", - "VoiceChannelTitle": "Informace o hlasovém kanálu", - "ThreadChannelTitle": "Informace o vláknu", - "StageChannelTitle": "Informace o jevišti", - "TextChannelTitle": "Informace o textovém kanálu", - "ForumChannelTitle": "Informace o fóru", - "CategoryChannelTitle": "Informace o kategorii", - "OtherChannelTitle:": "Informace o neznámém typu kanálu ({0})", - "PermsCountValue": { - "One": "{0:N0} oprávnění", - "TwoToFour": "{0:N0} oprávnění", - "FiveAndMore": "{0:N0} oprávnění" - }, - "PermsCount": "Uživatelské: {0}\nRole: {1}", - "PermsCountTitle": "Počet oprávnění", - "Channel": "Kanál", - "TagsCount": "Počet tagů", - "TagsCountValue": { - "One": "{0:N0} tag", - "TwoToFour": "{0:N0} tagy", - "FiveAndMore": "{0:N0} tagů" - }, - "ThreadCount": "Počet vláken", - "PublicThreadCountValue": { - "One": "{0:N0} veřejné vlákno", - "TwoToFour": "{0:N0} veřejná vlákna", - "FiveAndMore": "{0:N0} veřejných vláken" - }, - "PrivateThreadCountValue": { - "One": "{0:N0} soukromé vlákno", - "TwoToFour": "{0:N0} soukromá vlákna", - "FiveAndMore": "{0:N0} soukromých vláken" - }, - "MessageCount": "Počet zpráv", - "MessageCountValue": { - "One": "{0:N0} zpráva", - "TwoToFour": "{0:N0} zprávy", - "FiveAndMore": "{0:N0} zpráv" - }, - "FirstMessage": "První zpráva", - "LastMessage": "Poslední zpráva", - "Flags": { - "CommandsDisabled": "Deaktivovány všechny příkazy", - "AutoReplyDeactivated": "Deaktivovány automatické odpovědi", - "StatsHidden": "Skryté statistiky", - "EphemeralCommands": "Skryté příkazy", - "PointsDeactivated": "Body deaktivovány" - }, - "Configuration": "Konfigurace", - "TopTen": "TOP 10 uživatelů" - }, - "GetChannelboard": { - "NoAccess": "Informace o kanálu ti nemohu dát, protože tam nemáš přístup.", - "NoActivity": "Doteď nebyla zaznamenána žádná aktivita v kanálech.", - "Counts": { - "One": "{0:N0} zpráva", - "TwoToFour": "{0:N0} zprávy", - "FiveAndMore": "{0:N0} zpráv" - }, - "Row": "**{0}.** #{1} ({2})", - "Title": "Statistika aktivity v kanálech" - }, - "PostMessage": { - "GuildNotFound": "Nepodařilo se najít server.", - "ChannelNotFound": "Nepodařilo se najít kanál na serveru {0}.", - "NoContent": "Nebyl zadán žádný platný vstup k odeslání.", - "Success": "Zpráva byla odeslána." - }, - "ChannelDetail": { - "ChannelNotFound": "Požadovaný kanál nebyl nalezen." - }, - "ChannelSimpleList": { - "NoMutualGuild": "Požadovaný server nesdílíš s botem." - }, - "Clean": { - "ResultMessage": "Bylo úspěšně smazáno zpráv: **{0}**\nZ toho připnutých: **{1}**", - "UnsupportedChannel": "Mazat zprávy lze pouze z textových kanálů." - } - }, - "DuckModule": { - "GetDuckInfo": { - "CannotGetState": "Nepodařilo se zjistit stav kachny. Zkus to prosím později, nebo se podívej do kanálu {0}", - "DuckName": "U kachničky", - "Closed": "Kachna je zavřená.", - "NextOpenNotPlanned": "Další otvíračka není naplánovaná.", - "NextOpenAt": "Další otvíračka bude {0}.", - "Opened": "Kachna je otevřená!", - "Open": "Otevřeno", - "TimeToClose": " Otvíračka končí {0}.", - "Closing": "Zavíráme", - "ChillzoneTo": "Kachna je otevřená v režimu chillzóna až do {0}!", - "Note": "Poznámka" - } - }, - "GuildModule": { - "GetInfo": { - "CategoryCount": "Počet kategorií", - "TextChannelCount": "Počet textových kanálů", - "ThreadsCount": "Počet vláken", - "VoiceChannelCount": "Počet hlasových kanálů", - "RoleCount": "Počet rolí", - "EmoteCount": "Počet emotů (běžných/animovaných)", - "BanCount": "Počet banů", - "CreatedAt": "Vytvořen", - "Owner": "Vlastník", - "MemberCount": "Počet členů", - "PremiumTier": "Úroveň serveru", - "BoosterCount": "Počet boosterů", - "Improvements": "Vylepšení", - "DetailsTitle": "Podrobnosti", - "DetailsText": "Podrobné informace o serveru najdeš ve webové administraci (https://private.grillbot.eu/)", - "Features": { - "AnimatedIcon": "Animovaná ikona", - "Banner": "Banner", - "Commerce": "eKomerce", - "Community": "Komunitní režim", - "Discoverable": "Veřejně viditelný", - "InviteSplash": "Pozadí u pozvánky", - "MemberVerificationGateEnabled": "Verifikace při připojení", - "News": "Novinky", - "Partnered": "Partnerský program", - "PreviewEnabled": "Náhled serveru před připojením", - "VanityUrl": "Unikátní URL pozvánky", - "Verified": "Ověřený server", - "VIPRegions": "VIP hlasová oblast", - "WelcomeScreenEnabled": "Uvítací obrazovka", - "MonetizationEnabled": "Monetizace", - "MoreStickers": "Nálepky", - "PrivateThreads": "Soukromá vlákna", - "RoleIcons": "Ikony rolí", - "SevenDayThreadArchive": "Archivace vláken po týdnu", - "ThreeDayThreadArchive": "Archivace vláken po 3 dnech", - "TicketedEventsEnabled": "Události", - "AnimatedBanner": "Animovaný banner", - "TextInVoiceEnabled": "Text v hlasových kanálech", - "ThreadsEnabled": "Vlákna", - "ChannelBanner": "Banner kanálů", - "Hub": "Školní server", - "MoreEmoji": "Více emotů", - "RoleSubscriptionsAvailableForPurchase": "Placené role", - "MemberProfiles": "Alternativní profily uživatelů", - "NewThreadPermissions": "", - "ThreadsEnabledTesting": "", - "AutoModeration": "AutoMod", - "DiscoverableDisabled": "", - "EnabledDiscoverableBefore": "", - "Featureable": "", - "ForceRelay": "", - "HasDirectoryEntry": "", - "InternalEmployeeOnly": "Pouze pro zaměstnance Discordu", - "LinkedToHub": "Propojení se školním serverem", - "PremiumTier3Override": "", - "RelayEnabled": "", - "RoleSubscriptionsEnabled": "Povoleny placené role", - "ThreadsDefaultAutoArchiveDuration": "", - "DeveloperSupportServer": "Server podpory", - "InvitesDisabled": "", - "RaidAlertsDisabled": "Upozornění na raid vypnuto", - "ClydeEnabled": "Clyde AI", - "GuildWebPageVanityUrl": "Web page vanity URL" - } - }, - "GuildDetail": { - "NotFound": "Požadovaný server se nepodařilo najít." - }, - "UpdateGuild": { - "AdminChannelNotFound": "Nepodařilo se dohledat administrátorský kanál", - "MuteRoleNotFound": "Nepodařilo se dohledat roli, která reprezentuje umlčení uživatele při unverify.", - "EmoteSuggestionChannelNotFound": "Nepodařilo se dohledat kanál pro návrhy emotů.", - "VoteChannelNotFound": "Nepodařilo se dohledat kanál pro veřejná hlasování.", - "BotRoomChannelNotFound": "Nepodařilo se dohledat kanál pro boty." - } - }, - "MathModule": { - "SolveExpression": { - "Expression": "Výraz", - "ComputeFailed": "Výpočet se nezdařil", - "Report": "Hlášení", - "Result": "Výsledek", - "Timeout": "Vypršel časový limit pro zpracování výrazu" - } - }, - "MemeModule": { - "Hi": { - "Template": "Ahoj {0} {1}" - } - }, - "RemindModule": { - "Create": { - "Success": "Připomenutí bylo vytvořeno.", - "CopyMessage": "Pokud si někdo přeje dostat toto upozornění také, tak ať klikne na tlačítko {0}", - "Validation": { - "MustInFuture": "Datum a čas upozornění musí být v budoucnosti.", - "MinimalTimeTemplate": "Datum a čas upozornění musí být později než {0}", - "MinimalTime": { - "One": "{0:N0} minuta", - "TwoToFour": "{0:N0} minuty", - "FiveAndMore": "{0:N0} minut" - }, - "MessageRequired": "Text upozornění je povinný.", - "MaxLengthExceeded": "Zpráva může být dlouhá maximálně 1024 znaků." - } - }, - "CancelRemind": { - "Cancelled": "Upozornění bylo úspěšně zrušeno.", - "CancelledWithNotify": "Upozornění bylo úspěšně zrušeno a cílový uživatel byl upozorněn.", - "NotFound": "Požadované upozornění nebylo nalezeno.", - "AlreadyCancelled": "Nelze zrušit již zrušené upozornění.", - "AlreadyNotified": "Nelze zrušit již proběhlé upozornění.", - "InvalidOperator": "Upozornění může zrušit pouze ten, komu je určeno, nebo kdo jej založil." - }, - "Suggestions": { - "Incoming": "Příchozí #{0} ({1}) od uživatele {2}", - "Outgoing": "Odchozí #{0} ({2}) pro uživatele {2}" - }, - "NotifyMessage": { - "ForceTitle": "Máš předčasně nové upozornění", - "Title": "Máš nové upozornění", - "Fields": { - "Id": "ID", - "From": "Od", - "Attention": "POZOR", - "Message": "Zpráva", - "Options": "Možnosti" - }, - "Postponed": "Toto upozornění bylo odloženo už {0}x", - "Options": "Pokud si přeješ toto upozornění posunout, tak klikni na příslušné tlačítko podle počtu hodin." - }, - "Copy": { - "RemindNotFound": "Upozornění nebylo nalezeno.", - "SelfCopy": "Toto upozornění jsi založil, nemůžeš dostat to stejné.", - "WasCancelled": "Toto upozornění bylo zrušeno.", - "WasSent": "Toto upozornění již bylo odesláno.", - "CopyExists": "Toto upozornění jsi již jednou z tlačítka vytvořil. Nemůžeš vytvořit další." - }, - "List": { - "Embed": { - "Title": "Seznam čekajících upozornění pro {0}", - "NoItems": "Pro uživatele {0} nečekají žádné upozornění.", - "RowTitle": "#{0} - Od {1} v {2} (za {3})" - } - } - }, - "SearchingModule": { - "CreateSearch": { - "Success": "Hledání bylo úspěšně uloženo." - }, - "RemoveSearch": { - "Success": "Hledání bylo úspěšně smazáno.", - "InsufficientPermissions": "Hledání nebylo vámi založené a ani nemáte vyšší oprávnění k mazání hledání." + "BirthdayModule": { + "Add": { + "Success": "Datum narození bylo úspěšně uloženo." + }, + "Remove": { + "Success": "Datum narození bylo úspěšně odebráno." + }, + "Have": { + "Yes": "Ano. Máš uložené narozeniny.", + "No": "Ne. Nemáš uložené narozeniny." + }, + "Info": { + "NoOneHave": "Dnes nemá nikdo narozeniny. {0}", + "Parts": { + "WithYears": "**{0} ({1} let)**", + "WithoutYears": "**{0}**" + }, + "Template": { + "SingleForm": "Dnes má narozeniny {0} {1}", + "MultipleForm": "Dnes mají narozeniny {0} a {1} {2}" + } + } }, - "List": { - "Embed": { - "Title": "Hledání v kanálu #{0}", - "NoItems": "V kanálu {0} zatím nikdo nic nehledá.", - "NoItemsWithQuery": "V kanálu {0} zatím nikdo nic nehledá. Zkus jiný vyhledávací podřetězec." - } - } - }, - "SuggestionModule": { - "SessionExpired": "Platnost formuláře vypršela. Podej prosím návrh znovu.", - "PrivateMessageSuggestionComplete": "Tvůj návrh na přidání emote byl úspěšně zpracován.", - "VoteChannelNotDefined": "Nelze spustit hlasování o nových emotech, protože není definován kanál pro hlasování.", - "VoteChannelNotFound": "Nelze spustit hlasování o nových emotech, protože nebyl nalezen kanál pro hlasování.", - "NoForVote": "Neexistuje žádný schválený/zamítnutý návrh ke zpracování.", - "NoApprovedForVote": "Není žádný schválený návrh ke zpracování.", - "SuggestEmote": { - "NoEmoteAndAttachment": "Nelze podat návrh na nový emote, když není dodán emote. Emote lze dodat ve formě jiného emote, nebo obrázku.", - "EmoteExistsInGuild": "Nelze podat návrh na emote, který je již na tomto serveru.", - "ModalTitle": "Podání návrhu na nový emote", - "ModalEmoteName": "Název emote", - "ModalEmoteDescription": "Popis emote", - "ModalEmoteDescriptionPlaceholder": "Něco k doplnění? Co emote vyjadřuje? Proč bychom ho tu měli mít?" + "BotModule": { + "BotInfo": { + "CreatedAt": "Vytvořen", + "Uptime": "Uptime", + "Repository": "Repozitář", + "Documentation": "Dokumentace", + "Swagger": "Swagger", + "PrivateAdmin": "Administrace (Vyšší role)", + "PublicAdmin": "Administrace (Pro každého)", + "NavigationPage": "Rozcestník" + } }, - "SuggestionEmbed": { - "Title": "Nový návrh na emote.", - "EmoteNameTitle": "Název", - "EmoteDescriptionTitle": "Popis", - "VoteFinished": "Hlasování skončilo {0}", - "VoteRunning": "Hlasování běží, skončí {0}", - "VoteFinishedTitle": "Dokončeno hlasování o novém emote", - "CommunityApproved": "Komunitou schválen", - "Boolean": { - "True": "Ano", - "False": "Ne" - }, - "ApproveForVote": { - "True": "Schválen k hlasování", - "False": "Zamítnut k hlasování" - } + "ChannelModule": { + "ChannelInfo": { + "NoAccess": "Informace o kanálu ti nemohu dát, protože tam nemáš přístup.", + "CreatedAt": "Vytvořen", + "MemberCount": "Počet členů", + "MemberCountValue": { + "One": "{0:N0} člen", + "TwoToFour": "{0:N0} členové", + "FiveAndMore": "{0:N0} členů" + }, + "NewsChannelTitle": "Informace o kanálu s novinkami", + "VoiceChannelTitle": "Informace o hlasovém kanálu", + "ThreadChannelTitle": "Informace o vláknu", + "StageChannelTitle": "Informace o jevišti", + "TextChannelTitle": "Informace o textovém kanálu", + "ForumChannelTitle": "Informace o fóru", + "CategoryChannelTitle": "Informace o kategorii", + "OtherChannelTitle:": "Informace o neznámém typu kanálu ({0})", + "PermsCountValue": { + "One": "{0:N0} oprávnění", + "TwoToFour": "{0:N0} oprávnění", + "FiveAndMore": "{0:N0} oprávnění" + }, + "PermsCount": "Uživatelské: {0}\nRole: {1}", + "PermsCountTitle": "Počet oprávnění", + "Channel": "Kanál", + "TagsCount": "Počet tagů", + "TagsCountValue": { + "One": "{0:N0} tag", + "TwoToFour": "{0:N0} tagy", + "FiveAndMore": "{0:N0} tagů" + }, + "ThreadCount": "Počet vláken", + "PublicThreadCountValue": { + "One": "{0:N0} veřejné vlákno", + "TwoToFour": "{0:N0} veřejná vlákna", + "FiveAndMore": "{0:N0} veřejných vláken" + }, + "PrivateThreadCountValue": { + "One": "{0:N0} soukromé vlákno", + "TwoToFour": "{0:N0} soukromá vlákna", + "FiveAndMore": "{0:N0} soukromých vláken" + }, + "MessageCount": "Počet zpráv", + "MessageCountValue": { + "One": "{0:N0} zpráva", + "TwoToFour": "{0:N0} zprávy", + "FiveAndMore": "{0:N0} zpráv" + }, + "FirstMessage": "První zpráva", + "LastMessage": "Poslední zpráva", + "Flags": { + "CommandsDisabled": "Deaktivovány všechny příkazy", + "AutoReplyDeactivated": "Deaktivovány automatické odpovědi", + "StatsHidden": "Skryté statistiky", + "EphemeralCommands": "Skryté příkazy", + "PointsDeactivated": "Body deaktivovány" + }, + "Configuration": "Konfigurace", + "TopTen": "TOP 10 uživatelů" + }, + "GetChannelboard": { + "NoAccess": "Informace o kanálu ti nemohu dát, protože tam nemáš přístup.", + "NoActivity": "Doteď nebyla zaznamenána žádná aktivita v kanálech.", + "Counts": { + "One": "{0:N0} zpráva", + "TwoToFour": "{0:N0} zprávy", + "FiveAndMore": "{0:N0} zpráv" + }, + "Row": "**{0}.** #{1} ({2})", + "Title": "Statistika aktivity v kanálech" + }, + "PostMessage": { + "GuildNotFound": "Nepodařilo se najít server.", + "ChannelNotFound": "Nepodařilo se najít kanál na serveru {0}.", + "NoContent": "Nebyl zadán žádný platný vstup k odeslání.", + "Success": "Zpráva byla odeslána." + }, + "ChannelDetail": { + "ChannelNotFound": "Požadovaný kanál nebyl nalezen." + }, + "ChannelSimpleList": { + "NoMutualGuild": "Požadovaný server nesdílíš s botem." + }, + "Clean": { + "ResultMessage": "Bylo úspěšně smazáno zpráv: **{0}**\nZ toho připnutých: **{1}**", + "UnsupportedChannel": "Mazat zprávy lze pouze z textových kanálů." + } }, - "ProcessEmoteSuggestions": { - "Success": "Hlasování pro schválené emoty bylo spuštěno" + "DuckModule": { + "GetDuckInfo": { + "CannotGetState": "Nepodařilo se zjistit stav kachny. Zkus to prosím později, nebo se podívej do kanálu {0}", + "DuckName": "U kachničky", + "Closed": "Kachna je zavřená.", + "NextOpenNotPlanned": "Další otvíračka není naplánovaná.", + "NextOpenAt": "Další otvíračka bude {0}.", + "Opened": "Kachna je otevřená!", + "Open": "Otevřeno", + "TimeToClose": " Otvíračka končí {0}.", + "Closing": "Zavíráme", + "ChillzoneTo": "Kachna je otevřená v režimu chillzóna až do {0}!", + "Note": "Poznámka" + } }, - "ApprovalButtons": { - "Approve": "Schválit", - "Decline": "Zamítnout" + "GuildModule": { + "GetInfo": { + "CategoryCount": "Počet kategorií", + "TextChannelCount": "Počet textových kanálů", + "ThreadsCount": "Počet vláken", + "VoiceChannelCount": "Počet hlasových kanálů", + "RoleCount": "Počet rolí", + "EmoteCount": "Počet emotů (běžných/animovaných)", + "BanCount": "Počet banů", + "CreatedAt": "Vytvořen", + "Owner": "Vlastník", + "MemberCount": "Počet členů", + "PremiumTier": "Úroveň serveru", + "BoosterCount": "Počet boosterů", + "Improvements": "Vylepšení", + "DetailsTitle": "Podrobnosti", + "DetailsText": "Podrobné informace o serveru najdeš ve webové administraci (https://private.grillbot.eu/)", + "Features": { + "AnimatedIcon": "Animovaná ikona", + "Banner": "Banner", + "Commerce": "eKomerce", + "Community": "Komunitní režim", + "Discoverable": "Veřejně viditelný", + "InviteSplash": "Pozadí u pozvánky", + "MemberVerificationGateEnabled": "Verifikace při připojení", + "News": "Novinky", + "Partnered": "Partnerský program", + "PreviewEnabled": "Náhled serveru před připojením", + "VanityUrl": "Unikátní URL pozvánky", + "Verified": "Ověřený server", + "VIPRegions": "VIP hlasová oblast", + "WelcomeScreenEnabled": "Uvítací obrazovka", + "MonetizationEnabled": "Monetizace", + "MoreStickers": "Nálepky", + "PrivateThreads": "Soukromá vlákna", + "RoleIcons": "Ikony rolí", + "SevenDayThreadArchive": "Archivace vláken po týdnu", + "ThreeDayThreadArchive": "Archivace vláken po 3 dnech", + "TicketedEventsEnabled": "Události", + "AnimatedBanner": "Animovaný banner", + "TextInVoiceEnabled": "Text v hlasových kanálech", + "ThreadsEnabled": "Vlákna", + "ChannelBanner": "Banner kanálů", + "Hub": "Školní server", + "MoreEmoji": "Více emotů", + "RoleSubscriptionsAvailableForPurchase": "Placené role", + "MemberProfiles": "Alternativní profily uživatelů", + "NewThreadPermissions": "", + "ThreadsEnabledTesting": "", + "AutoModeration": "AutoMod", + "DiscoverableDisabled": "", + "EnabledDiscoverableBefore": "", + "Featureable": "", + "ForceRelay": "", + "HasDirectoryEntry": "", + "InternalEmployeeOnly": "Pouze pro zaměstnance Discordu", + "LinkedToHub": "Propojení se školním serverem", + "PremiumTier3Override": "", + "RelayEnabled": "", + "RoleSubscriptionsEnabled": "Povoleny placené role", + "ThreadsDefaultAutoArchiveDuration": "", + "DeveloperSupportServer": "Server podpory", + "InvitesDisabled": "", + "RaidAlertsDisabled": "Upozornění na raid vypnuto", + "ClydeEnabled": "Clyde AI", + "GuildWebPageVanityUrl": "Web page vanity URL" + } + }, + "GuildDetail": { + "NotFound": "Požadovaný server se nepodařilo najít." + }, + "UpdateGuild": { + "AdminChannelNotFound": "Nepodařilo se dohledat administrátorský kanál", + "MuteRoleNotFound": "Nepodařilo se dohledat roli, která reprezentuje umlčení uživatele při unverify.", + "EmoteSuggestionChannelNotFound": "Nepodařilo se dohledat kanál pro návrhy emotů.", + "VoteChannelNotFound": "Nepodařilo se dohledat kanál pro veřejná hlasování.", + "BotRoomChannelNotFound": "Nepodařilo se dohledat kanál pro boty." + } }, - "EmoteSuggestionChannelNotSet": { - "IsFinish": "Není nastaven kanál pro návrhy ({0}).", - "IsNotFinish": "Tvůj návrh nelze nyní zpracovat, protože není určen kanál pro návrhy." + "MathModule": { + "SolveExpression": { + "Expression": "Výraz", + "ComputeFailed": "Výpočet se nezdařil", + "Report": "Hlášení", + "Result": "Výsledek", + "Timeout": "Vypršel časový limit pro zpracování výrazu" + } }, - "EmoteSuggestionChannelNotFound": { - "IsFinish": "Nepodařilo se najít kanál pro návrhy ({0}).", - "NotFinish": "Tvůj návrh emote nelze nyní kvůli technickým důvodům zpracovat." - } - }, - "Unverify": { - "Validation": { - "KeepableCountExceeded": "Nelze si ponechat více než {0} rolí a kanálů.", - "GuildOwner": "Nelze provést odebrání přístupu, protože uživatel **{0}** je vlastník tohoto serveru.", - "Administrator": "Nelze provést odebrání přístupu, protože uživatel **{0}** má administrátorská oprávnění.", - "MultipleUnverify": "Nelze provést odebrání přístupu, protože uživatel **{0}** již má odebraný přístup do **{1}**.", - "MustBeInFuture": "Konec unverify musí být v budoucnosti.", - "MinimalTime": "Minimální čas pro unverify je {0}", - "HigherRoles": "Nelze provést odebírání přístupu, protože uživatel **{0}** má vyšší role. **({1})**", - "UnverifyWithoutReason": "Nelze bezdůvodně odebrat přístup. Přečti si nápovědu a pak to zkus znovu.", - "UndefinedKeepable": "{0} není ponechatelné." + "MemeModule": { + "Hi": { + "Template": "Ahoj {0} {1}" + } }, - "Update": { - "UnverifyNotFound": "Aktualizaci času nelze pro hledaného uživatele provést. Unverify nenalezeno.", - "NotEnoughTime": "Aktualizace data a času již není možná. Vypršel čas nebo zbývá méně než půl minuty." + "RemindModule": { + "Create": { + "Success": "Připomenutí bylo vytvořeno.", + "CopyMessage": "Pokud si někdo přeje dostat toto upozornění také, tak ať klikne na tlačítko {0}", + "Validation": { + "MustInFuture": "Datum a čas upozornění musí být v budoucnosti.", + "MinimalTimeTemplate": "Datum a čas upozornění musí být později než {0}", + "MinimalTime": { + "One": "{0:N0} minuta", + "TwoToFour": "{0:N0} minuty", + "FiveAndMore": "{0:N0} minut" + }, + "MessageRequired": "Text upozornění je povinný.", + "MaxLengthExceeded": "Zpráva může být dlouhá maximálně 1024 znaků." + } + }, + "CancelRemind": { + "Cancelled": "Upozornění bylo úspěšně zrušeno.", + "CancelledWithNotify": "Upozornění bylo úspěšně zrušeno a cílový uživatel byl upozorněn.", + "NotFound": "Požadované upozornění nebylo nalezeno.", + "AlreadyCancelled": "Nelze zrušit již zrušené upozornění.", + "AlreadyNotified": "Nelze zrušit již proběhlé upozornění.", + "InvalidOperator": "Upozornění může zrušit pouze ten, komu je určeno, nebo kdo jej založil." + }, + "Suggestions": { + "Incoming": "Příchozí #{0} ({1}) od uživatele {2}", + "Outgoing": "Odchozí #{0} ({2}) pro uživatele {2}" + }, + "NotifyMessage": { + "ForceTitle": "Máš předčasně nové upozornění", + "Title": "Máš nové upozornění", + "Fields": { + "Id": "ID", + "From": "Od", + "Attention": "POZOR", + "Message": "Zpráva", + "Options": "Možnosti" + }, + "Postponed": "Toto upozornění bylo odloženo už {0}x", + "Options": "Pokud si přeješ toto upozornění posunout, tak klikni na příslušné tlačítko podle počtu hodin." + }, + "Copy": { + "RemindNotFound": "Upozornění nebylo nalezeno.", + "SelfCopy": "Toto upozornění jsi založil, nemůžeš dostat to stejné.", + "WasCancelled": "Toto upozornění bylo zrušeno.", + "WasSent": "Toto upozornění již bylo odesláno.", + "CopyExists": "Toto upozornění jsi již jednou z tlačítka vytvořil. Nemůžeš vytvořit další." + }, + "List": { + "Embed": { + "Title": "Seznam čekajících upozornění pro {0}", + "NoItems": "Pro uživatele {0} nečekají žádné upozornění.", + "RowTitle": "#{0} - Od {1} v {2} (za {3})" + } + } }, - "Message": { - "UnverifyToChannelWithReason": "Dočasné odebrání přístupu pro uživatele **{0}** bylo dokončeno. Přístup bude navrácen **{1}**. Důvod: {2}", - "UnverifyToChannelWithoutReason": "Dočasné odebrání přístupu pro uživatele **{0}** bylo dokončeno. Přístup bude navrácen **{1}**.", - "PrivateUnverifyWithReason": "Byla ti dočasně odebrána všechna práva na serveru **{0}**. Přístup ti bude navrácen **{1}**. Důvod: {2}", - "PrivateUnverifyWithoutReason": "Byla ti dočasně odebrána všechna práva na serveru **{0}**. Přístup ti bude navrácen **{1}**", - "PrivateUpdate": "Byl ti aktualizován čas pro odebrání práv na serveru **{0}**. Přístup ti bude navrácen **{1}**.", - "PrivateUpdateWithReason": "Byl ti aktualizován čas pro odebrání práv na serveru **{0}**. Přístup ti bude navrácen **{1}**. Důvod: *{2}*", - "UpdateToChannel": "Reset konce odebrání přístupu pro uživatele **{0}** byl aktualizován.\nPřístup bude navrácen **{1}**", - "UpdateToChannelWithReason": "Reset konce odebrání přístupu pro uživatele **{0}** byl aktualizován.\nPřístup bude navrácen **{1}**\nDůvod: *{2}*", - "PrivateManuallyRemovedUnverify": "Byl ti předčasně vrácen přístup na serveru **{0}**.", - "ManuallyRemoveToChannel": "Předčasné vrácení přístupu pro uživatele **{0}** bylo dokončeno.", - "ManuallyRemoveFailed": "Předčasné vrácení přístupu pro uživatele **{0}** selhalo. ({1})", - "RemoveAccessUnverifyNotFound": "Předčasné vrácení přístupu pro uživatele **{0}** nelze provést. Unverify nebylo nalezeno.", - "UnverifyFailedToChannel": "Dočasné odebrání přístupu pro uživatele **{0}** se nezdařilo. Uživatel byl obnoven do původního stavu." + "SearchingModule": { + "CreateSearch": { + "Success": "Hledání bylo úspěšně uloženo." + }, + "RemoveSearch": { + "Success": "Hledání bylo úspěšně smazáno.", + "InsufficientPermissions": "Hledání nebylo vámi založené a ani nemáte vyšší oprávnění k mazání hledání." + }, + "List": { + "Embed": { + "Title": "Hledání v kanálu #{0}", + "NoItems": "V kanálu {0} zatím nikdo nic nehledá.", + "NoItemsWithQuery": "V kanálu {0} zatím nikdo nic nehledá. Zkus jiný vyhledávací podřetězec." + } + } }, - "GuildNotFound": "Server na kterém by se mělo nacházet unverify nebyl nalezen.", - "DestUserNotFound": "Uživatel, kterému mělo být přiřazeno unverify nebyl nalezen.", - "Recover": { - "LogItemNotFound": "Záznam o provedeném odebrání přístupu nebyl nalezen.", - "ValidUnverify": "Nelze provést obnovení přístupu uživateli, protože má aktuálně platné unverify.", - "GuildNotFound": "Nelze najít server, na kterém bylo uděleno unverify.", - "MemberNotFound": "Nelze vyhledat uživatele na serveru {0}" + "SuggestionModule": { + "SessionExpired": "Platnost formuláře vypršela. Podej prosím návrh znovu.", + "PrivateMessageSuggestionComplete": "Tvůj návrh na přidání emote byl úspěšně zpracován.", + "VoteChannelNotDefined": "Nelze spustit hlasování o nových emotech, protože není definován kanál pro hlasování.", + "VoteChannelNotFound": "Nelze spustit hlasování o nových emotech, protože nebyl nalezen kanál pro hlasování.", + "NoForVote": "Neexistuje žádný schválený/zamítnutý návrh ke zpracování.", + "NoApprovedForVote": "Není žádný schválený návrh ke zpracování.", + "SuggestEmote": { + "NoEmoteAndAttachment": "Nelze podat návrh na nový emote, když není dodán emote. Emote lze dodat ve formě jiného emote, nebo obrázku.", + "EmoteExistsInGuild": "Nelze podat návrh na emote, který je již na tomto serveru.", + "ModalTitle": "Podání návrhu na nový emote", + "ModalEmoteName": "Název emote", + "ModalEmoteDescription": "Popis emote", + "ModalEmoteDescriptionPlaceholder": "Něco k doplnění? Co emote vyjadřuje? Proč bychom ho tu měli mít?", + "UnsupportedImageFormat": "Nepodporovaný typ obrázku. Je možné nahrávat pouze přílohy s koncovkou PNG." + }, + "SuggestionEmbed": { + "Title": "Nový návrh na emote.", + "EmoteNameTitle": "Název", + "EmoteDescriptionTitle": "Popis", + "VoteFinished": "Hlasování skončilo {0}", + "VoteRunning": "Hlasování běží, skončí {0}", + "VoteFinishedTitle": "Dokončeno hlasování o novém emote", + "CommunityApproved": "Komunitou schválen", + "Boolean": { + "True": "Ano", + "False": "Ne" + }, + "ApproveForVote": { + "True": "Schválen k hlasování", + "False": "Zamítnut k hlasování" + } + }, + "ProcessEmoteSuggestions": { + "Success": "Hlasování pro schválené emoty bylo spuštěno" + }, + "ApprovalButtons": { + "Approve": "Schválit", + "Decline": "Zamítnout" + }, + "EmoteSuggestionChannelNotSet": { + "IsFinish": "Není nastaven kanál pro návrhy ({0}).", + "IsNotFinish": "Tvůj návrh nelze nyní zpracovat, protože není určen kanál pro návrhy." + }, + "EmoteSuggestionChannelNotFound": { + "IsFinish": "Nepodařilo se najít kanál pro návrhy ({0}).", + "NotFinish": "Tvůj návrh emote nelze nyní kvůli technickým důvodům zpracovat." + } }, - "ListEmbed": { - "NoUnverify": "Zatím neprobíhá žádné unverify.", - "Title": "Dočasné odebrání přístupu", - "Fields": { - "StartAt": "Začátek", - "EndAt": "Konec", - "EndFor": "Konec za", - "Selfunverify": "Selfunverify", - "Reason": "Důvod", - "RetainedRoles": "Ponechané role", - "RemovedRoles": "Odebrané role", - "RetainedChannels": "Ponechané kanály", - "RemovedChannels": "Odebrané kanály" - }, - "Boolean": { - "True": "Ano", - "False": "Ne" - } + "Unverify": { + "Validation": { + "KeepableCountExceeded": "Nelze si ponechat více než {0} rolí a kanálů.", + "GuildOwner": "Nelze provést odebrání přístupu, protože uživatel **{0}** je vlastník tohoto serveru.", + "Administrator": "Nelze provést odebrání přístupu, protože uživatel **{0}** má administrátorská oprávnění.", + "MultipleUnverify": "Nelze provést odebrání přístupu, protože uživatel **{0}** již má odebraný přístup do **{1}**.", + "MustBeInFuture": "Konec unverify musí být v budoucnosti.", + "MinimalTime": "Minimální čas pro unverify je {0}", + "HigherRoles": "Nelze provést odebírání přístupu, protože uživatel **{0}** má vyšší role. **({1})**", + "UnverifyWithoutReason": "Nelze bezdůvodně odebrat přístup. Přečti si nápovědu a pak to zkus znovu.", + "UndefinedKeepable": "{0} není ponechatelné." + }, + "Update": { + "UnverifyNotFound": "Aktualizaci času nelze pro hledaného uživatele provést. Unverify nenalezeno.", + "NotEnoughTime": "Aktualizace data a času již není možná. Vypršel čas nebo zbývá méně než půl minuty." + }, + "Message": { + "UnverifyToChannelWithReason": "Dočasné odebrání přístupu pro uživatele **{0}** bylo dokončeno. Přístup bude navrácen **{1}**. Důvod: {2}", + "UnverifyToChannelWithoutReason": "Dočasné odebrání přístupu pro uživatele **{0}** bylo dokončeno. Přístup bude navrácen **{1}**.", + "PrivateUnverifyWithReason": "Byla ti dočasně odebrána všechna práva na serveru **{0}**. Přístup ti bude navrácen **{1}**. Důvod: {2}", + "PrivateUnverifyWithoutReason": "Byla ti dočasně odebrána všechna práva na serveru **{0}**. Přístup ti bude navrácen **{1}**", + "PrivateUpdate": "Byl ti aktualizován čas pro odebrání práv na serveru **{0}**. Přístup ti bude navrácen **{1}**.", + "PrivateUpdateWithReason": "Byl ti aktualizován čas pro odebrání práv na serveru **{0}**. Přístup ti bude navrácen **{1}**. Důvod: *{2}*", + "UpdateToChannel": "Reset konce odebrání přístupu pro uživatele **{0}** byl aktualizován.\nPřístup bude navrácen **{1}**", + "UpdateToChannelWithReason": "Reset konce odebrání přístupu pro uživatele **{0}** byl aktualizován.\nPřístup bude navrácen **{1}**\nDůvod: *{2}*", + "PrivateManuallyRemovedUnverify": "Byl ti předčasně vrácen přístup na serveru **{0}**.", + "ManuallyRemoveToChannel": "Předčasné vrácení přístupu pro uživatele **{0}** bylo dokončeno.", + "ManuallyRemoveFailed": "Předčasné vrácení přístupu pro uživatele **{0}** selhalo. ({1})", + "RemoveAccessUnverifyNotFound": "Předčasné vrácení přístupu pro uživatele **{0}** nelze provést. Unverify nebylo nalezeno.", + "UnverifyFailedToChannel": "Dočasné odebrání přístupu pro uživatele **{0}** se nezdařilo. Uživatel byl obnoven do původního stavu." + }, + "GuildNotFound": "Server na kterém by se mělo nacházet unverify nebyl nalezen.", + "DestUserNotFound": "Uživatel, kterému mělo být přiřazeno unverify nebyl nalezen.", + "Recover": { + "LogItemNotFound": "Záznam o provedeném odebrání přístupu nebyl nalezen.", + "ValidUnverify": "Nelze provést obnovení přístupu uživateli, protože má aktuálně platné unverify.", + "GuildNotFound": "Nelze najít server, na kterém bylo uděleno unverify.", + "MemberNotFound": "Nelze vyhledat uživatele na serveru {0}" + }, + "ListEmbed": { + "NoUnverify": "Zatím neprobíhá žádné unverify.", + "Title": "Dočasné odebrání přístupu", + "Fields": { + "StartAt": "Začátek", + "EndAt": "Konec", + "EndFor": "Konec za", + "Selfunverify": "Selfunverify", + "Reason": "Důvod", + "RetainedRoles": "Ponechané role", + "RemovedRoles": "Odebrané role", + "RetainedChannels": "Ponechané kanály", + "RemovedChannels": "Odebrané kanály" + }, + "Boolean": { + "True": "Ano", + "False": "Ne" + } + }, + "SelfUnverify": { + "GenericError": "Provedení selfunverify se nezdařilo.", + "Keepables": { + "Exists": "Ponechatelná role nebo kanál {0}/{1} již existuje.", + "GroupNotExists": "Skupina ponechatelných rolí nebo kanálů {0} neexistuje.", + "NotExists": "Ponechatelná role nebo kanál {0}/{1} neexistuje.", + "List": { + "NoKeepables": "Nebyly nalezeny žádné ponechatelné přístupy.", + "Title": "Ponechatelné role a kanály", + "Other": "Ostatní" + } + } + } }, - "SelfUnverify": { - "GenericError": "Provedení selfunverify se nezdařilo.", - "Keepables": { - "Exists": "Ponechatelná role nebo kanál {0}/{1} již existuje.", - "GroupNotExists": "Skupina ponechatelných rolí nebo kanálů {0} neexistuje.", - "NotExists": "Ponechatelná role nebo kanál {0}/{1} neexistuje.", + "AuditLog": { + "RemoveItem": { + "NotFound": "Požadovaný záznam v logu nebyl nalezen." + }, "List": { - "NoKeepables": "Nebyly nalezeny žádné ponechatelné přístupy.", - "Title": "Ponechatelné role a kanály", - "Other": "Ostatní" + "TypesCombination": "Nelze filtrovat a vyloučit stejné typy současně.", + "IdNotNumber": "ID[{0}] není číslo." + }, + "GetFileContent": { + "NotFound": "Požadovaný soubor nebyl nalezen." + }, + "CreateLogItem": { + "Required": "Vyberte jeden typ logu.", + "MultipleTypes": "Bylo vybráno více typů logu. Vyberte pouze jeden." } - } - } - }, - "AuditLog": { - "RemoveItem": { - "NotFound": "Požadovaný záznam v logu nebyl nalezen." - }, - "List": { - "TypesCombination": "Nelze filtrovat a vyloučit stejné typy současně.", - "IdNotNumber": "ID[{0}] není číslo." }, - "GetFileContent": { - "NotFound": "Požadovaný soubor nebyl nalezen." + "Auth": { + "CreateToken": { + "UserNotFound": "Proveden pokus s neplatným tokenem, nebo uživatel neexistuje. Opakujte přihlášení.", + "PublicAdminBlocked": "Uživatel {0} má zablokovaný přístup do osobní administrace.", + "PrivateAdminDisabled": "Uživatel {0} nemá oprávnění pro přístup do administrace." + } }, - "CreateLogItem": { - "Required": "Vyberte jeden typ logu.", - "MultipleTypes": "Bylo vybráno více typů logu. Vyberte pouze jeden." - } - }, - "Auth": { - "CreateToken": { - "UserNotFound": "Proveden pokus s neplatným tokenem, nebo uživatel neexistuje. Opakujte přihlášení.", - "PublicAdminBlocked": "Uživatel {0} má zablokovaný přístup do osobní administrace.", - "PrivateAdminDisabled": "Uživatel {0} nemá oprávnění pro přístup do administrace." - } - }, - "AutoReply": { - "NotFound": "Požadovaná automatická odpověď s ID {0} nebyla nalezena." - }, - "User": { - "NotFound": "Uživatel s tímto identifikátorem nebyl nalezen.", - "UserStatus": { - "Online": "Online", - "Offline": "Offline", - "DoNotDisturb": "Nerušit", - "Idle": "Neaktivní" + "AutoReply": { + "NotFound": "Požadovaná automatická odpověď s ID {0} nebyla nalezena." }, - "InfoEmbed": { - "Title": "Informace o uživateli", - "Fields": { - "State": "Stav", - "Roles": "Role", - "CreatedAt": "Vytvořen", - "ActiveDevices": "Aktivní zařízení", - "JoinedAt": "Připojen (pořadí)", - "PremiumSince": "Boost od", - "Points": "Body", - "Reactions": "Reakce (udělené / získané)", - "MessageCount": "Počet zpráv", - "UnverifyCount": "Počet unverify", - "SelfUnverifyCount": "Počet selfunverify", - "UnverifyInfo": "Informace o unverify", - "UsedInvite": "Použitá pozvánka", - "MostActiveChannel": "Nejaktivnější kanál", - "LastMessageIn": "Poslední zpráva", - "WebAdminDetails": "Podrobnosti na webu", - "Badges": "Odznaky" - }, - "NoRoles": "*Uživatel nemá žádné role.*", - "UnverifyRow": "Uděleno {0}unverify do {1}. {2}", - "ReasonRow": "Důvod: {0}", - "UsedVanityInviteRow": "**{0}**\n{1}", - "UsedInviteRow": "**{0}**\nZaložil: **{1}** (**{2}**)", - "VanityInvite": "Univerzální pozvánka", - "WebAdminDetails": "Nepodařilo se sem vložit všechny informace které měly, ostatní informace jsou dostupné ve webové administraci." + "User": { + "NotFound": "Uživatel s tímto identifikátorem nebyl nalezen.", + "UserStatus": { + "Online": "Online", + "Offline": "Offline", + "DoNotDisturb": "Nerušit", + "Idle": "Neaktivní" + }, + "InfoEmbed": { + "Title": "Informace o uživateli", + "Fields": { + "State": "Stav", + "Roles": "Role", + "CreatedAt": "Vytvořen", + "ActiveDevices": "Aktivní zařízení", + "JoinedAt": "Připojen (pořadí)", + "PremiumSince": "Boost od", + "Points": "Body", + "Reactions": "Reakce (udělené / získané)", + "MessageCount": "Počet zpráv", + "UnverifyCount": "Počet unverify", + "SelfUnverifyCount": "Počet selfunverify", + "UnverifyInfo": "Informace o unverify", + "UsedInvite": "Použitá pozvánka", + "MostActiveChannel": "Nejaktivnější kanál", + "LastMessageIn": "Poslední zpráva", + "WebAdminDetails": "Podrobnosti na webu", + "Badges": "Odznaky" + }, + "NoRoles": "*Uživatel nemá žádné role.*", + "UnverifyRow": "Uděleno {0}unverify do {1}. {2}", + "ReasonRow": "Důvod: {0}", + "UsedVanityInviteRow": "**{0}**\n{1}", + "UsedInviteRow": "**{0}**\nZaložil: **{1}** (**{2}**)", + "VanityInvite": "Univerzální pozvánka", + "WebAdminDetails": "Nepodařilo se sem vložit všechny informace které měly, ostatní informace jsou dostupné ve webové administraci." + }, + "AccessList": { + "Title": "Seznam přístupů pro uživatele {0}", + "NoAccess": "Uživatel nemá přístup do žádného kanálu.", + "WithoutCategory": "Bez kategorie" + } }, - "AccessList": { - "Title": "Seznam přístupů pro uživatele {0}", - "NoAccess": "Uživatel nemá přístup do žádného kanálu.", - "WithoutCategory": "Bez kategorie" - } - }, - "Emojization": { - "NoContent": "Nebyl zadán žádný obsah k převodu.", - "DuplicateChar": "Nalezena duplicita ({0}).", - "Done": "Hotovo", - "EmptyResult": "Nepodařilo se provést převod. Zpráva obsahuje pouze neplatné znaky." - }, - "Points": { - "Service": { - "Increment": { - "UserNotFound": "Nelze převést body, protože uživatel který má obdržet body nebyl nalezen.", - "NotAcceptable": "Nepodařilo se vytvořit transakci nových bodů. Zkontrolujte, zda uživatel není bot, má povolené body a že již neexistuje stejná transakce." - }, - "Transfer": { - "SourceUserNotFound": "Nelze převést body, protože uživatel od kterého se přenáší body nebyl nalezen.", - "DestUserNotFound": "Nelze převést body, protože uživatel který má obdržet body nebyl nalezen.", - "SameAccounts": "Nelze převést body mezi stejnými účty.", - "UserIsBot": "Nelze převést body, protože uživatel není běžný uživatel.", - "InsufficientAmount": "Uživatel nemá požadované množství bodů k přenosu.", - "GuildNotFound": "Požadovaný server nebyl nalezen." - } + "Emojization": { + "NoContent": "Nebyl zadán žádný obsah k převodu.", + "DuplicateChar": "Nalezena duplicita ({0}).", + "Done": "Hotovo", + "EmptyResult": "Nepodařilo se provést převod. Zpráva obsahuje pouze neplatné znaky." + }, + "Points": { + "Service": { + "Increment": { + "UserNotFound": "Nelze převést body, protože uživatel který má obdržet body nebyl nalezen.", + "NotAcceptable": "Nepodařilo se vytvořit transakci nových bodů. Zkontrolujte, zda uživatel není bot, má povolené body a že již neexistuje stejná transakce." + }, + "Transfer": { + "SourceUserNotFound": "Nelze převést body, protože uživatel od kterého se přenáší body nebyl nalezen.", + "DestUserNotFound": "Nelze převést body, protože uživatel který má obdržet body nebyl nalezen.", + "SameAccounts": "Nelze převést body mezi stejnými účty.", + "UserIsBot": "Nelze převést body, protože uživatel není běžný uživatel.", + "InsufficientAmount": "Uživatel nemá požadované množství bodů k přenosu.", + "GuildNotFound": "Požadovaný server nebyl nalezen." + } + }, + "Board": { + "NoActivity": "Ještě nebyly zachyceny žádné události ukazující aktivitu na serveru.", + "Title": "Statistika aktivity dle bodů", + "Row": "**{0:N0}.** {1} ({2})", + "Counts": { + "One": "{0:N0} bod", + "TwoToFour": "{0:N0} body", + "FiveAndMore": "{0:N0} bodů" + } + }, + "Chart": { + "Title": { + "Guild": { + "Messages": "Body celého serveru ze zpráv za poslední rok", + "Reactions": "Body celého serveru z reakcí za poslední rok", + "Summary": "Celkové body serveru za poslední rok" + }, + "User": { + "Messages": "Body uživatelů ze zpráv za poslední rok", + "Reactions": "Body uživatelů z reakcí za poslední rok", + "Summary": "Celkové body uživatelů za poslední rok" + } + } + }, + "Image": { + "UserNotFound": "Uživatel nebyl nalezen na serveru.", + "NoActivity": "Uživatel {0} ještě neprojevil na serveru žádnou aktivitu.", + "IsBot": "Boti nesbírají žádné boty, tudíž nelze žádné body zobrazit." + } }, - "Board": { - "NoActivity": "Ještě nebyly zachyceny žádné události ukazující aktivitu na serveru.", - "Title": "Statistika aktivity dle bodů", - "Row": "**{0:N0}.** {1} ({2})", - "Counts": { - "One": "{0:N0} bod", - "TwoToFour": "{0:N0} body", - "FiveAndMore": "{0:N0} bodů" - } + "Pins": { + "UnpinCount": "Zprávy byly úspěšně odepnuty.\nCelkem odepnutých zpráv: **{0}**" }, - "Chart": { - "Title": { - "Guild": { - "Messages": "Body celého serveru ze zpráv za poslední rok", - "Reactions": "Body celého serveru z reakcí za poslední rok", - "Summary": "Celkové body serveru za poslední rok" - }, - "User": { - "Messages": "Body uživatelů ze zpráv za poslední rok", - "Reactions": "Body uživatelů z reakcí za poslední rok", - "Summary": "Celkové body uživatelů za poslední rok" + "Permissions": { + "Useless": { + "CheckSummary": "Nalezeno zbytečných oprávnění: **{0}**\nPočet kanálů: **{1}**\nPočet uživatelů: **{2}**" + }, + "Preconditions": { + "DmNotAllowed": "Použití příkazů v soukromé konverzaci není povoleno.", + "UserCommandsDisabled": "Byly ti zakázány všechny příkazy.", + "ChannelDisabled": "V tomto kanálu není možné provádět příkazy." } - } }, - "Image": { - "UserNotFound": "Uživatel nebyl nalezen na serveru.", - "NoActivity": "Uživatel {0} ještě neprojevil na serveru žádnou aktivitu.", - "IsBot": "Boti nesbírají žádné boty, tudíž nelze žádné body zobrazit." - } - }, - "Pins": { - "UnpinCount": "Zprávy byly úspěšně odepnuty.\nCelkem odepnutých zpráv: **{0}**" - }, - "Permissions": { - "Useless": { - "CheckSummary": "Nalezeno zbytečných oprávnění: **{0}**\nPočet kanálů: **{1}**\nPočet uživatelů: **{2}**" + "MessageClearSubModule": { + "ClearEmoteFromReactions": { + "Success": "Reakce pro emote {0} byly úspěšně smazány." + } }, - "Preconditions": { - "DmNotAllowed": "Použití příkazů v soukromé konverzaci není povoleno.", - "UserCommandsDisabled": "Byly ti zakázány všechny příkazy.", - "ChannelDisabled": "V tomto kanálu není možné provádět příkazy." - } - }, - "MessageClearSubModule": { - "ClearEmoteFromReactions": { - "Success": "Reakce pro emote {0} byly úspěšně smazány." - } - }, - "Roles": { - "MemberCounts": { - "One": "{0:N0} člen", - "TwoToFour": "{0:N0} členové", - "FiveAndMore": "{0:N0} členů" + "Roles": { + "MemberCounts": { + "One": "{0:N0} člen", + "TwoToFour": "{0:N0} členové", + "FiveAndMore": "{0:N0} členů" + }, + "RoleSummaryLine": "{0}, vytvořeno {1}, {2}, {3}, {4}", + "Mentionable": "tagovatelná", + "Managed": "spravuje Discord", + "PremiumSubscriberRole": "booster", + "GuildSummary": "Počet rolí: {0}\nPočet uživatelů s rolí: {1}\nPočet uživatelů bez role: {2}", + "ListTitle": "Seznam rolí", + "DetailTitle": "Detail role @{0}", + "DetailFields": { + "CreatedAt": "Vytvořeno", + "Everyone": "Everyone", + "Hoisted": "Separovaná", + "Managed": "Spravuje Discord", + "Mentionable": "Tagovatelná", + "MemberCount": "Počet členů", + "BoosterRole": "Booster role", + "BotUser": "Náleží botovi", + "Permissions": "Oprávnění" + }, + "Boolean": { + "True": "Ano", + "False": "Ne" + } }, - "RoleSummaryLine": "{0}, vytvořeno {1}, {2}, {3}, {4}", - "Mentionable": "tagovatelná", - "Managed": "spravuje Discord", - "PremiumSubscriberRole": "booster", - "GuildSummary": "Počet rolí: {0}\nPočet uživatelů s rolí: {1}\nPočet uživatelů bez role: {2}", - "ListTitle": "Seznam rolí", - "DetailTitle": "Detail role @{0}", - "DetailFields": { - "CreatedAt": "Vytvořeno", - "Everyone": "Everyone", - "Hoisted": "Separovaná", - "Managed": "Spravuje Discord", - "Mentionable": "Tagovatelná", - "MemberCount": "Počet členů", - "BoosterRole": "Booster role", - "BotUser": "Náleží botovi", - "Permissions": "Oprávnění" + "GuildScheduledEvents": { + "GuildNotFound": "Nepodařilo se najít server.", + "EventNotFound": "Nepodařilo se najít událost.", + "Required": { + "Name": "Název je povinný.", + "StartAt": "Je nutné zadat počátek události.", + "EndAt": "Je nutné zadat konec události.", + "Location": "Místo události je povinné." + }, + "ForbiddenAccess": "Tato událost nebyla vytvořena prostřednictvím bota.", + "CannotCancelEventEnded": "Nelze zrušit již ukončenou událost." + }, + "CooldownEnabled": "Voláš tento příkaz často. Chvíli počkej. Příkaz můžeš znovu zavolat za {0}.", + "ClickOnCommand": "Před odesláním lomítkového příkazu je nezbytné kliknout na automatický doplňovač", + "TypeConverters": { + "BooleanInvalidValue": "Zadaná hodnota není platná hodnota typu boolean. Povolené hodnoty jsou pouze true/false.", + "DateTimeInvalidFormat": "Zadaná hodnota není platné datum a čas.", + "EmoteInvalidFormat": "Zadaný emote se nepodařilo najít a současně to není unicode emoji.", + "Message": { + "InvalidUri": "Zadaná zpráva není ani identifikátor ani odkaz.", + "InvalidDiscordUriFormat": "Zadaný odkaz není ve správném formátu odkazující na Discord zprávu.", + "DmUnsupported": "Použití odkazů na soukromou konverzaci není podporován. Pokud chceš použít soukromou konverzaci, pak zavolej příkaz v soukromé konverzaci s identifikátorem zprávy.", + "InvalidGuildIdentifier": "Nesprávný formát identifikátoru serveru.", + "GuildNotFound": "Identifikátor serveru v odkazu odkazuje na server, kde se bot nenachází.", + "ChannelIdInvalidFormat": "Nesprávný formát identifikátoru kanálu.", + "ChannelNotFound": "Identifikátor kanálu v odkazu odkazuje na neexistující kanál.", + "InvalidMessageIdFormat": "Nesprávný formát identifikátoru zprávy.", + "UnknownMessage": "Identifikátor zprávy v odkazu odkazuje na neexistující zprávu." + }, + "UserNotFound": "Zadaný uživatel `{0}` nebyl nalezen." }, - "Boolean": { - "True": "Ano", - "False": "Ne" - } - }, - "GuildScheduledEvents": { - "GuildNotFound": "Nepodařilo se najít server.", - "EventNotFound": "Nepodařilo se najít událost.", - "Required": { - "Name": "Název je povinný.", - "StartAt": "Je nutné zadat počátek události.", - "EndAt": "Je nutné zadat konec události.", - "Location": "Místo události je povinné." + "PublicApiClients": { + "NotFound": "Požadovaný klient nebyl nalezen." }, - "ForbiddenAccess": "Tato událost nebyla vytvořena prostřednictvím bota.", - "CannotCancelEventEnded": "Nelze zrušit již ukončenou událost." - }, - "CooldownEnabled": "Voláš tento příkaz často. Chvíli počkej. Příkaz můžeš znovu zavolat za {0}.", - "ClickOnCommand": "Před odesláním lomítkového příkazu je nezbytné kliknout na automatický doplňovač", - "TypeConverters": { - "BooleanInvalidValue": "Zadaná hodnota není platná hodnota typu boolean. Povolené hodnoty jsou pouze true/false.", - "DateTimeInvalidFormat": "Zadaná hodnota není platné datum a čas.", - "EmoteInvalidFormat": "Zadaný emote se nepodařilo najít a současně to není unicode emoji.", - "Message": { - "InvalidUri": "Zadaná zpráva není ani identifikátor ani odkaz.", - "InvalidDiscordUriFormat": "Zadaný odkaz není ve správném formátu odkazující na Discord zprávu.", - "DmUnsupported": "Použití odkazů na soukromou konverzaci není podporován. Pokud chceš použít soukromou konverzaci, pak zavolej příkaz v soukromé konverzaci s identifikátorem zprávy.", - "InvalidGuildIdentifier": "Nesprávný formát identifikátoru serveru.", - "GuildNotFound": "Identifikátor serveru v odkazu odkazuje na server, kde se bot nenachází.", - "ChannelIdInvalidFormat": "Nesprávný formát identifikátoru kanálu.", - "ChannelNotFound": "Identifikátor kanálu v odkazu odkazuje na neexistující kanál.", - "InvalidMessageIdFormat": "Nesprávný formát identifikátoru zprávy.", - "UnknownMessage": "Identifikátor zprávy v odkazu odkazuje na neexistující zprávu." + "Jobs": { + "NotFound": "Nepodařilo se nalézt naplánovanou úlohu {0}." }, - "UserNotFound": "Zadaný uživatel `{0}` nebyl nalezen." - }, - "PublicApiClients": { - "NotFound": "Požadovaný klient nebyl nalezen." - }, - "Jobs": { - "NotFound": "Nepodařilo se nalézt naplánovanou úlohu {0}." - }, - "Invite": { - "NotFound": "Nepodařilo se najít pozvánku s kódem {0}" - }, - "Emote": { - "List": { - "Title": "Statistika používání emotů", - "NoStatsOfUser": "Pro uživatele `{0}` ještě nebyla zaznamenáno žádné použití emotu.", - "NoStats": "Ještě nebylo zaznamenáno žádné použití emotu.", - "FieldData": "Počet použití: **{0}**\nPoužilo uživatelů: **{1}**\nPrvní použití: **{2}**\nPoslední použití: **{3}**" + "Invite": { + "NotFound": "Nepodařilo se najít pozvánku s kódem {0}" }, - "Info": { - "NotSupported": "Unicode emoji nejsou v tomto příkazu podporovány.", - "NoActivity": "U tohoto emote ještě nebyla zaznamenána žádná aktivita.", - "Row": "**{0}.** {1} ({2})", - "UnknownUser": "Neznámý uživatel", - "Embed": { - "Title": "Informace o emote", - "Fields": { - "Name": "Název", - "Animated": "Animován", - "FirstOccurence": "První výskyt", - "LastOccurence": "Poslední výskyt", - "FromLastUse": "Od posledního použití", - "UseCount": "Počet použití", - "UsedUsers": "Počet uživatelů", - "Guild": "Server", - "TopTen": "TOP 10", - "Link": "Odkaz" + "Emote": { + "List": { + "Title": "Statistika používání emotů", + "NoStatsOfUser": "Pro uživatele `{0}` ještě nebyla zaznamenáno žádné použití emotu.", + "NoStats": "Ještě nebylo zaznamenáno žádné použití emotu.", + "FieldData": "Počet použití: **{0}**\nPoužilo uživatelů: **{1}**\nPrvní použití: **{2}**\nPoslední použití: **{3}**" }, - "Boolean": { - "True": "Ano", - "False": "Ne" + "Info": { + "NotSupported": "Unicode emoji nejsou v tomto příkazu podporovány.", + "NoActivity": "U tohoto emote ještě nebyla zaznamenána žádná aktivita.", + "Row": "**{0}.** {1} ({2})", + "UnknownUser": "Neznámý uživatel", + "Embed": { + "Title": "Informace o emote", + "Fields": { + "Name": "Název", + "Animated": "Animován", + "FirstOccurence": "První výskyt", + "LastOccurence": "Poslední výskyt", + "FromLastUse": "Od posledního použití", + "UseCount": "Počet použití", + "UsedUsers": "Počet uživatelů", + "Guild": "Server", + "TopTen": "TOP 10", + "Link": "Odkaz" + }, + "Boolean": { + "True": "Ano", + "False": "Ne" + } + } } - } } - } } diff --git a/src/GrillBot.Data/Resources/Localization/messages.en-US.json b/src/GrillBot.Data/Resources/Localization/messages.en-US.json index ddac23ac7..a2438e247 100644 --- a/src/GrillBot.Data/Resources/Localization/messages.en-US.json +++ b/src/GrillBot.Data/Resources/Localization/messages.en-US.json @@ -1,666 +1,667 @@ { - "BirthdayModule": { - "Add": { - "Success": "Date of birth has been saved successfully." - }, - "Remove": { - "Success": "Date of birth successfully removed." - }, - "Have": { - "Yes": "Yes. You have your birthday saved.", - "No": "No. You don't have your a birthday saved." - }, - "Info": { - "NoOneHave": "Nobody has a birthday today. {0}", - "Parts": { - "WithYears": "**{0} ({1} years)**", - "WithoutYears": "**{0}**" - }, - "Template": { - "SingleForm": "Today is the birthday of {0} {1}", - "MultipleForm": "Today is the birthday of {0} and {1} {2}" - } - } - }, - "BotModule": { - "BotInfo": { - "CreatedAt": "Created", - "Uptime": "Uptime", - "Repository": "Repository", - "Documentation": "Documentation", - "Swagger": "Swagger", - "PrivateAdmin": "Administration (Only admins)", - "PublicAdmin": "Administration (For everyone)", - "NavigationPage": "Navigation page" - } - }, - "ChannelModule": { - "ChannelInfo": { - "NoAccess": "I can't give you channel info because you don't have access to it.", - "CreatedAt": "Created", - "MemberCount": "Member count", - "MemberCountValue": { - "One": "{0:N0} member", - "TwoToFour": "{0:N0} members", - "FiveAndMore": "{0:N0} members" - }, - "NewsChannelTitle": "News feed information", - "VoiceChannelTitle": "Voice channel information", - "ThreadChannelTitle": "Thread information", - "StageChannelTitle": "Stage information", - "TextChannelTitle": "Text channel information", - "ForumChannelTitle": "Forum information", - "CategoryChannelTitle": "Category information", - "OtherChannelTitle:": "Unknown channel type information ({0})", - "PermsCountValue": { - "One": "{0:N0} permission", - "TwoToFour": "{0:N0} permissions", - "FiveAndMore": "{0:N0} permissions" - }, - "PermsCount": "User: {0}\nRole: {1}", - "PermsCountTitle": "Permissions count", - "Channel": "Channel", - "TagsCount": "Count of tags", - "TagsCountValue": { - "One": "{0:N0} tag", - "TwoToFour": "{0:N0} tags", - "FiveAndMore": "{0:N0} tags" - }, - "ThreadCount": "Count of threads", - "PublicThreadCountValue": { - "One": "{0:N0} public thread", - "TwoToFour": "{0:N0} public threads", - "FiveAndMore": "{0:N0} public threads" - }, - "PrivateThreadCountValue": { - "One": "{0:N0} private thread", - "TwoToFour": "{0:N0} private threads", - "FiveAndMore": "{0:N0} private threads" - }, - "MessageCount": "Count of messages", - "MessageCountValue": { - "One": "{0:N0} message", - "TwoToFour": "{0:N0} messages", - "FiveAndMore": "{0:N0} messages" - }, - "FirstMessage": "First message", - "LastMessage": "Last message", - "Flags": { - "CommandsDisabled": "All commands disabled", - "AutoReplyDeactivated": "Automatic replies disabled", - "StatsHidden": "Hidden statistics", - "EphemeralCommands": "Ephemeral commands", - "PointsDeactivated": "Points deactivated" - }, - "Configuration": "Configuration", - "TopTen": "TOP 10 users" - }, - "GetChannelboard": { - "NoAccess": "You don't have access to any channels.", - "NoActivity": "No channel activity has been recorded so far.", - "Counts": { - "One": "{0:N0} message", - "TwoToFour": "{0:N0} messages", - "FiveAndMore": "{0:N0} messages" - }, - "Row": "**{0}.** #{1} ({2})", - "Title": "Channel activity statistics" - }, - "PostMessage": { - "GuildNotFound": "Failed to find server.", - "ChannelNotFound": "Could not find channel on server {0}.", - "NoContent": "No valid input was entered to submit.", - "Success": "The message was sent." - }, - "ChannelDetail": { - "ChannelNotFound": "The requested channel was not found." - }, - "ChannelSimpleList": { - "NoMutualGuild": "You do not share the requested server with the bot." - }, - "Clean": { - "ResultMessage": "Successfully deleted messages: **{0}**\nOf those pinned: **{1}**", - "UnsupportedChannel": "Cannot delete messages from non text channels." - } - }, - "DuckModule": { - "GetDuckInfo": { - "CannotGetState": "The status of the duck could not be determined. Please try again later or watch channel {0}", - "DuckName": "U kachničky", - "Closed": "Kachna is closed.", - "NextOpenNotPlanned": "The next opener is not scheduled.", - "NextOpenAt": "Opening hours start {0}.", - "Opened": "Kachna is open!", - "Open": "OPEN", - "TimeToClose": " Opening hours ends {0}.", - "Closing": "We are closing", - "ChillzoneTo": "The duck is open in chillzone mode until {0}!", - "Note": "Note" - } - }, - "GuildModule": { - "GetInfo": { - "CategoryCount": "Count of categories", - "TextChannelCount": "Count of text channels", - "ThreadsCount": "Count of threads", - "VoiceChannelCount": "Count of voice channels", - "RoleCount": "Count of roles", - "EmoteCount": "Count of emotes (regular/animated)", - "BanCount": "Count of bans", - "CreatedAt": "Created", - "Owner": "Owner", - "MemberCount": "Count of members", - "PremiumTier": "Guild level", - "BoosterCount": "Count of boosters", - "Improvements": "Improvements", - "DetailsTitle": "Details", - "DetailsText": "You can find detailed information about the server in the web administration (https://private.grillbot.eu/)", - "Features": { - "AnimatedIcon": "Animated icon", - "Banner": "Banner", - "Commerce": "eCommerce", - "Community": "Community mode", - "Discoverable": "Publicly visible", - "InviteSplash": "Background for invitations", - "MemberVerificationGateEnabled": "Verification on connection", - "News": "News", - "Partnered": "Partner program", - "PreviewEnabled": "Preview the server before connecting", - "VanityUrl": "Vanity URL", - "Verified": "Verified server", - "VIPRegions": "VIP voice region", - "WelcomeScreenEnabled": "Welcome screen", - "MonetizationEnabled": "Monetization", - "MoreStickers": "Stickers", - "PrivateThreads": "Private threads", - "RoleIcons": "Role icons", - "SevenDayThreadArchive": "Archiving threads after a week", - "ThreeDayThreadArchive": "Thread archiving after 3 days", - "TicketedEventsEnabled": "Events", - "AnimatedBanner": "Animated banner", - "TextInVoiceEnabled": "Text in voice channels", - "ThreadsEnabled": "Threads", - "ChannelBanner": "Channel banner", - "Hub": "School server", - "MoreEmoji": "More emotes", - "RoleSubscriptionsAvailableForPurchase": "Paid roles", - "MemberProfiles": "Alternative user profiles", - "NewThreadPermissions": "", - "ThreadsEnabledTesting": "", - "AutoModeration": "AutoMod", - "DiscoverableDisabled": "", - "EnabledDiscoverableBefore": "", - "Featureable": "", - "ForceRelay": "", - "HasDirectoryEntry": "", - "InternalEmployeeOnly": "Only for Discord staff", - "LinkedToHub": "Linked with school server", - "PremiumTier3Override": "", - "RelayEnabled": "", - "RoleSubscriptionsEnabled": "Paid roles enabled", - "ThreadsDefaultAutoArchiveDuration": "", - "DeveloperSupportServer": "Support server", - "InvitesDisabled": "", - "RaidAlertsDisabled": "Raid alerts disabled", - "ClydeEnabled": "Clyde AI", - "GuildWebPageVanityUrl": "Web page vanity URL" - } - }, - "GuildDetail": { - "NotFound": "The requested server could not be found." - }, - "UpdateGuild": { - "AdminChannelNotFound": "Failed to locate admin channel.", - "MuteRoleNotFound": "Failed to find the role that represents silencing the user on unverify.", - "EmoteSuggestionChannelNotFound": "Failed to find channel for emote suggestions.", - "VoteChannelNotFound": "Could not find public polling channel.", - "BotRoomChannelNotFound": "Could not find channel for bots." - } - }, - "MathModule": { - "SolveExpression": { - "Expression": "Expression", - "ComputeFailed": "Calculation failed", - "Report": "Report", - "Result": "Result", - "Timeout": "Expression timed out" - } - }, - "MemeModule": { - "Hi": { - "Template": "Hi {0} {1}" - } - }, - "RemindModule": { - "Create": { - "Success": "A reminder has been created.", - "CopyMessage": "If anyone wishes to receive this notification as well, please click the {0} button.", - "Validation": { - "MustInFuture": "The reminder date and time must be in the future.", - "MinimalTimeTemplate": "The reminder date and time must be later than {0}.", - "MinimalTime": { - "One": "{0:N0} minute", - "TwoToFour": "{0:N0} minutes", - "FiveAndMore": "{0:N0} minutes" - }, - "MessageRequired": "The reminder text is required.", - "MaxLengthExceeded": "The reminder text can have only 1024 characters." - } - }, - "CancelRemind": { - "Cancelled": "The reminder was successfully canceled.", - "CancelledWithNotify": "The reminder was successfully canceled and the target user was notified.", - "NotFound": "The requested reminder was not found.", - "AlreadyCancelled": "You cannot cancel an already canceled reminder.", - "AlreadyNotified": "You cannot cancel a remind that has already been sent.", - "InvalidOperator": "A reminder can only be canceled by the person to whom it is addressed or who created it." - }, - "Suggestions": { - "Incoming": "Incoming #{0} ({1}) from user {2}", - "Outgoing": "Outgoing #{0} ({2}) to user {2}" - }, - "NotifyMessage": { - "ForceTitle": "You received a new reminder prematurely", - "Title": "You have a new reminder", - "Fields": { - "Id": "ID", - "From": "From", - "Attention": "ATTENTION", - "Message": "Message", - "Options": "Options" - }, - "Postponed": "This reminder has already been snoozed {0}x", - "Options": "If you wish to postpone this reminder, click on the appropriate button according to the count of hours." - }, - "Copy": { - "RemindNotFound": "Unable to find original reminder.", - "SelfCopy": "You created this notification, you cannot receive the same.", - "WasCancelled": "This reminder has been cancelled.", - "WasSent": "This reminder has already been sent.", - "CopyExists": "You have already created this notification once from the button. You cannot create another one." - }, - "List": { - "Embed": { - "Title": "List of pending reminders for user {0}", - "NoItems": "There are no reminders pending for user {0}.", - "RowTitle": "#{0} - From {1} at {2} (in {3})" - } - } - }, - "SearchingModule": { - "CreateSearch": { - "Success": "The search was successfully saved." - }, - "RemoveSearch": { - "Success": "The search was successfully deleted.", - "InsufficientPermissions": "You did not create this search and you also do not have elevated permissions to delete a search." + "BirthdayModule": { + "Add": { + "Success": "Date of birth has been saved successfully." + }, + "Remove": { + "Success": "Date of birth successfully removed." + }, + "Have": { + "Yes": "Yes. You have your birthday saved.", + "No": "No. You don't have your a birthday saved." + }, + "Info": { + "NoOneHave": "Nobody has a birthday today. {0}", + "Parts": { + "WithYears": "**{0} ({1} years)**", + "WithoutYears": "**{0}**" + }, + "Template": { + "SingleForm": "Today is the birthday of {0} {1}", + "MultipleForm": "Today is the birthday of {0} and {1} {2}" + } + } }, - "List": { - "Embed": { - "Title": "Searching in the channel #{0}", - "NoItems": "No one searching in the channel {0} yet.", - "NoItemsWithQuery": "No one searching in the channel {0} yet. Try to use another search query." - } - } - }, - "SuggestionModule": { - "SessionExpired": "Form expired. Please create your proposal again.", - "PrivateMessageSuggestionComplete": "Your suggestion to add an emote has been successfully processed.", - "VoteChannelNotDefined": "Voting about new emotes cannot be started. Channel for voting is not defined.", - "VoteChannelNotFound": "Voting about new emotes cannot be started. Channel for voting wasn't found.", - "NoForVote": "Not exists any approved/declined proposal for processing.", - "NoApprovedForVote": "Not exists any approved proposal for processing.", - "SuggestEmote": { - "NoEmoteAndAttachment": "Can't suggest a new emote when no emote is delivered. The emote can be delivered in the form of another emote or image.", - "EmoteExistsInGuild": "Cannot submit an emote that is already on this server.", - "ModalTitle": "Submitting a proposal for a new emote", - "ModalEmoteName": "The name of the emote", - "ModalEmoteDescription": "Description of the emote", - "ModalEmoteDescriptionPlaceholder": "Anything to add? What does the emote describe? Why should we have him here?" + "BotModule": { + "BotInfo": { + "CreatedAt": "Created", + "Uptime": "Uptime", + "Repository": "Repository", + "Documentation": "Documentation", + "Swagger": "Swagger", + "PrivateAdmin": "Administration (Only admins)", + "PublicAdmin": "Administration (For everyone)", + "NavigationPage": "Navigation page" + } }, - "SuggestionEmbed": { - "Title": "New proposal for emote.", - "EmoteNameTitle": "Emote name", - "EmoteDescriptionTitle": "Description", - "VoteFinished": "Vote finished", - "VoteRunning": "Vote running, ends at {0}", - "VoteFinishedTitle": "Finished voting about new emote", - "CommunityApproved": "Community approved", - "Boolean": { - "True": "Yes", - "False": "No" - }, - "ApproveForVote": { - "True": "Approved for voting", - "False": "Declined for voting" - } + "ChannelModule": { + "ChannelInfo": { + "NoAccess": "I can't give you channel info because you don't have access to it.", + "CreatedAt": "Created", + "MemberCount": "Member count", + "MemberCountValue": { + "One": "{0:N0} member", + "TwoToFour": "{0:N0} members", + "FiveAndMore": "{0:N0} members" + }, + "NewsChannelTitle": "News feed information", + "VoiceChannelTitle": "Voice channel information", + "ThreadChannelTitle": "Thread information", + "StageChannelTitle": "Stage information", + "TextChannelTitle": "Text channel information", + "ForumChannelTitle": "Forum information", + "CategoryChannelTitle": "Category information", + "OtherChannelTitle:": "Unknown channel type information ({0})", + "PermsCountValue": { + "One": "{0:N0} permission", + "TwoToFour": "{0:N0} permissions", + "FiveAndMore": "{0:N0} permissions" + }, + "PermsCount": "User: {0}\nRole: {1}", + "PermsCountTitle": "Permissions count", + "Channel": "Channel", + "TagsCount": "Count of tags", + "TagsCountValue": { + "One": "{0:N0} tag", + "TwoToFour": "{0:N0} tags", + "FiveAndMore": "{0:N0} tags" + }, + "ThreadCount": "Count of threads", + "PublicThreadCountValue": { + "One": "{0:N0} public thread", + "TwoToFour": "{0:N0} public threads", + "FiveAndMore": "{0:N0} public threads" + }, + "PrivateThreadCountValue": { + "One": "{0:N0} private thread", + "TwoToFour": "{0:N0} private threads", + "FiveAndMore": "{0:N0} private threads" + }, + "MessageCount": "Count of messages", + "MessageCountValue": { + "One": "{0:N0} message", + "TwoToFour": "{0:N0} messages", + "FiveAndMore": "{0:N0} messages" + }, + "FirstMessage": "First message", + "LastMessage": "Last message", + "Flags": { + "CommandsDisabled": "All commands disabled", + "AutoReplyDeactivated": "Automatic replies disabled", + "StatsHidden": "Hidden statistics", + "EphemeralCommands": "Ephemeral commands", + "PointsDeactivated": "Points deactivated" + }, + "Configuration": "Configuration", + "TopTen": "TOP 10 users" + }, + "GetChannelboard": { + "NoAccess": "You don't have access to any channels.", + "NoActivity": "No channel activity has been recorded so far.", + "Counts": { + "One": "{0:N0} message", + "TwoToFour": "{0:N0} messages", + "FiveAndMore": "{0:N0} messages" + }, + "Row": "**{0}.** #{1} ({2})", + "Title": "Channel activity statistics" + }, + "PostMessage": { + "GuildNotFound": "Failed to find server.", + "ChannelNotFound": "Could not find channel on server {0}.", + "NoContent": "No valid input was entered to submit.", + "Success": "The message was sent." + }, + "ChannelDetail": { + "ChannelNotFound": "The requested channel was not found." + }, + "ChannelSimpleList": { + "NoMutualGuild": "You do not share the requested server with the bot." + }, + "Clean": { + "ResultMessage": "Successfully deleted messages: **{0}**\nOf those pinned: **{1}**", + "UnsupportedChannel": "Cannot delete messages from non text channels." + } }, - "ProcessEmoteSuggestions": { - "Success": "Voting for approved emotes has started." + "DuckModule": { + "GetDuckInfo": { + "CannotGetState": "The status of the duck could not be determined. Please try again later or watch channel {0}", + "DuckName": "U kachničky", + "Closed": "Kachna is closed.", + "NextOpenNotPlanned": "The next opener is not scheduled.", + "NextOpenAt": "Opening hours start {0}.", + "Opened": "Kachna is open!", + "Open": "OPEN", + "TimeToClose": " Opening hours ends {0}.", + "Closing": "We are closing", + "ChillzoneTo": "The duck is open in chillzone mode until {0}!", + "Note": "Note" + } }, - "ApprovalButtons": { - "Approve": "Approve", - "Decline": "Decline" + "GuildModule": { + "GetInfo": { + "CategoryCount": "Count of categories", + "TextChannelCount": "Count of text channels", + "ThreadsCount": "Count of threads", + "VoiceChannelCount": "Count of voice channels", + "RoleCount": "Count of roles", + "EmoteCount": "Count of emotes (regular/animated)", + "BanCount": "Count of bans", + "CreatedAt": "Created", + "Owner": "Owner", + "MemberCount": "Count of members", + "PremiumTier": "Guild level", + "BoosterCount": "Count of boosters", + "Improvements": "Improvements", + "DetailsTitle": "Details", + "DetailsText": "You can find detailed information about the server in the web administration (https://private.grillbot.eu/)", + "Features": { + "AnimatedIcon": "Animated icon", + "Banner": "Banner", + "Commerce": "eCommerce", + "Community": "Community mode", + "Discoverable": "Publicly visible", + "InviteSplash": "Background for invitations", + "MemberVerificationGateEnabled": "Verification on connection", + "News": "News", + "Partnered": "Partner program", + "PreviewEnabled": "Preview the server before connecting", + "VanityUrl": "Vanity URL", + "Verified": "Verified server", + "VIPRegions": "VIP voice region", + "WelcomeScreenEnabled": "Welcome screen", + "MonetizationEnabled": "Monetization", + "MoreStickers": "Stickers", + "PrivateThreads": "Private threads", + "RoleIcons": "Role icons", + "SevenDayThreadArchive": "Archiving threads after a week", + "ThreeDayThreadArchive": "Thread archiving after 3 days", + "TicketedEventsEnabled": "Events", + "AnimatedBanner": "Animated banner", + "TextInVoiceEnabled": "Text in voice channels", + "ThreadsEnabled": "Threads", + "ChannelBanner": "Channel banner", + "Hub": "School server", + "MoreEmoji": "More emotes", + "RoleSubscriptionsAvailableForPurchase": "Paid roles", + "MemberProfiles": "Alternative user profiles", + "NewThreadPermissions": "", + "ThreadsEnabledTesting": "", + "AutoModeration": "AutoMod", + "DiscoverableDisabled": "", + "EnabledDiscoverableBefore": "", + "Featureable": "", + "ForceRelay": "", + "HasDirectoryEntry": "", + "InternalEmployeeOnly": "Only for Discord staff", + "LinkedToHub": "Linked with school server", + "PremiumTier3Override": "", + "RelayEnabled": "", + "RoleSubscriptionsEnabled": "Paid roles enabled", + "ThreadsDefaultAutoArchiveDuration": "", + "DeveloperSupportServer": "Support server", + "InvitesDisabled": "", + "RaidAlertsDisabled": "Raid alerts disabled", + "ClydeEnabled": "Clyde AI", + "GuildWebPageVanityUrl": "Web page vanity URL" + } + }, + "GuildDetail": { + "NotFound": "The requested server could not be found." + }, + "UpdateGuild": { + "AdminChannelNotFound": "Failed to locate admin channel.", + "MuteRoleNotFound": "Failed to find the role that represents silencing the user on unverify.", + "EmoteSuggestionChannelNotFound": "Failed to find channel for emote suggestions.", + "VoteChannelNotFound": "Could not find public polling channel.", + "BotRoomChannelNotFound": "Could not find channel for bots." + } }, - "EmoteSuggestionChannelNotSet": { - "IsFinish": "Channel for suggestions ({0}) is not set.", - "IsNotFinish": "Your proposal cannot be processed at this time because the proposal channel is not set up." + "MathModule": { + "SolveExpression": { + "Expression": "Expression", + "ComputeFailed": "Calculation failed", + "Report": "Report", + "Result": "Result", + "Timeout": "Expression timed out" + } }, - "EmoteSuggestionChannelNotFound": { - "IsFinish": "Channel for suggestions ({0}) is not set.", - "NotFinish": "Your proposal cannot be processed at this time due to technical reasons." - } - }, - "Unverify": { - "Validation": { - "KeepableCountExceeded": "Cannot keep more than {0} roles and channels.", - "GuildOwner": "Unable to remove access because user **{0}** is the owner of this server.", - "Administrator": "Unable to remove access because user **{0}** has administrative privileges.", - "MultipleUnverify": "Unable to revoke access because user **{0}** has already revoked access to **{1}**.", - "MustBeInFuture": "The end of unverify must be in the future.", - "MinimalTime": "Minimum time for unverify is {0}", - "HigherRoles": "Unable to remove access because user **{0}** has higher roles. **({1})**", - "UnverifyWithoutReason": "Access cannot be revoked without reason. Read the help and then try again.", - "UndefinedKeepable": "{0} is not retainable." + "MemeModule": { + "Hi": { + "Template": "Hi {0} {1}" + } }, - "Update": { - "UnverifyNotFound": "Unable to update the time for the searched user. Unverify not found.", - "NotEnoughTime": "Updating the date and time is no longer possible. Time is up or less than half a minute remains." + "RemindModule": { + "Create": { + "Success": "A reminder has been created.", + "CopyMessage": "If anyone wishes to receive this notification as well, please click the {0} button.", + "Validation": { + "MustInFuture": "The reminder date and time must be in the future.", + "MinimalTimeTemplate": "The reminder date and time must be later than {0}.", + "MinimalTime": { + "One": "{0:N0} minute", + "TwoToFour": "{0:N0} minutes", + "FiveAndMore": "{0:N0} minutes" + }, + "MessageRequired": "The reminder text is required.", + "MaxLengthExceeded": "The reminder text can have only 1024 characters." + } + }, + "CancelRemind": { + "Cancelled": "The reminder was successfully canceled.", + "CancelledWithNotify": "The reminder was successfully canceled and the target user was notified.", + "NotFound": "The requested reminder was not found.", + "AlreadyCancelled": "You cannot cancel an already canceled reminder.", + "AlreadyNotified": "You cannot cancel a remind that has already been sent.", + "InvalidOperator": "A reminder can only be canceled by the person to whom it is addressed or who created it." + }, + "Suggestions": { + "Incoming": "Incoming #{0} ({1}) from user {2}", + "Outgoing": "Outgoing #{0} ({2}) to user {2}" + }, + "NotifyMessage": { + "ForceTitle": "You received a new reminder prematurely", + "Title": "You have a new reminder", + "Fields": { + "Id": "ID", + "From": "From", + "Attention": "ATTENTION", + "Message": "Message", + "Options": "Options" + }, + "Postponed": "This reminder has already been snoozed {0}x", + "Options": "If you wish to postpone this reminder, click on the appropriate button according to the count of hours." + }, + "Copy": { + "RemindNotFound": "Unable to find original reminder.", + "SelfCopy": "You created this notification, you cannot receive the same.", + "WasCancelled": "This reminder has been cancelled.", + "WasSent": "This reminder has already been sent.", + "CopyExists": "You have already created this notification once from the button. You cannot create another one." + }, + "List": { + "Embed": { + "Title": "List of pending reminders for user {0}", + "NoItems": "There are no reminders pending for user {0}.", + "RowTitle": "#{0} - From {1} at {2} (in {3})" + } + } }, - "Message": { - "UnverifyToChannelWithReason": "Temporarily removing access for user **{0}** has been completed. Access will be restored **{1}**. Reason: {2}", - "UnverifyToChannelWithoutReason": "Temporarily removing access for user **{0}** has been completed. Access will be restored **{1}**.", - "PrivateUnverifyWithReason": "All rights on server **{0}** have been temporarily revoked. Access will be returned to you **{1}**. Reason: {2}", - "PrivateUnverifyWithoutReason": "All rights on server **{0}** have been temporarily revoked. Your access will be restored **{1}**", - "PrivateUpdate": "The time to revoke your rights on server **{0}** has been updated. Access will be returned to you **{1}**.", - "PrivateUpdateWithReason": "The time to revoke your rights on server **{0}** has been updated. Access will be returned to you **{1}**. Reason: *{2}*", - "UpdateToChannel": "End of access removal reset for user **{0}** has been updated.\nAccess will be restored to **{1}**", - "UpdateToChannelWithReason": "End of access removal reset for user **{0}** has been updated.\nAccess will be restored to **{1}**\nReason: *{2}*", - "PrivateManuallyRemovedUnverify": "Access to server **{0}** has been returned to you prematurely.", - "ManuallyRemoveToChannel": "Early rollback of access for user **{0}** has completed.", - "ManuallyRemoveFailed": "Early rollback of access for user **{0}** failed. ({1})", - "RemoveAccessUnverifyNotFound": "Unable to restore access early for user **{0}**. Unverify not found.", - "UnverifyFailedToChannel": "Failed to temporarily remove access for user **{0}**. The user has been restored to their original state." + "SearchingModule": { + "CreateSearch": { + "Success": "The search was successfully saved." + }, + "RemoveSearch": { + "Success": "The search was successfully deleted.", + "InsufficientPermissions": "You did not create this search and you also do not have elevated permissions to delete a search." + }, + "List": { + "Embed": { + "Title": "Searching in the channel #{0}", + "NoItems": "No one searching in the channel {0} yet.", + "NoItemsWithQuery": "No one searching in the channel {0} yet. Try to use another search query." + } + } }, - "GuildNotFound": "The server on which unverify should be located was not found.", - "DestUserNotFound": "The user to whom unverify should have been assigned was not found.", - "Recover": { - "LogItemNotFound": "A record of access removal was not found.", - "ValidUnverify": "Unable to restore access to the user because he currently has unverify in effect.", - "GuildNotFound": "Unable to find the server on which unverify was applied.", - "MemberNotFound": "Unable to locate user on guild {0}." + "SuggestionModule": { + "SessionExpired": "Form expired. Please create your proposal again.", + "PrivateMessageSuggestionComplete": "Your suggestion to add an emote has been successfully processed.", + "VoteChannelNotDefined": "Voting about new emotes cannot be started. Channel for voting is not defined.", + "VoteChannelNotFound": "Voting about new emotes cannot be started. Channel for voting wasn't found.", + "NoForVote": "Not exists any approved/declined proposal for processing.", + "NoApprovedForVote": "Not exists any approved proposal for processing.", + "SuggestEmote": { + "NoEmoteAndAttachment": "Can't suggest a new emote when no emote is delivered. The emote can be delivered in the form of another emote or image.", + "EmoteExistsInGuild": "Cannot submit an emote that is already on this server.", + "ModalTitle": "Submitting a proposal for a new emote", + "ModalEmoteName": "The name of the emote", + "ModalEmoteDescription": "Description of the emote", + "ModalEmoteDescriptionPlaceholder": "Anything to add? What does the emote describe? Why should we have him here?", + "UnsupportedImageFormat": "Unsupported image extension. Only images with the PNG extension are supported." + }, + "SuggestionEmbed": { + "Title": "New proposal for emote.", + "EmoteNameTitle": "Emote name", + "EmoteDescriptionTitle": "Description", + "VoteFinished": "Vote finished", + "VoteRunning": "Vote running, ends at {0}", + "VoteFinishedTitle": "Finished voting about new emote", + "CommunityApproved": "Community approved", + "Boolean": { + "True": "Yes", + "False": "No" + }, + "ApproveForVote": { + "True": "Approved for voting", + "False": "Declined for voting" + } + }, + "ProcessEmoteSuggestions": { + "Success": "Voting for approved emotes has started." + }, + "ApprovalButtons": { + "Approve": "Approve", + "Decline": "Decline" + }, + "EmoteSuggestionChannelNotSet": { + "IsFinish": "Channel for suggestions ({0}) is not set.", + "IsNotFinish": "Your proposal cannot be processed at this time because the proposal channel is not set up." + }, + "EmoteSuggestionChannelNotFound": { + "IsFinish": "Channel for suggestions ({0}) is not set.", + "NotFinish": "Your proposal cannot be processed at this time due to technical reasons." + } }, - "ListEmbed": { - "NoUnverify": "There is no unverify yet.", - "Title": "Temporary access removal", - "Fields": { - "StartAt": "Start at", - "EndAt": "End at", - "EndFor": "End for", - "Selfunverify": "Selfunverify", - "Reason": "Reason", - "RetainedRoles": "Retained roles", - "RemovedRoles": "Removed role", - "RetainedChannels": "Retained channels", - "RemovedChannels": "Removed channels" - }, - "Boolean": { - "True": "Yes", - "False": "No" - } + "Unverify": { + "Validation": { + "KeepableCountExceeded": "Cannot keep more than {0} roles and channels.", + "GuildOwner": "Unable to remove access because user **{0}** is the owner of this server.", + "Administrator": "Unable to remove access because user **{0}** has administrative privileges.", + "MultipleUnverify": "Unable to revoke access because user **{0}** has already revoked access to **{1}**.", + "MustBeInFuture": "The end of unverify must be in the future.", + "MinimalTime": "Minimum time for unverify is {0}", + "HigherRoles": "Unable to remove access because user **{0}** has higher roles. **({1})**", + "UnverifyWithoutReason": "Access cannot be revoked without reason. Read the help and then try again.", + "UndefinedKeepable": "{0} is not retainable." + }, + "Update": { + "UnverifyNotFound": "Unable to update the time for the searched user. Unverify not found.", + "NotEnoughTime": "Updating the date and time is no longer possible. Time is up or less than half a minute remains." + }, + "Message": { + "UnverifyToChannelWithReason": "Temporarily removing access for user **{0}** has been completed. Access will be restored **{1}**. Reason: {2}", + "UnverifyToChannelWithoutReason": "Temporarily removing access for user **{0}** has been completed. Access will be restored **{1}**.", + "PrivateUnverifyWithReason": "All rights on server **{0}** have been temporarily revoked. Access will be returned to you **{1}**. Reason: {2}", + "PrivateUnverifyWithoutReason": "All rights on server **{0}** have been temporarily revoked. Your access will be restored **{1}**", + "PrivateUpdate": "The time to revoke your rights on server **{0}** has been updated. Access will be returned to you **{1}**.", + "PrivateUpdateWithReason": "The time to revoke your rights on server **{0}** has been updated. Access will be returned to you **{1}**. Reason: *{2}*", + "UpdateToChannel": "End of access removal reset for user **{0}** has been updated.\nAccess will be restored to **{1}**", + "UpdateToChannelWithReason": "End of access removal reset for user **{0}** has been updated.\nAccess will be restored to **{1}**\nReason: *{2}*", + "PrivateManuallyRemovedUnverify": "Access to server **{0}** has been returned to you prematurely.", + "ManuallyRemoveToChannel": "Early rollback of access for user **{0}** has completed.", + "ManuallyRemoveFailed": "Early rollback of access for user **{0}** failed. ({1})", + "RemoveAccessUnverifyNotFound": "Unable to restore access early for user **{0}**. Unverify not found.", + "UnverifyFailedToChannel": "Failed to temporarily remove access for user **{0}**. The user has been restored to their original state." + }, + "GuildNotFound": "The server on which unverify should be located was not found.", + "DestUserNotFound": "The user to whom unverify should have been assigned was not found.", + "Recover": { + "LogItemNotFound": "A record of access removal was not found.", + "ValidUnverify": "Unable to restore access to the user because he currently has unverify in effect.", + "GuildNotFound": "Unable to find the server on which unverify was applied.", + "MemberNotFound": "Unable to locate user on guild {0}." + }, + "ListEmbed": { + "NoUnverify": "There is no unverify yet.", + "Title": "Temporary access removal", + "Fields": { + "StartAt": "Start at", + "EndAt": "End at", + "EndFor": "End for", + "Selfunverify": "Selfunverify", + "Reason": "Reason", + "RetainedRoles": "Retained roles", + "RemovedRoles": "Removed role", + "RetainedChannels": "Retained channels", + "RemovedChannels": "Removed channels" + }, + "Boolean": { + "True": "Yes", + "False": "No" + } + }, + "SelfUnverify": { + "GenericError": "Selfunverify failed.", + "Keepables": { + "Exists": "Keepable role or channel {0}/{1} already exists.", + "GroupNotExists": "The retainable role or channel group {0} does not exist.", + "NotExists": "Keepable role or channel {0}/{1} does not exist.", + "List": { + "NoKeepables": "No keepable accesses were found.", + "Title": "Keepable roles and channels", + "Other": "Other" + } + } + } }, - "SelfUnverify": { - "GenericError": "Selfunverify failed.", - "Keepables": { - "Exists": "Keepable role or channel {0}/{1} already exists.", - "GroupNotExists": "The retainable role or channel group {0} does not exist.", - "NotExists": "Keepable role or channel {0}/{1} does not exist.", + "AuditLog": { + "RemoveItem": { + "NotFound": "The requested log entry was not found." + }, "List": { - "NoKeepables": "No keepable accesses were found.", - "Title": "Keepable roles and channels", - "Other": "Other" + "TypesCombination": "You cannot filter and exclude the same types at the same time.", + "IdNotNumber": "ID[{0}] is not number." + }, + "GetFileContent": { + "NotFound": "The requested file was not found." + }, + "CreateLogItem": { + "Required": "Select one record type.", + "MultipleTypes": "Multiple record types have been selected. Select only one." } - } - } - }, - "AuditLog": { - "RemoveItem": { - "NotFound": "The requested log entry was not found." - }, - "List": { - "TypesCombination": "You cannot filter and exclude the same types at the same time.", - "IdNotNumber": "ID[{0}] is not number." }, - "GetFileContent": { - "NotFound": "The requested file was not found." + "Auth": { + "CreateToken": { + "UserNotFound": "An attempt was made with an invalid token, or the user does not exist. Please login again.", + "PublicAdminBlocked": "User {0} has blocked access to public administration.", + "PrivateAdminDisabled": "User {0} does not have permission to access private administration." + } }, - "CreateLogItem": { - "Required": "Select one record type.", - "MultipleTypes": "Multiple record types have been selected. Select only one." - } - }, - "Auth": { - "CreateToken": { - "UserNotFound": "An attempt was made with an invalid token, or the user does not exist. Please login again.", - "PublicAdminBlocked": "User {0} has blocked access to public administration.", - "PrivateAdminDisabled": "User {0} does not have permission to access private administration." - } - }, - "AutoReply": { - "NotFound": "The requested auto-reply with ID {0} was not found." - }, - "User": { - "NotFound": "No user with this identifier was found.", - "UserStatus": { - "Online": "Online", - "Offline": "Offline", - "DoNotDisturb": "Do not disturb", - "Idle": "Inactive" + "AutoReply": { + "NotFound": "The requested auto-reply with ID {0} was not found." }, - "InfoEmbed": { - "Title": "User information", - "Fields": { - "State": "Status", - "Roles": "Roles", - "CreatedAt": "Created at", - "ActiveDevices": "Active devices", - "JoinedAt": "Joined at (order)", - "PremiumSince": "Boost since", - "Points": "Points", - "Reactions": "Reactions (given / obtained)", - "MessageCount": "Count of messages", - "UnverifyCount": "Count of unverifies", - "SelfUnverifyCount": "Count of self-unverifies", - "UnverifyInfo": "Unverify info", - "UsedInvite": "Used invite", - "MostActiveChannel": "Most active channel", - "LastMessageIn": "Last message in", - "WebAdminDetails": "Details", - "Badges": "Badges" - }, - "NoRoles": "*The user has no roles.*", - "UnverifyRow": "User has {0}unverify to {1}. {2}", - "ReasonRow": "Reason: {0}", - "UsedVanityInviteRow": "**{0}**\n{1}", - "UsedInviteRow": "**{0}**\nCreated by: **{1}** (**{2}**)", - "VanityInvite": "Universal invite", - "WebAdminDetails": "It was not possible to insert all the information, the rest is available in the web administration." + "User": { + "NotFound": "No user with this identifier was found.", + "UserStatus": { + "Online": "Online", + "Offline": "Offline", + "DoNotDisturb": "Do not disturb", + "Idle": "Inactive" + }, + "InfoEmbed": { + "Title": "User information", + "Fields": { + "State": "Status", + "Roles": "Roles", + "CreatedAt": "Created at", + "ActiveDevices": "Active devices", + "JoinedAt": "Joined at (order)", + "PremiumSince": "Boost since", + "Points": "Points", + "Reactions": "Reactions (given / obtained)", + "MessageCount": "Count of messages", + "UnverifyCount": "Count of unverifies", + "SelfUnverifyCount": "Count of self-unverifies", + "UnverifyInfo": "Unverify info", + "UsedInvite": "Used invite", + "MostActiveChannel": "Most active channel", + "LastMessageIn": "Last message in", + "WebAdminDetails": "Details", + "Badges": "Badges" + }, + "NoRoles": "*The user has no roles.*", + "UnverifyRow": "User has {0}unverify to {1}. {2}", + "ReasonRow": "Reason: {0}", + "UsedVanityInviteRow": "**{0}**\n{1}", + "UsedInviteRow": "**{0}**\nCreated by: **{1}** (**{2}**)", + "VanityInvite": "Universal invite", + "WebAdminDetails": "It was not possible to insert all the information, the rest is available in the web administration." + }, + "AccessList": { + "Title": "Access list of user {0}", + "NoAccess": "User does not have access to any channel.", + "WithoutCategory": "Without category" + } }, - "AccessList": { - "Title": "Access list of user {0}", - "NoAccess": "User does not have access to any channel.", - "WithoutCategory": "Without category" - } - }, - "Emojization": { - "NoContent": "No content to convert was specified.", - "DuplicateChar": "Found duplicate ({0}).", - "Done": "Done", - "EmptyResult": "Conversion failed. The message contains only invalid characters." - }, - "Points": { - "Service": { - "Increment": { - "UserNotFound": "Unable to transfer points because the user to receive the points was not found.", - "NotAcceptable": "Creating of new transaction failed. Zkontrolujte, zda uživatel není robot, má povolené body a transakce neexistuje." - }, - "Transfer": { - "SourceUserNotFound": "Unable to transfer points because the user to transfer points from was not found.", - "DestUserNotFound": "Unable to transfer points because the user to receive the points was not found.", - "SameAccounts": "Points cannot be transferred between the same accounts.", - "UserIsBot": "Unable to transfer points because user is a bot.", - "InsufficientAmount": "The user does not have the required amount of points to transfer.", - "GuildNotFound": "Requested guild wasn't found." - } + "Emojization": { + "NoContent": "No content to convert was specified.", + "DuplicateChar": "Found duplicate ({0}).", + "Done": "Done", + "EmptyResult": "Conversion failed. The message contains only invalid characters." + }, + "Points": { + "Service": { + "Increment": { + "UserNotFound": "Unable to transfer points because the user to receive the points was not found.", + "NotAcceptable": "Creating of new transaction failed. Zkontrolujte, zda uživatel není robot, má povolené body a transakce neexistuje." + }, + "Transfer": { + "SourceUserNotFound": "Unable to transfer points because the user to transfer points from was not found.", + "DestUserNotFound": "Unable to transfer points because the user to receive the points was not found.", + "SameAccounts": "Points cannot be transferred between the same accounts.", + "UserIsBot": "Unable to transfer points because user is a bot.", + "InsufficientAmount": "The user does not have the required amount of points to transfer.", + "GuildNotFound": "Requested guild wasn't found." + } + }, + "Board": { + "NoActivity": "No events showing activity on the server have been captured yet.", + "Title": "Activity statistics by points", + "Row": "**{0:N0}.** {1} ({2})", + "Counts": { + "One": "{0:N0} point", + "TwoToFour": "{0:N0} points", + "FiveAndMore": "{0:N0} points" + } + }, + "Chart": { + "Title": { + "Guild": { + "Messages": "Points of guild from messages for the last year.", + "Reactions": "Points of guild from reactions for the last year.", + "Summary": "Total points of guild for the last year." + }, + "User": { + "Messages": "Points of users from messages for the last year.", + "Reactions": "Points of users from reactions for the last year.", + "Summary": "Total points of users for the last year." + } + } + }, + "Image": { + "UserNotFound": "User wasn't found in the guild.", + "NoActivity": "User {0} showed no activity yet.", + "IsBot": "The bots don't gather any points, therefore these points cannot be displayed." + } }, - "Board": { - "NoActivity": "No events showing activity on the server have been captured yet.", - "Title": "Activity statistics by points", - "Row": "**{0:N0}.** {1} ({2})", - "Counts": { - "One": "{0:N0} point", - "TwoToFour": "{0:N0} points", - "FiveAndMore": "{0:N0} points" - } + "Pins": { + "UnpinCount": "Messages successfully unpinned.\nTotal unpinned messages: **{0}**" }, - "Chart": { - "Title": { - "Guild": { - "Messages": "Points of guild from messages for the last year.", - "Reactions": "Points of guild from reactions for the last year.", - "Summary": "Total points of guild for the last year." - }, - "User": { - "Messages": "Points of users from messages for the last year.", - "Reactions": "Points of users from reactions for the last year.", - "Summary": "Total points of users for the last year." + "Permissions": { + "Useless": { + "CheckSummary": "Unnecessary permissions found: **{0}**\nCount of channels: **{1}**\nCount of users: **{2}**" + }, + "Preconditions": { + "DmNotAllowed": "Commands in a private conversations are not allowed.", + "UserCommandsDisabled": "You have been banned from all commands.", + "ChannelDisabled": "In this channel you are not able to use commands." } - } }, - "Image": { - "UserNotFound": "User wasn't found in the guild.", - "NoActivity": "User {0} showed no activity yet.", - "IsBot": "The bots don't gather any points, therefore these points cannot be displayed." - } - }, - "Pins": { - "UnpinCount": "Messages successfully unpinned.\nTotal unpinned messages: **{0}**" - }, - "Permissions": { - "Useless": { - "CheckSummary": "Unnecessary permissions found: **{0}**\nCount of channels: **{1}**\nCount of users: **{2}**" + "MessageClearSubModule": { + "ClearEmoteFromReactions": { + "Success": "Reactions for emote {0} have been successfully deleted." + } }, - "Preconditions": { - "DmNotAllowed": "Commands in a private conversations are not allowed.", - "UserCommandsDisabled": "You have been banned from all commands.", - "ChannelDisabled": "In this channel you are not able to use commands." - } - }, - "MessageClearSubModule": { - "ClearEmoteFromReactions": { - "Success": "Reactions for emote {0} have been successfully deleted." - } - }, - "Roles": { - "MemberCounts": { - "One": "{0:N0} member", - "TwoToFour": "{0:N0} members", - "FiveAndMore": "{0:N0} members" + "Roles": { + "MemberCounts": { + "One": "{0:N0} member", + "TwoToFour": "{0:N0} members", + "FiveAndMore": "{0:N0} members" + }, + "RoleSummaryLine": "{0}, created at {1}, {2}, {3}, {4}", + "Mentionable": "mentionable", + "Managed": "managed by Discord", + "PremiumSubscriberRole": "booster", + "GuildSummary": "Count of roles: {0}\nCount of users with role: {1}\nCount of users without a role: {2}", + "ListTitle": "List of roles", + "DetailTitle": "Detail of role @{0}", + "DetailFields": { + "CreatedAt": "Created at", + "Everyone": "Everyone", + "Hoisted": "Separated", + "Managed": "Managed by Discord", + "Mentionable": "Mentionable", + "MemberCount": "Count of members", + "BoosterRole": "Booster role", + "BotUser": "It belongs to the bot", + "Permissions": "Permissions" + }, + "Boolean": { + "True": "Yes", + "False": "No" + } }, - "RoleSummaryLine": "{0}, created at {1}, {2}, {3}, {4}", - "Mentionable": "mentionable", - "Managed": "managed by Discord", - "PremiumSubscriberRole": "booster", - "GuildSummary": "Count of roles: {0}\nCount of users with role: {1}\nCount of users without a role: {2}", - "ListTitle": "List of roles", - "DetailTitle": "Detail of role @{0}", - "DetailFields": { - "CreatedAt": "Created at", - "Everyone": "Everyone", - "Hoisted": "Separated", - "Managed": "Managed by Discord", - "Mentionable": "Mentionable", - "MemberCount": "Count of members", - "BoosterRole": "Booster role", - "BotUser": "It belongs to the bot", - "Permissions": "Permissions" + "GuildScheduledEvents": { + "GuildNotFound": "Unable to find guild.", + "EventNotFound": "Unable to find event.", + "Required": { + "Name": "Name is required.", + "StartAt": "It is necessary to set start time of the event.", + "EndAt": "It is necessary to set end time of the event.", + "Location": "Event location is required." + }, + "ForbiddenAccess": "This event wasn't created via bot.", + "CannotCancelEventEnded": "Cannot cancel cancelled event." + }, + "CooldownEnabled": "You're executing this command too often. Wait a moment. You can execute this command again after {0}", + "ClickOnCommand": "Before sending slash command you need to click on autocomplete", + "TypeConverters": { + "BooleanInvalidValue": "Provided string is not valid boolean value. Allowed values are only true or false.", + "DateTimeInvalidFormat": "Provided string is not valid datetime.", + "EmoteInvalidFormat": "Provided emoji could not be found.", + "Message": { + "InvalidUri": "Provided message identification is not identifier or link.", + "InvalidDiscordUriFormat": "The link provided does not have a valid Discord message linking format.", + "DmUnsupported": "Using links to private conversation is not supported. If you want use private conversation, call command in the private conversation with message identifier.", + "InvalidGuildIdentifier": "Provided guild identifier has no valid format.", + "GuildNotFound": "The guild with the specified identifier was not found.", + "ChannelIdInvalidFormat": "Provided channel identifier has no valid format.", + "ChannelNotFound": "The channel with the specified identifier was not found.", + "InvalidMessageIdFormat": "Provided message identifier has no valid format.", + "UnknownMessage": "The message with the specified identifier was not found." + }, + "UserNotFound": "Provided user `{0}` wasn't found." }, - "Boolean": { - "True": "Yes", - "False": "No" - } - }, - "GuildScheduledEvents": { - "GuildNotFound": "Unable to find guild.", - "EventNotFound": "Unable to find event.", - "Required": { - "Name": "Name is required.", - "StartAt": "It is necessary to set start time of the event.", - "EndAt": "It is necessary to set end time of the event.", - "Location": "Event location is required." + "PublicApiClients": { + "NotFound": "Requested client wasn't found." }, - "ForbiddenAccess": "This event wasn't created via bot.", - "CannotCancelEventEnded": "Cannot cancel cancelled event." - }, - "CooldownEnabled": "You're executing this command too often. Wait a moment. You can execute this command again after {0}", - "ClickOnCommand": "Before sending slash command you need to click on autocomplete", - "TypeConverters": { - "BooleanInvalidValue": "Provided string is not valid boolean value. Allowed values are only true or false.", - "DateTimeInvalidFormat": "Provided string is not valid datetime.", - "EmoteInvalidFormat": "Provided emoji could not be found.", - "Message": { - "InvalidUri": "Provided message identification is not identifier or link.", - "InvalidDiscordUriFormat": "The link provided does not have a valid Discord message linking format.", - "DmUnsupported": "Using links to private conversation is not supported. If you want use private conversation, call command in the private conversation with message identifier.", - "InvalidGuildIdentifier": "Provided guild identifier has no valid format.", - "GuildNotFound": "The guild with the specified identifier was not found.", - "ChannelIdInvalidFormat": "Provided channel identifier has no valid format.", - "ChannelNotFound": "The channel with the specified identifier was not found.", - "InvalidMessageIdFormat": "Provided message identifier has no valid format.", - "UnknownMessage": "The message with the specified identifier was not found." + "Jobs": { + "NotFound": "Unable to find job {0}." }, - "UserNotFound": "Provided user `{0}` wasn't found." - }, - "PublicApiClients": { - "NotFound": "Requested client wasn't found." - }, - "Jobs": { - "NotFound": "Unable to find job {0}." - }, - "Invite": { - "NotFound": "Unable to find invite with code {0}." - }, - "Emote": { - "List": { - "Title": "Usage statistics of emotes", - "NoStatsOfUser": "No emote usage has been recorded for user `{0}` yet.", - "NoStats": "No emote usage has been recorded.", - "FieldData": "Count of usage: **{0}**\nCount of users: **{1}**\nFirst use: **{2}**\nLast use: **{3}**" + "Invite": { + "NotFound": "Unable to find invite with code {0}." }, - "Info": { - "NotSupported": "Unicode emojis not supported in this command.", - "NoActivity": "No activity has been detected for this emoji yet.", - "Row": "**{0}.** {1} ({2})", - "UnknownUser": "Unknown user", - "Embed": { - "Title": "Emote information", - "Fields": { - "Name": "Name", - "Animated": "Animated", - "FirstOccurence": "First occurence", - "LastOccurence": "Last occurence", - "FromLastUse": "Duration from last use", - "UseCount": "Usage count", - "UsedUsers": "Users count", - "Guild": "Server", - "TopTen": "TOP 10", - "Link": "Link" + "Emote": { + "List": { + "Title": "Usage statistics of emotes", + "NoStatsOfUser": "No emote usage has been recorded for user `{0}` yet.", + "NoStats": "No emote usage has been recorded.", + "FieldData": "Count of usage: **{0}**\nCount of users: **{1}**\nFirst use: **{2}**\nLast use: **{3}**" }, - "Boolean": { - "True": "Yes", - "False": "No" + "Info": { + "NotSupported": "Unicode emojis not supported in this command.", + "NoActivity": "No activity has been detected for this emoji yet.", + "Row": "**{0}.** {1} ({2})", + "UnknownUser": "Unknown user", + "Embed": { + "Title": "Emote information", + "Fields": { + "Name": "Name", + "Animated": "Animated", + "FirstOccurence": "First occurence", + "LastOccurence": "Last occurence", + "FromLastUse": "Duration from last use", + "UseCount": "Usage count", + "UsedUsers": "Users count", + "Guild": "Server", + "TopTen": "TOP 10", + "Link": "Link" + }, + "Boolean": { + "True": "Yes", + "False": "No" + } + } } - } } - } }