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  }