Full Mattermost server source with integrated Community Enterprise features. Includes vendor directory for offline/air-gapped builds. Structure: - enterprise-impl/: Enterprise feature implementations - enterprise-community/: Init files that register implementations - enterprise/: Bridge imports (community_imports.go) - vendor/: All dependencies for offline builds Build (online): go build ./cmd/mattermost Build (offline/air-gapped): go build -mod=vendor ./cmd/mattermost 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2006 lines
64 KiB
Go
2006 lines
64 KiB
Go
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
|
// See LICENSE.txt for license information.
|
|
|
|
package sqlstore
|
|
|
|
import (
|
|
"database/sql"
|
|
"fmt"
|
|
"strings"
|
|
|
|
sq "github.com/mattermost/squirrel"
|
|
"github.com/pkg/errors"
|
|
|
|
"github.com/mattermost/mattermost/server/public/model"
|
|
"github.com/mattermost/mattermost/server/v8/channels/store"
|
|
)
|
|
|
|
type selectType int
|
|
|
|
const (
|
|
selectGroups selectType = iota
|
|
selectCountGroups
|
|
)
|
|
|
|
type groupTeam struct {
|
|
model.GroupSyncable
|
|
TeamId string
|
|
}
|
|
|
|
type groupChannel struct {
|
|
model.GroupSyncable
|
|
ChannelId string
|
|
}
|
|
|
|
type groupTeamJoin struct {
|
|
groupTeam
|
|
TeamDisplayName string
|
|
TeamType string
|
|
}
|
|
|
|
type groupChannelJoin struct {
|
|
groupChannel
|
|
ChannelDisplayName string
|
|
TeamDisplayName string
|
|
TeamType string
|
|
ChannelType string
|
|
TeamId string
|
|
}
|
|
|
|
type SqlGroupStore struct {
|
|
*SqlStore
|
|
userGroupsSelectQuery sq.SelectBuilder
|
|
groupMembersSelectQuery sq.SelectBuilder
|
|
groupMemberUsersSelectQuery sq.SelectBuilder
|
|
groupTeamsSelectQuery sq.SelectBuilder
|
|
groupChannelsSelectQuery sq.SelectBuilder
|
|
}
|
|
|
|
func newSqlGroupStore(sqlStore *SqlStore) store.GroupStore {
|
|
s := &SqlGroupStore{SqlStore: sqlStore}
|
|
|
|
s.userGroupsSelectQuery = s.getQueryBuilder().
|
|
Select(
|
|
"UserGroups.Id",
|
|
"UserGroups.Name",
|
|
"UserGroups.DisplayName",
|
|
"UserGroups.Description",
|
|
"UserGroups.Source",
|
|
"UserGroups.RemoteId",
|
|
"UserGroups.CreateAt",
|
|
"UserGroups.UpdateAt",
|
|
"UserGroups.DeleteAt",
|
|
"UserGroups.AllowReference",
|
|
).
|
|
From("UserGroups")
|
|
|
|
s.groupMembersSelectQuery = s.getQueryBuilder().
|
|
Select(
|
|
"GroupMembers.GroupId",
|
|
"GroupMembers.UserId",
|
|
"GroupMembers.CreateAt",
|
|
"GroupMembers.DeleteAt",
|
|
).From("GroupMembers")
|
|
|
|
s.groupMemberUsersSelectQuery = s.getQueryBuilder().
|
|
Select(getUsersColumns()...).
|
|
From("GroupMembers").
|
|
Join("Users ON Users.Id = GroupMembers.UserId")
|
|
|
|
s.groupTeamsSelectQuery = s.getQueryBuilder().
|
|
Select(
|
|
"GroupTeams.GroupId",
|
|
"GroupTeams.TeamId",
|
|
"GroupTeams.AutoAdd",
|
|
"GroupTeams.SchemeAdmin",
|
|
"GroupTeams.CreateAt",
|
|
"GroupTeams.UpdateAt",
|
|
"GroupTeams.DeleteAt",
|
|
).From("GroupTeams")
|
|
|
|
s.groupChannelsSelectQuery = s.getQueryBuilder().
|
|
Select(
|
|
"GroupChannels.GroupId",
|
|
"GroupChannels.ChannelId",
|
|
"GroupChannels.AutoAdd",
|
|
"GroupChannels.SchemeAdmin",
|
|
"GroupChannels.CreateAt",
|
|
"GroupChannels.UpdateAt",
|
|
"GroupChannels.DeleteAt",
|
|
).From("GroupChannels")
|
|
|
|
return s
|
|
}
|
|
|
|
func (s *SqlGroupStore) Create(group *model.Group) (*model.Group, error) {
|
|
if group.Id != "" {
|
|
return nil, store.NewErrInvalidInput("Group", "id", group.Id)
|
|
}
|
|
|
|
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().NamedExec(`INSERT INTO UserGroups
|
|
(Id, Name, DisplayName, Description, Source, RemoteId, CreateAt, UpdateAt, DeleteAt, AllowReference)
|
|
VALUES
|
|
(:Id, :Name, :DisplayName, :Description, :Source, :RemoteId, :CreateAt, :UpdateAt, :DeleteAt, :AllowReference)`, group); err != nil {
|
|
if IsUniqueConstraintError(err, []string{"Name", "groups_name_key"}) {
|
|
return nil, errors.Wrapf(err, "Group with name %s already exists", *group.Name)
|
|
}
|
|
return nil, errors.Wrap(err, "failed to save Group")
|
|
}
|
|
|
|
return group, nil
|
|
}
|
|
|
|
func (s *SqlGroupStore) CreateWithUserIds(g *model.GroupWithUserIds) (_ *model.Group, err error) {
|
|
if g.Id != "" {
|
|
return nil, store.NewErrInvalidInput("Group", "id", g.Id)
|
|
}
|
|
|
|
// Check if group values are formatted correctly
|
|
if appErr := g.IsValidForCreate(); appErr != nil {
|
|
return nil, appErr
|
|
}
|
|
|
|
// Check Users exist
|
|
if err = s.checkUsersExist(g.UserIds); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
g.Id = model.NewId()
|
|
g.CreateAt = model.GetMillis()
|
|
g.UpdateAt = g.CreateAt
|
|
|
|
groupInsertQuery, groupInsertArgs, err := s.getQueryBuilder().
|
|
Insert("UserGroups").
|
|
Columns("Id", "Name", "DisplayName", "Description", "Source", "RemoteId", "CreateAt", "UpdateAt", "DeleteAt", "AllowReference").
|
|
Values(g.Id, g.Name, g.DisplayName, g.Description, g.Source, g.RemoteId, g.CreateAt, g.UpdateAt, 0, g.AllowReference).
|
|
ToSql()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
usersInsertQuery, usersInsertArgs, err := s.buildInsertGroupUsersQuery(g.Id, g.UserIds)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
txn, err := s.GetMaster().Beginx()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer finalizeTransactionX(txn, &err)
|
|
|
|
// Create a new usergroup
|
|
if _, err = txn.Exec(groupInsertQuery, groupInsertArgs...); err != nil {
|
|
if IsUniqueConstraintError(err, []string{"Name", "groups_name_key"}) {
|
|
return nil, store.NewErrUniqueConstraint("Name")
|
|
}
|
|
return nil, errors.Wrap(err, "failed to save Group")
|
|
}
|
|
// Insert the Group Members
|
|
if _, err = executePossiblyEmptyQuery(txn, usersInsertQuery, usersInsertArgs...); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
groupGroupQuery := s.userGroupsSelectQuery.
|
|
Column("A.Count AS MemberCount").
|
|
InnerJoin(`(
|
|
SELECT
|
|
UserGroups.Id,
|
|
COUNT(GroupMembers.UserId) AS Count
|
|
FROM
|
|
UserGroups
|
|
LEFT JOIN GroupMembers ON UserGroups.Id = GroupMembers.GroupId
|
|
WHERE
|
|
UserGroups.Id = ?
|
|
GROUP BY
|
|
UserGroups.Id
|
|
ORDER BY
|
|
UserGroups.DisplayName,
|
|
UserGroups.Id
|
|
LIMIT
|
|
1 OFFSET 0
|
|
) AS A ON UserGroups.Id = A.Id`, g.Id).
|
|
OrderBy("UserGroups.CreateAt DESC")
|
|
|
|
var newGroup group
|
|
if err = txn.GetBuilder(&newGroup, groupGroupQuery); err != nil {
|
|
return nil, err
|
|
}
|
|
if err = txn.Commit(); err != nil {
|
|
return nil, err
|
|
}
|
|
return newGroup.ToModel(), nil
|
|
}
|
|
|
|
func (s *SqlGroupStore) checkUsersExist(userIDs []string) error {
|
|
if len(userIDs) == 0 {
|
|
return nil
|
|
}
|
|
usersSelectQuery := s.getQueryBuilder().
|
|
Select("Id").
|
|
From("Users").
|
|
Where(sq.Eq{"Id": userIDs, "DeleteAt": 0})
|
|
|
|
var rows []string
|
|
err := s.GetReplica().SelectBuilder(&rows, usersSelectQuery)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if len(rows) == len(userIDs) {
|
|
return nil
|
|
}
|
|
retrievedIDs := make(map[string]bool)
|
|
for _, userID := range rows {
|
|
retrievedIDs[userID] = true
|
|
}
|
|
for _, userID := range userIDs {
|
|
if _, ok := retrievedIDs[userID]; !ok {
|
|
return store.NewErrNotFound("User", userID)
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (s *SqlGroupStore) buildInsertGroupUsersQuery(groupId string, userIds []string) (query string, args []any, err error) {
|
|
if len(userIds) > 0 {
|
|
builder := s.getQueryBuilder().
|
|
Insert("GroupMembers").
|
|
Columns("GroupId", "UserId", "CreateAt", "DeleteAt")
|
|
for _, userId := range userIds {
|
|
builder = builder.Values(groupId, userId, model.GetMillis(), 0)
|
|
}
|
|
query, args, err = builder.ToSql()
|
|
}
|
|
return
|
|
}
|
|
|
|
func (s *SqlGroupStore) Get(groupId string) (*model.Group, error) {
|
|
var group model.Group
|
|
builder := s.userGroupsSelectQuery.
|
|
Where(sq.Eq{"Id": groupId})
|
|
|
|
if err := s.GetReplica().GetBuilder(&group, builder); err != nil {
|
|
if err == sql.ErrNoRows {
|
|
return nil, store.NewErrNotFound("Group", groupId)
|
|
}
|
|
return nil, errors.Wrapf(err, "failed to get Group with id=%s", groupId)
|
|
}
|
|
|
|
return &group, nil
|
|
}
|
|
|
|
func (s *SqlGroupStore) GetByName(name string, opts model.GroupSearchOpts) (*model.Group, error) {
|
|
var group model.Group
|
|
query := s.userGroupsSelectQuery.
|
|
Where(sq.Eq{"Name": name})
|
|
|
|
if opts.FilterAllowReference {
|
|
query = query.Where("AllowReference = true")
|
|
}
|
|
|
|
if err := s.GetReplica().GetBuilder(&group, query); err != nil {
|
|
if err == sql.ErrNoRows {
|
|
return nil, store.NewErrNotFound("Group", fmt.Sprintf("name=%s", name))
|
|
}
|
|
return nil, errors.Wrapf(err, "failed to get Group with name=%s", name)
|
|
}
|
|
|
|
return &group, nil
|
|
}
|
|
|
|
func (s *SqlGroupStore) GetByNames(names []string, viewRestrictions *model.ViewUsersRestrictions) ([]*model.Group, error) {
|
|
groups := []*model.Group{}
|
|
query := s.userGroupsSelectQuery.Where(sq.Eq{"Name": names})
|
|
query = applyViewRestrictionsFilter(query, viewRestrictions, true)
|
|
if err := s.GetReplica().SelectBuilder(&groups, query); err != nil {
|
|
return nil, errors.Wrap(err, "failed to find Groups by names")
|
|
}
|
|
return groups, nil
|
|
}
|
|
|
|
func (s *SqlGroupStore) GetByIDs(groupIDs []string) ([]*model.Group, error) {
|
|
groups := []*model.Group{}
|
|
query := s.userGroupsSelectQuery.Where(sq.Eq{"Id": groupIDs})
|
|
if err := s.GetReplica().SelectBuilder(&groups, query); err != nil {
|
|
return nil, errors.Wrap(err, "failed to find Groups by ids")
|
|
}
|
|
return groups, nil
|
|
}
|
|
|
|
func (s *SqlGroupStore) GetByRemoteID(remoteID string, groupSource model.GroupSource) (*model.Group, error) {
|
|
var group model.Group
|
|
builder := s.userGroupsSelectQuery.
|
|
Where(sq.Eq{
|
|
"RemoteId": remoteID,
|
|
"Source": groupSource,
|
|
})
|
|
|
|
if err := s.GetReplica().GetBuilder(&group, builder); err != nil {
|
|
if err == sql.ErrNoRows {
|
|
return nil, store.NewErrNotFound("Group", fmt.Sprintf("remoteId=%s", remoteID))
|
|
}
|
|
return nil, errors.Wrapf(err, "failed to get Group with remoteId=%s", remoteID)
|
|
}
|
|
|
|
return &group, nil
|
|
}
|
|
|
|
func (s *SqlGroupStore) GetAllBySource(groupSource model.GroupSource) ([]*model.Group, error) {
|
|
groups := []*model.Group{}
|
|
builder := s.userGroupsSelectQuery.
|
|
Where(sq.Eq{
|
|
"DeleteAt": 0,
|
|
"Source": groupSource,
|
|
})
|
|
|
|
if err := s.GetReplica().SelectBuilder(&groups, builder); err != nil {
|
|
return nil, errors.Wrapf(err, "failed to find Groups by groupSource=%v", groupSource)
|
|
}
|
|
|
|
return groups, nil
|
|
}
|
|
|
|
func (s *SqlGroupStore) GetByUser(userID string, opts model.GroupSearchOpts) ([]*model.Group, error) {
|
|
groups := []*model.Group{}
|
|
|
|
builder := s.userGroupsSelectQuery.
|
|
Join("GroupMembers ON GroupMembers.GroupId = UserGroups.Id").
|
|
Where(sq.Eq{
|
|
"GroupMembers.DeleteAt": 0,
|
|
"GroupMembers.UserId": userID,
|
|
})
|
|
|
|
if opts.FilterAllowReference {
|
|
builder = builder.Where("UserGroups.AllowReference = true")
|
|
}
|
|
|
|
if err := s.GetReplica().SelectBuilder(&groups, builder); err != nil {
|
|
return nil, errors.Wrapf(err, "failed to find Groups with userId=%s", userID)
|
|
}
|
|
|
|
return groups, nil
|
|
}
|
|
|
|
func (s *SqlGroupStore) Update(group *model.Group) (*model.Group, error) {
|
|
var retrievedGroup model.Group
|
|
builder := s.userGroupsSelectQuery.Where(sq.Eq{"Id": group.Id})
|
|
|
|
if err := s.GetReplica().GetBuilder(&retrievedGroup, builder); err != nil {
|
|
if err == sql.ErrNoRows {
|
|
return nil, store.NewErrNotFound("Group", group.Id)
|
|
}
|
|
return nil, errors.Wrapf(err, "failed to get Group with id=%s", group.Id)
|
|
}
|
|
|
|
// If updating DeleteAt it can only be to 0
|
|
if group.DeleteAt != retrievedGroup.DeleteAt && group.DeleteAt != 0 {
|
|
return nil, errors.New("DeleteAt should be 0 when updating")
|
|
}
|
|
|
|
// 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
|
|
}
|
|
|
|
res, err := s.GetMaster().NamedExec(`UPDATE UserGroups
|
|
SET Name=:Name, DisplayName=:DisplayName, Description=:Description, Source=:Source,
|
|
RemoteId=:RemoteId, CreateAt=:CreateAt, UpdateAt=:UpdateAt, DeleteAt=:DeleteAt, AllowReference=:AllowReference
|
|
WHERE Id=:Id`, group)
|
|
if err != nil {
|
|
if IsUniqueConstraintError(err, []string{"Name", "groups_name_key"}) {
|
|
return nil, store.NewErrUniqueConstraint("Name")
|
|
}
|
|
return nil, errors.Wrap(err, "failed to update Group")
|
|
}
|
|
rowsChanged, _ := res.RowsAffected()
|
|
if rowsChanged > 1 {
|
|
return nil, errors.Wrapf(err, "multiple Groups were update: %d", rowsChanged)
|
|
}
|
|
|
|
return group, nil
|
|
}
|
|
|
|
func (s *SqlGroupStore) Delete(groupID string) (*model.Group, error) {
|
|
var group model.Group
|
|
builder := s.userGroupsSelectQuery.
|
|
Where(sq.Eq{
|
|
"Id": groupID,
|
|
"DeleteAt": 0,
|
|
})
|
|
|
|
if err := s.GetReplica().GetBuilder(&group, builder); err != nil {
|
|
if err == sql.ErrNoRows {
|
|
return nil, store.NewErrNotFound("Group", groupID)
|
|
}
|
|
return nil, errors.Wrapf(err, "failed to get Group with id=%s", groupID)
|
|
}
|
|
|
|
time := model.GetMillis()
|
|
group.DeleteAt = time
|
|
group.UpdateAt = time
|
|
if _, err := s.GetMaster().Exec(`UPDATE UserGroups
|
|
SET DeleteAt=?, UpdateAt=?
|
|
WHERE Id=? AND DeleteAt=0`, group.DeleteAt, group.UpdateAt, groupID); err != nil {
|
|
return nil, errors.Wrapf(err, "failed to update Group with id=%s", groupID)
|
|
}
|
|
|
|
return &group, nil
|
|
}
|
|
|
|
func (s *SqlGroupStore) Restore(groupID string) (*model.Group, error) {
|
|
var group model.Group
|
|
builder := s.userGroupsSelectQuery.
|
|
Where(sq.And{
|
|
sq.Eq{"Id": groupID},
|
|
sq.NotEq{"DeleteAt": 0},
|
|
})
|
|
|
|
if err := s.GetReplica().GetBuilder(&group, builder); err != nil {
|
|
if err == sql.ErrNoRows {
|
|
return nil, store.NewErrNotFound("Group", groupID)
|
|
}
|
|
return nil, errors.Wrapf(err, "failed to get Group with id=%s", groupID)
|
|
}
|
|
|
|
group.UpdateAt = model.GetMillis()
|
|
group.DeleteAt = 0
|
|
if _, err := s.GetMaster().Exec(`UPDATE UserGroups
|
|
SET DeleteAt=0, UpdateAt=?
|
|
WHERE Id=? AND DeleteAt!=0`, group.UpdateAt, groupID); err != nil {
|
|
return nil, errors.Wrapf(err, "failed to update Group with id=%s", groupID)
|
|
}
|
|
|
|
return &group, nil
|
|
}
|
|
|
|
func (s *SqlGroupStore) GetMember(groupID, userID string) (*model.GroupMember, error) {
|
|
builder := s.groupMembersSelectQuery.
|
|
Where(sq.Eq{"UserId": userID}).
|
|
Where(sq.Eq{"GroupId": groupID}).
|
|
Where(sq.Eq{"DeleteAt": 0})
|
|
|
|
var groupMember model.GroupMember
|
|
if err := s.GetReplica().GetBuilder(&groupMember, builder); err != nil {
|
|
return nil, errors.Wrap(err, "GetMember")
|
|
}
|
|
return &groupMember, nil
|
|
}
|
|
|
|
func (s *SqlGroupStore) GetMemberUsers(groupID string) ([]*model.User, error) {
|
|
groupMembers := []*model.User{}
|
|
|
|
builder := s.groupMemberUsersSelectQuery.
|
|
Where(sq.Eq{
|
|
"GroupMembers.DeleteAt": 0,
|
|
"Users.DeleteAt": 0,
|
|
"GroupMembers.GroupId": groupID,
|
|
})
|
|
|
|
if err := s.GetReplica().SelectBuilder(&groupMembers, builder); err != nil {
|
|
return nil, errors.Wrapf(err, "failed to find member Users for Group with id=%s", groupID)
|
|
}
|
|
|
|
return groupMembers, nil
|
|
}
|
|
|
|
func (s *SqlGroupStore) GetMemberUsersPage(groupID string, page int, perPage int, viewRestrictions *model.ViewUsersRestrictions) ([]*model.User, error) {
|
|
return s.GetMemberUsersSortedPage(groupID, page, perPage, viewRestrictions, model.ShowUsername)
|
|
}
|
|
|
|
func (s *SqlGroupStore) GetMemberUsersSortedPage(groupID string, page int, perPage int, viewRestrictions *model.ViewUsersRestrictions, teammateNameDisplay string) ([]*model.User, error) {
|
|
groupMembers := []*model.User{}
|
|
|
|
userQuery := s.groupMemberUsersSelectQuery.
|
|
Where(sq.Eq{"GroupMembers.DeleteAt": 0}).
|
|
Where(sq.Eq{"Users.DeleteAt": 0}).
|
|
Where(sq.Eq{"GroupMembers.GroupId": groupID})
|
|
|
|
userQuery = applyViewRestrictionsFilter(userQuery, viewRestrictions, true)
|
|
|
|
orderQuery := s.getQueryBuilder().
|
|
Select(getUsersColumns()...).
|
|
FromSelect(userQuery, "Users")
|
|
|
|
if teammateNameDisplay == model.ShowNicknameFullName {
|
|
orderQuery = orderQuery.OrderBy(`
|
|
CASE
|
|
WHEN Users.Nickname != '' THEN Users.Nickname
|
|
WHEN Users.FirstName != '' AND Users.LastName != '' THEN CONCAT(Users.FirstName, ' ', Users.LastName)
|
|
WHEN Users.FirstName != '' THEN Users.FirstName
|
|
WHEN Users.LastName != '' THEN Users.LastName
|
|
ELSE Users.Username
|
|
END`)
|
|
} else if teammateNameDisplay == model.ShowFullName {
|
|
orderQuery = orderQuery.OrderBy(`
|
|
CASE
|
|
WHEN Users.FirstName != '' AND Users.LastName != '' THEN CONCAT(Users.FirstName, ' ', Users.LastName)
|
|
WHEN Users.FirstName != '' THEN Users.FirstName
|
|
WHEN Users.LastName != '' THEN Users.LastName
|
|
ELSE Users.Username
|
|
END`)
|
|
} else {
|
|
orderQuery = orderQuery.OrderBy("Users.Username")
|
|
}
|
|
|
|
orderQuery = orderQuery.
|
|
Limit(uint64(perPage)).
|
|
Offset(uint64(page * perPage))
|
|
|
|
if err := s.GetReplica().SelectBuilder(&groupMembers, orderQuery); err != nil {
|
|
return nil, errors.Wrapf(err, "failed to find member Users for Group with id=%s", groupID)
|
|
}
|
|
|
|
return groupMembers, nil
|
|
}
|
|
|
|
func (s *SqlGroupStore) GetNonMemberUsersPage(groupID string, page int, perPage int, viewRestrictions *model.ViewUsersRestrictions) ([]*model.User, error) {
|
|
groupMembers := []*model.User{}
|
|
|
|
builder := s.userGroupsSelectQuery.
|
|
Where(sq.Eq{"Id": groupID})
|
|
|
|
if err := s.GetReplica().GetBuilder(&model.Group{}, builder); err != nil {
|
|
return nil, errors.Wrap(err, "GetNonMemberUsersPage")
|
|
}
|
|
|
|
builder = s.getQueryBuilder().
|
|
Select(getUsersColumns()...).
|
|
From("Users").
|
|
LeftJoin("GroupMembers ON (GroupMembers.UserId = Users.Id AND GroupMembers.GroupId = ?)", groupID).
|
|
Where(sq.Eq{"Users.DeleteAt": 0}).
|
|
Where("(GroupMembers.UserID IS NULL OR GroupMembers.DeleteAt != 0)").
|
|
Limit(uint64(perPage)).
|
|
Offset(uint64(page * perPage)).
|
|
OrderBy("Users.Username ASC")
|
|
|
|
builder = applyViewRestrictionsFilter(builder, viewRestrictions, true)
|
|
|
|
if err := s.GetReplica().SelectBuilder(&groupMembers, builder); err != nil {
|
|
return nil, errors.Wrapf(err, "failed to find member Users for Group with id=%s", groupID)
|
|
}
|
|
|
|
return groupMembers, nil
|
|
}
|
|
|
|
func (s *SqlGroupStore) GetMemberCount(groupID string) (int64, error) {
|
|
return s.GetMemberCountWithRestrictions(groupID, nil)
|
|
}
|
|
|
|
func (s *SqlGroupStore) GetMemberCountWithRestrictions(groupID string, viewRestrictions *model.ViewUsersRestrictions) (int64, error) {
|
|
query := s.getQueryBuilder().
|
|
Select("COUNT(DISTINCT Users.Id)").
|
|
From("GroupMembers").
|
|
Join("Users ON Users.Id = GroupMembers.UserId").
|
|
Where(sq.Eq{"GroupMembers.GroupId": groupID}).
|
|
Where(sq.Eq{"Users.DeleteAt": 0}).
|
|
Where(sq.Eq{"GroupMembers.DeleteAt": 0})
|
|
|
|
query = applyViewRestrictionsFilter(query, viewRestrictions, false)
|
|
|
|
var count int64
|
|
if err := s.GetReplica().GetBuilder(&count, query); err != nil {
|
|
return int64(0), errors.Wrapf(err, "failed to count member Users for Group with id=%s", groupID)
|
|
}
|
|
|
|
return count, nil
|
|
}
|
|
|
|
func (s *SqlGroupStore) GetMemberUsersInTeam(groupID string, teamID string) ([]*model.User, error) {
|
|
groupMembers := []*model.User{}
|
|
|
|
query := s.groupMemberUsersSelectQuery.
|
|
Where(sq.Eq{
|
|
"GroupMembers.GroupId": groupID,
|
|
"GroupMembers.UserId": s.getQueryBuilder().
|
|
Select("TeamMembers.UserId").
|
|
From("TeamMembers").
|
|
Join("Teams ON Teams.Id = TeamMembers.TeamId").
|
|
Where(sq.Eq{
|
|
"TeamMembers.TeamId": teamID,
|
|
"TeamMembers.DeleteAt": 0,
|
|
}),
|
|
"GroupMembers.DeleteAt": 0,
|
|
"Users.DeleteAt": 0,
|
|
})
|
|
|
|
if err := s.GetReplica().SelectBuilder(&groupMembers, query); err != nil {
|
|
return nil, errors.Wrapf(err, "failed to member Users for groupId=%s and teamId=%s", groupID, teamID)
|
|
}
|
|
|
|
return groupMembers, nil
|
|
}
|
|
|
|
func (s *SqlGroupStore) GetMemberUsersNotInChannel(groupID string, channelID string) ([]*model.User, error) {
|
|
groupMembers := []*model.User{}
|
|
|
|
query := s.groupMemberUsersSelectQuery.
|
|
Where(sq.Eq{
|
|
"GroupMembers.GroupId": groupID,
|
|
}).
|
|
Where(sq.NotEq{
|
|
"GroupMembers.UserId": s.getQueryBuilder().
|
|
Select("ChannelMembers.UserId").
|
|
From("ChannelMembers").
|
|
Where(sq.Eq{
|
|
"ChannelMembers.ChannelId": channelID,
|
|
}),
|
|
}).
|
|
Where(sq.Eq{
|
|
"GroupMembers.UserId": s.getQueryBuilder().
|
|
Select("TeamMembers.UserId").
|
|
From("Channels").
|
|
Join("TeamMembers ON TeamMembers.TeamId = Channels.TeamId").
|
|
Where(sq.Eq{
|
|
"Channels.Id": channelID,
|
|
"TeamMembers.DeleteAt": 0,
|
|
}),
|
|
}).
|
|
Where(sq.Eq{
|
|
"GroupMembers.DeleteAt": 0,
|
|
"Users.DeleteAt": 0,
|
|
})
|
|
|
|
if err := s.GetReplica().SelectBuilder(&groupMembers, query); err != nil {
|
|
return nil, errors.Wrapf(err, "failed to member Users for groupId=%s and channelId!=%s", groupID, channelID)
|
|
}
|
|
|
|
return groupMembers, nil
|
|
}
|
|
|
|
func (s *SqlGroupStore) UpsertMember(groupID string, userID string) (*model.GroupMember, error) {
|
|
members, query, err := s.buildUpsertMembersQuery(groupID, []string{userID})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if _, err = s.GetMaster().ExecBuilder(query); err != nil {
|
|
return nil, errors.Wrap(err, "failed to save GroupMember")
|
|
}
|
|
return members[0], nil
|
|
}
|
|
|
|
func (s *SqlGroupStore) DeleteMember(groupID string, userID string) (*model.GroupMember, error) {
|
|
members, query, err := s.buildDeleteMembersQuery(groupID, []string{userID})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if _, err = s.GetMaster().ExecBuilder(query); err != nil {
|
|
return nil, errors.Wrapf(err, "failed to update GroupMember with groupId=%s and userId=%s", groupID, userID)
|
|
}
|
|
|
|
return members[0], nil
|
|
}
|
|
|
|
func (s *SqlGroupStore) PermanentDeleteMembersByUser(userId string) error {
|
|
builder := s.getQueryBuilder().
|
|
Delete("GroupMembers").
|
|
Where(sq.Eq{"UserId": userId})
|
|
if _, err := s.GetMaster().ExecBuilder(builder); err != nil {
|
|
return errors.Wrapf(err, "failed to permanent delete GroupMember with userId=%s", userId)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (s *SqlGroupStore) CreateGroupSyncable(groupSyncable *model.GroupSyncable) (*model.GroupSyncable, error) {
|
|
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().NamedExec(`INSERT INTO GroupTeams
|
|
(GroupId, AutoAdd, SchemeAdmin, CreateAt, DeleteAt, UpdateAt, TeamId)
|
|
VALUES
|
|
(:GroupId, :AutoAdd, :SchemeAdmin, :CreateAt, :DeleteAt, :UpdateAt, :TeamId)`, groupSyncableToGroupTeam(groupSyncable))
|
|
case model.GroupSyncableTypeChannel:
|
|
var channel *model.Channel
|
|
channel, err := s.Channel().Get(groupSyncable.SyncableId, false)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
_, insertErr = s.GetMaster().NamedExec(`INSERT INTO GroupChannels
|
|
(GroupId, AutoAdd, SchemeAdmin, CreateAt, DeleteAt, UpdateAt, ChannelId)
|
|
VALUES
|
|
(:GroupId, :AutoAdd, :SchemeAdmin, :CreateAt, :DeleteAt, :UpdateAt, :ChannelId)`, groupSyncableToGroupChannel(groupSyncable))
|
|
groupSyncable.TeamID = channel.TeamId
|
|
default:
|
|
return nil, fmt.Errorf("invalid GroupSyncableType: %s", groupSyncable.Type)
|
|
}
|
|
|
|
if insertErr != nil {
|
|
return nil, errors.Wrap(insertErr, "unable to insert GroupSyncable")
|
|
}
|
|
|
|
return groupSyncable, nil
|
|
}
|
|
|
|
func (s *SqlGroupStore) GetGroupSyncable(groupID string, syncableID string, syncableType model.GroupSyncableType) (*model.GroupSyncable, error) {
|
|
groupSyncable, err := s.getGroupSyncable(groupID, syncableID, syncableType)
|
|
if err != nil {
|
|
if err == sql.ErrNoRows {
|
|
return nil, store.NewErrNotFound("GroupSyncable", fmt.Sprintf("groupId=%s, syncableId=%s, syncableType=%s", groupID, syncableID, syncableType))
|
|
}
|
|
return nil, errors.Wrapf(err, "failed to find GroupSyncable with groupId=%s, syncableId=%s, syncableType=%s", groupID, syncableID, syncableType)
|
|
}
|
|
|
|
return groupSyncable, nil
|
|
}
|
|
|
|
func (s *SqlGroupStore) getGroupSyncable(groupID string, syncableID string, syncableType model.GroupSyncableType) (*model.GroupSyncable, error) {
|
|
var err error
|
|
var result any
|
|
|
|
switch syncableType {
|
|
case model.GroupSyncableTypeTeam:
|
|
var team groupTeam
|
|
err = s.GetReplica().GetBuilder(&team, s.groupTeamsSelectQuery.Where(sq.Eq{
|
|
"GroupTeams.GroupId": groupID,
|
|
"GroupTeams.TeamId": syncableID,
|
|
}))
|
|
result = &team
|
|
case model.GroupSyncableTypeChannel:
|
|
var ch groupChannel
|
|
err = s.GetReplica().GetBuilder(&ch, s.groupChannelsSelectQuery.Where(sq.Eq{
|
|
"GroupChannels.GroupId": groupID,
|
|
"GroupChannels.ChannelId": syncableID,
|
|
}))
|
|
result = &ch
|
|
}
|
|
|
|
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, error) {
|
|
groupSyncables := []*model.GroupSyncable{}
|
|
|
|
switch syncableType {
|
|
case model.GroupSyncableTypeTeam:
|
|
query := s.groupTeamsSelectQuery.
|
|
Columns("Teams.DisplayName AS TeamDisplayName", "Teams.Type AS TeamType").
|
|
Join("Teams ON Teams.Id = GroupTeams.TeamId").
|
|
Where(sq.Eq{
|
|
"GroupTeams.GroupId": groupID,
|
|
"GroupTeams.DeleteAt": 0,
|
|
})
|
|
|
|
results := []*groupTeamJoin{}
|
|
err := s.GetReplica().SelectBuilder(&results, query)
|
|
if err != nil {
|
|
return nil, errors.Wrapf(err, "failed to find GroupTeams with groupId=%s", groupID)
|
|
}
|
|
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:
|
|
query := s.groupChannelsSelectQuery.
|
|
Columns(
|
|
"Channels.DisplayName AS ChannelDisplayName",
|
|
"Teams.DisplayName AS TeamDisplayName",
|
|
"Channels.Type As ChannelType",
|
|
"Teams.Type As TeamType",
|
|
"Teams.Id AS TeamId",
|
|
).Join("Channels ON Channels.Id = GroupChannels.ChannelId").
|
|
Join("Teams ON Teams.Id = Channels.TeamId").
|
|
Where(sq.Eq{
|
|
"GroupChannels.GroupId": groupID,
|
|
"GroupChannels.DeleteAt": 0,
|
|
})
|
|
|
|
results := []*groupChannelJoin{}
|
|
err := s.GetReplica().SelectBuilder(&results, query)
|
|
if err != nil {
|
|
return nil, errors.Wrapf(err, "failed to find GroupChannels with groupId=%s", groupID)
|
|
}
|
|
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, error) {
|
|
retrievedGroupSyncable, err := s.getGroupSyncable(groupSyncable.GroupId, groupSyncable.SyncableId, groupSyncable.Type)
|
|
if err != nil {
|
|
if err == sql.ErrNoRows {
|
|
return nil, errors.Wrap(store.NewErrNotFound("GroupSyncable", fmt.Sprintf("groupId=%s, syncableId=%s, syncableType=%s", groupSyncable.GroupId, groupSyncable.SyncableId, groupSyncable.Type)), "GroupSyncable not found")
|
|
}
|
|
return nil, errors.Wrapf(err, "failed to find GroupSyncable with groupId=%s, syncableId=%s, syncableType=%s", groupSyncable.GroupId, groupSyncable.SyncableId, groupSyncable.Type)
|
|
}
|
|
|
|
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, errors.New("DeleteAt should be 0 when updating")
|
|
}
|
|
|
|
// 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().NamedExec(`UPDATE GroupTeams
|
|
SET AutoAdd=:AutoAdd, SchemeAdmin=:SchemeAdmin, CreateAt=:CreateAt,
|
|
DeleteAt=:DeleteAt, UpdateAt=:UpdateAt
|
|
WHERE GroupId=:GroupId AND TeamId=:TeamId`, groupSyncableToGroupTeam(groupSyncable))
|
|
case model.GroupSyncableTypeChannel:
|
|
// We need to get the TeamId so redux can manage channels when teams are unlinked
|
|
var channel *model.Channel
|
|
channel, channelErr := s.Channel().Get(groupSyncable.SyncableId, false)
|
|
if channelErr != nil {
|
|
return nil, channelErr
|
|
}
|
|
|
|
_, err = s.GetMaster().NamedExec(`UPDATE GroupChannels
|
|
SET AutoAdd=:AutoAdd, SchemeAdmin=:SchemeAdmin, CreateAt=:CreateAt,
|
|
DeleteAt=:DeleteAt, UpdateAt=:UpdateAt
|
|
WHERE GroupId=:GroupId AND ChannelId=:ChannelId`, groupSyncableToGroupChannel(groupSyncable))
|
|
|
|
groupSyncable.TeamID = channel.TeamId
|
|
default:
|
|
return nil, fmt.Errorf("invalid GroupSyncableType: %s", groupSyncable.Type)
|
|
}
|
|
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "failed to update GroupSyncable")
|
|
}
|
|
|
|
return groupSyncable, nil
|
|
}
|
|
|
|
func (s *SqlGroupStore) DeleteGroupSyncable(groupID string, syncableID string, syncableType model.GroupSyncableType) (*model.GroupSyncable, error) {
|
|
groupSyncable, err := s.getGroupSyncable(groupID, syncableID, syncableType)
|
|
if err != nil {
|
|
if err == sql.ErrNoRows {
|
|
return nil, store.NewErrNotFound("GroupSyncable", fmt.Sprintf("groupId=%s, syncableId=%s, syncableType=%s", groupID, syncableID, syncableType))
|
|
}
|
|
return nil, errors.Wrapf(err, "failed to find GroupSyncable with groupId=%s, syncableId=%s, syncableType=%s", groupID, syncableID, syncableType)
|
|
}
|
|
|
|
if groupSyncable.DeleteAt != 0 {
|
|
return nil, store.NewErrInvalidInput("GroupSyncable", "<groupId, syncableId, syncableType>", fmt.Sprintf("<%s, %s, %s>", groupSyncable.GroupId, groupSyncable.SyncableId, groupSyncable.Type))
|
|
}
|
|
|
|
time := model.GetMillis()
|
|
groupSyncable.DeleteAt = time
|
|
groupSyncable.UpdateAt = time
|
|
|
|
switch groupSyncable.Type {
|
|
case model.GroupSyncableTypeTeam:
|
|
_, err = s.GetMaster().NamedExec(`UPDATE GroupTeams
|
|
SET AutoAdd=:AutoAdd, SchemeAdmin=:SchemeAdmin, CreateAt=:CreateAt,
|
|
DeleteAt=:DeleteAt, UpdateAt=:UpdateAt
|
|
WHERE GroupId=:GroupId AND TeamId=:TeamId`, groupSyncableToGroupTeam(groupSyncable))
|
|
case model.GroupSyncableTypeChannel:
|
|
_, err = s.GetMaster().NamedExec(`UPDATE GroupChannels
|
|
SET AutoAdd=:AutoAdd, SchemeAdmin=:SchemeAdmin, CreateAt=:CreateAt,
|
|
DeleteAt=:DeleteAt, UpdateAt=:UpdateAt
|
|
WHERE GroupId=:GroupId AND ChannelId=:ChannelId`, groupSyncableToGroupChannel(groupSyncable))
|
|
default:
|
|
return nil, fmt.Errorf("invalid GroupSyncableType: %s", groupSyncable.Type)
|
|
}
|
|
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "failed to update GroupSyncable")
|
|
}
|
|
|
|
return groupSyncable, nil
|
|
}
|
|
|
|
func (s *SqlGroupStore) TeamMembersToAdd(since int64, teamID *string, reAddRemovedMembers bool) ([]*model.UserTeamIDPair, error) {
|
|
builder := s.getQueryBuilder().Select("GroupMembers.UserId UserID", "GroupTeams.TeamId TeamID").
|
|
From("GroupMembers").
|
|
Join("GroupTeams ON GroupTeams.GroupId = GroupMembers.GroupId").
|
|
Join("UserGroups ON UserGroups.Id = GroupMembers.GroupId").
|
|
Join("Teams ON Teams.Id = GroupTeams.TeamId").
|
|
Where(sq.Eq{
|
|
"UserGroups.DeleteAt": 0,
|
|
"GroupTeams.DeleteAt": 0,
|
|
"GroupTeams.AutoAdd": true,
|
|
"GroupMembers.DeleteAt": 0,
|
|
"Teams.DeleteAt": 0,
|
|
})
|
|
|
|
if !reAddRemovedMembers {
|
|
builder = builder.
|
|
JoinClause("LEFT OUTER JOIN TeamMembers ON TeamMembers.TeamId = GroupTeams.TeamId AND TeamMembers.UserId = GroupMembers.UserId").
|
|
Where(sq.Eq{"TeamMembers.UserId": nil}).
|
|
Where(sq.Or{
|
|
sq.GtOrEq{"GroupMembers.CreateAt": since},
|
|
sq.GtOrEq{"GroupTeams.UpdateAt": since},
|
|
})
|
|
}
|
|
if teamID != nil {
|
|
builder = builder.Where(sq.Eq{"Teams.Id": *teamID})
|
|
}
|
|
|
|
teamMembers := []*model.UserTeamIDPair{}
|
|
|
|
if err := s.GetMaster().SelectBuilder(&teamMembers, builder); err != nil {
|
|
return nil, errors.Wrap(err, "failed to find UserTeamIDPairs")
|
|
}
|
|
|
|
return teamMembers, nil
|
|
}
|
|
|
|
func (s *SqlGroupStore) ChannelMembersToAdd(since int64, channelID *string, reAddRemovedMembers bool) ([]*model.UserChannelIDPair, error) {
|
|
builder := s.getQueryBuilder().Select("GroupMembers.UserId UserID", "GroupChannels.ChannelId ChannelID").
|
|
From("GroupMembers").
|
|
Join("GroupChannels ON GroupChannels.GroupId = GroupMembers.GroupId").
|
|
Join("UserGroups ON UserGroups.Id = GroupMembers.GroupId").
|
|
Join("Channels ON Channels.Id = GroupChannels.ChannelId").
|
|
Where(sq.Eq{
|
|
"UserGroups.DeleteAt": 0,
|
|
"GroupChannels.DeleteAt": 0,
|
|
"GroupChannels.AutoAdd": true,
|
|
"GroupMembers.DeleteAt": 0,
|
|
"Channels.DeleteAt": 0,
|
|
})
|
|
|
|
if !reAddRemovedMembers {
|
|
builder = builder.
|
|
JoinClause("LEFT OUTER JOIN ChannelMemberHistory ON ChannelMemberHistory.ChannelId = GroupChannels.ChannelId AND ChannelMemberHistory.UserId = GroupMembers.UserId").
|
|
Where(sq.Eq{
|
|
"ChannelMemberHistory.UserId": nil,
|
|
"ChannelMemberHistory.LeaveTime": nil,
|
|
}).
|
|
Where(sq.Or{
|
|
sq.GtOrEq{"GroupMembers.CreateAt": since},
|
|
sq.GtOrEq{"GroupChannels.UpdateAt": since},
|
|
})
|
|
}
|
|
if channelID != nil {
|
|
builder = builder.Where(sq.Eq{"Channels.Id": *channelID})
|
|
}
|
|
|
|
channelMembers := []*model.UserChannelIDPair{}
|
|
|
|
if err := s.GetMaster().SelectBuilder(&channelMembers, builder); err != nil {
|
|
return nil, errors.Wrap(err, "failed to find UserChannelIDPairs")
|
|
}
|
|
|
|
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, error) {
|
|
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)`
|
|
|
|
builder := 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 {
|
|
builder = builder.Where(sq.Eq{"TeamMembers.TeamId": *teamID})
|
|
}
|
|
|
|
teamMembers := []*model.TeamMember{}
|
|
|
|
if err := s.GetReplica().SelectBuilder(&teamMembers, builder); err != nil {
|
|
return nil, errors.Wrap(err, "failed to find TeamMembers")
|
|
}
|
|
|
|
return teamMembers, nil
|
|
}
|
|
|
|
func (s *SqlGroupStore) CountGroupsByChannel(channelId string, opts model.GroupSearchOpts) (int64, error) {
|
|
builder := s.groupsBySyncableBaseQuery(model.GroupSyncableTypeChannel, selectCountGroups, channelId, opts)
|
|
|
|
var count int64
|
|
if err := s.GetReplica().GetBuilder(&count, builder); err != nil {
|
|
return int64(0), errors.Wrapf(err, "failed to count Groups by channel with channelId=%s", channelId)
|
|
}
|
|
|
|
return count, nil
|
|
}
|
|
|
|
type group struct {
|
|
Id string
|
|
Name *string
|
|
DisplayName string
|
|
Description string
|
|
Source model.GroupSource
|
|
RemoteId *string
|
|
CreateAt int64
|
|
UpdateAt int64
|
|
DeleteAt int64
|
|
HasSyncables bool
|
|
MemberCount *int
|
|
AllowReference bool
|
|
ChannelMemberCount *int
|
|
ChannelMemberTimezonesCount *int
|
|
}
|
|
|
|
func (g group) ToModel() *model.Group {
|
|
return &model.Group{
|
|
Id: g.Id,
|
|
Name: g.Name,
|
|
DisplayName: g.DisplayName,
|
|
Description: g.Description,
|
|
Source: g.Source,
|
|
RemoteId: g.RemoteId,
|
|
CreateAt: g.CreateAt,
|
|
UpdateAt: g.UpdateAt,
|
|
DeleteAt: g.DeleteAt,
|
|
HasSyncables: g.HasSyncables,
|
|
AllowReference: g.AllowReference,
|
|
MemberCount: g.MemberCount,
|
|
ChannelMemberCount: g.ChannelMemberCount,
|
|
ChannelMemberTimezonesCount: g.ChannelMemberTimezonesCount,
|
|
}
|
|
}
|
|
|
|
type groups []*group
|
|
|
|
func (groups groups) ToModel() []*model.Group {
|
|
res := make([]*model.Group, 0, len(groups))
|
|
for _, g := range groups {
|
|
res = append(res, g.ToModel())
|
|
}
|
|
return res
|
|
}
|
|
|
|
type groupWithSchemeAdmin struct {
|
|
group
|
|
SyncableSchemeAdmin *bool
|
|
}
|
|
|
|
func (g groupWithSchemeAdmin) ToModel() *model.GroupWithSchemeAdmin {
|
|
if g.SyncableSchemeAdmin == nil {
|
|
g.SyncableSchemeAdmin = model.NewPointer(false)
|
|
}
|
|
res := &model.GroupWithSchemeAdmin{
|
|
Group: *g.group.ToModel(),
|
|
SchemeAdmin: g.SyncableSchemeAdmin,
|
|
}
|
|
return res
|
|
}
|
|
|
|
type groupsWithSchemeAdmin []*groupWithSchemeAdmin
|
|
|
|
func (groups groupsWithSchemeAdmin) ToModel() []*model.GroupWithSchemeAdmin {
|
|
res := make([]*model.GroupWithSchemeAdmin, 0, len(groups))
|
|
for _, g := range groups {
|
|
res = append(res, g.ToModel())
|
|
}
|
|
return res
|
|
}
|
|
|
|
type groupAssociatedToChannelWithSchemeAdmin struct {
|
|
groupWithSchemeAdmin
|
|
ChannelId string
|
|
}
|
|
|
|
func (g groupAssociatedToChannelWithSchemeAdmin) ToModel() *model.GroupsAssociatedToChannelWithSchemeAdmin {
|
|
withSchemeAdmin := g.groupWithSchemeAdmin.ToModel()
|
|
return &model.GroupsAssociatedToChannelWithSchemeAdmin{
|
|
ChannelId: g.ChannelId,
|
|
SchemeAdmin: withSchemeAdmin.SchemeAdmin,
|
|
Group: withSchemeAdmin.Group,
|
|
}
|
|
}
|
|
|
|
type groupsAssociatedToChannelWithSchemeAdmin []groupAssociatedToChannelWithSchemeAdmin
|
|
|
|
func (groups groupsAssociatedToChannelWithSchemeAdmin) ToModel() []*model.GroupsAssociatedToChannelWithSchemeAdmin {
|
|
res := make([]*model.GroupsAssociatedToChannelWithSchemeAdmin, 0, len(groups))
|
|
for _, g := range groups {
|
|
res = append(res, g.ToModel())
|
|
}
|
|
return res
|
|
}
|
|
|
|
func (s *SqlGroupStore) GetGroupsByChannel(channelId string, opts model.GroupSearchOpts) ([]*model.GroupWithSchemeAdmin, error) {
|
|
builder := s.groupsBySyncableBaseQuery(model.GroupSyncableTypeChannel, selectGroups, channelId, opts)
|
|
|
|
if opts.PageOpts != nil {
|
|
offset := uint64(opts.PageOpts.Page * opts.PageOpts.PerPage)
|
|
builder = builder.OrderBy("UserGroups.DisplayName").Limit(uint64(opts.PageOpts.PerPage)).Offset(offset)
|
|
}
|
|
|
|
groups := groupsWithSchemeAdmin{}
|
|
if err := s.GetReplica().SelectBuilder(&groups, builder); err != nil {
|
|
return nil, errors.Wrapf(err, "failed to find Groups with channelId=%s", channelId)
|
|
}
|
|
|
|
return groups.ToModel(), nil
|
|
}
|
|
|
|
func (s *SqlGroupStore) ChannelMembersToRemove(channelID *string) ([]*model.ChannelMember, error) {
|
|
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)`
|
|
|
|
builder := s.getQueryBuilder().Select(
|
|
"ChannelMembers.ChannelId",
|
|
"ChannelMembers.UserId",
|
|
"ChannelMembers.LastViewedAt",
|
|
"ChannelMembers.MsgCount",
|
|
"ChannelMembers.MsgCountRoot",
|
|
"ChannelMembers.MentionCount",
|
|
"ChannelMembers.MentionCountRoot",
|
|
"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 {
|
|
builder = builder.Where(sq.Eq{"ChannelMembers.ChannelId": *channelID})
|
|
}
|
|
|
|
channelMembers := []*model.ChannelMember{}
|
|
|
|
if err := s.GetReplica().SelectBuilder(&channelMembers, builder); err != nil {
|
|
return nil, errors.Wrap(err, "failed to find ChannelMembers")
|
|
}
|
|
|
|
return channelMembers, nil
|
|
}
|
|
|
|
func (s *SqlGroupStore) groupsBySyncableBaseQuery(st model.GroupSyncableType, t selectType, syncableID string, opts model.GroupSearchOpts) sq.SelectBuilder {
|
|
var query sq.SelectBuilder
|
|
switch t {
|
|
case selectGroups:
|
|
query = s.userGroupsSelectQuery.
|
|
Column("gs.SchemeAdmin AS SyncableSchemeAdmin")
|
|
case selectCountGroups:
|
|
query = s.getQueryBuilder().
|
|
Select("COUNT(*)").
|
|
From("UserGroups")
|
|
}
|
|
|
|
if st == model.GroupSyncableTypeTeam {
|
|
query = query.
|
|
Join("GroupTeams gs ON gs.GroupId = UserGroups.Id").
|
|
Where(sq.Eq{
|
|
"gs.TeamId": syncableID,
|
|
"gs.DeleteAt": 0,
|
|
})
|
|
} else {
|
|
query = query.
|
|
Join("GroupChannels gs ON gs.GroupId = UserGroups.Id").
|
|
Where(sq.Eq{
|
|
"gs.ChannelId": syncableID,
|
|
"gs.DeleteAt": 0,
|
|
})
|
|
}
|
|
|
|
query = query.
|
|
Where(sq.Eq{
|
|
"UserGroups.DeleteAt": 0,
|
|
})
|
|
|
|
if opts.IncludeMemberCount && t == selectGroups {
|
|
query = query.
|
|
Column("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 = UserGroups.Id").
|
|
OrderBy("UserGroups.DisplayName")
|
|
}
|
|
|
|
if opts.FilterAllowReference && t == selectGroups {
|
|
query = query.Where("UserGroups.AllowReference = true")
|
|
}
|
|
|
|
if opts.Q != "" {
|
|
pattern := fmt.Sprintf("%%%s%%", sanitizeSearchTerm(opts.Q, "\\"))
|
|
operatorKeyword := "ILIKE"
|
|
query = query.Where(fmt.Sprintf("(UserGroups.Name %[1]s ? OR UserGroups.DisplayName %[1]s ?)", operatorKeyword), pattern, pattern)
|
|
}
|
|
|
|
return query
|
|
}
|
|
|
|
func (s *SqlGroupStore) getGroupsAssociatedToChannelsByTeam(teamID string, opts model.GroupSearchOpts) sq.SelectBuilder {
|
|
query := s.userGroupsSelectQuery.
|
|
Columns("gc.ChannelId", "gc.SchemeAdmin AS SyncableSchemeAdmin").
|
|
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 = UserGroups.Id`, teamID).
|
|
Where("UserGroups.DeleteAt = 0 AND gc.DeleteAt = 0").
|
|
OrderBy("UserGroups.DisplayName")
|
|
|
|
if opts.IncludeMemberCount {
|
|
query = s.userGroupsSelectQuery.
|
|
Columns("gc.ChannelId", "coalesce(Members.MemberCount, 0) AS MemberCount", "gc.SchemeAdmin AS SyncableSchemeAdmin").
|
|
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 = UserGroups.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 = UserGroups.Id`).
|
|
Where("UserGroups.DeleteAt = 0 AND gc.DeleteAt = 0").
|
|
OrderBy("UserGroups.DisplayName")
|
|
}
|
|
|
|
if opts.FilterAllowReference {
|
|
query = query.Where("UserGroups.AllowReference = true")
|
|
}
|
|
|
|
if opts.Q != "" {
|
|
pattern := fmt.Sprintf("%%%s%%", sanitizeSearchTerm(opts.Q, "\\"))
|
|
operatorKeyword := "ILIKE"
|
|
query = query.Where(fmt.Sprintf("(UserGroups.Name %[1]s ? OR UserGroups.DisplayName %[1]s ?)", operatorKeyword), pattern, pattern)
|
|
}
|
|
|
|
return query
|
|
}
|
|
|
|
func (s *SqlGroupStore) CountGroupsByTeam(teamId string, opts model.GroupSearchOpts) (int64, error) {
|
|
builder := s.groupsBySyncableBaseQuery(model.GroupSyncableTypeTeam, selectCountGroups, teamId, opts)
|
|
|
|
var count int64
|
|
if err := s.GetReplica().GetBuilder(&count, builder); err != nil {
|
|
return int64(0), errors.Wrapf(err, "failed to count Groups with teamId=%s", teamId)
|
|
}
|
|
|
|
return count, nil
|
|
}
|
|
|
|
func (s *SqlGroupStore) GetGroupsByTeam(teamId string, opts model.GroupSearchOpts) ([]*model.GroupWithSchemeAdmin, error) {
|
|
builder := s.groupsBySyncableBaseQuery(model.GroupSyncableTypeTeam, selectGroups, teamId, opts)
|
|
|
|
if opts.PageOpts != nil {
|
|
offset := uint64(opts.PageOpts.Page * opts.PageOpts.PerPage)
|
|
builder = builder.OrderBy("UserGroups.DisplayName").Limit(uint64(opts.PageOpts.PerPage)).Offset(offset)
|
|
}
|
|
|
|
groups := groupsWithSchemeAdmin{}
|
|
if err := s.GetReplica().SelectBuilder(&groups, builder); err != nil {
|
|
return nil, errors.Wrapf(err, "failed to find Groups with teamId=%s", teamId)
|
|
}
|
|
|
|
return groups.ToModel(), nil
|
|
}
|
|
|
|
func (s *SqlGroupStore) GetGroupsAssociatedToChannelsByTeam(teamId string, opts model.GroupSearchOpts) (map[string][]*model.GroupWithSchemeAdmin, error) {
|
|
builder := s.getGroupsAssociatedToChannelsByTeam(teamId, opts)
|
|
|
|
if opts.PageOpts != nil {
|
|
offset := uint64(opts.PageOpts.Page * opts.PageOpts.PerPage)
|
|
builder = builder.OrderBy("UserGroups.DisplayName").Limit(uint64(opts.PageOpts.PerPage)).Offset(offset)
|
|
}
|
|
|
|
tgroups := groupsAssociatedToChannelWithSchemeAdmin{}
|
|
|
|
if err := s.GetReplica().SelectBuilder(&tgroups, builder); err != nil {
|
|
return nil, errors.Wrapf(err, "failed to find Groups with teamId=%s", teamId)
|
|
}
|
|
|
|
groups := map[string][]*model.GroupWithSchemeAdmin{}
|
|
for _, tgroup := range tgroups {
|
|
group := tgroup.groupWithSchemeAdmin.ToModel()
|
|
groups[tgroup.ChannelId] = append(groups[tgroup.ChannelId], group)
|
|
}
|
|
|
|
return groups, nil
|
|
}
|
|
|
|
func (s *SqlGroupStore) GetGroups(page, perPage int, opts model.GroupSearchOpts, viewRestrictions *model.ViewUsersRestrictions) ([]*model.Group, error) {
|
|
groupsVar := groups{}
|
|
|
|
groupsQuery := s.userGroupsSelectQuery
|
|
|
|
if opts.IncludeMemberCount {
|
|
groupsQuery = groupsQuery.Column("coalesce(Members.MemberCount, 0) AS MemberCount")
|
|
}
|
|
|
|
if opts.IncludeChannelMemberCount != "" {
|
|
groupsQuery = groupsQuery.Column("coalesce(ChannelMembers.ChannelMemberCount, 0) AS ChannelMemberCount")
|
|
if opts.IncludeTimezones {
|
|
groupsQuery = groupsQuery.Column("coalesce(ChannelMembers.ChannelMemberTimezonesCount, 0) AS ChannelMemberTimezonesCount")
|
|
}
|
|
}
|
|
|
|
if opts.IncludeMemberCount {
|
|
countQuery := s.getQueryBuilder().
|
|
Select("GroupMembers.GroupId, COUNT(DISTINCT Users.Id) AS MemberCount").
|
|
From("GroupMembers").
|
|
LeftJoin("Users ON Users.Id = GroupMembers.UserId").
|
|
Where(sq.Eq{"GroupMembers.DeleteAt": 0}).
|
|
Where(sq.Eq{"Users.DeleteAt": 0}).
|
|
GroupBy("GroupId")
|
|
|
|
countQuery = applyViewRestrictionsFilter(countQuery, viewRestrictions, false)
|
|
|
|
countString, params, err := countQuery.PlaceholderFormat(sq.Question).ToSql()
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "get_groups_tosql")
|
|
}
|
|
groupsQuery = groupsQuery.
|
|
LeftJoin("("+countString+") AS Members ON Members.GroupId = UserGroups.Id", params...)
|
|
}
|
|
|
|
if opts.IncludeChannelMemberCount != "" {
|
|
selectStr := "GroupMembers.GroupId, COUNT(ChannelMembers.UserId) AS ChannelMemberCount"
|
|
joinStr := ""
|
|
|
|
if opts.IncludeTimezones {
|
|
selectStr += `,
|
|
COUNT(DISTINCT
|
|
(
|
|
CASE WHEN Timezone->>'useAutomaticTimezone' = 'true' AND length(Timezone->>'automaticTimezone') > 0
|
|
THEN Timezone->>'automaticTimezone'
|
|
WHEN Timezone->>'useAutomaticTimezone' = 'false' AND length(Timezone->>'manualTimezone') > 0
|
|
THEN Timezone->>'manualTimezone'
|
|
END
|
|
)) AS ChannelMemberTimezonesCount`
|
|
joinStr = "LEFT JOIN Users ON Users.Id = GroupMembers.UserId"
|
|
}
|
|
|
|
groupsQuery = groupsQuery.
|
|
LeftJoin("(SELECT "+selectStr+" FROM ChannelMembers LEFT JOIN GroupMembers ON GroupMembers.UserId = ChannelMembers.UserId AND GroupMembers.DeleteAt = 0 "+joinStr+" WHERE ChannelMembers.ChannelId = ? GROUP BY GroupId) AS ChannelMembers ON ChannelMembers.GroupId = UserGroups.Id", opts.IncludeChannelMemberCount)
|
|
}
|
|
|
|
if opts.FilterHasMember != "" {
|
|
groupsQuery = groupsQuery.
|
|
LeftJoin("GroupMembers ON GroupMembers.GroupId = UserGroups.Id").
|
|
Where("GroupMembers.UserId = ?", opts.FilterHasMember).
|
|
Where("GroupMembers.DeleteAt = 0")
|
|
}
|
|
|
|
if opts.Since > 0 {
|
|
groupsQuery = groupsQuery.Where(sq.Gt{
|
|
"UserGroups.UpdateAt": opts.Since,
|
|
})
|
|
}
|
|
|
|
if opts.FilterArchived {
|
|
groupsQuery = groupsQuery.Where("UserGroups.DeleteAt > 0")
|
|
} else if !opts.IncludeArchived && opts.Since <= 0 {
|
|
// Mobile needs to return archived groups when the since parameter is set, will need to keep this for backwards compatibility
|
|
groupsQuery = groupsQuery.Where("UserGroups.DeleteAt = 0")
|
|
}
|
|
|
|
if opts.IncludeArchived {
|
|
groupsQuery = groupsQuery.OrderBy("CASE WHEN UserGroups.DeleteAt = 0 THEN UserGroups.DisplayName end, CASE WHEN UserGroups.DeleteAt != 0 THEN UserGroups.DisplayName END")
|
|
} else {
|
|
groupsQuery = groupsQuery.OrderBy("UserGroups.DisplayName")
|
|
}
|
|
|
|
if perPage != 0 {
|
|
groupsQuery = groupsQuery.
|
|
Limit(uint64(perPage)).
|
|
Offset(uint64(page * perPage))
|
|
}
|
|
|
|
if opts.FilterAllowReference {
|
|
groupsQuery = groupsQuery.Where("UserGroups.AllowReference = true")
|
|
}
|
|
|
|
if opts.Q != "" {
|
|
pattern := fmt.Sprintf("%%%s%%", sanitizeSearchTerm(opts.Q, "\\"))
|
|
operatorKeyword := "ILIKE"
|
|
groupsQuery = groupsQuery.Where(fmt.Sprintf("(UserGroups.Name %[1]s ? OR UserGroups.DisplayName %[1]s ?)", operatorKeyword), pattern, pattern)
|
|
}
|
|
|
|
if len(opts.NotAssociatedToTeam) == 26 {
|
|
groupsQuery = groupsQuery.Where(`
|
|
UserGroups.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(`
|
|
UserGroups.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 UserGroups.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)
|
|
}
|
|
|
|
if opts.Source != "" {
|
|
groupsQuery = groupsQuery.Where("UserGroups.Source = ?", opts.Source)
|
|
} else if opts.OnlySyncableSources {
|
|
sources := model.GetSyncableGroupSources()
|
|
sourcePrefixes := model.GetSyncableGroupSourcePrefixes()
|
|
|
|
orClauses := sq.Or{}
|
|
if len(sources) > 0 {
|
|
orClauses = append(orClauses, sq.Eq{"UserGroups.Source": sources})
|
|
}
|
|
for _, prefix := range sourcePrefixes {
|
|
orClauses = append(orClauses, sq.Like{"UserGroups.Source": string(prefix) + "%"})
|
|
}
|
|
groupsQuery = groupsQuery.Where(orClauses)
|
|
}
|
|
|
|
if err := s.GetReplica().SelectBuilder(&groupsVar, groupsQuery); err != nil {
|
|
return nil, errors.Wrap(err, "failed to find Groups")
|
|
}
|
|
|
|
return groupsVar.ToModel(), nil
|
|
}
|
|
|
|
func (s *SqlGroupStore) teamMembersMinusGroupMembersQuery(teamID string, groupIDs []string, isCount bool) sq.SelectBuilder {
|
|
var builder sq.SelectBuilder
|
|
|
|
if isCount {
|
|
builder = s.getQueryBuilder().Select("count(DISTINCT Users.Id)")
|
|
} else {
|
|
builder = s.getQueryBuilder().Select().
|
|
Columns(getUsersColumns()...).
|
|
Column("coalesce(TeamMembers.SchemeGuest, false) SchemeGuest").
|
|
Column("TeamMembers.SchemeAdmin").
|
|
Column("TeamMembers.SchemeUser")
|
|
|
|
builder = builder.Column("string_agg(UserGroups.Id, ',') AS GroupIDs")
|
|
}
|
|
|
|
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, "', '")))
|
|
|
|
query, _ := subQuery.MustSql()
|
|
|
|
builder = builder.
|
|
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)", query))
|
|
|
|
if !isCount {
|
|
builder = builder.GroupBy("Users.Id, TeamMembers.SchemeGuest, TeamMembers.SchemeAdmin, TeamMembers.SchemeUser")
|
|
}
|
|
|
|
return builder
|
|
}
|
|
|
|
// 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, error) {
|
|
builder := s.teamMembersMinusGroupMembersQuery(teamID, groupIDs, false)
|
|
builder = builder.OrderBy("Users.Username ASC").Limit(uint64(perPage)).Offset(uint64(page * perPage))
|
|
|
|
users := []*model.UserWithGroups{}
|
|
if err := s.GetReplica().SelectBuilder(&users, builder); err != nil {
|
|
return nil, errors.Wrap(err, "failed to find UserWithGroups")
|
|
}
|
|
|
|
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, error) {
|
|
queryString, args, err := s.teamMembersMinusGroupMembersQuery(teamID, groupIDs, true).ToSql()
|
|
if err != nil {
|
|
return 0, errors.Wrap(err, "count_team_members_minus_group_members_tosql")
|
|
}
|
|
|
|
var count int64
|
|
if err := s.GetReplica().Get(&count, queryString, args...); err != nil {
|
|
return 0, errors.Wrap(err, "failed to count TeamMembers minus GroupMembers")
|
|
}
|
|
|
|
return count, nil
|
|
}
|
|
|
|
func (s *SqlGroupStore) channelMembersMinusGroupMembersQuery(channelID string, groupIDs []string, isCount bool) sq.SelectBuilder {
|
|
builder := s.getQueryBuilder().Select()
|
|
|
|
if isCount {
|
|
builder = builder.Column("count(DISTINCT Users.Id)")
|
|
} else {
|
|
builder = builder.Columns(getUsersColumns()...)
|
|
builder = builder.Columns(
|
|
"COALESCE(ChannelMembers.SchemeGuest, FALSE) SchemeGuest",
|
|
"ChannelMembers.SchemeAdmin",
|
|
"ChannelMembers.SchemeUser",
|
|
)
|
|
|
|
builder = builder.Column("string_agg(UserGroups.Id, ',') AS GroupIDs")
|
|
}
|
|
|
|
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, "', '")))
|
|
|
|
query, _ := subQuery.MustSql()
|
|
|
|
builder = builder.
|
|
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)", query))
|
|
|
|
if !isCount {
|
|
builder = builder.GroupBy("Users.Id, ChannelMembers.SchemeGuest, ChannelMembers.SchemeAdmin, ChannelMembers.SchemeUser")
|
|
}
|
|
|
|
return builder
|
|
}
|
|
|
|
// 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, error) {
|
|
builder := s.channelMembersMinusGroupMembersQuery(channelID, groupIDs, false)
|
|
builder = builder.OrderBy("Users.Username ASC").Limit(uint64(perPage)).Offset(uint64(page * perPage))
|
|
|
|
users := []*model.UserWithGroups{}
|
|
if err := s.GetReplica().SelectBuilder(&users, builder); err != nil {
|
|
return nil, errors.Wrap(err, "failed to find UserWithGroups")
|
|
}
|
|
|
|
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, error) {
|
|
queryString, args, err := s.channelMembersMinusGroupMembersQuery(channelID, groupIDs, true).ToSql()
|
|
if err != nil {
|
|
return 0, errors.Wrap(err, "count_channel_members_minus_group_members_tosql")
|
|
}
|
|
|
|
var count int64
|
|
if err := s.GetReplica().Get(&count, queryString, args...); err != nil {
|
|
return 0, errors.Wrap(err, "failed to count ChannelMembers")
|
|
}
|
|
|
|
return count, nil
|
|
}
|
|
|
|
func (s *SqlGroupStore) AdminRoleGroupsForSyncableMember(userID, syncableID string, syncableType model.GroupSyncableType) ([]string, error) {
|
|
var groupIds []string
|
|
var joinTable string
|
|
var syncableIdCol string
|
|
|
|
switch syncableType {
|
|
case model.GroupSyncableTypeChannel:
|
|
joinTable = "GroupChannels"
|
|
syncableIdCol = "ChannelId"
|
|
case model.GroupSyncableTypeTeam:
|
|
joinTable = "GroupTeams"
|
|
syncableIdCol = "TeamId"
|
|
default:
|
|
return nil, errors.New("invalid syncable type")
|
|
}
|
|
|
|
query := s.getQueryBuilder().
|
|
Select("GroupMembers.GroupId").
|
|
From("GroupMembers").
|
|
InnerJoin(joinTable + " AS joinGroup ON joinGroup.GroupId = GroupMembers.GroupId").
|
|
Where(sq.Eq{
|
|
"GroupMembers.UserId": userID,
|
|
"GroupMembers.DeleteAt": 0,
|
|
"joinGroup." + syncableIdCol: syncableID,
|
|
"joinGroup.DeleteAt": 0,
|
|
"joinGroup.SchemeAdmin": true,
|
|
})
|
|
|
|
if err := s.GetReplica().SelectBuilder(&groupIds, query); err != nil {
|
|
return nil, errors.Wrap(err, "failed to find Group ids")
|
|
}
|
|
|
|
return groupIds, nil
|
|
}
|
|
|
|
func (s *SqlGroupStore) PermittedSyncableAdmins(syncableID string, syncableType model.GroupSyncableType) ([]string, error) {
|
|
builder := 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)
|
|
|
|
var userIDs []string
|
|
if err := s.GetMaster().SelectBuilder(&userIDs, builder); err != nil {
|
|
return nil, errors.Wrapf(err, "failed to find User ids")
|
|
}
|
|
|
|
return userIDs, nil
|
|
}
|
|
|
|
func (s *SqlGroupStore) GroupCount() (int64, error) {
|
|
return s.countTable("UserGroups")
|
|
}
|
|
|
|
func (s *SqlGroupStore) GroupCountBySource(source model.GroupSource) (int64, error) {
|
|
return s.countTableWithSelectAndWhere("COUNT(*)", "UserGroups", sq.Eq{"Source": source, "DeleteAt": 0})
|
|
}
|
|
|
|
func (s *SqlGroupStore) GroupTeamCount() (int64, error) {
|
|
return s.countTable("GroupTeams")
|
|
}
|
|
|
|
func (s *SqlGroupStore) GroupChannelCount() (int64, error) {
|
|
return s.countTable("GroupChannels")
|
|
}
|
|
|
|
func (s *SqlGroupStore) GroupMemberCount() (int64, error) {
|
|
return s.countTable("GroupMembers")
|
|
}
|
|
|
|
func (s *SqlGroupStore) DistinctGroupMemberCount() (int64, error) {
|
|
return s.countTableWithSelectAndWhere("COUNT(DISTINCT UserId)", "GroupMembers", nil)
|
|
}
|
|
|
|
func (s *SqlGroupStore) DistinctGroupMemberCountForSource(source model.GroupSource) (int64, error) {
|
|
builder := s.getQueryBuilder().
|
|
Select("COUNT(DISTINCT GroupMembers.UserId)").
|
|
From("GroupMembers").
|
|
Join("UserGroups ON GroupMembers.GroupId = UserGroups.Id").
|
|
Where(sq.Eq{"UserGroups.Source": source, "GroupMembers.DeleteAt": 0})
|
|
|
|
var count int64
|
|
if err := s.GetReplica().GetBuilder(&count, builder); err != nil {
|
|
return 0, errors.Wrapf(err, "failed to select distinct groupmember count for source %q", source)
|
|
}
|
|
|
|
return count, nil
|
|
}
|
|
|
|
func (s *SqlGroupStore) GroupCountWithAllowReference() (int64, error) {
|
|
return s.countTableWithSelectAndWhere("COUNT(*)", "UserGroups", sq.Eq{"AllowReference": true, "DeleteAt": 0})
|
|
}
|
|
|
|
func (s *SqlGroupStore) countTable(tableName string) (int64, error) {
|
|
return s.countTableWithSelectAndWhere("COUNT(*)", tableName, nil)
|
|
}
|
|
|
|
func (s *SqlGroupStore) countTableWithSelectAndWhere(selectStr, tableName string, whereStmt map[string]any) (int64, error) {
|
|
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, errors.Wrap(err, "count_table_with_select_and_where_tosql")
|
|
}
|
|
|
|
var count int64
|
|
err = s.GetReplica().Get(&count, sql, args...)
|
|
if err != nil {
|
|
return 0, errors.Wrapf(err, "failed to count from table %s", tableName)
|
|
}
|
|
|
|
return count, nil
|
|
}
|
|
|
|
func (s *SqlGroupStore) UpsertMembers(groupID string, userIDs []string) ([]*model.GroupMember, error) {
|
|
members, query, err := s.buildUpsertMembersQuery(groupID, userIDs)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if _, err = s.GetMaster().ExecBuilder(query); err != nil {
|
|
return nil, errors.Wrap(err, "failed to save GroupMember")
|
|
}
|
|
|
|
return members, err
|
|
}
|
|
|
|
func (s *SqlGroupStore) buildUpsertMembersQuery(groupID string, userIDs []string) (members []*model.GroupMember, builder sq.InsertBuilder, err error) {
|
|
var retrievedGroup model.Group
|
|
// Check Group exists
|
|
if err = s.GetReplica().GetBuilder(&retrievedGroup, s.userGroupsSelectQuery.Where(sq.Eq{"UserGroups.Id": groupID})); err != nil {
|
|
err = errors.Wrapf(err, "failed to get UserGroup with groupId=%s", groupID)
|
|
return
|
|
}
|
|
|
|
// Check Users exist
|
|
if err = s.checkUsersExist(userIDs); err != nil {
|
|
return
|
|
}
|
|
|
|
builder = s.getQueryBuilder().
|
|
Insert("GroupMembers").
|
|
Columns("GroupId", "UserId", "CreateAt", "DeleteAt")
|
|
|
|
members = make([]*model.GroupMember, 0, len(userIDs))
|
|
createAt := model.GetMillis()
|
|
for _, userId := range userIDs {
|
|
member := &model.GroupMember{
|
|
GroupId: groupID,
|
|
UserId: userId,
|
|
CreateAt: createAt,
|
|
DeleteAt: 0,
|
|
}
|
|
builder = builder.Values(member.GroupId, member.UserId, member.CreateAt, member.DeleteAt)
|
|
members = append(members, member)
|
|
}
|
|
|
|
builder = builder.SuffixExpr(sq.Expr("ON CONFLICT (groupid, userid) DO UPDATE SET CreateAt = ?, DeleteAt = ?", createAt, 0))
|
|
|
|
return
|
|
}
|
|
|
|
func (s *SqlGroupStore) DeleteMembers(groupID string, userIDs []string) ([]*model.GroupMember, error) {
|
|
members, query, err := s.buildDeleteMembersQuery(groupID, userIDs)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if _, err = s.GetMaster().ExecBuilder(query); err != nil {
|
|
return nil, errors.Wrap(err, "failed to delete GroupMembers")
|
|
}
|
|
return members, err
|
|
}
|
|
|
|
func (s *SqlGroupStore) buildDeleteMembersQuery(groupID string, userIDs []string) (members []*model.GroupMember, builder sq.UpdateBuilder, err error) {
|
|
membersSelectQuery := s.groupMembersSelectQuery.
|
|
Where(sq.And{
|
|
sq.Eq{"GroupMembers.GroupId": groupID},
|
|
sq.Eq{"GroupMembers.UserId": userIDs},
|
|
sq.Eq{"GroupMembers.DeleteAt": 0},
|
|
})
|
|
|
|
if err = s.GetReplica().SelectBuilder(&members, membersSelectQuery); err != nil {
|
|
return
|
|
}
|
|
if len(members) != len(userIDs) {
|
|
retrievedRecords := make(map[string]bool)
|
|
for _, member := range members {
|
|
retrievedRecords[member.UserId] = true
|
|
}
|
|
for _, userID := range userIDs {
|
|
if _, ok := retrievedRecords[userID]; !ok {
|
|
err = store.NewErrNotFound("User", userID)
|
|
return
|
|
}
|
|
}
|
|
}
|
|
|
|
deleteAt := model.GetMillis()
|
|
|
|
for _, member := range members {
|
|
member.DeleteAt = deleteAt
|
|
}
|
|
|
|
builder = s.getQueryBuilder().
|
|
Update("GroupMembers").
|
|
Set("DeleteAt", deleteAt).
|
|
Where(sq.And{
|
|
sq.Eq{"GroupId": groupID},
|
|
sq.Eq{"UserId": userIDs},
|
|
})
|
|
|
|
return
|
|
}
|