diff --git a/CHANGELOG.md b/CHANGELOG.md index 0414da1da..d36c6dde0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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) diff --git a/app/Http/Controllers/AdminController.php b/app/Http/Controllers/AdminController.php index dae875e20..b54f26844 100644 --- a/app/Http/Controllers/AdminController.php +++ b/app/Http/Controllers/AdminController.php @@ -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')); } diff --git a/app/Http/Controllers/Api/ApiV1Controller.php b/app/Http/Controllers/Api/ApiV1Controller.php index 987878970..7ea3f5133 100644 --- a/app/Http/Controllers/Api/ApiV1Controller.php +++ b/app/Http/Controllers/Api/ApiV1Controller.php @@ -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') diff --git a/app/Http/Controllers/Api/BaseApiController.php b/app/Http/Controllers/Api/BaseApiController.php index ee58ada6b..c029b0feb 100644 --- a/app/Http/Controllers/Api/BaseApiController.php +++ b/app/Http/Controllers/Api/BaseApiController.php @@ -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) diff --git a/app/Http/Controllers/BookmarkController.php b/app/Http/Controllers/BookmarkController.php index 0357a36dd..a340460b9 100644 --- a/app/Http/Controllers/BookmarkController.php +++ b/app/Http/Controllers/BookmarkController.php @@ -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()) { diff --git a/app/Http/Controllers/InternalApiController.php b/app/Http/Controllers/InternalApiController.php index 059e5124f..4391c3c3c 100644 --- a/app/Http/Controllers/InternalApiController.php +++ b/app/Http/Controllers/InternalApiController.php @@ -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) diff --git a/app/Http/Controllers/PublicApiController.php b/app/Http/Controllers/PublicApiController.php index eb1721483..dc2051a4f 100644 --- a/app/Http/Controllers/PublicApiController.php +++ b/app/Http/Controllers/PublicApiController.php @@ -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(); diff --git a/app/Http/Controllers/StatusController.php b/app/Http/Controllers/StatusController.php index 71cd5b7d6..91c187a38 100644 --- a/app/Http/Controllers/StatusController.php +++ b/app/Http/Controllers/StatusController.php @@ -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); diff --git a/app/Services/BookmarkService.php b/app/Services/BookmarkService.php new file mode 100644 index 000000000..5af9625aa --- /dev/null +++ b/app/Services/BookmarkService.php @@ -0,0 +1,31 @@ +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); + }); + } } diff --git a/app/Services/ReblogService.php b/app/Services/ReblogService.php new file mode 100644 index 000000000..f6655c8f6 --- /dev/null +++ b/app/Services/ReblogService.php @@ -0,0 +1,29 @@ +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; } } diff --git a/app/Transformer/Api/StatusTransformer.php b/app/Transformer/Api/StatusTransformer.php index 13d62b473..d9cbeaa65 100644 --- a/app/Transformer/Api/StatusTransformer.php +++ b/app/Transformer/Api/StatusTransformer.php @@ -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), ]; } } diff --git a/public/_lang/en.json b/public/_lang/en.json index bad011304..fc91f9371 100644 Binary files a/public/_lang/en.json and b/public/_lang/en.json differ diff --git a/public/js/profile.js b/public/js/profile.js index 06f34539d..c2d4681e2 100644 Binary files a/public/js/profile.js and b/public/js/profile.js differ diff --git a/public/js/rempos.js b/public/js/rempos.js index 025996e91..a1c1fb468 100644 Binary files a/public/js/rempos.js and b/public/js/rempos.js differ diff --git a/public/js/rempro.js b/public/js/rempro.js index 0343573ae..57a9db681 100644 Binary files a/public/js/rempro.js and b/public/js/rempro.js differ diff --git a/public/js/spa.js b/public/js/spa.js index e728c8e01..b855833a1 100644 Binary files a/public/js/spa.js and b/public/js/spa.js differ diff --git a/public/js/status.js b/public/js/status.js index e1c8ae963..8250961f3 100644 Binary files a/public/js/status.js and b/public/js/status.js differ diff --git a/public/js/timeline.js b/public/js/timeline.js index 159aeab36..020e19244 100644 Binary files a/public/js/timeline.js and b/public/js/timeline.js differ diff --git a/public/mix-manifest.json b/public/mix-manifest.json index 5aae0b5ef..f78840877 100644 Binary files a/public/mix-manifest.json and b/public/mix-manifest.json differ diff --git a/resources/assets/js/i18n/en.json b/resources/assets/js/i18n/en.json index bad011304..fc91f9371 100644 --- a/resources/assets/js/i18n/en.json +++ b/resources/assets/js/i18n/en.json @@ -10,6 +10,7 @@ "shared": "Shared", "shares": "Shares", "unshare": "Unshare", + "bookmark": "Bookmark", "cancel": "Cancel", "copyLink": "Copy Link", "delete": "Delete", diff --git a/resources/lang/en/web.php b/resources/lang/en/web.php index 7cb5fa0e9..2c6a7b418 100644 --- a/resources/lang/en/web.php +++ b/resources/lang/en/web.php @@ -13,6 +13,7 @@ return [ 'shared' => 'Shared', 'shares' => 'Shares', 'unshare' => 'Unshare', + 'bookmark' => 'Bookmark', 'cancel' => 'Cancel', 'copyLink' => 'Copy Link', diff --git a/resources/views/layouts/partial/nav.blade.php b/resources/views/layouts/partial/nav.blade.php index 0cd1405e5..7cff72c7e 100644 --- a/resources/views/layouts/partial/nav.blade.php +++ b/resources/views/layouts/partial/nav.blade.php @@ -1,6 +1,6 @@