From 4214c5dd5724bbe0bb8225534568dd4b0e904068 Mon Sep 17 00:00:00 2001 From: crupest Date: Thu, 18 Jun 2020 19:41:51 +0800 Subject: feat(back): Timeline service add post modified since. --- Timeline.Tests/Helpers/TestClock.cs | 2 +- Timeline.Tests/Services/TimelineServiceTest.cs | 34 +++++++++- .../Resources/Services/TimelineService.Designer.cs | 9 +++ Timeline/Resources/Services/TimelineService.resx | 3 + Timeline/Services/TimelineService.cs | 77 ++++++++++++++++------ 5 files changed, 101 insertions(+), 24 deletions(-) diff --git a/Timeline.Tests/Helpers/TestClock.cs b/Timeline.Tests/Helpers/TestClock.cs index de7d0eb7..0cbf236d 100644 --- a/Timeline.Tests/Helpers/TestClock.cs +++ b/Timeline.Tests/Helpers/TestClock.cs @@ -36,7 +36,7 @@ namespace Timeline.Tests.Helpers { if (_currentTime == null) return SetMockCurrentTime(); - _currentTime.Value.Add(timeSpan); + _currentTime += timeSpan; return _currentTime.Value; } } diff --git a/Timeline.Tests/Services/TimelineServiceTest.cs b/Timeline.Tests/Services/TimelineServiceTest.cs index cb2ade61..4f081b5d 100644 --- a/Timeline.Tests/Services/TimelineServiceTest.cs +++ b/Timeline.Tests/Services/TimelineServiceTest.cs @@ -63,11 +63,11 @@ namespace Timeline.Tests.Services [InlineData("tl")] public async Task Timeline_LastModified(string timelineName) { - _clock.ForwardCurrentTime(); + var initTime = _clock.ForwardCurrentTime(); void Check(Models.Timeline timeline) { - timeline.NameLastModified.Should().Be(_clock.GetCurrentTime()); + timeline.NameLastModified.Should().Be(initTime); timeline.LastModified.Should().Be(_clock.GetCurrentTime()); } @@ -90,5 +90,35 @@ namespace Timeline.Tests.Services await _timelineService.ChangeMember(timelineName, new List { "admin" }, null); await GetAndCheck(); } + + [Theory] + [InlineData("@user")] + [InlineData("tl")] + public async Task GetPosts_ModifiedSince(string timelineName) + { + _clock.ForwardCurrentTime(); + + var userId = await _userService.GetUserIdByUsername("user"); + + var _ = TimelineHelper.ExtractTimelineName(timelineName, out var isPersonal); + if (!isPersonal) + await _timelineService.CreateTimeline(timelineName, userId); + + var postContentList = new string[] { "a", "b", "c", "d" }; + + DateTime testPoint = new DateTime(); + + foreach (var (content, index) in postContentList.Select((v, i) => (v, i))) + { + var t = _clock.ForwardCurrentTime(); + if (index == 1) + testPoint = t; + await _timelineService.CreateTextPost(timelineName, userId, content, null); + } + + var posts = await _timelineService.GetPosts(timelineName, testPoint); + posts.Should().HaveCount(2) + .And.Subject.Select(p => (p.Content as TextTimelinePostContent).Text).Should().Equal(postContentList.Skip(2)); + } } } diff --git a/Timeline/Resources/Services/TimelineService.Designer.cs b/Timeline/Resources/Services/TimelineService.Designer.cs index cfc381f9..e16c1337 100644 --- a/Timeline/Resources/Services/TimelineService.Designer.cs +++ b/Timeline/Resources/Services/TimelineService.Designer.cs @@ -105,6 +105,15 @@ namespace Timeline.Resources.Services { } } + /// + /// Looks up a localized string similar to The post has been deleted because content of entity is null.. + /// + internal static string ExceptionPostDeleted { + get { + return ResourceManager.GetString("ExceptionPostDeleted", resourceCulture); + } + } + /// /// Looks up a localized string similar to The timeline name is of bad format.. /// diff --git a/Timeline/Resources/Services/TimelineService.resx b/Timeline/Resources/Services/TimelineService.resx index c4f49b30..9314f51b 100644 --- a/Timeline/Resources/Services/TimelineService.resx +++ b/Timeline/Resources/Services/TimelineService.resx @@ -141,4 +141,7 @@ Image format type of the post does not exist in column "extra_content". Normally this couldn't be possible because it should be saved when post was created. However, we now re-detect the format and save it. + + The post has been deleted because content of entity is null. + \ No newline at end of file diff --git a/Timeline/Services/TimelineService.cs b/Timeline/Services/TimelineService.cs index 6c1e91c6..e0b1b51d 100644 --- a/Timeline/Services/TimelineService.cs +++ b/Timeline/Services/TimelineService.cs @@ -105,6 +105,20 @@ namespace Timeline.Services /// Task> GetPosts(string timelineName); + /// + /// Get the posts that have been modified since a given time in the timeline. + /// + /// The name of the timeline. + /// The time that posts have been modified since. + /// A list of all posts. + /// 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 . + /// + Task> GetPosts(string timelineName, DateTime modifiedSince); + /// /// Get the etag of data of a post. /// @@ -383,6 +397,34 @@ namespace Timeline.Services }; } + private async Task MapTimelinePostFromEntity(TimelinePostEntity entity, string timelineName) + { + if (entity.Content == null) + { + throw new ArgumentException(ExceptionPostDeleted, nameof(entity)); + } + + var author = await _userService.GetUserById(entity.AuthorId); + + var type = entity.ContentType; + + ITimelinePostContent content = type switch + { + TimelinePostContentTypes.Text => new TextTimelinePostContent(entity.Content), + TimelinePostContentTypes.Image => new ImageTimelinePostContent(entity.Content), + _ => throw new DatabaseCorruptedException(string.Format(CultureInfo.InvariantCulture, ExceptionDatabaseUnknownContentType, type)) + }; + + return new TimelinePost( + id: entity.LocalId, + author: author, + content: content, + time: entity.Time, + lastUpdated: entity.LastUpdated, + timelineName: timelineName + ); + } + private TimelineEntity CreateNewTimelineEntity(string? name, long ownerId) { var currentTime = _clock.GetCurrentTime(); @@ -488,30 +530,23 @@ namespace Timeline.Services var posts = new List(); foreach (var entity in postEntities) { - if (entity.Content == null) - { - throw new Exception(); - } - - var author = await _userService.GetUserById(entity.AuthorId); + posts.Add(await MapTimelinePostFromEntity(entity, timelineName)); + } + return posts; + } - var type = entity.ContentType; + public async Task> GetPosts(string timelineName, DateTime modifiedSince) + { + if (timelineName == null) + throw new ArgumentNullException(nameof(timelineName)); - ITimelinePostContent content = type switch - { - TimelinePostContentTypes.Text => new TextTimelinePostContent(entity.Content), - TimelinePostContentTypes.Image => new ImageTimelinePostContent(entity.Content), - _ => throw new DatabaseCorruptedException(string.Format(CultureInfo.InvariantCulture, ExceptionDatabaseUnknownContentType, type)) - }; + var timelineId = await FindTimelineId(timelineName); + var postEntities = await _database.TimelinePosts.OrderBy(p => p.Time).Where(p => p.TimelineId == timelineId && p.Content != null && p.LastUpdated > modifiedSince).ToListAsync(); - posts.Add(new TimelinePost( - id: entity.LocalId, - author: author, - content: content, - time: entity.Time, - lastUpdated: entity.LastUpdated, - timelineName: timelineName - )); + var posts = new List(); + foreach (var entity in postEntities) + { + posts.Add(await MapTimelinePostFromEntity(entity, timelineName)); } return posts; } -- cgit v1.2.3