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 }