using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Threading.Tasks;
using Timeline.Entities;
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;
namespace Timeline.Controllers
{
///
/// Operations about timeline.
///
[ApiController]
[Route("timelines/{timeline}/posts")]
[ProducesErrorResponseType(typeof(CommonResponse))]
public class TimelinePostController : MyControllerBase
{
private readonly ITimelineService _timelineService;
private readonly ITimelinePostService _postService;
private readonly IGenericMapper _mapper;
private readonly MarkdownProcessor _markdownProcessor;
public TimelinePostController(ITimelineService timelineService, ITimelinePostService timelinePostService, IGenericMapper mapper, MarkdownProcessor markdownProcessor)
{
_timelineService = timelineService;
_postService = timelinePostService;
_mapper = mapper;
_markdownProcessor = markdownProcessor;
}
private bool UserHasAllTimelineManagementPermission => UserHasPermission(UserPermission.AllTimelineManagement);
private Task Map(TimelinePostEntity post)
{
return _mapper.MapAsync(post, Url, User);
}
private Task> Map(List posts)
{
return _mapper.MapListAsync(posts, Url, User);
}
///
/// 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.GetTimelineIdByNameAsync(timeline);
if (!UserHasAllTimelineManagementPermission && !await _timelineService.HasReadPermissionAsync(timelineId, GetOptionalUserId()))
{
return ForbidWithCommonResponse();
}
var posts = await _postService.GetPostsAsync(timelineId, modifiedSince, includeDeleted ?? false);
var result = await Map(posts);
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.GetTimelineIdByNameAsync(timeline);
if (!UserHasAllTimelineManagementPermission && !await _timelineService.HasReadPermissionAsync(timelineId, GetOptionalUserId()))
{
return ForbidWithCommonResponse();
}
var post = await _postService.GetPostAsync(timelineId, postId);
var result = await Map(post);
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.GetTimelineIdByNameAsync(timeline);
if (!UserHasAllTimelineManagementPermission && !await _timelineService.HasReadPermissionAsync(timelineId, GetOptionalUserId()))
{
return ForbidWithCommonResponse();
}
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;
}
);
}
///
/// 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.GetTimelineIdByNameAsync(timeline);
var userId = GetUserId();
if (!UserHasAllTimelineManagementPermission && !await _timelineService.IsMemberOfAsync(timelineId, userId))
{
return ForbidWithCommonResponse();
}
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.CreatePostAsync(timelineId, userId, createRequest);
var result = await Map(post);
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.GetTimelineIdByNameAsync(timeline);
if (!UserHasAllTimelineManagementPermission && !await _postService.HasPostModifyPermissionAsync(timelineId, post, GetUserId(), true))
{
return ForbidWithCommonResponse();
}
var entity = await _postService.PatchPostAsync(timelineId, post, new TimelinePostPatchRequest { Time = body.Time, Color = body.Color });
var result = await Map(entity);
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.GetTimelineIdByNameAsync(timeline);
if (!UserHasAllTimelineManagementPermission && !await _postService.HasPostModifyPermissionAsync(timelineId, post, GetUserId(), true))
{
return ForbidWithCommonResponse();
}
await _postService.DeletePostAsync(timelineId, post);
return DeleteWithCommonDeleteResponse();
}
}
}