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>
1761 lines
58 KiB
Go
1761 lines
58 KiB
Go
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
|
// See LICENSE.txt for license information.
|
|
|
|
package sqlstore
|
|
|
|
import (
|
|
"database/sql"
|
|
"fmt"
|
|
"slices"
|
|
"strings"
|
|
|
|
sq "github.com/mattermost/squirrel"
|
|
"github.com/pkg/errors"
|
|
|
|
"github.com/mattermost/mattermost/server/public/model"
|
|
"github.com/mattermost/mattermost/server/public/shared/request"
|
|
"github.com/mattermost/mattermost/server/v8/channels/store"
|
|
"github.com/mattermost/mattermost/server/v8/channels/utils"
|
|
)
|
|
|
|
const (
|
|
TeamMemberExistsError = "store.sql_team.save_member.exists.app_error"
|
|
)
|
|
|
|
type SqlTeamStore struct {
|
|
*SqlStore
|
|
|
|
teamsQuery sq.SelectBuilder
|
|
teamMembersQuery sq.SelectBuilder
|
|
}
|
|
|
|
type teamMember struct {
|
|
TeamId string
|
|
UserId string
|
|
Roles string
|
|
DeleteAt int64
|
|
SchemeUser sql.NullBool
|
|
SchemeAdmin sql.NullBool
|
|
SchemeGuest sql.NullBool
|
|
CreateAt int64
|
|
}
|
|
|
|
func NewTeamMemberFromModel(tm *model.TeamMember) *teamMember {
|
|
return &teamMember{
|
|
TeamId: tm.TeamId,
|
|
UserId: tm.UserId,
|
|
Roles: tm.ExplicitRoles,
|
|
DeleteAt: tm.DeleteAt,
|
|
SchemeGuest: sql.NullBool{Valid: true, Bool: tm.SchemeGuest},
|
|
SchemeUser: sql.NullBool{Valid: true, Bool: tm.SchemeUser},
|
|
SchemeAdmin: sql.NullBool{Valid: true, Bool: tm.SchemeAdmin},
|
|
CreateAt: tm.CreateAt,
|
|
}
|
|
}
|
|
|
|
type teamMemberWithSchemeRoles struct {
|
|
TeamId string
|
|
UserId string
|
|
Roles string
|
|
DeleteAt int64
|
|
SchemeGuest sql.NullBool
|
|
SchemeUser sql.NullBool
|
|
SchemeAdmin sql.NullBool
|
|
TeamSchemeDefaultGuestRole sql.NullString
|
|
TeamSchemeDefaultUserRole sql.NullString
|
|
TeamSchemeDefaultAdminRole sql.NullString
|
|
CreateAt int64
|
|
}
|
|
|
|
type teamMemberWithSchemeRolesList []teamMemberWithSchemeRoles
|
|
|
|
func teamMemberSliceColumns() []string {
|
|
return []string{"TeamId", "UserId", "Roles", "DeleteAt", "SchemeUser", "SchemeAdmin", "SchemeGuest", "CreateAt"}
|
|
}
|
|
|
|
func teamMemberToSlice(member *model.TeamMember) []any {
|
|
resultSlice := []any{}
|
|
resultSlice = append(resultSlice, member.TeamId)
|
|
resultSlice = append(resultSlice, member.UserId)
|
|
resultSlice = append(resultSlice, member.ExplicitRoles)
|
|
resultSlice = append(resultSlice, member.DeleteAt)
|
|
resultSlice = append(resultSlice, member.SchemeUser)
|
|
resultSlice = append(resultSlice, member.SchemeAdmin)
|
|
resultSlice = append(resultSlice, member.SchemeGuest)
|
|
resultSlice = append(resultSlice, member.CreateAt)
|
|
return resultSlice
|
|
}
|
|
|
|
func wildcardSearchTerm(term string) string {
|
|
return strings.ToLower("%" + term + "%")
|
|
}
|
|
|
|
type rolesInfo struct {
|
|
roles []string
|
|
explicitRoles []string
|
|
schemeGuest bool
|
|
schemeUser bool
|
|
schemeAdmin bool
|
|
}
|
|
|
|
func getTeamRoles(schemeGuest, schemeUser, schemeAdmin bool, defaultTeamGuestRole, defaultTeamUserRole, defaultTeamAdminRole string, roles []string) rolesInfo {
|
|
result := rolesInfo{
|
|
roles: []string{},
|
|
explicitRoles: []string{},
|
|
schemeGuest: schemeGuest,
|
|
schemeUser: schemeUser,
|
|
schemeAdmin: schemeAdmin,
|
|
}
|
|
// Identify any scheme derived roles that are in "Roles" field due to not yet being migrated, and exclude
|
|
// them from ExplicitRoles field.
|
|
for _, role := range roles {
|
|
switch role {
|
|
case model.TeamGuestRoleId:
|
|
result.schemeGuest = true
|
|
case model.TeamUserRoleId:
|
|
result.schemeUser = true
|
|
case model.TeamAdminRoleId:
|
|
result.schemeAdmin = true
|
|
default:
|
|
result.explicitRoles = append(result.explicitRoles, role)
|
|
result.roles = append(result.roles, role)
|
|
}
|
|
}
|
|
|
|
// Add any scheme derived roles that are not in the Roles field due to being Implicit from the Scheme, and add
|
|
// them to the Roles field for backwards compatibility reasons.
|
|
var schemeImpliedRoles []string
|
|
if result.schemeGuest {
|
|
if defaultTeamGuestRole != "" {
|
|
schemeImpliedRoles = append(schemeImpliedRoles, defaultTeamGuestRole)
|
|
} else {
|
|
schemeImpliedRoles = append(schemeImpliedRoles, model.TeamGuestRoleId)
|
|
}
|
|
}
|
|
if result.schemeUser {
|
|
if defaultTeamUserRole != "" {
|
|
schemeImpliedRoles = append(schemeImpliedRoles, defaultTeamUserRole)
|
|
} else {
|
|
schemeImpliedRoles = append(schemeImpliedRoles, model.TeamUserRoleId)
|
|
}
|
|
}
|
|
if result.schemeAdmin {
|
|
if defaultTeamAdminRole != "" {
|
|
schemeImpliedRoles = append(schemeImpliedRoles, defaultTeamAdminRole)
|
|
} else {
|
|
schemeImpliedRoles = append(schemeImpliedRoles, model.TeamAdminRoleId)
|
|
}
|
|
}
|
|
for _, impliedRole := range schemeImpliedRoles {
|
|
alreadyThere := false
|
|
for _, role := range result.roles {
|
|
if role == impliedRole {
|
|
alreadyThere = true
|
|
}
|
|
}
|
|
if !alreadyThere {
|
|
result.roles = append(result.roles, impliedRole)
|
|
}
|
|
}
|
|
return result
|
|
}
|
|
|
|
func (db teamMemberWithSchemeRoles) ToModel() *model.TeamMember {
|
|
// Identify any scheme derived roles that are in "Roles" field due to not yet being migrated, and exclude
|
|
// them from ExplicitRoles field.
|
|
schemeGuest := db.SchemeGuest.Valid && db.SchemeGuest.Bool
|
|
schemeUser := db.SchemeUser.Valid && db.SchemeUser.Bool
|
|
schemeAdmin := db.SchemeAdmin.Valid && db.SchemeAdmin.Bool
|
|
|
|
defaultTeamGuestRole := ""
|
|
if db.TeamSchemeDefaultGuestRole.Valid {
|
|
defaultTeamGuestRole = db.TeamSchemeDefaultGuestRole.String
|
|
}
|
|
|
|
defaultTeamUserRole := ""
|
|
if db.TeamSchemeDefaultUserRole.Valid {
|
|
defaultTeamUserRole = db.TeamSchemeDefaultUserRole.String
|
|
}
|
|
|
|
defaultTeamAdminRole := ""
|
|
if db.TeamSchemeDefaultAdminRole.Valid {
|
|
defaultTeamAdminRole = db.TeamSchemeDefaultAdminRole.String
|
|
}
|
|
|
|
rolesResult := getTeamRoles(schemeGuest, schemeUser, schemeAdmin, defaultTeamGuestRole, defaultTeamUserRole, defaultTeamAdminRole, strings.Fields(db.Roles))
|
|
|
|
tm := &model.TeamMember{
|
|
TeamId: db.TeamId,
|
|
UserId: db.UserId,
|
|
Roles: strings.Join(rolesResult.roles, " "),
|
|
DeleteAt: db.DeleteAt,
|
|
SchemeGuest: rolesResult.schemeGuest,
|
|
SchemeUser: rolesResult.schemeUser,
|
|
SchemeAdmin: rolesResult.schemeAdmin,
|
|
ExplicitRoles: strings.Join(rolesResult.explicitRoles, " "),
|
|
CreateAt: db.CreateAt,
|
|
}
|
|
return tm
|
|
}
|
|
|
|
func (db teamMemberWithSchemeRolesList) ToModel() []*model.TeamMember {
|
|
tms := make([]*model.TeamMember, 0)
|
|
|
|
for _, tm := range db {
|
|
tms = append(tms, tm.ToModel())
|
|
}
|
|
|
|
return tms
|
|
}
|
|
|
|
func teamSliceColumns() []string {
|
|
return []string{
|
|
"Teams.Id",
|
|
"Teams.CreateAt",
|
|
"Teams.UpdateAt",
|
|
"Teams.DeleteAt",
|
|
"Teams.DisplayName",
|
|
"Teams.Name",
|
|
"Teams.Description",
|
|
"Teams.Email",
|
|
"Teams.Type",
|
|
"Teams.CompanyName",
|
|
"Teams.AllowedDomains",
|
|
"Teams.InviteId",
|
|
"Teams.AllowOpenInvite",
|
|
"Teams.LastTeamIconUpdate",
|
|
"Teams.SchemeId",
|
|
"Teams.GroupConstrained",
|
|
"Teams.CloudLimitsArchived",
|
|
}
|
|
}
|
|
|
|
func newSqlTeamStore(sqlStore *SqlStore) store.TeamStore {
|
|
s := &SqlTeamStore{
|
|
SqlStore: sqlStore,
|
|
}
|
|
|
|
s.teamsQuery = s.getQueryBuilder().
|
|
Select(teamSliceColumns()...).
|
|
From("Teams")
|
|
|
|
s.teamMembersQuery = s.getQueryBuilder().
|
|
Select(
|
|
"TeamMembers.TeamId",
|
|
"TeamMembers.UserId",
|
|
"TeamMembers.Roles",
|
|
"TeamMembers.DeleteAt",
|
|
"TeamMembers.SchemeUser",
|
|
"TeamMembers.SchemeAdmin",
|
|
"TeamMembers.SchemeGuest",
|
|
"TeamMembers.CreateAt",
|
|
).
|
|
From("TeamMembers")
|
|
return s
|
|
}
|
|
|
|
// Save adds the team to the database if a team with the same name does not already
|
|
// exist in the database. It returns the team added if the operation is successful.
|
|
func (s SqlTeamStore) Save(team *model.Team) (*model.Team, error) {
|
|
if team.Id != "" {
|
|
return nil, store.NewErrInvalidInput("Team", "id", team.Id)
|
|
}
|
|
|
|
team.PreSave()
|
|
|
|
if err := team.IsValid(); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if _, err := s.GetMaster().NamedExec(`INSERT INTO Teams
|
|
(Id, CreateAt, UpdateAt, DeleteAt, DisplayName, Name, Description, Email, Type, CompanyName, AllowedDomains,
|
|
InviteId, AllowOpenInvite, LastTeamIconUpdate, SchemeId, GroupConstrained, CloudLimitsArchived)
|
|
VALUES
|
|
(:Id, :CreateAt, :UpdateAt, :DeleteAt, :DisplayName, :Name, :Description, :Email, :Type, :CompanyName, :AllowedDomains,
|
|
:InviteId, :AllowOpenInvite, :LastTeamIconUpdate, :SchemeId, :GroupConstrained, :CloudLimitsArchived)`, team); err != nil {
|
|
if IsUniqueConstraintError(err, []string{"Name", "teams_name_key"}) {
|
|
return nil, store.NewErrInvalidInput("Team", "id", team.Id)
|
|
}
|
|
return nil, errors.Wrapf(err, "failed to save Team with id=%s", team.Id)
|
|
}
|
|
return team, nil
|
|
}
|
|
|
|
// Update updates the details of the team passed as the parameter using the team Id
|
|
// if the team exists in the database.
|
|
// It returns the updated team if the operation is successful.
|
|
func (s SqlTeamStore) Update(team *model.Team) (*model.Team, error) {
|
|
team.PreUpdate()
|
|
|
|
if err := team.IsValid(); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
oldTeam := model.Team{}
|
|
query := s.teamsQuery.Where(sq.Eq{"Id": team.Id})
|
|
|
|
err := s.GetMaster().GetBuilder(&oldTeam, query)
|
|
if err != nil {
|
|
return nil, errors.Wrapf(err, "failed to get Team with id=%s", team.Id)
|
|
}
|
|
|
|
if oldTeam.Id == "" {
|
|
return nil, store.NewErrInvalidInput("Team", "id", team.Id)
|
|
}
|
|
|
|
team.CreateAt = oldTeam.CreateAt
|
|
team.UpdateAt = model.GetMillis()
|
|
|
|
res, err := s.GetMaster().NamedExec(`UPDATE Teams
|
|
SET CreateAt=:CreateAt, UpdateAt=:UpdateAt, DeleteAt=:DeleteAt, DisplayName=:DisplayName, Name=:Name,
|
|
Description=:Description, Email=:Email, Type=:Type, CompanyName=:CompanyName, AllowedDomains=:AllowedDomains,
|
|
InviteId=:InviteId, AllowOpenInvite=:AllowOpenInvite, LastTeamIconUpdate=:LastTeamIconUpdate,
|
|
SchemeId=:SchemeId, GroupConstrained=:GroupConstrained, CloudLimitsArchived=:CloudLimitsArchived
|
|
WHERE Id=:Id`, team)
|
|
if err != nil {
|
|
return nil, errors.Wrapf(err, "failed to update Team with id=%s", team.Id)
|
|
}
|
|
|
|
count, err := res.RowsAffected()
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "failed to get rows_affected")
|
|
}
|
|
if count > 1 {
|
|
return nil, errors.Wrapf(err, "multiple Teams updated with id=%s", team.Id)
|
|
}
|
|
|
|
return team, nil
|
|
}
|
|
|
|
// Get returns from the database the team that matches the id provided as parameter.
|
|
// If the team doesn't exist it returns a model.AppError with a
|
|
// http.StatusNotFound in the StatusCode field.
|
|
func (s SqlTeamStore) Get(id string) (*model.Team, error) {
|
|
team := model.Team{}
|
|
|
|
query := s.teamsQuery.Where(sq.Eq{"Id": id})
|
|
|
|
if err := s.GetReplica().GetBuilder(&team, query); err != nil {
|
|
if err == sql.ErrNoRows {
|
|
return nil, store.NewErrNotFound("Team", id)
|
|
}
|
|
return nil, errors.Wrapf(err, "failed to get Team with id=%s", id)
|
|
}
|
|
if team.Id == "" {
|
|
return nil, store.NewErrNotFound("Team", id)
|
|
}
|
|
|
|
return &team, nil
|
|
}
|
|
|
|
func (s SqlTeamStore) GetMany(ids []string) ([]*model.Team, error) {
|
|
query := s.teamsQuery.Where(sq.Eq{"Teams.Id": ids})
|
|
|
|
teams := []*model.Team{}
|
|
err := s.GetReplica().SelectBuilder(&teams, query)
|
|
if err != nil {
|
|
return nil, errors.Wrapf(err, "failed to get teams with ids %v", ids)
|
|
}
|
|
|
|
if len(teams) == 0 {
|
|
return nil, store.NewErrNotFound("Team", fmt.Sprintf("ids=%v", ids))
|
|
}
|
|
|
|
return teams, nil
|
|
}
|
|
|
|
// GetByInviteId returns from the database the team that matches the inviteId provided as parameter.
|
|
// If the parameter provided is empty or if there is no match in the database, it returns a model.AppError
|
|
// with a http.StatusNotFound in the StatusCode field.
|
|
func (s SqlTeamStore) GetByInviteId(inviteId string) (*model.Team, error) {
|
|
team := model.Team{}
|
|
|
|
query, args, err := s.teamsQuery.Where(sq.Eq{"InviteId": inviteId}).ToSql()
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "team_tosql")
|
|
}
|
|
err = s.GetReplica().Get(&team, query, args...)
|
|
if err != nil {
|
|
return nil, store.NewErrNotFound("Team", fmt.Sprintf("inviteId=%s", inviteId))
|
|
}
|
|
|
|
if inviteId == "" || team.InviteId != inviteId {
|
|
return nil, store.NewErrNotFound("Team", fmt.Sprintf("inviteId=%s", inviteId))
|
|
}
|
|
return &team, nil
|
|
}
|
|
|
|
func (s SqlTeamStore) GetByEmptyInviteID() ([]*model.Team, error) {
|
|
teams := []*model.Team{}
|
|
|
|
query := s.teamsQuery.Where(sq.Eq{"InviteId": ""})
|
|
|
|
err := s.GetReplica().SelectBuilder(&teams, query)
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "failed to find Teams with empty InviteID")
|
|
}
|
|
return teams, nil
|
|
}
|
|
|
|
// GetByName returns from the database the team that matches the name provided as parameter.
|
|
// If there is no match in the database, it returns a model.AppError with a
|
|
// http.StatusNotFound in the StatusCode field.
|
|
func (s SqlTeamStore) GetByName(name string) (*model.Team, error) {
|
|
team := model.Team{}
|
|
query, args, err := s.teamsQuery.Where(sq.Eq{"Name": name}).ToSql()
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "team_tosql")
|
|
}
|
|
err = s.GetReplica().Get(&team, query, args...)
|
|
if err != nil {
|
|
if err == sql.ErrNoRows {
|
|
return nil, store.NewErrNotFound("Team", fmt.Sprintf("name=%s", name))
|
|
}
|
|
return nil, errors.Wrapf(err, "failed to find Team with name=%s", name)
|
|
}
|
|
return &team, nil
|
|
}
|
|
|
|
func (s SqlTeamStore) GetByNames(names []string) ([]*model.Team, error) {
|
|
uniqueNames := utils.RemoveDuplicatesFromStringArray(names)
|
|
|
|
query, args, err := s.teamsQuery.Where(sq.Eq{"Name": uniqueNames}).ToSql()
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "team_tosql")
|
|
}
|
|
|
|
teams := []*model.Team{}
|
|
err = s.GetReplica().Select(&teams, query, args...)
|
|
if err != nil {
|
|
if err == sql.ErrNoRows {
|
|
return nil, store.NewErrNotFound("Team", fmt.Sprintf("nameIn=%v", names))
|
|
}
|
|
return nil, errors.Wrap(err, "failed to find Teams")
|
|
}
|
|
if len(teams) != len(uniqueNames) {
|
|
return nil, store.NewErrNotFound("Team", fmt.Sprintf("nameIn=%v", names))
|
|
}
|
|
return teams, nil
|
|
}
|
|
|
|
func (s SqlTeamStore) teamSearchQuery(opts *model.TeamSearch, countQuery bool) sq.SelectBuilder {
|
|
var selectStr string
|
|
if countQuery {
|
|
selectStr = "count(*)"
|
|
} else {
|
|
selectStr = "t.*"
|
|
if opts.IncludePolicyID != nil && *opts.IncludePolicyID {
|
|
selectStr += ", RetentionPoliciesTeams.PolicyId as PolicyID"
|
|
}
|
|
}
|
|
|
|
query := s.getQueryBuilder().
|
|
Select(selectStr).
|
|
From("Teams as t")
|
|
|
|
// Don't order or limit if getting count
|
|
if !countQuery {
|
|
query = query.OrderBy("t.DisplayName")
|
|
|
|
if opts.IsPaginated() {
|
|
query = query.Limit(uint64(*opts.PerPage)).Offset(uint64(*opts.Page * *opts.PerPage))
|
|
}
|
|
}
|
|
|
|
term := opts.Term
|
|
if term != "" {
|
|
term = sanitizeSearchTerm(term, "\\")
|
|
term = wildcardSearchTerm(term)
|
|
|
|
operatorKeyword := "ILIKE"
|
|
|
|
query = query.Where(fmt.Sprintf("(Name %[1]s ? OR DisplayName %[1]s ?)", operatorKeyword), term, term)
|
|
}
|
|
|
|
if opts.PolicyID != nil && *opts.PolicyID != "" {
|
|
query = query.
|
|
InnerJoin("RetentionPoliciesTeams ON t.Id = RetentionPoliciesTeams.TeamId").
|
|
Where(sq.Eq{"RetentionPoliciesTeams.PolicyId": *opts.PolicyID})
|
|
} else if opts.ExcludePolicyConstrained != nil && *opts.ExcludePolicyConstrained {
|
|
query = query.
|
|
LeftJoin("RetentionPoliciesTeams ON t.Id = RetentionPoliciesTeams.TeamId").
|
|
Where("RetentionPoliciesTeams.TeamId IS NULL")
|
|
} else if opts.IncludePolicyID != nil && *opts.IncludePolicyID {
|
|
query = query.
|
|
LeftJoin("RetentionPoliciesTeams ON t.Id = RetentionPoliciesTeams.TeamId")
|
|
}
|
|
|
|
var teamFilters sq.Sqlizer
|
|
var openInviteFilter sq.Sqlizer
|
|
if opts.AllowOpenInvite != nil {
|
|
if *opts.AllowOpenInvite {
|
|
openInviteFilter = sq.Eq{"AllowOpenInvite": true}
|
|
} else {
|
|
openInviteFilter = sq.And{
|
|
sq.Or{
|
|
sq.NotEq{"AllowOpenInvite": true},
|
|
sq.Eq{"AllowOpenInvite": nil},
|
|
},
|
|
sq.Or{
|
|
sq.NotEq{"GroupConstrained": true},
|
|
sq.Eq{"GroupConstrained": nil},
|
|
},
|
|
}
|
|
}
|
|
|
|
teamFilters = openInviteFilter
|
|
}
|
|
|
|
var groupConstrainedFilter sq.Sqlizer
|
|
if opts.GroupConstrained != nil {
|
|
if *opts.GroupConstrained {
|
|
groupConstrainedFilter = sq.Eq{"GroupConstrained": true}
|
|
} else {
|
|
groupConstrainedFilter = sq.Or{
|
|
sq.NotEq{"GroupConstrained": true},
|
|
sq.Eq{"GroupConstrained": nil},
|
|
}
|
|
}
|
|
|
|
if teamFilters == nil {
|
|
teamFilters = groupConstrainedFilter
|
|
} else {
|
|
teamFilters = sq.Or{teamFilters, groupConstrainedFilter}
|
|
}
|
|
}
|
|
|
|
if opts.TeamType != nil {
|
|
teamTypeFilter := sq.Eq{"Type": *opts.TeamType}
|
|
teamFilters = sq.And{teamFilters, teamTypeFilter}
|
|
}
|
|
|
|
query = query.Where(teamFilters)
|
|
|
|
return query
|
|
}
|
|
|
|
// SearchAll returns from the database a list of teams that match the Name or DisplayName
|
|
// passed as the term search parameter.
|
|
func (s SqlTeamStore) SearchAll(opts *model.TeamSearch) ([]*model.Team, error) {
|
|
teams := []*model.Team{}
|
|
|
|
queryString, args, err := s.teamSearchQuery(opts, false).ToSql()
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "team_tosql")
|
|
}
|
|
|
|
if err = s.GetReplica().Select(&teams, queryString, args...); err != nil {
|
|
return nil, errors.Wrapf(err, "failed to find Teams with term=%s", opts.Term)
|
|
}
|
|
|
|
return teams, nil
|
|
}
|
|
|
|
// SearchAllPaged returns a teams list and the total count of teams that matched the search.
|
|
func (s SqlTeamStore) SearchAllPaged(opts *model.TeamSearch) ([]*model.Team, int64, error) {
|
|
teams := []*model.Team{}
|
|
var totalCount int64
|
|
|
|
queryString, args, err := s.teamSearchQuery(opts, false).ToSql()
|
|
if err != nil {
|
|
return nil, 0, errors.Wrap(err, "team_tosql")
|
|
}
|
|
if err = s.GetReplica().Select(&teams, queryString, args...); err != nil {
|
|
return nil, 0, errors.Wrapf(err, "failed to find Teams with term=%s", opts.Term)
|
|
}
|
|
|
|
queryString, args, err = s.teamSearchQuery(opts, true).ToSql()
|
|
if err != nil {
|
|
return nil, 0, errors.Wrap(err, "team_tosql")
|
|
}
|
|
|
|
err = s.GetReplica().Get(&totalCount, queryString, args...)
|
|
if err != nil {
|
|
return nil, 0, errors.Wrapf(err, "failed to count Teams with term=%s", opts.Term)
|
|
}
|
|
|
|
return teams, totalCount, nil
|
|
}
|
|
|
|
// SearchOpen returns from the database a list of public teams that match the Name or DisplayName
|
|
// passed as the term search parameter.
|
|
func (s SqlTeamStore) SearchOpen(opts *model.TeamSearch) ([]*model.Team, error) {
|
|
opts.TeamType = model.NewPointer("O")
|
|
opts.AllowOpenInvite = model.NewPointer(true)
|
|
return s.SearchAll(opts)
|
|
}
|
|
|
|
// SearchPrivate returns from the database a list of private teams that match the Name or DisplayName
|
|
// passed as the term search parameter.
|
|
func (s SqlTeamStore) SearchPrivate(opts *model.TeamSearch) ([]*model.Team, error) {
|
|
opts.TeamType = model.NewPointer("O")
|
|
opts.AllowOpenInvite = model.NewPointer(false)
|
|
return s.SearchAll(opts)
|
|
}
|
|
|
|
// GetAll returns all teams
|
|
func (s SqlTeamStore) GetAll() ([]*model.Team, error) {
|
|
teams := []*model.Team{}
|
|
|
|
query, args, err := s.teamsQuery.OrderBy("DisplayName").ToSql()
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "team_tosql")
|
|
}
|
|
|
|
err = s.GetReplica().Select(&teams, query, args...)
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "failed to find Teams")
|
|
}
|
|
return teams, nil
|
|
}
|
|
|
|
// GetAllPage returns teams, up to a total limit passed as parameter and paginated by offset number passed as parameter.
|
|
func (s SqlTeamStore) GetAllPage(offset int, limit int, opts *model.TeamSearch) ([]*model.Team, error) {
|
|
teams := []*model.Team{}
|
|
|
|
selectString := "Teams.*"
|
|
if opts != nil && opts.IncludePolicyID != nil && *opts.IncludePolicyID {
|
|
selectString += ", RetentionPoliciesTeams.PolicyId as PolicyID"
|
|
}
|
|
|
|
builder := s.getQueryBuilder().
|
|
Select(selectString).
|
|
From("Teams").
|
|
OrderBy("DisplayName").
|
|
Limit(uint64(limit)).
|
|
Offset(uint64(offset))
|
|
|
|
if opts != nil {
|
|
if (opts.ExcludePolicyConstrained != nil && *opts.ExcludePolicyConstrained) ||
|
|
(opts.IncludePolicyID != nil && *opts.IncludePolicyID) {
|
|
builder = builder.LeftJoin("RetentionPoliciesTeams ON Teams.Id = RetentionPoliciesTeams.TeamId")
|
|
}
|
|
if opts.ExcludePolicyConstrained != nil && *opts.ExcludePolicyConstrained {
|
|
builder = builder.Where("RetentionPoliciesTeams.TeamId IS NULL")
|
|
}
|
|
if opts.AllowOpenInvite != nil {
|
|
builder = builder.Where(sq.Eq{"AllowOpenInvite": *opts.AllowOpenInvite})
|
|
}
|
|
}
|
|
|
|
query, args, err := builder.ToSql()
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "team_tosql")
|
|
}
|
|
if err = s.GetReplica().Select(&teams, query, args...); err != nil {
|
|
return nil, errors.Wrap(err, "failed to find Teams")
|
|
}
|
|
|
|
return teams, nil
|
|
}
|
|
|
|
// GetTeamsByUserId returns from the database all teams that userId belongs to.
|
|
func (s SqlTeamStore) GetTeamsByUserId(userId string) ([]*model.Team, error) {
|
|
teams := []*model.Team{}
|
|
query := s.teamsQuery.
|
|
Join("TeamMembers ON TeamMembers.TeamId = Teams.Id").
|
|
Where(sq.Eq{"TeamMembers.UserId": userId, "TeamMembers.DeleteAt": 0, "Teams.DeleteAt": 0})
|
|
|
|
if err := s.GetReplica().SelectBuilder(&teams, query); err != nil {
|
|
return nil, errors.Wrap(err, "failed to find Teams")
|
|
}
|
|
|
|
return teams, nil
|
|
}
|
|
|
|
// GetAllPrivateTeamListing returns all private teams.
|
|
func (s SqlTeamStore) GetAllPrivateTeamListing() ([]*model.Team, error) {
|
|
query := s.teamsQuery.Where(sq.Eq{"AllowOpenInvite": false}).
|
|
OrderBy("DisplayName")
|
|
|
|
data := []*model.Team{}
|
|
if err := s.GetReplica().SelectBuilder(&data, query); err != nil {
|
|
return nil, errors.Wrap(err, "failed to find Teams")
|
|
}
|
|
return data, nil
|
|
}
|
|
|
|
// GetAllTeamListing returns all public teams.
|
|
func (s SqlTeamStore) GetAllTeamListing() ([]*model.Team, error) {
|
|
query := s.teamsQuery.Where(sq.Eq{"AllowOpenInvite": true}).
|
|
OrderBy("DisplayName")
|
|
|
|
data := []*model.Team{}
|
|
if err := s.GetReplica().SelectBuilder(&data, query); err != nil {
|
|
return nil, errors.Wrap(err, "failed to find Teams")
|
|
}
|
|
|
|
return data, nil
|
|
}
|
|
|
|
// PermanentDelete permanently deletes from the database the team entry that matches the teamId passed as parameter.
|
|
// To soft-delete the team you can Update it with the DeleteAt field set to the current millisecond using model.GetMillis()
|
|
func (s SqlTeamStore) PermanentDelete(teamId string) error {
|
|
sql, args, err := s.getQueryBuilder().
|
|
Delete("Teams").
|
|
Where(sq.Eq{"Id": teamId}).ToSql()
|
|
if err != nil {
|
|
return errors.Wrap(err, "team_tosql")
|
|
}
|
|
if _, err = s.GetMaster().Exec(sql, args...); err != nil {
|
|
return errors.Wrapf(err, "failed to delete Team with id=%s", teamId)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// AnalyticsTeamCount returns the total number of teams.
|
|
func (s SqlTeamStore) AnalyticsTeamCount(opts *model.TeamSearch) (int64, error) {
|
|
query := s.getQueryBuilder().Select("COUNT(*) FROM Teams")
|
|
if opts == nil || (opts.IncludeDeleted != nil && !*opts.IncludeDeleted) {
|
|
query = query.Where(sq.Eq{"DeleteAt": 0})
|
|
}
|
|
if opts != nil && opts.AllowOpenInvite != nil {
|
|
query = query.Where(sq.Eq{"AllowOpenInvite": *opts.AllowOpenInvite})
|
|
}
|
|
|
|
queryString, args, err := query.ToSql()
|
|
if err != nil {
|
|
return 0, errors.Wrap(err, "team_tosql")
|
|
}
|
|
|
|
var c int64
|
|
err = s.GetReplica().Get(&c, queryString, args...)
|
|
if err != nil {
|
|
return int64(0), errors.Wrap(err, "failed to count Teams")
|
|
}
|
|
|
|
return c, nil
|
|
}
|
|
|
|
func (s SqlTeamStore) getTeamMembersWithSchemeSelectQuery() sq.SelectBuilder {
|
|
query := s.teamMembersQuery.
|
|
Column("TeamScheme.DefaultTeamGuestRole TeamSchemeDefaultGuestRole").
|
|
Column("TeamScheme.DefaultTeamUserRole TeamSchemeDefaultUserRole").
|
|
Column("TeamScheme.DefaultTeamAdminRole TeamSchemeDefaultAdminRole").
|
|
LeftJoin("Teams ON TeamMembers.TeamId = Teams.Id").
|
|
LeftJoin("Schemes TeamScheme ON Teams.SchemeId = TeamScheme.Id")
|
|
|
|
return query
|
|
}
|
|
|
|
func (s SqlTeamStore) SaveMultipleMembers(members []*model.TeamMember, maxUsersPerTeam int) ([]*model.TeamMember, error) {
|
|
newTeamMembers := map[string]int{}
|
|
users := map[string]bool{}
|
|
for _, member := range members {
|
|
newTeamMembers[member.TeamId] = 0
|
|
}
|
|
|
|
for _, member := range members {
|
|
newTeamMembers[member.TeamId]++
|
|
users[member.UserId] = true
|
|
|
|
if err := member.IsValid(); err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
teams := []string{}
|
|
for team := range newTeamMembers {
|
|
teams = append(teams, team)
|
|
}
|
|
|
|
defaultTeamRolesByTeam := map[string]struct {
|
|
Id string
|
|
Guest sql.NullString
|
|
User sql.NullString
|
|
Admin sql.NullString
|
|
}{}
|
|
|
|
queryRoles := s.getQueryBuilder().
|
|
Select(
|
|
"Teams.Id as Id",
|
|
"TeamScheme.DefaultTeamGuestRole as Guest",
|
|
"TeamScheme.DefaultTeamUserRole as User",
|
|
"TeamScheme.DefaultTeamAdminRole as Admin",
|
|
).
|
|
From("Teams").
|
|
LeftJoin("Schemes TeamScheme ON Teams.SchemeId = TeamScheme.Id").
|
|
Where(sq.Eq{"Teams.Id": teams})
|
|
|
|
sqlRolesQuery, argsRoles, err := queryRoles.ToSql()
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "team_roles_tosql")
|
|
}
|
|
defaultTeamsRoles := []struct {
|
|
Id string
|
|
Guest sql.NullString
|
|
User sql.NullString
|
|
Admin sql.NullString
|
|
}{}
|
|
err = s.GetMaster().Select(&defaultTeamsRoles, sqlRolesQuery, argsRoles...)
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "default_team_roles_select")
|
|
}
|
|
|
|
for _, defaultRoles := range defaultTeamsRoles {
|
|
defaultTeamRolesByTeam[defaultRoles.Id] = defaultRoles
|
|
}
|
|
|
|
if maxUsersPerTeam >= 0 {
|
|
queryCount := s.getQueryBuilder().
|
|
Select(
|
|
"COUNT(0) as Count, TeamMembers.TeamId as TeamId",
|
|
).
|
|
From("TeamMembers").
|
|
Join("Users ON TeamMembers.UserId = Users.Id").
|
|
Where(sq.Eq{"TeamMembers.TeamId": teams}).
|
|
Where(sq.Eq{"TeamMembers.DeleteAt": 0}).
|
|
Where(sq.Eq{"Users.DeleteAt": 0}).
|
|
GroupBy("TeamMembers.TeamId")
|
|
|
|
sqlCountQuery, argsCount, errCount := queryCount.ToSql()
|
|
if errCount != nil {
|
|
return nil, errors.Wrap(err, "member_count_tosql")
|
|
}
|
|
|
|
counters := []struct {
|
|
Count int
|
|
TeamId string
|
|
}{}
|
|
|
|
err = s.GetMaster().Select(&counters, sqlCountQuery, argsCount...)
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "failed to count users in the teams of the memberships")
|
|
}
|
|
|
|
for teamId, newMembers := range newTeamMembers {
|
|
existingMembers := 0
|
|
for _, counter := range counters {
|
|
if counter.TeamId == teamId {
|
|
existingMembers = counter.Count
|
|
}
|
|
}
|
|
if existingMembers+newMembers > maxUsersPerTeam {
|
|
return nil, store.NewErrLimitExceeded("TeamMember", existingMembers+newMembers, "team members limit exceeded")
|
|
}
|
|
}
|
|
}
|
|
|
|
query := s.getQueryBuilder().Insert("TeamMembers").Columns(teamMemberSliceColumns()...)
|
|
for _, member := range members {
|
|
query = query.Values(teamMemberToSlice(member)...)
|
|
}
|
|
|
|
sql, args, err := query.ToSql()
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "insert_members_to_sql")
|
|
}
|
|
|
|
if _, err = s.GetMaster().Exec(sql, args...); err != nil {
|
|
if IsUniqueConstraintError(err, []string{"TeamId", "teammembers_pkey", "PRIMARY"}) {
|
|
return nil, store.NewErrConflict("TeamMember", err, "")
|
|
}
|
|
return nil, errors.Wrap(err, "unable_to_save_team_member")
|
|
}
|
|
|
|
newMembers := []*model.TeamMember{}
|
|
for _, member := range members {
|
|
s.InvalidateAllTeamIdsForUser(member.UserId)
|
|
defaultTeamGuestRole := defaultTeamRolesByTeam[member.TeamId].Guest.String
|
|
defaultTeamUserRole := defaultTeamRolesByTeam[member.TeamId].User.String
|
|
defaultTeamAdminRole := defaultTeamRolesByTeam[member.TeamId].Admin.String
|
|
rolesResult := getTeamRoles(member.SchemeGuest, member.SchemeUser, member.SchemeAdmin, defaultTeamGuestRole, defaultTeamUserRole, defaultTeamAdminRole, strings.Fields(member.ExplicitRoles))
|
|
newMember := *member
|
|
newMember.SchemeGuest = rolesResult.schemeGuest
|
|
newMember.SchemeUser = rolesResult.schemeUser
|
|
newMember.SchemeAdmin = rolesResult.schemeAdmin
|
|
newMember.Roles = strings.Join(rolesResult.roles, " ")
|
|
newMember.ExplicitRoles = strings.Join(rolesResult.explicitRoles, " ")
|
|
newMembers = append(newMembers, &newMember)
|
|
}
|
|
|
|
return newMembers, nil
|
|
}
|
|
|
|
func (s SqlTeamStore) SaveMember(rctx request.CTX, member *model.TeamMember, maxUsersPerTeam int) (*model.TeamMember, error) {
|
|
members, err := s.SaveMultipleMembers([]*model.TeamMember{member}, maxUsersPerTeam)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return members[0], nil
|
|
}
|
|
|
|
func (s SqlTeamStore) UpdateMultipleMembers(members []*model.TeamMember) ([]*model.TeamMember, error) {
|
|
teams := []string{}
|
|
for _, member := range members {
|
|
member.PreUpdate()
|
|
|
|
newTeamMember := NewTeamMemberFromModel(member)
|
|
|
|
if err := member.IsValid(); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if _, err := s.GetMaster().NamedExec(`UPDATE TeamMembers
|
|
SET Roles=:Roles, DeleteAt=:DeleteAt, CreateAt=:CreateAt, SchemeGuest=:SchemeGuest,
|
|
SchemeUser=:SchemeUser, SchemeAdmin=:SchemeAdmin
|
|
WHERE TeamId=:TeamId AND UserId=:UserId`, newTeamMember); err != nil {
|
|
return nil, errors.Wrap(err, "failed to update TeamMember")
|
|
}
|
|
teams = append(teams, member.TeamId)
|
|
}
|
|
|
|
query := s.getQueryBuilder().
|
|
Select(
|
|
"Teams.Id as Id",
|
|
"TeamScheme.DefaultTeamGuestRole as Guest",
|
|
"TeamScheme.DefaultTeamUserRole as User",
|
|
"TeamScheme.DefaultTeamAdminRole as Admin",
|
|
).
|
|
From("Teams").
|
|
LeftJoin("Schemes TeamScheme ON Teams.SchemeId = TeamScheme.Id").
|
|
Where(sq.Eq{"Teams.Id": teams})
|
|
|
|
sqlQuery, args, err := query.ToSql()
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "team_tosql")
|
|
}
|
|
defaultTeamsRoles := []struct {
|
|
Id string
|
|
Guest sql.NullString
|
|
User sql.NullString
|
|
Admin sql.NullString
|
|
}{}
|
|
err = s.GetMaster().Select(&defaultTeamsRoles, sqlQuery, args...)
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "failed to find Teams")
|
|
}
|
|
|
|
defaultTeamRolesByTeam := map[string]struct {
|
|
Id string
|
|
Guest sql.NullString
|
|
User sql.NullString
|
|
Admin sql.NullString
|
|
}{}
|
|
for _, defaultRoles := range defaultTeamsRoles {
|
|
defaultTeamRolesByTeam[defaultRoles.Id] = defaultRoles
|
|
}
|
|
|
|
updatedMembers := []*model.TeamMember{}
|
|
for _, member := range members {
|
|
s.InvalidateAllTeamIdsForUser(member.UserId)
|
|
defaultTeamGuestRole := defaultTeamRolesByTeam[member.TeamId].Guest.String
|
|
defaultTeamUserRole := defaultTeamRolesByTeam[member.TeamId].User.String
|
|
defaultTeamAdminRole := defaultTeamRolesByTeam[member.TeamId].Admin.String
|
|
rolesResult := getTeamRoles(member.SchemeGuest, member.SchemeUser, member.SchemeAdmin, defaultTeamGuestRole, defaultTeamUserRole, defaultTeamAdminRole, strings.Fields(member.ExplicitRoles))
|
|
updatedMember := *member
|
|
updatedMember.SchemeGuest = rolesResult.schemeGuest
|
|
updatedMember.SchemeUser = rolesResult.schemeUser
|
|
updatedMember.SchemeAdmin = rolesResult.schemeAdmin
|
|
updatedMember.Roles = strings.Join(rolesResult.roles, " ")
|
|
updatedMember.ExplicitRoles = strings.Join(rolesResult.explicitRoles, " ")
|
|
updatedMembers = append(updatedMembers, &updatedMember)
|
|
}
|
|
|
|
return updatedMembers, nil
|
|
}
|
|
|
|
func (s SqlTeamStore) UpdateMember(rctx request.CTX, member *model.TeamMember) (*model.TeamMember, error) {
|
|
members, err := s.UpdateMultipleMembers([]*model.TeamMember{member})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return members[0], nil
|
|
}
|
|
|
|
// GetMember returns a single member of the team that matches the teamId and userId provided as parameters.
|
|
func (s SqlTeamStore) GetMember(rctx request.CTX, teamId string, userId string) (*model.TeamMember, error) {
|
|
query := s.getTeamMembersWithSchemeSelectQuery().
|
|
Where(sq.Eq{"TeamMembers.TeamId": teamId}).
|
|
Where(sq.Eq{"TeamMembers.UserId": userId})
|
|
|
|
queryString, args, err := query.ToSql()
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "team_tosql")
|
|
}
|
|
|
|
var dbMember teamMemberWithSchemeRoles
|
|
err = s.DBXFromContext(rctx.Context()).Get(&dbMember, queryString, args...)
|
|
if err != nil {
|
|
if err == sql.ErrNoRows {
|
|
return nil, store.NewErrNotFound("TeamMember", fmt.Sprintf("teamId=%s, userId=%s", teamId, userId))
|
|
}
|
|
return nil, errors.Wrapf(err, "failed to find TeamMembers with teamId=%s and userId=%s", teamId, userId)
|
|
}
|
|
|
|
return dbMember.ToModel(), nil
|
|
}
|
|
|
|
// GetMembers returns a list of members from the database that matches the teamId passed as parameter and,
|
|
// also expects teamMembersGetOptions to be passed as a parameter which allows to further filter what to show in the result.
|
|
// TeamMembersGetOptions Model has following options->
|
|
// 1. Sort through USERNAME [ if provided, which otherwise defaults to ID ]
|
|
// 2. Sort through USERNAME [ if provided, which otherwise defaults to ID ] and exclude deleted members.
|
|
// 3. Return all the members but, exclude deleted ones.
|
|
// 4. Apply ViewUsersRestrictions to restrict what is visible to the user.
|
|
func (s SqlTeamStore) GetMembers(teamId string, offset int, limit int, teamMembersGetOptions *model.TeamMembersGetOptions) ([]*model.TeamMember, error) {
|
|
query := s.getTeamMembersWithSchemeSelectQuery().
|
|
Where(sq.Eq{"TeamMembers.TeamId": teamId}).
|
|
Where(sq.Eq{"TeamMembers.DeleteAt": 0}).
|
|
Limit(uint64(limit)).
|
|
Offset(uint64(offset))
|
|
|
|
if teamMembersGetOptions == nil || teamMembersGetOptions.Sort == "" {
|
|
query = query.OrderBy("UserId")
|
|
}
|
|
|
|
if teamMembersGetOptions != nil {
|
|
if teamMembersGetOptions.Sort == model.USERNAME || teamMembersGetOptions.ExcludeDeletedUsers {
|
|
query = query.LeftJoin("Users ON TeamMembers.UserId = Users.Id")
|
|
}
|
|
|
|
if teamMembersGetOptions.ExcludeDeletedUsers {
|
|
query = query.Where(sq.Eq{"Users.DeleteAt": 0})
|
|
}
|
|
|
|
if teamMembersGetOptions.Sort == model.USERNAME {
|
|
query = query.OrderBy(model.USERNAME)
|
|
}
|
|
|
|
query = applyTeamMemberViewRestrictionsFilter(query, teamMembersGetOptions.ViewRestrictions)
|
|
}
|
|
|
|
queryString, args, err := query.ToSql()
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "team_tosql")
|
|
}
|
|
|
|
dbMembers := teamMemberWithSchemeRolesList{}
|
|
err = s.GetReplica().Select(&dbMembers, queryString, args...)
|
|
if err != nil {
|
|
return nil, errors.Wrapf(err, "failed to find TeamMembers with teamId=%s", teamId)
|
|
}
|
|
|
|
return dbMembers.ToModel(), nil
|
|
}
|
|
|
|
// GetTotalMemberCount returns the number of all members in a team for the teamId passed as a parameter.
|
|
// Expects a restrictions parameter of type ViewUsersRestrictions that defines a set of Teams and Channels that are visible to the caller of the query, and applies restrictions with a filtered result.
|
|
func (s SqlTeamStore) GetTotalMemberCount(teamId string, restrictions *model.ViewUsersRestrictions) (int64, error) {
|
|
query := s.getQueryBuilder().
|
|
Select("count(DISTINCT TeamMembers.UserId)").
|
|
From("TeamMembers, Users").
|
|
Where("TeamMembers.DeleteAt = 0").
|
|
Where("TeamMembers.UserId = Users.Id").
|
|
Where(sq.Eq{"TeamMembers.TeamId": teamId})
|
|
|
|
query = applyTeamMemberViewRestrictionsFilterForStats(query, restrictions)
|
|
queryString, args, err := query.ToSql()
|
|
if err != nil {
|
|
return int64(0), errors.Wrap(err, "team_tosql")
|
|
}
|
|
|
|
var count int64
|
|
err = s.GetReplica().Get(&count, queryString, args...)
|
|
if err != nil {
|
|
return int64(0), errors.Wrap(err, "failed to count TeamMembers")
|
|
}
|
|
return count, nil
|
|
}
|
|
|
|
// GetActiveMemberCount returns the number of active members in a team for the teamId passed as a parameter i.e. members with 'DeleteAt = 0'
|
|
// Expects a restrictions parameter of type ViewUsersRestrictions that defines a set of Teams and Channels that are visible to the caller of the query, and applies restrictions with a filtered result.
|
|
func (s SqlTeamStore) GetActiveMemberCount(teamId string, restrictions *model.ViewUsersRestrictions) (int64, error) {
|
|
query := s.getQueryBuilder().
|
|
Select("count(DISTINCT TeamMembers.UserId)").
|
|
From("TeamMembers, Users").
|
|
Where("TeamMembers.DeleteAt = 0").
|
|
Where("TeamMembers.UserId = Users.Id").
|
|
Where("Users.DeleteAt = 0").
|
|
Where(sq.Eq{"TeamMembers.TeamId": teamId})
|
|
|
|
query = applyTeamMemberViewRestrictionsFilterForStats(query, restrictions)
|
|
queryString, args, err := query.ToSql()
|
|
if err != nil {
|
|
return 0, errors.Wrap(err, "team_tosql")
|
|
}
|
|
|
|
var count int64
|
|
err = s.GetReplica().Get(&count, queryString, args...)
|
|
if err != nil {
|
|
return 0, errors.Wrap(err, "failed to count TeamMembers")
|
|
}
|
|
|
|
return count, nil
|
|
}
|
|
|
|
// GetMembersByIds returns a list of members from the database that matches the teamId and the list of userIds passed as parameters.
|
|
// Expects a restrictions parameter of type ViewUsersRestrictions that defines a set of Teams and Channels that are visible to the caller of the query, and applies restrictions with a filtered result.
|
|
func (s SqlTeamStore) GetMembersByIds(teamId string, userIds []string, restrictions *model.ViewUsersRestrictions) ([]*model.TeamMember, error) {
|
|
if len(userIds) == 0 {
|
|
return nil, errors.New("invalid list of user ids")
|
|
}
|
|
|
|
query := s.getTeamMembersWithSchemeSelectQuery().
|
|
Where(sq.Eq{"TeamMembers.TeamId": teamId}).
|
|
Where(sq.Eq{"TeamMembers.UserId": userIds}).
|
|
Where(sq.Eq{"TeamMembers.DeleteAt": 0})
|
|
|
|
query = applyTeamMemberViewRestrictionsFilter(query, restrictions)
|
|
|
|
queryString, args, err := query.ToSql()
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "team_tosql")
|
|
}
|
|
|
|
dbMembers := teamMemberWithSchemeRolesList{}
|
|
if err = s.GetReplica().Select(&dbMembers, queryString, args...); err != nil {
|
|
return nil, errors.Wrap(err, "failed to find TeamMembers")
|
|
}
|
|
return dbMembers.ToModel(), nil
|
|
}
|
|
|
|
// GetTeamsForUser returns a list of teams that the user is a member of. Expects userId to be passed as a parameter. It can also negative the teamID passed.
|
|
func (s SqlTeamStore) GetTeamsForUser(rctx request.CTX, userId, excludeTeamID string, includeDeleted bool) ([]*model.TeamMember, error) {
|
|
query := s.getTeamMembersWithSchemeSelectQuery().
|
|
Where(sq.Eq{"TeamMembers.UserId": userId})
|
|
|
|
if excludeTeamID != "" {
|
|
query = query.Where(sq.NotEq{"TeamMembers.TeamId": excludeTeamID})
|
|
}
|
|
|
|
if !includeDeleted {
|
|
query = query.Where(sq.Eq{"TeamMembers.DeleteAt": 0})
|
|
}
|
|
|
|
queryString, args, err := query.ToSql()
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "team_tosql")
|
|
}
|
|
|
|
dbMembers := teamMemberWithSchemeRolesList{}
|
|
err = s.SqlStore.DBXFromContext(rctx.Context()).Select(&dbMembers, queryString, args...)
|
|
if err != nil {
|
|
return nil, errors.Wrapf(err, "failed to find TeamMembers with userId=%s", userId)
|
|
}
|
|
|
|
return dbMembers.ToModel(), nil
|
|
}
|
|
|
|
// GetTeamsForUserWithPagination returns limited TeamMembers according to the perPage parameter specified.
|
|
// It also offsets the records as per the page parameter supplied.
|
|
func (s SqlTeamStore) GetTeamsForUserWithPagination(userId string, page, perPage int) ([]*model.TeamMember, error) {
|
|
query := s.getTeamMembersWithSchemeSelectQuery().
|
|
Where(sq.Eq{"TeamMembers.UserId": userId}).
|
|
Limit(uint64(perPage)).
|
|
Offset(uint64(page * perPage))
|
|
|
|
queryString, args, err := query.ToSql()
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "team_tosql")
|
|
}
|
|
|
|
dbMembers := teamMemberWithSchemeRolesList{}
|
|
err = s.GetReplica().Select(&dbMembers, queryString, args...)
|
|
if err != nil {
|
|
return nil, errors.Wrapf(err, "failed to find TeamMembers with userId=%s", userId)
|
|
}
|
|
|
|
return dbMembers.ToModel(), nil
|
|
}
|
|
|
|
// GetChannelUnreadsForAllTeams returns unreads msg count, mention counts, and notifyProps
|
|
// for all the channels in all the teams except the excluded ones.
|
|
func (s SqlTeamStore) GetChannelUnreadsForAllTeams(excludeTeamId, userId string) ([]*model.ChannelUnread, error) {
|
|
query, args, err := s.getQueryBuilder().
|
|
Select("Channels.TeamId TeamId", "Channels.Id ChannelId", "(Channels.TotalMsgCount - ChannelMembers.MsgCount) MsgCount", "(Channels.TotalMsgCountRoot - ChannelMembers.MsgCountRoot) MsgCountRoot", "ChannelMembers.MentionCount MentionCount", "ChannelMembers.MentionCountRoot MentionCountRoot", "ChannelMembers.NotifyProps NotifyProps").
|
|
From("Channels").
|
|
Join("ChannelMembers ON Id = ChannelId").
|
|
Where(sq.Eq{"UserId": userId, "DeleteAt": 0}).
|
|
Where(sq.NotEq{"TeamId": excludeTeamId}).ToSql()
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "team_tosql")
|
|
}
|
|
data := []*model.ChannelUnread{}
|
|
err = s.GetReplica().Select(&data, query, args...)
|
|
if err != nil {
|
|
return nil, errors.Wrapf(err, "failed to find Channels with userId=%s and teamId!=%s", userId, excludeTeamId)
|
|
}
|
|
|
|
return data, nil
|
|
}
|
|
|
|
// GetChannelUnreadsForTeam returns unreads msg count, mention counts and notifyProps for all the channels in a single team.
|
|
func (s SqlTeamStore) GetChannelUnreadsForTeam(teamId, userId string) ([]*model.ChannelUnread, error) {
|
|
query, args, err := s.getQueryBuilder().
|
|
Select("Channels.TeamId TeamId", "Channels.Id ChannelId", "(Channels.TotalMsgCount - ChannelMembers.MsgCount) MsgCount", "(Channels.TotalMsgCountRoot - ChannelMembers.MsgCountRoot) MsgCountRoot", "ChannelMembers.MentionCount MentionCount", "ChannelMembers.MentionCountRoot MentionCountRoot", "ChannelMembers.NotifyProps NotifyProps").
|
|
From("Channels").
|
|
Join("ChannelMembers ON Id = ChannelId").
|
|
Where(sq.Eq{"UserId": userId, "TeamId": teamId, "DeleteAt": 0}).ToSql()
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "team_tosql")
|
|
}
|
|
|
|
channels := []*model.ChannelUnread{}
|
|
err = s.GetReplica().Select(&channels, query, args...)
|
|
if err != nil {
|
|
return nil, errors.Wrapf(err, "failed to find Channels with teamId=%s and userId=%s", teamId, userId)
|
|
}
|
|
return channels, nil
|
|
}
|
|
|
|
func (s SqlTeamStore) RemoveMembers(rctx request.CTX, teamId string, userIds []string) error {
|
|
builder := s.getQueryBuilder().
|
|
Delete("TeamMembers").
|
|
Where(sq.Eq{"TeamId": teamId}).
|
|
Where(sq.Eq{"UserId": userIds})
|
|
|
|
query, args, err := builder.ToSql()
|
|
if err != nil {
|
|
return errors.Wrap(err, "team_tosql")
|
|
}
|
|
_, err = s.GetMaster().Exec(query, args...)
|
|
if err != nil {
|
|
return errors.Wrapf(err, "failed to delete TeamMembers with teamId=%s and userId in %v", teamId, userIds)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// RemoveMember remove from the database the team members that match the userId and teamId passed as parameter.
|
|
func (s SqlTeamStore) RemoveMember(rctx request.CTX, teamId string, userId string) error {
|
|
return s.RemoveMembers(rctx, teamId, []string{userId})
|
|
}
|
|
|
|
// RemoveAllMembersByTeam removes from the database the team members that belong to the teamId passed as parameter.
|
|
func (s SqlTeamStore) RemoveAllMembersByTeam(teamId string) error {
|
|
query, args, err := s.getQueryBuilder().
|
|
Delete("TeamMembers").
|
|
Where(sq.Eq{"TeamId": teamId}).ToSql()
|
|
if err != nil {
|
|
return errors.Wrap(err, "team_tosql")
|
|
}
|
|
|
|
_, err = s.GetMaster().Exec(query, args...)
|
|
if err != nil {
|
|
return errors.Wrapf(err, "failed to delete TeamMembers with teamId=%s", teamId)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// RemoveAllMembersByUser removes from the database the team members that match the userId passed as parameter.
|
|
func (s SqlTeamStore) RemoveAllMembersByUser(rctx request.CTX, userId string) error {
|
|
query, args, err := s.getQueryBuilder().
|
|
Delete("TeamMembers").
|
|
Where(sq.Eq{"UserId": userId}).ToSql()
|
|
if err != nil {
|
|
return errors.Wrap(err, "team_tosql")
|
|
}
|
|
_, err = s.GetMaster().Exec(query, args...)
|
|
if err != nil {
|
|
return errors.Wrapf(err, "failed to delete TeamMembers with userId=%s", userId)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// UpdateLastTeamIconUpdate sets the last updated time for the icon based on the parameter passed in teamId. The
|
|
// LastTeamIconUpdate and UpdateAt fields are set to the parameter passed in curTime. Returns nil on success and an error
|
|
// otherwise.
|
|
func (s SqlTeamStore) UpdateLastTeamIconUpdate(teamId string, curTime int64) error {
|
|
query, args, err := s.getQueryBuilder().
|
|
Update("Teams").
|
|
SetMap(sq.Eq{"LastTeamIconUpdate": curTime, "UpdateAt": curTime}).
|
|
Where(sq.Eq{"Id": teamId}).ToSql()
|
|
if err != nil {
|
|
return errors.Wrap(err, "team_tosql")
|
|
}
|
|
|
|
if _, err = s.GetMaster().Exec(query, args...); err != nil {
|
|
return errors.Wrap(err, "failed to update Team")
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// GetTeamsByScheme returns from the database all teams that match the schemeId provided as parameter, up to
|
|
// a total limit passed as parameter and paginated by offset number passed as parameter.
|
|
func (s SqlTeamStore) GetTeamsByScheme(schemeId string, offset int, limit int) ([]*model.Team, error) {
|
|
query, args, err := s.teamsQuery.Where(sq.Eq{"SchemeId": schemeId}).
|
|
OrderBy("DisplayName").
|
|
Limit(uint64(limit)).
|
|
Offset(uint64(offset)).ToSql()
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "team_tosql")
|
|
}
|
|
|
|
teams := []*model.Team{}
|
|
err = s.GetReplica().Select(&teams, query, args...)
|
|
if err != nil {
|
|
return nil, errors.Wrapf(err, "failed to find Teams with schemeId=%s", schemeId)
|
|
}
|
|
return teams, nil
|
|
}
|
|
|
|
// MigrateTeamMembers performs the Advanced Permissions Phase 2 migration for TeamMember objects. Migration is done
|
|
// in batches as a single transaction per batch to ensure consistency but to also minimise execution time to avoid
|
|
// causing unnecessary table locks. **THIS FUNCTION SHOULD NOT BE USED FOR ANY OTHER PURPOSE.** Executing this function
|
|
// *after* the new Schemes functionality has been used on an installation will have unintended consequences.
|
|
func (s SqlTeamStore) MigrateTeamMembers(fromTeamId string, fromUserId string) (_ map[string]string, err error) {
|
|
var transaction *sqlxTxWrapper
|
|
|
|
if transaction, err = s.GetMaster().Beginx(); err != nil {
|
|
return nil, errors.Wrap(err, "begin_transaction")
|
|
}
|
|
defer finalizeTransactionX(transaction, &err)
|
|
|
|
query := s.teamMembersQuery.
|
|
Where(sq.Expr("(TeamMembers.TeamId, TeamMembers.UserId) > (?, ?)", fromTeamId, fromUserId)).
|
|
OrderBy("TeamMembers.TeamId, TeamMembers.UserId").
|
|
Limit(100)
|
|
|
|
teamMembers := []teamMember{}
|
|
if err := transaction.SelectBuilder(&teamMembers, query); err != nil {
|
|
return nil, errors.Wrap(err, "failed to find TeamMembers")
|
|
}
|
|
|
|
if len(teamMembers) == 0 {
|
|
// No more team members in query result means that the migration has finished.
|
|
return nil, nil
|
|
}
|
|
|
|
for i := range teamMembers {
|
|
member := teamMembers[i]
|
|
roles := strings.Fields(member.Roles)
|
|
var newRoles []string
|
|
if !member.SchemeAdmin.Valid {
|
|
member.SchemeAdmin = sql.NullBool{Bool: false, Valid: true}
|
|
}
|
|
if !member.SchemeUser.Valid {
|
|
member.SchemeUser = sql.NullBool{Bool: false, Valid: true}
|
|
}
|
|
if !member.SchemeGuest.Valid {
|
|
member.SchemeGuest = sql.NullBool{Bool: false, Valid: true}
|
|
}
|
|
for _, role := range roles {
|
|
if role == model.TeamAdminRoleId {
|
|
member.SchemeAdmin = sql.NullBool{Bool: true, Valid: true}
|
|
} else if role == model.TeamUserRoleId {
|
|
member.SchemeUser = sql.NullBool{Bool: true, Valid: true}
|
|
} else if role == model.TeamGuestRoleId {
|
|
member.SchemeGuest = sql.NullBool{Bool: true, Valid: true}
|
|
} else {
|
|
newRoles = append(newRoles, role)
|
|
}
|
|
}
|
|
member.Roles = strings.Join(newRoles, " ")
|
|
|
|
if _, err := transaction.NamedExec(`UPDATE TeamMembers
|
|
SET TeamId=:TeamId,
|
|
UserId=:UserId,
|
|
Roles=:Roles,
|
|
DeleteAt=:DeleteAt,
|
|
SchemeUser=:SchemeUser,
|
|
SchemeAdmin=:SchemeAdmin,
|
|
SchemeGuest=:SchemeGuest
|
|
WHERE TeamId=:TeamId AND UserId=:UserId`, &member); err != nil {
|
|
return nil, errors.Wrap(err, "failed to update TeamMember")
|
|
}
|
|
}
|
|
|
|
if err := transaction.Commit(); err != nil {
|
|
return nil, errors.Wrap(err, "commit_transaction")
|
|
}
|
|
|
|
data := make(map[string]string)
|
|
data["TeamId"] = teamMembers[len(teamMembers)-1].TeamId
|
|
data["UserId"] = teamMembers[len(teamMembers)-1].UserId
|
|
|
|
return data, nil
|
|
}
|
|
|
|
// ResetAllTeamSchemes Set all Team's SchemeId values to an empty string.
|
|
func (s SqlTeamStore) ResetAllTeamSchemes() error {
|
|
if _, err := s.GetMaster().Exec("UPDATE Teams SET SchemeId=''"); err != nil {
|
|
return errors.Wrap(err, "failed to update Teams")
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// ClearCaches method not implemented.
|
|
func (s SqlTeamStore) ClearCaches() {}
|
|
|
|
// InvalidateAllTeamIdsForUser does not execute anything because the store does not handle the cache.
|
|
//
|
|
//nolint:unparam
|
|
func (s SqlTeamStore) InvalidateAllTeamIdsForUser(userId string) {}
|
|
|
|
// ClearAllCustomRoleAssignments removes all custom role assignments from TeamMembers.
|
|
func (s SqlTeamStore) ClearAllCustomRoleAssignments() (err error) {
|
|
builtInRoles := model.MakeDefaultRoles()
|
|
lastUserId := strings.Repeat("0", 26)
|
|
lastTeamId := strings.Repeat("0", 26)
|
|
|
|
for {
|
|
var transaction *sqlxTxWrapper
|
|
var err error
|
|
|
|
if transaction, err = s.GetMaster().Beginx(); err != nil {
|
|
return errors.Wrap(err, "begin_transaction")
|
|
}
|
|
defer finalizeTransactionX(transaction, &err)
|
|
|
|
query := s.teamMembersQuery.
|
|
Where(sq.Expr("(TeamMembers.TeamId, TeamMembers.UserId) > (?, ?)", lastTeamId, lastUserId)).
|
|
OrderBy("TeamMembers.TeamId, TeamMembers.UserId").
|
|
Limit(1000)
|
|
|
|
teamMembers := []*teamMember{}
|
|
if err := transaction.SelectBuilder(&teamMembers, query); err != nil {
|
|
return errors.Wrap(err, "failed to find TeamMembers")
|
|
}
|
|
|
|
if len(teamMembers) == 0 {
|
|
break
|
|
}
|
|
|
|
for _, member := range teamMembers {
|
|
lastUserId = member.UserId
|
|
lastTeamId = member.TeamId
|
|
|
|
var newRoles []string
|
|
|
|
for role := range strings.FieldsSeq(member.Roles) {
|
|
for name := range builtInRoles {
|
|
if name == role {
|
|
newRoles = append(newRoles, role)
|
|
break
|
|
}
|
|
}
|
|
}
|
|
|
|
newRolesString := strings.Join(newRoles, " ")
|
|
if newRolesString != member.Roles {
|
|
if _, err := transaction.Exec("UPDATE TeamMembers SET Roles = ? WHERE UserId = ? AND TeamId = ?", newRolesString, member.UserId, member.TeamId); err != nil {
|
|
return errors.Wrap(err, "failed to update TeamMembers")
|
|
}
|
|
}
|
|
}
|
|
|
|
if err := transaction.Commit(); err != nil {
|
|
return errors.Wrap(err, "commit_transaction")
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// AnalyticsGetTeamCountForScheme returns the number of active teams that match the schemeId passed as parameter.
|
|
func (s SqlTeamStore) AnalyticsGetTeamCountForScheme(schemeId string) (int64, error) {
|
|
query, args, err := s.getQueryBuilder().
|
|
Select("count(*)").
|
|
From("Teams").
|
|
Where(sq.Eq{"SchemeId": schemeId, "DeleteAt": 0}).ToSql()
|
|
if err != nil {
|
|
return 0, errors.Wrap(err, "team_tosql")
|
|
}
|
|
|
|
var count int64
|
|
err = s.GetReplica().Get(&count, query, args...)
|
|
if err != nil {
|
|
return 0, errors.Wrapf(err, "failed to count Teams with schemeId=%s", schemeId)
|
|
}
|
|
|
|
return count, nil
|
|
}
|
|
|
|
// GetAllForExportAfter returns teams for export, up to a total limit passed as parameter where Teams.Id is greater than the afterId passed as parameter.
|
|
func (s SqlTeamStore) GetAllForExportAfter(limit int, afterId string) ([]*model.TeamForExport, error) {
|
|
data := []*model.TeamForExport{}
|
|
query, args, err := s.getQueryBuilder().
|
|
Select(teamSliceColumns()...).
|
|
Column("Schemes.Name as SchemeName").
|
|
From("Teams").
|
|
LeftJoin("Schemes ON Teams.SchemeId = Schemes.Id").
|
|
Where(sq.Gt{"Teams.Id": afterId}).
|
|
OrderBy("Id").
|
|
Limit(uint64(limit)).ToSql()
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "team_tosql")
|
|
}
|
|
if err = s.GetReplica().Select(&data, query, args...); err != nil {
|
|
return nil, errors.Wrap(err, "failed to find Teams")
|
|
}
|
|
|
|
return data, nil
|
|
}
|
|
|
|
// GetUserTeamIds get the team ids to which the user belongs to. allowFromCache parameter does not have any effect in this Store
|
|
//
|
|
//nolint:unparam
|
|
func (s SqlTeamStore) GetUserTeamIds(userId string, allowFromCache bool) ([]string, error) {
|
|
teamIds := []string{}
|
|
query, args, err := s.getQueryBuilder().
|
|
Select("TeamId").
|
|
From("TeamMembers").
|
|
Join("Teams ON TeamMembers.TeamId = Teams.Id").
|
|
Where(sq.Eq{"TeamMembers.UserId": userId, "TeamMembers.DeleteAt": 0, "Teams.DeleteAt": 0}).ToSql()
|
|
if err != nil {
|
|
return []string{}, errors.Wrap(err, "team_tosql")
|
|
}
|
|
err = s.GetReplica().Select(&teamIds, query, args...)
|
|
if err != nil {
|
|
return []string{}, errors.Wrapf(err, "failed to find TeamMembers with userId=%s", userId)
|
|
}
|
|
|
|
return teamIds, nil
|
|
}
|
|
|
|
// GetCommonTeamIDsForTwoUsers returns the intersection of all the teams to which the specified
|
|
// users belong.
|
|
func (s SqlTeamStore) GetCommonTeamIDsForTwoUsers(userID, otherUserID string) ([]string, error) {
|
|
var teamIDs []string
|
|
query, args, err := s.getQueryBuilder().
|
|
Select("TM1.TeamId").
|
|
From("TeamMembers AS TM1").
|
|
InnerJoin("TeamMembers AS TM2 ON TM1.TeamId = TM2.TeamId").
|
|
InnerJoin("Teams ON TM1.TeamId = Teams.Id").
|
|
Where(sq.And{
|
|
sq.Eq{"TM1.UserId": userID},
|
|
sq.Eq{"TM1.DeleteAt": 0},
|
|
sq.Eq{"TM2.UserId": otherUserID},
|
|
sq.Eq{"TM2.DeleteAt": 0},
|
|
sq.Eq{"Teams.DeleteAt": 0},
|
|
}).
|
|
ToSql()
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "team_tosql")
|
|
}
|
|
err = s.GetReplica().Select(&teamIDs, query, args...)
|
|
if err != nil {
|
|
return nil, errors.Wrapf(err, "failed to find TeamMembers with user IDs %s and %s", userID, otherUserID)
|
|
}
|
|
|
|
return teamIDs, nil
|
|
}
|
|
|
|
func (s SqlTeamStore) GetCommonTeamIDsForMultipleUsers(userIDs []string) ([]string, error) {
|
|
subQuery := s.getSubQueryBuilder().
|
|
Select("TeamId, UserId").
|
|
From("TeamMembers").
|
|
Where(sq.Eq{
|
|
"UserId": userIDs,
|
|
"DeleteAt": 0,
|
|
})
|
|
|
|
subQuerySQL, subQueryParams, err := subQuery.ToSql()
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "GetCommonTeamIDsForMultipleUsers_subquery_toSQL")
|
|
}
|
|
|
|
query := s.getQueryBuilder().
|
|
Select("t.Id").
|
|
From("Teams AS t").
|
|
Join("("+subQuerySQL+") AS tm ON t.Id = tm.TeamId", subQueryParams...).
|
|
Where(sq.Eq{
|
|
"t.DeleteAt": 0,
|
|
}).
|
|
GroupBy("t.Id").
|
|
Having(sq.Eq{
|
|
"COUNT(UserId)": len(userIDs),
|
|
})
|
|
|
|
querySQL, args, err := query.ToSql()
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "GetCommonTeamIDsForMultipleUsers_query_toSQL")
|
|
}
|
|
|
|
var teamIDs []string
|
|
|
|
if err := s.GetReplica().Select(&teamIDs, querySQL, args...); err != nil {
|
|
return nil, errors.Wrapf(err, "failed to find common team for users %v", userIDs)
|
|
}
|
|
|
|
return teamIDs, nil
|
|
}
|
|
|
|
// GetTeamMembersForExport gets the various teams for which a user, denoted by userId, is a part of.
|
|
func (s SqlTeamStore) GetTeamMembersForExport(userId string) ([]*model.TeamMemberForExport, error) {
|
|
members := []*model.TeamMemberForExport{}
|
|
query, args, err := s.getQueryBuilder().
|
|
Select("TeamMembers.TeamId", "TeamMembers.UserId", "TeamMembers.Roles", "TeamMembers.DeleteAt",
|
|
"(TeamMembers.SchemeGuest IS NOT NULL AND TeamMembers.SchemeGuest) as SchemeGuest",
|
|
"TeamMembers.SchemeUser", "TeamMembers.SchemeAdmin", "Teams.Name as TeamName").
|
|
From("TeamMembers").
|
|
Join("Teams ON TeamMembers.TeamId = Teams.Id").
|
|
Where(sq.Eq{"TeamMembers.UserId": userId, "Teams.DeleteAt": 0}).ToSql()
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "team_tosql")
|
|
}
|
|
err = s.GetReplica().Select(&members, query, args...)
|
|
if err != nil {
|
|
return nil, errors.Wrapf(err, "failed to find TeamMembers with userId=%s", userId)
|
|
}
|
|
return members, nil
|
|
}
|
|
|
|
// UserBelongsToTeams returns true if the user denoted by userId is a member of the teams in the teamIds string array.
|
|
func (s SqlTeamStore) UserBelongsToTeams(userId string, teamIds []string) (bool, error) {
|
|
idQuery := sq.Eq{
|
|
"UserId": userId,
|
|
"TeamId": teamIds,
|
|
"DeleteAt": 0,
|
|
}
|
|
|
|
query, params, err := s.getQueryBuilder().Select("Count(*)").From("TeamMembers").Where(idQuery).ToSql()
|
|
if err != nil {
|
|
return false, errors.Wrap(err, "team_tosql")
|
|
}
|
|
|
|
var c int64
|
|
err = s.GetReplica().Get(&c, query, params...)
|
|
if err != nil {
|
|
return false, errors.Wrap(err, "failed to count TeamMembers")
|
|
}
|
|
|
|
return c > 0, nil
|
|
}
|
|
|
|
// UpdateMembersRole updates all the members of teamID in the adminIDs string array to be admins and sets all other
|
|
// users as not being admin.
|
|
// It returns the list of userIDs whose roles got updated.
|
|
func (s SqlTeamStore) UpdateMembersRole(teamID string, adminIDs []string) (_ []*model.TeamMember, err error) {
|
|
transaction, err := s.GetMaster().Beginx()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer finalizeTransactionX(transaction, &err)
|
|
|
|
// TODO: https://mattermost.atlassian.net/browse/MM-63368
|
|
// On MySQL it's not possible to update a table and select from it in the same query.
|
|
// A SELECT and a UPDATE query are needed.
|
|
// Once we only support PostgreSQL, this can be done in a single query using RETURNING.
|
|
query, args, err := s.teamMembersQuery.
|
|
Where(sq.Eq{"TeamId": teamID, "DeleteAt": 0}).
|
|
Where(sq.Or{sq.Eq{"SchemeGuest": false}, sq.Expr("SchemeGuest IS NULL")}).
|
|
Where(
|
|
sq.Or{
|
|
// New admins
|
|
sq.And{
|
|
sq.Eq{"SchemeAdmin": false},
|
|
sq.Eq{"UserId": adminIDs},
|
|
},
|
|
// Demoted admins
|
|
sq.And{
|
|
sq.Eq{"SchemeAdmin": true},
|
|
sq.NotEq{"UserId": adminIDs},
|
|
},
|
|
},
|
|
).ToSql()
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "team_tosql")
|
|
}
|
|
|
|
var updatedMembers []*model.TeamMember
|
|
if err = transaction.Select(&updatedMembers, query, args...); err != nil {
|
|
return nil, errors.Wrap(err, "failed to get list of updated users")
|
|
}
|
|
|
|
// Update SchemeAdmin field as the data from the SQL is not updated yet
|
|
for _, member := range updatedMembers {
|
|
if slices.Contains(adminIDs, member.UserId) {
|
|
member.SchemeAdmin = true
|
|
} else {
|
|
member.SchemeAdmin = false
|
|
}
|
|
}
|
|
|
|
query, args, err = s.getQueryBuilder().
|
|
Update("TeamMembers").
|
|
Set("SchemeAdmin", sq.Case().When(sq.Eq{"UserId": adminIDs}, "true").Else("false")).
|
|
Where(sq.Eq{"TeamId": teamID, "DeleteAt": 0}).
|
|
Where(sq.Or{sq.Eq{"SchemeGuest": false}, sq.Expr("SchemeGuest IS NULL")}).ToSql()
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "team_tosql")
|
|
}
|
|
|
|
if _, err = transaction.Exec(query, args...); err != nil {
|
|
return nil, errors.Wrap(err, "failed to update TeamMembers")
|
|
}
|
|
|
|
if err = transaction.Commit(); err != nil {
|
|
return nil, errors.Wrap(err, "commit_transaction")
|
|
}
|
|
|
|
return updatedMembers, nil
|
|
}
|
|
|
|
func applyTeamMemberViewRestrictionsFilter(query sq.SelectBuilder, restrictions *model.ViewUsersRestrictions) sq.SelectBuilder {
|
|
if restrictions == nil {
|
|
return query
|
|
}
|
|
|
|
// If you have no access to teams or channels, return and empty result.
|
|
if restrictions.Teams != nil && len(restrictions.Teams) == 0 && restrictions.Channels != nil && len(restrictions.Channels) == 0 {
|
|
return query.Where("1 = 0")
|
|
}
|
|
|
|
teams := make([]any, len(restrictions.Teams))
|
|
for i, v := range restrictions.Teams {
|
|
teams[i] = v
|
|
}
|
|
channels := make([]any, len(restrictions.Channels))
|
|
for i, v := range restrictions.Channels {
|
|
channels[i] = v
|
|
}
|
|
|
|
resultQuery := query.Join("Users ru ON (TeamMembers.UserId = ru.Id)")
|
|
if len(restrictions.Teams) > 0 {
|
|
resultQuery = resultQuery.Join(fmt.Sprintf("TeamMembers rtm ON ( rtm.UserId = ru.Id AND rtm.DeleteAt = 0 AND rtm.TeamId IN (%s))", sq.Placeholders(len(teams))), teams...)
|
|
}
|
|
if len(restrictions.Channels) > 0 {
|
|
resultQuery = resultQuery.Join(fmt.Sprintf("ChannelMembers rcm ON ( rcm.UserId = ru.Id AND rcm.ChannelId IN (%s))", sq.Placeholders(len(channels))), channels...)
|
|
}
|
|
|
|
return resultQuery.Distinct()
|
|
}
|
|
|
|
func applyTeamMemberViewRestrictionsFilterForStats(query sq.SelectBuilder, restrictions *model.ViewUsersRestrictions) sq.SelectBuilder {
|
|
if restrictions == nil {
|
|
return query
|
|
}
|
|
|
|
// If you have no access to teams or channels, return and empty result.
|
|
if restrictions.Teams != nil && len(restrictions.Teams) == 0 && restrictions.Channels != nil && len(restrictions.Channels) == 0 {
|
|
return query.Where("1 = 0")
|
|
}
|
|
|
|
teams := make([]any, len(restrictions.Teams))
|
|
for i, v := range restrictions.Teams {
|
|
teams[i] = v
|
|
}
|
|
channels := make([]any, len(restrictions.Channels))
|
|
for i, v := range restrictions.Channels {
|
|
channels[i] = v
|
|
}
|
|
|
|
resultQuery := query
|
|
if len(restrictions.Teams) > 0 {
|
|
resultQuery = resultQuery.Join(fmt.Sprintf("TeamMembers rtm ON ( rtm.UserId = Users.Id AND rtm.DeleteAt = 0 AND rtm.TeamId IN (%s))", sq.Placeholders(len(teams))), teams...)
|
|
}
|
|
if len(restrictions.Channels) > 0 {
|
|
resultQuery = resultQuery.Join(fmt.Sprintf("ChannelMembers rcm ON ( rcm.UserId = Users.Id AND rcm.ChannelId IN (%s))", sq.Placeholders(len(channels))), channels...)
|
|
}
|
|
|
|
return resultQuery
|
|
}
|
|
|
|
// GroupSyncedTeamCount returns the number of teams that are group constrained.
|
|
func (s SqlTeamStore) GroupSyncedTeamCount() (int64, error) {
|
|
builder := s.getQueryBuilder().Select("COUNT(*)").From("Teams").Where(sq.Eq{"GroupConstrained": true, "DeleteAt": 0})
|
|
|
|
query, args, err := builder.ToSql()
|
|
if err != nil {
|
|
return 0, errors.Wrap(err, "team_tosql")
|
|
}
|
|
|
|
var count int64
|
|
err = s.GetReplica().Get(&count, query, args...)
|
|
if err != nil {
|
|
return 0, errors.Wrap(err, "failed to count Teams")
|
|
}
|
|
|
|
return count, nil
|
|
}
|