From 181b53e270ff7c0558edec75b8b255d487e796c3 Mon Sep 17 00:00:00 2001 From: 杨宇千 Date: Fri, 25 Oct 2019 23:03:36 +0800 Subject: Add user detail service. --- Timeline/Entities/DatabaseContext.cs | 1 + Timeline/Entities/User.cs | 2 + Timeline/Entities/UserDetail.cs | 21 ++++ .../Services/UserDetailService.Designer.cs | 99 ++++++++++++++++ Timeline/Resources/Services/UserDetailService.resx | 132 +++++++++++++++++++++ Timeline/Services/DatabaseExtensions.cs | 6 +- Timeline/Services/UserAvatarService.cs | 9 +- Timeline/Services/UserDetailService.cs | 102 ++++++++++++++++ Timeline/Timeline.csproj | 9 ++ 9 files changed, 373 insertions(+), 8 deletions(-) create mode 100644 Timeline/Entities/UserDetail.cs create mode 100644 Timeline/Resources/Services/UserDetailService.Designer.cs create mode 100644 Timeline/Resources/Services/UserDetailService.resx create mode 100644 Timeline/Services/UserDetailService.cs (limited to 'Timeline') diff --git a/Timeline/Entities/DatabaseContext.cs b/Timeline/Entities/DatabaseContext.cs index e1b98e7d..6c005b30 100644 --- a/Timeline/Entities/DatabaseContext.cs +++ b/Timeline/Entities/DatabaseContext.cs @@ -19,5 +19,6 @@ namespace Timeline.Entities public DbSet Users { get; set; } = default!; public DbSet UserAvatars { get; set; } = default!; + public DbSet UserDetails { get; set; } = default!; } } diff --git a/Timeline/Entities/User.cs b/Timeline/Entities/User.cs index 6e8e4967..02352b03 100644 --- a/Timeline/Entities/User.cs +++ b/Timeline/Entities/User.cs @@ -28,5 +28,7 @@ namespace Timeline.Entities public long Version { get; set; } public UserAvatar? Avatar { get; set; } + + public UserDetail? Detail { get; set; } } } diff --git a/Timeline/Entities/UserDetail.cs b/Timeline/Entities/UserDetail.cs new file mode 100644 index 00000000..45f87e2b --- /dev/null +++ b/Timeline/Entities/UserDetail.cs @@ -0,0 +1,21 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; +using System.Linq; +using System.Threading.Tasks; + +namespace Timeline.Entities +{ + [Table("user_details")] + public class UserDetail + { + [Column("id"), Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)] + public long Id { get; set; } + + [Column("nickname"), MaxLength(26)] + public string? Nickname { get; set; } + + public long UserId { get; set; } + } +} diff --git a/Timeline/Resources/Services/UserDetailService.Designer.cs b/Timeline/Resources/Services/UserDetailService.Designer.cs new file mode 100644 index 00000000..2f586b36 --- /dev/null +++ b/Timeline/Resources/Services/UserDetailService.Designer.cs @@ -0,0 +1,99 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace Timeline.Resources.Services { + using System; + + + /// + /// A strongly-typed resource class, for looking up localized strings, etc. + /// + // This class was auto-generated by the StronglyTypedResourceBuilder + // class via a tool like ResGen or Visual Studio. + // To add or remove a member, edit your .ResX file then rerun ResGen + // with the /str option, or rebuild your VS project. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "16.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class UserDetailService { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal UserDetailService() { + } + + /// + /// Returns the cached ResourceManager instance used by this class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager { + get { + if (object.ReferenceEquals(resourceMan, null)) { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Timeline.Resources.Services.UserDetailService", typeof(UserDetailService).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + + /// + /// Looks up a localized string similar to Length of nickname can't be bigger than 10.. + /// + internal static string ExceptionNicknameTooLong { + get { + return ResourceManager.GetString("ExceptionNicknameTooLong", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to A user_details entity has been created. User id is {0}. Nickname is {1}.. + /// + internal static string LogEntityNicknameCreate { + get { + return ResourceManager.GetString("LogEntityNicknameCreate", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Nickname of a user_details entity has been updated to a new value. User id is {0}. New value is {1}.. + /// + internal static string LogEntityNicknameSetNotNull { + get { + return ResourceManager.GetString("LogEntityNicknameSetNotNull", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Nickname of a user_details entity has been updated to null. User id is {0}.. + /// + internal static string LogEntityNicknameSetToNull { + get { + return ResourceManager.GetString("LogEntityNicknameSetToNull", resourceCulture); + } + } + } +} diff --git a/Timeline/Resources/Services/UserDetailService.resx b/Timeline/Resources/Services/UserDetailService.resx new file mode 100644 index 00000000..ea32aeda --- /dev/null +++ b/Timeline/Resources/Services/UserDetailService.resx @@ -0,0 +1,132 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Length of nickname can't be bigger than 10. + + + A user_details entity has been created. User id is {0}. Nickname is {1}. + + + Nickname of a user_details entity has been updated to a new value. User id is {0}. New value is {1}. + + + Nickname of a user_details entity has been updated to null. User id is {0}. + + \ No newline at end of file diff --git a/Timeline/Services/DatabaseExtensions.cs b/Timeline/Services/DatabaseExtensions.cs index 8cbc8fef..140c3146 100644 --- a/Timeline/Services/DatabaseExtensions.cs +++ b/Timeline/Services/DatabaseExtensions.cs @@ -9,6 +9,8 @@ namespace Timeline.Services { internal static class DatabaseExtensions { + private static readonly UsernameValidator usernameValidator = new UsernameValidator(); + /// /// Check the existence and get the id of the user. /// @@ -17,11 +19,11 @@ namespace Timeline.Services /// Thrown if is null. /// Thrown if is of bad format. /// Thrown if user does not exist. - internal static async Task CheckAndGetUser(DbSet userDbSet, UsernameValidator validator, string username) + internal static async Task CheckAndGetUser(DbSet userDbSet, string? username) { if (username == null) throw new ArgumentNullException(nameof(username)); - var (result, message) = validator.Validate(username); + var (result, message) = usernameValidator.Validate(username); if (!result) throw new UsernameBadFormatException(username, message); diff --git a/Timeline/Services/UserAvatarService.cs b/Timeline/Services/UserAvatarService.cs index 2afe9093..01201864 100644 --- a/Timeline/Services/UserAvatarService.cs +++ b/Timeline/Services/UserAvatarService.cs @@ -177,8 +177,6 @@ namespace Timeline.Services private readonly IETagGenerator _eTagGenerator; - private readonly UsernameValidator _usernameValidator; - private readonly IClock _clock; public UserAvatarService( @@ -194,13 +192,12 @@ namespace Timeline.Services _defaultUserAvatarProvider = defaultUserAvatarProvider; _avatarValidator = avatarValidator; _eTagGenerator = eTagGenerator; - _usernameValidator = new UsernameValidator(); _clock = clock; } public async Task GetAvatarETag(string username) { - var userId = await DatabaseExtensions.CheckAndGetUser(_database.Users, _usernameValidator, username); + var userId = await DatabaseExtensions.CheckAndGetUser(_database.Users, username); var eTag = (await _database.UserAvatars.Where(a => a.UserId == userId).Select(a => new { a.ETag }).SingleOrDefaultAsync())?.ETag; if (eTag == null) @@ -211,7 +208,7 @@ namespace Timeline.Services public async Task GetAvatar(string username) { - var userId = await DatabaseExtensions.CheckAndGetUser(_database.Users, _usernameValidator, username); + var userId = await DatabaseExtensions.CheckAndGetUser(_database.Users, username); var avatarEntity = await _database.UserAvatars.Where(a => a.UserId == userId).Select(a => new { a.Type, a.Data, a.LastModified }).SingleOrDefaultAsync(); @@ -253,7 +250,7 @@ namespace Timeline.Services throw new ArgumentException(Resources.Services.UserAvatarService.ExceptionAvatarTypeNullOrEmpty, nameof(avatar)); } - var userId = await DatabaseExtensions.CheckAndGetUser(_database.Users, _usernameValidator, username); + var userId = await DatabaseExtensions.CheckAndGetUser(_database.Users, username); var avatarEntity = await _database.UserAvatars.Where(a => a.UserId == userId).SingleOrDefaultAsync(); if (avatar == null) diff --git a/Timeline/Services/UserDetailService.cs b/Timeline/Services/UserDetailService.cs new file mode 100644 index 00000000..0b24e4e2 --- /dev/null +++ b/Timeline/Services/UserDetailService.cs @@ -0,0 +1,102 @@ +using Microsoft.Extensions.Logging; +using System; +using System.Linq; +using System.Threading.Tasks; +using Timeline.Entities; +using static Timeline.Resources.Services.UserDetailService; + +namespace Timeline.Services +{ + public interface IUserDetailService + { + /// + /// Get the nickname of the user with given username. + /// If the user does not set a nickname, the username is returned as the nickname. + /// + /// The username of the user to get nickname of. + /// The nickname of the user. + /// Thrown when is null. + /// Thrown when is of bad format. + /// Thrown when the user does not exist. + Task GetNickname(string username); + + /// + /// Set the nickname of the user with given username. + /// + /// The username of the user to set nickname of. + /// The nickname. Pass null to unset. + /// Thrown when is null. + /// Thrown when is not null but its length is bigger than 10. + /// Thrown when is of bad format. + /// Thrown when the user does not exist. + Task SetNickname(string username, string? nickname); + } + + public class UserDetailService : IUserDetailService + { + private readonly DatabaseContext _database; + + private readonly ILogger _logger; + + public UserDetailService(DatabaseContext database, ILogger logger) + { + _database = database; + _logger = logger; + } + + public async Task GetNickname(string username) + { + var userId = await DatabaseExtensions.CheckAndGetUser(_database.Users, username); + var nickname = _database.UserDetails.Where(d => d.UserId == userId).Select(d => new { d.Nickname }).SingleOrDefault()?.Nickname; + return nickname ?? username; + } + + public async Task SetNickname(string username, string? nickname) + { + if (nickname != null && nickname.Length > 10) + { + throw new ArgumentException(ExceptionNicknameTooLong, nameof(nickname)); + } + var userId = await DatabaseExtensions.CheckAndGetUser(_database.Users, username); + var userDetail = _database.UserDetails.Where(d => d.UserId == userId).SingleOrDefault(); + if (nickname == null) + { + if (userDetail == null || userDetail.Nickname == null) + { + return; + } + else + { + userDetail.Nickname = null; + await _database.SaveChangesAsync(); + _logger.LogInformation(LogEntityNicknameSetToNull, userId); + } + } + else + { + var create = userDetail == null; + if (create) + { + userDetail = new UserDetail + { + UserId = userId + }; + } + userDetail!.Nickname = nickname; + if (create) + { + _database.UserDetails.Add(userDetail); + } + await _database.SaveChangesAsync(); + if (create) + { + _logger.LogInformation(LogEntityNicknameCreate, userId, nickname); + } + else + { + _logger.LogInformation(LogEntityNicknameSetNotNull, userId, nickname); + } + } + } + } +} diff --git a/Timeline/Timeline.csproj b/Timeline/Timeline.csproj index b989cd3b..0260d725 100644 --- a/Timeline/Timeline.csproj +++ b/Timeline/Timeline.csproj @@ -89,6 +89,11 @@ True UserAvatarService.resx + + True + True + UserDetailService.resx + True True @@ -146,6 +151,10 @@ ResXFileCodeGenerator UserAvatarService.Designer.cs + + ResXFileCodeGenerator + UserDetailService.Designer.cs + ResXFileCodeGenerator UserService.Designer.cs -- cgit v1.2.3