Page MenuHomePhabricator

D7569.diff
No OneTemporary

D7569.diff

diff --git a/lib/types/avatar-types.js b/lib/types/avatar-types.js
--- a/lib/types/avatar-types.js
+++ b/lib/types/avatar-types.js
@@ -1,12 +1,20 @@
// @flow
+import t, { type TUnion } from 'tcomb';
+
import type { CreateUpdatesResult } from './update-types.js';
+import { tShape, tString } from '../utils/validation-utils.js';
export type EmojiAvatarDBContent = {
+type: 'emoji',
+emoji: string,
+color: string, // hex, without "#" or "0x"
};
+const emojiAvatarDBContentValidator = tShape<EmojiAvatarDBContent>({
+ type: tString('emoji'),
+ emoji: t.String,
+ color: t.String,
+});
export type ImageAvatarDBContent = {
+type: 'image',
@@ -16,6 +24,7 @@
export type ENSAvatarDBContent = {
+type: 'ens',
};
+const ensAvatarDBContentValidator = tShape({ type: tString('ens') });
export type AvatarDBContent =
| EmojiAvatarDBContent
@@ -29,16 +38,29 @@
| UpdateUserAvatarRemoveRequest;
export type ClientEmojiAvatar = EmojiAvatarDBContent;
+const clientEmojiAvatarValidator = emojiAvatarDBContentValidator;
+
export type ClientImageAvatar = {
+type: 'image',
+uri: string,
};
+const clientImageAvatarValidator = tShape<ClientImageAvatar>({
+ type: tString('image'),
+ uri: t.String,
+});
+
export type ClientENSAvatar = ENSAvatarDBContent;
+const clientENSAvatarValidator = ensAvatarDBContentValidator;
export type ClientAvatar =
| ClientEmojiAvatar
| ClientImageAvatar
| ClientENSAvatar;
+export const clientAvatarValidator: TUnion<ClientAvatar> = t.union([
+ clientEmojiAvatarValidator,
+ clientImageAvatarValidator,
+ clientENSAvatarValidator,
+]);
export type ResolvedClientAvatar = ClientEmojiAvatar | ClientImageAvatar;
diff --git a/lib/types/subscription-types.js b/lib/types/subscription-types.js
--- a/lib/types/subscription-types.js
+++ b/lib/types/subscription-types.js
@@ -1,6 +1,10 @@
// @flow
+import _mapValues from 'lodash/fp/mapValues.js';
+import t, { type TInterface } from 'tcomb';
+
import type { Shape } from './core.js';
+import { tShape } from '../utils/validation-utils.js';
export const threadSubscriptions = Object.freeze({
home: 'home',
@@ -12,6 +16,9 @@
() => boolean,
>;
+export const threadSubscriptionValidator: TInterface<ThreadSubscription> =
+ tShape<ThreadSubscription>(_mapValues(() => t.Boolean)(threadSubscriptions));
+
export type SubscriptionUpdateRequest = {
threadID: string,
updatedFields: Shape<ThreadSubscription>,
diff --git a/lib/types/thread-types.js b/lib/types/thread-types.js
--- a/lib/types/thread-types.js
+++ b/lib/types/thread-types.js
@@ -1,11 +1,13 @@
// @flow
import invariant from 'invariant';
+import t, { type TInterface } from 'tcomb';
-import type {
- AvatarDBContent,
- ClientAvatar,
- UpdateUserAvatarRequest,
+import {
+ type AvatarDBContent,
+ type ClientAvatar,
+ type UpdateUserAvatarRequest,
+ clientAvatarValidator,
} from './avatar-types.js';
import type { Shape } from './core.js';
import type { CalendarQuery, RawEntryInfo } from './entry-types.js';
@@ -14,10 +16,15 @@
RawMessageInfo,
MessageTruncationStatuses,
} from './message-types.js';
-import type { ThreadSubscription } from './subscription-types.js';
+import {
+ type ThreadSubscription,
+ threadSubscriptionValidator,
+} from './subscription-types.js';
import type { ServerUpdateInfo, ClientUpdateInfo } from './update-types.js';
import type { UserInfo, UserInfos } from './user-types.js';
import type { ThreadEntity } from '../utils/entity-text.js';
+import { values } from '../utils/objects.js';
+import { tNumEnum, tBool, tID, tShape } from '../utils/validation-utils.js';
export const threadTypes = Object.freeze({
//OPEN: 0, (DEPRECATED)
@@ -68,6 +75,8 @@
);
return threadType;
}
+const threadTypeValidator = tNumEnum(values(threadTypes));
+
export const communityThreadTypes: $ReadOnlyArray<number> = Object.freeze([
threadTypes.COMMUNITY_ROOT,
threadTypes.COMMUNITY_ANNOUNCEMENT_ROOT,
@@ -138,6 +147,7 @@
);
return ourThreadPermissions;
}
+const threadPermissionValidator = t.enums.of(values(threadPermissions));
export const threadPermissionPropagationPrefixes = Object.freeze({
DESCENDANT: 'descendant_',
@@ -165,14 +175,24 @@
| { +value: true, +source: string }
| { +value: false, +source: null };
+const threadPermissionInfoValidator = t.union([
+ tShape({ value: tBool(true), source: t.String }),
+ tShape({ value: tBool(false), source: t.Nil }),
+]);
+
export type ThreadPermissionsBlob = {
+[permission: string]: ThreadPermissionInfo,
};
export type ThreadRolePermissionsBlob = { +[permission: string]: boolean };
+const threadRolePermissionsBlobValidator = t.dict(t.String, t.Boolean);
export type ThreadPermissionsInfo = {
+[permission: ThreadPermission]: ThreadPermissionInfo,
};
+const threadPermissionsInfoValidator = t.dict(
+ threadPermissionValidator,
+ threadPermissionInfoValidator,
+);
export type MemberInfo = {
+id: string,
@@ -180,6 +200,12 @@
+permissions: ThreadPermissionsInfo,
+isSender: boolean,
};
+const memberInfoValidator = tShape<MemberInfo>({
+ id: t.String,
+ role: t.maybe(tID),
+ permissions: threadPermissionsInfoValidator,
+ isSender: t.Boolean,
+});
export type RelativeMemberInfo = {
...MemberInfo,
@@ -193,6 +219,12 @@
+permissions: ThreadRolePermissionsBlob,
+isDefault: boolean,
};
+const roleInfoValidator = tShape<RoleInfo>({
+ id: tID,
+ name: t.String,
+ permissions: threadRolePermissionsBlobValidator,
+ isDefault: t.Boolean,
+});
export type ThreadCurrentUserInfo = {
+role: ?string,
@@ -200,6 +232,12 @@
+subscription: ThreadSubscription,
+unread: ?boolean,
};
+const threadCurrentUserInfoValidator = tShape<ThreadCurrentUserInfo>({
+ role: t.maybe(tID),
+ permissions: threadPermissionsInfoValidator,
+ subscription: threadSubscriptionValidator,
+ unread: t.maybe(t.Boolean),
+});
export type RawThreadInfo = {
+id: string,
@@ -219,6 +257,25 @@
+repliesCount: number,
+pinnedCount?: number,
};
+export const rawThreadInfoValidator: TInterface<RawThreadInfo> =
+ tShape<RawThreadInfo>({
+ 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(memberInfoValidator),
+ roles: t.dict(tID, roleInfoValidator),
+ currentUser: threadCurrentUserInfoValidator,
+ sourceMessageID: t.maybe(tID),
+ repliesCount: t.Number,
+ pinnedCount: t.maybe(t.Number),
+ });
export type ThreadInfo = {
+id: string,
diff --git a/lib/types/validation.test.js b/lib/types/validation.test.js
--- a/lib/types/validation.test.js
+++ b/lib/types/validation.test.js
@@ -8,6 +8,7 @@
mediaValidator,
} from './media-types.js';
import { messageTypes } from './message-types-enum.js';
+import { threadTypes, rawThreadInfoValidator } from './thread-types.js';
import { messageSpecs } from '../shared/messages/message-specs.js';
describe('media validation', () => {
@@ -319,3 +320,316 @@
}
}
});
+
+describe('thread validation', () => {
+ const thread = {
+ id: '85171',
+ type: threadTypes.PERSONAL,
+ name: '',
+ description: '',
+ color: '6d49ab',
+ creationTime: 1675887298557,
+ parentThreadID: '1',
+ members: [
+ {
+ id: '256',
+ role: null,
+ permissions: {
+ know_of: {
+ value: true,
+ source: '1',
+ },
+ membership: {
+ value: false,
+ source: null,
+ },
+ visible: {
+ value: true,
+ source: '1',
+ },
+ voiced: {
+ value: true,
+ source: '1',
+ },
+ edit_entries: {
+ value: true,
+ source: '1',
+ },
+ edit_thread: {
+ value: true,
+ source: '1',
+ },
+ edit_thread_description: {
+ value: true,
+ source: '1',
+ },
+ edit_thread_color: {
+ value: true,
+ source: '1',
+ },
+ delete_thread: {
+ value: true,
+ source: '1',
+ },
+ create_subthreads: {
+ value: true,
+ source: '1',
+ },
+ create_sidebars: {
+ value: true,
+ source: '1',
+ },
+ join_thread: {
+ value: true,
+ source: '1',
+ },
+ edit_permissions: {
+ value: true,
+ source: '1',
+ },
+ add_members: {
+ value: true,
+ source: '1',
+ },
+ remove_members: {
+ value: true,
+ source: '1',
+ },
+ change_role: {
+ value: true,
+ source: '1',
+ },
+ leave_thread: {
+ value: false,
+ source: null,
+ },
+ react_to_message: {
+ value: false,
+ source: null,
+ },
+ edit_message: {
+ value: false,
+ source: null,
+ },
+ manage_pins: {
+ value: true,
+ source: '1',
+ },
+ },
+ isSender: false,
+ },
+ {
+ id: '83853',
+ role: '85172',
+ permissions: {
+ know_of: {
+ value: true,
+ source: '85171',
+ },
+ membership: {
+ value: false,
+ source: null,
+ },
+ visible: {
+ value: true,
+ source: '85171',
+ },
+ voiced: {
+ value: true,
+ source: '85171',
+ },
+ edit_entries: {
+ value: true,
+ source: '85171',
+ },
+ edit_thread: {
+ value: true,
+ source: '85171',
+ },
+ edit_thread_description: {
+ value: true,
+ source: '85171',
+ },
+ edit_thread_color: {
+ value: true,
+ source: '85171',
+ },
+ delete_thread: {
+ value: false,
+ source: null,
+ },
+ create_subthreads: {
+ value: false,
+ source: null,
+ },
+ create_sidebars: {
+ value: true,
+ source: '85171',
+ },
+ join_thread: {
+ value: false,
+ source: null,
+ },
+ edit_permissions: {
+ value: false,
+ source: null,
+ },
+ add_members: {
+ value: false,
+ source: null,
+ },
+ remove_members: {
+ value: false,
+ source: null,
+ },
+ change_role: {
+ value: false,
+ source: null,
+ },
+ leave_thread: {
+ value: false,
+ source: null,
+ },
+ react_to_message: {
+ value: true,
+ source: '85171',
+ },
+ edit_message: {
+ value: true,
+ source: '85171',
+ },
+ manage_pins: {
+ value: false,
+ source: null,
+ },
+ },
+ isSender: true,
+ },
+ ],
+ roles: {
+ '85172': {
+ id: '85172',
+ name: 'Members',
+ permissions: {
+ know_of: true,
+ visible: true,
+ voiced: true,
+ react_to_message: true,
+ edit_message: true,
+ edit_entries: true,
+ edit_thread: true,
+ edit_thread_color: true,
+ edit_thread_description: true,
+ create_sidebars: true,
+ descendant_open_know_of: true,
+ descendant_open_visible: true,
+ child_open_join_thread: true,
+ },
+ isDefault: true,
+ },
+ },
+ currentUser: {
+ role: '85172',
+ permissions: {
+ know_of: {
+ value: true,
+ source: '85171',
+ },
+ membership: {
+ value: false,
+ source: null,
+ },
+ visible: {
+ value: true,
+ source: '85171',
+ },
+ voiced: {
+ value: true,
+ source: '85171',
+ },
+ edit_entries: {
+ value: true,
+ source: '85171',
+ },
+ edit_thread: {
+ value: true,
+ source: '85171',
+ },
+ edit_thread_description: {
+ value: true,
+ source: '85171',
+ },
+ edit_thread_color: {
+ value: true,
+ source: '85171',
+ },
+ delete_thread: {
+ value: false,
+ source: null,
+ },
+ create_subthreads: {
+ value: false,
+ source: null,
+ },
+ create_sidebars: {
+ value: true,
+ source: '85171',
+ },
+ join_thread: {
+ value: false,
+ source: null,
+ },
+ edit_permissions: {
+ value: false,
+ source: null,
+ },
+ add_members: {
+ value: false,
+ source: null,
+ },
+ remove_members: {
+ value: false,
+ source: null,
+ },
+ change_role: {
+ value: false,
+ source: null,
+ },
+ leave_thread: {
+ value: false,
+ source: null,
+ },
+ react_to_message: {
+ value: true,
+ source: '85171',
+ },
+ edit_message: {
+ value: true,
+ source: '85171',
+ },
+ manage_pins: {
+ value: false,
+ source: null,
+ },
+ },
+ subscription: {
+ home: true,
+ pushNotifs: true,
+ },
+ unread: false,
+ },
+ repliesCount: 0,
+ containingThreadID: '1',
+ community: '1',
+ pinnedCount: 0,
+ };
+
+ it('should validate correct thread', () => {
+ expect(rawThreadInfoValidator.is(thread)).toBe(true);
+ });
+ it('should not validate incorrect thread', () => {
+ expect(
+ rawThreadInfoValidator.is({ ...thread, creationTime: undefined }),
+ ).toBe(false);
+ });
+});

File Metadata

Mime Type
text/plain
Expires
Sat, Nov 30, 6:20 AM (21 h, 33 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
2599913
Default Alt Text
D7569.diff (14 KB)

Event Timeline