// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // See LICENSE.txt for license information. package sqlstore import ( "database/sql" "fmt" "time" 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 SqlStatusStore struct { *SqlStore statusSelectQuery sq.SelectBuilder } func newSqlStatusStore(sqlStore *SqlStore) store.StatusStore { s := SqlStatusStore{ SqlStore: sqlStore, } manualColumnName := quoteColumnName(s.DriverName(), "Manual") s.statusSelectQuery = s.getQueryBuilder(). Select( "COALESCE(UserId, '') AS UserId", "COALESCE(Status, '') AS Status", fmt.Sprintf("COALESCE(%s, FALSE) AS %s", manualColumnName, manualColumnName), "COALESCE(LastActivityAt, 0) AS LastActivityAt", "COALESCE(DNDEndTime, 0) AS DNDEndTime", "COALESCE(PrevStatus, '') AS PrevStatus", ). From("Status") return &s } func (s SqlStatusStore) SaveOrUpdate(st *model.Status) error { query := s.getQueryBuilder(). Insert("Status"). Columns("UserId", "Status", quoteColumnName(s.DriverName(), "Manual"), "LastActivityAt", "DNDEndTime", "PrevStatus"). Values(st.UserId, st.Status, st.Manual, st.LastActivityAt, st.DNDEndTime, st.PrevStatus) query = query.SuffixExpr(sq.Expr("ON CONFLICT (userid) DO UPDATE SET Status = EXCLUDED.Status, Manual = EXCLUDED.Manual, LastActivityAt = EXCLUDED.LastActivityAt, DNDEndTime = EXCLUDED.DNDEndTime, PrevStatus = EXCLUDED.PrevStatus")) if _, err := s.GetMaster().ExecBuilder(query); err != nil { return errors.Wrap(err, "failed to upsert Status") } return nil } func (s SqlStatusStore) SaveOrUpdateMany(statuses map[string]*model.Status) error { if len(statuses) == 0 { return nil } // If there's only one status, use the existing method if len(statuses) == 1 { for _, st := range statuses { return s.SaveOrUpdate(st) } } query := s.getQueryBuilder(). Insert("Status"). Columns("UserId", "Status", quoteColumnName(s.DriverName(), "Manual"), "LastActivityAt", "DNDEndTime", "PrevStatus") // Add values for each unique status for _, st := range statuses { query = query.Values(st.UserId, st.Status, st.Manual, st.LastActivityAt, st.DNDEndTime, st.PrevStatus) } // Handle upsert for PostgreSQL query = query.SuffixExpr(sq.Expr("ON CONFLICT (userid) DO UPDATE SET Status = EXCLUDED.Status, Manual = EXCLUDED.Manual, LastActivityAt = EXCLUDED.LastActivityAt, DNDEndTime = EXCLUDED.DNDEndTime, PrevStatus = EXCLUDED.PrevStatus")) if _, err := s.GetMaster().ExecBuilder(query); err != nil { return errors.Wrap(err, "failed to upsert multiple Status records") } return nil } func (s SqlStatusStore) Get(userId string) (*model.Status, error) { query := s.statusSelectQuery.Where(sq.Eq{"UserId": userId}) var status model.Status if err := s.GetReplica().GetBuilder(&status, query); err != nil { if err == sql.ErrNoRows { return nil, store.NewErrNotFound("Status", fmt.Sprintf("userId=%s", userId)) } return nil, errors.Wrapf(err, "failed to get Status with userId=%s", userId) } return &status, nil } func (s SqlStatusStore) GetByIds(userIds []string) ([]*model.Status, error) { query := s.statusSelectQuery.Where(sq.Eq{"UserId": userIds}) statuses := []*model.Status{} err := s.GetReplica().SelectBuilder(&statuses, query) if err != nil { return nil, errors.Wrap(err, "failed to find Statuses") } return statuses, nil } func (s SqlStatusStore) UpdateExpiredDNDStatuses() (_ []*model.Status, err error) { queryString := s.getQueryBuilder(). Update("Status"). Where( sq.And{ sq.Eq{"Status": model.StatusDnd}, sq.Gt{"DNDEndTime": 0}, sq.LtOrEq{"DNDEndTime": time.Now().UTC().Unix()}, }, ). Set("Status", sq.Expr("PrevStatus")). Set("PrevStatus", model.StatusDnd). Set("DNDEndTime", 0). Set(quoteColumnName(s.DriverName(), "Manual"), false). Suffix("RETURNING *") statuses := []*model.Status{} err = s.GetMaster().SelectBuilder(&statuses, queryString) if err != nil { return nil, errors.Wrap(err, "failed to find Statuses") } return statuses, nil } func (s SqlStatusStore) ResetAll() error { if _, err := s.GetMaster().Exec(fmt.Sprintf("UPDATE Status SET Status = ? WHERE %s = false", quoteColumnName(s.DriverName(), "Manual")), model.StatusOffline); err != nil { return errors.Wrap(err, "failed to update Statuses") } return nil } func (s SqlStatusStore) GetTotalActiveUsersCount() (int64, error) { time := model.GetMillis() - (1000 * 60 * 60 * 24) query := s.getQueryBuilder(). Select("COUNT(UserId)"). From("Status"). Where(sq.Gt{"LastActivityAt": time}) var count int64 err := s.GetReplica().GetBuilder(&count, query) if err != nil { return count, errors.Wrap(err, "failed to count active users") } return count, nil } func (s SqlStatusStore) UpdateLastActivityAt(userId string, lastActivityAt int64) error { builder := s.getQueryBuilder(). Update("Status"). Set("LastActivityAt", lastActivityAt). Where(sq.Eq{"UserId": userId}) if _, err := s.GetMaster().ExecBuilder(builder); err != nil { return errors.Wrapf(err, "failed to update last activity for userId=%s", userId) } return nil }