diff options
Diffstat (limited to 'BackEnd/Timeline')
-rw-r--r-- | BackEnd/Timeline/Controllers/TimelinePostV2Controller.cs | 215 |
1 files changed, 215 insertions, 0 deletions
diff --git a/BackEnd/Timeline/Controllers/TimelinePostV2Controller.cs b/BackEnd/Timeline/Controllers/TimelinePostV2Controller.cs new file mode 100644 index 00000000..af1b3182 --- /dev/null +++ b/BackEnd/Timeline/Controllers/TimelinePostV2Controller.cs @@ -0,0 +1,215 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.SignalR; +using Timeline.Helpers.Cache; +using Timeline.Models; +using Timeline.Models.Http; +using Timeline.Models.Validation; +using Timeline.Services.Mapper; +using Timeline.Services.Timeline; +using Timeline.Services.User; +using Timeline.SignalRHub; + +namespace Timeline.Controllers +{ + [ApiController] + [Route("v2/timelines/{owner}/{timeline}/posts")] + public class TimelinePostV2Controller : MyControllerBase + { + private readonly ITimelineService _timelineService; + private readonly ITimelinePostService _postService; + + private readonly IGenericMapper _mapper; + + private readonly MarkdownProcessor _markdownProcessor; + + private readonly IHubContext<TimelineHub> _timelineHubContext; + + public TimelinePostV2Controller(ITimelineService timelineService, ITimelinePostService timelinePostService, IGenericMapper mapper, MarkdownProcessor markdownProcessor, IHubContext<TimelineHub> timelineHubContext) + { + _timelineService = timelineService; + _postService = timelinePostService; + _mapper = mapper; + _markdownProcessor = markdownProcessor; + _timelineHubContext = timelineHubContext; + } + + [HttpGet] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status403Forbidden)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + [ProducesResponseType(StatusCodes.Status422UnprocessableEntity)] + public async Task<ActionResult<List<HttpTimelinePost>>> ListAsync([FromRoute][Username] string owner, [FromRoute][TimelineName] string timeline, [FromQuery] DateTime? modifiedSince, [FromQuery] bool? includeDeleted, [FromQuery][Range(0, int.MaxValue)] int? page, [FromQuery][Range(1, int.MaxValue)] int? numberPerPage) + { + var timelineId = await _timelineService.GetTimelineIdAsync(owner, timeline); + if (!UserHasPermission(UserPermission.AllTimelineManagement) && !await _timelineService.HasReadPermissionAsync(timelineId, GetOptionalAuthUserId())) + { + return Forbid(); + } + var posts = await _postService.GetPostsAsync(timelineId, modifiedSince, includeDeleted ?? false, page, numberPerPage); + var result = await _mapper.MapListAsync<HttpTimelinePost>(posts, Url, User); + return result; + } + + [HttpGet("{post}")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status403Forbidden)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + [ProducesResponseType(StatusCodes.Status422UnprocessableEntity)] + public async Task<ActionResult<HttpTimelinePost>> GetAsync([FromRoute][Username] string owner, [FromRoute][TimelineName] string timeline, [FromRoute(Name = "post")] long postId) + { + var timelineId = await _timelineService.GetTimelineIdAsync(owner, timeline); + if (!UserHasPermission(UserPermission.AllTimelineManagement) && !await _timelineService.HasReadPermissionAsync(timelineId, GetOptionalAuthUserId())) + { + return Forbid(); + } + var post = await _postService.GetPostAsync(timelineId, postId); + var result = await _mapper.MapAsync<HttpTimelinePost>(post, Url, User); + return result; + } + + [HttpGet("{post}/data")] + [Produces(MimeTypes.ImagePng, MimeTypes.ImageJpeg, MimeTypes.ImageGif, MimeTypes.ImageWebp, MimeTypes.TextPlain, MimeTypes.TextMarkdown, MimeTypes.TextPlain, MimeTypes.ApplicationJson)] + [ProducesResponseType(typeof(byte[]), StatusCodes.Status200OK)] + [ProducesResponseType(typeof(void), StatusCodes.Status304NotModified)] + [ProducesResponseType(StatusCodes.Status403Forbidden)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + [ProducesResponseType(StatusCodes.Status422UnprocessableEntity)] + public async Task<ActionResult<ByteData>> DataIndexGetAsync([FromRoute][Username] string owner, [FromRoute][TimelineName] string timeline, [FromRoute] long post) + { + return await DataGetAsync(owner, timeline, post, 0); + } + + [HttpGet("{post}/data/{data_index}")] + [Produces(MimeTypes.ImagePng, MimeTypes.ImageJpeg, MimeTypes.ImageGif, MimeTypes.ImageWebp, MimeTypes.TextPlain, MimeTypes.TextMarkdown, MimeTypes.TextPlain, MimeTypes.ApplicationJson)] + [ProducesResponseType(typeof(byte[]), StatusCodes.Status200OK)] + [ProducesResponseType(typeof(void), StatusCodes.Status304NotModified)] + [ProducesResponseType(StatusCodes.Status403Forbidden)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + [ProducesResponseType(StatusCodes.Status422UnprocessableEntity)] + public async Task<ActionResult> DataGetAsync([FromRoute][Username] string owner, [FromRoute][TimelineName] string timeline, [FromRoute] long post, [FromRoute(Name = "data_index")][Range(0, 100)] long dataIndex) + { + var timelineId = await _timelineService.GetTimelineIdAsync(owner, timeline); + + if (!UserHasPermission(UserPermission.AllTimelineManagement) && !await _timelineService.HasReadPermissionAsync(timelineId, GetOptionalAuthUserId())) + { + return Forbid(); + } + + return await DataCacheHelper.GenerateActionResult(this, + () => _postService.GetPostDataDigestAsync(timelineId, post, dataIndex), + async () => + { + var data = await _postService.GetPostDataAsync(timelineId, post, dataIndex); + if (data.ContentType == MimeTypes.TextMarkdown) + { + return new ByteData(_markdownProcessor.Process(data.Data, Url, timeline, post), data.ContentType); + } + return data; + } + ); + } + + [HttpPost] + [Authorize] + [ProducesResponseType(StatusCodes.Status201Created)] + [ProducesResponseType(StatusCodes.Status401Unauthorized)] + [ProducesResponseType(StatusCodes.Status403Forbidden)] + [ProducesResponseType(StatusCodes.Status422UnprocessableEntity)] + public async Task<ActionResult<HttpTimelinePost>> PostAsync([FromRoute][Username] string owner, [FromRoute][TimelineName] string timeline, [FromBody] HttpTimelinePostCreateRequest body) + { + var timelineId = await _timelineService.GetTimelineIdAsync(owner, timeline); + + if (!UserHasPermission(UserPermission.AllTimelineManagement) && !await _timelineService.IsMemberOfAsync(timelineId, GetAuthUserId())) + { + return Forbid(); + } + + var createRequest = new TimelinePostCreateRequest() + { + Time = body.Time, + Color = body.Color + }; + + for (int i = 0; i < body.DataList.Count; i++) + { + var data = body.DataList[i]; + + if (data is null) + return UnprocessableEntity(new CommonResponse(ErrorCodes.Common.InvalidModel, $"Data at index {i} is null.")); + + try + { + var d = Convert.FromBase64String(data.Data); + createRequest.DataList.Add(new TimelinePostCreateRequestData(data.ContentType, d)); + } + catch (FormatException) + { + return UnprocessableEntity(new CommonResponse(ErrorCodes.Common.InvalidModel, $"Data at index {i} is not a valid base64 string.")); + } + } + + try + { + var post = await _postService.CreatePostAsync(timelineId, GetAuthUserId(), createRequest); + + var group = TimelineHub.GenerateTimelinePostChangeListeningGroupName(timeline); + await _timelineHubContext.Clients.Group(group).SendAsync(nameof(ITimelineClient.OnTimelinePostChanged), timeline); + + var result = await _mapper.MapAsync<HttpTimelinePost>(post, Url, User); + return CreatedAtAction("Get", new { owner = owner, timeline = timeline, post = post.LocalId }, result); + } + catch (TimelinePostCreateDataException e) + { + return UnprocessableEntity(new CommonResponse(ErrorCodes.Common.InvalidModel, $"Data at index {e.Index} is invalid. {e.Message}")); + } + } + + [HttpPatch("{post}")] + [Authorize] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status401Unauthorized)] + [ProducesResponseType(StatusCodes.Status403Forbidden)] + [ProducesResponseType(StatusCodes.Status422UnprocessableEntity)] + public async Task<ActionResult<HttpTimelinePost>> Patch([FromRoute][Username] string owner, [FromRoute][TimelineName] string timeline, [FromRoute] long post, [FromBody] HttpTimelinePostPatchRequest body) + { + var timelineId = await _timelineService.GetTimelineIdAsync(owner, timeline); + + if (!UserHasPermission(UserPermission.AllTimelineManagement) && !await _postService.HasPostModifyPermissionAsync(timelineId, post, GetAuthUserId(), true)) + { + return Forbid(); + } + + var entity = await _postService.PatchPostAsync(timelineId, post, new TimelinePostPatchRequest { Time = body.Time, Color = body.Color }); + var result = await _mapper.MapAsync<HttpTimelinePost>(entity, Url, User); + + return Ok(result); + } + + [HttpDelete("{post}")] + [Authorize] + [ProducesResponseType(StatusCodes.Status204NoContent)] + [ProducesResponseType(StatusCodes.Status401Unauthorized)] + [ProducesResponseType(StatusCodes.Status403Forbidden)] + [ProducesResponseType(StatusCodes.Status422UnprocessableEntity)] + public async Task<ActionResult> Delete([FromRoute][Username] string owner, [FromRoute][TimelineName] string timeline, [FromRoute] long post) + { + var timelineId = await _timelineService.GetTimelineIdAsync(owner, timeline); + + if (!UserHasPermission(UserPermission.AllTimelineManagement) && !await _postService.HasPostModifyPermissionAsync(timelineId, post, GetAuthUserId(), true)) + { + return Forbid(); + } + + await _postService.DeletePostAsync(timelineId, post); + + return NoContent(); + } + } +} + |