[backend/core] Move import/export to ImportExportService and resolve imported users

This commit is contained in:
pancakes 2024-10-27 23:37:01 +10:00 committed by Laura Hausmann
parent bfc36cbc48
commit 137dc0d0e6
No known key found for this signature in database
GPG key ID: D044E84C5BE01605
4 changed files with 68 additions and 53 deletions

View file

@ -19,7 +19,7 @@ namespace Iceshrimp.Backend.Controllers.Web;
[EnableRateLimiting("sliding")]
[Route("/api/iceshrimp/settings")]
[Produces(MediaTypeNames.Application.Json)]
public class SettingsController(DatabaseContext db, UserService userSvc) : ControllerBase
public class SettingsController(DatabaseContext db, ImportExportService importExportSvc) : ControllerBase
{
[HttpGet]
[ProducesResults(HttpStatusCode.OK)]
@ -79,7 +79,7 @@ public class SettingsController(DatabaseContext db, UserService userSvc) : Contr
if (followCount < 1)
throw GracefulException.BadRequest("You do not follow any users");
await userSvc.ExportFollowingAsync(user);
await importExportSvc.ExportFollowingAsync(user);
return Accepted();
}
@ -100,7 +100,7 @@ public class SettingsController(DatabaseContext db, UserService userSvc) : Contr
.Where(fqn => fqn.Contains('@'))
.ToList();
await userSvc.ImportFollowingAsync(user, fqns);
await importExportSvc.ImportFollowingAsync(user, fqns);
return Accepted();
}

View file

@ -65,6 +65,7 @@ public static class ServiceExtensions
.AddScoped<NotificationService>()
.AddScoped<DatabaseMaintenanceService>()
.AddScoped<BiteService>()
.AddScoped<ImportExportService>()
.AddScoped<UserProfileMentionsResolver>()
.AddScoped<AuthorizedFetchMiddleware>()
.AddScoped<InboxValidationMiddleware>()

View file

@ -0,0 +1,64 @@
using System.Text;
using Iceshrimp.Backend.Core.Configuration;
using Iceshrimp.Backend.Core.Database;
using Iceshrimp.Backend.Core.Database.Tables;
using Iceshrimp.Backend.Core.Extensions;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Options;
using static Iceshrimp.Backend.Core.Federation.ActivityPub.UserResolver;
namespace Iceshrimp.Backend.Core.Services;
public class ImportExportService(
DatabaseContext db,
ILogger<UserService> logger,
IOptions<Config.InstanceSection> instance,
CacheService cacheSvc,
DriveService driveSvc,
UserService userSvc,
ActivityPub.UserResolver userResolver
)
{
public async Task ExportFollowingAsync(User user)
{
var followees = await db.Followings
.Include(p => p.Followee)
.Where(p => p.FollowerId == user.Id)
.Select(p => p.Followee)
.Where(p => !p.IsDeleted && !p.IsSystemUser && p.MovedToUri == null)
.OrderBy(p => p.Host)
.ThenBy(p => p.UsernameLower)
.Select(p => p.GetFqn(instance.Value.AccountDomain))
.ToListAsync();
var stream = new MemoryStream(Encoding.UTF8.GetBytes(string.Join("\n", followees)));
await driveSvc.StoreFile(stream, user,
new DriveFileCreationRequest
{
Filename = $"following-{DateTime.UtcNow:yyyy-MM-dd-HH-mm-ss}.csv",
IsSensitive = false,
MimeType = "text/csv"
}, true);
}
public async Task ImportFollowingAsync(User user, List<string> fqns)
{
foreach (var fqn in fqns)
{
var followee = await userResolver.ResolveAsync($"acct:{fqn}", ResolveFlags.Acct);
try
{
await userSvc.FollowUserAsync(user, followee);
}
catch (Exception e)
{
logger.LogWarning("Failed to import follow {followee} for user {follower}: {error}",
followee.Id, user.Id, e);
}
}
await QueryableTimelineExtensions.ResetHeuristic(user, cacheSvc);
}
}

View file

@ -1,7 +1,6 @@
using System.Diagnostics.CodeAnalysis;
using System.Net;
using System.Security.Cryptography;
using System.Text;
using System.Text.RegularExpressions;
using AsyncKeyedLock;
using EntityFramework.Exceptions.Common;
@ -40,7 +39,6 @@ public class UserService(
QueueService queueSvc,
EventService eventSvc,
WebFingerService webFingerSvc,
CacheService cacheSvc,
ActivityPub.FederationControlService fedCtrlSvc
)
{
@ -947,54 +945,6 @@ public class UserService(
await db.UserListMembers.Where(p => p.UserList.User == user && p.User == followee).ExecuteDeleteAsync();
}
public async Task ExportFollowingAsync(User user)
{
var followees = await db.Followings
.Include(p => p.Followee)
.Where(p => p.FollowerId == user.Id)
.Select(p => p.Followee)
.Where(p => !p.IsDeleted && !p.IsSystemUser && p.MovedToUri == null)
.OrderBy(p => p.Host)
.ThenBy(p => p.UsernameLower)
.Select(p => p.GetFqn(instance.Value.AccountDomain))
.ToListAsync();
var stream = new MemoryStream(Encoding.UTF8.GetBytes(string.Join("\n", followees)));
await driveSvc.StoreFile(stream, user,
new DriveFileCreationRequest
{
Filename = $"following-{DateTime.UtcNow:yyyy-MM-dd-HH-mm-ss}.csv",
IsSensitive = false,
MimeType = "text/csv"
}, true);
}
public async Task ImportFollowingAsync(User user, List<string> fqns)
{
foreach (var fqn in fqns.Select(fqn => fqn.Split("@")))
{
var followee = await db.Users
.IncludeCommonProperties()
.FirstOrDefaultAsync(p => fqn[0].ToLower() == p.UsernameLower &&
fqn[1] == (p.Host ?? instance.Value.AccountDomain));
if (followee == null) continue;
try
{
await FollowUserAsync(user, followee);
}
catch (Exception e)
{
logger.LogWarning("Failed to import follow {followee} for user {follower}: {error}",
followee.Id, user.Id, e);
}
}
await QueryableTimelineExtensions.ResetHeuristic(user, cacheSvc);
}
[SuppressMessage("ReSharper", "SuggestBaseTypeForParameter", Justification = "Method only makes sense for users")]
private void UpdateUserPinnedNotesInBackground(ASActor actor, User user, bool force = false)
{