From 66658abde1220a53d0e022aaac8dd49a15034a34 Mon Sep 17 00:00:00 2001 From: crupest Date: Thu, 4 Feb 2021 22:03:08 +0800 Subject: ... --- .../IntegratedTests/TimelinePostTest.cs | 35 +++++++++++++++++++ .../Timeline/Controllers/TimelinePostController.cs | 39 +++++++++++++++++++--- BackEnd/Timeline/Models/Mapper/TimelineMapper.cs | 4 +-- BackEnd/Timeline/Services/TimelinePostService.cs | 30 +++++++++++++++++ 4 files changed, 102 insertions(+), 6 deletions(-) diff --git a/BackEnd/Timeline.Tests/IntegratedTests/TimelinePostTest.cs b/BackEnd/Timeline.Tests/IntegratedTests/TimelinePostTest.cs index f05ed7af..b4ddcb43 100644 --- a/BackEnd/Timeline.Tests/IntegratedTests/TimelinePostTest.cs +++ b/BackEnd/Timeline.Tests/IntegratedTests/TimelinePostTest.cs @@ -491,6 +491,8 @@ namespace Timeline.Tests.IntegratedTests Color = "#1" }); + long id; + { var post = await client.TestPostAsync($"timelines/{generator(1)}/posts", new HttpTimelinePostCreateRequest { @@ -498,7 +500,40 @@ namespace Timeline.Tests.IntegratedTests Color = "#aabbcc" }); post.Color.Should().Be("#aabbcc"); + id = post.Id; } + + { + var post = await client.TestGetAsync($"timelines/{generator(1)}/posts/{id}"); + post.Color.Should().Be("#aabbcc"); + } + } + + [Theory] + [MemberData(nameof(TimelineNameGeneratorTestData))] + public async Task GetPost(TimelineNameGenerator generator) + { + using var client = await CreateClientAsUser(); + + HttpTimelinePostCreateRequestContent CreateRequestContent() => new() + { + Type = "text", + Text = "aaa" + }; + + await client.TestGetAssertNotFoundAsync($"timelines/{generator(1)}/posts/1"); + + var post = await client.TestPostAsync($"timelines/{generator(1)}/posts", new HttpTimelinePostCreateRequest + { + Content = CreateRequestContent(), + }); + + var post2 = await client.TestGetAsync($"timelines/{generator(1)}/posts/{post.Id}"); + post2.Should().BeEquivalentTo(post); + + await client.TestDeleteAsync($"timelines/{generator(1)}/posts/{post.Id}"); + + await client.TestGetAssertNotFoundAsync($"timelines/{generator(1)}/posts/{post.Id}"); } } } diff --git a/BackEnd/Timeline/Controllers/TimelinePostController.cs b/BackEnd/Timeline/Controllers/TimelinePostController.cs index 3f31decf..0148f56e 100644 --- a/BackEnd/Timeline/Controllers/TimelinePostController.cs +++ b/BackEnd/Timeline/Controllers/TimelinePostController.cs @@ -53,7 +53,7 @@ namespace Timeline.Controllers [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status403Forbidden)] [ProducesResponseType(StatusCodes.Status404NotFound)] - public async Task>> PostList([FromRoute][GeneralTimelineName] string timeline, [FromQuery] DateTime? modifiedSince, [FromQuery] bool? includeDeleted) + public async Task>> List([FromRoute][GeneralTimelineName] string timeline, [FromQuery] DateTime? modifiedSince, [FromQuery] bool? includeDeleted) { var timelineId = await _timelineService.GetTimelineIdByName(timeline); @@ -68,6 +68,37 @@ namespace Timeline.Controllers return result; } + /// + /// Get a post of a timeline. + /// + /// The name of the timeline. + /// The post id. + /// The post. + [HttpGet("{post}")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status403Forbidden)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + public async Task> Get([FromRoute][GeneralTimelineName] string timeline, [FromRoute(Name = "post")] long postId) + { + var timelineId = await _timelineService.GetTimelineIdByName(timeline); + + if (!UserHasAllTimelineManagementPermission && !await _timelineService.HasReadPermission(timelineId, this.GetOptionalUserId())) + { + return StatusCode(StatusCodes.Status403Forbidden, ErrorResponse.Common.Forbid()); + } + + try + { + var post = await _postService.GetPost(timelineId, postId); + var result = await _timelineMapper.MapToHttp(post, timeline, Url); + return result; + } + catch (TimelinePostNotExistException) + { + return NotFound(ErrorResponse.TimelineController.PostNotExist()); + } + } + /// /// Get the data of a post. Usually a image post. /// @@ -82,7 +113,7 @@ namespace Timeline.Controllers [ProducesResponseType(StatusCodes.Status400BadRequest)] [ProducesResponseType(StatusCodes.Status403Forbidden)] [ProducesResponseType(StatusCodes.Status404NotFound)] - public async Task PostDataGet([FromRoute][GeneralTimelineName] string timeline, [FromRoute] long post, [FromHeader(Name = "If-None-Match")] string? ifNoneMatch) + public async Task DataGet([FromRoute][GeneralTimelineName] string timeline, [FromRoute] long post, [FromHeader(Name = "If-None-Match")] string? ifNoneMatch) { _ = ifNoneMatch; @@ -121,7 +152,7 @@ namespace Timeline.Controllers [ProducesResponseType(StatusCodes.Status400BadRequest)] [ProducesResponseType(StatusCodes.Status401Unauthorized)] [ProducesResponseType(StatusCodes.Status403Forbidden)] - public async Task> PostPost([FromRoute][GeneralTimelineName] string timeline, [FromBody] HttpTimelinePostCreateRequest body) + public async Task> Post([FromRoute][GeneralTimelineName] string timeline, [FromBody] HttpTimelinePostCreateRequest body) { var timelineId = await _timelineService.GetTimelineIdByName(timeline); var userId = this.GetUserId(); @@ -193,7 +224,7 @@ namespace Timeline.Controllers [ProducesResponseType(StatusCodes.Status400BadRequest)] [ProducesResponseType(StatusCodes.Status401Unauthorized)] [ProducesResponseType(StatusCodes.Status403Forbidden)] - public async Task PostDelete([FromRoute][GeneralTimelineName] string timeline, [FromRoute] long post) + public async Task Delete([FromRoute][GeneralTimelineName] string timeline, [FromRoute] long post) { var timelineId = await _timelineService.GetTimelineIdByName(timeline); diff --git a/BackEnd/Timeline/Models/Mapper/TimelineMapper.cs b/BackEnd/Timeline/Models/Mapper/TimelineMapper.cs index 94e55237..88c96d8a 100644 --- a/BackEnd/Timeline/Models/Mapper/TimelineMapper.cs +++ b/BackEnd/Timeline/Models/Mapper/TimelineMapper.cs @@ -49,7 +49,7 @@ namespace Timeline.Models.Mapper isBookmark: userId is not null && await _bookmarkTimelineService.IsBookmark(userId.Value, entity.Id, false, false), links: new HttpTimelineLinks( self: urlHelper.ActionLink(nameof(TimelineController.TimelineGet), nameof(TimelineController)[0..^nameof(Controller).Length], new { timeline = timelineName }), - posts: urlHelper.ActionLink(nameof(TimelinePostController.PostList), nameof(TimelinePostController)[0..^nameof(Controller).Length], new { timeline = timelineName }) + posts: urlHelper.ActionLink(nameof(TimelinePostController.List), nameof(TimelinePostController)[0..^nameof(Controller).Length], new { timeline = timelineName }) ) ); } @@ -84,7 +84,7 @@ namespace Timeline.Models.Mapper ( type: TimelinePostContentTypes.Image, text: null, - url: urlHelper.ActionLink(nameof(TimelinePostController.PostDataGet), nameof(TimelinePostController)[0..^nameof(Controller).Length], new { timeline = timelineName, post = entity.LocalId }), + url: urlHelper.ActionLink(nameof(TimelinePostController.DataGet), nameof(TimelinePostController)[0..^nameof(Controller).Length], new { timeline = timelineName, post = entity.LocalId }), eTag: $"\"{entity.Content}\"" ), _ => throw new DatabaseCorruptedException(string.Format(CultureInfo.InvariantCulture, "Unknown timeline post type {0}.", entity.ContentType)) diff --git a/BackEnd/Timeline/Services/TimelinePostService.cs b/BackEnd/Timeline/Services/TimelinePostService.cs index c2b773ff..cf5f4e55 100644 --- a/BackEnd/Timeline/Services/TimelinePostService.cs +++ b/BackEnd/Timeline/Services/TimelinePostService.cs @@ -44,6 +44,17 @@ namespace Timeline.Services /// 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 includeDelete = false); + /// /// Get the etag of data of a post. /// @@ -189,6 +200,25 @@ namespace Timeline.Services return await query.ToListAsync(); } + public async Task GetPost(long timelineId, long postId, bool includeDelete = false) + { + await CheckTimelineExistence(timelineId); + + var post = await _database.TimelinePosts.Where(p => p.TimelineId == timelineId && p.LocalId == postId).SingleOrDefaultAsync(); + + if (post is null) + { + throw new TimelinePostNotExistException(timelineId, postId, false); + } + + if (!includeDelete && post.Content is null) + { + throw new TimelinePostNotExistException(timelineId, postId, true); + } + + return post; + } + public async Task GetPostDataETag(long timelineId, long postId) { await CheckTimelineExistence(timelineId); -- cgit v1.2.3