Skip to content

Commit

Permalink
fix: handle status edit events (nolanlawson#2325)
Browse files Browse the repository at this point in the history
  • Loading branch information
nolanlawson authored and alice-werefox committed Apr 3, 2023
1 parent 695432e commit 2537bd0
Showing 6 changed files with 103 additions and 17 deletions.
8 changes: 6 additions & 2 deletions src/routes/_actions/stream/processMessage.js
Original file line number Diff line number Diff line change
@@ -2,8 +2,9 @@ import { mark, stop } from '../../_utils/marks.js'
import { deleteStatus } from '../deleteStatuses.js'
import { addStatusOrNotification } from '../addStatusOrNotification.js'
import { emit } from '../../_utils/eventBus.js'
import { updateStatus } from '../updateStatus.js'

const KNOWN_EVENTS = ['update', 'delete', 'notification', 'conversation', 'filters_changed']
const KNOWN_EVENTS = ['update', 'delete', 'notification', 'conversation', 'filters_changed', 'status.update']

export function processMessage (instanceName, timelineName, message) {
let { event, payload } = (message || {})
@@ -12,7 +13,7 @@ export function processMessage (instanceName, timelineName, message) {
return
}
mark('processMessage')
if (['update', 'notification', 'conversation'].includes(event)) {
if (['update', 'notification', 'conversation', 'status.update'].includes(event)) {
payload = JSON.parse(payload) // only these payloads are JSON-encoded for some reason
}

@@ -43,6 +44,9 @@ export function processMessage (instanceName, timelineName, message) {
case 'filters_changed':
emit('wordFiltersChanged', instanceName)
break
case 'status.update':
updateStatus(instanceName, payload)
break
}
stop('processMessage')
}
13 changes: 13 additions & 0 deletions src/routes/_actions/updateStatus.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { database } from '../_database/database.js'
import { scheduleIdleTask } from '../_utils/scheduleIdleTask.js'

async function doUpdateStatus (instanceName, newStatus) {
console.log('updating status', newStatus)
await database.updateStatus(instanceName, newStatus)
}

export function updateStatus (instanceName, newStatus) {
scheduleIdleTask(() => {
/* no await */ doUpdateStatus(instanceName, newStatus)
})
}
34 changes: 26 additions & 8 deletions src/routes/_api/statuses.js
Original file line number Diff line number Diff line change
@@ -1,18 +1,20 @@
import { auth, basename } from './utils.js'
import { DEFAULT_TIMEOUT, get, post, WRITE_TIMEOUT } from '../_utils/ajax.js'
import { DEFAULT_TIMEOUT, get, post, put, WRITE_TIMEOUT } from '../_utils/ajax.js'

export async function postStatus (instanceName, accessToken, text, inReplyToId, mediaIds,
// post is create, put is edit
async function postOrPutStatus (url, accessToken, method, text, inReplyToId, mediaIds,
sensitive, spoilerText, visibility, poll) {
const url = `${basename(instanceName)}/api/v1/statuses`

const body = {
status: text,
in_reply_to_id: inReplyToId,
media_ids: mediaIds,
sensitive,
spoiler_text: spoilerText,
visibility,
poll
poll,
...(method === 'post' && {
// you can't change these properties when editing
in_reply_to_id: inReplyToId,
visibility
})
}

for (const key of Object.keys(body)) {
@@ -23,7 +25,23 @@ export async function postStatus (instanceName, accessToken, text, inReplyToId,
}
}

return post(url, body, auth(accessToken), { timeout: WRITE_TIMEOUT })
const func = method === 'post' ? post : put

return func(url, body, auth(accessToken), { timeout: WRITE_TIMEOUT })
}

export async function postStatus (instanceName, accessToken, text, inReplyToId, mediaIds,
sensitive, spoilerText, visibility, poll) {
const url = `${basename(instanceName)}/api/v1/statuses`
return postOrPutStatus(url, accessToken, 'post', text, inReplyToId, mediaIds,
sensitive, spoilerText, visibility, poll)
}

export async function putStatus (instanceName, accessToken, id, text, inReplyToId, mediaIds,
sensitive, spoilerText, visibility, poll) {
const url = `${basename(instanceName)}/api/v1/statuses/${id}`
return postOrPutStatus(url, accessToken, 'put', text, inReplyToId, mediaIds,
sensitive, spoilerText, visibility, poll)
}

export async function getStatusContext (instanceName, accessToken, statusId) {
30 changes: 24 additions & 6 deletions src/routes/_database/timelines/updateStatus.js
Original file line number Diff line number Diff line change
@@ -3,12 +3,13 @@ import { getInCache, hasInCache, statusesCache } from '../cache.js'
import { STATUSES_STORE } from '../constants.js'
import { cacheStatus } from './cacheStatus.js'
import { putStatus } from './insertion.js'
import { cloneForStorage } from '../helpers.js'

//
// update statuses
//

async function updateStatus (instanceName, statusId, updateFunc) {
async function doUpdateStatus (instanceName, statusId, updateFunc) {
const db = await getDatabase(instanceName)
if (hasInCache(statusesCache, instanceName, statusId)) {
const status = getInCache(statusesCache, instanceName, statusId)
@@ -25,35 +26,52 @@ async function updateStatus (instanceName, statusId, updateFunc) {
}

export async function setStatusFavorited (instanceName, statusId, favorited) {
return updateStatus(instanceName, statusId, status => {
return doUpdateStatus(instanceName, statusId, status => {
const delta = (favorited ? 1 : 0) - (status.favourited ? 1 : 0)
status.favourited = favorited
status.favourites_count = (status.favourites_count || 0) + delta
})
}

export async function setStatusReblogged (instanceName, statusId, reblogged) {
return updateStatus(instanceName, statusId, status => {
return doUpdateStatus(instanceName, statusId, status => {
const delta = (reblogged ? 1 : 0) - (status.reblogged ? 1 : 0)
status.reblogged = reblogged
status.reblogs_count = (status.reblogs_count || 0) + delta
})
}

export async function setStatusPinned (instanceName, statusId, pinned) {
return updateStatus(instanceName, statusId, status => {
return doUpdateStatus(instanceName, statusId, status => {
status.pinned = pinned
})
}

export async function setStatusMuted (instanceName, statusId, muted) {
return updateStatus(instanceName, statusId, status => {
return doUpdateStatus(instanceName, statusId, status => {
status.muted = muted
})
}

export async function setStatusBookmarked (instanceName, statusId, bookmarked) {
return updateStatus(instanceName, statusId, status => {
return doUpdateStatus(instanceName, statusId, status => {
status.bookmarked = bookmarked
})
}

// For the full list, see https://docs.joinmastodon.org/methods/statuses/#edit
const PROPS_THAT_CAN_BE_EDITED = ['content', 'spoiler_text', 'sensitive', 'language', 'media_ids', 'poll']

export async function updateStatus (instanceName, newStatus) {
const clonedNewStatus = cloneForStorage(newStatus)
return doUpdateStatus(instanceName, newStatus.id, status => {
// We can't use a simple Object.assign() to merge because a prop might have been deleted
for (const prop of PROPS_THAT_CAN_BE_EDITED) {
if (!(prop in clonedNewStatus)) {
delete status[prop]
} else {
status[prop] = clonedNewStatus[prop]
}
}
})
}
7 changes: 6 additions & 1 deletion tests/serverActions.js
Original file line number Diff line number Diff line change
@@ -2,7 +2,7 @@ import { favoriteStatus } from '../src/routes/_api/favorite.js'
import fetch from 'node-fetch'
import FileApi from 'file-api'
import { users } from './users.js'
import { postStatus } from '../src/routes/_api/statuses.js'
import { postStatus, putStatus } from '../src/routes/_api/statuses.js'
import { deleteStatus } from '../src/routes/_api/delete.js'
import { authorizeFollowRequest, getFollowRequests } from '../src/routes/_api/followRequests.js'
import { followAccount, unfollowAccount } from '../src/routes/_api/follow.js'
@@ -33,6 +33,11 @@ export async function postAs (username, text) {
null, null, false, null, 'public')
}

export async function putAs (username, text, statusId) {
return putStatus(instanceName, users[username].accessToken, statusId, text,
null, null, false, null, 'public')
}

export async function postWithSpoilerAndPrivacyAs (username, text, spoiler, privacy) {
return postStatus(instanceName, users[username].accessToken, text,
null, null, true, spoiler, privacy)
28 changes: 28 additions & 0 deletions tests/spec/140-editing.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import {
getNthStatus, getUrl, goBack,
sleep
} from '../utils'
import { loginAsFoobar } from '../roles'
import { postAs, putAs } from '../serverActions'

fixture`140-editing.js`
.page`http://localhost:4002`

test('Edited toots are updated in the UI', async t => {
const { id: statusId } = await postAs('admin', 'yolo')
await sleep(500)

await loginAsFoobar(t)
await t.expect(getNthStatus(1).innerText).contains('yolo', { timeout: 20000 })

await putAs('admin', 'wait I mean YOLO', statusId)
await sleep(500)

await t.click(getNthStatus(1))
.expect(getUrl()).contains('/statuses')
.expect(getNthStatus(1).innerText).contains('wait I mean YOLO', { timeout: 20000 })
await goBack()
await t
.expect(getUrl()).eql('http://localhost:4002/')
.expect(getNthStatus(1).innerText).contains('wait I mean YOLO', { timeout: 20000 })
})

0 comments on commit 2537bd0

Please sign in to comment.