Skip to content

Commit

Permalink
Improvements on application architecture and code design (#30)
Browse files Browse the repository at this point in the history
  • Loading branch information
vanderlan authored Dec 29, 2023
1 parent 5ea0911 commit 2acdbaa
Show file tree
Hide file tree
Showing 37 changed files with 149 additions and 295 deletions.
11 changes: 11 additions & 0 deletions docker-compose.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,17 @@ services:
container_name: sqlserver
networks:
- backend-network

mssqltools:
image: mcr.microsoft.com/mssql-tools
depends_on:
- sqlserver
volumes:
- ./scripts/initial-script.sql:/tmp/initial-script.sql
- ./scripts/init-sqlserver.sh:/tmp/init-sqlserver.sh
command: /bin/bash ./tmp/init-sqlserver.sh
networks:
- backend-network

elasticsearch:
container_name: elasticsearch
Expand Down
1 change: 1 addition & 0 deletions scripts/init-sqlserver.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
/opt/mssql-tools/bin/sqlcmd -S sqlserver -U sa -P SqlServer2019! -d master -i /tmp/initial-script.sql
2 changes: 2 additions & 0 deletions scripts/initial-script.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
--script used to create the database used in integration tests, if the database name changes, it is necessary to update appsettings.Test.json
CREATE DATABASE OrionTests;
34 changes: 19 additions & 15 deletions src/Orion.Api/Configuration/HealthCheckConfiguration.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,25 @@ namespace Orion.Api.Configuration;

public static class HealthCheckConfiguration
{
private static readonly string[] DatabaseTags =
[
"database"
];
private static readonly string[] ElasticSearchTags =
[
"elasticsearch",
"kibana"
];

public static void AddApplicationHealthChecks(this IServiceCollection services, IConfiguration configuration)
{
services.AddHealthChecks().AddSqlServer(configuration["ConnectionStrings:OrionDatabase"],
tags: DatabaseTags);

services.AddHealthChecks().AddElasticsearch(configuration["ElasticConfiguration:Uri"],
tags: ElasticSearchTags);
}

public static void ConfigureHealthCheck(this IApplicationBuilder app)
{
app.UseHealthChecks("/health", new HealthCheckOptions
Expand All @@ -13,19 +32,4 @@ public static void ConfigureHealthCheck(this IApplicationBuilder app)
});
}

public static void AddApplicationHealthChecks(this IServiceCollection services, IConfiguration configuration)
{
services.AddHealthChecks().AddSqlServer(configuration["ConnectionStrings:OrionDatabase"],
tags: new[]
{
"database"
});

services.AddHealthChecks().AddElasticsearch(configuration["ElasticConfiguration:Uri"],
tags: new[]
{
"elasticsearch",
"kibana"
});
}
}
8 changes: 2 additions & 6 deletions src/Orion.Api/Controllers/Base/ApiController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,9 @@
namespace Orion.Api.Controllers.Base;

[ApiController]
public abstract class ApiController : ControllerBase
public abstract class ApiController(IMediator mediator) : ControllerBase
{
protected readonly IMediator Mediator;
protected ApiController(IMediator mediator)
{
Mediator = mediator;
}
protected readonly IMediator Mediator = mediator;

protected CreatedResult Created(object entity)
{
Expand Down
11 changes: 2 additions & 9 deletions src/Orion.Api/Controllers/V1/AuthController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,15 +16,8 @@ namespace Orion.Api.Controllers.V1;
[ApiVersion(1.0)]
[Route("api/[controller]")]
[AllowAnonymous]
public class AuthController : ApiController
public class AuthController(IMediator mediator, IConfiguration configuration) : ApiController(mediator)
{
private readonly IConfiguration _configuration;

public AuthController(IMediator mediator, IConfiguration configuration) : base(mediator)
{
_configuration = configuration;
}

[HttpPost("Login")]
[SwaggerResponse((int)HttpStatusCode.OK,"A success reponse with a Token, Refresh Token and expiration date" ,typeof(LoginWithCredentialsResponse))]
[SwaggerResponse((int)HttpStatusCode.BadRequest,"A error response with the error description", typeof(ExceptionResponse))]
Expand All @@ -51,7 +44,7 @@ private IActionResult AuthorizeUser(LoginWithCredentialsResponse loginWithCreden
{
if (loginWithCredentialsResponse != null)
{
var (token, validTo) = AuthenticationConfiguration.CreateToken(loginWithCredentialsResponse, _configuration);
var (token, validTo) = AuthenticationConfiguration.CreateToken(loginWithCredentialsResponse, configuration);

return Ok(
new UserApiTokenModel
Expand Down
7 changes: 1 addition & 6 deletions src/Orion.Api/Controllers/V1/UsersController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,8 @@ namespace Orion.Api.Controllers.V1;
[ApiVersion(1.0)]
[Route("api/[controller]")]
[AuthorizeFor(Roles.Admin)]
public class UsersController : ApiController
public class UsersController(IMediator mediator) : ApiController(mediator)
{
public UsersController(IMediator mediator) : base(mediator)
{

}

[HttpGet]
[SwaggerResponse((int)HttpStatusCode.OK,"A success response with a list of Users paginated", typeof(PagedList<UserGetPaginatedResponse>))]
[SwaggerResponse((int)HttpStatusCode.Unauthorized)]
Expand Down
21 changes: 5 additions & 16 deletions src/Orion.Api/Middleware/OrionMiddleware.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,26 +4,15 @@

namespace Orion.Api.Middleware;

public class OrionMiddleware
public class OrionMiddleware(RequestDelegate next, ILogger<OrionMiddleware> logger, IHostEnvironment env)
{
private readonly RequestDelegate _next;
private readonly ILogger<OrionMiddleware> _logger;
private readonly IHostEnvironment _env;

public OrionMiddleware(RequestDelegate next, ILogger<OrionMiddleware> logger, IHostEnvironment env)
{
_env = env;
_next = next;
_logger = logger;
}

public async Task Invoke(HttpContext context)
{
try
{
if (_next != null)
if (next != null)
{
await _next(context);
await next(context);
}
}
catch (Exception ex)
Expand All @@ -38,7 +27,7 @@ private async Task HandleExceptionAsync(HttpContext context, Exception exception

var errorResponse = new ExceptionResponse(exception.Message, NotificationType.Error);

if (exception is not BusinessException && _env.IsDevelopment())
if (exception is not BusinessException && env.IsDevelopment())
{
errorResponse = new ExceptionResponse(exception.Message, NotificationType.Error);
}
Expand Down Expand Up @@ -73,7 +62,7 @@ private async Task ProccessResponseAsync(HttpContext context, HttpStatusCode sta

if (statusCode == HttpStatusCode.InternalServerError)
foreach (var error in errorResponse.Errors)
_logger.LogError(exception, "Internal Server Error: {message}", error);
logger.LogError(exception, "Internal Server Error: {message}", error);

context.Response.StatusCode = (int)statusCode;
context.Response.ContentType = "application/json";
Expand Down
8 changes: 4 additions & 4 deletions src/Orion.Api/Orion.Api.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,10 @@
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Asp.Versioning.Mvc" Version="7.1.0" />
<PackageReference Include="AspNetCore.HealthChecks.Elasticsearch" Version="7.0.0" />
<PackageReference Include="AspNetCore.HealthChecks.SqlServer" Version="7.0.0" />
<PackageReference Include="AspNetCore.HealthChecks.UI.Client" Version="7.1.0" />
<PackageReference Include="Asp.Versioning.Mvc" Version="8.0.0" />
<PackageReference Include="AspNetCore.HealthChecks.Elasticsearch" Version="8.0.0" />
<PackageReference Include="AspNetCore.HealthChecks.SqlServer" Version="8.0.0" />
<PackageReference Include="AspNetCore.HealthChecks.UI.Client" Version="8.0.0" />
<PackageReference Include="FluentValidation.AspNetCore" Version="11.3.0" />
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="8.0.0" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="8.0.0">
Expand Down
2 changes: 1 addition & 1 deletion src/Orion.Api/appsettings.Development.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
}
},
"ConnectionStrings": {
"OrionDatabase": "Data Source=localhost,1433;Initial Catalog=Orion;User ID=Orion;Password=123;TrustServerCertificate=True"
"OrionDatabase": "Data Source=localhost,1434;Initial Catalog=OrionDev;User ID=sa;Password=SqlServer2019!;TrustServerCertificate=True"
},
"JwtOptions": {
"SymmetricSecurityKey": "5cCI6IkpXVCJ9.eyJlbWFpbCI6InZhbmRlcmxhbi5nc0BnbWFpbC5jb20iLCJodHRwOi8vc2NoZW1hcy5taWNyb3NvZnQuY29tL3dzLzIwMDgvMDYvaWRlbnRpdHkvY2xhaW1zL3JvbGUiOiJhZG1p",
Expand Down
2 changes: 1 addition & 1 deletion src/Orion.Api/appsettings.Production.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
}
},
"ConnectionStrings": {
"OrionDatabase": "Data Source=10.0.0.90,1433;Initial Catalog=Orion;User ID=Orion;Password=123;TrustServerCertificate=True"
"OrionDatabase": "Data Source=sqlserver;Initial Catalog=OrionDev;User ID=sa;Password=SqlServer2019!;TrustServerCertificate=True"
},
"JwtOptions": {
"SymmetricSecurityKey": "5cCI6IkpXVCJ9.eyJlbWFpbCI6InZhbmRlcmxhbi5nc0BnbWFpbC5jb20iLCJodHRwOi8vc2NoZW1hcy5taWNyb3NvZnQuY29tL3dzLzIwMDgvMDYvaWRlbnRpdHkvY2xhaW1zL3JvbGUiOiJhZG1p",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,11 @@

namespace Orion.Application.Core.Commands.LoginWithCredentials;

public class LoginWithCredentialsRequestHandler : IRequestHandler<LoginWithCredentialsRequest, LoginWithCredentialsResponse>
public class LoginWithCredentialsRequestHandler(IUserService userService) : IRequestHandler<LoginWithCredentialsRequest, LoginWithCredentialsResponse>
{
private readonly IUserService _userService;

public LoginWithCredentialsRequestHandler(IUserService userService)
{
_userService = userService;
}

public async Task<LoginWithCredentialsResponse> Handle(LoginWithCredentialsRequest request, CancellationToken cancellationToken)
{
var (user, refreshToken) = await _userService.SignInWithCredentialsAsync(request.Email, request.Password);
var (user, refreshToken) = await userService.SignInWithCredentialsAsync(request.Email, request.Password);

return new LoginWithCredentialsResponse
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,12 @@

namespace Orion.Application.Core.Commands.LoginWithRefreshToken;

public class LoginWithRefreshTokenRequestHandler : IRequestHandler<LoginWithRefreshTokenRequest, LoginWithRefreshTokenResponse>
public class LoginWithRefreshTokenRequestHandler(IUserService userService)
: IRequestHandler<LoginWithRefreshTokenRequest, LoginWithRefreshTokenResponse>
{
private readonly IUserService _userService;

public LoginWithRefreshTokenRequestHandler(IUserService userService)
{
_userService = userService;
}

public async Task<LoginWithRefreshTokenResponse> Handle(LoginWithRefreshTokenRequest request, CancellationToken cancellationToken)
{
var (user, refreshToken) = await _userService.SignInWithRefreshTokenAsync(request.RefreshToken, request.Token);
var (user, refreshToken) = await userService.SignInWithRefreshTokenAsync(request.RefreshToken, request.Token);

return new LoginWithRefreshTokenResponse
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,24 +5,17 @@

namespace Orion.Application.Core.Commands.UserChangePassword
{
public class UserUpdatePasswordRequestHandler : IRequestHandler<UserChangePasswordRequest, Unit>
public class UserUpdatePasswordRequestHandler(
IUserService userService,
ICurrentUser currentUser,
IMediator mediator)
: IRequestHandler<UserChangePasswordRequest, Unit>
{
private readonly IUserService _userService;
private readonly ICurrentUser _currentUser;
private readonly IMediator _mediator;

public UserUpdatePasswordRequestHandler(IUserService userService, ICurrentUser currentUser, IMediator mediator)
{
_userService = userService;
_currentUser = currentUser;
_mediator = mediator;
}

public async Task<Unit> Handle(UserChangePasswordRequest request, CancellationToken cancellationToken)
{
await _userService.ChangePasswordAsync(_currentUser.Id, request.CurrentPassword, request.NewPassword);
await userService.ChangePasswordAsync(currentUser.Id, request.CurrentPassword, request.NewPassword);

await _mediator.Publish(new UserPasswordChangedNotification(), cancellationToken);
await mediator.Publish(new UserPasswordChangedNotification(), cancellationToken);

return Unit.Value;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,22 +4,14 @@

namespace Orion.Application.Core.Commands.UserCreate;

public class UserCreateRequestHandler : IRequestHandler<UserCreateRequest, UserCreateResponse>
public class UserCreateRequestHandler(IUserService userService, IMediator mediator)
: IRequestHandler<UserCreateRequest, UserCreateResponse>
{
private readonly IUserService _userService;
private readonly IMediator _mediator;

public UserCreateRequestHandler(IUserService userService, IMediator mediator)
{
_userService = userService;
_mediator = mediator;
}

public async Task<UserCreateResponse> Handle(UserCreateRequest request, CancellationToken cancellationToken)
{
var userCreated = await _userService.AddAsync(request);
var userCreated = await userService.AddAsync(request);

await _mediator.Publish(new UserCreatedNotification(userCreated), cancellationToken);
await mediator.Publish(new UserCreatedNotification(userCreated), cancellationToken);

return (UserCreateResponse)userCreated;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,8 @@

namespace Orion.Application.Core.Commands.UserDelete;

public class UserDeleteRequest : IRequest <Unit>
public class UserDeleteRequest(string publicId) : IRequest<Unit>
{
public UserDeleteRequest(string publicId)
{
PublicId = publicId;
}

public string PublicId { get; private set; }
public string PublicId { get; private set; } = publicId;
}

Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,11 @@

namespace Orion.Application.Core.Commands.UserDelete;

public class UserDeleteRequestHandler : IRequestHandler<UserDeleteRequest, Unit>
public class UserDeleteRequestHandler(IUserService userService) : IRequestHandler<UserDeleteRequest, Unit>
{
private readonly IUserService _userService;

public UserDeleteRequestHandler(IUserService userService)
{
_userService = userService;
}

public async Task<Unit> Handle(UserDeleteRequest request, CancellationToken cancellationToken)
{
await _userService.DeleteAsync(request.PublicId);
await userService.DeleteAsync(request.PublicId);

return Unit.Value;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,11 @@

namespace Orion.Application.Core.Commands.UserUpdate;

public class UserUpdateRequestHandler : IRequestHandler<UserUpdateRequest, Unit>
public class UserUpdateRequestHandler(IUserService userService) : IRequestHandler<UserUpdateRequest, Unit>
{
private readonly IUserService _userService;

public UserUpdateRequestHandler(IUserService userService)
{
_userService = userService;
}

public async Task<Unit> Handle(UserUpdateRequest request, CancellationToken cancellationToken)
{
await _userService.UpdateAsync(request);
await userService.UpdateAsync(request);

return Unit.Value;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,8 @@

namespace Orion.Application.Core.Notifications.UserCreated
{
public class UserCreatedNotification : INotification
public class UserCreatedNotification(User user) : INotification
{
public UserCreatedNotification(User user)
{
Entity = user;
}

public User Entity { get; set; }
public User Entity { get; set; } = user;
}
}
Loading

0 comments on commit 2acdbaa

Please sign in to comment.