forked from mirror/pixelfed
Refactor AP profileFetch logic to fix race conditions and improve updating fields and avatars
This commit is contained in:
parent
108e380394
commit
505261dab1
2 changed files with 66 additions and 47 deletions
|
@ -26,7 +26,7 @@ class Profile extends Model
|
||||||
];
|
];
|
||||||
protected $hidden = ['private_key'];
|
protected $hidden = ['private_key'];
|
||||||
protected $visible = ['id', 'user_id', 'username', 'name'];
|
protected $visible = ['id', 'user_id', 'username', 'name'];
|
||||||
protected $fillable = ['user_id'];
|
protected $guarded = [];
|
||||||
|
|
||||||
public function user()
|
public function user()
|
||||||
{
|
{
|
||||||
|
|
|
@ -36,6 +36,7 @@ use App\Jobs\MediaPipeline\MediaStoragePipeline;
|
||||||
use App\Jobs\AvatarPipeline\RemoteAvatarFetch;
|
use App\Jobs\AvatarPipeline\RemoteAvatarFetch;
|
||||||
use App\Util\Media\License;
|
use App\Util\Media\License;
|
||||||
use App\Models\Poll;
|
use App\Models\Poll;
|
||||||
|
use Illuminate\Contracts\Cache\LockTimeoutException;
|
||||||
|
|
||||||
class Helpers {
|
class Helpers {
|
||||||
|
|
||||||
|
@ -664,26 +665,43 @@ class Helpers {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function profileFirstOrNew($url, $runJobs = false)
|
public static function profileFirstOrNew($url)
|
||||||
{
|
{
|
||||||
$url = self::validateUrl($url);
|
$url = self::validateUrl($url);
|
||||||
if($url == false || strlen($url) > 190) {
|
if($url == false) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$host = parse_url($url, PHP_URL_HOST);
|
||||||
|
$local = config('pixelfed.domain.app') == $host ? true : false;
|
||||||
|
|
||||||
|
if($local == true) {
|
||||||
|
$id = last(explode('/', $url));
|
||||||
|
return Profile::whereNull('status')
|
||||||
|
->whereNull('domain')
|
||||||
|
->whereUsername($id)
|
||||||
|
->firstOrFail();
|
||||||
|
}
|
||||||
|
|
||||||
|
if($profile = Profile::whereRemoteUrl($url)->first()) {
|
||||||
|
if($profile->last_fetched_at->lt(now()->subHours(24))) {
|
||||||
|
return self::profileUpdateOrCreate($url);
|
||||||
|
}
|
||||||
|
return $profile;
|
||||||
|
}
|
||||||
|
|
||||||
|
return self::profileUpdateOrCreate($url);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function profileUpdateOrCreate($url)
|
||||||
|
{
|
||||||
$hash = base64_encode($url);
|
$hash = base64_encode($url);
|
||||||
$key = 'ap:profile:by_url:' . $hash;
|
$key = 'ap:profile:by_url:' . $hash;
|
||||||
$ttl = now()->addSeconds(60);
|
$lock = Cache::lock($key, 30);
|
||||||
$profile = Cache::remember($key, $ttl, function() use($url, $runJobs) {
|
$profile = null;
|
||||||
$host = parse_url($url, PHP_URL_HOST);
|
|
||||||
$local = config('pixelfed.domain.app') == $host ? true : false;
|
|
||||||
|
|
||||||
if($local == true) {
|
try {
|
||||||
$id = last(explode('/', $url));
|
$lock->block(5);
|
||||||
return Profile::whereNull('status')
|
|
||||||
->whereNull('domain')
|
|
||||||
->whereUsername($id)
|
|
||||||
->firstOrFail();
|
|
||||||
}
|
|
||||||
|
|
||||||
$res = self::fetchProfileFromUrl($url);
|
$res = self::fetchProfileFromUrl($url);
|
||||||
if(isset($res['id']) == false) {
|
if(isset($res['id']) == false) {
|
||||||
|
@ -703,47 +721,48 @@ class Helpers {
|
||||||
abort_if(!self::validateUrl($res['inbox']), 400);
|
abort_if(!self::validateUrl($res['inbox']), 400);
|
||||||
abort_if(!self::validateUrl($res['id']), 400);
|
abort_if(!self::validateUrl($res['id']), 400);
|
||||||
|
|
||||||
$profile = Profile::whereRemoteUrl($res['id'])->first();
|
$profile = DB::transaction(function() use($domain, $webfinger, $res, $runJobs) {
|
||||||
if(!$profile) {
|
$instance = Instance::updateOrCreate([
|
||||||
$instance = Instance::firstOrCreate([
|
|
||||||
'domain' => $domain
|
'domain' => $domain
|
||||||
]);
|
]);
|
||||||
if($instance->wasRecentlyCreated == true) {
|
if($instance->wasRecentlyCreated == true) {
|
||||||
\App\Jobs\InstancePipeline\FetchNodeinfoPipeline::dispatch($instance)->onQueue('low');
|
\App\Jobs\InstancePipeline\FetchNodeinfoPipeline::dispatch($instance)->onQueue('low');
|
||||||
}
|
}
|
||||||
$profile = DB::transaction(function() use($domain, $webfinger, $res, $runJobs) {
|
|
||||||
$profile = new Profile();
|
$profile = Profile::updateOrCreate(
|
||||||
$profile->domain = strtolower($domain);
|
[
|
||||||
$profile->username = Purify::clean($webfinger);
|
'domain' => strtolower($domain),
|
||||||
$profile->name = isset($res['name']) ? Purify::clean($res['name']) : 'user';
|
'username' => Purify::clean($webfinger),
|
||||||
$profile->bio = isset($res['summary']) ? Purify::clean($res['summary']) : null;
|
'remote_url' => $res['id'],
|
||||||
$profile->sharedInbox = isset($res['endpoints']) && isset($res['endpoints']['sharedInbox']) ? $res['endpoints']['sharedInbox'] : null;
|
],
|
||||||
$profile->inbox_url = $res['inbox'];
|
[
|
||||||
$profile->outbox_url = isset($res['outbox']) ? $res['outbox'] : null;
|
'name' => isset($res['name']) ? Purify::clean($res['name']) : 'user',
|
||||||
$profile->remote_url = $res['id'];
|
'bio' => isset($res['summary']) ? Purify::clean($res['summary']) : null,
|
||||||
$profile->public_key = $res['publicKey']['publicKeyPem'];
|
'sharedInbox' => isset($res['endpoints']) && isset($res['endpoints']['sharedInbox']) ? $res['endpoints']['sharedInbox'] : null,
|
||||||
$profile->key_id = $res['publicKey']['id'];
|
'inbox_url' => $res['inbox'],
|
||||||
$profile->webfinger = Purify::clean($webfinger);
|
'outbox_url' => isset($res['outbox']) ? $res['outbox'] : null,
|
||||||
$profile->last_fetched_at = now();
|
'public_key' => $res['publicKey']['publicKeyPem'],
|
||||||
$profile->save();
|
'key_id' => $res['publicKey']['id'],
|
||||||
RemoteAvatarFetch::dispatch($profile);
|
'webfinger' => Purify::clean($webfinger),
|
||||||
return $profile;
|
]
|
||||||
});
|
);
|
||||||
} else {
|
|
||||||
// Update info after 24 hours
|
if( $profile->last_fetched_at == null ||
|
||||||
if($profile->last_fetched_at == null ||
|
$profile->last_fetched_at->lt(now()->subHours(24))
|
||||||
$profile->last_fetched_at->lt(now()->subHours(24)) == true
|
|
||||||
) {
|
) {
|
||||||
$profile->name = isset($res['name']) ? Purify::clean($res['name']) : 'user';
|
RemoteAvatarFetch::dispatch($profile);
|
||||||
$profile->bio = isset($res['summary']) ? Purify::clean($res['summary']) : null;
|
|
||||||
$profile->last_fetched_at = now();
|
|
||||||
$profile->sharedInbox = isset($res['endpoints']) && isset($res['endpoints']['sharedInbox']) && Helpers::validateUrl($res['endpoints']['sharedInbox']) ? $res['endpoints']['sharedInbox'] : null;
|
|
||||||
$profile->save();
|
|
||||||
}
|
}
|
||||||
RemoteAvatarFetch::dispatch($profile);
|
$profile->last_fetched_at = now();
|
||||||
}
|
$profile->save();
|
||||||
|
return $profile;
|
||||||
|
});
|
||||||
|
|
||||||
return $profile;
|
return $profile;
|
||||||
});
|
} catch (LockTimeoutException $e) {
|
||||||
|
} finally {
|
||||||
|
optional($lock)->release();
|
||||||
|
}
|
||||||
|
|
||||||
return $profile;
|
return $profile;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue