From 6cc3bfe7a96e66d27a5b7f08bc1dc15a742b36ab Mon Sep 17 00:00:00 2001 From: Christopher Speller Date: Thu, 22 Oct 2015 09:31:27 -0400 Subject: Refactoring direct channel creation to use transaction to avaoid state where channel is created without both users --- store/sql_channel_store.go | 193 +++++++++++++++++++++++++++++----------- store/sql_channel_store_test.go | 101 +++++++++++++++++++++ store/store.go | 1 + 3 files changed, 241 insertions(+), 54 deletions(-) (limited to 'store') diff --git a/store/sql_channel_store.go b/store/sql_channel_store.go index 56e190fee..8bedf0632 100644 --- a/store/sql_channel_store.go +++ b/store/sql_channel_store.go @@ -5,6 +5,7 @@ package store import ( l4g "code.google.com/p/log4go" + "github.com/go-gorp/gorp" "github.com/mattermost/platform/model" "github.com/mattermost/platform/utils" ) @@ -97,49 +98,76 @@ func (s SqlChannelStore) Save(channel *model.Channel) StoreChannel { storeChannel := make(StoreChannel) go func() { - result := StoreResult{} - - if len(channel.Id) > 0 { - result.Err = model.NewAppError("SqlChannelStore.Save", - "Must call update for exisiting channel", "id="+channel.Id) - storeChannel <- result - close(storeChannel) - return + var result StoreResult + if channel.Type == model.CHANNEL_DIRECT { + result.Err = model.NewAppError("SqlChannelStore.Save", "Use SaveDirectChannel to create a direct channel", "") + } else { + if transaction, err := s.GetMaster().Begin(); err != nil { + result.Err = model.NewAppError("SqlChannelStore.Save", "Unable to open transaction", err.Error()) + } else { + result = s.saveChannelT(transaction, channel) + if result.Err != nil { + transaction.Rollback() + } else { + if err := transaction.Commit(); err != nil { + result.Err = model.NewAppError("SqlChannelStore.Save", "Unable to commit transaction", err.Error()) + } + } + } } - channel.PreSave() - if result.Err = channel.IsValid(); result.Err != nil { - storeChannel <- result - close(storeChannel) - return - } + storeChannel <- result + close(storeChannel) + }() - if count, err := s.GetMaster().SelectInt("SELECT COUNT(0) FROM Channels WHERE TeamId = :TeamId AND DeleteAt = 0 AND (Type = 'O' OR Type = 'P')", map[string]interface{}{"TeamId": channel.TeamId}); err != nil { - result.Err = model.NewAppError("SqlChannelStore.Save", "Failed to get current channel count", "teamId="+channel.TeamId+", "+err.Error()) - storeChannel <- result - close(storeChannel) - return - } else if count > 150 { - result.Err = model.NewAppError("SqlChannelStore.Save", "You've reached the limit of the number of allowed channels.", "teamId="+channel.TeamId) - storeChannel <- result - close(storeChannel) - return - } + return storeChannel +} - if err := s.GetMaster().Insert(channel); err != nil { - if IsUniqueConstraintError(err.Error(), "Name", "channels_name_teamid_key") { - dupChannel := model.Channel{} - s.GetReplica().SelectOne(&dupChannel, "SELECT * FROM Channels WHERE TeamId = :TeamId AND Name = :Name AND DeleteAt > 0", map[string]interface{}{"TeamId": channel.TeamId, "Name": channel.Name}) - if dupChannel.DeleteAt > 0 { - result.Err = model.NewAppError("SqlChannelStore.Update", "A channel with that URL was previously created", "id="+channel.Id+", "+err.Error()) +func (s SqlChannelStore) SaveDirectChannel(directchannel *model.Channel, member1 *model.ChannelMember, member2 *model.ChannelMember) StoreChannel { + storeChannel := make(StoreChannel) + + go func() { + var result StoreResult + + if directchannel.Type != model.CHANNEL_DIRECT { + result.Err = model.NewAppError("SqlChannelStore.SaveDirectChannel", "Not a direct channel attempted to be created with SaveDirectChannel", "") + } else { + if transaction, err := s.GetMaster().Begin(); err != nil { + result.Err = model.NewAppError("SqlChannelStore.SaveDirectChannel", "Unable to open transaction", err.Error()) + } else { + channelResult := s.saveChannelT(transaction, directchannel) + + if channelResult.Err != nil { + transaction.Rollback() + result.Err = channelResult.Err } else { - result.Err = model.NewAppError("SqlChannelStore.Update", "A channel with that URL already exists", "id="+channel.Id+", "+err.Error()) + newChannel := channelResult.Data.(*model.Channel) + // Members need new channel ID + member1.ChannelId = newChannel.Id + member2.ChannelId = newChannel.Id + + member1Result := s.saveMemberT(transaction, member1, newChannel) + member2Result := s.saveMemberT(transaction, member2, newChannel) + + if member1Result.Err != nil || member2Result.Err != nil { + transaction.Rollback() + details := "" + if member1Result.Err != nil { + details += "Member1Err: " + member1Result.Err.Message + } + if member2Result.Err != nil { + details += "Member2Err: " + member2Result.Err.Message + } + result.Err = model.NewAppError("SqlChannelStore.SaveDirectChannel", "Unable to add direct channel members", details) + } else { + if err := transaction.Commit(); err != nil { + result.Err = model.NewAppError("SqlChannelStore.SaveDirectChannel", "Ubable to commit transaction", err.Error()) + } else { + result = channelResult + } + } } - } else { - result.Err = model.NewAppError("SqlChannelStore.Save", "We couldn't save the channel", "id="+channel.Id+", "+err.Error()) } - } else { - result.Data = channel } storeChannel <- result @@ -149,6 +177,46 @@ func (s SqlChannelStore) Save(channel *model.Channel) StoreChannel { return storeChannel } +func (s SqlChannelStore) saveChannelT(transaction *gorp.Transaction, channel *model.Channel) StoreResult { + result := StoreResult{} + + if len(channel.Id) > 0 { + result.Err = model.NewAppError("SqlChannelStore.Save", "Must call update for exisiting channel", "id="+channel.Id) + return result + } + + channel.PreSave() + if result.Err = channel.IsValid(); result.Err != nil { + return result + } + + if count, err := transaction.SelectInt("SELECT COUNT(0) FROM Channels WHERE TeamId = :TeamId AND DeleteAt = 0 AND (Type = 'O' OR Type = 'P')", map[string]interface{}{"TeamId": channel.TeamId}); err != nil { + result.Err = model.NewAppError("SqlChannelStore.Save", "Failed to get current channel count", "teamId="+channel.TeamId+", "+err.Error()) + return result + } else if count > 150 { + result.Err = model.NewAppError("SqlChannelStore.Save", "You've reached the limit of the number of allowed channels.", "teamId="+channel.TeamId) + return result + } + + if err := transaction.Insert(channel); err != nil { + if IsUniqueConstraintError(err.Error(), "Name", "channels_name_teamid_key") { + dupChannel := model.Channel{} + s.GetReplica().SelectOne(&dupChannel, "SELECT * FROM Channels WHERE TeamId = :TeamId AND Name = :Name AND DeleteAt > 0", map[string]interface{}{"TeamId": channel.TeamId, "Name": channel.Name}) + if dupChannel.DeleteAt > 0 { + result.Err = model.NewAppError("SqlChannelStore.Update", "A channel with that URL was previously created", "id="+channel.Id+", "+err.Error()) + } else { + result.Err = model.NewAppError("SqlChannelStore.Update", "A channel with that URL already exists", "id="+channel.Id+", "+err.Error()) + } + } else { + result.Err = model.NewAppError("SqlChannelStore.Save", "We couldn't save the channel", "id="+channel.Id+", "+err.Error()) + } + } else { + result.Data = channel + } + + return result +} + func (s SqlChannelStore) Update(channel *model.Channel) StoreChannel { storeChannel := make(StoreChannel) @@ -396,31 +464,27 @@ func (s SqlChannelStore) SaveMember(member *model.ChannelMember) StoreChannel { storeChannel := make(StoreChannel) go func() { - result := StoreResult{} - + var result StoreResult // Grab the channel we are saving this member to if cr := <-s.Get(member.ChannelId); cr.Err != nil { result.Err = cr.Err } else { channel := cr.Data.(*model.Channel) - member.PreSave() - if result.Err = member.IsValid(); result.Err != nil { - storeChannel <- result - return - } - - if err := s.GetMaster().Insert(member); err != nil { - if IsUniqueConstraintError(err.Error(), "ChannelId", "channelmembers_pkey") { - result.Err = model.NewAppError("SqlChannelStore.SaveMember", "A channel member with that id already exists", "channel_id="+member.ChannelId+", user_id="+member.UserId+", "+err.Error()) - } else { - result.Err = model.NewAppError("SqlChannelStore.SaveMember", "We couldn't save the channel member", "channel_id="+member.ChannelId+", user_id="+member.UserId+", "+err.Error()) - } + if transaction, err := s.GetMaster().Begin(); err != nil { + result.Err = model.NewAppError("SqlChannelStore.SaveMember", "Unable to open transaction", err.Error()) } else { - result.Data = member - // If sucessfull record members have changed in channel - if mu := <-s.extraUpdated(channel); mu.Err != nil { - result.Err = mu.Err + result = s.saveMemberT(transaction, member, channel) + if result.Err != nil { + transaction.Rollback() + } else { + if err := transaction.Commit(); err != nil { + result.Err = model.NewAppError("SqlChannelStore.SaveMember", "Unable to commit transaction", err.Error()) + } + // If sucessfull record members have changed in channel + if mu := <-s.extraUpdated(channel); mu.Err != nil { + result.Err = mu.Err + } } } } @@ -432,6 +496,27 @@ func (s SqlChannelStore) SaveMember(member *model.ChannelMember) StoreChannel { return storeChannel } +func (s SqlChannelStore) saveMemberT(transaction *gorp.Transaction, member *model.ChannelMember, channel *model.Channel) StoreResult { + result := StoreResult{} + + member.PreSave() + if result.Err = member.IsValid(); result.Err != nil { + return result + } + + if err := transaction.Insert(member); err != nil { + if IsUniqueConstraintError(err.Error(), "ChannelId", "channelmembers_pkey") { + result.Err = model.NewAppError("SqlChannelStore.SaveMember", "A channel member with that id already exists", "channel_id="+member.ChannelId+", user_id="+member.UserId+", "+err.Error()) + } else { + result.Err = model.NewAppError("SqlChannelStore.SaveMember", "We couldn't save the channel member", "channel_id="+member.ChannelId+", user_id="+member.UserId+", "+err.Error()) + } + } else { + result.Data = member + } + + return result +} + func (s SqlChannelStore) UpdateMember(member *model.ChannelMember) StoreChannel { storeChannel := make(StoreChannel) diff --git a/store/sql_channel_store_test.go b/store/sql_channel_store_test.go index b4e0f7593..60d3de56a 100644 --- a/store/sql_channel_store_test.go +++ b/store/sql_channel_store_test.go @@ -33,6 +33,14 @@ func TestChannelStoreSave(t *testing.T) { t.Fatal("should be unique name") } + o1.Id = "" + o1.Name = "a" + model.NewId() + "b" + o1.Type = model.CHANNEL_DIRECT + if err := (<-store.Channel().Save(&o1)).Err; err == nil { + t.Fatal("Should not be able to save direct channel") + } + + o1.Type = model.CHANNEL_OPEN for i := 0; i < 150; i++ { o1.Id = "" o1.Name = "a" + model.NewId() + "b" @@ -48,6 +56,61 @@ func TestChannelStoreSave(t *testing.T) { } } +func TestChannelStoreSaveDirectChannel(t *testing.T) { + Setup() + + teamId := model.NewId() + + o1 := model.Channel{} + o1.TeamId = teamId + o1.DisplayName = "Name" + o1.Name = "a" + model.NewId() + "b" + o1.Type = model.CHANNEL_DIRECT + + u1 := model.User{} + u1.TeamId = model.NewId() + u1.Email = model.NewId() + u1.Nickname = model.NewId() + Must(store.User().Save(&u1)) + + u2 := model.User{} + u2.TeamId = model.NewId() + u2.Email = model.NewId() + u2.Nickname = model.NewId() + Must(store.User().Save(&u2)) + + m1 := model.ChannelMember{} + m1.ChannelId = o1.Id + m1.UserId = u1.Id + m1.NotifyProps = model.GetDefaultChannelNotifyProps() + + m2 := model.ChannelMember{} + m2.ChannelId = o1.Id + m2.UserId = u2.Id + m2.NotifyProps = model.GetDefaultChannelNotifyProps() + + if err := (<-store.Channel().SaveDirectChannel(&o1, &m1, &m2)).Err; err != nil { + t.Fatal("couldn't save direct channel", err) + } + + members := (<-store.Channel().GetMembers(o1.Id)).Data.([]model.ChannelMember) + if len(members) != 2 { + t.Fatal("should have saved 2 members") + } + + if err := (<-store.Channel().SaveDirectChannel(&o1, &m1, &m2)).Err; err == nil { + t.Fatal("shouldn't be able to update from save") + } + + o1.Id = "" + o1.Name = "a" + model.NewId() + "b" + o1.Type = model.CHANNEL_OPEN + if err := (<-store.Channel().SaveDirectChannel(&o1, &m1, &m2)).Err; err == nil { + t.Fatal("Should not be able to save non-direct channel") + } + +} + func TestChannelStoreUpdate(t *testing.T) { Setup() @@ -99,6 +162,44 @@ func TestChannelStoreGet(t *testing.T) { if err := (<-store.Channel().Get("")).Err; err == nil { t.Fatal("Missing id should have failed") } + + u1 := model.User{} + u1.TeamId = model.NewId() + u1.Email = model.NewId() + u1.Nickname = model.NewId() + Must(store.User().Save(&u1)) + + u2 := model.User{} + u2.TeamId = model.NewId() + u2.Email = model.NewId() + u2.Nickname = model.NewId() + Must(store.User().Save(&u2)) + + o2 := model.Channel{} + o2.TeamId = model.NewId() + o2.DisplayName = "Direct Name" + o2.Name = "a" + model.NewId() + "b" + o2.Type = model.CHANNEL_DIRECT + + m1 := model.ChannelMember{} + m1.ChannelId = o2.Id + m1.UserId = u1.Id + m1.NotifyProps = model.GetDefaultChannelNotifyProps() + + m2 := model.ChannelMember{} + m2.ChannelId = o2.Id + m2.UserId = u2.Id + m2.NotifyProps = model.GetDefaultChannelNotifyProps() + + Must(store.Channel().SaveDirectChannel(&o2, &m1, &m2)) + + if r2 := <-store.Channel().Get(o2.Id); r2.Err != nil { + t.Fatal(r2.Err) + } else { + if r2.Data.(*model.Channel).ToJson() != o2.ToJson() { + t.Fatal("invalid returned channel") + } + } } func TestChannelStoreDelete(t *testing.T) { diff --git a/store/store.go b/store/store.go index 27731cee1..1cf686a70 100644 --- a/store/store.go +++ b/store/store.go @@ -54,6 +54,7 @@ type TeamStore interface { type ChannelStore interface { Save(channel *model.Channel) StoreChannel + SaveDirectChannel(channel *model.Channel, member1 *model.ChannelMember, member2 *model.ChannelMember) StoreChannel Update(channel *model.Channel) StoreChannel Get(id string) StoreChannel Delete(channelId string, time int64) StoreChannel -- cgit v1.2.3-1-g7c22