, string, string>(h => h.Key.ToLowerInvariant(), h => h.Value);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/BirdsiteLive/Views/Debuging/Index.cshtml b/src/BirdsiteLive/Views/Debuging/Index.cshtml
index 5bcde75..e343cf2 100644
--- a/src/BirdsiteLive/Views/Debuging/Index.cshtml
+++ b/src/BirdsiteLive/Views/Debuging/Index.cshtml
@@ -23,4 +23,10 @@
+
+
+
\ No newline at end of file
diff --git a/src/BirdsiteLive/Views/Migration/Delete.cshtml b/src/BirdsiteLive/Views/Migration/Delete.cshtml
new file mode 100644
index 0000000..568b08a
--- /dev/null
+++ b/src/BirdsiteLive/Views/Migration/Delete.cshtml
@@ -0,0 +1,51 @@
+@model MigrationData
+@{
+ ViewData["Title"] = "Migration";
+}
+
+
+ @if (!string.IsNullOrWhiteSpace(ViewData.Model.ErrorMessage))
+ {
+
+ @ViewData.Model.ErrorMessage
+
+ }
+
+ @if (ViewData.Model.MigrationSuccess)
+ {
+
+ The mirror has been successfully deleted
+
+ }
+
+
Delete @@@ViewData.Model.Acct mirror
+
+ @if (!ViewData.Model.IsTweetProvided)
+ {
+
What is needed?
+
+
You'll need access to the Twitter account to provide proof of ownership.
+
+
What will deletion do?
+
+
+ Deletion will remove all followers, delete the account and will be blacklisted so that it can't be recreated.
+
+ }
+
+
Start the deletion!
+
+
Please copy and post this string in a public Tweet (the string must be untampered, but you can write anything you want before or after it):
+
+
+
+
+
Provide deletion information:
+
+
\ No newline at end of file
diff --git a/src/BirdsiteLive/Views/Migration/Index.cshtml b/src/BirdsiteLive/Views/Migration/Index.cshtml
new file mode 100644
index 0000000..2d1db53
--- /dev/null
+++ b/src/BirdsiteLive/Views/Migration/Index.cshtml
@@ -0,0 +1,66 @@
+@model MigrationData
+@{
+ ViewData["Title"] = "Migration";
+}
+
+
+ @if (!string.IsNullOrWhiteSpace(ViewData.Model.ErrorMessage))
+ {
+
+ @ViewData.Model.ErrorMessage
+
+ }
+
+ @if (ViewData.Model.MigrationSuccess)
+ {
+
+ The mirror has been successfully migrated
+
+ }
+
+
Migrate @@@ViewData.Model.Acct mirror to my Fediverse account
+
+ @if (!ViewData.Model.IsAcctProvided && !ViewData.Model.IsTweetProvided)
+ {
+
What is needed?
+
+
You'll need a Fediverse account and access to the Twitter account to provide proof of ownership.
+
+
What will migration do?
+
+
+ Migration will notify followers of the migration of the mirror account to your fediverse account and will be disabled after that.
+
+ }
+
+
Start the migration!
+
+
Please copy and post this string in a public Tweet (the string must be untampered, but you can write anything you want before or after it):
+
+
+
+
+
Provide migration information:
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/BirdsiteLive/Views/Users/ApiSaturated.cshtml b/src/BirdsiteLive/Views/Users/ApiSaturated.cshtml
new file mode 100644
index 0000000..1f807aa
--- /dev/null
+++ b/src/BirdsiteLive/Views/Users/ApiSaturated.cshtml
@@ -0,0 +1,13 @@
+@using BirdsiteLive.Controllers;
+@{
+ ViewData["Title"] = "Api Saturated";
+}
+
+
+
429 Too Many Requests
+
+
+ The API is saturated.
+ Please consider using another instance.
+
+
\ No newline at end of file
diff --git a/src/BirdsiteLive/Views/Users/Index.cshtml b/src/BirdsiteLive/Views/Users/Index.cshtml
index f3812cc..d984892 100644
--- a/src/BirdsiteLive/Views/Users/Index.cshtml
+++ b/src/BirdsiteLive/Views/Users/Index.cshtml
@@ -31,7 +31,20 @@
- @if (ViewData.Model.Protected)
+ @if (ViewData.Model.Deleted)
+ {
+
+ This mirror has been deleted by its Twitter owner.
+
+ }
+ else if (!string.IsNullOrEmpty(ViewData.Model.MovedTo))
+ {
+
+ This account has been migrated by its Twitter owner and has been disabled.
+ You can follow this user at
@ViewData.Model.MovedToAcct.
+
+ }
+ else if (ViewData.Model.Protected)
{
This account is protected, BirdsiteLIVE cannot fetch their tweets and will not provide follow support until it is unprotected again.
@@ -50,4 +63,8 @@
}
+
+
\ No newline at end of file
diff --git a/src/BirdsiteLive/appsettings.json b/src/BirdsiteLive/appsettings.json
index 416493c..d734200 100644
--- a/src/BirdsiteLive/appsettings.json
+++ b/src/BirdsiteLive/appsettings.json
@@ -30,7 +30,9 @@
"DiscloseInstanceRestrictions": false,
"SensitiveTwitterAccounts": null,
"FailingTwitterUserCleanUpThreshold": 700,
- "MaxStatusFetchAge": 0
+ "MaxStatusFetchAge": 0,
+ "FailingFollowerCleanUpThreshold": 30000,
+ "UserCacheCapacity": 10000
},
"Db": {
"Type": "postgres",
diff --git a/src/BirdsiteLive/wwwroot/css/birdsite.css b/src/BirdsiteLive/wwwroot/css/birdsite.css
index 5b6023c..159a50a 100644
--- a/src/BirdsiteLive/wwwroot/css/birdsite.css
+++ b/src/BirdsiteLive/wwwroot/css/birdsite.css
@@ -71,3 +71,18 @@
margin-left: 60px;
/*font-weight: bold;*/
}
+
+.user-owner {
+ font-size: .8em;
+ padding-top: 20px;
+}
+
+/** Migration **/
+
+.migration__title {
+ font-size: 1.8em;
+}
+
+.migration__subtitle {
+ font-size: 1.4em;
+}
\ No newline at end of file
diff --git a/src/DataAccessLayers/BirdsiteLive.DAL.Postgres/BirdsiteLive.DAL.Postgres.csproj b/src/DataAccessLayers/BirdsiteLive.DAL.Postgres/BirdsiteLive.DAL.Postgres.csproj
index 690c878..0baeb19 100644
--- a/src/DataAccessLayers/BirdsiteLive.DAL.Postgres/BirdsiteLive.DAL.Postgres.csproj
+++ b/src/DataAccessLayers/BirdsiteLive.DAL.Postgres/BirdsiteLive.DAL.Postgres.csproj
@@ -1,7 +1,7 @@
- netstandard2.0
+ net6.0
diff --git a/src/DataAccessLayers/BirdsiteLive.DAL.Postgres/DataAccessLayers/DbInitializerPostgresDal.cs b/src/DataAccessLayers/BirdsiteLive.DAL.Postgres/DataAccessLayers/DbInitializerPostgresDal.cs
index 2e3acea..55b38f6 100644
--- a/src/DataAccessLayers/BirdsiteLive.DAL.Postgres/DataAccessLayers/DbInitializerPostgresDal.cs
+++ b/src/DataAccessLayers/BirdsiteLive.DAL.Postgres/DataAccessLayers/DbInitializerPostgresDal.cs
@@ -23,7 +23,7 @@ namespace BirdsiteLive.DAL.Postgres.DataAccessLayers
public class DbInitializerPostgresDal : PostgresBase, IDbInitializerDal
{
private readonly PostgresTools _tools;
- private readonly Version _currentVersion = new Version(2, 3);
+ private readonly Version _currentVersion = new Version(2, 5);
private const string DbVersionType = "db-version";
#region Ctor
@@ -134,7 +134,9 @@ namespace BirdsiteLive.DAL.Postgres.DataAccessLayers
new Tuple(new Version(1,0), new Version(2,0)),
new Tuple(new Version(2,0), new Version(2,1)),
new Tuple(new Version(2,1), new Version(2,2)),
- new Tuple(new Version(2,2), new Version(2,3))
+ new Tuple(new Version(2,2), new Version(2,3)),
+ new Tuple(new Version(2,3), new Version(2,4)),
+ new Tuple(new Version(2,4), new Version(2,5))
};
}
@@ -163,6 +165,25 @@ namespace BirdsiteLive.DAL.Postgres.DataAccessLayers
var addPostingError = $@"ALTER TABLE {_settings.FollowersTableName} ADD postingErrorCount SMALLINT";
await _tools.ExecuteRequestAsync(addPostingError);
}
+ else if (from == new Version(2, 3) && to == new Version(2, 4))
+ {
+ var alterLastSync = $@"ALTER TABLE {_settings.TwitterUserTableName} ALTER COLUMN fetchingErrorCount TYPE INTEGER";
+ await _tools.ExecuteRequestAsync(alterLastSync);
+
+ var alterPostingError = $@"ALTER TABLE {_settings.FollowersTableName} ALTER COLUMN postingErrorCount TYPE INTEGER";
+ await _tools.ExecuteRequestAsync(alterPostingError);
+ }
+ else if (from == new Version(2, 4) && to == new Version(2, 5))
+ {
+ var addMovedTo = $@"ALTER TABLE {_settings.TwitterUserTableName} ADD movedTo VARCHAR(2048)";
+ await _tools.ExecuteRequestAsync(addMovedTo);
+
+ var addMovedToAcct = $@"ALTER TABLE {_settings.TwitterUserTableName} ADD movedToAcct VARCHAR(305)";
+ await _tools.ExecuteRequestAsync(addMovedToAcct);
+
+ var addDeletedToAcct = $@"ALTER TABLE {_settings.TwitterUserTableName} ADD deleted BOOLEAN";
+ await _tools.ExecuteRequestAsync(addDeletedToAcct);
+ }
else
{
throw new NotImplementedException();
diff --git a/src/DataAccessLayers/BirdsiteLive.DAL.Postgres/DataAccessLayers/TwitterUserPostgresDal.cs b/src/DataAccessLayers/BirdsiteLive.DAL.Postgres/DataAccessLayers/TwitterUserPostgresDal.cs
index 11214d4..d542a76 100644
--- a/src/DataAccessLayers/BirdsiteLive.DAL.Postgres/DataAccessLayers/TwitterUserPostgresDal.cs
+++ b/src/DataAccessLayers/BirdsiteLive.DAL.Postgres/DataAccessLayers/TwitterUserPostgresDal.cs
@@ -18,7 +18,7 @@ namespace BirdsiteLive.DAL.Postgres.DataAccessLayers
}
#endregion
- public async Task CreateTwitterUserAsync(string acct, long lastTweetPostedId)
+ public async Task CreateTwitterUserAsync(string acct, long lastTweetPostedId, string movedTo = null, string movedToAcct = null)
{
acct = acct.ToLowerInvariant();
@@ -27,8 +27,15 @@ namespace BirdsiteLive.DAL.Postgres.DataAccessLayers
dbConnection.Open();
await dbConnection.ExecuteAsync(
- $"INSERT INTO {_settings.TwitterUserTableName} (acct,lastTweetPostedId,lastTweetSynchronizedForAllFollowersId) VALUES(@acct,@lastTweetPostedId,@lastTweetSynchronizedForAllFollowersId)",
- new { acct, lastTweetPostedId, lastTweetSynchronizedForAllFollowersId = lastTweetPostedId });
+ $"INSERT INTO {_settings.TwitterUserTableName} (acct,lastTweetPostedId,lastTweetSynchronizedForAllFollowersId, movedTo, movedToAcct) VALUES(@acct,@lastTweetPostedId,@lastTweetSynchronizedForAllFollowersId,@movedTo,@movedToAcct)",
+ new
+ {
+ acct,
+ lastTweetPostedId,
+ lastTweetSynchronizedForAllFollowersId = lastTweetPostedId,
+ movedTo,
+ movedToAcct
+ });
}
}
@@ -62,7 +69,7 @@ namespace BirdsiteLive.DAL.Postgres.DataAccessLayers
public async Task GetTwitterUsersCountAsync()
{
- var query = $"SELECT COUNT(*) FROM {_settings.TwitterUserTableName}";
+ var query = $"SELECT COUNT(*) FROM {_settings.TwitterUserTableName} WHERE (movedTo = '') IS NOT FALSE AND deleted IS NOT TRUE";
using (var dbConnection = Connection)
{
@@ -75,7 +82,7 @@ namespace BirdsiteLive.DAL.Postgres.DataAccessLayers
public async Task GetFailingTwitterUsersCountAsync()
{
- var query = $"SELECT COUNT(*) FROM {_settings.TwitterUserTableName} WHERE fetchingErrorCount > 0";
+ var query = $"SELECT COUNT(*) FROM {_settings.TwitterUserTableName} WHERE fetchingErrorCount > 0 AND (movedTo = '') IS NOT FALSE AND deleted IS NOT TRUE";
using (var dbConnection = Connection)
{
@@ -86,9 +93,10 @@ namespace BirdsiteLive.DAL.Postgres.DataAccessLayers
}
}
- public async Task GetAllTwitterUsersAsync(int maxNumber)
+ public async Task GetAllTwitterUsersAsync(int maxNumber, bool retrieveDisabledUser)
{
- var query = $"SELECT * FROM {_settings.TwitterUserTableName} ORDER BY lastSync ASC NULLS FIRST LIMIT @maxNumber";
+ var query = $"SELECT * FROM {_settings.TwitterUserTableName} WHERE (movedTo = '') IS NOT FALSE AND deleted IS NOT TRUE ORDER BY lastSync ASC NULLS FIRST LIMIT @maxNumber";
+ if (retrieveDisabledUser) query = $"SELECT * FROM {_settings.TwitterUserTableName} ORDER BY lastSync ASC NULLS FIRST LIMIT @maxNumber";
using (var dbConnection = Connection)
{
@@ -99,9 +107,10 @@ namespace BirdsiteLive.DAL.Postgres.DataAccessLayers
}
}
- public async Task GetAllTwitterUsersAsync()
+ public async Task GetAllTwitterUsersAsync(bool retrieveDisabledUser)
{
- var query = $"SELECT * FROM {_settings.TwitterUserTableName}";
+ var query = $"SELECT * FROM {_settings.TwitterUserTableName} WHERE (movedTo = '') IS NOT FALSE AND deleted IS NOT TRUE";
+ if(retrieveDisabledUser) query = $"SELECT * FROM {_settings.TwitterUserTableName}";
using (var dbConnection = Connection)
{
@@ -112,26 +121,36 @@ namespace BirdsiteLive.DAL.Postgres.DataAccessLayers
}
}
- public async Task UpdateTwitterUserAsync(int id, long lastTweetPostedId, long lastTweetSynchronizedForAllFollowersId, int fetchingErrorCount, DateTime lastSync)
+ public async Task UpdateTwitterUserAsync(int id, long lastTweetPostedId, long lastTweetSynchronizedForAllFollowersId, int fetchingErrorCount, DateTime lastSync, string movedTo, string movedToAcct, bool deleted)
{
if(id == default) throw new ArgumentException("id");
if(lastTweetPostedId == default) throw new ArgumentException("lastTweetPostedId");
if(lastTweetSynchronizedForAllFollowersId == default) throw new ArgumentException("lastTweetSynchronizedForAllFollowersId");
if(lastSync == default) throw new ArgumentException("lastSync");
- var query = $"UPDATE {_settings.TwitterUserTableName} SET lastTweetPostedId = @lastTweetPostedId, lastTweetSynchronizedForAllFollowersId = @lastTweetSynchronizedForAllFollowersId, fetchingErrorCount = @fetchingErrorCount, lastSync = @lastSync WHERE id = @id";
+ var query = $"UPDATE {_settings.TwitterUserTableName} SET lastTweetPostedId = @lastTweetPostedId, lastTweetSynchronizedForAllFollowersId = @lastTweetSynchronizedForAllFollowersId, fetchingErrorCount = @fetchingErrorCount, lastSync = @lastSync, movedTo = @movedTo, movedToAcct = @movedToAcct, deleted = @deleted WHERE id = @id";
using (var dbConnection = Connection)
{
dbConnection.Open();
- await dbConnection.QueryAsync(query, new { id, lastTweetPostedId, lastTweetSynchronizedForAllFollowersId, fetchingErrorCount, lastSync = lastSync.ToUniversalTime() });
+ await dbConnection.QueryAsync(query, new
+ {
+ id,
+ lastTweetPostedId,
+ lastTweetSynchronizedForAllFollowersId,
+ fetchingErrorCount,
+ lastSync = lastSync.ToUniversalTime(),
+ movedTo,
+ movedToAcct,
+ deleted
+ });
}
}
public async Task UpdateTwitterUserAsync(SyncTwitterUser user)
{
- await UpdateTwitterUserAsync(user.Id, user.LastTweetPostedId, user.LastTweetSynchronizedForAllFollowersId, user.FetchingErrorCount, user.LastSync);
+ await UpdateTwitterUserAsync(user.Id, user.LastTweetPostedId, user.LastTweetSynchronizedForAllFollowersId, user.FetchingErrorCount, user.LastSync, user.MovedTo, user.MovedToAcct, user.Deleted);
}
public async Task DeleteTwitterUserAsync(string acct)
diff --git a/src/DataAccessLayers/BirdsiteLive.DAL/BirdsiteLive.DAL.csproj b/src/DataAccessLayers/BirdsiteLive.DAL/BirdsiteLive.DAL.csproj
index 84e0bf0..847e55f 100644
--- a/src/DataAccessLayers/BirdsiteLive.DAL/BirdsiteLive.DAL.csproj
+++ b/src/DataAccessLayers/BirdsiteLive.DAL/BirdsiteLive.DAL.csproj
@@ -1,7 +1,7 @@
- netstandard2.0
+ net6.0
diff --git a/src/DataAccessLayers/BirdsiteLive.DAL/Contracts/ITwitterUserDal.cs b/src/DataAccessLayers/BirdsiteLive.DAL/Contracts/ITwitterUserDal.cs
index ef2cc36..0c58881 100644
--- a/src/DataAccessLayers/BirdsiteLive.DAL/Contracts/ITwitterUserDal.cs
+++ b/src/DataAccessLayers/BirdsiteLive.DAL/Contracts/ITwitterUserDal.cs
@@ -6,12 +6,13 @@ namespace BirdsiteLive.DAL.Contracts
{
public interface ITwitterUserDal
{
- Task CreateTwitterUserAsync(string acct, long lastTweetPostedId);
+ Task CreateTwitterUserAsync(string acct, long lastTweetPostedId, string movedTo = null,
+ string movedToAcct = null);
Task GetTwitterUserAsync(string acct);
Task GetTwitterUserAsync(int id);
- Task GetAllTwitterUsersAsync(int maxNumber);
- Task GetAllTwitterUsersAsync();
- Task UpdateTwitterUserAsync(int id, long lastTweetPostedId, long lastTweetSynchronizedForAllFollowersId, int fetchingErrorCount, DateTime lastSync);
+ Task GetAllTwitterUsersAsync(int maxNumber, bool retrieveDisabledUser);
+ Task GetAllTwitterUsersAsync(bool retrieveDisabledUser);
+ Task UpdateTwitterUserAsync(int id, long lastTweetPostedId, long lastTweetSynchronizedForAllFollowersId, int fetchingErrorCount, DateTime lastSync, string movedTo, string movedToAcct, bool deleted);
Task UpdateTwitterUserAsync(SyncTwitterUser user);
Task DeleteTwitterUserAsync(string acct);
Task DeleteTwitterUserAsync(int id);
diff --git a/src/DataAccessLayers/BirdsiteLive.DAL/Models/SyncTwitterUser.cs b/src/DataAccessLayers/BirdsiteLive.DAL/Models/SyncTwitterUser.cs
index 8b18ba1..5089afc 100644
--- a/src/DataAccessLayers/BirdsiteLive.DAL/Models/SyncTwitterUser.cs
+++ b/src/DataAccessLayers/BirdsiteLive.DAL/Models/SyncTwitterUser.cs
@@ -12,6 +12,11 @@ namespace BirdsiteLive.DAL.Models
public DateTime LastSync { get; set; }
- public int FetchingErrorCount { get; set; } //TODO: update DAL
+ public int FetchingErrorCount { get; set; }
+
+ public string MovedTo { get; set; }
+ public string MovedToAcct { get; set; }
+
+ public bool Deleted { get; set; }
}
}
\ No newline at end of file
diff --git a/src/Tests/BSLManager.Tests/BSLManager.Tests.csproj b/src/Tests/BSLManager.Tests/BSLManager.Tests.csproj
index 033cfe1..8d350bd 100644
--- a/src/Tests/BSLManager.Tests/BSLManager.Tests.csproj
+++ b/src/Tests/BSLManager.Tests/BSLManager.Tests.csproj
@@ -1,20 +1,21 @@
- netcoreapp3.1
+ net6.0
false
-
-
-
-
+
+
+
+
+
-
+
\ No newline at end of file
diff --git a/src/Tests/BirdsiteLive.ActivityPub.Tests/ApDeserializerTests.cs b/src/Tests/BirdsiteLive.ActivityPub.Tests/ApDeserializerTests.cs
index 3c85113..3d64e90 100644
--- a/src/Tests/BirdsiteLive.ActivityPub.Tests/ApDeserializerTests.cs
+++ b/src/Tests/BirdsiteLive.ActivityPub.Tests/ApDeserializerTests.cs
@@ -1,4 +1,5 @@
-using Microsoft.VisualStudio.TestTools.UnitTesting;
+using BirdsiteLive.ActivityPub.Models;
+using Microsoft.VisualStudio.TestTools.UnitTesting;
using Newtonsoft.Json;
namespace BirdsiteLive.ActivityPub.Tests
@@ -48,6 +49,20 @@ namespace BirdsiteLive.ActivityPub.Tests
Assert.AreEqual("https://mamot.fr/users/testtest", data.apObject.apObject);
}
+ [TestMethod]
+ public void DeleteDeserializationTest()
+ {
+ var json =
+ "{\"@context\": \"https://www.w3.org/ns/activitystreams\", \"id\": \"https://mastodon.technology/users/deleteduser#delete\", \"type\": \"Delete\", \"actor\": \"https://mastodon.technology/users/deleteduser\", \"to\": [\"https://www.w3.org/ns/activitystreams#Public\"],\"object\": \"https://mastodon.technology/users/deleteduser\",\"signature\": {\"type\": \"RsaSignature2017\",\"creator\": \"https://mastodon.technology/users/deleteduser#main-key\",\"created\": \"2020-11-19T22:43:01Z\",\"signatureValue\": \"peksQao4v5N+sMZgHXZ6xZnGaZrd0s+LqZimu63cnp7O5NBJM6gY9AAu/vKUgrh4C50r66f9OQdHg5yChQhc4ViE+yLR/3/e59YQimelmXJPpcC99Nt0YLU/iTRLsBehY3cDdC6+ogJKgpkToQvB6tG2KrPdrkreYh4Il4eXLKMfiQhgdKluOvenLnl2erPWfE02hIu/jpuljyxSuvJunMdU4yQVSZHTtk/I8q3jjzIzhgyb7ICWU5Hkx0H/47Q24ztsvOgiTWNgO+v6l9vA7qIhztENiRPhzGP5RCCzUKRAe6bcSu1Wfa3NKWqB9BeJ7s+2y2bD7ubPbiEE1MQV7Q==\"}}";
+
+ var data = ApDeserializer.ProcessActivity(json) as ActivityDelete;
+
+ Assert.AreEqual("https://mastodon.technology/users/deleteduser#delete", data.id);
+ Assert.AreEqual("Delete", data.type);
+ Assert.AreEqual("https://mastodon.technology/users/deleteduser", data.actor);
+ Assert.AreEqual("https://mastodon.technology/users/deleteduser", data.apObject);
+ }
+
//[TestMethod]
//public void NoteDeserializationTest()
//{
diff --git a/src/Tests/BirdsiteLive.ActivityPub.Tests/BirdsiteLive.ActivityPub.Tests.csproj b/src/Tests/BirdsiteLive.ActivityPub.Tests/BirdsiteLive.ActivityPub.Tests.csproj
index 611d29e..3619c7d 100644
--- a/src/Tests/BirdsiteLive.ActivityPub.Tests/BirdsiteLive.ActivityPub.Tests.csproj
+++ b/src/Tests/BirdsiteLive.ActivityPub.Tests/BirdsiteLive.ActivityPub.Tests.csproj
@@ -1,20 +1,20 @@
- netcoreapp3.1
+ net6.0
false
-
-
-
-
+
+
+
+
-
+
\ No newline at end of file
diff --git a/src/Tests/BirdsiteLive.Common.Tests/BirdsiteLive.Common.Tests.csproj b/src/Tests/BirdsiteLive.Common.Tests/BirdsiteLive.Common.Tests.csproj
index 0a52603..8308c44 100644
--- a/src/Tests/BirdsiteLive.Common.Tests/BirdsiteLive.Common.Tests.csproj
+++ b/src/Tests/BirdsiteLive.Common.Tests/BirdsiteLive.Common.Tests.csproj
@@ -1,20 +1,20 @@
- netcoreapp3.1
+ net6.0
false
-
-
-
-
+
+
+
+
-
+