diff --git a/Iceshrimp.Backend/Controllers/Web/UserController.cs b/Iceshrimp.Backend/Controllers/Web/UserController.cs
index f07bf2f4..cd55ec2b 100644
--- a/Iceshrimp.Backend/Controllers/Web/UserController.cs
+++ b/Iceshrimp.Backend/Controllers/Web/UserController.cs
@@ -141,6 +141,25 @@ public class UserController(
await userSvc.FollowUserAsync(user, followee);
}
+ [HttpPost("{id}/remove_from_followers")]
+ [ProducesResults(HttpStatusCode.OK)]
+ [ProducesErrors(HttpStatusCode.BadRequest, HttpStatusCode.NotFound)]
+ public async Task RemoveFromFollowers(string id)
+ {
+ var user = HttpContext.GetUserOrFail();
+ if (user.Id == id)
+ throw GracefulException.BadRequest("You cannot unfollow yourself");
+
+ var follower = await db.Users
+ .Where(p => p.Id == id)
+ .IncludeCommonProperties()
+ .PrecomputeRelationshipData(user)
+ .FirstOrDefaultAsync() ??
+ throw GracefulException.RecordNotFound();
+
+ await userSvc.RemoveFromFollowersAsync(user, follower);
+ }
+
[HttpPost("{id}/unfollow")]
[ProducesErrors(HttpStatusCode.BadRequest, HttpStatusCode.NotFound)]
public async Task UnfollowUser(string id)
diff --git a/Iceshrimp.Backend/Core/Services/UserService.cs b/Iceshrimp.Backend/Core/Services/UserService.cs
index ffe404bf..81085245 100644
--- a/Iceshrimp.Backend/Core/Services/UserService.cs
+++ b/Iceshrimp.Backend/Core/Services/UserService.cs
@@ -874,6 +874,61 @@ public class UserService(
}
}
+ ///
+ /// Make sure to call .PrecomputeRelationshipData(user) on the database query for the followee
+ ///
+ public async Task RemoveFromFollowersAsync(User user, User follower)
+ {
+ if ((follower.PrecomputedIsFollowing ?? false) && follower.IsRemoteUser)
+ {
+ var followingId = await db.Followings
+ .Where(p => p.Followee == user && p.Follower == follower)
+ .Select(p => p.Id)
+ .FirstAsync();
+
+ var accept = activityRenderer.RenderAccept(user, follower, followingId);
+ var activity = activityRenderer.RenderUndo(userRenderer.RenderLite(user), accept);
+ await deliverSvc.DeliverToAsync(activity, user, follower);
+ }
+
+ if (follower.PrecomputedIsFollowing ?? false)
+ {
+ var followers = await db.Followings
+ .Where(p => p.Followee == user && p.Follower == follower)
+ .ToListAsync();
+
+ await db.Users
+ .Where(p => p.Id == user.Id)
+ .ExecuteUpdateAsync(p => p.SetProperty(i => i.FollowersCount,
+ i => i.FollowersCount - followers.Count));
+
+ await db.Users
+ .Where(p => p.Id == follower.Id)
+ .ExecuteUpdateAsync(p => p.SetProperty(i => i.FollowingCount,
+ i => i.FollowingCount - followers.Count));
+
+ db.RemoveRange(followers);
+ await db.SaveChangesAsync();
+
+ if (follower.IsRemoteUser)
+ {
+ _ = followupTaskSvc.ExecuteTask("DecrementInstanceIncomingFollowsCounter", async provider =>
+ {
+ var bgDb = provider.GetRequiredService();
+ var bgInstanceSvc = provider.GetRequiredService();
+ var dbInstance =
+ await bgInstanceSvc.GetUpdatedInstanceMetadataAsync(follower);
+ await bgDb.Instances.Where(p => p.Id == dbInstance.Id)
+ .ExecuteUpdateAsync(p => p.SetProperty(i => i.IncomingFollows,
+ i => i.IncomingFollows - 1));
+ });
+ }
+
+ follower.PrecomputedIsFollowedBy = false;
+ eventSvc.RaiseUserUnfollowed(this, follower, user);
+ }
+ }
+
///
/// Make sure to call .PrecomputeRelationshipData(user) on the database query for the followee
///