diff --git a/keyserver/src/fetchers/update-fetchers.js b/keyserver/src/fetchers/update-fetchers.js index 85d5c678a..9d316b5a1 100644 --- a/keyserver/src/fetchers/update-fetchers.js +++ b/keyserver/src/fetchers/update-fetchers.js @@ -1,172 +1,97 @@ // @flow -import invariant from 'invariant'; - +import { updateSpecs } from 'lib/shared/updates/update-specs.js'; import type { CalendarQuery } from 'lib/types/entry-types.js'; import { updateTypes, assertUpdateType } from 'lib/types/update-types-enum.js'; import { type RawUpdateInfo } from 'lib/types/update-types.js'; import { ServerError } from 'lib/utils/errors.js'; import { type ViewerInfo, type FetchUpdatesResult, fetchUpdateInfosWithRawUpdateInfos, } from '../creators/update-creator.js'; import { dbQuery, SQL } from '../database/database.js'; import type { SQLStatementType } from '../database/types.js'; import type { Viewer } from '../session/viewer.js'; const defaultUpdateFetchResult = { updateInfos: [], userInfos: {} }; async function fetchUpdateInfosWithQuery( viewerInfo: ViewerInfo, query: SQLStatementType, ): Promise { if (!viewerInfo.viewer.loggedIn) { throw new ServerError('not_logged_in'); } if (viewerInfo.viewer.isScriptViewer) { return defaultUpdateFetchResult; } const [result] = await dbQuery(query); const rawUpdateInfos = []; for (const row of result) { rawUpdateInfos.push(rawUpdateInfoFromRow(row)); } return await fetchUpdateInfosWithRawUpdateInfos(rawUpdateInfos, viewerInfo); } function fetchUpdateInfos( viewer: Viewer, currentAsOf: number, calendarQuery: CalendarQuery, ): Promise { const query = SQL` SELECT id, type, content, time FROM updates WHERE user = ${viewer.id} AND time > ${currentAsOf} AND (updater IS NULL OR updater != ${viewer.session}) AND (target IS NULL OR target = ${viewer.session}) ORDER BY time ASC `; return fetchUpdateInfosWithQuery({ viewer, calendarQuery }, query); } -// ESLint doesn't recognize that invariant always throws -// eslint-disable-next-line consistent-return function rawUpdateInfoFromRow(row: Object): RawUpdateInfo { const type = assertUpdateType(row.type); - if (type === updateTypes.DELETE_ACCOUNT) { - const content = JSON.parse(row.content); - return { - type: updateTypes.DELETE_ACCOUNT, - id: row.id.toString(), - time: row.time, - deletedUserID: content.deletedUserID, - }; - } else if (type === updateTypes.UPDATE_THREAD) { - const { threadID } = JSON.parse(row.content); - return { - type: updateTypes.UPDATE_THREAD, - id: row.id.toString(), - time: row.time, - threadID, - }; - } else if (type === updateTypes.UPDATE_THREAD_READ_STATUS) { - const { threadID, unread } = JSON.parse(row.content); - return { - type: updateTypes.UPDATE_THREAD_READ_STATUS, - id: row.id.toString(), - time: row.time, - threadID, - unread, - }; - } else if (type === updateTypes.DELETE_THREAD) { - const { threadID } = JSON.parse(row.content); - return { - type: updateTypes.DELETE_THREAD, - id: row.id.toString(), - time: row.time, - threadID, - }; - } else if (type === updateTypes.JOIN_THREAD) { - const { threadID } = JSON.parse(row.content); - return { - type: updateTypes.JOIN_THREAD, - id: row.id.toString(), - time: row.time, - threadID, - }; - } else if (type === updateTypes.BAD_DEVICE_TOKEN) { - const { deviceToken } = JSON.parse(row.content); - return { - type: updateTypes.BAD_DEVICE_TOKEN, - id: row.id.toString(), - time: row.time, - deviceToken, - }; - } else if (type === updateTypes.UPDATE_ENTRY) { - const { entryID } = JSON.parse(row.content); - return { - type: updateTypes.UPDATE_ENTRY, - id: row.id.toString(), - time: row.time, - entryID, - }; - } else if (type === updateTypes.UPDATE_CURRENT_USER) { - return { - type: updateTypes.UPDATE_CURRENT_USER, - id: row.id.toString(), - time: row.time, - }; - } else if (type === updateTypes.UPDATE_USER) { - const content = JSON.parse(row.content); - return { - type: updateTypes.UPDATE_USER, - id: row.id.toString(), - time: row.time, - updatedUserID: content.updatedUserID, - }; - } - invariant(false, `unrecognized updateType ${type}`); + return updateSpecs[type].rawUpdateInfoFromRow(row); } const entryIDExtractString = '$.entryID'; function fetchUpdateInfoForEntryUpdate( viewer: Viewer, entryID: string, ): Promise { const query = SQL` SELECT id, type, content, time FROM updates WHERE user = ${viewer.id} AND type = ${updateTypes.UPDATE_ENTRY} AND JSON_EXTRACT(content, ${entryIDExtractString}) = ${entryID} ORDER BY time DESC LIMIT 1 `; return fetchUpdateInfosWithQuery({ viewer }, query); } const threadIDExtractString = '$.threadID'; function fetchUpdateInfoForThreadDeletion( viewer: Viewer, threadID: string, ): Promise { const query = SQL` SELECT id, type, content, time FROM updates WHERE user = ${viewer.id} AND type = ${updateTypes.DELETE_THREAD} AND JSON_EXTRACT(content, ${threadIDExtractString}) = ${threadID} ORDER BY time DESC LIMIT 1 `; return fetchUpdateInfosWithQuery({ viewer }, query); } export { fetchUpdateInfos, fetchUpdateInfoForEntryUpdate, fetchUpdateInfoForThreadDeletion, }; diff --git a/lib/shared/updates/bad-device-token-spec.js b/lib/shared/updates/bad-device-token-spec.js index e3e989e6f..e4002059d 100644 --- a/lib/shared/updates/bad-device-token-spec.js +++ b/lib/shared/updates/bad-device-token-spec.js @@ -1,7 +1,23 @@ // @flow import type { UpdateSpec } from './update-spec.js'; -import type { BadDeviceTokenUpdateInfo } from '../../types/update-types.js'; +import { updateTypes } from '../../types/update-types-enum.js'; +import type { + BadDeviceTokenRawUpdateInfo, + BadDeviceTokenUpdateInfo, +} from '../../types/update-types.js'; -export const badDeviceTokenSpec: UpdateSpec = - Object.freeze({}); +export const badDeviceTokenSpec: UpdateSpec< + BadDeviceTokenUpdateInfo, + BadDeviceTokenRawUpdateInfo, +> = Object.freeze({ + rawUpdateInfoFromRow(row: Object) { + const { deviceToken } = JSON.parse(row.content); + return { + type: updateTypes.BAD_DEVICE_TOKEN, + id: row.id.toString(), + time: row.time, + deviceToken, + }; + }, +}); diff --git a/lib/shared/updates/delete-account-spec.js b/lib/shared/updates/delete-account-spec.js index e6a82c477..7dc6c7a63 100644 --- a/lib/shared/updates/delete-account-spec.js +++ b/lib/shared/updates/delete-account-spec.js @@ -1,44 +1,59 @@ // @flow import type { UpdateSpec } from './update-spec.js'; import type { RawThreadInfos } from '../../types/thread-types.js'; -import type { AccountDeletionUpdateInfo } from '../../types/update-types.js'; +import { updateTypes } from '../../types/update-types-enum.js'; +import type { + AccountDeletionRawUpdateInfo, + AccountDeletionUpdateInfo, +} from '../../types/update-types.js'; import type { UserInfos } from '../../types/user-types.js'; -export const deleteAccountSpec: UpdateSpec = - Object.freeze({ - generateOpsForThreadUpdates( - storeThreadInfos: RawThreadInfos, - update: AccountDeletionUpdateInfo, - ) { - const operations = []; - for (const threadID in storeThreadInfos) { - const threadInfo = storeThreadInfos[threadID]; - const newMembers = threadInfo.members.filter( - member => member.id !== update.deletedUserID, - ); - if (newMembers.length < threadInfo.members.length) { - const updatedThread = { - ...threadInfo, - members: newMembers, - }; - operations.push({ - type: 'replace', - payload: { - id: threadID, - threadInfo: updatedThread, - }, - }); - } +export const deleteAccountSpec: UpdateSpec< + AccountDeletionUpdateInfo, + AccountDeletionRawUpdateInfo, +> = Object.freeze({ + generateOpsForThreadUpdates( + storeThreadInfos: RawThreadInfos, + update: AccountDeletionUpdateInfo, + ) { + const operations = []; + for (const threadID in storeThreadInfos) { + const threadInfo = storeThreadInfos[threadID]; + const newMembers = threadInfo.members.filter( + member => member.id !== update.deletedUserID, + ); + if (newMembers.length < threadInfo.members.length) { + const updatedThread = { + ...threadInfo, + members: newMembers, + }; + operations.push({ + type: 'replace', + payload: { + id: threadID, + threadInfo: updatedThread, + }, + }); } - return operations; - }, - reduceUserInfos(state: UserInfos, update: AccountDeletionUpdateInfo) { - const { deletedUserID } = update; - if (!state[deletedUserID]) { - return state; - } - const { [deletedUserID]: deleted, ...rest } = state; - return rest; - }, - }); + } + return operations; + }, + reduceUserInfos(state: UserInfos, update: AccountDeletionUpdateInfo) { + const { deletedUserID } = update; + if (!state[deletedUserID]) { + return state; + } + const { [deletedUserID]: deleted, ...rest } = state; + return rest; + }, + rawUpdateInfoFromRow(row: Object) { + const content = JSON.parse(row.content); + return { + type: updateTypes.DELETE_ACCOUNT, + id: row.id.toString(), + time: row.time, + deletedUserID: content.deletedUserID, + }; + }, +}); diff --git a/lib/shared/updates/delete-thread-spec.js b/lib/shared/updates/delete-thread-spec.js index bae1e970c..96946c4a7 100644 --- a/lib/shared/updates/delete-thread-spec.js +++ b/lib/shared/updates/delete-thread-spec.js @@ -1,36 +1,49 @@ // @flow import type { UpdateSpec } from './update-spec.js'; import type { RawThreadInfos } from '../../types/thread-types.js'; -import type { ThreadDeletionUpdateInfo } from '../../types/update-types.js'; +import { updateTypes } from '../../types/update-types-enum.js'; +import type { + ThreadDeletionRawUpdateInfo, + ThreadDeletionUpdateInfo, +} from '../../types/update-types.js'; -export const deleteThreadSpec: UpdateSpec = - Object.freeze({ - generateOpsForThreadUpdates( - storeThreadInfos: RawThreadInfos, - update: ThreadDeletionUpdateInfo, - ) { - if (storeThreadInfos[update.threadID]) { - return [ - { - type: 'remove', - payload: { - ids: [update.threadID], - }, +export const deleteThreadSpec: UpdateSpec< + ThreadDeletionUpdateInfo, + ThreadDeletionRawUpdateInfo, +> = Object.freeze({ + generateOpsForThreadUpdates( + storeThreadInfos: RawThreadInfos, + update: ThreadDeletionUpdateInfo, + ) { + if (storeThreadInfos[update.threadID]) { + return [ + { + type: 'remove', + payload: { + ids: [update.threadID], }, - ]; - } - return null; - }, - reduceCalendarThreadFilters( - filteredThreadIDs: $ReadOnlySet, - update: ThreadDeletionUpdateInfo, - ) { - if (!filteredThreadIDs.has(update.threadID)) { - return filteredThreadIDs; - } - return new Set( - [...filteredThreadIDs].filter(id => id !== update.threadID), - ); - }, - }); + }, + ]; + } + return null; + }, + reduceCalendarThreadFilters( + filteredThreadIDs: $ReadOnlySet, + update: ThreadDeletionUpdateInfo, + ) { + if (!filteredThreadIDs.has(update.threadID)) { + return filteredThreadIDs; + } + return new Set([...filteredThreadIDs].filter(id => id !== update.threadID)); + }, + rawUpdateInfoFromRow(row: Object) { + const { threadID } = JSON.parse(row.content); + return { + type: updateTypes.DELETE_THREAD, + id: row.id.toString(), + time: row.time, + threadID, + }; + }, +}); diff --git a/lib/shared/updates/join-thread-spec.js b/lib/shared/updates/join-thread-spec.js index dd25b259b..1715ec25f 100644 --- a/lib/shared/updates/join-thread-spec.js +++ b/lib/shared/updates/join-thread-spec.js @@ -1,83 +1,99 @@ // @flow import _isEqual from 'lodash/fp/isEqual.js'; import type { UpdateSpec } from './update-spec.js'; import type { RawEntryInfo } from '../../types/entry-types.js'; import type { RawMessageInfo, MessageTruncationStatuses, } from '../../types/message-types.js'; import type { RawThreadInfos } from '../../types/thread-types.js'; -import type { ThreadJoinUpdateInfo } from '../../types/update-types.js'; +import { updateTypes } from '../../types/update-types-enum.js'; +import type { + ThreadJoinUpdateInfo, + ThreadJoinRawUpdateInfo, +} from '../../types/update-types.js'; import { combineTruncationStatuses } from '../message-utils.js'; import { threadInFilterList } from '../thread-utils.js'; -export const joinThreadSpec: UpdateSpec = Object.freeze({ +export const joinThreadSpec: UpdateSpec< + ThreadJoinUpdateInfo, + ThreadJoinRawUpdateInfo, +> = Object.freeze({ generateOpsForThreadUpdates( storeThreadInfos: RawThreadInfos, update: ThreadJoinUpdateInfo, ) { if (_isEqual(storeThreadInfos[update.threadInfo.id])(update.threadInfo)) { return null; } return [ { type: 'replace', payload: { id: update.threadInfo.id, threadInfo: update.threadInfo, }, }, ]; }, mergeEntryInfos( entryIDs: Set, mergedEntryInfos: Array, update: ThreadJoinUpdateInfo, ) { for (const entryInfo of update.rawEntryInfos) { const entryID = entryInfo.id; if (!entryID || entryIDs.has(entryID)) { continue; } mergedEntryInfos.push(entryInfo); entryIDs.add(entryID); } }, reduceCalendarThreadFilters( filteredThreadIDs: $ReadOnlySet, update: ThreadJoinUpdateInfo, ) { if ( !threadInFilterList(update.threadInfo) || filteredThreadIDs.has(update.threadInfo.id) ) { return filteredThreadIDs; } return new Set([...filteredThreadIDs, update.threadInfo.id]); }, getRawMessageInfos(update: ThreadJoinUpdateInfo) { return update.rawMessageInfos; }, mergeMessageInfosAndTruncationStatuses( messageIDs: Set, messageInfos: Array, truncationStatuses: MessageTruncationStatuses, update: ThreadJoinUpdateInfo, ) { for (const messageInfo of update.rawMessageInfos) { const messageID = messageInfo.id; if (!messageID || messageIDs.has(messageID)) { continue; } messageInfos.push(messageInfo); messageIDs.add(messageID); } truncationStatuses[update.threadInfo.id] = combineTruncationStatuses( update.truncationStatus, truncationStatuses[update.threadInfo.id], ); }, + rawUpdateInfoFromRow(row: Object) { + const { threadID } = JSON.parse(row.content); + return { + type: updateTypes.JOIN_THREAD, + id: row.id.toString(), + time: row.time, + threadID, + }; + }, }); diff --git a/lib/shared/updates/update-current-user-spec.js b/lib/shared/updates/update-current-user-spec.js index 4d738eb9e..0033f3238 100644 --- a/lib/shared/updates/update-current-user-spec.js +++ b/lib/shared/updates/update-current-user-spec.js @@ -1,17 +1,30 @@ // @flow import _isEqual from 'lodash/fp/isEqual.js'; import type { UpdateSpec } from './update-spec.js'; -import type { CurrentUserUpdateInfo } from '../../types/update-types.js'; +import { updateTypes } from '../../types/update-types-enum.js'; +import type { + CurrentUserUpdateInfo, + CurrentUserRawUpdateInfo, +} from '../../types/update-types.js'; import type { CurrentUserInfo } from '../../types/user-types.js'; -export const updateCurrentUserSpec: UpdateSpec = - Object.freeze({ - reduceCurrentUser(state: ?CurrentUserInfo, update: CurrentUserUpdateInfo) { - if (!_isEqual(update.currentUserInfo)(state)) { - return update.currentUserInfo; - } - return state; - }, - }); +export const updateCurrentUserSpec: UpdateSpec< + CurrentUserUpdateInfo, + CurrentUserRawUpdateInfo, +> = Object.freeze({ + reduceCurrentUser(state: ?CurrentUserInfo, update: CurrentUserUpdateInfo) { + if (!_isEqual(update.currentUserInfo)(state)) { + return update.currentUserInfo; + } + return state; + }, + rawUpdateInfoFromRow(row: Object) { + return { + type: updateTypes.UPDATE_CURRENT_USER, + id: row.id.toString(), + time: row.time, + }; + }, +}); diff --git a/lib/shared/updates/update-entry-spec.js b/lib/shared/updates/update-entry-spec.js index c76c2c4d4..85aeb5aae 100644 --- a/lib/shared/updates/update-entry-spec.js +++ b/lib/shared/updates/update-entry-spec.js @@ -1,21 +1,35 @@ // @flow import type { UpdateSpec } from './update-spec.js'; import type { RawEntryInfo } from '../../types/entry-types.js'; -import type { EntryUpdateInfo } from '../../types/update-types.js'; +import { updateTypes } from '../../types/update-types-enum.js'; +import type { + EntryUpdateInfo, + EntryRawUpdateInfo, +} from '../../types/update-types.js'; -export const updateEntrySpec: UpdateSpec = Object.freeze({ - mergeEntryInfos( - entryIDs: Set, - mergedEntryInfos: Array, - update: EntryUpdateInfo, - ) { - const { entryInfo } = update; - const entryID = entryInfo.id; - if (!entryID || entryIDs.has(entryID)) { - return; - } - mergedEntryInfos.push(entryInfo); - entryIDs.add(entryID); - }, -}); +export const updateEntrySpec: UpdateSpec = + Object.freeze({ + mergeEntryInfos( + entryIDs: Set, + mergedEntryInfos: Array, + update: EntryUpdateInfo, + ) { + const { entryInfo } = update; + const entryID = entryInfo.id; + if (!entryID || entryIDs.has(entryID)) { + return; + } + mergedEntryInfos.push(entryInfo); + entryIDs.add(entryID); + }, + rawUpdateInfoFromRow(row: Object) { + const { entryID } = JSON.parse(row.content); + return { + type: updateTypes.UPDATE_ENTRY, + id: row.id.toString(), + time: row.time, + entryID, + }; + }, + }); diff --git a/lib/shared/updates/update-spec.js b/lib/shared/updates/update-spec.js index b778d7bc3..292e0dab2 100644 --- a/lib/shared/updates/update-spec.js +++ b/lib/shared/updates/update-spec.js @@ -1,39 +1,43 @@ // @flow import type { ThreadStoreOperation } from '../../ops/thread-store-ops.js'; import type { RawEntryInfo } from '../../types/entry-types.js'; import type { RawMessageInfo, MessageTruncationStatuses, } from '../../types/message-types.js'; import type { RawThreadInfos } from '../../types/thread-types.js'; -import type { ClientUpdateInfo } from '../../types/update-types.js'; +import type { + ClientUpdateInfo, + RawUpdateInfo, +} from '../../types/update-types.js'; import type { CurrentUserInfo, UserInfos } from '../../types/user-types.js'; -export type UpdateSpec = { +export type UpdateSpec = { +generateOpsForThreadUpdates?: ( storeThreadInfos: RawThreadInfos, update: UpdateInfo, ) => ?$ReadOnlyArray, +mergeEntryInfos?: ( entryIDs: Set, mergedEntryInfos: Array, update: UpdateInfo, ) => void, +reduceCurrentUser?: ( state: ?CurrentUserInfo, update: UpdateInfo, ) => ?CurrentUserInfo, +reduceUserInfos?: (state: UserInfos, update: UpdateInfo) => UserInfos, +reduceCalendarThreadFilters?: ( filteredThreadIDs: $ReadOnlySet, update: UpdateInfo, ) => $ReadOnlySet, +getRawMessageInfos?: (update: UpdateInfo) => $ReadOnlyArray, +mergeMessageInfosAndTruncationStatuses?: ( messageIDs: Set, messageInfos: Array, truncationStatuses: MessageTruncationStatuses, update: UpdateInfo, ) => void, + +rawUpdateInfoFromRow: (row: Object) => RawInfo, }; diff --git a/lib/shared/updates/update-specs.js b/lib/shared/updates/update-specs.js index f23a168eb..b0a0db3d5 100644 --- a/lib/shared/updates/update-specs.js +++ b/lib/shared/updates/update-specs.js @@ -1,27 +1,27 @@ // @flow import { badDeviceTokenSpec } from './bad-device-token-spec.js'; import { deleteAccountSpec } from './delete-account-spec.js'; import { deleteThreadSpec } from './delete-thread-spec.js'; import { joinThreadSpec } from './join-thread-spec.js'; import { updateCurrentUserSpec } from './update-current-user-spec.js'; import { updateEntrySpec } from './update-entry-spec.js'; import type { UpdateSpec } from './update-spec.js'; import { updateThreadReadStatusSpec } from './update-thread-read-status-spec.js'; import { updateThreadSpec } from './update-thread-spec.js'; import { updateUserSpec } from './update-user-spec.js'; import { updateTypes, type UpdateType } from '../../types/update-types-enum.js'; export const updateSpecs: { - +[UpdateType]: UpdateSpec<*>, + +[UpdateType]: UpdateSpec<*, *>, } = Object.freeze({ [updateTypes.DELETE_ACCOUNT]: deleteAccountSpec, [updateTypes.UPDATE_THREAD]: updateThreadSpec, [updateTypes.UPDATE_THREAD_READ_STATUS]: updateThreadReadStatusSpec, [updateTypes.DELETE_THREAD]: deleteThreadSpec, [updateTypes.JOIN_THREAD]: joinThreadSpec, [updateTypes.BAD_DEVICE_TOKEN]: badDeviceTokenSpec, [updateTypes.UPDATE_ENTRY]: updateEntrySpec, [updateTypes.UPDATE_CURRENT_USER]: updateCurrentUserSpec, [updateTypes.UPDATE_USER]: updateUserSpec, }); diff --git a/lib/shared/updates/update-thread-read-status-spec.js b/lib/shared/updates/update-thread-read-status-spec.js index 4ec47b6d5..fc0e897bc 100644 --- a/lib/shared/updates/update-thread-read-status-spec.js +++ b/lib/shared/updates/update-thread-read-status-spec.js @@ -1,40 +1,56 @@ // @flow import type { UpdateSpec } from './update-spec.js'; import type { RawThreadInfo, RawThreadInfos, } from '../../types/thread-types.js'; -import type { ThreadReadStatusUpdateInfo } from '../../types/update-types.js'; +import { updateTypes } from '../../types/update-types-enum.js'; +import type { + ThreadReadStatusUpdateInfo, + ThreadReadStatusRawUpdateInfo, +} from '../../types/update-types.js'; -export const updateThreadReadStatusSpec: UpdateSpec = - Object.freeze({ - generateOpsForThreadUpdates( - storeThreadInfos: RawThreadInfos, - update: ThreadReadStatusUpdateInfo, +export const updateThreadReadStatusSpec: UpdateSpec< + ThreadReadStatusUpdateInfo, + ThreadReadStatusRawUpdateInfo, +> = Object.freeze({ + generateOpsForThreadUpdates( + storeThreadInfos: RawThreadInfos, + update: ThreadReadStatusUpdateInfo, + ) { + const storeThreadInfo: ?RawThreadInfo = storeThreadInfos[update.threadID]; + if ( + !storeThreadInfo || + storeThreadInfo.currentUser.unread === update.unread ) { - const storeThreadInfo: ?RawThreadInfo = storeThreadInfos[update.threadID]; - if ( - !storeThreadInfo || - storeThreadInfo.currentUser.unread === update.unread - ) { - return null; - } - const updatedThread = { - ...storeThreadInfo, - currentUser: { - ...storeThreadInfo.currentUser, - unread: update.unread, - }, - }; - return [ - { - type: 'replace', - payload: { - id: update.threadID, - threadInfo: updatedThread, - }, + return null; + } + const updatedThread = { + ...storeThreadInfo, + currentUser: { + ...storeThreadInfo.currentUser, + unread: update.unread, + }, + }; + return [ + { + type: 'replace', + payload: { + id: update.threadID, + threadInfo: updatedThread, }, - ]; - }, - }); + }, + ]; + }, + rawUpdateInfoFromRow(row: Object) { + const { threadID, unread } = JSON.parse(row.content); + return { + type: updateTypes.UPDATE_THREAD_READ_STATUS, + id: row.id.toString(), + time: row.time, + threadID, + unread, + }; + }, +}); diff --git a/lib/shared/updates/update-thread-spec.js b/lib/shared/updates/update-thread-spec.js index ac196ce6d..3db83d6da 100644 --- a/lib/shared/updates/update-thread-spec.js +++ b/lib/shared/updates/update-thread-spec.js @@ -1,42 +1,58 @@ // @flow import _isEqual from 'lodash/fp/isEqual.js'; import type { UpdateSpec } from './update-spec.js'; import type { RawThreadInfos } from '../../types/thread-types.js'; -import type { ThreadUpdateInfo } from '../../types/update-types.js'; +import { updateTypes } from '../../types/update-types-enum.js'; +import type { + ThreadUpdateInfo, + ThreadRawUpdateInfo, +} from '../../types/update-types.js'; import { threadInFilterList } from '../thread-utils.js'; -export const updateThreadSpec: UpdateSpec = Object.freeze({ +export const updateThreadSpec: UpdateSpec< + ThreadUpdateInfo, + ThreadRawUpdateInfo, +> = Object.freeze({ generateOpsForThreadUpdates( storeThreadInfos: RawThreadInfos, update: ThreadUpdateInfo, ) { if (_isEqual(storeThreadInfos[update.threadInfo.id])(update.threadInfo)) { return null; } return [ { type: 'replace', payload: { id: update.threadInfo.id, threadInfo: update.threadInfo, }, }, ]; }, reduceCalendarThreadFilters( filteredThreadIDs: $ReadOnlySet, update: ThreadUpdateInfo, ) { if ( threadInFilterList(update.threadInfo) || !filteredThreadIDs.has(update.threadInfo.id) ) { return filteredThreadIDs; } return new Set( [...filteredThreadIDs].filter(id => id !== update.threadInfo.id), ); }, + rawUpdateInfoFromRow(row: Object) { + const { threadID } = JSON.parse(row.content); + return { + type: updateTypes.UPDATE_THREAD, + id: row.id.toString(), + time: row.time, + threadID, + }; + }, }); diff --git a/lib/shared/updates/update-user-spec.js b/lib/shared/updates/update-user-spec.js index 58dc63b29..07a272d80 100644 --- a/lib/shared/updates/update-user-spec.js +++ b/lib/shared/updates/update-user-spec.js @@ -1,6 +1,21 @@ // @flow import type { UpdateSpec } from './update-spec.js'; -import type { UserUpdateInfo } from '../../types/update-types.js'; +import { updateTypes } from '../../types/update-types-enum.js'; +import type { + UserUpdateInfo, + UserRawUpdateInfo, +} from '../../types/update-types.js'; -export const updateUserSpec: UpdateSpec = Object.freeze({}); +export const updateUserSpec: UpdateSpec = + Object.freeze({ + rawUpdateInfoFromRow(row: Object) { + const content = JSON.parse(row.content); + return { + type: updateTypes.UPDATE_USER, + id: row.id.toString(), + time: row.time, + updatedUserID: content.updatedUserID, + }; + }, + }); diff --git a/lib/types/update-types.js b/lib/types/update-types.js index 34bdc160b..a2c5c10d1 100644 --- a/lib/types/update-types.js +++ b/lib/types/update-types.js @@ -1,399 +1,399 @@ // @flow import t, { type TUnion, type TInterface } from 'tcomb'; import { type RawEntryInfo, rawEntryInfoValidator } from './entry-types.js'; import { type RawMessageInfo, rawMessageInfoValidator, type MessageTruncationStatus, messageTruncationStatusValidator, } from './message-types.js'; import { type RawThreadInfo, rawThreadInfoValidator } from './thread-types.js'; import { updateTypes } from './update-types-enum.js'; import { type UserInfo, userInfoValidator, type UserInfos, userInfosValidator, type LoggedInUserInfo, loggedInUserInfoValidator, } from './user-types.js'; import { tNumber, tShape, tID } from '../utils/validation-utils.js'; type AccountDeletionData = { +deletedUserID: string, }; type ThreadData = { +threadID: string, }; type ThreadReadStatusData = { +threadID: string, +unread: boolean, }; type ThreadDeletionData = { +threadID: string, }; type ThreadJoinData = { +threadID: string, }; type BadDeviceTokenData = { +deviceToken: string, }; type EntryData = { +entryID: string, }; type CurrentUserData = {}; type UserData = { // ID of the UserInfo being updated +updatedUserID: string, }; type SharedUpdateData = { +userID: string, +time: number, }; type AccountDeletionUpdateData = { ...SharedUpdateData, ...AccountDeletionData, +type: 0, }; type ThreadUpdateData = { ...SharedUpdateData, ...ThreadData, +type: 1, +targetSession?: string, }; type ThreadReadStatusUpdateData = { ...SharedUpdateData, ...ThreadReadStatusData, +type: 2, }; type ThreadDeletionUpdateData = { ...SharedUpdateData, ...ThreadDeletionData, +type: 3, }; type ThreadJoinUpdateData = { ...SharedUpdateData, ...ThreadJoinData, +type: 4, +targetSession?: string, }; type BadDeviceTokenUpdateData = { ...SharedUpdateData, ...BadDeviceTokenData, +type: 5, +targetCookie: string, }; type EntryUpdateData = { ...SharedUpdateData, ...EntryData, +type: 6, +targetSession: string, }; type CurrentUserUpdateData = { ...SharedUpdateData, ...CurrentUserData, +type: 7, }; type UserUpdateData = { ...SharedUpdateData, ...UserData, +type: 8, +targetSession?: string, }; export type UpdateData = | AccountDeletionUpdateData | ThreadUpdateData | ThreadReadStatusUpdateData | ThreadDeletionUpdateData | ThreadJoinUpdateData | BadDeviceTokenUpdateData | EntryUpdateData | CurrentUserUpdateData | UserUpdateData; type SharedRawUpdateInfo = { +id: string, +time: number, }; -type AccountDeletionRawUpdateInfo = { +export type AccountDeletionRawUpdateInfo = { ...SharedRawUpdateInfo, ...AccountDeletionData, +type: 0, }; -type ThreadRawUpdateInfo = { +export type ThreadRawUpdateInfo = { ...SharedRawUpdateInfo, ...ThreadData, +type: 1, }; -type ThreadReadStatusRawUpdateInfo = { +export type ThreadReadStatusRawUpdateInfo = { ...SharedRawUpdateInfo, ...ThreadReadStatusData, +type: 2, }; -type ThreadDeletionRawUpdateInfo = { +export type ThreadDeletionRawUpdateInfo = { ...SharedRawUpdateInfo, ...ThreadDeletionData, +type: 3, }; -type ThreadJoinRawUpdateInfo = { +export type ThreadJoinRawUpdateInfo = { ...SharedRawUpdateInfo, ...ThreadJoinData, +type: 4, }; -type BadDeviceTokenRawUpdateInfo = { +export type BadDeviceTokenRawUpdateInfo = { ...SharedRawUpdateInfo, ...BadDeviceTokenData, +type: 5, }; -type EntryRawUpdateInfo = { +export type EntryRawUpdateInfo = { ...SharedRawUpdateInfo, ...EntryData, +type: 6, }; -type CurrentUserRawUpdateInfo = { +export type CurrentUserRawUpdateInfo = { ...SharedRawUpdateInfo, ...CurrentUserData, +type: 7, }; -type UserRawUpdateInfo = { +export type UserRawUpdateInfo = { ...SharedRawUpdateInfo, ...UserData, +type: 8, }; export type RawUpdateInfo = | AccountDeletionRawUpdateInfo | ThreadRawUpdateInfo | ThreadReadStatusRawUpdateInfo | ThreadDeletionRawUpdateInfo | ThreadJoinRawUpdateInfo | BadDeviceTokenRawUpdateInfo | EntryRawUpdateInfo | CurrentUserRawUpdateInfo | UserRawUpdateInfo; export type AccountDeletionUpdateInfo = { +type: 0, +id: string, +time: number, +deletedUserID: string, }; export const accountDeletionUpdateInfoValidator: TInterface = tShape({ type: tNumber(updateTypes.DELETE_ACCOUNT), id: t.String, time: t.Number, deletedUserID: t.String, }); export type ThreadUpdateInfo = { +type: 1, +id: string, +time: number, +threadInfo: RawThreadInfo, }; export const threadUpdateInfoValidator: TInterface = tShape({ type: tNumber(updateTypes.UPDATE_THREAD), id: t.String, time: t.Number, threadInfo: rawThreadInfoValidator, }); export type ThreadReadStatusUpdateInfo = { +type: 2, +id: string, +time: number, +threadID: string, +unread: boolean, }; export const threadReadStatusUpdateInfoValidator: TInterface = tShape({ type: tNumber(updateTypes.UPDATE_THREAD_READ_STATUS), id: t.String, time: t.Number, threadID: tID, unread: t.Boolean, }); export type ThreadDeletionUpdateInfo = { +type: 3, +id: string, +time: number, +threadID: string, }; export const threadDeletionUpdateInfoValidator: TInterface = tShape({ type: tNumber(updateTypes.DELETE_THREAD), id: t.String, time: t.Number, threadID: tID, }); export type ThreadJoinUpdateInfo = { +type: 4, +id: string, +time: number, +threadInfo: RawThreadInfo, +rawMessageInfos: $ReadOnlyArray, +truncationStatus: MessageTruncationStatus, +rawEntryInfos: $ReadOnlyArray, }; export const threadJoinUpdateInfoValidator: TInterface = tShape({ type: tNumber(updateTypes.JOIN_THREAD), id: t.String, time: t.Number, threadInfo: rawThreadInfoValidator, rawMessageInfos: t.list(rawMessageInfoValidator), truncationStatus: messageTruncationStatusValidator, rawEntryInfos: t.list(rawEntryInfoValidator), }); export type BadDeviceTokenUpdateInfo = { +type: 5, +id: string, +time: number, +deviceToken: string, }; export const badDeviceTokenUpdateInfoValidator: TInterface = tShape({ type: tNumber(updateTypes.BAD_DEVICE_TOKEN), id: t.String, time: t.Number, deviceToken: t.String, }); export type EntryUpdateInfo = { +type: 6, +id: string, +time: number, +entryInfo: RawEntryInfo, }; export const entryUpdateInfoValidator: TInterface = tShape({ type: tNumber(updateTypes.UPDATE_ENTRY), id: t.String, time: t.Number, entryInfo: rawEntryInfoValidator, }); export type CurrentUserUpdateInfo = { +type: 7, +id: string, +time: number, +currentUserInfo: LoggedInUserInfo, }; export type UserUpdateInfo = { +type: 8, +id: string, +time: number, // Updated UserInfo is already contained within the UpdatesResultWithUserInfos +updatedUserID: string, }; export const userUpdateInfoValidator: TInterface = tShape({ type: tNumber(updateTypes.UPDATE_USER), id: t.String, time: t.Number, updatedUserID: t.String, }); export type ClientUpdateInfo = | AccountDeletionUpdateInfo | ThreadUpdateInfo | ThreadReadStatusUpdateInfo | ThreadDeletionUpdateInfo | ThreadJoinUpdateInfo | BadDeviceTokenUpdateInfo | EntryUpdateInfo | CurrentUserUpdateInfo | UserUpdateInfo; type ServerCurrentUserUpdateInfo = { +type: 7, +id: string, +time: number, +currentUserInfo: LoggedInUserInfo, }; export const serverCurrentUserUpdateInfoValidator: TInterface = tShape({ type: tNumber(updateTypes.UPDATE_CURRENT_USER), id: t.String, time: t.Number, currentUserInfo: loggedInUserInfoValidator, }); export type ServerUpdateInfo = | AccountDeletionUpdateInfo | ThreadUpdateInfo | ThreadReadStatusUpdateInfo | ThreadDeletionUpdateInfo | ThreadJoinUpdateInfo | BadDeviceTokenUpdateInfo | EntryUpdateInfo | ServerCurrentUserUpdateInfo | UserUpdateInfo; export const serverUpdateInfoValidator: TUnion = t.union([ accountDeletionUpdateInfoValidator, threadUpdateInfoValidator, threadReadStatusUpdateInfoValidator, threadDeletionUpdateInfoValidator, threadJoinUpdateInfoValidator, badDeviceTokenUpdateInfoValidator, entryUpdateInfoValidator, serverCurrentUserUpdateInfoValidator, userUpdateInfoValidator, ]); export type ServerUpdatesResult = { +currentAsOf: number, +newUpdates: $ReadOnlyArray, }; export const serverUpdatesResultValidator: TInterface = tShape({ currentAsOf: t.Number, newUpdates: t.list(serverUpdateInfoValidator), }); export type ServerUpdatesResultWithUserInfos = { +updatesResult: ServerUpdatesResult, +userInfos: $ReadOnlyArray, }; export const serverUpdatesResultWithUserInfosValidator: TInterface = tShape({ updatesResult: serverUpdatesResultValidator, userInfos: t.list(userInfoValidator), }); export type ClientUpdatesResult = { +currentAsOf: number, +newUpdates: $ReadOnlyArray, }; export type ClientUpdatesResultWithUserInfos = { +updatesResult: ClientUpdatesResult, +userInfos: $ReadOnlyArray, }; export type CreateUpdatesResult = { +viewerUpdates: $ReadOnlyArray, +userInfos: UserInfos, }; export const createUpdatesResultValidator: TInterface = tShape({ viewerUpdates: t.list(serverUpdateInfoValidator), userInfos: userInfosValidator, }); export type ServerCreateUpdatesResponse = { +viewerUpdates: $ReadOnlyArray, +userInfos: $ReadOnlyArray, }; export const serverCreateUpdatesResponseValidator: TInterface = tShape({ viewerUpdates: t.list(serverUpdateInfoValidator), userInfos: t.list(userInfoValidator), }); export type ClientCreateUpdatesResponse = { +viewerUpdates: $ReadOnlyArray, +userInfos: $ReadOnlyArray, }; export const processUpdatesActionType = 'PROCESS_UPDATES';