Files
mattermost/store/sqlstore/group_store.go
2020-06-04 11:31:07 -04:00

1515 lines
56 KiB
Go

// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
package sqlstore
import (
"database/sql"
"fmt"
"net/http"
"strings"
sq "github.com/Masterminds/squirrel"
"github.com/pkg/errors"
"github.com/mattermost/mattermost-server/v5/model"
"github.com/mattermost/mattermost-server/v5/store"
)
type selectType int
const (
selectGroups selectType = iota
selectCountGroups
)
type groupTeam struct {
model.GroupSyncable
TeamId string `db:"TeamId"`
}
type groupChannel struct {
model.GroupSyncable
ChannelId string `db:"ChannelId"`
}
type groupTeamJoin struct {
groupTeam
TeamDisplayName string `db:"TeamDisplayName"`
TeamType string `db:"TeamType"`
}
type groupChannelJoin struct {
groupChannel
ChannelDisplayName string `db:"ChannelDisplayName"`
TeamDisplayName string `db:"TeamDisplayName"`
TeamType string `db:"TeamType"`
ChannelType string `db:"ChannelType"`
TeamID string `db:"TeamId"`
}
type SqlGroupStore struct {
SqlStore
}
func newSqlGroupStore(sqlStore SqlStore) store.GroupStore {
s := &SqlGroupStore{SqlStore: sqlStore}
for _, db := range sqlStore.GetAllConns() {
groups := db.AddTableWithName(model.Group{}, "UserGroups").SetKeys(false, "Id")
groups.ColMap("Id").SetMaxSize(26)
groups.ColMap("Name").SetMaxSize(model.GroupNameMaxLength).SetUnique(true)
groups.ColMap("DisplayName").SetMaxSize(model.GroupDisplayNameMaxLength)
groups.ColMap("Description").SetMaxSize(model.GroupDescriptionMaxLength)
groups.ColMap("Source").SetMaxSize(model.GroupSourceMaxLength)
groups.ColMap("RemoteId").SetMaxSize(model.GroupRemoteIDMaxLength)
groups.SetUniqueTogether("Source", "RemoteId")
groupMembers := db.AddTableWithName(model.GroupMember{}, "GroupMembers").SetKeys(false, "GroupId", "UserId")
groupMembers.ColMap("GroupId").SetMaxSize(26)
groupMembers.ColMap("UserId").SetMaxSize(26)
groupTeams := db.AddTableWithName(groupTeam{}, "GroupTeams").SetKeys(false, "GroupId", "TeamId")
groupTeams.ColMap("GroupId").SetMaxSize(26)
groupTeams.ColMap("TeamId").SetMaxSize(26)
groupChannels := db.AddTableWithName(groupChannel{}, "GroupChannels").SetKeys(false, "GroupId", "ChannelId")
groupChannels.ColMap("GroupId").SetMaxSize(26)
groupChannels.ColMap("ChannelId").SetMaxSize(26)
}
return s
}
func (s *SqlGroupStore) createIndexesIfNotExists() {
s.CreateIndexIfNotExists("idx_groupmembers_create_at", "GroupMembers", "CreateAt")
s.CreateIndexIfNotExists("idx_usergroups_remote_id", "UserGroups", "RemoteId")
s.CreateIndexIfNotExists("idx_usergroups_delete_at", "UserGroups", "DeleteAt")
s.CreateIndexIfNotExists("idx_groupteams_teamid", "GroupTeams", "TeamId")
s.CreateIndexIfNotExists("idx_groupchannels_channelid", "GroupChannels", "ChannelId")
s.CreateColumnIfNotExistsNoDefault("Channels", "GroupConstrained", "tinyint(1)", "boolean")
s.CreateColumnIfNotExistsNoDefault("Teams", "GroupConstrained", "tinyint(1)", "boolean")
s.CreateIndexIfNotExists("idx_groupteams_schemeadmin", "GroupTeams", "SchemeAdmin")
s.CreateIndexIfNotExists("idx_groupchannels_schemeadmin", "GroupChannels", "SchemeAdmin")
}
func (s *SqlGroupStore) Create(group *model.Group) (*model.Group, *model.AppError) {
if len(group.Id) != 0 {
return nil, model.NewAppError("SqlGroupStore.GroupCreate", "model.group.id.app_error", nil, "", http.StatusBadRequest)
}
if err := group.IsValidForCreate(); err != nil {
return nil, err
}
group.Id = model.NewId()
group.CreateAt = model.GetMillis()
group.UpdateAt = group.CreateAt
if err := s.GetMaster().Insert(group); err != nil {
if IsUniqueConstraintError(err, []string{"Name", "groups_name_key"}) {
return nil, model.NewAppError("SqlGroupStore.GroupCreate", "store.sql_group.unique_constraint", nil, err.Error(), http.StatusInternalServerError)
}
return nil, model.NewAppError("SqlGroupStore.GroupCreate", "store.insert_error", nil, err.Error(), http.StatusInternalServerError)
}
return group, nil
}
func (s *SqlGroupStore) Get(groupId string) (*model.Group, *model.AppError) {
var group *model.Group
if err := s.GetReplica().SelectOne(&group, "SELECT * from UserGroups WHERE Id = :Id", map[string]interface{}{"Id": groupId}); err != nil {
if err == sql.ErrNoRows {
return nil, model.NewAppError("SqlGroupStore.GroupGet", "store.sql_group.no_rows", nil, err.Error(), http.StatusNotFound)
}
return nil, model.NewAppError("SqlGroupStore.GroupGet", "store.select_error", nil, err.Error(), http.StatusInternalServerError)
}
return group, nil
}
func (s *SqlGroupStore) GetByName(name string, opts model.GroupSearchOpts) (*model.Group, *model.AppError) {
var group *model.Group
query := s.getQueryBuilder().Select("*").From("UserGroups").Where(sq.Eq{"Name": name})
if opts.FilterAllowReference {
query = query.Where("AllowReference = true")
}
queryString, args, err := query.ToSql()
if err != nil {
return nil, model.NewAppError("SqlGroupStore.GetByName", "store.sql_group.app_error", nil, err.Error(), http.StatusInternalServerError)
}
if err := s.GetReplica().SelectOne(&group, queryString, args...); err != nil {
if err == sql.ErrNoRows {
return nil, model.NewAppError("SqlGroupStore.GroupGetByName", "store.sql_group.no_rows", nil, err.Error(), http.StatusNotFound)
}
return nil, model.NewAppError("SqlGroupStore.GroupGetByName", "store.select_error", nil, err.Error(), http.StatusInternalServerError)
}
return group, nil
}
func (s *SqlGroupStore) GetByIDs(groupIDs []string) ([]*model.Group, *model.AppError) {
var groups []*model.Group
query := s.getQueryBuilder().Select("*").From("UserGroups").Where(sq.Eq{"Id": groupIDs})
queryString, args, err := query.ToSql()
if err != nil {
return nil, model.NewAppError("SqlGroupStore.GetByIDs", "store.sql_group.app_error", nil, err.Error(), http.StatusInternalServerError)
}
if _, err := s.GetReplica().Select(&groups, queryString, args...); err != nil {
return nil, model.NewAppError("SqlGroupStore.GetByIDs", "store.select_error", nil, err.Error(), http.StatusInternalServerError)
}
return groups, nil
}
func (s *SqlGroupStore) GetByRemoteID(remoteID string, groupSource model.GroupSource) (*model.Group, *model.AppError) {
var group *model.Group
if err := s.GetReplica().SelectOne(&group, "SELECT * from UserGroups WHERE RemoteId = :RemoteId AND Source = :Source", map[string]interface{}{"RemoteId": remoteID, "Source": groupSource}); err != nil {
if err == sql.ErrNoRows {
return nil, model.NewAppError("SqlGroupStore.GroupGetByRemoteID", "store.sql_group.no_rows", nil, err.Error(), http.StatusNotFound)
}
return nil, model.NewAppError("SqlGroupStore.GroupGetByRemoteID", "store.select_error", nil, err.Error(), http.StatusInternalServerError)
}
return group, nil
}
func (s *SqlGroupStore) GetAllBySource(groupSource model.GroupSource) ([]*model.Group, *model.AppError) {
var groups []*model.Group
if _, err := s.GetReplica().Select(&groups, "SELECT * from UserGroups WHERE DeleteAt = 0 AND Source = :Source", map[string]interface{}{"Source": groupSource}); err != nil {
return nil, model.NewAppError("SqlGroupStore.GroupGetAllBySource", "store.select_error", nil, err.Error(), http.StatusInternalServerError)
}
return groups, nil
}
func (s *SqlGroupStore) GetByUser(userId string) ([]*model.Group, *model.AppError) {
var groups []*model.Group
query := `
SELECT
UserGroups.*
FROM
GroupMembers
JOIN UserGroups ON UserGroups.Id = GroupMembers.GroupId
WHERE
GroupMembers.DeleteAt = 0
AND UserId = :UserId`
if _, err := s.GetReplica().Select(&groups, query, map[string]interface{}{"UserId": userId}); err != nil {
return nil, model.NewAppError("SqlGroupStore.GetByUser", "store.select_error", nil, err.Error(), http.StatusInternalServerError)
}
return groups, nil
}
func (s *SqlGroupStore) Update(group *model.Group) (*model.Group, *model.AppError) {
var retrievedGroup *model.Group
if err := s.GetReplica().SelectOne(&retrievedGroup, "SELECT * FROM UserGroups WHERE Id = :Id", map[string]interface{}{"Id": group.Id}); err != nil {
if err == sql.ErrNoRows {
return nil, model.NewAppError("SqlGroupStore.GroupUpdate", "store.sql_group.no_rows", nil, "id="+group.Id+","+err.Error(), http.StatusNotFound)
}
return nil, model.NewAppError("SqlGroupStore.GroupUpdate", "store.select_error", nil, "id="+group.Id+","+err.Error(), http.StatusInternalServerError)
}
// If updating DeleteAt it can only be to 0
if group.DeleteAt != retrievedGroup.DeleteAt && group.DeleteAt != 0 {
return nil, model.NewAppError("SqlGroupStore.GroupUpdate", "model.group.delete_at.app_error", nil, "", http.StatusInternalServerError)
}
// Reset these properties, don't update them based on input
group.CreateAt = retrievedGroup.CreateAt
group.UpdateAt = model.GetMillis()
if err := group.IsValidForUpdate(); err != nil {
return nil, err
}
rowsChanged, err := s.GetMaster().Update(group)
if err != nil {
if IsUniqueConstraintError(err, []string{"Name", "groups_name_key"}) {
return nil, model.NewAppError("SqlGroupStore.GroupUpdate", "store.sql_group.unique_constraint", nil, err.Error(), http.StatusInternalServerError)
}
return nil, model.NewAppError("SqlGroupStore.GroupUpdate", "store.update_error", nil, err.Error(), http.StatusInternalServerError)
}
if rowsChanged != 1 {
return nil, model.NewAppError("SqlGroupStore.GroupUpdate", "store.sql_group.no_rows_changed", nil, "", http.StatusInternalServerError)
}
return group, nil
}
func (s *SqlGroupStore) Delete(groupID string) (*model.Group, *model.AppError) {
var group *model.Group
if err := s.GetReplica().SelectOne(&group, "SELECT * from UserGroups WHERE Id = :Id AND DeleteAt = 0", map[string]interface{}{"Id": groupID}); err != nil {
if err == sql.ErrNoRows {
return nil, model.NewAppError("SqlGroupStore.GroupDelete", "store.sql_group.no_rows", nil, "Id="+groupID+", "+err.Error(), http.StatusNotFound)
}
return nil, model.NewAppError("SqlGroupStore.GroupDelete", "store.select_error", nil, err.Error(), http.StatusInternalServerError)
}
time := model.GetMillis()
group.DeleteAt = time
group.UpdateAt = time
if _, err := s.GetMaster().Update(group); err != nil {
return nil, model.NewAppError("SqlGroupStore.GroupDelete", "store.update_error", nil, err.Error(), http.StatusInternalServerError)
}
return group, nil
}
func (s *SqlGroupStore) GetMemberUsers(groupID string) ([]*model.User, *model.AppError) {
var groupMembers []*model.User
query := `
SELECT
Users.*
FROM
GroupMembers
JOIN Users ON Users.Id = GroupMembers.UserId
WHERE
GroupMembers.DeleteAt = 0
AND Users.DeleteAt = 0
AND GroupId = :GroupId`
if _, err := s.GetReplica().Select(&groupMembers, query, map[string]interface{}{"GroupId": groupID}); err != nil {
return nil, model.NewAppError("SqlGroupStore.GetMemberUsers", "store.select_error", nil, err.Error(), http.StatusInternalServerError)
}
return groupMembers, nil
}
func (s *SqlGroupStore) GetMemberUsersPage(groupID string, page int, perPage int) ([]*model.User, *model.AppError) {
var groupMembers []*model.User
query := `
SELECT
Users.*
FROM
GroupMembers
JOIN Users ON Users.Id = GroupMembers.UserId
WHERE
GroupMembers.DeleteAt = 0
AND Users.DeleteAt = 0
AND GroupId = :GroupId
ORDER BY
GroupMembers.CreateAt DESC
LIMIT
:Limit
OFFSET
:Offset`
if _, err := s.GetReplica().Select(&groupMembers, query, map[string]interface{}{"GroupId": groupID, "Limit": perPage, "Offset": page * perPage}); err != nil {
return nil, model.NewAppError("SqlGroupStore.GroupGetMemberUsersPage", "store.select_error", nil, err.Error(), http.StatusInternalServerError)
}
return groupMembers, nil
}
func (s *SqlGroupStore) GetMemberCount(groupID string) (int64, *model.AppError) {
query := `
SELECT
count(*)
FROM
GroupMembers
JOIN Users ON Users.Id = GroupMembers.UserId
WHERE
GroupMembers.GroupId = :GroupId
AND Users.DeleteAt = 0`
count, err := s.GetReplica().SelectInt(query, map[string]interface{}{"GroupId": groupID})
if err != nil {
return int64(0), model.NewAppError("SqlGroupStore.GroupGetMemberUsersPage", "store.select_error", nil, err.Error(), http.StatusInternalServerError)
}
return count, nil
}
func (s *SqlGroupStore) GetMemberUsersInTeam(groupID string, teamID string) ([]*model.User, *model.AppError) {
var groupMembers []*model.User
query := `
SELECT
Users.*
FROM
GroupMembers
JOIN Users ON Users.Id = GroupMembers.UserId
WHERE
GroupId = :GroupId
AND GroupMembers.UserId IN (
SELECT TeamMembers.UserId
FROM TeamMembers
JOIN Teams ON Teams.Id = :TeamId
WHERE TeamMembers.TeamId = Teams.Id
AND TeamMembers.DeleteAt = 0
)
AND GroupMembers.DeleteAt = 0
AND Users.DeleteAt = 0
`
if _, err := s.GetReplica().Select(&groupMembers, query, map[string]interface{}{"GroupId": groupID, "TeamId": teamID}); err != nil {
return nil, model.NewAppError("SqlGroupStore.GetMemberUsersInTeam", "store.select_error", nil, err.Error(), http.StatusInternalServerError)
}
return groupMembers, nil
}
func (s *SqlGroupStore) GetMemberUsersNotInChannel(groupID string, channelID string) ([]*model.User, *model.AppError) {
var groupMembers []*model.User
query := `
SELECT
Users.*
FROM
GroupMembers
JOIN Users ON Users.Id = GroupMembers.UserId
WHERE
GroupId = :GroupId
AND GroupMembers.UserId NOT IN (
SELECT ChannelMembers.UserId
FROM ChannelMembers
WHERE ChannelMembers.ChannelId = :ChannelId
)
AND GroupMembers.UserId IN (
SELECT TeamMembers.UserId
FROM TeamMembers
JOIN Channels ON Channels.Id = :ChannelId
JOIN Teams ON Teams.Id = Channels.TeamId
WHERE TeamMembers.TeamId = Teams.Id
AND TeamMembers.DeleteAt = 0
)
AND GroupMembers.DeleteAt = 0
AND Users.DeleteAt = 0
`
if _, err := s.GetReplica().Select(&groupMembers, query, map[string]interface{}{"GroupId": groupID, "ChannelId": channelID}); err != nil {
return nil, model.NewAppError("SqlGroupStore.GetMemberUsersNotInChannel", "store.select_error", nil, err.Error(), http.StatusInternalServerError)
}
return groupMembers, nil
}
func (s *SqlGroupStore) UpsertMember(groupID string, userID string) (*model.GroupMember, *model.AppError) {
member := &model.GroupMember{
GroupId: groupID,
UserId: userID,
CreateAt: model.GetMillis(),
}
if err := member.IsValid(); err != nil {
return nil, err
}
var retrievedGroup *model.Group
if err := s.GetReplica().SelectOne(&retrievedGroup, "SELECT * FROM UserGroups WHERE Id = :Id", map[string]interface{}{"Id": groupID}); err != nil {
return nil, model.NewAppError("SqlGroupStore.GroupCreateOrRestoreMember", "store.insert_error", nil, "group_id="+member.GroupId+"user_id="+member.UserId+","+err.Error(), http.StatusInternalServerError)
}
var retrievedMember *model.GroupMember
if err := s.GetReplica().SelectOne(&retrievedMember, "SELECT * FROM GroupMembers WHERE GroupId = :GroupId AND UserId = :UserId", map[string]interface{}{"GroupId": member.GroupId, "UserId": member.UserId}); err != nil {
if err != sql.ErrNoRows {
return nil, model.NewAppError("SqlGroupStore.GroupCreateOrRestoreMember", "store.select_error", nil, "group_id="+member.GroupId+"user_id="+member.UserId+","+err.Error(), http.StatusInternalServerError)
}
}
if retrievedMember == nil {
if err := s.GetMaster().Insert(member); err != nil {
if IsUniqueConstraintError(err, []string{"GroupId", "UserId", "groupmembers_pkey", "PRIMARY"}) {
return nil, model.NewAppError("SqlGroupStore.GroupCreateOrRestoreMember", "store.sql_group.uniqueness_error", nil, "group_id="+member.GroupId+", user_id="+member.UserId+", "+err.Error(), http.StatusBadRequest)
}
return nil, model.NewAppError("SqlGroupStore.GroupCreateOrRestoreMember", "store.insert_error", nil, "group_id="+member.GroupId+", user_id="+member.UserId+", "+err.Error(), http.StatusInternalServerError)
}
} else {
member.DeleteAt = 0
var rowsChanged int64
var err error
if rowsChanged, err = s.GetMaster().Update(member); err != nil {
return nil, model.NewAppError("SqlGroupStore.GroupCreateOrRestoreMember", "store.update_error", nil, "group_id="+member.GroupId+", user_id="+member.UserId+", "+err.Error(), http.StatusInternalServerError)
}
if rowsChanged != 1 {
return nil, model.NewAppError("SqlGroupStore.GroupCreateOrRestoreMember", "store.sql_group.no_rows_changed", nil, "", http.StatusInternalServerError)
}
}
return member, nil
}
func (s *SqlGroupStore) DeleteMember(groupID string, userID string) (*model.GroupMember, *model.AppError) {
var retrievedMember *model.GroupMember
if err := s.GetReplica().SelectOne(&retrievedMember, "SELECT * FROM GroupMembers WHERE GroupId = :GroupId AND UserId = :UserId AND DeleteAt = 0", map[string]interface{}{"GroupId": groupID, "UserId": userID}); err != nil {
if err == sql.ErrNoRows {
return nil, model.NewAppError("SqlGroupStore.GroupDeleteMember", "store.sql_group.no_rows", nil, "group_id="+groupID+"user_id="+userID+","+err.Error(), http.StatusNotFound)
}
return nil, model.NewAppError("SqlGroupStore.GroupDeleteMember", "store.select_error", nil, "group_id="+groupID+"user_id="+userID+","+err.Error(), http.StatusInternalServerError)
}
retrievedMember.DeleteAt = model.GetMillis()
if _, err := s.GetMaster().Update(retrievedMember); err != nil {
return nil, model.NewAppError("SqlGroupStore.GroupDeleteMember", "store.update_error", nil, err.Error(), http.StatusInternalServerError)
}
return retrievedMember, nil
}
func (s *SqlGroupStore) PermanentDeleteMembersByUser(userId string) *model.AppError {
if _, err := s.GetMaster().Exec("DELETE FROM GroupMembers WHERE UserId = :UserId", map[string]interface{}{"UserId": userId}); err != nil {
return model.NewAppError("SqlGroupStore.GroupPermanentDeleteMembersByUser", "store.sql_group.permanent_delete_members_by_user.app_error", map[string]interface{}{"UserId": userId}, "", http.StatusInternalServerError)
}
return nil
}
func (s *SqlGroupStore) CreateGroupSyncable(groupSyncable *model.GroupSyncable) (*model.GroupSyncable, *model.AppError) {
if err := groupSyncable.IsValid(); err != nil {
return nil, err
}
// Reset values that shouldn't be updatable by parameter
groupSyncable.DeleteAt = 0
groupSyncable.CreateAt = model.GetMillis()
groupSyncable.UpdateAt = groupSyncable.CreateAt
var insertErr error
switch groupSyncable.Type {
case model.GroupSyncableTypeTeam:
if _, err := s.Team().Get(groupSyncable.SyncableId); err != nil {
return nil, err
}
insertErr = s.GetMaster().Insert(groupSyncableToGroupTeam(groupSyncable))
case model.GroupSyncableTypeChannel:
if _, err := s.Channel().Get(groupSyncable.SyncableId, false); err != nil {
var nfErr *store.ErrNotFound
switch {
case errors.As(err, &nfErr):
return nil, model.NewAppError("CreateGroupSyncable", "store.sql_channel.get.existing.app_error", nil, nfErr.Error(), http.StatusNotFound)
default:
return nil, model.NewAppError("CreateGroupSyncable", "store.sql_channel.get.find.app_error", nil, err.Error(), http.StatusInternalServerError)
}
}
insertErr = s.GetMaster().Insert(groupSyncableToGroupChannel(groupSyncable))
default:
return nil, model.NewAppError("SqlGroupStore.GroupCreateGroupSyncable", "model.group_syncable.type.app_error", nil, "group_id="+groupSyncable.GroupId+", syncable_id="+groupSyncable.SyncableId, http.StatusInternalServerError)
}
if insertErr != nil {
return nil, model.NewAppError("SqlGroupStore.GroupCreateGroupSyncable", "store.insert_error", nil, "group_id="+groupSyncable.GroupId+", syncable_id="+groupSyncable.SyncableId+", "+insertErr.Error(), http.StatusInternalServerError)
}
return groupSyncable, nil
}
func (s *SqlGroupStore) GetGroupSyncable(groupID string, syncableID string, syncableType model.GroupSyncableType) (*model.GroupSyncable, *model.AppError) {
groupSyncable, err := s.getGroupSyncable(groupID, syncableID, syncableType)
if err != nil {
if err == sql.ErrNoRows {
return nil, model.NewAppError("SqlGroupStore.GroupGetGroupSyncable", "store.sql_group.no_rows", nil, err.Error(), http.StatusNotFound)
}
return nil, model.NewAppError("SqlGroupStore.GroupGetGroupSyncable", "store.select_error", nil, err.Error(), http.StatusInternalServerError)
}
return groupSyncable, nil
}
func (s *SqlGroupStore) getGroupSyncable(groupID string, syncableID string, syncableType model.GroupSyncableType) (*model.GroupSyncable, error) {
var err error
var result interface{}
switch syncableType {
case model.GroupSyncableTypeTeam:
result, err = s.GetReplica().Get(groupTeam{}, groupID, syncableID)
case model.GroupSyncableTypeChannel:
result, err = s.GetReplica().Get(groupChannel{}, groupID, syncableID)
}
if err != nil {
return nil, err
}
if result == nil {
return nil, sql.ErrNoRows
}
groupSyncable := model.GroupSyncable{}
switch syncableType {
case model.GroupSyncableTypeTeam:
groupTeam := result.(*groupTeam)
groupSyncable.SyncableId = groupTeam.TeamId
groupSyncable.GroupId = groupTeam.GroupId
groupSyncable.AutoAdd = groupTeam.AutoAdd
groupSyncable.CreateAt = groupTeam.CreateAt
groupSyncable.DeleteAt = groupTeam.DeleteAt
groupSyncable.UpdateAt = groupTeam.UpdateAt
groupSyncable.Type = syncableType
case model.GroupSyncableTypeChannel:
groupChannel := result.(*groupChannel)
groupSyncable.SyncableId = groupChannel.ChannelId
groupSyncable.GroupId = groupChannel.GroupId
groupSyncable.AutoAdd = groupChannel.AutoAdd
groupSyncable.CreateAt = groupChannel.CreateAt
groupSyncable.DeleteAt = groupChannel.DeleteAt
groupSyncable.UpdateAt = groupChannel.UpdateAt
groupSyncable.Type = syncableType
default:
return nil, fmt.Errorf("unable to convert syncableType: %s", syncableType.String())
}
return &groupSyncable, nil
}
func (s *SqlGroupStore) GetAllGroupSyncablesByGroupId(groupID string, syncableType model.GroupSyncableType) ([]*model.GroupSyncable, *model.AppError) {
args := map[string]interface{}{"GroupId": groupID}
appErrF := func(msg string) *model.AppError {
return model.NewAppError("SqlGroupStore.GroupGetAllGroupSyncablesByGroup", "store.select_error", nil, msg, http.StatusInternalServerError)
}
groupSyncables := []*model.GroupSyncable{}
switch syncableType {
case model.GroupSyncableTypeTeam:
sqlQuery := `
SELECT
GroupTeams.*,
Teams.DisplayName AS TeamDisplayName,
Teams.Type AS TeamType
FROM
GroupTeams
JOIN Teams ON Teams.Id = GroupTeams.TeamId
WHERE
GroupId = :GroupId AND GroupTeams.DeleteAt = 0`
results := []*groupTeamJoin{}
_, err := s.GetReplica().Select(&results, sqlQuery, args)
if err != nil {
return nil, appErrF(err.Error())
}
for _, result := range results {
groupSyncable := &model.GroupSyncable{
SyncableId: result.TeamId,
GroupId: result.GroupId,
AutoAdd: result.AutoAdd,
CreateAt: result.CreateAt,
DeleteAt: result.DeleteAt,
UpdateAt: result.UpdateAt,
Type: syncableType,
TeamDisplayName: result.TeamDisplayName,
TeamType: result.TeamType,
SchemeAdmin: result.SchemeAdmin,
}
groupSyncables = append(groupSyncables, groupSyncable)
}
case model.GroupSyncableTypeChannel:
sqlQuery := `
SELECT
GroupChannels.*,
Channels.DisplayName AS ChannelDisplayName,
Teams.DisplayName AS TeamDisplayName,
Channels.Type As ChannelType,
Teams.Type As TeamType,
Teams.Id AS TeamId
FROM
GroupChannels
JOIN Channels ON Channels.Id = GroupChannels.ChannelId
JOIN Teams ON Teams.Id = Channels.TeamId
WHERE
GroupId = :GroupId AND GroupChannels.DeleteAt = 0`
results := []*groupChannelJoin{}
_, err := s.GetReplica().Select(&results, sqlQuery, args)
if err != nil {
return nil, appErrF(err.Error())
}
for _, result := range results {
groupSyncable := &model.GroupSyncable{
SyncableId: result.ChannelId,
GroupId: result.GroupId,
AutoAdd: result.AutoAdd,
CreateAt: result.CreateAt,
DeleteAt: result.DeleteAt,
UpdateAt: result.UpdateAt,
Type: syncableType,
ChannelDisplayName: result.ChannelDisplayName,
ChannelType: result.ChannelType,
TeamDisplayName: result.TeamDisplayName,
TeamType: result.TeamType,
TeamID: result.TeamID,
SchemeAdmin: result.SchemeAdmin,
}
groupSyncables = append(groupSyncables, groupSyncable)
}
}
return groupSyncables, nil
}
func (s *SqlGroupStore) UpdateGroupSyncable(groupSyncable *model.GroupSyncable) (*model.GroupSyncable, *model.AppError) {
retrievedGroupSyncable, err := s.getGroupSyncable(groupSyncable.GroupId, groupSyncable.SyncableId, groupSyncable.Type)
if err != nil {
if err == sql.ErrNoRows {
return nil, model.NewAppError("SqlGroupStore.GroupUpdateGroupSyncable", "store.sql_group.no_rows", nil, err.Error(), http.StatusInternalServerError)
}
return nil, model.NewAppError("SqlGroupStore.GroupUpdateGroupSyncable", "store.select_error", nil, "GroupId="+groupSyncable.GroupId+", SyncableId="+groupSyncable.SyncableId+", SyncableType="+groupSyncable.Type.String()+", "+err.Error(), http.StatusInternalServerError)
}
if err := groupSyncable.IsValid(); err != nil {
return nil, err
}
// If updating DeleteAt it can only be to 0
if groupSyncable.DeleteAt != retrievedGroupSyncable.DeleteAt && groupSyncable.DeleteAt != 0 {
return nil, model.NewAppError("SqlGroupStore.GroupUpdateGroupSyncable", "model.group.delete_at.app_error", nil, "", http.StatusInternalServerError)
}
// Reset these properties, don't update them based on input
groupSyncable.CreateAt = retrievedGroupSyncable.CreateAt
groupSyncable.UpdateAt = model.GetMillis()
switch groupSyncable.Type {
case model.GroupSyncableTypeTeam:
_, err = s.GetMaster().Update(groupSyncableToGroupTeam(groupSyncable))
case model.GroupSyncableTypeChannel:
_, err = s.GetMaster().Update(groupSyncableToGroupChannel(groupSyncable))
default:
return nil, model.NewAppError("SqlGroupStore.GroupUpdateGroupSyncable", "model.group_syncable.type.app_error", nil, "group_id="+groupSyncable.GroupId+", syncable_id="+groupSyncable.SyncableId, http.StatusInternalServerError)
}
if err != nil {
return nil, model.NewAppError("SqlGroupStore.GroupUpdateGroupSyncable", "store.update_error", nil, err.Error(), http.StatusInternalServerError)
}
return groupSyncable, nil
}
func (s *SqlGroupStore) DeleteGroupSyncable(groupID string, syncableID string, syncableType model.GroupSyncableType) (*model.GroupSyncable, *model.AppError) {
groupSyncable, err := s.getGroupSyncable(groupID, syncableID, syncableType)
if err != nil {
if err == sql.ErrNoRows {
return nil, model.NewAppError("SqlGroupStore.GroupDeleteGroupSyncable", "store.sql_group.no_rows", nil, "Id="+groupID+", "+err.Error(), http.StatusNotFound)
}
return nil, model.NewAppError("SqlGroupStore.GroupDeleteGroupSyncable", "store.select_error", nil, err.Error(), http.StatusInternalServerError)
}
if groupSyncable.DeleteAt != 0 {
return nil, model.NewAppError("SqlGroupStore.GroupDeleteGroupSyncable", "store.sql_group.group_syncable_already_deleted", nil, "group_id="+groupID+"syncable_id="+syncableID, http.StatusBadRequest)
}
time := model.GetMillis()
groupSyncable.DeleteAt = time
groupSyncable.UpdateAt = time
switch groupSyncable.Type {
case model.GroupSyncableTypeTeam:
_, err = s.GetMaster().Update(groupSyncableToGroupTeam(groupSyncable))
case model.GroupSyncableTypeChannel:
_, err = s.GetMaster().Update(groupSyncableToGroupChannel(groupSyncable))
default:
return nil, model.NewAppError("SqlGroupStore.GroupDeleteGroupSyncable", "model.group_syncable.type.app_error", nil, "group_id="+groupSyncable.GroupId+", syncable_id="+groupSyncable.SyncableId, http.StatusInternalServerError)
}
if err != nil {
return nil, model.NewAppError("SqlGroupStore.GroupDeleteGroupSyncable", "store.update_error", nil, err.Error(), http.StatusInternalServerError)
}
return groupSyncable, nil
}
func (s *SqlGroupStore) TeamMembersToAdd(since int64, teamID *string) ([]*model.UserTeamIDPair, *model.AppError) {
query := s.getQueryBuilder().Select("GroupMembers.UserId", "GroupTeams.TeamId").
From("GroupMembers").
Join("GroupTeams ON GroupTeams.GroupId = GroupMembers.GroupId").
Join("UserGroups ON UserGroups.Id = GroupMembers.GroupId").
Join("Teams ON Teams.Id = GroupTeams.TeamId").
JoinClause("LEFT OUTER JOIN TeamMembers ON TeamMembers.TeamId = GroupTeams.TeamId AND TeamMembers.UserId = GroupMembers.UserId").
Where(sq.Eq{
"TeamMembers.UserId": nil,
"UserGroups.DeleteAt": 0,
"GroupTeams.DeleteAt": 0,
"GroupTeams.AutoAdd": true,
"GroupMembers.DeleteAt": 0,
"Teams.DeleteAt": 0,
}).
Where("(GroupMembers.CreateAt >= ? OR GroupTeams.UpdateAt >= ?)", since, since)
if teamID != nil {
query = query.Where(sq.Eq{"Teams.Id": *teamID})
}
sql, params, err := query.ToSql()
if err != nil {
return nil, model.NewAppError("SqlGroupStore.TeamMembersToAdd", "store.sql_group.app_error", nil, err.Error(), http.StatusInternalServerError)
}
var teamMembers []*model.UserTeamIDPair
_, err = s.GetReplica().Select(&teamMembers, sql, params...)
if err != nil {
return nil, model.NewAppError("SqlGroupStore.TeamMembersToAdd", "store.select_error", nil, err.Error(), http.StatusInternalServerError)
}
return teamMembers, nil
}
func (s *SqlGroupStore) ChannelMembersToAdd(since int64, channelID *string) ([]*model.UserChannelIDPair, *model.AppError) {
query := s.getQueryBuilder().Select("GroupMembers.UserId", "GroupChannels.ChannelId").
From("GroupMembers").
Join("GroupChannels ON GroupChannels.GroupId = GroupMembers.GroupId").
Join("UserGroups ON UserGroups.Id = GroupMembers.GroupId").
Join("Channels ON Channels.Id = GroupChannels.ChannelId").
JoinClause("LEFT OUTER JOIN ChannelMemberHistory ON ChannelMemberHistory.ChannelId = GroupChannels.ChannelId AND ChannelMemberHistory.UserId = GroupMembers.UserId").
Where(sq.Eq{
"ChannelMemberHistory.UserId": nil,
"ChannelMemberHistory.LeaveTime": nil,
"UserGroups.DeleteAt": 0,
"GroupChannels.DeleteAt": 0,
"GroupChannels.AutoAdd": true,
"GroupMembers.DeleteAt": 0,
"Channels.DeleteAt": 0,
}).
Where("(GroupMembers.CreateAt >= ? OR GroupChannels.UpdateAt >= ?)", since, since)
if channelID != nil {
query = query.Where(sq.Eq{"Channels.Id": *channelID})
}
sql, params, err := query.ToSql()
if err != nil {
return nil, model.NewAppError("SqlGroupStore.ChannelMembersToAdd", "store.sql_group.app_error", nil, err.Error(), http.StatusInternalServerError)
}
var channelMembers []*model.UserChannelIDPair
_, err = s.GetReplica().Select(&channelMembers, sql, params...)
if err != nil {
return nil, model.NewAppError("SqlGroupStore.ChannelMembersToAdd", "store.select_error", nil, "", http.StatusInternalServerError)
}
return channelMembers, nil
}
func groupSyncableToGroupTeam(groupSyncable *model.GroupSyncable) *groupTeam {
return &groupTeam{
GroupSyncable: *groupSyncable,
TeamId: groupSyncable.SyncableId,
}
}
func groupSyncableToGroupChannel(groupSyncable *model.GroupSyncable) *groupChannel {
return &groupChannel{
GroupSyncable: *groupSyncable,
ChannelId: groupSyncable.SyncableId,
}
}
func (s *SqlGroupStore) TeamMembersToRemove(teamID *string) ([]*model.TeamMember, *model.AppError) {
whereStmt := `
(TeamMembers.TeamId,
TeamMembers.UserId)
NOT IN (
SELECT
Teams.Id AS TeamId,
GroupMembers.UserId
FROM
Teams
JOIN GroupTeams ON GroupTeams.TeamId = Teams.Id
JOIN UserGroups ON UserGroups.Id = GroupTeams.GroupId
JOIN GroupMembers ON GroupMembers.GroupId = UserGroups.Id
WHERE
Teams.GroupConstrained = TRUE
AND GroupTeams.DeleteAt = 0
AND UserGroups.DeleteAt = 0
AND Teams.DeleteAt = 0
AND GroupMembers.DeleteAt = 0
GROUP BY
Teams.Id,
GroupMembers.UserId)`
query := s.getQueryBuilder().Select(
"TeamMembers.TeamId",
"TeamMembers.UserId",
"TeamMembers.Roles",
"TeamMembers.DeleteAt",
"TeamMembers.SchemeUser",
"TeamMembers.SchemeAdmin",
"(TeamMembers.SchemeGuest IS NOT NULL AND TeamMembers.SchemeGuest) AS SchemeGuest",
).
From("TeamMembers").
Join("Teams ON Teams.Id = TeamMembers.TeamId").
LeftJoin("Bots ON Bots.UserId = TeamMembers.UserId").
Where(sq.Eq{"TeamMembers.DeleteAt": 0, "Teams.DeleteAt": 0, "Teams.GroupConstrained": true, "Bots.UserId": nil}).
Where(whereStmt)
if teamID != nil {
query = query.Where(sq.Eq{"TeamMembers.TeamId": *teamID})
}
sql, params, err := query.ToSql()
if err != nil {
return nil, model.NewAppError("SqlGroupStore.TeamMembersToRemove", "store.sql_group.app_error", nil, err.Error(), http.StatusInternalServerError)
}
var teamMembers []*model.TeamMember
_, err = s.GetReplica().Select(&teamMembers, sql, params...)
if err != nil {
return nil, model.NewAppError("SqlGroupStore.TeamMembersToRemove", "store.select_error", nil, "", http.StatusInternalServerError)
}
return teamMembers, nil
}
func (s *SqlGroupStore) CountGroupsByChannel(channelId string, opts model.GroupSearchOpts) (int64, *model.AppError) {
countQuery := s.groupsBySyncableBaseQuery(model.GroupSyncableTypeChannel, selectCountGroups, channelId, opts)
countQueryString, args, err := countQuery.ToSql()
if err != nil {
return int64(0), model.NewAppError("SqlGroupStore.CountGroupsByChannel", "store.sql_group.app_error", nil, err.Error(), http.StatusInternalServerError)
}
count, err := s.GetReplica().SelectInt(countQueryString, args...)
if err != nil {
return int64(0), model.NewAppError("SqlGroupStore.CountGroupsByChannel", "store.select_error", nil, err.Error(), http.StatusInternalServerError)
}
return count, nil
}
func (s *SqlGroupStore) GetGroupsByChannel(channelId string, opts model.GroupSearchOpts) ([]*model.GroupWithSchemeAdmin, *model.AppError) {
query := s.groupsBySyncableBaseQuery(model.GroupSyncableTypeChannel, selectGroups, channelId, opts)
if opts.PageOpts != nil {
offset := uint64(opts.PageOpts.Page * opts.PageOpts.PerPage)
query = query.OrderBy("ug.DisplayName").Limit(uint64(opts.PageOpts.PerPage)).Offset(offset)
}
queryString, args, err := query.ToSql()
if err != nil {
return nil, model.NewAppError("SqlGroupStore.GetGroupsByChannel", "store.sql_group.app_error", nil, err.Error(), http.StatusInternalServerError)
}
var groups []*model.GroupWithSchemeAdmin
_, err = s.GetReplica().Select(&groups, queryString, args...)
if err != nil {
return nil, model.NewAppError("SqlGroupStore.GetGroupsByChannel", "store.select_error", nil, err.Error(), http.StatusInternalServerError)
}
return groups, nil
}
func (s *SqlGroupStore) ChannelMembersToRemove(channelID *string) ([]*model.ChannelMember, *model.AppError) {
whereStmt := `
(ChannelMembers.ChannelId,
ChannelMembers.UserId)
NOT IN (
SELECT
Channels.Id AS ChannelId,
GroupMembers.UserId
FROM
Channels
JOIN GroupChannels ON GroupChannels.ChannelId = Channels.Id
JOIN UserGroups ON UserGroups.Id = GroupChannels.GroupId
JOIN GroupMembers ON GroupMembers.GroupId = UserGroups.Id
WHERE
Channels.GroupConstrained = TRUE
AND GroupChannels.DeleteAt = 0
AND UserGroups.DeleteAt = 0
AND Channels.DeleteAt = 0
AND GroupMembers.DeleteAt = 0
GROUP BY
Channels.Id,
GroupMembers.UserId)`
query := s.getQueryBuilder().Select(
"ChannelMembers.ChannelId",
"ChannelMembers.UserId",
"ChannelMembers.LastViewedAt",
"ChannelMembers.MsgCount",
"ChannelMembers.MentionCount",
"ChannelMembers.NotifyProps",
"ChannelMembers.LastUpdateAt",
"ChannelMembers.LastUpdateAt",
"ChannelMembers.SchemeUser",
"ChannelMembers.SchemeAdmin",
"(ChannelMembers.SchemeGuest IS NOT NULL AND ChannelMembers.SchemeGuest) AS SchemeGuest",
).
From("ChannelMembers").
Join("Channels ON Channels.Id = ChannelMembers.ChannelId").
LeftJoin("Bots ON Bots.UserId = ChannelMembers.UserId").
Where(sq.Eq{"Channels.DeleteAt": 0, "Channels.GroupConstrained": true, "Bots.UserId": nil}).
Where(whereStmt)
if channelID != nil {
query = query.Where(sq.Eq{"ChannelMembers.ChannelId": *channelID})
}
sql, params, err := query.ToSql()
if err != nil {
return nil, model.NewAppError("SqlGroupStore.ChannelMembersToRemove", "store.sql_group.app_error", nil, err.Error(), http.StatusInternalServerError)
}
var channelMembers []*model.ChannelMember
_, err = s.GetReplica().Select(&channelMembers, sql, params...)
if err != nil {
return nil, model.NewAppError("SqlGroupStore.ChannelMembersToRemove", "store.select_error", nil, err.Error(), http.StatusInternalServerError)
}
return channelMembers, nil
}
func (s *SqlGroupStore) groupsBySyncableBaseQuery(st model.GroupSyncableType, t selectType, syncableID string, opts model.GroupSearchOpts) sq.SelectBuilder {
selectStrs := map[selectType]string{
selectGroups: "ug.*, gs.SchemeAdmin AS SyncableSchemeAdmin",
selectCountGroups: "COUNT(*)",
}
var table string
var idCol string
if st == model.GroupSyncableTypeTeam {
table = "GroupTeams"
idCol = "TeamId"
} else {
table = "GroupChannels"
idCol = "ChannelId"
}
query := s.getQueryBuilder().
Select(selectStrs[t]).
From(fmt.Sprintf("%s gs", table)).
LeftJoin("UserGroups ug ON gs.GroupId = ug.Id").
Where(fmt.Sprintf("ug.DeleteAt = 0 AND gs.%s = ? AND gs.DeleteAt = 0", idCol), syncableID)
if opts.IncludeMemberCount && t == selectGroups {
query = s.getQueryBuilder().
Select(fmt.Sprintf("ug.*, coalesce(Members.MemberCount, 0) AS MemberCount, Group%ss.SchemeAdmin AS SyncableSchemeAdmin", st)).
From("UserGroups ug").
LeftJoin("(SELECT GroupMembers.GroupId, COUNT(*) AS MemberCount FROM GroupMembers LEFT JOIN Users ON Users.Id = GroupMembers.UserId WHERE GroupMembers.DeleteAt = 0 AND Users.DeleteAt = 0 GROUP BY GroupId) AS Members ON Members.GroupId = ug.Id").
LeftJoin(fmt.Sprintf("%[1]s ON %[1]s.GroupId = ug.Id", table)).
Where(fmt.Sprintf("ug.DeleteAt = 0 AND %[1]s.DeleteAt = 0 AND %[1]s.%[2]s = ?", table, idCol), syncableID).
OrderBy("ug.DisplayName")
}
if opts.FilterAllowReference && t == selectGroups {
query = query.Where("ug.AllowReference = true")
}
if len(opts.Q) > 0 {
pattern := fmt.Sprintf("%%%s%%", sanitizeSearchTerm(opts.Q, "\\"))
operatorKeyword := "ILIKE"
if s.DriverName() == model.DATABASE_DRIVER_MYSQL {
operatorKeyword = "LIKE"
}
query = query.Where(fmt.Sprintf("(ug.Name %[1]s ? OR ug.DisplayName %[1]s ?)", operatorKeyword), pattern, pattern)
}
return query
}
func (s *SqlGroupStore) getGroupsAssociatedToChannelsByTeam(st model.GroupSyncableType, teamID string, opts model.GroupSearchOpts) sq.SelectBuilder {
query := s.getQueryBuilder().
Select("gc.ChannelId, ug.*, gc.SchemeAdmin AS SyncableSchemeAdmin").
From("UserGroups ug").
LeftJoin(`
(SELECT
GroupChannels.GroupId, GroupChannels.ChannelId, GroupChannels.DeleteAt, GroupChannels.SchemeAdmin
FROM
GroupChannels
LEFT JOIN
Channels ON (Channels.Id = GroupChannels.ChannelId)
WHERE
GroupChannels.DeleteAt = 0
AND Channels.DeleteAt = 0
AND Channels.TeamId = ?) AS gc ON gc.GroupId = ug.Id`, teamID).
Where("ug.DeleteAt = 0 AND gc.DeleteAt = 0").
OrderBy("ug.DisplayName")
if opts.IncludeMemberCount {
query = s.getQueryBuilder().
Select("gc.ChannelId, ug.*, coalesce(Members.MemberCount, 0) AS MemberCount, gc.SchemeAdmin AS SyncableSchemeAdmin").
From("UserGroups ug").
LeftJoin(`
(SELECT
GroupChannels.ChannelId, GroupChannels.DeleteAt, GroupChannels.GroupId, GroupChannels.SchemeAdmin
FROM
GroupChannels
LEFT JOIN
Channels ON (Channels.Id = GroupChannels.ChannelId)
WHERE
GroupChannels.DeleteAt = 0
AND Channels.DeleteAt = 0
AND Channels.TeamId = ?) AS gc ON gc.GroupId = ug.Id`, teamID).
LeftJoin(`(
SELECT
GroupMembers.GroupId, COUNT(*) AS MemberCount
FROM
GroupMembers
LEFT JOIN
Users ON Users.Id = GroupMembers.UserId
WHERE
GroupMembers.DeleteAt = 0
AND Users.DeleteAt = 0
GROUP BY GroupId) AS Members
ON Members.GroupId = ug.Id`).
Where("ug.DeleteAt = 0 AND gc.DeleteAt = 0").
OrderBy("ug.DisplayName")
}
if opts.FilterAllowReference {
query = query.Where("ug.AllowReference = true")
}
if len(opts.Q) > 0 {
pattern := fmt.Sprintf("%%%s%%", sanitizeSearchTerm(opts.Q, "\\"))
operatorKeyword := "ILIKE"
if s.DriverName() == model.DATABASE_DRIVER_MYSQL {
operatorKeyword = "LIKE"
}
query = query.Where(fmt.Sprintf("(ug.Name %[1]s ? OR ug.DisplayName %[1]s ?)", operatorKeyword), pattern, pattern)
}
return query
}
func (s *SqlGroupStore) CountGroupsByTeam(teamId string, opts model.GroupSearchOpts) (int64, *model.AppError) {
countQuery := s.groupsBySyncableBaseQuery(model.GroupSyncableTypeTeam, selectCountGroups, teamId, opts)
countQueryString, args, err := countQuery.ToSql()
if err != nil {
return int64(0), model.NewAppError("SqlGroupStore.CountGroupsByTeam", "store.sql_group.app_error", nil, err.Error(), http.StatusInternalServerError)
}
count, err := s.GetReplica().SelectInt(countQueryString, args...)
if err != nil {
return int64(0), model.NewAppError("SqlGroupStore.CountGroupsByTeam", "store.select_error", nil, err.Error(), http.StatusInternalServerError)
}
return count, nil
}
func (s *SqlGroupStore) GetGroupsByTeam(teamId string, opts model.GroupSearchOpts) ([]*model.GroupWithSchemeAdmin, *model.AppError) {
query := s.groupsBySyncableBaseQuery(model.GroupSyncableTypeTeam, selectGroups, teamId, opts)
if opts.PageOpts != nil {
offset := uint64(opts.PageOpts.Page * opts.PageOpts.PerPage)
query = query.OrderBy("ug.DisplayName").Limit(uint64(opts.PageOpts.PerPage)).Offset(offset)
}
queryString, args, err := query.ToSql()
if err != nil {
return nil, model.NewAppError("SqlGroupStore.GetGroupsByTeam", "store.sql_group.app_error", nil, err.Error(), http.StatusInternalServerError)
}
var groups []*model.GroupWithSchemeAdmin
_, err = s.GetReplica().Select(&groups, queryString, args...)
if err != nil {
return nil, model.NewAppError("SqlGroupStore.GetGroupsByTeam", "store.select_error", nil, err.Error(), http.StatusInternalServerError)
}
return groups, nil
}
func (s *SqlGroupStore) GetGroupsAssociatedToChannelsByTeam(teamId string, opts model.GroupSearchOpts) (map[string][]*model.GroupWithSchemeAdmin, *model.AppError) {
query := s.getGroupsAssociatedToChannelsByTeam(model.GroupSyncableTypeTeam, teamId, opts)
if opts.PageOpts != nil {
offset := uint64(opts.PageOpts.Page * opts.PageOpts.PerPage)
query = query.OrderBy("ug.DisplayName").Limit(uint64(opts.PageOpts.PerPage)).Offset(offset)
}
queryString, args, err := query.ToSql()
if err != nil {
return nil, model.NewAppError("SqlGroupStore.GetGroupsAssociatedToChannelsByTeam", "store.sql_group.app_error", nil, err.Error(), http.StatusInternalServerError)
}
var tgroups []*model.GroupsAssociatedToChannelWithSchemeAdmin
_, err = s.GetReplica().Select(&tgroups, queryString, args...)
if err != nil {
return nil, model.NewAppError("SqlGroupStore.GetGroupsAssociatedToChannelsByTeam", "store.select_error", nil, err.Error(), http.StatusInternalServerError)
}
groups := map[string][]*model.GroupWithSchemeAdmin{}
for _, tgroup := range tgroups {
var group = model.GroupWithSchemeAdmin{}
group.Group = tgroup.Group
group.SchemeAdmin = tgroup.SchemeAdmin
if val, ok := groups[tgroup.ChannelId]; ok {
groups[tgroup.ChannelId] = append(val, &group)
} else {
groups[tgroup.ChannelId] = []*model.GroupWithSchemeAdmin{&group}
}
}
return groups, nil
}
func (s *SqlGroupStore) GetGroups(page, perPage int, opts model.GroupSearchOpts) ([]*model.Group, *model.AppError) {
var groups []*model.Group
groupsQuery := s.getQueryBuilder().Select("g.*")
if opts.IncludeMemberCount {
groupsQuery = s.getQueryBuilder().
Select("g.*, coalesce(Members.MemberCount, 0) AS MemberCount").
LeftJoin("(SELECT GroupMembers.GroupId, COUNT(*) AS MemberCount FROM GroupMembers LEFT JOIN Users ON Users.Id = GroupMembers.UserId WHERE GroupMembers.DeleteAt = 0 AND Users.DeleteAt = 0 GROUP BY GroupId) AS Members ON Members.GroupId = g.Id")
}
groupsQuery = groupsQuery.
From("UserGroups g").
OrderBy("g.DisplayName")
if opts.Since > 0 {
groupsQuery = groupsQuery.Where(sq.Gt{
"g.UpdateAt": opts.Since,
})
} else {
groupsQuery = groupsQuery.Where("g.DeleteAt = 0")
}
if perPage != 0 {
groupsQuery = groupsQuery.
Limit(uint64(perPage)).
Offset(uint64(page * perPage))
}
if opts.FilterAllowReference {
groupsQuery = groupsQuery.Where("g.AllowReference = true")
}
if len(opts.Q) > 0 {
pattern := fmt.Sprintf("%%%s%%", sanitizeSearchTerm(opts.Q, "\\"))
operatorKeyword := "ILIKE"
if s.DriverName() == model.DATABASE_DRIVER_MYSQL {
operatorKeyword = "LIKE"
}
groupsQuery = groupsQuery.Where(fmt.Sprintf("(g.Name %[1]s ? OR g.DisplayName %[1]s ?)", operatorKeyword), pattern, pattern)
}
if len(opts.NotAssociatedToTeam) == 26 {
groupsQuery = groupsQuery.Where(`
g.Id NOT IN (
SELECT
Id
FROM
UserGroups
JOIN GroupTeams ON GroupTeams.GroupId = UserGroups.Id
WHERE
GroupTeams.DeleteAt = 0
AND UserGroups.DeleteAt = 0
AND GroupTeams.TeamId = ?
)
`, opts.NotAssociatedToTeam)
}
if len(opts.NotAssociatedToChannel) == 26 {
groupsQuery = groupsQuery.Where(`
g.Id NOT IN (
SELECT
Id
FROM
UserGroups
JOIN GroupChannels ON GroupChannels.GroupId = UserGroups.Id
WHERE
GroupChannels.DeleteAt = 0
AND UserGroups.DeleteAt = 0
AND GroupChannels.ChannelId = ?
)
`, opts.NotAssociatedToChannel)
}
if opts.FilterParentTeamPermitted && len(opts.NotAssociatedToChannel) == 26 {
groupsQuery = groupsQuery.Where(`
CASE
WHEN (
SELECT
Teams.GroupConstrained
FROM
Teams
JOIN Channels ON Channels.TeamId = Teams.Id
WHERE
Channels.Id = ?
) THEN g.Id IN (
SELECT
GroupId
FROM
GroupTeams
WHERE
GroupTeams.DeleteAt = 0
AND GroupTeams.TeamId = (
SELECT
TeamId
FROM
Channels
WHERE
Id = ?
)
)
ELSE TRUE
END
`, opts.NotAssociatedToChannel, opts.NotAssociatedToChannel)
}
queryString, args, err := groupsQuery.ToSql()
if err != nil {
return nil, model.NewAppError("SqlGroupStore.GetGroups", "store.sql_group.app_error", nil, err.Error(), http.StatusInternalServerError)
}
if _, err = s.GetReplica().Select(&groups, queryString, args...); err != nil {
return nil, model.NewAppError("SqlGroupStore.GetGroups", "store.select_error", nil, err.Error(), http.StatusInternalServerError)
}
return groups, nil
}
func (s *SqlGroupStore) teamMembersMinusGroupMembersQuery(teamID string, groupIDs []string, isCount bool) sq.SelectBuilder {
var selectStr string
if isCount {
selectStr = "count(DISTINCT Users.Id)"
} else {
tmpl := "Users.*, coalesce(TeamMembers.SchemeGuest, false), TeamMembers.SchemeAdmin, TeamMembers.SchemeUser, %s AS GroupIDs"
if s.DriverName() == model.DATABASE_DRIVER_MYSQL {
selectStr = fmt.Sprintf(tmpl, "group_concat(UserGroups.Id)")
} else {
selectStr = fmt.Sprintf(tmpl, "string_agg(UserGroups.Id, ',')")
}
}
subQuery := s.getQueryBuilder().Select("GroupMembers.UserId").
From("GroupMembers").
Join("UserGroups ON UserGroups.Id = GroupMembers.GroupId").
Where("GroupMembers.DeleteAt = 0").
Where(fmt.Sprintf("GroupMembers.GroupId IN ('%s')", strings.Join(groupIDs, "', '")))
sql, _ := subQuery.MustSql()
query := s.getQueryBuilder().Select(selectStr).
From("TeamMembers").
Join("Teams ON Teams.Id = TeamMembers.TeamId").
Join("Users ON Users.Id = TeamMembers.UserId").
LeftJoin("Bots ON Bots.UserId = TeamMembers.UserId").
LeftJoin("GroupMembers ON GroupMembers.UserId = Users.Id").
LeftJoin("UserGroups ON UserGroups.Id = GroupMembers.GroupId").
Where("TeamMembers.DeleteAt = 0").
Where("Teams.DeleteAt = 0").
Where("Users.DeleteAt = 0").
Where("Bots.UserId IS NULL").
Where("Teams.Id = ?", teamID).
Where(fmt.Sprintf("Users.Id NOT IN (%s)", sql))
if !isCount {
query = query.GroupBy("Users.Id, TeamMembers.SchemeGuest, TeamMembers.SchemeAdmin, TeamMembers.SchemeUser")
}
return query
}
// TeamMembersMinusGroupMembers returns the set of users on the given team minus the set of users in the given
// groups.
func (s *SqlGroupStore) TeamMembersMinusGroupMembers(teamID string, groupIDs []string, page, perPage int) ([]*model.UserWithGroups, *model.AppError) {
query := s.teamMembersMinusGroupMembersQuery(teamID, groupIDs, false)
query = query.OrderBy("Users.Username ASC").Limit(uint64(perPage)).Offset(uint64(page * perPage))
queryString, args, err := query.ToSql()
if err != nil {
return nil, model.NewAppError("SqlGroupStore.TeamMembersMinusGroupMembers", "store.sql_group.app_error", nil, err.Error(), http.StatusInternalServerError)
}
var users []*model.UserWithGroups
if _, err = s.GetReplica().Select(&users, queryString, args...); err != nil {
return nil, model.NewAppError("SqlGroupStore.TeamMembersMinusGroupMembers", "store.select_error", nil, err.Error(), http.StatusInternalServerError)
}
return users, nil
}
// CountTeamMembersMinusGroupMembers returns the count of the set of users on the given team minus the set of users
// in the given groups.
func (s *SqlGroupStore) CountTeamMembersMinusGroupMembers(teamID string, groupIDs []string) (int64, *model.AppError) {
queryString, args, err := s.teamMembersMinusGroupMembersQuery(teamID, groupIDs, true).ToSql()
if err != nil {
return 0, model.NewAppError("SqlGroupStore.CountTeamMembersMinusGroupMembers", "store.sql_group.app_error", nil, err.Error(), http.StatusInternalServerError)
}
var count int64
if count, err = s.GetReplica().SelectInt(queryString, args...); err != nil {
return 0, model.NewAppError("SqlGroupStore.CountTeamMembersMinusGroupMembers", "store.select_error", nil, err.Error(), http.StatusInternalServerError)
}
return count, nil
}
func (s *SqlGroupStore) channelMembersMinusGroupMembersQuery(channelID string, groupIDs []string, isCount bool) sq.SelectBuilder {
var selectStr string
if isCount {
selectStr = "count(DISTINCT Users.Id)"
} else {
tmpl := "Users.*, coalesce(ChannelMembers.SchemeGuest, false), ChannelMembers.SchemeAdmin, ChannelMembers.SchemeUser, %s AS GroupIDs"
if s.DriverName() == model.DATABASE_DRIVER_MYSQL {
selectStr = fmt.Sprintf(tmpl, "group_concat(UserGroups.Id)")
} else {
selectStr = fmt.Sprintf(tmpl, "string_agg(UserGroups.Id, ',')")
}
}
subQuery := s.getQueryBuilder().Select("GroupMembers.UserId").
From("GroupMembers").
Join("UserGroups ON UserGroups.Id = GroupMembers.GroupId").
Where("GroupMembers.DeleteAt = 0").
Where(fmt.Sprintf("GroupMembers.GroupId IN ('%s')", strings.Join(groupIDs, "', '")))
sql, _ := subQuery.MustSql()
query := s.getQueryBuilder().Select(selectStr).
From("ChannelMembers").
Join("Channels ON Channels.Id = ChannelMembers.ChannelId").
Join("Users ON Users.Id = ChannelMembers.UserId").
LeftJoin("Bots ON Bots.UserId = ChannelMembers.UserId").
LeftJoin("GroupMembers ON GroupMembers.UserId = Users.Id").
LeftJoin("UserGroups ON UserGroups.Id = GroupMembers.GroupId").
Where("Channels.DeleteAt = 0").
Where("Users.DeleteAt = 0").
Where("Bots.UserId IS NULL").
Where("Channels.Id = ?", channelID).
Where(fmt.Sprintf("Users.Id NOT IN (%s)", sql))
if !isCount {
query = query.GroupBy("Users.Id, ChannelMembers.SchemeGuest, ChannelMembers.SchemeAdmin, ChannelMembers.SchemeUser")
}
return query
}
// ChannelMembersMinusGroupMembers returns the set of users in the given channel minus the set of users in the given
// groups.
func (s *SqlGroupStore) ChannelMembersMinusGroupMembers(channelID string, groupIDs []string, page, perPage int) ([]*model.UserWithGroups, *model.AppError) {
query := s.channelMembersMinusGroupMembersQuery(channelID, groupIDs, false)
query = query.OrderBy("Users.Username ASC").Limit(uint64(perPage)).Offset(uint64(page * perPage))
queryString, args, err := query.ToSql()
if err != nil {
return nil, model.NewAppError("SqlGroupStore.ChannelMembersMinusGroupMembers", "store.sql_group.app_error", nil, err.Error(), http.StatusInternalServerError)
}
var users []*model.UserWithGroups
if _, err = s.GetReplica().Select(&users, queryString, args...); err != nil {
return nil, model.NewAppError("SqlGroupStore.ChannelMembersMinusGroupMembers", "store.select_error", nil, err.Error(), http.StatusInternalServerError)
}
return users, nil
}
// CountChannelMembersMinusGroupMembers returns the count of the set of users in the given channel minus the set of users
// in the given groups.
func (s *SqlGroupStore) CountChannelMembersMinusGroupMembers(channelID string, groupIDs []string) (int64, *model.AppError) {
queryString, args, err := s.channelMembersMinusGroupMembersQuery(channelID, groupIDs, true).ToSql()
if err != nil {
return 0, model.NewAppError("SqlGroupStore.CountChannelMembersMinusGroupMembers", "store.sql_group.app_error", nil, err.Error(), http.StatusInternalServerError)
}
var count int64
if count, err = s.GetReplica().SelectInt(queryString, args...); err != nil {
return 0, model.NewAppError("SqlGroupStore.CountChannelMembersMinusGroupMembers", "store.select_error", nil, err.Error(), http.StatusInternalServerError)
}
return count, nil
}
func (s *SqlGroupStore) AdminRoleGroupsForSyncableMember(userID, syncableID string, syncableType model.GroupSyncableType) ([]string, *model.AppError) {
var groupIds []string
sql := fmt.Sprintf(`
SELECT
GroupMembers.GroupId
FROM
GroupMembers
INNER JOIN
Group%[1]ss ON Group%[1]ss.GroupId = GroupMembers.GroupId
WHERE
GroupMembers.UserId = :UserId
AND GroupMembers.DeleteAt = 0
AND %[1]sId = :%[1]sId
AND Group%[1]ss.DeleteAt = 0
AND Group%[1]ss.SchemeAdmin = TRUE`, syncableType)
_, err := s.GetReplica().Select(&groupIds, sql, map[string]interface{}{"UserId": userID, fmt.Sprintf("%sId", syncableType): syncableID})
if err != nil {
return nil, model.NewAppError("SqlGroupStore AdminRoleGroupsForSyncableMember", "store.select_error", nil, err.Error(), http.StatusInternalServerError)
}
return groupIds, nil
}
func (s *SqlGroupStore) PermittedSyncableAdmins(syncableID string, syncableType model.GroupSyncableType) ([]string, *model.AppError) {
query := s.getQueryBuilder().Select("UserId").
From(fmt.Sprintf("Group%ss", syncableType)).
Join(fmt.Sprintf("GroupMembers ON GroupMembers.GroupId = Group%ss.GroupId AND Group%[1]ss.SchemeAdmin = TRUE AND GroupMembers.DeleteAt = 0", syncableType.String())).Where(fmt.Sprintf("Group%[1]ss.%[1]sId = ?", syncableType.String()), syncableID)
sql, args, err := query.ToSql()
if err != nil {
return nil, model.NewAppError("SqlGroupStore.PermittedSyncableAdmins", "store.sql_group.app_error", nil, err.Error(), http.StatusInternalServerError)
}
var userIDs []string
if _, err = s.GetReplica().Select(&userIDs, sql, args...); err != nil {
return nil, model.NewAppError("SqlGroupStore.PermittedSyncableAdmins", "store.select_error", nil, err.Error(), http.StatusInternalServerError)
}
return userIDs, nil
}
func (s *SqlGroupStore) GroupCount() (int64, *model.AppError) {
return s.countTable("UserGroups")
}
func (s *SqlGroupStore) GroupTeamCount() (int64, *model.AppError) {
return s.countTable("GroupTeams")
}
func (s *SqlGroupStore) GroupChannelCount() (int64, *model.AppError) {
return s.countTable("GroupChannels")
}
func (s *SqlGroupStore) GroupMemberCount() (int64, *model.AppError) {
return s.countTable("GroupMembers")
}
func (s *SqlGroupStore) DistinctGroupMemberCount() (int64, *model.AppError) {
return s.countTableWithSelectAndWhere("COUNT(DISTINCT UserId)", "GroupMembers", nil)
}
func (s *SqlGroupStore) GroupCountWithAllowReference() (int64, *model.AppError) {
return s.countTableWithSelectAndWhere("COUNT(*)", "UserGroups", sq.Eq{"AllowReference": true, "DeleteAt": 0})
}
func (s *SqlGroupStore) countTable(tableName string) (int64, *model.AppError) {
return s.countTableWithSelectAndWhere("COUNT(*)", tableName, nil)
}
func (s *SqlGroupStore) countTableWithSelectAndWhere(selectStr, tableName string, whereStmt map[string]interface{}) (int64, *model.AppError) {
if whereStmt == nil {
whereStmt = sq.Eq{"DeleteAt": 0}
}
query := s.getQueryBuilder().Select(selectStr).From(tableName).Where(whereStmt)
sql, args, err := query.ToSql()
if err != nil {
return 0, model.NewAppError("SqlGroupStore.countTableWithSelect", "store.sql_group.app_error", nil, err.Error(), http.StatusInternalServerError)
}
count, err := s.GetReplica().SelectInt(sql, args...)
if err != nil {
return 0, model.NewAppError("SqlGroupStore.countTableWithSelect", "store.select_error", nil, err.Error(), http.StatusInternalServerError)
}
return count, nil
}