From 32fdf425e6b4f4edfb727fb3c0cbebe2c87fd663 Mon Sep 17 00:00:00 2001 From: crupest Date: Thu, 20 Aug 2020 00:39:09 +0800 Subject: ... --- Timeline/Models/Http/Common.cs | 11 ++++++++ Timeline/Models/Http/TokenController.cs | 38 +++++++++++++++++++++++--- Timeline/Models/Http/UserController.cs | 47 +++++++++++++++++++++++++++++++++ 3 files changed, 92 insertions(+), 4 deletions(-) (limited to 'Timeline/Models') diff --git a/Timeline/Models/Http/Common.cs b/Timeline/Models/Http/Common.cs index a9fc8a79..5fa22c9e 100644 --- a/Timeline/Models/Http/Common.cs +++ b/Timeline/Models/Http/Common.cs @@ -71,25 +71,36 @@ namespace Timeline.Models.Http } } + /// + /// Common response for delete method. + /// public class CommonDeleteResponse : CommonDataResponse { + /// public class ResponseData { + /// public ResponseData() { } + /// public ResponseData(bool delete) { Delete = delete; } + /// + /// True if the entry is deleted. False if the entry does not exist. + /// public bool Delete { get; set; } } + /// public CommonDeleteResponse() { } + /// public CommonDeleteResponse(int code, string message, bool delete) : base(code, message, new ResponseData(delete)) { diff --git a/Timeline/Models/Http/TokenController.cs b/Timeline/Models/Http/TokenController.cs index ea8b59ed..a42c44e5 100644 --- a/Timeline/Models/Http/TokenController.cs +++ b/Timeline/Models/Http/TokenController.cs @@ -1,32 +1,62 @@ using System.ComponentModel.DataAnnotations; +using Timeline.Controllers; namespace Timeline.Models.Http { + /// + /// Request model for . + /// public class CreateTokenRequest { - [Required] + /// + /// The username. + /// public string Username { get; set; } = default!; - [Required] + /// + /// The password. + /// public string Password { get; set; } = default!; - // in days, optional + /// + /// Optional token validation period. In days. If not specified, server will use a default one. + /// [Range(1, 365)] public int? Expire { get; set; } } + /// + /// Response model for . + /// public class CreateTokenResponse { + /// + /// The token created. + /// public string Token { get; set; } = default!; + /// + /// The user owning the token. + /// public UserInfo User { get; set; } = default!; } + /// + /// Request model for . + /// public class VerifyTokenRequest { - [Required] + /// + /// The token to verify. + /// public string Token { get; set; } = default!; } + /// + /// Response model for . + /// public class VerifyTokenResponse { + /// + /// The user owning the token. + /// public UserInfo User { get; set; } = default!; } } diff --git a/Timeline/Models/Http/UserController.cs b/Timeline/Models/Http/UserController.cs index 5ee02a95..ea30a0ea 100644 --- a/Timeline/Models/Http/UserController.cs +++ b/Timeline/Models/Http/UserController.cs @@ -1,48 +1,95 @@ using AutoMapper; using System.ComponentModel.DataAnnotations; +using Timeline.Controllers; using Timeline.Models.Validation; namespace Timeline.Models.Http { + /// + /// Request model for . + /// public class UserPatchRequest { + /// + /// New username. Null if not change. Need to be administrator. + /// [Username] public string? Username { get; set; } + /// + /// New password. Null if not change. Need to be administrator. + /// [MinLength(1)] public string? Password { get; set; } + /// + /// New nickname. Null if not change. Need to be administrator to change other's. + /// [Nickname] public string? Nickname { get; set; } + /// + /// Whether to be administrator. Null if not change. Need to be administrator. + /// public bool? Administrator { get; set; } } + /// + /// Request model for . + /// public class CreateUserRequest { + /// + /// Username of the new user. + /// [Required, Username] public string Username { get; set; } = default!; + /// + /// Password of the new user. + /// [Required, MinLength(1)] public string Password { get; set; } = default!; + /// + /// Whether the new user is administrator. + /// [Required] public bool? Administrator { get; set; } + /// + /// Nickname of the new user. + /// [Nickname] public string? Nickname { get; set; } } + /// + /// Request model for . + /// public class ChangePasswordRequest { + /// + /// Old password. + /// [Required(AllowEmptyStrings = false)] public string OldPassword { get; set; } = default!; + + /// + /// New password. + /// [Required(AllowEmptyStrings = false)] public string NewPassword { get; set; } = default!; } + /// + /// + /// public class UserControllerAutoMapperProfile : Profile { + /// + /// + /// public UserControllerAutoMapperProfile() { CreateMap(MemberList.Source); -- cgit v1.2.3 From 4475de3c0c86c4096b843d8bee8aff48b7e31896 Mon Sep 17 00:00:00 2001 From: crupest Date: Fri, 21 Aug 2020 22:49:48 +0800 Subject: ... --- Timeline.Tests/IntegratedTests/UserAvatarTest.cs | 15 ++--- Timeline/Controllers/UserAvatarController.cs | 25 +++----- Timeline/Filters/Header.cs | 69 +++++++++++++-------- Timeline/Formatters/BytesInputFormatter.cs | 79 ++++++++++++++++++++++++ Timeline/Models/ByteData.cs | 33 ++++++++++ Timeline/Startup.cs | 1 + 6 files changed, 167 insertions(+), 55 deletions(-) create mode 100644 Timeline/Formatters/BytesInputFormatter.cs create mode 100644 Timeline/Models/ByteData.cs (limited to 'Timeline/Models') diff --git a/Timeline.Tests/IntegratedTests/UserAvatarTest.cs b/Timeline.Tests/IntegratedTests/UserAvatarTest.cs index 91986cda..507b05ba 100644 --- a/Timeline.Tests/IntegratedTests/UserAvatarTest.cs +++ b/Timeline.Tests/IntegratedTests/UserAvatarTest.cs @@ -66,16 +66,14 @@ namespace Timeline.Tests.IntegratedTests using var content = new ByteArrayContent(new[] { (byte)0x00 }); content.Headers.ContentType = new MediaTypeHeaderValue("image/png"); var res = await client.PutAsync("users/user1/avatar", content); - res.Should().HaveStatusCode(HttpStatusCode.BadRequest) - .And.HaveCommonBody().Which.Code.Should().Be(ErrorCodes.Common.Header.ContentLength_Missing); ; + res.Should().BeInvalidModel(); } { using var content = new ByteArrayContent(new[] { (byte)0x00 }); content.Headers.ContentLength = 1; var res = await client.PutAsync("users/user1/avatar", content); - res.Should().HaveStatusCode(HttpStatusCode.BadRequest) - .And.HaveCommonBody().Which.Code.Should().Be(ErrorCodes.Common.Header.ContentType_Missing); + res.Should().BeInvalidModel(); } { @@ -83,8 +81,7 @@ namespace Timeline.Tests.IntegratedTests content.Headers.ContentLength = 0; content.Headers.ContentType = new MediaTypeHeaderValue("image/png"); var res = await client.PutAsync("users/user1/avatar", content); - res.Should().HaveStatusCode(HttpStatusCode.BadRequest) - .And.HaveCommonBody().Which.Code.Should().Be(ErrorCodes.Common.Header.ContentLength_Zero); + res.Should().BeInvalidModel(); } { @@ -106,8 +103,7 @@ namespace Timeline.Tests.IntegratedTests content.Headers.ContentLength = 2; content.Headers.ContentType = new MediaTypeHeaderValue("image/png"); var res = await client.PutAsync("users/user1/avatar", content); - res.Should().HaveStatusCode(HttpStatusCode.BadRequest) - .And.HaveCommonBody().Which.Code.Should().Be(ErrorCodes.Common.Content.UnmatchedLength_Smaller); + res.Should().BeInvalidModel(); } { @@ -115,8 +111,7 @@ namespace Timeline.Tests.IntegratedTests content.Headers.ContentLength = 1; content.Headers.ContentType = new MediaTypeHeaderValue("image/png"); var res = await client.PutAsync("users/user1/avatar", content); - res.Should().HaveStatusCode(HttpStatusCode.BadRequest) - .And.HaveCommonBody().Which.Code.Should().Be(ErrorCodes.Common.Content.UnmatchedLength_Bigger); + res.Should().BeInvalidModel(); } { diff --git a/Timeline/Controllers/UserAvatarController.cs b/Timeline/Controllers/UserAvatarController.cs index 52e87df2..32f63fc6 100644 --- a/Timeline/Controllers/UserAvatarController.cs +++ b/Timeline/Controllers/UserAvatarController.cs @@ -3,10 +3,12 @@ using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Logging; using System; +using System.IO; 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; @@ -72,20 +74,17 @@ namespace Timeline.Controllers /// 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] - [RequireContentType, RequireContentLength] [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) + public async Task Put([FromRoute][Username] string username, [FromBody] ByteData body) { - var contentLength = Request.ContentLength!.Value; - if (contentLength > 1000 * 1000 * 10) - return BadRequest(ErrorResponse.Common.Content.TooBig("10MB")); - if (!User.IsAdministrator() && User.Identity.Name != username) { _logger.LogInformation(Log.Format(LogPutForbid, @@ -106,20 +105,10 @@ namespace Timeline.Controllers try { - var data = new byte[contentLength]; - var bytesRead = await Request.Body.ReadAsync(data); - - if (bytesRead != contentLength) - return BadRequest(ErrorResponse.Common.Content.UnmatchedLength_Smaller()); - - var extraByte = new byte[1]; - if (await Request.Body.ReadAsync(extraByte) != 0) - return BadRequest(ErrorResponse.Common.Content.UnmatchedLength_Bigger()); - await _service.SetAvatar(id, new Avatar { - Data = data, - Type = Request.ContentType + Data = body.Data, + Type = body.ContentType }); _logger.LogInformation(Log.Format(LogPutSuccess, diff --git a/Timeline/Filters/Header.cs b/Timeline/Filters/Header.cs index 0db11faf..cc5ddd9f 100644 --- a/Timeline/Filters/Header.cs +++ b/Timeline/Filters/Header.cs @@ -4,45 +4,60 @@ using Timeline.Models.Http; namespace Timeline.Filters { - public class RequireContentTypeAttribute : ActionFilterAttribute + /// + /// Restrict max content length. + /// + public class MaxContentLengthFilter : IResourceFilter { - public override void OnActionExecuting(ActionExecutingContext context) + /// + /// + /// + /// Max length. + public MaxContentLengthFilter(long maxByteLength) { - if (context.HttpContext.Request.ContentType == null) - { - context.Result = new BadRequestObjectResult(ErrorResponse.Common.Header.ContentType_Missing()); - } + MaxByteLength = maxByteLength; } - } - - public class RequireContentLengthAttribute : ActionFilterAttribute - { - public RequireContentLengthAttribute() - : this(true) - { - } + /// + /// Max length. + /// + public long MaxByteLength { get; set; } - public RequireContentLengthAttribute(bool requireNonZero) + /// + public void OnResourceExecuted(ResourceExecutedContext context) { - RequireNonZero = requireNonZero; } - public bool RequireNonZero { get; set; } - - public override void OnActionExecuting(ActionExecutingContext context) + /// + public void OnResourceExecuting(ResourceExecutingContext context) { - if (context.HttpContext.Request.ContentLength == null) + var contentLength = context.HttpContext.Request.ContentLength; + if (contentLength != null && contentLength > MaxByteLength) { - context.Result = new BadRequestObjectResult(ErrorResponse.Common.Header.ContentLength_Missing()); - return; + context.Result = new BadRequestObjectResult(ErrorResponse.Common.Content.TooBig(MaxByteLength + "B")); } + } + } - if (RequireNonZero && context.HttpContext.Request.ContentLength.Value == 0) - { - context.Result = new BadRequestObjectResult(ErrorResponse.Common.Header.ContentLength_Zero()); - return; - } + /// + /// Restrict max content length. + /// + public class MaxContentLengthAttribute : TypeFilterAttribute + { + /// + /// + /// + /// Max length. + public MaxContentLengthAttribute(long maxByteLength) + : base(typeof(MaxContentLengthFilter)) + { + MaxByteLength = maxByteLength; + Arguments = new object[] { maxByteLength }; } + + /// + /// Max length. + /// + public long MaxByteLength { get; } } } diff --git a/Timeline/Formatters/BytesInputFormatter.cs b/Timeline/Formatters/BytesInputFormatter.cs new file mode 100644 index 00000000..ac6537c9 --- /dev/null +++ b/Timeline/Formatters/BytesInputFormatter.cs @@ -0,0 +1,79 @@ +using Microsoft.AspNetCore.Mvc.Formatters; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +using Microsoft.Net.Http.Headers; +using System; +using System.Threading.Tasks; +using Timeline.Models; + +namespace Timeline.Formatters +{ + /// + /// Formatter that reads body as bytes. + /// + public class BytesInputFormatter : InputFormatter + { + /// + /// + /// + public BytesInputFormatter() + { + SupportedMediaTypes.Add(new MediaTypeHeaderValue("image/png")); + SupportedMediaTypes.Add(new MediaTypeHeaderValue("image/jpeg")); + SupportedMediaTypes.Add(new MediaTypeHeaderValue("image/gif")); + SupportedMediaTypes.Add(new MediaTypeHeaderValue("image/webp")); + } + + /// + public override bool CanRead(InputFormatterContext context) + { + if (context == null) throw new ArgumentNullException(nameof(context)); + + if (context.ModelType == typeof(ByteData)) + return true; + + return false; + } + + /// + public override async Task ReadRequestBodyAsync(InputFormatterContext context) + { + var request = context.HttpContext.Request; + var contentLength = request.ContentLength; + + var logger = context.HttpContext.RequestServices.GetRequiredService>(); + + if (contentLength == null) + { + logger.LogInformation("Failed to read body as bytes. Content-Length is not set."); + return await InputFormatterResult.FailureAsync(); + } + + if (contentLength == 0) + { + logger.LogInformation("Failed to read body as bytes. Content-Length is 0."); + return await InputFormatterResult.FailureAsync(); + } + + var bodyStream = request.Body; + + var data = new byte[contentLength.Value]; + var bytesRead = await bodyStream.ReadAsync(data); + + if (bytesRead != contentLength) + { + logger.LogInformation("Failed to read body as bytes. Actual length of body is smaller than Content-Length."); + return await InputFormatterResult.FailureAsync(); + } + + var extraByte = new byte[1]; + if (await bodyStream.ReadAsync(extraByte) != 0) + { + logger.LogInformation("Failed to read body as bytes. Actual length of body is greater than Content-Length."); + return await InputFormatterResult.FailureAsync(); + } + + return await InputFormatterResult.SuccessAsync(new ByteData(data, request.ContentType)); + } + } +} diff --git a/Timeline/Models/ByteData.cs b/Timeline/Models/ByteData.cs new file mode 100644 index 00000000..7b832eb5 --- /dev/null +++ b/Timeline/Models/ByteData.cs @@ -0,0 +1,33 @@ +using NSwag.Annotations; + +namespace Timeline.Models +{ + /// + /// Model for reading http body as bytes. + /// + [OpenApiFile] + public class ByteData + { + /// + /// + /// The data. + /// The content type. + public ByteData(byte[] data, string contentType) + { + Data = data; + ContentType = contentType; + } + + /// + /// Data. + /// +#pragma warning disable CA1819 // Properties should not return arrays + public byte[] Data { get; } +#pragma warning restore CA1819 // Properties should not return arrays + + /// + /// Content type. + /// + public string ContentType { get; } + } +} diff --git a/Timeline/Startup.cs b/Timeline/Startup.cs index 960bbc2c..408057e5 100644 --- a/Timeline/Startup.cs +++ b/Timeline/Startup.cs @@ -49,6 +49,7 @@ namespace Timeline services.AddControllers(setup => { setup.InputFormatters.Add(new StringInputFormatter()); + setup.InputFormatters.Add(new BytesInputFormatter()); setup.UseApiRoutePrefix("api"); }) .AddJsonOptions(options => -- cgit v1.2.3 From a8ca52b2ec6009c1be036e74edb76161447371b8 Mon Sep 17 00:00:00 2001 From: crupest Date: Fri, 21 Aug 2020 22:52:20 +0800 Subject: ... --- Timeline.ErrorCodes/ErrorCodes.cs | 5 ---- Timeline/Models/Http/ErrorResponse.cs | 50 ----------------------------------- 2 files changed, 55 deletions(-) (limited to 'Timeline/Models') diff --git a/Timeline.ErrorCodes/ErrorCodes.cs b/Timeline.ErrorCodes/ErrorCodes.cs index 4637242a..91e0c1fd 100644 --- a/Timeline.ErrorCodes/ErrorCodes.cs +++ b/Timeline.ErrorCodes/ErrorCodes.cs @@ -17,16 +17,11 @@ public static class Header { public const int IfNonMatch_BadFormat = 1_000_01_01; - public const int ContentType_Missing = 1_000_02_01; - public const int ContentLength_Missing = 1_000_03_01; - public const int ContentLength_Zero = 1_000_03_02; } public static class Content { public const int TooBig = 1_000_11_01; - public const int UnmatchedLength_Smaller = 1_000_11_02; - public const int UnmatchedLength_Bigger = 1_000_11_03; } } diff --git a/Timeline/Models/Http/ErrorResponse.cs b/Timeline/Models/Http/ErrorResponse.cs index 9a4d190a..7ba536f9 100644 --- a/Timeline/Models/Http/ErrorResponse.cs +++ b/Timeline/Models/Http/ErrorResponse.cs @@ -53,36 +53,6 @@ namespace Timeline.Models.Http return new CommonResponse(ErrorCodes.Common.Header.IfNonMatch_BadFormat, string.Format(message, formatArgs)); } - public static CommonResponse ContentType_Missing(params object?[] formatArgs) - { - return new CommonResponse(ErrorCodes.Common.Header.ContentType_Missing, string.Format(Common_Header_ContentType_Missing, formatArgs)); - } - - public static CommonResponse CustomMessage_ContentType_Missing(string message, params object?[] formatArgs) - { - return new CommonResponse(ErrorCodes.Common.Header.ContentType_Missing, string.Format(message, formatArgs)); - } - - public static CommonResponse ContentLength_Missing(params object?[] formatArgs) - { - return new CommonResponse(ErrorCodes.Common.Header.ContentLength_Missing, string.Format(Common_Header_ContentLength_Missing, formatArgs)); - } - - public static CommonResponse CustomMessage_ContentLength_Missing(string message, params object?[] formatArgs) - { - return new CommonResponse(ErrorCodes.Common.Header.ContentLength_Missing, string.Format(message, formatArgs)); - } - - public static CommonResponse ContentLength_Zero(params object?[] formatArgs) - { - return new CommonResponse(ErrorCodes.Common.Header.ContentLength_Zero, string.Format(Common_Header_ContentLength_Zero, formatArgs)); - } - - public static CommonResponse CustomMessage_ContentLength_Zero(string message, params object?[] formatArgs) - { - return new CommonResponse(ErrorCodes.Common.Header.ContentLength_Zero, string.Format(message, formatArgs)); - } - } public static class Content @@ -98,26 +68,6 @@ namespace Timeline.Models.Http return new CommonResponse(ErrorCodes.Common.Content.TooBig, string.Format(message, formatArgs)); } - public static CommonResponse UnmatchedLength_Smaller(params object?[] formatArgs) - { - return new CommonResponse(ErrorCodes.Common.Content.UnmatchedLength_Smaller, string.Format(Common_Content_UnmatchedLength_Smaller, formatArgs)); - } - - public static CommonResponse CustomMessage_UnmatchedLength_Smaller(string message, params object?[] formatArgs) - { - return new CommonResponse(ErrorCodes.Common.Content.UnmatchedLength_Smaller, string.Format(message, formatArgs)); - } - - public static CommonResponse UnmatchedLength_Bigger(params object?[] formatArgs) - { - return new CommonResponse(ErrorCodes.Common.Content.UnmatchedLength_Bigger, string.Format(Common_Content_UnmatchedLength_Bigger, formatArgs)); - } - - public static CommonResponse CustomMessage_UnmatchedLength_Bigger(string message, params object?[] formatArgs) - { - return new CommonResponse(ErrorCodes.Common.Content.UnmatchedLength_Bigger, string.Format(message, formatArgs)); - } - } } -- cgit v1.2.3 From c79352d27df2506d2ab214f61d60e503a1d7675a Mon Sep 17 00:00:00 2001 From: crupest Date: Fri, 21 Aug 2020 23:32:53 +0800 Subject: ... --- Timeline.Tests/Helpers/TestClock.cs | 2 +- Timeline.Tests/IntegratedTests/TimelineTest.cs | 8 ++++---- Timeline/Models/Validation/Validator.cs | 4 ++-- Timeline/Services/ETagGenerator.cs | 2 +- Timeline/Services/TimelineService.cs | 2 ++ Timeline/Services/UserService.cs | 5 ++--- Timeline/Services/UserTokenException.cs | 4 ++-- Timeline/Timeline.csproj | 2 ++ 8 files changed, 16 insertions(+), 13 deletions(-) (limited to 'Timeline/Models') diff --git a/Timeline.Tests/Helpers/TestClock.cs b/Timeline.Tests/Helpers/TestClock.cs index ed2d65a6..34adb245 100644 --- a/Timeline.Tests/Helpers/TestClock.cs +++ b/Timeline.Tests/Helpers/TestClock.cs @@ -8,7 +8,7 @@ namespace Timeline.Tests.Helpers { public class TestClock : IClock { - private DateTime? _currentTime = null; + private DateTime? _currentTime; public DateTime GetCurrentTime() { diff --git a/Timeline.Tests/IntegratedTests/TimelineTest.cs b/Timeline.Tests/IntegratedTests/TimelineTest.cs index 16b3c7e4..3b4b1754 100644 --- a/Timeline.Tests/IntegratedTests/TimelineTest.cs +++ b/Timeline.Tests/IntegratedTests/TimelineTest.cs @@ -76,20 +76,20 @@ namespace Timeline.Tests.IntegratedTests if (subpath != null) { if (!subpath.StartsWith("/", StringComparison.OrdinalIgnoreCase)) - result.Append("/"); + result.Append('/'); result.Append(subpath); } if (query != null && query.Count != 0) { - result.Append("?"); + result.Append('?'); foreach (var (key, value, index) in query.Select((pair, index) => (pair.Key, pair.Value, index))) { result.Append(WebUtility.UrlEncode(key)); - result.Append("="); + result.Append('='); result.Append(WebUtility.UrlEncode(value)); if (index != query.Count - 1) - result.Append("&"); + result.Append('&'); } } diff --git a/Timeline/Models/Validation/Validator.cs b/Timeline/Models/Validation/Validator.cs index db139448..aef7891c 100644 --- a/Timeline/Models/Validation/Validator.cs +++ b/Timeline/Models/Validation/Validator.cs @@ -35,11 +35,11 @@ namespace Timeline.Models.Validation /// /// The type of accepted value. /// - /// Subclass should override to do the real validation. + /// Subclass should override to do the real validation. /// This class will check the nullity and type of value. /// If value is null, it will pass or fail depending on . /// If value is not null and not of type - /// it will fail and not call . + /// it will fail and not call . /// /// is true by default. /// diff --git a/Timeline/Services/ETagGenerator.cs b/Timeline/Services/ETagGenerator.cs index d328ea20..4493e903 100644 --- a/Timeline/Services/ETagGenerator.cs +++ b/Timeline/Services/ETagGenerator.cs @@ -33,7 +33,7 @@ namespace Timeline.Services return Task.Run(() => Convert.ToBase64String(_sha1.ComputeHash(source))); } - private bool _disposed = false; // To detect redundant calls + private bool _disposed; // To detect redundant calls public void Dispose() { diff --git a/Timeline/Services/TimelineService.cs b/Timeline/Services/TimelineService.cs index 0070fe3e..01f7f5fd 100644 --- a/Timeline/Services/TimelineService.cs +++ b/Timeline/Services/TimelineService.cs @@ -260,6 +260,7 @@ namespace Timeline.Services /// Thrown when timeline with name does not exist. /// If it is a personal timeline, then inner exception is . /// + /// /// This method does not check whether visitor is administrator. /// Return false if user with user id does not exist. /// @@ -277,6 +278,7 @@ namespace Timeline.Services /// Thrown when timeline with name does not exist. /// If it is a personal timeline, then inner exception is . /// + /// /// This method does not check whether visitor is administrator. /// Return false if user with visitor id does not exist. /// diff --git a/Timeline/Services/UserService.cs b/Timeline/Services/UserService.cs index d9b3da26..821bc33d 100644 --- a/Timeline/Services/UserService.cs +++ b/Timeline/Services/UserService.cs @@ -65,11 +65,10 @@ namespace Timeline.Services /// Create a user with given info. /// /// The info of new user. - /// The password, can't be null or empty. /// The the new user. /// Thrown when is null. /// Thrown when some fields in is bad. - /// Thrown when a user with given username already exists. + /// Thrown when a user with given username already exists. /// /// must not be null and must be a valid username. /// must not be null or empty. @@ -110,7 +109,7 @@ namespace Timeline.Services /// Thrown when is null. /// Thrown when is of bad format or some fields in is bad. /// Thrown when user with given id does not exist. - /// Thrown when user with the newusername already exist. + /// Thrown when user with the newusername already exist. /// /// Only , and will be used. /// If null, then not change. diff --git a/Timeline/Services/UserTokenException.cs b/Timeline/Services/UserTokenException.cs index ed0bae1a..d25fabb3 100644 --- a/Timeline/Services/UserTokenException.cs +++ b/Timeline/Services/UserTokenException.cs @@ -31,9 +31,9 @@ namespace Timeline.Services System.Runtime.Serialization.SerializationInfo info, System.Runtime.Serialization.StreamingContext context) : base(info, context) { } - public DateTime ExpireTime { get; private set; } = default; + public DateTime ExpireTime { get; private set; } - public DateTime VerifyTime { get; private set; } = default; + public DateTime VerifyTime { get; private set; } } [Serializable] diff --git a/Timeline/Timeline.csproj b/Timeline/Timeline.csproj index 5fc69fc8..5ceb97e6 100644 --- a/Timeline/Timeline.csproj +++ b/Timeline/Timeline.csproj @@ -18,6 +18,8 @@ 0.3.0 true true + + 1701;1702;1591 -- cgit v1.2.3 From f1c70edd559c72dcb47ff647f3f03ba5ae9a56cc Mon Sep 17 00:00:00 2001 From: crupest Date: Fri, 21 Aug 2020 23:44:53 +0800 Subject: ... --- Timeline/Models/Http/Timeline.cs | 75 ++++++++++++++++++++++++++++++ Timeline/Models/Http/TimelineController.cs | 33 +++++++++++++ Timeline/Models/Http/UserController.cs | 6 --- Timeline/Models/Http/UserInfo.cs | 31 +++++++++++- 4 files changed, 138 insertions(+), 7 deletions(-) (limited to 'Timeline/Models') diff --git a/Timeline/Models/Http/Timeline.cs b/Timeline/Models/Http/Timeline.cs index 52e26190..6498fa74 100644 --- a/Timeline/Models/Http/Timeline.cs +++ b/Timeline/Models/Http/Timeline.cs @@ -8,45 +8,120 @@ using Timeline.Controllers; namespace Timeline.Models.Http { + /// + /// Info of post content. + /// public class TimelinePostContentInfo { + /// + /// Type of the post content. + /// public string Type { get; set; } = default!; + /// + /// If post is of text type. This is the text. + /// public string? Text { get; set; } + /// + /// If post is of image type. This is the image url. + /// public string? Url { get; set; } } + /// + /// Info of a post. + /// public class TimelinePostInfo { + /// + /// Post id. + /// public long Id { get; set; } + /// + /// Content of the post. May be null if post is deleted. + /// public TimelinePostContentInfo? Content { get; set; } + /// + /// True if post is deleted. + /// public bool Deleted { get; set; } + /// + /// Post time. + /// public DateTime Time { get; set; } + /// + /// The author. May be null if the user has been deleted. + /// public UserInfo? Author { get; set; } = default!; + /// + /// Last updated time. + /// public DateTime LastUpdated { get; set; } = default!; } + /// + /// Info of a timeline. + /// public class TimelineInfo { + /// + /// Unique id. + /// public string UniqueId { get; set; } = default!; + /// + /// Name of timeline. + /// public string Name { get; set; } = default!; + /// + /// Last modified time of timeline name. + /// public DateTime NameLastModifed { get; set; } = default!; + /// + /// Timeline description. + /// public string Description { get; set; } = default!; + /// + /// Owner of the timeline. + /// public UserInfo Owner { get; set; } = default!; + /// + /// Visibility of the timeline. + /// public TimelineVisibility Visibility { get; set; } #pragma warning disable CA2227 // Collection properties should be read only + /// + /// Members of timeline. + /// public List Members { get; set; } = default!; #pragma warning restore CA2227 // Collection properties should be read only + /// + /// Create time of timeline. + /// public DateTime CreateTime { get; set; } = default!; + /// + /// Last modified time of timeline. + /// public DateTime LastModified { get; set; } = default!; #pragma warning disable CA1707 // Identifiers should not contain underscores + /// + /// Related links. + /// public TimelineInfoLinks _links { get; set; } = default!; #pragma warning restore CA1707 // Identifiers should not contain underscores } + /// + /// Related links for timeline. + /// public class TimelineInfoLinks { + /// + /// Self. + /// public string Self { get; set; } = default!; + /// + /// Posts url. + /// public string Posts { get; set; } = default!; } diff --git a/Timeline/Models/Http/TimelineController.cs b/Timeline/Models/Http/TimelineController.cs index 3e2e6b58..aad361ee 100644 --- a/Timeline/Models/Http/TimelineController.cs +++ b/Timeline/Models/Http/TimelineController.cs @@ -4,33 +4,66 @@ using Timeline.Models.Validation; namespace Timeline.Models.Http { + /// + /// Content of post create request. + /// public class TimelinePostCreateRequestContent { + /// + /// Type of post content. + /// [Required] public string Type { get; set; } = default!; + /// + /// If post is of text type, this is the text. + /// public string? Text { get; set; } + /// + /// If post is of image type, this is base64 of image data. + /// public string? Data { get; set; } } public class TimelinePostCreateRequest { + /// + /// Content of the new post. + /// [Required] public TimelinePostCreateRequestContent Content { get; set; } = default!; + /// + /// Time of the post. If not set, current time will be used. + /// public DateTime? Time { get; set; } } + /// + /// Create timeline request model. + /// public class TimelineCreateRequest { + /// + /// Name of the new timeline. Must be a valid name. + /// [Required] [TimelineName] public string Name { get; set; } = default!; } + /// + /// Patch timeline request model. + /// public class TimelinePatchRequest { + /// + /// New description. Null for not change. + /// public string? Description { get; set; } + /// + /// New visibility. Null for not change. + /// public TimelineVisibility? Visibility { get; set; } } } diff --git a/Timeline/Models/Http/UserController.cs b/Timeline/Models/Http/UserController.cs index ea30a0ea..6bc5a66e 100644 --- a/Timeline/Models/Http/UserController.cs +++ b/Timeline/Models/Http/UserController.cs @@ -82,14 +82,8 @@ namespace Timeline.Models.Http public string NewPassword { get; set; } = default!; } - /// - /// - /// public class UserControllerAutoMapperProfile : Profile { - /// - /// - /// public UserControllerAutoMapperProfile() { CreateMap(MemberList.Source); diff --git a/Timeline/Models/Http/UserInfo.cs b/Timeline/Models/Http/UserInfo.cs index c9a26072..d92a12c4 100644 --- a/Timeline/Models/Http/UserInfo.cs +++ b/Timeline/Models/Http/UserInfo.cs @@ -2,26 +2,55 @@ using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.Infrastructure; using Microsoft.AspNetCore.Mvc.Routing; -using System; using Timeline.Controllers; namespace Timeline.Models.Http { + /// + /// Info of a user. + /// public class UserInfo { + /// + /// Unique id. + /// public string UniqueId { get; set; } = default!; + /// + /// Username. + /// public string Username { get; set; } = default!; + /// + /// Nickname. + /// public string Nickname { get; set; } = default!; + /// + /// True if the user is a administrator. + /// public bool? Administrator { get; set; } = default!; #pragma warning disable CA1707 // Identifiers should not contain underscores + /// + /// Related links. + /// public UserInfoLinks _links { get; set; } = default!; #pragma warning restore CA1707 // Identifiers should not contain underscores } + /// + /// Related links for user. + /// public class UserInfoLinks { + /// + /// Self. + /// public string Self { get; set; } = default!; + /// + /// Avatar url. + /// public string Avatar { get; set; } = default!; + /// + /// Personal timeline url. + /// public string Timeline { get; set; } = default!; } -- cgit v1.2.3