浏览代码

Update StatusService, add non-public option and improve cache invalidation

Daniel Supernault 4 年之前
父节点
当前提交
15c4fdd90c
共有 2 个文件被更改,包括 412 次插入406 次删除
  1. 9 3
      app/Services/StatusService.php
  2. 403 403
      app/Status.php

+ 9 - 3
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));
 	}

+ 403 - 403
app/Status.php

@@ -10,408 +10,408 @@ use Illuminate\Database\Eloquent\SoftDeletes;
 
 class Status extends Model
 {
-    use HasSnowflakePrimary, SoftDeletes;
-
-    /**
-     * 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'];
-
-    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 MAX_MENTIONS = 5;
-
-    const MAX_HASHTAGS = 30;
-
-    const MAX_LINKS = 0;
-
-    public function profile()
-    {
-        return $this->belongsTo(Profile::class);
-    }
-
-    public function media()
-    {
-        return $this->hasMany(Media::class);
-    }
-
-    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 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'));
-            }
-
-            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 permalink($suffix = '/activity')
-    {
-        $id = $this->id;
-        $username = $this->profile->username;
-        $path = config('app.url')."/p/{$username}/{$id}{$suffix}";
-
-        return url($path);
-    }
-
-    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}");
-
-        return $url;
-    }
-
-    public function likes()
-    {
-        return $this->hasMany(Like::class);
-    }
-
-    public function liked() : bool
-    {
-        if(!Auth::check()) {
-            return false;
-        }
-
-        $pid = Auth::user()->profile_id;
-
-        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 comments()
-    {
-        return $this->hasMany(self::class, 'in_reply_to_id');
-    }
-
-    public function bookmarked()
-    {
-        if (!Auth::check()) {
-            return false;
-        }
-        $profile = Auth::user()->profile;
-
-        return Bookmark::whereProfileId($profile->id)->whereStatusId($this->id)->count();
-    }
-
-    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;
-
-        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 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 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 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,
-      ];
-
-        return $obj;
-    }
-
-    public function replyToText()
-    {
-        $actorName = $this->profile->username;
-
-        return "{$actorName} ".__('notification.commented');
-    }
-
-    public function replyToHtml()
-    {
-        $actorName = $this->profile->username;
-        $actorUrl = $this->profile->url();
-
-        return "<a href='{$actorUrl}' class='profile-link'>{$actorName}</a> ".
-          __('notification.commented');
-    }
-
-    public function shareToText()
-    {
-        $actorName = $this->profile->username;
-
-        return "{$actorName} ".__('notification.shared');
-    }
-
-    public function shareToHtml()
-    {
-        $actorName = $this->profile->username;
-        $actorUrl = $this->profile->url();
-
-        return "<a href='{$actorUrl}' class='profile-link'>{$actorName}</a> ".
-          __('notification.shared');
-    }
-
-    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 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);
-            }
-        }
-
-        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 '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];
-    }
-
-    public function place()
-    {
-        return $this->belongsTo(Place::class);
-    }
-
-    public function directMessage()
-    {
-        return $this->hasOne(DirectMessage::class);
-    }
+	use HasSnowflakePrimary, SoftDeletes;
+
+	/**
+	 * 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'];
+
+	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 MAX_MENTIONS = 5;
+
+	const MAX_HASHTAGS = 30;
+
+	const MAX_LINKS = 0;
+
+	public function profile()
+	{
+		return $this->belongsTo(Profile::class);
+	}
+
+	public function media()
+	{
+		return $this->hasMany(Media::class);
+	}
+
+	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 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'));
+			}
+
+			return url(Storage::url($this->firstMedia()->thumbnail_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}";
+
+		return url($path);
+	}
+
+	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}");
+
+		return $url;
+	}
+
+	public function likes()
+	{
+		return $this->hasMany(Like::class);
+	}
+
+	public function liked() : bool
+	{
+		if(!Auth::check()) {
+			return false;
+		}
+
+		$pid = Auth::user()->profile_id;
+
+		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 comments()
+	{
+		return $this->hasMany(self::class, 'in_reply_to_id');
+	}
+
+	public function bookmarked()
+	{
+		if (!Auth::check()) {
+			return false;
+		}
+		$profile = Auth::user()->profile;
+
+		return Bookmark::whereProfileId($profile->id)->whereStatusId($this->id)->count();
+	}
+
+	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;
+
+		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 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 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 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,
+	  ];
+
+		return $obj;
+	}
+
+	public function replyToText()
+	{
+		$actorName = $this->profile->username;
+
+		return "{$actorName} ".__('notification.commented');
+	}
+
+	public function replyToHtml()
+	{
+		$actorName = $this->profile->username;
+		$actorUrl = $this->profile->url();
+
+		return "<a href='{$actorUrl}' class='profile-link'>{$actorName}</a> ".
+		  __('notification.commented');
+	}
+
+	public function shareToText()
+	{
+		$actorName = $this->profile->username;
+
+		return "{$actorName} ".__('notification.shared');
+	}
+
+	public function shareToHtml()
+	{
+		$actorName = $this->profile->username;
+		$actorUrl = $this->profile->url();
+
+		return "<a href='{$actorUrl}' class='profile-link'>{$actorName}</a> ".
+		  __('notification.shared');
+	}
+
+	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 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);
+			}
+		}
+
+		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 '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];
+	}
+
+	public function place()
+	{
+		return $this->belongsTo(Place::class);
+	}
+
+	public function directMessage()
+	{
+		return $this->hasOne(DirectMessage::class);
+	}
 
 }