aboutsummaryrefslogtreecommitdiff
path: root/Timeline
diff options
context:
space:
mode:
Diffstat (limited to 'Timeline')
-rw-r--r--Timeline/Auth/MyAuthenticationHandler.cs13
-rw-r--r--Timeline/Controllers/ControllerAuthExtensions.cs40
-rw-r--r--Timeline/Controllers/PersonalTimelineController.cs143
-rw-r--r--Timeline/Controllers/Testing/TestingI18nController.cs36
-rw-r--r--Timeline/Controllers/TokenController.cs92
-rw-r--r--Timeline/Controllers/UserAvatarController.cs155
-rw-r--r--Timeline/Controllers/UserController.cs172
-rw-r--r--Timeline/Controllers/UserDetailController.cs49
-rw-r--r--Timeline/Entities/DatabaseContext.cs10
-rw-r--r--Timeline/Entities/TimelineEntity.cs4
-rw-r--r--Timeline/Entities/TimelineMemberEntity.cs2
-rw-r--r--Timeline/Entities/TimelinePostEntity.cs2
-rw-r--r--Timeline/Entities/UserAvatarEntity.cs (renamed from Timeline/Entities/UserAvatar.cs)6
-rw-r--r--Timeline/Entities/UserDetail.cs21
-rw-r--r--Timeline/Entities/UserEntity.cs (renamed from Timeline/Entities/User.cs)15
-rw-r--r--Timeline/ErrorCodes.cs35
-rw-r--r--Timeline/Filters/Header.cs57
-rw-r--r--Timeline/Filters/Timeline.cs26
-rw-r--r--Timeline/Filters/User.cs88
-rw-r--r--Timeline/Formatters/StringInputFormatter.cs1
-rw-r--r--Timeline/GlobalSuppressions.cs8
-rw-r--r--Timeline/Helpers/InvalidModelResponseFactory.cs3
-rw-r--r--Timeline/InvalidBranchException.cs16
-rw-r--r--Timeline/Migrations/DevelopmentDatabase/20200131100517_RefactorUser.Designer.cs243
-rw-r--r--Timeline/Migrations/DevelopmentDatabase/20200131100517_RefactorUser.cs129
-rw-r--r--Timeline/Migrations/DevelopmentDatabase/DevelopmentDatabaseContextModelSnapshot.cs92
-rw-r--r--Timeline/Migrations/ProductionDatabase/20200131152033_RefactorUser.Designer.cs244
-rw-r--r--Timeline/Migrations/ProductionDatabase/20200131152033_RefactorUser.cs55
-rw-r--r--Timeline/Migrations/ProductionDatabase/ProductionDatabaseContextModelSnapshot.cs92
-rw-r--r--Timeline/Models/Converters/JsonDateTimeConverter.cs1
-rw-r--r--Timeline/Models/Http/Common.cs33
-rw-r--r--Timeline/Models/Http/ErrorResponse.cs261
-rw-r--r--Timeline/Models/Http/Timeline.cs45
-rw-r--r--Timeline/Models/Http/TimelineCommon.cs (renamed from Timeline/Models/Timeline.cs)25
-rw-r--r--Timeline/Models/Http/TimelineController.cs20
-rw-r--r--Timeline/Models/Http/TokenController.cs (renamed from Timeline/Models/Http/Token.cs)0
-rw-r--r--Timeline/Models/Http/User.cs38
-rw-r--r--Timeline/Models/Http/UserController.cs53
-rw-r--r--Timeline/Models/Http/UserInfo.cs59
-rw-r--r--Timeline/Models/PutResult.cs17
-rw-r--r--Timeline/Models/UserInfo.cs23
-rw-r--r--Timeline/Models/Validation/NicknameValidator.cs25
-rw-r--r--Timeline/Models/Validation/UsernameValidator.cs1
-rw-r--r--Timeline/Models/Validation/Validator.cs28
-rw-r--r--Timeline/Resources/Common.resx123
-rw-r--r--Timeline/Resources/Controllers/ControllerAuthExtensions.Designer.cs (renamed from Timeline/Resources/Services/UserDetailService.Designer.cs)38
-rw-r--r--Timeline/Resources/Controllers/ControllerAuthExtensions.resx (renamed from Timeline/Resources/Models/Validation/Validator.zh.resx)11
-rw-r--r--Timeline/Resources/Controllers/TimelineController.Designer.cs54
-rw-r--r--Timeline/Resources/Controllers/TimelineController.resx18
-rw-r--r--Timeline/Resources/Controllers/TimelineController.zh.resx138
-rw-r--r--Timeline/Resources/Controllers/TokenController.Designer.cs45
-rw-r--r--Timeline/Resources/Controllers/TokenController.resx15
-rw-r--r--Timeline/Resources/Controllers/TokenController.zh.resx135
-rw-r--r--Timeline/Resources/Controllers/UserAvatarController.Designer.cs72
-rw-r--r--Timeline/Resources/Controllers/UserAvatarController.resx24
-rw-r--r--Timeline/Resources/Controllers/UserAvatarController.zh.resx144
-rw-r--r--Timeline/Resources/Controllers/UserController.Designer.cs118
-rw-r--r--Timeline/Resources/Controllers/UserController.resx42
-rw-r--r--Timeline/Resources/Controllers/UserController.zh.resx138
-rw-r--r--Timeline/Resources/Filters.Designer.cs63
-rw-r--r--Timeline/Resources/Filters.resx21
-rw-r--r--Timeline/Resources/Filters.zh.resx141
-rw-r--r--Timeline/Resources/Messages.Designer.cs315
-rw-r--r--Timeline/Resources/Messages.resx (renamed from Timeline/Resources/Models/Http/Common.zh.resx)92
-rw-r--r--Timeline/Resources/Models/Http/Common.Designer.cs36
-rw-r--r--Timeline/Resources/Models/Http/Common.resx12
-rw-r--r--Timeline/Resources/Models/Validation/NicknameValidator.Designer.cs (renamed from Timeline/Resources/Common.Designer.cs)14
-rw-r--r--Timeline/Resources/Models/Validation/NicknameValidator.resx (renamed from Timeline/Resources/Controllers/Testing/TestingI18nController.resx)4
-rw-r--r--Timeline/Resources/Models/Validation/UsernameValidator.zh.resx129
-rw-r--r--Timeline/Resources/Services/Exception.Designer.cs133
-rw-r--r--Timeline/Resources/Services/Exception.resx67
-rw-r--r--Timeline/Resources/Services/TimelineService.Designer.cs (renamed from Timeline/Resources/Controllers/Testing/TestingI18nController.Designer.cs)23
-rw-r--r--Timeline/Resources/Services/TimelineService.resx (renamed from Timeline/Resources/Controllers/Testing/TestingI18nController.zh.resx)7
-rw-r--r--Timeline/Resources/Services/UserDetailService.resx132
-rw-r--r--Timeline/Resources/Services/UserService.Designer.cs48
-rw-r--r--Timeline/Resources/Services/UserService.resx20
-rw-r--r--Timeline/Services/ConflictException.cs21
-rw-r--r--Timeline/Services/DatabaseExtensions.cs36
-rw-r--r--Timeline/Services/JwtBadVersionException.cs36
-rw-r--r--Timeline/Services/JwtUserTokenBadFormatException.cs48
-rw-r--r--Timeline/Services/JwtVerifyException.cs59
-rw-r--r--Timeline/Services/PasswordBadFormatException.cs27
-rw-r--r--Timeline/Services/TimelineAlreadyExistException.cs17
-rw-r--r--Timeline/Services/TimelineMemberOperationUserException.cs37
-rw-r--r--Timeline/Services/TimelineNameBadFormatException.cs21
-rw-r--r--Timeline/Services/TimelineService.cs394
-rw-r--r--Timeline/Services/User.cs18
-rw-r--r--Timeline/Services/UserAvatarService.cs50
-rw-r--r--Timeline/Services/UserDetailService.cs102
-rw-r--r--Timeline/Services/UserNotExistException.cs4
-rw-r--r--Timeline/Services/UserRoleConvert.cs (renamed from Timeline/Models/UserConvert.cs)26
-rw-r--r--Timeline/Services/UserService.cs552
-rw-r--r--Timeline/Services/UserTokenException.cs68
-rw-r--r--Timeline/Services/UserTokenManager.cs93
-rw-r--r--Timeline/Services/UserTokenService.cs (renamed from Timeline/Services/JwtService.cs)80
-rw-r--r--Timeline/Services/UsernameBadFormatException.cs27
-rw-r--r--Timeline/Services/UsernameConfictException.cs25
-rw-r--r--Timeline/Startup.cs43
-rw-r--r--Timeline/Timeline.csproj75
99 files changed, 2893 insertions, 3816 deletions
diff --git a/Timeline/Auth/MyAuthenticationHandler.cs b/Timeline/Auth/MyAuthenticationHandler.cs
index f5dcd697..3c97c329 100644
--- a/Timeline/Auth/MyAuthenticationHandler.cs
+++ b/Timeline/Auth/MyAuthenticationHandler.cs
@@ -3,11 +3,11 @@ using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Microsoft.Net.Http.Headers;
using System;
+using System.Globalization;
using System.Linq;
using System.Security.Claims;
using System.Text.Encodings.Web;
using System.Threading.Tasks;
-using Timeline.Models;
using Timeline.Services;
using static Timeline.Resources.Authentication.AuthHandler;
@@ -30,13 +30,13 @@ namespace Timeline.Auth
public class MyAuthenticationHandler : AuthenticationHandler<MyAuthenticationOptions>
{
private readonly ILogger<MyAuthenticationHandler> _logger;
- private readonly IUserService _userService;
+ private readonly IUserTokenManager _userTokenManager;
- public MyAuthenticationHandler(IOptionsMonitor<MyAuthenticationOptions> options, ILoggerFactory logger, UrlEncoder encoder, ISystemClock clock, IUserService userService)
+ public MyAuthenticationHandler(IOptionsMonitor<MyAuthenticationOptions> options, ILoggerFactory logger, UrlEncoder encoder, ISystemClock clock, IUserTokenManager userTokenManager)
: base(options, logger, encoder, clock)
{
_logger = logger.CreateLogger<MyAuthenticationHandler>();
- _userService = userService;
+ _userTokenManager = userTokenManager;
}
// return null if no token is found
@@ -78,11 +78,12 @@ namespace Timeline.Auth
try
{
- var userInfo = await _userService.VerifyToken(token);
+ var userInfo = await _userTokenManager.VerifyToken(token);
var identity = new ClaimsIdentity(AuthenticationConstants.Scheme);
+ identity.AddClaim(new Claim(ClaimTypes.NameIdentifier, userInfo.Id!.Value.ToString(CultureInfo.InvariantCulture), ClaimValueTypes.Integer64));
identity.AddClaim(new Claim(identity.NameClaimType, userInfo.Username, ClaimValueTypes.String));
- identity.AddClaims(UserRoleConvert.ToArray(userInfo.Administrator).Select(role => new Claim(identity.RoleClaimType, role, ClaimValueTypes.String)));
+ identity.AddClaims(UserRoleConvert.ToArray(userInfo.Administrator!.Value).Select(role => new Claim(identity.RoleClaimType, role, ClaimValueTypes.String)));
var principal = new ClaimsPrincipal();
principal.AddIdentity(identity);
diff --git a/Timeline/Controllers/ControllerAuthExtensions.cs b/Timeline/Controllers/ControllerAuthExtensions.cs
new file mode 100644
index 00000000..00a65454
--- /dev/null
+++ b/Timeline/Controllers/ControllerAuthExtensions.cs
@@ -0,0 +1,40 @@
+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/PersonalTimelineController.cs b/Timeline/Controllers/PersonalTimelineController.cs
index c864ed39..11353bb5 100644
--- a/Timeline/Controllers/PersonalTimelineController.cs
+++ b/Timeline/Controllers/PersonalTimelineController.cs
@@ -3,64 +3,22 @@ using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
using System.Collections.Generic;
-using System.Globalization;
using System.Threading.Tasks;
-using Timeline.Auth;
using Timeline.Filters;
-using Timeline.Models;
using Timeline.Models.Http;
using Timeline.Models.Validation;
using Timeline.Services;
-using static Timeline.Resources.Controllers.TimelineController;
-
-namespace Timeline
-{
- public static partial class ErrorCodes
- {
- public static partial class Http
- {
- public static class Timeline // ccc = 004
- {
- public const int PostListGetForbid = 10040101;
- public const int PostOperationCreateForbid = 10040102;
- public const int PostOperationDeleteForbid = 10040103;
- public const int PostOperationDeleteNotExist = 10040201;
- public const int ChangeMemberUserNotExist = 10040301;
- }
- }
- }
-}
namespace Timeline.Controllers
{
[ApiController]
+ [CatchTimelineNotExistException]
public class PersonalTimelineController : Controller
{
private readonly ILogger<PersonalTimelineController> _logger;
private readonly IPersonalTimelineService _service;
- private bool IsAdmin()
- {
- if (User != null)
- {
- return User.IsAdministrator();
- }
- return false;
- }
-
- private string? GetAuthUsername()
- {
- if (User == null)
- {
- return null;
- }
- else
- {
- return User.Identity.Name;
- }
- }
-
public PersonalTimelineController(ILogger<PersonalTimelineController> logger, IPersonalTimelineService service)
{
_logger = logger;
@@ -68,100 +26,105 @@ namespace Timeline.Controllers
}
[HttpGet("users/{username}/timeline")]
- [CatchTimelineNotExistException]
public async Task<ActionResult<BaseTimelineInfo>> TimelineGet([FromRoute][Username] string username)
{
return await _service.GetTimeline(username);
}
[HttpGet("users/{username}/timeline/posts")]
- [CatchTimelineNotExistException]
public async Task<ActionResult<IList<TimelinePostInfo>>> PostListGet([FromRoute][Username] string username)
{
- if (!IsAdmin() && !await _service.HasReadPermission(username, GetAuthUsername()))
+ if (!this.IsAdministrator() && !await _service.HasReadPermission(username, this.GetOptionalUserId()))
{
- return StatusCode(StatusCodes.Status403Forbidden,
- new CommonResponse(ErrorCodes.Http.Timeline.PostListGetForbid, MessagePostListGetForbid));
+ return StatusCode(StatusCodes.Status403Forbidden, ErrorResponse.Common.Forbid());
}
return await _service.GetPosts(username);
}
- [HttpPost("users/{username}/timeline/postop/create")]
+ [HttpPost("users/{username}/timeline/posts")]
[Authorize]
- [CatchTimelineNotExistException]
- public async Task<ActionResult<TimelinePostCreateResponse>> PostOperationCreate([FromRoute][Username] string username, [FromBody] TimelinePostCreateRequest body)
+ public async Task<ActionResult<TimelinePostInfo>> PostPost([FromRoute][Username] string username, [FromBody] TimelinePostCreateRequest body)
{
- if (!IsAdmin() && !await _service.IsMemberOf(username, GetAuthUsername()!))
+ var id = this.GetUserId();
+ if (!this.IsAdministrator() && !await _service.IsMemberOf(username, id))
{
- return StatusCode(StatusCodes.Status403Forbidden,
- new CommonResponse(ErrorCodes.Http.Timeline.PostOperationCreateForbid, MessagePostOperationCreateForbid));
+ return StatusCode(StatusCodes.Status403Forbidden, ErrorResponse.Common.Forbid());
}
- var res = await _service.CreatePost(username, User.Identity.Name!, body.Content, body.Time);
+ var res = await _service.CreatePost(username, id, body.Content, body.Time);
return res;
}
- [HttpPost("users/{username}/timeline/postop/delete")]
+ [HttpDelete("users/{username}/timeline/posts/{id}")]
[Authorize]
- [CatchTimelineNotExistException]
- public async Task<ActionResult> PostOperationDelete([FromRoute][Username] string username, [FromBody] TimelinePostDeleteRequest body)
+ public async Task<ActionResult> PostDelete([FromRoute][Username] string username, [FromRoute] long id)
{
try
{
- var postId = body.Id!.Value;
- if (!IsAdmin() && !await _service.HasPostModifyPermission(username, postId, GetAuthUsername()!))
+ if (!this.IsAdministrator() && !await _service.HasPostModifyPermission(username, id, this.GetUserId()))
{
- return StatusCode(StatusCodes.Status403Forbidden,
- new CommonResponse(ErrorCodes.Http.Timeline.PostOperationDeleteForbid, MessagePostOperationCreateForbid));
+ return StatusCode(StatusCodes.Status403Forbidden, ErrorResponse.Common.Forbid());
}
- await _service.DeletePost(username, postId);
+ await _service.DeletePost(username, id);
+ return Ok(CommonDeleteResponse.Delete());
}
catch (TimelinePostNotExistException)
{
- return BadRequest(new CommonResponse(
- ErrorCodes.Http.Timeline.PostOperationDeleteNotExist,
- MessagePostOperationDeleteNotExist));
+ return Ok(CommonDeleteResponse.NotExist());
}
- return Ok();
}
- [HttpPost("users/{username}/timeline/op/property")]
+ [HttpPatch("users/{username}/timeline")]
[Authorize]
- [SelfOrAdmin]
- [CatchTimelineNotExistException]
- public async Task<ActionResult> TimelineChangeProperty([FromRoute][Username] string username, [FromBody] TimelinePropertyChangeRequest body)
+ public async Task<ActionResult<BaseTimelineInfo>> TimelinePatch([FromRoute][Username] string username, [FromBody] TimelinePatchRequest body)
{
+ if (!this.IsAdministrator() && !(User.Identity.Name == username))
+ {
+ return StatusCode(StatusCodes.Status403Forbidden, ErrorResponse.Common.Forbid());
+ }
await _service.ChangeProperty(username, body);
- return Ok();
+ var timeline = await _service.GetTimeline(username);
+ return Ok(timeline);
}
- [HttpPost("users/{username}/timeline/op/member")]
+ [HttpPut("users/{username}/timeline/members/{member}")]
[Authorize]
- [SelfOrAdmin]
- [CatchTimelineNotExistException]
- public async Task<ActionResult> TimelineChangeMember([FromRoute][Username] string username, [FromBody] TimelineMemberChangeRequest body)
+ public async Task<ActionResult> TimelineMemberPut([FromRoute][Username] string username, [FromRoute][Username] string member)
{
+ if (!this.IsAdministrator() && !(User.Identity.Name == username))
+ {
+ return StatusCode(StatusCodes.Status403Forbidden, ErrorResponse.Common.Forbid());
+ }
+
try
{
- await _service.ChangeMember(username, body.Add, body.Remove);
+ await _service.ChangeMember(username, new List<string> { member }, null);
return Ok();
}
- catch (TimelineMemberOperationUserException e)
+ catch (UserNotExistException)
{
- if (e.InnerException is UsernameBadFormatException)
- {
- return BadRequest(CommonResponse.InvalidModel(
- string.Format(CultureInfo.CurrentCulture, MessageMemberUsernameBadFormat, e.Index, e.Operation)));
- }
- else if (e.InnerException is UserNotExistException)
- {
- return BadRequest(new CommonResponse(ErrorCodes.Http.Timeline.ChangeMemberUserNotExist,
- string.Format(CultureInfo.CurrentCulture, MessageMemberUserNotExist, e.Index, e.Operation)));
- }
+ return BadRequest(ErrorResponse.TimelineController.MemberPut_NotExist());
+ }
+ }
+
+ [HttpDelete("users/{username}/timeline/members/{member}")]
+ [Authorize]
+ public async Task<ActionResult> TimelineMemberDelete([FromRoute][Username] string username, [FromRoute][Username] string member)
+ {
+ if (!this.IsAdministrator() && !(User.Identity.Name == username))
+ {
+ return StatusCode(StatusCodes.Status403Forbidden, ErrorResponse.Common.Forbid());
+ }
- _logger.LogError(e, LogUnknownTimelineMemberOperationUserException);
- throw;
+ try
+ {
+ await _service.ChangeMember(username, null, new List<string> { member });
+ return Ok(CommonDeleteResponse.Delete());
+ }
+ catch (UserNotExistException)
+ {
+ return Ok(CommonDeleteResponse.NotExist());
}
}
}
diff --git a/Timeline/Controllers/Testing/TestingI18nController.cs b/Timeline/Controllers/Testing/TestingI18nController.cs
deleted file mode 100644
index febb56a5..00000000
--- a/Timeline/Controllers/Testing/TestingI18nController.cs
+++ /dev/null
@@ -1,36 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Threading.Tasks;
-using Microsoft.AspNetCore.Mvc;
-using Microsoft.Extensions.Localization;
-
-// For more information on enabling Web API for empty projects, visit https://go.microsoft.com/fwlink/?LinkID=397860
-
-namespace Timeline.Controllers.Testing
-{
- [Route("testing/i18n")]
- [ApiController]
- public class TestingI18nController : Controller
- {
- private readonly IStringLocalizer<TestingI18nController> _stringLocalizer;
-
- public TestingI18nController(IStringLocalizer<TestingI18nController> stringLocalizer)
- {
- _stringLocalizer = stringLocalizer;
- }
-
- [HttpGet("direct")]
- [System.Diagnostics.CodeAnalysis.SuppressMessage("Performance", "CA1822:Mark members as static")]
- public ActionResult<string> Direct()
- {
- return Resources.Controllers.Testing.TestingI18nController.TestString;
- }
-
- [HttpGet("localizer")]
- public ActionResult<string> Localizer()
- {
- return _stringLocalizer["TestString"].Value;
- }
- }
-}
diff --git a/Timeline/Controllers/TokenController.cs b/Timeline/Controllers/TokenController.cs
index 01f4778f..1fb0b17a 100644
--- a/Timeline/Controllers/TokenController.cs
+++ b/Timeline/Controllers/TokenController.cs
@@ -1,7 +1,7 @@
+using AutoMapper;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
-using Microsoft.IdentityModel.Tokens;
using System;
using System.Globalization;
using System.Threading.Tasks;
@@ -10,46 +10,24 @@ using Timeline.Models.Http;
using Timeline.Services;
using static Timeline.Resources.Controllers.TokenController;
-namespace Timeline
-{
- public static partial class ErrorCodes
- {
- public static partial class Http
- {
- public static class Token // bbb = 001
- {
- public static class Create // cc = 01
- {
- public const int BadCredential = 10010101;
- }
-
- public static class Verify // cc = 02
- {
- public const int BadFormat = 10010201;
- public const int UserNotExist = 10010202;
- public const int OldVersion = 10010203;
- public const int Expired = 10010204;
- }
- }
- }
- }
-}
-
namespace Timeline.Controllers
{
[Route("token")]
[ApiController]
public class TokenController : Controller
{
- private readonly IUserService _userService;
+ private readonly IUserTokenManager _userTokenManager;
private readonly ILogger<TokenController> _logger;
private readonly IClock _clock;
- public TokenController(IUserService userService, ILogger<TokenController> logger, IClock clock)
+ private readonly IMapper _mapper;
+
+ public TokenController(IUserTokenManager userTokenManager, ILogger<TokenController> logger, IClock clock, IMapper mapper)
{
- _userService = userService;
+ _userTokenManager = userTokenManager;
_logger = logger;
_clock = clock;
+ _mapper = mapper;
}
[HttpPost("create")]
@@ -72,7 +50,7 @@ namespace Timeline.Controllers
if (request.Expire != null)
expireTime = _clock.GetCurrentTime().AddDays(request.Expire.Value);
- var result = await _userService.CreateToken(request.Username, request.Password, expireTime);
+ var result = await _userTokenManager.CreateToken(request.Username, request.Password, expireTime);
_logger.LogInformation(Log.Format(LogCreateSuccess,
("Username", request.Username),
@@ -81,22 +59,18 @@ namespace Timeline.Controllers
return Ok(new CreateTokenResponse
{
Token = result.Token,
- User = result.User
+ User = _mapper.Map<UserInfo>(result.User)
});
}
catch (UserNotExistException e)
{
LogFailure(LogUserNotExist, e);
- return BadRequest(new CommonResponse(
- ErrorCodes.Http.Token.Create.BadCredential,
- ErrorBadCredential));
+ return BadRequest(ErrorResponse.TokenController.Create_BadCredential());
}
catch (BadPasswordException e)
{
LogFailure(LogBadPassword, e);
- return BadRequest(new CommonResponse(
- ErrorCodes.Http.Token.Create.BadCredential,
- ErrorBadCredential));
+ return BadRequest(ErrorResponse.TokenController.Create_BadCredential());
}
}
@@ -115,44 +89,34 @@ namespace Timeline.Controllers
try
{
- var result = await _userService.VerifyToken(request.Token);
+ var result = await _userTokenManager.VerifyToken(request.Token);
_logger.LogInformation(Log.Format(LogVerifySuccess,
("Username", result.Username), ("Token", request.Token)));
return Ok(new VerifyTokenResponse
{
- User = result
+ User = _mapper.Map<UserInfo>(result)
});
}
- catch (JwtVerifyException e)
+ catch (UserTokenTimeExpireException e)
{
- if (e.ErrorCode == JwtVerifyException.ErrorCodes.Expired)
- {
- var innerException = e.InnerException as SecurityTokenExpiredException;
- LogFailure(LogVerifyExpire, e, ("Expires", innerException?.Expires),
- ("Current Time", _clock.GetCurrentTime()));
- return BadRequest(new CommonResponse(
- ErrorCodes.Http.Token.Verify.Expired, ErrorVerifyExpire));
- }
- else if (e.ErrorCode == JwtVerifyException.ErrorCodes.OldVersion)
- {
- var innerException = e.InnerException as JwtBadVersionException;
- LogFailure(LogVerifyOldVersion, e,
- ("Token Version", innerException?.TokenVersion), ("Required Version", innerException?.RequiredVersion));
- return BadRequest(new CommonResponse(
- ErrorCodes.Http.Token.Verify.OldVersion, ErrorVerifyOldVersion));
- }
- else
- {
- LogFailure(LogVerifyBadFormat, e);
- return BadRequest(new CommonResponse(
- ErrorCodes.Http.Token.Verify.BadFormat, ErrorVerifyBadFormat));
- }
+ 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(new CommonResponse(
- ErrorCodes.Http.Token.Verify.UserNotExist, ErrorVerifyUserNotExist));
+ return BadRequest(ErrorResponse.TokenController.Verify_UserNotExist());
}
}
}
diff --git a/Timeline/Controllers/UserAvatarController.cs b/Timeline/Controllers/UserAvatarController.cs
index 7625f962..2dd279a8 100644
--- a/Timeline/Controllers/UserAvatarController.cs
+++ b/Timeline/Controllers/UserAvatarController.cs
@@ -14,39 +14,6 @@ using Timeline.Models.Validation;
using Timeline.Services;
using static Timeline.Resources.Controllers.UserAvatarController;
-namespace Timeline
-{
- public static partial class ErrorCodes
- {
- public static partial class Http
- {
- public static class UserAvatar // bbb = 003
- {
- public static class Get // cc = 01
- {
- public const int UserNotExist = 10030101;
- }
-
- public static class Put // cc = 02
- {
- public const int UserNotExist = 10030201;
- public const int Forbid = 10030202;
- public const int BadFormat_CantDecode = 10030203;
- public const int BadFormat_UnmatchedFormat = 10030204;
- public const int BadFormat_BadSize = 10030205;
-
- }
-
- public static class Delete // cc = 03
- {
- public const int UserNotExist = 10030301;
- public const int Forbid = 10030302;
- }
- }
- }
- }
-}
-
namespace Timeline.Controllers
{
[ApiController]
@@ -54,11 +21,13 @@ namespace Timeline.Controllers
{
private readonly ILogger<UserAvatarController> _logger;
+ private readonly IUserService _userService;
private readonly IUserAvatarService _service;
- public UserAvatarController(ILogger<UserAvatarController> logger, IUserAvatarService service)
+ public UserAvatarController(ILogger<UserAvatarController> logger, IUserService userService, IUserAvatarService service)
{
_logger = logger;
+ _userService = userService;
_service = service;
}
@@ -66,41 +35,45 @@ namespace Timeline.Controllers
[ResponseCache(NoStore = false, Location = ResponseCacheLocation.None, Duration = 0)]
public async Task<IActionResult> Get([FromRoute][Username] string username)
{
+ long id;
+ try
+ {
+ id = await _userService.GetUserIdByUsername(username);
+ }
+ catch (UserNotExistException e)
+ {
+ _logger.LogInformation(e, Log.Format(LogGetUserNotExist, ("Username", username)));
+ return NotFound(ErrorResponse.UserCommon.NotExist());
+ }
+
const string IfNonMatchHeaderKey = "If-None-Match";
- try
+ var eTagValue = $"\"{await _service.GetAvatarETag(id)}\"";
+ var eTag = new EntityTagHeaderValue(eTagValue);
+
+ if (Request.Headers.TryGetValue(IfNonMatchHeaderKey, out var value))
{
- var eTagValue = $"\"{await _service.GetAvatarETag(username)}\"";
- var eTag = new EntityTagHeaderValue(eTagValue);
+ if (!EntityTagHeaderValue.TryParseStrictList(value, out var eTagList))
+ {
+ _logger.LogInformation(Log.Format(LogGetBadIfNoneMatch,
+ ("Username", username), ("If-None-Match", value)));
+ return BadRequest(ErrorResponse.Common.Header.IfNonMatch_BadFormat());
+ }
- if (Request.Headers.TryGetValue(IfNonMatchHeaderKey, out var value))
+ if (eTagList.FirstOrDefault(e => e.Equals(eTag)) != null)
{
- if (!EntityTagHeaderValue.TryParseStrictList(value, out var eTagList))
- {
- _logger.LogInformation(Log.Format(LogGetBadIfNoneMatch,
- ("Username", username), ("If-None-Match", value)));
- return BadRequest(HeaderErrorResponse.BadIfNonMatch());
- }
-
- if (eTagList.FirstOrDefault(e => e.Equals(eTag)) != null)
- {
- Response.Headers.Add("ETag", eTagValue);
- _logger.LogInformation(Log.Format(LogGetReturnNotModify, ("Username", username)));
- return StatusCode(StatusCodes.Status304NotModified);
- }
+ Response.Headers.Add("ETag", eTagValue);
+ _logger.LogInformation(Log.Format(LogGetReturnNotModify, ("Username", username)));
+ return StatusCode(StatusCodes.Status304NotModified);
}
+ }
- var avatarInfo = await _service.GetAvatar(username);
- var avatar = avatarInfo.Avatar;
+ var avatarInfo = await _service.GetAvatar(id);
+ var avatar = avatarInfo.Avatar;
+
+ _logger.LogInformation(Log.Format(LogGetReturnData, ("Username", username)));
+ return File(avatar.Data, avatar.Type, new DateTimeOffset(avatarInfo.LastModified), eTag);
- _logger.LogInformation(Log.Format(LogGetReturnData, ("Username", username)));
- return File(avatar.Data, avatar.Type, new DateTimeOffset(avatarInfo.LastModified), eTag);
- }
- catch (UserNotExistException e)
- {
- _logger.LogInformation(e, Log.Format(LogGetUserNotExist, ("Username", username)));
- return NotFound(new CommonResponse(ErrorCodes.Http.UserAvatar.Get.UserNotExist, ErrorGetUserNotExist));
- }
}
[HttpPut("users/{username}/avatar")]
@@ -111,14 +84,24 @@ namespace Timeline.Controllers
{
var contentLength = Request.ContentLength!.Value;
if (contentLength > 1000 * 1000 * 10)
- return BadRequest(ContentErrorResponse.TooBig("10MB"));
+ return BadRequest(ErrorResponse.Common.Content.TooBig("10MB"));
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,
- new CommonResponse(ErrorCodes.Http.UserAvatar.Put.Forbid, ErrorPutForbid));
+ 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
@@ -127,13 +110,13 @@ namespace Timeline.Controllers
var bytesRead = await Request.Body.ReadAsync(data);
if (bytesRead != contentLength)
- return BadRequest(ContentErrorResponse.UnmatchedLength_Smaller());
+ return BadRequest(ErrorResponse.Common.Content.UnmatchedLength_Smaller());
var extraByte = new byte[1];
if (await Request.Body.ReadAsync(extraByte) != 0)
- return BadRequest(ContentErrorResponse.UnmatchedLength_Bigger());
+ return BadRequest(ErrorResponse.Common.Content.UnmatchedLength_Bigger());
- await _service.SetAvatar(username, new Avatar
+ await _service.SetAvatar(id, new Avatar
{
Data = data,
Type = Request.ContentType
@@ -143,27 +126,17 @@ namespace Timeline.Controllers
("Username", username), ("Mime Type", Request.ContentType)));
return Ok();
}
- catch (UserNotExistException e)
- {
- _logger.LogInformation(e, Log.Format(LogPutUserNotExist, ("Username", username)));
- return BadRequest(new CommonResponse(ErrorCodes.Http.UserAvatar.Put.UserNotExist, ErrorPutUserNotExist));
- }
catch (AvatarFormatException e)
{
- var (code, message) = e.Error switch
+ _logger.LogInformation(e, Log.Format(LogPutUserBadFormat, ("Username", username)));
+ return BadRequest(e.Error switch
{
- AvatarFormatException.ErrorReason.CantDecode =>
- (ErrorCodes.Http.UserAvatar.Put.BadFormat_CantDecode, ErrorPutBadFormatCantDecode),
- AvatarFormatException.ErrorReason.UnmatchedFormat =>
- (ErrorCodes.Http.UserAvatar.Put.BadFormat_UnmatchedFormat, ErrorPutBadFormatUnmatchedFormat),
- AvatarFormatException.ErrorReason.BadSize =>
- (ErrorCodes.Http.UserAvatar.Put.BadFormat_BadSize, ErrorPutBadFormatBadSize),
+ AvatarFormatException.ErrorReason.CantDecode => ErrorResponse.UserAvatar.BadFormat_CantDecode(),
+ AvatarFormatException.ErrorReason.UnmatchedFormat => ErrorResponse.UserAvatar.BadFormat_UnmatchedFormat(),
+ AvatarFormatException.ErrorReason.BadSize => ErrorResponse.UserAvatar.BadFormat_BadSize(),
_ =>
throw new Exception(ExceptionUnknownAvatarFormatError)
- };
-
- _logger.LogInformation(e, Log.Format(LogPutUserBadFormat, ("Username", username)));
- return BadRequest(new CommonResponse(code, message));
+ });
}
}
@@ -173,24 +146,24 @@ namespace Timeline.Controllers
{
if (!User.IsAdministrator() && User.Identity.Name != username)
{
- _logger.LogInformation(Log.Format(LogPutUserBadFormat,
+ _logger.LogInformation(Log.Format(LogDeleteForbid,
("Operator Username", User.Identity.Name), ("Username To Delete Avatar", username)));
- return StatusCode(StatusCodes.Status403Forbidden,
- new CommonResponse(ErrorCodes.Http.UserAvatar.Delete.Forbid, ErrorDeleteForbid));
+ return StatusCode(StatusCodes.Status403Forbidden, ErrorResponse.Common.Forbid());
}
+ long id;
try
{
- await _service.SetAvatar(username, null);
-
- _logger.LogInformation(Log.Format(LogDeleteSuccess, ("Username", username)));
- return Ok();
+ id = await _userService.GetUserIdByUsername(username);
}
catch (UserNotExistException e)
{
_logger.LogInformation(e, Log.Format(LogDeleteNotExist, ("Username", username)));
- return BadRequest(new CommonResponse(ErrorCodes.Http.UserAvatar.Delete.UserNotExist, ErrorDeleteUserNotExist));
+ return BadRequest(ErrorResponse.UserCommon.NotExist());
}
+
+ await _service.SetAvatar(id, null);
+ return Ok();
}
}
}
diff --git a/Timeline/Controllers/UserController.cs b/Timeline/Controllers/UserController.cs
index 0d950cd7..a3e8d816 100644
--- a/Timeline/Controllers/UserController.cs
+++ b/Timeline/Controllers/UserController.cs
@@ -1,158 +1,124 @@
+using AutoMapper;
using Microsoft.AspNetCore.Authorization;
+using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
-using System.Globalization;
+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 static Timeline.Resources.Controllers.UserController;
-
-namespace Timeline
-{
- public static partial class ErrorCodes
- {
- public static partial class Http
- {
- public static class User // bbb = 002
- {
- public static class Get // cc = 01
- {
- public const int NotExist = 10020101; // dd = 01
- }
-
- public static class Patch // cc = 03
- {
- public const int NotExist = 10020301; // dd = 01
- }
-
- public static class Op // cc = 1x
- {
- public static class ChangeUsername // cc = 11
- {
- public const int NotExist = 10021101; // dd = 01
- public const int AlreadyExist = 10021102; // dd = 02
- }
-
- public static class ChangePassword // cc = 12
- {
- public const int BadOldPassword = 10021201; // dd = 01
- }
- }
-
- }
- }
- }
-}
+using static Timeline.Resources.Messages;
namespace Timeline.Controllers
{
[ApiController]
public class UserController : Controller
{
-
private readonly ILogger<UserController> _logger;
private readonly IUserService _userService;
+ private readonly IMapper _mapper;
- public UserController(ILogger<UserController> logger, IUserService userService)
+ public UserController(ILogger<UserController> logger, IUserService userService, IMapper mapper)
{
_logger = logger;
_userService = userService;
+ _mapper = mapper;
}
- [HttpGet("users"), AdminAuthorize]
+ private UserInfo ConvertToUserInfo(User user) => _mapper.Map<UserInfo>(user);
+
+ [HttpGet("users")]
public async Task<ActionResult<UserInfo[]>> List()
{
- return Ok(await _userService.ListUsers());
+ var users = await _userService.GetUsers();
+ var result = users.Select(u => ConvertToUserInfo(u)).ToArray();
+ return Ok(result);
}
- [HttpGet("users/{username}"), AdminAuthorize]
+ [HttpGet("users/{username}")]
public async Task<ActionResult<UserInfo>> Get([FromRoute][Username] string username)
{
- var user = await _userService.GetUser(username);
- if (user == null)
+ try
{
- _logger.LogInformation(Log.Format(LogGetUserNotExist, ("Username", username)));
- return NotFound(new CommonResponse(ErrorCodes.Http.User.Get.NotExist, ErrorGetUserNotExist));
+ var user = await _userService.GetUserByUsername(username);
+ return Ok(ConvertToUserInfo(user));
}
- return Ok(user);
- }
-
- [HttpPut("users/{username}"), AdminAuthorize]
- public async Task<ActionResult<CommonPutResponse>> Put([FromBody] UserPutRequest request, [FromRoute][Username] string username)
- {
- var result = await _userService.PutUser(username, request.Password, request.Administrator!.Value);
- switch (result)
+ catch (UserNotExistException e)
{
- case PutResult.Create:
- _logger.LogInformation(Log.Format(LogPutCreate, ("Username", username)));
- return CreatedAtAction("Get", new { username }, CommonPutResponse.Create());
- case PutResult.Modify:
- _logger.LogInformation(Log.Format(LogPutModify, ("Username", username)));
- return Ok(CommonPutResponse.Modify());
- default:
- throw new InvalidBranchException();
+ _logger.LogInformation(e, Log.Format(LogGetUserNotExist, ("Username", username)));
+ return NotFound(ErrorResponse.UserCommon.NotExist());
}
}
- [HttpPatch("users/{username}"), AdminAuthorize]
- public async Task<ActionResult> Patch([FromBody] UserPatchRequest request, [FromRoute][Username] string username)
+ [HttpPatch("users/{username}"), Authorize]
+ public async Task<ActionResult<UserInfo>> Patch([FromBody] UserPatchRequest body, [FromRoute][Username] string username)
{
- try
+ if (this.IsAdministrator())
{
- await _userService.PatchUser(username, request.Password, request.Administrator);
- return Ok();
+ try
+ {
+ var user = await _userService.ModifyUser(username, _mapper.Map<User>(body));
+ return Ok(ConvertToUserInfo(user));
+ }
+ catch (UserNotExistException e)
+ {
+ _logger.LogInformation(e, Log.Format(LogPatchUserNotExist, ("Username", username)));
+ return NotFound(ErrorResponse.UserCommon.NotExist());
+ }
+ catch (ConflictException)
+ {
+ return BadRequest(ErrorResponse.UserController.UsernameConflict());
+ }
}
- catch (UserNotExistException e)
+ else
{
- _logger.LogInformation(e, Log.Format(LogPatchUserNotExist, ("Username", username)));
- return NotFound(new CommonResponse(ErrorCodes.Http.User.Patch.NotExist, ErrorPatchUserNotExist));
+ if (User.Identity.Name != username)
+ return StatusCode(StatusCodes.Status403Forbidden,
+ ErrorResponse.Common.CustomMessage_Forbid(Common_Forbid_NotSelf));
+
+ if (body.Username != null)
+ return StatusCode(StatusCodes.Status403Forbidden,
+ ErrorResponse.Common.CustomMessage_Forbid(UserController_Patch_Forbid_Username));
+
+ if (body.Password != null)
+ return StatusCode(StatusCodes.Status403Forbidden,
+ ErrorResponse.Common.CustomMessage_Forbid(UserController_Patch_Forbid_Password));
+
+ if (body.Administrator != null)
+ return StatusCode(StatusCodes.Status403Forbidden,
+ ErrorResponse.Common.CustomMessage_Forbid(UserController_Patch_Forbid_Administrator));
+
+ var user = await _userService.ModifyUser(this.GetUserId(), _mapper.Map<User>(body));
+ return Ok(ConvertToUserInfo(user));
}
}
[HttpDelete("users/{username}"), AdminAuthorize]
public async Task<ActionResult<CommonDeleteResponse>> Delete([FromRoute][Username] string username)
{
- try
- {
- await _userService.DeleteUser(username);
- _logger.LogInformation(Log.Format(LogDeleteDelete, ("Username", username)));
+ var delete = await _userService.DeleteUser(username);
+ if (delete)
return Ok(CommonDeleteResponse.Delete());
- }
- catch (UserNotExistException e)
- {
- _logger.LogInformation(e, Log.Format(LogDeleteNotExist, ("Username", username)));
+ else
return Ok(CommonDeleteResponse.NotExist());
- }
}
- [HttpPost("userop/changeusername"), AdminAuthorize]
- public async Task<ActionResult> ChangeUsername([FromBody] ChangeUsernameRequest request)
+ [HttpPost("userop/createuser"), AdminAuthorize]
+ public async Task<ActionResult<UserInfo>> CreateUser([FromBody] CreateUserRequest body)
{
try
{
- await _userService.ChangeUsername(request.OldUsername, request.NewUsername);
- _logger.LogInformation(Log.Format(LogChangeUsernameSuccess,
- ("Old Username", request.OldUsername), ("New Username", request.NewUsername)));
- return Ok();
- }
- catch (UserNotExistException e)
- {
- _logger.LogInformation(e, Log.Format(LogChangeUsernameNotExist,
- ("Old Username", request.OldUsername), ("New Username", request.NewUsername)));
- return BadRequest(new CommonResponse(ErrorCodes.Http.User.Op.ChangeUsername.NotExist,
- string.Format(CultureInfo.CurrentCulture, ErrorChangeUsernameNotExist, request.OldUsername)));
+ var user = await _userService.CreateUser(_mapper.Map<User>(body));
+ return Ok(ConvertToUserInfo(user));
}
- catch (UsernameConfictException e)
+ catch (ConflictException)
{
- _logger.LogInformation(e, Log.Format(LogChangeUsernameAlreadyExist,
- ("Old Username", request.OldUsername), ("New Username", request.NewUsername)));
- return BadRequest(new CommonResponse(ErrorCodes.Http.User.Op.ChangeUsername.AlreadyExist, ErrorChangeUsernameAlreadyExist));
+ return BadRequest(ErrorResponse.UserController.UsernameConflict());
}
- // there is no need to catch bad format exception because it is already checked in model validation.
}
[HttpPost("userop/changepassword"), Authorize]
@@ -160,18 +126,16 @@ namespace Timeline.Controllers
{
try
{
- await _userService.ChangePassword(User.Identity.Name!, request.OldPassword, request.NewPassword);
- _logger.LogInformation(Log.Format(LogChangePasswordSuccess, ("Username", User.Identity.Name)));
+ 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(new CommonResponse(ErrorCodes.Http.User.Op.ChangePassword.BadOldPassword,
- ErrorChangePasswordBadPassword));
+ return BadRequest(ErrorResponse.UserController.ChangePassword_BadOldPassword());
}
- // User can't be non-existent or the token is bad.
+ // User can't be non-existent or the token is bad.
}
}
}
diff --git a/Timeline/Controllers/UserDetailController.cs b/Timeline/Controllers/UserDetailController.cs
deleted file mode 100644
index 9de9899e..00000000
--- a/Timeline/Controllers/UserDetailController.cs
+++ /dev/null
@@ -1,49 +0,0 @@
-using Microsoft.AspNetCore.Mvc;
-using System.Threading.Tasks;
-using Timeline.Filters;
-using Timeline.Models.Validation;
-using Timeline.Services;
-using System.ComponentModel.DataAnnotations;
-using Microsoft.AspNetCore.Authorization;
-
-namespace Timeline.Controllers
-{
- [ApiController]
- public class UserDetailController : Controller
- {
- private readonly IUserDetailService _service;
-
- public UserDetailController(IUserDetailService service)
- {
- _service = service;
- }
-
- [HttpGet("users/{username}/nickname")]
- [CatchUserNotExistException]
- public async Task<ActionResult<string>> GetNickname([FromRoute][Username] string username)
- {
- return Ok(await _service.GetNickname(username));
- }
-
- [HttpPut("users/{username}/nickname")]
- [Authorize]
- [SelfOrAdmin]
- [CatchUserNotExistException]
- public async Task<ActionResult> PutNickname([FromRoute][Username] string username,
- [FromBody][StringLength(10, MinimumLength = 1)] string body)
- {
- await _service.SetNickname(username, body);
- return Ok();
- }
-
- [HttpDelete("users/{username}/nickname")]
- [Authorize]
- [SelfOrAdmin]
- [CatchUserNotExistException]
- public async Task<ActionResult> DeleteNickname([FromRoute][Username] string username)
- {
- await _service.SetNickname(username, null);
- return Ok();
- }
- }
-}
diff --git a/Timeline/Entities/DatabaseContext.cs b/Timeline/Entities/DatabaseContext.cs
index ffb6158a..cac33379 100644
--- a/Timeline/Entities/DatabaseContext.cs
+++ b/Timeline/Entities/DatabaseContext.cs
@@ -10,16 +10,14 @@ namespace Timeline.Entities
}
- [System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1062:Validate arguments of public methods")]
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
- modelBuilder.Entity<User>().Property(e => e.Version).HasDefaultValue(0);
- modelBuilder.Entity<User>().HasIndex(e => e.Name).IsUnique();
+ modelBuilder.Entity<UserEntity>().Property(e => e.Version).HasDefaultValue(0);
+ modelBuilder.Entity<UserEntity>().HasIndex(e => e.Username).IsUnique();
}
- public DbSet<User> Users { get; set; } = default!;
- public DbSet<UserAvatar> UserAvatars { get; set; } = default!;
- public DbSet<UserDetail> UserDetails { get; set; } = default!;
+ public DbSet<UserEntity> Users { get; set; } = default!;
+ public DbSet<UserAvatarEntity> UserAvatars { get; set; } = default!;
public DbSet<TimelineEntity> Timelines { get; set; } = default!;
public DbSet<TimelinePostEntity> TimelinePosts { get; set; } = default!;
public DbSet<TimelineMemberEntity> TimelineMembers { get; set; } = default!;
diff --git a/Timeline/Entities/TimelineEntity.cs b/Timeline/Entities/TimelineEntity.cs
index 9cacfcae..c50fe6dd 100644
--- a/Timeline/Entities/TimelineEntity.cs
+++ b/Timeline/Entities/TimelineEntity.cs
@@ -2,7 +2,7 @@
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
-using Timeline.Models;
+using Timeline.Models.Http;
namespace Timeline.Entities
{
@@ -26,7 +26,7 @@ namespace Timeline.Entities
public long OwnerId { get; set; }
[ForeignKey(nameof(OwnerId))]
- public User Owner { get; set; } = default!;
+ public UserEntity Owner { get; set; } = default!;
[Column("visibility")]
public TimelineVisibility Visibility { get; set; }
diff --git a/Timeline/Entities/TimelineMemberEntity.cs b/Timeline/Entities/TimelineMemberEntity.cs
index dbe861bd..e76f2099 100644
--- a/Timeline/Entities/TimelineMemberEntity.cs
+++ b/Timeline/Entities/TimelineMemberEntity.cs
@@ -13,7 +13,7 @@ namespace Timeline.Entities
public long UserId { get; set; }
[ForeignKey(nameof(UserId))]
- public User User { get; set; } = default!;
+ public UserEntity User { get; set; } = default!;
[Column("timeline")]
public long TimelineId { get; set; }
diff --git a/Timeline/Entities/TimelinePostEntity.cs b/Timeline/Entities/TimelinePostEntity.cs
index efef3ab5..a615c61f 100644
--- a/Timeline/Entities/TimelinePostEntity.cs
+++ b/Timeline/Entities/TimelinePostEntity.cs
@@ -20,7 +20,7 @@ namespace Timeline.Entities
public long AuthorId { get; set; }
[ForeignKey(nameof(AuthorId))]
- public User Author { get; set; } = default!;
+ public UserEntity Author { get; set; } = default!;
[Column("content")]
public string? Content { get; set; }
diff --git a/Timeline/Entities/UserAvatar.cs b/Timeline/Entities/UserAvatarEntity.cs
index 114246f3..6cecce1a 100644
--- a/Timeline/Entities/UserAvatar.cs
+++ b/Timeline/Entities/UserAvatarEntity.cs
@@ -6,7 +6,7 @@ namespace Timeline.Entities
{
[System.Diagnostics.CodeAnalysis.SuppressMessage("Performance", "CA1819:Properties should not return arrays", Justification = "This is data base entity.")]
[Table("user_avatars")]
- public class UserAvatar
+ public class UserAvatarEntity
{
[Column("id"), Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public long Id { get; set; }
@@ -23,6 +23,10 @@ namespace Timeline.Entities
[Column("last_modified"), Required]
public DateTime LastModified { get; set; }
+ [Column("user"), Required]
public long UserId { get; set; }
+
+ [ForeignKey(nameof(UserId))]
+ public UserEntity User { get; set; } = default!;
}
}
diff --git a/Timeline/Entities/UserDetail.cs b/Timeline/Entities/UserDetail.cs
deleted file mode 100644
index 45f87e2b..00000000
--- a/Timeline/Entities/UserDetail.cs
+++ /dev/null
@@ -1,21 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.ComponentModel.DataAnnotations;
-using System.ComponentModel.DataAnnotations.Schema;
-using System.Linq;
-using System.Threading.Tasks;
-
-namespace Timeline.Entities
-{
- [Table("user_details")]
- public class UserDetail
- {
- [Column("id"), Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)]
- public long Id { get; set; }
-
- [Column("nickname"), MaxLength(26)]
- public string? Nickname { get; set; }
-
- public long UserId { get; set; }
- }
-}
diff --git a/Timeline/Entities/User.cs b/Timeline/Entities/UserEntity.cs
index e725a69a..946c3fa2 100644
--- a/Timeline/Entities/User.cs
+++ b/Timeline/Entities/UserEntity.cs
@@ -12,26 +12,27 @@ namespace Timeline.Entities
[System.Diagnostics.CodeAnalysis.SuppressMessage("Usage", "CA2227:Collection properties should be read only", Justification = "This is an entity class.")]
[Table("users")]
- public class User
+ public class UserEntity
{
[Column("id"), Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public long Id { get; set; }
- [Column("name"), MaxLength(26), Required]
- public string Name { get; set; } = default!;
+ [Column("username"), MaxLength(26), Required]
+ public string Username { get; set; } = default!;
[Column("password"), Required]
- public string EncryptedPassword { get; set; } = default!;
+ public string Password { get; set; } = default!;
[Column("roles"), Required]
- public string RoleString { get; set; } = default!;
+ public string Roles { get; set; } = default!;
[Column("version"), Required]
public long Version { get; set; }
- public UserAvatar? Avatar { get; set; }
+ [Column("nickname"), MaxLength(100)]
+ public string? Nickname { get; set; }
- public UserDetail? Detail { get; set; }
+ public UserAvatarEntity? Avatar { get; set; }
public List<TimelineEntity> Timelines { get; set; } = default!;
diff --git a/Timeline/ErrorCodes.cs b/Timeline/ErrorCodes.cs
deleted file mode 100644
index c246953b..00000000
--- a/Timeline/ErrorCodes.cs
+++ /dev/null
@@ -1,35 +0,0 @@
-namespace Timeline
-{
- /// <summary>
- /// All error code constants.
- /// </summary>
- /// <remarks>
- /// Scheme:
- /// abbbccdd
- /// </remarks>
- public static partial class ErrorCodes
- {
- public static partial class Http // a = 1
- {
- public static class Common // bbb = 000
- {
- public const int InvalidModel = 10000000;
-
- public static class Header // cc = 0x
- {
- public static class IfNonMatch // cc = 01
- {
- public const int BadFormat = 10000101;
- }
- }
-
- public static class Content // cc = 11
- {
- public const int TooBig = 10001101;
- public const int UnmatchedLength_Smaller = 10001102;
- public const int UnmatchedLength_Bigger = 10001103;
- }
- }
- }
- }
-}
diff --git a/Timeline/Filters/Header.cs b/Timeline/Filters/Header.cs
index f5fb16aa..0db11faf 100644
--- a/Timeline/Filters/Header.cs
+++ b/Timeline/Filters/Header.cs
@@ -1,72 +1,22 @@
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
using Timeline.Models.Http;
-using static Timeline.Resources.Filters;
-
-namespace Timeline
-{
- public static partial class ErrorCodes
- {
- public static partial class Http
- {
- public static partial class Filter // bxx = 1xx
- {
- public static partial class Header // bbb = 100
- {
- public static class ContentType // cc = 01
- {
- public const int Missing = 11000101; // dd = 01
- }
-
- public static class ContentLength // cc = 02
- {
- public const int Missing = 11000201; // dd = 01
- public const int Zero = 11000202; // dd = 02
- }
- }
- }
-
- }
- }
-}
namespace Timeline.Filters
{
public class RequireContentTypeAttribute : ActionFilterAttribute
{
- internal static CommonResponse CreateResponse()
- {
- return new CommonResponse(
- ErrorCodes.Http.Filter.Header.ContentType.Missing,
- MessageHeaderContentTypeMissing);
- }
-
- [System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1062:Validate arguments of public methods")]
public override void OnActionExecuting(ActionExecutingContext context)
{
if (context.HttpContext.Request.ContentType == null)
{
- context.Result = new BadRequestObjectResult(CreateResponse());
+ context.Result = new BadRequestObjectResult(ErrorResponse.Common.Header.ContentType_Missing());
}
}
}
public class RequireContentLengthAttribute : ActionFilterAttribute
{
- internal static CommonResponse CreateMissingResponse()
- {
- return new CommonResponse(
- ErrorCodes.Http.Filter.Header.ContentLength.Missing,
- MessageHeaderContentLengthMissing);
- }
-
- internal static CommonResponse CreateZeroResponse()
- {
- return new CommonResponse(
- ErrorCodes.Http.Filter.Header.ContentLength.Zero,
- MessageHeaderContentLengthZero);
- }
-
public RequireContentLengthAttribute()
: this(true)
{
@@ -80,18 +30,17 @@ namespace Timeline.Filters
public bool RequireNonZero { get; set; }
- [System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1062:Validate arguments of public methods")]
public override void OnActionExecuting(ActionExecutingContext context)
{
if (context.HttpContext.Request.ContentLength == null)
{
- context.Result = new BadRequestObjectResult(CreateMissingResponse());
+ context.Result = new BadRequestObjectResult(ErrorResponse.Common.Header.ContentLength_Missing());
return;
}
if (RequireNonZero && context.HttpContext.Request.ContentLength.Value == 0)
{
- context.Result = new BadRequestObjectResult(CreateZeroResponse());
+ context.Result = new BadRequestObjectResult(ErrorResponse.Common.Header.ContentLength_Zero());
return;
}
}
diff --git a/Timeline/Filters/Timeline.cs b/Timeline/Filters/Timeline.cs
index 7859d409..ed78e645 100644
--- a/Timeline/Filters/Timeline.cs
+++ b/Timeline/Filters/Timeline.cs
@@ -2,44 +2,22 @@
using Microsoft.AspNetCore.Mvc.Filters;
using Timeline.Models.Http;
using Timeline.Services;
-using static Timeline.Resources.Filters;
-
-namespace Timeline
-{
- public static partial class ErrorCodes
- {
- public static partial class Http
- {
- public static partial class Filter // bxx = 1xx
- {
- public static class Timeline // bbb = 102
- {
- public const int UserNotExist = 11020101;
- public const int NameNotExist = 11020102;
- }
- }
- }
- }
-}
namespace Timeline.Filters
{
public class CatchTimelineNotExistExceptionAttribute : ExceptionFilterAttribute
{
- [System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1062:Validate arguments of public methods")]
public override void OnException(ExceptionContext context)
{
if (context.Exception is TimelineNotExistException e)
{
if (e.InnerException is UserNotExistException)
{
- context.Result = new BadRequestObjectResult(
- new CommonResponse(ErrorCodes.Http.Filter.Timeline.UserNotExist, MessageTimelineNotExistUser));
+ context.Result = new NotFoundObjectResult(ErrorResponse.UserCommon.NotExist());
}
else
{
- context.Result = new BadRequestObjectResult(
- new CommonResponse(ErrorCodes.Http.Filter.Timeline.NameNotExist, MessageTimelineNotExist));
+ throw new System.NotImplementedException();
}
}
}
diff --git a/Timeline/Filters/User.cs b/Timeline/Filters/User.cs
deleted file mode 100644
index 16c76750..00000000
--- a/Timeline/Filters/User.cs
+++ /dev/null
@@ -1,88 +0,0 @@
-using Microsoft.AspNetCore.Http;
-using Microsoft.AspNetCore.Mvc;
-using Microsoft.AspNetCore.Mvc.Filters;
-using Microsoft.Extensions.DependencyInjection;
-using Microsoft.Extensions.Logging;
-using System;
-using Timeline.Auth;
-using Timeline.Models.Http;
-using Timeline.Services;
-using static Timeline.Resources.Filters;
-
-namespace Timeline
-{
- public static partial class ErrorCodes
- {
- public static partial class Http
- {
- public static partial class Filter // bxx = 1xx
- {
- public static class User // bbb = 101
- {
- public const int NotExist = 11010101;
-
- public const int NotSelfOrAdminForbid = 11010201;
- }
- }
- }
- }
-}
-
-namespace Timeline.Filters
-{
- public class SelfOrAdminAttribute : ActionFilterAttribute
- {
- [System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1062:Validate arguments of public methods")]
- public override void OnActionExecuting(ActionExecutingContext context)
- {
- var logger = context.HttpContext.RequestServices.GetRequiredService<ILogger<SelfOrAdminAttribute>>();
-
- var user = context.HttpContext.User;
-
- if (user == null)
- {
- logger.LogError(LogSelfOrAdminNoUser);
- return;
- }
-
- if (context.ModelState.TryGetValue("username", out var model))
- {
- if (model.RawValue is string username)
- {
- if (!user.IsAdministrator() && user.Identity.Name != username)
- {
- context.Result = new ObjectResult(
- new CommonResponse(ErrorCodes.Http.Filter.User.NotSelfOrAdminForbid, MessageSelfOrAdminForbid))
- { StatusCode = StatusCodes.Status403Forbidden };
- }
- }
- else
- {
- logger.LogError(LogSelfOrAdminUsernameNotString);
- }
- }
- else
- {
- logger.LogError(LogSelfOrAdminNoUsername);
- }
- }
- }
-
- [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = true, Inherited = true)]
- public class CatchUserNotExistExceptionAttribute : ExceptionFilterAttribute
- {
- [System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1062:Validate arguments of public methods", Justification = "ASP.Net already checked.")]
- public override void OnException(ExceptionContext context)
- {
- if (context.Exception is UserNotExistException)
- {
- var body = new CommonResponse(ErrorCodes.Http.Filter.User.NotExist, MessageUserNotExist);
-
- if (context.HttpContext.Request.Method == "GET")
- context.Result = new NotFoundObjectResult(body);
- else
- context.Result = new BadRequestObjectResult(body);
- }
- }
- }
-}
diff --git a/Timeline/Formatters/StringInputFormatter.cs b/Timeline/Formatters/StringInputFormatter.cs
index 90847e36..b1924268 100644
--- a/Timeline/Formatters/StringInputFormatter.cs
+++ b/Timeline/Formatters/StringInputFormatter.cs
@@ -15,7 +15,6 @@ namespace Timeline.Formatters
SupportedEncodings.Add(Encoding.UTF8);
}
- [System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1062:Validate arguments of public methods")]
public override async Task<InputFormatterResult> ReadRequestBodyAsync(InputFormatterContext context, Encoding effectiveEncoding)
{
var request = context.HttpContext.Request;
diff --git a/Timeline/GlobalSuppressions.cs b/Timeline/GlobalSuppressions.cs
index 076c2005..2b0da576 100644
--- a/Timeline/GlobalSuppressions.cs
+++ b/Timeline/GlobalSuppressions.cs
@@ -7,6 +7,8 @@
[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1034:Nested types should not be visible", Justification = "This is not bad.")]
[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1062:Validate arguments of public methods", Justification = "No need to check the null because it's ASP.Net's duty.", Scope = "namespaceanddescendants", Target = "Timeline.Controllers")]
[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1062:Validate arguments of public methods", Justification = "Migrations code are auto generated.", Scope = "namespaceanddescendants", Target = "Timeline.Migrations")]
-[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Naming", "CA1707:Identifiers should not contain underscores", Justification = "Error code constant identifiers.", Scope = "type", Target = "Timeline.ErrorCodes")]
-[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Naming", "CA1716:Identifiers should not match keywords", Justification = "Error code constant identifiers.", Scope = "type", Target = "Timeline.ErrorCodes")]
-[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Naming", "CA1724:Type names should not match namespaces", Justification = "Error code constant identifiers.", Scope = "type", Target = "Timeline.ErrorCodes")]
+[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Naming", "CA1707:Identifiers should not contain underscores", Justification = "Generated error response identifiers.", Scope = "type", Target = "Timeline.Models.Http.ErrorResponse")]
+[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Naming", "CA1724:Type names should not match namespaces", Justification = "Generated error response identifiers.", Scope = "type", Target = "Timeline.Models.Http.ErrorResponse")]
+[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Globalization", "CA1305:Specify IFormatProvider", Justification = "Generated error response.", Scope = "type", Target = "Timeline.Models.Http.ErrorResponse")]
+[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1056:Uri properties should not be strings", Justification = "That's unnecessary.")]
+[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1062:Validate arguments of public methods", Justification = "Adundant")]
diff --git a/Timeline/Helpers/InvalidModelResponseFactory.cs b/Timeline/Helpers/InvalidModelResponseFactory.cs
index 643c99ac..9b253e7d 100644
--- a/Timeline/Helpers/InvalidModelResponseFactory.cs
+++ b/Timeline/Helpers/InvalidModelResponseFactory.cs
@@ -6,7 +6,6 @@ namespace Timeline.Helpers
{
public static class InvalidModelResponseFactory
{
- [System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1062:Validate arguments of public methods")]
public static IActionResult Factory(ActionContext context)
{
var modelState = context.ModelState;
@@ -20,7 +19,7 @@ namespace Timeline.Helpers
messageBuilder.AppendLine(error.ErrorMessage);
}
- return new BadRequestObjectResult(CommonResponse.InvalidModel(messageBuilder.ToString()));
+ return new BadRequestObjectResult(ErrorResponse.Common.CustomMessage_InvalidModel(messageBuilder.ToString()));
}
}
}
diff --git a/Timeline/InvalidBranchException.cs b/Timeline/InvalidBranchException.cs
deleted file mode 100644
index 32937c5d..00000000
--- a/Timeline/InvalidBranchException.cs
+++ /dev/null
@@ -1,16 +0,0 @@
-using System;
-
-namespace Timeline
-{
-
- [Serializable]
- public class InvalidBranchException : Exception
- {
- public InvalidBranchException() : base(Resources.Common.ExceptionInvalidBranch) { }
- public InvalidBranchException(string message) : base(message) { }
- public InvalidBranchException(string message, Exception inner) : base(message, inner) { }
- protected InvalidBranchException(
- System.Runtime.Serialization.SerializationInfo info,
- System.Runtime.Serialization.StreamingContext context) : base(info, context) { }
- }
-}
diff --git a/Timeline/Migrations/DevelopmentDatabase/20200131100517_RefactorUser.Designer.cs b/Timeline/Migrations/DevelopmentDatabase/20200131100517_RefactorUser.Designer.cs
new file mode 100644
index 00000000..13e322c8
--- /dev/null
+++ b/Timeline/Migrations/DevelopmentDatabase/20200131100517_RefactorUser.Designer.cs
@@ -0,0 +1,243 @@
+// <auto-generated />
+using System;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.EntityFrameworkCore.Infrastructure;
+using Microsoft.EntityFrameworkCore.Migrations;
+using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
+using Timeline.Entities;
+
+namespace Timeline.Migrations.DevelopmentDatabase
+{
+ [DbContext(typeof(DevelopmentDatabaseContext))]
+ [Migration("20200131100517_RefactorUser")]
+ partial class RefactorUser
+ {
+ protected override void BuildTargetModel(ModelBuilder modelBuilder)
+ {
+#pragma warning disable 612, 618
+ modelBuilder
+ .HasAnnotation("ProductVersion", "3.1.1");
+
+ modelBuilder.Entity("Timeline.Entities.TimelineEntity", b =>
+ {
+ b.Property<long>("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnName("id")
+ .HasColumnType("INTEGER");
+
+ b.Property<DateTime>("CreateTime")
+ .HasColumnName("create_time")
+ .HasColumnType("TEXT");
+
+ b.Property<string>("Description")
+ .HasColumnName("description")
+ .HasColumnType("TEXT");
+
+ b.Property<string>("Name")
+ .HasColumnName("name")
+ .HasColumnType("TEXT");
+
+ b.Property<long>("OwnerId")
+ .HasColumnName("owner")
+ .HasColumnType("INTEGER");
+
+ b.Property<int>("Visibility")
+ .HasColumnName("visibility")
+ .HasColumnType("INTEGER");
+
+ b.HasKey("Id");
+
+ b.HasIndex("OwnerId");
+
+ b.ToTable("timelines");
+ });
+
+ modelBuilder.Entity("Timeline.Entities.TimelineMemberEntity", b =>
+ {
+ b.Property<long>("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnName("id")
+ .HasColumnType("INTEGER");
+
+ b.Property<long>("TimelineId")
+ .HasColumnName("timeline")
+ .HasColumnType("INTEGER");
+
+ b.Property<long>("UserId")
+ .HasColumnName("user")
+ .HasColumnType("INTEGER");
+
+ b.HasKey("Id");
+
+ b.HasIndex("TimelineId");
+
+ b.HasIndex("UserId");
+
+ b.ToTable("timeline_members");
+ });
+
+ modelBuilder.Entity("Timeline.Entities.TimelinePostEntity", b =>
+ {
+ b.Property<long>("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnName("id")
+ .HasColumnType("INTEGER");
+
+ b.Property<long>("AuthorId")
+ .HasColumnName("author")
+ .HasColumnType("INTEGER");
+
+ b.Property<string>("Content")
+ .HasColumnName("content")
+ .HasColumnType("TEXT");
+
+ b.Property<DateTime>("LastUpdated")
+ .HasColumnName("last_updated")
+ .HasColumnType("TEXT");
+
+ b.Property<DateTime>("Time")
+ .HasColumnName("time")
+ .HasColumnType("TEXT");
+
+ b.Property<long>("TimelineId")
+ .HasColumnName("timeline")
+ .HasColumnType("INTEGER");
+
+ b.HasKey("Id");
+
+ b.HasIndex("AuthorId");
+
+ b.HasIndex("TimelineId");
+
+ b.ToTable("timeline_posts");
+ });
+
+ modelBuilder.Entity("Timeline.Entities.UserAvatarEntity", b =>
+ {
+ b.Property<long>("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnName("id")
+ .HasColumnType("INTEGER");
+
+ b.Property<byte[]>("Data")
+ .HasColumnName("data")
+ .HasColumnType("BLOB");
+
+ b.Property<string>("ETag")
+ .HasColumnName("etag")
+ .HasColumnType("TEXT")
+ .HasMaxLength(30);
+
+ b.Property<DateTime>("LastModified")
+ .HasColumnName("last_modified")
+ .HasColumnType("TEXT");
+
+ b.Property<string>("Type")
+ .HasColumnName("type")
+ .HasColumnType("TEXT");
+
+ b.Property<long>("UserId")
+ .HasColumnName("user")
+ .HasColumnType("INTEGER");
+
+ b.HasKey("Id");
+
+ b.HasIndex("UserId")
+ .IsUnique();
+
+ b.ToTable("user_avatars");
+ });
+
+ modelBuilder.Entity("Timeline.Entities.UserEntity", b =>
+ {
+ b.Property<long>("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnName("id")
+ .HasColumnType("INTEGER");
+
+ b.Property<string>("Nickname")
+ .HasColumnName("nickname")
+ .HasColumnType("TEXT")
+ .HasMaxLength(100);
+
+ b.Property<string>("Password")
+ .IsRequired()
+ .HasColumnName("password")
+ .HasColumnType("TEXT");
+
+ b.Property<string>("Roles")
+ .IsRequired()
+ .HasColumnName("roles")
+ .HasColumnType("TEXT");
+
+ b.Property<string>("Username")
+ .IsRequired()
+ .HasColumnName("username")
+ .HasColumnType("TEXT")
+ .HasMaxLength(26);
+
+ b.Property<long>("Version")
+ .ValueGeneratedOnAdd()
+ .HasColumnName("version")
+ .HasColumnType("INTEGER")
+ .HasDefaultValue(0L);
+
+ b.HasKey("Id");
+
+ b.HasIndex("Username")
+ .IsUnique();
+
+ b.ToTable("users");
+ });
+
+ modelBuilder.Entity("Timeline.Entities.TimelineEntity", b =>
+ {
+ b.HasOne("Timeline.Entities.UserEntity", "Owner")
+ .WithMany("Timelines")
+ .HasForeignKey("OwnerId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+ });
+
+ modelBuilder.Entity("Timeline.Entities.TimelineMemberEntity", b =>
+ {
+ b.HasOne("Timeline.Entities.TimelineEntity", "Timeline")
+ .WithMany("Members")
+ .HasForeignKey("TimelineId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+
+ b.HasOne("Timeline.Entities.UserEntity", "User")
+ .WithMany("TimelinesJoined")
+ .HasForeignKey("UserId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+ });
+
+ modelBuilder.Entity("Timeline.Entities.TimelinePostEntity", b =>
+ {
+ b.HasOne("Timeline.Entities.UserEntity", "Author")
+ .WithMany("TimelinePosts")
+ .HasForeignKey("AuthorId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+
+ b.HasOne("Timeline.Entities.TimelineEntity", "Timeline")
+ .WithMany("Posts")
+ .HasForeignKey("TimelineId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+ });
+
+ modelBuilder.Entity("Timeline.Entities.UserAvatarEntity", b =>
+ {
+ b.HasOne("Timeline.Entities.UserEntity", "User")
+ .WithOne("Avatar")
+ .HasForeignKey("Timeline.Entities.UserAvatarEntity", "UserId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+ });
+#pragma warning restore 612, 618
+ }
+ }
+}
diff --git a/Timeline/Migrations/DevelopmentDatabase/20200131100517_RefactorUser.cs b/Timeline/Migrations/DevelopmentDatabase/20200131100517_RefactorUser.cs
new file mode 100644
index 00000000..ade65eb1
--- /dev/null
+++ b/Timeline/Migrations/DevelopmentDatabase/20200131100517_RefactorUser.cs
@@ -0,0 +1,129 @@
+using Microsoft.EntityFrameworkCore.Migrations;
+
+namespace Timeline.Migrations.DevelopmentDatabase
+{
+ public partial class RefactorUser : Migration
+ {
+ protected override void Up(MigrationBuilder migrationBuilder)
+ {
+ migrationBuilder.RenameColumn(name: "name", table: "users", newName: "username");
+ migrationBuilder.RenameIndex(name: "IX_users_name", table: "users", newName: "IX_users_username");
+
+ migrationBuilder.AddColumn<string>(
+ name: "nickname",
+ table: "users",
+ maxLength: 100,
+ nullable: true);
+
+ migrationBuilder.Sql(@"
+UPDATE users
+ SET nickname = (
+ SELECT nickname
+ FROM user_details
+ WHERE user_details.UserId = users.id
+ );
+ ");
+
+ /*
+ migrationBuilder.RenameColumn(name: "UserId", table: "user_avatars", newName: "user");
+
+ migrationBuilder.DropForeignKey(
+ name: "FK_user_avatars_users_UserId",
+ table: "user_avatars");
+
+ migrationBuilder.AddForeignKey(
+ name: "FK_user_avatars_users_user",
+ table: "user_avatars",
+ column: "user",
+ principalTable: "users",
+ principalColumn: "id",
+ onDelete: ReferentialAction.Cascade);
+
+ migrationBuilder.RenameIndex(
+ name: "IX_user_avatars_UserId",
+ table: "user_avatars",
+ newName: "IX_user_avatars_user");
+ */
+
+ migrationBuilder.Sql(@"
+CREATE TABLE user_avatars_backup (
+ id INTEGER NOT NULL
+ CONSTRAINT PK_user_avatars PRIMARY KEY AUTOINCREMENT,
+ data BLOB,
+ type TEXT,
+ etag TEXT,
+ last_modified TEXT NOT NULL,
+ user INTEGER NOT NULL,
+ CONSTRAINT FK_user_avatars_users_user FOREIGN KEY (
+ user
+ )
+ REFERENCES users (id) ON DELETE CASCADE
+);
+
+INSERT INTO user_avatars_backup (id, data, type, etag, last_modified, user)
+ SELECT id, data, type, etag, last_modified, UserId FROM user_avatars;
+
+DROP TABLE user_avatars;
+
+ALTER TABLE user_avatars_backup
+ RENAME TO user_avatars;
+
+CREATE UNIQUE INDEX IX_user_avatars_user ON user_avatars (user);
+ ");
+
+ // migrationBuilder.DropTable(name: "user_details");
+
+ }
+
+ protected override void Down(MigrationBuilder migrationBuilder)
+ {
+ migrationBuilder.Sql(@"
+CREATE TABLE user_avatars_backup (
+ id INTEGER NOT NULL
+ CONSTRAINT PK_user_avatars PRIMARY KEY AUTOINCREMENT,
+ data BLOB,
+ type TEXT,
+ etag TEXT,
+ last_modified TEXT NOT NULL,
+ UserId INTEGER NOT NULL,
+ CONSTRAINT FK_user_avatars_users_UserId FOREIGN KEY (
+ user
+ )
+ REFERENCES users (id) ON DELETE CASCADE
+);
+
+INSERT INTO user_avatars_backup (id, data, type, etag, last_modified, UserId)
+ SELECT id, data, type, etag, last_modified, user FROM user_avatars;
+
+DROP TABLE user_avatars;
+
+ALTER TABLE user_avatars_backup
+ RENAME TO user_avatars;
+
+CREATE UNIQUE INDEX IX_user_avatars_UserId ON user_avatars (UserId);
+ ");
+
+ migrationBuilder.Sql(@"
+CREATE TABLE users_backup (
+ id INTEGER NOT NULL
+ CONSTRAINT PK_users PRIMARY KEY AUTOINCREMENT,
+ name TEXT NOT NULL,
+ password TEXT NOT NULL,
+ roles TEXT NOT NULL,
+ version INTEGER NOT NULL
+ DEFAULT 0
+);
+
+INSERT INTO users_backup (id, name, password, roles, version)
+ SELECT id, username, password, roles, version FROM users;
+
+DROP TABLE users;
+
+ALTER TABLE users_backup
+ RENAME TO users;
+
+CREATE UNIQUE INDEX IX_users_name ON users (name);
+ ");
+ }
+ }
+}
diff --git a/Timeline/Migrations/DevelopmentDatabase/DevelopmentDatabaseContextModelSnapshot.cs b/Timeline/Migrations/DevelopmentDatabase/DevelopmentDatabaseContextModelSnapshot.cs
index 6fbaea5f..5da49dbe 100644
--- a/Timeline/Migrations/DevelopmentDatabase/DevelopmentDatabaseContextModelSnapshot.cs
+++ b/Timeline/Migrations/DevelopmentDatabase/DevelopmentDatabaseContextModelSnapshot.cs
@@ -14,7 +14,7 @@ namespace Timeline.Migrations.DevelopmentDatabase
{
#pragma warning disable 612, 618
modelBuilder
- .HasAnnotation("ProductVersion", "3.1.0");
+ .HasAnnotation("ProductVersion", "3.1.1");
modelBuilder.Entity("Timeline.Entities.TimelineEntity", b =>
{
@@ -110,44 +110,7 @@ namespace Timeline.Migrations.DevelopmentDatabase
b.ToTable("timeline_posts");
});
- modelBuilder.Entity("Timeline.Entities.User", b =>
- {
- b.Property<long>("Id")
- .ValueGeneratedOnAdd()
- .HasColumnName("id")
- .HasColumnType("INTEGER");
-
- b.Property<string>("EncryptedPassword")
- .IsRequired()
- .HasColumnName("password")
- .HasColumnType("TEXT");
-
- b.Property<string>("Name")
- .IsRequired()
- .HasColumnName("name")
- .HasColumnType("TEXT")
- .HasMaxLength(26);
-
- b.Property<string>("RoleString")
- .IsRequired()
- .HasColumnName("roles")
- .HasColumnType("TEXT");
-
- b.Property<long>("Version")
- .ValueGeneratedOnAdd()
- .HasColumnName("version")
- .HasColumnType("INTEGER")
- .HasDefaultValue(0L);
-
- b.HasKey("Id");
-
- b.HasIndex("Name")
- .IsUnique();
-
- b.ToTable("users");
- });
-
- modelBuilder.Entity("Timeline.Entities.UserAvatar", b =>
+ modelBuilder.Entity("Timeline.Entities.UserAvatarEntity", b =>
{
b.Property<long>("Id")
.ValueGeneratedOnAdd()
@@ -172,6 +135,7 @@ namespace Timeline.Migrations.DevelopmentDatabase
.HasColumnType("TEXT");
b.Property<long>("UserId")
+ .HasColumnName("user")
.HasColumnType("INTEGER");
b.HasKey("Id");
@@ -182,7 +146,7 @@ namespace Timeline.Migrations.DevelopmentDatabase
b.ToTable("user_avatars");
});
- modelBuilder.Entity("Timeline.Entities.UserDetail", b =>
+ modelBuilder.Entity("Timeline.Entities.UserEntity", b =>
{
b.Property<long>("Id")
.ValueGeneratedOnAdd()
@@ -192,22 +156,41 @@ namespace Timeline.Migrations.DevelopmentDatabase
b.Property<string>("Nickname")
.HasColumnName("nickname")
.HasColumnType("TEXT")
+ .HasMaxLength(100);
+
+ b.Property<string>("Password")
+ .IsRequired()
+ .HasColumnName("password")
+ .HasColumnType("TEXT");
+
+ b.Property<string>("Roles")
+ .IsRequired()
+ .HasColumnName("roles")
+ .HasColumnType("TEXT");
+
+ b.Property<string>("Username")
+ .IsRequired()
+ .HasColumnName("username")
+ .HasColumnType("TEXT")
.HasMaxLength(26);
- b.Property<long>("UserId")
- .HasColumnType("INTEGER");
+ b.Property<long>("Version")
+ .ValueGeneratedOnAdd()
+ .HasColumnName("version")
+ .HasColumnType("INTEGER")
+ .HasDefaultValue(0L);
b.HasKey("Id");
- b.HasIndex("UserId")
+ b.HasIndex("Username")
.IsUnique();
- b.ToTable("user_details");
+ b.ToTable("users");
});
modelBuilder.Entity("Timeline.Entities.TimelineEntity", b =>
{
- b.HasOne("Timeline.Entities.User", "Owner")
+ b.HasOne("Timeline.Entities.UserEntity", "Owner")
.WithMany("Timelines")
.HasForeignKey("OwnerId")
.OnDelete(DeleteBehavior.Cascade)
@@ -222,7 +205,7 @@ namespace Timeline.Migrations.DevelopmentDatabase
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
- b.HasOne("Timeline.Entities.User", "User")
+ b.HasOne("Timeline.Entities.UserEntity", "User")
.WithMany("TimelinesJoined")
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
@@ -231,7 +214,7 @@ namespace Timeline.Migrations.DevelopmentDatabase
modelBuilder.Entity("Timeline.Entities.TimelinePostEntity", b =>
{
- b.HasOne("Timeline.Entities.User", "Author")
+ b.HasOne("Timeline.Entities.UserEntity", "Author")
.WithMany("TimelinePosts")
.HasForeignKey("AuthorId")
.OnDelete(DeleteBehavior.Cascade)
@@ -244,20 +227,11 @@ namespace Timeline.Migrations.DevelopmentDatabase
.IsRequired();
});
- modelBuilder.Entity("Timeline.Entities.UserAvatar", b =>
+ modelBuilder.Entity("Timeline.Entities.UserAvatarEntity", b =>
{
- b.HasOne("Timeline.Entities.User", null)
+ b.HasOne("Timeline.Entities.UserEntity", "User")
.WithOne("Avatar")
- .HasForeignKey("Timeline.Entities.UserAvatar", "UserId")
- .OnDelete(DeleteBehavior.Cascade)
- .IsRequired();
- });
-
- modelBuilder.Entity("Timeline.Entities.UserDetail", b =>
- {
- b.HasOne("Timeline.Entities.User", null)
- .WithOne("Detail")
- .HasForeignKey("Timeline.Entities.UserDetail", "UserId")
+ .HasForeignKey("Timeline.Entities.UserAvatarEntity", "UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
diff --git a/Timeline/Migrations/ProductionDatabase/20200131152033_RefactorUser.Designer.cs b/Timeline/Migrations/ProductionDatabase/20200131152033_RefactorUser.Designer.cs
new file mode 100644
index 00000000..bb0bb5af
--- /dev/null
+++ b/Timeline/Migrations/ProductionDatabase/20200131152033_RefactorUser.Designer.cs
@@ -0,0 +1,244 @@
+// <auto-generated />
+using System;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.EntityFrameworkCore.Infrastructure;
+using Microsoft.EntityFrameworkCore.Migrations;
+using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
+using Timeline.Entities;
+
+namespace Timeline.Migrations.ProductionDatabase
+{
+ [DbContext(typeof(ProductionDatabaseContext))]
+ [Migration("20200131152033_RefactorUser")]
+ partial class RefactorUser
+ {
+ protected override void BuildTargetModel(ModelBuilder modelBuilder)
+ {
+#pragma warning disable 612, 618
+ modelBuilder
+ .HasAnnotation("ProductVersion", "3.1.1")
+ .HasAnnotation("Relational:MaxIdentifierLength", 64);
+
+ modelBuilder.Entity("Timeline.Entities.TimelineEntity", b =>
+ {
+ b.Property<long>("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnName("id")
+ .HasColumnType("bigint");
+
+ b.Property<DateTime>("CreateTime")
+ .HasColumnName("create_time")
+ .HasColumnType("datetime(6)");
+
+ b.Property<string>("Description")
+ .HasColumnName("description")
+ .HasColumnType("longtext CHARACTER SET utf8mb4");
+
+ b.Property<string>("Name")
+ .HasColumnName("name")
+ .HasColumnType("longtext CHARACTER SET utf8mb4");
+
+ b.Property<long>("OwnerId")
+ .HasColumnName("owner")
+ .HasColumnType("bigint");
+
+ b.Property<int>("Visibility")
+ .HasColumnName("visibility")
+ .HasColumnType("int");
+
+ b.HasKey("Id");
+
+ b.HasIndex("OwnerId");
+
+ b.ToTable("timelines");
+ });
+
+ modelBuilder.Entity("Timeline.Entities.TimelineMemberEntity", b =>
+ {
+ b.Property<long>("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnName("id")
+ .HasColumnType("bigint");
+
+ b.Property<long>("TimelineId")
+ .HasColumnName("timeline")
+ .HasColumnType("bigint");
+
+ b.Property<long>("UserId")
+ .HasColumnName("user")
+ .HasColumnType("bigint");
+
+ b.HasKey("Id");
+
+ b.HasIndex("TimelineId");
+
+ b.HasIndex("UserId");
+
+ b.ToTable("timeline_members");
+ });
+
+ modelBuilder.Entity("Timeline.Entities.TimelinePostEntity", b =>
+ {
+ b.Property<long>("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnName("id")
+ .HasColumnType("bigint");
+
+ b.Property<long>("AuthorId")
+ .HasColumnName("author")
+ .HasColumnType("bigint");
+
+ b.Property<string>("Content")
+ .HasColumnName("content")
+ .HasColumnType("longtext CHARACTER SET utf8mb4");
+
+ b.Property<DateTime>("LastUpdated")
+ .HasColumnName("last_updated")
+ .HasColumnType("datetime(6)");
+
+ b.Property<DateTime>("Time")
+ .HasColumnName("time")
+ .HasColumnType("datetime(6)");
+
+ b.Property<long>("TimelineId")
+ .HasColumnName("timeline")
+ .HasColumnType("bigint");
+
+ b.HasKey("Id");
+
+ b.HasIndex("AuthorId");
+
+ b.HasIndex("TimelineId");
+
+ b.ToTable("timeline_posts");
+ });
+
+ modelBuilder.Entity("Timeline.Entities.UserAvatarEntity", b =>
+ {
+ b.Property<long>("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnName("id")
+ .HasColumnType("bigint");
+
+ b.Property<byte[]>("Data")
+ .HasColumnName("data")
+ .HasColumnType("longblob");
+
+ b.Property<string>("ETag")
+ .HasColumnName("etag")
+ .HasColumnType("varchar(30) CHARACTER SET utf8mb4")
+ .HasMaxLength(30);
+
+ b.Property<DateTime>("LastModified")
+ .HasColumnName("last_modified")
+ .HasColumnType("datetime(6)");
+
+ b.Property<string>("Type")
+ .HasColumnName("type")
+ .HasColumnType("longtext CHARACTER SET utf8mb4");
+
+ b.Property<long>("UserId")
+ .HasColumnName("user")
+ .HasColumnType("bigint");
+
+ b.HasKey("Id");
+
+ b.HasIndex("UserId")
+ .IsUnique();
+
+ b.ToTable("user_avatars");
+ });
+
+ modelBuilder.Entity("Timeline.Entities.UserEntity", b =>
+ {
+ b.Property<long>("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnName("id")
+ .HasColumnType("bigint");
+
+ b.Property<string>("Nickname")
+ .HasColumnName("nickname")
+ .HasColumnType("varchar(100) CHARACTER SET utf8mb4")
+ .HasMaxLength(100);
+
+ b.Property<string>("Password")
+ .IsRequired()
+ .HasColumnName("password")
+ .HasColumnType("longtext CHARACTER SET utf8mb4");
+
+ b.Property<string>("Roles")
+ .IsRequired()
+ .HasColumnName("roles")
+ .HasColumnType("longtext CHARACTER SET utf8mb4");
+
+ b.Property<string>("Username")
+ .IsRequired()
+ .HasColumnName("username")
+ .HasColumnType("varchar(26) CHARACTER SET utf8mb4")
+ .HasMaxLength(26);
+
+ b.Property<long>("Version")
+ .ValueGeneratedOnAdd()
+ .HasColumnName("version")
+ .HasColumnType("bigint")
+ .HasDefaultValue(0L);
+
+ b.HasKey("Id");
+
+ b.HasIndex("Username")
+ .IsUnique();
+
+ b.ToTable("users");
+ });
+
+ modelBuilder.Entity("Timeline.Entities.TimelineEntity", b =>
+ {
+ b.HasOne("Timeline.Entities.UserEntity", "Owner")
+ .WithMany("Timelines")
+ .HasForeignKey("OwnerId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+ });
+
+ modelBuilder.Entity("Timeline.Entities.TimelineMemberEntity", b =>
+ {
+ b.HasOne("Timeline.Entities.TimelineEntity", "Timeline")
+ .WithMany("Members")
+ .HasForeignKey("TimelineId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+
+ b.HasOne("Timeline.Entities.UserEntity", "User")
+ .WithMany("TimelinesJoined")
+ .HasForeignKey("UserId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+ });
+
+ modelBuilder.Entity("Timeline.Entities.TimelinePostEntity", b =>
+ {
+ b.HasOne("Timeline.Entities.UserEntity", "Author")
+ .WithMany("TimelinePosts")
+ .HasForeignKey("AuthorId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+
+ b.HasOne("Timeline.Entities.TimelineEntity", "Timeline")
+ .WithMany("Posts")
+ .HasForeignKey("TimelineId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+ });
+
+ modelBuilder.Entity("Timeline.Entities.UserAvatarEntity", b =>
+ {
+ b.HasOne("Timeline.Entities.UserEntity", "User")
+ .WithOne("Avatar")
+ .HasForeignKey("Timeline.Entities.UserAvatarEntity", "UserId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+ });
+#pragma warning restore 612, 618
+ }
+ }
+}
diff --git a/Timeline/Migrations/ProductionDatabase/20200131152033_RefactorUser.cs b/Timeline/Migrations/ProductionDatabase/20200131152033_RefactorUser.cs
new file mode 100644
index 00000000..a0ca5212
--- /dev/null
+++ b/Timeline/Migrations/ProductionDatabase/20200131152033_RefactorUser.cs
@@ -0,0 +1,55 @@
+using Microsoft.EntityFrameworkCore.Metadata;
+using Microsoft.EntityFrameworkCore.Migrations;
+
+namespace Timeline.Migrations.ProductionDatabase
+{
+ public partial class RefactorUser : Migration
+ {
+ protected override void Up(MigrationBuilder migrationBuilder)
+ {
+ migrationBuilder.Sql(@"
+START TRANSACTION;
+
+ALTER TABLE `users`
+ CHANGE COLUMN `name` `username` varchar (26) NOT NULL,
+ RENAME INDEX IX_users_name TO IX_users_username,
+ ADD `nickname` varchar(100) CHARACTER SET utf8mb4 NULL;
+
+UPDATE users
+ SET nickname = (
+ SELECT nickname
+ FROM user_details
+ WHERE user_details.UserId = users.id
+ );
+
+ALTER TABLE `user_avatars`
+ CHANGE COLUMN `UserId` `user` bigint (20) NOT NULL,
+ RENAME INDEX IX_user_avatars_UserId TO IX_user_avatars_user,
+ DROP FOREIGN KEY FK_user_avatars_users_UserId,
+ ADD CONSTRAINT FK_user_avatars_users_user FOREIGN KEY (`user`) REFERENCES `users` (`id`) ON DELETE CASCADE;
+
+COMMIT;
+ ");
+ }
+
+ protected override void Down(MigrationBuilder migrationBuilder)
+ {
+ migrationBuilder.Sql(@"
+START TRANSACTION;
+
+ALTER TABLE `users`
+ CHANGE COLUMN `username` `name` varchar (26) NOT NULL,
+ RENAME INDEX IX_users_username TO IX_users_name,
+ DROP COLUMN `nickname`;
+
+ALTER TABLE `user_avatars`
+ CHANGE COLUMN `user` `UserId` bigint (20) NOT NULL,
+ RENAME INDEX IX_user_avatars_user TO IX_user_avatars_UserId,
+ DROP FOREIGN KEY FK_user_avatars_users_user,
+ ADD CONSTRAINT FK_user_avatars_users_UserId FOREIGN KEY (`UserId`) REFERENCES `users` (`id`) ON DELETE CASCADE;
+
+COMMIT;
+ ");
+ }
+ }
+}
diff --git a/Timeline/Migrations/ProductionDatabase/ProductionDatabaseContextModelSnapshot.cs b/Timeline/Migrations/ProductionDatabase/ProductionDatabaseContextModelSnapshot.cs
index 67ef52a4..bfc9b768 100644
--- a/Timeline/Migrations/ProductionDatabase/ProductionDatabaseContextModelSnapshot.cs
+++ b/Timeline/Migrations/ProductionDatabase/ProductionDatabaseContextModelSnapshot.cs
@@ -14,7 +14,7 @@ namespace Timeline.Migrations.ProductionDatabase
{
#pragma warning disable 612, 618
modelBuilder
- .HasAnnotation("ProductVersion", "3.1.0")
+ .HasAnnotation("ProductVersion", "3.1.1")
.HasAnnotation("Relational:MaxIdentifierLength", 64);
modelBuilder.Entity("Timeline.Entities.TimelineEntity", b =>
@@ -111,44 +111,7 @@ namespace Timeline.Migrations.ProductionDatabase
b.ToTable("timeline_posts");
});
- modelBuilder.Entity("Timeline.Entities.User", b =>
- {
- b.Property<long>("Id")
- .ValueGeneratedOnAdd()
- .HasColumnName("id")
- .HasColumnType("bigint");
-
- b.Property<string>("EncryptedPassword")
- .IsRequired()
- .HasColumnName("password")
- .HasColumnType("longtext CHARACTER SET utf8mb4");
-
- b.Property<string>("Name")
- .IsRequired()
- .HasColumnName("name")
- .HasColumnType("varchar(26) CHARACTER SET utf8mb4")
- .HasMaxLength(26);
-
- b.Property<string>("RoleString")
- .IsRequired()
- .HasColumnName("roles")
- .HasColumnType("longtext CHARACTER SET utf8mb4");
-
- b.Property<long>("Version")
- .ValueGeneratedOnAdd()
- .HasColumnName("version")
- .HasColumnType("bigint")
- .HasDefaultValue(0L);
-
- b.HasKey("Id");
-
- b.HasIndex("Name")
- .IsUnique();
-
- b.ToTable("users");
- });
-
- modelBuilder.Entity("Timeline.Entities.UserAvatar", b =>
+ modelBuilder.Entity("Timeline.Entities.UserAvatarEntity", b =>
{
b.Property<long>("Id")
.ValueGeneratedOnAdd()
@@ -173,6 +136,7 @@ namespace Timeline.Migrations.ProductionDatabase
.HasColumnType("longtext CHARACTER SET utf8mb4");
b.Property<long>("UserId")
+ .HasColumnName("user")
.HasColumnType("bigint");
b.HasKey("Id");
@@ -183,7 +147,7 @@ namespace Timeline.Migrations.ProductionDatabase
b.ToTable("user_avatars");
});
- modelBuilder.Entity("Timeline.Entities.UserDetail", b =>
+ modelBuilder.Entity("Timeline.Entities.UserEntity", b =>
{
b.Property<long>("Id")
.ValueGeneratedOnAdd()
@@ -192,23 +156,42 @@ namespace Timeline.Migrations.ProductionDatabase
b.Property<string>("Nickname")
.HasColumnName("nickname")
+ .HasColumnType("varchar(100) CHARACTER SET utf8mb4")
+ .HasMaxLength(100);
+
+ b.Property<string>("Password")
+ .IsRequired()
+ .HasColumnName("password")
+ .HasColumnType("longtext CHARACTER SET utf8mb4");
+
+ b.Property<string>("Roles")
+ .IsRequired()
+ .HasColumnName("roles")
+ .HasColumnType("longtext CHARACTER SET utf8mb4");
+
+ b.Property<string>("Username")
+ .IsRequired()
+ .HasColumnName("username")
.HasColumnType("varchar(26) CHARACTER SET utf8mb4")
.HasMaxLength(26);
- b.Property<long>("UserId")
- .HasColumnType("bigint");
+ b.Property<long>("Version")
+ .ValueGeneratedOnAdd()
+ .HasColumnName("version")
+ .HasColumnType("bigint")
+ .HasDefaultValue(0L);
b.HasKey("Id");
- b.HasIndex("UserId")
+ b.HasIndex("Username")
.IsUnique();
- b.ToTable("user_details");
+ b.ToTable("users");
});
modelBuilder.Entity("Timeline.Entities.TimelineEntity", b =>
{
- b.HasOne("Timeline.Entities.User", "Owner")
+ b.HasOne("Timeline.Entities.UserEntity", "Owner")
.WithMany("Timelines")
.HasForeignKey("OwnerId")
.OnDelete(DeleteBehavior.Cascade)
@@ -223,7 +206,7 @@ namespace Timeline.Migrations.ProductionDatabase
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
- b.HasOne("Timeline.Entities.User", "User")
+ b.HasOne("Timeline.Entities.UserEntity", "User")
.WithMany("TimelinesJoined")
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
@@ -232,7 +215,7 @@ namespace Timeline.Migrations.ProductionDatabase
modelBuilder.Entity("Timeline.Entities.TimelinePostEntity", b =>
{
- b.HasOne("Timeline.Entities.User", "Author")
+ b.HasOne("Timeline.Entities.UserEntity", "Author")
.WithMany("TimelinePosts")
.HasForeignKey("AuthorId")
.OnDelete(DeleteBehavior.Cascade)
@@ -245,20 +228,11 @@ namespace Timeline.Migrations.ProductionDatabase
.IsRequired();
});
- modelBuilder.Entity("Timeline.Entities.UserAvatar", b =>
+ modelBuilder.Entity("Timeline.Entities.UserAvatarEntity", b =>
{
- b.HasOne("Timeline.Entities.User", null)
+ b.HasOne("Timeline.Entities.UserEntity", "User")
.WithOne("Avatar")
- .HasForeignKey("Timeline.Entities.UserAvatar", "UserId")
- .OnDelete(DeleteBehavior.Cascade)
- .IsRequired();
- });
-
- modelBuilder.Entity("Timeline.Entities.UserDetail", b =>
- {
- b.HasOne("Timeline.Entities.User", null)
- .WithOne("Detail")
- .HasForeignKey("Timeline.Entities.UserDetail", "UserId")
+ .HasForeignKey("Timeline.Entities.UserAvatarEntity", "UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
diff --git a/Timeline/Models/Converters/JsonDateTimeConverter.cs b/Timeline/Models/Converters/JsonDateTimeConverter.cs
index 69af53c1..ef129a01 100644
--- a/Timeline/Models/Converters/JsonDateTimeConverter.cs
+++ b/Timeline/Models/Converters/JsonDateTimeConverter.cs
@@ -6,7 +6,6 @@ using System.Text.Json.Serialization;
namespace Timeline.Models.Converters
{
- [System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1062:Validate arguments of public methods")]
public class JsonDateTimeConverter : JsonConverter<DateTime>
{
public override DateTime Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
diff --git a/Timeline/Models/Http/Common.cs b/Timeline/Models/Http/Common.cs
index d1e95397..a9fc8a79 100644
--- a/Timeline/Models/Http/Common.cs
+++ b/Timeline/Models/Http/Common.cs
@@ -1,15 +1,9 @@
-using System.Globalization;
using static Timeline.Resources.Models.Http.Common;
namespace Timeline.Models.Http
{
public class CommonResponse
{
- internal static CommonResponse InvalidModel(string message)
- {
- return new CommonResponse(ErrorCodes.Http.Common.InvalidModel, message);
- }
-
public CommonResponse()
{
@@ -25,33 +19,6 @@ namespace Timeline.Models.Http
public string? Message { get; set; }
}
- internal static class HeaderErrorResponse
- {
- internal static CommonResponse BadIfNonMatch()
- {
- return new CommonResponse(ErrorCodes.Http.Common.Header.IfNonMatch.BadFormat, MessageHeaderIfNonMatchBad);
- }
- }
-
- internal static class ContentErrorResponse
- {
- internal static CommonResponse TooBig(string maxLength)
- {
- return new CommonResponse(ErrorCodes.Http.Common.Content.TooBig,
- string.Format(CultureInfo.CurrentCulture, MessageContentTooBig, maxLength));
- }
-
- internal static CommonResponse UnmatchedLength_Smaller()
- {
- return new CommonResponse(ErrorCodes.Http.Common.Content.UnmatchedLength_Smaller, MessageContentUnmatchedLengthSmaller);
- }
- internal static CommonResponse UnmatchedLength_Bigger()
- {
- return new CommonResponse(ErrorCodes.Http.Common.Content.UnmatchedLength_Bigger, MessageContentUnmatchedLengthBigger);
- }
- }
-
-
public class CommonDataResponse<T> : CommonResponse
{
public CommonDataResponse()
diff --git a/Timeline/Models/Http/ErrorResponse.cs b/Timeline/Models/Http/ErrorResponse.cs
new file mode 100644
index 00000000..87516638
--- /dev/null
+++ b/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 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 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
+ {
+
+ 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 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));
+ }
+
+ }
+
+ }
+
+ 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 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));
+ }
+
+ }
+
+ }
+
+} \ No newline at end of file
diff --git a/Timeline/Models/Http/Timeline.cs b/Timeline/Models/Http/Timeline.cs
deleted file mode 100644
index 06b88ad1..00000000
--- a/Timeline/Models/Http/Timeline.cs
+++ /dev/null
@@ -1,45 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.ComponentModel.DataAnnotations;
-using System.Linq;
-using System.Threading.Tasks;
-using Timeline.Entities;
-
-namespace Timeline.Models.Http
-{
- public class TimelinePostCreateRequest
- {
- [Required(AllowEmptyStrings = true)]
- public string Content { get; set; } = default!;
-
- public DateTime? Time { get; set; }
- }
-
- public class TimelinePostCreateResponse
- {
- public long Id { get; set; }
-
- public DateTime Time { get; set; }
- }
-
- public class TimelinePostDeleteRequest
- {
- [Required]
- public long? Id { get; set; }
- }
-
- public class TimelinePropertyChangeRequest
- {
- public string? Description { get; set; }
-
- public TimelineVisibility? Visibility { get; set; }
- }
-
- [System.Diagnostics.CodeAnalysis.SuppressMessage("Usage", "CA2227:Collection properties should be read only", Justification = "This is a DTO class.")]
- public class TimelineMemberChangeRequest
- {
- public List<string>? Add { get; set; }
-
- public List<string>? Remove { get; set; }
- }
-}
diff --git a/Timeline/Models/Timeline.cs b/Timeline/Models/Http/TimelineCommon.cs
index 752c698d..febb8186 100644
--- a/Timeline/Models/Timeline.cs
+++ b/Timeline/Models/Http/TimelineCommon.cs
@@ -1,7 +1,7 @@
using System;
using System.Collections.Generic;
-namespace Timeline.Models
+namespace Timeline.Models.Http
{
public enum TimelineVisibility
{
@@ -22,30 +22,19 @@ namespace Timeline.Models
public class TimelinePostInfo
{
public long Id { get; set; }
-
- public string? Content { get; set; }
-
+ public string Content { get; set; } = default!;
public DateTime Time { get; set; }
-
- /// <summary>
- /// The username of the author.
- /// </summary>
- public string Author { get; set; } = default!;
+ public UserInfo Author { get; set; } = default!;
+ public DateTime LastUpdated { get; set; } = default!;
}
[System.Diagnostics.CodeAnalysis.SuppressMessage("Usage", "CA2227:Collection properties should be read only", Justification = "This is a DTO class.")]
public class BaseTimelineInfo
{
- public string? Description { get; set; }
-
- /// <summary>
- /// The username of the owner.
- /// </summary>
- public string Owner { get; set; } = default!;
-
+ public string Description { get; set; } = default!;
+ public UserInfo Owner { get; set; } = default!;
public TimelineVisibility Visibility { get; set; }
-
- public List<string> Members { get; set; } = default!;
+ public List<UserInfo> Members { get; set; } = default!;
}
public class TimelineInfo : BaseTimelineInfo
diff --git a/Timeline/Models/Http/TimelineController.cs b/Timeline/Models/Http/TimelineController.cs
new file mode 100644
index 00000000..f9a4d3e5
--- /dev/null
+++ b/Timeline/Models/Http/TimelineController.cs
@@ -0,0 +1,20 @@
+using System;
+using System.ComponentModel.DataAnnotations;
+
+namespace Timeline.Models.Http
+{
+ public class TimelinePostCreateRequest
+ {
+ [Required(AllowEmptyStrings = true)]
+ public string Content { get; set; } = default!;
+
+ public DateTime? Time { get; set; }
+ }
+
+ public class TimelinePatchRequest
+ {
+ public string? Description { get; set; }
+
+ public TimelineVisibility? Visibility { get; set; }
+ }
+}
diff --git a/Timeline/Models/Http/Token.cs b/Timeline/Models/Http/TokenController.cs
index ea8b59ed..ea8b59ed 100644
--- a/Timeline/Models/Http/Token.cs
+++ b/Timeline/Models/Http/TokenController.cs
diff --git a/Timeline/Models/Http/User.cs b/Timeline/Models/Http/User.cs
deleted file mode 100644
index 516c1329..00000000
--- a/Timeline/Models/Http/User.cs
+++ /dev/null
@@ -1,38 +0,0 @@
-using System.ComponentModel.DataAnnotations;
-using Timeline.Models.Validation;
-
-namespace Timeline.Models.Http
-{
- public class UserPutRequest
- {
- [Required]
- public string Password { get; set; } = default!;
- [Required]
- public bool? Administrator { get; set; }
- }
-
- public class UserPatchRequest
- {
- public string? Password { get; set; }
- public bool? Administrator { get; set; }
- }
-
- public class ChangeUsernameRequest
- {
- [Required]
- [Username]
- public string OldUsername { get; set; } = default!;
-
- [Required]
- [Username]
- public string NewUsername { get; set; } = default!;
- }
-
- public class ChangePasswordRequest
- {
- [Required]
- public string OldPassword { get; set; } = default!;
- [Required]
- public string NewPassword { get; set; } = default!;
- }
-}
diff --git a/Timeline/Models/Http/UserController.cs b/Timeline/Models/Http/UserController.cs
new file mode 100644
index 00000000..e4c95cbd
--- /dev/null
+++ b/Timeline/Models/Http/UserController.cs
@@ -0,0 +1,53 @@
+using AutoMapper;
+using System.ComponentModel.DataAnnotations;
+using Timeline.Models.Validation;
+using Timeline.Services;
+
+namespace Timeline.Models.Http
+{
+ public class UserPatchRequest
+ {
+ [Username]
+ public string? Username { get; set; }
+
+ [MinLength(1)]
+ public string? Password { get; set; }
+
+ [Nickname]
+ public string? Nickname { get; set; }
+
+ public bool? Administrator { get; set; }
+ }
+
+ public class CreateUserRequest
+ {
+ [Required, Username]
+ public string Username { get; set; } = default!;
+
+ [Required, MinLength(1)]
+ public string Password { get; set; } = default!;
+
+ [Required]
+ public bool? Administrator { get; set; }
+
+ [Nickname]
+ public string? Nickname { get; set; }
+ }
+
+ public class ChangePasswordRequest
+ {
+ [Required(AllowEmptyStrings = false)]
+ public string OldPassword { get; set; } = default!;
+ [Required(AllowEmptyStrings = false)]
+ public string NewPassword { get; set; } = default!;
+ }
+
+ public class UserControllerAutoMapperProfile : Profile
+ {
+ public UserControllerAutoMapperProfile()
+ {
+ CreateMap<UserPatchRequest, User>(MemberList.Source);
+ CreateMap<CreateUserRequest, User>(MemberList.Source);
+ }
+ }
+}
diff --git a/Timeline/Models/Http/UserInfo.cs b/Timeline/Models/Http/UserInfo.cs
new file mode 100644
index 00000000..0d1d702b
--- /dev/null
+++ b/Timeline/Models/Http/UserInfo.cs
@@ -0,0 +1,59 @@
+using AutoMapper;
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.AspNetCore.Mvc.Infrastructure;
+using Microsoft.AspNetCore.Mvc.Routing;
+using Timeline.Controllers;
+using Timeline.Services;
+
+namespace Timeline.Models.Http
+{
+ public class UserInfo
+ {
+ public string Username { get; set; } = default!;
+ public string Nickname { get; set; } = default!;
+ public bool? Administrator { get; set; } = default!;
+#pragma warning disable CA1707
+ public UserInfoLinks? _links { get; set; }
+#pragma warning restore CA1707
+ }
+
+ public class UserInfoLinks
+ {
+ public string Avatar { get; set; } = default!;
+ public string Timeline { get; set; } = default!;
+ }
+
+ public class UserInfoLinksValueResolver : IValueResolver<User, UserInfo, UserInfoLinks?>
+ {
+ 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)
+ {
+ if (_actionContextAccessor.ActionContext == null)
+ return null;
+
+ var urlHelper = _urlHelperFactory.GetUrlHelper(_actionContextAccessor.ActionContext);
+ var result = new UserInfoLinks
+ {
+ Avatar = urlHelper.ActionLink(nameof(UserAvatarController.Get), nameof(UserAvatarController)[0..^nameof(Controller).Length], new { destination.Username }),
+ Timeline = urlHelper.ActionLink(nameof(PersonalTimelineController.TimelineGet), nameof(PersonalTimelineController)[0..^nameof(Controller).Length], new { destination.Username })
+ };
+ return result;
+ }
+ }
+
+ public class UserInfoAutoMapperProfile : Profile
+ {
+ public UserInfoAutoMapperProfile()
+ {
+ CreateMap<User, UserInfo>().ForMember(u => u._links, opt => opt.MapFrom<UserInfoLinksValueResolver>());
+ }
+ }
+}
diff --git a/Timeline/Models/PutResult.cs b/Timeline/Models/PutResult.cs
deleted file mode 100644
index cecf86e6..00000000
--- a/Timeline/Models/PutResult.cs
+++ /dev/null
@@ -1,17 +0,0 @@
-namespace Timeline.Models
-{
- /// <summary>
- /// Represents the result of a "put" operation.
- /// </summary>
- public enum PutResult
- {
- /// <summary>
- /// Indicates the item did not exist and now is created.
- /// </summary>
- Create,
- /// <summary>
- /// Indicates the item exists already and is modified.
- /// </summary>
- Modify
- }
-}
diff --git a/Timeline/Models/UserInfo.cs b/Timeline/Models/UserInfo.cs
deleted file mode 100644
index b60bdfa2..00000000
--- a/Timeline/Models/UserInfo.cs
+++ /dev/null
@@ -1,23 +0,0 @@
-namespace Timeline.Models
-{
- public sealed class UserInfo
- {
- public UserInfo()
- {
- }
-
- public UserInfo(string username, bool administrator)
- {
- Username = username;
- Administrator = administrator;
- }
-
- public string Username { get; set; } = default!;
- public bool Administrator { get; set; } = default!;
-
- public override string ToString()
- {
- return $"Username: {Username} ; Administrator: {Administrator}";
- }
- }
-}
diff --git a/Timeline/Models/Validation/NicknameValidator.cs b/Timeline/Models/Validation/NicknameValidator.cs
new file mode 100644
index 00000000..1d6ab163
--- /dev/null
+++ b/Timeline/Models/Validation/NicknameValidator.cs
@@ -0,0 +1,25 @@
+using System;
+using static Timeline.Resources.Models.Validation.NicknameValidator;
+
+namespace Timeline.Models.Validation
+{
+ public class NicknameValidator : Validator<string>
+ {
+ protected override (bool, string) DoValidate(string value)
+ {
+ if (value.Length > 25)
+ return (false, MessageTooLong);
+
+ return (true, GetSuccessMessage());
+ }
+ }
+
+ [AttributeUsage(AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Parameter, AllowMultiple = false)]
+ public class NicknameAttribute : ValidateWithAttribute
+ {
+ public NicknameAttribute() : base(typeof(NicknameValidator))
+ {
+
+ }
+ }
+}
diff --git a/Timeline/Models/Validation/UsernameValidator.cs b/Timeline/Models/Validation/UsernameValidator.cs
index fc6cdf37..d8f3bdc0 100644
--- a/Timeline/Models/Validation/UsernameValidator.cs
+++ b/Timeline/Models/Validation/UsernameValidator.cs
@@ -8,7 +8,6 @@ namespace Timeline.Models.Validation
{
public const int MaxLength = 26;
- [System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1062:Validate arguments of public methods", Justification = "Already checked in base class.")]
protected override (bool, string) DoValidate(string value)
{
if (value.Length == 0)
diff --git a/Timeline/Models/Validation/Validator.cs b/Timeline/Models/Validation/Validator.cs
index a16f6f81..ead7dbef 100644
--- a/Timeline/Models/Validation/Validator.cs
+++ b/Timeline/Models/Validation/Validator.cs
@@ -20,24 +20,46 @@ namespace Timeline.Models.Validation
(bool, string) Validate(object? value);
}
+ public static class ValidatorExtensions
+ {
+ public static bool Validate(this IValidator validator, object? value, out string message)
+ {
+ if (validator == null)
+ throw new ArgumentNullException(nameof(validator));
+
+ var (r, m) = validator.Validate(value);
+ message = m;
+ return r;
+ }
+ }
+
/// <summary>
/// Convenient base class for validator.
/// </summary>
/// <typeparam name="T">The type of accepted value.</typeparam>
/// <remarks>
/// Subclass should override <see cref="DoValidate(T, out string)"/> to do the real validation.
- /// This class will check the nullity and type of value. If value is null or not of type <typeparamref name="T"/>
- /// it will return false and not call <see cref="DoValidate(T, out string)"/>.
+ /// This class will check the nullity and type of value.
+ /// If value is null, it will pass or fail depending on <see cref="PermitNull"/>.
+ /// If value is not null and not of type <typeparamref name="T"/>
+ /// it will fail and not call <see cref="DoValidate(T, out string)"/>.
+ ///
+ /// <see cref="PermitNull"/> is true by default.
///
/// If you want some other behaviours, write the validator from scratch.
/// </remarks>
public abstract class Validator<T> : IValidator
{
+ protected bool PermitNull { get; set; } = true;
+
public (bool, string) Validate(object? value)
{
if (value == null)
{
- return (false, ValidatorMessageNull);
+ if (PermitNull)
+ return (true, GetSuccessMessage());
+ else
+ return (false, ValidatorMessageNull);
}
if (value is T v)
diff --git a/Timeline/Resources/Common.resx b/Timeline/Resources/Common.resx
deleted file mode 100644
index 8a036996..00000000
--- a/Timeline/Resources/Common.resx
+++ /dev/null
@@ -1,123 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<root>
- <!--
- Microsoft ResX Schema
-
- Version 2.0
-
- The primary goals of this format is to allow a simple XML format
- that is mostly human readable. The generation and parsing of the
- various data types are done through the TypeConverter classes
- associated with the data types.
-
- Example:
-
- ... ado.net/XML headers & schema ...
- <resheader name="resmimetype">text/microsoft-resx</resheader>
- <resheader name="version">2.0</resheader>
- <resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
- <resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
- <data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
- <data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
- <data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
- <value>[base64 mime encoded serialized .NET Framework object]</value>
- </data>
- <data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
- <value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
- <comment>This is a comment</comment>
- </data>
-
- There are any number of "resheader" rows that contain simple
- name/value pairs.
-
- Each data row contains a name, and value. The row also contains a
- type or mimetype. Type corresponds to a .NET class that support
- text/value conversion through the TypeConverter architecture.
- Classes that don't support this are serialized and stored with the
- mimetype set.
-
- The mimetype is used for serialized objects, and tells the
- ResXResourceReader how to depersist the object. This is currently not
- extensible. For a given mimetype the value must be set accordingly:
-
- Note - application/x-microsoft.net.object.binary.base64 is the format
- that the ResXResourceWriter will generate, however the reader can
- read any of the formats listed below.
-
- mimetype: application/x-microsoft.net.object.binary.base64
- value : The object must be serialized with
- : System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
- : and then encoded with base64 encoding.
-
- mimetype: application/x-microsoft.net.object.soap.base64
- value : The object must be serialized with
- : System.Runtime.Serialization.Formatters.Soap.SoapFormatter
- : and then encoded with base64 encoding.
-
- mimetype: application/x-microsoft.net.object.bytearray.base64
- value : The object must be serialized into a byte array
- : using a System.ComponentModel.TypeConverter
- : and then encoded with base64 encoding.
- -->
- <xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
- <xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
- <xsd:element name="root" msdata:IsDataSet="true">
- <xsd:complexType>
- <xsd:choice maxOccurs="unbounded">
- <xsd:element name="metadata">
- <xsd:complexType>
- <xsd:sequence>
- <xsd:element name="value" type="xsd:string" minOccurs="0" />
- </xsd:sequence>
- <xsd:attribute name="name" use="required" type="xsd:string" />
- <xsd:attribute name="type" type="xsd:string" />
- <xsd:attribute name="mimetype" type="xsd:string" />
- <xsd:attribute ref="xml:space" />
- </xsd:complexType>
- </xsd:element>
- <xsd:element name="assembly">
- <xsd:complexType>
- <xsd:attribute name="alias" type="xsd:string" />
- <xsd:attribute name="name" type="xsd:string" />
- </xsd:complexType>
- </xsd:element>
- <xsd:element name="data">
- <xsd:complexType>
- <xsd:sequence>
- <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
- <xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
- </xsd:sequence>
- <xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
- <xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
- <xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
- <xsd:attribute ref="xml:space" />
- </xsd:complexType>
- </xsd:element>
- <xsd:element name="resheader">
- <xsd:complexType>
- <xsd:sequence>
- <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
- </xsd:sequence>
- <xsd:attribute name="name" type="xsd:string" use="required" />
- </xsd:complexType>
- </xsd:element>
- </xsd:choice>
- </xsd:complexType>
- </xsd:element>
- </xsd:schema>
- <resheader name="resmimetype">
- <value>text/microsoft-resx</value>
- </resheader>
- <resheader name="version">
- <value>2.0</value>
- </resheader>
- <resheader name="reader">
- <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
- </resheader>
- <resheader name="writer">
- <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
- </resheader>
- <data name="ExceptionInvalidBranch" xml:space="preserve">
- <value>The branch is invalid. Normally this branch is not reachable.</value>
- </data>
-</root> \ No newline at end of file
diff --git a/Timeline/Resources/Services/UserDetailService.Designer.cs b/Timeline/Resources/Controllers/ControllerAuthExtensions.Designer.cs
index 2f586b36..70a1d605 100644
--- a/Timeline/Resources/Services/UserDetailService.Designer.cs
+++ b/Timeline/Resources/Controllers/ControllerAuthExtensions.Designer.cs
@@ -8,7 +8,7 @@
// </auto-generated>
//------------------------------------------------------------------------------
-namespace Timeline.Resources.Services {
+namespace Timeline.Resources.Controllers {
using System;
@@ -22,14 +22,14 @@ namespace Timeline.Resources.Services {
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "16.0.0.0")]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
- internal class UserDetailService {
+ internal class ControllerAuthExtensions {
private static global::System.Resources.ResourceManager resourceMan;
private static global::System.Globalization.CultureInfo resourceCulture;
[global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
- internal UserDetailService() {
+ internal ControllerAuthExtensions() {
}
/// <summary>
@@ -39,7 +39,7 @@ namespace Timeline.Resources.Services {
internal static global::System.Resources.ResourceManager ResourceManager {
get {
if (object.ReferenceEquals(resourceMan, null)) {
- global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Timeline.Resources.Services.UserDetailService", typeof(UserDetailService).Assembly);
+ global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Timeline.Resources.Controllers.ControllerAuthExtensions", typeof(ControllerAuthExtensions).Assembly);
resourceMan = temp;
}
return resourceMan;
@@ -61,38 +61,20 @@ namespace Timeline.Resources.Services {
}
/// <summary>
- /// Looks up a localized string similar to Length of nickname can&apos;t be bigger than 10..
+ /// Looks up a localized string similar to Failed to get user id because User has no NameIdentifier claim..
/// </summary>
- internal static string ExceptionNicknameTooLong {
+ internal static string ExceptionNoUserIdentifierClaim {
get {
- return ResourceManager.GetString("ExceptionNicknameTooLong", resourceCulture);
+ return ResourceManager.GetString("ExceptionNoUserIdentifierClaim", resourceCulture);
}
}
/// <summary>
- /// Looks up a localized string similar to A user_details entity has been created. User id is {0}. Nickname is {1}..
+ /// Looks up a localized string similar to Failed to get user id because NameIdentifier claim is not a number..
/// </summary>
- internal static string LogEntityNicknameCreate {
+ internal static string ExceptionUserIdentifierClaimBadFormat {
get {
- return ResourceManager.GetString("LogEntityNicknameCreate", resourceCulture);
- }
- }
-
- /// <summary>
- /// Looks up a localized string similar to Nickname of a user_details entity has been updated to a new value. User id is {0}. New value is {1}..
- /// </summary>
- internal static string LogEntityNicknameSetNotNull {
- get {
- return ResourceManager.GetString("LogEntityNicknameSetNotNull", resourceCulture);
- }
- }
-
- /// <summary>
- /// Looks up a localized string similar to Nickname of a user_details entity has been updated to null. User id is {0}..
- /// </summary>
- internal static string LogEntityNicknameSetToNull {
- get {
- return ResourceManager.GetString("LogEntityNicknameSetToNull", resourceCulture);
+ return ResourceManager.GetString("ExceptionUserIdentifierClaimBadFormat", resourceCulture);
}
}
}
diff --git a/Timeline/Resources/Models/Validation/Validator.zh.resx b/Timeline/Resources/Controllers/ControllerAuthExtensions.resx
index 2f98e7e3..03e6d95a 100644
--- a/Timeline/Resources/Models/Validation/Validator.zh.resx
+++ b/Timeline/Resources/Controllers/ControllerAuthExtensions.resx
@@ -117,13 +117,10 @@
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
- <data name="ValidatorMessageBadType" xml:space="preserve">
- <value>值不是类型{0}的实例。</value>
+ <data name="ExceptionNoUserIdentifierClaim" xml:space="preserve">
+ <value>Failed to get user id because User has no NameIdentifier claim.</value>
</data>
- <data name="ValidatorMessageNull" xml:space="preserve">
- <value>值不能为null.</value>
- </data>
- <data name="ValidatorMessageSuccess" xml:space="preserve">
- <value>验证成功。</value>
+ <data name="ExceptionUserIdentifierClaimBadFormat" xml:space="preserve">
+ <value>Failed to get user id because NameIdentifier claim is not a number.</value>
</data>
</root> \ No newline at end of file
diff --git a/Timeline/Resources/Controllers/TimelineController.Designer.cs b/Timeline/Resources/Controllers/TimelineController.Designer.cs
index 47c43fa2..ae6414e6 100644
--- a/Timeline/Resources/Controllers/TimelineController.Designer.cs
+++ b/Timeline/Resources/Controllers/TimelineController.Designer.cs
@@ -77,59 +77,5 @@ namespace Timeline.Resources.Controllers {
return ResourceManager.GetString("LogUnknownTimelineMemberOperationUserException", resourceCulture);
}
}
-
- /// <summary>
- /// Looks up a localized string similar to The {0}-st username to do operation {1} on is of bad format..
- /// </summary>
- internal static string MessageMemberUsernameBadFormat {
- get {
- return ResourceManager.GetString("MessageMemberUsernameBadFormat", resourceCulture);
- }
- }
-
- /// <summary>
- /// Looks up a localized string similar to The {0}-st user to do operation {1} on does not exist..
- /// </summary>
- internal static string MessageMemberUserNotExist {
- get {
- return ResourceManager.GetString("MessageMemberUserNotExist", resourceCulture);
- }
- }
-
- /// <summary>
- /// Looks up a localized string similar to You have no permission to read posts of the timeline..
- /// </summary>
- internal static string MessagePostListGetForbid {
- get {
- return ResourceManager.GetString("MessagePostListGetForbid", resourceCulture);
- }
- }
-
- /// <summary>
- /// Looks up a localized string similar to You have no permission to create posts in the timeline..
- /// </summary>
- internal static string MessagePostOperationCreateForbid {
- get {
- return ResourceManager.GetString("MessagePostOperationCreateForbid", resourceCulture);
- }
- }
-
- /// <summary>
- /// Looks up a localized string similar to You have no permission to delete posts in the timeline..
- /// </summary>
- internal static string MessagePostOperationDeleteForbid {
- get {
- return ResourceManager.GetString("MessagePostOperationDeleteForbid", resourceCulture);
- }
- }
-
- /// <summary>
- /// Looks up a localized string similar to The post to delete does not exist..
- /// </summary>
- internal static string MessagePostOperationDeleteNotExist {
- get {
- return ResourceManager.GetString("MessagePostOperationDeleteNotExist", resourceCulture);
- }
- }
}
}
diff --git a/Timeline/Resources/Controllers/TimelineController.resx b/Timeline/Resources/Controllers/TimelineController.resx
index 0cf7e881..4cf3d6fb 100644
--- a/Timeline/Resources/Controllers/TimelineController.resx
+++ b/Timeline/Resources/Controllers/TimelineController.resx
@@ -123,22 +123,4 @@
<data name="LogUnknownTimelineMemberOperationUserException" xml:space="preserve">
<value>An unknown TimelineMemberOperationUserException is thrown. Can't recognize its inner exception. It is rethrown.</value>
</data>
- <data name="MessageMemberUsernameBadFormat" xml:space="preserve">
- <value>The {0}-st username to do operation {1} on is of bad format.</value>
- </data>
- <data name="MessageMemberUserNotExist" xml:space="preserve">
- <value>The {0}-st user to do operation {1} on does not exist.</value>
- </data>
- <data name="MessagePostListGetForbid" xml:space="preserve">
- <value>You have no permission to read posts of the timeline.</value>
- </data>
- <data name="MessagePostOperationCreateForbid" xml:space="preserve">
- <value>You have no permission to create posts in the timeline.</value>
- </data>
- <data name="MessagePostOperationDeleteForbid" xml:space="preserve">
- <value>You have no permission to delete posts in the timeline.</value>
- </data>
- <data name="MessagePostOperationDeleteNotExist" xml:space="preserve">
- <value>The post to delete does not exist.</value>
- </data>
</root> \ No newline at end of file
diff --git a/Timeline/Resources/Controllers/TimelineController.zh.resx b/Timeline/Resources/Controllers/TimelineController.zh.resx
deleted file mode 100644
index 170ab4cd..00000000
--- a/Timeline/Resources/Controllers/TimelineController.zh.resx
+++ /dev/null
@@ -1,138 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<root>
- <!--
- Microsoft ResX Schema
-
- Version 2.0
-
- The primary goals of this format is to allow a simple XML format
- that is mostly human readable. The generation and parsing of the
- various data types are done through the TypeConverter classes
- associated with the data types.
-
- Example:
-
- ... ado.net/XML headers & schema ...
- <resheader name="resmimetype">text/microsoft-resx</resheader>
- <resheader name="version">2.0</resheader>
- <resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
- <resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
- <data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
- <data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
- <data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
- <value>[base64 mime encoded serialized .NET Framework object]</value>
- </data>
- <data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
- <value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
- <comment>This is a comment</comment>
- </data>
-
- There are any number of "resheader" rows that contain simple
- name/value pairs.
-
- Each data row contains a name, and value. The row also contains a
- type or mimetype. Type corresponds to a .NET class that support
- text/value conversion through the TypeConverter architecture.
- Classes that don't support this are serialized and stored with the
- mimetype set.
-
- The mimetype is used for serialized objects, and tells the
- ResXResourceReader how to depersist the object. This is currently not
- extensible. For a given mimetype the value must be set accordingly:
-
- Note - application/x-microsoft.net.object.binary.base64 is the format
- that the ResXResourceWriter will generate, however the reader can
- read any of the formats listed below.
-
- mimetype: application/x-microsoft.net.object.binary.base64
- value : The object must be serialized with
- : System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
- : and then encoded with base64 encoding.
-
- mimetype: application/x-microsoft.net.object.soap.base64
- value : The object must be serialized with
- : System.Runtime.Serialization.Formatters.Soap.SoapFormatter
- : and then encoded with base64 encoding.
-
- mimetype: application/x-microsoft.net.object.bytearray.base64
- value : The object must be serialized into a byte array
- : using a System.ComponentModel.TypeConverter
- : and then encoded with base64 encoding.
- -->
- <xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
- <xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
- <xsd:element name="root" msdata:IsDataSet="true">
- <xsd:complexType>
- <xsd:choice maxOccurs="unbounded">
- <xsd:element name="metadata">
- <xsd:complexType>
- <xsd:sequence>
- <xsd:element name="value" type="xsd:string" minOccurs="0" />
- </xsd:sequence>
- <xsd:attribute name="name" use="required" type="xsd:string" />
- <xsd:attribute name="type" type="xsd:string" />
- <xsd:attribute name="mimetype" type="xsd:string" />
- <xsd:attribute ref="xml:space" />
- </xsd:complexType>
- </xsd:element>
- <xsd:element name="assembly">
- <xsd:complexType>
- <xsd:attribute name="alias" type="xsd:string" />
- <xsd:attribute name="name" type="xsd:string" />
- </xsd:complexType>
- </xsd:element>
- <xsd:element name="data">
- <xsd:complexType>
- <xsd:sequence>
- <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
- <xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
- </xsd:sequence>
- <xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
- <xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
- <xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
- <xsd:attribute ref="xml:space" />
- </xsd:complexType>
- </xsd:element>
- <xsd:element name="resheader">
- <xsd:complexType>
- <xsd:sequence>
- <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
- </xsd:sequence>
- <xsd:attribute name="name" type="xsd:string" use="required" />
- </xsd:complexType>
- </xsd:element>
- </xsd:choice>
- </xsd:complexType>
- </xsd:element>
- </xsd:schema>
- <resheader name="resmimetype">
- <value>text/microsoft-resx</value>
- </resheader>
- <resheader name="version">
- <value>2.0</value>
- </resheader>
- <resheader name="reader">
- <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
- </resheader>
- <resheader name="writer">
- <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
- </resheader>
- <data name="MessageMemberUsernameBadFormat" xml:space="preserve">
- <value>第{0}个做{1}操作的用户名格式错误。</value>
- </data>
- <data name="MessageMemberUserNotExist" xml:space="preserve">
- <value>第{0}个做{1}操作的用户不存在。</value>
- </data>
- <data name="MessagePostListGetForbid" xml:space="preserve">
- <value>你没有权限读取这个时间线消息。</value>
- </data>
- <data name="MessagePostOperationCreateForbid" xml:space="preserve">
- <value>你没有权限在这个时间线中创建消息。</value>
- </data>
- <data name="MessagePostOperationDeleteForbid" xml:space="preserve">
- <value>你没有权限在这个时间线中删除消息。</value>
- </data>
- <data name="MessagePostOperationDeleteNotExist" xml:space="preserve">
- <value>要删除的消息不存在。</value>
- </data>
-</root> \ No newline at end of file
diff --git a/Timeline/Resources/Controllers/TokenController.Designer.cs b/Timeline/Resources/Controllers/TokenController.Designer.cs
index 22e6a8be..a7c2864b 100644
--- a/Timeline/Resources/Controllers/TokenController.Designer.cs
+++ b/Timeline/Resources/Controllers/TokenController.Designer.cs
@@ -61,51 +61,6 @@ namespace Timeline.Resources.Controllers {
}
/// <summary>
- /// Looks up a localized string similar to Username or password is invalid..
- /// </summary>
- internal static string ErrorBadCredential {
- get {
- return ResourceManager.GetString("ErrorBadCredential", resourceCulture);
- }
- }
-
- /// <summary>
- /// Looks up a localized string similar to The token is of bad format. It might not be created by the server..
- /// </summary>
- internal static string ErrorVerifyBadFormat {
- get {
- return ResourceManager.GetString("ErrorVerifyBadFormat", resourceCulture);
- }
- }
-
- /// <summary>
- /// Looks up a localized string similar to The token is expired..
- /// </summary>
- internal static string ErrorVerifyExpire {
- get {
- return ResourceManager.GetString("ErrorVerifyExpire", resourceCulture);
- }
- }
-
- /// <summary>
- /// Looks up a localized string similar to Token has an old version. User might have update some info..
- /// </summary>
- internal static string ErrorVerifyOldVersion {
- get {
- return ResourceManager.GetString("ErrorVerifyOldVersion", resourceCulture);
- }
- }
-
- /// <summary>
- /// Looks up a localized string similar to User does not exist. Administrator might have deleted this user..
- /// </summary>
- internal static string ErrorVerifyUserNotExist {
- get {
- return ResourceManager.GetString("ErrorVerifyUserNotExist", resourceCulture);
- }
- }
-
- /// <summary>
/// Looks up a localized string similar to The password is wrong..
/// </summary>
internal static string LogBadPassword {
diff --git a/Timeline/Resources/Controllers/TokenController.resx b/Timeline/Resources/Controllers/TokenController.resx
index 42e1ff92..683d6cc9 100644
--- a/Timeline/Resources/Controllers/TokenController.resx
+++ b/Timeline/Resources/Controllers/TokenController.resx
@@ -117,21 +117,6 @@
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
- <data name="ErrorBadCredential" xml:space="preserve">
- <value>Username or password is invalid.</value>
- </data>
- <data name="ErrorVerifyBadFormat" xml:space="preserve">
- <value>The token is of bad format. It might not be created by the server.</value>
- </data>
- <data name="ErrorVerifyExpire" xml:space="preserve">
- <value>The token is expired.</value>
- </data>
- <data name="ErrorVerifyOldVersion" xml:space="preserve">
- <value>Token has an old version. User might have update some info.</value>
- </data>
- <data name="ErrorVerifyUserNotExist" xml:space="preserve">
- <value>User does not exist. Administrator might have deleted this user.</value>
- </data>
<data name="LogBadPassword" xml:space="preserve">
<value>The password is wrong.</value>
</data>
diff --git a/Timeline/Resources/Controllers/TokenController.zh.resx b/Timeline/Resources/Controllers/TokenController.zh.resx
deleted file mode 100644
index 51e0f25b..00000000
--- a/Timeline/Resources/Controllers/TokenController.zh.resx
+++ /dev/null
@@ -1,135 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<root>
- <!--
- Microsoft ResX Schema
-
- Version 2.0
-
- The primary goals of this format is to allow a simple XML format
- that is mostly human readable. The generation and parsing of the
- various data types are done through the TypeConverter classes
- associated with the data types.
-
- Example:
-
- ... ado.net/XML headers & schema ...
- <resheader name="resmimetype">text/microsoft-resx</resheader>
- <resheader name="version">2.0</resheader>
- <resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
- <resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
- <data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
- <data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
- <data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
- <value>[base64 mime encoded serialized .NET Framework object]</value>
- </data>
- <data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
- <value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
- <comment>This is a comment</comment>
- </data>
-
- There are any number of "resheader" rows that contain simple
- name/value pairs.
-
- Each data row contains a name, and value. The row also contains a
- type or mimetype. Type corresponds to a .NET class that support
- text/value conversion through the TypeConverter architecture.
- Classes that don't support this are serialized and stored with the
- mimetype set.
-
- The mimetype is used for serialized objects, and tells the
- ResXResourceReader how to depersist the object. This is currently not
- extensible. For a given mimetype the value must be set accordingly:
-
- Note - application/x-microsoft.net.object.binary.base64 is the format
- that the ResXResourceWriter will generate, however the reader can
- read any of the formats listed below.
-
- mimetype: application/x-microsoft.net.object.binary.base64
- value : The object must be serialized with
- : System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
- : and then encoded with base64 encoding.
-
- mimetype: application/x-microsoft.net.object.soap.base64
- value : The object must be serialized with
- : System.Runtime.Serialization.Formatters.Soap.SoapFormatter
- : and then encoded with base64 encoding.
-
- mimetype: application/x-microsoft.net.object.bytearray.base64
- value : The object must be serialized into a byte array
- : using a System.ComponentModel.TypeConverter
- : and then encoded with base64 encoding.
- -->
- <xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
- <xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
- <xsd:element name="root" msdata:IsDataSet="true">
- <xsd:complexType>
- <xsd:choice maxOccurs="unbounded">
- <xsd:element name="metadata">
- <xsd:complexType>
- <xsd:sequence>
- <xsd:element name="value" type="xsd:string" minOccurs="0" />
- </xsd:sequence>
- <xsd:attribute name="name" use="required" type="xsd:string" />
- <xsd:attribute name="type" type="xsd:string" />
- <xsd:attribute name="mimetype" type="xsd:string" />
- <xsd:attribute ref="xml:space" />
- </xsd:complexType>
- </xsd:element>
- <xsd:element name="assembly">
- <xsd:complexType>
- <xsd:attribute name="alias" type="xsd:string" />
- <xsd:attribute name="name" type="xsd:string" />
- </xsd:complexType>
- </xsd:element>
- <xsd:element name="data">
- <xsd:complexType>
- <xsd:sequence>
- <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
- <xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
- </xsd:sequence>
- <xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
- <xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
- <xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
- <xsd:attribute ref="xml:space" />
- </xsd:complexType>
- </xsd:element>
- <xsd:element name="resheader">
- <xsd:complexType>
- <xsd:sequence>
- <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
- </xsd:sequence>
- <xsd:attribute name="name" type="xsd:string" use="required" />
- </xsd:complexType>
- </xsd:element>
- </xsd:choice>
- </xsd:complexType>
- </xsd:element>
- </xsd:schema>
- <resheader name="resmimetype">
- <value>text/microsoft-resx</value>
- </resheader>
- <resheader name="version">
- <value>2.0</value>
- </resheader>
- <resheader name="reader">
- <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
- </resheader>
- <resheader name="writer">
- <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
- </resheader>
- <data name="ErrorBadCredential" xml:space="preserve">
- <value>用户名或密码错误。</value>
- </data>
- <data name="ErrorVerifyBadFormat" xml:space="preserve">
- <value>符号格式错误。这个符号可能不是这个服务器创建的。</value>
- </data>
- <data name="ErrorVerifyExpire" xml:space="preserve">
- <value>符号过期了。</value>
- </data>
- <data name="ErrorVerifyOldVersion" xml:space="preserve">
- <value>符号是一个旧版本。用户可能已经更新了信息。</value>
- </data>
- <data name="ErrorVerifyUserNotExist" xml:space="preserve">
- <value>用户不存在。管理员可能已经删除了这个用户。</value>
- </data>
-</root> \ No newline at end of file
diff --git a/Timeline/Resources/Controllers/UserAvatarController.Designer.cs b/Timeline/Resources/Controllers/UserAvatarController.Designer.cs
index 1dacb19f..e6eeb1e8 100644
--- a/Timeline/Resources/Controllers/UserAvatarController.Designer.cs
+++ b/Timeline/Resources/Controllers/UserAvatarController.Designer.cs
@@ -61,78 +61,6 @@ namespace Timeline.Resources.Controllers {
}
/// <summary>
- /// Looks up a localized string similar to Normal user can&apos;t delete other&apos;s avatar..
- /// </summary>
- internal static string ErrorDeleteForbid {
- get {
- return ResourceManager.GetString("ErrorDeleteForbid", resourceCulture);
- }
- }
-
- /// <summary>
- /// Looks up a localized string similar to User does not exist..
- /// </summary>
- internal static string ErrorDeleteUserNotExist {
- get {
- return ResourceManager.GetString("ErrorDeleteUserNotExist", resourceCulture);
- }
- }
-
- /// <summary>
- /// Looks up a localized string similar to User does not exist..
- /// </summary>
- internal static string ErrorGetUserNotExist {
- get {
- return ResourceManager.GetString("ErrorGetUserNotExist", resourceCulture);
- }
- }
-
- /// <summary>
- /// Looks up a localized string similar to Image is not a square..
- /// </summary>
- internal static string ErrorPutBadFormatBadSize {
- get {
- return ResourceManager.GetString("ErrorPutBadFormatBadSize", resourceCulture);
- }
- }
-
- /// <summary>
- /// Looks up a localized string similar to Decoding image failed..
- /// </summary>
- internal static string ErrorPutBadFormatCantDecode {
- get {
- return ResourceManager.GetString("ErrorPutBadFormatCantDecode", resourceCulture);
- }
- }
-
- /// <summary>
- /// Looks up a localized string similar to Image format is not the one in header..
- /// </summary>
- internal static string ErrorPutBadFormatUnmatchedFormat {
- get {
- return ResourceManager.GetString("ErrorPutBadFormatUnmatchedFormat", resourceCulture);
- }
- }
-
- /// <summary>
- /// Looks up a localized string similar to Normal user can&apos;t change other&apos;s avatar..
- /// </summary>
- internal static string ErrorPutForbid {
- get {
- return ResourceManager.GetString("ErrorPutForbid", resourceCulture);
- }
- }
-
- /// <summary>
- /// Looks up a localized string similar to User does not exist..
- /// </summary>
- internal static string ErrorPutUserNotExist {
- get {
- return ResourceManager.GetString("ErrorPutUserNotExist", resourceCulture);
- }
- }
-
- /// <summary>
/// Looks up a localized string similar to Unknown AvatarDataException.ErrorReason value..
/// </summary>
internal static string ExceptionUnknownAvatarFormatError {
diff --git a/Timeline/Resources/Controllers/UserAvatarController.resx b/Timeline/Resources/Controllers/UserAvatarController.resx
index 3f444b04..58860c83 100644
--- a/Timeline/Resources/Controllers/UserAvatarController.resx
+++ b/Timeline/Resources/Controllers/UserAvatarController.resx
@@ -117,30 +117,6 @@
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
- <data name="ErrorDeleteForbid" xml:space="preserve">
- <value>Normal user can't delete other's avatar.</value>
- </data>
- <data name="ErrorDeleteUserNotExist" xml:space="preserve">
- <value>User does not exist.</value>
- </data>
- <data name="ErrorGetUserNotExist" xml:space="preserve">
- <value>User does not exist.</value>
- </data>
- <data name="ErrorPutBadFormatBadSize" xml:space="preserve">
- <value>Image is not a square.</value>
- </data>
- <data name="ErrorPutBadFormatCantDecode" xml:space="preserve">
- <value>Decoding image failed.</value>
- </data>
- <data name="ErrorPutBadFormatUnmatchedFormat" xml:space="preserve">
- <value>Image format is not the one in header.</value>
- </data>
- <data name="ErrorPutForbid" xml:space="preserve">
- <value>Normal user can't change other's avatar.</value>
- </data>
- <data name="ErrorPutUserNotExist" xml:space="preserve">
- <value>User does not exist.</value>
- </data>
<data name="ExceptionUnknownAvatarFormatError" xml:space="preserve">
<value>Unknown AvatarDataException.ErrorReason value.</value>
</data>
diff --git a/Timeline/Resources/Controllers/UserAvatarController.zh.resx b/Timeline/Resources/Controllers/UserAvatarController.zh.resx
deleted file mode 100644
index 94de1606..00000000
--- a/Timeline/Resources/Controllers/UserAvatarController.zh.resx
+++ /dev/null
@@ -1,144 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<root>
- <!--
- Microsoft ResX Schema
-
- Version 2.0
-
- The primary goals of this format is to allow a simple XML format
- that is mostly human readable. The generation and parsing of the
- various data types are done through the TypeConverter classes
- associated with the data types.
-
- Example:
-
- ... ado.net/XML headers & schema ...
- <resheader name="resmimetype">text/microsoft-resx</resheader>
- <resheader name="version">2.0</resheader>
- <resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
- <resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
- <data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
- <data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
- <data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
- <value>[base64 mime encoded serialized .NET Framework object]</value>
- </data>
- <data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
- <value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
- <comment>This is a comment</comment>
- </data>
-
- There are any number of "resheader" rows that contain simple
- name/value pairs.
-
- Each data row contains a name, and value. The row also contains a
- type or mimetype. Type corresponds to a .NET class that support
- text/value conversion through the TypeConverter architecture.
- Classes that don't support this are serialized and stored with the
- mimetype set.
-
- The mimetype is used for serialized objects, and tells the
- ResXResourceReader how to depersist the object. This is currently not
- extensible. For a given mimetype the value must be set accordingly:
-
- Note - application/x-microsoft.net.object.binary.base64 is the format
- that the ResXResourceWriter will generate, however the reader can
- read any of the formats listed below.
-
- mimetype: application/x-microsoft.net.object.binary.base64
- value : The object must be serialized with
- : System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
- : and then encoded with base64 encoding.
-
- mimetype: application/x-microsoft.net.object.soap.base64
- value : The object must be serialized with
- : System.Runtime.Serialization.Formatters.Soap.SoapFormatter
- : and then encoded with base64 encoding.
-
- mimetype: application/x-microsoft.net.object.bytearray.base64
- value : The object must be serialized into a byte array
- : using a System.ComponentModel.TypeConverter
- : and then encoded with base64 encoding.
- -->
- <xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
- <xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
- <xsd:element name="root" msdata:IsDataSet="true">
- <xsd:complexType>
- <xsd:choice maxOccurs="unbounded">
- <xsd:element name="metadata">
- <xsd:complexType>
- <xsd:sequence>
- <xsd:element name="value" type="xsd:string" minOccurs="0" />
- </xsd:sequence>
- <xsd:attribute name="name" use="required" type="xsd:string" />
- <xsd:attribute name="type" type="xsd:string" />
- <xsd:attribute name="mimetype" type="xsd:string" />
- <xsd:attribute ref="xml:space" />
- </xsd:complexType>
- </xsd:element>
- <xsd:element name="assembly">
- <xsd:complexType>
- <xsd:attribute name="alias" type="xsd:string" />
- <xsd:attribute name="name" type="xsd:string" />
- </xsd:complexType>
- </xsd:element>
- <xsd:element name="data">
- <xsd:complexType>
- <xsd:sequence>
- <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
- <xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
- </xsd:sequence>
- <xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
- <xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
- <xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
- <xsd:attribute ref="xml:space" />
- </xsd:complexType>
- </xsd:element>
- <xsd:element name="resheader">
- <xsd:complexType>
- <xsd:sequence>
- <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
- </xsd:sequence>
- <xsd:attribute name="name" type="xsd:string" use="required" />
- </xsd:complexType>
- </xsd:element>
- </xsd:choice>
- </xsd:complexType>
- </xsd:element>
- </xsd:schema>
- <resheader name="resmimetype">
- <value>text/microsoft-resx</value>
- </resheader>
- <resheader name="version">
- <value>2.0</value>
- </resheader>
- <resheader name="reader">
- <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
- </resheader>
- <resheader name="writer">
- <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
- </resheader>
- <data name="ErrorDeleteForbid" xml:space="preserve">
- <value>普通用户不能删除其他用户的头像。</value>
- </data>
- <data name="ErrorDeleteUserNotExist" xml:space="preserve">
- <value>用户不存在。</value>
- </data>
- <data name="ErrorGetUserNotExist" xml:space="preserve">
- <value>用户不存在。</value>
- </data>
- <data name="ErrorPutBadFormatBadSize" xml:space="preserve">
- <value>图片不是正方形。</value>
- </data>
- <data name="ErrorPutBadFormatCantDecode" xml:space="preserve">
- <value>解码图片失败。</value>
- </data>
- <data name="ErrorPutBadFormatUnmatchedFormat" xml:space="preserve">
- <value>图片格式与请求头中指示的不一样。</value>
- </data>
- <data name="ErrorPutForbid" xml:space="preserve">
- <value>普通用户不能修改其他用户的头像。</value>
- </data>
- <data name="ErrorPutUserNotExist" xml:space="preserve">
- <value>用户不存在。</value>
- </data>
-</root> \ No newline at end of file
diff --git a/Timeline/Resources/Controllers/UserController.Designer.cs b/Timeline/Resources/Controllers/UserController.Designer.cs
index 0c9ac0d7..c8067614 100644
--- a/Timeline/Resources/Controllers/UserController.Designer.cs
+++ b/Timeline/Resources/Controllers/UserController.Designer.cs
@@ -61,56 +61,11 @@ namespace Timeline.Resources.Controllers {
}
/// <summary>
- /// Looks up a localized string similar to Old password is wrong..
+ /// Looks up a localized string similar to Unknown PutResult..
/// </summary>
- internal static string ErrorChangePasswordBadPassword {
+ internal static string ExceptionUnknownPutResult {
get {
- return ResourceManager.GetString("ErrorChangePasswordBadPassword", resourceCulture);
- }
- }
-
- /// <summary>
- /// Looks up a localized string similar to The new username {0} already exists..
- /// </summary>
- internal static string ErrorChangeUsernameAlreadyExist {
- get {
- return ResourceManager.GetString("ErrorChangeUsernameAlreadyExist", resourceCulture);
- }
- }
-
- /// <summary>
- /// Looks up a localized string similar to The old username {0} does not exist..
- /// </summary>
- internal static string ErrorChangeUsernameNotExist {
- get {
- return ResourceManager.GetString("ErrorChangeUsernameNotExist", resourceCulture);
- }
- }
-
- /// <summary>
- /// Looks up a localized string similar to The user does not exist..
- /// </summary>
- internal static string ErrorGetUserNotExist {
- get {
- return ResourceManager.GetString("ErrorGetUserNotExist", resourceCulture);
- }
- }
-
- /// <summary>
- /// Looks up a localized string similar to Can&apos;t patch a user that does not exist..
- /// </summary>
- internal static string ErrorPatchUserNotExist {
- get {
- return ResourceManager.GetString("ErrorPatchUserNotExist", resourceCulture);
- }
- }
-
- /// <summary>
- /// Looks up a localized string similar to Username is of bad format..
- /// </summary>
- internal static string ErrorPutBadUsername {
- get {
- return ResourceManager.GetString("ErrorPutBadUsername", resourceCulture);
+ return ResourceManager.GetString("ExceptionUnknownPutResult", resourceCulture);
}
}
@@ -124,20 +79,11 @@ namespace Timeline.Resources.Controllers {
}
/// <summary>
- /// Looks up a localized string similar to A user has changed password..
- /// </summary>
- internal static string LogChangePasswordSuccess {
- get {
- return ResourceManager.GetString("LogChangePasswordSuccess", resourceCulture);
- }
- }
-
- /// <summary>
/// Looks up a localized string similar to Attempt to change a user&apos;s username to a existent one failed..
/// </summary>
- internal static string LogChangeUsernameAlreadyExist {
+ internal static string LogChangeUsernameConflict {
get {
- return ResourceManager.GetString("LogChangeUsernameAlreadyExist", resourceCulture);
+ return ResourceManager.GetString("LogChangeUsernameConflict", resourceCulture);
}
}
@@ -151,33 +97,6 @@ namespace Timeline.Resources.Controllers {
}
/// <summary>
- /// Looks up a localized string similar to A user has changed username..
- /// </summary>
- internal static string LogChangeUsernameSuccess {
- get {
- return ResourceManager.GetString("LogChangeUsernameSuccess", resourceCulture);
- }
- }
-
- /// <summary>
- /// Looks up a localized string similar to A user has been deleted..
- /// </summary>
- internal static string LogDeleteDelete {
- get {
- return ResourceManager.GetString("LogDeleteDelete", resourceCulture);
- }
- }
-
- /// <summary>
- /// Looks up a localized string similar to Attempt to delete a user that does not exist..
- /// </summary>
- internal static string LogDeleteNotExist {
- get {
- return ResourceManager.GetString("LogDeleteNotExist", resourceCulture);
- }
- }
-
- /// <summary>
/// Looks up a localized string similar to Attempt to retrieve info of a user that does not exist failed..
/// </summary>
internal static string LogGetUserNotExist {
@@ -194,32 +113,5 @@ namespace Timeline.Resources.Controllers {
return ResourceManager.GetString("LogPatchUserNotExist", resourceCulture);
}
}
-
- /// <summary>
- /// Looks up a localized string similar to Attempt to create a user with bad username failed..
- /// </summary>
- internal static string LogPutBadUsername {
- get {
- return ResourceManager.GetString("LogPutBadUsername", resourceCulture);
- }
- }
-
- /// <summary>
- /// Looks up a localized string similar to A user has been created..
- /// </summary>
- internal static string LogPutCreate {
- get {
- return ResourceManager.GetString("LogPutCreate", resourceCulture);
- }
- }
-
- /// <summary>
- /// Looks up a localized string similar to A user has been modified..
- /// </summary>
- internal static string LogPutModify {
- get {
- return ResourceManager.GetString("LogPutModify", resourceCulture);
- }
- }
}
}
diff --git a/Timeline/Resources/Controllers/UserController.resx b/Timeline/Resources/Controllers/UserController.resx
index 50aa13d6..0bdf4845 100644
--- a/Timeline/Resources/Controllers/UserController.resx
+++ b/Timeline/Resources/Controllers/UserController.resx
@@ -117,58 +117,22 @@
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
- <data name="ErrorChangePasswordBadPassword" xml:space="preserve">
- <value>Old password is wrong.</value>
- </data>
- <data name="ErrorChangeUsernameAlreadyExist" xml:space="preserve">
- <value>The new username {0} already exists.</value>
- </data>
- <data name="ErrorChangeUsernameNotExist" xml:space="preserve">
- <value>The old username {0} does not exist.</value>
- </data>
- <data name="ErrorGetUserNotExist" xml:space="preserve">
- <value>The user does not exist.</value>
- </data>
- <data name="ErrorPatchUserNotExist" xml:space="preserve">
- <value>Can't patch a user that does not exist.</value>
- </data>
- <data name="ErrorPutBadUsername" xml:space="preserve">
- <value>Username is of bad format.</value>
+ <data name="ExceptionUnknownPutResult" xml:space="preserve">
+ <value>Unknown PutResult.</value>
</data>
<data name="LogChangePasswordBadPassword" xml:space="preserve">
<value>Attempt to change password with wrong old password failed.</value>
</data>
- <data name="LogChangePasswordSuccess" xml:space="preserve">
- <value>A user has changed password.</value>
- </data>
- <data name="LogChangeUsernameAlreadyExist" xml:space="preserve">
+ <data name="LogChangeUsernameConflict" xml:space="preserve">
<value>Attempt to change a user's username to a existent one failed.</value>
</data>
<data name="LogChangeUsernameNotExist" xml:space="preserve">
<value>Attempt to change a username of a user that does not exist failed.</value>
</data>
- <data name="LogChangeUsernameSuccess" xml:space="preserve">
- <value>A user has changed username.</value>
- </data>
- <data name="LogDeleteDelete" xml:space="preserve">
- <value>A user has been deleted.</value>
- </data>
- <data name="LogDeleteNotExist" xml:space="preserve">
- <value>Attempt to delete a user that does not exist.</value>
- </data>
<data name="LogGetUserNotExist" xml:space="preserve">
<value>Attempt to retrieve info of a user that does not exist failed.</value>
</data>
<data name="LogPatchUserNotExist" xml:space="preserve">
<value>Attempt to patch a user that does not exist failed.</value>
</data>
- <data name="LogPutBadUsername" xml:space="preserve">
- <value>Attempt to create a user with bad username failed.</value>
- </data>
- <data name="LogPutCreate" xml:space="preserve">
- <value>A user has been created.</value>
- </data>
- <data name="LogPutModify" xml:space="preserve">
- <value>A user has been modified.</value>
- </data>
</root> \ No newline at end of file
diff --git a/Timeline/Resources/Controllers/UserController.zh.resx b/Timeline/Resources/Controllers/UserController.zh.resx
deleted file mode 100644
index 3556083e..00000000
--- a/Timeline/Resources/Controllers/UserController.zh.resx
+++ /dev/null
@@ -1,138 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<root>
- <!--
- Microsoft ResX Schema
-
- Version 2.0
-
- The primary goals of this format is to allow a simple XML format
- that is mostly human readable. The generation and parsing of the
- various data types are done through the TypeConverter classes
- associated with the data types.
-
- Example:
-
- ... ado.net/XML headers & schema ...
- <resheader name="resmimetype">text/microsoft-resx</resheader>
- <resheader name="version">2.0</resheader>
- <resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
- <resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
- <data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
- <data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
- <data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
- <value>[base64 mime encoded serialized .NET Framework object]</value>
- </data>
- <data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
- <value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
- <comment>This is a comment</comment>
- </data>
-
- There are any number of "resheader" rows that contain simple
- name/value pairs.
-
- Each data row contains a name, and value. The row also contains a
- type or mimetype. Type corresponds to a .NET class that support
- text/value conversion through the TypeConverter architecture.
- Classes that don't support this are serialized and stored with the
- mimetype set.
-
- The mimetype is used for serialized objects, and tells the
- ResXResourceReader how to depersist the object. This is currently not
- extensible. For a given mimetype the value must be set accordingly:
-
- Note - application/x-microsoft.net.object.binary.base64 is the format
- that the ResXResourceWriter will generate, however the reader can
- read any of the formats listed below.
-
- mimetype: application/x-microsoft.net.object.binary.base64
- value : The object must be serialized with
- : System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
- : and then encoded with base64 encoding.
-
- mimetype: application/x-microsoft.net.object.soap.base64
- value : The object must be serialized with
- : System.Runtime.Serialization.Formatters.Soap.SoapFormatter
- : and then encoded with base64 encoding.
-
- mimetype: application/x-microsoft.net.object.bytearray.base64
- value : The object must be serialized into a byte array
- : using a System.ComponentModel.TypeConverter
- : and then encoded with base64 encoding.
- -->
- <xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
- <xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
- <xsd:element name="root" msdata:IsDataSet="true">
- <xsd:complexType>
- <xsd:choice maxOccurs="unbounded">
- <xsd:element name="metadata">
- <xsd:complexType>
- <xsd:sequence>
- <xsd:element name="value" type="xsd:string" minOccurs="0" />
- </xsd:sequence>
- <xsd:attribute name="name" use="required" type="xsd:string" />
- <xsd:attribute name="type" type="xsd:string" />
- <xsd:attribute name="mimetype" type="xsd:string" />
- <xsd:attribute ref="xml:space" />
- </xsd:complexType>
- </xsd:element>
- <xsd:element name="assembly">
- <xsd:complexType>
- <xsd:attribute name="alias" type="xsd:string" />
- <xsd:attribute name="name" type="xsd:string" />
- </xsd:complexType>
- </xsd:element>
- <xsd:element name="data">
- <xsd:complexType>
- <xsd:sequence>
- <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
- <xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
- </xsd:sequence>
- <xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
- <xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
- <xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
- <xsd:attribute ref="xml:space" />
- </xsd:complexType>
- </xsd:element>
- <xsd:element name="resheader">
- <xsd:complexType>
- <xsd:sequence>
- <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
- </xsd:sequence>
- <xsd:attribute name="name" type="xsd:string" use="required" />
- </xsd:complexType>
- </xsd:element>
- </xsd:choice>
- </xsd:complexType>
- </xsd:element>
- </xsd:schema>
- <resheader name="resmimetype">
- <value>text/microsoft-resx</value>
- </resheader>
- <resheader name="version">
- <value>2.0</value>
- </resheader>
- <resheader name="reader">
- <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
- </resheader>
- <resheader name="writer">
- <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
- </resheader>
- <data name="ErrorChangePasswordBadPassword" xml:space="preserve">
- <value>旧密码错误。</value>
- </data>
- <data name="ErrorChangeUsernameAlreadyExist" xml:space="preserve">
- <value>新用户名{0}已经存在。</value>
- </data>
- <data name="ErrorChangeUsernameNotExist" xml:space="preserve">
- <value>旧用户名{0}不存在。</value>
- </data>
- <data name="ErrorGetUserNotExist" xml:space="preserve">
- <value>用户不存在。</value>
- </data>
- <data name="ErrorPatchUserNotExist" xml:space="preserve">
- <value>不能修改一个不存在的用户。</value>
- </data>
- <data name="ErrorPutBadUsername" xml:space="preserve">
- <value>用户名格式错误。</value>
- </data>
-</root> \ No newline at end of file
diff --git a/Timeline/Resources/Filters.Designer.cs b/Timeline/Resources/Filters.Designer.cs
index 5576190d..dedfe498 100644
--- a/Timeline/Resources/Filters.Designer.cs
+++ b/Timeline/Resources/Filters.Designer.cs
@@ -86,68 +86,5 @@ namespace Timeline.Resources {
return ResourceManager.GetString("LogSelfOrAdminUsernameNotString", resourceCulture);
}
}
-
- /// <summary>
- /// Looks up a localized string similar to Header Content-Length is missing or of bad format..
- /// </summary>
- internal static string MessageHeaderContentLengthMissing {
- get {
- return ResourceManager.GetString("MessageHeaderContentLengthMissing", resourceCulture);
- }
- }
-
- /// <summary>
- /// Looks up a localized string similar to Header Content-Length must not be 0..
- /// </summary>
- internal static string MessageHeaderContentLengthZero {
- get {
- return ResourceManager.GetString("MessageHeaderContentLengthZero", resourceCulture);
- }
- }
-
- /// <summary>
- /// Looks up a localized string similar to Header Content-Type is required..
- /// </summary>
- internal static string MessageHeaderContentTypeMissing {
- get {
- return ResourceManager.GetString("MessageHeaderContentTypeMissing", resourceCulture);
- }
- }
-
- /// <summary>
- /// Looks up a localized string similar to You can&apos;t access the resource unless you are the owner or administrator..
- /// </summary>
- internal static string MessageSelfOrAdminForbid {
- get {
- return ResourceManager.GetString("MessageSelfOrAdminForbid", resourceCulture);
- }
- }
-
- /// <summary>
- /// Looks up a localized string similar to The requested timeline does not exist..
- /// </summary>
- internal static string MessageTimelineNotExist {
- get {
- return ResourceManager.GetString("MessageTimelineNotExist", resourceCulture);
- }
- }
-
- /// <summary>
- /// Looks up a localized string similar to The requested personal timeline does not exist because the user does not exist..
- /// </summary>
- internal static string MessageTimelineNotExistUser {
- get {
- return ResourceManager.GetString("MessageTimelineNotExistUser", resourceCulture);
- }
- }
-
- /// <summary>
- /// Looks up a localized string similar to The user does not exist..
- /// </summary>
- internal static string MessageUserNotExist {
- get {
- return ResourceManager.GetString("MessageUserNotExist", resourceCulture);
- }
- }
}
}
diff --git a/Timeline/Resources/Filters.resx b/Timeline/Resources/Filters.resx
index 7bfbc703..22620889 100644
--- a/Timeline/Resources/Filters.resx
+++ b/Timeline/Resources/Filters.resx
@@ -126,25 +126,4 @@
<data name="LogSelfOrAdminUsernameNotString" xml:space="preserve">
<value>You apply a SelfOrAdminAttribute on an action, found a model named username, but it is not string.</value>
</data>
- <data name="MessageHeaderContentLengthMissing" xml:space="preserve">
- <value>Header Content-Length is missing or of bad format.</value>
- </data>
- <data name="MessageHeaderContentLengthZero" xml:space="preserve">
- <value>Header Content-Length must not be 0.</value>
- </data>
- <data name="MessageHeaderContentTypeMissing" xml:space="preserve">
- <value>Header Content-Type is required.</value>
- </data>
- <data name="MessageSelfOrAdminForbid" xml:space="preserve">
- <value>You can't access the resource unless you are the owner or administrator.</value>
- </data>
- <data name="MessageTimelineNotExist" xml:space="preserve">
- <value>The requested timeline does not exist.</value>
- </data>
- <data name="MessageTimelineNotExistUser" xml:space="preserve">
- <value>The requested personal timeline does not exist because the user does not exist.</value>
- </data>
- <data name="MessageUserNotExist" xml:space="preserve">
- <value>The user does not exist.</value>
- </data>
</root> \ No newline at end of file
diff --git a/Timeline/Resources/Filters.zh.resx b/Timeline/Resources/Filters.zh.resx
deleted file mode 100644
index 36aac788..00000000
--- a/Timeline/Resources/Filters.zh.resx
+++ /dev/null
@@ -1,141 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<root>
- <!--
- Microsoft ResX Schema
-
- Version 2.0
-
- The primary goals of this format is to allow a simple XML format
- that is mostly human readable. The generation and parsing of the
- various data types are done through the TypeConverter classes
- associated with the data types.
-
- Example:
-
- ... ado.net/XML headers & schema ...
- <resheader name="resmimetype">text/microsoft-resx</resheader>
- <resheader name="version">2.0</resheader>
- <resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
- <resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
- <data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
- <data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
- <data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
- <value>[base64 mime encoded serialized .NET Framework object]</value>
- </data>
- <data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
- <value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
- <comment>This is a comment</comment>
- </data>
-
- There are any number of "resheader" rows that contain simple
- name/value pairs.
-
- Each data row contains a name, and value. The row also contains a
- type or mimetype. Type corresponds to a .NET class that support
- text/value conversion through the TypeConverter architecture.
- Classes that don't support this are serialized and stored with the
- mimetype set.
-
- The mimetype is used for serialized objects, and tells the
- ResXResourceReader how to depersist the object. This is currently not
- extensible. For a given mimetype the value must be set accordingly:
-
- Note - application/x-microsoft.net.object.binary.base64 is the format
- that the ResXResourceWriter will generate, however the reader can
- read any of the formats listed below.
-
- mimetype: application/x-microsoft.net.object.binary.base64
- value : The object must be serialized with
- : System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
- : and then encoded with base64 encoding.
-
- mimetype: application/x-microsoft.net.object.soap.base64
- value : The object must be serialized with
- : System.Runtime.Serialization.Formatters.Soap.SoapFormatter
- : and then encoded with base64 encoding.
-
- mimetype: application/x-microsoft.net.object.bytearray.base64
- value : The object must be serialized into a byte array
- : using a System.ComponentModel.TypeConverter
- : and then encoded with base64 encoding.
- -->
- <xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
- <xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
- <xsd:element name="root" msdata:IsDataSet="true">
- <xsd:complexType>
- <xsd:choice maxOccurs="unbounded">
- <xsd:element name="metadata">
- <xsd:complexType>
- <xsd:sequence>
- <xsd:element name="value" type="xsd:string" minOccurs="0" />
- </xsd:sequence>
- <xsd:attribute name="name" use="required" type="xsd:string" />
- <xsd:attribute name="type" type="xsd:string" />
- <xsd:attribute name="mimetype" type="xsd:string" />
- <xsd:attribute ref="xml:space" />
- </xsd:complexType>
- </xsd:element>
- <xsd:element name="assembly">
- <xsd:complexType>
- <xsd:attribute name="alias" type="xsd:string" />
- <xsd:attribute name="name" type="xsd:string" />
- </xsd:complexType>
- </xsd:element>
- <xsd:element name="data">
- <xsd:complexType>
- <xsd:sequence>
- <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
- <xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
- </xsd:sequence>
- <xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
- <xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
- <xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
- <xsd:attribute ref="xml:space" />
- </xsd:complexType>
- </xsd:element>
- <xsd:element name="resheader">
- <xsd:complexType>
- <xsd:sequence>
- <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
- </xsd:sequence>
- <xsd:attribute name="name" type="xsd:string" use="required" />
- </xsd:complexType>
- </xsd:element>
- </xsd:choice>
- </xsd:complexType>
- </xsd:element>
- </xsd:schema>
- <resheader name="resmimetype">
- <value>text/microsoft-resx</value>
- </resheader>
- <resheader name="version">
- <value>2.0</value>
- </resheader>
- <resheader name="reader">
- <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
- </resheader>
- <resheader name="writer">
- <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
- </resheader>
- <data name="MessageHeaderContentLengthMissing" xml:space="preserve">
- <value>请求头Content-Length缺失或者格式不对。</value>
- </data>
- <data name="MessageHeaderContentLengthZero" xml:space="preserve">
- <value>请求头Content-Length不能为0。</value>
- </data>
- <data name="MessageHeaderContentTypeMissing" xml:space="preserve">
- <value>缺少必需的请求头Content-Type。</value>
- </data>
- <data name="MessageSelfOrAdminForbid" xml:space="preserve">
- <value>你无权访问该资源除非你是资源的拥有者或者管理员。</value>
- </data>
- <data name="MessageTimelineNotExist" xml:space="preserve">
- <value>请求的时间线不存在。</value>
- </data>
- <data name="MessageTimelineNotExistUser" xml:space="preserve">
- <value>请求的个人时间线不存在因为该用户不存在。</value>
- </data>
- <data name="MessageUserNotExist" xml:space="preserve">
- <value>用户不存在。</value>
- </data>
-</root> \ No newline at end of file
diff --git a/Timeline/Resources/Messages.Designer.cs b/Timeline/Resources/Messages.Designer.cs
new file mode 100644
index 00000000..332c8817
--- /dev/null
+++ b/Timeline/Resources/Messages.Designer.cs
@@ -0,0 +1,315 @@
+//------------------------------------------------------------------------------
+// <auto-generated>
+// This code was generated by a tool.
+// Runtime Version:4.0.30319.42000
+//
+// Changes to this file may cause incorrect behavior and will be lost if
+// the code is regenerated.
+// </auto-generated>
+//------------------------------------------------------------------------------
+
+namespace Timeline.Resources {
+ using System;
+
+
+ /// <summary>
+ /// A strongly-typed resource class, for looking up localized strings, etc.
+ /// </summary>
+ // This class was auto-generated by the StronglyTypedResourceBuilder
+ // class via a tool like ResGen or Visual Studio.
+ // To add or remove a member, edit your .ResX file then rerun ResGen
+ // with the /str option, or rebuild your VS project.
+ [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "16.0.0.0")]
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
+ [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
+ internal class Messages {
+
+ private static global::System.Resources.ResourceManager resourceMan;
+
+ private static global::System.Globalization.CultureInfo resourceCulture;
+
+ [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
+ internal Messages() {
+ }
+
+ /// <summary>
+ /// Returns the cached ResourceManager instance used by this class.
+ /// </summary>
+ [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
+ internal static global::System.Resources.ResourceManager ResourceManager {
+ get {
+ if (object.ReferenceEquals(resourceMan, null)) {
+ global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Timeline.Resources.Messages", typeof(Messages).Assembly);
+ resourceMan = temp;
+ }
+ return resourceMan;
+ }
+ }
+
+ /// <summary>
+ /// Overrides the current thread's CurrentUICulture property for all
+ /// resource lookups using this strongly typed resource class.
+ /// </summary>
+ [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
+ internal static global::System.Globalization.CultureInfo Culture {
+ get {
+ return resourceCulture;
+ }
+ set {
+ resourceCulture = value;
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to Body is too big. It can&apos;t be bigger than {0}..
+ /// </summary>
+ internal static string Common_Content_TooBig {
+ get {
+ return ResourceManager.GetString("Common_Content_TooBig", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to Actual body length is bigger than it in header..
+ /// </summary>
+ internal static string Common_Content_UnmatchedLength_Bigger {
+ get {
+ return ResourceManager.GetString("Common_Content_UnmatchedLength_Bigger", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to Actual body length is smaller than it in header..
+ /// </summary>
+ internal static string Common_Content_UnmatchedLength_Smaller {
+ get {
+ return ResourceManager.GetString("Common_Content_UnmatchedLength_Smaller", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to You have no permission to do the operation..
+ /// </summary>
+ internal static string Common_Forbid {
+ get {
+ return ResourceManager.GetString("Common_Forbid", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to You are not the resource owner..
+ /// </summary>
+ internal static string Common_Forbid_NotSelf {
+ get {
+ return ResourceManager.GetString("Common_Forbid_NotSelf", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to Header Content-Length is missing or of bad format..
+ /// </summary>
+ internal static string Common_Header_ContentLength_Missing {
+ get {
+ return ResourceManager.GetString("Common_Header_ContentLength_Missing", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to Header Content-Length must not be 0..
+ /// </summary>
+ internal static string Common_Header_ContentLength_Zero {
+ get {
+ return ResourceManager.GetString("Common_Header_ContentLength_Zero", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to Header Content-Type is missing..
+ /// </summary>
+ internal static string Common_Header_ContentType_Missing {
+ get {
+ return ResourceManager.GetString("Common_Header_ContentType_Missing", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to Header If-Non-Match is of bad format..
+ /// </summary>
+ internal static string Common_Header_IfNonMatch_BadFormat {
+ get {
+ return ResourceManager.GetString("Common_Header_IfNonMatch_BadFormat", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to Model is of bad format..
+ /// </summary>
+ internal static string Common_InvalidModel {
+ get {
+ return ResourceManager.GetString("Common_InvalidModel", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to The {0}-st username to do operation {1} on is of bad format..
+ /// </summary>
+ internal static string TimelineController_ChangeMember_UsernameBadFormat {
+ get {
+ return ResourceManager.GetString("TimelineController_ChangeMember_UsernameBadFormat", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to The {0}-st user to do operation {1} on does not exist..
+ /// </summary>
+ internal static string TimelineController_ChangeMember_UserNotExist {
+ get {
+ return ResourceManager.GetString("TimelineController_ChangeMember_UserNotExist", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to The user to set as member does not exist..
+ /// </summary>
+ internal static string TimelineController_MemberPut_NotExist {
+ get {
+ return ResourceManager.GetString("TimelineController_MemberPut_NotExist", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to The post to delete does not exist..
+ /// </summary>
+ internal static string TimelineController_PostOperationDelete_NotExist {
+ get {
+ return ResourceManager.GetString("TimelineController_PostOperationDelete_NotExist", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to Username or password is invalid..
+ /// </summary>
+ internal static string TokenController_Create_BadCredential {
+ get {
+ return ResourceManager.GetString("TokenController_Create_BadCredential", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to The token is of bad format. It might not be created by the server..
+ /// </summary>
+ internal static string TokenController_Verify_BadFormat {
+ get {
+ return ResourceManager.GetString("TokenController_Verify_BadFormat", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to Token has an old version. User might have update some info..
+ /// </summary>
+ internal static string TokenController_Verify_OldVersion {
+ get {
+ return ResourceManager.GetString("TokenController_Verify_OldVersion", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to The token is expired..
+ /// </summary>
+ internal static string TokenController_Verify_TimeExpired {
+ get {
+ return ResourceManager.GetString("TokenController_Verify_TimeExpired", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to User does not exist. Administrator might have deleted this user..
+ /// </summary>
+ internal static string TokenController_Verify_UserNotExist {
+ get {
+ return ResourceManager.GetString("TokenController_Verify_UserNotExist", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to Image is not a square..
+ /// </summary>
+ internal static string UserAvatar_BadFormat_BadSize {
+ get {
+ return ResourceManager.GetString("UserAvatar_BadFormat_BadSize", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to Image decode failed..
+ /// </summary>
+ internal static string UserAvatar_BadFormat_CantDecode {
+ get {
+ return ResourceManager.GetString("UserAvatar_BadFormat_CantDecode", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to Image format does not match the one in header..
+ /// </summary>
+ internal static string UserAvatar_BadFormat_UnmatchedFormat {
+ get {
+ return ResourceManager.GetString("UserAvatar_BadFormat_UnmatchedFormat", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to The user to operate on does not exist..
+ /// </summary>
+ internal static string UserCommon_NotExist {
+ get {
+ return ResourceManager.GetString("UserCommon_NotExist", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to Old password is wrong..
+ /// </summary>
+ internal static string UserController_ChangePassword_BadOldPassword {
+ get {
+ return ResourceManager.GetString("UserController_ChangePassword_BadOldPassword", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to You can&apos;t set permission unless you are administrator..
+ /// </summary>
+ internal static string UserController_Patch_Forbid_Administrator {
+ get {
+ return ResourceManager.GetString("UserController_Patch_Forbid_Administrator", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to You can&apos;t set password unless you are administrator. If you want to change password, use /userop/changepassword ..
+ /// </summary>
+ internal static string UserController_Patch_Forbid_Password {
+ get {
+ return ResourceManager.GetString("UserController_Patch_Forbid_Password", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to You can&apos;t set username unless you are administrator..
+ /// </summary>
+ internal static string UserController_Patch_Forbid_Username {
+ get {
+ return ResourceManager.GetString("UserController_Patch_Forbid_Username", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to A user with given username already exists..
+ /// </summary>
+ internal static string UserController_UsernameConflict {
+ get {
+ return ResourceManager.GetString("UserController_UsernameConflict", resourceCulture);
+ }
+ }
+ }
+}
diff --git a/Timeline/Resources/Models/Http/Common.zh.resx b/Timeline/Resources/Messages.resx
index 467916a2..cb6c3891 100644
--- a/Timeline/Resources/Models/Http/Common.zh.resx
+++ b/Timeline/Resources/Messages.resx
@@ -117,28 +117,88 @@
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
- <data name="MessageContentTooBig" xml:space="preserve">
- <value>请求体太大。它不能超过{0}.</value>
+ <data name="Common_Content_TooBig" xml:space="preserve">
+ <value>Body is too big. It can't be bigger than {0}.</value>
</data>
- <data name="MessageContentUnmatchedLengthBigger" xml:space="preserve">
- <value>实际的请求体长度比头中指示的大。</value>
+ <data name="Common_Content_UnmatchedLength_Bigger" xml:space="preserve">
+ <value>Actual body length is bigger than it in header.</value>
</data>
- <data name="MessageContentUnmatchedLengthSmaller" xml:space="preserve">
- <value>实际的请求体长度比头中指示的小。</value>
+ <data name="Common_Content_UnmatchedLength_Smaller" xml:space="preserve">
+ <value>Actual body length is smaller than it in header.</value>
</data>
- <data name="MessageDeleteDelete" xml:space="preserve">
- <value>删除了一个项目。</value>
+ <data name="Common_Forbid" xml:space="preserve">
+ <value>You have no permission to do the operation.</value>
</data>
- <data name="MessageDeleteNotExist" xml:space="preserve">
- <value>要删除的项目不存在,什么都没有修改。</value>
+ <data name="Common_Forbid_NotSelf" xml:space="preserve">
+ <value>You are not the resource owner.</value>
</data>
- <data name="MessageHeaderIfNonMatchBad" xml:space="preserve">
- <value>请求头If-Non-Match格式不对。</value>
+ <data name="Common_Header_ContentLength_Missing" xml:space="preserve">
+ <value>Header Content-Length is missing or of bad format.</value>
</data>
- <data name="MessagePutCreate" xml:space="preserve">
- <value>创建了一个新项目。</value>
+ <data name="Common_Header_ContentLength_Zero" xml:space="preserve">
+ <value>Header Content-Length must not be 0.</value>
</data>
- <data name="MessagePutModify" xml:space="preserve">
- <value>修改了一个已存在的项目。</value>
+ <data name="Common_Header_ContentType_Missing" xml:space="preserve">
+ <value>Header Content-Type is missing.</value>
+ </data>
+ <data name="Common_Header_IfNonMatch_BadFormat" xml:space="preserve">
+ <value>Header If-Non-Match is of bad format.</value>
+ </data>
+ <data name="Common_InvalidModel" xml:space="preserve">
+ <value>Model is of bad format.</value>
+ </data>
+ <data name="TimelineController_ChangeMember_UsernameBadFormat" xml:space="preserve">
+ <value>The {0}-st username to do operation {1} on is of bad format.</value>
+ </data>
+ <data name="TimelineController_ChangeMember_UserNotExist" xml:space="preserve">
+ <value>The {0}-st user to do operation {1} on does not exist.</value>
+ </data>
+ <data name="TimelineController_MemberPut_NotExist" xml:space="preserve">
+ <value>The user to set as member does not exist.</value>
+ </data>
+ <data name="TimelineController_PostOperationDelete_NotExist" xml:space="preserve">
+ <value>The post to delete does not exist.</value>
+ </data>
+ <data name="TokenController_Create_BadCredential" xml:space="preserve">
+ <value>Username or password is invalid.</value>
+ </data>
+ <data name="TokenController_Verify_BadFormat" xml:space="preserve">
+ <value>The token is of bad format. It might not be created by the server.</value>
+ </data>
+ <data name="TokenController_Verify_OldVersion" xml:space="preserve">
+ <value>Token has an old version. User might have update some info.</value>
+ </data>
+ <data name="TokenController_Verify_TimeExpired" xml:space="preserve">
+ <value>The token is expired.</value>
+ </data>
+ <data name="TokenController_Verify_UserNotExist" xml:space="preserve">
+ <value>User does not exist. Administrator might have deleted this user.</value>
+ </data>
+ <data name="UserAvatar_BadFormat_BadSize" xml:space="preserve">
+ <value>Image is not a square.</value>
+ </data>
+ <data name="UserAvatar_BadFormat_CantDecode" xml:space="preserve">
+ <value>Image decode failed.</value>
+ </data>
+ <data name="UserAvatar_BadFormat_UnmatchedFormat" xml:space="preserve">
+ <value>Image format does not match the one in header.</value>
+ </data>
+ <data name="UserCommon_NotExist" xml:space="preserve">
+ <value>The user to operate on does not exist.</value>
+ </data>
+ <data name="UserController_ChangePassword_BadOldPassword" xml:space="preserve">
+ <value>Old password is wrong.</value>
+ </data>
+ <data name="UserController_Patch_Forbid_Administrator" xml:space="preserve">
+ <value>You can't set permission unless you are administrator.</value>
+ </data>
+ <data name="UserController_Patch_Forbid_Password" xml:space="preserve">
+ <value>You can't set password unless you are administrator. If you want to change password, use /userop/changepassword .</value>
+ </data>
+ <data name="UserController_Patch_Forbid_Username" xml:space="preserve">
+ <value>You can't set username unless you are administrator.</value>
+ </data>
+ <data name="UserController_UsernameConflict" xml:space="preserve">
+ <value>A user with given username already exists.</value>
</data>
</root> \ No newline at end of file
diff --git a/Timeline/Resources/Models/Http/Common.Designer.cs b/Timeline/Resources/Models/Http/Common.Designer.cs
index 4eebd2bc..5165463e 100644
--- a/Timeline/Resources/Models/Http/Common.Designer.cs
+++ b/Timeline/Resources/Models/Http/Common.Designer.cs
@@ -61,33 +61,6 @@ namespace Timeline.Resources.Models.Http {
}
/// <summary>
- /// Looks up a localized string similar to Body is too big. It can&apos;t be bigger than {0}..
- /// </summary>
- internal static string MessageContentTooBig {
- get {
- return ResourceManager.GetString("MessageContentTooBig", resourceCulture);
- }
- }
-
- /// <summary>
- /// Looks up a localized string similar to Actual body length is bigger than it in header..
- /// </summary>
- internal static string MessageContentUnmatchedLengthBigger {
- get {
- return ResourceManager.GetString("MessageContentUnmatchedLengthBigger", resourceCulture);
- }
- }
-
- /// <summary>
- /// Looks up a localized string similar to Actual body length is smaller than it in header..
- /// </summary>
- internal static string MessageContentUnmatchedLengthSmaller {
- get {
- return ResourceManager.GetString("MessageContentUnmatchedLengthSmaller", resourceCulture);
- }
- }
-
- /// <summary>
/// Looks up a localized string similar to An existent item is deleted..
/// </summary>
internal static string MessageDeleteDelete {
@@ -106,15 +79,6 @@ namespace Timeline.Resources.Models.Http {
}
/// <summary>
- /// Looks up a localized string similar to Header If-Non-Match is of bad format..
- /// </summary>
- internal static string MessageHeaderIfNonMatchBad {
- get {
- return ResourceManager.GetString("MessageHeaderIfNonMatchBad", resourceCulture);
- }
- }
-
- /// <summary>
/// Looks up a localized string similar to A new item is created..
/// </summary>
internal static string MessagePutCreate {
diff --git a/Timeline/Resources/Models/Http/Common.resx b/Timeline/Resources/Models/Http/Common.resx
index 540c6c58..85ec4d32 100644
--- a/Timeline/Resources/Models/Http/Common.resx
+++ b/Timeline/Resources/Models/Http/Common.resx
@@ -117,24 +117,12 @@
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
- <data name="MessageContentTooBig" xml:space="preserve">
- <value>Body is too big. It can't be bigger than {0}.</value>
- </data>
- <data name="MessageContentUnmatchedLengthBigger" xml:space="preserve">
- <value>Actual body length is bigger than it in header.</value>
- </data>
- <data name="MessageContentUnmatchedLengthSmaller" xml:space="preserve">
- <value>Actual body length is smaller than it in header.</value>
- </data>
<data name="MessageDeleteDelete" xml:space="preserve">
<value>An existent item is deleted.</value>
</data>
<data name="MessageDeleteNotExist" xml:space="preserve">
<value>The item does not exist, so nothing is changed.</value>
</data>
- <data name="MessageHeaderIfNonMatchBad" xml:space="preserve">
- <value>Header If-Non-Match is of bad format.</value>
- </data>
<data name="MessagePutCreate" xml:space="preserve">
<value>A new item is created.</value>
</data>
diff --git a/Timeline/Resources/Common.Designer.cs b/Timeline/Resources/Models/Validation/NicknameValidator.Designer.cs
index 4f1c8e3f..522f305a 100644
--- a/Timeline/Resources/Common.Designer.cs
+++ b/Timeline/Resources/Models/Validation/NicknameValidator.Designer.cs
@@ -8,7 +8,7 @@
// </auto-generated>
//------------------------------------------------------------------------------
-namespace Timeline.Resources {
+namespace Timeline.Resources.Models.Validation {
using System;
@@ -22,14 +22,14 @@ namespace Timeline.Resources {
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "16.0.0.0")]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
- internal class Common {
+ internal class NicknameValidator {
private static global::System.Resources.ResourceManager resourceMan;
private static global::System.Globalization.CultureInfo resourceCulture;
[global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
- internal Common() {
+ internal NicknameValidator() {
}
/// <summary>
@@ -39,7 +39,7 @@ namespace Timeline.Resources {
internal static global::System.Resources.ResourceManager ResourceManager {
get {
if (object.ReferenceEquals(resourceMan, null)) {
- global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Timeline.Resources.Common", typeof(Common).Assembly);
+ global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Timeline.Resources.Models.Validation.NicknameValidator", typeof(NicknameValidator).Assembly);
resourceMan = temp;
}
return resourceMan;
@@ -61,11 +61,11 @@ namespace Timeline.Resources {
}
/// <summary>
- /// Looks up a localized string similar to The branch is invalid. Normally this branch is not reachable..
+ /// Looks up a localized string similar to Nickname is too long..
/// </summary>
- internal static string ExceptionInvalidBranch {
+ internal static string MessageTooLong {
get {
- return ResourceManager.GetString("ExceptionInvalidBranch", resourceCulture);
+ return ResourceManager.GetString("MessageTooLong", resourceCulture);
}
}
}
diff --git a/Timeline/Resources/Controllers/Testing/TestingI18nController.resx b/Timeline/Resources/Models/Validation/NicknameValidator.resx
index 57dfd5b9..b191b505 100644
--- a/Timeline/Resources/Controllers/Testing/TestingI18nController.resx
+++ b/Timeline/Resources/Models/Validation/NicknameValidator.resx
@@ -117,7 +117,7 @@
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
- <data name="TestString" xml:space="preserve">
- <value>English test string.</value>
+ <data name="MessageTooLong" xml:space="preserve">
+ <value>Nickname is too long.</value>
</data>
</root> \ No newline at end of file
diff --git a/Timeline/Resources/Models/Validation/UsernameValidator.zh.resx b/Timeline/Resources/Models/Validation/UsernameValidator.zh.resx
deleted file mode 100644
index 89d519b0..00000000
--- a/Timeline/Resources/Models/Validation/UsernameValidator.zh.resx
+++ /dev/null
@@ -1,129 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<root>
- <!--
- Microsoft ResX Schema
-
- Version 2.0
-
- The primary goals of this format is to allow a simple XML format
- that is mostly human readable. The generation and parsing of the
- various data types are done through the TypeConverter classes
- associated with the data types.
-
- Example:
-
- ... ado.net/XML headers & schema ...
- <resheader name="resmimetype">text/microsoft-resx</resheader>
- <resheader name="version">2.0</resheader>
- <resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
- <resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
- <data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
- <data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
- <data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
- <value>[base64 mime encoded serialized .NET Framework object]</value>
- </data>
- <data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
- <value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
- <comment>This is a comment</comment>
- </data>
-
- There are any number of "resheader" rows that contain simple
- name/value pairs.
-
- Each data row contains a name, and value. The row also contains a
- type or mimetype. Type corresponds to a .NET class that support
- text/value conversion through the TypeConverter architecture.
- Classes that don't support this are serialized and stored with the
- mimetype set.
-
- The mimetype is used for serialized objects, and tells the
- ResXResourceReader how to depersist the object. This is currently not
- extensible. For a given mimetype the value must be set accordingly:
-
- Note - application/x-microsoft.net.object.binary.base64 is the format
- that the ResXResourceWriter will generate, however the reader can
- read any of the formats listed below.
-
- mimetype: application/x-microsoft.net.object.binary.base64
- value : The object must be serialized with
- : System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
- : and then encoded with base64 encoding.
-
- mimetype: application/x-microsoft.net.object.soap.base64
- value : The object must be serialized with
- : System.Runtime.Serialization.Formatters.Soap.SoapFormatter
- : and then encoded with base64 encoding.
-
- mimetype: application/x-microsoft.net.object.bytearray.base64
- value : The object must be serialized into a byte array
- : using a System.ComponentModel.TypeConverter
- : and then encoded with base64 encoding.
- -->
- <xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
- <xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
- <xsd:element name="root" msdata:IsDataSet="true">
- <xsd:complexType>
- <xsd:choice maxOccurs="unbounded">
- <xsd:element name="metadata">
- <xsd:complexType>
- <xsd:sequence>
- <xsd:element name="value" type="xsd:string" minOccurs="0" />
- </xsd:sequence>
- <xsd:attribute name="name" use="required" type="xsd:string" />
- <xsd:attribute name="type" type="xsd:string" />
- <xsd:attribute name="mimetype" type="xsd:string" />
- <xsd:attribute ref="xml:space" />
- </xsd:complexType>
- </xsd:element>
- <xsd:element name="assembly">
- <xsd:complexType>
- <xsd:attribute name="alias" type="xsd:string" />
- <xsd:attribute name="name" type="xsd:string" />
- </xsd:complexType>
- </xsd:element>
- <xsd:element name="data">
- <xsd:complexType>
- <xsd:sequence>
- <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
- <xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
- </xsd:sequence>
- <xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
- <xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
- <xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
- <xsd:attribute ref="xml:space" />
- </xsd:complexType>
- </xsd:element>
- <xsd:element name="resheader">
- <xsd:complexType>
- <xsd:sequence>
- <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
- </xsd:sequence>
- <xsd:attribute name="name" type="xsd:string" use="required" />
- </xsd:complexType>
- </xsd:element>
- </xsd:choice>
- </xsd:complexType>
- </xsd:element>
- </xsd:schema>
- <resheader name="resmimetype">
- <value>text/microsoft-resx</value>
- </resheader>
- <resheader name="version">
- <value>2.0</value>
- </resheader>
- <resheader name="reader">
- <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
- </resheader>
- <resheader name="writer">
- <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
- </resheader>
- <data name="MessageEmptyString" xml:space="preserve">
- <value>空字符串是不允许的。</value>
- </data>
- <data name="MessageInvalidChar" xml:space="preserve">
- <value>无效的字符,只能使用字母、数字、下划线和连字符。</value>
- </data>
- <data name="MessageTooLong" xml:space="preserve">
- <value>太长了,不能大于26个字符。</value>
- </data>
-</root> \ No newline at end of file
diff --git a/Timeline/Resources/Services/Exception.Designer.cs b/Timeline/Resources/Services/Exception.Designer.cs
index 1b46f9e9..e6806873 100644
--- a/Timeline/Resources/Services/Exception.Designer.cs
+++ b/Timeline/Resources/Services/Exception.Designer.cs
@@ -115,6 +115,15 @@ namespace Timeline.Resources.Services {
}
/// <summary>
+ /// Looks up a localized string similar to A present resource conflicts with the given resource..
+ /// </summary>
+ internal static string ConflictException {
+ get {
+ return ResourceManager.GetString("ConflictException", resourceCulture);
+ }
+ }
+
+ /// <summary>
/// Looks up a localized string similar to The hashes password is of bad format. It might not be created by server..
/// </summary>
internal static string HashedPasswordBadFromatException {
@@ -178,128 +187,74 @@ namespace Timeline.Resources.Services {
}
/// <summary>
- /// Looks up a localized string similar to The version of the jwt token is old..
- /// </summary>
- internal static string JwtBadVersionException {
- get {
- return ResourceManager.GetString("JwtBadVersionException", resourceCulture);
- }
- }
-
- /// <summary>
- /// Looks up a localized string similar to The token didn&apos;t pass verification because {0}, see inner exception for information..
- /// </summary>
- internal static string JwtVerifyException {
- get {
- return ResourceManager.GetString("JwtVerifyException", resourceCulture);
- }
- }
-
- /// <summary>
- /// Looks up a localized string similar to token is expired..
+ /// Looks up a localized string similar to The token didn&apos;t pass verification because {0}..
/// </summary>
- internal static string JwtVerifyExceptionExpired {
+ internal static string JwtUserTokenBadFormatException {
get {
- return ResourceManager.GetString("JwtVerifyExceptionExpired", resourceCulture);
+ return ResourceManager.GetString("JwtUserTokenBadFormatException", resourceCulture);
}
}
/// <summary>
- /// Looks up a localized string similar to id claim is not a number..
+ /// Looks up a localized string similar to id claim is not a number.
/// </summary>
- internal static string JwtVerifyExceptionIdClaimBadFormat {
+ internal static string JwtUserTokenBadFormatExceptionIdBadFormat {
get {
- return ResourceManager.GetString("JwtVerifyExceptionIdClaimBadFormat", resourceCulture);
+ return ResourceManager.GetString("JwtUserTokenBadFormatExceptionIdBadFormat", resourceCulture);
}
}
/// <summary>
- /// Looks up a localized string similar to id claim does not exist..
+ /// Looks up a localized string similar to id claim does not exist.
/// </summary>
- internal static string JwtVerifyExceptionNoIdClaim {
+ internal static string JwtUserTokenBadFormatExceptionIdMissing {
get {
- return ResourceManager.GetString("JwtVerifyExceptionNoIdClaim", resourceCulture);
+ return ResourceManager.GetString("JwtUserTokenBadFormatExceptionIdMissing", resourceCulture);
}
}
/// <summary>
- /// Looks up a localized string similar to version claim does not exist..
+ /// Looks up a localized string similar to other error, see inner exception for information.
/// </summary>
- internal static string JwtVerifyExceptionNoVersionClaim {
+ internal static string JwtUserTokenBadFormatExceptionOthers {
get {
- return ResourceManager.GetString("JwtVerifyExceptionNoVersionClaim", resourceCulture);
+ return ResourceManager.GetString("JwtUserTokenBadFormatExceptionOthers", resourceCulture);
}
}
/// <summary>
- /// Looks up a localized string similar to version of token is old..
- /// </summary>
- internal static string JwtVerifyExceptionOldVersion {
- get {
- return ResourceManager.GetString("JwtVerifyExceptionOldVersion", resourceCulture);
- }
- }
-
- /// <summary>
- /// Looks up a localized string similar to uncommon error..
- /// </summary>
- internal static string JwtVerifyExceptionOthers {
- get {
- return ResourceManager.GetString("JwtVerifyExceptionOthers", resourceCulture);
- }
- }
-
- /// <summary>
- /// Looks up a localized string similar to unknown error code..
+ /// Looks up a localized string similar to unknown error.
/// </summary>
- internal static string JwtVerifyExceptionUnknown {
+ internal static string JwtUserTokenBadFormatExceptionUnknown {
get {
- return ResourceManager.GetString("JwtVerifyExceptionUnknown", resourceCulture);
+ return ResourceManager.GetString("JwtUserTokenBadFormatExceptionUnknown", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to version claim is not a number..
/// </summary>
- internal static string JwtVerifyExceptionVersionClaimBadFormat {
+ internal static string JwtUserTokenBadFormatExceptionVersionBadFormat {
get {
- return ResourceManager.GetString("JwtVerifyExceptionVersionClaimBadFormat", resourceCulture);
+ return ResourceManager.GetString("JwtUserTokenBadFormatExceptionVersionBadFormat", resourceCulture);
}
}
/// <summary>
- /// Looks up a localized string similar to The timeline with that name already exists..
- /// </summary>
- internal static string TimelineAlreadyExistException {
- get {
- return ResourceManager.GetString("TimelineAlreadyExistException", resourceCulture);
- }
- }
-
- /// <summary>
- /// Looks up a localized string similar to An exception happened when add or remove member on timeline..
- /// </summary>
- internal static string TimelineMemberOperationException {
- get {
- return ResourceManager.GetString("TimelineMemberOperationException", resourceCulture);
- }
- }
-
- /// <summary>
- /// Looks up a localized string similar to An exception happened when do operation {0} on the {1} member on timeline..
+ /// Looks up a localized string similar to version claim does not exist..
/// </summary>
- internal static string TimelineMemberOperationExceptionDetail {
+ internal static string JwtUserTokenBadFormatExceptionVersionMissing {
get {
- return ResourceManager.GetString("TimelineMemberOperationExceptionDetail", resourceCulture);
+ return ResourceManager.GetString("JwtUserTokenBadFormatExceptionVersionMissing", resourceCulture);
}
}
/// <summary>
- /// Looks up a localized string similar to Timeline name is of bad format. If this is a personal timeline, it means the username is of bad format and inner exception should be a UsernameBadFormatException..
+ /// Looks up a localized string similar to Password is of bad format..
/// </summary>
- internal static string TimelineNameBadFormatException {
+ internal static string PasswordBadFormatException {
get {
- return ResourceManager.GetString("TimelineNameBadFormatException", resourceCulture);
+ return ResourceManager.GetString("PasswordBadFormatException", resourceCulture);
}
}
@@ -322,38 +277,38 @@ namespace Timeline.Resources.Services {
}
/// <summary>
- /// Looks up a localized string similar to The use is not a member of the timeline..
+ /// Looks up a localized string similar to The user does not exist..
/// </summary>
- internal static string TimelineUserNotMemberException {
+ internal static string UserNotExistException {
get {
- return ResourceManager.GetString("TimelineUserNotMemberException", resourceCulture);
+ return ResourceManager.GetString("UserNotExistException", resourceCulture);
}
}
/// <summary>
- /// Looks up a localized string similar to The username is of bad format..
+ /// Looks up a localized string similar to The token is of bad format, which means it may not be created by the server..
/// </summary>
- internal static string UsernameBadFormatException {
+ internal static string UserTokenBadFormatException {
get {
- return ResourceManager.GetString("UsernameBadFormatException", resourceCulture);
+ return ResourceManager.GetString("UserTokenBadFormatException", resourceCulture);
}
}
/// <summary>
- /// Looks up a localized string similar to The username already exists..
+ /// Looks up a localized string similar to The token is of bad version..
/// </summary>
- internal static string UsernameConfictException {
+ internal static string UserTokenBadVersionException {
get {
- return ResourceManager.GetString("UsernameConfictException", resourceCulture);
+ return ResourceManager.GetString("UserTokenBadVersionException", resourceCulture);
}
}
/// <summary>
- /// Looks up a localized string similar to The user does not exist..
+ /// Looks up a localized string similar to The token is expired because its expiration time has passed..
/// </summary>
- internal static string UserNotExistException {
+ internal static string UserTokenTimeExpireException {
get {
- return ResourceManager.GetString("UserNotExistException", resourceCulture);
+ return ResourceManager.GetString("UserTokenTimeExpireException", resourceCulture);
}
}
}
diff --git a/Timeline/Resources/Services/Exception.resx b/Timeline/Resources/Services/Exception.resx
index 1d9c0037..11ae5f27 100644
--- a/Timeline/Resources/Services/Exception.resx
+++ b/Timeline/Resources/Services/Exception.resx
@@ -135,6 +135,9 @@
<data name="BadPasswordException" xml:space="preserve">
<value>The password is wrong.</value>
</data>
+ <data name="ConflictException" xml:space="preserve">
+ <value>A present resource conflicts with the given resource.</value>
+ </data>
<data name="HashedPasswordBadFromatException" xml:space="preserve">
<value>The hashes password is of bad format. It might not be created by server.</value>
</data>
@@ -156,47 +159,29 @@
<data name="HashedPasswordBadFromatExceptionNotUnknownMarker" xml:space="preserve">
<value>Unknown format marker.</value>
</data>
- <data name="JwtBadVersionException" xml:space="preserve">
- <value>The version of the jwt token is old.</value>
- </data>
- <data name="JwtVerifyException" xml:space="preserve">
- <value>The token didn't pass verification because {0}, see inner exception for information.</value>
+ <data name="JwtUserTokenBadFormatException" xml:space="preserve">
+ <value>The token didn't pass verification because {0}.</value>
</data>
- <data name="JwtVerifyExceptionExpired" xml:space="preserve">
- <value>token is expired.</value>
+ <data name="JwtUserTokenBadFormatExceptionIdBadFormat" xml:space="preserve">
+ <value>id claim is not a number</value>
</data>
- <data name="JwtVerifyExceptionIdClaimBadFormat" xml:space="preserve">
- <value>id claim is not a number.</value>
+ <data name="JwtUserTokenBadFormatExceptionIdMissing" xml:space="preserve">
+ <value>id claim does not exist</value>
</data>
- <data name="JwtVerifyExceptionNoIdClaim" xml:space="preserve">
- <value>id claim does not exist.</value>
- </data>
- <data name="JwtVerifyExceptionNoVersionClaim" xml:space="preserve">
- <value>version claim does not exist.</value>
+ <data name="JwtUserTokenBadFormatExceptionOthers" xml:space="preserve">
+ <value>other error, see inner exception for information</value>
</data>
- <data name="JwtVerifyExceptionOldVersion" xml:space="preserve">
- <value>version of token is old.</value>
- </data>
- <data name="JwtVerifyExceptionOthers" xml:space="preserve">
- <value>uncommon error.</value>
- </data>
- <data name="JwtVerifyExceptionUnknown" xml:space="preserve">
- <value>unknown error code.</value>
+ <data name="JwtUserTokenBadFormatExceptionUnknown" xml:space="preserve">
+ <value>unknown error</value>
</data>
- <data name="JwtVerifyExceptionVersionClaimBadFormat" xml:space="preserve">
+ <data name="JwtUserTokenBadFormatExceptionVersionBadFormat" xml:space="preserve">
<value>version claim is not a number.</value>
</data>
- <data name="TimelineAlreadyExistException" xml:space="preserve">
- <value>The timeline with that name already exists.</value>
- </data>
- <data name="TimelineMemberOperationException" xml:space="preserve">
- <value>An exception happened when add or remove member on timeline.</value>
- </data>
- <data name="TimelineMemberOperationExceptionDetail" xml:space="preserve">
- <value>An exception happened when do operation {0} on the {1} member on timeline.</value>
+ <data name="JwtUserTokenBadFormatExceptionVersionMissing" xml:space="preserve">
+ <value>version claim does not exist.</value>
</data>
- <data name="TimelineNameBadFormatException" xml:space="preserve">
- <value>Timeline name is of bad format. If this is a personal timeline, it means the username is of bad format and inner exception should be a UsernameBadFormatException.</value>
+ <data name="PasswordBadFormatException" xml:space="preserve">
+ <value>Password is of bad format.</value>
</data>
<data name="TimelineNotExistException" xml:space="preserve">
<value>Timeline does not exist. If this is a personal timeline, it means the user does not exist and inner exception should be a UserNotExistException.</value>
@@ -204,16 +189,16 @@
<data name="TimelinePostNotExistException" xml:space="preserve">
<value>The timeline post does not exist. You can't do operation on it.</value>
</data>
- <data name="TimelineUserNotMemberException" xml:space="preserve">
- <value>The use is not a member of the timeline.</value>
+ <data name="UserNotExistException" xml:space="preserve">
+ <value>The user does not exist.</value>
</data>
- <data name="UsernameBadFormatException" xml:space="preserve">
- <value>The username is of bad format.</value>
+ <data name="UserTokenBadFormatException" xml:space="preserve">
+ <value>The token is of bad format, which means it may not be created by the server.</value>
</data>
- <data name="UsernameConfictException" xml:space="preserve">
- <value>The username already exists.</value>
+ <data name="UserTokenBadVersionException" xml:space="preserve">
+ <value>The token is of bad version.</value>
</data>
- <data name="UserNotExistException" xml:space="preserve">
- <value>The user does not exist.</value>
+ <data name="UserTokenTimeExpireException" xml:space="preserve">
+ <value>The token is expired because its expiration time has passed.</value>
</data>
</root> \ No newline at end of file
diff --git a/Timeline/Resources/Controllers/Testing/TestingI18nController.Designer.cs b/Timeline/Resources/Services/TimelineService.Designer.cs
index e015c5fc..8212c252 100644
--- a/Timeline/Resources/Controllers/Testing/TestingI18nController.Designer.cs
+++ b/Timeline/Resources/Services/TimelineService.Designer.cs
@@ -8,7 +8,7 @@
// </auto-generated>
//------------------------------------------------------------------------------
-namespace Timeline.Resources.Controllers.Testing {
+namespace Timeline.Resources.Services {
using System;
@@ -22,14 +22,14 @@ namespace Timeline.Resources.Controllers.Testing {
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "16.0.0.0")]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
- internal class TestingI18nController {
+ internal class TimelineService {
private static global::System.Resources.ResourceManager resourceMan;
private static global::System.Globalization.CultureInfo resourceCulture;
[global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
- internal TestingI18nController() {
+ internal TimelineService() {
}
/// <summary>
@@ -39,7 +39,7 @@ namespace Timeline.Resources.Controllers.Testing {
internal static global::System.Resources.ResourceManager ResourceManager {
get {
if (object.ReferenceEquals(resourceMan, null)) {
- global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Timeline.Resources.Controllers.Testing.TestingI18nController", typeof(TestingI18nController).Assembly);
+ global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Timeline.Resources.Services.TimelineService", typeof(TimelineService).Assembly);
resourceMan = temp;
}
return resourceMan;
@@ -61,11 +61,20 @@ namespace Timeline.Resources.Controllers.Testing {
}
/// <summary>
- /// Looks up a localized string similar to English test string..
+ /// Looks up a localized string similar to The number {0} username is invalid..
/// </summary>
- internal static string TestString {
+ internal static string ExceptionChangeMemberUsernameBadFormat {
get {
- return ResourceManager.GetString("TestString", resourceCulture);
+ return ResourceManager.GetString("ExceptionChangeMemberUsernameBadFormat", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to The owner username of personal timeline is of bad format..
+ /// </summary>
+ internal static string ExceptionFindTimelineUsernameBadFormat {
+ get {
+ return ResourceManager.GetString("ExceptionFindTimelineUsernameBadFormat", resourceCulture);
}
}
}
diff --git a/Timeline/Resources/Controllers/Testing/TestingI18nController.zh.resx b/Timeline/Resources/Services/TimelineService.resx
index 6931cdf6..0429a2f8 100644
--- a/Timeline/Resources/Controllers/Testing/TestingI18nController.zh.resx
+++ b/Timeline/Resources/Services/TimelineService.resx
@@ -117,7 +117,10 @@
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
- <data name="TestString" xml:space="preserve">
- <value>中文测试字符串。</value>
+ <data name="ExceptionChangeMemberUsernameBadFormat" xml:space="preserve">
+ <value>The number {0} username is invalid.</value>
+ </data>
+ <data name="ExceptionFindTimelineUsernameBadFormat" xml:space="preserve">
+ <value>The owner username of personal timeline is of bad format.</value>
</data>
</root> \ No newline at end of file
diff --git a/Timeline/Resources/Services/UserDetailService.resx b/Timeline/Resources/Services/UserDetailService.resx
deleted file mode 100644
index ea32aeda..00000000
--- a/Timeline/Resources/Services/UserDetailService.resx
+++ /dev/null
@@ -1,132 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<root>
- <!--
- Microsoft ResX Schema
-
- Version 2.0
-
- The primary goals of this format is to allow a simple XML format
- that is mostly human readable. The generation and parsing of the
- various data types are done through the TypeConverter classes
- associated with the data types.
-
- Example:
-
- ... ado.net/XML headers & schema ...
- <resheader name="resmimetype">text/microsoft-resx</resheader>
- <resheader name="version">2.0</resheader>
- <resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
- <resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
- <data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
- <data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
- <data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
- <value>[base64 mime encoded serialized .NET Framework object]</value>
- </data>
- <data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
- <value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
- <comment>This is a comment</comment>
- </data>
-
- There are any number of "resheader" rows that contain simple
- name/value pairs.
-
- Each data row contains a name, and value. The row also contains a
- type or mimetype. Type corresponds to a .NET class that support
- text/value conversion through the TypeConverter architecture.
- Classes that don't support this are serialized and stored with the
- mimetype set.
-
- The mimetype is used for serialized objects, and tells the
- ResXResourceReader how to depersist the object. This is currently not
- extensible. For a given mimetype the value must be set accordingly:
-
- Note - application/x-microsoft.net.object.binary.base64 is the format
- that the ResXResourceWriter will generate, however the reader can
- read any of the formats listed below.
-
- mimetype: application/x-microsoft.net.object.binary.base64
- value : The object must be serialized with
- : System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
- : and then encoded with base64 encoding.
-
- mimetype: application/x-microsoft.net.object.soap.base64
- value : The object must be serialized with
- : System.Runtime.Serialization.Formatters.Soap.SoapFormatter
- : and then encoded with base64 encoding.
-
- mimetype: application/x-microsoft.net.object.bytearray.base64
- value : The object must be serialized into a byte array
- : using a System.ComponentModel.TypeConverter
- : and then encoded with base64 encoding.
- -->
- <xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
- <xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
- <xsd:element name="root" msdata:IsDataSet="true">
- <xsd:complexType>
- <xsd:choice maxOccurs="unbounded">
- <xsd:element name="metadata">
- <xsd:complexType>
- <xsd:sequence>
- <xsd:element name="value" type="xsd:string" minOccurs="0" />
- </xsd:sequence>
- <xsd:attribute name="name" use="required" type="xsd:string" />
- <xsd:attribute name="type" type="xsd:string" />
- <xsd:attribute name="mimetype" type="xsd:string" />
- <xsd:attribute ref="xml:space" />
- </xsd:complexType>
- </xsd:element>
- <xsd:element name="assembly">
- <xsd:complexType>
- <xsd:attribute name="alias" type="xsd:string" />
- <xsd:attribute name="name" type="xsd:string" />
- </xsd:complexType>
- </xsd:element>
- <xsd:element name="data">
- <xsd:complexType>
- <xsd:sequence>
- <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
- <xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
- </xsd:sequence>
- <xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
- <xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
- <xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
- <xsd:attribute ref="xml:space" />
- </xsd:complexType>
- </xsd:element>
- <xsd:element name="resheader">
- <xsd:complexType>
- <xsd:sequence>
- <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
- </xsd:sequence>
- <xsd:attribute name="name" type="xsd:string" use="required" />
- </xsd:complexType>
- </xsd:element>
- </xsd:choice>
- </xsd:complexType>
- </xsd:element>
- </xsd:schema>
- <resheader name="resmimetype">
- <value>text/microsoft-resx</value>
- </resheader>
- <resheader name="version">
- <value>2.0</value>
- </resheader>
- <resheader name="reader">
- <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
- </resheader>
- <resheader name="writer">
- <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
- </resheader>
- <data name="ExceptionNicknameTooLong" xml:space="preserve">
- <value>Length of nickname can't be bigger than 10.</value>
- </data>
- <data name="LogEntityNicknameCreate" xml:space="preserve">
- <value>A user_details entity has been created. User id is {0}. Nickname is {1}.</value>
- </data>
- <data name="LogEntityNicknameSetNotNull" xml:space="preserve">
- <value>Nickname of a user_details entity has been updated to a new value. User id is {0}. New value is {1}.</value>
- </data>
- <data name="LogEntityNicknameSetToNull" xml:space="preserve">
- <value>Nickname of a user_details entity has been updated to null. User id is {0}.</value>
- </data>
-</root> \ No newline at end of file
diff --git a/Timeline/Resources/Services/UserService.Designer.cs b/Timeline/Resources/Services/UserService.Designer.cs
index 2a04dded..cdf7f390 100644
--- a/Timeline/Resources/Services/UserService.Designer.cs
+++ b/Timeline/Resources/Services/UserService.Designer.cs
@@ -70,6 +70,15 @@ namespace Timeline.Resources.Services {
}
/// <summary>
+ /// Looks up a localized string similar to Nickname is of bad format, because {}..
+ /// </summary>
+ internal static string ExceptionNicknameBadFormat {
+ get {
+ return ResourceManager.GetString("ExceptionNicknameBadFormat", resourceCulture);
+ }
+ }
+
+ /// <summary>
/// Looks up a localized string similar to Old username is of bad format..
/// </summary>
internal static string ExceptionOldUsernameBadFormat {
@@ -79,20 +88,47 @@ namespace Timeline.Resources.Services {
}
/// <summary>
- /// Looks up a localized string similar to A cache entry is created..
+ /// Looks up a localized string similar to Password can&apos;t be empty..
+ /// </summary>
+ internal static string ExceptionPasswordEmpty {
+ get {
+ return ResourceManager.GetString("ExceptionPasswordEmpty", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to Password can&apos;t be null..
+ /// </summary>
+ internal static string ExceptionPasswordNull {
+ get {
+ return ResourceManager.GetString("ExceptionPasswordNull", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to Username is of bad format, because {}..
+ /// </summary>
+ internal static string ExceptionUsernameBadFormat {
+ get {
+ return ResourceManager.GetString("ExceptionUsernameBadFormat", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to A user with given username already exists..
/// </summary>
- internal static string LogCacheCreate {
+ internal static string ExceptionUsernameConflict {
get {
- return ResourceManager.GetString("LogCacheCreate", resourceCulture);
+ return ResourceManager.GetString("ExceptionUsernameConflict", resourceCulture);
}
}
/// <summary>
- /// Looks up a localized string similar to A cache entry is removed..
+ /// Looks up a localized string similar to Username can&apos;t be null..
/// </summary>
- internal static string LogCacheRemove {
+ internal static string ExceptionUsernameNull {
get {
- return ResourceManager.GetString("LogCacheRemove", resourceCulture);
+ return ResourceManager.GetString("ExceptionUsernameNull", resourceCulture);
}
}
diff --git a/Timeline/Resources/Services/UserService.resx b/Timeline/Resources/Services/UserService.resx
index 3670d8f9..09bd4abb 100644
--- a/Timeline/Resources/Services/UserService.resx
+++ b/Timeline/Resources/Services/UserService.resx
@@ -120,14 +120,26 @@
<data name="ExceptionNewUsernameBadFormat" xml:space="preserve">
<value>New username is of bad format.</value>
</data>
+ <data name="ExceptionNicknameBadFormat" xml:space="preserve">
+ <value>Nickname is of bad format, because {}.</value>
+ </data>
<data name="ExceptionOldUsernameBadFormat" xml:space="preserve">
<value>Old username is of bad format.</value>
</data>
- <data name="LogCacheCreate" xml:space="preserve">
- <value>A cache entry is created.</value>
+ <data name="ExceptionPasswordEmpty" xml:space="preserve">
+ <value>Password can't be empty.</value>
+ </data>
+ <data name="ExceptionPasswordNull" xml:space="preserve">
+ <value>Password can't be null.</value>
+ </data>
+ <data name="ExceptionUsernameBadFormat" xml:space="preserve">
+ <value>Username is of bad format, because {}.</value>
+ </data>
+ <data name="ExceptionUsernameConflict" xml:space="preserve">
+ <value>A user with given username already exists.</value>
</data>
- <data name="LogCacheRemove" xml:space="preserve">
- <value>A cache entry is removed.</value>
+ <data name="ExceptionUsernameNull" xml:space="preserve">
+ <value>Username can't be null.</value>
</data>
<data name="LogDatabaseCreate" xml:space="preserve">
<value>A new user entry is added to the database.</value>
diff --git a/Timeline/Services/ConflictException.cs b/Timeline/Services/ConflictException.cs
new file mode 100644
index 00000000..6ede183a
--- /dev/null
+++ b/Timeline/Services/ConflictException.cs
@@ -0,0 +1,21 @@
+using System;
+
+namespace Timeline.Services
+{
+ /// <summary>
+ /// Thrown when a resource already exists and conflicts with the given resource.
+ /// </summary>
+ /// <remarks>
+ /// For example a username already exists and conflicts with the given username.
+ /// </remarks>
+ [Serializable]
+ public class ConflictException : Exception
+ {
+ public ConflictException() : base(Resources.Services.Exception.ConflictException) { }
+ public ConflictException(string message) : base(message) { }
+ public ConflictException(string message, Exception inner) : base(message, inner) { }
+ protected ConflictException(
+ System.Runtime.Serialization.SerializationInfo info,
+ System.Runtime.Serialization.StreamingContext context) : base(info, context) { }
+ }
+}
diff --git a/Timeline/Services/DatabaseExtensions.cs b/Timeline/Services/DatabaseExtensions.cs
deleted file mode 100644
index 140c3146..00000000
--- a/Timeline/Services/DatabaseExtensions.cs
+++ /dev/null
@@ -1,36 +0,0 @@
-using Microsoft.EntityFrameworkCore;
-using System;
-using System.Linq;
-using System.Threading.Tasks;
-using Timeline.Entities;
-using Timeline.Models.Validation;
-
-namespace Timeline.Services
-{
- internal static class DatabaseExtensions
- {
- private static readonly UsernameValidator usernameValidator = new UsernameValidator();
-
- /// <summary>
- /// Check the existence and get the id of the user.
- /// </summary>
- /// <param name="username">The username of the user.</param>
- /// <returns>The user id.</returns>
- /// <exception cref="ArgumentNullException">Thrown if <paramref name="username"/> is null.</exception>
- /// <exception cref="UsernameBadFormatException">Thrown if <paramref name="username"/> is of bad format.</exception>
- /// <exception cref="UserNotExistException">Thrown if user does not exist.</exception>
- internal static async Task<long> CheckAndGetUser(DbSet<User> userDbSet, string? username)
- {
- if (username == null)
- throw new ArgumentNullException(nameof(username));
- var (result, message) = usernameValidator.Validate(username);
- if (!result)
- throw new UsernameBadFormatException(username, message);
-
- var userId = await userDbSet.Where(u => u.Name == username).Select(u => u.Id).SingleOrDefaultAsync();
- if (userId == 0)
- throw new UserNotExistException(username);
- return userId;
- }
- }
-}
diff --git a/Timeline/Services/JwtBadVersionException.cs b/Timeline/Services/JwtBadVersionException.cs
deleted file mode 100644
index 4ce17710..00000000
--- a/Timeline/Services/JwtBadVersionException.cs
+++ /dev/null
@@ -1,36 +0,0 @@
-using System;
-using Timeline.Helpers;
-
-namespace Timeline.Services
-{
- [Serializable]
- public class JwtBadVersionException : Exception
- {
- public JwtBadVersionException() : base(Resources.Services.Exception.JwtBadVersionException) { }
- public JwtBadVersionException(string message) : base(message) { }
- public JwtBadVersionException(string message, Exception inner) : base(message, inner) { }
-
- public JwtBadVersionException(long tokenVersion, long requiredVersion)
- : base(Log.Format(Resources.Services.Exception.JwtBadVersionException,
- ("Token Version", tokenVersion),
- ("Required Version", requiredVersion)))
- {
- TokenVersion = tokenVersion;
- RequiredVersion = requiredVersion;
- }
-
- protected JwtBadVersionException(
- System.Runtime.Serialization.SerializationInfo info,
- System.Runtime.Serialization.StreamingContext context) : base(info, context) { }
-
- /// <summary>
- /// The version in the token.
- /// </summary>
- public long? TokenVersion { get; set; }
-
- /// <summary>
- /// The version required.
- /// </summary>
- public long? RequiredVersion { get; set; }
- }
-}
diff --git a/Timeline/Services/JwtUserTokenBadFormatException.cs b/Timeline/Services/JwtUserTokenBadFormatException.cs
new file mode 100644
index 00000000..c528c3e3
--- /dev/null
+++ b/Timeline/Services/JwtUserTokenBadFormatException.cs
@@ -0,0 +1,48 @@
+using System;
+using System.Globalization;
+using static Timeline.Resources.Services.Exception;
+
+namespace Timeline.Services
+{
+ [Serializable]
+ public class JwtUserTokenBadFormatException : UserTokenBadFormatException
+ {
+ public enum ErrorKind
+ {
+ NoIdClaim,
+ IdClaimBadFormat,
+ NoVersionClaim,
+ VersionClaimBadFormat,
+ Other
+ }
+
+ public JwtUserTokenBadFormatException() : this("", ErrorKind.Other) { }
+ public JwtUserTokenBadFormatException(string message) : base(message) { }
+ public JwtUserTokenBadFormatException(string message, Exception inner) : base(message, inner) { }
+
+ public JwtUserTokenBadFormatException(string token, ErrorKind type) : base(token, GetErrorMessage(type)) { ErrorType = type; }
+ public JwtUserTokenBadFormatException(string token, ErrorKind type, Exception inner) : base(token, GetErrorMessage(type), inner) { ErrorType = type; }
+ public JwtUserTokenBadFormatException(string token, ErrorKind type, string message, Exception inner) : base(token, message, inner) { ErrorType = type; }
+ protected JwtUserTokenBadFormatException(
+ System.Runtime.Serialization.SerializationInfo info,
+ System.Runtime.Serialization.StreamingContext context) : base(info, context) { }
+
+ public ErrorKind ErrorType { get; set; }
+
+ private static string GetErrorMessage(ErrorKind type)
+ {
+ var reason = type switch
+ {
+ ErrorKind.NoIdClaim => JwtUserTokenBadFormatExceptionIdMissing,
+ ErrorKind.IdClaimBadFormat => JwtUserTokenBadFormatExceptionIdBadFormat,
+ ErrorKind.NoVersionClaim => JwtUserTokenBadFormatExceptionVersionMissing,
+ ErrorKind.VersionClaimBadFormat => JwtUserTokenBadFormatExceptionVersionBadFormat,
+ ErrorKind.Other => JwtUserTokenBadFormatExceptionOthers,
+ _ => JwtUserTokenBadFormatExceptionUnknown
+ };
+
+ return string.Format(CultureInfo.CurrentCulture,
+ Resources.Services.Exception.JwtUserTokenBadFormatException, reason);
+ }
+ }
+}
diff --git a/Timeline/Services/JwtVerifyException.cs b/Timeline/Services/JwtVerifyException.cs
deleted file mode 100644
index a915b51a..00000000
--- a/Timeline/Services/JwtVerifyException.cs
+++ /dev/null
@@ -1,59 +0,0 @@
-using Microsoft.IdentityModel.Tokens;
-using System;
-using System.Globalization;
-using static Timeline.Resources.Services.Exception;
-
-namespace Timeline.Services
-{
- [Serializable]
- public class JwtVerifyException : Exception
- {
- public static class ErrorCodes
- {
- // Codes in -1000 ~ -1999 usually means the user provides a token that is not created by this server.
-
- public const int Others = -1001;
- public const int NoIdClaim = -1002;
- public const int IdClaimBadFormat = -1003;
- public const int NoVersionClaim = -1004;
- public const int VersionClaimBadFormat = -1005;
-
- /// <summary>
- /// Corresponds to <see cref="SecurityTokenExpiredException"/>.
- /// </summary>
- public const int Expired = -2001;
- public const int OldVersion = -2002;
- }
-
- public JwtVerifyException() : base(GetErrorMessage(0)) { }
- public JwtVerifyException(string message) : base(message) { }
- public JwtVerifyException(string message, Exception inner) : base(message, inner) { }
-
- public JwtVerifyException(int code) : base(GetErrorMessage(code)) { ErrorCode = code; }
- public JwtVerifyException(string message, int code) : base(message) { ErrorCode = code; }
- public JwtVerifyException(Exception inner, int code) : base(GetErrorMessage(code), inner) { ErrorCode = code; }
- public JwtVerifyException(string message, Exception inner, int code) : base(message, inner) { ErrorCode = code; }
- protected JwtVerifyException(
- System.Runtime.Serialization.SerializationInfo info,
- System.Runtime.Serialization.StreamingContext context) : base(info, context) { }
-
- public int ErrorCode { get; set; }
-
- private static string GetErrorMessage(int errorCode)
- {
- var reason = errorCode switch
- {
- ErrorCodes.Others => JwtVerifyExceptionOthers,
- ErrorCodes.NoIdClaim => JwtVerifyExceptionNoIdClaim,
- ErrorCodes.IdClaimBadFormat => JwtVerifyExceptionIdClaimBadFormat,
- ErrorCodes.NoVersionClaim => JwtVerifyExceptionNoVersionClaim,
- ErrorCodes.VersionClaimBadFormat => JwtVerifyExceptionVersionClaimBadFormat,
- ErrorCodes.Expired => JwtVerifyExceptionExpired,
- ErrorCodes.OldVersion => JwtVerifyExceptionOldVersion,
- _ => JwtVerifyExceptionUnknown
- };
-
- return string.Format(CultureInfo.InvariantCulture, Resources.Services.Exception.JwtVerifyException, reason);
- }
- }
-}
diff --git a/Timeline/Services/PasswordBadFormatException.cs b/Timeline/Services/PasswordBadFormatException.cs
new file mode 100644
index 00000000..2029ebb4
--- /dev/null
+++ b/Timeline/Services/PasswordBadFormatException.cs
@@ -0,0 +1,27 @@
+using System;
+
+namespace Timeline.Services
+{
+
+ [Serializable]
+ public class PasswordBadFormatException : Exception
+ {
+ public PasswordBadFormatException() : base(Resources.Services.Exception.PasswordBadFormatException) { }
+ public PasswordBadFormatException(string message) : base(message) { }
+ public PasswordBadFormatException(string message, Exception inner) : base(message, inner) { }
+
+ public PasswordBadFormatException(string password, string validationMessage) : this()
+ {
+ Password = password;
+ ValidationMessage = validationMessage;
+ }
+
+ protected PasswordBadFormatException(
+ System.Runtime.Serialization.SerializationInfo info,
+ System.Runtime.Serialization.StreamingContext context) : base(info, context) { }
+
+ public string Password { get; set; } = "";
+
+ public string ValidationMessage { get; set; } = "";
+ }
+}
diff --git a/Timeline/Services/TimelineAlreadyExistException.cs b/Timeline/Services/TimelineAlreadyExistException.cs
deleted file mode 100644
index c2dea1f9..00000000
--- a/Timeline/Services/TimelineAlreadyExistException.cs
+++ /dev/null
@@ -1,17 +0,0 @@
-using System;
-
-namespace Timeline.Services
-{
- [Serializable]
- public class TimelineAlreadyExistException : Exception
- {
- public TimelineAlreadyExistException() : base(Resources.Services.Exception.TimelineAlreadyExistException) { }
- public TimelineAlreadyExistException(string name) : base(Resources.Services.Exception.TimelineAlreadyExistException) { Name = name; }
- public TimelineAlreadyExistException(string name, Exception inner) : base(Resources.Services.Exception.TimelineAlreadyExistException, inner) { Name = name; }
- protected TimelineAlreadyExistException(
- System.Runtime.Serialization.SerializationInfo info,
- System.Runtime.Serialization.StreamingContext context) : base(info, context) { }
-
- public string? Name { get; set; }
- }
-}
diff --git a/Timeline/Services/TimelineMemberOperationUserException.cs b/Timeline/Services/TimelineMemberOperationUserException.cs
deleted file mode 100644
index 543ee160..00000000
--- a/Timeline/Services/TimelineMemberOperationUserException.cs
+++ /dev/null
@@ -1,37 +0,0 @@
-using System;
-using System.Globalization;
-
-namespace Timeline.Services
-{
- [Serializable]
- public class TimelineMemberOperationUserException : Exception
- {
- public enum MemberOperation
- {
- Add,
- Remove
- }
-
- public TimelineMemberOperationUserException() : base(Resources.Services.Exception.TimelineMemberOperationException) { }
- public TimelineMemberOperationUserException(string message) : base(message) { }
- public TimelineMemberOperationUserException(string message, Exception inner) : base(message, inner) { }
- protected TimelineMemberOperationUserException(
- System.Runtime.Serialization.SerializationInfo info,
- System.Runtime.Serialization.StreamingContext context) : base(info, context) { }
-
- public TimelineMemberOperationUserException(int index, MemberOperation operation, string username, Exception inner)
- : base(MakeMessage(operation, index), inner) { Operation = operation; Index = index; Username = username; }
-
- private static string MakeMessage(MemberOperation operation, int index) => string.Format(CultureInfo.CurrentCulture,
- Resources.Services.Exception.TimelineMemberOperationExceptionDetail, operation, index);
-
- public MemberOperation? Operation { get; set; }
-
- /// <summary>
- /// The index of the member on which the operation failed.
- /// </summary>
- public int? Index { get; set; }
-
- public string? Username { get; set; }
- }
-}
diff --git a/Timeline/Services/TimelineNameBadFormatException.cs b/Timeline/Services/TimelineNameBadFormatException.cs
deleted file mode 100644
index 5120a175..00000000
--- a/Timeline/Services/TimelineNameBadFormatException.cs
+++ /dev/null
@@ -1,21 +0,0 @@
-using System;
-
-namespace Timeline.Services
-{
- [Serializable]
- public class TimelineNameBadFormatException : Exception
- {
- public TimelineNameBadFormatException()
- : base(Resources.Services.Exception.TimelineNameBadFormatException) { }
- public TimelineNameBadFormatException(string name)
- : base(Resources.Services.Exception.TimelineNameBadFormatException) { Name = name; }
- public TimelineNameBadFormatException(string name, Exception inner)
- : base(Resources.Services.Exception.TimelineNameBadFormatException, inner) { Name = name; }
-
- protected TimelineNameBadFormatException(
- System.Runtime.Serialization.SerializationInfo info,
- System.Runtime.Serialization.StreamingContext context) : base(info, context) { }
-
- public string? Name { get; set; }
- }
-}
diff --git a/Timeline/Services/TimelineService.cs b/Timeline/Services/TimelineService.cs
index f7b0e0e9..0ea68265 100644
--- a/Timeline/Services/TimelineService.cs
+++ b/Timeline/Services/TimelineService.cs
@@ -1,13 +1,15 @@
-using Microsoft.EntityFrameworkCore;
+using AutoMapper;
+using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
using System;
using System.Collections.Generic;
+using System.Globalization;
using System.Linq;
using System.Threading.Tasks;
using Timeline.Entities;
-using Timeline.Models;
using Timeline.Models.Http;
using Timeline.Models.Validation;
+using static Timeline.Resources.Services.TimelineService;
namespace Timeline.Services
{
@@ -28,12 +30,7 @@ namespace Timeline.Services
/// <param name="name">Username or the timeline name. See remarks of <see cref="IBaseTimelineService"/>.</param>
/// <returns>A list of all posts.</returns>
/// <exception cref="ArgumentNullException">Thrown when <paramref name="name"/> is null.</exception>
- /// <exception cref="TimelineNameBadFormatException">
- /// Thrown when timeline name is of bad format.
- /// For normal timeline, it means name is an empty string.
- /// For personal timeline, it means the username is of bad format,
- /// the inner exception should be a <see cref="UsernameBadFormatException"/>.
- /// </exception>
+ /// <exception cref="ArgumentException">Thrown when <paramref name="name"/> is illegal. It is not a valid timeline name (for normal timeline service) or a valid username (for personal timeline service).</exception>
/// <exception cref="TimelineNotExistException">
/// Thrown when timeline does not exist.
/// For normal timeline, it means the name does not exist.
@@ -46,26 +43,20 @@ namespace Timeline.Services
/// Create a new post in timeline.
/// </summary>
/// <param name="name">Username or the timeline name. See remarks of <ssee cref="IBaseTimelineService"/>.</param>
- /// <param name="author">The author's username.</param>
+ /// <param name="authorId">The author's id.</param>
/// <param name="content">The content.</param>
/// <param name="time">The time of the post. If null, then use current time.</param>
/// <returns>The info of the created post.</returns>
- /// <exception cref="ArgumentNullException">Thrown when <paramref name="name"/> or <paramref name="author"/> or <paramref name="content"/> is null.</exception>
- /// <exception cref="TimelineNameBadFormatException">
- /// Thrown when timeline name is of bad format.
- /// For normal timeline, it means name is an empty string.
- /// For personal timeline, it means the username is of bad format,
- /// the inner exception should be a <see cref="UsernameBadFormatException"/>.
- /// </exception>
+ /// <exception cref="ArgumentNullException">Thrown when <paramref name="name"/> or <paramref name="content"/> is null.</exception>
+ /// <exception cref="ArgumentException">Thrown when <paramref name="name"/> is illegal. It is not a valid timeline name (for normal timeline service) or a valid username (for personal timeline service).</exception>
/// <exception cref="TimelineNotExistException">
/// Thrown when timeline does not exist.
/// For normal timeline, it means the name does not exist.
/// For personal timeline, it means the user of that username does not exist
/// and the inner exception should be a <see cref="UserNotExistException"/>.
/// </exception>
- /// <exception cref="UsernameBadFormatException">Thrown if <paramref name="author"/> is of bad format.</exception>
- /// <exception cref="UserNotExistException">Thrown if <paramref name="author"/> does not exist.</exception>
- Task<TimelinePostCreateResponse> CreatePost(string name, string author, string content, DateTime? time);
+ /// <exception cref="UserNotExistException">Thrown if user with <paramref name="authorId"/> does not exist.</exception>
+ Task<TimelinePostInfo> CreatePost(string name, long authorId, string content, DateTime? time);
/// <summary>
/// Delete a post
@@ -73,12 +64,7 @@ namespace Timeline.Services
/// <param name="name">Username or the timeline name. See remarks of <see cref="IBaseTimelineService"/>.</param>
/// <param name="id">The id of the post to delete.</param>
/// <exception cref="ArgumentNullException">Thrown when <paramref name="name"/> or <paramref name="username"/> is null.</exception>
- /// <exception cref="TimelineNameBadFormatException">
- /// Thrown when timeline name is of bad format.
- /// For normal timeline, it means name is an empty string.
- /// For personal timeline, it means the username is of bad format,
- /// the inner exception should be a <see cref="UsernameBadFormatException"/>.
- /// </exception>
+ /// <exception cref="ArgumentException">Thrown when <paramref name="name"/> is illegal. It is not a valid timeline name (for normal timeline service) or a valid username (for personal timeline service).</exception>
/// <exception cref="TimelineNotExistException">
/// Thrown when timeline does not exist.
/// For normal timeline, it means the name does not exist.
@@ -95,50 +81,22 @@ namespace Timeline.Services
Task DeletePost(string name, long id);
/// <summary>
- /// Set the properties of a timeline.
- /// </summary>
- /// <param name="name">Username or the timeline name. See remarks of <see cref="IBaseTimelineService"/>.</param>
- /// <param name="newProperties">The new properties. Null member means not to change.</param>
- /// <exception cref="ArgumentNullException">Thrown when <paramref name="name"/> or <paramref name="newProperties"/> is null.</exception>
- /// <exception cref="TimelineNameBadFormatException">
- /// Thrown when timeline name is of bad format.
- /// For normal timeline, it means name is an empty string.
- /// For personal timeline, it means the username is of bad format,
- /// the inner exception should be a <see cref="UsernameBadFormatException"/>.
- /// </exception>
- /// <exception cref="TimelineNotExistException">
- /// Thrown when timeline does not exist.
- /// For normal timeline, it means the name does not exist.
- /// For personal timeline, it means the user of that username does not exist
- /// and the inner exception should be a <see cref="UserNotExistException"/>.
- /// </exception>
- Task ChangeProperty(string name, TimelinePropertyChangeRequest newProperties);
-
- /// <summary>
/// Remove members to a timeline.
/// </summary>
/// <param name="name">Username or the timeline name. See remarks of <see cref="IBaseTimelineService"/>.</param>
/// <param name="add">A list of usernames of members to add. May be null.</param>
/// <param name="remove">A list of usernames of members to remove. May be null.</param>
/// <exception cref="ArgumentNullException">Thrown when <paramref name="name"/> is null.</exception>
- /// <exception cref="TimelineNameBadFormatException">
- /// Thrown when timeline name is of bad format.
- /// For normal timeline, it means name is an empty string.
- /// For personal timeline, it means the username is of bad format,
- /// the inner exception should be a <see cref="UsernameBadFormatException"/>.
- /// </exception>
+ /// <exception cref="ArgumentException">Thrown when <paramref name="name"/> is illegal. It is not a valid timeline name (for normal timeline service) or a valid username (for personal timeline service).</exception>
+ /// <exception cref="ArgumentException">Thrown when names in <paramref name="add"/> or <paramref name="remove"/> is not a valid username.</exception>
/// <exception cref="TimelineNotExistException">
/// Thrown when timeline does not exist.
/// For normal timeline, it means the name does not exist.
/// For personal timeline, it means the user of that username does not exist
/// and the inner exception should be a <see cref="UserNotExistException"/>.
/// </exception>
- /// <exception cref="TimelineMemberOperationUserException">
- /// Thrown when an exception occurs on the user list.
- /// The inner exception is <see cref="UsernameBadFormatException"/>
- /// when one of the username is invalid.
- /// The inner exception is <see cref="UserNotExistException"/>
- /// when one of the user to change does not exist.
+ /// <exception cref="UserNotExistException">
+ /// Thrown when one of the user to change does not exist.
/// </exception>
/// <remarks>
/// Operating on a username that is of bad format or does not exist always throws.
@@ -153,42 +111,30 @@ namespace Timeline.Services
/// Verify whether a visitor has the permission to read a timeline.
/// </summary>
/// <param name="name">Username or the timeline name. See remarks of <see cref="IBaseTimelineService"/>.</param>
- /// <param name="username">The user to check on. Null means visitor without account.</param>
+ /// <param name="visitorId">The id of the user to check on. Null means visitor without account.</param>
/// <returns>True if can read, false if can't read.</returns>
/// <exception cref="ArgumentNullException">Thrown when <paramref name="name"/> is null.</exception>
- /// <exception cref="TimelineNameBadFormatException">
- /// Thrown when timeline name is of bad format.
- /// For normal timeline, it means name is an empty string.
- /// For personal timeline, it means the username is of bad format,
- /// the inner exception should be a <see cref="UsernameBadFormatException"/>.
- /// </exception>
+ /// <exception cref="ArgumentException">Thrown when <paramref name="name"/> is illegal. It is not a valid timeline name (for normal timeline service) or a valid username (for personal timeline service).</exception>
/// <exception cref="TimelineNotExistException">
/// Thrown when timeline does not exist.
/// For normal timeline, it means the name does not exist.
/// For personal timeline, it means the user of that username does not exist
/// and the inner exception should be a <see cref="UserNotExistException"/>.
/// </exception>
- /// <exception cref="UsernameBadFormatException">
- /// Thrown when <paramref name="username"/> is of bad format.
- /// </exception>
- /// <exception cref="UserNotExistException">
- /// Thrown when <paramref name="username"/> does not exist.
- /// </exception>
- Task<bool> HasReadPermission(string name, string? username);
+ /// <remarks>
+ /// This method does not check whether visitor is administrator.
+ /// Return false if user with visitor id does not exist.
+ /// </remarks>
+ Task<bool> HasReadPermission(string name, long? visitorId);
/// <summary>
/// Verify whether a user has the permission to modify a post.
/// </summary>
/// <param name="name">Username or the timeline name. See remarks of <see cref="IBaseTimelineService"/>.</param>
- /// <param name="username">The user to check on.</param>
+ /// <param name="modifierId">The id of the user to check on.</param>
/// <returns>True if can modify, false if can't modify.</returns>
- /// <exception cref="ArgumentNullException">Thrown when <paramref name="name"/> or <paramref name="username"/> is null.</exception>
- /// <exception cref="TimelineNameBadFormatException">
- /// Thrown when timeline name is of bad format.
- /// For normal timeline, it means name is an empty string.
- /// For personal timeline, it means the username is of bad format,
- /// the inner exception should be a <see cref="UsernameBadFormatException"/>.
- /// </exception>
+ /// <exception cref="ArgumentNullException">Thrown when <paramref name="name"/> is null.</exception>
+ /// <exception cref="ArgumentException">Thrown when <paramref name="name"/> is illegal. It is not a valid timeline name (for normal timeline service) or a valid username (for personal timeline service).</exception>
/// <exception cref="TimelineNotExistException">
/// Thrown when timeline does not exist.
/// For normal timeline, it means the name does not exist.
@@ -198,47 +144,32 @@ namespace Timeline.Services
/// <exception cref="TimelinePostNotExistException">
/// Thrown when the post with given id does not exist or is deleted already.
/// </exception>
- /// <exception cref="UsernameBadFormatException">
- /// Thrown when <paramref name="username"/> is of bad format.
- /// </exception>
- /// <exception cref="UserNotExistException">
- /// Thrown when <paramref name="username"/> does not exist.
- /// </exception>
/// <remarks>
/// This method does not check whether the user is administrator.
/// It only checks whether he is the author of the post or the owner of the timeline.
+ /// Return false when user with modifier id does not exist.
/// </remarks>
- Task<bool> HasPostModifyPermission(string name, long id, string username);
+ Task<bool> HasPostModifyPermission(string name, long id, long modifierId);
/// <summary>
/// Verify whether a user is member of a timeline.
/// </summary>
/// <param name="name">Username or the timeline name. See remarks of <see cref="IBaseTimelineService"/>.</param>
- /// <param name="username">The user to check on.</param>
+ /// <param name="userId">The id of user to check on.</param>
/// <returns>True if it is a member, false if not.</returns>
- /// <exception cref="ArgumentNullException">Thrown when <paramref name="name"/> or <paramref name="username"/> is null.</exception>
- /// <exception cref="TimelineNameBadFormatException">
- /// Thrown when timeline name is of bad format.
- /// For normal timeline, it means name is an empty string.
- /// For personal timeline, it means the username is of bad format,
- /// the inner exception should be a <see cref="UsernameBadFormatException"/>.
- /// </exception>
+ /// <exception cref="ArgumentNullException">Thrown when <paramref name="name"/> is null.</exception>
+ /// <exception cref="ArgumentException">Thrown when <paramref name="name"/> is illegal. It is not a valid timeline name (for normal timeline service) or a valid username (for personal timeline service).</exception>
/// <exception cref="TimelineNotExistException">
/// Thrown when timeline does not exist.
/// For normal timeline, it means the name does not exist.
/// For personal timeline, it means the user of that username does not exist
/// and the inner exception should be a <see cref="UserNotExistException"/>.
/// </exception>
- /// <exception cref="UsernameBadFormatException">
- /// Thrown when <paramref name="username"/> is not a valid username.
- /// </exception>
- /// <exception cref="UserNotExistException">
- /// Thrown when user <paramref name="username"/> does not exist.
- /// </exception>
/// <remarks>
/// Timeline owner is also considered as a member.
+ /// Return false when user with user id does not exist.
/// </remarks>
- Task<bool> IsMemberOf(string name, string username);
+ Task<bool> IsMemberOf(string name, long userId);
}
/// <summary>
@@ -252,7 +183,7 @@ namespace Timeline.Services
/// <param name="name">The name of the timeline.</param>
/// <returns>The timeline info.</returns>
/// <exception cref="ArgumentNullException">Thrown when <paramref name="name"/> is null.</exception>
- /// <exception cref="TimelineNameBadFormatException">
+ /// <exception cref="ArgumentException">
/// Thrown when timeline name is invalid. Currently it means it is an empty string.
/// </exception>
/// <exception cref="TimelineNotExistException">
@@ -264,20 +195,12 @@ namespace Timeline.Services
/// Create a timeline.
/// </summary>
/// <param name="name">The name of the timeline.</param>
- /// <param name="owner">The owner of the timeline.</param>
+ /// <param name="owner">The id of owner of the timeline.</param>
/// <exception cref="ArgumentNullException">Thrown when <paramref name="name"/> or <paramref name="owner"/> is null.</exception>
- /// <exception cref="TimelineNameBadFormatException">
- /// Thrown when timeline name is invalid. Currently it means it is an empty string.
- /// </exception>
- /// <exception cref="TimelineAlreadyExistException">
- /// Thrown when the timeline already exists.
- /// </exception>
- /// <exception cref="UsernameBadFormatException">
- /// Thrown when the username of the owner is not valid.
- /// </exception>
- /// <exception cref="UserNotExistException">
- /// Thrown when the owner user does not exist.</exception>
- Task CreateTimeline(string name, string owner);
+ /// <exception cref="ArgumentException">Thrown when timeline name is invalid. Currently it means it is an empty string.</exception>
+ /// <exception cref="ConflictException">Thrown when the timeline already exists.</exception>
+ /// <exception cref="UserNotExistException">Thrown when the owner user does not exist.</exception>
+ Task CreateTimeline(string name, long owner);
}
public interface IPersonalTimelineService : IBaseTimelineService
@@ -290,21 +213,40 @@ namespace Timeline.Services
/// <exception cref="ArgumentNullException">
/// Thrown when <paramref name="username"/> is null.
/// </exception>
- /// <exception cref="TimelineNameBadFormatException">
- /// Thrown when <paramref name="username"/> is of bad format. Inner exception MUST be <see cref="UsernameBadFormatException"/>.
+ /// <exception cref="ArgumentException">
+ /// Thrown when <paramref name="username"/> is of bad format.
/// </exception>
/// <exception cref="TimelineNotExistException">
/// Thrown when the user does not exist. Inner exception MUST be <see cref="UserNotExistException"/>.
/// </exception>
Task<BaseTimelineInfo> GetTimeline(string username);
+
+ /// <summary>
+ /// Set the properties of a timeline.
+ /// </summary>
+ /// <param name="name">Username or the timeline name. See remarks of <see cref="IBaseTimelineService"/>.</param>
+ /// <param name="newProperties">The new properties. Null member means not to change.</param>
+ /// <exception cref="ArgumentNullException">
+ /// Thrown when <paramref name="username"/> is null.
+ /// </exception>
+ /// <exception cref="ArgumentException">
+ /// Thrown when <paramref name="username"/> is of bad format.
+ /// </exception>
+ /// <exception cref="TimelineNotExistException">
+ /// Thrown when the user does not exist. Inner exception MUST be <see cref="UserNotExistException"/>.
+ /// </exception>
+ Task ChangeProperty(string name, TimelinePatchRequest newProperties);
+
}
public abstract class BaseTimelineService : IBaseTimelineService
{
- protected BaseTimelineService(ILoggerFactory loggerFactory, DatabaseContext database, IClock clock)
+ protected BaseTimelineService(ILoggerFactory loggerFactory, DatabaseContext database, IUserService userService, IMapper mapper, IClock clock)
{
Clock = clock;
Database = database;
+ UserService = userService;
+ Mapper = mapper;
}
protected IClock Clock { get; }
@@ -313,6 +255,10 @@ namespace Timeline.Services
protected DatabaseContext Database { get; }
+ protected IUserService UserService { get; }
+
+ protected IMapper Mapper { get; }
+
/// <summary>
/// Find the timeline id by the name.
/// For details, see remarks.
@@ -320,12 +266,7 @@ namespace Timeline.Services
/// <param name="name">The username or the timeline name. See remarks.</param>
/// <returns>The id of the timeline entity.</returns>
/// <exception cref="ArgumentNullException">Thrown when <paramref name="name"/> is null.</exception>
- /// <exception cref="TimelineNameBadFormatException">
- /// Thrown when timeline name is of bad format.
- /// For normal timeline, it means name is an empty string.
- /// For personal timeline, it means the username is of bad format,
- /// the inner exception should be a <see cref="UsernameBadFormatException"/>.
- /// </exception>
+ /// <exception cref="ArgumentException">Thrown when <paramref name="name"/> is illegal. It is not a valid timeline name (for normal timeline service) or a valid username (for personal timeline service).</exception>
/// <exception cref="TimelineNotExistException">
/// Thrown when timeline does not exist.
/// For normal timeline, it means the name does not exist.
@@ -347,66 +288,60 @@ namespace Timeline.Services
if (name == null)
throw new ArgumentNullException(nameof(name));
+
var timelineId = await FindTimelineId(name);
var postEntities = await Database.TimelinePosts.OrderBy(p => p.Time).Where(p => p.TimelineId == timelineId && p.Content != null).ToListAsync();
+
var posts = new List<TimelinePostInfo>();
foreach (var entity in postEntities)
{
- posts.Add(new TimelinePostInfo
+ if (entity.Content != null) // otherwise it is deleted
{
- Id = entity.Id,
- Content = entity.Content,
- Author = (await Database.Users.Where(u => u.Id == entity.AuthorId).Select(u => new { u.Name }).SingleAsync()).Name,
- Time = entity.Time
- });
+ var author = Mapper.Map<UserInfo>(await UserService.GetUserById(entity.AuthorId));
+ posts.Add(new TimelinePostInfo
+ {
+ Id = entity.Id,
+ Content = entity.Content,
+ Author = author,
+ Time = entity.Time,
+ LastUpdated = entity.LastUpdated
+ });
+ }
}
return posts;
}
- public async Task<TimelinePostCreateResponse> CreatePost(string name, string author, string content, DateTime? time)
+ public async Task<TimelinePostInfo> CreatePost(string name, long authorId, string content, DateTime? time)
{
if (name == null)
throw new ArgumentNullException(nameof(name));
- if (author == null)
- throw new ArgumentNullException(nameof(author));
if (content == null)
throw new ArgumentNullException(nameof(content));
- {
- var (result, message) = UsernameValidator.Validate(author);
- if (!result)
- {
- throw new UsernameBadFormatException(author, message);
- }
- }
-
var timelineId = await FindTimelineId(name);
-
- var authorEntity = Database.Users.Where(u => u.Name == author).Select(u => new { u.Id }).SingleOrDefault();
- if (authorEntity == null)
- {
- throw new UserNotExistException(author);
- }
- var authorId = authorEntity.Id;
+ var author = Mapper.Map<UserInfo>(await UserService.GetUserById(authorId));
var currentTime = Clock.GetCurrentTime();
+ var finalTime = time ?? currentTime;
var postEntity = new TimelinePostEntity
{
Content = content,
AuthorId = authorId,
TimelineId = timelineId,
- Time = time ?? currentTime,
+ Time = finalTime,
LastUpdated = currentTime
};
-
Database.TimelinePosts.Add(postEntity);
await Database.SaveChangesAsync();
- return new TimelinePostCreateResponse
+ return new TimelinePostInfo
{
Id = postEntity.Id,
- Time = postEntity.Time
+ Content = content,
+ Author = author,
+ Time = finalTime,
+ LastUpdated = currentTime
};
}
@@ -415,6 +350,9 @@ namespace Timeline.Services
if (name == null)
throw new ArgumentNullException(nameof(name));
+ // Currently we don't use the result. But we need to check the timeline.
+ var _ = await FindTimelineId(name);
+
var post = await Database.TimelinePosts.Where(p => p.Id == id).SingleOrDefaultAsync();
if (post == null)
@@ -426,7 +364,7 @@ namespace Timeline.Services
await Database.SaveChangesAsync();
}
- public async Task ChangeProperty(string name, TimelinePropertyChangeRequest newProperties)
+ public async Task ChangeProperty(string name, TimelinePatchRequest newProperties)
{
if (name == null)
throw new ArgumentNullException(nameof(name));
@@ -455,27 +393,23 @@ namespace Timeline.Services
if (name == null)
throw new ArgumentNullException(nameof(name));
- // remove duplication and check the format of each username.
- // Return a username->index map.
- Dictionary<string, int>? RemoveDuplicateAndCheckFormat(IList<string>? list, TimelineMemberOperationUserException.MemberOperation operation)
+ List<string>? RemoveDuplicateAndCheckFormat(IList<string>? list, string paramName)
{
if (list != null)
{
- Dictionary<string, int> result = new Dictionary<string, int>();
+ List<string> result = new List<string>();
var count = list.Count;
for (var index = 0; index < count; index++)
{
var username = list[index];
- if (result.ContainsKey(username))
+ if (result.Contains(username))
{
continue;
}
var (validationResult, message) = UsernameValidator.Validate(username);
if (!validationResult)
- throw new TimelineMemberOperationUserException(
- index, operation, username,
- new UsernameBadFormatException(username, message));
- result.Add(username, index);
+ throw new ArgumentException(string.Format(CultureInfo.CurrentCulture, ExceptionChangeMemberUsernameBadFormat, index), nameof(paramName));
+ result.Add(username);
}
return result;
}
@@ -484,13 +418,13 @@ namespace Timeline.Services
return null;
}
}
- var simplifiedAdd = RemoveDuplicateAndCheckFormat(add, TimelineMemberOperationUserException.MemberOperation.Add);
- var simplifiedRemove = RemoveDuplicateAndCheckFormat(remove, TimelineMemberOperationUserException.MemberOperation.Remove);
+ var simplifiedAdd = RemoveDuplicateAndCheckFormat(add, nameof(add));
+ var simplifiedRemove = RemoveDuplicateAndCheckFormat(remove, nameof(remove));
// remove those both in add and remove
if (simplifiedAdd != null && simplifiedRemove != null)
{
- var usersToClean = simplifiedRemove.Keys.Where(u => simplifiedAdd.ContainsKey(u));
+ var usersToClean = simplifiedRemove.Where(u => simplifiedAdd.Contains(u)).ToList();
foreach (var u in usersToClean)
{
simplifiedAdd.Remove(u);
@@ -500,26 +434,20 @@ namespace Timeline.Services
var timelineId = await FindTimelineId(name);
- async Task<List<long>?> CheckExistenceAndGetId(Dictionary<string, int>? map, TimelineMemberOperationUserException.MemberOperation operation)
+ async Task<List<long>?> CheckExistenceAndGetId(List<string>? list)
{
- if (map == null)
+ if (list == null)
return null;
List<long> result = new List<long>();
- foreach (var (username, index) in map)
+ foreach (var username in list)
{
- var user = await Database.Users.Where(u => u.Name == username).Select(u => new { u.Id }).SingleOrDefaultAsync();
- if (user == null)
- {
- throw new TimelineMemberOperationUserException(index, operation, username,
- new UserNotExistException(username));
- }
- result.Add(user.Id);
+ result.Add(await UserService.GetUserIdByUsername(username));
}
return result;
}
- var userIdsAdd = await CheckExistenceAndGetId(simplifiedAdd, TimelineMemberOperationUserException.MemberOperation.Add);
- var userIdsRemove = await CheckExistenceAndGetId(simplifiedRemove, TimelineMemberOperationUserException.MemberOperation.Remove);
+ var userIdsAdd = await CheckExistenceAndGetId(simplifiedAdd);
+ var userIdsRemove = await CheckExistenceAndGetId(simplifiedRemove);
if (userIdsAdd != null)
{
@@ -536,30 +464,11 @@ namespace Timeline.Services
await Database.SaveChangesAsync();
}
- public async Task<bool> HasReadPermission(string name, string? username)
+ public async Task<bool> HasReadPermission(string name, long? visitorId)
{
if (name == null)
throw new ArgumentNullException(nameof(name));
- long? userId = null;
- if (username != null)
- {
- var (result, message) = UsernameValidator.Validate(username);
- if (!result)
- {
- throw new UsernameBadFormatException(username);
- }
-
- var user = await Database.Users.Where(u => u.Name == username).Select(u => new { u.Id }).SingleOrDefaultAsync();
-
- if (user == null)
- {
- throw new UserNotExistException(username);
- }
-
- userId = user.Id;
- }
-
var timelineId = await FindTimelineId(name);
var timelineEntity = await Database.Timelines.Where(t => t.Id == timelineId).Select(t => new { t.Visibility }).SingleAsync();
@@ -567,43 +476,24 @@ namespace Timeline.Services
if (timelineEntity.Visibility == TimelineVisibility.Public)
return true;
- if (timelineEntity.Visibility == TimelineVisibility.Register && username != null)
+ if (timelineEntity.Visibility == TimelineVisibility.Register && visitorId != null)
return true;
- if (userId == null)
+ if (visitorId == null)
{
return false;
}
else
{
- var memberEntity = await Database.TimelineMembers.Where(m => m.UserId == userId && m.TimelineId == timelineId).SingleOrDefaultAsync();
+ var memberEntity = await Database.TimelineMembers.Where(m => m.UserId == visitorId && m.TimelineId == timelineId).SingleOrDefaultAsync();
return memberEntity != null;
}
}
- public async Task<bool> HasPostModifyPermission(string name, long id, string username)
+ public async Task<bool> HasPostModifyPermission(string name, long id, long modifierId)
{
if (name == null)
throw new ArgumentNullException(nameof(name));
- if (username == null)
- throw new ArgumentNullException(nameof(username));
-
- {
- var (result, message) = UsernameValidator.Validate(username);
- if (!result)
- {
- throw new UsernameBadFormatException(username);
- }
- }
-
- var user = await Database.Users.Where(u => u.Name == username).Select(u => new { u.Id }).SingleOrDefaultAsync();
-
- if (user == null)
- {
- throw new UserNotExistException(username);
- }
-
- var userId = user.Id;
var timelineId = await FindTimelineId(name);
@@ -614,32 +504,13 @@ namespace Timeline.Services
if (postEntity == null)
throw new TimelinePostNotExistException(id);
- return timelineEntity.OwnerId == userId || postEntity.AuthorId == userId;
+ return timelineEntity.OwnerId == modifierId || postEntity.AuthorId == modifierId;
}
- public async Task<bool> IsMemberOf(string name, string username)
+ public async Task<bool> IsMemberOf(string name, long userId)
{
if (name == null)
throw new ArgumentNullException(nameof(name));
- if (username == null)
- throw new ArgumentNullException(nameof(username));
-
- {
- var (result, message) = UsernameValidator.Validate(username);
- if (!result)
- {
- throw new UsernameBadFormatException(username);
- }
- }
-
- var user = await Database.Users.Where(u => u.Name == username).Select(u => new { u.Id }).SingleOrDefaultAsync();
-
- if (user == null)
- {
- throw new UserNotExistException(username);
- }
-
- var userId = user.Id;
var timelineId = await FindTimelineId(name);
@@ -648,38 +519,33 @@ namespace Timeline.Services
if (userId == timelineEntity.OwnerId)
return true;
- var timelineMemberEntity = await Database.TimelineMembers.Where(m => m.TimelineId == timelineId && m.UserId == userId).SingleOrDefaultAsync();
-
- return timelineMemberEntity != null;
+ return await Database.TimelineMembers.AnyAsync(m => m.TimelineId == timelineId && m.UserId == userId);
}
}
public class PersonalTimelineService : BaseTimelineService, IPersonalTimelineService
{
- public PersonalTimelineService(ILoggerFactory loggerFactory, DatabaseContext database, IClock clock)
- : base(loggerFactory, database, clock)
+ public PersonalTimelineService(ILoggerFactory loggerFactory, DatabaseContext database, IUserService userService, IMapper mapper, IClock clock)
+ : base(loggerFactory, database, userService, mapper, clock)
{
}
protected override async Task<long> FindTimelineId(string name)
{
+ long userId;
+ try
{
- var (result, message) = UsernameValidator.Validate(name);
- if (!result)
- {
- throw new TimelineNameBadFormatException(name, new UsernameBadFormatException(name, message));
- }
+ userId = await UserService.GetUserIdByUsername(name);
}
-
- var userEntity = await Database.Users.Where(u => u.Name == name).Select(u => new { u.Id }).SingleOrDefaultAsync();
-
- if (userEntity == null)
+ catch (ArgumentException e)
{
- throw new TimelineNotExistException(name, new UserNotExistException(name));
+ throw new ArgumentException(ExceptionFindTimelineUsernameBadFormat, nameof(name), e);
+ }
+ catch (UserNotExistException e)
+ {
+ throw new TimelineNotExistException(name, e);
}
-
- var userId = userEntity.Id;
var timelineEntity = await Database.Timelines.Where(t => t.OwnerId == userId && t.Name == null).Select(t => new { t.Id }).SingleOrDefaultAsync();
@@ -715,16 +581,20 @@ namespace Timeline.Services
var timelineMemberEntities = await Database.TimelineMembers.Where(m => m.TimelineId == timelineId).Select(m => new { m.UserId }).ToListAsync();
- var memberUsernameTasks = timelineMemberEntities.Select(m => Database.Users.Where(u => u.Id == m.UserId).Select(u => u.Name).SingleAsync()).ToArray();
+ var owner = Mapper.Map<UserInfo>(await UserService.GetUserById(timelineEntity.OwnerId));
- var memberUsernames = await Task.WhenAll(memberUsernameTasks);
+ var members = new List<UserInfo>();
+ foreach (var memberEntity in timelineMemberEntities)
+ {
+ members.Add(Mapper.Map<UserInfo>(await UserService.GetUserById(memberEntity.UserId)));
+ }
return new BaseTimelineInfo
{
Description = timelineEntity.Description ?? "",
- Owner = username,
+ Owner = owner,
Visibility = timelineEntity.Visibility,
- Members = memberUsernames.ToList()
+ Members = members
};
}
diff --git a/Timeline/Services/User.cs b/Timeline/Services/User.cs
new file mode 100644
index 00000000..09a472e5
--- /dev/null
+++ b/Timeline/Services/User.cs
@@ -0,0 +1,18 @@
+namespace Timeline.Services
+{
+ public class User
+ {
+ public string? Username { get; set; }
+ public string? Nickname { get; set; }
+
+ #region adminsecret
+ public bool? Administrator { get; set; }
+ #endregion adminsecret
+
+ #region secret
+ public long? Id { get; set; }
+ public string? Password { get; set; }
+ public long? Version { get; set; }
+ #endregion secret
+ }
+}
diff --git a/Timeline/Services/UserAvatarService.cs b/Timeline/Services/UserAvatarService.cs
index 01201864..39b408e6 100644
--- a/Timeline/Services/UserAvatarService.cs
+++ b/Timeline/Services/UserAvatarService.cs
@@ -11,7 +11,6 @@ using System.Linq;
using System.Threading.Tasks;
using Timeline.Entities;
using Timeline.Helpers;
-using Timeline.Models.Validation;
namespace Timeline.Services
{
@@ -61,36 +60,27 @@ namespace Timeline.Services
public interface IUserAvatarService
{
/// <summary>
- /// Get the etag of a user's avatar.
+ /// Get the etag of a user's avatar. Warning: This method does not check the user existence.
/// </summary>
- /// <param name="username">The username of the user to get avatar etag of.</param>
+ /// <param name="id">The id of the user to get avatar etag of.</param>
/// <returns>The etag.</returns>
- /// <exception cref="ArgumentNullException">Thrown if <paramref name="username"/> is null.</exception>
- /// <exception cref="UsernameBadFormatException">Thrown if the <paramref name="username"/> is of bad format.</exception>
- /// <exception cref="UserNotExistException">Thrown if the user does not exist.</exception>
- Task<string> GetAvatarETag(string username);
+ Task<string> GetAvatarETag(long id);
/// <summary>
- /// Get avatar of a user. If the user has no avatar set, a default one is returned.
+ /// Get avatar of a user. If the user has no avatar set, a default one is returned. Warning: This method does not check the user existence.
/// </summary>
- /// <param name="username">The username of the user to get avatar of.</param>
+ /// <param name="id">The id of the user to get avatar of.</param>
/// <returns>The avatar info.</returns>
- /// <exception cref="ArgumentNullException">Thrown if <paramref name="username"/> is null.</exception>
- /// <exception cref="UsernameBadFormatException">Thrown if the <paramref name="username"/> is of bad format.</exception>
- /// <exception cref="UserNotExistException">Thrown if the user does not exist.</exception>
- Task<AvatarInfo> GetAvatar(string username);
+ Task<AvatarInfo> GetAvatar(long id);
/// <summary>
- /// Set avatar for a user.
+ /// Set avatar for a user. Warning: This method does not check the user existence.
/// </summary>
- /// <param name="username">The username of the user to set avatar for.</param>
+ /// <param name="id">The id of the user to set avatar for.</param>
/// <param name="avatar">The avatar. Can be null to delete the saved avatar.</param>
- /// <exception cref="ArgumentNullException">Throw if <paramref name="username"/> is null.</exception>
/// <exception cref="ArgumentException">Thrown if any field in <paramref name="avatar"/> is null when <paramref name="avatar"/> is not null.</exception>
- /// <exception cref="UsernameBadFormatException">Thrown if the <paramref name="username"/> is of bad format.</exception>
- /// <exception cref="UserNotExistException">Thrown if the user does not exist.</exception>
/// <exception cref="AvatarFormatException">Thrown if avatar is of bad format.</exception>
- Task SetAvatar(string username, Avatar? avatar);
+ Task SetAvatar(long id, Avatar? avatar);
}
// TODO! : Make this configurable.
@@ -104,7 +94,6 @@ namespace Timeline.Services
private DateTime _cacheLastModified;
private string _cacheETag = default!;
- [System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1062:Validate arguments of public methods", Justification = "DI.")]
public DefaultUserAvatarProvider(IWebHostEnvironment environment, IETagGenerator eTagGenerator)
{
_avatarPath = Path.Combine(environment.ContentRootPath, "default-avatar.png");
@@ -195,22 +184,18 @@ namespace Timeline.Services
_clock = clock;
}
- public async Task<string> GetAvatarETag(string username)
+ public async Task<string> GetAvatarETag(long id)
{
- var userId = await DatabaseExtensions.CheckAndGetUser(_database.Users, username);
-
- var eTag = (await _database.UserAvatars.Where(a => a.UserId == userId).Select(a => new { a.ETag }).SingleOrDefaultAsync())?.ETag;
+ var eTag = (await _database.UserAvatars.Where(a => a.UserId == id).Select(a => new { a.ETag }).SingleOrDefaultAsync())?.ETag;
if (eTag == null)
return await _defaultUserAvatarProvider.GetDefaultAvatarETag();
else
return eTag;
}
- public async Task<AvatarInfo> GetAvatar(string username)
+ public async Task<AvatarInfo> GetAvatar(long id)
{
- var userId = await DatabaseExtensions.CheckAndGetUser(_database.Users, username);
-
- var avatarEntity = await _database.UserAvatars.Where(a => a.UserId == userId).Select(a => new { a.Type, a.Data, a.LastModified }).SingleOrDefaultAsync();
+ var avatarEntity = await _database.UserAvatars.Where(a => a.UserId == id).Select(a => new { a.Type, a.Data, a.LastModified }).SingleOrDefaultAsync();
if (avatarEntity != null)
{
@@ -240,7 +225,7 @@ namespace Timeline.Services
return defaultAvatar;
}
- public async Task SetAvatar(string username, Avatar? avatar)
+ public async Task SetAvatar(long id, Avatar? avatar)
{
if (avatar != null)
{
@@ -250,8 +235,7 @@ namespace Timeline.Services
throw new ArgumentException(Resources.Services.UserAvatarService.ExceptionAvatarTypeNullOrEmpty, nameof(avatar));
}
- var userId = await DatabaseExtensions.CheckAndGetUser(_database.Users, username);
- var avatarEntity = await _database.UserAvatars.Where(a => a.UserId == userId).SingleOrDefaultAsync();
+ var avatarEntity = await _database.UserAvatars.Where(a => a.UserId == id).SingleOrDefaultAsync();
if (avatar == null)
{
@@ -275,13 +259,13 @@ namespace Timeline.Services
var create = avatarEntity == null;
if (create)
{
- avatarEntity = new UserAvatar();
+ avatarEntity = new UserAvatarEntity();
}
avatarEntity!.Type = avatar.Type;
avatarEntity.Data = avatar.Data;
avatarEntity.ETag = await _eTagGenerator.Generate(avatar.Data);
avatarEntity.LastModified = _clock.GetCurrentTime();
- avatarEntity.UserId = userId;
+ avatarEntity.UserId = id;
if (create)
{
_database.UserAvatars.Add(avatarEntity);
diff --git a/Timeline/Services/UserDetailService.cs b/Timeline/Services/UserDetailService.cs
deleted file mode 100644
index 0b24e4e2..00000000
--- a/Timeline/Services/UserDetailService.cs
+++ /dev/null
@@ -1,102 +0,0 @@
-using Microsoft.Extensions.Logging;
-using System;
-using System.Linq;
-using System.Threading.Tasks;
-using Timeline.Entities;
-using static Timeline.Resources.Services.UserDetailService;
-
-namespace Timeline.Services
-{
- public interface IUserDetailService
- {
- /// <summary>
- /// Get the nickname of the user with given username.
- /// If the user does not set a nickname, the username is returned as the nickname.
- /// </summary>
- /// <param name="username">The username of the user to get nickname of.</param>
- /// <returns>The nickname of the user.</returns>
- /// <exception cref="ArgumentNullException">Thrown when <paramref name="username"/> is null.</exception>
- /// <exception cref="UsernameBadFormatException">Thrown when <paramref name="username"/> is of bad format.</exception>
- /// <exception cref="UserNotExistException">Thrown when the user does not exist.</exception>
- Task<string> GetNickname(string username);
-
- /// <summary>
- /// Set the nickname of the user with given username.
- /// </summary>
- /// <param name="username">The username of the user to set nickname of.</param>
- /// <param name="nickname">The nickname. Pass null to unset.</param>
- /// <exception cref="ArgumentNullException">Thrown when <paramref name="username"/> is null.</exception>
- /// <exception cref="ArgumentException">Thrown when <paramref name="nickname"/> is not null but its length is bigger than 10.</exception>
- /// <exception cref="UsernameBadFormatException">Thrown when <paramref name="username"/> is of bad format.</exception>
- /// <exception cref="UserNotExistException">Thrown when the user does not exist.</exception>
- Task SetNickname(string username, string? nickname);
- }
-
- public class UserDetailService : IUserDetailService
- {
- private readonly DatabaseContext _database;
-
- private readonly ILogger<UserDetailService> _logger;
-
- public UserDetailService(DatabaseContext database, ILogger<UserDetailService> logger)
- {
- _database = database;
- _logger = logger;
- }
-
- public async Task<string> GetNickname(string username)
- {
- var userId = await DatabaseExtensions.CheckAndGetUser(_database.Users, username);
- var nickname = _database.UserDetails.Where(d => d.UserId == userId).Select(d => new { d.Nickname }).SingleOrDefault()?.Nickname;
- return nickname ?? username;
- }
-
- public async Task SetNickname(string username, string? nickname)
- {
- if (nickname != null && nickname.Length > 10)
- {
- throw new ArgumentException(ExceptionNicknameTooLong, nameof(nickname));
- }
- var userId = await DatabaseExtensions.CheckAndGetUser(_database.Users, username);
- var userDetail = _database.UserDetails.Where(d => d.UserId == userId).SingleOrDefault();
- if (nickname == null)
- {
- if (userDetail == null || userDetail.Nickname == null)
- {
- return;
- }
- else
- {
- userDetail.Nickname = null;
- await _database.SaveChangesAsync();
- _logger.LogInformation(LogEntityNicknameSetToNull, userId);
- }
- }
- else
- {
- var create = userDetail == null;
- if (create)
- {
- userDetail = new UserDetail
- {
- UserId = userId
- };
- }
- userDetail!.Nickname = nickname;
- if (create)
- {
- _database.UserDetails.Add(userDetail);
- }
- await _database.SaveChangesAsync();
- if (create)
- {
- _logger.LogInformation(LogEntityNicknameCreate, userId, nickname);
- }
- else
- {
- _logger.LogInformation(LogEntityNicknameSetNotNull, userId, nickname);
- }
- }
- }
- }
-}
diff --git a/Timeline/Services/UserNotExistException.cs b/Timeline/Services/UserNotExistException.cs
index c7317f56..fd0b5ecf 100644
--- a/Timeline/Services/UserNotExistException.cs
+++ b/Timeline/Services/UserNotExistException.cs
@@ -31,11 +31,11 @@ namespace Timeline.Services
/// <summary>
/// The username of the user that does not exist.
/// </summary>
- public string? Username { get; set; }
+ public string Username { get; set; } = "";
/// <summary>
/// The id of the user that does not exist.
/// </summary>
- public long? Id { get; set; }
+ public long Id { get; set; }
}
}
diff --git a/Timeline/Models/UserConvert.cs b/Timeline/Services/UserRoleConvert.cs
index 5b132421..f27ee1bb 100644
--- a/Timeline/Models/UserConvert.cs
+++ b/Timeline/Services/UserRoleConvert.cs
@@ -2,33 +2,9 @@ using System;
using System.Collections.Generic;
using System.Linq;
using Timeline.Entities;
-using Timeline.Services;
-namespace Timeline.Models
+namespace Timeline.Services
{
- public static class UserConvert
- {
- public static UserInfo CreateUserInfo(User user)
- {
- if (user == null)
- throw new ArgumentNullException(nameof(user));
- return new UserInfo(user.Name, UserRoleConvert.ToBool(user.RoleString));
- }
-
- internal static UserCache CreateUserCache(User user)
- {
- if (user == null)
- throw new ArgumentNullException(nameof(user));
- return new UserCache
- {
- Username = user.Name,
- Administrator = UserRoleConvert.ToBool(user.RoleString),
- Version = user.Version
- };
- }
- }
-
- [System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1062:Validate arguments of public methods", Justification = "No need.")]
public static class UserRoleConvert
{
public const string UserRole = UserRoles.User;
diff --git a/Timeline/Services/UserService.cs b/Timeline/Services/UserService.cs
index 4012539f..7dc7159d 100644
--- a/Timeline/Services/UserService.cs
+++ b/Timeline/Services/UserService.cs
@@ -1,396 +1,462 @@
using Microsoft.EntityFrameworkCore;
-using Microsoft.Extensions.Caching.Memory;
using Microsoft.Extensions.Logging;
using System;
+using System.Globalization;
using System.Linq;
using System.Threading.Tasks;
using Timeline.Entities;
using Timeline.Helpers;
-using Timeline.Models;
using Timeline.Models.Validation;
+using static Timeline.Resources.Services.UserService;
namespace Timeline.Services
{
- public class CreateTokenResult
- {
- public string Token { get; set; } = default!;
- public UserInfo User { get; set; } = default!;
- }
-
public interface IUserService
{
/// <summary>
- /// Try to anthenticate with the given username and password.
- /// If success, create a token and return the user info.
+ /// Try to verify the given username and password.
/// </summary>
- /// <param name="username">The username of the user to anthenticate.</param>
- /// <param name="password">The password of the user to anthenticate.</param>
- /// <param name="expires">The expired time point. Null then use default. See <see cref="JwtService.GenerateJwtToken(TokenInfo, DateTime?)"/> for what is default.</param>
- /// <returns>An <see cref="CreateTokenResult"/> containing the created token and user info.</returns>
+ /// <param name="username">The username of the user to verify.</param>
+ /// <param name="password">The password of the user to verify.</param>
+ /// <returns>The user info and auth info.</returns>
/// <exception cref="ArgumentNullException">Thrown when <paramref name="username"/> or <paramref name="password"/> is null.</exception>
- /// <exception cref="UsernameBadFormatException">Thrown when username is of bad format.</exception>
+ /// <exception cref="ArgumentException">Thrown when <paramref name="username"/> is of bad format or <paramref name="password"/> is empty.</exception>
/// <exception cref="UserNotExistException">Thrown when the user with given username does not exist.</exception>
/// <exception cref="BadPasswordException">Thrown when password is wrong.</exception>
- Task<CreateTokenResult> CreateToken(string username, string password, DateTime? expires = null);
+ Task<User> VerifyCredential(string username, string password);
/// <summary>
- /// Verify the given token.
- /// If success, return the user info.
+ /// Try to get a user by id.
/// </summary>
- /// <param name="token">The token to verify.</param>
- /// <returns>The user info specified by the token.</returns>
- /// <exception cref="ArgumentNullException">Thrown when <paramref name="token"/> is null.</exception>
- /// <exception cref="JwtVerifyException">Thrown when the token is of bad format. Thrown by <see cref="JwtService.VerifyJwtToken(string)"/>.</exception>
- /// <exception cref="UserNotExistException">Thrown when the user specified by the token does not exist. Usually it has been deleted after the token was issued.</exception>
- Task<UserInfo> VerifyToken(string token);
+ /// <param name="id">The id of the user.</param>
+ /// <returns>The user info.</returns>
+ /// <exception cref="UserNotExistException">Thrown when the user with given id does not exist.</exception>
+ Task<User> GetUserById(long id);
/// <summary>
/// Get the user info of given username.
/// </summary>
/// <param name="username">Username of the user.</param>
- /// <returns>The info of the user. Null if the user of given username does not exists.</returns>
+ /// <returns>The info of the user.</returns>
/// <exception cref="ArgumentNullException">Thrown when <paramref name="username"/> is null.</exception>
- /// <exception cref="UsernameBadFormatException">Thrown when <paramref name="username"/> is of bad format.</exception>
- Task<UserInfo> GetUser(string username);
+ /// <exception cref="ArgumentException">Thrown when <paramref name="username"/> is of bad format.</exception>
+ /// <exception cref="UserNotExistException">Thrown when the user with given username does not exist.</exception>
+ Task<User> GetUserByUsername(string username);
+
+ /// <summary>
+ /// Get the user id of given username.
+ /// </summary>
+ /// <param name="username">Username of the user.</param>
+ /// <returns>The id of the user.</returns>
+ /// <exception cref="ArgumentNullException">Thrown when <paramref name="username"/> is null.</exception>
+ /// <exception cref="ArgumentException">Thrown when <paramref name="username"/> is of bad format.</exception>
+ /// <exception cref="UserNotExistException">Thrown when the user with given username does not exist.</exception>
+ Task<long> GetUserIdByUsername(string username);
/// <summary>
/// List all users.
/// </summary>
/// <returns>The user info of users.</returns>
- Task<UserInfo[]> ListUsers();
+ Task<User[]> GetUsers();
/// <summary>
- /// Create or modify a user with given username.
- /// Username must be match with [a-zA-z0-9-_].
+ /// Create a user with given info.
/// </summary>
- /// <param name="username">Username of user.</param>
- /// <param name="password">Password of user.</param>
- /// <param name="administrator">Whether the user is administrator.</param>
- /// <returns>
- /// Return <see cref="PutResult.Create"/> if a new user is created.
- /// Return <see cref="PutResult.Modify"/> if a existing user is modified.
- /// </returns>
- /// <exception cref="ArgumentNullException">Thrown when <paramref name="username"/> or <paramref name="password"/> is null.</exception>
- /// <exception cref="UsernameBadFormatException">Thrown when <paramref name="username"/> is of bad format.</exception>
- Task<PutResult> PutUser(string username, string password, bool administrator);
+ /// <param name="info">The info of new user.</param>
+ /// <param name="password">The password, can't be null or empty.</param>
+ /// <returns>The the new user.</returns>
+ /// <exception cref="ArgumentNullException">Thrown when <paramref name="info"/>is null.</exception>
+ /// <exception cref="ArgumentException">Thrown when some fields in <paramref name="info"/> is bad.</exception>
+ /// <exception cref="ConflictException">Thrown when a user with given username already exists.</exception>
+ /// <remarks>
+ /// <see cref="User.Username"/> must not be null and must be a valid username.
+ /// <see cref="User.Password"/> must not be null or empty.
+ /// <see cref="User.Administrator"/> is false by default (null).
+ /// <see cref="User.Nickname"/> must be a valid nickname if set. It is empty by default.
+ /// Other fields are ignored.
+ /// </remarks>
+ Task<User> CreateUser(User info);
/// <summary>
- /// Partially modify a user of given username.
+ /// Modify a user's info.
+ /// </summary>
+ /// <param name="id">The id of the user.</param>
+ /// <param name="info">The new info. May be null.</param>
+ /// <returns>The new user info.</returns>
+ /// <exception cref="ArgumentException">Thrown when some fields in <paramref name="info"/> is bad.</exception>
+ /// <exception cref="UserNotExistException">Thrown when user with given id does not exist.</exception>
+ /// <remarks>
+ /// Only <see cref="User.Username"/>, <see cref="User.Administrator"/>, <see cref="User.Password"/> and <see cref="User.Nickname"/> will be used.
+ /// If null, then not change.
+ /// Other fields are ignored.
+ /// Version will increase if password is changed.
+ ///
+ /// <see cref="User.Username"/> must be a valid username if set.
+ /// <see cref="User.Password"/> can't be empty if set.
+ /// <see cref="User.Nickname"/> must be a valid nickname if set.
///
- /// Note that whether actually modified or not, Version of the user will always increase.
+ /// </remarks>
+ /// <seealso cref="ModifyUser(string, User)"/>
+ Task<User> ModifyUser(long id, User? info);
+
+ /// <summary>
+ /// Modify a user's info.
/// </summary>
- /// <param name="username">Username of the user to modify. Can't be null.</param>
- /// <param name="password">New password. Null if not modify.</param>
- /// <param name="administrator">Whether the user is administrator. Null if not modify.</param>
- /// <exception cref="ArgumentNullException">Thrown if <paramref name="username"/> is null.</exception>
- /// <exception cref="UsernameBadFormatException">Thrown when <paramref name="username"/> is of bad format.</exception>
- /// <exception cref="UserNotExistException">Thrown if the user with given username does not exist.</exception>
- Task PatchUser(string username, string? password, bool? administrator);
+ /// <param name="username">The username of the user.</param>
+ /// <param name="info">The new info. May be null.</param>
+ /// <returns>The new user info.</returns>
+ /// <exception cref="ArgumentNullException">Thrown when <paramref name="username"/> is null.</exception>
+ /// <exception cref="ArgumentException">Thrown when <paramref name="username"/> is of bad format or some fields in <paramref name="info"/> is bad.</exception>
+ /// <exception cref="UserNotExistException">Thrown when user with given id does not exist.</exception>
+ /// <exception cref="ConflictException">Thrown when user with the newusername already exist.</exception>
+ /// <remarks>
+ /// Only <see cref="User.Administrator"/>, <see cref="User.Password"/> and <see cref="User.Nickname"/> will be used.
+ /// If null, then not change.
+ /// Other fields are ignored.
+ /// After modified, even if nothing is changed, version will increase.
+ ///
+ /// <see cref="User.Username"/> must be a valid username if set.
+ /// <see cref="User.Password"/> can't be empty if set.
+ /// <see cref="User.Nickname"/> must be a valid nickname if set.
+ ///
+ /// Note: Whether <see cref="User.Version"/> is set or not, version will increase and not set to the specified value if there is one.
+ /// </remarks>
+ /// <seealso cref="ModifyUser(long, User)"/>
+ Task<User> ModifyUser(string username, User? info);
+
+ /// <summary>
+ /// Delete a user of given id.
+ /// </summary>
+ /// <param name="id">Id of the user to delete.</param>
+ /// <returns>True if user is deleted, false if user not exist.</returns>
+ Task<bool> DeleteUser(long id);
/// <summary>
/// Delete a user of given username.
/// </summary>
- /// <param name="username">Username of thet user to delete. Can't be null.</param>
+ /// <param name="username">Username of the user to delete. Can't be null.</param>
+ /// <returns>True if user is deleted, false if user not exist.</returns>
/// <exception cref="ArgumentNullException">Thrown if <paramref name="username"/> is null.</exception>
- /// <exception cref="UsernameBadFormatException">Thrown when <paramref name="username"/> is of bad format.</exception>
- /// <exception cref="UserNotExistException">Thrown if the user with given username does not exist.</exception>
- Task DeleteUser(string username);
+ /// <exception cref="ArgumentException">Thrown when <paramref name="username"/> is of bad format.</exception>
+ Task<bool> DeleteUser(string username);
/// <summary>
/// Try to change a user's password with old password.
/// </summary>
- /// <param name="username">The name of user to change password of.</param>
- /// <param name="oldPassword">The user's old password.</param>
- /// <param name="newPassword">The user's new password.</param>
- /// <exception cref="ArgumentNullException">Thrown if <paramref name="username"/> or <paramref name="oldPassword"/> or <paramref name="newPassword"/> is null.</exception>
- /// <exception cref="UsernameBadFormatException">Thrown when <paramref name="username"/> is of bad format.</exception>
+ /// <param name="id">The id of user to change password of.</param>
+ /// <param name="oldPassword">Old password.</param>
+ /// <param name="newPassword">New password.</param>
+ /// <exception cref="ArgumentNullException">Thrown if <paramref name="oldPassword"/> or <paramref name="newPassword"/> is null.</exception>
+ /// <exception cref="ArgumentException">Thrown if <paramref name="oldPassword"/> or <paramref name="newPassword"/> is empty.</exception>
/// <exception cref="UserNotExistException">Thrown if the user with given username does not exist.</exception>
/// <exception cref="BadPasswordException">Thrown if the old password is wrong.</exception>
- Task ChangePassword(string username, string oldPassword, string newPassword);
-
- /// <summary>
- /// Change a user's username.
- /// </summary>
- /// <param name="oldUsername">The user's old username.</param>
- /// <param name="newUsername">The new username.</param>
- /// <exception cref="ArgumentNullException">Thrown if <paramref name="oldUsername"/> or <paramref name="newUsername"/> is null.</exception>
- /// <exception cref="UserNotExistException">Thrown if the user with old username does not exist.</exception>
- /// <exception cref="UsernameBadFormatException">Thrown if the <paramref name="oldUsername"/> or <paramref name="newUsername"/> is of bad format.</exception>
- /// <exception cref="UsernameConfictException">Thrown if user with the new username already exists.</exception>
- Task ChangeUsername(string oldUsername, string newUsername);
- }
-
- internal class UserCache
- {
- public string Username { get; set; } = default!;
- public bool Administrator { get; set; }
- public long Version { get; set; }
-
- public UserInfo ToUserInfo()
- {
- return new UserInfo(Username, Administrator);
- }
+ Task ChangePassword(long id, string oldPassword, string newPassword);
}
public class UserService : IUserService
{
private readonly ILogger<UserService> _logger;
- private readonly IMemoryCache _memoryCache;
private readonly DatabaseContext _databaseContext;
- private readonly IJwtService _jwtService;
private readonly IPasswordService _passwordService;
- private readonly UsernameValidator _usernameValidator;
-
- public UserService(ILogger<UserService> logger, IMemoryCache memoryCache, DatabaseContext databaseContext, IJwtService jwtService, IPasswordService passwordService)
+ private readonly UsernameValidator _usernameValidator = new UsernameValidator();
+ private readonly NicknameValidator _nicknameValidator = new NicknameValidator();
+ public UserService(ILogger<UserService> logger, DatabaseContext databaseContext, IPasswordService passwordService)
{
_logger = logger;
- _memoryCache = memoryCache;
_databaseContext = databaseContext;
- _jwtService = jwtService;
_passwordService = passwordService;
-
- _usernameValidator = new UsernameValidator();
}
- private static string GenerateCacheKeyByUserId(long id) => $"user:{id}";
+ private void CheckUsernameFormat(string username, string? paramName)
+ {
+ if (!_usernameValidator.Validate(username, out var message))
+ {
+ throw new ArgumentException(string.Format(CultureInfo.CurrentCulture, ExceptionUsernameBadFormat, message), paramName);
+ }
+ }
- private void RemoveCache(long id)
+ private static void CheckPasswordFormat(string password, string? paramName)
{
- var key = GenerateCacheKeyByUserId(id);
- _memoryCache.Remove(key);
- _logger.LogInformation(Log.Format(Resources.Services.UserService.LogCacheRemove, ("Key", key)));
+ if (password.Length == 0)
+ {
+ throw new ArgumentException(ExceptionPasswordEmpty, paramName);
+ }
}
- private void CheckUsernameFormat(string username, string? additionalMessage = null)
+ private void CheckNicknameFormat(string nickname, string? paramName)
{
- var (result, message) = _usernameValidator.Validate(username);
- if (!result)
+ if (!_nicknameValidator.Validate(nickname, out var message))
{
- if (additionalMessage == null)
- throw new UsernameBadFormatException(username, message);
- else
- throw new UsernameBadFormatException(username, additionalMessage + message);
+ throw new ArgumentException(string.Format(CultureInfo.CurrentCulture, ExceptionNicknameBadFormat, message), paramName);
}
}
- public async Task<CreateTokenResult> CreateToken(string username, string password, DateTime? expires)
+ private static void ThrowUsernameConflict()
+ {
+ throw new ConflictException(ExceptionUsernameConflict);
+ }
+
+ private static User CreateUserFromEntity(UserEntity entity)
+ {
+ return new User
+ {
+ Username = entity.Username,
+ Administrator = UserRoleConvert.ToBool(entity.Roles),
+ Nickname = string.IsNullOrEmpty(entity.Nickname) ? entity.Username : entity.Nickname,
+ Id = entity.Id,
+ Version = entity.Version
+ };
+ }
+
+ public async Task<User> VerifyCredential(string username, string password)
{
if (username == null)
throw new ArgumentNullException(nameof(username));
if (password == null)
throw new ArgumentNullException(nameof(password));
- CheckUsernameFormat(username);
- // We need password info, so always check the database.
- var user = await _databaseContext.Users.Where(u => u.Name == username).SingleOrDefaultAsync();
+ CheckUsernameFormat(username, nameof(username));
+ CheckPasswordFormat(password, nameof(password));
- if (user == null)
+ var entity = await _databaseContext.Users.Where(u => u.Username == username).SingleOrDefaultAsync();
+
+ if (entity == null)
throw new UserNotExistException(username);
- if (!_passwordService.VerifyPassword(user.EncryptedPassword, password))
+ if (!_passwordService.VerifyPassword(entity.Password, password))
throw new BadPasswordException(password);
- var token = _jwtService.GenerateJwtToken(new TokenInfo
- {
- Id = user.Id,
- Version = user.Version
- }, expires);
-
- return new CreateTokenResult
- {
- Token = token,
- User = UserConvert.CreateUserInfo(user)
- };
+ return CreateUserFromEntity(entity);
}
- public async Task<UserInfo> VerifyToken(string token)
+ public async Task<User> GetUserById(long id)
{
- if (token == null)
- throw new ArgumentNullException(nameof(token));
+ var user = await _databaseContext.Users.Where(u => u.Id == id).SingleOrDefaultAsync();
- TokenInfo tokenInfo;
- tokenInfo = _jwtService.VerifyJwtToken(token);
+ if (user == null)
+ throw new UserNotExistException(id);
- var id = tokenInfo.Id;
- var key = GenerateCacheKeyByUserId(id);
- if (!_memoryCache.TryGetValue<UserCache>(key, out var cache))
- {
- // no cache, check the database
- var user = await _databaseContext.Users.Where(u => u.Id == id).SingleOrDefaultAsync();
+ return CreateUserFromEntity(user);
+ }
- if (user == null)
- throw new UserNotExistException(id);
+ public async Task<User> GetUserByUsername(string username)
+ {
+ if (username == null)
+ throw new ArgumentNullException(nameof(username));
- // create cache
- cache = UserConvert.CreateUserCache(user);
- _memoryCache.CreateEntry(key).SetValue(cache);
- _logger.LogInformation(Log.Format(Resources.Services.UserService.LogCacheCreate, ("Key", key)));
- }
+ CheckUsernameFormat(username, nameof(username));
- if (tokenInfo.Version != cache.Version)
- throw new JwtVerifyException(new JwtBadVersionException(tokenInfo.Version, cache.Version), JwtVerifyException.ErrorCodes.OldVersion);
+ var entity = await _databaseContext.Users.Where(user => user.Username == username).SingleOrDefaultAsync();
- return cache.ToUserInfo();
+ if (entity == null)
+ throw new UserNotExistException(username);
+
+ return CreateUserFromEntity(entity);
}
- public async Task<UserInfo> GetUser(string username)
+ public async Task<long> GetUserIdByUsername(string username)
{
if (username == null)
throw new ArgumentNullException(nameof(username));
- CheckUsernameFormat(username);
- return await _databaseContext.Users
- .Where(user => user.Name == username)
- .Select(user => UserConvert.CreateUserInfo(user))
- .SingleOrDefaultAsync();
+ CheckUsernameFormat(username, nameof(username));
+
+ var entity = await _databaseContext.Users.Where(user => user.Username == username).Select(u => new { u.Id }).SingleOrDefaultAsync();
+
+ if (entity == null)
+ throw new UserNotExistException(username);
+
+ return entity.Id;
}
- public async Task<UserInfo[]> ListUsers()
+ public async Task<User[]> GetUsers()
{
- return await _databaseContext.Users
- .Select(user => UserConvert.CreateUserInfo(user))
- .ToArrayAsync();
+ var entities = await _databaseContext.Users.ToArrayAsync();
+ return entities.Select(user => CreateUserFromEntity(user)).ToArray();
}
- public async Task<PutResult> PutUser(string username, string password, bool administrator)
+ public async Task<User> CreateUser(User info)
{
- if (username == null)
- throw new ArgumentNullException(nameof(username));
- if (password == null)
- throw new ArgumentNullException(nameof(password));
- CheckUsernameFormat(username);
+ if (info == null)
+ throw new ArgumentNullException(nameof(info));
- var user = await _databaseContext.Users.Where(u => u.Name == username).SingleOrDefaultAsync();
+ if (info.Username == null)
+ throw new ArgumentException(ExceptionUsernameNull, nameof(info));
+ CheckUsernameFormat(info.Username, nameof(info));
- if (user == null)
- {
- var newUser = new User
- {
- Name = username,
- EncryptedPassword = _passwordService.HashPassword(password),
- RoleString = UserRoleConvert.ToString(administrator),
- Avatar = null
- };
- await _databaseContext.AddAsync(newUser);
- await _databaseContext.SaveChangesAsync();
- _logger.LogInformation(Log.Format(Resources.Services.UserService.LogDatabaseCreate,
- ("Id", newUser.Id), ("Username", username), ("Administrator", administrator)));
- return PutResult.Create;
- }
+ if (info.Password == null)
+ throw new ArgumentException(ExceptionPasswordNull, nameof(info));
+ CheckPasswordFormat(info.Password, nameof(info));
+
+ if (info.Nickname != null)
+ CheckNicknameFormat(info.Nickname, nameof(info));
+
+ var username = info.Username;
+
+ var conflict = await _databaseContext.Users.AnyAsync(u => u.Username == username);
+ if (conflict)
+ ThrowUsernameConflict();
- user.EncryptedPassword = _passwordService.HashPassword(password);
- user.RoleString = UserRoleConvert.ToString(administrator);
- user.Version += 1;
+ var administrator = info.Administrator ?? false;
+ var password = info.Password;
+ var nickname = info.Nickname;
+
+ var newEntity = new UserEntity
+ {
+ Username = username,
+ Password = _passwordService.HashPassword(password),
+ Roles = UserRoleConvert.ToString(administrator),
+ Nickname = nickname,
+ Version = 1
+ };
+ _databaseContext.Users.Add(newEntity);
await _databaseContext.SaveChangesAsync();
- _logger.LogInformation(Log.Format(Resources.Services.UserService.LogDatabaseUpdate,
- ("Id", user.Id), ("Username", username), ("Administrator", administrator)));
- //clear cache
- RemoveCache(user.Id);
+ _logger.LogInformation(Log.Format(LogDatabaseCreate,
+ ("Id", newEntity.Id), ("Username", username), ("Administrator", administrator)));
- return PutResult.Modify;
+ return CreateUserFromEntity(newEntity);
}
- public async Task PatchUser(string username, string? password, bool? administrator)
+ private void ValidateModifyUserInfo(User? info)
{
- if (username == null)
- throw new ArgumentNullException(nameof(username));
- CheckUsernameFormat(username);
+ if (info != null)
+ {
+ if (info.Username != null)
+ CheckUsernameFormat(info.Username, nameof(info));
- var user = await _databaseContext.Users.Where(u => u.Name == username).SingleOrDefaultAsync();
- if (user == null)
- throw new UserNotExistException(username);
+ if (info.Password != null)
+ CheckPasswordFormat(info.Password, nameof(info));
- if (password != null)
- {
- user.EncryptedPassword = _passwordService.HashPassword(password);
+ if (info.Nickname != null)
+ CheckNicknameFormat(info.Nickname, nameof(info));
}
+ }
- if (administrator != null)
+ private async Task UpdateUserEntity(UserEntity entity, User? info)
+ {
+ if (info != null)
{
- user.RoleString = UserRoleConvert.ToString(administrator.Value);
+ var username = info.Username;
+ if (username != null)
+ {
+ var conflict = await _databaseContext.Users.AnyAsync(u => u.Username == username);
+ if (conflict)
+ ThrowUsernameConflict();
+
+ entity.Username = username;
+ }
+
+ var password = info.Password;
+ if (password != null)
+ {
+ entity.Password = _passwordService.HashPassword(password);
+ entity.Version += 1;
+ }
+
+ var administrator = info.Administrator;
+ if (administrator.HasValue)
+ {
+ entity.Roles = UserRoleConvert.ToString(administrator.Value);
+ }
+
+ var nickname = info.Nickname;
+ if (nickname != null)
+ {
+ entity.Nickname = nickname;
+ }
}
+ }
+
+
+ public async Task<User> ModifyUser(long id, User? info)
+ {
+ ValidateModifyUserInfo(info);
+
+ var entity = await _databaseContext.Users.Where(u => u.Id == id).SingleOrDefaultAsync();
+ if (entity == null)
+ throw new UserNotExistException(id);
+
+ await UpdateUserEntity(entity, info);
- user.Version += 1;
await _databaseContext.SaveChangesAsync();
- _logger.LogInformation(Resources.Services.UserService.LogDatabaseUpdate, ("Id", user.Id));
+ _logger.LogInformation(LogDatabaseUpdate, ("Id", id));
- //clear cache
- RemoveCache(user.Id);
+ return CreateUserFromEntity(entity);
}
- public async Task DeleteUser(string username)
+ public async Task<User> ModifyUser(string username, User? info)
{
if (username == null)
throw new ArgumentNullException(nameof(username));
- CheckUsernameFormat(username);
+ CheckUsernameFormat(username, nameof(username));
- var user = await _databaseContext.Users.Where(u => u.Name == username).SingleOrDefaultAsync();
- if (user == null)
+ ValidateModifyUserInfo(info);
+
+ var entity = await _databaseContext.Users.Where(u => u.Username == username).SingleOrDefaultAsync();
+ if (entity == null)
throw new UserNotExistException(username);
- _databaseContext.Users.Remove(user);
+ await UpdateUserEntity(entity, info);
+
await _databaseContext.SaveChangesAsync();
- _logger.LogInformation(Log.Format(Resources.Services.UserService.LogDatabaseRemove,
- ("Id", user.Id)));
+ _logger.LogInformation(LogDatabaseUpdate, ("Username", username));
+
+ return CreateUserFromEntity(entity);
+ }
+
+ public async Task<bool> DeleteUser(long id)
+ {
+ var user = await _databaseContext.Users.Where(u => u.Id == id).SingleOrDefaultAsync();
+ if (user == null)
+ return false;
- //clear cache
- RemoveCache(user.Id);
+ _databaseContext.Users.Remove(user);
+ await _databaseContext.SaveChangesAsync();
+ _logger.LogInformation(Log.Format(LogDatabaseRemove, ("Id", id), ("Username", user.Username)));
+ return true;
}
- public async Task ChangePassword(string username, string oldPassword, string newPassword)
+ public async Task<bool> DeleteUser(string username)
{
if (username == null)
throw new ArgumentNullException(nameof(username));
- if (oldPassword == null)
- throw new ArgumentNullException(nameof(oldPassword));
- if (newPassword == null)
- throw new ArgumentNullException(nameof(newPassword));
- CheckUsernameFormat(username);
+ CheckUsernameFormat(username, nameof(username));
- var user = await _databaseContext.Users.Where(u => u.Name == username).SingleOrDefaultAsync();
+ var user = await _databaseContext.Users.Where(u => u.Username == username).SingleOrDefaultAsync();
if (user == null)
- throw new UserNotExistException(username);
-
- var verifyResult = _passwordService.VerifyPassword(user.EncryptedPassword, oldPassword);
- if (!verifyResult)
- throw new BadPasswordException(oldPassword);
+ return false;
- user.EncryptedPassword = _passwordService.HashPassword(newPassword);
- user.Version += 1;
+ _databaseContext.Users.Remove(user);
await _databaseContext.SaveChangesAsync();
- _logger.LogInformation(Log.Format(Resources.Services.UserService.LogDatabaseUpdate,
- ("Id", user.Id), ("Operation", "Change password")));
- //clear cache
- RemoveCache(user.Id);
+ _logger.LogInformation(Log.Format(LogDatabaseRemove, ("Id", user.Id), ("Username", username)));
+ return true;
}
- public async Task ChangeUsername(string oldUsername, string newUsername)
+ public async Task ChangePassword(long id, string oldPassword, string newPassword)
{
- if (oldUsername == null)
- throw new ArgumentNullException(nameof(oldUsername));
- if (newUsername == null)
- throw new ArgumentNullException(nameof(newUsername));
- CheckUsernameFormat(oldUsername, Resources.Services.UserService.ExceptionOldUsernameBadFormat);
- CheckUsernameFormat(newUsername, Resources.Services.UserService.ExceptionNewUsernameBadFormat);
-
- var user = await _databaseContext.Users.Where(u => u.Name == oldUsername).SingleOrDefaultAsync();
- if (user == null)
- throw new UserNotExistException(oldUsername);
+ if (oldPassword == null)
+ throw new ArgumentNullException(nameof(oldPassword));
+ if (newPassword == null)
+ throw new ArgumentNullException(nameof(newPassword));
+ CheckPasswordFormat(oldPassword, nameof(oldPassword));
+ CheckPasswordFormat(newPassword, nameof(newPassword));
+
+ var entity = await _databaseContext.Users.Where(u => u.Id == id).SingleOrDefaultAsync();
- var conflictUser = await _databaseContext.Users.Where(u => u.Name == newUsername).SingleOrDefaultAsync();
- if (conflictUser != null)
- throw new UsernameConfictException(newUsername);
+ if (entity == null)
+ throw new UserNotExistException(id);
+
+ if (!_passwordService.VerifyPassword(entity.Password, oldPassword))
+ throw new BadPasswordException(oldPassword);
- user.Name = newUsername;
- user.Version += 1;
+ entity.Password = _passwordService.HashPassword(newPassword);
+ entity.Version += 1;
await _databaseContext.SaveChangesAsync();
- _logger.LogInformation(Log.Format(Resources.Services.UserService.LogDatabaseUpdate,
- ("Id", user.Id), ("Old Username", oldUsername), ("New Username", newUsername)));
- RemoveCache(user.Id);
+ _logger.LogInformation(Log.Format(LogDatabaseUpdate, ("Id", id), ("Operation", "Change password")));
}
}
}
diff --git a/Timeline/Services/UserTokenException.cs b/Timeline/Services/UserTokenException.cs
new file mode 100644
index 00000000..ed0bae1a
--- /dev/null
+++ b/Timeline/Services/UserTokenException.cs
@@ -0,0 +1,68 @@
+using System;
+
+namespace Timeline.Services
+{
+
+ [Serializable]
+ public class UserTokenException : Exception
+ {
+ public UserTokenException() { }
+ public UserTokenException(string message) : base(message) { }
+ public UserTokenException(string message, Exception inner) : base(message, inner) { }
+ public UserTokenException(string token, string message) : base(message) { Token = token; }
+ public UserTokenException(string token, string message, Exception inner) : base(message, inner) { Token = token; }
+ protected UserTokenException(
+ System.Runtime.Serialization.SerializationInfo info,
+ System.Runtime.Serialization.StreamingContext context) : base(info, context) { }
+
+ public string Token { get; private set; } = "";
+ }
+
+
+ [Serializable]
+ public class UserTokenTimeExpireException : UserTokenException
+ {
+ public UserTokenTimeExpireException() : base(Resources.Services.Exception.UserTokenTimeExpireException) { }
+ public UserTokenTimeExpireException(string message) : base(message) { }
+ public UserTokenTimeExpireException(string message, Exception inner) : base(message, inner) { }
+ public UserTokenTimeExpireException(string token, DateTime expireTime, DateTime verifyTime) : base(token, Resources.Services.Exception.UserTokenTimeExpireException) { ExpireTime = expireTime; VerifyTime = verifyTime; }
+ public UserTokenTimeExpireException(string token, DateTime expireTime, DateTime verifyTime, Exception inner) : base(token, Resources.Services.Exception.UserTokenTimeExpireException, inner) { ExpireTime = expireTime; VerifyTime = verifyTime; }
+ protected UserTokenTimeExpireException(
+ System.Runtime.Serialization.SerializationInfo info,
+ System.Runtime.Serialization.StreamingContext context) : base(info, context) { }
+
+ public DateTime ExpireTime { get; private set; } = default;
+
+ public DateTime VerifyTime { get; private set; } = default;
+ }
+
+ [Serializable]
+ public class UserTokenBadVersionException : UserTokenException
+ {
+ public UserTokenBadVersionException() : base(Resources.Services.Exception.UserTokenBadVersionException) { }
+ public UserTokenBadVersionException(string message) : base(message) { }
+ public UserTokenBadVersionException(string message, Exception inner) : base(message, inner) { }
+ public UserTokenBadVersionException(string token, long tokenVersion, long requiredVersion) : base(token, Resources.Services.Exception.UserTokenBadVersionException) { TokenVersion = tokenVersion; RequiredVersion = requiredVersion; }
+ public UserTokenBadVersionException(string token, long tokenVersion, long requiredVersion, Exception inner) : base(token, Resources.Services.Exception.UserTokenBadVersionException, inner) { TokenVersion = tokenVersion; RequiredVersion = requiredVersion; }
+ protected UserTokenBadVersionException(
+ System.Runtime.Serialization.SerializationInfo info,
+ System.Runtime.Serialization.StreamingContext context) : base(info, context) { }
+
+ public long TokenVersion { get; set; }
+
+ public long RequiredVersion { get; set; }
+ }
+
+ [Serializable]
+ public class UserTokenBadFormatException : UserTokenException
+ {
+ public UserTokenBadFormatException() : base(Resources.Services.Exception.UserTokenBadFormatException) { }
+ public UserTokenBadFormatException(string token) : base(token, Resources.Services.Exception.UserTokenBadFormatException) { }
+ public UserTokenBadFormatException(string token, string message) : base(token, message) { }
+ public UserTokenBadFormatException(string token, Exception inner) : base(token, Resources.Services.Exception.UserTokenBadFormatException, inner) { }
+ public UserTokenBadFormatException(string token, string message, Exception inner) : base(token, message, inner) { }
+ protected UserTokenBadFormatException(
+ System.Runtime.Serialization.SerializationInfo info,
+ System.Runtime.Serialization.StreamingContext context) : base(info, context) { }
+ }
+}
diff --git a/Timeline/Services/UserTokenManager.cs b/Timeline/Services/UserTokenManager.cs
new file mode 100644
index 00000000..3e9ef3d4
--- /dev/null
+++ b/Timeline/Services/UserTokenManager.cs
@@ -0,0 +1,93 @@
+using Microsoft.Extensions.Logging;
+using System;
+using System.Threading.Tasks;
+using Timeline.Models;
+
+namespace Timeline.Services
+{
+ public class UserTokenCreateResult
+ {
+ public string Token { get; set; } = default!;
+ public User User { get; set; } = default!;
+ }
+
+ public interface IUserTokenManager
+ {
+ /// <summary>
+ /// Try to create a token for given username and password.
+ /// </summary>
+ /// <param name="username">The username.</param>
+ /// <param name="password">The password.</param>
+ /// <param name="expireAt">The expire time of the token.</param>
+ /// <returns>The created token and the user info.</returns>
+ /// <exception cref="ArgumentNullException">Thrown when <paramref name="username"/> or <paramref name="password"/> is null.</exception>
+ /// <exception cref="UsernameBadFormatException">Thrown when <paramref name="username"/> is of bad format.</exception>
+ /// <exception cref="UserNotExistException">Thrown when the user with <paramref name="username"/> does not exist.</exception>
+ /// <exception cref="BadPasswordException">Thrown when <paramref name="password"/> is wrong.</exception>
+ public Task<UserTokenCreateResult> CreateToken(string username, string password, DateTime? expireAt = null);
+
+ /// <summary>
+ /// Verify a token and get the saved user info. This also check the database for existence of the user.
+ /// </summary>
+ /// <param name="token">The token.</param>
+ /// <returns>The user stored in token.</returns>
+ /// <exception cref="ArgumentNullException">Thrown when <paramref name="token"/> is null.</exception>
+ /// <exception cref="UserTokenTimeExpireException">Thrown when the token is expired.</exception>
+ /// <exception cref="UserTokenBadVersionException">Thrown when the token is of bad version.</exception>
+ /// <exception cref="UserTokenBadFormatException">Thrown when the token is of bad format.</exception>
+ /// <exception cref="UserNotExistException">Thrown when the user specified by the token does not exist. Usually the user had been deleted after the token was issued.</exception>
+ public Task<User> VerifyToken(string token);
+ }
+
+ public class UserTokenManager : IUserTokenManager
+ {
+ private readonly ILogger<UserTokenManager> _logger;
+ private readonly IUserService _userService;
+ private readonly IUserTokenService _userTokenService;
+ private readonly IClock _clock;
+
+ public UserTokenManager(ILogger<UserTokenManager> logger, IUserService userService, IUserTokenService userTokenService, IClock clock)
+ {
+ _logger = logger;
+ _userService = userService;
+ _userTokenService = userTokenService;
+ _clock = clock;
+ }
+
+ public async Task<UserTokenCreateResult> CreateToken(string username, string password, DateTime? expireAt = null)
+ {
+ if (username == null)
+ throw new ArgumentNullException(nameof(username));
+ if (password == null)
+ throw new ArgumentNullException(nameof(password));
+
+ var user = await _userService.VerifyCredential(username, password);
+ var token = _userTokenService.GenerateToken(new UserTokenInfo { Id = user.Id!.Value, Version = user.Version!.Value, ExpireAt = expireAt });
+
+ return new UserTokenCreateResult { Token = token, User = user };
+ }
+
+
+ public async Task<User> VerifyToken(string token)
+ {
+ if (token == null)
+ throw new ArgumentNullException(nameof(token));
+
+ var tokenInfo = _userTokenService.VerifyToken(token);
+
+ if (tokenInfo.ExpireAt.HasValue)
+ {
+ var currentTime = _clock.GetCurrentTime();
+ if (tokenInfo.ExpireAt < currentTime)
+ throw new UserTokenTimeExpireException(token, tokenInfo.ExpireAt.Value, currentTime);
+ }
+
+ var user = await _userService.GetUserById(tokenInfo.Id);
+
+ if (tokenInfo.Version < user.Version)
+ throw new UserTokenBadVersionException(token, tokenInfo.Version, user.Version.Value);
+
+ return user;
+ }
+ }
+}
diff --git a/Timeline/Services/JwtService.cs b/Timeline/Services/UserTokenService.cs
index bf92966a..cf7286f4 100644
--- a/Timeline/Services/JwtService.cs
+++ b/Timeline/Services/UserTokenService.cs
@@ -9,50 +9,59 @@ using Timeline.Configs;
namespace Timeline.Services
{
- public class TokenInfo
+ public class UserTokenInfo
{
public long Id { get; set; }
public long Version { get; set; }
+ public DateTime? ExpireAt { get; set; }
}
- public interface IJwtService
+ public interface IUserTokenService
{
/// <summary>
- /// Create a JWT token for a given token info.
+ /// Create a token for a given token info.
/// </summary>
/// <param name="tokenInfo">The info to generate token.</param>
- /// <param name="expires">The expire time. If null then use current time with offset in config.</param>
/// <returns>Return the generated token.</returns>
/// <exception cref="ArgumentNullException">Thrown when <paramref name="tokenInfo"/> is null.</exception>
- string GenerateJwtToken(TokenInfo tokenInfo, DateTime? expires = null);
+ string GenerateToken(UserTokenInfo tokenInfo);
/// <summary>
- /// Verify a JWT token.
- /// Return null is <paramref name="token"/> is null.
+ /// Verify a token and get the saved info.
/// </summary>
- /// <param name="token">The token string to verify.</param>
- /// <returns>Return the saved info in token.</returns>
+ /// <param name="token">The token to verify.</param>
+ /// <returns>The saved info in token.</returns>
/// <exception cref="ArgumentNullException">Thrown when <paramref name="token"/> is null.</exception>
- /// <exception cref="JwtVerifyException">Thrown when the token is invalid.</exception>
- TokenInfo VerifyJwtToken(string token);
-
+ /// <exception cref="UserTokenBadFormatException">Thrown when the token is of bad format.</exception>
+ /// <remarks>
+ /// If this method throw <see cref="UserTokenBadFormatException"/>, it usually means the token is not created by this service.
+ /// </remarks>
+ UserTokenInfo VerifyToken(string token);
}
- public class JwtService : IJwtService
+ public class JwtUserTokenService : IUserTokenService
{
private const string VersionClaimType = "timeline_version";
private readonly IOptionsMonitor<JwtConfig> _jwtConfig;
- private readonly JwtSecurityTokenHandler _tokenHandler = new JwtSecurityTokenHandler();
private readonly IClock _clock;
- public JwtService(IOptionsMonitor<JwtConfig> jwtConfig, IClock clock)
+ private readonly JwtSecurityTokenHandler _tokenHandler = new JwtSecurityTokenHandler();
+ private SymmetricSecurityKey _tokenSecurityKey;
+
+ public JwtUserTokenService(IOptionsMonitor<JwtConfig> jwtConfig, IClock clock)
{
_jwtConfig = jwtConfig;
_clock = clock;
+
+ _tokenSecurityKey = new SymmetricSecurityKey(Encoding.ASCII.GetBytes(jwtConfig.CurrentValue.SigningKey));
+ jwtConfig.OnChange(config =>
+ {
+ _tokenSecurityKey = new SymmetricSecurityKey(Encoding.ASCII.GetBytes(config.SigningKey));
+ });
}
- public string GenerateJwtToken(TokenInfo tokenInfo, DateTime? expires = null)
+ public string GenerateToken(UserTokenInfo tokenInfo)
{
if (tokenInfo == null)
throw new ArgumentNullException(nameof(tokenInfo));
@@ -71,7 +80,7 @@ namespace Timeline.Services
SigningCredentials = new SigningCredentials(
new SymmetricSecurityKey(Encoding.ASCII.GetBytes(config.SigningKey)), SecurityAlgorithms.HmacSha384),
IssuedAt = _clock.GetCurrentTime(),
- Expires = expires.GetValueOrDefault(_clock.GetCurrentTime().AddSeconds(config.DefaultExpireOffset)),
+ Expires = tokenInfo.ExpireAt.GetValueOrDefault(_clock.GetCurrentTime().AddSeconds(config.DefaultExpireOffset)),
NotBefore = _clock.GetCurrentTime() // I must explicitly set this or it will use the current time by default and mock is not work in which case test will not pass.
};
@@ -82,7 +91,7 @@ namespace Timeline.Services
}
- public TokenInfo VerifyJwtToken(string token)
+ public UserTokenInfo VerifyToken(string token)
{
if (token == null)
throw new ArgumentNullException(nameof(token));
@@ -95,37 +104,42 @@ namespace Timeline.Services
ValidateIssuer = true,
ValidateAudience = true,
ValidateIssuerSigningKey = true,
- ValidateLifetime = true,
+ ValidateLifetime = false,
ValidIssuer = config.Issuer,
ValidAudience = config.Audience,
- IssuerSigningKey = new SymmetricSecurityKey(Encoding.ASCII.GetBytes(config.SigningKey))
- }, out _);
+ IssuerSigningKey = _tokenSecurityKey
+ }, out var t);
var idClaim = principal.FindFirstValue(ClaimTypes.NameIdentifier);
if (idClaim == null)
- throw new JwtVerifyException(JwtVerifyException.ErrorCodes.NoIdClaim);
+ throw new JwtUserTokenBadFormatException(token, JwtUserTokenBadFormatException.ErrorKind.NoIdClaim);
if (!long.TryParse(idClaim, out var id))
- throw new JwtVerifyException(JwtVerifyException.ErrorCodes.IdClaimBadFormat);
+ throw new JwtUserTokenBadFormatException(token, JwtUserTokenBadFormatException.ErrorKind.IdClaimBadFormat);
var versionClaim = principal.FindFirstValue(VersionClaimType);
if (versionClaim == null)
- throw new JwtVerifyException(JwtVerifyException.ErrorCodes.NoVersionClaim);
+ throw new JwtUserTokenBadFormatException(token, JwtUserTokenBadFormatException.ErrorKind.NoVersionClaim);
if (!long.TryParse(versionClaim, out var version))
- throw new JwtVerifyException(JwtVerifyException.ErrorCodes.VersionClaimBadFormat);
+ throw new JwtUserTokenBadFormatException(token, JwtUserTokenBadFormatException.ErrorKind.VersionClaimBadFormat);
+
+ var decodedToken = (JwtSecurityToken)t;
+ var exp = decodedToken.Payload.Exp;
+ DateTime? expireAt = null;
+ if (exp.HasValue)
+ {
+ expireAt = EpochTime.DateTime(exp.Value);
+ }
- return new TokenInfo
+ return new UserTokenInfo
{
Id = id,
- Version = version
+ Version = version,
+ ExpireAt = expireAt
};
}
- catch (SecurityTokenExpiredException e)
- {
- throw new JwtVerifyException(e, JwtVerifyException.ErrorCodes.Expired);
- }
- catch (Exception e)
+ catch (Exception e) when (e is SecurityTokenException || e is ArgumentException)
{
- throw new JwtVerifyException(e, JwtVerifyException.ErrorCodes.Others);
+ throw new JwtUserTokenBadFormatException(token, JwtUserTokenBadFormatException.ErrorKind.Other, e);
}
}
}
diff --git a/Timeline/Services/UsernameBadFormatException.cs b/Timeline/Services/UsernameBadFormatException.cs
deleted file mode 100644
index d82bf962..00000000
--- a/Timeline/Services/UsernameBadFormatException.cs
+++ /dev/null
@@ -1,27 +0,0 @@
-using System;
-
-namespace Timeline.Services
-{
- /// <summary>
- /// Thrown when username is of bad format.
- /// </summary>
- [Serializable]
- public class UsernameBadFormatException : Exception
- {
- public UsernameBadFormatException() : base(Resources.Services.Exception.UsernameBadFormatException) { }
- public UsernameBadFormatException(string username) : this() { Username = username; }
- public UsernameBadFormatException(string username, Exception inner) : base(Resources.Services.Exception.UsernameBadFormatException, inner) { Username = username; }
-
- public UsernameBadFormatException(string username, string message) : base(message) { Username = username; }
- public UsernameBadFormatException(string username, string message, Exception inner) : base(message, inner) { Username = username; }
-
- protected UsernameBadFormatException(
- System.Runtime.Serialization.SerializationInfo info,
- System.Runtime.Serialization.StreamingContext context) : base(info, context) { }
-
- /// <summary>
- /// Username of bad format.
- /// </summary>
- public string? Username { get; private set; }
- }
-}
diff --git a/Timeline/Services/UsernameConfictException.cs b/Timeline/Services/UsernameConfictException.cs
deleted file mode 100644
index fde1eda6..00000000
--- a/Timeline/Services/UsernameConfictException.cs
+++ /dev/null
@@ -1,25 +0,0 @@
-using System;
-using Timeline.Helpers;
-
-namespace Timeline.Services
-{
- /// <summary>
- /// Thrown when the user already exists.
- /// </summary>
- [Serializable]
- public class UsernameConfictException : Exception
- {
- public UsernameConfictException() : base(Resources.Services.Exception.UsernameConfictException) { }
- public UsernameConfictException(string username) : base(Log.Format(Resources.Services.Exception.UsernameConfictException, ("Username", username))) { Username = username; }
- public UsernameConfictException(string username, string message) : base(message) { Username = username; }
- public UsernameConfictException(string message, Exception inner) : base(message, inner) { }
- protected UsernameConfictException(
- System.Runtime.Serialization.SerializationInfo info,
- System.Runtime.Serialization.StreamingContext context) : base(info, context) { }
-
- /// <summary>
- /// The username that already exists.
- /// </summary>
- public string? Username { get; set; }
- }
-}
diff --git a/Timeline/Startup.cs b/Timeline/Startup.cs
index 5b6499a4..86349a27 100644
--- a/Timeline/Startup.cs
+++ b/Timeline/Startup.cs
@@ -1,14 +1,16 @@
+using AutoMapper;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.HttpOverrides;
-using Microsoft.AspNetCore.Localization;
+using Microsoft.AspNetCore.Mvc.Infrastructure;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.DependencyInjection.Extensions;
using Microsoft.Extensions.Hosting;
+using Pomelo.EntityFrameworkCore.MySql.Infrastructure;
+using Pomelo.EntityFrameworkCore.MySql.Storage;
using System;
-using System.Collections.Generic;
-using System.Globalization;
using System.Text.Json.Serialization;
using Timeline.Auth;
using Timeline.Configs;
@@ -50,7 +52,6 @@ namespace Timeline
});
services.Configure<JwtConfig>(Configuration.GetSection(nameof(JwtConfig)));
- var jwtConfig = Configuration.GetSection(nameof(JwtConfig)).Get<JwtConfig>();
services.AddAuthentication(AuthenticationConstants.Scheme)
.AddScheme<MyAuthenticationOptions, MyAuthenticationHandler>(AuthenticationConstants.Scheme, AuthenticationConstants.DisplayName, o => { });
services.AddAuthorization();
@@ -78,20 +79,20 @@ namespace Timeline
});
}
- services.AddLocalization(options =>
- {
- options.ResourcesPath = "Resources";
- });
+ services.AddAutoMapper(GetType().Assembly);
- services.AddScoped<IUserService, UserService>();
- services.AddScoped<IJwtService, JwtService>();
- services.AddTransient<IPasswordService, PasswordService>();
services.AddTransient<IClock, Clock>();
+
+ services.AddTransient<IPasswordService, PasswordService>();
+ services.AddScoped<IUserService, UserService>();
+ services.AddScoped<IUserTokenService, JwtUserTokenService>();
+ services.AddScoped<IUserTokenManager, UserTokenManager>();
services.AddUserAvatarService();
- services.AddScoped<IUserDetailService, UserDetailService>();
services.AddScoped<IPersonalTimelineService, PersonalTimelineService>();
+ services.TryAddSingleton<IActionContextAccessor, ActionContextAccessor>();
+
var databaseConfig = Configuration.GetSection(nameof(DatabaseConfig)).Get<DatabaseConfig>();
if (databaseConfig.UseDevelopment)
@@ -109,11 +110,10 @@ namespace Timeline
{
if (databaseConfig.ConnectionString == null)
throw new InvalidOperationException("DatabaseConfig.ConnectionString is not set. Please set it as a mysql connection string.");
- options.UseMySql(databaseConfig.ConnectionString);
+ options.UseMySql(databaseConfig.ConnectionString,
+ mySqlOptions => mySqlOptions.ServerVersion(new ServerVersion(new Version(5, 7), ServerType.MySql)));
});
}
-
- services.AddMemoryCache();
}
@@ -127,19 +127,6 @@ namespace Timeline
app.UseRouting();
- var supportedCultures = new List<CultureInfo>
- {
- new CultureInfo("en"),
- new CultureInfo("zh")
- };
-
- app.UseRequestLocalization(new RequestLocalizationOptions
- {
- DefaultRequestCulture = new RequestCulture("en"),
- SupportedCultures = supportedCultures,
- SupportedUICultures = supportedCultures
- });
-
app.UseCors();
app.UseAuthentication();
diff --git a/Timeline/Timeline.csproj b/Timeline/Timeline.csproj
index 681f49cb..1a3a07cd 100644
--- a/Timeline/Timeline.csproj
+++ b/Timeline/Timeline.csproj
@@ -15,38 +15,39 @@
</ItemGroup>
<ItemGroup>
+ <PackageReference Include="AutoMapper" Version="9.0.0" />
+ <PackageReference Include="AutoMapper.Extensions.Microsoft.DependencyInjection" Version="7.0.0" />
<PackageReference Include="Microsoft.CodeAnalysis.FxCopAnalyzers" Version="2.9.8">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
- <PackageReference Include="Microsoft.EntityFrameworkCore" Version="3.1.0" />
- <PackageReference Include="Microsoft.EntityFrameworkCore.Analyzers" Version="3.1.0" />
- <PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="3.1.0" />
- <PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="3.1.0">
+ <PackageReference Include="Microsoft.EntityFrameworkCore" Version="3.1.1" />
+ <PackageReference Include="Microsoft.EntityFrameworkCore.Analyzers" Version="3.1.1" />
+ <PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="3.1.1" />
+ <PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="3.1.1">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
- <PackageReference Include="Pomelo.EntityFrameworkCore.MySql" Version="3.1.0" />
+ <PackageReference Include="Pomelo.EntityFrameworkCore.MySql" Version="3.1.1" />
<PackageReference Include="Pomelo.EntityFrameworkCore.MySql.Design" Version="1.1.2" />
<PackageReference Include="SixLabors.ImageSharp" Version="1.0.0-dev002868" />
<PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="5.6.0" />
</ItemGroup>
<ItemGroup>
+ <ProjectReference Include="..\Timeline.ErrorCodes\Timeline.ErrorCodes.csproj" />
+ </ItemGroup>
+
+ <ItemGroup>
<Compile Update="Resources\Authentication\AuthHandler.Designer.cs">
<DesignTime>True</DesignTime>
<AutoGen>True</AutoGen>
<DependentUpon>AuthHandler.resx</DependentUpon>
</Compile>
- <Compile Update="Resources\Common.Designer.cs">
- <DesignTime>True</DesignTime>
- <AutoGen>True</AutoGen>
- <DependentUpon>Common.resx</DependentUpon>
- </Compile>
- <Compile Update="Resources\Controllers\Testing\TestingI18nController.Designer.cs">
- <DesignTime>True</DesignTime>
- <AutoGen>True</AutoGen>
- <DependentUpon>TestingI18nController.resx</DependentUpon>
+ <Compile Update="Resources\Controllers\ControllerAuthExtensions.Designer.cs">
+ <DesignTime>True</DesignTime>
+ <AutoGen>True</AutoGen>
+ <DependentUpon>ControllerAuthExtensions.resx</DependentUpon>
</Compile>
<Compile Update="Resources\Controllers\TimelineController.Designer.cs">
<DesignTime>True</DesignTime>
@@ -73,11 +74,21 @@
<AutoGen>True</AutoGen>
<DependentUpon>Filters.resx</DependentUpon>
</Compile>
+ <Compile Update="Resources\Messages.Designer.cs">
+ <DesignTime>True</DesignTime>
+ <AutoGen>True</AutoGen>
+ <DependentUpon>Messages.resx</DependentUpon>
+ </Compile>
<Compile Update="Resources\Models\Http\Common.Designer.cs">
<DesignTime>True</DesignTime>
<AutoGen>True</AutoGen>
<DependentUpon>Common.resx</DependentUpon>
</Compile>
+ <Compile Update="Resources\Models\Validation\NicknameValidator.Designer.cs">
+ <DesignTime>True</DesignTime>
+ <AutoGen>True</AutoGen>
+ <DependentUpon>NicknameValidator.resx</DependentUpon>
+ </Compile>
<Compile Update="Resources\Models\Validation\UsernameValidator.Designer.cs">
<DesignTime>True</DesignTime>
<AutoGen>True</AutoGen>
@@ -93,16 +104,16 @@
<AutoGen>True</AutoGen>
<DependentUpon>Exception.resx</DependentUpon>
</Compile>
+ <Compile Update="Resources\Services\TimelineService.Designer.cs">
+ <DesignTime>True</DesignTime>
+ <AutoGen>True</AutoGen>
+ <DependentUpon>TimelineService.resx</DependentUpon>
+ </Compile>
<Compile Update="Resources\Services\UserAvatarService.Designer.cs">
<DesignTime>True</DesignTime>
<AutoGen>True</AutoGen>
<DependentUpon>UserAvatarService.resx</DependentUpon>
</Compile>
- <Compile Update="Resources\Services\UserDetailService.Designer.cs">
- <DesignTime>True</DesignTime>
- <AutoGen>True</AutoGen>
- <DependentUpon>UserDetailService.resx</DependentUpon>
- </Compile>
<Compile Update="Resources\Services\UserService.Designer.cs">
<DesignTime>True</DesignTime>
<AutoGen>True</AutoGen>
@@ -115,13 +126,9 @@
<Generator>ResXFileCodeGenerator</Generator>
<LastGenOutput>AuthHandler.Designer.cs</LastGenOutput>
</EmbeddedResource>
- <EmbeddedResource Update="Resources\Common.resx">
- <Generator>ResXFileCodeGenerator</Generator>
- <LastGenOutput>Common.Designer.cs</LastGenOutput>
- </EmbeddedResource>
- <EmbeddedResource Update="Resources\Controllers\Testing\TestingI18nController.resx">
- <Generator>ResXFileCodeGenerator</Generator>
- <LastGenOutput>TestingI18nController.Designer.cs</LastGenOutput>
+ <EmbeddedResource Update="Resources\Controllers\ControllerAuthExtensions.resx">
+ <Generator>ResXFileCodeGenerator</Generator>
+ <LastGenOutput>ControllerAuthExtensions.Designer.cs</LastGenOutput>
</EmbeddedResource>
<EmbeddedResource Update="Resources\Controllers\TimelineController.resx">
<Generator>ResXFileCodeGenerator</Generator>
@@ -144,10 +151,18 @@
<Generator>ResXFileCodeGenerator</Generator>
<LastGenOutput>Filters.Designer.cs</LastGenOutput>
</EmbeddedResource>
+ <EmbeddedResource Update="Resources\Messages.resx">
+ <Generator>ResXFileCodeGenerator</Generator>
+ <LastGenOutput>Messages.Designer.cs</LastGenOutput>
+ </EmbeddedResource>
<EmbeddedResource Update="Resources\Models\Http\Common.resx">
<Generator>ResXFileCodeGenerator</Generator>
<LastGenOutput>Common.Designer.cs</LastGenOutput>
</EmbeddedResource>
+ <EmbeddedResource Update="Resources\Models\Validation\NicknameValidator.resx">
+ <Generator>ResXFileCodeGenerator</Generator>
+ <LastGenOutput>NicknameValidator.Designer.cs</LastGenOutput>
+ </EmbeddedResource>
<EmbeddedResource Update="Resources\Models\Validation\UsernameValidator.resx">
<Generator>ResXFileCodeGenerator</Generator>
<LastGenOutput>UsernameValidator.Designer.cs</LastGenOutput>
@@ -160,14 +175,14 @@
<Generator>ResXFileCodeGenerator</Generator>
<LastGenOutput>Exception.Designer.cs</LastGenOutput>
</EmbeddedResource>
+ <EmbeddedResource Update="Resources\Services\TimelineService.resx">
+ <Generator>ResXFileCodeGenerator</Generator>
+ <LastGenOutput>TimelineService.Designer.cs</LastGenOutput>
+ </EmbeddedResource>
<EmbeddedResource Update="Resources\Services\UserAvatarService.resx">
<Generator>ResXFileCodeGenerator</Generator>
<LastGenOutput>UserAvatarService.Designer.cs</LastGenOutput>
</EmbeddedResource>
- <EmbeddedResource Update="Resources\Services\UserDetailService.resx">
- <Generator>ResXFileCodeGenerator</Generator>
- <LastGenOutput>UserDetailService.Designer.cs</LastGenOutput>
- </EmbeddedResource>
<EmbeddedResource Update="Resources\Services\UserService.resx">
<Generator>ResXFileCodeGenerator</Generator>
<LastGenOutput>UserService.Designer.cs</LastGenOutput>