aboutsummaryrefslogtreecommitdiff
path: root/Timeline/Controllers
diff options
context:
space:
mode:
authorcrupest <crupest@outlook.com>2020-10-27 19:21:35 +0800
committercrupest <crupest@outlook.com>2020-10-27 19:21:35 +0800
commitac769e656b122ff569c3f1534701b71e00fed586 (patch)
tree72966645ff1e25139d3995262e1c4349f2c14733 /Timeline/Controllers
parent14e5848c23c643cea9b5d709770747d98c3d75e2 (diff)
downloadtimeline-ac769e656b122ff569c3f1534701b71e00fed586.tar.gz
timeline-ac769e656b122ff569c3f1534701b71e00fed586.tar.bz2
timeline-ac769e656b122ff569c3f1534701b71e00fed586.zip
Split front and back end.
Diffstat (limited to 'Timeline/Controllers')
-rw-r--r--Timeline/Controllers/ControllerAuthExtensions.cs40
-rw-r--r--Timeline/Controllers/Testing/TestingAuthController.cs32
-rw-r--r--Timeline/Controllers/TimelineController.cs491
-rw-r--r--Timeline/Controllers/TokenController.cs142
-rw-r--r--Timeline/Controllers/UserAvatarController.cs174
-rw-r--r--Timeline/Controllers/UserController.cs195
6 files changed, 0 insertions, 1074 deletions
diff --git a/Timeline/Controllers/ControllerAuthExtensions.cs b/Timeline/Controllers/ControllerAuthExtensions.cs
deleted file mode 100644
index 00a65454..00000000
--- a/Timeline/Controllers/ControllerAuthExtensions.cs
+++ /dev/null
@@ -1,40 +0,0 @@
-using Microsoft.AspNetCore.Mvc;
-using System;
-using System.Security.Claims;
-using Timeline.Auth;
-using static Timeline.Resources.Controllers.ControllerAuthExtensions;
-
-namespace Timeline.Controllers
-{
- public static class ControllerAuthExtensions
- {
- public static bool IsAdministrator(this ControllerBase controller)
- {
- return controller.User != null && controller.User.IsAdministrator();
- }
-
- public static long GetUserId(this ControllerBase controller)
- {
- var claim = controller.User.FindFirst(ClaimTypes.NameIdentifier);
- if (claim == null)
- throw new InvalidOperationException(ExceptionNoUserIdentifierClaim);
-
- if (long.TryParse(claim.Value, out var value))
- return value;
-
- throw new InvalidOperationException(ExceptionUserIdentifierClaimBadFormat);
- }
-
- public static long? GetOptionalUserId(this ControllerBase controller)
- {
- var claim = controller.User.FindFirst(ClaimTypes.NameIdentifier);
- if (claim == null)
- return null;
-
- if (long.TryParse(claim.Value, out var value))
- return value;
-
- throw new InvalidOperationException(ExceptionUserIdentifierClaimBadFormat);
- }
- }
-}
diff --git a/Timeline/Controllers/Testing/TestingAuthController.cs b/Timeline/Controllers/Testing/TestingAuthController.cs
deleted file mode 100644
index 4d3b3ec7..00000000
--- a/Timeline/Controllers/Testing/TestingAuthController.cs
+++ /dev/null
@@ -1,32 +0,0 @@
-using Microsoft.AspNetCore.Authorization;
-using Microsoft.AspNetCore.Mvc;
-using Timeline.Auth;
-
-namespace Timeline.Controllers.Testing
-{
- [Route("testing/auth")]
- [ApiController]
- public class TestingAuthController : Controller
- {
- [HttpGet("[action]")]
- [Authorize]
- public ActionResult Authorize()
- {
- return Ok();
- }
-
- [HttpGet("[action]")]
- [UserAuthorize]
- public new ActionResult User()
- {
- return Ok();
- }
-
- [HttpGet("[action]")]
- [AdminAuthorize]
- public ActionResult Admin()
- {
- return Ok();
- }
- }
-}
diff --git a/Timeline/Controllers/TimelineController.cs b/Timeline/Controllers/TimelineController.cs
deleted file mode 100644
index 9a3147ea..00000000
--- a/Timeline/Controllers/TimelineController.cs
+++ /dev/null
@@ -1,491 +0,0 @@
-using AutoMapper;
-using Microsoft.AspNetCore.Authorization;
-using Microsoft.AspNetCore.Http;
-using Microsoft.AspNetCore.Mvc;
-using Microsoft.Extensions.Logging;
-using System;
-using System.Collections.Generic;
-using System.ComponentModel.DataAnnotations;
-using System.Threading.Tasks;
-using Timeline.Filters;
-using Timeline.Helpers;
-using Timeline.Models;
-using Timeline.Models.Http;
-using Timeline.Models.Validation;
-using Timeline.Services;
-using Timeline.Services.Exceptions;
-
-namespace Timeline.Controllers
-{
- /// <summary>
- /// Operations about timeline.
- /// </summary>
- [ApiController]
- [CatchTimelineNotExistException]
- [ProducesErrorResponseType(typeof(CommonResponse))]
- public class TimelineController : Controller
- {
- private readonly ILogger<TimelineController> _logger;
-
- private readonly IUserService _userService;
- private readonly ITimelineService _service;
-
- private readonly IMapper _mapper;
-
- /// <summary>
- ///
- /// </summary>
- public TimelineController(ILogger<TimelineController> logger, IUserService userService, ITimelineService service, IMapper mapper)
- {
- _logger = logger;
- _userService = userService;
- _service = service;
- _mapper = mapper;
- }
-
- /// <summary>
- /// List all timelines.
- /// </summary>
- /// <param name="relate">A username. If set, only timelines related to the user will return.</param>
- /// <param name="relateType">Specify the relation type, may be 'own' or 'join'. If not set, both type will return.</param>
- /// <param name="visibility">"Private" or "Register" or "Public". If set, only timelines whose visibility is specified one will return.</param>
- /// <returns>The timeline list.</returns>
- [HttpGet("timelines")]
- [ProducesResponseType(StatusCodes.Status200OK)]
- [ProducesResponseType(StatusCodes.Status400BadRequest)]
- public async Task<ActionResult<List<TimelineInfo>>> TimelineList([FromQuery][Username] string? relate, [FromQuery][RegularExpression("(own)|(join)")] string? relateType, [FromQuery] string? visibility)
- {
- List<TimelineVisibility>? visibilityFilter = null;
- if (visibility != null)
- {
- visibilityFilter = new List<TimelineVisibility>();
- var items = visibility.Split('|');
- foreach (var item in items)
- {
- if (item.Equals(nameof(TimelineVisibility.Private), StringComparison.OrdinalIgnoreCase))
- {
- if (!visibilityFilter.Contains(TimelineVisibility.Private))
- visibilityFilter.Add(TimelineVisibility.Private);
- }
- else if (item.Equals(nameof(TimelineVisibility.Register), StringComparison.OrdinalIgnoreCase))
- {
- if (!visibilityFilter.Contains(TimelineVisibility.Register))
- visibilityFilter.Add(TimelineVisibility.Register);
- }
- else if (item.Equals(nameof(TimelineVisibility.Public), StringComparison.OrdinalIgnoreCase))
- {
- if (!visibilityFilter.Contains(TimelineVisibility.Public))
- visibilityFilter.Add(TimelineVisibility.Public);
- }
- else
- {
- return BadRequest(ErrorResponse.Common.CustomMessage_InvalidModel(Resources.Messages.TimelineController_QueryVisibilityUnknown, item));
- }
- }
- }
-
- TimelineUserRelationship? relationship = null;
- if (relate != null)
- {
- try
- {
- var relatedUserId = await _userService.GetUserIdByUsername(relate);
-
- relationship = new TimelineUserRelationship(relateType switch
- {
- "own" => TimelineUserRelationshipType.Own,
- "join" => TimelineUserRelationshipType.Join,
- _ => TimelineUserRelationshipType.Default
- }, relatedUserId);
- }
- catch (UserNotExistException)
- {
- return BadRequest(ErrorResponse.TimelineController.QueryRelateNotExist());
- }
- }
-
- var timelines = await _service.GetTimelines(relationship, visibilityFilter);
- var result = _mapper.Map<List<TimelineInfo>>(timelines);
- return result;
- }
-
- /// <summary>
- /// Get info of a timeline.
- /// </summary>
- /// <param name="name">The timeline name.</param>
- /// <param name="checkUniqueId">A unique id. If specified and if-modified-since is also specified, the timeline info will return when unique id is not the specified one even if it is not modified.</param>
- /// <param name="queryIfModifiedSince">Same effect as If-Modified-Since header and take precedence than it.</param>
- /// <param name="headerIfModifiedSince">If specified, will return 304 if not modified.</param>
- /// <returns>The timeline info.</returns>
- [HttpGet("timelines/{name}")]
- [ProducesResponseType(StatusCodes.Status200OK)]
- [ProducesResponseType(StatusCodes.Status304NotModified)]
- [ProducesResponseType(StatusCodes.Status404NotFound)]
- public async Task<ActionResult<TimelineInfo>> TimelineGet([FromRoute][GeneralTimelineName] string name, [FromQuery] string? checkUniqueId, [FromQuery(Name = "ifModifiedSince")] DateTime? queryIfModifiedSince, [FromHeader(Name = "If-Modified-Since")] DateTime? headerIfModifiedSince)
- {
- DateTime? ifModifiedSince = null;
- if (queryIfModifiedSince.HasValue)
- {
- ifModifiedSince = queryIfModifiedSince.Value;
- }
- else if (headerIfModifiedSince != null)
- {
- ifModifiedSince = headerIfModifiedSince.Value;
- }
-
- bool returnNotModified = false;
-
- if (ifModifiedSince.HasValue)
- {
- var lastModified = await _service.GetTimelineLastModifiedTime(name);
- if (lastModified < ifModifiedSince.Value)
- {
- if (checkUniqueId != null)
- {
- var uniqueId = await _service.GetTimelineUniqueId(name);
- if (uniqueId == checkUniqueId)
- {
- returnNotModified = true;
- }
- }
- else
- {
- returnNotModified = true;
- }
- }
- }
-
- if (returnNotModified)
- {
- return StatusCode(StatusCodes.Status304NotModified);
- }
- else
- {
- var timeline = await _service.GetTimeline(name);
- var result = _mapper.Map<TimelineInfo>(timeline);
- return result;
- }
- }
-
- /// <summary>
- /// Get posts of a timeline.
- /// </summary>
- /// <param name="name">The name of the timeline.</param>
- /// <param name="modifiedSince">If set, only posts modified since the time will return.</param>
- /// <param name="includeDeleted">If set to true, deleted post will also return.</param>
- /// <returns>The post list.</returns>
- [HttpGet("timelines/{name}/posts")]
- [ProducesResponseType(StatusCodes.Status200OK)]
- [ProducesResponseType(StatusCodes.Status403Forbidden)]
- [ProducesResponseType(StatusCodes.Status404NotFound)]
- public async Task<ActionResult<List<TimelinePostInfo>>> PostListGet([FromRoute][GeneralTimelineName] string name, [FromQuery] DateTime? modifiedSince, [FromQuery] bool? includeDeleted)
- {
- if (!this.IsAdministrator() && !await _service.HasReadPermission(name, this.GetOptionalUserId()))
- {
- return StatusCode(StatusCodes.Status403Forbidden, ErrorResponse.Common.Forbid());
- }
-
- List<TimelinePost> posts = await _service.GetPosts(name, modifiedSince, includeDeleted ?? false);
-
- var result = _mapper.Map<List<TimelinePostInfo>>(posts);
- return result;
- }
-
- /// <summary>
- /// Get the data of a post. Usually a image post.
- /// </summary>
- /// <param name="name">Timeline name.</param>
- /// <param name="id">The id of the post.</param>
- /// <param name="ifNoneMatch">If-None-Match header.</param>
- /// <returns>The data.</returns>
- [HttpGet("timelines/{name}/posts/{id}/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<IActionResult> PostDataGet([FromRoute][GeneralTimelineName] string name, [FromRoute] long id, [FromHeader(Name = "If-None-Match")] string? ifNoneMatch)
- {
- _ = ifNoneMatch;
- if (!this.IsAdministrator() && !await _service.HasReadPermission(name, this.GetOptionalUserId()))
- {
- return StatusCode(StatusCodes.Status403Forbidden, ErrorResponse.Common.Forbid());
- }
-
- try
- {
- return await DataCacheHelper.GenerateActionResult(this, () => _service.GetPostDataETag(name, id), async () =>
- {
- var data = await _service.GetPostData(name, id);
- return data;
- });
- }
- catch (TimelinePostNotExistException)
- {
- return NotFound(ErrorResponse.TimelineController.PostNotExist());
- }
- catch (TimelinePostNoDataException)
- {
- return BadRequest(ErrorResponse.TimelineController.PostNoData());
- }
- }
-
- /// <summary>
- /// Create a new post.
- /// </summary>
- /// <param name="name">Timeline name.</param>
- /// <param name="body"></param>
- /// <returns>Info of new post.</returns>
- [HttpPost("timelines/{name}/posts")]
- [Authorize]
- [ProducesResponseType(StatusCodes.Status200OK)]
- [ProducesResponseType(StatusCodes.Status400BadRequest)]
- [ProducesResponseType(StatusCodes.Status401Unauthorized)]
- [ProducesResponseType(StatusCodes.Status403Forbidden)]
- public async Task<ActionResult<TimelinePostInfo>> PostPost([FromRoute][GeneralTimelineName] string name, [FromBody] TimelinePostCreateRequest body)
- {
- var id = this.GetUserId();
- if (!this.IsAdministrator() && !await _service.IsMemberOf(name, id))
- {
- return StatusCode(StatusCodes.Status403Forbidden, ErrorResponse.Common.Forbid());
- }
-
- var content = body.Content;
-
- TimelinePost 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 _service.CreateTextPost(name, id, 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 _service.CreateImagePost(name, id, 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 = _mapper.Map<TimelinePostInfo>(post);
- return result;
- }
-
- /// <summary>
- /// Delete a post.
- /// </summary>
- /// <param name="name">Timeline name.</param>
- /// <param name="id">Post id.</param>
- /// <returns>Info of deletion.</returns>
- [HttpDelete("timelines/{name}/posts/{id}")]
- [Authorize]
- [ProducesResponseType(StatusCodes.Status200OK)]
- [ProducesResponseType(StatusCodes.Status400BadRequest)]
- [ProducesResponseType(StatusCodes.Status401Unauthorized)]
- [ProducesResponseType(StatusCodes.Status403Forbidden)]
- public async Task<ActionResult<CommonDeleteResponse>> PostDelete([FromRoute][GeneralTimelineName] string name, [FromRoute] long id)
- {
- if (!this.IsAdministrator() && !await _service.HasPostModifyPermission(name, id, this.GetUserId()))
- {
- return StatusCode(StatusCodes.Status403Forbidden, ErrorResponse.Common.Forbid());
- }
- try
- {
- await _service.DeletePost(name, id);
- return CommonDeleteResponse.Delete();
- }
- catch (TimelinePostNotExistException)
- {
- return CommonDeleteResponse.NotExist();
- }
- }
-
- /// <summary>
- /// Change properties of a timeline.
- /// </summary>
- /// <param name="name">Timeline name.</param>
- /// <param name="body"></param>
- /// <returns>The new info.</returns>
- [HttpPatch("timelines/{name}")]
- [Authorize]
- [ProducesResponseType(StatusCodes.Status200OK)]
- [ProducesResponseType(StatusCodes.Status400BadRequest)]
- [ProducesResponseType(StatusCodes.Status401Unauthorized)]
- [ProducesResponseType(StatusCodes.Status403Forbidden)]
- public async Task<ActionResult<TimelineInfo>> TimelinePatch([FromRoute][GeneralTimelineName] string name, [FromBody] TimelinePatchRequest body)
- {
- if (!this.IsAdministrator() && !(await _service.HasManagePermission(name, this.GetUserId())))
- {
- return StatusCode(StatusCodes.Status403Forbidden, ErrorResponse.Common.Forbid());
- }
- await _service.ChangeProperty(name, _mapper.Map<TimelineChangePropertyRequest>(body));
- var timeline = await _service.GetTimeline(name);
- var result = _mapper.Map<TimelineInfo>(timeline);
- return result;
- }
-
- /// <summary>
- /// Add a member to timeline.
- /// </summary>
- /// <param name="name">Timeline name.</param>
- /// <param name="member">The new member's username.</param>
- [HttpPut("timelines/{name}/members/{member}")]
- [Authorize]
- [ProducesResponseType(StatusCodes.Status200OK)]
- [ProducesResponseType(StatusCodes.Status400BadRequest)]
- [ProducesResponseType(StatusCodes.Status401Unauthorized)]
- [ProducesResponseType(StatusCodes.Status403Forbidden)]
- public async Task<ActionResult> TimelineMemberPut([FromRoute][GeneralTimelineName] string name, [FromRoute][Username] string member)
- {
- if (!this.IsAdministrator() && !(await _service.HasManagePermission(name, this.GetUserId())))
- {
- return StatusCode(StatusCodes.Status403Forbidden, ErrorResponse.Common.Forbid());
- }
-
- try
- {
- await _service.ChangeMember(name, new List<string> { member }, null);
- return Ok();
- }
- catch (UserNotExistException)
- {
- return BadRequest(ErrorResponse.TimelineController.MemberPut_NotExist());
- }
- }
-
- /// <summary>
- /// Remove a member from timeline.
- /// </summary>
- /// <param name="name">Timeline name.</param>
- /// <param name="member">The member's username.</param>
- [HttpDelete("timelines/{name}/members/{member}")]
- [Authorize]
- [ProducesResponseType(StatusCodes.Status200OK)]
- [ProducesResponseType(StatusCodes.Status401Unauthorized)]
- [ProducesResponseType(StatusCodes.Status403Forbidden)]
- public async Task<ActionResult> TimelineMemberDelete([FromRoute][GeneralTimelineName] string name, [FromRoute][Username] string member)
- {
- if (!this.IsAdministrator() && !(await _service.HasManagePermission(name, this.GetUserId())))
- {
- return StatusCode(StatusCodes.Status403Forbidden, ErrorResponse.Common.Forbid());
- }
-
- try
- {
- await _service.ChangeMember(name, null, new List<string> { member });
- return Ok(CommonDeleteResponse.Delete());
- }
- catch (UserNotExistException)
- {
- return Ok(CommonDeleteResponse.NotExist());
- }
- }
-
- /// <summary>
- /// Create a timeline.
- /// </summary>
- /// <param name="body"></param>
- /// <returns>Info of new timeline.</returns>
- [HttpPost("timelines")]
- [Authorize]
- [ProducesResponseType(StatusCodes.Status200OK)]
- [ProducesResponseType(StatusCodes.Status400BadRequest)]
- [ProducesResponseType(StatusCodes.Status401Unauthorized)]
- public async Task<ActionResult<TimelineInfo>> TimelineCreate([FromBody] TimelineCreateRequest body)
- {
- var userId = this.GetUserId();
-
- try
- {
- var timeline = await _service.CreateTimeline(body.Name, userId);
- var result = _mapper.Map<TimelineInfo>(timeline);
- return result;
- }
- catch (EntityAlreadyExistException e) when (e.EntityName == EntityNames.Timeline)
- {
- return BadRequest(ErrorResponse.TimelineController.NameConflict());
- }
- }
-
- /// <summary>
- /// Delete a timeline.
- /// </summary>
- /// <param name="name">Timeline name.</param>
- /// <returns>Info of deletion.</returns>
- [HttpDelete("timelines/{name}")]
- [Authorize]
- [ProducesResponseType(StatusCodes.Status200OK)]
- [ProducesResponseType(StatusCodes.Status400BadRequest)]
- [ProducesResponseType(StatusCodes.Status401Unauthorized)]
- [ProducesResponseType(StatusCodes.Status403Forbidden)]
- public async Task<ActionResult<CommonDeleteResponse>> TimelineDelete([FromRoute][TimelineName] string name)
- {
- if (!this.IsAdministrator() && !(await _service.HasManagePermission(name, this.GetUserId())))
- {
- return StatusCode(StatusCodes.Status403Forbidden, ErrorResponse.Common.Forbid());
- }
-
- try
- {
- await _service.DeleteTimeline(name);
- return CommonDeleteResponse.Delete();
- }
- catch (TimelineNotExistException)
- {
- return CommonDeleteResponse.NotExist();
- }
- }
-
- [HttpPost("timelineop/changename")]
- [Authorize]
- [ProducesResponseType(StatusCodes.Status200OK)]
- [ProducesResponseType(StatusCodes.Status400BadRequest)]
- [ProducesResponseType(StatusCodes.Status401Unauthorized)]
- [ProducesResponseType(StatusCodes.Status403Forbidden)]
- public async Task<ActionResult<TimelineInfo>> TimelineOpChangeName([FromBody] TimelineChangeNameRequest body)
- {
- if (!this.IsAdministrator() && !(await _service.HasManagePermission(body.OldName, this.GetUserId())))
- {
- return StatusCode(StatusCodes.Status403Forbidden, ErrorResponse.Common.Forbid());
- }
-
- try
- {
- var timeline = await _service.ChangeTimelineName(body.OldName, body.NewName);
- return Ok(_mapper.Map<TimelineInfo>(timeline));
- }
- catch (EntityAlreadyExistException)
- {
- return BadRequest(ErrorResponse.TimelineController.NameConflict());
- }
- }
- }
-}
diff --git a/Timeline/Controllers/TokenController.cs b/Timeline/Controllers/TokenController.cs
deleted file mode 100644
index 8f2ca600..00000000
--- a/Timeline/Controllers/TokenController.cs
+++ /dev/null
@@ -1,142 +0,0 @@
-using AutoMapper;
-using Microsoft.AspNetCore.Authorization;
-using Microsoft.AspNetCore.Http;
-using Microsoft.AspNetCore.Mvc;
-using Microsoft.Extensions.Logging;
-using System;
-using System.Globalization;
-using System.Threading.Tasks;
-using Timeline.Helpers;
-using Timeline.Models.Http;
-using Timeline.Services;
-using Timeline.Services.Exceptions;
-using static Timeline.Resources.Controllers.TokenController;
-
-namespace Timeline.Controllers
-{
- /// <summary>
- /// Operation about tokens.
- /// </summary>
- [Route("token")]
- [ApiController]
- [ProducesErrorResponseType(typeof(CommonResponse))]
- public class TokenController : Controller
- {
- private readonly IUserTokenManager _userTokenManager;
- private readonly ILogger<TokenController> _logger;
- private readonly IClock _clock;
-
- private readonly IMapper _mapper;
-
- /// <summary></summary>
- public TokenController(IUserTokenManager userTokenManager, ILogger<TokenController> logger, IClock clock, IMapper mapper)
- {
- _userTokenManager = userTokenManager;
- _logger = logger;
- _clock = clock;
- _mapper = mapper;
- }
-
- /// <summary>
- /// Create a new token for a user.
- /// </summary>
- /// <returns>Result of token creation.</returns>
- [HttpPost("create")]
- [AllowAnonymous]
- [ProducesResponseType(StatusCodes.Status200OK)]
- [ProducesResponseType(StatusCodes.Status400BadRequest)]
- public async Task<ActionResult<CreateTokenResponse>> Create([FromBody] CreateTokenRequest request)
- {
- void LogFailure(string reason, Exception? e = null)
- {
- _logger.LogInformation(e, Log.Format(LogCreateFailure,
- ("Reason", reason),
- ("Username", request.Username),
- ("Password", request.Password),
- ("Expire (in days)", request.Expire)
- ));
- }
-
- try
- {
- DateTime? expireTime = null;
- if (request.Expire != null)
- expireTime = _clock.GetCurrentTime().AddDays(request.Expire.Value);
-
- var result = await _userTokenManager.CreateToken(request.Username, request.Password, expireTime);
-
- _logger.LogInformation(Log.Format(LogCreateSuccess,
- ("Username", request.Username),
- ("Expire At", expireTime?.ToString(CultureInfo.CurrentCulture.DateTimeFormat) ?? "default")
- ));
- return Ok(new CreateTokenResponse
- {
- Token = result.Token,
- User = _mapper.Map<UserInfo>(result.User)
- });
- }
- catch (UserNotExistException e)
- {
- LogFailure(LogUserNotExist, e);
- return BadRequest(ErrorResponse.TokenController.Create_BadCredential());
- }
- catch (BadPasswordException e)
- {
- LogFailure(LogBadPassword, e);
- return BadRequest(ErrorResponse.TokenController.Create_BadCredential());
- }
- }
-
- /// <summary>
- /// Verify a token.
- /// </summary>
- /// <returns>Result of token verification.</returns>
- [HttpPost("verify")]
- [AllowAnonymous]
- [ProducesResponseType(StatusCodes.Status200OK)]
- [ProducesResponseType(StatusCodes.Status400BadRequest)]
- public async Task<ActionResult<VerifyTokenResponse>> Verify([FromBody] VerifyTokenRequest request)
- {
- void LogFailure(string reason, Exception? e = null, params (string, object?)[] otherProperties)
- {
- var properties = new (string, object?)[2 + otherProperties.Length];
- properties[0] = ("Reason", reason);
- properties[1] = ("Token", request.Token);
- otherProperties.CopyTo(properties, 2);
- _logger.LogInformation(e, Log.Format(LogVerifyFailure, properties));
- }
-
- try
- {
- var result = await _userTokenManager.VerifyToken(request.Token);
- _logger.LogInformation(Log.Format(LogVerifySuccess,
- ("Username", result.Username), ("Token", request.Token)));
- return Ok(new VerifyTokenResponse
- {
- User = _mapper.Map<UserInfo>(result)
- });
- }
- catch (UserTokenTimeExpireException e)
- {
- LogFailure(LogVerifyExpire, e, ("Expire Time", e.ExpireTime), ("Verify Time", e.VerifyTime));
- return BadRequest(ErrorResponse.TokenController.Verify_TimeExpired());
- }
- catch (UserTokenBadVersionException e)
- {
- LogFailure(LogVerifyOldVersion, e, ("Token Version", e.TokenVersion), ("Required Version", e.RequiredVersion));
- return BadRequest(ErrorResponse.TokenController.Verify_OldVersion());
-
- }
- catch (UserTokenBadFormatException e)
- {
- LogFailure(LogVerifyBadFormat, e);
- return BadRequest(ErrorResponse.TokenController.Verify_BadFormat());
- }
- catch (UserNotExistException e)
- {
- LogFailure(LogVerifyUserNotExist, e);
- return BadRequest(ErrorResponse.TokenController.Verify_UserNotExist());
- }
- }
- }
-}
diff --git a/Timeline/Controllers/UserAvatarController.cs b/Timeline/Controllers/UserAvatarController.cs
deleted file mode 100644
index bc4afa30..00000000
--- a/Timeline/Controllers/UserAvatarController.cs
+++ /dev/null
@@ -1,174 +0,0 @@
-using Microsoft.AspNetCore.Authorization;
-using Microsoft.AspNetCore.Http;
-using Microsoft.AspNetCore.Mvc;
-using Microsoft.Extensions.Logging;
-using Microsoft.Net.Http.Headers;
-using System;
-using System.Threading.Tasks;
-using Timeline.Auth;
-using Timeline.Filters;
-using Timeline.Helpers;
-using Timeline.Models;
-using Timeline.Models.Http;
-using Timeline.Models.Validation;
-using Timeline.Services;
-using Timeline.Services.Exceptions;
-using static Timeline.Resources.Controllers.UserAvatarController;
-
-namespace Timeline.Controllers
-{
- /// <summary>
- /// Operations about user avatar.
- /// </summary>
- [ApiController]
- [ProducesErrorResponseType(typeof(CommonResponse))]
- public class UserAvatarController : Controller
- {
- private readonly ILogger<UserAvatarController> _logger;
-
- private readonly IUserService _userService;
- private readonly IUserAvatarService _service;
-
- /// <summary>
- ///
- /// </summary>
- public UserAvatarController(ILogger<UserAvatarController> logger, IUserService userService, IUserAvatarService service)
- {
- _logger = logger;
- _userService = userService;
- _service = service;
- }
-
- /// <summary>
- /// Get avatar of a user.
- /// </summary>
- /// <param name="username">Username of the user to get avatar of.</param>
- /// <param name="ifNoneMatch">If-None-Match header.</param>
- /// <returns>Avatar data.</returns>
- [HttpGet("users/{username}/avatar")]
- [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.Status404NotFound)]
- public async Task<IActionResult> Get([FromRoute][Username] string username, [FromHeader(Name = "If-None-Match")] string? ifNoneMatch)
- {
- _ = ifNoneMatch;
- long id;
- try
- {
- id = await _userService.GetUserIdByUsername(username);
- }
- catch (UserNotExistException e)
- {
- _logger.LogInformation(e, Log.Format(LogGetUserNotExist, ("Username", username)));
- return NotFound(ErrorResponse.UserCommon.NotExist());
- }
-
- return await DataCacheHelper.GenerateActionResult(this, () => _service.GetAvatarETag(id), async () =>
- {
- var avatar = await _service.GetAvatar(id);
- return avatar.ToCacheableData();
- });
- }
-
- /// <summary>
- /// Set avatar of a user. You have to be administrator to change other's.
- /// </summary>
- /// <param name="username">Username of the user to set avatar of.</param>
- /// <param name="body">The avatar data.</param>
- [HttpPut("users/{username}/avatar")]
- [Authorize]
- [Consumes("image/png", "image/jpeg", "image/gif", "image/webp")]
- [MaxContentLength(1000 * 1000 * 10)]
- [ProducesResponseType(typeof(void), StatusCodes.Status200OK)]
- [ProducesResponseType(StatusCodes.Status400BadRequest)]
- [ProducesResponseType(StatusCodes.Status401Unauthorized)]
- [ProducesResponseType(StatusCodes.Status403Forbidden)]
- public async Task<IActionResult> Put([FromRoute][Username] string username, [FromBody] ByteData body)
- {
- if (!User.IsAdministrator() && User.Identity.Name != username)
- {
- _logger.LogInformation(Log.Format(LogPutForbid,
- ("Operator Username", User.Identity.Name), ("Username To Put Avatar", username)));
- return StatusCode(StatusCodes.Status403Forbidden, ErrorResponse.Common.Forbid());
- }
-
- long id;
- try
- {
- id = await _userService.GetUserIdByUsername(username);
- }
- catch (UserNotExistException e)
- {
- _logger.LogInformation(e, Log.Format(LogPutUserNotExist, ("Username", username)));
- return BadRequest(ErrorResponse.UserCommon.NotExist());
- }
-
- try
- {
- var etag = await _service.SetAvatar(id, new Avatar
- {
- Data = body.Data,
- Type = body.ContentType
- });
-
- _logger.LogInformation(Log.Format(LogPutSuccess,
- ("Username", username), ("Mime Type", Request.ContentType)));
-
- Response.Headers.Append("ETag", new EntityTagHeaderValue($"\"{etag}\"").ToString());
-
- return Ok();
- }
- catch (ImageException e)
- {
- _logger.LogInformation(e, Log.Format(LogPutUserBadFormat, ("Username", username)));
- return BadRequest(e.Error switch
- {
- ImageException.ErrorReason.CantDecode => ErrorResponse.UserAvatar.BadFormat_CantDecode(),
- ImageException.ErrorReason.UnmatchedFormat => ErrorResponse.UserAvatar.BadFormat_UnmatchedFormat(),
- ImageException.ErrorReason.NotSquare => ErrorResponse.UserAvatar.BadFormat_BadSize(),
- _ =>
- throw new Exception(ExceptionUnknownAvatarFormatError)
- });
- }
- }
-
- /// <summary>
- /// Reset the avatar to the default one. You have to be administrator to reset other's.
- /// </summary>
- /// <param name="username">Username of the user.</param>
- /// <response code="200">Succeeded to reset.</response>
- /// <response code="400">Error code is 10010001 if user does not exist.</response>
- /// <response code="401">You have not logged in.</response>
- /// <response code="403">You are not administrator.</response>
- [HttpDelete("users/{username}/avatar")]
- [ProducesResponseType(typeof(void), StatusCodes.Status200OK)]
- [ProducesResponseType(StatusCodes.Status400BadRequest)]
- [ProducesResponseType(StatusCodes.Status401Unauthorized)]
- [ProducesResponseType(StatusCodes.Status403Forbidden)]
- [Authorize]
- public async Task<IActionResult> Delete([FromRoute][Username] string username)
- {
- if (!User.IsAdministrator() && User.Identity.Name != username)
- {
- _logger.LogInformation(Log.Format(LogDeleteForbid,
- ("Operator Username", User.Identity.Name), ("Username To Delete Avatar", username)));
- return StatusCode(StatusCodes.Status403Forbidden, ErrorResponse.Common.Forbid());
- }
-
- long id;
- try
- {
- id = await _userService.GetUserIdByUsername(username);
- }
- catch (UserNotExistException e)
- {
- _logger.LogInformation(e, Log.Format(LogDeleteNotExist, ("Username", username)));
- return BadRequest(ErrorResponse.UserCommon.NotExist());
- }
-
- await _service.SetAvatar(id, null);
- return Ok();
- }
- }
-}
diff --git a/Timeline/Controllers/UserController.cs b/Timeline/Controllers/UserController.cs
deleted file mode 100644
index 02c09aab..00000000
--- a/Timeline/Controllers/UserController.cs
+++ /dev/null
@@ -1,195 +0,0 @@
-using AutoMapper;
-using Microsoft.AspNetCore.Authorization;
-using Microsoft.AspNetCore.Http;
-using Microsoft.AspNetCore.Mvc;
-using Microsoft.Extensions.Logging;
-using System.Linq;
-using System.Threading.Tasks;
-using Timeline.Auth;
-using Timeline.Helpers;
-using Timeline.Models;
-using Timeline.Models.Http;
-using Timeline.Models.Validation;
-using Timeline.Services;
-using Timeline.Services.Exceptions;
-using static Timeline.Resources.Controllers.UserController;
-using static Timeline.Resources.Messages;
-
-namespace Timeline.Controllers
-{
- /// <summary>
- /// Operations about users.
- /// </summary>
- [ApiController]
- [ProducesErrorResponseType(typeof(CommonResponse))]
- public class UserController : Controller
- {
- private readonly ILogger<UserController> _logger;
- private readonly IUserService _userService;
- private readonly IUserDeleteService _userDeleteService;
- private readonly IMapper _mapper;
-
- /// <summary></summary>
- public UserController(ILogger<UserController> logger, IUserService userService, IUserDeleteService userDeleteService, IMapper mapper)
- {
- _logger = logger;
- _userService = userService;
- _userDeleteService = userDeleteService;
- _mapper = mapper;
- }
-
- private UserInfo ConvertToUserInfo(User user) => _mapper.Map<UserInfo>(user);
-
- /// <summary>
- /// Get all users.
- /// </summary>
- /// <returns>All user list.</returns>
- [HttpGet("users")]
- [ProducesResponseType(StatusCodes.Status200OK)]
- public async Task<ActionResult<UserInfo[]>> List()
- {
- var users = await _userService.GetUsers();
- var result = users.Select(u => ConvertToUserInfo(u)).ToArray();
- return Ok(result);
- }
-
- /// <summary>
- /// Get a user's info.
- /// </summary>
- /// <param name="username">Username of the user.</param>
- /// <returns>User info.</returns>
- [HttpGet("users/{username}")]
- [ProducesResponseType(StatusCodes.Status200OK)]
- [ProducesResponseType(StatusCodes.Status404NotFound)]
- public async Task<ActionResult<UserInfo>> Get([FromRoute][Username] string username)
- {
- try
- {
- var user = await _userService.GetUserByUsername(username);
- return Ok(ConvertToUserInfo(user));
- }
- catch (UserNotExistException e)
- {
- _logger.LogInformation(e, Log.Format(LogGetUserNotExist, ("Username", username)));
- return NotFound(ErrorResponse.UserCommon.NotExist());
- }
- }
-
- /// <summary>
- /// Change a user's property.
- /// </summary>
- /// <param name="body"></param>
- /// <param name="username">Username of the user to change.</param>
- /// <returns>The new user info.</returns>
- [HttpPatch("users/{username}"), Authorize]
- [ProducesResponseType(StatusCodes.Status200OK)]
- [ProducesResponseType(StatusCodes.Status400BadRequest)]
- [ProducesResponseType(StatusCodes.Status401Unauthorized)]
- [ProducesResponseType(StatusCodes.Status403Forbidden)]
- [ProducesResponseType(StatusCodes.Status404NotFound)]
- public async Task<ActionResult<UserInfo>> Patch([FromBody] UserPatchRequest body, [FromRoute][Username] string username)
- {
- if (this.IsAdministrator())
- {
- try
- {
- var user = await _userService.ModifyUser(username, _mapper.Map<User>(body));
- return Ok(ConvertToUserInfo(user));
- }
- catch (UserNotExistException e)
- {
- _logger.LogInformation(e, Log.Format(LogPatchUserNotExist, ("Username", username)));
- return NotFound(ErrorResponse.UserCommon.NotExist());
- }
- catch (EntityAlreadyExistException e) when (e.EntityName == EntityNames.User)
- {
- return BadRequest(ErrorResponse.UserController.UsernameConflict());
- }
- }
- else
- {
- if (User.Identity.Name != username)
- return StatusCode(StatusCodes.Status403Forbidden,
- ErrorResponse.Common.CustomMessage_Forbid(Common_Forbid_NotSelf));
-
- if (body.Username != null)
- return StatusCode(StatusCodes.Status403Forbidden,
- ErrorResponse.Common.CustomMessage_Forbid(UserController_Patch_Forbid_Username));
-
- if (body.Password != null)
- return StatusCode(StatusCodes.Status403Forbidden,
- ErrorResponse.Common.CustomMessage_Forbid(UserController_Patch_Forbid_Password));
-
- if (body.Administrator != null)
- return StatusCode(StatusCodes.Status403Forbidden,
- ErrorResponse.Common.CustomMessage_Forbid(UserController_Patch_Forbid_Administrator));
-
- var user = await _userService.ModifyUser(this.GetUserId(), _mapper.Map<User>(body));
- return Ok(ConvertToUserInfo(user));
- }
- }
-
- /// <summary>
- /// Delete a user and all his related data. You have to be administrator.
- /// </summary>
- /// <param name="username">Username of the user to delete.</param>
- /// <returns>Info of deletion.</returns>
- [HttpDelete("users/{username}"), AdminAuthorize]
- [ProducesResponseType(StatusCodes.Status200OK)]
- [ProducesResponseType(StatusCodes.Status401Unauthorized)]
- [ProducesResponseType(StatusCodes.Status403Forbidden)]
- public async Task<ActionResult<CommonDeleteResponse>> Delete([FromRoute][Username] string username)
- {
- var delete = await _userDeleteService.DeleteUser(username);
- if (delete)
- return Ok(CommonDeleteResponse.Delete());
- else
- return Ok(CommonDeleteResponse.NotExist());
- }
-
- /// <summary>
- /// Create a new user. You have to be administrator.
- /// </summary>
- /// <returns>The new user's info.</returns>
- [HttpPost("userop/createuser"), AdminAuthorize]
- [ProducesResponseType(StatusCodes.Status200OK)]
- [ProducesResponseType(StatusCodes.Status400BadRequest)]
- [ProducesResponseType(StatusCodes.Status401Unauthorized)]
- [ProducesResponseType(StatusCodes.Status403Forbidden)]
- public async Task<ActionResult<UserInfo>> CreateUser([FromBody] CreateUserRequest body)
- {
- try
- {
- var user = await _userService.CreateUser(_mapper.Map<User>(body));
- return Ok(ConvertToUserInfo(user));
- }
- catch (EntityAlreadyExistException e) when (e.EntityName == EntityNames.User)
- {
- return BadRequest(ErrorResponse.UserController.UsernameConflict());
- }
- }
-
- /// <summary>
- /// Change password with old password.
- /// </summary>
- [HttpPost("userop/changepassword"), Authorize]
- [ProducesResponseType(StatusCodes.Status200OK)]
- [ProducesResponseType(StatusCodes.Status400BadRequest)]
- [ProducesResponseType(StatusCodes.Status401Unauthorized)]
- public async Task<ActionResult> ChangePassword([FromBody] ChangePasswordRequest request)
- {
- try
- {
- await _userService.ChangePassword(this.GetUserId(), request.OldPassword, request.NewPassword);
- return Ok();
- }
- catch (BadPasswordException e)
- {
- _logger.LogInformation(e, Log.Format(LogChangePasswordBadPassword,
- ("Username", User.Identity.Name), ("Old Password", request.OldPassword)));
- return BadRequest(ErrorResponse.UserController.ChangePassword_BadOldPassword());
- }
- // User can't be non-existent or the token is bad.
- }
- }
-}