diff --git a/lib/permissions/minimally-encoded-thread-permissions-validators.js b/lib/permissions/minimally-encoded-thread-permissions-validators.js index 504e4b46f..ad7a49670 100644 --- a/lib/permissions/minimally-encoded-thread-permissions-validators.js +++ b/lib/permissions/minimally-encoded-thread-permissions-validators.js @@ -1,97 +1,99 @@ // @flow import t, { type TInterface, type TUnion } from 'tcomb'; import { tHexEncodedPermissionsBitmask, tHexEncodedRolePermission, } from './minimally-encoded-thread-permissions.js'; +import { specialRoleValidator } from './special-roles.js'; import { clientAvatarValidator } from '../types/avatar-types.js'; import type { MemberInfo, ThreadCurrentUserInfo, RawThreadInfo, RelativeMemberInfo, RoleInfo, ThreadInfo, } from '../types/minimally-encoded-thread-permissions-types.js'; import { threadTypeValidator } from '../types/thread-types-enum.js'; import { legacyMemberInfoValidator, legacyRawThreadInfoValidator, legacyRoleInfoValidator, legacyThreadCurrentUserInfoValidator, } from '../types/thread-types.js'; import type { LegacyRawThreadInfo } from '../types/thread-types.js'; import { threadEntityValidator } from '../utils/entity-text.js'; import { tBool, tID, tShape } from '../utils/validation-utils.js'; const roleInfoValidator: TInterface = tShape({ ...legacyRoleInfoValidator.meta.props, minimallyEncoded: tBool(true), permissions: t.list(tHexEncodedRolePermission), + specialRole: t.maybe(specialRoleValidator), }); const threadCurrentUserInfoValidator: TInterface = tShape({ ...legacyThreadCurrentUserInfoValidator.meta.props, minimallyEncoded: tBool(true), permissions: tHexEncodedPermissionsBitmask, }); const MemberInfoValidator: TInterface = tShape({ ...legacyMemberInfoValidator.meta.props, minimallyEncoded: tBool(true), permissions: tHexEncodedPermissionsBitmask, }); const relativeMemberInfoValidator: TInterface = tShape({ ...MemberInfoValidator.meta.props, username: t.maybe(t.String), isViewer: t.Boolean, }); const threadInfoValidator: TInterface = tShape({ minimallyEncoded: tBool(true), id: tID, type: threadTypeValidator, name: t.maybe(t.String), uiName: t.union([t.String, threadEntityValidator]), 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(relativeMemberInfoValidator), roles: t.dict(tID, roleInfoValidator), currentUser: threadCurrentUserInfoValidator, sourceMessageID: t.maybe(tID), repliesCount: t.Number, pinnedCount: t.maybe(t.Number), }); const rawThreadInfoValidator: TInterface = tShape( { ...legacyRawThreadInfoValidator.meta.props, minimallyEncoded: tBool(true), members: t.list(MemberInfoValidator), roles: t.dict(tID, roleInfoValidator), currentUser: threadCurrentUserInfoValidator, }, ); export const mixedRawThreadInfoValidator: TUnion< LegacyRawThreadInfo | RawThreadInfo, > = t.union([legacyRawThreadInfoValidator, rawThreadInfoValidator]); export { roleInfoValidator, threadCurrentUserInfoValidator, MemberInfoValidator, relativeMemberInfoValidator, threadInfoValidator, rawThreadInfoValidator, }; diff --git a/lib/permissions/special-roles.js b/lib/permissions/special-roles.js index 89c296bca..63fe82195 100644 --- a/lib/permissions/special-roles.js +++ b/lib/permissions/special-roles.js @@ -1,11 +1,21 @@ // @flow +import type { TRefinement } from 'tcomb'; + +import { values } from '../utils/objects.js'; +import { tNumEnum } from '../utils/validation-utils.js'; + export const specialRoles = Object.freeze({ DEFAULT_ROLE: 1, ADMIN_ROLE: 2, }); +export type SpecialRole = $Values; +export const specialRoleValidator: TRefinement = tNumEnum( + values(specialRoles), +); + export const defaultSpecialRoles = Object.freeze({ Members: specialRoles.DEFAULT_ROLE, Admins: specialRoles.ADMIN_ROLE, }); diff --git a/lib/types/minimally-encoded-thread-permissions-types.js b/lib/types/minimally-encoded-thread-permissions-types.js index 9d71f8bb3..3c32ec757 100644 --- a/lib/types/minimally-encoded-thread-permissions-types.js +++ b/lib/types/minimally-encoded-thread-permissions-types.js @@ -1,195 +1,197 @@ // @flow import invariant from 'invariant'; import _mapValues from 'lodash/fp/mapValues.js'; import type { ClientAvatar } from './avatar-types.js'; import type { ThreadType } from './thread-types-enum.js'; import type { LegacyMemberInfo, LegacyRawThreadInfo, LegacyRoleInfo, LegacyThreadCurrentUserInfo, } from './thread-types.js'; import { decodeThreadRolePermissionsBitmaskArray, permissionsToBitmaskHex, threadPermissionsFromBitmaskHex, threadRolePermissionsBlobToBitmaskArray, } from '../permissions/minimally-encoded-thread-permissions.js'; +import type { SpecialRole } from '../permissions/special-roles.js'; import type { ThreadEntity } from '../utils/entity-text.js'; export type RoleInfo = $ReadOnly<{ ...LegacyRoleInfo, +minimallyEncoded: true, +permissions: $ReadOnlyArray, + +specialRole?: ?SpecialRole, }>; const minimallyEncodeRoleInfo = (roleInfo: LegacyRoleInfo): RoleInfo => { invariant( !('minimallyEncoded' in roleInfo), 'roleInfo is already minimally encoded.', ); return { ...roleInfo, minimallyEncoded: true, permissions: threadRolePermissionsBlobToBitmaskArray(roleInfo.permissions), }; }; const decodeMinimallyEncodedRoleInfo = ( minimallyEncodedRoleInfo: RoleInfo, ): LegacyRoleInfo => { - const { minimallyEncoded, ...rest } = minimallyEncodedRoleInfo; + const { minimallyEncoded, specialRole, ...rest } = minimallyEncodedRoleInfo; return { ...rest, permissions: decodeThreadRolePermissionsBitmaskArray( minimallyEncodedRoleInfo.permissions, ), }; }; export type ThreadCurrentUserInfo = $ReadOnly<{ ...LegacyThreadCurrentUserInfo, +minimallyEncoded: true, +permissions: string, }>; const minimallyEncodeThreadCurrentUserInfo = ( threadCurrentUserInfo: LegacyThreadCurrentUserInfo, ): ThreadCurrentUserInfo => { invariant( !('minimallyEncoded' in threadCurrentUserInfo), 'threadCurrentUserInfo is already minimally encoded.', ); return { ...threadCurrentUserInfo, minimallyEncoded: true, permissions: permissionsToBitmaskHex(threadCurrentUserInfo.permissions), }; }; const decodeMinimallyEncodedThreadCurrentUserInfo = ( minimallyEncodedThreadCurrentUserInfo: ThreadCurrentUserInfo, ): LegacyThreadCurrentUserInfo => { const { minimallyEncoded, ...rest } = minimallyEncodedThreadCurrentUserInfo; return { ...rest, permissions: threadPermissionsFromBitmaskHex( minimallyEncodedThreadCurrentUserInfo.permissions, ), }; }; export type MemberInfo = $ReadOnly<{ ...LegacyMemberInfo, +minimallyEncoded: true, +permissions: string, }>; const minimallyEncodeMemberInfo = ( memberInfo: LegacyMemberInfo, ): MemberInfo => { invariant( !('minimallyEncoded' in memberInfo), 'memberInfo is already minimally encoded.', ); return { ...memberInfo, minimallyEncoded: true, permissions: permissionsToBitmaskHex(memberInfo.permissions), }; }; const decodeMinimallyEncodedMemberInfo = ( minimallyEncodedMemberInfo: MemberInfo, ): LegacyMemberInfo => { const { minimallyEncoded, ...rest } = minimallyEncodedMemberInfo; return { ...rest, permissions: threadPermissionsFromBitmaskHex( minimallyEncodedMemberInfo.permissions, ), }; }; export type RelativeMemberInfo = $ReadOnly<{ ...MemberInfo, +username: ?string, +isViewer: boolean, }>; export type RawThreadInfo = $ReadOnly<{ ...LegacyRawThreadInfo, +minimallyEncoded: true, +members: $ReadOnlyArray, +roles: { +[id: string]: RoleInfo }, +currentUser: ThreadCurrentUserInfo, }>; const minimallyEncodeRawThreadInfo = ( rawThreadInfo: LegacyRawThreadInfo, ): RawThreadInfo => { invariant( !('minimallyEncoded' in rawThreadInfo), 'rawThreadInfo is already minimally encoded.', ); const { members, roles, currentUser, ...rest } = rawThreadInfo; return { ...rest, minimallyEncoded: true, members: members.map(minimallyEncodeMemberInfo), roles: _mapValues(minimallyEncodeRoleInfo)(roles), currentUser: minimallyEncodeThreadCurrentUserInfo(currentUser), }; }; const decodeMinimallyEncodedRawThreadInfo = ( minimallyEncodedRawThreadInfo: RawThreadInfo, ): LegacyRawThreadInfo => { const { minimallyEncoded, members, roles, currentUser, ...rest } = minimallyEncodedRawThreadInfo; return { ...rest, members: members.map(decodeMinimallyEncodedMemberInfo), roles: _mapValues(decodeMinimallyEncodedRoleInfo)(roles), currentUser: decodeMinimallyEncodedThreadCurrentUserInfo(currentUser), }; }; export type ThreadInfo = $ReadOnly<{ +minimallyEncoded: true, +id: string, +type: ThreadType, +name: ?string, +uiName: string | ThreadEntity, +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]: RoleInfo }, +currentUser: ThreadCurrentUserInfo, +sourceMessageID?: string, +repliesCount: number, +pinnedCount?: number, }>; export type ResolvedThreadInfo = $ReadOnly<{ ...ThreadInfo, +uiName: string, }>; export { minimallyEncodeRoleInfo, decodeMinimallyEncodedRoleInfo, minimallyEncodeThreadCurrentUserInfo, decodeMinimallyEncodedThreadCurrentUserInfo, minimallyEncodeMemberInfo, decodeMinimallyEncodedMemberInfo, minimallyEncodeRawThreadInfo, decodeMinimallyEncodedRawThreadInfo, };