0
Fork 0
mirror of https://github.com/TryGhost/Ghost.git synced 2025-01-20 22:42:53 -05:00

Fixed intermittent error when posting a note in admin-x-activitypub (#21917)

refs
[AP-629](https://linear.app/ghost/issue/AP-629/notes-say-they-error-but-post-correctly)

Fixed intermittent error when posting a note in `admin-x-activitypub`

The error was intermittent due to it only occurring when a specific set
of steps occurred, and the query cache being periodically cleared.

The error itself was due to incorrectly expecting the `outbox:${handle}`
query to be an array when it was in fact an object.

This PR also resolves and issue where the reply count for new notes
would display `NaN` (because the `replyCount` property was not present
on newly created notes)
This commit is contained in:
Michael Barrett 2024-12-18 19:05:31 +00:00 committed by GitHub
parent 79e5991ac2
commit 06cd63a3ab
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 29 additions and 11 deletions

View file

@ -1,6 +1,6 @@
{ {
"name": "@tryghost/admin-x-activitypub", "name": "@tryghost/admin-x-activitypub",
"version": "0.3.38", "version": "0.3.39",
"license": "MIT", "license": "MIT",
"repository": { "repository": {
"type": "git", "type": "git",

View file

@ -258,13 +258,13 @@ export class ActivityPubAPI {
}; };
} }
async reply(id: string, content: string) { async reply(id: string, content: string): Promise<Activity> {
const url = new URL(`.ghost/activitypub/actions/reply/${encodeURIComponent(id)}`, this.apiUrl); const url = new URL(`.ghost/activitypub/actions/reply/${encodeURIComponent(id)}`, this.apiUrl);
const response = await this.fetchJSON(url, 'POST', {content}); const response = await this.fetchJSON(url, 'POST', {content});
return response; return response;
} }
async note(content: string) { async note(content: string): Promise<Activity> {
const url = new URL('.ghost/activitypub/actions/note', this.apiUrl); const url = new URL('.ghost/activitypub/actions/note', this.apiUrl);
const response = await this.fetchJSON(url, 'POST', {content}); const response = await this.fetchJSON(url, 'POST', {content});
return response; return response;

View file

@ -356,13 +356,13 @@ const ArticleModal: React.FC<ArticleModalProps> = ({
updateActivity(activityId, { updateActivity(activityId, {
object: { object: {
...object, ...object,
replyCount: object.replyCount + 1 replyCount: (object.replyCount ?? 0) + 1
} }
} as Partial<Activity>); } as Partial<Activity>);
// Update the replyCount on the current activity loaded in the modal // Update the replyCount on the current activity loaded in the modal
// This is used for when we navigate via the history // This is used for when we navigate via the history
object.replyCount = object.replyCount + 1; object.replyCount = (object.replyCount ?? 0) + 1;
} }
const replyBoxRef = useRef<HTMLDivElement>(null); const replyBoxRef = useRef<HTMLDivElement>(null);

View file

@ -17,23 +17,28 @@ const NewPostModal = NiceModal.create(() => {
const handlePost = async () => { const handlePost = async () => {
const trimmedContent = content.trim(); const trimmedContent = content.trim();
if (!trimmedContent) { if (!trimmedContent) {
return; return;
} }
await noteMutation.mutate({content: trimmedContent}, { noteMutation.mutate({content: trimmedContent}, {
onSuccess() { onSuccess() {
showToast({ showToast({
message: 'Note posted', message: 'Note posted',
type: 'success' type: 'success'
}); });
modal.remove(); modal.remove();
}, },
onError() { onError(error) {
showToast({ showToast({
message: 'An error occurred while posting your note.', message: 'An error occurred while posting your note.',
type: 'error' type: 'error'
}); });
// eslint-disable-next-line no-console
console.error(error);
} }
}); });
}; };

View file

@ -488,7 +488,8 @@ export function useReplyMutationForUser(handle: string) {
async mutationFn({id, content}: {id: string, content: string}) { async mutationFn({id, content}: {id: string, content: string}) {
const siteUrl = await getSiteUrl(); const siteUrl = await getSiteUrl();
const api = createActivityPubAPI(handle, siteUrl); const api = createActivityPubAPI(handle, siteUrl);
return await api.reply(id, content) as Activity;
return api.reply(id, content);
} }
}); });
} }
@ -500,15 +501,27 @@ export function useNoteMutationForUser(handle: string) {
async mutationFn({content}: {content: string}) { async mutationFn({content}: {content: string}) {
const siteUrl = await getSiteUrl(); const siteUrl = await getSiteUrl();
const api = createActivityPubAPI(handle, siteUrl); const api = createActivityPubAPI(handle, siteUrl);
return await api.note(content) as Activity;
return api.note(content);
}, },
onSuccess: (activity: Activity) => { onSuccess: (activity: Activity) => {
queryClient.setQueryData([`outbox:${handle}`], (current?: Activity[]) => { queryClient.setQueryData([`outbox:${handle}`], (current?: {pages: {data: Activity[]}[]}) => {
if (current === undefined) { if (current === undefined) {
return current; return current;
} }
return [activity, ...current]; return {
...current,
pages: current.pages.map((page: {data: Activity[]}, index: number) => {
if (index === 0) {
return {
...page,
data: [activity, ...page.data]
};
}
return page;
})
};
}); });
queryClient.setQueriesData([`activities:${handle}`, GET_ACTIVITIES_QUERY_KEY_FEED], (current?: {pages: {data: Activity[]}[]}) => { queryClient.setQueriesData([`activities:${handle}`, GET_ACTIVITIES_QUERY_KEY_FEED], (current?: {pages: {data: Activity[]}[]}) => {