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;
}
}
}