[backend/services] Move profile field verification into background-task queue

This commit is contained in:
pancakes 2025-01-22 23:12:09 +10:00 committed by Laura Hausmann
parent 9d0cb44743
commit 4fe3fe8d2c
No known key found for this signature in database
GPG key ID: D044E84C5BE01605
2 changed files with 70 additions and 35 deletions

View file

@ -47,6 +47,9 @@ public class BackgroundTaskQueue(int parallelism)
case UserPurgeJobData userPurgeJob: case UserPurgeJobData userPurgeJob:
await ProcessUserPurgeAsync(userPurgeJob, scope, token); await ProcessUserPurgeAsync(userPurgeJob, scope, token);
break; break;
case ProfileFieldUpdateJobData profileFieldUpdateJob:
await ProcessProfileFieldUpdateAsync(profileFieldUpdateJob, scope, token);
break;
} }
} }
@ -299,6 +302,46 @@ public class BackgroundTaskQueue(int parallelism)
logger.LogDebug("User {id} purged successfully", jobData.UserId); logger.LogDebug("User {id} purged successfully", jobData.UserId);
} }
[SuppressMessage("ReSharper", "EntityFramework.NPlusOne.IncompleteDataQuery", Justification = "Projectables")]
[SuppressMessage("ReSharper", "EntityFramework.NPlusOne.IncompleteDataUsage", Justification = "Same as above")]
private static async Task ProcessProfileFieldUpdateAsync(
ProfileFieldUpdateJobData jobData,
IServiceProvider scope,
CancellationToken token
)
{
var db = scope.GetRequiredService<DatabaseContext>();
var userSvc = scope.GetRequiredService<UserService>();
var logger = scope.GetRequiredService<ILogger<BackgroundTaskQueue>>();
logger.LogDebug("Processing profile field update for user {id}", jobData.UserId);
var user = await db.Users.IncludeCommonProperties().FirstOrDefaultAsync(p => p.Id == jobData.UserId, token);
if (user == null)
{
logger.LogDebug("Failed to update profile fields for user {id}: user not found in database", jobData.UserId);
return;
}
if (user.UserProfile == null)
{
logger.LogDebug("Failed to update profile fields for user {id}: user profile not found in database", jobData.UserId);
return;
}
var profileUrl = user.Host != null ? user.UserProfile.Url : null;
if (user.Host != null && profileUrl == null)
{
logger.LogDebug("Failed to update profile fields for user {id}: profile URL not found in database", jobData.UserId);
return;
}
user = await userSvc.VerifyProfileFieldsAsync(user, profileUrl);
db.Update(user.UserProfile!);
await db.SaveChangesAsync(token);
logger.LogDebug("Profile fields for user {id} updated successfully", jobData.UserId);
}
} }
[JsonDerivedType(typeof(DriveFileDeleteJobData), "driveFileDelete")] [JsonDerivedType(typeof(DriveFileDeleteJobData), "driveFileDelete")]
@ -307,6 +350,7 @@ public class BackgroundTaskQueue(int parallelism)
[JsonDerivedType(typeof(FilterExpiryJobData), "filterExpiry")] [JsonDerivedType(typeof(FilterExpiryJobData), "filterExpiry")]
[JsonDerivedType(typeof(UserDeleteJobData), "userDelete")] [JsonDerivedType(typeof(UserDeleteJobData), "userDelete")]
[JsonDerivedType(typeof(UserPurgeJobData), "userPurge")] [JsonDerivedType(typeof(UserPurgeJobData), "userPurge")]
[JsonDerivedType(typeof(ProfileFieldUpdateJobData), "profileFieldUpdate")]
public abstract class BackgroundTaskJobData; public abstract class BackgroundTaskJobData;
public class DriveFileDeleteJobData : BackgroundTaskJobData public class DriveFileDeleteJobData : BackgroundTaskJobData
@ -339,3 +383,8 @@ public class UserPurgeJobData : BackgroundTaskJobData
{ {
[JR] [J("userId")] public required string UserId { get; set; } [JR] [J("userId")] public required string UserId { get; set; }
} }
public class ProfileFieldUpdateJobData : BackgroundTaskJobData
{
[JR] [J("userId")] public required string UserId { get; set; }
}

View file

@ -226,7 +226,7 @@ public class UserService(
var processPendingDeletes = await ResolveAvatarAndBannerAsync(user, actor); var processPendingDeletes = await ResolveAvatarAndBannerAsync(user, actor);
await processPendingDeletes(); await processPendingDeletes();
user = await UpdateProfileMentionsAsync(user, actor); user = await UpdateProfileMentionsAsync(user, actor);
UpdateProfileFieldsInBackground(user); await queueSvc.BackgroundTaskQueue.EnqueueAsync(new ProfileFieldUpdateJobData { UserId = user.Id});
UpdateUserPinnedNotesInBackground(actor, user); UpdateUserPinnedNotesInBackground(actor, user);
_ = followupTaskSvc.ExecuteTaskAsync("UpdateInstanceUserCounter", async provider => _ = followupTaskSvc.ExecuteTaskAsync("UpdateInstanceUserCounter", async provider =>
{ {
@ -361,7 +361,7 @@ public class UserService(
await db.SaveChangesAsync(); await db.SaveChangesAsync();
await processPendingDeletes(); await processPendingDeletes();
user = await UpdateProfileMentionsAsync(user, actor, true); user = await UpdateProfileMentionsAsync(user, actor, true);
UpdateProfileFieldsInBackground(user); await queueSvc.BackgroundTaskQueue.EnqueueAsync(new ProfileFieldUpdateJobData { UserId = user.Id});
UpdateUserPinnedNotesInBackground(actor, user, true); UpdateUserPinnedNotesInBackground(actor, user, true);
return user; return user;
} }
@ -400,7 +400,7 @@ public class UserService(
await db.SaveChangesAsync(); await db.SaveChangesAsync();
user = await UpdateProfileMentionsAsync(user, null, wait: true); user = await UpdateProfileMentionsAsync(user, null, wait: true);
UpdateProfileFieldsInBackground(user); await queueSvc.BackgroundTaskQueue.EnqueueAsync(new ProfileFieldUpdateJobData { UserId = user.Id});
var avatar = await db.DriveFiles.FirstOrDefaultAsync(p => p.Id == user.AvatarId); var avatar = await db.DriveFiles.FirstOrDefaultAsync(p => p.Id == user.AvatarId);
var banner = await db.DriveFiles.FirstOrDefaultAsync(p => p.Id == user.BannerId); var banner = await db.DriveFiles.FirstOrDefaultAsync(p => p.Id == user.BannerId);
@ -1170,26 +1170,15 @@ public class UserService(
return user; return user;
} }
[SuppressMessage("ReSharper", "EntityFramework.NPlusOne.IncompleteDataQuery", Justification = "Projectables")]
[SuppressMessage("ReSharper", "EntityFramework.NPlusOne.IncompleteDataUsage", Justification = "Same as above")] [SuppressMessage("ReSharper", "EntityFramework.NPlusOne.IncompleteDataUsage", Justification = "Same as above")]
private void UpdateProfileFieldsInBackground(User user) public async Task<User> VerifyProfileFieldsAsync(User user, string? profileUrl)
{ {
if (KeyedLocker.IsInUse($"profileFields:{user.Id}")) return; profileUrl ??= user.GetPublicUrl(instance.Value);
_ = followupTaskSvc.ExecuteTaskAsync("UpdateProfileFieldsInBackground", async provider => foreach (var userProfileField in user.UserProfile!.Fields)
{ {
using (await KeyedLocker.LockAsync($"profileFields:{user.Id}")) if (!userProfileField.Value.StartsWith("https://")
{ || !Uri.TryCreate(userProfileField.Value, UriKind.Absolute, out var uri))
var bgDbContext = provider.GetRequiredService<DatabaseContext>();
var userId = user.Id;
var bgUser = await bgDbContext.Users.IncludeCommonProperties().FirstOrDefaultAsync(p => p.Id == userId);
if (bgUser?.UserProfile == null) return;
var profileUrl = user.Host != null ? user.UserProfile?.Url : bgUser.GetPublicUrl(instance.Value);
if (profileUrl == null) return;
foreach (var userProfileField in bgUser.UserProfile.Fields)
{
if (!userProfileField.Value.StartsWith("https://") || !Uri.TryCreate(userProfileField.Value, UriKind.Absolute, out var uri))
continue; continue;
var res = await httpClient.GetAsync(uri); var res = await httpClient.GetAsync(uri);
@ -1207,10 +1196,7 @@ public class UserService(
|| a.GetAttribute("href") == user.GetUriOrPublicUri(instance.Value)); || a.GetAttribute("href") == user.GetUriOrPublicUri(instance.Value));
} }
bgDbContext.Update(bgUser.UserProfile); return user;
await bgDbContext.SaveChangesAsync();
}
});
} }
private List<string> ResolveHashtags(IMfmNode[]? parsedText, ASActor? actor = null) private List<string> ResolveHashtags(IMfmNode[]? parsedText, ASActor? actor = null)