aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorcrupest <crupest@outlook.com>2020-12-19 20:08:49 +0800
committercrupest <crupest@outlook.com>2020-12-19 20:08:49 +0800
commitcf8a869de33cfa5db1967698059abccaaeaba4c9 (patch)
tree5e4a504e02b9d8592be7ec5b1aba28d2dd876ddf
parentab3aedad37fe4634efb0d6939d7a40642bfff032 (diff)
downloadtimeline-cf8a869de33cfa5db1967698059abccaaeaba4c9.tar.gz
timeline-cf8a869de33cfa5db1967698059abccaaeaba4c9.tar.bz2
timeline-cf8a869de33cfa5db1967698059abccaaeaba4c9.zip
feat: Bookmark timeline REST api.
-rw-r--r--BackEnd/Timeline.ErrorCodes/ErrorCodes.cs5
-rw-r--r--BackEnd/Timeline.Tests/IntegratedTests/BookmarkTimelineTest.cs87
-rw-r--r--BackEnd/Timeline.Tests/IntegratedTests/HighlightTimelineTest.cs1
-rw-r--r--BackEnd/Timeline.Tests/IntegratedTests/HttpClientTestExtensions.cs5
-rw-r--r--BackEnd/Timeline/Controllers/BookmarkTimelineController.cs114
-rw-r--r--BackEnd/Timeline/Controllers/HighlightTimelineController.cs8
-rw-r--r--BackEnd/Timeline/Models/Http/BookmarkTimeline.cs23
-rw-r--r--BackEnd/Timeline/Models/Http/HighlightTimeline.cs6
-rw-r--r--BackEnd/Timeline/Startup.cs1
9 files changed, 249 insertions, 1 deletions
diff --git a/BackEnd/Timeline.ErrorCodes/ErrorCodes.cs b/BackEnd/Timeline.ErrorCodes/ErrorCodes.cs
index a8519216..c65bf26e 100644
--- a/BackEnd/Timeline.ErrorCodes/ErrorCodes.cs
+++ b/BackEnd/Timeline.ErrorCodes/ErrorCodes.cs
@@ -68,6 +68,11 @@
{
public const int NonHighlight = 1_105_01_01;
}
+
+ public static class BookmarkTimelineController
+ {
+ public const int NonBookmark = 1_106_01_01;
+ }
}
}
diff --git a/BackEnd/Timeline.Tests/IntegratedTests/BookmarkTimelineTest.cs b/BackEnd/Timeline.Tests/IntegratedTests/BookmarkTimelineTest.cs
new file mode 100644
index 00000000..e6ae178f
--- /dev/null
+++ b/BackEnd/Timeline.Tests/IntegratedTests/BookmarkTimelineTest.cs
@@ -0,0 +1,87 @@
+using FluentAssertions;
+using System.Collections.Generic;
+using System.Threading.Tasks;
+using Timeline.Models.Http;
+using Xunit;
+
+namespace Timeline.Tests.IntegratedTests
+{
+ public class BookmarkTimelineTest : IntegratedTestBase
+ {
+ [Fact]
+ public async Task AuthTest()
+ {
+ using var client = await CreateDefaultClient();
+
+ await client.TestPutAssertUnauthorizedAsync("bookmarks/@user1");
+ await client.TestDeleteAssertUnauthorizedAsync("bookmarks/@user1");
+ await client.TestPostAssertUnauthorizedAsync("bookmarkop/move", new HttpBookmarkTimelineMoveRequest { Timeline = "aaa", NewPosition = 1 });
+ }
+
+ [Fact]
+ public async Task InvalidModel()
+ {
+ using var client = await CreateClientAsUser();
+
+ await client.TestPutAssertInvalidModelAsync("bookmarks/!!!");
+ await client.TestDeleteAssertInvalidModelAsync("bookmarks/!!!");
+ await client.TestPostAssertInvalidModelAsync("bookmarkop/move", new HttpBookmarkTimelineMoveRequest { Timeline = null!, NewPosition = 1 });
+ await client.TestPostAssertInvalidModelAsync("bookmarkop/move", new HttpBookmarkTimelineMoveRequest { Timeline = "!!!", NewPosition = 1 });
+ await client.TestPostAssertInvalidModelAsync("bookmarkop/move", new HttpBookmarkTimelineMoveRequest { Timeline = "aaa", NewPosition = null });
+ }
+
+ [Fact]
+ public async Task ShouldWork()
+ {
+ using var client = await CreateClientAsUser();
+ await client.TestPostAsync("timelines", new TimelineCreateRequest { Name = "t1" });
+
+
+ {
+ var h = await client.TestGetAsync<List<HttpTimeline>>("bookmarks");
+ h.Should().BeEmpty();
+ }
+
+ await client.TestPutAsync("bookmarks/@user1");
+
+ {
+ var h = await client.TestGetAsync<List<HttpTimeline>>("bookmarks");
+ h.Should().HaveCount(1);
+ h[0].Name.Should().Be("@user1");
+ }
+
+ await client.TestPutAsync("bookmarks/t1");
+
+ {
+ var h = await client.TestGetAsync<List<HttpTimeline>>("bookmarks");
+ h.Should().HaveCount(2);
+ h[0].Name.Should().Be("@user1");
+ h[1].Name.Should().Be("t1");
+ }
+
+ await client.TestPostAsync("bookmarkop/move", new HttpHighlightTimelineMoveRequest { Timeline = "@user1", NewPosition = 2 });
+
+ {
+ var h = await client.TestGetAsync<List<HttpTimeline>>("bookmarks");
+ h.Should().HaveCount(2);
+ h[0].Name.Should().Be("t1");
+ h[1].Name.Should().Be("@user1");
+ }
+
+ await client.TestDeleteAsync("bookmarks/@user1");
+
+ {
+ var h = await client.TestGetAsync<List<HttpTimeline>>("bookmarks");
+ h.Should().HaveCount(1);
+ h[0].Name.Should().Be("t1");
+ }
+
+ await client.TestDeleteAsync("bookmarks/t1");
+
+ {
+ var h = await client.TestGetAsync<List<HttpTimeline>>("bookmarks");
+ h.Should().BeEmpty();
+ }
+ }
+ }
+}
diff --git a/BackEnd/Timeline.Tests/IntegratedTests/HighlightTimelineTest.cs b/BackEnd/Timeline.Tests/IntegratedTests/HighlightTimelineTest.cs
index d4b4d55d..63f40a1e 100644
--- a/BackEnd/Timeline.Tests/IntegratedTests/HighlightTimelineTest.cs
+++ b/BackEnd/Timeline.Tests/IntegratedTests/HighlightTimelineTest.cs
@@ -28,6 +28,7 @@ namespace Timeline.Tests.IntegratedTests
await client.TestPutAssertInvalidModelAsync("highlights/!!!");
await client.TestDeleteAssertInvalidModelAsync("highlights/!!!");
await client.TestPostAssertInvalidModelAsync("highlightop/move", new HttpHighlightTimelineMoveRequest { Timeline = null!, NewPosition = 1 });
+ await client.TestPostAssertInvalidModelAsync("highlightop/move", new HttpHighlightTimelineMoveRequest { Timeline = "!!!", NewPosition = 1 });
await client.TestPostAssertInvalidModelAsync("highlightop/move", new HttpHighlightTimelineMoveRequest { Timeline = "aaa", NewPosition = null });
}
diff --git a/BackEnd/Timeline.Tests/IntegratedTests/HttpClientTestExtensions.cs b/BackEnd/Timeline.Tests/IntegratedTests/HttpClientTestExtensions.cs
index ec517362..b219f092 100644
--- a/BackEnd/Timeline.Tests/IntegratedTests/HttpClientTestExtensions.cs
+++ b/BackEnd/Timeline.Tests/IntegratedTests/HttpClientTestExtensions.cs
@@ -192,6 +192,11 @@ namespace Timeline.Tests.IntegratedTests
await client.TestJsonSendAssertUnauthorizedAsync(HttpMethod.Patch, url, jsonBody, errorCode, headerSetup);
}
+ public static async Task TestPutAssertUnauthorizedAsync(this HttpClient client, string url, object? jsonBody = null, int? errorCode = null, HeaderSetup? headerSetup = null)
+ {
+ await client.TestJsonSendAssertUnauthorizedAsync(HttpMethod.Put, url, jsonBody, errorCode, headerSetup);
+ }
+
public static async Task TestDeleteAssertUnauthorizedAsync(this HttpClient client, string url, object? jsonBody = null, int? errorCode = null, HeaderSetup? headerSetup = null)
{
await client.TestJsonSendAssertUnauthorizedAsync(HttpMethod.Delete, url, jsonBody, errorCode, headerSetup);
diff --git a/BackEnd/Timeline/Controllers/BookmarkTimelineController.cs b/BackEnd/Timeline/Controllers/BookmarkTimelineController.cs
new file mode 100644
index 00000000..9dff95f3
--- /dev/null
+++ b/BackEnd/Timeline/Controllers/BookmarkTimelineController.cs
@@ -0,0 +1,114 @@
+using AutoMapper;
+using Microsoft.AspNetCore.Authorization;
+using Microsoft.AspNetCore.Mvc;
+using System.Collections.Generic;
+using System.Threading.Tasks;
+using Timeline.Models.Http;
+using Timeline.Models.Validation;
+using Timeline.Services;
+using Timeline.Services.Exceptions;
+
+namespace Timeline.Controllers
+{
+ /// <summary>
+ /// Api related to timeline bookmarks.
+ /// </summary>
+ [ApiController]
+ [ProducesErrorResponseType(typeof(CommonResponse))]
+ public class BookmarkTimelineController : Controller
+ {
+ private readonly IBookmarkTimelineService _service;
+
+ private readonly IMapper _mapper;
+
+ public BookmarkTimelineController(IBookmarkTimelineService service, IMapper mapper)
+ {
+ _service = service;
+ _mapper = mapper;
+ }
+
+ /// <summary>
+ /// Get bookmark list in order.
+ /// </summary>
+ /// <returns>Bookmarks.</returns>
+ [HttpGet("bookmarks")]
+ [Authorize]
+ [ProducesResponseType(200)]
+ [ProducesResponseType(401)]
+ public async Task<ActionResult<List<HttpTimeline>>> List()
+ {
+ var bookmarks = await _service.GetBookmarks(this.GetUserId());
+ return Ok(_mapper.Map<List<HttpTimeline>>(bookmarks));
+ }
+
+ /// <summary>
+ /// Add a bookmark.
+ /// </summary>
+ /// <param name="timeline">Timeline name.</param>
+ [HttpPut("bookmarks/{timeline}")]
+ [Authorize]
+ [ProducesResponseType(200)]
+ [ProducesResponseType(400)]
+ [ProducesResponseType(401)]
+ public async Task<ActionResult> Put([GeneralTimelineName] string timeline)
+ {
+ try
+ {
+ await _service.AddBookmark(this.GetUserId(), timeline);
+ return Ok();
+ }
+ catch (TimelineNotExistException)
+ {
+ return BadRequest(ErrorResponse.TimelineController.NotExist());
+ }
+ }
+
+ /// <summary>
+ /// Remove a bookmark.
+ /// </summary>
+ /// <param name="timeline">Timeline name.</param>
+ [HttpDelete("bookmarks/{timeline}")]
+ [Authorize]
+ [ProducesResponseType(200)]
+ [ProducesResponseType(400)]
+ [ProducesResponseType(401)]
+ public async Task<ActionResult> Delete([GeneralTimelineName] string timeline)
+ {
+ try
+ {
+ await _service.RemoveBookmark(this.GetUserId(), timeline);
+ return Ok();
+ }
+ catch (TimelineNotExistException)
+ {
+ return BadRequest(ErrorResponse.TimelineController.NotExist());
+ }
+ }
+
+ /// <summary>
+ /// Move a bookmark to new posisition.
+ /// </summary>
+ /// <param name="request">Request body.</param>
+ [HttpPost("bookmarkop/move")]
+ [Authorize]
+ [ProducesResponseType(200)]
+ [ProducesResponseType(400)]
+ [ProducesResponseType(401)]
+ public async Task<ActionResult> Move([FromBody] HttpBookmarkTimelineMoveRequest request)
+ {
+ try
+ {
+ await _service.MoveBookmark(this.GetUserId(), request.Timeline, request.NewPosition!.Value);
+ return Ok();
+ }
+ catch (TimelineNotExistException)
+ {
+ return BadRequest(ErrorResponse.TimelineController.NotExist());
+ }
+ catch (InvalidBookmarkException)
+ {
+ return BadRequest(new CommonResponse(ErrorCodes.BookmarkTimelineController.NonBookmark, "You can't move a non-bookmark timeline."));
+ }
+ }
+ }
+}
diff --git a/BackEnd/Timeline/Controllers/HighlightTimelineController.cs b/BackEnd/Timeline/Controllers/HighlightTimelineController.cs
index 0b6e1665..519d6161 100644
--- a/BackEnd/Timeline/Controllers/HighlightTimelineController.cs
+++ b/BackEnd/Timeline/Controllers/HighlightTimelineController.cs
@@ -46,6 +46,8 @@ namespace Timeline.Controllers
[PermissionAuthorize(UserPermission.HighlightTimelineManagement)]
[ProducesResponseType(200)]
[ProducesResponseType(400)]
+ [ProducesResponseType(401)]
+ [ProducesResponseType(403)]
public async Task<ActionResult> Put([GeneralTimelineName] string timeline)
{
try
@@ -67,6 +69,8 @@ namespace Timeline.Controllers
[PermissionAuthorize(UserPermission.HighlightTimelineManagement)]
[ProducesResponseType(200)]
[ProducesResponseType(400)]
+ [ProducesResponseType(401)]
+ [ProducesResponseType(403)]
public async Task<ActionResult> Delete([GeneralTimelineName] string timeline)
{
try
@@ -81,12 +85,14 @@ namespace Timeline.Controllers
}
/// <summary>
- /// Move a highlight position.
+ /// Move a highlight to new position.
/// </summary>
[HttpPost("highlightop/move")]
[PermissionAuthorize(UserPermission.HighlightTimelineManagement)]
[ProducesResponseType(200)]
[ProducesResponseType(400)]
+ [ProducesResponseType(401)]
+ [ProducesResponseType(403)]
public async Task<ActionResult> Move([FromBody] HttpHighlightTimelineMoveRequest body)
{
try
diff --git a/BackEnd/Timeline/Models/Http/BookmarkTimeline.cs b/BackEnd/Timeline/Models/Http/BookmarkTimeline.cs
new file mode 100644
index 00000000..14be1112
--- /dev/null
+++ b/BackEnd/Timeline/Models/Http/BookmarkTimeline.cs
@@ -0,0 +1,23 @@
+using System.ComponentModel.DataAnnotations;
+using Timeline.Models.Validation;
+
+namespace Timeline.Models.Http
+{
+ /// <summary>
+ /// Move bookmark timeline request body model.
+ /// </summary>
+ public class HttpBookmarkTimelineMoveRequest
+ {
+ /// <summary>
+ /// Timeline name.
+ /// </summary>
+ [GeneralTimelineName]
+ public string Timeline { get; set; } = default!;
+
+ /// <summary>
+ /// New position, starting at 1.
+ /// </summary>
+ [Required]
+ public long? NewPosition { get; set; }
+ }
+}
diff --git a/BackEnd/Timeline/Models/Http/HighlightTimeline.cs b/BackEnd/Timeline/Models/Http/HighlightTimeline.cs
index e5aed068..5af0e528 100644
--- a/BackEnd/Timeline/Models/Http/HighlightTimeline.cs
+++ b/BackEnd/Timeline/Models/Http/HighlightTimeline.cs
@@ -8,9 +8,15 @@ namespace Timeline.Models.Http
/// </summary>
public class HttpHighlightTimelineMoveRequest
{
+ /// <summary>
+ /// Timeline name.
+ /// </summary>
[GeneralTimelineName]
public string Timeline { get; set; } = default!;
+ /// <summary>
+ /// New position, starting at 1.
+ /// </summary>
[Required]
public long? NewPosition { get; set; }
}
diff --git a/BackEnd/Timeline/Startup.cs b/BackEnd/Timeline/Startup.cs
index d20fc54b..66c708ac 100644
--- a/BackEnd/Timeline/Startup.cs
+++ b/BackEnd/Timeline/Startup.cs
@@ -102,6 +102,7 @@ namespace Timeline
services.AddScoped<ITimelinePostService, TimelinePostService>();
services.AddScoped<IHighlightTimelineService, HighlightTimelineService>();
+ services.AddScoped<IBookmarkTimelineService, BookmarkTimelineService>();
services.AddDbContext<DatabaseContext>((services, options) =>
{