From adc82ecab3d76b22419ad200b1b06a653f727944 Mon Sep 17 00:00:00 2001 From: Daniel Supernault Date: Sat, 4 Mar 2023 23:47:29 -0700 Subject: [PATCH] Update ApiV1Controller, fix link header pagination in /api/v1/statuses/{id}/favourited_by --- app/Http/Controllers/Api/ApiV1Controller.php | 44 +++++++---- .../components/partials/post/LikeModal.vue | 24 +++--- .../partials/profile/ProfileFollowers.vue | 2 + .../partials/profile/ProfileFollowing.vue | 2 + .../assets/js/components/PostComponent.vue | 76 +++++++++++++++---- 5 files changed, 109 insertions(+), 39 deletions(-) diff --git a/app/Http/Controllers/Api/ApiV1Controller.php b/app/Http/Controllers/Api/ApiV1Controller.php index ce76bf7f..8957f6e2 100644 --- a/app/Http/Controllers/Api/ApiV1Controller.php +++ b/app/Http/Controllers/Api/ApiV1Controller.php @@ -2658,13 +2658,17 @@ class ApiV1Controller extends Controller abort_if(!$request->user(), 403); $this->validate($request, [ - 'limit' => 'nullable|integer|min:1|max:100' + 'limit' => 'nullable|integer|min:1|max:80' ]); - $limit = $request->input('limit') ?? 10; + $limit = $request->input('limit', 10); $user = $request->user(); + $pid = $user->profile_id; $status = Status::findOrFail($id); - $author = intval($status->profile_id) === intval($user->profile_id) || $user->is_admin; + $account = AccountService::get($status->profile_id, true); + abort_if(!$account, 404); + $author = intval($status->profile_id) === intval($pid) || $user->is_admin; + $napi = $request->has(self::PF_API_ENTITY_KEY); abort_if( !$status->type || @@ -2674,7 +2678,7 @@ class ApiV1Controller extends Controller if(!$author) { if($status->scope == 'private') { - abort_if(!FollowerService::follows($user->profile_id, $status->profile_id), 403); + abort_if(!FollowerService::follows($pid, $status->profile_id), 403); } else { abort_if(!in_array($status->scope, ['public','unlisted']), 403); } @@ -2696,29 +2700,39 @@ class ApiV1Controller extends Controller $headers = []; if($author && $res->hasPages()) { $links = ''; - if($res->previousPageUrl()) { - $links = '<' . $res->previousPageUrl() .'>; rel="prev"'; - } - if($res->nextPageUrl()) { - if(!empty($links)) { - $links .= ', '; + if($res->onFirstPage()) { + if($res->nextPageUrl()) { + $links = '<' . $res->nextPageUrl() .'>; rel="prev"'; + } + } else { + if($res->previousPageUrl()) { + $links = '<' . $res->previousPageUrl() .'>; rel="next"'; + } + + if($res->nextPageUrl()) { + if(!empty($links)) { + $links .= ', '; + } + $links .= '<' . $res->nextPageUrl() .'>; rel="prev"'; } - $links .= '<' . $res->nextPageUrl() .'>; rel="next"'; } $headers = ['Link' => $links]; } - $res = $res->map(function($like) use($user) { - $account = AccountService::getMastodon($like->profile_id, true); + $res = $res->map(function($like) use($pid, $napi) { + $account = $napi ? AccountService::get($like->profile_id, true) : AccountService::getMastodon($like->profile_id, true); if(!$account) { return false; } - $account['follows'] = $like->profile_id == $user->profile_id ? null : FollowerService::follows($user->profile_id, $like->profile_id); + + if($napi) { + $account['follows'] = $like->profile_id == $pid ? null : FollowerService::follows($pid, $like->profile_id); + } return $account; }) - ->filter(function($account) use($user) { + ->filter(function($account) { return $account && isset($account['id']); }) ->values(); diff --git a/resources/assets/components/partials/post/LikeModal.vue b/resources/assets/components/partials/post/LikeModal.vue index a5164752..3c069d03 100644 --- a/resources/assets/components/partials/post/LikeModal.vue +++ b/resources/assets/components/partials/post/LikeModal.vue @@ -104,7 +104,7 @@ isFetchingMore: false, likes: [], ids: [], - page: undefined, + cursor: undefined, isUpdatingFollowState: false, followStateIndex: undefined, user: window._sharedData.user @@ -119,13 +119,14 @@ this.isFetchingMore = false; this.likes = []; this.ids = []; - this.page = undefined; + this.cursor = undefined; }, fetchLikes() { axios.get('/api/v1/statuses/'+this.status.id+'/favourited_by', { params: { - limit: 40 + limit: 40, + '_pe': 1 } }) .then(res => { @@ -133,19 +134,21 @@ this.likes = res.data; if(res.headers && res.headers.link) { const links = parseLinkHeader(res.headers.link); - if(links.next) { - this.page = links.next.cursor; + if(links.prev) { + this.cursor = links.prev.cursor; this.canLoadMore = true; } else { this.canLoadMore = false; } + } else { + this.canLoadMore = false; } this.isLoading = false; }); }, open() { - if(this.page) { + if(this.cursor) { this.clear(); } this.isOpen = true; @@ -163,7 +166,8 @@ axios.get('/api/v1/statuses/'+this.status.id+'/favourited_by', { params: { limit: 10, - cursor: this.page + cursor: this.cursor, + '_pe': 1 } }).then(res => { if(!res.data || !res.data.length) { @@ -179,11 +183,13 @@ }) if(res.headers && res.headers.link) { const links = parseLinkHeader(res.headers.link); - if(links.next) { - this.page = links.next.cursor; + if(links.prev) { + this.cursor = links.prev.cursor; } else { this.canLoadMore = false; } + } else { + this.canLoadMore = false; } this.isFetchingMore = false; }) diff --git a/resources/assets/components/partials/profile/ProfileFollowers.vue b/resources/assets/components/partials/profile/ProfileFollowers.vue index f5d83369..572ba97e 100644 --- a/resources/assets/components/partials/profile/ProfileFollowers.vue +++ b/resources/assets/components/partials/profile/ProfileFollowers.vue @@ -151,6 +151,8 @@ } else { this.canLoadMore = false; } + } else { + this.canLoadMore = false; } this.feed.push(...res.data); this.isLoaded = true; diff --git a/resources/assets/components/partials/profile/ProfileFollowing.vue b/resources/assets/components/partials/profile/ProfileFollowing.vue index f9704921..ae226048 100644 --- a/resources/assets/components/partials/profile/ProfileFollowing.vue +++ b/resources/assets/components/partials/profile/ProfileFollowing.vue @@ -149,6 +149,8 @@ } else { this.canLoadMore = false; } + } else { + this.canLoadMore = false; } this.feed.push(...res.data); this.isLoaded = true; diff --git a/resources/assets/js/components/PostComponent.vue b/resources/assets/js/components/PostComponent.vue index 7d0c5a92..073eaa99 100644 --- a/resources/assets/js/components/PostComponent.vue +++ b/resources/assets/js/components/PostComponent.vue @@ -371,7 +371,7 @@ centered title="Likes" body-class="list-group-flush py-3 px-0"> -
+ - +
+
+ +
`; self.content = self.content.replace(`:${emoji.shortcode}:`, img); }); - self.likesPage = 2; self.sharesPage = 2; self.showCaption = !response.data.status.sensitive; if(self.status.comments_disabled == false) { @@ -886,15 +891,35 @@ export default { window.location.href = '/login?next=' + encodeURIComponent('/p/' + this.status.shortcode); return; } - if(this.likes.length) { + if(this.likes && this.likes.length) { this.$refs.likesModal.show(); return; } - axios.get('/api/v2/likes/profile/'+this.statusUsername+'/status/'+this.statusId) + axios.get('/api/v1/statuses/'+ this.statusId + '/favourited_by', { + params: { + limit: 40, + '_pe': 1 + } + }) .then(res => { - this.likes = res.data.data; + this.likes = res.data; + + if(res.headers && res.headers.link) { + const links = parseLinkHeader(res.headers.link); + if(links.prev) { + this.likesCursor = links.prev.cursor; + this.likesCanLoadMore = true; + } else { + this.likesCanLoadMore = false; + } + } else { + this.likesCanLoadMore = false; + } this.$refs.likesModal.show(); - }); + }) + .then(() => { + setTimeout(() => { this.likedLoaded = true }, 1000); + }) }, sharesModal() { @@ -914,15 +939,36 @@ export default { }, infiniteLikesHandler($state) { - let api = '/api/v2/likes/profile/'+this.statusUsername+'/status/'+this.statusId; - axios.get(api, { + if(!this.likesCanLoadMore) { + $state.complete(); + return; + } + + axios.get('/api/v1/statuses/'+ this.statusId + '/favourited_by', { params: { - page: this.likesPage, + cursor: this.likesCursor, + limit: 20, + '_pe': 1 }, - }).then(({ data }) => { - if (data.data.length > 0) { - this.likes.push(...data.data); - this.likesPage++; + }).then(res => { + if (res && res.data.length) { + this.likes.push(...res.data); + } + + if(res.headers && res.headers.link) { + const links = parseLinkHeader(res.headers.link); + if(links.prev) { + this.likesCursor = links.prev.cursor; + this.likesCanLoadMore = true; + } else { + this.likesCanLoadMore = false; + } + } else { + this.likesCanLoadMore = false; + } + return this.likesCanLoadMore; + }).then(res => { + if(res) { $state.loaded(); } else { $state.complete();