From df1ef1e21d8d889a2c9abd440039533c6a43818f Mon Sep 17 00:00:00 2001 From: crupest Date: Thu, 7 Jan 2021 16:23:20 +0800 Subject: 史诗级重构! MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- BackEnd/Timeline.ErrorCodes/ErrorCodes.cs | 5 +- BackEnd/Timeline.Tests/Helpers/TestDatabase.cs | 18 +- .../IntegratedTests/IntegratedTestBase.cs | 5 +- .../Timeline.Tests/IntegratedTests/TimelineTest.cs | 32 +- .../Services/BookmarkTimelineServiceTest.cs | 102 ----- .../Services/HighlightTimelineServiceTest.cs | 96 ---- .../Services/TimelinePostServiceTest.cs | 200 --------- .../Timeline.Tests/Services/TimelineServiceTest.cs | 149 ------ BackEnd/Timeline/Auth/MyAuthenticationHandler.cs | 2 +- .../Controllers/BookmarkTimelineController.cs | 16 +- .../Controllers/HighlightTimelineController.cs | 12 +- BackEnd/Timeline/Controllers/TimelineController.cs | 185 ++++---- BackEnd/Timeline/Controllers/TokenController.cs | 10 +- BackEnd/Timeline/Controllers/UserController.cs | 15 +- BackEnd/Timeline/GlobalSuppressions.cs | 1 + ...210107074715_AddRootUserPermissions.Designer.cs | 498 +++++++++++++++++++++ .../20210107074715_AddRootUserPermissions.cs | 20 + BackEnd/Timeline/Models/Http/Common.cs | 120 ----- BackEnd/Timeline/Models/Http/CommonResponse.cs | 130 ++++++ BackEnd/Timeline/Models/Http/ErrorResponse.cs | 10 - BackEnd/Timeline/Models/Http/Timeline.cs | 137 ++---- BackEnd/Timeline/Models/Http/TimelineController.cs | 3 +- BackEnd/Timeline/Models/Http/User.cs | 73 +-- BackEnd/Timeline/Models/Mapper/TimelineMapper.cs | 85 ++++ BackEnd/Timeline/Models/Mapper/UserMapper.cs | 38 ++ BackEnd/Timeline/Models/Timeline.cs | 27 ++ BackEnd/Timeline/Models/TimelineInfo.cs | 130 ------ BackEnd/Timeline/Models/UserInfo.cs | 48 -- .../Resources/Services/Exceptions.Designer.cs | 4 +- .../Timeline/Resources/Services/Exceptions.resx | 144 +++--- BackEnd/Timeline/Services/BasicTimelineService.cs | 12 + .../Timeline/Services/BookmarkTimelineService.cs | 20 +- .../Exceptions/TimelinePostNotExistException.cs | 17 +- .../Timeline/Services/HighlightTimelineService.cs | 20 +- BackEnd/Timeline/Services/TimelinePostService.cs | 221 +++------ BackEnd/Timeline/Services/TimelineService.cs | 449 +++++++------------ BackEnd/Timeline/Services/UserService.cs | 56 +-- BackEnd/Timeline/Services/UserTokenManager.cs | 7 +- 38 files changed, 1389 insertions(+), 1728 deletions(-) delete mode 100644 BackEnd/Timeline.Tests/Services/BookmarkTimelineServiceTest.cs delete mode 100644 BackEnd/Timeline.Tests/Services/HighlightTimelineServiceTest.cs delete mode 100644 BackEnd/Timeline.Tests/Services/TimelinePostServiceTest.cs delete mode 100644 BackEnd/Timeline.Tests/Services/TimelineServiceTest.cs create mode 100644 BackEnd/Timeline/Migrations/20210107074715_AddRootUserPermissions.Designer.cs create mode 100644 BackEnd/Timeline/Migrations/20210107074715_AddRootUserPermissions.cs delete mode 100644 BackEnd/Timeline/Models/Http/Common.cs create mode 100644 BackEnd/Timeline/Models/Http/CommonResponse.cs create mode 100644 BackEnd/Timeline/Models/Mapper/TimelineMapper.cs create mode 100644 BackEnd/Timeline/Models/Mapper/UserMapper.cs create mode 100644 BackEnd/Timeline/Models/Timeline.cs delete mode 100644 BackEnd/Timeline/Models/TimelineInfo.cs delete mode 100644 BackEnd/Timeline/Models/UserInfo.cs diff --git a/BackEnd/Timeline.ErrorCodes/ErrorCodes.cs b/BackEnd/Timeline.ErrorCodes/ErrorCodes.cs index c65bf26e..b8ec63ec 100644 --- a/BackEnd/Timeline.ErrorCodes/ErrorCodes.cs +++ b/BackEnd/Timeline.ErrorCodes/ErrorCodes.cs @@ -1,4 +1,6 @@ -namespace Timeline.Models.Http +using System; + +namespace Timeline.Models.Http { /// /// All error code constants. @@ -58,7 +60,6 @@ { public const int NameConflict = 1_104_01_01; public const int NotExist = 1_104_02_01; - public const int MemberPut_NotExist = 1_104_03_01; public const int QueryRelateNotExist = 1_104_04_01; public const int PostNotExist = 1_104_05_01; public const int PostNoData = 1_104_05_02; diff --git a/BackEnd/Timeline.Tests/Helpers/TestDatabase.cs b/BackEnd/Timeline.Tests/Helpers/TestDatabase.cs index 00164835..7b9a992f 100644 --- a/BackEnd/Timeline.Tests/Helpers/TestDatabase.cs +++ b/BackEnd/Timeline.Tests/Helpers/TestDatabase.cs @@ -33,16 +33,22 @@ namespace Timeline.Tests.Helpers { Key = JwtTokenGenerateHelper.GenerateKey() }); + await context.SaveChangesAsync(); - if (_createUser) - { - var passwordService = new PasswordService(); - var userService = new UserService(NullLogger.Instance, context, passwordService, new UserPermissionService(context), new Clock()); + var passwordService = new PasswordService(); + var userService = new UserService(NullLogger.Instance, context, passwordService, new Clock()); - var admin = await userService.CreateUser("admin", "adminpw"); - await userService.ModifyUser(admin.Id, new ModifyUserParams() { Nickname = "administrator" }); + var admin = await userService.CreateUser("admin", "adminpw"); + await userService.ModifyUser(admin.Id, new ModifyUserParams() { Nickname = "administrator" }); + admin.Permissions.Add(new UserPermissionEntity { Permission = UserPermission.AllTimelineManagement.ToString() }); + admin.Permissions.Add(new UserPermissionEntity { Permission = UserPermission.HighlightTimelineManagement.ToString() }); + admin.Permissions.Add(new UserPermissionEntity { Permission = UserPermission.UserManagement.ToString() }); + await context.SaveChangesAsync(); + + if (_createUser) + { var user = await userService.CreateUser("user", "userpw"); await userService.ModifyUser(user.Id, new ModifyUserParams() { Nickname = "imuser" }); } diff --git a/BackEnd/Timeline.Tests/IntegratedTests/IntegratedTestBase.cs b/BackEnd/Timeline.Tests/IntegratedTests/IntegratedTestBase.cs index 82aed24e..b700ada2 100644 --- a/BackEnd/Timeline.Tests/IntegratedTests/IntegratedTestBase.cs +++ b/BackEnd/Timeline.Tests/IntegratedTests/IntegratedTestBase.cs @@ -56,10 +56,7 @@ namespace Timeline.Tests.IntegratedTests { using var scope = TestApp.Host.Services.CreateScope(); - var users = new List<(string username, string password, string nickname)>() - { - ("admin", "adminpw", "administrator") - }; + var users = new List<(string username, string password, string nickname)>(); for (int i = 1; i <= TestUserCount; i++) { diff --git a/BackEnd/Timeline.Tests/IntegratedTests/TimelineTest.cs b/BackEnd/Timeline.Tests/IntegratedTests/TimelineTest.cs index 12dd2b8d..f0715082 100644 --- a/BackEnd/Timeline.Tests/IntegratedTests/TimelineTest.cs +++ b/BackEnd/Timeline.Tests/IntegratedTests/TimelineTest.cs @@ -21,11 +21,7 @@ namespace Timeline.Tests.IntegratedTests { public static HttpTimelinePostContent TextPostContent(string text) { - return new HttpTimelinePostContent - { - Type = "text", - Text = text - }; + return new HttpTimelinePostContent("text", text, null, null); } public static HttpTimelinePostCreateRequest TextPostCreateRequest(string text, DateTime? time = null) @@ -261,15 +257,15 @@ namespace Timeline.Tests.IntegratedTests using var client = await CreateClientAsAdministrator(); await client.TestDeleteAssertInvalidModelAsync("timelines/!!!"); - await client.TestDeleteAsync("timelines/t2", true); - await client.TestDeleteAsync("timelines/t2", false); + await client.TestDeleteAsync("timelines/t2"); + await client.TestDeleteAssertErrorAsync("timelines/t2"); } { using var client = await CreateClientAs(1); await client.TestDeleteAssertInvalidModelAsync("timelines/!!!"); - await client.TestDeleteAsync("timelines/t1", true); + await client.TestDeleteAsync("timelines/t1"); await client.TestDeleteAssertErrorAsync("timelines/t1"); } } @@ -355,13 +351,13 @@ namespace Timeline.Tests.IntegratedTests } await AssertEmptyMembers(); - await client.TestPutAssertErrorAsync($"timelines/{timelineName}/members/usernotexist", errorCode: ErrorCodes.TimelineController.MemberPut_NotExist); + await client.TestPutAssertErrorAsync($"timelines/{timelineName}/members/usernotexist", errorCode: ErrorCodes.UserCommon.NotExist); await AssertEmptyMembers(); await client.PutTimelineMemberAsync(timelineName, "user2"); await AssertMembers(new List { await client.GetUserAsync("user2") }); await client.DeleteTimelineMemberAsync(timelineName, "user2", true); await AssertEmptyMembers(); - await client.DeleteTimelineMemberAsync(timelineName, "aaa", false); + await client.TestDeleteAssertErrorAsync($"timelines/{timelineName}/members/usernotexist", errorCode: ErrorCodes.UserCommon.NotExist); await AssertEmptyMembers(); } @@ -549,9 +545,9 @@ namespace Timeline.Tests.IntegratedTests body.Should().BeEquivalentTo(createRes, createRes2); } { - await client.TestDeleteAsync($"timelines/{generator(1)}/posts/{createRes.Id}", true); - await client.TestDeleteAsync($"timelines/{generator(1)}/posts/{createRes.Id}", false); - await client.TestDeleteAsync($"timelines/{generator(1)}/posts/30000", false); + await client.TestDeleteAsync($"timelines/{generator(1)}/posts/{createRes.Id}"); + await client.TestDeleteAssertErrorAsync($"timelines/{generator(1)}/posts/{createRes.Id}"); + await client.TestDeleteAssertErrorAsync($"timelines/{generator(1)}/posts/30000"); } { var body = await client.TestGetAsync>($"timelines/{generator(1)}/posts"); @@ -651,8 +647,8 @@ namespace Timeline.Tests.IntegratedTests } await CacheTestHelper.TestCache(client, $"timelines/{generator(1)}/posts/{postId}/data"); - await client.TestDeleteAsync($"timelines/{generator(1)}/posts/{postId}", true); - await client.TestDeleteAsync($"timelines/{generator(1)}/posts/{postId}", false); + await client.TestDeleteAsync($"timelines/{generator(1)}/posts/{postId}"); + await client.TestDeleteAssertErrorAsync($"timelines/{generator(1)}/posts/{postId}"); { var body = await client.TestGetAsync>($"timelines/{generator(1)}/posts"); @@ -736,7 +732,7 @@ namespace Timeline.Tests.IntegratedTests await Task.Delay(1000); } - await client.TestDeleteAsync($"timelines/{generator(1)}/posts/{posts[2].Id}", true); + await client.TestDeleteAsync($"timelines/{generator(1)}/posts/{posts[2].Id}"); { var body = await client.TestGetAsync>($"timelines/{generator(1)}/posts?modifiedSince={posts[1].LastUpdated.ToString("s", CultureInfo.InvariantCulture) }"); @@ -763,7 +759,7 @@ namespace Timeline.Tests.IntegratedTests foreach (var id in new long[] { posts[0].Id, posts[2].Id }) { - await client.TestDeleteAsync($"timelines/{generator(1)}/posts/{id}", true); + await client.TestDeleteAsync($"timelines/{generator(1)}/posts/{id}"); } { @@ -791,7 +787,7 @@ namespace Timeline.Tests.IntegratedTests await Task.Delay(1000); } - await client.TestDeleteAsync($"timelines/{generator(1)}/posts/{posts[2].Id}", true); + await client.TestDeleteAsync($"timelines/{generator(1)}/posts/{posts[2].Id}"); { diff --git a/BackEnd/Timeline.Tests/Services/BookmarkTimelineServiceTest.cs b/BackEnd/Timeline.Tests/Services/BookmarkTimelineServiceTest.cs deleted file mode 100644 index 849936ec..00000000 --- a/BackEnd/Timeline.Tests/Services/BookmarkTimelineServiceTest.cs +++ /dev/null @@ -1,102 +0,0 @@ -using FluentAssertions; -using Microsoft.Extensions.Logging.Abstractions; -using System.Threading.Tasks; -using Timeline.Services; -using Timeline.Tests.Helpers; -using Xunit; - -namespace Timeline.Tests.Services -{ - public class BookmarkTimelineServiceTest : DatabaseBasedTest - { - private BookmarkTimelineService _service = default!; - private UserService _userService = default!; - private TimelineService _timelineService = default!; - - protected override void OnDatabaseCreated() - { - var clock = new TestClock(); - _userService = new UserService(NullLogger.Instance, Database, new PasswordService(), new UserPermissionService(Database), clock); - _timelineService = new TimelineService(Database, _userService, clock); - _service = new BookmarkTimelineService(Database, _userService, _timelineService); - } - - [Fact] - public async Task Should_Work() - { - var userId = await _userService.GetUserIdByUsername("user"); - - { - var b = await _service.GetBookmarks(userId); - b.Should().BeEmpty(); - } - - await _timelineService.CreateTimeline("tl", userId); - await _service.AddBookmark(userId, "tl"); - - { - var b = await _service.GetBookmarks(userId); - b.Should().HaveCount(1).And.BeEquivalentTo(await _timelineService.GetTimeline("tl")); - } - } - - [Fact] - public async Task NewOne_Should_BeAtLast() - { - var userId = await _userService.GetUserIdByUsername("user"); - await _timelineService.CreateTimeline("t1", userId); - await _service.AddBookmark(userId, "t1"); - - await _timelineService.CreateTimeline("t2", userId); - await _service.AddBookmark(userId, "t2"); - - var b = await _service.GetBookmarks(userId); - - b.Should().HaveCount(2); - b[0].Name.Should().Be("t1"); - b[1].Name.Should().Be("t2"); - } - - [Fact] - public async Task Multiple_Should_Work() - { - var userId = await _userService.GetUserIdByUsername("user"); - - // make timeline id not same as entity id. - await _timelineService.CreateTimeline("t0", userId); - - await _timelineService.CreateTimeline("t1", userId); - await _service.AddBookmark(userId, "t1"); - - await _timelineService.CreateTimeline("t2", userId); - await _service.AddBookmark(userId, "t2"); - - await _timelineService.CreateTimeline("t3", userId); - await _service.AddBookmark(userId, "t3"); - - await _service.MoveBookmark(userId, "t3", 2); - (await _service.GetBookmarks(userId))[1].Name.Should().Be("t3"); - - await _service.MoveBookmark(userId, "t1", 3); - (await _service.GetBookmarks(userId))[2].Name.Should().Be("t1"); - - await _service.RemoveBookmark(userId, "t2"); - await _service.RemoveBookmark(userId, "t1"); - await _service.RemoveBookmark(userId, "t3"); - (await _service.GetBookmarks(userId)).Should().BeEmpty(); - } - - [Fact] - public async Task AddExist_Should_DoNothing() - { - var userId = await _userService.GetUserIdByUsername("user"); - - await _timelineService.CreateTimeline("t", userId); - - await _service.AddBookmark(userId, "t"); - await _service.AddBookmark(userId, "t"); - - (await _service.GetBookmarks(userId)).Should().HaveCount(1); - } - } -} diff --git a/BackEnd/Timeline.Tests/Services/HighlightTimelineServiceTest.cs b/BackEnd/Timeline.Tests/Services/HighlightTimelineServiceTest.cs deleted file mode 100644 index f48404a9..00000000 --- a/BackEnd/Timeline.Tests/Services/HighlightTimelineServiceTest.cs +++ /dev/null @@ -1,96 +0,0 @@ -using FluentAssertions; -using Microsoft.Extensions.Logging.Abstractions; -using System.Threading.Tasks; -using Timeline.Services; -using Timeline.Tests.Helpers; -using Xunit; -using Xunit.Abstractions; - -namespace Timeline.Tests.Services -{ - public class HighlightTimelineServiceTest : DatabaseBasedTest - { - private readonly TestClock _clock = new TestClock(); - private UserService _userService = default!; - private TimelineService _timelineService = default!; - - private HighlightTimelineService _service = default!; - - public HighlightTimelineServiceTest(ITestOutputHelper testOutputHelper) - : base(testOutputHelper) - { - - } - - protected override void OnDatabaseCreated() - { - _userService = new UserService(NullLogger.Instance, Database, new PasswordService(), new UserPermissionService(Database), _clock); - _timelineService = new TimelineService(Database, _userService, _clock); - _service = new HighlightTimelineService(Database, _userService, _timelineService, _clock); - } - - [Fact] - public async Task Should_Work() - { - { - var ht = await _service.GetHighlightTimelines(); - ht.Should().BeEmpty(); - } - - var userId = await _userService.GetUserIdByUsername("user"); - await _timelineService.CreateTimeline("tl", userId); - await _service.AddHighlightTimeline("tl", userId); - - { - var ht = await _service.GetHighlightTimelines(); - ht.Should().HaveCount(1).And.BeEquivalentTo(await _timelineService.GetTimeline("tl")); - } - } - - [Fact] - public async Task NewOne_Should_BeAtLast() - { - var userId = await _userService.GetUserIdByUsername("user"); - await _timelineService.CreateTimeline("t1", userId); - await _service.AddHighlightTimeline("t1", userId); - - await _timelineService.CreateTimeline("t2", userId); - await _service.AddHighlightTimeline("t2", userId); - - var ht = await _service.GetHighlightTimelines(); - - ht.Should().HaveCount(2); - ht[0].Name.Should().Be("t1"); - ht[1].Name.Should().Be("t2"); - } - - [Fact] - public async Task Multiple_Should_Work() - { - var userId = await _userService.GetUserIdByUsername("user"); - - // make timeline id not same as entity id. - await _timelineService.CreateTimeline("t0", userId); - - await _timelineService.CreateTimeline("t1", userId); - await _service.AddHighlightTimeline("t1", userId); - - await _timelineService.CreateTimeline("t2", userId); - await _service.AddHighlightTimeline("t2", userId); - - await _timelineService.CreateTimeline("t3", userId); - await _service.AddHighlightTimeline("t3", userId); - - await _service.MoveHighlightTimeline("t3", 2); - (await _service.GetHighlightTimelines())[1].Name.Should().Be("t3"); - - await _service.MoveHighlightTimeline("t1", 3); - (await _service.GetHighlightTimelines())[2].Name.Should().Be("t1"); - - await _service.RemoveHighlightTimeline("t2", userId); - await _service.RemoveHighlightTimeline("t1", userId); - await _service.RemoveHighlightTimeline("t3", userId); - (await _service.GetHighlightTimelines()).Should().BeEmpty(); - } - } -} diff --git a/BackEnd/Timeline.Tests/Services/TimelinePostServiceTest.cs b/BackEnd/Timeline.Tests/Services/TimelinePostServiceTest.cs deleted file mode 100644 index 7771ae0b..00000000 --- a/BackEnd/Timeline.Tests/Services/TimelinePostServiceTest.cs +++ /dev/null @@ -1,200 +0,0 @@ -using FluentAssertions; -using Microsoft.Extensions.Logging.Abstractions; -using System; -using System.Linq; -using System.Threading.Tasks; -using Timeline.Models; -using Timeline.Services; -using Timeline.Tests.Helpers; -using Xunit; - -namespace Timeline.Tests.Services -{ - public class TimelinePostServiceTest : DatabaseBasedTest - { - private readonly PasswordService _passwordService = new PasswordService(); - - private readonly ETagGenerator _eTagGenerator = new ETagGenerator(); - - private readonly ImageValidator _imageValidator = new ImageValidator(); - - private readonly TestClock _clock = new TestClock(); - - private DataManager _dataManager = default!; - - private UserPermissionService _userPermissionService = default!; - - private UserService _userService = default!; - - private TimelineService _timelineService = default!; - - private TimelinePostService _timelinePostService = default!; - - private UserDeleteService _userDeleteService = default!; - - protected override void OnDatabaseCreated() - { - _dataManager = new DataManager(Database, _eTagGenerator); - _userPermissionService = new UserPermissionService(Database); - _userService = new UserService(NullLogger.Instance, Database, _passwordService, _userPermissionService, _clock); - _timelineService = new TimelineService(Database, _userService, _clock); - _timelinePostService = new TimelinePostService(NullLogger.Instance, Database, _timelineService, _userService, _dataManager, _imageValidator, _clock); - _userDeleteService = new UserDeleteService(NullLogger.Instance, Database, _timelinePostService); - } - - protected override void BeforeDatabaseDestroy() - { - _eTagGenerator.Dispose(); - } - - [Theory] - [InlineData("@user")] - [InlineData("tl")] - public async Task GetPosts_ModifiedSince(string timelineName) - { - _clock.ForwardCurrentTime(); - - var userId = await _userService.GetUserIdByUsername("user"); - - var _ = TimelineHelper.ExtractTimelineName(timelineName, out var isPersonal); - if (!isPersonal) - await _timelineService.CreateTimeline(timelineName, userId); - - var postContentList = new string[] { "a", "b", "c", "d" }; - - DateTime testPoint = new DateTime(); - - foreach (var (content, index) in postContentList.Select((v, i) => (v, i))) - { - var t = _clock.ForwardCurrentTime(); - if (index == 1) - testPoint = t; - await _timelinePostService.CreateTextPost(timelineName, userId, content, null); - } - - var posts = await _timelinePostService.GetPosts(timelineName, testPoint); - posts.Should().HaveCount(3) - .And.Subject.Select(p => ((TextTimelinePostContent)p.Content!).Text).Should().Equal(postContentList.Skip(1)); - } - - [Theory] - [InlineData("@user")] - [InlineData("tl")] - public async Task GetPosts_IncludeDeleted(string timelineName) - { - var userId = await _userService.GetUserIdByUsername("user"); - - var _ = TimelineHelper.ExtractTimelineName(timelineName, out var isPersonal); - if (!isPersonal) - await _timelineService.CreateTimeline(timelineName, userId); - - var postContentList = new string[] { "a", "b", "c", "d" }; - - foreach (var content in postContentList) - { - await _timelinePostService.CreateTextPost(timelineName, userId, content, null); - } - - var posts = await _timelinePostService.GetPosts(timelineName); - posts.Should().HaveCount(4); - posts.Select(p => p.Deleted).Should().Equal(Enumerable.Repeat(false, posts.Count)); - posts.Select(p => ((TextTimelinePostContent)p.Content!).Text).Should().Equal(postContentList); - - foreach (var id in new long[] { posts[0].Id, posts[2].Id }) - { - await _timelinePostService.DeletePost(timelineName, id); - } - - posts = await _timelinePostService.GetPosts(timelineName); - posts.Should().HaveCount(2); - posts.Select(p => p.Deleted).Should().Equal(Enumerable.Repeat(false, posts.Count)); - posts.Select(p => ((TextTimelinePostContent)p.Content!).Text).Should().Equal(new string[] { "b", "d" }); - - posts = await _timelinePostService.GetPosts(timelineName, includeDeleted: true); - posts.Should().HaveCount(4); - posts.Select(p => p.Deleted).Should().Equal(new bool[] { true, false, true, false }); - posts.Where(p => !p.Deleted).Select(p => ((TextTimelinePostContent)p.Content!).Text).Should().Equal(new string[] { "b", "d" }); - } - - [Theory] - [InlineData("@admin")] - [InlineData("tl")] - public async Task GetPosts_ModifiedSince_UsernameChange(string timelineName) - { - var time1 = _clock.ForwardCurrentTime(); - - var userId = await _userService.GetUserIdByUsername("user"); - - var _ = TimelineHelper.ExtractTimelineName(timelineName, out var isPersonal); - if (!isPersonal) - await _timelineService.CreateTimeline(timelineName, userId); - - var postContentList = new string[] { "a", "b", "c", "d" }; - - foreach (var (content, index) in postContentList.Select((v, i) => (v, i))) - { - await _timelinePostService.CreateTextPost(timelineName, userId, content, null); - } - - var time2 = _clock.ForwardCurrentTime(); - - { - var posts = await _timelinePostService.GetPosts(timelineName, time2); - posts.Should().HaveCount(0); - } - - { - await _userService.ModifyUser(userId, new ModifyUserParams { Nickname = "haha" }); - var posts = await _timelinePostService.GetPosts(timelineName, time2); - posts.Should().HaveCount(0); - } - - { - await _userService.ModifyUser(userId, new ModifyUserParams { Username = "haha" }); - var posts = await _timelinePostService.GetPosts(timelineName, time2); - posts.Should().HaveCount(4); - } - } - - [Theory] - [InlineData("@admin")] - [InlineData("tl")] - public async Task GetPosts_ModifiedSince_UserDelete(string timelineName) - { - var time1 = _clock.ForwardCurrentTime(); - - var userId = await _userService.GetUserIdByUsername("user"); - var adminId = await _userService.GetUserIdByUsername("admin"); - - var _ = TimelineHelper.ExtractTimelineName(timelineName, out var isPersonal); - if (!isPersonal) - await _timelineService.CreateTimeline(timelineName, adminId); - - var postContentList = new string[] { "a", "b", "c", "d" }; - - foreach (var (content, index) in postContentList.Select((v, i) => (v, i))) - { - await _timelinePostService.CreateTextPost(timelineName, userId, content, null); - } - - var time2 = _clock.ForwardCurrentTime(); - - { - var posts = await _timelinePostService.GetPosts(timelineName, time2); - posts.Should().HaveCount(0); - } - - await _userDeleteService.DeleteUser("user"); - - { - var posts = await _timelinePostService.GetPosts(timelineName, time2); - posts.Should().HaveCount(0); - } - - { - var posts = await _timelinePostService.GetPosts(timelineName, time2, true); - posts.Should().HaveCount(4); - } - } - } -} diff --git a/BackEnd/Timeline.Tests/Services/TimelineServiceTest.cs b/BackEnd/Timeline.Tests/Services/TimelineServiceTest.cs deleted file mode 100644 index 70f54ede..00000000 --- a/BackEnd/Timeline.Tests/Services/TimelineServiceTest.cs +++ /dev/null @@ -1,149 +0,0 @@ -using FluentAssertions; -using Microsoft.Extensions.Logging.Abstractions; -using System; -using System.Collections.Generic; -using System.Threading.Tasks; -using Timeline.Models; -using Timeline.Services; -using Timeline.Services.Exceptions; -using Timeline.Tests.Helpers; -using Xunit; - -namespace Timeline.Tests.Services -{ - public class TimelineServiceTest : DatabaseBasedTest - { - private readonly PasswordService _passwordService = new PasswordService(); - - private readonly TestClock _clock = new TestClock(); - - private UserPermissionService _userPermissionService = default!; - - private UserService _userService = default!; - - private TimelineService _timelineService = default!; - - protected override void OnDatabaseCreated() - { - _userPermissionService = new UserPermissionService(Database); - _userService = new UserService(NullLogger.Instance, Database, _passwordService, _userPermissionService, _clock); - _timelineService = new TimelineService(Database, _userService, _clock); - } - - [Theory] - [InlineData("@user")] - [InlineData("tl")] - public async Task Timeline_GetLastModified(string timelineName) - { - var time = _clock.ForwardCurrentTime(); - - var _ = TimelineHelper.ExtractTimelineName(timelineName, out var isPersonal); - if (!isPersonal) - await _timelineService.CreateTimeline(timelineName, await _userService.GetUserIdByUsername("user")); - - var t = await _timelineService.GetTimelineLastModifiedTime(timelineName); - - t.Should().Be(time); - } - - [Theory] - [InlineData("@user")] - [InlineData("tl")] - public async Task Timeline_GetUnqiueId(string timelineName) - { - var _ = TimelineHelper.ExtractTimelineName(timelineName, out var isPersonal); - if (!isPersonal) - await _timelineService.CreateTimeline(timelineName, await _userService.GetUserIdByUsername("user")); - - var uniqueId = await _timelineService.GetTimelineUniqueId(timelineName); - - uniqueId.Should().NotBeNullOrEmpty(); - } - - [Theory] - [InlineData("@user")] - [InlineData("tl")] - public async Task Timeline_LastModified(string timelineName) - { - var initTime = _clock.ForwardCurrentTime(); - - void Check(TimelineInfo timeline) - { - timeline.NameLastModified.Should().Be(initTime); - timeline.LastModified.Should().Be(_clock.GetCurrentTime()); - } - - async Task GetAndCheck() - { - Check(await _timelineService.GetTimeline(timelineName)); - } - - var _ = TimelineHelper.ExtractTimelineName(timelineName, out var isPersonal); - if (!isPersonal) - Check(await _timelineService.CreateTimeline(timelineName, await _userService.GetUserIdByUsername("user"))); - - await GetAndCheck(); - - _clock.ForwardCurrentTime(); - await _timelineService.ChangeProperty(timelineName, new TimelineChangePropertyRequest { Visibility = TimelineVisibility.Public }); - await GetAndCheck(); - - _clock.ForwardCurrentTime(); - await _timelineService.ChangeMember(timelineName, new List { "admin" }, null); - await GetAndCheck(); - } - - [Theory] - [InlineData("@admin")] - [InlineData("tl")] - public async Task Title(string timelineName) - { - var _ = TimelineHelper.ExtractTimelineName(timelineName, out var isPersonal); - if (!isPersonal) - await _timelineService.CreateTimeline(timelineName, await _userService.GetUserIdByUsername("user")); - - { - var timeline = await _timelineService.GetTimeline(timelineName); - timeline.Title.Should().Be(timelineName); - } - - { - await _timelineService.ChangeProperty(timelineName, new TimelineChangePropertyRequest { Title = null }); - var timeline = await _timelineService.GetTimeline(timelineName); - timeline.Title.Should().Be(timelineName); - } - - { - await _timelineService.ChangeProperty(timelineName, new TimelineChangePropertyRequest { Title = "atitle" }); - var timeline = await _timelineService.GetTimeline(timelineName); - timeline.Title.Should().Be("atitle"); - } - } - - [Fact] - public async Task ChangeName() - { - _clock.ForwardCurrentTime(); - - await _timelineService.Awaiting(s => s.ChangeTimelineName("!!!", "newtl")).Should().ThrowAsync(); - await _timelineService.Awaiting(s => s.ChangeTimelineName("tl", "!!!")).Should().ThrowAsync(); - await _timelineService.Awaiting(s => s.ChangeTimelineName("tl", "newtl")).Should().ThrowAsync(); - - await _timelineService.CreateTimeline("tl", await _userService.GetUserIdByUsername("user")); - await _timelineService.CreateTimeline("tl2", await _userService.GetUserIdByUsername("user")); - - await _timelineService.Awaiting(s => s.ChangeTimelineName("tl", "tl2")).Should().ThrowAsync(); - - var time = _clock.ForwardCurrentTime(); - - await _timelineService.ChangeTimelineName("tl", "newtl"); - - { - var timeline = await _timelineService.GetTimeline("newtl"); - timeline.Name.Should().Be("newtl"); - timeline.LastModified.Should().Be(time); - timeline.NameLastModified.Should().Be(time); - } - } - } -} diff --git a/BackEnd/Timeline/Auth/MyAuthenticationHandler.cs b/BackEnd/Timeline/Auth/MyAuthenticationHandler.cs index 223ff187..c57c96bc 100644 --- a/BackEnd/Timeline/Auth/MyAuthenticationHandler.cs +++ b/BackEnd/Timeline/Auth/MyAuthenticationHandler.cs @@ -84,7 +84,7 @@ namespace Timeline.Auth var identity = new ClaimsIdentity(AuthenticationConstants.Scheme); identity.AddClaim(new Claim(ClaimTypes.NameIdentifier, user.Id.ToString(CultureInfo.InvariantCulture), ClaimValueTypes.Integer64)); identity.AddClaim(new Claim(identity.NameClaimType, user.Username, ClaimValueTypes.String)); - identity.AddClaims(user.Permissions.Select(permission => new Claim(AuthenticationConstants.PermissionClaimName, permission.ToString(), ClaimValueTypes.String))); + identity.AddClaims(user.Permissions.Select(permission => new Claim(AuthenticationConstants.PermissionClaimName, permission.Permission, ClaimValueTypes.String))); var principal = new ClaimsPrincipal(); principal.AddIdentity(identity); diff --git a/BackEnd/Timeline/Controllers/BookmarkTimelineController.cs b/BackEnd/Timeline/Controllers/BookmarkTimelineController.cs index 9dff95f3..7412232d 100644 --- a/BackEnd/Timeline/Controllers/BookmarkTimelineController.cs +++ b/BackEnd/Timeline/Controllers/BookmarkTimelineController.cs @@ -1,9 +1,9 @@ -using AutoMapper; -using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using System.Collections.Generic; using System.Threading.Tasks; using Timeline.Models.Http; +using Timeline.Models.Mapper; using Timeline.Models.Validation; using Timeline.Services; using Timeline.Services.Exceptions; @@ -18,13 +18,12 @@ namespace Timeline.Controllers public class BookmarkTimelineController : Controller { private readonly IBookmarkTimelineService _service; + private readonly ITimelineService _timelineService; - private readonly IMapper _mapper; - - public BookmarkTimelineController(IBookmarkTimelineService service, IMapper mapper) + public BookmarkTimelineController(IBookmarkTimelineService service, ITimelineService timelineService) { _service = service; - _mapper = mapper; + _timelineService = timelineService; } /// @@ -37,8 +36,9 @@ namespace Timeline.Controllers [ProducesResponseType(401)] public async Task>> List() { - var bookmarks = await _service.GetBookmarks(this.GetUserId()); - return Ok(_mapper.Map>(bookmarks)); + var ids = await _service.GetBookmarks(this.GetUserId()); + var timelines = await _timelineService.GetTimelineList(ids); + return Ok(timelines.MapToHttp(Url)); } /// diff --git a/BackEnd/Timeline/Controllers/HighlightTimelineController.cs b/BackEnd/Timeline/Controllers/HighlightTimelineController.cs index 519d6161..76650b00 100644 --- a/BackEnd/Timeline/Controllers/HighlightTimelineController.cs +++ b/BackEnd/Timeline/Controllers/HighlightTimelineController.cs @@ -4,6 +4,7 @@ using System.Collections.Generic; using System.Threading.Tasks; using Timeline.Auth; using Timeline.Models.Http; +using Timeline.Models.Mapper; using Timeline.Models.Validation; using Timeline.Services; using Timeline.Services.Exceptions; @@ -18,12 +19,12 @@ namespace Timeline.Controllers public class HighlightTimelineController : Controller { private readonly IHighlightTimelineService _service; - private readonly IMapper _mapper; + private readonly ITimelineService _timelineService; - public HighlightTimelineController(IHighlightTimelineService service, IMapper mapper) + public HighlightTimelineController(IHighlightTimelineService service, ITimelineService timelineService) { _service = service; - _mapper = mapper; + _timelineService = timelineService; } /// @@ -34,8 +35,9 @@ namespace Timeline.Controllers [ProducesResponseType(200)] public async Task>> List() { - var t = await _service.GetHighlightTimelines(); - return _mapper.Map>(t); + var ids = await _service.GetHighlightTimelines(); + var timelines = await _timelineService.GetTimelineList(ids); + return timelines.MapToHttp(Url); } /// diff --git a/BackEnd/Timeline/Controllers/TimelineController.cs b/BackEnd/Timeline/Controllers/TimelineController.cs index 27b4b7a7..b1401a03 100644 --- a/BackEnd/Timeline/Controllers/TimelineController.cs +++ b/BackEnd/Timeline/Controllers/TimelineController.cs @@ -2,15 +2,16 @@ using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; -using Microsoft.Extensions.Logging; using System; using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using System.Threading.Tasks; +using Timeline.Entities; using Timeline.Filters; using Timeline.Helpers; using Timeline.Models; using Timeline.Models.Http; +using Timeline.Models.Mapper; using Timeline.Models.Validation; using Timeline.Services; using Timeline.Services.Exceptions; @@ -25,8 +26,6 @@ namespace Timeline.Controllers [ProducesErrorResponseType(typeof(CommonResponse))] public class TimelineController : Controller { - private readonly ILogger _logger; - private readonly IUserService _userService; private readonly ITimelineService _service; private readonly ITimelinePostService _postService; @@ -36,9 +35,8 @@ namespace Timeline.Controllers /// /// /// - public TimelineController(ILogger logger, IUserService userService, ITimelineService service, ITimelinePostService timelinePostService, IMapper mapper) + public TimelineController(IUserService userService, ITimelineService service, ITimelinePostService timelinePostService, IMapper mapper) { - _logger = logger; _userService = userService; _service = service; _postService = timelinePostService; @@ -109,44 +107,46 @@ namespace Timeline.Controllers } var timelines = await _service.GetTimelines(relationship, visibilityFilter); - var result = _mapper.Map>(timelines); + var result = timelines.MapToHttp(Url); return result; } /// /// Get info of a timeline. /// - /// The timeline name. + /// The timeline name. /// A unique id. If specified and if-modified-since is also specified, the timeline info will return when unique id is not the specified one even if it is not modified. /// Same effect as If-Modified-Since header and take precedence than it. /// If specified, will return 304 if not modified. /// The timeline info. - [HttpGet("timelines/{name}")] + [HttpGet("timelines/{timeline}")] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status304NotModified)] [ProducesResponseType(StatusCodes.Status404NotFound)] - public async Task> TimelineGet([FromRoute][GeneralTimelineName] string name, [FromQuery] string? checkUniqueId, [FromQuery(Name = "ifModifiedSince")] DateTime? queryIfModifiedSince, [FromHeader(Name = "If-Modified-Since")] DateTime? headerIfModifiedSince) + public async Task> TimelineGet([FromRoute][GeneralTimelineName] string timeline, [FromQuery] string? checkUniqueId, [FromQuery(Name = "ifModifiedSince")] DateTime? queryIfModifiedSince, [FromHeader(Name = "If-Modified-Since")] DateTime? headerIfModifiedSince) { DateTime? ifModifiedSince = null; if (queryIfModifiedSince.HasValue) { ifModifiedSince = queryIfModifiedSince.Value; } - else if (headerIfModifiedSince != null) + else if (headerIfModifiedSince is not null) { ifModifiedSince = headerIfModifiedSince.Value; } + var timelineId = await _service.GetTimelineIdByName(timeline); + bool returnNotModified = false; if (ifModifiedSince.HasValue) { - var lastModified = await _service.GetTimelineLastModifiedTime(name); + var lastModified = await _service.GetTimelineLastModifiedTime(timelineId); if (lastModified < ifModifiedSince.Value) { if (checkUniqueId != null) { - var uniqueId = await _service.GetTimelineUniqueId(name); + var uniqueId = await _service.GetTimelineUniqueId(timelineId); if (uniqueId == checkUniqueId) { returnNotModified = true; @@ -165,8 +165,8 @@ namespace Timeline.Controllers } else { - var timeline = await _service.GetTimeline(name); - var result = _mapper.Map(timeline); + var t = await _service.GetTimeline(timelineId); + var result = t.MapToHttp(Url); return result; } } @@ -174,56 +174,59 @@ namespace Timeline.Controllers /// /// Get posts of a timeline. /// - /// The name of the timeline. + /// The name of the timeline. /// If set, only posts modified since the time will return. /// If set to true, deleted post will also return. /// The post list. - [HttpGet("timelines/{name}/posts")] + [HttpGet("timelines/{timeline}/posts")] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status403Forbidden)] [ProducesResponseType(StatusCodes.Status404NotFound)] - public async Task>> PostListGet([FromRoute][GeneralTimelineName] string name, [FromQuery] DateTime? modifiedSince, [FromQuery] bool? includeDeleted) + public async Task>> PostListGet([FromRoute][GeneralTimelineName] string timeline, [FromQuery] DateTime? modifiedSince, [FromQuery] bool? includeDeleted) { - if (!UserHasAllTimelineManagementPermission && !await _service.HasReadPermission(name, this.GetOptionalUserId())) + var timelineId = await _service.GetTimelineIdByName(timeline); + + if (!UserHasAllTimelineManagementPermission && !await _service.HasReadPermission(timelineId, this.GetOptionalUserId())) { return StatusCode(StatusCodes.Status403Forbidden, ErrorResponse.Common.Forbid()); } - List posts = await _postService.GetPosts(name, modifiedSince, includeDeleted ?? false); + var posts = await _postService.GetPosts(timelineId, modifiedSince, includeDeleted ?? false); - var result = _mapper.Map>(posts); + var result = posts.MapToHttp(timeline, Url); return result; } /// /// Get the data of a post. Usually a image post. /// - /// Timeline name. - /// The id of the post. + /// Timeline name. + /// The id of the post. /// If-None-Match header. /// The data. - [HttpGet("timelines/{name}/posts/{id}/data")] + [HttpGet("timelines/{timeline}/posts/{post}/data")] [Produces("image/png", "image/jpeg", "image/gif", "image/webp", "application/json", "text/json")] [ProducesResponseType(typeof(byte[]), StatusCodes.Status200OK)] [ProducesResponseType(typeof(void), StatusCodes.Status304NotModified)] [ProducesResponseType(StatusCodes.Status400BadRequest)] [ProducesResponseType(StatusCodes.Status403Forbidden)] [ProducesResponseType(StatusCodes.Status404NotFound)] - public async Task PostDataGet([FromRoute][GeneralTimelineName] string name, [FromRoute] long id, [FromHeader(Name = "If-None-Match")] string? ifNoneMatch) + public async Task PostDataGet([FromRoute][GeneralTimelineName] string timeline, [FromRoute] long post, [FromHeader(Name = "If-None-Match")] string? ifNoneMatch) { _ = ifNoneMatch; - if (!UserHasAllTimelineManagementPermission && !await _service.HasReadPermission(name, this.GetOptionalUserId())) + + var timelineId = await _service.GetTimelineIdByName(timeline); + + if (!UserHasAllTimelineManagementPermission && !await _service.HasReadPermission(timelineId, this.GetOptionalUserId())) { return StatusCode(StatusCodes.Status403Forbidden, ErrorResponse.Common.Forbid()); } try { - return await DataCacheHelper.GenerateActionResult(this, () => _postService.GetPostDataETag(name, id), async () => - { - var data = await _postService.GetPostData(name, id); - return data; - }); + return await DataCacheHelper.GenerateActionResult(this, + () => _postService.GetPostDataETag(timelineId, post), + async () => await _postService.GetPostData(timelineId, post)); } catch (TimelinePostNotExistException) { @@ -238,26 +241,28 @@ namespace Timeline.Controllers /// /// Create a new post. /// - /// Timeline name. + /// Timeline name. /// /// Info of new post. - [HttpPost("timelines/{name}/posts")] + [HttpPost("timelines/{timeline}/posts")] [Authorize] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status400BadRequest)] [ProducesResponseType(StatusCodes.Status401Unauthorized)] [ProducesResponseType(StatusCodes.Status403Forbidden)] - public async Task> PostPost([FromRoute][GeneralTimelineName] string name, [FromBody] HttpTimelinePostCreateRequest body) + public async Task> PostPost([FromRoute][GeneralTimelineName] string timeline, [FromBody] HttpTimelinePostCreateRequest body) { - var id = this.GetUserId(); - if (!UserHasAllTimelineManagementPermission && !await _service.IsMemberOf(name, id)) + var timelineId = await _service.GetTimelineIdByName(timeline); + var userId = this.GetUserId(); + + if (!UserHasAllTimelineManagementPermission && !await _service.IsMemberOf(timelineId, userId)) { return StatusCode(StatusCodes.Status403Forbidden, ErrorResponse.Common.Forbid()); } var content = body.Content; - TimelinePostInfo post; + TimelinePostEntity post; if (content.Type == TimelinePostContentTypes.Text) { @@ -266,7 +271,7 @@ namespace Timeline.Controllers { return BadRequest(ErrorResponse.Common.CustomMessage_InvalidModel(Resources.Messages.TimelineController_TextContentTextRequired)); } - post = await _postService.CreateTextPost(name, id, text, body.Time); + post = await _postService.CreateTextPost(timelineId, userId, text, body.Time); } else if (content.Type == TimelinePostContentTypes.Image) { @@ -287,7 +292,7 @@ namespace Timeline.Controllers try { - post = await _postService.CreateImagePost(name, id, data, body.Time); + post = await _postService.CreateImagePost(timelineId, userId, data, body.Time); } catch (ImageException) { @@ -299,117 +304,128 @@ namespace Timeline.Controllers return BadRequest(ErrorResponse.Common.CustomMessage_InvalidModel(Resources.Messages.TimelineController_ContentUnknownType)); } - var result = _mapper.Map(post); + var result = post.MapToHttp(timeline, Url); return result; } /// /// Delete a post. /// - /// Timeline name. - /// Post id. + /// Timeline name. + /// Post id. /// Info of deletion. - [HttpDelete("timelines/{name}/posts/{id}")] + [HttpDelete("timelines/{timeline}/posts/{post}")] [Authorize] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status400BadRequest)] [ProducesResponseType(StatusCodes.Status401Unauthorized)] [ProducesResponseType(StatusCodes.Status403Forbidden)] - public async Task> PostDelete([FromRoute][GeneralTimelineName] string name, [FromRoute] long id) + public async Task PostDelete([FromRoute][GeneralTimelineName] string timeline, [FromRoute] long post) { - if (!UserHasAllTimelineManagementPermission && !await _postService.HasPostModifyPermission(name, id, this.GetUserId())) - { - return StatusCode(StatusCodes.Status403Forbidden, ErrorResponse.Common.Forbid()); - } + var timelineId = await _service.GetTimelineIdByName(timeline); + try { - await _postService.DeletePost(name, id); - return CommonDeleteResponse.Delete(); + if (!UserHasAllTimelineManagementPermission && !await _postService.HasPostModifyPermission(timelineId, post, this.GetUserId(), true)) + { + return StatusCode(StatusCodes.Status403Forbidden, ErrorResponse.Common.Forbid()); + } + await _postService.DeletePost(timelineId, post); + return Ok(); } catch (TimelinePostNotExistException) { - return CommonDeleteResponse.NotExist(); + return BadRequest(ErrorResponse.TimelineController.PostNotExist()); } } /// /// Change properties of a timeline. /// - /// Timeline name. + /// Timeline name. /// /// The new info. - [HttpPatch("timelines/{name}")] + [HttpPatch("timelines/{timeline}")] [Authorize] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status400BadRequest)] [ProducesResponseType(StatusCodes.Status401Unauthorized)] [ProducesResponseType(StatusCodes.Status403Forbidden)] - public async Task> TimelinePatch([FromRoute][GeneralTimelineName] string name, [FromBody] HttpTimelinePatchRequest body) + public async Task> TimelinePatch([FromRoute][GeneralTimelineName] string timeline, [FromBody] HttpTimelinePatchRequest body) { - if (!UserHasAllTimelineManagementPermission && !(await _service.HasManagePermission(name, this.GetUserId()))) + var timelineId = await _service.GetTimelineIdByName(timeline); + + if (!UserHasAllTimelineManagementPermission && !await _service.HasManagePermission(timelineId, this.GetUserId())) { return StatusCode(StatusCodes.Status403Forbidden, ErrorResponse.Common.Forbid()); } - await _service.ChangeProperty(name, _mapper.Map(body)); - var timeline = await _service.GetTimeline(name); - var result = _mapper.Map(timeline); + await _service.ChangeProperty(timelineId, _mapper.Map(body)); + var t = await _service.GetTimeline(timelineId); + var result = t.MapToHttp(Url); return result; } /// /// Add a member to timeline. /// - /// Timeline name. + /// Timeline name. /// The new member's username. - [HttpPut("timelines/{name}/members/{member}")] + [HttpPut("timelines/{timeline}/members/{member}")] [Authorize] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status400BadRequest)] [ProducesResponseType(StatusCodes.Status401Unauthorized)] [ProducesResponseType(StatusCodes.Status403Forbidden)] - public async Task TimelineMemberPut([FromRoute][GeneralTimelineName] string name, [FromRoute][Username] string member) + public async Task> TimelineMemberPut([FromRoute][GeneralTimelineName] string timeline, [FromRoute][Username] string member) { - if (!UserHasAllTimelineManagementPermission && !(await _service.HasManagePermission(name, this.GetUserId()))) + var timelineId = await _service.GetTimelineIdByName(timeline); + + if (!UserHasAllTimelineManagementPermission && !(await _service.HasManagePermission(timelineId, this.GetUserId()))) { return StatusCode(StatusCodes.Status403Forbidden, ErrorResponse.Common.Forbid()); } try { - await _service.ChangeMember(name, new List { member }, null); - return Ok(); + var userId = await _userService.GetUserIdByUsername(member); + var create = await _service.AddMember(timelineId, userId); + return Ok(CommonPutResponse.Create(create)); } catch (UserNotExistException) { - return BadRequest(ErrorResponse.TimelineController.MemberPut_NotExist()); + return BadRequest(ErrorResponse.UserCommon.NotExist()); } } /// /// Remove a member from timeline. /// - /// Timeline name. + /// Timeline name. /// The member's username. - [HttpDelete("timelines/{name}/members/{member}")] + [HttpDelete("timelines/{timeline}/members/{member}")] [Authorize] [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status400BadRequest)] [ProducesResponseType(StatusCodes.Status401Unauthorized)] [ProducesResponseType(StatusCodes.Status403Forbidden)] - public async Task TimelineMemberDelete([FromRoute][GeneralTimelineName] string name, [FromRoute][Username] string member) + public async Task TimelineMemberDelete([FromRoute][GeneralTimelineName] string timeline, [FromRoute][Username] string member) { - if (!UserHasAllTimelineManagementPermission && !(await _service.HasManagePermission(name, this.GetUserId()))) + var timelineId = await _service.GetTimelineIdByName(timeline); + + if (!UserHasAllTimelineManagementPermission && !(await _service.HasManagePermission(timelineId, this.GetUserId()))) { return StatusCode(StatusCodes.Status403Forbidden, ErrorResponse.Common.Forbid()); } try { - await _service.ChangeMember(name, null, new List { member }); - return Ok(CommonDeleteResponse.Delete()); + var userId = await _userService.GetUserIdByUsername(member); + var delete = await _service.RemoveMember(timelineId, userId); + return Ok(CommonDeleteResponse.Create(delete)); } catch (UserNotExistException) { - return Ok(CommonDeleteResponse.NotExist()); + return BadRequest(ErrorResponse.UserCommon.NotExist()); } } @@ -430,7 +446,7 @@ namespace Timeline.Controllers try { var timeline = await _service.CreateTimeline(body.Name, userId); - var result = _mapper.Map(timeline); + var result = timeline.MapToHttp(Url); return result; } catch (EntityAlreadyExistException e) when (e.EntityName == EntityNames.Timeline) @@ -442,29 +458,31 @@ namespace Timeline.Controllers /// /// Delete a timeline. /// - /// Timeline name. + /// Timeline name. /// Info of deletion. - [HttpDelete("timelines/{name}")] + [HttpDelete("timelines/{timeline}")] [Authorize] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status400BadRequest)] [ProducesResponseType(StatusCodes.Status401Unauthorized)] [ProducesResponseType(StatusCodes.Status403Forbidden)] - public async Task> TimelineDelete([FromRoute][TimelineName] string name) + public async Task TimelineDelete([FromRoute][TimelineName] string timeline) { - if (!UserHasAllTimelineManagementPermission && !(await _service.HasManagePermission(name, this.GetUserId()))) + var timelineId = await _service.GetTimelineIdByName(timeline); + + if (!UserHasAllTimelineManagementPermission && !(await _service.HasManagePermission(timelineId, this.GetUserId()))) { return StatusCode(StatusCodes.Status403Forbidden, ErrorResponse.Common.Forbid()); } try { - await _service.DeleteTimeline(name); - return CommonDeleteResponse.Delete(); + await _service.DeleteTimeline(timelineId); + return Ok(); } catch (TimelineNotExistException) { - return CommonDeleteResponse.NotExist(); + return BadRequest(ErrorResponse.TimelineController.NotExist()); } } @@ -476,15 +494,18 @@ namespace Timeline.Controllers [ProducesResponseType(StatusCodes.Status403Forbidden)] public async Task> TimelineOpChangeName([FromBody] HttpTimelineChangeNameRequest body) { - if (!UserHasAllTimelineManagementPermission && !(await _service.HasManagePermission(body.OldName, this.GetUserId()))) + var timelineId = await _service.GetTimelineIdByName(body.OldName); + + if (!UserHasAllTimelineManagementPermission && !(await _service.HasManagePermission(timelineId, this.GetUserId()))) { return StatusCode(StatusCodes.Status403Forbidden, ErrorResponse.Common.Forbid()); } try { - var timeline = await _service.ChangeTimelineName(body.OldName, body.NewName); - return Ok(_mapper.Map(timeline)); + await _service.ChangeTimelineName(timelineId, body.NewName); + var timeline = await _service.GetTimeline(timelineId); + return Ok(timeline.MapToHttp(Url)); } catch (EntityAlreadyExistException) { diff --git a/BackEnd/Timeline/Controllers/TokenController.cs b/BackEnd/Timeline/Controllers/TokenController.cs index c801b8cc..e695a10e 100644 --- a/BackEnd/Timeline/Controllers/TokenController.cs +++ b/BackEnd/Timeline/Controllers/TokenController.cs @@ -8,6 +8,7 @@ using System.Globalization; using System.Threading.Tasks; using Timeline.Helpers; using Timeline.Models.Http; +using Timeline.Models.Mapper; using Timeline.Services; using Timeline.Services.Exceptions; using static Timeline.Resources.Controllers.TokenController; @@ -27,16 +28,13 @@ namespace Timeline.Controllers private readonly ILogger _logger; private readonly IClock _clock; - private readonly IMapper _mapper; - /// - public TokenController(IUserCredentialService userCredentialService, IUserTokenManager userTokenManager, ILogger logger, IClock clock, IMapper mapper) + public TokenController(IUserCredentialService userCredentialService, IUserTokenManager userTokenManager, ILogger logger, IClock clock) { _userCredentialService = userCredentialService; _userTokenManager = userTokenManager; _logger = logger; _clock = clock; - _mapper = mapper; } /// @@ -74,7 +72,7 @@ namespace Timeline.Controllers return Ok(new HttpCreateTokenResponse { Token = result.Token, - User = _mapper.Map(result.User) + User = result.User.MapToHttp(Url) }); } catch (UserNotExistException e) @@ -115,7 +113,7 @@ namespace Timeline.Controllers ("Username", result.Username), ("Token", request.Token))); return Ok(new HttpVerifyTokenResponse { - User = _mapper.Map(result) + User = result.MapToHttp(Url) }); } catch (UserTokenTimeExpireException e) diff --git a/BackEnd/Timeline/Controllers/UserController.cs b/BackEnd/Timeline/Controllers/UserController.cs index 3727da36..93b17b2e 100644 --- a/BackEnd/Timeline/Controllers/UserController.cs +++ b/BackEnd/Timeline/Controllers/UserController.cs @@ -3,12 +3,11 @@ using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Logging; -using System.Linq; using System.Threading.Tasks; using Timeline.Auth; using Timeline.Helpers; -using Timeline.Models; using Timeline.Models.Http; +using Timeline.Models.Mapper; using Timeline.Models.Validation; using Timeline.Services; using Timeline.Services.Exceptions; @@ -42,8 +41,6 @@ namespace Timeline.Controllers _mapper = mapper; } - private HttpUser ConvertToUserInfo(UserInfo user) => _mapper.Map(user); - private bool UserHasUserManagementPermission => this.UserHasPermission(UserPermission.UserManagement); /// @@ -55,7 +52,7 @@ namespace Timeline.Controllers public async Task> List() { var users = await _userService.GetUsers(); - var result = users.Select(u => ConvertToUserInfo(u)).ToArray(); + var result = users.MapToHttp(Url); return Ok(result); } @@ -73,7 +70,7 @@ namespace Timeline.Controllers { var id = await _userService.GetUserIdByUsername(username); var user = await _userService.GetUser(id); - return Ok(ConvertToUserInfo(user)); + return Ok(user.MapToHttp(Url)); } catch (UserNotExistException e) { @@ -102,7 +99,7 @@ namespace Timeline.Controllers { var id = await _userService.GetUserIdByUsername(username); var user = await _userService.ModifyUser(id, _mapper.Map(body)); - return Ok(ConvertToUserInfo(user)); + return Ok(user.MapToHttp(Url)); } catch (UserNotExistException e) { @@ -129,7 +126,7 @@ namespace Timeline.Controllers ErrorResponse.Common.CustomMessage_Forbid(UserController_Patch_Forbid_Password)); var user = await _userService.ModifyUser(this.GetUserId(), _mapper.Map(body)); - return Ok(ConvertToUserInfo(user)); + return Ok(user.MapToHttp(Url)); } } @@ -173,7 +170,7 @@ namespace Timeline.Controllers try { var user = await _userService.CreateUser(body.Username, body.Password); - return Ok(ConvertToUserInfo(user)); + return Ok(user.MapToHttp(Url)); } catch (EntityAlreadyExistException e) when (e.EntityName == EntityNames.User) { diff --git a/BackEnd/Timeline/GlobalSuppressions.cs b/BackEnd/Timeline/GlobalSuppressions.cs index 178ae09c..1d6905a5 100644 --- a/BackEnd/Timeline/GlobalSuppressions.cs +++ b/BackEnd/Timeline/GlobalSuppressions.cs @@ -13,3 +13,4 @@ using System.Diagnostics.CodeAnalysis; [assembly: SuppressMessage("Design", "CA1056:Uri properties should not be strings", Justification = "That's unnecessary.")] [assembly: SuppressMessage("Design", "CA1062:Validate arguments of public methods", Justification = "Redundant")] [assembly: SuppressMessage("Globalization", "CA1303:Do not pass literals as localized parameters", Justification = "No localization demand.")] +[assembly: SuppressMessage("Design", "CA1054:URI-like parameters should not be strings")] diff --git a/BackEnd/Timeline/Migrations/20210107074715_AddRootUserPermissions.Designer.cs b/BackEnd/Timeline/Migrations/20210107074715_AddRootUserPermissions.Designer.cs new file mode 100644 index 00000000..6cd41998 --- /dev/null +++ b/BackEnd/Timeline/Migrations/20210107074715_AddRootUserPermissions.Designer.cs @@ -0,0 +1,498 @@ +// +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("20210107074715_AddRootUserPermissions")] + partial class AddRootUserPermissions + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "5.0.0"); + + modelBuilder.Entity("Timeline.Entities.BookmarkTimelineEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("id"); + + b.Property("Rank") + .HasColumnType("INTEGER") + .HasColumnName("rank"); + + b.Property("TimelineId") + .HasColumnType("INTEGER") + .HasColumnName("timeline"); + + b.Property("UserId") + .HasColumnType("INTEGER") + .HasColumnName("user"); + + b.HasKey("Id"); + + b.HasIndex("TimelineId"); + + b.HasIndex("UserId"); + + b.ToTable("bookmark_timelines"); + }); + + modelBuilder.Entity("Timeline.Entities.DataEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("id"); + + b.Property("Data") + .IsRequired() + .HasColumnType("BLOB") + .HasColumnName("data"); + + b.Property("Ref") + .HasColumnType("INTEGER") + .HasColumnName("ref"); + + b.Property("Tag") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("tag"); + + b.HasKey("Id"); + + b.HasIndex("Tag") + .IsUnique(); + + b.ToTable("data"); + }); + + modelBuilder.Entity("Timeline.Entities.HighlightTimelineEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("id"); + + b.Property("AddTime") + .HasColumnType("TEXT") + .HasColumnName("add_time"); + + b.Property("OperatorId") + .HasColumnType("INTEGER") + .HasColumnName("operator_id"); + + b.Property("Order") + .HasColumnType("INTEGER") + .HasColumnName("order"); + + b.Property("TimelineId") + .HasColumnType("INTEGER") + .HasColumnName("timeline_id"); + + b.HasKey("Id"); + + b.HasIndex("OperatorId"); + + b.HasIndex("TimelineId"); + + b.ToTable("highlight_timelines"); + }); + + modelBuilder.Entity("Timeline.Entities.JwtTokenEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("id"); + + b.Property("Key") + .IsRequired() + .HasColumnType("BLOB") + .HasColumnName("key"); + + b.HasKey("Id"); + + b.ToTable("jwt_token"); + }); + + modelBuilder.Entity("Timeline.Entities.TimelineEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("id"); + + b.Property("CreateTime") + .HasColumnType("TEXT") + .HasColumnName("create_time"); + + b.Property("CurrentPostLocalId") + .HasColumnType("INTEGER") + .HasColumnName("current_post_local_id"); + + b.Property("Description") + .HasColumnType("TEXT") + .HasColumnName("description"); + + b.Property("LastModified") + .HasColumnType("TEXT") + .HasColumnName("last_modified"); + + b.Property("Name") + .HasColumnType("TEXT") + .HasColumnName("name"); + + b.Property("NameLastModified") + .HasColumnType("TEXT") + .HasColumnName("name_last_modified"); + + b.Property("OwnerId") + .HasColumnType("INTEGER") + .HasColumnName("owner"); + + b.Property("Title") + .HasColumnType("TEXT") + .HasColumnName("title"); + + b.Property("UniqueId") + .IsRequired() + .ValueGeneratedOnAdd() + .HasColumnType("TEXT") + .HasColumnName("unique_id") + .HasDefaultValueSql("lower(hex(randomblob(16)))"); + + b.Property("Visibility") + .HasColumnType("INTEGER") + .HasColumnName("visibility"); + + b.HasKey("Id"); + + b.HasIndex("OwnerId"); + + b.ToTable("timelines"); + }); + + modelBuilder.Entity("Timeline.Entities.TimelineMemberEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("id"); + + b.Property("TimelineId") + .HasColumnType("INTEGER") + .HasColumnName("timeline"); + + b.Property("UserId") + .HasColumnType("INTEGER") + .HasColumnName("user"); + + b.HasKey("Id"); + + b.HasIndex("TimelineId"); + + b.HasIndex("UserId"); + + b.ToTable("timeline_members"); + }); + + modelBuilder.Entity("Timeline.Entities.TimelinePostEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("id"); + + b.Property("AuthorId") + .HasColumnType("INTEGER") + .HasColumnName("author"); + + b.Property("Content") + .HasColumnType("TEXT") + .HasColumnName("content"); + + b.Property("ContentType") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("content_type"); + + b.Property("ExtraContent") + .HasColumnType("TEXT") + .HasColumnName("extra_content"); + + b.Property("LastUpdated") + .HasColumnType("TEXT") + .HasColumnName("last_updated"); + + b.Property("LocalId") + .HasColumnType("INTEGER") + .HasColumnName("local_id"); + + b.Property("Time") + .HasColumnType("TEXT") + .HasColumnName("time"); + + b.Property("TimelineId") + .HasColumnType("INTEGER") + .HasColumnName("timeline"); + + b.HasKey("Id"); + + b.HasIndex("AuthorId"); + + b.HasIndex("TimelineId"); + + b.ToTable("timeline_posts"); + }); + + modelBuilder.Entity("Timeline.Entities.UserAvatarEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("id"); + + b.Property("DataTag") + .HasColumnType("TEXT") + .HasColumnName("data_tag"); + + b.Property("LastModified") + .HasColumnType("TEXT") + .HasColumnName("last_modified"); + + b.Property("Type") + .HasColumnType("TEXT") + .HasColumnName("type"); + + b.Property("UserId") + .HasColumnType("INTEGER") + .HasColumnName("user"); + + b.HasKey("Id"); + + b.HasIndex("UserId") + .IsUnique(); + + b.ToTable("user_avatars"); + }); + + modelBuilder.Entity("Timeline.Entities.UserEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("id"); + + b.Property("CreateTime") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT") + .HasColumnName("create_time") + .HasDefaultValueSql("datetime('now', 'utc')"); + + b.Property("LastModified") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT") + .HasColumnName("last_modified") + .HasDefaultValueSql("datetime('now', 'utc')"); + + b.Property("Nickname") + .HasColumnType("TEXT") + .HasColumnName("nickname"); + + b.Property("Password") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("password"); + + b.Property("UniqueId") + .IsRequired() + .ValueGeneratedOnAdd() + .HasColumnType("TEXT") + .HasColumnName("unique_id") + .HasDefaultValueSql("lower(hex(randomblob(16)))"); + + b.Property("Username") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("username"); + + b.Property("UsernameChangeTime") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT") + .HasColumnName("username_change_time") + .HasDefaultValueSql("datetime('now', 'utc')"); + + b.Property("Version") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasDefaultValue(0L) + .HasColumnName("version"); + + b.HasKey("Id"); + + b.HasIndex("Username") + .IsUnique(); + + b.ToTable("users"); + }); + + modelBuilder.Entity("Timeline.Entities.UserPermissionEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("id"); + + b.Property("Permission") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("permission"); + + b.Property("UserId") + .HasColumnType("INTEGER") + .HasColumnName("user_id"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("user_permission"); + }); + + modelBuilder.Entity("Timeline.Entities.BookmarkTimelineEntity", b => + { + b.HasOne("Timeline.Entities.TimelineEntity", "Timeline") + .WithMany() + .HasForeignKey("TimelineId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Timeline.Entities.UserEntity", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Timeline"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Timeline.Entities.HighlightTimelineEntity", b => + { + b.HasOne("Timeline.Entities.UserEntity", "Operator") + .WithMany() + .HasForeignKey("OperatorId"); + + b.HasOne("Timeline.Entities.TimelineEntity", "Timeline") + .WithMany() + .HasForeignKey("TimelineId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Operator"); + + b.Navigation("Timeline"); + }); + + modelBuilder.Entity("Timeline.Entities.TimelineEntity", b => + { + b.HasOne("Timeline.Entities.UserEntity", "Owner") + .WithMany("Timelines") + .HasForeignKey("OwnerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Owner"); + }); + + modelBuilder.Entity("Timeline.Entities.TimelineMemberEntity", b => + { + b.HasOne("Timeline.Entities.TimelineEntity", "Timeline") + .WithMany("Members") + .HasForeignKey("TimelineId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Timeline.Entities.UserEntity", "User") + .WithMany("TimelinesJoined") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Timeline"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Timeline.Entities.TimelinePostEntity", b => + { + b.HasOne("Timeline.Entities.UserEntity", "Author") + .WithMany("TimelinePosts") + .HasForeignKey("AuthorId"); + + b.HasOne("Timeline.Entities.TimelineEntity", "Timeline") + .WithMany("Posts") + .HasForeignKey("TimelineId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Author"); + + b.Navigation("Timeline"); + }); + + modelBuilder.Entity("Timeline.Entities.UserAvatarEntity", b => + { + b.HasOne("Timeline.Entities.UserEntity", "User") + .WithOne("Avatar") + .HasForeignKey("Timeline.Entities.UserAvatarEntity", "UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Timeline.Entities.UserPermissionEntity", b => + { + b.HasOne("Timeline.Entities.UserEntity", "User") + .WithMany("Permissions") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Timeline.Entities.TimelineEntity", b => + { + b.Navigation("Members"); + + b.Navigation("Posts"); + }); + + modelBuilder.Entity("Timeline.Entities.UserEntity", b => + { + b.Navigation("Avatar"); + + b.Navigation("Permissions"); + + b.Navigation("TimelinePosts"); + + b.Navigation("Timelines"); + + b.Navigation("TimelinesJoined"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/BackEnd/Timeline/Migrations/20210107074715_AddRootUserPermissions.cs b/BackEnd/Timeline/Migrations/20210107074715_AddRootUserPermissions.cs new file mode 100644 index 00000000..ff5db722 --- /dev/null +++ b/BackEnd/Timeline/Migrations/20210107074715_AddRootUserPermissions.cs @@ -0,0 +1,20 @@ +using Microsoft.EntityFrameworkCore.Migrations; +using Timeline.Services; + +namespace Timeline.Migrations +{ + public partial class AddRootUserPermissions : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.InsertData("user_permission", new string[] { "user_id", "permission" }, new object[] { 1, UserPermission.UserManagement.ToString() }); + migrationBuilder.InsertData("user_permission", new string[] { "user_id", "permission" }, new object[] { 1, UserPermission.AllTimelineManagement.ToString() }); + migrationBuilder.InsertData("user_permission", new string[] { "user_id", "permission" }, new object[] { 1, UserPermission.HighlightTimelineManagement.ToString() }); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + + } + } +} diff --git a/BackEnd/Timeline/Models/Http/Common.cs b/BackEnd/Timeline/Models/Http/Common.cs deleted file mode 100644 index 2101a1bb..00000000 --- a/BackEnd/Timeline/Models/Http/Common.cs +++ /dev/null @@ -1,120 +0,0 @@ -using static Timeline.Resources.Models.Http.Common; - -namespace Timeline.Models.Http -{ - public class CommonResponse - { - public CommonResponse() - { - - } - - public CommonResponse(int code, string message) - { - Code = code; - Message = message; - } - - public int Code { get; set; } - public string? Message { get; set; } - } - - public class CommonDataResponse : CommonResponse - { - public CommonDataResponse() - { - - } - - public CommonDataResponse(int code, string message, T data) - : base(code, message) - { - Data = data; - } - - public T Data { get; set; } = default!; - } - - public class CommonPutResponse : CommonDataResponse - { - public class ResponseData - { - public ResponseData() { } - - public ResponseData(bool create) - { - Create = create; - } - - 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() - { - return new CommonPutResponse(0, MessagePutCreate, true); - } - - internal static CommonPutResponse Modify() - { - return new CommonPutResponse(0, MessagePutModify, false); - } - } - - /// - /// Common response for delete method. - /// - public class CommonDeleteResponse : CommonDataResponse - { - /// - public class ResponseData - { - /// - public ResponseData() { } - - /// - public ResponseData(bool delete) - { - Delete = delete; - } - - /// - /// True if the entry is deleted. False if the entry 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() - { - return new CommonDeleteResponse(0, MessageDeleteDelete, true); - } - - internal static CommonDeleteResponse NotExist() - { - return new CommonDeleteResponse(0, MessageDeleteNotExist, false); - } - } -} diff --git a/BackEnd/Timeline/Models/Http/CommonResponse.cs b/BackEnd/Timeline/Models/Http/CommonResponse.cs new file mode 100644 index 00000000..3d0ed509 --- /dev/null +++ b/BackEnd/Timeline/Models/Http/CommonResponse.cs @@ -0,0 +1,130 @@ +using static Timeline.Resources.Models.Http.Common; + +namespace Timeline.Models.Http +{ + public class CommonResponse + { + public CommonResponse() + { + + } + + public CommonResponse(int code, string message) + { + Code = code; + Message = message; + } + + public int Code { get; set; } + public string? Message { get; set; } + } + + public class CommonDataResponse : CommonResponse + { + public CommonDataResponse() + { + + } + + public CommonDataResponse(int code, string message, T data) + : base(code, message) + { + Data = data; + } + + public T Data { get; set; } = default!; + } + + public class CommonPutResponse : CommonDataResponse + { + public class ResponseData + { + public ResponseData() { } + + public ResponseData(bool create) + { + Create = create; + } + + 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(bool create) + { + return new CommonPutResponse(0, MessagePutCreate, create); + } + + internal static CommonPutResponse Create() + { + return new CommonPutResponse(0, MessagePutCreate, true); + } + + internal static CommonPutResponse Modify() + { + return new CommonPutResponse(0, MessagePutModify, false); + } + } + + /// + /// Common response for delete method. + /// + public class CommonDeleteResponse : CommonDataResponse + { + /// + public class ResponseData + { + /// + public ResponseData() { } + + /// + public ResponseData(bool delete) + { + Delete = delete; + } + + /// + /// True if the entry is deleted. False if the entry 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 Create(bool delete) + { + return new CommonDeleteResponse(0, MessageDeleteDelete, delete); + } + + internal static CommonDeleteResponse Delete() + { + return new CommonDeleteResponse(0, MessageDeleteDelete, true); + } + + internal static CommonDeleteResponse NotExist() + { + return new CommonDeleteResponse(0, MessageDeleteNotExist, false); + } + } +} diff --git a/BackEnd/Timeline/Models/Http/ErrorResponse.cs b/BackEnd/Timeline/Models/Http/ErrorResponse.cs index 10755fd1..1bc46680 100644 --- a/BackEnd/Timeline/Models/Http/ErrorResponse.cs +++ b/BackEnd/Timeline/Models/Http/ErrorResponse.cs @@ -234,16 +234,6 @@ namespace Timeline.Models.Http return new CommonResponse(ErrorCodes.TimelineController.NotExist, string.Format(message, formatArgs)); } - public static CommonResponse MemberPut_NotExist(params object?[] formatArgs) - { - return new CommonResponse(ErrorCodes.TimelineController.MemberPut_NotExist, string.Format(TimelineController_MemberPut_NotExist, formatArgs)); - } - - public static CommonResponse CustomMessage_MemberPut_NotExist(string message, params object?[] formatArgs) - { - return new CommonResponse(ErrorCodes.TimelineController.MemberPut_NotExist, string.Format(message, formatArgs)); - } - public static CommonResponse QueryRelateNotExist(params object?[] formatArgs) { return new CommonResponse(ErrorCodes.TimelineController.QueryRelateNotExist, string.Format(TimelineController_QueryRelateNotExist, formatArgs)); diff --git a/BackEnd/Timeline/Models/Http/Timeline.cs b/BackEnd/Timeline/Models/Http/Timeline.cs index 8e3831e1..06fa4e5a 100644 --- a/BackEnd/Timeline/Models/Http/Timeline.cs +++ b/BackEnd/Timeline/Models/Http/Timeline.cs @@ -1,10 +1,5 @@ -using AutoMapper; -using Microsoft.AspNetCore.Mvc; -using Microsoft.AspNetCore.Mvc.Infrastructure; -using Microsoft.AspNetCore.Mvc.Routing; -using System; +using System; using System.Collections.Generic; -using Timeline.Controllers; namespace Timeline.Models.Http { @@ -13,6 +8,16 @@ namespace Timeline.Models.Http /// public class HttpTimelinePostContent { + public HttpTimelinePostContent() { } + + public HttpTimelinePostContent(string type, string? text, string? url, string? eTag) + { + Type = type; + Text = text; + Url = url; + ETag = eTag; + } + /// /// Type of the post content. /// @@ -36,6 +41,18 @@ namespace Timeline.Models.Http /// public class HttpTimelinePost { + public HttpTimelinePost() { } + + public HttpTimelinePost(long id, HttpTimelinePostContent? content, bool deleted, DateTime time, HttpUser? author, DateTime lastUpdated) + { + Id = id; + Content = content; + Deleted = deleted; + Time = time; + Author = author; + LastUpdated = lastUpdated; + } + /// /// Post id. /// @@ -67,6 +84,23 @@ namespace Timeline.Models.Http /// public class HttpTimeline { + public HttpTimeline() { } + + public HttpTimeline(string uniqueId, string title, string name, DateTime nameLastModifed, string description, HttpUser owner, TimelineVisibility visibility, List members, DateTime createTime, DateTime lastModified, HttpTimelineLinks links) + { + UniqueId = uniqueId; + Title = title; + Name = name; + NameLastModifed = nameLastModifed; + Description = description; + Owner = owner; + Visibility = visibility; + Members = members; + CreateTime = createTime; + LastModified = lastModified; + _links = links; + } + /// /// Unique id. /// @@ -123,6 +157,14 @@ namespace Timeline.Models.Http /// public class HttpTimelineLinks { + public HttpTimelineLinks() { } + + public HttpTimelineLinks(string self, string posts) + { + Self = self; + Posts = posts; + } + /// /// Self. /// @@ -132,87 +174,4 @@ namespace Timeline.Models.Http /// public string Posts { get; set; } = default!; } - - public class HttpTimelineLinksValueResolver : IValueResolver - { - private readonly IActionContextAccessor _actionContextAccessor; - private readonly IUrlHelperFactory _urlHelperFactory; - - public HttpTimelineLinksValueResolver(IActionContextAccessor actionContextAccessor, IUrlHelperFactory urlHelperFactory) - { - _actionContextAccessor = actionContextAccessor; - _urlHelperFactory = urlHelperFactory; - } - - public HttpTimelineLinks Resolve(TimelineInfo source, HttpTimeline destination, HttpTimelineLinks destMember, ResolutionContext context) - { - var actionContext = _actionContextAccessor.AssertActionContextForUrlFill(); - var urlHelper = _urlHelperFactory.GetUrlHelper(actionContext); - - return new HttpTimelineLinks - { - Self = urlHelper.ActionLink(nameof(TimelineController.TimelineGet), nameof(TimelineController)[0..^nameof(Controller).Length], new { source.Name }), - Posts = urlHelper.ActionLink(nameof(TimelineController.PostListGet), nameof(TimelineController)[0..^nameof(Controller).Length], new { source.Name }) - }; - } - } - - public class HttpTimelinePostContentResolver : IValueResolver - { - private readonly IActionContextAccessor _actionContextAccessor; - private readonly IUrlHelperFactory _urlHelperFactory; - - public HttpTimelinePostContentResolver(IActionContextAccessor actionContextAccessor, IUrlHelperFactory urlHelperFactory) - { - _actionContextAccessor = actionContextAccessor; - _urlHelperFactory = urlHelperFactory; - } - - public HttpTimelinePostContent? Resolve(TimelinePostInfo source, HttpTimelinePost destination, HttpTimelinePostContent? destMember, ResolutionContext context) - { - var actionContext = _actionContextAccessor.AssertActionContextForUrlFill(); - var urlHelper = _urlHelperFactory.GetUrlHelper(actionContext); - - var sourceContent = source.Content; - - if (sourceContent == null) - { - return null; - } - - if (sourceContent is TextTimelinePostContent textContent) - { - return new HttpTimelinePostContent - { - Type = TimelinePostContentTypes.Text, - Text = textContent.Text - }; - } - else if (sourceContent is ImageTimelinePostContent imageContent) - { - return new HttpTimelinePostContent - { - Type = TimelinePostContentTypes.Image, - Url = urlHelper.ActionLink( - action: nameof(TimelineController.PostDataGet), - controller: nameof(TimelineController)[0..^nameof(Controller).Length], - values: new { Name = source.TimelineName, Id = source.Id }), - ETag = $"\"{imageContent.DataTag}\"" - }; - } - else - { - throw new InvalidOperationException(Resources.Models.Http.Exception.UnknownPostContentType); - } - } - } - - public class HttpTimelineAutoMapperProfile : Profile - { - public HttpTimelineAutoMapperProfile() - { - CreateMap().ForMember(u => u._links, opt => opt.MapFrom()); - CreateMap().ForMember(p => p.Content, opt => opt.MapFrom()); - } - } } diff --git a/BackEnd/Timeline/Models/Http/TimelineController.cs b/BackEnd/Timeline/Models/Http/TimelineController.cs index 42a926fd..f6039b35 100644 --- a/BackEnd/Timeline/Models/Http/TimelineController.cs +++ b/BackEnd/Timeline/Models/Http/TimelineController.cs @@ -2,6 +2,7 @@ using System; using System.ComponentModel.DataAnnotations; using Timeline.Models.Validation; +using Timeline.Services; namespace Timeline.Models.Http { @@ -96,7 +97,7 @@ namespace Timeline.Models.Http { public HttpTimelineControllerAutoMapperProfile() { - CreateMap(); + CreateMap(); } } } diff --git a/BackEnd/Timeline/Models/Http/User.cs b/BackEnd/Timeline/Models/Http/User.cs index bdb40b9f..994c08bf 100644 --- a/BackEnd/Timeline/Models/Http/User.cs +++ b/BackEnd/Timeline/Models/Http/User.cs @@ -1,10 +1,4 @@ -using AutoMapper; -using Microsoft.AspNetCore.Mvc; -using Microsoft.AspNetCore.Mvc.Infrastructure; -using Microsoft.AspNetCore.Mvc.Routing; -using System.Collections.Generic; -using Timeline.Controllers; -using Timeline.Services; +using System.Collections.Generic; namespace Timeline.Models.Http { @@ -13,6 +7,17 @@ namespace Timeline.Models.Http /// public class HttpUser { + public HttpUser() { } + + public HttpUser(string uniqueId, string username, string nickname, List permissions, HttpUserLinks links) + { + UniqueId = uniqueId; + Username = username; + Nickname = nickname; + Permissions = permissions; + _links = links; + } + /// /// Unique id. /// @@ -44,6 +49,15 @@ namespace Timeline.Models.Http /// public class HttpUserLinks { + public HttpUserLinks() { } + + public HttpUserLinks(string self, string avatar, string timeline) + { + Self = self; + Avatar = avatar; + Timeline = timeline; + } + /// /// Self. /// @@ -57,49 +71,4 @@ namespace Timeline.Models.Http /// public string Timeline { get; set; } = default!; } - - public class HttpUserPermissionsValueConverter : ITypeConverter> - { - public List Convert(UserPermissions source, List destination, ResolutionContext context) - { - return source.ToStringList(); - } - } - - public class HttpUserLinksValueResolver : IValueResolver - { - private readonly IActionContextAccessor _actionContextAccessor; - private readonly IUrlHelperFactory _urlHelperFactory; - - public HttpUserLinksValueResolver(IActionContextAccessor actionContextAccessor, IUrlHelperFactory urlHelperFactory) - { - _actionContextAccessor = actionContextAccessor; - _urlHelperFactory = urlHelperFactory; - } - - public HttpUserLinks Resolve(UserInfo source, HttpUser destination, HttpUserLinks destMember, ResolutionContext context) - { - var actionContext = _actionContextAccessor.AssertActionContextForUrlFill(); - var urlHelper = _urlHelperFactory.GetUrlHelper(actionContext); - - var result = new HttpUserLinks - { - Self = urlHelper.ActionLink(nameof(UserController.Get), nameof(UserController)[0..^nameof(Controller).Length], new { destination.Username }), - Avatar = urlHelper.ActionLink(nameof(UserAvatarController.Get), nameof(UserAvatarController)[0..^nameof(Controller).Length], new { destination.Username }), - Timeline = urlHelper.ActionLink(nameof(TimelineController.TimelineGet), nameof(TimelineController)[0..^nameof(Controller).Length], new { Name = "@" + destination.Username }) - }; - return result; - } - } - - public class HttpUserAutoMapperProfile : Profile - { - public HttpUserAutoMapperProfile() - { - CreateMap>() - .ConvertUsing(); - CreateMap() - .ForMember(u => u._links, opt => opt.MapFrom()); - } - } } diff --git a/BackEnd/Timeline/Models/Mapper/TimelineMapper.cs b/BackEnd/Timeline/Models/Mapper/TimelineMapper.cs new file mode 100644 index 00000000..89a5c0c8 --- /dev/null +++ b/BackEnd/Timeline/Models/Mapper/TimelineMapper.cs @@ -0,0 +1,85 @@ +using AutoMapper; +using Microsoft.AspNetCore.Mvc; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using System.Threading.Tasks; +using Timeline.Controllers; +using Timeline.Entities; +using Timeline.Models.Http; +using Timeline.Services; + +namespace Timeline.Models.Mapper +{ + public static class TimelineMapper + { + public static HttpTimeline MapToHttp(this TimelineEntity entity, IUrlHelper urlHelper) + { + var timelineName = entity.Name is null ? "@" + entity.Owner.Username : entity.Name; + + return new HttpTimeline( + uniqueId: entity.UniqueId, + title: string.IsNullOrEmpty(entity.Title) ? timelineName : entity.Title, + name: timelineName, + nameLastModifed: entity.NameLastModified, + description: entity.Description ?? "", + owner: entity.Owner.MapToHttp(urlHelper), + visibility: entity.Visibility, + members: entity.Members.Select(m => m.User.MapToHttp(urlHelper)).ToList(), + createTime: entity.CreateTime, + lastModified: entity.LastModified, + links: new HttpTimelineLinks( + self: urlHelper.ActionLink(nameof(TimelineController.TimelineGet), nameof(TimelineController)[0..^nameof(Controller).Length], new { timeline = timelineName }), + posts: urlHelper.ActionLink(nameof(TimelineController.PostListGet), nameof(TimelineController)[0..^nameof(Controller).Length], new { timeline = timelineName }) + ) + ); + } + + public static List MapToHttp(this List entites, IUrlHelper urlHelper) + { + return entites.Select(e => e.MapToHttp(urlHelper)).ToList(); + } + + + public static HttpTimelinePost MapToHttp(this TimelinePostEntity entity, string timelineName, IUrlHelper urlHelper) + { + HttpTimelinePostContent? content = null; + + if (entity.Content != null) + { + content = entity.ContentType switch + { + TimelinePostContentTypes.Text => new HttpTimelinePostContent + ( + type: TimelinePostContentTypes.Text, + text: entity.Content, + url: null, + eTag: null + ), + TimelinePostContentTypes.Image => new HttpTimelinePostContent + ( + type: TimelinePostContentTypes.Image, + text: null, + url: urlHelper.ActionLink(nameof(TimelineController.PostDataGet), nameof(TimelineController)[0..^nameof(Controller).Length], new { timeline = timelineName, post = entity.LocalId }), + eTag: $"\"{entity.Content}\"" + ), + _ => throw new DatabaseCorruptedException(string.Format(CultureInfo.InvariantCulture, "Unknown timeline post type {0}.", entity.ContentType)) + }; + } + + return new HttpTimelinePost( + id: entity.LocalId, + content: content, + deleted: content is null, + time: entity.Time, + author: entity.Author?.MapToHttp(urlHelper), + lastUpdated: entity.LastUpdated + ); + } + + public static List MapToHttp(this List entities, string timelineName, IUrlHelper urlHelper) + { + return entities.Select(e => e.MapToHttp(timelineName, urlHelper)).ToList(); + } + } +} diff --git a/BackEnd/Timeline/Models/Mapper/UserMapper.cs b/BackEnd/Timeline/Models/Mapper/UserMapper.cs new file mode 100644 index 00000000..3255dca9 --- /dev/null +++ b/BackEnd/Timeline/Models/Mapper/UserMapper.cs @@ -0,0 +1,38 @@ +using Microsoft.AspNetCore.Mvc; +using System.Collections.Generic; +using System.Linq; +using Timeline.Controllers; +using Timeline.Entities; +using Timeline.Models.Http; +using Timeline.Services; + +namespace Timeline.Models.Mapper +{ + public static class UserMapper + { + public static HttpUser MapToHttp(this UserEntity entity, IUrlHelper urlHelper) + { + return new HttpUser( + uniqueId: entity.UniqueId, + username: entity.Username, + nickname: string.IsNullOrEmpty(entity.Nickname) ? entity.Username : entity.Nickname, + permissions: MapPermission(entity), + links: new HttpUserLinks( + self: urlHelper.ActionLink(nameof(UserController.Get), nameof(UserController)[0..^nameof(Controller).Length], new { entity.Username }), + avatar: urlHelper.ActionLink(nameof(UserAvatarController.Get), nameof(UserAvatarController)[0..^nameof(Controller).Length], new { entity.Username }), + timeline: urlHelper.ActionLink(nameof(TimelineController.TimelineGet), nameof(TimelineController)[0..^nameof(Controller).Length], new { timeline = "@" + entity.Username }) + ) + ); + } + + public static List MapToHttp(this List entities, IUrlHelper urlHelper) + { + return entities.Select(e => e.MapToHttp(urlHelper)).ToList(); + } + + private static List MapPermission(UserEntity entity) + { + return entity.Permissions.Select(p => p.Permission).ToList(); + } + } +} diff --git a/BackEnd/Timeline/Models/Timeline.cs b/BackEnd/Timeline/Models/Timeline.cs new file mode 100644 index 00000000..fa3c0eb3 --- /dev/null +++ b/BackEnd/Timeline/Models/Timeline.cs @@ -0,0 +1,27 @@ +using System; +using System.Collections.Generic; + +namespace Timeline.Models +{ + public enum TimelineVisibility + { + /// + /// All people including those without accounts. + /// + Public, + /// + /// Only people signed in. + /// + Register, + /// + /// Only member. + /// + Private + } + + public static class TimelinePostContentTypes + { + public const string Text = "text"; + public const string Image = "image"; + } +} diff --git a/BackEnd/Timeline/Models/TimelineInfo.cs b/BackEnd/Timeline/Models/TimelineInfo.cs deleted file mode 100644 index 649af274..00000000 --- a/BackEnd/Timeline/Models/TimelineInfo.cs +++ /dev/null @@ -1,130 +0,0 @@ -using System; -using System.Collections.Generic; - -namespace Timeline.Models -{ - public enum TimelineVisibility - { - /// - /// All people including those without accounts. - /// - Public, - /// - /// Only people signed in. - /// - Register, - /// - /// Only member. - /// - Private - } - - public static class TimelinePostContentTypes - { - public const string Text = "text"; - public const string Image = "image"; - } - - public interface ITimelinePostContent - { - public string Type { get; } - } - - public class TextTimelinePostContent : ITimelinePostContent - { - public TextTimelinePostContent(string text) { Text = text; } - - public string Type { get; } = TimelinePostContentTypes.Text; - public string Text { get; set; } - } - - public class ImageTimelinePostContent : ITimelinePostContent - { - public ImageTimelinePostContent(string dataTag) { DataTag = dataTag; } - - public string Type { get; } = TimelinePostContentTypes.Image; - - /// - /// The tag of the data. The tag of the entry in DataManager. Also the etag (not quoted). - /// - public string DataTag { get; set; } - } - - public class TimelinePostInfo - { - public TimelinePostInfo() - { - - } - - public TimelinePostInfo(long id, ITimelinePostContent? content, DateTime time, UserInfo? author, DateTime lastUpdated, string timelineName) - { - Id = id; - Content = content; - Time = time; - Author = author; - LastUpdated = lastUpdated; - TimelineName = timelineName; - } - - public long Id { get; set; } - public ITimelinePostContent? Content { get; set; } - public bool Deleted => Content == null; - public DateTime Time { get; set; } - public UserInfo? Author { get; set; } - public DateTime LastUpdated { get; set; } - public string TimelineName { get; set; } = default!; - } - - public class TimelineInfo - { - public TimelineInfo() - { - - } - - public TimelineInfo( - string uniqueId, - string name, - DateTime nameLastModified, - string title, - string description, - UserInfo owner, - TimelineVisibility visibility, - List members, - DateTime createTime, - DateTime lastModified) - { - UniqueId = uniqueId; - Name = name; - NameLastModified = nameLastModified; - Title = title; - Description = description; - Owner = owner; - Visibility = visibility; - Members = members; - CreateTime = createTime; - LastModified = lastModified; - } - - public string UniqueId { get; set; } = default!; - public string Name { get; set; } = default!; - public DateTime NameLastModified { get; set; } = default!; - public string Title { get; set; } = default!; - 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 - public DateTime CreateTime { get; set; } = default!; - public DateTime LastModified { get; set; } = default!; - } - - public class TimelineChangePropertyRequest - { - public string? Title { get; set; } - public string? Description { get; set; } - public TimelineVisibility? Visibility { get; set; } - } -} diff --git a/BackEnd/Timeline/Models/UserInfo.cs b/BackEnd/Timeline/Models/UserInfo.cs deleted file mode 100644 index e8d57def..00000000 --- a/BackEnd/Timeline/Models/UserInfo.cs +++ /dev/null @@ -1,48 +0,0 @@ -using System; -using Timeline.Services; - -namespace Timeline.Models -{ - public class UserInfo - { - public UserInfo() - { - - } - - public UserInfo( - long id, - string uniqueId, - string username, - string nickname, - UserPermissions permissions, - DateTime usernameChangeTime, - DateTime createTime, - DateTime lastModified, - long version) - { - Id = id; - UniqueId = uniqueId; - Username = username; - Nickname = nickname; - Permissions = permissions; - UsernameChangeTime = usernameChangeTime; - CreateTime = createTime; - LastModified = lastModified; - Version = version; - } - - public long Id { get; set; } - public string UniqueId { get; set; } = default!; - - public string Username { get; set; } = default!; - public string Nickname { get; set; } = default!; - - public UserPermissions Permissions { get; set; } = default!; - - public DateTime UsernameChangeTime { get; set; } - public DateTime CreateTime { get; set; } - public DateTime LastModified { get; set; } - public long Version { get; set; } - } -} diff --git a/BackEnd/Timeline/Resources/Services/Exceptions.Designer.cs b/BackEnd/Timeline/Resources/Services/Exceptions.Designer.cs index 1dbe11c9..7f00d60d 100644 --- a/BackEnd/Timeline/Resources/Services/Exceptions.Designer.cs +++ b/BackEnd/Timeline/Resources/Services/Exceptions.Designer.cs @@ -160,7 +160,7 @@ namespace Timeline.Resources.Services { } /// - /// Looks up a localized string similar to Request timeline name is "{0}". Request timeline post id is "{1}".. + /// Looks up a localized string similar to Request timeline id is "{0}". Request timeline post id is "{1}".. /// internal static string TimelinePostNotExistException { get { @@ -169,7 +169,7 @@ namespace Timeline.Resources.Services { } /// - /// Looks up a localized string similar to Request timeline name is "{0}". Request timeline post id is "{1}". The post does not exist because it is deleted.. + /// Looks up a localized string similar to Request timeline id is "{0}". Request timeline post id is "{1}". The post does not exist because it is deleted.. /// internal static string TimelinePostNotExistExceptionDeleted { get { diff --git a/BackEnd/Timeline/Resources/Services/Exceptions.resx b/BackEnd/Timeline/Resources/Services/Exceptions.resx index e9595caa..d988b084 100644 --- a/BackEnd/Timeline/Resources/Services/Exceptions.resx +++ b/BackEnd/Timeline/Resources/Services/Exceptions.resx @@ -1,76 +1,96 @@  + 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. + --> + + + + + + + + + + + + + + + + + + - + + @@ -89,13 +109,13 @@ text/microsoft-resx - 1.3 + 2.0 - System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.3500.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.3500.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 The required entity of type "{0}" does not exist. @@ -113,7 +133,7 @@ Request timeline name is "{0}". If this is a personal timeline whose name starts with '@', it means the user does not exist and inner exception should be a UserNotExistException. - Request timeline name is "{0}". Request timeline post id is "{1}". + Request timeline id is "{0}". Request timeline post id is "{1}". Request username is "{0}". Request id is "{1}". @@ -137,6 +157,6 @@ image's actual mime type is not the specified one - Request timeline name is "{0}". Request timeline post id is "{1}". The post does not exist because it is deleted. + Request timeline id is "{0}". Request timeline post id is "{1}". The post does not exist because it is deleted. \ No newline at end of file diff --git a/BackEnd/Timeline/Services/BasicTimelineService.cs b/BackEnd/Timeline/Services/BasicTimelineService.cs index 0d9f64a9..be500135 100644 --- a/BackEnd/Timeline/Services/BasicTimelineService.cs +++ b/BackEnd/Timeline/Services/BasicTimelineService.cs @@ -15,6 +15,13 @@ namespace Timeline.Services /// public interface IBasicTimelineService { + /// + /// Check whether a timeline with given id exists without getting full info. + /// + /// The timeline id. + /// True if exist. Otherwise false. + Task CheckExistence(long id); + /// /// Get the timeline id by name. /// @@ -67,6 +74,11 @@ namespace Timeline.Services }; } + public async Task CheckExistence(long id) + { + return await _database.Timelines.AnyAsync(t => t.Id == id); + } + public async Task GetTimelineIdByName(string timelineName) { if (timelineName == null) diff --git a/BackEnd/Timeline/Services/BookmarkTimelineService.cs b/BackEnd/Timeline/Services/BookmarkTimelineService.cs index 2ec3af62..380e1909 100644 --- a/BackEnd/Timeline/Services/BookmarkTimelineService.cs +++ b/BackEnd/Timeline/Services/BookmarkTimelineService.cs @@ -4,7 +4,6 @@ using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using Timeline.Entities; -using Timeline.Models; using Timeline.Services.Exceptions; namespace Timeline.Services @@ -30,9 +29,9 @@ namespace Timeline.Services /// Get bookmarks of a user. /// /// User id of bookmark owner. - /// Bookmarks in order. + /// Id of Bookmark timelines in order. /// Thrown when user does not exist. - Task> GetBookmarks(long userId); + Task> GetBookmarks(long userId); /// /// Add a bookmark to tail to a user. @@ -75,9 +74,9 @@ namespace Timeline.Services { private readonly DatabaseContext _database; private readonly IBasicUserService _userService; - private readonly ITimelineService _timelineService; + private readonly IBasicTimelineService _timelineService; - public BookmarkTimelineService(DatabaseContext database, IBasicUserService userService, ITimelineService timelineService) + public BookmarkTimelineService(DatabaseContext database, IBasicUserService userService, IBasicTimelineService timelineService) { _database = database; _userService = userService; @@ -109,21 +108,14 @@ namespace Timeline.Services await _database.SaveChangesAsync(); } - public async Task> GetBookmarks(long userId) + public async Task> GetBookmarks(long userId) { if (!await _userService.CheckUserExistence(userId)) throw new UserNotExistException(userId); var entities = await _database.BookmarkTimelines.Where(t => t.UserId == userId).OrderBy(t => t.Rank).Select(t => new { t.TimelineId }).ToListAsync(); - List result = new(); - - foreach (var entity in entities) - { - result.Add(await _timelineService.GetTimelineById(entity.TimelineId)); - } - - return result; + return entities.Select(e => e.TimelineId).ToList(); } public async Task MoveBookmark(long userId, string timelineName, long newPosition) diff --git a/BackEnd/Timeline/Services/Exceptions/TimelinePostNotExistException.cs b/BackEnd/Timeline/Services/Exceptions/TimelinePostNotExistException.cs index f95dd410..2a7b5b28 100644 --- a/BackEnd/Timeline/Services/Exceptions/TimelinePostNotExistException.cs +++ b/BackEnd/Timeline/Services/Exceptions/TimelinePostNotExistException.cs @@ -14,16 +14,21 @@ namespace Timeline.Services.Exceptions protected TimelinePostNotExistException( System.Runtime.Serialization.SerializationInfo info, System.Runtime.Serialization.StreamingContext context) : base(info, context) { } + public TimelinePostNotExistException(long? timelineId, long? postId, bool isDelete, string? message = null, Exception? inner = null) + : base(EntityNames.TimelinePost, null, MakeMessage(timelineId, postId, isDelete).AppendAdditionalMessage(message), inner) + { + TimelineId = timelineId; + PostId = postId; + IsDelete = isDelete; + } - public TimelinePostNotExistException(string? timelineName, long? id, bool isDelete, string? message = null, Exception? inner = null) : base(EntityNames.TimelinePost, null, MakeMessage(timelineName, id, isDelete).AppendAdditionalMessage(message), inner) { TimelineName = timelineName; Id = id; IsDelete = isDelete; } - - private static string MakeMessage(string? timelineName, long? id, bool isDelete) + private static string MakeMessage(long? timelineId, long? postId, bool isDelete) { - return string.Format(CultureInfo.InvariantCulture, isDelete ? Resources.Services.Exceptions.TimelinePostNotExistExceptionDeleted : Resources.Services.Exceptions.TimelinePostNotExistException, timelineName ?? "", id); + return string.Format(CultureInfo.InvariantCulture, isDelete ? Resources.Services.Exceptions.TimelinePostNotExistExceptionDeleted : Resources.Services.Exceptions.TimelinePostNotExistException, timelineId, postId); } - public string? TimelineName { get; set; } - public long? Id { get; set; } + public long? TimelineId { get; set; } + public long? PostId { get; set; } /// /// True if the post is deleted. False if the post does not exist at all. diff --git a/BackEnd/Timeline/Services/HighlightTimelineService.cs b/BackEnd/Timeline/Services/HighlightTimelineService.cs index b19efe21..d0a06fe7 100644 --- a/BackEnd/Timeline/Services/HighlightTimelineService.cs +++ b/BackEnd/Timeline/Services/HighlightTimelineService.cs @@ -4,7 +4,6 @@ using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using Timeline.Entities; -using Timeline.Models; using Timeline.Services.Exceptions; namespace Timeline.Services @@ -29,8 +28,8 @@ namespace Timeline.Services /// /// Get all highlight timelines in order. /// - /// A list of all highlight timelines. - Task> GetHighlightTimelines(); + /// Id list of all highlight timelines. + Task> GetHighlightTimelines(); /// /// Add a timeline to highlight list. @@ -75,10 +74,10 @@ namespace Timeline.Services { private readonly DatabaseContext _database; private readonly IBasicUserService _userService; - private readonly ITimelineService _timelineService; + private readonly IBasicTimelineService _timelineService; private readonly IClock _clock; - public HighlightTimelineService(DatabaseContext database, IBasicUserService userService, ITimelineService timelineService, IClock clock) + public HighlightTimelineService(DatabaseContext database, IBasicUserService userService, IBasicTimelineService timelineService, IClock clock) { _database = database; _userService = userService; @@ -106,18 +105,11 @@ namespace Timeline.Services await _database.SaveChangesAsync(); } - public async Task> GetHighlightTimelines() + public async Task> GetHighlightTimelines() { var entities = await _database.HighlightTimelines.OrderBy(t => t.Order).Select(t => new { t.TimelineId }).ToListAsync(); - var result = new List(); - - foreach (var entity in entities) - { - result.Add(await _timelineService.GetTimelineById(entity.TimelineId)); - } - - return result; + return entities.Select(e => e.TimelineId).ToList(); } public async Task RemoveHighlightTimeline(string timelineName, long? operatorId) diff --git a/BackEnd/Timeline/Services/TimelinePostService.cs b/BackEnd/Timeline/Services/TimelinePostService.cs index 35513a36..9f0fd550 100644 --- a/BackEnd/Timeline/Services/TimelinePostService.cs +++ b/BackEnd/Timeline/Services/TimelinePostService.cs @@ -29,103 +29,74 @@ namespace Timeline.Services /// /// Get all the posts in the timeline. /// - /// The name of the timeline. + /// The id of the timeline. /// The time that posts have been modified since. /// Whether include deleted posts. /// A list of all posts. - /// Thrown when is null. - /// Throw when is of bad format. - /// - /// Thrown when timeline with name does not exist. - /// If it is a personal timeline, then inner exception is . - /// - Task> GetPosts(string timelineName, DateTime? modifiedSince = null, bool includeDeleted = false); + /// Thrown when timeline does not exist. + Task> GetPosts(long timelineId, DateTime? modifiedSince = null, bool includeDeleted = false); /// /// Get the etag of data of a post. /// - /// The name of the timeline of the post. + /// The id of the timeline of the post. /// The id of the post. /// The etag of the data. - /// Thrown when is null. - /// Throw when is of bad format. - /// - /// Thrown when timeline with name does not exist. - /// If it is a personal timeline, then inner exception is . - /// + /// Thrown when timeline does not exist. /// Thrown when post of does not exist or has been deleted. /// Thrown when post has no data. - /// - Task GetPostDataETag(string timelineName, long postId); + Task GetPostDataETag(long timelineId, long postId); /// /// Get the data of a post. /// - /// The name of the timeline of the post. + /// The id of the timeline of the post. /// The id of the post. /// The etag of the data. - /// Thrown when is null. - /// Throw when is of bad format. - /// - /// Thrown when timeline with name does not exist. - /// If it is a personal timeline, then inner exception is . - /// + /// Thrown when timeline does not exist. /// Thrown when post of does not exist or has been deleted. /// Thrown when post has no data. - /// - Task GetPostData(string timelineName, long postId); + /// + Task GetPostData(long timelineId, long postId); /// /// Create a new text post in timeline. /// - /// The name of the timeline to create post against. + /// The id of the timeline to create post against. /// The author's user id. /// The content text. /// The time of the post. If null, then current time is used. /// The info of the created post. - /// Thrown when or is null. - /// Throw when is of bad format. - /// - /// Thrown when timeline with name does not exist. - /// If it is a personal timeline, then inner exception is . - /// + /// Thrown when is null. + /// Thrown when timeline does not exist. /// Thrown if user of does not exist. - Task CreateTextPost(string timelineName, long authorId, string text, DateTime? time); + Task CreateTextPost(long timelineId, long authorId, string text, DateTime? time); /// /// Create a new image post in timeline. /// - /// The name of the timeline to create post against. + /// The id of the timeline to create post against. /// The author's user id. /// The image data. /// The time of the post. If null, then use current time. /// The info of the created post. - /// Thrown when or is null. - /// Throw when is of bad format. - /// - /// Thrown when timeline with name does not exist. - /// If it is a personal timeline, then inner exception is . - /// + /// Thrown when is null. + /// Thrown when timeline does not exist. /// Thrown if user of does not exist. /// Thrown if data is not a image. Validated by . - Task CreateImagePost(string timelineName, long authorId, byte[] imageData, DateTime? time); + Task CreateImagePost(long timelineId, long authorId, byte[] imageData, DateTime? time); /// /// Delete a post. /// - /// The name of the timeline to delete post against. + /// The id of the timeline to delete post against. /// The id of the post to delete. - /// Thrown when is null. - /// Throw when is of bad format. - /// - /// Thrown when timeline with name does not exist. - /// If it is a personal timeline, then inner exception is . - /// + /// Thrown when timeline does not exist. /// Thrown when the post with given id does not exist or is deleted already. /// - /// First use to check the permission. + /// First use to check the permission. /// - Task DeletePost(string timelineName, long postId); + Task DeletePost(long timelineId, long postId); /// /// Delete all posts of the given user. Used when delete a user. @@ -136,17 +107,12 @@ namespace Timeline.Services /// /// Verify whether a user has the permission to modify a post. /// - /// The name of the timeline. + /// The id of the timeline. /// The id of the post. /// The id of the user to check on. /// True if you want it to throw . Default false. /// True if can modify, false if can't modify. - /// Thrown when is null. - /// Throw when is of bad format. - /// - /// Thrown when timeline with name does not exist. - /// If it is a personal timeline, then inner exception is . - /// + /// Thrown when timeline does not exist. /// Thrown when the post with given id does not exist or is deleted already and is true. /// /// Unless is true, this method should return true if the post does not exist. @@ -155,7 +121,7 @@ namespace Timeline.Services /// It only checks whether he is the author of the post or the owner of the timeline. /// Return false when user with modifier id does not exist. /// - Task HasPostModifyPermission(string timelineName, long postId, long modifierId, bool throwOnPostNotExist = false); + Task HasPostModifyPermission(long timelineId, long postId, long modifierId, bool throwOnPostNotExist = false); } public class TimelinePostService : ITimelinePostService @@ -179,42 +145,18 @@ namespace Timeline.Services _clock = clock; } - private async Task MapTimelinePostFromEntity(TimelinePostEntity entity, string timelineName) + private async Task CheckTimelineExistence(long timelineId) { - UserInfo? author = entity.AuthorId.HasValue ? await _userService.GetUser(entity.AuthorId.Value) : null; - - ITimelinePostContent? content = null; - - if (entity.Content != null) - { - var type = entity.ContentType; - - content = type switch - { - TimelinePostContentTypes.Text => new TextTimelinePostContent(entity.Content), - TimelinePostContentTypes.Image => new ImageTimelinePostContent(entity.Content), - _ => throw new DatabaseCorruptedException(string.Format(CultureInfo.InvariantCulture, ExceptionDatabaseUnknownContentType, type)) - }; - } - - return new TimelinePostInfo( - id: entity.LocalId, - author: author, - content: content, - time: entity.Time, - lastUpdated: entity.LastUpdated, - timelineName: timelineName - ); + if (!await _basicTimelineService.CheckExistence(timelineId)) + throw new TimelineNotExistException(timelineId); } - public async Task> GetPosts(string timelineName, DateTime? modifiedSince = null, bool includeDeleted = false) + public async Task> GetPosts(long timelineId, DateTime? modifiedSince = null, bool includeDeleted = false) { - modifiedSince = modifiedSince?.MyToUtc(); + await CheckTimelineExistence(timelineId); - if (timelineName == null) - throw new ArgumentNullException(nameof(timelineName)); + modifiedSince = modifiedSince?.MyToUtc(); - var timelineId = await _basicTimelineService.GetTimelineIdByName(timelineName); IQueryable query = _database.TimelinePosts.Where(p => p.TimelineId == timelineId); if (!includeDeleted) @@ -229,30 +171,20 @@ namespace Timeline.Services query = query.OrderBy(p => p.Time); - var postEntities = await query.ToListAsync(); - - var posts = new List(); - foreach (var entity in postEntities) - { - posts.Add(await MapTimelinePostFromEntity(entity, timelineName)); - } - return posts; + return await query.Include(p => p.Author!.Permissions).ToListAsync(); } - public async Task GetPostDataETag(string timelineName, long postId) + public async Task GetPostDataETag(long timelineId, long postId) { - if (timelineName == null) - throw new ArgumentNullException(nameof(timelineName)); - - var timelineId = await _basicTimelineService.GetTimelineIdByName(timelineName); + await CheckTimelineExistence(timelineId); var postEntity = await _database.TimelinePosts.Where(p => p.TimelineId == timelineId && p.LocalId == postId).SingleOrDefaultAsync(); if (postEntity == null) - throw new TimelinePostNotExistException(timelineName, postId, false); + throw new TimelinePostNotExistException(timelineId, postId, false); if (postEntity.Content == null) - throw new TimelinePostNotExistException(timelineName, postId, true); + throw new TimelinePostNotExistException(timelineId, postId, true); if (postEntity.ContentType != TimelinePostContentTypes.Image) throw new TimelinePostNoDataException(ExceptionGetDataNonImagePost); @@ -262,19 +194,17 @@ namespace Timeline.Services return tag; } - public async Task GetPostData(string timelineName, long postId) + public async Task GetPostData(long timelineId, long postId) { - if (timelineName == null) - throw new ArgumentNullException(nameof(timelineName)); + await CheckTimelineExistence(timelineId); - var timelineId = await _basicTimelineService.GetTimelineIdByName(timelineName); var postEntity = await _database.TimelinePosts.Where(p => p.TimelineId == timelineId && p.LocalId == postId).SingleOrDefaultAsync(); if (postEntity == null) - throw new TimelinePostNotExistException(timelineName, postId, false); + throw new TimelinePostNotExistException(timelineId, postId, false); if (postEntity.Content == null) - throw new TimelinePostNotExistException(timelineName, postId, true); + throw new TimelinePostNotExistException(timelineId, postId, true); if (postEntity.ContentType != TimelinePostContentTypes.Image) throw new TimelinePostNoDataException(ExceptionGetDataNonImagePost); @@ -309,16 +239,15 @@ namespace Timeline.Services }; } - public async Task CreateTextPost(string timelineName, long authorId, string text, DateTime? time) + public async Task CreateTextPost(long timelineId, long authorId, string text, DateTime? time) { - time = time?.MyToUtc(); - - if (timelineName == null) - throw new ArgumentNullException(nameof(timelineName)); - if (text == null) + if (text is null) throw new ArgumentNullException(nameof(text)); - var timelineId = await _basicTimelineService.GetTimelineIdByName(timelineName); + await CheckTimelineExistence(timelineId); + + time = time?.MyToUtc(); + var timelineEntity = await _database.Timelines.Where(t => t.Id == timelineId).SingleAsync(); var author = await _userService.GetUser(authorId); @@ -341,27 +270,20 @@ namespace Timeline.Services _database.TimelinePosts.Add(postEntity); await _database.SaveChangesAsync(); + await _database.Entry(postEntity).Reference(p => p.Author).Query().Include(a => a.Permissions).LoadAsync(); - return new TimelinePostInfo( - id: postEntity.LocalId, - content: new TextTimelinePostContent(text), - time: finalTime, - author: author, - lastUpdated: currentTime, - timelineName: timelineName - ); + return postEntity; } - public async Task CreateImagePost(string timelineName, long authorId, byte[] data, DateTime? time) + public async Task CreateImagePost(long timelineId, long authorId, byte[] data, DateTime? time) { - time = time?.MyToUtc(); - - if (timelineName == null) - throw new ArgumentNullException(nameof(timelineName)); - if (data == null) + if (data is null) throw new ArgumentNullException(nameof(data)); - var timelineId = await _basicTimelineService.GetTimelineIdByName(timelineName); + await CheckTimelineExistence(timelineId); + + time = time?.MyToUtc(); + var timelineEntity = await _database.Timelines.Where(t => t.Id == timelineId).SingleAsync(); var author = await _userService.GetUser(authorId); @@ -391,30 +313,22 @@ namespace Timeline.Services _database.TimelinePosts.Add(postEntity); await _database.SaveChangesAsync(); - return new TimelinePostInfo( - id: postEntity.LocalId, - content: new ImageTimelinePostContent(tag), - time: finalTime, - author: author, - lastUpdated: currentTime, - timelineName: timelineName - ); + await _database.Entry(postEntity).Reference(p => p.Author).Query().Include(a => a.Permissions).LoadAsync(); + + return postEntity; } - public async Task DeletePost(string timelineName, long id) + public async Task DeletePost(long timelineId, long postId) { - if (timelineName == null) - throw new ArgumentNullException(nameof(timelineName)); - - var timelineId = await _basicTimelineService.GetTimelineIdByName(timelineName); + await CheckTimelineExistence(timelineId); - var post = await _database.TimelinePosts.Where(p => p.TimelineId == timelineId && p.LocalId == id).SingleOrDefaultAsync(); + var post = await _database.TimelinePosts.Where(p => p.TimelineId == timelineId && p.LocalId == postId).SingleOrDefaultAsync(); if (post == null) - throw new TimelinePostNotExistException(timelineName, id, false); + throw new TimelinePostNotExistException(timelineId, postId, false); if (post.Content == null) - throw new TimelinePostNotExistException(timelineName, id, true); + throw new TimelinePostNotExistException(timelineId, postId, true); string? dataTag = null; @@ -463,12 +377,9 @@ namespace Timeline.Services } } - public async Task HasPostModifyPermission(string timelineName, long postId, long modifierId, bool throwOnPostNotExist = false) + public async Task HasPostModifyPermission(long timelineId, long postId, long modifierId, bool throwOnPostNotExist = false) { - if (timelineName == null) - throw new ArgumentNullException(nameof(timelineName)); - - var timelineId = await _basicTimelineService.GetTimelineIdByName(timelineName); + await CheckTimelineExistence(timelineId); var timelineEntity = await _database.Timelines.Where(t => t.Id == timelineId).Select(t => new { t.OwnerId }).SingleAsync(); @@ -477,14 +388,14 @@ namespace Timeline.Services if (postEntity == null) { if (throwOnPostNotExist) - throw new TimelinePostNotExistException(timelineName, postId, false); + throw new TimelinePostNotExistException(timelineId, postId, false); else return true; } if (postEntity.Content == null && throwOnPostNotExist) { - throw new TimelinePostNotExistException(timelineName, postId, true); + throw new TimelinePostNotExistException(timelineId, postId, true); } return timelineEntity.OwnerId == modifierId || postEntity.AuthorId == modifierId; diff --git a/BackEnd/Timeline/Services/TimelineService.cs b/BackEnd/Timeline/Services/TimelineService.cs index b65b3cf4..e310951a 100644 --- a/BackEnd/Timeline/Services/TimelineService.cs +++ b/BackEnd/Timeline/Services/TimelineService.cs @@ -1,7 +1,6 @@ using Microsoft.EntityFrameworkCore; using System; using System.Collections.Generic; -using System.Globalization; using System.Linq; using System.Threading.Tasks; using Timeline.Entities; @@ -19,7 +18,7 @@ namespace Timeline.Services if (name.StartsWith("@", StringComparison.OrdinalIgnoreCase)) { isPersonal = true; - return name.Substring(1); + return name[1..]; } else { @@ -48,6 +47,13 @@ namespace Timeline.Services public long UserId { get; set; } } + public class TimelineChangePropertyParams + { + public string? Title { get; set; } + public string? Description { get; set; } + public TimelineVisibility? Visibility { get; set; } + } + /// /// This define the interface of both personal timeline and ordinary timeline. /// @@ -56,139 +62,94 @@ namespace Timeline.Services /// /// Get the timeline last modified time (not include name change). /// - /// The name of the timeline. - /// The timeline info. - /// Thrown when is null. - /// Throw when is of bad format. - /// - /// Thrown when timeline with name does not exist. - /// If it is a personal timeline, then inner exception is . - /// - Task GetTimelineLastModifiedTime(string timelineName); + /// The id of the timeline. + /// The timeline modified time. + /// Thrown when timeline does not exist. + Task GetTimelineLastModifiedTime(long id); /// /// Get the timeline unique id. /// - /// The name of the timeline. - /// The timeline info. - /// Thrown when is null. - /// Throw when is of bad format. - /// - /// Thrown when timeline with name does not exist. - /// If it is a personal timeline, then inner exception is . - /// - Task GetTimelineUniqueId(string timelineName); + /// The id of the timeline. + /// The timeline unique id. + /// Thrown when timeline does not exist. + Task GetTimelineUniqueId(long id); /// /// Get the timeline info. /// - /// The name of the timeline. + /// Id of timeline. /// The timeline info. - /// Thrown when is null. - /// Throw when is of bad format. - /// - /// Thrown when timeline with name does not exist. - /// If it is a personal timeline, then inner exception is . - /// - Task GetTimeline(string timelineName); + /// Thrown when timeline does not exist. + Task GetTimeline(long id); /// - /// Get timeline by id. + /// Set the properties of a timeline. /// - /// Id of timeline. - /// The timeline. + /// The id of the timeline. + /// The new properties. Null member means not to change. + /// Thrown when is null. /// Thrown when timeline with given id does not exist. - Task GetTimelineById(long id); + Task ChangeProperty(long id, TimelineChangePropertyParams newProperties); /// - /// Set the properties of a timeline. + /// Add a member to timeline. /// - /// The name of the timeline. - /// The new properties. Null member means not to change. - /// Thrown when or is null. - /// Throw when is of bad format. - /// - /// Thrown when timeline with name does not exist. - /// If it is a personal timeline, then inner exception is . - /// - Task ChangeProperty(string timelineName, TimelineChangePropertyRequest newProperties); + /// Timeline id. + /// User id. + /// True if the memeber was added. False if it is already a member. + /// Thrown when timeline does not exist. + /// Thrown when the user does not exist. + Task AddMember(long timelineId, long userId); /// - /// Change member of timeline. + /// Remove a member from timeline. /// - /// The name of the timeline. - /// A list of usernames of members to add. May be null. - /// A list of usernames of members to remove. May be null. - /// Thrown when is null. - /// Throw when is of bad format. - /// - /// Thrown when timeline with name does not exist. - /// If it is a personal timeline, then inner exception is . - /// - /// Thrown when names in or is not a valid username. - /// Thrown when one of the user to change does not exist. - /// - /// Operating on a username that is of bad format or does not exist always throws. - /// Add a user that already is a member has no effects. - /// Remove a user that is not a member also has not effects. - /// Add and remove an identical user results in no effects. - /// More than one same usernames are regarded as one. - /// - Task ChangeMember(string timelineName, IList? membersToAdd, IList? membersToRemove); + /// Timeline id. + /// User id. + /// True if the memeber was removed. False if it was not a member before. + /// Thrown when timeline does not exist. + /// Thrown when the user does not exist. + Task RemoveMember(long timelineId, long userId); /// /// Check whether a user can manage(change timeline info, member, ...) a timeline. /// - /// The name of the timeline. + /// The id of the timeline. /// The id of the user to check on. /// True if the user can manage the timeline, otherwise false. - /// Thrown when is null. - /// Throw when is of bad format. - /// - /// Thrown when timeline with name does not exist. - /// If it is a personal timeline, then inner exception is . - /// + /// Thrown when timeline does not exist. /// /// This method does not check whether visitor is administrator. /// Return false if user with user id does not exist. /// - Task HasManagePermission(string timelineName, long userId); + Task HasManagePermission(long timelineId, long userId); /// /// Verify whether a visitor has the permission to read a timeline. /// - /// The name of the timeline. + /// The id of the timeline. /// The id of the user to check on. Null means visitor without account. /// True if can read, false if can't read. - /// Thrown when is null. - /// Throw when is of bad format. - /// - /// Thrown when timeline with name does not exist. - /// If it is a personal timeline, then inner exception is . - /// + /// Thrown when timeline does not exist. /// /// This method does not check whether visitor is administrator. /// Return false if user with visitor id does not exist. /// - Task HasReadPermission(string timelineName, long? visitorId); + Task HasReadPermission(long timelineId, long? visitorId); /// /// Verify whether a user is member of a timeline. /// - /// The name of the timeline. + /// The id of the timeline. /// The id of user to check on. /// True if it is a member, false if not. - /// Thrown when is null. - /// Throw when is of bad format. - /// - /// Thrown when timeline with name does not exist. - /// If it is a personal timeline, then inner exception is . - /// + /// Thrown when timeline does not exist. /// /// Timeline owner is also considered as a member. /// Return false when user with user id does not exist. /// - Task IsMemberOf(string timelineName, long userId); + Task IsMemberOf(long timelineId, long userId); /// /// Get all timelines including personal and ordinary timelines. @@ -199,7 +160,7 @@ namespace Timeline.Services /// /// If user with related user id does not exist, empty list will be returned. /// - Task> GetTimelines(TimelineUserRelationship? relate = null, List? visibility = null); + Task> GetTimelines(TimelineUserRelationship? relate = null, List? visibility = null); /// /// Create a timeline. @@ -211,36 +172,33 @@ namespace Timeline.Services /// Thrown when timeline name is invalid. /// Thrown when the timeline already exists. /// Thrown when the owner user does not exist. - Task CreateTimeline(string timelineName, long ownerId); + Task CreateTimeline(string timelineName, long ownerId); /// /// Delete a timeline. /// - /// The name of the timeline to delete. - /// Thrown when is null. - /// Thrown when timeline name is invalid. + /// The id of the timeline to delete. /// Thrown when the timeline does not exist. - Task DeleteTimeline(string timelineName); + Task DeleteTimeline(long id); /// /// Change name of a timeline. /// - /// The old timeline name. + /// The timeline id. /// The new timeline name. - /// The new timeline info. - /// Thrown when or is null. - /// Thrown when or is of invalid format. + /// Thrown when is null. + /// Thrown when is of invalid format. /// Thrown when timeline does not exist. /// Thrown when a timeline with new name already exists. /// /// You can only change name of general timeline. /// - Task ChangeTimelineName(string oldTimelineName, string newTimelineName); + Task ChangeTimelineName(long id, string newTimelineName); } public class TimelineService : BasicTimelineService, ITimelineService { - public TimelineService(DatabaseContext database, IUserService userService, IClock clock) + public TimelineService(DatabaseContext database, IBasicUserService userService, IClock clock) : base(database, userService, clock) { _database = database; @@ -250,12 +208,10 @@ namespace Timeline.Services private readonly DatabaseContext _database; - private readonly IUserService _userService; + private readonly IBasicUserService _userService; private readonly IClock _clock; - private readonly UsernameValidator _usernameValidator = new UsernameValidator(); - private readonly TimelineNameValidator _timelineNameValidator = new TimelineNameValidator(); private void ValidateTimelineName(string name, string paramName) @@ -266,230 +222,138 @@ namespace Timeline.Services } } - /// Remember to include Members when query. - private async Task MapTimelineFromEntity(TimelineEntity entity) + public async Task GetTimelineLastModifiedTime(long id) { - var owner = await _userService.GetUser(entity.OwnerId); + var entity = await _database.Timelines.Where(t => t.Id == id).Select(t => new { t.LastModified }).SingleOrDefaultAsync(); - var members = new List(); - foreach (var memberEntity in entity.Members) - { - members.Add(await _userService.GetUser(memberEntity.UserId)); - } - - var name = entity.Name ?? ("@" + owner.Username); - - return new TimelineInfo( - entity.UniqueId, - name, - entity.NameLastModified, - string.IsNullOrEmpty(entity.Title) ? name : entity.Title, - entity.Description ?? "", - owner, - entity.Visibility, - members, - entity.CreateTime, - entity.LastModified - ); - } - - public async Task GetTimelineLastModifiedTime(string timelineName) - { - if (timelineName == null) - throw new ArgumentNullException(nameof(timelineName)); - - var timelineId = await GetTimelineIdByName(timelineName); - - var timelineEntity = await _database.Timelines.Where(t => t.Id == timelineId).Select(t => new { t.LastModified }).SingleAsync(); - - return timelineEntity.LastModified; - } - - public async Task GetTimelineUniqueId(string timelineName) - { - if (timelineName == null) - throw new ArgumentNullException(nameof(timelineName)); - - var timelineId = await GetTimelineIdByName(timelineName); - - var timelineEntity = await _database.Timelines.Where(t => t.Id == timelineId).Select(t => new { t.UniqueId }).SingleAsync(); + if (entity is null) + throw new TimelineNotExistException(id); - return timelineEntity.UniqueId; + return entity.LastModified; } - public async Task GetTimeline(string timelineName) + public async Task GetTimelineUniqueId(long id) { - if (timelineName == null) - throw new ArgumentNullException(nameof(timelineName)); - - var timelineId = await GetTimelineIdByName(timelineName); + var entity = await _database.Timelines.Where(t => t.Id == id).Select(t => new { t.UniqueId }).SingleOrDefaultAsync(); - var timelineEntity = await _database.Timelines.Where(t => t.Id == timelineId).Include(t => t.Members).SingleAsync(); + if (entity is null) + throw new TimelineNotExistException(id); - return await MapTimelineFromEntity(timelineEntity); + return entity.UniqueId; } - public async Task GetTimelineById(long id) + public async Task GetTimeline(long id) { - var timelineEntity = await _database.Timelines.Where(t => t.Id == id).Include(t => t.Members).SingleOrDefaultAsync(); + var entity = await _database.Timelines.Where(t => t.Id == id).Include(t => t.Owner).ThenInclude(o => o.Permissions).Include(t => t.Members).ThenInclude(m => m.User).ThenInclude(u => u.Permissions).SingleOrDefaultAsync(); - if (timelineEntity is null) + if (entity is null) throw new TimelineNotExistException(id); - return await MapTimelineFromEntity(timelineEntity); + return entity; } - public async Task ChangeProperty(string timelineName, TimelineChangePropertyRequest newProperties) + public async Task ChangeProperty(long id, TimelineChangePropertyParams newProperties) { - if (timelineName == null) - throw new ArgumentNullException(nameof(timelineName)); - if (newProperties == null) + if (newProperties is null) throw new ArgumentNullException(nameof(newProperties)); - var timelineId = await GetTimelineIdByName(timelineName); + var entity = await _database.Timelines.Where(t => t.Id == id).SingleOrDefaultAsync(); - var timelineEntity = await _database.Timelines.Where(t => t.Id == timelineId).SingleAsync(); + if (entity is null) + throw new TimelineNotExistException(id); var changed = false; if (newProperties.Title != null) { changed = true; - timelineEntity.Title = newProperties.Title; + entity.Title = newProperties.Title; } if (newProperties.Description != null) { changed = true; - timelineEntity.Description = newProperties.Description; + entity.Description = newProperties.Description; } if (newProperties.Visibility.HasValue) { changed = true; - timelineEntity.Visibility = newProperties.Visibility.Value; + entity.Visibility = newProperties.Visibility.Value; } if (changed) { var currentTime = _clock.GetCurrentTime(); - timelineEntity.LastModified = currentTime; + entity.LastModified = currentTime; } await _database.SaveChangesAsync(); } - public async Task ChangeMember(string timelineName, IList? add, IList? remove) + public async Task AddMember(long timelineId, long userId) { - if (timelineName == null) - throw new ArgumentNullException(nameof(timelineName)); + if (!await CheckExistence(timelineId)) + throw new TimelineNotExistException(timelineId); - List? RemoveDuplicateAndCheckFormat(IList? list, string paramName) - { - if (list != null) - { - List result = new List(); - var count = list.Count; - for (var index = 0; index < count; index++) - { - var username = list[index]; - if (result.Contains(username)) - { - continue; - } - var (validationResult, message) = _usernameValidator.Validate(username); - if (!validationResult) - throw new ArgumentException(string.Format(CultureInfo.CurrentCulture, ExceptionChangeMemberUsernameBadFormat, index), nameof(paramName)); - result.Add(username); - } - return result; - } - else - { - return null; - } - } - var simplifiedAdd = RemoveDuplicateAndCheckFormat(add, nameof(add)); - var simplifiedRemove = RemoveDuplicateAndCheckFormat(remove, nameof(remove)); + if (!await _userService.CheckUserExistence(userId)) + throw new UserNotExistException(userId); - // remove those both in add and remove - if (simplifiedAdd != null && simplifiedRemove != null) - { - var usersToClean = simplifiedRemove.Where(u => simplifiedAdd.Contains(u)).ToList(); - foreach (var u in usersToClean) - { - simplifiedAdd.Remove(u); - simplifiedRemove.Remove(u); - } + if (await _database.TimelineMembers.AnyAsync(m => m.TimelineId == timelineId && m.UserId == userId)) + return false; - if (simplifiedAdd.Count == 0) - simplifiedAdd = null; - if (simplifiedRemove.Count == 0) - simplifiedRemove = null; - } + var entity = new TimelineMemberEntity { UserId = userId, TimelineId = timelineId }; + _database.TimelineMembers.Add(entity); - if (simplifiedAdd == null && simplifiedRemove == null) - return; + var timelineEntity = await _database.Timelines.Where(t => t.Id == timelineId).SingleAsync(); + timelineEntity.LastModified = _clock.GetCurrentTime(); - var timelineId = await GetTimelineIdByName(timelineName); + await _database.SaveChangesAsync(); + return true; + } - async Task?> CheckExistenceAndGetId(List? list) - { - if (list == null) - return null; + public async Task RemoveMember(long timelineId, long userId) + { + if (!await CheckExistence(timelineId)) + throw new TimelineNotExistException(timelineId); - List result = new List(); - foreach (var username in list) - { - result.Add(await _userService.GetUserIdByUsername(username)); - } - return result; - } - var userIdsAdd = await CheckExistenceAndGetId(simplifiedAdd); - var userIdsRemove = await CheckExistenceAndGetId(simplifiedRemove); + if (!await _userService.CheckUserExistence(userId)) + throw new UserNotExistException(userId); - if (userIdsAdd != null) - { - var membersToAdd = userIdsAdd.Select(id => new TimelineMemberEntity { UserId = id, TimelineId = timelineId }).ToList(); - _database.TimelineMembers.AddRange(membersToAdd); - } + var entity = await _database.TimelineMembers.SingleOrDefaultAsync(m => m.TimelineId == timelineId && m.UserId == userId); + if (entity is null) return false; - if (userIdsRemove != null) - { - var membersToRemove = await _database.TimelineMembers.Where(m => m.TimelineId == timelineId && userIdsRemove.Contains(m.UserId)).ToListAsync(); - _database.TimelineMembers.RemoveRange(membersToRemove); - } + _database.TimelineMembers.Remove(entity); var timelineEntity = await _database.Timelines.Where(t => t.Id == timelineId).SingleAsync(); timelineEntity.LastModified = _clock.GetCurrentTime(); await _database.SaveChangesAsync(); + return true; } - public async Task HasManagePermission(string timelineName, long userId) + public async Task HasManagePermission(long timelineId, long userId) { - if (timelineName == null) - throw new ArgumentNullException(nameof(timelineName)); + var entity = await _database.Timelines.Where(t => t.Id == timelineId).Select(t => new { t.OwnerId }).SingleOrDefaultAsync(); - var timelineId = await GetTimelineIdByName(timelineName); - var timelineEntity = await _database.Timelines.Where(t => t.Id == timelineId).Select(t => new { t.OwnerId }).SingleAsync(); + if (entity is null) + throw new TimelineNotExistException(timelineId); - return userId == timelineEntity.OwnerId; + return entity.OwnerId == userId; } - public async Task HasReadPermission(string timelineName, long? visitorId) + public async Task HasReadPermission(long timelineId, long? visitorId) { - if (timelineName == null) - throw new ArgumentNullException(nameof(timelineName)); + var entity = await _database.Timelines.Where(t => t.Id == timelineId).Select(t => new { t.Visibility }).SingleOrDefaultAsync(); - var timelineId = await GetTimelineIdByName(timelineName); - var timelineEntity = await _database.Timelines.Where(t => t.Id == timelineId).Select(t => new { t.Visibility }).SingleAsync(); + if (entity is null) + throw new TimelineNotExistException(timelineId); - if (timelineEntity.Visibility == TimelineVisibility.Public) + if (entity.Visibility == TimelineVisibility.Public) return true; - if (timelineEntity.Visibility == TimelineVisibility.Register && visitorId != null) + if (entity.Visibility == TimelineVisibility.Register && visitorId != null) return true; if (visitorId == null) @@ -499,26 +363,24 @@ namespace Timeline.Services else { var memberEntity = await _database.TimelineMembers.Where(m => m.UserId == visitorId && m.TimelineId == timelineId).SingleOrDefaultAsync(); - return memberEntity != null; + return memberEntity is not null; } } - public async Task IsMemberOf(string timelineName, long userId) + public async Task IsMemberOf(long timelineId, long userId) { - if (timelineName == null) - throw new ArgumentNullException(nameof(timelineName)); - - var timelineId = await GetTimelineIdByName(timelineName); + var entity = await _database.Timelines.Where(t => t.Id == timelineId).Select(t => new { t.OwnerId }).SingleOrDefaultAsync(); - var timelineEntity = await _database.Timelines.Where(t => t.Id == timelineId).Select(t => new { t.OwnerId }).SingleAsync(); + if (entity is null) + throw new TimelineNotExistException(timelineId); - if (userId == timelineEntity.OwnerId) + if (userId == entity.OwnerId) return true; return await _database.TimelineMembers.AnyAsync(m => m.TimelineId == timelineId && m.UserId == userId); } - public async Task> GetTimelines(TimelineUserRelationship? relate = null, List? visibility = null) + public async Task> GetTimelines(TimelineUserRelationship? relate = null, List? visibility = null) { List entities; @@ -535,7 +397,7 @@ namespace Timeline.Services if (relate == null) { - entities = await ApplyTimelineVisibilityFilter(_database.Timelines).Include(t => t.Members).ToListAsync(); + entities = await ApplyTimelineVisibilityFilter(_database.Timelines).Include(t => t.Owner).ThenInclude(o => o.Permissions).Include(t => t.Members).ThenInclude(m => m.User).ThenInclude(u => u.Permissions).ToListAsync(); } else { @@ -543,80 +405,66 @@ namespace Timeline.Services if ((relate.Type & TimelineUserRelationshipType.Own) != 0) { - entities.AddRange(await ApplyTimelineVisibilityFilter(_database.Timelines.Where(t => t.OwnerId == relate.UserId)).Include(t => t.Members).ToListAsync()); + entities.AddRange(await ApplyTimelineVisibilityFilter(_database.Timelines.Where(t => t.OwnerId == relate.UserId)).Include(t => t.Owner).ThenInclude(o => o.Permissions).Include(t => t.Members).ThenInclude(m => m.User).ThenInclude(u => u.Permissions).ToListAsync()); } if ((relate.Type & TimelineUserRelationshipType.Join) != 0) { - entities.AddRange(await ApplyTimelineVisibilityFilter(_database.TimelineMembers.Where(m => m.UserId == relate.UserId).Include(m => m.Timeline).ThenInclude(t => t.Members).Select(m => m.Timeline)).ToListAsync()); + entities.AddRange(await ApplyTimelineVisibilityFilter(_database.TimelineMembers.Where(m => m.UserId == relate.UserId).Include(m => m.Timeline).ThenInclude(t => t.Members).ThenInclude(m => m.User).ThenInclude(u => u.Permissions).Include(t => t.Timeline.Owner.Permissions).Select(m => m.Timeline)).ToListAsync()); } } - var result = new List(); - foreach (var entity in entities) - { - result.Add(await MapTimelineFromEntity(entity)); - } - - return result; + return entities; } - public async Task CreateTimeline(string name, long owner) + public async Task CreateTimeline(string name, long owner) { if (name == null) throw new ArgumentNullException(nameof(name)); ValidateTimelineName(name, nameof(name)); - var user = await _userService.GetUser(owner); - var conflict = await _database.Timelines.AnyAsync(t => t.Name == name); if (conflict) throw new EntityAlreadyExistException(EntityNames.Timeline, null, ExceptionTimelineNameConflict); - var newEntity = CreateNewTimelineEntity(name, user.Id); + var entity = CreateNewTimelineEntity(name, owner); - _database.Timelines.Add(newEntity); + _database.Timelines.Add(entity); await _database.SaveChangesAsync(); - return await MapTimelineFromEntity(newEntity); + await _database.Entry(entity).Reference(e => e.Owner).Query().Include(o => o.Permissions).LoadAsync(); + await _database.Entry(entity).Collection(e => e.Members).Query().Include(m => m.User).ThenInclude(u => u.Permissions).LoadAsync(); + + return entity; } - public async Task DeleteTimeline(string name) + public async Task DeleteTimeline(long id) { - if (name == null) - throw new ArgumentNullException(nameof(name)); - - ValidateTimelineName(name, nameof(name)); + var entity = await _database.Timelines.Where(t => t.Id == id).SingleOrDefaultAsync(); - var entity = await _database.Timelines.Where(t => t.Name == name).SingleOrDefaultAsync(); - - if (entity == null) - throw new TimelineNotExistException(name); + if (entity is null) + throw new TimelineNotExistException(id); _database.Timelines.Remove(entity); await _database.SaveChangesAsync(); } - public async Task ChangeTimelineName(string oldTimelineName, string newTimelineName) + public async Task ChangeTimelineName(long id, string newTimelineName) { - if (oldTimelineName == null) - throw new ArgumentNullException(nameof(oldTimelineName)); if (newTimelineName == null) throw new ArgumentNullException(nameof(newTimelineName)); - ValidateTimelineName(oldTimelineName, nameof(oldTimelineName)); ValidateTimelineName(newTimelineName, nameof(newTimelineName)); - var entity = await _database.Timelines.Include(t => t.Members).Where(t => t.Name == oldTimelineName).SingleOrDefaultAsync(); + var entity = await _database.Timelines.Where(t => t.Id == id).SingleOrDefaultAsync(); - if (entity == null) - throw new TimelineNotExistException(oldTimelineName); + if (entity is null) + throw new TimelineNotExistException(id); - if (oldTimelineName == newTimelineName) - return await MapTimelineFromEntity(entity); + if (entity.Name == newTimelineName) return; var conflict = await _database.Timelines.AnyAsync(t => t.Name == newTimelineName); @@ -630,8 +478,19 @@ namespace Timeline.Services entity.LastModified = now; await _database.SaveChangesAsync(); + } + } - return await MapTimelineFromEntity(entity); + public static class TimelineServiceExtensions + { + public static async Task> GetTimelineList(this ITimelineService service, IEnumerable ids) + { + var timelines = new List(); + foreach (var id in ids) + { + timelines.Add(await service.GetTimeline(id)); + } + return timelines; } } } diff --git a/BackEnd/Timeline/Services/UserService.cs b/BackEnd/Timeline/Services/UserService.cs index c99e86b0..d341759c 100644 --- a/BackEnd/Timeline/Services/UserService.cs +++ b/BackEnd/Timeline/Services/UserService.cs @@ -7,7 +7,6 @@ using System.Linq; using System.Threading.Tasks; using Timeline.Entities; using Timeline.Helpers; -using Timeline.Models; using Timeline.Models.Validation; using Timeline.Services.Exceptions; using static Timeline.Resources.Services.UserService; @@ -32,13 +31,13 @@ namespace Timeline.Services /// The id of the user. /// The user info. /// Thrown when the user with given id does not exist. - Task GetUser(long id); + Task GetUser(long id); /// /// List all users. /// /// The user info of users. - Task> GetUsers(); + Task> GetUsers(); /// /// Create a user with given info. @@ -49,7 +48,7 @@ namespace Timeline.Services /// Thrown when or is null. /// Thrown when or is of bad format. /// Thrown when a user with given username already exists. - Task CreateUser(string username, string password); + Task CreateUser(string username, string password); /// /// Modify a user. @@ -62,7 +61,7 @@ namespace Timeline.Services /// /// Version will increase if password is changed. /// - Task ModifyUser(long id, ModifyUserParams? param); + Task ModifyUser(long id, ModifyUserParams? param); } public class UserService : BasicUserService, IUserService @@ -73,17 +72,15 @@ namespace Timeline.Services private readonly DatabaseContext _databaseContext; private readonly IPasswordService _passwordService; - private readonly IUserPermissionService _userPermissionService; private readonly UsernameValidator _usernameValidator = new UsernameValidator(); private readonly NicknameValidator _nicknameValidator = new NicknameValidator(); - public UserService(ILogger logger, DatabaseContext databaseContext, IPasswordService passwordService, IUserPermissionService userPermissionService, IClock clock) : base(databaseContext) + public UserService(ILogger logger, DatabaseContext databaseContext, IPasswordService passwordService, IClock clock) : base(databaseContext) { _logger = logger; _databaseContext = databaseContext; _passwordService = passwordService; - _userPermissionService = userPermissionService; _clock = clock; } @@ -116,43 +113,22 @@ namespace Timeline.Services throw new EntityAlreadyExistException(EntityNames.User, ExceptionUsernameConflict); } - private async Task CreateUserFromEntity(UserEntity entity) + public async Task GetUser(long id) { - var permission = await _userPermissionService.GetPermissionsOfUserAsync(entity.Id); - return new UserInfo( - entity.Id, - entity.UniqueId, - entity.Username, - string.IsNullOrEmpty(entity.Nickname) ? entity.Username : entity.Nickname, - permission, - entity.UsernameChangeTime, - entity.CreateTime, - entity.LastModified, - entity.Version - ); - } - - public async Task GetUser(long id) - { - var user = await _databaseContext.Users.Where(u => u.Id == id).SingleOrDefaultAsync(); + var user = await _databaseContext.Users.Where(u => u.Id == id).Include(u => u.Permissions).SingleOrDefaultAsync(); if (user == null) throw new UserNotExistException(id); - return await CreateUserFromEntity(user); + return user; } - public async Task> GetUsers() + public async Task> GetUsers() { - List result = new(); - foreach (var entity in await _databaseContext.Users.ToArrayAsync()) - { - result.Add(await CreateUserFromEntity(entity)); - } - return result; + return await _databaseContext.Users.Include(u => u.Permissions).ToListAsync(); } - public async Task CreateUser(string username, string password) + public async Task CreateUser(string username, string password) { if (username == null) throw new ArgumentNullException(nameof(username)); @@ -177,10 +153,12 @@ namespace Timeline.Services _logger.LogInformation(Log.Format(LogDatabaseCreate, ("Id", newEntity.Id), ("Username", username))); - return await CreateUserFromEntity(newEntity); + await _databaseContext.Entry(newEntity).Collection(e => e.Permissions).LoadAsync(); + + return newEntity; } - public async Task ModifyUser(long id, ModifyUserParams? param) + public async Task ModifyUser(long id, ModifyUserParams? param) { if (param != null) { @@ -194,7 +172,7 @@ namespace Timeline.Services CheckNicknameFormat(param.Nickname, nameof(param)); } - var entity = await _databaseContext.Users.Where(u => u.Id == id).SingleOrDefaultAsync(); + var entity = await _databaseContext.Users.Where(u => u.Id == id).Include(u => u.Permissions).SingleOrDefaultAsync(); if (entity == null) throw new UserNotExistException(id); @@ -238,7 +216,7 @@ namespace Timeline.Services _logger.LogInformation(LogDatabaseUpdate, ("Id", id)); } - return await CreateUserFromEntity(entity); + return entity; } } } diff --git a/BackEnd/Timeline/Services/UserTokenManager.cs b/BackEnd/Timeline/Services/UserTokenManager.cs index b887b987..4e24c922 100644 --- a/BackEnd/Timeline/Services/UserTokenManager.cs +++ b/BackEnd/Timeline/Services/UserTokenManager.cs @@ -1,6 +1,7 @@ using Microsoft.Extensions.Logging; using System; using System.Threading.Tasks; +using Timeline.Entities; using Timeline.Helpers; using Timeline.Models; using Timeline.Services.Exceptions; @@ -10,7 +11,7 @@ namespace Timeline.Services public class UserTokenCreateResult { public string Token { get; set; } = default!; - public UserInfo User { get; set; } = default!; + public UserEntity User { get; set; } = default!; } public interface IUserTokenManager @@ -38,7 +39,7 @@ namespace Timeline.Services /// Thrown when the token is of bad version. /// Thrown when the token is of bad format. /// Thrown when the user specified by the token does not exist. Usually the user had been deleted after the token was issued. - public Task VerifyToken(string token); + public Task VerifyToken(string token); } public class UserTokenManager : IUserTokenManager @@ -75,7 +76,7 @@ namespace Timeline.Services } - public async Task VerifyToken(string token) + public async Task VerifyToken(string token) { if (token == null) throw new ArgumentNullException(nameof(token)); -- cgit v1.2.3