Merge pull request #3295 from pixelfed/staging

Staging
This commit is contained in:
daniel 2022-03-08 21:02:27 -07:00 committed by GitHub
commit 7ab581f700
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
13 changed files with 307 additions and 74 deletions

View file

@ -67,6 +67,12 @@
- Updated admin diagnostics, add more configuration data to help diagnose potential issues. ([eab96fc3](https://github.com/pixelfed/pixelfed/commit/eab96fc3))
- Updated ConfigCacheService, fix discover features. ([ad48521a](https://github.com/pixelfed/pixelfed/commit/ad48521a))
- Updated MediaTransformer, fix type case bug. Fixes #3281. ([c1669253](https://github.com/pixelfed/pixelfed/commit/c1669253))
- Updated SpaController, redirect web ui hashtags to legacy page for unauthenticated users. ([a44b812b](https://github.com/pixelfed/pixelfed/commit/a44b812b))
- Updated ApiV1Controller, fixes #3288. ([3e670774](https://github.com/pixelfed/pixelfed/commit/3e670774))
- Updated AP Helpers, fixes #3287. ([b78bff72](https://github.com/pixelfed/pixelfed/commit/b78bff72))
- Updated AP Helpers, fixes #3290. ([53975206](https://github.com/pixelfed/pixelfed/commit/53975206))
- Updated AccountController, refresh relationship after handling follow request. ([fe768785](https://github.com/pixelfed/pixelfed/commit/fe768785))
- Updated CollectionController, fixes #3289. ([c7e1e473](https://github.com/pixelfed/pixelfed/commit/c7e1e473))
- ([](https://github.com/pixelfed/pixelfed/commit/))
## [v0.11.2 (2022-01-09)](https://github.com/pixelfed/pixelfed/compare/v0.11.1...v0.11.2)

View file

@ -406,6 +406,7 @@ class AccountController extends Controller
Cache::forget('profile:follower_count:'.$pid);
Cache::forget('profile:following_count:'.$pid);
RelationshipService::refresh($pid, $follower->id);
return response()->json(['msg' => 'success'], 200);
}

View file

@ -2547,7 +2547,7 @@ class ApiV1Controller extends Controller
$sortBy = $request->input('sort', 'all');
if($sortBy == 'all' && $status['replies_count'] && $request->has('refresh_cache')) {
if($sortBy == 'all' && isset($status['replies_count']) && $status['replies_count'] && $request->has('refresh_cache')) {
if(!Cache::has('status:replies:all-rc:' . $id)) {
Cache::forget('status:replies:all:' . $id);
Cache::put('status:replies:all-rc:' . $id, true, 300);

View file

@ -17,6 +17,8 @@ use App\Transformer\Api\{
};
use League\Fractal\Serializer\ArraySerializer;
use League\Fractal\Pagination\IlluminatePaginatorAdapter;
use App\Services\CollectionService;
use App\Services\FollowerService;
use App\Services\StatusService;
class CollectionController extends Controller
@ -30,18 +32,25 @@ class CollectionController extends Controller
'profile_id' => $profile->id,
'published_at' => null
]);
$collection->visibility = 'draft';
$collection->save();
return view('collection.create', compact('collection'));
}
public function show(Request $request, int $id)
{
$user = $request->user();
$collection = Collection::findOrFail($id);
if($collection->published_at == null || $collection->visibility != 'public') {
if(!$user || $user->profile_id != $collection->profile_id) {
abort_unless($user && $user->is_admin, 404);
}
}
$collection = CollectionService::getCollection($id);
abort_if(!$collection, 404);
if($collection['published_at'] == null || $collection['visibility'] != 'public') {
abort_if(!$user, 404);
if($user->profile_id != $collection['pid']) {
if(!$user->is_admin) {
abort_if($collection['visibility'] != 'private', 404);
abort_if(!FollowerService::follows($user->profile_id, $collection['pid']), 404);
}
}
}
return view('collection.show', compact('collection'));
}
@ -57,7 +66,7 @@ class CollectionController extends Controller
$this->validate($request, [
'title' => 'nullable',
'description' => 'nullable',
'visibility' => 'nullable|string|in:public,private'
'visibility' => 'nullable|string|in:public,private,draft'
]);
$profile = Auth::user()->profile;
@ -67,7 +76,7 @@ class CollectionController extends Controller
$collection->visibility = e($request->input('visibility'));
$collection->save();
return 200;
return CollectionService::setCollection($collection->id, $collection);
}
public function publish(Request $request, int $id)
@ -76,7 +85,7 @@ class CollectionController extends Controller
$this->validate($request, [
'title' => 'nullable',
'description' => 'nullable',
'visibility' => 'required|alpha|in:public,private'
'visibility' => 'required|alpha|in:public,private,draft'
]);
$profile = Auth::user()->profile;
$collection = Collection::whereProfileId($profile->id)->findOrFail($id);
@ -88,8 +97,7 @@ class CollectionController extends Controller
$collection->visibility = e($request->input('visibility'));
$collection->published_at = now();
$collection->save();
return $collection->url();
return CollectionService::setCollection($collection->id, $collection);
}
public function delete(Request $request, int $id)
@ -105,6 +113,8 @@ class CollectionController extends Controller
return 200;
}
CollectionService::deleteCollection($id);
return redirect('/');
}
@ -139,82 +149,98 @@ class CollectionController extends Controller
'order' => $count,
]);
return 200;
CollectionService::addItem(
$collection->id,
$status->id,
$count
);
return StatusService::get($status->id);
}
public function get(Request $request, $id)
public function getCollection(Request $request, $id)
{
$user = $request->user();
$collection = Collection::findOrFail($id);
if($collection->published_at == null || $collection->visibility != 'public') {
if(!$user || $user->profile_id != $collection->profile_id) {
abort_unless($user && $user->is_admin, 404);
}
}
$user = $request->user();
$collection = CollectionService::getCollection($id);
if($collection['published_at'] == null || $collection['visibility'] != 'public') {
abort_unless($user, 404);
if($user->profile_id != $collection['pid']) {
if(!$user->is_admin) {
abort_if($collection['visibility'] != 'private', 404);
abort_if(!FollowerService::follows($user->profile_id, $collection['pid']), 404);
}
}
}
return [
'id' => (string) $collection->id,
'visibility' => $collection->visibility,
'title' => $collection->title,
'description' => $collection->description,
'thumb' => $collection->posts()->first()->thumb(),
'url' => $collection->url(),
'post_count' => $collection->posts()->count(),
'published_at' => $collection->published_at
];
return $collection;
}
public function getItems(Request $request, int $id)
{
$collection = Collection::findOrFail($id);
if($collection->visibility !== 'public') {
abort_if(!Auth::check() || Auth::user()->profile_id != $collection->profile_id, 404);
}
$user = $request->user();
$collection = CollectionService::getCollection($id);
if($collection['published_at'] == null || $collection['visibility'] != 'public') {
abort_unless($user, 404);
if($user->profile_id != $collection['pid']) {
if(!$user->is_admin) {
abort_if($collection['visibility'] != 'private', 404);
abort_if(!FollowerService::follows($user->profile_id, $collection['pid']), 404);
}
}
}
$page = $request->input('page') ?? 1;
$start = $page == 1 ? 0 : ($page * 10 - 10);
$end = $start + 10;
$items = CollectionService::getItems($id, $start, $end);
$res = CollectionItem::whereCollectionId($id)
->pluck('object_id')
return collect($items)
->map(function($id) {
return StatusService::get($id);
})
->filter(function($post) {
return $post && isset($post['account']);
->filter(function($item) {
return $item && isset($item['account'], $item['media_attachments']);
})
->values();
return response()->json($res);
}
public function getUserCollections(Request $request, int $id)
{
$user = $request->user();
$pid = $user ? $user->profile_id : null;
$follows = false;
$visibility = ['public'];
$profile = Profile::whereNull('status')
->whereNull('domain')
->findOrFail($id);
if($profile->is_private) {
abort_if(!$pid, 404);
abort_if(!$profile->id != $pid, 404);
if($pid) {
$follows = FollowerService::follows($pid, $profile->id);
}
$visibility = $pid == $profile->id ? ['public', 'private'] : ['public'];
if($profile->is_private) {
abort_if(!$pid, 404);
if(!$user->is_admin) {
abort_if($profile->id != $pid && $follows == false, 404);
}
}
$owner = $pid ? $pid == $profile->id : false;
if($follows) {
$visibility = ['public', 'private'];
}
if($pid && $pid == $profile->id) {
$visibility = ['public', 'private', 'draft'];
}
return Collection::whereProfileId($profile->id)
->whereIn('visibility', $visibility)
->orderByDesc('id')
->paginate(9)
->map(function($collection) {
return [
'id' => (string) $collection->id,
'visibility' => $collection->visibility,
'title' => $collection->title,
'description' => $collection->description,
'thumb' => $collection->posts()->first()->thumb(),
'url' => $collection->url(),
'post_count' => $collection->posts()->count(),
'published_at' => $collection->published_at
];
return CollectionService::getCollection($collection->id);
});
}
@ -240,6 +266,11 @@ class CollectionController extends Controller
->whereIn('type', ['photo', 'photo:album', 'video'])
->findOrFail($postId);
CollectionService::removeItem(
$collection->id,
$status->id
);
$item = CollectionItem::whereCollectionId($collection->id)
->whereObjectType('App\Status')
->whereObjectId($status->id)

View file

@ -126,4 +126,13 @@ class SpaController extends Controller
}
return redirect('/i/web/profile/' . $id);
}
public function hashtagRedirect(Request $request, $tag)
{
if(!$request->user()) {
return redirect('/discover/tags/' . $tag);
}
return view('layouts.spa');
}
}

View file

@ -0,0 +1,141 @@
<?php
namespace App\Services;
use App\Collection;
use App\CollectionItem;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Redis;
class CollectionService
{
const CACHE_KEY = 'pf:services:collections:';
public static function getItems($id, $start = 0, $stop = 10)
{
if(self::count($id)) {
return Redis::zrangebyscore(self::CACHE_KEY . 'items:' . $id, $start, $stop);
}
return self::coldBootItems($id);
}
public static function addItem($id, $sid, $score)
{
Redis::zadd(self::CACHE_KEY . 'items:' . $id, $score, $sid);
}
public static function removeItem($id, $sid)
{
Redis::zrem(self::CACHE_KEY . 'items:' . $id, $sid);
}
public static function clearItems($id)
{
Redis::del(self::CACHE_KEY . 'items:' . $id);
}
public static function coldBootItems($id)
{
return Cache::remember(self::CACHE_KEY . 'items:' . $id, 86400, function() use($id) {
return CollectionItem::whereCollectionId($id)
->orderBy('order')
->get()
->each(function($item) use ($id) {
self::addItem($id, $item->object_id, $item->order);
})
->map(function($item) {
return (string) $item->object_id;
})
->values()
->toArray();
});
}
public static function count($id)
{
$count = Redis::zcard(self::CACHE_KEY . 'items:' . $id);
if(!$count) {
self::coldBootItems($id);
$count = Redis::zcard(self::CACHE_KEY . 'items:' . $id);
}
return $count;
}
public static function getCollection($id)
{
$collection = Cache::remember(self::CACHE_KEY . 'get:' . $id, 86400, function() use($id) {
$collection = Collection::find($id);
if(!$collection) {
return false;
}
$account = AccountService::get($collection->profile_id);
if(!$account) {
return false;
}
return [
'id' => (string) $collection->id,
'pid' => (string) $collection->profile_id,
'username' => $account['username'],
'visibility' => $collection->visibility,
'title' => $collection->title,
'description' => $collection->description,
'thumb' => self::getThumb($id),
'url' => $collection->url(),
'published_at' => $collection->published_at
];
});
if($collection) {
$collection['post_count'] = self::count($id);
}
return $collection;
}
public static function setCollection($id, $collection)
{
$account = AccountService::get($collection->profile_id);
if(!$account) {
return false;
}
$res = [
'id' => (string) $collection->id,
'pid' => (string) $collection->profile_id,
'username' => $account['username'],
'visibility' => $collection->visibility,
'title' => $collection->title,
'description' => $collection->description,
'thumb' => self::getThumb($id),
'url' => $collection->url(),
'published_at' => $collection->published_at
];
Cache::put(self::CACHE_KEY . 'get:' . $id, $res, 86400);
$res['post_count'] = self::count($id);
return $res;
}
public static function deleteCollection($id)
{
Redis::del(self::CACHE_KEY . 'items:' . $id);
Cache::forget(self::CACHE_KEY . 'get:' . $id);
}
public static function getThumb($id)
{
$item = self::getItems($id, 0, 1);
if(!$item || empty($item)) {
return '/storage/no-preview.png';
}
$status = StatusService::get($item[0]);
if(!$status) {
return '/storage/no-preview.png';
}
if(!isset($status['media_attachments']) || empty($status['media_attachments'])) {
return '/storage/no-preview.png';
}
return $status['media_attachments'][0]['url'];
}
}

View file

@ -71,11 +71,25 @@ class Helpers {
$mimeTypes = explode(',', config_cache('pixelfed.media_types'));
$mediaTypes = in_array('video/mp4', $mimeTypes) ? ['Document', 'Image', 'Video'] : ['Document', 'Image'];
// Peertube
// $mediaTypes = in_array('video/mp4', $mimeTypes) ? ['Document', 'Image', 'Video', 'Link'] : ['Document', 'Image'];
if(!isset($activity['attachment']) || empty($activity['attachment'])) {
return false;
}
// peertube
// $attachment = is_array($activity['url']) ?
// collect($activity['url'])
// ->filter(function($media) {
// return $media['type'] == 'Link' && $media['mediaType'] == 'video/mp4';
// })
// ->take(1)
// ->values()
// ->toArray()[0] : $activity['attachment'];
$attachment = $activity['attachment'];
$valid = Validator::make($attachment, [
'*.type' => [
'required',
@ -88,7 +102,7 @@ class Helpers {
'string',
Rule::in($mimeTypes)
],
'*.name' => 'nullable|string|max:255'
'*.name' => 'sometimes|nullable|string|max:255'
])->passes();
return $valid;
@ -247,6 +261,19 @@ class Helpers {
return self::fetchFromUrl($url);
}
public static function pluckval($val)
{
if(is_string($val)) {
return $val;
}
if(is_array($val)) {
return !empty($val) ? $val[0] : null;
}
return null;
}
public static function statusFirstOrFetch($url, $replyTo = false)
{
$url = self::validateUrl($url);
@ -330,7 +357,7 @@ class Helpers {
}
}
$id = isset($res['id']) ? $res['id'] : $url;
$id = isset($res['id']) ? self::pluckval($res['id']) : self::pluckval($url);
$idDomain = parse_url($id, PHP_URL_HOST);
$urlDomain = parse_url($url, PHP_URL_HOST);
@ -338,9 +365,20 @@ class Helpers {
return;
}
if(isset($activity['object']['attributedTo'])) {
$actorDomain = parse_url($activity['object']['attributedTo'], PHP_URL_HOST);
if(!self::validateUrl($activity['object']['attributedTo']) ||
$attributedTo = is_string($activity['object']['attributedTo']) ?
$activity['object']['attributedTo'] :
(is_array($activity['object']['attributedTo']) ?
collect($activity['object']['attributedTo'])
->filter(function($o) {
return $o && isset($o['type']) && $o['type'] == 'Person';
})
->pluck('id')
->first() : null
);
if($attributedTo) {
$actorDomain = parse_url($attributedTo, PHP_URL_HOST);
if(!self::validateUrl($attributedTo) ||
$idDomain !== $actorDomain ||
$actorDomain !== $urlDomain
)
@ -353,14 +391,14 @@ class Helpers {
return;
}
$profile = self::profileFirstOrNew($activity['object']['attributedTo']);
$profile = self::profileFirstOrNew($attributedTo);
if(isset($activity['object']['inReplyTo']) && !empty($activity['object']['inReplyTo']) || $replyTo == true) {
$reply_to = self::statusFirstOrFetch($activity['object']['inReplyTo'], false);
$reply_to = self::statusFirstOrFetch(self::pluckval($activity['object']['inReplyTo']), false);
$reply_to = optional($reply_to)->id;
} else {
$reply_to = null;
}
$ts = is_array($res['published']) ? $res['published'][0] : $res['published'];
$ts = self::pluckval($res['published']);
if($scope == 'public' && in_array($urlDomain, InstanceService::getUnlistedDomains())) {
$scope = 'unlisted';
@ -399,8 +437,8 @@ class Helpers {
return DB::transaction(function() use($profile, $res, $url, $ts, $reply_to, $cw, $scope, $id) {
$status = new Status;
$status->profile_id = $profile->id;
$status->url = isset($res['url']) ? $res['url'] : $url;
$status->uri = isset($res['url']) ? $res['url'] : $url;
$status->url = isset($res['url']) && is_string($res['url']) ? $res['url'] : $url;
$status->uri = isset($res['url']) && is_string($res['url']) ? $res['url'] : $url;
$status->object_url = $id;
$status->caption = strip_tags($res['content']);
$status->rendered = Purify::clean($res['content']);
@ -486,10 +524,16 @@ class Helpers {
public static function importNoteAttachment($data, Status $status)
{
if(self::verifyAttachments($data) == false) {
// \Log::info('importNoteAttachment::failedVerification.', [$data['id']]);
$status->viewType();
return;
}
$attachments = isset($data['object']) ? $data['object']['attachment'] : $data['attachment'];
// peertube
// if(!$attachments) {
// $obj = isset($data['object']) ? $data['object'] : $data;
// $attachments = is_array($obj['url']) ? $obj['url'] : null;
// }
$user = $status->profile;
$storagePath = MediaPathService::get($user, 2);
$allowed = explode(',', config_cache('pixelfed.media_types'));
@ -585,7 +629,7 @@ class Helpers {
$profile->bio = isset($res['summary']) ? Purify::clean($res['summary']) : null;
$profile->sharedInbox = isset($res['endpoints']) && isset($res['endpoints']['sharedInbox']) ? $res['endpoints']['sharedInbox'] : null;
$profile->inbox_url = $res['inbox'];
$profile->outbox_url = $res['outbox'];
$profile->outbox_url = isset($res['outbox']) ? $res['outbox'] : null;
$profile->remote_url = $res['id'];
$profile->public_key = $res['publicKey']['publicKeyPem'];
$profile->key_id = $res['publicKey']['id'];

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View file

@ -4,12 +4,12 @@
<div class="container">
<collection-component
collection-id="{{$collection->id}}"
collection-title="{{$collection->title}}"
collection-description="{{$collection->description}}"
collection-visibility="{{$collection->visibility}}"
profile-id="{{$collection->profile_id}}"
profile-username="{{$collection->profile->username}}"
collection-id="{{$collection['id']}}"
collection-title="{{$collection['title']}}"
collection-description="{{$collection['description']}}"
collection-visibility="{{$collection['visibility']}}"
profile-id="{{$collection['pid']}}"
profile-username="{{$collection['username']}}"
></collection-component>
</div>
@ -30,4 +30,4 @@
<script type="text/javascript" src="{{mix('js/compose.js')}}" async></script>
<script type="text/javascript" src="{{mix('js/collections.js')}}"></script>
<script type="text/javascript">App.boot()</script>
@endpush
@endpush

View file

@ -237,7 +237,7 @@ Route::domain(config('pixelfed.domain.app'))->middleware(['validemail', 'twofact
Route::get('collection/items/{id}', 'CollectionController@getItems');
Route::post('collection/item', 'CollectionController@storeId');
Route::delete('collection/item', 'CollectionController@deleteId');
Route::get('collection/{id}', 'CollectionController@get');
Route::get('collection/{id}', 'CollectionController@getCollection');
Route::post('collection/{id}', 'CollectionController@store');
Route::delete('collection/{id}', 'CollectionController@delete');
Route::post('collection/{id}/publish', 'CollectionController@publish');
@ -351,6 +351,7 @@ Route::domain(config('pixelfed.domain.app'))->middleware(['validemail', 'twofact
Route::post('warning', 'AccountInterstitialController@read');
Route::get('my2020', 'SeasonalController@yearInReview');
Route::get('web/hashtag/{tag}', 'SpaController@hashtagRedirect');
Route::get('web/username/{id}', 'SpaController@usernameRedirect');
Route::get('web/post/{id}', 'SpaController@webPost');
Route::get('web/profile/{id}', 'SpaController@webProfile');