github.com/xzl8028/xenia-server@v0.0.0-20190809101854-18450a97da63/store/sqlstore/channel_member_history_store.go (about)

     1  // Copyright (c) 2017-present Xenia, Inc. All Rights Reserved.
     2  // See License.txt for license information.
     3  
     4  package sqlstore
     5  
     6  import (
     7  	"fmt"
     8  	"net/http"
     9  
    10  	"database/sql"
    11  
    12  	"github.com/xzl8028/xenia-server/mlog"
    13  	"github.com/xzl8028/xenia-server/model"
    14  	"github.com/xzl8028/xenia-server/store"
    15  )
    16  
    17  type SqlChannelMemberHistoryStore struct {
    18  	SqlStore
    19  }
    20  
    21  func NewSqlChannelMemberHistoryStore(sqlStore SqlStore) store.ChannelMemberHistoryStore {
    22  	s := &SqlChannelMemberHistoryStore{
    23  		SqlStore: sqlStore,
    24  	}
    25  
    26  	for _, db := range sqlStore.GetAllConns() {
    27  		table := db.AddTableWithName(model.ChannelMemberHistory{}, "ChannelMemberHistory").SetKeys(false, "ChannelId", "UserId", "JoinTime")
    28  		table.ColMap("ChannelId").SetMaxSize(26)
    29  		table.ColMap("UserId").SetMaxSize(26)
    30  		table.ColMap("JoinTime").SetNotNull(true)
    31  	}
    32  
    33  	return s
    34  }
    35  
    36  func (s SqlChannelMemberHistoryStore) LogJoinEvent(userId string, channelId string, joinTime int64) *model.AppError {
    37  	channelMemberHistory := &model.ChannelMemberHistory{
    38  		UserId:    userId,
    39  		ChannelId: channelId,
    40  		JoinTime:  joinTime,
    41  	}
    42  
    43  	if err := s.GetMaster().Insert(channelMemberHistory); err != nil {
    44  		return model.NewAppError("SqlChannelMemberHistoryStore.LogJoinEvent", "store.sql_channel_member_history.log_join_event.app_error", nil, err.Error(), http.StatusInternalServerError)
    45  	}
    46  	return nil
    47  }
    48  
    49  func (s SqlChannelMemberHistoryStore) LogLeaveEvent(userId string, channelId string, leaveTime int64) *model.AppError {
    50  	query := `
    51  		UPDATE ChannelMemberHistory
    52  		SET LeaveTime = :LeaveTime
    53  		WHERE UserId = :UserId
    54  		AND ChannelId = :ChannelId
    55  		AND LeaveTime IS NULL`
    56  
    57  	params := map[string]interface{}{"UserId": userId, "ChannelId": channelId, "LeaveTime": leaveTime}
    58  	sqlResult, err := s.GetMaster().Exec(query, params)
    59  	if err != nil {
    60  		return model.NewAppError("SqlChannelMemberHistoryStore.LogLeaveEvent", "store.sql_channel_member_history.log_leave_event.update_error", params, err.Error(), http.StatusInternalServerError)
    61  	}
    62  
    63  	if rows, err := sqlResult.RowsAffected(); err == nil && rows != 1 {
    64  		// there was no join event to update - this is best effort, so no need to raise an error
    65  		mlog.Warn(fmt.Sprintf("Channel join event for user %v and channel %v not found", userId, channelId), mlog.String("user_id", userId))
    66  	}
    67  	return nil
    68  }
    69  
    70  func (s SqlChannelMemberHistoryStore) GetUsersInChannelDuring(startTime int64, endTime int64, channelId string) ([]*model.ChannelMemberHistoryResult, *model.AppError) {
    71  	useChannelMemberHistory, err := s.hasDataAtOrBefore(startTime)
    72  	if err != nil {
    73  		return nil, model.NewAppError("SqlChannelMemberHistoryStore.GetUsersInChannelAt", "store.sql_channel_member_history.get_users_in_channel_during.app_error", nil, err.Error(), http.StatusInternalServerError)
    74  	}
    75  
    76  	if useChannelMemberHistory {
    77  		// the export period starts after the ChannelMemberHistory table was first introduced, so we can use the
    78  		// data from it for our export
    79  		channelMemberHistories, err2 := s.getFromChannelMemberHistoryTable(startTime, endTime, channelId)
    80  		if err2 != nil {
    81  			return nil, model.NewAppError("SqlChannelMemberHistoryStore.GetUsersInChannelAt", "store.sql_channel_member_history.get_users_in_channel_during.app_error", nil, err2.Error(), http.StatusInternalServerError)
    82  		}
    83  		return channelMemberHistories, nil
    84  	}
    85  
    86  	// the export period starts before the ChannelMemberHistory table was introduced, so we need to fake the
    87  	// data by assuming that anybody who has ever joined the channel in question was present during the export period.
    88  	// this may not always be true, but it's better than saying that somebody wasn't there when they were
    89  	channelMemberHistories, err := s.getFromChannelMembersTable(startTime, endTime, channelId)
    90  	if err != nil {
    91  		return nil, model.NewAppError("SqlChannelMemberHistoryStore.GetUsersInChannelAt", "store.sql_channel_member_history.get_users_in_channel_during.app_error", nil, err.Error(), http.StatusInternalServerError)
    92  	}
    93  	return channelMemberHistories, nil
    94  }
    95  
    96  func (s SqlChannelMemberHistoryStore) hasDataAtOrBefore(time int64) (bool, error) {
    97  	type NullableCountResult struct {
    98  		Min sql.NullInt64
    99  	}
   100  	var result NullableCountResult
   101  	query := "SELECT MIN(JoinTime) AS Min FROM ChannelMemberHistory"
   102  	if err := s.GetReplica().SelectOne(&result, query); err != nil {
   103  		return false, err
   104  	} else if result.Min.Valid {
   105  		return result.Min.Int64 <= time, nil
   106  	} else {
   107  		// if the result was null, there are no rows in the table, so there is no data from before
   108  		return false, nil
   109  	}
   110  }
   111  
   112  func (s SqlChannelMemberHistoryStore) getFromChannelMemberHistoryTable(startTime int64, endTime int64, channelId string) ([]*model.ChannelMemberHistoryResult, error) {
   113  	query := `
   114  			SELECT
   115  				cmh.*,
   116  				u.Email,
   117  				u.Username,
   118  			    Bots.UserId IS NOT NULL AS IsBot
   119  			FROM ChannelMemberHistory cmh
   120  			INNER JOIN Users u ON cmh.UserId = u.Id
   121  			LEFT JOIN Bots ON Bots.UserId = u.Id
   122  			WHERE cmh.ChannelId = :ChannelId
   123  			AND cmh.JoinTime <= :EndTime
   124  			AND (cmh.LeaveTime IS NULL OR cmh.LeaveTime >= :StartTime)
   125  			ORDER BY cmh.JoinTime ASC`
   126  
   127  	params := map[string]interface{}{"ChannelId": channelId, "StartTime": startTime, "EndTime": endTime}
   128  	var histories []*model.ChannelMemberHistoryResult
   129  	if _, err := s.GetReplica().Select(&histories, query, params); err != nil {
   130  		return nil, err
   131  	} else {
   132  		return histories, nil
   133  	}
   134  }
   135  
   136  func (s SqlChannelMemberHistoryStore) getFromChannelMembersTable(startTime int64, endTime int64, channelId string) ([]*model.ChannelMemberHistoryResult, error) {
   137  	query := `
   138  		SELECT DISTINCT
   139  			ch.ChannelId,
   140  			ch.UserId,
   141  			u.Email,
   142  			u.Username,
   143  		    Bots.UserId IS NOT NULL AS IsBot
   144  
   145  		FROM ChannelMembers AS ch
   146  		INNER JOIN Users AS u ON ch.UserId = u.id
   147  		LEFT JOIN Bots ON Bots.UserId = u.Id
   148  		WHERE ch.ChannelId = :ChannelId`
   149  
   150  	params := map[string]interface{}{"ChannelId": channelId}
   151  	var histories []*model.ChannelMemberHistoryResult
   152  	if _, err := s.GetReplica().Select(&histories, query, params); err != nil {
   153  		return nil, err
   154  	} else {
   155  		// we have to fill in the join/leave times, because that data doesn't exist in the channel members table
   156  		for _, channelMemberHistory := range histories {
   157  			channelMemberHistory.JoinTime = startTime
   158  			channelMemberHistory.LeaveTime = model.NewInt64(endTime)
   159  		}
   160  		return histories, nil
   161  	}
   162  }
   163  
   164  func (s SqlChannelMemberHistoryStore) PermanentDeleteBatch(endTime int64, limit int64) (int64, *model.AppError) {
   165  	var query string
   166  	if s.DriverName() == model.DATABASE_DRIVER_POSTGRES {
   167  		query =
   168  			`DELETE FROM ChannelMemberHistory
   169  				 WHERE ctid IN (
   170  					SELECT ctid FROM ChannelMemberHistory
   171  					WHERE LeaveTime IS NOT NULL
   172  					AND LeaveTime <= :EndTime
   173  					LIMIT :Limit
   174  				);`
   175  	} else {
   176  		query =
   177  			`DELETE FROM ChannelMemberHistory
   178  				 WHERE LeaveTime IS NOT NULL
   179  				 AND LeaveTime <= :EndTime
   180  				 LIMIT :Limit`
   181  	}
   182  
   183  	params := map[string]interface{}{"EndTime": endTime, "Limit": limit}
   184  	sqlResult, err := s.GetMaster().Exec(query, params)
   185  	if err != nil {
   186  		return int64(0), model.NewAppError("SqlChannelMemberHistoryStore.PermanentDeleteBatchForChannel", "store.sql_channel_member_history.permanent_delete_batch.app_error", params, err.Error(), http.StatusInternalServerError)
   187  	}
   188  
   189  	rowsAffected, err := sqlResult.RowsAffected()
   190  	if err != nil {
   191  		return int64(0), model.NewAppError("SqlChannelMemberHistoryStore.PermanentDeleteBatchForChannel", "store.sql_channel_member_history.permanent_delete_batch.app_error", params, err.Error(), http.StatusInternalServerError)
   192  	}
   193  	return rowsAffected, nil
   194  }