mirror of
https://github.com/pixelfed/pixelfed.git
synced 2024-11-09 16:24:51 +00:00
Refactor following & relationship logic. Replace FollowerObserver with FollowerService and added RelationshipService to cache results. Removed NotificationTransformer includes and replaced with cached services to improve performance and reduce database queries.
This commit is contained in:
parent
0a8eb81bf0
commit
80d9b9399a
8 changed files with 151 additions and 139 deletions
|
@ -55,6 +55,7 @@ use App\Services\{
|
|||
MediaPathService,
|
||||
PublicTimelineService,
|
||||
ProfileService,
|
||||
RelationshipService,
|
||||
SearchApiV2Service,
|
||||
StatusService,
|
||||
MediaBlocklistService
|
||||
|
@ -551,7 +552,7 @@ class ApiV1Controller extends Controller
|
|||
*
|
||||
* @param array|integer $id
|
||||
*
|
||||
* @return \App\Transformer\Api\RelationshipTransformer
|
||||
* @return \App\Services\RelationshipService
|
||||
*/
|
||||
public function accountRelationshipsById(Request $request)
|
||||
{
|
||||
|
@ -563,12 +564,9 @@ class ApiV1Controller extends Controller
|
|||
]);
|
||||
$pid = $request->user()->profile_id ?? $request->user()->profile->id;
|
||||
$ids = collect($request->input('id'));
|
||||
$filtered = $ids->filter(function($v) use($pid) {
|
||||
return $v != $pid;
|
||||
$res = $ids->map(function($id) use($pid) {
|
||||
return RelationshipService::get($pid, $id);
|
||||
});
|
||||
$relations = Profile::whereNull('status')->findOrFail($filtered->values());
|
||||
$fractal = new Fractal\Resource\Collection($relations, new RelationshipTransformer());
|
||||
$res = $this->fractal->createData($fractal)->toArray();
|
||||
return response()->json($res);
|
||||
}
|
||||
|
||||
|
|
|
@ -12,6 +12,7 @@ use Auth, Cache;
|
|||
use Illuminate\Http\Request;
|
||||
use App\Jobs\FollowPipeline\FollowPipeline;
|
||||
use App\Util\ActivityPub\Helpers;
|
||||
use App\Services\FollowerService;
|
||||
|
||||
class FollowerController extends Controller
|
||||
{
|
||||
|
@ -70,7 +71,9 @@ class FollowerController extends Controller
|
|||
]);
|
||||
if($remote == true && config('federation.activitypub.remoteFollow') == true) {
|
||||
$this->sendFollow($user, $target);
|
||||
}
|
||||
}
|
||||
|
||||
FollowerService::add($user->id, $target->id);
|
||||
} elseif ($private == false && $isFollowing == 0) {
|
||||
if($user->following()->count() >= Follower::MAX_FOLLOWING) {
|
||||
abort(400, 'You cannot follow more than ' . Follower::MAX_FOLLOWING . ' accounts');
|
||||
|
@ -87,6 +90,7 @@ class FollowerController extends Controller
|
|||
if($remote == true && config('federation.activitypub.remoteFollow') == true) {
|
||||
$this->sendFollow($user, $target);
|
||||
}
|
||||
FollowerService::add($user->id, $target->id);
|
||||
FollowPipeline::dispatch($follower);
|
||||
} else {
|
||||
if($force == true) {
|
||||
|
@ -101,6 +105,7 @@ class FollowerController extends Controller
|
|||
Follower::whereProfileId($user->id)
|
||||
->whereFollowingId($target->id)
|
||||
->delete();
|
||||
FollowerService::remove($user->id, $target->id);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,64 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace App\Observers;
|
||||
|
||||
use App\Follower;
|
||||
use App\Services\FollowerService;
|
||||
|
||||
class FollowerObserver
|
||||
{
|
||||
/**
|
||||
* Handle the Follower "created" event.
|
||||
*
|
||||
* @param \App\Models\Follower $follower
|
||||
* @return void
|
||||
*/
|
||||
public function created(Follower $follower)
|
||||
{
|
||||
FollowerService::add($follower->profile_id, $follower->following_id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle the Follower "updated" event.
|
||||
*
|
||||
* @param \App\Models\Follower $follower
|
||||
* @return void
|
||||
*/
|
||||
public function updated(Follower $follower)
|
||||
{
|
||||
FollowerService::add($follower->profile_id, $follower->following_id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle the Follower "deleted" event.
|
||||
*
|
||||
* @param \App\Models\Follower $follower
|
||||
* @return void
|
||||
*/
|
||||
public function deleted(Follower $follower)
|
||||
{
|
||||
FollowerService::remove($follower->profile_id, $follower->following_id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle the Follower "restored" event.
|
||||
*
|
||||
* @param \App\Models\Follower $follower
|
||||
* @return void
|
||||
*/
|
||||
public function restored(Follower $follower)
|
||||
{
|
||||
FollowerService::add($follower->profile_id, $follower->following_id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle the Follower "force deleted" event.
|
||||
*
|
||||
* @param \App\Models\Follower $follower
|
||||
* @return void
|
||||
*/
|
||||
public function forceDeleted(Follower $follower)
|
||||
{
|
||||
FollowerService::remove($follower->profile_id, $follower->following_id);
|
||||
}
|
||||
}
|
|
@ -4,7 +4,6 @@ namespace App\Providers;
|
|||
|
||||
use App\Observers\{
|
||||
AvatarObserver,
|
||||
FollowerObserver,
|
||||
LikeObserver,
|
||||
NotificationObserver,
|
||||
ModLogObserver,
|
||||
|
@ -15,7 +14,6 @@ use App\Observers\{
|
|||
};
|
||||
use App\{
|
||||
Avatar,
|
||||
Follower,
|
||||
Like,
|
||||
Notification,
|
||||
ModLog,
|
||||
|
@ -50,7 +48,6 @@ class AppServiceProvider extends ServiceProvider
|
|||
StatusHashtag::observe(StatusHashtagObserver::class);
|
||||
User::observe(UserObserver::class);
|
||||
UserFilter::observe(UserFilterObserver::class);
|
||||
Follower::observe(FollowerObserver::class);
|
||||
Horizon::auth(function ($request) {
|
||||
return Auth::check() && $request->user()->is_admin;
|
||||
});
|
||||
|
|
|
@ -17,12 +17,14 @@ class FollowerService
|
|||
|
||||
public static function add($actor, $target)
|
||||
{
|
||||
RelationshipService::refresh($actor, $target);
|
||||
Redis::zadd(self::FOLLOWING_KEY . $actor, $target, $target);
|
||||
Redis::zadd(self::FOLLOWERS_KEY . $target, $actor, $actor);
|
||||
}
|
||||
|
||||
public static function remove($actor, $target)
|
||||
{
|
||||
RelationshipService::refresh($actor, $target);
|
||||
Redis::zrem(self::FOLLOWING_KEY . $actor, $target);
|
||||
Redis::zrem(self::FOLLOWERS_KEY . $target, $actor);
|
||||
Cache::forget('pf:services:follow:audience:' . $actor);
|
||||
|
|
86
app/Services/RelationshipService.php
Normal file
86
app/Services/RelationshipService.php
Normal file
|
@ -0,0 +1,86 @@
|
|||
<?php
|
||||
|
||||
namespace App\Services;
|
||||
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
use App\Follower;
|
||||
use App\FollowRequest;
|
||||
use App\Profile;
|
||||
use App\UserFilter;
|
||||
|
||||
class RelationshipService
|
||||
{
|
||||
const CACHE_KEY = 'pf:services:urel:';
|
||||
|
||||
public static function get($aid, $tid)
|
||||
{
|
||||
$actor = AccountService::get($aid);
|
||||
$target = AccountService::get($tid);
|
||||
if(!$actor || !$target) {
|
||||
return self::defaultRelation($tid);
|
||||
}
|
||||
|
||||
if($actor['id'] === $target['id']) {
|
||||
return self::defaultRelation($tid);
|
||||
}
|
||||
|
||||
return Cache::remember(self::key("a_{$aid}:t_{$tid}"), 1209600, function() use($aid, $tid) {
|
||||
return [
|
||||
'id' => (string) $tid,
|
||||
'following' => Follower::whereProfileId($aid)->whereFollowingId($tid)->exists(),
|
||||
'followed_by' => Follower::whereProfileId($tid)->whereFollowingId($aid)->exists(),
|
||||
'blocking' => UserFilter::whereUserId($aid)
|
||||
->whereFilterableType('App\Profile')
|
||||
->whereFilterableId($tid)
|
||||
->whereFilterType('block')
|
||||
->exists(),
|
||||
'muting' => UserFilter::whereUserId($aid)
|
||||
->whereFilterableType('App\Profile')
|
||||
->whereFilterableId($tid)
|
||||
->whereFilterType('mute')
|
||||
->exists(),
|
||||
'muting_notifications' => null,
|
||||
'requested' => FollowRequest::whereFollowerId($aid)
|
||||
->whereFollowingId($tid)
|
||||
->exists(),
|
||||
'domain_blocking' => null,
|
||||
'showing_reblogs' => null,
|
||||
'endorsed' => false
|
||||
];
|
||||
});
|
||||
}
|
||||
|
||||
public static function delete($aid, $tid)
|
||||
{
|
||||
return Cache::forget(self::key("a_{$aid}:t_{$tid}"));
|
||||
}
|
||||
|
||||
public static function refresh($aid, $tid)
|
||||
{
|
||||
self::delete($tid, $aid);
|
||||
self::delete($aid, $tid);
|
||||
self::get($tid, $aid);
|
||||
return self::get($aid, $tid);
|
||||
}
|
||||
|
||||
public static function defaultRelation($tid)
|
||||
{
|
||||
return [
|
||||
'id' => (string) $tid,
|
||||
'following' => false,
|
||||
'followed_by' => false,
|
||||
'blocking' => false,
|
||||
'muting' => false,
|
||||
'muting_notifications' => null,
|
||||
'requested' => false,
|
||||
'domain_blocking' => null,
|
||||
'showing_reblogs' => null,
|
||||
'endorsed' => false
|
||||
];
|
||||
}
|
||||
|
||||
protected static function key($suffix)
|
||||
{
|
||||
return self::CACHE_KEY . $suffix;
|
||||
}
|
||||
}
|
|
@ -2,50 +2,65 @@
|
|||
|
||||
namespace App\Transformer\Api;
|
||||
|
||||
use App\{
|
||||
Notification,
|
||||
Status
|
||||
};
|
||||
use App\Notification;
|
||||
use App\Services\AccountService;
|
||||
use App\Services\HashidService;
|
||||
use App\Services\StatusService;
|
||||
use League\Fractal;
|
||||
|
||||
class NotificationTransformer extends Fractal\TransformerAbstract
|
||||
{
|
||||
protected $defaultIncludes = [
|
||||
'account',
|
||||
'status',
|
||||
'relationship',
|
||||
'modlog',
|
||||
'tagged'
|
||||
// 'relationship',
|
||||
];
|
||||
|
||||
public function transform(Notification $notification)
|
||||
{
|
||||
return [
|
||||
$res = [
|
||||
'id' => (string) $notification->id,
|
||||
'type' => $this->replaceTypeVerb($notification->action),
|
||||
'created_at' => (string) $notification->created_at->format('c'),
|
||||
];
|
||||
}
|
||||
|
||||
public function includeAccount(Notification $notification)
|
||||
{
|
||||
return $this->item($notification->actor, new AccountTransformer());
|
||||
}
|
||||
$n = $notification;
|
||||
if($n->item_id && $n->item_type == 'App\Status' && in_array($n->action, ['group:comment'])) {
|
||||
$status = $n->status;
|
||||
$res['group_id'] = $status->group_id;
|
||||
|
||||
public function includeStatus(Notification $notification)
|
||||
{
|
||||
$item = $notification;
|
||||
if($item->item_id && $item->item_type == 'App\Status') {
|
||||
$status = Status::with('media')->find($item->item_id);
|
||||
if($status) {
|
||||
return $this->item($status, new StatusTransformer());
|
||||
} else {
|
||||
return null;
|
||||
if($n->action == 'group:comment') {
|
||||
$res['group_post_url'] = GroupPost::whereStatusId($status->id)->first()->url();
|
||||
}
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
|
||||
if(in_array($n->action, ['group.join.approved', 'group.join.rejected', 'group.like'])) {
|
||||
$res['group'] = GroupService::get($n->item_id);
|
||||
}
|
||||
|
||||
if($n->actor_id) {
|
||||
$res['account'] = AccountService::get($n->actor_id);
|
||||
}
|
||||
|
||||
if($n->item_id && $n->item_type == 'App\Status') {
|
||||
$res['status'] = StatusService::get($n->item_id, false);
|
||||
}
|
||||
|
||||
if($n->item_id && $n->item_type == 'App\ModLog') {
|
||||
$ml = $n->item;
|
||||
$res['modlog'] = [
|
||||
'id' => $ml->object_uid,
|
||||
'url' => url('/i/admin/users/modlogs/' . $ml->object_uid)
|
||||
];
|
||||
}
|
||||
|
||||
if($n->item_id && $n->item_type == 'App\MediaTag') {
|
||||
$ml = $n->item;
|
||||
$res['tagged'] = [
|
||||
'username' => $ml->tagged_username,
|
||||
'post_url' => '/p/'.HashidService::encode($ml->status_id)
|
||||
];
|
||||
}
|
||||
|
||||
return $res;
|
||||
}
|
||||
|
||||
public function replaceTypeVerb($verb)
|
||||
|
@ -57,13 +72,21 @@ class NotificationTransformer extends Fractal\TransformerAbstract
|
|||
'reblog' => 'share',
|
||||
'share' => 'share',
|
||||
'like' => 'favourite',
|
||||
'group:like' => 'favourite',
|
||||
'comment' => 'comment',
|
||||
'admin.user.modlog.comment' => 'modlog',
|
||||
'tagged' => 'tagged',
|
||||
'group:comment' => 'group:comment',
|
||||
'story:react' => 'story:react',
|
||||
'story:comment' => 'story:comment'
|
||||
'story:comment' => 'story:comment',
|
||||
'group:join:approved' => 'group:join:approved',
|
||||
'group:join:rejected' => 'group:join:rejected'
|
||||
];
|
||||
|
||||
if(!isset($verbs[$verb])) {
|
||||
return $verb;
|
||||
}
|
||||
|
||||
return $verbs[$verb];
|
||||
}
|
||||
|
||||
|
@ -71,42 +94,4 @@ class NotificationTransformer extends Fractal\TransformerAbstract
|
|||
{
|
||||
return $this->item($notification->actor, new RelationshipTransformer());
|
||||
}
|
||||
|
||||
public function includeModlog(Notification $notification)
|
||||
{
|
||||
$n = $notification;
|
||||
if($n->item_id && $n->item_type == 'App\ModLog') {
|
||||
$ml = $n->item;
|
||||
if(!empty($ml)) {
|
||||
$res = $this->item($ml, function($ml) {
|
||||
return [
|
||||
'id' => $ml->object_uid,
|
||||
'url' => url('/i/admin/users/modlogs/' . $ml->object_uid)
|
||||
];
|
||||
});
|
||||
return $res;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public function includeTagged(Notification $notification)
|
||||
{
|
||||
$n = $notification;
|
||||
if($n->item_id && $n->item_type == 'App\MediaTag') {
|
||||
$ml = $n->item;
|
||||
$res = $this->item($ml, function($ml) {
|
||||
return [
|
||||
'username' => $ml->tagged_username,
|
||||
'post_url' => '/p/'.HashidService::encode($ml->status_id)
|
||||
];
|
||||
});
|
||||
return $res;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -455,6 +455,7 @@ class Inbox
|
|||
Cache::forget('profile:follower_count:'.$actor->id);
|
||||
Cache::forget('profile:following_count:'.$target->id);
|
||||
Cache::forget('profile:following_count:'.$actor->id);
|
||||
FollowerService::add($actor->id, $target->id);
|
||||
|
||||
} else {
|
||||
$follower = new Follower;
|
||||
|
@ -464,6 +465,7 @@ class Inbox
|
|||
$follower->save();
|
||||
|
||||
FollowPipeline::dispatch($follower);
|
||||
FollowerService::add($actor->id, $target->id);
|
||||
|
||||
// send Accept to remote profile
|
||||
$accept = [
|
||||
|
@ -722,6 +724,7 @@ class Inbox
|
|||
->whereItemId($following->id)
|
||||
->whereItemType('App\Profile')
|
||||
->forceDelete();
|
||||
FollowerService::remove($profile->id, $following->id);
|
||||
break;
|
||||
|
||||
case 'Like':
|
||||
|
|
Loading…
Reference in a new issue