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. --- .../Models/Http/ActionContextAccessorExtensions.cs | 14 ++ BackEnd/Timeline/Models/Http/Common.cs | 120 ++++++++++ BackEnd/Timeline/Models/Http/ErrorResponse.cs | 261 +++++++++++++++++++++ BackEnd/Timeline/Models/Http/Timeline.cs | 219 +++++++++++++++++ BackEnd/Timeline/Models/Http/TimelineController.cs | 93 ++++++++ BackEnd/Timeline/Models/Http/TokenController.cs | 62 +++++ BackEnd/Timeline/Models/Http/UserController.cs | 93 ++++++++ BackEnd/Timeline/Models/Http/UserInfo.cs | 90 +++++++ 8 files changed, 952 insertions(+) create mode 100644 BackEnd/Timeline/Models/Http/ActionContextAccessorExtensions.cs create mode 100644 BackEnd/Timeline/Models/Http/Common.cs create mode 100644 BackEnd/Timeline/Models/Http/ErrorResponse.cs create mode 100644 BackEnd/Timeline/Models/Http/Timeline.cs create mode 100644 BackEnd/Timeline/Models/Http/TimelineController.cs create mode 100644 BackEnd/Timeline/Models/Http/TokenController.cs create mode 100644 BackEnd/Timeline/Models/Http/UserController.cs create mode 100644 BackEnd/Timeline/Models/Http/UserInfo.cs (limited to 'BackEnd/Timeline/Models/Http') diff --git a/BackEnd/Timeline/Models/Http/ActionContextAccessorExtensions.cs b/BackEnd/Timeline/Models/Http/ActionContextAccessorExtensions.cs new file mode 100644 index 00000000..bcc55c5a --- /dev/null +++ b/BackEnd/Timeline/Models/Http/ActionContextAccessorExtensions.cs @@ -0,0 +1,14 @@ +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.Infrastructure; +using System; + +namespace Timeline.Models.Http +{ + public static class ActionContextAccessorExtensions + { + public static ActionContext AssertActionContextForUrlFill(this IActionContextAccessor accessor) + { + return accessor.ActionContext ?? throw new InvalidOperationException(Resources.Models.Http.Exception.ActionContextNull); + } + } +} diff --git a/BackEnd/Timeline/Models/Http/Common.cs b/BackEnd/Timeline/Models/Http/Common.cs new file mode 100644 index 00000000..5fa22c9e --- /dev/null +++ b/BackEnd/Timeline/Models/Http/Common.cs @@ -0,0 +1,120 @@ +using static Timeline.Resources.Models.Http.Common; + +namespace Timeline.Models.Http +{ + public class CommonResponse + { + public CommonResponse() + { + + } + + public CommonResponse(int code, string message) + { + Code = code; + Message = message; + } + + public int Code { get; set; } + public string? Message { get; set; } + } + + public class CommonDataResponse : CommonResponse + { + public CommonDataResponse() + { + + } + + public CommonDataResponse(int code, string message, T data) + : base(code, message) + { + Data = data; + } + + public T Data { get; set; } = default!; + } + + public class CommonPutResponse : CommonDataResponse + { + public class ResponseData + { + public ResponseData() { } + + public ResponseData(bool create) + { + Create = create; + } + + public bool Create { get; set; } + } + + public CommonPutResponse() + { + + } + + public CommonPutResponse(int code, string message, bool create) + : base(code, message, new ResponseData(create)) + { + + } + + internal static CommonPutResponse Create() + { + return new CommonPutResponse(0, MessagePutCreate, true); + } + + internal static CommonPutResponse Modify() + { + return new CommonPutResponse(0, MessagePutModify, false); + } + } + + /// + /// 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)) + { + + } + + internal static CommonDeleteResponse Delete() + { + return new CommonDeleteResponse(0, MessageDeleteDelete, true); + } + + internal static CommonDeleteResponse NotExist() + { + return new CommonDeleteResponse(0, MessageDeleteNotExist, false); + } + } +} diff --git a/BackEnd/Timeline/Models/Http/ErrorResponse.cs b/BackEnd/Timeline/Models/Http/ErrorResponse.cs new file mode 100644 index 00000000..ac86481f --- /dev/null +++ b/BackEnd/Timeline/Models/Http/ErrorResponse.cs @@ -0,0 +1,261 @@ +using static Timeline.Resources.Messages; + +namespace Timeline.Models.Http +{ + public static class ErrorResponse + { + public static class Common + { + public static CommonResponse InvalidModel(params object?[] formatArgs) + { + return new CommonResponse(ErrorCodes.Common.InvalidModel, string.Format(Common_InvalidModel, formatArgs)); + } + + public static CommonResponse CustomMessage_InvalidModel(string message, params object?[] formatArgs) + { + return new CommonResponse(ErrorCodes.Common.InvalidModel, string.Format(message, formatArgs)); + } + + public static CommonResponse Forbid(params object?[] formatArgs) + { + return new CommonResponse(ErrorCodes.Common.Forbid, string.Format(Common_Forbid, formatArgs)); + } + + public static CommonResponse CustomMessage_Forbid(string message, params object?[] formatArgs) + { + return new CommonResponse(ErrorCodes.Common.Forbid, string.Format(message, formatArgs)); + } + + public static CommonResponse UnknownEndpoint(params object?[] formatArgs) + { + return new CommonResponse(ErrorCodes.Common.UnknownEndpoint, string.Format(Common_UnknownEndpoint, formatArgs)); + } + + public static CommonResponse CustomMessage_UnknownEndpoint(string message, params object?[] formatArgs) + { + return new CommonResponse(ErrorCodes.Common.UnknownEndpoint, string.Format(message, formatArgs)); + } + + public static class Header + { + public static CommonResponse IfNonMatch_BadFormat(params object?[] formatArgs) + { + return new CommonResponse(ErrorCodes.Common.Header.IfNonMatch_BadFormat, string.Format(Common_Header_IfNonMatch_BadFormat, formatArgs)); + } + + public static CommonResponse CustomMessage_IfNonMatch_BadFormat(string message, params object?[] formatArgs) + { + return new CommonResponse(ErrorCodes.Common.Header.IfNonMatch_BadFormat, string.Format(message, formatArgs)); + } + + } + + public static class Content + { + public static CommonResponse TooBig(params object?[] formatArgs) + { + return new CommonResponse(ErrorCodes.Common.Content.TooBig, string.Format(Common_Content_TooBig, formatArgs)); + } + + public static CommonResponse CustomMessage_TooBig(string message, params object?[] formatArgs) + { + return new CommonResponse(ErrorCodes.Common.Content.TooBig, string.Format(message, formatArgs)); + } + + } + + } + + public static class UserCommon + { + public static CommonResponse NotExist(params object?[] formatArgs) + { + return new CommonResponse(ErrorCodes.UserCommon.NotExist, string.Format(UserCommon_NotExist, formatArgs)); + } + + public static CommonResponse CustomMessage_NotExist(string message, params object?[] formatArgs) + { + return new CommonResponse(ErrorCodes.UserCommon.NotExist, string.Format(message, formatArgs)); + } + + } + + public static class TokenController + { + public static CommonResponse Create_BadCredential(params object?[] formatArgs) + { + return new CommonResponse(ErrorCodes.TokenController.Create_BadCredential, string.Format(TokenController_Create_BadCredential, formatArgs)); + } + + public static CommonResponse CustomMessage_Create_BadCredential(string message, params object?[] formatArgs) + { + return new CommonResponse(ErrorCodes.TokenController.Create_BadCredential, string.Format(message, formatArgs)); + } + + public static CommonResponse Verify_BadFormat(params object?[] formatArgs) + { + return new CommonResponse(ErrorCodes.TokenController.Verify_BadFormat, string.Format(TokenController_Verify_BadFormat, formatArgs)); + } + + public static CommonResponse CustomMessage_Verify_BadFormat(string message, params object?[] formatArgs) + { + return new CommonResponse(ErrorCodes.TokenController.Verify_BadFormat, string.Format(message, formatArgs)); + } + + public static CommonResponse Verify_UserNotExist(params object?[] formatArgs) + { + return new CommonResponse(ErrorCodes.TokenController.Verify_UserNotExist, string.Format(TokenController_Verify_UserNotExist, formatArgs)); + } + + public static CommonResponse CustomMessage_Verify_UserNotExist(string message, params object?[] formatArgs) + { + return new CommonResponse(ErrorCodes.TokenController.Verify_UserNotExist, string.Format(message, formatArgs)); + } + + public static CommonResponse Verify_OldVersion(params object?[] formatArgs) + { + return new CommonResponse(ErrorCodes.TokenController.Verify_OldVersion, string.Format(TokenController_Verify_OldVersion, formatArgs)); + } + + public static CommonResponse CustomMessage_Verify_OldVersion(string message, params object?[] formatArgs) + { + return new CommonResponse(ErrorCodes.TokenController.Verify_OldVersion, string.Format(message, formatArgs)); + } + + public static CommonResponse Verify_TimeExpired(params object?[] formatArgs) + { + return new CommonResponse(ErrorCodes.TokenController.Verify_TimeExpired, string.Format(TokenController_Verify_TimeExpired, formatArgs)); + } + + public static CommonResponse CustomMessage_Verify_TimeExpired(string message, params object?[] formatArgs) + { + return new CommonResponse(ErrorCodes.TokenController.Verify_TimeExpired, string.Format(message, formatArgs)); + } + + } + + public static class UserController + { + public static CommonResponse UsernameConflict(params object?[] formatArgs) + { + return new CommonResponse(ErrorCodes.UserController.UsernameConflict, string.Format(UserController_UsernameConflict, formatArgs)); + } + + public static CommonResponse CustomMessage_UsernameConflict(string message, params object?[] formatArgs) + { + return new CommonResponse(ErrorCodes.UserController.UsernameConflict, string.Format(message, formatArgs)); + } + + public static CommonResponse ChangePassword_BadOldPassword(params object?[] formatArgs) + { + return new CommonResponse(ErrorCodes.UserController.ChangePassword_BadOldPassword, string.Format(UserController_ChangePassword_BadOldPassword, formatArgs)); + } + + public static CommonResponse CustomMessage_ChangePassword_BadOldPassword(string message, params object?[] formatArgs) + { + return new CommonResponse(ErrorCodes.UserController.ChangePassword_BadOldPassword, string.Format(message, formatArgs)); + } + + } + + public static class UserAvatar + { + public static CommonResponse BadFormat_CantDecode(params object?[] formatArgs) + { + return new CommonResponse(ErrorCodes.UserAvatar.BadFormat_CantDecode, string.Format(UserAvatar_BadFormat_CantDecode, formatArgs)); + } + + public static CommonResponse CustomMessage_BadFormat_CantDecode(string message, params object?[] formatArgs) + { + return new CommonResponse(ErrorCodes.UserAvatar.BadFormat_CantDecode, string.Format(message, formatArgs)); + } + + public static CommonResponse BadFormat_UnmatchedFormat(params object?[] formatArgs) + { + return new CommonResponse(ErrorCodes.UserAvatar.BadFormat_UnmatchedFormat, string.Format(UserAvatar_BadFormat_UnmatchedFormat, formatArgs)); + } + + public static CommonResponse CustomMessage_BadFormat_UnmatchedFormat(string message, params object?[] formatArgs) + { + return new CommonResponse(ErrorCodes.UserAvatar.BadFormat_UnmatchedFormat, string.Format(message, formatArgs)); + } + + public static CommonResponse BadFormat_BadSize(params object?[] formatArgs) + { + return new CommonResponse(ErrorCodes.UserAvatar.BadFormat_BadSize, string.Format(UserAvatar_BadFormat_BadSize, formatArgs)); + } + + public static CommonResponse CustomMessage_BadFormat_BadSize(string message, params object?[] formatArgs) + { + return new CommonResponse(ErrorCodes.UserAvatar.BadFormat_BadSize, string.Format(message, formatArgs)); + } + + } + + public static class TimelineController + { + public static CommonResponse NameConflict(params object?[] formatArgs) + { + return new CommonResponse(ErrorCodes.TimelineController.NameConflict, string.Format(TimelineController_NameConflict, formatArgs)); + } + + public static CommonResponse CustomMessage_NameConflict(string message, params object?[] formatArgs) + { + return new CommonResponse(ErrorCodes.TimelineController.NameConflict, string.Format(message, formatArgs)); + } + + public static CommonResponse NotExist(params object?[] formatArgs) + { + return new CommonResponse(ErrorCodes.TimelineController.NotExist, string.Format(TimelineController_NotExist, formatArgs)); + } + + public static CommonResponse CustomMessage_NotExist(string message, params object?[] formatArgs) + { + return new CommonResponse(ErrorCodes.TimelineController.NotExist, string.Format(message, formatArgs)); + } + + public static CommonResponse MemberPut_NotExist(params object?[] formatArgs) + { + return new CommonResponse(ErrorCodes.TimelineController.MemberPut_NotExist, string.Format(TimelineController_MemberPut_NotExist, formatArgs)); + } + + public static CommonResponse CustomMessage_MemberPut_NotExist(string message, params object?[] formatArgs) + { + return new CommonResponse(ErrorCodes.TimelineController.MemberPut_NotExist, string.Format(message, formatArgs)); + } + + public static CommonResponse QueryRelateNotExist(params object?[] formatArgs) + { + return new CommonResponse(ErrorCodes.TimelineController.QueryRelateNotExist, string.Format(TimelineController_QueryRelateNotExist, formatArgs)); + } + + public static CommonResponse CustomMessage_QueryRelateNotExist(string message, params object?[] formatArgs) + { + return new CommonResponse(ErrorCodes.TimelineController.QueryRelateNotExist, string.Format(message, formatArgs)); + } + + public static CommonResponse PostNotExist(params object?[] formatArgs) + { + return new CommonResponse(ErrorCodes.TimelineController.PostNotExist, string.Format(TimelineController_PostNotExist, formatArgs)); + } + + public static CommonResponse CustomMessage_PostNotExist(string message, params object?[] formatArgs) + { + return new CommonResponse(ErrorCodes.TimelineController.PostNotExist, string.Format(message, formatArgs)); + } + + public static CommonResponse PostNoData(params object?[] formatArgs) + { + return new CommonResponse(ErrorCodes.TimelineController.PostNoData, string.Format(TimelineController_PostNoData, formatArgs)); + } + + public static CommonResponse CustomMessage_PostNoData(string message, params object?[] formatArgs) + { + return new CommonResponse(ErrorCodes.TimelineController.PostNoData, string.Format(message, formatArgs)); + } + + } + + } + +} diff --git a/BackEnd/Timeline/Models/Http/Timeline.cs b/BackEnd/Timeline/Models/Http/Timeline.cs new file mode 100644 index 00000000..a81b33f5 --- /dev/null +++ b/BackEnd/Timeline/Models/Http/Timeline.cs @@ -0,0 +1,219 @@ +using AutoMapper; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.Infrastructure; +using Microsoft.AspNetCore.Mvc.Routing; +using System; +using System.Collections.Generic; +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; } + /// + /// If post has data (currently it means it's a image post), this is the data etag. + /// + public string? ETag { 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!; + /// + /// Title. + /// + public string Title { 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!; + } + + public class TimelineInfoLinksValueResolver : IValueResolver + { + private readonly IActionContextAccessor _actionContextAccessor; + private readonly IUrlHelperFactory _urlHelperFactory; + + public TimelineInfoLinksValueResolver(IActionContextAccessor actionContextAccessor, IUrlHelperFactory urlHelperFactory) + { + _actionContextAccessor = actionContextAccessor; + _urlHelperFactory = urlHelperFactory; + } + + public TimelineInfoLinks Resolve(Timeline source, TimelineInfo destination, TimelineInfoLinks destMember, ResolutionContext context) + { + var actionContext = _actionContextAccessor.AssertActionContextForUrlFill(); + var urlHelper = _urlHelperFactory.GetUrlHelper(actionContext); + + return new TimelineInfoLinks + { + Self = urlHelper.ActionLink(nameof(TimelineController.TimelineGet), nameof(TimelineController)[0..^nameof(Controller).Length], new { source.Name }), + Posts = urlHelper.ActionLink(nameof(TimelineController.PostListGet), nameof(TimelineController)[0..^nameof(Controller).Length], new { source.Name }) + }; + } + } + + public class TimelinePostContentResolver : IValueResolver + { + private readonly IActionContextAccessor _actionContextAccessor; + private readonly IUrlHelperFactory _urlHelperFactory; + + public TimelinePostContentResolver(IActionContextAccessor actionContextAccessor, IUrlHelperFactory urlHelperFactory) + { + _actionContextAccessor = actionContextAccessor; + _urlHelperFactory = urlHelperFactory; + } + + public TimelinePostContentInfo? Resolve(TimelinePost source, TimelinePostInfo destination, TimelinePostContentInfo? destMember, ResolutionContext context) + { + var actionContext = _actionContextAccessor.AssertActionContextForUrlFill(); + var urlHelper = _urlHelperFactory.GetUrlHelper(actionContext); + + var sourceContent = source.Content; + + if (sourceContent == null) + { + return null; + } + + if (sourceContent is TextTimelinePostContent textContent) + { + return new TimelinePostContentInfo + { + Type = TimelinePostContentTypes.Text, + Text = textContent.Text + }; + } + else if (sourceContent is ImageTimelinePostContent imageContent) + { + return new TimelinePostContentInfo + { + Type = TimelinePostContentTypes.Image, + Url = urlHelper.ActionLink( + action: nameof(TimelineController.PostDataGet), + controller: nameof(TimelineController)[0..^nameof(Controller).Length], + values: new { Name = source.TimelineName, Id = source.Id }), + ETag = $"\"{imageContent.DataTag}\"" + }; + } + else + { + throw new InvalidOperationException(Resources.Models.Http.Exception.UnknownPostContentType); + } + } + } + + public class TimelineInfoAutoMapperProfile : Profile + { + public TimelineInfoAutoMapperProfile() + { + CreateMap().ForMember(u => u._links, opt => opt.MapFrom()); + CreateMap().ForMember(p => p.Content, opt => opt.MapFrom()); + CreateMap(); + } + } +} diff --git a/BackEnd/Timeline/Models/Http/TimelineController.cs b/BackEnd/Timeline/Models/Http/TimelineController.cs new file mode 100644 index 00000000..7bd141ed --- /dev/null +++ b/BackEnd/Timeline/Models/Http/TimelineController.cs @@ -0,0 +1,93 @@ +using System; +using System.ComponentModel.DataAnnotations; +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 title. Null for not change. + /// + public string? Title { get; set; } + + /// + /// New description. Null for not change. + /// + public string? Description { get; set; } + + /// + /// New visibility. Null for not change. + /// + public TimelineVisibility? Visibility { get; set; } + } + + /// + /// Change timeline name request model. + /// + public class TimelineChangeNameRequest + { + /// + /// Old name of timeline. + /// + [Required] + [TimelineName] + public string OldName { get; set; } = default!; + /// + /// New name of timeline. + /// + [Required] + [TimelineName] + public string NewName { get; set; } = default!; + } +} diff --git a/BackEnd/Timeline/Models/Http/TokenController.cs b/BackEnd/Timeline/Models/Http/TokenController.cs new file mode 100644 index 00000000..a42c44e5 --- /dev/null +++ b/BackEnd/Timeline/Models/Http/TokenController.cs @@ -0,0 +1,62 @@ +using System.ComponentModel.DataAnnotations; +using Timeline.Controllers; + +namespace Timeline.Models.Http +{ + /// + /// Request model for . + /// + public class CreateTokenRequest + { + /// + /// The username. + /// + public string Username { get; set; } = default!; + /// + /// The password. + /// + public string Password { get; set; } = default!; + /// + /// 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 + { + /// + /// 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/BackEnd/Timeline/Models/Http/UserController.cs b/BackEnd/Timeline/Models/Http/UserController.cs new file mode 100644 index 00000000..6bc5a66e --- /dev/null +++ b/BackEnd/Timeline/Models/Http/UserController.cs @@ -0,0 +1,93 @@ +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); + CreateMap(MemberList.Source); + } + } +} diff --git a/BackEnd/Timeline/Models/Http/UserInfo.cs b/BackEnd/Timeline/Models/Http/UserInfo.cs new file mode 100644 index 00000000..d92a12c4 --- /dev/null +++ b/BackEnd/Timeline/Models/Http/UserInfo.cs @@ -0,0 +1,90 @@ +using AutoMapper; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.Infrastructure; +using Microsoft.AspNetCore.Mvc.Routing; +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!; + } + + public class UserInfoLinksValueResolver : IValueResolver + { + private readonly IActionContextAccessor _actionContextAccessor; + private readonly IUrlHelperFactory _urlHelperFactory; + + public UserInfoLinksValueResolver(IActionContextAccessor actionContextAccessor, IUrlHelperFactory urlHelperFactory) + { + _actionContextAccessor = actionContextAccessor; + _urlHelperFactory = urlHelperFactory; + } + + public UserInfoLinks Resolve(User source, UserInfo destination, UserInfoLinks destMember, ResolutionContext context) + { + var actionContext = _actionContextAccessor.AssertActionContextForUrlFill(); + var urlHelper = _urlHelperFactory.GetUrlHelper(actionContext); + + var result = new UserInfoLinks + { + Self = urlHelper.ActionLink(nameof(UserController.Get), nameof(UserController)[0..^nameof(Controller).Length], new { destination.Username }), + Avatar = urlHelper.ActionLink(nameof(UserAvatarController.Get), nameof(UserAvatarController)[0..^nameof(Controller).Length], new { destination.Username }), + Timeline = urlHelper.ActionLink(nameof(TimelineController.TimelineGet), nameof(TimelineController)[0..^nameof(Controller).Length], new { Name = "@" + destination.Username }) + }; + return result; + } + } + + public class UserInfoAutoMapperProfile : Profile + { + public UserInfoAutoMapperProfile() + { + CreateMap().ForMember(u => u._links, opt => opt.MapFrom()); + } + } +} -- cgit v1.2.3