using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Logging; using System; using System.Collections.Generic; using System.Globalization; using System.Linq; using System.Threading.Tasks; using Timeline.Entities; using Timeline.Models.Validation; namespace Timeline.Services.User { /// /// Null means not change. /// public class ModifyUserParams { public string? Username { get; set; } public string? Password { get; set; } public string? Nickname { get; set; } } public interface IUserService : IBasicUserService { /// /// Try to get a user by id. /// /// The id of the user. /// The user info. /// Thrown when the user with given id does not exist. Task GetUser(long id); /// /// List all users. /// /// The user info of users. Task> GetUsers(); /// /// Create a user with given info. /// /// The username of new user. /// The password of new user. /// The the new user. /// Thrown when or is null. /// Thrown when or is of bad format. /// Thrown when a user with given username already exists. Task CreateUser(string username, string password); /// /// Modify a user. /// /// The id of the user. /// The new information. /// The new user info. /// Thrown when some fields in is bad. /// Thrown when user with given id does not exist. /// /// Version will increase if password is changed. /// Task ModifyUser(long id, ModifyUserParams? param); } public class UserService : BasicUserService, IUserService { private readonly ILogger _logger; private readonly IClock _clock; private readonly DatabaseContext _databaseContext; private readonly IPasswordService _passwordService; private readonly UsernameValidator _usernameValidator = new UsernameValidator(); private readonly NicknameValidator _nicknameValidator = new NicknameValidator(); public UserService(ILogger logger, DatabaseContext databaseContext, IPasswordService passwordService, IClock clock) : base(databaseContext) { _logger = logger; _databaseContext = databaseContext; _passwordService = passwordService; _clock = clock; } private void CheckUsernameFormat(string username, string? paramName) { if (!_usernameValidator.Validate(username, out var message)) { throw new ArgumentException(string.Format(CultureInfo.CurrentCulture, Resource.ExceptionUsernameBadFormat, message), paramName); } } private static void CheckPasswordFormat(string password, string? paramName) { if (password.Length == 0) { throw new ArgumentException(Resource.ExceptionPasswordEmpty, paramName); } } private void CheckNicknameFormat(string nickname, string? paramName) { if (!_nicknameValidator.Validate(nickname, out var message)) { throw new ArgumentException(string.Format(CultureInfo.CurrentCulture, Resource.ExceptionNicknameBadFormat, message), paramName); } } private static void ThrowUsernameConflict(object? user) { throw new UserAlreadyExistException(user); } public async Task GetUser(long id) { var user = await _databaseContext.Users.Where(u => u.Id == id).SingleOrDefaultAsync(); if (user == null) throw new UserNotExistException(id); return user; } public async Task> GetUsers() { return await _databaseContext.Users.ToListAsync(); } public async Task CreateUser(string username, string password) { if (username == null) throw new ArgumentNullException(nameof(username)); if (password == null) throw new ArgumentNullException(nameof(password)); CheckUsernameFormat(username, nameof(username)); CheckPasswordFormat(password, nameof(password)); var conflict = await _databaseContext.Users.AnyAsync(u => u.Username == username); if (conflict) ThrowUsernameConflict(null); var newEntity = new UserEntity { Username = username, Password = _passwordService.HashPassword(password), Version = 1 }; _databaseContext.Users.Add(newEntity); await _databaseContext.SaveChangesAsync(); return newEntity; } public async Task ModifyUser(long id, ModifyUserParams? param) { if (param != null) { if (param.Username != null) CheckUsernameFormat(param.Username, nameof(param)); if (param.Password != null) CheckPasswordFormat(param.Password, nameof(param)); if (param.Nickname != null) CheckNicknameFormat(param.Nickname, nameof(param)); } var entity = await _databaseContext.Users.Where(u => u.Id == id).SingleOrDefaultAsync(); if (entity == null) throw new UserNotExistException(id); if (param != null) { var now = _clock.GetCurrentTime(); bool updateLastModified = false; var username = param.Username; if (username != null && username != entity.Username) { var conflict = await _databaseContext.Users.AnyAsync(u => u.Username == username); if (conflict) ThrowUsernameConflict(null); entity.Username = username; entity.UsernameChangeTime = now; updateLastModified = true; } var password = param.Password; if (password != null) { entity.Password = _passwordService.HashPassword(password); entity.Version += 1; } var nickname = param.Nickname; if (nickname != null && nickname != entity.Nickname) { entity.Nickname = nickname; updateLastModified = true; } if (updateLastModified) { entity.LastModified = now; } await _databaseContext.SaveChangesAsync(); } return entity; } } }