aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Timeline/Controllers/UserController.cs30
-rw-r--r--Timeline/Models/Http/User.cs10
-rw-r--r--Timeline/Services/UserService.cs57
3 files changed, 96 insertions, 1 deletions
diff --git a/Timeline/Controllers/UserController.cs b/Timeline/Controllers/UserController.cs
index d38f96e1..bd13f0a3 100644
--- a/Timeline/Controllers/UserController.cs
+++ b/Timeline/Controllers/UserController.cs
@@ -22,7 +22,10 @@ namespace Timeline.Controllers
public const int Patch_NotExist = -3001;
- public const int ChangePassword_BadOldPassword = -4001;
+ public const int ChangeUsername_NotExist = -4001;
+ public const int ChangeUsername_AlreadyExist = -4002;
+
+ public const int ChangePassword_BadOldPassword = -5001;
}
private readonly ILogger<UserController> _logger;
@@ -108,6 +111,31 @@ namespace Timeline.Controllers
}
}
+ [HttpPost("userop/changeusername"), AdminAuthorize]
+ public async Task<IActionResult> ChangeUsername([FromBody] ChangeUsernameRequest request)
+ {
+ try
+ {
+ await _userService.ChangeUsername(request.OldUsername, request.NewUsername);
+ _logger.LogInformation(FormatLogMessage("A user changed username.",
+ Pair("Old Username", request.OldUsername), Pair("New Username", request.NewUsername)));
+ return Ok();
+ }
+ catch (UserNotExistException e)
+ {
+ _logger.LogInformation(e, FormatLogMessage("Attempt to change a non-existent user's username failed.",
+ Pair("Old Username", request.OldUsername), Pair("New Username", request.NewUsername)));
+ return BadRequest(new CommonResponse(ErrorCodes.ChangeUsername_NotExist, $"The user {request.OldUsername} does not exist."));
+ }
+ catch (UserAlreadyExistException e)
+ {
+ _logger.LogInformation(e, FormatLogMessage("Attempt to change a user's username to a existent one failed.",
+ Pair("Old Username", request.OldUsername), Pair("New Username", request.NewUsername)));
+ return BadRequest(new CommonResponse(ErrorCodes.ChangeUsername_AlreadyExist, $"The user {request.NewUsername} already exists."));
+ }
+ // there is no need to catch bad format exception because it is already checked in model validation.
+ }
+
[HttpPost("userop/changepassword"), Authorize]
public async Task<IActionResult> ChangePassword([FromBody] ChangePasswordRequest request)
{
diff --git a/Timeline/Models/Http/User.cs b/Timeline/Models/Http/User.cs
index d45543fb..4308a19c 100644
--- a/Timeline/Models/Http/User.cs
+++ b/Timeline/Models/Http/User.cs
@@ -1,4 +1,5 @@
using System.ComponentModel.DataAnnotations;
+using Timeline.Models.Validation;
namespace Timeline.Models.Http
{
@@ -16,6 +17,15 @@ namespace Timeline.Models.Http
public bool? Administrator { get; set; }
}
+ public class ChangeUsernameRequest
+ {
+ [Required]
+ public string OldUsername { get; set; }
+
+ [Required, ValidateWith(typeof(UsernameValidator))]
+ public string NewUsername { get; set; }
+ }
+
public class ChangePasswordRequest
{
[Required]
diff --git a/Timeline/Services/UserService.cs b/Timeline/Services/UserService.cs
index 27145683..96c3e256 100644
--- a/Timeline/Services/UserService.cs
+++ b/Timeline/Services/UserService.cs
@@ -121,6 +121,26 @@ namespace Timeline.Services
public string Username { get; private set; }
}
+
+ /// <summary>
+ /// Thrown when the user already exists.
+ /// </summary>
+ [Serializable]
+ public class UserAlreadyExistException : Exception
+ {
+ public UserAlreadyExistException(string username) : base($"User {username} already exists.") { Username = username; }
+ public UserAlreadyExistException(string username, string message) : base(message) { Username = username; }
+ public UserAlreadyExistException(string message, Exception inner) : base(message, inner) { }
+ protected UserAlreadyExistException(
+ System.Runtime.Serialization.SerializationInfo info,
+ System.Runtime.Serialization.StreamingContext context) : base(info, context) { }
+
+ /// <summary>
+ /// The username that already exists.
+ /// </summary>
+ public string Username { get; set; }
+ }
+
public interface IUserService
{
/// <summary>
@@ -204,6 +224,17 @@ namespace Timeline.Services
/// <exception cref="UserNotExistException">Thrown if the user with given username does not exist.</exception>
/// <exception cref="BadPasswordException">Thrown if the old password is wrong.</exception>
Task ChangePassword(string username, string oldPassword, string newPassword);
+
+ /// <summary>
+ /// Change a user's username.
+ /// </summary>
+ /// <param name="oldUsername">The user's old username.</param>
+ /// <param name="newUsername">The new username.</param>
+ /// <exception cref="ArgumentException">Thrown if <paramref name="oldUsername"/> or <paramref name="newUsername"/> is null or empty.</exception>
+ /// <exception cref="UserNotExistException">Thrown if the user with old username does not exist.</exception>
+ /// <exception cref="UsernameBadFormatException">Thrown if the new username is not accepted because of bad format.</exception>
+ /// <exception cref="UserAlreadyExistException">Thrown if user with the new username already exists.</exception>
+ Task ChangeUsername(string oldUsername, string newUsername);
}
internal class UserCache
@@ -432,5 +463,31 @@ namespace Timeline.Services
//clear cache
RemoveCache(user.Id);
}
+
+ public async Task ChangeUsername(string oldUsername, string newUsername)
+ {
+ if (string.IsNullOrEmpty(oldUsername))
+ throw new ArgumentException("Old username is null or empty", nameof(oldUsername));
+ if (string.IsNullOrEmpty(newUsername))
+ throw new ArgumentException("New username is null or empty", nameof(newUsername));
+
+ if (!_usernameValidator.Validate(newUsername, out var message))
+ throw new UsernameBadFormatException(newUsername, $"New username is of bad format. {message}");
+
+ var user = await _databaseContext.Users.Where(u => u.Name == oldUsername).SingleOrDefaultAsync();
+ if (user == null)
+ throw new UserNotExistException(oldUsername);
+
+ var conflictUser = await _databaseContext.Users.Where(u => u.Name == newUsername).SingleOrDefaultAsync();
+ if (conflictUser != null)
+ throw new UserAlreadyExistException(newUsername);
+
+ user.Name = newUsername;
+ user.Version += 1;
+ await _databaseContext.SaveChangesAsync();
+ _logger.LogInformation(FormatLogMessage("A user entry changed name field.",
+ Pair("Id", user.Id), Pair("Old Username", oldUsername), Pair("New Username", newUsername)));
+ RemoveCache(user.Id);
+ }
}
}