From ffd50da0e45df6d1c5c27bff4a5b459f201fd7ef Mon Sep 17 00:00:00 2001 From: crupest Date: Wed, 28 Apr 2021 17:16:45 +0800 Subject: ... --- .../Services/Api/BookmarkTimelineService.cs | 8 +- .../Services/Api/HighlightTimelineService.cs | 8 +- BackEnd/Timeline/Services/Mapper/TimelineMapper.cs | 6 +- .../Services/Timeline/BasicTimelineService.cs | 57 ++---- .../Services/Timeline/IBasicTimelineService.cs | 36 ++++ .../Services/Timeline/ITimelinePostService.cs | 123 ++++++++++++ .../Timeline/Services/Timeline/ITimelineService.cs | 122 ++++++++++++ .../Services/Timeline/Resource.Designer.cs | 18 ++ BackEnd/Timeline/Services/Timeline/Resource.resx | 6 + .../Timeline/TimelineChangePropertyParams.cs | 13 ++ .../Timeline/Services/Timeline/TimelineHelper.cs | 21 ++ .../Services/Timeline/TimelinePostCreateRequest.cs | 17 ++ .../Timeline/TimelinePostCreateRequestData.cs | 16 ++ .../Services/Timeline/TimelinePostPatchRequest.cs | 10 + .../Services/Timeline/TimelinePostService.cs | 166 ++-------------- .../Timeline/Services/Timeline/TimelineService.cs | 213 +++------------------ .../Services/Timeline/TimelineServiceExtensions.cs | 19 ++ .../Services/Timeline/TimelineUserRelationship.cs | 14 ++ .../Timeline/TimelineUserRelationshipType.cs | 9 + .../Timeline/Services/Token/IUserTokenManager.cs | 4 +- .../Timeline/Services/Token/UserTokenManager.cs | 4 +- .../Timeline/Services/User/UserDeleteService.cs | 2 +- 22 files changed, 494 insertions(+), 398 deletions(-) create mode 100644 BackEnd/Timeline/Services/Timeline/IBasicTimelineService.cs create mode 100644 BackEnd/Timeline/Services/Timeline/ITimelinePostService.cs create mode 100644 BackEnd/Timeline/Services/Timeline/ITimelineService.cs create mode 100644 BackEnd/Timeline/Services/Timeline/TimelineChangePropertyParams.cs create mode 100644 BackEnd/Timeline/Services/Timeline/TimelineHelper.cs create mode 100644 BackEnd/Timeline/Services/Timeline/TimelinePostCreateRequest.cs create mode 100644 BackEnd/Timeline/Services/Timeline/TimelinePostCreateRequestData.cs create mode 100644 BackEnd/Timeline/Services/Timeline/TimelinePostPatchRequest.cs create mode 100644 BackEnd/Timeline/Services/Timeline/TimelineServiceExtensions.cs create mode 100644 BackEnd/Timeline/Services/Timeline/TimelineUserRelationship.cs create mode 100644 BackEnd/Timeline/Services/Timeline/TimelineUserRelationshipType.cs (limited to 'BackEnd/Timeline/Services') diff --git a/BackEnd/Timeline/Services/Api/BookmarkTimelineService.cs b/BackEnd/Timeline/Services/Api/BookmarkTimelineService.cs index cabc1db2..37b55199 100644 --- a/BackEnd/Timeline/Services/Api/BookmarkTimelineService.cs +++ b/BackEnd/Timeline/Services/Api/BookmarkTimelineService.cs @@ -96,7 +96,7 @@ namespace Timeline.Services.Api if (!await _userService.CheckUserExistenceAsync(userId)) throw new UserNotExistException(userId); - if (!await _timelineService.CheckExistence(timelineId)) + if (!await _timelineService.CheckTimelineExistenceAsync(timelineId)) throw new TimelineNotExistException(timelineId); if (await _database.BookmarkTimelines.AnyAsync(t => t.TimelineId == timelineId && t.UserId == userId)) @@ -128,7 +128,7 @@ namespace Timeline.Services.Api if (checkUserExistence && !await _userService.CheckUserExistenceAsync(userId)) throw new UserNotExistException(userId); - if (checkTimelineExistence && !await _timelineService.CheckExistence(timelineId)) + if (checkTimelineExistence && !await _timelineService.CheckTimelineExistenceAsync(timelineId)) throw new TimelineNotExistException(timelineId); return await _database.BookmarkTimelines.AnyAsync(b => b.TimelineId == timelineId && b.UserId == userId); @@ -139,7 +139,7 @@ namespace Timeline.Services.Api if (!await _userService.CheckUserExistenceAsync(userId)) throw new UserNotExistException(userId); - if (!await _timelineService.CheckExistence(timelineId)) + if (!await _timelineService.CheckTimelineExistenceAsync(timelineId)) throw new TimelineNotExistException(timelineId); var entity = await _database.BookmarkTimelines.SingleOrDefaultAsync(t => t.TimelineId == timelineId && t.UserId == userId); @@ -181,7 +181,7 @@ namespace Timeline.Services.Api if (!await _userService.CheckUserExistenceAsync(userId)) throw new UserNotExistException(userId); - if (!await _timelineService.CheckExistence(timelineId)) + if (!await _timelineService.CheckTimelineExistenceAsync(timelineId)) throw new TimelineNotExistException(timelineId); var entity = await _database.BookmarkTimelines.SingleOrDefaultAsync(t => t.UserId == userId && t.TimelineId == timelineId); diff --git a/BackEnd/Timeline/Services/Api/HighlightTimelineService.cs b/BackEnd/Timeline/Services/Api/HighlightTimelineService.cs index 419aa68d..8224f1fe 100644 --- a/BackEnd/Timeline/Services/Api/HighlightTimelineService.cs +++ b/BackEnd/Timeline/Services/Api/HighlightTimelineService.cs @@ -92,7 +92,7 @@ namespace Timeline.Services.Api public async Task AddHighlightTimeline(long timelineId, long? operatorId) { - if (!await _timelineService.CheckExistence(timelineId)) + if (!await _timelineService.CheckTimelineExistenceAsync(timelineId)) throw new TimelineNotExistException(timelineId); if (operatorId.HasValue && !await _userService.CheckUserExistenceAsync(operatorId.Value)) @@ -118,7 +118,7 @@ namespace Timeline.Services.Api public async Task RemoveHighlightTimeline(long timelineId, long? operatorId) { - if (!await _timelineService.CheckExistence(timelineId)) + if (!await _timelineService.CheckTimelineExistenceAsync(timelineId)) throw new TimelineNotExistException(timelineId); if (operatorId.HasValue && !await _userService.CheckUserExistenceAsync(operatorId.Value)) @@ -146,7 +146,7 @@ namespace Timeline.Services.Api public async Task MoveHighlightTimeline(long timelineId, long newPosition) { - if (!await _timelineService.CheckExistence(timelineId)) + if (!await _timelineService.CheckTimelineExistenceAsync(timelineId)) throw new TimelineNotExistException(timelineId); var entity = await _database.HighlightTimelines.SingleOrDefaultAsync(t => t.TimelineId == timelineId); @@ -185,7 +185,7 @@ namespace Timeline.Services.Api public async Task IsHighlightTimeline(long timelineId, bool checkTimelineExistence = true) { - if (checkTimelineExistence && !await _timelineService.CheckExistence(timelineId)) + if (checkTimelineExistence && !await _timelineService.CheckTimelineExistenceAsync(timelineId)) throw new TimelineNotExistException(timelineId); return await _database.HighlightTimelines.AnyAsync(t => t.TimelineId == timelineId); diff --git a/BackEnd/Timeline/Services/Mapper/TimelineMapper.cs b/BackEnd/Timeline/Services/Mapper/TimelineMapper.cs index 5d823a04..c8279b42 100644 --- a/BackEnd/Timeline/Services/Mapper/TimelineMapper.cs +++ b/BackEnd/Timeline/Services/Mapper/TimelineMapper.cs @@ -49,7 +49,7 @@ namespace Timeline.Services.Mapper } else { - manageable = await _timelineService.HasManagePermission(entity.Id, userId.Value); + manageable = await _timelineService.HasManagePermissionAsync(entity.Id, userId.Value); } bool postable; @@ -59,7 +59,7 @@ namespace Timeline.Services.Mapper } else { - postable = await _timelineService.IsMemberOf(entity.Id, userId.Value); + postable = await _timelineService.IsMemberOfAsync(entity.Id, userId.Value); } return new HttpTimeline( @@ -123,7 +123,7 @@ namespace Timeline.Services.Mapper } else { - editable = await _timelinePostService.HasPostModifyPermission(entity.TimelineId, entity.LocalId, userId.Value); + editable = await _timelinePostService.HasPostModifyPermissionAsync(entity.TimelineId, entity.LocalId, userId.Value); } diff --git a/BackEnd/Timeline/Services/Timeline/BasicTimelineService.cs b/BackEnd/Timeline/Services/Timeline/BasicTimelineService.cs index d633be4d..7476a860 100644 --- a/BackEnd/Timeline/Services/Timeline/BasicTimelineService.cs +++ b/BackEnd/Timeline/Services/Timeline/BasicTimelineService.cs @@ -1,4 +1,5 @@ using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Logging; using System; using System.Collections.Generic; using System.Linq; @@ -10,39 +11,10 @@ using Timeline.Services.User; namespace Timeline.Services.Timeline { - /// - /// This service provide some basic timeline functions, which should be used internally for other services. - /// - public interface IBasicTimelineService - { - /// - /// Check whether a timeline with given id exists without getting full info. - /// - /// The timeline id. - /// True if exist. Otherwise false. - Task CheckExistence(long id); - - /// - /// Get the timeline id by name. - /// - /// Timeline name. - /// Id of the timeline. - /// Thrown when is null. - /// Throw when is of bad format. - /// - /// Thrown when timeline with name does not exist. - /// If it is a personal timeline, then inner exception is . - /// - /// - /// If name is of personal timeline and the timeline does not exist, it will be created if user exists. - /// If the user does not exist, will be thrown with as inner exception. - /// - Task GetTimelineIdByName(string timelineName); - } - - public class BasicTimelineService : IBasicTimelineService { + private readonly ILogger _logger; + private readonly DatabaseContext _database; private readonly IBasicUserService _basicUserService; @@ -50,8 +22,9 @@ namespace Timeline.Services.Timeline private readonly GeneralTimelineNameValidator _generalTimelineNameValidator = new GeneralTimelineNameValidator(); - public BasicTimelineService(DatabaseContext database, IBasicUserService basicUserService, IClock clock) + public BasicTimelineService(ILoggerFactory loggerFactory, DatabaseContext database, IBasicUserService basicUserService, IClock clock) { + _logger = loggerFactory.CreateLogger(); _database = database; _basicUserService = basicUserService; _clock = clock; @@ -74,27 +47,33 @@ namespace Timeline.Services.Timeline }; } - public async Task CheckExistence(long id) + protected void CheckGeneralTimelineName(string timelineName, string? paramName) + { + if (!_generalTimelineNameValidator.Validate(timelineName, out var message)) + throw new ArgumentException(string.Format(Resource.ExceptionGeneralTimelineNameBadFormat, message), paramName); + } + + public async Task CheckTimelineExistenceAsync(long id) { return await _database.Timelines.AnyAsync(t => t.Id == id); } - public async Task GetTimelineIdByName(string timelineName) + public async Task GetTimelineIdByNameAsync(string timelineName) { if (timelineName == null) throw new ArgumentNullException(nameof(timelineName)); - if (!_generalTimelineNameValidator.Validate(timelineName, out var message)) - throw new ArgumentException(message); + CheckGeneralTimelineName(timelineName, nameof(timelineName)); - timelineName = TimelineHelper.ExtractTimelineName(timelineName, out var isPersonal); + var name = TimelineHelper.ExtractTimelineName(timelineName, out var isPersonal); if (isPersonal) { + var username = name; long userId; try { - userId = await _basicUserService.GetUserIdByUsernameAsync(timelineName); + userId = await _basicUserService.GetUserIdByUsernameAsync(username); } catch (UserNotExistException e) { @@ -113,6 +92,8 @@ namespace Timeline.Services.Timeline _database.Timelines.Add(newTimelineEntity); await _database.SaveChangesAsync(); + _logger.LogInformation(Resource.LogPersonalTimelineAutoCreate, username); + return newTimelineEntity.Id; } } diff --git a/BackEnd/Timeline/Services/Timeline/IBasicTimelineService.cs b/BackEnd/Timeline/Services/Timeline/IBasicTimelineService.cs new file mode 100644 index 00000000..b32fa3f7 --- /dev/null +++ b/BackEnd/Timeline/Services/Timeline/IBasicTimelineService.cs @@ -0,0 +1,36 @@ +using System; +using System.Threading.Tasks; +using Timeline.Services.User; + +namespace Timeline.Services.Timeline +{ + /// + /// This service provide some basic timeline functions, which should be used internally for other services. + /// + public interface IBasicTimelineService + { + /// + /// Check whether a timeline with given id exists without getting full info. + /// + /// The timeline id. + /// True if exist. Otherwise false. + Task CheckTimelineExistenceAsync(long id); + + /// + /// Get the timeline id by name. + /// + /// Timeline name. + /// Id of the timeline. + /// Thrown when is null. + /// Throw when is of bad format. + /// + /// Thrown when timeline with name does not exist. + /// If it is a personal timeline, then inner exception is . + /// + /// + /// If name is of personal timeline and the timeline does not exist, it will be created if user exists. + /// If the user does not exist, will be thrown with as inner exception. + /// + Task GetTimelineIdByNameAsync(string timelineName); + } +} diff --git a/BackEnd/Timeline/Services/Timeline/ITimelinePostService.cs b/BackEnd/Timeline/Services/Timeline/ITimelinePostService.cs new file mode 100644 index 00000000..50af9fc2 --- /dev/null +++ b/BackEnd/Timeline/Services/Timeline/ITimelinePostService.cs @@ -0,0 +1,123 @@ +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using Timeline.Entities; +using Timeline.Helpers.Cache; +using Timeline.Models; +using Timeline.Services.Imaging; +using Timeline.Services.User; + +namespace Timeline.Services.Timeline +{ + public interface ITimelinePostService + { + /// + /// Get all the posts in the timeline. + /// + /// The id of the timeline. + /// The time that posts have been modified since. + /// Whether include deleted posts. + /// A list of all posts. + /// Thrown when timeline does not exist. + Task> GetPostsAsync(long timelineId, DateTime? modifiedSince = null, bool includeDeleted = false); + + /// + /// Get a post of a timeline. + /// + /// The id of the timeline of the post. + /// The id of the post. + /// If true, return the entity even if it is deleted. + /// The post. + /// Thrown when timeline does not exist. + /// Thrown when post of does not exist or has been deleted. + Task GetPostAsync(long timelineId, long postId, bool includeDeleted = false); + + /// + /// Get the data digest of a post. + /// + /// The timeline id. + /// The post id. + /// The index of the data. + /// The data digest. + /// Thrown when timeline does not exist. + /// Thrown when post of does not exist or has been deleted. + /// Thrown when data of that index does not exist. + Task GetPostDataDigestAsync(long timelineId, long postId, long dataIndex); + + /// + /// Get the data of a post. + /// + /// The timeline id. + /// The post id. + /// The index of the data. + /// The data. + /// Thrown when timeline does not exist. + /// Thrown when post of does not exist or has been deleted. + /// Thrown when data of that index does not exist. + Task GetPostDataAsync(long timelineId, long postId, long dataIndex); + + /// + /// Create a new post in timeline. + /// + /// The id of the timeline to create post against. + /// The author's user id. + /// Info about the post. + /// The entity of the created post. + /// Thrown when is null. + /// Thrown when is of invalid format. + /// Thrown when timeline does not exist. + /// Thrown if user of does not exist. + /// Thrown if data is not a image. Validated by . + Task CreatePostAsync(long timelineId, long authorId, TimelinePostCreateRequest request); + + /// + /// Modify a post. Change its properties or replace its content. + /// + /// The timeline id. + /// The post id. + /// The request. + /// The entity of the patched post. + /// Thrown when is null. + /// Thrown when is of invalid format. + /// Thrown when timeline does not exist. + /// Thrown when post does not exist. + Task PatchPostAsync(long timelineId, long postId, TimelinePostPatchRequest request); + + /// + /// Delete a post. + /// + /// The id of the timeline to delete post against. + /// The id of the post to delete. + /// Thrown when timeline does not exist. + /// Thrown when the post with given id does not exist or is deleted already. + /// + /// First use to check the permission. + /// + Task DeletePostAsync(long timelineId, long postId); + + /// + /// Delete all posts of the given user. Used when delete a user. + /// + /// The id of the user. + Task DeleteAllPostsOfUserAsync(long userId); + + /// + /// Verify whether a user has the permission to modify a post. + /// + /// The id of the timeline. + /// The id of the post. + /// The id of the user to check on. + /// True if you want it to throw . Default false. + /// True if can modify, false if can't modify. + /// Thrown when timeline does not exist. + /// Thrown when the post with given id does not exist or is deleted already and is true. + /// + /// Unless is true, this method should return true if the post does not exist. + /// If the post is deleted, its author info still exists, so it is checked as the post is not deleted unless is true. + /// 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 HasPostModifyPermissionAsync(long timelineId, long postId, long modifierId, bool throwOnPostNotExist = false); + } +} diff --git a/BackEnd/Timeline/Services/Timeline/ITimelineService.cs b/BackEnd/Timeline/Services/Timeline/ITimelineService.cs new file mode 100644 index 00000000..9c313b0b --- /dev/null +++ b/BackEnd/Timeline/Services/Timeline/ITimelineService.cs @@ -0,0 +1,122 @@ +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using Timeline.Entities; +using Timeline.Models; +using Timeline.Services.User; + +namespace Timeline.Services.Timeline +{ + /// + /// This define the interface of both personal timeline and ordinary timeline. + /// + public interface ITimelineService : IBasicTimelineService + { + /// + /// Get the timeline info. + /// + /// Id of timeline. + /// The timeline info. + /// Thrown when timeline does not exist. + Task GetTimelineAsync(long id); + + /// + /// Set the properties of a timeline. + /// + /// The id of the timeline. + /// The new properties. Null member means not to change. + /// Thrown when is null. + /// Thrown when timeline with given id does not exist. + /// Thrown when a timeline with new name already exists. + Task ChangePropertyAsync(long id, TimelineChangePropertyParams newProperties); + + /// + /// Add a member to timeline. + /// + /// Timeline id. + /// User id. + /// True if the memeber was added. False if it is already a member. + /// Thrown when timeline does not exist. + /// Thrown when the user does not exist. + Task AddMemberAsync(long timelineId, long userId); + + /// + /// Remove a member from timeline. + /// + /// Timeline id. + /// User id. + /// True if the memeber was removed. False if it was not a member before. + /// Thrown when timeline does not exist. + /// Thrown when the user does not exist. + Task RemoveMemberAsync(long timelineId, long userId); + + /// + /// Check whether a user can manage(change timeline info, member, ...) a timeline. + /// + /// The id of the timeline. + /// The id of the user to check on. + /// True if the user can manage the timeline, otherwise false. + /// Thrown when timeline does not exist. + /// + /// This method does not check whether visitor is administrator. + /// Return false if user with user id does not exist. + /// + Task HasManagePermissionAsync(long timelineId, long userId); + + /// + /// Verify whether a visitor has the permission to read a timeline. + /// + /// The id of the timeline. + /// The id of the user to check on. Null means visitor without account. + /// True if can read, false if can't read. + /// Thrown when timeline does not exist. + /// + /// This method does not check whether visitor is administrator. + /// Return false if user with visitor id does not exist. + /// + Task HasReadPermissionAsync(long timelineId, long? visitorId); + + /// + /// Verify whether a user is member of a timeline. + /// + /// The id of the timeline. + /// The id of user to check on. + /// True if it is a member, false if not. + /// Thrown when timeline does not exist. + /// + /// Timeline owner is also considered as a member. + /// Return false when user with user id does not exist. + /// + Task IsMemberOfAsync(long timelineId, long userId); + + /// + /// Get all timelines including personal and ordinary timelines. + /// + /// Filter timelines related (own or is a member) to specific user. + /// Filter timelines with given visibility. If null or empty, all visibilities are returned. Duplicate value are ignored. + /// The list of timelines. + /// + /// If user with related user id does not exist, empty list will be returned. + /// + Task> GetTimelinesAsync(TimelineUserRelationship? relate = null, List? visibility = null); + + /// + /// Create a timeline. + /// + /// The name of the timeline. + /// The id of owner of the timeline. + /// The info of the new timeline. + /// Thrown when is null. + /// Thrown when timeline name is invalid. + /// Thrown when the timeline already exists. + /// Thrown when the owner user does not exist. + Task CreateTimelineAsync(string timelineName, long ownerId); + + /// + /// Delete a timeline. + /// + /// The id of the timeline to delete. + /// Thrown when the timeline does not exist. + Task DeleteTimelineAsync(long id); + } +} diff --git a/BackEnd/Timeline/Services/Timeline/Resource.Designer.cs b/BackEnd/Timeline/Services/Timeline/Resource.Designer.cs index 31fd6320..6faea295 100644 --- a/BackEnd/Timeline/Services/Timeline/Resource.Designer.cs +++ b/BackEnd/Timeline/Services/Timeline/Resource.Designer.cs @@ -60,6 +60,15 @@ namespace Timeline.Services.Timeline { } } + /// + /// Looks up a localized string similar to This timeline name is neither a valid personal timeline name nor a valid ordinary timeline name. {0}. + /// + internal static string ExceptionGeneralTimelineNameBadFormat { + get { + return ResourceManager.GetString("ExceptionGeneralTimelineNameBadFormat", resourceCulture); + } + } + /// /// Looks up a localized string similar to Timeline with given constraints already exist.. /// @@ -113,5 +122,14 @@ namespace Timeline.Services.Timeline { return ResourceManager.GetString("ExceptionTimelinePostNoExistReasonNotCreated", resourceCulture); } } + + /// + /// Looks up a localized string similar to A personal timeline for user with username={0} is created automatically.. + /// + internal static string LogPersonalTimelineAutoCreate { + get { + return ResourceManager.GetString("LogPersonalTimelineAutoCreate", resourceCulture); + } + } } } diff --git a/BackEnd/Timeline/Services/Timeline/Resource.resx b/BackEnd/Timeline/Services/Timeline/Resource.resx index 7fd7b5c7..3a233e55 100644 --- a/BackEnd/Timeline/Services/Timeline/Resource.resx +++ b/BackEnd/Timeline/Services/Timeline/Resource.resx @@ -117,6 +117,9 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + This timeline name is neither a valid personal timeline name nor a valid ordinary timeline name. {0} + Timeline with given constraints already exist. @@ -135,4 +138,7 @@ it has not been created + + A personal timeline for user with username={0} is created automatically. + \ No newline at end of file diff --git a/BackEnd/Timeline/Services/Timeline/TimelineChangePropertyParams.cs b/BackEnd/Timeline/Services/Timeline/TimelineChangePropertyParams.cs new file mode 100644 index 00000000..57c5c90c --- /dev/null +++ b/BackEnd/Timeline/Services/Timeline/TimelineChangePropertyParams.cs @@ -0,0 +1,13 @@ +using Timeline.Models; + +namespace Timeline.Services.Timeline +{ + public class TimelineChangePropertyParams + { + public string? Name { get; set; } + public string? Title { get; set; } + public string? Description { get; set; } + public TimelineVisibility? Visibility { get; set; } + public string? Color { get; set; } + } +} diff --git a/BackEnd/Timeline/Services/Timeline/TimelineHelper.cs b/BackEnd/Timeline/Services/Timeline/TimelineHelper.cs new file mode 100644 index 00000000..6c75f04c --- /dev/null +++ b/BackEnd/Timeline/Services/Timeline/TimelineHelper.cs @@ -0,0 +1,21 @@ +using System; + +namespace Timeline.Services.Timeline +{ + public static class TimelineHelper + { + public static string ExtractTimelineName(string name, out bool isPersonal) + { + if (name.StartsWith("@", StringComparison.OrdinalIgnoreCase)) + { + isPersonal = true; + return name[1..]; + } + else + { + isPersonal = false; + return name; + } + } + } +} diff --git a/BackEnd/Timeline/Services/Timeline/TimelinePostCreateRequest.cs b/BackEnd/Timeline/Services/Timeline/TimelinePostCreateRequest.cs new file mode 100644 index 00000000..1a56e11a --- /dev/null +++ b/BackEnd/Timeline/Services/Timeline/TimelinePostCreateRequest.cs @@ -0,0 +1,17 @@ +using System; +using System.Collections.Generic; + +namespace Timeline.Services.Timeline +{ + public class TimelinePostCreateRequest + { + public string? Color { get; set; } + + /// If not set, current time is used. + public DateTime? Time { get; set; } + +#pragma warning disable CA2227 + public List DataList { get; set; } = new List(); +#pragma warning restore CA2227 + } +} diff --git a/BackEnd/Timeline/Services/Timeline/TimelinePostCreateRequestData.cs b/BackEnd/Timeline/Services/Timeline/TimelinePostCreateRequestData.cs new file mode 100644 index 00000000..a8ea3c3b --- /dev/null +++ b/BackEnd/Timeline/Services/Timeline/TimelinePostCreateRequestData.cs @@ -0,0 +1,16 @@ +namespace Timeline.Services.Timeline +{ + public class TimelinePostCreateRequestData + { + public TimelinePostCreateRequestData(string contentType, byte[] data) + { + ContentType = contentType; + Data = data; + } + + public string ContentType { get; set; } +#pragma warning disable CA1819 // Properties should not return arrays + public byte[] Data { get; set; } +#pragma warning restore CA1819 // Properties should not return arrays + } +} diff --git a/BackEnd/Timeline/Services/Timeline/TimelinePostPatchRequest.cs b/BackEnd/Timeline/Services/Timeline/TimelinePostPatchRequest.cs new file mode 100644 index 00000000..92403a07 --- /dev/null +++ b/BackEnd/Timeline/Services/Timeline/TimelinePostPatchRequest.cs @@ -0,0 +1,10 @@ +using System; + +namespace Timeline.Services.Timeline +{ + public class TimelinePostPatchRequest + { + public string? Color { get; set; } + public DateTime? Time { get; set; } + } +} diff --git a/BackEnd/Timeline/Services/Timeline/TimelinePostService.cs b/BackEnd/Timeline/Services/Timeline/TimelinePostService.cs index d18e65e0..65a01b37 100644 --- a/BackEnd/Timeline/Services/Timeline/TimelinePostService.cs +++ b/BackEnd/Timeline/Services/Timeline/TimelinePostService.cs @@ -16,150 +16,6 @@ using Timeline.Services.User; namespace Timeline.Services.Timeline { - public class TimelinePostCreateRequestData - { - public TimelinePostCreateRequestData(string contentType, byte[] data) - { - ContentType = contentType; - Data = data; - } - - public string ContentType { get; set; } -#pragma warning disable CA1819 // Properties should not return arrays - public byte[] Data { get; set; } -#pragma warning restore CA1819 // Properties should not return arrays - } - - public class TimelinePostCreateRequest - { - public string? Color { get; set; } - - /// If not set, current time is used. - public DateTime? Time { get; set; } - -#pragma warning disable CA2227 - public List DataList { get; set; } = new List(); -#pragma warning restore CA2227 - } - - public class TimelinePostPatchRequest - { - public string? Color { get; set; } - public DateTime? Time { get; set; } - } - - public interface ITimelinePostService - { - /// - /// Get all the posts in the timeline. - /// - /// The id of the timeline. - /// The time that posts have been modified since. - /// Whether include deleted posts. - /// A list of all posts. - /// Thrown when timeline does not exist. - Task> GetPosts(long timelineId, DateTime? modifiedSince = null, bool includeDeleted = false); - - /// - /// Get a post of a timeline. - /// - /// The id of the timeline of the post. - /// The id of the post. - /// If true, return the entity even if it is deleted. - /// The post. - /// Thrown when timeline does not exist. - /// Thrown when post of does not exist or has been deleted. - Task GetPost(long timelineId, long postId, bool includeDeleted = false); - - /// - /// Get the data digest of a post. - /// - /// The timeline id. - /// The post id. - /// The index of the data. - /// The data digest. - /// Thrown when timeline does not exist. - /// Thrown when post of does not exist or has been deleted. - /// Thrown when data of that index does not exist. - Task GetPostDataDigest(long timelineId, long postId, long dataIndex); - - /// - /// Get the data of a post. - /// - /// The timeline id. - /// The post id. - /// The index of the data. - /// The data. - /// Thrown when timeline does not exist. - /// Thrown when post of does not exist or has been deleted. - /// Thrown when data of that index does not exist. - Task GetPostData(long timelineId, long postId, long dataIndex); - - /// - /// Create a new post in timeline. - /// - /// The id of the timeline to create post against. - /// The author's user id. - /// Info about the post. - /// The entity of the created post. - /// Thrown when is null. - /// Thrown when is of invalid format. - /// Thrown when timeline does not exist. - /// Thrown if user of does not exist. - /// Thrown if data is not a image. Validated by . - Task CreatePost(long timelineId, long authorId, TimelinePostCreateRequest request); - - /// - /// Modify a post. Change its properties or replace its content. - /// - /// The timeline id. - /// The post id. - /// The request. - /// The entity of the patched post. - /// Thrown when is null. - /// Thrown when is of invalid format. - /// Thrown when timeline does not exist. - /// Thrown when post does not exist. - Task PatchPost(long timelineId, long postId, TimelinePostPatchRequest request); - - /// - /// Delete a post. - /// - /// The id of the timeline to delete post against. - /// The id of the post to delete. - /// Thrown when timeline does not exist. - /// Thrown when the post with given id does not exist or is deleted already. - /// - /// First use to check the permission. - /// - Task DeletePost(long timelineId, long postId); - - /// - /// Delete all posts of the given user. Used when delete a user. - /// - /// The id of the user. - Task DeleteAllPostsOfUser(long userId); - - /// - /// Verify whether a user has the permission to modify a post. - /// - /// The id of the timeline. - /// The id of the post. - /// The id of the user to check on. - /// True if you want it to throw . Default false. - /// True if can modify, false if can't modify. - /// Thrown when timeline does not exist. - /// Thrown when the post with given id does not exist or is deleted already and is true. - /// - /// Unless is true, this method should return true if the post does not exist. - /// If the post is deleted, its author info still exists, so it is checked as the post is not deleted unless is true. - /// 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(long timelineId, long postId, long modifierId, bool throwOnPostNotExist = false); - } - public class TimelinePostService : ITimelinePostService { private readonly ILogger _logger; @@ -184,7 +40,7 @@ namespace Timeline.Services.Timeline private async Task CheckTimelineExistence(long timelineId) { - if (!await _basicTimelineService.CheckExistence(timelineId)) + if (!await _basicTimelineService.CheckTimelineExistenceAsync(timelineId)) throw new TimelineNotExistException(timelineId); } @@ -194,7 +50,7 @@ namespace Timeline.Services.Timeline throw new UserNotExistException(userId); } - public async Task> GetPosts(long timelineId, DateTime? modifiedSince = null, bool includeDeleted = false) + public async Task> GetPostsAsync(long timelineId, DateTime? modifiedSince = null, bool includeDeleted = false) { await CheckTimelineExistence(timelineId); @@ -217,7 +73,7 @@ namespace Timeline.Services.Timeline return await query.ToListAsync(); } - public async Task GetPost(long timelineId, long postId, bool includeDeleted = false) + public async Task GetPostAsync(long timelineId, long postId, bool includeDeleted = false) { await CheckTimelineExistence(timelineId); @@ -236,7 +92,7 @@ namespace Timeline.Services.Timeline return post; } - public async Task GetPostDataDigest(long timelineId, long postId, long dataIndex) + public async Task GetPostDataDigestAsync(long timelineId, long postId, long dataIndex) { await CheckTimelineExistence(timelineId); @@ -256,7 +112,7 @@ namespace Timeline.Services.Timeline return new CacheableDataDigest(dataEntity.DataTag, dataEntity.LastUpdated); } - public async Task GetPostData(long timelineId, long postId, long dataIndex) + public async Task GetPostDataAsync(long timelineId, long postId, long dataIndex) { await CheckTimelineExistence(timelineId); @@ -278,7 +134,7 @@ namespace Timeline.Services.Timeline return new ByteData(data, dataEntity.Kind); } - public async Task CreatePost(long timelineId, long authorId, TimelinePostCreateRequest request) + public async Task CreatePostAsync(long timelineId, long authorId, TimelinePostCreateRequest request) { if (request is null) throw new ArgumentNullException(nameof(request)); @@ -382,7 +238,7 @@ namespace Timeline.Services.Timeline return postEntity; } - public async Task PatchPost(long timelineId, long postId, TimelinePostPatchRequest request) + public async Task PatchPostAsync(long timelineId, long postId, TimelinePostPatchRequest request) { if (request is null) throw new ArgumentNullException(nameof(request)); @@ -417,7 +273,7 @@ namespace Timeline.Services.Timeline return entity; } - public async Task DeletePost(long timelineId, long postId) + public async Task DeletePostAsync(long timelineId, long postId) { await CheckTimelineExistence(timelineId); @@ -448,17 +304,17 @@ namespace Timeline.Services.Timeline await transaction.CommitAsync(); } - public async Task DeleteAllPostsOfUser(long userId) + public async Task DeleteAllPostsOfUserAsync(long userId) { var postEntities = await _database.TimelinePosts.Where(p => p.AuthorId == userId).Select(p => new { p.TimelineId, p.LocalId }).ToListAsync(); foreach (var postEntity in postEntities) { - await this.DeletePost(postEntity.TimelineId, postEntity.LocalId); + await this.DeletePostAsync(postEntity.TimelineId, postEntity.LocalId); } } - public async Task HasPostModifyPermission(long timelineId, long postId, long modifierId, bool throwOnPostNotExist = false) + public async Task HasPostModifyPermissionAsync(long timelineId, long postId, long modifierId, bool throwOnPostNotExist = false) { await CheckTimelineExistence(timelineId); diff --git a/BackEnd/Timeline/Services/Timeline/TimelineService.cs b/BackEnd/Timeline/Services/Timeline/TimelineService.cs index 0086726e..cea93272 100644 --- a/BackEnd/Timeline/Services/Timeline/TimelineService.cs +++ b/BackEnd/Timeline/Services/Timeline/TimelineService.cs @@ -1,4 +1,5 @@ using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Logging; using System; using System.Collections.Generic; using System.Linq; @@ -10,173 +11,10 @@ using Timeline.Services.User; namespace Timeline.Services.Timeline { - public static class TimelineHelper - { - public static string ExtractTimelineName(string name, out bool isPersonal) - { - if (name.StartsWith("@", StringComparison.OrdinalIgnoreCase)) - { - isPersonal = true; - return name[1..]; - } - else - { - isPersonal = false; - return name; - } - } - } - - public enum TimelineUserRelationshipType - { - Own = 0b1, - Join = 0b10, - Default = Own | Join - } - - public class TimelineUserRelationship - { - public TimelineUserRelationship(TimelineUserRelationshipType type, long userId) - { - Type = type; - UserId = userId; - } - - public TimelineUserRelationshipType Type { get; set; } - public long UserId { get; set; } - } - - public class TimelineChangePropertyParams - { - public string? Name { get; set; } - public string? Title { get; set; } - public string? Description { get; set; } - public TimelineVisibility? Visibility { get; set; } - public string? Color { get; set; } - } - - /// - /// This define the interface of both personal timeline and ordinary timeline. - /// - public interface ITimelineService : IBasicTimelineService - { - /// - /// Get the timeline info. - /// - /// Id of timeline. - /// The timeline info. - /// Thrown when timeline does not exist. - Task GetTimeline(long id); - - /// - /// Set the properties of a timeline. - /// - /// The id of the timeline. - /// The new properties. Null member means not to change. - /// Thrown when is null. - /// Thrown when timeline with given id does not exist. - /// Thrown when a timeline with new name already exists. - Task ChangeProperty(long id, TimelineChangePropertyParams newProperties); - - /// - /// Add a member to timeline. - /// - /// Timeline id. - /// User id. - /// True if the memeber was added. False if it is already a member. - /// Thrown when timeline does not exist. - /// Thrown when the user does not exist. - Task AddMember(long timelineId, long userId); - - /// - /// Remove a member from timeline. - /// - /// Timeline id. - /// User id. - /// True if the memeber was removed. False if it was not a member before. - /// Thrown when timeline does not exist. - /// Thrown when the user does not exist. - Task RemoveMember(long timelineId, long userId); - - /// - /// Check whether a user can manage(change timeline info, member, ...) a timeline. - /// - /// The id of the timeline. - /// The id of the user to check on. - /// True if the user can manage the timeline, otherwise false. - /// Thrown when timeline does not exist. - /// - /// This method does not check whether visitor is administrator. - /// Return false if user with user id does not exist. - /// - Task HasManagePermission(long timelineId, long userId); - - /// - /// Verify whether a visitor has the permission to read a timeline. - /// - /// The id of the timeline. - /// The id of the user to check on. Null means visitor without account. - /// True if can read, false if can't read. - /// Thrown when timeline does not exist. - /// - /// This method does not check whether visitor is administrator. - /// Return false if user with visitor id does not exist. - /// - Task HasReadPermission(long timelineId, long? visitorId); - - /// - /// Verify whether a user is member of a timeline. - /// - /// The id of the timeline. - /// The id of user to check on. - /// True if it is a member, false if not. - /// Thrown when timeline does not exist. - /// - /// Timeline owner is also considered as a member. - /// Return false when user with user id does not exist. - /// - Task IsMemberOf(long timelineId, long userId); - - /// - /// Get all timelines including personal and ordinary timelines. - /// - /// Filter timelines related (own or is a member) to specific user. - /// Filter timelines with given visibility. If null or empty, all visibilities are returned. Duplicate value are ignored. - /// The list of timelines. - /// - /// If user with related user id does not exist, empty list will be returned. - /// - Task> GetTimelines(TimelineUserRelationship? relate = null, List? visibility = null); - - /// - /// Create a timeline. - /// - /// The name of the timeline. - /// The id of owner of the timeline. - /// The info of the new timeline. - /// Thrown when is null. - /// Thrown when timeline name is invalid. - /// Thrown when the timeline already exists. - /// Thrown when the owner user does not exist. - Task CreateTimeline(string timelineName, long ownerId); - - /// - /// Delete a timeline. - /// - /// The id of the timeline to delete. - /// Thrown when the timeline does not exist. - Task DeleteTimeline(long id); - } public class TimelineService : BasicTimelineService, ITimelineService { - public TimelineService(DatabaseContext database, IBasicUserService userService, IClock clock) - : base(database, userService, clock) - { - _database = database; - _userService = userService; - _clock = clock; - } + private readonly ILogger _logger; private readonly DatabaseContext _database; @@ -188,6 +26,16 @@ namespace Timeline.Services.Timeline private readonly ColorValidator _colorValidator = new ColorValidator(); + public TimelineService(ILoggerFactory loggerFactory, DatabaseContext database, IBasicUserService userService, IClock clock) + : base(loggerFactory, database, userService, clock) + { + _logger = loggerFactory.CreateLogger(); + _database = database; + _userService = userService; + _clock = clock; + } + + private void ValidateTimelineName(string name, string paramName) { if (!_timelineNameValidator.Validate(name, out var message)) @@ -196,7 +44,7 @@ namespace Timeline.Services.Timeline } } - public async Task GetTimeline(long id) + public async Task GetTimelineAsync(long id) { var entity = await _database.Timelines.Where(t => t.Id == id).SingleOrDefaultAsync(); @@ -206,7 +54,7 @@ namespace Timeline.Services.Timeline return entity; } - public async Task ChangeProperty(long id, TimelineChangePropertyParams newProperties) + public async Task ChangePropertyAsync(long id, TimelineChangePropertyParams newProperties) { if (newProperties is null) throw new ArgumentNullException(nameof(newProperties)); @@ -279,9 +127,9 @@ namespace Timeline.Services.Timeline await _database.SaveChangesAsync(); } - public async Task AddMember(long timelineId, long userId) + public async Task AddMemberAsync(long timelineId, long userId) { - if (!await CheckExistence(timelineId)) + if (!await CheckTimelineExistenceAsync(timelineId)) throw new TimelineNotExistException(timelineId); if (!await _userService.CheckUserExistenceAsync(userId)) @@ -301,9 +149,9 @@ namespace Timeline.Services.Timeline return true; } - public async Task RemoveMember(long timelineId, long userId) + public async Task RemoveMemberAsync(long timelineId, long userId) { - if (!await CheckExistence(timelineId)) + if (!await CheckTimelineExistenceAsync(timelineId)) throw new TimelineNotExistException(timelineId); if (!await _userService.CheckUserExistenceAsync(userId)) @@ -321,7 +169,7 @@ namespace Timeline.Services.Timeline return true; } - public async Task HasManagePermission(long timelineId, long userId) + public async Task HasManagePermissionAsync(long timelineId, long userId) { var entity = await _database.Timelines.Where(t => t.Id == timelineId).Select(t => new { t.OwnerId }).SingleOrDefaultAsync(); @@ -331,7 +179,7 @@ namespace Timeline.Services.Timeline return entity.OwnerId == userId; } - public async Task HasReadPermission(long timelineId, long? visitorId) + public async Task HasReadPermissionAsync(long timelineId, long? visitorId) { var entity = await _database.Timelines.Where(t => t.Id == timelineId).Select(t => new { t.Visibility }).SingleOrDefaultAsync(); @@ -355,7 +203,7 @@ namespace Timeline.Services.Timeline } } - public async Task IsMemberOf(long timelineId, long userId) + public async Task IsMemberOfAsync(long timelineId, long userId) { var entity = await _database.Timelines.Where(t => t.Id == timelineId).Select(t => new { t.OwnerId }).SingleOrDefaultAsync(); @@ -368,7 +216,7 @@ namespace Timeline.Services.Timeline return await _database.TimelineMembers.AnyAsync(m => m.TimelineId == timelineId && m.UserId == userId); } - public async Task> GetTimelines(TimelineUserRelationship? relate = null, List? visibility = null) + public async Task> GetTimelinesAsync(TimelineUserRelationship? relate = null, List? visibility = null) { List entities; @@ -405,7 +253,7 @@ namespace Timeline.Services.Timeline return entities; } - public async Task CreateTimeline(string name, long owner) + public async Task CreateTimelineAsync(string name, long owner) { if (name == null) throw new ArgumentNullException(nameof(name)); @@ -425,7 +273,7 @@ namespace Timeline.Services.Timeline return entity; } - public async Task DeleteTimeline(long id) + public async Task DeleteTimelineAsync(long id) { var entity = await _database.Timelines.Where(t => t.Id == id).SingleOrDefaultAsync(); @@ -436,17 +284,4 @@ namespace Timeline.Services.Timeline await _database.SaveChangesAsync(); } } - - public static class TimelineServiceExtensions - { - public static async Task> GetTimelineList(this ITimelineService service, IEnumerable ids) - { - var timelines = new List(); - foreach (var id in ids) - { - timelines.Add(await service.GetTimeline(id)); - } - return timelines; - } - } } diff --git a/BackEnd/Timeline/Services/Timeline/TimelineServiceExtensions.cs b/BackEnd/Timeline/Services/Timeline/TimelineServiceExtensions.cs new file mode 100644 index 00000000..7b745168 --- /dev/null +++ b/BackEnd/Timeline/Services/Timeline/TimelineServiceExtensions.cs @@ -0,0 +1,19 @@ +using System.Collections.Generic; +using System.Threading.Tasks; +using Timeline.Entities; + +namespace Timeline.Services.Timeline +{ + public static class TimelineServiceExtensions + { + public static async Task> GetTimelineList(this ITimelineService service, IEnumerable ids) + { + var timelines = new List(); + foreach (var id in ids) + { + timelines.Add(await service.GetTimelineAsync(id)); + } + return timelines; + } + } +} diff --git a/BackEnd/Timeline/Services/Timeline/TimelineUserRelationship.cs b/BackEnd/Timeline/Services/Timeline/TimelineUserRelationship.cs new file mode 100644 index 00000000..70789e6f --- /dev/null +++ b/BackEnd/Timeline/Services/Timeline/TimelineUserRelationship.cs @@ -0,0 +1,14 @@ +namespace Timeline.Services.Timeline +{ + public class TimelineUserRelationship + { + public TimelineUserRelationship(TimelineUserRelationshipType type, long userId) + { + Type = type; + UserId = userId; + } + + public TimelineUserRelationshipType Type { get; set; } + public long UserId { get; set; } + } +} diff --git a/BackEnd/Timeline/Services/Timeline/TimelineUserRelationshipType.cs b/BackEnd/Timeline/Services/Timeline/TimelineUserRelationshipType.cs new file mode 100644 index 00000000..4c063cc0 --- /dev/null +++ b/BackEnd/Timeline/Services/Timeline/TimelineUserRelationshipType.cs @@ -0,0 +1,9 @@ +namespace Timeline.Services.Timeline +{ + public enum TimelineUserRelationshipType + { + Own = 0b1, + Join = 0b10, + Default = Own | Join + } +} diff --git a/BackEnd/Timeline/Services/Token/IUserTokenManager.cs b/BackEnd/Timeline/Services/Token/IUserTokenManager.cs index c6eaa5b7..bdc1add3 100644 --- a/BackEnd/Timeline/Services/Token/IUserTokenManager.cs +++ b/BackEnd/Timeline/Services/Token/IUserTokenManager.cs @@ -18,7 +18,7 @@ namespace Timeline.Services.Token /// Thrown when is of bad format. /// Thrown when the user with does not exist. /// Thrown when is wrong. - public Task CreateToken(string username, string password, DateTime? expireAt = null); + public Task CreateTokenAsync(string username, string password, DateTime? expireAt = null); /// /// Verify a token and get the saved user info. This also check the database for existence of the user. @@ -30,6 +30,6 @@ namespace Timeline.Services.Token /// Thrown when the token is of bad version. /// Thrown when the token is of bad format. /// Thrown when the user specified by the token does not exist. Usually the user had been deleted after the token was issued. - public Task VerifyToken(string token); + public Task VerifyTokenAsync(string token); } } diff --git a/BackEnd/Timeline/Services/Token/UserTokenManager.cs b/BackEnd/Timeline/Services/Token/UserTokenManager.cs index 1d5348a5..5aa85a5e 100644 --- a/BackEnd/Timeline/Services/Token/UserTokenManager.cs +++ b/BackEnd/Timeline/Services/Token/UserTokenManager.cs @@ -26,7 +26,7 @@ namespace Timeline.Services.Token _clock = clock; } - public async Task CreateToken(string username, string password, DateTime? expireAt = null) + public async Task CreateTokenAsync(string username, string password, DateTime? expireAt = null) { expireAt = expireAt?.MyToUtc(); @@ -51,7 +51,7 @@ namespace Timeline.Services.Token } - public async Task VerifyToken(string token) + public async Task VerifyTokenAsync(string token) { if (token == null) throw new ArgumentNullException(nameof(token)); diff --git a/BackEnd/Timeline/Services/User/UserDeleteService.cs b/BackEnd/Timeline/Services/User/UserDeleteService.cs index e0391841..3e3e29e2 100644 --- a/BackEnd/Timeline/Services/User/UserDeleteService.cs +++ b/BackEnd/Timeline/Services/User/UserDeleteService.cs @@ -44,7 +44,7 @@ namespace Timeline.Services.User if (user.Id == 1) throw new InvalidOperationOnRootUserException(Resource.ExceptionDeleteRootUser); - await _timelinePostService.DeleteAllPostsOfUser(user.Id); + await _timelinePostService.DeleteAllPostsOfUserAsync(user.Id); _databaseContext.Users.Remove(user); -- cgit v1.2.3