first iteration of logic to apply moderation policy
This commit is contained in:
parent
392c7ca494
commit
9920316863
11 changed files with 289 additions and 1 deletions
44
src/BirdsiteLive.Moderation/Actions/RemoveFollowerAction.cs
Normal file
44
src/BirdsiteLive.Moderation/Actions/RemoveFollowerAction.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
16
src/BirdsiteLive.Moderation/BirdsiteLive.Moderation.csproj
Normal file
16
src/BirdsiteLive.Moderation/BirdsiteLive.Moderation.csproj
Normal 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>
|
61
src/BirdsiteLive.Moderation/ModerationPipeline.cs
Normal file
61
src/BirdsiteLive.Moderation/ModerationPipeline.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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}
|
||||
|
|
|
@ -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");
|
||||
|
|
|
@ -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");
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
Reference in a new issue