From 9e84b1e9ad1f2a45cd3e09759c69989fdc588c3d Mon Sep 17 00:00:00 2001 From: crupest Date: Sun, 2 Feb 2020 00:31:33 +0800 Subject: ... --- .../IntegratedTests/PersonalTimelineTest.cs | 14 +-- Timeline/Controllers/PersonalTimelineController.cs | 8 +- Timeline/Models/Http/TimelineCommon.cs | 36 +++++- Timeline/Services/TimelineService.cs | 133 +++++++++------------ 4 files changed, 101 insertions(+), 90 deletions(-) diff --git a/Timeline.Tests/IntegratedTests/PersonalTimelineTest.cs b/Timeline.Tests/IntegratedTests/PersonalTimelineTest.cs index 81446fd8..cc170a98 100644 --- a/Timeline.Tests/IntegratedTests/PersonalTimelineTest.cs +++ b/Timeline.Tests/IntegratedTests/PersonalTimelineTest.cs @@ -25,7 +25,7 @@ namespace Timeline.Tests.IntegratedTests using var client = await CreateDefaultClient(); var res = await client.GetAsync("users/user1/timeline"); var body = res.Should().HaveStatusCode(200) - .And.HaveJsonBody().Which; + .And.HaveJsonBody().Which; body.Owner.Should().BeEquivalentTo(UserInfos[1]); body.Visibility.Should().Be(TimelineVisibility.Register); body.Description.Should().Be(""); @@ -109,7 +109,7 @@ namespace Timeline.Tests.IntegratedTests { var res = await client.GetAsync("users/user1/timeline"); var body = res.Should().HaveStatusCode(200) - .And.HaveJsonBody() + .And.HaveJsonBody() .Which.Description.Should().Be(description); } @@ -120,21 +120,21 @@ namespace Timeline.Tests.IntegratedTests var res = await client.PatchAsJsonAsync("users/user1/timeline", new TimelinePatchRequest { Description = mockDescription }); res.Should().HaveStatusCode(200) - .And.HaveJsonBody().Which.Description.Should().Be(mockDescription); + .And.HaveJsonBody().Which.Description.Should().Be(mockDescription); await AssertDescription(mockDescription); } { var res = await client.PatchAsJsonAsync("users/user1/timeline", new TimelinePatchRequest { Description = null }); res.Should().HaveStatusCode(200) - .And.HaveJsonBody().Which.Description.Should().Be(mockDescription); + .And.HaveJsonBody().Which.Description.Should().Be(mockDescription); await AssertDescription(mockDescription); } { var res = await client.PatchAsJsonAsync("users/user1/timeline", new TimelinePatchRequest { Description = "" }); res.Should().HaveStatusCode(200) - .And.HaveJsonBody().Which.Description.Should().Be(""); + .And.HaveJsonBody().Which.Description.Should().Be(""); await AssertDescription(""); } } @@ -149,7 +149,7 @@ namespace Timeline.Tests.IntegratedTests { var res = await client.GetAsync(getUrl); res.Should().HaveStatusCode(200) - .And.HaveJsonBody() + .And.HaveJsonBody() .Which.Members.Should().NotBeNull().And.BeEquivalentTo(members); } @@ -157,7 +157,7 @@ namespace Timeline.Tests.IntegratedTests { var res = await client.GetAsync(getUrl); res.Should().HaveStatusCode(200) - .And.HaveJsonBody() + .And.HaveJsonBody() .Which.Members.Should().NotBeNull().And.BeEmpty(); } diff --git a/Timeline/Controllers/PersonalTimelineController.cs b/Timeline/Controllers/PersonalTimelineController.cs index 11353bb5..842da015 100644 --- a/Timeline/Controllers/PersonalTimelineController.cs +++ b/Timeline/Controllers/PersonalTimelineController.cs @@ -26,9 +26,9 @@ namespace Timeline.Controllers } [HttpGet("users/{username}/timeline")] - public async Task> TimelineGet([FromRoute][Username] string username) + public async Task> TimelineGet([FromRoute][Username] string username) { - return await _service.GetTimeline(username); + return (await _service.GetTimeline(username)).FillLinks(Url); } [HttpGet("users/{username}/timeline/posts")] @@ -77,14 +77,14 @@ namespace Timeline.Controllers [HttpPatch("users/{username}/timeline")] [Authorize] - public async Task> TimelinePatch([FromRoute][Username] string username, [FromBody] TimelinePatchRequest body) + public async Task> TimelinePatch([FromRoute][Username] string username, [FromBody] TimelinePatchRequest body) { if (!this.IsAdministrator() && !(User.Identity.Name == username)) { return StatusCode(StatusCodes.Status403Forbidden, ErrorResponse.Common.Forbid()); } await _service.ChangeProperty(username, body); - var timeline = await _service.GetTimeline(username); + var timeline = (await _service.GetTimeline(username)).FillLinks(Url); return Ok(timeline); } diff --git a/Timeline/Models/Http/TimelineCommon.cs b/Timeline/Models/Http/TimelineCommon.cs index febb8186..0b2a714c 100644 --- a/Timeline/Models/Http/TimelineCommon.cs +++ b/Timeline/Models/Http/TimelineCommon.cs @@ -1,5 +1,7 @@ -using System; +using Microsoft.AspNetCore.Mvc; +using System; using System.Collections.Generic; +using Timeline.Controllers; namespace Timeline.Models.Http { @@ -28,17 +30,41 @@ namespace Timeline.Models.Http public DateTime LastUpdated { get; set; } = default!; } - [System.Diagnostics.CodeAnalysis.SuppressMessage("Usage", "CA2227:Collection properties should be read only", Justification = "This is a DTO class.")] - public class BaseTimelineInfo + public class TimelineInfo { + public string? Name { get; set; } public string Description { get; set; } = default!; public UserInfo Owner { get; set; } = default!; public TimelineVisibility Visibility { get; set; } +#pragma warning disable CA2227 // Collection properties should be read only public List Members { get; set; } = default!; +#pragma warning restore CA2227 // Collection properties should be read only + +#pragma warning disable CA1707 // Identifiers should not contain underscores + public TimelineInfoLinks? _links { get; set; } +#pragma warning restore CA1707 // Identifiers should not contain underscores + } + + public class TimelineInfoLinks + { + public string Posts { get; set; } = default!; } - public class TimelineInfo : BaseTimelineInfo + public static class TimelineInfoExtensions { - public string Name { get; set; } = default!; + public static TimelineInfo FillLinks(this TimelineInfo info, IUrlHelper urlHelper) + { + if (info == null) + throw new ArgumentNullException(nameof(info)); + if (urlHelper == null) + throw new ArgumentNullException(nameof(urlHelper)); + + info._links = new TimelineInfoLinks + { + Posts = urlHelper.ActionLink(nameof(PersonalTimelineController.PostListGet), nameof(PersonalTimelineController)[0..^nameof(Controller).Length], new { info.Owner.Username }) + }; + + return info; + } } } diff --git a/Timeline/Services/TimelineService.cs b/Timeline/Services/TimelineService.cs index 0ea68265..a16237ca 100644 --- a/Timeline/Services/TimelineService.cs +++ b/Timeline/Services/TimelineService.cs @@ -24,6 +24,37 @@ namespace Timeline.Services /// public interface IBaseTimelineService { + /// + /// Get the timeline info. + /// + /// Username or the timeline name. See remarks of . + /// The timeline info. + /// Thrown when is null. + /// Thrown when is illegal. It is not a valid timeline name (for normal timeline service) or a valid username (for personal timeline service). + /// + /// 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 . + /// + Task GetTimeline(string name); + + /// + /// Set the properties of a timeline. + /// + /// Username or the timeline name. See remarks of . + /// The new properties. Null member means not to change. + /// The timeline info. + /// Thrown when is null. + /// Thrown when is illegal. It is not a valid timeline name (for normal timeline service) or a valid username (for personal timeline service). + /// + /// 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 . + /// + Task ChangeProperty(string name, TimelinePatchRequest newProperties); + /// /// Get all the posts in the timeline. /// @@ -177,20 +208,6 @@ namespace Timeline.Services /// public interface ITimelineService : IBaseTimelineService { - /// - /// Get the timeline info. - /// - /// The name of the timeline. - /// The timeline info. - /// Thrown when is null. - /// - /// Thrown when timeline name is invalid. Currently it means it is an empty string. - /// - /// - /// Thrown when timeline with the name does not exist. - /// - Task GetTimeline(string name); - /// /// Create a timeline. /// @@ -205,37 +222,6 @@ namespace Timeline.Services public interface IPersonalTimelineService : IBaseTimelineService { - /// - /// Get the timeline info. - /// - /// The username of the owner of the personal timeline. - /// The timeline info. - /// - /// Thrown when is null. - /// - /// - /// Thrown when is of bad format. - /// - /// - /// Thrown when the user does not exist. Inner exception MUST be . - /// - Task GetTimeline(string username); - - /// - /// Set the properties of a timeline. - /// - /// Username or the timeline name. See remarks of . - /// The new properties. Null member means not to change. - /// - /// Thrown when is null. - /// - /// - /// Thrown when is of bad format. - /// - /// - /// Thrown when the user does not exist. Inner exception MUST be . - /// - Task ChangeProperty(string name, TimelinePatchRequest newProperties); } @@ -283,6 +269,34 @@ namespace Timeline.Services /// protected abstract Task FindTimelineId(string name); + public async Task GetTimeline(string name) + { + if (name == null) + throw new ArgumentNullException(nameof(name)); + + var timelineId = await FindTimelineId(name); + + var timelineEntity = await Database.Timelines.Where(t => t.Id == timelineId).SingleAsync(); + + var timelineMemberEntities = await Database.TimelineMembers.Where(m => m.TimelineId == timelineId).Select(m => new { m.UserId }).ToListAsync(); + + var owner = Mapper.Map(await UserService.GetUserById(timelineEntity.OwnerId)); + + var members = new List(); + foreach (var memberEntity in timelineMemberEntities) + { + members.Add(Mapper.Map(await UserService.GetUserById(memberEntity.UserId))); + } + + return new TimelineInfo + { + Description = timelineEntity.Description ?? "", + Owner = owner, + Visibility = timelineEntity.Visibility, + Members = members + }; + } + public async Task> GetPosts(string name) { if (name == null) @@ -569,34 +583,5 @@ namespace Timeline.Services return newTimelineEntity.Id; } } - - public async Task GetTimeline(string username) - { - if (username == null) - throw new ArgumentNullException(nameof(username)); - - var timelineId = await FindTimelineId(username); - - var timelineEntity = await Database.Timelines.Where(t => t.Id == timelineId).SingleAsync(); - - var timelineMemberEntities = await Database.TimelineMembers.Where(m => m.TimelineId == timelineId).Select(m => new { m.UserId }).ToListAsync(); - - var owner = Mapper.Map(await UserService.GetUserById(timelineEntity.OwnerId)); - - var members = new List(); - foreach (var memberEntity in timelineMemberEntities) - { - members.Add(Mapper.Map(await UserService.GetUserById(memberEntity.UserId))); - } - - return new BaseTimelineInfo - { - Description = timelineEntity.Description ?? "", - Owner = owner, - Visibility = timelineEntity.Visibility, - Members = members - }; - } - } } -- cgit v1.2.3