From 310e05545f18301a72f750be98cd7dd5fc8db5a0 Mon Sep 17 00:00:00 2001 From: LD-Reborn Date: Sat, 25 Oct 2025 22:15:08 +0200 Subject: [PATCH] Implemented users update button in frontend --- src/Controllers/UsersController.cs | 96 ++++---- src/Helpers/UsersHelper.cs | 44 ++++ src/Models/UserModels.cs | 12 +- src/Models/UsersRequestModels.cs | 14 +- src/Models/UsersResponseModels.cs | 13 ++ src/Views/Home/Users.cshtml | 355 ++++++++++++++++++++++++++--- 6 files changed, 458 insertions(+), 76 deletions(-) create mode 100644 src/Helpers/UsersHelper.cs create mode 100644 src/Models/UsersResponseModels.cs diff --git a/src/Controllers/UsersController.cs b/src/Controllers/UsersController.cs index 96cb3b4..ac43bd4 100644 --- a/src/Controllers/UsersController.cs +++ b/src/Controllers/UsersController.cs @@ -1,4 +1,3 @@ -// Controllers/UsersController.cs using Berufsschule_HAM.Services; using Microsoft.AspNetCore.Mvc; using Novell.Directory.Ldap; @@ -8,6 +7,7 @@ using System.Security.Cryptography; using System.Text; using Microsoft.AspNetCore.Authorization; using System.Text.Json; +using System.Buffers.Text; [Authorize(Roles = "CanManageUsers")] [Route("[controller]")] @@ -66,7 +66,7 @@ public class UsersController : Controller try { jpegPhoto ??= Convert.ToBase64String(System.IO.File.ReadAllBytes("wwwroot/user_default.jpeg")); // TODO: cleanup - make this a config setting - uid ??= sn.ToLower() + cn.ToLower(); + uid ??= UsersHelper.CreateUsername(cn, sn); title ??= ""; description ??= "{}"; if (!userPassword.StartsWith('{')) @@ -98,53 +98,67 @@ public class UsersController : Controller } [HttpPost("Update")] - public async Task Update([FromBody] UsersModifyRequestModel requestModel) + public async Task Update([FromBody] UsersModifyRequestModel requestModel) { if (requestModel is null) { _logger.LogError("Unable to update a user because the UsersModifyRequestModel is null"); - return false; + return new() { Success = false }; } - string uid = requestModel.uid; - UserModel? user = null; - if (requestModel.Cn is not null) + try { - await _ldap.UpdateUser(uid, "cn", requestModel.Cn); - user ??= await _ldap.GetUserByUidAsync(uid); - string newUid = user.Sn?.ToLower() + requestModel.Cn.ToLower(); - await _ldap.UpdateUser(uid, "uid", newUid); - uid = newUid; - } - if (requestModel.Sn is not null) + string uid = requestModel.Uid; + UserModel? user = null; + if (requestModel.NewUid is not null && requestModel.NewUid.Length > 0) + { + await _ldap.UpdateUser(uid, "uid", requestModel.NewUid); + uid = requestModel.NewUid; + } + if (requestModel.Title is not null) + { + await _ldap.UpdateUser(uid, "title", requestModel.Title); + } + if (requestModel.Description is not null) + { + await _ldap.UpdateUser(uid, "description", JsonSerializer.Serialize(requestModel.Description)); + } + if (requestModel.JpegPhoto is not null && requestModel.JpegPhoto.Length > 0) + { + await _ldap.UpdateUser(uid, "jpegPhoto", requestModel.JpegPhoto); + } + if (requestModel.UserPassword is not null && requestModel.UserPassword.Length > 0) + { + await _ldap.UpdateUser(uid, "userPassword", "{SHA256}" + Convert.ToBase64String(SHA256.HashData(Encoding.UTF8.GetBytes(requestModel.UserPassword)))); + } + + string newUid = uid; + if (requestModel.Cn is not null) + { + await _ldap.UpdateUser(uid, "cn", requestModel.Cn); + user ??= await _ldap.GetUserByUidAsync(uid); + newUid = UsersHelper.CreateUsername(requestModel.Cn, user.Sn ?? ""); + + } + if (requestModel.Sn is not null) + { + await _ldap.UpdateUser(uid, "sn", requestModel.Sn); + user ??= await _ldap.GetUserByUidAsync(uid); + newUid = UsersHelper.CreateUsername(user.Cn ?? "", requestModel.Sn); + } + if (newUid.Length == 0) + { + throw new Exception("Username cannot be empty"); + } + if (newUid != uid) + { + await _ldap.UpdateUser(uid, "uid", newUid); + uid = newUid; + } + return new() { Success = true, NewUid = uid }; + } catch (Exception ex) { - await _ldap.UpdateUser(uid, "sn", requestModel.Sn); - user ??= await _ldap.GetUserByUidAsync(uid); - string newUid = requestModel.Sn.ToLower() + user.Cn?.ToLower(); - await _ldap.UpdateUser(uid, "uid", newUid); - uid = newUid; + return new() { Success = false, Exception = ex.Message }; } - if (requestModel.NewUid is not null) - { - await _ldap.UpdateUser(uid, "uid", requestModel.NewUid); - uid = requestModel.NewUid; - } - if (requestModel.Title is not null) - { - await _ldap.UpdateUser(uid, "title", requestModel.Title); - } - if (requestModel.Description is not null) - { - await _ldap.UpdateUser(uid, "description", requestModel.Description); - } - if (requestModel.JpegPhoto is not null) - { - await _ldap.UpdateUser(uid, "jpegPhoto", requestModel.JpegPhoto); - } - if (requestModel.UserPassword is not null) - { - await _ldap.UpdateUser(uid, "userPassword", requestModel.UserPassword); - } - return true; } [HttpPost("AddGroup")] diff --git a/src/Helpers/UsersHelper.cs b/src/Helpers/UsersHelper.cs new file mode 100644 index 0000000..7f16c4e --- /dev/null +++ b/src/Helpers/UsersHelper.cs @@ -0,0 +1,44 @@ +using System.Globalization; +using System.Text; +using System.Text.RegularExpressions; +namespace Berufsschule_HAM.Helpers; + +public static partial class UsersHelper +{ + public static string CreateUsername(string givenName, string surname) + { + if (string.IsNullOrWhiteSpace(givenName) || string.IsNullOrWhiteSpace(surname)) + throw new ArgumentException("Given name and surname must not be empty."); + + string combined = (surname + givenName).ToLowerInvariant(); + + // Normalize to decompose accents (e.g., é -> e + ́) + string normalized = combined.Normalize(NormalizationForm.FormD); + + // Remove diacritics + var sb = new StringBuilder(); + foreach (var c in normalized) + { + var category = CharUnicodeInfo.GetUnicodeCategory(c); + if (category != UnicodeCategory.NonSpacingMark) + sb.Append(c); + } + + string cleaned = sb.ToString(); + + // Replace German ligatures etc. + cleaned = cleaned + .Replace("ß", "ss") + .Replace("æ", "ae") + .Replace("œ", "oe") + .Replace("ø", "o"); + + // Remove everything not a-z + cleaned = AtoZ().Replace(cleaned, ""); + + return cleaned; + } + + [GeneratedRegex("[^a-z]")] + private static partial Regex AtoZ(); +} \ No newline at end of file diff --git a/src/Models/UserModels.cs b/src/Models/UserModels.cs index 7fe6616..52cd777 100644 --- a/src/Models/UserModels.cs +++ b/src/Models/UserModels.cs @@ -1,4 +1,5 @@ using System.Text.Json; +using System.Text.Json.Serialization; namespace Berufsschule_HAM.Models; @@ -26,17 +27,24 @@ public class UserModel public class UserDescription { + [JsonPropertyName("BirthDate")] public required string BirthDate { get; set; } + [JsonPropertyName("Address")] public required UserAddress Address { get; set; } + [JsonPropertyName("Workplace")] public required string Workplace { get; set; } + [JsonPropertyName("Groups")] public List? Groups { get; set; } } public class UserAddress { + [JsonPropertyName("City")] public string? City { get; set; } + [JsonPropertyName("Street")] public string? Street { get; set; } - public string? StreetNr { get; set; } + [JsonPropertyName("StreetNr")] + public int? StreetNr { get; set; } } public class UserAuthenticationResult @@ -61,5 +69,5 @@ public class UserTableViewModel public required string Title { get; set; } public required string Name { get; set; } public required string Surname { get; set; } - public required string Workplace { get; set; } + public required UserDescription Description { get; set; } } \ No newline at end of file diff --git a/src/Models/UsersRequestModels.cs b/src/Models/UsersRequestModels.cs index 0abfe5a..9b98e63 100644 --- a/src/Models/UsersRequestModels.cs +++ b/src/Models/UsersRequestModels.cs @@ -1,3 +1,5 @@ +using System.Text.Json.Serialization; + namespace Berufsschule_HAM.Models; public class UsersIndexRequestModel @@ -13,13 +15,21 @@ public class UsersIndexRequestModel public class UsersModifyRequestModel { - public required string uid { get; set; } + [JsonPropertyName("Uid")] + public required string Uid { get; set; } + [JsonPropertyName("NewUid")] public string? NewUid { get; set; } = null; + [JsonPropertyName("Cn")] public string? Cn { get; set; } = null; + [JsonPropertyName("Sn")] public string? Sn { get; set; } = null; + [JsonPropertyName("Title")] public string? Title { get; set; } = null; - public string? Description { get; set; } = null; + [JsonPropertyName("Description")] + public UserDescription? Description { get; set; } = null; + [JsonPropertyName("JpegPhoto")] public string? JpegPhoto { get; set; } = null; + [JsonPropertyName("UserPassword")] public string? UserPassword { get; set; } = null; } diff --git a/src/Models/UsersResponseModels.cs b/src/Models/UsersResponseModels.cs new file mode 100644 index 0000000..0df701d --- /dev/null +++ b/src/Models/UsersResponseModels.cs @@ -0,0 +1,13 @@ +using System.Text.Json.Serialization; + +namespace Berufsschule_HAM.Models; + +public class UsersUpdateRequestModel +{ + [JsonPropertyName("Success")] + public required bool Success { get; set; } + [JsonPropertyName("Exception")] + public string? Exception { get; set; } + [JsonPropertyName("NewUid")] + public string? NewUid { get; set; } +} \ No newline at end of file diff --git a/src/Views/Home/Users.cshtml b/src/Views/Home/Users.cshtml index c526b36..84e5392 100644 --- a/src/Views/Home/Users.cshtml +++ b/src/Views/Home/Users.cshtml @@ -1,6 +1,8 @@ @using Microsoft.AspNetCore.Mvc.Localization @using Berufsschule_HAM.Models @using System.Buffers.Text +@using System.Text.Json; +@using System.Text.Json.Serialization; @model UsersIndexViewModel @inject IViewLocalizer T @{ @@ -42,15 +44,29 @@ @userTableViewModel.Title @userTableViewModel.Name @userTableViewModel.Surname - @userTableViewModel.Workplace + @userTableViewModel.Description.Workplace
- +
@@ -84,6 +100,34 @@ + + + + + + +