From ac769e656b122ff569c3f1534701b71e00fed586 Mon Sep 17 00:00:00 2001 From: crupest Date: Tue, 27 Oct 2020 19:21:35 +0800 Subject: Split front and back end. --- Timeline/Controllers/ControllerAuthExtensions.cs | 40 -- .../Controllers/Testing/TestingAuthController.cs | 32 -- Timeline/Controllers/TimelineController.cs | 491 --------------------- Timeline/Controllers/TokenController.cs | 142 ------ Timeline/Controllers/UserAvatarController.cs | 174 -------- Timeline/Controllers/UserController.cs | 195 -------- 6 files changed, 1074 deletions(-) delete mode 100644 Timeline/Controllers/ControllerAuthExtensions.cs delete mode 100644 Timeline/Controllers/Testing/TestingAuthController.cs delete mode 100644 Timeline/Controllers/TimelineController.cs delete mode 100644 Timeline/Controllers/TokenController.cs delete mode 100644 Timeline/Controllers/UserAvatarController.cs delete mode 100644 Timeline/Controllers/UserController.cs (limited to 'Timeline/Controllers') 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 -{ - /// - /// Operations about timeline. - /// - [ApiController] - [CatchTimelineNotExistException] - [ProducesErrorResponseType(typeof(CommonResponse))] - public class TimelineController : Controller - { - private readonly ILogger _logger; - - private readonly IUserService _userService; - private readonly ITimelineService _service; - - private readonly IMapper _mapper; - - /// - /// - /// - public TimelineController(ILogger logger, IUserService userService, ITimelineService service, IMapper mapper) - { - _logger = logger; - _userService = userService; - _service = service; - _mapper = mapper; - } - - /// - /// List all timelines. - /// - /// A username. If set, only timelines related to the user will return. - /// Specify the relation type, may be 'own' or 'join'. If not set, both type will return. - /// "Private" or "Register" or "Public". If set, only timelines whose visibility is specified one will return. - /// The timeline list. - [HttpGet("timelines")] - [ProducesResponseType(StatusCodes.Status200OK)] - [ProducesResponseType(StatusCodes.Status400BadRequest)] - public async Task>> TimelineList([FromQuery][Username] string? relate, [FromQuery][RegularExpression("(own)|(join)")] string? relateType, [FromQuery] string? visibility) - { - List? visibilityFilter = null; - if (visibility != null) - { - visibilityFilter = new List(); - 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>(timelines); - return result; - } - - /// - /// Get info of a timeline. - /// - /// The timeline name. - /// 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. - /// Same effect as If-Modified-Since header and take precedence than it. - /// If specified, will return 304 if not modified. - /// The timeline info. - [HttpGet("timelines/{name}")] - [ProducesResponseType(StatusCodes.Status200OK)] - [ProducesResponseType(StatusCodes.Status304NotModified)] - [ProducesResponseType(StatusCodes.Status404NotFound)] - public async Task> 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(timeline); - return result; - } - } - - /// - /// 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("timelines/{name}/posts")] - [ProducesResponseType(StatusCodes.Status200OK)] - [ProducesResponseType(StatusCodes.Status403Forbidden)] - [ProducesResponseType(StatusCodes.Status404NotFound)] - public async Task>> 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 posts = await _service.GetPosts(name, modifiedSince, includeDeleted ?? false); - - var result = _mapper.Map>(posts); - 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("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 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()); - } - } - - /// - /// Create a new post. - /// - /// Timeline name. - /// - /// Info of new post. - [HttpPost("timelines/{name}/posts")] - [Authorize] - [ProducesResponseType(StatusCodes.Status200OK)] - [ProducesResponseType(StatusCodes.Status400BadRequest)] - [ProducesResponseType(StatusCodes.Status401Unauthorized)] - [ProducesResponseType(StatusCodes.Status403Forbidden)] - public async Task> 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(post); - return result; - } - - /// - /// Delete a post. - /// - /// Timeline name. - /// Post id. - /// Info of deletion. - [HttpDelete("timelines/{name}/posts/{id}")] - [Authorize] - [ProducesResponseType(StatusCodes.Status200OK)] - [ProducesResponseType(StatusCodes.Status400BadRequest)] - [ProducesResponseType(StatusCodes.Status401Unauthorized)] - [ProducesResponseType(StatusCodes.Status403Forbidden)] - public async Task> 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(); - } - } - - /// - /// Change properties of a timeline. - /// - /// Timeline name. - /// - /// The new info. - [HttpPatch("timelines/{name}")] - [Authorize] - [ProducesResponseType(StatusCodes.Status200OK)] - [ProducesResponseType(StatusCodes.Status400BadRequest)] - [ProducesResponseType(StatusCodes.Status401Unauthorized)] - [ProducesResponseType(StatusCodes.Status403Forbidden)] - public async Task> 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(body)); - var timeline = await _service.GetTimeline(name); - var result = _mapper.Map(timeline); - return result; - } - - /// - /// Add a member to timeline. - /// - /// Timeline name. - /// The new member's username. - [HttpPut("timelines/{name}/members/{member}")] - [Authorize] - [ProducesResponseType(StatusCodes.Status200OK)] - [ProducesResponseType(StatusCodes.Status400BadRequest)] - [ProducesResponseType(StatusCodes.Status401Unauthorized)] - [ProducesResponseType(StatusCodes.Status403Forbidden)] - public async Task 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 { member }, null); - return Ok(); - } - catch (UserNotExistException) - { - return BadRequest(ErrorResponse.TimelineController.MemberPut_NotExist()); - } - } - - /// - /// Remove a member from timeline. - /// - /// Timeline name. - /// The member's username. - [HttpDelete("timelines/{name}/members/{member}")] - [Authorize] - [ProducesResponseType(StatusCodes.Status200OK)] - [ProducesResponseType(StatusCodes.Status401Unauthorized)] - [ProducesResponseType(StatusCodes.Status403Forbidden)] - public async Task 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 { member }); - return Ok(CommonDeleteResponse.Delete()); - } - catch (UserNotExistException) - { - return Ok(CommonDeleteResponse.NotExist()); - } - } - - /// - /// Create a timeline. - /// - /// - /// Info of new timeline. - [HttpPost("timelines")] - [Authorize] - [ProducesResponseType(StatusCodes.Status200OK)] - [ProducesResponseType(StatusCodes.Status400BadRequest)] - [ProducesResponseType(StatusCodes.Status401Unauthorized)] - public async Task> TimelineCreate([FromBody] TimelineCreateRequest body) - { - var userId = this.GetUserId(); - - try - { - var timeline = await _service.CreateTimeline(body.Name, userId); - var result = _mapper.Map(timeline); - return result; - } - catch (EntityAlreadyExistException e) when (e.EntityName == EntityNames.Timeline) - { - return BadRequest(ErrorResponse.TimelineController.NameConflict()); - } - } - - /// - /// Delete a timeline. - /// - /// Timeline name. - /// Info of deletion. - [HttpDelete("timelines/{name}")] - [Authorize] - [ProducesResponseType(StatusCodes.Status200OK)] - [ProducesResponseType(StatusCodes.Status400BadRequest)] - [ProducesResponseType(StatusCodes.Status401Unauthorized)] - [ProducesResponseType(StatusCodes.Status403Forbidden)] - public async Task> 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> 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(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 -{ - /// - /// Operation about tokens. - /// - [Route("token")] - [ApiController] - [ProducesErrorResponseType(typeof(CommonResponse))] - public class TokenController : Controller - { - private readonly IUserTokenManager _userTokenManager; - private readonly ILogger _logger; - private readonly IClock _clock; - - private readonly IMapper _mapper; - - /// - public TokenController(IUserTokenManager userTokenManager, ILogger logger, IClock clock, IMapper mapper) - { - _userTokenManager = userTokenManager; - _logger = logger; - _clock = clock; - _mapper = mapper; - } - - /// - /// Create a new token for a user. - /// - /// Result of token creation. - [HttpPost("create")] - [AllowAnonymous] - [ProducesResponseType(StatusCodes.Status200OK)] - [ProducesResponseType(StatusCodes.Status400BadRequest)] - public async Task> 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(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()); - } - } - - /// - /// Verify a token. - /// - /// Result of token verification. - [HttpPost("verify")] - [AllowAnonymous] - [ProducesResponseType(StatusCodes.Status200OK)] - [ProducesResponseType(StatusCodes.Status400BadRequest)] - public async Task> 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(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 -{ - /// - /// Operations about user avatar. - /// - [ApiController] - [ProducesErrorResponseType(typeof(CommonResponse))] - public class UserAvatarController : Controller - { - private readonly ILogger _logger; - - private readonly IUserService _userService; - private readonly IUserAvatarService _service; - - /// - /// - /// - public UserAvatarController(ILogger logger, IUserService userService, IUserAvatarService service) - { - _logger = logger; - _userService = userService; - _service = service; - } - - /// - /// Get avatar of a user. - /// - /// Username of the user to get avatar of. - /// If-None-Match header. - /// Avatar data. - [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 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(); - }); - } - - /// - /// Set avatar of a user. You have to be administrator to change other's. - /// - /// Username of the user to set avatar of. - /// The avatar data. - [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 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) - }); - } - } - - /// - /// Reset the avatar to the default one. You have to be administrator to reset other's. - /// - /// Username of the user. - /// Succeeded to reset. - /// Error code is 10010001 if user does not exist. - /// You have not logged in. - /// You are not administrator. - [HttpDelete("users/{username}/avatar")] - [ProducesResponseType(typeof(void), StatusCodes.Status200OK)] - [ProducesResponseType(StatusCodes.Status400BadRequest)] - [ProducesResponseType(StatusCodes.Status401Unauthorized)] - [ProducesResponseType(StatusCodes.Status403Forbidden)] - [Authorize] - public async Task 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 -{ - /// - /// Operations about users. - /// - [ApiController] - [ProducesErrorResponseType(typeof(CommonResponse))] - public class UserController : Controller - { - private readonly ILogger _logger; - private readonly IUserService _userService; - private readonly IUserDeleteService _userDeleteService; - private readonly IMapper _mapper; - - /// - public UserController(ILogger logger, IUserService userService, IUserDeleteService userDeleteService, IMapper mapper) - { - _logger = logger; - _userService = userService; - _userDeleteService = userDeleteService; - _mapper = mapper; - } - - private UserInfo ConvertToUserInfo(User user) => _mapper.Map(user); - - /// - /// Get all users. - /// - /// All user list. - [HttpGet("users")] - [ProducesResponseType(StatusCodes.Status200OK)] - public async Task> List() - { - var users = await _userService.GetUsers(); - var result = users.Select(u => ConvertToUserInfo(u)).ToArray(); - return Ok(result); - } - - /// - /// Get a user's info. - /// - /// Username of the user. - /// User info. - [HttpGet("users/{username}")] - [ProducesResponseType(StatusCodes.Status200OK)] - [ProducesResponseType(StatusCodes.Status404NotFound)] - public async Task> 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()); - } - } - - /// - /// Change a user's property. - /// - /// - /// Username of the user to change. - /// The new user info. - [HttpPatch("users/{username}"), Authorize] - [ProducesResponseType(StatusCodes.Status200OK)] - [ProducesResponseType(StatusCodes.Status400BadRequest)] - [ProducesResponseType(StatusCodes.Status401Unauthorized)] - [ProducesResponseType(StatusCodes.Status403Forbidden)] - [ProducesResponseType(StatusCodes.Status404NotFound)] - public async Task> Patch([FromBody] UserPatchRequest body, [FromRoute][Username] string username) - { - if (this.IsAdministrator()) - { - try - { - var user = await _userService.ModifyUser(username, _mapper.Map(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(body)); - return Ok(ConvertToUserInfo(user)); - } - } - - /// - /// Delete a user and all his related data. You have to be administrator. - /// - /// Username of the user to delete. - /// Info of deletion. - [HttpDelete("users/{username}"), AdminAuthorize] - [ProducesResponseType(StatusCodes.Status200OK)] - [ProducesResponseType(StatusCodes.Status401Unauthorized)] - [ProducesResponseType(StatusCodes.Status403Forbidden)] - public async Task> Delete([FromRoute][Username] string username) - { - var delete = await _userDeleteService.DeleteUser(username); - if (delete) - return Ok(CommonDeleteResponse.Delete()); - else - return Ok(CommonDeleteResponse.NotExist()); - } - - /// - /// Create a new user. You have to be administrator. - /// - /// The new user's info. - [HttpPost("userop/createuser"), AdminAuthorize] - [ProducesResponseType(StatusCodes.Status200OK)] - [ProducesResponseType(StatusCodes.Status400BadRequest)] - [ProducesResponseType(StatusCodes.Status401Unauthorized)] - [ProducesResponseType(StatusCodes.Status403Forbidden)] - public async Task> CreateUser([FromBody] CreateUserRequest body) - { - try - { - var user = await _userService.CreateUser(_mapper.Map(body)); - return Ok(ConvertToUserInfo(user)); - } - catch (EntityAlreadyExistException e) when (e.EntityName == EntityNames.User) - { - return BadRequest(ErrorResponse.UserController.UsernameConflict()); - } - } - - /// - /// Change password with old password. - /// - [HttpPost("userop/changepassword"), Authorize] - [ProducesResponseType(StatusCodes.Status200OK)] - [ProducesResponseType(StatusCodes.Status400BadRequest)] - [ProducesResponseType(StatusCodes.Status401Unauthorized)] - public async Task 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. - } - } -} -- cgit v1.2.3