|
|
|
|
<?php
|
|
|
|
|
|
|
|
|
|
namespace App\Models;
|
|
|
|
|
|
|
|
|
|
use App\HasSnowflakePrimary;
|
|
|
|
|
use App\Http\Controllers\StatusController;
|
|
|
|
|
use App\Services\AccountService;
|
|
|
|
|
use App\Services\StatusService;
|
|
|
|
|
use Illuminate\Database\Eloquent\Collection;
|
feat: add framework integration tests for Laravel 12→13 upgrade readiness
Framework tests verify core Laravel integration points:
- ServiceProviderTest: app boot, guard resolution, route loading, config_cache
- RoutingTest: named routes, duplicates, api/oauth prefixes, middleware groups
- EloquentTest: User/Profile/Status factories, relationships, casts, soft deletes
- QueueTest: job dispatch, serialization, middleware, unique IDs
- ConfigTest: config loading, env overrides, auth/cache/queue settings
- MiddlewarePipelineTest: CSRF, auth, throttle, password confirm, 2FA, admin
Also:
- Add HasFactory trait to Status model
- Fix StatusFactory: remove non-existent 'place' column, add 'rendered' field
- Add Api/AccountTest for account endpoint coverage (254 total tests)
4 weeks ago
|
|
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
|
|
|
|
use Illuminate\Database\Eloquent\Model;
|
fix: add return type declarations to Eloquent relation methods
Larastan 3.x requires explicit return types on relation methods to
verify relation existence when using with(), has(), etc. This adds
the appropriate return type declarations to all relation methods
flagged by the larastan.relationExistence rule.
Models fixed:
- Profile (avatar, statuses)
- User (profile)
- Status (profile, media, hashtags)
- DirectMessage (status, author, recipient)
- Report (reporter, status, reportedUser)
- Like (actor, status)
- Media (status)
- Notification (item)
- HashtagFollow (hashtag)
- OauthClient (user)
- Story (profile)
- StatusHashtag (status, hashtag, profile, media)
- AccountInterstitial (user)
- Hashtag (posts)
- CustomFilter (keywords)
- CustomFilterKeyword (customFilter)
- AdminShadowFilter (profile)
- ImportPost (status)
4 weeks ago
|
|
|
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
|
|
|
|
use Illuminate\Database\Eloquent\Relations\HasMany;
|
|
|
|
|
use Illuminate\Database\Eloquent\Relations\HasManyThrough;
|
|
|
|
|
use Illuminate\Database\Eloquent\SoftDeletes;
|
|
|
|
|
use Illuminate\Support\Carbon;
|
|
|
|
|
use Illuminate\Support\Facades\Auth;
|
|
|
|
|
use Illuminate\Support\Facades\Storage;
|
|
|
|
|
use Illuminate\Support\Str;
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @property int $id
|
|
|
|
|
* @property int $profile_id
|
|
|
|
|
* @property string|null $type
|
|
|
|
|
* @property string|null $caption
|
|
|
|
|
* @property int|null $in_reply_to_id
|
|
|
|
|
* @property int|null $in_reply_to_profile_id
|
|
|
|
|
* @property int|null $reblog_of_id
|
|
|
|
|
* @property bool $is_nsfw
|
|
|
|
|
* @property string|null $scope
|
|
|
|
|
* @property string|null $visibility
|
|
|
|
|
* @property string|null $cw_summary
|
|
|
|
|
* @property bool $comments_disabled
|
|
|
|
|
* @property int $likes_count
|
|
|
|
|
* @property int $reblogs_count
|
|
|
|
|
* @property int $reply_count
|
|
|
|
|
* @property bool $local
|
|
|
|
|
* @property string|null $place
|
|
|
|
|
* @property Carbon|null $created_at
|
|
|
|
|
* @property Carbon|null $updated_at
|
|
|
|
|
* @property Carbon|null $deleted_at
|
|
|
|
|
* @property Carbon|null $edited_at
|
|
|
|
|
* @property-read Profile $profile
|
|
|
|
|
* @property-read Collection<int, Media> $media
|
|
|
|
|
* @property-read Collection<int, Mention> $mentions
|
|
|
|
|
*/
|
|
|
|
|
class Status extends Model
|
|
|
|
|
{
|
feat: add framework integration tests for Laravel 12→13 upgrade readiness
Framework tests verify core Laravel integration points:
- ServiceProviderTest: app boot, guard resolution, route loading, config_cache
- RoutingTest: named routes, duplicates, api/oauth prefixes, middleware groups
- EloquentTest: User/Profile/Status factories, relationships, casts, soft deletes
- QueueTest: job dispatch, serialization, middleware, unique IDs
- ConfigTest: config loading, env overrides, auth/cache/queue settings
- MiddlewarePipelineTest: CSRF, auth, throttle, password confirm, 2FA, admin
Also:
- Add HasFactory trait to Status model
- Fix StatusFactory: remove non-existent 'place' column, add 'rendered' field
- Add Api/AccountTest for account endpoint coverage (254 total tests)
4 weeks ago
|
|
|
use HasFactory, HasSnowflakePrimary, SoftDeletes;
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Indicates if the IDs are auto-incrementing.
|
|
|
|
|
*
|
|
|
|
|
* @var bool
|
|
|
|
|
*/
|
|
|
|
|
public $incrementing = false;
|
|
|
|
|
|
|
|
|
|
protected $guarded = [];
|
|
|
|
|
|
|
|
|
|
protected function casts(): array
|
|
|
|
|
{
|
|
|
|
|
return [
|
|
|
|
|
'deleted_at' => 'datetime',
|
|
|
|
|
'edited_at' => 'datetime',
|
|
|
|
|
];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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 = 20;
|
|
|
|
|
|
|
|
|
|
const MAX_HASHTAGS = 60;
|
|
|
|
|
|
|
|
|
|
const MAX_LINKS = 5;
|
|
|
|
|
|
fix: add return type declarations to Eloquent relation methods
Larastan 3.x requires explicit return types on relation methods to
verify relation existence when using with(), has(), etc. This adds
the appropriate return type declarations to all relation methods
flagged by the larastan.relationExistence rule.
Models fixed:
- Profile (avatar, statuses)
- User (profile)
- Status (profile, media, hashtags)
- DirectMessage (status, author, recipient)
- Report (reporter, status, reportedUser)
- Like (actor, status)
- Media (status)
- Notification (item)
- HashtagFollow (hashtag)
- OauthClient (user)
- Story (profile)
- StatusHashtag (status, hashtag, profile, media)
- AccountInterstitial (user)
- Hashtag (posts)
- CustomFilter (keywords)
- CustomFilterKeyword (customFilter)
- AdminShadowFilter (profile)
- ImportPost (status)
4 weeks ago
|
|
|
public function profile(): BelongsTo
|
|
|
|
|
{
|
|
|
|
|
return $this->belongsTo(Profile::class);
|
|
|
|
|
}
|
|
|
|
|
|
fix: add return type declarations to Eloquent relation methods
Larastan 3.x requires explicit return types on relation methods to
verify relation existence when using with(), has(), etc. This adds
the appropriate return type declarations to all relation methods
flagged by the larastan.relationExistence rule.
Models fixed:
- Profile (avatar, statuses)
- User (profile)
- Status (profile, media, hashtags)
- DirectMessage (status, author, recipient)
- Report (reporter, status, reportedUser)
- Like (actor, status)
- Media (status)
- Notification (item)
- HashtagFollow (hashtag)
- OauthClient (user)
- Story (profile)
- StatusHashtag (status, hashtag, profile, media)
- AccountInterstitial (user)
- Hashtag (posts)
- CustomFilter (keywords)
- CustomFilterKeyword (customFilter)
- AdminShadowFilter (profile)
- ImportPost (status)
4 weeks ago
|
|
|
public function media(): HasMany
|
|
|
|
|
{
|
|
|
|
|
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)
|
|
|
|
|
{
|
|
|
|
|
$entity = StatusService::get($this->id, false);
|
|
|
|
|
|
|
|
|
|
if (! $entity || ! isset($entity['media_attachments']) || empty($entity['media_attachments'])) {
|
|
|
|
|
return url(Storage::url('public/no-preview.png'));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ((! isset($entity['sensitive']) || $entity['sensitive']) && ! $showNsfw) {
|
|
|
|
|
return url(Storage::url('public/no-preview.png'));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (! isset($entity['visibility']) || ! in_array($entity['visibility'], ['public', 'unlisted'])) {
|
|
|
|
|
return url(Storage::url('public/no-preview.png'));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return collect($entity['media_attachments'])
|
|
|
|
|
->filter(fn ($media): bool => $media['type'] == 'image' && in_array($media['mime'], ['image/jpeg', 'image/png', 'image/jpg']))
|
|
|
|
|
->map(function ($media) {
|
|
|
|
|
if (! Str::endsWith($media['preview_url'], ['no-preview.png', 'no-preview.jpg'])) {
|
|
|
|
|
return $media['preview_url'];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return $media['url'];
|
|
|
|
|
})
|
|
|
|
|
->first() ?? url(Storage::url('public/no-preview.png'));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function url($forceLocal = false)
|
|
|
|
|
{
|
|
|
|
|
if ($this->uri) {
|
|
|
|
|
return $forceLocal ? "/i/web/post/_/{$this->profile_id}/{$this->id}" : $this->uri;
|
|
|
|
|
}
|
|
|
|
|
$id = $this->id;
|
|
|
|
|
$account = AccountService::get($this->profile_id, true);
|
|
|
|
|
if (! $account || ! isset($account['username'])) {
|
|
|
|
|
return '/404';
|
|
|
|
|
}
|
|
|
|
|
$path = url(config('app.url')."/p/{$account['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(): string
|
|
|
|
|
{
|
|
|
|
|
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);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function conversation()
|
|
|
|
|
{
|
|
|
|
|
return $this->hasOne(Conversation::class);
|
|
|
|
|
}
|
|
|
|
|
|
fix: add return type declarations to Eloquent relation methods
Larastan 3.x requires explicit return types on relation methods to
verify relation existence when using with(), has(), etc. This adds
the appropriate return type declarations to all relation methods
flagged by the larastan.relationExistence rule.
Models fixed:
- Profile (avatar, statuses)
- User (profile)
- Status (profile, media, hashtags)
- DirectMessage (status, author, recipient)
- Report (reporter, status, reportedUser)
- Like (actor, status)
- Media (status)
- Notification (item)
- HashtagFollow (hashtag)
- OauthClient (user)
- Story (profile)
- StatusHashtag (status, hashtag, profile, media)
- AccountInterstitial (user)
- Hashtag (posts)
- CustomFilter (keywords)
- CustomFilterKeyword (customFilter)
- AdminShadowFilter (profile)
- ImportPost (status)
4 weeks ago
|
|
|
public function hashtags(): HasManyThrough
|
|
|
|
|
{
|
|
|
|
|
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(): string
|
|
|
|
|
{
|
|
|
|
|
return route('report.form')."?type=post&id={$this->id}";
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function toActivityStream(): array
|
|
|
|
|
{
|
|
|
|
|
$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' => 'Document',
|
|
|
|
|
'name' => null,
|
|
|
|
|
'url' => $mediaCollection,
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
return $obj;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function recentComments()
|
|
|
|
|
{
|
|
|
|
|
return $this->comments()->orderBy('created_at', 'desc')->take(3);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function scopeToAudience($audience)
|
|
|
|
|
{
|
|
|
|
|
if (! in_array($audience, ['to', 'cc']) || $this->local == false) {
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
$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);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function poll()
|
|
|
|
|
{
|
|
|
|
|
return $this->hasOne(Poll::class);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function edits()
|
|
|
|
|
{
|
|
|
|
|
return $this->hasMany(StatusEdit::class);
|
|
|
|
|
}
|
|
|
|
|
}
|