1// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
2// See LICENSE.txt for license information.
3
4package app
5
6import (
7	"encoding/json"
8	"errors"
9	"net/http"
10
11	"github.com/mattermost/mattermost-server/v6/model"
12	"github.com/mattermost/mattermost-server/v6/shared/mlog"
13	"github.com/mattermost/mattermost-server/v6/store"
14)
15
16func (a *App) GetGroup(id string) (*model.Group, *model.AppError) {
17	group, err := a.Srv().Store.Group().Get(id)
18	if err != nil {
19		var nfErr *store.ErrNotFound
20		switch {
21		case errors.As(err, &nfErr):
22			return nil, model.NewAppError("GetGroup", "app.group.no_rows", nil, nfErr.Error(), http.StatusNotFound)
23		default:
24			return nil, model.NewAppError("GetGroup", "app.select_error", nil, err.Error(), http.StatusInternalServerError)
25		}
26	}
27
28	return group, nil
29}
30
31func (a *App) GetGroupByName(name string, opts model.GroupSearchOpts) (*model.Group, *model.AppError) {
32	group, err := a.Srv().Store.Group().GetByName(name, opts)
33	if err != nil {
34		var nfErr *store.ErrNotFound
35		switch {
36		case errors.As(err, &nfErr):
37			return nil, model.NewAppError("GetGroupByName", "app.group.no_rows", nil, nfErr.Error(), http.StatusNotFound)
38		default:
39			return nil, model.NewAppError("GetGroupByName", "app.select_error", nil, err.Error(), http.StatusInternalServerError)
40		}
41	}
42
43	return group, nil
44}
45
46func (a *App) GetGroupByRemoteID(remoteID string, groupSource model.GroupSource) (*model.Group, *model.AppError) {
47	group, err := a.Srv().Store.Group().GetByRemoteID(remoteID, groupSource)
48	if err != nil {
49		var nfErr *store.ErrNotFound
50		switch {
51		case errors.As(err, &nfErr):
52			return nil, model.NewAppError("GetGroupByRemoteID", "app.group.no_rows", nil, nfErr.Error(), http.StatusNotFound)
53		default:
54			return nil, model.NewAppError("GetGroupByRemoteID", "app.select_error", nil, err.Error(), http.StatusInternalServerError)
55		}
56	}
57
58	return group, nil
59}
60
61func (a *App) GetGroupsBySource(groupSource model.GroupSource) ([]*model.Group, *model.AppError) {
62	groups, err := a.Srv().Store.Group().GetAllBySource(groupSource)
63	if err != nil {
64		return nil, model.NewAppError("GetGroupsBySource", "app.select_error", nil, err.Error(), http.StatusInternalServerError)
65	}
66
67	return groups, nil
68}
69
70func (a *App) GetGroupsByUserId(userID string) ([]*model.Group, *model.AppError) {
71	groups, err := a.Srv().Store.Group().GetByUser(userID)
72	if err != nil {
73		return nil, model.NewAppError("GetGroupsByUserId", "app.select_error", nil, err.Error(), http.StatusInternalServerError)
74	}
75
76	return groups, nil
77}
78
79func (a *App) CreateGroup(group *model.Group) (*model.Group, *model.AppError) {
80	group, err := a.Srv().Store.Group().Create(group)
81	if err != nil {
82		var invErr *store.ErrInvalidInput
83		var appErr *model.AppError
84		switch {
85		case errors.As(err, &appErr):
86			return nil, appErr
87		case errors.As(err, &invErr):
88			return nil, model.NewAppError("CreateGroup", "app.group.id.app_error", nil, invErr.Error(), http.StatusBadRequest)
89		default:
90			return nil, model.NewAppError("CreateGroup", "app.insert_error", nil, err.Error(), http.StatusInternalServerError)
91		}
92	}
93
94	return group, nil
95}
96
97func (a *App) UpdateGroup(group *model.Group) (*model.Group, *model.AppError) {
98	updatedGroup, err := a.Srv().Store.Group().Update(group)
99
100	if err == nil {
101		messageWs := model.NewWebSocketEvent(model.WebsocketEventReceivedGroup, "", "", "", nil)
102		groupJSON, jsonErr := json.Marshal(updatedGroup)
103		if jsonErr != nil {
104			mlog.Warn("Failed to encode group to JSON", mlog.Err(jsonErr))
105		}
106		messageWs.Add("group", string(groupJSON))
107		a.Publish(messageWs)
108	}
109
110	if err != nil {
111		var nfErr *store.ErrNotFound
112		var appErr *model.AppError
113		switch {
114		case errors.As(err, &appErr):
115			return nil, appErr
116		case errors.As(err, &nfErr):
117			return nil, model.NewAppError("UpdateGroup", "app.group.no_rows", nil, nfErr.Error(), http.StatusNotFound)
118		default:
119			return nil, model.NewAppError("UpdateGroup", "app.select_error", nil, err.Error(), http.StatusInternalServerError)
120		}
121	}
122
123	return updatedGroup, nil
124}
125
126func (a *App) DeleteGroup(groupID string) (*model.Group, *model.AppError) {
127	deletedGroup, err := a.Srv().Store.Group().Delete(groupID)
128
129	if err == nil {
130		messageWs := model.NewWebSocketEvent(model.WebsocketEventReceivedGroup, "", "", "", nil)
131		groupJSON, jsonErr := json.Marshal(deletedGroup)
132		if jsonErr != nil {
133			mlog.Warn("Failed to encode group to JSON", mlog.Err(jsonErr))
134		}
135		messageWs.Add("group", string(groupJSON))
136		a.Publish(messageWs)
137	}
138
139	if err != nil {
140		var nfErr *store.ErrNotFound
141		switch {
142		case errors.As(err, &nfErr):
143			return nil, model.NewAppError("DeleteGroup", "app.group.no_rows", nil, nfErr.Error(), http.StatusNotFound)
144		default:
145			return nil, model.NewAppError("DeleteGroup", "app.update_error", nil, err.Error(), http.StatusInternalServerError)
146		}
147	}
148
149	return deletedGroup, nil
150}
151
152func (a *App) GetGroupMemberCount(groupID string) (int64, *model.AppError) {
153	count, err := a.Srv().Store.Group().GetMemberCount(groupID)
154	if err != nil {
155		return 0, model.NewAppError("GetGroupMemberCount", "app.select_error", nil, err.Error(), http.StatusInternalServerError)
156	}
157
158	return count, nil
159}
160
161func (a *App) GetGroupMemberUsers(groupID string) ([]*model.User, *model.AppError) {
162	users, err := a.Srv().Store.Group().GetMemberUsers(groupID)
163	if err != nil {
164		return nil, model.NewAppError("GetGroupMemberUsers", "app.select_error", nil, err.Error(), http.StatusInternalServerError)
165	}
166
167	return users, nil
168}
169
170func (a *App) GetGroupMemberUsersPage(groupID string, page int, perPage int) ([]*model.User, int, *model.AppError) {
171	members, err := a.Srv().Store.Group().GetMemberUsersPage(groupID, page, perPage)
172	if err != nil {
173		return nil, 0, model.NewAppError("GetGroupMemberUsersPage", "app.select_error", nil, err.Error(), http.StatusInternalServerError)
174	}
175
176	count, appErr := a.GetGroupMemberCount(groupID)
177	if appErr != nil {
178		return nil, 0, appErr
179	}
180	return members, int(count), nil
181}
182
183func (a *App) UpsertGroupMember(groupID string, userID string) (*model.GroupMember, *model.AppError) {
184	groupMember, err := a.Srv().Store.Group().UpsertMember(groupID, userID)
185	if err != nil {
186		var invErr *store.ErrInvalidInput
187		var appErr *model.AppError
188		switch {
189		case errors.As(err, &appErr):
190			return nil, appErr
191		case errors.As(err, &invErr):
192			return nil, model.NewAppError("UpsertGroupMember", "app.group.uniqueness_error", nil, invErr.Error(), http.StatusBadRequest)
193		default:
194			return nil, model.NewAppError("UpsertGroupMember", "app.update_error", nil, err.Error(), http.StatusInternalServerError)
195		}
196	}
197
198	return groupMember, nil
199}
200
201func (a *App) DeleteGroupMember(groupID string, userID string) (*model.GroupMember, *model.AppError) {
202	groupMember, err := a.Srv().Store.Group().DeleteMember(groupID, userID)
203	if err != nil {
204		var nfErr *store.ErrNotFound
205		switch {
206		case errors.As(err, &nfErr):
207			return nil, model.NewAppError("DeleteGroupMember", "app.group.no_rows", nil, nfErr.Error(), http.StatusNotFound)
208		default:
209			return nil, model.NewAppError("DeleteGroupMember", "app.update_error", nil, err.Error(), http.StatusInternalServerError)
210		}
211	}
212
213	return groupMember, nil
214}
215
216func (a *App) UpsertGroupSyncable(groupSyncable *model.GroupSyncable) (*model.GroupSyncable, *model.AppError) {
217	gs, err := a.Srv().Store.Group().GetGroupSyncable(groupSyncable.GroupId, groupSyncable.SyncableId, groupSyncable.Type)
218	var notFoundErr *store.ErrNotFound
219	if err != nil && !errors.As(err, &notFoundErr) {
220		return nil, model.NewAppError("UpsertGroupSyncable", "app.select_error", nil, err.Error(), http.StatusInternalServerError)
221	}
222
223	// reject the syncable creation if the group isn't already associated to the parent team
224	if groupSyncable.Type == model.GroupSyncableTypeChannel {
225		channel, nErr := a.Srv().Store.Channel().Get(groupSyncable.SyncableId, true)
226		if nErr != nil {
227			var nfErr *store.ErrNotFound
228			switch {
229			case errors.As(nErr, &nfErr):
230				return nil, model.NewAppError("UpsertGroupSyncable", "app.channel.get.existing.app_error", nil, nfErr.Error(), http.StatusNotFound)
231			default:
232				return nil, model.NewAppError("UpsertGroupSyncable", "app.channel.get.find.app_error", nil, nErr.Error(), http.StatusInternalServerError)
233			}
234		}
235
236		var team *model.Team
237		team, nErr = a.Srv().Store.Team().Get(channel.TeamId)
238		if nErr != nil {
239			var nfErr *store.ErrNotFound
240			switch {
241			case errors.As(nErr, &nfErr):
242				return nil, model.NewAppError("UpsertGroupSyncable", "app.team.get.find.app_error", nil, nfErr.Error(), http.StatusNotFound)
243			default:
244				return nil, model.NewAppError("UpsertGroupSyncable", "app.team.get.finding.app_error", nil, nErr.Error(), http.StatusInternalServerError)
245			}
246		}
247		if team.IsGroupConstrained() {
248			var teamGroups []*model.GroupWithSchemeAdmin
249			teamGroups, err = a.Srv().Store.Group().GetGroupsByTeam(channel.TeamId, model.GroupSearchOpts{})
250			if err != nil {
251				return nil, model.NewAppError("UpsertGroupSyncable", "app.select_error", nil, err.Error(), http.StatusInternalServerError)
252			}
253			var permittedGroup bool
254			for _, teamGroup := range teamGroups {
255				if teamGroup.Group.Id == groupSyncable.GroupId {
256					permittedGroup = true
257					break
258				}
259			}
260			if !permittedGroup {
261				return nil, model.NewAppError("UpsertGroupSyncable", "group_not_associated_to_synced_team", nil, "", http.StatusBadRequest)
262			}
263		} else {
264			_, appErr := a.UpsertGroupSyncable(model.NewGroupTeam(groupSyncable.GroupId, team.Id, groupSyncable.AutoAdd))
265			if appErr != nil {
266				return nil, appErr
267			}
268		}
269	}
270
271	if gs == nil {
272		gs, err = a.Srv().Store.Group().CreateGroupSyncable(groupSyncable)
273		if err != nil {
274			var nfErr *store.ErrNotFound
275			var appErr *model.AppError
276			switch {
277			case errors.As(err, &appErr):
278				return nil, appErr
279			case errors.As(err, &nfErr):
280				return nil, model.NewAppError("UpsertGroupSyncable", "store.sql_channel.get.existing.app_error", nil, nfErr.Error(), http.StatusNotFound)
281			default:
282				return nil, model.NewAppError("UpsertGroupSyncable", "app.insert_error", nil, err.Error(), http.StatusInternalServerError)
283			}
284		}
285	} else {
286		gs, err = a.Srv().Store.Group().UpdateGroupSyncable(groupSyncable)
287		if err != nil {
288			var appErr *model.AppError
289			switch {
290			case errors.As(err, &appErr):
291				return nil, appErr
292			default:
293				return nil, model.NewAppError("UpsertGroupSyncable", "app.update_error", nil, err.Error(), http.StatusInternalServerError)
294			}
295		}
296	}
297
298	var messageWs *model.WebSocketEvent
299	if gs.Type == model.GroupSyncableTypeTeam {
300		messageWs = model.NewWebSocketEvent(model.WebsocketEventReceivedGroupAssociatedToTeam, gs.SyncableId, "", "", nil)
301	} else {
302		messageWs = model.NewWebSocketEvent(model.WebsocketEventReceivedGroupAssociatedToChannel, "", gs.SyncableId, "", nil)
303	}
304	messageWs.Add("group_id", gs.GroupId)
305	a.Publish(messageWs)
306
307	return gs, nil
308}
309
310func (a *App) GetGroupSyncable(groupID string, syncableID string, syncableType model.GroupSyncableType) (*model.GroupSyncable, *model.AppError) {
311	group, err := a.Srv().Store.Group().GetGroupSyncable(groupID, syncableID, syncableType)
312	if err != nil {
313		var nfErr *store.ErrNotFound
314		switch {
315		case errors.As(err, &nfErr):
316			return nil, model.NewAppError("GetGroupSyncable", "app.group.no_rows", nil, nfErr.Error(), http.StatusNotFound)
317		default:
318			return nil, model.NewAppError("GetGroupSyncable", "app.select_error", nil, err.Error(), http.StatusInternalServerError)
319		}
320	}
321
322	return group, nil
323}
324
325func (a *App) GetGroupSyncables(groupID string, syncableType model.GroupSyncableType) ([]*model.GroupSyncable, *model.AppError) {
326	groups, err := a.Srv().Store.Group().GetAllGroupSyncablesByGroupId(groupID, syncableType)
327	if err != nil {
328		return nil, model.NewAppError("GetGroupSyncables", "app.select_error", nil, err.Error(), http.StatusInternalServerError)
329	}
330
331	return groups, nil
332}
333
334func (a *App) UpdateGroupSyncable(groupSyncable *model.GroupSyncable) (*model.GroupSyncable, *model.AppError) {
335	if groupSyncable.DeleteAt == 0 {
336		// updating a *deleted* GroupSyncable, so no need to ensure the GroupTeam is present (as done in the upsert)
337		gs, err := a.Srv().Store.Group().UpdateGroupSyncable(groupSyncable)
338		if err != nil {
339			var appErr *model.AppError
340			switch {
341			case errors.As(err, &appErr):
342				return nil, appErr
343			default:
344				return nil, model.NewAppError("UpdateGroupSyncable", "app.update_error", nil, err.Error(), http.StatusInternalServerError)
345			}
346		}
347
348		return gs, nil
349	}
350
351	// do an upsert to ensure that there's an associated GroupTeam
352	gs, err := a.UpsertGroupSyncable(groupSyncable)
353	if err != nil {
354		return nil, err
355	}
356
357	return gs, nil
358}
359
360func (a *App) DeleteGroupSyncable(groupID string, syncableID string, syncableType model.GroupSyncableType) (*model.GroupSyncable, *model.AppError) {
361	gs, err := a.Srv().Store.Group().DeleteGroupSyncable(groupID, syncableID, syncableType)
362	if err != nil {
363		var invErr *store.ErrInvalidInput
364		var nfErr *store.ErrNotFound
365		switch {
366		case errors.As(err, &nfErr):
367			return nil, model.NewAppError("DeleteGroupSyncable", "app.group.no_rows", nil, nfErr.Error(), http.StatusNotFound)
368		case errors.As(err, &invErr):
369			return nil, model.NewAppError("DeleteGroupSyncable", "app.group.group_syncable_already_deleted", nil, invErr.Error(), http.StatusBadRequest)
370		default:
371			return nil, model.NewAppError("DeleteGroupSyncable", "app.update_error", nil, err.Error(), http.StatusInternalServerError)
372		}
373	}
374
375	// if a GroupTeam is being deleted delete all associated GroupChannels
376	if gs.Type == model.GroupSyncableTypeTeam {
377		allGroupChannels, err := a.Srv().Store.Group().GetAllGroupSyncablesByGroupId(gs.GroupId, model.GroupSyncableTypeChannel)
378		if err != nil {
379			return nil, model.NewAppError("DeleteGroupSyncable", "app.select_error", nil, err.Error(), http.StatusInternalServerError)
380		}
381
382		for _, groupChannel := range allGroupChannels {
383			_, err = a.Srv().Store.Group().DeleteGroupSyncable(groupChannel.GroupId, groupChannel.SyncableId, groupChannel.Type)
384			if err != nil {
385				var invErr *store.ErrInvalidInput
386				var nfErr *store.ErrNotFound
387				switch {
388				case errors.As(err, &nfErr):
389					return nil, model.NewAppError("DeleteGroupSyncable", "app.group.no_rows", nil, nfErr.Error(), http.StatusNotFound)
390				case errors.As(err, &invErr):
391					return nil, model.NewAppError("DeleteGroupSyncable", "app.group.group_syncable_already_deleted", nil, invErr.Error(), http.StatusBadRequest)
392				default:
393					return nil, model.NewAppError("DeleteGroupSyncable", "app.update_error", nil, err.Error(), http.StatusInternalServerError)
394				}
395			}
396		}
397	}
398
399	var messageWs *model.WebSocketEvent
400	if gs.Type == model.GroupSyncableTypeTeam {
401		messageWs = model.NewWebSocketEvent(model.WebsocketEventReceivedGroupNotAssociatedToTeam, gs.SyncableId, "", "", nil)
402	} else {
403		messageWs = model.NewWebSocketEvent(model.WebsocketEventReceivedGroupNotAssociatedToChannel, "", gs.SyncableId, "", nil)
404	}
405
406	messageWs.Add("group_id", gs.GroupId)
407	a.Publish(messageWs)
408
409	return gs, nil
410}
411
412// TeamMembersToAdd returns a slice of UserTeamIDPair that need newly created memberships
413// based on the groups configurations. The returned list can be optionally scoped to a single given team.
414//
415// Typically since will be the last successful group sync time.
416// If includeRemovedMembers is true, then team members who left or were removed from the team will
417// be included; otherwise, they will be excluded.
418func (a *App) TeamMembersToAdd(since int64, teamID *string, includeRemovedMembers bool) ([]*model.UserTeamIDPair, *model.AppError) {
419	userTeams, err := a.Srv().Store.Group().TeamMembersToAdd(since, teamID, includeRemovedMembers)
420	if err != nil {
421		return nil, model.NewAppError("TeamMembersToAdd", "app.select_error", nil, err.Error(), http.StatusInternalServerError)
422	}
423
424	return userTeams, nil
425}
426
427// ChannelMembersToAdd returns a slice of UserChannelIDPair that need newly created memberships
428// based on the groups configurations. The returned list can be optionally scoped to a single given channel.
429//
430// Typically since will be the last successful group sync time.
431// If includeRemovedMembers is true, then channel members who left or were removed from the channel will
432// be included; otherwise, they will be excluded.
433func (a *App) ChannelMembersToAdd(since int64, channelID *string, includeRemovedMembers bool) ([]*model.UserChannelIDPair, *model.AppError) {
434	userChannels, err := a.Srv().Store.Group().ChannelMembersToAdd(since, channelID, includeRemovedMembers)
435	if err != nil {
436		return nil, model.NewAppError("ChannelMembersToAdd", "app.select_error", nil, err.Error(), http.StatusInternalServerError)
437	}
438
439	return userChannels, nil
440}
441
442func (a *App) TeamMembersToRemove(teamID *string) ([]*model.TeamMember, *model.AppError) {
443	teamMembers, err := a.Srv().Store.Group().TeamMembersToRemove(teamID)
444	if err != nil {
445		return nil, model.NewAppError("TeamMembersToRemove", "app.select_error", nil, err.Error(), http.StatusInternalServerError)
446	}
447
448	return teamMembers, nil
449}
450
451func (a *App) ChannelMembersToRemove(teamID *string) ([]*model.ChannelMember, *model.AppError) {
452	channelMembers, err := a.Srv().Store.Group().ChannelMembersToRemove(teamID)
453	if err != nil {
454		return nil, model.NewAppError("ChannelMembersToRemove", "app.select_error", nil, err.Error(), http.StatusInternalServerError)
455	}
456
457	return channelMembers, nil
458}
459
460func (a *App) GetGroupsByChannel(channelID string, opts model.GroupSearchOpts) ([]*model.GroupWithSchemeAdmin, int, *model.AppError) {
461	groups, err := a.Srv().Store.Group().GetGroupsByChannel(channelID, opts)
462	if err != nil {
463		return nil, 0, model.NewAppError("GetGroupsByChannel", "app.select_error", nil, err.Error(), http.StatusInternalServerError)
464	}
465
466	count, err := a.Srv().Store.Group().CountGroupsByChannel(channelID, opts)
467	if err != nil {
468		return nil, 0, model.NewAppError("GetGroupsByChannel", "app.select_error", nil, err.Error(), http.StatusInternalServerError)
469	}
470
471	return groups, int(count), nil
472}
473
474// GetGroupsByTeam returns the paged list and the total count of group associated to the given team.
475func (a *App) GetGroupsByTeam(teamID string, opts model.GroupSearchOpts) ([]*model.GroupWithSchemeAdmin, int, *model.AppError) {
476	groups, err := a.Srv().Store.Group().GetGroupsByTeam(teamID, opts)
477	if err != nil {
478		return nil, 0, model.NewAppError("GetGroupsByTeam", "app.select_error", nil, err.Error(), http.StatusInternalServerError)
479	}
480
481	count, err := a.Srv().Store.Group().CountGroupsByTeam(teamID, opts)
482	if err != nil {
483		return nil, 0, model.NewAppError("GetGroupsByTeam", "app.select_error", nil, err.Error(), http.StatusInternalServerError)
484	}
485
486	return groups, int(count), nil
487}
488
489func (a *App) GetGroupsAssociatedToChannelsByTeam(teamID string, opts model.GroupSearchOpts) (map[string][]*model.GroupWithSchemeAdmin, *model.AppError) {
490	groupsAssociatedByChannelId, err := a.Srv().Store.Group().GetGroupsAssociatedToChannelsByTeam(teamID, opts)
491	if err != nil {
492		return nil, model.NewAppError("GetGroupsAssociatedToChannelsByTeam", "app.select_error", nil, err.Error(), http.StatusInternalServerError)
493	}
494
495	return groupsAssociatedByChannelId, nil
496}
497
498func (a *App) GetGroups(page, perPage int, opts model.GroupSearchOpts) ([]*model.Group, *model.AppError) {
499	groups, err := a.Srv().Store.Group().GetGroups(page, perPage, opts)
500	if err != nil {
501		return nil, model.NewAppError("GetGroups", "app.select_error", nil, err.Error(), http.StatusInternalServerError)
502	}
503
504	return groups, nil
505}
506
507// TeamMembersMinusGroupMembers returns the set of users on the given team minus the set of users in the given
508// groups.
509//
510// The result can be used, for example, to determine the set of users who would be removed from a team if the team
511// were group-constrained with the given groups.
512func (a *App) TeamMembersMinusGroupMembers(teamID string, groupIDs []string, page, perPage int) ([]*model.UserWithGroups, int64, *model.AppError) {
513	users, err := a.Srv().Store.Group().TeamMembersMinusGroupMembers(teamID, groupIDs, page, perPage)
514	if err != nil {
515		return nil, 0, model.NewAppError("TeamMembersMinusGroupMembers", "app.select_error", nil, err.Error(), http.StatusInternalServerError)
516	}
517
518	// parse all group ids of all users
519	allUsersGroupIDMap := map[string]bool{}
520	for _, user := range users {
521		for _, groupID := range user.GetGroupIDs() {
522			allUsersGroupIDMap[groupID] = true
523		}
524	}
525
526	// create a slice of distinct group ids
527	var allUsersGroupIDSlice []string
528	for key := range allUsersGroupIDMap {
529		allUsersGroupIDSlice = append(allUsersGroupIDSlice, key)
530	}
531
532	// retrieve groups from DB
533	groups, appErr := a.GetGroupsByIDs(allUsersGroupIDSlice)
534	if appErr != nil {
535		return nil, 0, appErr
536	}
537
538	// map groups by id
539	groupMap := map[string]*model.Group{}
540	for _, group := range groups {
541		groupMap[group.Id] = group
542	}
543
544	// populate each instance's groups field
545	for _, user := range users {
546		user.Groups = []*model.Group{}
547		for _, groupID := range user.GetGroupIDs() {
548			group, ok := groupMap[groupID]
549			if ok {
550				user.Groups = append(user.Groups, group)
551			}
552		}
553	}
554
555	totalCount, err := a.Srv().Store.Group().CountTeamMembersMinusGroupMembers(teamID, groupIDs)
556	if err != nil {
557		return nil, 0, model.NewAppError("TeamMembersMinusGroupMembers", "app.select_error", nil, err.Error(), http.StatusInternalServerError)
558	}
559	return users, totalCount, nil
560}
561
562func (a *App) GetGroupsByIDs(groupIDs []string) ([]*model.Group, *model.AppError) {
563	groups, err := a.Srv().Store.Group().GetByIDs(groupIDs)
564	if err != nil {
565		return nil, model.NewAppError("GetGroupsByIDs", "app.select_error", nil, err.Error(), http.StatusInternalServerError)
566	}
567
568	return groups, nil
569}
570
571// ChannelMembersMinusGroupMembers returns the set of users in the given channel minus the set of users in the given
572// groups.
573//
574// The result can be used, for example, to determine the set of users who would be removed from a channel if the
575// channel were group-constrained with the given groups.
576func (a *App) ChannelMembersMinusGroupMembers(channelID string, groupIDs []string, page, perPage int) ([]*model.UserWithGroups, int64, *model.AppError) {
577	users, err := a.Srv().Store.Group().ChannelMembersMinusGroupMembers(channelID, groupIDs, page, perPage)
578	if err != nil {
579		return nil, 0, model.NewAppError("ChannelMembersMinusGroupMembers", "app.select_error", nil, err.Error(), http.StatusInternalServerError)
580	}
581
582	// parse all group ids of all users
583	allUsersGroupIDMap := map[string]bool{}
584	for _, user := range users {
585		for _, groupID := range user.GetGroupIDs() {
586			allUsersGroupIDMap[groupID] = true
587		}
588	}
589
590	// create a slice of distinct group ids
591	var allUsersGroupIDSlice []string
592	for key := range allUsersGroupIDMap {
593		allUsersGroupIDSlice = append(allUsersGroupIDSlice, key)
594	}
595
596	// retrieve groups from DB
597	groups, appErr := a.GetGroupsByIDs(allUsersGroupIDSlice)
598	if appErr != nil {
599		return nil, 0, appErr
600	}
601
602	// map groups by id
603	groupMap := map[string]*model.Group{}
604	for _, group := range groups {
605		groupMap[group.Id] = group
606	}
607
608	// populate each instance's groups field
609	for _, user := range users {
610		user.Groups = []*model.Group{}
611		for _, groupID := range user.GetGroupIDs() {
612			group, ok := groupMap[groupID]
613			if ok {
614				user.Groups = append(user.Groups, group)
615			}
616		}
617	}
618
619	totalCount, err := a.Srv().Store.Group().CountChannelMembersMinusGroupMembers(channelID, groupIDs)
620	if err != nil {
621		return nil, 0, model.NewAppError("ChannelMembersMinusGroupMembers", "app.select_error", nil, err.Error(), http.StatusInternalServerError)
622	}
623	return users, totalCount, nil
624}
625
626// UserIsInAdminRoleGroup returns true at least one of the user's groups are configured to set the members as
627// admins in the given syncable.
628func (a *App) UserIsInAdminRoleGroup(userID, syncableID string, syncableType model.GroupSyncableType) (bool, *model.AppError) {
629	groupIDs, err := a.Srv().Store.Group().AdminRoleGroupsForSyncableMember(userID, syncableID, syncableType)
630	if err != nil {
631		return false, model.NewAppError("UserIsInAdminRoleGroup", "app.select_error", nil, err.Error(), http.StatusInternalServerError)
632	}
633
634	if len(groupIDs) == 0 {
635		return false, nil
636	}
637
638	return true, nil
639}
640