saving following in db

This commit is contained in:
Nicolas Constant 2020-07-07 21:03:20 -04:00
parent 34bf9ff140
commit 5f5eeb9530
No known key found for this signature in database
GPG key ID: 1E9F677FB01A5688
15 changed files with 174 additions and 43 deletions

View file

@ -3,5 +3,7 @@
public class InstanceSettings
{
public string Domain { get; set; }
public string PostgresConnString { get; set; }
}
}

View file

@ -8,6 +8,7 @@
<ProjectReference Include="..\BirdsiteLive.ActivityPub\BirdsiteLive.ActivityPub.csproj" />
<ProjectReference Include="..\BirdsiteLive.Cryptography\BirdsiteLive.Cryptography.csproj" />
<ProjectReference Include="..\BirdsiteLive.Twitter\BirdsiteLive.Twitter.csproj" />
<ProjectReference Include="..\DataAccessLayers\BirdsiteLive.DAL\BirdsiteLive.DAL.csproj" />
</ItemGroup>
</Project>

View file

@ -0,0 +1,55 @@
using System.Threading.Tasks;
using BirdsiteLive.DAL.Contracts;
namespace BirdsiteLive.Domain.BusinessUseCases
{
public interface IProcessFollowUser
{
Task ExecuteAsync(string followerUsername, string followerDomain, string followerInbox, string twitterUser);
}
public class ProcessFollowUser : IProcessFollowUser
{
private readonly IFollowersDal _followerDal;
private readonly ITwitterUserDal _twitterUserDal;
#region Ctor
public ProcessFollowUser(IFollowersDal followerDal, ITwitterUserDal twitterUserDal)
{
_followerDal = followerDal;
_twitterUserDal = twitterUserDal;
}
#endregion
public async Task ExecuteAsync(string followerUsername, string followerDomain, string followerInbox, string twitterUsername)
{
// Get Follower and Twitter Users
var follower = await _followerDal.GetFollowerAsync(followerUsername, followerDomain);
if (follower == null)
{
await _followerDal.CreateFollowerAsync(followerUsername, followerDomain, followerInbox);
follower = await _followerDal.GetFollowerAsync(followerUsername, followerDomain);
}
var twitterUser = await _twitterUserDal.GetTwitterUserAsync(twitterUsername);
if (twitterUser == null)
{
await _twitterUserDal.CreateTwitterUserAsync(twitterUsername, -1);
twitterUser = await _twitterUserDal.GetTwitterUserAsync(twitterUsername);
}
// Update Follower
var twitterUserId = twitterUser.Id;
if(!follower.Followings.Contains(twitterUserId))
follower.Followings.Add(twitterUserId);
if(!follower.FollowingsSyncStatus.ContainsKey(twitterUserId))
follower.FollowingsSyncStatus.Add(twitterUserId, -1);
follower.FollowingsSyncStatus[twitterUserId] = -1;
// Save Follower
await _followerDal.UpdateFollowerAsync(follower);
}
}
}

View file

@ -0,0 +1,7 @@
namespace BirdsiteLive.Domain.BusinessUseCases
{
public class ProcessUnfollowUser
{
}
}

View file

@ -8,6 +8,7 @@ using System.Threading.Tasks;
using BirdsiteLive.ActivityPub;
using BirdsiteLive.Common.Settings;
using BirdsiteLive.Cryptography;
using BirdsiteLive.Domain.BusinessUseCases;
using BirdsiteLive.Twitter.Models;
using Tweetinvi.Core.Exceptions;
using Tweetinvi.Models;
@ -23,15 +24,18 @@ namespace BirdsiteLive.Domain
public class UserService : IUserService
{
private readonly IProcessFollowUser _processFollowUser;
private readonly ICryptoService _cryptoService;
private readonly IActivityPubService _activityPubService;
private readonly string _host;
#region Ctor
public UserService(InstanceSettings instanceSettings, ICryptoService cryptoService, IActivityPubService activityPubService)
public UserService(InstanceSettings instanceSettings, ICryptoService cryptoService, IActivityPubService activityPubService, IProcessFollowUser processFollowUser)
{
_cryptoService = cryptoService;
_activityPubService = activityPubService;
_processFollowUser = processFollowUser;
_host = $"https://{instanceSettings.Domain.Replace("https://",string.Empty).Replace("http://", string.Empty).TrimEnd('/')}";
}
#endregion
@ -100,15 +104,21 @@ namespace BirdsiteLive.Domain
return note;
}
public async Task<bool> FollowRequestedAsync(string signature, string method, string path, string queryString, Dictionary<string, string> requestHeaders, ActivityFollow activity)
public async Task<bool> FollowRequestedAsync(string signature, string method, string path, string queryString, Dictionary<string, string> requestHeaders, ActivityFollow activity)
{
// Validate
if (!await ValidateSignature(activity.actor, signature, method, path, queryString, requestHeaders)) return false;
var sigValidation = await ValidateSignature(activity.actor, signature, method, path, queryString, requestHeaders);
if (!sigValidation.SignatureIsValidated) return false;
// Save Follow in DB
// Send Accept Activity
var targetHost = activity.actor.Replace("https://", string.Empty).Split('/').First();
var followerUserName = sigValidation.User.name.ToLowerInvariant();
var followerHost = sigValidation.User.url.Replace("https://", string.Empty).Split('/').First();
var followerInbox = sigValidation.User.inbox;
var twitterUser = activity.apObject.Split('/').Last().Replace("@", string.Empty);
await _processFollowUser.ExecuteAsync(followerUserName, followerHost, followerInbox, twitterUser);
// Send Accept Activity
//var followerHost = activity.actor.Replace("https://", string.Empty).Split('/').First();
var acceptFollow = new ActivityAcceptFollow()
{
context = "https://www.w3.org/ns/activitystreams",
@ -123,11 +133,11 @@ namespace BirdsiteLive.Domain
apObject = activity.apObject
}
};
var result = await _activityPubService.PostDataAsync(acceptFollow, targetHost, activity.apObject);
var result = await _activityPubService.PostDataAsync(acceptFollow, followerHost, activity.apObject);
return result == HttpStatusCode.Accepted;
}
private async Task<bool> ValidateSignature(string actor, string rawSig, string method, string path, string queryString, Dictionary<string, string> requestHeaders)
private async Task<SignatureValidationResult> ValidateSignature(string actor, string rawSig, string method, string path, string queryString, Dictionary<string, string> requestHeaders)
{
var signatures = rawSig.Split(',');
var signature_header = new Dictionary<string, string>();
@ -184,7 +194,17 @@ namespace BirdsiteLive.Domain
var result = signKey.VerifyData(Encoding.UTF8.GetBytes(toSign.ToString()), sig, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1);
return result;
return new SignatureValidationResult()
{
SignatureIsValidated = result,
User = remoteUser
};
}
}
public class SignatureValidationResult
{
public bool SignatureIsValidated { get; set; }
public Actor User { get; set; }
}
}

View file

@ -17,6 +17,7 @@
<ProjectReference Include="..\BirdsiteLive.Cryptography\BirdsiteLive.Cryptography.csproj" />
<ProjectReference Include="..\BirdsiteLive.Domain\BirdsiteLive.Domain.csproj" />
<ProjectReference Include="..\BirdsiteLive.Twitter\BirdsiteLive.Twitter.csproj" />
<ProjectReference Include="..\DataAccessLayers\BirdsiteLive.DAL.Postgres\BirdsiteLive.DAL.Postgres.csproj" />
</ItemGroup>

View file

@ -18,10 +18,10 @@ namespace BirdsiteLive.Controllers
private readonly InstanceSettings _settings;
#region Ctor
public WellKnownController(IOptions<InstanceSettings> settings, ITwitterService twitterService)
public WellKnownController(InstanceSettings settings, ITwitterService twitterService)
{
_twitterService = twitterService;
_settings = settings.Value;
_settings = settings;
}
#endregion

View file

@ -1,6 +1,7 @@
using System;
using System.Threading;
using System.Threading.Tasks;
using BirdsiteLive.DAL.Contracts;
using BirdsiteLive.Domain;
using Microsoft.Extensions.Hosting;
@ -8,22 +9,41 @@ namespace BirdsiteLive.Services
{
public class FederationService : BackgroundService
{
private readonly IDbInitializerDal _dbInitializerDal;
private readonly IUserService _userService;
#region Ctor
public FederationService(IUserService userService)
public FederationService(IDbInitializerDal dbInitializerDal, IUserService userService)
{
_dbInitializerDal = dbInitializerDal;
_userService = userService;
}
#endregion
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
await DbInitAsync();
for (;;)
{
Console.WriteLine("RUNNING SERVICE");
await Task.Delay(TimeSpan.FromSeconds(5), stoppingToken);
}
}
private async Task DbInitAsync()
{
var currentVersion = await _dbInitializerDal.GetCurrentDbVersionAsync();
var mandatoryVersion = _dbInitializerDal.GetMandatoryDbVersion();
if (currentVersion == null)
{
await _dbInitializerDal.InitDbAsync();
}
else if (currentVersion != mandatoryVersion)
{
throw new NotImplementedException();
}
}
}
}

View file

@ -3,6 +3,9 @@ using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using BirdsiteLive.Common.Settings;
using BirdsiteLive.DAL.Contracts;
using BirdsiteLive.DAL.Postgres.DataAccessLayers;
using BirdsiteLive.DAL.Postgres.Settings;
using BirdsiteLive.Models;
using Lamar;
using Microsoft.AspNetCore.Builder;
@ -34,7 +37,7 @@ namespace BirdsiteLive
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.Configure<InstanceSettings>(Configuration.GetSection("Instance"));
//services.Configure<InstanceSettings>(Configuration.GetSection("Instance"));
//services.Configure<TwitterSettings>(Configuration.GetSection("Twitter"));
services.AddControllersWithViews();
@ -48,15 +51,27 @@ namespace BirdsiteLive
var instanceSettings = Configuration.GetSection("Instance").Get<InstanceSettings>();
services.For<InstanceSettings>().Use(x => instanceSettings);
var postgresSettings = new PostgresSettings
{
ConnString = instanceSettings.PostgresConnString
};
services.For<PostgresSettings>().Use(x => postgresSettings);
services.For<ITwitterUserDal>().Use<TwitterUserPostgresDal>().Singleton();
services.For<IFollowersDal>().Use<FollowersPostgresDal>().Singleton();
services.For<IDbInitializerDal>().Use<DbInitializerPostgresDal>().Singleton();
services.Scan(_ =>
{
_.Assembly("BirdsiteLive.Twitter");
_.Assembly("BirdsiteLive.Domain");
_.Assembly("BirdsiteLive.DAL");
_.Assembly("BirdsiteLive.DAL.Postgres");
_.TheCallingAssembly();
//_.AssemblyContainingType<IDal>();
//_.Exclude(type => type.Name.Contains("Settings"));
_.WithDefaultConventions();
_.LookForRegistries();

View file

@ -8,7 +8,8 @@
},
"AllowedHosts": "*",
"Instance": {
"Domain": "domain.name"
"Domain": "domain.name",
"PostgresConnString": "Host=127.0.0.1;Username=username;Password=password;Database=mydb"
},
"Twitter": {
"ConsumerKey": "twitter.api.key",

View file

@ -20,8 +20,11 @@ namespace BirdsiteLive.DAL.Postgres.DataAccessLayers
}
#endregion
public async Task CreateFollowerAsync(string acct, string host, int[] followings, Dictionary<int, long> followingSyncStatus, string inboxUrl)
public async Task CreateFollowerAsync(string acct, string host, string inboxUrl, int[] followings = null, Dictionary<int, long> followingSyncStatus = null)
{
if(followings == null) followings = new int[0];
if(followingSyncStatus == null) followingSyncStatus = new Dictionary<int, long>();
var serializedDic = JsonConvert.SerializeObject(followingSyncStatus);
acct = acct.ToLowerInvariant();
@ -68,18 +71,19 @@ namespace BirdsiteLive.DAL.Postgres.DataAccessLayers
}
}
public async Task UpdateFollowerAsync(int id, int[] followings, Dictionary<int, long> followingsSyncStatus)
public async Task UpdateFollowerAsync(Follower follower)
{
if (id == default) throw new ArgumentException("id");
if (follower == default) throw new ArgumentException("follower");
if (follower.Id == default) throw new ArgumentException("id");
var serializedDic = JsonConvert.SerializeObject(followingsSyncStatus);
var serializedDic = JsonConvert.SerializeObject(follower.FollowingsSyncStatus);
var query = $"UPDATE {_settings.FollowersTableName} SET followings = @followings, followingsSyncStatus = CAST(@followingsSyncStatus as json) WHERE id = @id";
using (var dbConnection = Connection)
{
dbConnection.Open();
await dbConnection.QueryAsync(query, new { id, followings, followingsSyncStatus = serializedDic });
await dbConnection.QueryAsync(query, new { follower.Id, follower.Followings, followingsSyncStatus = serializedDic });
}
}
@ -125,7 +129,7 @@ namespace BirdsiteLive.DAL.Postgres.DataAccessLayers
Acct = follower.Acct,
Host = follower.Host,
InboxUrl = follower.InboxUrl,
Followings = follower.Followings,
Followings = follower.Followings.ToList(),
FollowingsSyncStatus = JsonConvert.DeserializeObject<Dictionary<int,long>>(follower.FollowingsSyncStatus)
};
}

View file

@ -4,9 +4,9 @@
{
public string ConnString { get; set; }
public string DbVersionTableName { get; set; } = "db-version";
public string TwitterUserTableName { get; set; } = "twitter-users";
public string DbVersionTableName { get; set; } = "db_version";
public string TwitterUserTableName { get; set; } = "twitter_users";
public string FollowersTableName { get; set; } = "followers";
public string CachedTweetsTableName { get; set; } = "cached-tweets";
public string CachedTweetsTableName { get; set; } = "cached_tweets";
}
}

View file

@ -7,9 +7,10 @@ namespace BirdsiteLive.DAL.Contracts
public interface IFollowersDal
{
Task<Follower> GetFollowerAsync(string acct, string host);
Task CreateFollowerAsync(string acct, string host, int[] followings, Dictionary<int, long> followingSyncStatus, string inboxUrl);
Task CreateFollowerAsync(string acct, string host, string inboxUrl, int[] followings = null,
Dictionary<int, long> followingSyncStatus = null);
Task<Follower[]> GetFollowersAsync(int followedUserId);
Task UpdateFollowerAsync(int id, int[] followings, Dictionary<int, long> followingSyncStatus);
Task UpdateFollowerAsync(Follower follower);
Task DeleteFollowerAsync(int id);
Task DeleteFollowerAsync(string acct, string host);
}

View file

@ -6,7 +6,7 @@ namespace BirdsiteLive.DAL.Models
{
public int Id { get; set; }
public int[] Followings { get; set; }
public List<int> Followings { get; set; }
public Dictionary<int, long> FollowingsSyncStatus { get; set; }
public string Acct { get; set; }

View file

@ -48,7 +48,7 @@ namespace BirdsiteLive.DAL.Postgres.Tests.DataAccessLayers
var inboxUrl = "https://domain.ext/myhandle/inbox";
var dal = new FollowersPostgresDal(_settings);
await dal.CreateFollowerAsync(acct, host, following, followingSync, inboxUrl);
await dal.CreateFollowerAsync(acct, host, inboxUrl, following, followingSync);
var result = await dal.GetFollowerAsync(acct, host);
@ -56,7 +56,7 @@ namespace BirdsiteLive.DAL.Postgres.Tests.DataAccessLayers
Assert.AreEqual(acct, result.Acct);
Assert.AreEqual(host, result.Host);
Assert.AreEqual(inboxUrl, result.InboxUrl);
Assert.AreEqual(following.Length, result.Followings.Length);
Assert.AreEqual(following.Length, result.Followings.Count);
Assert.AreEqual(following[0], result.Followings[0]);
Assert.AreEqual(followingSync.Count, result.FollowingsSyncStatus.Count);
Assert.AreEqual(followingSync.First().Key, result.FollowingsSyncStatus.First().Key);
@ -74,21 +74,21 @@ namespace BirdsiteLive.DAL.Postgres.Tests.DataAccessLayers
var following = new[] { 1,2,3 };
var followingSync = new Dictionary<int, long>();
var inboxUrl = "https://domain.ext/myhandle1/inbox";
await dal.CreateFollowerAsync(acct, host, following, followingSync, inboxUrl);
await dal.CreateFollowerAsync(acct, host, inboxUrl, following, followingSync);
//User 2
acct = "myhandle2";
host = "domain.ext";
following = new[] { 2, 4, 5 };
inboxUrl = "https://domain.ext/myhandle2/inbox";
await dal.CreateFollowerAsync(acct, host, following, followingSync, inboxUrl);
await dal.CreateFollowerAsync(acct, host, inboxUrl, following, followingSync);
//User 2
acct = "myhandle3";
host = "domain.ext";
following = new[] { 1 };
inboxUrl = "https://domain.ext/myhandle3/inbox";
await dal.CreateFollowerAsync(acct, host, following, followingSync, inboxUrl);
await dal.CreateFollowerAsync(acct, host, inboxUrl, following, followingSync);
var result = await dal.GetFollowersAsync(2);
Assert.AreEqual(2, result.Length);
@ -115,22 +115,24 @@ namespace BirdsiteLive.DAL.Postgres.Tests.DataAccessLayers
var inboxUrl = "https://domain.ext/myhandle/inbox";
var dal = new FollowersPostgresDal(_settings);
await dal.CreateFollowerAsync(acct, host, following, followingSync, inboxUrl);
await dal.CreateFollowerAsync(acct, host, inboxUrl, following, followingSync);
var result = await dal.GetFollowerAsync(acct, host);
var updatedFollowing = new[] { 12, 19, 23, 24 };
var updatedFollowingSync = new Dictionary<int, long>()
{
var updatedFollowing = new List<int> { 12, 19, 23, 24 };
var updatedFollowingSync = new Dictionary<int, long>(){
{12, 170L},
{19, 171L},
{23, 172L},
{24, 173L}
};
result.Followings = updatedFollowing.ToList();
result.FollowingsSyncStatus = updatedFollowingSync;
await dal.UpdateFollowerAsync(result.Id, updatedFollowing, updatedFollowingSync);
await dal.UpdateFollowerAsync(result);
result = await dal.GetFollowerAsync(acct, host);
Assert.AreEqual(updatedFollowing.Length, result.Followings.Length);
Assert.AreEqual(updatedFollowing.Count, result.Followings.Count);
Assert.AreEqual(updatedFollowing[0], result.Followings[0]);
Assert.AreEqual(updatedFollowingSync.Count, result.FollowingsSyncStatus.Count);
Assert.AreEqual(updatedFollowingSync.First().Key, result.FollowingsSyncStatus.First().Key);
@ -152,7 +154,7 @@ namespace BirdsiteLive.DAL.Postgres.Tests.DataAccessLayers
var inboxUrl = "https://domain.ext/myhandle/inbox";
var dal = new FollowersPostgresDal(_settings);
await dal.CreateFollowerAsync(acct, host, following, followingSync, inboxUrl);
await dal.CreateFollowerAsync(acct, host, inboxUrl, following, followingSync);
var result = await dal.GetFollowerAsync(acct, host);
var updatedFollowing = new[] { 12, 19 };
@ -161,11 +163,13 @@ namespace BirdsiteLive.DAL.Postgres.Tests.DataAccessLayers
{12, 170L},
{19, 171L}
};
result.Followings = updatedFollowing.ToList();
result.FollowingsSyncStatus = updatedFollowingSync;
await dal.UpdateFollowerAsync(result.Id, updatedFollowing, updatedFollowingSync);
await dal.UpdateFollowerAsync(result);
result = await dal.GetFollowerAsync(acct, host);
Assert.AreEqual(updatedFollowing.Length, result.Followings.Length);
Assert.AreEqual(updatedFollowing.Length, result.Followings.Count);
Assert.AreEqual(updatedFollowing[0], result.Followings[0]);
Assert.AreEqual(updatedFollowingSync.Count, result.FollowingsSyncStatus.Count);
Assert.AreEqual(updatedFollowingSync.First().Key, result.FollowingsSyncStatus.First().Key);
@ -187,7 +191,7 @@ namespace BirdsiteLive.DAL.Postgres.Tests.DataAccessLayers
var inboxUrl = "https://domain.ext/myhandle/inbox";
var dal = new FollowersPostgresDal(_settings);
await dal.CreateFollowerAsync(acct, host, following, followingSync, inboxUrl);
await dal.CreateFollowerAsync(acct, host, inboxUrl, following, followingSync);
var result = await dal.GetFollowerAsync(acct, host);
Assert.IsNotNull(result);
@ -212,7 +216,7 @@ namespace BirdsiteLive.DAL.Postgres.Tests.DataAccessLayers
var inboxUrl = "https://domain.ext/myhandle/inbox";
var dal = new FollowersPostgresDal(_settings);
await dal.CreateFollowerAsync(acct, host, following, followingSync, inboxUrl);
await dal.CreateFollowerAsync(acct, host, inboxUrl, following, followingSync);
var result = await dal.GetFollowerAsync(acct, host);
Assert.IsNotNull(result);