basic support for delete streaming
This commit is contained in:
parent
d4e48ac6fa
commit
23a247a8c2
|
@ -38,7 +38,7 @@ async function insertUpdatesIntoThreads (instanceName, updates) {
|
|||
return
|
||||
}
|
||||
|
||||
let threads = store.getThreadsForTimeline(instanceName)
|
||||
let threads = store.getThreads(instanceName)
|
||||
|
||||
for (let timelineName of Object.keys(threads)) {
|
||||
let thread = threads[timelineName]
|
||||
|
|
|
@ -1,21 +1,28 @@
|
|||
import { getIdsThatRebloggedThisStatus, getIdThatThisStatusReblogged, getNotificationIdsForStatuses } from './statuses'
|
||||
import { getIdsThatRebloggedThisStatus, getNotificationIdsForStatuses } from './statuses'
|
||||
import { store } from '../_store/store'
|
||||
import { scheduleIdleTask } from '../_utils/scheduleIdleTask'
|
||||
import { database } from '../_database/database'
|
||||
import forEach from 'lodash/forEach'
|
||||
|
||||
function deleteStatusIdsFromStore (instanceName, idsToDelete) {
|
||||
let idsToDeleteSet = new Set(idsToDelete)
|
||||
let timelines = store.get('timelines')
|
||||
if (timelines && timelines[instanceName]) {
|
||||
Object.keys(timelines[instanceName]).forEach(timelineName => {
|
||||
let timelineData = timelines[instanceName][timelineName]
|
||||
if (timelineName !== 'notifications') {
|
||||
timelineData.timelineItemIds = timelineData.timelineItemIds.filter(_ => !idsToDeleteSet.has(_))
|
||||
timelineData.itemIdsToAdd = timelineData.itemIdsToAdd.filter(_ => !idsToDeleteSet.has(_))
|
||||
}
|
||||
let idWasNotDeleted = id => !idsToDeleteSet.has(id)
|
||||
|
||||
let timelinesToTimelineItemIds = store.getAllTimelineData(instanceName, 'timelineItemIds')
|
||||
|
||||
forEach(timelinesToTimelineItemIds, (timelineItemIds, timelineName) => {
|
||||
store.setForTimeline(instanceName, timelineName, {
|
||||
timelineItemIds: timelineItemIds.filter(idWasNotDeleted)
|
||||
})
|
||||
})
|
||||
|
||||
let timelinesToItemIdsToAdd = store.getAllTimelineData(instanceName, 'itemIdsToAdd')
|
||||
|
||||
forEach(timelinesToItemIdsToAdd, (itemIdsToAdd, timelineName) => {
|
||||
store.setForTimeline(instanceName, timelineName, {
|
||||
itemIdsToAdd: itemIdsToAdd.filter(idWasNotDeleted)
|
||||
})
|
||||
})
|
||||
store.set({timelines: timelines})
|
||||
}
|
||||
}
|
||||
|
||||
async function deleteStatusesAndNotifications (instanceName, statusIdsToDelete, notificationIdsToDelete) {
|
||||
|
@ -24,9 +31,9 @@ async function deleteStatusesAndNotifications (instanceName, statusIdsToDelete,
|
|||
}
|
||||
|
||||
async function doDeleteStatus (instanceName, statusId) {
|
||||
let reblogId = await getIdThatThisStatusReblogged(instanceName, statusId)
|
||||
let rebloggedIds = await getIdsThatRebloggedThisStatus(reblogId || statusId)
|
||||
let statusIdsToDelete = Array.from(new Set([statusId, reblogId].concat(rebloggedIds).filter(Boolean)))
|
||||
console.log('deleting statusId', statusId)
|
||||
let rebloggedIds = await getIdsThatRebloggedThisStatus(instanceName, statusId)
|
||||
let statusIdsToDelete = Array.from(new Set([statusId].concat(rebloggedIds).filter(Boolean)))
|
||||
let notificationIdsToDelete = new Set(await getNotificationIdsForStatuses(instanceName, statusIdsToDelete))
|
||||
await Promise.all([
|
||||
deleteStatusesAndNotifications(instanceName, statusIdsToDelete, notificationIdsToDelete)
|
||||
|
|
|
@ -7,16 +7,15 @@ import { addStatusOrNotification } from './addStatusOrNotification'
|
|||
function processMessage (instanceName, timelineName, message) {
|
||||
mark('processMessage')
|
||||
let { event, payload } = message
|
||||
let parsedPayload = JSON.parse(payload)
|
||||
switch (event) {
|
||||
case 'delete':
|
||||
deleteStatus(instanceName, parsedPayload)
|
||||
deleteStatus(instanceName, payload)
|
||||
break
|
||||
case 'update':
|
||||
addStatusOrNotification(instanceName, timelineName, parsedPayload)
|
||||
addStatusOrNotification(instanceName, timelineName, JSON.parse(payload))
|
||||
break
|
||||
case 'notification':
|
||||
addStatusOrNotification(instanceName, 'notifications', parsedPayload)
|
||||
addStatusOrNotification(instanceName, 'notifications', JSON.parse(payload))
|
||||
break
|
||||
}
|
||||
stop('processMessage')
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
import { auth, basename } from './utils'
|
||||
import { deleteWithTimeout } from '../_utils/ajax'
|
||||
|
||||
export async function deleteStatus (instanceName, accessToken, statusId) {
|
||||
let url = `${basename(instanceName)}/api/v1/statuses/${statusId}`
|
||||
return deleteWithTimeout(url, auth(accessToken))
|
||||
}
|
|
@ -9,13 +9,14 @@ import {
|
|||
PINNED_STATUSES_STORE,
|
||||
TIMESTAMP,
|
||||
REBLOG_ID,
|
||||
THREADS_STORE
|
||||
THREADS_STORE,
|
||||
STATUS_ID
|
||||
} from './constants'
|
||||
|
||||
const openReqs = {}
|
||||
const databaseCache = {}
|
||||
|
||||
const DB_VERSION = 5
|
||||
const DB_VERSION = 6
|
||||
|
||||
export function getDatabase (instanceName) {
|
||||
if (!instanceName) {
|
||||
|
@ -54,6 +55,9 @@ export function getDatabase (instanceName) {
|
|||
tx.objectStore(NOTIFICATIONS_STORE).createIndex('statusId', 'statusId')
|
||||
db.createObjectStore(THREADS_STORE)
|
||||
}
|
||||
if (e.oldVersion < 6) {
|
||||
tx.objectStore(NOTIFICATIONS_STORE).createIndex(STATUS_ID, STATUS_ID)
|
||||
}
|
||||
}
|
||||
req.onsuccess = () => resolve(req.result)
|
||||
})
|
||||
|
|
|
@ -288,6 +288,24 @@ export async function getReblogsForStatus (instanceName, id) {
|
|||
})
|
||||
}
|
||||
|
||||
//
|
||||
// lookups by statusId
|
||||
//
|
||||
|
||||
export async function getNotificationIdsForStatuses (instanceName, statusIds) {
|
||||
const db = await getDatabase(instanceName)
|
||||
await dbPromise(db, NOTIFICATIONS_STORE, 'readonly', (notificationsStore, callback) => {
|
||||
let res = []
|
||||
callback(res)
|
||||
statusIds.forEach(statusId => {
|
||||
let req = notificationsStore.index(STATUS_ID).getAllKeys(IDBKeyRange.only(statusId))
|
||||
req.onsuccess = e => {
|
||||
res = res.concat(e.target.result)
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
//
|
||||
// deletes
|
||||
//
|
||||
|
|
|
@ -20,23 +20,22 @@ export function timelineMixins (Store) {
|
|||
return root && root[instanceName] && root[instanceName][timelineName]
|
||||
}
|
||||
|
||||
Store.prototype.getAllTimelineData = function (instanceName, key) {
|
||||
let root = this.get(`timelineData_${key}`) || {}
|
||||
return root[instanceName] || {}
|
||||
}
|
||||
|
||||
Store.prototype.setForCurrentTimeline = function (obj) {
|
||||
let instanceName = this.get('currentInstance')
|
||||
let timelineName = this.get('currentTimeline')
|
||||
this.setForTimeline(instanceName, timelineName, obj)
|
||||
}
|
||||
|
||||
Store.prototype.getThreadsForTimeline = function (instanceName) {
|
||||
let root = this.get('timelineData_timelineItemIds') || {}
|
||||
let instanceData = root[instanceName] = root[instanceName] || {}
|
||||
Store.prototype.getThreads = function (instanceName) {
|
||||
let instanceData = this.getAllTimelineData(instanceName, 'timelineItemIds')
|
||||
|
||||
return pickBy(instanceData, (value, key) => {
|
||||
return key.startsWith('status/')
|
||||
})
|
||||
}
|
||||
|
||||
Store.prototype.getThreadsForCurrentTimeline = function () {
|
||||
let instanceName = this.get('currentInstance')
|
||||
return this.getThreadsForTimeline(instanceName)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -52,6 +52,17 @@ async function _get (url, headers, timeout) {
|
|||
return throwErrorIfInvalidResponse(response)
|
||||
}
|
||||
|
||||
async function _delete (url, headers, timeout) {
|
||||
let fetchFunc = timeout ? fetchWithTimeout : fetch
|
||||
let response = await fetchFunc(url, {
|
||||
method: 'DELETE',
|
||||
headers: Object.assign(headers, {
|
||||
'Accept': 'application/json'
|
||||
})
|
||||
})
|
||||
return throwErrorIfInvalidResponse(response)
|
||||
}
|
||||
|
||||
export async function post (url, body, headers = {}) {
|
||||
return _post(url, body, headers, false)
|
||||
}
|
||||
|
@ -68,6 +79,10 @@ export async function get (url, headers = {}) {
|
|||
return _get(url, headers, false)
|
||||
}
|
||||
|
||||
export async function deleteWithTimeout (url, headers = {}) {
|
||||
return _delete(url, headers, true)
|
||||
}
|
||||
|
||||
export function paramsString (paramsObject) {
|
||||
let res = ''
|
||||
Object.keys(paramsObject).forEach((key, i) => {
|
||||
|
|
|
@ -3,16 +3,23 @@ import fetch from 'node-fetch'
|
|||
import FileApi from 'file-api'
|
||||
import { users } from './users'
|
||||
import { postStatus } from '../routes/_api/statuses'
|
||||
import { deleteStatus } from '../routes/_api/delete'
|
||||
|
||||
global.fetch = fetch
|
||||
global.File = FileApi.File
|
||||
global.FormData = FileApi.FormData
|
||||
|
||||
const instanceName = 'localhost:3000'
|
||||
|
||||
export async function favoriteStatusAsAdmin (statusId) {
|
||||
return favoriteStatus('localhost:3000', users.admin.accessToken, statusId)
|
||||
return favoriteStatus(instanceName, users.admin.accessToken, statusId)
|
||||
}
|
||||
|
||||
export async function postAsAdmin (text) {
|
||||
return postStatus('localhost:3000', users.admin.accessToken, text,
|
||||
return postStatus(instanceName, users.admin.accessToken, text,
|
||||
null, null, false, null, 'public')
|
||||
}
|
||||
|
||||
export async function deleteAsAdmin (statusId) {
|
||||
return deleteStatus(instanceName, users.admin.accessToken, statusId)
|
||||
}
|
||||
|
|
|
@ -0,0 +1,15 @@
|
|||
import { foobarRole } from '../roles'
|
||||
import { getNthStatus } from '../utils'
|
||||
import { deleteAsAdmin, postAsAdmin } from '../serverActions'
|
||||
|
||||
fixture`105-deletes.js`
|
||||
.page`http://localhost:4002`
|
||||
|
||||
test('deleted statuses are removed from the timeline', async t => {
|
||||
await t.useRole(foobarRole)
|
||||
.hover(getNthStatus(0))
|
||||
let status = await postAsAdmin("I'm gonna delete this")
|
||||
await t.expect(getNthStatus(0).innerText).contains("I'm gonna delete this")
|
||||
await deleteAsAdmin(status.id)
|
||||
await t.expect(getNthStatus(0).innerText).notContains("I'm gonna delete this")
|
||||
})
|
Loading…
Reference in New Issue