github.com/coincircle/mattermost-server@v4.8.1-0.20180321182714-9d701c704416+incompatible/store/sqlstore/supplier_reactions.go (about) 1 // Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. 2 // See License.txt for license information. 3 4 package sqlstore 5 6 import ( 7 "context" 8 "net/http" 9 10 l4g "github.com/alecthomas/log4go" 11 12 "github.com/mattermost/gorp" 13 "github.com/mattermost/mattermost-server/model" 14 "github.com/mattermost/mattermost-server/store" 15 "github.com/mattermost/mattermost-server/utils" 16 ) 17 18 func initSqlSupplierReactions(sqlStore SqlStore) { 19 for _, db := range sqlStore.GetAllConns() { 20 table := db.AddTableWithName(model.Reaction{}, "Reactions").SetKeys(false, "UserId", "PostId", "EmojiName") 21 table.ColMap("UserId").SetMaxSize(26) 22 table.ColMap("PostId").SetMaxSize(26) 23 table.ColMap("EmojiName").SetMaxSize(64) 24 } 25 } 26 27 func (s *SqlSupplier) ReactionSave(ctx context.Context, reaction *model.Reaction, hints ...store.LayeredStoreHint) *store.LayeredStoreSupplierResult { 28 result := store.NewSupplierResult() 29 30 reaction.PreSave() 31 if result.Err = reaction.IsValid(); result.Err != nil { 32 return result 33 } 34 35 if transaction, err := s.GetMaster().Begin(); err != nil { 36 result.Err = model.NewAppError("SqlReactionStore.Save", "store.sql_reaction.save.begin.app_error", nil, err.Error(), http.StatusInternalServerError) 37 } else { 38 err := saveReactionAndUpdatePost(transaction, reaction) 39 40 if err != nil { 41 transaction.Rollback() 42 43 // We don't consider duplicated save calls as an error 44 if !IsUniqueConstraintError(err, []string{"reactions_pkey", "PRIMARY"}) { 45 result.Err = model.NewAppError("SqlPreferenceStore.Save", "store.sql_reaction.save.save.app_error", nil, err.Error(), http.StatusBadRequest) 46 } 47 } else { 48 if err := transaction.Commit(); err != nil { 49 // don't need to rollback here since the transaction is already closed 50 result.Err = model.NewAppError("SqlPreferenceStore.Save", "store.sql_reaction.save.commit.app_error", nil, err.Error(), http.StatusInternalServerError) 51 } 52 } 53 54 if result.Err == nil { 55 result.Data = reaction 56 } 57 } 58 59 return result 60 } 61 62 func (s *SqlSupplier) ReactionDelete(ctx context.Context, reaction *model.Reaction, hints ...store.LayeredStoreHint) *store.LayeredStoreSupplierResult { 63 result := store.NewSupplierResult() 64 65 if transaction, err := s.GetMaster().Begin(); err != nil { 66 result.Err = model.NewAppError("SqlReactionStore.Delete", "store.sql_reaction.delete.begin.app_error", nil, err.Error(), http.StatusInternalServerError) 67 } else { 68 err := deleteReactionAndUpdatePost(transaction, reaction) 69 70 if err != nil { 71 transaction.Rollback() 72 73 result.Err = model.NewAppError("SqlPreferenceStore.Delete", "store.sql_reaction.delete.app_error", nil, err.Error(), http.StatusInternalServerError) 74 } else if err := transaction.Commit(); err != nil { 75 // don't need to rollback here since the transaction is already closed 76 result.Err = model.NewAppError("SqlPreferenceStore.Delete", "store.sql_reaction.delete.commit.app_error", nil, err.Error(), http.StatusInternalServerError) 77 } else { 78 result.Data = reaction 79 } 80 } 81 82 return result 83 } 84 85 func (s *SqlSupplier) ReactionGetForPost(ctx context.Context, postId string, hints ...store.LayeredStoreHint) *store.LayeredStoreSupplierResult { 86 result := store.NewSupplierResult() 87 88 var reactions []*model.Reaction 89 90 if _, err := s.GetReplica().Select(&reactions, 91 `SELECT 92 * 93 FROM 94 Reactions 95 WHERE 96 PostId = :PostId 97 ORDER BY 98 CreateAt`, map[string]interface{}{"PostId": postId}); err != nil { 99 result.Err = model.NewAppError("SqlReactionStore.GetForPost", "store.sql_reaction.get_for_post.app_error", nil, "", http.StatusInternalServerError) 100 } else { 101 result.Data = reactions 102 } 103 104 return result 105 } 106 107 func (s *SqlSupplier) ReactionDeleteAllWithEmojiName(ctx context.Context, emojiName string, hints ...store.LayeredStoreHint) *store.LayeredStoreSupplierResult { 108 result := store.NewSupplierResult() 109 110 var reactions []*model.Reaction 111 112 if _, err := s.GetReplica().Select(&reactions, 113 `SELECT 114 * 115 FROM 116 Reactions 117 WHERE 118 EmojiName = :EmojiName`, map[string]interface{}{"EmojiName": emojiName}); err != nil { 119 result.Err = model.NewAppError("SqlReactionStore.DeleteAllWithEmojiName", 120 "store.sql_reaction.delete_all_with_emoji_name.get_reactions.app_error", nil, 121 "emoji_name="+emojiName+", error="+err.Error(), http.StatusInternalServerError) 122 return result 123 } 124 125 if _, err := s.GetMaster().Exec( 126 `DELETE FROM 127 Reactions 128 WHERE 129 EmojiName = :EmojiName`, map[string]interface{}{"EmojiName": emojiName}); err != nil { 130 result.Err = model.NewAppError("SqlReactionStore.DeleteAllWithEmojiName", 131 "store.sql_reaction.delete_all_with_emoji_name.delete_reactions.app_error", nil, 132 "emoji_name="+emojiName+", error="+err.Error(), http.StatusInternalServerError) 133 return result 134 } 135 136 for _, reaction := range reactions { 137 if _, err := s.GetMaster().Exec(UPDATE_POST_HAS_REACTIONS_ON_DELETE_QUERY, 138 map[string]interface{}{"PostId": reaction.PostId, "UpdateAt": model.GetMillis()}); err != nil { 139 l4g.Warn(utils.T("store.sql_reaction.delete_all_with_emoji_name.update_post.warn"), reaction.PostId, err.Error()) 140 } 141 } 142 143 return result 144 } 145 146 func (s *SqlSupplier) ReactionPermanentDeleteBatch(ctx context.Context, endTime int64, limit int64, hints ...store.LayeredStoreHint) *store.LayeredStoreSupplierResult { 147 result := store.NewSupplierResult() 148 149 var query string 150 if s.DriverName() == "postgres" { 151 query = "DELETE from Reactions WHERE CreateAt = any (array (SELECT CreateAt FROM Reactions WHERE CreateAt < :EndTime LIMIT :Limit))" 152 } else { 153 query = "DELETE from Reactions WHERE CreateAt < :EndTime LIMIT :Limit" 154 } 155 156 sqlResult, err := s.GetMaster().Exec(query, map[string]interface{}{"EndTime": endTime, "Limit": limit}) 157 if err != nil { 158 result.Err = model.NewAppError("SqlReactionStore.PermanentDeleteBatch", "store.sql_reaction.permanent_delete_batch.app_error", nil, ""+err.Error(), http.StatusInternalServerError) 159 } else { 160 rowsAffected, err1 := sqlResult.RowsAffected() 161 if err1 != nil { 162 result.Err = model.NewAppError("SqlReactionStore.PermanentDeleteBatch", "store.sql_reaction.permanent_delete_batch.app_error", nil, ""+err.Error(), http.StatusInternalServerError) 163 result.Data = int64(0) 164 } else { 165 result.Data = rowsAffected 166 } 167 } 168 169 return result 170 } 171 172 func saveReactionAndUpdatePost(transaction *gorp.Transaction, reaction *model.Reaction) error { 173 if err := transaction.Insert(reaction); err != nil { 174 return err 175 } 176 177 return updatePostForReactionsOnInsert(transaction, reaction.PostId) 178 } 179 180 func deleteReactionAndUpdatePost(transaction *gorp.Transaction, reaction *model.Reaction) error { 181 if _, err := transaction.Exec( 182 `DELETE FROM 183 Reactions 184 WHERE 185 PostId = :PostId AND 186 UserId = :UserId AND 187 EmojiName = :EmojiName`, 188 map[string]interface{}{"PostId": reaction.PostId, "UserId": reaction.UserId, "EmojiName": reaction.EmojiName}); err != nil { 189 return err 190 } 191 192 return updatePostForReactionsOnDelete(transaction, reaction.PostId) 193 } 194 195 const ( 196 // Set HasReactions = true if and only if the post has reactions, update UpdateAt only if HasReactions changes 197 UPDATE_POST_HAS_REACTIONS_ON_DELETE_QUERY = `UPDATE 198 Posts 199 SET 200 UpdateAt = (CASE 201 WHEN HasReactions != (SELECT count(0) > 0 FROM Reactions WHERE PostId = :PostId) THEN :UpdateAt 202 ELSE UpdateAt 203 END), 204 HasReactions = (SELECT count(0) > 0 FROM Reactions WHERE PostId = :PostId) 205 WHERE 206 Id = :PostId` 207 ) 208 209 func updatePostForReactionsOnDelete(transaction *gorp.Transaction, postId string) error { 210 _, err := transaction.Exec(UPDATE_POST_HAS_REACTIONS_ON_DELETE_QUERY, map[string]interface{}{"PostId": postId, "UpdateAt": model.GetMillis()}) 211 212 return err 213 } 214 215 func updatePostForReactionsOnInsert(transaction *gorp.Transaction, postId string) error { 216 _, err := transaction.Exec( 217 `UPDATE 218 Posts 219 SET 220 HasReactions = True, 221 UpdateAt = :UpdateAt 222 WHERE 223 Id = :PostId AND HasReactions = False`, 224 map[string]interface{}{"PostId": postId, "UpdateAt": model.GetMillis()}) 225 226 return err 227 }