From fe354ee396cb8adc94951bbbc72d73aa0af75a0e Mon Sep 17 00:00:00 2001 From: Shaz MJ Date: Tue, 7 Jun 2022 07:55:28 +1000 Subject: [PATCH] [Gekidou] Fetch (and save) groups on @mention auto-complete (#6323) * WIP * Actions updated to fetch remote first, and local on error * Groups fetch and save * PR Feedback: prepare vs store and undefined fix * Forgot to add file --- app/actions/local/group.ts | 95 +++++++++++++ app/actions/remote/groups.ts | 126 +++++++++--------- .../autocomplete/at_mention/at_mention.tsx | 25 ++-- .../handlers/group.test.ts | 54 ++++++++ .../server_data_operator/handlers/group.ts | 46 +++++++ .../operator/server_data_operator/index.ts | 18 ++- .../transformers/group.test.ts | 35 +++++ .../transformers/group.ts | 44 ++++++ app/queries/servers/group.ts | 35 +++++ types/database/database.d.ts | 4 + 10 files changed, 405 insertions(+), 77 deletions(-) create mode 100644 app/actions/local/group.ts create mode 100644 app/database/operator/server_data_operator/handlers/group.test.ts create mode 100644 app/database/operator/server_data_operator/handlers/group.ts create mode 100644 app/database/operator/server_data_operator/transformers/group.test.ts create mode 100644 app/database/operator/server_data_operator/transformers/group.ts create mode 100644 app/queries/servers/group.ts diff --git a/app/actions/local/group.ts b/app/actions/local/group.ts new file mode 100644 index 0000000000..2bb8dc1f93 --- /dev/null +++ b/app/actions/local/group.ts @@ -0,0 +1,95 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. + +import {fetchFilteredChannelGroups, fetchFilteredTeamGroups, fetchGroupsForAutocomplete} from '@actions/remote/groups'; +import DatabaseManager from '@database/manager'; +import {prepareGroups, queryGroupsByName, queryGroupsByNameInChannel, queryGroupsByNameInTeam} from '@queries/servers/group'; + +import type GroupModel from '@typings/database/models/servers/group'; + +export const searchGroupsByName = async (serverUrl: string, name: string): Promise => { + const operator = DatabaseManager.serverDatabases[serverUrl]?.operator; + if (!operator) { + throw new Error(`${serverUrl} operator not found`); + } + + try { + const groups = await fetchGroupsForAutocomplete(serverUrl, name); + + if (groups && Array.isArray(groups)) { + return groups; + } + throw groups.error; + } catch (e) { + // eslint-disable-next-line no-console + console.log('searchGroupsByName - ERROR', e); + return queryGroupsByName(operator.database, name).fetch(); + } +}; + +export const searchGroupsByNameInTeam = async (serverUrl: string, name: string, teamId: string): Promise => { + const operator = DatabaseManager.serverDatabases[serverUrl]?.operator; + if (!operator) { + throw new Error(`${serverUrl} operator not found`); + } + + try { + const groups = await fetchFilteredTeamGroups(serverUrl, name, teamId); + + if (groups && Array.isArray(groups)) { + return groups; + } + throw groups.error; + } catch (e) { + // eslint-disable-next-line no-console + console.log('searchGroupsByNameInTeam - ERROR', e); + return queryGroupsByNameInTeam(operator.database, name, teamId).fetch(); + } +}; + +export const searchGroupsByNameInChannel = async (serverUrl: string, name: string, channelId: string): Promise => { + const operator = DatabaseManager.serverDatabases[serverUrl]?.operator; + if (!operator) { + throw new Error(`${serverUrl} operator not found`); + } + + try { + const groups = await fetchFilteredChannelGroups(serverUrl, name, channelId); + + if (groups && Array.isArray(groups)) { + return groups; + } + throw groups.error; + } catch (e) { + // eslint-disable-next-line no-console + console.log('searchGroupsByNameInChannel - ERROR', e); + return queryGroupsByNameInChannel(operator.database, name, channelId).fetch(); + } +}; + +/** + * Store fetched groups locally + * + * @param serverUrl string - The Server URL + * @param groups Group[] - The groups fetched from the API + * @param prepareRecordsOnly boolean - Wether to only prepare records without saving + */ +export const storeGroups = async (serverUrl: string, groups: Group[]) => { + const operator = DatabaseManager.serverDatabases[serverUrl]?.operator; + if (!operator) { + throw new Error(`${serverUrl} operator not found`); + } + + try { + const preparedGroups = await prepareGroups(operator, groups); + + if (preparedGroups.length) { + operator.batchRecords(preparedGroups); + } + + return preparedGroups; + } catch (e) { + return {error: e}; + } +}; + diff --git a/app/actions/remote/groups.ts b/app/actions/remote/groups.ts index 64d1fa99b3..523c32cdda 100644 --- a/app/actions/remote/groups.ts +++ b/app/actions/remote/groups.ts @@ -1,104 +1,104 @@ // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // See LICENSE.txt for license information. +import {storeGroups} from '@actions/local/group'; +import {prepareGroups} from '@app/queries/servers/group'; import {Client} from '@client/rest'; +import DatabaseManager from '@database/manager'; import NetworkManager from '@managers/network_manager'; import {forceLogoutIfNecessary} from './session'; -export const fetchGroupsForChannel = async (serverUrl: string, channelId: string) => { - let client: Client; +export const fetchGroupsForAutocomplete = async (serverUrl: string, query: string, fetchOnly = false) => { try { - client = NetworkManager.getClient(serverUrl); - return client.getAllGroupsAssociatedToChannel(channelId); + const client: Client = NetworkManager.getClient(serverUrl); + const response = await client.getGroups(query); + + // Save locally + if (!fetchOnly) { + return await storeGroups(serverUrl, response); + } + + const operator = DatabaseManager.serverDatabases[serverUrl]?.operator; + if (!operator) { + throw new Error(`${serverUrl} operator not found`); + } + + return await prepareGroups(operator, response); } catch (error) { forceLogoutIfNecessary(serverUrl, error as ClientErrorProps); return {error}; } }; -export const fetchGroupsForMembership = async (serverUrl: string, userId: string) => { - let client: Client; +export const fetchGroupsForChannel = async (serverUrl: string, channelId: string, fetchOnly = false) => { try { - client = NetworkManager.getClient(serverUrl); - return client.getAllGroupsAssociatedToMembership(userId); + const client = NetworkManager.getClient(serverUrl); + const response = await client.getAllGroupsAssociatedToChannel(channelId); + + if (!fetchOnly) { + return await storeGroups(serverUrl, response.groups); + } + + const operator = DatabaseManager.serverDatabases[serverUrl]?.operator; + if (!operator) { + throw new Error(`${serverUrl} operator not found`); + } + + return await prepareGroups(operator, response.groups); } catch (error) { forceLogoutIfNecessary(serverUrl, error as ClientErrorProps); return {error}; } }; -export const fetchGroupsForTeam = async (serverUrl: string, teamId: string) => { - let client: Client; +export const fetchGroupsForTeam = async (serverUrl: string, teamId: string, fetchOnly = false) => { try { - client = NetworkManager.getClient(serverUrl); - return client.getAllGroupsAssociatedToTeam(teamId); + const client: Client = NetworkManager.getClient(serverUrl); + const response = await client.getAllGroupsAssociatedToTeam(teamId); + + if (!fetchOnly) { + return await storeGroups(serverUrl, response.groups); + } + + // return await storeGroups(serverUrl, response.groups, true); + const operator = DatabaseManager.serverDatabases[serverUrl]?.operator; + if (!operator) { + throw new Error(`${serverUrl} operator not found`); + } + + return await prepareGroups(operator, response.groups); } catch (error) { forceLogoutIfNecessary(serverUrl, error as ClientErrorProps); return {error}; } }; -export const fetchGroupsForAutocomplete = async (serverUrl: string, query: string) => { - let client: Client; +export const fetchFilteredTeamGroups = async (serverUrl: string, searchTerm: string, teamId: string) => { try { - client = NetworkManager.getClient(serverUrl); - return client.getGroups(query); + const groups = await fetchGroupsForTeam(serverUrl, teamId); + + if (groups && Array.isArray(groups)) { + return groups.filter((g) => g.name.toLowerCase().includes(searchTerm.toLowerCase())); + } + + throw groups.error; } catch (error) { - forceLogoutIfNecessary(serverUrl, error as ClientErrorProps); return {error}; } }; -export const fetchMembershipsForGroup = async (serverUrl: string, groupId: string) => { - let client: Client; +export const fetchFilteredChannelGroups = async (serverUrl: string, searchTerm: string, channelId: string) => { try { - client = NetworkManager.getClient(serverUrl); - return client.getAllMembershipsAssociatedToGroup(groupId); + const groups = await fetchGroupsForChannel(serverUrl, channelId); + + if (groups && Array.isArray(groups)) { + return groups.filter((g) => g.name.toLowerCase().includes(searchTerm.toLowerCase())); + } + + throw groups.error; } catch (error) { - forceLogoutIfNecessary(serverUrl, error as ClientErrorProps); return {error}; } }; -export const fetchTeamsForGroup = async (serverUrl: string, groupId: string) => { - let client: Client; - try { - client = NetworkManager.getClient(serverUrl); - return client.getAllTeamsAssociatedToGroup(groupId); - } catch (error) { - forceLogoutIfNecessary(serverUrl, error as ClientErrorProps); - return {error}; - } -}; - -export const fetchChannelsForGroup = async (serverUrl: string, groupId: string) => { - let client: Client; - try { - client = NetworkManager.getClient(serverUrl); - return client.getAllChannelsAssociatedToGroup(groupId); - } catch (error) { - forceLogoutIfNecessary(serverUrl, error as ClientErrorProps); - return {error}; - } -}; - -export const fetchFilteredTeamGroups = async (serverUrl: string, teamId: string, searchTerm: string) => { - const response = await fetchGroupsForTeam(serverUrl, teamId); - - if (response && 'groups' in response) { - return response.groups.filter((g) => g.name.toLowerCase().includes(searchTerm.toLowerCase())); - } - - return []; -}; - -export const fetchFilteredChannelGroups = async (serverUrl: string, channelId: string, searchTerm: string) => { - const response = await fetchGroupsForChannel(serverUrl, channelId); - - if (response && 'groups' in response) { - return response.groups.filter((g) => g.name.toLowerCase().includes(searchTerm.toLowerCase())); - } - - return []; -}; diff --git a/app/components/autocomplete/at_mention/at_mention.tsx b/app/components/autocomplete/at_mention/at_mention.tsx index 0dd747fa7d..9682576db9 100644 --- a/app/components/autocomplete/at_mention/at_mention.tsx +++ b/app/components/autocomplete/at_mention/at_mention.tsx @@ -5,7 +5,7 @@ import {debounce} from 'lodash'; import React, {useCallback, useEffect, useMemo, useState} from 'react'; import {Platform, SectionList, SectionListData, SectionListRenderItemInfo} from 'react-native'; -import {fetchFilteredChannelGroups, fetchFilteredTeamGroups, fetchGroupsForAutocomplete} from '@actions/remote/groups'; +import {searchGroupsByName, searchGroupsByNameInChannel, searchGroupsByNameInTeam} from '@actions/local/group'; import {searchUsers} from '@actions/remote/user'; import GroupMentionItem from '@components/autocomplete/at_mention_group/at_mention_group'; import AtMentionItem from '@components/autocomplete/at_mention_item'; @@ -19,6 +19,7 @@ import {t} from '@i18n'; import {queryAllUsers} from '@queries/servers/user'; import {makeStyleSheetFromTheme} from '@utils/theme'; +import type GroupModel from '@typings/database/models/servers/group'; import type UserModel from '@typings/database/models/servers/user'; const SECTION_KEY_TEAM_MEMBERS = 'teamMembers'; @@ -33,7 +34,7 @@ type SpecialMention = { defaultMessage: string; } -type UserMentionSections = Array> +type UserMentionSections = Array> const getMatchTermForAtMention = (() => { let lastMatchTerm: string | null = null; @@ -93,7 +94,7 @@ const filterLocalResults = (users: UserModel[], term: string) => { ); }; -const makeSections = (teamMembers: Array, usersInChannel: Array, usersOutOfChannel: Array, groups: Group[], showSpecialMentions: boolean, isLocal = false, isSearch = false) => { +const makeSections = (teamMembers: Array, usersInChannel: Array, usersOutOfChannel: Array, groups: GroupModel[], showSpecialMentions: boolean, isLocal = false, isSearch = false) => { const newSections: UserMentionSections = []; if (isSearch) { @@ -200,7 +201,7 @@ const getStyleFromTheme = makeStyleSheetFromTheme((theme) => { const emptyProfileList: UserProfile[] = []; const emptyModelList: UserModel[] = []; const emptySectionList: UserMentionSections = []; -const emptyGroupList: Group[] = []; +const emptyGroupList: GroupModel[] = []; const getAllUsers = async (serverUrl: string) => { const database = DatabaseManager.serverDatabases[serverUrl]?.database; @@ -233,7 +234,7 @@ const AtMention = ({ const [sections, setSections] = useState(emptySectionList); const [usersInChannel, setUsersInChannel] = useState(emptyProfileList); const [usersOutOfChannel, setUsersOutOfChannel] = useState(emptyProfileList); - const [groups, setGroups] = useState(emptyGroupList); + const [groups, setGroups] = useState(emptyGroupList); const [loading, setLoading] = useState(false); const [noResultsTerm, setNoResultsTerm] = useState(null); const [localCursorPosition, setLocalCursorPosition] = useState(cursorPosition); // To avoid errors due to delay between value changes and cursor position changes. @@ -311,12 +312,12 @@ const AtMention = ({ ); }, [completeMention]); - const renderGroupMentions = useCallback((item: Group) => { + const renderGroupMentions = useCallback((item: GroupModel) => { return ( ); @@ -332,12 +333,12 @@ const AtMention = ({ ); }, [completeMention]); - const renderItem = useCallback(({item, section}: SectionListRenderItemInfo) => { + const renderItem = useCallback(({item, section}: SectionListRenderItemInfo) => { switch (section.key) { case SECTION_KEY_SPECIAL: return renderSpecialMentions(item as SpecialMention); case SECTION_KEY_GROUPS: - return renderGroupMentions(item as Group); + return renderGroupMentions(item as GroupModel); default: return renderAtMentions(item as UserProfile); } @@ -363,7 +364,7 @@ const AtMention = ({ if (useGroupMentions && matchTerm && matchTerm !== '') { // If the channel is constrained, we only show groups for that channel if (isChannelConstrained && channelId) { - fetchFilteredChannelGroups(serverUrl, channelId, matchTerm).then((g) => { + searchGroupsByNameInChannel(serverUrl, matchTerm, channelId).then((g) => { setGroups(g.length ? g : emptyGroupList); }).catch(() => { setGroups(emptyGroupList); @@ -372,7 +373,7 @@ const AtMention = ({ // If there is no channel constraint, but a team constraint - only show groups for team if (isTeamConstrained && !isChannelConstrained) { - fetchFilteredTeamGroups(serverUrl, teamId!, matchTerm).then((g) => { + searchGroupsByNameInTeam(serverUrl, matchTerm, teamId!).then((g) => { setGroups(g.length ? g : emptyGroupList); }).catch(() => { setGroups(emptyGroupList); @@ -381,7 +382,7 @@ const AtMention = ({ // No constraints? Search all groups if (!isTeamConstrained && !isChannelConstrained) { - fetchGroupsForAutocomplete(serverUrl, matchTerm || '').then((g) => { + searchGroupsByName(serverUrl, matchTerm || '').then((g) => { setGroups(Array.isArray(g) ? g : emptyGroupList); }).catch(() => { setGroups(emptyGroupList); diff --git a/app/database/operator/server_data_operator/handlers/group.test.ts b/app/database/operator/server_data_operator/handlers/group.test.ts new file mode 100644 index 0000000000..05faef215b --- /dev/null +++ b/app/database/operator/server_data_operator/handlers/group.test.ts @@ -0,0 +1,54 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. +// See LICENSE.txt for license information. + +import {MM_TABLES} from '@constants/database'; +import DatabaseManager from '@database/manager'; +import { + transformGroupRecord, +} from '@database/operator/server_data_operator/transformers/group'; + +import ServerDataOperator from '..'; + +describe('*** Operator: Group Handlers tests ***', () => { + let operator: ServerDataOperator; + beforeAll(async () => { + await DatabaseManager.init(['baseHandler.test.com']); + operator = DatabaseManager.serverDatabases['baseHandler.test.com'].operator; + }); + + it('=> handleGroups: should write to the GROUP table', async () => { + expect.assertions(2); + + const spyOnHandleRecords = jest.spyOn(operator, 'handleRecords'); + const groups: Group[] = [ + { + id: 'kjlw9j1ttnxwig7tnqgebg7dtipno', + name: 'test', + display_name: 'Test', + source: 'custom', + remote_id: 'iuh4r89egnslnvakjsdjhg', + description: 'Test description', + member_count: 0, + allow_reference: true, + create_at: 0, + update_at: 0, + delete_at: 0, + }, + ]; + + await operator.handleGroups({ + groups, + prepareRecordsOnly: false, + }); + + expect(spyOnHandleRecords).toHaveBeenCalledTimes(1); + expect(spyOnHandleRecords).toHaveBeenCalledWith({ + fieldName: 'id', + createOrUpdateRawValues: groups, + tableName: MM_TABLES.SERVER.GROUP, + prepareRecordsOnly: false, + transformer: transformGroupRecord, + }); + }); +}); diff --git a/app/database/operator/server_data_operator/handlers/group.ts b/app/database/operator/server_data_operator/handlers/group.ts new file mode 100644 index 0000000000..1ee2762ecd --- /dev/null +++ b/app/database/operator/server_data_operator/handlers/group.ts @@ -0,0 +1,46 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. + +import {MM_TABLES} from '@constants/database'; +import {transformGroupRecord} from '@database/operator/server_data_operator/transformers/group'; +import {getUniqueRawsBy} from '@database/operator/utils/general'; + +import type {HandleGroupArgs} from '@typings/database/database'; +import type GroupModel from '@typings/database/models/servers/group'; + +const {GROUP} = MM_TABLES.SERVER; +export interface GroupHandlerMix { + handleGroups: ({groups, prepareRecordsOnly}: HandleGroupArgs) => Promise; +} + +const GroupHandler = (superclass: any) => class extends superclass implements GroupHandlerMix { + /** + * handleGroups: Handler responsible for the Create/Update operations occurring on the Group table from the 'Server' schema + * @param {HandleGroupArgs} groupsArgs + * @param {Group[]} groupsArgs.groups + * @param {boolean} groupsArgs.prepareRecordsOnly + * @throws DataOperatorException + * @returns {Promise} + */ + handleGroups = async ({groups, prepareRecordsOnly = true}: HandleGroupArgs): Promise => { + if (!groups?.length) { + // eslint-disable-next-line no-console + console.warn( + 'An empty or undefined "groups" array has been passed to the handleGroups method', + ); + return []; + } + + const createOrUpdateRawValues = getUniqueRawsBy({raws: groups, key: 'id'}); + + return this.handleRecords({ + fieldName: 'id', + transformer: transformGroupRecord, + createOrUpdateRawValues, + tableName: GROUP, + prepareRecordsOnly, + }); + }; +}; + +export default GroupHandler; diff --git a/app/database/operator/server_data_operator/index.ts b/app/database/operator/server_data_operator/index.ts index 28b6d9e044..5ab0f1b7a8 100644 --- a/app/database/operator/server_data_operator/index.ts +++ b/app/database/operator/server_data_operator/index.ts @@ -4,6 +4,7 @@ import ServerDataOperatorBase from '@database/operator/server_data_operator/handlers'; import CategoryHandler, {CategoryHandlerMix} from '@database/operator/server_data_operator/handlers/category'; 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'; @@ -16,12 +17,25 @@ import mix from '@utils/mix'; import type {Database} from '@nozbe/watermelondb'; -interface ServerDataOperator extends ServerDataOperatorBase, PostHandlerMix, PostsInChannelHandlerMix, - PostsInThreadHandlerMix, ReactionHandlerMix, UserHandlerMix, ChannelHandlerMix, CategoryHandlerMix, TeamHandlerMix, ThreadHandlerMix, ThreadInTeamHandlerMix {} +interface ServerDataOperator extends + CategoryHandlerMix, + ChannelHandlerMix, + GroupHandlerMix, + PostHandlerMix, + PostsInChannelHandlerMix, + PostsInThreadHandlerMix, + ReactionHandlerMix, + ServerDataOperatorBase, + TeamHandlerMix, + ThreadHandlerMix, + ThreadInTeamHandlerMix, + UserHandlerMix +{} class ServerDataOperator extends mix(ServerDataOperatorBase).with( CategoryHandler, ChannelHandler, + GroupHandler, PostHandler, PostsInChannelHandler, PostsInThreadHandler, diff --git a/app/database/operator/server_data_operator/transformers/group.test.ts b/app/database/operator/server_data_operator/transformers/group.test.ts new file mode 100644 index 0000000000..8079542150 --- /dev/null +++ b/app/database/operator/server_data_operator/transformers/group.test.ts @@ -0,0 +1,35 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. + +import { + transformGroupRecord, +} from '@database/operator/server_data_operator/transformers/group'; +import {createTestConnection} from '@database/operator/utils/create_test_connection'; +import {OperationType} from '@typings/database/enums'; + +describe('*** GROUP Prepare Records Test ***', () => { + it('=> transformGroupRecord: should return an array of type GroupModel', async () => { + // expect.assertions(3); + + const database = await createTestConnection({databaseName: 'group_prepare_records', setActive: true}); + expect(database).toBeTruthy(); + + const preparedRecords = await transformGroupRecord({ + action: OperationType.CREATE, + database: database!, + value: { + record: undefined, + raw: { + id: 'kow9j1ttnxwig7tnqgebg7dtipno', + display_name: 'Test', + name: 'recent', + source: 'custom', + remote_id: 'custom', + } as Group, + }, + }); + + expect(preparedRecords).toBeTruthy(); + expect(preparedRecords.collection.modelClass.name).toBe('GroupModel'); + }); +}); diff --git a/app/database/operator/server_data_operator/transformers/group.ts b/app/database/operator/server_data_operator/transformers/group.ts new file mode 100644 index 0000000000..69e7755b01 --- /dev/null +++ b/app/database/operator/server_data_operator/transformers/group.ts @@ -0,0 +1,44 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. +// See LICENSE.txt for license information. + +import {MM_TABLES} from '@constants/database'; +import {prepareBaseRecord} from '@database/operator/server_data_operator/transformers/index'; +import {OperationType} from '@typings/database/enums'; + +import type {TransformerArgs} from '@typings/database/database'; +import type GroupModel from '@typings/database/models/servers/group'; + +const { + GROUP, +} = MM_TABLES.SERVER; + +/** + * transformGroupRecord: Prepares a record of the SERVER database 'Group' table for update or create actions. + * @param {TransformerArgs} operator + * @param {Database} operator.database + * @param {RecordPair} operator.value + * @returns {Promise} + */ +export const transformGroupRecord = ({action, database, value}: TransformerArgs): Promise => { + const raw = value.raw as Group; + const record = value.record as GroupModel; + const isCreateAction = action === OperationType.CREATE; + + // id of group comes from server response + const fieldsMapper = (group: GroupModel) => { + group._raw.id = isCreateAction ? (raw?.id ?? group.id) : record.id; + group.name = raw.name; + group.displayName = raw.display_name; + group.source = raw.source; + group.remoteId = raw.remote_id; + }; + + return prepareBaseRecord({ + action, + database, + tableName: GROUP, + value, + fieldsMapper, + }) as Promise; +}; diff --git a/app/queries/servers/group.ts b/app/queries/servers/group.ts new file mode 100644 index 0000000000..e414bc1147 --- /dev/null +++ b/app/queries/servers/group.ts @@ -0,0 +1,35 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. + +import {Database, Q} from '@nozbe/watermelondb'; + +import {MM_TABLES} from '@constants/database'; + +import type ServerDataOperator from '@database/operator/server_data_operator'; +import type GroupModel from '@typings/database/models/servers/group'; + +const {SERVER: {GROUP, GROUP_CHANNEL, GROUP_TEAM}} = MM_TABLES; + +export const queryGroupsByName = (database: Database, name: string) => { + return database.collections.get(GROUP).query( + Q.where('name', Q.like(`%${Q.sanitizeLikeString(name)}%`)), + ); +}; + +export const queryGroupsByNameInTeam = (database: Database, name: string, teamId: string) => { + return database.collections.get(GROUP).query( + Q.on(GROUP_TEAM, 'team_id', teamId), + Q.where('name', Q.like(`%${Q.sanitizeLikeString(name)}%`)), + ); +}; + +export const queryGroupsByNameInChannel = (database: Database, name: string, channelId: string) => { + return database.collections.get(GROUP).query( + Q.on(GROUP_CHANNEL, 'channel_id', channelId), + Q.where('name', Q.like(`%${Q.sanitizeLikeString(name)}%`)), + ); +}; + +export const prepareGroups = (operator: ServerDataOperator, groups: Group[]) => { + return operator.handleGroups({groups, prepareRecordsOnly: true}); +}; diff --git a/types/database/database.d.ts b/types/database/database.d.ts index e2ed712fa3..90973aabe1 100644 --- a/types/database/database.d.ts +++ b/types/database/database.d.ts @@ -218,6 +218,10 @@ export type HandleCategoryArgs = PrepareOnly & { categories?: Category[]; }; +export type HandleGroupArgs = PrepareOnly & { + groups?: Group[]; +}; + export type HandleCategoryChannelArgs = PrepareOnly & { categoryChannels?: CategoryChannel[]; };