forked from Ivasoft/mattermost-mobile
[Gekidou] fix database schema and models (#5553)
* fix database schema and models * fix types
This commit is contained in:
@@ -190,7 +190,7 @@ export const loadMe = async (serverUrl: string, {deviceToken, user}: LoadMeArgs)
|
||||
|
||||
const myTeams = teamUnreads.map((unread) => {
|
||||
const matchingTeam = teamMemberships.find((team) => team.team_id === unread.team_id);
|
||||
return {team_id: unread.team_id, roles: matchingTeam?.roles ?? '', is_unread: unread.msg_count > 0, mentions_count: unread.mention_count};
|
||||
return {id: unread.team_id, roles: matchingTeam?.roles ?? '', is_unread: unread.msg_count > 0, mentions_count: unread.mention_count};
|
||||
});
|
||||
|
||||
const myTeamRecords = operator.handleMyTeam({
|
||||
|
||||
16
app/constants/action_type.ts
Normal file
16
app/constants/action_type.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import keyMirror from '@utils/key_mirror';
|
||||
|
||||
export const POSTS = keyMirror({
|
||||
RECEIVED_IN_CHANNEL: null,
|
||||
RECEIVED_SINCE: null,
|
||||
RECEIVED_AFTER: null,
|
||||
RECEIVED_BEFORE: null,
|
||||
RECEIVED_NEW: null,
|
||||
});
|
||||
|
||||
export default {
|
||||
POSTS,
|
||||
};
|
||||
@@ -9,6 +9,7 @@ export default {
|
||||
LOGS_PAGE_SIZE_DEFAULT: 10000,
|
||||
PROFILE_CHUNK_SIZE: 100,
|
||||
CHANNELS_CHUNK_SIZE: 50,
|
||||
POST_CHUNK_SIZE: 60,
|
||||
TEAMS_CHUNK_SIZE: 50,
|
||||
SEARCH_TIMEOUT_MILLISECONDS: 100,
|
||||
STATUS_INTERVAL: 60000,
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import ActionType from './action_type';
|
||||
import Attachment from './attachment';
|
||||
import Database from './database';
|
||||
import Device from './device';
|
||||
@@ -9,6 +10,7 @@ import General from './general';
|
||||
import List from './list';
|
||||
import Navigation from './navigation';
|
||||
import Network from './network';
|
||||
import Permissions from './permissions';
|
||||
import Preferences from './preferences';
|
||||
import Screens from './screens';
|
||||
import SSO, {REDIRECT_URL_SCHEME, REDIRECT_URL_SCHEME_DEV} from './sso';
|
||||
@@ -16,6 +18,7 @@ import View, {Upgrade} from './view';
|
||||
import WebsocketEvents from './websocket';
|
||||
|
||||
export {
|
||||
ActionType,
|
||||
Attachment,
|
||||
Database,
|
||||
Device,
|
||||
@@ -24,6 +27,7 @@ export {
|
||||
List,
|
||||
Navigation,
|
||||
Network,
|
||||
Permissions,
|
||||
Preferences,
|
||||
REDIRECT_URL_SCHEME,
|
||||
REDIRECT_URL_SCHEME_DEV,
|
||||
|
||||
84
app/constants/permissions.ts
Normal file
84
app/constants/permissions.ts
Normal file
@@ -0,0 +1,84 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
export default {
|
||||
INVITE_USER: 'invite_user',
|
||||
ADD_USER_TO_TEAM: 'add_user_to_team',
|
||||
USE_SLASH_COMMANDS: 'use_slash_commands',
|
||||
MANAGE_SLASH_COMMANDS: 'manage_slash_commands',
|
||||
MANAGE_OTHERS_SLASH_COMMANDS: 'manage_others_slash_commands',
|
||||
CREATE_PUBLIC_CHANNEL: 'create_public_channel',
|
||||
CREATE_PRIVATE_CHANNEL: 'create_private_channel',
|
||||
MANAGE_PUBLIC_CHANNEL_MEMBERS: 'manage_public_channel_members',
|
||||
MANAGE_PRIVATE_CHANNEL_MEMBERS: 'manage_private_channel_members',
|
||||
ASSIGN_SYSTEM_ADMIN_ROLE: 'assign_system_admin_role',
|
||||
MANAGE_ROLES: 'manage_roles',
|
||||
MANAGE_TEAM_ROLES: 'manage_team_roles',
|
||||
MANAGE_CHANNEL_ROLES: 'manage_channel_roles',
|
||||
MANAGE_SYSTEM: 'manage_system',
|
||||
CREATE_DIRECT_CHANNEL: 'create_direct_channel',
|
||||
CREATE_GROUP_CHANNEL: 'create_group_channel',
|
||||
MANAGE_PUBLIC_CHANNEL_PROPERTIES: 'manage_public_channel_properties',
|
||||
MANAGE_PRIVATE_CHANNEL_PROPERTIES: 'manage_private_channel_properties',
|
||||
LIST_PUBLIC_TEAMS: 'list_public_teams',
|
||||
JOIN_PUBLIC_TEAMS: 'join_public_teams',
|
||||
LIST_PRIVATE_TEAMS: 'list_private_teams',
|
||||
JOIN_PRIVATE_TEAMS: 'join_private_teams',
|
||||
LIST_TEAM_CHANNELS: 'list_team_channels',
|
||||
JOIN_PUBLIC_CHANNELS: 'join_public_channels',
|
||||
DELETE_PUBLIC_CHANNEL: 'delete_public_channel',
|
||||
CONVERT_PUBLIC_CHANNEL_TO_PRIVATE: 'convert_public_channel_to_private',
|
||||
DELETE_PRIVATE_CHANNEL: 'delete_private_channel',
|
||||
EDIT_OTHER_USERS: 'edit_other_users',
|
||||
READ_CHANNEL: 'read_channel',
|
||||
READ_PUBLIC_CHANNEL: 'read_public_channel',
|
||||
ADD_REACTION: 'add_reaction',
|
||||
REMOVE_REACTION: 'remove_reaction',
|
||||
REMOVE_OTHERS_REACTIONS: 'remove_others_reactions',
|
||||
PERMANENT_DELETE_USER: 'permanent_delete_user',
|
||||
UPLOAD_FILE: 'upload_file',
|
||||
GET_PUBLIC_LINK: 'get_public_link',
|
||||
MANAGE_WEBHOOKS: 'manage_webhooks',
|
||||
MANAGE_OTHERS_WEBHOOKS: 'manage_others_webhooks',
|
||||
MANAGE_INCOMING_WEBHOOKS: 'manage_incoming_webhooks',
|
||||
MANAGE_OTHERS_INCOMING_WEBHOOKS: 'manage_others_incoming_webhooks',
|
||||
MANAGE_OUTGOING_WEBHOOKS: 'manage_outgoing_webhooks',
|
||||
MANAGE_OTHERS_OUTGOING_WEBHOOKS: 'manage_others_outgoing_webhooks',
|
||||
MANAGE_OAUTH: 'manage_oauth',
|
||||
MANAGE_SYSTEM_WIDE_OAUTH: 'manage_system_wide_oauth',
|
||||
CREATE_POST: 'create_post',
|
||||
CREATE_POST_PUBLIC: 'create_post_public',
|
||||
EDIT_POST: 'edit_post',
|
||||
EDIT_OTHERS_POSTS: 'edit_others_posts',
|
||||
DELETE_POST: 'delete_post',
|
||||
DELETE_OTHERS_POSTS: 'delete_others_posts',
|
||||
REMOVE_USER_FROM_TEAM: 'remove_user_from_team',
|
||||
CREATE_TEAM: 'create_team',
|
||||
MANAGE_TEAM: 'manage_team',
|
||||
IMPORT_TEAM: 'import_team',
|
||||
VIEW_TEAM: 'view_team',
|
||||
LIST_USERS_WITHOUT_TEAM: 'list_users_without_team',
|
||||
CREATE_USER_ACCESS_TOKEN: 'create_user_access_token',
|
||||
READ_USER_ACCESS_TOKEN: 'read_user_access_token',
|
||||
REVOKE_USER_ACCESS_TOKEN: 'revoke_user_access_token',
|
||||
MANAGE_JOBS: 'manage_jobs',
|
||||
MANAGE_EMOJIS: 'manage_emojis',
|
||||
MANAGE_OTHERS_EMOJIS: 'manage_others_emojis',
|
||||
CREATE_EMOJIS: 'create_emojis',
|
||||
DELETE_EMOJIS: 'delete_emojis',
|
||||
DELETE_OTHERS_EMOJIS: 'delete_others_emojis',
|
||||
VIEW_MEMBERS: 'view_members',
|
||||
INVITE_GUEST: 'invite_guest',
|
||||
PROMOTE_GUEST: 'promote_guest',
|
||||
DEMOTE_TO_GUEST: 'demote_to_guest',
|
||||
USE_CHANNEL_MENTIONS: 'use_channel_mentions',
|
||||
USE_GROUP_MENTIONS: 'use_group_mentions',
|
||||
|
||||
CHANNEL_MODERATED_PERMISSIONS: {
|
||||
CREATE_POST: 'create_post',
|
||||
CREATE_REACTIONS: 'create_reactions',
|
||||
MANAGE_MEMBERS: 'manage_members',
|
||||
USE_CHANNEL_MENTIONS: 'use_channel_mentions',
|
||||
},
|
||||
MANAGE_BOTS: 'manage_bots',
|
||||
MANAGE_OTHERS_BOTS: 'manage_others_bots',
|
||||
};
|
||||
@@ -1,7 +1,6 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {Platform} from 'react-native';
|
||||
import DeviceInfo from 'react-native-device-info';
|
||||
|
||||
import keyMirror from '@utils/key_mirror';
|
||||
@@ -111,7 +110,6 @@ const RequiredServer = {
|
||||
export default {
|
||||
...ViewTypes,
|
||||
RequiredServer,
|
||||
POST_VISIBILITY_CHUNK_SIZE: Platform.OS === 'android' ? 15 : 60,
|
||||
FEATURE_TOGGLE_PREFIX: 'feature_enabled_',
|
||||
EMBED_PREVIEW: 'embed_preview',
|
||||
LINK_PREVIEW_DISPLAY: 'link_previews',
|
||||
|
||||
@@ -61,7 +61,7 @@ export default class ChannelModel extends Model {
|
||||
[MY_CHANNEL_SETTINGS]: {type: 'has_many', foreignKey: 'channel_id'},
|
||||
|
||||
/** A CHANNEL can be associated with multiple POSTS_IN_CHANNEL (relationship is 1:N) */
|
||||
[POSTS_IN_CHANNEL]: {type: 'has_many', foreignKey: 'channel_id'},
|
||||
[POSTS_IN_CHANNEL]: {type: 'has_many', foreignKey: 'id'},
|
||||
|
||||
/** A CHANNEL can contain multiple POST (relationship is 1:N) */
|
||||
[POST]: {type: 'has_many', foreignKey: 'channel_id'},
|
||||
|
||||
@@ -24,12 +24,9 @@ export default class ChannelInfoModel extends Model {
|
||||
static associations: Associations = {
|
||||
|
||||
/** A CHANNEL is associated with only one CHANNEL_INFO (relationship is 1:1) */
|
||||
[CHANNEL]: {type: 'belongs_to', key: 'channel_id'},
|
||||
[CHANNEL]: {type: 'belongs_to', key: 'id'},
|
||||
};
|
||||
|
||||
/** channel_id : The foreign key from CHANNEL */
|
||||
@field('channel_id') channelId!: string;
|
||||
|
||||
/** guest_count : The number of guest in this channel */
|
||||
@field('guest_count') guestCount!: number;
|
||||
|
||||
@@ -46,5 +43,5 @@ export default class ChannelInfoModel extends Model {
|
||||
@field('purpose') purpose!: string;
|
||||
|
||||
/** channel : The lazy query property to the record from CHANNEL table */
|
||||
@immutableRelation(CHANNEL, 'channel_id') channel!: Relation<ChannelModel>;
|
||||
@immutableRelation(CHANNEL, 'id') channel!: Relation<ChannelModel>;
|
||||
}
|
||||
|
||||
@@ -22,12 +22,9 @@ export default class MyChannelModel extends Model {
|
||||
static associations: Associations = {
|
||||
|
||||
/** A CHANNEL can be associated to only one record from the MY_CHANNEL table (relationship is 1:1) */
|
||||
[CHANNEL]: {type: 'belongs_to', key: 'channel_id'},
|
||||
[CHANNEL]: {type: 'belongs_to', key: 'id'},
|
||||
};
|
||||
|
||||
/** channel_id : The foreign key to the related Channel record */
|
||||
@field('channel_id') channelId!: string;
|
||||
|
||||
/** last_post_at : The timestamp for any last post on this channel */
|
||||
@field('last_post_at') lastPostAt!: number;
|
||||
|
||||
@@ -44,5 +41,5 @@ export default class MyChannelModel extends Model {
|
||||
@field('roles') roles!: string;
|
||||
|
||||
/** channel : The relation pointing to the CHANNEL table */
|
||||
@immutableRelation(CHANNEL, 'channel_id') channel!: Relation<ChannelModel>;
|
||||
@immutableRelation(CHANNEL, 'id') channel!: Relation<ChannelModel>;
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {Relation} from '@nozbe/watermelondb';
|
||||
import {field, immutableRelation, json} from '@nozbe/watermelondb/decorators';
|
||||
import {immutableRelation, json} from '@nozbe/watermelondb/decorators';
|
||||
import Model, {Associations} from '@nozbe/watermelondb/Model';
|
||||
|
||||
import {MM_TABLES} from '@constants/database';
|
||||
@@ -24,15 +24,12 @@ export default class MyChannelSettingsModel extends Model {
|
||||
static associations: Associations = {
|
||||
|
||||
/** A CHANNEL is related to only one MY_CHANNEL_SETTINGS (relationship is 1:1) */
|
||||
[CHANNEL]: {type: 'belongs_to', key: 'channel_id'},
|
||||
[CHANNEL]: {type: 'belongs_to', key: 'id'},
|
||||
};
|
||||
|
||||
/** channel_id : The foreign key to the related CHANNEL record */
|
||||
@field('channel_id') channelId!: string;
|
||||
|
||||
/** notify_props : Configurations with regards to this channel */
|
||||
@json('notify_props', safeParseJSON) notifyProps!: ChannelNotifyProps;
|
||||
|
||||
/** channel : The relation pointing to the CHANNEL table */
|
||||
@immutableRelation(CHANNEL, 'channel_id') channel!: Relation<ChannelModel>;
|
||||
@immutableRelation(CHANNEL, 'id') channel!: Relation<ChannelModel>;
|
||||
}
|
||||
|
||||
@@ -22,7 +22,7 @@ export default class MyTeamModel extends Model {
|
||||
static associations: Associations = {
|
||||
|
||||
/** TEAM and MY_TEAM have a 1:1 relationship. */
|
||||
[TEAM]: {type: 'belongs_to', key: 'team_id'},
|
||||
[TEAM]: {type: 'belongs_to', key: 'id'},
|
||||
};
|
||||
|
||||
/** is_unread : Boolean flag for unread messages on team level */
|
||||
@@ -34,9 +34,6 @@ export default class MyTeamModel extends Model {
|
||||
/** roles : The different permissions that this user has in the team, concatenated together with comma to form a single string. */
|
||||
@field('roles') roles!: string;
|
||||
|
||||
/** team_id : The foreign key of the 'parent' Team record */
|
||||
@field('team_id') teamId!: string;
|
||||
|
||||
/** team : The relation to the TEAM, that this user belongs to */
|
||||
@relation(MY_TEAM, 'team_id') team!: Relation<TeamModel>;
|
||||
@relation(MY_TEAM, 'id') team!: Relation<TeamModel>;
|
||||
}
|
||||
|
||||
@@ -38,10 +38,10 @@ export default class PostModel extends Model {
|
||||
[FILE]: {type: 'has_many', foreignKey: 'post_id'},
|
||||
|
||||
/** A POST can have multiple POSTS_IN_THREAD. (relationship is 1:N)*/
|
||||
[POSTS_IN_THREAD]: {type: 'has_many', foreignKey: 'post_id'},
|
||||
[POSTS_IN_THREAD]: {type: 'has_many', foreignKey: 'id'},
|
||||
|
||||
/** A POST can have POST_METADATA. (relationship is 1:1)*/
|
||||
[POST_METADATA]: {type: 'has_many', foreignKey: 'post_id'},
|
||||
[POST_METADATA]: {type: 'has_many', foreignKey: 'id'},
|
||||
|
||||
/** A POST can have multiple REACTION. (relationship is 1:N)*/
|
||||
[REACTION]: {type: 'has_many', foreignKey: 'post_id'},
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {Relation} from '@nozbe/watermelondb';
|
||||
import {field, immutableRelation, json} from '@nozbe/watermelondb/decorators';
|
||||
import {immutableRelation, json} from '@nozbe/watermelondb/decorators';
|
||||
import Model, {Associations} from '@nozbe/watermelondb/Model';
|
||||
|
||||
import {MM_TABLES} from '@constants/database';
|
||||
@@ -23,15 +23,12 @@ export default class PostMetadataModel extends Model {
|
||||
static associations: Associations = {
|
||||
|
||||
/** A POST can have multiple POST_METADATA.(relationship is 1:N)*/
|
||||
[POST]: {type: 'belongs_to', key: 'post_id'},
|
||||
[POST]: {type: 'belongs_to', key: 'id'},
|
||||
};
|
||||
|
||||
/** post_id : The foreign key of the parent POST model */
|
||||
@field('post_id') postId!: string;
|
||||
|
||||
/** data : Different types of data ranging from embeds to images. */
|
||||
@json('data', safeParseJSON) data!: PostMetadata;
|
||||
|
||||
/** post: The record representing the POST parent. */
|
||||
@immutableRelation(POST, 'post_id') post!: Relation<PostModel>;
|
||||
@immutableRelation(POST, 'id') post!: Relation<PostModel>;
|
||||
}
|
||||
|
||||
@@ -23,12 +23,9 @@ export default class PostsInChannelModel extends Model {
|
||||
static associations: Associations = {
|
||||
|
||||
/** A CHANNEL can have multiple POSTS_IN_CHANNEL. (relationship is 1:N)*/
|
||||
[CHANNEL]: {type: 'belongs_to', key: 'channel_id'},
|
||||
[CHANNEL]: {type: 'belongs_to', key: 'id'},
|
||||
};
|
||||
|
||||
/** channel_id : The foreign key of the related parent channel */
|
||||
@field('channel_id') channelId!: string;
|
||||
|
||||
/** earliest : The earliest timestamp of the post in that channel */
|
||||
@field('earliest') earliest!: number;
|
||||
|
||||
@@ -36,5 +33,5 @@ export default class PostsInChannelModel extends Model {
|
||||
@field('latest') latest!: number;
|
||||
|
||||
/** channel : The parent record of the channel for those posts */
|
||||
@immutableRelation(CHANNEL, 'channel_id') channel!: Relation<ChannelModel>;
|
||||
@immutableRelation(CHANNEL, 'id') channel!: Relation<ChannelModel>;
|
||||
}
|
||||
|
||||
@@ -22,8 +22,8 @@ export default class PostsInThreadModel extends Model {
|
||||
/** associations : Describes every relationship to this table. */
|
||||
static associations: Associations = {
|
||||
|
||||
/** A POST can have multiple POSTS_IN_THREAD.(relationship is 1:N)*/
|
||||
[POST]: {type: 'belongs_to', key: 'post_id'},
|
||||
/** A POST can have a POSTS_IN_THREAD.(relationship is 1:1)*/
|
||||
[POST]: {type: 'belongs_to', key: 'id'},
|
||||
};
|
||||
|
||||
/** earliest : Lower bound of a timestamp range */
|
||||
@@ -32,9 +32,6 @@ export default class PostsInThreadModel extends Model {
|
||||
/** latest : Upper bound of a timestamp range */
|
||||
@field('latest') latest!: number;
|
||||
|
||||
/** post_id : The foreign key of the related Post model */
|
||||
@field('post_id') postId!: string;
|
||||
|
||||
/** post : The related record to the parent Post model */
|
||||
@immutableRelation(POST, 'post_id') post!: Relation<PostModel>;
|
||||
@immutableRelation(POST, 'id') post!: Relation<PostModel>;
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {Relation} from '@nozbe/watermelondb';
|
||||
import {field, immutableRelation, json} from '@nozbe/watermelondb/decorators';
|
||||
import {immutableRelation, json} from '@nozbe/watermelondb/decorators';
|
||||
import Model, {Associations} from '@nozbe/watermelondb/Model';
|
||||
|
||||
import {MM_TABLES} from '@constants/database';
|
||||
@@ -24,15 +24,12 @@ export default class TeamChannelHistoryModel extends Model {
|
||||
static associations: Associations = {
|
||||
|
||||
/** A TEAM and TEAM_CHANNEL_HISTORY share a 1:1 relationship */
|
||||
[TEAM]: {type: 'belongs_to', key: 'team_id'},
|
||||
[TEAM]: {type: 'belongs_to', key: 'id'},
|
||||
};
|
||||
|
||||
/** team_id : The foreign key to the related Team record */
|
||||
@field('team_id') teamId!: string;
|
||||
|
||||
/** channel_ids : An array containing the last 5 channels visited within this team order by recency */
|
||||
@json('channel_ids', safeParseJSON) channelIds!: string[];
|
||||
|
||||
/** team : The related record from the parent Team model */
|
||||
@immutableRelation(TEAM, 'team_id') team!: Relation<TeamModel>;
|
||||
@immutableRelation(TEAM, 'id') team!: Relation<TeamModel>;
|
||||
}
|
||||
|
||||
@@ -46,7 +46,7 @@ export default class BaseDataOperator {
|
||||
* @param {(existing: Model, newElement: RawValue) => boolean} inputsArg.findMatchingRecordBy
|
||||
* @returns {Promise<{ProcessRecordResults}>}
|
||||
*/
|
||||
processRecords = async ({createOrUpdateRawValues, deleteRawValues = [], tableName, findMatchingRecordBy, fieldName}: ProcessRecordsArgs): Promise<ProcessRecordResults> => {
|
||||
processRecords = async ({createOrUpdateRawValues = [], deleteRawValues = [], tableName, findMatchingRecordBy, fieldName}: ProcessRecordsArgs): Promise<ProcessRecordResults> => {
|
||||
const getRecords = async (rawValues: RawValue[]) => {
|
||||
// We will query a table where one of its fields can match a range of values. Hence, here we are extracting all those potential values.
|
||||
const columnValues: string[] = getRangeOfValues({fieldName, raws: rawValues});
|
||||
|
||||
@@ -6,6 +6,7 @@ import type ChannelInfoModel from '@typings/database/models/servers/channel_info
|
||||
import type ChannelMembershipModel from '@typings/database/models/servers/channel_membership';
|
||||
import type CustomEmojiModel from '@typings/database/models/servers/custom_emoji';
|
||||
import type DraftModel from '@typings/database/models/servers/draft';
|
||||
import type FileModel from '@typings/database/models/servers/file';
|
||||
import type GroupModel from '@typings/database/models/servers/group';
|
||||
import type GroupMembershipModel from '@typings/database/models/servers/group_membership';
|
||||
import type GroupsInChannelModel from '@typings/database/models/servers/groups_in_channel';
|
||||
@@ -14,6 +15,7 @@ import type MyChannelModel from '@typings/database/models/servers/my_channel';
|
||||
import type MyChannelSettingsModel from '@typings/database/models/servers/my_channel_settings';
|
||||
import type MyTeamModel from '@typings/database/models/servers/my_team';
|
||||
import type PostModel from '@typings/database/models/servers/post';
|
||||
import type PostMetadataModel from '@typings/database/models/servers/post_metadata';
|
||||
import type PreferenceModel from '@typings/database/models/servers/preference';
|
||||
import type RoleModel from '@typings/database/models/servers/role';
|
||||
import type SlashCommandModel from '@typings/database/models/servers/slash_command';
|
||||
@@ -97,7 +99,7 @@ export const isRecordTeamEqualToRaw = (record: TeamModel, raw: Team) => {
|
||||
};
|
||||
|
||||
export const isRecordTeamChannelHistoryEqualToRaw = (record: TeamChannelHistoryModel, raw: TeamChannelHistory) => {
|
||||
return raw.team_id === record.teamId;
|
||||
return raw.id === record.id;
|
||||
};
|
||||
|
||||
export const isRecordTeamSearchHistoryEqualToRaw = (record: TeamSearchHistoryModel, raw: TeamSearchHistory) => {
|
||||
@@ -109,7 +111,7 @@ export const isRecordSlashCommandEqualToRaw = (record: SlashCommandModel, raw: S
|
||||
};
|
||||
|
||||
export const isRecordMyTeamEqualToRaw = (record: MyTeamModel, raw: MyTeam) => {
|
||||
return raw.team_id === record.teamId;
|
||||
return raw.id === record.id;
|
||||
};
|
||||
|
||||
export const isRecordChannelEqualToRaw = (record: ChannelModel, raw: Channel) => {
|
||||
@@ -117,13 +119,21 @@ export const isRecordChannelEqualToRaw = (record: ChannelModel, raw: Channel) =>
|
||||
};
|
||||
|
||||
export const isRecordMyChannelSettingsEqualToRaw = (record: MyChannelSettingsModel, raw: ChannelMembership) => {
|
||||
return raw.channel_id === record.channelId;
|
||||
return raw.channel_id === record.id;
|
||||
};
|
||||
|
||||
export const isRecordChannelInfoEqualToRaw = (record: ChannelInfoModel, raw: ChannelInfo) => {
|
||||
return raw.channel_id === record.channelId;
|
||||
return raw.id === record.id;
|
||||
};
|
||||
|
||||
export const isRecordMyChannelEqualToRaw = (record: MyChannelModel, raw: ChannelMembership) => {
|
||||
return raw.channel_id === record.channelId;
|
||||
return raw.channel_id === record.id;
|
||||
};
|
||||
|
||||
export const isRecordFileEqualToRaw = (record: FileModel, raw: FileInfo) => {
|
||||
return raw.id === record.id;
|
||||
};
|
||||
|
||||
export const isRecordMetadataEqualToRaw = (record: PostMetadataModel, raw: Metadata) => {
|
||||
return raw.id === record.id;
|
||||
};
|
||||
|
||||
@@ -72,6 +72,7 @@ describe('*** Operator: Channel Handlers tests ***', () => {
|
||||
const spyOnHandleRecords = jest.spyOn(operator, 'handleRecords');
|
||||
const settings: ChannelMembership[] = [
|
||||
{
|
||||
id: 'c',
|
||||
user_id: 'me',
|
||||
channel_id: 'c',
|
||||
roles: '',
|
||||
@@ -96,7 +97,7 @@ describe('*** Operator: Channel Handlers tests ***', () => {
|
||||
|
||||
expect(spyOnHandleRecords).toHaveBeenCalledTimes(1);
|
||||
expect(spyOnHandleRecords).toHaveBeenCalledWith({
|
||||
fieldName: 'channel_id',
|
||||
fieldName: 'id',
|
||||
createOrUpdateRawValues: settings,
|
||||
tableName: 'MyChannelSettings',
|
||||
prepareRecordsOnly: false,
|
||||
@@ -111,7 +112,7 @@ describe('*** Operator: Channel Handlers tests ***', () => {
|
||||
const spyOnHandleRecords = jest.spyOn(operator, 'handleRecords');
|
||||
const channelInfos = [
|
||||
{
|
||||
channel_id: 'c',
|
||||
id: 'c',
|
||||
guest_count: 10,
|
||||
header: 'channel info header',
|
||||
member_count: 10,
|
||||
@@ -128,7 +129,7 @@ describe('*** Operator: Channel Handlers tests ***', () => {
|
||||
expect(spyOnHandleRecords).toHaveBeenCalledTimes(1);
|
||||
|
||||
expect(spyOnHandleRecords).toHaveBeenCalledWith({
|
||||
fieldName: 'channel_id',
|
||||
fieldName: 'id',
|
||||
createOrUpdateRawValues: channelInfos,
|
||||
tableName: 'ChannelInfo',
|
||||
prepareRecordsOnly: false,
|
||||
@@ -143,6 +144,7 @@ describe('*** Operator: Channel Handlers tests ***', () => {
|
||||
const spyOnHandleRecords = jest.spyOn(operator, 'handleRecords');
|
||||
const myChannels: ChannelMembership[] = [
|
||||
{
|
||||
id: 'c',
|
||||
user_id: 'me',
|
||||
channel_id: 'c',
|
||||
last_post_at: 1617311494451,
|
||||
@@ -168,7 +170,7 @@ describe('*** Operator: Channel Handlers tests ***', () => {
|
||||
|
||||
expect(spyOnHandleRecords).toHaveBeenCalledTimes(1);
|
||||
expect(spyOnHandleRecords).toHaveBeenCalledWith({
|
||||
fieldName: 'channel_id',
|
||||
fieldName: 'id',
|
||||
createOrUpdateRawValues: myChannels,
|
||||
tableName: 'MyChannel',
|
||||
prepareRecordsOnly: false,
|
||||
|
||||
@@ -80,10 +80,10 @@ const ChannelHandler = (superclass: any) => class extends superclass {
|
||||
);
|
||||
}
|
||||
|
||||
const createOrUpdateRawValues = getUniqueRawsBy({raws: settings, key: 'channel_id'});
|
||||
const createOrUpdateRawValues = getUniqueRawsBy({raws: settings, key: 'id'});
|
||||
|
||||
return this.handleRecords({
|
||||
fieldName: 'channel_id',
|
||||
fieldName: 'id',
|
||||
findMatchingRecordBy: isRecordMyChannelSettingsEqualToRaw,
|
||||
transformer: transformMyChannelSettingsRecord,
|
||||
prepareRecordsOnly,
|
||||
@@ -109,11 +109,11 @@ const ChannelHandler = (superclass: any) => class extends superclass {
|
||||
|
||||
const createOrUpdateRawValues = getUniqueRawsBy({
|
||||
raws: channelInfos,
|
||||
key: 'channel_id',
|
||||
key: 'id',
|
||||
});
|
||||
|
||||
return this.handleRecords({
|
||||
fieldName: 'channel_id',
|
||||
fieldName: 'id',
|
||||
findMatchingRecordBy: isRecordChannelInfoEqualToRaw,
|
||||
transformer: transformChannelInfoRecord,
|
||||
prepareRecordsOnly,
|
||||
@@ -139,11 +139,11 @@ const ChannelHandler = (superclass: any) => class extends superclass {
|
||||
|
||||
const createOrUpdateRawValues = getUniqueRawsBy({
|
||||
raws: myChannels,
|
||||
key: 'channel_id',
|
||||
key: 'id',
|
||||
});
|
||||
|
||||
return this.handleRecords({
|
||||
fieldName: 'channel_id',
|
||||
fieldName: 'id',
|
||||
findMatchingRecordBy: isRecordMyChannelEqualToRaw,
|
||||
transformer: transformMyChannelRecord,
|
||||
prepareRecordsOnly,
|
||||
|
||||
@@ -77,7 +77,7 @@ describe('*** DataOperator: Base Handlers tests ***', () => {
|
||||
|
||||
expect(spyOnHandleRecords).toHaveBeenCalledTimes(1);
|
||||
expect(spyOnHandleRecords).toHaveBeenCalledWith({
|
||||
fieldName: 'id',
|
||||
fieldName: 'name',
|
||||
createOrUpdateRawValues: emojis,
|
||||
tableName: 'CustomEmoji',
|
||||
prepareRecordsOnly: false,
|
||||
|
||||
@@ -18,6 +18,8 @@ import {
|
||||
} from '@database/operator/server_data_operator/transformers/general';
|
||||
import {getUniqueRawsBy} from '@database/operator/utils/general';
|
||||
|
||||
import type {Model} from '@nozbe/watermelondb';
|
||||
|
||||
import type {HandleCustomEmojiArgs, HandleRoleArgs, HandleSystemArgs, HandleTOSArgs, OperationArgs} from '@typings/database/database';
|
||||
import type RoleModel from '@typings/database/models/servers/role';
|
||||
import type CustomEmojiModel from '@typings/database/models/servers/custom_emoji';
|
||||
@@ -52,11 +54,11 @@ export default class ServerDataOperatorBase extends BaseDataOperator {
|
||||
}
|
||||
|
||||
return this.handleRecords({
|
||||
fieldName: 'id',
|
||||
fieldName: 'name',
|
||||
findMatchingRecordBy: isRecordCustomEmojiEqualToRaw,
|
||||
transformer: transformCustomEmojiRecord,
|
||||
prepareRecordsOnly,
|
||||
createOrUpdateRawValues: getUniqueRawsBy({raws: emojis, key: 'id'}),
|
||||
createOrUpdateRawValues: getUniqueRawsBy({raws: emojis, key: 'name'}),
|
||||
tableName: CUSTOM_EMOJI,
|
||||
}) as Promise<CustomEmojiModel[]>;
|
||||
}
|
||||
@@ -104,7 +106,7 @@ export default class ServerDataOperatorBase extends BaseDataOperator {
|
||||
* @param {(TransformerArgs) => Promise<Model>} execute.recordOperator
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
execute = async ({createRaws, transformer, tableName, updateRaws}: OperationArgs): Promise<void> => {
|
||||
execute = async ({createRaws, transformer, tableName, updateRaws}: OperationArgs): Promise<Model[]> => {
|
||||
const models = await this.prepareRecords({
|
||||
tableName,
|
||||
createRaws,
|
||||
@@ -115,5 +117,7 @@ export default class ServerDataOperatorBase extends BaseDataOperator {
|
||||
if (models?.length > 0) {
|
||||
await this.batchRecords(models);
|
||||
}
|
||||
|
||||
return models;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,12 +1,19 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {Q} from '@nozbe/watermelondb';
|
||||
|
||||
import {ActionType} from '@constants';
|
||||
import DatabaseManager from '@database/manager';
|
||||
import {isRecordDraftEqualToRaw} from '@database/operator/server_data_operator/comparators';
|
||||
import {transformDraftRecord} from '@database/operator/server_data_operator/transformers/post';
|
||||
import {createPostsChain} from '@database/operator/utils/post';
|
||||
|
||||
import ServerDataOperator from '..';
|
||||
|
||||
Q.experimentalSortBy = jest.fn().mockImplementation((field) => {
|
||||
return Q.where(field, Q.gte(0));
|
||||
});
|
||||
describe('*** Operator: Post Handlers tests ***', () => {
|
||||
let operator: ServerDataOperator;
|
||||
|
||||
@@ -93,7 +100,7 @@ describe('*** Operator: Post Handlers tests ***', () => {
|
||||
reactions: [
|
||||
{
|
||||
user_id: 'njic1w1k5inefp848jwk6oukio',
|
||||
post_id: 'a7ebyw883trm884p1qcgt8yw4a',
|
||||
post_id: '8swgtrrdiff89jnsiwiip3y1eoe',
|
||||
emoji_name: 'clap',
|
||||
create_at: 1608252965442,
|
||||
},
|
||||
@@ -207,6 +214,13 @@ describe('*** Operator: Post Handlers tests ***', () => {
|
||||
},
|
||||
];
|
||||
|
||||
const order = [
|
||||
'8swgtrrdiff89jnsiwiip3y1eoe',
|
||||
'8fcnk3p1jt8mmkaprgajoxz115a',
|
||||
'3y3w3a6gkbg73bnj3xund9o5ic',
|
||||
];
|
||||
const actionType = ActionType.POSTS.RECEIVED_IN_CHANNEL;
|
||||
|
||||
const spyOnHandleFiles = jest.spyOn(operator, 'handleFiles');
|
||||
const spyOnHandlePostMetadata = jest.spyOn(operator, 'handlePostMetadata');
|
||||
const spyOnHandleReactions = jest.spyOn(operator, 'handleReactions');
|
||||
@@ -216,25 +230,25 @@ describe('*** Operator: Post Handlers tests ***', () => {
|
||||
|
||||
// handlePosts will in turn call handlePostsInThread
|
||||
await operator.handlePosts({
|
||||
orders: [
|
||||
'8swgtrrdiff89jnsiwiip3y1eoe',
|
||||
'8fcnk3p1jt8mmkaprgajoxz115a',
|
||||
'3y3w3a6gkbg73bnj3xund9o5ic',
|
||||
],
|
||||
values: posts,
|
||||
actionType,
|
||||
order,
|
||||
posts,
|
||||
previousPostId: '',
|
||||
});
|
||||
|
||||
expect(spyOnHandleReactions).toHaveBeenCalledTimes(1);
|
||||
expect(spyOnHandleReactions).toHaveBeenCalledWith({
|
||||
reactions: [
|
||||
{
|
||||
user_id: 'njic1w1k5inefp848jwk6oukio',
|
||||
post_id: 'a7ebyw883trm884p1qcgt8yw4a',
|
||||
emoji_name: 'clap',
|
||||
create_at: 1608252965442,
|
||||
},
|
||||
],
|
||||
postsReactions: [{
|
||||
post_id: '8swgtrrdiff89jnsiwiip3y1eoe',
|
||||
reactions: [
|
||||
{
|
||||
user_id: 'njic1w1k5inefp848jwk6oukio',
|
||||
post_id: '8swgtrrdiff89jnsiwiip3y1eoe',
|
||||
emoji_name: 'clap',
|
||||
create_at: 1608252965442,
|
||||
},
|
||||
],
|
||||
}],
|
||||
prepareRecordsOnly: true,
|
||||
});
|
||||
|
||||
@@ -302,14 +316,14 @@ describe('*** Operator: Post Handlers tests ***', () => {
|
||||
},
|
||||
},
|
||||
},
|
||||
post_id: '8swgtrrdiff89jnsiwiip3y1eoe',
|
||||
id: '8swgtrrdiff89jnsiwiip3y1eoe',
|
||||
}],
|
||||
prepareRecordsOnly: true,
|
||||
});
|
||||
|
||||
expect(spyOnHandleCustomEmojis).toHaveBeenCalledTimes(1);
|
||||
expect(spyOnHandleCustomEmojis).toHaveBeenCalledWith({
|
||||
prepareRecordsOnly: false,
|
||||
prepareRecordsOnly: true,
|
||||
emojis: [
|
||||
{
|
||||
id: 'dgwyadacdbbwjc8t357h6hwsrh',
|
||||
@@ -322,12 +336,19 @@ describe('*** Operator: Post Handlers tests ***', () => {
|
||||
],
|
||||
});
|
||||
|
||||
const postInThreadExpected: Record<string, Post[]> = {};
|
||||
posts.filter((p) => p.root_id).forEach((p) => {
|
||||
if (postInThreadExpected[p.root_id]) {
|
||||
postInThreadExpected[p.root_id].push(p);
|
||||
} else {
|
||||
postInThreadExpected[p.root_id] = [p];
|
||||
}
|
||||
});
|
||||
expect(spyOnHandlePostsInThread).toHaveBeenCalledTimes(1);
|
||||
expect(spyOnHandlePostsInThread).toHaveBeenCalledWith([
|
||||
{earliest: 1596032651747, post_id: '8swgtrrdiff89jnsiwiip3y1eoe'},
|
||||
]);
|
||||
expect(spyOnHandlePostsInThread).toHaveBeenCalledWith(postInThreadExpected, ActionType.POSTS.RECEIVED_IN_CHANNEL, true);
|
||||
|
||||
const linkedPosts = createPostsChain({order, posts, previousPostId: ''});
|
||||
expect(spyOnHandlePostsInChannel).toHaveBeenCalledTimes(1);
|
||||
expect(spyOnHandlePostsInChannel).toHaveBeenCalledWith(posts.slice(0, 3));
|
||||
expect(spyOnHandlePostsInChannel).toHaveBeenCalledWith(linkedPosts.slice(0, 3), actionType, true);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,24 +1,21 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {Q} from '@nozbe/watermelondb';
|
||||
import Model from '@nozbe/watermelondb/Model';
|
||||
|
||||
import {MM_TABLES} from '@constants/database';
|
||||
import {ActionType, Database} from '@constants';
|
||||
import DataOperatorException from '@database/exceptions/data_operator_exception';
|
||||
import {isRecordDraftEqualToRaw, isRecordPostEqualToRaw} from '@database/operator/server_data_operator/comparators';
|
||||
import {isRecordDraftEqualToRaw, isRecordFileEqualToRaw, isRecordMetadataEqualToRaw, isRecordPostEqualToRaw} from '@database/operator/server_data_operator/comparators';
|
||||
import {
|
||||
transformDraftRecord,
|
||||
transformFileRecord,
|
||||
transformPostInThreadRecord,
|
||||
transformPostMetadataRecord,
|
||||
transformPostRecord,
|
||||
transformPostsInChannelRecord,
|
||||
} from '@database/operator/server_data_operator/transformers/post';
|
||||
import {getRawRecordPairs, getUniqueRawsBy, retrieveRecords} from '@database/operator/utils/general';
|
||||
import {createPostsChain, sanitizePosts} from '@database/operator/utils/post';
|
||||
import {getUniqueRawsBy} from '@database/operator/utils/general';
|
||||
import {createPostsChain} from '@database/operator/utils/post';
|
||||
|
||||
import type {HandleDraftArgs, HandleFilesArgs, HandlePostMetadataArgs, HandlePostsArgs, RecordPair} from '@typings/database/database';
|
||||
import type {HandleDraftArgs, HandleFilesArgs, HandlePostMetadataArgs, HandlePostsArgs, ProcessRecordResults} from '@typings/database/database';
|
||||
import type DraftModel from '@typings/database/models/servers/draft';
|
||||
import type FileModel from '@typings/database/models/servers/file';
|
||||
import type PostModel from '@typings/database/models/servers/post';
|
||||
@@ -31,16 +28,14 @@ const {
|
||||
DRAFT,
|
||||
FILE,
|
||||
POST,
|
||||
POSTS_IN_CHANNEL,
|
||||
POSTS_IN_THREAD,
|
||||
POST_METADATA,
|
||||
} = MM_TABLES.SERVER;
|
||||
} = Database.MM_TABLES.SERVER;
|
||||
|
||||
export interface PostHandlerMix {
|
||||
handleDraft: ({drafts, prepareRecordsOnly}: HandleDraftArgs) => Promise<DraftModel[]>;
|
||||
handleFiles: ({files, prepareRecordsOnly}: HandleFilesArgs) => Promise<FileModel[]>;
|
||||
handlePostMetadata: ({metadatas, prepareRecordsOnly}: HandlePostMetadataArgs) => Promise<PostMetadataModel[]>;
|
||||
handlePosts: ({orders, values, previousPostId}: HandlePostsArgs) => Promise<void>;
|
||||
handlePosts: ({actionType, order, posts, previousPostId}: HandlePostsArgs) => Promise<void>;
|
||||
handlePostsInChannel: (posts: Post[]) => Promise<void>;
|
||||
handlePostsInThread: (rootPosts: PostsInThread[]) => Promise<void>;
|
||||
}
|
||||
@@ -76,158 +71,143 @@ const PostHandler = (superclass: any) => class extends superclass {
|
||||
/**
|
||||
* handlePosts: Handler responsible for the Create/Update operations occurring on the Post table from the 'Server' schema
|
||||
* @param {HandlePostsArgs} handlePosts
|
||||
* @param {string} handlePosts.actionType
|
||||
* @param {string[]} handlePosts.orders
|
||||
* @param {RawPost[]} handlePosts.values
|
||||
* @param {string | undefined} handlePosts.previousPostId
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
handlePosts = async ({orders, values, previousPostId}: HandlePostsArgs): Promise<void> => {
|
||||
handlePosts = async ({actionType, order, posts, previousPostId = ''}: HandlePostsArgs): Promise<void> => {
|
||||
const tableName = POST;
|
||||
|
||||
// We rely on the order array; if it is empty, we stop processing
|
||||
if (!orders.length) {
|
||||
throw new DataOperatorException(
|
||||
'An empty "order" array has been passed to the handlePosts method',
|
||||
);
|
||||
// We rely on the posts array; if it is empty, we stop processing
|
||||
if (!posts.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
const rawValues = getUniqueRawsBy({
|
||||
raws: values,
|
||||
// Get unique posts in case they are duplicated
|
||||
const uniquePosts = getUniqueRawsBy({
|
||||
raws: posts,
|
||||
key: 'id',
|
||||
}) as Post[];
|
||||
|
||||
// By sanitizing the values, we are separating 'posts' that needs updating ( i.e. un-ordered posts ) from those that need to be created in our database
|
||||
const {postsOrdered, postsUnordered} = sanitizePosts({
|
||||
posts: rawValues,
|
||||
orders,
|
||||
});
|
||||
const emojis: CustomEmoji[] = [];
|
||||
const files: FileInfo[] = [];
|
||||
const metadatas: Metadata[] = [];
|
||||
const postsReactions: ReactionsPerPost[] = [];
|
||||
const pendingPostsToDelete: Post[] = [];
|
||||
const postsInThread: Record<string, Post[]> = {};
|
||||
|
||||
// Here we verify in our database that the postsOrdered truly need 'CREATION'
|
||||
const futureEntries = await this.processRecords({
|
||||
createOrUpdateRawValues: postsOrdered,
|
||||
// Let's process the post data
|
||||
for (const post of posts) {
|
||||
// Find any pending posts that matches the ones received to mark for deletion
|
||||
if (post.pending_post_id) {
|
||||
pendingPostsToDelete.push({
|
||||
...post,
|
||||
id: post.pending_post_id,
|
||||
});
|
||||
}
|
||||
|
||||
if (post.root_id) {
|
||||
if (postsInThread[post.root_id]) {
|
||||
postsInThread[post.root_id].push(post);
|
||||
} else {
|
||||
postsInThread[post.root_id] = [post];
|
||||
}
|
||||
}
|
||||
|
||||
// Process the metadata of each post
|
||||
if (post?.metadata && Object.keys(post?.metadata).length > 0) {
|
||||
const data = post.metadata;
|
||||
|
||||
// Extracts reaction from post's metadata
|
||||
if (data.reactions) {
|
||||
postsReactions.push({post_id: post.id, reactions: data.reactions});
|
||||
delete data.reactions;
|
||||
}
|
||||
|
||||
// Extracts emojis from post's metadata
|
||||
if (data.emojis) {
|
||||
emojis.push(...data.emojis);
|
||||
delete data.emojis;
|
||||
}
|
||||
|
||||
// Extracts files from post's metadata
|
||||
if (data.files) {
|
||||
files.push(...data.files);
|
||||
delete data.files;
|
||||
}
|
||||
|
||||
metadatas.push({
|
||||
data,
|
||||
id: post.id,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Process the posts to get which ones need to be created and which updated
|
||||
const processedPosts = (await this.processRecords({
|
||||
createOrUpdateRawValues: uniquePosts,
|
||||
deleteRawValues: pendingPostsToDelete,
|
||||
tableName,
|
||||
findMatchingRecordBy: isRecordPostEqualToRaw,
|
||||
fieldName: 'id',
|
||||
});
|
||||
})) as ProcessRecordResults;
|
||||
|
||||
if (futureEntries.createRaws?.length) {
|
||||
let batch: Model[] = [];
|
||||
const files: FileInfo[] = [];
|
||||
const postsInThread = [];
|
||||
const reactions: Reaction[] = [];
|
||||
const emojis: CustomEmoji[] = [];
|
||||
const metadatas: Metadata[] = [];
|
||||
const preparedPosts = (await this.prepareRecords({
|
||||
createRaws: processedPosts.createRaws,
|
||||
updateRaws: processedPosts.updateRaws,
|
||||
deleteRaws: processedPosts.deleteRaws,
|
||||
transformer: transformPostRecord,
|
||||
tableName,
|
||||
})) as PostModel[];
|
||||
|
||||
// We create the 'chain of posts' by linking each posts' previousId to the post before it in the order array
|
||||
const linkedRawPosts: RecordPair[] = createPostsChain({
|
||||
orders,
|
||||
previousPostId: previousPostId || '',
|
||||
rawPosts: postsOrdered,
|
||||
});
|
||||
// Add the models to be batched here
|
||||
const batch: Model[] = [...preparedPosts];
|
||||
|
||||
// Prepares records for batch processing onto the 'Post' table for the server schema
|
||||
const posts = (await this.prepareRecords({
|
||||
createRaws: linkedRawPosts,
|
||||
transformer: transformPostRecord,
|
||||
tableName,
|
||||
})) as PostModel[];
|
||||
if (postsReactions.length) {
|
||||
// calls handler for Reactions
|
||||
const postReactions = (await this.handleReactions({postsReactions, prepareRecordsOnly: true})) as ReactionModel[];
|
||||
batch.push(...postReactions);
|
||||
}
|
||||
|
||||
// Appends the processed records into the final batch array
|
||||
batch = batch.concat(posts);
|
||||
if (files.length) {
|
||||
// calls handler for Files
|
||||
const postFiles = await this.handleFiles({files, prepareRecordsOnly: true});
|
||||
batch.push(...postFiles);
|
||||
}
|
||||
|
||||
// Starts extracting information from each post to build up for related tables' data
|
||||
for (const post of postsOrdered) {
|
||||
// PostInThread handler: checks for id === root_id , if so, then call PostsInThread operator
|
||||
if (!post.root_id) {
|
||||
postsInThread.push({
|
||||
earliest: post.create_at,
|
||||
post_id: post.id,
|
||||
});
|
||||
}
|
||||
if (metadatas.length) {
|
||||
// calls handler for postMetadata ( embeds and images )
|
||||
const postMetadata = await this.handlePostMetadata({metadatas, prepareRecordsOnly: true});
|
||||
batch.push(...postMetadata);
|
||||
}
|
||||
|
||||
if (post?.metadata && Object.keys(post?.metadata).length > 0) {
|
||||
const data = post.metadata;
|
||||
if (emojis.length) {
|
||||
const postEmojis = await this.handleCustomEmojis({emojis, prepareRecordsOnly: true});
|
||||
batch.push(...postEmojis);
|
||||
}
|
||||
|
||||
// Extracts reaction from post's metadata
|
||||
if (data.reactions) {
|
||||
reactions.push(...data.reactions);
|
||||
delete data.reactions;
|
||||
}
|
||||
|
||||
// Extracts emojis from post's metadata
|
||||
if (data.emojis) {
|
||||
emojis.push(...data.emojis);
|
||||
delete data.emojis;
|
||||
}
|
||||
|
||||
// Extracts files from post's metadata
|
||||
if (data.files) {
|
||||
files.push(...data.files);
|
||||
delete data.files;
|
||||
}
|
||||
|
||||
metadatas.push({
|
||||
data,
|
||||
post_id: post.id,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (reactions.length) {
|
||||
// calls handler for Reactions
|
||||
const postReactions = (await this.handleReactions({reactions, prepareRecordsOnly: true})) as ReactionModel[];
|
||||
batch = batch.concat(postReactions);
|
||||
}
|
||||
|
||||
if (files.length) {
|
||||
// calls handler for Files
|
||||
const postFiles = await this.handleFiles({files, prepareRecordsOnly: true});
|
||||
batch = batch.concat(postFiles);
|
||||
}
|
||||
|
||||
if (metadatas.length) {
|
||||
// calls handler for postMetadata ( embeds and images )
|
||||
const postMetadata = await this.handlePostMetadata({
|
||||
metadatas,
|
||||
prepareRecordsOnly: true,
|
||||
});
|
||||
|
||||
batch = batch.concat(postMetadata);
|
||||
}
|
||||
|
||||
if (batch.length) {
|
||||
await this.batchRecords(batch);
|
||||
}
|
||||
|
||||
// LAST: calls handler for CustomEmojis, PostsInThread, PostsInChannel
|
||||
if (emojis.length) {
|
||||
await this.handleCustomEmojis({
|
||||
emojis,
|
||||
prepareRecordsOnly: false,
|
||||
});
|
||||
}
|
||||
|
||||
if (postsInThread.length) {
|
||||
await this.handlePostsInThread(postsInThread);
|
||||
}
|
||||
|
||||
if (postsOrdered.length) {
|
||||
await this.handlePostsInChannel(postsOrdered);
|
||||
// link the newly received posts
|
||||
const linkedPosts = createPostsChain({order, posts, previousPostId});
|
||||
if (linkedPosts.length) {
|
||||
const postsInChannel = await this.handlePostsInChannel(linkedPosts, actionType as never, true);
|
||||
if (postsInChannel.length) {
|
||||
batch.push(...postsInChannel);
|
||||
}
|
||||
}
|
||||
|
||||
if (postsUnordered.length) {
|
||||
// Truly update those posts that have a different update_at value
|
||||
await this.handleRecords({
|
||||
findMatchingRecordBy: isRecordPostEqualToRaw,
|
||||
fieldName: 'id',
|
||||
trasformer: transformPostRecord,
|
||||
createOrUpdateRawValues: postsUnordered,
|
||||
tableName: POST,
|
||||
prepareRecordsOnly: false,
|
||||
});
|
||||
if (Object.keys(postsInThread).length) {
|
||||
const postsInThreads = await this.handlePostsInThread(postsInThread, actionType as never, true);
|
||||
if (postsInThreads.length) {
|
||||
batch.push(...postsInThreads);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
if (batch.length) {
|
||||
await this.batchRecords(batch);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* handleFiles: Handler responsible for the Create/Update operations occurring on the File table from the 'Server' schema
|
||||
@@ -241,8 +221,16 @@ const PostHandler = (superclass: any) => class extends superclass {
|
||||
return [];
|
||||
}
|
||||
|
||||
const processedFiles = (await this.processRecords({
|
||||
createOrUpdateRawValues: files,
|
||||
tableName: FILE,
|
||||
findMatchingRecordBy: isRecordFileEqualToRaw,
|
||||
fieldName: 'id',
|
||||
})) as ProcessRecordResults;
|
||||
|
||||
const postFiles = await this.prepareRecords({
|
||||
createRaws: getRawRecordPairs(files),
|
||||
createRaws: processedFiles.createRaws,
|
||||
updateRaws: processedFiles.updateRaws,
|
||||
transformer: transformFileRecord,
|
||||
tableName: FILE,
|
||||
});
|
||||
@@ -267,8 +255,16 @@ const PostHandler = (superclass: any) => class extends superclass {
|
||||
* @returns {Promise<PostMetadataModel[]>}
|
||||
*/
|
||||
handlePostMetadata = async ({metadatas, prepareRecordsOnly}: HandlePostMetadataArgs): Promise<PostMetadataModel[]> => {
|
||||
const processedMetas = (await this.processRecords({
|
||||
createOrUpdateRawValues: metadatas,
|
||||
tableName: POST_METADATA,
|
||||
findMatchingRecordBy: isRecordMetadataEqualToRaw,
|
||||
fieldName: 'id',
|
||||
})) as ProcessRecordResults;
|
||||
|
||||
const postMetas = await this.prepareRecords({
|
||||
createRaws: getRawRecordPairs([metadatas]),
|
||||
createRaws: processedMetas.createRaws,
|
||||
updateRaws: processedMetas.updateRaws,
|
||||
transformer: transformPostMetadataRecord,
|
||||
tableName: POST_METADATA,
|
||||
});
|
||||
@@ -289,41 +285,22 @@ const PostHandler = (superclass: any) => class extends superclass {
|
||||
* @param {PostsInThread[]} rootPosts
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
handlePostsInThread = async (rootPosts: PostsInThread[]): Promise<void> => {
|
||||
if (!rootPosts.length) {
|
||||
return;
|
||||
handlePostsInThread = async (postsMap: Record<string, Post[]>, actionType: never, prepareRecordsOnly = false): Promise<PostsInThreadModel[]> => {
|
||||
if (!postsMap || !Object.keys(postsMap).length) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const postIds = rootPosts.map((postThread) => postThread.post_id);
|
||||
const rawPostsInThreads: PostsInThread[] = [];
|
||||
|
||||
// Retrieves all threads whereby their root_id can be one of the element in the postIds array
|
||||
const threads = (await this.database.collections.
|
||||
get(POST).
|
||||
query(Q.where('root_id', Q.oneOf(postIds))).
|
||||
fetch()) as PostModel[];
|
||||
|
||||
// The aim here is to find the last reply in that thread; hence the latest create_at value
|
||||
rootPosts.forEach((rootPost) => {
|
||||
const maxCreateAt: number = threads.reduce((max: number, thread: PostModel) => {
|
||||
return thread.createAt > max ? thread.createAt : maxCreateAt;
|
||||
}, 0);
|
||||
|
||||
// Collects all 'raw' postInThreads objects that will be sent to the operatePostsInThread function
|
||||
rawPostsInThreads.push({...rootPost, latest: maxCreateAt});
|
||||
});
|
||||
|
||||
if (rawPostsInThreads.length) {
|
||||
const postInThreadRecords = (await this.prepareRecords({
|
||||
createRaws: getRawRecordPairs(rawPostsInThreads),
|
||||
transformer: transformPostInThreadRecord,
|
||||
tableName: POSTS_IN_THREAD,
|
||||
})) as PostsInThreadModel[];
|
||||
|
||||
if (postInThreadRecords?.length) {
|
||||
await this.batchRecords(postInThreadRecords);
|
||||
switch (actionType) {
|
||||
case ActionType.POSTS.RECEIVED_IN_CHANNEL:
|
||||
case ActionType.POSTS.RECEIVED_SINCE:
|
||||
case ActionType.POSTS.RECEIVED_AFTER:
|
||||
case ActionType.POSTS.RECEIVED_BEFORE:
|
||||
return this.handleReceivedPostsInThread(postsMap, prepareRecordsOnly) as Promise<PostsInThreadModel[]>;
|
||||
case ActionType.POSTS.RECEIVED_NEW: {
|
||||
return this.handleReceivedPostForThread(Object.values(postsMap)[0], prepareRecordsOnly) as Promise<PostsInThreadModel[]>;
|
||||
}
|
||||
}
|
||||
|
||||
return [];
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -331,101 +308,30 @@ const PostHandler = (superclass: any) => class extends superclass {
|
||||
* @param {Post[]} posts
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
handlePostsInChannel = async (posts: Post[]): Promise<void> => {
|
||||
handlePostsInChannel = async (posts: Post[], actionType: never, prepareRecordsOnly = false): Promise<PostsInChannelModel[]> => {
|
||||
// At this point, the parameter 'posts' is already a chain of posts. Now, we have to figure out how to plug it
|
||||
// into existing chains in the PostsInChannel table
|
||||
|
||||
if (!posts.length) {
|
||||
return;
|
||||
const permittedActions = Object.values(ActionType.POSTS);
|
||||
|
||||
if (!posts.length || !permittedActions.includes(actionType)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
// Sort a clone of 'posts' array by create_at
|
||||
const sortedPosts = [...posts].sort((a, b) => {
|
||||
return a.create_at - b.create_at;
|
||||
});
|
||||
|
||||
// The first element (beginning of chain)
|
||||
const tipOfChain: Post = sortedPosts[0];
|
||||
|
||||
// Channel Id for this chain of posts
|
||||
const channelId = tipOfChain.channel_id;
|
||||
|
||||
// Find smallest 'create_at' value in chain
|
||||
const earliest = tipOfChain.create_at;
|
||||
|
||||
// Find highest 'create_at' value in chain; -1 means we are dealing with one item in the posts array
|
||||
const latest = sortedPosts[sortedPosts.length - 1].create_at;
|
||||
|
||||
// Find the records in the PostsInChannel table that have a matching channel_id
|
||||
// const chunks = (await database.collections.get(POSTS_IN_CHANNEL).query(Q.where('channel_id', channelId)).fetch()) as PostsInChannel[];
|
||||
const chunks = (await retrieveRecords({
|
||||
database: this.database,
|
||||
tableName: POSTS_IN_CHANNEL,
|
||||
condition: Q.where('channel_id', channelId),
|
||||
})) as PostsInChannelModel[];
|
||||
|
||||
const createPostsInChannelRecord = async () => {
|
||||
await this.execute({
|
||||
createRaws: [{record: undefined, raw: {channel_id: channelId, earliest, latest}}],
|
||||
tableName: POSTS_IN_CHANNEL,
|
||||
transformer: transformPostsInChannelRecord,
|
||||
});
|
||||
};
|
||||
|
||||
// chunk length 0; then it's a new chunk to be added to the PostsInChannel table
|
||||
if (chunks.length === 0) {
|
||||
await createPostsInChannelRecord();
|
||||
return;
|
||||
switch (actionType) {
|
||||
case ActionType.POSTS.RECEIVED_IN_CHANNEL:
|
||||
return this.handleReceivedPostsInChannel(posts, prepareRecordsOnly) as Promise<PostsInChannelModel[]>;
|
||||
case ActionType.POSTS.RECEIVED_SINCE:
|
||||
return this.handleReceivedPostsInChannelSince(posts, prepareRecordsOnly) as Promise<PostsInChannelModel[]>;
|
||||
case ActionType.POSTS.RECEIVED_AFTER:
|
||||
return this.handleReceivedPostsInChannelAfter(posts, prepareRecordsOnly) as Promise<PostsInChannelModel[]>;
|
||||
case ActionType.POSTS.RECEIVED_BEFORE:
|
||||
return this.handleReceivedPostsInChannelBefore(posts, prepareRecordsOnly) as Promise<PostsInChannelModel[]>;
|
||||
case ActionType.POSTS.RECEIVED_NEW:
|
||||
return this.handleReceivedPostForChannel(posts[0], prepareRecordsOnly) as Promise<PostsInChannelModel[]>;
|
||||
}
|
||||
|
||||
// Sort chunks (in-place) by earliest field ( oldest to newest )
|
||||
chunks.sort((a, b) => {
|
||||
return a.earliest - b.earliest;
|
||||
});
|
||||
|
||||
let found = false;
|
||||
let targetChunk: PostsInChannelModel;
|
||||
|
||||
for (const chunk of chunks) {
|
||||
// find if we should plug the chain before
|
||||
if (earliest < chunk.earliest) {
|
||||
found = true;
|
||||
targetChunk = chunk;
|
||||
}
|
||||
|
||||
if (found) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (found) {
|
||||
// We have a potential chunk to plug nearby
|
||||
const potentialPosts = (await retrieveRecords({
|
||||
database: this.database,
|
||||
tableName: POST,
|
||||
condition: Q.where('create_at', earliest),
|
||||
})) as PostModel[];
|
||||
|
||||
if (potentialPosts?.length > 0) {
|
||||
const targetPost = potentialPosts[0];
|
||||
|
||||
// now we decide if we need to operate on the targetChunk or just create a new chunk
|
||||
const isChainable = tipOfChain.prev_post_id === targetPost.previousPostId;
|
||||
|
||||
if (isChainable) {
|
||||
// Update this chunk's data in PostsInChannel table. earliest comes from tipOfChain while latest comes from chunk
|
||||
await this.database.action(async () => {
|
||||
await targetChunk.update((postInChannel) => {
|
||||
postInChannel.earliest = earliest;
|
||||
});
|
||||
});
|
||||
} else {
|
||||
await createPostsInChannelRecord();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
await createPostsInChannelRecord();
|
||||
}
|
||||
return [];
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
@@ -0,0 +1,186 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {Q} from '@nozbe/watermelondb';
|
||||
|
||||
import {Database} from '@constants';
|
||||
import {retrieveRecords} from '@database/operator/utils/general';
|
||||
import {transformPostsInChannelRecord} from '@database/operator/server_data_operator/transformers/post';
|
||||
import {getPostListEdges} from '@database//operator/utils/post';
|
||||
|
||||
import type PostsInChannelModel from '@typings/database/models/servers/posts_in_channel';
|
||||
|
||||
export interface PostsInChannelHandlerMix {
|
||||
handleReceivedPostsInChannel: (posts: Post[], prepareRecordsOnly?: boolean) => Promise<PostsInChannelModel[]>;
|
||||
handleReceivedPostsInChannelSince: (posts: Post[], prepareRecordsOnly?: boolean) => Promise<PostsInChannelModel[]>;
|
||||
handleReceivedPostsInChannelBefore: (posts: Post[], prepareRecordsOnly?: boolean) => Promise<PostsInChannelModel[]>;
|
||||
handleReceivedPostsInChannelAfter: (posts: Post[], prepareRecordsOnly?: boolean) => Promise<PostsInChannelModel[]>;
|
||||
handleReceivedPostForChannel: (post: Post, prepareRecordsOnly?: boolean) => Promise<PostsInChannelModel[]>;
|
||||
}
|
||||
|
||||
const {POSTS_IN_CHANNEL} = Database.MM_TABLES.SERVER;
|
||||
|
||||
const PostsInChannelHandler = (superclass: any) => class extends superclass {
|
||||
_createPostsInChannelRecord = (channelId: string, earliest: number, latest: number, prepareRecordsOnly = false): Promise<PostsInChannelModel[]> => {
|
||||
// We should prepare instead of execute
|
||||
if (prepareRecordsOnly) {
|
||||
return this.prepareRecords({
|
||||
tableName: POSTS_IN_CHANNEL,
|
||||
createRaws: [{record: undefined, raw: {channel_id: channelId, earliest, latest}}],
|
||||
transformer: transformPostsInChannelRecord,
|
||||
});
|
||||
}
|
||||
return this.execute({
|
||||
createRaws: [{record: undefined, raw: {channel_id: channelId, earliest, latest}}],
|
||||
tableName: POSTS_IN_CHANNEL,
|
||||
transformer: transformPostsInChannelRecord,
|
||||
});
|
||||
};
|
||||
|
||||
_mergePostInChannelChunks = async (newChunk: PostsInChannelModel, existingChunks: PostsInChannelModel[], prepareRecordsOnly = false) => {
|
||||
const result: PostsInChannelModel[] = [];
|
||||
for (const chunk of existingChunks) {
|
||||
if (newChunk.earliest <= chunk.earliest && newChunk.latest >= chunk.latest) {
|
||||
if (!prepareRecordsOnly) {
|
||||
newChunk.prepareUpdate();
|
||||
}
|
||||
result.push(newChunk);
|
||||
result.push(chunk.prepareDestroyPermanently());
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (result.length && !prepareRecordsOnly) {
|
||||
await this.batchRecords(result);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
handleReceivedPostsInChannel = async (posts: Post[], prepareRecordsOnly = false): Promise<PostsInChannelModel[]> => {
|
||||
if (!posts.length) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const {firstPost, lastPost} = getPostListEdges(posts);
|
||||
|
||||
// Channel Id for this chain of posts
|
||||
const channelId = firstPost.channel_id;
|
||||
|
||||
// Find smallest 'create_at' value in chain
|
||||
const earliest = firstPost.create_at;
|
||||
|
||||
// Find highest 'create_at' value in chain; -1 means we are dealing with one item in the posts array
|
||||
const latest = lastPost.create_at;
|
||||
|
||||
// Find the records in the PostsInChannel table that have a matching channel_id
|
||||
// const chunks = (await database.collections.get(POSTS_IN_CHANNEL).query(Q.where('channel_id', channelId)).fetch()) as PostsInChannel[];
|
||||
const chunks = (await retrieveRecords({
|
||||
database: this.database,
|
||||
tableName: POSTS_IN_CHANNEL,
|
||||
condition: (Q.where('id', channelId), Q.experimentalSortBy('latest', Q.desc)),
|
||||
})) as PostsInChannelModel[];
|
||||
|
||||
// chunk length 0; then it's a new chunk to be added to the PostsInChannel table
|
||||
if (chunks.length === 0) {
|
||||
return this._createPostsInChannelRecord(channelId, earliest, latest, prepareRecordsOnly);
|
||||
}
|
||||
|
||||
let targetChunk: PostsInChannelModel|undefined;
|
||||
|
||||
for (const chunk of chunks) {
|
||||
// find if we should plug the chain before
|
||||
if (firstPost.create_at >= chunk.earliest || latest <= chunk.latest) {
|
||||
targetChunk = chunk;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (targetChunk) {
|
||||
// If the chunk was found, Update the chunk and return
|
||||
if (prepareRecordsOnly) {
|
||||
targetChunk.prepareUpdate((record) => {
|
||||
record.earliest = Math.min(record.earliest, earliest);
|
||||
record.latest = Math.max(record.latest, latest);
|
||||
});
|
||||
return [targetChunk];
|
||||
}
|
||||
|
||||
targetChunk = await this.database.action(async () => {
|
||||
return targetChunk!.update((record) => {
|
||||
record.earliest = Math.min(record.earliest, earliest);
|
||||
record.latest = Math.max(record.latest, latest);
|
||||
});
|
||||
});
|
||||
|
||||
return [targetChunk!];
|
||||
}
|
||||
|
||||
// Create a new chunk and merge them if needed
|
||||
const newChunk = await this._createPostsInChannelRecord(channelId, earliest, latest, prepareRecordsOnly);
|
||||
const merged = await this._mergePostInChannelChunks(newChunk[0], chunks, prepareRecordsOnly);
|
||||
return merged;
|
||||
};
|
||||
|
||||
handleReceivedPostsInChannelSince = async (posts: Post[], prepareRecordsOnly = false): Promise<PostsInChannelModel[]> => {
|
||||
if (!posts.length) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const {firstPost} = getPostListEdges(posts);
|
||||
let latest = 0;
|
||||
|
||||
let recentChunk: PostsInChannelModel|undefined;
|
||||
const chunks = (await retrieveRecords({
|
||||
database: this.database,
|
||||
tableName: POSTS_IN_CHANNEL,
|
||||
condition: (Q.where('id', firstPost.channel_id), Q.experimentalSortBy('latest', Q.desc)),
|
||||
})) as PostsInChannelModel[];
|
||||
|
||||
if (chunks.length) {
|
||||
recentChunk = chunks[0];
|
||||
|
||||
// add any new recent post while skipping the ones that were just updated
|
||||
for (const post of posts) {
|
||||
if (post.create_at > recentChunk.latest) {
|
||||
latest = post.create_at;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (recentChunk && recentChunk.latest < latest) {
|
||||
// We've got new posts that belong to this chunk
|
||||
if (prepareRecordsOnly) {
|
||||
recentChunk.prepareUpdate((record) => {
|
||||
record.latest = Math.max(record.latest, latest);
|
||||
});
|
||||
|
||||
return [recentChunk];
|
||||
}
|
||||
|
||||
recentChunk = await this.database.action(async () => {
|
||||
return recentChunk!.update((record) => {
|
||||
record.latest = Math.max(record.latest, latest);
|
||||
});
|
||||
});
|
||||
|
||||
return [recentChunk!];
|
||||
}
|
||||
|
||||
return [];
|
||||
};
|
||||
|
||||
handleReceivedPostsInChannelBefore = async (posts: Post[], prepareRecordsOnly = false): Promise<PostsInChannelModel[]> => {
|
||||
throw new Error(`handleReceivedPostsInChannelBefore Not implemented yet. posts count${posts.length} prepareRecordsOnly=${prepareRecordsOnly}`);
|
||||
}
|
||||
|
||||
handleReceivedPostsInChannelAfter = async (posts: Post[], prepareRecordsOnly = false): Promise<PostsInChannelModel[]> => {
|
||||
throw new Error(`handleReceivedPostsInChannelAfter Not implemented yet. posts count${posts.length} prepareRecordsOnly=${prepareRecordsOnly}`);
|
||||
}
|
||||
|
||||
handleReceivedPostForChannel = async (post: Post, prepareRecordsOnly = false): Promise<PostsInChannelModel[]> => {
|
||||
throw new Error(`handleReceivedPostsInChannelAfter Not implemented yet. postId ${post.id} prepareRecordsOnly=${prepareRecordsOnly}`);
|
||||
}
|
||||
};
|
||||
|
||||
export default PostsInChannelHandler;
|
||||
@@ -0,0 +1,73 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {Q} from '@nozbe/watermelondb';
|
||||
|
||||
import {Database} from '@constants';
|
||||
import {getRawRecordPairs, retrieveRecords} from '@database/operator/utils/general';
|
||||
import {transformPostInThreadRecord} from '@database/operator/server_data_operator/transformers/post';
|
||||
import {getPostListEdges} from '@database//operator/utils/post';
|
||||
|
||||
import type PostsInThreadModel from '@typings/database/models/servers/posts_in_thread';
|
||||
|
||||
export interface PostsInThreadHandlerMix {
|
||||
handleReceivedPostsInThread: (postsMap: Record<string, Post[]>, prepareRecordsOnly?: boolean) => Promise<PostsInThreadModel[]>;
|
||||
handleReceivedPostForThread: (post: Post, prepareRecordsOnly?: boolean) => Promise<PostsInThreadModel[]>;
|
||||
}
|
||||
|
||||
const {POSTS_IN_THREAD} = Database.MM_TABLES.SERVER;
|
||||
|
||||
const PostsInThreadHandler = (superclass: any) => class extends superclass {
|
||||
handleReceivedPostsInThread = async (postsMap: Record<string, Post[]>, prepareRecordsOnly = false): Promise<PostsInThreadModel[]> => {
|
||||
if (!Object.keys(postsMap).length) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const update: PostsInThread[] = [];
|
||||
const create: PostsInThread[] = [];
|
||||
const ids = Object.keys(postsMap);
|
||||
for await (const rootId of ids) {
|
||||
const {firstPost, lastPost} = getPostListEdges(postsMap[rootId]);
|
||||
const chunks = (await retrieveRecords({
|
||||
database: this.database,
|
||||
tableName: POSTS_IN_THREAD,
|
||||
condition: Q.where('id', rootId),
|
||||
})) as PostsInThreadModel[];
|
||||
|
||||
if (chunks.length) {
|
||||
const chunk = chunks[0];
|
||||
update.push({
|
||||
id: rootId,
|
||||
earliest: Math.min(chunk.earliest, firstPost.create_at),
|
||||
latest: Math.max(chunk.latest, lastPost.create_at),
|
||||
});
|
||||
} else {
|
||||
// create chunk
|
||||
create.push({
|
||||
id: rootId,
|
||||
earliest: firstPost.create_at,
|
||||
latest: lastPost.create_at,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const postInThreadRecords = (await this.prepareRecords({
|
||||
createRaws: getRawRecordPairs(create),
|
||||
updateRaws: getRawRecordPairs(update),
|
||||
transformer: transformPostInThreadRecord,
|
||||
tableName: POSTS_IN_THREAD,
|
||||
})) as PostsInThreadModel[];
|
||||
|
||||
if (postInThreadRecords?.length && !prepareRecordsOnly) {
|
||||
await this.batchRecords(postInThreadRecords);
|
||||
}
|
||||
|
||||
return postInThreadRecords;
|
||||
};
|
||||
|
||||
handleReceivedPostForThread = async (post: Post, prepareRecordsOnly = false): Promise<PostsInThreadModel[]> => {
|
||||
throw new Error(`handleReceivedPostForThread Not implemented yet. postId ${post.id} prepareRecordsOnly=${prepareRecordsOnly}`);
|
||||
}
|
||||
};
|
||||
|
||||
export default PostsInThreadHandler;
|
||||
@@ -108,7 +108,7 @@ describe('*** Operator: Team Handlers tests ***', () => {
|
||||
const spyOnHandleRecords = jest.spyOn(operator, 'handleRecords');
|
||||
const myTeams = [
|
||||
{
|
||||
team_id: 'teamA',
|
||||
id: 'teamA',
|
||||
roles: 'roleA, roleB, roleC',
|
||||
is_unread: true,
|
||||
mentions_count: 3,
|
||||
@@ -122,7 +122,7 @@ describe('*** Operator: Team Handlers tests ***', () => {
|
||||
|
||||
expect(spyOnHandleRecords).toHaveBeenCalledTimes(1);
|
||||
expect(spyOnHandleRecords).toHaveBeenCalledWith({
|
||||
fieldName: 'team_id',
|
||||
fieldName: 'id',
|
||||
createOrUpdateRawValues: myTeams,
|
||||
tableName: 'MyTeam',
|
||||
prepareRecordsOnly: false,
|
||||
@@ -137,7 +137,7 @@ describe('*** Operator: Team Handlers tests ***', () => {
|
||||
const spyOnHandleRecords = jest.spyOn(operator, 'handleRecords');
|
||||
const teamChannelHistories = [
|
||||
{
|
||||
team_id: 'a',
|
||||
id: 'a',
|
||||
channel_ids: ['ca', 'cb'],
|
||||
},
|
||||
];
|
||||
@@ -149,7 +149,7 @@ describe('*** Operator: Team Handlers tests ***', () => {
|
||||
|
||||
expect(spyOnHandleRecords).toHaveBeenCalledTimes(1);
|
||||
expect(spyOnHandleRecords).toHaveBeenCalledWith({
|
||||
fieldName: 'team_id',
|
||||
fieldName: 'id',
|
||||
createOrUpdateRawValues: teamChannelHistories,
|
||||
tableName: 'TeamChannelHistory',
|
||||
prepareRecordsOnly: false,
|
||||
|
||||
@@ -120,10 +120,10 @@ const TeamHandler = (superclass: any) => class extends superclass {
|
||||
);
|
||||
}
|
||||
|
||||
const createOrUpdateRawValues = getUniqueRawsBy({raws: teamChannelHistories, key: 'team_id'});
|
||||
const createOrUpdateRawValues = getUniqueRawsBy({raws: teamChannelHistories, key: 'id'});
|
||||
|
||||
return this.handleRecords({
|
||||
fieldName: 'team_id',
|
||||
fieldName: 'id',
|
||||
findMatchingRecordBy: isRecordTeamChannelHistoryEqualToRaw,
|
||||
transformer: transformTeamChannelHistoryRecord,
|
||||
prepareRecordsOnly,
|
||||
@@ -204,7 +204,7 @@ const TeamHandler = (superclass: any) => class extends superclass {
|
||||
const createOrUpdateRawValues = getUniqueRawsBy({raws: myTeams, key: 'team_id'});
|
||||
|
||||
return this.handleRecords({
|
||||
fieldName: 'team_id',
|
||||
fieldName: 'id',
|
||||
findMatchingRecordBy: isRecordMyTeamEqualToRaw,
|
||||
transformer: transformMyTeamRecord,
|
||||
prepareRecordsOnly,
|
||||
|
||||
@@ -22,26 +22,29 @@ describe('*** Operator: User Handlers tests ***', () => {
|
||||
operator = DatabaseManager.serverDatabases['baseHandler.test.com'].operator;
|
||||
});
|
||||
|
||||
it('=> HandleReactions: should write to both Reactions and CustomEmoji tables', async () => {
|
||||
it('=> HandleReactions: should write to Reactions table', async () => {
|
||||
expect.assertions(2);
|
||||
|
||||
const spyOnPrepareRecords = jest.spyOn(operator, 'prepareRecords');
|
||||
const spyOnBatchOperation = jest.spyOn(operator, 'batchRecords');
|
||||
|
||||
await operator.handleReactions({
|
||||
reactions: [
|
||||
{
|
||||
create_at: 1608263728086,
|
||||
emoji_name: 'p4p1',
|
||||
post_id: '4r9jmr7eqt8dxq3f9woypzurry',
|
||||
user_id: 'ooumoqgq3bfiijzwbn8badznwc',
|
||||
},
|
||||
],
|
||||
postsReactions: [{
|
||||
post_id: '4r9jmr7eqt8dxq3f9woypzurry',
|
||||
reactions: [
|
||||
{
|
||||
create_at: 1608263728086,
|
||||
emoji_name: 'p4p1',
|
||||
post_id: '4r9jmr7eqt8dxq3f9woypzurry',
|
||||
user_id: 'ooumoqgq3bfiijzwbn8badznwc',
|
||||
},
|
||||
],
|
||||
}],
|
||||
prepareRecordsOnly: false,
|
||||
});
|
||||
|
||||
// Called twice: Once for Reaction record and once for CustomEmoji record
|
||||
expect(spyOnPrepareRecords).toHaveBeenCalledTimes(2);
|
||||
// Called twice: Once for Reaction record
|
||||
expect(spyOnPrepareRecords).toHaveBeenCalledTimes(1);
|
||||
|
||||
// Only one batch operation for both tables
|
||||
expect(spyOnBatchOperation).toHaveBeenCalledTimes(1);
|
||||
|
||||
@@ -8,14 +8,13 @@ import {
|
||||
isRecordPreferenceEqualToRaw,
|
||||
isRecordUserEqualToRaw,
|
||||
} from '@database/operator/server_data_operator/comparators';
|
||||
import {transformCustomEmojiRecord} from '@database/operator/server_data_operator/transformers/general';
|
||||
import {
|
||||
transformChannelMembershipRecord,
|
||||
transformPreferenceRecord,
|
||||
transformReactionRecord,
|
||||
transformUserRecord,
|
||||
} from '@database/operator/server_data_operator/transformers/user';
|
||||
import {getRawRecordPairs, getUniqueRawsBy} from '@database/operator/utils/general';
|
||||
import {getUniqueRawsBy} from '@database/operator/utils/general';
|
||||
import {sanitizeReactions} from '@database/operator/utils/reaction';
|
||||
|
||||
import type ChannelMembershipModel from '@typings/database/models/servers/channel_membership';
|
||||
@@ -32,7 +31,6 @@ import type UserModel from '@typings/database/models/servers/user';
|
||||
|
||||
const {
|
||||
CHANNEL_MEMBERSHIP,
|
||||
CUSTOM_EMOJI,
|
||||
PREFERENCE,
|
||||
REACTION,
|
||||
USER,
|
||||
@@ -41,7 +39,7 @@ const {
|
||||
export interface UserHandlerMix {
|
||||
handleChannelMembership: ({channelMemberships, prepareRecordsOnly}: HandleChannelMembershipArgs) => Promise<ChannelMembershipModel[]>;
|
||||
handlePreferences: ({preferences, prepareRecordsOnly}: HandlePreferencesArgs) => Promise<PreferenceModel[]>;
|
||||
handleReactions: ({reactions, prepareRecordsOnly}: HandleReactionsArgs) => Promise<Array<ReactionModel | CustomEmojiModel>>;
|
||||
handleReactions: ({postsReactions, prepareRecordsOnly}: HandleReactionsArgs) => Promise<Array<ReactionModel | CustomEmojiModel>>;
|
||||
handleUsers: ({users, prepareRecordsOnly}: HandleUsersArgs) => Promise<UserModel[]>;
|
||||
}
|
||||
|
||||
@@ -103,54 +101,47 @@ const UserHandler = (superclass: any) => class extends superclass {
|
||||
/**
|
||||
* handleReactions: Handler responsible for the Create/Update operations occurring on the Reaction table from the 'Server' schema
|
||||
* @param {HandleReactionsArgs} handleReactions
|
||||
* @param {Reaction[]} handleReactions.reactions
|
||||
* @param {ReactionsPerPost[]} handleReactions.reactions
|
||||
* @param {boolean} handleReactions.prepareRecordsOnly
|
||||
* @throws DataOperatorException
|
||||
* @returns {Promise<Array<(ReactionModel | CustomEmojiModel)>>}
|
||||
*/
|
||||
handleReactions = async ({reactions, prepareRecordsOnly}: HandleReactionsArgs): Promise<Array<(ReactionModel | CustomEmojiModel)>> => {
|
||||
let batchRecords: Array<ReactionModel | CustomEmojiModel> = [];
|
||||
handleReactions = async ({postsReactions, prepareRecordsOnly}: HandleReactionsArgs): Promise<ReactionModel[]> => {
|
||||
const batchRecords: ReactionModel[] = [];
|
||||
|
||||
if (!reactions.length) {
|
||||
if (!postsReactions.length) {
|
||||
throw new DataOperatorException(
|
||||
'An empty "reactions" array has been passed to the handleReactions method',
|
||||
);
|
||||
}
|
||||
|
||||
const rawValues = getUniqueRawsBy({raws: reactions, key: 'emoji_name'}) as Reaction[];
|
||||
for await (const postReactions of postsReactions) {
|
||||
const {post_id, reactions} = postReactions;
|
||||
const rawValues = getUniqueRawsBy({raws: reactions, key: 'emoji_name'}) as Reaction[];
|
||||
const {
|
||||
createReactions,
|
||||
deleteReactions,
|
||||
} = await sanitizeReactions({
|
||||
database: this.database,
|
||||
post_id,
|
||||
rawReactions: rawValues,
|
||||
});
|
||||
|
||||
const {
|
||||
createEmojis,
|
||||
createReactions,
|
||||
deleteReactions,
|
||||
} = await sanitizeReactions({
|
||||
database: this.database,
|
||||
post_id: reactions[0].post_id,
|
||||
rawReactions: rawValues,
|
||||
});
|
||||
if (createReactions?.length) {
|
||||
// Prepares record for model Reactions
|
||||
const reactionsRecords = (await this.prepareRecords({
|
||||
createRaws: createReactions,
|
||||
transformer: transformReactionRecord,
|
||||
tableName: REACTION,
|
||||
})) as ReactionModel[];
|
||||
batchRecords.push(...reactionsRecords);
|
||||
}
|
||||
|
||||
if (createReactions.length) {
|
||||
// Prepares record for model Reactions
|
||||
const reactionsRecords = (await this.prepareRecords({
|
||||
createRaws: createReactions,
|
||||
transformer: transformReactionRecord,
|
||||
tableName: REACTION,
|
||||
})) as ReactionModel[];
|
||||
batchRecords = batchRecords.concat(reactionsRecords);
|
||||
if (deleteReactions?.length) {
|
||||
batchRecords.push(...deleteReactions);
|
||||
}
|
||||
}
|
||||
|
||||
if (createEmojis.length) {
|
||||
// Prepares records for model CustomEmoji
|
||||
const emojiRecords = (await this.prepareRecords({
|
||||
createRaws: getRawRecordPairs(createEmojis),
|
||||
transformer: transformCustomEmojiRecord,
|
||||
tableName: CUSTOM_EMOJI,
|
||||
})) as CustomEmojiModel[];
|
||||
batchRecords = batchRecords.concat(emojiRecords);
|
||||
}
|
||||
|
||||
batchRecords = batchRecords.concat(deleteReactions);
|
||||
|
||||
if (prepareRecordsOnly) {
|
||||
return batchRecords;
|
||||
}
|
||||
|
||||
@@ -5,18 +5,23 @@ import ServerDataOperatorBase from '@database/operator/server_data_operator/hand
|
||||
import ChannelHandler, {ChannelHandlerMix} from '@database/operator/server_data_operator/handlers/channel';
|
||||
import GroupHandler, {GroupHandlerMix} from '@database/operator/server_data_operator/handlers/group';
|
||||
import PostHandler, {PostHandlerMix} from '@database/operator/server_data_operator/handlers/post';
|
||||
import PostsInChannelHandler, {PostsInChannelHandlerMix} from '@database/operator/server_data_operator/handlers/posts_in_channel';
|
||||
import PostsInThreadHandler, {PostsInThreadHandlerMix} from '@database/operator/server_data_operator/handlers/posts_in_thread';
|
||||
import TeamHandler, {TeamHandlerMix} from '@database/operator/server_data_operator/handlers/team';
|
||||
import UserHandler, {UserHandlerMix} from '@database/operator/server_data_operator/handlers/user';
|
||||
import mix from '@utils/mix';
|
||||
|
||||
import type {Database} from '@nozbe/watermelondb';
|
||||
|
||||
interface ServerDataOperator extends ServerDataOperatorBase, PostHandlerMix, UserHandlerMix, GroupHandlerMix, ChannelHandlerMix, TeamHandlerMix {}
|
||||
interface ServerDataOperator extends ServerDataOperatorBase, PostHandlerMix, PostsInChannelHandlerMix,
|
||||
PostsInThreadHandlerMix, UserHandlerMix, GroupHandlerMix, ChannelHandlerMix, TeamHandlerMix {}
|
||||
|
||||
class ServerDataOperator extends mix(ServerDataOperatorBase).with(
|
||||
ChannelHandler,
|
||||
GroupHandler,
|
||||
PostHandler,
|
||||
PostsInChannelHandler,
|
||||
PostsInThreadHandler,
|
||||
TeamHandler,
|
||||
UserHandler,
|
||||
) {
|
||||
|
||||
@@ -96,6 +96,7 @@ describe('*** CHANNEL Prepare Records Test ***', () => {
|
||||
value: {
|
||||
record: undefined,
|
||||
raw: {
|
||||
id: 'c',
|
||||
channel_id: 'c',
|
||||
guest_count: 10,
|
||||
header: 'channel info header',
|
||||
|
||||
@@ -65,8 +65,7 @@ export const transformMyChannelSettingsRecord = ({action, database, value}: Tran
|
||||
const isCreateAction = action === OperationType.CREATE;
|
||||
|
||||
const fieldsMapper = (myChannelSetting: MyChannelSettingsModel) => {
|
||||
myChannelSetting._raw.id = isCreateAction ? myChannelSetting.id : record.id;
|
||||
myChannelSetting.channelId = raw.channel_id;
|
||||
myChannelSetting._raw.id = isCreateAction ? (raw.channel_id || myChannelSetting.id) : record.id;
|
||||
myChannelSetting.notifyProps = raw.notify_props;
|
||||
};
|
||||
|
||||
@@ -92,8 +91,7 @@ export const transformChannelInfoRecord = ({action, database, value}: Transforme
|
||||
const isCreateAction = action === OperationType.CREATE;
|
||||
|
||||
const fieldsMapper = (channelInfo: ChannelInfoModel) => {
|
||||
channelInfo._raw.id = isCreateAction ? channelInfo.id : record.id;
|
||||
channelInfo.channelId = raw.channel_id;
|
||||
channelInfo._raw.id = isCreateAction ? (raw.id || channelInfo.id) : record.id;
|
||||
channelInfo.guestCount = raw.guest_count;
|
||||
channelInfo.header = raw.header;
|
||||
channelInfo.memberCount = raw.member_count;
|
||||
@@ -123,8 +121,7 @@ export const transformMyChannelRecord = ({action, database, value}: TransformerA
|
||||
const isCreateAction = action === OperationType.CREATE;
|
||||
|
||||
const fieldsMapper = (myChannel: MyChannelModel) => {
|
||||
myChannel._raw.id = isCreateAction ? myChannel.id : record.id;
|
||||
myChannel.channelId = raw.channel_id;
|
||||
myChannel._raw.id = isCreateAction ? (raw.channel_id || myChannel.id) : record.id;
|
||||
myChannel.roles = raw.roles;
|
||||
myChannel.messageCount = raw.msg_count;
|
||||
myChannel.mentionsCount = raw.mention_count;
|
||||
|
||||
@@ -76,7 +76,7 @@ export const transformPostInThreadRecord = ({action, database, value}: Transform
|
||||
const isCreateAction = action === OperationType.CREATE;
|
||||
|
||||
const fieldsMapper = (postsInThread: PostsInThreadModel) => {
|
||||
postsInThread.postId = isCreateAction ? raw.post_id : record.id;
|
||||
postsInThread._raw.id = isCreateAction ? raw.id : record.id;
|
||||
postsInThread.earliest = raw.earliest;
|
||||
postsInThread.latest = raw.latest!;
|
||||
};
|
||||
@@ -104,7 +104,7 @@ export const transformFileRecord = ({action, database, value}: TransformerArgs):
|
||||
|
||||
// If isCreateAction is true, we will use the id (API response) from the RAW, else we shall use the existing record id from the database
|
||||
const fieldsMapper = (file: FileModel) => {
|
||||
file._raw.id = isCreateAction ? (raw?.id ?? file.id) : record.id;
|
||||
file._raw.id = isCreateAction ? (raw.id || file.id) : record.id;
|
||||
file.postId = raw.post_id;
|
||||
file.name = raw.name;
|
||||
file.extension = raw.extension;
|
||||
@@ -138,9 +138,8 @@ export const transformPostMetadataRecord = ({action, database, value}: Transform
|
||||
const isCreateAction = action === OperationType.CREATE;
|
||||
|
||||
const fieldsMapper = (postMeta: PostMetadataModel) => {
|
||||
postMeta._raw.id = isCreateAction ? postMeta.id : record.id;
|
||||
postMeta._raw.id = isCreateAction ? (raw.id || postMeta.id) : record.id;
|
||||
postMeta.data = raw.data;
|
||||
postMeta.postId = raw.post_id;
|
||||
};
|
||||
|
||||
return prepareBaseRecord({
|
||||
@@ -194,8 +193,7 @@ export const transformPostsInChannelRecord = ({action, database, value}: Transfo
|
||||
const isCreateAction = action === OperationType.CREATE;
|
||||
|
||||
const fieldsMapper = (postsInChannel: PostsInChannelModel) => {
|
||||
postsInChannel._raw.id = isCreateAction ? postsInChannel.id : record.id;
|
||||
postsInChannel.channelId = raw.channel_id;
|
||||
postsInChannel._raw.id = isCreateAction ? (raw.channel_id || postsInChannel.id) : record.id;
|
||||
postsInChannel.earliest = raw.earliest;
|
||||
postsInChannel.latest = raw.latest;
|
||||
};
|
||||
|
||||
@@ -62,7 +62,7 @@ describe('*** TEAM Prepare Records Test ***', () => {
|
||||
value: {
|
||||
record: undefined,
|
||||
raw: {
|
||||
team_id: 'teamA',
|
||||
id: 'teamA',
|
||||
roles: 'roleA, roleB, roleC',
|
||||
is_unread: true,
|
||||
mentions_count: 3,
|
||||
@@ -122,7 +122,7 @@ describe('*** TEAM Prepare Records Test ***', () => {
|
||||
value: {
|
||||
record: undefined,
|
||||
raw: {
|
||||
team_id: 'a',
|
||||
id: 'a',
|
||||
channel_ids: ['ca', 'cb'],
|
||||
},
|
||||
},
|
||||
|
||||
@@ -98,8 +98,7 @@ export const transformTeamChannelHistoryRecord = ({action, database, value}: Tra
|
||||
const isCreateAction = action === OperationType.CREATE;
|
||||
|
||||
const fieldsMapper = (teamChannelHistory: TeamChannelHistoryModel) => {
|
||||
teamChannelHistory._raw.id = isCreateAction ? (teamChannelHistory.id) : record.id;
|
||||
teamChannelHistory.teamId = raw.team_id;
|
||||
teamChannelHistory._raw.id = isCreateAction ? (raw.id || teamChannelHistory.id) : record.id;
|
||||
teamChannelHistory.channelIds = raw.channel_ids;
|
||||
};
|
||||
|
||||
@@ -189,8 +188,7 @@ export const transformMyTeamRecord = ({action, database, value}: TransformerArgs
|
||||
const isCreateAction = action === OperationType.CREATE;
|
||||
|
||||
const fieldsMapper = (myTeam: MyTeamModel) => {
|
||||
myTeam._raw.id = isCreateAction ? myTeam.id : record.id;
|
||||
myTeam.teamId = raw.team_id;
|
||||
myTeam._raw.id = isCreateAction ? (raw.id || myTeam.id) : record.id;
|
||||
myTeam.roles = raw.roles;
|
||||
myTeam.isUnread = raw.is_unread;
|
||||
myTeam.mentionsCount = raw.mentions_count;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import type {ChainPostsArgs, RecordPair, SanitizePostsArgs} from '@typings/database/database';
|
||||
import type {ChainPostsArgs, SanitizePostsArgs} from '@typings/database/database';
|
||||
|
||||
/**
|
||||
* sanitizePosts: Creates arrays of ordered and unordered posts. Unordered posts are those posts that are not
|
||||
@@ -32,29 +32,36 @@ export const sanitizePosts = ({posts, orders}: SanitizePostsArgs) => {
|
||||
* createPostsChain: Basically creates the 'chain of posts' using the 'orders' array; each post is linked to the other
|
||||
* by the previous_post_id field.
|
||||
* @param {ChainPostsArgs} chainPosts
|
||||
* @param {string[]} chainPosts.orders
|
||||
* @param {Post[]} chainPosts.rawPosts
|
||||
* @param {string[]} chainPosts.order
|
||||
* @param {Post[]} chainPosts.posts
|
||||
* @param {string} chainPosts.previousPostId
|
||||
* @returns {Post[]}
|
||||
*/
|
||||
export const createPostsChain = ({orders, rawPosts, previousPostId = ''}: ChainPostsArgs) => {
|
||||
const posts: RecordPair[] = [];
|
||||
export const createPostsChain = ({order, posts, previousPostId = ''}: ChainPostsArgs) => {
|
||||
return order.reduce((result, id, index) => {
|
||||
const post = posts.find((p) => p.id === id);
|
||||
|
||||
rawPosts.forEach((post) => {
|
||||
const postId = post.id;
|
||||
const orderIndex = orders.findIndex((order) => {
|
||||
return order === postId;
|
||||
});
|
||||
|
||||
if (orderIndex === -1) {
|
||||
// This case will not occur as we are using 'ordered' posts for this step. However, if this happens, that
|
||||
// implies that we might be dealing with an unordered post and in which case we do not action on it.
|
||||
} else if (orderIndex === 0) {
|
||||
posts.push({record: undefined, raw: {...post, prev_post_id: previousPostId}});
|
||||
} else {
|
||||
posts.push({record: undefined, raw: {...post, prev_post_id: orders[orderIndex - 1]}});
|
||||
if (post) {
|
||||
if (index === order.length - 1) {
|
||||
result.push({...post, prev_post_id: previousPostId});
|
||||
} else {
|
||||
result.push({...post, prev_post_id: order[index + 1]});
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}, [] as Post[]);
|
||||
};
|
||||
|
||||
export const getPostListEdges = (posts: Post[]) => {
|
||||
// Sort a clone of 'posts' array by create_at
|
||||
const sortedPosts = [...posts].sort((a, b) => {
|
||||
return a.create_at - b.create_at;
|
||||
});
|
||||
|
||||
return posts;
|
||||
// The first element (beginning of chain)
|
||||
const firstPost = sortedPosts[0];
|
||||
const lastPost = sortedPosts[sortedPosts.length - 1];
|
||||
|
||||
return {firstPost, lastPost};
|
||||
};
|
||||
|
||||
@@ -17,7 +17,7 @@ const {REACTION} = MM_TABLES.SERVER;
|
||||
* @param {Database} sanitizeReactions.database
|
||||
* @param {string} sanitizeReactions.post_id
|
||||
* @param {RawReaction[]} sanitizeReactions.rawReactions
|
||||
* @returns {Promise<{createReactions: RawReaction[], createEmojis: {name: string}[], deleteReactions: Reaction[]}>}
|
||||
* @returns {Promise<{createReactions: RawReaction[], deleteReactions: Reaction[]}>}
|
||||
*/
|
||||
export const sanitizeReactions = async ({database, post_id, rawReactions}: SanitizeReactionsArgs) => {
|
||||
const reactions = (await database.collections.
|
||||
@@ -30,28 +30,18 @@ export const sanitizeReactions = async ({database, post_id, rawReactions}: Sanit
|
||||
|
||||
const createReactions: RecordPair[] = [];
|
||||
|
||||
const emojiSet = new Set();
|
||||
|
||||
for (let i = 0; i < rawReactions.length; i++) {
|
||||
const rawReaction = rawReactions[i];
|
||||
const raw = rawReactions[i];
|
||||
|
||||
// Do we have a similar value of rawReaction in the REACTION table?
|
||||
const idxPresent = reactions.findIndex((value) => {
|
||||
return (
|
||||
value.userId === rawReaction.user_id &&
|
||||
value.emojiName === rawReaction.emoji_name
|
||||
);
|
||||
});
|
||||
// If the reaction is not present let's add it to the db
|
||||
const exists = reactions.find((r) => (
|
||||
r.userId === raw.user_id &&
|
||||
r.emojiName === raw.emoji_name));
|
||||
|
||||
if (idxPresent === -1) {
|
||||
// So, we don't have a similar Reaction object. That one is new...so we'll create it
|
||||
createReactions.push({record: undefined, raw: rawReaction});
|
||||
|
||||
// If that reaction is new, that implies that the emoji might also be new
|
||||
emojiSet.add(rawReaction.emoji_name);
|
||||
if (exists) {
|
||||
similarObjects.push(exists);
|
||||
} else {
|
||||
// we have a similar object in both reactions and rawReactions; we'll pop it out from both arrays
|
||||
similarObjects.push(reactions[idxPresent]);
|
||||
createReactions.push({raw});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -60,9 +50,5 @@ export const sanitizeReactions = async ({database, post_id, rawReactions}: Sanit
|
||||
filter((reaction) => !similarObjects.includes(reaction)).
|
||||
map((outCast) => outCast.prepareDestroyPermanently());
|
||||
|
||||
const createEmojis = Array.from(emojiSet).map((emoji) => {
|
||||
return {name: emoji};
|
||||
});
|
||||
|
||||
return {createReactions, createEmojis, deleteReactions};
|
||||
return {createReactions, deleteReactions};
|
||||
};
|
||||
|
||||
@@ -22,46 +22,46 @@ describe('DataOperator: Utils tests', () => {
|
||||
it('=> createPostsChain: should link posts amongst each other based on order array', () => {
|
||||
const previousPostId = 'prev_xxyuoxmehne';
|
||||
const chainedOfPosts = createPostsChain({
|
||||
orders: mockedPosts.order,
|
||||
rawPosts: Object.values(mockedPosts.posts) as Post[],
|
||||
order: mockedPosts.order,
|
||||
posts: Object.values(mockedPosts.posts) as Post[],
|
||||
previousPostId,
|
||||
});
|
||||
|
||||
// eslint-disable-next-line max-nested-callbacks
|
||||
const post1 = chainedOfPosts.find((post) => {
|
||||
const p = post.raw as Post;
|
||||
const p = post;
|
||||
return p.id === '8swgtrrdiff89jnsiwiip3y1eoe';
|
||||
})?.raw as Post;
|
||||
});
|
||||
|
||||
expect(post1).toBeTruthy();
|
||||
expect(post1?.prev_post_id).toBe(previousPostId);
|
||||
expect(post1?.prev_post_id).toBe('8fcnk3p1jt8mmkaprgajoxz115a');
|
||||
|
||||
// eslint-disable-next-line max-nested-callbacks
|
||||
const post2 = chainedOfPosts.find((post) => {
|
||||
const p = post.raw as Post;
|
||||
const p = post;
|
||||
return p.id === '8fcnk3p1jt8mmkaprgajoxz115a';
|
||||
})?.raw as Post;
|
||||
});
|
||||
|
||||
expect(post2).toBeTruthy();
|
||||
expect(post2!.prev_post_id).toBe('8swgtrrdiff89jnsiwiip3y1eoe');
|
||||
expect(post2!.prev_post_id).toBe('3y3w3a6gkbg73bnj3xund9o5ic');
|
||||
|
||||
// eslint-disable-next-line max-nested-callbacks
|
||||
const post3 = chainedOfPosts.find((post) => {
|
||||
const p = post.raw as Post;
|
||||
const p = post;
|
||||
return p.id === '3y3w3a6gkbg73bnj3xund9o5ic';
|
||||
})?.raw as Post;
|
||||
});
|
||||
|
||||
expect(post3).toBeTruthy();
|
||||
expect(post3?.prev_post_id).toBe('8fcnk3p1jt8mmkaprgajoxz115a');
|
||||
expect(post3?.prev_post_id).toBe('4btbnmticjgw7ewd3qopmpiwqw');
|
||||
|
||||
// eslint-disable-next-line max-nested-callbacks
|
||||
const post4 = chainedOfPosts.find((post) => {
|
||||
const p = post.raw as Post;
|
||||
const p = post;
|
||||
return p.id === '4btbnmticjgw7ewd3qopmpiwqw';
|
||||
})?.raw as Post;
|
||||
});
|
||||
|
||||
expect(post4).toBeTruthy();
|
||||
expect(post4!.prev_post_id).toBe('3y3w3a6gkbg73bnj3xund9o5ic');
|
||||
expect(post4!.prev_post_id).toBe(previousPostId);
|
||||
});
|
||||
|
||||
it('=> sanitizeReactions: should triage between reactions that needs creation/deletion and emojis to be created', async () => {
|
||||
@@ -76,14 +76,17 @@ describe('DataOperator: Utils tests', () => {
|
||||
|
||||
// we commit one Reaction to our database
|
||||
const prepareRecords = await server?.operator.handleReactions({
|
||||
reactions: [
|
||||
{
|
||||
user_id: 'beqkgo4wzbn98kjzjgc1p5n91o',
|
||||
post_id: '8ww8kb1dbpf59fu4d5xhu5nf5w',
|
||||
emoji_name: 'tada_will_be_removed',
|
||||
create_at: 1601558322701,
|
||||
},
|
||||
],
|
||||
postsReactions: [{
|
||||
post_id: '8ww8kb1dbpf59fu4d5xhu5nf5w',
|
||||
reactions: [
|
||||
{
|
||||
user_id: 'beqkgo4wzbn98kjzjgc1p5n91o',
|
||||
post_id: '8ww8kb1dbpf59fu4d5xhu5nf5w',
|
||||
emoji_name: 'tada_will_be_removed',
|
||||
create_at: 1601558322701,
|
||||
},
|
||||
],
|
||||
}],
|
||||
prepareRecordsOnly: true,
|
||||
}) as ReactionModel[];
|
||||
|
||||
@@ -95,7 +98,6 @@ describe('DataOperator: Utils tests', () => {
|
||||
|
||||
const {
|
||||
createReactions,
|
||||
createEmojis,
|
||||
deleteReactions,
|
||||
} = await sanitizeReactions({
|
||||
database: server!.database,
|
||||
@@ -108,7 +110,5 @@ describe('DataOperator: Utils tests', () => {
|
||||
expect(deleteReactions[0].emojiName).toBe('tada_will_be_removed');
|
||||
|
||||
expect(createReactions.length).toBe(3);
|
||||
|
||||
expect(createEmojis.length).toBe(3);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -10,7 +10,6 @@ const {CHANNEL_INFO} = MM_TABLES.SERVER;
|
||||
export default tableSchema({
|
||||
name: CHANNEL_INFO,
|
||||
columns: [
|
||||
{name: 'channel_id', type: 'string', isIndexed: true},
|
||||
{name: 'guest_count', type: 'number'},
|
||||
{name: 'header', type: 'string'},
|
||||
{name: 'member_count', type: 'number'},
|
||||
|
||||
@@ -10,7 +10,6 @@ const {MY_CHANNEL} = MM_TABLES.SERVER;
|
||||
export default tableSchema({
|
||||
name: MY_CHANNEL,
|
||||
columns: [
|
||||
{name: 'channel_id', type: 'string', isIndexed: true},
|
||||
{name: 'last_post_at', type: 'number'},
|
||||
{name: 'last_viewed_at', type: 'number'},
|
||||
{name: 'mentions_count', type: 'number'},
|
||||
|
||||
@@ -10,7 +10,6 @@ const {MY_CHANNEL_SETTINGS} = MM_TABLES.SERVER;
|
||||
export default tableSchema({
|
||||
name: MY_CHANNEL_SETTINGS,
|
||||
columns: [
|
||||
{name: 'channel_id', type: 'string', isIndexed: true},
|
||||
{name: 'notify_props', type: 'string'},
|
||||
],
|
||||
});
|
||||
|
||||
@@ -13,6 +13,5 @@ export default tableSchema({
|
||||
{name: 'is_unread', type: 'boolean'},
|
||||
{name: 'mentions_count', type: 'number'},
|
||||
{name: 'roles', type: 'string'},
|
||||
{name: 'team_id', type: 'string', isIndexed: true},
|
||||
],
|
||||
});
|
||||
|
||||
@@ -18,7 +18,7 @@ export default tableSchema({
|
||||
{name: 'is_pinned', type: 'boolean'},
|
||||
{name: 'message', type: 'string'},
|
||||
{name: 'original_id', type: 'string'},
|
||||
{name: 'pending_post_id', type: 'string'},
|
||||
{name: 'pending_post_id', type: 'string', isIndexed: true},
|
||||
{name: 'previous_post_id', type: 'string'},
|
||||
{name: 'props', type: 'string'},
|
||||
{name: 'root_id', type: 'string'},
|
||||
|
||||
@@ -11,6 +11,5 @@ export default tableSchema({
|
||||
name: POST_METADATA,
|
||||
columns: [
|
||||
{name: 'data', type: 'string'},
|
||||
{name: 'post_id', type: 'string', isIndexed: true},
|
||||
],
|
||||
});
|
||||
|
||||
@@ -10,7 +10,6 @@ const {POSTS_IN_CHANNEL} = MM_TABLES.SERVER;
|
||||
export default tableSchema({
|
||||
name: POSTS_IN_CHANNEL,
|
||||
columns: [
|
||||
{name: 'channel_id', type: 'string', isIndexed: true},
|
||||
{name: 'earliest', type: 'number'},
|
||||
{name: 'latest', type: 'number'},
|
||||
],
|
||||
|
||||
@@ -12,6 +12,5 @@ export default tableSchema({
|
||||
columns: [
|
||||
{name: 'earliest', type: 'number'},
|
||||
{name: 'latest', type: 'number'},
|
||||
{name: 'post_id', type: 'string', isIndexed: true},
|
||||
],
|
||||
});
|
||||
|
||||
@@ -11,6 +11,5 @@ export default tableSchema({
|
||||
name: TEAM_CHANNEL_HISTORY,
|
||||
columns: [
|
||||
{name: 'channel_ids', type: 'string'},
|
||||
{name: 'team_id', type: 'string', isIndexed: true},
|
||||
],
|
||||
});
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
/* eslint-disable max-lines */
|
||||
|
||||
import {MM_TABLES} from '@constants/database';
|
||||
|
||||
import {serverSchema} from './index';
|
||||
@@ -44,7 +46,6 @@ describe('*** Test schema for SERVER database ***', () => {
|
||||
[CHANNEL_INFO]: {
|
||||
name: CHANNEL_INFO,
|
||||
columns: {
|
||||
channel_id: {name: 'channel_id', type: 'string', isIndexed: true},
|
||||
guest_count: {name: 'guest_count', type: 'number'},
|
||||
header: {name: 'header', type: 'string'},
|
||||
member_count: {name: 'member_count', type: 'number'},
|
||||
@@ -52,7 +53,6 @@ describe('*** Test schema for SERVER database ***', () => {
|
||||
purpose: {name: 'purpose', type: 'string'},
|
||||
},
|
||||
columnArray: [
|
||||
{name: 'channel_id', type: 'string', isIndexed: true},
|
||||
{name: 'guest_count', type: 'number'},
|
||||
{name: 'header', type: 'string'},
|
||||
{name: 'member_count', type: 'number'},
|
||||
@@ -110,7 +110,6 @@ describe('*** Test schema for SERVER database ***', () => {
|
||||
[MY_CHANNEL]: {
|
||||
name: MY_CHANNEL,
|
||||
columns: {
|
||||
channel_id: {name: 'channel_id', type: 'string', isIndexed: true},
|
||||
last_post_at: {name: 'last_post_at', type: 'number'},
|
||||
last_viewed_at: {name: 'last_viewed_at', type: 'number'},
|
||||
mentions_count: {name: 'mentions_count', type: 'number'},
|
||||
@@ -118,7 +117,6 @@ describe('*** Test schema for SERVER database ***', () => {
|
||||
roles: {name: 'roles', type: 'string'},
|
||||
},
|
||||
columnArray: [
|
||||
{name: 'channel_id', type: 'string', isIndexed: true},
|
||||
{name: 'last_post_at', type: 'number'},
|
||||
{name: 'last_viewed_at', type: 'number'},
|
||||
{name: 'mentions_count', type: 'number'},
|
||||
@@ -129,23 +127,19 @@ describe('*** Test schema for SERVER database ***', () => {
|
||||
[MY_CHANNEL_SETTINGS]: {
|
||||
name: MY_CHANNEL_SETTINGS,
|
||||
columns: {
|
||||
channel_id: {name: 'channel_id', type: 'string', isIndexed: true},
|
||||
notify_props: {name: 'notify_props', type: 'string'},
|
||||
},
|
||||
columnArray: [
|
||||
{name: 'channel_id', type: 'string', isIndexed: true},
|
||||
{name: 'notify_props', type: 'string'},
|
||||
],
|
||||
},
|
||||
[POSTS_IN_CHANNEL]: {
|
||||
name: POSTS_IN_CHANNEL,
|
||||
columns: {
|
||||
channel_id: {name: 'channel_id', type: 'string', isIndexed: true},
|
||||
earliest: {name: 'earliest', type: 'number'},
|
||||
latest: {name: 'latest', type: 'number'},
|
||||
},
|
||||
columnArray: [
|
||||
{name: 'channel_id', type: 'string', isIndexed: true},
|
||||
{name: 'earliest', type: 'number'},
|
||||
{name: 'latest', type: 'number'},
|
||||
],
|
||||
@@ -195,23 +189,19 @@ describe('*** Test schema for SERVER database ***', () => {
|
||||
columns: {
|
||||
earliest: {name: 'earliest', type: 'number'},
|
||||
latest: {name: 'latest', type: 'number'},
|
||||
post_id: {name: 'post_id', type: 'string', isIndexed: true},
|
||||
},
|
||||
columnArray: [
|
||||
{name: 'earliest', type: 'number'},
|
||||
{name: 'latest', type: 'number'},
|
||||
{name: 'post_id', type: 'string', isIndexed: true},
|
||||
],
|
||||
},
|
||||
[POST_METADATA]: {
|
||||
name: POST_METADATA,
|
||||
columns: {
|
||||
data: {name: 'data', type: 'string'},
|
||||
post_id: {name: 'post_id', type: 'string', isIndexed: true},
|
||||
},
|
||||
columnArray: [
|
||||
{name: 'data', type: 'string'},
|
||||
{name: 'post_id', type: 'string', isIndexed: true},
|
||||
],
|
||||
},
|
||||
[POST]: {
|
||||
@@ -225,7 +215,7 @@ describe('*** Test schema for SERVER database ***', () => {
|
||||
is_pinned: {name: 'is_pinned', type: 'boolean'},
|
||||
message: {name: 'message', type: 'string'},
|
||||
original_id: {name: 'original_id', type: 'string'},
|
||||
pending_post_id: {name: 'pending_post_id', type: 'string'},
|
||||
pending_post_id: {name: 'pending_post_id', type: 'string', isIndexed: true},
|
||||
previous_post_id: {name: 'previous_post_id', type: 'string'},
|
||||
props: {name: 'props', type: 'string'},
|
||||
root_id: {name: 'root_id', type: 'string'},
|
||||
@@ -241,7 +231,7 @@ describe('*** Test schema for SERVER database ***', () => {
|
||||
{name: 'is_pinned', type: 'boolean'},
|
||||
{name: 'message', type: 'string'},
|
||||
{name: 'original_id', type: 'string'},
|
||||
{name: 'pending_post_id', type: 'string'},
|
||||
{name: 'pending_post_id', type: 'string', isIndexed: true},
|
||||
{name: 'previous_post_id', type: 'string'},
|
||||
{name: 'props', type: 'string'},
|
||||
{name: 'root_id', type: 'string'},
|
||||
@@ -333,13 +323,11 @@ describe('*** Test schema for SERVER database ***', () => {
|
||||
is_unread: {name: 'is_unread', type: 'boolean'},
|
||||
mentions_count: {name: 'mentions_count', type: 'number'},
|
||||
roles: {name: 'roles', type: 'string'},
|
||||
team_id: {name: 'team_id', type: 'string', isIndexed: true},
|
||||
},
|
||||
columnArray: [
|
||||
{name: 'is_unread', type: 'boolean'},
|
||||
{name: 'mentions_count', type: 'number'},
|
||||
{name: 'roles', type: 'string'},
|
||||
{name: 'team_id', type: 'string', isIndexed: true},
|
||||
],
|
||||
},
|
||||
[ROLE]: {
|
||||
@@ -425,11 +413,9 @@ describe('*** Test schema for SERVER database ***', () => {
|
||||
name: TEAM_CHANNEL_HISTORY,
|
||||
columns: {
|
||||
channel_ids: {name: 'channel_ids', type: 'string'},
|
||||
team_id: {name: 'team_id', type: 'string', isIndexed: true},
|
||||
},
|
||||
columnArray: [
|
||||
{name: 'channel_ids', type: 'string'},
|
||||
{name: 'team_id', type: 'string', isIndexed: true},
|
||||
],
|
||||
},
|
||||
[TEAM_MEMBERSHIP]: {
|
||||
|
||||
@@ -9,7 +9,7 @@ export const prepareMyTeams = (operator: ServerDataOperator, teams: Team[], memb
|
||||
const teamMembershipRecords = operator.handleTeamMemberships({prepareRecordsOnly: true, teamMemberships: memberships});
|
||||
const myTeams: MyTeam[] = unreads.map((unread) => {
|
||||
const matchingTeam = memberships.find((team) => team.team_id === unread.team_id);
|
||||
return {team_id: unread.team_id, roles: matchingTeam?.roles ?? '', is_unread: unread.msg_count > 0, mentions_count: unread.mention_count};
|
||||
return {id: unread.team_id, roles: matchingTeam?.roles ?? '', is_unread: unread.msg_count > 0, mentions_count: unread.mention_count};
|
||||
});
|
||||
const myTeamRecords = operator.handleMyTeam({
|
||||
prepareRecordsOnly: true,
|
||||
|
||||
11
types/database/database.d.ts
vendored
11
types/database/database.d.ts
vendored
@@ -69,7 +69,7 @@ export type CreateServerDatabaseArgs = {
|
||||
|
||||
export type HandleReactionsArgs = {
|
||||
prepareRecordsOnly: boolean;
|
||||
reactions: Reaction[];
|
||||
postsReactions: ReactionsPerPost[];
|
||||
};
|
||||
|
||||
export type HandleFilesArgs = {
|
||||
@@ -83,9 +83,10 @@ export type HandlePostMetadataArgs = {
|
||||
};
|
||||
|
||||
export type HandlePostsArgs = {
|
||||
orders: string[];
|
||||
actionType: string;
|
||||
order: string[];
|
||||
previousPostId?: string;
|
||||
values: Post[];
|
||||
posts: Post[];
|
||||
};
|
||||
|
||||
export type SanitizeReactionsArgs = {
|
||||
@@ -95,9 +96,9 @@ export type SanitizeReactionsArgs = {
|
||||
};
|
||||
|
||||
export type ChainPostsArgs = {
|
||||
orders: string[];
|
||||
order: string[];
|
||||
previousPostId: string;
|
||||
rawPosts: Post[];
|
||||
posts: Post[];
|
||||
};
|
||||
|
||||
export type SanitizePostsArgs = {
|
||||
|
||||
@@ -16,9 +16,6 @@ export default class ChannelInfoModel extends Model {
|
||||
/** associations : Describes every relationship to this table. */
|
||||
static associations: Associations;
|
||||
|
||||
/** channel_id : The foreign key from CHANNEL */
|
||||
channelId: string;
|
||||
|
||||
/** guest_count : The number of guest in this channel */
|
||||
guestCount: number;
|
||||
|
||||
|
||||
@@ -14,9 +14,6 @@ export default class MyChannelModel extends Model {
|
||||
/** associations : Describes every relationship to this table. */
|
||||
static associations: Associations;
|
||||
|
||||
/** channel_id : The foreign key to the related Channel record */
|
||||
channelId: string;
|
||||
|
||||
/** last_post_at : The timestamp for any last post on this channel */
|
||||
lastPostAt: number;
|
||||
|
||||
|
||||
@@ -15,9 +15,6 @@ export default class MyChannelSettingsModel extends Model {
|
||||
/** associations : Describes every relationship to this table. */
|
||||
static associations: Associations;
|
||||
|
||||
/** channel_id : The foreign key to the related CHANNEL record */
|
||||
channelId: string;
|
||||
|
||||
/** notify_props : Configurations with regards to this channel */
|
||||
notifyProps: Partial<ChannelNotifyProps>;
|
||||
|
||||
|
||||
3
types/database/models/servers/my_team.d.ts
vendored
3
types/database/models/servers/my_team.d.ts
vendored
@@ -23,9 +23,6 @@ export default class MyTeamModel extends Model {
|
||||
/** roles : The different permissions that this user has in the team, concatenated together with comma to form a single string. */
|
||||
roles: string;
|
||||
|
||||
/** team_id : The foreign key of the 'parent' Team table */
|
||||
teamId: string;
|
||||
|
||||
/** team : The relation to the TEAM table, that this user belongs to */
|
||||
team: Relation<TeamModel>;
|
||||
}
|
||||
|
||||
@@ -14,9 +14,6 @@ export default class PostMetadataModel extends Model {
|
||||
/** associations : Describes every relationship to this table. */
|
||||
static associations: Associations;
|
||||
|
||||
/** post_id : The foreign key of the parent POST model */
|
||||
postId: string;
|
||||
|
||||
/** data : Different types of data ranging from arrays, emojis, files to images and reactions. */
|
||||
data: PostMetadata;
|
||||
|
||||
|
||||
@@ -15,9 +15,6 @@ export default class PostsInChannelModel extends Model {
|
||||
/** associations : Describes every relationship to this table. */
|
||||
static associations: Associations;
|
||||
|
||||
/** channel_id : The foreign key of the related parent channel */
|
||||
channelId: string;
|
||||
|
||||
/** earliest : The earliest timestamp of the post in that channel */
|
||||
earliest: number;
|
||||
|
||||
|
||||
@@ -21,9 +21,6 @@ export default class PostsInThreadModel extends Model {
|
||||
/** latest : Upper bound of a timestamp range */
|
||||
latest: number;
|
||||
|
||||
/** post_id : The foreign key of the related Post model */
|
||||
postId: string;
|
||||
|
||||
/** post : The related record to the parent Post model */
|
||||
post: Relation<PostModel>;
|
||||
}
|
||||
|
||||
@@ -15,9 +15,6 @@ export default class TeamChannelHistoryModel extends Model {
|
||||
/** associations : Describes every relationship to this table. */
|
||||
static associations: Associations;
|
||||
|
||||
/** team_id : The foreign key to the related Team record */
|
||||
teamId: string;
|
||||
|
||||
/** channel_ids : An array containing the last 5 channels visited within this team order by recency */
|
||||
channelIds: string[];
|
||||
|
||||
|
||||
13
types/database/raw_values.d.ts
vendored
13
types/database/raw_values.d.ts
vendored
@@ -8,7 +8,7 @@ type AppInfo = {
|
||||
};
|
||||
|
||||
type ChannelInfo = {
|
||||
channel_id: string;
|
||||
id: string;
|
||||
guest_count: number;
|
||||
header: string;
|
||||
member_count: number;
|
||||
@@ -30,7 +30,7 @@ type GroupMembership = {
|
||||
};
|
||||
|
||||
type MyTeam = {
|
||||
team_id: string;
|
||||
id: string;
|
||||
roles: string;
|
||||
is_unread: boolean;
|
||||
mentions_count: number;
|
||||
@@ -45,12 +45,17 @@ type PostsInChannel = {
|
||||
type PostsInThread = {
|
||||
earliest: number;
|
||||
latest?: number;
|
||||
post_id: string;
|
||||
id: string;
|
||||
};
|
||||
|
||||
type Metadata = {
|
||||
data: PostMetadata;
|
||||
id: string;
|
||||
}
|
||||
|
||||
type ReactionsPerPost = {
|
||||
post_id: string;
|
||||
reactions: Reactions[];
|
||||
}
|
||||
|
||||
type IdValue = {
|
||||
@@ -59,7 +64,7 @@ type IdValue = {
|
||||
};
|
||||
|
||||
type TeamChannelHistory = {
|
||||
team_id: string;
|
||||
id: string;
|
||||
channel_ids: string[];
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user