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