Merge pull request #93 from NicolasConstant/topic_backwhitelisting
Backwhitelisting PR
This commit is contained in:
commit
dbb485be5d
60 changed files with 3131 additions and 109 deletions
2
.github/workflows/dotnet-core.yml
vendored
2
.github/workflows/dotnet-core.yml
vendored
|
@ -24,5 +24,5 @@ jobs:
|
|||
run: dotnet build --configuration Release --no-restore
|
||||
working-directory: ${{env.working-directory}}
|
||||
- name: Test
|
||||
run: dotnet test --no-restore --verbosity quiet
|
||||
run: dotnet test --no-restore --verbosity minimal
|
||||
working-directory: ${{env.working-directory}}
|
||||
|
|
77
VARIABLES.md
77
VARIABLES.md
|
@ -2,6 +2,40 @@
|
|||
|
||||
You can configure some of BirdsiteLIVE's settings via environment variables (those are optionnals):
|
||||
|
||||
## Blacklisting & Whitelisting
|
||||
|
||||
### Fediverse users and instances
|
||||
|
||||
Here are the supported patterns to describe Fediverse users and/or instances:
|
||||
|
||||
* `@user@instance.ext` to describe a Fediverse user
|
||||
* `instance.ext` to describe an instance under a domain name
|
||||
* `*.instance.ext` to describe instances from all subdomains of a domain name (this doesn't include the instance.ext, if you want both you need to add both)
|
||||
|
||||
You can whitelist or blacklist fediverses users by settings the followings variables with the above patterns separated by `;`:
|
||||
|
||||
* `Moderation:FollowersWhiteListing` Fediverse Whitelisting
|
||||
* `Moderation:FollowersBlackListing` Fediverse Blacklisting
|
||||
|
||||
If the whitelisting is set, only given patterns can follow twitter accounts on the instance.
|
||||
If blacklisted, the given patterns can't follow twitter accounts on the instance.
|
||||
If both whitelisting and blacklisting are set, only the whitelisting will be active.
|
||||
|
||||
### Twitter users
|
||||
|
||||
Here is the supported pattern to describe Twitter users:
|
||||
|
||||
* `twitter_handle` to describe a Twitter user
|
||||
|
||||
You can whitelist or blacklist twitter users by settings the followings variables with the above pattern separated by `;`:
|
||||
|
||||
* `Moderation:TwitterAccountsWhiteListing` Twitter Whitelisting
|
||||
* `Moderation:TwitterAccountsBlackListing` Twitter Blacklisting
|
||||
|
||||
If the whitelisting is set, only given patterns can be followed on the instance.
|
||||
If blacklisted, the given patterns can't be followed on the instance.
|
||||
If both whitelisting and blacklisting are set, only the whitelisting will be active.
|
||||
|
||||
## Logging
|
||||
|
||||
* `Logging:Type` (default: none) set the type of the logging and monitoring system, currently the only type supported is `insights` for *Azure Application Insights* (PR welcome to support other types)
|
||||
|
@ -11,4 +45,45 @@ You can configure some of BirdsiteLIVE's settings via environment variables (tho
|
|||
|
||||
* `Instance:Name` (default: BirdsiteLIVE) the name of the instance
|
||||
* `Instance:ResolveMentionsInProfiles` (default: true) to enable or disable mentions parsing in profile's description. Resolving it will consume more User's API calls since newly discovered account can also contain references to others accounts as well. On a big instance it is recommended to disable it.
|
||||
* `Instance:PublishReplies` (default: false) to enable or disable replies publishing.
|
||||
* `Instance:PublishReplies` (default: false) to enable or disable replies publishing.
|
||||
|
||||
# Docker Compose full example
|
||||
|
||||
In order to illustrate above variables, here is an example of an updated `docker-compose.yml` file:
|
||||
|
||||
```diff
|
||||
version: "3"
|
||||
|
||||
networks:
|
||||
[...]
|
||||
|
||||
services:
|
||||
server:
|
||||
image: nicolasconstant/birdsitelive:latest
|
||||
[...]
|
||||
environment:
|
||||
- Instance:Domain=domain.name
|
||||
- Instance:AdminEmail=name@domain.ext
|
||||
- Db:Type=postgres
|
||||
- Db:Host=db
|
||||
- Db:Name=birdsitelive
|
||||
- Db:User=birdsitelive
|
||||
- Db:Password=birdsitelive
|
||||
- Twitter:ConsumerKey=twitter.api.key
|
||||
- Twitter:ConsumerSecret=twitter.api.key
|
||||
+ - Moderation:FollowersWhiteListing=@me@my-instance.ca;friend-instance.com;*.friend-instance.com
|
||||
+ - Moderation:TwitterAccountsBlackListing=douchebag;jerk_88;theRealIdiot
|
||||
+ - Instance:Name=MyTwitterRelay
|
||||
+ - Instance:ResolveMentionsInProfiles=false
|
||||
+ - Instance:PublishReplies=true
|
||||
networks:
|
||||
[...]
|
||||
|
||||
db:
|
||||
image: postgres:9.6
|
||||
[...]
|
||||
```
|
||||
|
||||
## Apply the modifications
|
||||
|
||||
After the modification of the `docker-compose.yml` file, you will need to run `docker-compose up -d` to apply the changes.
|
||||
|
|
|
@ -41,7 +41,6 @@ namespace BirdsiteLive.ActivityPub
|
|||
}
|
||||
};
|
||||
return acceptFollow;
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
|
10
src/BirdsiteLive.Common/Settings/ModerationSettings.cs
Normal file
10
src/BirdsiteLive.Common/Settings/ModerationSettings.cs
Normal file
|
@ -0,0 +1,10 @@
|
|||
namespace BirdsiteLive.Common.Settings
|
||||
{
|
||||
public class ModerationSettings
|
||||
{
|
||||
public string FollowersWhiteListing { get; set; }
|
||||
public string FollowersBlackListing { get; set; }
|
||||
public string TwitterAccountsWhiteListing { get; set; }
|
||||
public string TwitterAccountsBlackListing { get; set; }
|
||||
}
|
||||
}
|
|
@ -5,7 +5,7 @@ namespace BirdsiteLive.Domain.BusinessUseCases
|
|||
{
|
||||
public interface IProcessFollowUser
|
||||
{
|
||||
Task ExecuteAsync(string followerUsername, string followerDomain, string twitterUsername, string followerInbox, string sharedInbox);
|
||||
Task ExecuteAsync(string followerUsername, string followerDomain, string twitterUsername, string followerInbox, string sharedInbox, string followerActorId);
|
||||
}
|
||||
|
||||
public class ProcessFollowUser : IProcessFollowUser
|
||||
|
@ -21,13 +21,13 @@ namespace BirdsiteLive.Domain.BusinessUseCases
|
|||
}
|
||||
#endregion
|
||||
|
||||
public async Task ExecuteAsync(string followerUsername, string followerDomain, string twitterUsername, string followerInbox, string sharedInbox)
|
||||
public async Task ExecuteAsync(string followerUsername, string followerDomain, string twitterUsername, string followerInbox, string sharedInbox, string followerActorId)
|
||||
{
|
||||
// Get Follower and Twitter Users
|
||||
var follower = await _followerDal.GetFollowerAsync(followerUsername, followerDomain);
|
||||
if (follower == null)
|
||||
{
|
||||
await _followerDal.CreateFollowerAsync(followerUsername, followerDomain, followerInbox, sharedInbox);
|
||||
await _followerDal.CreateFollowerAsync(followerUsername, followerDomain, followerInbox, sharedInbox, followerActorId);
|
||||
follower = await _followerDal.GetFollowerAsync(followerUsername, followerDomain);
|
||||
}
|
||||
|
||||
|
|
148
src/BirdsiteLive.Domain/Repository/ModerationRepository.cs
Normal file
148
src/BirdsiteLive.Domain/Repository/ModerationRepository.cs
Normal file
|
@ -0,0 +1,148 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
using BirdsiteLive.Common.Settings;
|
||||
using BirdsiteLive.Domain.Tools;
|
||||
|
||||
namespace BirdsiteLive.Domain.Repository
|
||||
{
|
||||
public interface IModerationRepository
|
||||
{
|
||||
ModerationTypeEnum GetModerationType(ModerationEntityTypeEnum type);
|
||||
ModeratedTypeEnum CheckStatus(ModerationEntityTypeEnum type, string entity);
|
||||
}
|
||||
|
||||
public class ModerationRepository : IModerationRepository
|
||||
{
|
||||
private readonly Regex[] _followersWhiteListing;
|
||||
private readonly Regex[] _followersBlackListing;
|
||||
private readonly Regex[] _twitterAccountsWhiteListing;
|
||||
private readonly Regex[] _twitterAccountsBlackListing;
|
||||
|
||||
private readonly Dictionary<ModerationEntityTypeEnum, ModerationTypeEnum> _modMode =
|
||||
new Dictionary<ModerationEntityTypeEnum, ModerationTypeEnum>();
|
||||
|
||||
#region Ctor
|
||||
public ModerationRepository(ModerationSettings settings)
|
||||
{
|
||||
var parsedFollowersWhiteListing = ModerationParser.Parse(settings.FollowersWhiteListing);
|
||||
var parsedFollowersBlackListing = ModerationParser.Parse(settings.FollowersBlackListing);
|
||||
var parsedTwitterAccountsWhiteListing = ModerationParser.Parse(settings.TwitterAccountsWhiteListing);
|
||||
var parsedTwitterAccountsBlackListing = ModerationParser.Parse(settings.TwitterAccountsBlackListing);
|
||||
|
||||
_followersWhiteListing = parsedFollowersWhiteListing
|
||||
.Select(x => ModerationRegexParser.Parse(ModerationEntityTypeEnum.Follower, x))
|
||||
.ToArray();
|
||||
_followersBlackListing = parsedFollowersBlackListing
|
||||
.Select(x => ModerationRegexParser.Parse(ModerationEntityTypeEnum.Follower, x))
|
||||
.ToArray();
|
||||
_twitterAccountsWhiteListing = parsedTwitterAccountsWhiteListing
|
||||
.Select(x => ModerationRegexParser.Parse(ModerationEntityTypeEnum.TwitterAccount, x))
|
||||
.ToArray();
|
||||
_twitterAccountsBlackListing = parsedTwitterAccountsBlackListing
|
||||
.Select(x => ModerationRegexParser.Parse(ModerationEntityTypeEnum.TwitterAccount, x))
|
||||
.ToArray();
|
||||
|
||||
// Set Follower moderation politic
|
||||
if (_followersWhiteListing.Any())
|
||||
_modMode.Add(ModerationEntityTypeEnum.Follower, ModerationTypeEnum.WhiteListing);
|
||||
else if (_followersBlackListing.Any())
|
||||
_modMode.Add(ModerationEntityTypeEnum.Follower, ModerationTypeEnum.BlackListing);
|
||||
else
|
||||
_modMode.Add(ModerationEntityTypeEnum.Follower, ModerationTypeEnum.None);
|
||||
|
||||
// Set Twitter account moderation politic
|
||||
if (_twitterAccountsWhiteListing.Any())
|
||||
_modMode.Add(ModerationEntityTypeEnum.TwitterAccount, ModerationTypeEnum.WhiteListing);
|
||||
else if (_twitterAccountsBlackListing.Any())
|
||||
_modMode.Add(ModerationEntityTypeEnum.TwitterAccount, ModerationTypeEnum.BlackListing);
|
||||
else
|
||||
_modMode.Add(ModerationEntityTypeEnum.TwitterAccount, ModerationTypeEnum.None);
|
||||
}
|
||||
#endregion
|
||||
|
||||
public ModerationTypeEnum GetModerationType(ModerationEntityTypeEnum type)
|
||||
{
|
||||
return _modMode[type];
|
||||
}
|
||||
|
||||
public ModeratedTypeEnum CheckStatus(ModerationEntityTypeEnum type, string entity)
|
||||
{
|
||||
if (_modMode[type] == ModerationTypeEnum.None) return ModeratedTypeEnum.None;
|
||||
|
||||
switch (type)
|
||||
{
|
||||
case ModerationEntityTypeEnum.Follower:
|
||||
return ProcessFollower(entity);
|
||||
case ModerationEntityTypeEnum.TwitterAccount:
|
||||
return ProcessTwitterAccount(entity);
|
||||
}
|
||||
|
||||
throw new NotImplementedException($"Type {type} is not supported");
|
||||
}
|
||||
|
||||
private ModeratedTypeEnum ProcessFollower(string entity)
|
||||
{
|
||||
var politic = _modMode[ModerationEntityTypeEnum.Follower];
|
||||
|
||||
switch (politic)
|
||||
{
|
||||
case ModerationTypeEnum.None:
|
||||
return ModeratedTypeEnum.None;
|
||||
case ModerationTypeEnum.BlackListing:
|
||||
if (_followersBlackListing.Any(x => x.IsMatch(entity)))
|
||||
return ModeratedTypeEnum.BlackListed;
|
||||
return ModeratedTypeEnum.None;
|
||||
case ModerationTypeEnum.WhiteListing:
|
||||
if (_followersWhiteListing.Any(x => x.IsMatch(entity)))
|
||||
return ModeratedTypeEnum.WhiteListed;
|
||||
return ModeratedTypeEnum.None;
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException();
|
||||
}
|
||||
}
|
||||
|
||||
private ModeratedTypeEnum ProcessTwitterAccount(string entity)
|
||||
{
|
||||
var politic = _modMode[ModerationEntityTypeEnum.TwitterAccount];
|
||||
|
||||
switch (politic)
|
||||
{
|
||||
case ModerationTypeEnum.None:
|
||||
return ModeratedTypeEnum.None;
|
||||
case ModerationTypeEnum.BlackListing:
|
||||
if (_twitterAccountsBlackListing.Any(x => x.IsMatch(entity)))
|
||||
return ModeratedTypeEnum.BlackListed;
|
||||
return ModeratedTypeEnum.None;
|
||||
case ModerationTypeEnum.WhiteListing:
|
||||
if (_twitterAccountsWhiteListing.Any(x => x.IsMatch(entity)))
|
||||
return ModeratedTypeEnum.WhiteListed;
|
||||
return ModeratedTypeEnum.None;
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public enum ModerationEntityTypeEnum
|
||||
{
|
||||
Unknown = 0,
|
||||
Follower = 1,
|
||||
TwitterAccount = 2
|
||||
}
|
||||
|
||||
public enum ModerationTypeEnum
|
||||
{
|
||||
None = 0,
|
||||
BlackListing = 1,
|
||||
WhiteListing = 2
|
||||
}
|
||||
|
||||
public enum ModeratedTypeEnum
|
||||
{
|
||||
None = 0,
|
||||
BlackListed = 1,
|
||||
WhiteListed = 2
|
||||
}
|
||||
}
|
|
@ -41,7 +41,6 @@ namespace BirdsiteLive.Domain
|
|||
var noteUrl = UrlFactory.GetNoteUrl(_instanceSettings.Domain, username, tweet.Id.ToString());
|
||||
|
||||
var to = $"{actorUrl}/followers";
|
||||
var apPublic = "https://www.w3.org/ns/activitystreams#Public";
|
||||
|
||||
var extractedTags = _statusExtractor.Extract(tweet.MessageContent);
|
||||
_statisticsHandler.ExtractedStatus(extractedTags.tags.Count(x => x.type == "Mention"));
|
||||
|
@ -70,11 +69,9 @@ namespace BirdsiteLive.Domain
|
|||
attributedTo = actorUrl,
|
||||
|
||||
inReplyTo = inReplyTo,
|
||||
//to = new [] {to},
|
||||
//cc = new [] { apPublic },
|
||||
|
||||
to = new[] { to },
|
||||
//cc = new[] { apPublic },
|
||||
//cc = new[] { "https://www.w3.org/ns/activitystreams#Public" },
|
||||
cc = new string[0],
|
||||
|
||||
sensitive = false,
|
||||
|
|
23
src/BirdsiteLive.Domain/Tools/ModerationParser.cs
Normal file
23
src/BirdsiteLive.Domain/Tools/ModerationParser.cs
Normal file
|
@ -0,0 +1,23 @@
|
|||
using System;
|
||||
using System.Linq;
|
||||
|
||||
namespace BirdsiteLive.Domain.Tools
|
||||
{
|
||||
public class ModerationParser
|
||||
{
|
||||
public static string[] Parse(string entry)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(entry)) return new string[0];
|
||||
|
||||
var separationChar = '|';
|
||||
if (entry.Contains(";")) separationChar = ';';
|
||||
else if (entry.Contains(",")) separationChar = ',';
|
||||
|
||||
var splitEntries = entry
|
||||
.Split(new[] {separationChar}, StringSplitOptions.RemoveEmptyEntries)
|
||||
.Where(x => !string.IsNullOrWhiteSpace(x))
|
||||
.Select(x => x.ToLowerInvariant().Trim());
|
||||
return splitEntries.ToArray();
|
||||
}
|
||||
}
|
||||
}
|
28
src/BirdsiteLive.Domain/Tools/ModerationRegexParser.cs
Normal file
28
src/BirdsiteLive.Domain/Tools/ModerationRegexParser.cs
Normal file
|
@ -0,0 +1,28 @@
|
|||
using System;
|
||||
using System.Text.RegularExpressions;
|
||||
using BirdsiteLive.Domain.Repository;
|
||||
using Org.BouncyCastle.Pkcs;
|
||||
|
||||
namespace BirdsiteLive.Domain.Tools
|
||||
{
|
||||
public class ModerationRegexParser
|
||||
{
|
||||
public static Regex Parse(ModerationEntityTypeEnum type, string data)
|
||||
{
|
||||
data = data.ToLowerInvariant().Trim();
|
||||
|
||||
if (type == ModerationEntityTypeEnum.Follower)
|
||||
{
|
||||
if (data.StartsWith("@"))
|
||||
return new Regex($@"^{data}$");
|
||||
|
||||
if (data.StartsWith("*"))
|
||||
data = data.Replace("*", "(.+)");
|
||||
|
||||
return new Regex($@"^@(.+)@{data}$");
|
||||
}
|
||||
|
||||
return new Regex($@"^{data}$");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -11,6 +11,7 @@ using BirdsiteLive.Common.Regexes;
|
|||
using BirdsiteLive.Common.Settings;
|
||||
using BirdsiteLive.Cryptography;
|
||||
using BirdsiteLive.Domain.BusinessUseCases;
|
||||
using BirdsiteLive.Domain.Repository;
|
||||
using BirdsiteLive.Domain.Statistics;
|
||||
using BirdsiteLive.Domain.Tools;
|
||||
using BirdsiteLive.Twitter;
|
||||
|
@ -25,6 +26,8 @@ namespace BirdsiteLive.Domain
|
|||
Actor GetUser(TwitterUser twitterUser);
|
||||
Task<bool> FollowRequestedAsync(string signature, string method, string path, string queryString, Dictionary<string, string> requestHeaders, ActivityFollow activity, string body);
|
||||
Task<bool> UndoFollowRequestedAsync(string signature, string method, string path, string queryString, Dictionary<string, string> requestHeaders, ActivityUndoFollow activity, string body);
|
||||
|
||||
Task<bool> SendRejectFollowAsync(ActivityFollow activity, string followerHost);
|
||||
}
|
||||
|
||||
public class UserService : IUserService
|
||||
|
@ -40,8 +43,10 @@ namespace BirdsiteLive.Domain
|
|||
|
||||
private readonly ITwitterUserService _twitterUserService;
|
||||
|
||||
private readonly IModerationRepository _moderationRepository;
|
||||
|
||||
#region Ctor
|
||||
public UserService(InstanceSettings instanceSettings, ICryptoService cryptoService, IActivityPubService activityPubService, IProcessFollowUser processFollowUser, IProcessUndoFollowUser processUndoFollowUser, IStatusExtractor statusExtractor, IExtractionStatisticsHandler statisticsHandler, ITwitterUserService twitterUserService)
|
||||
public UserService(InstanceSettings instanceSettings, ICryptoService cryptoService, IActivityPubService activityPubService, IProcessFollowUser processFollowUser, IProcessUndoFollowUser processUndoFollowUser, IStatusExtractor statusExtractor, IExtractionStatisticsHandler statisticsHandler, ITwitterUserService twitterUserService, IModerationRepository moderationRepository)
|
||||
{
|
||||
_instanceSettings = instanceSettings;
|
||||
_cryptoService = cryptoService;
|
||||
|
@ -51,6 +56,7 @@ namespace BirdsiteLive.Domain
|
|||
_statusExtractor = statusExtractor;
|
||||
_statisticsHandler = statisticsHandler;
|
||||
_twitterUserService = twitterUserService;
|
||||
_moderationRepository = moderationRepository;
|
||||
}
|
||||
#endregion
|
||||
|
||||
|
@ -119,62 +125,94 @@ namespace BirdsiteLive.Domain
|
|||
var sigValidation = await ValidateSignature(activity.actor, signature, method, path, queryString, requestHeaders, body);
|
||||
if (!sigValidation.SignatureIsValidated) return false;
|
||||
|
||||
// Save Follow in DB
|
||||
var followerUserName = sigValidation.User.preferredUsername.ToLowerInvariant();
|
||||
// Prepare data
|
||||
var followerUserName = sigValidation.User.preferredUsername.ToLowerInvariant().Trim();
|
||||
var followerHost = sigValidation.User.url.Replace("https://", string.Empty).Split('/').First();
|
||||
var followerInbox = sigValidation.User.inbox;
|
||||
var followerSharedInbox = sigValidation.User?.endpoints?.sharedInbox;
|
||||
var twitterUser = activity.apObject.Split('/').Last().Replace("@", string.Empty);
|
||||
var twitterUser = activity.apObject.Split('/').Last().Replace("@", string.Empty).ToLowerInvariant().Trim();
|
||||
|
||||
// Make sure to only keep routes
|
||||
followerInbox = OnlyKeepRoute(followerInbox, followerHost);
|
||||
followerSharedInbox = OnlyKeepRoute(followerSharedInbox, followerHost);
|
||||
|
||||
// Validate Moderation status
|
||||
var followerModPolicy = _moderationRepository.GetModerationType(ModerationEntityTypeEnum.Follower);
|
||||
if (followerModPolicy != ModerationTypeEnum.None)
|
||||
{
|
||||
var followerStatus = _moderationRepository.CheckStatus(ModerationEntityTypeEnum.Follower, $"@{followerUserName}@{followerHost}");
|
||||
|
||||
if(followerModPolicy == ModerationTypeEnum.WhiteListing && followerStatus != ModeratedTypeEnum.WhiteListed ||
|
||||
followerModPolicy == ModerationTypeEnum.BlackListing && followerStatus == ModeratedTypeEnum.BlackListed)
|
||||
return await SendRejectFollowAsync(activity, followerHost);
|
||||
}
|
||||
|
||||
// Validate TwitterAccount status
|
||||
var twitterAccountModPolicy = _moderationRepository.GetModerationType(ModerationEntityTypeEnum.TwitterAccount);
|
||||
if (twitterAccountModPolicy != ModerationTypeEnum.None)
|
||||
{
|
||||
var twitterUserStatus = _moderationRepository.CheckStatus(ModerationEntityTypeEnum.TwitterAccount, twitterUser);
|
||||
if (twitterAccountModPolicy == ModerationTypeEnum.WhiteListing && twitterUserStatus != ModeratedTypeEnum.WhiteListed ||
|
||||
twitterAccountModPolicy == ModerationTypeEnum.BlackListing && twitterUserStatus == ModeratedTypeEnum.BlackListed)
|
||||
return await SendRejectFollowAsync(activity, followerHost);
|
||||
}
|
||||
|
||||
// Validate User Protected
|
||||
var user = _twitterUserService.GetUser(twitterUser);
|
||||
if (!user.Protected)
|
||||
{
|
||||
// Execute
|
||||
await _processFollowUser.ExecuteAsync(followerUserName, followerHost, twitterUser, followerInbox, followerSharedInbox);
|
||||
await _processFollowUser.ExecuteAsync(followerUserName, followerHost, twitterUser, followerInbox, followerSharedInbox, activity.actor);
|
||||
|
||||
// Send Accept Activity
|
||||
var acceptFollow = new ActivityAcceptFollow()
|
||||
{
|
||||
context = "https://www.w3.org/ns/activitystreams",
|
||||
id = $"{activity.apObject}#accepts/follows/{Guid.NewGuid()}",
|
||||
type = "Accept",
|
||||
actor = activity.apObject,
|
||||
apObject = new ActivityFollow()
|
||||
{
|
||||
id = activity.id,
|
||||
type = activity.type,
|
||||
actor = activity.actor,
|
||||
apObject = activity.apObject
|
||||
}
|
||||
};
|
||||
var result = await _activityPubService.PostDataAsync(acceptFollow, followerHost, activity.apObject);
|
||||
return result == HttpStatusCode.Accepted || result == HttpStatusCode.OK; //TODO: revamp this for better error handling
|
||||
return await SendAcceptFollowAsync(activity, followerHost);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Send Reject Activity
|
||||
var acceptFollow = new ActivityRejectFollow()
|
||||
{
|
||||
context = "https://www.w3.org/ns/activitystreams",
|
||||
id = $"{activity.apObject}#rejects/follows/{Guid.NewGuid()}",
|
||||
type = "Reject",
|
||||
actor = activity.apObject,
|
||||
apObject = new ActivityFollow()
|
||||
{
|
||||
id = activity.id,
|
||||
type = activity.type,
|
||||
actor = activity.actor,
|
||||
apObject = activity.apObject
|
||||
}
|
||||
};
|
||||
var result = await _activityPubService.PostDataAsync(acceptFollow, followerHost, activity.apObject);
|
||||
return result == HttpStatusCode.Accepted || result == HttpStatusCode.OK; //TODO: revamp this for better error handling
|
||||
return await SendRejectFollowAsync(activity, followerHost);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<bool> SendAcceptFollowAsync(ActivityFollow activity, string followerHost)
|
||||
{
|
||||
var acceptFollow = new ActivityAcceptFollow()
|
||||
{
|
||||
context = "https://www.w3.org/ns/activitystreams",
|
||||
id = $"{activity.apObject}#accepts/follows/{Guid.NewGuid()}",
|
||||
type = "Accept",
|
||||
actor = activity.apObject,
|
||||
apObject = new ActivityFollow()
|
||||
{
|
||||
id = activity.id,
|
||||
type = activity.type,
|
||||
actor = activity.actor,
|
||||
apObject = activity.apObject
|
||||
}
|
||||
};
|
||||
var result = await _activityPubService.PostDataAsync(acceptFollow, followerHost, activity.apObject);
|
||||
return result == HttpStatusCode.Accepted ||
|
||||
result == HttpStatusCode.OK; //TODO: revamp this for better error handling
|
||||
}
|
||||
|
||||
public async Task<bool> SendRejectFollowAsync(ActivityFollow activity, string followerHost)
|
||||
{
|
||||
var acceptFollow = new ActivityRejectFollow()
|
||||
{
|
||||
context = "https://www.w3.org/ns/activitystreams",
|
||||
id = $"{activity.apObject}#rejects/follows/{Guid.NewGuid()}",
|
||||
type = "Reject",
|
||||
actor = activity.apObject,
|
||||
apObject = new ActivityFollow()
|
||||
{
|
||||
id = activity.id,
|
||||
type = activity.type,
|
||||
actor = activity.actor,
|
||||
apObject = activity.apObject
|
||||
}
|
||||
};
|
||||
var result = await _activityPubService.PostDataAsync(acceptFollow, followerHost, activity.apObject);
|
||||
return result == HttpStatusCode.Accepted ||
|
||||
result == HttpStatusCode.OK; //TODO: revamp this for better error handling
|
||||
}
|
||||
|
||||
private string OnlyKeepRoute(string inbox, string host)
|
||||
{
|
||||
|
|
|
@ -0,0 +1,52 @@
|
|||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using BirdsiteLive.ActivityPub;
|
||||
using BirdsiteLive.ActivityPub.Converters;
|
||||
using BirdsiteLive.Common.Settings;
|
||||
using BirdsiteLive.DAL.Contracts;
|
||||
using BirdsiteLive.DAL.Models;
|
||||
using BirdsiteLive.Domain;
|
||||
|
||||
namespace BirdsiteLive.Moderation.Actions
|
||||
{
|
||||
public interface IRejectAllFollowingsAction
|
||||
{
|
||||
Task ProcessAsync(Follower follower);
|
||||
}
|
||||
|
||||
public class RejectAllFollowingsAction : IRejectAllFollowingsAction
|
||||
{
|
||||
private readonly ITwitterUserDal _twitterUserDal;
|
||||
private readonly IUserService _userService;
|
||||
private readonly InstanceSettings _instanceSettings;
|
||||
|
||||
#region Ctor
|
||||
public RejectAllFollowingsAction(ITwitterUserDal twitterUserDal, IUserService userService, InstanceSettings instanceSettings)
|
||||
{
|
||||
_twitterUserDal = twitterUserDal;
|
||||
_userService = userService;
|
||||
_instanceSettings = instanceSettings;
|
||||
}
|
||||
#endregion
|
||||
|
||||
public async Task ProcessAsync(Follower follower)
|
||||
{
|
||||
foreach (var following in follower.Followings)
|
||||
{
|
||||
try
|
||||
{
|
||||
var f = await _twitterUserDal.GetTwitterUserAsync(following);
|
||||
var activityFollowing = new ActivityFollow
|
||||
{
|
||||
type = "Follow",
|
||||
actor = follower.ActorId,
|
||||
apObject = UrlFactory.GetActorUrl(_instanceSettings.Domain, f.Acct)
|
||||
};
|
||||
|
||||
await _userService.SendRejectFollowAsync(activityFollowing, follower.Host);
|
||||
}
|
||||
catch (Exception) { }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
44
src/BirdsiteLive.Moderation/Actions/RejectFollowingAction.cs
Normal file
44
src/BirdsiteLive.Moderation/Actions/RejectFollowingAction.cs
Normal file
|
@ -0,0 +1,44 @@
|
|||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using BirdsiteLive.ActivityPub;
|
||||
using BirdsiteLive.ActivityPub.Converters;
|
||||
using BirdsiteLive.Common.Settings;
|
||||
using BirdsiteLive.DAL.Models;
|
||||
using BirdsiteLive.Domain;
|
||||
|
||||
namespace BirdsiteLive.Moderation.Actions
|
||||
{
|
||||
public interface IRejectFollowingAction
|
||||
{
|
||||
Task ProcessAsync(Follower follower, SyncTwitterUser twitterUser);
|
||||
}
|
||||
|
||||
public class RejectFollowingAction : IRejectFollowingAction
|
||||
{
|
||||
private readonly IUserService _userService;
|
||||
private readonly InstanceSettings _instanceSettings;
|
||||
|
||||
#region Ctor
|
||||
public RejectFollowingAction(IUserService userService, InstanceSettings instanceSettings)
|
||||
{
|
||||
_userService = userService;
|
||||
_instanceSettings = instanceSettings;
|
||||
}
|
||||
#endregion
|
||||
|
||||
public async Task ProcessAsync(Follower follower, SyncTwitterUser twitterUser)
|
||||
{
|
||||
try
|
||||
{
|
||||
var activityFollowing = new ActivityFollow
|
||||
{
|
||||
type = "Follow",
|
||||
actor = follower.ActorId,
|
||||
apObject = UrlFactory.GetActorUrl(_instanceSettings.Domain, twitterUser.Acct)
|
||||
};
|
||||
await _userService.SendRejectFollowAsync(activityFollowing, follower.Host);
|
||||
}
|
||||
catch (Exception) { }
|
||||
}
|
||||
}
|
||||
}
|
51
src/BirdsiteLive.Moderation/Actions/RemoveFollowerAction.cs
Normal file
51
src/BirdsiteLive.Moderation/Actions/RemoveFollowerAction.cs
Normal file
|
@ -0,0 +1,51 @@
|
|||
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 BirdsiteLive.DAL.Models;
|
||||
using BirdsiteLive.Domain;
|
||||
|
||||
namespace BirdsiteLive.Moderation.Actions
|
||||
{
|
||||
public interface IRemoveFollowerAction
|
||||
{
|
||||
Task ProcessAsync(Follower follower);
|
||||
}
|
||||
|
||||
public class RemoveFollowerAction : IRemoveFollowerAction
|
||||
{
|
||||
private readonly IFollowersDal _followersDal;
|
||||
private readonly ITwitterUserDal _twitterUserDal;
|
||||
private readonly IRejectAllFollowingsAction _rejectAllFollowingsAction;
|
||||
|
||||
#region Ctor
|
||||
public RemoveFollowerAction(IFollowersDal followersDal, ITwitterUserDal twitterUserDal, IRejectAllFollowingsAction rejectAllFollowingsAction)
|
||||
{
|
||||
_followersDal = followersDal;
|
||||
_twitterUserDal = twitterUserDal;
|
||||
_rejectAllFollowingsAction = rejectAllFollowingsAction;
|
||||
}
|
||||
#endregion
|
||||
|
||||
public async Task ProcessAsync(Follower follower)
|
||||
{
|
||||
// Perform undo following to user instance
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,57 @@
|
|||
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;
|
||||
private readonly IRejectFollowingAction _rejectFollowingAction;
|
||||
|
||||
#region Ctor
|
||||
public RemoveTwitterAccountAction(IFollowersDal followersDal, ITwitterUserDal twitterUserDal, IRejectFollowingAction rejectFollowingAction)
|
||||
{
|
||||
_followersDal = followersDal;
|
||||
_twitterUserDal = twitterUserDal;
|
||||
_rejectFollowingAction = rejectFollowingAction;
|
||||
}
|
||||
#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
|
||||
await _rejectFollowingAction.ProcessAsync(follower, twitterUser);
|
||||
|
||||
// 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(twitterUserId);
|
||||
}
|
||||
}
|
||||
}
|
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,45 @@
|
|||
using System.Threading.Tasks;
|
||||
using BirdsiteLive.DAL.Contracts;
|
||||
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)
|
||||
{
|
||||
if (type == ModerationTypeEnum.None) return;
|
||||
|
||||
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,45 @@
|
|||
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)
|
||||
{
|
||||
if (type == ModerationTypeEnum.None) return;
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -41,6 +41,10 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BirdsiteLive.Pipeline.Tests
|
|||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BirdsiteLive.DAL.Tests", "Tests\BirdsiteLive.DAL.Tests\BirdsiteLive.DAL.Tests.csproj", "{5A1E3EB5-6CBB-470D-8A0D-10F8C18353D5}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BirdsiteLive.Moderation", "BirdsiteLive.Moderation\BirdsiteLive.Moderation.csproj", "{4BE541AC-8A93-4FA3-98AC-956CC2D5B748}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BirdsiteLive.Moderation.Tests", "Tests\BirdsiteLive.Moderation.Tests\BirdsiteLive.Moderation.Tests.csproj", "{0A311BF3-4FD9-4303-940A-A3778890561C}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BirdsiteLive.Common.Tests", "Tests\BirdsiteLive.Common.Tests\BirdsiteLive.Common.Tests.csproj", "{C69F7582-6050-44DC-BAAB-7C8F0BDA525C}"
|
||||
EndProject
|
||||
Global
|
||||
|
@ -109,6 +113,14 @@ 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
|
||||
{0A311BF3-4FD9-4303-940A-A3778890561C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{0A311BF3-4FD9-4303-940A-A3778890561C}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{0A311BF3-4FD9-4303-940A-A3778890561C}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{0A311BF3-4FD9-4303-940A-A3778890561C}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{C69F7582-6050-44DC-BAAB-7C8F0BDA525C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{C69F7582-6050-44DC-BAAB-7C8F0BDA525C}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{C69F7582-6050-44DC-BAAB-7C8F0BDA525C}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
|
@ -132,6 +144,8 @@ 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}
|
||||
{0A311BF3-4FD9-4303-940A-A3778890561C} = {A32D3458-09D0-4E0A-BA4B-8C411B816B94}
|
||||
{C69F7582-6050-44DC-BAAB-7C8F0BDA525C} = {A32D3458-09D0-4E0A-BA4B-8C411B816B94}
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
<TargetFramework>netcoreapp3.1</TargetFramework>
|
||||
<UserSecretsId>d21486de-a812-47eb-a419-05682bb68856</UserSecretsId>
|
||||
<DockerDefaultTargetOS>Linux</DockerDefaultTargetOS>
|
||||
<Version>0.14.5</Version>
|
||||
<Version>0.15.0</Version>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
@ -18,6 +18,7 @@
|
|||
<ProjectReference Include="..\BirdsiteLive.Common\BirdsiteLive.Common.csproj" />
|
||||
<ProjectReference Include="..\BirdsiteLive.Cryptography\BirdsiteLive.Cryptography.csproj" />
|
||||
<ProjectReference Include="..\BirdsiteLive.Domain\BirdsiteLive.Domain.csproj" />
|
||||
<ProjectReference Include="..\BirdsiteLive.Moderation\BirdsiteLive.Moderation.csproj" />
|
||||
<ProjectReference Include="..\BirdsiteLive.Pipeline\BirdsiteLive.Pipeline.csproj" />
|
||||
<ProjectReference Include="..\BirdsiteLive.Twitter\BirdsiteLive.Twitter.csproj" />
|
||||
<ProjectReference Include="..\DataAccessLayers\BirdsiteLive.DAL.Postgres\BirdsiteLive.DAL.Postgres.csproj" />
|
||||
|
|
59
src/BirdsiteLive/Component/NodeInfoViewComponent.cs
Normal file
59
src/BirdsiteLive/Component/NodeInfoViewComponent.cs
Normal file
|
@ -0,0 +1,59 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using BirdsiteLive.DAL.Contracts;
|
||||
using BirdsiteLive.Domain.Repository;
|
||||
using BirdsiteLive.Services;
|
||||
using BirdsiteLive.Statistics.Domain;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Razor.Language.Intermediate;
|
||||
|
||||
namespace BirdsiteLive.Component
|
||||
{
|
||||
public class NodeInfoViewComponent : ViewComponent
|
||||
{
|
||||
private readonly IModerationRepository _moderationRepository;
|
||||
private readonly ICachedStatisticsService _cachedStatisticsService;
|
||||
|
||||
#region Ctor
|
||||
public NodeInfoViewComponent(IModerationRepository moderationRepository, ICachedStatisticsService cachedStatisticsService)
|
||||
{
|
||||
_moderationRepository = moderationRepository;
|
||||
_cachedStatisticsService = cachedStatisticsService;
|
||||
}
|
||||
#endregion
|
||||
|
||||
public async Task<IViewComponentResult> InvokeAsync()
|
||||
{
|
||||
var followerPolicy = _moderationRepository.GetModerationType(ModerationEntityTypeEnum.Follower);
|
||||
var twitterAccountPolicy = _moderationRepository.GetModerationType(ModerationEntityTypeEnum.TwitterAccount);
|
||||
|
||||
var statistics = await _cachedStatisticsService.GetStatisticsAsync();
|
||||
|
||||
var viewModel = new NodeInfoViewModel
|
||||
{
|
||||
BlacklistingEnabled = followerPolicy == ModerationTypeEnum.BlackListing ||
|
||||
twitterAccountPolicy == ModerationTypeEnum.BlackListing,
|
||||
WhitelistingEnabled = followerPolicy == ModerationTypeEnum.WhiteListing ||
|
||||
twitterAccountPolicy == ModerationTypeEnum.WhiteListing,
|
||||
InstanceSaturation = statistics.Saturation
|
||||
};
|
||||
|
||||
//viewModel = new NodeInfoViewModel
|
||||
//{
|
||||
// BlacklistingEnabled = false,
|
||||
// WhitelistingEnabled = false,
|
||||
// InstanceSaturation = 175
|
||||
//};
|
||||
return View(viewModel);
|
||||
}
|
||||
}
|
||||
|
||||
public class NodeInfoViewModel
|
||||
{
|
||||
public bool BlacklistingEnabled { get; set; }
|
||||
public bool WhitelistingEnabled { get; set; }
|
||||
public int InstanceSaturation { get; set; }
|
||||
}
|
||||
}
|
58
src/BirdsiteLive/Controllers/AboutController.cs
Normal file
58
src/BirdsiteLive/Controllers/AboutController.cs
Normal file
|
@ -0,0 +1,58 @@
|
|||
using Microsoft.AspNetCore.Mvc;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using BirdsiteLive.Domain.Repository;
|
||||
using BirdsiteLive.Services;
|
||||
|
||||
namespace BirdsiteLive.Controllers
|
||||
{
|
||||
public class AboutController : Controller
|
||||
{
|
||||
private readonly IModerationRepository _moderationRepository;
|
||||
private readonly ICachedStatisticsService _cachedStatisticsService;
|
||||
|
||||
#region Ctor
|
||||
public AboutController(IModerationRepository moderationRepository, ICachedStatisticsService cachedStatisticsService)
|
||||
{
|
||||
_moderationRepository = moderationRepository;
|
||||
_cachedStatisticsService = cachedStatisticsService;
|
||||
}
|
||||
#endregion
|
||||
|
||||
public async Task<IActionResult> Index()
|
||||
{
|
||||
var stats = await _cachedStatisticsService.GetStatisticsAsync();
|
||||
return View(stats);
|
||||
}
|
||||
|
||||
public IActionResult Blacklisting()
|
||||
{
|
||||
var status = GetModerationStatus();
|
||||
return View("Blacklisting", status);
|
||||
}
|
||||
|
||||
public IActionResult Whitelisting()
|
||||
{
|
||||
var status = GetModerationStatus();
|
||||
return View("Whitelisting", status);
|
||||
}
|
||||
|
||||
private ModerationStatus GetModerationStatus()
|
||||
{
|
||||
var status = new ModerationStatus
|
||||
{
|
||||
Followers = _moderationRepository.GetModerationType(ModerationEntityTypeEnum.Follower),
|
||||
TwitterAccounts = _moderationRepository.GetModerationType(ModerationEntityTypeEnum.TwitterAccount)
|
||||
};
|
||||
return status;
|
||||
}
|
||||
}
|
||||
|
||||
public class ModerationStatus
|
||||
{
|
||||
public ModerationTypeEnum Followers { get; set; }
|
||||
public ModerationTypeEnum TwitterAccounts { get; set; }
|
||||
}
|
||||
}
|
|
@ -19,13 +19,15 @@ namespace BirdsiteLive.Controllers
|
|||
private readonly InstanceSettings _instanceSettings;
|
||||
private readonly ICryptoService _cryptoService;
|
||||
private readonly IActivityPubService _activityPubService;
|
||||
private readonly IUserService _userService;
|
||||
|
||||
#region Ctor
|
||||
public DebugingController(InstanceSettings instanceSettings, ICryptoService cryptoService, IActivityPubService activityPubService)
|
||||
public DebugingController(InstanceSettings instanceSettings, ICryptoService cryptoService, IActivityPubService activityPubService, IUserService userService)
|
||||
{
|
||||
_instanceSettings = instanceSettings;
|
||||
_cryptoService = cryptoService;
|
||||
_activityPubService = activityPubService;
|
||||
_userService = userService;
|
||||
}
|
||||
#endregion
|
||||
|
||||
|
@ -67,7 +69,6 @@ namespace BirdsiteLive.Controllers
|
|||
var noteUrl = $"https://{_instanceSettings.Domain}/@{username}/{noteGuid}";
|
||||
|
||||
var to = $"{actor}/followers";
|
||||
var apPublic = "https://www.w3.org/ns/activitystreams#Public";
|
||||
|
||||
var now = DateTime.UtcNow;
|
||||
var nowString = now.ToString("s") + "Z";
|
||||
|
@ -80,7 +81,7 @@ namespace BirdsiteLive.Controllers
|
|||
actor = actor,
|
||||
published = nowString,
|
||||
to = new []{ to },
|
||||
//cc = new [] { apPublic },
|
||||
//cc = new [] { "https://www.w3.org/ns/activitystreams#Public" },
|
||||
apObject = new Note()
|
||||
{
|
||||
id = noteId,
|
||||
|
@ -90,7 +91,7 @@ namespace BirdsiteLive.Controllers
|
|||
url = noteUrl,
|
||||
attributedTo = actor,
|
||||
to = new[] { to },
|
||||
//cc = new [] { apPublic },
|
||||
//cc = new [] { "https://www.w3.org/ns/activitystreams#Public" },
|
||||
sensitive = false,
|
||||
content = "<p>Woooot</p>",
|
||||
attachment = new Attachment[0],
|
||||
|
@ -102,6 +103,20 @@ namespace BirdsiteLive.Controllers
|
|||
|
||||
return View("Index");
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
public async Task<IActionResult> PostRejectFollow()
|
||||
{
|
||||
var activityFollow = new ActivityFollow
|
||||
{
|
||||
type = "Follow",
|
||||
actor = "https://mastodon.technology/users/testtest",
|
||||
apObject = $"https://{_instanceSettings.Domain}/users/afp"
|
||||
};
|
||||
|
||||
await _userService.SendRejectFollowAsync(activityFollow, "mastodon.technology");
|
||||
return View("Index");
|
||||
}
|
||||
}
|
||||
|
||||
public static class HtmlHelperExtensions
|
||||
|
|
|
@ -159,13 +159,11 @@ namespace BirdsiteLive.Controllers
|
|||
return Accepted();
|
||||
}
|
||||
}
|
||||
|
||||
return Accepted();
|
||||
}
|
||||
|
||||
[Route("/users/{id}/followers")]
|
||||
[HttpGet]
|
||||
public async Task<IActionResult> Followers(string id)
|
||||
public IActionResult Followers(string id)
|
||||
{
|
||||
var r = Request.Headers["Accept"].First();
|
||||
if (!r.Contains("application/activity+json")) return NotFound();
|
||||
|
|
|
@ -7,6 +7,7 @@ using BirdsiteLive.ActivityPub.Converters;
|
|||
using BirdsiteLive.Common.Regexes;
|
||||
using BirdsiteLive.Common.Settings;
|
||||
using BirdsiteLive.DAL.Contracts;
|
||||
using BirdsiteLive.Domain.Repository;
|
||||
using BirdsiteLive.Models;
|
||||
using BirdsiteLive.Models.WellKnownModels;
|
||||
using BirdsiteLive.Twitter;
|
||||
|
@ -18,15 +19,17 @@ namespace BirdsiteLive.Controllers
|
|||
[ApiController]
|
||||
public class WellKnownController : ControllerBase
|
||||
{
|
||||
private readonly IModerationRepository _moderationRepository;
|
||||
private readonly ITwitterUserService _twitterUserService;
|
||||
private readonly ITwitterUserDal _twitterUserDal;
|
||||
private readonly InstanceSettings _settings;
|
||||
|
||||
#region Ctor
|
||||
public WellKnownController(InstanceSettings settings, ITwitterUserService twitterUserService, ITwitterUserDal twitterUserDal)
|
||||
public WellKnownController(InstanceSettings settings, ITwitterUserService twitterUserService, ITwitterUserDal twitterUserDal, IModerationRepository moderationRepository)
|
||||
{
|
||||
_twitterUserService = twitterUserService;
|
||||
_twitterUserDal = twitterUserDal;
|
||||
_moderationRepository = moderationRepository;
|
||||
_settings = settings;
|
||||
}
|
||||
#endregion
|
||||
|
@ -58,6 +61,7 @@ namespace BirdsiteLive.Controllers
|
|||
{
|
||||
var version = System.Reflection.Assembly.GetEntryAssembly().GetName().Version.ToString(3);
|
||||
var twitterUsersCount = await _twitterUserDal.GetTwitterUsersCountAsync();
|
||||
var isOpenRegistration = _moderationRepository.GetModerationType(ModerationEntityTypeEnum.Follower) != ModerationTypeEnum.WhiteListing;
|
||||
|
||||
if (id == "2.0")
|
||||
{
|
||||
|
@ -81,7 +85,7 @@ namespace BirdsiteLive.Controllers
|
|||
{
|
||||
"activitypub"
|
||||
},
|
||||
openRegistrations = false,
|
||||
openRegistrations = isOpenRegistration,
|
||||
services = new Models.WellKnownModels.Services()
|
||||
{
|
||||
inbound = new object[0],
|
||||
|
@ -117,7 +121,7 @@ namespace BirdsiteLive.Controllers
|
|||
{
|
||||
"activitypub"
|
||||
},
|
||||
openRegistrations = false,
|
||||
openRegistrations = isOpenRegistration,
|
||||
services = new Models.WellKnownModels.Services()
|
||||
{
|
||||
inbound = new object[0],
|
||||
|
|
53
src/BirdsiteLive/Services/CachedStatisticsService.cs
Normal file
53
src/BirdsiteLive/Services/CachedStatisticsService.cs
Normal file
|
@ -0,0 +1,53 @@
|
|||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using BirdsiteLive.DAL.Contracts;
|
||||
using BirdsiteLive.Statistics.Domain;
|
||||
|
||||
namespace BirdsiteLive.Services
|
||||
{
|
||||
public interface ICachedStatisticsService
|
||||
{
|
||||
Task<CachedStatistics> GetStatisticsAsync();
|
||||
}
|
||||
|
||||
public class CachedStatisticsService : ICachedStatisticsService
|
||||
{
|
||||
private readonly ITwitterStatisticsHandler _twitterStatisticsHandler;
|
||||
private readonly ITwitterUserDal _twitterUserDal;
|
||||
|
||||
private static CachedStatistics _cachedStatistics;
|
||||
|
||||
#region Ctor
|
||||
public CachedStatisticsService(ITwitterStatisticsHandler twitterStatisticsHandler, ITwitterUserDal twitterUserDal)
|
||||
{
|
||||
_twitterStatisticsHandler = twitterStatisticsHandler;
|
||||
_twitterUserDal = twitterUserDal;
|
||||
}
|
||||
#endregion
|
||||
|
||||
public async Task<CachedStatistics> GetStatisticsAsync()
|
||||
{
|
||||
if (_cachedStatistics == null ||
|
||||
(DateTime.UtcNow - _cachedStatistics.RefreshedTime).TotalMinutes > 15)
|
||||
{
|
||||
var twitterUserMax = _twitterStatisticsHandler.GetStatistics().UserCallsMax;
|
||||
var twitterUserCount = await _twitterUserDal.GetTwitterUsersCountAsync();
|
||||
var saturation = (int)((double)twitterUserCount / twitterUserMax * 100);
|
||||
|
||||
_cachedStatistics = new CachedStatistics
|
||||
{
|
||||
RefreshedTime = DateTime.UtcNow,
|
||||
Saturation = saturation
|
||||
};
|
||||
}
|
||||
|
||||
return _cachedStatistics;
|
||||
}
|
||||
}
|
||||
|
||||
public class CachedStatistics
|
||||
{
|
||||
public DateTime RefreshedTime { get; set; }
|
||||
public int Saturation { get; set; }
|
||||
}
|
||||
}
|
|
@ -4,6 +4,7 @@ using System.Threading;
|
|||
using System.Threading.Tasks;
|
||||
using BirdsiteLive.DAL;
|
||||
using BirdsiteLive.DAL.Contracts;
|
||||
using BirdsiteLive.Moderation;
|
||||
using BirdsiteLive.Pipeline;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
|
||||
|
@ -12,13 +13,15 @@ namespace BirdsiteLive.Services
|
|||
public class FederationService : BackgroundService
|
||||
{
|
||||
private readonly IDatabaseInitializer _databaseInitializer;
|
||||
private readonly IModerationPipeline _moderationPipeline;
|
||||
private readonly IStatusPublicationPipeline _statusPublicationPipeline;
|
||||
private readonly IHostApplicationLifetime _applicationLifetime;
|
||||
|
||||
#region Ctor
|
||||
public FederationService(IDatabaseInitializer databaseInitializer, IStatusPublicationPipeline statusPublicationPipeline, IHostApplicationLifetime applicationLifetime)
|
||||
public FederationService(IDatabaseInitializer databaseInitializer, IModerationPipeline moderationPipeline, IStatusPublicationPipeline statusPublicationPipeline, IHostApplicationLifetime applicationLifetime)
|
||||
{
|
||||
_databaseInitializer = databaseInitializer;
|
||||
_moderationPipeline = moderationPipeline;
|
||||
_statusPublicationPipeline = statusPublicationPipeline;
|
||||
_applicationLifetime = applicationLifetime;
|
||||
}
|
||||
|
@ -29,6 +32,7 @@ namespace BirdsiteLive.Services
|
|||
try
|
||||
{
|
||||
await _databaseInitializer.InitAndMigrateDbAsync();
|
||||
await _moderationPipeline.ApplyModerationSettingsAsync();
|
||||
await _statusPublicationPipeline.ExecuteAsync(stoppingToken);
|
||||
}
|
||||
finally
|
||||
|
|
|
@ -66,7 +66,10 @@ namespace BirdsiteLive
|
|||
|
||||
var logsSettings = Configuration.GetSection("Logging").Get<LogsSettings>();
|
||||
services.For<LogsSettings>().Use(x => logsSettings);
|
||||
|
||||
|
||||
var moderationSettings = Configuration.GetSection("Moderation").Get<ModerationSettings>();
|
||||
services.For<ModerationSettings>().Use(x => moderationSettings);
|
||||
|
||||
if (string.Equals(dbSettings.Type, DbTypes.Postgres, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
var connString = $"Host={dbSettings.Host};Username={dbSettings.User};Password={dbSettings.Password};Database={dbSettings.Name}";
|
||||
|
@ -96,6 +99,7 @@ namespace BirdsiteLive
|
|||
_.Assembly("BirdsiteLive.Domain");
|
||||
_.Assembly("BirdsiteLive.DAL");
|
||||
_.Assembly("BirdsiteLive.DAL.Postgres");
|
||||
_.Assembly("BirdsiteLive.Moderation");
|
||||
_.Assembly("BirdsiteLive.Pipeline");
|
||||
_.TheCallingAssembly();
|
||||
|
||||
|
|
27
src/BirdsiteLive/Views/About/Blacklisting.cshtml
Normal file
27
src/BirdsiteLive/Views/About/Blacklisting.cshtml
Normal file
|
@ -0,0 +1,27 @@
|
|||
@using BirdsiteLive.Domain.Repository
|
||||
@model BirdsiteLive.Controllers.ModerationStatus
|
||||
@{
|
||||
ViewData["Title"] = "Blacklisting";
|
||||
}
|
||||
|
||||
<div class="col-12 col-sm-12 col-md-10 col-lg-8 mx-auto">
|
||||
<h2>Blacklisting</h2>
|
||||
|
||||
@if (Model.Followers == ModerationTypeEnum.BlackListing)
|
||||
{
|
||||
<p><br />This node is blacklisting some instances and/or Fediverse users.<br /><br /></p>
|
||||
}
|
||||
|
||||
@if (Model.TwitterAccounts == ModerationTypeEnum.BlackListing)
|
||||
{
|
||||
<p><br />This node is blacklisting some twitter users.<br /><br /></p>
|
||||
}
|
||||
|
||||
@if (Model.Followers != ModerationTypeEnum.BlackListing && Model.TwitterAccounts != ModerationTypeEnum.BlackListing)
|
||||
{
|
||||
<p><br />This node is not using blacklisting.<br /><br /></p>
|
||||
}
|
||||
|
||||
@*<h2>FAQ</h2>
|
||||
<p>TODO</p>*@
|
||||
</div>
|
30
src/BirdsiteLive/Views/About/Index.cshtml
Normal file
30
src/BirdsiteLive/Views/About/Index.cshtml
Normal file
|
@ -0,0 +1,30 @@
|
|||
@model BirdsiteLive.Services.CachedStatistics
|
||||
@{
|
||||
ViewData["Title"] = "About";
|
||||
}
|
||||
|
||||
<div class="col-12 col-sm-12 col-md-10 col-lg-8 mx-auto">
|
||||
<h2>Node Saturation</h2>
|
||||
|
||||
<p>
|
||||
<br/>
|
||||
This node usage is at @Model.Saturation%<br/>
|
||||
<br/>
|
||||
</p>
|
||||
|
||||
<h2>FAQ</h2>
|
||||
<h4>Why is there a limit on the node?</h4>
|
||||
|
||||
<p>BirdsiteLIVE rely on the Twitter API to provide high quality content. This API has limitations and therefore limits node capacity.</p>
|
||||
|
||||
<h4>What happen when the node is saturated?</h4>
|
||||
|
||||
<p>
|
||||
When the saturation rate goes above 100% the node will no longer update all accounts every 15 minutes and instead will reduce the pooling rate to stay under the API limits, the more saturated a node is the less efficient it will be.<br />
|
||||
The software doesn't scale, and it's by design.
|
||||
</p>
|
||||
|
||||
<h4>How can I reduce the node's saturation?</h4>
|
||||
|
||||
<p>If you're not on your own node, be reasonable and don't follow too much accounts. And if you can, host your own node. BirdsiteLIVE doesn't require a lot of resources to work and therefore is really cheap to self-host.</p>
|
||||
</div>
|
27
src/BirdsiteLive/Views/About/Whitelisting.cshtml
Normal file
27
src/BirdsiteLive/Views/About/Whitelisting.cshtml
Normal file
|
@ -0,0 +1,27 @@
|
|||
@using BirdsiteLive.Domain.Repository
|
||||
@model BirdsiteLive.Controllers.ModerationStatus
|
||||
@{
|
||||
ViewData["Title"] = "Whitelisting";
|
||||
}
|
||||
|
||||
<div class="col-12 col-sm-12 col-md-10 col-lg-8 mx-auto">
|
||||
<h2>Whitelisting</h2>
|
||||
|
||||
@if (Model.Followers == ModerationTypeEnum.WhiteListing)
|
||||
{
|
||||
<p><br />This node is whitelisting some instances and/or Fediverse users.<br /><br /></p>
|
||||
}
|
||||
|
||||
@if (Model.TwitterAccounts == ModerationTypeEnum.WhiteListing)
|
||||
{
|
||||
<p><br />This node is whitelisting some twitter users.<br /><br /></p>
|
||||
}
|
||||
|
||||
@if (Model.Followers != ModerationTypeEnum.WhiteListing && Model.TwitterAccounts != ModerationTypeEnum.WhiteListing)
|
||||
{
|
||||
<p><br />This node is not using whitelisting.<br /><br /></p>
|
||||
}
|
||||
|
||||
@*<h2>FAQ</h2>
|
||||
<p>TODO</p>*@
|
||||
</div>
|
|
@ -16,4 +16,11 @@
|
|||
<!-- Input and Submit elements -->
|
||||
|
||||
<button type="submit" value="Submit">Post Note</button>
|
||||
</form>
|
||||
|
||||
|
||||
<form asp-controller="Debuging" asp-action="PostRejectFollow" method="post">
|
||||
<!-- Input and Submit elements -->
|
||||
|
||||
<button type="submit" value="Submit">Reject Follow</button>
|
||||
</form>
|
|
@ -0,0 +1,22 @@
|
|||
@model BirdsiteLive.Component.NodeInfoViewModel
|
||||
|
||||
<div>
|
||||
@if (ViewData.Model.WhitelistingEnabled)
|
||||
{
|
||||
<a asp-controller="About" asp-action="Whitelisting" class="badge badge-light" title="What does this mean?">Whitelisting Enabled</a>
|
||||
}
|
||||
@if (ViewData.Model.BlacklistingEnabled)
|
||||
{
|
||||
<a asp-controller="About" asp-action="Blacklisting" class="badge badge-light" title="What does this mean?">Blacklisting Enabled</a>
|
||||
}
|
||||
|
||||
<div class="node-progress-bar">
|
||||
<div class="node-progress-bar__label"><a asp-controller="About" asp-action="Index">Instance saturation:</a></div>
|
||||
<div class="progress node-progress-bar__bar">
|
||||
<div class="progress-bar
|
||||
@((ViewData.Model.InstanceSaturation > 50 && ViewData.Model.InstanceSaturation < 75) ? "bg-warning ":"")
|
||||
@((ViewData.Model.InstanceSaturation > 75 && ViewData.Model.InstanceSaturation < 100) ? "bg-danger ":"")
|
||||
@((ViewData.Model.InstanceSaturation > 100) ? "bg-saturation-danger ":"")" style="width: @ViewData.Model.InstanceSaturation%">@ViewData.Model.InstanceSaturation%</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
|
@ -9,6 +9,7 @@
|
|||
<link rel="stylesheet" href="~/lib/bootstrap/dist/css/bootstrap.min.css" />
|
||||
<link rel="stylesheet" href="~/css/site.css" />
|
||||
<link rel="stylesheet" href="~/css/birdsite.css" />
|
||||
<link rel="stylesheet" href="~/css/pattern.css" />
|
||||
</head>
|
||||
<body>
|
||||
<header>
|
||||
|
@ -39,6 +40,11 @@
|
|||
</div>
|
||||
|
||||
<footer class="border-top footer text-muted">
|
||||
<div class="wrapper-nodeinfo">
|
||||
<div class="container container-nodeinfo">
|
||||
@await Component.InvokeAsync("NodeInfo")
|
||||
</div>
|
||||
</div>
|
||||
<div class="container">
|
||||
|
||||
<a href="https://github.com/NicolasConstant/BirdsiteLive">Github</a> @*<a asp-area="" asp-controller="Home" asp-action="Privacy">Privacy</a>*@
|
||||
|
|
|
@ -2,6 +2,11 @@
|
|||
"Logging": {
|
||||
"Type": "none",
|
||||
"InstrumentationKey": "key",
|
||||
"ApplicationInsights": {
|
||||
"LogLevel": {
|
||||
"Default": "Warning"
|
||||
}
|
||||
},
|
||||
"LogLevel": {
|
||||
"Default": "Information",
|
||||
"Microsoft": "Warning",
|
||||
|
@ -10,7 +15,7 @@
|
|||
},
|
||||
"AllowedHosts": "*",
|
||||
"Instance": {
|
||||
"Name": "BirdsiteLIVE",
|
||||
"Name": "BirdsiteLIVE",
|
||||
"Domain": "domain.name",
|
||||
"AdminEmail": "me@domain.name",
|
||||
"ResolveMentionsInProfiles": true,
|
||||
|
@ -27,5 +32,11 @@
|
|||
"Twitter": {
|
||||
"ConsumerKey": "twitter.api.key",
|
||||
"ConsumerSecret": "twitter.api.key"
|
||||
},
|
||||
"Moderation": {
|
||||
"FollowersWhiteListing": null,
|
||||
"FollowersBlackListing": null,
|
||||
"TwitterAccountsWhiteListing": null,
|
||||
"TwitterAccountsBlackListing": null
|
||||
}
|
||||
}
|
||||
|
|
71
src/BirdsiteLive/wwwroot/css/pattern.css
Normal file
71
src/BirdsiteLive/wwwroot/css/pattern.css
Normal file
|
@ -0,0 +1,71 @@
|
|||
.container-nodeinfo {
|
||||
line-height: 30px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.wrapper-nodeinfo {
|
||||
border-bottom: 1px solid #dee2e6;
|
||||
}
|
||||
|
||||
.bg-saturation-danger {
|
||||
background-color: #800000 !important;
|
||||
}
|
||||
|
||||
@media (max-width: 767px) {
|
||||
.node-progress-bar {
|
||||
display: block;
|
||||
width: 100%;
|
||||
margin-top: 3px;
|
||||
margin-bottom: 7px;
|
||||
}
|
||||
|
||||
.node-progress-bar__label {
|
||||
display: block;
|
||||
font-size: 12px;
|
||||
/*float: left;*/
|
||||
padding: 0 5px 0 0;
|
||||
/*height: 15px;*/
|
||||
line-height: 15px;
|
||||
}
|
||||
|
||||
.node-progress-bar__bar {
|
||||
width: 80%;
|
||||
margin: auto;
|
||||
margin-top: 5px;
|
||||
}
|
||||
|
||||
body {
|
||||
margin-bottom: 135px;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@media (min-width: 768px) {
|
||||
.node-progress-bar {
|
||||
display: inline-block;
|
||||
width: 400px;
|
||||
}
|
||||
|
||||
.node-progress-bar__label {
|
||||
display: inline-block;
|
||||
font-size: 12px;
|
||||
float: left;
|
||||
padding: 0 5px 0 0;
|
||||
/*height: 15px;*/
|
||||
line-height: 15px;
|
||||
}
|
||||
|
||||
.node-progress-bar__bar {
|
||||
width: 200px;
|
||||
position: relative;
|
||||
top: -1px;
|
||||
}
|
||||
|
||||
body {
|
||||
margin-bottom: 95px;
|
||||
}
|
||||
}
|
||||
|
||||
.node-progress-bar__label a {
|
||||
color: gray;
|
||||
}
|
|
@ -23,7 +23,7 @@ namespace BirdsiteLive.DAL.Postgres.DataAccessLayers
|
|||
public class DbInitializerPostgresDal : PostgresBase, IDbInitializerDal
|
||||
{
|
||||
private readonly PostgresTools _tools;
|
||||
private readonly Version _currentVersion = new Version(2, 0);
|
||||
private readonly Version _currentVersion = new Version(2, 1);
|
||||
private const string DbVersionType = "db-version";
|
||||
|
||||
#region Ctor
|
||||
|
@ -131,7 +131,8 @@ namespace BirdsiteLive.DAL.Postgres.DataAccessLayers
|
|||
{
|
||||
return new[]
|
||||
{
|
||||
new Tuple<Version, Version>(new Version(1,0), new Version(2,0))
|
||||
new Tuple<Version, Version>(new Version(1,0), new Version(2,0)),
|
||||
new Tuple<Version, Version>(new Version(2,0), new Version(2,1))
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -144,12 +145,19 @@ namespace BirdsiteLive.DAL.Postgres.DataAccessLayers
|
|||
|
||||
var addIndex = $@"CREATE INDEX IF NOT EXISTS lastsync_twitteruser ON {_settings.TwitterUserTableName}(lastSync)";
|
||||
await _tools.ExecuteRequestAsync(addIndex);
|
||||
|
||||
await UpdateDbVersionAsync(to);
|
||||
return to;
|
||||
}
|
||||
else if (from == new Version(2, 0) && to == new Version(2, 1))
|
||||
{
|
||||
var addActorId = $@"ALTER TABLE {_settings.FollowersTableName} ADD actorId VARCHAR(2048)";
|
||||
await _tools.ExecuteRequestAsync(addActorId);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
throw new NotImplementedException();
|
||||
await UpdateDbVersionAsync(to);
|
||||
return to;
|
||||
}
|
||||
|
||||
private async Task UpdateDbVersionAsync(Version newVersion)
|
||||
|
|
|
@ -20,7 +20,7 @@ namespace BirdsiteLive.DAL.Postgres.DataAccessLayers
|
|||
}
|
||||
#endregion
|
||||
|
||||
public async Task CreateFollowerAsync(string acct, string host, string inboxRoute, string sharedInboxRoute, int[] followings = null, Dictionary<int, long> followingSyncStatus = null)
|
||||
public async Task CreateFollowerAsync(string acct, string host, string inboxRoute, string sharedInboxRoute, string actorId, int[] followings = null, Dictionary<int, long> followingSyncStatus = null)
|
||||
{
|
||||
if(followings == null) followings = new int[0];
|
||||
if(followingSyncStatus == null) followingSyncStatus = new Dictionary<int, long>();
|
||||
|
@ -35,8 +35,8 @@ namespace BirdsiteLive.DAL.Postgres.DataAccessLayers
|
|||
dbConnection.Open();
|
||||
|
||||
await dbConnection.ExecuteAsync(
|
||||
$"INSERT INTO {_settings.FollowersTableName} (acct,host,inboxRoute,sharedInboxRoute,followings,followingsSyncStatus) VALUES(@acct,@host,@inboxRoute,@sharedInboxRoute,@followings,CAST(@followingsSyncStatus as json))",
|
||||
new { acct, host, inboxRoute, sharedInboxRoute, followings, followingsSyncStatus = serializedDic });
|
||||
$"INSERT INTO {_settings.FollowersTableName} (acct,host,inboxRoute,sharedInboxRoute,followings,followingsSyncStatus,actorId) VALUES(@acct,@host,@inboxRoute,@sharedInboxRoute,@followings,CAST(@followingsSyncStatus as json),@actorId)",
|
||||
new { acct, host, inboxRoute, sharedInboxRoute, followings, followingsSyncStatus = serializedDic, actorId });
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -84,6 +84,19 @@ namespace BirdsiteLive.DAL.Postgres.DataAccessLayers
|
|||
}
|
||||
}
|
||||
|
||||
public async Task<Follower[]> GetAllFollowersAsync()
|
||||
{
|
||||
var query = $"SELECT * FROM {_settings.FollowersTableName}";
|
||||
|
||||
using (var dbConnection = Connection)
|
||||
{
|
||||
dbConnection.Open();
|
||||
|
||||
var result = await dbConnection.QueryAsync<SerializedFollower>(query);
|
||||
return result.Select(Convert).ToArray();
|
||||
}
|
||||
}
|
||||
|
||||
public async Task UpdateFollowerAsync(Follower follower)
|
||||
{
|
||||
if (follower == default) throw new ArgumentException("follower");
|
||||
|
@ -116,8 +129,8 @@ namespace BirdsiteLive.DAL.Postgres.DataAccessLayers
|
|||
|
||||
public async Task DeleteFollowerAsync(string acct, string host)
|
||||
{
|
||||
if (acct == default) throw new ArgumentException("acct");
|
||||
if (host == default) throw new ArgumentException("host");
|
||||
if (string.IsNullOrWhiteSpace(acct)) throw new ArgumentException("acct");
|
||||
if (string.IsNullOrWhiteSpace(host)) throw new ArgumentException("host");
|
||||
|
||||
acct = acct.ToLowerInvariant();
|
||||
host = host.ToLowerInvariant();
|
||||
|
@ -142,6 +155,7 @@ namespace BirdsiteLive.DAL.Postgres.DataAccessLayers
|
|||
Acct = follower.Acct,
|
||||
Host = follower.Host,
|
||||
InboxRoute = follower.InboxRoute,
|
||||
ActorId = follower.ActorId,
|
||||
SharedInboxRoute = follower.SharedInboxRoute,
|
||||
Followings = follower.Followings.ToList(),
|
||||
FollowingsSyncStatus = JsonConvert.DeserializeObject<Dictionary<int,long>>(follower.FollowingsSyncStatus)
|
||||
|
@ -159,5 +173,6 @@ namespace BirdsiteLive.DAL.Postgres.DataAccessLayers
|
|||
public string Host { get; set; }
|
||||
public string InboxRoute { get; set; }
|
||||
public string SharedInboxRoute { get; set; }
|
||||
public string ActorId { get; set; }
|
||||
}
|
||||
}
|
|
@ -5,9 +5,7 @@ using BirdsiteLive.DAL.Contracts;
|
|||
using BirdsiteLive.DAL.Models;
|
||||
using BirdsiteLive.DAL.Postgres.DataAccessLayers.Base;
|
||||
using BirdsiteLive.DAL.Postgres.Settings;
|
||||
using BirdsiteLive.DAL.Postgres.Tools;
|
||||
using Dapper;
|
||||
using Npgsql;
|
||||
|
||||
namespace BirdsiteLive.DAL.Postgres.DataAccessLayers
|
||||
{
|
||||
|
@ -44,7 +42,20 @@ namespace BirdsiteLive.DAL.Postgres.DataAccessLayers
|
|||
{
|
||||
dbConnection.Open();
|
||||
|
||||
var result = (await dbConnection.QueryAsync<SyncTwitterUser>(query, new { acct = acct })).FirstOrDefault();
|
||||
var result = (await dbConnection.QueryAsync<SyncTwitterUser>(query, new { acct })).FirstOrDefault();
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<SyncTwitterUser> GetTwitterUserAsync(int id)
|
||||
{
|
||||
var query = $"SELECT * FROM {_settings.TwitterUserTableName} WHERE id = @id";
|
||||
|
||||
using (var dbConnection = Connection)
|
||||
{
|
||||
dbConnection.Open();
|
||||
|
||||
var result = (await dbConnection.QueryAsync<SyncTwitterUser>(query, new { id })).FirstOrDefault();
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
@ -75,6 +86,19 @@ namespace BirdsiteLive.DAL.Postgres.DataAccessLayers
|
|||
}
|
||||
}
|
||||
|
||||
public async Task<SyncTwitterUser[]> GetAllTwitterUsersAsync()
|
||||
{
|
||||
var query = $"SELECT * FROM {_settings.TwitterUserTableName}";
|
||||
|
||||
using (var dbConnection = Connection)
|
||||
{
|
||||
dbConnection.Open();
|
||||
|
||||
var result = await dbConnection.QueryAsync<SyncTwitterUser>(query);
|
||||
return result.ToArray();
|
||||
}
|
||||
}
|
||||
|
||||
public async Task UpdateTwitterUserAsync(int id, long lastTweetPostedId, long lastTweetSynchronizedForAllFollowersId, DateTime lastSync)
|
||||
{
|
||||
if(id == default) throw new ArgumentException("id");
|
||||
|
@ -94,7 +118,7 @@ namespace BirdsiteLive.DAL.Postgres.DataAccessLayers
|
|||
|
||||
public async Task DeleteTwitterUserAsync(string acct)
|
||||
{
|
||||
if (acct == default) throw new ArgumentException("acct");
|
||||
if (string.IsNullOrWhiteSpace(acct)) throw new ArgumentException("acct");
|
||||
|
||||
acct = acct.ToLowerInvariant();
|
||||
|
||||
|
@ -107,5 +131,19 @@ namespace BirdsiteLive.DAL.Postgres.DataAccessLayers
|
|||
await dbConnection.QueryAsync(query, new { acct });
|
||||
}
|
||||
}
|
||||
|
||||
public async Task DeleteTwitterUserAsync(int id)
|
||||
{
|
||||
if (id == default) throw new ArgumentException("id");
|
||||
|
||||
var query = $"DELETE FROM {_settings.TwitterUserTableName} WHERE id = @id";
|
||||
|
||||
using (var dbConnection = Connection)
|
||||
{
|
||||
dbConnection.Open();
|
||||
|
||||
await dbConnection.QueryAsync(query, new { id });
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -7,9 +7,10 @@ namespace BirdsiteLive.DAL.Contracts
|
|||
public interface IFollowersDal
|
||||
{
|
||||
Task<Follower> GetFollowerAsync(string acct, string host);
|
||||
Task CreateFollowerAsync(string acct, string host, string inboxRoute, string sharedInboxRoute, int[] followings = null,
|
||||
Task CreateFollowerAsync(string acct, string host, string inboxRoute, string sharedInboxRoute, string actorId, 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);
|
||||
|
|
|
@ -8,9 +8,12 @@ namespace BirdsiteLive.DAL.Contracts
|
|||
{
|
||||
Task CreateTwitterUserAsync(string acct, long lastTweetPostedId);
|
||||
Task<SyncTwitterUser> GetTwitterUserAsync(string acct);
|
||||
Task<SyncTwitterUser> GetTwitterUserAsync(int id);
|
||||
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();
|
||||
}
|
||||
}
|
|
@ -9,6 +9,7 @@ namespace BirdsiteLive.DAL.Models
|
|||
public List<int> Followings { get; set; }
|
||||
public Dictionary<int, long> FollowingsSyncStatus { get; set; }
|
||||
|
||||
public string ActorId { get; set; }
|
||||
public string Acct { get; set; }
|
||||
public string Host { get; set; }
|
||||
public string InboxRoute { get; set; }
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
using System.Threading.Tasks;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using NuGet.Frameworks;
|
||||
|
||||
namespace BirdsiteLive.Cryptography.Tests
|
||||
{
|
||||
|
@ -7,13 +8,12 @@ namespace BirdsiteLive.Cryptography.Tests
|
|||
public class MagicKeyTests
|
||||
{
|
||||
[TestMethod]
|
||||
public async Task Test()
|
||||
public void Test()
|
||||
{
|
||||
var g = MagicKey.Generate();
|
||||
|
||||
var magicKey = new MagicKey(g.PrivateKey);
|
||||
|
||||
|
||||
Assert.IsNotNull(magicKey);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,8 +1,8 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using BirdsiteLive.DAL.Models;
|
||||
using BirdsiteLive.DAL.Postgres.DataAccessLayers;
|
||||
using BirdsiteLive.DAL.Postgres.Tests.DataAccessLayers.Base;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
|
@ -41,9 +41,10 @@ namespace BirdsiteLive.DAL.Postgres.Tests.DataAccessLayers
|
|||
};
|
||||
var inboxRoute = "/myhandle/inbox";
|
||||
var sharedInboxRoute = "/inbox";
|
||||
var actorId = $"https://{host}/{acct}";
|
||||
|
||||
var dal = new FollowersPostgresDal(_settings);
|
||||
await dal.CreateFollowerAsync(acct, host, inboxRoute, sharedInboxRoute, following, followingSync);
|
||||
await dal.CreateFollowerAsync(acct, host, inboxRoute, sharedInboxRoute, actorId, following, followingSync);
|
||||
|
||||
var result = await dal.GetFollowerAsync(acct, host);
|
||||
|
||||
|
@ -52,6 +53,7 @@ namespace BirdsiteLive.DAL.Postgres.Tests.DataAccessLayers
|
|||
Assert.AreEqual(host, result.Host);
|
||||
Assert.AreEqual(inboxRoute, result.InboxRoute);
|
||||
Assert.AreEqual(sharedInboxRoute, result.SharedInboxRoute);
|
||||
Assert.AreEqual(actorId, result.ActorId);
|
||||
Assert.AreEqual(following.Length, result.Followings.Count);
|
||||
Assert.AreEqual(following[0], result.Followings[0]);
|
||||
Assert.AreEqual(followingSync.Count, result.FollowingsSyncStatus.Count);
|
||||
|
@ -59,6 +61,38 @@ namespace BirdsiteLive.DAL.Postgres.Tests.DataAccessLayers
|
|||
Assert.AreEqual(followingSync.First().Value, result.FollowingsSyncStatus.First().Value);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public async Task CreateAndGetFollower_NoFollowings()
|
||||
{
|
||||
var acct = "myhandle";
|
||||
var host = "domain.ext";
|
||||
var inboxRoute = "/myhandle/inbox";
|
||||
var sharedInboxRoute = "/inbox";
|
||||
var actorId = $"https://{host}/{acct}";
|
||||
|
||||
var dal = new FollowersPostgresDal(_settings);
|
||||
await dal.CreateFollowerAsync(acct, host, inboxRoute, sharedInboxRoute, actorId, null, null);
|
||||
|
||||
var result = await dal.GetFollowerAsync(acct, host);
|
||||
|
||||
Assert.IsNotNull(result);
|
||||
Assert.AreEqual(acct, result.Acct);
|
||||
Assert.AreEqual(host, result.Host);
|
||||
Assert.AreEqual(actorId, result.ActorId);
|
||||
Assert.AreEqual(inboxRoute, result.InboxRoute);
|
||||
Assert.AreEqual(sharedInboxRoute, result.SharedInboxRoute);
|
||||
Assert.AreEqual(0, result.Followings.Count);
|
||||
Assert.AreEqual(0, result.FollowingsSyncStatus.Count);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[ExpectedException(typeof(ArgumentException))]
|
||||
public async Task GetFollowers_NoId()
|
||||
{
|
||||
var dal = new FollowersPostgresDal(_settings);
|
||||
await dal.GetFollowersAsync(default);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public async Task CreateAndGetFollower_NoSharedInbox()
|
||||
{
|
||||
|
@ -73,9 +107,10 @@ namespace BirdsiteLive.DAL.Postgres.Tests.DataAccessLayers
|
|||
};
|
||||
var inboxRoute = "/myhandle/inbox";
|
||||
string sharedInboxRoute = null;
|
||||
var actorId = $"https://{host}/{acct}";
|
||||
|
||||
var dal = new FollowersPostgresDal(_settings);
|
||||
await dal.CreateFollowerAsync(acct, host, inboxRoute, sharedInboxRoute, following, followingSync);
|
||||
await dal.CreateFollowerAsync(acct, host, inboxRoute, sharedInboxRoute, actorId, following, followingSync);
|
||||
|
||||
var result = await dal.GetFollowerAsync(acct, host);
|
||||
|
||||
|
@ -83,6 +118,7 @@ namespace BirdsiteLive.DAL.Postgres.Tests.DataAccessLayers
|
|||
Assert.AreEqual(acct, result.Acct);
|
||||
Assert.AreEqual(host, result.Host);
|
||||
Assert.AreEqual(inboxRoute, result.InboxRoute);
|
||||
Assert.AreEqual(actorId, result.ActorId);
|
||||
Assert.AreEqual(sharedInboxRoute, result.SharedInboxRoute);
|
||||
Assert.AreEqual(following.Length, result.Followings.Count);
|
||||
Assert.AreEqual(following[0], result.Followings[0]);
|
||||
|
@ -103,7 +139,8 @@ namespace BirdsiteLive.DAL.Postgres.Tests.DataAccessLayers
|
|||
var followingSync = new Dictionary<int, long>();
|
||||
var inboxRoute = "/myhandle1/inbox";
|
||||
var sharedInboxRoute = "/inbox";
|
||||
await dal.CreateFollowerAsync(acct, host, inboxRoute, sharedInboxRoute, following, followingSync);
|
||||
var actorId = $"https://{host}/{acct}";
|
||||
await dal.CreateFollowerAsync(acct, host, inboxRoute, sharedInboxRoute, actorId, following, followingSync);
|
||||
|
||||
//User 2
|
||||
acct = "myhandle2";
|
||||
|
@ -111,7 +148,8 @@ namespace BirdsiteLive.DAL.Postgres.Tests.DataAccessLayers
|
|||
following = new[] { 2, 4, 5 };
|
||||
inboxRoute = "/myhandle2/inbox";
|
||||
sharedInboxRoute = "/inbox2";
|
||||
await dal.CreateFollowerAsync(acct, host, inboxRoute, sharedInboxRoute, following, followingSync);
|
||||
actorId = $"https://{host}/{acct}";
|
||||
await dal.CreateFollowerAsync(acct, host, inboxRoute, sharedInboxRoute, actorId, following, followingSync);
|
||||
|
||||
//User 2
|
||||
acct = "myhandle3";
|
||||
|
@ -119,7 +157,8 @@ namespace BirdsiteLive.DAL.Postgres.Tests.DataAccessLayers
|
|||
following = new[] { 1 };
|
||||
inboxRoute = "/myhandle3/inbox";
|
||||
sharedInboxRoute = "/inbox3";
|
||||
await dal.CreateFollowerAsync(acct, host, inboxRoute, sharedInboxRoute, following, followingSync);
|
||||
actorId = $"https://{host}/{acct}";
|
||||
await dal.CreateFollowerAsync(acct, host, inboxRoute, sharedInboxRoute, actorId, following, followingSync);
|
||||
|
||||
var result = await dal.GetFollowersAsync(2);
|
||||
Assert.AreEqual(2, result.Length);
|
||||
|
@ -131,6 +170,43 @@ namespace BirdsiteLive.DAL.Postgres.Tests.DataAccessLayers
|
|||
Assert.AreEqual(0, result.Length);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public async Task GetAllFollowersAsync()
|
||||
{
|
||||
var dal = new FollowersPostgresDal(_settings);
|
||||
|
||||
//User 1
|
||||
var acct = "myhandle1";
|
||||
var host = "domain.ext";
|
||||
var following = new[] { 1, 2, 3 };
|
||||
var followingSync = new Dictionary<int, long>();
|
||||
var inboxRoute = "/myhandle1/inbox";
|
||||
var sharedInboxRoute = "/inbox";
|
||||
var actorId = $"https://{host}/{acct}";
|
||||
await dal.CreateFollowerAsync(acct, host, inboxRoute, sharedInboxRoute, actorId, following, followingSync);
|
||||
|
||||
//User 2
|
||||
acct = "myhandle2";
|
||||
host = "domain.ext";
|
||||
following = new[] { 2, 4, 5 };
|
||||
inboxRoute = "/myhandle2/inbox";
|
||||
sharedInboxRoute = "/inbox2";
|
||||
actorId = $"https://{host}/{acct}";
|
||||
await dal.CreateFollowerAsync(acct, host, inboxRoute, sharedInboxRoute, actorId, following, followingSync);
|
||||
|
||||
//User 2
|
||||
acct = "myhandle3";
|
||||
host = "domain.ext";
|
||||
following = new[] { 1 };
|
||||
inboxRoute = "/myhandle3/inbox";
|
||||
sharedInboxRoute = "/inbox3";
|
||||
actorId = $"https://{host}/{acct}";
|
||||
await dal.CreateFollowerAsync(acct, host, inboxRoute, sharedInboxRoute, actorId, following, followingSync);
|
||||
|
||||
var result = await dal.GetAllFollowersAsync();
|
||||
Assert.AreEqual(3, result.Length);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public async Task CountFollowersAsync()
|
||||
{
|
||||
|
@ -146,7 +222,8 @@ namespace BirdsiteLive.DAL.Postgres.Tests.DataAccessLayers
|
|||
var followingSync = new Dictionary<int, long>();
|
||||
var inboxRoute = "/myhandle1/inbox";
|
||||
var sharedInboxRoute = "/inbox";
|
||||
await dal.CreateFollowerAsync(acct, host, inboxRoute, sharedInboxRoute, following, followingSync);
|
||||
var actorId = $"https://{host}/{acct}";
|
||||
await dal.CreateFollowerAsync(acct, host, inboxRoute, sharedInboxRoute, actorId, following, followingSync);
|
||||
|
||||
//User 2
|
||||
acct = "myhandle2";
|
||||
|
@ -154,7 +231,8 @@ namespace BirdsiteLive.DAL.Postgres.Tests.DataAccessLayers
|
|||
following = new[] { 2, 4, 5 };
|
||||
inboxRoute = "/myhandle2/inbox";
|
||||
sharedInboxRoute = "/inbox2";
|
||||
await dal.CreateFollowerAsync(acct, host, inboxRoute, sharedInboxRoute, following, followingSync);
|
||||
actorId = $"https://{host}/{acct}";
|
||||
await dal.CreateFollowerAsync(acct, host, inboxRoute, sharedInboxRoute, actorId, following, followingSync);
|
||||
|
||||
//User 2
|
||||
acct = "myhandle3";
|
||||
|
@ -162,7 +240,8 @@ namespace BirdsiteLive.DAL.Postgres.Tests.DataAccessLayers
|
|||
following = new[] { 1 };
|
||||
inboxRoute = "/myhandle3/inbox";
|
||||
sharedInboxRoute = "/inbox3";
|
||||
await dal.CreateFollowerAsync(acct, host, inboxRoute, sharedInboxRoute, following, followingSync);
|
||||
actorId = $"https://{host}/{acct}";
|
||||
await dal.CreateFollowerAsync(acct, host, inboxRoute, sharedInboxRoute, actorId, following, followingSync);
|
||||
|
||||
result = await dal.GetFollowersCountAsync();
|
||||
Assert.AreEqual(3, result);
|
||||
|
@ -182,9 +261,10 @@ namespace BirdsiteLive.DAL.Postgres.Tests.DataAccessLayers
|
|||
};
|
||||
var inboxRoute = "/myhandle/inbox";
|
||||
var sharedInboxRoute = "/inbox";
|
||||
var actorId = $"https://{host}/{acct}";
|
||||
|
||||
var dal = new FollowersPostgresDal(_settings);
|
||||
await dal.CreateFollowerAsync(acct, host, inboxRoute, sharedInboxRoute, following, followingSync);
|
||||
await dal.CreateFollowerAsync(acct, host, inboxRoute, sharedInboxRoute, actorId, following, followingSync);
|
||||
var result = await dal.GetFollowerAsync(acct, host);
|
||||
|
||||
var updatedFollowing = new List<int> { 12, 19, 23, 24 };
|
||||
|
@ -222,9 +302,10 @@ namespace BirdsiteLive.DAL.Postgres.Tests.DataAccessLayers
|
|||
};
|
||||
var inboxRoute = "/myhandle/inbox";
|
||||
var sharedInboxRoute = "/inbox";
|
||||
var actorId = $"https://{host}/{acct}";
|
||||
|
||||
var dal = new FollowersPostgresDal(_settings);
|
||||
await dal.CreateFollowerAsync(acct, host, inboxRoute, sharedInboxRoute, following, followingSync);
|
||||
await dal.CreateFollowerAsync(acct, host, inboxRoute, sharedInboxRoute, actorId, following, followingSync);
|
||||
var result = await dal.GetFollowerAsync(acct, host);
|
||||
|
||||
var updatedFollowing = new[] { 12, 19 };
|
||||
|
@ -246,6 +327,27 @@ namespace BirdsiteLive.DAL.Postgres.Tests.DataAccessLayers
|
|||
Assert.AreEqual(updatedFollowingSync.First().Value, result.FollowingsSyncStatus.First().Value);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[ExpectedException(typeof(ArgumentException))]
|
||||
public async Task Update_NoFollower()
|
||||
{
|
||||
var dal = new FollowersPostgresDal(_settings);
|
||||
await dal.UpdateFollowerAsync(null);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[ExpectedException(typeof(ArgumentException))]
|
||||
public async Task Update_NoFollowerId()
|
||||
{
|
||||
var follower = new Follower
|
||||
{
|
||||
Id = default
|
||||
};
|
||||
|
||||
var dal = new FollowersPostgresDal(_settings);
|
||||
await dal.UpdateFollowerAsync(follower);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public async Task CreateAndDeleteFollower_ById()
|
||||
{
|
||||
|
@ -260,9 +362,10 @@ namespace BirdsiteLive.DAL.Postgres.Tests.DataAccessLayers
|
|||
};
|
||||
var inboxRoute = "/myhandle/inbox";
|
||||
var sharedInboxRoute = "/inbox";
|
||||
var actorId = $"https://{host}/{acct}";
|
||||
|
||||
var dal = new FollowersPostgresDal(_settings);
|
||||
await dal.CreateFollowerAsync(acct, host, inboxRoute, sharedInboxRoute, following, followingSync);
|
||||
await dal.CreateFollowerAsync(acct, host, inboxRoute, sharedInboxRoute, actorId, following, followingSync);
|
||||
var result = await dal.GetFollowerAsync(acct, host);
|
||||
Assert.IsNotNull(result);
|
||||
|
||||
|
@ -286,9 +389,10 @@ namespace BirdsiteLive.DAL.Postgres.Tests.DataAccessLayers
|
|||
};
|
||||
var inboxRoute = "/myhandle/inbox";
|
||||
var sharedInboxRoute = "/inbox";
|
||||
var actorId = $"https://{host}/{acct}";
|
||||
|
||||
var dal = new FollowersPostgresDal(_settings);
|
||||
await dal.CreateFollowerAsync(acct, host, inboxRoute, sharedInboxRoute, following, followingSync);
|
||||
await dal.CreateFollowerAsync(acct, host, inboxRoute, sharedInboxRoute, actorId, following, followingSync);
|
||||
var result = await dal.GetFollowerAsync(acct, host);
|
||||
Assert.IsNotNull(result);
|
||||
|
||||
|
@ -297,5 +401,29 @@ namespace BirdsiteLive.DAL.Postgres.Tests.DataAccessLayers
|
|||
result = await dal.GetFollowerAsync(acct, host);
|
||||
Assert.IsNull(result);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[ExpectedException(typeof(ArgumentException))]
|
||||
public async Task Delete_NoFollowerId()
|
||||
{
|
||||
var dal = new FollowersPostgresDal(_settings);
|
||||
await dal.DeleteFollowerAsync(default);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[ExpectedException(typeof(ArgumentException))]
|
||||
public async Task Delete_NoAcct()
|
||||
{
|
||||
var dal = new FollowersPostgresDal(_settings);
|
||||
await dal.DeleteFollowerAsync(string.Empty, string.Empty);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[ExpectedException(typeof(ArgumentException))]
|
||||
public async Task Delete_NoHost()
|
||||
{
|
||||
var dal = new FollowersPostgresDal(_settings);
|
||||
await dal.DeleteFollowerAsync("acct", string.Empty);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -50,6 +50,24 @@ namespace BirdsiteLive.DAL.Postgres.Tests.DataAccessLayers
|
|||
Assert.IsTrue(result.Id > 0);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public async Task CreateAndGetUser_byId()
|
||||
{
|
||||
var acct = "myid";
|
||||
var lastTweetId = 1548L;
|
||||
|
||||
var dal = new TwitterUserPostgresDal(_settings);
|
||||
|
||||
await dal.CreateTwitterUserAsync(acct, lastTweetId);
|
||||
var result = await dal.GetTwitterUserAsync(acct);
|
||||
var resultById = await dal.GetTwitterUserAsync(result.Id);
|
||||
|
||||
Assert.AreEqual(acct, resultById.Acct);
|
||||
Assert.AreEqual(lastTweetId, resultById.LastTweetPostedId);
|
||||
Assert.AreEqual(lastTweetId, resultById.LastTweetSynchronizedForAllFollowersId);
|
||||
Assert.AreEqual(result.Id, resultById.Id);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public async Task CreateUpdateAndGetUser()
|
||||
{
|
||||
|
@ -75,10 +93,42 @@ namespace BirdsiteLive.DAL.Postgres.Tests.DataAccessLayers
|
|||
Assert.IsTrue(Math.Abs((now.ToUniversalTime() - result.LastSync).Milliseconds) < 100);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[ExpectedException(typeof(ArgumentException))]
|
||||
public async Task Update_NoId()
|
||||
{
|
||||
var dal = new TwitterUserPostgresDal(_settings);
|
||||
await dal.UpdateTwitterUserAsync(default, default, default, DateTime.UtcNow);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[ExpectedException(typeof(ArgumentException))]
|
||||
public async Task Update_NoLastTweetPostedId()
|
||||
{
|
||||
var dal = new TwitterUserPostgresDal(_settings);
|
||||
await dal.UpdateTwitterUserAsync(12, default, default, DateTime.UtcNow);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[ExpectedException(typeof(ArgumentException))]
|
||||
public async Task Update_NoLastTweetSynchronizedForAllFollowersId()
|
||||
{
|
||||
var dal = new TwitterUserPostgresDal(_settings);
|
||||
await dal.UpdateTwitterUserAsync(12, 9556, default, DateTime.UtcNow);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[ExpectedException(typeof(ArgumentException))]
|
||||
public async Task Update_NoLastSync()
|
||||
{
|
||||
var dal = new TwitterUserPostgresDal(_settings);
|
||||
await dal.UpdateTwitterUserAsync(12, 9556, 65, default);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public async Task CreateAndDeleteUser()
|
||||
{
|
||||
var acct = "myid";
|
||||
var acct = "myacct";
|
||||
var lastTweetId = 1548L;
|
||||
|
||||
var dal = new TwitterUserPostgresDal(_settings);
|
||||
|
@ -93,7 +143,40 @@ namespace BirdsiteLive.DAL.Postgres.Tests.DataAccessLayers
|
|||
}
|
||||
|
||||
[TestMethod]
|
||||
public async Task GetAllTwitterUsers()
|
||||
[ExpectedException(typeof(ArgumentException))]
|
||||
public async Task DeleteUser_NotAcct()
|
||||
{
|
||||
var dal = new TwitterUserPostgresDal(_settings);
|
||||
await dal.DeleteTwitterUserAsync(string.Empty);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public async Task CreateAndDeleteUser_byId()
|
||||
{
|
||||
var acct = "myacct";
|
||||
var lastTweetId = 1548L;
|
||||
|
||||
var dal = new TwitterUserPostgresDal(_settings);
|
||||
|
||||
await dal.CreateTwitterUserAsync(acct, lastTweetId);
|
||||
var result = await dal.GetTwitterUserAsync(acct);
|
||||
Assert.IsNotNull(result);
|
||||
|
||||
await dal.DeleteTwitterUserAsync(result.Id);
|
||||
result = await dal.GetTwitterUserAsync(acct);
|
||||
Assert.IsNull(result);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[ExpectedException(typeof(ArgumentException))]
|
||||
public async Task DeleteUser_NotAcct_byId()
|
||||
{
|
||||
var dal = new TwitterUserPostgresDal(_settings);
|
||||
await dal.DeleteTwitterUserAsync(default(int));
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public async Task GetAllTwitterUsers_Top()
|
||||
{
|
||||
var dal = new TwitterUserPostgresDal(_settings);
|
||||
for (var i = 0; i < 1000; i++)
|
||||
|
@ -147,6 +230,26 @@ namespace BirdsiteLive.DAL.Postgres.Tests.DataAccessLayers
|
|||
Assert.IsTrue(Math.Abs((acc.LastSync - oldest.ToUniversalTime()).TotalMilliseconds) < 1000);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public async Task GetAllTwitterUsers()
|
||||
{
|
||||
var dal = new TwitterUserPostgresDal(_settings);
|
||||
for (var i = 0; i < 1000; i++)
|
||||
{
|
||||
var acct = $"myid{i}";
|
||||
var lastTweetId = 1548L;
|
||||
|
||||
await dal.CreateTwitterUserAsync(acct, lastTweetId);
|
||||
}
|
||||
|
||||
var result = await dal.GetAllTwitterUsersAsync();
|
||||
Assert.AreEqual(1000, result.Length);
|
||||
Assert.IsFalse(result[0].Id == default);
|
||||
Assert.IsFalse(result[0].Acct == default);
|
||||
Assert.IsFalse(result[0].LastTweetPostedId == default);
|
||||
Assert.IsFalse(result[0].LastTweetSynchronizedForAllFollowersId == default);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public async Task CountTwitterUsers()
|
||||
{
|
||||
|
|
|
@ -18,4 +18,8 @@
|
|||
<ProjectReference Include="..\..\BirdsiteLive.Domain\BirdsiteLive.Domain.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Folder Include="Repository\" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
|
|
@ -21,6 +21,7 @@ namespace BirdsiteLive.Domain.Tests.BusinessUseCases
|
|||
var twitterName = "handle";
|
||||
var followerInbox = "/user/testest";
|
||||
var inbox = "/inbox";
|
||||
var actorId = "actorUrl";
|
||||
|
||||
var follower = new Follower
|
||||
{
|
||||
|
@ -55,6 +56,7 @@ namespace BirdsiteLive.Domain.Tests.BusinessUseCases
|
|||
It.Is<string>(y => y == domain),
|
||||
It.Is<string>(y => y == followerInbox),
|
||||
It.Is<string>(y => y == inbox),
|
||||
It.Is<string>(y => y == actorId),
|
||||
null,
|
||||
null))
|
||||
.Returns(Task.CompletedTask);
|
||||
|
@ -80,7 +82,7 @@ namespace BirdsiteLive.Domain.Tests.BusinessUseCases
|
|||
#endregion
|
||||
|
||||
var action = new ProcessFollowUser(followersDalMock.Object, twitterUserDalMock.Object);
|
||||
await action.ExecuteAsync(username, domain, twitterName, followerInbox, inbox);
|
||||
await action.ExecuteAsync(username, domain, twitterName, followerInbox, inbox, actorId);
|
||||
|
||||
#region Validations
|
||||
followersDalMock.VerifyAll();
|
||||
|
@ -97,7 +99,8 @@ namespace BirdsiteLive.Domain.Tests.BusinessUseCases
|
|||
var twitterName = "handle";
|
||||
var followerInbox = "/user/testest";
|
||||
var inbox = "/inbox";
|
||||
|
||||
var actorId = "actorUrl";
|
||||
|
||||
var follower = new Follower
|
||||
{
|
||||
Id = 1,
|
||||
|
@ -138,7 +141,7 @@ namespace BirdsiteLive.Domain.Tests.BusinessUseCases
|
|||
#endregion
|
||||
|
||||
var action = new ProcessFollowUser(followersDalMock.Object, twitterUserDalMock.Object);
|
||||
await action.ExecuteAsync(username, domain, twitterName, followerInbox, inbox);
|
||||
await action.ExecuteAsync(username, domain, twitterName, followerInbox, inbox, actorId);
|
||||
|
||||
#region Validations
|
||||
followersDalMock.VerifyAll();
|
||||
|
|
|
@ -0,0 +1,347 @@
|
|||
using BirdsiteLive.Common.Settings;
|
||||
using BirdsiteLive.Domain.Repository;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
|
||||
namespace BirdsiteLive.Domain.Tests.Repository
|
||||
{
|
||||
[TestClass]
|
||||
public class ModerationRepositoryTests
|
||||
{
|
||||
#region GetModerationType
|
||||
[TestMethod]
|
||||
public void GetModerationType_Follower_WhiteListing_Test()
|
||||
{
|
||||
#region Stubs
|
||||
var settings = new ModerationSettings
|
||||
{
|
||||
FollowersWhiteListing = "@me@domain.ext"
|
||||
};
|
||||
#endregion
|
||||
|
||||
var repo = new ModerationRepository(settings);
|
||||
|
||||
#region Validations
|
||||
Assert.AreEqual(ModerationTypeEnum.WhiteListing ,repo.GetModerationType(ModerationEntityTypeEnum.Follower));
|
||||
Assert.AreEqual(ModerationTypeEnum.None, repo.GetModerationType(ModerationEntityTypeEnum.TwitterAccount));
|
||||
#endregion
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void GetModerationType_TwitterAccount_WhiteListing_Test()
|
||||
{
|
||||
#region Stubs
|
||||
var settings = new ModerationSettings
|
||||
{
|
||||
TwitterAccountsWhiteListing = "@me@domain.ext"
|
||||
};
|
||||
#endregion
|
||||
|
||||
var repo = new ModerationRepository(settings);
|
||||
|
||||
#region Validations
|
||||
Assert.AreEqual(ModerationTypeEnum.None, repo.GetModerationType(ModerationEntityTypeEnum.Follower));
|
||||
Assert.AreEqual(ModerationTypeEnum.WhiteListing, repo.GetModerationType(ModerationEntityTypeEnum.TwitterAccount));
|
||||
#endregion
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void GetModerationType_FollowerTwitterAccount_WhiteListing_Test()
|
||||
{
|
||||
#region Stubs
|
||||
var settings = new ModerationSettings
|
||||
{
|
||||
FollowersWhiteListing = "@me@domain.ext",
|
||||
TwitterAccountsWhiteListing = "@me@domain.ext"
|
||||
};
|
||||
#endregion
|
||||
|
||||
var repo = new ModerationRepository(settings);
|
||||
|
||||
#region Validations
|
||||
Assert.AreEqual(ModerationTypeEnum.WhiteListing, repo.GetModerationType(ModerationEntityTypeEnum.Follower));
|
||||
Assert.AreEqual(ModerationTypeEnum.WhiteListing, repo.GetModerationType(ModerationEntityTypeEnum.TwitterAccount));
|
||||
#endregion
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void GetModerationType_Follower_BlackListing_Test()
|
||||
{
|
||||
#region Stubs
|
||||
var settings = new ModerationSettings
|
||||
{
|
||||
FollowersBlackListing = "@me@domain.ext"
|
||||
};
|
||||
#endregion
|
||||
|
||||
var repo = new ModerationRepository(settings);
|
||||
|
||||
#region Validations
|
||||
Assert.AreEqual(ModerationTypeEnum.BlackListing, repo.GetModerationType(ModerationEntityTypeEnum.Follower));
|
||||
Assert.AreEqual(ModerationTypeEnum.None, repo.GetModerationType(ModerationEntityTypeEnum.TwitterAccount));
|
||||
#endregion
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void GetModerationType_TwitterAccount_BlackListing_Test()
|
||||
{
|
||||
#region Stubs
|
||||
var settings = new ModerationSettings
|
||||
{
|
||||
TwitterAccountsBlackListing = "@me@domain.ext"
|
||||
};
|
||||
#endregion
|
||||
|
||||
var repo = new ModerationRepository(settings);
|
||||
|
||||
#region Validations
|
||||
Assert.AreEqual(ModerationTypeEnum.None, repo.GetModerationType(ModerationEntityTypeEnum.Follower));
|
||||
Assert.AreEqual(ModerationTypeEnum.BlackListing, repo.GetModerationType(ModerationEntityTypeEnum.TwitterAccount));
|
||||
#endregion
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void GetModerationType_FollowerTwitterAccount_BlackListing_Test()
|
||||
{
|
||||
#region Stubs
|
||||
var settings = new ModerationSettings
|
||||
{
|
||||
FollowersBlackListing = "@me@domain.ext",
|
||||
TwitterAccountsBlackListing = "@me@domain.ext"
|
||||
};
|
||||
#endregion
|
||||
|
||||
var repo = new ModerationRepository(settings);
|
||||
|
||||
#region Validations
|
||||
Assert.AreEqual(ModerationTypeEnum.BlackListing, repo.GetModerationType(ModerationEntityTypeEnum.Follower));
|
||||
Assert.AreEqual(ModerationTypeEnum.BlackListing, repo.GetModerationType(ModerationEntityTypeEnum.TwitterAccount));
|
||||
#endregion
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void GetModerationType_Follower_BothListing_Test()
|
||||
{
|
||||
#region Stubs
|
||||
var settings = new ModerationSettings
|
||||
{
|
||||
FollowersBlackListing = "@me@domain.ext",
|
||||
FollowersWhiteListing = "@me@domain.ext",
|
||||
};
|
||||
#endregion
|
||||
|
||||
var repo = new ModerationRepository(settings);
|
||||
|
||||
#region Validations
|
||||
Assert.AreEqual(ModerationTypeEnum.WhiteListing, repo.GetModerationType(ModerationEntityTypeEnum.Follower));
|
||||
Assert.AreEqual(ModerationTypeEnum.None, repo.GetModerationType(ModerationEntityTypeEnum.TwitterAccount));
|
||||
#endregion
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void GetModerationType_TwitterAccount_BothListing_Test()
|
||||
{
|
||||
#region Stubs
|
||||
var settings = new ModerationSettings
|
||||
{
|
||||
TwitterAccountsBlackListing = "@me@domain.ext",
|
||||
TwitterAccountsWhiteListing = "@me@domain.ext"
|
||||
};
|
||||
#endregion
|
||||
|
||||
var repo = new ModerationRepository(settings);
|
||||
|
||||
#region Validations
|
||||
Assert.AreEqual(ModerationTypeEnum.None, repo.GetModerationType(ModerationEntityTypeEnum.Follower));
|
||||
Assert.AreEqual(ModerationTypeEnum.WhiteListing, repo.GetModerationType(ModerationEntityTypeEnum.TwitterAccount));
|
||||
#endregion
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void GetModerationType_FollowerTwitterAccount_BothListing_Test()
|
||||
{
|
||||
#region Stubs
|
||||
var settings = new ModerationSettings
|
||||
{
|
||||
FollowersBlackListing = "@me@domain.ext",
|
||||
FollowersWhiteListing = "@me@domain.ext",
|
||||
TwitterAccountsBlackListing = "@me@domain.ext",
|
||||
TwitterAccountsWhiteListing = "@me@domain.ext"
|
||||
};
|
||||
#endregion
|
||||
|
||||
var repo = new ModerationRepository(settings);
|
||||
|
||||
#region Validations
|
||||
Assert.AreEqual(ModerationTypeEnum.WhiteListing, repo.GetModerationType(ModerationEntityTypeEnum.Follower));
|
||||
Assert.AreEqual(ModerationTypeEnum.WhiteListing, repo.GetModerationType(ModerationEntityTypeEnum.TwitterAccount));
|
||||
#endregion
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region CheckStatus
|
||||
[TestMethod]
|
||||
public void CheckStatus_Follower_WhiteListing_Test()
|
||||
{
|
||||
#region Stubs
|
||||
var settings = new ModerationSettings
|
||||
{
|
||||
FollowersWhiteListing = "@me@domain.ext"
|
||||
};
|
||||
#endregion
|
||||
|
||||
var repo = new ModerationRepository(settings);
|
||||
|
||||
#region Validations
|
||||
Assert.AreEqual(ModeratedTypeEnum.WhiteListed, repo.CheckStatus(ModerationEntityTypeEnum.Follower, "@me@domain.ext"));
|
||||
Assert.AreEqual(ModeratedTypeEnum.None, repo.CheckStatus(ModerationEntityTypeEnum.Follower, "@me2@domain.ext"));
|
||||
#endregion
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void CheckStatus_Follower_WhiteListing_Instance_Test()
|
||||
{
|
||||
#region Stubs
|
||||
var settings = new ModerationSettings
|
||||
{
|
||||
FollowersWhiteListing = "domain.ext"
|
||||
};
|
||||
#endregion
|
||||
|
||||
var repo = new ModerationRepository(settings);
|
||||
|
||||
#region Validations
|
||||
Assert.AreEqual(ModeratedTypeEnum.WhiteListed, repo.CheckStatus(ModerationEntityTypeEnum.Follower, "@me@domain.ext"));
|
||||
Assert.AreEqual(ModeratedTypeEnum.WhiteListed, repo.CheckStatus(ModerationEntityTypeEnum.Follower, "@me2@domain.ext"));
|
||||
Assert.AreEqual(ModeratedTypeEnum.None, repo.CheckStatus(ModerationEntityTypeEnum.Follower, "@me2@domain2.ext"));
|
||||
#endregion
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void CheckStatus_Follower_WhiteListing_SubDomain_Test()
|
||||
{
|
||||
#region Stubs
|
||||
var settings = new ModerationSettings
|
||||
{
|
||||
FollowersWhiteListing = "*.domain.ext"
|
||||
};
|
||||
#endregion
|
||||
|
||||
var repo = new ModerationRepository(settings);
|
||||
|
||||
#region Validations
|
||||
Assert.AreEqual(ModeratedTypeEnum.WhiteListed, repo.CheckStatus(ModerationEntityTypeEnum.Follower, "@me@s.domain.ext"));
|
||||
Assert.AreEqual(ModeratedTypeEnum.WhiteListed, repo.CheckStatus(ModerationEntityTypeEnum.Follower, "@me2@s2.domain.ext"));
|
||||
Assert.AreEqual(ModeratedTypeEnum.None, repo.CheckStatus(ModerationEntityTypeEnum.Follower, "@me2@domain.ext"));
|
||||
Assert.AreEqual(ModeratedTypeEnum.None, repo.CheckStatus(ModerationEntityTypeEnum.Follower, "@me2@domain2.ext"));
|
||||
#endregion
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void CheckStatus_TwitterAccount_WhiteListing_Test()
|
||||
{
|
||||
#region Stubs
|
||||
var settings = new ModerationSettings
|
||||
{
|
||||
TwitterAccountsWhiteListing = "handle"
|
||||
};
|
||||
#endregion
|
||||
|
||||
var repo = new ModerationRepository(settings);
|
||||
|
||||
#region Validations
|
||||
Assert.AreEqual(ModeratedTypeEnum.WhiteListed, repo.CheckStatus(ModerationEntityTypeEnum.TwitterAccount, "handle"));
|
||||
Assert.AreEqual(ModeratedTypeEnum.None, repo.CheckStatus(ModerationEntityTypeEnum.TwitterAccount, "handle2"));
|
||||
#endregion
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void CheckStatus_Follower_BlackListing_Test()
|
||||
{
|
||||
#region Stubs
|
||||
var settings = new ModerationSettings
|
||||
{
|
||||
FollowersBlackListing = "@me@domain.ext"
|
||||
};
|
||||
#endregion
|
||||
|
||||
var repo = new ModerationRepository(settings);
|
||||
|
||||
#region Validations
|
||||
Assert.AreEqual(ModeratedTypeEnum.BlackListed, repo.CheckStatus(ModerationEntityTypeEnum.Follower, "@me@domain.ext"));
|
||||
Assert.AreEqual(ModeratedTypeEnum.None, repo.CheckStatus(ModerationEntityTypeEnum.Follower, "@me2@domain.ext"));
|
||||
#endregion
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void CheckStatus_TwitterAccount_BlackListing_Test()
|
||||
{
|
||||
#region Stubs
|
||||
var settings = new ModerationSettings
|
||||
{
|
||||
TwitterAccountsBlackListing = "handle"
|
||||
};
|
||||
#endregion
|
||||
|
||||
var repo = new ModerationRepository(settings);
|
||||
|
||||
#region Validations
|
||||
Assert.AreEqual(ModeratedTypeEnum.BlackListed, repo.CheckStatus(ModerationEntityTypeEnum.TwitterAccount, "handle"));
|
||||
Assert.AreEqual(ModeratedTypeEnum.None, repo.CheckStatus(ModerationEntityTypeEnum.TwitterAccount, "handle2"));
|
||||
#endregion
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void CheckStatus_Follower_BothListing_Test()
|
||||
{
|
||||
#region Stubs
|
||||
var settings = new ModerationSettings
|
||||
{
|
||||
FollowersWhiteListing = "@me@domain.ext",
|
||||
FollowersBlackListing = "@me@domain.ext"
|
||||
};
|
||||
#endregion
|
||||
|
||||
var repo = new ModerationRepository(settings);
|
||||
|
||||
#region Validations
|
||||
Assert.AreEqual(ModeratedTypeEnum.WhiteListed, repo.CheckStatus(ModerationEntityTypeEnum.Follower, "@me@domain.ext"));
|
||||
Assert.AreEqual(ModeratedTypeEnum.None, repo.CheckStatus(ModerationEntityTypeEnum.Follower, "@me2@domain.ext"));
|
||||
#endregion
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void CheckStatus_TwitterAccount_BothListing_Test()
|
||||
{
|
||||
#region Stubs
|
||||
var settings = new ModerationSettings
|
||||
{
|
||||
TwitterAccountsWhiteListing = "handle",
|
||||
TwitterAccountsBlackListing = "handle"
|
||||
};
|
||||
#endregion
|
||||
|
||||
var repo = new ModerationRepository(settings);
|
||||
|
||||
#region Validations
|
||||
Assert.AreEqual(ModeratedTypeEnum.WhiteListed, repo.CheckStatus(ModerationEntityTypeEnum.TwitterAccount, "handle"));
|
||||
Assert.AreEqual(ModeratedTypeEnum.None, repo.CheckStatus(ModerationEntityTypeEnum.TwitterAccount, "handle2"));
|
||||
#endregion
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void CheckStatus_None_Test()
|
||||
{
|
||||
#region Stubs
|
||||
var settings = new ModerationSettings();
|
||||
#endregion
|
||||
|
||||
var repo = new ModerationRepository(settings);
|
||||
|
||||
#region Validations
|
||||
Assert.AreEqual(ModeratedTypeEnum.None, repo.CheckStatus(ModerationEntityTypeEnum.Follower, "@handle@domain.ext"));
|
||||
Assert.AreEqual(ModeratedTypeEnum.None, repo.CheckStatus(ModerationEntityTypeEnum.TwitterAccount, "handle"));
|
||||
#endregion
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
}
|
|
@ -0,0 +1,149 @@
|
|||
using System.Linq;
|
||||
using BirdsiteLive.Domain.Tools;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
|
||||
namespace BirdsiteLive.Domain.Tests.Tools
|
||||
{
|
||||
[TestClass]
|
||||
public class ModerationParserTests
|
||||
{
|
||||
[TestMethod]
|
||||
public void Parse_Simple_Test()
|
||||
{
|
||||
#region Stubs
|
||||
var entry = "test";
|
||||
#endregion
|
||||
|
||||
var result = ModerationParser.Parse(entry);
|
||||
|
||||
#region Validations
|
||||
Assert.AreEqual(1, result.Length);
|
||||
Assert.AreEqual("test", result.First());
|
||||
#endregion
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Parse_Null_Test()
|
||||
{
|
||||
#region Stubs
|
||||
string entry = null;
|
||||
#endregion
|
||||
|
||||
var result = ModerationParser.Parse(entry);
|
||||
|
||||
#region Validations
|
||||
Assert.AreEqual(0, result.Length);
|
||||
#endregion
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Parse_WhiteSpace_Test()
|
||||
{
|
||||
#region Stubs
|
||||
var entry = " ";
|
||||
#endregion
|
||||
|
||||
var result = ModerationParser.Parse(entry);
|
||||
|
||||
#region Validations
|
||||
Assert.AreEqual(0, result.Length);
|
||||
#endregion
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Parse_PipeSeparator_Test()
|
||||
{
|
||||
#region Stubs
|
||||
var entry = "test|test2";
|
||||
#endregion
|
||||
|
||||
var result = ModerationParser.Parse(entry);
|
||||
|
||||
#region Validations
|
||||
Assert.AreEqual(2, result.Length);
|
||||
Assert.AreEqual("test", result[0]);
|
||||
Assert.AreEqual("test2", result[1]);
|
||||
#endregion
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Parse_SemicolonSeparator_Test()
|
||||
{
|
||||
#region Stubs
|
||||
var entry = "test;test2";
|
||||
#endregion
|
||||
|
||||
var result = ModerationParser.Parse(entry);
|
||||
|
||||
#region Validations
|
||||
Assert.AreEqual(2, result.Length);
|
||||
Assert.AreEqual("test", result[0]);
|
||||
Assert.AreEqual("test2", result[1]);
|
||||
#endregion
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Parse_CommaSeparator_Test()
|
||||
{
|
||||
#region Stubs
|
||||
var entry = "test,test2";
|
||||
#endregion
|
||||
|
||||
var result = ModerationParser.Parse(entry);
|
||||
|
||||
#region Validations
|
||||
Assert.AreEqual(2, result.Length);
|
||||
Assert.AreEqual("test", result[0]);
|
||||
Assert.AreEqual("test2", result[1]);
|
||||
#endregion
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Parse_SemicolonSeparator_EmptyEntry_Test()
|
||||
{
|
||||
#region Stubs
|
||||
var entry = "test;test2;";
|
||||
#endregion
|
||||
|
||||
var result = ModerationParser.Parse(entry);
|
||||
|
||||
#region Validations
|
||||
Assert.AreEqual(2, result.Length);
|
||||
Assert.AreEqual("test", result[0]);
|
||||
Assert.AreEqual("test2", result[1]);
|
||||
#endregion
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Parse_SemicolonSeparator_WhiteSpace_Test()
|
||||
{
|
||||
#region Stubs
|
||||
var entry = "test; test2";
|
||||
#endregion
|
||||
|
||||
var result = ModerationParser.Parse(entry);
|
||||
|
||||
#region Validations
|
||||
Assert.AreEqual(2, result.Length);
|
||||
Assert.AreEqual("test", result[0]);
|
||||
Assert.AreEqual("test2", result[1]);
|
||||
#endregion
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Parse_SemicolonSeparator_EmptyEntry_WhiteSpace_Test()
|
||||
{
|
||||
#region Stubs
|
||||
var entry = "test; test2; ";
|
||||
#endregion
|
||||
|
||||
var result = ModerationParser.Parse(entry);
|
||||
|
||||
#region Validations
|
||||
Assert.AreEqual(2, result.Length);
|
||||
Assert.AreEqual("test", result[0]);
|
||||
Assert.AreEqual("test2", result[1]);
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,78 @@
|
|||
using BirdsiteLive.Domain.Repository;
|
||||
using BirdsiteLive.Domain.Tools;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
|
||||
namespace BirdsiteLive.Domain.Tests.Tools
|
||||
{
|
||||
[TestClass]
|
||||
public class ModerationRegexParserTests
|
||||
{
|
||||
[TestMethod]
|
||||
public void Parse_TwitterAccount_Simple_Test()
|
||||
{
|
||||
#region Stubs
|
||||
var pattern = "handle";
|
||||
#endregion
|
||||
|
||||
var regex = ModerationRegexParser.Parse(ModerationEntityTypeEnum.TwitterAccount, pattern);
|
||||
|
||||
#region Validations
|
||||
Assert.IsTrue(regex.IsMatch(pattern));
|
||||
Assert.IsFalse(regex.IsMatch("handles"));
|
||||
Assert.IsFalse(regex.IsMatch("andle"));
|
||||
#endregion
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Parse_Follower_Handle_Test()
|
||||
{
|
||||
#region Stubs
|
||||
var pattern = "@handle@domain.ext";
|
||||
#endregion
|
||||
|
||||
var regex = ModerationRegexParser.Parse(ModerationEntityTypeEnum.Follower, pattern);
|
||||
|
||||
#region Validations
|
||||
Assert.IsTrue(regex.IsMatch(pattern));
|
||||
Assert.IsFalse(regex.IsMatch("@handle2@domain.ext"));
|
||||
Assert.IsFalse(regex.IsMatch("@handle@seb.domain.ext"));
|
||||
#endregion
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Parse_Follower_Domain_Test()
|
||||
{
|
||||
#region Stubs
|
||||
var pattern = "domain.ext";
|
||||
#endregion
|
||||
|
||||
var regex = ModerationRegexParser.Parse(ModerationEntityTypeEnum.Follower, pattern);
|
||||
|
||||
#region Validations
|
||||
Assert.IsTrue(regex.IsMatch("@handle@domain.ext"));
|
||||
Assert.IsTrue(regex.IsMatch("@handle2@domain.ext"));
|
||||
Assert.IsFalse(regex.IsMatch("@handle2@domain2.ext"));
|
||||
Assert.IsFalse(regex.IsMatch("@handle@seb.domain.ext"));
|
||||
#endregion
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Parse_Follower_SubDomains_Test()
|
||||
{
|
||||
#region Stubs
|
||||
var pattern = "*.domain.ext";
|
||||
#endregion
|
||||
|
||||
var regex = ModerationRegexParser.Parse(ModerationEntityTypeEnum.Follower, pattern);
|
||||
|
||||
#region Validations
|
||||
Assert.IsTrue(regex.IsMatch("@handle2@sub.domain.ext"));
|
||||
Assert.IsTrue(regex.IsMatch("@han@sub3.domain.ext"));
|
||||
Assert.IsFalse(regex.IsMatch("@handle@domain.ext"));
|
||||
Assert.IsFalse(regex.IsMatch("@handle2@.domain.ext"));
|
||||
Assert.IsFalse(regex.IsMatch("@handle2@domain2.ext"));
|
||||
Assert.IsFalse(regex.IsMatch("@handle@seb.domain2.ext"));
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,114 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using BirdsiteLive.ActivityPub;
|
||||
using BirdsiteLive.Common.Settings;
|
||||
using BirdsiteLive.DAL.Contracts;
|
||||
using BirdsiteLive.DAL.Models;
|
||||
using BirdsiteLive.Domain;
|
||||
using BirdsiteLive.Moderation.Actions;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using Moq;
|
||||
|
||||
namespace BirdsiteLive.Moderation.Tests.Actions
|
||||
{
|
||||
[TestClass]
|
||||
public class RejectAllFollowingsActionTests
|
||||
{
|
||||
[TestMethod]
|
||||
public async Task ProcessAsync()
|
||||
{
|
||||
#region Stubs
|
||||
var follower = new Follower
|
||||
{
|
||||
Followings = new List<int>
|
||||
{
|
||||
24
|
||||
},
|
||||
Host = "host"
|
||||
};
|
||||
|
||||
var settings = new InstanceSettings
|
||||
{
|
||||
Domain = "domain"
|
||||
};
|
||||
#endregion
|
||||
|
||||
#region Mocks
|
||||
var twitterUserDalMock = new Mock<ITwitterUserDal>(MockBehavior.Strict);
|
||||
twitterUserDalMock
|
||||
.Setup(x => x.GetTwitterUserAsync(
|
||||
It.Is<int>(y => y == 24)))
|
||||
.ReturnsAsync(new SyncTwitterUser
|
||||
{
|
||||
Id = 24,
|
||||
Acct = "acct"
|
||||
});
|
||||
|
||||
var userServiceMock = new Mock<IUserService>(MockBehavior.Strict);
|
||||
userServiceMock
|
||||
.Setup(x => x.SendRejectFollowAsync(
|
||||
It.Is<ActivityFollow>(y => y.type == "Follow"),
|
||||
It.IsNotNull<string>()
|
||||
))
|
||||
.ReturnsAsync(true);
|
||||
#endregion
|
||||
|
||||
var action = new RejectAllFollowingsAction(twitterUserDalMock.Object, userServiceMock.Object, settings);
|
||||
await action.ProcessAsync(follower);
|
||||
|
||||
#region Validations
|
||||
twitterUserDalMock.VerifyAll();
|
||||
userServiceMock.VerifyAll();
|
||||
#endregion
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public async Task ProcessAsync_Exception()
|
||||
{
|
||||
#region Stubs
|
||||
var follower = new Follower
|
||||
{
|
||||
Followings = new List<int>
|
||||
{
|
||||
24
|
||||
},
|
||||
Host = "host"
|
||||
};
|
||||
|
||||
var settings = new InstanceSettings
|
||||
{
|
||||
Domain = "domain"
|
||||
};
|
||||
#endregion
|
||||
|
||||
#region Mocks
|
||||
var twitterUserDalMock = new Mock<ITwitterUserDal>(MockBehavior.Strict);
|
||||
twitterUserDalMock
|
||||
.Setup(x => x.GetTwitterUserAsync(
|
||||
It.Is<int>(y => y == 24)))
|
||||
.ReturnsAsync(new SyncTwitterUser
|
||||
{
|
||||
Id = 24,
|
||||
Acct = "acct"
|
||||
});
|
||||
|
||||
var userServiceMock = new Mock<IUserService>(MockBehavior.Strict);
|
||||
userServiceMock
|
||||
.Setup(x => x.SendRejectFollowAsync(
|
||||
It.Is<ActivityFollow>(y => y.type == "Follow"),
|
||||
It.IsNotNull<string>()
|
||||
))
|
||||
.Throws(new Exception());
|
||||
#endregion
|
||||
|
||||
var action = new RejectAllFollowingsAction(twitterUserDalMock.Object, userServiceMock.Object, settings);
|
||||
await action.ProcessAsync(follower);
|
||||
|
||||
#region Validations
|
||||
twitterUserDalMock.VerifyAll();
|
||||
userServiceMock.VerifyAll();
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,103 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using BirdsiteLive.ActivityPub;
|
||||
using BirdsiteLive.Common.Settings;
|
||||
using BirdsiteLive.DAL.Models;
|
||||
using BirdsiteLive.Domain;
|
||||
using BirdsiteLive.Moderation.Actions;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using Moq;
|
||||
|
||||
namespace BirdsiteLive.Moderation.Tests.Actions
|
||||
{
|
||||
[TestClass]
|
||||
public class RejectFollowingActionTests
|
||||
{
|
||||
[TestMethod]
|
||||
public async Task ProcessAsync()
|
||||
{
|
||||
#region Stubs
|
||||
var follower = new Follower
|
||||
{
|
||||
Followings = new List<int>
|
||||
{
|
||||
24
|
||||
},
|
||||
Host = "host"
|
||||
};
|
||||
|
||||
var settings = new InstanceSettings
|
||||
{
|
||||
Domain = "domain"
|
||||
};
|
||||
|
||||
var twitterUser = new SyncTwitterUser
|
||||
{
|
||||
Id = 24,
|
||||
Acct = "acct"
|
||||
};
|
||||
#endregion
|
||||
|
||||
#region Mocks
|
||||
var userServiceMock = new Mock<IUserService>(MockBehavior.Strict);
|
||||
userServiceMock
|
||||
.Setup(x => x.SendRejectFollowAsync(
|
||||
It.Is<ActivityFollow>(y => y.type == "Follow"),
|
||||
It.IsNotNull<string>()
|
||||
))
|
||||
.ReturnsAsync(true);
|
||||
#endregion
|
||||
|
||||
var action = new RejectFollowingAction(userServiceMock.Object, settings);
|
||||
await action.ProcessAsync(follower, twitterUser);
|
||||
|
||||
#region Validations
|
||||
userServiceMock.VerifyAll();
|
||||
#endregion
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public async Task ProcessAsync_Exception()
|
||||
{
|
||||
#region Stubs
|
||||
var follower = new Follower
|
||||
{
|
||||
Followings = new List<int>
|
||||
{
|
||||
24
|
||||
},
|
||||
Host = "host"
|
||||
};
|
||||
|
||||
var settings = new InstanceSettings
|
||||
{
|
||||
Domain = "domain"
|
||||
};
|
||||
|
||||
var twitterUser = new SyncTwitterUser
|
||||
{
|
||||
Id = 24,
|
||||
Acct = "acct"
|
||||
};
|
||||
#endregion
|
||||
|
||||
#region Mocks
|
||||
var userServiceMock = new Mock<IUserService>(MockBehavior.Strict);
|
||||
userServiceMock
|
||||
.Setup(x => x.SendRejectFollowAsync(
|
||||
It.Is<ActivityFollow>(y => y.type == "Follow"),
|
||||
It.IsNotNull<string>()
|
||||
))
|
||||
.Throws(new Exception());
|
||||
#endregion
|
||||
|
||||
var action = new RejectFollowingAction(userServiceMock.Object, settings);
|
||||
await action.ProcessAsync(follower, twitterUser);
|
||||
|
||||
#region Validations
|
||||
userServiceMock.VerifyAll();
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,111 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using BirdsiteLive.DAL.Contracts;
|
||||
using BirdsiteLive.DAL.Models;
|
||||
using BirdsiteLive.Moderation.Actions;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using Moq;
|
||||
|
||||
namespace BirdsiteLive.Moderation.Tests.Actions
|
||||
{
|
||||
[TestClass]
|
||||
public class RemoveFollowerActionTests
|
||||
{
|
||||
[TestMethod]
|
||||
public async Task ProcessAsync_NoMoreFollowings()
|
||||
{
|
||||
#region Stubs
|
||||
var follower = new Follower
|
||||
{
|
||||
Id = 12,
|
||||
Followings = new List<int> { 1 }
|
||||
};
|
||||
#endregion
|
||||
|
||||
#region Mocks
|
||||
var rejectAllFollowingsActionMock = new Mock<IRejectAllFollowingsAction>(MockBehavior.Strict);
|
||||
rejectAllFollowingsActionMock
|
||||
.Setup(x => x.ProcessAsync(
|
||||
It.Is<Follower>(y => y.Id == follower.Id)))
|
||||
.Returns(Task.CompletedTask);
|
||||
|
||||
var followersDalMock = new Mock<IFollowersDal>(MockBehavior.Strict);
|
||||
followersDalMock
|
||||
.Setup(x => x.GetFollowersAsync(
|
||||
It.Is<int>(y => y == 1)))
|
||||
.ReturnsAsync(new[] {follower});
|
||||
|
||||
followersDalMock
|
||||
.Setup(x => x.DeleteFollowerAsync(
|
||||
It.Is<int>(y => y == 12)))
|
||||
.Returns(Task.CompletedTask);
|
||||
|
||||
var twitterUserDalMock = new Mock<ITwitterUserDal>(MockBehavior.Strict);
|
||||
twitterUserDalMock
|
||||
.Setup(x => x.DeleteTwitterUserAsync(
|
||||
It.Is<int>(y => y == 1)))
|
||||
.Returns(Task.CompletedTask);
|
||||
#endregion
|
||||
|
||||
var action = new RemoveFollowerAction(followersDalMock.Object, twitterUserDalMock.Object, rejectAllFollowingsActionMock.Object);
|
||||
await action.ProcessAsync(follower);
|
||||
|
||||
#region Validations
|
||||
followersDalMock.VerifyAll();
|
||||
twitterUserDalMock.VerifyAll();
|
||||
rejectAllFollowingsActionMock.VerifyAll();
|
||||
#endregion
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public async Task ProcessAsync_HaveFollowings()
|
||||
{
|
||||
#region Stubs
|
||||
var follower = new Follower
|
||||
{
|
||||
Id = 12,
|
||||
Followings = new List<int> { 1 }
|
||||
};
|
||||
|
||||
var followers = new List<Follower>
|
||||
{
|
||||
follower,
|
||||
new Follower
|
||||
{
|
||||
Id = 11
|
||||
}
|
||||
};
|
||||
#endregion
|
||||
|
||||
#region Mocks
|
||||
var rejectAllFollowingsActionMock = new Mock<IRejectAllFollowingsAction>(MockBehavior.Strict);
|
||||
rejectAllFollowingsActionMock
|
||||
.Setup(x => x.ProcessAsync(
|
||||
It.Is<Follower>(y => y.Id == follower.Id)))
|
||||
.Returns(Task.CompletedTask);
|
||||
|
||||
var followersDalMock = new Mock<IFollowersDal>(MockBehavior.Strict);
|
||||
followersDalMock
|
||||
.Setup(x => x.GetFollowersAsync(
|
||||
It.Is<int>(y => y == 1)))
|
||||
.ReturnsAsync(followers.ToArray());
|
||||
|
||||
followersDalMock
|
||||
.Setup(x => x.DeleteFollowerAsync(
|
||||
It.Is<int>(y => y == 12)))
|
||||
.Returns(Task.CompletedTask);
|
||||
|
||||
var twitterUserDalMock = new Mock<ITwitterUserDal>(MockBehavior.Strict);
|
||||
#endregion
|
||||
|
||||
var action = new RemoveFollowerAction(followersDalMock.Object, twitterUserDalMock.Object, rejectAllFollowingsActionMock.Object);
|
||||
await action.ProcessAsync(follower);
|
||||
|
||||
#region Validations
|
||||
followersDalMock.VerifyAll();
|
||||
twitterUserDalMock.VerifyAll();
|
||||
rejectAllFollowingsActionMock.VerifyAll();
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,131 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using BirdsiteLive.DAL.Contracts;
|
||||
using BirdsiteLive.DAL.Models;
|
||||
using BirdsiteLive.Moderation.Actions;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using Moq;
|
||||
|
||||
namespace BirdsiteLive.Moderation.Tests.Actions
|
||||
{
|
||||
[TestClass]
|
||||
public class RemoveTwitterAccountActionTests
|
||||
{
|
||||
[TestMethod]
|
||||
public async Task ProcessAsync_RemoveFollower()
|
||||
{
|
||||
#region Stubs
|
||||
var twitter = new SyncTwitterUser
|
||||
{
|
||||
Id = 24,
|
||||
Acct = "my-acct"
|
||||
};
|
||||
|
||||
var followers = new List<Follower>
|
||||
{
|
||||
new Follower
|
||||
{
|
||||
Id = 48,
|
||||
Followings = new List<int>{ 24 },
|
||||
FollowingsSyncStatus = new Dictionary<int, long> { { 24, 1024 } }
|
||||
}
|
||||
};
|
||||
#endregion
|
||||
|
||||
#region Mocks
|
||||
var followersDalMock = new Mock<IFollowersDal>(MockBehavior.Strict);
|
||||
followersDalMock
|
||||
.Setup(x => x.GetFollowersAsync(
|
||||
It.Is<int>(y => y == 24)))
|
||||
.ReturnsAsync(followers.ToArray());
|
||||
|
||||
followersDalMock
|
||||
.Setup(x => x.DeleteFollowerAsync(
|
||||
It.Is<int>(y => y == 48)))
|
||||
.Returns(Task.CompletedTask);
|
||||
|
||||
var twitterUserDalMock = new Mock<ITwitterUserDal>(MockBehavior.Strict);
|
||||
twitterUserDalMock
|
||||
.Setup(x => x.DeleteTwitterUserAsync(
|
||||
It.Is<int>(y => y == 24)))
|
||||
.Returns(Task.CompletedTask);
|
||||
|
||||
var rejectFollowingActionMock = new Mock<IRejectFollowingAction>(MockBehavior.Strict);
|
||||
rejectFollowingActionMock
|
||||
.Setup(x => x.ProcessAsync(
|
||||
It.Is<Follower>(y => y.Id == 48),
|
||||
It.Is<SyncTwitterUser>(y => y.Acct == twitter.Acct)))
|
||||
.Returns(Task.CompletedTask);
|
||||
#endregion
|
||||
|
||||
var action = new RemoveTwitterAccountAction(followersDalMock.Object, twitterUserDalMock.Object, rejectFollowingActionMock.Object);
|
||||
await action.ProcessAsync(twitter);
|
||||
|
||||
#region Validations
|
||||
followersDalMock.VerifyAll();
|
||||
twitterUserDalMock.VerifyAll();
|
||||
rejectFollowingActionMock.VerifyAll();
|
||||
#endregion
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public async Task ProcessAsync_KeepFollower()
|
||||
{
|
||||
#region Stubs
|
||||
var twitter = new SyncTwitterUser
|
||||
{
|
||||
Id = 24,
|
||||
Acct = "my-acct"
|
||||
};
|
||||
|
||||
var followers = new List<Follower>
|
||||
{
|
||||
new Follower
|
||||
{
|
||||
Id = 48,
|
||||
Followings = new List<int>{ 24, 36 },
|
||||
FollowingsSyncStatus = new Dictionary<int, long> { { 24, 1024 }, { 36, 24 } }
|
||||
}
|
||||
};
|
||||
#endregion
|
||||
|
||||
#region Mocks
|
||||
var followersDalMock = new Mock<IFollowersDal>(MockBehavior.Strict);
|
||||
followersDalMock
|
||||
.Setup(x => x.GetFollowersAsync(
|
||||
It.Is<int>(y => y == 24)))
|
||||
.ReturnsAsync(followers.ToArray());
|
||||
|
||||
followersDalMock
|
||||
.Setup(x => x.UpdateFollowerAsync(
|
||||
It.Is<Follower>(y => y.Id == 48
|
||||
&& y.Followings.Count == 1
|
||||
&& y.FollowingsSyncStatus.Count == 1
|
||||
)))
|
||||
.Returns(Task.CompletedTask);
|
||||
|
||||
var twitterUserDalMock = new Mock<ITwitterUserDal>(MockBehavior.Strict);
|
||||
twitterUserDalMock
|
||||
.Setup(x => x.DeleteTwitterUserAsync(
|
||||
It.Is<int>(y => y == 24)))
|
||||
.Returns(Task.CompletedTask);
|
||||
|
||||
var rejectFollowingActionMock = new Mock<IRejectFollowingAction>(MockBehavior.Strict);
|
||||
rejectFollowingActionMock
|
||||
.Setup(x => x.ProcessAsync(
|
||||
It.Is<Follower>(y => y.Id == 48),
|
||||
It.Is<SyncTwitterUser>(y => y.Acct == twitter.Acct)))
|
||||
.Returns(Task.CompletedTask);
|
||||
#endregion
|
||||
|
||||
var action = new RemoveTwitterAccountAction(followersDalMock.Object, twitterUserDalMock.Object, rejectFollowingActionMock.Object);
|
||||
await action.ProcessAsync(twitter);
|
||||
|
||||
#region Validations
|
||||
followersDalMock.VerifyAll();
|
||||
twitterUserDalMock.VerifyAll();
|
||||
rejectFollowingActionMock.VerifyAll();
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netcoreapp3.1</TargetFramework>
|
||||
|
||||
<IsPackable>false</IsPackable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.7.1" />
|
||||
<PackageReference Include="Moq" Version="4.14.5" />
|
||||
<PackageReference Include="MSTest.TestAdapter" Version="2.1.1" />
|
||||
<PackageReference Include="MSTest.TestFramework" Version="2.1.1" />
|
||||
<PackageReference Include="coverlet.collector" Version="1.3.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\BirdsiteLive.Moderation\BirdsiteLive.Moderation.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
|
@ -0,0 +1,106 @@
|
|||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using BirdsiteLive.Domain.Repository;
|
||||
using BirdsiteLive.Moderation.Processors;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using Moq;
|
||||
|
||||
namespace BirdsiteLive.Moderation.Tests
|
||||
{
|
||||
[TestClass]
|
||||
public class ModerationPipelineTests
|
||||
{
|
||||
[TestMethod]
|
||||
public async Task ApplyModerationSettingsAsync_None()
|
||||
{
|
||||
#region Mocks
|
||||
var moderationRepositoryMock = new Mock<IModerationRepository>(MockBehavior.Strict);
|
||||
moderationRepositoryMock
|
||||
.Setup(x => x.GetModerationType(ModerationEntityTypeEnum.Follower))
|
||||
.Returns(ModerationTypeEnum.None);
|
||||
moderationRepositoryMock
|
||||
.Setup(x => x.GetModerationType(ModerationEntityTypeEnum.TwitterAccount))
|
||||
.Returns(ModerationTypeEnum.None);
|
||||
|
||||
var followerModerationProcessorMock = new Mock<IFollowerModerationProcessor>(MockBehavior.Strict);
|
||||
var twitterAccountModerationProcessorMock = new Mock<ITwitterAccountModerationProcessor>(MockBehavior.Strict);
|
||||
var loggerMock = new Mock<ILogger<ModerationPipeline>>(MockBehavior.Strict);
|
||||
#endregion
|
||||
|
||||
var pipeline = new ModerationPipeline(moderationRepositoryMock.Object, followerModerationProcessorMock.Object, twitterAccountModerationProcessorMock.Object, loggerMock.Object);
|
||||
await pipeline.ApplyModerationSettingsAsync();
|
||||
|
||||
#region Validations
|
||||
moderationRepositoryMock.VerifyAll();
|
||||
followerModerationProcessorMock.VerifyAll();
|
||||
twitterAccountModerationProcessorMock.VerifyAll();
|
||||
loggerMock.VerifyAll();
|
||||
#endregion
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public async Task ApplyModerationSettingsAsync_Process()
|
||||
{
|
||||
#region Mocks
|
||||
var moderationRepositoryMock = new Mock<IModerationRepository>(MockBehavior.Strict);
|
||||
moderationRepositoryMock
|
||||
.Setup(x => x.GetModerationType(ModerationEntityTypeEnum.Follower))
|
||||
.Returns(ModerationTypeEnum.WhiteListing);
|
||||
moderationRepositoryMock
|
||||
.Setup(x => x.GetModerationType(ModerationEntityTypeEnum.TwitterAccount))
|
||||
.Returns(ModerationTypeEnum.BlackListing);
|
||||
|
||||
var followerModerationProcessorMock = new Mock<IFollowerModerationProcessor>(MockBehavior.Strict);
|
||||
followerModerationProcessorMock
|
||||
.Setup(x => x.ProcessAsync(
|
||||
It.Is<ModerationTypeEnum>(y => y == ModerationTypeEnum.WhiteListing)))
|
||||
.Returns(Task.CompletedTask);
|
||||
|
||||
var twitterAccountModerationProcessorMock = new Mock<ITwitterAccountModerationProcessor>(MockBehavior.Strict);
|
||||
twitterAccountModerationProcessorMock
|
||||
.Setup(x => x.ProcessAsync(
|
||||
It.Is<ModerationTypeEnum>(y => y == ModerationTypeEnum.BlackListing)))
|
||||
.Returns(Task.CompletedTask);
|
||||
|
||||
var loggerMock = new Mock<ILogger<ModerationPipeline>>(MockBehavior.Strict);
|
||||
#endregion
|
||||
|
||||
var pipeline = new ModerationPipeline(moderationRepositoryMock.Object, followerModerationProcessorMock.Object, twitterAccountModerationProcessorMock.Object, loggerMock.Object);
|
||||
await pipeline.ApplyModerationSettingsAsync();
|
||||
|
||||
#region Validations
|
||||
moderationRepositoryMock.VerifyAll();
|
||||
followerModerationProcessorMock.VerifyAll();
|
||||
twitterAccountModerationProcessorMock.VerifyAll();
|
||||
loggerMock.VerifyAll();
|
||||
#endregion
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public async Task ApplyModerationSettingsAsync_Exception()
|
||||
{
|
||||
#region Mocks
|
||||
var moderationRepositoryMock = new Mock<IModerationRepository>(MockBehavior.Strict);
|
||||
moderationRepositoryMock
|
||||
.Setup(x => x.GetModerationType(ModerationEntityTypeEnum.Follower))
|
||||
.Throws(new Exception());
|
||||
|
||||
var followerModerationProcessorMock = new Mock<IFollowerModerationProcessor>(MockBehavior.Strict);
|
||||
var twitterAccountModerationProcessorMock = new Mock<ITwitterAccountModerationProcessor>(MockBehavior.Strict);
|
||||
|
||||
var loggerMock = new Mock<ILogger<ModerationPipeline>>();
|
||||
#endregion
|
||||
|
||||
var pipeline = new ModerationPipeline(moderationRepositoryMock.Object, followerModerationProcessorMock.Object, twitterAccountModerationProcessorMock.Object, loggerMock.Object);
|
||||
await pipeline.ApplyModerationSettingsAsync();
|
||||
|
||||
#region Validations
|
||||
moderationRepositoryMock.VerifyAll();
|
||||
followerModerationProcessorMock.VerifyAll();
|
||||
twitterAccountModerationProcessorMock.VerifyAll();
|
||||
loggerMock.VerifyAll();
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,204 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using BirdsiteLive.DAL.Contracts;
|
||||
using BirdsiteLive.DAL.Models;
|
||||
using BirdsiteLive.Domain.Repository;
|
||||
using BirdsiteLive.Moderation.Actions;
|
||||
using BirdsiteLive.Moderation.Processors;
|
||||
using Castle.DynamicProxy.Generators.Emitters;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using Moq;
|
||||
|
||||
namespace BirdsiteLive.Moderation.Tests.Processors
|
||||
{
|
||||
[TestClass]
|
||||
public class FollowerModerationProcessorTests
|
||||
{
|
||||
[TestMethod]
|
||||
public async Task ProcessAsync_None()
|
||||
{
|
||||
#region Mocks
|
||||
var followersDalMock = new Mock<IFollowersDal>(MockBehavior.Strict);
|
||||
var moderationRepositoryMock = new Mock<IModerationRepository>(MockBehavior.Strict);
|
||||
var removeFollowerActionMock = new Mock<IRemoveFollowerAction>(MockBehavior.Strict);
|
||||
#endregion
|
||||
|
||||
var processor = new FollowerModerationProcessor(followersDalMock.Object, moderationRepositoryMock.Object, removeFollowerActionMock.Object);
|
||||
await processor.ProcessAsync(ModerationTypeEnum.None);
|
||||
|
||||
#region Validations
|
||||
followersDalMock.VerifyAll();
|
||||
moderationRepositoryMock.VerifyAll();
|
||||
removeFollowerActionMock.VerifyAll();
|
||||
#endregion
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public async Task ProcessAsync_WhiteListing_WhiteListed()
|
||||
{
|
||||
#region Stubs
|
||||
var allFollowers = new List<Follower>
|
||||
{
|
||||
new Follower
|
||||
{
|
||||
Acct = "acct",
|
||||
Host = "host"
|
||||
}
|
||||
};
|
||||
#endregion
|
||||
|
||||
#region Mocks
|
||||
var followersDalMock = new Mock<IFollowersDal>(MockBehavior.Strict);
|
||||
followersDalMock
|
||||
.Setup(x => x.GetAllFollowersAsync())
|
||||
.ReturnsAsync(allFollowers.ToArray());
|
||||
|
||||
var moderationRepositoryMock = new Mock<IModerationRepository>(MockBehavior.Strict);
|
||||
moderationRepositoryMock
|
||||
.Setup(x => x.CheckStatus(
|
||||
It.Is<ModerationEntityTypeEnum>(y => y == ModerationEntityTypeEnum.Follower),
|
||||
It.Is<string>(y => y == "@acct@host")))
|
||||
.Returns(ModeratedTypeEnum.WhiteListed);
|
||||
|
||||
var removeFollowerActionMock = new Mock<IRemoveFollowerAction>(MockBehavior.Strict);
|
||||
#endregion
|
||||
|
||||
var processor = new FollowerModerationProcessor(followersDalMock.Object, moderationRepositoryMock.Object, removeFollowerActionMock.Object);
|
||||
await processor.ProcessAsync(ModerationTypeEnum.WhiteListing);
|
||||
|
||||
#region Validations
|
||||
followersDalMock.VerifyAll();
|
||||
moderationRepositoryMock.VerifyAll();
|
||||
removeFollowerActionMock.VerifyAll();
|
||||
#endregion
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public async Task ProcessAsync_WhiteListing_NotWhiteListed()
|
||||
{
|
||||
#region Stubs
|
||||
var allFollowers = new List<Follower>
|
||||
{
|
||||
new Follower
|
||||
{
|
||||
Acct = "acct",
|
||||
Host = "host"
|
||||
}
|
||||
};
|
||||
#endregion
|
||||
|
||||
#region Mocks
|
||||
var followersDalMock = new Mock<IFollowersDal>(MockBehavior.Strict);
|
||||
followersDalMock
|
||||
.Setup(x => x.GetAllFollowersAsync())
|
||||
.ReturnsAsync(allFollowers.ToArray());
|
||||
|
||||
var moderationRepositoryMock = new Mock<IModerationRepository>(MockBehavior.Strict);
|
||||
moderationRepositoryMock
|
||||
.Setup(x => x.CheckStatus(
|
||||
It.Is<ModerationEntityTypeEnum>(y => y == ModerationEntityTypeEnum.Follower),
|
||||
It.Is<string>(y => y == "@acct@host")))
|
||||
.Returns(ModeratedTypeEnum.None);
|
||||
|
||||
var removeFollowerActionMock = new Mock<IRemoveFollowerAction>(MockBehavior.Strict);
|
||||
removeFollowerActionMock
|
||||
.Setup(x => x.ProcessAsync(
|
||||
It.Is<Follower>(y => y.Acct == "acct")))
|
||||
.Returns(Task.CompletedTask);
|
||||
#endregion
|
||||
|
||||
var processor = new FollowerModerationProcessor(followersDalMock.Object, moderationRepositoryMock.Object, removeFollowerActionMock.Object);
|
||||
await processor.ProcessAsync(ModerationTypeEnum.WhiteListing);
|
||||
|
||||
#region Validations
|
||||
followersDalMock.VerifyAll();
|
||||
moderationRepositoryMock.VerifyAll();
|
||||
removeFollowerActionMock.VerifyAll();
|
||||
#endregion
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public async Task ProcessAsync_BlackListing_BlackListed()
|
||||
{
|
||||
#region Stubs
|
||||
var allFollowers = new List<Follower>
|
||||
{
|
||||
new Follower
|
||||
{
|
||||
Acct = "acct",
|
||||
Host = "host"
|
||||
}
|
||||
};
|
||||
#endregion
|
||||
|
||||
#region Mocks
|
||||
var followersDalMock = new Mock<IFollowersDal>(MockBehavior.Strict);
|
||||
followersDalMock
|
||||
.Setup(x => x.GetAllFollowersAsync())
|
||||
.ReturnsAsync(allFollowers.ToArray());
|
||||
|
||||
var moderationRepositoryMock = new Mock<IModerationRepository>(MockBehavior.Strict);
|
||||
moderationRepositoryMock
|
||||
.Setup(x => x.CheckStatus(
|
||||
It.Is<ModerationEntityTypeEnum>(y => y == ModerationEntityTypeEnum.Follower),
|
||||
It.Is<string>(y => y == "@acct@host")))
|
||||
.Returns(ModeratedTypeEnum.BlackListed);
|
||||
|
||||
var removeFollowerActionMock = new Mock<IRemoveFollowerAction>(MockBehavior.Strict);
|
||||
removeFollowerActionMock
|
||||
.Setup(x => x.ProcessAsync(
|
||||
It.Is<Follower>(y => y.Acct == "acct")))
|
||||
.Returns(Task.CompletedTask);
|
||||
#endregion
|
||||
|
||||
var processor = new FollowerModerationProcessor(followersDalMock.Object, moderationRepositoryMock.Object, removeFollowerActionMock.Object);
|
||||
await processor.ProcessAsync(ModerationTypeEnum.BlackListing);
|
||||
|
||||
#region Validations
|
||||
followersDalMock.VerifyAll();
|
||||
moderationRepositoryMock.VerifyAll();
|
||||
removeFollowerActionMock.VerifyAll();
|
||||
#endregion
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public async Task ProcessAsync_BlackListing_NotBlackListed()
|
||||
{
|
||||
#region Stubs
|
||||
var allFollowers = new List<Follower>
|
||||
{
|
||||
new Follower
|
||||
{
|
||||
Acct = "acct",
|
||||
Host = "host"
|
||||
}
|
||||
};
|
||||
#endregion
|
||||
|
||||
#region Mocks
|
||||
var followersDalMock = new Mock<IFollowersDal>(MockBehavior.Strict);
|
||||
followersDalMock
|
||||
.Setup(x => x.GetAllFollowersAsync())
|
||||
.ReturnsAsync(allFollowers.ToArray());
|
||||
|
||||
var moderationRepositoryMock = new Mock<IModerationRepository>(MockBehavior.Strict);
|
||||
moderationRepositoryMock
|
||||
.Setup(x => x.CheckStatus(
|
||||
It.Is<ModerationEntityTypeEnum>(y => y == ModerationEntityTypeEnum.Follower),
|
||||
It.Is<string>(y => y == "@acct@host")))
|
||||
.Returns(ModeratedTypeEnum.None);
|
||||
|
||||
var removeFollowerActionMock = new Mock<IRemoveFollowerAction>(MockBehavior.Strict);
|
||||
#endregion
|
||||
|
||||
var processor = new FollowerModerationProcessor(followersDalMock.Object, moderationRepositoryMock.Object, removeFollowerActionMock.Object);
|
||||
await processor.ProcessAsync(ModerationTypeEnum.BlackListing);
|
||||
|
||||
#region Validations
|
||||
followersDalMock.VerifyAll();
|
||||
moderationRepositoryMock.VerifyAll();
|
||||
removeFollowerActionMock.VerifyAll();
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,199 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using BirdsiteLive.DAL.Contracts;
|
||||
using BirdsiteLive.DAL.Models;
|
||||
using BirdsiteLive.Domain.Repository;
|
||||
using BirdsiteLive.Moderation.Actions;
|
||||
using BirdsiteLive.Moderation.Processors;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using Moq;
|
||||
|
||||
namespace BirdsiteLive.Moderation.Tests.Processors
|
||||
{
|
||||
[TestClass]
|
||||
public class TwitterAccountModerationProcessorTests
|
||||
{
|
||||
[TestMethod]
|
||||
public async Task ProcessAsync_None()
|
||||
{
|
||||
#region Mocks
|
||||
var twitterUserDalMock = new Mock<ITwitterUserDal>(MockBehavior.Strict);
|
||||
var moderationRepositoryMock = new Mock<IModerationRepository>(MockBehavior.Strict);
|
||||
var removeTwitterAccountActionMock = new Mock<IRemoveTwitterAccountAction>(MockBehavior.Strict);
|
||||
#endregion
|
||||
|
||||
var processor = new TwitterAccountModerationProcessor(twitterUserDalMock.Object, moderationRepositoryMock.Object, removeTwitterAccountActionMock.Object);
|
||||
await processor.ProcessAsync(ModerationTypeEnum.None);
|
||||
|
||||
#region Validations
|
||||
twitterUserDalMock.VerifyAll();
|
||||
moderationRepositoryMock.VerifyAll();
|
||||
removeTwitterAccountActionMock.VerifyAll();
|
||||
#endregion
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public async Task ProcessAsync_WhiteListing_WhiteListed()
|
||||
{
|
||||
#region Stubs
|
||||
var allUsers = new List<SyncTwitterUser>
|
||||
{
|
||||
new SyncTwitterUser
|
||||
{
|
||||
Acct = "acct"
|
||||
}
|
||||
};
|
||||
#endregion
|
||||
|
||||
#region Mocks
|
||||
var twitterUserDalMock = new Mock<ITwitterUserDal>(MockBehavior.Strict);
|
||||
twitterUserDalMock
|
||||
.Setup(x => x.GetAllTwitterUsersAsync())
|
||||
.ReturnsAsync(allUsers.ToArray());
|
||||
|
||||
var moderationRepositoryMock = new Mock<IModerationRepository>(MockBehavior.Strict);
|
||||
moderationRepositoryMock
|
||||
.Setup(x => x.CheckStatus(
|
||||
It.Is<ModerationEntityTypeEnum>(y => y == ModerationEntityTypeEnum.TwitterAccount),
|
||||
It.Is<string>(y => y == "acct")))
|
||||
.Returns(ModeratedTypeEnum.WhiteListed);
|
||||
|
||||
var removeTwitterAccountActionMock = new Mock<IRemoveTwitterAccountAction>(MockBehavior.Strict);
|
||||
#endregion
|
||||
|
||||
var processor = new TwitterAccountModerationProcessor(twitterUserDalMock.Object, moderationRepositoryMock.Object, removeTwitterAccountActionMock.Object);
|
||||
await processor.ProcessAsync(ModerationTypeEnum.WhiteListing);
|
||||
|
||||
#region Validations
|
||||
twitterUserDalMock.VerifyAll();
|
||||
moderationRepositoryMock.VerifyAll();
|
||||
removeTwitterAccountActionMock.VerifyAll();
|
||||
#endregion
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public async Task ProcessAsync_WhiteListing_NotWhiteListed()
|
||||
{
|
||||
#region Stubs
|
||||
var allUsers = new List<SyncTwitterUser>
|
||||
{
|
||||
new SyncTwitterUser
|
||||
{
|
||||
Acct = "acct"
|
||||
}
|
||||
};
|
||||
#endregion
|
||||
|
||||
#region Mocks
|
||||
var twitterUserDalMock = new Mock<ITwitterUserDal>(MockBehavior.Strict);
|
||||
twitterUserDalMock
|
||||
.Setup(x => x.GetAllTwitterUsersAsync())
|
||||
.ReturnsAsync(allUsers.ToArray());
|
||||
|
||||
var moderationRepositoryMock = new Mock<IModerationRepository>(MockBehavior.Strict);
|
||||
moderationRepositoryMock
|
||||
.Setup(x => x.CheckStatus(
|
||||
It.Is<ModerationEntityTypeEnum>(y => y == ModerationEntityTypeEnum.TwitterAccount),
|
||||
It.Is<string>(y => y == "acct")))
|
||||
.Returns(ModeratedTypeEnum.None);
|
||||
|
||||
var removeTwitterAccountActionMock = new Mock<IRemoveTwitterAccountAction>(MockBehavior.Strict);
|
||||
removeTwitterAccountActionMock
|
||||
.Setup(x => x.ProcessAsync(
|
||||
It.Is<SyncTwitterUser>(y => y.Acct == "acct")))
|
||||
.Returns(Task.CompletedTask);
|
||||
#endregion
|
||||
|
||||
var processor = new TwitterAccountModerationProcessor(twitterUserDalMock.Object, moderationRepositoryMock.Object, removeTwitterAccountActionMock.Object);
|
||||
await processor.ProcessAsync(ModerationTypeEnum.WhiteListing);
|
||||
|
||||
#region Validations
|
||||
twitterUserDalMock.VerifyAll();
|
||||
moderationRepositoryMock.VerifyAll();
|
||||
removeTwitterAccountActionMock.VerifyAll();
|
||||
#endregion
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public async Task ProcessAsync_BlackListing_BlackListed()
|
||||
{
|
||||
#region Stubs
|
||||
var allUsers = new List<SyncTwitterUser>
|
||||
{
|
||||
new SyncTwitterUser
|
||||
{
|
||||
Acct = "acct"
|
||||
}
|
||||
};
|
||||
#endregion
|
||||
|
||||
#region Mocks
|
||||
var twitterUserDalMock = new Mock<ITwitterUserDal>(MockBehavior.Strict);
|
||||
twitterUserDalMock
|
||||
.Setup(x => x.GetAllTwitterUsersAsync())
|
||||
.ReturnsAsync(allUsers.ToArray());
|
||||
|
||||
var moderationRepositoryMock = new Mock<IModerationRepository>(MockBehavior.Strict);
|
||||
moderationRepositoryMock
|
||||
.Setup(x => x.CheckStatus(
|
||||
It.Is<ModerationEntityTypeEnum>(y => y == ModerationEntityTypeEnum.TwitterAccount),
|
||||
It.Is<string>(y => y == "acct")))
|
||||
.Returns(ModeratedTypeEnum.BlackListed);
|
||||
|
||||
var removeTwitterAccountActionMock = new Mock<IRemoveTwitterAccountAction>(MockBehavior.Strict);
|
||||
removeTwitterAccountActionMock
|
||||
.Setup(x => x.ProcessAsync(
|
||||
It.Is<SyncTwitterUser>(y => y.Acct == "acct")))
|
||||
.Returns(Task.CompletedTask);
|
||||
#endregion
|
||||
|
||||
var processor = new TwitterAccountModerationProcessor(twitterUserDalMock.Object, moderationRepositoryMock.Object, removeTwitterAccountActionMock.Object);
|
||||
await processor.ProcessAsync(ModerationTypeEnum.BlackListing);
|
||||
|
||||
#region Validations
|
||||
twitterUserDalMock.VerifyAll();
|
||||
moderationRepositoryMock.VerifyAll();
|
||||
removeTwitterAccountActionMock.VerifyAll();
|
||||
#endregion
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public async Task ProcessAsync_BlackListing_NotBlackListed()
|
||||
{
|
||||
#region Stubs
|
||||
var allUsers = new List<SyncTwitterUser>
|
||||
{
|
||||
new SyncTwitterUser
|
||||
{
|
||||
Acct = "acct"
|
||||
}
|
||||
};
|
||||
#endregion
|
||||
|
||||
#region Mocks
|
||||
var twitterUserDalMock = new Mock<ITwitterUserDal>(MockBehavior.Strict);
|
||||
twitterUserDalMock
|
||||
.Setup(x => x.GetAllTwitterUsersAsync())
|
||||
.ReturnsAsync(allUsers.ToArray());
|
||||
|
||||
var moderationRepositoryMock = new Mock<IModerationRepository>(MockBehavior.Strict);
|
||||
moderationRepositoryMock
|
||||
.Setup(x => x.CheckStatus(
|
||||
It.Is<ModerationEntityTypeEnum>(y => y == ModerationEntityTypeEnum.TwitterAccount),
|
||||
It.Is<string>(y => y == "acct")))
|
||||
.Returns(ModeratedTypeEnum.None);
|
||||
|
||||
var removeTwitterAccountActionMock = new Mock<IRemoveTwitterAccountAction>(MockBehavior.Strict);
|
||||
#endregion
|
||||
|
||||
var processor = new TwitterAccountModerationProcessor(twitterUserDalMock.Object, moderationRepositoryMock.Object, removeTwitterAccountActionMock.Object);
|
||||
await processor.ProcessAsync(ModerationTypeEnum.BlackListing);
|
||||
|
||||
#region Validations
|
||||
twitterUserDalMock.VerifyAll();
|
||||
moderationRepositoryMock.VerifyAll();
|
||||
removeTwitterAccountActionMock.VerifyAll();
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
}
|
|
@ -48,9 +48,9 @@ namespace BirdsiteLive.Pipeline.Tests.Processors
|
|||
|
||||
var processor = new RetrieveTwitterUsersProcessor(twitterUserDalMock.Object, maxUsersNumberProviderMock.Object, loggerMock.Object);
|
||||
processor.WaitFactor = 10;
|
||||
processor.GetTwitterUsersAsync(buffer, CancellationToken.None);
|
||||
var t = processor.GetTwitterUsersAsync(buffer, CancellationToken.None);
|
||||
|
||||
await Task.Delay(50);
|
||||
await Task.WhenAny(t, Task.Delay(50));
|
||||
|
||||
#region Validations
|
||||
maxUsersNumberProviderMock.VerifyAll();
|
||||
|
@ -95,10 +95,10 @@ namespace BirdsiteLive.Pipeline.Tests.Processors
|
|||
|
||||
var processor = new RetrieveTwitterUsersProcessor(twitterUserDalMock.Object, maxUsersNumberProviderMock.Object, loggerMock.Object);
|
||||
processor.WaitFactor = 2;
|
||||
processor.GetTwitterUsersAsync(buffer, CancellationToken.None);
|
||||
|
||||
await Task.Delay(300);
|
||||
var t = processor.GetTwitterUsersAsync(buffer, CancellationToken.None);
|
||||
|
||||
await Task.WhenAny(t, Task.Delay(300));
|
||||
|
||||
#region Validations
|
||||
maxUsersNumberProviderMock.VerifyAll();
|
||||
twitterUserDalMock.VerifyAll();
|
||||
|
@ -142,9 +142,14 @@ namespace BirdsiteLive.Pipeline.Tests.Processors
|
|||
|
||||
var processor = new RetrieveTwitterUsersProcessor(twitterUserDalMock.Object, maxUsersNumberProviderMock.Object, loggerMock.Object);
|
||||
processor.WaitFactor = 2;
|
||||
processor.GetTwitterUsersAsync(buffer, CancellationToken.None);
|
||||
var t = processor.GetTwitterUsersAsync(buffer, CancellationToken.None);
|
||||
var t2 = Task.Run(async () =>
|
||||
{
|
||||
while (buffer.Count < 11)
|
||||
await Task.Delay(50);
|
||||
});
|
||||
|
||||
await Task.Delay(200);
|
||||
await Task.WhenAny(t, t2, Task.Delay(5000));
|
||||
|
||||
#region Validations
|
||||
maxUsersNumberProviderMock.VerifyAll();
|
||||
|
@ -181,9 +186,9 @@ namespace BirdsiteLive.Pipeline.Tests.Processors
|
|||
|
||||
var processor = new RetrieveTwitterUsersProcessor(twitterUserDalMock.Object, maxUsersNumberProviderMock.Object, loggerMock.Object);
|
||||
processor.WaitFactor = 1;
|
||||
processor.GetTwitterUsersAsync(buffer, CancellationToken.None);
|
||||
var t =processor.GetTwitterUsersAsync(buffer, CancellationToken.None);
|
||||
|
||||
await Task.Delay(50);
|
||||
await Task.WhenAny(t, Task.Delay(50));
|
||||
|
||||
#region Validations
|
||||
maxUsersNumberProviderMock.VerifyAll();
|
||||
|
|
|
@ -0,0 +1,50 @@
|
|||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Threading.Tasks.Dataflow;
|
||||
using BirdsiteLive.DAL.Models;
|
||||
using BirdsiteLive.Pipeline.Contracts;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using Moq;
|
||||
|
||||
namespace BirdsiteLive.Pipeline.Tests
|
||||
{
|
||||
[TestClass]
|
||||
public class StatusPublicationPipelineTests
|
||||
{
|
||||
[TestMethod]
|
||||
public async Task ExecuteAsync_Test()
|
||||
{
|
||||
#region Stubs
|
||||
var ct = new CancellationTokenSource(10);
|
||||
#endregion
|
||||
|
||||
#region Mocks
|
||||
var retrieveTwitterUsersProcessor = new Mock<IRetrieveTwitterUsersProcessor>(MockBehavior.Strict);
|
||||
retrieveTwitterUsersProcessor
|
||||
.Setup(x => x.GetTwitterUsersAsync(
|
||||
It.IsAny<BufferBlock<SyncTwitterUser[]>>(),
|
||||
It.IsAny<CancellationToken>()))
|
||||
.Returns(Task.Delay(0));
|
||||
|
||||
var retrieveTweetsProcessor = new Mock<IRetrieveTweetsProcessor>(MockBehavior.Strict);
|
||||
var retrieveFollowersProcessor = new Mock<IRetrieveFollowersProcessor>(MockBehavior.Strict);
|
||||
var sendTweetsToFollowersProcessor = new Mock<ISendTweetsToFollowersProcessor>(MockBehavior.Strict);
|
||||
var saveProgressionProcessor = new Mock<ISaveProgressionProcessor>(MockBehavior.Strict);
|
||||
var logger = new Mock<ILogger<StatusPublicationPipeline>>();
|
||||
#endregion
|
||||
|
||||
var pipeline = new StatusPublicationPipeline(retrieveTweetsProcessor.Object, retrieveTwitterUsersProcessor.Object, retrieveFollowersProcessor.Object, sendTweetsToFollowersProcessor.Object, saveProgressionProcessor.Object, logger.Object);
|
||||
await pipeline.ExecuteAsync(ct.Token);
|
||||
|
||||
#region Validations
|
||||
retrieveTwitterUsersProcessor.VerifyAll();
|
||||
retrieveTweetsProcessor.VerifyAll();
|
||||
retrieveFollowersProcessor.VerifyAll();
|
||||
sendTweetsToFollowersProcessor.VerifyAll();
|
||||
saveProgressionProcessor.VerifyAll();
|
||||
logger.VerifyAll();
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
}
|
Reference in a new issue