commit
27312dd3c4
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
|
run: dotnet build --configuration Release --no-restore
|
||||||
working-directory: ${{env.working-directory}}
|
working-directory: ${{env.working-directory}}
|
||||||
- name: Test
|
- name: Test
|
||||||
run: dotnet test --no-restore --verbosity quiet
|
run: dotnet test --no-restore --verbosity minimal
|
||||||
working-directory: ${{env.working-directory}}
|
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):
|
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
|
||||||
|
|
||||||
* `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)
|
* `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: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: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;
|
return acceptFollow;
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
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
|
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
|
public class ProcessFollowUser : IProcessFollowUser
|
||||||
|
@ -21,13 +21,13 @@ namespace BirdsiteLive.Domain.BusinessUseCases
|
||||||
}
|
}
|
||||||
#endregion
|
#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
|
// Get Follower and Twitter Users
|
||||||
var follower = await _followerDal.GetFollowerAsync(followerUsername, followerDomain);
|
var follower = await _followerDal.GetFollowerAsync(followerUsername, followerDomain);
|
||||||
if (follower == null)
|
if (follower == null)
|
||||||
{
|
{
|
||||||
await _followerDal.CreateFollowerAsync(followerUsername, followerDomain, followerInbox, sharedInbox);
|
await _followerDal.CreateFollowerAsync(followerUsername, followerDomain, followerInbox, sharedInbox, followerActorId);
|
||||||
follower = await _followerDal.GetFollowerAsync(followerUsername, followerDomain);
|
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 noteUrl = UrlFactory.GetNoteUrl(_instanceSettings.Domain, username, tweet.Id.ToString());
|
||||||
|
|
||||||
var to = $"{actorUrl}/followers";
|
var to = $"{actorUrl}/followers";
|
||||||
var apPublic = "https://www.w3.org/ns/activitystreams#Public";
|
|
||||||
|
|
||||||
var extractedTags = _statusExtractor.Extract(tweet.MessageContent);
|
var extractedTags = _statusExtractor.Extract(tweet.MessageContent);
|
||||||
_statisticsHandler.ExtractedStatus(extractedTags.tags.Count(x => x.type == "Mention"));
|
_statisticsHandler.ExtractedStatus(extractedTags.tags.Count(x => x.type == "Mention"));
|
||||||
|
@ -70,11 +69,9 @@ namespace BirdsiteLive.Domain
|
||||||
attributedTo = actorUrl,
|
attributedTo = actorUrl,
|
||||||
|
|
||||||
inReplyTo = inReplyTo,
|
inReplyTo = inReplyTo,
|
||||||
//to = new [] {to},
|
|
||||||
//cc = new [] { apPublic },
|
|
||||||
|
|
||||||
to = new[] { to },
|
to = new[] { to },
|
||||||
//cc = new[] { apPublic },
|
//cc = new[] { "https://www.w3.org/ns/activitystreams#Public" },
|
||||||
cc = new string[0],
|
cc = new string[0],
|
||||||
|
|
||||||
sensitive = false,
|
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.Common.Settings;
|
||||||
using BirdsiteLive.Cryptography;
|
using BirdsiteLive.Cryptography;
|
||||||
using BirdsiteLive.Domain.BusinessUseCases;
|
using BirdsiteLive.Domain.BusinessUseCases;
|
||||||
|
using BirdsiteLive.Domain.Repository;
|
||||||
using BirdsiteLive.Domain.Statistics;
|
using BirdsiteLive.Domain.Statistics;
|
||||||
using BirdsiteLive.Domain.Tools;
|
using BirdsiteLive.Domain.Tools;
|
||||||
using BirdsiteLive.Twitter;
|
using BirdsiteLive.Twitter;
|
||||||
|
@ -25,6 +26,8 @@ namespace BirdsiteLive.Domain
|
||||||
Actor GetUser(TwitterUser twitterUser);
|
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> 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> 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
|
public class UserService : IUserService
|
||||||
|
@ -40,8 +43,10 @@ namespace BirdsiteLive.Domain
|
||||||
|
|
||||||
private readonly ITwitterUserService _twitterUserService;
|
private readonly ITwitterUserService _twitterUserService;
|
||||||
|
|
||||||
|
private readonly IModerationRepository _moderationRepository;
|
||||||
|
|
||||||
#region Ctor
|
#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;
|
_instanceSettings = instanceSettings;
|
||||||
_cryptoService = cryptoService;
|
_cryptoService = cryptoService;
|
||||||
|
@ -51,6 +56,7 @@ namespace BirdsiteLive.Domain
|
||||||
_statusExtractor = statusExtractor;
|
_statusExtractor = statusExtractor;
|
||||||
_statisticsHandler = statisticsHandler;
|
_statisticsHandler = statisticsHandler;
|
||||||
_twitterUserService = twitterUserService;
|
_twitterUserService = twitterUserService;
|
||||||
|
_moderationRepository = moderationRepository;
|
||||||
}
|
}
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
|
@ -119,62 +125,94 @@ namespace BirdsiteLive.Domain
|
||||||
var sigValidation = await ValidateSignature(activity.actor, signature, method, path, queryString, requestHeaders, body);
|
var sigValidation = await ValidateSignature(activity.actor, signature, method, path, queryString, requestHeaders, body);
|
||||||
if (!sigValidation.SignatureIsValidated) return false;
|
if (!sigValidation.SignatureIsValidated) return false;
|
||||||
|
|
||||||
// Save Follow in DB
|
// Prepare data
|
||||||
var followerUserName = sigValidation.User.preferredUsername.ToLowerInvariant();
|
var followerUserName = sigValidation.User.preferredUsername.ToLowerInvariant().Trim();
|
||||||
var followerHost = sigValidation.User.url.Replace("https://", string.Empty).Split('/').First();
|
var followerHost = sigValidation.User.url.Replace("https://", string.Empty).Split('/').First();
|
||||||
var followerInbox = sigValidation.User.inbox;
|
var followerInbox = sigValidation.User.inbox;
|
||||||
var followerSharedInbox = sigValidation.User?.endpoints?.sharedInbox;
|
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
|
// Make sure to only keep routes
|
||||||
followerInbox = OnlyKeepRoute(followerInbox, followerHost);
|
followerInbox = OnlyKeepRoute(followerInbox, followerHost);
|
||||||
followerSharedInbox = OnlyKeepRoute(followerSharedInbox, 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);
|
var user = _twitterUserService.GetUser(twitterUser);
|
||||||
if (!user.Protected)
|
if (!user.Protected)
|
||||||
{
|
{
|
||||||
// Execute
|
// Execute
|
||||||
await _processFollowUser.ExecuteAsync(followerUserName, followerHost, twitterUser, followerInbox, followerSharedInbox);
|
await _processFollowUser.ExecuteAsync(followerUserName, followerHost, twitterUser, followerInbox, followerSharedInbox, activity.actor);
|
||||||
|
|
||||||
// Send Accept Activity
|
return await SendAcceptFollowAsync(activity, 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
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// Send Reject Activity
|
return await SendRejectFollowAsync(activity, 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 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)
|
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
|
EndProject
|
||||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BirdsiteLive.DAL.Tests", "Tests\BirdsiteLive.DAL.Tests\BirdsiteLive.DAL.Tests.csproj", "{5A1E3EB5-6CBB-470D-8A0D-10F8C18353D5}"
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BirdsiteLive.DAL.Tests", "Tests\BirdsiteLive.DAL.Tests\BirdsiteLive.DAL.Tests.csproj", "{5A1E3EB5-6CBB-470D-8A0D-10F8C18353D5}"
|
||||||
EndProject
|
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}"
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BirdsiteLive.Common.Tests", "Tests\BirdsiteLive.Common.Tests\BirdsiteLive.Common.Tests.csproj", "{C69F7582-6050-44DC-BAAB-7C8F0BDA525C}"
|
||||||
EndProject
|
EndProject
|
||||||
Global
|
Global
|
||||||
|
@ -109,6 +113,14 @@ Global
|
||||||
{5A1E3EB5-6CBB-470D-8A0D-10F8C18353D5}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
{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.ActiveCfg = Release|Any CPU
|
||||||
{5A1E3EB5-6CBB-470D-8A0D-10F8C18353D5}.Release|Any CPU.Build.0 = 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.ActiveCfg = Debug|Any CPU
|
||||||
{C69F7582-6050-44DC-BAAB-7C8F0BDA525C}.Debug|Any CPU.Build.0 = 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
|
{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}
|
{F544D745-89A8-4DEA-B61C-A7E6C53C1D63} = {A32D3458-09D0-4E0A-BA4B-8C411B816B94}
|
||||||
{BF51CA81-5A7A-46F8-B4FB-861C6BE59298} = {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}
|
{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}
|
{C69F7582-6050-44DC-BAAB-7C8F0BDA525C} = {A32D3458-09D0-4E0A-BA4B-8C411B816B94}
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
<TargetFramework>netcoreapp3.1</TargetFramework>
|
<TargetFramework>netcoreapp3.1</TargetFramework>
|
||||||
<UserSecretsId>d21486de-a812-47eb-a419-05682bb68856</UserSecretsId>
|
<UserSecretsId>d21486de-a812-47eb-a419-05682bb68856</UserSecretsId>
|
||||||
<DockerDefaultTargetOS>Linux</DockerDefaultTargetOS>
|
<DockerDefaultTargetOS>Linux</DockerDefaultTargetOS>
|
||||||
<Version>0.14.5</Version>
|
<Version>0.15.0</Version>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
@ -18,6 +18,7 @@
|
||||||
<ProjectReference Include="..\BirdsiteLive.Common\BirdsiteLive.Common.csproj" />
|
<ProjectReference Include="..\BirdsiteLive.Common\BirdsiteLive.Common.csproj" />
|
||||||
<ProjectReference Include="..\BirdsiteLive.Cryptography\BirdsiteLive.Cryptography.csproj" />
|
<ProjectReference Include="..\BirdsiteLive.Cryptography\BirdsiteLive.Cryptography.csproj" />
|
||||||
<ProjectReference Include="..\BirdsiteLive.Domain\BirdsiteLive.Domain.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.Pipeline\BirdsiteLive.Pipeline.csproj" />
|
||||||
<ProjectReference Include="..\BirdsiteLive.Twitter\BirdsiteLive.Twitter.csproj" />
|
<ProjectReference Include="..\BirdsiteLive.Twitter\BirdsiteLive.Twitter.csproj" />
|
||||||
<ProjectReference Include="..\DataAccessLayers\BirdsiteLive.DAL.Postgres\BirdsiteLive.DAL.Postgres.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 InstanceSettings _instanceSettings;
|
||||||
private readonly ICryptoService _cryptoService;
|
private readonly ICryptoService _cryptoService;
|
||||||
private readonly IActivityPubService _activityPubService;
|
private readonly IActivityPubService _activityPubService;
|
||||||
|
private readonly IUserService _userService;
|
||||||
|
|
||||||
#region Ctor
|
#region Ctor
|
||||||
public DebugingController(InstanceSettings instanceSettings, ICryptoService cryptoService, IActivityPubService activityPubService)
|
public DebugingController(InstanceSettings instanceSettings, ICryptoService cryptoService, IActivityPubService activityPubService, IUserService userService)
|
||||||
{
|
{
|
||||||
_instanceSettings = instanceSettings;
|
_instanceSettings = instanceSettings;
|
||||||
_cryptoService = cryptoService;
|
_cryptoService = cryptoService;
|
||||||
_activityPubService = activityPubService;
|
_activityPubService = activityPubService;
|
||||||
|
_userService = userService;
|
||||||
}
|
}
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
|
@ -67,7 +69,6 @@ namespace BirdsiteLive.Controllers
|
||||||
var noteUrl = $"https://{_instanceSettings.Domain}/@{username}/{noteGuid}";
|
var noteUrl = $"https://{_instanceSettings.Domain}/@{username}/{noteGuid}";
|
||||||
|
|
||||||
var to = $"{actor}/followers";
|
var to = $"{actor}/followers";
|
||||||
var apPublic = "https://www.w3.org/ns/activitystreams#Public";
|
|
||||||
|
|
||||||
var now = DateTime.UtcNow;
|
var now = DateTime.UtcNow;
|
||||||
var nowString = now.ToString("s") + "Z";
|
var nowString = now.ToString("s") + "Z";
|
||||||
|
@ -80,7 +81,7 @@ namespace BirdsiteLive.Controllers
|
||||||
actor = actor,
|
actor = actor,
|
||||||
published = nowString,
|
published = nowString,
|
||||||
to = new []{ to },
|
to = new []{ to },
|
||||||
//cc = new [] { apPublic },
|
//cc = new [] { "https://www.w3.org/ns/activitystreams#Public" },
|
||||||
apObject = new Note()
|
apObject = new Note()
|
||||||
{
|
{
|
||||||
id = noteId,
|
id = noteId,
|
||||||
|
@ -90,7 +91,7 @@ namespace BirdsiteLive.Controllers
|
||||||
url = noteUrl,
|
url = noteUrl,
|
||||||
attributedTo = actor,
|
attributedTo = actor,
|
||||||
to = new[] { to },
|
to = new[] { to },
|
||||||
//cc = new [] { apPublic },
|
//cc = new [] { "https://www.w3.org/ns/activitystreams#Public" },
|
||||||
sensitive = false,
|
sensitive = false,
|
||||||
content = "<p>Woooot</p>",
|
content = "<p>Woooot</p>",
|
||||||
attachment = new Attachment[0],
|
attachment = new Attachment[0],
|
||||||
|
@ -102,6 +103,20 @@ namespace BirdsiteLive.Controllers
|
||||||
|
|
||||||
return View("Index");
|
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
|
public static class HtmlHelperExtensions
|
||||||
|
|
|
@ -159,13 +159,11 @@ namespace BirdsiteLive.Controllers
|
||||||
return Accepted();
|
return Accepted();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return Accepted();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[Route("/users/{id}/followers")]
|
[Route("/users/{id}/followers")]
|
||||||
[HttpGet]
|
[HttpGet]
|
||||||
public async Task<IActionResult> Followers(string id)
|
public IActionResult Followers(string id)
|
||||||
{
|
{
|
||||||
var r = Request.Headers["Accept"].First();
|
var r = Request.Headers["Accept"].First();
|
||||||
if (!r.Contains("application/activity+json")) return NotFound();
|
if (!r.Contains("application/activity+json")) return NotFound();
|
||||||
|
|
|
@ -7,6 +7,7 @@ using BirdsiteLive.ActivityPub.Converters;
|
||||||
using BirdsiteLive.Common.Regexes;
|
using BirdsiteLive.Common.Regexes;
|
||||||
using BirdsiteLive.Common.Settings;
|
using BirdsiteLive.Common.Settings;
|
||||||
using BirdsiteLive.DAL.Contracts;
|
using BirdsiteLive.DAL.Contracts;
|
||||||
|
using BirdsiteLive.Domain.Repository;
|
||||||
using BirdsiteLive.Models;
|
using BirdsiteLive.Models;
|
||||||
using BirdsiteLive.Models.WellKnownModels;
|
using BirdsiteLive.Models.WellKnownModels;
|
||||||
using BirdsiteLive.Twitter;
|
using BirdsiteLive.Twitter;
|
||||||
|
@ -18,15 +19,17 @@ namespace BirdsiteLive.Controllers
|
||||||
[ApiController]
|
[ApiController]
|
||||||
public class WellKnownController : ControllerBase
|
public class WellKnownController : ControllerBase
|
||||||
{
|
{
|
||||||
|
private readonly IModerationRepository _moderationRepository;
|
||||||
private readonly ITwitterUserService _twitterUserService;
|
private readonly ITwitterUserService _twitterUserService;
|
||||||
private readonly ITwitterUserDal _twitterUserDal;
|
private readonly ITwitterUserDal _twitterUserDal;
|
||||||
private readonly InstanceSettings _settings;
|
private readonly InstanceSettings _settings;
|
||||||
|
|
||||||
#region Ctor
|
#region Ctor
|
||||||
public WellKnownController(InstanceSettings settings, ITwitterUserService twitterUserService, ITwitterUserDal twitterUserDal)
|
public WellKnownController(InstanceSettings settings, ITwitterUserService twitterUserService, ITwitterUserDal twitterUserDal, IModerationRepository moderationRepository)
|
||||||
{
|
{
|
||||||
_twitterUserService = twitterUserService;
|
_twitterUserService = twitterUserService;
|
||||||
_twitterUserDal = twitterUserDal;
|
_twitterUserDal = twitterUserDal;
|
||||||
|
_moderationRepository = moderationRepository;
|
||||||
_settings = settings;
|
_settings = settings;
|
||||||
}
|
}
|
||||||
#endregion
|
#endregion
|
||||||
|
@ -58,6 +61,7 @@ namespace BirdsiteLive.Controllers
|
||||||
{
|
{
|
||||||
var version = System.Reflection.Assembly.GetEntryAssembly().GetName().Version.ToString(3);
|
var version = System.Reflection.Assembly.GetEntryAssembly().GetName().Version.ToString(3);
|
||||||
var twitterUsersCount = await _twitterUserDal.GetTwitterUsersCountAsync();
|
var twitterUsersCount = await _twitterUserDal.GetTwitterUsersCountAsync();
|
||||||
|
var isOpenRegistration = _moderationRepository.GetModerationType(ModerationEntityTypeEnum.Follower) != ModerationTypeEnum.WhiteListing;
|
||||||
|
|
||||||
if (id == "2.0")
|
if (id == "2.0")
|
||||||
{
|
{
|
||||||
|
@ -81,7 +85,7 @@ namespace BirdsiteLive.Controllers
|
||||||
{
|
{
|
||||||
"activitypub"
|
"activitypub"
|
||||||
},
|
},
|
||||||
openRegistrations = false,
|
openRegistrations = isOpenRegistration,
|
||||||
services = new Models.WellKnownModels.Services()
|
services = new Models.WellKnownModels.Services()
|
||||||
{
|
{
|
||||||
inbound = new object[0],
|
inbound = new object[0],
|
||||||
|
@ -117,7 +121,7 @@ namespace BirdsiteLive.Controllers
|
||||||
{
|
{
|
||||||
"activitypub"
|
"activitypub"
|
||||||
},
|
},
|
||||||
openRegistrations = false,
|
openRegistrations = isOpenRegistration,
|
||||||
services = new Models.WellKnownModels.Services()
|
services = new Models.WellKnownModels.Services()
|
||||||
{
|
{
|
||||||
inbound = new object[0],
|
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.Common.Settings;
|
||||||
|
using BirdsiteLive.DAL.Contracts;
|
||||||
|
|
||||||
|
namespace BirdsiteLive.Services
|
||||||
|
{
|
||||||
|
public interface ICachedStatisticsService
|
||||||
|
{
|
||||||
|
Task<CachedStatistics> GetStatisticsAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
public class CachedStatisticsService : ICachedStatisticsService
|
||||||
|
{
|
||||||
|
private readonly ITwitterUserDal _twitterUserDal;
|
||||||
|
|
||||||
|
private static CachedStatistics _cachedStatistics;
|
||||||
|
private readonly InstanceSettings _instanceSettings;
|
||||||
|
|
||||||
|
#region Ctor
|
||||||
|
public CachedStatisticsService(ITwitterUserDal twitterUserDal, InstanceSettings instanceSettings)
|
||||||
|
{
|
||||||
|
_twitterUserDal = twitterUserDal;
|
||||||
|
_instanceSettings = instanceSettings;
|
||||||
|
}
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
public async Task<CachedStatistics> GetStatisticsAsync()
|
||||||
|
{
|
||||||
|
if (_cachedStatistics == null ||
|
||||||
|
(DateTime.UtcNow - _cachedStatistics.RefreshedTime).TotalMinutes > 15)
|
||||||
|
{
|
||||||
|
var twitterUserMax = _instanceSettings.MaxUsersCapacity;
|
||||||
|
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 System.Threading.Tasks;
|
||||||
using BirdsiteLive.DAL;
|
using BirdsiteLive.DAL;
|
||||||
using BirdsiteLive.DAL.Contracts;
|
using BirdsiteLive.DAL.Contracts;
|
||||||
|
using BirdsiteLive.Moderation;
|
||||||
using BirdsiteLive.Pipeline;
|
using BirdsiteLive.Pipeline;
|
||||||
using Microsoft.Extensions.Hosting;
|
using Microsoft.Extensions.Hosting;
|
||||||
|
|
||||||
|
@ -12,13 +13,15 @@ namespace BirdsiteLive.Services
|
||||||
public class FederationService : BackgroundService
|
public class FederationService : BackgroundService
|
||||||
{
|
{
|
||||||
private readonly IDatabaseInitializer _databaseInitializer;
|
private readonly IDatabaseInitializer _databaseInitializer;
|
||||||
|
private readonly IModerationPipeline _moderationPipeline;
|
||||||
private readonly IStatusPublicationPipeline _statusPublicationPipeline;
|
private readonly IStatusPublicationPipeline _statusPublicationPipeline;
|
||||||
private readonly IHostApplicationLifetime _applicationLifetime;
|
private readonly IHostApplicationLifetime _applicationLifetime;
|
||||||
|
|
||||||
#region Ctor
|
#region Ctor
|
||||||
public FederationService(IDatabaseInitializer databaseInitializer, IStatusPublicationPipeline statusPublicationPipeline, IHostApplicationLifetime applicationLifetime)
|
public FederationService(IDatabaseInitializer databaseInitializer, IModerationPipeline moderationPipeline, IStatusPublicationPipeline statusPublicationPipeline, IHostApplicationLifetime applicationLifetime)
|
||||||
{
|
{
|
||||||
_databaseInitializer = databaseInitializer;
|
_databaseInitializer = databaseInitializer;
|
||||||
|
_moderationPipeline = moderationPipeline;
|
||||||
_statusPublicationPipeline = statusPublicationPipeline;
|
_statusPublicationPipeline = statusPublicationPipeline;
|
||||||
_applicationLifetime = applicationLifetime;
|
_applicationLifetime = applicationLifetime;
|
||||||
}
|
}
|
||||||
|
@ -29,6 +32,7 @@ namespace BirdsiteLive.Services
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
await _databaseInitializer.InitAndMigrateDbAsync();
|
await _databaseInitializer.InitAndMigrateDbAsync();
|
||||||
|
await _moderationPipeline.ApplyModerationSettingsAsync();
|
||||||
await _statusPublicationPipeline.ExecuteAsync(stoppingToken);
|
await _statusPublicationPipeline.ExecuteAsync(stoppingToken);
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
|
|
|
@ -66,7 +66,10 @@ namespace BirdsiteLive
|
||||||
|
|
||||||
var logsSettings = Configuration.GetSection("Logging").Get<LogsSettings>();
|
var logsSettings = Configuration.GetSection("Logging").Get<LogsSettings>();
|
||||||
services.For<LogsSettings>().Use(x => 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))
|
if (string.Equals(dbSettings.Type, DbTypes.Postgres, StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
var connString = $"Host={dbSettings.Host};Username={dbSettings.User};Password={dbSettings.Password};Database={dbSettings.Name}";
|
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.Domain");
|
||||||
_.Assembly("BirdsiteLive.DAL");
|
_.Assembly("BirdsiteLive.DAL");
|
||||||
_.Assembly("BirdsiteLive.DAL.Postgres");
|
_.Assembly("BirdsiteLive.DAL.Postgres");
|
||||||
|
_.Assembly("BirdsiteLive.Moderation");
|
||||||
_.Assembly("BirdsiteLive.Pipeline");
|
_.Assembly("BirdsiteLive.Pipeline");
|
||||||
_.TheCallingAssembly();
|
_.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 -->
|
<!-- Input and Submit elements -->
|
||||||
|
|
||||||
<button type="submit" value="Submit">Post Note</button>
|
<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>
|
</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="~/lib/bootstrap/dist/css/bootstrap.min.css" />
|
||||||
<link rel="stylesheet" href="~/css/site.css" />
|
<link rel="stylesheet" href="~/css/site.css" />
|
||||||
<link rel="stylesheet" href="~/css/birdsite.css" />
|
<link rel="stylesheet" href="~/css/birdsite.css" />
|
||||||
|
<link rel="stylesheet" href="~/css/pattern.css" />
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<header>
|
<header>
|
||||||
|
@ -39,6 +40,11 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<footer class="border-top footer text-muted">
|
<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">
|
<div class="container">
|
||||||
|
|
||||||
<a href="https://github.com/NicolasConstant/BirdsiteLive">Github</a> @*<a asp-area="" asp-controller="Home" asp-action="Privacy">Privacy</a>*@
|
<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": {
|
"Logging": {
|
||||||
"Type": "none",
|
"Type": "none",
|
||||||
"InstrumentationKey": "key",
|
"InstrumentationKey": "key",
|
||||||
|
"ApplicationInsights": {
|
||||||
|
"LogLevel": {
|
||||||
|
"Default": "Warning"
|
||||||
|
}
|
||||||
|
},
|
||||||
"LogLevel": {
|
"LogLevel": {
|
||||||
"Default": "Information",
|
"Default": "Information",
|
||||||
"Microsoft": "Warning",
|
"Microsoft": "Warning",
|
||||||
|
@ -10,7 +15,7 @@
|
||||||
},
|
},
|
||||||
"AllowedHosts": "*",
|
"AllowedHosts": "*",
|
||||||
"Instance": {
|
"Instance": {
|
||||||
"Name": "BirdsiteLIVE",
|
"Name": "BirdsiteLIVE",
|
||||||
"Domain": "domain.name",
|
"Domain": "domain.name",
|
||||||
"AdminEmail": "me@domain.name",
|
"AdminEmail": "me@domain.name",
|
||||||
"ResolveMentionsInProfiles": true,
|
"ResolveMentionsInProfiles": true,
|
||||||
|
@ -27,5 +32,11 @@
|
||||||
"Twitter": {
|
"Twitter": {
|
||||||
"ConsumerKey": "twitter.api.key",
|
"ConsumerKey": "twitter.api.key",
|
||||||
"ConsumerSecret": "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
|
public class DbInitializerPostgresDal : PostgresBase, IDbInitializerDal
|
||||||
{
|
{
|
||||||
private readonly PostgresTools _tools;
|
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";
|
private const string DbVersionType = "db-version";
|
||||||
|
|
||||||
#region Ctor
|
#region Ctor
|
||||||
|
@ -131,7 +131,8 @@ namespace BirdsiteLive.DAL.Postgres.DataAccessLayers
|
||||||
{
|
{
|
||||||
return new[]
|
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)";
|
var addIndex = $@"CREATE INDEX IF NOT EXISTS lastsync_twitteruser ON {_settings.TwitterUserTableName}(lastSync)";
|
||||||
await _tools.ExecuteRequestAsync(addIndex);
|
await _tools.ExecuteRequestAsync(addIndex);
|
||||||
|
}
|
||||||
await UpdateDbVersionAsync(to);
|
else if (from == new Version(2, 0) && to == new Version(2, 1))
|
||||||
return to;
|
{
|
||||||
|
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)
|
private async Task UpdateDbVersionAsync(Version newVersion)
|
||||||
|
|
|
@ -20,7 +20,7 @@ namespace BirdsiteLive.DAL.Postgres.DataAccessLayers
|
||||||
}
|
}
|
||||||
#endregion
|
#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(followings == null) followings = new int[0];
|
||||||
if(followingSyncStatus == null) followingSyncStatus = new Dictionary<int, long>();
|
if(followingSyncStatus == null) followingSyncStatus = new Dictionary<int, long>();
|
||||||
|
@ -35,8 +35,8 @@ namespace BirdsiteLive.DAL.Postgres.DataAccessLayers
|
||||||
dbConnection.Open();
|
dbConnection.Open();
|
||||||
|
|
||||||
await dbConnection.ExecuteAsync(
|
await dbConnection.ExecuteAsync(
|
||||||
$"INSERT INTO {_settings.FollowersTableName} (acct,host,inboxRoute,sharedInboxRoute,followings,followingsSyncStatus) VALUES(@acct,@host,@inboxRoute,@sharedInboxRoute,@followings,CAST(@followingsSyncStatus as json))",
|
$"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 });
|
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)
|
public async Task UpdateFollowerAsync(Follower follower)
|
||||||
{
|
{
|
||||||
if (follower == default) throw new ArgumentException("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)
|
public async Task DeleteFollowerAsync(string acct, string host)
|
||||||
{
|
{
|
||||||
if (acct == default) throw new ArgumentException("acct");
|
if (string.IsNullOrWhiteSpace(acct)) throw new ArgumentException("acct");
|
||||||
if (host == default) throw new ArgumentException("host");
|
if (string.IsNullOrWhiteSpace(host)) throw new ArgumentException("host");
|
||||||
|
|
||||||
acct = acct.ToLowerInvariant();
|
acct = acct.ToLowerInvariant();
|
||||||
host = host.ToLowerInvariant();
|
host = host.ToLowerInvariant();
|
||||||
|
@ -142,6 +155,7 @@ namespace BirdsiteLive.DAL.Postgres.DataAccessLayers
|
||||||
Acct = follower.Acct,
|
Acct = follower.Acct,
|
||||||
Host = follower.Host,
|
Host = follower.Host,
|
||||||
InboxRoute = follower.InboxRoute,
|
InboxRoute = follower.InboxRoute,
|
||||||
|
ActorId = follower.ActorId,
|
||||||
SharedInboxRoute = follower.SharedInboxRoute,
|
SharedInboxRoute = follower.SharedInboxRoute,
|
||||||
Followings = follower.Followings.ToList(),
|
Followings = follower.Followings.ToList(),
|
||||||
FollowingsSyncStatus = JsonConvert.DeserializeObject<Dictionary<int,long>>(follower.FollowingsSyncStatus)
|
FollowingsSyncStatus = JsonConvert.DeserializeObject<Dictionary<int,long>>(follower.FollowingsSyncStatus)
|
||||||
|
@ -159,5 +173,6 @@ namespace BirdsiteLive.DAL.Postgres.DataAccessLayers
|
||||||
public string Host { get; set; }
|
public string Host { get; set; }
|
||||||
public string InboxRoute { get; set; }
|
public string InboxRoute { get; set; }
|
||||||
public string SharedInboxRoute { 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.Models;
|
||||||
using BirdsiteLive.DAL.Postgres.DataAccessLayers.Base;
|
using BirdsiteLive.DAL.Postgres.DataAccessLayers.Base;
|
||||||
using BirdsiteLive.DAL.Postgres.Settings;
|
using BirdsiteLive.DAL.Postgres.Settings;
|
||||||
using BirdsiteLive.DAL.Postgres.Tools;
|
|
||||||
using Dapper;
|
using Dapper;
|
||||||
using Npgsql;
|
|
||||||
|
|
||||||
namespace BirdsiteLive.DAL.Postgres.DataAccessLayers
|
namespace BirdsiteLive.DAL.Postgres.DataAccessLayers
|
||||||
{
|
{
|
||||||
|
@ -44,7 +42,20 @@ namespace BirdsiteLive.DAL.Postgres.DataAccessLayers
|
||||||
{
|
{
|
||||||
dbConnection.Open();
|
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;
|
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)
|
public async Task UpdateTwitterUserAsync(int id, long lastTweetPostedId, long lastTweetSynchronizedForAllFollowersId, DateTime lastSync)
|
||||||
{
|
{
|
||||||
if(id == default) throw new ArgumentException("id");
|
if(id == default) throw new ArgumentException("id");
|
||||||
|
@ -94,7 +118,7 @@ namespace BirdsiteLive.DAL.Postgres.DataAccessLayers
|
||||||
|
|
||||||
public async Task DeleteTwitterUserAsync(string acct)
|
public async Task DeleteTwitterUserAsync(string acct)
|
||||||
{
|
{
|
||||||
if (acct == default) throw new ArgumentException("acct");
|
if (string.IsNullOrWhiteSpace(acct)) throw new ArgumentException("acct");
|
||||||
|
|
||||||
acct = acct.ToLowerInvariant();
|
acct = acct.ToLowerInvariant();
|
||||||
|
|
||||||
|
@ -107,5 +131,19 @@ namespace BirdsiteLive.DAL.Postgres.DataAccessLayers
|
||||||
await dbConnection.QueryAsync(query, new { acct });
|
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
|
public interface IFollowersDal
|
||||||
{
|
{
|
||||||
Task<Follower> GetFollowerAsync(string acct, string host);
|
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);
|
Dictionary<int, long> followingSyncStatus = null);
|
||||||
Task<Follower[]> GetFollowersAsync(int followedUserId);
|
Task<Follower[]> GetFollowersAsync(int followedUserId);
|
||||||
|
Task<Follower[]> GetAllFollowersAsync();
|
||||||
Task UpdateFollowerAsync(Follower follower);
|
Task UpdateFollowerAsync(Follower follower);
|
||||||
Task DeleteFollowerAsync(int id);
|
Task DeleteFollowerAsync(int id);
|
||||||
Task DeleteFollowerAsync(string acct, string host);
|
Task DeleteFollowerAsync(string acct, string host);
|
||||||
|
|
|
@ -8,9 +8,12 @@ namespace BirdsiteLive.DAL.Contracts
|
||||||
{
|
{
|
||||||
Task CreateTwitterUserAsync(string acct, long lastTweetPostedId);
|
Task CreateTwitterUserAsync(string acct, long lastTweetPostedId);
|
||||||
Task<SyncTwitterUser> GetTwitterUserAsync(string acct);
|
Task<SyncTwitterUser> GetTwitterUserAsync(string acct);
|
||||||
|
Task<SyncTwitterUser> GetTwitterUserAsync(int id);
|
||||||
Task<SyncTwitterUser[]> GetAllTwitterUsersAsync(int maxNumber);
|
Task<SyncTwitterUser[]> GetAllTwitterUsersAsync(int maxNumber);
|
||||||
|
Task<SyncTwitterUser[]> GetAllTwitterUsersAsync();
|
||||||
Task UpdateTwitterUserAsync(int id, long lastTweetPostedId, long lastTweetSynchronizedForAllFollowersId, DateTime lastSync);
|
Task UpdateTwitterUserAsync(int id, long lastTweetPostedId, long lastTweetSynchronizedForAllFollowersId, DateTime lastSync);
|
||||||
Task DeleteTwitterUserAsync(string acct);
|
Task DeleteTwitterUserAsync(string acct);
|
||||||
|
Task DeleteTwitterUserAsync(int id);
|
||||||
Task<int> GetTwitterUsersCountAsync();
|
Task<int> GetTwitterUsersCountAsync();
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -9,6 +9,7 @@ namespace BirdsiteLive.DAL.Models
|
||||||
public List<int> Followings { get; set; }
|
public List<int> Followings { get; set; }
|
||||||
public Dictionary<int, long> FollowingsSyncStatus { get; set; }
|
public Dictionary<int, long> FollowingsSyncStatus { get; set; }
|
||||||
|
|
||||||
|
public string ActorId { get; set; }
|
||||||
public string Acct { get; set; }
|
public string Acct { get; set; }
|
||||||
public string Host { get; set; }
|
public string Host { get; set; }
|
||||||
public string InboxRoute { get; set; }
|
public string InboxRoute { get; set; }
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||||
|
using NuGet.Frameworks;
|
||||||
|
|
||||||
namespace BirdsiteLive.Cryptography.Tests
|
namespace BirdsiteLive.Cryptography.Tests
|
||||||
{
|
{
|
||||||
|
@ -7,13 +8,12 @@ namespace BirdsiteLive.Cryptography.Tests
|
||||||
public class MagicKeyTests
|
public class MagicKeyTests
|
||||||
{
|
{
|
||||||
[TestMethod]
|
[TestMethod]
|
||||||
public async Task Test()
|
public void Test()
|
||||||
{
|
{
|
||||||
var g = MagicKey.Generate();
|
var g = MagicKey.Generate();
|
||||||
|
|
||||||
var magicKey = new MagicKey(g.PrivateKey);
|
var magicKey = new MagicKey(g.PrivateKey);
|
||||||
|
|
||||||
|
Assert.IsNotNull(magicKey);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1,8 +1,8 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.ComponentModel;
|
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using BirdsiteLive.DAL.Models;
|
||||||
using BirdsiteLive.DAL.Postgres.DataAccessLayers;
|
using BirdsiteLive.DAL.Postgres.DataAccessLayers;
|
||||||
using BirdsiteLive.DAL.Postgres.Tests.DataAccessLayers.Base;
|
using BirdsiteLive.DAL.Postgres.Tests.DataAccessLayers.Base;
|
||||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||||
|
@ -41,9 +41,10 @@ namespace BirdsiteLive.DAL.Postgres.Tests.DataAccessLayers
|
||||||
};
|
};
|
||||||
var inboxRoute = "/myhandle/inbox";
|
var inboxRoute = "/myhandle/inbox";
|
||||||
var sharedInboxRoute = "/inbox";
|
var sharedInboxRoute = "/inbox";
|
||||||
|
var actorId = $"https://{host}/{acct}";
|
||||||
|
|
||||||
var dal = new FollowersPostgresDal(_settings);
|
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 result = await dal.GetFollowerAsync(acct, host);
|
||||||
|
|
||||||
|
@ -52,6 +53,7 @@ namespace BirdsiteLive.DAL.Postgres.Tests.DataAccessLayers
|
||||||
Assert.AreEqual(host, result.Host);
|
Assert.AreEqual(host, result.Host);
|
||||||
Assert.AreEqual(inboxRoute, result.InboxRoute);
|
Assert.AreEqual(inboxRoute, result.InboxRoute);
|
||||||
Assert.AreEqual(sharedInboxRoute, result.SharedInboxRoute);
|
Assert.AreEqual(sharedInboxRoute, result.SharedInboxRoute);
|
||||||
|
Assert.AreEqual(actorId, result.ActorId);
|
||||||
Assert.AreEqual(following.Length, result.Followings.Count);
|
Assert.AreEqual(following.Length, result.Followings.Count);
|
||||||
Assert.AreEqual(following[0], result.Followings[0]);
|
Assert.AreEqual(following[0], result.Followings[0]);
|
||||||
Assert.AreEqual(followingSync.Count, result.FollowingsSyncStatus.Count);
|
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);
|
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]
|
[TestMethod]
|
||||||
public async Task CreateAndGetFollower_NoSharedInbox()
|
public async Task CreateAndGetFollower_NoSharedInbox()
|
||||||
{
|
{
|
||||||
|
@ -73,9 +107,10 @@ namespace BirdsiteLive.DAL.Postgres.Tests.DataAccessLayers
|
||||||
};
|
};
|
||||||
var inboxRoute = "/myhandle/inbox";
|
var inboxRoute = "/myhandle/inbox";
|
||||||
string sharedInboxRoute = null;
|
string sharedInboxRoute = null;
|
||||||
|
var actorId = $"https://{host}/{acct}";
|
||||||
|
|
||||||
var dal = new FollowersPostgresDal(_settings);
|
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 result = await dal.GetFollowerAsync(acct, host);
|
||||||
|
|
||||||
|
@ -83,6 +118,7 @@ namespace BirdsiteLive.DAL.Postgres.Tests.DataAccessLayers
|
||||||
Assert.AreEqual(acct, result.Acct);
|
Assert.AreEqual(acct, result.Acct);
|
||||||
Assert.AreEqual(host, result.Host);
|
Assert.AreEqual(host, result.Host);
|
||||||
Assert.AreEqual(inboxRoute, result.InboxRoute);
|
Assert.AreEqual(inboxRoute, result.InboxRoute);
|
||||||
|
Assert.AreEqual(actorId, result.ActorId);
|
||||||
Assert.AreEqual(sharedInboxRoute, result.SharedInboxRoute);
|
Assert.AreEqual(sharedInboxRoute, result.SharedInboxRoute);
|
||||||
Assert.AreEqual(following.Length, result.Followings.Count);
|
Assert.AreEqual(following.Length, result.Followings.Count);
|
||||||
Assert.AreEqual(following[0], result.Followings[0]);
|
Assert.AreEqual(following[0], result.Followings[0]);
|
||||||
|
@ -103,7 +139,8 @@ namespace BirdsiteLive.DAL.Postgres.Tests.DataAccessLayers
|
||||||
var followingSync = new Dictionary<int, long>();
|
var followingSync = new Dictionary<int, long>();
|
||||||
var inboxRoute = "/myhandle1/inbox";
|
var inboxRoute = "/myhandle1/inbox";
|
||||||
var sharedInboxRoute = "/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
|
//User 2
|
||||||
acct = "myhandle2";
|
acct = "myhandle2";
|
||||||
|
@ -111,7 +148,8 @@ namespace BirdsiteLive.DAL.Postgres.Tests.DataAccessLayers
|
||||||
following = new[] { 2, 4, 5 };
|
following = new[] { 2, 4, 5 };
|
||||||
inboxRoute = "/myhandle2/inbox";
|
inboxRoute = "/myhandle2/inbox";
|
||||||
sharedInboxRoute = "/inbox2";
|
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
|
//User 2
|
||||||
acct = "myhandle3";
|
acct = "myhandle3";
|
||||||
|
@ -119,7 +157,8 @@ namespace BirdsiteLive.DAL.Postgres.Tests.DataAccessLayers
|
||||||
following = new[] { 1 };
|
following = new[] { 1 };
|
||||||
inboxRoute = "/myhandle3/inbox";
|
inboxRoute = "/myhandle3/inbox";
|
||||||
sharedInboxRoute = "/inbox3";
|
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);
|
var result = await dal.GetFollowersAsync(2);
|
||||||
Assert.AreEqual(2, result.Length);
|
Assert.AreEqual(2, result.Length);
|
||||||
|
@ -131,6 +170,43 @@ namespace BirdsiteLive.DAL.Postgres.Tests.DataAccessLayers
|
||||||
Assert.AreEqual(0, result.Length);
|
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]
|
[TestMethod]
|
||||||
public async Task CountFollowersAsync()
|
public async Task CountFollowersAsync()
|
||||||
{
|
{
|
||||||
|
@ -146,7 +222,8 @@ namespace BirdsiteLive.DAL.Postgres.Tests.DataAccessLayers
|
||||||
var followingSync = new Dictionary<int, long>();
|
var followingSync = new Dictionary<int, long>();
|
||||||
var inboxRoute = "/myhandle1/inbox";
|
var inboxRoute = "/myhandle1/inbox";
|
||||||
var sharedInboxRoute = "/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
|
//User 2
|
||||||
acct = "myhandle2";
|
acct = "myhandle2";
|
||||||
|
@ -154,7 +231,8 @@ namespace BirdsiteLive.DAL.Postgres.Tests.DataAccessLayers
|
||||||
following = new[] { 2, 4, 5 };
|
following = new[] { 2, 4, 5 };
|
||||||
inboxRoute = "/myhandle2/inbox";
|
inboxRoute = "/myhandle2/inbox";
|
||||||
sharedInboxRoute = "/inbox2";
|
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
|
//User 2
|
||||||
acct = "myhandle3";
|
acct = "myhandle3";
|
||||||
|
@ -162,7 +240,8 @@ namespace BirdsiteLive.DAL.Postgres.Tests.DataAccessLayers
|
||||||
following = new[] { 1 };
|
following = new[] { 1 };
|
||||||
inboxRoute = "/myhandle3/inbox";
|
inboxRoute = "/myhandle3/inbox";
|
||||||
sharedInboxRoute = "/inbox3";
|
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();
|
result = await dal.GetFollowersCountAsync();
|
||||||
Assert.AreEqual(3, result);
|
Assert.AreEqual(3, result);
|
||||||
|
@ -182,9 +261,10 @@ namespace BirdsiteLive.DAL.Postgres.Tests.DataAccessLayers
|
||||||
};
|
};
|
||||||
var inboxRoute = "/myhandle/inbox";
|
var inboxRoute = "/myhandle/inbox";
|
||||||
var sharedInboxRoute = "/inbox";
|
var sharedInboxRoute = "/inbox";
|
||||||
|
var actorId = $"https://{host}/{acct}";
|
||||||
|
|
||||||
var dal = new FollowersPostgresDal(_settings);
|
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 result = await dal.GetFollowerAsync(acct, host);
|
||||||
|
|
||||||
var updatedFollowing = new List<int> { 12, 19, 23, 24 };
|
var updatedFollowing = new List<int> { 12, 19, 23, 24 };
|
||||||
|
@ -222,9 +302,10 @@ namespace BirdsiteLive.DAL.Postgres.Tests.DataAccessLayers
|
||||||
};
|
};
|
||||||
var inboxRoute = "/myhandle/inbox";
|
var inboxRoute = "/myhandle/inbox";
|
||||||
var sharedInboxRoute = "/inbox";
|
var sharedInboxRoute = "/inbox";
|
||||||
|
var actorId = $"https://{host}/{acct}";
|
||||||
|
|
||||||
var dal = new FollowersPostgresDal(_settings);
|
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 result = await dal.GetFollowerAsync(acct, host);
|
||||||
|
|
||||||
var updatedFollowing = new[] { 12, 19 };
|
var updatedFollowing = new[] { 12, 19 };
|
||||||
|
@ -246,6 +327,27 @@ namespace BirdsiteLive.DAL.Postgres.Tests.DataAccessLayers
|
||||||
Assert.AreEqual(updatedFollowingSync.First().Value, result.FollowingsSyncStatus.First().Value);
|
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]
|
[TestMethod]
|
||||||
public async Task CreateAndDeleteFollower_ById()
|
public async Task CreateAndDeleteFollower_ById()
|
||||||
{
|
{
|
||||||
|
@ -260,9 +362,10 @@ namespace BirdsiteLive.DAL.Postgres.Tests.DataAccessLayers
|
||||||
};
|
};
|
||||||
var inboxRoute = "/myhandle/inbox";
|
var inboxRoute = "/myhandle/inbox";
|
||||||
var sharedInboxRoute = "/inbox";
|
var sharedInboxRoute = "/inbox";
|
||||||
|
var actorId = $"https://{host}/{acct}";
|
||||||
|
|
||||||
var dal = new FollowersPostgresDal(_settings);
|
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 result = await dal.GetFollowerAsync(acct, host);
|
||||||
Assert.IsNotNull(result);
|
Assert.IsNotNull(result);
|
||||||
|
|
||||||
|
@ -286,9 +389,10 @@ namespace BirdsiteLive.DAL.Postgres.Tests.DataAccessLayers
|
||||||
};
|
};
|
||||||
var inboxRoute = "/myhandle/inbox";
|
var inboxRoute = "/myhandle/inbox";
|
||||||
var sharedInboxRoute = "/inbox";
|
var sharedInboxRoute = "/inbox";
|
||||||
|
var actorId = $"https://{host}/{acct}";
|
||||||
|
|
||||||
var dal = new FollowersPostgresDal(_settings);
|
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 result = await dal.GetFollowerAsync(acct, host);
|
||||||
Assert.IsNotNull(result);
|
Assert.IsNotNull(result);
|
||||||
|
|
||||||
|
@ -297,5 +401,29 @@ namespace BirdsiteLive.DAL.Postgres.Tests.DataAccessLayers
|
||||||
result = await dal.GetFollowerAsync(acct, host);
|
result = await dal.GetFollowerAsync(acct, host);
|
||||||
Assert.IsNull(result);
|
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);
|
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]
|
[TestMethod]
|
||||||
public async Task CreateUpdateAndGetUser()
|
public async Task CreateUpdateAndGetUser()
|
||||||
{
|
{
|
||||||
|
@ -75,10 +93,42 @@ namespace BirdsiteLive.DAL.Postgres.Tests.DataAccessLayers
|
||||||
Assert.IsTrue(Math.Abs((now.ToUniversalTime() - result.LastSync).Milliseconds) < 100);
|
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]
|
[TestMethod]
|
||||||
public async Task CreateAndDeleteUser()
|
public async Task CreateAndDeleteUser()
|
||||||
{
|
{
|
||||||
var acct = "myid";
|
var acct = "myacct";
|
||||||
var lastTweetId = 1548L;
|
var lastTweetId = 1548L;
|
||||||
|
|
||||||
var dal = new TwitterUserPostgresDal(_settings);
|
var dal = new TwitterUserPostgresDal(_settings);
|
||||||
|
@ -93,7 +143,40 @@ namespace BirdsiteLive.DAL.Postgres.Tests.DataAccessLayers
|
||||||
}
|
}
|
||||||
|
|
||||||
[TestMethod]
|
[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);
|
var dal = new TwitterUserPostgresDal(_settings);
|
||||||
for (var i = 0; i < 1000; i++)
|
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);
|
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]
|
[TestMethod]
|
||||||
public async Task CountTwitterUsers()
|
public async Task CountTwitterUsers()
|
||||||
{
|
{
|
||||||
|
|
|
@ -18,4 +18,8 @@
|
||||||
<ProjectReference Include="..\..\BirdsiteLive.Domain\BirdsiteLive.Domain.csproj" />
|
<ProjectReference Include="..\..\BirdsiteLive.Domain\BirdsiteLive.Domain.csproj" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<Folder Include="Repository\" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
|
|
@ -21,6 +21,7 @@ namespace BirdsiteLive.Domain.Tests.BusinessUseCases
|
||||||
var twitterName = "handle";
|
var twitterName = "handle";
|
||||||
var followerInbox = "/user/testest";
|
var followerInbox = "/user/testest";
|
||||||
var inbox = "/inbox";
|
var inbox = "/inbox";
|
||||||
|
var actorId = "actorUrl";
|
||||||
|
|
||||||
var follower = new Follower
|
var follower = new Follower
|
||||||
{
|
{
|
||||||
|
@ -55,6 +56,7 @@ namespace BirdsiteLive.Domain.Tests.BusinessUseCases
|
||||||
It.Is<string>(y => y == domain),
|
It.Is<string>(y => y == domain),
|
||||||
It.Is<string>(y => y == followerInbox),
|
It.Is<string>(y => y == followerInbox),
|
||||||
It.Is<string>(y => y == inbox),
|
It.Is<string>(y => y == inbox),
|
||||||
|
It.Is<string>(y => y == actorId),
|
||||||
null,
|
null,
|
||||||
null))
|
null))
|
||||||
.Returns(Task.CompletedTask);
|
.Returns(Task.CompletedTask);
|
||||||
|
@ -80,7 +82,7 @@ namespace BirdsiteLive.Domain.Tests.BusinessUseCases
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
var action = new ProcessFollowUser(followersDalMock.Object, twitterUserDalMock.Object);
|
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
|
#region Validations
|
||||||
followersDalMock.VerifyAll();
|
followersDalMock.VerifyAll();
|
||||||
|
@ -97,7 +99,8 @@ namespace BirdsiteLive.Domain.Tests.BusinessUseCases
|
||||||
var twitterName = "handle";
|
var twitterName = "handle";
|
||||||
var followerInbox = "/user/testest";
|
var followerInbox = "/user/testest";
|
||||||
var inbox = "/inbox";
|
var inbox = "/inbox";
|
||||||
|
var actorId = "actorUrl";
|
||||||
|
|
||||||
var follower = new Follower
|
var follower = new Follower
|
||||||
{
|
{
|
||||||
Id = 1,
|
Id = 1,
|
||||||
|
@ -138,7 +141,7 @@ namespace BirdsiteLive.Domain.Tests.BusinessUseCases
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
var action = new ProcessFollowUser(followersDalMock.Object, twitterUserDalMock.Object);
|
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
|
#region Validations
|
||||||
followersDalMock.VerifyAll();
|
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);
|
var processor = new RetrieveTwitterUsersProcessor(twitterUserDalMock.Object, maxUsersNumberProviderMock.Object, loggerMock.Object);
|
||||||
processor.WaitFactor = 10;
|
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
|
#region Validations
|
||||||
maxUsersNumberProviderMock.VerifyAll();
|
maxUsersNumberProviderMock.VerifyAll();
|
||||||
|
@ -95,10 +95,10 @@ namespace BirdsiteLive.Pipeline.Tests.Processors
|
||||||
|
|
||||||
var processor = new RetrieveTwitterUsersProcessor(twitterUserDalMock.Object, maxUsersNumberProviderMock.Object, loggerMock.Object);
|
var processor = new RetrieveTwitterUsersProcessor(twitterUserDalMock.Object, maxUsersNumberProviderMock.Object, loggerMock.Object);
|
||||||
processor.WaitFactor = 2;
|
processor.WaitFactor = 2;
|
||||||
processor.GetTwitterUsersAsync(buffer, CancellationToken.None);
|
var t = processor.GetTwitterUsersAsync(buffer, CancellationToken.None);
|
||||||
|
|
||||||
await Task.Delay(300);
|
|
||||||
|
|
||||||
|
await Task.WhenAny(t, Task.Delay(300));
|
||||||
|
|
||||||
#region Validations
|
#region Validations
|
||||||
maxUsersNumberProviderMock.VerifyAll();
|
maxUsersNumberProviderMock.VerifyAll();
|
||||||
twitterUserDalMock.VerifyAll();
|
twitterUserDalMock.VerifyAll();
|
||||||
|
@ -142,9 +142,14 @@ namespace BirdsiteLive.Pipeline.Tests.Processors
|
||||||
|
|
||||||
var processor = new RetrieveTwitterUsersProcessor(twitterUserDalMock.Object, maxUsersNumberProviderMock.Object, loggerMock.Object);
|
var processor = new RetrieveTwitterUsersProcessor(twitterUserDalMock.Object, maxUsersNumberProviderMock.Object, loggerMock.Object);
|
||||||
processor.WaitFactor = 2;
|
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
|
#region Validations
|
||||||
maxUsersNumberProviderMock.VerifyAll();
|
maxUsersNumberProviderMock.VerifyAll();
|
||||||
|
@ -181,9 +186,9 @@ namespace BirdsiteLive.Pipeline.Tests.Processors
|
||||||
|
|
||||||
var processor = new RetrieveTwitterUsersProcessor(twitterUserDalMock.Object, maxUsersNumberProviderMock.Object, loggerMock.Object);
|
var processor = new RetrieveTwitterUsersProcessor(twitterUserDalMock.Object, maxUsersNumberProviderMock.Object, loggerMock.Object);
|
||||||
processor.WaitFactor = 1;
|
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
|
#region Validations
|
||||||
maxUsersNumberProviderMock.VerifyAll();
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Add table
Reference in a new issue