diff --git a/lib/shared/updates/delete-account-spec.js b/lib/shared/updates/delete-account-spec.js index 0d88e9633..e4cb8628e 100644 --- a/lib/shared/updates/delete-account-spec.js +++ b/lib/shared/updates/delete-account-spec.js @@ -1,103 +1,103 @@ // @flow import t from 'tcomb'; import type { UpdateSpec } from './update-spec.js'; import type { RawThreadInfos } from '../../types/thread-types.js'; import { updateTypes } from '../../types/update-types-enum.js'; import type { AccountDeletionRawUpdateInfo, AccountDeletionUpdateData, AccountDeletionUpdateInfo, } from '../../types/update-types.js'; import type { UserInfos } from '../../types/user-types.js'; -import { tNumber, tShape } from '../../utils/validation-utils.js'; +import { tNumber, tShape, tUserID } from '../../utils/validation-utils.js'; export const deleteAccountSpec: UpdateSpec< AccountDeletionUpdateInfo, AccountDeletionRawUpdateInfo, AccountDeletionUpdateData, > = 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; }, rawUpdateInfoFromRow(row: Object) { const content = JSON.parse(row.content); return { type: updateTypes.DELETE_ACCOUNT, id: row.id.toString(), time: row.time, deletedUserID: content.deletedUserID, }; }, updateContentForServerDB(data: AccountDeletionUpdateData) { return JSON.stringify({ deletedUserID: data.deletedUserID }); }, rawInfoFromData(data: AccountDeletionUpdateData, id: string) { return { type: updateTypes.DELETE_ACCOUNT, id, time: data.time, deletedUserID: data.deletedUserID, }; }, updateInfoFromRawInfo(info: AccountDeletionRawUpdateInfo) { return { type: updateTypes.DELETE_ACCOUNT, id: info.id, time: info.time, deletedUserID: info.deletedUserID, }; }, deleteCondition: new Set([ updateTypes.DELETE_ACCOUNT, updateTypes.UPDATE_USER, ]), keyForUpdateData(data: AccountDeletionUpdateData) { return data.deletedUserID; }, keyForUpdateInfo(info: AccountDeletionUpdateInfo) { return info.deletedUserID; }, typesOfReplacedUpdatesForMatchingKey: 'all_types', generateOpsForUserInfoUpdates(update: AccountDeletionUpdateInfo) { return [{ type: 'remove_users', payload: { ids: [update.deletedUserID] } }]; }, infoValidator: tShape({ type: tNumber(updateTypes.DELETE_ACCOUNT), id: t.String, time: t.Number, - deletedUserID: t.String, + deletedUserID: tUserID, }), }); diff --git a/lib/shared/updates/update-user-spec.js b/lib/shared/updates/update-user-spec.js index 88f0558c2..b3b8769d8 100644 --- a/lib/shared/updates/update-user-spec.js +++ b/lib/shared/updates/update-user-spec.js @@ -1,67 +1,67 @@ // @flow import t from 'tcomb'; import type { UpdateSpec } from './update-spec.js'; import { updateTypes } from '../../types/update-types-enum.js'; import type { UserUpdateInfo, UserRawUpdateInfo, UserUpdateData, } from '../../types/update-types.js'; -import { tNumber, tShape } from '../../utils/validation-utils.js'; +import { tNumber, tShape, tUserID } from '../../utils/validation-utils.js'; export const updateUserSpec: UpdateSpec< UserUpdateInfo, UserRawUpdateInfo, UserUpdateData, > = 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, }; }, entitiesToFetch(info: UserRawUpdateInfo) { return { userID: info.updatedUserID, }; }, updateContentForServerDB(data: UserUpdateData) { const { updatedUserID } = data; return JSON.stringify({ updatedUserID }); }, rawInfoFromData(data: UserUpdateData, id: string) { return { type: updateTypes.UPDATE_USER, id, time: data.time, updatedUserID: data.updatedUserID, }; }, updateInfoFromRawInfo(info: UserRawUpdateInfo) { return { type: updateTypes.UPDATE_USER, id: info.id, time: info.time, updatedUserID: info.updatedUserID, }; }, deleteCondition: new Set([updateTypes.UPDATE_USER]), keyForUpdateData(data: UserUpdateData) { return data.updatedUserID; }, keyForUpdateInfo(info: UserUpdateInfo) { return info.updatedUserID; }, typesOfReplacedUpdatesForMatchingKey: null, infoValidator: tShape({ type: tNumber(updateTypes.UPDATE_USER), id: t.String, time: t.Number, - updatedUserID: t.String, + updatedUserID: tUserID, }), }); diff --git a/lib/types/thread-types.js b/lib/types/thread-types.js index 379eff30a..78e241bbe 100644 --- a/lib/types/thread-types.js +++ b/lib/types/thread-types.js @@ -1,427 +1,427 @@ // @flow import t, { type TInterface } from 'tcomb'; import { type AvatarDBContent, type ClientAvatar, clientAvatarValidator, type UpdateUserAvatarRequest, } from './avatar-types.js'; import type { CalendarQuery } from './entry-types.js'; import type { Media } from './media-types.js'; import type { MessageTruncationStatuses, RawMessageInfo, } from './message-types.js'; import type { RawThreadInfo, ResolvedThreadInfo, ThreadInfo, } from './minimally-encoded-thread-permissions-types.js'; import { type ThreadSubscription, threadSubscriptionValidator, } from './subscription-types.js'; import { type ThreadPermissionsInfo, threadPermissionsInfoValidator, type ThreadRolePermissionsBlob, threadRolePermissionsBlobValidator, type UserSurfacedPermission, } from './thread-permission-types.js'; import { type ThreadType, threadTypeValidator } from './thread-types-enum.js'; import type { ClientUpdateInfo, ServerUpdateInfo } from './update-types.js'; import type { UserInfo, UserInfos } from './user-types.js'; import type { SpecialRole } from '../permissions/special-roles.js'; import { type ThreadEntity } from '../utils/entity-text.js'; -import { tID, tShape } from '../utils/validation-utils.js'; +import { tID, tShape, tUserID } from '../utils/validation-utils.js'; export type LegacyMemberInfo = { +id: string, +role: ?string, +permissions: ThreadPermissionsInfo, +isSender: boolean, }; export const legacyMemberInfoValidator: TInterface = tShape({ - id: t.String, + id: tUserID, role: t.maybe(tID), permissions: threadPermissionsInfoValidator, isSender: t.Boolean, }); export type ClientLegacyRoleInfo = { +id: string, +name: string, +permissions: ThreadRolePermissionsBlob, +isDefault: boolean, }; export const clientLegacyRoleInfoValidator: TInterface = tShape({ id: tID, name: t.String, permissions: threadRolePermissionsBlobValidator, isDefault: t.Boolean, }); export type ServerLegacyRoleInfo = { +id: string, +name: string, +permissions: ThreadRolePermissionsBlob, +isDefault: boolean, +specialRole: ?SpecialRole, }; export type LegacyThreadCurrentUserInfo = { +role: ?string, +permissions: ThreadPermissionsInfo, +subscription: ThreadSubscription, +unread: ?boolean, }; export const legacyThreadCurrentUserInfoValidator: TInterface = tShape({ role: t.maybe(tID), permissions: threadPermissionsInfoValidator, subscription: threadSubscriptionValidator, unread: t.maybe(t.Boolean), }); export type LegacyRawThreadInfo = { +id: string, +type: ThreadType, +name: ?string, +avatar?: ?ClientAvatar, +description: ?string, +color: string, // hex, without "#" or "0x" +creationTime: number, // millisecond timestamp +parentThreadID: ?string, +containingThreadID: ?string, +community: ?string, +members: $ReadOnlyArray, +roles: { +[id: string]: ClientLegacyRoleInfo }, +currentUser: LegacyThreadCurrentUserInfo, +sourceMessageID?: string, +repliesCount: number, +pinnedCount?: number, }; export type LegacyRawThreadInfos = { +[id: string]: LegacyRawThreadInfo, }; export const legacyRawThreadInfoValidator: TInterface = tShape({ id: tID, type: threadTypeValidator, name: t.maybe(t.String), avatar: t.maybe(clientAvatarValidator), description: t.maybe(t.String), color: t.String, creationTime: t.Number, parentThreadID: t.maybe(tID), containingThreadID: t.maybe(tID), community: t.maybe(tID), members: t.list(legacyMemberInfoValidator), roles: t.dict(tID, clientLegacyRoleInfoValidator), currentUser: legacyThreadCurrentUserInfoValidator, sourceMessageID: t.maybe(tID), repliesCount: t.Number, pinnedCount: t.maybe(t.Number), }); export type MixedRawThreadInfos = { +[id: string]: LegacyRawThreadInfo | RawThreadInfo, }; export type RawThreadInfos = { +[id: string]: RawThreadInfo, }; export type ServerMemberInfo = { +id: string, +role: ?string, +permissions: ThreadPermissionsInfo, +subscription: ThreadSubscription, +unread: ?boolean, +isSender: boolean, }; export type ServerThreadInfo = { +id: string, +type: ThreadType, +name: ?string, +avatar?: AvatarDBContent, +description: ?string, +color: string, // hex, without "#" or "0x" +creationTime: number, // millisecond timestamp +parentThreadID: ?string, +containingThreadID: ?string, +community: ?string, +depth: number, +members: $ReadOnlyArray, +roles: { +[id: string]: ServerLegacyRoleInfo }, +sourceMessageID?: string, +repliesCount: number, +pinnedCount: number, }; export type LegacyThreadStore = { +threadInfos: MixedRawThreadInfos, }; export type ThreadStore = { +threadInfos: RawThreadInfos, }; export type ClientDBThreadInfo = { +id: string, +type: number, +name: ?string, +avatar?: ?string, +description: ?string, +color: string, +creationTime: string, +parentThreadID: ?string, +containingThreadID: ?string, +community: ?string, +members: string, +roles: string, +currentUser: string, +sourceMessageID?: string, +repliesCount: number, +pinnedCount?: number, }; export type ThreadDeletionRequest = { +threadID: string, +accountPassword?: empty, }; export type RemoveMembersRequest = { +threadID: string, +memberIDs: $ReadOnlyArray, }; export type RoleChangeRequest = { +threadID: string, +memberIDs: $ReadOnlyArray, +role: string, }; export type ChangeThreadSettingsResult = { +updatesResult: { +newUpdates: $ReadOnlyArray, }, +newMessageInfos: $ReadOnlyArray, }; export type ChangeThreadSettingsPayload = { +threadID: string, +updatesResult: { +newUpdates: $ReadOnlyArray, }, +newMessageInfos: $ReadOnlyArray, }; export type LeaveThreadRequest = { +threadID: string, }; export type LeaveThreadResult = { +updatesResult: { +newUpdates: $ReadOnlyArray, }, }; export type LeaveThreadPayload = { +updatesResult: { +newUpdates: $ReadOnlyArray, }, }; export type ThreadChanges = Partial<{ +type: ThreadType, +name: string, +description: string, +color: string, +parentThreadID: ?string, +newMemberIDs: $ReadOnlyArray, +avatar: UpdateUserAvatarRequest, }>; export type UpdateThreadRequest = { +threadID: string, +changes: ThreadChanges, +accountPassword?: empty, }; export type BaseNewThreadRequest = { +id?: ?string, +name?: ?string, +description?: ?string, +color?: ?string, +parentThreadID?: ?string, +initialMemberIDs?: ?$ReadOnlyArray, +ghostMemberIDs?: ?$ReadOnlyArray, }; type NewThreadRequest = | { +type: 3 | 4 | 6 | 7 | 8 | 9 | 10 | 11 | 12, ...BaseNewThreadRequest, } | { +type: 5, +sourceMessageID: string, ...BaseNewThreadRequest, }; export type ClientNewThreadRequest = { ...NewThreadRequest, +calendarQuery: CalendarQuery, }; export type ServerNewThreadRequest = { ...NewThreadRequest, +calendarQuery?: ?CalendarQuery, }; export type NewThreadResponse = { +updatesResult: { +newUpdates: $ReadOnlyArray, }, +newMessageInfos: $ReadOnlyArray, +userInfos: UserInfos, +newThreadID: string, }; export type NewThreadResult = { +updatesResult: { +newUpdates: $ReadOnlyArray, }, +newMessageInfos: $ReadOnlyArray, +userInfos: UserInfos, +newThreadID: string, }; export type ServerThreadJoinRequest = { +threadID: string, +calendarQuery?: ?CalendarQuery, +inviteLinkSecret?: string, }; export type ClientThreadJoinRequest = { +threadID: string, +calendarQuery: CalendarQuery, +inviteLinkSecret?: string, }; export type ThreadJoinResult = { +updatesResult: { +newUpdates: $ReadOnlyArray, }, +rawMessageInfos: $ReadOnlyArray, +truncationStatuses: MessageTruncationStatuses, +userInfos: UserInfos, }; export type ThreadJoinPayload = { +updatesResult: { newUpdates: $ReadOnlyArray, }, +rawMessageInfos: $ReadOnlyArray, +truncationStatuses: MessageTruncationStatuses, +userInfos: $ReadOnlyArray, }; export type ThreadFetchMediaResult = { +media: $ReadOnlyArray, }; export type ThreadFetchMediaRequest = { +threadID: string, +limit: number, +offset: number, }; export type SidebarInfo = { +threadInfo: ThreadInfo, +lastUpdatedTime: number, +mostRecentNonLocalMessage: ?string, }; export type ToggleMessagePinRequest = { +messageID: string, +action: 'pin' | 'unpin', }; export type ToggleMessagePinResult = { +newMessageInfos: $ReadOnlyArray, +threadID: string, }; type CreateRoleAction = { +community: string, +name: string, +permissions: $ReadOnlyArray, +action: 'create_role', }; type EditRoleAction = { +community: string, +existingRoleID: string, +name: string, +permissions: $ReadOnlyArray, +action: 'edit_role', }; export type RoleModificationRequest = CreateRoleAction | EditRoleAction; export type RoleModificationResult = { +threadInfo: LegacyRawThreadInfo | RawThreadInfo, +updatesResult: { +newUpdates: $ReadOnlyArray, }, }; export type RoleModificationPayload = { +threadInfo: LegacyRawThreadInfo | RawThreadInfo, +updatesResult: { +newUpdates: $ReadOnlyArray, }, }; export type RoleDeletionRequest = { +community: string, +roleID: string, }; export type RoleDeletionResult = { +threadInfo: LegacyRawThreadInfo | RawThreadInfo, +updatesResult: { +newUpdates: $ReadOnlyArray, }, }; export type RoleDeletionPayload = { +threadInfo: LegacyRawThreadInfo | RawThreadInfo, +updatesResult: { +newUpdates: $ReadOnlyArray, }, }; // We can show a max of 3 sidebars inline underneath their parent in the chat // tab. If there are more, we show a button that opens a modal to see the rest export const maxReadSidebars = 3; // We can show a max of 5 sidebars inline underneath their parent // in the chat tab if every one of the displayed sidebars is unread export const maxUnreadSidebars = 5; export type ThreadStoreThreadInfos = LegacyRawThreadInfos; export type ChatMentionCandidate = { +threadInfo: ResolvedThreadInfo, +rawChatName: string | ThreadEntity, }; export type ChatMentionCandidates = { +[id: string]: ChatMentionCandidate, }; export type ChatMentionCandidatesObj = { +[id: string]: ChatMentionCandidates, }; export type UserProfileThreadInfo = { +threadInfo: ThreadInfo, +pendingPersonalThreadUserInfo?: UserInfo, }; diff --git a/lib/types/user-types.js b/lib/types/user-types.js index a5bb3711b..4d9247161 100644 --- a/lib/types/user-types.js +++ b/lib/types/user-types.js @@ -1,120 +1,120 @@ // @flow import t, { type TInterface, type TDict, type TUnion } from 'tcomb'; import { type DefaultNotificationPayload, defaultNotificationPayloadValidator, } from './account-types.js'; import { type ClientAvatar, clientAvatarValidator } from './avatar-types.js'; import { type UserRelationshipStatus, userRelationshipStatusValidator, } from './relationship-types.js'; import { tBool, tShape, tUserID } from '../utils/validation-utils.js'; export type GlobalUserInfo = { +id: string, +username: ?string, +avatar?: ?ClientAvatar, }; export type GlobalAccountUserInfo = { +id: string, +username: string, +avatar?: ?ClientAvatar, }; export const globalAccountUserInfoValidator: TInterface = tShape({ id: t.String, username: t.String, avatar: t.maybe(clientAvatarValidator), }); export type UserInfo = { +id: string, +username: ?string, +relationshipStatus?: UserRelationshipStatus, +avatar?: ?ClientAvatar, }; export const userInfoValidator: TInterface = tShape({ - id: t.String, + id: tUserID, username: t.maybe(t.String), relationshipStatus: t.maybe(userRelationshipStatusValidator), avatar: t.maybe(clientAvatarValidator), }); export type UserInfos = { +[id: string]: UserInfo }; export const userInfosValidator: TDict = t.dict( t.String, userInfoValidator, ); export type AccountUserInfo = { +id: string, +username: string, +relationshipStatus?: UserRelationshipStatus, +avatar?: ?ClientAvatar, }; export const accountUserInfoValidator: TInterface = tShape({ id: tUserID, username: t.String, relationshipStatus: t.maybe(userRelationshipStatusValidator), avatar: t.maybe(clientAvatarValidator), }); export type UserStore = { +userInfos: UserInfos, }; export type RelativeUserInfo = { +id: string, +username: ?string, +isViewer: boolean, +avatar?: ?ClientAvatar, }; export type LoggedInUserInfo = { +id: string, +username: string, +settings?: DefaultNotificationPayload, +avatar?: ?ClientAvatar, }; export const loggedInUserInfoValidator: TInterface = tShape({ - id: t.String, + id: tUserID, username: t.String, settings: t.maybe(defaultNotificationPayloadValidator), avatar: t.maybe(clientAvatarValidator), }); export type LoggedOutUserInfo = { +anonymous: true, }; export const loggedOutUserInfoValidator: TInterface = tShape({ anonymous: tBool(true) }); export type CurrentUserInfo = LoggedInUserInfo | LoggedOutUserInfo; export const currentUserInfoValidator: TUnion = t.union([ loggedInUserInfoValidator, loggedOutUserInfoValidator, ]); export type PasswordUpdate = { +updatedFields: { +password?: ?string, +email?: empty, }, +currentPassword: string, }; export type UserListItem = { ...AccountUserInfo, +disabled?: boolean, +notice?: string, +alert?: { +text: string, +title: string, }, +avatar?: ?ClientAvatar, };