diff --git a/README.md b/README.md index 98a9f215..be79dace 100644 --- a/README.md +++ b/README.md @@ -73,8 +73,8 @@ var dto = json.Deserialize(); ### NOTE -- Elastic.Apm.NetCoreAll (v.1.24.x and above) is spam logs. -- DotNetCap.CAP (v.7.x.x and above) is MongoDB error. +- Elastic.Apm... (v.1.24.x and above) is spam logs. +- DotNetCap.CAP... (v.7.x.x and above) is MongoDB error. - Do not [Remove Unused References...] in layers: - Host: - Microsoft.EntityFrameworkCore.Tools diff --git a/host/YANLib.HttpApi.Host/YANLibHttpApiHostModule.cs b/host/YANLib.HttpApi.Host/YANLibHttpApiHostModule.cs index e63125e5..a59af59b 100644 --- a/host/YANLib.HttpApi.Host/YANLibHttpApiHostModule.cs +++ b/host/YANLib.HttpApi.Host/YANLibHttpApiHostModule.cs @@ -31,7 +31,6 @@ using Volo.Abp.Swashbuckle; using YANLib.Core; using YANLib.EntityFrameworkCore; -using YANLib.Middlewares; using YANLib.Utilities; using static Elastic.Apm.Agent; using static HealthChecks.UI.Client.UIResponseWriter; @@ -230,7 +229,10 @@ public override void OnApplicationInitialization(ApplicationInitializationContex _ = app.UseAuthentication(); _ = app.UseAuthorization(); _ = app.UseSwagger(); + +#if RELEASE _ = app.UseMiddleware(); +#endif _ = app.UseAbpSwaggerUI(c => { diff --git a/host/YANLib.HttpApi.Host/appsettings.Development.json b/host/YANLib.HttpApi.Host/appsettings.Development.json index db951d6a..f96af639 100644 --- a/host/YANLib.HttpApi.Host/appsettings.Development.json +++ b/host/YANLib.HttpApi.Host/appsettings.Development.json @@ -65,8 +65,8 @@ "Password": "admin123" }, "RemoteServices": { - "TestApi": { - "BaseUrl": "http://sample-api.local/" + "EcommerceApi": { + "BaseUrl": "https://ecommerce.yamiannephilim.com/api/" } }, "Serilog": { diff --git a/host/YANLib.HttpApi.Host/appsettings.Production.json b/host/YANLib.HttpApi.Host/appsettings.Production.json index c285659c..b13e99d9 100644 --- a/host/YANLib.HttpApi.Host/appsettings.Production.json +++ b/host/YANLib.HttpApi.Host/appsettings.Production.json @@ -65,8 +65,8 @@ "Password": "admin123" }, "RemoteServices": { - "TestApi": { - "BaseUrl": "http://sample-api.local/" + "EcommerceApi": { + "BaseUrl": "https://ecommerce.yamiannephilim.com/api/" } }, "Serilog": { diff --git a/src/YANLib.Application.Contracts/RemoteService/IRemoteService.cs b/src/YANLib.Application.Contracts/RemoteService/IRemoteService.cs new file mode 100644 index 00000000..c00896b7 --- /dev/null +++ b/src/YANLib.Application.Contracts/RemoteService/IRemoteService.cs @@ -0,0 +1,11 @@ +using RestSharp; +using System.Collections.Generic; +using System.Threading.Tasks; +using Volo.Abp.Application.Services; + +namespace YANLib.RemoteService; + +public interface IRemoteService : IApplicationService +{ + public ValueTask InvokeApi(string remoteRoot, string path, Method method, Dictionary headers = null, string jsonInput = null, Dictionary queryParams = null); +} diff --git a/src/YANLib.Application.Contracts/Requests/EcommerceLoginRequest.cs b/src/YANLib.Application.Contracts/Requests/EcommerceLoginRequest.cs new file mode 100644 index 00000000..d271c73d --- /dev/null +++ b/src/YANLib.Application.Contracts/Requests/EcommerceLoginRequest.cs @@ -0,0 +1,12 @@ +using System.ComponentModel; + +namespace YANLib.Requests; + +public sealed class EcommerceLoginRequest +{ + [DefaultValue("nguyenvana@gmail.com")] + public required string Username { get; set; } + + [DefaultValue("nguyenvana")] + public required string Password { get; set; } +} diff --git a/src/YANLib.Application.Contracts/Services/IEcommerceService.cs b/src/YANLib.Application.Contracts/Services/IEcommerceService.cs new file mode 100644 index 00000000..07f6d861 --- /dev/null +++ b/src/YANLib.Application.Contracts/Services/IEcommerceService.cs @@ -0,0 +1,12 @@ +using System.Threading.Tasks; +using Volo.Abp.Application.Services; +using YANLib.Requests; + +namespace YANLib.Services; + +public interface IEcommerceService : IApplicationService +{ + public ValueTask GetAccessToken(EcommerceLoginRequest request); + + public ValueTask GetRefreshToken(string accessToken); +} diff --git a/src/YANLib.Application.Contracts/YANLib.Application.Contracts.csproj b/src/YANLib.Application.Contracts/YANLib.Application.Contracts.csproj index dce2aafb..f5fccfd1 100644 --- a/src/YANLib.Application.Contracts/YANLib.Application.Contracts.csproj +++ b/src/YANLib.Application.Contracts/YANLib.Application.Contracts.csproj @@ -10,6 +10,7 @@ + diff --git a/src/YANLib.Application.Redis/Services/Implements/DeveloperTypeRedisService.cs b/src/YANLib.Application.Redis/Services/Implements/DeveloperTypeRedisService.cs index eac4964d..6b20290b 100644 --- a/src/YANLib.Application.Redis/Services/Implements/DeveloperTypeRedisService.cs +++ b/src/YANLib.Application.Redis/Services/Implements/DeveloperTypeRedisService.cs @@ -1,4 +1,5 @@ -using Microsoft.Extensions.Logging; +using Elastic.Apm.StackExchange.Redis; +using Microsoft.Extensions.Logging; using StackExchange.Redis; using Volo.Abp; using YANLib.Application.Redis.ConnectionFactory; @@ -23,6 +24,7 @@ public DeveloperTypeRedisService(ILogger logger, IRed _logger = logger; _connectionFactory = connectionFactory; _connectionMultiplexer = _connectionFactory.Connection(); + _connectionMultiplexer.UseElasticApm(); _database = _connectionMultiplexer.GetDatabase(); } diff --git a/src/YANLib.Application.Redis/YANLib.Application.Redis.csproj b/src/YANLib.Application.Redis/YANLib.Application.Redis.csproj index bf033c75..0e7d4749 100644 --- a/src/YANLib.Application.Redis/YANLib.Application.Redis.csproj +++ b/src/YANLib.Application.Redis/YANLib.Application.Redis.csproj @@ -8,6 +8,7 @@ + diff --git a/src/YANLib.Application/RemoteService/RemoteService.cs b/src/YANLib.Application/RemoteService/RemoteService.cs new file mode 100644 index 00000000..a48d8505 --- /dev/null +++ b/src/YANLib.Application/RemoteService/RemoteService.cs @@ -0,0 +1,94 @@ +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; +using NUglify.Helpers; +using RestSharp; +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using Volo.Abp.Http; +using Volo.Abp.Http.Client; +using YANLib.Core; +using static Newtonsoft.Json.Linq.JObject; +using static RestSharp.ParameterType; +using static System.Net.HttpStatusCode; +using static YANLib.YANLibDomainErrorCodes; + +namespace YANLib.RemoteService; + +public class RemoteService( + ILogger logger, + IOptionsSnapshot remoteServiceOptions +) : YANLibAppService, IRemoteService +{ + private readonly ILogger _logger = logger; + private readonly AbpRemoteServiceOptions _remoteServiceOptions = remoteServiceOptions.Value; + + public async ValueTask InvokeApi(string remoteRoot, string path, Method method, Dictionary headers = null, string jsonInput = null, Dictionary queryParams = null) + { + try + { + var req = new RestRequest(path, method); + + if (headers.IsNotEmptyAndNull()) + { + headers.ForEach(x => req.AddHeader(x.Key, x.Value)); + } + else + { + _ = req.AddHeader("Accept", "*/*"); + _ = req.AddHeader("Content-Type", "application/json"); + } + + if (jsonInput.IsNotWhiteSpaceAndNull()) + { + _ = req.AddParameter("application/json", jsonInput, RequestBody); + } + + if (queryParams.IsNotEmptyAndNull()) + { + queryParams.ForEach(x => req.AddParameter(x.Key, x.Value, QueryString)); + } + + var res = await new RestClient(_remoteServiceOptions.RemoteServices.GetConfigurationOrDefaultOrNull(remoteRoot)?.BaseUrl)?.ExecuteAsync(req); + + if (res.StatusCode is OK) + { + return res.Content.Deserialize(); + } + else + { + _logger.LogError("Invoke API: {PathRoot} - {Code} - {Error} - {Content}", $"{remoteRoot}{path}", res.StatusCode, res.ErrorMessage, res.Content); + + if (res.Content.IsWhiteSpaceOrNull()) + { + throw new AbpRemoteCallException(new RemoteServiceErrorInfo + { + Code = NOT_FOUND, + Message = res.ErrorException.Message + }) + { + HttpStatusCode = res.StatusCode.ToInt() + }; + } + else + { + var jtoken = Parse(res.Content)["error"]?.ToString(); + + throw new AbpRemoteCallException(jtoken?.Deserialize() ?? new RemoteServiceErrorInfo + { + Message = jtoken + }) + { + HttpStatusCode = res.StatusCode.ToInt() + }; + } + } + } + catch (Exception ex) + { + _logger.LogError(ex, "InvokeApiRemoteService-Exception: {PathRoot} - {Method} - {JsonInput} - {QueryParams}", $"{remoteRoot}{path}", method.ToString(), jsonInput, queryParams.Serialize()); + + throw; + } + } +} diff --git a/src/YANLib.Application/Services/EcommerceService.cs b/src/YANLib.Application/Services/EcommerceService.cs new file mode 100644 index 00000000..a050ffc7 --- /dev/null +++ b/src/YANLib.Application/Services/EcommerceService.cs @@ -0,0 +1,59 @@ +using Microsoft.Extensions.Logging; +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using YANLib.Core; +using YANLib.RemoteService; +using YANLib.Requests; +using static RestSharp.Method; +using static YANLib.YANLibConsts.RemoteService; +using static YANLib.YANLibConsts.RemoteService.Path; + +namespace YANLib.Services; + +public class EcommerceService( + ILogger logger, + IRemoteService remoteService +) : YANLibAppService, IEcommerceService +{ + private readonly ILogger _logger = logger; + private readonly IRemoteService _remoteService = remoteService; + + public async ValueTask GetAccessToken(EcommerceLoginRequest request) + { + var json = request.Serialize(); + + try + { + var hdrs = new Dictionary + { + { "Accept", "*/*" }, + { "Content-Type", "application/x-www-form-urlencoded" } + }; + return await _remoteService.InvokeApi(EcommerceApi, Login, Post, jsonInput: json); + } + catch (Exception ex) + { + _logger.LogError(ex, "GetAccessTokenEcommerceService-Exception: {Request}", json); + + throw; + } + } + + public async ValueTask GetRefreshToken(string accessToken) + { + try + { + return await _remoteService.InvokeApi(EcommerceApi, Refresh, Get, new Dictionary + { + { "Authorization", $"Bearer {accessToken}" } + }); + } + catch (Exception ex) + { + _logger.LogError(ex, "GetRefreshTokenEcommerceService-Exception: {AccessToken}", accessToken); + + throw; + } + } +} diff --git a/src/YANLib.Application/Validations/EcommerceValidation.cs b/src/YANLib.Application/Validations/EcommerceValidation.cs new file mode 100644 index 00000000..3a5e1db8 --- /dev/null +++ b/src/YANLib.Application/Validations/EcommerceValidation.cs @@ -0,0 +1,35 @@ +using FluentValidation; +using System.Collections.Generic; +using System.Linq; +using YANLib.Core; +using YANLib.Requests; +using static YANLib.YANLibDomainErrorCodes; + +namespace YANLib.Validations; + +public sealed class EcommerceValidator : AbstractValidator +{ + public EcommerceValidator() + { + _ = RuleFor(x => x.Username).NotNull().NotEmpty().WithErrorCode(BAD_REQUEST_USER_NAME).WithMessage(YANLibDomainErrorMessages.BAD_REQUEST_USER_NAME); + _ = RuleFor(x => x.Password).NotNull().NotEmpty().WithErrorCode(BAD_REQUEST_PWD).WithMessage(YANLibDomainErrorMessages.BAD_REQUEST_PWD); + } +} + +public sealed class EcommerceValidators : AbstractValidator> +{ + public EcommerceValidators() + { + _ = RuleFor(x => x).NotNull().NotEmpty().WithErrorCode(BAD_REQUEST).WithMessage(YANLibDomainErrorMessages.BAD_REQUEST); + _ = RuleForEach(s => s).SetValidator(new EcommerceValidator()); + _ = RuleFor(x => x).Must(IsNotEmptyAndNull).WithErrorCode(BAD_REQUEST).WithMessage(YANLibDomainErrorMessages.BAD_REQUEST); + _ = RuleFor(x => x).Must(UsernameIsNotWhiteSpace).WithErrorCode(BAD_REQUEST_USER_NAME).WithMessage(YANLibDomainErrorMessages.BAD_REQUEST_USER_NAME); + _ = RuleFor(x => x).Must(PasswordIsNotWhiteSpace).WithErrorCode(BAD_REQUEST_PWD).WithMessage(YANLibDomainErrorMessages.BAD_REQUEST_PWD); + } + + private bool IsNotEmptyAndNull(List requests) => requests.IsNotEmptyAndNull(); + + private bool UsernameIsNotWhiteSpace(List requests) => requests.Select(x => x.Username).AllNotWhiteSpaceAndNull(); + + private bool PasswordIsNotWhiteSpace(List requests) => requests.Select(x => x.Password).AllNotWhiteSpaceAndNull(); +} diff --git a/src/YANLib.Application/YANLib.Application.csproj b/src/YANLib.Application/YANLib.Application.csproj index 676d40e7..d3afd095 100644 --- a/src/YANLib.Application/YANLib.Application.csproj +++ b/src/YANLib.Application/YANLib.Application.csproj @@ -22,6 +22,7 @@ + diff --git a/src/YANLib.Domain.Shared/Localization/YANLib/vi.json b/src/YANLib.Domain.Shared/Localization/YANLib/vi.json index 9cad14e5..c33d9e4c 100644 --- a/src/YANLib.Domain.Shared/Localization/YANLib/vi.json +++ b/src/YANLib.Domain.Shared/Localization/YANLib/vi.json @@ -7,6 +7,8 @@ "YANLib:430": "Mã truyền vào không hợp lệ!", "YANLib:440": "Điểm truyền vào không hợp lệ!", "YANLib:450": "Mã hồ sơ lập trình viên truyền vào không hợp lệ!", + "YANLib:460": "Tên người dùng truyền vào không hợp lệ!", + "YANLib:470": "Mật khẩu truyền vào không hợp lệ!", "YANLib:403": "Lỗi quy tắc!", "YANLib:413": "Mã {Id} đã tồn tại!", "YANLib:423": "Mã định danh {IdCard} đã tồn tại!", diff --git a/src/YANLib.Domain.Shared/YANLibDomainErrorCodes.cs b/src/YANLib.Domain.Shared/YANLibDomainErrorCodes.cs index fc975706..6f3bace6 100644 --- a/src/YANLib.Domain.Shared/YANLibDomainErrorCodes.cs +++ b/src/YANLib.Domain.Shared/YANLibDomainErrorCodes.cs @@ -8,6 +8,8 @@ public static class YANLibDomainErrorCodes public const string BAD_REQUEST_ID = "YANLib:430"; public const string BAD_REQUEST_GPA = "YANLib:440"; public const string BAD_REQUEST_DEV_ID = "YANLib:450"; + public const string BAD_REQUEST_USER_NAME = "YANLib:460"; + public const string BAD_REQUEST_PWD = "YANLib:470"; public const string BUSINESS_ERROR = "YANLib:403"; public const string EXIST_ID = "YANLib:413"; diff --git a/src/YANLib.Domain.Shared/YANLibDomainErrorMessages.cs b/src/YANLib.Domain.Shared/YANLibDomainErrorMessages.cs index f9459b9c..2a9a23f4 100644 --- a/src/YANLib.Domain.Shared/YANLibDomainErrorMessages.cs +++ b/src/YANLib.Domain.Shared/YANLibDomainErrorMessages.cs @@ -8,6 +8,8 @@ public static class YANLibDomainErrorMessages public const string BAD_REQUEST_ID = "Mã truyền vào không hợp lệ!"; public const string BAD_REQUEST_GPA = "Điểm truyền vào không hợp lệ!"; public const string BAD_REQUEST_DEV_ID = "Mã hồ sơ lập trình viên truyền vào không hợp lệ!"; + public const string BAD_REQUEST_USER_NAME = "Tên người dùng truyền vào không hợp lệ!"; + public const string BAD_REQUEST_PWD = "Mật khẩu truyền vào không hợp lệ!"; public const string BUSINESS_ERROR = "Lỗi quy tắc!"; public const string EXIST_ID = "Mã đã tồn tại!"; diff --git a/src/YANLib.Domain/YANLibConsts.cs b/src/YANLib.Domain/YANLibConsts.cs index ac28cbf4..35c2fab4 100644 --- a/src/YANLib.Domain/YANLibConsts.cs +++ b/src/YANLib.Domain/YANLibConsts.cs @@ -42,4 +42,15 @@ public readonly struct RedisConstant public static readonly string DeveloperTypeGroup = $"{YanlibPrefix}:{SamplePrefix}:{DeveloperTypePrefix}"; } + + public readonly struct RemoteService + { + public const string EcommerceApi = "EcommerceApi"; + + public readonly struct Path + { + public const string Login = "login"; + public const string Refresh = "refresh"; + } + } } diff --git a/src/YANLib.HttpApi/Controllers/DeveloperController.cs b/src/YANLib.HttpApi/Controllers/DeveloperController.cs index aaa1c57e..1c848075 100644 --- a/src/YANLib.HttpApi/Controllers/DeveloperController.cs +++ b/src/YANLib.HttpApi/Controllers/DeveloperController.cs @@ -12,7 +12,7 @@ namespace YANLib.Controllers; [RemoteService] [ApiExplorerSettings(GroupName = "sample")] -[Route("api/yanlib/developers")] +[Route("api/developers")] public sealed class DeveloperController(ILogger logger, IDeveloperService service) : YANLibController { private readonly ILogger _logger = logger; diff --git a/src/YANLib.HttpApi/Controllers/DeveloperTypeController.cs b/src/YANLib.HttpApi/Controllers/DeveloperTypeController.cs index 5d541123..4e56069d 100644 --- a/src/YANLib.HttpApi/Controllers/DeveloperTypeController.cs +++ b/src/YANLib.HttpApi/Controllers/DeveloperTypeController.cs @@ -12,7 +12,7 @@ namespace YANLib.Controllers; [RemoteService] [ApiExplorerSettings(GroupName = "sample")] -[Route("api/yanlib/developer-types")] +[Route("api/developer-types")] public sealed class DeveloperTypeController(ILogger logger, IDeveloperTypeService service) : YANLibController { private readonly ILogger _logger = logger; @@ -49,7 +49,7 @@ public async ValueTask Update(int code, [Required] DeveloperTypeU return Ok(await _service.Update(code, request)); } - [SwaggerOperation(Summary = "Đồng bộ tất cả định nghĩa Developer Types từ Database sang Redis")] [HttpPost("sync-db-to-redis")] + [SwaggerOperation(Summary = "Đồng bộ tất cả định nghĩa Developer Types từ Database sang Redis")] public async ValueTask SyncDbToRedis() => Ok(await _service.SyncDbToRedis()); } diff --git a/src/YANLib.HttpApi/Controllers/EcommerceController.cs b/src/YANLib.HttpApi/Controllers/EcommerceController.cs new file mode 100644 index 00000000..44d01958 --- /dev/null +++ b/src/YANLib.HttpApi/Controllers/EcommerceController.cs @@ -0,0 +1,38 @@ +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Logging; +using Swashbuckle.AspNetCore.Annotations; +using System.ComponentModel.DataAnnotations; +using System.Threading.Tasks; +using Volo.Abp; +using YANLib.Core; +using YANLib.Requests; +using YANLib.Services; + +namespace YANLib.Controllers; + +[RemoteService] +[ApiExplorerSettings(GroupName = "sample")] +[Route("api/remote/ecommerce")] +public sealed class EcommerceController(ILogger logger, IEcommerceService service) : YANLibController +{ + private readonly ILogger _logger = logger; + private readonly IEcommerceService _service = service; + + [HttpPost("access-token")] + [SwaggerOperation(Summary = "Lấy access token của API login của trang Ecommerce")] + public async ValueTask GetAccessToken([Required] EcommerceLoginRequest request) + { + _logger.LogInformation("GetAccessTokenEcommerceController: {Request}", request.Serialize()); + + return Ok(await _service.GetAccessToken(request)); + } + + [HttpGet("refresh-token")] + [SwaggerOperation(Summary = "Lấy refresh token của API refresh của trang Ecommerce")] + public async ValueTask GetRefreshToken([Required] string accessToken) + { + _logger.LogInformation("GetRefreshTokenEcommerceController: {AccessToken}", accessToken); + + return Ok(await _service.GetRefreshToken(accessToken)); + } +} diff --git a/src/YANLib.HttpApi/Controllers/ElasticsearchController.cs b/src/YANLib.HttpApi/Controllers/ElasticsearchController.cs index 9c102ccb..46df6f94 100644 --- a/src/YANLib.HttpApi/Controllers/ElasticsearchController.cs +++ b/src/YANLib.HttpApi/Controllers/ElasticsearchController.cs @@ -12,7 +12,7 @@ namespace YANLib.Controllers; [RemoteService] [ApiExplorerSettings(GroupName = "sample")] -[Route("api/yanlib/es")] +[Route("api/es")] public sealed class ElasticsearchController(ILogger logger, IDeveloperEsService developerEsService) : YANLibController { private readonly ILogger _logger = logger; diff --git a/src/YANLib.HttpApi/Controllers/IdSnowflakeController.cs b/src/YANLib.HttpApi/Controllers/IdSnowflakeController.cs index 27930c4a..92635ce0 100644 --- a/src/YANLib.HttpApi/Controllers/IdSnowflakeController.cs +++ b/src/YANLib.HttpApi/Controllers/IdSnowflakeController.cs @@ -9,7 +9,7 @@ namespace YANLib.Controllers; [RemoteService] [ApiExplorerSettings(GroupName = "test")] -[Route("api/yanlib/id-snowflakes")] +[Route("api/id-snowflakes")] public sealed class IdSnowflakeController(ILogger logger) : YANLibController { private readonly ILogger _logger = logger; diff --git a/src/YANLib.HttpApi/Controllers/YANJsonController.cs b/src/YANLib.HttpApi/Controllers/YANJsonController.cs index 55703b17..22df9886 100644 --- a/src/YANLib.HttpApi/Controllers/YANJsonController.cs +++ b/src/YANLib.HttpApi/Controllers/YANJsonController.cs @@ -11,7 +11,7 @@ namespace YANLib.Controllers; [RemoteService] [ApiExplorerSettings(GroupName = "test")] -[Route("api/yanlib/json")] +[Route("api/json")] public sealed class YANJsonController(ILogger logger, IYANJsonService service) : YANLibController { private readonly ILogger _logger = logger;