From 602496ec08eb6333debd0fee95d2c1089f8a7229 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ji=C5=99=C3=AD=20Vrabec?= Date: Mon, 11 Aug 2025 22:08:13 +0200 Subject: [PATCH] Add user profile endpoints --- .../UserProfile/UserProfileAdminStatusPut.cs | 6 - .../ApiModels/UserProfile/UserProfileGet.cs | 23 ++++ .../ApiModels/UserProfile/UserProfilePut.cs | 7 +- .../UserProfile/UserProfileSelfPut.cs | 9 ++ .../Controllers/UserProfileController.cs | 33 ++++-- DrinkRateAPI/DbEntities/DbUserProfile.cs | 1 - DrinkRateAPI/Services/UserProfileService.cs | 109 ++++++++++++++---- 7 files changed, 147 insertions(+), 41 deletions(-) delete mode 100644 DrinkRateAPI/ApiModels/UserProfile/UserProfileAdminStatusPut.cs create mode 100644 DrinkRateAPI/ApiModels/UserProfile/UserProfileSelfPut.cs diff --git a/DrinkRateAPI/ApiModels/UserProfile/UserProfileAdminStatusPut.cs b/DrinkRateAPI/ApiModels/UserProfile/UserProfileAdminStatusPut.cs deleted file mode 100644 index b7dc8c7..0000000 --- a/DrinkRateAPI/ApiModels/UserProfile/UserProfileAdminStatusPut.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace DrinkRateAPI.ApiModels.UserProfile; - -public class UserProfileAdminStatusPut -{ - public bool ChangeStatusTo { get; set; } -} \ No newline at end of file diff --git a/DrinkRateAPI/ApiModels/UserProfile/UserProfileGet.cs b/DrinkRateAPI/ApiModels/UserProfile/UserProfileGet.cs index ed241cf..5f21fec 100644 --- a/DrinkRateAPI/ApiModels/UserProfile/UserProfileGet.cs +++ b/DrinkRateAPI/ApiModels/UserProfile/UserProfileGet.cs @@ -2,5 +2,28 @@ namespace DrinkRateAPI.ApiModels.UserProfile; public class UserProfileGet { + /// + /// User profile ID + /// + public string Id { get; set; } + /// + /// User profile name + /// + public string UserName { get; set; } + + /// + /// Is user admin + /// + public bool IsAdmin { get; set; } + + /// + /// Is user deleted + /// + public bool IsDeleted { get; set; } + + /// + /// Applicaton user ID of the user profile + /// + public string ApplicationUserId { get; set; } } \ No newline at end of file diff --git a/DrinkRateAPI/ApiModels/UserProfile/UserProfilePut.cs b/DrinkRateAPI/ApiModels/UserProfile/UserProfilePut.cs index 020071d..ecf2fd4 100644 --- a/DrinkRateAPI/ApiModels/UserProfile/UserProfilePut.cs +++ b/DrinkRateAPI/ApiModels/UserProfile/UserProfilePut.cs @@ -1,6 +1,9 @@ namespace DrinkRateAPI.ApiModels.UserProfile; -public class UserProfilePut +public class UserProfilePut : UserProfileSelfPut { - public string UserName { get; set; } + /// + /// Is user admin + /// + public bool? IsAdmin { get; set; } } \ No newline at end of file diff --git a/DrinkRateAPI/ApiModels/UserProfile/UserProfileSelfPut.cs b/DrinkRateAPI/ApiModels/UserProfile/UserProfileSelfPut.cs new file mode 100644 index 0000000..8ca7d1b --- /dev/null +++ b/DrinkRateAPI/ApiModels/UserProfile/UserProfileSelfPut.cs @@ -0,0 +1,9 @@ +namespace DrinkRateAPI.ApiModels.UserProfile; + +public class UserProfileSelfPut +{ + /// + /// User profile name + /// + public string? UserName { get; set; } +} \ No newline at end of file diff --git a/DrinkRateAPI/Controllers/UserProfileController.cs b/DrinkRateAPI/Controllers/UserProfileController.cs index 7c4cfa0..eb527aa 100644 --- a/DrinkRateAPI/Controllers/UserProfileController.cs +++ b/DrinkRateAPI/Controllers/UserProfileController.cs @@ -1,4 +1,3 @@ -using System.Security.Claims; using DrinkRateAPI.ApiModels.UserProfile; using DrinkRateAPI.Services; using Microsoft.AspNetCore.Authorization; @@ -10,30 +9,40 @@ namespace DrinkRateAPI.Controllers; [Route("userProfile")] public class UserProfileController : ControllerBase { - private readonly ILogger _logger; private readonly UserProfileService _userProfileService; - public UserProfileController(ILogger logger, UserProfileService userProfileService) + public UserProfileController(UserProfileService userProfileService) { - _logger = logger; _userProfileService = userProfileService; } [HttpPut] - public UserProfileGet PutUserProfile([FromBody] UserProfilePut userProfile) + [Produces("application/json")] + public async Task PutUserProfileSelf([FromBody] UserProfileSelfPut userProfile) { - throw new ApplicationException(); - var x = User.FindFirst(ClaimTypes.NameIdentifier)?.Value; //HttpContext.User.Identities.First(); - return new(); + return await _userProfileService.PutUserProfileSelfAsync(User, userProfile); } - [HttpPut("{userId}/adminStatus")] + [HttpGet] + [Produces("application/json")] + public async Task GetUserProfileSelf() + { + return await _userProfileService.GetUserProfileSelfAsync(User); + } + + [HttpPut("{userId}")] [Authorize(Policy = "AdminOnly")] [Produces("application/json")] - public async Task PutUserAdminStatus(string userId, [FromBody] UserProfileAdminStatusPut body) + public async Task PutUserProfile(string userId, [FromBody] UserProfilePut userProfile) { - var changedProfile = await _userProfileService.PutUserProfileAdminStatusAsync(userId, body.ChangeStatusTo); + return await _userProfileService.PutUserProfileAsync(User, userProfile, userId); + } - return Ok(changedProfile); + [HttpGet("{userId}")] + [Authorize(Policy = "AdminOnly")] + [Produces("application/json")] + public async Task GetUserProfile(string userId) + { + return await _userProfileService.GetUserProfileAsync(User, userId); } } \ No newline at end of file diff --git a/DrinkRateAPI/DbEntities/DbUserProfile.cs b/DrinkRateAPI/DbEntities/DbUserProfile.cs index bde7fa5..55fa050 100644 --- a/DrinkRateAPI/DbEntities/DbUserProfile.cs +++ b/DrinkRateAPI/DbEntities/DbUserProfile.cs @@ -20,6 +20,5 @@ public class DbUserProfile : DbEntityWithHistory public bool IsDeleted { get; set; } public Guid ApplicationUserId { get; set; } - public virtual DbApplicationUser ApplicationUser { get; set; } } \ No newline at end of file diff --git a/DrinkRateAPI/Services/UserProfileService.cs b/DrinkRateAPI/Services/UserProfileService.cs index 8726f93..ad8c03d 100644 --- a/DrinkRateAPI/Services/UserProfileService.cs +++ b/DrinkRateAPI/Services/UserProfileService.cs @@ -2,12 +2,8 @@ using System.Security.Claims; using DrinkRateAPI.ApiModels.UserProfile; using DrinkRateAPI.Contexts; using DrinkRateAPI.DbEntities; -using DrinkRateAPI.DbEntities; using DrinkRateAPI.Exceptions; using Microsoft.EntityFrameworkCore; -using Microsoft.AspNetCore.Authorization; -using Microsoft.AspNetCore.Mvc; - namespace DrinkRateAPI.Services; @@ -21,29 +17,102 @@ public class UserProfileService(ApplicationDbContext context, ApplicationUserSer return userProfile.IsAdmin; } - public async Task PutUserProfileAdminStatusAsync(string userId, bool changeStatusTo) + public async Task PutUserProfileSelfAsync(ClaimsPrincipal identity, UserProfileSelfPut userProfileSelfPut) { - var userProfile = GetUserProfileById(userId); - userProfile.IsAdmin = changeStatusTo; + var authenticatedUser = await _applicationUserService.UserProfileByApplicationUserAsync(identity); + + var userId = authenticatedUser.Id.ToString(); + await PutUserProfile(userProfileSelfPut, userId, false); + + return await GetUserProfile(userId); + } + + public async Task GetUserProfileSelfAsync(ClaimsPrincipal identity) + { + var authenticatedUser = await _applicationUserService.UserProfileByApplicationUserAsync(identity); + + var userId = authenticatedUser.Id.ToString(); + + return await GetUserProfile(userId); + } + + public async Task PutUserProfileAsync(ClaimsPrincipal identity, UserProfilePut userProfilePut, string userId) + { + var authenticatedUser = await _applicationUserService.UserProfileByApplicationUserAsync(identity); + + if (authenticatedUser.Id.ToString() == userId) + { + // Prevent admin de-admining him/herself + await PutUserProfile(userProfilePut, userId, false); + } + else + { + await PutUserProfile(userProfilePut, userId, IsUserProfileAdmin(authenticatedUser)); + } + + return await GetUserProfile(userId); + } + + public async Task GetUserProfileAsync(ClaimsPrincipal identity, string userId) + { + var authenticatedUser = await _applicationUserService.UserProfileByApplicationUserAsync(identity); + + return await GetUserProfile(userId); + } + + private async Task PutUserProfile(TUserProfilePut userProfilePut, string userId, bool byAdmin) where TUserProfilePut : UserProfileSelfPut + { + var userProfile = await GetUserProfileById(userId); + + if (!string.IsNullOrEmpty(userProfilePut.UserName) && userProfile.UserName != userProfilePut.UserName) + { + var userByName = await TryGetUserProfileByUserName(userProfilePut.UserName); + if (userByName == null) + { + userProfile.UserName = userProfilePut.UserName; + } + else + { + throw new BadRequestException($"User with username {userProfilePut.UserName} already exists"); + } + } + + if (byAdmin && userProfilePut is UserProfilePut adminPut && adminPut.IsAdmin != null) + { + userProfile.IsAdmin = (bool)adminPut.IsAdmin; + } + _context.UserProfiles.Update(userProfile); await _context.SaveChangesAsync(); - - return userProfile; - } - - public async Task PutUserProfileAsync(UserProfilePut userProfile, ClaimsPrincipal identity) - { - var profile = _applicationUserService.UserProfileByApplicationUserAsync(identity); - - - - return new(); } - public DbUserProfile GetUserProfileById(string userId) + private async Task GetUserProfile(string userId) { - var userProfile = _context.UserProfiles.FirstOrDefault(x => x.Id.ToString() == userId); + var userProfile = await GetUserProfileById(userId); + + var userProfileGet = new UserProfileGet + { + Id = userProfile.Id.ToString(), + UserName = userProfile.UserName, + IsAdmin = userProfile.IsAdmin, + IsDeleted = userProfile.IsDeleted, + ApplicationUserId = userProfile.ApplicationUserId.ToString(), + }; + + return userProfileGet; + } + + private async Task GetUserProfileById(string userId) + { + var userProfile = await _context.UserProfiles.FirstOrDefaultAsync(x => x.Id.ToString() == userId); return userProfile ?? throw new NotFoundException(); } + + private async Task TryGetUserProfileByUserName(string userName) + { + var userProfile = await _context.UserProfiles.FirstOrDefaultAsync(x => x.UserName == userName); + + return userProfile; + } } \ No newline at end of file