first iteration of logic to apply moderation policy

This commit is contained in:
Nicolas Constant 2021-02-05 01:12:54 -05:00
parent 392c7ca494
commit 9920316863
No known key found for this signature in database
GPG Key ID: 1E9F677FB01A5688
11 changed files with 289 additions and 1 deletions

View File

@ -0,0 +1,44 @@
using System.Linq;
using System.Threading.Tasks;
using BirdsiteLive.DAL.Contracts;
using BirdsiteLive.DAL.Models;
namespace BirdsiteLive.Moderation.Actions
{
public interface IRemoveFollowerAction
{
Task ProcessAsync(Follower follower);
}
public class RemoveFollowerAction : IRemoveFollowerAction
{
private readonly IFollowersDal _followersDal;
private readonly ITwitterUserDal _twitterUserDal;
#region Ctor
public RemoveFollowerAction(IFollowersDal followersDal, ITwitterUserDal twitterUserDal)
{
_followersDal = followersDal;
_twitterUserDal = twitterUserDal;
}
#endregion
public async Task ProcessAsync(Follower follower)
{
// Perform undo following to user instance
// TODO: Insert ActivityPub magic here
// 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);
}
}
}

View File

@ -0,0 +1,55 @@
using System.Linq;
using System.Threading.Tasks;
using BirdsiteLive.DAL.Contracts;
using BirdsiteLive.DAL.Models;
namespace BirdsiteLive.Moderation.Actions
{
public interface IRemoveTwitterAccountAction
{
Task ProcessAsync(SyncTwitterUser twitterUser);
}
public class RemoveTwitterAccountAction : IRemoveTwitterAccountAction
{
private readonly IFollowersDal _followersDal;
private readonly ITwitterUserDal _twitterUserDal;
#region Ctor
public RemoveTwitterAccountAction(IFollowersDal followersDal, ITwitterUserDal twitterUserDal)
{
_followersDal = followersDal;
_twitterUserDal = twitterUserDal;
}
#endregion
public async Task ProcessAsync(SyncTwitterUser twitterUser)
{
// Check Followers
var twitterUserId = twitterUser.Id;
var followers = await _followersDal.GetFollowersAsync(twitterUserId);
// Remove all Followers
foreach (var follower in followers)
{
// Perform undo following to user instance
// TODO: Insert ActivityPub magic here
// Remove following from DB
if (follower.Followings.Contains(twitterUserId))
follower.Followings.Remove(twitterUserId);
if (follower.FollowingsSyncStatus.ContainsKey(twitterUserId))
follower.FollowingsSyncStatus.Remove(twitterUserId);
if (follower.Followings.Any())
await _followersDal.UpdateFollowerAsync(follower);
else
await _followersDal.DeleteFollowerAsync(follower.Id);
}
// Remove twitter user
await _twitterUserDal.DeleteTwitterUserAsync(twitterUser.Acct);
}
}
}

View File

@ -0,0 +1,16 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\BirdsiteLive.Domain\BirdsiteLive.Domain.csproj" />
</ItemGroup>
<ItemGroup>
<Folder Include="Actions\" />
<Folder Include="Processors\" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,61 @@
using System;
using System.Threading.Tasks;
using BirdsiteLive.Domain.Repository;
using BirdsiteLive.Moderation.Processors;
using Microsoft.Extensions.Logging;
namespace BirdsiteLive.Moderation
{
public interface IModerationPipeline
{
Task ApplyModerationSettingsAsync();
}
public class ModerationPipeline : IModerationPipeline
{
private readonly IModerationRepository _moderationRepository;
private readonly IFollowerModerationProcessor _followerModerationProcessor;
private readonly ITwitterAccountModerationProcessor _twitterAccountModerationProcessor;
private readonly ILogger<ModerationPipeline> _logger;
#region Ctor
public ModerationPipeline(IModerationRepository moderationRepository, IFollowerModerationProcessor followerModerationProcessor, ITwitterAccountModerationProcessor twitterAccountModerationProcessor, ILogger<ModerationPipeline> logger)
{
_moderationRepository = moderationRepository;
_followerModerationProcessor = followerModerationProcessor;
_twitterAccountModerationProcessor = twitterAccountModerationProcessor;
_logger = logger;
}
#endregion
public async Task ApplyModerationSettingsAsync()
{
try
{
await CheckFollowerModerationPolicyAsync();
await CheckTwitterAccountModerationPolicyAsync();
}
catch (Exception e)
{
_logger.LogCritical(e, "ModerationPipeline execution failed.");
}
}
private async Task CheckFollowerModerationPolicyAsync()
{
var followerPolicy = _moderationRepository.GetModerationType(ModerationEntityTypeEnum.Follower);
if (followerPolicy == ModerationTypeEnum.None) return;
await _followerModerationProcessor.ProcessAsync(followerPolicy);
}
private async Task CheckTwitterAccountModerationPolicyAsync()
{
var twitterAccountPolicy = _moderationRepository.GetModerationType(ModerationEntityTypeEnum.TwitterAccount);
if (twitterAccountPolicy == ModerationTypeEnum.None) return;
await _twitterAccountModerationProcessor.ProcessAsync(twitterAccountPolicy);
}
}
}

View File

@ -0,0 +1,44 @@
using System.Threading.Tasks;
using BirdsiteLive.DAL.Contracts;
using BirdsiteLive.DAL.Models;
using BirdsiteLive.Domain.Repository;
using BirdsiteLive.Moderation.Actions;
namespace BirdsiteLive.Moderation.Processors
{
public interface IFollowerModerationProcessor
{
Task ProcessAsync(ModerationTypeEnum type);
}
public class FollowerModerationProcessor : IFollowerModerationProcessor
{
private readonly IFollowersDal _followersDal;
private readonly IModerationRepository _moderationRepository;
private readonly IRemoveFollowerAction _removeFollowerAction;
#region Ctor
public FollowerModerationProcessor(IFollowersDal followersDal, IModerationRepository moderationRepository, IRemoveFollowerAction removeFollowerAction)
{
_followersDal = followersDal;
_moderationRepository = moderationRepository;
_removeFollowerAction = removeFollowerAction;
}
#endregion
public async Task ProcessAsync(ModerationTypeEnum type)
{
var followers = await _followersDal.GetAllFollowersAsync();
foreach (var follower in followers)
{
var followerHandle = $"@{follower.Acct.Trim()}@{follower.Host.Trim()}".ToLowerInvariant();
var status = _moderationRepository.CheckStatus(ModerationEntityTypeEnum.Follower, followerHandle);
if (type == ModerationTypeEnum.WhiteListing && status != ModeratedTypeEnum.WhiteListed ||
type == ModerationTypeEnum.BlackListing && status == ModeratedTypeEnum.BlackListed)
await _removeFollowerAction.ProcessAsync(follower);
}
}
}
}

View File

@ -0,0 +1,43 @@
using System.Threading.Tasks;
using BirdsiteLive.DAL.Contracts;
using BirdsiteLive.Domain.Repository;
using BirdsiteLive.Moderation.Actions;
namespace BirdsiteLive.Moderation.Processors
{
public interface ITwitterAccountModerationProcessor
{
Task ProcessAsync(ModerationTypeEnum type);
}
public class TwitterAccountModerationProcessor : ITwitterAccountModerationProcessor
{
private readonly ITwitterUserDal _twitterUserDal;
private readonly IModerationRepository _moderationRepository;
private readonly IRemoveTwitterAccountAction _removeTwitterAccountAction;
#region Ctor
public TwitterAccountModerationProcessor(ITwitterUserDal twitterUserDal, IModerationRepository moderationRepository, IRemoveTwitterAccountAction removeTwitterAccountAction)
{
_twitterUserDal = twitterUserDal;
_moderationRepository = moderationRepository;
_removeTwitterAccountAction = removeTwitterAccountAction;
}
#endregion
public async Task ProcessAsync(ModerationTypeEnum type)
{
var twitterUsers = await _twitterUserDal.GetAllTwitterUsersAsync();
foreach (var user in twitterUsers)
{
var userHandle = user.Acct.ToLowerInvariant().Trim();
var status = _moderationRepository.CheckStatus(ModerationEntityTypeEnum.TwitterAccount, userHandle);
if (type == ModerationTypeEnum.WhiteListing && status != ModeratedTypeEnum.WhiteListed ||
type == ModerationTypeEnum.BlackListing && status == ModeratedTypeEnum.BlackListed)
await _removeTwitterAccountAction.ProcessAsync(user);
}
}
}
}

View File

@ -39,7 +39,9 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BirdsiteLive.Domain.Tests",
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BirdsiteLive.Pipeline.Tests", "Tests\BirdsiteLive.Pipeline.Tests\BirdsiteLive.Pipeline.Tests.csproj", "{BF51CA81-5A7A-46F8-B4FB-861C6BE59298}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BirdsiteLive.DAL.Tests", "Tests\BirdsiteLive.DAL.Tests\BirdsiteLive.DAL.Tests.csproj", "{5A1E3EB5-6CBB-470D-8A0D-10F8C18353D5}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BirdsiteLive.DAL.Tests", "Tests\BirdsiteLive.DAL.Tests\BirdsiteLive.DAL.Tests.csproj", "{5A1E3EB5-6CBB-470D-8A0D-10F8C18353D5}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BirdsiteLive.Moderation", "BirdsiteLive.Moderation\BirdsiteLive.Moderation.csproj", "{4BE541AC-8A93-4FA3-98AC-956CC2D5B748}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
@ -107,6 +109,10 @@ Global
{5A1E3EB5-6CBB-470D-8A0D-10F8C18353D5}.Debug|Any CPU.Build.0 = Debug|Any CPU
{5A1E3EB5-6CBB-470D-8A0D-10F8C18353D5}.Release|Any CPU.ActiveCfg = Release|Any CPU
{5A1E3EB5-6CBB-470D-8A0D-10F8C18353D5}.Release|Any CPU.Build.0 = Release|Any CPU
{4BE541AC-8A93-4FA3-98AC-956CC2D5B748}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{4BE541AC-8A93-4FA3-98AC-956CC2D5B748}.Debug|Any CPU.Build.0 = Debug|Any CPU
{4BE541AC-8A93-4FA3-98AC-956CC2D5B748}.Release|Any CPU.ActiveCfg = Release|Any CPU
{4BE541AC-8A93-4FA3-98AC-956CC2D5B748}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@ -126,6 +132,7 @@ Global
{F544D745-89A8-4DEA-B61C-A7E6C53C1D63} = {A32D3458-09D0-4E0A-BA4B-8C411B816B94}
{BF51CA81-5A7A-46F8-B4FB-861C6BE59298} = {A32D3458-09D0-4E0A-BA4B-8C411B816B94}
{5A1E3EB5-6CBB-470D-8A0D-10F8C18353D5} = {A32D3458-09D0-4E0A-BA4B-8C411B816B94}
{4BE541AC-8A93-4FA3-98AC-956CC2D5B748} = {DA3C160C-4811-4E26-A5AD-42B81FAF2D7C}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {69E8DCAD-4C37-4010-858F-5F94E6FBABCE}

View File

@ -84,6 +84,11 @@ namespace BirdsiteLive.DAL.Postgres.DataAccessLayers
}
}
public async Task<Follower[]> GetAllFollowersAsync()
{
throw new NotImplementedException();
}
public async Task UpdateFollowerAsync(Follower follower)
{
if (follower == default) throw new ArgumentException("follower");

View File

@ -49,6 +49,11 @@ namespace BirdsiteLive.DAL.Postgres.DataAccessLayers
}
}
public async Task DeleteTwitterUserAsync(int id)
{
throw new NotImplementedException();
}
public async Task<int> GetTwitterUsersCountAsync()
{
var query = $"SELECT COUNT(*) FROM {_settings.TwitterUserTableName}";
@ -75,6 +80,11 @@ namespace BirdsiteLive.DAL.Postgres.DataAccessLayers
}
}
public async Task<SyncTwitterUser[]> GetAllTwitterUsersAsync()
{
throw new NotImplementedException();
}
public async Task UpdateTwitterUserAsync(int id, long lastTweetPostedId, long lastTweetSynchronizedForAllFollowersId, DateTime lastSync)
{
if(id == default) throw new ArgumentException("id");

View File

@ -10,6 +10,7 @@ namespace BirdsiteLive.DAL.Contracts
Task CreateFollowerAsync(string acct, string host, string inboxRoute, string sharedInboxRoute, int[] followings = null,
Dictionary<int, long> followingSyncStatus = null);
Task<Follower[]> GetFollowersAsync(int followedUserId);
Task<Follower[]> GetAllFollowersAsync();
Task UpdateFollowerAsync(Follower follower);
Task DeleteFollowerAsync(int id);
Task DeleteFollowerAsync(string acct, string host);

View File

@ -9,8 +9,10 @@ namespace BirdsiteLive.DAL.Contracts
Task CreateTwitterUserAsync(string acct, long lastTweetPostedId);
Task<SyncTwitterUser> GetTwitterUserAsync(string acct);
Task<SyncTwitterUser[]> GetAllTwitterUsersAsync(int maxNumber);
Task<SyncTwitterUser[]> GetAllTwitterUsersAsync();
Task UpdateTwitterUserAsync(int id, long lastTweetPostedId, long lastTweetSynchronizedForAllFollowersId, DateTime lastSync);
Task DeleteTwitterUserAsync(string acct);
Task DeleteTwitterUserAsync(int id);
Task<int> GetTwitterUsersCountAsync();
}
}