github.com/mattermost/mattermost-server/v5@v5.39.3/store/sqlstore/status_store.go (about)

     1  // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
     2  // See LICENSE.txt for license information.
     3  
     4  package sqlstore
     5  
     6  import (
     7  	"database/sql"
     8  	"fmt"
     9  	"time"
    10  
    11  	sq "github.com/Masterminds/squirrel"
    12  	"github.com/pkg/errors"
    13  
    14  	"github.com/mattermost/gorp"
    15  	"github.com/mattermost/mattermost-server/v5/model"
    16  	"github.com/mattermost/mattermost-server/v5/store"
    17  )
    18  
    19  type SqlStatusStore struct {
    20  	*SqlStore
    21  }
    22  
    23  func newSqlStatusStore(sqlStore *SqlStore) store.StatusStore {
    24  	s := &SqlStatusStore{sqlStore}
    25  
    26  	for _, db := range sqlStore.GetAllConns() {
    27  		table := db.AddTableWithName(model.Status{}, "Status").SetKeys(false, "UserId")
    28  		table.ColMap("UserId").SetMaxSize(26)
    29  		table.ColMap("Status").SetMaxSize(32)
    30  		table.ColMap("ActiveChannel").SetMaxSize(26)
    31  		table.ColMap("PrevStatus").SetMaxSize(32)
    32  	}
    33  
    34  	return s
    35  }
    36  
    37  func (s SqlStatusStore) createIndexesIfNotExists() {
    38  	s.CreateIndexIfNotExists("idx_status_status", "Status", "Status")
    39  }
    40  
    41  func (s SqlStatusStore) SaveOrUpdate(st *model.Status) error {
    42  	query := s.getQueryBuilder().
    43  		Insert("Status").
    44  		Columns("UserId", "Status", "Manual", "LastActivityAt", "DNDEndTime", "PrevStatus").
    45  		Values(st.UserId, st.Status, st.Manual, st.LastActivityAt, st.DNDEndTime, st.PrevStatus)
    46  
    47  	if s.DriverName() == model.DATABASE_DRIVER_MYSQL {
    48  		query = query.SuffixExpr(sq.Expr("ON DUPLICATE KEY UPDATE Status = ?, Manual = ?, LastActivityAt = ?, DNDEndTime = ?, PrevStatus = ?",
    49  			st.Status, st.Manual, st.LastActivityAt, st.DNDEndTime, st.PrevStatus))
    50  	} else {
    51  		query = query.SuffixExpr(sq.Expr("ON CONFLICT (userid) DO UPDATE SET Status = ?, Manual = ?, LastActivityAt = ?, DNDEndTime = ?, PrevStatus = ?",
    52  			st.Status, st.Manual, st.LastActivityAt, st.DNDEndTime, st.PrevStatus))
    53  	}
    54  
    55  	queryString, args, err := query.ToSql()
    56  	if err != nil {
    57  		return errors.Wrap(err, "status_tosql")
    58  	}
    59  
    60  	if _, err := s.GetMaster().Exec(queryString, args...); err != nil {
    61  		return errors.Wrap(err, "failed to upsert Status")
    62  	}
    63  
    64  	return nil
    65  }
    66  
    67  func (s SqlStatusStore) Get(userId string) (*model.Status, error) {
    68  	var status model.Status
    69  
    70  	if err := s.GetReplica().SelectOne(&status,
    71  		`SELECT
    72  			*
    73  		FROM
    74  			Status
    75  		WHERE
    76  			UserId = :UserId`, map[string]interface{}{"UserId": userId}); err != nil {
    77  		if err == sql.ErrNoRows {
    78  			return nil, store.NewErrNotFound("Status", fmt.Sprintf("userId=%s", userId))
    79  		}
    80  		return nil, errors.Wrapf(err, "failed to get Status with userId=%s", userId)
    81  	}
    82  	return &status, nil
    83  }
    84  
    85  func (s SqlStatusStore) GetByIds(userIds []string) ([]*model.Status, error) {
    86  	query := s.getQueryBuilder().
    87  		Select("UserId, Status, Manual, LastActivityAt").
    88  		From("Status").
    89  		Where(sq.Eq{"UserId": userIds})
    90  	queryString, args, err := query.ToSql()
    91  	if err != nil {
    92  		return nil, errors.Wrap(err, "status_tosql")
    93  	}
    94  	rows, err := s.GetReplica().Db.Query(queryString, args...)
    95  	if err != nil {
    96  		return nil, errors.Wrap(err, "failed to find Statuses")
    97  	}
    98  	var statuses []*model.Status
    99  	defer rows.Close()
   100  	for rows.Next() {
   101  		var status model.Status
   102  		if err = rows.Scan(&status.UserId, &status.Status, &status.Manual, &status.LastActivityAt); err != nil {
   103  			return nil, errors.Wrap(err, "unable to scan from rows")
   104  		}
   105  		statuses = append(statuses, &status)
   106  	}
   107  	if err = rows.Err(); err != nil {
   108  		return nil, errors.Wrap(err, "failed while iterating over rows")
   109  	}
   110  
   111  	return statuses, nil
   112  }
   113  
   114  // MySQL doesn't have support for RETURNING clause, so we use a transaction to get the updated rows.
   115  func (s SqlStatusStore) updateExpiredStatuses(t *gorp.Transaction) ([]*model.Status, error) {
   116  	var statuses []*model.Status
   117  	currUnixTime := time.Now().UTC().Unix()
   118  	selectQuery, selectParams, err := s.getQueryBuilder().
   119  		Select("*").
   120  		From("Status").
   121  		Where(
   122  			sq.And{
   123  				sq.Eq{"Status": model.STATUS_DND},
   124  				sq.Gt{"DNDEndTime": 0},
   125  				sq.LtOrEq{"DNDEndTime": currUnixTime},
   126  			},
   127  		).ToSql()
   128  	if err != nil {
   129  		return nil, errors.Wrap(err, "status_tosql")
   130  	}
   131  	_, err = t.Select(&statuses, selectQuery, selectParams...)
   132  	if err != nil {
   133  		return nil, errors.Wrap(err, "updateExpiredStatusesT: failed to get expired dnd statuses")
   134  	}
   135  	updateQuery, args, err := s.getQueryBuilder().
   136  		Update("Status").
   137  		Where(
   138  			sq.And{
   139  				sq.Eq{"Status": model.STATUS_DND},
   140  				sq.Gt{"DNDEndTime": 0},
   141  				sq.LtOrEq{"DNDEndTime": currUnixTime},
   142  			},
   143  		).
   144  		Set("Status", sq.Expr("PrevStatus")).
   145  		Set("PrevStatus", model.STATUS_DND).
   146  		Set("DNDEndTime", 0).
   147  		Set("Manual", false).
   148  		ToSql()
   149  
   150  	if err != nil {
   151  		return nil, errors.Wrap(err, "status_tosql")
   152  	}
   153  
   154  	if _, err := t.Exec(updateQuery, args...); err != nil {
   155  		return nil, errors.Wrapf(err, "updateExpiredStatusesT: failed to update statuses")
   156  	}
   157  
   158  	return statuses, nil
   159  }
   160  
   161  func (s SqlStatusStore) UpdateExpiredDNDStatuses() ([]*model.Status, error) {
   162  	if s.DriverName() == model.DATABASE_DRIVER_MYSQL {
   163  		transaction, err := s.GetMaster().Begin()
   164  		if err != nil {
   165  			return nil, errors.Wrap(err, "UpdateExpiredDNDStatuses: begin_transaction")
   166  		}
   167  		defer finalizeTransaction(transaction)
   168  		statuses, err := s.updateExpiredStatuses(transaction)
   169  		if err != nil {
   170  			return nil, errors.Wrap(err, "UpdateExpiredDNDStatuses: updateExpiredDNDStatusesT")
   171  		}
   172  		if err := transaction.Commit(); err != nil {
   173  			return nil, errors.Wrap(err, "UpdateExpiredDNDStatuses: commit_transaction")
   174  		}
   175  
   176  		for _, status := range statuses {
   177  			status.Status = status.PrevStatus
   178  			status.PrevStatus = model.STATUS_DND
   179  			status.DNDEndTime = 0
   180  			status.Manual = false
   181  		}
   182  
   183  		return statuses, nil
   184  	}
   185  
   186  	queryString, args, err := s.getQueryBuilder().
   187  		Update("Status").
   188  		Where(
   189  			sq.And{
   190  				sq.Eq{"Status": model.STATUS_DND},
   191  				sq.Gt{"DNDEndTime": 0},
   192  				sq.LtOrEq{"DNDEndTime": time.Now().UTC().Unix()},
   193  			},
   194  		).
   195  		Set("Status", sq.Expr("PrevStatus")).
   196  		Set("PrevStatus", model.STATUS_DND).
   197  		Set("DNDEndTime", 0).
   198  		Set("Manual", false).
   199  		Suffix("RETURNING *").
   200  		ToSql()
   201  
   202  	if err != nil {
   203  		return nil, errors.Wrap(err, "status_tosql")
   204  	}
   205  
   206  	rows, err := s.GetMaster().Query(queryString, args...)
   207  	if err != nil {
   208  		return nil, errors.Wrap(err, "failed to find Statuses")
   209  	}
   210  	defer rows.Close()
   211  	var statuses []*model.Status
   212  	for rows.Next() {
   213  		var status model.Status
   214  		if err = rows.Scan(&status.UserId, &status.Status, &status.Manual, &status.LastActivityAt,
   215  			&status.DNDEndTime, &status.PrevStatus); err != nil {
   216  			return nil, errors.Wrap(err, "unable to scan from rows")
   217  		}
   218  		statuses = append(statuses, &status)
   219  	}
   220  	if err = rows.Err(); err != nil {
   221  		return nil, errors.Wrap(err, "failed while iterating over rows")
   222  	}
   223  
   224  	return statuses, nil
   225  }
   226  
   227  func (s SqlStatusStore) ResetAll() error {
   228  	if _, err := s.GetMaster().Exec("UPDATE Status SET Status = :Status WHERE Manual = false", map[string]interface{}{"Status": model.STATUS_OFFLINE}); err != nil {
   229  		return errors.Wrap(err, "failed to update Statuses")
   230  	}
   231  	return nil
   232  }
   233  
   234  func (s SqlStatusStore) GetTotalActiveUsersCount() (int64, error) {
   235  	time := model.GetMillis() - (1000 * 60 * 60 * 24)
   236  	count, err := s.GetReplica().SelectInt("SELECT COUNT(UserId) FROM Status WHERE LastActivityAt > :Time", map[string]interface{}{"Time": time})
   237  	if err != nil {
   238  		return count, errors.Wrap(err, "failed to count active users")
   239  	}
   240  	return count, nil
   241  }
   242  
   243  func (s SqlStatusStore) UpdateLastActivityAt(userId string, lastActivityAt int64) error {
   244  	if _, err := s.GetMaster().Exec("UPDATE Status SET LastActivityAt = :Time WHERE UserId = :UserId", map[string]interface{}{"UserId": userId, "Time": lastActivityAt}); err != nil {
   245  		return errors.Wrapf(err, "failed to update last activity for userId=%s", userId)
   246  	}
   247  
   248  	return nil
   249  }