From 415a91f08422a9a18958552ec21a25f336ef81c4 Mon Sep 17 00:00:00 2001 From: crupest Date: Sun, 31 Jan 2021 15:42:52 +0800 Subject: ... --- .../Timeline/Controllers/TimelinePostController.cs | 213 +++++++++++++++++++++ 1 file changed, 213 insertions(+) create mode 100644 BackEnd/Timeline/Controllers/TimelinePostController.cs (limited to 'BackEnd/Timeline/Controllers/TimelinePostController.cs') diff --git a/BackEnd/Timeline/Controllers/TimelinePostController.cs b/BackEnd/Timeline/Controllers/TimelinePostController.cs new file mode 100644 index 00000000..afe9b36f --- /dev/null +++ b/BackEnd/Timeline/Controllers/TimelinePostController.cs @@ -0,0 +1,213 @@ +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using Timeline.Entities; +using Timeline.Filters; +using Timeline.Helpers; +using Timeline.Models; +using Timeline.Models.Http; +using Timeline.Models.Mapper; +using Timeline.Models.Validation; +using Timeline.Services; +using Timeline.Services.Exceptions; + +namespace Timeline.Controllers +{ + /// + /// Operations about timeline. + /// + [ApiController] + [Route("timelines/{timeline}/posts")] + [CatchTimelineNotExistException] + [ProducesErrorResponseType(typeof(CommonResponse))] + public class TimelinePostController : Controller + { + private readonly ITimelineService _timelineService; + private readonly ITimelinePostService _postService; + + private readonly TimelineMapper _timelineMapper; + + /// + /// + /// + public TimelinePostController(ITimelineService timelineService, ITimelinePostService timelinePostService, TimelineMapper timelineMapper) + { + _timelineService = timelineService; + _postService = timelinePostService; + _timelineMapper = timelineMapper; + } + + private bool UserHasAllTimelineManagementPermission => this.UserHasPermission(UserPermission.AllTimelineManagement); + + /// + /// Get posts of a timeline. + /// + /// The name of the timeline. + /// If set, only posts modified since the time will return. + /// If set to true, deleted post will also return. + /// The post list. + [HttpGet] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status403Forbidden)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + public async Task>> PostList([FromRoute][GeneralTimelineName] string timeline, [FromQuery] DateTime? modifiedSince, [FromQuery] bool? includeDeleted) + { + var timelineId = await _timelineService.GetTimelineIdByName(timeline); + + if (!UserHasAllTimelineManagementPermission && !await _timelineService.HasReadPermission(timelineId, this.GetOptionalUserId())) + { + return StatusCode(StatusCodes.Status403Forbidden, ErrorResponse.Common.Forbid()); + } + + var posts = await _postService.GetPosts(timelineId, modifiedSince, includeDeleted ?? false); + + var result = await _timelineMapper.MapToHttp(posts, timeline, Url); + return result; + } + + /// + /// Get the data of a post. Usually a image post. + /// + /// Timeline name. + /// The id of the post. + /// If-None-Match header. + /// The data. + [HttpGet("{post}/data")] + [Produces("image/png", "image/jpeg", "image/gif", "image/webp", "application/json", "text/json")] + [ProducesResponseType(typeof(byte[]), StatusCodes.Status200OK)] + [ProducesResponseType(typeof(void), StatusCodes.Status304NotModified)] + [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) + { + _ = ifNoneMatch; + + var timelineId = await _timelineService.GetTimelineIdByName(timeline); + + if (!UserHasAllTimelineManagementPermission && !await _timelineService.HasReadPermission(timelineId, this.GetOptionalUserId())) + { + return StatusCode(StatusCodes.Status403Forbidden, ErrorResponse.Common.Forbid()); + } + + try + { + return await DataCacheHelper.GenerateActionResult(this, + () => _postService.GetPostDataETag(timelineId, post), + async () => await _postService.GetPostData(timelineId, post)); + } + catch (TimelinePostNotExistException) + { + return NotFound(ErrorResponse.TimelineController.PostNotExist()); + } + catch (TimelinePostNoDataException) + { + return BadRequest(ErrorResponse.TimelineController.PostNoData()); + } + } + + /// + /// Create a new post. + /// + /// Timeline name. + /// + /// Info of new post. + [HttpPost] + [Authorize] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status400BadRequest)] + [ProducesResponseType(StatusCodes.Status401Unauthorized)] + [ProducesResponseType(StatusCodes.Status403Forbidden)] + public async Task> PostPost([FromRoute][GeneralTimelineName] string timeline, [FromBody] HttpTimelinePostCreateRequest body) + { + var timelineId = await _timelineService.GetTimelineIdByName(timeline); + var userId = this.GetUserId(); + + if (!UserHasAllTimelineManagementPermission && !await _timelineService.IsMemberOf(timelineId, userId)) + { + return StatusCode(StatusCodes.Status403Forbidden, ErrorResponse.Common.Forbid()); + } + + var content = body.Content; + + TimelinePostEntity post; + + if (content.Type == TimelinePostContentTypes.Text) + { + var text = content.Text; + if (text == null) + { + return BadRequest(ErrorResponse.Common.CustomMessage_InvalidModel(Resources.Messages.TimelineController_TextContentTextRequired)); + } + post = await _postService.CreateTextPost(timelineId, userId, text, body.Time); + } + else if (content.Type == TimelinePostContentTypes.Image) + { + var base64Data = content.Data; + if (base64Data == null) + { + return BadRequest(ErrorResponse.Common.CustomMessage_InvalidModel(Resources.Messages.TimelineController_ImageContentDataRequired)); + } + byte[] data; + try + { + data = Convert.FromBase64String(base64Data); + } + catch (FormatException) + { + return BadRequest(ErrorResponse.Common.CustomMessage_InvalidModel(Resources.Messages.TimelineController_ImageContentDataNotBase64)); + } + + try + { + post = await _postService.CreateImagePost(timelineId, userId, data, body.Time); + } + catch (ImageException) + { + return BadRequest(ErrorResponse.Common.CustomMessage_InvalidModel(Resources.Messages.TimelineController_ImageContentDataNotImage)); + } + } + else + { + return BadRequest(ErrorResponse.Common.CustomMessage_InvalidModel(Resources.Messages.TimelineController_ContentUnknownType)); + } + + var result = await _timelineMapper.MapToHttp(post, timeline, Url); + return result; + } + + /// + /// Delete a post. + /// + /// Timeline name. + /// Post id. + /// Info of deletion. + [HttpDelete("{post}")] + [Authorize] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status400BadRequest)] + [ProducesResponseType(StatusCodes.Status401Unauthorized)] + [ProducesResponseType(StatusCodes.Status403Forbidden)] + public async Task PostDelete([FromRoute][GeneralTimelineName] string timeline, [FromRoute] long post) + { + var timelineId = await _timelineService.GetTimelineIdByName(timeline); + + try + { + if (!UserHasAllTimelineManagementPermission && !await _postService.HasPostModifyPermission(timelineId, post, this.GetUserId(), true)) + { + return StatusCode(StatusCodes.Status403Forbidden, ErrorResponse.Common.Forbid()); + } + await _postService.DeletePost(timelineId, post); + return Ok(); + } + catch (TimelinePostNotExistException) + { + return BadRequest(ErrorResponse.TimelineController.PostNotExist()); + } + } + } +} -- cgit v1.2.3