Update ApiV1Controller, fix link header pagination in /api/v1/statuses/{id}/favourited_by

This commit is contained in:
Daniel Supernault 2023-03-04 23:47:29 -07:00
parent 1f4f8252f2
commit adc82ecab3
No known key found for this signature in database
GPG key ID: 0DEF1C662C9033F7
5 changed files with 109 additions and 39 deletions

View file

@ -2658,13 +2658,17 @@ class ApiV1Controller extends Controller
abort_if(!$request->user(), 403); abort_if(!$request->user(), 403);
$this->validate($request, [ $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(); $user = $request->user();
$pid = $user->profile_id;
$status = Status::findOrFail($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( abort_if(
!$status->type || !$status->type ||
@ -2674,7 +2678,7 @@ class ApiV1Controller extends Controller
if(!$author) { if(!$author) {
if($status->scope == 'private') { 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 { } else {
abort_if(!in_array($status->scope, ['public','unlisted']), 403); abort_if(!in_array($status->scope, ['public','unlisted']), 403);
} }
@ -2696,29 +2700,39 @@ class ApiV1Controller extends Controller
$headers = []; $headers = [];
if($author && $res->hasPages()) { if($author && $res->hasPages()) {
$links = ''; $links = '';
if($res->previousPageUrl()) {
$links = '<' . $res->previousPageUrl() .'>; rel="prev"';
}
if($res->nextPageUrl()) { if($res->onFirstPage()) {
if(!empty($links)) { if($res->nextPageUrl()) {
$links .= ', '; $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]; $headers = ['Link' => $links];
} }
$res = $res->map(function($like) use($user) { $res = $res->map(function($like) use($pid, $napi) {
$account = AccountService::getMastodon($like->profile_id, true); $account = $napi ? AccountService::get($like->profile_id, true) : AccountService::getMastodon($like->profile_id, true);
if(!$account) { if(!$account) {
return false; 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; return $account;
}) })
->filter(function($account) use($user) { ->filter(function($account) {
return $account && isset($account['id']); return $account && isset($account['id']);
}) })
->values(); ->values();

View file

@ -104,7 +104,7 @@
isFetchingMore: false, isFetchingMore: false,
likes: [], likes: [],
ids: [], ids: [],
page: undefined, cursor: undefined,
isUpdatingFollowState: false, isUpdatingFollowState: false,
followStateIndex: undefined, followStateIndex: undefined,
user: window._sharedData.user user: window._sharedData.user
@ -119,13 +119,14 @@
this.isFetchingMore = false; this.isFetchingMore = false;
this.likes = []; this.likes = [];
this.ids = []; this.ids = [];
this.page = undefined; this.cursor = undefined;
}, },
fetchLikes() { fetchLikes() {
axios.get('/api/v1/statuses/'+this.status.id+'/favourited_by', { axios.get('/api/v1/statuses/'+this.status.id+'/favourited_by', {
params: { params: {
limit: 40 limit: 40,
'_pe': 1
} }
}) })
.then(res => { .then(res => {
@ -133,19 +134,21 @@
this.likes = res.data; this.likes = res.data;
if(res.headers && res.headers.link) { if(res.headers && res.headers.link) {
const links = parseLinkHeader(res.headers.link); const links = parseLinkHeader(res.headers.link);
if(links.next) { if(links.prev) {
this.page = links.next.cursor; this.cursor = links.prev.cursor;
this.canLoadMore = true; this.canLoadMore = true;
} else { } else {
this.canLoadMore = false; this.canLoadMore = false;
} }
} else {
this.canLoadMore = false;
} }
this.isLoading = false; this.isLoading = false;
}); });
}, },
open() { open() {
if(this.page) { if(this.cursor) {
this.clear(); this.clear();
} }
this.isOpen = true; this.isOpen = true;
@ -163,7 +166,8 @@
axios.get('/api/v1/statuses/'+this.status.id+'/favourited_by', { axios.get('/api/v1/statuses/'+this.status.id+'/favourited_by', {
params: { params: {
limit: 10, limit: 10,
cursor: this.page cursor: this.cursor,
'_pe': 1
} }
}).then(res => { }).then(res => {
if(!res.data || !res.data.length) { if(!res.data || !res.data.length) {
@ -179,11 +183,13 @@
}) })
if(res.headers && res.headers.link) { if(res.headers && res.headers.link) {
const links = parseLinkHeader(res.headers.link); const links = parseLinkHeader(res.headers.link);
if(links.next) { if(links.prev) {
this.page = links.next.cursor; this.cursor = links.prev.cursor;
} else { } else {
this.canLoadMore = false; this.canLoadMore = false;
} }
} else {
this.canLoadMore = false;
} }
this.isFetchingMore = false; this.isFetchingMore = false;
}) })

View file

@ -151,6 +151,8 @@
} else { } else {
this.canLoadMore = false; this.canLoadMore = false;
} }
} else {
this.canLoadMore = false;
} }
this.feed.push(...res.data); this.feed.push(...res.data);
this.isLoaded = true; this.isLoaded = true;

View file

@ -149,6 +149,8 @@
} else { } else {
this.canLoadMore = false; this.canLoadMore = false;
} }
} else {
this.canLoadMore = false;
} }
this.feed.push(...res.data); this.feed.push(...res.data);
this.isLoaded = true; this.isLoaded = true;

View file

@ -371,7 +371,7 @@
centered centered
title="Likes" title="Likes"
body-class="list-group-flush py-3 px-0"> body-class="list-group-flush py-3 px-0">
<div class="list-group"> <div v-if="likedLoaded" class="list-group">
<div class="list-group-item border-0 py-1" v-for="(user, index) in likes" :key="'modal_likes_'+index"> <div class="list-group-item border-0 py-1" v-for="(user, index) in likes" :key="'modal_likes_'+index">
<div class="media"> <div class="media">
<a :href="user.url"> <a :href="user.url">
@ -392,11 +392,14 @@
</div> </div>
</div> </div>
</div> </div>
<infinite-loading @infinite="infiniteLikesHandler" spinner="spiral"> <infinite-loading v-if="likesCanLoadMore" @infinite="infiniteLikesHandler" spinner="spiral">
<div slot="no-more"></div> <div slot="no-more"></div>
<div slot="no-results"></div> <div slot="no-results"></div>
</infinite-loading> </infinite-loading>
</div> </div>
<div v-else class="d-flex justify-content-center align-items-center h-100">
<b-spinner />
</div>
</b-modal> </b-modal>
<b-modal ref="sharesModal" <b-modal ref="sharesModal"
id="s-modal" id="s-modal"
@ -667,6 +670,7 @@ import VueTribute from 'vue-tribute';
import PollCard from './partials/PollCard.vue'; import PollCard from './partials/PollCard.vue';
import CommentFeed from './partials/CommentFeed.vue'; import CommentFeed from './partials/CommentFeed.vue';
import StatusCard from './partials/StatusCard.vue'; import StatusCard from './partials/StatusCard.vue';
import { parseLinkHeader } from '@web3-storage/parse-link-header';
pixelfed.postComponent = {}; pixelfed.postComponent = {};
@ -702,7 +706,9 @@ export default {
shared: false shared: false
}, },
likes: [], likes: [],
likesPage: 1, likesCursor: null,
likesCanLoadMore: true,
likedLoaded: false,
shares: [], shares: [],
sharesPage: 1, sharesPage: 1,
lightboxMedia: false, lightboxMedia: false,
@ -847,7 +853,6 @@ export default {
let img = `<img draggable="false" class="emojione custom-emoji" alt="${emoji.shortcode}" title="${emoji.shortcode}" src="${emoji.url}" data-original="${emoji.url}" data-static="${emoji.static_url}" width="18" height="18" onerror="this.onerror=null;this.src='/storage/emoji/missing.png';" />`; let img = `<img draggable="false" class="emojione custom-emoji" alt="${emoji.shortcode}" title="${emoji.shortcode}" src="${emoji.url}" data-original="${emoji.url}" data-static="${emoji.static_url}" width="18" height="18" onerror="this.onerror=null;this.src='/storage/emoji/missing.png';" />`;
self.content = self.content.replace(`:${emoji.shortcode}:`, img); self.content = self.content.replace(`:${emoji.shortcode}:`, img);
}); });
self.likesPage = 2;
self.sharesPage = 2; self.sharesPage = 2;
self.showCaption = !response.data.status.sensitive; self.showCaption = !response.data.status.sensitive;
if(self.status.comments_disabled == false) { if(self.status.comments_disabled == false) {
@ -886,15 +891,35 @@ export default {
window.location.href = '/login?next=' + encodeURIComponent('/p/' + this.status.shortcode); window.location.href = '/login?next=' + encodeURIComponent('/p/' + this.status.shortcode);
return; return;
} }
if(this.likes.length) { if(this.likes && this.likes.length) {
this.$refs.likesModal.show(); this.$refs.likesModal.show();
return; 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 => { .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(); this.$refs.likesModal.show();
}); })
.then(() => {
setTimeout(() => { this.likedLoaded = true }, 1000);
})
}, },
sharesModal() { sharesModal() {
@ -914,15 +939,36 @@ export default {
}, },
infiniteLikesHandler($state) { infiniteLikesHandler($state) {
let api = '/api/v2/likes/profile/'+this.statusUsername+'/status/'+this.statusId; if(!this.likesCanLoadMore) {
axios.get(api, { $state.complete();
return;
}
axios.get('/api/v1/statuses/'+ this.statusId + '/favourited_by', {
params: { params: {
page: this.likesPage, cursor: this.likesCursor,
limit: 20,
'_pe': 1
}, },
}).then(({ data }) => { }).then(res => {
if (data.data.length > 0) { if (res && res.data.length) {
this.likes.push(...data.data); this.likes.push(...res.data);
this.likesPage++; }
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(); $state.loaded();
} else { } else {
$state.complete(); $state.complete();