forked from Ivasoft/mattermost-mobile
[Gekidou] groups (#5593)
* Rename groups in channel/team to group channel/team * Redefine groups schema * Groups action and operator * Add group at mentions * fix uni test * Update types/database/models/servers/group.d.ts Co-authored-by: Avinash Lingaloo <avinashlng1080@gmail.com> Co-authored-by: Avinash Lingaloo <avinashlng1080@gmail.com>
This commit is contained in:
@@ -16,6 +16,7 @@ import {addChannelToTeamHistory, prepareMyTeams} from '@queries/servers/team';
|
||||
import {selectDefaultChannelForTeam} from '@utils/channel';
|
||||
|
||||
import {fetchMissingSidebarInfo, fetchMyChannelsForTeam, MyChannelsRequest} from './channel';
|
||||
import {fetchGroupsForTeam} from './group';
|
||||
import {fetchPostsForChannel, fetchPostsForUnreadChannels} from './post';
|
||||
import {MyPreferencesRequest, fetchMyPreferences} from './preference';
|
||||
import {fetchRolesIfNeeded} from './role';
|
||||
@@ -203,9 +204,9 @@ const deferredLoginActions = async (
|
||||
}
|
||||
|
||||
// defer groups for team
|
||||
// if (initialTeam) {
|
||||
// await fetchGroupsForTeam(serverUrl, initialTeam.id);
|
||||
// }
|
||||
if (initialTeam) {
|
||||
await fetchGroupsForTeam(serverUrl, initialTeam.id);
|
||||
}
|
||||
|
||||
// defer fetch channels and unread posts for other teams
|
||||
if (teamData.teams?.length && teamData.memberships?.length) {
|
||||
|
||||
133
app/actions/remote/group.ts
Normal file
133
app/actions/remote/group.ts
Normal file
@@ -0,0 +1,133 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {Model} from '@nozbe/watermelondb';
|
||||
|
||||
import DatabaseManager from '@database/manager';
|
||||
import NetworkManager from '@init/network_manager';
|
||||
import {queryCommonSystemValues, queryWebSocketLastDisconnected} from '@queries/servers/system';
|
||||
import {queryTeamById} from '@queries/servers/team';
|
||||
|
||||
import {forceLogoutIfNecessary} from './session';
|
||||
|
||||
export const fetchGroupsForTeam = async (serverUrl: string, teamId: string) => {
|
||||
const database = DatabaseManager.serverDatabases[serverUrl]?.database;
|
||||
if (!database) {
|
||||
return {error: `${serverUrl} database not found`};
|
||||
}
|
||||
|
||||
let client;
|
||||
try {
|
||||
client = NetworkManager.getClient(serverUrl);
|
||||
} catch (error) {
|
||||
return {error};
|
||||
}
|
||||
|
||||
try {
|
||||
const system = await queryCommonSystemValues(database);
|
||||
const team = await queryTeamById(database, teamId);
|
||||
const hasLicense = system.license.IsLicensed === 'true' && system.license.LDAPGroups === 'true';
|
||||
|
||||
if (hasLicense && team) {
|
||||
const groups: Group[] = [];
|
||||
const groupsChannels: GroupChannelRelation[] = [];
|
||||
const groupsTeams: GroupTeamRelation[] = [];
|
||||
const groupMemberships: GroupMembership[] = [];
|
||||
if (team.isGroupConstrained) {
|
||||
const [groupsAssociatedToChannelsInTeam, groupsAssociatedToTeam] = await Promise.all<{groups: Record<string, Group[]>}, {groups: Group[]; total_group_count: number}>([
|
||||
client.getAllGroupsAssociatedToChannelsInTeam(teamId, true),
|
||||
client.getAllGroupsAssociatedToTeam(teamId, true),
|
||||
]);
|
||||
|
||||
if (groupsAssociatedToChannelsInTeam.groups) {
|
||||
const keys = Object.keys(groupsAssociatedToChannelsInTeam.groups);
|
||||
for (const key of keys) {
|
||||
for (const group of groupsAssociatedToChannelsInTeam.groups[key]) {
|
||||
groups.push(group);
|
||||
groupsChannels.push({
|
||||
channel_id: key,
|
||||
group_id: group.id,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (groupsAssociatedToTeam.groups) {
|
||||
for (const group of groupsAssociatedToTeam.groups) {
|
||||
groups.push(group);
|
||||
groupsTeams.push({group_id: group.id, team_id: teamId});
|
||||
}
|
||||
}
|
||||
} else {
|
||||
const since = await queryWebSocketLastDisconnected(database);
|
||||
const [groupsAssociatedToChannelsInTeam, allGroups] = await Promise.all<{groups: Record<string, Group[]>}, Group[]>([
|
||||
client.getAllGroupsAssociatedToChannelsInTeam(teamId, true),
|
||||
client.getGroups(true, 0, 0, since),
|
||||
]);
|
||||
|
||||
if (groupsAssociatedToChannelsInTeam.groups) {
|
||||
const keys = Object.keys(groupsAssociatedToChannelsInTeam.groups);
|
||||
for (const key of keys) {
|
||||
for (const group of groupsAssociatedToChannelsInTeam.groups[key]) {
|
||||
groups.push(group);
|
||||
groupsChannels.push({
|
||||
channel_id: key,
|
||||
group_id: group.id,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (allGroups?.length) {
|
||||
groups.push(...allGroups);
|
||||
}
|
||||
}
|
||||
|
||||
const userGroups = await client.getGroupsByUserId(system.currentUserId);
|
||||
if (userGroups) {
|
||||
for (const mg of userGroups) {
|
||||
groupMemberships.push({group_id: mg.id, user_id: system.currentUserId});
|
||||
groups.push(mg);
|
||||
}
|
||||
}
|
||||
|
||||
const models: Model[] = [];
|
||||
const {operator} = DatabaseManager.serverDatabases[serverUrl];
|
||||
if (groups.length) {
|
||||
const gModels = await operator.handleGroup({groups, prepareRecordsOnly: true});
|
||||
if (gModels.length) {
|
||||
models.push(...gModels);
|
||||
}
|
||||
}
|
||||
|
||||
if (groupsChannels.length) {
|
||||
const gcModels = await operator.handleGroupsChannel({groupsChannels, prepareRecordsOnly: true});
|
||||
if (gcModels.length) {
|
||||
models.push(...gcModels);
|
||||
}
|
||||
}
|
||||
|
||||
if (groupsTeams.length) {
|
||||
const gtModels = await operator.handleGroupsTeam({groupsTeams, prepareRecordsOnly: true});
|
||||
if (gtModels.length) {
|
||||
models.push(...gtModels);
|
||||
}
|
||||
}
|
||||
|
||||
if (groupMemberships.length) {
|
||||
const gmModels = await operator.handleGroupMembership({groupMemberships, prepareRecordsOnly: true});
|
||||
if (gmModels.length) {
|
||||
models.push(...gmModels);
|
||||
}
|
||||
}
|
||||
|
||||
if (models.length) {
|
||||
await operator.batchRecords(models.flat());
|
||||
}
|
||||
}
|
||||
return null;
|
||||
} catch (error) {
|
||||
forceLogoutIfNecessary(serverUrl, error);
|
||||
return {error};
|
||||
}
|
||||
};
|
||||
@@ -8,7 +8,7 @@ import {PER_PAGE_DEFAULT} from './constants';
|
||||
export interface ClientGroupsMix {
|
||||
getGroups: (filterAllowReference?: boolean, page?: number, perPage?: number, since?: number) => Promise<Group[]>;
|
||||
getGroupsByUserId: (userID: string) => Promise<Group[]>;
|
||||
getAllGroupsAssociatedToTeam: (teamID: string, filterAllowReference?: boolean) => Promise<{groups: Group[]; tota_group_count: number}>;
|
||||
getAllGroupsAssociatedToTeam: (teamID: string, filterAllowReference?: boolean) => Promise<{groups: Group[]; total_group_count: number}>;
|
||||
getAllGroupsAssociatedToChannelsInTeam: (teamID: string, filterAllowReference?: boolean) => Promise<{groups: Record<string, Group[]>}>;
|
||||
getAllGroupsAssociatedToChannel: (channelID: string, filterAllowReference?: boolean) => Promise<Group[]>;
|
||||
}
|
||||
|
||||
@@ -22,17 +22,21 @@ import {showModal, showModalOverCurrentContext} from '@screens/navigation';
|
||||
import {displayUsername, getUserMentionKeys, getUsersByUsername} from '@utils/user';
|
||||
|
||||
import type {WithDatabaseArgs} from '@typings/database/database';
|
||||
import type GroupModel from '@typings/database/models/servers/group';
|
||||
import type GroupMembershipModel from '@typings/database/models/servers/group_membership';
|
||||
import type PreferenceModel from '@typings/database/models/servers/preference';
|
||||
import type SystemModel from '@typings/database/models/servers/system';
|
||||
import type UserModelType from '@typings/database/models/servers/user';
|
||||
|
||||
type AtMentionProps = {
|
||||
currentUserId: SystemModel;
|
||||
database: Database;
|
||||
groupsByName?: Record<string, Group>;
|
||||
groups: GroupModel[];
|
||||
isSearchResult?: boolean;
|
||||
mentionKeys: Array<{key: string }>;
|
||||
mentionName: string;
|
||||
mentionStyle: TextStyle;
|
||||
myGroups: GroupMembershipModel[];
|
||||
onPostPress?: (e: GestureResponderEvent) => void;
|
||||
teammateNameDisplay: string;
|
||||
textStyle: StyleProp<TextStyle>;
|
||||
@@ -46,7 +50,7 @@ type AtMentionArgs = {
|
||||
mentionName: string;
|
||||
}
|
||||
|
||||
const {SERVER: {PREFERENCE, SYSTEM, USER}} = MM_TABLES;
|
||||
const {SERVER: {GROUP, GROUP_MEMBERSHIP, PREFERENCE, SYSTEM, USER}} = MM_TABLES;
|
||||
|
||||
const style = StyleSheet.create({
|
||||
bottomSheet: {
|
||||
@@ -55,12 +59,14 @@ const style = StyleSheet.create({
|
||||
});
|
||||
|
||||
const AtMention = ({
|
||||
currentUserId,
|
||||
database,
|
||||
groupsByName,
|
||||
groups,
|
||||
isSearchResult,
|
||||
mentionName,
|
||||
mentionKeys,
|
||||
mentionStyle,
|
||||
myGroups,
|
||||
onPostPress,
|
||||
teammateNameDisplay,
|
||||
textStyle,
|
||||
@@ -90,15 +96,19 @@ const AtMention = ({
|
||||
return new UserModel(database.get(USER), {username: ''});
|
||||
}, [users, mentionName]);
|
||||
const userMentionKeys = useMemo(() => {
|
||||
if (user.id !== currentUserId.value) {
|
||||
return [];
|
||||
}
|
||||
|
||||
if (mentionKeys) {
|
||||
return mentionKeys;
|
||||
}
|
||||
return getUserMentionKeys(user);
|
||||
}, [user, mentionKeys]);
|
||||
return getUserMentionKeys(user, groups, myGroups);
|
||||
}, [currentUserId.value, groups, mentionKeys, myGroups, user]);
|
||||
|
||||
const getGroupFromMentionName = () => {
|
||||
const mentionNameTrimmed = mentionName.toLowerCase().replace(/[._-]*$/, '');
|
||||
return groupsByName?.[mentionNameTrimmed];
|
||||
return groups.find((g) => g.name === mentionNameTrimmed);
|
||||
};
|
||||
|
||||
const goToUserProfile = useCallback(() => {
|
||||
@@ -190,9 +200,8 @@ const AtMention = ({
|
||||
isMention = true;
|
||||
canPress = true;
|
||||
} else {
|
||||
// TODO: Add group functionality
|
||||
const group = getGroupFromMentionName();
|
||||
if (group?.allow_reference) {
|
||||
if (group?.allowReference) {
|
||||
highlighted = userMentionKeys.some((item) => item.key === `@${group.name}`);
|
||||
isMention = true;
|
||||
mention = group.name;
|
||||
@@ -249,9 +258,10 @@ const AtMention = ({
|
||||
};
|
||||
|
||||
const withPreferences = withObservables([], ({database}: WithDatabaseArgs) => ({
|
||||
preferences: database.get(PREFERENCE).query(Q.where('category', Preferences.CATEGORY_DISPLAY_SETTINGS)).observe(),
|
||||
config: database.get(SYSTEM).findAndObserve(SYSTEM_IDENTIFIERS.CONFIG),
|
||||
currentUserId: database.get(SYSTEM).findAndObserve(SYSTEM_IDENTIFIERS.CURRENT_USER_ID),
|
||||
license: database.get(SYSTEM).findAndObserve(SYSTEM_IDENTIFIERS.LICENSE),
|
||||
preferences: database.get(PREFERENCE).query(Q.where('category', Preferences.CATEGORY_DISPLAY_SETTINGS)).observe(),
|
||||
}));
|
||||
|
||||
const withAtMention = withObservables(['mentionName', 'preferences', 'config', 'license'], ({database, mentionName, preferences, config, license}: WithDatabaseArgs & AtMentionArgs) => {
|
||||
@@ -263,6 +273,8 @@ const withAtMention = withObservables(['mentionName', 'preferences', 'config', '
|
||||
const teammateNameDisplay = of(getTeammateNameDisplaySetting(preferences, config.value, license.value));
|
||||
|
||||
return {
|
||||
groups: database.get(GROUP).query(Q.where('delete_at', Q.eq(0))).observe(),
|
||||
myGroups: database.get(GROUP_MEMBERSHIP).query().observe(),
|
||||
teammateNameDisplay,
|
||||
users: database.get(USER).query(
|
||||
Q.where('username', Q.like(
|
||||
|
||||
@@ -17,8 +17,8 @@ export const MM_TABLES = {
|
||||
DRAFT: 'Draft',
|
||||
FILE: 'File',
|
||||
GROUP: 'Group',
|
||||
GROUPS_IN_CHANNEL: 'GroupsInChannel',
|
||||
GROUPS_IN_TEAM: 'GroupsInTeam',
|
||||
GROUPS_CHANNEL: 'GroupsChannel',
|
||||
GROUPS_TEAM: 'GroupsTeam',
|
||||
GROUP_MEMBERSHIP: 'GroupMembership',
|
||||
MY_CHANNEL: 'MyChannel',
|
||||
MY_CHANNEL_SETTINGS: 'MyChannelSettings',
|
||||
|
||||
@@ -16,7 +16,7 @@ import {InfoModel, GlobalModel, ServersModel} from '@database/models/app';
|
||||
import {schema as appSchema} from '@app/database/schema/app';
|
||||
import ServerDatabaseMigrations from '@database/migration/server';
|
||||
import {ChannelModel, ChannelInfoModel, ChannelMembershipModel, CustomEmojiModel, DraftModel, FileModel,
|
||||
GroupModel, GroupMembershipModel, GroupsInChannelModel, GroupsInTeamModel, MyChannelModel, MyChannelSettingsModel, MyTeamModel,
|
||||
GroupModel, GroupMembershipModel, GroupsChannelModel, GroupsTeamModel, MyChannelModel, MyChannelSettingsModel, MyTeamModel,
|
||||
PostModel, PostMetadataModel, PostsInChannelModel, PostsInThreadModel, PreferenceModel, ReactionModel, RoleModel,
|
||||
SlashCommandModel, SystemModel, TeamModel, TeamChannelHistoryModel, TeamMembershipModel, TeamSearchHistoryModel,
|
||||
TermsOfServiceModel, UserModel,
|
||||
@@ -51,7 +51,7 @@ class DatabaseManager {
|
||||
this.appModels = [InfoModel, GlobalModel, ServersModel];
|
||||
this.serverModels = [
|
||||
ChannelModel, ChannelInfoModel, ChannelMembershipModel, CustomEmojiModel, DraftModel, FileModel,
|
||||
GroupModel, GroupMembershipModel, GroupsInChannelModel, GroupsInTeamModel, MyChannelModel, MyChannelSettingsModel, MyTeamModel,
|
||||
GroupModel, GroupMembershipModel, GroupsChannelModel, GroupsTeamModel, MyChannelModel, MyChannelSettingsModel, MyTeamModel,
|
||||
PostModel, PostMetadataModel, PostsInChannelModel, PostsInThreadModel, PreferenceModel, ReactionModel, RoleModel,
|
||||
SlashCommandModel, SystemModel, TeamModel, TeamChannelHistoryModel, TeamMembershipModel, TeamSearchHistoryModel,
|
||||
TermsOfServiceModel, UserModel,
|
||||
|
||||
@@ -16,7 +16,7 @@ import {InfoModel, GlobalModel, ServersModel} from '@database/models/app';
|
||||
import {schema as appSchema} from '@database/schema/app';
|
||||
import ServerDatabaseMigrations from '@database/migration/server';
|
||||
import {ChannelModel, ChannelInfoModel, ChannelMembershipModel, CustomEmojiModel, DraftModel, FileModel,
|
||||
GroupModel, GroupMembershipModel, GroupsInChannelModel, GroupsInTeamModel, MyChannelModel, MyChannelSettingsModel, MyTeamModel,
|
||||
GroupModel, GroupMembershipModel, GroupsChannelModel, GroupsTeamModel, MyChannelModel, MyChannelSettingsModel, MyTeamModel,
|
||||
PostModel, PostMetadataModel, PostsInChannelModel, PostsInThreadModel, PreferenceModel, ReactionModel, RoleModel,
|
||||
SlashCommandModel, SystemModel, TeamModel, TeamChannelHistoryModel, TeamMembershipModel, TeamSearchHistoryModel,
|
||||
TermsOfServiceModel, UserModel,
|
||||
@@ -45,7 +45,7 @@ class DatabaseManager {
|
||||
this.appModels = [InfoModel, GlobalModel, ServersModel];
|
||||
this.serverModels = [
|
||||
ChannelModel, ChannelInfoModel, ChannelMembershipModel, CustomEmojiModel, DraftModel, FileModel,
|
||||
GroupModel, GroupMembershipModel, GroupsInChannelModel, GroupsInTeamModel, MyChannelModel, MyChannelSettingsModel, MyTeamModel,
|
||||
GroupModel, GroupMembershipModel, GroupsChannelModel, GroupsTeamModel, MyChannelModel, MyChannelSettingsModel, MyTeamModel,
|
||||
PostModel, PostMetadataModel, PostsInChannelModel, PostsInThreadModel, PreferenceModel, ReactionModel, RoleModel,
|
||||
SlashCommandModel, SystemModel, TeamModel, TeamChannelHistoryModel, TeamMembershipModel, TeamSearchHistoryModel,
|
||||
TermsOfServiceModel, UserModel,
|
||||
|
||||
@@ -10,7 +10,7 @@ import {MM_TABLES} from '@constants/database';
|
||||
import type ChannelInfoModel from '@typings/database/models/servers/channel_info';
|
||||
import type ChannelMembershipModel from '@typings/database/models/servers/channel_membership';
|
||||
import type DraftModel from '@typings/database/models/servers/draft';
|
||||
import type GroupsInChannelModel from '@typings/database/models/servers/groups_in_channel';
|
||||
import type GroupsChannelModel from '@typings/database/models/servers/groups_channel';
|
||||
import type MyChannelModel from '@typings/database/models/servers/my_channel';
|
||||
import type MyChannelSettingsModel from '@typings/database/models/servers/my_channel_settings';
|
||||
import type PostModel from '@typings/database/models/servers/post';
|
||||
@@ -23,7 +23,7 @@ const {
|
||||
CHANNEL_INFO,
|
||||
CHANNEL_MEMBERSHIP,
|
||||
DRAFT,
|
||||
GROUPS_IN_CHANNEL,
|
||||
GROUPS_CHANNEL,
|
||||
MY_CHANNEL,
|
||||
MY_CHANNEL_SETTINGS,
|
||||
POSTS_IN_CHANNEL,
|
||||
@@ -48,8 +48,8 @@ export default class ChannelModel extends Model {
|
||||
/** A CHANNEL can be associated with multiple DRAFT (relationship is 1:N) */
|
||||
[DRAFT]: {type: 'has_many', foreignKey: 'channel_id'},
|
||||
|
||||
/** A CHANNEL can be associated with multiple GROUPS_IN_CHANNEL (relationship is 1:N) */
|
||||
[GROUPS_IN_CHANNEL]: {type: 'has_many', foreignKey: 'channel_id'},
|
||||
/** A CHANNEL can be associated with multiple GROUPS_CHANNEL (relationship is 1:N) */
|
||||
[GROUPS_CHANNEL]: {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'},
|
||||
@@ -100,8 +100,8 @@ export default class ChannelModel extends Model {
|
||||
/** drafts : All drafts for this channel */
|
||||
@children(DRAFT) drafts!: DraftModel[];
|
||||
|
||||
/** groupsInChannel : Every group contained in this channel */
|
||||
@children(GROUPS_IN_CHANNEL) groupsInChannel!: GroupsInChannelModel[];
|
||||
/** groupsChannel : Every group contained in this channel */
|
||||
@children(GROUPS_CHANNEL) groupsChannel!: GroupsChannelModel[];
|
||||
|
||||
/** posts : All posts made in that channel */
|
||||
@children(POST) posts!: PostModel[];
|
||||
|
||||
@@ -7,10 +7,10 @@ import {children, field} from '@nozbe/watermelondb/decorators';
|
||||
import {MM_TABLES} from '@constants/database';
|
||||
|
||||
import type GroupMembershipModel from '@typings/database/models/servers/group_membership';
|
||||
import type GroupsInChannelModel from '@typings/database/models/servers/groups_in_channel';
|
||||
import type GroupsInTeamModel from '@typings/database/models/servers/groups_in_team';
|
||||
import type GroupsChannelModel from '@typings/database/models/servers/groups_channel';
|
||||
import type GroupsTeamModel from '@typings/database/models/servers/groups_team';
|
||||
|
||||
const {GROUP, GROUPS_IN_CHANNEL, GROUPS_IN_TEAM, GROUP_MEMBERSHIP} = MM_TABLES.SERVER;
|
||||
const {GROUP, GROUPS_CHANNEL, GROUPS_TEAM, GROUP_MEMBERSHIP} = MM_TABLES.SERVER;
|
||||
|
||||
/**
|
||||
* The Group model unifies/assembles users, teams and channels based on a common ground. For example, a group can be
|
||||
@@ -24,27 +24,33 @@ export default class GroupModel extends Model {
|
||||
/** associations : Describes every relationship to this table. */
|
||||
static associations: Associations = {
|
||||
|
||||
/** A GROUP has a 1:N relationship with GROUPS_IN_CHANNEL */
|
||||
[GROUPS_IN_CHANNEL]: {type: 'has_many', foreignKey: 'group_id'},
|
||||
/** A GROUP has a 1:N relationship with GROUPS_CHANNEL */
|
||||
[GROUPS_CHANNEL]: {type: 'has_many', foreignKey: 'group_id'},
|
||||
|
||||
/** A GROUP has a 1:N relationship with GROUPS_IN_TEAM */
|
||||
[GROUPS_IN_TEAM]: {type: 'has_many', foreignKey: 'group_id'},
|
||||
/** A GROUP has a 1:N relationship with GROUPS_TEAM */
|
||||
[GROUPS_TEAM]: {type: 'has_many', foreignKey: 'group_id'},
|
||||
|
||||
/** A GROUP has a 1:N relationship with GROUP_MEMBERSHIP */
|
||||
[GROUP_MEMBERSHIP]: {type: 'has_many', foreignKey: 'group_id'},
|
||||
};
|
||||
|
||||
/** allow_reference : Determins if the group can be referenced in mentions */
|
||||
@field('allow_reference') allowReference!: boolean;
|
||||
|
||||
/** delete_at : When the group was deleted */
|
||||
@field('delete_at') deleteAt!: number;
|
||||
|
||||
/** display_name : The display name for the group */
|
||||
@field('display_name') displayName!: string;
|
||||
|
||||
/** name : The name of the group */
|
||||
@field('name') name!: string;
|
||||
|
||||
/** groupsInChannel : All the related children records from GroupsInChannel */
|
||||
@children(GROUPS_IN_CHANNEL) groupsInChannel!: GroupsInChannelModel[];
|
||||
/** groupsChannel : All the related children records from GroupsChannel */
|
||||
@children(GROUPS_CHANNEL) groupsChannel!: GroupsChannelModel[];
|
||||
|
||||
/** groupsInTeam : All the related children records from GroupsInTeam */
|
||||
@children(GROUPS_IN_TEAM) groupsInTeam!: GroupsInTeamModel[];
|
||||
/** groupsTeam : All the related children records from GroupsTeam */
|
||||
@children(GROUPS_TEAM) groupsTeam!: GroupsTeamModel[];
|
||||
|
||||
/** groupMemberships : All the related children records from GroupMembership */
|
||||
@children(GROUP_MEMBERSHIP) groupMemberships!: GroupMembershipModel[];
|
||||
|
||||
@@ -10,22 +10,22 @@ import {MM_TABLES} from '@constants/database';
|
||||
import type ChannelModel from '@typings/database/models/servers/channel';
|
||||
import type GroupModel from '@typings/database/models/servers/group';
|
||||
|
||||
const {GROUP, GROUPS_IN_CHANNEL, CHANNEL} = MM_TABLES.SERVER;
|
||||
const {GROUP, GROUPS_CHANNEL, CHANNEL} = MM_TABLES.SERVER;
|
||||
|
||||
/**
|
||||
* The GroupsInChannel links the Channel model with the Group model
|
||||
* The GroupsChannel links the Channel model with the Group model
|
||||
*/
|
||||
export default class GroupsInChannelModel extends Model {
|
||||
/** table (name) : GroupsInChannel */
|
||||
static table = GROUPS_IN_CHANNEL;
|
||||
export default class GroupsChannelModel extends Model {
|
||||
/** table (name) : GroupsChannel */
|
||||
static table = GROUPS_CHANNEL;
|
||||
|
||||
/** associations : Describes every relationship to this table. */
|
||||
static associations: Associations = {
|
||||
|
||||
/** A GROUP can be associated with multiple GROUPS_IN_CHANNEL (relationship is 1:N) */
|
||||
/** A GROUP can be associated with multiple GROUPS_CHANNEL (relationship is 1:N) */
|
||||
[GROUP]: {type: 'belongs_to', key: 'group_id'},
|
||||
|
||||
/** A CHANNEL can be associated with multiple GROUPS_IN_CHANNEL (relationship is 1:N) */
|
||||
/** A CHANNEL can be associated with multiple GROUPS_CHANNEL (relationship is 1:N) */
|
||||
[CHANNEL]: {type: 'belongs_to', key: 'channel_id'},
|
||||
};
|
||||
|
||||
@@ -35,12 +35,6 @@ export default class GroupsInChannelModel extends Model {
|
||||
/** group_id : The foreign key of the related GROUP model */
|
||||
@field('group_id') groupId!: string;
|
||||
|
||||
/** member_count : The number of members in that group */
|
||||
@field('member_count') memberCount!: number;
|
||||
|
||||
/** timezone_count : The number of timezones in that group */
|
||||
@field('timezone_count') timezoneCount!: number;
|
||||
|
||||
/** channel : The related record to the parent Channel model */
|
||||
@immutableRelation(CHANNEL, 'channel_id') channel!: Relation<ChannelModel>;
|
||||
|
||||
@@ -10,22 +10,22 @@ import {MM_TABLES} from '@constants/database';
|
||||
import type GroupModel from '@typings/database/models/servers/group';
|
||||
import type TeamModel from '@typings/database/models/servers/team';
|
||||
|
||||
const {GROUP, GROUPS_IN_TEAM, TEAM} = MM_TABLES.SERVER;
|
||||
const {GROUP, GROUPS_TEAM, TEAM} = MM_TABLES.SERVER;
|
||||
|
||||
/**
|
||||
* The GroupsInTeam links the Team model with the Group model
|
||||
* The GroupsTeam links the Team model with the Group model
|
||||
*/
|
||||
export default class GroupsInTeamModel extends Model {
|
||||
/** table (name) : GroupsInTeam */
|
||||
static table = GROUPS_IN_TEAM;
|
||||
export default class GroupsTeamModel extends Model {
|
||||
/** table (name) : GroupsTeam */
|
||||
static table = GROUPS_TEAM;
|
||||
|
||||
/** associations : Describes every relationship to this table. */
|
||||
static associations: Associations = {
|
||||
|
||||
/** GroupsInTeam can belong to only one Group */
|
||||
/** GroupsTeam can belong to only one Group */
|
||||
[GROUP]: {type: 'belongs_to', key: 'group_id'},
|
||||
|
||||
/** GroupsInTeam can belong to only one Team */
|
||||
/** GroupsTeam can belong to only one Team */
|
||||
[TEAM]: {type: 'belongs_to', key: 'team_id'},
|
||||
};
|
||||
|
||||
@@ -8,8 +8,8 @@ export {default as CustomEmojiModel} from './custom_emoji';
|
||||
export {default as DraftModel} from './draft';
|
||||
export {default as FileModel} from './file';
|
||||
export {default as GroupMembershipModel} from './group_membership';
|
||||
export {default as GroupsInChannelModel} from './groups_in_channel';
|
||||
export {default as GroupsInTeamModel} from './groups_in_team';
|
||||
export {default as GroupsChannelModel} from './groups_channel';
|
||||
export {default as GroupsTeamModel} from './groups_team';
|
||||
export {default as GroupModel} from './group';
|
||||
export {default as MyChannelSettingsModel} from './my_channel_settings';
|
||||
export {default as MyChannelModel} from './my_channel';
|
||||
|
||||
@@ -8,7 +8,7 @@ import Model, {Associations} from '@nozbe/watermelondb/Model';
|
||||
import {MM_TABLES} from '@constants/database';
|
||||
|
||||
import type ChannelModel from '@typings/database/models/servers/channel';
|
||||
import type GroupsInTeamModel from '@typings/database/models/servers/groups_in_team';
|
||||
import type GroupsTeamModel from '@typings/database/models/servers/groups_team';
|
||||
import type MyTeamModel from '@typings/database/models/servers/my_team';
|
||||
import type SlashCommandModel from '@typings/database/models/servers/slash_command';
|
||||
import type TeamChannelHistoryModel from '@typings/database/models/servers/team_channel_history';
|
||||
@@ -17,7 +17,7 @@ import type TeamSearchHistoryModel from '@typings/database/models/servers/team_s
|
||||
|
||||
const {
|
||||
CHANNEL,
|
||||
GROUPS_IN_TEAM,
|
||||
GROUPS_TEAM,
|
||||
TEAM,
|
||||
MY_TEAM,
|
||||
SLASH_COMMAND,
|
||||
@@ -39,8 +39,8 @@ export default class TeamModel extends Model {
|
||||
/** A TEAM has a 1:N relationship with CHANNEL. A TEAM can possess multiple channels */
|
||||
[CHANNEL]: {type: 'has_many', foreignKey: 'team_id'},
|
||||
|
||||
/** A TEAM has a 1:N relationship with GROUPS_IN_TEAM. A TEAM can possess multiple groups */
|
||||
[GROUPS_IN_TEAM]: {type: 'has_many', foreignKey: 'team_id'},
|
||||
/** A TEAM has a 1:N relationship with GROUPS_TEAM. A TEAM can possess multiple groups */
|
||||
[GROUPS_TEAM]: {type: 'has_many', foreignKey: 'team_id'},
|
||||
|
||||
/** A TEAM has a 1:N relationship with SLASH_COMMAND. A TEAM can possess multiple slash commands */
|
||||
[SLASH_COMMAND]: {type: 'has_many', foreignKey: 'team_id'},
|
||||
@@ -82,8 +82,8 @@ export default class TeamModel extends Model {
|
||||
/** channels : All the channels associated with this team */
|
||||
@children(CHANNEL) channels!: ChannelModel[];
|
||||
|
||||
/** groupsInTeam : All the groups associated with this team */
|
||||
@children(GROUPS_IN_TEAM) groupsInTeam!: GroupsInTeamModel[];
|
||||
/** groupsTeam : All the groups associated with this team */
|
||||
@children(GROUPS_TEAM) groupsTeam!: GroupsTeamModel[];
|
||||
|
||||
/** myTeam : Retrieves additional information about the team that this user is possibly part of. */
|
||||
@immutableRelation(MY_TEAM, 'id') myTeam!: Relation<MyTeamModel>;
|
||||
|
||||
@@ -9,8 +9,8 @@ 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';
|
||||
import type GroupsInTeamModel from '@typings/database/models/servers/groups_in_team';
|
||||
import type GroupsChannelModel from '@typings/database/models/servers/groups_channel';
|
||||
import type GroupsTeamModel from '@typings/database/models/servers/groups_team';
|
||||
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';
|
||||
@@ -86,11 +86,11 @@ export const isRecordGroupEqualToRaw = (record: GroupModel, raw: Group) => {
|
||||
return raw.id === record.id;
|
||||
};
|
||||
|
||||
export const isRecordGroupsInTeamEqualToRaw = (record: GroupsInTeamModel, raw: GroupTeam) => {
|
||||
export const isRecordGroupsTeamEqualToRaw = (record: GroupsTeamModel, raw: GroupTeam) => {
|
||||
return raw.team_id === record.teamId && raw.group_id === record.groupId;
|
||||
};
|
||||
|
||||
export const isRecordGroupsInChannelEqualToRaw = (record: GroupsInChannelModel, raw: GroupChannel) => {
|
||||
export const isRecordGroupsChannelEqualToRaw = (record: GroupsChannelModel, raw: GroupChannel) => {
|
||||
return raw.channel_id === record.channelId && raw.group_id === record.groupId;
|
||||
};
|
||||
|
||||
|
||||
@@ -5,14 +5,14 @@ import DatabaseManager from '@database/manager';
|
||||
import {
|
||||
isRecordGroupEqualToRaw,
|
||||
isRecordGroupMembershipEqualToRaw,
|
||||
isRecordGroupsInChannelEqualToRaw,
|
||||
isRecordGroupsInTeamEqualToRaw,
|
||||
isRecordGroupsChannelEqualToRaw,
|
||||
isRecordGroupsTeamEqualToRaw,
|
||||
} from '@database/operator/server_data_operator/comparators';
|
||||
import {
|
||||
transformGroupMembershipRecord,
|
||||
transformGroupRecord,
|
||||
transformGroupsInChannelRecord,
|
||||
transformGroupsInTeamRecord,
|
||||
transformGroupsChannelRecord,
|
||||
transformGroupsTeamRecord,
|
||||
} from '@database/operator/server_data_operator/transformers/group';
|
||||
|
||||
import ServerDataOperator from '..';
|
||||
@@ -41,7 +41,7 @@ describe('*** Operator: Group Handlers tests ***', () => {
|
||||
has_syncables: true,
|
||||
type: '',
|
||||
member_count: 1,
|
||||
allow_reference: false,
|
||||
allow_reference: true,
|
||||
},
|
||||
];
|
||||
|
||||
@@ -52,7 +52,7 @@ describe('*** Operator: Group Handlers tests ***', () => {
|
||||
|
||||
expect(spyOnHandleRecords).toHaveBeenCalledTimes(1);
|
||||
expect(spyOnHandleRecords).toHaveBeenCalledWith({
|
||||
fieldName: 'name',
|
||||
fieldName: 'id',
|
||||
createOrUpdateRawValues: groups,
|
||||
tableName: 'Group',
|
||||
prepareRecordsOnly: false,
|
||||
@@ -61,11 +61,11 @@ describe('*** Operator: Group Handlers tests ***', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('=> HandleGroupsInTeam: should write to the GROUPS_IN_TEAM table', async () => {
|
||||
it('=> HandleGroupsTeam: should write to the GROUPS_TEAM table', async () => {
|
||||
expect.assertions(2);
|
||||
|
||||
const spyOnHandleRecords = jest.spyOn(operator, 'handleRecords');
|
||||
const groupsInTeams = [
|
||||
const groupsTeams = [
|
||||
{
|
||||
team_id: 'team_899',
|
||||
team_display_name: '',
|
||||
@@ -78,27 +78,27 @@ describe('*** Operator: Group Handlers tests ***', () => {
|
||||
},
|
||||
];
|
||||
|
||||
await operator.handleGroupsInTeam({
|
||||
groupsInTeams,
|
||||
await operator.handleGroupsTeam({
|
||||
groupsTeams,
|
||||
prepareRecordsOnly: false,
|
||||
});
|
||||
|
||||
expect(spyOnHandleRecords).toHaveBeenCalledTimes(1);
|
||||
expect(spyOnHandleRecords).toHaveBeenCalledWith({
|
||||
fieldName: 'group_id',
|
||||
createOrUpdateRawValues: groupsInTeams,
|
||||
tableName: 'GroupsInTeam',
|
||||
createOrUpdateRawValues: groupsTeams,
|
||||
tableName: 'GroupsTeam',
|
||||
prepareRecordsOnly: false,
|
||||
findMatchingRecordBy: isRecordGroupsInTeamEqualToRaw,
|
||||
transformer: transformGroupsInTeamRecord,
|
||||
findMatchingRecordBy: isRecordGroupsTeamEqualToRaw,
|
||||
transformer: transformGroupsTeamRecord,
|
||||
});
|
||||
});
|
||||
|
||||
it('=> HandleGroupsInChannel: should write to the GROUPS_IN_CHANNEL table', async () => {
|
||||
it('=> HandleGroupsChannel: should write to the GROUPS_CHANNEL table', async () => {
|
||||
expect.assertions(2);
|
||||
|
||||
const spyOnHandleRecords = jest.spyOn(operator, 'handleRecords');
|
||||
const groupsInChannels = [
|
||||
const groupsChannels = [
|
||||
{
|
||||
auto_add: true,
|
||||
channel_display_name: '',
|
||||
@@ -111,24 +111,22 @@ describe('*** Operator: Group Handlers tests ***', () => {
|
||||
team_id: '',
|
||||
team_type: '',
|
||||
update_at: 0,
|
||||
member_count: 0,
|
||||
timezone_count: 0,
|
||||
},
|
||||
];
|
||||
|
||||
await operator.handleGroupsInChannel({
|
||||
groupsInChannels,
|
||||
await operator.handleGroupsChannel({
|
||||
groupsChannels,
|
||||
prepareRecordsOnly: false,
|
||||
});
|
||||
|
||||
expect(spyOnHandleRecords).toHaveBeenCalledTimes(1);
|
||||
expect(spyOnHandleRecords).toHaveBeenCalledWith({
|
||||
fieldName: 'group_id',
|
||||
createOrUpdateRawValues: groupsInChannels,
|
||||
tableName: 'GroupsInChannel',
|
||||
createOrUpdateRawValues: groupsChannels,
|
||||
tableName: 'GroupsChannel',
|
||||
prepareRecordsOnly: false,
|
||||
findMatchingRecordBy: isRecordGroupsInChannelEqualToRaw,
|
||||
transformer: transformGroupsInChannelRecord,
|
||||
findMatchingRecordBy: isRecordGroupsChannelEqualToRaw,
|
||||
transformer: transformGroupsChannelRecord,
|
||||
});
|
||||
});
|
||||
|
||||
@@ -150,7 +148,7 @@ describe('*** Operator: Group Handlers tests ***', () => {
|
||||
|
||||
expect(spyOnHandleRecords).toHaveBeenCalledTimes(1);
|
||||
expect(spyOnHandleRecords).toHaveBeenCalledWith({
|
||||
fieldName: 'user_id',
|
||||
fieldName: 'group_id',
|
||||
createOrUpdateRawValues: groupMemberships,
|
||||
tableName: 'GroupMembership',
|
||||
prepareRecordsOnly: false,
|
||||
|
||||
@@ -6,35 +6,35 @@ import DataOperatorException from '@database/exceptions/data_operator_exception'
|
||||
import {
|
||||
isRecordGroupEqualToRaw,
|
||||
isRecordGroupMembershipEqualToRaw,
|
||||
isRecordGroupsInChannelEqualToRaw,
|
||||
isRecordGroupsInTeamEqualToRaw,
|
||||
isRecordGroupsChannelEqualToRaw,
|
||||
isRecordGroupsTeamEqualToRaw,
|
||||
} from '@database/operator/server_data_operator/comparators';
|
||||
import {
|
||||
transformGroupMembershipRecord,
|
||||
transformGroupRecord,
|
||||
transformGroupsInChannelRecord,
|
||||
transformGroupsInTeamRecord,
|
||||
transformGroupsChannelRecord,
|
||||
transformGroupsTeamRecord,
|
||||
} from '@database/operator/server_data_operator/transformers/group';
|
||||
import {getUniqueRawsBy} from '@database/operator/utils/general';
|
||||
|
||||
import type {HandleGroupArgs, HandleGroupMembershipArgs, HandleGroupsInChannelArgs, HandleGroupsInTeamArgs} from '@typings/database/database';
|
||||
import type {HandleGroupArgs, HandleGroupMembershipArgs, HandleGroupsChannelArgs, HandleGroupsTeamArgs} from '@typings/database/database';
|
||||
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';
|
||||
import type GroupsInTeamModel from '@typings/database/models/servers/groups_in_team';
|
||||
import type GroupsChannelModel from '@typings/database/models/servers/groups_channel';
|
||||
import type GroupsTeamModel from '@typings/database/models/servers/groups_team';
|
||||
|
||||
const {
|
||||
GROUP,
|
||||
GROUPS_IN_CHANNEL,
|
||||
GROUPS_IN_TEAM,
|
||||
GROUPS_CHANNEL,
|
||||
GROUPS_TEAM,
|
||||
GROUP_MEMBERSHIP,
|
||||
} = MM_TABLES.SERVER;
|
||||
|
||||
export interface GroupHandlerMix {
|
||||
handleGroupMembership: ({groupMemberships, prepareRecordsOnly}: HandleGroupMembershipArgs) => Promise<GroupMembershipModel[]>;
|
||||
handleGroup: ({groups, prepareRecordsOnly}: HandleGroupArgs) => Promise<GroupModel[]>;
|
||||
handleGroupsInTeam: ({groupsInTeams, prepareRecordsOnly}: HandleGroupsInTeamArgs) => Promise<GroupsInTeamModel[]>;
|
||||
handleGroupsInChannel: ({groupsInChannels, prepareRecordsOnly}: HandleGroupsInChannelArgs) => Promise<GroupsInChannelModel[]>;
|
||||
handleGroupsTeam: ({groupsTeams, prepareRecordsOnly}: HandleGroupsTeamArgs) => Promise<GroupsTeamModel[]>;
|
||||
handleGroupsChannel: ({groupsChannels, prepareRecordsOnly}: HandleGroupsChannelArgs) => Promise<GroupsChannelModel[]>;
|
||||
}
|
||||
|
||||
const GroupHandler = (superclass: any) => class extends superclass {
|
||||
@@ -56,7 +56,7 @@ const GroupHandler = (superclass: any) => class extends superclass {
|
||||
const createOrUpdateRawValues = getUniqueRawsBy({raws: groupMemberships, key: 'group_id'});
|
||||
|
||||
return this.handleRecords({
|
||||
fieldName: 'user_id',
|
||||
fieldName: 'group_id',
|
||||
findMatchingRecordBy: isRecordGroupMembershipEqualToRaw,
|
||||
transformer: transformGroupMembershipRecord,
|
||||
prepareRecordsOnly,
|
||||
@@ -80,10 +80,10 @@ const GroupHandler = (superclass: any) => class extends superclass {
|
||||
);
|
||||
}
|
||||
|
||||
const createOrUpdateRawValues = getUniqueRawsBy({raws: groups, key: 'name'});
|
||||
const createOrUpdateRawValues = getUniqueRawsBy({raws: groups, key: 'id'});
|
||||
|
||||
return this.handleRecords({
|
||||
fieldName: 'name',
|
||||
fieldName: 'id',
|
||||
findMatchingRecordBy: isRecordGroupEqualToRaw,
|
||||
transformer: transformGroupRecord,
|
||||
prepareRecordsOnly,
|
||||
@@ -93,56 +93,58 @@ const GroupHandler = (superclass: any) => class extends superclass {
|
||||
};
|
||||
|
||||
/**
|
||||
* handleGroupsInTeam: Handler responsible for the Create/Update operations occurring on the GROUPS_IN_TEAM table from the 'Server' schema
|
||||
* @param {HandleGroupsInTeamArgs} groupsInTeamsArgs
|
||||
* @param {RawGroupsInTeam[]} groupsInTeamsArgs.groupsInTeams
|
||||
* @param {boolean} groupsInTeamsArgs.prepareRecordsOnly
|
||||
* handleGroupsTeam: Handler responsible for the Create/Update operations occurring on the GROUPS_TEAM table from the 'Server' schema
|
||||
* @param {HandleGroupsTeamArgs} groupsTeamsArgs
|
||||
* @param {GroupsTeam[]} groupsTeamsArgs.groupsTeams
|
||||
* @param {boolean} groupsTeamsArgs.prepareRecordsOnly
|
||||
* @throws DataOperatorException
|
||||
* @returns {Promise<GroupsInTeamModel[]>}
|
||||
* @returns {Promise<GroupsTeamModel[]>}
|
||||
*/
|
||||
handleGroupsInTeam = ({groupsInTeams, prepareRecordsOnly = true}: HandleGroupsInTeamArgs): Promise<GroupsInTeamModel[]> => {
|
||||
if (!groupsInTeams.length) {
|
||||
handleGroupsTeam = ({groupsTeams, prepareRecordsOnly = true}: HandleGroupsTeamArgs): Promise<GroupsTeamModel[]> => {
|
||||
if (!groupsTeams.length) {
|
||||
throw new DataOperatorException(
|
||||
'An empty "groups" array has been passed to the handleGroupsInTeam method',
|
||||
'An empty "groups" array has been passed to the handleGroupsTeam method',
|
||||
);
|
||||
}
|
||||
|
||||
const createOrUpdateRawValues = getUniqueRawsBy({raws: groupsInTeams, key: 'group_id'});
|
||||
const createOrUpdateRawValues = groupsTeams.filter((gt, index, self) => (
|
||||
index === self.findIndex((item) => item.team_id === gt.team_id && item.group_id === gt.group_id)));
|
||||
|
||||
return this.handleRecords({
|
||||
fieldName: 'group_id',
|
||||
findMatchingRecordBy: isRecordGroupsInTeamEqualToRaw,
|
||||
transformer: transformGroupsInTeamRecord,
|
||||
findMatchingRecordBy: isRecordGroupsTeamEqualToRaw,
|
||||
transformer: transformGroupsTeamRecord,
|
||||
prepareRecordsOnly,
|
||||
createOrUpdateRawValues,
|
||||
tableName: GROUPS_IN_TEAM,
|
||||
tableName: GROUPS_TEAM,
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* handleGroupsInChannel: Handler responsible for the Create/Update operations occurring on the GROUPS_IN_CHANNEL table from the 'Server' schema
|
||||
* @param {HandleGroupsInChannelArgs} groupsInChannelsArgs
|
||||
* @param {RawGroupsInChannel[]} groupsInChannelsArgs.groupsInChannels
|
||||
* @param {boolean} groupsInChannelsArgs.prepareRecordsOnly
|
||||
* handleGroupsChannel: Handler responsible for the Create/Update operations occurring on the GROUPS_CHANNEL table from the 'Server' schema
|
||||
* @param {HandleGroupsChannelArgs} groupsChannelsArgs
|
||||
* @param {GroupsChannel[]} groupsChannelsArgs.groupsChannels
|
||||
* @param {boolean} groupsChannelsArgs.prepareRecordsOnly
|
||||
* @throws DataOperatorException
|
||||
* @returns {Promise<GroupsInChannelModel[]>}
|
||||
* @returns {Promise<GroupsChannelModel[]>}
|
||||
*/
|
||||
handleGroupsInChannel = ({groupsInChannels, prepareRecordsOnly = true}: HandleGroupsInChannelArgs): Promise<GroupsInChannelModel[]> => {
|
||||
if (!groupsInChannels.length) {
|
||||
handleGroupsChannel = ({groupsChannels, prepareRecordsOnly = true}: HandleGroupsChannelArgs): Promise<GroupsChannelModel[]> => {
|
||||
if (!groupsChannels.length) {
|
||||
throw new DataOperatorException(
|
||||
'An empty "groups" array has been passed to the handleGroupsInTeam method',
|
||||
'An empty "groups" array has been passed to the handleGroupsTeam method',
|
||||
);
|
||||
}
|
||||
|
||||
const createOrUpdateRawValues = getUniqueRawsBy({raws: groupsInChannels, key: 'channel_id'});
|
||||
const createOrUpdateRawValues = groupsChannels.filter((gc, index, self) => (
|
||||
index === self.findIndex((item) => item.channel_id === gc.channel_id && item.group_id === gc.group_id)));
|
||||
|
||||
return this.handleRecords({
|
||||
fieldName: 'group_id',
|
||||
findMatchingRecordBy: isRecordGroupsInChannelEqualToRaw,
|
||||
transformer: transformGroupsInChannelRecord,
|
||||
findMatchingRecordBy: isRecordGroupsChannelEqualToRaw,
|
||||
transformer: transformGroupsChannelRecord,
|
||||
prepareRecordsOnly,
|
||||
createOrUpdateRawValues,
|
||||
tableName: GROUPS_IN_CHANNEL,
|
||||
tableName: GROUPS_CHANNEL,
|
||||
});
|
||||
};
|
||||
};
|
||||
|
||||
@@ -4,8 +4,8 @@
|
||||
import {
|
||||
transformGroupMembershipRecord,
|
||||
transformGroupRecord,
|
||||
transformGroupsInChannelRecord,
|
||||
transformGroupsInTeamRecord,
|
||||
transformGroupsChannelRecord,
|
||||
transformGroupsTeamRecord,
|
||||
} from '@database/operator/server_data_operator/transformers/group';
|
||||
import {createTestConnection} from '@database/operator/utils/create_test_connection';
|
||||
import {OperationType} from '@typings/database/enums';
|
||||
@@ -43,13 +43,13 @@ describe('*** GROUP Prepare Records Test ***', () => {
|
||||
expect(preparedRecords!.collection.modelClass.name).toBe('GroupModel');
|
||||
});
|
||||
|
||||
it('=> transformGroupsInTeamRecord: should return an array of type GroupsInTeam', async () => {
|
||||
it('=> transformGroupsTeamRecord: should return an array of type GroupsTeam', async () => {
|
||||
expect.assertions(3);
|
||||
|
||||
const database = await createTestConnection({databaseName: 'group_prepare_records', setActive: true});
|
||||
expect(database).toBeTruthy();
|
||||
|
||||
const preparedRecords = await transformGroupsInTeamRecord({
|
||||
const preparedRecords = await transformGroupsTeamRecord({
|
||||
action: OperationType.CREATE,
|
||||
database: database!,
|
||||
value: {
|
||||
@@ -68,16 +68,16 @@ describe('*** GROUP Prepare Records Test ***', () => {
|
||||
});
|
||||
|
||||
expect(preparedRecords).toBeTruthy();
|
||||
expect(preparedRecords!.collection.modelClass.name).toBe('GroupsInTeamModel');
|
||||
expect(preparedRecords!.collection.modelClass.name).toBe('GroupsTeamModel');
|
||||
});
|
||||
|
||||
it('=> transformGroupsInChannelRecord: should return an array of type GroupsInChannel', async () => {
|
||||
it('=> transformGroupsChannelRecord: should return an array of type GroupsChannel', async () => {
|
||||
expect.assertions(3);
|
||||
|
||||
const database = await createTestConnection({databaseName: 'group_prepare_records', setActive: true});
|
||||
expect(database).toBeTruthy();
|
||||
|
||||
const preparedRecords = await transformGroupsInChannelRecord({
|
||||
const preparedRecords = await transformGroupsChannelRecord({
|
||||
action: OperationType.CREATE,
|
||||
database: database!,
|
||||
value: {
|
||||
@@ -99,7 +99,7 @@ describe('*** GROUP Prepare Records Test ***', () => {
|
||||
});
|
||||
|
||||
expect(preparedRecords).toBeTruthy();
|
||||
expect(preparedRecords!.collection.modelClass.name).toBe('GroupsInChannelModel');
|
||||
expect(preparedRecords!.collection.modelClass.name).toBe('GroupsChannelModel');
|
||||
});
|
||||
|
||||
it('=> transformGroupMembershipRecord: should return an array of type GroupMembership', async () => {
|
||||
|
||||
@@ -8,13 +8,13 @@ import type {TransformerArgs} from '@typings/database/database';
|
||||
import {OperationType} from '@typings/database/enums';
|
||||
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';
|
||||
import type GroupsInTeamModel from '@typings/database/models/servers/groups_in_team';
|
||||
import type GroupsChannelModel from '@typings/database/models/servers/groups_channel';
|
||||
import type GroupsTeamModel from '@typings/database/models/servers/groups_team';
|
||||
|
||||
const {
|
||||
GROUP,
|
||||
GROUPS_IN_CHANNEL,
|
||||
GROUPS_IN_TEAM,
|
||||
GROUPS_CHANNEL,
|
||||
GROUPS_TEAM,
|
||||
GROUP_MEMBERSHIP,
|
||||
} = MM_TABLES.SERVER;
|
||||
|
||||
@@ -61,6 +61,8 @@ export const transformGroupRecord = ({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 = (group: GroupModel) => {
|
||||
group._raw.id = isCreateAction ? (raw?.id ?? group.id) : record.id;
|
||||
group.allowReference = raw.allow_reference;
|
||||
group.deleteAt = raw.delete_at;
|
||||
group.name = raw.name;
|
||||
group.displayName = raw.display_name;
|
||||
};
|
||||
@@ -75,57 +77,55 @@ export const transformGroupRecord = ({action, database, value}: TransformerArgs)
|
||||
};
|
||||
|
||||
/**
|
||||
* transformGroupsInTeamRecord: Prepares a record of the SERVER database 'GroupsInTeam' table for update or create actions.
|
||||
* transformGroupsTeamRecord: Prepares a record of the SERVER database 'GroupsTeam' table for update or create actions.
|
||||
* @param {DataFactory} operator
|
||||
* @param {Database} operator.database
|
||||
* @param {RecordPair} operator.value
|
||||
* @returns {Promise<GroupsInTeamModel>}
|
||||
* @returns {Promise<GroupsTeamModel>}
|
||||
*/
|
||||
export const transformGroupsInTeamRecord = ({action, database, value}: TransformerArgs): Promise<GroupsInTeamModel> => {
|
||||
export const transformGroupsTeamRecord = ({action, database, value}: TransformerArgs): Promise<GroupsTeamModel> => {
|
||||
const raw = value.raw as GroupTeam;
|
||||
const record = value.record as GroupsInTeamModel;
|
||||
const record = value.record as GroupsTeamModel;
|
||||
const isCreateAction = action === OperationType.CREATE;
|
||||
|
||||
const fieldsMapper = (groupsInTeam: GroupsInTeamModel) => {
|
||||
groupsInTeam._raw.id = isCreateAction ? groupsInTeam.id : record.id;
|
||||
groupsInTeam.teamId = raw.team_id;
|
||||
groupsInTeam.groupId = raw.group_id;
|
||||
const fieldsMapper = (groupsTeam: GroupsTeamModel) => {
|
||||
groupsTeam._raw.id = isCreateAction ? groupsTeam.id : record.id;
|
||||
groupsTeam.teamId = raw.team_id;
|
||||
groupsTeam.groupId = raw.group_id;
|
||||
};
|
||||
|
||||
return prepareBaseRecord({
|
||||
action,
|
||||
database,
|
||||
tableName: GROUPS_IN_TEAM,
|
||||
tableName: GROUPS_TEAM,
|
||||
value,
|
||||
fieldsMapper,
|
||||
}) as Promise<GroupsInTeamModel>;
|
||||
}) as Promise<GroupsTeamModel>;
|
||||
};
|
||||
|
||||
/**
|
||||
* transformGroupsInChannelRecord: Prepares a record of the SERVER database 'GroupsInChannel' table for update or create actions.
|
||||
* transformGroupsChannelRecord: Prepares a record of the SERVER database 'GroupsChannel' table for update or create actions.
|
||||
* @param {DataFactory} operator
|
||||
* @param {Database} operator.database
|
||||
* @param {RecordPair} operator.value
|
||||
* @returns {Promise<GroupsInChannelModel>}
|
||||
* @returns {Promise<GroupsChannelModel>}
|
||||
*/
|
||||
export const transformGroupsInChannelRecord = ({action, database, value}: TransformerArgs): Promise<GroupsInChannelModel> => {
|
||||
const raw = value.raw as GroupChannel;
|
||||
const record = value.record as GroupsInChannelModel;
|
||||
export const transformGroupsChannelRecord = ({action, database, value}: TransformerArgs): Promise<GroupsChannelModel> => {
|
||||
const raw = value.raw as GroupChannelRelation;
|
||||
const record = value.record as GroupsChannelModel;
|
||||
const isCreateAction = action === OperationType.CREATE;
|
||||
|
||||
const fieldsMapper = (groupsInChannel: GroupsInChannelModel) => {
|
||||
groupsInChannel._raw.id = isCreateAction ? groupsInChannel.id : record.id;
|
||||
groupsInChannel.channelId = raw.channel_id;
|
||||
groupsInChannel.groupId = raw.group_id;
|
||||
groupsInChannel.memberCount = raw.member_count;
|
||||
groupsInChannel.timezoneCount = raw.timezone_count;
|
||||
const fieldsMapper = (groupsChannel: GroupsChannelModel) => {
|
||||
groupsChannel._raw.id = isCreateAction ? groupsChannel.id : record.id;
|
||||
groupsChannel.channelId = raw.channel_id;
|
||||
groupsChannel.groupId = raw.group_id;
|
||||
};
|
||||
|
||||
return prepareBaseRecord({
|
||||
action,
|
||||
database,
|
||||
tableName: GROUPS_IN_CHANNEL,
|
||||
tableName: GROUPS_CHANNEL,
|
||||
value,
|
||||
fieldsMapper,
|
||||
}) as Promise<GroupsInChannelModel>;
|
||||
}) as Promise<GroupsChannelModel>;
|
||||
};
|
||||
|
||||
@@ -12,8 +12,8 @@ import {
|
||||
FileSchema,
|
||||
GroupMembershipSchema,
|
||||
GroupSchema,
|
||||
GroupsInChannelSchema,
|
||||
GroupsInTeamSchema,
|
||||
GroupsChannelSchema,
|
||||
GroupsTeamSchema,
|
||||
MyChannelSchema,
|
||||
MyChannelSettingsSchema,
|
||||
MyTeamSchema,
|
||||
@@ -45,8 +45,8 @@ export const serverSchema: AppSchema = appSchema({
|
||||
FileSchema,
|
||||
GroupMembershipSchema,
|
||||
GroupSchema,
|
||||
GroupsInChannelSchema,
|
||||
GroupsInTeamSchema,
|
||||
GroupsChannelSchema,
|
||||
GroupsTeamSchema,
|
||||
MyChannelSchema,
|
||||
MyChannelSettingsSchema,
|
||||
MyTeamSchema,
|
||||
|
||||
@@ -10,6 +10,8 @@ const {GROUP} = MM_TABLES.SERVER;
|
||||
export default tableSchema({
|
||||
name: GROUP,
|
||||
columns: [
|
||||
{name: 'allow_reference', type: 'boolean'},
|
||||
{name: 'delete_at', type: 'number'},
|
||||
{name: 'display_name', type: 'string'},
|
||||
{name: 'name', type: 'string'},
|
||||
],
|
||||
|
||||
@@ -5,10 +5,10 @@ import {tableSchema} from '@nozbe/watermelondb';
|
||||
|
||||
import {MM_TABLES} from '@constants/database';
|
||||
|
||||
const {GROUPS_IN_CHANNEL} = MM_TABLES.SERVER;
|
||||
const {GROUPS_CHANNEL} = MM_TABLES.SERVER;
|
||||
|
||||
export default tableSchema({
|
||||
name: GROUPS_IN_CHANNEL,
|
||||
name: GROUPS_CHANNEL,
|
||||
columns: [
|
||||
{name: 'channel_id', type: 'string', isIndexed: true},
|
||||
{name: 'group_id', type: 'string', isIndexed: true},
|
||||
@@ -5,10 +5,10 @@ import {tableSchema} from '@nozbe/watermelondb';
|
||||
|
||||
import {MM_TABLES} from '@constants/database';
|
||||
|
||||
const {GROUPS_IN_TEAM} = MM_TABLES.SERVER;
|
||||
const {GROUPS_TEAM} = MM_TABLES.SERVER;
|
||||
|
||||
export default tableSchema({
|
||||
name: GROUPS_IN_TEAM,
|
||||
name: GROUPS_TEAM,
|
||||
columns: [
|
||||
{name: 'group_id', type: 'string', isIndexed: true},
|
||||
{name: 'team_id', type: 'string', isIndexed: true},
|
||||
@@ -9,8 +9,8 @@ export {default as DraftSchema} from './draft';
|
||||
export {default as FileSchema} from './file';
|
||||
export {default as GroupMembershipSchema} from './group_membership';
|
||||
export {default as GroupSchema} from './group';
|
||||
export {default as GroupsInChannelSchema} from './groups_in_channel';
|
||||
export {default as GroupsInTeamSchema} from './groups_in_team';
|
||||
export {default as GroupsChannelSchema} from './groups_channel';
|
||||
export {default as GroupsTeamSchema} from './groups_team';
|
||||
export {default as MyChannelSchema} from './my_channel';
|
||||
export {default as MyChannelSettingsSchema} from './my_channel_settings';
|
||||
export {default as MyTeamSchema} from './my_team';
|
||||
|
||||
@@ -15,8 +15,8 @@ const {
|
||||
DRAFT,
|
||||
FILE,
|
||||
GROUP,
|
||||
GROUPS_IN_CHANNEL,
|
||||
GROUPS_IN_TEAM,
|
||||
GROUPS_CHANNEL,
|
||||
GROUPS_TEAM,
|
||||
GROUP_MEMBERSHIP,
|
||||
MY_CHANNEL,
|
||||
MY_CHANNEL_SETTINGS,
|
||||
@@ -248,16 +248,20 @@ describe('*** Test schema for SERVER database ***', () => {
|
||||
[GROUP]: {
|
||||
name: GROUP,
|
||||
columns: {
|
||||
allow_reference: {name: 'allow_reference', type: 'boolean'},
|
||||
delete_at: {name: 'delete_at', type: 'number'},
|
||||
display_name: {name: 'display_name', type: 'string'},
|
||||
name: {name: 'name', type: 'string'},
|
||||
},
|
||||
columnArray: [
|
||||
{name: 'allow_reference', type: 'boolean'},
|
||||
{name: 'delete_at', type: 'number'},
|
||||
{name: 'display_name', type: 'string'},
|
||||
{name: 'name', type: 'string'},
|
||||
],
|
||||
},
|
||||
[GROUPS_IN_CHANNEL]: {
|
||||
name: GROUPS_IN_CHANNEL,
|
||||
[GROUPS_CHANNEL]: {
|
||||
name: GROUPS_CHANNEL,
|
||||
columns: {
|
||||
channel_id: {name: 'channel_id', type: 'string', isIndexed: true},
|
||||
group_id: {name: 'group_id', type: 'string', isIndexed: true},
|
||||
@@ -271,8 +275,8 @@ describe('*** Test schema for SERVER database ***', () => {
|
||||
{name: 'timezone_count', type: 'number'},
|
||||
],
|
||||
},
|
||||
[GROUPS_IN_TEAM]: {
|
||||
name: GROUPS_IN_TEAM,
|
||||
[GROUPS_TEAM]: {
|
||||
name: GROUPS_TEAM,
|
||||
columns: {
|
||||
group_id: {name: 'group_id', type: 'string', isIndexed: true},
|
||||
team_id: {name: 'team_id', type: 'string', isIndexed: true},
|
||||
|
||||
@@ -5,6 +5,9 @@ import {General, Preferences} from '@constants';
|
||||
import {UserModel} from '@database/models/server';
|
||||
import {DEFAULT_LOCALE, getLocalizedMessage, t} from '@i18n';
|
||||
|
||||
import type GroupModel from '@typings/database/models/servers/group';
|
||||
import type GroupMembershipModel from '@typings/database/models/servers/group_membership';
|
||||
|
||||
export function displayUsername(user?: UserProfile | UserModel, locale?: string, teammateDisplayNameSetting?: string, useFallbackUsername = true) {
|
||||
let name = useFallbackUsername ? getLocalizedMessage(locale || DEFAULT_LOCALE, t('channel_loader.someone'), 'Someone') : '';
|
||||
|
||||
@@ -94,7 +97,7 @@ export const getUsersByUsername = (users: UserModel[]) => {
|
||||
return usersByUsername;
|
||||
};
|
||||
|
||||
export const getUserMentionKeys = (user: UserModel) => {
|
||||
export const getUserMentionKeys = (user: UserModel, groups: GroupModel[], userGroups: GroupMembershipModel[]) => {
|
||||
const keys: UserMentionKey[] = [];
|
||||
|
||||
if (!user.notifyProps) {
|
||||
@@ -123,5 +126,17 @@ export const getUserMentionKeys = (user: UserModel) => {
|
||||
keys.push({key: usernameKey});
|
||||
}
|
||||
|
||||
if (groups.length && userGroups.length) {
|
||||
const groupMentions = userGroups.reduce((result: Array<{key: string}>, ug: GroupMembershipModel) => {
|
||||
const group = groups.find((g) => ug.groupId === g.id);
|
||||
if (group) {
|
||||
result.push({key: `@${group.name}`});
|
||||
}
|
||||
return result;
|
||||
}, []);
|
||||
|
||||
keys.push(...groupMentions);
|
||||
}
|
||||
|
||||
return keys;
|
||||
};
|
||||
|
||||
4
types/api/groups.d.ts
vendored
4
types/api/groups.d.ts
vendored
@@ -45,8 +45,8 @@ type GroupChannel = {
|
||||
create_at: number;
|
||||
delete_at: number;
|
||||
update_at: number;
|
||||
member_count: number;
|
||||
timezone_count: number;
|
||||
member_count?: number;
|
||||
timezone_count?: number;
|
||||
};
|
||||
type GroupSyncables = {
|
||||
teams: GroupTeam[];
|
||||
|
||||
8
types/database/database.d.ts
vendored
8
types/database/database.d.ts
vendored
@@ -213,12 +213,12 @@ export type HandleTeamArgs = PrepareOnly & {
|
||||
teams: Team[];
|
||||
};
|
||||
|
||||
export type HandleGroupsInChannelArgs = PrepareOnly & {
|
||||
groupsInChannels: GroupChannel[];
|
||||
export type HandleGroupsChannelArgs = PrepareOnly & {
|
||||
groupsChannels: GroupChannelRelation[];
|
||||
};
|
||||
|
||||
export type HandleGroupsInTeamArgs = PrepareOnly &{
|
||||
groupsInTeams: GroupTeam[];
|
||||
export type HandleGroupsTeamArgs = PrepareOnly &{
|
||||
groupsTeams: GroupTeamRelation[];
|
||||
};
|
||||
|
||||
export type HandleGroupArgs = PrepareOnly & {
|
||||
|
||||
4
types/database/models/servers/channel.d.ts
vendored
4
types/database/models/servers/channel.d.ts
vendored
@@ -47,8 +47,8 @@ export default class ChannelModel extends Model {
|
||||
/** drafts : All drafts for this channel */
|
||||
drafts: Query<DraftModel>;
|
||||
|
||||
/** groupsInChannel : Every group contained in this channel */
|
||||
groupsInChannel: Query<GroupsInChannelModel>;
|
||||
/** groupsChannel : Every group contained in this channel */
|
||||
groupsChannel: Query<GroupsChannelModel>;
|
||||
|
||||
/** posts : All posts made in the channel */
|
||||
posts: Query<PostModel>;
|
||||
|
||||
14
types/database/models/servers/group.d.ts
vendored
14
types/database/models/servers/group.d.ts
vendored
@@ -15,17 +15,23 @@ export default class GroupModel extends Model {
|
||||
/** associations : Describes every relationship to this table. */
|
||||
static associations: Associations;
|
||||
|
||||
/** allow_reference : Determins if the group can be referenced in mentions */
|
||||
allowReference: boolean;
|
||||
|
||||
/** delete_at : When the group was deleted */
|
||||
deleteAt: number;
|
||||
|
||||
/** display_name : The display name for the group */
|
||||
displayName: string;
|
||||
|
||||
/** name : The name of the group */
|
||||
name: string;
|
||||
|
||||
/** groupsInChannel : All the related children records from GroupsInChannel */
|
||||
groupsInChannel: Query<GroupsInChannelModel>;
|
||||
/** groupsChannel : All the related children records from GroupsChannel */
|
||||
groupsChannel: Query<GroupsChannelModel>;
|
||||
|
||||
/** groupsInTeam : All the related children records from GroupsInTeam */
|
||||
groupsInTeam: Query<GroupsInTeamModel>;
|
||||
/** groupsTeam : All the related children records from GroupsTeam */
|
||||
groupsTeam: Query<GroupsTeamModel>;
|
||||
|
||||
/** groupMemberships : All the related children records from GroupMembership */
|
||||
groupMemberships: Query<GroupMembershipModel>;
|
||||
|
||||
@@ -5,10 +5,10 @@ import {Relation} from '@nozbe/watermelondb';
|
||||
import Model, {Associations} from '@nozbe/watermelondb/Model';
|
||||
|
||||
/**
|
||||
* The GroupsInChannel links the Channel model with the Group model
|
||||
* The GroupsChannel links the Channel model with the Group model
|
||||
*/
|
||||
export default class GroupsInChannelModel extends Model {
|
||||
/** table (name) : GroupsInChannel */
|
||||
export default class GroupsChannelModel extends Model {
|
||||
/** table (name) : GroupsChannel */
|
||||
static table: string;
|
||||
|
||||
/** associations : Describes every relationship to this table. */
|
||||
@@ -20,12 +20,6 @@ export default class GroupsInChannelModel extends Model {
|
||||
/** group_id : The foreign key of the related GROUP model */
|
||||
groupId: string;
|
||||
|
||||
/** member_count : The number of members in that group */
|
||||
memberCount: number;
|
||||
|
||||
/** timezone_count : The number of timezones in that group */
|
||||
timezoneCount: number;
|
||||
|
||||
/** channel : The related record to the parent Channel model */
|
||||
channel: Relation<ChannelModel>;
|
||||
|
||||
@@ -5,10 +5,10 @@ import {Relation} from '@nozbe/watermelondb';
|
||||
import Model, {Associations} from '@nozbe/watermelondb/Model';
|
||||
|
||||
/**
|
||||
* The GroupsInTeam links the Team model with the Group model
|
||||
* The GroupsTeam links the Team model with the Group model
|
||||
*/
|
||||
export default class GroupsInTeamModel extends Model {
|
||||
/** table (name) : GroupsInTeam */
|
||||
export default class GroupsTeamModel extends Model {
|
||||
/** table (name) : GroupsTeam */
|
||||
static table: string;
|
||||
|
||||
/** associations : Describes every relationship to this table. */
|
||||
4
types/database/models/servers/team.d.ts
vendored
4
types/database/models/servers/team.d.ts
vendored
@@ -44,8 +44,8 @@ export default class TeamModel extends Model {
|
||||
/** channels : All the channels associated with this team */
|
||||
channels: Query<ChannelModel>;
|
||||
|
||||
/** groupsInTeam : All the groups associated with this team */
|
||||
groupsInTeam: Query<GroupsInTeamModel>;
|
||||
/** groupsTeam : All the groups associated with this team */
|
||||
groupsTeam: Query<GroupsTeamModel>;
|
||||
|
||||
/** myTeam : Retrieves additional information about the team that this user is possibly part of. This query might yield no result if the user isn't part of a team. */
|
||||
myTeam: Relation<MyTeamModel>;
|
||||
|
||||
10
types/database/raw_values.d.ts
vendored
10
types/database/raw_values.d.ts
vendored
@@ -29,6 +29,16 @@ type GroupMembership = {
|
||||
group_id: string;
|
||||
};
|
||||
|
||||
type GroupChannelRelation = {
|
||||
channel_id: string;
|
||||
group_id: string;
|
||||
}
|
||||
|
||||
type GroupTeamRelation = {
|
||||
group_id: string;
|
||||
team_id: string;
|
||||
}
|
||||
|
||||
type MyTeam = {
|
||||
id: string;
|
||||
roles: string;
|
||||
|
||||
Reference in New Issue
Block a user