Merge pull request #3194 from pixelfed/staging

Staging
This commit is contained in:
daniel 2022-01-28 22:20:38 -07:00 committed by GitHub
commit a2c56c4e71
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
26 changed files with 228 additions and 74 deletions

View file

@ -19,6 +19,14 @@
- Updated ApiV1Controller, improve follow count cache invalidation. ([4b6effb9](https://github.com/pixelfed/pixelfed/commit/4b6effb9))
- Updated web routes, fix atom feeds for account usernames containing a dot. ([8c54ab57](https://github.com/pixelfed/pixelfed/commit/8c54ab57))
- Updated atom feeds, include media alt text. Fixes #3184. ([5d9b6863](https://github.com/pixelfed/pixelfed/commit/5d9b6863))
- Updated ApiV1Controller, add custom_emoji endpoint. ([16e72518](https://github.com/pixelfed/pixelfed/commit/16e72518))
- Updated InternalApiController, redirect remote post and profiles to Metro 2.0. ([3c35158e](https://github.com/pixelfed/pixelfed/commit/3c35158e))
- Updated BaseApiController, improve favourites endpoint. ([f063cb01](https://github.com/pixelfed/pixelfed/commit/f063cb01))
- Updated ApiV1Controller, invalidate status reply cache on new reply. ([3c261bbf](https://github.com/pixelfed/pixelfed/commit/3c261bbf))
- Updated PublicApiController, add bookmark state to timeline endpoints. ([c0b1e042](https://github.com/pixelfed/pixelfed/commit/c0b1e042))
- Updated ApiV1Controller, fix private status replies returning 404. ([73226360](https://github.com/pixelfed/pixelfed/commit/73226360))
- Updated StatusService, use BookmarkService for bookmarked state. ([a7d71551](https://github.com/pixelfed/pixelfed/commit/a7d71551))
- Updated Apis, added ReblogService to improve reblogged state for api entities ([6cfd6be5](https://github.com/pixelfed/pixelfed/commit/6cfd6be5))
- ([](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

@ -360,6 +360,7 @@ class AdminController extends Controller
if($request->has('cc')) {
Cache::forget('pf:admin:custom_emoji:stats');
Cache::forget('pf:custom_emoji');
return redirect(route('admin.custom-emoji'));
}
@ -463,6 +464,7 @@ class AdminController extends Controller
$request->emoji->storeAs('public/emoji', $fileName);
$emoji->media_path = 'emoji/' . $fileName;
$emoji->save();
Cache::forget('pf:custom_emoji');
return redirect(route('admin.custom-emoji'));
}
@ -471,6 +473,7 @@ class AdminController extends Controller
abort_unless(config('federation.custom_emoji.enabled'), 404);
$emoji = CustomEmoji::findOrFail($id);
Storage::delete("public/{$emoji->media_path}");
Cache::forget('pf:custom_emoji');
$emoji->delete();
return redirect(route('admin.custom-emoji'));
}

View file

@ -65,6 +65,7 @@ use App\Services\{
NotificationService,
MediaPathService,
PublicTimelineService,
ReblogService,
RelationshipService,
SearchApiV2Service,
StatusService,
@ -77,6 +78,7 @@ use App\Util\Localization\Localization;
use App\Util\Media\License;
use App\Jobs\MediaPipeline\MediaSyncLicensePipeline;
use App\Services\DiscoverService;
use App\Services\CustomEmojiService;
class ApiV1Controller extends Controller
{
@ -920,7 +922,7 @@ class ApiV1Controller extends Controller
*/
public function customEmojis()
{
return response()->json([]);
return response(CustomEmojiService::all())->header('Content-Type', 'application/json');
}
/**
@ -1645,6 +1647,7 @@ class ApiV1Controller extends Controller
if($pid) {
$status['favourited'] = (bool) LikeService::liked($pid, $s['id']);
$status['reblogged'] = (bool) ReblogService::get($pid, $status['id']);
}
return $status;
})
@ -1675,6 +1678,7 @@ class ApiV1Controller extends Controller
if($pid) {
$status['favourited'] = (bool) LikeService::liked($pid, $s['id']);
$status['reblogged'] = (bool) ReblogService::get($pid, $status['id']);
}
return $status;
})
@ -1797,6 +1801,7 @@ class ApiV1Controller extends Controller
if($user) {
$status['favourited'] = (bool) LikeService::liked($user->profile_id, $k);
$status['reblogged'] = (bool) ReblogService::get($user->profile_id, $status['id']);
}
return $status;
})
@ -1838,7 +1843,7 @@ class ApiV1Controller extends Controller
}
$res['favourited'] = LikeService::liked($user->profile_id, $res['id']);
$res['reblogged'] = false;
$res['reblogged'] = ReblogService::get($user->profile_id, $res['id']);
return response()->json($res);
}
@ -2109,6 +2114,7 @@ class ApiV1Controller extends Controller
$status->in_reply_to_profile_id = $parent->profile_id;
$status->save();
StatusService::del($parent->id);
Cache::forget('status:replies:all:' . $parent->id);
}
if($ids) {
@ -2236,7 +2242,7 @@ class ApiV1Controller extends Controller
}
StatusService::del($status->id);
ReblogService::add($user->profile_id, $status->id);
$res = StatusService::getMastodon($status->id);
$res['reblogged'] = true;
@ -2276,6 +2282,7 @@ class ApiV1Controller extends Controller
}
UndoSharePipeline::dispatch($reblog);
ReblogService::del($user->profile_id, $status->id);
$res = StatusService::getMastodon($status->id);
$res['reblogged'] = true;
@ -2512,12 +2519,25 @@ class ApiV1Controller extends Controller
$limit = $request->input('limit', 3);
$pid = $request->user()->profile_id;
$status = StatusService::getMastodon($id);
$status = StatusService::getMastodon($id, false);
abort_if(!$status || !in_array($status['visibility'], ['public', 'unlisted']), 404);
abort_if(!$status, 404);
if($status['visibility'] == 'private') {
if($pid != $status['account']['id']) {
abort_unless(FollowerService::follows($pid, $status['account']['id']), 404);
}
}
$sortBy = $request->input('sort', 'all');
if($sortBy == 'all' && $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);
}
}
if($sortBy == 'all' && !$request->has('cursor')) {
$ids = Cache::remember('status:replies:all:' . $id, 86400, function() use($id) {
return DB::table('statuses')

View file

@ -259,27 +259,29 @@ class BaseApiController extends Controller
public function accountLikes(Request $request)
{
$user = $request->user();
abort_if(!$request->user(), 403);
$this->validate($request, [
'page' => 'sometimes|int|min:1|max:20',
'limit' => 'sometimes|int|min:1|max:10'
]);
$limit = 10;
$page = (int) $request->input('page', 1);
$user = $request->user();
$limit = $request->input('limit', 10);
if($page > 20) {
return [];
}
$favourites = $user->profile->likes()
->latest()
->simplePaginate($limit)
->pluck('status_id');
$statuses = Status::find($favourites)->reverse();
$resource = new Fractal\Resource\Collection($statuses, new StatusStatelessTransformer());
$res = $this->fractal->createData($resource)->toArray();
return response()->json($res, 200, [], JSON_PRETTY_PRINT|JSON_UNESCAPED_SLASHES);
$res = \DB::table('likes')
->whereProfileId($user->profile_id)
->latest()
->simplePaginate($limit)
->map(function($id) {
$status = StatusService::get($id->status_id, false);
$status['favourited'] = true;
return $status;
})
->filter(function($post) {
return $post && isset($post['account']);
})
->values();
return response()->json($res);
}
public function archive(Request $request, $id)

View file

@ -6,6 +6,7 @@ use App\Bookmark;
use App\Status;
use Auth;
use Illuminate\Http\Request;
use App\Services\BookmarkService;
class BookmarkController extends Controller
{
@ -28,7 +29,10 @@ class BookmarkController extends Controller
);
if (!$bookmark->wasRecentlyCreated) {
BookmarkService::del($profile->id, $status->id);
$bookmark->delete();
} else {
BookmarkService::add($profile->id, $status->id);
}
if ($request->ajax()) {

View file

@ -43,6 +43,7 @@ use App\Services\SnowflakeService;
use App\Services\StatusService;
use App\Services\UserFilterService;
use App\Services\DiscoverService;
use App\Services\BookmarkService;
class InternalApiController extends Controller
{
@ -316,16 +317,21 @@ class InternalApiController extends Controller
public function bookmarks(Request $request)
{
$res = Bookmark::whereProfileId($request->user()->profile_id)
$pid = $request->user()->profile_id;
$res = Bookmark::whereProfileId($pid)
->orderByDesc('created_at')
->simplePaginate(10)
->map(function($bookmark) {
$status = StatusService::get($bookmark->status_id);
->map(function($bookmark) use($pid) {
$status = StatusService::get($bookmark->status_id, false);
$status['bookmarked_at'] = $bookmark->created_at->format('c');
if($status) {
BookmarkService::add($pid, $status['id']);
}
return $status;
})
->filter(function($bookmark) {
return isset($bookmark['id']);
return $bookmark && isset($bookmark['id']);
})
->values();
@ -410,26 +416,12 @@ class InternalApiController extends Controller
public function remoteProfile(Request $request, $id)
{
$profile = Profile::whereNull('status')
->whereNotNull('domain')
->findOrFail($id);
$user = Auth::user();
return view('profile.remote', compact('profile', 'user'));
return redirect('/i/web/profile/' . $id);
}
public function remoteStatus(Request $request, $profileId, $statusId)
{
$user = Profile::whereNull('status')
->whereNotNull('domain')
->findOrFail($profileId);
$status = Status::whereProfileId($user->id)
->whereNull('reblog_of_id')
->whereIn('visibility', ['public', 'unlisted'])
->findOrFail($statusId);
$template = $status->in_reply_to_id ? 'status.reply' : 'status.remote';
return view($template, compact('user', 'status'));
return redirect('/i/web/post/' . $statusId);
}
public function requestEmailVerification(Request $request)

View file

@ -27,10 +27,12 @@ use App\Transformer\Api\{
};
use App\Services\{
AccountService,
BookmarkService,
FollowerService,
LikeService,
PublicTimelineService,
ProfileService,
ReblogService,
RelationshipService,
StatusService,
SnowflakeService,
@ -327,6 +329,8 @@ class PublicApiController extends Controller
return false;
}
$status['favourited'] = (bool) LikeService::liked($user->profile_id, $s->id);
$status['bookmarked'] = (bool) BookmarkService::get($user->profile_id, $s->id);
$status['reblogged'] = (bool) ReblogService::get($user->profile_id, $s->id);
return $status;
})
->filter(function($s) use($filtered) {
@ -369,6 +373,8 @@ class PublicApiController extends Controller
return false;
}
$status['favourited'] = (bool) LikeService::liked($user->profile_id, $s->id);
$status['bookmarked'] = (bool) BookmarkService::get($user->profile_id, $s->id);
$status['reblogged'] = (bool) ReblogService::get($user->profile_id, $s->id);
return $status;
})
->filter(function($s) use($filtered) {
@ -398,6 +404,8 @@ class PublicApiController extends Controller
$status = StatusService::get($k);
if($user) {
$status['favourited'] = (bool) LikeService::liked($user->profile_id, $k);
$status['bookmarked'] = (bool) BookmarkService::get($user->profile_id, $k);
$status['reblogged'] = (bool) ReblogService::get($user->profile_id, $k);
$status['relationship'] = RelationshipService::get($user->profile_id, $status['account']['id']);
}
return $status;
@ -481,7 +489,7 @@ class PublicApiController extends Controller
if($min || $max) {
$dir = $min ? '>' : '<';
$id = $min ?? $max;
$timeline = Status::select(
return Status::select(
'id',
'uri',
'caption',
@ -508,13 +516,27 @@ class PublicApiController extends Controller
->with('profile', 'hashtags', 'mentions')
->where('id', $dir, $id)
->whereIn('profile_id', $following)
->whereNotIn('profile_id', $filtered)
->whereIn('visibility',['public', 'unlisted', 'private'])
->orderBy('created_at', 'desc')
->limit($limit)
->get();
->get()
->map(function($s) use ($user) {
$status = StatusService::get($s->id);
if(!$status) {
return false;
}
$status['favourited'] = (bool) LikeService::liked($user->profile_id, $s->id);
$status['bookmarked'] = (bool) BookmarkService::get($user->profile_id, $s->id);
$status['reblogged'] = (bool) ReblogService::get($user->profile_id, $s->id);
return $status;
})
->filter(function($s) use($filtered) {
return $s && in_array($s['account']['id'], $filtered) == false;
})
->values()
->toArray();
} else {
$timeline = Status::select(
return Status::select(
'id',
'uri',
'caption',
@ -540,15 +562,26 @@ class PublicApiController extends Controller
})
->with('profile', 'hashtags', 'mentions')
->whereIn('profile_id', $following)
->whereNotIn('profile_id', $filtered)
->whereIn('visibility',['public', 'unlisted', 'private'])
->orderBy('created_at', 'desc')
->simplePaginate($limit);
->limit($limit)
->get()
->map(function($s) use ($user) {
$status = StatusService::get($s->id);
if(!$status) {
return false;
}
$status['favourited'] = (bool) LikeService::liked($user->profile_id, $s->id);
$status['bookmarked'] = (bool) BookmarkService::get($user->profile_id, $s->id);
$status['reblogged'] = (bool) ReblogService::get($user->profile_id, $s->id);
return $status;
})
->filter(function($s) use($filtered) {
return $s && in_array($s['account']['id'], $filtered) == false;
})
->values()
->toArray();
}
$fractal = new Fractal\Resource\Collection($timeline, new StatusTransformer());
$res = $this->fractal->createData($fractal)->toArray();
return response()->json($res);
}
public function networkTimelineApi(Request $request)
@ -595,6 +628,8 @@ class PublicApiController extends Controller
->map(function($s) use ($user) {
$status = StatusService::get($s->id);
$status['favourited'] = (bool) LikeService::liked($user->profile_id, $s->id);
$status['bookmarked'] = (bool) BookmarkService::get($user->profile_id, $s->id);
$status['reblogged'] = (bool) ReblogService::get($user->profile_id, $s->id);
return $status;
});
$res = $timeline->toArray();
@ -618,6 +653,8 @@ class PublicApiController extends Controller
->map(function($s) use ($user) {
$status = StatusService::get($s->id);
$status['favourited'] = (bool) LikeService::liked($user->profile_id, $s->id);
$status['bookmarked'] = (bool) BookmarkService::get($user->profile_id, $s->id);
$status['reblogged'] = (bool) ReblogService::get($user->profile_id, $s->id);
return $status;
});
$res = $timeline->toArray();

View file

@ -25,6 +25,7 @@ use Illuminate\Support\Str;
use App\Services\HashidService;
use App\Services\StatusService;
use App\Util\Media\License;
use App\Services\ReblogService;
class StatusController extends Controller
{
@ -245,6 +246,7 @@ class StatusController extends Controller
->get();
foreach ($shares as $share) {
UndoSharePipeline::dispatch($share);
ReblogService::del($profile->id, $status->id);
$count--;
}
} else {
@ -255,6 +257,7 @@ class StatusController extends Controller
$share->save();
$count++;
SharePipeline::dispatch($share);
ReblogService::add($profile->id, $status->id);
}
Cache::forget('status:'.$status->id.':sharedby:userid:'.$user->id);

View file

@ -0,0 +1,31 @@
<?php
namespace App\Services;
use App\Bookmark;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Redis;
class BookmarkService
{
const CACHE_KEY = 'pf:services:bookmarks:';
public static function get($profileId, $statusId)
{
if (!Redis::zcard(self::CACHE_KEY . $profileId)) {
return false;
}
return Redis::zscore(self::CACHE_KEY . $profileId, $statusId) != null;
}
public static function add($profileId, $statusId)
{
return Redis::zadd(self::CACHE_KEY . $profileId, $statusId, $statusId);
}
public static function del($profileId, $statusId)
{
return Redis::zrem(self::CACHE_KEY . $profileId, $statusId);
}
}

View file

@ -71,8 +71,8 @@ class CustomEmojiService
$emoji->save();
$name = str_replace(':', '', $json['name']);
Cache::forget('pf:custom_emoji');
Cache::forget('pf:custom_emoji:' . $name);
if($id) {
StatusService::del($id);
}
@ -104,4 +104,28 @@ class CustomEmojiService
return true;
}
public static function all()
{
return Cache::rememberForever('pf:custom_emoji', function() {
$pgsql = config('database.default') === 'pgsql';
return CustomEmoji::when(!$pgsql, function($q, $pgsql) {
return $q->groupBy('shortcode');
})
->get()
->map(function($emojo) {
$url = url('storage/' . $emojo->media_path);
return [
'shortcode' => str_replace(':', '', $emojo->shortcode),
'url' => $url,
'static_path' => $url,
'visible_in_picker' => $emojo->disabled == false
];
})
->when($pgsql, function($collection) {
return $collection->unique('shortcode');
})
->toJson(JSON_UNESCAPED_SLASHES);
});
}
}

View file

@ -0,0 +1,29 @@
<?php
namespace App\Services;
use Illuminate\Support\Facades\Redis;
class ReblogService
{
const CACHE_KEY = 'pf:services:reblogs:';
public static function get($profileId, $statusId)
{
if (!Redis::zcard(self::CACHE_KEY . $profileId)) {
return false;
}
return Redis::zscore(self::CACHE_KEY . $profileId, $statusId) != null;
}
public static function add($profileId, $statusId)
{
return Redis::zadd(self::CACHE_KEY . $profileId, $statusId, $statusId);
}
public static function del($profileId, $statusId)
{
return Redis::zrem(self::CACHE_KEY . $profileId, $statusId);
}
}

View file

@ -165,20 +165,14 @@ class StatusService
public static function isShared($id, $pid = null)
{
return $pid ?
DB::table('statuses')
->where('reblog_of_id', $id)
->where('profile_id', $pid)
->exists() :
ReblogService::get($pid, $id) :
false;
}
public static function isBookmarked($id, $pid = null)
{
return $pid ?
DB::table('bookmarks')
->where('status_id', $id)
->where('profile_id', $pid)
->exists() :
BookmarkService::get($pid, $id) :
false;
}
}

View file

@ -17,13 +17,15 @@ use App\Services\ProfileService;
use Illuminate\Support\Str;
use App\Services\PollService;
use App\Models\CustomEmoji;
use App\Services\BookmarkService;
class StatusTransformer extends Fractal\TransformerAbstract
{
public function transform(Status $status)
{
$pid = request()->user()->profile_id;
$taggedPeople = MediaTagService::get($status->id);
$poll = $status->type === 'poll' ? PollService::get($status->id, request()->user()->profile_id) : null;
$poll = $status->type === 'poll' ? PollService::get($status->id, $pid) : null;
return [
'_v' => 1,
@ -69,6 +71,7 @@ class StatusTransformer extends Fractal\TransformerAbstract
'account' => ProfileService::get($status->profile_id),
'tags' => StatusHashtagService::statusTags($status->id),
'poll' => $poll,
'bookmarked' => BookmarkService::get($pid, $status->id),
];
}
}

Binary file not shown.

BIN
public/js/profile.js vendored

Binary file not shown.

BIN
public/js/rempos.js vendored

Binary file not shown.

BIN
public/js/rempro.js vendored

Binary file not shown.

BIN
public/js/spa.js vendored

Binary file not shown.

BIN
public/js/status.js vendored

Binary file not shown.

BIN
public/js/timeline.js vendored

Binary file not shown.

Binary file not shown.

View file

@ -10,6 +10,7 @@
"shared": "Shared",
"shares": "Shares",
"unshare": "Unshare",
"bookmark": "Bookmark",
"cancel": "Cancel",
"copyLink": "Copy Link",
"delete": "Delete",

View file

@ -13,6 +13,7 @@ return [
'shared' => 'Shared',
'shares' => 'Shares',
'unshare' => 'Unshare',
'bookmark' => 'Bookmark',
'cancel' => 'Cancel',
'copyLink' => 'Copy Link',

View file

@ -1,6 +1,6 @@
<nav class="navbar navbar-expand navbar-light navbar-laravel shadow-none border-bottom sticky-top py-1">
<div class="container">
<a class="navbar-brand d-flex align-items-center" href="{{ route('timeline.personal') }}" title="Logo">
<a class="navbar-brand d-flex align-items-center" href="/" title="Logo">
<img src="/img/pixelfed-icon-color.svg" height="30px" class="px-2" loading="eager" alt="Pixelfed logo">
<span class="font-weight-bold mb-0 d-none d-sm-block" style="font-size:20px;">{{ config_cache('app.name') }}</span>
</a>
@ -18,13 +18,13 @@
<ul class="navbar-nav ml-auto">
<li>
<a class="nav-link font-weight-bold text-dark" href="{{ route('login') }}" title="Login">
<a class="nav-link font-weight-bold text-dark" href="/login" title="Login">
{{ __('Login') }}
</a>
</li>
@if(config_cache('pixelfed.open_registration') && in_array(config_cache('system.user_mode'), ['default', 'admin']))
<li>
<a class="ml-3 nav-link font-weight-bold text-dark" href="{{ route('register') }}" title="Register">
<a class="ml-3 nav-link font-weight-bold text-dark" href="/register" title="Register">
{{ __('Register') }}
</a>
</li>
@ -71,13 +71,13 @@
</span>
My Feed
</a>
<a class="dropdown-item lead" href="{{route('timeline.public')}}">
<a class="dropdown-item lead" href="/i/web/timeline/local">
<span style="width: 50px;margin-right:14px;">
<span class="fal fa-stream text-lighter fa-lg"></span>
</span>
Public Feed
</a>
<a class="dropdown-item lead" href="{{route('timeline.network')}}">
<a class="dropdown-item lead" href="/i/web/timeline/global">
<span style="width: 50px;margin-right:14px;">
<span class="fal fa-globe text-lighter fa-lg"></span>
</span>
@ -90,7 +90,7 @@
</span>
Home
</a>
<a class="dropdown-item lead" href="{{route('timeline.public')}}">
<a class="dropdown-item lead" href="/i/web/timeline/local">
<span style="width: 50px;margin-right:14px;">
<span class="fas fa-stream text-lighter fa-lg"></span>
</span>
@ -98,12 +98,13 @@
</a>
@endif
<div class="dropdown-divider"></div>
<a class="dropdown-item lead" href="{{route('discover')}}">
<a class="dropdown-item lead" href="/i/web/discover">
<span style="width: 50px;margin-right:14px;">
<span class="fal fa-compass text-lighter fa-lg"></span>
</span>
{{__('navmenu.discover')}}
</a>
@if(config_cache('instance.stories.enabled'))
<a class="dropdown-item lead" href="/i/stories/new">
<span style="width: 50px;margin-right:14px;">
@ -119,14 +120,14 @@
</span>
{{__('navmenu.myProfile')}}
</a>
<a class="dropdown-item lead" href="{{route('settings')}}">
<a class="dropdown-item lead" href="/settings/home">
<span style="width: 50px;margin-right:14px;">
<span class="fal fa-cog text-lighter fa-lg"></span>
</span>
{{__('navmenu.settings')}}
</a>
@if(Auth::user()->is_admin == true)
<a class="dropdown-item lead" href="{{ route('admin.home') }}">
<a class="dropdown-item lead" href="/i/admin/dashboard">
<span style="width: 50px;margin-right:14px;">
<span class="fal fa-shield-alt text-lighter fa-lg"></span>
</span>
@ -134,7 +135,7 @@
</a>
@endif
<div class="dropdown-divider"></div>
<a class="dropdown-item lead" href="{{ route('logout') }}"
<a class="dropdown-item lead" href="/logout"
onclick="event.preventDefault();
document.getElementById('logout-form').submit();">
<span style="width: 50px;margin-right:14px;" class="text-lighter">
@ -143,7 +144,7 @@
<span class="text-lighter">{{ __('navmenu.logout') }}</span>
</a>
<form id="logout-form" action="{{ route('logout') }}" method="POST" style="display: none;">
<form id="logout-form" action="/logout" method="POST" style="display: none;">
@csrf
</form>
</div>

View file

@ -1,2 +1,3 @@
*
!missing.png
!.gitignore

Binary file not shown.

After

Width:  |  Height:  |  Size: 81 B