diff options
author | 杨宇千 <crupest@outlook.com> | 2019-10-24 20:15:58 +0800 |
---|---|---|
committer | GitHub <noreply@github.com> | 2019-10-24 20:15:58 +0800 |
commit | 7305358a88ffc87f51f7b78deb4f07ef99120beb (patch) | |
tree | 7ca5010a06829cc5fadea1ea17ae72d082fc344c /Timeline | |
parent | 297d0c9029360f1d5334ed843b9b299356740ec1 (diff) | |
parent | a0f3cd7599a48c14fb5492fb1c6e2dbd0a82fb45 (diff) | |
download | timeline-7305358a88ffc87f51f7b78deb4f07ef99120beb.tar.gz timeline-7305358a88ffc87f51f7b78deb4f07ef99120beb.tar.bz2 timeline-7305358a88ffc87f51f7b78deb4f07ef99120beb.zip |
Merge pull request #50 from crupest/refactor
Refactor : A Huge Step
Diffstat (limited to 'Timeline')
85 files changed, 5870 insertions, 1418 deletions
diff --git a/Timeline/Authenticate/PrincipalExtensions.cs b/Timeline/Authenticate/PrincipalExtensions.cs deleted file mode 100644 index fa39ea89..00000000 --- a/Timeline/Authenticate/PrincipalExtensions.cs +++ /dev/null @@ -1,13 +0,0 @@ -using System.Security.Principal;
-using Timeline.Entities;
-
-namespace Timeline.Authenticate
-{
- public static class PrincipalExtensions
- {
- public static bool IsAdmin(this IPrincipal principal)
- {
- return principal.IsInRole(UserRoles.Admin);
- }
- }
-}
diff --git a/Timeline/Authenticate/Attribute.cs b/Timeline/Authentication/Attribute.cs index 239a2a1c..370b37e1 100644 --- a/Timeline/Authenticate/Attribute.cs +++ b/Timeline/Authentication/Attribute.cs @@ -1,7 +1,7 @@ using Microsoft.AspNetCore.Authorization;
using Timeline.Entities;
-namespace Timeline.Authenticate
+namespace Timeline.Authentication
{
public class AdminAuthorizeAttribute : AuthorizeAttribute
{
diff --git a/Timeline/Authenticate/AuthHandler.cs b/Timeline/Authentication/AuthHandler.cs index f9409c1a..47ed1d71 100644 --- a/Timeline/Authenticate/AuthHandler.cs +++ b/Timeline/Authentication/AuthHandler.cs @@ -10,7 +10,7 @@ using System.Threading.Tasks; using Timeline.Models;
using Timeline.Services;
-namespace Timeline.Authenticate
+namespace Timeline.Authentication
{
static class AuthConstants
{
@@ -18,7 +18,7 @@ namespace Timeline.Authenticate public const string DisplayName = "My Jwt Auth Scheme";
}
- class AuthOptions : AuthenticationSchemeOptions
+ public class AuthOptions : AuthenticationSchemeOptions
{
/// <summary>
/// The query param key to search for token. If null then query params are not searched for token. Default to <c>"token"</c>.
@@ -26,7 +26,7 @@ namespace Timeline.Authenticate public string TokenQueryParamKey { get; set; } = "token";
}
- class AuthHandler : AuthenticationHandler<AuthOptions>
+ public class AuthHandler : AuthenticationHandler<AuthOptions>
{
private readonly ILogger<AuthHandler> _logger;
private readonly IUserService _userService;
@@ -39,14 +39,14 @@ namespace Timeline.Authenticate }
// return null if no token is found
- private string ExtractToken()
+ private string? ExtractToken()
{
// check the authorization header
string header = Request.Headers[HeaderNames.Authorization];
- if (!string.IsNullOrEmpty(header) && header.StartsWith("Bearer ", StringComparison.OrdinalIgnoreCase))
+ if (!string.IsNullOrEmpty(header) && header.StartsWith("Bearer ", StringComparison.InvariantCultureIgnoreCase))
{
var token = header.Substring("Bearer ".Length).Trim();
- _logger.LogInformation("Token is found in authorization header. Token is {} .", token);
+ _logger.LogInformation(Resources.Authentication.AuthHandler.LogTokenFoundInHeader, token);
return token;
}
@@ -57,7 +57,7 @@ namespace Timeline.Authenticate string token = Request.Query[paramQueryKey];
if (!string.IsNullOrEmpty(token))
{
- _logger.LogInformation("Token is found in query param with key \"{}\". Token is {} .", paramQueryKey, token);
+ _logger.LogInformation(Resources.Authentication.AuthHandler.LogTokenFoundInQuery, paramQueryKey, token);
return token;
}
}
@@ -71,7 +71,7 @@ namespace Timeline.Authenticate var token = ExtractToken();
if (string.IsNullOrEmpty(token))
{
- _logger.LogInformation("No jwt token is found.");
+ _logger.LogInformation(Resources.Authentication.AuthHandler.LogTokenNotFound);
return AuthenticateResult.NoResult();
}
@@ -81,20 +81,16 @@ namespace Timeline.Authenticate var identity = new ClaimsIdentity(AuthConstants.Scheme);
identity.AddClaim(new Claim(identity.NameClaimType, userInfo.Username, ClaimValueTypes.String));
- identity.AddClaims(UserUtility.IsAdminToRoleArray(userInfo.Administrator).Select(role => new Claim(identity.RoleClaimType, role, ClaimValueTypes.String)));
+ identity.AddClaims(UserRoleConvert.ToArray(userInfo.Administrator).Select(role => new Claim(identity.RoleClaimType, role, ClaimValueTypes.String)));
var principal = new ClaimsPrincipal();
principal.AddIdentity(identity);
return AuthenticateResult.Success(new AuthenticationTicket(principal, AuthConstants.Scheme));
}
- catch (ArgumentException)
+ catch (Exception e) when (e! is ArgumentException)
{
- throw; // this exception usually means server error.
- }
- catch (Exception e)
- {
- _logger.LogInformation(e, "A jwt token validation failed.");
+ _logger.LogInformation(e, Resources.Authentication.AuthHandler.LogTokenValidationFail);
return AuthenticateResult.Fail(e);
}
}
diff --git a/Timeline/Authentication/PrincipalExtensions.cs b/Timeline/Authentication/PrincipalExtensions.cs new file mode 100644 index 00000000..8d77ab62 --- /dev/null +++ b/Timeline/Authentication/PrincipalExtensions.cs @@ -0,0 +1,13 @@ +using System.Security.Principal;
+using Timeline.Entities;
+
+namespace Timeline.Authentication
+{
+ internal static class PrincipalExtensions
+ {
+ internal static bool IsAdministrator(this IPrincipal principal)
+ {
+ return principal.IsInRole(UserRoles.Admin);
+ }
+ }
+}
diff --git a/Timeline/Configs/DatabaseConfig.cs b/Timeline/Configs/DatabaseConfig.cs index e24ecdfb..c9309b08 100644 --- a/Timeline/Configs/DatabaseConfig.cs +++ b/Timeline/Configs/DatabaseConfig.cs @@ -2,6 +2,6 @@ namespace Timeline.Configs {
public class DatabaseConfig
{
- public string ConnectionString { get; set; }
+ public string ConnectionString { get; set; } = default!;
}
}
diff --git a/Timeline/Configs/JwtConfig.cs b/Timeline/Configs/JwtConfig.cs index 8c61d7bc..8a17825e 100644 --- a/Timeline/Configs/JwtConfig.cs +++ b/Timeline/Configs/JwtConfig.cs @@ -2,9 +2,9 @@ namespace Timeline.Configs {
public class JwtConfig
{
- public string Issuer { get; set; }
- public string Audience { get; set; }
- public string SigningKey { get; set; }
+ public string Issuer { get; set; } = default!;
+ public string Audience { get; set; } = default!;
+ public string SigningKey { get; set; } = default!;
/// <summary>
/// Set the default value of expire offset of jwt token.
diff --git a/Timeline/Controllers/UserTestController.cs b/Timeline/Controllers/Testing/TestingAuthController.cs index 2a5f36a1..67b5b2ef 100644 --- a/Timeline/Controllers/UserTestController.cs +++ b/Timeline/Controllers/Testing/TestingAuthController.cs @@ -1,12 +1,12 @@ using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
-using Timeline.Authenticate;
+using Timeline.Authentication;
-namespace Timeline.Controllers
+namespace Timeline.Controllers.Testing
{
- [Route("Test/User")]
+ [Route("testing/auth")]
[ApiController]
- public class UserTestController : Controller
+ public class TestingAuthController : Controller
{
[HttpGet("[action]")]
[Authorize]
diff --git a/Timeline/Controllers/TokenController.cs b/Timeline/Controllers/TokenController.cs index 3c166448..4e32d26f 100644 --- a/Timeline/Controllers/TokenController.cs +++ b/Timeline/Controllers/TokenController.cs @@ -3,71 +3,84 @@ using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Logging;
using Microsoft.IdentityModel.Tokens;
using System;
-using System.Collections.Generic;
using System.Threading.Tasks;
using Timeline.Models.Http;
using Timeline.Services;
-using static Timeline.Helpers.MyLogHelper;
+using Timeline.Helpers;
+using Microsoft.Extensions.Localization;
+using System.Globalization;
+using static Timeline.Resources.Controllers.TokenController;
-namespace Timeline.Controllers
+namespace Timeline
{
- [Route("token")]
- [ApiController]
- public class TokenController : Controller
+ public static partial class ErrorCodes
{
- private static class LoggingEventIds
- {
- public const int CreateSucceeded = 1000;
- public const int CreateFailed = 1001;
-
- public const int VerifySucceeded = 2000;
- public const int VerifyFailed = 2001;
- }
-
- public static class ErrorCodes
+ public static partial class Http
{
- public const int Create_UserNotExist = -1001;
- public const int Create_BadPassword = -1002;
- public const int Create_BadExpireOffset = -1003;
+ public static class Token // bbb = 001
+ {
+ public static class Create // cc = 01
+ {
+ public const int BadCredential = 10010101;
+ }
- public const int Verify_BadToken = -2001;
- public const int Verify_UserNotExist = -2002;
- public const int Verify_BadVersion = -2003;
- public const int Verify_Expired = -2004;
+ 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 ILogger<TokenController> _logger;
private readonly IClock _clock;
+ private readonly IStringLocalizer<TokenController> _localizer;
- public TokenController(IUserService userService, ILogger<TokenController> logger, IClock clock)
+ public TokenController(IUserService userService, ILogger<TokenController> logger, IClock clock, IStringLocalizer<TokenController> localizer)
{
_userService = userService;
_logger = logger;
_clock = clock;
+ _localizer = localizer;
}
[HttpPost("create")]
[AllowAnonymous]
- public async Task<IActionResult> Create([FromBody] CreateTokenRequest request)
+ public async Task<ActionResult<CreateTokenResponse>> Create([FromBody] CreateTokenRequest request)
{
- void LogFailure(string reason, int code, Exception e = null)
+ void LogFailure(string reason, Exception? e = null)
{
- _logger.LogInformation(LoggingEventIds.CreateFailed, e, FormatLogMessage("Attemp to login failed.",
- Pair("Reason", reason),
- Pair("Code", code),
- Pair("Username", request.Username),
- Pair("Password", request.Password),
- Pair("Expire Offset (in days)", request.ExpireOffset)));
+ _logger.LogInformation(e, Log.Format(LogCreateFailure,
+ ("Reason", reason),
+ ("Username", request.Username),
+ ("Password", request.Password),
+ ("Expire (in days)", request.Expire)
+ ));
}
try
{
- var expiredTime = request.ExpireOffset == null ? null : (DateTime?)(_clock.GetCurrentTime().AddDays(request.ExpireOffset.Value));
- var result = await _userService.CreateToken(request.Username, request.Password, expiredTime);
- _logger.LogInformation(LoggingEventIds.CreateSucceeded, FormatLogMessage("Attemp to login succeeded.",
- Pair("Username", request.Username),
- Pair("Expire Time", expiredTime == null ? "default" : expiredTime.Value.ToString())));
+ DateTime? expireTime = null;
+ if (request.Expire != null)
+ expireTime = _clock.GetCurrentTime().AddDays(request.Expire.Value);
+
+ var result = await _userService.CreateToken(request.Username, request.Password, expireTime);
+
+ _logger.LogInformation(Log.Format(LogCreateSuccess,
+ ("Username", request.Username),
+ ("Expire At", expireTime?.ToString(CultureInfo.CurrentUICulture.DateTimeFormat) ?? "default")
+ ));
return Ok(new CreateTokenResponse
{
Token = result.Token,
@@ -76,75 +89,71 @@ namespace Timeline.Controllers }
catch (UserNotExistException e)
{
- var code = ErrorCodes.Create_UserNotExist;
- LogFailure("User does not exist.", code, e);
- return BadRequest(new CommonResponse(code, "Bad username or password."));
+ LogFailure(LogUserNotExist, e);
+ return BadRequest(new CommonResponse(ErrorCodes.Http.Token.Create.BadCredential,
+ _localizer["ErrorBadCredential"]));
}
catch (BadPasswordException e)
{
- var code = ErrorCodes.Create_BadPassword;
- LogFailure("Password is wrong.", code, e);
- return BadRequest(new CommonResponse(code, "Bad username or password."));
+ LogFailure(LogBadPassword, e);
+ return BadRequest(new CommonResponse(ErrorCodes.Http.Token.Create.BadCredential,
+ _localizer["ErrorBadCredential"]));
}
}
[HttpPost("verify")]
[AllowAnonymous]
- public async Task<IActionResult> Verify([FromBody] VerifyTokenRequest request)
+ public async Task<ActionResult<VerifyTokenResponse>> Verify([FromBody] VerifyTokenRequest request)
{
- void LogFailure(string reason, int code, Exception e = null, params KeyValuePair<string, object>[] otherProperties)
+ void LogFailure(string reason, Exception? e = null, params (string, object?)[] otherProperties)
{
- var properties = new KeyValuePair<string, object>[3 + otherProperties.Length];
- properties[0] = Pair("Reason", reason);
- properties[1] = Pair("Code", code);
- properties[2] = Pair("Token", request.Token);
- otherProperties.CopyTo(properties, 3);
- _logger.LogInformation(LoggingEventIds.VerifyFailed, e, FormatLogMessage("Token verification failed.", properties));
+ var properties = new (string, object?)[2 + otherProperties.Length];
+ properties[0] = ("Reason", reason);
+ properties[1] = ("Token", request.Token);
+ otherProperties.CopyTo(properties, 2);
+ _logger.LogInformation(e, Log.Format(LogVerifyFailure, properties));
}
try
{
var result = await _userService.VerifyToken(request.Token);
- _logger.LogInformation(LoggingEventIds.VerifySucceeded,
- FormatLogMessage("Token verification succeeded.",
- Pair("Username", result.Username), Pair("Token", request.Token)));
+ _logger.LogInformation(Log.Format(LogVerifySuccess,
+ ("Username", result.Username), ("Token", request.Token)));
return Ok(new VerifyTokenResponse
{
User = result
});
}
- catch (JwtTokenVerifyException e)
+ catch (JwtVerifyException e)
{
- if (e.ErrorCode == JwtTokenVerifyException.ErrorCodes.Expired)
+ if (e.ErrorCode == JwtVerifyException.ErrorCodes.Expired)
{
- const string message = "Token is expired.";
- var code = ErrorCodes.Verify_Expired;
var innerException = e.InnerException as SecurityTokenExpiredException;
- LogFailure(message, code, e, Pair("Expires", innerException.Expires));
- return BadRequest(new CommonResponse(code, message));
+ LogFailure(LogVerifyExpire, e, ("Expires", innerException?.Expires),
+ ("Current Time", _clock.GetCurrentTime()));
+ return BadRequest(new CommonResponse(
+ ErrorCodes.Http.Token.Verify.Expired, _localizer["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, _localizer["ErrorVerifyOldVersion"]));
}
else
{
- const string message = "Token is of bad format.";
- var code = ErrorCodes.Verify_BadToken;
- LogFailure(message, code, e);
- return BadRequest(new CommonResponse(code, message));
+ LogFailure(LogVerifyBadFormat, e);
+ return BadRequest(new CommonResponse(
+ ErrorCodes.Http.Token.Verify.BadFormat, _localizer["ErrorVerifyBadFormat"]));
}
}
catch (UserNotExistException e)
{
- const string message = "User does not exist. Administrator might have deleted this user.";
- var code = ErrorCodes.Verify_UserNotExist;
- LogFailure(message, code, e);
- return BadRequest(new CommonResponse(code, message));
- }
- catch (BadTokenVersionException e)
- {
- const string message = "Token has a old version.";
- var code = ErrorCodes.Verify_BadVersion;
- LogFailure(message, code, e);
- _logger.LogInformation(LoggingEventIds.VerifyFailed, e, "Attemp to verify a bad token because version is old. Code: {} Token: {}.", code, request.Token);
- return BadRequest(new CommonResponse(code, message));
+ LogFailure(LogVerifyUserNotExist, e);
+ return BadRequest(new CommonResponse(
+ ErrorCodes.Http.Token.Verify.UserNotExist, _localizer["ErrorVerifyUserNotExist"]));
}
}
}
diff --git a/Timeline/Controllers/UserAvatarController.cs b/Timeline/Controllers/UserAvatarController.cs index e77076ca..838a3928 100644 --- a/Timeline/Controllers/UserAvatarController.cs +++ b/Timeline/Controllers/UserAvatarController.cs @@ -1,68 +1,75 @@ using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
+using Microsoft.Extensions.Localization;
using Microsoft.Extensions.Logging;
using Microsoft.Net.Http.Headers;
using System;
using System.Linq;
using System.Threading.Tasks;
-using Timeline.Authenticate;
+using Timeline.Authentication;
using Timeline.Filters;
+using Timeline.Helpers;
using Timeline.Models.Http;
+using Timeline.Models.Validation;
using Timeline.Services;
-namespace Timeline.Controllers
+namespace Timeline
{
- [ApiController]
- public class UserAvatarController : Controller
+ public static partial class ErrorCodes
{
- public static class ErrorCodes
+ public static partial class Http
{
- public const int Get_UserNotExist = -1001;
-
- public const int Put_UserNotExist = -2001;
- public const int Put_Forbid = -2002;
- public const int Put_BadFormat_CantDecode = -2011;
- public const int Put_BadFormat_UnmatchedFormat = -2012;
- public const int Put_BadFormat_BadSize = -2013;
- public const int Put_Content_TooBig = -2021;
- public const int Put_Content_UnmatchedLength_Less = -2022;
- public const int Put_Content_UnmatchedLength_Bigger = -2023;
+ public static class UserAvatar // bbb = 003
+ {
+ public static class Get // cc = 01
+ {
+ public const int UserNotExist = 10030101;
+ }
- public const int Delete_UserNotExist = -3001;
- public const int Delete_Forbid = -3002;
+ 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 int From(AvatarDataException.ErrorReason error)
- {
- switch (error)
+ public static class Delete // cc = 03
{
- case AvatarDataException.ErrorReason.CantDecode:
- return Put_BadFormat_CantDecode;
- case AvatarDataException.ErrorReason.UnmatchedFormat:
- return Put_BadFormat_UnmatchedFormat;
- case AvatarDataException.ErrorReason.BadSize:
- return Put_BadFormat_BadSize;
- default:
- throw new Exception("Unknown AvatarDataException.ErrorReason value.");
+ public const int UserNotExist = 10030301;
+ public const int Forbid = 10030302;
}
}
}
+ }
+}
+namespace Timeline.Controllers
+{
+ [ApiController]
+ public class UserAvatarController : Controller
+ {
private readonly ILogger<UserAvatarController> _logger;
private readonly IUserAvatarService _service;
- public UserAvatarController(ILogger<UserAvatarController> logger, IUserAvatarService service)
+ private readonly IStringLocalizerFactory _localizerFactory;
+ private readonly IStringLocalizer<UserAvatarController> _localizer;
+
+ public UserAvatarController(ILogger<UserAvatarController> logger, IUserAvatarService service, IStringLocalizerFactory localizerFactory)
{
_logger = logger;
_service = service;
+ _localizerFactory = localizerFactory;
+ _localizer = new StringLocalizer<UserAvatarController>(localizerFactory);
}
[HttpGet("users/{username}/avatar")]
- [Authorize]
[ResponseCache(NoStore = false, Location = ResponseCacheLocation.None, Duration = 0)]
- public async Task<IActionResult> Get([FromRoute] string username)
+ public async Task<IActionResult> Get([FromRoute][Username] string username)
{
const string IfNonMatchHeaderKey = "If-None-Match";
@@ -74,11 +81,16 @@ namespace Timeline.Controllers if (Request.Headers.TryGetValue(IfNonMatchHeaderKey, out var value))
{
if (!EntityTagHeaderValue.TryParseStrictList(value, out var eTagList))
- return BadRequest(CommonResponse.BadIfNonMatch());
+ {
+ _logger.LogInformation(Log.Format(Resources.Controllers.UserAvatarController.LogGetBadIfNoneMatch,
+ ("Username", username), ("If-None-Match", value)));
+ return BadRequest(HeaderErrorResponse.BadIfNonMatch(_localizerFactory));
+ }
if (eTagList.FirstOrDefault(e => e.Equals(eTag)) != null)
{
Response.Headers.Add("ETag", eTagValue);
+ _logger.LogInformation(Log.Format(Resources.Controllers.UserAvatarController.LogGetReturnNotModify, ("Username", username)));
return StatusCode(StatusCodes.Status304NotModified);
}
}
@@ -86,12 +98,13 @@ namespace Timeline.Controllers var avatarInfo = await _service.GetAvatar(username);
var avatar = avatarInfo.Avatar;
+ _logger.LogInformation(Log.Format(Resources.Controllers.UserAvatarController.LogGetReturnData, ("Username", username)));
return File(avatar.Data, avatar.Type, new DateTimeOffset(avatarInfo.LastModified), eTag);
}
catch (UserNotExistException e)
{
- _logger.LogInformation(e, $"Attempt to get a avatar of a non-existent user failed. Username: {username} .");
- return NotFound(new CommonResponse(ErrorCodes.Get_UserNotExist, "User does not exist."));
+ _logger.LogInformation(e, Log.Format(Resources.Controllers.UserAvatarController.LogGetUserNotExist, ("Username", username)));
+ return NotFound(new CommonResponse(ErrorCodes.Http.UserAvatar.Get.UserNotExist, _localizer["ErrorGetUserNotExist"]));
}
}
@@ -99,18 +112,18 @@ namespace Timeline.Controllers [Authorize]
[RequireContentType, RequireContentLength]
[Consumes("image/png", "image/jpeg", "image/gif", "image/webp")]
- public async Task<IActionResult> Put(string username)
+ public async Task<IActionResult> Put([FromRoute][Username] string username)
{
- var contentLength = Request.ContentLength.Value;
+ var contentLength = Request.ContentLength!.Value;
if (contentLength > 1000 * 1000 * 10)
- return BadRequest(new CommonResponse(ErrorCodes.Put_Content_TooBig,
- "Content can't be bigger than 10MB."));
+ return BadRequest(ContentErrorResponse.TooBig(_localizerFactory, "10MB"));
- if (!User.IsAdmin() && User.Identity.Name != username)
+ if (!User.IsAdministrator() && User.Identity.Name != username)
{
- _logger.LogInformation($"Attempt to put a avatar of other user as a non-admin failed. Operator Username: {User.Identity.Name} ; Username To Put Avatar: {username} .");
+ _logger.LogInformation(Log.Format(Resources.Controllers.UserAvatarController.LogPutForbid,
+ ("Operator Username", User.Identity.Name), ("Username To Put Avatar", username)));
return StatusCode(StatusCodes.Status403Forbidden,
- new CommonResponse(ErrorCodes.Put_Forbid, "Normal user can't change other's avatar."));
+ new CommonResponse(ErrorCodes.Http.UserAvatar.Put.Forbid, _localizer["ErrorPutForbid"]));
}
try
@@ -119,13 +132,11 @@ namespace Timeline.Controllers var bytesRead = await Request.Body.ReadAsync(data);
if (bytesRead != contentLength)
- return BadRequest(new CommonResponse(ErrorCodes.Put_Content_UnmatchedLength_Less,
- $"Content length in header is {contentLength} but actual length is {bytesRead}."));
+ return BadRequest(ContentErrorResponse.UnmatchedLength_Smaller(_localizerFactory));
var extraByte = new byte[1];
if (await Request.Body.ReadAsync(extraByte) != 0)
- return BadRequest(new CommonResponse(ErrorCodes.Put_Content_UnmatchedLength_Bigger,
- $"Content length in header is {contentLength} but actual length is bigger than that."));
+ return BadRequest(ContentErrorResponse.UnmatchedLength_Bigger(_localizerFactory));
await _service.SetAvatar(username, new Avatar
{
@@ -133,43 +144,57 @@ namespace Timeline.Controllers Type = Request.ContentType
});
- _logger.LogInformation($"Succeed to put a avatar of a user. Username: {username} ; Mime Type: {Request.ContentType} .");
+ _logger.LogInformation(Log.Format(Resources.Controllers.UserAvatarController.LogPutSuccess,
+ ("Username", username), ("Mime Type", Request.ContentType)));
return Ok();
}
catch (UserNotExistException e)
{
- _logger.LogInformation(e, $"Attempt to put a avatar of a non-existent user failed. Username: {username} .");
- return BadRequest(new CommonResponse(ErrorCodes.Put_UserNotExist, "User does not exist."));
+ _logger.LogInformation(e, Log.Format(Resources.Controllers.UserAvatarController.LogPutUserNotExist, ("Username", username)));
+ return BadRequest(new CommonResponse(ErrorCodes.Http.UserAvatar.Put.UserNotExist, _localizer["ErrorPutUserNotExist"]));
}
- catch (AvatarDataException e)
+ catch (AvatarFormatException e)
{
- _logger.LogInformation(e, $"Attempt to put a avatar of a bad format failed. Username: {username} .");
- return BadRequest(new CommonResponse(ErrorCodes.From(e.Error), "Bad format."));
+ var (code, message) = e.Error switch
+ {
+ AvatarFormatException.ErrorReason.CantDecode =>
+ (ErrorCodes.Http.UserAvatar.Put.BadFormat_CantDecode, _localizer["ErrorPutBadFormatCantDecode"]),
+ AvatarFormatException.ErrorReason.UnmatchedFormat =>
+ (ErrorCodes.Http.UserAvatar.Put.BadFormat_UnmatchedFormat, _localizer["ErrorPutBadFormatUnmatchedFormat"]),
+ AvatarFormatException.ErrorReason.BadSize =>
+ (ErrorCodes.Http.UserAvatar.Put.BadFormat_BadSize, _localizer["ErrorPutBadFormatBadSize"]),
+ _ =>
+ throw new Exception(Resources.Controllers.UserAvatarController.ExceptionUnknownAvatarFormatError)
+ };
+
+ _logger.LogInformation(e, Log.Format(Resources.Controllers.UserAvatarController.LogPutUserBadFormat, ("Username", username)));
+ return BadRequest(new CommonResponse(code, message));
}
}
[HttpDelete("users/{username}/avatar")]
[Authorize]
- public async Task<IActionResult> Delete([FromRoute] string username)
+ public async Task<IActionResult> Delete([FromRoute][Username] string username)
{
- if (!User.IsAdmin() && User.Identity.Name != username)
+ if (!User.IsAdministrator() && User.Identity.Name != username)
{
- _logger.LogInformation($"Attempt to delete a avatar of other user as a non-admin failed. Operator Username: {User.Identity.Name} ; Username To Put Avatar: {username} .");
+ _logger.LogInformation(Log.Format(Resources.Controllers.UserAvatarController.LogPutUserBadFormat,
+ ("Operator Username", User.Identity.Name), ("Username To Delete Avatar", username)));
return StatusCode(StatusCodes.Status403Forbidden,
- new CommonResponse(ErrorCodes.Delete_Forbid, "Normal user can't delete other's avatar."));
+ new CommonResponse(ErrorCodes.Http.UserAvatar.Delete.Forbid, _localizer["ErrorDeleteForbid"]));
}
try
{
await _service.SetAvatar(username, null);
- _logger.LogInformation($"Succeed to delete a avatar of a user. Username: {username} .");
+ _logger.LogInformation(Log.Format(Resources.Controllers.UserAvatarController.LogDeleteSuccess, ("Username", username)));
return Ok();
}
catch (UserNotExistException e)
{
- _logger.LogInformation(e, $"Attempt to delete a avatar of a non-existent user failed. Username: {username} .");
- return BadRequest(new CommonResponse(ErrorCodes.Delete_UserNotExist, "User does not exist."));
+ _logger.LogInformation(e, Log.Format(Resources.Controllers.UserAvatarController.LogDeleteNotExist, ("Username", username)));
+ return BadRequest(new CommonResponse(ErrorCodes.Http.UserAvatar.Delete.UserNotExist, _localizer["ErrorDeleteUserNotExist"]));
}
}
}
diff --git a/Timeline/Controllers/UserController.cs b/Timeline/Controllers/UserController.cs index bd13f0a3..1771dc85 100644 --- a/Timeline/Controllers/UserController.cs +++ b/Timeline/Controllers/UserController.cs @@ -1,40 +1,70 @@ using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
+using Microsoft.Extensions.Localization;
using Microsoft.Extensions.Logging;
-using System;
using System.Threading.Tasks;
-using Timeline.Authenticate;
+using Timeline.Authentication;
+using Timeline.Helpers;
using Timeline.Models;
using Timeline.Models.Http;
+using Timeline.Models.Validation;
using Timeline.Services;
-using static Timeline.Helpers.MyLogHelper;
+using static Timeline.Resources.Controllers.UserController;
-namespace Timeline.Controllers
+namespace Timeline
{
- [ApiController]
- public class UserController : Controller
+ public static partial class ErrorCodes
{
- public static class ErrorCodes
+ public static partial class Http
{
- public const int Get_NotExist = -1001;
+ public static class User // bbb = 002
+ {
+ public static class Get // cc = 01
+ {
+ public const int NotExist = 10020101; // dd = 01
+ }
- public const int Put_BadUsername = -2001;
+ public static class Patch // cc = 03
+ {
+ public const int NotExist = 10020301; // dd = 01
+ }
- public const int Patch_NotExist = -3001;
+ 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 const int ChangeUsername_NotExist = -4001;
- public const int ChangeUsername_AlreadyExist = -4002;
+ public static class ChangePassword // cc = 12
+ {
+ public const int BadOldPassword = 10021201; // dd = 01
+ }
+ }
- public const int ChangePassword_BadOldPassword = -5001;
+ }
}
+ }
+}
+
+namespace Timeline.Controllers
+{
+ [ApiController]
+ public class UserController : Controller
+ {
private readonly ILogger<UserController> _logger;
private readonly IUserService _userService;
+ private readonly IStringLocalizerFactory _localizerFactory;
+ private readonly IStringLocalizer _localizer;
- public UserController(ILogger<UserController> logger, IUserService userService)
+ public UserController(ILogger<UserController> logger, IUserService userService, IStringLocalizerFactory localizerFactory)
{
_logger = logger;
_userService = userService;
+ _localizerFactory = localizerFactory;
+ _localizer = localizerFactory.Create(GetType());
}
[HttpGet("users"), AdminAuthorize]
@@ -44,44 +74,36 @@ namespace Timeline.Controllers }
[HttpGet("users/{username}"), AdminAuthorize]
- public async Task<IActionResult> Get([FromRoute] string username)
+ public async Task<ActionResult<UserInfo>> Get([FromRoute][Username] string username)
{
var user = await _userService.GetUser(username);
if (user == null)
{
- _logger.LogInformation(FormatLogMessage("Attempt to get a non-existent user.", Pair("Username", username)));
- return NotFound(new CommonResponse(ErrorCodes.Get_NotExist, "The user does not exist."));
+ _logger.LogInformation(Log.Format(LogGetUserNotExist, ("Username", username)));
+ return NotFound(new CommonResponse(ErrorCodes.Http.User.Get.NotExist, _localizer["ErrorGetUserNotExist"]));
}
return Ok(user);
}
[HttpPut("users/{username}"), AdminAuthorize]
- public async Task<IActionResult> Put([FromBody] UserPutRequest request, [FromRoute] string username)
+ public async Task<ActionResult<CommonPutResponse>> Put([FromBody] UserPutRequest request, [FromRoute][Username] string username)
{
- try
- {
- var result = await _userService.PutUser(username, request.Password, request.Administrator.Value);
- switch (result)
- {
- case PutResult.Created:
- _logger.LogInformation(FormatLogMessage("A user is created.", Pair("Username", username)));
- return CreatedAtAction("Get", new { username }, CommonPutResponse.Created);
- case PutResult.Modified:
- _logger.LogInformation(FormatLogMessage("A user is modified.", Pair("Username", username)));
- return Ok(CommonPutResponse.Modified);
- default:
- throw new Exception("Unreachable code.");
- }
- }
- catch (UsernameBadFormatException e)
+ var result = await _userService.PutUser(username, request.Password, request.Administrator!.Value);
+ switch (result)
{
- _logger.LogInformation(e, FormatLogMessage("Attempt to create a user with bad username failed.", Pair("Username", username)));
- return BadRequest(new CommonResponse(ErrorCodes.Put_BadUsername, "Username is of bad format."));
+ case PutResult.Create:
+ _logger.LogInformation(Log.Format(LogPutCreate, ("Username", username)));
+ return CreatedAtAction("Get", new { username }, CommonPutResponse.Create(_localizerFactory));
+ case PutResult.Modify:
+ _logger.LogInformation(Log.Format(LogPutModify, ("Username", username)));
+ return Ok(CommonPutResponse.Modify(_localizerFactory));
+ default:
+ throw new InvalidBranchException();
}
}
[HttpPatch("users/{username}"), AdminAuthorize]
- public async Task<IActionResult> Patch([FromBody] UserPatchRequest request, [FromRoute] string username)
+ public async Task<ActionResult> Patch([FromBody] UserPatchRequest request, [FromRoute][Username] string username)
{
try
{
@@ -90,66 +112,67 @@ namespace Timeline.Controllers }
catch (UserNotExistException e)
{
- _logger.LogInformation(e, FormatLogMessage("Attempt to patch a non-existent user.", Pair("Username", username)));
- return NotFound(new CommonResponse(ErrorCodes.Patch_NotExist, "The user does not exist."));
+ _logger.LogInformation(e, Log.Format(LogPatchUserNotExist, ("Username", username)));
+ return NotFound(new CommonResponse(ErrorCodes.Http.User.Patch.NotExist, _localizer["ErrorPatchUserNotExist"]));
}
}
[HttpDelete("users/{username}"), AdminAuthorize]
- public async Task<IActionResult> Delete([FromRoute] string username)
+ public async Task<ActionResult<CommonDeleteResponse>> Delete([FromRoute][Username] string username)
{
try
{
await _userService.DeleteUser(username);
- _logger.LogInformation(FormatLogMessage("A user is deleted.", Pair("Username", username)));
- return Ok(CommonDeleteResponse.Deleted);
+ _logger.LogInformation(Log.Format(LogDeleteDelete, ("Username", username)));
+ return Ok(CommonDeleteResponse.Delete(_localizerFactory));
}
catch (UserNotExistException e)
{
- _logger.LogInformation(e, FormatLogMessage("Attempt to delete a non-existent user.", Pair("Username", username)));
- return Ok(CommonDeleteResponse.NotExists);
+ _logger.LogInformation(e, Log.Format(LogDeleteNotExist, ("Username", username)));
+ return Ok(CommonDeleteResponse.NotExist(_localizerFactory));
}
}
[HttpPost("userop/changeusername"), AdminAuthorize]
- public async Task<IActionResult> ChangeUsername([FromBody] ChangeUsernameRequest request)
+ public async Task<ActionResult> ChangeUsername([FromBody] ChangeUsernameRequest request)
{
try
{
await _userService.ChangeUsername(request.OldUsername, request.NewUsername);
- _logger.LogInformation(FormatLogMessage("A user changed username.",
- Pair("Old Username", request.OldUsername), Pair("New Username", request.NewUsername)));
+ _logger.LogInformation(Log.Format(LogChangeUsernameSuccess,
+ ("Old Username", request.OldUsername), ("New Username", request.NewUsername)));
return Ok();
}
catch (UserNotExistException e)
{
- _logger.LogInformation(e, FormatLogMessage("Attempt to change a non-existent user's username failed.",
- Pair("Old Username", request.OldUsername), Pair("New Username", request.NewUsername)));
- return BadRequest(new CommonResponse(ErrorCodes.ChangeUsername_NotExist, $"The user {request.OldUsername} does not exist."));
+ _logger.LogInformation(e, Log.Format(LogChangeUsernameNotExist,
+ ("Old Username", request.OldUsername), ("New Username", request.NewUsername)));
+ return BadRequest(new CommonResponse(ErrorCodes.Http.User.Op.ChangeUsername.NotExist, _localizer["ErrorChangeUsernameNotExist", request.OldUsername]));
}
- catch (UserAlreadyExistException e)
+ catch (UsernameConfictException e)
{
- _logger.LogInformation(e, FormatLogMessage("Attempt to change a user's username to a existent one failed.",
- Pair("Old Username", request.OldUsername), Pair("New Username", request.NewUsername)));
- return BadRequest(new CommonResponse(ErrorCodes.ChangeUsername_AlreadyExist, $"The user {request.NewUsername} already exists."));
+ _logger.LogInformation(e, Log.Format(LogChangeUsernameAlreadyExist,
+ ("Old Username", request.OldUsername), ("New Username", request.NewUsername)));
+ return BadRequest(new CommonResponse(ErrorCodes.Http.User.Op.ChangeUsername.AlreadyExist, _localizer["ErrorChangeUsernameAlreadyExist"]));
}
// there is no need to catch bad format exception because it is already checked in model validation.
}
[HttpPost("userop/changepassword"), Authorize]
- public async Task<IActionResult> ChangePassword([FromBody] ChangePasswordRequest request)
+ public async Task<ActionResult> ChangePassword([FromBody] ChangePasswordRequest request)
{
try
{
- await _userService.ChangePassword(User.Identity.Name, request.OldPassword, request.NewPassword);
- _logger.LogInformation(FormatLogMessage("A user changed password.", Pair("Username", User.Identity.Name)));
+ await _userService.ChangePassword(User.Identity.Name!, request.OldPassword, request.NewPassword);
+ _logger.LogInformation(Log.Format(LogChangePasswordSuccess, ("Username", User.Identity.Name)));
return Ok();
}
catch (BadPasswordException e)
{
- _logger.LogInformation(e, FormatLogMessage("A user attempt to change password but old password is wrong.",
- Pair("Username", User.Identity.Name), Pair("Old Password", request.OldPassword)));
- return BadRequest(new CommonResponse(ErrorCodes.ChangePassword_BadOldPassword, "Old password is wrong."));
+ _logger.LogInformation(e, Log.Format(LogChangePasswordBadPassword,
+ ("Username", User.Identity.Name), ("Old Password", request.OldPassword)));
+ return BadRequest(new CommonResponse(ErrorCodes.Http.User.Op.ChangePassword.BadOldPassword,
+ _localizer["ErrorChangePasswordBadPassword"]));
}
// 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 5e1183c1..00000000 --- a/Timeline/Controllers/UserDetailController.cs +++ /dev/null @@ -1,96 +0,0 @@ -using Microsoft.AspNetCore.Authorization;
-using Microsoft.AspNetCore.Http;
-using Microsoft.AspNetCore.Mvc;
-using Microsoft.Extensions.Logging;
-using System.Threading.Tasks;
-using Timeline.Authenticate;
-using Timeline.Models;
-using Timeline.Models.Http;
-using Timeline.Services;
-
-namespace Timeline.Controllers
-{
- [Route("users/{username}")]
- [ProducesErrorResponseType(typeof(CommonResponse))]
- [ApiController]
- public class UserDetailController : Controller
- {
- public static class ErrorCodes
- {
- public const int Get_UserNotExist = -1001;
-
- public const int Patch_Forbid = -2001;
- public const int Patch_UserNotExist = -2002;
-
- public const int GetNickname_UserNotExist = -3001;
- }
-
- private readonly ILogger<UserDetailController> _logger;
- private readonly IUserDetailService _service;
-
- public UserDetailController(ILogger<UserDetailController> logger, IUserDetailService service)
- {
- _logger = logger;
- _service = service;
- }
-
- [HttpGet("nickname")]
- [UserAuthorize]
- [ProducesResponseType(StatusCodes.Status200OK, Type = typeof(UserDetail))]
- [ProducesResponseType(StatusCodes.Status404NotFound)]
- public async Task<IActionResult> GetNickname([FromRoute] string username)
- {
- try
- {
- var nickname = await _service.GetUserNickname(username);
- return Ok(new UserDetail
- {
- Nickname = nickname
- });
- }
- catch (UserNotExistException)
- {
- return NotFound(new CommonResponse(ErrorCodes.GetNickname_UserNotExist, "The user does not exist."));
- }
- }
-
- [HttpGet("details")]
- [UserAuthorize]
- [ProducesResponseType(StatusCodes.Status200OK, Type = typeof(UserDetail))]
- [ProducesResponseType(StatusCodes.Status404NotFound)]
- public async Task<IActionResult> Get([FromRoute] string username)
- {
- try
- {
- var detail = await _service.GetUserDetail(username);
- return Ok(detail);
- }
- catch (UserNotExistException)
- {
- return NotFound(new CommonResponse(ErrorCodes.Get_UserNotExist, "The user does not exist."));
- }
- }
-
- [HttpPatch("details")]
- [Authorize]
- [ProducesResponseType(StatusCodes.Status200OK, Type = typeof(void))]
- [ProducesResponseType(StatusCodes.Status400BadRequest)]
- [ProducesResponseType(StatusCodes.Status403Forbidden)]
- [ProducesResponseType(StatusCodes.Status404NotFound)]
- public async Task<IActionResult> Patch([FromRoute] string username, [FromBody] UserDetail detail)
- {
- if (!User.IsAdmin() && User.Identity.Name != username)
- return StatusCode(StatusCodes.Status403Forbidden, new CommonResponse(ErrorCodes.Patch_Forbid, "You can't change other's details unless you are admin."));
-
- try
- {
- await _service.UpdateUserDetail(username, detail);
- return Ok();
- }
- catch (UserNotExistException)
- {
- return NotFound(new CommonResponse(ErrorCodes.Patch_UserNotExist, "The user does not exist."));
- }
- }
- }
-}
diff --git a/Timeline/Entities/DatabaseContext.cs b/Timeline/Entities/DatabaseContext.cs index d9815660..e1b98e7d 100644 --- a/Timeline/Entities/DatabaseContext.cs +++ b/Timeline/Entities/DatabaseContext.cs @@ -1,38 +1,7 @@ using Microsoft.EntityFrameworkCore;
-using System.ComponentModel.DataAnnotations;
-using System.ComponentModel.DataAnnotations.Schema;
namespace Timeline.Entities
{
- public static class UserRoles
- {
- public const string Admin = "admin";
- public const string User = "user";
- }
-
- [Table("users")]
- public class User
- {
- [Column("id"), Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)]
- public long Id { get; set; }
-
- [Column("name"), MaxLength(26), Required]
- public string Name { get; set; }
-
- [Column("password"), Required]
- public string EncryptedPassword { get; set; }
-
- [Column("roles"), Required]
- public string RoleString { get; set; }
-
- [Column("version"), Required]
- public long Version { get; set; }
-
- public UserAvatar Avatar { get; set; }
-
- public UserDetailEntity Detail { get; set; }
- }
-
public class DatabaseContext : DbContext
{
public DatabaseContext(DbContextOptions<DatabaseContext> options)
@@ -41,14 +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();
}
- public DbSet<User> Users { get; set; }
- public DbSet<UserAvatar> UserAvatars { get; set; }
- public DbSet<UserDetailEntity> UserDetails { get; set; }
+ public DbSet<User> Users { get; set; } = default!;
+ public DbSet<UserAvatar> UserAvatars { get; set; } = default!;
}
}
diff --git a/Timeline/Entities/User.cs b/Timeline/Entities/User.cs new file mode 100644 index 00000000..6e8e4967 --- /dev/null +++ b/Timeline/Entities/User.cs @@ -0,0 +1,32 @@ +using System.ComponentModel.DataAnnotations;
+using System.ComponentModel.DataAnnotations.Schema;
+
+namespace Timeline.Entities
+{
+ public static class UserRoles
+ {
+ public const string Admin = "admin";
+ public const string User = "user";
+ }
+
+ [Table("users")]
+ public class User
+ {
+ [Column("id"), Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)]
+ public long Id { get; set; }
+
+ [Column("name"), MaxLength(26), Required]
+ public string Name { get; set; } = default!;
+
+ [Column("password"), Required]
+ public string EncryptedPassword { get; set; } = default!;
+
+ [Column("roles"), Required]
+ public string RoleString { get; set; } = default!;
+
+ [Column("version"), Required]
+ public long Version { get; set; }
+
+ public UserAvatar? Avatar { get; set; }
+ }
+}
diff --git a/Timeline/Entities/UserAvatar.cs b/Timeline/Entities/UserAvatar.cs index d549aea5..a5b18b94 100644 --- a/Timeline/Entities/UserAvatar.cs +++ b/Timeline/Entities/UserAvatar.cs @@ -11,29 +11,18 @@ namespace Timeline.Entities public long Id { get; set; }
[Column("data")]
- public byte[] Data { get; set; }
+ [System.Diagnostics.CodeAnalysis.SuppressMessage("Performance", "CA1819:Properties should not return arrays", Justification = "This is data base entity.")]
+ public byte[]? Data { get; set; }
[Column("type")]
- public string Type { get; set; }
+ public string? Type { get; set; }
[Column("etag"), MaxLength(30)]
- public string ETag { get; set; }
+ public string? ETag { get; set; }
[Column("last_modified"), Required]
public DateTime LastModified { get; set; }
public long UserId { get; set; }
-
- public static UserAvatar Create(DateTime lastModified)
- {
- return new UserAvatar
- {
- Id = 0,
- Data = null,
- Type = null,
- ETag = null,
- LastModified = lastModified
- };
- }
}
}
diff --git a/Timeline/Entities/UserDetail.cs b/Timeline/Entities/UserDetail.cs deleted file mode 100644 index bc14dbe6..00000000 --- a/Timeline/Entities/UserDetail.cs +++ /dev/null @@ -1,29 +0,0 @@ -using System.ComponentModel.DataAnnotations;
-using System.ComponentModel.DataAnnotations.Schema;
-
-namespace Timeline.Entities
-{
- [Table("user_details")]
- public class UserDetailEntity
- {
- [Column("id"), Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)]
- public long Id { get; set; }
-
- [Column("nickname"), MaxLength(15)]
- public string Nickname { get; set; }
-
- [Column("qq"), MaxLength(15)]
- public string QQ { get; set; }
-
- [Column("email"), MaxLength(50)]
- public string Email { get; set; }
-
- [Column("phone_number"), MaxLength(15)]
- public string PhoneNumber { get; set; }
-
- [Column("description")]
- public string Description { get; set; }
-
- public long UserId { get; set; }
- }
-}
diff --git a/Timeline/ErrorCodes.cs b/Timeline/ErrorCodes.cs new file mode 100644 index 00000000..5e7f003a --- /dev/null +++ b/Timeline/ErrorCodes.cs @@ -0,0 +1,36 @@ +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 = 01
+ {
+ public const int Missing_ContentType = 10000101; // dd = 01
+ public const int Missing_ContentLength = 10000102; // dd = 02
+ public const int Zero_ContentLength = 10000103; // dd = 03
+ public const int BadFormat_IfNonMatch = 10000104; // dd = 04
+ }
+
+ public static class Content // cc = 02
+ {
+ public const int TooBig = 1000201;
+ public const int UnmatchedLength_Smaller = 10030202;
+ public const int UnmatchedLength_Bigger = 10030203;
+ }
+ }
+ }
+
+ }
+}
diff --git a/Timeline/Filters/ContentHeaderAttributes.cs b/Timeline/Filters/ContentHeaderAttributes.cs index 14685a01..e3d4eeb2 100644 --- a/Timeline/Filters/ContentHeaderAttributes.cs +++ b/Timeline/Filters/ContentHeaderAttributes.cs @@ -1,16 +1,20 @@ using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Localization;
using Timeline.Models.Http;
namespace Timeline.Filters
{
public class RequireContentTypeAttribute : ActionFilterAttribute
{
+ [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(CommonResponse.MissingContentType());
+ var localizerFactory = context.HttpContext.RequestServices.GetRequiredService<IStringLocalizerFactory>();
+ context.Result = new BadRequestObjectResult(HeaderErrorResponse.MissingContentType(localizerFactory));
}
}
}
@@ -30,17 +34,20 @@ 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(CommonResponse.MissingContentLength());
+ var localizerFactory = context.HttpContext.RequestServices.GetRequiredService<IStringLocalizerFactory>();
+ context.Result = new BadRequestObjectResult(HeaderErrorResponse.MissingContentLength(localizerFactory));
return;
}
if (RequireNonZero && context.HttpContext.Request.ContentLength.Value == 0)
{
- context.Result = new BadRequestObjectResult(CommonResponse.ZeroContentLength());
+ var localizerFactory = context.HttpContext.RequestServices.GetRequiredService<IStringLocalizerFactory>();
+ context.Result = new BadRequestObjectResult(HeaderErrorResponse.ZeroContentLength(localizerFactory));
return;
}
}
diff --git a/Timeline/GlobalSuppressions.cs b/Timeline/GlobalSuppressions.cs new file mode 100644 index 00000000..e720e5b7 --- /dev/null +++ b/Timeline/GlobalSuppressions.cs @@ -0,0 +1,13 @@ +// This file is used by Code Analysis to maintain SuppressMessage
+// attributes that are applied to this project.
+// Project-level suppressions either have no target or are given
+// a specific target and scoped to a namespace, type, member, etc.
+
+[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Reliability", "CA2007:Consider calling ConfigureAwait on the awaited task", Justification = "This is not a UI application.")]
+[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("Performance", "CA1824:Mark assemblies with NeutralResourcesLanguageAttribute", Justification = "Applying this breaks the function of resources in my app.")]
diff --git a/Timeline/Helpers/InvalidModelResponseFactory.cs b/Timeline/Helpers/InvalidModelResponseFactory.cs index c792e845..643c99ac 100644 --- a/Timeline/Helpers/InvalidModelResponseFactory.cs +++ b/Timeline/Helpers/InvalidModelResponseFactory.cs @@ -6,6 +6,7 @@ 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;
diff --git a/Timeline/Helpers/LanguageHelper.cs b/Timeline/Helpers/LanguageHelper.cs new file mode 100644 index 00000000..b0156b8b --- /dev/null +++ b/Timeline/Helpers/LanguageHelper.cs @@ -0,0 +1,12 @@ +using System.Linq;
+
+namespace Timeline.Helpers
+{
+ public static class LanguageHelper
+ {
+ public static bool AreSame(this bool firstBool, params bool[] otherBools)
+ {
+ return otherBools.All(b => b == firstBool);
+ }
+ }
+}
diff --git a/Timeline/Helpers/Log.cs b/Timeline/Helpers/Log.cs index 123e8a8e..68c975fa 100644 --- a/Timeline/Helpers/Log.cs +++ b/Timeline/Helpers/Log.cs @@ -3,20 +3,19 @@ using System.Text; namespace Timeline.Helpers
{
- public static class MyLogHelper
+ public static class Log
{
- public static KeyValuePair<string, object> Pair(string key, object value) => new KeyValuePair<string, object>(key, value);
-
- public static string FormatLogMessage(string summary, params KeyValuePair<string, object>[] properties)
+ public static string Format(string summary, params (string, object?)[] properties)
{
var builder = new StringBuilder();
builder.Append(summary);
foreach (var property in properties)
{
+ var (key, value) = property;
builder.AppendLine();
- builder.Append(property.Key);
+ builder.Append(key);
builder.Append(" : ");
- builder.Append(property.Value);
+ builder.Append(value);
}
return builder.ToString();
}
diff --git a/Timeline/Helpers/StringLocalizerFactoryExtensions.cs b/Timeline/Helpers/StringLocalizerFactoryExtensions.cs new file mode 100644 index 00000000..c2252b2c --- /dev/null +++ b/Timeline/Helpers/StringLocalizerFactoryExtensions.cs @@ -0,0 +1,19 @@ +
+using Microsoft.Extensions.Localization;
+using System.Reflection;
+
+namespace Timeline.Helpers
+{
+ internal static class StringLocalizerFactoryExtensions
+ {
+ internal static IStringLocalizer Create(this IStringLocalizerFactory factory, string basename)
+ {
+ return factory.Create(basename, new AssemblyName(typeof(StringLocalizerFactoryExtensions).Assembly.FullName!).Name);
+ }
+
+ internal static StringLocalizer<T> Create<T>(this IStringLocalizerFactory factory)
+ {
+ return new StringLocalizer<T>(factory);
+ }
+ }
+}
\ No newline at end of file diff --git a/Timeline/InvalidBranchException.cs b/Timeline/InvalidBranchException.cs new file mode 100644 index 00000000..32937c5d --- /dev/null +++ b/Timeline/InvalidBranchException.cs @@ -0,0 +1,16 @@ +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/20190822072156_AddUserDetail.Designer.cs b/Timeline/Migrations/20190822072156_AddUserDetail.Designer.cs deleted file mode 100644 index 2bbcf673..00000000 --- a/Timeline/Migrations/20190822072156_AddUserDetail.Designer.cs +++ /dev/null @@ -1,136 +0,0 @@ -// <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
-{
- [DbContext(typeof(DatabaseContext))]
- [Migration("20190822072156_AddUserDetail")]
- partial class AddUserDetail
- {
- protected override void BuildTargetModel(ModelBuilder modelBuilder)
- {
-#pragma warning disable 612, 618
- modelBuilder
- .HasAnnotation("ProductVersion", "2.2.6-servicing-10079")
- .HasAnnotation("Relational:MaxIdentifierLength", 64);
-
- modelBuilder.Entity("Timeline.Entities.User", b =>
- {
- b.Property<long>("Id")
- .ValueGeneratedOnAdd()
- .HasColumnName("id");
-
- b.Property<string>("EncryptedPassword")
- .IsRequired()
- .HasColumnName("password");
-
- b.Property<string>("Name")
- .IsRequired()
- .HasColumnName("name")
- .HasMaxLength(26);
-
- b.Property<string>("RoleString")
- .IsRequired()
- .HasColumnName("roles");
-
- b.Property<long>("Version")
- .ValueGeneratedOnAdd()
- .HasColumnName("version")
- .HasDefaultValue(0L);
-
- b.HasKey("Id");
-
- b.HasIndex("Name")
- .IsUnique();
-
- b.ToTable("users");
- });
-
- modelBuilder.Entity("Timeline.Entities.UserAvatar", b =>
- {
- b.Property<long>("Id")
- .ValueGeneratedOnAdd()
- .HasColumnName("id");
-
- b.Property<byte[]>("Data")
- .HasColumnName("data");
-
- b.Property<string>("ETag")
- .HasColumnName("etag")
- .HasMaxLength(30);
-
- b.Property<DateTime>("LastModified")
- .HasColumnName("last_modified");
-
- b.Property<string>("Type")
- .HasColumnName("type");
-
- b.Property<long>("UserId");
-
- b.HasKey("Id");
-
- b.HasIndex("UserId")
- .IsUnique();
-
- b.ToTable("user_avatars");
- });
-
- modelBuilder.Entity("Timeline.Entities.UserDetailEntity", b =>
- {
- b.Property<long>("Id")
- .ValueGeneratedOnAdd()
- .HasColumnName("id");
-
- b.Property<string>("Description")
- .HasColumnName("description");
-
- b.Property<string>("EMail")
- .HasColumnName("email")
- .HasMaxLength(50);
-
- b.Property<string>("Nickname")
- .HasColumnName("nickname")
- .HasMaxLength(15);
-
- b.Property<string>("PhoneNumber")
- .HasColumnName("phone_number")
- .HasMaxLength(15);
-
- b.Property<string>("QQ")
- .HasColumnName("qq")
- .HasMaxLength(15);
-
- b.Property<long>("UserId");
-
- b.HasKey("Id");
-
- b.HasIndex("UserId")
- .IsUnique();
-
- b.ToTable("user_details");
- });
-
- modelBuilder.Entity("Timeline.Entities.UserAvatar", b =>
- {
- b.HasOne("Timeline.Entities.User")
- .WithOne("Avatar")
- .HasForeignKey("Timeline.Entities.UserAvatar", "UserId")
- .OnDelete(DeleteBehavior.Cascade);
- });
-
- modelBuilder.Entity("Timeline.Entities.UserDetailEntity", b =>
- {
- b.HasOne("Timeline.Entities.User")
- .WithOne("Detail")
- .HasForeignKey("Timeline.Entities.UserDetailEntity", "UserId")
- .OnDelete(DeleteBehavior.Cascade);
- });
-#pragma warning restore 612, 618
- }
- }
-}
diff --git a/Timeline/Migrations/20190822072156_AddUserDetail.cs b/Timeline/Migrations/20190822072156_AddUserDetail.cs deleted file mode 100644 index 4aa6446b..00000000 --- a/Timeline/Migrations/20190822072156_AddUserDetail.cs +++ /dev/null @@ -1,47 +0,0 @@ -using Microsoft.EntityFrameworkCore.Metadata;
-using Microsoft.EntityFrameworkCore.Migrations;
-
-namespace Timeline.Migrations
-{
- public partial class AddUserDetail : Migration
- {
- protected override void Up(MigrationBuilder migrationBuilder)
- {
- migrationBuilder.CreateTable(
- name: "user_details",
- columns: table => new
- {
- id = table.Column<long>(nullable: false)
- .Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn),
- nickname = table.Column<string>(maxLength: 15, nullable: true),
- qq = table.Column<string>(maxLength: 15, nullable: true),
- email = table.Column<string>(maxLength: 50, nullable: true),
- phone_number = table.Column<string>(maxLength: 15, nullable: true),
- description = table.Column<string>(nullable: true),
- UserId = table.Column<long>(nullable: false)
- },
- constraints: table =>
- {
- table.PrimaryKey("PK_user_details", x => x.id);
- table.ForeignKey(
- name: "FK_user_details_users_UserId",
- column: x => x.UserId,
- principalTable: "users",
- principalColumn: "id",
- onDelete: ReferentialAction.Cascade);
- });
-
- migrationBuilder.CreateIndex(
- name: "IX_user_details_UserId",
- table: "user_details",
- column: "UserId",
- unique: true);
- }
-
- protected override void Down(MigrationBuilder migrationBuilder)
- {
- migrationBuilder.DropTable(
- name: "user_details");
- }
- }
-}
diff --git a/Timeline/Models/Http/Common.cs b/Timeline/Models/Http/Common.cs index a72f187c..39ddddd9 100644 --- a/Timeline/Models/Http/Common.cs +++ b/Timeline/Models/Http/Common.cs @@ -1,76 +1,164 @@ +using Microsoft.Extensions.Localization;
+using Timeline.Helpers;
+
namespace Timeline.Models.Http
{
public class CommonResponse
{
- public static class ErrorCodes
+ internal static CommonResponse InvalidModel(string message)
+ {
+ return new CommonResponse(ErrorCodes.Http.Common.InvalidModel, message);
+ }
+
+ public CommonResponse()
{
- /// <summary>
- /// Used when the model is invaid.
- /// For example a required field is null.
- /// </summary>
- public const int InvalidModel = -100;
- public const int Header_Missing_ContentType = -111;
- public const int Header_Missing_ContentLength = -112;
- public const int Header_Zero_ContentLength = -113;
- public const int Header_BadFormat_IfNonMatch = -114;
}
- public static CommonResponse InvalidModel(string message)
+ public CommonResponse(int code, string message)
{
- return new CommonResponse(ErrorCodes.InvalidModel, message);
+ Code = code;
+ Message = message;
}
- public static CommonResponse MissingContentType()
+ public int Code { get; set; }
+ public string? Message { get; set; }
+ }
+
+ internal static class HeaderErrorResponse
+ {
+ internal static CommonResponse MissingContentType(IStringLocalizerFactory localizerFactory)
{
- return new CommonResponse(ErrorCodes.Header_Missing_ContentType, "Header Content-Type is required.");
+ var localizer = localizerFactory.Create("Models.Http.Common");
+ return new CommonResponse(ErrorCodes.Http.Common.Header.Missing_ContentType, localizer["HeaderMissingContentType"]);
}
- public static CommonResponse MissingContentLength()
+ internal static CommonResponse MissingContentLength(IStringLocalizerFactory localizerFactory)
{
- return new CommonResponse(ErrorCodes.Header_Missing_ContentLength, "Header Content-Length is missing or of bad format.");
+ var localizer = localizerFactory.Create("Models.Http.Common");
+ return new CommonResponse(ErrorCodes.Http.Common.Header.Missing_ContentLength, localizer["HeaderMissingContentLength"]);
}
- public static CommonResponse ZeroContentLength()
+ internal static CommonResponse ZeroContentLength(IStringLocalizerFactory localizerFactory)
{
- return new CommonResponse(ErrorCodes.Header_Zero_ContentLength, "Header Content-Length must not be 0.");
+ var localizer = localizerFactory.Create("Models.Http.Common");
+ return new CommonResponse(ErrorCodes.Http.Common.Header.Zero_ContentLength, localizer["HeaderZeroContentLength"]);
}
- public static CommonResponse BadIfNonMatch()
+ internal static CommonResponse BadIfNonMatch(IStringLocalizerFactory localizerFactory)
{
- return new CommonResponse(ErrorCodes.Header_BadFormat_IfNonMatch, "Header If-Non-Match is of bad format.");
+ var localizer = localizerFactory.Create("Models.Http.Common");
+ return new CommonResponse(ErrorCodes.Http.Common.Header.BadFormat_IfNonMatch, localizer["HeaderBadIfNonMatch"]);
}
+ }
- public CommonResponse()
+ internal static class ContentErrorResponse
+ {
+ internal static CommonResponse TooBig(IStringLocalizerFactory localizerFactory, string maxLength)
+ {
+ var localizer = localizerFactory.Create("Models.Http.Common");
+ return new CommonResponse(ErrorCodes.Http.Common.Content.TooBig, localizer["ContentTooBig", maxLength]);
+ }
+
+ internal static CommonResponse UnmatchedLength_Smaller(IStringLocalizerFactory localizerFactory)
+ {
+ var localizer = localizerFactory.Create("Models.Http.Common");
+ return new CommonResponse(ErrorCodes.Http.Common.Content.UnmatchedLength_Smaller, localizer["ContentUnmatchedLengthSmaller"]);
+ }
+ internal static CommonResponse UnmatchedLength_Bigger(IStringLocalizerFactory localizerFactory)
+ {
+ var localizer = localizerFactory.Create("Models.Http.Common");
+ return new CommonResponse(ErrorCodes.Http.Common.Content.UnmatchedLength_Bigger, localizer["ContentUnmatchedLengthBigger"]);
+ }
+ }
+
+
+ public class CommonDataResponse<T> : CommonResponse
+ {
+ public CommonDataResponse()
{
}
- public CommonResponse(int code, string message)
+ public CommonDataResponse(int code, string message, T data)
+ : base(code, message)
{
- Code = code;
- Message = message;
+ Data = data;
}
- public int Code { get; set; }
- public string Message { get; set; }
+ public T Data { get; set; } = default!;
}
- public static class CommonPutResponse
+ public class CommonPutResponse : CommonDataResponse<CommonPutResponse.ResponseData>
{
- public const int CreatedCode = 0;
- public const int ModifiedCode = 1;
+ public class ResponseData
+ {
+ public ResponseData(bool create)
+ {
+ Create = create;
+ }
- public static CommonResponse Created { get; } = new CommonResponse(CreatedCode, "A new item is created.");
- public static CommonResponse Modified { get; } = new CommonResponse(ModifiedCode, "An existent item is modified.");
+ public bool Create { get; set; }
+ }
+
+ public CommonPutResponse()
+ {
+
+ }
+
+ public CommonPutResponse(int code, string message, bool create)
+ : base(code, message, new ResponseData(create))
+ {
+
+ }
+
+ internal static CommonPutResponse Create(IStringLocalizerFactory localizerFactory)
+ {
+ var localizer = localizerFactory.Create("Models.Http.Common");
+ return new CommonPutResponse(0, localizer["PutCreate"], true);
+ }
+
+ internal static CommonPutResponse Modify(IStringLocalizerFactory localizerFactory)
+ {
+ var localizer = localizerFactory.Create("Models.Http.Common");
+ return new CommonPutResponse(0, localizer["PutModify"], false);
+
+ }
}
- public static class CommonDeleteResponse
+ public class CommonDeleteResponse : CommonDataResponse<CommonDeleteResponse.ResponseData>
{
- public const int DeletedCode = 0;
- public const int NotExistsCode = 1;
+ public class ResponseData
+ {
+ public ResponseData(bool delete)
+ {
+ Delete = delete;
+ }
- public static CommonResponse Deleted { get; } = new CommonResponse(DeletedCode, "An existent item is deleted.");
- public static CommonResponse NotExists { get; } = new CommonResponse(NotExistsCode, "The item does not exist.");
+ public bool Delete { get; set; }
+ }
+
+ public CommonDeleteResponse()
+ {
+
+ }
+
+ public CommonDeleteResponse(int code, string message, bool delete)
+ : base(code, message, new ResponseData(delete))
+ {
+
+ }
+
+ internal static CommonDeleteResponse Delete(IStringLocalizerFactory localizerFactory)
+ {
+ var localizer = localizerFactory.Create("Models.Http.Common");
+ return new CommonDeleteResponse(0, localizer["DeleteDelete"], true);
+ }
+
+ internal static CommonDeleteResponse NotExist(IStringLocalizerFactory localizerFactory)
+ {
+ var localizer = localizerFactory.Create("Models.Models.Http.Common");
+ return new CommonDeleteResponse(0, localizer["DeleteNotExist"], false);
+ }
}
}
diff --git a/Timeline/Models/Http/Token.cs b/Timeline/Models/Http/Token.cs index 68a66d0a..ea8b59ed 100644 --- a/Timeline/Models/Http/Token.cs +++ b/Timeline/Models/Http/Token.cs @@ -5,28 +5,28 @@ namespace Timeline.Models.Http public class CreateTokenRequest
{
[Required]
- public string Username { get; set; }
+ public string Username { get; set; } = default!;
[Required]
- public string Password { get; set; }
+ public string Password { get; set; } = default!;
// in days, optional
[Range(1, 365)]
- public int? ExpireOffset { get; set; }
+ public int? Expire { get; set; }
}
public class CreateTokenResponse
{
- public string Token { get; set; }
- public UserInfo User { get; set; }
+ public string Token { get; set; } = default!;
+ public UserInfo User { get; set; } = default!;
}
public class VerifyTokenRequest
{
[Required]
- public string Token { get; set; }
+ public string Token { get; set; } = default!;
}
public class VerifyTokenResponse
{
- public UserInfo User { get; set; }
+ public UserInfo User { get; set; } = default!;
}
}
diff --git a/Timeline/Models/Http/User.cs b/Timeline/Models/Http/User.cs index 4308a19c..516c1329 100644 --- a/Timeline/Models/Http/User.cs +++ b/Timeline/Models/Http/User.cs @@ -6,31 +6,33 @@ namespace Timeline.Models.Http public class UserPutRequest
{
[Required]
- public string Password { get; set; }
+ public string Password { get; set; } = default!;
[Required]
public bool? Administrator { get; set; }
}
public class UserPatchRequest
{
- public string Password { get; set; }
+ public string? Password { get; set; }
public bool? Administrator { get; set; }
}
public class ChangeUsernameRequest
{
[Required]
- public string OldUsername { get; set; }
+ [Username]
+ public string OldUsername { get; set; } = default!;
- [Required, ValidateWith(typeof(UsernameValidator))]
- public string NewUsername { get; set; }
+ [Required]
+ [Username]
+ public string NewUsername { get; set; } = default!;
}
public class ChangePasswordRequest
{
[Required]
- public string OldPassword { get; set; }
+ public string OldPassword { get; set; } = default!;
[Required]
- public string NewPassword { get; set; }
+ public string NewPassword { get; set; } = default!;
}
}
diff --git a/Timeline/Models/PutResult.cs b/Timeline/Models/PutResult.cs index 544602eb..cecf86e6 100644 --- a/Timeline/Models/PutResult.cs +++ b/Timeline/Models/PutResult.cs @@ -8,10 +8,10 @@ namespace Timeline.Models /// <summary>
/// Indicates the item did not exist and now is created.
/// </summary>
- Created,
+ Create,
/// <summary>
/// Indicates the item exists already and is modified.
/// </summary>
- Modified
+ Modify
}
}
diff --git a/Timeline/Models/UserConvert.cs b/Timeline/Models/UserConvert.cs new file mode 100644 index 00000000..5b132421 --- /dev/null +++ b/Timeline/Models/UserConvert.cs @@ -0,0 +1,67 @@ +using System;
+using System.Collections.Generic;
+using System.Linq;
+using Timeline.Entities;
+using Timeline.Services;
+
+namespace Timeline.Models
+{
+ 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;
+ public const string AdminRole = UserRoles.Admin;
+
+ public static string[] ToArray(bool administrator)
+ {
+ return administrator ? new string[] { UserRole, AdminRole } : new string[] { UserRole };
+ }
+
+ public static string[] ToArray(string s)
+ {
+ return s.Split(',').ToArray();
+ }
+
+ public static bool ToBool(IReadOnlyCollection<string> roles)
+ {
+ return roles.Contains(AdminRole);
+ }
+
+ public static string ToString(IReadOnlyCollection<string> roles)
+ {
+ return string.Join(',', roles);
+ }
+
+ public static string ToString(bool administrator)
+ {
+ return administrator ? UserRole + "," + AdminRole : UserRole;
+ }
+
+ public static bool ToBool(string s)
+ {
+ return s.Contains("admin", StringComparison.InvariantCulture);
+ }
+ }
+}
diff --git a/Timeline/Models/UserDetail.cs b/Timeline/Models/UserDetail.cs deleted file mode 100644 index 1a6c0c6a..00000000 --- a/Timeline/Models/UserDetail.cs +++ /dev/null @@ -1,45 +0,0 @@ -using System.ComponentModel.DataAnnotations;
-using Timeline.Entities;
-using Timeline.Models.Validation;
-using Newtonsoft.Json;
-
-namespace Timeline.Models
-{
- public class UserDetail
- {
- [MaxLength(10)]
- public string Nickname { get; set; }
-
- [ValidateWith(typeof(UserDetailValidators.QQValidator))]
- [JsonProperty(PropertyName = "qq")]
- public string QQ { get; set; }
-
- [ValidateWith(typeof(UserDetailValidators.EMailValidator))]
- public string Email { get; set; }
-
- [ValidateWith(typeof(UserDetailValidators.PhoneNumberValidator))]
- public string PhoneNumber { get; set; }
-
- public string Description { get; set; }
-
- private static string CoerceEmptyToNull(string value)
- {
- if (string.IsNullOrEmpty(value))
- return null;
- else
- return value;
- }
-
- public static UserDetail From(UserDetailEntity entity)
- {
- return new UserDetail
- {
- Nickname = CoerceEmptyToNull(entity.Nickname),
- QQ = CoerceEmptyToNull(entity.QQ),
- Email = CoerceEmptyToNull(entity.Email),
- PhoneNumber = CoerceEmptyToNull(entity.PhoneNumber),
- Description = CoerceEmptyToNull(entity.Description)
- };
- }
- }
-}
diff --git a/Timeline/Models/UserInfo.cs b/Timeline/Models/UserInfo.cs index e502855b..b60bdfa2 100644 --- a/Timeline/Models/UserInfo.cs +++ b/Timeline/Models/UserInfo.cs @@ -12,8 +12,8 @@ namespace Timeline.Models Administrator = administrator;
}
- public string Username { get; set; }
- public bool Administrator { get; set; }
+ public string Username { get; set; } = default!;
+ public bool Administrator { get; set; } = default!;
public override string ToString()
{
diff --git a/Timeline/Models/UserUtility.cs b/Timeline/Models/UserUtility.cs deleted file mode 100644 index 405987b5..00000000 --- a/Timeline/Models/UserUtility.cs +++ /dev/null @@ -1,60 +0,0 @@ -using System;
-using System.Linq;
-using Timeline.Entities;
-using Timeline.Services;
-
-namespace Timeline.Models
-{
- public static class UserUtility
- {
- public const string UserRole = UserRoles.User;
- public const string AdminRole = UserRoles.Admin;
-
- public static string[] UserRoleArray { get; } = new string[] { UserRole };
- public static string[] AdminRoleArray { get; } = new string[] { UserRole, AdminRole };
-
- public static string[] IsAdminToRoleArray(bool isAdmin)
- {
- return isAdmin ? AdminRoleArray : UserRoleArray;
- }
-
- public static bool RoleArrayToIsAdmin(string[] roles)
- {
- return roles.Contains(AdminRole);
- }
-
- public static string[] RoleStringToRoleArray(string roleString)
- {
- return roleString.Split(',').ToArray();
- }
-
- public static string RoleArrayToRoleString(string[] roles)
- {
- return string.Join(',', roles);
- }
-
- public static string IsAdminToRoleString(bool isAdmin)
- {
- return RoleArrayToRoleString(IsAdminToRoleArray(isAdmin));
- }
-
- public static bool RoleStringToIsAdmin(string roleString)
- {
- return RoleArrayToIsAdmin(RoleStringToRoleArray(roleString));
- }
-
- public static UserInfo CreateUserInfo(User user)
- {
- if (user == null)
- throw new ArgumentNullException(nameof(user));
- return new UserInfo(user.Name, RoleStringToIsAdmin(user.RoleString));
- }
-
- internal static UserCache CreateUserCache(User user)
- {
- if (user == null)
- throw new ArgumentNullException(nameof(user));
- return new UserCache { Username = user.Name, Administrator = RoleStringToIsAdmin(user.RoleString), Version = user.Version };
- }
- }
-}
diff --git a/Timeline/Models/Validation/UserDetailValidator.cs b/Timeline/Models/Validation/UserDetailValidator.cs deleted file mode 100644 index 19c82edb..00000000 --- a/Timeline/Models/Validation/UserDetailValidator.cs +++ /dev/null @@ -1,116 +0,0 @@ -using System;
-using System.Net.Mail;
-
-namespace Timeline.Models.Validation
-{
- public abstract class OptionalStringValidator : IValidator
- {
- public bool Validate(object value, out string message)
- {
- if (value == null)
- {
- message = ValidationConstants.SuccessMessage;
- return true;
- }
-
- if (value is string s)
- {
- if (s.Length == 0)
- {
- message = ValidationConstants.SuccessMessage;
- return true;
- }
- return DoValidate(s, out message);
- }
- else
- {
- message = "Value is not of type string.";
- return false;
- }
- }
-
- protected abstract bool DoValidate(string value, out string message);
- }
-
- public static class UserDetailValidators
- {
-
- public class QQValidator : OptionalStringValidator
- {
- protected override bool DoValidate(string value, out string message)
- {
- if (value.Length < 5)
- {
- message = "QQ is too short.";
- return false;
- }
-
- if (value.Length > 11)
- {
- message = "QQ is too long.";
- return false;
- }
-
- foreach (var c in value)
- {
- if (!char.IsDigit(c))
- {
- message = "QQ must only contain digit.";
- return false;
- }
- }
-
- message = ValidationConstants.SuccessMessage;
- return true;
- }
- }
-
- public class EMailValidator : OptionalStringValidator
- {
- protected override bool DoValidate(string value, out string message)
- {
- if (value.Length > 50)
- {
- message = "E-Mail is too long.";
- return false;
- }
-
- try
- {
- var _ = new MailAddress(value);
- }
- catch (FormatException)
- {
- message = "The format of E-Mail is bad.";
- return false;
- }
- message = ValidationConstants.SuccessMessage;
- return true;
- }
- }
-
- public class PhoneNumberValidator : OptionalStringValidator
- {
- protected override bool DoValidate(string value, out string message)
- {
- if (value.Length > 14)
- {
- message = "Phone number is too long.";
- return false;
- }
-
- foreach (var c in value)
- {
- if (!char.IsDigit(c))
- {
- message = "Phone number can only contain digit.";
- return false;
- }
- }
-
- message = ValidationConstants.SuccessMessage;
- return true;
- }
- }
- }
-}
diff --git a/Timeline/Models/Validation/UsernameValidator.cs b/Timeline/Models/Validation/UsernameValidator.cs index e4891400..dc237add 100644 --- a/Timeline/Models/Validation/UsernameValidator.cs +++ b/Timeline/Models/Validation/UsernameValidator.cs @@ -1,45 +1,51 @@ -using System.Linq;
-using System.Text.RegularExpressions;
+using System;
+using System.Linq;
namespace Timeline.Models.Validation
{
public class UsernameValidator : Validator<string>
{
public const int MaxLength = 26;
- public const string RegexPattern = @"^[a-zA-Z0-9_][a-zA-Z0-9-_]*$";
- private readonly Regex _regex = new Regex(RegexPattern);
-
- protected override bool DoValidate(string value, out string message)
+ [System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1062:Validate arguments of public methods", Justification = "Already checked in base class.")]
+ protected override (bool, ValidationMessageGenerator) DoValidate(string value)
{
if (value.Length == 0)
{
- message = "An empty string is not permitted.";
- return false;
+ return (false, factory =>
+ factory?.Create(typeof(UsernameValidator))?["ValidationMessageEmptyString"]
+ ?? Resources.Models.Validation.UsernameValidator.InvariantValidationMessageEmptyString);
}
if (value.Length > 26)
{
- message = $"Too long, more than 26 characters is not premitted, found {value.Length}.";
- return false;
+ return (false, factory =>
+ factory?.Create(typeof(UsernameValidator))?["ValidationMessageTooLong"]
+ ?? Resources.Models.Validation.UsernameValidator.InvariantValidationMessageTooLong);
}
foreach ((char c, int i) in value.Select((c, i) => (c, i)))
- if (char.IsWhiteSpace(c))
+ {
+ if (!(char.IsLetterOrDigit(c) || c == '-' || c == '_'))
{
- message = $"A whitespace is found at {i} . Whitespace is not permited.";
- return false;
+ return (false, factory =>
+ factory?.Create(typeof(UsernameValidator))?["ValidationMessageInvalidChar"]
+ ?? Resources.Models.Validation.UsernameValidator.InvariantValidationMessageInvalidChar);
}
-
- var match = _regex.Match(value);
- if (!match.Success)
- {
- message = "Regex match failed.";
- return false;
}
- message = ValidationConstants.SuccessMessage;
- return true;
+ return (true, SuccessMessageGenerator);
+ }
+ }
+
+ [AttributeUsage(AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Parameter,
+ AllowMultiple = false)]
+ public class UsernameAttribute : ValidateWithAttribute
+ {
+ public UsernameAttribute()
+ : base(typeof(UsernameValidator))
+ {
+
}
}
}
diff --git a/Timeline/Models/Validation/Validator.cs b/Timeline/Models/Validation/Validator.cs index a1acbed9..d2c7c377 100644 --- a/Timeline/Models/Validation/Validator.cs +++ b/Timeline/Models/Validation/Validator.cs @@ -1,11 +1,21 @@ -using System;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Localization;
+using System;
using System.ComponentModel.DataAnnotations;
+using Timeline.Helpers;
namespace Timeline.Models.Validation
{
/// <summary>
+ /// Generate a message from a localizer factory.
+ /// If localizerFactory is null, it should return a culture-invariant message.
+ /// </summary>
+ /// <param name="localizerFactory">The localizer factory. Could be null.</param>
+ /// <returns>The message.</returns>
+ public delegate string ValidationMessageGenerator(IStringLocalizerFactory? localizerFactory);
+
+ /// <summary>
/// A validator to validate value.
- /// See <see cref="Validate(object, out string)"/>.
/// </summary>
public interface IValidator
{
@@ -13,14 +23,8 @@ namespace Timeline.Models.Validation /// Validate given value.
/// </summary>
/// <param name="value">The value to validate.</param>
- /// <param name="message">The validation message.</param>
- /// <returns>True if validation passed. Otherwise false.</returns>
- bool Validate(object value, out string message);
- }
-
- public static class ValidationConstants
- {
- public const string SuccessMessage = "Validation succeeded.";
+ /// <returns>Validation success or not and the message generator.</returns>
+ (bool, ValidationMessageGenerator) Validate(object? value);
}
/// <summary>
@@ -36,27 +40,32 @@ namespace Timeline.Models.Validation /// </remarks>
public abstract class Validator<T> : IValidator
{
- public bool Validate(object value, out string message)
+ public (bool, ValidationMessageGenerator) Validate(object? value)
{
if (value == null)
{
- message = "Value is null.";
- return false;
+ return (false, factory =>
+ factory?.Create("Models.Validation.Validator")?["ValidatorMessageNull"]
+ ?? Resources.Models.Validation.Validator.InvariantValidatorMessageNull
+ );
}
if (value is T v)
{
-
- return DoValidate(v, out message);
+ return DoValidate(v);
}
else
{
- message = $"Value is not of type {typeof(T).Name}";
- return false;
+ return (false, factory =>
+ factory?.Create("Models.Validation.Validator")?["ValidatorMessageBadType", typeof(T).FullName]
+ ?? Resources.Models.Validation.Validator.InvariantValidatorMessageBadType);
}
}
- protected abstract bool DoValidate(T value, out string message);
+ protected static ValidationMessageGenerator SuccessMessageGenerator { get; } = factory =>
+ factory?.Create("Models.Validation.Validator")?["ValidatorMessageSuccess"] ?? Resources.Models.Validation.Validator.InvariantValidatorMessageSuccess;
+
+ protected abstract (bool, ValidationMessageGenerator) DoValidate(T value);
}
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Parameter,
@@ -84,24 +93,33 @@ namespace Timeline.Models.Validation throw new ArgumentNullException(nameof(validatorType));
if (!typeof(IValidator).IsAssignableFrom(validatorType))
- throw new ArgumentException("Given type is not assignable to IValidator.", nameof(validatorType));
+ throw new ArgumentException(
+ Resources.Models.Validation.Validator.ValidateWithAttributeNotValidator,
+ nameof(validatorType));
try
{
- _validator = Activator.CreateInstance(validatorType) as IValidator;
+ _validator = (Activator.CreateInstance(validatorType) as IValidator)!;
}
catch (Exception e)
{
- throw new ArgumentException("Failed to create a validator instance from default constructor. See inner exception.", e);
+ throw new ArgumentException(
+ Resources.Models.Validation.Validator.ValidateWithAttributeCreateFail, e);
}
}
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
- if (_validator.Validate(value, out var message))
+ var (result, messageGenerator) = _validator.Validate(value);
+ if (result)
+ {
return ValidationResult.Success;
+ }
else
- return new ValidationResult(string.Format("Field {0} is bad. {1}", validationContext.DisplayName, message));
+ {
+ var localizerFactory = validationContext.GetRequiredService<IStringLocalizerFactory>();
+ return new ValidationResult(messageGenerator(localizerFactory));
+ }
}
}
}
diff --git a/Timeline/Program.cs b/Timeline/Program.cs index dfc93b9e..4a098adf 100644 --- a/Timeline/Program.cs +++ b/Timeline/Program.cs @@ -3,10 +3,11 @@ using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.FileProviders;
using Microsoft.Extensions.Hosting;
+using System.Resources;
namespace Timeline
{
- public class Program
+ public static class Program
{
public static void Main(string[] args)
{
diff --git a/Timeline/Resources/Authentication/AuthHandler.Designer.cs b/Timeline/Resources/Authentication/AuthHandler.Designer.cs new file mode 100644 index 00000000..fd4540ea --- /dev/null +++ b/Timeline/Resources/Authentication/AuthHandler.Designer.cs @@ -0,0 +1,99 @@ +//------------------------------------------------------------------------------
+// <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.Authentication {
+ 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 AuthHandler {
+
+ private static global::System.Resources.ResourceManager resourceMan;
+
+ private static global::System.Globalization.CultureInfo resourceCulture;
+
+ [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
+ internal AuthHandler() {
+ }
+
+ /// <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.Authentication.AuthHandler", typeof(AuthHandler).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 Token is found in authorization header. Token is {0} ..
+ /// </summary>
+ internal static string LogTokenFoundInHeader {
+ get {
+ return ResourceManager.GetString("LogTokenFoundInHeader", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to Token is found in query param with key "{0}". Token is {1} ..
+ /// </summary>
+ internal static string LogTokenFoundInQuery {
+ get {
+ return ResourceManager.GetString("LogTokenFoundInQuery", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to No jwt token is found..
+ /// </summary>
+ internal static string LogTokenNotFound {
+ get {
+ return ResourceManager.GetString("LogTokenNotFound", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to A jwt token validation failed..
+ /// </summary>
+ internal static string LogTokenValidationFail {
+ get {
+ return ResourceManager.GetString("LogTokenValidationFail", resourceCulture);
+ }
+ }
+ }
+}
diff --git a/Timeline/Resources/Authentication/AuthHandler.resx b/Timeline/Resources/Authentication/AuthHandler.resx new file mode 100644 index 00000000..4cddc8ce --- /dev/null +++ b/Timeline/Resources/Authentication/AuthHandler.resx @@ -0,0 +1,132 @@ +<?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="LogTokenFoundInHeader" xml:space="preserve">
+ <value>Token is found in authorization header. Token is {0} .</value>
+ </data>
+ <data name="LogTokenFoundInQuery" xml:space="preserve">
+ <value>Token is found in query param with key "{0}". Token is {1} .</value>
+ </data>
+ <data name="LogTokenNotFound" xml:space="preserve">
+ <value>No jwt token is found.</value>
+ </data>
+ <data name="LogTokenValidationFail" xml:space="preserve">
+ <value>A jwt token validation failed.</value>
+ </data>
+</root>
\ No newline at end of file diff --git a/Timeline/Resources/Common.Designer.cs b/Timeline/Resources/Common.Designer.cs new file mode 100644 index 00000000..4f1c8e3f --- /dev/null +++ b/Timeline/Resources/Common.Designer.cs @@ -0,0 +1,72 @@ +//------------------------------------------------------------------------------
+// <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 Common {
+
+ 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() {
+ }
+
+ /// <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.Common", typeof(Common).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 The branch is invalid. Normally this branch is not reachable..
+ /// </summary>
+ internal static string ExceptionInvalidBranch {
+ get {
+ return ResourceManager.GetString("ExceptionInvalidBranch", resourceCulture);
+ }
+ }
+ }
+}
diff --git a/Timeline/Resources/Common.resx b/Timeline/Resources/Common.resx new file mode 100644 index 00000000..8a036996 --- /dev/null +++ b/Timeline/Resources/Common.resx @@ -0,0 +1,123 @@ +<?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/Controllers/TokenController.Designer.cs b/Timeline/Resources/Controllers/TokenController.Designer.cs new file mode 100644 index 00000000..a7c2864b --- /dev/null +++ b/Timeline/Resources/Controllers/TokenController.Designer.cs @@ -0,0 +1,153 @@ +//------------------------------------------------------------------------------
+// <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.Controllers {
+ 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 TokenController {
+
+ private static global::System.Resources.ResourceManager resourceMan;
+
+ private static global::System.Globalization.CultureInfo resourceCulture;
+
+ [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
+ internal TokenController() {
+ }
+
+ /// <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.Controllers.TokenController", typeof(TokenController).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 The password is wrong..
+ /// </summary>
+ internal static string LogBadPassword {
+ get {
+ return ResourceManager.GetString("LogBadPassword", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to A user failed to create a token..
+ /// </summary>
+ internal static string LogCreateFailure {
+ get {
+ return ResourceManager.GetString("LogCreateFailure", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to A user succeeded to create a token..
+ /// </summary>
+ internal static string LogCreateSuccess {
+ get {
+ return ResourceManager.GetString("LogCreateSuccess", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to The user does not exist..
+ /// </summary>
+ internal static string LogUserNotExist {
+ get {
+ return ResourceManager.GetString("LogUserNotExist", 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 LogVerifyBadFormat {
+ get {
+ return ResourceManager.GetString("LogVerifyBadFormat", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to The token is expired..
+ /// </summary>
+ internal static string LogVerifyExpire {
+ get {
+ return ResourceManager.GetString("LogVerifyExpire", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to A token failed to be verified..
+ /// </summary>
+ internal static string LogVerifyFailure {
+ get {
+ return ResourceManager.GetString("LogVerifyFailure", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to Token has an old version. User might have update some info..
+ /// </summary>
+ internal static string LogVerifyOldVersion {
+ get {
+ return ResourceManager.GetString("LogVerifyOldVersion", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to A token succeeded to be verified..
+ /// </summary>
+ internal static string LogVerifySuccess {
+ get {
+ return ResourceManager.GetString("LogVerifySuccess", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to User does not exist. Administrator might have deleted this user..
+ /// </summary>
+ internal static string LogVerifyUserNotExist {
+ get {
+ return ResourceManager.GetString("LogVerifyUserNotExist", resourceCulture);
+ }
+ }
+ }
+}
diff --git a/Timeline/Resources/Controllers/TokenController.en.resx b/Timeline/Resources/Controllers/TokenController.en.resx new file mode 100644 index 00000000..4a3d94f9 --- /dev/null +++ b/Timeline/Resources/Controllers/TokenController.en.resx @@ -0,0 +1,135 @@ +<?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>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>
+</root>
\ No newline at end of file diff --git a/Timeline/Resources/Controllers/TokenController.resx b/Timeline/Resources/Controllers/TokenController.resx new file mode 100644 index 00000000..683d6cc9 --- /dev/null +++ b/Timeline/Resources/Controllers/TokenController.resx @@ -0,0 +1,150 @@ +<?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="LogBadPassword" xml:space="preserve">
+ <value>The password is wrong.</value>
+ </data>
+ <data name="LogCreateFailure" xml:space="preserve">
+ <value>A user failed to create a token.</value>
+ </data>
+ <data name="LogCreateSuccess" xml:space="preserve">
+ <value>A user succeeded to create a token.</value>
+ </data>
+ <data name="LogUserNotExist" xml:space="preserve">
+ <value>The user does not exist.</value>
+ </data>
+ <data name="LogVerifyBadFormat" xml:space="preserve">
+ <value>The token is of bad format. It might not be created by the server.</value>
+ </data>
+ <data name="LogVerifyExpire" xml:space="preserve">
+ <value>The token is expired.</value>
+ </data>
+ <data name="LogVerifyFailure" xml:space="preserve">
+ <value>A token failed to be verified.</value>
+ </data>
+ <data name="LogVerifyOldVersion" xml:space="preserve">
+ <value>Token has an old version. User might have update some info.</value>
+ </data>
+ <data name="LogVerifySuccess" xml:space="preserve">
+ <value>A token succeeded to be verified.</value>
+ </data>
+ <data name="LogVerifyUserNotExist" xml:space="preserve">
+ <value>User does not exist. Administrator might have deleted this user.</value>
+ </data>
+</root>
\ No newline at end of file diff --git a/Timeline/Resources/Controllers/TokenController.zh.resx b/Timeline/Resources/Controllers/TokenController.zh.resx new file mode 100644 index 00000000..51e0f25b --- /dev/null +++ b/Timeline/Resources/Controllers/TokenController.zh.resx @@ -0,0 +1,135 @@ +<?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 new file mode 100644 index 00000000..e6eeb1e8 --- /dev/null +++ b/Timeline/Resources/Controllers/UserAvatarController.Designer.cs @@ -0,0 +1,171 @@ +//------------------------------------------------------------------------------
+// <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.Controllers {
+ 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 UserAvatarController {
+
+ private static global::System.Resources.ResourceManager resourceMan;
+
+ private static global::System.Globalization.CultureInfo resourceCulture;
+
+ [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
+ internal UserAvatarController() {
+ }
+
+ /// <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.Controllers.UserAvatarController", typeof(UserAvatarController).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 Unknown AvatarDataException.ErrorReason value..
+ /// </summary>
+ internal static string ExceptionUnknownAvatarFormatError {
+ get {
+ return ResourceManager.GetString("ExceptionUnknownAvatarFormatError", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to Attempt to delete a avatar of other user as a non-admin failed..
+ /// </summary>
+ internal static string LogDeleteForbid {
+ get {
+ return ResourceManager.GetString("LogDeleteForbid", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to Attempt to delete a avatar of a non-existent user failed..
+ /// </summary>
+ internal static string LogDeleteNotExist {
+ get {
+ return ResourceManager.GetString("LogDeleteNotExist", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to Succeed to delete a avatar of a user..
+ /// </summary>
+ internal static string LogDeleteSuccess {
+ get {
+ return ResourceManager.GetString("LogDeleteSuccess", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to Attempt to get a avatar with If-None-Match in bad format..
+ /// </summary>
+ internal static string LogGetBadIfNoneMatch {
+ get {
+ return ResourceManager.GetString("LogGetBadIfNoneMatch", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to Returned full data for a get avatar attempt..
+ /// </summary>
+ internal static string LogGetReturnData {
+ get {
+ return ResourceManager.GetString("LogGetReturnData", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to Returned NotModify for a get avatar attempt..
+ /// </summary>
+ internal static string LogGetReturnNotModify {
+ get {
+ return ResourceManager.GetString("LogGetReturnNotModify", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to Attempt to get a avatar of a non-existent user failed..
+ /// </summary>
+ internal static string LogGetUserNotExist {
+ get {
+ return ResourceManager.GetString("LogGetUserNotExist", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to Attempt to put a avatar of other user as a non-admin failed..
+ /// </summary>
+ internal static string LogPutForbid {
+ get {
+ return ResourceManager.GetString("LogPutForbid", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to Succeed to put a avatar of a user..
+ /// </summary>
+ internal static string LogPutSuccess {
+ get {
+ return ResourceManager.GetString("LogPutSuccess", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to Attempt to put a avatar of a bad format failed..
+ /// </summary>
+ internal static string LogPutUserBadFormat {
+ get {
+ return ResourceManager.GetString("LogPutUserBadFormat", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to Attempt to put a avatar of a non-existent user failed..
+ /// </summary>
+ internal static string LogPutUserNotExist {
+ get {
+ return ResourceManager.GetString("LogPutUserNotExist", resourceCulture);
+ }
+ }
+ }
+}
diff --git a/Timeline/Resources/Controllers/UserAvatarController.en.resx b/Timeline/Resources/Controllers/UserAvatarController.en.resx new file mode 100644 index 00000000..cf92ae6d --- /dev/null +++ b/Timeline/Resources/Controllers/UserAvatarController.en.resx @@ -0,0 +1,144 @@ +<?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>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>
+</root>
\ No newline at end of file diff --git a/Timeline/Resources/Controllers/UserAvatarController.resx b/Timeline/Resources/Controllers/UserAvatarController.resx new file mode 100644 index 00000000..58860c83 --- /dev/null +++ b/Timeline/Resources/Controllers/UserAvatarController.resx @@ -0,0 +1,156 @@ +<?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="ExceptionUnknownAvatarFormatError" xml:space="preserve">
+ <value>Unknown AvatarDataException.ErrorReason value.</value>
+ </data>
+ <data name="LogDeleteForbid" xml:space="preserve">
+ <value>Attempt to delete a avatar of other user as a non-admin failed.</value>
+ </data>
+ <data name="LogDeleteNotExist" xml:space="preserve">
+ <value>Attempt to delete a avatar of a non-existent user failed.</value>
+ </data>
+ <data name="LogDeleteSuccess" xml:space="preserve">
+ <value>Succeed to delete a avatar of a user.</value>
+ </data>
+ <data name="LogGetBadIfNoneMatch" xml:space="preserve">
+ <value>Attempt to get a avatar with If-None-Match in bad format.</value>
+ </data>
+ <data name="LogGetReturnData" xml:space="preserve">
+ <value>Returned full data for a get avatar attempt.</value>
+ </data>
+ <data name="LogGetReturnNotModify" xml:space="preserve">
+ <value>Returned NotModify for a get avatar attempt.</value>
+ </data>
+ <data name="LogGetUserNotExist" xml:space="preserve">
+ <value>Attempt to get a avatar of a non-existent user failed.</value>
+ </data>
+ <data name="LogPutForbid" xml:space="preserve">
+ <value>Attempt to put a avatar of other user as a non-admin failed.</value>
+ </data>
+ <data name="LogPutSuccess" xml:space="preserve">
+ <value>Succeed to put a avatar of a user.</value>
+ </data>
+ <data name="LogPutUserBadFormat" xml:space="preserve">
+ <value>Attempt to put a avatar of a bad format failed.</value>
+ </data>
+ <data name="LogPutUserNotExist" xml:space="preserve">
+ <value>Attempt to put a avatar of a non-existent user failed.</value>
+ </data>
+</root>
\ No newline at end of file diff --git a/Timeline/Resources/Controllers/UserAvatarController.zh.resx b/Timeline/Resources/Controllers/UserAvatarController.zh.resx new file mode 100644 index 00000000..94de1606 --- /dev/null +++ b/Timeline/Resources/Controllers/UserAvatarController.zh.resx @@ -0,0 +1,144 @@ +<?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 new file mode 100644 index 00000000..df9cab4c --- /dev/null +++ b/Timeline/Resources/Controllers/UserController.Designer.cs @@ -0,0 +1,171 @@ +//------------------------------------------------------------------------------
+// <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.Controllers {
+ 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 UserController {
+
+ private static global::System.Resources.ResourceManager resourceMan;
+
+ private static global::System.Globalization.CultureInfo resourceCulture;
+
+ [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
+ internal UserController() {
+ }
+
+ /// <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.Controllers.UserController", typeof(UserController).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 Attempt to change password with wrong old password failed..
+ /// </summary>
+ internal static string LogChangePasswordBadPassword {
+ get {
+ return ResourceManager.GetString("LogChangePasswordBadPassword", resourceCulture);
+ }
+ }
+
+ /// <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's username to a existent one failed..
+ /// </summary>
+ internal static string LogChangeUsernameAlreadyExist {
+ get {
+ return ResourceManager.GetString("LogChangeUsernameAlreadyExist", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to Attempt to change a username of a user that does not exist failed..
+ /// </summary>
+ internal static string LogChangeUsernameNotExist {
+ get {
+ return ResourceManager.GetString("LogChangeUsernameNotExist", resourceCulture);
+ }
+ }
+
+ /// <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 {
+ get {
+ return ResourceManager.GetString("LogGetUserNotExist", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to Attempt to patch a user that does not exist failed..
+ /// </summary>
+ internal static string LogPatchUserNotExist {
+ get {
+ 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.en.resx b/Timeline/Resources/Controllers/UserController.en.resx new file mode 100644 index 00000000..0bd1dfe3 --- /dev/null +++ b/Timeline/Resources/Controllers/UserController.en.resx @@ -0,0 +1,138 @@ +<?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>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>
+</root>
\ No newline at end of file diff --git a/Timeline/Resources/Controllers/UserController.resx b/Timeline/Resources/Controllers/UserController.resx new file mode 100644 index 00000000..d720d1c1 --- /dev/null +++ b/Timeline/Resources/Controllers/UserController.resx @@ -0,0 +1,156 @@ +<?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="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">
+ <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 new file mode 100644 index 00000000..3556083e --- /dev/null +++ b/Timeline/Resources/Controllers/UserController.zh.resx @@ -0,0 +1,138 @@ +<?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/Models/Http/Common.en.resx b/Timeline/Resources/Models/Http/Common.en.resx new file mode 100644 index 00000000..10407d76 --- /dev/null +++ b/Timeline/Resources/Models/Http/Common.en.resx @@ -0,0 +1,153 @@ +<?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="ContentTooBig" xml:space="preserve">
+ <value>Body is too big. It can't be bigger than {0}.</value>
+ </data>
+ <data name="ContentUnmatchedLengthBigger" xml:space="preserve">
+ <value>Actual body length is bigger than it in header.</value>
+ </data>
+ <data name="ContentUnmatchedLengthSmaller" xml:space="preserve">
+ <value>Actual body length is smaller than it in header.</value>
+ </data>
+ <data name="DeleteDelete" xml:space="preserve">
+ <value>An existent item is deleted.</value>
+ </data>
+ <data name="DeleteNotExist" xml:space="preserve">
+ <value>The item does not exist, so nothing is changed.</value>
+ </data>
+ <data name="HeaderBadIfNonMatch" xml:space="preserve">
+ <value>Header If-Non-Match is of bad format.</value>
+ </data>
+ <data name="HeaderMissingContentLength" xml:space="preserve">
+ <value>Header Content-Length is missing or of bad format.</value>
+ </data>
+ <data name="HeaderMissingContentType" xml:space="preserve">
+ <value>Header Content-Type is required.</value>
+ </data>
+ <data name="HeaderZeroContentLength" xml:space="preserve">
+ <value>Header Content-Length must not be 0.</value>
+ </data>
+ <data name="PutCreate" xml:space="preserve">
+ <value>A new item is created.</value>
+ </data>
+ <data name="PutModify" xml:space="preserve">
+ <value>An existent item is modified.</value>
+ </data>
+</root>
\ No newline at end of file diff --git a/Timeline/Resources/Models/Http/Common.zh.resx b/Timeline/Resources/Models/Http/Common.zh.resx new file mode 100644 index 00000000..528dc7ab --- /dev/null +++ b/Timeline/Resources/Models/Http/Common.zh.resx @@ -0,0 +1,153 @@ +<?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="ContentTooBig" xml:space="preserve">
+ <value>请求体太大。它不能超过{0}.</value>
+ </data>
+ <data name="ContentUnmatchedLengthBigger" xml:space="preserve">
+ <value>实际的请求体长度比头中指示的大。</value>
+ </data>
+ <data name="ContentUnmatchedLengthSmaller" xml:space="preserve">
+ <value>实际的请求体长度比头中指示的小。</value>
+ </data>
+ <data name="DeleteDelete" xml:space="preserve">
+ <value>删除了一个项目。</value>
+ </data>
+ <data name="DeleteNotExist" xml:space="preserve">
+ <value>要删除的项目不存在,什么都没有修改。</value>
+ </data>
+ <data name="HeaderBadIfNonMatch" xml:space="preserve">
+ <value>头If-Non-Match格式不对。</value>
+ </data>
+ <data name="HeaderMissingContentLength" xml:space="preserve">
+ <value>头Content-Length缺失或者格式不对。</value>
+ </data>
+ <data name="HeaderMissingContentType" xml:space="preserve">
+ <value>缺少必需的头Content-Type。</value>
+ </data>
+ <data name="HeaderZeroContentLength" xml:space="preserve">
+ <value>头Content-Length不能为0。</value>
+ </data>
+ <data name="PutCreate" xml:space="preserve">
+ <value>创建了一个新项目。</value>
+ </data>
+ <data name="PutModify" xml:space="preserve">
+ <value>修改了一个已存在的项目。</value>
+ </data>
+</root>
\ No newline at end of file diff --git a/Timeline/Resources/Models/Validation/UsernameValidator.Designer.cs b/Timeline/Resources/Models/Validation/UsernameValidator.Designer.cs new file mode 100644 index 00000000..a4c35326 --- /dev/null +++ b/Timeline/Resources/Models/Validation/UsernameValidator.Designer.cs @@ -0,0 +1,90 @@ +//------------------------------------------------------------------------------
+// <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.Models.Validation {
+ 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 UsernameValidator {
+
+ private static global::System.Resources.ResourceManager resourceMan;
+
+ private static global::System.Globalization.CultureInfo resourceCulture;
+
+ [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
+ internal UsernameValidator() {
+ }
+
+ /// <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.Models.Validation.UsernameValidator", typeof(UsernameValidator).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 An empty string is not allowed..
+ /// </summary>
+ internal static string InvariantValidationMessageEmptyString {
+ get {
+ return ResourceManager.GetString("InvariantValidationMessageEmptyString", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to Invalid character, only alphabet, digit, underscore and hyphen are allowed. .
+ /// </summary>
+ internal static string InvariantValidationMessageInvalidChar {
+ get {
+ return ResourceManager.GetString("InvariantValidationMessageInvalidChar", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to Too long, more than 26 characters is not premitted..
+ /// </summary>
+ internal static string InvariantValidationMessageTooLong {
+ get {
+ return ResourceManager.GetString("InvariantValidationMessageTooLong", resourceCulture);
+ }
+ }
+ }
+}
diff --git a/Timeline/Resources/Models/Validation/UsernameValidator.en.resx b/Timeline/Resources/Models/Validation/UsernameValidator.en.resx new file mode 100644 index 00000000..9171b856 --- /dev/null +++ b/Timeline/Resources/Models/Validation/UsernameValidator.en.resx @@ -0,0 +1,129 @@ +<?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="ValidationMessageEmptyString" xml:space="preserve">
+ <value>An empty string is not allowed.</value>
+ </data>
+ <data name="ValidationMessageInvalidChar" xml:space="preserve">
+ <value>Invalid character, only alphabet, digit, underscore and hyphen are allowed.</value>
+ </data>
+ <data name="ValidationMessageTooLong" xml:space="preserve">
+ <value>Too long, more than 26 characters is not premitted.</value>
+ </data>
+</root>
\ No newline at end of file diff --git a/Timeline/Resources/Models/Validation/UsernameValidator.resx b/Timeline/Resources/Models/Validation/UsernameValidator.resx new file mode 100644 index 00000000..80cae2d5 --- /dev/null +++ b/Timeline/Resources/Models/Validation/UsernameValidator.resx @@ -0,0 +1,129 @@ +<?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="InvariantValidationMessageEmptyString" xml:space="preserve">
+ <value>An empty string is not allowed.</value>
+ </data>
+ <data name="InvariantValidationMessageInvalidChar" xml:space="preserve">
+ <value>Invalid character, only alphabet, digit, underscore and hyphen are allowed. </value>
+ </data>
+ <data name="InvariantValidationMessageTooLong" xml:space="preserve">
+ <value>Too long, more than 26 characters is not premitted.</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 new file mode 100644 index 00000000..1c8a936c --- /dev/null +++ b/Timeline/Resources/Models/Validation/UsernameValidator.zh.resx @@ -0,0 +1,129 @@ +<?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="ValidationMessageEmptyString" xml:space="preserve">
+ <value>空字符串是不允许的。</value>
+ </data>
+ <data name="ValidationMessageInvalidChar" xml:space="preserve">
+ <value>无效的字符,只能使用字母、数字、下划线和连字符。</value>
+ </data>
+ <data name="ValidationMessageTooLong" xml:space="preserve">
+ <value>太长了,不能大于26个字符。</value>
+ </data>
+</root>
\ No newline at end of file diff --git a/Timeline/Resources/Models/Validation/Validator.Designer.cs b/Timeline/Resources/Models/Validation/Validator.Designer.cs new file mode 100644 index 00000000..4cbc13de --- /dev/null +++ b/Timeline/Resources/Models/Validation/Validator.Designer.cs @@ -0,0 +1,108 @@ +//------------------------------------------------------------------------------
+// <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.Models.Validation {
+ 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 Validator {
+
+ private static global::System.Resources.ResourceManager resourceMan;
+
+ private static global::System.Globalization.CultureInfo resourceCulture;
+
+ [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
+ internal Validator() {
+ }
+
+ /// <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.Models.Validation.Validator", typeof(Validator).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 Value is not of type {0}..
+ /// </summary>
+ internal static string InvariantValidatorMessageBadType {
+ get {
+ return ResourceManager.GetString("InvariantValidatorMessageBadType", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to Value can't be null..
+ /// </summary>
+ internal static string InvariantValidatorMessageNull {
+ get {
+ return ResourceManager.GetString("InvariantValidatorMessageNull", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to Validation succeeded..
+ /// </summary>
+ internal static string InvariantValidatorMessageSuccess {
+ get {
+ return ResourceManager.GetString("InvariantValidatorMessageSuccess", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to Failed to create a validator instance from default constructor. See inner exception..
+ /// </summary>
+ internal static string ValidateWithAttributeCreateFail {
+ get {
+ return ResourceManager.GetString("ValidateWithAttributeCreateFail", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to Given type is not assignable to IValidator..
+ /// </summary>
+ internal static string ValidateWithAttributeNotValidator {
+ get {
+ return ResourceManager.GetString("ValidateWithAttributeNotValidator", resourceCulture);
+ }
+ }
+ }
+}
diff --git a/Timeline/Resources/Models/Validation/Validator.en.resx b/Timeline/Resources/Models/Validation/Validator.en.resx new file mode 100644 index 00000000..8d2fbede --- /dev/null +++ b/Timeline/Resources/Models/Validation/Validator.en.resx @@ -0,0 +1,129 @@ +<?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="ValidatorMessageBadType" xml:space="preserve">
+ <value>Value is not of type {0}.</value>
+ </data>
+ <data name="ValidatorMessageNull" xml:space="preserve">
+ <value>Value can't be null.</value>
+ </data>
+ <data name="ValidatorMessageSuccess" xml:space="preserve">
+ <value>Validation succeeded.</value>
+ </data>
+</root>
\ No newline at end of file diff --git a/Timeline/Resources/Models/Validation/Validator.resx b/Timeline/Resources/Models/Validation/Validator.resx new file mode 100644 index 00000000..0e8f53a6 --- /dev/null +++ b/Timeline/Resources/Models/Validation/Validator.resx @@ -0,0 +1,135 @@ +<?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="InvariantValidatorMessageBadType" xml:space="preserve">
+ <value>Value is not of type {0}.</value>
+ </data>
+ <data name="InvariantValidatorMessageNull" xml:space="preserve">
+ <value>Value can't be null.</value>
+ </data>
+ <data name="InvariantValidatorMessageSuccess" xml:space="preserve">
+ <value>Validation succeeded.</value>
+ </data>
+ <data name="ValidateWithAttributeCreateFail" xml:space="preserve">
+ <value>Failed to create a validator instance from default constructor. See inner exception.</value>
+ </data>
+ <data name="ValidateWithAttributeNotValidator" xml:space="preserve">
+ <value>Given type is not assignable to IValidator.</value>
+ </data>
+</root>
\ No newline at end of file diff --git a/Timeline/Resources/Models/Validation/Validator.zh.resx b/Timeline/Resources/Models/Validation/Validator.zh.resx new file mode 100644 index 00000000..2f98e7e3 --- /dev/null +++ b/Timeline/Resources/Models/Validation/Validator.zh.resx @@ -0,0 +1,129 @@ +<?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="ValidatorMessageBadType" xml:space="preserve">
+ <value>值不是类型{0}的实例。</value>
+ </data>
+ <data name="ValidatorMessageNull" xml:space="preserve">
+ <value>值不能为null.</value>
+ </data>
+ <data name="ValidatorMessageSuccess" xml:space="preserve">
+ <value>验证成功。</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 new file mode 100644 index 00000000..ddf60f45 --- /dev/null +++ b/Timeline/Resources/Services/Exception.Designer.cs @@ -0,0 +1,297 @@ +//------------------------------------------------------------------------------
+// <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.Services {
+ 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 Exception {
+
+ private static global::System.Resources.ResourceManager resourceMan;
+
+ private static global::System.Globalization.CultureInfo resourceCulture;
+
+ [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
+ internal Exception() {
+ }
+
+ /// <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.Services.Exception", typeof(Exception).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 Avartar is of bad format because {0}..
+ /// </summary>
+ internal static string AvatarFormatException {
+ get {
+ return ResourceManager.GetString("AvatarFormatException", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to image is not a square, aka, width is not equal to height.
+ /// </summary>
+ internal static string AvatarFormatExceptionBadSize {
+ get {
+ return ResourceManager.GetString("AvatarFormatExceptionBadSize", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to failed to decode image, see inner exception.
+ /// </summary>
+ internal static string AvatarFormatExceptionCantDecode {
+ get {
+ return ResourceManager.GetString("AvatarFormatExceptionCantDecode", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to unknown error.
+ /// </summary>
+ internal static string AvatarFormatExceptionUnknownError {
+ get {
+ return ResourceManager.GetString("AvatarFormatExceptionUnknownError", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to image's actual mime type is not the specified one.
+ /// </summary>
+ internal static string AvatarFormatExceptionUnmatchedFormat {
+ get {
+ return ResourceManager.GetString("AvatarFormatExceptionUnmatchedFormat", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to The password is wrong..
+ /// </summary>
+ internal static string BadPasswordException {
+ get {
+ return ResourceManager.GetString("BadPasswordException", 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 {
+ get {
+ return ResourceManager.GetString("HashedPasswordBadFromatException", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to Not of valid base64 format. See inner exception..
+ /// </summary>
+ internal static string HashedPasswordBadFromatExceptionNotBase64 {
+ get {
+ return ResourceManager.GetString("HashedPasswordBadFromatExceptionNotBase64", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to Decoded hashed password is of length 0..
+ /// </summary>
+ internal static string HashedPasswordBadFromatExceptionNotLength0 {
+ get {
+ return ResourceManager.GetString("HashedPasswordBadFromatExceptionNotLength0", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to See inner exception..
+ /// </summary>
+ internal static string HashedPasswordBadFromatExceptionNotOthers {
+ get {
+ return ResourceManager.GetString("HashedPasswordBadFromatExceptionNotOthers", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to Salt length < 128 bits..
+ /// </summary>
+ internal static string HashedPasswordBadFromatExceptionNotSaltTooShort {
+ get {
+ return ResourceManager.GetString("HashedPasswordBadFromatExceptionNotSaltTooShort", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to Subkey length < 128 bits..
+ /// </summary>
+ internal static string HashedPasswordBadFromatExceptionNotSubkeyTooShort {
+ get {
+ return ResourceManager.GetString("HashedPasswordBadFromatExceptionNotSubkeyTooShort", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to Unknown format marker..
+ /// </summary>
+ internal static string HashedPasswordBadFromatExceptionNotUnknownMarker {
+ get {
+ return ResourceManager.GetString("HashedPasswordBadFromatExceptionNotUnknownMarker", resourceCulture);
+ }
+ }
+
+ /// <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'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..
+ /// </summary>
+ internal static string JwtVerifyExceptionExpired {
+ get {
+ return ResourceManager.GetString("JwtVerifyExceptionExpired", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to id claim is not a number..
+ /// </summary>
+ internal static string JwtVerifyExceptionIdClaimBadFormat {
+ get {
+ return ResourceManager.GetString("JwtVerifyExceptionIdClaimBadFormat", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to id claim does not exist..
+ /// </summary>
+ internal static string JwtVerifyExceptionNoIdClaim {
+ get {
+ return ResourceManager.GetString("JwtVerifyExceptionNoIdClaim", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to version claim does not exist..
+ /// </summary>
+ internal static string JwtVerifyExceptionNoVersionClaim {
+ get {
+ return ResourceManager.GetString("JwtVerifyExceptionNoVersionClaim", 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..
+ /// </summary>
+ internal static string JwtVerifyExceptionUnknown {
+ get {
+ return ResourceManager.GetString("JwtVerifyExceptionUnknown", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to version claim is not a number..
+ /// </summary>
+ internal static string JwtVerifyExceptionVersionClaimBadFormat {
+ get {
+ return ResourceManager.GetString("JwtVerifyExceptionVersionClaimBadFormat", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to The username is of bad format..
+ /// </summary>
+ internal static string UsernameBadFormatException {
+ get {
+ return ResourceManager.GetString("UsernameBadFormatException", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to The username already exists..
+ /// </summary>
+ internal static string UsernameConfictException {
+ get {
+ return ResourceManager.GetString("UsernameConfictException", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to The user does not exist..
+ /// </summary>
+ internal static string UserNotExistException {
+ get {
+ return ResourceManager.GetString("UserNotExistException", resourceCulture);
+ }
+ }
+ }
+}
diff --git a/Timeline/Resources/Services/Exception.resx b/Timeline/Resources/Services/Exception.resx new file mode 100644 index 00000000..12bf9afb --- /dev/null +++ b/Timeline/Resources/Services/Exception.resx @@ -0,0 +1,198 @@ +<?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="AvatarFormatException" xml:space="preserve">
+ <value>Avartar is of bad format because {0}.</value>
+ </data>
+ <data name="AvatarFormatExceptionBadSize" xml:space="preserve">
+ <value>image is not a square, aka, width is not equal to height</value>
+ </data>
+ <data name="AvatarFormatExceptionCantDecode" xml:space="preserve">
+ <value>failed to decode image, see inner exception</value>
+ </data>
+ <data name="AvatarFormatExceptionUnknownError" xml:space="preserve">
+ <value>unknown error</value>
+ </data>
+ <data name="AvatarFormatExceptionUnmatchedFormat" xml:space="preserve">
+ <value>image's actual mime type is not the specified one</value>
+ </data>
+ <data name="BadPasswordException" xml:space="preserve">
+ <value>The password is wrong.</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>
+ <data name="HashedPasswordBadFromatExceptionNotBase64" xml:space="preserve">
+ <value>Not of valid base64 format. See inner exception.</value>
+ </data>
+ <data name="HashedPasswordBadFromatExceptionNotLength0" xml:space="preserve">
+ <value>Decoded hashed password is of length 0.</value>
+ </data>
+ <data name="HashedPasswordBadFromatExceptionNotOthers" xml:space="preserve">
+ <value>See inner exception.</value>
+ </data>
+ <data name="HashedPasswordBadFromatExceptionNotSaltTooShort" xml:space="preserve">
+ <value>Salt length < 128 bits.</value>
+ </data>
+ <data name="HashedPasswordBadFromatExceptionNotSubkeyTooShort" xml:space="preserve">
+ <value>Subkey length < 128 bits.</value>
+ </data>
+ <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>
+ <data name="JwtVerifyExceptionExpired" xml:space="preserve">
+ <value>token is expired.</value>
+ </data>
+ <data name="JwtVerifyExceptionIdClaimBadFormat" xml:space="preserve">
+ <value>id claim is not a number.</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>
+ <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>
+ <data name="JwtVerifyExceptionVersionClaimBadFormat" xml:space="preserve">
+ <value>version claim is not a number.</value>
+ </data>
+ <data name="UsernameBadFormatException" xml:space="preserve">
+ <value>The username is of bad format.</value>
+ </data>
+ <data name="UsernameConfictException" xml:space="preserve">
+ <value>The username already exists.</value>
+ </data>
+ <data name="UserNotExistException" xml:space="preserve">
+ <value>The user does not exist.</value>
+ </data>
+</root>
\ No newline at end of file diff --git a/Timeline/Resources/Services/UserAvatarService.Designer.cs b/Timeline/Resources/Services/UserAvatarService.Designer.cs new file mode 100644 index 00000000..6ee6fef9 --- /dev/null +++ b/Timeline/Resources/Services/UserAvatarService.Designer.cs @@ -0,0 +1,108 @@ +//------------------------------------------------------------------------------
+// <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.Services {
+ 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 UserAvatarService {
+
+ private static global::System.Resources.ResourceManager resourceMan;
+
+ private static global::System.Globalization.CultureInfo resourceCulture;
+
+ [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
+ internal UserAvatarService() {
+ }
+
+ /// <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.Services.UserAvatarService", typeof(UserAvatarService).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 Data of avatar is null..
+ /// </summary>
+ internal static string ArgumentAvatarDataNull {
+ get {
+ return ResourceManager.GetString("ArgumentAvatarDataNull", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to Type of avatar is null or empty..
+ /// </summary>
+ internal static string ArgumentAvatarTypeNullOrEmpty {
+ get {
+ return ResourceManager.GetString("ArgumentAvatarTypeNullOrEmpty", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to Database corupted! One of type and data of a avatar is null but the other is not..
+ /// </summary>
+ internal static string DatabaseCorruptedDataAndTypeNotSame {
+ get {
+ return ResourceManager.GetString("DatabaseCorruptedDataAndTypeNotSame", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to Created an entry in user_avatars..
+ /// </summary>
+ internal static string LogCreateEntity {
+ get {
+ return ResourceManager.GetString("LogCreateEntity", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to Updated an entry in user_avatars..
+ /// </summary>
+ internal static string LogUpdateEntity {
+ get {
+ return ResourceManager.GetString("LogUpdateEntity", resourceCulture);
+ }
+ }
+ }
+}
diff --git a/Timeline/Resources/Services/UserAvatarService.resx b/Timeline/Resources/Services/UserAvatarService.resx new file mode 100644 index 00000000..3269bf13 --- /dev/null +++ b/Timeline/Resources/Services/UserAvatarService.resx @@ -0,0 +1,135 @@ +<?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="ArgumentAvatarDataNull" xml:space="preserve">
+ <value>Data of avatar is null.</value>
+ </data>
+ <data name="ArgumentAvatarTypeNullOrEmpty" xml:space="preserve">
+ <value>Type of avatar is null or empty.</value>
+ </data>
+ <data name="DatabaseCorruptedDataAndTypeNotSame" xml:space="preserve">
+ <value>Database corupted! One of type and data of a avatar is null but the other is not.</value>
+ </data>
+ <data name="LogCreateEntity" xml:space="preserve">
+ <value>Created an entry in user_avatars.</value>
+ </data>
+ <data name="LogUpdateEntity" xml:space="preserve">
+ <value>Updated an entry in user_avatars.</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 new file mode 100644 index 00000000..2a04dded --- /dev/null +++ b/Timeline/Resources/Services/UserService.Designer.cs @@ -0,0 +1,126 @@ +//------------------------------------------------------------------------------
+// <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.Services {
+ 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 UserService {
+
+ private static global::System.Resources.ResourceManager resourceMan;
+
+ private static global::System.Globalization.CultureInfo resourceCulture;
+
+ [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
+ internal UserService() {
+ }
+
+ /// <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.Services.UserService", typeof(UserService).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 New username is of bad format..
+ /// </summary>
+ internal static string ExceptionNewUsernameBadFormat {
+ get {
+ return ResourceManager.GetString("ExceptionNewUsernameBadFormat", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to Old username is of bad format..
+ /// </summary>
+ internal static string ExceptionOldUsernameBadFormat {
+ get {
+ return ResourceManager.GetString("ExceptionOldUsernameBadFormat", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to A cache entry is created..
+ /// </summary>
+ internal static string LogCacheCreate {
+ get {
+ return ResourceManager.GetString("LogCacheCreate", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to A cache entry is removed..
+ /// </summary>
+ internal static string LogCacheRemove {
+ get {
+ return ResourceManager.GetString("LogCacheRemove", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to A new user entry is added to the database..
+ /// </summary>
+ internal static string LogDatabaseCreate {
+ get {
+ return ResourceManager.GetString("LogDatabaseCreate", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to A user entry is removed from the database..
+ /// </summary>
+ internal static string LogDatabaseRemove {
+ get {
+ return ResourceManager.GetString("LogDatabaseRemove", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to A user entry is updated to the database..
+ /// </summary>
+ internal static string LogDatabaseUpdate {
+ get {
+ return ResourceManager.GetString("LogDatabaseUpdate", resourceCulture);
+ }
+ }
+ }
+}
diff --git a/Timeline/Resources/Services/UserService.resx b/Timeline/Resources/Services/UserService.resx new file mode 100644 index 00000000..3670d8f9 --- /dev/null +++ b/Timeline/Resources/Services/UserService.resx @@ -0,0 +1,141 @@ +<?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="ExceptionNewUsernameBadFormat" xml:space="preserve">
+ <value>New username is of bad format.</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>
+ <data name="LogCacheRemove" xml:space="preserve">
+ <value>A cache entry is removed.</value>
+ </data>
+ <data name="LogDatabaseCreate" xml:space="preserve">
+ <value>A new user entry is added to the database.</value>
+ </data>
+ <data name="LogDatabaseRemove" xml:space="preserve">
+ <value>A user entry is removed from the database.</value>
+ </data>
+ <data name="LogDatabaseUpdate" xml:space="preserve">
+ <value>A user entry is updated to the database.</value>
+ </data>
+</root>
\ No newline at end of file diff --git a/Timeline/Services/AvatarFormatException.cs b/Timeline/Services/AvatarFormatException.cs new file mode 100644 index 00000000..788eabb2 --- /dev/null +++ b/Timeline/Services/AvatarFormatException.cs @@ -0,0 +1,51 @@ +using System;
+using System.Globalization;
+
+namespace Timeline.Services
+{
+ /// <summary>
+ /// Thrown when avatar is of bad format.
+ /// </summary>
+ [Serializable]
+ public class AvatarFormatException : Exception
+ {
+ public enum ErrorReason
+ {
+ /// <summary>
+ /// Decoding image failed.
+ /// </summary>
+ CantDecode,
+ /// <summary>
+ /// Decoding succeeded but the real type is not the specified type.
+ /// </summary>
+ UnmatchedFormat,
+ /// <summary>
+ /// Image is not a square.
+ /// </summary>
+ BadSize
+ }
+
+ public AvatarFormatException() : base(MakeMessage(null)) { }
+ public AvatarFormatException(string message) : base(message) { }
+ public AvatarFormatException(string message, Exception inner) : base(message, inner) { }
+
+ public AvatarFormatException(Avatar avatar, ErrorReason error) : base(MakeMessage(error)) { Avatar = avatar; Error = error; }
+ public AvatarFormatException(Avatar avatar, ErrorReason error, Exception inner) : base(MakeMessage(error), inner) { Avatar = avatar; Error = error; }
+
+ protected AvatarFormatException(
+ System.Runtime.Serialization.SerializationInfo info,
+ System.Runtime.Serialization.StreamingContext context) : base(info, context) { }
+
+ private static string MakeMessage(ErrorReason? reason) =>
+ string.Format(CultureInfo.InvariantCulture, Resources.Services.Exception.AvatarFormatException, reason switch
+ {
+ ErrorReason.CantDecode => Resources.Services.Exception.AvatarFormatExceptionCantDecode,
+ ErrorReason.UnmatchedFormat => Resources.Services.Exception.AvatarFormatExceptionUnmatchedFormat,
+ ErrorReason.BadSize => Resources.Services.Exception.AvatarFormatExceptionBadSize,
+ _ => Resources.Services.Exception.AvatarFormatExceptionUnknownError
+ });
+
+ public ErrorReason? Error { get; set; }
+ public Avatar? Avatar { get; set; }
+ }
+}
diff --git a/Timeline/Services/BadPasswordException.cs b/Timeline/Services/BadPasswordException.cs new file mode 100644 index 00000000..ee8a42db --- /dev/null +++ b/Timeline/Services/BadPasswordException.cs @@ -0,0 +1,27 @@ +using System;
+using Timeline.Helpers;
+
+namespace Timeline.Services
+{
+ [Serializable]
+ public class BadPasswordException : Exception
+ {
+ public BadPasswordException() : base(Resources.Services.Exception.UserNotExistException) { }
+ public BadPasswordException(string message, Exception inner) : base(message, inner) { }
+
+ public BadPasswordException(string badPassword)
+ : base(Log.Format(Resources.Services.Exception.UserNotExistException, ("Bad Password", badPassword)))
+ {
+ Password = badPassword;
+ }
+
+ protected BadPasswordException(
+ System.Runtime.Serialization.SerializationInfo info,
+ System.Runtime.Serialization.StreamingContext context) : base(info, context) { }
+
+ /// <summary>
+ /// The wrong password.
+ /// </summary>
+ public string? Password { get; set; }
+ }
+}
diff --git a/Timeline/Services/DatabaseExtensions.cs b/Timeline/Services/DatabaseExtensions.cs index a37cf05b..62b22f00 100644 --- a/Timeline/Services/DatabaseExtensions.cs +++ b/Timeline/Services/DatabaseExtensions.cs @@ -4,22 +4,27 @@ using System.Collections.Generic; using System.Linq;
using System.Threading.Tasks;
using Timeline.Entities;
+using Timeline.Models.Validation;
namespace Timeline.Services
{
- public static class DatabaseExtensions
+ internal static class DatabaseExtensions
{
/// <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="ArgumentException">Thrown if <paramref name="username"/> is null or empty.</exception>
+ /// <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>
- public static async Task<long> CheckAndGetUser(DbSet<User> userDbSet, string username)
+ internal static async Task<long> CheckAndGetUser(DbSet<User> userDbSet, UsernameValidator validator, string username)
{
- if (string.IsNullOrEmpty(username))
- throw new ArgumentException("Username is null or empty.", nameof(username));
+ if (username == null)
+ throw new ArgumentNullException(nameof(username));
+ var (result, messageGenerator) = validator.Validate(username);
+ if (!result)
+ throw new UsernameBadFormatException(username, messageGenerator(null));
var userId = await userDbSet.Where(u => u.Name == username).Select(u => u.Id).SingleOrDefaultAsync();
if (userId == 0)
diff --git a/Timeline/Services/ETagGenerator.cs b/Timeline/Services/ETagGenerator.cs index e2abebdc..d328ea20 100644 --- a/Timeline/Services/ETagGenerator.cs +++ b/Timeline/Services/ETagGenerator.cs @@ -1,33 +1,45 @@ using System;
using System.Security.Cryptography;
+using System.Threading.Tasks;
namespace Timeline.Services
{
public interface IETagGenerator
{
- string Generate(byte[] source);
+ /// <summary>
+ /// Generate a etag for given source.
+ /// </summary>
+ /// <param name="source">The source data.</param>
+ /// <returns>The generated etag.</returns>
+ /// <exception cref="ArgumentNullException">Thrown if <paramref name="source"/> is null.</exception>
+ Task<string> Generate(byte[] source);
}
- public class ETagGenerator : IETagGenerator, IDisposable
+ public sealed class ETagGenerator : IETagGenerator, IDisposable
{
private readonly SHA1 _sha1;
+ [System.Diagnostics.CodeAnalysis.SuppressMessage("Security", "CA5350:Do Not Use Weak Cryptographic Algorithms", Justification = "Sha1 is enough ??? I don't know.")]
public ETagGenerator()
{
_sha1 = SHA1.Create();
}
- public string Generate(byte[] source)
+ public Task<string> Generate(byte[] source)
{
- if (source == null || source.Length == 0)
- throw new ArgumentException("Source is null or empty.", nameof(source));
+ if (source == null)
+ throw new ArgumentNullException(nameof(source));
- return Convert.ToBase64String(_sha1.ComputeHash(source));
+ return Task.Run(() => Convert.ToBase64String(_sha1.ComputeHash(source)));
}
+ private bool _disposed = false; // To detect redundant calls
+
public void Dispose()
{
+ if (_disposed) return;
_sha1.Dispose();
+ _disposed = true;
}
}
}
diff --git a/Timeline/Services/JwtBadVersionException.cs b/Timeline/Services/JwtBadVersionException.cs new file mode 100644 index 00000000..4ce17710 --- /dev/null +++ b/Timeline/Services/JwtBadVersionException.cs @@ -0,0 +1,36 @@ +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/JwtService.cs b/Timeline/Services/JwtService.cs index 350c5e80..bf92966a 100644 --- a/Timeline/Services/JwtService.cs +++ b/Timeline/Services/JwtService.cs @@ -1,6 +1,7 @@ using Microsoft.Extensions.Options;
using Microsoft.IdentityModel.Tokens;
using System;
+using System.Globalization;
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using System.Text;
@@ -14,57 +15,6 @@ namespace Timeline.Services public long Version { get; set; }
}
- [Serializable]
- public class JwtTokenVerifyException : 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 JwtTokenVerifyException(int code) : base(GetErrorMessage(code)) { ErrorCode = code; }
- public JwtTokenVerifyException(string message, int code) : base(message) { ErrorCode = code; }
- public JwtTokenVerifyException(Exception inner, int code) : base(GetErrorMessage(code), inner) { ErrorCode = code; }
- public JwtTokenVerifyException(string message, Exception inner, int code) : base(message, inner) { ErrorCode = code; }
- protected JwtTokenVerifyException(
- System.Runtime.Serialization.SerializationInfo info,
- System.Runtime.Serialization.StreamingContext context) : base(info, context) { }
-
- public int ErrorCode { get; private set; }
-
- private static string GetErrorMessage(int errorCode)
- {
- switch (errorCode)
- {
- case ErrorCodes.Others:
- return "Uncommon error, see inner exception for more information.";
- case ErrorCodes.NoIdClaim:
- return "Id claim does not exist.";
- case ErrorCodes.IdClaimBadFormat:
- return "Id claim is not a number.";
- case ErrorCodes.NoVersionClaim:
- return "Version claim does not exist.";
- case ErrorCodes.VersionClaimBadFormat:
- return "Version claim is not a number";
- case ErrorCodes.Expired:
- return "Token is expired.";
- default:
- return "Unknown error code.";
- }
- }
- }
-
public interface IJwtService
{
/// <summary>
@@ -83,7 +33,7 @@ namespace Timeline.Services /// <param name="token">The token string to verify.</param>
/// <returns>Return the saved info in token.</returns>
/// <exception cref="ArgumentNullException">Thrown when <paramref name="token"/> is null.</exception>
- /// <exception cref="JwtTokenVerifyException">Thrown when the token is invalid.</exception>
+ /// <exception cref="JwtVerifyException">Thrown when the token is invalid.</exception>
TokenInfo VerifyJwtToken(string token);
}
@@ -110,8 +60,8 @@ namespace Timeline.Services var config = _jwtConfig.CurrentValue;
var identity = new ClaimsIdentity();
- identity.AddClaim(new Claim(ClaimTypes.NameIdentifier, tokenInfo.Id.ToString(), ClaimValueTypes.Integer64));
- identity.AddClaim(new Claim(VersionClaimType, tokenInfo.Version.ToString(), ClaimValueTypes.Integer64));
+ identity.AddClaim(new Claim(ClaimTypes.NameIdentifier, tokenInfo.Id.ToString(CultureInfo.InvariantCulture.NumberFormat), ClaimValueTypes.Integer64));
+ identity.AddClaim(new Claim(VersionClaimType, tokenInfo.Version.ToString(CultureInfo.InvariantCulture.NumberFormat), ClaimValueTypes.Integer64));
var tokenDescriptor = new SecurityTokenDescriptor()
{
@@ -153,15 +103,15 @@ namespace Timeline.Services var idClaim = principal.FindFirstValue(ClaimTypes.NameIdentifier);
if (idClaim == null)
- throw new JwtTokenVerifyException(JwtTokenVerifyException.ErrorCodes.NoIdClaim);
+ throw new JwtVerifyException(JwtVerifyException.ErrorCodes.NoIdClaim);
if (!long.TryParse(idClaim, out var id))
- throw new JwtTokenVerifyException(JwtTokenVerifyException.ErrorCodes.IdClaimBadFormat);
+ throw new JwtVerifyException(JwtVerifyException.ErrorCodes.IdClaimBadFormat);
var versionClaim = principal.FindFirstValue(VersionClaimType);
if (versionClaim == null)
- throw new JwtTokenVerifyException(JwtTokenVerifyException.ErrorCodes.NoVersionClaim);
+ throw new JwtVerifyException(JwtVerifyException.ErrorCodes.NoVersionClaim);
if (!long.TryParse(versionClaim, out var version))
- throw new JwtTokenVerifyException(JwtTokenVerifyException.ErrorCodes.VersionClaimBadFormat);
+ throw new JwtVerifyException(JwtVerifyException.ErrorCodes.VersionClaimBadFormat);
return new TokenInfo
{
@@ -171,11 +121,11 @@ namespace Timeline.Services }
catch (SecurityTokenExpiredException e)
{
- throw new JwtTokenVerifyException(e, JwtTokenVerifyException.ErrorCodes.Expired);
+ throw new JwtVerifyException(e, JwtVerifyException.ErrorCodes.Expired);
}
catch (Exception e)
{
- throw new JwtTokenVerifyException(e, JwtTokenVerifyException.ErrorCodes.Others);
+ throw new JwtVerifyException(e, JwtVerifyException.ErrorCodes.Others);
}
}
}
diff --git a/Timeline/Services/JwtVerifyException.cs b/Timeline/Services/JwtVerifyException.cs new file mode 100644 index 00000000..a915b51a --- /dev/null +++ b/Timeline/Services/JwtVerifyException.cs @@ -0,0 +1,59 @@ +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/PasswordService.cs b/Timeline/Services/PasswordService.cs index e09a1365..e04a861b 100644 --- a/Timeline/Services/PasswordService.cs +++ b/Timeline/Services/PasswordService.cs @@ -12,13 +12,23 @@ namespace Timeline.Services [Serializable]
public class HashedPasswordBadFromatException : Exception
{
- public HashedPasswordBadFromatException(string hashedPassword, string message) : base(message) { HashedPassword = hashedPassword; }
- public HashedPasswordBadFromatException(string hashedPassword, string message, Exception inner) : base(message, inner) { HashedPassword = hashedPassword; }
+ private static string MakeMessage(string reason)
+ {
+ return Resources.Services.Exception.HashedPasswordBadFromatException + " Reason: " + reason;
+ }
+
+ public HashedPasswordBadFromatException() : base(Resources.Services.Exception.HashedPasswordBadFromatException) { }
+
+ public HashedPasswordBadFromatException(string message) : base(message) { }
+ public HashedPasswordBadFromatException(string message, Exception inner) : base(message, inner) { }
+
+ public HashedPasswordBadFromatException(string hashedPassword, string reason) : base(MakeMessage(reason)) { HashedPassword = hashedPassword; }
+ public HashedPasswordBadFromatException(string hashedPassword, string reason, Exception inner) : base(MakeMessage(reason), inner) { HashedPassword = hashedPassword; }
protected HashedPasswordBadFromatException(
System.Runtime.Serialization.SerializationInfo info,
System.Runtime.Serialization.StreamingContext context) : base(info, context) { }
- public string HashedPassword { get; private set; }
+ public string? HashedPassword { get; set; }
}
public interface IPasswordService
@@ -140,22 +150,20 @@ namespace Timeline.Services }
catch (FormatException e)
{
- throw new HashedPasswordBadFromatException(hashedPassword, "Not of valid base64 format. See inner exception.", e);
+ throw new HashedPasswordBadFromatException(hashedPassword, Resources.Services.Exception.HashedPasswordBadFromatExceptionNotBase64, e);
}
// read the format marker from the hashed password
if (decodedHashedPassword.Length == 0)
{
- throw new HashedPasswordBadFromatException(hashedPassword, "Decoded hashed password is of length 0.");
+ throw new HashedPasswordBadFromatException(hashedPassword, Resources.Services.Exception.HashedPasswordBadFromatExceptionNotLength0);
}
- switch (decodedHashedPassword[0])
- {
- case 0x01:
- return VerifyHashedPasswordV3(decodedHashedPassword, providedPassword, hashedPassword);
- default:
- throw new HashedPasswordBadFromatException(hashedPassword, "Unknown format marker.");
- }
+ return (decodedHashedPassword[0]) switch
+ {
+ 0x01 => VerifyHashedPasswordV3(decodedHashedPassword, providedPassword, hashedPassword),
+ _ => throw new HashedPasswordBadFromatException(hashedPassword, Resources.Services.Exception.HashedPasswordBadFromatExceptionNotUnknownMarker),
+ };
}
private bool VerifyHashedPasswordV3(byte[] hashedPassword, string password, string hashedPasswordString)
@@ -170,7 +178,7 @@ namespace Timeline.Services // Read the salt: must be >= 128 bits
if (saltLength < 128 / 8)
{
- throw new HashedPasswordBadFromatException(hashedPasswordString, "Salt length < 128 bits.");
+ throw new HashedPasswordBadFromatException(hashedPasswordString, Resources.Services.Exception.HashedPasswordBadFromatExceptionNotSaltTooShort);
}
byte[] salt = new byte[saltLength];
Buffer.BlockCopy(hashedPassword, 13, salt, 0, salt.Length);
@@ -179,7 +187,7 @@ namespace Timeline.Services int subkeyLength = hashedPassword.Length - 13 - salt.Length;
if (subkeyLength < 128 / 8)
{
- throw new HashedPasswordBadFromatException(hashedPasswordString, "Subkey length < 128 bits.");
+ throw new HashedPasswordBadFromatException(hashedPasswordString, Resources.Services.Exception.HashedPasswordBadFromatExceptionNotSubkeyTooShort);
}
byte[] expectedSubkey = new byte[subkeyLength];
Buffer.BlockCopy(hashedPassword, 13 + salt.Length, expectedSubkey, 0, expectedSubkey.Length);
@@ -193,7 +201,7 @@ namespace Timeline.Services // This should never occur except in the case of a malformed payload, where
// we might go off the end of the array. Regardless, a malformed payload
// implies verification failed.
- throw new HashedPasswordBadFromatException(hashedPasswordString, "See inner exception.", e);
+ throw new HashedPasswordBadFromatException(hashedPasswordString, Resources.Services.Exception.HashedPasswordBadFromatExceptionNotOthers, e);
}
}
diff --git a/Timeline/Services/UserAvatarService.cs b/Timeline/Services/UserAvatarService.cs index ecec5a31..ff80003c 100644 --- a/Timeline/Services/UserAvatarService.cs +++ b/Timeline/Services/UserAvatarService.cs @@ -10,54 +10,25 @@ using System.IO; using System.Linq;
using System.Threading.Tasks;
using Timeline.Entities;
+using Timeline.Helpers;
+using Timeline.Models.Validation;
namespace Timeline.Services
{
public class Avatar
{
- public string Type { get; set; }
- public byte[] Data { get; set; }
+ public string Type { get; set; } = default!;
+ [System.Diagnostics.CodeAnalysis.SuppressMessage("Performance", "CA1819:Properties should not return arrays", Justification = "DTO Object")]
+ public byte[] Data { get; set; } = default!;
}
public class AvatarInfo
{
- public Avatar Avatar { get; set; }
+ public Avatar Avatar { get; set; } = default!;
public DateTime LastModified { get; set; }
}
/// <summary>
- /// Thrown when avatar is of bad format.
- /// </summary>
- [Serializable]
- public class AvatarDataException : Exception
- {
- public enum ErrorReason
- {
- /// <summary>
- /// Decoding image failed.
- /// </summary>
- CantDecode,
- /// <summary>
- /// Decoding succeeded but the real type is not the specified type.
- /// </summary>
- UnmatchedFormat,
- /// <summary>
- /// Image is not a square.
- /// </summary>
- BadSize
- }
-
- public AvatarDataException(Avatar avatar, ErrorReason error, string message) : base(message) { Avatar = avatar; Error = error; }
- public AvatarDataException(Avatar avatar, ErrorReason error, string message, Exception inner) : base(message, inner) { Avatar = avatar; Error = error; }
- protected AvatarDataException(
- System.Runtime.Serialization.SerializationInfo info,
- System.Runtime.Serialization.StreamingContext context) : base(info, context) { }
-
- public ErrorReason Error { get; set; }
- public Avatar Avatar { get; set; }
- }
-
- /// <summary>
/// Provider for default user avatar.
/// </summary>
/// <remarks>
@@ -83,7 +54,7 @@ namespace Timeline.Services /// Validate a avatar's format and size info.
/// </summary>
/// <param name="avatar">The avatar to validate.</param>
- /// <exception cref="AvatarDataException">Thrown when validation failed.</exception>
+ /// <exception cref="AvatarFormatException">Thrown when validation failed.</exception>
Task Validate(Avatar avatar);
}
@@ -94,16 +65,18 @@ namespace Timeline.Services /// </summary>
/// <param name="username">The username of the user to get avatar etag of.</param>
/// <returns>The etag.</returns>
- /// <exception cref="ArgumentException">Thrown if <paramref name="username"/> is null or empty.</exception>
+ /// <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);
/// <summary>
- /// Get avatar of a user. If the user has no avatar, a default one is returned.
+ /// Get avatar of a user. If the user has no avatar set, a default one is returned.
/// </summary>
/// <param name="username">The username of the user to get avatar of.</param>
/// <returns>The avatar info.</returns>
- /// <exception cref="ArgumentException">Thrown if <paramref name="username"/> is null or empty.</exception>
+ /// <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);
@@ -112,38 +85,41 @@ namespace Timeline.Services /// </summary>
/// <param name="username">The username of the user to set avatar for.</param>
/// <param name="avatar">The avatar. Can be null to delete the saved avatar.</param>
- /// <exception cref="ArgumentException">Throw if <paramref name="username"/> is null or empty.
- /// Or thrown if <paramref name="avatar"/> is not null but <see cref="Avatar.Type"/> is null or empty or <see cref="Avatar.Data"/> is null.</exception>
+ /// <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="AvatarDataException">Thrown if avatar is of bad format.</exception>
- Task SetAvatar(string username, Avatar avatar);
+ /// <exception cref="AvatarFormatException">Thrown if avatar is of bad format.</exception>
+ Task SetAvatar(string username, Avatar? avatar);
}
+ // TODO! : Make this configurable.
public class DefaultUserAvatarProvider : IDefaultUserAvatarProvider
{
- private readonly IWebHostEnvironment _environment;
-
private readonly IETagGenerator _eTagGenerator;
- private byte[] _cacheData;
+ private readonly string _avatarPath;
+
+ private byte[] _cacheData = default!;
private DateTime _cacheLastModified;
- private string _cacheETag;
+ private string _cacheETag = default!;
+ [System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1062:Validate arguments of public methods", Justification = "DI.")]
public DefaultUserAvatarProvider(IWebHostEnvironment environment, IETagGenerator eTagGenerator)
{
- _environment = environment;
+ _avatarPath = Path.Combine(environment.ContentRootPath, "default-avatar.png");
_eTagGenerator = eTagGenerator;
}
private async Task CheckAndInit()
{
- if (_cacheData != null)
- return;
-
- var path = Path.Combine(_environment.ContentRootPath, "default-avatar.png");
- _cacheData = await File.ReadAllBytesAsync(path);
- _cacheLastModified = File.GetLastWriteTime(path);
- _cacheETag = _eTagGenerator.Generate(_cacheData);
+ var path = _avatarPath;
+ if (_cacheData == null || File.GetLastWriteTime(path) > _cacheLastModified)
+ {
+ _cacheData = await File.ReadAllBytesAsync(path);
+ _cacheLastModified = File.GetLastWriteTime(path);
+ _cacheETag = await _eTagGenerator.Generate(_cacheData);
+ }
}
public async Task<string> GetDefaultAvatarETag()
@@ -175,17 +151,15 @@ namespace Timeline.Services {
try
{
- using (var image = Image.Load(avatar.Data, out IImageFormat format))
- {
- if (!format.MimeTypes.Contains(avatar.Type))
- throw new AvatarDataException(avatar, AvatarDataException.ErrorReason.UnmatchedFormat, "Image's actual mime type is not the specified one.");
- if (image.Width != image.Height)
- throw new AvatarDataException(avatar, AvatarDataException.ErrorReason.BadSize, "Image is not a square, aka, width is not equal to height.");
- }
+ using var image = Image.Load(avatar.Data, out IImageFormat format);
+ if (!format.MimeTypes.Contains(avatar.Type))
+ throw new AvatarFormatException(avatar, AvatarFormatException.ErrorReason.UnmatchedFormat);
+ if (image.Width != image.Height)
+ throw new AvatarFormatException(avatar, AvatarFormatException.ErrorReason.BadSize);
}
catch (UnknownImageFormatException e)
{
- throw new AvatarDataException(avatar, AvatarDataException.ErrorReason.CantDecode, "Failed to decode image. See inner exception.", e);
+ throw new AvatarFormatException(avatar, AvatarFormatException.ErrorReason.CantDecode, e);
}
});
}
@@ -203,25 +177,32 @@ namespace Timeline.Services private readonly IETagGenerator _eTagGenerator;
+ private readonly UsernameValidator _usernameValidator;
+
+ private readonly IClock _clock;
+
public UserAvatarService(
ILogger<UserAvatarService> logger,
DatabaseContext database,
IDefaultUserAvatarProvider defaultUserAvatarProvider,
IUserAvatarValidator avatarValidator,
- IETagGenerator eTagGenerator)
+ IETagGenerator eTagGenerator,
+ IClock clock)
{
_logger = logger;
_database = database;
_defaultUserAvatarProvider = defaultUserAvatarProvider;
_avatarValidator = avatarValidator;
_eTagGenerator = eTagGenerator;
+ _usernameValidator = new UsernameValidator();
+ _clock = clock;
}
public async Task<string> GetAvatarETag(string username)
{
- var userId = await DatabaseExtensions.CheckAndGetUser(_database.Users, username);
+ var userId = await DatabaseExtensions.CheckAndGetUser(_database.Users, _usernameValidator, username);
- var eTag = (await _database.UserAvatars.Where(a => a.UserId == userId).Select(a => new { a.ETag }).SingleAsync()).ETag;
+ var eTag = (await _database.UserAvatars.Where(a => a.UserId == userId).Select(a => new { a.ETag }).SingleOrDefaultAsync())?.ETag;
if (eTag == null)
return await _defaultUserAvatarProvider.GetDefaultAvatarETag();
else
@@ -230,73 +211,88 @@ namespace Timeline.Services public async Task<AvatarInfo> GetAvatar(string username)
{
- var userId = await DatabaseExtensions.CheckAndGetUser(_database.Users, username);
+ var userId = await DatabaseExtensions.CheckAndGetUser(_database.Users, _usernameValidator, username);
- var avatar = await _database.UserAvatars.Where(a => a.UserId == userId).Select(a => new { a.Type, a.Data, a.LastModified }).SingleAsync();
+ var avatarEntity = await _database.UserAvatars.Where(a => a.UserId == userId).Select(a => new { a.Type, a.Data, a.LastModified }).SingleOrDefaultAsync();
- if ((avatar.Type == null) != (avatar.Data == null))
+ if (avatarEntity != null)
{
- _logger.LogCritical("Database corupted! One of type and data of a avatar is null but the other is not.");
- throw new DatabaseCorruptedException();
- }
+ if (!LanguageHelper.AreSame(avatarEntity.Data == null, avatarEntity.Type == null))
+ {
+ var message = Resources.Services.UserAvatarService.DatabaseCorruptedDataAndTypeNotSame;
+ _logger.LogCritical(message);
+ throw new DatabaseCorruptedException(message);
+ }
- if (avatar.Data == null)
- {
- var defaultAvatar = await _defaultUserAvatarProvider.GetDefaultAvatar();
- defaultAvatar.LastModified = defaultAvatar.LastModified > avatar.LastModified ? defaultAvatar.LastModified : avatar.LastModified;
- return defaultAvatar;
- }
- else
- {
- return new AvatarInfo
+ if (avatarEntity.Data != null)
{
- Avatar = new Avatar
+ return new AvatarInfo
{
- Type = avatar.Type,
- Data = avatar.Data
- },
- LastModified = avatar.LastModified
- };
+ Avatar = new Avatar
+ {
+ Type = avatarEntity.Type!,
+ Data = avatarEntity.Data
+ },
+ LastModified = avatarEntity.LastModified
+ };
+ }
}
+ var defaultAvatar = await _defaultUserAvatarProvider.GetDefaultAvatar();
+ if (avatarEntity != null)
+ defaultAvatar.LastModified = defaultAvatar.LastModified > avatarEntity.LastModified ? defaultAvatar.LastModified : avatarEntity.LastModified;
+ return defaultAvatar;
}
- public async Task SetAvatar(string username, Avatar avatar)
+ public async Task SetAvatar(string username, Avatar? avatar)
{
if (avatar != null)
{
- if (string.IsNullOrEmpty(avatar.Type))
- throw new ArgumentException("Type of avatar is null or empty.", nameof(avatar));
if (avatar.Data == null)
- throw new ArgumentException("Data of avatar is null.", nameof(avatar));
+ throw new ArgumentException(Resources.Services.UserAvatarService.ArgumentAvatarDataNull, nameof(avatar));
+ if (string.IsNullOrEmpty(avatar.Type))
+ throw new ArgumentException(Resources.Services.UserAvatarService.ArgumentAvatarTypeNullOrEmpty, nameof(avatar));
}
- var userId = await DatabaseExtensions.CheckAndGetUser(_database.Users, username);
-
- var avatarEntity = await _database.UserAvatars.Where(a => a.UserId == userId).SingleAsync();
+ var userId = await DatabaseExtensions.CheckAndGetUser(_database.Users, _usernameValidator, username);
+ var avatarEntity = await _database.UserAvatars.Where(a => a.UserId == userId).SingleOrDefaultAsync();
if (avatar == null)
{
- if (avatarEntity.Data == null)
+ if (avatarEntity == null || avatarEntity.Data == null)
+ {
return;
+ }
else
{
avatarEntity.Data = null;
avatarEntity.Type = null;
avatarEntity.ETag = null;
- avatarEntity.LastModified = DateTime.Now;
+ avatarEntity.LastModified = _clock.GetCurrentTime();
await _database.SaveChangesAsync();
- _logger.LogInformation("Updated an entry in user_avatars.");
+ _logger.LogInformation(Resources.Services.UserAvatarService.LogUpdateEntity);
}
}
else
{
await _avatarValidator.Validate(avatar);
- avatarEntity.Type = avatar.Type;
+ var create = avatarEntity == null;
+ if (create)
+ {
+ avatarEntity = new UserAvatar();
+ }
+ avatarEntity!.Type = avatar.Type;
avatarEntity.Data = avatar.Data;
- avatarEntity.ETag = _eTagGenerator.Generate(avatar.Data);
- avatarEntity.LastModified = DateTime.Now;
+ avatarEntity.ETag = await _eTagGenerator.Generate(avatar.Data);
+ avatarEntity.LastModified = _clock.GetCurrentTime();
+ avatarEntity.UserId = userId;
+ if (create)
+ {
+ _database.UserAvatars.Add(avatarEntity);
+ }
await _database.SaveChangesAsync();
- _logger.LogInformation("Updated an entry in user_avatars.");
+ _logger.LogInformation(create ?
+ Resources.Services.UserAvatarService.LogCreateEntity
+ : Resources.Services.UserAvatarService.LogUpdateEntity);
}
}
}
@@ -308,7 +304,7 @@ namespace Timeline.Services services.TryAddTransient<IETagGenerator, ETagGenerator>();
services.AddScoped<IUserAvatarService, UserAvatarService>();
services.AddSingleton<IDefaultUserAvatarProvider, DefaultUserAvatarProvider>();
- services.AddSingleton<IUserAvatarValidator, UserAvatarValidator>();
+ services.AddTransient<IUserAvatarValidator, UserAvatarValidator>();
}
}
}
diff --git a/Timeline/Services/UserDetailService.cs b/Timeline/Services/UserDetailService.cs deleted file mode 100644 index 5e049435..00000000 --- a/Timeline/Services/UserDetailService.cs +++ /dev/null @@ -1,135 +0,0 @@ -using Microsoft.EntityFrameworkCore;
-using Microsoft.Extensions.DependencyInjection;
-using Microsoft.Extensions.Logging;
-using System;
-using System.Linq;
-using System.Threading.Tasks;
-using Timeline.Entities;
-using Timeline.Models;
-
-namespace Timeline.Services
-{
- public interface IUserDetailService
- {
- /// <summary>
- /// Get the nickname of user.
- /// </summary>
- /// <param name="username">The username to get nickname of.</param>
- /// <returns>The user's nickname. Null if not set.</returns>
- /// <exception cref="ArgumentException">Thrown if <paramref name="username"/> is null or empty.</exception>
- /// <exception cref="UserNotExistException">Thrown if user doesn't exist.</exception>
- Task<string> GetUserNickname(string username);
-
- /// <summary>
- /// Get the detail of user.
- /// </summary>
- /// <param name="username">The username to get user detail of.</param>
- /// <returns>The user detail.</returns>
- /// <exception cref="ArgumentException">Thrown if <paramref name="username"/> is null or empty.</exception>
- /// <exception cref="UserNotExistException">Thrown if user doesn't exist.</exception>
- Task<UserDetail> GetUserDetail(string username);
-
- /// <summary>
- /// Update the detail of user. This function does not do data check.
- /// </summary>
- /// <param name="username">The username to get user detail of.</param>
- /// <param name="detail">The detail to update. Can't be null. Any null member means not set.</param>
- /// <exception cref="ArgumentException">Thrown if <paramref name="username"/> is null or empty or <paramref name="detail"/> is null.</exception>
- /// <exception cref="UserNotExistException">Thrown if user doesn't exist.</exception>
- Task UpdateUserDetail(string username, UserDetail detail);
- }
-
- public class UserDetailService : IUserDetailService
- {
- private readonly ILogger<UserDetailService> _logger;
-
- private readonly DatabaseContext _databaseContext;
-
- public UserDetailService(ILogger<UserDetailService> logger, DatabaseContext databaseContext)
- {
- _logger = logger;
- _databaseContext = databaseContext;
- }
-
- private async Task<UserDetailEntity> CreateEntity(long userId)
- {
- var entity = new UserDetailEntity()
- {
- UserId = userId
- };
- _databaseContext.UserDetails.Add(entity);
- await _databaseContext.SaveChangesAsync();
- _logger.LogInformation("An entity is created in user_details.");
- return entity;
- }
-
- // Check the existence of user detail entry
- private async Task<UserDetailEntity> CheckAndInit(long userId)
- {
- var detail = await _databaseContext.UserDetails.Where(e => e.UserId == userId).SingleOrDefaultAsync();
- if (detail == null)
- {
- detail = await CreateEntity(userId);
- }
- return detail;
- }
-
- public async Task<string> GetUserNickname(string username)
- {
- var userId = await DatabaseExtensions.CheckAndGetUser(_databaseContext.Users, username);
- var detail = await _databaseContext.UserDetails.Where(e => e.UserId == userId).Select(e => new { e.Nickname }).SingleOrDefaultAsync();
- if (detail == null)
- {
- var entity = await CreateEntity(userId);
- return null;
- }
- else
- {
- var nickname = detail.Nickname;
- return string.IsNullOrEmpty(nickname) ? null : nickname;
- }
- }
-
- public async Task<UserDetail> GetUserDetail(string username)
- {
- var userId = await DatabaseExtensions.CheckAndGetUser(_databaseContext.Users, username);
- var detailEntity = await CheckAndInit(userId);
- return UserDetail.From(detailEntity);
- }
-
- public async Task UpdateUserDetail(string username, UserDetail detail)
- {
- if (detail == null)
- throw new ArgumentNullException(nameof(detail));
-
- var userId = await DatabaseExtensions.CheckAndGetUser(_databaseContext.Users, username);
- var detailEntity = await CheckAndInit(userId);
-
- if (detail.Nickname != null)
- detailEntity.Nickname = detail.Nickname;
-
- if (detail.QQ != null)
- detailEntity.QQ = detail.QQ;
-
- if (detail.Email != null)
- detailEntity.Email = detail.Email;
-
- if (detail.PhoneNumber != null)
- detailEntity.PhoneNumber = detail.PhoneNumber;
-
- if (detail.Description != null)
- detailEntity.Description = detail.Description;
-
- await _databaseContext.SaveChangesAsync();
- _logger.LogInformation("An entity is updated in user_details.");
- }
- }
-
- public static class UserDetailServiceCollectionExtensions
- {
- public static void AddUserDetailService(this IServiceCollection services)
- {
- services.AddScoped<IUserDetailService, UserDetailService>();
- }
- }
-}
diff --git a/Timeline/Services/UserNotExistException.cs b/Timeline/Services/UserNotExistException.cs new file mode 100644 index 00000000..c7317f56 --- /dev/null +++ b/Timeline/Services/UserNotExistException.cs @@ -0,0 +1,41 @@ +using System;
+using Timeline.Helpers;
+
+namespace Timeline.Services
+{
+ /// <summary>
+ /// The user requested does not exist.
+ /// </summary>
+ [Serializable]
+ public class UserNotExistException : Exception
+ {
+ public UserNotExistException() : base(Resources.Services.Exception.UserNotExistException) { }
+ public UserNotExistException(string message, Exception inner) : base(message, inner) { }
+
+ public UserNotExistException(string username)
+ : base(Log.Format(Resources.Services.Exception.UserNotExistException, ("Username", username)))
+ {
+ Username = username;
+ }
+
+ public UserNotExistException(long id)
+ : base(Log.Format(Resources.Services.Exception.UserNotExistException, ("Id", id)))
+ {
+ Id = id;
+ }
+
+ protected UserNotExistException(
+ System.Runtime.Serialization.SerializationInfo info,
+ System.Runtime.Serialization.StreamingContext context) : base(info, context) { }
+
+ /// <summary>
+ /// The username of the user that does not exist.
+ /// </summary>
+ public string? Username { get; set; }
+
+ /// <summary>
+ /// The id of the user that does not exist.
+ /// </summary>
+ public long? Id { get; set; }
+ }
+}
diff --git a/Timeline/Services/UserService.cs b/Timeline/Services/UserService.cs index 347b8cbb..8f354fc7 100644 --- a/Timeline/Services/UserService.cs +++ b/Timeline/Services/UserService.cs @@ -5,140 +5,16 @@ using System; using System.Linq;
using System.Threading.Tasks;
using Timeline.Entities;
+using Timeline.Helpers;
using Timeline.Models;
using Timeline.Models.Validation;
-using static Timeline.Helpers.MyLogHelper;
-using static Timeline.Models.UserUtility;
namespace Timeline.Services
{
public class CreateTokenResult
{
- public string Token { get; set; }
- public UserInfo User { get; set; }
- }
-
- [Serializable]
- public class UserNotExistException : Exception
- {
- private const string message = "The user does not exist.";
-
- public UserNotExistException(string username)
- : base(FormatLogMessage(message, Pair("Username", username)))
- {
- Username = username;
- }
-
- public UserNotExistException(long id)
- : base(FormatLogMessage(message, Pair("Id", id)))
- {
- Id = id;
- }
-
- public UserNotExistException(string message, Exception inner) : base(message, inner) { }
-
- protected UserNotExistException(
- System.Runtime.Serialization.SerializationInfo info,
- System.Runtime.Serialization.StreamingContext context) : base(info, context) { }
-
- /// <summary>
- /// The username that does not exist. May be null then <see cref="Id"/> is not null.
- /// </summary>
- public string Username { get; private set; }
-
- /// <summary>
- /// The id that does not exist. May be null then <see cref="Username"/> is not null.
- /// </summary>
- public long? Id { get; private set; }
- }
-
- [Serializable]
- public class BadPasswordException : Exception
- {
- public BadPasswordException(string badPassword)
- : base(FormatLogMessage("Password is wrong.", Pair("Bad Password", badPassword)))
- {
- Password = badPassword;
- }
-
- public BadPasswordException(string message, Exception inner) : base(message, inner) { }
-
- protected BadPasswordException(
- System.Runtime.Serialization.SerializationInfo info,
- System.Runtime.Serialization.StreamingContext context) : base(info, context) { }
-
- /// <summary>
- /// The wrong password.
- /// </summary>
- public string Password { get; private set; }
- }
-
-
- [Serializable]
- public class BadTokenVersionException : Exception
- {
- public BadTokenVersionException(long tokenVersion, long requiredVersion)
- : base(FormatLogMessage("Token version is expired.",
- Pair("Token Version", tokenVersion),
- Pair("Required Version", requiredVersion)))
- {
- TokenVersion = tokenVersion;
- RequiredVersion = requiredVersion;
- }
-
- public BadTokenVersionException(string message, Exception inner) : base(message, inner) { }
-
- protected BadTokenVersionException(
- System.Runtime.Serialization.SerializationInfo info,
- System.Runtime.Serialization.StreamingContext context) : base(info, context) { }
-
- /// <summary>
- /// The version in the token.
- /// </summary>
- public long TokenVersion { get; private set; }
-
- /// <summary>
- /// The version required.
- /// </summary>
- public long RequiredVersion { get; private set; }
- }
-
- /// <summary>
- /// Thrown when username is of bad format.
- /// </summary>
- [Serializable]
- public class UsernameBadFormatException : Exception
- {
- 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; }
- }
-
-
- /// <summary>
- /// Thrown when the user already exists.
- /// </summary>
- [Serializable]
- public class UserAlreadyExistException : Exception
- {
- public UserAlreadyExistException(string username) : base($"User {username} already exists.") { Username = username; }
- public UserAlreadyExistException(string username, string message) : base(message) { Username = username; }
- public UserAlreadyExistException(string message, Exception inner) : base(message, inner) { }
- protected UserAlreadyExistException(
- 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; }
+ public string Token { get; set; } = default!;
+ public UserInfo User { get; set; } = default!;
}
public interface IUserService
@@ -152,6 +28,7 @@ namespace Timeline.Services /// <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>
/// <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="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);
@@ -163,9 +40,8 @@ namespace Timeline.Services /// <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="JwtTokenVerifyException">Thrown when the token is of bad format. Thrown by <see cref="JwtService.VerifyJwtToken(string)"/>.</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>
- /// <exception cref="BadTokenVersionException">Thrown when the version in the token is expired. User needs to recreate the token.</exception>
Task<UserInfo> VerifyToken(string token);
/// <summary>
@@ -173,6 +49,8 @@ namespace Timeline.Services /// </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>
+ /// <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);
/// <summary>
@@ -188,10 +66,12 @@ namespace Timeline.Services /// <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.Created"/> if a new user is created.
- /// Return <see cref="PutResult.Modified"/> if a existing user is modified.</returns>
- /// <exception cref="UsernameBadFormatException">Thrown when <paramref name="username"/> is of bad format.</exception>
+ /// <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);
/// <summary>
@@ -203,14 +83,16 @@ namespace Timeline.Services /// <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);
+ Task PatchUser(string username, string? password, bool? administrator);
/// <summary>
/// Delete a user of given username.
/// </summary>
/// <param name="username">Username of thet user to delete. Can't be null.</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 DeleteUser(string username);
@@ -221,6 +103,7 @@ namespace Timeline.Services /// <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>
/// <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);
@@ -230,16 +113,16 @@ namespace Timeline.Services /// </summary>
/// <param name="oldUsername">The user's old username.</param>
/// <param name="newUsername">The new username.</param>
- /// <exception cref="ArgumentException">Thrown if <paramref name="oldUsername"/> or <paramref name="newUsername"/> is null or empty.</exception>
+ /// <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 new username is not accepted because of bad format.</exception>
- /// <exception cref="UserAlreadyExistException">Thrown if user with the new username already exists.</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; }
+ public string Username { get; set; } = default!;
public bool Administrator { get; set; }
public long Version { get; set; }
@@ -272,13 +155,25 @@ namespace Timeline.Services _usernameValidator = new UsernameValidator();
}
- private string GenerateCacheKeyByUserId(long id) => $"user:{id}";
+ private static string GenerateCacheKeyByUserId(long id) => $"user:{id}";
private void RemoveCache(long id)
{
var key = GenerateCacheKeyByUserId(id);
_memoryCache.Remove(key);
- _logger.LogInformation(FormatLogMessage("A cache entry is removed.", Pair("Key", key)));
+ _logger.LogInformation(Log.Format(Resources.Services.UserService.LogCacheRemove, ("Key", key)));
+ }
+
+ private void CheckUsernameFormat(string username, string? message = null)
+ {
+ var (result, messageGenerator) = _usernameValidator.Validate(username);
+ if (!result)
+ {
+ if (message == null)
+ throw new UsernameBadFormatException(username, messageGenerator(null));
+ else
+ throw new UsernameBadFormatException(username, message + messageGenerator(null));
+ }
}
public async Task<CreateTokenResult> CreateToken(string username, string password, DateTime? expires)
@@ -287,6 +182,7 @@ namespace Timeline.Services 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();
@@ -306,7 +202,7 @@ namespace Timeline.Services return new CreateTokenResult
{
Token = token,
- User = CreateUserInfo(user)
+ User = UserConvert.CreateUserInfo(user)
};
}
@@ -329,29 +225,33 @@ namespace Timeline.Services throw new UserNotExistException(id);
// create cache
- cache = CreateUserCache(user);
+ cache = UserConvert.CreateUserCache(user);
_memoryCache.CreateEntry(key).SetValue(cache);
- _logger.LogInformation(FormatLogMessage("A cache entry is created.", Pair("Key", key)));
+ _logger.LogInformation(Log.Format(Resources.Services.UserService.LogCacheCreate, ("Key", key)));
}
if (tokenInfo.Version != cache.Version)
- throw new BadTokenVersionException(tokenInfo.Version, cache.Version);
+ throw new JwtVerifyException(new JwtBadVersionException(tokenInfo.Version, cache.Version), JwtVerifyException.ErrorCodes.OldVersion);
return cache.ToUserInfo();
}
public async Task<UserInfo> GetUser(string username)
{
+ if (username == null)
+ throw new ArgumentNullException(nameof(username));
+ CheckUsernameFormat(username);
+
return await _databaseContext.Users
.Where(user => user.Name == username)
- .Select(user => CreateUserInfo(user))
+ .Select(user => UserConvert.CreateUserInfo(user))
.SingleOrDefaultAsync();
}
public async Task<UserInfo[]> ListUsers()
{
return await _databaseContext.Users
- .Select(user => CreateUserInfo(user))
+ .Select(user => UserConvert.CreateUserInfo(user))
.ToArrayAsync();
}
@@ -361,11 +261,7 @@ namespace Timeline.Services throw new ArgumentNullException(nameof(username));
if (password == null)
throw new ArgumentNullException(nameof(password));
-
- if (!_usernameValidator.Validate(username, out var message))
- {
- throw new UsernameBadFormatException(username, message);
- }
+ CheckUsernameFormat(username);
var user = await _databaseContext.Users.Where(u => u.Name == username).SingleOrDefaultAsync();
@@ -375,31 +271,34 @@ namespace Timeline.Services {
Name = username,
EncryptedPassword = _passwordService.HashPassword(password),
- RoleString = IsAdminToRoleString(administrator),
- Avatar = UserAvatar.Create(DateTime.Now)
+ RoleString = UserRoleConvert.ToString(administrator),
+ Avatar = null
};
await _databaseContext.AddAsync(newUser);
await _databaseContext.SaveChangesAsync();
- _logger.LogInformation(FormatLogMessage("A new user entry is added to the database.", Pair("Id", newUser.Id)));
- return PutResult.Created;
+ _logger.LogInformation(Log.Format(Resources.Services.UserService.LogDatabaseCreate,
+ ("Id", newUser.Id), ("Username", username), ("Administrator", administrator)));
+ return PutResult.Create;
}
user.EncryptedPassword = _passwordService.HashPassword(password);
- user.RoleString = IsAdminToRoleString(administrator);
+ user.RoleString = UserRoleConvert.ToString(administrator);
user.Version += 1;
await _databaseContext.SaveChangesAsync();
- _logger.LogInformation(FormatLogMessage("A user entry is updated to the database.", Pair("Id", user.Id)));
+ _logger.LogInformation(Log.Format(Resources.Services.UserService.LogDatabaseUpdate,
+ ("Id", user.Id), ("Username", username), ("Administrator", administrator)));
//clear cache
RemoveCache(user.Id);
- return PutResult.Modified;
+ return PutResult.Modify;
}
- public async Task PatchUser(string username, string password, bool? administrator)
+ public async Task PatchUser(string username, string? password, bool? administrator)
{
if (username == null)
throw new ArgumentNullException(nameof(username));
+ CheckUsernameFormat(username);
var user = await _databaseContext.Users.Where(u => u.Name == username).SingleOrDefaultAsync();
if (user == null)
@@ -412,12 +311,12 @@ namespace Timeline.Services if (administrator != null)
{
- user.RoleString = IsAdminToRoleString(administrator.Value);
+ user.RoleString = UserRoleConvert.ToString(administrator.Value);
}
user.Version += 1;
await _databaseContext.SaveChangesAsync();
- _logger.LogInformation(FormatLogMessage("A user entry is updated to the database.", Pair("Id", user.Id)));
+ _logger.LogInformation(Resources.Services.UserService.LogDatabaseUpdate, ("Id", user.Id));
//clear cache
RemoveCache(user.Id);
@@ -427,6 +326,7 @@ namespace Timeline.Services {
if (username == null)
throw new ArgumentNullException(nameof(username));
+ CheckUsernameFormat(username);
var user = await _databaseContext.Users.Where(u => u.Name == username).SingleOrDefaultAsync();
if (user == null)
@@ -434,7 +334,8 @@ namespace Timeline.Services _databaseContext.Users.Remove(user);
await _databaseContext.SaveChangesAsync();
- _logger.LogInformation(FormatLogMessage("A user entry is removed from the database.", Pair("Id", user.Id)));
+ _logger.LogInformation(Log.Format(Resources.Services.UserService.LogDatabaseRemove,
+ ("Id", user.Id)));
//clear cache
RemoveCache(user.Id);
@@ -448,6 +349,7 @@ namespace Timeline.Services throw new ArgumentNullException(nameof(oldPassword));
if (newPassword == null)
throw new ArgumentNullException(nameof(newPassword));
+ CheckUsernameFormat(username);
var user = await _databaseContext.Users.Where(u => u.Name == username).SingleOrDefaultAsync();
if (user == null)
@@ -460,19 +362,20 @@ namespace Timeline.Services user.EncryptedPassword = _passwordService.HashPassword(newPassword);
user.Version += 1;
await _databaseContext.SaveChangesAsync();
+ _logger.LogInformation(Log.Format(Resources.Services.UserService.LogDatabaseUpdate,
+ ("Id", user.Id), ("Operation", "Change password")));
//clear cache
RemoveCache(user.Id);
}
public async Task ChangeUsername(string oldUsername, string newUsername)
{
- if (string.IsNullOrEmpty(oldUsername))
- throw new ArgumentException("Old username is null or empty", nameof(oldUsername));
- if (string.IsNullOrEmpty(newUsername))
- throw new ArgumentException("New username is null or empty", nameof(newUsername));
-
- if (!_usernameValidator.Validate(newUsername, out var message))
- throw new UsernameBadFormatException(newUsername, $"New username is of bad format. {message}");
+ 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)
@@ -480,13 +383,13 @@ namespace Timeline.Services var conflictUser = await _databaseContext.Users.Where(u => u.Name == newUsername).SingleOrDefaultAsync();
if (conflictUser != null)
- throw new UserAlreadyExistException(newUsername);
+ throw new UsernameConfictException(newUsername);
user.Name = newUsername;
user.Version += 1;
await _databaseContext.SaveChangesAsync();
- _logger.LogInformation(FormatLogMessage("A user entry changed name field.",
- Pair("Id", user.Id), Pair("Old Username", oldUsername), Pair("New Username", newUsername)));
+ _logger.LogInformation(Log.Format(Resources.Services.UserService.LogDatabaseUpdate,
+ ("Id", user.Id), ("Old Username", oldUsername), ("New Username", newUsername)));
RemoveCache(user.Id);
}
}
diff --git a/Timeline/Services/UsernameBadFormatException.cs b/Timeline/Services/UsernameBadFormatException.cs new file mode 100644 index 00000000..04354d22 --- /dev/null +++ b/Timeline/Services/UsernameBadFormatException.cs @@ -0,0 +1,27 @@ +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 message) : base(message) { }
+ public UsernameBadFormatException(string message, Exception inner) : base(message, inner) { }
+
+ 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 new file mode 100644 index 00000000..fde1eda6 --- /dev/null +++ b/Timeline/Services/UsernameConfictException.cs @@ -0,0 +1,25 @@ +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 8e8a6393..b44add6f 100644 --- a/Timeline/Startup.cs +++ b/Timeline/Startup.cs @@ -2,10 +2,13 @@ using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Cors.Infrastructure;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.HttpOverrides;
+using Microsoft.AspNetCore.Localization;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
-using Timeline.Authenticate;
+using System.Collections.Generic;
+using System.Globalization;
+using Timeline.Authentication;
using Timeline.Configs;
using Timeline.Entities;
using Timeline.Helpers;
@@ -13,6 +16,7 @@ using Timeline.Services; namespace Timeline
{
+ [System.Diagnostics.CodeAnalysis.SuppressMessage("Performance", "CA1822:Mark members as static")]
public class Startup
{
public Startup(IConfiguration configuration, IWebHostEnvironment environment)
@@ -27,11 +31,12 @@ namespace Timeline // This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
- services.AddMvc()
+ services.AddControllers()
.ConfigureApiBehaviorOptions(options =>
{
options.InvalidModelStateResponseFactory = InvalidModelResponseFactory.Factory;
- });
+ })
+ .AddNewtonsoftJson();
services.Configure<JwtConfig>(Configuration.GetSection(nameof(JwtConfig)));
var jwtConfig = Configuration.GetSection(nameof(JwtConfig)).Get<JwtConfig>();
@@ -48,13 +53,17 @@ namespace Timeline );
});
+ services.AddLocalization(options =>
+ {
+ options.ResourcesPath = "Resources";
+ });
+
services.AddScoped<IUserService, UserService>();
services.AddScoped<IJwtService, JwtService>();
services.AddTransient<IPasswordService, PasswordService>();
services.AddTransient<IClock, Clock>();
services.AddUserAvatarService();
- services.AddUserDetailService();
var databaseConfig = Configuration.GetSection(nameof(DatabaseConfig)).Get<DatabaseConfig>();
@@ -63,11 +72,10 @@ namespace Timeline options.UseMySql(databaseConfig.ConnectionString);
});
- services.AddHttpClient();
-
services.AddMemoryCache();
}
+
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app)
{
@@ -78,6 +86,19 @@ 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 f01b8e31..519a802d 100644 --- a/Timeline/Timeline.csproj +++ b/Timeline/Timeline.csproj @@ -4,6 +4,9 @@ <IsPackable>false</IsPackable>
<UserSecretsId>1f6fb74d-4277-4bc0-aeea-b1fc5ffb0b43</UserSecretsId>
<Authors>crupest</Authors>
+
+ <LangVersion>8.0</LangVersion>
+ <Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
@@ -13,9 +16,127 @@ </ItemGroup>
<ItemGroup>
+ <PackageReference Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="3.0.0" />
+ <PackageReference Include="Microsoft.CodeAnalysis.FxCopAnalyzers" Version="2.9.6">
+ <PrivateAssets>all</PrivateAssets>
+ <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
+ </PackageReference>
+ <PackageReference Include="Microsoft.EntityFrameworkCore" Version="3.0.0" />
+ <PackageReference Include="Microsoft.EntityFrameworkCore.Analyzers" Version="3.0.0" />
+ <PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="3.0.0">
+ <PrivateAssets>all</PrivateAssets>
+ <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
+ </PackageReference>
<PackageReference Include="Pomelo.EntityFrameworkCore.MySql" Version="3.0.0-rc1.final" />
<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.5.0" />
+ <PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="5.6.0" />
+ </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\TokenController.Designer.cs">
+ <DesignTime>True</DesignTime>
+ <AutoGen>True</AutoGen>
+ <DependentUpon>TokenController.resx</DependentUpon>
+ </Compile>
+ <Compile Update="Resources\Controllers\UserAvatarController.Designer.cs">
+ <DesignTime>True</DesignTime>
+ <AutoGen>True</AutoGen>
+ <DependentUpon>UserAvatarController.resx</DependentUpon>
+ </Compile>
+ <Compile Update="Resources\Controllers\UserController.Designer.cs">
+ <DesignTime>True</DesignTime>
+ <AutoGen>True</AutoGen>
+ <DependentUpon>UserController.resx</DependentUpon>
+ </Compile>
+ <Compile Update="Resources\Models\Validation\UsernameValidator.Designer.cs">
+ <DesignTime>True</DesignTime>
+ <AutoGen>True</AutoGen>
+ <DependentUpon>UsernameValidator.resx</DependentUpon>
+ </Compile>
+ <Compile Update="Resources\Models\Validation\Validator.Designer.cs">
+ <DesignTime>True</DesignTime>
+ <AutoGen>True</AutoGen>
+ <DependentUpon>Validator.resx</DependentUpon>
+ </Compile>
+ <Compile Update="Resources\Services\Exception.Designer.cs">
+ <DesignTime>True</DesignTime>
+ <AutoGen>True</AutoGen>
+ <DependentUpon>Exception.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\UserService.Designer.cs">
+ <DesignTime>True</DesignTime>
+ <AutoGen>True</AutoGen>
+ <DependentUpon>UserService.resx</DependentUpon>
+ </Compile>
+ </ItemGroup>
+
+ <ItemGroup>
+ <EmbeddedResource Update="Resources\Authentication\AuthHandler.resx">
+ <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\TokenController.resx">
+ <SubType>Designer</SubType>
+ <Generator>ResXFileCodeGenerator</Generator>
+ <LastGenOutput>TokenController.Designer.cs</LastGenOutput>
+ </EmbeddedResource>
+ <EmbeddedResource Update="Resources\Controllers\TokenController.zh.resx">
+ <SubType>Designer</SubType>
+ <Generator></Generator>
+ </EmbeddedResource>
+ <EmbeddedResource Update="Resources\Controllers\TokenController.en.resx">
+ <Generator></Generator>
+ </EmbeddedResource>
+ <EmbeddedResource Update="Resources\Controllers\UserAvatarController.resx">
+ <Generator>ResXFileCodeGenerator</Generator>
+ <LastGenOutput>UserAvatarController.Designer.cs</LastGenOutput>
+ </EmbeddedResource>
+ <EmbeddedResource Update="Resources\Controllers\UserController.resx">
+ <Generator>ResXFileCodeGenerator</Generator>
+ <LastGenOutput>UserController.Designer.cs</LastGenOutput>
+ </EmbeddedResource>
+ <EmbeddedResource Update="Resources\Models\Validation\UsernameValidator.resx">
+ <Generator>ResXFileCodeGenerator</Generator>
+ <LastGenOutput>UsernameValidator.Designer.cs</LastGenOutput>
+ </EmbeddedResource>
+ <EmbeddedResource Update="Resources\Models\Validation\Validator.en.resx">
+ <Generator></Generator>
+ </EmbeddedResource>
+ <EmbeddedResource Update="Resources\Models\Validation\Validator.resx">
+ <Generator>ResXFileCodeGenerator</Generator>
+ <LastGenOutput>Validator.Designer.cs</LastGenOutput>
+ </EmbeddedResource>
+ <EmbeddedResource Update="Resources\Services\Exception.resx">
+ <Generator>ResXFileCodeGenerator</Generator>
+ <LastGenOutput>Exception.Designer.cs</LastGenOutput>
+ </EmbeddedResource>
+ <EmbeddedResource Update="Resources\Services\UserAvatarService.resx">
+ <Generator>ResXFileCodeGenerator</Generator>
+ <LastGenOutput>UserAvatarService.Designer.cs</LastGenOutput>
+ </EmbeddedResource>
+ <EmbeddedResource Update="Resources\Services\UserService.resx">
+ <Generator>ResXFileCodeGenerator</Generator>
+ <LastGenOutput>UserService.Designer.cs</LastGenOutput>
+ </EmbeddedResource>
</ItemGroup>
</Project>
|