using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using System.ComponentModel.DataAnnotations;
using Timeline.Filters;
using Timeline.Helpers.Cache;
using Timeline.Models;
using Timeline.Models.Http;
using Timeline.Models.Mapper;
using Timeline.Models.Validation;
using Timeline.Services;
using Timeline.Entities;
namespace Timeline.Controllers
{
///
/// Operations about timeline.
///
[ApiController]
[Route("timelines/{timeline}/posts")]
[CatchTimelineNotExistException]
[CatchTimelinePostNotExistException]
[CatchTimelinePostDataNotExistException]
[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);
private Task Map(TimelinePostEntity post, string timelineName)
{
return _timelineMapper.MapToHttp(post, timelineName, Url, this.GetOptionalUserId(), UserHasAllTimelineManagementPermission);
}
private Task> Map(List post, string timelineName)
{
return _timelineMapper.MapToHttp(post, timelineName, Url, this.GetOptionalUserId(), UserHasAllTimelineManagementPermission);
}
///
/// 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>> List([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 Map(posts, timeline);
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());
}
var post = await _postService.GetPost(timelineId, postId);
var result = await Map(post, timeline);
return result;
}
///
/// Get the first data of a post.
///
/// Timeline name.
/// The id of the post.
/// The data.
[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.Status400BadRequest)]
[ProducesResponseType(StatusCodes.Status403Forbidden)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public async Task> DataIndexGet([FromRoute][GeneralTimelineName] string timeline, [FromRoute] long post)
{
return await DataGet(timeline, post, 0);
}
///
/// Get the data of a post. Usually a image post.
///
/// Timeline name.
/// The id of the post.
/// Index of the data.
/// The data.
[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.Status400BadRequest)]
[ProducesResponseType(StatusCodes.Status403Forbidden)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public async Task DataGet([FromRoute][GeneralTimelineName] string timeline, [FromRoute] long post, [FromRoute(Name = "data_index")][Range(0, 100)] long dataIndex)
{
var timelineId = await _timelineService.GetTimelineIdByName(timeline);
if (!UserHasAllTimelineManagementPermission && !await _timelineService.HasReadPermission(timelineId, this.GetOptionalUserId()))
{
return StatusCode(StatusCodes.Status403Forbidden, ErrorResponse.Common.Forbid());
}
return await DataCacheHelper.GenerateActionResult(this,
() => _postService.GetPostDataDigest(timelineId, post, dataIndex),
() => _postService.GetPostData(timelineId, post, dataIndex)
);
}
///
/// 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> Post([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 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 BadRequest(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 BadRequest(new CommonResponse(ErrorCodes.Common.InvalidModel, $"Data at index {i} is not a valid base64 string."));
}
}
try
{
var post = await _postService.CreatePost(timelineId, userId, createRequest);
var result = await Map(post, timeline);
return result;
}
catch (TimelinePostCreateDataException e)
{
return BadRequest(new CommonResponse(ErrorCodes.Common.InvalidModel, $"Data at index {e.Index} is invalid. {e.Message}"));
}
}
///
/// Update a post except content.
///
/// Timeline name.
/// Post id.
/// Request body.
/// New info of post.
[HttpPatch("{post}")]
[Authorize]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
[ProducesResponseType(StatusCodes.Status401Unauthorized)]
[ProducesResponseType(StatusCodes.Status403Forbidden)]
public async Task> Patch([FromRoute][GeneralTimelineName] string timeline, [FromRoute] long post, [FromBody] HttpTimelinePostPatchRequest body)
{
var timelineId = await _timelineService.GetTimelineIdByName(timeline);
if (!UserHasAllTimelineManagementPermission && !await _postService.HasPostModifyPermission(timelineId, post, this.GetUserId(), true))
{
return StatusCode(StatusCodes.Status403Forbidden, ErrorResponse.Common.Forbid());
}
var entity = await _postService.PatchPost(timelineId, post, new TimelinePostPatchRequest { Time = body.Time, Color = body.Color });
var result = await Map(entity, timeline);
return Ok(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 Delete([FromRoute][GeneralTimelineName] string timeline, [FromRoute] long post)
{
var timelineId = await _timelineService.GetTimelineIdByName(timeline);
if (!UserHasAllTimelineManagementPermission && !await _postService.HasPostModifyPermission(timelineId, post, this.GetUserId(), true))
{
return StatusCode(StatusCodes.Status403Forbidden, ErrorResponse.Common.Forbid());
}
await _postService.DeletePost(timelineId, post);
return Ok();
}
}
}