Skip to content

Commit

Permalink
Merge pull request #32 from SpotifaiI/development
Browse files Browse the repository at this point in the history
add async methods and update custom errors
  • Loading branch information
tdayko authored Apr 20, 2024
2 parents 2660700 + b8f879c commit 1d53e01
Show file tree
Hide file tree
Showing 37 changed files with 211 additions and 141 deletions.
27 changes: 17 additions & 10 deletions src/HigiaServer.API/Endpoints/AuthenticationEndpoint.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,7 @@ public static class AuthenticationEndpoint
{
public static IEndpointRouteBuilder AddAuthenticationEndpoint(this IEndpointRouteBuilder app)
{
var authEndpoint = app.MapGroup("higia-server/api/auth")
.WithTags("Authentication");
var authEndpoint = app.MapGroup("higia-server/api/auth").WithTags("Authentication");

// register
authEndpoint.MapPost("register", HandleRegister)
Expand All @@ -41,10 +40,14 @@ public static IEndpointRouteBuilder AddAuthenticationEndpoint(this IEndpointRout
return app;
}

private static async Task<IResult> HandleRegister(RegisterRequest request, IUserRepository repository,
IMapper mapper, IJwtTokenService jwtTokenService)
private static async Task<IResult> HandleRegister(
RegisterRequest request,
IUserRepository repository,
IMapper mapper,
IJwtTokenService jwtTokenService
)
{
if (repository.GetUserByEmail(request.Email) != null) throw new DuplicateEmailException(request.Email);
if (await repository.GetUserByEmail(request.Email) != null) throw new DuplicateEmailException(request.Email);

request.Password = BCrypt.Net.BCrypt.HashPassword(request.Password);
var user = mapper.Map<User>(request);
Expand All @@ -59,17 +62,21 @@ private static async Task<IResult> HandleRegister(RegisterRequest request, IUser
return Results.Ok(new StandardSuccessResponse<AuthenticationResponse>(authResponse));
}

private static async Task<IResult> HandleLogin(LoginRequest request, IUserRepository repository, IMapper mapper,
IJwtTokenService jwtTokenService)
private static async Task<IResult> HandleLogin(
LoginRequest request,
IUserRepository repository,
IMapper mapper,
IJwtTokenService jwtTokenService
)
{
if (repository.GetUserByEmail(request.Email) is not { } user) throw new EmailGivenNotFoundException(request.Email);
if (await repository.GetUserByEmail(request.Email) is not { } user) throw new EmailGivenNotFoundException(request.Email);
if (!BCrypt.Net.BCrypt.Verify(request.Password, user.Password)) throw new InvalidPasswordException();

var authResponse = new AuthenticationResponse(
mapper.Map<UserResponse>(user),
jwtTokenService.GenerateToken(user)
);

return Results.Ok(new StandardSuccessResponse<AuthenticationResponse>(authResponse));
}
}
}
70 changes: 51 additions & 19 deletions src/HigiaServer.API/Endpoints/TaskEndpoint.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,15 @@
using HigiaServer.Application.Contracts.Responses;
using HigiaServer.Application.Errors;
using HigiaServer.Application.Repositories;
using Task = HigiaServer.Domain.Entities.Task;
using HigiaServer.Domain.Enums;

namespace HigiaServer.API.Endpoints;

public static class TaskEndpoint
{
public static IEndpointRouteBuilder AddTaskEndpoint(this IEndpointRouteBuilder app)
{
var authEndpoint = app.MapGroup("higia-server/api/tasks")
.WithTags("Tasks");
var authEndpoint = app.MapGroup("higia-server/api/tasks").WithTags("Tasks");

// add task
authEndpoint.MapPost("/", HandleAddTask)
Expand All @@ -25,9 +24,9 @@ public static IEndpointRouteBuilder AddTaskEndpoint(this IEndpointRouteBuilder a
x.Summary = "Add Tasks";
return x;
});

// get task by id
authEndpoint.MapGet("/{taskId}", GetTask)
authEndpoint.MapGet("/{taskId}", HandleGetTask)
.WithName("Get task by id")
.Produces<TaskResponse>()
.WithOpenApi(x =>
Expand All @@ -36,47 +35,80 @@ public static IEndpointRouteBuilder AddTaskEndpoint(this IEndpointRouteBuilder a
return x;
});

authEndpoint.MapPatch("/{taskId}/{status}", HandleUpdateTaskStatus)
.WithName("Update Task Status")
.WithOpenApi(x =>
{
x.Summary = "Get task by id";
return x;
});

return app;
}

#region private methods


private static async Task<IResult> HandleUpdateTaskStatus(
HttpContext context,
string taskId,
Status status,
ITaskRepository taskRepository
)
{
CheckAuthorizationAsAdministrator(context);
if (!Guid.TryParse(taskId, out var id)) throw new NonGuidTypeException(taskId);
var task = await taskRepository.GetTaskById(id) ?? throw new TaskIdGivenNotFoundException(id.ToString());

task.UpdateTaskStatus(status);
taskRepository.UpdateTask(task);

context.Response.Headers.Location = $"{context.Request.Scheme}://{context.Request.Host}/{context.Request.Path}/{task.Id}";
return Results.Ok();
}

private static async Task<IResult> HandleAddTask(
HttpContext context,
AddTaskRequest request,
IUserRepository userRepository,
ITaskRepository taskRepository,
IMapper mapper)
IMapper mapper
)
{
if (!context.User!.Identity!.IsAuthenticated) throw new UnauthenticatedException();
if (context.User.FindFirstValue(ClaimTypes.Role) == "collaborator") throw new UnauthorizedAccessException();
var task = mapper.Map<Task>(request);

var collaborators = request.CollaboratorsId
.Select(id => userRepository.GetUserById(id) ?? throw new CollaboratorIdNotFound(id.ToString()))
CheckAuthorizationAsAdministrator(context);
var task = mapper.Map<Domain.Entities.Task>(request);

var collaborators = (await Task.WhenAll(request.CollaboratorsId
.Select(async id => await userRepository.GetUserById(id) ?? throw new CollaboratorIdNotFound(id.ToString()))))
.ToList();

task.AddCollaboratorsToTask(collaborators);
taskRepository.AddTask(task);

var location = new Uri($"{context.Request.Scheme}://{context.Request.Host}/{context.Request.Path}/{task.Id}");

var taskResponse = mapper.Map<TaskResponse>(task);
return Results.Created(location, new StandardSuccessResponse<TaskResponse>(taskResponse, location));
}

private static async Task<IResult> GetTask(
private static async Task<IResult> HandleGetTask(
HttpContext context,
string taskId,
ITaskRepository taskRepository,
IMapper mapper)
IMapper mapper
)
{
if (!context.User!.Identity!.IsAuthenticated) throw new AuthenticationException();
if (!Guid.TryParse(taskId, out var id)) throw new NonGuidTypeException(taskId);

var task = taskRepository.GetTaskById(id) ?? throw new TaskIdGivenNotFoundException(id.ToString());
var task = await taskRepository.GetTaskById(id) ?? throw new TaskIdGivenNotFoundException(id.ToString());

var taskResponse = mapper.Map<TaskResponse>(task);
return Results.Ok(new StandardSuccessResponse<TaskResponse>(taskResponse));
}


private static void CheckAuthorizationAsAdministrator(HttpContext context)
{
if (!context.User!.Identity!.IsAuthenticated) throw new UnauthenticatedException();
if (context.User.FindFirstValue(ClaimTypes.Role) != "admin") throw new UnauthorizedAccessException();
}
#endregion
}
}
4 changes: 2 additions & 2 deletions src/HigiaServer.API/Extensions/CustomErrorsExtension.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ public static WebApplication AddCustomErrors(this WebApplication app)
IServiceException serviceException => ((int)serviceException.StatusCode, serviceException.ErrorMessage),
_ => (StatusCodes.Status500InternalServerError, "An error occurred while processing your request")
};

return Results.Problem(new ProblemDetails
{
Title = title,
Expand All @@ -31,4 +31,4 @@ public static WebApplication AddCustomErrors(this WebApplication app)

return app;
}
}
}
15 changes: 5 additions & 10 deletions src/HigiaServer.API/Extensions/CustomSwaggerExtension.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,7 @@ public static IServiceCollection AddCustomSwagger(this IServiceCollection servic
{
services.AddSwaggerGen(options =>
{
options.SwaggerDoc("v1", new OpenApiInfo
{
Title = "Higia Server",
Version = "v2"
});
options.SwaggerDoc("v1", new OpenApiInfo { Title = "Higia Server", Version = "v2" });

var jwtSecurityScheme = new OpenApiSecurityScheme
{
Expand All @@ -31,10 +27,9 @@ public static IServiceCollection AddCustomSwagger(this IServiceCollection servic
}
};
options.AddSecurityDefinition(jwtSecurityScheme.Reference.Id, jwtSecurityScheme);
options.AddSecurityRequirement(new OpenApiSecurityRequirement
{
{ jwtSecurityScheme, Array.Empty<string>() }
});
options.AddSecurityRequirement(
new OpenApiSecurityRequirement { { jwtSecurityScheme, Array.Empty<string>() } }
);
});

return services;
Expand All @@ -48,4 +43,4 @@ public static WebApplication AddCustomSwagger(this WebApplication app)

return app;
}
}
}
34 changes: 19 additions & 15 deletions src/HigiaServer.API/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,21 +16,25 @@
options.AddPolicy("admin", policy => policy.RequireRole("admin"));
options.AddPolicy("collaborator", policy => policy.RequireRole("collaborator"));
});
builder.Services.AddAuthentication(x =>
{
x.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
x.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
}).AddJwtBearer(x =>
{
var key = Encoding.ASCII.GetBytes(builder.Configuration.GetValue<string>("JwtSettings:SecretKey")!);
x.TokenValidationParameters = new TokenValidationParameters
builder
.Services.AddAuthentication(x =>
{
ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey(key),
ValidateIssuer = false,
ValidateAudience = false
};
});
x.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
x.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(x =>
{
var key = Encoding.ASCII.GetBytes(
builder.Configuration.GetValue<string>("JwtSettings:SecretKey")!
);
x.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey(key),
ValidateIssuer = false,
ValidateAudience = false
};
});

var app = builder.Build();

Expand All @@ -42,4 +46,4 @@
app.AddCustomErrors();
app.AddCustomSwagger();

app.Run();
app.Run();
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,12 @@ public class AddTaskRequest(
UrgencyLevel urgencyLevel,
Coordinates coordinates,
List<Guid> collaboratorsId,
string? description)
string? description
)
{
public string Title { get; private set; } = title;
public UrgencyLevel UrgencyLevel { get; private set; } = urgencyLevel;
public Status Status { get; private set; } = Status.New;
public Coordinates Coordinates { get; private set; } = coordinates;
public List<Guid> CollaboratorsId { get; private set; } = collaboratorsId;
public string? Description { get; private set; } = description;
Expand All @@ -20,4 +22,4 @@ public class Coordinates(string latitude, string longitude)
{
public string Latitude { get; private set; } = latitude;
public string Longitude { get; private set; } = longitude;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@ namespace HigiaServer.Application.Contracts.Requests;

public class LoginRequest(string email, string password)
{
[EmailAddress] public string Email { get; private set; } = email;
[EmailAddress]
public string Email { get; private set; } = email;

[PasswordPropertyText] public string Password { get; private set; } = password;
}
[PasswordPropertyText]
public string Password { get; private set; } = password;
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,11 @@ public class RegisterRequest(string email, string password, string name, bool is
{
public string Name { get; private set; } = name;

[EmailAddress] public string Email { get; private set; } = email;
[EmailAddress]
public string Email { get; private set; } = email;

[PasswordPropertyText] public string Password { get; set; } = password;
[PasswordPropertyText]
public string Password { get; set; } = password;

public bool IsAdmin { get; private set; } = isAdmin;
}
}
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
namespace HigiaServer.Application.Contracts.Responses;

public record AuthenticationResponse(UserResponse User, string Token);
public record AuthenticationResponse(UserResponse User, string Token);
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,4 @@ public class StandardSuccessResponse<T>(T data, Uri? location = null)

[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public Uri? Location { get; set; } = location;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ public record TaskResponse(
Guid Id,
string Title,
UrgencyLevel UrgencyLevel,
Status Status,
Coordinates Coordinates,
List<Guid> Collaborators,
string? Description);
string? Description
);

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,4 @@ public class UserResponse(Guid id, string email, string name, bool isAdmin)
public string Name { get; private set; } = name;
[EmailAddress] public string Email { get; private set; } = email;
public bool IsAdmin { get; private set; } = isAdmin;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ namespace HigiaServer.Application.Errors;

public class CollaboratorIdNotFound(string collaboratorId) : Exception, IServiceException
{
public HttpStatusCode StatusCode => HttpStatusCode.NotFound;
public HttpStatusCode StatusCode => HttpStatusCode.BadRequest;

public string ErrorMessage =>
$"The Collaborator with id {collaboratorId} was not found in the system. Please provide a valid Id.";
}
}
5 changes: 3 additions & 2 deletions src/HigiaServer.Application/Errors/DuplicateEmailException.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,6 @@ namespace HigiaServer.Application.Errors;
public class DuplicateEmailException(string email) : Exception, IServiceException
{
public HttpStatusCode StatusCode => HttpStatusCode.Conflict;
public string ErrorMessage => $"The email {email} is already in use. Please provide a different email.";
}
public string ErrorMessage =>
$"The email {email} is already in use. Please provide a different email.";
}
Loading

0 comments on commit 1d53e01

Please sign in to comment.