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

Seperated update user info into different flows/events #16

Merged
merged 3 commits into from
May 20, 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
26 changes: 16 additions & 10 deletions Core/Services/External/BlobStorage/BlobStorageService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,22 +9,26 @@ namespace Core.Services.External.BlobStorage;

public class BlobStorageService(IOptions<AzureBlobStorageOptions> azureBlobStorageOptions) : IBlobStorageService
{
public async Task<string> SaveImageToBlobStorage(string base64Image, string userEmail, string? blobUrl = null)
public async Task<string> SaveImageToBlobStorage(string base64Image, string userEmail, bool isPlantImage, string? blobUrl = null)
{
var imageBytes = Convert.FromBase64String(base64Image);
var blobName = blobUrl is not null
? GetBlobNameFromUrl(blobUrl)
? GetBlobNameFromUrl(blobUrl, isPlantImage)
: userEmail + "_" + Guid.NewGuid();

var blobClient = new BlobClient(azureBlobStorageOptions.Value.ConnectionString, azureBlobStorageOptions.Value.PlantImagesContainer, blobName);
var container = isPlantImage ? azureBlobStorageOptions.Value.PlantImagesContainer : azureBlobStorageOptions.Value.UserProfileImagesContainer;
var blobClient = new BlobClient(azureBlobStorageOptions.Value.ConnectionString, container, blobName);
var binaryData = new BinaryData(imageBytes);
await blobClient.UploadAsync(binaryData, true);
return WebUtility.UrlDecode(blobClient.Uri.ToString());
}

public async Task<bool> DeleteImageFromBlobStorage(string imageUrl)
public async Task<bool> DeleteImageFromBlobStorage(string blobUrl, bool isPlantImage)
{
var blobClient = new BlobClient(new Uri(imageUrl));
var blobName = GetBlobNameFromUrl(blobUrl, isPlantImage);

var container = isPlantImage ? azureBlobStorageOptions.Value.PlantImagesContainer : azureBlobStorageOptions.Value.UserProfileImagesContainer;
var blobClient = new BlobClient(azureBlobStorageOptions.Value.ConnectionString, container, blobName);
return await blobClient.DeleteIfExistsAsync();
}

Expand All @@ -41,11 +45,12 @@ public async Task<string> GetImageFromBlobStorage(string imageUrl)
return Convert.ToBase64String(imageBytes);
}

public string GenerateSasUri(string blobUrl)
public string GenerateSasUri(string blobUrl, bool isPlantImage)
{
var blobServiceClient = new BlobServiceClient(azureBlobStorageOptions.Value.ConnectionString);
var blobContainerClient = blobServiceClient.GetBlobContainerClient(azureBlobStorageOptions.Value.PlantImagesContainer);
var blobClient = blobContainerClient.GetBlobClient(GetBlobNameFromUrl(blobUrl));
var container = isPlantImage ? azureBlobStorageOptions.Value.PlantImagesContainer : azureBlobStorageOptions.Value.UserProfileImagesContainer;
var blobContainerClient = blobServiceClient.GetBlobContainerClient(container);
var blobClient = blobContainerClient.GetBlobClient(GetBlobNameFromUrl(blobUrl, isPlantImage));

var blobSasBuilder = new BlobSasBuilder
{
Expand All @@ -68,8 +73,9 @@ public string GetBlobUrlFromSasUri(string sasUri)
return blobUriBuilder.ToString();
}

private string GetBlobNameFromUrl(string blobUrl)
private string GetBlobNameFromUrl(string blobUrl, bool isPlantImage)
{
return WebUtility.UrlDecode(new Uri(blobUrl).AbsolutePath.Substring(azureBlobStorageOptions.Value.PlantImagesContainer.Length + 2));
var container = isPlantImage ? azureBlobStorageOptions.Value.PlantImagesContainer : azureBlobStorageOptions.Value.UserProfileImagesContainer;
return WebUtility.UrlDecode(new Uri(blobUrl).AbsolutePath.Substring(container.Length + 2));
}
}
6 changes: 3 additions & 3 deletions Core/Services/External/BlobStorage/IBlobStorageService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@ namespace Core.Services.External.BlobStorage;

public interface IBlobStorageService
{
public Task<string> SaveImageToBlobStorage(string base64Image, string userEmail, string? blobUrl = null);
public Task<bool> DeleteImageFromBlobStorage(string imageUrl);
public Task<string> SaveImageToBlobStorage(string base64Image, string userEmail, bool isPlantImage, string? blobUrl = null);
public Task<bool> DeleteImageFromBlobStorage(string imageUrl, bool isPlantImage);
public Task<string> GetImageFromBlobStorage(string imageUrl);
public string GenerateSasUri(string blobUrl);
public string GenerateSasUri(string blobUrl, bool isPlantImage);
public string GetBlobUrlFromSasUri(string sasUri);
}
6 changes: 3 additions & 3 deletions Core/Services/External/BlobStorage/MockBlobStorageService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,12 @@ namespace Core.Services.External.BlobStorage;

public class MockBlobStorageService : IBlobStorageService
{
public Task<string> SaveImageToBlobStorage(string base64Image, string userEmail, string? blobUrl = null)
public Task<string> SaveImageToBlobStorage(string base64Image, string userEmail, bool isPlantImage, string? blobUrl = null)
{
return Task.FromResult("https://www.example.com");
}

public Task<bool> DeleteImageFromBlobStorage(string imageUrl)
public Task<bool> DeleteImageFromBlobStorage(string imageUrl, bool isPlantImage)
{
return Task.FromResult(true);
}
Expand All @@ -17,7 +17,7 @@ public Task<string> GetImageFromBlobStorage(string imageUrl)
return Task.FromResult("base64Image");
}

public string GenerateSasUri(string blobUrl)
public string GenerateSasUri(string blobUrl, bool isPlantImage)
{
return "https://www.example.com";
}
Expand Down
14 changes: 7 additions & 7 deletions Core/Services/PlantService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ public async Task<Plant> CreatePlant(CreatePlantDto createPlantDto, string logge
var ímageUrl = azureBlobStorageOptions.Value.DefaultPlantImageUrl;
if (createPlantDto.Base64Image is not null)
{
ímageUrl = await blobStorageService.SaveImageToBlobStorage(createPlantDto.Base64Image, loggedInUser);
ímageUrl = await blobStorageService.SaveImageToBlobStorage(createPlantDto.Base64Image, loggedInUser, true);
}

// Insert plant first to get the plantId
Expand All @@ -45,28 +45,28 @@ public async Task<Plant> CreatePlant(CreatePlantDto createPlantDto, string logge
var requirementsDto = createPlantDto.CreateRequirementsDto;
requirementsDto.PlantId = plant.PlantId;
plant.Requirements = await requirementService.CreateRequirements(requirementsDto);
plant.ImageUrl = blobStorageService.GenerateSasUri(plant.ImageUrl);
plant.ImageUrl = blobStorageService.GenerateSasUri(plant.ImageUrl, true);
return plant;
}

public async Task<Plant> GetPlantById(Guid id, string requesterEmail)
{
var plant = await VerifyPlantExistsAndUserHasAccess(id, requesterEmail);
plant.ImageUrl = blobStorageService.GenerateSasUri(plant.ImageUrl);
plant.ImageUrl = blobStorageService.GenerateSasUri(plant.ImageUrl, true);
return plant;
}

public async Task<List<Plant>> GetPlantsForUser(string userEmail, int pageNumber, int pageSize)
{
var plants = await plantRepository.GetPlantsForUser(userEmail, pageNumber, pageSize);
plants.ForEach(plant => plant.ImageUrl = blobStorageService.GenerateSasUri(plant.ImageUrl)); // Otherwise the client can't access the image
plants.ForEach(plant => plant.ImageUrl = blobStorageService.GenerateSasUri(plant.ImageUrl, true)); // Otherwise the client can't access the image
return plants;
}

public async Task<List<Plant>> GetPlantsForCollection(Guid collectionId)
{
var plants = await plantRepository.GetPlantsForCollection(collectionId);
plants.ForEach(plant => plant.ImageUrl = blobStorageService.GenerateSasUri(plant.ImageUrl)); // Otherwise the client can't access the image
plants.ForEach(plant => plant.ImageUrl = blobStorageService.GenerateSasUri(plant.ImageUrl, true)); // Otherwise the client can't access the image
return plants;
}

Expand All @@ -85,7 +85,7 @@ public async Task<Plant> UpdatePlant(UpdatePlantDto updatePlantDto, string reque
var imageUrl = blobStorageService.GetBlobUrlFromSasUri(plant.ImageUrl);
if (updatePlantDto.Base64Image is not null)
{
imageUrl = await blobStorageService.SaveImageToBlobStorage(updatePlantDto.Base64Image, requesterEmail, plant.ImageUrl);
imageUrl = await blobStorageService.SaveImageToBlobStorage(updatePlantDto.Base64Image, requesterEmail, true, plant.ImageUrl);
}

// Update the plant
Expand All @@ -100,7 +100,7 @@ public async Task<Plant> UpdatePlant(UpdatePlantDto updatePlantDto, string reque
ConditionsLogs = plant.ConditionsLogs
};

plant.ImageUrl = blobStorageService.GenerateSasUri(plant.ImageUrl);
plant.ImageUrl = blobStorageService.GenerateSasUri(plant.ImageUrl, true);
return await plantRepository.UpdatePlant(plant);
}

Expand Down
54 changes: 39 additions & 15 deletions Core/Services/UserService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ public async Task CreateUser(RegisterUserDto registerUserDto)

if (registerUserDto.Base64Image != null)
{
registerUserDto.BlobUrl = await blobStorageService.SaveImageToBlobStorage(registerUserDto.Base64Image, registerUserDto.Email);
registerUserDto.BlobUrl = await blobStorageService.SaveImageToBlobStorage(registerUserDto.Base64Image, registerUserDto.Email, false);
}

await userRepository.CreateUser(registerUserDto);
Expand All @@ -35,23 +35,47 @@ public async Task<User> GetUserByEmail(string email)
if (user == null) throw new NotFoundException();
return user;
}

public async Task<GetUserDto?> UpdateUser(UserDto userDto, string email)
public async Task DeleteProfileImage(string email)
{
var userToUpdate = await userRepository.GetUserByEmail(email);
if (userToUpdate == null) throw new NotFoundException("User not found");
var user = await ValidateAndGetUser(email);

if (userDto.Username != null && !userDto.Username.Equals(string.Empty))
{
userToUpdate.UserName = userDto.Username;
}
if (user.BlobUrl == null) return;

var currentBlobUrl = userToUpdate.BlobUrl;
if (userDto.Base64Image != null)
{
userDto.BlobUrl = await blobStorageService.SaveImageToBlobStorage(userDto.Base64Image, email, currentBlobUrl);
}
var blobToDelete = blobStorageService.GetBlobUrlFromSasUri(user.BlobUrl);
await blobStorageService.DeleteImageFromBlobStorage(blobToDelete, false);
user.BlobUrl = null;
await userRepository.UpdateUserProfile(user);
}

public async Task<string> UpdateUserProfileImage(string email, string base64Image)
{
var userToUpdate = await ValidateAndGetUser(email);

return await userRepository.UpdateUser(userToUpdate, userDto.Password);
var currentBlobUrl = userToUpdate.BlobUrl;
userToUpdate.BlobUrl = await blobStorageService.SaveImageToBlobStorage(base64Image, email, false, string.IsNullOrEmpty(currentBlobUrl) ? null : currentBlobUrl);
await userRepository.UpdateUserProfile(userToUpdate);
var blobUrl = blobStorageService.GenerateSasUri(userToUpdate.BlobUrl, false);
return blobUrl;
}

public async Task<string> UpdateUsername(string email, string username)
{
var userToUpdate = await ValidateAndGetUser(email);
userToUpdate.UserName = username;
await userRepository.UpdateUserProfile(userToUpdate);
return userToUpdate.UserName;
}

public async Task UpdatePassword(string email, string password)
{
var userToUpdate = await ValidateAndGetUser(email);
await userRepository.UpdatePassword(userToUpdate, password);
}

private async Task<User> ValidateAndGetUser(string email)
{
var user = await userRepository.GetUserByEmail(email);
if (user == null) throw new NotFoundException("User not found");
return user;
}
}
30 changes: 11 additions & 19 deletions Infrastructure/Repositories/UserRepository.cs
Original file line number Diff line number Diff line change
Expand Up @@ -49,28 +49,20 @@ public async Task CreateUser(RegisterUserDto registerUserDto)

return user;
}

public async Task<GetUserDto?> UpdateUser(User userToUpdate, string? password)
public async Task UpdateUserProfile(User userToUpdate)
{
await using var context = await dbContextFactory.CreateDbContextAsync();

if (password != null && !password.Equals(string.Empty))
{
var passwordHashAndSalt = PasswordHasher.HashPassword(password);
userToUpdate.PasswordHash = passwordHashAndSalt[1];
userToUpdate.PasswordSalt = passwordHashAndSalt[0];
}

var getUserDto = new GetUserDto
{
UserEmail = userToUpdate.UserEmail,
Username = userToUpdate.UserName,
BlobUrl = userToUpdate.BlobUrl
};

context.Users.Update(userToUpdate);
await context.SaveChangesAsync();

return getUserDto;
}

public async Task UpdatePassword(User userToUpdate, string password)
{
await using var context = await dbContextFactory.CreateDbContextAsync();
var passwordHashAndSalt = PasswordHasher.HashPassword(password);
userToUpdate.PasswordHash = passwordHashAndSalt[1];
userToUpdate.PasswordSalt = passwordHashAndSalt[0];
context.Users.Update(userToUpdate);
await context.SaveChangesAsync();
}
}
9 changes: 7 additions & 2 deletions api/Events/Auth/Client/ClientWantsToLogIn.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using api.Events.Auth.Server;
using api.Extensions;
using Core.Services;
using Core.Services.External.BlobStorage;
using Fleck;
using lib;
using Shared.Dtos;
Expand All @@ -14,7 +15,7 @@ public class ClientWantsToLogInDto : BaseDto
public LoginDto LoginDto { get; set; } = null!;
}

public class ClientWantsToLogIn(UserService userService)
public class ClientWantsToLogIn(UserService userService, IBlobStorageService blobStorageService)
: BaseEventHandler<ClientWantsToLogInDto>
{
public override async Task Handle(ClientWantsToLogInDto dto, IWebSocketConnection socket)
Expand All @@ -28,9 +29,13 @@ public override async Task Handle(ClientWantsToLogInDto dto, IWebSocketConnectio
{
UserEmail = user.UserEmail,
Username = user.UserName,
BlobUrl = user.BlobUrl
};

if (!string.IsNullOrEmpty(user.BlobUrl))
{
getUserDto.BlobUrl = blobStorageService.GenerateSasUri(user.BlobUrl, false);
}

socket.SendDto(new ServerAuthenticatesUser
{
Jwt = jwt,
Expand Down
35 changes: 35 additions & 0 deletions api/Events/User/ClientWantsToDeleteProfileImage.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
using api.Events.Global;
using api.Events.User.ServerResponses;
using api.Extensions;
using Core.Services;
using Fleck;
using lib;
using Serilog;
using Shared.Exceptions;
using Shared.Models;

namespace api.Events.User;

public class ClientWantsToDeleteProfileImageDto : BaseDtoWithJwt
{
}

public class ClientWantsToDeleteProfileImage(UserService userService, JwtService jwtService) : BaseEventHandler<ClientWantsToDeleteProfileImageDto>
{
public override async Task Handle(ClientWantsToDeleteProfileImageDto dto, IWebSocketConnection socket)
{
var email = jwtService.GetEmailFromJwt(dto.Jwt);
try
{
await userService.DeleteProfileImage(email);
socket.SendDto(new ServerConfirmsDeleteProfileImage());
} catch (Exception e) when (e is not NotFoundException)
{
socket.SendDto(new ServerRejectsUpdate
{
Error = "Delete failed"
});
}
}
}

33 changes: 33 additions & 0 deletions api/Events/User/ClientWantsToUpdatePassword.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
using api.Events.User.ServerResponses;
using api.Extensions;
using Core.Services;
using Fleck;
using lib;
using Shared.Exceptions;
using Shared.Models;

namespace api.Events.User;

public class ClientWantsToUpdatePasswordDto : BaseDtoWithJwt
{
public string password { get; set; } = null!;
}

public class ClientWantsToUpdatePassword(UserService userService, JwtService jwtService) : BaseEventHandler<ClientWantsToUpdatePasswordDto>
{
public override async Task Handle(ClientWantsToUpdatePasswordDto dto, IWebSocketConnection socket)
{
var email = jwtService.GetEmailFromJwt(dto.Jwt);
try
{
await userService.UpdatePassword(email, dto.password);
socket.SendDto(new ServerConfirmsUpdatePassword());
} catch (Exception e) when (e is not NotFoundException)
{
socket.SendDto(new ServerRejectsUpdate
{
Error = "Update password failed"
});
}
}
}
Loading
Loading