From c2c29da545eed33a96c06a41ff73e5ae707bb3f6 Mon Sep 17 00:00:00 2001 From: crupest Date: Mon, 8 Feb 2021 19:12:44 +0800 Subject: ... --- BackEnd/Timeline/Services/TimelinePostService.cs | 178 ++++++++++++++++++----- 1 file changed, 140 insertions(+), 38 deletions(-) (limited to 'BackEnd/Timeline/Services/TimelinePostService.cs') diff --git a/BackEnd/Timeline/Services/TimelinePostService.cs b/BackEnd/Timeline/Services/TimelinePostService.cs index 076c45e8..66ec8090 100644 --- a/BackEnd/Timeline/Services/TimelinePostService.cs +++ b/BackEnd/Timeline/Services/TimelinePostService.cs @@ -31,27 +31,55 @@ namespace Timeline.Services public class TimelinePostCreateRequestTextContent : TimelinePostCreateRequestContent { + private string _text; + public TimelinePostCreateRequestTextContent(string text) { - Text = text; + if (text is null) + throw new ArgumentNullException(nameof(text)); + + _text = text; } public override string TypeName => TimelinePostContentTypes.Text; - public string Text { get; set; } + public string Text + { + get => _text; + set + { + if (value is null) + throw new ArgumentNullException(nameof(value)); + _text = value; + } + } } public class TimelinePostCreateRequestImageContent : TimelinePostCreateRequestContent { + private byte[] _data; + public TimelinePostCreateRequestImageContent(byte[] data) { - Data = data; + if (data is null) + throw new ArgumentNullException(nameof(data)); + + _data = data; } public override string TypeName => TimelinePostContentTypes.Image; #pragma warning disable CA1819 // Properties should not return arrays - public byte[] Data { get; set; } + public byte[] Data + { + get => _data; + set + { + if (value is null) + throw new ArgumentNullException(nameof(value)); + _data = value; + } + } #pragma warning restore CA1819 // Properties should not return arrays } @@ -70,6 +98,13 @@ namespace Timeline.Services public TimelinePostCreateRequestContent Content { get; set; } } + public class TimelinePostPatchRequest + { + public string? Color { get; set; } + public DateTime? Time { get; set; } + public TimelinePostCreateRequestContent? Content { get; set; } + } + public interface ITimelinePostService { /// @@ -122,7 +157,7 @@ namespace Timeline.Services /// The id of the timeline to create post against. /// The author's user id. /// Info about the post. - /// The info of the created post. + /// The entity of the created post. /// Thrown when is null. /// Thrown when is of invalid format. /// Thrown when timeline does not exist. @@ -130,6 +165,20 @@ namespace Timeline.Services /// 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. + /// Thrown if data is not a image. Validated by . + Task PatchPost(long timelineId, long postId, TimelinePostPatchRequest request); + /// /// Delete a post. /// @@ -309,6 +358,36 @@ namespace Timeline.Services }; } + private async Task SaveContent(TimelinePostEntity entity, TimelinePostCreateRequestContent content) + { + switch (content) + { + case TimelinePostCreateRequestTextContent c: + entity.ContentType = c.TypeName; + entity.Content = c.Text; + break; + case TimelinePostCreateRequestImageContent c: + var imageFormat = await _imageValidator.Validate(c.Data); + var imageFormatText = imageFormat.DefaultMimeType; + + var tag = await _dataManager.RetainEntry(c.Data); + + entity.ContentType = content.TypeName; + entity.Content = tag; + entity.ExtraContent = imageFormatText; + break; + default: + throw new ArgumentException("Unknown content type.", nameof(content)); + }; + } + + private async Task CleanContent(TimelinePostEntity entity) + { + if (entity.Content is not null && entity.ContentType == TimelinePostContentTypes.Image) + await _dataManager.FreeEntry(entity.Content); + entity.Content = null; + } + public async Task CreatePost(long timelineId, long authorId, TimelinePostCreateRequest request) { if (request is null) @@ -331,6 +410,8 @@ namespace Timeline.Services var currentTime = _clock.GetCurrentTime(); var finalTime = request.Time ?? currentTime; + await using var transaction = await _database.Database.BeginTransactionAsync(); + var postEntity = new TimelinePostEntity { AuthorId = authorId, @@ -340,26 +421,7 @@ namespace Timeline.Services Color = request.Color }; - switch (request.Content) - { - case TimelinePostCreateRequestTextContent content: - postEntity.ContentType = content.TypeName; - postEntity.Content = content.Text; - break; - case TimelinePostCreateRequestImageContent content: - var imageFormat = await _imageValidator.Validate(content.Data); - var imageFormatText = imageFormat.DefaultMimeType; - - var tag = await _dataManager.RetainEntry(content.Data); - - postEntity.ContentType = content.TypeName; - postEntity.Content = tag; - postEntity.ExtraContent = imageFormatText; - break; - default: - throw new ArgumentException("Unknown content type.", nameof(request)); - }; - + await SaveContent(postEntity, request.Content); var timelineEntity = await _database.Timelines.Where(t => t.Id == timelineId).SingleAsync(); timelineEntity.CurrentPostLocalId += 1; @@ -369,37 +431,77 @@ namespace Timeline.Services await _database.SaveChangesAsync(); + await transaction.CommitAsync(); + return postEntity; } - public async Task DeletePost(long timelineId, long postId) + public async Task PatchPost(long timelineId, long postId, TimelinePostPatchRequest request) { + if (request is null) + throw new ArgumentNullException(nameof(request)); + + { + if (!_colorValidator.Validate(request.Color, out var message)) + throw new ArgumentException("Color is not valid.", nameof(request)); + } + + request.Time = request.Time?.MyToUtc(); + await CheckTimelineExistence(timelineId); - var post = await _database.TimelinePosts.Where(p => p.TimelineId == timelineId && p.LocalId == postId).SingleOrDefaultAsync(); + var entity = await _database.TimelinePosts.Where(p => p.TimelineId == timelineId && p.LocalId == postId).SingleOrDefaultAsync(); - if (post == null) + await using var transaction = await _database.Database.BeginTransactionAsync(); + + if (entity is null) throw new TimelinePostNotExistException(timelineId, postId, false); - if (post.Content == null) + if (entity.Content is null) throw new TimelinePostNotExistException(timelineId, postId, true); - string? dataTag = null; + if (request.Time.HasValue) + entity.Time = request.Time.Value; + + if (request.Color is not null) + entity.Color = request.Color; - if (post.ContentType == TimelinePostContentTypes.Image) + if (request.Content is not null) { - dataTag = post.Content; + await CleanContent(entity); + await SaveContent(entity, request.Content); } - post.Content = null; - post.LastUpdated = _clock.GetCurrentTime(); + entity.LastUpdated = _clock.GetCurrentTime(); await _database.SaveChangesAsync(); - if (dataTag != null) - { - await _dataManager.FreeEntry(dataTag); - } + await transaction.CommitAsync(); + + return entity; + } + + public async Task DeletePost(long timelineId, long postId) + { + await CheckTimelineExistence(timelineId); + + var entity = await _database.TimelinePosts.Where(p => p.TimelineId == timelineId && p.LocalId == postId).SingleOrDefaultAsync(); + + if (entity == null) + throw new TimelinePostNotExistException(timelineId, postId, false); + + if (entity.Content == null) + throw new TimelinePostNotExistException(timelineId, postId, true); + + await using var transaction = await _database.Database.BeginTransactionAsync(); + + await CleanContent(entity); + + entity.LastUpdated = _clock.GetCurrentTime(); + + await _database.SaveChangesAsync(); + + await transaction.CommitAsync(); } public async Task DeleteAllPostsOfUser(long userId) -- cgit v1.2.3