From 4db131899145b7aca0ea5fd36984cf1542c9619b Mon Sep 17 00:00:00 2001 From: crupest Date: Sat, 9 Apr 2022 18:38:46 +0800 Subject: ... --- .../IntegratedTests2/HttpClientTestExtensions.cs | 5 +- .../IntegratedTests2/IntegratedTestBase.cs | 7 +- .../IntegratedTests2/TimelinePostTest2.cs | 1 - .../IntegratedTests2/TimelinePostTest3.cs | 1 - .../IntegratedTests2/TimelineTest.cs | 4 +- .../IntegratedTests2/TimelineTest2.cs | 3 +- .../IntegratedTests2/TimelineTest3.cs | 4 +- BackEnd/Timeline/Models/Page.cs | 28 +++++++ .../Timeline/Services/EntityDeletedException.cs | 24 ++++++ .../Services/Timeline/ITimelinePostService.cs | 25 +++++- .../Services/Timeline/TimelinePostService.cs | 90 ++++++++++++++++++---- 11 files changed, 159 insertions(+), 33 deletions(-) create mode 100644 BackEnd/Timeline/Models/Page.cs create mode 100644 BackEnd/Timeline/Services/EntityDeletedException.cs diff --git a/BackEnd/Timeline.Tests/IntegratedTests2/HttpClientTestExtensions.cs b/BackEnd/Timeline.Tests/IntegratedTests2/HttpClientTestExtensions.cs index 0124b72a..cd7daf3e 100644 --- a/BackEnd/Timeline.Tests/IntegratedTests2/HttpClientTestExtensions.cs +++ b/BackEnd/Timeline.Tests/IntegratedTests2/HttpClientTestExtensions.cs @@ -1,10 +1,9 @@ -using FluentAssertions; -using System; +using System; using System.Net; using System.Net.Http; using System.Net.Http.Json; using System.Threading.Tasks; -using Timeline.Models.Http; +using FluentAssertions; using Timeline.Tests.Helpers; namespace Timeline.Tests.IntegratedTests2 diff --git a/BackEnd/Timeline.Tests/IntegratedTests2/IntegratedTestBase.cs b/BackEnd/Timeline.Tests/IntegratedTests2/IntegratedTestBase.cs index 1d01fd0e..24a869a0 100644 --- a/BackEnd/Timeline.Tests/IntegratedTests2/IntegratedTestBase.cs +++ b/BackEnd/Timeline.Tests/IntegratedTests2/IntegratedTestBase.cs @@ -1,9 +1,8 @@ -using Microsoft.AspNetCore.TestHost; -using Microsoft.Extensions.DependencyInjection; -using System; -using System.Collections.Generic; +using System; using System.Net.Http; using System.Threading.Tasks; +using Microsoft.AspNetCore.TestHost; +using Microsoft.Extensions.DependencyInjection; using Timeline.Models.Http; using Timeline.Services.User; using Timeline.Tests.Helpers; diff --git a/BackEnd/Timeline.Tests/IntegratedTests2/TimelinePostTest2.cs b/BackEnd/Timeline.Tests/IntegratedTests2/TimelinePostTest2.cs index 5abd969d..9aaae21a 100644 --- a/BackEnd/Timeline.Tests/IntegratedTests2/TimelinePostTest2.cs +++ b/BackEnd/Timeline.Tests/IntegratedTests2/TimelinePostTest2.cs @@ -4,7 +4,6 @@ using System.Net; using System.Net.Http; using System.Text; using System.Threading.Tasks; -using FluentAssertions; using Timeline.Models; using Timeline.Models.Http; using Xunit; diff --git a/BackEnd/Timeline.Tests/IntegratedTests2/TimelinePostTest3.cs b/BackEnd/Timeline.Tests/IntegratedTests2/TimelinePostTest3.cs index 345a8e81..5313a7c8 100644 --- a/BackEnd/Timeline.Tests/IntegratedTests2/TimelinePostTest3.cs +++ b/BackEnd/Timeline.Tests/IntegratedTests2/TimelinePostTest3.cs @@ -4,7 +4,6 @@ using System.Net; using System.Net.Http; using System.Text; using System.Threading.Tasks; -using FluentAssertions; using Timeline.Models; using Timeline.Models.Http; using Timeline.Tests.Helpers; diff --git a/BackEnd/Timeline.Tests/IntegratedTests2/TimelineTest.cs b/BackEnd/Timeline.Tests/IntegratedTests2/TimelineTest.cs index 59b2d225..807314f4 100644 --- a/BackEnd/Timeline.Tests/IntegratedTests2/TimelineTest.cs +++ b/BackEnd/Timeline.Tests/IntegratedTests2/TimelineTest.cs @@ -1,9 +1,7 @@ -using System; -using System.Net; +using System.Net; using System.Net.Http; using System.Threading.Tasks; using FluentAssertions; -using Timeline.Models; using Timeline.Models.Http; using Xunit; using Xunit.Abstractions; diff --git a/BackEnd/Timeline.Tests/IntegratedTests2/TimelineTest2.cs b/BackEnd/Timeline.Tests/IntegratedTests2/TimelineTest2.cs index c3a19bbb..b5566ba0 100644 --- a/BackEnd/Timeline.Tests/IntegratedTests2/TimelineTest2.cs +++ b/BackEnd/Timeline.Tests/IntegratedTests2/TimelineTest2.cs @@ -1,5 +1,4 @@ -using System; -using System.Net; +using System.Net; using System.Net.Http; using System.Threading.Tasks; using FluentAssertions; diff --git a/BackEnd/Timeline.Tests/IntegratedTests2/TimelineTest3.cs b/BackEnd/Timeline.Tests/IntegratedTests2/TimelineTest3.cs index 05e01f95..2c8d4c39 100644 --- a/BackEnd/Timeline.Tests/IntegratedTests2/TimelineTest3.cs +++ b/BackEnd/Timeline.Tests/IntegratedTests2/TimelineTest3.cs @@ -1,9 +1,7 @@ -using System; -using System.Net; +using System.Net; using System.Net.Http; using System.Threading.Tasks; using FluentAssertions; -using Timeline.Models; using Timeline.Models.Http; using Xunit; using Xunit.Abstractions; diff --git a/BackEnd/Timeline/Models/Page.cs b/BackEnd/Timeline/Models/Page.cs new file mode 100644 index 00000000..807702c1 --- /dev/null +++ b/BackEnd/Timeline/Models/Page.cs @@ -0,0 +1,28 @@ +using System; +using System.Collections.Generic; + +namespace Timeline.Models +{ + public class Page + { + public Page() + { + } + + public Page(long pageNumber, long pageSize, long totalCount, List items) + { + PageNumber = pageNumber; + PageSize = pageSize; + TotalPageCount = totalCount / PageSize + (totalCount % PageSize != 0 ? 1 : 0); + TotalCount = totalCount; + Items = items; + } + + public long PageNumber { get; set; } + public long PageSize { get; set; } + public long TotalPageCount { get; set; } + public long TotalCount { get; set; } + public List Items { get; set; } = new List(); + } +} + diff --git a/BackEnd/Timeline/Services/EntityDeletedException.cs b/BackEnd/Timeline/Services/EntityDeletedException.cs new file mode 100644 index 00000000..a31da594 --- /dev/null +++ b/BackEnd/Timeline/Services/EntityDeletedException.cs @@ -0,0 +1,24 @@ +using System; +using System.Collections.Generic; + +namespace Timeline.Services +{ + /// + /// Thrown when an entity is deleted. + /// + [Serializable] + public class EntityDeletedException : EntityException + { + public EntityDeletedException() : base() { } + public EntityDeletedException(string? message) : base(message) { } + public EntityDeletedException(string? message, Exception? inner) : base(message, inner) { } + public EntityDeletedException(EntityType entityType, IDictionary constraints, string? message = null, Exception? inner = null) + : base(entityType, constraints, message ?? Resource.ExceptionEntityNotExist, inner) + { + + } + protected EntityDeletedException( + System.Runtime.Serialization.SerializationInfo info, + System.Runtime.Serialization.StreamingContext context) : base(info, context) { } + } +} diff --git a/BackEnd/Timeline/Services/Timeline/ITimelinePostService.cs b/BackEnd/Timeline/Services/Timeline/ITimelinePostService.cs index c0edf857..f595af87 100644 --- a/BackEnd/Timeline/Services/Timeline/ITimelinePostService.cs +++ b/BackEnd/Timeline/Services/Timeline/ITimelinePostService.cs @@ -4,7 +4,6 @@ using System.Threading.Tasks; using Timeline.Entities; using Timeline.Helpers.Cache; using Timeline.Models; -using Timeline.Services.Imaging; namespace Timeline.Services.Timeline { @@ -55,7 +54,29 @@ namespace Timeline.Services.Timeline /// 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); + Task GetPostDataAsync(long timelineId, long postId, long dataIndex); + + /// + /// Get posts of a timeline. + /// + /// The timeline id. + /// If not null, only posts modified since (including) the time will be returned. + /// The page to get. Starts from 1. + /// Number per page. + /// A task containing a page of post entity. + /// Thrown when timeline does not exist. + Task> GetPostsV2Async(long timelineId, DateTime? modifiedSince = null, int? page = null, int? numberPerPage = null); + + /// + /// Get a post of a timeline. + /// + /// The timeline id. + /// The post id. + /// A task containing a post entity. + /// Thrown when timeline does not exist. + /// Thrown when post does not exist. + /// Thrown when post is deleted. + Task GetPostV2Async(long timelineId, long postId); /// /// Create a new post in timeline. diff --git a/BackEnd/Timeline/Services/Timeline/TimelinePostService.cs b/BackEnd/Timeline/Services/Timeline/TimelinePostService.cs index e6297d2c..40f226ce 100644 --- a/BackEnd/Timeline/Services/Timeline/TimelinePostService.cs +++ b/BackEnd/Timeline/Services/Timeline/TimelinePostService.cs @@ -20,8 +20,8 @@ namespace Timeline.Services.Timeline { private readonly ILogger _logger; private readonly DatabaseContext _database; - private readonly ITimelineService _basicTimelineService; - private readonly IUserService _basicUserService; + private readonly ITimelineService _timelineService; + private readonly IUserService _userService; private readonly IDataManager _dataManager; private readonly IImageService _imageValidator; private readonly IClock _clock; @@ -32,8 +32,8 @@ namespace Timeline.Services.Timeline { _logger = logger; _database = database; - _basicTimelineService = basicTimelineService; - _basicUserService = basicUserService; + _timelineService = basicTimelineService; + _userService = basicUserService; _dataManager = dataManager; _imageValidator = imageValidator; _clock = clock; @@ -53,6 +53,15 @@ namespace Timeline.Services.Timeline ["post-id"] = postId, ["deleted"] = deleted }); + } + + private static EntityNotExistException CreatePostDeletedException(long timelineId, long postId) + { + return new EntityNotExistException(EntityTypes.TimelinePost, new Dictionary + { + ["timeline-id"] = timelineId, + ["post-id"] = postId, + }); } private static EntityNotExistException CreatePostDataNotExistException(long timelineId, long postId, long dataIndex) @@ -73,7 +82,7 @@ namespace Timeline.Services.Timeline throw new ArgumentOutOfRangeException(nameof(numberPerPage), Resource.ExceptionNumberPerPageZeroOrNegative); - await _basicTimelineService.ThrowIfTimelineNotExist(timelineId); + await _timelineService.ThrowIfTimelineNotExist(timelineId); modifiedSince = modifiedSince?.MyToUtc(); @@ -102,7 +111,7 @@ namespace Timeline.Services.Timeline public async Task GetPostAsync(long timelineId, long postId, bool includeDeleted = false) { - await _basicTimelineService.ThrowIfTimelineNotExist(timelineId); + await _timelineService.ThrowIfTimelineNotExist(timelineId); var post = await _database.TimelinePosts.Where(p => p.TimelineId == timelineId && p.LocalId == postId).SingleOrDefaultAsync(); @@ -121,7 +130,7 @@ namespace Timeline.Services.Timeline public async Task GetPostDataDigestAsync(long timelineId, long postId, long dataIndex) { - await _basicTimelineService.ThrowIfTimelineNotExist(timelineId); + await _timelineService.ThrowIfTimelineNotExist(timelineId); var postEntity = await _database.TimelinePosts.Where(p => p.TimelineId == timelineId && p.LocalId == postId).Select(p => new { p.Id, p.Deleted }).SingleOrDefaultAsync(); @@ -141,7 +150,7 @@ namespace Timeline.Services.Timeline public async Task GetPostDataAsync(long timelineId, long postId, long dataIndex) { - await _basicTimelineService.ThrowIfTimelineNotExist(timelineId); + await _timelineService.ThrowIfTimelineNotExist(timelineId); var postEntity = await _database.TimelinePosts.Where(p => p.TimelineId == timelineId && p.LocalId == postId).Select(p => new { p.Id, p.Deleted }).SingleOrDefaultAsync(); @@ -215,8 +224,8 @@ namespace Timeline.Services.Timeline request.Time = request.Time?.MyToUtc(); - await _basicTimelineService.ThrowIfTimelineNotExist(timelineId); - await _basicUserService.ThrowIfUserNotExist(authorId); + await _timelineService.ThrowIfTimelineNotExist(timelineId); + await _userService.ThrowIfUserNotExist(authorId); var currentTime = _clock.GetCurrentTime(); var finalTime = request.Time ?? currentTime; @@ -274,7 +283,7 @@ namespace Timeline.Services.Timeline request.Time = request.Time?.MyToUtc(); - await _basicTimelineService.ThrowIfTimelineNotExist(timelineId); + await _timelineService.ThrowIfTimelineNotExist(timelineId); var entity = await _database.TimelinePosts.Where(p => p.TimelineId == timelineId && p.LocalId == postId).SingleOrDefaultAsync(); @@ -309,7 +318,7 @@ namespace Timeline.Services.Timeline public async Task DeletePostAsync(long timelineId, long postId) { - await _basicTimelineService.ThrowIfTimelineNotExist(timelineId); + await _timelineService.ThrowIfTimelineNotExist(timelineId); var entity = await _database.TimelinePosts.Where(p => p.TimelineId == timelineId && p.LocalId == postId).SingleOrDefaultAsync(); @@ -351,7 +360,7 @@ namespace Timeline.Services.Timeline public async Task HasPostModifyPermissionAsync(long timelineId, long postId, long modifierId, bool throwOnPostNotExist = false) { - await _basicTimelineService.ThrowIfTimelineNotExist(timelineId); + await _timelineService.ThrowIfTimelineNotExist(timelineId); var timelineEntity = await _database.Timelines.Where(t => t.Id == timelineId).Select(t => new { t.OwnerId }).SingleAsync(); @@ -371,6 +380,59 @@ namespace Timeline.Services.Timeline } return timelineEntity.OwnerId == modifierId || postEntity.AuthorId == modifierId; - } + } + + public async Task> GetPostsV2Async(long timelineId, DateTime? modifiedSince = null, int? page = null, int? numberPerPage = null) + { + if (page.HasValue && page < 0) + throw new ArgumentOutOfRangeException(nameof(page), Resource.ExceptionPageNegative); + if (numberPerPage.HasValue && numberPerPage <= 0) + throw new ArgumentOutOfRangeException(nameof(numberPerPage), Resource.ExceptionNumberPerPageZeroOrNegative); + + + var timeline = await _timelineService.GetTimelineAsync(timelineId); + + modifiedSince = modifiedSince?.MyToUtc(); + + IQueryable query = _database.TimelinePosts.Where(p => p.TimelineId == timelineId); + + if (modifiedSince.HasValue) + { + query = query.Where(p => p.LastUpdated >= modifiedSince || (p.Author != null && p.Author.UsernameChangeTime >= modifiedSince)); + } + + query = query.OrderBy(p => p.Time); + + var pageNumber = page.GetValueOrDefault(1); + var pageSize = numberPerPage.GetValueOrDefault(20); + + if (pageNumber > 1) + { + query = query.Skip(pageSize * (pageNumber - 1)).Take(pageSize); + } + + var items = await query.ToListAsync(); + + return new Page(pageNumber, pageSize, timeline.CurrentPostLocalId, items); + } + + public async Task GetPostV2Async(long timelineId, long postId) + { + await _timelineService.ThrowIfTimelineNotExist(timelineId); + + var post = await _database.TimelinePosts.Where(p => p.TimelineId == timelineId && p.LocalId == postId).SingleOrDefaultAsync(); + + if (post is null) + { + throw CreatePostNotExistException(timelineId, postId, false); + } + + if (post.Deleted) + { + throw CreatePostDeletedException(timelineId, postId); + } + + return post; + } } } -- cgit v1.2.3