diff --git a/Constants.cs b/Constants.cs index 76bfeffc2dfb729690df6defb13280c0ff65e8e5..9c5d77e5f03d0576292852ed32d6ea59983e5ab9 100644 --- a/Constants.cs +++ b/Constants.cs @@ -21,6 +21,7 @@ public class Constants public static class AzureBlobContainer { + public const string ProfilePictures = "profile-pictures"; public const string BannerPictures = "banner-pictures"; } diff --git a/Controllers/ProfileController.cs b/Controllers/ProfileController.cs index 7247d8fbffaf4a4b8b83e871086c077960d13dd2..ab7bb128f04d2275cd39f3cafb53c568f080e78f 100644 --- a/Controllers/ProfileController.cs +++ b/Controllers/ProfileController.cs @@ -1,5 +1,6 @@ namespace Group17profile.Controllers; +using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Middleware; using Models.DTOs; @@ -24,7 +25,7 @@ public class ProfileController : DefaultAuthenticationController { try { - return Ok(await _profileService.GetProfileForUser(1)); + return Ok(await _profileService.GetProfileForUser(UserId)); } catch (Exception ex) { @@ -48,12 +49,53 @@ public class ProfileController : DefaultAuthenticationController } } - [HttpPost("UploadBannerPicture")] + [HttpPost("UploadProfilePicture")] [Consumes("multipart/form-data")] - public async Task<ActionResult<ResponseEnvelope<ProfileDTO>>> UploadBannerPicture(IFormFile? profilePicture) + public async Task<ActionResult<ResponseEnvelope<ProfileDTO>>> UploadProfilePicture(IFormFile? profilePicture) { if (profilePicture == null) return BadRequest("Please upload a file."); - return Ok(await _profileService.UploadBannerPicture(UserId, profilePicture)); + return Ok(await _profileService.UploadProfilePicture(UserId, profilePicture)); + } + + [HttpPost("UploadBannerPicture")] + [Consumes("multipart/form-data")] + public async Task<ActionResult<ResponseEnvelope<ProfileDTO>>> UploadBannerPicture(IFormFile? bannerPicture) + { + if (bannerPicture == null) + return BadRequest("Please upload a file."); + return Ok(await _profileService.UploadBannerPicture(UserId, bannerPicture)); + } + + [HttpGet("GetProfileByUserId")] + [SwaggerResponse(200, Type = typeof(ResponseEnvelope<ProfileDTO>))] + [SwaggerResponse(400, Type = typeof(ResponseEnvelope<BadRequestObjectResult>))] + [AllowAnonymous] + public async Task<ActionResult<ResponseEnvelope<ProfileDTO>>> GetProfileByUserId(int userId) + { + try + { + return Ok(await _profileService.GetProfileForUser(userId)); + } + catch (Exception ex) + { + return BadRequest(ex.Message); + } + } + + [HttpPost("GetProfilesByUserIds")] + [SwaggerResponse(200, Type = typeof(ResponseEnvelope<List<ProfileDTO>>))] + [SwaggerResponse(400, Type = typeof(ResponseEnvelope<BadRequestObjectResult>))] + [AllowAnonymous] + public async Task<ActionResult<ResponseEnvelope<List<ProfileDTO>>>> GetProfilesByUserIds(List<int> userIds) + { + try + { + return Ok(await _profileService.GetProfilesByUserIds(userIds)); + } + catch (Exception ex) + { + return BadRequest(ex.Message); + } } } \ No newline at end of file diff --git a/Models/DTOs/ProfileDTO.cs b/Models/DTOs/ProfileDTO.cs index cc4627e6320b7a4bf96a596ece65667e6081e77f..a50dac3c2101f15c37c49d1be9ef0fba34bc92ca 100644 --- a/Models/DTOs/ProfileDTO.cs +++ b/Models/DTOs/ProfileDTO.cs @@ -7,6 +7,7 @@ public class ProfileDTO : DefaultGuidDTO public int? UserId { get; set; } public string? Biography { get; set; } + public string? ProfilePictureUrl { get; set; } public string? BannerUrl { get; set; } public string? Gender { get; set; } diff --git a/Models/Entities/Profile.cs b/Models/Entities/Profile.cs index d49e7cd9f04640b41088533a766e9ab2a88b6d84..f9723365352a776fbd2d968f05c3ffe5d4883956 100644 --- a/Models/Entities/Profile.cs +++ b/Models/Entities/Profile.cs @@ -7,6 +7,7 @@ public class Profile : DefaultGuidEntity public int? UserId { get; set; } public string? Biography { get; set; } + public string? ProfilePictureUrl { get; set; } public string? BannerUrl { get; set; } public string? Gender { get; set; } diff --git a/Repositories/ProfileRepository.cs b/Repositories/ProfileRepository.cs index 52ddf97be8406c2e602b6825f95dfaed8fec6b57..f62ae8a7abbb55c2de5dd18501d2e7f9eedc8143 100644 --- a/Repositories/ProfileRepository.cs +++ b/Repositories/ProfileRepository.cs @@ -9,24 +9,24 @@ public interface IProfileRepository { Task<Profile> CreateProfileAsync(Profile profile); Task<Profile?> GetProfileAsync(int userId); + Task<List<Profile>> GetProfilesByUserIds(List<int> userIds); Task<Profile> UpdateProfileAsync(int userId, string profileId, Profile profile); Task DeleteProfileAsync(int userId, string profileId); - Task<IEnumerable<Profile?>> GetProfilesByMoodAsync(string mood); } public class ProfileRepository : IProfileRepository { - private readonly Container? _profileDetailsContainer; + private readonly Container? _profileContainer; public ProfileRepository(ProfileDbContext context) { - _profileDetailsContainer = context.Profile; + _profileContainer = context.Profile; } public async Task<Profile> CreateProfileAsync(Profile profile) { var response = - await _profileDetailsContainer?.CreateItemAsync(profile, + await _profileContainer?.CreateItemAsync(profile, new PartitionKey(profile.UserId.GetValueOrDefault()))!; return response.Resource; } @@ -38,7 +38,7 @@ public class ProfileRepository : IProfileRepository var query = new QueryDefinition("SELECT * FROM c WHERE c.UserId = @UserId") .WithParameter("@UserId", userId); - var iterator = _profileDetailsContainer?.GetItemQueryIterator<Profile>(query); + var iterator = _profileContainer?.GetItemQueryIterator<Profile>(query); if (iterator is {HasMoreResults: false}) return null; var response = await iterator.ReadNextAsync(); @@ -50,31 +50,41 @@ public class ProfileRepository : IProfileRepository } } - public async Task<Profile> UpdateProfileAsync(int userId, string profileId, Profile profile) + public async Task<List<Profile>> GetProfilesByUserIds(List<int> userIds) { - var response = await _profileDetailsContainer.ReplaceItemAsync(profile, profileId, new PartitionKey(userId)); - return response.Resource; - } + try + { + var userIdsString = string.Join(",", userIds); + var queryString = $"SELECT * FROM c WHERE c.UserId IN ({userIdsString})"; + var query = new QueryDefinition(queryString); - public async Task DeleteProfileAsync(int userId, string profileId) - { - await _profileDetailsContainer.DeleteItemAsync<Profile>(profileId, new PartitionKey(userId)); - } + var iterator = _profileContainer?.GetItemQueryIterator<Profile>(query); - public async Task<IEnumerable<Profile?>> GetProfilesByMoodAsync(string mood) - { - var query = new QueryDefinition("SELECT * FROM c WHERE c.Mood = @mood") - .WithParameter("@mood", mood); + if (iterator is {HasMoreResults: false}) return new List<Profile>(); + var profiles = new List<Profile>(); - var iterator = _profileDetailsContainer?.GetItemQueryIterator<Profile>(query); + while (iterator is {HasMoreResults: true}) + { + var response = await iterator.ReadNextAsync(); + profiles.AddRange(response); + } - var profiles = new List<Profile>(); - while (iterator.HasMoreResults) + return profiles; + } + catch (CosmosException ex) when (ex.StatusCode == HttpStatusCode.NotFound) { - var response = await iterator.ReadNextAsync(); - profiles.AddRange(response); + return new List<Profile>(); } + } + + public async Task<Profile> UpdateProfileAsync(int userId, string profileId, Profile profile) + { + var response = await _profileContainer.ReplaceItemAsync(profile, profileId, new PartitionKey(userId)); + return response.Resource; + } - return profiles; + public async Task DeleteProfileAsync(int userId, string profileId) + { + await _profileContainer.DeleteItemAsync<Profile>(profileId, new PartitionKey(userId)); } } \ No newline at end of file diff --git a/Services/ProfileService.cs b/Services/ProfileService.cs index 3c741e3b524cf6ebb8a678fa2b63109c3077fe5a..9a21ec05f7ffabe7273961c306d78d6ef530dd22 100644 --- a/Services/ProfileService.cs +++ b/Services/ProfileService.cs @@ -10,7 +10,9 @@ using Profile = Models.Entities.Profile; public interface IProfileService { Task<ProfileDTO> GetProfileForUser(int userId); + Task<List<ProfileDTO>> GetProfilesByUserIds(List<int> userIds); Task<ProfileDTO> CreateOrUpdateProfile(ProfileDTO profile, int userId); + Task<ProfileDTO> UploadProfilePicture(int userId, IFormFile profilePicture); Task<ProfileDTO> UploadBannerPicture(int userId, IFormFile bannerPicture); } @@ -32,6 +34,9 @@ public class ProfileService : IProfileService var profile = await _profileRepository.GetProfileAsync(userId); if (profile == null) throw new ProfileException("Profile does not exist."); + if (!string.IsNullOrWhiteSpace(profile.ProfilePictureUrl)) + profile.ProfilePictureUrl += + _storageService.GetSasForFile(Constants.AzureBlobContainer.ProfilePictures, profile.ProfilePictureUrl); if (!string.IsNullOrWhiteSpace(profile.BannerUrl)) profile.BannerUrl += _storageService.GetSasForFile(Constants.AzureBlobContainer.BannerPictures, profile.BannerUrl); @@ -47,6 +52,13 @@ public class ProfileService : IProfileService var age = DateTimeOffset.Now.Year - profile.DoB.GetValueOrDefault().Year; if (age is <= 16 or >= 100) throw new ProfileException("Please enter valid age."); + if (!string.IsNullOrWhiteSpace(profile.ProfilePictureUrl)) + { + var profilePictureUri = new Uri(profile.ProfilePictureUrl); + if (!string.IsNullOrWhiteSpace(profilePictureUri.Query)) + profile.ProfilePictureUrl = profile.ProfilePictureUrl.Replace(profilePictureUri.Query, ""); + } + if (!string.IsNullOrWhiteSpace(profile.BannerUrl)) { var bannerUri = new Uri(profile.BannerUrl); @@ -55,6 +67,15 @@ public class ProfileService : IProfileService } var record = _mapper.Map<Profile>(profile); + if (profile.FavouriteShows != null && profile.FavouriteShows.Count != 0) + { + var builder = new StringBuilder(); + foreach (var show in profile.FavouriteShows) + builder.Append($"{show},"); + + record.FavouriteShows = builder.ToString(); + } + var existing = await _profileRepository.GetProfileAsync(userId); if (existing == null) { @@ -68,14 +89,9 @@ public class ProfileService : IProfileService record = await _profileRepository.UpdateProfileAsync(userId, record.Id.ToString(), record); } - if (profile.FavouriteShows != null && profile.FavouriteShows.Count != 0) - { - var builder = new StringBuilder(); - foreach (var show in profile.FavouriteShows) - builder.Append($"{show},"); - - record.FavouriteShows = builder.ToString(); - } + if (!string.IsNullOrWhiteSpace(record.ProfilePictureUrl)) + record.ProfilePictureUrl += + _storageService.GetSasForFile(Constants.AzureBlobContainer.ProfilePictures, record.ProfilePictureUrl); if (!string.IsNullOrWhiteSpace(record.BannerUrl)) record.BannerUrl += @@ -84,6 +100,31 @@ public class ProfileService : IProfileService return _mapper.Map<ProfileDTO>(record); } + public async Task<ProfileDTO> UploadProfilePicture(int userId, IFormFile profilePicture) + { + var profile = await _profileRepository.GetProfileAsync(userId); + using var ms = new MemoryStream(); + await profilePicture.CopyToAsync(ms); + ms.Position = 0; + var fileName = $"{userId}/{profilePicture.Name}"; + var image = await _storageService.SaveImageAsJpgBlob(Constants.AzureBlobContainer.ProfilePictures, fileName, + ms); + if (profile == null) + { + profile = new Profile {ProfilePictureUrl = image.ToString(), Id = Guid.NewGuid(), UserId = userId}; + profile = await _profileRepository.CreateProfileAsync(profile); + } + else + { + profile.ProfilePictureUrl = image.ToString(); + profile = await _profileRepository.UpdateProfileAsync(userId, profile.Id.ToString(), profile); + } + + profile.ProfilePictureUrl += + _storageService.GetSasForFile(Constants.AzureBlobContainer.ProfilePictures, image.ToString()); + return _mapper.Map<ProfileDTO>(profile); + } + public async Task<ProfileDTO> UploadBannerPicture(int userId, IFormFile bannerPicture) { var profile = await _profileRepository.GetProfileAsync(userId); @@ -94,7 +135,7 @@ public class ProfileService : IProfileService var image = await _storageService.SaveImageAsJpgBlob(Constants.AzureBlobContainer.BannerPictures, fileName, ms); if (profile == null) { - profile = new Profile {BannerUrl = image.ToString(), UserId = userId}; + profile = new Profile {BannerUrl = image.ToString(), Id = Guid.NewGuid(), UserId = userId}; profile = await _profileRepository.CreateProfileAsync(profile); } else @@ -107,4 +148,23 @@ public class ProfileService : IProfileService _storageService.GetSasForFile(Constants.AzureBlobContainer.BannerPictures, image.ToString()); return _mapper.Map<ProfileDTO>(profile); } + + public async Task<List<ProfileDTO>> GetProfilesByUserIds(List<int> userIds) + { + var profiles = await _profileRepository.GetProfilesByUserIds(userIds); + var mapped = _mapper.ProjectTo<ProfileDTO>(profiles.AsQueryable()).ToList(); + if (profiles.Count == 0) return mapped; + foreach (var profile in mapped) + { + if (!string.IsNullOrWhiteSpace(profile.ProfilePictureUrl)) + profile.ProfilePictureUrl += + _storageService.GetSasForFile(Constants.AzureBlobContainer.ProfilePictures, + profile.ProfilePictureUrl); + if (!string.IsNullOrWhiteSpace(profile.BannerUrl)) + profile.BannerUrl += + _storageService.GetSasForFile(Constants.AzureBlobContainer.BannerPictures, profile.BannerUrl); + } + + return mapped; + } } \ No newline at end of file