From 79ab2b304d93b1029515bd3f954db4e5a73f4168 Mon Sep 17 00:00:00 2001 From: crupest Date: Thu, 30 Jan 2020 20:26:52 +0800 Subject: ... --- Timeline/Services/ConfictException.cs | 21 -- Timeline/Services/ConflictException.cs | 21 ++ Timeline/Services/DatabaseExtensions.cs | 36 -- Timeline/Services/TimelineAlreadyExistException.cs | 17 - .../TimelineMemberOperationUserException.cs | 37 --- .../Services/TimelineNameBadFormatException.cs | 21 -- Timeline/Services/TimelineService.cs | 363 +++++++-------------- Timeline/Services/User.cs | 33 +- Timeline/Services/UserAvatarService.cs | 48 +-- Timeline/Services/UserRoleConvert.cs | 1 - Timeline/Services/UserService.cs | 36 +- Timeline/Services/UserTokenService.cs | 1 - 12 files changed, 182 insertions(+), 453 deletions(-) delete mode 100644 Timeline/Services/ConfictException.cs create mode 100644 Timeline/Services/ConflictException.cs delete mode 100644 Timeline/Services/DatabaseExtensions.cs delete mode 100644 Timeline/Services/TimelineAlreadyExistException.cs delete mode 100644 Timeline/Services/TimelineMemberOperationUserException.cs delete mode 100644 Timeline/Services/TimelineNameBadFormatException.cs (limited to 'Timeline/Services') diff --git a/Timeline/Services/ConfictException.cs b/Timeline/Services/ConfictException.cs deleted file mode 100644 index dcd77366..00000000 --- a/Timeline/Services/ConfictException.cs +++ /dev/null @@ -1,21 +0,0 @@ -using System; - -namespace Timeline.Services -{ - /// - /// Thrown when a resource already exists and conflicts with the given resource. - /// - /// - /// For example a username already exists and conflicts with the given username. - /// - [Serializable] - public class ConfictException : Exception - { - public ConfictException() : base(Resources.Services.Exception.ConfictException) { } - public ConfictException(string message) : base(message) { } - public ConfictException(string message, Exception inner) : base(message, inner) { } - protected ConfictException( - System.Runtime.Serialization.SerializationInfo info, - System.Runtime.Serialization.StreamingContext context) : base(info, context) { } - } -} diff --git a/Timeline/Services/ConflictException.cs b/Timeline/Services/ConflictException.cs new file mode 100644 index 00000000..6ede183a --- /dev/null +++ b/Timeline/Services/ConflictException.cs @@ -0,0 +1,21 @@ +using System; + +namespace Timeline.Services +{ + /// + /// Thrown when a resource already exists and conflicts with the given resource. + /// + /// + /// For example a username already exists and conflicts with the given username. + /// + [Serializable] + public class ConflictException : Exception + { + public ConflictException() : base(Resources.Services.Exception.ConflictException) { } + public ConflictException(string message) : base(message) { } + public ConflictException(string message, Exception inner) : base(message, inner) { } + protected ConflictException( + System.Runtime.Serialization.SerializationInfo info, + System.Runtime.Serialization.StreamingContext context) : base(info, context) { } + } +} diff --git a/Timeline/Services/DatabaseExtensions.cs b/Timeline/Services/DatabaseExtensions.cs deleted file mode 100644 index e77dd01a..00000000 --- a/Timeline/Services/DatabaseExtensions.cs +++ /dev/null @@ -1,36 +0,0 @@ -using Microsoft.EntityFrameworkCore; -using System; -using System.Linq; -using System.Threading.Tasks; -using Timeline.Entities; -using Timeline.Models.Validation; - -namespace Timeline.Services -{ - internal static class DatabaseExtensions - { - private static readonly UsernameValidator usernameValidator = new UsernameValidator(); - - /// - /// Check the existence and get the id of the user. - /// - /// The username of the user. - /// The user id. - /// Thrown if is null. - /// Thrown if is of bad format. - /// Thrown if user does not exist. - internal static async Task CheckAndGetUser(DbSet userDbSet, string? username) - { - if (username == null) - throw new ArgumentNullException(nameof(username)); - var (result, message) = usernameValidator.Validate(username); - if (!result) - throw new UsernameBadFormatException(username, message); - - var userId = await userDbSet.Where(u => u.Username == username).Select(u => u.Id).SingleOrDefaultAsync(); - if (userId == 0) - throw new UserNotExistException(username); - return userId; - } - } -} diff --git a/Timeline/Services/TimelineAlreadyExistException.cs b/Timeline/Services/TimelineAlreadyExistException.cs deleted file mode 100644 index c2dea1f9..00000000 --- a/Timeline/Services/TimelineAlreadyExistException.cs +++ /dev/null @@ -1,17 +0,0 @@ -using System; - -namespace Timeline.Services -{ - [Serializable] - public class TimelineAlreadyExistException : Exception - { - public TimelineAlreadyExistException() : base(Resources.Services.Exception.TimelineAlreadyExistException) { } - public TimelineAlreadyExistException(string name) : base(Resources.Services.Exception.TimelineAlreadyExistException) { Name = name; } - public TimelineAlreadyExistException(string name, Exception inner) : base(Resources.Services.Exception.TimelineAlreadyExistException, inner) { Name = name; } - protected TimelineAlreadyExistException( - System.Runtime.Serialization.SerializationInfo info, - System.Runtime.Serialization.StreamingContext context) : base(info, context) { } - - public string? Name { get; set; } - } -} diff --git a/Timeline/Services/TimelineMemberOperationUserException.cs b/Timeline/Services/TimelineMemberOperationUserException.cs deleted file mode 100644 index 543ee160..00000000 --- a/Timeline/Services/TimelineMemberOperationUserException.cs +++ /dev/null @@ -1,37 +0,0 @@ -using System; -using System.Globalization; - -namespace Timeline.Services -{ - [Serializable] - public class TimelineMemberOperationUserException : Exception - { - public enum MemberOperation - { - Add, - Remove - } - - public TimelineMemberOperationUserException() : base(Resources.Services.Exception.TimelineMemberOperationException) { } - public TimelineMemberOperationUserException(string message) : base(message) { } - public TimelineMemberOperationUserException(string message, Exception inner) : base(message, inner) { } - protected TimelineMemberOperationUserException( - System.Runtime.Serialization.SerializationInfo info, - System.Runtime.Serialization.StreamingContext context) : base(info, context) { } - - public TimelineMemberOperationUserException(int index, MemberOperation operation, string username, Exception inner) - : base(MakeMessage(operation, index), inner) { Operation = operation; Index = index; Username = username; } - - private static string MakeMessage(MemberOperation operation, int index) => string.Format(CultureInfo.CurrentCulture, - Resources.Services.Exception.TimelineMemberOperationExceptionDetail, operation, index); - - public MemberOperation? Operation { get; set; } - - /// - /// The index of the member on which the operation failed. - /// - public int? Index { get; set; } - - public string? Username { get; set; } - } -} diff --git a/Timeline/Services/TimelineNameBadFormatException.cs b/Timeline/Services/TimelineNameBadFormatException.cs deleted file mode 100644 index 5120a175..00000000 --- a/Timeline/Services/TimelineNameBadFormatException.cs +++ /dev/null @@ -1,21 +0,0 @@ -using System; - -namespace Timeline.Services -{ - [Serializable] - public class TimelineNameBadFormatException : Exception - { - public TimelineNameBadFormatException() - : base(Resources.Services.Exception.TimelineNameBadFormatException) { } - public TimelineNameBadFormatException(string name) - : base(Resources.Services.Exception.TimelineNameBadFormatException) { Name = name; } - public TimelineNameBadFormatException(string name, Exception inner) - : base(Resources.Services.Exception.TimelineNameBadFormatException, inner) { Name = name; } - - protected TimelineNameBadFormatException( - System.Runtime.Serialization.SerializationInfo info, - System.Runtime.Serialization.StreamingContext context) : base(info, context) { } - - public string? Name { get; set; } - } -} diff --git a/Timeline/Services/TimelineService.cs b/Timeline/Services/TimelineService.cs index f43d2de5..89936aa2 100644 --- a/Timeline/Services/TimelineService.cs +++ b/Timeline/Services/TimelineService.cs @@ -1,13 +1,15 @@ -using Microsoft.EntityFrameworkCore; +using AutoMapper; +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; using Timeline.Models.Http; using Timeline.Models.Validation; +using static Timeline.Resources.Services.TimelineService; namespace Timeline.Services { @@ -28,12 +30,7 @@ namespace Timeline.Services /// Username or the timeline name. See remarks of . /// A list of all posts. /// Thrown when is null. - /// - /// Thrown when timeline name is of bad format. - /// For normal timeline, it means name is an empty string. - /// For personal timeline, it means the username is of bad format, - /// the inner exception should be a . - /// + /// Thrown when is illegal. It is not a valid timeline name (for normal timeline service) or a valid username (for personal timeline service). /// /// Thrown when timeline does not exist. /// For normal timeline, it means the name does not exist. @@ -46,26 +43,20 @@ namespace Timeline.Services /// Create a new post in timeline. /// /// Username or the timeline name. See remarks of . - /// The author's username. + /// The author's id. /// The content. /// The time of the post. If null, then use current time. /// The info of the created post. - /// Thrown when or or is null. - /// - /// Thrown when timeline name is of bad format. - /// For normal timeline, it means name is an empty string. - /// For personal timeline, it means the username is of bad format, - /// the inner exception should be a . - /// + /// Thrown when or is null. + /// Thrown when is illegal. It is not a valid timeline name (for normal timeline service) or a valid username (for personal timeline service). /// /// Thrown when timeline does not exist. /// For normal timeline, it means the name does not exist. /// For personal timeline, it means the user of that username does not exist /// and the inner exception should be a . /// - /// Thrown if is of bad format. - /// Thrown if does not exist. - Task CreatePost(string name, string author, string content, DateTime? time); + /// Thrown if user with does not exist. + Task CreatePost(string name, long authorId, string content, DateTime? time); /// /// Delete a post @@ -73,12 +64,7 @@ namespace Timeline.Services /// Username or the timeline name. See remarks of . /// The id of the post to delete. /// Thrown when or is null. - /// - /// Thrown when timeline name is of bad format. - /// For normal timeline, it means name is an empty string. - /// For personal timeline, it means the username is of bad format, - /// the inner exception should be a . - /// + /// Thrown when is illegal. It is not a valid timeline name (for normal timeline service) or a valid username (for personal timeline service). /// /// Thrown when timeline does not exist. /// For normal timeline, it means the name does not exist. @@ -100,19 +86,14 @@ namespace Timeline.Services /// Username or the timeline name. See remarks of . /// The new properties. Null member means not to change. /// Thrown when or is null. - /// - /// Thrown when timeline name is of bad format. - /// For normal timeline, it means name is an empty string. - /// For personal timeline, it means the username is of bad format, - /// the inner exception should be a . - /// + /// Thrown when is illegal. It is not a valid timeline name (for normal timeline service) or a valid username (for personal timeline service). /// /// Thrown when timeline does not exist. /// For normal timeline, it means the name does not exist. /// For personal timeline, it means the user of that username does not exist /// and the inner exception should be a . /// - Task ChangeProperty(string name, TimelinePropertyChangeRequest newProperties); + Task ChangeProperty(string name, TimelinePatchRequest newProperties); /// /// Remove members to a timeline. @@ -121,24 +102,16 @@ namespace Timeline.Services /// A list of usernames of members to add. May be null. /// A list of usernames of members to remove. May be null. /// Thrown when is null. - /// - /// Thrown when timeline name is of bad format. - /// For normal timeline, it means name is an empty string. - /// For personal timeline, it means the username is of bad format, - /// the inner exception should be a . - /// + /// Thrown when is illegal. It is not a valid timeline name (for normal timeline service) or a valid username (for personal timeline service). + /// Thrown when names in or is not a valid username. /// /// Thrown when timeline does not exist. /// For normal timeline, it means the name does not exist. /// For personal timeline, it means the user of that username does not exist /// and the inner exception should be a . /// - /// - /// Thrown when an exception occurs on the user list. - /// The inner exception is - /// when one of the username is invalid. - /// The inner exception is - /// when one of the user to change does not exist. + /// + /// Thrown when one of the user to change does not exist. /// /// /// Operating on a username that is of bad format or does not exist always throws. @@ -153,42 +126,30 @@ namespace Timeline.Services /// Verify whether a visitor has the permission to read a timeline. /// /// Username or the timeline name. See remarks of . - /// The user to check on. Null means visitor without account. + /// The id of the user to check on. Null means visitor without account. /// True if can read, false if can't read. /// Thrown when is null. - /// - /// Thrown when timeline name is of bad format. - /// For normal timeline, it means name is an empty string. - /// For personal timeline, it means the username is of bad format, - /// the inner exception should be a . - /// + /// Thrown when is illegal. It is not a valid timeline name (for normal timeline service) or a valid username (for personal timeline service). /// /// Thrown when timeline does not exist. /// For normal timeline, it means the name does not exist. /// For personal timeline, it means the user of that username does not exist /// and the inner exception should be a . /// - /// - /// Thrown when is of bad format. - /// - /// - /// Thrown when does not exist. - /// - Task HasReadPermission(string name, string? username); + /// + /// This method does not check whether visitor is administrator. + /// Return false if user with visitor id does not exist. + /// + Task HasReadPermission(string name, long? visitorId); /// /// Verify whether a user has the permission to modify a post. /// /// Username or the timeline name. See remarks of . - /// The user to check on. + /// The id of the user to check on. /// True if can modify, false if can't modify. - /// Thrown when or is null. - /// - /// Thrown when timeline name is of bad format. - /// For normal timeline, it means name is an empty string. - /// For personal timeline, it means the username is of bad format, - /// the inner exception should be a . - /// + /// Thrown when is null. + /// Thrown when is illegal. It is not a valid timeline name (for normal timeline service) or a valid username (for personal timeline service). /// /// Thrown when timeline does not exist. /// For normal timeline, it means the name does not exist. @@ -198,47 +159,32 @@ namespace Timeline.Services /// /// Thrown when the post with given id does not exist or is deleted already. /// - /// - /// Thrown when is of bad format. - /// - /// - /// Thrown when does not exist. - /// /// /// This method does not check whether the user is administrator. /// It only checks whether he is the author of the post or the owner of the timeline. + /// Return false when user with modifier id does not exist. /// - Task HasPostModifyPermission(string name, long id, string username); + Task HasPostModifyPermission(string name, long id, long modifierId); /// /// Verify whether a user is member of a timeline. /// /// Username or the timeline name. See remarks of . - /// The user to check on. + /// The id of user to check on. /// True if it is a member, false if not. - /// Thrown when or is null. - /// - /// Thrown when timeline name is of bad format. - /// For normal timeline, it means name is an empty string. - /// For personal timeline, it means the username is of bad format, - /// the inner exception should be a . - /// + /// Thrown when is null. + /// Thrown when is illegal. It is not a valid timeline name (for normal timeline service) or a valid username (for personal timeline service). /// /// Thrown when timeline does not exist. /// For normal timeline, it means the name does not exist. /// For personal timeline, it means the user of that username does not exist /// and the inner exception should be a . /// - /// - /// Thrown when is not a valid username. - /// - /// - /// Thrown when user does not exist. - /// /// /// Timeline owner is also considered as a member. + /// Return false when user with user id does not exist. /// - Task IsMemberOf(string name, string username); + Task IsMemberOf(string name, long userId); } /// @@ -252,7 +198,7 @@ namespace Timeline.Services /// The name of the timeline. /// The timeline info. /// Thrown when is null. - /// + /// /// Thrown when timeline name is invalid. Currently it means it is an empty string. /// /// @@ -264,20 +210,12 @@ namespace Timeline.Services /// Create a timeline. /// /// The name of the timeline. - /// The owner of the timeline. + /// The id of owner of the timeline. /// Thrown when or is null. - /// - /// Thrown when timeline name is invalid. Currently it means it is an empty string. - /// - /// - /// Thrown when the timeline already exists. - /// - /// - /// Thrown when the username of the owner is not valid. - /// - /// - /// Thrown when the owner user does not exist. - Task CreateTimeline(string name, string owner); + /// Thrown when timeline name is invalid. Currently it means it is an empty string. + /// Thrown when the timeline already exists. + /// Thrown when the owner user does not exist. + Task CreateTimeline(string name, long owner); } public interface IPersonalTimelineService : IBaseTimelineService @@ -290,8 +228,8 @@ namespace Timeline.Services /// /// Thrown when is null. /// - /// - /// Thrown when is of bad format. Inner exception MUST be . + /// + /// Thrown when is of bad format. /// /// /// Thrown when the user does not exist. Inner exception MUST be . @@ -301,10 +239,12 @@ namespace Timeline.Services public abstract class BaseTimelineService : IBaseTimelineService { - protected BaseTimelineService(ILoggerFactory loggerFactory, DatabaseContext database, IClock clock) + protected BaseTimelineService(ILoggerFactory loggerFactory, DatabaseContext database, IUserService userService, IMapper mapper, IClock clock) { Clock = clock; Database = database; + UserService = userService; + Mapper = mapper; } protected IClock Clock { get; } @@ -313,6 +253,10 @@ namespace Timeline.Services protected DatabaseContext Database { get; } + protected IUserService UserService { get; } + + protected IMapper Mapper { get; } + /// /// Find the timeline id by the name. /// For details, see remarks. @@ -320,12 +264,7 @@ namespace Timeline.Services /// The username or the timeline name. See remarks. /// The id of the timeline entity. /// Thrown when is null. - /// - /// Thrown when timeline name is of bad format. - /// For normal timeline, it means name is an empty string. - /// For personal timeline, it means the username is of bad format, - /// the inner exception should be a . - /// + /// Thrown when is illegal. It is not a valid timeline name (for normal timeline service) or a valid username (for personal timeline service). /// /// Thrown when timeline does not exist. /// For normal timeline, it means the name does not exist. @@ -347,66 +286,60 @@ namespace Timeline.Services if (name == null) throw new ArgumentNullException(nameof(name)); + var timelineId = await FindTimelineId(name); var postEntities = await Database.TimelinePosts.OrderBy(p => p.Time).Where(p => p.TimelineId == timelineId && p.Content != null).ToListAsync(); + var posts = new List(); foreach (var entity in postEntities) { - posts.Add(new TimelinePostInfo + if (entity.Content != null) // otherwise it is deleted { - Id = entity.Id, - Content = entity.Content, - Author = (await Database.Users.Where(u => u.Id == entity.AuthorId).Select(u => new { u.Username }).SingleAsync()).Name, - Time = entity.Time - }); + var author = Mapper.Map(UserService.GetUserById(entity.AuthorId)); + posts.Add(new TimelinePostInfo + { + Id = entity.Id, + Content = entity.Content, + Author = author, + Time = entity.Time, + LastUpdated = entity.LastUpdated + }); + } } return posts; } - public async Task CreatePost(string name, string author, string content, DateTime? time) + public async Task CreatePost(string name, long authorId, string content, DateTime? time) { if (name == null) throw new ArgumentNullException(nameof(name)); - if (author == null) - throw new ArgumentNullException(nameof(author)); if (content == null) throw new ArgumentNullException(nameof(content)); - { - var (result, message) = UsernameValidator.Validate(author); - if (!result) - { - throw new UsernameBadFormatException(author, message); - } - } - var timelineId = await FindTimelineId(name); - - var authorEntity = Database.Users.Where(u => u.Username == author).Select(u => new { u.Id }).SingleOrDefault(); - if (authorEntity == null) - { - throw new UserNotExistException(author); - } - var authorId = authorEntity.Id; + var author = Mapper.Map(await UserService.GetUserById(authorId)); var currentTime = Clock.GetCurrentTime(); + var finalTime = time ?? currentTime; var postEntity = new TimelinePostEntity { Content = content, AuthorId = authorId, TimelineId = timelineId, - Time = time ?? currentTime, + Time = finalTime, LastUpdated = currentTime }; - Database.TimelinePosts.Add(postEntity); await Database.SaveChangesAsync(); - return new TimelinePostCreateResponse + return new TimelinePostInfo { Id = postEntity.Id, - Time = postEntity.Time + Content = content, + Author = author, + Time = finalTime, + LastUpdated = currentTime }; } @@ -426,7 +359,7 @@ namespace Timeline.Services await Database.SaveChangesAsync(); } - public async Task ChangeProperty(string name, TimelinePropertyChangeRequest newProperties) + public async Task ChangeProperty(string name, TimelinePatchRequest newProperties) { if (name == null) throw new ArgumentNullException(nameof(name)); @@ -455,27 +388,23 @@ namespace Timeline.Services if (name == null) throw new ArgumentNullException(nameof(name)); - // remove duplication and check the format of each username. - // Return a username->index map. - Dictionary? RemoveDuplicateAndCheckFormat(IList? list, TimelineMemberOperationUserException.MemberOperation operation) + List? RemoveDuplicateAndCheckFormat(IList? list, string paramName) { if (list != null) { - Dictionary result = new Dictionary(); + List result = new List(); var count = list.Count; for (var index = 0; index < count; index++) { var username = list[index]; - if (result.ContainsKey(username)) + if (result.Contains(username)) { continue; } var (validationResult, message) = UsernameValidator.Validate(username); if (!validationResult) - throw new TimelineMemberOperationUserException( - index, operation, username, - new UsernameBadFormatException(username, message)); - result.Add(username, index); + throw new ArgumentException(string.Format(CultureInfo.CurrentCulture, ExceptionChangeMemberUsernameBadFormat, index), nameof(paramName)); + result.Add(username); } return result; } @@ -484,13 +413,13 @@ namespace Timeline.Services return null; } } - var simplifiedAdd = RemoveDuplicateAndCheckFormat(add, TimelineMemberOperationUserException.MemberOperation.Add); - var simplifiedRemove = RemoveDuplicateAndCheckFormat(remove, TimelineMemberOperationUserException.MemberOperation.Remove); + var simplifiedAdd = RemoveDuplicateAndCheckFormat(add, nameof(add)); + var simplifiedRemove = RemoveDuplicateAndCheckFormat(remove, nameof(remove)); // remove those both in add and remove if (simplifiedAdd != null && simplifiedRemove != null) { - var usersToClean = simplifiedRemove.Keys.Where(u => simplifiedAdd.ContainsKey(u)); + var usersToClean = simplifiedRemove.Where(u => simplifiedAdd.Contains(u)).ToList(); foreach (var u in usersToClean) { simplifiedAdd.Remove(u); @@ -500,26 +429,20 @@ namespace Timeline.Services var timelineId = await FindTimelineId(name); - async Task?> CheckExistenceAndGetId(Dictionary? map, TimelineMemberOperationUserException.MemberOperation operation) + async Task?> CheckExistenceAndGetId(List? list) { - if (map == null) + if (list == null) return null; List result = new List(); - foreach (var (username, index) in map) + foreach (var username in list) { - var user = await Database.Users.Where(u => u.Username == username).Select(u => new { u.Id }).SingleOrDefaultAsync(); - if (user == null) - { - throw new TimelineMemberOperationUserException(index, operation, username, - new UserNotExistException(username)); - } - result.Add(user.Id); + result.Add(await UserService.GetUserIdByUsername(username)); } return result; } - var userIdsAdd = await CheckExistenceAndGetId(simplifiedAdd, TimelineMemberOperationUserException.MemberOperation.Add); - var userIdsRemove = await CheckExistenceAndGetId(simplifiedRemove, TimelineMemberOperationUserException.MemberOperation.Remove); + var userIdsAdd = await CheckExistenceAndGetId(simplifiedAdd); + var userIdsRemove = await CheckExistenceAndGetId(simplifiedRemove); if (userIdsAdd != null) { @@ -536,30 +459,11 @@ namespace Timeline.Services await Database.SaveChangesAsync(); } - public async Task HasReadPermission(string name, string? username) + public async Task HasReadPermission(string name, long? visitorId) { if (name == null) throw new ArgumentNullException(nameof(name)); - long? userId = null; - if (username != null) - { - var (result, message) = UsernameValidator.Validate(username); - if (!result) - { - throw new UsernameBadFormatException(username); - } - - var user = await Database.Users.Where(u => u.Username == username).Select(u => new { u.Id }).SingleOrDefaultAsync(); - - if (user == null) - { - throw new UserNotExistException(username); - } - - userId = user.Id; - } - var timelineId = await FindTimelineId(name); var timelineEntity = await Database.Timelines.Where(t => t.Id == timelineId).Select(t => new { t.Visibility }).SingleAsync(); @@ -567,43 +471,24 @@ namespace Timeline.Services if (timelineEntity.Visibility == TimelineVisibility.Public) return true; - if (timelineEntity.Visibility == TimelineVisibility.Register && username != null) + if (timelineEntity.Visibility == TimelineVisibility.Register && visitorId != null) return true; - if (userId == null) + if (visitorId == null) { return false; } else { - var memberEntity = await Database.TimelineMembers.Where(m => m.UserId == userId && m.TimelineId == timelineId).SingleOrDefaultAsync(); + var memberEntity = await Database.TimelineMembers.Where(m => m.UserId == visitorId && m.TimelineId == timelineId).SingleOrDefaultAsync(); return memberEntity != null; } } - public async Task HasPostModifyPermission(string name, long id, string username) + public async Task HasPostModifyPermission(string name, long id, long modifierId) { if (name == null) throw new ArgumentNullException(nameof(name)); - if (username == null) - throw new ArgumentNullException(nameof(username)); - - { - var (result, message) = UsernameValidator.Validate(username); - if (!result) - { - throw new UsernameBadFormatException(username); - } - } - - var user = await Database.Users.Where(u => u.Username == username).Select(u => new { u.Id }).SingleOrDefaultAsync(); - - if (user == null) - { - throw new UserNotExistException(username); - } - - var userId = user.Id; var timelineId = await FindTimelineId(name); @@ -614,32 +499,13 @@ namespace Timeline.Services if (postEntity == null) throw new TimelinePostNotExistException(id); - return timelineEntity.OwnerId == userId || postEntity.AuthorId == userId; + return timelineEntity.OwnerId == modifierId || postEntity.AuthorId == modifierId; } - public async Task IsMemberOf(string name, string username) + public async Task IsMemberOf(string name, long userId) { if (name == null) throw new ArgumentNullException(nameof(name)); - if (username == null) - throw new ArgumentNullException(nameof(username)); - - { - var (result, message) = UsernameValidator.Validate(username); - if (!result) - { - throw new UsernameBadFormatException(username); - } - } - - var user = await Database.Users.Where(u => u.Username == username).Select(u => new { u.Id }).SingleOrDefaultAsync(); - - if (user == null) - { - throw new UserNotExistException(username); - } - - var userId = user.Id; var timelineId = await FindTimelineId(name); @@ -648,38 +514,33 @@ namespace Timeline.Services if (userId == timelineEntity.OwnerId) return true; - var timelineMemberEntity = await Database.TimelineMembers.Where(m => m.TimelineId == timelineId && m.UserId == userId).SingleOrDefaultAsync(); - - return timelineMemberEntity != null; + return await Database.TimelineMembers.AnyAsync(m => m.TimelineId == timelineId && m.UserId == userId); } } public class PersonalTimelineService : BaseTimelineService, IPersonalTimelineService { - public PersonalTimelineService(ILoggerFactory loggerFactory, DatabaseContext database, IClock clock) - : base(loggerFactory, database, clock) + public PersonalTimelineService(ILoggerFactory loggerFactory, DatabaseContext database, IUserService userService, IMapper mapper, IClock clock) + : base(loggerFactory, database, userService, mapper, clock) { } protected override async Task FindTimelineId(string name) { + long userId; + try { - var (result, message) = UsernameValidator.Validate(name); - if (!result) - { - throw new TimelineNameBadFormatException(name, new UsernameBadFormatException(name, message)); - } + userId = await UserService.GetUserIdByUsername(name); } - - var userEntity = await Database.Users.Where(u => u.Username == name).Select(u => new { u.Id }).SingleOrDefaultAsync(); - - if (userEntity == null) + catch (ArgumentException e) { - throw new TimelineNotExistException(name, new UserNotExistException(name)); + throw new ArgumentException(ExceptionFindTimelineUsernameBadFormat, nameof(name), e); + } + catch (UserNotExistException e) + { + throw new TimelineNotExistException(name, e); } - - var userId = userEntity.Id; var timelineEntity = await Database.Timelines.Where(t => t.OwnerId == userId && t.Name == null).Select(t => new { t.Id }).SingleOrDefaultAsync(); @@ -715,16 +576,20 @@ namespace Timeline.Services var timelineMemberEntities = await Database.TimelineMembers.Where(m => m.TimelineId == timelineId).Select(m => new { m.UserId }).ToListAsync(); - var memberUsernameTasks = timelineMemberEntities.Select(m => Database.Users.Where(u => u.Id == m.UserId).Select(u => u.Username).SingleAsync()).ToArray(); + var owner = Mapper.Map(await UserService.GetUserById(timelineEntity.OwnerId)); - var memberUsernames = await Task.WhenAll(memberUsernameTasks); + var members = new List(); + foreach (var memberEntity in timelineMemberEntities) + { + members.Add(Mapper.Map(await UserService.GetUserById(memberEntity.UserId))); + } return new BaseTimelineInfo { Description = timelineEntity.Description ?? "", - Owner = username, + Owner = owner, Visibility = timelineEntity.Visibility, - Members = memberUsernames.ToList() + Members = members }; } diff --git a/Timeline/Services/User.cs b/Timeline/Services/User.cs index f63a374e..09a472e5 100644 --- a/Timeline/Services/User.cs +++ b/Timeline/Services/User.cs @@ -1,14 +1,9 @@ -using Microsoft.AspNetCore.Mvc; -using System; -using Timeline.Controllers; - -namespace Timeline.Services +namespace Timeline.Services { public class User { public string? Username { get; set; } public string? Nickname { get; set; } - public string? AvatarUrl { get; set; } #region adminsecret public bool? Administrator { get; set; } @@ -20,30 +15,4 @@ namespace Timeline.Services public long? Version { get; set; } #endregion secret } - - public static class UserExtensions - { - public static User EraseSecretAndFinalFill(this User user, IUrlHelper urlHelper, bool adminstrator) - { - if (user == null) - throw new ArgumentNullException(nameof(user)); - - var result = new User - { - Username = user.Username, - Nickname = user.Nickname, - AvatarUrl = urlHelper.ActionLink(action: nameof(UserAvatarController.Get), controller: nameof(UserAvatarController), values: new - { - user.Username - }) - }; - - if (adminstrator) - { - result.Administrator = user.Administrator; - } - - return result; - } - } } diff --git a/Timeline/Services/UserAvatarService.cs b/Timeline/Services/UserAvatarService.cs index ac7dd857..39b408e6 100644 --- a/Timeline/Services/UserAvatarService.cs +++ b/Timeline/Services/UserAvatarService.cs @@ -11,7 +11,6 @@ using System.Linq; using System.Threading.Tasks; using Timeline.Entities; using Timeline.Helpers; -using Timeline.Models.Validation; namespace Timeline.Services { @@ -61,36 +60,27 @@ namespace Timeline.Services public interface IUserAvatarService { /// - /// Get the etag of a user's avatar. + /// Get the etag of a user's avatar. Warning: This method does not check the user existence. /// - /// The username of the user to get avatar etag of. + /// The id of the user to get avatar etag of. /// The etag. - /// Thrown if is null. - /// Thrown if the is of bad format. - /// Thrown if the user does not exist. - Task GetAvatarETag(string username); + Task GetAvatarETag(long id); /// - /// Get avatar of a user. If the user has no avatar set, a default one is returned. + /// Get avatar of a user. If the user has no avatar set, a default one is returned. Warning: This method does not check the user existence. /// - /// The username of the user to get avatar of. + /// The id of the user to get avatar of. /// The avatar info. - /// Thrown if is null. - /// Thrown if the is of bad format. - /// Thrown if the user does not exist. - Task GetAvatar(string username); + Task GetAvatar(long id); /// - /// Set avatar for a user. + /// Set avatar for a user. Warning: This method does not check the user existence. /// - /// The username of the user to set avatar for. + /// The id of the user to set avatar for. /// The avatar. Can be null to delete the saved avatar. - /// Throw if is null. /// Thrown if any field in is null when is not null. - /// Thrown if the is of bad format. - /// Thrown if the user does not exist. /// Thrown if avatar is of bad format. - Task SetAvatar(string username, Avatar? avatar); + Task SetAvatar(long id, Avatar? avatar); } // TODO! : Make this configurable. @@ -104,7 +94,6 @@ namespace Timeline.Services private DateTime _cacheLastModified; private string _cacheETag = default!; - [System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1062:Validate arguments of public methods", Justification = "DI.")] public DefaultUserAvatarProvider(IWebHostEnvironment environment, IETagGenerator eTagGenerator) { _avatarPath = Path.Combine(environment.ContentRootPath, "default-avatar.png"); @@ -195,22 +184,18 @@ namespace Timeline.Services _clock = clock; } - public async Task GetAvatarETag(string username) + public async Task GetAvatarETag(long id) { - 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; + var eTag = (await _database.UserAvatars.Where(a => a.UserId == id).Select(a => new { a.ETag }).SingleOrDefaultAsync())?.ETag; if (eTag == null) return await _defaultUserAvatarProvider.GetDefaultAvatarETag(); else return eTag; } - public async Task GetAvatar(string username) + public async Task GetAvatar(long id) { - 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(); + var avatarEntity = await _database.UserAvatars.Where(a => a.UserId == id).Select(a => new { a.Type, a.Data, a.LastModified }).SingleOrDefaultAsync(); if (avatarEntity != null) { @@ -240,7 +225,7 @@ namespace Timeline.Services return defaultAvatar; } - public async Task SetAvatar(string username, Avatar? avatar) + public async Task SetAvatar(long id, Avatar? avatar) { if (avatar != null) { @@ -250,8 +235,7 @@ namespace Timeline.Services throw new ArgumentException(Resources.Services.UserAvatarService.ExceptionAvatarTypeNullOrEmpty, nameof(avatar)); } - var userId = await DatabaseExtensions.CheckAndGetUser(_database.Users, username); - var avatarEntity = await _database.UserAvatars.Where(a => a.UserId == userId).SingleOrDefaultAsync(); + var avatarEntity = await _database.UserAvatars.Where(a => a.UserId == id).SingleOrDefaultAsync(); if (avatar == null) { @@ -281,7 +265,7 @@ namespace Timeline.Services avatarEntity.Data = avatar.Data; avatarEntity.ETag = await _eTagGenerator.Generate(avatar.Data); avatarEntity.LastModified = _clock.GetCurrentTime(); - avatarEntity.UserId = userId; + avatarEntity.UserId = id; if (create) { _database.UserAvatars.Add(avatarEntity); diff --git a/Timeline/Services/UserRoleConvert.cs b/Timeline/Services/UserRoleConvert.cs index 4fa4a7b8..f27ee1bb 100644 --- a/Timeline/Services/UserRoleConvert.cs +++ b/Timeline/Services/UserRoleConvert.cs @@ -5,7 +5,6 @@ using Timeline.Entities; namespace Timeline.Services { - [System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1062:Validate arguments of public methods", Justification = "No need.")] public static class UserRoleConvert { public const string UserRole = UserRoles.User; diff --git a/Timeline/Services/UserService.cs b/Timeline/Services/UserService.cs index ff2306c5..1197bb73 100644 --- a/Timeline/Services/UserService.cs +++ b/Timeline/Services/UserService.cs @@ -43,6 +43,16 @@ namespace Timeline.Services /// Thrown when the user with given username does not exist. Task GetUserByUsername(string username); + /// + /// Get the user id of given username. + /// + /// Username of the user. + /// The id of the user. + /// Thrown when is null. + /// Thrown when is of bad format. + /// Thrown when the user with given username does not exist. + Task GetUserIdByUsername(string username); + /// /// List all users. /// @@ -57,7 +67,7 @@ namespace Timeline.Services /// The id of the new user. /// Thrown when is null. /// Thrown when some fields in is bad. - /// Thrown when a user with given username already exists. + /// Thrown when a user with given username already exists. /// /// must not be null and must be a valid username. /// must not be null or empty. @@ -78,13 +88,12 @@ namespace Timeline.Services /// Only , , and will be used. /// If null, then not change. /// Other fields are ignored. - /// After modified, even if nothing is changed, version will increase. + /// Version will increase if password is changed. /// /// must be a valid username if set. /// can't be empty if set. /// must be a valid nickname if set. /// - /// Note: Whether is set or not, version will increase and not set to the specified value if there is one. /// /// Task ModifyUser(long id, User? info); @@ -97,6 +106,7 @@ namespace Timeline.Services /// Thrown when is null. /// Thrown when is of bad format or some fields in is bad. /// Thrown when user with given id does not exist. + /// Thrown when user with the newusername already exist. /// /// Only , and will be used. /// If null, then not change. @@ -184,7 +194,7 @@ namespace Timeline.Services private static void ThrowUsernameConflict() { - throw new ConfictException(ExceptionUsernameConflict); + throw new ConflictException(ExceptionUsernameConflict); } private static User CreateUserFromEntity(UserEntity entity) @@ -245,6 +255,21 @@ namespace Timeline.Services return CreateUserFromEntity(entity); } + public async Task GetUserIdByUsername(string username) + { + if (username == null) + throw new ArgumentNullException(nameof(username)); + + CheckUsernameFormat(username, nameof(username)); + + var entity = await _databaseContext.Users.Where(user => user.Username == username).Select(u => new { u.Id }).SingleOrDefaultAsync(); + + if (entity == null) + throw new UserNotExistException(username); + + return entity.Id; + } + public async Task GetUsers() { var entities = await _databaseContext.Users.ToArrayAsync(); @@ -325,6 +350,7 @@ namespace Timeline.Services if (password != null) { entity.Password = _passwordService.HashPassword(password); + entity.Version += 1; } var administrator = info.Administrator; @@ -339,8 +365,6 @@ namespace Timeline.Services entity.Nickname = nickname; } } - - entity.Version += 1; } diff --git a/Timeline/Services/UserTokenService.cs b/Timeline/Services/UserTokenService.cs index c246fdff..cf7286f4 100644 --- a/Timeline/Services/UserTokenService.cs +++ b/Timeline/Services/UserTokenService.cs @@ -49,7 +49,6 @@ namespace Timeline.Services private readonly JwtSecurityTokenHandler _tokenHandler = new JwtSecurityTokenHandler(); private SymmetricSecurityKey _tokenSecurityKey; - [System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1062:Validate arguments of public methods", Justification = "")] public JwtUserTokenService(IOptionsMonitor jwtConfig, IClock clock) { _jwtConfig = jwtConfig; -- cgit v1.2.3