From 7205a09eaa9410399a54935dbe02114e8aa2699b Mon Sep 17 00:00:00 2001 From: Nicolas Constant Date: Mon, 13 Dec 2021 20:43:57 -0500 Subject: [PATCH] added Delete logic --- .../BusinessUseCases/ProcessDeleteUser.cs | 51 ++++++++++ .../Tools/SigValidationResultExtractor.cs | 22 +++++ src/BirdsiteLive.Domain/UserService.cs | 15 ++- .../Actions/RemoveFollowerAction.cs | 29 ++---- .../ProcessDeleteUserTests.cs | 97 +++++++++++++++++++ .../Actions/RemoveFollowerActionTests.cs | 54 +++-------- 6 files changed, 199 insertions(+), 69 deletions(-) create mode 100644 src/BirdsiteLive.Domain/BusinessUseCases/ProcessDeleteUser.cs create mode 100644 src/BirdsiteLive.Domain/Tools/SigValidationResultExtractor.cs create mode 100644 src/Tests/BirdsiteLive.Domain.Tests/BusinessUseCases/ProcessDeleteUserTests.cs diff --git a/src/BirdsiteLive.Domain/BusinessUseCases/ProcessDeleteUser.cs b/src/BirdsiteLive.Domain/BusinessUseCases/ProcessDeleteUser.cs new file mode 100644 index 0000000..a35b6c8 --- /dev/null +++ b/src/BirdsiteLive.Domain/BusinessUseCases/ProcessDeleteUser.cs @@ -0,0 +1,51 @@ +using System.Linq; +using System.Threading.Tasks; +using BirdsiteLive.DAL.Contracts; +using BirdsiteLive.DAL.Models; + +namespace BirdsiteLive.Domain.BusinessUseCases +{ + public interface IProcessDeleteUser + { + Task ExecuteAsync(Follower follower); + Task ExecuteAsync(string followerUsername, string followerDomain); + } + + public class ProcessDeleteUser : IProcessDeleteUser + { + private readonly IFollowersDal _followersDal; + private readonly ITwitterUserDal _twitterUserDal; + + #region Ctor + public ProcessDeleteUser(IFollowersDal followersDal, ITwitterUserDal twitterUserDal) + { + _followersDal = followersDal; + _twitterUserDal = twitterUserDal; + } + #endregion + + public async Task ExecuteAsync(string followerUsername, string followerDomain) + { + // Get Follower and Twitter Users + var follower = await _followersDal.GetFollowerAsync(followerUsername, followerDomain); + if (follower == null) return; + + await ExecuteAsync(follower); + } + + public async Task ExecuteAsync(Follower follower) + { + // Remove twitter users if no more followers + var followings = follower.Followings; + foreach (var following in followings) + { + var followers = await _followersDal.GetFollowersAsync(following); + if (followers.Length == 1 && followers.First().Id == follower.Id) + await _twitterUserDal.DeleteTwitterUserAsync(following); + } + + // Remove follower from DB + await _followersDal.DeleteFollowerAsync(follower.Id); + } + } +} \ No newline at end of file diff --git a/src/BirdsiteLive.Domain/Tools/SigValidationResultExtractor.cs b/src/BirdsiteLive.Domain/Tools/SigValidationResultExtractor.cs new file mode 100644 index 0000000..1d6890a --- /dev/null +++ b/src/BirdsiteLive.Domain/Tools/SigValidationResultExtractor.cs @@ -0,0 +1,22 @@ +using System.Linq; + +namespace BirdsiteLive.Domain.Tools +{ + public class SigValidationResultExtractor + { + public static string GetUserName(SignatureValidationResult result) + { + return result.User.preferredUsername.ToLowerInvariant().Trim(); + } + + public static string GetHost(SignatureValidationResult result) + { + return result.User.url.Replace("https://", string.Empty).Split('/').First(); + } + + public static string GetSharedInbox(SignatureValidationResult result) + { + return result.User?.endpoints?.sharedInbox; + } + } +} \ No newline at end of file diff --git a/src/BirdsiteLive.Domain/UserService.cs b/src/BirdsiteLive.Domain/UserService.cs index fedfcca..a080180 100644 --- a/src/BirdsiteLive.Domain/UserService.cs +++ b/src/BirdsiteLive.Domain/UserService.cs @@ -34,6 +34,7 @@ namespace BirdsiteLive.Domain public class UserService : IUserService { + private readonly IProcessDeleteUser _processDeleteUser; private readonly IProcessFollowUser _processFollowUser; private readonly IProcessUndoFollowUser _processUndoFollowUser; @@ -48,7 +49,7 @@ namespace BirdsiteLive.Domain private readonly IModerationRepository _moderationRepository; #region Ctor - public UserService(InstanceSettings instanceSettings, ICryptoService cryptoService, IActivityPubService activityPubService, IProcessFollowUser processFollowUser, IProcessUndoFollowUser processUndoFollowUser, IStatusExtractor statusExtractor, IExtractionStatisticsHandler statisticsHandler, ITwitterUserService twitterUserService, IModerationRepository moderationRepository) + public UserService(InstanceSettings instanceSettings, ICryptoService cryptoService, IActivityPubService activityPubService, IProcessFollowUser processFollowUser, IProcessUndoFollowUser processUndoFollowUser, IStatusExtractor statusExtractor, IExtractionStatisticsHandler statisticsHandler, ITwitterUserService twitterUserService, IModerationRepository moderationRepository, IProcessDeleteUser processDeleteUser) { _instanceSettings = instanceSettings; _cryptoService = cryptoService; @@ -59,6 +60,7 @@ namespace BirdsiteLive.Domain _statisticsHandler = statisticsHandler; _twitterUserService = twitterUserService; _moderationRepository = moderationRepository; + _processDeleteUser = processDeleteUser; } #endregion @@ -128,10 +130,10 @@ namespace BirdsiteLive.Domain if (!sigValidation.SignatureIsValidated) return false; // Prepare data - var followerUserName = sigValidation.User.preferredUsername.ToLowerInvariant().Trim(); - var followerHost = sigValidation.User.url.Replace("https://", string.Empty).Split('/').First(); + var followerUserName = SigValidationResultExtractor.GetUserName(sigValidation); + var followerHost = SigValidationResultExtractor.GetHost(sigValidation); var followerInbox = sigValidation.User.inbox; - var followerSharedInbox = sigValidation.User?.endpoints?.sharedInbox; + var followerSharedInbox = SigValidationResultExtractor.GetSharedInbox(sigValidation); var twitterUser = activity.apObject.Split('/').Last().Replace("@", string.Empty).ToLowerInvariant().Trim(); // Make sure to only keep routes @@ -268,7 +270,10 @@ namespace BirdsiteLive.Domain if (!sigValidation.SignatureIsValidated) return false; // Remove user and followings - throw new NotImplementedException(); + var followerUserName = SigValidationResultExtractor.GetUserName(sigValidation); + var followerHost = SigValidationResultExtractor.GetHost(sigValidation); + + await _processDeleteUser.ExecuteAsync(followerUserName, followerHost); return true; } diff --git a/src/BirdsiteLive.Moderation/Actions/RemoveFollowerAction.cs b/src/BirdsiteLive.Moderation/Actions/RemoveFollowerAction.cs index 8ab3132..4721154 100644 --- a/src/BirdsiteLive.Moderation/Actions/RemoveFollowerAction.cs +++ b/src/BirdsiteLive.Moderation/Actions/RemoveFollowerAction.cs @@ -1,12 +1,6 @@ -using System; -using System.Linq; -using System.Threading.Tasks; -using BirdsiteLive.ActivityPub; -using BirdsiteLive.ActivityPub.Converters; -using BirdsiteLive.Common.Settings; -using BirdsiteLive.DAL.Contracts; +using System.Threading.Tasks; using BirdsiteLive.DAL.Models; -using BirdsiteLive.Domain; +using BirdsiteLive.Domain.BusinessUseCases; namespace BirdsiteLive.Moderation.Actions { @@ -17,16 +11,14 @@ namespace BirdsiteLive.Moderation.Actions public class RemoveFollowerAction : IRemoveFollowerAction { - private readonly IFollowersDal _followersDal; - private readonly ITwitterUserDal _twitterUserDal; private readonly IRejectAllFollowingsAction _rejectAllFollowingsAction; + private readonly IProcessDeleteUser _processDeleteUser; #region Ctor - public RemoveFollowerAction(IFollowersDal followersDal, ITwitterUserDal twitterUserDal, IRejectAllFollowingsAction rejectAllFollowingsAction) + public RemoveFollowerAction(IRejectAllFollowingsAction rejectAllFollowingsAction, IProcessDeleteUser processDeleteUser) { - _followersDal = followersDal; - _twitterUserDal = twitterUserDal; _rejectAllFollowingsAction = rejectAllFollowingsAction; + _processDeleteUser = processDeleteUser; } #endregion @@ -36,16 +28,7 @@ namespace BirdsiteLive.Moderation.Actions await _rejectAllFollowingsAction.ProcessAsync(follower); // Remove twitter users if no more followers - var followings = follower.Followings; - foreach (var following in followings) - { - var followers = await _followersDal.GetFollowersAsync(following); - if (followers.Length == 1 && followers.First().Id == follower.Id) - await _twitterUserDal.DeleteTwitterUserAsync(following); - } - - // Remove follower from DB - await _followersDal.DeleteFollowerAsync(follower.Id); + await _processDeleteUser.ExecuteAsync(follower); } } } \ No newline at end of file diff --git a/src/Tests/BirdsiteLive.Domain.Tests/BusinessUseCases/ProcessDeleteUserTests.cs b/src/Tests/BirdsiteLive.Domain.Tests/BusinessUseCases/ProcessDeleteUserTests.cs new file mode 100644 index 0000000..85900da --- /dev/null +++ b/src/Tests/BirdsiteLive.Domain.Tests/BusinessUseCases/ProcessDeleteUserTests.cs @@ -0,0 +1,97 @@ +using System.Collections.Generic; +using System.Threading.Tasks; +using BirdsiteLive.DAL.Contracts; +using BirdsiteLive.DAL.Models; +using BirdsiteLive.Domain.BusinessUseCases; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Moq; + +namespace BirdsiteLive.Domain.Tests.BusinessUseCases +{ + [TestClass] + public class ProcessDeleteUserTests + { + [TestMethod] + public async Task ExecuteAsync_NoMoreFollowings() + { + #region Stubs + var follower = new Follower + { + Id = 12, + Followings = new List { 1 } + }; + #endregion + + #region Mocks + var followersDalMock = new Mock(MockBehavior.Strict); + followersDalMock + .Setup(x => x.GetFollowersAsync( + It.Is(y => y == 1))) + .ReturnsAsync(new[] { follower }); + + followersDalMock + .Setup(x => x.DeleteFollowerAsync( + It.Is(y => y == 12))) + .Returns(Task.CompletedTask); + + var twitterUserDalMock = new Mock(MockBehavior.Strict); + twitterUserDalMock + .Setup(x => x.DeleteTwitterUserAsync( + It.Is(y => y == 1))) + .Returns(Task.CompletedTask); + #endregion + + var action = new ProcessDeleteUser(followersDalMock.Object, twitterUserDalMock.Object); + await action.ExecuteAsync(follower); + + #region Validations + followersDalMock.VerifyAll(); + twitterUserDalMock.VerifyAll(); + #endregion + } + + [TestMethod] + public async Task ExecuteAsync_HaveFollowings() + { + #region Stubs + var follower = new Follower + { + Id = 12, + Followings = new List { 1 } + }; + + var followers = new List + { + follower, + new Follower + { + Id = 11 + } + }; + #endregion + + #region Mocks + var followersDalMock = new Mock(MockBehavior.Strict); + followersDalMock + .Setup(x => x.GetFollowersAsync( + It.Is(y => y == 1))) + .ReturnsAsync(followers.ToArray()); + + followersDalMock + .Setup(x => x.DeleteFollowerAsync( + It.Is(y => y == 12))) + .Returns(Task.CompletedTask); + + var twitterUserDalMock = new Mock(MockBehavior.Strict); + #endregion + + var action = new ProcessDeleteUser(followersDalMock.Object, twitterUserDalMock.Object); + await action.ExecuteAsync(follower); + + #region Validations + followersDalMock.VerifyAll(); + twitterUserDalMock.VerifyAll(); + #endregion + } + } +} \ No newline at end of file diff --git a/src/Tests/BirdsiteLive.Moderation.Tests/Actions/RemoveFollowerActionTests.cs b/src/Tests/BirdsiteLive.Moderation.Tests/Actions/RemoveFollowerActionTests.cs index 3b83739..34f40b2 100644 --- a/src/Tests/BirdsiteLive.Moderation.Tests/Actions/RemoveFollowerActionTests.cs +++ b/src/Tests/BirdsiteLive.Moderation.Tests/Actions/RemoveFollowerActionTests.cs @@ -2,6 +2,7 @@ using System.Threading.Tasks; using BirdsiteLive.DAL.Contracts; using BirdsiteLive.DAL.Models; +using BirdsiteLive.Domain.BusinessUseCases; using BirdsiteLive.Moderation.Actions; using Microsoft.VisualStudio.TestTools.UnitTesting; using Moq; @@ -29,31 +30,19 @@ namespace BirdsiteLive.Moderation.Tests.Actions It.Is(y => y.Id == follower.Id))) .Returns(Task.CompletedTask); - var followersDalMock = new Mock(MockBehavior.Strict); - followersDalMock - .Setup(x => x.GetFollowersAsync( - It.Is(y => y == 1))) - .ReturnsAsync(new[] {follower}); - - followersDalMock - .Setup(x => x.DeleteFollowerAsync( - It.Is(y => y == 12))) - .Returns(Task.CompletedTask); - - var twitterUserDalMock = new Mock(MockBehavior.Strict); - twitterUserDalMock - .Setup(x => x.DeleteTwitterUserAsync( - It.Is(y => y == 1))) + var processDeleteUserMock = new Mock(MockBehavior.Strict); + processDeleteUserMock + .Setup(x => x.ExecuteAsync( + It.Is(y => y.Id == follower.Id))) .Returns(Task.CompletedTask); #endregion - var action = new RemoveFollowerAction(followersDalMock.Object, twitterUserDalMock.Object, rejectAllFollowingsActionMock.Object); + var action = new RemoveFollowerAction(rejectAllFollowingsActionMock.Object, processDeleteUserMock.Object); await action.ProcessAsync(follower); #region Validations - followersDalMock.VerifyAll(); - twitterUserDalMock.VerifyAll(); rejectAllFollowingsActionMock.VerifyAll(); + processDeleteUserMock.VerifyAll(); #endregion } @@ -66,15 +55,6 @@ namespace BirdsiteLive.Moderation.Tests.Actions Id = 12, Followings = new List { 1 } }; - - var followers = new List - { - follower, - new Follower - { - Id = 11 - } - }; #endregion #region Mocks @@ -84,27 +64,19 @@ namespace BirdsiteLive.Moderation.Tests.Actions It.Is(y => y.Id == follower.Id))) .Returns(Task.CompletedTask); - var followersDalMock = new Mock(MockBehavior.Strict); - followersDalMock - .Setup(x => x.GetFollowersAsync( - It.Is(y => y == 1))) - .ReturnsAsync(followers.ToArray()); - - followersDalMock - .Setup(x => x.DeleteFollowerAsync( - It.Is(y => y == 12))) + var processDeleteUserMock = new Mock(MockBehavior.Strict); + processDeleteUserMock + .Setup(x => x.ExecuteAsync( + It.Is(y => y.Id == follower.Id))) .Returns(Task.CompletedTask); - - var twitterUserDalMock = new Mock(MockBehavior.Strict); #endregion - var action = new RemoveFollowerAction(followersDalMock.Object, twitterUserDalMock.Object, rejectAllFollowingsActionMock.Object); + var action = new RemoveFollowerAction(rejectAllFollowingsActionMock.Object, processDeleteUserMock.Object); await action.ProcessAsync(follower); #region Validations - followersDalMock.VerifyAll(); - twitterUserDalMock.VerifyAll(); rejectAllFollowingsActionMock.VerifyAll(); + processDeleteUserMock.VerifyAll(); #endregion } }