github.com/mad-app/mattermost-server@v5.11.1+incompatible/store/sqlstore/channel_member_history_store.go (about)

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