diff options
-rw-r--r-- | Timeline.Tests/Controllers/PersonalTimelineControllerTest.cs | 111 | ||||
-rw-r--r-- | Timeline/Controllers/PersonalTimelineController.cs | 86 | ||||
-rw-r--r-- | Timeline/Filters/Timeline.cs | 47 | ||||
-rw-r--r-- | Timeline/Models/Http/Timeline.cs | 21 | ||||
-rw-r--r-- | Timeline/Resources/Controllers/TimelineController.Designer.cs | 27 | ||||
-rw-r--r-- | Timeline/Resources/Controllers/TimelineController.resx | 9 | ||||
-rw-r--r-- | Timeline/Resources/Controllers/TimelineController.zh.resx | 6 | ||||
-rw-r--r-- | Timeline/Resources/Filters.Designer.cs | 18 | ||||
-rw-r--r-- | Timeline/Resources/Filters.resx | 6 | ||||
-rw-r--r-- | Timeline/Resources/Filters.zh.resx | 6 | ||||
-rw-r--r-- | Timeline/Resources/Services/Exception.Designer.cs | 15 | ||||
-rw-r--r-- | Timeline/Resources/Services/Exception.resx | 7 | ||||
-rw-r--r-- | Timeline/Services/TimelineMemberOperationUserException.cs | 15 | ||||
-rw-r--r-- | Timeline/Services/TimelinePostNotExistException.cs | 23 | ||||
-rw-r--r-- | Timeline/Services/TimelineService.cs | 62 |
15 files changed, 406 insertions, 53 deletions
diff --git a/Timeline.Tests/Controllers/PersonalTimelineControllerTest.cs b/Timeline.Tests/Controllers/PersonalTimelineControllerTest.cs new file mode 100644 index 00000000..d5c470ee --- /dev/null +++ b/Timeline.Tests/Controllers/PersonalTimelineControllerTest.cs @@ -0,0 +1,111 @@ +using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading.Tasks;
+using Timeline.Controllers;
+using Timeline.Services;
+using Moq;
+using Microsoft.Extensions.Logging.Abstractions;
+using Xunit;
+using FluentAssertions;
+using Microsoft.AspNetCore.Mvc;
+using Timeline.Filters;
+using Timeline.Tests.Helpers;
+using Timeline.Models.Validation;
+using System.Reflection;
+using Microsoft.AspNetCore.Authorization;
+using Timeline.Models.Http;
+
+namespace Timeline.Tests.Controllers
+{
+ public class PersonalTimelineControllerTest : IDisposable
+ {
+ private readonly Mock<IPersonalTimelineService> _service;
+
+ private readonly PersonalTimelineController _controller;
+
+ public PersonalTimelineControllerTest()
+ {
+ _service = new Mock<IPersonalTimelineService>();
+ _controller = new PersonalTimelineController(NullLogger<PersonalTimelineController>.Instance, _service.Object);
+ }
+
+ public void Dispose()
+ {
+ _controller.Dispose();
+ }
+
+ [Fact]
+ public void AttributeTest()
+ {
+ static void AssertUsernameParameter(MethodInfo m)
+ {
+ m.GetParameter("username")
+ .Should().BeDecoratedWith<FromRouteAttribute>()
+ .And.BeDecoratedWith<UsernameAttribute>();
+ }
+
+ static void AssertBodyParamter<TBody>(MethodInfo m)
+ {
+ var p = m.GetParameter("body");
+ p.Should().BeDecoratedWith<FromBodyAttribute>();
+ p.ParameterType.Should().Be(typeof(TBody));
+ }
+
+ var type = typeof(PersonalTimelineController);
+ type.Should().BeDecoratedWith<ApiControllerAttribute>();
+
+ {
+ var m = type.GetMethod(nameof(PersonalTimelineController.TimelineGet));
+ m.Should().BeDecoratedWith<CatchTimelineNotExistExceptionAttribute>()
+ .And.BeDecoratedWith<HttpGetAttribute>();
+ AssertUsernameParameter(m);
+ }
+
+ {
+ var m = type.GetMethod(nameof(PersonalTimelineController.PostsGet));
+ m.Should().BeDecoratedWith<CatchTimelineNotExistExceptionAttribute>()
+ .And.BeDecoratedWith<HttpGetAttribute>();
+ AssertUsernameParameter(m);
+ }
+
+ {
+ var m = type.GetMethod(nameof(PersonalTimelineController.TimelinePost));
+ m.Should().BeDecoratedWith<CatchTimelineNotExistExceptionAttribute>()
+ .And.BeDecoratedWith<AuthorizeAttribute>()
+ .And.BeDecoratedWith<HttpPostAttribute>();
+ AssertUsernameParameter(m);
+ AssertBodyParamter<TimelinePostCreateRequest>(m);
+ }
+
+ {
+ var m = type.GetMethod(nameof(PersonalTimelineController.TimelinePostDelete));
+ m.Should().BeDecoratedWith<CatchTimelineNotExistExceptionAttribute>()
+ .And.BeDecoratedWith<AuthorizeAttribute>()
+ .And.BeDecoratedWith<HttpPostAttribute>();
+ AssertUsernameParameter(m);
+ AssertBodyParamter<TimelinePostDeleteRequest>(m);
+ }
+
+ {
+ var m = type.GetMethod(nameof(PersonalTimelineController.TimelineChangeProperty));
+ m.Should().BeDecoratedWith<CatchTimelineNotExistExceptionAttribute>()
+ .And.BeDecoratedWith<AuthorizeAttribute>()
+ .And.BeDecoratedWith<SelfOrAdminAttribute>()
+ .And.BeDecoratedWith<HttpPostAttribute>();
+ AssertUsernameParameter(m);
+ AssertBodyParamter<TimelinePropertyChangeRequest>(m);
+ }
+
+ {
+ var m = type.GetMethod(nameof(PersonalTimelineController.TimelineChangeMember));
+ m.Should().BeDecoratedWith<CatchTimelineNotExistExceptionAttribute>()
+ .And.BeDecoratedWith<AuthorizeAttribute>()
+ .And.BeDecoratedWith<SelfOrAdminAttribute>()
+ .And.BeDecoratedWith<HttpPostAttribute>();
+ AssertUsernameParameter(m);
+ AssertBodyParamter<TimelineMemberChangeRequest>(m);
+ }
+ }
+ }
+}
diff --git a/Timeline/Controllers/PersonalTimelineController.cs b/Timeline/Controllers/PersonalTimelineController.cs index 1535a0b2..f006ad47 100644 --- a/Timeline/Controllers/PersonalTimelineController.cs +++ b/Timeline/Controllers/PersonalTimelineController.cs @@ -1,13 +1,11 @@ using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
-using System;
+using Microsoft.Extensions.Logging;
using System.Collections.Generic;
-using System.ComponentModel.DataAnnotations;
-using System.Linq;
+using System.Globalization;
using System.Threading.Tasks;
using Timeline.Auth;
-using Timeline.Entities;
using Timeline.Filters;
using Timeline.Models;
using Timeline.Models.Http;
@@ -25,6 +23,7 @@ namespace Timeline {
public const int PostsGetForbid = 10040101;
public const int PostsCreateForbid = 10040102;
+ public const int MemberAddNotExist = 10040201;
}
}
}
@@ -35,6 +34,8 @@ namespace Timeline.Controllers [ApiController]
public class PersonalTimelineController : Controller
{
+ private readonly ILogger<PersonalTimelineController> _logger;
+
private readonly IPersonalTimelineService _service;
private bool IsAdmin()
@@ -58,18 +59,21 @@ namespace Timeline.Controllers }
}
- public PersonalTimelineController(IPersonalTimelineService service)
+ public PersonalTimelineController(ILogger<PersonalTimelineController> logger, IPersonalTimelineService service)
{
+ _logger = logger;
_service = service;
}
[HttpGet("users/{username}/timeline")]
+ [CatchTimelineNotExistException]
public async Task<ActionResult<BaseTimelineInfo>> TimelineGet([FromRoute][Username] string username)
{
return await _service.GetTimeline(username);
}
[HttpGet("users/{username}/timeline/posts")]
+ [CatchTimelineNotExistException]
public async Task<ActionResult<IList<TimelinePostInfo>>> PostsGet([FromRoute][Username] string username)
{
if (!IsAdmin() && !await _service.HasReadPermission(username, GetAuthUsername()))
@@ -81,9 +85,10 @@ namespace Timeline.Controllers return await _service.GetPosts(username);
}
- [HttpPost("user/{username}/timeline/posts/create")]
+ [HttpPost("user/{username}/timeline/postop/create")]
[Authorize]
- public async Task<ActionResult> PostsCreate([FromRoute][Username] string username, [FromBody] TimelinePostCreateRequest body)
+ [CatchTimelineNotExistException]
+ public async Task<ActionResult<TimelinePostCreateResponse>> TimelinePost([FromRoute][Username] string username, [FromBody] TimelinePostCreateRequest body)
{
if (!IsAdmin() && !await _service.IsMemberOf(username, GetAuthUsername()!))
{
@@ -91,51 +96,62 @@ namespace Timeline.Controllers new CommonResponse(ErrorCodes.Http.Timeline.PostsCreateForbid, MessagePostsCreateForbid));
}
- await _service.CreatePost(username, User.Identity.Name!, body.Content, body.Time);
- return Ok();
+ var res = await _service.CreatePost(username, User.Identity.Name!, body.Content, body.Time);
+ return res;
}
- [HttpPut("user/{username}/timeline/description")]
+ [HttpPost("user/{username}/timeline/postop/delete")]
[Authorize]
- [SelfOrAdmin]
- public async Task<ActionResult> TimelinePutDescription([FromRoute][Username] string username, [FromBody] string body)
- {
- await _service.SetDescription(username, body);
- return Ok();
- }
-
- private static TimelineVisibility StringToVisibility(string s)
+ [CatchTimelineNotExistException]
+ public async Task<ActionResult> TimelinePostDelete([FromRoute][Username] string username, [FromBody] TimelinePostDeleteRequest body)
{
- if ("public".Equals(s, StringComparison.InvariantCultureIgnoreCase))
+ var postId = body.Id!.Value;
+ if (!IsAdmin() && !await _service.HasPostModifyPermission(username, postId, GetAuthUsername()!))
{
- return TimelineVisibility.Public;
- }
- else if ("register".Equals(s, StringComparison.InvariantCultureIgnoreCase))
- {
- return TimelineVisibility.Register;
- }
- else if ("private".Equals(s, StringComparison.InvariantCultureIgnoreCase))
- {
- return TimelineVisibility.Private;
+ return StatusCode(StatusCodes.Status403Forbidden,
+ new CommonResponse(ErrorCodes.Http.Timeline.PostsCreateForbid, MessagePostsCreateForbid));
}
- throw new ArgumentException(ExceptionStringToVisibility);
+ await _service.DeletePost(username, postId);
+ return Ok();
}
- [HttpPut("user/{username}/timeline/visibility")]
+ [HttpPost("user/{username}/timeline/op/property")]
[Authorize]
[SelfOrAdmin]
- public async Task<ActionResult> TimelinePutVisibility([FromRoute][Username] string username, [FromBody][RegularExpression("public|register|private")] string body)
+ [CatchTimelineNotExistException]
+ public async Task<ActionResult> TimelineChangeProperty([FromRoute][Username] string username, [FromBody] TimelinePropertyChangeRequest body)
{
- await _service.SetVisibility(username, StringToVisibility(body));
+ await _service.ChangeProperty(username, body);
return Ok();
}
- [HttpPost("user/{username}/timeline/members/change")]
+ [HttpPost("user/{username}/timeline/op/member")]
[Authorize]
[SelfOrAdmin]
- public async Task<ActionResult> TimelineMembersChange([FromRoute][Username] string username, [FromBody] TimelineMemberChangeRequest body)
+ [CatchTimelineNotExistException]
+ public async Task<ActionResult> TimelineChangeMember([FromRoute][Username] string username, [FromBody] TimelineMemberChangeRequest body)
{
- //TODO!
+ try
+ {
+ await _service.ChangeMember(username, body.Add, body.Remove);
+ return Ok();
+ }
+ catch (TimelineMemberOperationUserException e)
+ {
+ if (e.InnerException is UsernameBadFormatException)
+ {
+ return BadRequest(CommonResponse.InvalidModel(
+ string.Format(CultureInfo.CurrentCulture, MessageMemberUsernameBadFormat, e.Index, e.Operation)));
+ }
+ else if (e.InnerException is UserNotExistException)
+ {
+ return BadRequest(new CommonResponse(ErrorCodes.Http.Timeline.MemberAddNotExist,
+ string.Format(CultureInfo.CurrentCulture, MessageMemberUserNotExist, e.Index, e.Operation)));
+ }
+
+ _logger.LogError(e, LogUnknownTimelineMemberOperationUserException);
+ throw;
+ }
}
}
}
diff --git a/Timeline/Filters/Timeline.cs b/Timeline/Filters/Timeline.cs new file mode 100644 index 00000000..7859d409 --- /dev/null +++ b/Timeline/Filters/Timeline.cs @@ -0,0 +1,47 @@ +using Microsoft.AspNetCore.Mvc;
+using Microsoft.AspNetCore.Mvc.Filters;
+using Timeline.Models.Http;
+using Timeline.Services;
+using static Timeline.Resources.Filters;
+
+namespace Timeline
+{
+ public static partial class ErrorCodes
+ {
+ public static partial class Http
+ {
+ public static partial class Filter // bxx = 1xx
+ {
+ public static class Timeline // bbb = 102
+ {
+ public const int UserNotExist = 11020101;
+ public const int NameNotExist = 11020102;
+ }
+ }
+ }
+ }
+}
+
+namespace Timeline.Filters
+{
+ public class CatchTimelineNotExistExceptionAttribute : ExceptionFilterAttribute
+ {
+ [System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1062:Validate arguments of public methods")]
+ public override void OnException(ExceptionContext context)
+ {
+ if (context.Exception is TimelineNotExistException e)
+ {
+ if (e.InnerException is UserNotExistException)
+ {
+ context.Result = new BadRequestObjectResult(
+ new CommonResponse(ErrorCodes.Http.Filter.Timeline.UserNotExist, MessageTimelineNotExistUser));
+ }
+ else
+ {
+ context.Result = new BadRequestObjectResult(
+ new CommonResponse(ErrorCodes.Http.Filter.Timeline.NameNotExist, MessageTimelineNotExist));
+ }
+ }
+ }
+ }
+}
diff --git a/Timeline/Models/Http/Timeline.cs b/Timeline/Models/Http/Timeline.cs index 37de9e58..f676afa0 100644 --- a/Timeline/Models/Http/Timeline.cs +++ b/Timeline/Models/Http/Timeline.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Threading.Tasks;
+using Timeline.Entities;
namespace Timeline.Models.Http
{
@@ -14,6 +15,26 @@ namespace Timeline.Models.Http public DateTime? Time { get; set; }
}
+ public class TimelinePostCreateResponse
+ {
+ public long Id { get; set; }
+
+ public DateTime Time { get; set; }
+ }
+
+ public class TimelinePostDeleteRequest
+ {
+ [Required]
+ public long? Id { get; set; }
+ }
+
+ public class TimelinePropertyChangeRequest
+ {
+ public string? Description { get; set; }
+
+ public TimelineVisibility? Visibility { get; set; }
+ }
+
[System.Diagnostics.CodeAnalysis.SuppressMessage("Usage", "CA2227:Collection properties should be read only", Justification = "This is a DTO class.")]
public class TimelineMemberChangeRequest
{
diff --git a/Timeline/Resources/Controllers/TimelineController.Designer.cs b/Timeline/Resources/Controllers/TimelineController.Designer.cs index 1e56f651..5a4209c3 100644 --- a/Timeline/Resources/Controllers/TimelineController.Designer.cs +++ b/Timeline/Resources/Controllers/TimelineController.Designer.cs @@ -70,6 +70,33 @@ namespace Timeline.Resources.Controllers { }
/// <summary>
+ /// Looks up a localized string similar to An unknown TimelineMemberOperationUserException is thrown. Can't recognize its inner exception. It is rethrown..
+ /// </summary>
+ internal static string LogUnknownTimelineMemberOperationUserException {
+ get {
+ return ResourceManager.GetString("LogUnknownTimelineMemberOperationUserException", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to The {0}-st username to do operation {1} on is of bad format..
+ /// </summary>
+ internal static string MessageMemberUsernameBadFormat {
+ get {
+ return ResourceManager.GetString("MessageMemberUsernameBadFormat", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to The {0}-st user to do operation {1} on does not exist..
+ /// </summary>
+ internal static string MessageMemberUserNotExist {
+ get {
+ return ResourceManager.GetString("MessageMemberUserNotExist", resourceCulture);
+ }
+ }
+
+ /// <summary>
/// Looks up a localized string similar to You have no permission to create posts in the timeline..
/// </summary>
internal static string MessagePostsCreateForbid {
diff --git a/Timeline/Resources/Controllers/TimelineController.resx b/Timeline/Resources/Controllers/TimelineController.resx index 420ac419..7e323164 100644 --- a/Timeline/Resources/Controllers/TimelineController.resx +++ b/Timeline/Resources/Controllers/TimelineController.resx @@ -120,6 +120,15 @@ <data name="ExceptionStringToVisibility" xml:space="preserve">
<value>An unknown timeline visibility value. Can't convert it.</value>
</data>
+ <data name="LogUnknownTimelineMemberOperationUserException" xml:space="preserve">
+ <value>An unknown TimelineMemberOperationUserException is thrown. Can't recognize its inner exception. It is rethrown.</value>
+ </data>
+ <data name="MessageMemberUsernameBadFormat" xml:space="preserve">
+ <value>The {0}-st username to do operation {1} on is of bad format.</value>
+ </data>
+ <data name="MessageMemberUserNotExist" xml:space="preserve">
+ <value>The {0}-st user to do operation {1} on does not exist.</value>
+ </data>
<data name="MessagePostsCreateForbid" xml:space="preserve">
<value>You have no permission to create posts in the timeline.</value>
</data>
diff --git a/Timeline/Resources/Controllers/TimelineController.zh.resx b/Timeline/Resources/Controllers/TimelineController.zh.resx index e22f44fa..cacce5fa 100644 --- a/Timeline/Resources/Controllers/TimelineController.zh.resx +++ b/Timeline/Resources/Controllers/TimelineController.zh.resx @@ -117,6 +117,12 @@ <resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
+ <data name="MessageMemberUsernameBadFormat" xml:space="preserve">
+ <value>第{0}个做{1}操作的用户名格式错误。</value>
+ </data>
+ <data name="MessageMemberUserNotExist" xml:space="preserve">
+ <value>第{0}个做{1}操作的用户不存在。</value>
+ </data>
<data name="MessagePostsCreateForbid" xml:space="preserve">
<value>你没有权限在这个时间线中创建消息。</value>
</data>
diff --git a/Timeline/Resources/Filters.Designer.cs b/Timeline/Resources/Filters.Designer.cs index 3481e4ae..5576190d 100644 --- a/Timeline/Resources/Filters.Designer.cs +++ b/Timeline/Resources/Filters.Designer.cs @@ -124,6 +124,24 @@ namespace Timeline.Resources { }
/// <summary>
+ /// Looks up a localized string similar to The requested timeline does not exist..
+ /// </summary>
+ internal static string MessageTimelineNotExist {
+ get {
+ return ResourceManager.GetString("MessageTimelineNotExist", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to The requested personal timeline does not exist because the user does not exist..
+ /// </summary>
+ internal static string MessageTimelineNotExistUser {
+ get {
+ return ResourceManager.GetString("MessageTimelineNotExistUser", resourceCulture);
+ }
+ }
+
+ /// <summary>
/// Looks up a localized string similar to The user does not exist..
/// </summary>
internal static string MessageUserNotExist {
diff --git a/Timeline/Resources/Filters.resx b/Timeline/Resources/Filters.resx index b91d4612..7bfbc703 100644 --- a/Timeline/Resources/Filters.resx +++ b/Timeline/Resources/Filters.resx @@ -138,6 +138,12 @@ <data name="MessageSelfOrAdminForbid" xml:space="preserve">
<value>You can't access the resource unless you are the owner or administrator.</value>
</data>
+ <data name="MessageTimelineNotExist" xml:space="preserve">
+ <value>The requested timeline does not exist.</value>
+ </data>
+ <data name="MessageTimelineNotExistUser" xml:space="preserve">
+ <value>The requested personal timeline does not exist because the user does not exist.</value>
+ </data>
<data name="MessageUserNotExist" xml:space="preserve">
<value>The user does not exist.</value>
</data>
diff --git a/Timeline/Resources/Filters.zh.resx b/Timeline/Resources/Filters.zh.resx index 159ac04a..36aac788 100644 --- a/Timeline/Resources/Filters.zh.resx +++ b/Timeline/Resources/Filters.zh.resx @@ -129,6 +129,12 @@ <data name="MessageSelfOrAdminForbid" xml:space="preserve">
<value>你无权访问该资源除非你是资源的拥有者或者管理员。</value>
</data>
+ <data name="MessageTimelineNotExist" xml:space="preserve">
+ <value>请求的时间线不存在。</value>
+ </data>
+ <data name="MessageTimelineNotExistUser" xml:space="preserve">
+ <value>请求的个人时间线不存在因为该用户不存在。</value>
+ </data>
<data name="MessageUserNotExist" xml:space="preserve">
<value>用户不存在。</value>
</data>
diff --git a/Timeline/Resources/Services/Exception.Designer.cs b/Timeline/Resources/Services/Exception.Designer.cs index 1f6493cb..970c306d 100644 --- a/Timeline/Resources/Services/Exception.Designer.cs +++ b/Timeline/Resources/Services/Exception.Designer.cs @@ -286,11 +286,11 @@ namespace Timeline.Resources.Services { }
/// <summary>
- /// Looks up a localized string similar to An exception happened when operating on the {} member on timeline..
+ /// Looks up a localized string similar to An exception happened when do operation {} on the {} member on timeline..
/// </summary>
- internal static string TimelineMemberOperationExceptionIndex {
+ internal static string TimelineMemberOperationExceptionDetail {
get {
- return ResourceManager.GetString("TimelineMemberOperationExceptionIndex", resourceCulture);
+ return ResourceManager.GetString("TimelineMemberOperationExceptionDetail", resourceCulture);
}
}
@@ -313,6 +313,15 @@ namespace Timeline.Resources.Services { }
/// <summary>
+ /// Looks up a localized string similar to The timeline post does not exist. You can't do operation on it..
+ /// </summary>
+ internal static string TimelinePostNotExistException {
+ get {
+ return ResourceManager.GetString("TimelinePostNotExistException", resourceCulture);
+ }
+ }
+
+ /// <summary>
/// Looks up a localized string similar to The use is not a member of the timeline..
/// </summary>
internal static string TimelineUserNotMemberException {
diff --git a/Timeline/Resources/Services/Exception.resx b/Timeline/Resources/Services/Exception.resx index 3e9d3747..c8f6676a 100644 --- a/Timeline/Resources/Services/Exception.resx +++ b/Timeline/Resources/Services/Exception.resx @@ -192,8 +192,8 @@ <data name="TimelineMemberOperationException" xml:space="preserve">
<value>An exception happened when add or remove member on timeline.</value>
</data>
- <data name="TimelineMemberOperationExceptionIndex" xml:space="preserve">
- <value>An exception happened when operating on the {} member on timeline.</value>
+ <data name="TimelineMemberOperationExceptionDetail" xml:space="preserve">
+ <value>An exception happened when do operation {} on the {} member on timeline.</value>
</data>
<data name="TimelineNameBadFormatException" xml:space="preserve">
<value>Timeline name is of bad format. If this is a personal timeline, it means the username is of bad format and inner exception should be a UsernameBadFormatException.</value>
@@ -201,6 +201,9 @@ <data name="TimelineNotExistException" xml:space="preserve">
<value>Timeline does not exist. If this is a personal timeline, it means the user does not exist and inner exception should be a UserNotExistException.</value>
</data>
+ <data name="TimelinePostNotExistException" xml:space="preserve">
+ <value>The timeline post does not exist. You can't do operation on it.</value>
+ </data>
<data name="TimelineUserNotMemberException" xml:space="preserve">
<value>The use is not a member of the timeline.</value>
</data>
diff --git a/Timeline/Services/TimelineMemberOperationUserException.cs b/Timeline/Services/TimelineMemberOperationUserException.cs index 998f1a6e..543ee160 100644 --- a/Timeline/Services/TimelineMemberOperationUserException.cs +++ b/Timeline/Services/TimelineMemberOperationUserException.cs @@ -6,6 +6,12 @@ namespace Timeline.Services [Serializable]
public class TimelineMemberOperationUserException : Exception
{
+ public enum MemberOperation
+ {
+ Add,
+ Remove
+ }
+
public TimelineMemberOperationUserException() : base(Resources.Services.Exception.TimelineMemberOperationException) { }
public TimelineMemberOperationUserException(string message) : base(message) { }
public TimelineMemberOperationUserException(string message, Exception inner) : base(message, inner) { }
@@ -13,10 +19,13 @@ namespace Timeline.Services System.Runtime.Serialization.SerializationInfo info,
System.Runtime.Serialization.StreamingContext context) : base(info, context) { }
- public TimelineMemberOperationUserException(int index, string username, Exception inner) : base(MakeIndexMessage(index), inner) { Index = index; Username = username; }
+ public TimelineMemberOperationUserException(int index, MemberOperation operation, string username, Exception inner)
+ : base(MakeMessage(operation, index), inner) { Operation = operation; Index = index; Username = username; }
+
+ private static string MakeMessage(MemberOperation operation, int index) => string.Format(CultureInfo.CurrentCulture,
+ Resources.Services.Exception.TimelineMemberOperationExceptionDetail, operation, index);
- private static string MakeIndexMessage(int index) => string.Format(CultureInfo.CurrentCulture,
- Resources.Services.Exception.TimelineMemberOperationExceptionIndex, index);
+ public MemberOperation? Operation { get; set; }
/// <summary>
/// The index of the member on which the operation failed.
diff --git a/Timeline/Services/TimelinePostNotExistException.cs b/Timeline/Services/TimelinePostNotExistException.cs new file mode 100644 index 00000000..97e5d550 --- /dev/null +++ b/Timeline/Services/TimelinePostNotExistException.cs @@ -0,0 +1,23 @@ +using System;
+
+namespace Timeline.Services
+{
+ [Serializable]
+ public class TimelinePostNotExistException : Exception
+ {
+ public TimelinePostNotExistException() { }
+ public TimelinePostNotExistException(string message) : base(message) { }
+ public TimelinePostNotExistException(string message, Exception inner) : base(message, inner) { }
+ protected TimelinePostNotExistException(
+ System.Runtime.Serialization.SerializationInfo info,
+ System.Runtime.Serialization.StreamingContext context) : base(info, context) { }
+
+ public TimelinePostNotExistException(long id) : base(Resources.Services.Exception.TimelinePostNotExistException) { Id = id; }
+
+ public TimelinePostNotExistException(long id, string message) : base(message) { Id = id; }
+
+ public TimelinePostNotExistException(long id, string message, Exception inner) : base(message, inner) { Id = id; }
+
+ public long Id { get; set; }
+ }
+}
diff --git a/Timeline/Services/TimelineService.cs b/Timeline/Services/TimelineService.cs index 7fe32cac..28b1f91d 100644 --- a/Timeline/Services/TimelineService.cs +++ b/Timeline/Services/TimelineService.cs @@ -4,6 +4,7 @@ using System.Linq; using System.Threading.Tasks;
using Timeline.Entities;
using Timeline.Models;
+using Timeline.Models.Http;
namespace Timeline.Services
{
@@ -45,7 +46,7 @@ namespace Timeline.Services /// <param name="author">The author's username.</param>
/// <param name="content">The content.</param>
/// <param name="time">The time of the post. If null, then use current time.</param>
- /// <returns></returns>
+ /// <returns>The info of the created post.</returns>
/// <exception cref="ArgumentNullException">Thrown when <paramref name="name"/> or <paramref name="author"/> or <paramref name="content"/> is null.</exception>
/// <exception cref="TimelineNameBadFormatException">
/// Thrown when timeline name is of bad format.
@@ -61,14 +62,14 @@ namespace Timeline.Services /// </exception>
/// <exception cref="UsernameBadFormatException">Thrown if <paramref name="author"/> is of bad format.</exception>
/// <exception cref="UserNotExistException">Thrown if <paramref name="author"/> does not exist.</exception>
- Task<long> CreatePost(string name, string author, string content, DateTime? time);
+ Task<TimelinePostCreateResponse> CreatePost(string name, string author, string content, DateTime? time);
/// <summary>
- /// Set the visibility permission of a timeline.
+ /// Delete a post
/// </summary>
/// <param name="name">Username or the timeline name. See remarks of <see cref="IBaseTimelineService"/>.</param>
- /// <param name="visibility">The new visibility.</param>
- /// <exception cref="ArgumentNullException">Thrown when <paramref name="name"/> is null.</exception>
+ /// <param name="id">The id of the post to delete.</param>
+ /// <exception cref="ArgumentNullException">Thrown when <paramref name="name"/> or <paramref name="username"/> is null.</exception>
/// <exception cref="TimelineNameBadFormatException">
/// Thrown when timeline name is of bad format.
/// For normal timeline, it means name is an empty string.
@@ -81,14 +82,21 @@ namespace Timeline.Services /// For personal timeline, it means the user of that username does not exist
/// and the inner exception should be a <see cref="UserNotExistException"/>.
/// </exception>
- Task SetVisibility(string name, TimelineVisibility visibility);
+ /// <exception cref="TimelinePostNotExistException">
+ /// Thrown when the post with given id does not exist or is deleted already.
+ /// </exception>
+ /// <remarks>
+ /// First use <see cref="IBaseTimelineService.HasPostModifyPermission(string, long, string)"/>
+ /// to check the permission.
+ /// </remarks>
+ Task DeletePost(string name, long id);
/// <summary>
- /// Set the description of a timeline.
+ /// Set the properties of a timeline.
/// </summary>
/// <param name="name">Username or the timeline name. See remarks of <see cref="IBaseTimelineService"/>.</param>
- /// <param name="description">The new description.</param>
- /// <exception cref="ArgumentNullException">Thrown when <paramref name="name"/> or <paramref name="description"/> is null.</exception>
+ /// <param name="newProperties">The new properties. Null member means not to change.</param>
+ /// <exception cref="ArgumentNullException">Thrown when <paramref name="name"/> or <paramref name="newProperties"/> is null.</exception>
/// <exception cref="TimelineNameBadFormatException">
/// Thrown when timeline name is of bad format.
/// For normal timeline, it means name is an empty string.
@@ -101,7 +109,7 @@ namespace Timeline.Services /// For personal timeline, it means the user of that username does not exist
/// and the inner exception should be a <see cref="UserNotExistException"/>.
/// </exception>
- Task SetDescription(string name, string description);
+ Task ChangeProperty(string name, TimelinePropertyChangeRequest newProperties);
/// <summary>
/// Remove members to a timeline.
@@ -160,6 +168,40 @@ namespace Timeline.Services Task<bool> HasReadPermission(string name, string? username);
/// <summary>
+ /// Verify whether a user has the permission to modify a post.
+ /// </summary>
+ /// <param name="name">Username or the timeline name. See remarks of <see cref="IBaseTimelineService"/>.</param>
+ /// <param name="username">The user to check on.</param>
+ /// <returns>True if can modify, false if can't modify.</returns>
+ /// <exception cref="ArgumentNullException">Thrown when <paramref name="name"/> or <paramref name="username"/> is null.</exception>
+ /// <exception cref="TimelineNameBadFormatException">
+ /// Thrown when timeline name is of bad format.
+ /// For normal timeline, it means name is an empty string.
+ /// For personal timeline, it means the username is of bad format,
+ /// the inner exception should be a <see cref="UsernameBadFormatException"/>.
+ /// </exception>
+ /// <exception cref="TimelineNotExistException">
+ /// Thrown when timeline does not exist.
+ /// For normal timeline, it means the name does not exist.
+ /// For personal timeline, it means the user of that username does not exist
+ /// and the inner exception should be a <see cref="UserNotExistException"/>.
+ /// </exception>
+ /// <exception cref="TimelinePostNotExistException">
+ /// Thrown when the post with given id does not exist or is deleted already.
+ /// </exception>
+ /// <exception cref="UsernameBadFormatException">
+ /// Thrown when <paramref name="username"/> is of bad format.
+ /// </exception>
+ /// <exception cref="UserNotExistException">
+ /// Thrown when <paramref name="username"/> does not exist.
+ /// </exception>
+ /// <remarks>
+ /// This method does not check whether the user is administrator.
+ /// It only checks whether he is the author of the post or the owner of the timeline.
+ /// </remarks>
+ Task<bool> HasPostModifyPermission(string name, long id, string username);
+
+ /// <summary>
/// Verify whether a user is member of a timeline.
/// </summary>
/// <param name="name">Username or the timeline name. See remarks of <see cref="IBaseTimelineService"/>.</param>
|