Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/bertus menu register screen #5

Merged
merged 3 commits into from
Dec 8, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 0 additions & 25 deletions .dockerignore

This file was deleted.

6 changes: 3 additions & 3 deletions .editorconfig
Original file line number Diff line number Diff line change
Expand Up @@ -86,9 +86,9 @@ dotnet_code_quality_unused_parameters = all:error
csharp_style_namespace_declarations= file_scoped:error

# var preferences
csharp_style_var_elsewhere = false:error
csharp_style_var_for_built_in_types = false:error
csharp_style_var_when_type_is_apparent = true:error
csharp_style_var_elsewhere = false:silent
csharp_style_var_for_built_in_types = false:silent
csharp_style_var_when_type_is_apparent = true:silent

# Expression-bodied members
csharp_style_expression_bodied_accessors = true:error
Expand Down
13 changes: 3 additions & 10 deletions Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -7,38 +7,33 @@
<AnalysisMode>Default</AnalysisMode>
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
</PropertyGroup>

<ItemGroup>
<!-- Analysis -->
<PackageVersion Include="Microsoft.Extensions.DependencyInjection" Version="9.0.0" />
<PackageVersion Include="SonarAnalyzer.CSharp" Version="9.19.0.84025" />
<PackageVersion Include="FluentAssertions" Version="6.12.1" />
<PackageVersion Include="NetArchTest.Rules" Version="1.3.2" />

<!-- API and Documentation -->
<PackageVersion Include="Microsoft.AspNetCore.OpenApi" Version="8.0.10" />
<PackageVersion Include="Spectre.Console" Version="0.49.1" />
<PackageVersion Include="Swashbuckle.AspNetCore" Version="6.9.0" />

<!-- Authentication -->
<PackageVersion Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="8.0.10" />

<!-- Database and ORM -->
<PackageVersion Include="Microsoft.EntityFrameworkCore" Version="8.0.10" />
<PackageVersion Include="Microsoft.EntityFrameworkCore.InMemory" Version="8.0.3" />
<PackageVersion Include="Microsoft.EntityFrameworkCore.Sqlite" Version="8.0.11" />
<PackageVersion Include="Microsoft.EntityFrameworkCore.Tools" Version="8.0.10" />
<PackageVersion Include="EFCore.NamingConventions" Version="8.0.3" />

<!-- Health Checks -->
<PackageVersion Include="AspNetCore.HealthChecks.UI.Client" Version="8.0.1" />
<PackageVersion Include="AspNetCore.HealthChecks.Sqlite" Version="8.0.2" />
<PackageVersion Include="Microsoft.Extensions.Diagnostics.HealthChecks" Version="8.0.10" />

<!-- Logging -->
<PackageVersion Include="Serilog" Version="4.1.0" />
<PackageVersion Include="Serilog.AspNetCore" Version="8.0.3" />
<PackageVersion Include="Serilog.Sinks.Seq" Version="8.0.0" />
<PackageVersion Include="Microsoft.Extensions.Logging.Abstractions" Version="8.0.2" />

<!-- Testing -->
<PackageVersion Include="coverlet.collector" Version="6.0.2" />
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.11.1" />
Expand All @@ -48,14 +43,12 @@
<PackageVersion Include="Faker.Net.8.0" Version="1.0.0" />
<PackageVersion Include="Microsoft.Extensions.Hosting" Version="8.0.1" />
<PackageVersion Include="Microsoft.Extensions.Hosting.Abstractions" Version="8.0.1" />

<!-- Validation and Mediation -->
<PackageVersion Include="FluentValidation.DependencyInjectionExtensions" Version="11.10.0" />
<PackageVersion Include="MediatR" Version="12.4.1" />
<PackageVersion Include="MediatR.Contracts" Version="2.0.1" />

<!-- Utilities -->
<PackageVersion Include="Newtonsoft.Json" Version="13.0.3" />
<PackageVersion Include="Microsoft.VisualStudio.Azure.Containers.Tools.Targets" Version="1.21.0" />
</ItemGroup>
</Project>
</Project>
1 change: 0 additions & 1 deletion Test.txt

This file was deleted.

22 changes: 22 additions & 0 deletions src/Application/Abstractions/Screen/IScreen.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
using SharedKernel;

namespace Application.Abstractions.Screen;

/// <summary>
/// Represents a screen in the application.
/// </summary>
/// <typeparam name="TResult">The type of result this screen produces.</typeparam>
public interface IScreen<TResult>
{
/// <summary>
/// Gets the supported result type for this screen.
/// </summary>
Type ResultType => typeof(TResult);

/// <summary>
/// Shows the screen and handles user interaction.
/// </summary>
/// <param name="token">The cancellation token.</param>
/// <returns>A result containing the screen's output.</returns>
Task<Result<TResult>> ShowAsync(CancellationToken token);
}
12 changes: 12 additions & 0 deletions src/Application/Screens/MenuSelection.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
namespace Application.Screens;

/// <summary> Menu Selections </summary>
public enum MenuSelection
{
/// <summary> Login </summary>
Login,
/// <summary> Register </summary>
Register,
/// <summary> Exit </summary>
Exit
}
14 changes: 9 additions & 5 deletions src/Application/Users/Register/RegisterUserCommandValidator.cs
Original file line number Diff line number Diff line change
@@ -1,14 +1,18 @@
using FluentValidation;
using Domain.Users;
using FluentValidation;

namespace Application.Users.Register;

internal sealed class RegisterUserCommandValidator : AbstractValidator<RegisterUserCommand>
{
public RegisterUserCommandValidator()
{
RuleFor(c => c.FirstName).NotEmpty();
RuleFor(c => c.LastName).NotEmpty();
RuleFor(c => c.Email).NotEmpty().EmailAddress();
RuleFor(c => c.Password).NotEmpty().MinimumLength(8);
RuleFor(c => c.FirstName).NotEmpty().WithMessage("First name is required.");
RuleFor(c => c.LastName).NotEmpty().WithMessage("Last name is required.");
RuleFor(c => c.Email)
.NotEmpty().WithMessage("Email address is required.");
RuleFor(c => c.Password)
.NotEmpty().WithMessage("Password is required.")
.MinimumLength(8).WithMessage("Password must be at least 8 characters long.");
}
}
56 changes: 56 additions & 0 deletions src/Presentation/App.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
using Application.Screens;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Presentation.Screens;
using Spectre.Console;

namespace Presentation;

// A hosted service that can be run by the Host
// This could be replaced by more complex logic such as background tasks,
// scheduled jobs, or other application logic
public class App(IServiceProvider serviceProvider) : IHostedService
{
// This method is called when the host starts
public async Task StartAsync(CancellationToken cancellationToken)
{
// Display a fancy header
DisplayHeader();

var menuScreen = serviceProvider.GetRequiredService<MenuScreen>();
var menuSelection = await menuScreen.ShowAsync(cancellationToken);
while (menuSelection.Value != MenuSelection.Exit)
{
switch (menuSelection.Value)
{
case MenuSelection.Register:
var registerScreen = serviceProvider.GetRequiredService<RegisterScreen>();
await registerScreen.ShowAsync(cancellationToken);
break;
case MenuSelection.Login:
break;
}

menuSelection = await menuScreen.ShowAsync(cancellationToken);
AnsiConsole.Clear();
DisplayHeader();
}
await StopAsync(cancellationToken);
}

private static void DisplayHeader()
{
AnsiConsole.Write(
new FigletText(" Welcome to Elevator Simulator")
.LeftJustified()
.Color(Color.Blue)
);
}

// This method is called when the host is shutting down
public Task StopAsync(CancellationToken cancellationToken)
{
AnsiConsole.MarkupLine("[grey]Shutting down...[/]");
return Task.CompletedTask;
}
}
42 changes: 42 additions & 0 deletions src/Presentation/DependencyInjections.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
using Infrastructure.Database;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Presentation.Screens;

namespace Presentation;

/// <summary> Contains service collection extension methods for registering screens. </summary>
public static class DependencyInjections
{
/// <summary> Registers the screens. </summary>
public static IServiceCollection AddScreens(
this IServiceCollection services,
IConfiguration configuration)
{
services.AddTransient<MenuScreen>();
services.AddTransient<RegisterScreen>();
return services;
}

/// <summary> Run migrations for the EF Core database context. </summary>
public static async Task<IHost> RunMigrationsAsync(this IHost host)
{
using var scope = host.Services.CreateScope();
var logger = scope.ServiceProvider.GetRequiredService<ILogger<ApplicationDbContext>>();
var dbContext = scope.ServiceProvider.GetRequiredService<ApplicationDbContext>();
try
{
await dbContext.Database.MigrateAsync();
logger.LogInformation($"Successfully migrated the database");
}
catch (Exception ex)
{
logger.LogError(ex, $"An error occurred while migrating the database");
throw;
}
return host;
}
}
74 changes: 74 additions & 0 deletions src/Presentation/Extensions/CustomResults.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
using Microsoft.AspNetCore.Http;
using SharedKernel;

namespace Presentation.Extensions;

public static class CustomResults
{
public static IResult Problem(Result result)
{
if (result.IsSuccess)
{
throw new InvalidOperationException();
}

return Results.Problem(
title: GetTitle(result.Error),
detail: GetDetail(result.Error),
type: GetType(result.Error.Type),
statusCode: GetStatusCode(result.Error.Type),
extensions: GetErrors(result));

static string GetTitle(Error error) =>
error.Type switch
{
ErrorType.Validation => error.Code,
ErrorType.Problem => error.Code,
ErrorType.NotFound => error.Code,
ErrorType.Conflict => error.Code,
_ => "Server failure"
};

static string GetDetail(Error error) =>
error.Type switch
{
ErrorType.Validation => error.Description,
ErrorType.Problem => error.Description,
ErrorType.NotFound => error.Description,
ErrorType.Conflict => error.Description,
_ => "An unexpected error occurred"
};

static string GetType(ErrorType errorType) =>
errorType switch
{
ErrorType.Validation => "https://tools.ietf.org/html/rfc7231#section-6.5.1",
ErrorType.Problem => "https://tools.ietf.org/html/rfc7231#section-6.5.1",
ErrorType.NotFound => "https://tools.ietf.org/html/rfc7231#section-6.5.4",
ErrorType.Conflict => "https://tools.ietf.org/html/rfc7231#section-6.5.8",
_ => "https://tools.ietf.org/html/rfc7231#section-6.6.1"
};

static int GetStatusCode(ErrorType errorType) =>
errorType switch
{
ErrorType.Validation => StatusCodes.Status400BadRequest,
ErrorType.NotFound => StatusCodes.Status404NotFound,
ErrorType.Conflict => StatusCodes.Status409Conflict,
_ => StatusCodes.Status500InternalServerError
};

static Dictionary<string, object?>? GetErrors(Result result)
{
if (result.Error is not ValidationError validationError)
{
return null;
}

return new Dictionary<string, object?>
{
{ "errors", validationError.Errors }
};
}
}
}
22 changes: 22 additions & 0 deletions src/Presentation/Extensions/ResultExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
using SharedKernel;

namespace Presentation.Extensions;

public static class ResultExtensions
{
public static TOut Match<TOut>(
this Result result,
Func<TOut> onSuccess,
Func<Result, TOut> onFailure)
{
return result.IsSuccess ? onSuccess() : onFailure(result);
}

public static TOut Match<TIn, TOut>(
this Result<TIn> result,
Func<TIn, TOut> onSuccess,
Func<Result<TIn>, TOut> onFailure)
{
return result.IsSuccess ? onSuccess(result.Value) : onFailure(result);
}
}
23 changes: 22 additions & 1 deletion src/Presentation/Presentation.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,29 @@
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<PublishAot>true</PublishAot>
<InvariantGlobalization>true</InvariantGlobalization>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.Extensions.DependencyInjection" />
<PackageReference Include="Microsoft.Extensions.Hosting" />
<PackageReference Include="Spectre.Console" />
<PackageReference Include="Serilog.AspNetCore" />
<PackageReference Include="Serilog.Sinks.Seq" />
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\Application\Application.csproj" />
<ProjectReference Include="..\Infrastructure\Infrastructure.csproj" />
</ItemGroup>

<ItemGroup>
<None Update="appsettings.json">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="appsettings.Development.json">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
</ItemGroup>

</Project>
Loading
Loading