From 15c4fdd90cfa159b0c92836ff45408f6e2e3982e Mon Sep 17 00:00:00 2001 From: Daniel Supernault Date: Mon, 26 Jul 2021 19:02:11 -0600 Subject: [PATCH] Update StatusService, add non-public option and improve cache invalidation --- app/Services/StatusService.php | 12 +- app/Status.php | 692 ++++++++++++++++----------------- 2 files changed, 355 insertions(+), 349 deletions(-) diff --git a/app/Services/StatusService.php b/app/Services/StatusService.php index 8807e37b1..892005f7e 100644 --- a/app/Services/StatusService.php +++ b/app/Services/StatusService.php @@ -21,10 +21,14 @@ class StatusService { return self::CACHE_KEY . $id; } - public static function get($id) + public static function get($id, $publicOnly = true) { - return Cache::remember(self::key($id), now()->addDays(7), function() use($id) { - $status = Status::whereScope('public')->find($id); + return Cache::remember(self::key($id), now()->addDays(7), function() use($id, $publicOnly) { + if($publicOnly) { + $status = Status::whereScope('public')->find($id); + } else { + $status = Status::whereIn('scope', ['public', 'private', 'unlisted'])->find($id); + } if(!$status) { return null; } @@ -37,6 +41,8 @@ class StatusService { public static function del($id) { + Cache::forget('pf:services:sh:id:' . $id); + Cache::forget('status:transformer:media:attachments:' . $id); PublicTimelineService::rem($id); return Cache::forget(self::key($id)); } diff --git a/app/Status.php b/app/Status.php index d9adae0ac..e14802406 100644 --- a/app/Status.php +++ b/app/Status.php @@ -10,408 +10,408 @@ use Illuminate\Database\Eloquent\SoftDeletes; class Status extends Model { - use HasSnowflakePrimary, SoftDeletes; + use HasSnowflakePrimary, SoftDeletes; - /** - * Indicates if the IDs are auto-incrementing. - * - * @var bool - */ - public $incrementing = false; + /** + * Indicates if the IDs are auto-incrementing. + * + * @var bool + */ + public $incrementing = false; - /** - * The attributes that should be mutated to dates. - * - * @var array - */ - protected $dates = ['deleted_at']; + /** + * The attributes that should be mutated to dates. + * + * @var array + */ + protected $dates = ['deleted_at']; - protected $fillable = ['profile_id', 'visibility', 'in_reply_to_id', 'reblog_of_id', 'type']; + protected $fillable = ['profile_id', 'visibility', 'in_reply_to_id', 'reblog_of_id', 'type']; - const STATUS_TYPES = [ - 'text', - 'photo', - 'photo:album', - 'video', - 'video:album', - 'photo:video:album', - 'share', - 'reply', - 'story', - 'story:reply', - 'story:reaction', - 'story:live', - 'loop' - ]; + const STATUS_TYPES = [ + 'text', + 'photo', + 'photo:album', + 'video', + 'video:album', + 'photo:video:album', + 'share', + 'reply', + 'story', + 'story:reply', + 'story:reaction', + 'story:live', + 'loop' + ]; - const MAX_MENTIONS = 5; + const MAX_MENTIONS = 5; - const MAX_HASHTAGS = 30; + const MAX_HASHTAGS = 30; - const MAX_LINKS = 0; + const MAX_LINKS = 0; - public function profile() - { - return $this->belongsTo(Profile::class); - } + public function profile() + { + return $this->belongsTo(Profile::class); + } - public function media() - { - return $this->hasMany(Media::class); - } + public function media() + { + return $this->hasMany(Media::class); + } - public function firstMedia() - { - return $this->hasMany(Media::class)->orderBy('order', 'asc')->first(); - } + public function firstMedia() + { + return $this->hasMany(Media::class)->orderBy('order', 'asc')->first(); + } - public function viewType() - { - if($this->type) { - return $this->type; - } - return $this->setType(); - } + public function viewType() + { + if($this->type) { + return $this->type; + } + return $this->setType(); + } - public function setType() - { - if(in_array($this->type, self::STATUS_TYPES)) { - return $this->type; - } - $mimes = $this->media->pluck('mime')->toArray(); - $type = StatusController::mimeTypeCheck($mimes); - if($type) { - $this->type = $type; - $this->save(); - return $type; - } - } + public function setType() + { + if(in_array($this->type, self::STATUS_TYPES)) { + return $this->type; + } + $mimes = $this->media->pluck('mime')->toArray(); + $type = StatusController::mimeTypeCheck($mimes); + if($type) { + $this->type = $type; + $this->save(); + return $type; + } + } - public function thumb($showNsfw = false) - { - $key = $showNsfw ? 'status:thumb:nsfw1'.$this->id : 'status:thumb:nsfw0'.$this->id; - return Cache::remember($key, now()->addMinutes(15), function() use ($showNsfw) { - $type = $this->type ?? $this->setType(); - $is_nsfw = !$showNsfw ? $this->is_nsfw : false; - if ($this->media->count() == 0 || $is_nsfw || !in_array($type,['photo', 'photo:album', 'video'])) { - return url(Storage::url('public/no-preview.png')); - } + public function thumb($showNsfw = false) + { + $key = $showNsfw ? 'status:thumb:nsfw1'.$this->id : 'status:thumb:nsfw0'.$this->id; + return Cache::remember($key, now()->addMinutes(15), function() use ($showNsfw) { + $type = $this->type ?? $this->setType(); + $is_nsfw = !$showNsfw ? $this->is_nsfw : false; + if ($this->media->count() == 0 || $is_nsfw || !in_array($type,['photo', 'photo:album', 'video'])) { + return url(Storage::url('public/no-preview.png')); + } - return url(Storage::url($this->firstMedia()->thumbnail_path)); - }); - } + return url(Storage::url($this->firstMedia()->thumbnail_path)); + }); + } - public function url() - { - if($this->uri) { - return $this->uri; - } else { - $id = $this->id; - $username = $this->profile->username; - $path = url(config('app.url')."/p/{$username}/{$id}"); - return $path; - } - } + public function url($forceLocal = false) + { + if($this->uri) { + return $forceLocal ? "/i/web/post/_/{$this->profile_id}/{$this->id}" : $this->uri; + } else { + $id = $this->id; + $username = $this->profile->username; + $path = url(config('app.url')."/p/{$username}/{$id}"); + return $path; + } + } - public function permalink($suffix = '/activity') - { - $id = $this->id; - $username = $this->profile->username; - $path = config('app.url')."/p/{$username}/{$id}{$suffix}"; + public function permalink($suffix = '/activity') + { + $id = $this->id; + $username = $this->profile->username; + $path = config('app.url')."/p/{$username}/{$id}{$suffix}"; - return url($path); - } + return url($path); + } - public function editUrl() - { - return $this->url().'/edit'; - } + public function editUrl() + { + return $this->url().'/edit'; + } - public function mediaUrl() - { - $media = $this->firstMedia(); - $path = $media->media_path; - $hash = is_null($media->processed_at) ? md5('unprocessed') : md5($media->created_at); - $url = $media->cdn_url ? $media->cdn_url . "?v={$hash}" : url(Storage::url($path)."?v={$hash}"); + public function mediaUrl() + { + $media = $this->firstMedia(); + $path = $media->media_path; + $hash = is_null($media->processed_at) ? md5('unprocessed') : md5($media->created_at); + $url = $media->cdn_url ? $media->cdn_url . "?v={$hash}" : url(Storage::url($path)."?v={$hash}"); - return $url; - } + return $url; + } - public function likes() - { - return $this->hasMany(Like::class); - } + public function likes() + { + return $this->hasMany(Like::class); + } - public function liked() : bool - { - if(!Auth::check()) { - return false; - } + public function liked() : bool + { + if(!Auth::check()) { + return false; + } - $pid = Auth::user()->profile_id; + $pid = Auth::user()->profile_id; - return Like::select('status_id', 'profile_id') - ->whereStatusId($this->id) - ->whereProfileId($pid) - ->exists(); - } + return Like::select('status_id', 'profile_id') + ->whereStatusId($this->id) + ->whereProfileId($pid) + ->exists(); + } - public function likedBy() - { - return $this->hasManyThrough( - Profile::class, - Like::class, - 'status_id', - 'id', - 'id', - 'profile_id' - ); - } + public function likedBy() + { + return $this->hasManyThrough( + Profile::class, + Like::class, + 'status_id', + 'id', + 'id', + 'profile_id' + ); + } - public function comments() - { - return $this->hasMany(self::class, 'in_reply_to_id'); - } + public function comments() + { + return $this->hasMany(self::class, 'in_reply_to_id'); + } - public function bookmarked() - { - if (!Auth::check()) { - return false; - } - $profile = Auth::user()->profile; + public function bookmarked() + { + if (!Auth::check()) { + return false; + } + $profile = Auth::user()->profile; - return Bookmark::whereProfileId($profile->id)->whereStatusId($this->id)->count(); - } + return Bookmark::whereProfileId($profile->id)->whereStatusId($this->id)->count(); + } - public function shares() - { - return $this->hasMany(self::class, 'reblog_of_id'); - } + public function shares() + { + return $this->hasMany(self::class, 'reblog_of_id'); + } - public function shared() : bool - { - if(!Auth::check()) { - return false; - } - $pid = Auth::user()->profile_id; + public function shared() : bool + { + if(!Auth::check()) { + return false; + } + $pid = Auth::user()->profile_id; - return $this->select('profile_id', 'reblog_of_id') - ->whereProfileId($pid) - ->whereReblogOfId($this->id) - ->exists(); - } + return $this->select('profile_id', 'reblog_of_id') + ->whereProfileId($pid) + ->whereReblogOfId($this->id) + ->exists(); + } - public function sharedBy() - { - return $this->hasManyThrough( - Profile::class, - Status::class, - 'reblog_of_id', - 'id', - 'id', - 'profile_id' - ); - } + public function sharedBy() + { + return $this->hasManyThrough( + Profile::class, + Status::class, + 'reblog_of_id', + 'id', + 'id', + 'profile_id' + ); + } - public function parent() - { - $parent = $this->in_reply_to_id ?? $this->reblog_of_id; - if (!empty($parent)) { - return $this->findOrFail($parent); - } else { - return false; - } - } + public function parent() + { + $parent = $this->in_reply_to_id ?? $this->reblog_of_id; + if (!empty($parent)) { + return $this->findOrFail($parent); + } else { + return false; + } + } - public function conversation() - { - return $this->hasOne(Conversation::class); - } + public function conversation() + { + return $this->hasOne(Conversation::class); + } - public function hashtags() - { - return $this->hasManyThrough( - Hashtag::class, - StatusHashtag::class, - 'status_id', - 'id', - 'id', - 'hashtag_id' - ); - } + public function hashtags() + { + return $this->hasManyThrough( + Hashtag::class, + StatusHashtag::class, + 'status_id', + 'id', + 'id', + 'hashtag_id' + ); + } - public function mentions() - { - return $this->hasManyThrough( - Profile::class, - Mention::class, - 'status_id', - 'id', - 'id', - 'profile_id' - ); - } + public function mentions() + { + return $this->hasManyThrough( + Profile::class, + Mention::class, + 'status_id', + 'id', + 'id', + 'profile_id' + ); + } - public function reportUrl() - { - return route('report.form')."?type=post&id={$this->id}"; - } + public function reportUrl() + { + return route('report.form')."?type=post&id={$this->id}"; + } - public function toActivityStream() - { - $media = $this->media; - $mediaCollection = []; - foreach ($media as $image) { - $mediaCollection[] = [ - 'type' => 'Link', - 'href' => $image->url(), - 'mediaType' => $image->mime, - ]; - } - $obj = [ - '@context' => 'https://www.w3.org/ns/activitystreams', - 'type' => 'Image', - 'name' => null, - 'url' => $mediaCollection, - ]; + public function toActivityStream() + { + $media = $this->media; + $mediaCollection = []; + foreach ($media as $image) { + $mediaCollection[] = [ + 'type' => 'Link', + 'href' => $image->url(), + 'mediaType' => $image->mime, + ]; + } + $obj = [ + '@context' => 'https://www.w3.org/ns/activitystreams', + 'type' => 'Image', + 'name' => null, + 'url' => $mediaCollection, + ]; - return $obj; - } + return $obj; + } - public function replyToText() - { - $actorName = $this->profile->username; + public function replyToText() + { + $actorName = $this->profile->username; - return "{$actorName} ".__('notification.commented'); - } + return "{$actorName} ".__('notification.commented'); + } - public function replyToHtml() - { - $actorName = $this->profile->username; - $actorUrl = $this->profile->url(); + public function replyToHtml() + { + $actorName = $this->profile->username; + $actorUrl = $this->profile->url(); - return "{$actorName} ". - __('notification.commented'); - } + return "{$actorName} ". + __('notification.commented'); + } - public function shareToText() - { - $actorName = $this->profile->username; + public function shareToText() + { + $actorName = $this->profile->username; - return "{$actorName} ".__('notification.shared'); - } + return "{$actorName} ".__('notification.shared'); + } - public function shareToHtml() - { - $actorName = $this->profile->username; - $actorUrl = $this->profile->url(); + public function shareToHtml() + { + $actorName = $this->profile->username; + $actorUrl = $this->profile->url(); - return "{$actorName} ". - __('notification.shared'); - } + return "{$actorName} ". + __('notification.shared'); + } - public function recentComments() - { - return $this->comments()->orderBy('created_at', 'desc')->take(3); - } + public function recentComments() + { + return $this->comments()->orderBy('created_at', 'desc')->take(3); + } - public function toActivityPubObject() - { - if($this->local == false) { - return; - } - $profile = $this->profile; - $to = $this->scopeToAudience('to'); - $cc = $this->scopeToAudience('cc'); - return [ - '@context' => 'https://www.w3.org/ns/activitystreams', - 'id' => $this->permalink(), - 'type' => 'Create', - 'actor' => $profile->permalink(), - 'published' => $this->created_at->format('c'), - 'to' => $to, - 'cc' => $cc, - 'object' => [ - 'id' => $this->url(), - 'type' => 'Note', - 'summary' => null, - 'inReplyTo' => null, - 'published' => $this->created_at->format('c'), - 'url' => $this->url(), - 'attributedTo' => $this->profile->url(), - 'to' => $to, - 'cc' => $cc, - 'sensitive' => (bool) $this->is_nsfw, - 'content' => $this->rendered, - 'attachment' => $this->media->map(function($media) { - return [ - 'type' => 'Document', - 'mediaType' => $media->mime, - 'url' => $media->url(), - 'name' => null - ]; - })->toArray() - ] - ]; - } + public function toActivityPubObject() + { + if($this->local == false) { + return; + } + $profile = $this->profile; + $to = $this->scopeToAudience('to'); + $cc = $this->scopeToAudience('cc'); + return [ + '@context' => 'https://www.w3.org/ns/activitystreams', + 'id' => $this->permalink(), + 'type' => 'Create', + 'actor' => $profile->permalink(), + 'published' => $this->created_at->format('c'), + 'to' => $to, + 'cc' => $cc, + 'object' => [ + 'id' => $this->url(), + 'type' => 'Note', + 'summary' => null, + 'inReplyTo' => null, + 'published' => $this->created_at->format('c'), + 'url' => $this->url(), + 'attributedTo' => $this->profile->url(), + 'to' => $to, + 'cc' => $cc, + 'sensitive' => (bool) $this->is_nsfw, + 'content' => $this->rendered, + 'attachment' => $this->media->map(function($media) { + return [ + 'type' => 'Document', + 'mediaType' => $media->mime, + 'url' => $media->url(), + 'name' => null + ]; + })->toArray() + ] + ]; + } - public function scopeToAudience($audience) - { - if(!in_array($audience, ['to', 'cc']) || $this->local == false) { - return; - } - $res = []; - $res['to'] = []; - $res['cc'] = []; - $scope = $this->scope; - $mentions = $this->mentions->map(function ($mention) { - return $mention->permalink(); - })->toArray(); + public function scopeToAudience($audience) + { + if(!in_array($audience, ['to', 'cc']) || $this->local == false) { + return; + } + $res = []; + $res['to'] = []; + $res['cc'] = []; + $scope = $this->scope; + $mentions = $this->mentions->map(function ($mention) { + return $mention->permalink(); + })->toArray(); - if($this->in_reply_to_id != null) { - $parent = $this->parent(); - if($parent) { - $mentions = array_merge([$parent->profile->permalink()], $mentions); - } - } + if($this->in_reply_to_id != null) { + $parent = $this->parent(); + if($parent) { + $mentions = array_merge([$parent->profile->permalink()], $mentions); + } + } - switch ($scope) { - case 'public': - $res['to'] = [ - "https://www.w3.org/ns/activitystreams#Public" - ]; - $res['cc'] = array_merge([$this->profile->permalink('/followers')], $mentions); - break; + switch ($scope) { + case 'public': + $res['to'] = [ + "https://www.w3.org/ns/activitystreams#Public" + ]; + $res['cc'] = array_merge([$this->profile->permalink('/followers')], $mentions); + break; - case 'unlisted': - $res['to'] = array_merge([$this->profile->permalink('/followers')], $mentions); - $res['cc'] = [ - "https://www.w3.org/ns/activitystreams#Public" - ]; - break; + case 'unlisted': + $res['to'] = array_merge([$this->profile->permalink('/followers')], $mentions); + $res['cc'] = [ + "https://www.w3.org/ns/activitystreams#Public" + ]; + break; - case 'private': - $res['to'] = array_merge([$this->profile->permalink('/followers')], $mentions); - $res['cc'] = []; - break; + case 'private': + $res['to'] = array_merge([$this->profile->permalink('/followers')], $mentions); + $res['cc'] = []; + break; - // TODO: Update scope when DMs are supported - case 'direct': - $res['to'] = []; - $res['cc'] = []; - break; - } - return $res[$audience]; - } + // TODO: Update scope when DMs are supported + case 'direct': + $res['to'] = []; + $res['cc'] = []; + break; + } + return $res[$audience]; + } - public function place() - { - return $this->belongsTo(Place::class); - } + public function place() + { + return $this->belongsTo(Place::class); + } - public function directMessage() - { - return $this->hasOne(DirectMessage::class); - } + public function directMessage() + { + return $this->hasOne(DirectMessage::class); + } }