Этот коммит содержится в:
=Corey Hulen
2015-06-14 23:53:32 -08:00
Коммит cf7a05f80f
665 изменённых файлов: 129987 добавлений и 0 удалений

75
store/redis.go Обычный файл
Просмотреть файл

@@ -0,0 +1,75 @@
// Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved.
// See License.txt for license information.
package store
import (
l4g "code.google.com/p/log4go"
"github.com/mattermost/platform/model"
"github.com/mattermost/platform/utils"
"gopkg.in/redis.v2"
"strings"
"time"
)
var client *redis.Client
func RedisClient() *redis.Client {
if client == nil {
addr := utils.Cfg.RedisSettings.DataSource
client = redis.NewTCPClient(&redis.Options{
Addr: addr,
Password: "",
DB: 0,
PoolSize: utils.Cfg.RedisSettings.MaxOpenConns,
})
l4g.Info("Pinging redis at '%v'", addr)
pong, err := client.Ping().Result()
if err != nil {
l4g.Critical("Failed to open redis connection to '%v' err:%v", addr, err)
time.Sleep(time.Second)
panic("Failed to open redis connection " + err.Error())
}
if pong != "PONG" {
l4g.Critical("Failed to ping redis connection to '%v' err:%v", addr, err)
time.Sleep(time.Second)
panic("Failed to open ping connection " + err.Error())
}
}
return client
}
func RedisClose() {
l4g.Info("Closing redis")
if client != nil {
client.Close()
client = nil
}
}
func PublishAndForget(message *model.Message) {
go func() {
c := RedisClient()
result := c.Publish(message.TeamId, message.ToJson())
if result.Err() != nil {
l4g.Error("Failed to publish message err=%v, payload=%v", result.Err(), message.ToJson())
}
}()
}
func GetMessageFromPayload(m interface{}) *model.Message {
if msg, found := m.(*redis.Message); found {
return model.MessageFromJson(strings.NewReader(msg.Payload))
} else {
return nil
}
}

59
store/redis_test.go Обычный файл
Просмотреть файл

@@ -0,0 +1,59 @@
// Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved.
// See License.txt for license information.
package store
import (
"fmt"
"github.com/mattermost/platform/model"
"github.com/mattermost/platform/utils"
"testing"
)
func TestRedis(t *testing.T) {
utils.LoadConfig("config.json")
c := RedisClient()
if c == nil {
t.Fatal("should have a valid redis connection")
}
pubsub := c.PubSub()
defer pubsub.Close()
m := model.NewMessage(model.NewId(), model.NewId(), model.NewId(), model.ACTION_TYPING)
m.Add("RootId", model.NewId())
err := pubsub.Subscribe(m.TeamId)
if err != nil {
t.Fatal(err)
}
// should be the subscribe success message
// lets gobble that up
if _, err := pubsub.Receive(); err != nil {
t.Fatal(err)
}
PublishAndForget(m)
fmt.Println("here1")
if msg, err := pubsub.Receive(); err != nil {
t.Fatal(err)
} else {
rmsg := GetMessageFromPayload(msg)
if m.TeamId != rmsg.TeamId {
t.Fatal("Ids do not match")
}
if m.Props["RootId"] != rmsg.Props["RootId"] {
t.Fatal("Ids do not match")
}
}
RedisClose()
}

88
store/sql_audit_store.go Обычный файл
Просмотреть файл

@@ -0,0 +1,88 @@
// Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved.
// See License.txt for license information.
package store
import (
"github.com/mattermost/platform/model"
)
type SqlAuditStore struct {
*SqlStore
}
func NewSqlAuditStore(sqlStore *SqlStore) AuditStore {
s := &SqlAuditStore{sqlStore}
for _, db := range sqlStore.GetAllConns() {
table := db.AddTableWithName(model.Audit{}, "Audits").SetKeys(false, "Id")
table.ColMap("Id").SetMaxSize(26)
table.ColMap("UserId").SetMaxSize(26)
table.ColMap("Action").SetMaxSize(64)
table.ColMap("ExtraInfo").SetMaxSize(128)
table.ColMap("IpAddress").SetMaxSize(64)
table.ColMap("SessionId").SetMaxSize(26)
}
return s
}
func (s SqlAuditStore) UpgradeSchemaIfNeeded() {
}
func (s SqlAuditStore) CreateIndexesIfNotExists() {
s.CreateIndexIfNotExists("idx_user_id", "Audits", "UserId")
}
func (s SqlAuditStore) Save(audit *model.Audit) StoreChannel {
storeChannel := make(StoreChannel)
go func() {
result := StoreResult{}
audit.Id = model.NewId()
audit.CreateAt = model.GetMillis()
if err := s.GetMaster().Insert(audit); err != nil {
result.Err = model.NewAppError("SqlAuditStore.Save",
"We encounted an error saving the audit", "user_id="+
audit.UserId+" action="+audit.Action)
}
storeChannel <- result
close(storeChannel)
}()
return storeChannel
}
func (s SqlAuditStore) Get(user_id string, limit int) StoreChannel {
storeChannel := make(StoreChannel)
go func() {
result := StoreResult{}
if limit > 1000 {
limit = 1000
result.Err = model.NewAppError("SqlAuditStore.Get", "Limit exceeded for paging", "user_id="+user_id)
storeChannel <- result
close(storeChannel)
return
}
var audits model.Audits
if _, err := s.GetReplica().Select(&audits, "SELECT * FROM Audits WHERE UserId = ? ORDER BY CreateAt DESC LIMIT ?",
user_id, limit); err != nil {
result.Err = model.NewAppError("SqlAuditStore.Get", "We encounted an error finding the audits", "user_id="+user_id)
} else {
result.Data = audits
}
storeChannel <- result
close(storeChannel)
}()
return storeChannel
}

40
store/sql_audit_store_test.go Обычный файл
Просмотреть файл

@@ -0,0 +1,40 @@
// Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved.
// See License.txt for license information.
package store
import (
"github.com/mattermost/platform/model"
"testing"
)
func TestSqlAuditStore(t *testing.T) {
Setup()
audit := &model.Audit{UserId: model.NewId(), IpAddress: "ipaddress", Action: "Action"}
<-store.Audit().Save(audit)
<-store.Audit().Save(audit)
<-store.Audit().Save(audit)
audit.ExtraInfo = "extra"
<-store.Audit().Save(audit)
c := store.Audit().Get(audit.UserId, 100)
result := <-c
audits := result.Data.(model.Audits)
if len(audits) != 4 {
t.Fatal("Failed to save and retrieve 4 audit logs")
}
if audits[0].ExtraInfo != "extra" {
t.Fatal("Failed to save property for extra info")
}
c = store.Audit().Get("missing", 100)
result = <-c
audits = result.Data.(model.Audits)
if len(audits) != 0 {
t.Fatal("Should have returned empty because user_id is missing")
}
}

554
store/sql_channel_store.go Обычный файл
Просмотреть файл

@@ -0,0 +1,554 @@
// Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved.
// See License.txt for license information.
package store
import (
"github.com/mattermost/platform/model"
"github.com/mattermost/platform/utils"
"strings"
)
type SqlChannelStore struct {
*SqlStore
}
func NewSqlChannelStore(sqlStore *SqlStore) ChannelStore {
s := &SqlChannelStore{sqlStore}
for _, db := range sqlStore.GetAllConns() {
table := db.AddTableWithName(model.Channel{}, "Channels").SetKeys(false, "Id")
table.ColMap("Id").SetMaxSize(26)
table.ColMap("TeamId").SetMaxSize(26)
table.ColMap("Type").SetMaxSize(1)
table.ColMap("DisplayName").SetMaxSize(64)
table.ColMap("Name").SetMaxSize(64)
table.SetUniqueTogether("Name", "TeamId")
table.ColMap("Description").SetMaxSize(1024)
tablem := db.AddTableWithName(model.ChannelMember{}, "ChannelMembers").SetKeys(false, "ChannelId", "UserId")
tablem.ColMap("ChannelId").SetMaxSize(26)
tablem.ColMap("UserId").SetMaxSize(26)
tablem.ColMap("Roles").SetMaxSize(64)
tablem.ColMap("NotifyLevel").SetMaxSize(20)
}
return s
}
func (s SqlChannelStore) UpgradeSchemaIfNeeded() {
}
func (s SqlChannelStore) CreateIndexesIfNotExists() {
s.CreateIndexIfNotExists("idx_team_id", "Channels", "TeamId")
}
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
}
channel.PreSave()
if result.Err = channel.IsValid(); result.Err != nil {
storeChannel <- result
close(storeChannel)
return
}
if count, err := s.GetMaster().SelectInt("SELECT COUNT(0) FROM Channels WHERE TeamId = ? AND DeleteAt = 0 AND (Type ='O' || Type ='P')", 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
}
if err := s.GetMaster().Insert(channel); err != nil {
if strings.Contains(err.Error(), "Duplicate entry") && strings.Contains(err.Error(), "for key 'Name'") {
result.Err = model.NewAppError("SqlChannelStore.Save", "A channel with that name 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
}
storeChannel <- result
close(storeChannel)
}()
return storeChannel
}
func (s SqlChannelStore) Update(channel *model.Channel) StoreChannel {
storeChannel := make(StoreChannel)
go func() {
result := StoreResult{}
channel.PreUpdate()
if result.Err = channel.IsValid(); result.Err != nil {
storeChannel <- result
close(storeChannel)
return
}
if count, err := s.GetMaster().Update(channel); err != nil {
if strings.Contains(err.Error(), "Duplicate entry") && strings.Contains(err.Error(), "for key 'Name'") {
result.Err = model.NewAppError("SqlChannelStore.Update", "A channel with that name already exists", "id="+channel.Id+", "+err.Error())
} else {
result.Err = model.NewAppError("SqlChannelStore.Update", "We encounted an error updating the channel", "id="+channel.Id+", "+err.Error())
}
} else if count != 1 {
result.Err = model.NewAppError("SqlChannelStore.Update", "We couldn't update the channel", "id="+channel.Id)
} else {
result.Data = channel
}
storeChannel <- result
close(storeChannel)
}()
return storeChannel
}
func (s SqlChannelStore) Get(id string) StoreChannel {
storeChannel := make(StoreChannel)
go func() {
result := StoreResult{}
if obj, err := s.GetReplica().Get(model.Channel{}, id); err != nil {
result.Err = model.NewAppError("SqlChannelStore.Get", "We encounted an error finding the channel", "id="+id+", "+err.Error())
} else if obj == nil {
result.Err = model.NewAppError("SqlChannelStore.Get", "We couldn't find the existing channel", "id="+id)
} else {
result.Data = obj.(*model.Channel)
}
storeChannel <- result
close(storeChannel)
}()
return storeChannel
}
func (s SqlChannelStore) Delete(channelId string, time int64) StoreChannel {
storeChannel := make(StoreChannel)
go func() {
result := StoreResult{}
_, err := s.GetMaster().Exec("Update Channels SET DeleteAt = ?, UpdateAt = ? WHERE Id = ?", time, time, channelId)
if err != nil {
result.Err = model.NewAppError("SqlChannelStore.Delete", "We couldn't delete the channel", "id="+channelId+", err="+err.Error())
}
storeChannel <- result
close(storeChannel)
}()
return storeChannel
}
type channelWithMember struct {
model.Channel
model.ChannelMember
}
func (s SqlChannelStore) GetChannels(teamId string, userId string) StoreChannel {
storeChannel := make(StoreChannel)
go func() {
result := StoreResult{}
var data []channelWithMember
_, err := s.GetReplica().Select(&data, "SELECT * FROM Channels, ChannelMembers WHERE Id = ChannelId AND TeamId = ? AND UserId = ? AND DeleteAt = 0 ORDER BY DisplayName", teamId, userId)
if err != nil {
result.Err = model.NewAppError("SqlChannelStore.GetChannels", "We couldn't get the channels", "teamId="+teamId+", userId="+userId+", err="+err.Error())
} else {
channels := &model.ChannelList{make([]*model.Channel, len(data)), make(map[string]*model.ChannelMember)}
for i := range data {
v := data[i]
channels.Channels[i] = &v.Channel
channels.Members[v.Channel.Id] = &v.ChannelMember
}
if len(channels.Channels) == 0 {
result.Err = model.NewAppError("SqlChannelStore.GetChannels", "No channels were found", "teamId="+teamId+", userId="+userId)
} else {
result.Data = channels
}
}
storeChannel <- result
close(storeChannel)
}()
return storeChannel
}
func (s SqlChannelStore) GetMoreChannels(teamId string, userId string) StoreChannel {
storeChannel := make(StoreChannel)
go func() {
result := StoreResult{}
var data []*model.Channel
_, err := s.GetReplica().Select(&data,
`SELECT
*
FROM
Channels
WHERE
TeamId = ?
AND Type IN ("O")
AND DeleteAt = 0
AND Id NOT IN (SELECT
Channels.Id
FROM
Channels,
ChannelMembers
WHERE
Id = ChannelId
AND TeamId = ?
AND UserId = ?
AND DeleteAt = 0)
ORDER BY DisplayName`,
teamId, teamId, userId)
if err != nil {
result.Err = model.NewAppError("SqlChannelStore.GetMoreChannels", "We couldn't get the channels", "teamId="+teamId+", userId="+userId+", err="+err.Error())
} else {
result.Data = &model.ChannelList{data, make(map[string]*model.ChannelMember)}
}
storeChannel <- result
close(storeChannel)
}()
return storeChannel
}
func (s SqlChannelStore) GetByName(teamId string, name string) StoreChannel {
storeChannel := make(StoreChannel)
go func() {
result := StoreResult{}
channel := model.Channel{}
if err := s.GetReplica().SelectOne(&channel, "SELECT * FROM Channels WHERE TeamId=? AND Name=? AND DeleteAt = 0", teamId, name); err != nil {
result.Err = model.NewAppError("SqlChannelStore.GetByName", "We couldn't find the existing channel", "teamId="+teamId+", "+"name="+name+", "+err.Error())
} else {
result.Data = &channel
}
storeChannel <- result
close(storeChannel)
}()
return storeChannel
}
func (s SqlChannelStore) SaveMember(member *model.ChannelMember) StoreChannel {
storeChannel := make(StoreChannel)
go func() {
result := StoreResult{}
if result.Err = member.IsValid(); result.Err != nil {
storeChannel <- result
return
}
if err := s.GetMaster().Insert(member); err != nil {
if strings.Contains(err.Error(), "Duplicate entry") && strings.Contains(err.Error(), "for key 'ChannelId'") {
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
}
storeChannel <- result
close(storeChannel)
}()
return storeChannel
}
func (s SqlChannelStore) GetMembers(channelId string) StoreChannel {
storeChannel := make(StoreChannel)
go func() {
result := StoreResult{}
var members []model.ChannelMember
_, err := s.GetReplica().Select(&members, "SELECT * FROM ChannelMembers WHERE ChannelId = ?", channelId)
if err != nil {
result.Err = model.NewAppError("SqlChannelStore.GetMembers", "We couldn't get the channel members", "channel_id="+channelId+err.Error())
} else {
result.Data = members
}
storeChannel <- result
close(storeChannel)
}()
return storeChannel
}
func (s SqlChannelStore) GetMember(channelId string, userId string) StoreChannel {
storeChannel := make(StoreChannel)
go func() {
result := StoreResult{}
var member model.ChannelMember
err := s.GetReplica().SelectOne(&member, "SELECT * FROM ChannelMembers WHERE ChannelId = ? AND UserId = ?", channelId, userId)
if err != nil {
result.Err = model.NewAppError("SqlChannelStore.GetMember", "We couldn't get the channel member", "channel_id="+channelId+"user_id="+userId+","+err.Error())
} else {
result.Data = member
}
storeChannel <- result
close(storeChannel)
}()
return storeChannel
}
func (s SqlChannelStore) GetExtraMembers(channelId string, limit int) StoreChannel {
storeChannel := make(StoreChannel)
go func() {
result := StoreResult{}
var members []model.ExtraMember
_, err := s.GetReplica().Select(&members, "SELECT Id, FullName, Email, ChannelMembers.Roles, Username FROM ChannelMembers, Users WHERE ChannelMembers.UserId = Users.Id AND ChannelId = ? LIMIT ?", channelId, limit)
if err != nil {
result.Err = model.NewAppError("SqlChannelStore.GetExtraMembers", "We couldn't get the extra info for channel members", "channel_id="+channelId+", "+err.Error())
} else {
for i, _ := range members {
members[i].Sanitize(utils.SanitizeOptions)
}
result.Data = members
}
storeChannel <- result
close(storeChannel)
}()
return storeChannel
}
func (s SqlChannelStore) RemoveMember(channelId string, userId string) StoreChannel {
storeChannel := make(StoreChannel)
go func() {
result := StoreResult{}
_, err := s.GetMaster().Exec("DELETE FROM ChannelMembers WHERE ChannelId = ? AND UserId = ?", channelId, userId)
if err != nil {
result.Err = model.NewAppError("SqlChannelStore.RemoveMember", "We couldn't remove the channel member", "channel_id="+channelId+", user_id="+userId+", "+err.Error())
}
storeChannel <- result
close(storeChannel)
}()
return storeChannel
}
func (s SqlChannelStore) CheckPermissionsTo(teamId string, channelId string, userId string) StoreChannel {
storeChannel := make(StoreChannel)
go func() {
result := StoreResult{}
count, err := s.GetReplica().SelectInt(
`SELECT
COUNT(0)
FROM
Channels,
ChannelMembers
WHERE
Channels.Id = ChannelMembers.ChannelId
AND Channels.TeamId = ?
AND Channels.DeleteAt = 0
AND ChannelMembers.ChannelId = ?
AND ChannelMembers.UserId = ?`,
teamId, channelId, userId)
if err != nil {
result.Err = model.NewAppError("SqlChannelStore.CheckPermissionsTo", "We couldn't check the permissions", "channel_id="+channelId+", user_id="+userId+", "+err.Error())
} else {
result.Data = count
}
storeChannel <- result
close(storeChannel)
}()
return storeChannel
}
func (s SqlChannelStore) CheckPermissionsToByName(teamId string, channelName string, userId string) StoreChannel {
storeChannel := make(StoreChannel)
go func() {
result := StoreResult{}
channelId, err := s.GetReplica().SelectStr(
`SELECT
Channels.Id
FROM
Channels,
ChannelMembers
WHERE
Channels.Id = ChannelMembers.ChannelId
AND Channels.TeamId = ?
AND Channels.Name = ?
AND Channels.DeleteAt = 0
AND ChannelMembers.UserId = ?`,
teamId, channelName, userId)
if err != nil {
result.Err = model.NewAppError("SqlChannelStore.CheckPermissionsToByName", "We couldn't check the permissions", "channel_id="+channelName+", user_id="+userId+", "+err.Error())
} else {
result.Data = channelId
}
storeChannel <- result
close(storeChannel)
}()
return storeChannel
}
func (s SqlChannelStore) CheckOpenChannelPermissions(teamId string, channelId string) StoreChannel {
storeChannel := make(StoreChannel)
go func() {
result := StoreResult{}
count, err := s.GetReplica().SelectInt(
`SELECT
COUNT(0)
FROM
Channels
WHERE
Channels.Id = ?
AND Channels.TeamId = ?
AND Channels.Type = ?`,
channelId, teamId, model.CHANNEL_OPEN)
if err != nil {
result.Err = model.NewAppError("SqlChannelStore.CheckOpenChannelPermissions", "We couldn't check the permissions", "channel_id="+channelId+", "+err.Error())
} else {
result.Data = count
}
storeChannel <- result
close(storeChannel)
}()
return storeChannel
}
func (s SqlChannelStore) UpdateLastViewedAt(channelId string, userId string) StoreChannel {
storeChannel := make(StoreChannel)
go func() {
result := StoreResult{}
_, err := s.GetMaster().Exec(
`UPDATE
ChannelMembers, Channels
SET
ChannelMembers.MentionCount = 0,
ChannelMembers.MsgCount = Channels.TotalMsgCount,
ChannelMembers.LastViewedAt = Channels.LastPostAt
WHERE
Channels.Id = ChannelMembers.ChannelId
AND UserId = ?
AND ChannelId = ?`,
userId, channelId)
if err != nil {
result.Err = model.NewAppError("SqlChannelStore.UpdateLastViewedAt", "We couldn't update the last viewed at time", "channel_id="+channelId+", user_id="+userId+", "+err.Error())
}
storeChannel <- result
close(storeChannel)
}()
return storeChannel
}
func (s SqlChannelStore) IncrementMentionCount(channelId string, userId string) StoreChannel {
storeChannel := make(StoreChannel)
go func() {
result := StoreResult{}
_, err := s.GetMaster().Exec(
`UPDATE
ChannelMembers
SET
MentionCount = MentionCount + 1
WHERE
UserId = ?
AND ChannelId = ?`,
userId, channelId)
if err != nil {
result.Err = model.NewAppError("SqlChannelStore.IncrementMentionCount", "We couldn't increment the mention count", "channel_id="+channelId+", user_id="+userId+", "+err.Error())
}
storeChannel <- result
close(storeChannel)
}()
return storeChannel
}
func (s SqlChannelStore) UpdateNotifyLevel(channelId, userId, notifyLevel string) StoreChannel {
storeChannel := make(StoreChannel)
go func() {
result := StoreResult{}
_, err := s.GetMaster().Exec(
`UPDATE
ChannelMembers
SET
NotifyLevel = ?
WHERE
UserId = ?
AND ChannelId = ?`,
notifyLevel, userId, channelId)
if err != nil {
result.Err = model.NewAppError("SqlChannelStore.UpdateNotifyLevel", "We couldn't update the notify level", "channel_id="+channelId+", user_id="+userId+", "+err.Error())
}
storeChannel <- result
close(storeChannel)
}()
return storeChannel
}

493
store/sql_channel_store_test.go Обычный файл
Просмотреть файл

@@ -0,0 +1,493 @@
// Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved.
// See License.txt for license information.
package store
import (
"github.com/mattermost/platform/model"
"testing"
)
func TestChannelStoreSave(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_OPEN
if err := (<-store.Channel().Save(&o1)).Err; err != nil {
t.Fatal("couldn't save item", err)
}
if err := (<-store.Channel().Save(&o1)).Err; err == nil {
t.Fatal("shouldn't be able to update from save")
}
o1.Id = ""
if err := (<-store.Channel().Save(&o1)).Err; err == nil {
t.Fatal("should be unique name")
}
for i := 0; i < 150; i++ {
o1.Id = ""
o1.Name = "a" + model.NewId() + "b"
if err := (<-store.Channel().Save(&o1)).Err; err != nil {
t.Fatal("couldn't save item", err)
}
}
o1.Id = ""
o1.Name = "a" + model.NewId() + "b"
if err := (<-store.Channel().Save(&o1)).Err; err == nil {
t.Fatal("should be the limit")
}
}
func TestChannelStoreUpdate(t *testing.T) {
Setup()
o1 := model.Channel{}
o1.TeamId = model.NewId()
o1.DisplayName = "Name"
o1.Name = "a" + model.NewId() + "b"
o1.Type = model.CHANNEL_OPEN
<-store.Channel().Save(&o1)
if err := (<-store.Channel().Update(&o1)).Err; err != nil {
t.Fatal(err)
}
o1.Id = "missing"
if err := (<-store.Channel().Update(&o1)).Err; err == nil {
t.Fatal("Update should have failed because of missing key")
}
o1.Id = model.NewId()
if err := (<-store.Channel().Update(&o1)).Err; err == nil {
t.Fatal("Update should have faile because id change")
}
}
func TestChannelStoreGet(t *testing.T) {
Setup()
o1 := model.Channel{}
o1.TeamId = model.NewId()
o1.DisplayName = "Name"
o1.Name = "a" + model.NewId() + "b"
o1.Type = model.CHANNEL_OPEN
<-store.Channel().Save(&o1)
if r1 := <-store.Channel().Get(o1.Id); r1.Err != nil {
t.Fatal(r1.Err)
} else {
if r1.Data.(*model.Channel).ToJson() != o1.ToJson() {
t.Fatal("invalid returned channel")
}
}
if err := (<-store.Channel().Get("")).Err; err == nil {
t.Fatal("Missing id should have failed")
}
}
func TestChannelStoreDelete(t *testing.T) {
Setup()
o1 := model.Channel{}
o1.TeamId = model.NewId()
o1.DisplayName = "Channel1"
o1.Name = "a" + model.NewId() + "b"
o1.Type = model.CHANNEL_OPEN
<-store.Channel().Save(&o1)
o2 := model.Channel{}
o2.TeamId = o1.TeamId
o2.DisplayName = "Channel2"
o2.Name = "a" + model.NewId() + "b"
o2.Type = model.CHANNEL_OPEN
<-store.Channel().Save(&o2)
o3 := model.Channel{}
o3.TeamId = o1.TeamId
o3.DisplayName = "Channel3"
o3.Name = "a" + model.NewId() + "b"
o3.Type = model.CHANNEL_OPEN
<-store.Channel().Save(&o3)
o4 := model.Channel{}
o4.TeamId = o1.TeamId
o4.DisplayName = "Channel4"
o4.Name = "a" + model.NewId() + "b"
o4.Type = model.CHANNEL_OPEN
<-store.Channel().Save(&o4)
m1 := model.ChannelMember{}
m1.ChannelId = o1.Id
m1.UserId = model.NewId()
m1.NotifyLevel = model.CHANNEL_NOTIFY_ALL
<-store.Channel().SaveMember(&m1)
m2 := model.ChannelMember{}
m2.ChannelId = o2.Id
m2.UserId = m1.UserId
m2.NotifyLevel = model.CHANNEL_NOTIFY_ALL
<-store.Channel().SaveMember(&m2)
if r := <-store.Channel().Delete(o1.Id, model.GetMillis()); r.Err != nil {
t.Fatal(r.Err)
}
if r := <-store.Channel().Get(o1.Id); r.Data.(*model.Channel).DeleteAt == 0 {
t.Fatal("should have been deleted")
}
if r := <-store.Channel().Delete(o3.Id, model.GetMillis()); r.Err != nil {
t.Fatal(r.Err)
}
cresult := <-store.Channel().GetChannels(o1.TeamId, m1.UserId)
list := cresult.Data.(*model.ChannelList)
if len(list.Channels) != 1 {
t.Fatal("invalid number of channels")
}
cresult = <-store.Channel().GetMoreChannels(o1.TeamId, m1.UserId)
list = cresult.Data.(*model.ChannelList)
if len(list.Channels) != 1 {
t.Fatal("invalid number of channels")
}
}
func TestChannelStoreGetByName(t *testing.T) {
Setup()
o1 := model.Channel{}
o1.TeamId = model.NewId()
o1.DisplayName = "Name"
o1.Name = "a" + model.NewId() + "b"
o1.Type = model.CHANNEL_OPEN
<-store.Channel().Save(&o1)
if r1 := <-store.Channel().GetByName(o1.TeamId, o1.Name); r1.Err != nil {
t.Fatal(r1.Err)
} else {
if r1.Data.(*model.Channel).ToJson() != o1.ToJson() {
t.Fatal("invalid returned channel")
}
}
if err := (<-store.Channel().GetByName(o1.TeamId, "")).Err; err == nil {
t.Fatal("Missing id should have failed")
}
}
func TestChannelMemberStore(t *testing.T) {
Setup()
u1 := model.User{}
u1.TeamId = model.NewId()
u1.Email = model.NewId()
u1.FullName = model.NewId()
<-store.User().Save(&u1)
u2 := model.User{}
u2.TeamId = model.NewId()
u2.Email = model.NewId()
u2.FullName = model.NewId()
<-store.User().Save(&u2)
o1 := model.ChannelMember{}
o1.ChannelId = model.NewId()
o1.UserId = u1.Id
o1.NotifyLevel = model.CHANNEL_NOTIFY_ALL
<-store.Channel().SaveMember(&o1)
o2 := model.ChannelMember{}
o2.ChannelId = o1.ChannelId
o2.UserId = u2.Id
o2.NotifyLevel = model.CHANNEL_NOTIFY_ALL
<-store.Channel().SaveMember(&o2)
members := (<-store.Channel().GetMembers(o1.ChannelId)).Data.([]model.ChannelMember)
if len(members) != 2 {
t.Fatal("should have saved 2 members")
}
<-store.Channel().RemoveMember(o2.ChannelId, o2.UserId)
members = (<-store.Channel().GetMembers(o1.ChannelId)).Data.([]model.ChannelMember)
if len(members) != 1 {
t.Fatal("should have removed 1 member")
}
member := (<-store.Channel().GetMember(o1.ChannelId, o1.UserId)).Data.(model.ChannelMember)
if member.ChannelId != o1.ChannelId {
t.Fatal("should have go member")
}
extraMembers := (<-store.Channel().GetExtraMembers(o1.ChannelId, 20)).Data.([]model.ExtraMember)
if len(extraMembers) != 1 {
t.Fatal("should have 1 extra members")
}
if err := (<-store.Channel().SaveMember(&o1)).Err; err == nil {
t.Fatal("Should have been a duplicate")
}
}
func TestChannelStorePermissionsTo(t *testing.T) {
Setup()
o1 := model.Channel{}
o1.TeamId = model.NewId()
o1.DisplayName = "Channel1"
o1.Name = "a" + model.NewId() + "b"
o1.Type = model.CHANNEL_OPEN
<-store.Channel().Save(&o1)
m1 := model.ChannelMember{}
m1.ChannelId = o1.Id
m1.UserId = model.NewId()
m1.NotifyLevel = model.CHANNEL_NOTIFY_ALL
<-store.Channel().SaveMember(&m1)
count := (<-store.Channel().CheckPermissionsTo(o1.TeamId, o1.Id, m1.UserId)).Data.(int64)
if count != 1 {
t.Fatal("should have permissions")
}
count = (<-store.Channel().CheckPermissionsTo("junk", o1.Id, m1.UserId)).Data.(int64)
if count != 0 {
t.Fatal("shouldn't have permissions")
}
count = (<-store.Channel().CheckPermissionsTo(o1.TeamId, "junk", m1.UserId)).Data.(int64)
if count != 0 {
t.Fatal("shouldn't have permissions")
}
count = (<-store.Channel().CheckPermissionsTo(o1.TeamId, o1.Id, "junk")).Data.(int64)
if count != 0 {
t.Fatal("shouldn't have permissions")
}
channelId := (<-store.Channel().CheckPermissionsToByName(o1.TeamId, o1.Name, m1.UserId)).Data.(string)
if channelId != o1.Id {
t.Fatal("should have permissions")
}
channelId = (<-store.Channel().CheckPermissionsToByName(o1.TeamId, "missing", m1.UserId)).Data.(string)
if channelId != "" {
t.Fatal("should not have permissions")
}
}
func TestChannelStoreOpenChannelPermissionsTo(t *testing.T) {
Setup()
o1 := model.Channel{}
o1.TeamId = model.NewId()
o1.DisplayName = "Channel1"
o1.Name = "a" + model.NewId() + "b"
o1.Type = model.CHANNEL_OPEN
<-store.Channel().Save(&o1)
count := (<-store.Channel().CheckOpenChannelPermissions(o1.TeamId, o1.Id)).Data.(int64)
if count != 1 {
t.Fatal("should have permissions")
}
count = (<-store.Channel().CheckOpenChannelPermissions("junk", o1.Id)).Data.(int64)
if count != 0 {
t.Fatal("shouldn't have permissions")
}
count = (<-store.Channel().CheckOpenChannelPermissions(o1.TeamId, "junk")).Data.(int64)
if count != 0 {
t.Fatal("shouldn't have permissions")
}
}
func TestChannelStoreGetChannels(t *testing.T) {
Setup()
o2 := model.Channel{}
o2.TeamId = model.NewId()
o2.DisplayName = "Channel2"
o2.Name = "a" + model.NewId() + "b"
o2.Type = model.CHANNEL_OPEN
<-store.Channel().Save(&o2)
o1 := model.Channel{}
o1.TeamId = model.NewId()
o1.DisplayName = "Channel1"
o1.Name = "a" + model.NewId() + "b"
o1.Type = model.CHANNEL_OPEN
<-store.Channel().Save(&o1)
m1 := model.ChannelMember{}
m1.ChannelId = o1.Id
m1.UserId = model.NewId()
m1.NotifyLevel = model.CHANNEL_NOTIFY_ALL
<-store.Channel().SaveMember(&m1)
m2 := model.ChannelMember{}
m2.ChannelId = o1.Id
m2.UserId = model.NewId()
m2.NotifyLevel = model.CHANNEL_NOTIFY_ALL
<-store.Channel().SaveMember(&m2)
m3 := model.ChannelMember{}
m3.ChannelId = o2.Id
m3.UserId = model.NewId()
m3.NotifyLevel = model.CHANNEL_NOTIFY_ALL
<-store.Channel().SaveMember(&m3)
cresult := <-store.Channel().GetChannels(o1.TeamId, m1.UserId)
list := cresult.Data.(*model.ChannelList)
if list.Channels[0].Id != o1.Id {
t.Fatal("missing channel")
}
}
func TestChannelStoreGetMoreChannels(t *testing.T) {
Setup()
o1 := model.Channel{}
o1.TeamId = model.NewId()
o1.DisplayName = "Channel1"
o1.Name = "a" + model.NewId() + "b"
o1.Type = model.CHANNEL_OPEN
<-store.Channel().Save(&o1)
o2 := model.Channel{}
o2.TeamId = model.NewId()
o2.DisplayName = "Channel2"
o2.Name = "a" + model.NewId() + "b"
o2.Type = model.CHANNEL_OPEN
<-store.Channel().Save(&o2)
m1 := model.ChannelMember{}
m1.ChannelId = o1.Id
m1.UserId = model.NewId()
m1.NotifyLevel = model.CHANNEL_NOTIFY_ALL
<-store.Channel().SaveMember(&m1)
m2 := model.ChannelMember{}
m2.ChannelId = o1.Id
m2.UserId = model.NewId()
m2.NotifyLevel = model.CHANNEL_NOTIFY_ALL
<-store.Channel().SaveMember(&m2)
m3 := model.ChannelMember{}
m3.ChannelId = o2.Id
m3.UserId = model.NewId()
m3.NotifyLevel = model.CHANNEL_NOTIFY_ALL
<-store.Channel().SaveMember(&m3)
o3 := model.Channel{}
o3.TeamId = o1.TeamId
o3.DisplayName = "ChannelA"
o3.Name = "a" + model.NewId() + "b"
o3.Type = model.CHANNEL_OPEN
<-store.Channel().Save(&o3)
o4 := model.Channel{}
o4.TeamId = o1.TeamId
o4.DisplayName = "ChannelB"
o4.Name = "a" + model.NewId() + "b"
o4.Type = model.CHANNEL_PRIVATE
<-store.Channel().Save(&o4)
o5 := model.Channel{}
o5.TeamId = o1.TeamId
o5.DisplayName = "ChannelC"
o5.Name = "a" + model.NewId() + "b"
o5.Type = model.CHANNEL_PRIVATE
<-store.Channel().Save(&o5)
cresult := <-store.Channel().GetMoreChannels(o1.TeamId, m1.UserId)
list := cresult.Data.(*model.ChannelList)
if len(list.Channels) != 1 {
t.Fatal("wrong list")
}
if list.Channels[0].Name != o3.Name {
t.Fatal("missing channel")
}
}
func TestChannelStoreUpdateLastViewedAt(t *testing.T) {
Setup()
o1 := model.Channel{}
o1.TeamId = model.NewId()
o1.DisplayName = "Channel1"
o1.Name = "a" + model.NewId() + "b"
o1.Type = model.CHANNEL_OPEN
o1.TotalMsgCount = 25
<-store.Channel().Save(&o1)
m1 := model.ChannelMember{}
m1.ChannelId = o1.Id
m1.UserId = model.NewId()
m1.NotifyLevel = model.CHANNEL_NOTIFY_ALL
<-store.Channel().SaveMember(&m1)
err := (<-store.Channel().UpdateLastViewedAt(m1.ChannelId, m1.UserId)).Err
if err != nil {
t.Fatal("failed to update")
}
err = (<-store.Channel().UpdateLastViewedAt(m1.ChannelId, "missing id")).Err
if err != nil {
t.Fatal("failed to update")
}
}
func TestChannelStoreIncrementMentionCount(t *testing.T) {
Setup()
o1 := model.Channel{}
o1.TeamId = model.NewId()
o1.DisplayName = "Channel1"
o1.Name = "a" + model.NewId() + "b"
o1.Type = model.CHANNEL_OPEN
o1.TotalMsgCount = 25
<-store.Channel().Save(&o1)
m1 := model.ChannelMember{}
m1.ChannelId = o1.Id
m1.UserId = model.NewId()
m1.NotifyLevel = model.CHANNEL_NOTIFY_ALL
<-store.Channel().SaveMember(&m1)
err := (<-store.Channel().IncrementMentionCount(m1.ChannelId, m1.UserId)).Err
if err != nil {
t.Fatal("failed to update")
}
err = (<-store.Channel().IncrementMentionCount(m1.ChannelId, "missing id")).Err
if err != nil {
t.Fatal("failed to update")
}
err = (<-store.Channel().IncrementMentionCount("missing id", m1.UserId)).Err
if err != nil {
t.Fatal("failed to update")
}
err = (<-store.Channel().IncrementMentionCount("missing id", "missing id")).Err
if err != nil {
t.Fatal("failed to update")
}
}

410
store/sql_post_store.go Обычный файл
Просмотреть файл

@@ -0,0 +1,410 @@
// Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved.
// See License.txt for license information.
package store
import (
"fmt"
"github.com/mattermost/platform/model"
"strconv"
"strings"
)
type SqlPostStore struct {
*SqlStore
}
func NewSqlPostStore(sqlStore *SqlStore) PostStore {
s := &SqlPostStore{sqlStore}
for _, db := range sqlStore.GetAllConns() {
table := db.AddTableWithName(model.Post{}, "Posts").SetKeys(false, "Id")
table.ColMap("Id").SetMaxSize(26)
table.ColMap("UserId").SetMaxSize(26)
table.ColMap("ChannelId").SetMaxSize(26)
table.ColMap("RootId").SetMaxSize(26)
table.ColMap("ParentId").SetMaxSize(26)
table.ColMap("Message").SetMaxSize(4000)
table.ColMap("Type").SetMaxSize(26)
table.ColMap("Hashtags").SetMaxSize(1000)
table.ColMap("Props").SetMaxSize(4000)
table.ColMap("Filenames").SetMaxSize(4000)
}
return s
}
func (s SqlPostStore) UpgradeSchemaIfNeeded() {
}
func (s SqlPostStore) CreateIndexesIfNotExists() {
s.CreateIndexIfNotExists("idx_update_at", "Posts", "UpdateAt")
s.CreateIndexIfNotExists("idx_create_at", "Posts", "CreateAt")
s.CreateIndexIfNotExists("idx_channel_id", "Posts", "ChannelId")
s.CreateIndexIfNotExists("idx_root_id", "Posts", "RootId")
s.CreateFullTextIndexIfNotExists("idx_message_txt", "Posts", "Message")
s.CreateFullTextIndexIfNotExists("idx_hashtags_txt", "Posts", "Hashtags")
}
func (s SqlPostStore) Save(post *model.Post) StoreChannel {
storeChannel := make(StoreChannel)
go func() {
result := StoreResult{}
if len(post.Id) > 0 {
result.Err = model.NewAppError("SqlPostStore.Save",
"You cannot update an existing Post", "id="+post.Id)
storeChannel <- result
close(storeChannel)
return
}
post.PreSave()
if result.Err = post.IsValid(); result.Err != nil {
storeChannel <- result
close(storeChannel)
return
}
if err := s.GetMaster().Insert(post); err != nil {
result.Err = model.NewAppError("SqlPostStore.Save", "We couldn't save the Post", "id="+post.Id+", "+err.Error())
} else {
time := model.GetMillis()
s.GetMaster().Exec("UPDATE Channels SET LastPostAt = ?, TotalMsgCount = TotalMsgCount + 1 WHERE Id = ?", time, post.ChannelId)
if len(post.RootId) > 0 {
s.GetMaster().Exec("UPDATE Posts SET UpdateAt = ? WHERE Id = ?", time, post.RootId)
}
result.Data = post
}
storeChannel <- result
close(storeChannel)
}()
return storeChannel
}
func (s SqlPostStore) Update(oldPost *model.Post, newMessage string, newHashtags string) StoreChannel {
storeChannel := make(StoreChannel)
go func() {
result := StoreResult{}
editPost := *oldPost
editPost.Message = newMessage
editPost.UpdateAt = model.GetMillis()
editPost.Hashtags = newHashtags
oldPost.DeleteAt = editPost.UpdateAt
oldPost.UpdateAt = editPost.UpdateAt
oldPost.OriginalId = oldPost.Id
oldPost.Id = model.NewId()
if result.Err = editPost.IsValid(); result.Err != nil {
storeChannel <- result
close(storeChannel)
return
}
if _, err := s.GetMaster().Update(&editPost); err != nil {
result.Err = model.NewAppError("SqlPostStore.Update", "We couldn't update the Post", "id="+editPost.Id+", "+err.Error())
} else {
time := model.GetMillis()
s.GetMaster().Exec("UPDATE Channels SET LastPostAt = ? WHERE Id = ?", time, editPost.ChannelId)
if len(editPost.RootId) > 0 {
s.GetMaster().Exec("UPDATE Posts SET UpdateAt = ? WHERE Id = ?", time, editPost.RootId)
}
// mark the old post as deleted
s.GetMaster().Insert(oldPost)
result.Data = &editPost
}
storeChannel <- result
close(storeChannel)
}()
return storeChannel
}
func (s SqlPostStore) Get(id string) StoreChannel {
storeChannel := make(StoreChannel)
go func() {
result := StoreResult{}
pl := &model.PostList{}
var post model.Post
err := s.GetReplica().SelectOne(&post, "SELECT * FROM Posts WHERE Id = ? AND DeleteAt = 0", id)
if err != nil {
result.Err = model.NewAppError("SqlPostStore.GetPost", "We couldn't get the post", "id="+id+err.Error())
}
if post.ImgCount > 0 {
post.Filenames = []string{}
for i := 0; int64(i) < post.ImgCount; i++ {
fileUrl := "/api/v1/files/get_image/" + post.ChannelId + "/" + post.Id + "/" + strconv.Itoa(i+1) + ".png"
post.Filenames = append(post.Filenames, fileUrl)
}
}
pl.AddPost(&post)
pl.AddOrder(id)
rootId := post.RootId
if rootId == "" {
rootId = post.Id
}
var posts []*model.Post
_, err = s.GetReplica().Select(&posts, "SELECT * FROM Posts WHERE (Id = ? OR RootId = ?) AND DeleteAt = 0", rootId, rootId)
if err != nil {
result.Err = model.NewAppError("SqlPostStore.GetPost", "We couldn't get the post", "root_id="+rootId+err.Error())
} else {
for _, p := range posts {
pl.AddPost(p)
}
}
result.Data = pl
storeChannel <- result
close(storeChannel)
}()
return storeChannel
}
type etagPosts struct {
Id string
UpdateAt int64
}
func (s SqlPostStore) GetEtag(channelId string) StoreChannel {
storeChannel := make(StoreChannel)
go func() {
result := StoreResult{}
var et etagPosts
err := s.GetReplica().SelectOne(&et, "SELECT Id, UpdateAt FROM Posts WHERE ChannelId = ? ORDER BY UpdateAt DESC LIMIT 1", channelId)
if err != nil {
result.Data = fmt.Sprintf("%v.0.%v", model.ETAG_ROOT_VERSION, model.GetMillis())
} else {
result.Data = fmt.Sprintf("%v.%v.%v", model.ETAG_ROOT_VERSION, et.Id, et.UpdateAt)
}
storeChannel <- result
close(storeChannel)
}()
return storeChannel
}
func (s SqlPostStore) Delete(postId string, time int64) StoreChannel {
storeChannel := make(StoreChannel)
go func() {
result := StoreResult{}
_, err := s.GetMaster().Exec("Update Posts SET DeleteAt = ?, UpdateAt = ? WHERE Id = ? OR ParentId = ? OR RootId = ?", time, time, postId, postId, postId)
if err != nil {
result.Err = model.NewAppError("SqlPostStore.Delete", "We couldn't delete the post", "id="+postId+", err="+err.Error())
}
storeChannel <- result
close(storeChannel)
}()
return storeChannel
}
func (s SqlPostStore) GetPosts(channelId string, offset int, limit int) StoreChannel {
storeChannel := make(StoreChannel)
go func() {
result := StoreResult{}
if limit > 1000 {
result.Err = model.NewAppError("SqlPostStore.GetLinearPosts", "Limit exceeded for paging", "channelId="+channelId)
storeChannel <- result
close(storeChannel)
return
}
rpc := s.getRootPosts(channelId, offset, limit)
cpc := s.getParentsPosts(channelId, offset, limit)
if rpr := <-rpc; rpr.Err != nil {
result.Err = rpr.Err
} else if cpr := <-cpc; cpr.Err != nil {
result.Err = cpr.Err
} else {
posts := rpr.Data.([]*model.Post)
parents := cpr.Data.([]*model.Post)
list := &model.PostList{Order: make([]string, 0, len(posts))}
for _, p := range posts {
if p.ImgCount > 0 {
p.Filenames = []string{}
for i := 0; int64(i) < p.ImgCount; i++ {
fileUrl := "/api/v1/files/get_image/" + p.ChannelId + "/" + p.Id + "/" + strconv.Itoa(i+1) + ".png"
p.Filenames = append(p.Filenames, fileUrl)
}
}
list.AddPost(p)
list.AddOrder(p.Id)
}
for _, p := range parents {
if p.ImgCount > 0 {
p.Filenames = []string{}
for i := 0; int64(i) < p.ImgCount; i++ {
fileUrl := "/api/v1/files/get_image/" + p.ChannelId + "/" + p.Id + "/" + strconv.Itoa(i+1) + ".png"
p.Filenames = append(p.Filenames, fileUrl)
}
}
list.AddPost(p)
}
list.MakeNonNil()
result.Data = list
}
storeChannel <- result
close(storeChannel)
}()
return storeChannel
}
func (s SqlPostStore) getRootPosts(channelId string, offset int, limit int) StoreChannel {
storeChannel := make(StoreChannel)
go func() {
result := StoreResult{}
var posts []*model.Post
_, err := s.GetReplica().Select(&posts, "SELECT * FROM Posts WHERE ChannelId = ? AND DeleteAt = 0 ORDER BY CreateAt DESC LIMIT ?,?", channelId, offset, limit)
if err != nil {
result.Err = model.NewAppError("SqlPostStore.GetLinearPosts", "We couldn't get the posts for the channel", "channelId="+channelId+err.Error())
} else {
result.Data = posts
}
storeChannel <- result
close(storeChannel)
}()
return storeChannel
}
func (s SqlPostStore) getParentsPosts(channelId string, offset int, limit int) StoreChannel {
storeChannel := make(StoreChannel)
go func() {
result := StoreResult{}
var posts []*model.Post
_, err := s.GetReplica().Select(&posts,
`SELECT
q2.*
FROM
Posts q2
INNER JOIN
(SELECT DISTINCT
q3.RootId
FROM
(SELECT
RootId
FROM
Posts
WHERE
ChannelId = ?
AND DeleteAt = 0
ORDER BY CreateAt DESC
LIMIT ?, ?) q3) q1 ON q1.RootId = q2.RootId
WHERE
ChannelId = ?
AND DeleteAt = 0
ORDER BY CreateAt`,
channelId, offset, limit, channelId)
if err != nil {
result.Err = model.NewAppError("SqlPostStore.GetLinearPosts", "We couldn't get the parent post for the channel", "channelId="+channelId+err.Error())
} else {
result.Data = posts
}
storeChannel <- result
close(storeChannel)
}()
return storeChannel
}
func (s SqlPostStore) Search(teamId string, userId string, terms string, isHashtagSearch bool) StoreChannel {
storeChannel := make(StoreChannel)
go func() {
result := StoreResult{}
searchType := "Message"
if isHashtagSearch {
searchType = "Hashtags"
}
// @ has a speical meaning in INNODB FULLTEXT indexes and
// is reserved for calc'ing distances so you
// cannot escape it so we replace it.
terms = strings.Replace(terms, "@", " ", -1)
searchQuery := fmt.Sprintf(`SELECT
*
FROM
Posts
WHERE
DeleteAt = 0
AND ChannelId IN (SELECT
Id
FROM
Channels,
ChannelMembers
WHERE
Id = ChannelId AND TeamId = ?
AND UserId = ?
AND DeleteAt = 0)
AND MATCH (%s) AGAINST (? IN BOOLEAN MODE)
ORDER BY CreateAt DESC
LIMIT 100`, searchType)
var posts []*model.Post
_, err := s.GetReplica().Select(&posts, searchQuery, teamId, userId, terms)
if err != nil {
result.Err = model.NewAppError("SqlPostStore.Search", "We encounted an error while searching for posts", "teamId="+teamId+", err="+err.Error())
} else {
list := &model.PostList{Order: make([]string, 0, len(posts))}
for _, p := range posts {
list.AddPost(p)
list.AddOrder(p.Id)
}
list.MakeNonNil()
result.Data = list
}
storeChannel <- result
close(storeChannel)
}()
return storeChannel
}

481
store/sql_post_store_test.go Обычный файл
Просмотреть файл

@@ -0,0 +1,481 @@
// Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved.
// See License.txt for license information.
package store
import (
"github.com/mattermost/platform/model"
"strings"
"testing"
"time"
)
func TestPostStoreSave(t *testing.T) {
Setup()
o1 := model.Post{}
o1.ChannelId = model.NewId()
o1.UserId = model.NewId()
o1.Message = "a" + model.NewId() + "b"
if err := (<-store.Post().Save(&o1)).Err; err != nil {
t.Fatal("couldn't save item", err)
}
if err := (<-store.Post().Save(&o1)).Err; err == nil {
t.Fatal("shouldn't be able to update from save")
}
}
func TestPostStoreGet(t *testing.T) {
Setup()
o1 := &model.Post{}
o1.ChannelId = model.NewId()
o1.UserId = model.NewId()
o1.Message = "a" + model.NewId() + "b"
etag1 := (<-store.Post().GetEtag(o1.ChannelId)).Data.(string)
if strings.Index(etag1, model.ETAG_ROOT_VERSION+".0.") != 0 {
t.Fatal("Invalid Etag")
}
o1 = (<-store.Post().Save(o1)).Data.(*model.Post)
etag2 := (<-store.Post().GetEtag(o1.ChannelId)).Data.(string)
if strings.Index(etag2, model.ETAG_ROOT_VERSION+"."+o1.Id) != 0 {
t.Fatal("Invalid Etag")
}
if r1 := <-store.Post().Get(o1.Id); r1.Err != nil {
t.Fatal(r1.Err)
} else {
if r1.Data.(*model.PostList).Posts[o1.Id].CreateAt != o1.CreateAt {
t.Fatal("invalid returned post")
}
}
if err := (<-store.Post().Get("123")).Err; err == nil {
t.Fatal("Missing id should have failed")
}
}
func TestPostStoreUpdate(t *testing.T) {
Setup()
o1 := &model.Post{}
o1.ChannelId = model.NewId()
o1.UserId = model.NewId()
o1.Message = "a" + model.NewId() + "AAAAAAAAAAA"
o1 = (<-store.Post().Save(o1)).Data.(*model.Post)
o2 := &model.Post{}
o2.ChannelId = o1.ChannelId
o2.UserId = model.NewId()
o2.Message = "a" + model.NewId() + "CCCCCCCCC"
o2.ParentId = o1.Id
o2.RootId = o1.Id
o2 = (<-store.Post().Save(o2)).Data.(*model.Post)
o3 := &model.Post{}
o3.ChannelId = o1.ChannelId
o3.UserId = model.NewId()
o3.Message = "a" + model.NewId() + "QQQQQQQQQQ"
o3 = (<-store.Post().Save(o3)).Data.(*model.Post)
ro1 := (<-store.Post().Get(o1.Id)).Data.(*model.PostList).Posts[o1.Id]
ro2 := (<-store.Post().Get(o1.Id)).Data.(*model.PostList).Posts[o2.Id]
ro6 := (<-store.Post().Get(o3.Id)).Data.(*model.PostList).Posts[o3.Id]
if ro1.Message != o1.Message {
t.Fatal("Failed to save/get")
}
msg := o1.Message + "BBBBBBBBBB"
if result := <-store.Post().Update(ro1, msg, ""); result.Err != nil {
t.Fatal(result.Err)
}
msg2 := o2.Message + "DDDDDDD"
if result := <-store.Post().Update(ro2, msg2, ""); result.Err != nil {
t.Fatal(result.Err)
}
msg3 := o3.Message + "WWWWWWW"
if result := <-store.Post().Update(ro6, msg3, "#hashtag"); result.Err != nil {
t.Fatal(result.Err)
}
ro3 := (<-store.Post().Get(o1.Id)).Data.(*model.PostList).Posts[o1.Id]
if ro3.Message != msg {
t.Fatal("Failed to update/get")
}
ro4 := (<-store.Post().Get(o1.Id)).Data.(*model.PostList).Posts[o2.Id]
if ro4.Message != msg2 {
t.Fatal("Failed to update/get")
}
ro5 := (<-store.Post().Get(o3.Id)).Data.(*model.PostList).Posts[o3.Id]
if ro5.Message != msg3 && ro5.Hashtags != "#hashtag" {
t.Fatal("Failed to update/get")
}
}
func TestPostStoreDelete(t *testing.T) {
Setup()
o1 := &model.Post{}
o1.ChannelId = model.NewId()
o1.UserId = model.NewId()
o1.Message = "a" + model.NewId() + "b"
etag1 := (<-store.Post().GetEtag(o1.ChannelId)).Data.(string)
if strings.Index(etag1, model.ETAG_ROOT_VERSION+".0.") != 0 {
t.Fatal("Invalid Etag")
}
o1 = (<-store.Post().Save(o1)).Data.(*model.Post)
if r1 := <-store.Post().Get(o1.Id); r1.Err != nil {
t.Fatal(r1.Err)
} else {
if r1.Data.(*model.PostList).Posts[o1.Id].CreateAt != o1.CreateAt {
t.Fatal("invalid returned post")
}
}
if r2 := <-store.Post().Delete(o1.Id, model.GetMillis()); r2.Err != nil {
t.Fatal(r2.Err)
}
if r3 := (<-store.Post().Get(o1.Id)); r3.Err == nil {
t.Log(r3.Data)
t.Fatal("Missing id should have failed")
}
etag2 := (<-store.Post().GetEtag(o1.ChannelId)).Data.(string)
if strings.Index(etag2, model.ETAG_ROOT_VERSION+"."+o1.Id) != 0 {
t.Fatal("Invalid Etag")
}
}
func TestPostStoreDelete1Level(t *testing.T) {
Setup()
o1 := &model.Post{}
o1.ChannelId = model.NewId()
o1.UserId = model.NewId()
o1.Message = "a" + model.NewId() + "b"
o1 = (<-store.Post().Save(o1)).Data.(*model.Post)
o2 := &model.Post{}
o2.ChannelId = o1.ChannelId
o2.UserId = model.NewId()
o2.Message = "a" + model.NewId() + "b"
o2.ParentId = o1.Id
o2.RootId = o1.Id
o2 = (<-store.Post().Save(o2)).Data.(*model.Post)
if r2 := <-store.Post().Delete(o1.Id, model.GetMillis()); r2.Err != nil {
t.Fatal(r2.Err)
}
if r3 := (<-store.Post().Get(o1.Id)); r3.Err == nil {
t.Fatal("Deleted id should have failed")
}
if r4 := (<-store.Post().Get(o2.Id)); r4.Err == nil {
t.Fatal("Deleted id should have failed")
}
}
func TestPostStoreDelete2Level(t *testing.T) {
Setup()
o1 := &model.Post{}
o1.ChannelId = model.NewId()
o1.UserId = model.NewId()
o1.Message = "a" + model.NewId() + "b"
o1 = (<-store.Post().Save(o1)).Data.(*model.Post)
o2 := &model.Post{}
o2.ChannelId = o1.ChannelId
o2.UserId = model.NewId()
o2.Message = "a" + model.NewId() + "b"
o2.ParentId = o1.Id
o2.RootId = o1.Id
o2 = (<-store.Post().Save(o2)).Data.(*model.Post)
o3 := &model.Post{}
o3.ChannelId = o1.ChannelId
o3.UserId = model.NewId()
o3.Message = "a" + model.NewId() + "b"
o3.ParentId = o2.Id
o3.RootId = o1.Id
o3 = (<-store.Post().Save(o3)).Data.(*model.Post)
o4 := &model.Post{}
o4.ChannelId = model.NewId()
o4.UserId = model.NewId()
o4.Message = "a" + model.NewId() + "b"
o4 = (<-store.Post().Save(o4)).Data.(*model.Post)
if r2 := <-store.Post().Delete(o1.Id, model.GetMillis()); r2.Err != nil {
t.Fatal(r2.Err)
}
if r3 := (<-store.Post().Get(o1.Id)); r3.Err == nil {
t.Fatal("Deleted id should have failed")
}
if r4 := (<-store.Post().Get(o2.Id)); r4.Err == nil {
t.Fatal("Deleted id should have failed")
}
if r5 := (<-store.Post().Get(o3.Id)); r5.Err == nil {
t.Fatal("Deleted id should have failed")
}
if r6 := <-store.Post().Get(o4.Id); r6.Err != nil {
t.Fatal(r6.Err)
}
}
func TestPostStoreGetWithChildren(t *testing.T) {
Setup()
o1 := &model.Post{}
o1.ChannelId = model.NewId()
o1.UserId = model.NewId()
o1.Message = "a" + model.NewId() + "b"
o1 = (<-store.Post().Save(o1)).Data.(*model.Post)
o2 := &model.Post{}
o2.ChannelId = o1.ChannelId
o2.UserId = model.NewId()
o2.Message = "a" + model.NewId() + "b"
o2.ParentId = o1.Id
o2.RootId = o1.Id
o2 = (<-store.Post().Save(o2)).Data.(*model.Post)
o3 := &model.Post{}
o3.ChannelId = o1.ChannelId
o3.UserId = model.NewId()
o3.Message = "a" + model.NewId() + "b"
o3.ParentId = o2.Id
o3.RootId = o1.Id
o3 = (<-store.Post().Save(o3)).Data.(*model.Post)
if r1 := <-store.Post().Get(o1.Id); r1.Err != nil {
t.Fatal(r1.Err)
} else {
pl := r1.Data.(*model.PostList)
if len(pl.Posts) != 3 {
t.Fatal("invalid returned post")
}
}
<-store.Post().Delete(o3.Id, model.GetMillis())
if r2 := <-store.Post().Get(o1.Id); r2.Err != nil {
t.Fatal(r2.Err)
} else {
pl := r2.Data.(*model.PostList)
if len(pl.Posts) != 2 {
t.Fatal("invalid returned post")
}
}
<-store.Post().Delete(o2.Id, model.GetMillis())
if r3 := <-store.Post().Get(o1.Id); r3.Err != nil {
t.Fatal(r3.Err)
} else {
pl := r3.Data.(*model.PostList)
if len(pl.Posts) != 1 {
t.Fatal("invalid returned post")
}
}
}
func TestPostStoreGetPostsWtihDetails(t *testing.T) {
Setup()
o1 := &model.Post{}
o1.ChannelId = model.NewId()
o1.UserId = model.NewId()
o1.Message = "a" + model.NewId() + "b"
o1 = (<-store.Post().Save(o1)).Data.(*model.Post)
time.Sleep(2 * time.Millisecond)
o2 := &model.Post{}
o2.ChannelId = o1.ChannelId
o2.UserId = model.NewId()
o2.Message = "a" + model.NewId() + "b"
o2.ParentId = o1.Id
o2.RootId = o1.Id
o2 = (<-store.Post().Save(o2)).Data.(*model.Post)
time.Sleep(2 * time.Millisecond)
o2a := &model.Post{}
o2a.ChannelId = o1.ChannelId
o2a.UserId = model.NewId()
o2a.Message = "a" + model.NewId() + "b"
o2a.ParentId = o1.Id
o2a.RootId = o1.Id
o2a = (<-store.Post().Save(o2a)).Data.(*model.Post)
time.Sleep(2 * time.Millisecond)
o3 := &model.Post{}
o3.ChannelId = o1.ChannelId
o3.UserId = model.NewId()
o3.Message = "a" + model.NewId() + "b"
o3.ParentId = o1.Id
o3.RootId = o1.Id
o3 = (<-store.Post().Save(o3)).Data.(*model.Post)
time.Sleep(2 * time.Millisecond)
o4 := &model.Post{}
o4.ChannelId = o1.ChannelId
o4.UserId = model.NewId()
o4.Message = "a" + model.NewId() + "b"
o4 = (<-store.Post().Save(o4)).Data.(*model.Post)
time.Sleep(2 * time.Millisecond)
o5 := &model.Post{}
o5.ChannelId = o1.ChannelId
o5.UserId = model.NewId()
o5.Message = "a" + model.NewId() + "b"
o5.ParentId = o4.Id
o5.RootId = o4.Id
o5 = (<-store.Post().Save(o5)).Data.(*model.Post)
r1 := (<-store.Post().GetPosts(o1.ChannelId, 0, 4)).Data.(*model.PostList)
if r1.Order[0] != o5.Id {
t.Fatal("invalid order")
}
if r1.Order[1] != o4.Id {
t.Fatal("invalid order")
}
if r1.Order[2] != o3.Id {
t.Fatal("invalid order")
}
if r1.Order[3] != o2a.Id {
t.Fatal("invalid order")
}
if len(r1.Posts) != 6 {
t.Fatal("wrong size")
}
if r1.Posts[o1.Id].Message != o1.Message {
t.Fatal("Missing parent")
}
}
func TestPostStoreSearch(t *testing.T) {
Setup()
teamId := model.NewId()
userId := model.NewId()
c1 := &model.Channel{}
c1.TeamId = teamId
c1.DisplayName = "Channel1"
c1.Name = "a" + model.NewId() + "b"
c1.Type = model.CHANNEL_OPEN
c1 = (<-store.Channel().Save(c1)).Data.(*model.Channel)
m1 := model.ChannelMember{}
m1.ChannelId = c1.Id
m1.UserId = userId
m1.NotifyLevel = model.CHANNEL_NOTIFY_ALL
<-store.Channel().SaveMember(&m1)
c2 := &model.Channel{}
c2.TeamId = teamId
c2.DisplayName = "Channel1"
c2.Name = "a" + model.NewId() + "b"
c2.Type = model.CHANNEL_OPEN
c2 = (<-store.Channel().Save(c2)).Data.(*model.Channel)
o1 := &model.Post{}
o1.ChannelId = c1.Id
o1.UserId = model.NewId()
o1.Message = "corey mattermost new york"
o1 = (<-store.Post().Save(o1)).Data.(*model.Post)
o2 := &model.Post{}
o2.ChannelId = c1.Id
o2.UserId = model.NewId()
o2.Message = "New Jersey is where John is from"
o2 = (<-store.Post().Save(o2)).Data.(*model.Post)
o3 := &model.Post{}
o3.ChannelId = c2.Id
o3.UserId = model.NewId()
o3.Message = "New Jersey is where John is from corey new york"
o3 = (<-store.Post().Save(o3)).Data.(*model.Post)
o4 := &model.Post{}
o4.ChannelId = c1.Id
o4.UserId = model.NewId()
o4.Hashtags = "#hashtag"
o4.Message = "message"
o4 = (<-store.Post().Save(o4)).Data.(*model.Post)
o5 := &model.Post{}
o5.ChannelId = c1.Id
o5.UserId = model.NewId()
o5.Hashtags = "#secret #howdy"
o5 = (<-store.Post().Save(o5)).Data.(*model.Post)
r1 := (<-store.Post().Search(teamId, userId, "corey", false)).Data.(*model.PostList)
if len(r1.Order) != 1 && r1.Order[0] != o1.Id {
t.Fatal("returned wrong serach result")
}
r2 := (<-store.Post().Search(teamId, userId, "new york", false)).Data.(*model.PostList)
if len(r2.Order) != 2 && r2.Order[0] != o2.Id {
t.Fatal("returned wrong serach result")
}
r3 := (<-store.Post().Search(teamId, userId, "new", false)).Data.(*model.PostList)
if len(r3.Order) != 2 && r3.Order[0] != o1.Id {
t.Fatal("returned wrong serach result")
}
r4 := (<-store.Post().Search(teamId, userId, "john", false)).Data.(*model.PostList)
if len(r4.Order) != 1 && r4.Order[0] != o2.Id {
t.Fatal("returned wrong serach result")
}
r5 := (<-store.Post().Search(teamId, userId, "matter*", false)).Data.(*model.PostList)
if len(r5.Order) != 1 && r5.Order[0] != o1.Id {
t.Fatal("returned wrong serach result")
}
r6 := (<-store.Post().Search(teamId, userId, "#hashtag", true)).Data.(*model.PostList)
if len(r6.Order) != 1 && r6.Order[0] != o4.Id {
t.Fatal("returned wrong serach result")
}
r7 := (<-store.Post().Search(teamId, userId, "#secret", true)).Data.(*model.PostList)
if len(r7.Order) != 1 && r7.Order[0] != o5.Id {
t.Fatal("returned wrong serach result")
}
r8 := (<-store.Post().Search(teamId, userId, "@thisshouldmatchnothing", true)).Data.(*model.PostList)
if len(r8.Order) != 0 {
t.Fatal("returned wrong serach result")
}
}

176
store/sql_session_store.go Обычный файл
Просмотреть файл

@@ -0,0 +1,176 @@
// Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved.
// See License.txt for license information.
package store
import (
l4g "code.google.com/p/log4go"
"github.com/mattermost/platform/model"
)
type SqlSessionStore struct {
*SqlStore
}
func NewSqlSessionStore(sqlStore *SqlStore) SessionStore {
us := &SqlSessionStore{sqlStore}
for _, db := range sqlStore.GetAllConns() {
table := db.AddTableWithName(model.Session{}, "Sessions").SetKeys(false, "Id")
table.ColMap("Id").SetMaxSize(26)
table.ColMap("AltId").SetMaxSize(26)
table.ColMap("UserId").SetMaxSize(26)
table.ColMap("TeamId").SetMaxSize(26)
table.ColMap("DeviceId").SetMaxSize(128)
table.ColMap("Roles").SetMaxSize(64)
table.ColMap("Props").SetMaxSize(1000)
}
return us
}
func (me SqlSessionStore) UpgradeSchemaIfNeeded() {
}
func (me SqlSessionStore) CreateIndexesIfNotExists() {
me.CreateIndexIfNotExists("idx_user_id", "Sessions", "UserId")
}
func (me SqlSessionStore) Save(session *model.Session) StoreChannel {
storeChannel := make(StoreChannel)
go func() {
result := StoreResult{}
if len(session.Id) > 0 {
result.Err = model.NewAppError("SqlSessionStore.Save", "Cannot update existing session", "id="+session.Id)
storeChannel <- result
close(storeChannel)
return
}
session.PreSave()
if cur := <-me.CleanUpExpiredSessions(session.UserId); cur.Err != nil {
l4g.Error("Failed to cleanup sessions in Save err=%v", cur.Err)
}
if err := me.GetMaster().Insert(session); err != nil {
result.Err = model.NewAppError("SqlSessionStore.Save", "We couldn't save the session", "id="+session.Id+", "+err.Error())
} else {
result.Data = session
}
storeChannel <- result
close(storeChannel)
}()
return storeChannel
}
func (me SqlSessionStore) Get(id string) StoreChannel {
storeChannel := make(StoreChannel)
go func() {
result := StoreResult{}
if obj, err := me.GetReplica().Get(model.Session{}, id); err != nil {
result.Err = model.NewAppError("SqlSessionStore.Get", "We encounted an error finding the session", "id="+id+", "+err.Error())
} else if obj == nil {
result.Err = model.NewAppError("SqlSessionStore.Get", "We couldn't find the existing session", "id="+id)
} else {
result.Data = obj.(*model.Session)
}
storeChannel <- result
close(storeChannel)
}()
return storeChannel
}
func (me SqlSessionStore) GetSessions(userId string) StoreChannel {
storeChannel := make(StoreChannel)
go func() {
if cur := <-me.CleanUpExpiredSessions(userId); cur.Err != nil {
l4g.Error("Failed to cleanup sessions in getSessions err=%v", cur.Err)
}
result := StoreResult{}
var sessions []*model.Session
if _, err := me.GetReplica().Select(&sessions, "SELECT * FROM Sessions WHERE UserId = ? ORDER BY LastActivityAt DESC", userId); err != nil {
result.Err = model.NewAppError("SqlSessionStore.GetSessions", "We encounted an error while finding user sessions", err.Error())
} else {
result.Data = sessions
}
storeChannel <- result
close(storeChannel)
}()
return storeChannel
}
func (me SqlSessionStore) Remove(sessionIdOrAlt string) StoreChannel {
storeChannel := make(StoreChannel)
go func() {
result := StoreResult{}
_, err := me.GetMaster().Exec("DELETE FROM Sessions WHERE Id = ? Or AltId = ?", sessionIdOrAlt, sessionIdOrAlt)
if err != nil {
result.Err = model.NewAppError("SqlSessionStore.RemoveSession", "We couldn't remove the session", "id="+sessionIdOrAlt+", err="+err.Error())
}
storeChannel <- result
close(storeChannel)
}()
return storeChannel
}
func (me SqlSessionStore) CleanUpExpiredSessions(userId string) StoreChannel {
storeChannel := make(StoreChannel)
go func() {
result := StoreResult{}
if _, err := me.GetMaster().Exec("DELETE FROM Sessions WHERE UserId = ? AND ExpiresAt != 0 AND ? > ExpiresAt", userId, model.GetMillis()); err != nil {
result.Err = model.NewAppError("SqlSessionStore.CleanUpExpiredSessions", "We encounted an error while deleting expired user sessions", err.Error())
} else {
result.Data = userId
}
storeChannel <- result
close(storeChannel)
}()
return storeChannel
}
func (me SqlSessionStore) UpdateLastActivityAt(sessionId string, time int64) StoreChannel {
storeChannel := make(StoreChannel)
go func() {
result := StoreResult{}
if _, err := me.GetMaster().Exec("UPDATE Sessions SET LastActivityAt = ? WHERE Id = ?", time, sessionId); err != nil {
result.Err = model.NewAppError("SqlSessionStore.UpdateLastActivityAt", "We couldn't update the last_activity_at", "sessionId="+sessionId)
} else {
result.Data = sessionId
}
storeChannel <- result
close(storeChannel)
}()
return storeChannel
}

134
store/sql_session_store_test.go Обычный файл
Просмотреть файл

@@ -0,0 +1,134 @@
// Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved.
// See License.txt for license information.
package store
import (
"github.com/mattermost/platform/model"
"testing"
)
func TestSessionStoreSave(t *testing.T) {
Setup()
s1 := model.Session{}
s1.UserId = model.NewId()
s1.TeamId = model.NewId()
if err := (<-store.Session().Save(&s1)).Err; err != nil {
t.Fatal(err)
}
}
func TestSessionGet(t *testing.T) {
Setup()
s1 := model.Session{}
s1.UserId = model.NewId()
s1.TeamId = model.NewId()
<-store.Session().Save(&s1)
s2 := model.Session{}
s2.UserId = s1.UserId
s2.TeamId = s1.TeamId
<-store.Session().Save(&s2)
s3 := model.Session{}
s3.UserId = s1.UserId
s3.TeamId = s1.TeamId
s3.ExpiresAt = 1
<-store.Session().Save(&s3)
if rs1 := (<-store.Session().Get(s1.Id)); rs1.Err != nil {
t.Fatal(rs1.Err)
} else {
if rs1.Data.(*model.Session).Id != s1.Id {
t.Fatal("should match")
}
}
if rs2 := (<-store.Session().GetSessions(s1.UserId)); rs2.Err != nil {
t.Fatal(rs2.Err)
} else {
if len(rs2.Data.([]*model.Session)) != 2 {
t.Fatal("should match len")
}
}
}
func TestSessionRemove(t *testing.T) {
Setup()
s1 := model.Session{}
s1.UserId = model.NewId()
s1.TeamId = model.NewId()
<-store.Session().Save(&s1)
if rs1 := (<-store.Session().Get(s1.Id)); rs1.Err != nil {
t.Fatal(rs1.Err)
} else {
if rs1.Data.(*model.Session).Id != s1.Id {
t.Fatal("should match")
}
}
<-store.Session().Remove(s1.Id)
if rs2 := (<-store.Session().Get(s1.Id)); rs2.Err == nil {
t.Fatal("should have been removed")
}
}
func TestSessionRemoveAlt(t *testing.T) {
Setup()
s1 := model.Session{}
s1.UserId = model.NewId()
s1.TeamId = model.NewId()
<-store.Session().Save(&s1)
if rs1 := (<-store.Session().Get(s1.Id)); rs1.Err != nil {
t.Fatal(rs1.Err)
} else {
if rs1.Data.(*model.Session).Id != s1.Id {
t.Fatal("should match")
}
}
<-store.Session().Remove(s1.AltId)
if rs2 := (<-store.Session().Get(s1.Id)); rs2.Err == nil {
t.Fatal("should have been removed")
}
if rs3 := (<-store.Session().GetSessions(s1.UserId)); rs3.Err != nil {
t.Fatal(rs3.Err)
} else {
if len(rs3.Data.([]*model.Session)) != 0 {
t.Fatal("should match len")
}
}
}
func TestSessionStoreUpdateLastActivityAt(t *testing.T) {
Setup()
s1 := model.Session{}
s1.UserId = model.NewId()
s1.TeamId = model.NewId()
<-store.Session().Save(&s1)
if err := (<-store.Session().UpdateLastActivityAt(s1.Id, 1234567890)).Err; err != nil {
t.Fatal(err)
}
if r1 := <-store.Session().Get(s1.Id); r1.Err != nil {
t.Fatal(r1.Err)
} else {
if r1.Data.(*model.Session).LastActivityAt != 1234567890 {
t.Fatal("LastActivityAt not updated correctly")
}
}
}

372
store/sql_store.go Обычный файл
Просмотреть файл

@@ -0,0 +1,372 @@
// Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved.
// See License.txt for license information.
package store
import (
l4g "code.google.com/p/log4go"
"crypto/aes"
"crypto/cipher"
crand "crypto/rand"
dbsql "database/sql"
"encoding/base64"
"encoding/json"
"errors"
"fmt"
"github.com/go-gorp/gorp"
_ "github.com/go-sql-driver/mysql"
"github.com/mattermost/platform/model"
"github.com/mattermost/platform/utils"
"io"
sqltrace "log"
"math/rand"
"os"
"time"
)
type SqlStore struct {
master *gorp.DbMap
replicas []*gorp.DbMap
team TeamStore
channel ChannelStore
post PostStore
user UserStore
audit AuditStore
session SessionStore
}
func NewSqlStore() Store {
sqlStore := &SqlStore{}
sqlStore.master = setupConnection("master", utils.Cfg.SqlSettings.DriverName,
utils.Cfg.SqlSettings.DataSource, utils.Cfg.SqlSettings.MaxIdleConns,
utils.Cfg.SqlSettings.MaxOpenConns, utils.Cfg.SqlSettings.Trace)
sqlStore.replicas = make([]*gorp.DbMap, len(utils.Cfg.SqlSettings.DataSourceReplicas))
for i, replica := range utils.Cfg.SqlSettings.DataSourceReplicas {
sqlStore.replicas[i] = setupConnection(fmt.Sprintf("replica-%v", i), utils.Cfg.SqlSettings.DriverName, replica,
utils.Cfg.SqlSettings.MaxIdleConns, utils.Cfg.SqlSettings.MaxOpenConns,
utils.Cfg.SqlSettings.Trace)
}
sqlStore.team = NewSqlTeamStore(sqlStore)
sqlStore.channel = NewSqlChannelStore(sqlStore)
sqlStore.post = NewSqlPostStore(sqlStore)
sqlStore.user = NewSqlUserStore(sqlStore)
sqlStore.audit = NewSqlAuditStore(sqlStore)
sqlStore.session = NewSqlSessionStore(sqlStore)
sqlStore.master.CreateTablesIfNotExists()
sqlStore.team.(*SqlTeamStore).CreateIndexesIfNotExists()
sqlStore.channel.(*SqlChannelStore).CreateIndexesIfNotExists()
sqlStore.post.(*SqlPostStore).CreateIndexesIfNotExists()
sqlStore.user.(*SqlUserStore).CreateIndexesIfNotExists()
sqlStore.audit.(*SqlAuditStore).CreateIndexesIfNotExists()
sqlStore.session.(*SqlSessionStore).CreateIndexesIfNotExists()
sqlStore.team.(*SqlTeamStore).UpgradeSchemaIfNeeded()
sqlStore.channel.(*SqlChannelStore).UpgradeSchemaIfNeeded()
sqlStore.post.(*SqlPostStore).UpgradeSchemaIfNeeded()
sqlStore.user.(*SqlUserStore).UpgradeSchemaIfNeeded()
sqlStore.audit.(*SqlAuditStore).UpgradeSchemaIfNeeded()
sqlStore.session.(*SqlSessionStore).UpgradeSchemaIfNeeded()
return sqlStore
}
func setupConnection(con_type string, driver string, dataSource string, maxIdle int, maxOpen int, trace bool) *gorp.DbMap {
db, err := dbsql.Open(driver, dataSource)
if err != nil {
l4g.Critical("Failed to open sql connection to '%v' err:%v", dataSource, err)
time.Sleep(time.Second)
panic("Failed to open sql connection" + err.Error())
}
l4g.Info("Pinging sql %v database at '%v'", con_type, dataSource)
err = db.Ping()
if err != nil {
l4g.Critical("Failed to ping db err:%v", err)
time.Sleep(time.Second)
panic("Failed to open sql connection " + err.Error())
}
db.SetMaxIdleConns(maxIdle)
db.SetMaxOpenConns(maxOpen)
var dbmap *gorp.DbMap
if driver == "sqlite3" {
dbmap = &gorp.DbMap{Db: db, TypeConverter: mattermConverter{}, Dialect: gorp.SqliteDialect{}}
} else if driver == "mysql" {
dbmap = &gorp.DbMap{Db: db, TypeConverter: mattermConverter{}, Dialect: gorp.MySQLDialect{Engine: "InnoDB", Encoding: "UTF8"}}
} else {
l4g.Critical("Failed to create dialect specific driver")
time.Sleep(time.Second)
panic("Failed to create dialect specific driver " + err.Error())
}
if trace {
dbmap.TraceOn("", sqltrace.New(os.Stdout, "sql-trace:", sqltrace.Lmicroseconds))
}
return dbmap
}
func (ss SqlStore) CreateColumnIfNotExists(tableName string, columnName string, afterName string, colType string, defaultValue string) bool {
count, err := ss.GetMaster().SelectInt(
`SELECT
COUNT(0) AS column_exists
FROM
information_schema.COLUMNS
WHERE
TABLE_SCHEMA = DATABASE()
AND TABLE_NAME = ?
AND COLUMN_NAME = ?`,
tableName,
columnName,
)
if err != nil {
l4g.Critical("Failed to check if column exists %v", err)
time.Sleep(time.Second)
panic("Failed to check if column exists " + err.Error())
}
if count > 0 {
return false
}
_, err = ss.GetMaster().Exec("ALTER TABLE " + tableName + " ADD " + columnName + " " + colType + " DEFAULT '" + defaultValue + "'" + " AFTER " + afterName)
if err != nil {
l4g.Critical("Failed to create column %v", err)
time.Sleep(time.Second)
panic("Failed to create column " + err.Error())
}
return true
}
func (ss SqlStore) RemoveColumnIfExists(tableName string, columnName string) bool {
count, err := ss.GetMaster().SelectInt(
`SELECT
COUNT(0) AS column_exists
FROM
information_schema.COLUMNS
WHERE
TABLE_SCHEMA = DATABASE()
AND TABLE_NAME = ?
AND COLUMN_NAME = ?`,
tableName,
columnName,
)
if err != nil {
l4g.Critical("Failed to check if column exists %v", err)
time.Sleep(time.Second)
panic("Failed to check if column exists " + err.Error())
}
if count == 0 {
return false
}
_, err = ss.GetMaster().Exec("ALTER TABLE " + tableName + " DROP COLUMN " + columnName)
if err != nil {
l4g.Critical("Failed to drop column %v", err)
time.Sleep(time.Second)
panic("Failed to drop column " + err.Error())
}
return true
}
func (ss SqlStore) CreateIndexIfNotExists(indexName string, tableName string, columnName string) {
ss.createIndexIfNotExists(indexName, tableName, columnName, false)
}
func (ss SqlStore) CreateFullTextIndexIfNotExists(indexName string, tableName string, columnName string) {
ss.createIndexIfNotExists(indexName, tableName, columnName, true)
}
func (ss SqlStore) createIndexIfNotExists(indexName string, tableName string, columnName string, fullText bool) {
count, err := ss.GetMaster().SelectInt("SELECT COUNT(0) AS index_exists FROM information_schema.statistics WHERE TABLE_SCHEMA = DATABASE() and table_name = ? AND index_name = ?", tableName, indexName)
if err != nil {
l4g.Critical("Failed to check index", err)
time.Sleep(time.Second)
panic("Failed to check index" + err.Error())
}
if count > 0 {
return
}
fullTextIndex := ""
if fullText {
fullTextIndex = " FULLTEXT "
}
_, err = ss.GetMaster().Exec("CREATE " + fullTextIndex + " INDEX " + indexName + " ON " + tableName + " (" + columnName + ")")
if err != nil {
l4g.Critical("Failed to create index", err)
time.Sleep(time.Second)
panic("Failed to create index " + err.Error())
}
}
func (ss SqlStore) GetMaster() *gorp.DbMap {
return ss.master
}
func (ss SqlStore) GetReplica() *gorp.DbMap {
return ss.replicas[rand.Intn(len(ss.replicas))]
}
func (ss SqlStore) GetAllConns() []*gorp.DbMap {
all := make([]*gorp.DbMap, len(ss.replicas)+1)
copy(all, ss.replicas)
all[len(ss.replicas)] = ss.master
return all
}
func (ss SqlStore) Close() {
l4g.Info("Closing SqlStore")
ss.master.Db.Close()
for _, replica := range ss.replicas {
replica.Db.Close()
}
}
func (ss SqlStore) Team() TeamStore {
return ss.team
}
func (ss SqlStore) Channel() ChannelStore {
return ss.channel
}
func (ss SqlStore) Post() PostStore {
return ss.post
}
func (ss SqlStore) User() UserStore {
return ss.user
}
func (ss SqlStore) Session() SessionStore {
return ss.session
}
func (ss SqlStore) Audit() AuditStore {
return ss.audit
}
type mattermConverter struct{}
func (me mattermConverter) ToDb(val interface{}) (interface{}, error) {
switch t := val.(type) {
case model.StringMap:
return model.MapToJson(t), nil
case model.StringArray:
return model.ArrayToJson(t), nil
case model.EncryptStringMap:
return encrypt([]byte(utils.Cfg.SqlSettings.AtRestEncryptKey), model.MapToJson(t))
}
return val, nil
}
func (me mattermConverter) FromDb(target interface{}) (gorp.CustomScanner, bool) {
switch target.(type) {
case *model.StringMap:
binder := func(holder, target interface{}) error {
s, ok := holder.(*string)
if !ok {
return errors.New("FromDb: Unable to convert StringMap to *string")
}
b := []byte(*s)
return json.Unmarshal(b, target)
}
return gorp.CustomScanner{new(string), target, binder}, true
case *model.StringArray:
binder := func(holder, target interface{}) error {
s, ok := holder.(*string)
if !ok {
return errors.New("FromDb: Unable to convert StringArray to *string")
}
b := []byte(*s)
return json.Unmarshal(b, target)
}
return gorp.CustomScanner{new(string), target, binder}, true
case *model.EncryptStringMap:
binder := func(holder, target interface{}) error {
s, ok := holder.(*string)
if !ok {
return errors.New("FromDb: Unable to convert EncryptStringMap to *string")
}
ue, err := decrypt([]byte(utils.Cfg.SqlSettings.AtRestEncryptKey), *s)
if err != nil {
return err
}
b := []byte(ue)
return json.Unmarshal(b, target)
}
return gorp.CustomScanner{new(string), target, binder}, true
}
return gorp.CustomScanner{}, false
}
func encrypt(key []byte, text string) (string, error) {
if text == "" || text == "{}" {
return "", nil
}
plaintext := []byte(text)
block, err := aes.NewCipher(key)
if err != nil {
return "", err
}
ciphertext := make([]byte, aes.BlockSize+len(plaintext))
iv := ciphertext[:aes.BlockSize]
if _, err := io.ReadFull(crand.Reader, iv); err != nil {
return "", err
}
stream := cipher.NewCFBEncrypter(block, iv)
stream.XORKeyStream(ciphertext[aes.BlockSize:], plaintext)
return base64.URLEncoding.EncodeToString(ciphertext), nil
}
func decrypt(key []byte, cryptoText string) (string, error) {
if cryptoText == "" || cryptoText == "{}" {
return "{}", nil
}
ciphertext, _ := base64.URLEncoding.DecodeString(cryptoText)
block, err := aes.NewCipher(key)
if err != nil {
return "", err
}
if len(ciphertext) < aes.BlockSize {
return "", errors.New("ciphertext too short")
}
iv := ciphertext[:aes.BlockSize]
ciphertext = ciphertext[aes.BlockSize:]
stream := cipher.NewCFBDecrypter(block, iv)
stream.XORKeyStream(ciphertext, ciphertext)
return fmt.Sprintf("%s", ciphertext), nil
}

83
store/sql_store_test.go Обычный файл
Просмотреть файл

@@ -0,0 +1,83 @@
// Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved.
// See License.txt for license information.
package store
import (
"github.com/mattermost/platform/model"
"github.com/mattermost/platform/utils"
"strings"
"testing"
)
var store Store
func Setup() {
if store == nil {
utils.LoadConfig("config.json")
store = NewSqlStore()
}
}
func TestSqlStore1(t *testing.T) {
utils.LoadConfig("config.json")
utils.Cfg.SqlSettings.Trace = true
store := NewSqlStore()
store.Close()
utils.LoadConfig("config.json")
}
func TestSqlStore2(t *testing.T) {
defer func() {
if r := recover(); r == nil {
t.Fatal("should have been fatal")
}
}()
utils.LoadConfig("config.json")
utils.Cfg.SqlSettings.DriverName = "missing"
store = NewSqlStore()
utils.LoadConfig("config.json")
}
func TestSqlStore3(t *testing.T) {
defer func() {
if r := recover(); r == nil {
t.Fatal("should have been fatal")
}
}()
utils.LoadConfig("config.json")
utils.Cfg.SqlSettings.DataSource = "missing"
store = NewSqlStore()
utils.LoadConfig("config.json")
}
func TestEncrypt(t *testing.T) {
m := make(map[string]string)
key := []byte("IPc17oYK9NAj6WfJeCqm5AxIBF6WBNuN") // AES-256
originalText1 := model.MapToJson(m)
cryptoText1, _ := encrypt(key, originalText1)
text1, _ := decrypt(key, cryptoText1)
rm1 := model.MapFromJson(strings.NewReader(text1))
if len(rm1) != 0 {
t.Fatal("error in encrypt")
}
m["key"] = "value"
originalText2 := model.MapToJson(m)
cryptoText2, _ := encrypt(key, originalText2)
text2, _ := decrypt(key, cryptoText2)
rm2 := model.MapFromJson(strings.NewReader(text2))
if rm2["key"] != "value" {
t.Fatal("error in encrypt")
}
}

195
store/sql_team_store.go Обычный файл
Просмотреть файл

@@ -0,0 +1,195 @@
// Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved.
// See License.txt for license information.
package store
import (
"github.com/mattermost/platform/model"
"strings"
)
type SqlTeamStore struct {
*SqlStore
}
func NewSqlTeamStore(sqlStore *SqlStore) TeamStore {
s := &SqlTeamStore{sqlStore}
for _, db := range sqlStore.GetAllConns() {
table := db.AddTableWithName(model.Team{}, "Teams").SetKeys(false, "Id")
table.ColMap("Id").SetMaxSize(26)
table.ColMap("Name").SetMaxSize(64)
table.ColMap("Domain").SetMaxSize(64).SetUnique(true)
table.ColMap("Email").SetMaxSize(128)
table.ColMap("CompanyName").SetMaxSize(64)
table.ColMap("AllowedDomains").SetMaxSize(500)
}
return s
}
func (s SqlTeamStore) UpgradeSchemaIfNeeded() {
}
func (s SqlTeamStore) CreateIndexesIfNotExists() {
}
func (s SqlTeamStore) Save(team *model.Team) StoreChannel {
storeChannel := make(StoreChannel)
go func() {
result := StoreResult{}
if len(team.Id) > 0 {
result.Err = model.NewAppError("SqlTeamStore.Save",
"Must call update for exisiting team", "id="+team.Id)
storeChannel <- result
close(storeChannel)
return
}
team.PreSave()
if result.Err = team.IsValid(); result.Err != nil {
storeChannel <- result
close(storeChannel)
return
}
if err := s.GetMaster().Insert(team); err != nil {
if strings.Contains(err.Error(), "Duplicate entry") && strings.Contains(err.Error(), "for key 'Domain'") {
result.Err = model.NewAppError("SqlTeamStore.Save", "A team with that domain already exists", "id="+team.Id+", "+err.Error())
} else {
result.Err = model.NewAppError("SqlTeamStore.Save", "We couldn't save the team", "id="+team.Id+", "+err.Error())
}
} else {
result.Data = team
}
storeChannel <- result
close(storeChannel)
}()
return storeChannel
}
func (s SqlTeamStore) Update(team *model.Team) StoreChannel {
storeChannel := make(StoreChannel)
go func() {
result := StoreResult{}
team.PreUpdate()
if result.Err = team.IsValid(); result.Err != nil {
storeChannel <- result
close(storeChannel)
return
}
if oldResult, err := s.GetMaster().Get(model.Team{}, team.Id); err != nil {
result.Err = model.NewAppError("SqlTeamStore.Update", "We encounted an error finding the team", "id="+team.Id+", "+err.Error())
} else if oldResult == nil {
result.Err = model.NewAppError("SqlTeamStore.Update", "We couldn't find the existing team to update", "id="+team.Id)
} else {
oldTeam := oldResult.(*model.Team)
team.CreateAt = oldTeam.CreateAt
team.Domain = oldTeam.Domain
if count, err := s.GetMaster().Update(team); err != nil {
result.Err = model.NewAppError("SqlTeamStore.Update", "We encounted an error updating the team", "id="+team.Id+", "+err.Error())
} else if count != 1 {
result.Err = model.NewAppError("SqlTeamStore.Update", "We couldn't update the team", "id="+team.Id)
} else {
result.Data = team
}
}
storeChannel <- result
close(storeChannel)
}()
return storeChannel
}
func (s SqlTeamStore) UpdateName(name string, teamId string) StoreChannel {
storeChannel := make(StoreChannel)
go func() {
result := StoreResult{}
if _, err := s.GetMaster().Exec("UPDATE Teams SET Name = ? WHERE Id = ?", name, teamId); err != nil {
result.Err = model.NewAppError("SqlTeamStore.UpdateName", "We couldn't update the team name", "team_id="+teamId)
} else {
result.Data = teamId
}
storeChannel <- result
close(storeChannel)
}()
return storeChannel
}
func (s SqlTeamStore) Get(id string) StoreChannel {
storeChannel := make(StoreChannel)
go func() {
result := StoreResult{}
if obj, err := s.GetReplica().Get(model.Team{}, id); err != nil {
result.Err = model.NewAppError("SqlTeamStore.Get", "We encounted an error finding the team", "id="+id+", "+err.Error())
} else if obj == nil {
result.Err = model.NewAppError("SqlTeamStore.Get", "We couldn't find the existing team", "id="+id)
} else {
result.Data = obj.(*model.Team)
}
storeChannel <- result
close(storeChannel)
}()
return storeChannel
}
func (s SqlTeamStore) GetByDomain(domain string) StoreChannel {
storeChannel := make(StoreChannel)
go func() {
result := StoreResult{}
team := model.Team{}
if err := s.GetReplica().SelectOne(&team, "SELECT * FROM Teams WHERE Domain=?", domain); err != nil {
result.Err = model.NewAppError("SqlTeamStore.GetByDomain", "We couldn't find the existing team", "domain="+domain+", "+err.Error())
}
result.Data = &team
storeChannel <- result
close(storeChannel)
}()
return storeChannel
}
func (s SqlTeamStore) GetTeamsForEmail(email string) StoreChannel {
storeChannel := make(StoreChannel)
go func() {
result := StoreResult{}
var data []*model.Team
if _, err := s.GetReplica().Select(&data, "SELECT Teams.* FROM Teams, Users WHERE Teams.Id = Users.TeamId AND Users.Email = ?", email); err != nil {
result.Err = model.NewAppError("SqlTeamStore.GetTeamsForEmail", "We encounted a problem when looking up teams", "email="+email+", "+err.Error())
}
result.Data = data
storeChannel <- result
close(storeChannel)
}()
return storeChannel
}

155
store/sql_team_store_test.go Обычный файл
Просмотреть файл

@@ -0,0 +1,155 @@
// Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved.
// See License.txt for license information.
package store
import (
"github.com/mattermost/platform/model"
"testing"
)
func TestTeamStoreSave(t *testing.T) {
Setup()
o1 := model.Team{}
o1.Name = "Name"
o1.Domain = "a" + model.NewId() + "b"
o1.Email = model.NewId() + "@nowhere.com"
o1.Type = model.TEAM_OPEN
if err := (<-store.Team().Save(&o1)).Err; err != nil {
t.Fatal("couldn't save item", err)
}
if err := (<-store.Team().Save(&o1)).Err; err == nil {
t.Fatal("shouldn't be able to update from save")
}
o1.Id = ""
if err := (<-store.Team().Save(&o1)).Err; err == nil {
t.Fatal("should be unique domain")
}
}
func TestTeamStoreUpdate(t *testing.T) {
Setup()
o1 := model.Team{}
o1.Name = "Name"
o1.Domain = "a" + model.NewId() + "b"
o1.Email = model.NewId() + "@nowhere.com"
o1.Type = model.TEAM_OPEN
<-store.Team().Save(&o1)
if err := (<-store.Team().Update(&o1)).Err; err != nil {
t.Fatal(err)
}
o1.Id = "missing"
if err := (<-store.Team().Update(&o1)).Err; err == nil {
t.Fatal("Update should have failed because of missing key")
}
o1.Id = model.NewId()
if err := (<-store.Team().Update(&o1)).Err; err == nil {
t.Fatal("Update should have faile because id change")
}
}
func TestTeamStoreUpdateName(t *testing.T) {
Setup()
o1 := &model.Team{}
o1.Name = "Name"
o1.Domain = "a" + model.NewId() + "b"
o1.Email = model.NewId() + "@nowhere.com"
o1.Type = model.TEAM_OPEN
o1 = (<-store.Team().Save(o1)).Data.(*model.Team)
newName := "NewName"
if err := (<-store.Team().UpdateName(newName, o1.Id)).Err; err != nil {
t.Fatal(err)
}
ro1 := (<-store.Team().Get(o1.Id)).Data.(*model.Team)
if ro1.Name != newName {
t.Fatal("Name not updated")
}
}
func TestTeamStoreGet(t *testing.T) {
Setup()
o1 := model.Team{}
o1.Name = "Name"
o1.Domain = "a" + model.NewId() + "b"
o1.Email = model.NewId() + "@nowhere.com"
o1.Type = model.TEAM_OPEN
<-store.Team().Save(&o1)
if r1 := <-store.Team().Get(o1.Id); r1.Err != nil {
t.Fatal(r1.Err)
} else {
if r1.Data.(*model.Team).ToJson() != o1.ToJson() {
t.Fatal("invalid returned team")
}
}
if err := (<-store.Team().Get("")).Err; err == nil {
t.Fatal("Missing id should have failed")
}
}
func TestTeamStoreGetByDomain(t *testing.T) {
Setup()
o1 := model.Team{}
o1.Name = "Name"
o1.Domain = "a" + model.NewId() + "b"
o1.Email = model.NewId() + "@nowhere.com"
o1.Type = model.TEAM_OPEN
<-store.Team().Save(&o1)
if r1 := <-store.Team().GetByDomain(o1.Domain); r1.Err != nil {
t.Fatal(r1.Err)
} else {
if r1.Data.(*model.Team).ToJson() != o1.ToJson() {
t.Fatal("invalid returned team")
}
}
if err := (<-store.Team().GetByDomain("")).Err; err == nil {
t.Fatal("Missing id should have failed")
}
}
func TestTeamStoreGetForEmail(t *testing.T) {
Setup()
o1 := model.Team{}
o1.Name = "Name"
o1.Domain = "a" + model.NewId() + "b"
o1.Email = model.NewId() + "@nowhere.com"
o1.Type = model.TEAM_OPEN
<-store.Team().Save(&o1)
u1 := model.User{}
u1.TeamId = o1.Id
u1.Email = model.NewId()
<-store.User().Save(&u1)
if r1 := <-store.Team().GetTeamsForEmail(u1.Email); r1.Err != nil {
t.Fatal(r1.Err)
} else {
teams := r1.Data.([]*model.Team)
if teams[0].Id != o1.Id {
t.Fatal("failed to lookup by email")
}
}
if r1 := <-store.Team().GetTeamsForEmail("missing"); r1.Err != nil {
t.Fatal(r1.Err)
}
}

367
store/sql_user_store.go Обычный файл
Просмотреть файл

@@ -0,0 +1,367 @@
// Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved.
// See License.txt for license information.
package store
import (
"fmt"
"github.com/mattermost/platform/model"
"github.com/mattermost/platform/utils"
"strings"
)
type SqlUserStore struct {
*SqlStore
}
func NewSqlUserStore(sqlStore *SqlStore) UserStore {
us := &SqlUserStore{sqlStore}
for _, db := range sqlStore.GetAllConns() {
table := db.AddTableWithName(model.User{}, "Users").SetKeys(false, "Id")
table.ColMap("Id").SetMaxSize(26)
table.ColMap("TeamId").SetMaxSize(26)
table.ColMap("Username").SetMaxSize(64)
table.ColMap("Password").SetMaxSize(128)
table.ColMap("AuthData").SetMaxSize(128)
table.ColMap("Email").SetMaxSize(128)
table.ColMap("FullName").SetMaxSize(64)
table.ColMap("Roles").SetMaxSize(64)
table.ColMap("Props").SetMaxSize(4000)
table.ColMap("NotifyProps").SetMaxSize(2000)
table.SetUniqueTogether("Email", "TeamId")
table.SetUniqueTogether("Username", "TeamId")
}
return us
}
func (s SqlUserStore) UpgradeSchemaIfNeeded() {
}
func (us SqlUserStore) CreateIndexesIfNotExists() {
us.CreateIndexIfNotExists("idx_team_id", "Users", "TeamId")
}
func (us SqlUserStore) Save(user *model.User) StoreChannel {
storeChannel := make(StoreChannel)
go func() {
result := StoreResult{}
if len(user.Id) > 0 {
result.Err = model.NewAppError("SqlUserStore.Save", "Must call update for exisiting user", "user_id="+user.Id)
storeChannel <- result
close(storeChannel)
return
}
user.PreSave()
if result.Err = user.IsValid(); result.Err != nil {
storeChannel <- result
close(storeChannel)
return
}
if count, err := us.GetMaster().SelectInt("SELECT COUNT(0) FROM Users WHERE TeamId = ? AND DeleteAt = 0", user.TeamId); err != nil {
result.Err = model.NewAppError("SqlUserStore.Save", "Failed to get current team member count", "teamId="+user.TeamId+", "+err.Error())
storeChannel <- result
close(storeChannel)
return
} else if int(count) > utils.Cfg.TeamSettings.MaxUsersPerTeam {
result.Err = model.NewAppError("SqlUserStore.Save", "You've reached the limit of the number of allowed accounts.", "teamId="+user.TeamId)
storeChannel <- result
close(storeChannel)
return
}
if err := us.GetMaster().Insert(user); err != nil {
if strings.Contains(err.Error(), "Duplicate entry") && strings.Contains(err.Error(), "for key 'Email'") {
result.Err = model.NewAppError("SqlUserStore.Save", "An account with that email already exists.", "user_id="+user.Id+", "+err.Error())
} else if strings.Contains(err.Error(), "Duplicate entry") && strings.Contains(err.Error(), "for key 'Username'") {
result.Err = model.NewAppError("SqlUserStore.Save", "An account with that username already exists.", "user_id="+user.Id+", "+err.Error())
} else {
result.Err = model.NewAppError("SqlUserStore.Save", "We couldn't save the account.", "user_id="+user.Id+", "+err.Error())
}
} else {
result.Data = user
}
storeChannel <- result
close(storeChannel)
}()
return storeChannel
}
func (us SqlUserStore) Update(user *model.User, allowRoleActiveUpdate bool) StoreChannel {
storeChannel := make(StoreChannel)
go func() {
result := StoreResult{}
user.PreUpdate()
if result.Err = user.IsValid(); result.Err != nil {
storeChannel <- result
close(storeChannel)
return
}
if oldUserResult, err := us.GetMaster().Get(model.User{}, user.Id); err != nil {
result.Err = model.NewAppError("SqlUserStore.Update", "We encounted an error finding the account", "user_id="+user.Id+", "+err.Error())
} else if oldUserResult == nil {
result.Err = model.NewAppError("SqlUserStore.Update", "We couldn't find the existing account to update", "user_id="+user.Id)
} else {
oldUser := oldUserResult.(*model.User)
user.CreateAt = oldUser.CreateAt
user.AuthData = oldUser.AuthData
user.Password = oldUser.Password
user.LastPasswordUpdate = oldUser.LastPasswordUpdate
user.TeamId = oldUser.TeamId
user.LastActivityAt = oldUser.LastActivityAt
user.LastPingAt = oldUser.LastPingAt
user.EmailVerified = oldUser.EmailVerified
if !allowRoleActiveUpdate {
user.Roles = oldUser.Roles
user.DeleteAt = oldUser.DeleteAt
}
if user.Email != oldUser.Email {
user.EmailVerified = false
}
if count, err := us.GetMaster().Update(user); err != nil {
result.Err = model.NewAppError("SqlUserStore.Update", "We encounted an error updating the account", "user_id="+user.Id+", "+err.Error())
} else if count != 1 {
result.Err = model.NewAppError("SqlUserStore.Update", "We couldn't update the account", "user_id="+user.Id)
} else {
result.Data = [2]*model.User{user, oldUser}
}
}
storeChannel <- result
close(storeChannel)
}()
return storeChannel
}
func (us SqlUserStore) UpdateLastPingAt(userId string, time int64) StoreChannel {
storeChannel := make(StoreChannel)
go func() {
result := StoreResult{}
if _, err := us.GetMaster().Exec("UPDATE Users SET LastPingAt = ? WHERE Id = ?", time, userId); err != nil {
result.Err = model.NewAppError("SqlUserStore.UpdateLastPingAt", "We couldn't update the last_ping_at", "user_id="+userId)
} else {
result.Data = userId
}
storeChannel <- result
close(storeChannel)
}()
return storeChannel
}
func (us SqlUserStore) UpdateLastActivityAt(userId string, time int64) StoreChannel {
storeChannel := make(StoreChannel)
go func() {
result := StoreResult{}
if _, err := us.GetMaster().Exec("UPDATE Users SET LastActivityAt = ? WHERE Id = ?", time, userId); err != nil {
result.Err = model.NewAppError("SqlUserStore.UpdateLastActivityAt", "We couldn't update the last_activity_at", "user_id="+userId)
} else {
result.Data = userId
}
storeChannel <- result
close(storeChannel)
}()
return storeChannel
}
func (us SqlUserStore) UpdateUserAndSessionActivity(userId string, sessionId string, time int64) StoreChannel {
storeChannel := make(StoreChannel)
go func() {
result := StoreResult{}
if _, err := us.GetMaster().Exec("UPDATE Sessions, Users SET Users.LastActivityAt = ?, Sessions.LastActivityAt = ? WHERE Users.Id = ? AND Sessions.Id = ?", time, time, userId, sessionId); err != nil {
result.Err = model.NewAppError("SqlUserStore.UpdateLastActivityAt", "We couldn't update the last_activity_at", "user_id="+userId+" session_id="+sessionId+" err="+err.Error())
} else {
result.Data = userId
}
storeChannel <- result
close(storeChannel)
}()
return storeChannel
}
func (us SqlUserStore) UpdatePassword(userId, hashedPassword string) StoreChannel {
storeChannel := make(StoreChannel)
go func() {
result := StoreResult{}
updateAt := model.GetMillis()
if _, err := us.GetMaster().Exec("UPDATE Users SET Password = ?, LastPasswordUpdate = ?, UpdateAt = ? WHERE Id = ?", hashedPassword, updateAt, updateAt, userId); err != nil {
result.Err = model.NewAppError("SqlUserStore.UpdatePassword", "We couldn't update the user password", "id="+userId+", "+err.Error())
} else {
result.Data = userId
}
storeChannel <- result
close(storeChannel)
}()
return storeChannel
}
func (us SqlUserStore) Get(id string) StoreChannel {
storeChannel := make(StoreChannel)
go func() {
result := StoreResult{}
if obj, err := us.GetReplica().Get(model.User{}, id); err != nil {
result.Err = model.NewAppError("SqlUserStore.Get", "We encounted an error finding the account", "user_id="+id+", "+err.Error())
} else if obj == nil {
result.Err = model.NewAppError("SqlUserStore.Get", "We couldn't find the existing account", "user_id="+id)
} else {
result.Data = obj.(*model.User)
}
storeChannel <- result
close(storeChannel)
}()
return storeChannel
}
func (s SqlUserStore) GetEtagForProfiles(teamId string) StoreChannel {
storeChannel := make(StoreChannel)
go func() {
result := StoreResult{}
updateAt, err := s.GetReplica().SelectInt("SELECT UpdateAt FROM Users WHERE TeamId = ? ORDER BY UpdateAt DESC LIMIT 1", teamId)
if err != nil {
result.Data = fmt.Sprintf("%v.%v", model.ETAG_ROOT_VERSION, model.GetMillis())
} else {
result.Data = fmt.Sprintf("%v.%v", model.ETAG_ROOT_VERSION, updateAt)
}
storeChannel <- result
close(storeChannel)
}()
return storeChannel
}
func (us SqlUserStore) GetProfiles(teamId string) StoreChannel {
storeChannel := make(StoreChannel)
go func() {
result := StoreResult{}
var users []*model.User
if _, err := us.GetReplica().Select(&users, "SELECT * FROM Users WHERE TeamId = ?", teamId); err != nil {
result.Err = model.NewAppError("SqlUserStore.GetProfiles", "We encounted an error while finding user profiles", err.Error())
} else {
userMap := make(map[string]*model.User)
for _, u := range users {
u.Password = ""
u.AuthData = ""
userMap[u.Id] = u
}
result.Data = userMap
}
storeChannel <- result
close(storeChannel)
}()
return storeChannel
}
func (us SqlUserStore) GetByEmail(teamId string, email string) StoreChannel {
storeChannel := make(StoreChannel)
go func() {
result := StoreResult{}
user := model.User{}
if err := us.GetReplica().SelectOne(&user, "SELECT * FROM Users WHERE TeamId=? AND Email=?", teamId, email); err != nil {
result.Err = model.NewAppError("SqlUserStore.GetByEmail", "We couldn't find the existing account", "teamId="+teamId+", email="+email+", "+err.Error())
}
result.Data = &user
storeChannel <- result
close(storeChannel)
}()
return storeChannel
}
func (us SqlUserStore) GetByUsername(teamId string, username string) StoreChannel {
storeChannel := make(StoreChannel)
go func() {
result := StoreResult{}
user := model.User{}
if err := us.GetReplica().SelectOne(&user, "SELECT * FROM Users WHERE TeamId=? AND Username=?", teamId, username); err != nil {
result.Err = model.NewAppError("SqlUserStore.GetByUsername", "We couldn't find the existing account", "teamId="+teamId+", username="+username+", "+err.Error())
}
result.Data = &user
storeChannel <- result
close(storeChannel)
}()
return storeChannel
}
func (us SqlUserStore) VerifyEmail(userId string) StoreChannel {
storeChannel := make(StoreChannel)
go func() {
result := StoreResult{}
if _, err := us.GetMaster().Exec("UPDATE Users SET EmailVerified = 1 WHERE Id = ?", userId); err != nil {
result.Err = model.NewAppError("SqlUserStore.VerifyEmail", "Unable to update verify email field", "userId="+userId+", "+err.Error())
}
result.Data = userId
storeChannel <- result
close(storeChannel)
}()
return storeChannel
}

276
store/sql_user_store_test.go Обычный файл
Просмотреть файл

@@ -0,0 +1,276 @@
// Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved.
// See License.txt for license information.
package store
import (
"github.com/mattermost/platform/model"
"strings"
"testing"
)
func TestUserStoreSave(t *testing.T) {
Setup()
u1 := model.User{}
u1.Email = model.NewId()
u1.Username = model.NewId()
u1.TeamId = model.NewId()
if err := (<-store.User().Save(&u1)).Err; err != nil {
t.Fatal("couldn't save user", err)
}
if err := (<-store.User().Save(&u1)).Err; err == nil {
t.Fatal("shouldn't be able to update user from save")
}
u1.Id = ""
if err := (<-store.User().Save(&u1)).Err; err == nil {
t.Fatal("should be unique email")
}
u1.Email = ""
if err := (<-store.User().Save(&u1)).Err; err == nil {
t.Fatal("should be unique username")
}
u1.Email = strings.Repeat("0123456789", 20)
u1.Username = ""
if err := (<-store.User().Save(&u1)).Err; err == nil {
t.Fatal("should be unique username")
}
for i := 0; i < 150; i++ {
u1.Id = ""
u1.Email = model.NewId()
u1.Username = model.NewId()
if err := (<-store.User().Save(&u1)).Err; err != nil {
t.Fatal("couldn't save item", err)
}
}
u1.Id = ""
u1.Email = model.NewId()
u1.Username = model.NewId()
if err := (<-store.User().Save(&u1)).Err; err == nil {
t.Fatal("should be the limit", err)
}
}
func TestUserStoreUpdate(t *testing.T) {
Setup()
u1 := model.User{}
u1.TeamId = model.NewId()
u1.Email = model.NewId()
<-store.User().Save(&u1)
if err := (<-store.User().Update(&u1, false)).Err; err != nil {
t.Fatal(err)
}
u1.Id = "missing"
if err := (<-store.User().Update(&u1, false)).Err; err == nil {
t.Fatal("Update should have failed because of missing key")
}
u1.Id = model.NewId()
if err := (<-store.User().Update(&u1, false)).Err; err == nil {
t.Fatal("Update should have faile because id change")
}
}
func TestUserStoreUpdateLastPingAt(t *testing.T) {
Setup()
u1 := model.User{}
u1.TeamId = model.NewId()
u1.Email = model.NewId()
<-store.User().Save(&u1)
if err := (<-store.User().UpdateLastPingAt(u1.Id, 1234567890)).Err; err != nil {
t.Fatal(err)
}
if r1 := <-store.User().Get(u1.Id); r1.Err != nil {
t.Fatal(r1.Err)
} else {
if r1.Data.(*model.User).LastPingAt != 1234567890 {
t.Fatal("LastPingAt not updated correctly")
}
}
}
func TestUserStoreUpdateLastActivityAt(t *testing.T) {
Setup()
u1 := model.User{}
u1.TeamId = model.NewId()
u1.Email = model.NewId()
<-store.User().Save(&u1)
if err := (<-store.User().UpdateLastActivityAt(u1.Id, 1234567890)).Err; err != nil {
t.Fatal(err)
}
if r1 := <-store.User().Get(u1.Id); r1.Err != nil {
t.Fatal(r1.Err)
} else {
if r1.Data.(*model.User).LastActivityAt != 1234567890 {
t.Fatal("LastActivityAt not updated correctly")
}
}
}
func TestUserStoreUpdateUserAndSessionActivity(t *testing.T) {
Setup()
u1 := model.User{}
u1.TeamId = model.NewId()
u1.Email = model.NewId()
<-store.User().Save(&u1)
s1 := model.Session{}
s1.UserId = u1.Id
s1.TeamId = u1.TeamId
<-store.Session().Save(&s1)
if err := (<-store.User().UpdateUserAndSessionActivity(u1.Id, s1.Id, 1234567890)).Err; err != nil {
t.Fatal(err)
}
if r1 := <-store.User().Get(u1.Id); r1.Err != nil {
t.Fatal(r1.Err)
} else {
if r1.Data.(*model.User).LastActivityAt != 1234567890 {
t.Fatal("LastActivityAt not updated correctly for user")
}
}
if r2 := <-store.Session().Get(s1.Id); r2.Err != nil {
t.Fatal(r2.Err)
} else {
if r2.Data.(*model.Session).LastActivityAt != 1234567890 {
t.Fatal("LastActivityAt not updated correctly for session")
}
}
}
func TestUserStoreGet(t *testing.T) {
Setup()
u1 := model.User{}
u1.TeamId = model.NewId()
u1.Email = model.NewId()
<-store.User().Save(&u1)
if r1 := <-store.User().Get(u1.Id); r1.Err != nil {
t.Fatal(r1.Err)
} else {
if r1.Data.(*model.User).ToJson() != u1.ToJson() {
t.Fatal("invalid returned user")
}
}
if err := (<-store.User().Get("")).Err; err == nil {
t.Fatal("Missing id should have failed")
}
}
func TestUserStoreGetProfiles(t *testing.T) {
Setup()
u1 := model.User{}
u1.TeamId = model.NewId()
u1.Email = model.NewId()
<-store.User().Save(&u1)
u2 := model.User{}
u2.TeamId = u1.TeamId
u2.Email = model.NewId()
<-store.User().Save(&u2)
if r1 := <-store.User().GetProfiles(u1.TeamId); r1.Err != nil {
t.Fatal(r1.Err)
} else {
users := r1.Data.(map[string]*model.User)
if len(users) != 2 {
t.Fatal("invalid returned users")
}
if users[u1.Id].Id != u1.Id {
t.Fatal("invalid returned user")
}
}
if r2 := <-store.User().GetProfiles("123"); r2.Err != nil {
t.Fatal(r2.Err)
} else {
if len(r2.Data.(map[string]*model.User)) != 0 {
t.Fatal("should have returned empty map")
}
}
}
func TestUserStoreGetByEmail(t *testing.T) {
Setup()
u1 := model.User{}
u1.TeamId = model.NewId()
u1.Email = model.NewId()
<-store.User().Save(&u1)
if err := (<-store.User().GetByEmail(u1.TeamId, u1.Email)).Err; err != nil {
t.Fatal(err)
}
if err := (<-store.User().GetByEmail("", "")).Err; err == nil {
t.Fatal("Should have failed because of missing email")
}
}
func TestUserStoreGetByUsername(t *testing.T) {
Setup()
u1 := model.User{}
u1.TeamId = model.NewId()
u1.Email = model.NewId()
u1.Username = model.NewId()
<-store.User().Save(&u1)
if err := (<-store.User().GetByUsername(u1.TeamId, u1.Username)).Err; err != nil {
t.Fatal(err)
}
if err := (<-store.User().GetByUsername("", "")).Err; err == nil {
t.Fatal("Should have failed because of missing username")
}
}
func TestUserStoreUpdatePassword(t *testing.T) {
Setup()
u1 := model.User{}
u1.TeamId = model.NewId()
u1.Email = model.NewId()
<-store.User().Save(&u1)
hashedPassword := model.HashPassword("newpwd")
if err := (<-store.User().UpdatePassword(u1.Id, hashedPassword)).Err; err != nil {
t.Fatal(err)
}
if r1 := <-store.User().GetByEmail(u1.TeamId, u1.Email); r1.Err != nil {
t.Fatal(r1.Err)
} else {
user := r1.Data.(*model.User)
if user.Password != hashedPassword {
t.Fatal("Password was not updated correctly")
}
}
}

94
store/store.go Обычный файл
Просмотреть файл

@@ -0,0 +1,94 @@
// Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved.
// See License.txt for license information.
package store
import (
"github.com/mattermost/platform/model"
)
type StoreResult struct {
Data interface{}
Err *model.AppError
}
type StoreChannel chan StoreResult
type Store interface {
Team() TeamStore
Channel() ChannelStore
Post() PostStore
User() UserStore
Audit() AuditStore
Session() SessionStore
Close()
}
type TeamStore interface {
Save(team *model.Team) StoreChannel
Update(team *model.Team) StoreChannel
UpdateName(name string, teamId string) StoreChannel
Get(id string) StoreChannel
GetByDomain(domain string) StoreChannel
GetTeamsForEmail(domain string) StoreChannel
}
type ChannelStore interface {
Save(channel *model.Channel) StoreChannel
Update(channel *model.Channel) StoreChannel
Get(id string) StoreChannel
Delete(channelId string, time int64) StoreChannel
GetByName(team_id string, domain string) StoreChannel
GetChannels(teamId string, userId string) StoreChannel
GetMoreChannels(teamId string, userId string) StoreChannel
SaveMember(member *model.ChannelMember) StoreChannel
GetMembers(channelId string) StoreChannel
GetMember(channelId string, userId string) StoreChannel
RemoveMember(channelId string, userId string) StoreChannel
GetExtraMembers(channelId string, limit int) StoreChannel
CheckPermissionsTo(teamId string, channelId string, userId string) StoreChannel
CheckOpenChannelPermissions(teamId string, channelId string) StoreChannel
CheckPermissionsToByName(teamId string, channelName string, userId string) StoreChannel
UpdateLastViewedAt(channelId string, userId string) StoreChannel
IncrementMentionCount(channelId string, userId string) StoreChannel
UpdateNotifyLevel(channelId string, userId string, notifyLevel string) StoreChannel
}
type PostStore interface {
Save(post *model.Post) StoreChannel
Update(post *model.Post, newMessage string, newHashtags string) StoreChannel
Get(id string) StoreChannel
Delete(postId string, time int64) StoreChannel
GetPosts(channelId string, offset int, limit int) StoreChannel
GetEtag(channelId string) StoreChannel
Search(teamId string, userId string, terms string, isHashtagSearch bool) StoreChannel
}
type UserStore interface {
Save(user *model.User) StoreChannel
Update(user *model.User, allowRoleUpdate bool) StoreChannel
UpdateLastPingAt(userId string, time int64) StoreChannel
UpdateLastActivityAt(userId string, time int64) StoreChannel
UpdateUserAndSessionActivity(userId string, sessionId string, time int64) StoreChannel
UpdatePassword(userId, newPassword string) StoreChannel
Get(id string) StoreChannel
GetProfiles(teamId string) StoreChannel
GetByEmail(teamId string, email string) StoreChannel
GetByUsername(teamId string, username string) StoreChannel
VerifyEmail(userId string) StoreChannel
GetEtagForProfiles(teamId string) StoreChannel
}
type SessionStore interface {
Save(session *model.Session) StoreChannel
Get(id string) StoreChannel
GetSessions(userId string) StoreChannel
Remove(sessionIdOrAlt string) StoreChannel
UpdateLastActivityAt(sessionId string, time int64) StoreChannel
}
type AuditStore interface {
Save(audit *model.Audit) StoreChannel
Get(user_id string, limit int) StoreChannel
}