Skip to content
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
1 change: 1 addition & 0 deletions api.tests/UserEndpointTests/GetUserTests.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using exercise.wwwapi.DTOs;
using exercise.wwwapi.DTOs.GetUsers;
using exercise.wwwapi.DTOs.Login;
using exercise.wwwapi.DTOs.Users;
using exercise.wwwapi.Endpoints;
using System.Text.Json;

Expand Down
3 changes: 2 additions & 1 deletion exercise.wwwapi/DTOs/GetObjects/UsersSuccessDTO.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System.Text.Json.Serialization;
using exercise.wwwapi.DTOs.Users;
using System.Text.Json.Serialization;

namespace exercise.wwwapi.DTOs.GetUsers;

Expand Down
3 changes: 2 additions & 1 deletion exercise.wwwapi/DTOs/Login/LoginSuccessDTO.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System.Text.Json.Serialization;
using exercise.wwwapi.DTOs.Users;
using System.Text.Json.Serialization;

namespace exercise.wwwapi.DTOs.Login;

Expand Down
3 changes: 2 additions & 1 deletion exercise.wwwapi/DTOs/Register/RegisterSuccessDTO.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System.ComponentModel.DataAnnotations.Schema;
using exercise.wwwapi.DTOs.Users;
using System.ComponentModel.DataAnnotations.Schema;

namespace exercise.wwwapi.DTOs.Register;

Expand Down
22 changes: 22 additions & 0 deletions exercise.wwwapi/DTOs/Users/PatchUserDTO.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
using exercise.wwwapi.Enums;
using System.Text.Json.Serialization;

namespace exercise.wwwapi.DTOs.Users
{
public class PatchUserDTO
{
public string? Email { get; set; }

public string? Password { get; set; }

public string? FirstName { get; set; }

public string? LastName { get; set; }
public string? Bio { get; set; }
public string? Github { get; set; }
public string? Username { get; set; }
public string? Mobile { get; set; }
public Specialism? Specialism { get; set; }
public Role? Role { get; set; }
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
using System.ComponentModel.DataAnnotations.Schema;
using System.Text.Json.Serialization;

namespace exercise.wwwapi.DTOs;
namespace exercise.wwwapi.DTOs.Users;

public class UserDTO
{
Expand Down
96 changes: 29 additions & 67 deletions exercise.wwwapi/Endpoints/UserEndpoints.cs
Original file line number Diff line number Diff line change
Expand Up @@ -34,18 +34,18 @@ public static void ConfigureAuthApi(this WebApplication app)
users.MapPost("/", Register).WithSummary("Create user"); //OKOKOK
users.MapGet("/by_cohortcourse/{id}", GetUsersByCohortCourse).WithSummary("Get all users from a cohortCourse"); //OKOKOK
users.MapGet("/by_cohort/{id}", GetUsersByCohort).WithSummary("Get all users from a cohort"); //OKOKOK
users.MapGet("/", GetUsers).WithSummary("Get all users or filter by first name, last name or full name");
users.MapGet("/{id}", GetUserById).WithSummary("Get user by user id");
app.MapPost("/login", Login).WithSummary("Localhost Login");
users.MapPatch("/{id}", UpdateUser).RequireAuthorization().WithSummary("Update a user");
users.MapGet("/", GetUsers).WithSummary("Get all users or filter by first name, last name or full name");//OKOKOK
users.MapGet("/{id}", GetUserById).WithSummary("Get user by user id"); //OKOKOK
app.MapPost("/login", Login).WithSummary("Localhost Login"); //OKOKOK
users.MapPatch("/{id}", UpdateUser).RequireAuthorization().WithSummary("Update a user");//OKOKOK
users.MapDelete("/{id}", DeleteUser).RequireAuthorization().WithSummary("Delete a user");
}

[ProducesResponseType(StatusCodes.Status200OK)]
private static async Task<IResult> GetUsers(IRepository<User> userRepository, string? searchTerm,
ClaimsPrincipal claimPrincipal)
{
var results = (await userRepository.GetAllAsync(u => u.Notes)).ToList();
var results = await userRepository.GetWithIncludes(a => a.Include(u => u.Notes));

if (!string.IsNullOrWhiteSpace(searchTerm))
{
Expand All @@ -56,19 +56,9 @@ private static async Task<IResult> GetUsers(IRepository<User> userRepository, st
var userRole = claimPrincipal.Role();
var authorizedAsTeacher = AuthorizeTeacher(claimPrincipal);

var userData = new UsersSuccessDTO
{
Users = results.Select(user => authorizedAsTeacher
? UserFactory.GetUserDTO(user, PrivilegeLevel.Teacher)
: UserFactory.GetUserDTO(user, PrivilegeLevel.Student))
.ToList()
};


var response = new ResponseDTO<UsersSuccessDTO>
{
Status = "success",
Data = userData
};
var response = results.Select(u => new UserDTO(u)).ToList();
return TypedResults.Ok(response);
}

Expand Down Expand Up @@ -168,15 +158,17 @@ private static async Task<IResult> Register(PostUserDTO request, IRepository<Use
private static async Task<IResult> Login(LoginRequestDTO request, IRepository<User> userRepository,
IConfigurationSettings configurationSettings)
{
var allUsers = await userRepository.GetAllAsync();
var user = allUsers.FirstOrDefault(u => u.Email == request.Email);
if (user == null)
var response = await userRepository.GetWithIncludes(x => x.Where(u => u.Email == request.Email)); // uses where-statement to filter data before fetching
if (response.Count == 0)
{
return Results.BadRequest(new Payload<object>
{
Status = "User does not exist", Data = new { email = "Invalid email and/or password provided" }
Status = "User does not exist",
Data = new { email = "Invalid email and/or password provided" }
});
}
var user = response.FirstOrDefault();


if (!BCrypt.Net.BCrypt.Verify(request.Password, user.PasswordHash))
{
Expand All @@ -189,7 +181,7 @@ private static async Task<IResult> Login(LoginRequestDTO request, IRepository<Us

var token = CreateToken(user, configurationSettings);

var response = new ResponseDTO<LoginSuccessDTO>
var result = new ResponseDTO<LoginSuccessDTO>
{
Status = "Success",
Data = new LoginSuccessDTO
Expand All @@ -198,41 +190,24 @@ private static async Task<IResult> Login(LoginRequestDTO request, IRepository<Us
User = UserFactory.GetUserDTO(user, PrivilegeLevel.Student)
}
};
return Results.Ok(response);
return Results.Ok(result);
}

[ProducesResponseType(StatusCodes.Status404NotFound)]
[ProducesResponseType(StatusCodes.Status200OK)]
public static async Task<IResult> GetUserById(IRepository<User> userRepository, int id, ClaimsPrincipal claimsPrincipal)
{
var user = await userRepository.GetByIdAsync(
id,
user => user.Notes
);
if (user == null)
var response = await userRepository.GetByIdWithIncludes(x => x.Include(u => u.Notes), id);

if (response == null)
{
return TypedResults.NotFound();
}

var response = new ResponseDTO<UserDTO>
{
Status = "success",
Data = UserFactory.GetUserDTO(user, PrivilegeLevel.Student)
};
var result = new UserDTO(response);

var userRole = claimsPrincipal.Role();

if (userRole == "Teacher")
{
response.Data.Notes = user.Notes.Select(note => new NoteDTO
{
Id = note.Id,
Title = note.Title,
Content = note.Content,
CreatedAt = note.CreatedAt,
UpdatedAt = note.UpdatedAt
}).ToList();
}


return TypedResults.Ok(response);
}
Expand All @@ -242,16 +217,13 @@ public static async Task<IResult> GetUserById(IRepository<User> userRepository,
[ProducesResponseType(StatusCodes.Status400BadRequest)]
[ProducesResponseType(StatusCodes.Status401Unauthorized)]
public static async Task<IResult> UpdateUser(IRepository<User> userRepository, int id,
UpdateUserRequestDTO request,
IValidator<UpdateUserRequestDTO> validator, ClaimsPrincipal claimsPrinciple
PatchUserDTO request,
IValidator<PatchUserDTO> validator, ClaimsPrincipal claimsPrinciple
)
{
// Only teacher can edit protected fields
var authorized = AuthorizeTeacher(claimsPrinciple);
if (!authorized && (request.StartDate is not null
|| request.EndDate is not null
|| request.CohortId is not null
|| request.Specialism is not null
if (!authorized && (request.Specialism is not null
|| request.Role is not null))
{
return Results.Unauthorized();
Expand Down Expand Up @@ -316,22 +288,12 @@ public static async Task<IResult> UpdateUser(IRepository<User> userRepository, i
userRepository.Update(user);
await userRepository.SaveAsync();

var response = new ResponseDTO<UpdateUserSuccessDTO>()
var result = await userRepository.GetByIdWithIncludes(x => x.Include(u => u.Notes), id);

var response = new ResponseDTO<UserDTO>()
{
Status = "success",
Data = new UpdateUserSuccessDTO()
{
Id = user.Id,
Email = user.Email,
FirstName = user.FirstName,
LastName = user.LastName,
Bio = user.Bio,
Github = user.Github,
Username = user.Username,
Mobile = user.Mobile,
Specialism = (Specialism)user.Specialism,
Role = (Role)user.Role,
}
Data = new UserDTO(result)
};

return TypedResults.Ok(response);
Expand All @@ -350,7 +312,7 @@ public static async Task<IResult> DeleteUser(IRepository<User> userRepository, i
return Results.Unauthorized();
}

var user = await userRepository.GetByIdAsync(id);
var user = await userRepository.GetByIdWithIncludes(null, id);
if (user == null)
{
return TypedResults.NotFound();
Expand Down Expand Up @@ -378,7 +340,7 @@ private static string CreateToken(User user, IConfigurationSettings configuratio
new(ClaimTypes.Role, user.Role.ToString())
};

var tokenKey = Environment.GetEnvironmentVariable(Globals.EnvironmentEnvVariable) == "Staging"
var tokenKey = Environment.GetEnvironmentVariable(Globals.EnvironmentEnvVariable) == "Staging"
? Globals.TestTokenKey
: Globals.TokenKey;
var rawToken = configurationSettings.GetValue(tokenKey);
Expand Down
4 changes: 2 additions & 2 deletions exercise.wwwapi/Factories/UserFactory.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
using exercise.wwwapi.DTOs;
using exercise.wwwapi.DTOs.Notes;
using exercise.wwwapi.DTOs.Notes;
using exercise.wwwapi.DTOs.Users;
using exercise.wwwapi.Enums;
using exercise.wwwapi.Models;
using System.Numerics;
Expand Down
5 changes: 3 additions & 2 deletions exercise.wwwapi/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@

// Register validators
builder.Services.AddScoped<IValidator<PostUserDTO>, UserRegisterValidator>();
builder.Services.AddScoped<IValidator<UpdateUserRequestDTO>, UserUpdateValidator>();
builder.Services.AddScoped<IValidator<PatchUserDTO>, UserUpdateValidator>();
builder.Services.AddScoped<IValidator<CreateNoteRequestDTO>, CreateNoteValidator>();
builder.Services.AddScoped<IValidator<UpdateNoteRequestDTO>, UpdateNoteValidator>();

Expand Down Expand Up @@ -197,4 +197,5 @@

public partial class Program
{
} // needed for testing - please ignore
} // needed for testing - please ignore

Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
using exercise.wwwapi.DTOs.UpdateUser;
using exercise.wwwapi.DTOs.Users;
using FluentValidation;

namespace exercise.wwwapi.Validators.UserValidators;

public class UserUpdateValidator : AbstractValidator<UpdateUserRequestDTO>
public class UserUpdateValidator : AbstractValidator<PatchUserDTO>
{
public UserUpdateValidator()
{
Expand Down