Move sql store code into store/sqlstore package (#7502)
* move sql store code into store/sqlstore package * move non-sql constants back up to store * fix api test * derp
Этот коммит содержится в:
коммит произвёл
Joram Wilander
родитель
b2c5b97601
Коммит
49fe5fbf3d
146
store/sqlstore/audit_store.go
Обычный файл
146
store/sqlstore/audit_store.go
Обычный файл
@@ -0,0 +1,146 @@
|
||||
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
|
||||
// See License.txt for license information.
|
||||
|
||||
package sqlstore
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/mattermost/mattermost-server/model"
|
||||
"github.com/mattermost/mattermost-server/store"
|
||||
"github.com/mattermost/mattermost-server/utils"
|
||||
)
|
||||
|
||||
type SqlAuditStore struct {
|
||||
SqlStore
|
||||
}
|
||||
|
||||
func NewSqlAuditStore(sqlStore SqlStore) store.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(512)
|
||||
table.ColMap("ExtraInfo").SetMaxSize(1024)
|
||||
table.ColMap("IpAddress").SetMaxSize(64)
|
||||
table.ColMap("SessionId").SetMaxSize(26)
|
||||
}
|
||||
|
||||
return s
|
||||
}
|
||||
|
||||
func (s SqlAuditStore) CreateIndexesIfNotExists() {
|
||||
s.CreateIndexIfNotExists("idx_audits_user_id", "Audits", "UserId")
|
||||
}
|
||||
|
||||
func (s SqlAuditStore) Save(audit *model.Audit) store.StoreChannel {
|
||||
|
||||
storeChannel := make(store.StoreChannel, 1)
|
||||
|
||||
go func() {
|
||||
result := store.StoreResult{}
|
||||
|
||||
audit.Id = model.NewId()
|
||||
audit.CreateAt = model.GetMillis()
|
||||
|
||||
if err := s.GetMaster().Insert(audit); err != nil {
|
||||
result.Err = model.NewAppError("SqlAuditStore.Save", "store.sql_audit.save.saving.app_error", nil, "user_id="+audit.UserId+" action="+audit.Action, http.StatusInternalServerError)
|
||||
}
|
||||
|
||||
storeChannel <- result
|
||||
close(storeChannel)
|
||||
}()
|
||||
|
||||
return storeChannel
|
||||
}
|
||||
|
||||
func (s SqlAuditStore) Get(user_id string, offset int, limit int) store.StoreChannel {
|
||||
|
||||
storeChannel := make(store.StoreChannel, 1)
|
||||
|
||||
go func() {
|
||||
result := store.StoreResult{}
|
||||
|
||||
if limit > 1000 {
|
||||
limit = 1000
|
||||
result.Err = model.NewAppError("SqlAuditStore.Get", "store.sql_audit.get.limit.app_error", nil, "user_id="+user_id, http.StatusBadRequest)
|
||||
storeChannel <- result
|
||||
close(storeChannel)
|
||||
return
|
||||
}
|
||||
|
||||
query := "SELECT * FROM Audits"
|
||||
|
||||
if len(user_id) != 0 {
|
||||
query += " WHERE UserId = :user_id"
|
||||
}
|
||||
|
||||
query += " ORDER BY CreateAt DESC LIMIT :limit OFFSET :offset"
|
||||
|
||||
var audits model.Audits
|
||||
if _, err := s.GetReplica().Select(&audits, query, map[string]interface{}{"user_id": user_id, "limit": limit, "offset": offset}); err != nil {
|
||||
result.Err = model.NewAppError("SqlAuditStore.Get", "store.sql_audit.get.finding.app_error", nil, "user_id="+user_id, http.StatusInternalServerError)
|
||||
} else {
|
||||
result.Data = audits
|
||||
}
|
||||
|
||||
storeChannel <- result
|
||||
close(storeChannel)
|
||||
}()
|
||||
|
||||
return storeChannel
|
||||
}
|
||||
|
||||
func (s SqlAuditStore) PermanentDeleteByUser(userId string) store.StoreChannel {
|
||||
|
||||
storeChannel := make(store.StoreChannel, 1)
|
||||
|
||||
go func() {
|
||||
result := store.StoreResult{}
|
||||
|
||||
if _, err := s.GetMaster().Exec("DELETE FROM Audits WHERE UserId = :userId",
|
||||
map[string]interface{}{"userId": userId}); err != nil {
|
||||
result.Err = model.NewAppError("SqlAuditStore.Delete", "store.sql_audit.permanent_delete_by_user.app_error", nil, "user_id="+userId, http.StatusInternalServerError)
|
||||
}
|
||||
|
||||
storeChannel <- result
|
||||
close(storeChannel)
|
||||
}()
|
||||
|
||||
return storeChannel
|
||||
}
|
||||
|
||||
func (s SqlAuditStore) PermanentDeleteBatch(endTime int64, limit int64) store.StoreChannel {
|
||||
storeChannel := make(store.StoreChannel, 1)
|
||||
|
||||
go func() {
|
||||
result := store.StoreResult{}
|
||||
|
||||
var query string
|
||||
if *utils.Cfg.SqlSettings.DriverName == "postgres" {
|
||||
query = "DELETE from Audits WHERE Id = any (array (SELECT Id FROM Audits WHERE CreateAt < :EndTime LIMIT :Limit))"
|
||||
} else {
|
||||
query = "DELETE from Audits WHERE CreateAt < :EndTime LIMIT :Limit"
|
||||
}
|
||||
|
||||
sqlResult, err := s.GetMaster().Exec(query, map[string]interface{}{"EndTime": endTime, "Limit": limit})
|
||||
if err != nil {
|
||||
result.Err = model.NewAppError("SqlAuditStore.PermanentDeleteBatch", "store.sql_audit.permanent_delete_batch.app_error", nil, ""+err.Error(), http.StatusInternalServerError)
|
||||
} else {
|
||||
rowsAffected, err1 := sqlResult.RowsAffected()
|
||||
if err1 != nil {
|
||||
result.Err = model.NewAppError("SqlAuditStore.PermanentDeleteBatch", "store.sql_audit.permanent_delete_batch.app_error", nil, ""+err.Error(), http.StatusInternalServerError)
|
||||
result.Data = int64(0)
|
||||
} else {
|
||||
result.Data = rowsAffected
|
||||
}
|
||||
}
|
||||
|
||||
storeChannel <- result
|
||||
close(storeChannel)
|
||||
}()
|
||||
|
||||
return storeChannel
|
||||
}
|
||||
90
store/sqlstore/audit_store_test.go
Обычный файл
90
store/sqlstore/audit_store_test.go
Обычный файл
@@ -0,0 +1,90 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See License.txt for license information.
|
||||
|
||||
package sqlstore
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/mattermost/mattermost-server/model"
|
||||
"github.com/mattermost/mattermost-server/store"
|
||||
)
|
||||
|
||||
func TestSqlAuditStore(t *testing.T) {
|
||||
ss := Setup()
|
||||
|
||||
audit := &model.Audit{UserId: model.NewId(), IpAddress: "ipaddress", Action: "Action"}
|
||||
store.Must(ss.Audit().Save(audit))
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
store.Must(ss.Audit().Save(audit))
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
store.Must(ss.Audit().Save(audit))
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
audit.ExtraInfo = "extra"
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
store.Must(ss.Audit().Save(audit))
|
||||
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
|
||||
c := ss.Audit().Get(audit.UserId, 0, 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 = ss.Audit().Get("missing", 0, 100)
|
||||
result = <-c
|
||||
audits = result.Data.(model.Audits)
|
||||
|
||||
if len(audits) != 0 {
|
||||
t.Fatal("Should have returned empty because user_id is missing")
|
||||
}
|
||||
|
||||
c = ss.Audit().Get("", 0, 100)
|
||||
result = <-c
|
||||
audits = result.Data.(model.Audits)
|
||||
|
||||
if len(audits) < 4 {
|
||||
t.Fatal("Failed to save and retrieve 4 audit logs")
|
||||
}
|
||||
|
||||
if r2 := <-ss.Audit().PermanentDeleteByUser(audit.UserId); r2.Err != nil {
|
||||
t.Fatal(r2.Err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAuditStorePermanentDeleteBatch(t *testing.T) {
|
||||
ss := Setup()
|
||||
|
||||
a1 := &model.Audit{UserId: model.NewId(), IpAddress: "ipaddress", Action: "Action"}
|
||||
store.Must(ss.Audit().Save(a1))
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
a2 := &model.Audit{UserId: a1.UserId, IpAddress: "ipaddress", Action: "Action"}
|
||||
store.Must(ss.Audit().Save(a2))
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
cutoff := model.GetMillis()
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
a3 := &model.Audit{UserId: a1.UserId, IpAddress: "ipaddress", Action: "Action"}
|
||||
store.Must(ss.Audit().Save(a3))
|
||||
|
||||
if r := <-ss.Audit().Get(a1.UserId, 0, 100); len(r.Data.(model.Audits)) != 3 {
|
||||
t.Fatal("Expected 3 audits. Got ", len(r.Data.(model.Audits)))
|
||||
}
|
||||
|
||||
store.Must(ss.Audit().PermanentDeleteBatch(cutoff, 1000000))
|
||||
|
||||
if r := <-ss.Audit().Get(a1.UserId, 0, 100); len(r.Data.(model.Audits)) != 1 {
|
||||
t.Fatal("Expected 1 audit. Got ", len(r.Data.(model.Audits)))
|
||||
}
|
||||
|
||||
if r2 := <-ss.Audit().PermanentDeleteByUser(a1.UserId); r2.Err != nil {
|
||||
t.Fatal(r2.Err)
|
||||
}
|
||||
}
|
||||
1664
store/sqlstore/channel_store.go
Обычный файл
1664
store/sqlstore/channel_store.go
Обычный файл
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
1962
store/sqlstore/channel_store_test.go
Обычный файл
1962
store/sqlstore/channel_store_test.go
Обычный файл
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
229
store/sqlstore/cluster_discovery_store.go
Обычный файл
229
store/sqlstore/cluster_discovery_store.go
Обычный файл
@@ -0,0 +1,229 @@
|
||||
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
|
||||
// See License.txt for license information.
|
||||
|
||||
package sqlstore
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/mattermost/mattermost-server/model"
|
||||
"github.com/mattermost/mattermost-server/store"
|
||||
)
|
||||
|
||||
type sqlClusterDiscoveryStore struct {
|
||||
SqlStore
|
||||
}
|
||||
|
||||
func NewSqlClusterDiscoveryStore(sqlStore SqlStore) store.ClusterDiscoveryStore {
|
||||
s := &sqlClusterDiscoveryStore{sqlStore}
|
||||
|
||||
for _, db := range sqlStore.GetAllConns() {
|
||||
table := db.AddTableWithName(model.ClusterDiscovery{}, "ClusterDiscovery").SetKeys(false, "Id")
|
||||
table.ColMap("Id").SetMaxSize(26)
|
||||
table.ColMap("Type").SetMaxSize(64)
|
||||
table.ColMap("ClusterName").SetMaxSize(64)
|
||||
table.ColMap("Hostname").SetMaxSize(512)
|
||||
}
|
||||
|
||||
return s
|
||||
}
|
||||
|
||||
func (s sqlClusterDiscoveryStore) Save(ClusterDiscovery *model.ClusterDiscovery) store.StoreChannel {
|
||||
|
||||
storeChannel := make(store.StoreChannel, 1)
|
||||
|
||||
go func() {
|
||||
result := store.StoreResult{}
|
||||
|
||||
ClusterDiscovery.PreSave()
|
||||
if result.Err = ClusterDiscovery.IsValid(); result.Err != nil {
|
||||
storeChannel <- result
|
||||
close(storeChannel)
|
||||
return
|
||||
}
|
||||
|
||||
if err := s.GetMaster().Insert(ClusterDiscovery); err != nil {
|
||||
result.Err = model.NewAppError("SqlClusterDiscoveryStore.Save", "Failed to save ClusterDiscovery row", nil, err.Error(), http.StatusInternalServerError)
|
||||
}
|
||||
|
||||
storeChannel <- result
|
||||
close(storeChannel)
|
||||
}()
|
||||
|
||||
return storeChannel
|
||||
}
|
||||
|
||||
func (s sqlClusterDiscoveryStore) Delete(ClusterDiscovery *model.ClusterDiscovery) store.StoreChannel {
|
||||
storeChannel := make(store.StoreChannel, 1)
|
||||
|
||||
go func() {
|
||||
result := store.StoreResult{}
|
||||
result.Data = false
|
||||
|
||||
if count, err := s.GetMaster().SelectInt(
|
||||
`
|
||||
DELETE
|
||||
FROM
|
||||
ClusterDiscovery
|
||||
WHERE
|
||||
Type = :Type
|
||||
AND ClusterName = :ClusterName
|
||||
AND Hostname = :Hostname
|
||||
`,
|
||||
map[string]interface{}{
|
||||
"Type": ClusterDiscovery.Type,
|
||||
"ClusterName": ClusterDiscovery.ClusterName,
|
||||
"Hostname": ClusterDiscovery.Hostname,
|
||||
},
|
||||
); err != nil {
|
||||
result.Err = model.NewAppError("SqlClusterDiscoveryStore.Delete", "Failed to delete", nil, err.Error(), http.StatusInternalServerError)
|
||||
} else {
|
||||
if count > 0 {
|
||||
result.Data = true
|
||||
}
|
||||
}
|
||||
|
||||
storeChannel <- result
|
||||
close(storeChannel)
|
||||
}()
|
||||
|
||||
return storeChannel
|
||||
}
|
||||
|
||||
func (s sqlClusterDiscoveryStore) Exists(ClusterDiscovery *model.ClusterDiscovery) store.StoreChannel {
|
||||
storeChannel := make(store.StoreChannel, 1)
|
||||
|
||||
go func() {
|
||||
result := store.StoreResult{}
|
||||
result.Data = false
|
||||
|
||||
if count, err := s.GetMaster().SelectInt(
|
||||
`
|
||||
SELECT
|
||||
COUNT(*)
|
||||
FROM
|
||||
ClusterDiscovery
|
||||
WHERE
|
||||
Type = :Type
|
||||
AND ClusterName = :ClusterName
|
||||
AND Hostname = :Hostname
|
||||
`,
|
||||
map[string]interface{}{
|
||||
"Type": ClusterDiscovery.Type,
|
||||
"ClusterName": ClusterDiscovery.ClusterName,
|
||||
"Hostname": ClusterDiscovery.Hostname,
|
||||
},
|
||||
); err != nil {
|
||||
result.Err = model.NewAppError("SqlClusterDiscoveryStore.Exists", "Failed to check if it exists", nil, err.Error(), http.StatusInternalServerError)
|
||||
} else {
|
||||
if count > 0 {
|
||||
result.Data = true
|
||||
}
|
||||
}
|
||||
|
||||
storeChannel <- result
|
||||
close(storeChannel)
|
||||
}()
|
||||
|
||||
return storeChannel
|
||||
}
|
||||
|
||||
func (s sqlClusterDiscoveryStore) GetAll(ClusterDiscoveryType, clusterName string) store.StoreChannel {
|
||||
|
||||
storeChannel := make(store.StoreChannel, 1)
|
||||
|
||||
go func() {
|
||||
result := store.StoreResult{}
|
||||
|
||||
lastPingAt := model.GetMillis() - model.CDS_OFFLINE_AFTER_MILLIS
|
||||
|
||||
var list []*model.ClusterDiscovery
|
||||
if _, err := s.GetMaster().Select(
|
||||
&list,
|
||||
`
|
||||
SELECT
|
||||
*
|
||||
FROM
|
||||
ClusterDiscovery
|
||||
WHERE
|
||||
Type = :ClusterDiscoveryType
|
||||
AND ClusterName = :ClusterName
|
||||
AND LastPingAt > :LastPingAt
|
||||
`,
|
||||
map[string]interface{}{
|
||||
"ClusterDiscoveryType": ClusterDiscoveryType,
|
||||
"ClusterName": clusterName,
|
||||
"LastPingAt": lastPingAt,
|
||||
},
|
||||
); err != nil {
|
||||
result.Err = model.NewAppError("SqlClusterDiscoveryStore.GetAllForType", "Failed to get all disoery rows", nil, err.Error(), http.StatusInternalServerError)
|
||||
} else {
|
||||
result.Data = list
|
||||
}
|
||||
|
||||
storeChannel <- result
|
||||
close(storeChannel)
|
||||
}()
|
||||
|
||||
return storeChannel
|
||||
}
|
||||
|
||||
func (s sqlClusterDiscoveryStore) SetLastPingAt(ClusterDiscovery *model.ClusterDiscovery) store.StoreChannel {
|
||||
storeChannel := make(store.StoreChannel, 1)
|
||||
|
||||
go func() {
|
||||
result := store.StoreResult{}
|
||||
|
||||
if _, err := s.GetMaster().Exec(
|
||||
`
|
||||
UPDATE ClusterDiscovery
|
||||
SET
|
||||
LastPingAt = :LastPingAt
|
||||
WHERE
|
||||
Type = :Type
|
||||
AND ClusterName = :ClusterName
|
||||
AND Hostname = :Hostname
|
||||
`,
|
||||
map[string]interface{}{
|
||||
"LastPingAt": model.GetMillis(),
|
||||
"Type": ClusterDiscovery.Type,
|
||||
"ClusterName": ClusterDiscovery.ClusterName,
|
||||
"Hostname": ClusterDiscovery.Hostname,
|
||||
},
|
||||
); err != nil {
|
||||
result.Err = model.NewAppError("SqlClusterDiscoveryStore.GetAllForType", "Failed to update last ping at", nil, err.Error(), http.StatusInternalServerError)
|
||||
}
|
||||
|
||||
storeChannel <- result
|
||||
close(storeChannel)
|
||||
}()
|
||||
|
||||
return storeChannel
|
||||
}
|
||||
|
||||
func (s sqlClusterDiscoveryStore) Cleanup() store.StoreChannel {
|
||||
|
||||
storeChannel := make(store.StoreChannel, 1)
|
||||
|
||||
go func() {
|
||||
result := store.StoreResult{}
|
||||
|
||||
if _, err := s.GetMaster().Exec(
|
||||
`
|
||||
DELETE FROM ClusterDiscovery
|
||||
WHERE
|
||||
LastPingAt < :LastPingAt
|
||||
`,
|
||||
map[string]interface{}{
|
||||
"LastPingAt": model.GetMillis() - model.CDS_OFFLINE_AFTER_MILLIS,
|
||||
},
|
||||
); err != nil {
|
||||
result.Err = model.NewAppError("SqlClusterDiscoveryStore.Save", "Failed to save ClusterDiscovery row", nil, err.Error(), http.StatusInternalServerError)
|
||||
}
|
||||
|
||||
storeChannel <- result
|
||||
close(storeChannel)
|
||||
}()
|
||||
|
||||
return storeChannel
|
||||
}
|
||||
202
store/sqlstore/cluster_discovery_store_test.go
Обычный файл
202
store/sqlstore/cluster_discovery_store_test.go
Обычный файл
@@ -0,0 +1,202 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See License.txt for license information.
|
||||
|
||||
package sqlstore
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"time"
|
||||
|
||||
"github.com/mattermost/mattermost-server/model"
|
||||
"github.com/mattermost/mattermost-server/store"
|
||||
)
|
||||
|
||||
func TestSqlClusterDiscoveryStore(t *testing.T) {
|
||||
ss := Setup()
|
||||
|
||||
discovery := &model.ClusterDiscovery{
|
||||
ClusterName: "cluster_name",
|
||||
Hostname: "hostname" + model.NewId(),
|
||||
Type: "test_test",
|
||||
}
|
||||
|
||||
if result := <-ss.ClusterDiscovery().Save(discovery); result.Err != nil {
|
||||
t.Fatal(result.Err)
|
||||
}
|
||||
|
||||
if result := <-ss.ClusterDiscovery().Cleanup(); result.Err != nil {
|
||||
t.Fatal(result.Err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSqlClusterDiscoveryStoreDelete(t *testing.T) {
|
||||
ss := Setup()
|
||||
|
||||
discovery := &model.ClusterDiscovery{
|
||||
ClusterName: "cluster_name",
|
||||
Hostname: "hostname" + model.NewId(),
|
||||
Type: "test_test",
|
||||
}
|
||||
|
||||
if result := <-ss.ClusterDiscovery().Save(discovery); result.Err != nil {
|
||||
t.Fatal(result.Err)
|
||||
}
|
||||
|
||||
if result := <-ss.ClusterDiscovery().Delete(discovery); result.Err != nil {
|
||||
t.Fatal(result.Err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSqlClusterDiscoveryStoreLastPing(t *testing.T) {
|
||||
ss := Setup()
|
||||
|
||||
discovery := &model.ClusterDiscovery{
|
||||
ClusterName: "cluster_name_lastPing",
|
||||
Hostname: "hostname" + model.NewId(),
|
||||
Type: "test_test_lastPing" + model.NewId(),
|
||||
}
|
||||
|
||||
if result := <-ss.ClusterDiscovery().Save(discovery); result.Err != nil {
|
||||
t.Fatal(result.Err)
|
||||
}
|
||||
|
||||
if result := <-ss.ClusterDiscovery().SetLastPingAt(discovery); result.Err != nil {
|
||||
t.Fatal(result.Err)
|
||||
}
|
||||
|
||||
ttime := model.GetMillis()
|
||||
|
||||
time.Sleep(1 * time.Second)
|
||||
|
||||
if result := <-ss.ClusterDiscovery().SetLastPingAt(discovery); result.Err != nil {
|
||||
t.Fatal(result.Err)
|
||||
}
|
||||
|
||||
if result := <-ss.ClusterDiscovery().GetAll(discovery.Type, "cluster_name_lastPing"); result.Err != nil {
|
||||
t.Fatal(result.Err)
|
||||
} else {
|
||||
list := result.Data.([]*model.ClusterDiscovery)
|
||||
|
||||
if len(list) != 1 {
|
||||
t.Fatal("should only be 1 items")
|
||||
return
|
||||
}
|
||||
|
||||
if list[0].LastPingAt-ttime < 500 {
|
||||
t.Fatal("failed to set time")
|
||||
}
|
||||
}
|
||||
|
||||
discovery2 := &model.ClusterDiscovery{
|
||||
ClusterName: "cluster_name_missing",
|
||||
Hostname: "hostname" + model.NewId(),
|
||||
Type: "test_test_missing",
|
||||
}
|
||||
|
||||
if result := <-ss.ClusterDiscovery().SetLastPingAt(discovery2); result.Err != nil {
|
||||
t.Fatal(result.Err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSqlClusterDiscoveryStoreExists(t *testing.T) {
|
||||
ss := Setup()
|
||||
|
||||
discovery := &model.ClusterDiscovery{
|
||||
ClusterName: "cluster_name_Exists",
|
||||
Hostname: "hostname" + model.NewId(),
|
||||
Type: "test_test_Exists" + model.NewId(),
|
||||
}
|
||||
|
||||
if result := <-ss.ClusterDiscovery().Save(discovery); result.Err != nil {
|
||||
t.Fatal(result.Err)
|
||||
}
|
||||
|
||||
if result := <-ss.ClusterDiscovery().Exists(discovery); result.Err != nil {
|
||||
t.Fatal(result.Err)
|
||||
} else {
|
||||
val := result.Data.(bool)
|
||||
if !val {
|
||||
t.Fatal("should be true")
|
||||
}
|
||||
}
|
||||
|
||||
discovery.ClusterName = "cluster_name_Exists2"
|
||||
|
||||
if result := <-ss.ClusterDiscovery().Exists(discovery); result.Err != nil {
|
||||
t.Fatal(result.Err)
|
||||
} else {
|
||||
val := result.Data.(bool)
|
||||
if val {
|
||||
t.Fatal("should be true")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestSqlClusterDiscoveryGetStore(t *testing.T) {
|
||||
ss := Setup()
|
||||
|
||||
testType1 := model.NewId()
|
||||
|
||||
discovery1 := &model.ClusterDiscovery{
|
||||
ClusterName: "cluster_name",
|
||||
Hostname: "hostname1",
|
||||
Type: testType1,
|
||||
}
|
||||
store.Must(ss.ClusterDiscovery().Save(discovery1))
|
||||
|
||||
discovery2 := &model.ClusterDiscovery{
|
||||
ClusterName: "cluster_name",
|
||||
Hostname: "hostname2",
|
||||
Type: testType1,
|
||||
}
|
||||
store.Must(ss.ClusterDiscovery().Save(discovery2))
|
||||
|
||||
discovery3 := &model.ClusterDiscovery{
|
||||
ClusterName: "cluster_name",
|
||||
Hostname: "hostname3",
|
||||
Type: testType1,
|
||||
CreateAt: 1,
|
||||
LastPingAt: 1,
|
||||
}
|
||||
store.Must(ss.ClusterDiscovery().Save(discovery3))
|
||||
|
||||
testType2 := model.NewId()
|
||||
|
||||
discovery4 := &model.ClusterDiscovery{
|
||||
ClusterName: "cluster_name",
|
||||
Hostname: "hostname1",
|
||||
Type: testType2,
|
||||
}
|
||||
store.Must(ss.ClusterDiscovery().Save(discovery4))
|
||||
|
||||
if result := <-ss.ClusterDiscovery().GetAll(testType1, "cluster_name"); result.Err != nil {
|
||||
t.Fatal(result.Err)
|
||||
} else {
|
||||
list := result.Data.([]*model.ClusterDiscovery)
|
||||
|
||||
if len(list) != 2 {
|
||||
t.Fatal("Should only have returned 2")
|
||||
}
|
||||
}
|
||||
|
||||
if result := <-ss.ClusterDiscovery().GetAll(testType2, "cluster_name"); result.Err != nil {
|
||||
t.Fatal(result.Err)
|
||||
} else {
|
||||
list := result.Data.([]*model.ClusterDiscovery)
|
||||
|
||||
if len(list) != 1 {
|
||||
t.Fatal("Should only have returned 1")
|
||||
}
|
||||
}
|
||||
|
||||
if result := <-ss.ClusterDiscovery().GetAll(model.NewId(), "cluster_name"); result.Err != nil {
|
||||
t.Fatal(result.Err)
|
||||
} else {
|
||||
list := result.Data.([]*model.ClusterDiscovery)
|
||||
|
||||
if len(list) != 0 {
|
||||
t.Fatal("shouldn't be any")
|
||||
}
|
||||
}
|
||||
}
|
||||
226
store/sqlstore/command_store.go
Обычный файл
226
store/sqlstore/command_store.go
Обычный файл
@@ -0,0 +1,226 @@
|
||||
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
|
||||
// See License.txt for license information.
|
||||
|
||||
package sqlstore
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/mattermost/mattermost-server/model"
|
||||
"github.com/mattermost/mattermost-server/store"
|
||||
)
|
||||
|
||||
type SqlCommandStore struct {
|
||||
SqlStore
|
||||
}
|
||||
|
||||
func NewSqlCommandStore(sqlStore SqlStore) store.CommandStore {
|
||||
s := &SqlCommandStore{sqlStore}
|
||||
|
||||
for _, db := range sqlStore.GetAllConns() {
|
||||
tableo := db.AddTableWithName(model.Command{}, "Commands").SetKeys(false, "Id")
|
||||
tableo.ColMap("Id").SetMaxSize(26)
|
||||
tableo.ColMap("Token").SetMaxSize(26)
|
||||
tableo.ColMap("CreatorId").SetMaxSize(26)
|
||||
tableo.ColMap("TeamId").SetMaxSize(26)
|
||||
tableo.ColMap("Trigger").SetMaxSize(128)
|
||||
tableo.ColMap("URL").SetMaxSize(1024)
|
||||
tableo.ColMap("Method").SetMaxSize(1)
|
||||
tableo.ColMap("Username").SetMaxSize(64)
|
||||
tableo.ColMap("IconURL").SetMaxSize(1024)
|
||||
tableo.ColMap("AutoCompleteDesc").SetMaxSize(1024)
|
||||
tableo.ColMap("AutoCompleteHint").SetMaxSize(1024)
|
||||
tableo.ColMap("DisplayName").SetMaxSize(64)
|
||||
tableo.ColMap("Description").SetMaxSize(128)
|
||||
}
|
||||
|
||||
return s
|
||||
}
|
||||
|
||||
func (s SqlCommandStore) CreateIndexesIfNotExists() {
|
||||
s.CreateIndexIfNotExists("idx_command_team_id", "Commands", "TeamId")
|
||||
s.CreateIndexIfNotExists("idx_command_update_at", "Commands", "UpdateAt")
|
||||
s.CreateIndexIfNotExists("idx_command_create_at", "Commands", "CreateAt")
|
||||
s.CreateIndexIfNotExists("idx_command_delete_at", "Commands", "DeleteAt")
|
||||
}
|
||||
|
||||
func (s SqlCommandStore) Save(command *model.Command) store.StoreChannel {
|
||||
storeChannel := make(store.StoreChannel, 1)
|
||||
|
||||
go func() {
|
||||
result := store.StoreResult{}
|
||||
|
||||
if len(command.Id) > 0 {
|
||||
result.Err = model.NewAppError("SqlCommandStore.Save", "store.sql_command.save.saving_overwrite.app_error", nil, "id="+command.Id, http.StatusBadRequest)
|
||||
storeChannel <- result
|
||||
close(storeChannel)
|
||||
return
|
||||
}
|
||||
|
||||
command.PreSave()
|
||||
if result.Err = command.IsValid(); result.Err != nil {
|
||||
storeChannel <- result
|
||||
close(storeChannel)
|
||||
return
|
||||
}
|
||||
|
||||
if err := s.GetMaster().Insert(command); err != nil {
|
||||
result.Err = model.NewAppError("SqlCommandStore.Save", "store.sql_command.save.saving.app_error", nil, "id="+command.Id+", "+err.Error(), http.StatusInternalServerError)
|
||||
} else {
|
||||
result.Data = command
|
||||
}
|
||||
|
||||
storeChannel <- result
|
||||
close(storeChannel)
|
||||
}()
|
||||
|
||||
return storeChannel
|
||||
}
|
||||
|
||||
func (s SqlCommandStore) Get(id string) store.StoreChannel {
|
||||
storeChannel := make(store.StoreChannel, 1)
|
||||
|
||||
go func() {
|
||||
result := store.StoreResult{}
|
||||
|
||||
var command model.Command
|
||||
|
||||
if err := s.GetReplica().SelectOne(&command, "SELECT * FROM Commands WHERE Id = :Id AND DeleteAt = 0", map[string]interface{}{"Id": id}); err != nil {
|
||||
result.Err = model.NewAppError("SqlCommandStore.Get", "store.sql_command.save.get.app_error", nil, "id="+id+", err="+err.Error(), http.StatusInternalServerError)
|
||||
}
|
||||
|
||||
result.Data = &command
|
||||
|
||||
storeChannel <- result
|
||||
close(storeChannel)
|
||||
}()
|
||||
|
||||
return storeChannel
|
||||
}
|
||||
|
||||
func (s SqlCommandStore) GetByTeam(teamId string) store.StoreChannel {
|
||||
storeChannel := make(store.StoreChannel, 1)
|
||||
|
||||
go func() {
|
||||
result := store.StoreResult{}
|
||||
|
||||
var commands []*model.Command
|
||||
|
||||
if _, err := s.GetReplica().Select(&commands, "SELECT * FROM Commands WHERE TeamId = :TeamId AND DeleteAt = 0", map[string]interface{}{"TeamId": teamId}); err != nil {
|
||||
result.Err = model.NewAppError("SqlCommandStore.GetByTeam", "store.sql_command.save.get_team.app_error", nil, "teamId="+teamId+", err="+err.Error(), http.StatusInternalServerError)
|
||||
}
|
||||
|
||||
result.Data = commands
|
||||
|
||||
storeChannel <- result
|
||||
close(storeChannel)
|
||||
}()
|
||||
|
||||
return storeChannel
|
||||
}
|
||||
|
||||
func (s SqlCommandStore) Delete(commandId string, time int64) store.StoreChannel {
|
||||
storeChannel := make(store.StoreChannel, 1)
|
||||
|
||||
go func() {
|
||||
result := store.StoreResult{}
|
||||
|
||||
_, err := s.GetMaster().Exec("Update Commands SET DeleteAt = :DeleteAt, UpdateAt = :UpdateAt WHERE Id = :Id", map[string]interface{}{"DeleteAt": time, "UpdateAt": time, "Id": commandId})
|
||||
if err != nil {
|
||||
result.Err = model.NewAppError("SqlCommandStore.Delete", "store.sql_command.save.delete.app_error", nil, "id="+commandId+", err="+err.Error(), http.StatusInternalServerError)
|
||||
}
|
||||
|
||||
storeChannel <- result
|
||||
close(storeChannel)
|
||||
}()
|
||||
|
||||
return storeChannel
|
||||
}
|
||||
|
||||
func (s SqlCommandStore) PermanentDeleteByTeam(teamId string) store.StoreChannel {
|
||||
storeChannel := make(store.StoreChannel, 1)
|
||||
|
||||
go func() {
|
||||
result := store.StoreResult{}
|
||||
|
||||
_, err := s.GetMaster().Exec("DELETE FROM Commands WHERE TeamId = :TeamId", map[string]interface{}{"TeamId": teamId})
|
||||
if err != nil {
|
||||
result.Err = model.NewAppError("SqlCommandStore.DeleteByTeam", "store.sql_command.save.delete_perm.app_error", nil, "id="+teamId+", err="+err.Error(), http.StatusInternalServerError)
|
||||
}
|
||||
|
||||
storeChannel <- result
|
||||
close(storeChannel)
|
||||
}()
|
||||
|
||||
return storeChannel
|
||||
}
|
||||
|
||||
func (s SqlCommandStore) PermanentDeleteByUser(userId string) store.StoreChannel {
|
||||
storeChannel := make(store.StoreChannel, 1)
|
||||
|
||||
go func() {
|
||||
result := store.StoreResult{}
|
||||
|
||||
_, err := s.GetMaster().Exec("DELETE FROM Commands WHERE CreatorId = :UserId", map[string]interface{}{"UserId": userId})
|
||||
if err != nil {
|
||||
result.Err = model.NewAppError("SqlCommandStore.DeleteByUser", "store.sql_command.save.delete_perm.app_error", nil, "id="+userId+", err="+err.Error(), http.StatusInternalServerError)
|
||||
}
|
||||
|
||||
storeChannel <- result
|
||||
close(storeChannel)
|
||||
}()
|
||||
|
||||
return storeChannel
|
||||
}
|
||||
|
||||
func (s SqlCommandStore) Update(cmd *model.Command) store.StoreChannel {
|
||||
storeChannel := make(store.StoreChannel, 1)
|
||||
|
||||
go func() {
|
||||
result := store.StoreResult{}
|
||||
|
||||
cmd.UpdateAt = model.GetMillis()
|
||||
|
||||
if _, err := s.GetMaster().Update(cmd); err != nil {
|
||||
result.Err = model.NewAppError("SqlCommandStore.Update", "store.sql_command.save.update.app_error", nil, "id="+cmd.Id+", "+err.Error(), http.StatusInternalServerError)
|
||||
} else {
|
||||
result.Data = cmd
|
||||
}
|
||||
|
||||
storeChannel <- result
|
||||
close(storeChannel)
|
||||
}()
|
||||
|
||||
return storeChannel
|
||||
}
|
||||
|
||||
func (s SqlCommandStore) AnalyticsCommandCount(teamId string) store.StoreChannel {
|
||||
storeChannel := make(store.StoreChannel, 1)
|
||||
|
||||
go func() {
|
||||
result := store.StoreResult{}
|
||||
|
||||
query :=
|
||||
`SELECT
|
||||
COUNT(*)
|
||||
FROM
|
||||
Commands
|
||||
WHERE
|
||||
DeleteAt = 0`
|
||||
|
||||
if len(teamId) > 0 {
|
||||
query += " AND TeamId = :TeamId"
|
||||
}
|
||||
|
||||
if c, err := s.GetReplica().SelectInt(query, map[string]interface{}{"TeamId": teamId}); err != nil {
|
||||
result.Err = model.NewAppError("SqlCommandStore.AnalyticsCommandCount", "store.sql_command.analytics_command_count.app_error", nil, err.Error(), http.StatusInternalServerError)
|
||||
} else {
|
||||
result.Data = c
|
||||
}
|
||||
|
||||
storeChannel <- result
|
||||
close(storeChannel)
|
||||
}()
|
||||
|
||||
return storeChannel
|
||||
}
|
||||
221
store/sqlstore/command_store_test.go
Обычный файл
221
store/sqlstore/command_store_test.go
Обычный файл
@@ -0,0 +1,221 @@
|
||||
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
|
||||
// See License.txt for license information.
|
||||
|
||||
package sqlstore
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/mattermost/mattermost-server/model"
|
||||
)
|
||||
|
||||
func TestCommandStoreSave(t *testing.T) {
|
||||
ss := Setup()
|
||||
|
||||
o1 := model.Command{}
|
||||
o1.CreatorId = model.NewId()
|
||||
o1.Method = model.COMMAND_METHOD_POST
|
||||
o1.TeamId = model.NewId()
|
||||
o1.URL = "http://nowhere.com/"
|
||||
o1.Trigger = "trigger"
|
||||
|
||||
if err := (<-ss.Command().Save(&o1)).Err; err != nil {
|
||||
t.Fatal("couldn't save item", err)
|
||||
}
|
||||
|
||||
if err := (<-ss.Command().Save(&o1)).Err; err == nil {
|
||||
t.Fatal("shouldn't be able to update from save")
|
||||
}
|
||||
}
|
||||
|
||||
func TestCommandStoreGet(t *testing.T) {
|
||||
ss := Setup()
|
||||
|
||||
o1 := &model.Command{}
|
||||
o1.CreatorId = model.NewId()
|
||||
o1.Method = model.COMMAND_METHOD_POST
|
||||
o1.TeamId = model.NewId()
|
||||
o1.URL = "http://nowhere.com/"
|
||||
o1.Trigger = "trigger"
|
||||
|
||||
o1 = (<-ss.Command().Save(o1)).Data.(*model.Command)
|
||||
|
||||
if r1 := <-ss.Command().Get(o1.Id); r1.Err != nil {
|
||||
t.Fatal(r1.Err)
|
||||
} else {
|
||||
if r1.Data.(*model.Command).CreateAt != o1.CreateAt {
|
||||
t.Fatal("invalid returned command")
|
||||
}
|
||||
}
|
||||
|
||||
if err := (<-ss.Command().Get("123")).Err; err == nil {
|
||||
t.Fatal("Missing id should have failed")
|
||||
}
|
||||
}
|
||||
|
||||
func TestCommandStoreGetByTeam(t *testing.T) {
|
||||
ss := Setup()
|
||||
|
||||
o1 := &model.Command{}
|
||||
o1.CreatorId = model.NewId()
|
||||
o1.Method = model.COMMAND_METHOD_POST
|
||||
o1.TeamId = model.NewId()
|
||||
o1.URL = "http://nowhere.com/"
|
||||
o1.Trigger = "trigger"
|
||||
|
||||
o1 = (<-ss.Command().Save(o1)).Data.(*model.Command)
|
||||
|
||||
if r1 := <-ss.Command().GetByTeam(o1.TeamId); r1.Err != nil {
|
||||
t.Fatal(r1.Err)
|
||||
} else {
|
||||
if r1.Data.([]*model.Command)[0].CreateAt != o1.CreateAt {
|
||||
t.Fatal("invalid returned command")
|
||||
}
|
||||
}
|
||||
|
||||
if result := <-ss.Command().GetByTeam("123"); result.Err != nil {
|
||||
t.Fatal(result.Err)
|
||||
} else {
|
||||
if len(result.Data.([]*model.Command)) != 0 {
|
||||
t.Fatal("no commands should have returned")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestCommandStoreDelete(t *testing.T) {
|
||||
ss := Setup()
|
||||
|
||||
o1 := &model.Command{}
|
||||
o1.CreatorId = model.NewId()
|
||||
o1.Method = model.COMMAND_METHOD_POST
|
||||
o1.TeamId = model.NewId()
|
||||
o1.URL = "http://nowhere.com/"
|
||||
o1.Trigger = "trigger"
|
||||
|
||||
o1 = (<-ss.Command().Save(o1)).Data.(*model.Command)
|
||||
|
||||
if r1 := <-ss.Command().Get(o1.Id); r1.Err != nil {
|
||||
t.Fatal(r1.Err)
|
||||
} else {
|
||||
if r1.Data.(*model.Command).CreateAt != o1.CreateAt {
|
||||
t.Fatal("invalid returned command")
|
||||
}
|
||||
}
|
||||
|
||||
if r2 := <-ss.Command().Delete(o1.Id, model.GetMillis()); r2.Err != nil {
|
||||
t.Fatal(r2.Err)
|
||||
}
|
||||
|
||||
if r3 := (<-ss.Command().Get(o1.Id)); r3.Err == nil {
|
||||
t.Log(r3.Data)
|
||||
t.Fatal("Missing id should have failed")
|
||||
}
|
||||
}
|
||||
|
||||
func TestCommandStoreDeleteByTeam(t *testing.T) {
|
||||
ss := Setup()
|
||||
|
||||
o1 := &model.Command{}
|
||||
o1.CreatorId = model.NewId()
|
||||
o1.Method = model.COMMAND_METHOD_POST
|
||||
o1.TeamId = model.NewId()
|
||||
o1.URL = "http://nowhere.com/"
|
||||
o1.Trigger = "trigger"
|
||||
|
||||
o1 = (<-ss.Command().Save(o1)).Data.(*model.Command)
|
||||
|
||||
if r1 := <-ss.Command().Get(o1.Id); r1.Err != nil {
|
||||
t.Fatal(r1.Err)
|
||||
} else {
|
||||
if r1.Data.(*model.Command).CreateAt != o1.CreateAt {
|
||||
t.Fatal("invalid returned command")
|
||||
}
|
||||
}
|
||||
|
||||
if r2 := <-ss.Command().PermanentDeleteByTeam(o1.TeamId); r2.Err != nil {
|
||||
t.Fatal(r2.Err)
|
||||
}
|
||||
|
||||
if r3 := (<-ss.Command().Get(o1.Id)); r3.Err == nil {
|
||||
t.Log(r3.Data)
|
||||
t.Fatal("Missing id should have failed")
|
||||
}
|
||||
}
|
||||
|
||||
func TestCommandStoreDeleteByUser(t *testing.T) {
|
||||
ss := Setup()
|
||||
|
||||
o1 := &model.Command{}
|
||||
o1.CreatorId = model.NewId()
|
||||
o1.Method = model.COMMAND_METHOD_POST
|
||||
o1.TeamId = model.NewId()
|
||||
o1.URL = "http://nowhere.com/"
|
||||
o1.Trigger = "trigger"
|
||||
|
||||
o1 = (<-ss.Command().Save(o1)).Data.(*model.Command)
|
||||
|
||||
if r1 := <-ss.Command().Get(o1.Id); r1.Err != nil {
|
||||
t.Fatal(r1.Err)
|
||||
} else {
|
||||
if r1.Data.(*model.Command).CreateAt != o1.CreateAt {
|
||||
t.Fatal("invalid returned command")
|
||||
}
|
||||
}
|
||||
|
||||
if r2 := <-ss.Command().PermanentDeleteByUser(o1.CreatorId); r2.Err != nil {
|
||||
t.Fatal(r2.Err)
|
||||
}
|
||||
|
||||
if r3 := (<-ss.Command().Get(o1.Id)); r3.Err == nil {
|
||||
t.Log(r3.Data)
|
||||
t.Fatal("Missing id should have failed")
|
||||
}
|
||||
}
|
||||
|
||||
func TestCommandStoreUpdate(t *testing.T) {
|
||||
ss := Setup()
|
||||
|
||||
o1 := &model.Command{}
|
||||
o1.CreatorId = model.NewId()
|
||||
o1.Method = model.COMMAND_METHOD_POST
|
||||
o1.TeamId = model.NewId()
|
||||
o1.URL = "http://nowhere.com/"
|
||||
o1.Trigger = "trigger"
|
||||
|
||||
o1 = (<-ss.Command().Save(o1)).Data.(*model.Command)
|
||||
|
||||
o1.Token = model.NewId()
|
||||
|
||||
if r2 := <-ss.Command().Update(o1); r2.Err != nil {
|
||||
t.Fatal(r2.Err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCommandCount(t *testing.T) {
|
||||
ss := Setup()
|
||||
|
||||
o1 := &model.Command{}
|
||||
o1.CreatorId = model.NewId()
|
||||
o1.Method = model.COMMAND_METHOD_POST
|
||||
o1.TeamId = model.NewId()
|
||||
o1.URL = "http://nowhere.com/"
|
||||
o1.Trigger = "trigger"
|
||||
|
||||
o1 = (<-ss.Command().Save(o1)).Data.(*model.Command)
|
||||
|
||||
if r1 := <-ss.Command().AnalyticsCommandCount(""); r1.Err != nil {
|
||||
t.Fatal(r1.Err)
|
||||
} else {
|
||||
if r1.Data.(int64) == 0 {
|
||||
t.Fatal("should be at least 1 command")
|
||||
}
|
||||
}
|
||||
|
||||
if r2 := <-ss.Command().AnalyticsCommandCount(o1.TeamId); r2.Err != nil {
|
||||
t.Fatal(r2.Err)
|
||||
} else {
|
||||
if r2.Data.(int64) != 1 {
|
||||
t.Fatal("should be 1 command")
|
||||
}
|
||||
}
|
||||
}
|
||||
125
store/sqlstore/command_webhook_store.go
Обычный файл
125
store/sqlstore/command_webhook_store.go
Обычный файл
@@ -0,0 +1,125 @@
|
||||
// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
|
||||
// See License.txt for license information.
|
||||
|
||||
package sqlstore
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"net/http"
|
||||
|
||||
l4g "github.com/alecthomas/log4go"
|
||||
|
||||
"github.com/mattermost/mattermost-server/model"
|
||||
"github.com/mattermost/mattermost-server/store"
|
||||
)
|
||||
|
||||
type SqlCommandWebhookStore struct {
|
||||
SqlStore
|
||||
}
|
||||
|
||||
func NewSqlCommandWebhookStore(sqlStore SqlStore) store.CommandWebhookStore {
|
||||
s := &SqlCommandWebhookStore{sqlStore}
|
||||
|
||||
for _, db := range sqlStore.GetAllConns() {
|
||||
tablec := db.AddTableWithName(model.CommandWebhook{}, "CommandWebhooks").SetKeys(false, "Id")
|
||||
tablec.ColMap("Id").SetMaxSize(26)
|
||||
tablec.ColMap("CommandId").SetMaxSize(26)
|
||||
tablec.ColMap("UserId").SetMaxSize(26)
|
||||
tablec.ColMap("ChannelId").SetMaxSize(26)
|
||||
tablec.ColMap("RootId").SetMaxSize(26)
|
||||
tablec.ColMap("ParentId").SetMaxSize(26)
|
||||
}
|
||||
|
||||
return s
|
||||
}
|
||||
|
||||
func (s SqlCommandWebhookStore) CreateIndexesIfNotExists() {
|
||||
s.CreateIndexIfNotExists("idx_command_webhook_create_at", "CommandWebhooks", "CreateAt")
|
||||
}
|
||||
|
||||
func (s SqlCommandWebhookStore) Save(webhook *model.CommandWebhook) store.StoreChannel {
|
||||
storeChannel := make(store.StoreChannel, 1)
|
||||
|
||||
go func() {
|
||||
result := store.StoreResult{}
|
||||
|
||||
if len(webhook.Id) > 0 {
|
||||
result.Err = model.NewAppError("SqlCommandWebhookStore.Save", "store.sql_command_webhooks.save.existing.app_error", nil, "id="+webhook.Id, http.StatusBadRequest)
|
||||
storeChannel <- result
|
||||
close(storeChannel)
|
||||
return
|
||||
}
|
||||
|
||||
webhook.PreSave()
|
||||
if result.Err = webhook.IsValid(); result.Err != nil {
|
||||
storeChannel <- result
|
||||
close(storeChannel)
|
||||
return
|
||||
}
|
||||
|
||||
if err := s.GetMaster().Insert(webhook); err != nil {
|
||||
result.Err = model.NewAppError("SqlCommandWebhookStore.Save", "store.sql_command_webhooks.save.app_error", nil, "id="+webhook.Id+", "+err.Error(), http.StatusInternalServerError)
|
||||
} else {
|
||||
result.Data = webhook
|
||||
}
|
||||
|
||||
storeChannel <- result
|
||||
close(storeChannel)
|
||||
}()
|
||||
|
||||
return storeChannel
|
||||
}
|
||||
|
||||
func (s SqlCommandWebhookStore) Get(id string) store.StoreChannel {
|
||||
storeChannel := make(store.StoreChannel, 1)
|
||||
|
||||
go func() {
|
||||
result := store.StoreResult{}
|
||||
|
||||
var webhook model.CommandWebhook
|
||||
|
||||
exptime := model.GetMillis() - model.COMMAND_WEBHOOK_LIFETIME
|
||||
if err := s.GetReplica().SelectOne(&webhook, "SELECT * FROM CommandWebhooks WHERE Id = :Id AND CreateAt > :ExpTime", map[string]interface{}{"Id": id, "ExpTime": exptime}); err != nil {
|
||||
result.Err = model.NewAppError("SqlCommandWebhookStore.Get", "store.sql_command_webhooks.get.app_error", nil, "id="+id+", err="+err.Error(), http.StatusInternalServerError)
|
||||
if err == sql.ErrNoRows {
|
||||
result.Err.StatusCode = http.StatusNotFound
|
||||
}
|
||||
}
|
||||
|
||||
result.Data = &webhook
|
||||
|
||||
storeChannel <- result
|
||||
close(storeChannel)
|
||||
}()
|
||||
|
||||
return storeChannel
|
||||
}
|
||||
|
||||
func (s SqlCommandWebhookStore) TryUse(id string, limit int) store.StoreChannel {
|
||||
storeChannel := make(store.StoreChannel, 1)
|
||||
|
||||
go func() {
|
||||
result := store.StoreResult{}
|
||||
|
||||
if sqlResult, err := s.GetMaster().Exec("UPDATE CommandWebhooks SET UseCount = UseCount + 1 WHERE Id = :Id AND UseCount < :UseLimit", map[string]interface{}{"Id": id, "UseLimit": limit}); err != nil {
|
||||
result.Err = model.NewAppError("SqlCommandWebhookStore.TryUse", "store.sql_command_webhooks.try_use.app_error", nil, "id="+id+", err="+err.Error(), http.StatusInternalServerError)
|
||||
} else if rows, _ := sqlResult.RowsAffected(); rows == 0 {
|
||||
result.Err = model.NewAppError("SqlCommandWebhookStore.TryUse", "store.sql_command_webhooks.try_use.invalid.app_error", nil, "id="+id, http.StatusBadRequest)
|
||||
}
|
||||
|
||||
result.Data = id
|
||||
|
||||
storeChannel <- result
|
||||
close(storeChannel)
|
||||
}()
|
||||
|
||||
return storeChannel
|
||||
}
|
||||
|
||||
func (s SqlCommandWebhookStore) Cleanup() {
|
||||
l4g.Debug("Cleaning up command webhook store.")
|
||||
exptime := model.GetMillis() - model.COMMAND_WEBHOOK_LIFETIME
|
||||
if _, err := s.GetMaster().Exec("DELETE FROM CommandWebhooks WHERE CreateAt < :ExpTime", map[string]interface{}{"ExpTime": exptime}); err != nil {
|
||||
l4g.Error("Unable to cleanup command webhook store.")
|
||||
}
|
||||
}
|
||||
65
store/sqlstore/command_webhook_store_test.go
Обычный файл
65
store/sqlstore/command_webhook_store_test.go
Обычный файл
@@ -0,0 +1,65 @@
|
||||
// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
|
||||
// See License.txt for license information.
|
||||
|
||||
package sqlstore
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"net/http"
|
||||
|
||||
"github.com/mattermost/mattermost-server/model"
|
||||
)
|
||||
|
||||
func TestCommandWebhookStore(t *testing.T) {
|
||||
ss := Setup()
|
||||
|
||||
cws := ss.CommandWebhook()
|
||||
|
||||
h1 := &model.CommandWebhook{}
|
||||
h1.CommandId = model.NewId()
|
||||
h1.UserId = model.NewId()
|
||||
h1.ChannelId = model.NewId()
|
||||
h1 = (<-cws.Save(h1)).Data.(*model.CommandWebhook)
|
||||
|
||||
if r1 := <-cws.Get(h1.Id); r1.Err != nil {
|
||||
t.Fatal(r1.Err)
|
||||
} else {
|
||||
if *r1.Data.(*model.CommandWebhook) != *h1 {
|
||||
t.Fatal("invalid returned webhook")
|
||||
}
|
||||
}
|
||||
|
||||
if err := (<-cws.Get("123")).Err; err.StatusCode != http.StatusNotFound {
|
||||
t.Fatal("Should have set the status as not found for missing id")
|
||||
}
|
||||
|
||||
h2 := &model.CommandWebhook{}
|
||||
h2.CreateAt = model.GetMillis() - 2*model.COMMAND_WEBHOOK_LIFETIME
|
||||
h2.CommandId = model.NewId()
|
||||
h2.UserId = model.NewId()
|
||||
h2.ChannelId = model.NewId()
|
||||
h2 = (<-cws.Save(h2)).Data.(*model.CommandWebhook)
|
||||
|
||||
if err := (<-cws.Get(h2.Id)).Err; err == nil || err.StatusCode != http.StatusNotFound {
|
||||
t.Fatal("Should have set the status as not found for expired webhook")
|
||||
}
|
||||
|
||||
cws.Cleanup()
|
||||
|
||||
if err := (<-cws.Get(h1.Id)).Err; err != nil {
|
||||
t.Fatal("Should have no error getting unexpired webhook")
|
||||
}
|
||||
|
||||
if err := (<-cws.Get(h2.Id)).Err; err.StatusCode != http.StatusNotFound {
|
||||
t.Fatal("Should have set the status as not found for expired webhook")
|
||||
}
|
||||
|
||||
if err := (<-cws.TryUse(h1.Id, 1)).Err; err != nil {
|
||||
t.Fatal("Should be able to use webhook once")
|
||||
}
|
||||
|
||||
if err := (<-cws.TryUse(h1.Id, 1)).Err; err == nil || err.StatusCode != http.StatusBadRequest {
|
||||
t.Fatal("Should be able to use webhook once")
|
||||
}
|
||||
}
|
||||
267
store/sqlstore/compliance_store.go
Обычный файл
267
store/sqlstore/compliance_store.go
Обычный файл
@@ -0,0 +1,267 @@
|
||||
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
|
||||
// See License.txt for license information.
|
||||
|
||||
package sqlstore
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/mattermost/mattermost-server/model"
|
||||
"github.com/mattermost/mattermost-server/store"
|
||||
)
|
||||
|
||||
type SqlComplianceStore struct {
|
||||
SqlStore
|
||||
}
|
||||
|
||||
func NewSqlComplianceStore(sqlStore SqlStore) store.ComplianceStore {
|
||||
s := &SqlComplianceStore{sqlStore}
|
||||
|
||||
for _, db := range sqlStore.GetAllConns() {
|
||||
table := db.AddTableWithName(model.Compliance{}, "Compliances").SetKeys(false, "Id")
|
||||
table.ColMap("Id").SetMaxSize(26)
|
||||
table.ColMap("UserId").SetMaxSize(26)
|
||||
table.ColMap("Status").SetMaxSize(64)
|
||||
table.ColMap("Desc").SetMaxSize(512)
|
||||
table.ColMap("Type").SetMaxSize(64)
|
||||
table.ColMap("Keywords").SetMaxSize(512)
|
||||
table.ColMap("Emails").SetMaxSize(1024)
|
||||
}
|
||||
|
||||
return s
|
||||
}
|
||||
|
||||
func (s SqlComplianceStore) CreateIndexesIfNotExists() {
|
||||
}
|
||||
|
||||
func (s SqlComplianceStore) Save(compliance *model.Compliance) store.StoreChannel {
|
||||
|
||||
storeChannel := make(store.StoreChannel, 1)
|
||||
|
||||
go func() {
|
||||
result := store.StoreResult{}
|
||||
|
||||
compliance.PreSave()
|
||||
if result.Err = compliance.IsValid(); result.Err != nil {
|
||||
storeChannel <- result
|
||||
close(storeChannel)
|
||||
return
|
||||
}
|
||||
|
||||
if err := s.GetMaster().Insert(compliance); err != nil {
|
||||
result.Err = model.NewAppError("SqlComplianceStore.Save", "store.sql_compliance.save.saving.app_error", nil, err.Error(), http.StatusInternalServerError)
|
||||
} else {
|
||||
result.Data = compliance
|
||||
}
|
||||
|
||||
storeChannel <- result
|
||||
close(storeChannel)
|
||||
}()
|
||||
|
||||
return storeChannel
|
||||
}
|
||||
|
||||
func (us SqlComplianceStore) Update(compliance *model.Compliance) store.StoreChannel {
|
||||
|
||||
storeChannel := make(store.StoreChannel, 1)
|
||||
|
||||
go func() {
|
||||
result := store.StoreResult{}
|
||||
|
||||
if result.Err = compliance.IsValid(); result.Err != nil {
|
||||
storeChannel <- result
|
||||
close(storeChannel)
|
||||
return
|
||||
}
|
||||
|
||||
if _, err := us.GetMaster().Update(compliance); err != nil {
|
||||
result.Err = model.NewAppError("SqlComplianceStore.Update", "store.sql_compliance.save.saving.app_error", nil, err.Error(), http.StatusInternalServerError)
|
||||
} else {
|
||||
result.Data = compliance
|
||||
}
|
||||
|
||||
storeChannel <- result
|
||||
close(storeChannel)
|
||||
}()
|
||||
|
||||
return storeChannel
|
||||
}
|
||||
|
||||
func (s SqlComplianceStore) GetAll(offset, limit int) store.StoreChannel {
|
||||
|
||||
storeChannel := make(store.StoreChannel, 1)
|
||||
|
||||
go func() {
|
||||
result := store.StoreResult{}
|
||||
|
||||
query := "SELECT * FROM Compliances ORDER BY CreateAt DESC LIMIT :Limit OFFSET :Offset"
|
||||
|
||||
var compliances model.Compliances
|
||||
if _, err := s.GetReplica().Select(&compliances, query, map[string]interface{}{"Offset": offset, "Limit": limit}); err != nil {
|
||||
result.Err = model.NewAppError("SqlComplianceStore.Get", "store.sql_compliance.get.finding.app_error", nil, err.Error(), http.StatusInternalServerError)
|
||||
} else {
|
||||
result.Data = compliances
|
||||
}
|
||||
|
||||
storeChannel <- result
|
||||
close(storeChannel)
|
||||
}()
|
||||
|
||||
return storeChannel
|
||||
}
|
||||
|
||||
func (us SqlComplianceStore) Get(id string) store.StoreChannel {
|
||||
|
||||
storeChannel := make(store.StoreChannel, 1)
|
||||
|
||||
go func() {
|
||||
result := store.StoreResult{}
|
||||
|
||||
if obj, err := us.GetReplica().Get(model.Compliance{}, id); err != nil {
|
||||
result.Err = model.NewAppError("SqlComplianceStore.Get", "store.sql_compliance.get.finding.app_error", nil, err.Error(), http.StatusInternalServerError)
|
||||
} else if obj == nil {
|
||||
result.Err = model.NewAppError("SqlComplianceStore.Get", "store.sql_compliance.get.finding.app_error", nil, err.Error(), http.StatusNotFound)
|
||||
} else {
|
||||
result.Data = obj.(*model.Compliance)
|
||||
}
|
||||
|
||||
storeChannel <- result
|
||||
close(storeChannel)
|
||||
|
||||
}()
|
||||
|
||||
return storeChannel
|
||||
}
|
||||
|
||||
func (s SqlComplianceStore) ComplianceExport(job *model.Compliance) store.StoreChannel {
|
||||
storeChannel := make(store.StoreChannel, 1)
|
||||
|
||||
go func() {
|
||||
result := store.StoreResult{}
|
||||
|
||||
props := map[string]interface{}{"StartTime": job.StartAt, "EndTime": job.EndAt}
|
||||
|
||||
keywordQuery := ""
|
||||
keywords := strings.Fields(strings.TrimSpace(strings.ToLower(strings.Replace(job.Keywords, ",", " ", -1))))
|
||||
if len(keywords) > 0 {
|
||||
|
||||
keywordQuery = "AND ("
|
||||
|
||||
for index, keyword := range keywords {
|
||||
if index >= 1 {
|
||||
keywordQuery += " OR LOWER(Posts.Message) LIKE :Keyword" + strconv.Itoa(index)
|
||||
} else {
|
||||
keywordQuery += "LOWER(Posts.Message) LIKE :Keyword" + strconv.Itoa(index)
|
||||
}
|
||||
|
||||
props["Keyword"+strconv.Itoa(index)] = "%" + keyword + "%"
|
||||
}
|
||||
|
||||
keywordQuery += ")"
|
||||
}
|
||||
|
||||
emailQuery := ""
|
||||
emails := strings.Fields(strings.TrimSpace(strings.ToLower(strings.Replace(job.Emails, ",", " ", -1))))
|
||||
if len(emails) > 0 {
|
||||
|
||||
emailQuery = "AND ("
|
||||
|
||||
for index, email := range emails {
|
||||
if index >= 1 {
|
||||
emailQuery += " OR Users.Email = :Email" + strconv.Itoa(index)
|
||||
} else {
|
||||
emailQuery += "Users.Email = :Email" + strconv.Itoa(index)
|
||||
}
|
||||
|
||||
props["Email"+strconv.Itoa(index)] = email
|
||||
}
|
||||
|
||||
emailQuery += ")"
|
||||
}
|
||||
|
||||
query :=
|
||||
`(SELECT
|
||||
Teams.Name AS TeamName,
|
||||
Teams.DisplayName AS TeamDisplayName,
|
||||
Channels.Name AS ChannelName,
|
||||
Channels.DisplayName AS ChannelDisplayName,
|
||||
Users.Username AS UserUsername,
|
||||
Users.Email AS UserEmail,
|
||||
Users.Nickname AS UserNickname,
|
||||
Posts.Id AS PostId,
|
||||
Posts.CreateAt AS PostCreateAt,
|
||||
Posts.UpdateAt AS PostUpdateAt,
|
||||
Posts.DeleteAt AS PostDeleteAt,
|
||||
Posts.RootId AS PostRootId,
|
||||
Posts.ParentId AS PostParentId,
|
||||
Posts.OriginalId AS PostOriginalId,
|
||||
Posts.Message AS PostMessage,
|
||||
Posts.Type AS PostType,
|
||||
Posts.Props AS PostProps,
|
||||
Posts.Hashtags AS PostHashtags,
|
||||
Posts.FileIds AS PostFileIds
|
||||
FROM
|
||||
Teams,
|
||||
Channels,
|
||||
Users,
|
||||
Posts
|
||||
WHERE
|
||||
Teams.Id = Channels.TeamId
|
||||
AND Posts.ChannelId = Channels.Id
|
||||
AND Posts.UserId = Users.Id
|
||||
AND Posts.CreateAt > :StartTime
|
||||
AND Posts.CreateAt <= :EndTime
|
||||
` + emailQuery + `
|
||||
` + keywordQuery + `)
|
||||
UNION ALL
|
||||
(SELECT
|
||||
'direct-messages' AS TeamName,
|
||||
'Direct Messages' AS TeamDisplayName,
|
||||
Channels.Name AS ChannelName,
|
||||
Channels.DisplayName AS ChannelDisplayName,
|
||||
Users.Username AS UserUsername,
|
||||
Users.Email AS UserEmail,
|
||||
Users.Nickname AS UserNickname,
|
||||
Posts.Id AS PostId,
|
||||
Posts.CreateAt AS PostCreateAt,
|
||||
Posts.UpdateAt AS PostUpdateAt,
|
||||
Posts.DeleteAt AS PostDeleteAt,
|
||||
Posts.RootId AS PostRootId,
|
||||
Posts.ParentId AS PostParentId,
|
||||
Posts.OriginalId AS PostOriginalId,
|
||||
Posts.Message AS PostMessage,
|
||||
Posts.Type AS PostType,
|
||||
Posts.Props AS PostProps,
|
||||
Posts.Hashtags AS PostHashtags,
|
||||
Posts.FileIds AS PostFileIds
|
||||
FROM
|
||||
Channels,
|
||||
Users,
|
||||
Posts
|
||||
WHERE
|
||||
Channels.TeamId = ''
|
||||
AND Posts.ChannelId = Channels.Id
|
||||
AND Posts.UserId = Users.Id
|
||||
AND Posts.CreateAt > :StartTime
|
||||
AND Posts.CreateAt <= :EndTime
|
||||
` + emailQuery + `
|
||||
` + keywordQuery + `)
|
||||
ORDER BY PostCreateAt
|
||||
LIMIT 30000`
|
||||
|
||||
var cposts []*model.CompliancePost
|
||||
|
||||
if _, err := s.GetReplica().Select(&cposts, query, props); err != nil {
|
||||
result.Err = model.NewAppError("SqlPostStore.ComplianceExport", "store.sql_post.compliance_export.app_error", nil, err.Error(), http.StatusInternalServerError)
|
||||
} else {
|
||||
result.Data = cposts
|
||||
}
|
||||
|
||||
storeChannel <- result
|
||||
close(storeChannel)
|
||||
}()
|
||||
|
||||
return storeChannel
|
||||
}
|
||||
318
store/sqlstore/compliance_store_test.go
Обычный файл
318
store/sqlstore/compliance_store_test.go
Обычный файл
@@ -0,0 +1,318 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See License.txt for license information.
|
||||
|
||||
package sqlstore
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/mattermost/mattermost-server/model"
|
||||
"github.com/mattermost/mattermost-server/store"
|
||||
)
|
||||
|
||||
func TestSqlComplianceStore(t *testing.T) {
|
||||
ss := Setup()
|
||||
|
||||
compliance1 := &model.Compliance{Desc: "Audit for federal subpoena case #22443", UserId: model.NewId(), Status: model.COMPLIANCE_STATUS_FAILED, StartAt: model.GetMillis() - 1, EndAt: model.GetMillis() + 1, Type: model.COMPLIANCE_TYPE_ADHOC}
|
||||
store.Must(ss.Compliance().Save(compliance1))
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
|
||||
compliance2 := &model.Compliance{Desc: "Audit for federal subpoena case #11458", UserId: model.NewId(), Status: model.COMPLIANCE_STATUS_RUNNING, StartAt: model.GetMillis() - 1, EndAt: model.GetMillis() + 1, Type: model.COMPLIANCE_TYPE_ADHOC}
|
||||
store.Must(ss.Compliance().Save(compliance2))
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
|
||||
c := ss.Compliance().GetAll(0, 1000)
|
||||
result := <-c
|
||||
compliances := result.Data.(model.Compliances)
|
||||
|
||||
if compliances[0].Status != model.COMPLIANCE_STATUS_RUNNING && compliance2.Id != compliances[0].Id {
|
||||
t.Fatal()
|
||||
}
|
||||
|
||||
compliance2.Status = model.COMPLIANCE_STATUS_FAILED
|
||||
store.Must(ss.Compliance().Update(compliance2))
|
||||
|
||||
c = ss.Compliance().GetAll(0, 1000)
|
||||
result = <-c
|
||||
compliances = result.Data.(model.Compliances)
|
||||
|
||||
if compliances[0].Status != model.COMPLIANCE_STATUS_FAILED && compliance2.Id != compliances[0].Id {
|
||||
t.Fatal()
|
||||
}
|
||||
|
||||
c = ss.Compliance().GetAll(0, 1)
|
||||
result = <-c
|
||||
compliances = result.Data.(model.Compliances)
|
||||
|
||||
if len(compliances) != 1 {
|
||||
t.Fatal("should only have returned 1")
|
||||
}
|
||||
|
||||
c = ss.Compliance().GetAll(1, 1)
|
||||
result = <-c
|
||||
compliances = result.Data.(model.Compliances)
|
||||
|
||||
if len(compliances) != 1 {
|
||||
t.Fatal("should only have returned 1")
|
||||
}
|
||||
|
||||
rc2 := (<-ss.Compliance().Get(compliance2.Id)).Data.(*model.Compliance)
|
||||
if rc2.Status != compliance2.Status {
|
||||
t.Fatal()
|
||||
}
|
||||
}
|
||||
|
||||
func TestComplianceExport(t *testing.T) {
|
||||
ss := Setup()
|
||||
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
|
||||
t1 := &model.Team{}
|
||||
t1.DisplayName = "DisplayName"
|
||||
t1.Name = "zz" + model.NewId() + "b"
|
||||
t1.Email = model.NewId() + "@nowhere.com"
|
||||
t1.Type = model.TEAM_OPEN
|
||||
t1 = store.Must(ss.Team().Save(t1)).(*model.Team)
|
||||
|
||||
u1 := &model.User{}
|
||||
u1.Email = model.NewId()
|
||||
u1.Username = model.NewId()
|
||||
u1 = store.Must(ss.User().Save(u1)).(*model.User)
|
||||
store.Must(ss.Team().SaveMember(&model.TeamMember{TeamId: t1.Id, UserId: u1.Id}))
|
||||
|
||||
u2 := &model.User{}
|
||||
u2.Email = model.NewId()
|
||||
u2.Username = model.NewId()
|
||||
u2 = store.Must(ss.User().Save(u2)).(*model.User)
|
||||
store.Must(ss.Team().SaveMember(&model.TeamMember{TeamId: t1.Id, UserId: u2.Id}))
|
||||
|
||||
c1 := &model.Channel{}
|
||||
c1.TeamId = t1.Id
|
||||
c1.DisplayName = "Channel2"
|
||||
c1.Name = "zz" + model.NewId() + "b"
|
||||
c1.Type = model.CHANNEL_OPEN
|
||||
c1 = store.Must(ss.Channel().Save(c1)).(*model.Channel)
|
||||
|
||||
o1 := &model.Post{}
|
||||
o1.ChannelId = c1.Id
|
||||
o1.UserId = u1.Id
|
||||
o1.CreateAt = model.GetMillis()
|
||||
o1.Message = "zz" + model.NewId() + "b"
|
||||
o1 = store.Must(ss.Post().Save(o1)).(*model.Post)
|
||||
|
||||
o1a := &model.Post{}
|
||||
o1a.ChannelId = c1.Id
|
||||
o1a.UserId = u1.Id
|
||||
o1a.CreateAt = o1.CreateAt + 10
|
||||
o1a.Message = "zz" + model.NewId() + "b"
|
||||
o1a = store.Must(ss.Post().Save(o1a)).(*model.Post)
|
||||
|
||||
o2 := &model.Post{}
|
||||
o2.ChannelId = c1.Id
|
||||
o2.UserId = u1.Id
|
||||
o2.CreateAt = o1.CreateAt + 20
|
||||
o2.Message = "zz" + model.NewId() + "b"
|
||||
o2 = store.Must(ss.Post().Save(o2)).(*model.Post)
|
||||
|
||||
o2a := &model.Post{}
|
||||
o2a.ChannelId = c1.Id
|
||||
o2a.UserId = u2.Id
|
||||
o2a.CreateAt = o1.CreateAt + 30
|
||||
o2a.Message = "zz" + model.NewId() + "b"
|
||||
o2a = store.Must(ss.Post().Save(o2a)).(*model.Post)
|
||||
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
|
||||
cr1 := &model.Compliance{Desc: "test" + model.NewId(), StartAt: o1.CreateAt - 1, EndAt: o2a.CreateAt + 1}
|
||||
if r1 := <-ss.Compliance().ComplianceExport(cr1); r1.Err != nil {
|
||||
t.Fatal(r1.Err)
|
||||
} else {
|
||||
cposts := r1.Data.([]*model.CompliancePost)
|
||||
|
||||
if len(cposts) != 4 {
|
||||
t.Fatal("return wrong results length")
|
||||
}
|
||||
|
||||
if cposts[0].PostId != o1.Id {
|
||||
t.Fatal("Wrong sort")
|
||||
}
|
||||
|
||||
if cposts[3].PostId != o2a.Id {
|
||||
t.Fatal("Wrong sort")
|
||||
}
|
||||
}
|
||||
|
||||
cr2 := &model.Compliance{Desc: "test" + model.NewId(), StartAt: o1.CreateAt - 1, EndAt: o2a.CreateAt + 1, Emails: u2.Email}
|
||||
if r1 := <-ss.Compliance().ComplianceExport(cr2); r1.Err != nil {
|
||||
t.Fatal(r1.Err)
|
||||
} else {
|
||||
cposts := r1.Data.([]*model.CompliancePost)
|
||||
|
||||
if len(cposts) != 1 {
|
||||
t.Fatal("return wrong results length")
|
||||
}
|
||||
|
||||
if cposts[0].PostId != o2a.Id {
|
||||
t.Fatal("Wrong sort")
|
||||
}
|
||||
}
|
||||
|
||||
cr3 := &model.Compliance{Desc: "test" + model.NewId(), StartAt: o1.CreateAt - 1, EndAt: o2a.CreateAt + 1, Emails: u2.Email + ", " + u1.Email}
|
||||
if r1 := <-ss.Compliance().ComplianceExport(cr3); r1.Err != nil {
|
||||
t.Fatal(r1.Err)
|
||||
} else {
|
||||
cposts := r1.Data.([]*model.CompliancePost)
|
||||
|
||||
if len(cposts) != 4 {
|
||||
t.Fatal("return wrong results length")
|
||||
}
|
||||
|
||||
if cposts[0].PostId != o1.Id {
|
||||
t.Fatal("Wrong sort")
|
||||
}
|
||||
|
||||
if cposts[3].PostId != o2a.Id {
|
||||
t.Fatal("Wrong sort")
|
||||
}
|
||||
}
|
||||
|
||||
cr4 := &model.Compliance{Desc: "test" + model.NewId(), StartAt: o1.CreateAt - 1, EndAt: o2a.CreateAt + 1, Keywords: o2a.Message}
|
||||
if r1 := <-ss.Compliance().ComplianceExport(cr4); r1.Err != nil {
|
||||
t.Fatal(r1.Err)
|
||||
} else {
|
||||
cposts := r1.Data.([]*model.CompliancePost)
|
||||
|
||||
if len(cposts) != 1 {
|
||||
t.Fatal("return wrong results length")
|
||||
}
|
||||
|
||||
if cposts[0].PostId != o2a.Id {
|
||||
t.Fatal("Wrong sort")
|
||||
}
|
||||
}
|
||||
|
||||
cr5 := &model.Compliance{Desc: "test" + model.NewId(), StartAt: o1.CreateAt - 1, EndAt: o2a.CreateAt + 1, Keywords: o2a.Message + " " + o1.Message}
|
||||
if r1 := <-ss.Compliance().ComplianceExport(cr5); r1.Err != nil {
|
||||
t.Fatal(r1.Err)
|
||||
} else {
|
||||
cposts := r1.Data.([]*model.CompliancePost)
|
||||
|
||||
if len(cposts) != 2 {
|
||||
t.Fatal("return wrong results length")
|
||||
}
|
||||
|
||||
if cposts[0].PostId != o1.Id {
|
||||
t.Fatal("Wrong sort")
|
||||
}
|
||||
}
|
||||
|
||||
cr6 := &model.Compliance{Desc: "test" + model.NewId(), StartAt: o1.CreateAt - 1, EndAt: o2a.CreateAt + 1, Emails: u2.Email + ", " + u1.Email, Keywords: o2a.Message + " " + o1.Message}
|
||||
if r1 := <-ss.Compliance().ComplianceExport(cr6); r1.Err != nil {
|
||||
t.Fatal(r1.Err)
|
||||
} else {
|
||||
cposts := r1.Data.([]*model.CompliancePost)
|
||||
|
||||
if len(cposts) != 2 {
|
||||
t.Fatal("return wrong results length")
|
||||
}
|
||||
|
||||
if cposts[0].PostId != o1.Id {
|
||||
t.Fatal("Wrong sort")
|
||||
}
|
||||
|
||||
if cposts[1].PostId != o2a.Id {
|
||||
t.Fatal("Wrong sort")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestComplianceExportDirectMessages(t *testing.T) {
|
||||
ss := Setup()
|
||||
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
|
||||
t1 := &model.Team{}
|
||||
t1.DisplayName = "DisplayName"
|
||||
t1.Name = "zz" + model.NewId() + "b"
|
||||
t1.Email = model.NewId() + "@nowhere.com"
|
||||
t1.Type = model.TEAM_OPEN
|
||||
t1 = store.Must(ss.Team().Save(t1)).(*model.Team)
|
||||
|
||||
u1 := &model.User{}
|
||||
u1.Email = model.NewId()
|
||||
u1.Username = model.NewId()
|
||||
u1 = store.Must(ss.User().Save(u1)).(*model.User)
|
||||
store.Must(ss.Team().SaveMember(&model.TeamMember{TeamId: t1.Id, UserId: u1.Id}))
|
||||
|
||||
u2 := &model.User{}
|
||||
u2.Email = model.NewId()
|
||||
u2.Username = model.NewId()
|
||||
u2 = store.Must(ss.User().Save(u2)).(*model.User)
|
||||
store.Must(ss.Team().SaveMember(&model.TeamMember{TeamId: t1.Id, UserId: u2.Id}))
|
||||
|
||||
c1 := &model.Channel{}
|
||||
c1.TeamId = t1.Id
|
||||
c1.DisplayName = "Channel2"
|
||||
c1.Name = "zz" + model.NewId() + "b"
|
||||
c1.Type = model.CHANNEL_OPEN
|
||||
c1 = store.Must(ss.Channel().Save(c1)).(*model.Channel)
|
||||
|
||||
cDM := store.Must(ss.Channel().CreateDirectChannel(u1.Id, u2.Id)).(*model.Channel)
|
||||
|
||||
o1 := &model.Post{}
|
||||
o1.ChannelId = c1.Id
|
||||
o1.UserId = u1.Id
|
||||
o1.CreateAt = model.GetMillis()
|
||||
o1.Message = "zz" + model.NewId() + "b"
|
||||
o1 = store.Must(ss.Post().Save(o1)).(*model.Post)
|
||||
|
||||
o1a := &model.Post{}
|
||||
o1a.ChannelId = c1.Id
|
||||
o1a.UserId = u1.Id
|
||||
o1a.CreateAt = o1.CreateAt + 10
|
||||
o1a.Message = "zz" + model.NewId() + "b"
|
||||
o1a = store.Must(ss.Post().Save(o1a)).(*model.Post)
|
||||
|
||||
o2 := &model.Post{}
|
||||
o2.ChannelId = c1.Id
|
||||
o2.UserId = u1.Id
|
||||
o2.CreateAt = o1.CreateAt + 20
|
||||
o2.Message = "zz" + model.NewId() + "b"
|
||||
o2 = store.Must(ss.Post().Save(o2)).(*model.Post)
|
||||
|
||||
o2a := &model.Post{}
|
||||
o2a.ChannelId = c1.Id
|
||||
o2a.UserId = u2.Id
|
||||
o2a.CreateAt = o1.CreateAt + 30
|
||||
o2a.Message = "zz" + model.NewId() + "b"
|
||||
o2a = store.Must(ss.Post().Save(o2a)).(*model.Post)
|
||||
|
||||
o3 := &model.Post{}
|
||||
o3.ChannelId = cDM.Id
|
||||
o3.UserId = u1.Id
|
||||
o3.CreateAt = o1.CreateAt + 40
|
||||
o3.Message = "zz" + model.NewId() + "b"
|
||||
o3 = store.Must(ss.Post().Save(o3)).(*model.Post)
|
||||
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
|
||||
cr1 := &model.Compliance{Desc: "test" + model.NewId(), StartAt: o1.CreateAt - 1, EndAt: o3.CreateAt + 1, Emails: u1.Email}
|
||||
if r1 := <-ss.Compliance().ComplianceExport(cr1); r1.Err != nil {
|
||||
t.Fatal(r1.Err)
|
||||
} else {
|
||||
cposts := r1.Data.([]*model.CompliancePost)
|
||||
|
||||
if len(cposts) != 4 {
|
||||
t.Fatal("return wrong results length")
|
||||
}
|
||||
|
||||
if cposts[0].PostId != o1.Id {
|
||||
t.Fatal("Wrong sort")
|
||||
}
|
||||
|
||||
if cposts[len(cposts)-1].PostId != o3.Id {
|
||||
t.Fatal("Wrong sort")
|
||||
}
|
||||
}
|
||||
}
|
||||
212
store/sqlstore/emoji_store.go
Обычный файл
212
store/sqlstore/emoji_store.go
Обычный файл
@@ -0,0 +1,212 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See License.txt for license information.
|
||||
|
||||
package sqlstore
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/mattermost/mattermost-server/einterfaces"
|
||||
"github.com/mattermost/mattermost-server/model"
|
||||
"github.com/mattermost/mattermost-server/store"
|
||||
"github.com/mattermost/mattermost-server/utils"
|
||||
)
|
||||
|
||||
const (
|
||||
EMOJI_CACHE_SIZE = 5000
|
||||
EMOJI_CACHE_SEC = 1800 // 30 mins
|
||||
)
|
||||
|
||||
var emojiCache *utils.Cache = utils.NewLru(EMOJI_CACHE_SIZE)
|
||||
|
||||
type SqlEmojiStore struct {
|
||||
SqlStore
|
||||
metrics einterfaces.MetricsInterface
|
||||
}
|
||||
|
||||
func NewSqlEmojiStore(sqlStore SqlStore, metrics einterfaces.MetricsInterface) store.EmojiStore {
|
||||
s := &SqlEmojiStore{
|
||||
SqlStore: sqlStore,
|
||||
metrics: metrics,
|
||||
}
|
||||
|
||||
for _, db := range sqlStore.GetAllConns() {
|
||||
table := db.AddTableWithName(model.Emoji{}, "Emoji").SetKeys(false, "Id")
|
||||
table.ColMap("Id").SetMaxSize(26)
|
||||
table.ColMap("CreatorId").SetMaxSize(26)
|
||||
table.ColMap("Name").SetMaxSize(64)
|
||||
|
||||
table.SetUniqueTogether("Name", "DeleteAt")
|
||||
}
|
||||
|
||||
return s
|
||||
}
|
||||
|
||||
func (es SqlEmojiStore) CreateIndexesIfNotExists() {
|
||||
es.CreateIndexIfNotExists("idx_emoji_update_at", "Emoji", "UpdateAt")
|
||||
es.CreateIndexIfNotExists("idx_emoji_create_at", "Emoji", "CreateAt")
|
||||
es.CreateIndexIfNotExists("idx_emoji_delete_at", "Emoji", "DeleteAt")
|
||||
}
|
||||
|
||||
func (es SqlEmojiStore) Save(emoji *model.Emoji) store.StoreChannel {
|
||||
storeChannel := make(store.StoreChannel, 1)
|
||||
|
||||
go func() {
|
||||
result := store.StoreResult{}
|
||||
|
||||
emoji.PreSave()
|
||||
if result.Err = emoji.IsValid(); result.Err != nil {
|
||||
storeChannel <- result
|
||||
close(storeChannel)
|
||||
return
|
||||
}
|
||||
|
||||
if err := es.GetMaster().Insert(emoji); err != nil {
|
||||
result.Err = model.NewAppError("SqlEmojiStore.Save", "store.sql_emoji.save.app_error", nil, "id="+emoji.Id+", "+err.Error(), http.StatusInternalServerError)
|
||||
} else {
|
||||
result.Data = emoji
|
||||
}
|
||||
|
||||
storeChannel <- result
|
||||
close(storeChannel)
|
||||
}()
|
||||
|
||||
return storeChannel
|
||||
}
|
||||
|
||||
func (es SqlEmojiStore) Get(id string, allowFromCache bool) store.StoreChannel {
|
||||
storeChannel := make(store.StoreChannel, 1)
|
||||
|
||||
go func() {
|
||||
result := store.StoreResult{}
|
||||
|
||||
if allowFromCache {
|
||||
if cacheItem, ok := emojiCache.Get(id); ok {
|
||||
if es.metrics != nil {
|
||||
es.metrics.IncrementMemCacheHitCounter("Emoji")
|
||||
}
|
||||
result.Data = cacheItem.(*model.Emoji)
|
||||
storeChannel <- result
|
||||
close(storeChannel)
|
||||
return
|
||||
} else {
|
||||
if es.metrics != nil {
|
||||
es.metrics.IncrementMemCacheMissCounter("Emoji")
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if es.metrics != nil {
|
||||
es.metrics.IncrementMemCacheMissCounter("Emoji")
|
||||
}
|
||||
}
|
||||
|
||||
var emoji *model.Emoji
|
||||
|
||||
if err := es.GetReplica().SelectOne(&emoji,
|
||||
`SELECT
|
||||
*
|
||||
FROM
|
||||
Emoji
|
||||
WHERE
|
||||
Id = :Id
|
||||
AND DeleteAt = 0`, map[string]interface{}{"Id": id}); err != nil {
|
||||
result.Err = model.NewAppError("SqlEmojiStore.Get", "store.sql_emoji.get.app_error", nil, "id="+id+", "+err.Error(), http.StatusNotFound)
|
||||
} else {
|
||||
result.Data = emoji
|
||||
|
||||
if allowFromCache {
|
||||
emojiCache.AddWithExpiresInSecs(id, emoji, EMOJI_CACHE_SEC)
|
||||
}
|
||||
}
|
||||
|
||||
storeChannel <- result
|
||||
close(storeChannel)
|
||||
}()
|
||||
|
||||
return storeChannel
|
||||
}
|
||||
|
||||
func (es SqlEmojiStore) GetByName(name string) store.StoreChannel {
|
||||
storeChannel := make(store.StoreChannel, 1)
|
||||
|
||||
go func() {
|
||||
result := store.StoreResult{}
|
||||
|
||||
var emoji *model.Emoji
|
||||
|
||||
if err := es.GetReplica().SelectOne(&emoji,
|
||||
`SELECT
|
||||
*
|
||||
FROM
|
||||
Emoji
|
||||
WHERE
|
||||
Name = :Name
|
||||
AND DeleteAt = 0`, map[string]interface{}{"Name": name}); err != nil {
|
||||
result.Err = model.NewAppError("SqlEmojiStore.GetByName", "store.sql_emoji.get_by_name.app_error", nil, "name="+name+", "+err.Error(), http.StatusInternalServerError)
|
||||
} else {
|
||||
result.Data = emoji
|
||||
}
|
||||
|
||||
storeChannel <- result
|
||||
close(storeChannel)
|
||||
}()
|
||||
|
||||
return storeChannel
|
||||
}
|
||||
|
||||
func (es SqlEmojiStore) GetList(offset, limit int) store.StoreChannel {
|
||||
storeChannel := make(store.StoreChannel, 1)
|
||||
|
||||
go func() {
|
||||
result := store.StoreResult{}
|
||||
|
||||
var emoji []*model.Emoji
|
||||
|
||||
if _, err := es.GetReplica().Select(&emoji,
|
||||
`SELECT
|
||||
*
|
||||
FROM
|
||||
Emoji
|
||||
WHERE
|
||||
DeleteAt = 0
|
||||
LIMIT :Limit OFFSET :Offset`, map[string]interface{}{"Offset": offset, "Limit": limit}); err != nil {
|
||||
result.Err = model.NewAppError("SqlEmojiStore.GetList", "store.sql_emoji.get_all.app_error", nil, err.Error(), http.StatusInternalServerError)
|
||||
} else {
|
||||
result.Data = emoji
|
||||
}
|
||||
|
||||
storeChannel <- result
|
||||
close(storeChannel)
|
||||
}()
|
||||
|
||||
return storeChannel
|
||||
}
|
||||
|
||||
func (es SqlEmojiStore) Delete(id string, time int64) store.StoreChannel {
|
||||
storeChannel := make(store.StoreChannel, 1)
|
||||
|
||||
go func() {
|
||||
result := store.StoreResult{}
|
||||
|
||||
if sqlResult, err := es.GetMaster().Exec(
|
||||
`Update
|
||||
Emoji
|
||||
SET
|
||||
DeleteAt = :DeleteAt,
|
||||
UpdateAt = :UpdateAt
|
||||
WHERE
|
||||
Id = :Id
|
||||
AND DeleteAt = 0`, map[string]interface{}{"DeleteAt": time, "UpdateAt": time, "Id": id}); err != nil {
|
||||
result.Err = model.NewAppError("SqlEmojiStore.Delete", "store.sql_emoji.delete.app_error", nil, "id="+id+", err="+err.Error(), http.StatusInternalServerError)
|
||||
} else if rows, _ := sqlResult.RowsAffected(); rows == 0 {
|
||||
result.Err = model.NewAppError("SqlEmojiStore.Delete", "store.sql_emoji.delete.no_results", nil, "id="+id+", err="+err.Error(), http.StatusBadRequest)
|
||||
}
|
||||
|
||||
emojiCache.Remove(id)
|
||||
|
||||
storeChannel <- result
|
||||
close(storeChannel)
|
||||
}()
|
||||
|
||||
return storeChannel
|
||||
}
|
||||
176
store/sqlstore/emoji_store_test.go
Обычный файл
176
store/sqlstore/emoji_store_test.go
Обычный файл
@@ -0,0 +1,176 @@
|
||||
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
|
||||
// See License.txt for license information.
|
||||
|
||||
package sqlstore
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/mattermost/mattermost-server/model"
|
||||
"github.com/mattermost/mattermost-server/store"
|
||||
)
|
||||
|
||||
func TestEmojiSaveDelete(t *testing.T) {
|
||||
ss := Setup()
|
||||
|
||||
emoji1 := &model.Emoji{
|
||||
CreatorId: model.NewId(),
|
||||
Name: model.NewId(),
|
||||
}
|
||||
|
||||
if result := <-ss.Emoji().Save(emoji1); result.Err != nil {
|
||||
t.Fatal(result.Err)
|
||||
}
|
||||
|
||||
if len(emoji1.Id) != 26 {
|
||||
t.Fatal("should've set id for emoji")
|
||||
}
|
||||
|
||||
emoji2 := model.Emoji{
|
||||
CreatorId: model.NewId(),
|
||||
Name: emoji1.Name,
|
||||
}
|
||||
if result := <-ss.Emoji().Save(&emoji2); result.Err == nil {
|
||||
t.Fatal("shouldn't be able to save emoji with duplicate name")
|
||||
}
|
||||
|
||||
if result := <-ss.Emoji().Delete(emoji1.Id, time.Now().Unix()); result.Err != nil {
|
||||
t.Fatal(result.Err)
|
||||
}
|
||||
|
||||
if result := <-ss.Emoji().Save(&emoji2); result.Err != nil {
|
||||
t.Fatal("should be able to save emoji with duplicate name now that original has been deleted", result.Err)
|
||||
}
|
||||
|
||||
if result := <-ss.Emoji().Delete(emoji2.Id, time.Now().Unix()+1); result.Err != nil {
|
||||
t.Fatal(result.Err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestEmojiGet(t *testing.T) {
|
||||
ss := Setup()
|
||||
|
||||
emojis := []model.Emoji{
|
||||
{
|
||||
CreatorId: model.NewId(),
|
||||
Name: model.NewId(),
|
||||
},
|
||||
{
|
||||
CreatorId: model.NewId(),
|
||||
Name: model.NewId(),
|
||||
},
|
||||
{
|
||||
CreatorId: model.NewId(),
|
||||
Name: model.NewId(),
|
||||
},
|
||||
}
|
||||
|
||||
for i, emoji := range emojis {
|
||||
emojis[i] = *store.Must(ss.Emoji().Save(&emoji)).(*model.Emoji)
|
||||
}
|
||||
defer func() {
|
||||
for _, emoji := range emojis {
|
||||
store.Must(ss.Emoji().Delete(emoji.Id, time.Now().Unix()))
|
||||
}
|
||||
}()
|
||||
|
||||
for _, emoji := range emojis {
|
||||
if result := <-ss.Emoji().Get(emoji.Id, false); result.Err != nil {
|
||||
t.Fatalf("failed to get emoji with id %v: %v", emoji.Id, result.Err)
|
||||
}
|
||||
}
|
||||
|
||||
for _, emoji := range emojis {
|
||||
if result := <-ss.Emoji().Get(emoji.Id, true); result.Err != nil {
|
||||
t.Fatalf("failed to get emoji with id %v: %v", emoji.Id, result.Err)
|
||||
}
|
||||
}
|
||||
|
||||
for _, emoji := range emojis {
|
||||
if result := <-ss.Emoji().Get(emoji.Id, true); result.Err != nil {
|
||||
t.Fatalf("failed to get emoji with id %v: %v", emoji.Id, result.Err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestEmojiGetByName(t *testing.T) {
|
||||
ss := Setup()
|
||||
|
||||
emojis := []model.Emoji{
|
||||
{
|
||||
CreatorId: model.NewId(),
|
||||
Name: model.NewId(),
|
||||
},
|
||||
{
|
||||
CreatorId: model.NewId(),
|
||||
Name: model.NewId(),
|
||||
},
|
||||
{
|
||||
CreatorId: model.NewId(),
|
||||
Name: model.NewId(),
|
||||
},
|
||||
}
|
||||
|
||||
for i, emoji := range emojis {
|
||||
emojis[i] = *store.Must(ss.Emoji().Save(&emoji)).(*model.Emoji)
|
||||
}
|
||||
defer func() {
|
||||
for _, emoji := range emojis {
|
||||
store.Must(ss.Emoji().Delete(emoji.Id, time.Now().Unix()))
|
||||
}
|
||||
}()
|
||||
|
||||
for _, emoji := range emojis {
|
||||
if result := <-ss.Emoji().GetByName(emoji.Name); result.Err != nil {
|
||||
t.Fatalf("failed to get emoji with name %v: %v", emoji.Name, result.Err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestEmojiGetList(t *testing.T) {
|
||||
ss := Setup()
|
||||
|
||||
emojis := []model.Emoji{
|
||||
{
|
||||
CreatorId: model.NewId(),
|
||||
Name: model.NewId(),
|
||||
},
|
||||
{
|
||||
CreatorId: model.NewId(),
|
||||
Name: model.NewId(),
|
||||
},
|
||||
{
|
||||
CreatorId: model.NewId(),
|
||||
Name: model.NewId(),
|
||||
},
|
||||
}
|
||||
|
||||
for i, emoji := range emojis {
|
||||
emojis[i] = *store.Must(ss.Emoji().Save(&emoji)).(*model.Emoji)
|
||||
}
|
||||
defer func() {
|
||||
for _, emoji := range emojis {
|
||||
store.Must(ss.Emoji().Delete(emoji.Id, time.Now().Unix()))
|
||||
}
|
||||
}()
|
||||
|
||||
if result := <-ss.Emoji().GetList(0, 100); result.Err != nil {
|
||||
t.Fatal(result.Err)
|
||||
} else {
|
||||
for _, emoji := range emojis {
|
||||
found := false
|
||||
|
||||
for _, savedEmoji := range result.Data.([]*model.Emoji) {
|
||||
if emoji.Id == savedEmoji.Id {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if !found {
|
||||
t.Fatalf("failed to get emoji with id %v", emoji.Id)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
317
store/sqlstore/file_info_store.go
Обычный файл
317
store/sqlstore/file_info_store.go
Обычный файл
@@ -0,0 +1,317 @@
|
||||
// See License.txt for license information.
|
||||
|
||||
package sqlstore
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"net/http"
|
||||
|
||||
"github.com/mattermost/mattermost-server/einterfaces"
|
||||
"github.com/mattermost/mattermost-server/model"
|
||||
"github.com/mattermost/mattermost-server/store"
|
||||
"github.com/mattermost/mattermost-server/utils"
|
||||
)
|
||||
|
||||
type SqlFileInfoStore struct {
|
||||
SqlStore
|
||||
metrics einterfaces.MetricsInterface
|
||||
}
|
||||
|
||||
const (
|
||||
FILE_INFO_CACHE_SIZE = 25000
|
||||
FILE_INFO_CACHE_SEC = 1800 // 30 minutes
|
||||
)
|
||||
|
||||
var fileInfoCache *utils.Cache = utils.NewLru(FILE_INFO_CACHE_SIZE)
|
||||
|
||||
func ClearFileCaches() {
|
||||
fileInfoCache.Purge()
|
||||
}
|
||||
|
||||
func NewSqlFileInfoStore(sqlStore SqlStore, metrics einterfaces.MetricsInterface) store.FileInfoStore {
|
||||
s := &SqlFileInfoStore{
|
||||
SqlStore: sqlStore,
|
||||
metrics: metrics,
|
||||
}
|
||||
|
||||
for _, db := range sqlStore.GetAllConns() {
|
||||
table := db.AddTableWithName(model.FileInfo{}, "FileInfo").SetKeys(false, "Id")
|
||||
table.ColMap("Id").SetMaxSize(26)
|
||||
table.ColMap("CreatorId").SetMaxSize(26)
|
||||
table.ColMap("PostId").SetMaxSize(26)
|
||||
table.ColMap("Path").SetMaxSize(512)
|
||||
table.ColMap("ThumbnailPath").SetMaxSize(512)
|
||||
table.ColMap("PreviewPath").SetMaxSize(512)
|
||||
table.ColMap("Name").SetMaxSize(256)
|
||||
table.ColMap("Extension").SetMaxSize(64)
|
||||
table.ColMap("MimeType").SetMaxSize(256)
|
||||
}
|
||||
|
||||
return s
|
||||
}
|
||||
|
||||
func (fs SqlFileInfoStore) CreateIndexesIfNotExists() {
|
||||
fs.CreateIndexIfNotExists("idx_fileinfo_update_at", "FileInfo", "UpdateAt")
|
||||
fs.CreateIndexIfNotExists("idx_fileinfo_create_at", "FileInfo", "CreateAt")
|
||||
fs.CreateIndexIfNotExists("idx_fileinfo_delete_at", "FileInfo", "DeleteAt")
|
||||
fs.CreateIndexIfNotExists("idx_fileinfo_postid_at", "FileInfo", "PostId")
|
||||
}
|
||||
|
||||
func (fs SqlFileInfoStore) Save(info *model.FileInfo) store.StoreChannel {
|
||||
storeChannel := make(store.StoreChannel, 1)
|
||||
|
||||
go func() {
|
||||
result := store.StoreResult{}
|
||||
|
||||
info.PreSave()
|
||||
if result.Err = info.IsValid(); result.Err != nil {
|
||||
storeChannel <- result
|
||||
close(storeChannel)
|
||||
return
|
||||
}
|
||||
|
||||
if err := fs.GetMaster().Insert(info); err != nil {
|
||||
result.Err = model.NewAppError("SqlFileInfoStore.Save", "store.sql_file_info.save.app_error", nil, err.Error(), http.StatusInternalServerError)
|
||||
} else {
|
||||
result.Data = info
|
||||
}
|
||||
|
||||
storeChannel <- result
|
||||
close(storeChannel)
|
||||
}()
|
||||
|
||||
return storeChannel
|
||||
}
|
||||
|
||||
func (fs SqlFileInfoStore) Get(id string) store.StoreChannel {
|
||||
storeChannel := make(store.StoreChannel, 1)
|
||||
|
||||
go func() {
|
||||
result := store.StoreResult{}
|
||||
|
||||
info := &model.FileInfo{}
|
||||
|
||||
if err := fs.GetReplica().SelectOne(info,
|
||||
`SELECT
|
||||
*
|
||||
FROM
|
||||
FileInfo
|
||||
WHERE
|
||||
Id = :Id
|
||||
AND DeleteAt = 0`, map[string]interface{}{"Id": id}); err != nil {
|
||||
if err == sql.ErrNoRows {
|
||||
result.Err = model.NewAppError("SqlFileInfoStore.Get", "store.sql_file_info.get.app_error", nil, "id="+id+", "+err.Error(), http.StatusNotFound)
|
||||
} else {
|
||||
result.Err = model.NewAppError("SqlFileInfoStore.Get", "store.sql_file_info.get.app_error", nil, "id="+id+", "+err.Error(), http.StatusInternalServerError)
|
||||
}
|
||||
} else {
|
||||
result.Data = info
|
||||
}
|
||||
|
||||
storeChannel <- result
|
||||
close(storeChannel)
|
||||
}()
|
||||
|
||||
return storeChannel
|
||||
}
|
||||
|
||||
func (fs SqlFileInfoStore) GetByPath(path string) store.StoreChannel {
|
||||
storeChannel := make(store.StoreChannel, 1)
|
||||
|
||||
go func() {
|
||||
result := store.StoreResult{}
|
||||
|
||||
info := &model.FileInfo{}
|
||||
|
||||
if err := fs.GetReplica().SelectOne(info,
|
||||
`SELECT
|
||||
*
|
||||
FROM
|
||||
FileInfo
|
||||
WHERE
|
||||
Path = :Path
|
||||
AND DeleteAt = 0
|
||||
LIMIT 1`, map[string]interface{}{"Path": path}); err != nil {
|
||||
result.Err = model.NewAppError("SqlFileInfoStore.GetByPath", "store.sql_file_info.get_by_path.app_error", nil, "path="+path+", "+err.Error(), http.StatusInternalServerError)
|
||||
} else {
|
||||
result.Data = info
|
||||
}
|
||||
|
||||
storeChannel <- result
|
||||
close(storeChannel)
|
||||
}()
|
||||
|
||||
return storeChannel
|
||||
}
|
||||
|
||||
func (fs SqlFileInfoStore) InvalidateFileInfosForPostCache(postId string) {
|
||||
fileInfoCache.Remove(postId)
|
||||
}
|
||||
|
||||
func (fs SqlFileInfoStore) GetForPost(postId string, readFromMaster bool, allowFromCache bool) store.StoreChannel {
|
||||
storeChannel := make(store.StoreChannel, 1)
|
||||
|
||||
go func() {
|
||||
result := store.StoreResult{}
|
||||
|
||||
if allowFromCache {
|
||||
if cacheItem, ok := fileInfoCache.Get(postId); ok {
|
||||
if fs.metrics != nil {
|
||||
fs.metrics.IncrementMemCacheHitCounter("File Info Cache")
|
||||
}
|
||||
|
||||
result.Data = cacheItem.([]*model.FileInfo)
|
||||
storeChannel <- result
|
||||
close(storeChannel)
|
||||
return
|
||||
} else {
|
||||
if fs.metrics != nil {
|
||||
fs.metrics.IncrementMemCacheMissCounter("File Info Cache")
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if fs.metrics != nil {
|
||||
fs.metrics.IncrementMemCacheMissCounter("File Info Cache")
|
||||
}
|
||||
}
|
||||
|
||||
var infos []*model.FileInfo
|
||||
|
||||
dbmap := fs.GetReplica()
|
||||
|
||||
if readFromMaster {
|
||||
dbmap = fs.GetMaster()
|
||||
}
|
||||
|
||||
if _, err := dbmap.Select(&infos,
|
||||
`SELECT
|
||||
*
|
||||
FROM
|
||||
FileInfo
|
||||
WHERE
|
||||
PostId = :PostId
|
||||
AND DeleteAt = 0
|
||||
ORDER BY
|
||||
CreateAt`, map[string]interface{}{"PostId": postId}); err != nil {
|
||||
result.Err = model.NewAppError("SqlFileInfoStore.GetForPost",
|
||||
"store.sql_file_info.get_for_post.app_error", nil, "post_id="+postId+", "+err.Error(), http.StatusInternalServerError)
|
||||
} else {
|
||||
if len(infos) > 0 {
|
||||
fileInfoCache.AddWithExpiresInSecs(postId, infos, FILE_INFO_CACHE_SEC)
|
||||
}
|
||||
|
||||
result.Data = infos
|
||||
}
|
||||
|
||||
storeChannel <- result
|
||||
close(storeChannel)
|
||||
}()
|
||||
|
||||
return storeChannel
|
||||
}
|
||||
|
||||
func (fs SqlFileInfoStore) AttachToPost(fileId, postId string) store.StoreChannel {
|
||||
storeChannel := make(store.StoreChannel, 1)
|
||||
|
||||
go func() {
|
||||
result := store.StoreResult{}
|
||||
|
||||
if _, err := fs.GetMaster().Exec(
|
||||
`UPDATE
|
||||
FileInfo
|
||||
SET
|
||||
PostId = :PostId
|
||||
WHERE
|
||||
Id = :Id
|
||||
AND PostId = ''`, map[string]interface{}{"PostId": postId, "Id": fileId}); err != nil {
|
||||
result.Err = model.NewAppError("SqlFileInfoStore.AttachToPost",
|
||||
"store.sql_file_info.attach_to_post.app_error", nil, "post_id="+postId+", file_id="+fileId+", err="+err.Error(), http.StatusInternalServerError)
|
||||
}
|
||||
|
||||
storeChannel <- result
|
||||
close(storeChannel)
|
||||
}()
|
||||
|
||||
return storeChannel
|
||||
}
|
||||
|
||||
func (fs SqlFileInfoStore) DeleteForPost(postId string) store.StoreChannel {
|
||||
storeChannel := make(store.StoreChannel, 1)
|
||||
|
||||
go func() {
|
||||
result := store.StoreResult{}
|
||||
|
||||
if _, err := fs.GetMaster().Exec(
|
||||
`UPDATE
|
||||
FileInfo
|
||||
SET
|
||||
DeleteAt = :DeleteAt
|
||||
WHERE
|
||||
PostId = :PostId`, map[string]interface{}{"DeleteAt": model.GetMillis(), "PostId": postId}); err != nil {
|
||||
result.Err = model.NewAppError("SqlFileInfoStore.DeleteForPost",
|
||||
"store.sql_file_info.delete_for_post.app_error", nil, "post_id="+postId+", err="+err.Error(), http.StatusInternalServerError)
|
||||
} else {
|
||||
result.Data = postId
|
||||
}
|
||||
|
||||
storeChannel <- result
|
||||
close(storeChannel)
|
||||
}()
|
||||
|
||||
return storeChannel
|
||||
}
|
||||
|
||||
func (fs SqlFileInfoStore) PermanentDelete(fileId string) store.StoreChannel {
|
||||
storeChannel := make(store.StoreChannel, 1)
|
||||
|
||||
go func() {
|
||||
result := store.StoreResult{}
|
||||
|
||||
if _, err := fs.GetMaster().Exec(
|
||||
`DELETE FROM
|
||||
FileInfo
|
||||
WHERE
|
||||
Id = :FileId`, map[string]interface{}{"FileId": fileId}); err != nil {
|
||||
result.Err = model.NewAppError("SqlFileInfoStore.PermanentDelete",
|
||||
"store.sql_file_info.permanent_delete.app_error", nil, "file_id="+fileId+", err="+err.Error(), http.StatusInternalServerError)
|
||||
}
|
||||
|
||||
storeChannel <- result
|
||||
close(storeChannel)
|
||||
}()
|
||||
|
||||
return storeChannel
|
||||
}
|
||||
|
||||
func (s SqlFileInfoStore) PermanentDeleteBatch(endTime int64, limit int64) store.StoreChannel {
|
||||
storeChannel := make(store.StoreChannel, 1)
|
||||
|
||||
go func() {
|
||||
result := store.StoreResult{}
|
||||
|
||||
var query string
|
||||
if *utils.Cfg.SqlSettings.DriverName == "postgres" {
|
||||
query = "DELETE from FileInfo WHERE Id = any (array (SELECT Id FROM FileInfo WHERE CreateAt < :EndTime LIMIT :Limit))"
|
||||
} else {
|
||||
query = "DELETE from FileInfo WHERE CreateAt < :EndTime LIMIT :Limit"
|
||||
}
|
||||
|
||||
sqlResult, err := s.GetMaster().Exec(query, map[string]interface{}{"EndTime": endTime, "Limit": limit})
|
||||
if err != nil {
|
||||
result.Err = model.NewAppError("SqlFileInfoStore.PermanentDeleteBatch", "store.sql_file_info.permanent_delete_batch.app_error", nil, ""+err.Error(), http.StatusInternalServerError)
|
||||
} else {
|
||||
rowsAffected, err1 := sqlResult.RowsAffected()
|
||||
if err1 != nil {
|
||||
result.Err = model.NewAppError("SqlFileInfoStore.PermanentDeleteBatch", "store.sql_file_info.permanent_delete_batch.app_error", nil, ""+err.Error(), http.StatusInternalServerError)
|
||||
result.Data = int64(0)
|
||||
} else {
|
||||
result.Data = rowsAffected
|
||||
}
|
||||
}
|
||||
|
||||
storeChannel <- result
|
||||
close(storeChannel)
|
||||
}()
|
||||
|
||||
return storeChannel
|
||||
}
|
||||
300
store/sqlstore/file_info_store_test.go
Обычный файл
300
store/sqlstore/file_info_store_test.go
Обычный файл
@@ -0,0 +1,300 @@
|
||||
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
|
||||
// See License.txt for license information.
|
||||
|
||||
package sqlstore
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/mattermost/mattermost-server/model"
|
||||
"github.com/mattermost/mattermost-server/store"
|
||||
)
|
||||
|
||||
func TestFileInfoSaveGet(t *testing.T) {
|
||||
ss := Setup()
|
||||
|
||||
info := &model.FileInfo{
|
||||
CreatorId: model.NewId(),
|
||||
Path: "file.txt",
|
||||
}
|
||||
|
||||
if result := <-ss.FileInfo().Save(info); result.Err != nil {
|
||||
t.Fatal(result.Err)
|
||||
} else if returned := result.Data.(*model.FileInfo); len(returned.Id) == 0 {
|
||||
t.Fatal("should've assigned an id to FileInfo")
|
||||
} else {
|
||||
info = returned
|
||||
}
|
||||
defer func() {
|
||||
<-ss.FileInfo().PermanentDelete(info.Id)
|
||||
}()
|
||||
|
||||
if result := <-ss.FileInfo().Get(info.Id); result.Err != nil {
|
||||
t.Fatal(result.Err)
|
||||
} else if returned := result.Data.(*model.FileInfo); returned.Id != info.Id {
|
||||
t.Log(info)
|
||||
t.Log(returned)
|
||||
t.Fatal("should've returned correct FileInfo")
|
||||
}
|
||||
|
||||
info2 := store.Must(ss.FileInfo().Save(&model.FileInfo{
|
||||
CreatorId: model.NewId(),
|
||||
Path: "file.txt",
|
||||
DeleteAt: 123,
|
||||
})).(*model.FileInfo)
|
||||
|
||||
if result := <-ss.FileInfo().Get(info2.Id); result.Err == nil {
|
||||
t.Fatal("shouldn't have gotten deleted file")
|
||||
}
|
||||
defer func() {
|
||||
<-ss.FileInfo().PermanentDelete(info2.Id)
|
||||
}()
|
||||
}
|
||||
|
||||
func TestFileInfoSaveGetByPath(t *testing.T) {
|
||||
ss := Setup()
|
||||
|
||||
info := &model.FileInfo{
|
||||
CreatorId: model.NewId(),
|
||||
Path: fmt.Sprintf("%v/file.txt", model.NewId()),
|
||||
}
|
||||
|
||||
if result := <-ss.FileInfo().Save(info); result.Err != nil {
|
||||
t.Fatal(result.Err)
|
||||
} else if returned := result.Data.(*model.FileInfo); len(returned.Id) == 0 {
|
||||
t.Fatal("should've assigned an id to FileInfo")
|
||||
} else {
|
||||
info = returned
|
||||
}
|
||||
defer func() {
|
||||
<-ss.FileInfo().PermanentDelete(info.Id)
|
||||
}()
|
||||
|
||||
if result := <-ss.FileInfo().GetByPath(info.Path); result.Err != nil {
|
||||
t.Fatal(result.Err)
|
||||
} else if returned := result.Data.(*model.FileInfo); returned.Id != info.Id {
|
||||
t.Log(info)
|
||||
t.Log(returned)
|
||||
t.Fatal("should've returned correct FileInfo")
|
||||
}
|
||||
|
||||
info2 := store.Must(ss.FileInfo().Save(&model.FileInfo{
|
||||
CreatorId: model.NewId(),
|
||||
Path: "file.txt",
|
||||
DeleteAt: 123,
|
||||
})).(*model.FileInfo)
|
||||
|
||||
if result := <-ss.FileInfo().GetByPath(info2.Id); result.Err == nil {
|
||||
t.Fatal("shouldn't have gotten deleted file")
|
||||
}
|
||||
defer func() {
|
||||
<-ss.FileInfo().PermanentDelete(info2.Id)
|
||||
}()
|
||||
}
|
||||
|
||||
func TestFileInfoGetForPost(t *testing.T) {
|
||||
ss := Setup()
|
||||
|
||||
userId := model.NewId()
|
||||
postId := model.NewId()
|
||||
|
||||
infos := []*model.FileInfo{
|
||||
{
|
||||
PostId: postId,
|
||||
CreatorId: userId,
|
||||
Path: "file.txt",
|
||||
},
|
||||
{
|
||||
PostId: postId,
|
||||
CreatorId: userId,
|
||||
Path: "file.txt",
|
||||
},
|
||||
{
|
||||
PostId: postId,
|
||||
CreatorId: userId,
|
||||
Path: "file.txt",
|
||||
DeleteAt: 123,
|
||||
},
|
||||
{
|
||||
PostId: model.NewId(),
|
||||
CreatorId: userId,
|
||||
Path: "file.txt",
|
||||
},
|
||||
}
|
||||
|
||||
for i, info := range infos {
|
||||
infos[i] = store.Must(ss.FileInfo().Save(info)).(*model.FileInfo)
|
||||
defer func(id string) {
|
||||
<-ss.FileInfo().PermanentDelete(id)
|
||||
}(infos[i].Id)
|
||||
}
|
||||
|
||||
if result := <-ss.FileInfo().GetForPost(postId, true, false); result.Err != nil {
|
||||
t.Fatal(result.Err)
|
||||
} else if returned := result.Data.([]*model.FileInfo); len(returned) != 2 {
|
||||
t.Fatal("should've returned exactly 2 file infos")
|
||||
}
|
||||
|
||||
if result := <-ss.FileInfo().GetForPost(postId, false, false); result.Err != nil {
|
||||
t.Fatal(result.Err)
|
||||
} else if returned := result.Data.([]*model.FileInfo); len(returned) != 2 {
|
||||
t.Fatal("should've returned exactly 2 file infos")
|
||||
}
|
||||
|
||||
if result := <-ss.FileInfo().GetForPost(postId, true, true); result.Err != nil {
|
||||
t.Fatal(result.Err)
|
||||
} else if returned := result.Data.([]*model.FileInfo); len(returned) != 2 {
|
||||
t.Fatal("should've returned exactly 2 file infos")
|
||||
}
|
||||
}
|
||||
|
||||
func TestFileInfoAttachToPost(t *testing.T) {
|
||||
ss := Setup()
|
||||
|
||||
userId := model.NewId()
|
||||
postId := model.NewId()
|
||||
|
||||
info1 := store.Must(ss.FileInfo().Save(&model.FileInfo{
|
||||
CreatorId: userId,
|
||||
Path: "file.txt",
|
||||
})).(*model.FileInfo)
|
||||
defer func() {
|
||||
<-ss.FileInfo().PermanentDelete(info1.Id)
|
||||
}()
|
||||
|
||||
if len(info1.PostId) != 0 {
|
||||
t.Fatal("file shouldn't have a PostId")
|
||||
}
|
||||
|
||||
if result := <-ss.FileInfo().AttachToPost(info1.Id, postId); result.Err != nil {
|
||||
t.Fatal(result.Err)
|
||||
} else {
|
||||
info1 = store.Must(ss.FileInfo().Get(info1.Id)).(*model.FileInfo)
|
||||
}
|
||||
|
||||
if len(info1.PostId) == 0 {
|
||||
t.Fatal("file should now have a PostId")
|
||||
}
|
||||
|
||||
info2 := store.Must(ss.FileInfo().Save(&model.FileInfo{
|
||||
CreatorId: userId,
|
||||
Path: "file.txt",
|
||||
})).(*model.FileInfo)
|
||||
defer func() {
|
||||
<-ss.FileInfo().PermanentDelete(info2.Id)
|
||||
}()
|
||||
|
||||
if result := <-ss.FileInfo().AttachToPost(info2.Id, postId); result.Err != nil {
|
||||
t.Fatal(result.Err)
|
||||
} else {
|
||||
info2 = store.Must(ss.FileInfo().Get(info2.Id)).(*model.FileInfo)
|
||||
}
|
||||
|
||||
if result := <-ss.FileInfo().GetForPost(postId, true, false); result.Err != nil {
|
||||
t.Fatal(result.Err)
|
||||
} else if infos := result.Data.([]*model.FileInfo); len(infos) != 2 {
|
||||
t.Fatal("should've returned exactly 2 file infos")
|
||||
}
|
||||
}
|
||||
|
||||
func TestFileInfoDeleteForPost(t *testing.T) {
|
||||
ss := Setup()
|
||||
|
||||
userId := model.NewId()
|
||||
postId := model.NewId()
|
||||
|
||||
infos := []*model.FileInfo{
|
||||
{
|
||||
PostId: postId,
|
||||
CreatorId: userId,
|
||||
Path: "file.txt",
|
||||
},
|
||||
{
|
||||
PostId: postId,
|
||||
CreatorId: userId,
|
||||
Path: "file.txt",
|
||||
},
|
||||
{
|
||||
PostId: postId,
|
||||
CreatorId: userId,
|
||||
Path: "file.txt",
|
||||
DeleteAt: 123,
|
||||
},
|
||||
{
|
||||
PostId: model.NewId(),
|
||||
CreatorId: userId,
|
||||
Path: "file.txt",
|
||||
},
|
||||
}
|
||||
|
||||
for i, info := range infos {
|
||||
infos[i] = store.Must(ss.FileInfo().Save(info)).(*model.FileInfo)
|
||||
defer func(id string) {
|
||||
<-ss.FileInfo().PermanentDelete(id)
|
||||
}(infos[i].Id)
|
||||
}
|
||||
|
||||
if result := <-ss.FileInfo().DeleteForPost(postId); result.Err != nil {
|
||||
t.Fatal(result.Err)
|
||||
}
|
||||
|
||||
if infos := store.Must(ss.FileInfo().GetForPost(postId, true, false)).([]*model.FileInfo); len(infos) != 0 {
|
||||
t.Fatal("shouldn't have returned any file infos")
|
||||
}
|
||||
}
|
||||
|
||||
func TestFileInfoPermanentDelete(t *testing.T) {
|
||||
ss := Setup()
|
||||
|
||||
info := store.Must(ss.FileInfo().Save(&model.FileInfo{
|
||||
PostId: model.NewId(),
|
||||
CreatorId: model.NewId(),
|
||||
Path: "file.txt",
|
||||
})).(*model.FileInfo)
|
||||
|
||||
if result := <-ss.FileInfo().PermanentDelete(info.Id); result.Err != nil {
|
||||
t.Fatal(result.Err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestFileInfoPermanentDeleteBatch(t *testing.T) {
|
||||
ss := Setup()
|
||||
|
||||
postId := model.NewId()
|
||||
|
||||
store.Must(ss.FileInfo().Save(&model.FileInfo{
|
||||
PostId: postId,
|
||||
CreatorId: model.NewId(),
|
||||
Path: "file.txt",
|
||||
CreateAt: 1000,
|
||||
}))
|
||||
|
||||
store.Must(ss.FileInfo().Save(&model.FileInfo{
|
||||
PostId: postId,
|
||||
CreatorId: model.NewId(),
|
||||
Path: "file.txt",
|
||||
CreateAt: 1200,
|
||||
}))
|
||||
|
||||
store.Must(ss.FileInfo().Save(&model.FileInfo{
|
||||
PostId: postId,
|
||||
CreatorId: model.NewId(),
|
||||
Path: "file.txt",
|
||||
CreateAt: 2000,
|
||||
}))
|
||||
|
||||
if result := <-ss.FileInfo().GetForPost(postId, true, false); result.Err != nil {
|
||||
t.Fatal(result.Err)
|
||||
} else if len(result.Data.([]*model.FileInfo)) != 3 {
|
||||
t.Fatal("Expected 3 fileInfos")
|
||||
}
|
||||
|
||||
store.Must(ss.FileInfo().PermanentDeleteBatch(1500, 1000))
|
||||
|
||||
if result := <-ss.FileInfo().GetForPost(postId, true, false); result.Err != nil {
|
||||
t.Fatal(result.Err)
|
||||
} else if len(result.Data.([]*model.FileInfo)) != 1 {
|
||||
t.Fatal("Expected 3 fileInfos")
|
||||
}
|
||||
}
|
||||
349
store/sqlstore/job_store.go
Обычный файл
349
store/sqlstore/job_store.go
Обычный файл
@@ -0,0 +1,349 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See License.txt for license information.
|
||||
|
||||
package sqlstore
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"net/http"
|
||||
|
||||
"github.com/mattermost/gorp"
|
||||
"github.com/mattermost/mattermost-server/model"
|
||||
"github.com/mattermost/mattermost-server/store"
|
||||
)
|
||||
|
||||
type SqlJobStore struct {
|
||||
SqlStore
|
||||
}
|
||||
|
||||
func NewSqlJobStore(sqlStore SqlStore) store.JobStore {
|
||||
s := &SqlJobStore{sqlStore}
|
||||
|
||||
for _, db := range sqlStore.GetAllConns() {
|
||||
table := db.AddTableWithName(model.Job{}, "Jobs").SetKeys(false, "Id")
|
||||
table.ColMap("Id").SetMaxSize(26)
|
||||
table.ColMap("Type").SetMaxSize(32)
|
||||
table.ColMap("Status").SetMaxSize(32)
|
||||
table.ColMap("Data").SetMaxSize(1024)
|
||||
}
|
||||
|
||||
return s
|
||||
}
|
||||
|
||||
func (jss SqlJobStore) CreateIndexesIfNotExists() {
|
||||
jss.CreateIndexIfNotExists("idx_jobs_type", "Jobs", "Type")
|
||||
}
|
||||
|
||||
func (jss SqlJobStore) Save(job *model.Job) store.StoreChannel {
|
||||
storeChannel := make(store.StoreChannel, 1)
|
||||
|
||||
go func() {
|
||||
result := store.StoreResult{}
|
||||
if err := jss.GetMaster().Insert(job); err != nil {
|
||||
result.Err = model.NewAppError("SqlJobStore.Save", "store.sql_job.save.app_error", nil, "id="+job.Id+", "+err.Error(), http.StatusInternalServerError)
|
||||
} else {
|
||||
result.Data = job
|
||||
}
|
||||
|
||||
storeChannel <- result
|
||||
close(storeChannel)
|
||||
}()
|
||||
|
||||
return storeChannel
|
||||
}
|
||||
|
||||
func (jss SqlJobStore) UpdateOptimistically(job *model.Job, currentStatus string) store.StoreChannel {
|
||||
storeChannel := make(store.StoreChannel, 1)
|
||||
|
||||
go func() {
|
||||
result := store.StoreResult{}
|
||||
|
||||
if sqlResult, err := jss.GetMaster().Exec(
|
||||
`UPDATE
|
||||
Jobs
|
||||
SET
|
||||
LastActivityAt = :LastActivityAt,
|
||||
Status = :Status,
|
||||
Progress = :Progress,
|
||||
Data = :Data
|
||||
WHERE
|
||||
Id = :Id
|
||||
AND
|
||||
Status = :OldStatus`,
|
||||
map[string]interface{}{
|
||||
"Id": job.Id,
|
||||
"OldStatus": currentStatus,
|
||||
"LastActivityAt": model.GetMillis(),
|
||||
"Status": job.Status,
|
||||
"Data": job.DataToJson(),
|
||||
"Progress": job.Progress,
|
||||
}); err != nil {
|
||||
result.Err = model.NewAppError("SqlJobStore.UpdateOptimistically", "store.sql_job.update.app_error", nil, "id="+job.Id+", "+err.Error(), http.StatusInternalServerError)
|
||||
} else {
|
||||
rows, err := sqlResult.RowsAffected()
|
||||
|
||||
if err != nil {
|
||||
result.Err = model.NewAppError("SqlJobStore.UpdateStatus", "store.sql_job.update.app_error", nil, "id="+job.Id+", "+err.Error(), http.StatusInternalServerError)
|
||||
} else {
|
||||
if rows == 1 {
|
||||
result.Data = true
|
||||
} else {
|
||||
result.Data = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
storeChannel <- result
|
||||
close(storeChannel)
|
||||
}()
|
||||
|
||||
return storeChannel
|
||||
}
|
||||
|
||||
func (jss SqlJobStore) UpdateStatus(id string, status string) store.StoreChannel {
|
||||
storeChannel := make(store.StoreChannel, 1)
|
||||
|
||||
go func() {
|
||||
result := store.StoreResult{}
|
||||
|
||||
job := &model.Job{
|
||||
Id: id,
|
||||
Status: status,
|
||||
LastActivityAt: model.GetMillis(),
|
||||
}
|
||||
|
||||
if _, err := jss.GetMaster().UpdateColumns(func(col *gorp.ColumnMap) bool {
|
||||
return col.ColumnName == "Status" || col.ColumnName == "LastActivityAt"
|
||||
}, job); err != nil {
|
||||
result.Err = model.NewAppError("SqlJobStore.UpdateStatus", "store.sql_job.update.app_error", nil, "id="+id+", "+err.Error(), http.StatusInternalServerError)
|
||||
}
|
||||
|
||||
if result.Err == nil {
|
||||
result.Data = job
|
||||
}
|
||||
|
||||
storeChannel <- result
|
||||
close(storeChannel)
|
||||
}()
|
||||
|
||||
return storeChannel
|
||||
}
|
||||
|
||||
func (jss SqlJobStore) UpdateStatusOptimistically(id string, currentStatus string, newStatus string) store.StoreChannel {
|
||||
storeChannel := make(store.StoreChannel, 1)
|
||||
|
||||
go func() {
|
||||
result := store.StoreResult{}
|
||||
|
||||
var startAtClause string
|
||||
if newStatus == model.JOB_STATUS_IN_PROGRESS {
|
||||
startAtClause = `StartAt = :StartAt,`
|
||||
}
|
||||
|
||||
if sqlResult, err := jss.GetMaster().Exec(
|
||||
`UPDATE
|
||||
Jobs
|
||||
SET `+startAtClause+`
|
||||
Status = :NewStatus,
|
||||
LastActivityAt = :LastActivityAt
|
||||
WHERE
|
||||
Id = :Id
|
||||
AND
|
||||
Status = :OldStatus`, map[string]interface{}{"Id": id, "OldStatus": currentStatus, "NewStatus": newStatus, "StartAt": model.GetMillis(), "LastActivityAt": model.GetMillis()}); err != nil {
|
||||
result.Err = model.NewAppError("SqlJobStore.UpdateStatus", "store.sql_job.update.app_error", nil, "id="+id+", "+err.Error(), http.StatusInternalServerError)
|
||||
} else {
|
||||
rows, err := sqlResult.RowsAffected()
|
||||
|
||||
if err != nil {
|
||||
result.Err = model.NewAppError("SqlJobStore.UpdateStatus", "store.sql_job.update.app_error", nil, "id="+id+", "+err.Error(), http.StatusInternalServerError)
|
||||
} else {
|
||||
if rows == 1 {
|
||||
result.Data = true
|
||||
} else {
|
||||
result.Data = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
storeChannel <- result
|
||||
close(storeChannel)
|
||||
}()
|
||||
|
||||
return storeChannel
|
||||
}
|
||||
|
||||
func (jss SqlJobStore) Get(id string) store.StoreChannel {
|
||||
storeChannel := make(store.StoreChannel, 1)
|
||||
|
||||
go func() {
|
||||
result := store.StoreResult{}
|
||||
|
||||
var status *model.Job
|
||||
|
||||
if err := jss.GetReplica().SelectOne(&status,
|
||||
`SELECT
|
||||
*
|
||||
FROM
|
||||
Jobs
|
||||
WHERE
|
||||
Id = :Id`, map[string]interface{}{"Id": id}); err != nil {
|
||||
if err == sql.ErrNoRows {
|
||||
result.Err = model.NewAppError("SqlJobStore.Get", "store.sql_job.get.app_error", nil, "Id="+id+", "+err.Error(), http.StatusNotFound)
|
||||
} else {
|
||||
result.Err = model.NewAppError("SqlJobStore.Get", "store.sql_job.get.app_error", nil, "Id="+id+", "+err.Error(), http.StatusInternalServerError)
|
||||
}
|
||||
} else {
|
||||
result.Data = status
|
||||
}
|
||||
|
||||
storeChannel <- result
|
||||
close(storeChannel)
|
||||
}()
|
||||
|
||||
return storeChannel
|
||||
}
|
||||
|
||||
func (jss SqlJobStore) GetAllPage(offset int, limit int) store.StoreChannel {
|
||||
storeChannel := make(store.StoreChannel, 1)
|
||||
|
||||
go func() {
|
||||
result := store.StoreResult{}
|
||||
|
||||
var statuses []*model.Job
|
||||
|
||||
if _, err := jss.GetReplica().Select(&statuses,
|
||||
`SELECT
|
||||
*
|
||||
FROM
|
||||
Jobs
|
||||
ORDER BY
|
||||
CreateAt DESC
|
||||
LIMIT
|
||||
:Limit
|
||||
OFFSET
|
||||
:Offset`, map[string]interface{}{"Limit": limit, "Offset": offset}); err != nil {
|
||||
result.Err = model.NewAppError("SqlJobStore.GetAllPage", "store.sql_job.get_all.app_error", nil, err.Error(), http.StatusInternalServerError)
|
||||
} else {
|
||||
result.Data = statuses
|
||||
}
|
||||
|
||||
storeChannel <- result
|
||||
close(storeChannel)
|
||||
}()
|
||||
|
||||
return storeChannel
|
||||
}
|
||||
|
||||
func (jss SqlJobStore) GetAllByType(jobType string) store.StoreChannel {
|
||||
storeChannel := make(store.StoreChannel, 1)
|
||||
|
||||
go func() {
|
||||
result := store.StoreResult{}
|
||||
|
||||
var statuses []*model.Job
|
||||
|
||||
if _, err := jss.GetReplica().Select(&statuses,
|
||||
`SELECT
|
||||
*
|
||||
FROM
|
||||
Jobs
|
||||
WHERE
|
||||
Type = :Type
|
||||
ORDER BY
|
||||
CreateAt DESC`, map[string]interface{}{"Type": jobType}); err != nil {
|
||||
result.Err = model.NewAppError("SqlJobStore.GetAllByType", "store.sql_job.get_all.app_error", nil, "Type="+jobType+", "+err.Error(), http.StatusInternalServerError)
|
||||
} else {
|
||||
result.Data = statuses
|
||||
}
|
||||
|
||||
storeChannel <- result
|
||||
close(storeChannel)
|
||||
}()
|
||||
|
||||
return storeChannel
|
||||
}
|
||||
|
||||
func (jss SqlJobStore) GetAllByTypePage(jobType string, offset int, limit int) store.StoreChannel {
|
||||
storeChannel := make(store.StoreChannel, 1)
|
||||
|
||||
go func() {
|
||||
result := store.StoreResult{}
|
||||
|
||||
var statuses []*model.Job
|
||||
|
||||
if _, err := jss.GetReplica().Select(&statuses,
|
||||
`SELECT
|
||||
*
|
||||
FROM
|
||||
Jobs
|
||||
WHERE
|
||||
Type = :Type
|
||||
ORDER BY
|
||||
CreateAt DESC
|
||||
LIMIT
|
||||
:Limit
|
||||
OFFSET
|
||||
:Offset`, map[string]interface{}{"Type": jobType, "Limit": limit, "Offset": offset}); err != nil {
|
||||
result.Err = model.NewAppError("SqlJobStore.GetAllByTypePage", "store.sql_job.get_all.app_error", nil, "Type="+jobType+", "+err.Error(), http.StatusInternalServerError)
|
||||
} else {
|
||||
result.Data = statuses
|
||||
}
|
||||
|
||||
storeChannel <- result
|
||||
close(storeChannel)
|
||||
}()
|
||||
|
||||
return storeChannel
|
||||
}
|
||||
|
||||
func (jss SqlJobStore) GetAllByStatus(status string) store.StoreChannel {
|
||||
storeChannel := make(store.StoreChannel, 1)
|
||||
|
||||
go func() {
|
||||
result := store.StoreResult{}
|
||||
|
||||
var statuses []*model.Job
|
||||
|
||||
if _, err := jss.GetReplica().Select(&statuses,
|
||||
`SELECT
|
||||
*
|
||||
FROM
|
||||
Jobs
|
||||
WHERE
|
||||
Status = :Status
|
||||
ORDER BY
|
||||
CreateAt ASC`, map[string]interface{}{"Status": status}); err != nil {
|
||||
result.Err = model.NewAppError("SqlJobStore.GetAllByStatus", "store.sql_job.get_all.app_error", nil, "Status="+status+", "+err.Error(), http.StatusInternalServerError)
|
||||
} else {
|
||||
result.Data = statuses
|
||||
}
|
||||
|
||||
storeChannel <- result
|
||||
close(storeChannel)
|
||||
}()
|
||||
|
||||
return storeChannel
|
||||
}
|
||||
|
||||
func (jss SqlJobStore) Delete(id string) store.StoreChannel {
|
||||
storeChannel := make(store.StoreChannel, 1)
|
||||
|
||||
go func() {
|
||||
result := store.StoreResult{}
|
||||
|
||||
if _, err := jss.GetMaster().Exec(
|
||||
`DELETE FROM
|
||||
Jobs
|
||||
WHERE
|
||||
Id = :Id`, map[string]interface{}{"Id": id}); err != nil {
|
||||
result.Err = model.NewAppError("SqlJobStore.DeleteByType", "store.sql_job.delete.app_error", nil, "id="+id+", "+err.Error(), http.StatusInternalServerError)
|
||||
} else {
|
||||
result.Data = id
|
||||
}
|
||||
|
||||
storeChannel <- result
|
||||
close(storeChannel)
|
||||
}()
|
||||
|
||||
return storeChannel
|
||||
}
|
||||
407
store/sqlstore/job_store_test.go
Обычный файл
407
store/sqlstore/job_store_test.go
Обычный файл
@@ -0,0 +1,407 @@
|
||||
// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
|
||||
// See License.txt for license information.
|
||||
|
||||
package sqlstore
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"time"
|
||||
|
||||
"github.com/mattermost/mattermost-server/model"
|
||||
"github.com/mattermost/mattermost-server/store"
|
||||
)
|
||||
|
||||
func TestJobSaveGet(t *testing.T) {
|
||||
ss := Setup()
|
||||
|
||||
job := &model.Job{
|
||||
Id: model.NewId(),
|
||||
Type: model.NewId(),
|
||||
Status: model.NewId(),
|
||||
Data: map[string]string{
|
||||
"Processed": "0",
|
||||
"Total": "12345",
|
||||
"LastProcessed": "abcd",
|
||||
},
|
||||
}
|
||||
|
||||
if result := <-ss.Job().Save(job); result.Err != nil {
|
||||
t.Fatal(result.Err)
|
||||
}
|
||||
|
||||
defer func() {
|
||||
<-ss.Job().Delete(job.Id)
|
||||
}()
|
||||
|
||||
if result := <-ss.Job().Get(job.Id); result.Err != nil {
|
||||
t.Fatal(result.Err)
|
||||
} else if received := result.Data.(*model.Job); received.Id != job.Id {
|
||||
t.Fatal("received incorrect job after save")
|
||||
} else if received.Data["Total"] != "12345" {
|
||||
t.Fatal("data field was not retrieved successfully:", received.Data)
|
||||
}
|
||||
}
|
||||
|
||||
func TestJobGetAllByType(t *testing.T) {
|
||||
ss := Setup()
|
||||
|
||||
jobType := model.NewId()
|
||||
|
||||
jobs := []*model.Job{
|
||||
{
|
||||
Id: model.NewId(),
|
||||
Type: jobType,
|
||||
},
|
||||
{
|
||||
Id: model.NewId(),
|
||||
Type: jobType,
|
||||
},
|
||||
{
|
||||
Id: model.NewId(),
|
||||
Type: model.NewId(),
|
||||
},
|
||||
}
|
||||
|
||||
for _, job := range jobs {
|
||||
store.Must(ss.Job().Save(job))
|
||||
defer ss.Job().Delete(job.Id)
|
||||
}
|
||||
|
||||
if result := <-ss.Job().GetAllByType(jobType); result.Err != nil {
|
||||
t.Fatal(result.Err)
|
||||
} else if received := result.Data.([]*model.Job); len(received) != 2 {
|
||||
t.Fatal("received wrong number of jobs")
|
||||
} else if received[0].Id != jobs[0].Id && received[1].Id != jobs[0].Id {
|
||||
t.Fatal("should've received first jobs")
|
||||
} else if received[0].Id != jobs[1].Id && received[1].Id != jobs[1].Id {
|
||||
t.Fatal("should've received second jobs")
|
||||
}
|
||||
}
|
||||
|
||||
func TestJobGetAllByTypePage(t *testing.T) {
|
||||
ss := Setup()
|
||||
|
||||
jobType := model.NewId()
|
||||
|
||||
jobs := []*model.Job{
|
||||
{
|
||||
Id: model.NewId(),
|
||||
Type: jobType,
|
||||
CreateAt: 1000,
|
||||
},
|
||||
{
|
||||
Id: model.NewId(),
|
||||
Type: jobType,
|
||||
CreateAt: 999,
|
||||
},
|
||||
{
|
||||
Id: model.NewId(),
|
||||
Type: jobType,
|
||||
CreateAt: 1001,
|
||||
},
|
||||
{
|
||||
Id: model.NewId(),
|
||||
Type: model.NewId(),
|
||||
CreateAt: 1002,
|
||||
},
|
||||
}
|
||||
|
||||
for _, job := range jobs {
|
||||
store.Must(ss.Job().Save(job))
|
||||
defer ss.Job().Delete(job.Id)
|
||||
}
|
||||
|
||||
if result := <-ss.Job().GetAllByTypePage(jobType, 0, 2); result.Err != nil {
|
||||
t.Fatal(result.Err)
|
||||
} else if received := result.Data.([]*model.Job); len(received) != 2 {
|
||||
t.Fatal("received wrong number of jobs")
|
||||
} else if received[0].Id != jobs[2].Id {
|
||||
t.Fatal("should've received newest job first")
|
||||
} else if received[1].Id != jobs[0].Id {
|
||||
t.Fatal("should've received second newest job second")
|
||||
}
|
||||
|
||||
if result := <-ss.Job().GetAllByTypePage(jobType, 2, 2); result.Err != nil {
|
||||
t.Fatal(result.Err)
|
||||
} else if received := result.Data.([]*model.Job); len(received) != 1 {
|
||||
t.Fatal("received wrong number of jobs")
|
||||
} else if received[0].Id != jobs[1].Id {
|
||||
t.Fatal("should've received oldest job last")
|
||||
}
|
||||
}
|
||||
|
||||
func TestJobGetAllPage(t *testing.T) {
|
||||
ss := Setup()
|
||||
|
||||
jobType := model.NewId()
|
||||
createAtTime := model.GetMillis()
|
||||
|
||||
jobs := []*model.Job{
|
||||
{
|
||||
Id: model.NewId(),
|
||||
Type: jobType,
|
||||
CreateAt: createAtTime + 1,
|
||||
},
|
||||
{
|
||||
Id: model.NewId(),
|
||||
Type: jobType,
|
||||
CreateAt: createAtTime,
|
||||
},
|
||||
{
|
||||
Id: model.NewId(),
|
||||
Type: jobType,
|
||||
CreateAt: createAtTime + 2,
|
||||
},
|
||||
}
|
||||
|
||||
for _, job := range jobs {
|
||||
store.Must(ss.Job().Save(job))
|
||||
defer ss.Job().Delete(job.Id)
|
||||
}
|
||||
|
||||
if result := <-ss.Job().GetAllPage(0, 2); result.Err != nil {
|
||||
t.Fatal(result.Err)
|
||||
} else if received := result.Data.([]*model.Job); len(received) != 2 {
|
||||
t.Fatal("received wrong number of jobs")
|
||||
} else if received[0].Id != jobs[2].Id {
|
||||
t.Fatal("should've received newest job first")
|
||||
} else if received[1].Id != jobs[0].Id {
|
||||
t.Fatal("should've received second newest job second")
|
||||
}
|
||||
|
||||
if result := <-ss.Job().GetAllPage(2, 2); result.Err != nil {
|
||||
t.Fatal(result.Err)
|
||||
} else if received := result.Data.([]*model.Job); len(received) < 1 {
|
||||
t.Fatal("received wrong number of jobs")
|
||||
} else if received[0].Id != jobs[1].Id {
|
||||
t.Fatal("should've received oldest job last")
|
||||
}
|
||||
}
|
||||
|
||||
func TestJobGetAllByStatus(t *testing.T) {
|
||||
ss := Setup()
|
||||
|
||||
jobType := model.NewId()
|
||||
status := model.NewId()
|
||||
|
||||
jobs := []*model.Job{
|
||||
{
|
||||
Id: model.NewId(),
|
||||
Type: jobType,
|
||||
CreateAt: 1000,
|
||||
Status: status,
|
||||
Data: map[string]string{
|
||||
"test": "data",
|
||||
},
|
||||
},
|
||||
{
|
||||
Id: model.NewId(),
|
||||
Type: jobType,
|
||||
CreateAt: 999,
|
||||
Status: status,
|
||||
},
|
||||
{
|
||||
Id: model.NewId(),
|
||||
Type: jobType,
|
||||
CreateAt: 1001,
|
||||
Status: status,
|
||||
},
|
||||
{
|
||||
Id: model.NewId(),
|
||||
Type: jobType,
|
||||
CreateAt: 1002,
|
||||
Status: model.NewId(),
|
||||
},
|
||||
}
|
||||
|
||||
for _, job := range jobs {
|
||||
store.Must(ss.Job().Save(job))
|
||||
defer ss.Job().Delete(job.Id)
|
||||
}
|
||||
|
||||
if result := <-ss.Job().GetAllByStatus(status); result.Err != nil {
|
||||
t.Fatal(result.Err)
|
||||
} else if received := result.Data.([]*model.Job); len(received) != 3 {
|
||||
t.Fatal("received wrong number of jobs")
|
||||
} else if received[0].Id != jobs[1].Id || received[1].Id != jobs[0].Id || received[2].Id != jobs[2].Id {
|
||||
t.Fatal("should've received jobs ordered by CreateAt time")
|
||||
} else if received[1].Data["test"] != "data" {
|
||||
t.Fatal("should've received job data field back as saved")
|
||||
}
|
||||
}
|
||||
|
||||
func TestJobUpdateOptimistically(t *testing.T) {
|
||||
ss := Setup()
|
||||
|
||||
job := &model.Job{
|
||||
Id: model.NewId(),
|
||||
Type: model.JOB_TYPE_DATA_RETENTION,
|
||||
CreateAt: model.GetMillis(),
|
||||
Status: model.JOB_STATUS_PENDING,
|
||||
}
|
||||
|
||||
if result := <-ss.Job().Save(job); result.Err != nil {
|
||||
t.Fatal(result.Err)
|
||||
}
|
||||
defer ss.Job().Delete(job.Id)
|
||||
|
||||
job.LastActivityAt = model.GetMillis()
|
||||
job.Status = model.JOB_STATUS_IN_PROGRESS
|
||||
job.Progress = 50
|
||||
job.Data = map[string]string{
|
||||
"Foo": "Bar",
|
||||
}
|
||||
|
||||
if result := <-ss.Job().UpdateOptimistically(job, model.JOB_STATUS_SUCCESS); result.Err != nil {
|
||||
if result.Data.(bool) {
|
||||
t.Fatal("should have failed due to incorrect old status")
|
||||
}
|
||||
}
|
||||
|
||||
time.Sleep(2 * time.Millisecond)
|
||||
|
||||
if result := <-ss.Job().UpdateOptimistically(job, model.JOB_STATUS_PENDING); result.Err != nil {
|
||||
t.Fatal(result.Err)
|
||||
} else {
|
||||
if !result.Data.(bool) {
|
||||
t.Fatal("Should have successfully updated")
|
||||
}
|
||||
|
||||
var updatedJob *model.Job
|
||||
|
||||
if result := <-ss.Job().Get(job.Id); result.Err != nil {
|
||||
t.Fatal(result.Err)
|
||||
} else {
|
||||
updatedJob = result.Data.(*model.Job)
|
||||
}
|
||||
|
||||
if updatedJob.Type != job.Type || updatedJob.CreateAt != job.CreateAt || updatedJob.Status != job.Status || updatedJob.LastActivityAt <= job.LastActivityAt || updatedJob.Progress != job.Progress || updatedJob.Data["Foo"] != job.Data["Foo"] {
|
||||
t.Fatal("Some update property was not as expected")
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestJobUpdateStatusUpdateStatusOptimistically(t *testing.T) {
|
||||
ss := Setup()
|
||||
|
||||
job := &model.Job{
|
||||
Id: model.NewId(),
|
||||
Type: model.JOB_TYPE_DATA_RETENTION,
|
||||
CreateAt: model.GetMillis(),
|
||||
Status: model.JOB_STATUS_SUCCESS,
|
||||
}
|
||||
|
||||
var lastUpdateAt int64
|
||||
if result := <-ss.Job().Save(job); result.Err != nil {
|
||||
t.Fatal(result.Err)
|
||||
} else {
|
||||
lastUpdateAt = result.Data.(*model.Job).LastActivityAt
|
||||
}
|
||||
|
||||
defer ss.Job().Delete(job.Id)
|
||||
|
||||
time.Sleep(2 * time.Millisecond)
|
||||
|
||||
if result := <-ss.Job().UpdateStatus(job.Id, model.JOB_STATUS_PENDING); result.Err != nil {
|
||||
t.Fatal(result.Err)
|
||||
} else {
|
||||
received := result.Data.(*model.Job)
|
||||
if received.Status != model.JOB_STATUS_PENDING {
|
||||
t.Fatal("status wasn't updated")
|
||||
}
|
||||
if received.LastActivityAt <= lastUpdateAt {
|
||||
t.Fatal("lastActivityAt wasn't updated")
|
||||
}
|
||||
lastUpdateAt = received.LastActivityAt
|
||||
}
|
||||
|
||||
time.Sleep(2 * time.Millisecond)
|
||||
|
||||
if result := <-ss.Job().UpdateStatusOptimistically(job.Id, model.JOB_STATUS_IN_PROGRESS, model.JOB_STATUS_SUCCESS); result.Err != nil {
|
||||
t.Fatal(result.Err)
|
||||
} else {
|
||||
if result.Data.(bool) {
|
||||
t.Fatal("should be false due to incorrect original status")
|
||||
}
|
||||
}
|
||||
|
||||
if result := <-ss.Job().Get(job.Id); result.Err != nil {
|
||||
t.Fatal(result.Err)
|
||||
} else {
|
||||
received := result.Data.(*model.Job)
|
||||
if received.Status != model.JOB_STATUS_PENDING {
|
||||
t.Fatal("should still be pending")
|
||||
}
|
||||
if received.LastActivityAt != lastUpdateAt {
|
||||
t.Fatal("last activity at shouldn't have changed")
|
||||
}
|
||||
}
|
||||
|
||||
time.Sleep(2 * time.Millisecond)
|
||||
|
||||
if result := <-ss.Job().UpdateStatusOptimistically(job.Id, model.JOB_STATUS_PENDING, model.JOB_STATUS_IN_PROGRESS); result.Err != nil {
|
||||
t.Fatal(result.Err)
|
||||
} else {
|
||||
if !result.Data.(bool) {
|
||||
t.Fatal("should have succeeded")
|
||||
}
|
||||
}
|
||||
|
||||
var startAtSet int64
|
||||
if result := <-ss.Job().Get(job.Id); result.Err != nil {
|
||||
t.Fatal(result.Err)
|
||||
} else {
|
||||
received := result.Data.(*model.Job)
|
||||
if received.Status != model.JOB_STATUS_IN_PROGRESS {
|
||||
t.Fatal("should be in progress")
|
||||
}
|
||||
if received.StartAt == 0 {
|
||||
t.Fatal("received should have start at set")
|
||||
}
|
||||
if received.LastActivityAt <= lastUpdateAt {
|
||||
t.Fatal("lastActivityAt wasn't updated")
|
||||
}
|
||||
lastUpdateAt = received.LastActivityAt
|
||||
startAtSet = received.StartAt
|
||||
}
|
||||
|
||||
time.Sleep(2 * time.Millisecond)
|
||||
|
||||
if result := <-ss.Job().UpdateStatusOptimistically(job.Id, model.JOB_STATUS_IN_PROGRESS, model.JOB_STATUS_SUCCESS); result.Err != nil {
|
||||
t.Fatal(result.Err)
|
||||
} else {
|
||||
if !result.Data.(bool) {
|
||||
t.Fatal("should have succeeded")
|
||||
}
|
||||
}
|
||||
|
||||
if result := <-ss.Job().Get(job.Id); result.Err != nil {
|
||||
t.Fatal(result.Err)
|
||||
} else {
|
||||
received := result.Data.(*model.Job)
|
||||
if received.Status != model.JOB_STATUS_SUCCESS {
|
||||
t.Fatal("should be success status")
|
||||
}
|
||||
if received.StartAt != startAtSet {
|
||||
t.Fatal("startAt should not have changed")
|
||||
}
|
||||
if received.LastActivityAt <= lastUpdateAt {
|
||||
t.Fatal("lastActivityAt wasn't updated")
|
||||
}
|
||||
lastUpdateAt = received.LastActivityAt
|
||||
}
|
||||
}
|
||||
|
||||
func TestJobDelete(t *testing.T) {
|
||||
ss := Setup()
|
||||
|
||||
job := store.Must(ss.Job().Save(&model.Job{
|
||||
Id: model.NewId(),
|
||||
})).(*model.Job)
|
||||
|
||||
if result := <-ss.Job().Delete(job.Id); result.Err != nil {
|
||||
t.Fatal(result.Err)
|
||||
}
|
||||
}
|
||||
83
store/sqlstore/license_store.go
Обычный файл
83
store/sqlstore/license_store.go
Обычный файл
@@ -0,0 +1,83 @@
|
||||
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
|
||||
// See License.txt for license information.
|
||||
|
||||
package sqlstore
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/mattermost/mattermost-server/model"
|
||||
"github.com/mattermost/mattermost-server/store"
|
||||
)
|
||||
|
||||
type SqlLicenseStore struct {
|
||||
SqlStore
|
||||
}
|
||||
|
||||
func NewSqlLicenseStore(sqlStore SqlStore) store.LicenseStore {
|
||||
ls := &SqlLicenseStore{sqlStore}
|
||||
|
||||
for _, db := range sqlStore.GetAllConns() {
|
||||
table := db.AddTableWithName(model.LicenseRecord{}, "Licenses").SetKeys(false, "Id")
|
||||
table.ColMap("Id").SetMaxSize(26)
|
||||
table.ColMap("Bytes").SetMaxSize(10000)
|
||||
}
|
||||
|
||||
return ls
|
||||
}
|
||||
|
||||
func (ls SqlLicenseStore) CreateIndexesIfNotExists() {
|
||||
}
|
||||
|
||||
func (ls SqlLicenseStore) Save(license *model.LicenseRecord) store.StoreChannel {
|
||||
|
||||
storeChannel := make(store.StoreChannel, 1)
|
||||
|
||||
go func() {
|
||||
result := store.StoreResult{}
|
||||
|
||||
license.PreSave()
|
||||
if result.Err = license.IsValid(); result.Err != nil {
|
||||
storeChannel <- result
|
||||
close(storeChannel)
|
||||
return
|
||||
}
|
||||
|
||||
// Only insert if not exists
|
||||
if err := ls.GetReplica().SelectOne(&model.LicenseRecord{}, "SELECT * FROM Licenses WHERE Id = :Id", map[string]interface{}{"Id": license.Id}); err != nil {
|
||||
if err := ls.GetMaster().Insert(license); err != nil {
|
||||
result.Err = model.NewAppError("SqlLicenseStore.Save", "store.sql_license.save.app_error", nil, "license_id="+license.Id+", "+err.Error(), http.StatusInternalServerError)
|
||||
} else {
|
||||
result.Data = license
|
||||
}
|
||||
}
|
||||
|
||||
storeChannel <- result
|
||||
close(storeChannel)
|
||||
}()
|
||||
|
||||
return storeChannel
|
||||
}
|
||||
|
||||
func (ls SqlLicenseStore) Get(id string) store.StoreChannel {
|
||||
|
||||
storeChannel := make(store.StoreChannel, 1)
|
||||
|
||||
go func() {
|
||||
result := store.StoreResult{}
|
||||
|
||||
if obj, err := ls.GetReplica().Get(model.LicenseRecord{}, id); err != nil {
|
||||
result.Err = model.NewAppError("SqlLicenseStore.Get", "store.sql_license.get.app_error", nil, "license_id="+id+", "+err.Error(), http.StatusInternalServerError)
|
||||
} else if obj == nil {
|
||||
result.Err = model.NewAppError("SqlLicenseStore.Get", "store.sql_license.get.missing.app_error", nil, "license_id="+id, http.StatusNotFound)
|
||||
} else {
|
||||
result.Data = obj.(*model.LicenseRecord)
|
||||
}
|
||||
|
||||
storeChannel <- result
|
||||
close(storeChannel)
|
||||
|
||||
}()
|
||||
|
||||
return storeChannel
|
||||
}
|
||||
55
store/sqlstore/license_store_test.go
Обычный файл
55
store/sqlstore/license_store_test.go
Обычный файл
@@ -0,0 +1,55 @@
|
||||
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
|
||||
// See License.txt for license information.
|
||||
|
||||
package sqlstore
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/mattermost/mattermost-server/model"
|
||||
"github.com/mattermost/mattermost-server/store"
|
||||
)
|
||||
|
||||
func TestLicenseStoreSave(t *testing.T) {
|
||||
ss := Setup()
|
||||
|
||||
l1 := model.LicenseRecord{}
|
||||
l1.Id = model.NewId()
|
||||
l1.Bytes = "junk"
|
||||
|
||||
if err := (<-ss.License().Save(&l1)).Err; err != nil {
|
||||
t.Fatal("couldn't save license record", err)
|
||||
}
|
||||
|
||||
if err := (<-ss.License().Save(&l1)).Err; err != nil {
|
||||
t.Fatal("shouldn't fail on trying to save existing license record", err)
|
||||
}
|
||||
|
||||
l1.Id = ""
|
||||
|
||||
if err := (<-ss.License().Save(&l1)).Err; err == nil {
|
||||
t.Fatal("should fail on invalid license", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestLicenseStoreGet(t *testing.T) {
|
||||
ss := Setup()
|
||||
|
||||
l1 := model.LicenseRecord{}
|
||||
l1.Id = model.NewId()
|
||||
l1.Bytes = "junk"
|
||||
|
||||
store.Must(ss.License().Save(&l1))
|
||||
|
||||
if r := <-ss.License().Get(l1.Id); r.Err != nil {
|
||||
t.Fatal("couldn't get license", r.Err)
|
||||
} else {
|
||||
if r.Data.(*model.LicenseRecord).Bytes != l1.Bytes {
|
||||
t.Fatal("license bytes didn't match")
|
||||
}
|
||||
}
|
||||
|
||||
if err := (<-ss.License().Get("missing")).Err; err == nil {
|
||||
t.Fatal("should fail on get license", err)
|
||||
}
|
||||
}
|
||||
570
store/sqlstore/oauth_store.go
Обычный файл
570
store/sqlstore/oauth_store.go
Обычный файл
@@ -0,0 +1,570 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See License.txt for license information.
|
||||
|
||||
package sqlstore
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/mattermost/gorp"
|
||||
"github.com/mattermost/mattermost-server/model"
|
||||
"github.com/mattermost/mattermost-server/store"
|
||||
"github.com/mattermost/mattermost-server/utils"
|
||||
)
|
||||
|
||||
type SqlOAuthStore struct {
|
||||
SqlStore
|
||||
}
|
||||
|
||||
func NewSqlOAuthStore(sqlStore SqlStore) store.OAuthStore {
|
||||
as := &SqlOAuthStore{sqlStore}
|
||||
|
||||
for _, db := range sqlStore.GetAllConns() {
|
||||
table := db.AddTableWithName(model.OAuthApp{}, "OAuthApps").SetKeys(false, "Id")
|
||||
table.ColMap("Id").SetMaxSize(26)
|
||||
table.ColMap("CreatorId").SetMaxSize(26)
|
||||
table.ColMap("ClientSecret").SetMaxSize(128)
|
||||
table.ColMap("Name").SetMaxSize(64)
|
||||
table.ColMap("Description").SetMaxSize(512)
|
||||
table.ColMap("CallbackUrls").SetMaxSize(1024)
|
||||
table.ColMap("Homepage").SetMaxSize(256)
|
||||
table.ColMap("IconURL").SetMaxSize(512)
|
||||
|
||||
tableAuth := db.AddTableWithName(model.AuthData{}, "OAuthAuthData").SetKeys(false, "Code")
|
||||
tableAuth.ColMap("UserId").SetMaxSize(26)
|
||||
tableAuth.ColMap("ClientId").SetMaxSize(26)
|
||||
tableAuth.ColMap("Code").SetMaxSize(128)
|
||||
tableAuth.ColMap("RedirectUri").SetMaxSize(256)
|
||||
tableAuth.ColMap("State").SetMaxSize(128)
|
||||
tableAuth.ColMap("Scope").SetMaxSize(128)
|
||||
|
||||
tableAccess := db.AddTableWithName(model.AccessData{}, "OAuthAccessData").SetKeys(false, "Token")
|
||||
tableAccess.ColMap("ClientId").SetMaxSize(26)
|
||||
tableAccess.ColMap("UserId").SetMaxSize(26)
|
||||
tableAccess.ColMap("Token").SetMaxSize(26)
|
||||
tableAccess.ColMap("RefreshToken").SetMaxSize(26)
|
||||
tableAccess.ColMap("RedirectUri").SetMaxSize(256)
|
||||
tableAccess.ColMap("Scope").SetMaxSize(128)
|
||||
tableAccess.SetUniqueTogether("ClientId", "UserId")
|
||||
}
|
||||
|
||||
return as
|
||||
}
|
||||
|
||||
func (as SqlOAuthStore) CreateIndexesIfNotExists() {
|
||||
as.CreateIndexIfNotExists("idx_oauthapps_creator_id", "OAuthApps", "CreatorId")
|
||||
as.CreateIndexIfNotExists("idx_oauthaccessdata_client_id", "OAuthAccessData", "ClientId")
|
||||
as.CreateIndexIfNotExists("idx_oauthaccessdata_user_id", "OAuthAccessData", "UserId")
|
||||
as.CreateIndexIfNotExists("idx_oauthaccessdata_refresh_token", "OAuthAccessData", "RefreshToken")
|
||||
as.CreateIndexIfNotExists("idx_oauthauthdata_client_id", "OAuthAuthData", "Code")
|
||||
}
|
||||
|
||||
func (as SqlOAuthStore) SaveApp(app *model.OAuthApp) store.StoreChannel {
|
||||
|
||||
storeChannel := make(store.StoreChannel, 1)
|
||||
|
||||
go func() {
|
||||
result := store.StoreResult{}
|
||||
|
||||
if len(app.Id) > 0 {
|
||||
result.Err = model.NewAppError("SqlOAuthStore.SaveApp", "store.sql_oauth.save_app.existing.app_error", nil, "app_id="+app.Id, http.StatusBadRequest)
|
||||
storeChannel <- result
|
||||
close(storeChannel)
|
||||
return
|
||||
}
|
||||
|
||||
app.PreSave()
|
||||
if result.Err = app.IsValid(); result.Err != nil {
|
||||
storeChannel <- result
|
||||
close(storeChannel)
|
||||
return
|
||||
}
|
||||
|
||||
if err := as.GetMaster().Insert(app); err != nil {
|
||||
result.Err = model.NewAppError("SqlOAuthStore.SaveApp", "store.sql_oauth.save_app.save.app_error", nil, "app_id="+app.Id+", "+err.Error(), http.StatusInternalServerError)
|
||||
} else {
|
||||
result.Data = app
|
||||
}
|
||||
|
||||
storeChannel <- result
|
||||
close(storeChannel)
|
||||
}()
|
||||
|
||||
return storeChannel
|
||||
}
|
||||
|
||||
func (as SqlOAuthStore) UpdateApp(app *model.OAuthApp) store.StoreChannel {
|
||||
|
||||
storeChannel := make(store.StoreChannel, 1)
|
||||
|
||||
go func() {
|
||||
result := store.StoreResult{}
|
||||
|
||||
app.PreUpdate()
|
||||
|
||||
if result.Err = app.IsValid(); result.Err != nil {
|
||||
storeChannel <- result
|
||||
close(storeChannel)
|
||||
return
|
||||
}
|
||||
|
||||
if oldAppResult, err := as.GetMaster().Get(model.OAuthApp{}, app.Id); err != nil {
|
||||
result.Err = model.NewAppError("SqlOAuthStore.UpdateApp", "store.sql_oauth.update_app.finding.app_error", nil, "app_id="+app.Id+", "+err.Error(), http.StatusInternalServerError)
|
||||
} else if oldAppResult == nil {
|
||||
result.Err = model.NewAppError("SqlOAuthStore.UpdateApp", "store.sql_oauth.update_app.find.app_error", nil, "app_id="+app.Id, http.StatusBadRequest)
|
||||
} else {
|
||||
oldApp := oldAppResult.(*model.OAuthApp)
|
||||
app.CreateAt = oldApp.CreateAt
|
||||
app.CreatorId = oldApp.CreatorId
|
||||
|
||||
if count, err := as.GetMaster().Update(app); err != nil {
|
||||
result.Err = model.NewAppError("SqlOAuthStore.UpdateApp", "store.sql_oauth.update_app.updating.app_error", nil, "app_id="+app.Id+", "+err.Error(), http.StatusInternalServerError)
|
||||
} else if count != 1 {
|
||||
result.Err = model.NewAppError("SqlOAuthStore.UpdateApp", "store.sql_oauth.update_app.update.app_error", nil, "app_id="+app.Id, http.StatusBadRequest)
|
||||
} else {
|
||||
result.Data = [2]*model.OAuthApp{app, oldApp}
|
||||
}
|
||||
}
|
||||
|
||||
storeChannel <- result
|
||||
close(storeChannel)
|
||||
}()
|
||||
|
||||
return storeChannel
|
||||
}
|
||||
|
||||
func (as SqlOAuthStore) GetApp(id string) store.StoreChannel {
|
||||
|
||||
storeChannel := make(store.StoreChannel, 1)
|
||||
|
||||
go func() {
|
||||
result := store.StoreResult{}
|
||||
|
||||
if obj, err := as.GetReplica().Get(model.OAuthApp{}, id); err != nil {
|
||||
result.Err = model.NewAppError("SqlOAuthStore.GetApp", "store.sql_oauth.get_app.finding.app_error", nil, "app_id="+id+", "+err.Error(), http.StatusInternalServerError)
|
||||
} else if obj == nil {
|
||||
result.Err = model.NewAppError("SqlOAuthStore.GetApp", "store.sql_oauth.get_app.find.app_error", nil, "app_id="+id, http.StatusNotFound)
|
||||
} else {
|
||||
result.Data = obj.(*model.OAuthApp)
|
||||
}
|
||||
|
||||
storeChannel <- result
|
||||
close(storeChannel)
|
||||
|
||||
}()
|
||||
|
||||
return storeChannel
|
||||
}
|
||||
|
||||
func (as SqlOAuthStore) GetAppByUser(userId string, offset, limit int) store.StoreChannel {
|
||||
|
||||
storeChannel := make(store.StoreChannel, 1)
|
||||
|
||||
go func() {
|
||||
result := store.StoreResult{}
|
||||
|
||||
var apps []*model.OAuthApp
|
||||
|
||||
if _, err := as.GetReplica().Select(&apps, "SELECT * FROM OAuthApps WHERE CreatorId = :UserId LIMIT :Limit OFFSET :Offset", map[string]interface{}{"UserId": userId, "Offset": offset, "Limit": limit}); err != nil {
|
||||
result.Err = model.NewAppError("SqlOAuthStore.GetAppByUser", "store.sql_oauth.get_app_by_user.find.app_error", nil, "user_id="+userId+", "+err.Error(), http.StatusInternalServerError)
|
||||
}
|
||||
|
||||
result.Data = apps
|
||||
|
||||
storeChannel <- result
|
||||
close(storeChannel)
|
||||
}()
|
||||
|
||||
return storeChannel
|
||||
}
|
||||
|
||||
func (as SqlOAuthStore) GetApps(offset, limit int) store.StoreChannel {
|
||||
|
||||
storeChannel := make(store.StoreChannel, 1)
|
||||
|
||||
go func() {
|
||||
result := store.StoreResult{}
|
||||
|
||||
var apps []*model.OAuthApp
|
||||
|
||||
if _, err := as.GetReplica().Select(&apps, "SELECT * FROM OAuthApps LIMIT :Limit OFFSET :Offset", map[string]interface{}{"Offset": offset, "Limit": limit}); err != nil {
|
||||
result.Err = model.NewAppError("SqlOAuthStore.GetAppByUser", "store.sql_oauth.get_apps.find.app_error", nil, "err="+err.Error(), http.StatusInternalServerError)
|
||||
}
|
||||
|
||||
result.Data = apps
|
||||
|
||||
storeChannel <- result
|
||||
close(storeChannel)
|
||||
}()
|
||||
|
||||
return storeChannel
|
||||
}
|
||||
|
||||
func (as SqlOAuthStore) GetAuthorizedApps(userId string, offset, limit int) store.StoreChannel {
|
||||
storeChannel := make(store.StoreChannel, 1)
|
||||
|
||||
go func() {
|
||||
result := store.StoreResult{}
|
||||
|
||||
var apps []*model.OAuthApp
|
||||
|
||||
if _, err := as.GetReplica().Select(&apps,
|
||||
`SELECT o.* FROM OAuthApps AS o INNER JOIN
|
||||
Preferences AS p ON p.Name=o.Id AND p.UserId=:UserId LIMIT :Limit OFFSET :Offset`, map[string]interface{}{"UserId": userId, "Offset": offset, "Limit": limit}); err != nil {
|
||||
result.Err = model.NewAppError("SqlOAuthStore.GetAuthorizedApps", "store.sql_oauth.get_apps.find.app_error", nil, "err="+err.Error(), http.StatusInternalServerError)
|
||||
}
|
||||
|
||||
result.Data = apps
|
||||
|
||||
storeChannel <- result
|
||||
close(storeChannel)
|
||||
}()
|
||||
|
||||
return storeChannel
|
||||
}
|
||||
|
||||
func (as SqlOAuthStore) DeleteApp(id string) store.StoreChannel {
|
||||
storeChannel := make(store.StoreChannel, 1)
|
||||
|
||||
go func() {
|
||||
result := store.StoreResult{}
|
||||
|
||||
// wrap in a transaction so that if one fails, everything fails
|
||||
transaction, err := as.GetMaster().Begin()
|
||||
if err != nil {
|
||||
result.Err = model.NewAppError("SqlOAuthStore.DeleteApp", "store.sql_oauth.delete.open_transaction.app_error", nil, err.Error(), http.StatusInternalServerError)
|
||||
} else {
|
||||
if extrasResult := as.deleteApp(transaction, id); extrasResult.Err != nil {
|
||||
result = extrasResult
|
||||
}
|
||||
|
||||
if result.Err == nil {
|
||||
if err := transaction.Commit(); err != nil {
|
||||
// don't need to rollback here since the transaction is already closed
|
||||
result.Err = model.NewAppError("SqlOAuthStore.DeleteApp", "store.sql_oauth.delete.commit_transaction.app_error", nil, err.Error(), http.StatusInternalServerError)
|
||||
}
|
||||
} else {
|
||||
if err := transaction.Rollback(); err != nil {
|
||||
result.Err = model.NewAppError("SqlOAuthStore.DeleteApp", "store.sql_oauth.delete.rollback_transaction.app_error", nil, err.Error(), http.StatusInternalServerError)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
storeChannel <- result
|
||||
close(storeChannel)
|
||||
}()
|
||||
|
||||
return storeChannel
|
||||
}
|
||||
|
||||
func (as SqlOAuthStore) SaveAccessData(accessData *model.AccessData) store.StoreChannel {
|
||||
|
||||
storeChannel := make(store.StoreChannel, 1)
|
||||
|
||||
go func() {
|
||||
result := store.StoreResult{}
|
||||
|
||||
if result.Err = accessData.IsValid(); result.Err != nil {
|
||||
storeChannel <- result
|
||||
close(storeChannel)
|
||||
return
|
||||
}
|
||||
|
||||
if err := as.GetMaster().Insert(accessData); err != nil {
|
||||
result.Err = model.NewAppError("SqlOAuthStore.SaveAccessData", "store.sql_oauth.save_access_data.app_error", nil, err.Error(), http.StatusInternalServerError)
|
||||
} else {
|
||||
result.Data = accessData
|
||||
}
|
||||
|
||||
storeChannel <- result
|
||||
close(storeChannel)
|
||||
}()
|
||||
|
||||
return storeChannel
|
||||
}
|
||||
|
||||
func (as SqlOAuthStore) GetAccessData(token string) store.StoreChannel {
|
||||
|
||||
storeChannel := make(store.StoreChannel, 1)
|
||||
|
||||
go func() {
|
||||
result := store.StoreResult{}
|
||||
|
||||
accessData := model.AccessData{}
|
||||
|
||||
if err := as.GetReplica().SelectOne(&accessData, "SELECT * FROM OAuthAccessData WHERE Token = :Token", map[string]interface{}{"Token": token}); err != nil {
|
||||
result.Err = model.NewAppError("SqlOAuthStore.GetAccessData", "store.sql_oauth.get_access_data.app_error", nil, err.Error(), http.StatusInternalServerError)
|
||||
} else {
|
||||
result.Data = &accessData
|
||||
}
|
||||
|
||||
storeChannel <- result
|
||||
close(storeChannel)
|
||||
|
||||
}()
|
||||
|
||||
return storeChannel
|
||||
}
|
||||
|
||||
func (as SqlOAuthStore) GetAccessDataByUserForApp(userId, clientId string) store.StoreChannel {
|
||||
|
||||
storeChannel := make(store.StoreChannel, 1)
|
||||
|
||||
go func() {
|
||||
result := store.StoreResult{}
|
||||
|
||||
var accessData []*model.AccessData
|
||||
|
||||
if _, err := as.GetReplica().Select(&accessData,
|
||||
"SELECT * FROM OAuthAccessData WHERE UserId = :UserId AND ClientId = :ClientId",
|
||||
map[string]interface{}{"UserId": userId, "ClientId": clientId}); err != nil {
|
||||
result.Err = model.NewAppError("SqlOAuthStore.GetAccessDataByUserForApp", "store.sql_oauth.get_access_data_by_user_for_app.app_error", nil, "user_id="+userId+" client_id="+clientId, http.StatusInternalServerError)
|
||||
} else {
|
||||
result.Data = accessData
|
||||
}
|
||||
|
||||
storeChannel <- result
|
||||
close(storeChannel)
|
||||
|
||||
}()
|
||||
|
||||
return storeChannel
|
||||
}
|
||||
|
||||
func (as SqlOAuthStore) GetAccessDataByRefreshToken(token string) store.StoreChannel {
|
||||
|
||||
storeChannel := make(store.StoreChannel, 1)
|
||||
|
||||
go func() {
|
||||
result := store.StoreResult{}
|
||||
|
||||
accessData := model.AccessData{}
|
||||
|
||||
if err := as.GetReplica().SelectOne(&accessData, "SELECT * FROM OAuthAccessData WHERE RefreshToken = :Token", map[string]interface{}{"Token": token}); err != nil {
|
||||
result.Err = model.NewAppError("SqlOAuthStore.GetAccessData", "store.sql_oauth.get_access_data.app_error", nil, err.Error(), http.StatusInternalServerError)
|
||||
} else {
|
||||
result.Data = &accessData
|
||||
}
|
||||
|
||||
storeChannel <- result
|
||||
close(storeChannel)
|
||||
|
||||
}()
|
||||
|
||||
return storeChannel
|
||||
}
|
||||
|
||||
func (as SqlOAuthStore) GetPreviousAccessData(userId, clientId string) store.StoreChannel {
|
||||
|
||||
storeChannel := make(store.StoreChannel, 1)
|
||||
|
||||
go func() {
|
||||
result := store.StoreResult{}
|
||||
|
||||
accessData := model.AccessData{}
|
||||
|
||||
if err := as.GetReplica().SelectOne(&accessData, "SELECT * FROM OAuthAccessData WHERE ClientId = :ClientId AND UserId = :UserId",
|
||||
map[string]interface{}{"ClientId": clientId, "UserId": userId}); err != nil {
|
||||
if strings.Contains(err.Error(), "no rows") {
|
||||
result.Data = nil
|
||||
} else {
|
||||
result.Err = model.NewAppError("SqlOAuthStore.GetPreviousAccessData", "store.sql_oauth.get_previous_access_data.app_error", nil, err.Error(), http.StatusNotFound)
|
||||
}
|
||||
} else {
|
||||
result.Data = &accessData
|
||||
}
|
||||
|
||||
storeChannel <- result
|
||||
close(storeChannel)
|
||||
|
||||
}()
|
||||
|
||||
return storeChannel
|
||||
}
|
||||
|
||||
func (as SqlOAuthStore) UpdateAccessData(accessData *model.AccessData) store.StoreChannel {
|
||||
storeChannel := make(store.StoreChannel, 1)
|
||||
|
||||
go func() {
|
||||
result := store.StoreResult{}
|
||||
|
||||
if result.Err = accessData.IsValid(); result.Err != nil {
|
||||
storeChannel <- result
|
||||
close(storeChannel)
|
||||
return
|
||||
}
|
||||
|
||||
if _, err := as.GetMaster().Exec("UPDATE OAuthAccessData SET Token = :Token, ExpiresAt = :ExpiresAt, RefreshToken = :RefreshToken WHERE ClientId = :ClientId AND UserID = :UserId",
|
||||
map[string]interface{}{"Token": accessData.Token, "ExpiresAt": accessData.ExpiresAt, "RefreshToken": accessData.RefreshToken, "ClientId": accessData.ClientId, "UserId": accessData.UserId}); err != nil {
|
||||
result.Err = model.NewAppError("SqlOAuthStore.Update", "store.sql_oauth.update_access_data.app_error", nil,
|
||||
"clientId="+accessData.ClientId+",userId="+accessData.UserId+", "+err.Error(), http.StatusInternalServerError)
|
||||
} else {
|
||||
result.Data = accessData
|
||||
}
|
||||
|
||||
storeChannel <- result
|
||||
close(storeChannel)
|
||||
}()
|
||||
|
||||
return storeChannel
|
||||
}
|
||||
|
||||
func (as SqlOAuthStore) RemoveAccessData(token string) store.StoreChannel {
|
||||
storeChannel := make(store.StoreChannel, 1)
|
||||
|
||||
go func() {
|
||||
result := store.StoreResult{}
|
||||
|
||||
if _, err := as.GetMaster().Exec("DELETE FROM OAuthAccessData WHERE Token = :Token", map[string]interface{}{"Token": token}); err != nil {
|
||||
result.Err = model.NewAppError("SqlOAuthStore.RemoveAccessData", "store.sql_oauth.remove_access_data.app_error", nil, "err="+err.Error(), http.StatusInternalServerError)
|
||||
}
|
||||
|
||||
storeChannel <- result
|
||||
close(storeChannel)
|
||||
}()
|
||||
|
||||
return storeChannel
|
||||
}
|
||||
|
||||
func (as SqlOAuthStore) SaveAuthData(authData *model.AuthData) store.StoreChannel {
|
||||
|
||||
storeChannel := make(store.StoreChannel, 1)
|
||||
|
||||
go func() {
|
||||
result := store.StoreResult{}
|
||||
|
||||
authData.PreSave()
|
||||
if result.Err = authData.IsValid(); result.Err != nil {
|
||||
storeChannel <- result
|
||||
close(storeChannel)
|
||||
return
|
||||
}
|
||||
|
||||
if err := as.GetMaster().Insert(authData); err != nil {
|
||||
result.Err = model.NewAppError("SqlOAuthStore.SaveAuthData", "store.sql_oauth.save_auth_data.app_error", nil, err.Error(), http.StatusInternalServerError)
|
||||
} else {
|
||||
result.Data = authData
|
||||
}
|
||||
|
||||
storeChannel <- result
|
||||
close(storeChannel)
|
||||
}()
|
||||
|
||||
return storeChannel
|
||||
}
|
||||
|
||||
func (as SqlOAuthStore) GetAuthData(code string) store.StoreChannel {
|
||||
|
||||
storeChannel := make(store.StoreChannel, 1)
|
||||
|
||||
go func() {
|
||||
result := store.StoreResult{}
|
||||
|
||||
if obj, err := as.GetReplica().Get(model.AuthData{}, code); err != nil {
|
||||
result.Err = model.NewAppError("SqlOAuthStore.GetAuthData", "store.sql_oauth.get_auth_data.finding.app_error", nil, err.Error(), http.StatusInternalServerError)
|
||||
} else if obj == nil {
|
||||
result.Err = model.NewAppError("SqlOAuthStore.GetAuthData", "store.sql_oauth.get_auth_data.find.app_error", nil, "", http.StatusNotFound)
|
||||
} else {
|
||||
result.Data = obj.(*model.AuthData)
|
||||
}
|
||||
|
||||
storeChannel <- result
|
||||
close(storeChannel)
|
||||
|
||||
}()
|
||||
|
||||
return storeChannel
|
||||
}
|
||||
|
||||
func (as SqlOAuthStore) RemoveAuthData(code string) store.StoreChannel {
|
||||
storeChannel := make(store.StoreChannel, 1)
|
||||
|
||||
go func() {
|
||||
result := store.StoreResult{}
|
||||
|
||||
_, err := as.GetMaster().Exec("DELETE FROM OAuthAuthData WHERE Code = :Code", map[string]interface{}{"Code": code})
|
||||
if err != nil {
|
||||
result.Err = model.NewAppError("SqlOAuthStore.RemoveAuthData", "store.sql_oauth.remove_auth_data.app_error", nil, "err="+err.Error(), http.StatusInternalServerError)
|
||||
}
|
||||
|
||||
storeChannel <- result
|
||||
close(storeChannel)
|
||||
}()
|
||||
|
||||
return storeChannel
|
||||
}
|
||||
|
||||
func (as SqlOAuthStore) PermanentDeleteAuthDataByUser(userId string) store.StoreChannel {
|
||||
storeChannel := make(store.StoreChannel, 1)
|
||||
|
||||
go func() {
|
||||
result := store.StoreResult{}
|
||||
|
||||
_, err := as.GetMaster().Exec("DELETE FROM OAuthAccessData WHERE UserId = :UserId", map[string]interface{}{"UserId": userId})
|
||||
if err != nil {
|
||||
result.Err = model.NewAppError("SqlOAuthStore.RemoveAuthDataByUserId", "store.sql_oauth.permanent_delete_auth_data_by_user.app_error", nil, "err="+err.Error(), http.StatusInternalServerError)
|
||||
}
|
||||
|
||||
storeChannel <- result
|
||||
close(storeChannel)
|
||||
}()
|
||||
|
||||
return storeChannel
|
||||
}
|
||||
|
||||
func (as SqlOAuthStore) deleteApp(transaction *gorp.Transaction, clientId string) store.StoreResult {
|
||||
result := store.StoreResult{}
|
||||
|
||||
if _, err := transaction.Exec("DELETE FROM OAuthApps WHERE Id = :Id", map[string]interface{}{"Id": clientId}); err != nil {
|
||||
result.Err = model.NewAppError("SqlOAuthStore.DeleteApp", "store.sql_oauth.delete_app.app_error", nil, "id="+clientId+", err="+err.Error(), http.StatusInternalServerError)
|
||||
return result
|
||||
}
|
||||
|
||||
return as.deleteOAuthAppSessions(transaction, clientId)
|
||||
}
|
||||
|
||||
func (as SqlOAuthStore) deleteOAuthAppSessions(transaction *gorp.Transaction, clientId string) store.StoreResult {
|
||||
result := store.StoreResult{}
|
||||
|
||||
query := ""
|
||||
if *utils.Cfg.SqlSettings.DriverName == model.DATABASE_DRIVER_POSTGRES {
|
||||
query = "DELETE FROM Sessions s USING OAuthAccessData o WHERE o.Token = s.Token AND o.ClientId = :Id"
|
||||
} else if *utils.Cfg.SqlSettings.DriverName == model.DATABASE_DRIVER_MYSQL {
|
||||
query = "DELETE s.* FROM Sessions s INNER JOIN OAuthAccessData o ON o.Token = s.Token WHERE o.ClientId = :Id"
|
||||
}
|
||||
|
||||
if _, err := transaction.Exec(query, map[string]interface{}{"Id": clientId}); err != nil {
|
||||
result.Err = model.NewAppError("SqlOAuthStore.DeleteApp", "store.sql_oauth.delete_app.app_error", nil, "id="+clientId+", err="+err.Error(), http.StatusInternalServerError)
|
||||
return result
|
||||
}
|
||||
|
||||
return as.deleteOAuthTokens(transaction, clientId)
|
||||
}
|
||||
|
||||
func (as SqlOAuthStore) deleteOAuthTokens(transaction *gorp.Transaction, clientId string) store.StoreResult {
|
||||
result := store.StoreResult{}
|
||||
|
||||
if _, err := transaction.Exec("DELETE FROM OAuthAccessData WHERE ClientId = :Id", map[string]interface{}{"Id": clientId}); err != nil {
|
||||
result.Err = model.NewAppError("SqlOAuthStore.DeleteApp", "store.sql_oauth.delete_app.app_error", nil, "id="+clientId+", err="+err.Error(), http.StatusInternalServerError)
|
||||
return result
|
||||
}
|
||||
|
||||
return as.deleteAppExtras(transaction, clientId)
|
||||
}
|
||||
|
||||
func (as SqlOAuthStore) deleteAppExtras(transaction *gorp.Transaction, clientId string) store.StoreResult {
|
||||
result := store.StoreResult{}
|
||||
|
||||
if _, err := transaction.Exec(
|
||||
`DELETE FROM
|
||||
Preferences
|
||||
WHERE
|
||||
Category = :Category
|
||||
AND Name = :Name`, map[string]interface{}{"Category": model.PREFERENCE_CATEGORY_AUTHORIZED_OAUTH_APP, "Name": clientId}); err != nil {
|
||||
result.Err = model.NewAppError("SqlOAuthStore.DeleteApp", "store.sql_preference.delete.app_error", nil, err.Error(), http.StatusInternalServerError)
|
||||
return result
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
446
store/sqlstore/oauth_store_test.go
Обычный файл
446
store/sqlstore/oauth_store_test.go
Обычный файл
@@ -0,0 +1,446 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See License.txt for license information.
|
||||
|
||||
package sqlstore
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/mattermost/mattermost-server/model"
|
||||
"github.com/mattermost/mattermost-server/store"
|
||||
)
|
||||
|
||||
func TestOAuthStoreSaveApp(t *testing.T) {
|
||||
ss := Setup()
|
||||
|
||||
a1 := model.OAuthApp{}
|
||||
a1.CreatorId = model.NewId()
|
||||
a1.CallbackUrls = []string{"https://nowhere.com"}
|
||||
a1.Homepage = "https://nowhere.com"
|
||||
|
||||
// Try to save an app that already has an Id
|
||||
a1.Id = model.NewId()
|
||||
if err := (<-ss.OAuth().SaveApp(&a1)).Err; err == nil {
|
||||
t.Fatal("Should have failed, cannot add an OAuth app cannot be save with an Id, it has to be updated")
|
||||
}
|
||||
|
||||
// Try to save an Invalid App
|
||||
a1.Id = ""
|
||||
if err := (<-ss.OAuth().SaveApp(&a1)).Err; err == nil {
|
||||
t.Fatal("Should have failed, app should be invalid cause it doesn' have a name set")
|
||||
}
|
||||
|
||||
// Save the app
|
||||
a1.Id = ""
|
||||
a1.Name = "TestApp" + model.NewId()
|
||||
if err := (<-ss.OAuth().SaveApp(&a1)).Err; err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestOAuthStoreGetApp(t *testing.T) {
|
||||
ss := Setup()
|
||||
|
||||
a1 := model.OAuthApp{}
|
||||
a1.CreatorId = model.NewId()
|
||||
a1.Name = "TestApp" + model.NewId()
|
||||
a1.CallbackUrls = []string{"https://nowhere.com"}
|
||||
a1.Homepage = "https://nowhere.com"
|
||||
store.Must(ss.OAuth().SaveApp(&a1))
|
||||
|
||||
// Lets try to get and app that does not exists
|
||||
if err := (<-ss.OAuth().GetApp("fake0123456789abcderfgret1")).Err; err == nil {
|
||||
t.Fatal("Should have failed. App does not exists")
|
||||
}
|
||||
|
||||
if err := (<-ss.OAuth().GetApp(a1.Id)).Err; err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Lets try and get the app from a user that hasn't created any apps
|
||||
if result := (<-ss.OAuth().GetAppByUser("fake0123456789abcderfgret1", 0, 1000)); result.Err == nil {
|
||||
if len(result.Data.([]*model.OAuthApp)) > 0 {
|
||||
t.Fatal("Should have failed. Fake user hasn't created any apps")
|
||||
}
|
||||
} else {
|
||||
t.Fatal(result.Err)
|
||||
}
|
||||
|
||||
if err := (<-ss.OAuth().GetAppByUser(a1.CreatorId, 0, 1000)).Err; err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if err := (<-ss.OAuth().GetApps(0, 1000)).Err; err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestOAuthStoreUpdateApp(t *testing.T) {
|
||||
ss := Setup()
|
||||
|
||||
a1 := model.OAuthApp{}
|
||||
a1.CreatorId = model.NewId()
|
||||
a1.Name = "TestApp" + model.NewId()
|
||||
a1.CallbackUrls = []string{"https://nowhere.com"}
|
||||
a1.Homepage = "https://nowhere.com"
|
||||
store.Must(ss.OAuth().SaveApp(&a1))
|
||||
|
||||
// temporarily save the created app id
|
||||
id := a1.Id
|
||||
|
||||
a1.CreateAt = 1
|
||||
a1.ClientSecret = "pwd"
|
||||
a1.CreatorId = "12345678901234567890123456"
|
||||
|
||||
// Lets update the app by removing the name
|
||||
a1.Name = ""
|
||||
if result := <-ss.OAuth().UpdateApp(&a1); result.Err == nil {
|
||||
t.Fatal("Should have failed. App name is not set")
|
||||
}
|
||||
|
||||
// Lets not find the app that we are trying to update
|
||||
a1.Id = "fake0123456789abcderfgret1"
|
||||
a1.Name = "NewName"
|
||||
if result := <-ss.OAuth().UpdateApp(&a1); result.Err == nil {
|
||||
t.Fatal("Should have failed. Not able to find the app")
|
||||
}
|
||||
|
||||
a1.Id = id
|
||||
if result := <-ss.OAuth().UpdateApp(&a1); result.Err != nil {
|
||||
t.Fatal(result.Err)
|
||||
} else {
|
||||
ua1 := (result.Data.([2]*model.OAuthApp)[0])
|
||||
if ua1.Name != "NewName" {
|
||||
t.Fatal("name did not update")
|
||||
}
|
||||
if ua1.CreateAt == 1 {
|
||||
t.Fatal("create at should not have updated")
|
||||
}
|
||||
if ua1.CreatorId == "12345678901234567890123456" {
|
||||
t.Fatal("creator id should not have updated")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestOAuthStoreSaveAccessData(t *testing.T) {
|
||||
ss := Setup()
|
||||
|
||||
a1 := model.AccessData{}
|
||||
a1.ClientId = model.NewId()
|
||||
a1.UserId = model.NewId()
|
||||
|
||||
// Lets try and save an incomplete access data
|
||||
if err := (<-ss.OAuth().SaveAccessData(&a1)).Err; err == nil {
|
||||
t.Fatal("Should have failed. Access data needs the token")
|
||||
}
|
||||
|
||||
a1.Token = model.NewId()
|
||||
a1.RefreshToken = model.NewId()
|
||||
a1.RedirectUri = "http://example.com"
|
||||
|
||||
if err := (<-ss.OAuth().SaveAccessData(&a1)).Err; err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestOAuthUpdateAccessData(t *testing.T) {
|
||||
ss := Setup()
|
||||
|
||||
a1 := model.AccessData{}
|
||||
a1.ClientId = model.NewId()
|
||||
a1.UserId = model.NewId()
|
||||
a1.Token = model.NewId()
|
||||
a1.RefreshToken = model.NewId()
|
||||
a1.ExpiresAt = model.GetMillis()
|
||||
a1.RedirectUri = "http://example.com"
|
||||
store.Must(ss.OAuth().SaveAccessData(&a1))
|
||||
|
||||
//Try to update to invalid Refresh Token
|
||||
refreshToken := a1.RefreshToken
|
||||
a1.RefreshToken = model.NewId() + "123"
|
||||
if err := (<-ss.OAuth().UpdateAccessData(&a1)).Err; err == nil {
|
||||
t.Fatal("Should have failed with invalid token")
|
||||
}
|
||||
|
||||
//Try to update to invalid RedirectUri
|
||||
a1.RefreshToken = model.NewId()
|
||||
a1.RedirectUri = ""
|
||||
if err := (<-ss.OAuth().UpdateAccessData(&a1)).Err; err == nil {
|
||||
t.Fatal("Should have failed with invalid Redirect URI")
|
||||
}
|
||||
|
||||
// Should update fine
|
||||
a1.RedirectUri = "http://example.com"
|
||||
if result := <-ss.OAuth().UpdateAccessData(&a1); result.Err != nil {
|
||||
t.Fatal(result.Err)
|
||||
} else {
|
||||
ra1 := result.Data.(*model.AccessData)
|
||||
if ra1.RefreshToken == refreshToken {
|
||||
t.Fatal("refresh tokens didn't match")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestOAuthStoreGetAccessData(t *testing.T) {
|
||||
ss := Setup()
|
||||
|
||||
a1 := model.AccessData{}
|
||||
a1.ClientId = model.NewId()
|
||||
a1.UserId = model.NewId()
|
||||
a1.Token = model.NewId()
|
||||
a1.RefreshToken = model.NewId()
|
||||
a1.ExpiresAt = model.GetMillis()
|
||||
a1.RedirectUri = "http://example.com"
|
||||
store.Must(ss.OAuth().SaveAccessData(&a1))
|
||||
|
||||
if err := (<-ss.OAuth().GetAccessData("invalidToken")).Err; err == nil {
|
||||
t.Fatal("Should have failed. There is no data with an invalid token")
|
||||
}
|
||||
|
||||
if result := <-ss.OAuth().GetAccessData(a1.Token); result.Err != nil {
|
||||
t.Fatal(result.Err)
|
||||
} else {
|
||||
ra1 := result.Data.(*model.AccessData)
|
||||
if a1.Token != ra1.Token {
|
||||
t.Fatal("tokens didn't match")
|
||||
}
|
||||
}
|
||||
|
||||
if err := (<-ss.OAuth().GetPreviousAccessData(a1.UserId, a1.ClientId)).Err; err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if err := (<-ss.OAuth().GetPreviousAccessData("user", "junk")).Err; err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Try to get the Access data using an invalid refresh token
|
||||
if err := (<-ss.OAuth().GetAccessDataByRefreshToken(a1.Token)).Err; err == nil {
|
||||
t.Fatal("Should have failed. There is no data with an invalid token")
|
||||
}
|
||||
|
||||
// Get the Access Data using the refresh token
|
||||
if result := <-ss.OAuth().GetAccessDataByRefreshToken(a1.RefreshToken); result.Err != nil {
|
||||
t.Fatal(result.Err)
|
||||
} else {
|
||||
ra1 := result.Data.(*model.AccessData)
|
||||
if a1.RefreshToken != ra1.RefreshToken {
|
||||
t.Fatal("tokens didn't match")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestOAuthStoreRemoveAccessData(t *testing.T) {
|
||||
ss := Setup()
|
||||
|
||||
a1 := model.AccessData{}
|
||||
a1.ClientId = model.NewId()
|
||||
a1.UserId = model.NewId()
|
||||
a1.Token = model.NewId()
|
||||
a1.RefreshToken = model.NewId()
|
||||
a1.RedirectUri = "http://example.com"
|
||||
store.Must(ss.OAuth().SaveAccessData(&a1))
|
||||
|
||||
if err := (<-ss.OAuth().RemoveAccessData(a1.Token)).Err; err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if result := (<-ss.OAuth().GetPreviousAccessData(a1.UserId, a1.ClientId)); result.Err != nil {
|
||||
} else {
|
||||
if result.Data != nil {
|
||||
t.Fatal("did not delete access token")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestOAuthStoreSaveAuthData(t *testing.T) {
|
||||
ss := Setup()
|
||||
|
||||
a1 := model.AuthData{}
|
||||
a1.ClientId = model.NewId()
|
||||
a1.UserId = model.NewId()
|
||||
a1.Code = model.NewId()
|
||||
a1.RedirectUri = "http://example.com"
|
||||
if err := (<-ss.OAuth().SaveAuthData(&a1)).Err; err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestOAuthStoreGetAuthData(t *testing.T) {
|
||||
ss := Setup()
|
||||
|
||||
a1 := model.AuthData{}
|
||||
a1.ClientId = model.NewId()
|
||||
a1.UserId = model.NewId()
|
||||
a1.Code = model.NewId()
|
||||
a1.RedirectUri = "http://example.com"
|
||||
store.Must(ss.OAuth().SaveAuthData(&a1))
|
||||
|
||||
if err := (<-ss.OAuth().GetAuthData(a1.Code)).Err; err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestOAuthStoreRemoveAuthData(t *testing.T) {
|
||||
ss := Setup()
|
||||
|
||||
a1 := model.AuthData{}
|
||||
a1.ClientId = model.NewId()
|
||||
a1.UserId = model.NewId()
|
||||
a1.Code = model.NewId()
|
||||
a1.RedirectUri = "http://example.com"
|
||||
store.Must(ss.OAuth().SaveAuthData(&a1))
|
||||
|
||||
if err := (<-ss.OAuth().RemoveAuthData(a1.Code)).Err; err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if err := (<-ss.OAuth().GetAuthData(a1.Code)).Err; err == nil {
|
||||
t.Fatal("should have errored - auth code removed")
|
||||
}
|
||||
}
|
||||
|
||||
func TestOAuthStoreRemoveAuthDataByUser(t *testing.T) {
|
||||
ss := Setup()
|
||||
|
||||
a1 := model.AuthData{}
|
||||
a1.ClientId = model.NewId()
|
||||
a1.UserId = model.NewId()
|
||||
a1.Code = model.NewId()
|
||||
a1.RedirectUri = "http://example.com"
|
||||
store.Must(ss.OAuth().SaveAuthData(&a1))
|
||||
|
||||
if err := (<-ss.OAuth().PermanentDeleteAuthDataByUser(a1.UserId)).Err; err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestOAuthGetAuthorizedApps(t *testing.T) {
|
||||
ss := Setup()
|
||||
|
||||
a1 := model.OAuthApp{}
|
||||
a1.CreatorId = model.NewId()
|
||||
a1.Name = "TestApp" + model.NewId()
|
||||
a1.CallbackUrls = []string{"https://nowhere.com"}
|
||||
a1.Homepage = "https://nowhere.com"
|
||||
store.Must(ss.OAuth().SaveApp(&a1))
|
||||
|
||||
// Lets try and get an Authorized app for a user who hasn't authorized it
|
||||
if result := <-ss.OAuth().GetAuthorizedApps("fake0123456789abcderfgret1", 0, 1000); result.Err == nil {
|
||||
if len(result.Data.([]*model.OAuthApp)) > 0 {
|
||||
t.Fatal("Should have failed. Fake user hasn't authorized the app")
|
||||
}
|
||||
} else {
|
||||
t.Fatal(result.Err)
|
||||
}
|
||||
|
||||
// allow the app
|
||||
p := model.Preference{}
|
||||
p.UserId = a1.CreatorId
|
||||
p.Category = model.PREFERENCE_CATEGORY_AUTHORIZED_OAUTH_APP
|
||||
p.Name = a1.Id
|
||||
p.Value = "true"
|
||||
store.Must(ss.Preference().Save(&model.Preferences{p}))
|
||||
|
||||
if result := <-ss.OAuth().GetAuthorizedApps(a1.CreatorId, 0, 1000); result.Err != nil {
|
||||
t.Fatal(result.Err)
|
||||
} else {
|
||||
apps := result.Data.([]*model.OAuthApp)
|
||||
if len(apps) == 0 {
|
||||
t.Fatal("It should have return apps")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestOAuthGetAccessDataByUserForApp(t *testing.T) {
|
||||
ss := Setup()
|
||||
|
||||
a1 := model.OAuthApp{}
|
||||
a1.CreatorId = model.NewId()
|
||||
a1.Name = "TestApp" + model.NewId()
|
||||
a1.CallbackUrls = []string{"https://nowhere.com"}
|
||||
a1.Homepage = "https://nowhere.com"
|
||||
store.Must(ss.OAuth().SaveApp(&a1))
|
||||
|
||||
// allow the app
|
||||
p := model.Preference{}
|
||||
p.UserId = a1.CreatorId
|
||||
p.Category = model.PREFERENCE_CATEGORY_AUTHORIZED_OAUTH_APP
|
||||
p.Name = a1.Id
|
||||
p.Value = "true"
|
||||
store.Must(ss.Preference().Save(&model.Preferences{p}))
|
||||
|
||||
if result := <-ss.OAuth().GetAuthorizedApps(a1.CreatorId, 0, 1000); result.Err != nil {
|
||||
t.Fatal(result.Err)
|
||||
} else {
|
||||
apps := result.Data.([]*model.OAuthApp)
|
||||
if len(apps) == 0 {
|
||||
t.Fatal("It should have return apps")
|
||||
}
|
||||
}
|
||||
|
||||
// save the token
|
||||
ad1 := model.AccessData{}
|
||||
ad1.ClientId = a1.Id
|
||||
ad1.UserId = a1.CreatorId
|
||||
ad1.Token = model.NewId()
|
||||
ad1.RefreshToken = model.NewId()
|
||||
ad1.RedirectUri = "http://example.com"
|
||||
|
||||
if err := (<-ss.OAuth().SaveAccessData(&ad1)).Err; err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if result := <-ss.OAuth().GetAccessDataByUserForApp(a1.CreatorId, a1.Id); result.Err != nil {
|
||||
t.Fatal(result.Err)
|
||||
} else {
|
||||
accessData := result.Data.([]*model.AccessData)
|
||||
if len(accessData) == 0 {
|
||||
t.Fatal("It should have return access data")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestOAuthStoreDeleteApp(t *testing.T) {
|
||||
ss := Setup()
|
||||
|
||||
a1 := model.OAuthApp{}
|
||||
a1.CreatorId = model.NewId()
|
||||
a1.Name = "TestApp" + model.NewId()
|
||||
a1.CallbackUrls = []string{"https://nowhere.com"}
|
||||
a1.Homepage = "https://nowhere.com"
|
||||
store.Must(ss.OAuth().SaveApp(&a1))
|
||||
|
||||
// delete a non-existent app
|
||||
if err := (<-ss.OAuth().DeleteApp("fakeclientId")).Err; err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
s1 := model.Session{}
|
||||
s1.UserId = model.NewId()
|
||||
s1.Token = model.NewId()
|
||||
s1.IsOAuth = true
|
||||
|
||||
store.Must(ss.Session().Save(&s1))
|
||||
|
||||
ad1 := model.AccessData{}
|
||||
ad1.ClientId = a1.Id
|
||||
ad1.UserId = a1.CreatorId
|
||||
ad1.Token = s1.Token
|
||||
ad1.RefreshToken = model.NewId()
|
||||
ad1.RedirectUri = "http://example.com"
|
||||
|
||||
store.Must(ss.OAuth().SaveAccessData(&ad1))
|
||||
|
||||
if err := (<-ss.OAuth().DeleteApp(a1.Id)).Err; err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if err := (<-ss.Session().Get(s1.Token)).Err; err == nil {
|
||||
t.Fatal("should error - session should be deleted")
|
||||
}
|
||||
|
||||
if err := (<-ss.OAuth().GetAccessData(s1.Token)).Err; err == nil {
|
||||
t.Fatal("should error - access data should be deleted")
|
||||
}
|
||||
}
|
||||
1393
store/sqlstore/post_store.go
Обычный файл
1393
store/sqlstore/post_store.go
Обычный файл
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
1703
store/sqlstore/post_store_test.go
Обычный файл
1703
store/sqlstore/post_store_test.go
Обычный файл
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
427
store/sqlstore/preference_store.go
Обычный файл
427
store/sqlstore/preference_store.go
Обычный файл
@@ -0,0 +1,427 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See License.txt for license information.
|
||||
|
||||
package sqlstore
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
l4g "github.com/alecthomas/log4go"
|
||||
"github.com/mattermost/gorp"
|
||||
|
||||
"github.com/mattermost/mattermost-server/model"
|
||||
"github.com/mattermost/mattermost-server/store"
|
||||
"github.com/mattermost/mattermost-server/utils"
|
||||
)
|
||||
|
||||
type SqlPreferenceStore struct {
|
||||
SqlStore
|
||||
}
|
||||
|
||||
const (
|
||||
FEATURE_TOGGLE_PREFIX = "feature_enabled_"
|
||||
)
|
||||
|
||||
func NewSqlPreferenceStore(sqlStore SqlStore) store.PreferenceStore {
|
||||
s := &SqlPreferenceStore{sqlStore}
|
||||
|
||||
for _, db := range sqlStore.GetAllConns() {
|
||||
table := db.AddTableWithName(model.Preference{}, "Preferences").SetKeys(false, "UserId", "Category", "Name")
|
||||
table.ColMap("UserId").SetMaxSize(26)
|
||||
table.ColMap("Category").SetMaxSize(32)
|
||||
table.ColMap("Name").SetMaxSize(32)
|
||||
table.ColMap("Value").SetMaxSize(2000)
|
||||
}
|
||||
|
||||
return s
|
||||
}
|
||||
|
||||
func (s SqlPreferenceStore) CreateIndexesIfNotExists() {
|
||||
s.CreateIndexIfNotExists("idx_preferences_user_id", "Preferences", "UserId")
|
||||
s.CreateIndexIfNotExists("idx_preferences_category", "Preferences", "Category")
|
||||
s.CreateIndexIfNotExists("idx_preferences_name", "Preferences", "Name")
|
||||
}
|
||||
|
||||
func (s SqlPreferenceStore) DeleteUnusedFeatures() {
|
||||
l4g.Debug(utils.T("store.sql_preference.delete_unused_features.debug"))
|
||||
|
||||
sql := `DELETE
|
||||
FROM Preferences
|
||||
WHERE
|
||||
Category = :Category
|
||||
AND Value = :Value
|
||||
AND Name LIKE '` + FEATURE_TOGGLE_PREFIX + `%'`
|
||||
|
||||
queryParams := map[string]string{
|
||||
"Category": model.PREFERENCE_CATEGORY_ADVANCED_SETTINGS,
|
||||
"Value": "false",
|
||||
}
|
||||
s.GetMaster().Exec(sql, queryParams)
|
||||
}
|
||||
|
||||
func (s SqlPreferenceStore) Save(preferences *model.Preferences) store.StoreChannel {
|
||||
storeChannel := make(store.StoreChannel, 1)
|
||||
|
||||
go func() {
|
||||
result := store.StoreResult{}
|
||||
|
||||
// wrap in a transaction so that if one fails, everything fails
|
||||
transaction, err := s.GetMaster().Begin()
|
||||
if err != nil {
|
||||
result.Err = model.NewAppError("SqlPreferenceStore.Save", "store.sql_preference.save.open_transaction.app_error", nil, err.Error(), http.StatusInternalServerError)
|
||||
} else {
|
||||
for _, preference := range *preferences {
|
||||
if upsertResult := s.save(transaction, &preference); upsertResult.Err != nil {
|
||||
result = upsertResult
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if result.Err == nil {
|
||||
if err := transaction.Commit(); err != nil {
|
||||
// don't need to rollback here since the transaction is already closed
|
||||
result.Err = model.NewAppError("SqlPreferenceStore.Save", "store.sql_preference.save.commit_transaction.app_error", nil, err.Error(), http.StatusInternalServerError)
|
||||
} else {
|
||||
result.Data = len(*preferences)
|
||||
}
|
||||
} else {
|
||||
if err := transaction.Rollback(); err != nil {
|
||||
result.Err = model.NewAppError("SqlPreferenceStore.Save", "store.sql_preference.save.rollback_transaction.app_error", nil, err.Error(), http.StatusInternalServerError)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
storeChannel <- result
|
||||
close(storeChannel)
|
||||
}()
|
||||
|
||||
return storeChannel
|
||||
}
|
||||
|
||||
func (s SqlPreferenceStore) save(transaction *gorp.Transaction, preference *model.Preference) store.StoreResult {
|
||||
result := store.StoreResult{}
|
||||
|
||||
preference.PreUpdate()
|
||||
|
||||
if result.Err = preference.IsValid(); result.Err != nil {
|
||||
return result
|
||||
}
|
||||
|
||||
params := map[string]interface{}{
|
||||
"UserId": preference.UserId,
|
||||
"Category": preference.Category,
|
||||
"Name": preference.Name,
|
||||
"Value": preference.Value,
|
||||
}
|
||||
|
||||
if *utils.Cfg.SqlSettings.DriverName == model.DATABASE_DRIVER_MYSQL {
|
||||
if _, err := transaction.Exec(
|
||||
`INSERT INTO
|
||||
Preferences
|
||||
(UserId, Category, Name, Value)
|
||||
VALUES
|
||||
(:UserId, :Category, :Name, :Value)
|
||||
ON DUPLICATE KEY UPDATE
|
||||
Value = :Value`, params); err != nil {
|
||||
result.Err = model.NewAppError("SqlPreferenceStore.save", "store.sql_preference.save.updating.app_error", nil, err.Error(), http.StatusInternalServerError)
|
||||
}
|
||||
} else if *utils.Cfg.SqlSettings.DriverName == model.DATABASE_DRIVER_POSTGRES {
|
||||
// postgres has no way to upsert values until version 9.5 and trying inserting and then updating causes transactions to abort
|
||||
count, err := transaction.SelectInt(
|
||||
`SELECT
|
||||
count(0)
|
||||
FROM
|
||||
Preferences
|
||||
WHERE
|
||||
UserId = :UserId
|
||||
AND Category = :Category
|
||||
AND Name = :Name`, params)
|
||||
if err != nil {
|
||||
result.Err = model.NewAppError("SqlPreferenceStore.save", "store.sql_preference.save.updating.app_error", nil, err.Error(), http.StatusInternalServerError)
|
||||
return result
|
||||
}
|
||||
|
||||
if count == 1 {
|
||||
s.update(transaction, preference)
|
||||
} else {
|
||||
s.insert(transaction, preference)
|
||||
}
|
||||
} else {
|
||||
result.Err = model.NewAppError("SqlPreferenceStore.save", "store.sql_preference.save.missing_driver.app_error", nil, "Failed to update preference because of missing driver", http.StatusNotImplemented)
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
func (s SqlPreferenceStore) insert(transaction *gorp.Transaction, preference *model.Preference) store.StoreResult {
|
||||
result := store.StoreResult{}
|
||||
|
||||
if err := transaction.Insert(preference); err != nil {
|
||||
if IsUniqueConstraintError(err, []string{"UserId", "preferences_pkey"}) {
|
||||
result.Err = model.NewAppError("SqlPreferenceStore.insert", "store.sql_preference.insert.exists.app_error", nil,
|
||||
"user_id="+preference.UserId+", category="+preference.Category+", name="+preference.Name+", "+err.Error(), http.StatusBadRequest)
|
||||
} else {
|
||||
result.Err = model.NewAppError("SqlPreferenceStore.insert", "store.sql_preference.insert.save.app_error", nil,
|
||||
"user_id="+preference.UserId+", category="+preference.Category+", name="+preference.Name+", "+err.Error(), http.StatusInternalServerError)
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
func (s SqlPreferenceStore) update(transaction *gorp.Transaction, preference *model.Preference) store.StoreResult {
|
||||
result := store.StoreResult{}
|
||||
|
||||
if _, err := transaction.Update(preference); err != nil {
|
||||
result.Err = model.NewAppError("SqlPreferenceStore.update", "store.sql_preference.update.app_error", nil,
|
||||
"user_id="+preference.UserId+", category="+preference.Category+", name="+preference.Name+", "+err.Error(), http.StatusInternalServerError)
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
func (s SqlPreferenceStore) Get(userId string, category string, name string) store.StoreChannel {
|
||||
storeChannel := make(store.StoreChannel, 1)
|
||||
|
||||
go func() {
|
||||
result := store.StoreResult{}
|
||||
|
||||
var preference model.Preference
|
||||
|
||||
if err := s.GetReplica().SelectOne(&preference,
|
||||
`SELECT
|
||||
*
|
||||
FROM
|
||||
Preferences
|
||||
WHERE
|
||||
UserId = :UserId
|
||||
AND Category = :Category
|
||||
AND Name = :Name`, map[string]interface{}{"UserId": userId, "Category": category, "Name": name}); err != nil {
|
||||
result.Err = model.NewAppError("SqlPreferenceStore.Get", "store.sql_preference.get.app_error", nil, err.Error(), http.StatusInternalServerError)
|
||||
} else {
|
||||
result.Data = preference
|
||||
}
|
||||
|
||||
storeChannel <- result
|
||||
close(storeChannel)
|
||||
}()
|
||||
|
||||
return storeChannel
|
||||
}
|
||||
|
||||
func (s SqlPreferenceStore) GetCategory(userId string, category string) store.StoreChannel {
|
||||
storeChannel := make(store.StoreChannel, 1)
|
||||
|
||||
go func() {
|
||||
result := store.StoreResult{}
|
||||
|
||||
var preferences model.Preferences
|
||||
|
||||
if _, err := s.GetReplica().Select(&preferences,
|
||||
`SELECT
|
||||
*
|
||||
FROM
|
||||
Preferences
|
||||
WHERE
|
||||
UserId = :UserId
|
||||
AND Category = :Category`, map[string]interface{}{"UserId": userId, "Category": category}); err != nil {
|
||||
result.Err = model.NewAppError("SqlPreferenceStore.GetCategory", "store.sql_preference.get_category.app_error", nil, err.Error(), http.StatusInternalServerError)
|
||||
} else {
|
||||
result.Data = preferences
|
||||
}
|
||||
|
||||
storeChannel <- result
|
||||
close(storeChannel)
|
||||
}()
|
||||
|
||||
return storeChannel
|
||||
}
|
||||
|
||||
func (s SqlPreferenceStore) GetAll(userId string) store.StoreChannel {
|
||||
storeChannel := make(store.StoreChannel, 1)
|
||||
|
||||
go func() {
|
||||
result := store.StoreResult{}
|
||||
|
||||
var preferences model.Preferences
|
||||
|
||||
if _, err := s.GetReplica().Select(&preferences,
|
||||
`SELECT
|
||||
*
|
||||
FROM
|
||||
Preferences
|
||||
WHERE
|
||||
UserId = :UserId`, map[string]interface{}{"UserId": userId}); err != nil {
|
||||
result.Err = model.NewAppError("SqlPreferenceStore.GetAll", "store.sql_preference.get_all.app_error", nil, err.Error(), http.StatusInternalServerError)
|
||||
} else {
|
||||
result.Data = preferences
|
||||
}
|
||||
|
||||
storeChannel <- result
|
||||
close(storeChannel)
|
||||
}()
|
||||
|
||||
return storeChannel
|
||||
}
|
||||
|
||||
func (s SqlPreferenceStore) PermanentDeleteByUser(userId string) store.StoreChannel {
|
||||
storeChannel := make(store.StoreChannel, 1)
|
||||
|
||||
go func() {
|
||||
result := store.StoreResult{}
|
||||
|
||||
if _, err := s.GetMaster().Exec(
|
||||
`DELETE FROM Preferences WHERE UserId = :UserId`, map[string]interface{}{"UserId": userId}); err != nil {
|
||||
result.Err = model.NewAppError("SqlPreferenceStore.Delete", "store.sql_preference.permanent_delete_by_user.app_error", nil, err.Error(), http.StatusInternalServerError)
|
||||
}
|
||||
|
||||
storeChannel <- result
|
||||
close(storeChannel)
|
||||
}()
|
||||
|
||||
return storeChannel
|
||||
}
|
||||
|
||||
func (s SqlPreferenceStore) IsFeatureEnabled(feature, userId string) store.StoreChannel {
|
||||
storeChannel := make(store.StoreChannel, 1)
|
||||
|
||||
go func() {
|
||||
result := store.StoreResult{}
|
||||
if value, err := s.GetReplica().SelectStr(`SELECT
|
||||
value
|
||||
FROM
|
||||
Preferences
|
||||
WHERE
|
||||
UserId = :UserId
|
||||
AND Category = :Category
|
||||
AND Name = :Name`, map[string]interface{}{"UserId": userId, "Category": model.PREFERENCE_CATEGORY_ADVANCED_SETTINGS, "Name": FEATURE_TOGGLE_PREFIX + feature}); err != nil {
|
||||
result.Err = model.NewAppError("SqlPreferenceStore.IsFeatureEnabled", "store.sql_preference.is_feature_enabled.app_error", nil, err.Error(), http.StatusInternalServerError)
|
||||
} else {
|
||||
result.Data = value == "true"
|
||||
}
|
||||
|
||||
storeChannel <- result
|
||||
close(storeChannel)
|
||||
}()
|
||||
|
||||
return storeChannel
|
||||
}
|
||||
|
||||
func (s SqlPreferenceStore) Delete(userId, category, name string) store.StoreChannel {
|
||||
storeChannel := make(store.StoreChannel, 1)
|
||||
|
||||
go func() {
|
||||
result := store.StoreResult{}
|
||||
|
||||
if _, err := s.GetMaster().Exec(
|
||||
`DELETE FROM
|
||||
Preferences
|
||||
WHERE
|
||||
UserId = :UserId
|
||||
AND Category = :Category
|
||||
AND Name = :Name`, map[string]interface{}{"UserId": userId, "Category": category, "Name": name}); err != nil {
|
||||
result.Err = model.NewAppError("SqlPreferenceStore.Delete", "store.sql_preference.delete.app_error", nil, err.Error(), http.StatusInternalServerError)
|
||||
}
|
||||
|
||||
storeChannel <- result
|
||||
close(storeChannel)
|
||||
}()
|
||||
|
||||
return storeChannel
|
||||
}
|
||||
|
||||
func (s SqlPreferenceStore) DeleteCategory(userId string, category string) store.StoreChannel {
|
||||
storeChannel := make(store.StoreChannel, 1)
|
||||
|
||||
go func() {
|
||||
result := store.StoreResult{}
|
||||
|
||||
if _, err := s.GetMaster().Exec(
|
||||
`DELETE FROM
|
||||
Preferences
|
||||
WHERE
|
||||
UserId = :UserId
|
||||
AND Category = :Category`, map[string]interface{}{"UserId": userId, "Category": category}); err != nil {
|
||||
result.Err = model.NewAppError("SqlPreferenceStore.DeleteCategory", "store.sql_preference.delete.app_error", nil, err.Error(), http.StatusInternalServerError)
|
||||
}
|
||||
|
||||
storeChannel <- result
|
||||
close(storeChannel)
|
||||
}()
|
||||
|
||||
return storeChannel
|
||||
}
|
||||
|
||||
func (s SqlPreferenceStore) DeleteCategoryAndName(category string, name string) store.StoreChannel {
|
||||
storeChannel := make(store.StoreChannel, 1)
|
||||
|
||||
go func() {
|
||||
result := store.StoreResult{}
|
||||
|
||||
if _, err := s.GetMaster().Exec(
|
||||
`DELETE FROM
|
||||
Preferences
|
||||
WHERE
|
||||
Name = :Name
|
||||
AND Category = :Category`, map[string]interface{}{"Name": name, "Category": category}); err != nil {
|
||||
result.Err = model.NewAppError("SqlPreferenceStore.DeleteCategoryAndName", "store.sql_preference.delete.app_error", nil, err.Error(), http.StatusInternalServerError)
|
||||
}
|
||||
|
||||
storeChannel <- result
|
||||
close(storeChannel)
|
||||
}()
|
||||
|
||||
return storeChannel
|
||||
}
|
||||
|
||||
func (s SqlPreferenceStore) CleanupFlagsBatch(limit int64) store.StoreChannel {
|
||||
storeChannel := make(store.StoreChannel, 1)
|
||||
|
||||
go func() {
|
||||
result := store.StoreResult{}
|
||||
|
||||
query :=
|
||||
`DELETE FROM
|
||||
Preferences
|
||||
WHERE
|
||||
Category = :Category
|
||||
AND Name IN (
|
||||
SELECT
|
||||
*
|
||||
FROM (
|
||||
SELECT
|
||||
Preferences.Name
|
||||
FROM
|
||||
Preferences
|
||||
LEFT JOIN
|
||||
Posts
|
||||
ON
|
||||
Preferences.Name = Posts.Id
|
||||
WHERE
|
||||
Preferences.Category = :Category
|
||||
AND Posts.Id IS null
|
||||
LIMIT
|
||||
:Limit
|
||||
)
|
||||
AS t
|
||||
)`
|
||||
|
||||
sqlResult, err := s.GetMaster().Exec(query, map[string]interface{}{"Category": model.PREFERENCE_CATEGORY_FLAGGED_POST, "Limit": limit})
|
||||
if err != nil {
|
||||
result.Err = model.NewAppError("SqlPostStore.CleanupFlagsBatch", "store.sql_preference.cleanup_flags_batch.app_error", nil, ""+err.Error(), http.StatusInternalServerError)
|
||||
} else {
|
||||
rowsAffected, err1 := sqlResult.RowsAffected()
|
||||
if err1 != nil {
|
||||
result.Err = model.NewAppError("SqlPostStore.CleanupFlagsBatch", "store.sql_preference.cleanup_flags_batch.app_error", nil, ""+err.Error(), http.StatusInternalServerError)
|
||||
result.Data = int64(0)
|
||||
} else {
|
||||
result.Data = rowsAffected
|
||||
}
|
||||
}
|
||||
|
||||
storeChannel <- result
|
||||
close(storeChannel)
|
||||
}()
|
||||
|
||||
return storeChannel
|
||||
}
|
||||
517
store/sqlstore/preference_store_test.go
Обычный файл
517
store/sqlstore/preference_store_test.go
Обычный файл
@@ -0,0 +1,517 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See License.txt for license information.
|
||||
|
||||
package sqlstore
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/mattermost/mattermost-server/model"
|
||||
"github.com/mattermost/mattermost-server/store"
|
||||
)
|
||||
|
||||
func TestPreferenceSave(t *testing.T) {
|
||||
ss := Setup()
|
||||
|
||||
id := model.NewId()
|
||||
|
||||
preferences := model.Preferences{
|
||||
{
|
||||
UserId: id,
|
||||
Category: model.PREFERENCE_CATEGORY_DIRECT_CHANNEL_SHOW,
|
||||
Name: model.NewId(),
|
||||
Value: "value1a",
|
||||
},
|
||||
{
|
||||
UserId: id,
|
||||
Category: model.PREFERENCE_CATEGORY_DIRECT_CHANNEL_SHOW,
|
||||
Name: model.NewId(),
|
||||
Value: "value1b",
|
||||
},
|
||||
}
|
||||
if count := store.Must(ss.Preference().Save(&preferences)); count != 2 {
|
||||
t.Fatal("got incorrect number of rows saved")
|
||||
}
|
||||
|
||||
for _, preference := range preferences {
|
||||
if data := store.Must(ss.Preference().Get(preference.UserId, preference.Category, preference.Name)).(model.Preference); preference != data {
|
||||
t.Fatal("got incorrect preference after first Save")
|
||||
}
|
||||
}
|
||||
|
||||
preferences[0].Value = "value2a"
|
||||
preferences[1].Value = "value2b"
|
||||
if count := store.Must(ss.Preference().Save(&preferences)); count != 2 {
|
||||
t.Fatal("got incorrect number of rows saved")
|
||||
}
|
||||
|
||||
for _, preference := range preferences {
|
||||
if data := store.Must(ss.Preference().Get(preference.UserId, preference.Category, preference.Name)).(model.Preference); preference != data {
|
||||
t.Fatal("got incorrect preference after second Save")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestPreferenceGet(t *testing.T) {
|
||||
ss := Setup()
|
||||
|
||||
userId := model.NewId()
|
||||
category := model.PREFERENCE_CATEGORY_DIRECT_CHANNEL_SHOW
|
||||
name := model.NewId()
|
||||
|
||||
preferences := model.Preferences{
|
||||
{
|
||||
UserId: userId,
|
||||
Category: category,
|
||||
Name: name,
|
||||
},
|
||||
{
|
||||
UserId: userId,
|
||||
Category: category,
|
||||
Name: model.NewId(),
|
||||
},
|
||||
{
|
||||
UserId: userId,
|
||||
Category: model.NewId(),
|
||||
Name: name,
|
||||
},
|
||||
{
|
||||
UserId: model.NewId(),
|
||||
Category: category,
|
||||
Name: name,
|
||||
},
|
||||
}
|
||||
|
||||
store.Must(ss.Preference().Save(&preferences))
|
||||
|
||||
if result := <-ss.Preference().Get(userId, category, name); result.Err != nil {
|
||||
t.Fatal(result.Err)
|
||||
} else if data := result.Data.(model.Preference); data != preferences[0] {
|
||||
t.Fatal("got incorrect preference")
|
||||
}
|
||||
|
||||
// make sure getting a missing preference fails
|
||||
if result := <-ss.Preference().Get(model.NewId(), model.NewId(), model.NewId()); result.Err == nil {
|
||||
t.Fatal("no error on getting a missing preference")
|
||||
}
|
||||
}
|
||||
|
||||
func TestPreferenceGetCategory(t *testing.T) {
|
||||
ss := Setup()
|
||||
|
||||
userId := model.NewId()
|
||||
category := model.PREFERENCE_CATEGORY_DIRECT_CHANNEL_SHOW
|
||||
name := model.NewId()
|
||||
|
||||
preferences := model.Preferences{
|
||||
{
|
||||
UserId: userId,
|
||||
Category: category,
|
||||
Name: name,
|
||||
},
|
||||
// same user/category, different name
|
||||
{
|
||||
UserId: userId,
|
||||
Category: category,
|
||||
Name: model.NewId(),
|
||||
},
|
||||
// same user/name, different category
|
||||
{
|
||||
UserId: userId,
|
||||
Category: model.NewId(),
|
||||
Name: name,
|
||||
},
|
||||
// same name/category, different user
|
||||
{
|
||||
UserId: model.NewId(),
|
||||
Category: category,
|
||||
Name: name,
|
||||
},
|
||||
}
|
||||
|
||||
store.Must(ss.Preference().Save(&preferences))
|
||||
|
||||
if result := <-ss.Preference().GetCategory(userId, category); result.Err != nil {
|
||||
t.Fatal(result.Err)
|
||||
} else if data := result.Data.(model.Preferences); len(data) != 2 {
|
||||
t.Fatal("got the wrong number of preferences")
|
||||
} else if !((data[0] == preferences[0] && data[1] == preferences[1]) || (data[0] == preferences[1] && data[1] == preferences[0])) {
|
||||
t.Fatal("got incorrect preferences")
|
||||
}
|
||||
|
||||
// make sure getting a missing preference category doesn't fail
|
||||
if result := <-ss.Preference().GetCategory(model.NewId(), model.NewId()); result.Err != nil {
|
||||
t.Fatal(result.Err)
|
||||
} else if data := result.Data.(model.Preferences); len(data) != 0 {
|
||||
t.Fatal("shouldn't have got any preferences")
|
||||
}
|
||||
}
|
||||
|
||||
func TestPreferenceGetAll(t *testing.T) {
|
||||
ss := Setup()
|
||||
|
||||
userId := model.NewId()
|
||||
category := model.PREFERENCE_CATEGORY_DIRECT_CHANNEL_SHOW
|
||||
name := model.NewId()
|
||||
|
||||
preferences := model.Preferences{
|
||||
{
|
||||
UserId: userId,
|
||||
Category: category,
|
||||
Name: name,
|
||||
},
|
||||
// same user/category, different name
|
||||
{
|
||||
UserId: userId,
|
||||
Category: category,
|
||||
Name: model.NewId(),
|
||||
},
|
||||
// same user/name, different category
|
||||
{
|
||||
UserId: userId,
|
||||
Category: model.NewId(),
|
||||
Name: name,
|
||||
},
|
||||
// same name/category, different user
|
||||
{
|
||||
UserId: model.NewId(),
|
||||
Category: category,
|
||||
Name: name,
|
||||
},
|
||||
}
|
||||
|
||||
store.Must(ss.Preference().Save(&preferences))
|
||||
|
||||
if result := <-ss.Preference().GetAll(userId); result.Err != nil {
|
||||
t.Fatal(result.Err)
|
||||
} else if data := result.Data.(model.Preferences); len(data) != 3 {
|
||||
t.Fatal("got the wrong number of preferences")
|
||||
} else {
|
||||
for i := 0; i < 3; i++ {
|
||||
if data[0] != preferences[i] && data[1] != preferences[i] && data[2] != preferences[i] {
|
||||
t.Fatal("got incorrect preferences")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestPreferenceDeleteByUser(t *testing.T) {
|
||||
ss := Setup()
|
||||
|
||||
userId := model.NewId()
|
||||
category := model.PREFERENCE_CATEGORY_DIRECT_CHANNEL_SHOW
|
||||
name := model.NewId()
|
||||
|
||||
preferences := model.Preferences{
|
||||
{
|
||||
UserId: userId,
|
||||
Category: category,
|
||||
Name: name,
|
||||
},
|
||||
// same user/category, different name
|
||||
{
|
||||
UserId: userId,
|
||||
Category: category,
|
||||
Name: model.NewId(),
|
||||
},
|
||||
// same user/name, different category
|
||||
{
|
||||
UserId: userId,
|
||||
Category: model.NewId(),
|
||||
Name: name,
|
||||
},
|
||||
// same name/category, different user
|
||||
{
|
||||
UserId: model.NewId(),
|
||||
Category: category,
|
||||
Name: name,
|
||||
},
|
||||
}
|
||||
|
||||
store.Must(ss.Preference().Save(&preferences))
|
||||
|
||||
if result := <-ss.Preference().PermanentDeleteByUser(userId); result.Err != nil {
|
||||
t.Fatal(result.Err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsFeatureEnabled(t *testing.T) {
|
||||
ss := Setup()
|
||||
|
||||
feature1 := "testFeat1"
|
||||
feature2 := "testFeat2"
|
||||
feature3 := "testFeat3"
|
||||
|
||||
userId := model.NewId()
|
||||
category := model.PREFERENCE_CATEGORY_ADVANCED_SETTINGS
|
||||
|
||||
features := model.Preferences{
|
||||
{
|
||||
UserId: userId,
|
||||
Category: category,
|
||||
Name: FEATURE_TOGGLE_PREFIX + feature1,
|
||||
Value: "true",
|
||||
},
|
||||
{
|
||||
UserId: userId,
|
||||
Category: category,
|
||||
Name: model.NewId(),
|
||||
Value: "false",
|
||||
},
|
||||
{
|
||||
UserId: userId,
|
||||
Category: model.NewId(),
|
||||
Name: FEATURE_TOGGLE_PREFIX + feature1,
|
||||
Value: "false",
|
||||
},
|
||||
{
|
||||
UserId: model.NewId(),
|
||||
Category: category,
|
||||
Name: FEATURE_TOGGLE_PREFIX + feature2,
|
||||
Value: "false",
|
||||
},
|
||||
{
|
||||
UserId: model.NewId(),
|
||||
Category: category,
|
||||
Name: FEATURE_TOGGLE_PREFIX + feature3,
|
||||
Value: "foobar",
|
||||
},
|
||||
}
|
||||
|
||||
store.Must(ss.Preference().Save(&features))
|
||||
|
||||
if result := <-ss.Preference().IsFeatureEnabled(feature1, userId); result.Err != nil {
|
||||
t.Fatal(result.Err)
|
||||
} else if data := result.Data.(bool); data != true {
|
||||
t.Fatalf("got incorrect setting for feature1, %v=%v", true, data)
|
||||
}
|
||||
|
||||
if result := <-ss.Preference().IsFeatureEnabled(feature2, userId); result.Err != nil {
|
||||
t.Fatal(result.Err)
|
||||
} else if data := result.Data.(bool); data != false {
|
||||
t.Fatalf("got incorrect setting for feature2, %v=%v", false, data)
|
||||
}
|
||||
|
||||
// make sure we get false if something different than "true" or "false" has been saved to database
|
||||
if result := <-ss.Preference().IsFeatureEnabled(feature3, userId); result.Err != nil {
|
||||
t.Fatal(result.Err)
|
||||
} else if data := result.Data.(bool); data != false {
|
||||
t.Fatalf("got incorrect setting for feature3, %v=%v", false, data)
|
||||
}
|
||||
|
||||
// make sure false is returned if a non-existent feature is queried
|
||||
if result := <-ss.Preference().IsFeatureEnabled("someOtherFeature", userId); result.Err != nil {
|
||||
t.Fatal(result.Err)
|
||||
} else if data := result.Data.(bool); data != false {
|
||||
t.Fatalf("got incorrect setting for non-existent feature 'someOtherFeature', %v=%v", false, data)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDeleteUnusedFeatures(t *testing.T) {
|
||||
ss := Setup()
|
||||
|
||||
userId1 := model.NewId()
|
||||
userId2 := model.NewId()
|
||||
category := model.PREFERENCE_CATEGORY_ADVANCED_SETTINGS
|
||||
feature1 := "feature1"
|
||||
feature2 := "feature2"
|
||||
|
||||
features := model.Preferences{
|
||||
{
|
||||
UserId: userId1,
|
||||
Category: category,
|
||||
Name: FEATURE_TOGGLE_PREFIX + feature1,
|
||||
Value: "true",
|
||||
},
|
||||
{
|
||||
UserId: userId2,
|
||||
Category: category,
|
||||
Name: FEATURE_TOGGLE_PREFIX + feature1,
|
||||
Value: "false",
|
||||
},
|
||||
{
|
||||
UserId: userId1,
|
||||
Category: category,
|
||||
Name: FEATURE_TOGGLE_PREFIX + feature2,
|
||||
Value: "false",
|
||||
},
|
||||
{
|
||||
UserId: userId2,
|
||||
Category: category,
|
||||
Name: FEATURE_TOGGLE_PREFIX + feature2,
|
||||
Value: "true",
|
||||
},
|
||||
}
|
||||
|
||||
store.Must(ss.Preference().Save(&features))
|
||||
|
||||
ss.Preference().(*SqlPreferenceStore).DeleteUnusedFeatures()
|
||||
|
||||
//make sure features with value "false" have actually been deleted from the database
|
||||
if val, err := ss.Preference().(*SqlPreferenceStore).GetReplica().SelectInt(`SELECT COUNT(*)
|
||||
FROM Preferences
|
||||
WHERE Category = :Category
|
||||
AND Value = :Val
|
||||
AND Name LIKE '`+FEATURE_TOGGLE_PREFIX+`%'`, map[string]interface{}{"Category": model.PREFERENCE_CATEGORY_ADVANCED_SETTINGS, "Val": "false"}); err != nil {
|
||||
t.Fatal(err)
|
||||
} else if val != 0 {
|
||||
t.Fatalf("Found %d features with value 'false', expected all to be deleted", val)
|
||||
}
|
||||
//
|
||||
// make sure features with value "true" remain saved
|
||||
if val, err := ss.Preference().(*SqlPreferenceStore).GetReplica().SelectInt(`SELECT COUNT(*)
|
||||
FROM Preferences
|
||||
WHERE Category = :Category
|
||||
AND Value = :Val
|
||||
AND Name LIKE '`+FEATURE_TOGGLE_PREFIX+`%'`, map[string]interface{}{"Category": model.PREFERENCE_CATEGORY_ADVANCED_SETTINGS, "Val": "true"}); err != nil {
|
||||
t.Fatal(err)
|
||||
} else if val == 0 {
|
||||
t.Fatalf("Found %d features with value 'true', expected to find at least %d features", val, 2)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPreferenceDelete(t *testing.T) {
|
||||
ss := Setup()
|
||||
|
||||
preference := model.Preference{
|
||||
UserId: model.NewId(),
|
||||
Category: model.PREFERENCE_CATEGORY_DIRECT_CHANNEL_SHOW,
|
||||
Name: model.NewId(),
|
||||
Value: "value1a",
|
||||
}
|
||||
|
||||
store.Must(ss.Preference().Save(&model.Preferences{preference}))
|
||||
|
||||
if prefs := store.Must(ss.Preference().GetAll(preference.UserId)).(model.Preferences); len([]model.Preference(prefs)) != 1 {
|
||||
t.Fatal("should've returned 1 preference")
|
||||
}
|
||||
|
||||
if result := <-ss.Preference().Delete(preference.UserId, preference.Category, preference.Name); result.Err != nil {
|
||||
t.Fatal(result.Err)
|
||||
}
|
||||
|
||||
if prefs := store.Must(ss.Preference().GetAll(preference.UserId)).(model.Preferences); len([]model.Preference(prefs)) != 0 {
|
||||
t.Fatal("should've returned no preferences")
|
||||
}
|
||||
}
|
||||
|
||||
func TestPreferenceDeleteCategory(t *testing.T) {
|
||||
ss := Setup()
|
||||
|
||||
category := model.NewId()
|
||||
userId := model.NewId()
|
||||
|
||||
preference1 := model.Preference{
|
||||
UserId: userId,
|
||||
Category: category,
|
||||
Name: model.NewId(),
|
||||
Value: "value1a",
|
||||
}
|
||||
|
||||
preference2 := model.Preference{
|
||||
UserId: userId,
|
||||
Category: category,
|
||||
Name: model.NewId(),
|
||||
Value: "value1a",
|
||||
}
|
||||
|
||||
store.Must(ss.Preference().Save(&model.Preferences{preference1, preference2}))
|
||||
|
||||
if prefs := store.Must(ss.Preference().GetAll(userId)).(model.Preferences); len([]model.Preference(prefs)) != 2 {
|
||||
t.Fatal("should've returned 2 preferences")
|
||||
}
|
||||
|
||||
if result := <-ss.Preference().DeleteCategory(userId, category); result.Err != nil {
|
||||
t.Fatal(result.Err)
|
||||
}
|
||||
|
||||
if prefs := store.Must(ss.Preference().GetAll(userId)).(model.Preferences); len([]model.Preference(prefs)) != 0 {
|
||||
t.Fatal("should've returned no preferences")
|
||||
}
|
||||
}
|
||||
|
||||
func TestPreferenceDeleteCategoryAndName(t *testing.T) {
|
||||
ss := Setup()
|
||||
|
||||
category := model.NewId()
|
||||
name := model.NewId()
|
||||
userId := model.NewId()
|
||||
userId2 := model.NewId()
|
||||
|
||||
preference1 := model.Preference{
|
||||
UserId: userId,
|
||||
Category: category,
|
||||
Name: name,
|
||||
Value: "value1a",
|
||||
}
|
||||
|
||||
preference2 := model.Preference{
|
||||
UserId: userId2,
|
||||
Category: category,
|
||||
Name: name,
|
||||
Value: "value1a",
|
||||
}
|
||||
|
||||
store.Must(ss.Preference().Save(&model.Preferences{preference1, preference2}))
|
||||
|
||||
if prefs := store.Must(ss.Preference().GetAll(userId)).(model.Preferences); len([]model.Preference(prefs)) != 1 {
|
||||
t.Fatal("should've returned 1 preference")
|
||||
}
|
||||
|
||||
if prefs := store.Must(ss.Preference().GetAll(userId2)).(model.Preferences); len([]model.Preference(prefs)) != 1 {
|
||||
t.Fatal("should've returned 1 preference")
|
||||
}
|
||||
|
||||
if result := <-ss.Preference().DeleteCategoryAndName(category, name); result.Err != nil {
|
||||
t.Fatal(result.Err)
|
||||
}
|
||||
|
||||
if prefs := store.Must(ss.Preference().GetAll(userId)).(model.Preferences); len([]model.Preference(prefs)) != 0 {
|
||||
t.Fatal("should've returned no preferences")
|
||||
}
|
||||
|
||||
if prefs := store.Must(ss.Preference().GetAll(userId2)).(model.Preferences); len([]model.Preference(prefs)) != 0 {
|
||||
t.Fatal("should've returned no preferences")
|
||||
}
|
||||
}
|
||||
|
||||
func TestPreferenceCleanupFlagsBatch(t *testing.T) {
|
||||
ss := Setup()
|
||||
|
||||
category := model.PREFERENCE_CATEGORY_FLAGGED_POST
|
||||
userId := model.NewId()
|
||||
|
||||
o1 := &model.Post{}
|
||||
o1.ChannelId = model.NewId()
|
||||
o1.UserId = userId
|
||||
o1.Message = "zz" + model.NewId() + "AAAAAAAAAAA"
|
||||
o1.CreateAt = 1000
|
||||
o1 = (<-ss.Post().Save(o1)).Data.(*model.Post)
|
||||
|
||||
preference1 := model.Preference{
|
||||
UserId: userId,
|
||||
Category: category,
|
||||
Name: o1.Id,
|
||||
Value: "true",
|
||||
}
|
||||
|
||||
preference2 := model.Preference{
|
||||
UserId: userId,
|
||||
Category: category,
|
||||
Name: model.NewId(),
|
||||
Value: "true",
|
||||
}
|
||||
|
||||
store.Must(ss.Preference().Save(&model.Preferences{preference1, preference2}))
|
||||
|
||||
result := <-ss.Preference().CleanupFlagsBatch(10000)
|
||||
assert.Nil(t, result.Err)
|
||||
|
||||
result = <-ss.Preference().Get(userId, category, preference1.Name)
|
||||
assert.Nil(t, result.Err)
|
||||
|
||||
result = <-ss.Preference().Get(userId, category, preference2.Name)
|
||||
assert.NotNil(t, result.Err)
|
||||
}
|
||||
352
store/sqlstore/reaction_store_test.go
Обычный файл
352
store/sqlstore/reaction_store_test.go
Обычный файл
@@ -0,0 +1,352 @@
|
||||
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
|
||||
// See License.txt for license information.
|
||||
|
||||
package sqlstore
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/mattermost/mattermost-server/model"
|
||||
"github.com/mattermost/mattermost-server/store"
|
||||
)
|
||||
|
||||
func TestReactionSave(t *testing.T) {
|
||||
ss := Setup()
|
||||
|
||||
post := store.Must(ss.Post().Save(&model.Post{
|
||||
ChannelId: model.NewId(),
|
||||
UserId: model.NewId(),
|
||||
})).(*model.Post)
|
||||
firstUpdateAt := post.UpdateAt
|
||||
|
||||
reaction1 := &model.Reaction{
|
||||
UserId: model.NewId(),
|
||||
PostId: post.Id,
|
||||
EmojiName: model.NewId(),
|
||||
}
|
||||
if result := <-ss.Reaction().Save(reaction1); result.Err != nil {
|
||||
t.Fatal(result.Err)
|
||||
} else if saved := result.Data.(*model.Reaction); saved.UserId != reaction1.UserId ||
|
||||
saved.PostId != reaction1.PostId || saved.EmojiName != reaction1.EmojiName {
|
||||
t.Fatal("should've saved reaction and returned it")
|
||||
}
|
||||
|
||||
var secondUpdateAt int64
|
||||
if postList := store.Must(ss.Post().Get(reaction1.PostId)).(*model.PostList); !postList.Posts[post.Id].HasReactions {
|
||||
t.Fatal("should've set HasReactions = true on post")
|
||||
} else if postList.Posts[post.Id].UpdateAt == firstUpdateAt {
|
||||
t.Fatal("should've marked post as updated when HasReactions changed")
|
||||
} else {
|
||||
secondUpdateAt = postList.Posts[post.Id].UpdateAt
|
||||
}
|
||||
|
||||
if result := <-ss.Reaction().Save(reaction1); result.Err != nil {
|
||||
t.Log(result.Err)
|
||||
t.Fatal("should've allowed saving a duplicate reaction")
|
||||
}
|
||||
|
||||
// different user
|
||||
reaction2 := &model.Reaction{
|
||||
UserId: model.NewId(),
|
||||
PostId: reaction1.PostId,
|
||||
EmojiName: reaction1.EmojiName,
|
||||
}
|
||||
if result := <-ss.Reaction().Save(reaction2); result.Err != nil {
|
||||
t.Fatal(result.Err)
|
||||
}
|
||||
|
||||
if postList := store.Must(ss.Post().Get(reaction2.PostId)).(*model.PostList); postList.Posts[post.Id].UpdateAt != secondUpdateAt {
|
||||
t.Fatal("shouldn't mark as updated when HasReactions hasn't changed")
|
||||
}
|
||||
|
||||
// different post
|
||||
reaction3 := &model.Reaction{
|
||||
UserId: reaction1.UserId,
|
||||
PostId: model.NewId(),
|
||||
EmojiName: reaction1.EmojiName,
|
||||
}
|
||||
if result := <-ss.Reaction().Save(reaction3); result.Err != nil {
|
||||
t.Fatal(result.Err)
|
||||
}
|
||||
|
||||
// different emoji
|
||||
reaction4 := &model.Reaction{
|
||||
UserId: reaction1.UserId,
|
||||
PostId: reaction1.PostId,
|
||||
EmojiName: model.NewId(),
|
||||
}
|
||||
if result := <-ss.Reaction().Save(reaction4); result.Err != nil {
|
||||
t.Fatal(result.Err)
|
||||
}
|
||||
|
||||
// invalid reaction
|
||||
reaction5 := &model.Reaction{
|
||||
UserId: reaction1.UserId,
|
||||
PostId: reaction1.PostId,
|
||||
}
|
||||
if result := <-ss.Reaction().Save(reaction5); result.Err == nil {
|
||||
t.Fatal("should've failed for invalid reaction")
|
||||
}
|
||||
}
|
||||
|
||||
func TestReactionDelete(t *testing.T) {
|
||||
ss := Setup()
|
||||
|
||||
post := store.Must(ss.Post().Save(&model.Post{
|
||||
ChannelId: model.NewId(),
|
||||
UserId: model.NewId(),
|
||||
})).(*model.Post)
|
||||
|
||||
reaction := &model.Reaction{
|
||||
UserId: model.NewId(),
|
||||
PostId: post.Id,
|
||||
EmojiName: model.NewId(),
|
||||
}
|
||||
|
||||
store.Must(ss.Reaction().Save(reaction))
|
||||
firstUpdateAt := store.Must(ss.Post().Get(reaction.PostId)).(*model.PostList).Posts[post.Id].UpdateAt
|
||||
|
||||
if result := <-ss.Reaction().Delete(reaction); result.Err != nil {
|
||||
t.Fatal(result.Err)
|
||||
}
|
||||
|
||||
if result := <-ss.Reaction().GetForPost(post.Id, false); result.Err != nil {
|
||||
t.Fatal(result.Err)
|
||||
} else if len(result.Data.([]*model.Reaction)) != 0 {
|
||||
t.Fatal("should've deleted reaction")
|
||||
}
|
||||
|
||||
if postList := store.Must(ss.Post().Get(post.Id)).(*model.PostList); postList.Posts[post.Id].HasReactions {
|
||||
t.Fatal("should've set HasReactions = false on post")
|
||||
} else if postList.Posts[post.Id].UpdateAt == firstUpdateAt {
|
||||
t.Fatal("shouldn't mark as updated when HasReactions has changed after deleting reactions")
|
||||
}
|
||||
}
|
||||
|
||||
func TestReactionGetForPost(t *testing.T) {
|
||||
ss := Setup()
|
||||
|
||||
postId := model.NewId()
|
||||
|
||||
userId := model.NewId()
|
||||
|
||||
reactions := []*model.Reaction{
|
||||
{
|
||||
UserId: userId,
|
||||
PostId: postId,
|
||||
EmojiName: "smile",
|
||||
},
|
||||
{
|
||||
UserId: model.NewId(),
|
||||
PostId: postId,
|
||||
EmojiName: "smile",
|
||||
},
|
||||
{
|
||||
UserId: userId,
|
||||
PostId: postId,
|
||||
EmojiName: "sad",
|
||||
},
|
||||
{
|
||||
UserId: userId,
|
||||
PostId: model.NewId(),
|
||||
EmojiName: "angry",
|
||||
},
|
||||
}
|
||||
|
||||
for _, reaction := range reactions {
|
||||
store.Must(ss.Reaction().Save(reaction))
|
||||
}
|
||||
|
||||
if result := <-ss.Reaction().GetForPost(postId, false); result.Err != nil {
|
||||
t.Fatal(result.Err)
|
||||
} else if returned := result.Data.([]*model.Reaction); len(returned) != 3 {
|
||||
t.Fatal("should've returned 3 reactions")
|
||||
} else {
|
||||
for _, reaction := range reactions {
|
||||
found := false
|
||||
|
||||
for _, returnedReaction := range returned {
|
||||
if returnedReaction.UserId == reaction.UserId && returnedReaction.PostId == reaction.PostId &&
|
||||
returnedReaction.EmojiName == reaction.EmojiName {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if !found && reaction.PostId == postId {
|
||||
t.Fatalf("should've returned reaction for post %v", reaction)
|
||||
} else if found && reaction.PostId != postId {
|
||||
t.Fatal("shouldn't have returned reaction for another post")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Should return cached item
|
||||
if result := <-ss.Reaction().GetForPost(postId, true); result.Err != nil {
|
||||
t.Fatal(result.Err)
|
||||
} else if returned := result.Data.([]*model.Reaction); len(returned) != 3 {
|
||||
t.Fatal("should've returned 3 reactions")
|
||||
} else {
|
||||
for _, reaction := range reactions {
|
||||
found := false
|
||||
|
||||
for _, returnedReaction := range returned {
|
||||
if returnedReaction.UserId == reaction.UserId && returnedReaction.PostId == reaction.PostId &&
|
||||
returnedReaction.EmojiName == reaction.EmojiName {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if !found && reaction.PostId == postId {
|
||||
t.Fatalf("should've returned reaction for post %v", reaction)
|
||||
} else if found && reaction.PostId != postId {
|
||||
t.Fatal("shouldn't have returned reaction for another post")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestReactionDeleteAllWithEmojiName(t *testing.T) {
|
||||
ss := Setup()
|
||||
|
||||
emojiToDelete := model.NewId()
|
||||
|
||||
post := store.Must(ss.Post().Save(&model.Post{
|
||||
ChannelId: model.NewId(),
|
||||
UserId: model.NewId(),
|
||||
})).(*model.Post)
|
||||
post2 := store.Must(ss.Post().Save(&model.Post{
|
||||
ChannelId: model.NewId(),
|
||||
UserId: model.NewId(),
|
||||
})).(*model.Post)
|
||||
post3 := store.Must(ss.Post().Save(&model.Post{
|
||||
ChannelId: model.NewId(),
|
||||
UserId: model.NewId(),
|
||||
})).(*model.Post)
|
||||
|
||||
userId := model.NewId()
|
||||
|
||||
reactions := []*model.Reaction{
|
||||
{
|
||||
UserId: userId,
|
||||
PostId: post.Id,
|
||||
EmojiName: emojiToDelete,
|
||||
},
|
||||
{
|
||||
UserId: model.NewId(),
|
||||
PostId: post.Id,
|
||||
EmojiName: emojiToDelete,
|
||||
},
|
||||
{
|
||||
UserId: userId,
|
||||
PostId: post.Id,
|
||||
EmojiName: "sad",
|
||||
},
|
||||
{
|
||||
UserId: userId,
|
||||
PostId: post2.Id,
|
||||
EmojiName: "angry",
|
||||
},
|
||||
{
|
||||
UserId: userId,
|
||||
PostId: post3.Id,
|
||||
EmojiName: emojiToDelete,
|
||||
},
|
||||
}
|
||||
|
||||
for _, reaction := range reactions {
|
||||
store.Must(ss.Reaction().Save(reaction))
|
||||
}
|
||||
|
||||
if result := <-ss.Reaction().DeleteAllWithEmojiName(emojiToDelete); result.Err != nil {
|
||||
t.Fatal(result.Err)
|
||||
}
|
||||
|
||||
// check that the reactions were deleted
|
||||
if returned := store.Must(ss.Reaction().GetForPost(post.Id, false)).([]*model.Reaction); len(returned) != 1 {
|
||||
t.Fatal("should've only removed reactions with emoji name")
|
||||
} else {
|
||||
for _, reaction := range returned {
|
||||
if reaction.EmojiName == "smile" {
|
||||
t.Fatal("should've removed reaction with emoji name")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if returned := store.Must(ss.Reaction().GetForPost(post2.Id, false)).([]*model.Reaction); len(returned) != 1 {
|
||||
t.Fatal("should've only removed reactions with emoji name")
|
||||
}
|
||||
|
||||
if returned := store.Must(ss.Reaction().GetForPost(post3.Id, false)).([]*model.Reaction); len(returned) != 0 {
|
||||
t.Fatal("should've only removed reactions with emoji name")
|
||||
}
|
||||
|
||||
// check that the posts are updated
|
||||
if postList := store.Must(ss.Post().Get(post.Id)).(*model.PostList); !postList.Posts[post.Id].HasReactions {
|
||||
t.Fatal("post should still have reactions")
|
||||
}
|
||||
|
||||
if postList := store.Must(ss.Post().Get(post2.Id)).(*model.PostList); !postList.Posts[post2.Id].HasReactions {
|
||||
t.Fatal("post should still have reactions")
|
||||
}
|
||||
|
||||
if postList := store.Must(ss.Post().Get(post3.Id)).(*model.PostList); postList.Posts[post3.Id].HasReactions {
|
||||
t.Fatal("post shouldn't have reactions any more")
|
||||
}
|
||||
}
|
||||
|
||||
func TestReactionStorePermanentDeleteBatch(t *testing.T) {
|
||||
ss := Setup()
|
||||
|
||||
post := store.Must(ss.Post().Save(&model.Post{
|
||||
ChannelId: model.NewId(),
|
||||
UserId: model.NewId(),
|
||||
})).(*model.Post)
|
||||
|
||||
reactions := []*model.Reaction{
|
||||
{
|
||||
UserId: model.NewId(),
|
||||
PostId: post.Id,
|
||||
EmojiName: "sad",
|
||||
CreateAt: 1000,
|
||||
},
|
||||
{
|
||||
UserId: model.NewId(),
|
||||
PostId: post.Id,
|
||||
EmojiName: "sad",
|
||||
CreateAt: 1500,
|
||||
},
|
||||
{
|
||||
UserId: model.NewId(),
|
||||
PostId: post.Id,
|
||||
EmojiName: "sad",
|
||||
CreateAt: 2000,
|
||||
},
|
||||
{
|
||||
UserId: model.NewId(),
|
||||
PostId: post.Id,
|
||||
EmojiName: "sad",
|
||||
CreateAt: 2000,
|
||||
},
|
||||
}
|
||||
|
||||
// Need to hang on to a reaction to delete later in order to clear the cache, as "allowFromCache" isn't honoured any more.
|
||||
var lastReaction *model.Reaction
|
||||
for _, reaction := range reactions {
|
||||
lastReaction = store.Must(ss.Reaction().Save(reaction)).(*model.Reaction)
|
||||
}
|
||||
|
||||
if returned := store.Must(ss.Reaction().GetForPost(post.Id, false)).([]*model.Reaction); len(returned) != 4 {
|
||||
t.Fatal("expected 4 reactions")
|
||||
}
|
||||
|
||||
store.Must(ss.Reaction().PermanentDeleteBatch(1800, 1000))
|
||||
|
||||
// This is to force a clear of the cache.
|
||||
store.Must(ss.Reaction().Delete(lastReaction))
|
||||
|
||||
if returned := store.Must(ss.Reaction().GetForPost(post.Id, false)).([]*model.Reaction); len(returned) != 1 {
|
||||
t.Fatalf("expected 1 reaction. Got: %v", len(returned))
|
||||
}
|
||||
}
|
||||
349
store/sqlstore/session_store.go
Обычный файл
349
store/sqlstore/session_store.go
Обычный файл
@@ -0,0 +1,349 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See License.txt for license information.
|
||||
|
||||
package sqlstore
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
l4g "github.com/alecthomas/log4go"
|
||||
|
||||
"github.com/mattermost/mattermost-server/model"
|
||||
"github.com/mattermost/mattermost-server/store"
|
||||
"github.com/mattermost/mattermost-server/utils"
|
||||
)
|
||||
|
||||
type SqlSessionStore struct {
|
||||
SqlStore
|
||||
}
|
||||
|
||||
func NewSqlSessionStore(sqlStore SqlStore) store.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("Token").SetMaxSize(26)
|
||||
table.ColMap("UserId").SetMaxSize(26)
|
||||
table.ColMap("DeviceId").SetMaxSize(512)
|
||||
table.ColMap("Roles").SetMaxSize(64)
|
||||
table.ColMap("Props").SetMaxSize(1000)
|
||||
}
|
||||
|
||||
return us
|
||||
}
|
||||
|
||||
func (me SqlSessionStore) CreateIndexesIfNotExists() {
|
||||
me.CreateIndexIfNotExists("idx_sessions_user_id", "Sessions", "UserId")
|
||||
me.CreateIndexIfNotExists("idx_sessions_token", "Sessions", "Token")
|
||||
me.CreateIndexIfNotExists("idx_sessions_expires_at", "Sessions", "ExpiresAt")
|
||||
me.CreateIndexIfNotExists("idx_sessions_create_at", "Sessions", "CreateAt")
|
||||
me.CreateIndexIfNotExists("idx_sessions_last_activity_at", "Sessions", "LastActivityAt")
|
||||
}
|
||||
|
||||
func (me SqlSessionStore) Save(session *model.Session) store.StoreChannel {
|
||||
|
||||
storeChannel := make(store.StoreChannel, 1)
|
||||
|
||||
go func() {
|
||||
result := store.StoreResult{}
|
||||
|
||||
if len(session.Id) > 0 {
|
||||
result.Err = model.NewAppError("SqlSessionStore.Save", "store.sql_session.save.existing.app_error", nil, "id="+session.Id, http.StatusBadRequest)
|
||||
storeChannel <- result
|
||||
close(storeChannel)
|
||||
return
|
||||
}
|
||||
|
||||
session.PreSave()
|
||||
|
||||
if cur := <-me.CleanUpExpiredSessions(session.UserId); cur.Err != nil {
|
||||
l4g.Error(utils.T("store.sql_session.save.cleanup.error"), cur.Err)
|
||||
}
|
||||
|
||||
tcs := me.Team().GetTeamsForUser(session.UserId)
|
||||
|
||||
if err := me.GetMaster().Insert(session); err != nil {
|
||||
result.Err = model.NewAppError("SqlSessionStore.Save", "store.sql_session.save.app_error", nil, "id="+session.Id+", "+err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
} else {
|
||||
result.Data = session
|
||||
}
|
||||
|
||||
if rtcs := <-tcs; rtcs.Err != nil {
|
||||
result.Err = model.NewAppError("SqlSessionStore.Save", "store.sql_session.save.app_error", nil, "id="+session.Id+", "+rtcs.Err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
} else {
|
||||
tempMembers := rtcs.Data.([]*model.TeamMember)
|
||||
session.TeamMembers = make([]*model.TeamMember, 0, len(tempMembers))
|
||||
for _, tm := range tempMembers {
|
||||
if tm.DeleteAt == 0 {
|
||||
session.TeamMembers = append(session.TeamMembers, tm)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
storeChannel <- result
|
||||
close(storeChannel)
|
||||
}()
|
||||
|
||||
return storeChannel
|
||||
}
|
||||
|
||||
func (me SqlSessionStore) Get(sessionIdOrToken string) store.StoreChannel {
|
||||
|
||||
storeChannel := make(store.StoreChannel, 1)
|
||||
|
||||
go func() {
|
||||
result := store.StoreResult{}
|
||||
|
||||
var sessions []*model.Session
|
||||
|
||||
if _, err := me.GetReplica().Select(&sessions, "SELECT * FROM Sessions WHERE Token = :Token OR Id = :Id LIMIT 1", map[string]interface{}{"Token": sessionIdOrToken, "Id": sessionIdOrToken}); err != nil {
|
||||
result.Err = model.NewAppError("SqlSessionStore.Get", "store.sql_session.get.app_error", nil, "sessionIdOrToken="+sessionIdOrToken+", "+err.Error(), http.StatusInternalServerError)
|
||||
} else if sessions == nil || len(sessions) == 0 {
|
||||
result.Err = model.NewAppError("SqlSessionStore.Get", "store.sql_session.get.app_error", nil, "sessionIdOrToken="+sessionIdOrToken, http.StatusNotFound)
|
||||
} else {
|
||||
result.Data = sessions[0]
|
||||
|
||||
tcs := me.Team().GetTeamsForUser(sessions[0].UserId)
|
||||
if rtcs := <-tcs; rtcs.Err != nil {
|
||||
result.Err = model.NewAppError("SqlSessionStore.Get", "store.sql_session.get.app_error", nil, "sessionIdOrToken="+sessionIdOrToken+", "+rtcs.Err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
} else {
|
||||
tempMembers := rtcs.Data.([]*model.TeamMember)
|
||||
sessions[0].TeamMembers = make([]*model.TeamMember, 0, len(tempMembers))
|
||||
for _, tm := range tempMembers {
|
||||
if tm.DeleteAt == 0 {
|
||||
sessions[0].TeamMembers = append(sessions[0].TeamMembers, tm)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
storeChannel <- result
|
||||
close(storeChannel)
|
||||
|
||||
}()
|
||||
|
||||
return storeChannel
|
||||
}
|
||||
|
||||
func (me SqlSessionStore) GetSessions(userId string) store.StoreChannel {
|
||||
storeChannel := make(store.StoreChannel, 1)
|
||||
|
||||
go func() {
|
||||
|
||||
if cur := <-me.CleanUpExpiredSessions(userId); cur.Err != nil {
|
||||
l4g.Error(utils.T("store.sql_session.get_sessions.error"), cur.Err)
|
||||
}
|
||||
|
||||
result := store.StoreResult{}
|
||||
var sessions []*model.Session
|
||||
|
||||
tcs := me.Team().GetTeamsForUser(userId)
|
||||
|
||||
if _, err := me.GetReplica().Select(&sessions, "SELECT * FROM Sessions WHERE UserId = :UserId ORDER BY LastActivityAt DESC", map[string]interface{}{"UserId": userId}); err != nil {
|
||||
result.Err = model.NewAppError("SqlSessionStore.GetSessions", "store.sql_session.get_sessions.app_error", nil, err.Error(), http.StatusInternalServerError)
|
||||
} else {
|
||||
|
||||
result.Data = sessions
|
||||
}
|
||||
|
||||
if rtcs := <-tcs; rtcs.Err != nil {
|
||||
result.Err = model.NewAppError("SqlSessionStore.GetSessions", "store.sql_session.get_sessions.app_error", nil, rtcs.Err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
} else {
|
||||
for _, session := range sessions {
|
||||
tempMembers := rtcs.Data.([]*model.TeamMember)
|
||||
session.TeamMembers = make([]*model.TeamMember, 0, len(tempMembers))
|
||||
for _, tm := range tempMembers {
|
||||
if tm.DeleteAt == 0 {
|
||||
session.TeamMembers = append(session.TeamMembers, tm)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
storeChannel <- result
|
||||
close(storeChannel)
|
||||
}()
|
||||
|
||||
return storeChannel
|
||||
}
|
||||
|
||||
func (me SqlSessionStore) GetSessionsWithActiveDeviceIds(userId string) store.StoreChannel {
|
||||
storeChannel := make(store.StoreChannel, 1)
|
||||
|
||||
go func() {
|
||||
|
||||
result := store.StoreResult{}
|
||||
var sessions []*model.Session
|
||||
|
||||
if _, err := me.GetReplica().Select(&sessions, "SELECT * FROM Sessions WHERE UserId = :UserId AND ExpiresAt != 0 AND :ExpiresAt <= ExpiresAt AND DeviceId != ''", map[string]interface{}{"UserId": userId, "ExpiresAt": model.GetMillis()}); err != nil {
|
||||
result.Err = model.NewAppError("SqlSessionStore.GetActiveSessionsWithDeviceIds", "store.sql_session.get_sessions.app_error", nil, err.Error(), http.StatusInternalServerError)
|
||||
} else {
|
||||
|
||||
result.Data = sessions
|
||||
}
|
||||
|
||||
storeChannel <- result
|
||||
close(storeChannel)
|
||||
}()
|
||||
|
||||
return storeChannel
|
||||
}
|
||||
|
||||
func (me SqlSessionStore) Remove(sessionIdOrToken string) store.StoreChannel {
|
||||
storeChannel := make(store.StoreChannel, 1)
|
||||
|
||||
go func() {
|
||||
result := store.StoreResult{}
|
||||
|
||||
_, err := me.GetMaster().Exec("DELETE FROM Sessions WHERE Id = :Id Or Token = :Token", map[string]interface{}{"Id": sessionIdOrToken, "Token": sessionIdOrToken})
|
||||
if err != nil {
|
||||
result.Err = model.NewAppError("SqlSessionStore.RemoveSession", "store.sql_session.remove.app_error", nil, "id="+sessionIdOrToken+", err="+err.Error(), http.StatusInternalServerError)
|
||||
}
|
||||
|
||||
storeChannel <- result
|
||||
close(storeChannel)
|
||||
}()
|
||||
|
||||
return storeChannel
|
||||
}
|
||||
|
||||
func (me SqlSessionStore) RemoveAllSessions() store.StoreChannel {
|
||||
storeChannel := make(store.StoreChannel, 1)
|
||||
|
||||
go func() {
|
||||
result := store.StoreResult{}
|
||||
|
||||
_, err := me.GetMaster().Exec("DELETE FROM Sessions")
|
||||
if err != nil {
|
||||
result.Err = model.NewAppError("SqlSessionStore.RemoveAllSessions", "store.sql_session.remove_all_sessions_for_team.app_error", nil, err.Error(), http.StatusInternalServerError)
|
||||
}
|
||||
|
||||
storeChannel <- result
|
||||
close(storeChannel)
|
||||
}()
|
||||
|
||||
return storeChannel
|
||||
}
|
||||
|
||||
func (me SqlSessionStore) PermanentDeleteSessionsByUser(userId string) store.StoreChannel {
|
||||
storeChannel := make(store.StoreChannel, 1)
|
||||
|
||||
go func() {
|
||||
result := store.StoreResult{}
|
||||
|
||||
_, err := me.GetMaster().Exec("DELETE FROM Sessions WHERE UserId = :UserId", map[string]interface{}{"UserId": userId})
|
||||
if err != nil {
|
||||
result.Err = model.NewAppError("SqlSessionStore.RemoveAllSessionsForUser", "store.sql_session.permanent_delete_sessions_by_user.app_error", nil, "id="+userId+", err="+err.Error(), http.StatusInternalServerError)
|
||||
}
|
||||
|
||||
storeChannel <- result
|
||||
close(storeChannel)
|
||||
}()
|
||||
|
||||
return storeChannel
|
||||
}
|
||||
|
||||
func (me SqlSessionStore) CleanUpExpiredSessions(userId string) store.StoreChannel {
|
||||
storeChannel := make(store.StoreChannel, 1)
|
||||
|
||||
go func() {
|
||||
result := store.StoreResult{}
|
||||
|
||||
if _, err := me.GetMaster().Exec("DELETE FROM Sessions WHERE UserId = :UserId AND ExpiresAt != 0 AND :ExpiresAt > ExpiresAt", map[string]interface{}{"UserId": userId, "ExpiresAt": model.GetMillis()}); err != nil {
|
||||
result.Err = model.NewAppError("SqlSessionStore.CleanUpExpiredSessions", "store.sql_session.cleanup_expired_sessions.app_error", nil, err.Error(), http.StatusInternalServerError)
|
||||
} else {
|
||||
result.Data = userId
|
||||
}
|
||||
|
||||
storeChannel <- result
|
||||
close(storeChannel)
|
||||
}()
|
||||
|
||||
return storeChannel
|
||||
}
|
||||
|
||||
func (me SqlSessionStore) UpdateLastActivityAt(sessionId string, time int64) store.StoreChannel {
|
||||
storeChannel := make(store.StoreChannel, 1)
|
||||
|
||||
go func() {
|
||||
result := store.StoreResult{}
|
||||
|
||||
if _, err := me.GetMaster().Exec("UPDATE Sessions SET LastActivityAt = :LastActivityAt WHERE Id = :Id", map[string]interface{}{"LastActivityAt": time, "Id": sessionId}); err != nil {
|
||||
result.Err = model.NewAppError("SqlSessionStore.UpdateLastActivityAt", "store.sql_session.update_last_activity.app_error", nil, "sessionId="+sessionId, http.StatusInternalServerError)
|
||||
} else {
|
||||
result.Data = sessionId
|
||||
}
|
||||
|
||||
storeChannel <- result
|
||||
close(storeChannel)
|
||||
}()
|
||||
|
||||
return storeChannel
|
||||
}
|
||||
|
||||
func (me SqlSessionStore) UpdateRoles(userId, roles string) store.StoreChannel {
|
||||
storeChannel := make(store.StoreChannel, 1)
|
||||
|
||||
go func() {
|
||||
result := store.StoreResult{}
|
||||
if _, err := me.GetMaster().Exec("UPDATE Sessions SET Roles = :Roles WHERE UserId = :UserId", map[string]interface{}{"Roles": roles, "UserId": userId}); err != nil {
|
||||
result.Err = model.NewAppError("SqlSessionStore.UpdateRoles", "store.sql_session.update_roles.app_error", nil, "userId="+userId, http.StatusInternalServerError)
|
||||
} else {
|
||||
result.Data = userId
|
||||
}
|
||||
|
||||
storeChannel <- result
|
||||
close(storeChannel)
|
||||
}()
|
||||
|
||||
return storeChannel
|
||||
}
|
||||
|
||||
func (me SqlSessionStore) UpdateDeviceId(id string, deviceId string, expiresAt int64) store.StoreChannel {
|
||||
storeChannel := make(store.StoreChannel, 1)
|
||||
|
||||
go func() {
|
||||
result := store.StoreResult{}
|
||||
if _, err := me.GetMaster().Exec("UPDATE Sessions SET DeviceId = :DeviceId, ExpiresAt = :ExpiresAt WHERE Id = :Id", map[string]interface{}{"DeviceId": deviceId, "Id": id, "ExpiresAt": expiresAt}); err != nil {
|
||||
result.Err = model.NewAppError("SqlSessionStore.UpdateDeviceId", "store.sql_session.update_device_id.app_error", nil, err.Error(), http.StatusInternalServerError)
|
||||
} else {
|
||||
result.Data = deviceId
|
||||
}
|
||||
|
||||
storeChannel <- result
|
||||
close(storeChannel)
|
||||
}()
|
||||
|
||||
return storeChannel
|
||||
}
|
||||
|
||||
func (me SqlSessionStore) AnalyticsSessionCount() store.StoreChannel {
|
||||
storeChannel := make(store.StoreChannel, 1)
|
||||
|
||||
go func() {
|
||||
result := store.StoreResult{}
|
||||
|
||||
query :=
|
||||
`SELECT
|
||||
COUNT(*)
|
||||
FROM
|
||||
Sessions
|
||||
WHERE ExpiresAt > :Time`
|
||||
|
||||
if c, err := me.GetReplica().SelectInt(query, map[string]interface{}{"Time": model.GetMillis()}); err != nil {
|
||||
result.Err = model.NewAppError("SqlSessionStore.AnalyticsSessionCount", "store.sql_session.analytics_session_count.app_error", nil, err.Error(), http.StatusInternalServerError)
|
||||
} else {
|
||||
result.Data = c
|
||||
}
|
||||
|
||||
storeChannel <- result
|
||||
close(storeChannel)
|
||||
}()
|
||||
|
||||
return storeChannel
|
||||
}
|
||||
258
store/sqlstore/session_store_test.go
Обычный файл
258
store/sqlstore/session_store_test.go
Обычный файл
@@ -0,0 +1,258 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See License.txt for license information.
|
||||
|
||||
package sqlstore
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/mattermost/mattermost-server/model"
|
||||
"github.com/mattermost/mattermost-server/store"
|
||||
)
|
||||
|
||||
func TestSessionStoreSave(t *testing.T) {
|
||||
ss := Setup()
|
||||
|
||||
s1 := model.Session{}
|
||||
s1.UserId = model.NewId()
|
||||
|
||||
if err := (<-ss.Session().Save(&s1)).Err; err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSessionGet(t *testing.T) {
|
||||
ss := Setup()
|
||||
|
||||
s1 := model.Session{}
|
||||
s1.UserId = model.NewId()
|
||||
store.Must(ss.Session().Save(&s1))
|
||||
|
||||
s2 := model.Session{}
|
||||
s2.UserId = s1.UserId
|
||||
store.Must(ss.Session().Save(&s2))
|
||||
|
||||
s3 := model.Session{}
|
||||
s3.UserId = s1.UserId
|
||||
s3.ExpiresAt = 1
|
||||
store.Must(ss.Session().Save(&s3))
|
||||
|
||||
if rs1 := (<-ss.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 := (<-ss.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 TestSessionGetWithDeviceId(t *testing.T) {
|
||||
ss := Setup()
|
||||
|
||||
s1 := model.Session{}
|
||||
s1.UserId = model.NewId()
|
||||
s1.ExpiresAt = model.GetMillis() + 10000
|
||||
store.Must(ss.Session().Save(&s1))
|
||||
|
||||
s2 := model.Session{}
|
||||
s2.UserId = s1.UserId
|
||||
s2.DeviceId = model.NewId()
|
||||
s2.ExpiresAt = model.GetMillis() + 10000
|
||||
store.Must(ss.Session().Save(&s2))
|
||||
|
||||
s3 := model.Session{}
|
||||
s3.UserId = s1.UserId
|
||||
s3.ExpiresAt = 1
|
||||
s3.DeviceId = model.NewId()
|
||||
store.Must(ss.Session().Save(&s3))
|
||||
|
||||
if rs1 := (<-ss.Session().GetSessionsWithActiveDeviceIds(s1.UserId)); rs1.Err != nil {
|
||||
t.Fatal(rs1.Err)
|
||||
} else {
|
||||
if len(rs1.Data.([]*model.Session)) != 1 {
|
||||
t.Fatal("should match len")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestSessionRemove(t *testing.T) {
|
||||
ss := Setup()
|
||||
|
||||
s1 := model.Session{}
|
||||
s1.UserId = model.NewId()
|
||||
store.Must(ss.Session().Save(&s1))
|
||||
|
||||
if rs1 := (<-ss.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.Must(ss.Session().Remove(s1.Id))
|
||||
|
||||
if rs2 := (<-ss.Session().Get(s1.Id)); rs2.Err == nil {
|
||||
t.Fatal("should have been removed")
|
||||
}
|
||||
}
|
||||
|
||||
func TestSessionRemoveAll(t *testing.T) {
|
||||
ss := Setup()
|
||||
|
||||
s1 := model.Session{}
|
||||
s1.UserId = model.NewId()
|
||||
store.Must(ss.Session().Save(&s1))
|
||||
|
||||
if rs1 := (<-ss.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.Must(ss.Session().RemoveAllSessions())
|
||||
|
||||
if rs2 := (<-ss.Session().Get(s1.Id)); rs2.Err == nil {
|
||||
t.Fatal("should have been removed")
|
||||
}
|
||||
}
|
||||
|
||||
func TestSessionRemoveByUser(t *testing.T) {
|
||||
ss := Setup()
|
||||
|
||||
s1 := model.Session{}
|
||||
s1.UserId = model.NewId()
|
||||
store.Must(ss.Session().Save(&s1))
|
||||
|
||||
if rs1 := (<-ss.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.Must(ss.Session().PermanentDeleteSessionsByUser(s1.UserId))
|
||||
|
||||
if rs2 := (<-ss.Session().Get(s1.Id)); rs2.Err == nil {
|
||||
t.Fatal("should have been removed")
|
||||
}
|
||||
}
|
||||
|
||||
func TestSessionRemoveToken(t *testing.T) {
|
||||
ss := Setup()
|
||||
|
||||
s1 := model.Session{}
|
||||
s1.UserId = model.NewId()
|
||||
store.Must(ss.Session().Save(&s1))
|
||||
|
||||
if rs1 := (<-ss.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.Must(ss.Session().Remove(s1.Token))
|
||||
|
||||
if rs2 := (<-ss.Session().Get(s1.Id)); rs2.Err == nil {
|
||||
t.Fatal("should have been removed")
|
||||
}
|
||||
|
||||
if rs3 := (<-ss.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 TestSessionUpdateDeviceId(t *testing.T) {
|
||||
ss := Setup()
|
||||
|
||||
s1 := model.Session{}
|
||||
s1.UserId = model.NewId()
|
||||
store.Must(ss.Session().Save(&s1))
|
||||
|
||||
if rs1 := (<-ss.Session().UpdateDeviceId(s1.Id, model.PUSH_NOTIFY_APPLE+":1234567890", s1.ExpiresAt)); rs1.Err != nil {
|
||||
t.Fatal(rs1.Err)
|
||||
}
|
||||
|
||||
s2 := model.Session{}
|
||||
s2.UserId = model.NewId()
|
||||
store.Must(ss.Session().Save(&s2))
|
||||
|
||||
if rs2 := (<-ss.Session().UpdateDeviceId(s2.Id, model.PUSH_NOTIFY_APPLE+":1234567890", s1.ExpiresAt)); rs2.Err != nil {
|
||||
t.Fatal(rs2.Err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSessionUpdateDeviceId2(t *testing.T) {
|
||||
ss := Setup()
|
||||
|
||||
s1 := model.Session{}
|
||||
s1.UserId = model.NewId()
|
||||
store.Must(ss.Session().Save(&s1))
|
||||
|
||||
if rs1 := (<-ss.Session().UpdateDeviceId(s1.Id, model.PUSH_NOTIFY_APPLE_REACT_NATIVE+":1234567890", s1.ExpiresAt)); rs1.Err != nil {
|
||||
t.Fatal(rs1.Err)
|
||||
}
|
||||
|
||||
s2 := model.Session{}
|
||||
s2.UserId = model.NewId()
|
||||
store.Must(ss.Session().Save(&s2))
|
||||
|
||||
if rs2 := (<-ss.Session().UpdateDeviceId(s2.Id, model.PUSH_NOTIFY_APPLE_REACT_NATIVE+":1234567890", s1.ExpiresAt)); rs2.Err != nil {
|
||||
t.Fatal(rs2.Err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSessionStoreUpdateLastActivityAt(t *testing.T) {
|
||||
ss := Setup()
|
||||
|
||||
s1 := model.Session{}
|
||||
s1.UserId = model.NewId()
|
||||
store.Must(ss.Session().Save(&s1))
|
||||
|
||||
if err := (<-ss.Session().UpdateLastActivityAt(s1.Id, 1234567890)).Err; err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if r1 := <-ss.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")
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestSessionCount(t *testing.T) {
|
||||
ss := Setup()
|
||||
|
||||
s1 := model.Session{}
|
||||
s1.UserId = model.NewId()
|
||||
s1.ExpiresAt = model.GetMillis() + 100000
|
||||
store.Must(ss.Session().Save(&s1))
|
||||
|
||||
if r1 := <-ss.Session().AnalyticsSessionCount(); r1.Err != nil {
|
||||
t.Fatal(r1.Err)
|
||||
} else {
|
||||
if r1.Data.(int64) == 0 {
|
||||
t.Fatal("should have at least 1 session")
|
||||
}
|
||||
}
|
||||
}
|
||||
245
store/sqlstore/status_store.go
Обычный файл
245
store/sqlstore/status_store.go
Обычный файл
@@ -0,0 +1,245 @@
|
||||
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
|
||||
// See License.txt for license information.
|
||||
|
||||
package sqlstore
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/mattermost/mattermost-server/model"
|
||||
"github.com/mattermost/mattermost-server/store"
|
||||
)
|
||||
|
||||
const (
|
||||
MISSING_STATUS_ERROR = "store.sql_status.get.missing.app_error"
|
||||
)
|
||||
|
||||
type SqlStatusStore struct {
|
||||
SqlStore
|
||||
}
|
||||
|
||||
func NewSqlStatusStore(sqlStore SqlStore) store.StatusStore {
|
||||
s := &SqlStatusStore{sqlStore}
|
||||
|
||||
for _, db := range sqlStore.GetAllConns() {
|
||||
table := db.AddTableWithName(model.Status{}, "Status").SetKeys(false, "UserId")
|
||||
table.ColMap("UserId").SetMaxSize(26)
|
||||
table.ColMap("Status").SetMaxSize(32)
|
||||
table.ColMap("ActiveChannel").SetMaxSize(26)
|
||||
}
|
||||
|
||||
return s
|
||||
}
|
||||
|
||||
func (s SqlStatusStore) CreateIndexesIfNotExists() {
|
||||
s.CreateIndexIfNotExists("idx_status_user_id", "Status", "UserId")
|
||||
s.CreateIndexIfNotExists("idx_status_status", "Status", "Status")
|
||||
}
|
||||
|
||||
func (s SqlStatusStore) SaveOrUpdate(status *model.Status) store.StoreChannel {
|
||||
storeChannel := make(store.StoreChannel, 1)
|
||||
|
||||
go func() {
|
||||
result := store.StoreResult{}
|
||||
|
||||
if err := s.GetReplica().SelectOne(&model.Status{}, "SELECT * FROM Status WHERE UserId = :UserId", map[string]interface{}{"UserId": status.UserId}); err == nil {
|
||||
if _, err := s.GetMaster().Update(status); err != nil {
|
||||
result.Err = model.NewAppError("SqlStatusStore.SaveOrUpdate", "store.sql_status.update.app_error", nil, err.Error(), http.StatusInternalServerError)
|
||||
}
|
||||
} else {
|
||||
if err := s.GetMaster().Insert(status); err != nil {
|
||||
if !(strings.Contains(err.Error(), "for key 'PRIMARY'") && strings.Contains(err.Error(), "Duplicate entry")) {
|
||||
result.Err = model.NewAppError("SqlStatusStore.SaveOrUpdate", "store.sql_status.save.app_error", nil, err.Error(), http.StatusInternalServerError)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
storeChannel <- result
|
||||
close(storeChannel)
|
||||
}()
|
||||
|
||||
return storeChannel
|
||||
}
|
||||
|
||||
func (s SqlStatusStore) Get(userId string) store.StoreChannel {
|
||||
storeChannel := make(store.StoreChannel, 1)
|
||||
|
||||
go func() {
|
||||
result := store.StoreResult{}
|
||||
|
||||
var status model.Status
|
||||
|
||||
if err := s.GetReplica().SelectOne(&status,
|
||||
`SELECT
|
||||
*
|
||||
FROM
|
||||
Status
|
||||
WHERE
|
||||
UserId = :UserId`, map[string]interface{}{"UserId": userId}); err != nil {
|
||||
if err == sql.ErrNoRows {
|
||||
result.Err = model.NewAppError("SqlStatusStore.Get", MISSING_STATUS_ERROR, nil, err.Error(), http.StatusNotFound)
|
||||
} else {
|
||||
result.Err = model.NewAppError("SqlStatusStore.Get", "store.sql_status.get.app_error", nil, err.Error(), http.StatusInternalServerError)
|
||||
}
|
||||
} else {
|
||||
result.Data = &status
|
||||
}
|
||||
|
||||
storeChannel <- result
|
||||
close(storeChannel)
|
||||
}()
|
||||
|
||||
return storeChannel
|
||||
}
|
||||
|
||||
func (s SqlStatusStore) GetByIds(userIds []string) store.StoreChannel {
|
||||
storeChannel := make(store.StoreChannel, 1)
|
||||
|
||||
go func() {
|
||||
result := store.StoreResult{}
|
||||
|
||||
props := make(map[string]interface{})
|
||||
idQuery := ""
|
||||
|
||||
for index, userId := range userIds {
|
||||
if len(idQuery) > 0 {
|
||||
idQuery += ", "
|
||||
}
|
||||
|
||||
props["userId"+strconv.Itoa(index)] = userId
|
||||
idQuery += ":userId" + strconv.Itoa(index)
|
||||
}
|
||||
|
||||
var statuses []*model.Status
|
||||
if _, err := s.GetReplica().Select(&statuses, "SELECT * FROM Status WHERE UserId IN ("+idQuery+")", props); err != nil {
|
||||
result.Err = model.NewAppError("SqlStatusStore.GetByIds", "store.sql_status.get.app_error", nil, err.Error(), http.StatusInternalServerError)
|
||||
} else {
|
||||
result.Data = statuses
|
||||
}
|
||||
|
||||
storeChannel <- result
|
||||
close(storeChannel)
|
||||
}()
|
||||
|
||||
return storeChannel
|
||||
}
|
||||
|
||||
func (s SqlStatusStore) GetOnlineAway() store.StoreChannel {
|
||||
storeChannel := make(store.StoreChannel, 1)
|
||||
|
||||
go func() {
|
||||
result := store.StoreResult{}
|
||||
|
||||
var statuses []*model.Status
|
||||
if _, err := s.GetReplica().Select(&statuses, "SELECT * FROM Status WHERE Status = :Online OR Status = :Away LIMIT 300", map[string]interface{}{"Online": model.STATUS_ONLINE, "Away": model.STATUS_AWAY}); err != nil {
|
||||
result.Err = model.NewAppError("SqlStatusStore.GetOnlineAway", "store.sql_status.get_online_away.app_error", nil, err.Error(), http.StatusInternalServerError)
|
||||
} else {
|
||||
result.Data = statuses
|
||||
}
|
||||
|
||||
storeChannel <- result
|
||||
close(storeChannel)
|
||||
}()
|
||||
|
||||
return storeChannel
|
||||
}
|
||||
|
||||
func (s SqlStatusStore) GetOnline() store.StoreChannel {
|
||||
storeChannel := make(store.StoreChannel, 1)
|
||||
|
||||
go func() {
|
||||
result := store.StoreResult{}
|
||||
|
||||
var statuses []*model.Status
|
||||
if _, err := s.GetReplica().Select(&statuses, "SELECT * FROM Status WHERE Status = :Online", map[string]interface{}{"Online": model.STATUS_ONLINE}); err != nil {
|
||||
result.Err = model.NewAppError("SqlStatusStore.GetOnline", "store.sql_status.get_online.app_error", nil, err.Error(), http.StatusInternalServerError)
|
||||
} else {
|
||||
result.Data = statuses
|
||||
}
|
||||
|
||||
storeChannel <- result
|
||||
close(storeChannel)
|
||||
}()
|
||||
|
||||
return storeChannel
|
||||
}
|
||||
|
||||
func (s SqlStatusStore) GetAllFromTeam(teamId string) store.StoreChannel {
|
||||
storeChannel := make(store.StoreChannel, 1)
|
||||
|
||||
go func() {
|
||||
result := store.StoreResult{}
|
||||
|
||||
var statuses []*model.Status
|
||||
if _, err := s.GetReplica().Select(&statuses,
|
||||
`SELECT s.* FROM Status AS s INNER JOIN
|
||||
TeamMembers AS tm ON tm.TeamId=:TeamId AND s.UserId=tm.UserId`, map[string]interface{}{"TeamId": teamId}); err != nil {
|
||||
result.Err = model.NewAppError("SqlStatusStore.GetAllFromTeam", "store.sql_status.get_team_statuses.app_error", nil, err.Error(), http.StatusInternalServerError)
|
||||
} else {
|
||||
result.Data = statuses
|
||||
}
|
||||
|
||||
storeChannel <- result
|
||||
close(storeChannel)
|
||||
}()
|
||||
|
||||
return storeChannel
|
||||
}
|
||||
|
||||
func (s SqlStatusStore) ResetAll() store.StoreChannel {
|
||||
storeChannel := make(store.StoreChannel, 1)
|
||||
|
||||
go func() {
|
||||
result := store.StoreResult{}
|
||||
|
||||
if _, err := s.GetMaster().Exec("UPDATE Status SET Status = :Status WHERE Manual = false", map[string]interface{}{"Status": model.STATUS_OFFLINE}); err != nil {
|
||||
result.Err = model.NewAppError("SqlStatusStore.ResetAll", "store.sql_status.reset_all.app_error", nil, "", http.StatusInternalServerError)
|
||||
}
|
||||
|
||||
storeChannel <- result
|
||||
close(storeChannel)
|
||||
}()
|
||||
|
||||
return storeChannel
|
||||
}
|
||||
|
||||
func (s SqlStatusStore) GetTotalActiveUsersCount() store.StoreChannel {
|
||||
storeChannel := make(store.StoreChannel, 1)
|
||||
|
||||
go func() {
|
||||
result := store.StoreResult{}
|
||||
|
||||
time := model.GetMillis() - (1000 * 60 * 60 * 24)
|
||||
|
||||
if count, err := s.GetReplica().SelectInt("SELECT COUNT(UserId) FROM Status WHERE LastActivityAt > :Time", map[string]interface{}{"Time": time}); err != nil {
|
||||
result.Err = model.NewAppError("SqlStatusStore.GetTotalActiveUsersCount", "store.sql_status.get_total_active_users_count.app_error", nil, err.Error(), http.StatusInternalServerError)
|
||||
} else {
|
||||
result.Data = count
|
||||
}
|
||||
|
||||
storeChannel <- result
|
||||
close(storeChannel)
|
||||
}()
|
||||
|
||||
return storeChannel
|
||||
}
|
||||
|
||||
func (s SqlStatusStore) UpdateLastActivityAt(userId string, lastActivityAt int64) store.StoreChannel {
|
||||
storeChannel := make(store.StoreChannel, 1)
|
||||
|
||||
go func() {
|
||||
result := store.StoreResult{}
|
||||
|
||||
if _, err := s.GetMaster().Exec("UPDATE Status SET LastActivityAt = :Time WHERE UserId = :UserId", map[string]interface{}{"UserId": userId, "Time": lastActivityAt}); err != nil {
|
||||
result.Err = model.NewAppError("SqlStatusStore.UpdateLastActivityAt", "store.sql_status.update_last_activity_at.app_error", nil, "", http.StatusInternalServerError)
|
||||
}
|
||||
|
||||
storeChannel <- result
|
||||
close(storeChannel)
|
||||
}()
|
||||
|
||||
return storeChannel
|
||||
}
|
||||
105
store/sqlstore/status_store_test.go
Обычный файл
105
store/sqlstore/status_store_test.go
Обычный файл
@@ -0,0 +1,105 @@
|
||||
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
|
||||
// See License.txt for license information.
|
||||
|
||||
package sqlstore
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/mattermost/mattermost-server/model"
|
||||
"github.com/mattermost/mattermost-server/store"
|
||||
)
|
||||
|
||||
func TestSqlStatusStore(t *testing.T) {
|
||||
ss := Setup()
|
||||
|
||||
status := &model.Status{UserId: model.NewId(), Status: model.STATUS_ONLINE, Manual: false, LastActivityAt: 0, ActiveChannel: ""}
|
||||
|
||||
if err := (<-ss.Status().SaveOrUpdate(status)).Err; err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
status.LastActivityAt = 10
|
||||
|
||||
if err := (<-ss.Status().SaveOrUpdate(status)).Err; err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if err := (<-ss.Status().Get(status.UserId)).Err; err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
status2 := &model.Status{UserId: model.NewId(), Status: model.STATUS_AWAY, Manual: false, LastActivityAt: 0, ActiveChannel: ""}
|
||||
if err := (<-ss.Status().SaveOrUpdate(status2)).Err; err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
status3 := &model.Status{UserId: model.NewId(), Status: model.STATUS_OFFLINE, Manual: false, LastActivityAt: 0, ActiveChannel: ""}
|
||||
if err := (<-ss.Status().SaveOrUpdate(status3)).Err; err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if result := <-ss.Status().GetOnlineAway(); result.Err != nil {
|
||||
t.Fatal(result.Err)
|
||||
} else {
|
||||
statuses := result.Data.([]*model.Status)
|
||||
for _, status := range statuses {
|
||||
if status.Status == model.STATUS_OFFLINE {
|
||||
t.Fatal("should not have returned offline statuses")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if result := <-ss.Status().GetOnline(); result.Err != nil {
|
||||
t.Fatal(result.Err)
|
||||
} else {
|
||||
statuses := result.Data.([]*model.Status)
|
||||
for _, status := range statuses {
|
||||
if status.Status != model.STATUS_ONLINE {
|
||||
t.Fatal("should not have returned offline statuses")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if result := <-ss.Status().GetByIds([]string{status.UserId, "junk"}); result.Err != nil {
|
||||
t.Fatal(result.Err)
|
||||
} else {
|
||||
statuses := result.Data.([]*model.Status)
|
||||
if len(statuses) != 1 {
|
||||
t.Fatal("should only have 1 status")
|
||||
}
|
||||
}
|
||||
|
||||
if err := (<-ss.Status().ResetAll()).Err; err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if result := <-ss.Status().Get(status.UserId); result.Err != nil {
|
||||
t.Fatal(result.Err)
|
||||
} else {
|
||||
status := result.Data.(*model.Status)
|
||||
if status.Status != model.STATUS_OFFLINE {
|
||||
t.Fatal("should be offline")
|
||||
}
|
||||
}
|
||||
|
||||
if result := <-ss.Status().UpdateLastActivityAt(status.UserId, 10); result.Err != nil {
|
||||
t.Fatal(result.Err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestActiveUserCount(t *testing.T) {
|
||||
ss := Setup()
|
||||
|
||||
status := &model.Status{UserId: model.NewId(), Status: model.STATUS_ONLINE, Manual: false, LastActivityAt: model.GetMillis(), ActiveChannel: ""}
|
||||
store.Must(ss.Status().SaveOrUpdate(status))
|
||||
|
||||
if result := <-ss.Status().GetTotalActiveUsersCount(); result.Err != nil {
|
||||
t.Fatal(result.Err)
|
||||
} else {
|
||||
count := result.Data.(int64)
|
||||
if count <= 0 {
|
||||
t.Fatal()
|
||||
}
|
||||
}
|
||||
}
|
||||
87
store/sqlstore/store.go
Обычный файл
87
store/sqlstore/store.go
Обычный файл
@@ -0,0 +1,87 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See License.txt for license information.
|
||||
|
||||
package sqlstore
|
||||
|
||||
import (
|
||||
_ "github.com/go-sql-driver/mysql"
|
||||
_ "github.com/lib/pq"
|
||||
"github.com/mattermost/gorp"
|
||||
|
||||
"github.com/mattermost/mattermost-server/store"
|
||||
)
|
||||
|
||||
/*type SqlStore struct {
|
||||
master *gorp.DbMap
|
||||
replicas []*gorp.DbMap
|
||||
searchReplicas []*gorp.DbMap
|
||||
team TeamStore
|
||||
channel ChannelStore
|
||||
post PostStore
|
||||
user UserStore
|
||||
audit AuditStore
|
||||
compliance ComplianceStore
|
||||
session SessionStore
|
||||
oauth OAuthStore
|
||||
system SystemStore
|
||||
webhook WebhookStore
|
||||
command CommandStore
|
||||
preference PreferenceStore
|
||||
license LicenseStore
|
||||
token TokenStore
|
||||
emoji EmojiStore
|
||||
status StatusStore
|
||||
fileInfo FileInfoStore
|
||||
reaction ReactionStore
|
||||
jobStatus JobStatusStore
|
||||
SchemaVersion string
|
||||
rrCounter int64
|
||||
srCounter int64
|
||||
}*/
|
||||
|
||||
type SqlStore interface {
|
||||
GetCurrentSchemaVersion() string
|
||||
GetMaster() *gorp.DbMap
|
||||
GetSearchReplica() *gorp.DbMap
|
||||
GetReplica() *gorp.DbMap
|
||||
TotalMasterDbConnections() int
|
||||
TotalReadDbConnections() int
|
||||
TotalSearchDbConnections() int
|
||||
MarkSystemRanUnitTests()
|
||||
DoesTableExist(tablename string) bool
|
||||
DoesColumnExist(tableName string, columName string) bool
|
||||
CreateColumnIfNotExists(tableName string, columnName string, mySqlColType string, postgresColType string, defaultValue string) bool
|
||||
RemoveColumnIfExists(tableName string, columnName string) bool
|
||||
RemoveTableIfExists(tableName string) bool
|
||||
RenameColumnIfExists(tableName string, oldColumnName string, newColumnName string, colType string) bool
|
||||
GetMaxLengthOfColumnIfExists(tableName string, columnName string) string
|
||||
AlterColumnTypeIfExists(tableName string, columnName string, mySqlColType string, postgresColType string) bool
|
||||
CreateUniqueIndexIfNotExists(indexName string, tableName string, columnName string) bool
|
||||
CreateIndexIfNotExists(indexName string, tableName string, columnName string) bool
|
||||
CreateFullTextIndexIfNotExists(indexName string, tableName string, columnName string) bool
|
||||
RemoveIndexIfExists(indexName string, tableName string) bool
|
||||
GetAllConns() []*gorp.DbMap
|
||||
Close()
|
||||
Team() store.TeamStore
|
||||
Channel() store.ChannelStore
|
||||
Post() store.PostStore
|
||||
User() store.UserStore
|
||||
Audit() store.AuditStore
|
||||
ClusterDiscovery() store.ClusterDiscoveryStore
|
||||
Compliance() store.ComplianceStore
|
||||
Session() store.SessionStore
|
||||
OAuth() store.OAuthStore
|
||||
System() store.SystemStore
|
||||
Webhook() store.WebhookStore
|
||||
Command() store.CommandStore
|
||||
CommandWebhook() store.CommandWebhookStore
|
||||
Preference() store.PreferenceStore
|
||||
License() store.LicenseStore
|
||||
Token() store.TokenStore
|
||||
Emoji() store.EmojiStore
|
||||
Status() store.StatusStore
|
||||
FileInfo() store.FileInfoStore
|
||||
Reaction() store.ReactionStore
|
||||
Job() store.JobStore
|
||||
UserAccessToken() store.UserAccessTokenStore
|
||||
}
|
||||
151
store/sqlstore/store_test.go
Обычный файл
151
store/sqlstore/store_test.go
Обычный файл
@@ -0,0 +1,151 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See License.txt for license information.
|
||||
|
||||
package sqlstore
|
||||
|
||||
import (
|
||||
"github.com/mattermost/mattermost-server/store"
|
||||
"github.com/mattermost/mattermost-server/utils"
|
||||
)
|
||||
|
||||
var sqlStore store.Store
|
||||
|
||||
func Setup() store.Store {
|
||||
if sqlStore == nil {
|
||||
utils.TranslationsPreInit()
|
||||
utils.LoadConfig("config.json")
|
||||
utils.InitTranslations(utils.Cfg.LocalizationSettings)
|
||||
sqlStore = store.NewLayeredStore(NewSqlSupplier(nil), nil, nil)
|
||||
|
||||
sqlStore.MarkSystemRanUnitTests()
|
||||
}
|
||||
return sqlStore
|
||||
}
|
||||
|
||||
/*
|
||||
func TestSqlStore1(t *testing.T) {
|
||||
utils.TranslationsPreInit()
|
||||
utils.LoadConfig("config.json")
|
||||
utils.Cfg.SqlSettings.Trace = true
|
||||
|
||||
store := NewSqlStore()
|
||||
ss.Close()
|
||||
|
||||
utils.Cfg.SqlSettings.DataSourceReplicas = []string{utils.Cfg.SqlSettings.DataSource}
|
||||
|
||||
store = NewSqlStore()
|
||||
ss.TotalMasterDbConnections()
|
||||
ss.TotalReadDbConnections()
|
||||
ss.Close()
|
||||
|
||||
utils.LoadConfig("config.json")
|
||||
}
|
||||
|
||||
func TestAlertDbCmds(t *testing.T) {
|
||||
ss := Setup()
|
||||
|
||||
sqlStore := store.(SqlStore)
|
||||
|
||||
if !sqlStore.DoesTableExist("Systems") {
|
||||
t.Fatal("Failed table exists")
|
||||
}
|
||||
|
||||
if sqlStore.DoesColumnExist("Systems", "Test") {
|
||||
t.Fatal("Column should not exist")
|
||||
}
|
||||
|
||||
if !sqlStore.CreateColumnIfNotExists("Systems", "Test", "VARCHAR(50)", "VARCHAR(50)", "") {
|
||||
t.Fatal("Failed to create column")
|
||||
}
|
||||
|
||||
maxLen := sqlStore.GetMaxLengthOfColumnIfExists("Systems", "Test")
|
||||
|
||||
if maxLen != "50" {
|
||||
t.Fatal("Failed to get max length found " + maxLen)
|
||||
}
|
||||
|
||||
if !sqlStore.AlterColumnTypeIfExists("Systems", "Test", "VARCHAR(25)", "VARCHAR(25)") {
|
||||
t.Fatal("failed to alter column size")
|
||||
}
|
||||
|
||||
maxLen2 := sqlStore.GetMaxLengthOfColumnIfExists("Systems", "Test")
|
||||
|
||||
if maxLen2 != "25" {
|
||||
t.Fatal("Failed to get max length")
|
||||
}
|
||||
|
||||
if !sqlStore.RenameColumnIfExists("Systems", "Test", "Test1", "VARCHAR(25)") {
|
||||
t.Fatal("Failed to rename column")
|
||||
}
|
||||
|
||||
if sqlStore.DoesColumnExist("Systems", "Test") {
|
||||
t.Fatal("Column should not exist")
|
||||
}
|
||||
|
||||
if !sqlStore.DoesColumnExist("Systems", "Test1") {
|
||||
t.Fatal("Column should exist")
|
||||
}
|
||||
|
||||
sqlStore.CreateIndexIfNotExists("idx_systems_test1", "Systems", "Test1")
|
||||
sqlStore.RemoveIndexIfExists("idx_systems_test1", "Systems")
|
||||
|
||||
sqlStore.CreateFullTextIndexIfNotExists("idx_systems_test1", "Systems", "Test1")
|
||||
sqlStore.RemoveIndexIfExists("idx_systems_test1", "Systems")
|
||||
|
||||
if !sqlStore.RemoveColumnIfExists("Systems", "Test1") {
|
||||
t.Fatal("Failed to remove columns")
|
||||
}
|
||||
|
||||
if sqlStore.DoesColumnExist("Systems", "Test1") {
|
||||
t.Fatal("Column should not exist")
|
||||
}
|
||||
}
|
||||
|
||||
func TestCreateIndexIfNotExists(t *testing.T) {
|
||||
ss := Setup()
|
||||
|
||||
sqlStore := store.(SqlStore)
|
||||
|
||||
defer sqlStore.RemoveColumnIfExists("Systems", "Test")
|
||||
if !sqlStore.CreateColumnIfNotExists("Systems", "Test", "VARCHAR(50)", "VARCHAR(50)", "") {
|
||||
t.Fatal("Failed to create test column")
|
||||
}
|
||||
|
||||
defer sqlStore.RemoveIndexIfExists("idx_systems_create_index_test", "Systems")
|
||||
if !sqlStore.CreateIndexIfNotExists("idx_systems_create_index_test", "Systems", "Test") {
|
||||
t.Fatal("Should've created test index")
|
||||
}
|
||||
|
||||
if sqlStore.CreateIndexIfNotExists("idx_systems_create_index_test", "Systems", "Test") {
|
||||
t.Fatal("Shouldn't have created index that already exists")
|
||||
}
|
||||
}
|
||||
|
||||
func TestRemoveIndexIfExists(t *testing.T) {
|
||||
ss := Setup()
|
||||
|
||||
sqlStore := store.(SqlStore)
|
||||
|
||||
defer sqlStore.RemoveColumnIfExists("Systems", "Test")
|
||||
if !sqlStore.CreateColumnIfNotExists("Systems", "Test", "VARCHAR(50)", "VARCHAR(50)", "") {
|
||||
t.Fatal("Failed to create test column")
|
||||
}
|
||||
|
||||
if sqlStore.RemoveIndexIfExists("idx_systems_remove_index_test", "Systems") {
|
||||
t.Fatal("Should've failed to remove index that doesn't exist")
|
||||
}
|
||||
|
||||
defer sqlStore.RemoveIndexIfExists("idx_systems_remove_index_test", "Systems")
|
||||
if !sqlStore.CreateIndexIfNotExists("idx_systems_remove_index_test", "Systems", "Test") {
|
||||
t.Fatal("Should've created test index")
|
||||
}
|
||||
|
||||
if !sqlStore.RemoveIndexIfExists("idx_systems_remove_index_test", "Systems") {
|
||||
t.Fatal("Should've removed index that exists")
|
||||
}
|
||||
|
||||
if sqlStore.RemoveIndexIfExists("idx_systems_remove_index_test", "Systems") {
|
||||
t.Fatal("Should've failed to remove index that was already removed")
|
||||
}
|
||||
}
|
||||
*/
|
||||
884
store/sqlstore/supplier.go
Обычный файл
884
store/sqlstore/supplier.go
Обычный файл
@@ -0,0 +1,884 @@
|
||||
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
|
||||
// See License.txt for license information.
|
||||
|
||||
package sqlstore
|
||||
|
||||
import (
|
||||
"context"
|
||||
dbsql "database/sql"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
sqltrace "log"
|
||||
"os"
|
||||
"strings"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
l4g "github.com/alecthomas/log4go"
|
||||
"github.com/go-sql-driver/mysql"
|
||||
"github.com/lib/pq"
|
||||
"github.com/mattermost/gorp"
|
||||
"github.com/mattermost/mattermost-server/einterfaces"
|
||||
"github.com/mattermost/mattermost-server/model"
|
||||
"github.com/mattermost/mattermost-server/store"
|
||||
"github.com/mattermost/mattermost-server/utils"
|
||||
)
|
||||
|
||||
const (
|
||||
INDEX_TYPE_FULL_TEXT = "full_text"
|
||||
INDEX_TYPE_DEFAULT = "default"
|
||||
MAX_DB_CONN_LIFETIME = 60
|
||||
DB_PING_ATTEMPTS = 18
|
||||
DB_PING_TIMEOUT_SECS = 10
|
||||
)
|
||||
|
||||
const (
|
||||
EXIT_CREATE_TABLE = 100
|
||||
EXIT_DB_OPEN = 101
|
||||
EXIT_PING = 102
|
||||
EXIT_NO_DRIVER = 103
|
||||
EXIT_TABLE_EXISTS = 104
|
||||
EXIT_TABLE_EXISTS_MYSQL = 105
|
||||
EXIT_COLUMN_EXISTS = 106
|
||||
EXIT_DOES_COLUMN_EXISTS_POSTGRES = 107
|
||||
EXIT_DOES_COLUMN_EXISTS_MYSQL = 108
|
||||
EXIT_DOES_COLUMN_EXISTS_MISSING = 109
|
||||
EXIT_CREATE_COLUMN_POSTGRES = 110
|
||||
EXIT_CREATE_COLUMN_MYSQL = 111
|
||||
EXIT_CREATE_COLUMN_MISSING = 112
|
||||
EXIT_REMOVE_COLUMN = 113
|
||||
EXIT_RENAME_COLUMN = 114
|
||||
EXIT_MAX_COLUMN = 115
|
||||
EXIT_ALTER_COLUMN = 116
|
||||
EXIT_CREATE_INDEX_POSTGRES = 117
|
||||
EXIT_CREATE_INDEX_MYSQL = 118
|
||||
EXIT_CREATE_INDEX_FULL_MYSQL = 119
|
||||
EXIT_CREATE_INDEX_MISSING = 120
|
||||
EXIT_REMOVE_INDEX_POSTGRES = 121
|
||||
EXIT_REMOVE_INDEX_MYSQL = 122
|
||||
EXIT_REMOVE_INDEX_MISSING = 123
|
||||
EXIT_REMOVE_TABLE = 134
|
||||
)
|
||||
|
||||
type SqlSupplierOldStores struct {
|
||||
team store.TeamStore
|
||||
channel store.ChannelStore
|
||||
post store.PostStore
|
||||
user store.UserStore
|
||||
audit store.AuditStore
|
||||
cluster store.ClusterDiscoveryStore
|
||||
compliance store.ComplianceStore
|
||||
session store.SessionStore
|
||||
oauth store.OAuthStore
|
||||
system store.SystemStore
|
||||
webhook store.WebhookStore
|
||||
command store.CommandStore
|
||||
commandWebhook store.CommandWebhookStore
|
||||
preference store.PreferenceStore
|
||||
license store.LicenseStore
|
||||
token store.TokenStore
|
||||
emoji store.EmojiStore
|
||||
status store.StatusStore
|
||||
fileInfo store.FileInfoStore
|
||||
reaction store.ReactionStore
|
||||
job store.JobStore
|
||||
userAccessToken store.UserAccessTokenStore
|
||||
}
|
||||
|
||||
type SqlSupplier struct {
|
||||
// rrCounter and srCounter should be kept first.
|
||||
// See https://github.com/mattermost/mattermost-server/pull/7281
|
||||
rrCounter int64
|
||||
srCounter int64
|
||||
next store.LayeredStoreSupplier
|
||||
master *gorp.DbMap
|
||||
replicas []*gorp.DbMap
|
||||
searchReplicas []*gorp.DbMap
|
||||
oldStores SqlSupplierOldStores
|
||||
}
|
||||
|
||||
func NewSqlSupplier(metrics einterfaces.MetricsInterface) *SqlSupplier {
|
||||
supplier := &SqlSupplier{
|
||||
rrCounter: 0,
|
||||
srCounter: 0,
|
||||
}
|
||||
|
||||
supplier.initConnection()
|
||||
|
||||
supplier.oldStores.team = NewSqlTeamStore(supplier)
|
||||
supplier.oldStores.channel = NewSqlChannelStore(supplier, metrics)
|
||||
supplier.oldStores.post = NewSqlPostStore(supplier, metrics)
|
||||
supplier.oldStores.user = NewSqlUserStore(supplier, metrics)
|
||||
supplier.oldStores.audit = NewSqlAuditStore(supplier)
|
||||
supplier.oldStores.cluster = NewSqlClusterDiscoveryStore(supplier)
|
||||
supplier.oldStores.compliance = NewSqlComplianceStore(supplier)
|
||||
supplier.oldStores.session = NewSqlSessionStore(supplier)
|
||||
supplier.oldStores.oauth = NewSqlOAuthStore(supplier)
|
||||
supplier.oldStores.system = NewSqlSystemStore(supplier)
|
||||
supplier.oldStores.webhook = NewSqlWebhookStore(supplier, metrics)
|
||||
supplier.oldStores.command = NewSqlCommandStore(supplier)
|
||||
supplier.oldStores.commandWebhook = NewSqlCommandWebhookStore(supplier)
|
||||
supplier.oldStores.preference = NewSqlPreferenceStore(supplier)
|
||||
supplier.oldStores.license = NewSqlLicenseStore(supplier)
|
||||
supplier.oldStores.token = NewSqlTokenStore(supplier)
|
||||
supplier.oldStores.emoji = NewSqlEmojiStore(supplier, metrics)
|
||||
supplier.oldStores.status = NewSqlStatusStore(supplier)
|
||||
supplier.oldStores.fileInfo = NewSqlFileInfoStore(supplier, metrics)
|
||||
supplier.oldStores.job = NewSqlJobStore(supplier)
|
||||
supplier.oldStores.userAccessToken = NewSqlUserAccessTokenStore(supplier)
|
||||
|
||||
initSqlSupplierReactions(supplier)
|
||||
|
||||
err := supplier.GetMaster().CreateTablesIfNotExists()
|
||||
if err != nil {
|
||||
l4g.Critical(utils.T("store.sql.creating_tables.critical"), err)
|
||||
time.Sleep(time.Second)
|
||||
os.Exit(EXIT_CREATE_TABLE)
|
||||
}
|
||||
|
||||
UpgradeDatabase(supplier)
|
||||
|
||||
supplier.oldStores.team.(*SqlTeamStore).CreateIndexesIfNotExists()
|
||||
supplier.oldStores.channel.(*SqlChannelStore).CreateIndexesIfNotExists()
|
||||
supplier.oldStores.post.(*SqlPostStore).CreateIndexesIfNotExists()
|
||||
supplier.oldStores.user.(*SqlUserStore).CreateIndexesIfNotExists()
|
||||
supplier.oldStores.audit.(*SqlAuditStore).CreateIndexesIfNotExists()
|
||||
supplier.oldStores.compliance.(*SqlComplianceStore).CreateIndexesIfNotExists()
|
||||
supplier.oldStores.session.(*SqlSessionStore).CreateIndexesIfNotExists()
|
||||
supplier.oldStores.oauth.(*SqlOAuthStore).CreateIndexesIfNotExists()
|
||||
supplier.oldStores.system.(*SqlSystemStore).CreateIndexesIfNotExists()
|
||||
supplier.oldStores.webhook.(*SqlWebhookStore).CreateIndexesIfNotExists()
|
||||
supplier.oldStores.command.(*SqlCommandStore).CreateIndexesIfNotExists()
|
||||
supplier.oldStores.commandWebhook.(*SqlCommandWebhookStore).CreateIndexesIfNotExists()
|
||||
supplier.oldStores.preference.(*SqlPreferenceStore).CreateIndexesIfNotExists()
|
||||
supplier.oldStores.license.(*SqlLicenseStore).CreateIndexesIfNotExists()
|
||||
supplier.oldStores.token.(*SqlTokenStore).CreateIndexesIfNotExists()
|
||||
supplier.oldStores.emoji.(*SqlEmojiStore).CreateIndexesIfNotExists()
|
||||
supplier.oldStores.status.(*SqlStatusStore).CreateIndexesIfNotExists()
|
||||
supplier.oldStores.fileInfo.(*SqlFileInfoStore).CreateIndexesIfNotExists()
|
||||
supplier.oldStores.job.(*SqlJobStore).CreateIndexesIfNotExists()
|
||||
supplier.oldStores.userAccessToken.(*SqlUserAccessTokenStore).CreateIndexesIfNotExists()
|
||||
|
||||
supplier.oldStores.preference.(*SqlPreferenceStore).DeleteUnusedFeatures()
|
||||
|
||||
return supplier
|
||||
}
|
||||
|
||||
func (s *SqlSupplier) SetChainNext(next store.LayeredStoreSupplier) {
|
||||
s.next = next
|
||||
}
|
||||
|
||||
func (s *SqlSupplier) Next() store.LayeredStoreSupplier {
|
||||
return s.next
|
||||
}
|
||||
|
||||
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(utils.T("store.sql.open_conn.critical"), err)
|
||||
time.Sleep(time.Second)
|
||||
os.Exit(EXIT_DB_OPEN)
|
||||
}
|
||||
|
||||
for i := 0; i < DB_PING_ATTEMPTS; i++ {
|
||||
l4g.Info("Pinging SQL %v database", con_type)
|
||||
ctx, cancel := context.WithTimeout(context.Background(), DB_PING_TIMEOUT_SECS*time.Second)
|
||||
defer cancel()
|
||||
err = db.PingContext(ctx)
|
||||
if err == nil {
|
||||
break
|
||||
} else {
|
||||
if i == DB_PING_ATTEMPTS-1 {
|
||||
l4g.Critical("Failed to ping DB, server will exit err=%v", err)
|
||||
time.Sleep(time.Second)
|
||||
os.Exit(EXIT_PING)
|
||||
} else {
|
||||
l4g.Error("Failed to ping DB retrying in %v seconds err=%v", DB_PING_TIMEOUT_SECS, err)
|
||||
time.Sleep(DB_PING_TIMEOUT_SECS * time.Second)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
db.SetMaxIdleConns(maxIdle)
|
||||
db.SetMaxOpenConns(maxOpen)
|
||||
db.SetConnMaxLifetime(time.Duration(MAX_DB_CONN_LIFETIME) * time.Minute)
|
||||
|
||||
var dbmap *gorp.DbMap
|
||||
|
||||
connectionTimeout := time.Duration(*utils.Cfg.SqlSettings.QueryTimeout) * time.Second
|
||||
|
||||
if driver == "sqlite3" {
|
||||
dbmap = &gorp.DbMap{Db: db, TypeConverter: mattermConverter{}, Dialect: gorp.SqliteDialect{}, QueryTimeout: connectionTimeout}
|
||||
} else if driver == model.DATABASE_DRIVER_MYSQL {
|
||||
dbmap = &gorp.DbMap{Db: db, TypeConverter: mattermConverter{}, Dialect: gorp.MySQLDialect{Engine: "InnoDB", Encoding: "UTF8MB4"}, QueryTimeout: connectionTimeout}
|
||||
} else if driver == model.DATABASE_DRIVER_POSTGRES {
|
||||
dbmap = &gorp.DbMap{Db: db, TypeConverter: mattermConverter{}, Dialect: gorp.PostgresDialect{}, QueryTimeout: connectionTimeout}
|
||||
} else {
|
||||
l4g.Critical(utils.T("store.sql.dialect_driver.critical"))
|
||||
time.Sleep(time.Second)
|
||||
os.Exit(EXIT_NO_DRIVER)
|
||||
}
|
||||
|
||||
if trace {
|
||||
dbmap.TraceOn("", sqltrace.New(os.Stdout, "sql-trace:", sqltrace.Lmicroseconds))
|
||||
}
|
||||
|
||||
return dbmap
|
||||
}
|
||||
|
||||
func (s *SqlSupplier) initConnection() {
|
||||
s.master = setupConnection("master", *utils.Cfg.SqlSettings.DriverName,
|
||||
*utils.Cfg.SqlSettings.DataSource, *utils.Cfg.SqlSettings.MaxIdleConns,
|
||||
*utils.Cfg.SqlSettings.MaxOpenConns, utils.Cfg.SqlSettings.Trace)
|
||||
|
||||
if len(utils.Cfg.SqlSettings.DataSourceReplicas) == 0 {
|
||||
s.replicas = make([]*gorp.DbMap, 1)
|
||||
s.replicas[0] = s.master
|
||||
} else {
|
||||
s.replicas = make([]*gorp.DbMap, len(utils.Cfg.SqlSettings.DataSourceReplicas))
|
||||
for i, replica := range utils.Cfg.SqlSettings.DataSourceReplicas {
|
||||
s.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)
|
||||
}
|
||||
}
|
||||
|
||||
if len(utils.Cfg.SqlSettings.DataSourceSearchReplicas) == 0 {
|
||||
s.searchReplicas = s.replicas
|
||||
} else {
|
||||
s.searchReplicas = make([]*gorp.DbMap, len(utils.Cfg.SqlSettings.DataSourceSearchReplicas))
|
||||
for i, replica := range utils.Cfg.SqlSettings.DataSourceSearchReplicas {
|
||||
s.searchReplicas[i] = setupConnection(fmt.Sprintf("search-replica-%v", i), *utils.Cfg.SqlSettings.DriverName, replica,
|
||||
*utils.Cfg.SqlSettings.MaxIdleConns, *utils.Cfg.SqlSettings.MaxOpenConns,
|
||||
utils.Cfg.SqlSettings.Trace)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (ss *SqlSupplier) GetCurrentSchemaVersion() string {
|
||||
version, _ := ss.GetMaster().SelectStr("SELECT Value FROM Systems WHERE Name='Version'")
|
||||
return version
|
||||
}
|
||||
|
||||
func (ss *SqlSupplier) GetMaster() *gorp.DbMap {
|
||||
return ss.master
|
||||
}
|
||||
|
||||
func (ss *SqlSupplier) GetSearchReplica() *gorp.DbMap {
|
||||
rrNum := atomic.AddInt64(&ss.srCounter, 1) % int64(len(ss.searchReplicas))
|
||||
return ss.searchReplicas[rrNum]
|
||||
}
|
||||
|
||||
func (ss *SqlSupplier) GetReplica() *gorp.DbMap {
|
||||
rrNum := atomic.AddInt64(&ss.rrCounter, 1) % int64(len(ss.replicas))
|
||||
return ss.replicas[rrNum]
|
||||
}
|
||||
|
||||
func (ss *SqlSupplier) TotalMasterDbConnections() int {
|
||||
return ss.GetMaster().Db.Stats().OpenConnections
|
||||
}
|
||||
|
||||
func (ss *SqlSupplier) TotalReadDbConnections() int {
|
||||
|
||||
if len(utils.Cfg.SqlSettings.DataSourceReplicas) == 0 {
|
||||
return 0
|
||||
}
|
||||
|
||||
count := 0
|
||||
for _, db := range ss.replicas {
|
||||
count = count + db.Db.Stats().OpenConnections
|
||||
}
|
||||
|
||||
return count
|
||||
}
|
||||
|
||||
func (ss *SqlSupplier) TotalSearchDbConnections() int {
|
||||
if len(utils.Cfg.SqlSettings.DataSourceSearchReplicas) == 0 {
|
||||
return 0
|
||||
}
|
||||
|
||||
count := 0
|
||||
for _, db := range ss.searchReplicas {
|
||||
count = count + db.Db.Stats().OpenConnections
|
||||
}
|
||||
|
||||
return count
|
||||
}
|
||||
|
||||
func (ss *SqlSupplier) MarkSystemRanUnitTests() {
|
||||
if result := <-ss.System().Get(); result.Err == nil {
|
||||
props := result.Data.(model.StringMap)
|
||||
unitTests := props[model.SYSTEM_RAN_UNIT_TESTS]
|
||||
if len(unitTests) == 0 {
|
||||
systemTests := &model.System{Name: model.SYSTEM_RAN_UNIT_TESTS, Value: "1"}
|
||||
<-ss.System().Save(systemTests)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (ss *SqlSupplier) DoesTableExist(tableName string) bool {
|
||||
if *utils.Cfg.SqlSettings.DriverName == model.DATABASE_DRIVER_POSTGRES {
|
||||
count, err := ss.GetMaster().SelectInt(
|
||||
`SELECT count(relname) FROM pg_class WHERE relname=$1`,
|
||||
strings.ToLower(tableName),
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
l4g.Critical(utils.T("store.sql.table_exists.critical"), err)
|
||||
time.Sleep(time.Second)
|
||||
os.Exit(EXIT_TABLE_EXISTS)
|
||||
}
|
||||
|
||||
return count > 0
|
||||
|
||||
} else if *utils.Cfg.SqlSettings.DriverName == model.DATABASE_DRIVER_MYSQL {
|
||||
|
||||
count, err := ss.GetMaster().SelectInt(
|
||||
`SELECT
|
||||
COUNT(0) AS table_exists
|
||||
FROM
|
||||
information_schema.TABLES
|
||||
WHERE
|
||||
TABLE_SCHEMA = DATABASE()
|
||||
AND TABLE_NAME = ?
|
||||
`,
|
||||
tableName,
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
l4g.Critical(utils.T("store.sql.table_exists.critical"), err)
|
||||
time.Sleep(time.Second)
|
||||
os.Exit(EXIT_TABLE_EXISTS_MYSQL)
|
||||
}
|
||||
|
||||
return count > 0
|
||||
|
||||
} else {
|
||||
l4g.Critical(utils.T("store.sql.column_exists_missing_driver.critical"))
|
||||
time.Sleep(time.Second)
|
||||
os.Exit(EXIT_COLUMN_EXISTS)
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func (ss *SqlSupplier) DoesColumnExist(tableName string, columnName string) bool {
|
||||
if *utils.Cfg.SqlSettings.DriverName == model.DATABASE_DRIVER_POSTGRES {
|
||||
count, err := ss.GetMaster().SelectInt(
|
||||
`SELECT COUNT(0)
|
||||
FROM pg_attribute
|
||||
WHERE attrelid = $1::regclass
|
||||
AND attname = $2
|
||||
AND NOT attisdropped`,
|
||||
strings.ToLower(tableName),
|
||||
strings.ToLower(columnName),
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
if err.Error() == "pq: relation \""+strings.ToLower(tableName)+"\" does not exist" {
|
||||
return false
|
||||
}
|
||||
|
||||
l4g.Critical(utils.T("store.sql.column_exists.critical"), err)
|
||||
time.Sleep(time.Second)
|
||||
os.Exit(EXIT_DOES_COLUMN_EXISTS_POSTGRES)
|
||||
}
|
||||
|
||||
return count > 0
|
||||
|
||||
} else if *utils.Cfg.SqlSettings.DriverName == model.DATABASE_DRIVER_MYSQL {
|
||||
|
||||
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(utils.T("store.sql.column_exists.critical"), err)
|
||||
time.Sleep(time.Second)
|
||||
os.Exit(EXIT_DOES_COLUMN_EXISTS_MYSQL)
|
||||
}
|
||||
|
||||
return count > 0
|
||||
|
||||
} else {
|
||||
l4g.Critical(utils.T("store.sql.column_exists_missing_driver.critical"))
|
||||
time.Sleep(time.Second)
|
||||
os.Exit(EXIT_DOES_COLUMN_EXISTS_MISSING)
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func (ss *SqlSupplier) CreateColumnIfNotExists(tableName string, columnName string, mySqlColType string, postgresColType string, defaultValue string) bool {
|
||||
|
||||
if ss.DoesColumnExist(tableName, columnName) {
|
||||
return false
|
||||
}
|
||||
|
||||
if *utils.Cfg.SqlSettings.DriverName == model.DATABASE_DRIVER_POSTGRES {
|
||||
_, err := ss.GetMaster().ExecNoTimeout("ALTER TABLE " + tableName + " ADD " + columnName + " " + postgresColType + " DEFAULT '" + defaultValue + "'")
|
||||
if err != nil {
|
||||
l4g.Critical(utils.T("store.sql.create_column.critical"), err)
|
||||
time.Sleep(time.Second)
|
||||
os.Exit(EXIT_CREATE_COLUMN_POSTGRES)
|
||||
}
|
||||
|
||||
return true
|
||||
|
||||
} else if *utils.Cfg.SqlSettings.DriverName == model.DATABASE_DRIVER_MYSQL {
|
||||
_, err := ss.GetMaster().ExecNoTimeout("ALTER TABLE " + tableName + " ADD " + columnName + " " + mySqlColType + " DEFAULT '" + defaultValue + "'")
|
||||
if err != nil {
|
||||
l4g.Critical(utils.T("store.sql.create_column.critical"), err)
|
||||
time.Sleep(time.Second)
|
||||
os.Exit(EXIT_CREATE_COLUMN_MYSQL)
|
||||
}
|
||||
|
||||
return true
|
||||
|
||||
} else {
|
||||
l4g.Critical(utils.T("store.sql.create_column_missing_driver.critical"))
|
||||
time.Sleep(time.Second)
|
||||
os.Exit(EXIT_CREATE_COLUMN_MISSING)
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func (ss *SqlSupplier) RemoveColumnIfExists(tableName string, columnName string) bool {
|
||||
|
||||
if !ss.DoesColumnExist(tableName, columnName) {
|
||||
return false
|
||||
}
|
||||
|
||||
_, err := ss.GetMaster().ExecNoTimeout("ALTER TABLE " + tableName + " DROP COLUMN " + columnName)
|
||||
if err != nil {
|
||||
l4g.Critical("Failed to drop column %v", err)
|
||||
time.Sleep(time.Second)
|
||||
os.Exit(EXIT_REMOVE_COLUMN)
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func (ss *SqlSupplier) RemoveTableIfExists(tableName string) bool {
|
||||
if !ss.DoesTableExist(tableName) {
|
||||
return false
|
||||
}
|
||||
|
||||
_, err := ss.GetMaster().ExecNoTimeout("DROP TABLE " + tableName)
|
||||
if err != nil {
|
||||
l4g.Critical("Failed to drop table %v", err)
|
||||
time.Sleep(time.Second)
|
||||
os.Exit(EXIT_REMOVE_TABLE)
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func (ss *SqlSupplier) RenameColumnIfExists(tableName string, oldColumnName string, newColumnName string, colType string) bool {
|
||||
if !ss.DoesColumnExist(tableName, oldColumnName) {
|
||||
return false
|
||||
}
|
||||
|
||||
var err error
|
||||
if *utils.Cfg.SqlSettings.DriverName == model.DATABASE_DRIVER_MYSQL {
|
||||
_, err = ss.GetMaster().ExecNoTimeout("ALTER TABLE " + tableName + " CHANGE " + oldColumnName + " " + newColumnName + " " + colType)
|
||||
} else if *utils.Cfg.SqlSettings.DriverName == model.DATABASE_DRIVER_POSTGRES {
|
||||
_, err = ss.GetMaster().ExecNoTimeout("ALTER TABLE " + tableName + " RENAME COLUMN " + oldColumnName + " TO " + newColumnName)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
l4g.Critical(utils.T("store.sql.rename_column.critical"), err)
|
||||
time.Sleep(time.Second)
|
||||
os.Exit(EXIT_RENAME_COLUMN)
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func (ss *SqlSupplier) GetMaxLengthOfColumnIfExists(tableName string, columnName string) string {
|
||||
if !ss.DoesColumnExist(tableName, columnName) {
|
||||
return ""
|
||||
}
|
||||
|
||||
var result string
|
||||
var err error
|
||||
if *utils.Cfg.SqlSettings.DriverName == model.DATABASE_DRIVER_MYSQL {
|
||||
result, err = ss.GetMaster().SelectStr("SELECT CHARACTER_MAXIMUM_LENGTH FROM information_schema.columns WHERE table_name = '" + tableName + "' AND COLUMN_NAME = '" + columnName + "'")
|
||||
} else if *utils.Cfg.SqlSettings.DriverName == model.DATABASE_DRIVER_POSTGRES {
|
||||
result, err = ss.GetMaster().SelectStr("SELECT character_maximum_length FROM information_schema.columns WHERE table_name = '" + strings.ToLower(tableName) + "' AND column_name = '" + strings.ToLower(columnName) + "'")
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
l4g.Critical(utils.T("store.sql.maxlength_column.critical"), err)
|
||||
time.Sleep(time.Second)
|
||||
os.Exit(EXIT_MAX_COLUMN)
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
func (ss *SqlSupplier) AlterColumnTypeIfExists(tableName string, columnName string, mySqlColType string, postgresColType string) bool {
|
||||
if !ss.DoesColumnExist(tableName, columnName) {
|
||||
return false
|
||||
}
|
||||
|
||||
var err error
|
||||
if *utils.Cfg.SqlSettings.DriverName == model.DATABASE_DRIVER_MYSQL {
|
||||
_, err = ss.GetMaster().ExecNoTimeout("ALTER TABLE " + tableName + " MODIFY " + columnName + " " + mySqlColType)
|
||||
} else if *utils.Cfg.SqlSettings.DriverName == model.DATABASE_DRIVER_POSTGRES {
|
||||
_, err = ss.GetMaster().ExecNoTimeout("ALTER TABLE " + strings.ToLower(tableName) + " ALTER COLUMN " + strings.ToLower(columnName) + " TYPE " + postgresColType)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
l4g.Critical(utils.T("store.sql.alter_column_type.critical"), err)
|
||||
time.Sleep(time.Second)
|
||||
os.Exit(EXIT_ALTER_COLUMN)
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func (ss *SqlSupplier) CreateUniqueIndexIfNotExists(indexName string, tableName string, columnName string) bool {
|
||||
return ss.createIndexIfNotExists(indexName, tableName, columnName, INDEX_TYPE_DEFAULT, true)
|
||||
}
|
||||
|
||||
func (ss *SqlSupplier) CreateIndexIfNotExists(indexName string, tableName string, columnName string) bool {
|
||||
return ss.createIndexIfNotExists(indexName, tableName, columnName, INDEX_TYPE_DEFAULT, false)
|
||||
}
|
||||
|
||||
func (ss *SqlSupplier) CreateFullTextIndexIfNotExists(indexName string, tableName string, columnName string) bool {
|
||||
return ss.createIndexIfNotExists(indexName, tableName, columnName, INDEX_TYPE_FULL_TEXT, false)
|
||||
}
|
||||
|
||||
func (ss *SqlSupplier) createIndexIfNotExists(indexName string, tableName string, columnName string, indexType string, unique bool) bool {
|
||||
|
||||
uniqueStr := ""
|
||||
if unique {
|
||||
uniqueStr = "UNIQUE "
|
||||
}
|
||||
|
||||
if *utils.Cfg.SqlSettings.DriverName == model.DATABASE_DRIVER_POSTGRES {
|
||||
_, errExists := ss.GetMaster().SelectStr("SELECT $1::regclass", indexName)
|
||||
// It should fail if the index does not exist
|
||||
if errExists == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
query := ""
|
||||
if indexType == INDEX_TYPE_FULL_TEXT {
|
||||
postgresColumnNames := convertMySQLFullTextColumnsToPostgres(columnName)
|
||||
query = "CREATE INDEX " + indexName + " ON " + tableName + " USING gin(to_tsvector('english', " + postgresColumnNames + "))"
|
||||
} else {
|
||||
query = "CREATE " + uniqueStr + "INDEX " + indexName + " ON " + tableName + " (" + columnName + ")"
|
||||
}
|
||||
|
||||
_, err := ss.GetMaster().ExecNoTimeout(query)
|
||||
if err != nil {
|
||||
l4g.Critical(utils.T("store.sql.create_index.critical"), errExists)
|
||||
l4g.Critical(utils.T("store.sql.create_index.critical"), err)
|
||||
time.Sleep(time.Second)
|
||||
os.Exit(EXIT_CREATE_INDEX_POSTGRES)
|
||||
}
|
||||
} else if *utils.Cfg.SqlSettings.DriverName == model.DATABASE_DRIVER_MYSQL {
|
||||
|
||||
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(utils.T("store.sql.check_index.critical"), err)
|
||||
time.Sleep(time.Second)
|
||||
os.Exit(EXIT_CREATE_INDEX_MYSQL)
|
||||
}
|
||||
|
||||
if count > 0 {
|
||||
return false
|
||||
}
|
||||
|
||||
fullTextIndex := ""
|
||||
if indexType == INDEX_TYPE_FULL_TEXT {
|
||||
fullTextIndex = " FULLTEXT "
|
||||
}
|
||||
|
||||
_, err = ss.GetMaster().ExecNoTimeout("CREATE " + uniqueStr + fullTextIndex + " INDEX " + indexName + " ON " + tableName + " (" + columnName + ")")
|
||||
if err != nil {
|
||||
l4g.Critical(utils.T("store.sql.create_index.critical"), err)
|
||||
time.Sleep(time.Second)
|
||||
os.Exit(EXIT_CREATE_INDEX_FULL_MYSQL)
|
||||
}
|
||||
} else {
|
||||
l4g.Critical(utils.T("store.sql.create_index_missing_driver.critical"))
|
||||
time.Sleep(time.Second)
|
||||
os.Exit(EXIT_CREATE_INDEX_MISSING)
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func (ss *SqlSupplier) RemoveIndexIfExists(indexName string, tableName string) bool {
|
||||
|
||||
if *utils.Cfg.SqlSettings.DriverName == model.DATABASE_DRIVER_POSTGRES {
|
||||
_, err := ss.GetMaster().SelectStr("SELECT $1::regclass", indexName)
|
||||
// It should fail if the index does not exist
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
_, err = ss.GetMaster().ExecNoTimeout("DROP INDEX " + indexName)
|
||||
if err != nil {
|
||||
l4g.Critical(utils.T("store.sql.remove_index.critical"), err)
|
||||
time.Sleep(time.Second)
|
||||
os.Exit(EXIT_REMOVE_INDEX_POSTGRES)
|
||||
}
|
||||
|
||||
return true
|
||||
} else if *utils.Cfg.SqlSettings.DriverName == model.DATABASE_DRIVER_MYSQL {
|
||||
|
||||
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(utils.T("store.sql.check_index.critical"), err)
|
||||
time.Sleep(time.Second)
|
||||
os.Exit(EXIT_REMOVE_INDEX_MYSQL)
|
||||
}
|
||||
|
||||
if count <= 0 {
|
||||
return false
|
||||
}
|
||||
|
||||
_, err = ss.GetMaster().ExecNoTimeout("DROP INDEX " + indexName + " ON " + tableName)
|
||||
if err != nil {
|
||||
l4g.Critical(utils.T("store.sql.remove_index.critical"), err)
|
||||
time.Sleep(time.Second)
|
||||
os.Exit(EXIT_REMOVE_INDEX_MYSQL)
|
||||
}
|
||||
} else {
|
||||
l4g.Critical(utils.T("store.sql.create_index_missing_driver.critical"))
|
||||
time.Sleep(time.Second)
|
||||
os.Exit(EXIT_REMOVE_INDEX_MISSING)
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func IsUniqueConstraintError(err error, indexName []string) bool {
|
||||
unique := false
|
||||
if pqErr, ok := err.(*pq.Error); ok && pqErr.Code == "23505" {
|
||||
unique = true
|
||||
}
|
||||
|
||||
if mysqlErr, ok := err.(*mysql.MySQLError); ok && mysqlErr.Number == 1062 {
|
||||
unique = true
|
||||
}
|
||||
|
||||
field := false
|
||||
for _, contain := range indexName {
|
||||
if strings.Contains(err.Error(), contain) {
|
||||
field = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return unique && field
|
||||
}
|
||||
|
||||
func (ss *SqlSupplier) 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 *SqlSupplier) Close() {
|
||||
l4g.Info(utils.T("store.sql.closing.info"))
|
||||
ss.master.Db.Close()
|
||||
for _, replica := range ss.replicas {
|
||||
replica.Db.Close()
|
||||
}
|
||||
}
|
||||
|
||||
func (ss *SqlSupplier) Team() store.TeamStore {
|
||||
return ss.oldStores.team
|
||||
}
|
||||
|
||||
func (ss *SqlSupplier) Channel() store.ChannelStore {
|
||||
return ss.oldStores.channel
|
||||
}
|
||||
|
||||
func (ss *SqlSupplier) Post() store.PostStore {
|
||||
return ss.oldStores.post
|
||||
}
|
||||
|
||||
func (ss *SqlSupplier) User() store.UserStore {
|
||||
return ss.oldStores.user
|
||||
}
|
||||
|
||||
func (ss *SqlSupplier) Session() store.SessionStore {
|
||||
return ss.oldStores.session
|
||||
}
|
||||
|
||||
func (ss *SqlSupplier) Audit() store.AuditStore {
|
||||
return ss.oldStores.audit
|
||||
}
|
||||
|
||||
func (ss *SqlSupplier) ClusterDiscovery() store.ClusterDiscoveryStore {
|
||||
return ss.oldStores.cluster
|
||||
}
|
||||
|
||||
func (ss *SqlSupplier) Compliance() store.ComplianceStore {
|
||||
return ss.oldStores.compliance
|
||||
}
|
||||
|
||||
func (ss *SqlSupplier) OAuth() store.OAuthStore {
|
||||
return ss.oldStores.oauth
|
||||
}
|
||||
|
||||
func (ss *SqlSupplier) System() store.SystemStore {
|
||||
return ss.oldStores.system
|
||||
}
|
||||
|
||||
func (ss *SqlSupplier) Webhook() store.WebhookStore {
|
||||
return ss.oldStores.webhook
|
||||
}
|
||||
|
||||
func (ss *SqlSupplier) Command() store.CommandStore {
|
||||
return ss.oldStores.command
|
||||
}
|
||||
|
||||
func (ss *SqlSupplier) CommandWebhook() store.CommandWebhookStore {
|
||||
return ss.oldStores.commandWebhook
|
||||
}
|
||||
|
||||
func (ss *SqlSupplier) Preference() store.PreferenceStore {
|
||||
return ss.oldStores.preference
|
||||
}
|
||||
|
||||
func (ss *SqlSupplier) License() store.LicenseStore {
|
||||
return ss.oldStores.license
|
||||
}
|
||||
|
||||
func (ss *SqlSupplier) Token() store.TokenStore {
|
||||
return ss.oldStores.token
|
||||
}
|
||||
|
||||
func (ss *SqlSupplier) Emoji() store.EmojiStore {
|
||||
return ss.oldStores.emoji
|
||||
}
|
||||
|
||||
func (ss *SqlSupplier) Status() store.StatusStore {
|
||||
return ss.oldStores.status
|
||||
}
|
||||
|
||||
func (ss *SqlSupplier) FileInfo() store.FileInfoStore {
|
||||
return ss.oldStores.fileInfo
|
||||
}
|
||||
|
||||
func (ss *SqlSupplier) Reaction() store.ReactionStore {
|
||||
return ss.oldStores.reaction
|
||||
}
|
||||
|
||||
func (ss *SqlSupplier) Job() store.JobStore {
|
||||
return ss.oldStores.job
|
||||
}
|
||||
|
||||
func (ss *SqlSupplier) UserAccessToken() store.UserAccessTokenStore {
|
||||
return ss.oldStores.userAccessToken
|
||||
}
|
||||
|
||||
func (ss *SqlSupplier) DropAllTables() {
|
||||
ss.master.TruncateTables()
|
||||
}
|
||||
|
||||
type mattermConverter struct{}
|
||||
|
||||
func (me mattermConverter) ToDb(val interface{}) (interface{}, error) {
|
||||
|
||||
switch t := val.(type) {
|
||||
case model.StringMap:
|
||||
return model.MapToJson(t), nil
|
||||
case map[string]string:
|
||||
return model.MapToJson(model.StringMap(t)), nil
|
||||
case model.StringArray:
|
||||
return model.ArrayToJson(t), nil
|
||||
case model.StringInterface:
|
||||
return model.StringInterfaceToJson(t), nil
|
||||
case map[string]interface{}:
|
||||
return model.StringInterfaceToJson(model.StringInterface(t)), nil
|
||||
}
|
||||
|
||||
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(utils.T("store.sql.convert_string_map"))
|
||||
}
|
||||
b := []byte(*s)
|
||||
return json.Unmarshal(b, target)
|
||||
}
|
||||
return gorp.CustomScanner{Holder: new(string), Target: target, Binder: binder}, true
|
||||
case *map[string]string:
|
||||
binder := func(holder, target interface{}) error {
|
||||
s, ok := holder.(*string)
|
||||
if !ok {
|
||||
return errors.New(utils.T("store.sql.convert_string_map"))
|
||||
}
|
||||
b := []byte(*s)
|
||||
return json.Unmarshal(b, target)
|
||||
}
|
||||
return gorp.CustomScanner{Holder: new(string), Target: target, Binder: binder}, true
|
||||
case *model.StringArray:
|
||||
binder := func(holder, target interface{}) error {
|
||||
s, ok := holder.(*string)
|
||||
if !ok {
|
||||
return errors.New(utils.T("store.sql.convert_string_array"))
|
||||
}
|
||||
b := []byte(*s)
|
||||
return json.Unmarshal(b, target)
|
||||
}
|
||||
return gorp.CustomScanner{Holder: new(string), Target: target, Binder: binder}, true
|
||||
case *model.StringInterface:
|
||||
binder := func(holder, target interface{}) error {
|
||||
s, ok := holder.(*string)
|
||||
if !ok {
|
||||
return errors.New(utils.T("store.sql.convert_string_interface"))
|
||||
}
|
||||
b := []byte(*s)
|
||||
return json.Unmarshal(b, target)
|
||||
}
|
||||
return gorp.CustomScanner{Holder: new(string), Target: target, Binder: binder}, true
|
||||
case *map[string]interface{}:
|
||||
binder := func(holder, target interface{}) error {
|
||||
s, ok := holder.(*string)
|
||||
if !ok {
|
||||
return errors.New(utils.T("store.sql.convert_string_interface"))
|
||||
}
|
||||
b := []byte(*s)
|
||||
return json.Unmarshal(b, target)
|
||||
}
|
||||
return gorp.CustomScanner{Holder: new(string), Target: target, Binder: binder}, true
|
||||
}
|
||||
|
||||
return gorp.CustomScanner{}, false
|
||||
}
|
||||
|
||||
func convertMySQLFullTextColumnsToPostgres(columnNames string) string {
|
||||
columns := strings.Split(columnNames, ", ")
|
||||
concatenatedColumnNames := ""
|
||||
for i, c := range columns {
|
||||
concatenatedColumnNames += c
|
||||
if i < len(columns)-1 {
|
||||
concatenatedColumnNames += " || ' ' || "
|
||||
}
|
||||
}
|
||||
|
||||
return concatenatedColumnNames
|
||||
}
|
||||
213
store/sqlstore/supplier_reactions.go
Обычный файл
213
store/sqlstore/supplier_reactions.go
Обычный файл
@@ -0,0 +1,213 @@
|
||||
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
|
||||
// See License.txt for license information.
|
||||
|
||||
package sqlstore
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
|
||||
l4g "github.com/alecthomas/log4go"
|
||||
|
||||
"github.com/mattermost/gorp"
|
||||
"github.com/mattermost/mattermost-server/model"
|
||||
"github.com/mattermost/mattermost-server/store"
|
||||
"github.com/mattermost/mattermost-server/utils"
|
||||
)
|
||||
|
||||
func initSqlSupplierReactions(sqlStore SqlStore) {
|
||||
for _, db := range sqlStore.GetAllConns() {
|
||||
table := db.AddTableWithName(model.Reaction{}, "Reactions").SetKeys(false, "UserId", "PostId", "EmojiName")
|
||||
table.ColMap("UserId").SetMaxSize(26)
|
||||
table.ColMap("PostId").SetMaxSize(26)
|
||||
table.ColMap("EmojiName").SetMaxSize(64)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *SqlSupplier) ReactionSave(ctx context.Context, reaction *model.Reaction, hints ...store.LayeredStoreHint) *store.LayeredStoreSupplierResult {
|
||||
result := store.NewSupplierResult()
|
||||
|
||||
reaction.PreSave()
|
||||
if result.Err = reaction.IsValid(); result.Err != nil {
|
||||
return result
|
||||
}
|
||||
|
||||
if transaction, err := s.GetMaster().Begin(); err != nil {
|
||||
result.Err = model.NewAppError("SqlReactionStore.Save", "store.sql_reaction.save.begin.app_error", nil, err.Error(), http.StatusInternalServerError)
|
||||
} else {
|
||||
err := saveReactionAndUpdatePost(transaction, reaction)
|
||||
|
||||
if err != nil {
|
||||
transaction.Rollback()
|
||||
|
||||
// We don't consider duplicated save calls as an error
|
||||
if !IsUniqueConstraintError(err, []string{"reactions_pkey", "PRIMARY"}) {
|
||||
result.Err = model.NewAppError("SqlPreferenceStore.Save", "store.sql_reaction.save.save.app_error", nil, err.Error(), http.StatusBadRequest)
|
||||
}
|
||||
} else {
|
||||
if err := transaction.Commit(); err != nil {
|
||||
// don't need to rollback here since the transaction is already closed
|
||||
result.Err = model.NewAppError("SqlPreferenceStore.Save", "store.sql_reaction.save.commit.app_error", nil, err.Error(), http.StatusInternalServerError)
|
||||
}
|
||||
}
|
||||
|
||||
if result.Err == nil {
|
||||
result.Data = reaction
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
func (s *SqlSupplier) ReactionDelete(ctx context.Context, reaction *model.Reaction, hints ...store.LayeredStoreHint) *store.LayeredStoreSupplierResult {
|
||||
result := store.NewSupplierResult()
|
||||
|
||||
if transaction, err := s.GetMaster().Begin(); err != nil {
|
||||
result.Err = model.NewAppError("SqlReactionStore.Delete", "store.sql_reaction.delete.begin.app_error", nil, err.Error(), http.StatusInternalServerError)
|
||||
} else {
|
||||
err := deleteReactionAndUpdatePost(transaction, reaction)
|
||||
|
||||
if err != nil {
|
||||
transaction.Rollback()
|
||||
|
||||
result.Err = model.NewAppError("SqlPreferenceStore.Delete", "store.sql_reaction.delete.app_error", nil, err.Error(), http.StatusInternalServerError)
|
||||
} else if err := transaction.Commit(); err != nil {
|
||||
// don't need to rollback here since the transaction is already closed
|
||||
result.Err = model.NewAppError("SqlPreferenceStore.Delete", "store.sql_reaction.delete.commit.app_error", nil, err.Error(), http.StatusInternalServerError)
|
||||
} else {
|
||||
result.Data = reaction
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
func (s *SqlSupplier) ReactionGetForPost(ctx context.Context, postId string, hints ...store.LayeredStoreHint) *store.LayeredStoreSupplierResult {
|
||||
result := store.NewSupplierResult()
|
||||
|
||||
var reactions []*model.Reaction
|
||||
|
||||
if _, err := s.GetReplica().Select(&reactions,
|
||||
`SELECT
|
||||
*
|
||||
FROM
|
||||
Reactions
|
||||
WHERE
|
||||
PostId = :PostId
|
||||
ORDER BY
|
||||
CreateAt`, map[string]interface{}{"PostId": postId}); err != nil {
|
||||
result.Err = model.NewAppError("SqlReactionStore.GetForPost", "store.sql_reaction.get_for_post.app_error", nil, "", http.StatusInternalServerError)
|
||||
} else {
|
||||
result.Data = reactions
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
func (s *SqlSupplier) ReactionDeleteAllWithEmojiName(ctx context.Context, emojiName string, hints ...store.LayeredStoreHint) *store.LayeredStoreSupplierResult {
|
||||
result := store.NewSupplierResult()
|
||||
|
||||
var reactions []*model.Reaction
|
||||
|
||||
if _, err := s.GetReplica().Select(&reactions,
|
||||
`SELECT
|
||||
*
|
||||
FROM
|
||||
Reactions
|
||||
WHERE
|
||||
EmojiName = :EmojiName`, map[string]interface{}{"EmojiName": emojiName}); err != nil {
|
||||
result.Err = model.NewAppError("SqlReactionStore.DeleteAllWithEmojiName",
|
||||
"store.sql_reaction.delete_all_with_emoji_name.get_reactions.app_error", nil,
|
||||
"emoji_name="+emojiName+", error="+err.Error(), http.StatusInternalServerError)
|
||||
return result
|
||||
}
|
||||
|
||||
if _, err := s.GetMaster().Exec(
|
||||
`DELETE FROM
|
||||
Reactions
|
||||
WHERE
|
||||
EmojiName = :EmojiName`, map[string]interface{}{"EmojiName": emojiName}); err != nil {
|
||||
result.Err = model.NewAppError("SqlReactionStore.DeleteAllWithEmojiName",
|
||||
"store.sql_reaction.delete_all_with_emoji_name.delete_reactions.app_error", nil,
|
||||
"emoji_name="+emojiName+", error="+err.Error(), http.StatusInternalServerError)
|
||||
return result
|
||||
}
|
||||
|
||||
for _, reaction := range reactions {
|
||||
if _, err := s.GetMaster().Exec(UPDATE_POST_HAS_REACTIONS_QUERY,
|
||||
map[string]interface{}{"PostId": reaction.PostId, "UpdateAt": model.GetMillis()}); err != nil {
|
||||
l4g.Warn(utils.T("store.sql_reaction.delete_all_with_emoji_name.update_post.warn"), reaction.PostId, err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
func (s *SqlSupplier) ReactionPermanentDeleteBatch(ctx context.Context, endTime int64, limit int64, hints ...store.LayeredStoreHint) *store.LayeredStoreSupplierResult {
|
||||
result := store.NewSupplierResult()
|
||||
|
||||
var query string
|
||||
if *utils.Cfg.SqlSettings.DriverName == "postgres" {
|
||||
query = "DELETE from Reactions WHERE Id = any (array (SELECT Id FROM Reactions WHERE CreateAt < :EndTime LIMIT :Limit))"
|
||||
} else {
|
||||
query = "DELETE from Reactions WHERE CreateAt < :EndTime LIMIT :Limit"
|
||||
}
|
||||
|
||||
sqlResult, err := s.GetMaster().Exec(query, map[string]interface{}{"EndTime": endTime, "Limit": limit})
|
||||
if err != nil {
|
||||
result.Err = model.NewAppError("SqlReactionStore.PermanentDeleteBatch", "store.sql_reaction.permanent_delete_batch.app_error", nil, ""+err.Error(), http.StatusInternalServerError)
|
||||
} else {
|
||||
rowsAffected, err1 := sqlResult.RowsAffected()
|
||||
if err1 != nil {
|
||||
result.Err = model.NewAppError("SqlReactionStore.PermanentDeleteBatch", "store.sql_reaction.permanent_delete_batch.app_error", nil, ""+err.Error(), http.StatusInternalServerError)
|
||||
result.Data = int64(0)
|
||||
} else {
|
||||
result.Data = rowsAffected
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
func saveReactionAndUpdatePost(transaction *gorp.Transaction, reaction *model.Reaction) error {
|
||||
if err := transaction.Insert(reaction); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return updatePostForReactions(transaction, reaction.PostId)
|
||||
}
|
||||
|
||||
func deleteReactionAndUpdatePost(transaction *gorp.Transaction, reaction *model.Reaction) error {
|
||||
if _, err := transaction.Exec(
|
||||
`DELETE FROM
|
||||
Reactions
|
||||
WHERE
|
||||
PostId = :PostId AND
|
||||
UserId = :UserId AND
|
||||
EmojiName = :EmojiName`,
|
||||
map[string]interface{}{"PostId": reaction.PostId, "UserId": reaction.UserId, "EmojiName": reaction.EmojiName}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return updatePostForReactions(transaction, reaction.PostId)
|
||||
}
|
||||
|
||||
const (
|
||||
// Set HasReactions = true if and only if the post has reactions, update UpdateAt only if HasReactions changes
|
||||
UPDATE_POST_HAS_REACTIONS_QUERY = `UPDATE
|
||||
Posts
|
||||
SET
|
||||
UpdateAt = (CASE
|
||||
WHEN HasReactions != (SELECT count(0) > 0 FROM Reactions WHERE PostId = :PostId) THEN :UpdateAt
|
||||
ELSE UpdateAt
|
||||
END),
|
||||
HasReactions = (SELECT count(0) > 0 FROM Reactions WHERE PostId = :PostId)
|
||||
WHERE
|
||||
Id = :PostId`
|
||||
)
|
||||
|
||||
func updatePostForReactions(transaction *gorp.Transaction, postId string) error {
|
||||
_, err := transaction.Exec(UPDATE_POST_HAS_REACTIONS_QUERY, map[string]interface{}{"PostId": postId, "UpdateAt": model.GetMillis()})
|
||||
|
||||
return err
|
||||
}
|
||||
137
store/sqlstore/system_store.go
Обычный файл
137
store/sqlstore/system_store.go
Обычный файл
@@ -0,0 +1,137 @@
|
||||
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
|
||||
// See License.txt for license information.
|
||||
|
||||
package sqlstore
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/mattermost/mattermost-server/model"
|
||||
"github.com/mattermost/mattermost-server/store"
|
||||
)
|
||||
|
||||
type SqlSystemStore struct {
|
||||
SqlStore
|
||||
}
|
||||
|
||||
func NewSqlSystemStore(sqlStore SqlStore) store.SystemStore {
|
||||
s := &SqlSystemStore{sqlStore}
|
||||
|
||||
for _, db := range sqlStore.GetAllConns() {
|
||||
table := db.AddTableWithName(model.System{}, "Systems").SetKeys(false, "Name")
|
||||
table.ColMap("Name").SetMaxSize(64)
|
||||
table.ColMap("Value").SetMaxSize(1024)
|
||||
}
|
||||
|
||||
return s
|
||||
}
|
||||
|
||||
func (s SqlSystemStore) CreateIndexesIfNotExists() {
|
||||
}
|
||||
|
||||
func (s SqlSystemStore) Save(system *model.System) store.StoreChannel {
|
||||
|
||||
storeChannel := make(store.StoreChannel, 1)
|
||||
|
||||
go func() {
|
||||
result := store.StoreResult{}
|
||||
|
||||
if err := s.GetMaster().Insert(system); err != nil {
|
||||
result.Err = model.NewAppError("SqlSystemStore.Save", "store.sql_system.save.app_error", nil, err.Error(), http.StatusInternalServerError)
|
||||
}
|
||||
|
||||
storeChannel <- result
|
||||
close(storeChannel)
|
||||
}()
|
||||
|
||||
return storeChannel
|
||||
}
|
||||
|
||||
func (s SqlSystemStore) SaveOrUpdate(system *model.System) store.StoreChannel {
|
||||
|
||||
storeChannel := make(store.StoreChannel, 1)
|
||||
|
||||
go func() {
|
||||
result := store.StoreResult{}
|
||||
|
||||
if err := s.GetReplica().SelectOne(&model.System{}, "SELECT * FROM Systems WHERE Name = :Name", map[string]interface{}{"Name": system.Name}); err == nil {
|
||||
if _, err := s.GetMaster().Update(system); err != nil {
|
||||
result.Err = model.NewAppError("SqlSystemStore.SaveOrUpdate", "store.sql_system.update.app_error", nil, "", http.StatusInternalServerError)
|
||||
}
|
||||
} else {
|
||||
if err := s.GetMaster().Insert(system); err != nil {
|
||||
result.Err = model.NewAppError("SqlSystemStore.SaveOrUpdate", "store.sql_system.save.app_error", nil, "", http.StatusInternalServerError)
|
||||
}
|
||||
}
|
||||
|
||||
storeChannel <- result
|
||||
close(storeChannel)
|
||||
}()
|
||||
|
||||
return storeChannel
|
||||
}
|
||||
|
||||
func (s SqlSystemStore) Update(system *model.System) store.StoreChannel {
|
||||
|
||||
storeChannel := make(store.StoreChannel, 1)
|
||||
|
||||
go func() {
|
||||
result := store.StoreResult{}
|
||||
|
||||
if _, err := s.GetMaster().Update(system); err != nil {
|
||||
result.Err = model.NewAppError("SqlSystemStore.Update", "store.sql_system.update.app_error", nil, "", http.StatusInternalServerError)
|
||||
}
|
||||
|
||||
storeChannel <- result
|
||||
close(storeChannel)
|
||||
}()
|
||||
|
||||
return storeChannel
|
||||
}
|
||||
|
||||
func (s SqlSystemStore) Get() store.StoreChannel {
|
||||
|
||||
storeChannel := make(store.StoreChannel, 1)
|
||||
|
||||
go func() {
|
||||
result := store.StoreResult{}
|
||||
|
||||
var systems []model.System
|
||||
props := make(model.StringMap)
|
||||
if _, err := s.GetReplica().Select(&systems, "SELECT * FROM Systems"); err != nil {
|
||||
result.Err = model.NewAppError("SqlSystemStore.Get", "store.sql_system.get.app_error", nil, "", http.StatusInternalServerError)
|
||||
} else {
|
||||
for _, prop := range systems {
|
||||
props[prop.Name] = prop.Value
|
||||
}
|
||||
|
||||
result.Data = props
|
||||
}
|
||||
|
||||
storeChannel <- result
|
||||
close(storeChannel)
|
||||
}()
|
||||
|
||||
return storeChannel
|
||||
}
|
||||
|
||||
func (s SqlSystemStore) GetByName(name string) store.StoreChannel {
|
||||
|
||||
storeChannel := make(store.StoreChannel, 1)
|
||||
|
||||
go func() {
|
||||
result := store.StoreResult{}
|
||||
|
||||
var system model.System
|
||||
if err := s.GetReplica().SelectOne(&system, "SELECT * FROM Systems WHERE Name = :Name", map[string]interface{}{"Name": name}); err != nil {
|
||||
result.Err = model.NewAppError("SqlSystemStore.GetByName", "store.sql_system.get_by_name.app_error", nil, "", http.StatusInternalServerError)
|
||||
}
|
||||
|
||||
result.Data = &system
|
||||
|
||||
storeChannel <- result
|
||||
close(storeChannel)
|
||||
}()
|
||||
|
||||
return storeChannel
|
||||
}
|
||||
57
store/sqlstore/system_store_test.go
Обычный файл
57
store/sqlstore/system_store_test.go
Обычный файл
@@ -0,0 +1,57 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See License.txt for license information.
|
||||
|
||||
package sqlstore
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/mattermost/mattermost-server/model"
|
||||
"github.com/mattermost/mattermost-server/store"
|
||||
)
|
||||
|
||||
func TestSqlSystemStore(t *testing.T) {
|
||||
ss := Setup()
|
||||
|
||||
system := &model.System{Name: model.NewId(), Value: "value"}
|
||||
store.Must(ss.System().Save(system))
|
||||
|
||||
result := <-ss.System().Get()
|
||||
systems := result.Data.(model.StringMap)
|
||||
|
||||
if systems[system.Name] != system.Value {
|
||||
t.Fatal()
|
||||
}
|
||||
|
||||
system.Value = "value2"
|
||||
store.Must(ss.System().Update(system))
|
||||
|
||||
result2 := <-ss.System().Get()
|
||||
systems2 := result2.Data.(model.StringMap)
|
||||
|
||||
if systems2[system.Name] != system.Value {
|
||||
t.Fatal()
|
||||
}
|
||||
|
||||
result3 := <-ss.System().GetByName(system.Name)
|
||||
rsystem := result3.Data.(*model.System)
|
||||
if rsystem.Value != system.Value {
|
||||
t.Fatal()
|
||||
}
|
||||
}
|
||||
|
||||
func TestSqlSystemStoreSaveOrUpdate(t *testing.T) {
|
||||
ss := Setup()
|
||||
|
||||
system := &model.System{Name: model.NewId(), Value: "value"}
|
||||
|
||||
if err := (<-ss.System().SaveOrUpdate(system)).Err; err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
system.Value = "value2"
|
||||
|
||||
if r := <-ss.System().SaveOrUpdate(system); r.Err != nil {
|
||||
t.Fatal(r.Err)
|
||||
}
|
||||
}
|
||||
837
store/sqlstore/team_store.go
Обычный файл
837
store/sqlstore/team_store.go
Обычный файл
@@ -0,0 +1,837 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See License.txt for license information.
|
||||
|
||||
package sqlstore
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
"github.com/mattermost/mattermost-server/model"
|
||||
"github.com/mattermost/mattermost-server/store"
|
||||
"github.com/mattermost/mattermost-server/utils"
|
||||
)
|
||||
|
||||
const (
|
||||
TEAM_MEMBER_EXISTS_ERROR = "store.sql_team.save_member.exists.app_error"
|
||||
)
|
||||
|
||||
type SqlTeamStore struct {
|
||||
SqlStore
|
||||
}
|
||||
|
||||
func NewSqlTeamStore(sqlStore SqlStore) store.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("DisplayName").SetMaxSize(64)
|
||||
table.ColMap("Name").SetMaxSize(64).SetUnique(true)
|
||||
table.ColMap("Description").SetMaxSize(255)
|
||||
table.ColMap("Email").SetMaxSize(128)
|
||||
table.ColMap("CompanyName").SetMaxSize(64)
|
||||
table.ColMap("AllowedDomains").SetMaxSize(500)
|
||||
table.ColMap("InviteId").SetMaxSize(32)
|
||||
|
||||
tablem := db.AddTableWithName(model.TeamMember{}, "TeamMembers").SetKeys(false, "TeamId", "UserId")
|
||||
tablem.ColMap("TeamId").SetMaxSize(26)
|
||||
tablem.ColMap("UserId").SetMaxSize(26)
|
||||
tablem.ColMap("Roles").SetMaxSize(64)
|
||||
}
|
||||
|
||||
return s
|
||||
}
|
||||
|
||||
func (s SqlTeamStore) CreateIndexesIfNotExists() {
|
||||
s.CreateIndexIfNotExists("idx_teams_name", "Teams", "Name")
|
||||
s.RemoveIndexIfExists("idx_teams_description", "Teams")
|
||||
s.CreateIndexIfNotExists("idx_teams_invite_id", "Teams", "InviteId")
|
||||
s.CreateIndexIfNotExists("idx_teams_update_at", "Teams", "UpdateAt")
|
||||
s.CreateIndexIfNotExists("idx_teams_create_at", "Teams", "CreateAt")
|
||||
s.CreateIndexIfNotExists("idx_teams_delete_at", "Teams", "DeleteAt")
|
||||
|
||||
s.CreateIndexIfNotExists("idx_teammembers_team_id", "TeamMembers", "TeamId")
|
||||
s.CreateIndexIfNotExists("idx_teammembers_user_id", "TeamMembers", "UserId")
|
||||
s.CreateIndexIfNotExists("idx_teammembers_delete_at", "TeamMembers", "DeleteAt")
|
||||
}
|
||||
|
||||
func (s SqlTeamStore) Save(team *model.Team) store.StoreChannel {
|
||||
storeChannel := make(store.StoreChannel, 1)
|
||||
|
||||
go func() {
|
||||
result := store.StoreResult{}
|
||||
|
||||
if len(team.Id) > 0 {
|
||||
result.Err = model.NewAppError("SqlTeamStore.Save",
|
||||
"store.sql_team.save.existing.app_error", nil, "id="+team.Id, http.StatusBadRequest)
|
||||
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 IsUniqueConstraintError(err, []string{"Name", "teams_name_key"}) {
|
||||
result.Err = model.NewAppError("SqlTeamStore.Save", "store.sql_team.save.domain_exists.app_error", nil, "id="+team.Id+", "+err.Error(), http.StatusBadRequest)
|
||||
} else {
|
||||
result.Err = model.NewAppError("SqlTeamStore.Save", "store.sql_team.save.app_error", nil, "id="+team.Id+", "+err.Error(), http.StatusInternalServerError)
|
||||
}
|
||||
} else {
|
||||
result.Data = team
|
||||
}
|
||||
|
||||
storeChannel <- result
|
||||
close(storeChannel)
|
||||
}()
|
||||
|
||||
return storeChannel
|
||||
}
|
||||
|
||||
func (s SqlTeamStore) Update(team *model.Team) store.StoreChannel {
|
||||
|
||||
storeChannel := make(store.StoreChannel, 1)
|
||||
|
||||
go func() {
|
||||
result := store.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", "store.sql_team.update.finding.app_error", nil, "id="+team.Id+", "+err.Error(), http.StatusInternalServerError)
|
||||
} else if oldResult == nil {
|
||||
result.Err = model.NewAppError("SqlTeamStore.Update", "store.sql_team.update.find.app_error", nil, "id="+team.Id, http.StatusBadRequest)
|
||||
} else {
|
||||
oldTeam := oldResult.(*model.Team)
|
||||
team.CreateAt = oldTeam.CreateAt
|
||||
team.UpdateAt = model.GetMillis()
|
||||
team.Name = oldTeam.Name
|
||||
|
||||
if count, err := s.GetMaster().Update(team); err != nil {
|
||||
result.Err = model.NewAppError("SqlTeamStore.Update", "store.sql_team.update.updating.app_error", nil, "id="+team.Id+", "+err.Error(), http.StatusInternalServerError)
|
||||
} else if count != 1 {
|
||||
result.Err = model.NewAppError("SqlTeamStore.Update", "store.sql_team.update.app_error", nil, "id="+team.Id, http.StatusInternalServerError)
|
||||
} else {
|
||||
result.Data = team
|
||||
}
|
||||
}
|
||||
|
||||
storeChannel <- result
|
||||
close(storeChannel)
|
||||
}()
|
||||
|
||||
return storeChannel
|
||||
}
|
||||
|
||||
func (s SqlTeamStore) UpdateDisplayName(name string, teamId string) store.StoreChannel {
|
||||
|
||||
storeChannel := make(store.StoreChannel, 1)
|
||||
|
||||
go func() {
|
||||
result := store.StoreResult{}
|
||||
|
||||
if _, err := s.GetMaster().Exec("UPDATE Teams SET DisplayName = :Name WHERE Id = :Id", map[string]interface{}{"Name": name, "Id": teamId}); err != nil {
|
||||
result.Err = model.NewAppError("SqlTeamStore.UpdateName", "store.sql_team.update_display_name.app_error", nil, "team_id="+teamId, http.StatusInternalServerError)
|
||||
} else {
|
||||
result.Data = teamId
|
||||
}
|
||||
|
||||
storeChannel <- result
|
||||
close(storeChannel)
|
||||
}()
|
||||
|
||||
return storeChannel
|
||||
}
|
||||
|
||||
func (s SqlTeamStore) Get(id string) store.StoreChannel {
|
||||
storeChannel := make(store.StoreChannel, 1)
|
||||
|
||||
go func() {
|
||||
result := store.StoreResult{}
|
||||
|
||||
if obj, err := s.GetReplica().Get(model.Team{}, id); err != nil {
|
||||
result.Err = model.NewAppError("SqlTeamStore.Get", "store.sql_team.get.finding.app_error", nil, "id="+id+", "+err.Error(), http.StatusInternalServerError)
|
||||
} else if obj == nil {
|
||||
result.Err = model.NewAppError("SqlTeamStore.Get", "store.sql_team.get.find.app_error", nil, "id="+id, http.StatusNotFound)
|
||||
} else {
|
||||
team := obj.(*model.Team)
|
||||
if len(team.InviteId) == 0 {
|
||||
team.InviteId = team.Id
|
||||
}
|
||||
|
||||
result.Data = team
|
||||
}
|
||||
|
||||
storeChannel <- result
|
||||
close(storeChannel)
|
||||
}()
|
||||
|
||||
return storeChannel
|
||||
}
|
||||
|
||||
func (s SqlTeamStore) GetByInviteId(inviteId string) store.StoreChannel {
|
||||
storeChannel := make(store.StoreChannel, 1)
|
||||
|
||||
go func() {
|
||||
result := store.StoreResult{}
|
||||
|
||||
team := model.Team{}
|
||||
|
||||
if err := s.GetReplica().SelectOne(&team, "SELECT * FROM Teams WHERE Id = :InviteId OR InviteId = :InviteId", map[string]interface{}{"InviteId": inviteId}); err != nil {
|
||||
result.Err = model.NewAppError("SqlTeamStore.GetByInviteId", "store.sql_team.get_by_invite_id.finding.app_error", nil, "inviteId="+inviteId+", "+err.Error(), http.StatusNotFound)
|
||||
}
|
||||
|
||||
if len(team.InviteId) == 0 {
|
||||
team.InviteId = team.Id
|
||||
}
|
||||
|
||||
if len(inviteId) == 0 || team.InviteId != inviteId {
|
||||
result.Err = model.NewAppError("SqlTeamStore.GetByInviteId", "store.sql_team.get_by_invite_id.find.app_error", nil, "inviteId="+inviteId, http.StatusNotFound)
|
||||
}
|
||||
|
||||
result.Data = &team
|
||||
|
||||
storeChannel <- result
|
||||
close(storeChannel)
|
||||
}()
|
||||
|
||||
return storeChannel
|
||||
}
|
||||
|
||||
func (s SqlTeamStore) GetByName(name string) store.StoreChannel {
|
||||
storeChannel := make(store.StoreChannel, 1)
|
||||
|
||||
go func() {
|
||||
result := store.StoreResult{}
|
||||
|
||||
team := model.Team{}
|
||||
|
||||
if err := s.GetReplica().SelectOne(&team, "SELECT * FROM Teams WHERE Name = :Name", map[string]interface{}{"Name": name}); err != nil {
|
||||
result.Err = model.NewAppError("SqlTeamStore.GetByName", "store.sql_team.get_by_name.app_error", nil, "name="+name+", "+err.Error(), http.StatusInternalServerError)
|
||||
}
|
||||
|
||||
if len(team.InviteId) == 0 {
|
||||
team.InviteId = team.Id
|
||||
}
|
||||
|
||||
result.Data = &team
|
||||
|
||||
storeChannel <- result
|
||||
close(storeChannel)
|
||||
}()
|
||||
|
||||
return storeChannel
|
||||
}
|
||||
|
||||
func (s SqlTeamStore) SearchByName(name string) store.StoreChannel {
|
||||
storeChannel := make(store.StoreChannel, 1)
|
||||
|
||||
go func() {
|
||||
result := store.StoreResult{}
|
||||
|
||||
var teams []*model.Team
|
||||
|
||||
if _, err := s.GetReplica().Select(&teams, "SELECT * FROM Teams WHERE Name LIKE :Name", map[string]interface{}{"Name": name + "%"}); err != nil {
|
||||
result.Err = model.NewAppError("SqlTeamStore.SearchByName", "store.sql_team.get_by_name.app_error", nil, "name="+name+", "+err.Error(), http.StatusInternalServerError)
|
||||
}
|
||||
|
||||
result.Data = teams
|
||||
|
||||
storeChannel <- result
|
||||
close(storeChannel)
|
||||
}()
|
||||
|
||||
return storeChannel
|
||||
}
|
||||
|
||||
func (s SqlTeamStore) SearchAll(term string) store.StoreChannel {
|
||||
storeChannel := make(store.StoreChannel, 1)
|
||||
|
||||
go func() {
|
||||
result := store.StoreResult{}
|
||||
|
||||
var teams []*model.Team
|
||||
|
||||
if _, err := s.GetReplica().Select(&teams, "SELECT * FROM Teams WHERE Name LIKE :Term OR DisplayName LIKE :Term", map[string]interface{}{"Term": term + "%"}); err != nil {
|
||||
result.Err = model.NewAppError("SqlTeamStore.SearchAll", "store.sql_team.search_all_team.app_error", nil, "term="+term+", "+err.Error(), http.StatusInternalServerError)
|
||||
}
|
||||
|
||||
result.Data = teams
|
||||
|
||||
storeChannel <- result
|
||||
close(storeChannel)
|
||||
}()
|
||||
|
||||
return storeChannel
|
||||
}
|
||||
|
||||
func (s SqlTeamStore) SearchOpen(term string) store.StoreChannel {
|
||||
storeChannel := make(store.StoreChannel, 1)
|
||||
|
||||
go func() {
|
||||
result := store.StoreResult{}
|
||||
|
||||
var teams []*model.Team
|
||||
|
||||
if _, err := s.GetReplica().Select(&teams, "SELECT * FROM Teams WHERE Type = 'O' AND AllowOpenInvite = true AND (Name LIKE :Term OR DisplayName LIKE :Term)", map[string]interface{}{"Term": term + "%"}); err != nil {
|
||||
result.Err = model.NewAppError("SqlTeamStore.SearchOpen", "store.sql_team.search_open_team.app_error", nil, "term="+term+", "+err.Error(), http.StatusInternalServerError)
|
||||
}
|
||||
|
||||
result.Data = teams
|
||||
|
||||
storeChannel <- result
|
||||
close(storeChannel)
|
||||
}()
|
||||
|
||||
return storeChannel
|
||||
}
|
||||
|
||||
func (s SqlTeamStore) GetAll() store.StoreChannel {
|
||||
storeChannel := make(store.StoreChannel, 1)
|
||||
|
||||
go func() {
|
||||
result := store.StoreResult{}
|
||||
|
||||
var data []*model.Team
|
||||
if _, err := s.GetReplica().Select(&data, "SELECT * FROM Teams"); err != nil {
|
||||
result.Err = model.NewAppError("SqlTeamStore.GetAllTeams", "store.sql_team.get_all.app_error", nil, err.Error(), http.StatusInternalServerError)
|
||||
}
|
||||
|
||||
for _, team := range data {
|
||||
if len(team.InviteId) == 0 {
|
||||
team.InviteId = team.Id
|
||||
}
|
||||
}
|
||||
|
||||
result.Data = data
|
||||
|
||||
storeChannel <- result
|
||||
close(storeChannel)
|
||||
}()
|
||||
|
||||
return storeChannel
|
||||
}
|
||||
|
||||
func (s SqlTeamStore) GetAllPage(offset int, limit int) store.StoreChannel {
|
||||
storeChannel := make(store.StoreChannel, 1)
|
||||
|
||||
go func() {
|
||||
result := store.StoreResult{}
|
||||
|
||||
var data []*model.Team
|
||||
if _, err := s.GetReplica().Select(&data, "SELECT * FROM Teams LIMIT :Limit OFFSET :Offset", map[string]interface{}{"Offset": offset, "Limit": limit}); err != nil {
|
||||
result.Err = model.NewAppError("SqlTeamStore.GetAllTeams", "store.sql_team.get_all.app_error", nil, err.Error(), http.StatusInternalServerError)
|
||||
}
|
||||
|
||||
for _, team := range data {
|
||||
if len(team.InviteId) == 0 {
|
||||
team.InviteId = team.Id
|
||||
}
|
||||
}
|
||||
|
||||
result.Data = data
|
||||
|
||||
storeChannel <- result
|
||||
close(storeChannel)
|
||||
}()
|
||||
|
||||
return storeChannel
|
||||
}
|
||||
|
||||
func (s SqlTeamStore) GetTeamsByUserId(userId string) store.StoreChannel {
|
||||
storeChannel := make(store.StoreChannel, 1)
|
||||
|
||||
go func() {
|
||||
result := store.StoreResult{}
|
||||
|
||||
var data []*model.Team
|
||||
if _, err := s.GetReplica().Select(&data, "SELECT Teams.* FROM Teams, TeamMembers WHERE TeamMembers.TeamId = Teams.Id AND TeamMembers.UserId = :UserId AND TeamMembers.DeleteAt = 0 AND Teams.DeleteAt = 0", map[string]interface{}{"UserId": userId}); err != nil {
|
||||
result.Err = model.NewAppError("SqlTeamStore.GetTeamsByUserId", "store.sql_team.get_all.app_error", nil, err.Error(), http.StatusInternalServerError)
|
||||
}
|
||||
|
||||
for _, team := range data {
|
||||
if len(team.InviteId) == 0 {
|
||||
team.InviteId = team.Id
|
||||
}
|
||||
}
|
||||
|
||||
result.Data = data
|
||||
|
||||
storeChannel <- result
|
||||
close(storeChannel)
|
||||
}()
|
||||
|
||||
return storeChannel
|
||||
}
|
||||
|
||||
func (s SqlTeamStore) GetAllTeamListing() store.StoreChannel {
|
||||
storeChannel := make(store.StoreChannel, 1)
|
||||
|
||||
go func() {
|
||||
result := store.StoreResult{}
|
||||
|
||||
query := "SELECT * FROM Teams WHERE AllowOpenInvite = 1"
|
||||
|
||||
if *utils.Cfg.SqlSettings.DriverName == model.DATABASE_DRIVER_POSTGRES {
|
||||
query = "SELECT * FROM Teams WHERE AllowOpenInvite = true"
|
||||
}
|
||||
|
||||
var data []*model.Team
|
||||
if _, err := s.GetReplica().Select(&data, query); err != nil {
|
||||
result.Err = model.NewAppError("SqlTeamStore.GetAllTeamListing", "store.sql_team.get_all_team_listing.app_error", nil, err.Error(), http.StatusInternalServerError)
|
||||
}
|
||||
|
||||
for _, team := range data {
|
||||
if len(team.InviteId) == 0 {
|
||||
team.InviteId = team.Id
|
||||
}
|
||||
}
|
||||
|
||||
result.Data = data
|
||||
|
||||
storeChannel <- result
|
||||
close(storeChannel)
|
||||
}()
|
||||
|
||||
return storeChannel
|
||||
}
|
||||
|
||||
func (s SqlTeamStore) GetAllTeamPageListing(offset int, limit int) store.StoreChannel {
|
||||
storeChannel := make(store.StoreChannel, 1)
|
||||
|
||||
go func() {
|
||||
result := store.StoreResult{}
|
||||
|
||||
query := "SELECT * FROM Teams WHERE AllowOpenInvite = 1 LIMIT :Limit OFFSET :Offset"
|
||||
|
||||
if *utils.Cfg.SqlSettings.DriverName == model.DATABASE_DRIVER_POSTGRES {
|
||||
query = "SELECT * FROM Teams WHERE AllowOpenInvite = true LIMIT :Limit OFFSET :Offset"
|
||||
}
|
||||
|
||||
var data []*model.Team
|
||||
if _, err := s.GetReplica().Select(&data, query, map[string]interface{}{"Offset": offset, "Limit": limit}); err != nil {
|
||||
result.Err = model.NewAppError("SqlTeamStore.GetAllTeamListing", "store.sql_team.get_all_team_listing.app_error", nil, err.Error(), http.StatusInternalServerError)
|
||||
}
|
||||
|
||||
for _, team := range data {
|
||||
if len(team.InviteId) == 0 {
|
||||
team.InviteId = team.Id
|
||||
}
|
||||
}
|
||||
|
||||
result.Data = data
|
||||
|
||||
storeChannel <- result
|
||||
close(storeChannel)
|
||||
}()
|
||||
|
||||
return storeChannel
|
||||
}
|
||||
|
||||
func (s SqlTeamStore) PermanentDelete(teamId string) store.StoreChannel {
|
||||
storeChannel := make(store.StoreChannel, 1)
|
||||
|
||||
go func() {
|
||||
result := store.StoreResult{}
|
||||
|
||||
if _, err := s.GetMaster().Exec("DELETE FROM Teams WHERE Id = :TeamId", map[string]interface{}{"TeamId": teamId}); err != nil {
|
||||
result.Err = model.NewAppError("SqlTeamStore.Delete", "store.sql_team.permanent_delete.app_error", nil, "teamId="+teamId+", "+err.Error(), http.StatusInternalServerError)
|
||||
}
|
||||
|
||||
storeChannel <- result
|
||||
close(storeChannel)
|
||||
}()
|
||||
|
||||
return storeChannel
|
||||
}
|
||||
|
||||
func (s SqlTeamStore) AnalyticsTeamCount() store.StoreChannel {
|
||||
storeChannel := make(store.StoreChannel, 1)
|
||||
|
||||
go func() {
|
||||
result := store.StoreResult{}
|
||||
|
||||
if c, err := s.GetReplica().SelectInt("SELECT COUNT(*) FROM Teams WHERE DeleteAt = 0", map[string]interface{}{}); err != nil {
|
||||
result.Err = model.NewAppError("SqlTeamStore.AnalyticsTeamCount", "store.sql_team.analytics_team_count.app_error", nil, err.Error(), http.StatusInternalServerError)
|
||||
} else {
|
||||
result.Data = c
|
||||
}
|
||||
|
||||
storeChannel <- result
|
||||
close(storeChannel)
|
||||
}()
|
||||
|
||||
return storeChannel
|
||||
}
|
||||
|
||||
func (s SqlTeamStore) SaveMember(member *model.TeamMember) store.StoreChannel {
|
||||
storeChannel := make(store.StoreChannel, 1)
|
||||
|
||||
go func() {
|
||||
result := store.StoreResult{}
|
||||
|
||||
if result.Err = member.IsValid(); result.Err != nil {
|
||||
storeChannel <- result
|
||||
close(storeChannel)
|
||||
return
|
||||
}
|
||||
|
||||
if count, err := s.GetMaster().SelectInt(
|
||||
`SELECT
|
||||
COUNT(0)
|
||||
FROM
|
||||
TeamMembers
|
||||
INNER JOIN
|
||||
Users
|
||||
ON
|
||||
TeamMembers.UserId = Users.Id
|
||||
WHERE
|
||||
TeamId = :TeamId
|
||||
AND TeamMembers.DeleteAt = 0
|
||||
AND Users.DeleteAt = 0`, map[string]interface{}{"TeamId": member.TeamId}); err != nil {
|
||||
result.Err = model.NewAppError("SqlUserStore.Save", "store.sql_user.save.member_count.app_error", nil, "teamId="+member.TeamId+", "+err.Error(), http.StatusInternalServerError)
|
||||
storeChannel <- result
|
||||
close(storeChannel)
|
||||
return
|
||||
} else if int(count) >= *utils.Cfg.TeamSettings.MaxUsersPerTeam {
|
||||
result.Err = model.NewAppError("SqlUserStore.Save", "store.sql_user.save.max_accounts.app_error", nil, "teamId="+member.TeamId, http.StatusBadRequest)
|
||||
storeChannel <- result
|
||||
close(storeChannel)
|
||||
return
|
||||
}
|
||||
|
||||
if err := s.GetMaster().Insert(member); err != nil {
|
||||
if IsUniqueConstraintError(err, []string{"TeamId", "teammembers_pkey", "PRIMARY"}) {
|
||||
result.Err = model.NewAppError("SqlTeamStore.SaveMember", TEAM_MEMBER_EXISTS_ERROR, nil, "team_id="+member.TeamId+", user_id="+member.UserId+", "+err.Error(), http.StatusBadRequest)
|
||||
} else {
|
||||
result.Err = model.NewAppError("SqlTeamStore.SaveMember", "store.sql_team.save_member.save.app_error", nil, "team_id="+member.TeamId+", user_id="+member.UserId+", "+err.Error(), http.StatusInternalServerError)
|
||||
}
|
||||
} else {
|
||||
result.Data = member
|
||||
}
|
||||
|
||||
storeChannel <- result
|
||||
close(storeChannel)
|
||||
}()
|
||||
|
||||
return storeChannel
|
||||
}
|
||||
|
||||
func (s SqlTeamStore) UpdateMember(member *model.TeamMember) store.StoreChannel {
|
||||
storeChannel := make(store.StoreChannel, 1)
|
||||
|
||||
go func() {
|
||||
result := store.StoreResult{}
|
||||
|
||||
member.PreUpdate()
|
||||
|
||||
if result.Err = member.IsValid(); result.Err != nil {
|
||||
storeChannel <- result
|
||||
close(storeChannel)
|
||||
return
|
||||
}
|
||||
|
||||
if _, err := s.GetMaster().Update(member); err != nil {
|
||||
result.Err = model.NewAppError("SqlTeamStore.UpdateMember", "store.sql_team.save_member.save.app_error", nil, err.Error(), http.StatusInternalServerError)
|
||||
} else {
|
||||
result.Data = member
|
||||
}
|
||||
|
||||
storeChannel <- result
|
||||
close(storeChannel)
|
||||
}()
|
||||
|
||||
return storeChannel
|
||||
}
|
||||
|
||||
func (s SqlTeamStore) GetMember(teamId string, userId string) store.StoreChannel {
|
||||
storeChannel := make(store.StoreChannel, 1)
|
||||
|
||||
go func() {
|
||||
result := store.StoreResult{}
|
||||
|
||||
var member model.TeamMember
|
||||
err := s.GetReplica().SelectOne(&member, "SELECT * FROM TeamMembers WHERE TeamId = :TeamId AND UserId = :UserId", map[string]interface{}{"TeamId": teamId, "UserId": userId})
|
||||
if err != nil {
|
||||
if err == sql.ErrNoRows {
|
||||
result.Err = model.NewAppError("SqlTeamStore.GetMember", "store.sql_team.get_member.missing.app_error", nil, "teamId="+teamId+" userId="+userId+" "+err.Error(), http.StatusNotFound)
|
||||
} else {
|
||||
result.Err = model.NewAppError("SqlTeamStore.GetMember", "store.sql_team.get_member.app_error", nil, "teamId="+teamId+" userId="+userId+" "+err.Error(), http.StatusInternalServerError)
|
||||
}
|
||||
} else {
|
||||
result.Data = &member
|
||||
}
|
||||
|
||||
storeChannel <- result
|
||||
close(storeChannel)
|
||||
}()
|
||||
|
||||
return storeChannel
|
||||
}
|
||||
|
||||
func (s SqlTeamStore) GetMembers(teamId string, offset int, limit int) store.StoreChannel {
|
||||
storeChannel := make(store.StoreChannel, 1)
|
||||
|
||||
go func() {
|
||||
result := store.StoreResult{}
|
||||
|
||||
var members []*model.TeamMember
|
||||
_, err := s.GetReplica().Select(&members, "SELECT * FROM TeamMembers WHERE TeamId = :TeamId AND DeleteAt = 0 LIMIT :Limit OFFSET :Offset", map[string]interface{}{"TeamId": teamId, "Offset": offset, "Limit": limit})
|
||||
if err != nil {
|
||||
result.Err = model.NewAppError("SqlTeamStore.GetMembers", "store.sql_team.get_members.app_error", nil, "teamId="+teamId+" "+err.Error(), http.StatusInternalServerError)
|
||||
} else {
|
||||
result.Data = members
|
||||
}
|
||||
|
||||
storeChannel <- result
|
||||
close(storeChannel)
|
||||
}()
|
||||
|
||||
return storeChannel
|
||||
}
|
||||
|
||||
func (s SqlTeamStore) GetTotalMemberCount(teamId string) store.StoreChannel {
|
||||
storeChannel := make(store.StoreChannel, 1)
|
||||
|
||||
go func() {
|
||||
result := store.StoreResult{}
|
||||
|
||||
count, err := s.GetReplica().SelectInt(`
|
||||
SELECT
|
||||
count(*)
|
||||
FROM
|
||||
TeamMembers,
|
||||
Users
|
||||
WHERE
|
||||
TeamMembers.UserId = Users.Id
|
||||
AND TeamMembers.TeamId = :TeamId
|
||||
AND TeamMembers.DeleteAt = 0`, map[string]interface{}{"TeamId": teamId})
|
||||
if err != nil {
|
||||
result.Err = model.NewAppError("SqlTeamStore.GetTotalMemberCount", "store.sql_team.get_member_count.app_error", nil, "teamId="+teamId+" "+err.Error(), http.StatusInternalServerError)
|
||||
} else {
|
||||
result.Data = count
|
||||
}
|
||||
|
||||
storeChannel <- result
|
||||
close(storeChannel)
|
||||
}()
|
||||
|
||||
return storeChannel
|
||||
}
|
||||
|
||||
func (s SqlTeamStore) GetActiveMemberCount(teamId string) store.StoreChannel {
|
||||
storeChannel := make(store.StoreChannel, 1)
|
||||
|
||||
go func() {
|
||||
result := store.StoreResult{}
|
||||
|
||||
count, err := s.GetReplica().SelectInt(`
|
||||
SELECT
|
||||
count(*)
|
||||
FROM
|
||||
TeamMembers,
|
||||
Users
|
||||
WHERE
|
||||
TeamMembers.UserId = Users.Id
|
||||
AND TeamMembers.TeamId = :TeamId
|
||||
AND TeamMembers.DeleteAt = 0
|
||||
AND Users.DeleteAt = 0`, map[string]interface{}{"TeamId": teamId})
|
||||
if err != nil {
|
||||
result.Err = model.NewAppError("SqlTeamStore.GetActiveMemberCount", "store.sql_team.get_member_count.app_error", nil, "teamId="+teamId+" "+err.Error(), http.StatusInternalServerError)
|
||||
} else {
|
||||
result.Data = count
|
||||
}
|
||||
|
||||
storeChannel <- result
|
||||
close(storeChannel)
|
||||
}()
|
||||
|
||||
return storeChannel
|
||||
}
|
||||
|
||||
func (s SqlTeamStore) GetMembersByIds(teamId string, userIds []string) store.StoreChannel {
|
||||
storeChannel := make(store.StoreChannel, 1)
|
||||
|
||||
go func() {
|
||||
result := store.StoreResult{}
|
||||
|
||||
var members []*model.TeamMember
|
||||
props := make(map[string]interface{})
|
||||
idQuery := ""
|
||||
|
||||
for index, userId := range userIds {
|
||||
if len(idQuery) > 0 {
|
||||
idQuery += ", "
|
||||
}
|
||||
|
||||
props["userId"+strconv.Itoa(index)] = userId
|
||||
idQuery += ":userId" + strconv.Itoa(index)
|
||||
}
|
||||
|
||||
props["TeamId"] = teamId
|
||||
|
||||
if _, err := s.GetReplica().Select(&members, "SELECT * FROM TeamMembers WHERE TeamId = :TeamId AND UserId IN ("+idQuery+") AND DeleteAt = 0", props); err != nil {
|
||||
result.Err = model.NewAppError("SqlTeamStore.GetMembersByIds", "store.sql_team.get_members_by_ids.app_error", nil, "teamId="+teamId+" "+err.Error(), http.StatusInternalServerError)
|
||||
} else {
|
||||
result.Data = members
|
||||
}
|
||||
|
||||
storeChannel <- result
|
||||
close(storeChannel)
|
||||
}()
|
||||
|
||||
return storeChannel
|
||||
}
|
||||
|
||||
func (s SqlTeamStore) GetTeamsForUser(userId string) store.StoreChannel {
|
||||
storeChannel := make(store.StoreChannel, 1)
|
||||
|
||||
go func() {
|
||||
result := store.StoreResult{}
|
||||
|
||||
var members []*model.TeamMember
|
||||
_, err := s.GetReplica().Select(&members, "SELECT * FROM TeamMembers WHERE UserId = :UserId", map[string]interface{}{"UserId": userId})
|
||||
if err != nil {
|
||||
result.Err = model.NewAppError("SqlTeamStore.GetMembers", "store.sql_team.get_members.app_error", nil, "userId="+userId+" "+err.Error(), http.StatusInternalServerError)
|
||||
} else {
|
||||
result.Data = members
|
||||
}
|
||||
|
||||
storeChannel <- result
|
||||
close(storeChannel)
|
||||
}()
|
||||
|
||||
return storeChannel
|
||||
}
|
||||
|
||||
func (s SqlTeamStore) GetChannelUnreadsForAllTeams(excludeTeamId, userId string) store.StoreChannel {
|
||||
storeChannel := make(store.StoreChannel, 1)
|
||||
|
||||
go func() {
|
||||
result := store.StoreResult{}
|
||||
|
||||
var data []*model.ChannelUnread
|
||||
_, err := s.GetReplica().Select(&data,
|
||||
`SELECT
|
||||
Channels.TeamId TeamId, Channels.Id ChannelId, (Channels.TotalMsgCount - ChannelMembers.MsgCount) MsgCount, ChannelMembers.MentionCount MentionCount, ChannelMembers.NotifyProps NotifyProps
|
||||
FROM
|
||||
Channels, ChannelMembers
|
||||
WHERE
|
||||
Id = ChannelId
|
||||
AND UserId = :UserId
|
||||
AND DeleteAt = 0
|
||||
AND TeamId != :TeamId`,
|
||||
map[string]interface{}{"UserId": userId, "TeamId": excludeTeamId})
|
||||
|
||||
if err != nil {
|
||||
result.Err = model.NewAppError("SqlTeamStore.GetChannelUnreadsForAllTeams", "store.sql_team.get_unread.app_error", nil, "userId="+userId+" "+err.Error(), http.StatusInternalServerError)
|
||||
} else {
|
||||
result.Data = data
|
||||
}
|
||||
|
||||
storeChannel <- result
|
||||
close(storeChannel)
|
||||
}()
|
||||
|
||||
return storeChannel
|
||||
}
|
||||
|
||||
func (s SqlTeamStore) GetChannelUnreadsForTeam(teamId, userId string) store.StoreChannel {
|
||||
storeChannel := make(store.StoreChannel, 1)
|
||||
|
||||
go func() {
|
||||
result := store.StoreResult{}
|
||||
|
||||
var data []*model.ChannelUnread
|
||||
_, err := s.GetReplica().Select(&data,
|
||||
`SELECT
|
||||
Channels.TeamId TeamId, Channels.Id ChannelId, (Channels.TotalMsgCount - ChannelMembers.MsgCount) MsgCount, ChannelMembers.MentionCount MentionCount, ChannelMembers.NotifyProps NotifyProps
|
||||
FROM
|
||||
Channels, ChannelMembers
|
||||
WHERE
|
||||
Id = ChannelId
|
||||
AND UserId = :UserId
|
||||
AND TeamId = :TeamId
|
||||
AND DeleteAt = 0`,
|
||||
map[string]interface{}{"TeamId": teamId, "UserId": userId})
|
||||
|
||||
if err != nil {
|
||||
result.Err = model.NewAppError("SqlTeamStore.GetChannelUnreadsForTeam", "store.sql_team.get_unread.app_error", nil, "teamId="+teamId+" "+err.Error(), http.StatusInternalServerError)
|
||||
} else {
|
||||
result.Data = data
|
||||
}
|
||||
|
||||
storeChannel <- result
|
||||
close(storeChannel)
|
||||
}()
|
||||
|
||||
return storeChannel
|
||||
}
|
||||
|
||||
func (s SqlTeamStore) RemoveMember(teamId string, userId string) store.StoreChannel {
|
||||
storeChannel := make(store.StoreChannel, 1)
|
||||
|
||||
go func() {
|
||||
result := store.StoreResult{}
|
||||
|
||||
_, err := s.GetMaster().Exec("DELETE FROM TeamMembers WHERE TeamId = :TeamId AND UserId = :UserId", map[string]interface{}{"TeamId": teamId, "UserId": userId})
|
||||
if err != nil {
|
||||
result.Err = model.NewAppError("SqlChannelStore.RemoveMember", "store.sql_team.remove_member.app_error", nil, "team_id="+teamId+", user_id="+userId+", "+err.Error(), http.StatusInternalServerError)
|
||||
}
|
||||
|
||||
storeChannel <- result
|
||||
close(storeChannel)
|
||||
}()
|
||||
|
||||
return storeChannel
|
||||
}
|
||||
|
||||
func (s SqlTeamStore) RemoveAllMembersByTeam(teamId string) store.StoreChannel {
|
||||
storeChannel := make(store.StoreChannel, 1)
|
||||
|
||||
go func() {
|
||||
result := store.StoreResult{}
|
||||
|
||||
_, err := s.GetMaster().Exec("DELETE FROM TeamMembers WHERE TeamId = :TeamId", map[string]interface{}{"TeamId": teamId})
|
||||
if err != nil {
|
||||
result.Err = model.NewAppError("SqlChannelStore.RemoveMember", "store.sql_team.remove_member.app_error", nil, "team_id="+teamId+", "+err.Error(), http.StatusInternalServerError)
|
||||
}
|
||||
|
||||
storeChannel <- result
|
||||
close(storeChannel)
|
||||
}()
|
||||
|
||||
return storeChannel
|
||||
}
|
||||
|
||||
func (s SqlTeamStore) RemoveAllMembersByUser(userId string) store.StoreChannel {
|
||||
storeChannel := make(store.StoreChannel, 1)
|
||||
|
||||
go func() {
|
||||
result := store.StoreResult{}
|
||||
|
||||
_, err := s.GetMaster().Exec("DELETE FROM TeamMembers WHERE UserId = :UserId", map[string]interface{}{"UserId": userId})
|
||||
if err != nil {
|
||||
result.Err = model.NewAppError("SqlChannelStore.RemoveMember", "store.sql_team.remove_member.app_error", nil, "user_id="+userId+", "+err.Error(), http.StatusInternalServerError)
|
||||
}
|
||||
|
||||
storeChannel <- result
|
||||
close(storeChannel)
|
||||
}()
|
||||
|
||||
return storeChannel
|
||||
}
|
||||
1028
store/sqlstore/team_store_test.go
Обычный файл
1028
store/sqlstore/team_store_test.go
Обычный файл
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
110
store/sqlstore/tokens_store.go
Обычный файл
110
store/sqlstore/tokens_store.go
Обычный файл
@@ -0,0 +1,110 @@
|
||||
// Copyright (c) 2017 Mattermost, Inc. All Rights Reserved.
|
||||
// See License.txt for license information.
|
||||
|
||||
package sqlstore
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"net/http"
|
||||
|
||||
l4g "github.com/alecthomas/log4go"
|
||||
|
||||
"github.com/mattermost/mattermost-server/model"
|
||||
"github.com/mattermost/mattermost-server/store"
|
||||
)
|
||||
|
||||
type SqlTokenStore struct {
|
||||
SqlStore
|
||||
}
|
||||
|
||||
func NewSqlTokenStore(sqlStore SqlStore) store.TokenStore {
|
||||
s := &SqlTokenStore{sqlStore}
|
||||
|
||||
for _, db := range sqlStore.GetAllConns() {
|
||||
table := db.AddTableWithName(model.Token{}, "Tokens").SetKeys(false, "Token")
|
||||
table.ColMap("Token").SetMaxSize(64)
|
||||
table.ColMap("Type").SetMaxSize(64)
|
||||
table.ColMap("Extra").SetMaxSize(128)
|
||||
}
|
||||
|
||||
return s
|
||||
}
|
||||
|
||||
func (s SqlTokenStore) CreateIndexesIfNotExists() {
|
||||
}
|
||||
|
||||
func (s SqlTokenStore) Save(token *model.Token) store.StoreChannel {
|
||||
|
||||
storeChannel := make(store.StoreChannel, 1)
|
||||
|
||||
go func() {
|
||||
result := store.StoreResult{}
|
||||
|
||||
if result.Err = token.IsValid(); result.Err != nil {
|
||||
storeChannel <- result
|
||||
close(storeChannel)
|
||||
return
|
||||
}
|
||||
|
||||
if err := s.GetMaster().Insert(token); err != nil {
|
||||
result.Err = model.NewAppError("SqlTokenStore.Save", "store.sql_recover.save.app_error", nil, "", http.StatusInternalServerError)
|
||||
}
|
||||
|
||||
storeChannel <- result
|
||||
close(storeChannel)
|
||||
}()
|
||||
|
||||
return storeChannel
|
||||
}
|
||||
|
||||
func (s SqlTokenStore) Delete(token string) store.StoreChannel {
|
||||
|
||||
storeChannel := make(store.StoreChannel, 1)
|
||||
|
||||
go func() {
|
||||
result := store.StoreResult{}
|
||||
|
||||
if _, err := s.GetMaster().Exec("DELETE FROM Tokens WHERE Token = :Token", map[string]interface{}{"Token": token}); err != nil {
|
||||
result.Err = model.NewAppError("SqlTokenStore.Delete", "store.sql_recover.delete.app_error", nil, "", http.StatusInternalServerError)
|
||||
}
|
||||
|
||||
storeChannel <- result
|
||||
close(storeChannel)
|
||||
}()
|
||||
|
||||
return storeChannel
|
||||
}
|
||||
|
||||
func (s SqlTokenStore) GetByToken(tokenString string) store.StoreChannel {
|
||||
|
||||
storeChannel := make(store.StoreChannel, 1)
|
||||
|
||||
go func() {
|
||||
result := store.StoreResult{}
|
||||
|
||||
token := model.Token{}
|
||||
|
||||
if err := s.GetReplica().SelectOne(&token, "SELECT * FROM Tokens WHERE Token = :Token", map[string]interface{}{"Token": tokenString}); err != nil {
|
||||
if err == sql.ErrNoRows {
|
||||
result.Err = model.NewAppError("SqlTokenStore.GetByToken", "store.sql_recover.get_by_code.app_error", nil, err.Error(), http.StatusBadRequest)
|
||||
} else {
|
||||
result.Err = model.NewAppError("SqlTokenStore.GetByToken", "store.sql_recover.get_by_code.app_error", nil, err.Error(), http.StatusInternalServerError)
|
||||
}
|
||||
}
|
||||
|
||||
result.Data = &token
|
||||
|
||||
storeChannel <- result
|
||||
close(storeChannel)
|
||||
}()
|
||||
|
||||
return storeChannel
|
||||
}
|
||||
|
||||
func (s SqlTokenStore) Cleanup() {
|
||||
l4g.Debug("Cleaning up token store.")
|
||||
deltime := model.GetMillis() - model.MAX_TOKEN_EXIPRY_TIME
|
||||
if _, err := s.GetMaster().Exec("DELETE FROM Tokens WHERE CreateAt < :DelTime", map[string]interface{}{"DelTime": deltime}); err != nil {
|
||||
l4g.Error("Unable to cleanup token store.")
|
||||
}
|
||||
}
|
||||
310
store/sqlstore/upgrade.go
Обычный файл
310
store/sqlstore/upgrade.go
Обычный файл
@@ -0,0 +1,310 @@
|
||||
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
|
||||
// See License.txt for license information.
|
||||
|
||||
package sqlstore
|
||||
|
||||
import (
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
l4g "github.com/alecthomas/log4go"
|
||||
|
||||
"github.com/mattermost/mattermost-server/model"
|
||||
"github.com/mattermost/mattermost-server/utils"
|
||||
)
|
||||
|
||||
const (
|
||||
VERSION_4_3_0 = "4.3.0"
|
||||
VERSION_4_2_0 = "4.2.0"
|
||||
VERSION_4_1_0 = "4.1.0"
|
||||
VERSION_4_0_0 = "4.0.0"
|
||||
VERSION_3_10_0 = "3.10.0"
|
||||
VERSION_3_9_0 = "3.9.0"
|
||||
VERSION_3_8_0 = "3.8.0"
|
||||
VERSION_3_7_0 = "3.7.0"
|
||||
VERSION_3_6_0 = "3.6.0"
|
||||
VERSION_3_5_0 = "3.5.0"
|
||||
VERSION_3_4_0 = "3.4.0"
|
||||
VERSION_3_3_0 = "3.3.0"
|
||||
VERSION_3_2_0 = "3.2.0"
|
||||
VERSION_3_1_0 = "3.1.0"
|
||||
VERSION_3_0_0 = "3.0.0"
|
||||
OLDEST_SUPPORTED_VERSION = VERSION_3_0_0
|
||||
)
|
||||
|
||||
const (
|
||||
EXIT_VERSION_SAVE_MISSING = 1001
|
||||
EXIT_TOO_OLD = 1002
|
||||
EXIT_VERSION_SAVE = 1003
|
||||
EXIT_THEME_MIGRATION = 1004
|
||||
)
|
||||
|
||||
func UpgradeDatabase(sqlStore SqlStore) {
|
||||
|
||||
UpgradeDatabaseToVersion31(sqlStore)
|
||||
UpgradeDatabaseToVersion32(sqlStore)
|
||||
UpgradeDatabaseToVersion33(sqlStore)
|
||||
UpgradeDatabaseToVersion34(sqlStore)
|
||||
UpgradeDatabaseToVersion35(sqlStore)
|
||||
UpgradeDatabaseToVersion36(sqlStore)
|
||||
UpgradeDatabaseToVersion37(sqlStore)
|
||||
UpgradeDatabaseToVersion38(sqlStore)
|
||||
UpgradeDatabaseToVersion39(sqlStore)
|
||||
UpgradeDatabaseToVersion310(sqlStore)
|
||||
UpgradeDatabaseToVersion40(sqlStore)
|
||||
UpgradeDatabaseToVersion41(sqlStore)
|
||||
UpgradeDatabaseToVersion42(sqlStore)
|
||||
|
||||
// If the SchemaVersion is empty this this is the first time it has ran
|
||||
// so lets set it to the current version.
|
||||
if sqlStore.GetCurrentSchemaVersion() == "" {
|
||||
if result := <-sqlStore.System().SaveOrUpdate(&model.System{Name: "Version", Value: model.CurrentVersion}); result.Err != nil {
|
||||
l4g.Critical(result.Err.Error())
|
||||
time.Sleep(time.Second)
|
||||
os.Exit(EXIT_VERSION_SAVE_MISSING)
|
||||
}
|
||||
|
||||
l4g.Info(utils.T("store.sql.schema_set.info"), model.CurrentVersion)
|
||||
}
|
||||
|
||||
// If we're not on the current version then it's too old to be upgraded
|
||||
if sqlStore.GetCurrentSchemaVersion() != model.CurrentVersion {
|
||||
l4g.Critical(utils.T("store.sql.schema_version.critical"), sqlStore.GetCurrentSchemaVersion(), OLDEST_SUPPORTED_VERSION, model.CurrentVersion, OLDEST_SUPPORTED_VERSION)
|
||||
time.Sleep(time.Second)
|
||||
os.Exit(EXIT_TOO_OLD)
|
||||
}
|
||||
}
|
||||
|
||||
func saveSchemaVersion(sqlStore SqlStore, version string) {
|
||||
if result := <-sqlStore.System().Update(&model.System{Name: "Version", Value: version}); result.Err != nil {
|
||||
l4g.Critical(result.Err.Error())
|
||||
time.Sleep(time.Second)
|
||||
os.Exit(EXIT_VERSION_SAVE)
|
||||
}
|
||||
|
||||
l4g.Warn(utils.T("store.sql.upgraded.warn"), version)
|
||||
}
|
||||
|
||||
func shouldPerformUpgrade(sqlStore SqlStore, currentSchemaVersion string, expectedSchemaVersion string) bool {
|
||||
if sqlStore.GetCurrentSchemaVersion() == currentSchemaVersion {
|
||||
l4g.Warn(utils.T("store.sql.schema_out_of_date.warn"), currentSchemaVersion)
|
||||
l4g.Warn(utils.T("store.sql.schema_upgrade_attempt.warn"), expectedSchemaVersion)
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func UpgradeDatabaseToVersion31(sqlStore SqlStore) {
|
||||
if shouldPerformUpgrade(sqlStore, VERSION_3_0_0, VERSION_3_1_0) {
|
||||
sqlStore.CreateColumnIfNotExists("OutgoingWebhooks", "ContentType", "varchar(128)", "varchar(128)", "")
|
||||
saveSchemaVersion(sqlStore, VERSION_3_1_0)
|
||||
}
|
||||
}
|
||||
|
||||
func UpgradeDatabaseToVersion32(sqlStore SqlStore) {
|
||||
if shouldPerformUpgrade(sqlStore, VERSION_3_1_0, VERSION_3_2_0) {
|
||||
sqlStore.CreateColumnIfNotExists("TeamMembers", "DeleteAt", "bigint(20)", "bigint", "0")
|
||||
|
||||
saveSchemaVersion(sqlStore, VERSION_3_2_0)
|
||||
}
|
||||
}
|
||||
|
||||
func themeMigrationFailed(err error) {
|
||||
l4g.Critical(utils.T("store.sql_user.migrate_theme.critical"), err)
|
||||
time.Sleep(time.Second)
|
||||
os.Exit(EXIT_THEME_MIGRATION)
|
||||
}
|
||||
|
||||
func UpgradeDatabaseToVersion33(sqlStore SqlStore) {
|
||||
if shouldPerformUpgrade(sqlStore, VERSION_3_2_0, VERSION_3_3_0) {
|
||||
if sqlStore.DoesColumnExist("Users", "ThemeProps") {
|
||||
params := map[string]interface{}{
|
||||
"Category": model.PREFERENCE_CATEGORY_THEME,
|
||||
"Name": "",
|
||||
}
|
||||
|
||||
transaction, err := sqlStore.GetMaster().Begin()
|
||||
if err != nil {
|
||||
themeMigrationFailed(err)
|
||||
}
|
||||
|
||||
// increase size of Value column of Preferences table to match the size of the ThemeProps column
|
||||
if *utils.Cfg.SqlSettings.DriverName == model.DATABASE_DRIVER_POSTGRES {
|
||||
if _, err := transaction.Exec("ALTER TABLE Preferences ALTER COLUMN Value TYPE varchar(2000)"); err != nil {
|
||||
themeMigrationFailed(err)
|
||||
}
|
||||
} else if *utils.Cfg.SqlSettings.DriverName == model.DATABASE_DRIVER_MYSQL {
|
||||
if _, err := transaction.Exec("ALTER TABLE Preferences MODIFY Value text"); err != nil {
|
||||
themeMigrationFailed(err)
|
||||
}
|
||||
}
|
||||
|
||||
// copy data across
|
||||
if _, err := transaction.Exec(
|
||||
`INSERT INTO
|
||||
Preferences(UserId, Category, Name, Value)
|
||||
SELECT
|
||||
Id, '`+model.PREFERENCE_CATEGORY_THEME+`', '', ThemeProps
|
||||
FROM
|
||||
Users
|
||||
WHERE
|
||||
Users.ThemeProps != 'null'`, params); err != nil {
|
||||
themeMigrationFailed(err)
|
||||
}
|
||||
|
||||
// delete old data
|
||||
if _, err := transaction.Exec("ALTER TABLE Users DROP COLUMN ThemeProps"); err != nil {
|
||||
themeMigrationFailed(err)
|
||||
}
|
||||
|
||||
if err := transaction.Commit(); err != nil {
|
||||
themeMigrationFailed(err)
|
||||
}
|
||||
|
||||
// rename solarized_* code themes to solarized-* to match client changes in 3.0
|
||||
var data model.Preferences
|
||||
if _, err := sqlStore.GetMaster().Select(&data, "SELECT * FROM Preferences WHERE Category = '"+model.PREFERENCE_CATEGORY_THEME+"' AND Value LIKE '%solarized_%'"); err == nil {
|
||||
for i := range data {
|
||||
data[i].Value = strings.Replace(data[i].Value, "solarized_", "solarized-", -1)
|
||||
}
|
||||
|
||||
sqlStore.Preference().Save(&data)
|
||||
}
|
||||
}
|
||||
|
||||
sqlStore.CreateColumnIfNotExists("OAuthApps", "IsTrusted", "tinyint(1)", "boolean", "0")
|
||||
sqlStore.CreateColumnIfNotExists("OAuthApps", "IconURL", "varchar(512)", "varchar(512)", "")
|
||||
sqlStore.CreateColumnIfNotExists("OAuthAccessData", "ClientId", "varchar(26)", "varchar(26)", "")
|
||||
sqlStore.CreateColumnIfNotExists("OAuthAccessData", "UserId", "varchar(26)", "varchar(26)", "")
|
||||
sqlStore.CreateColumnIfNotExists("OAuthAccessData", "ExpiresAt", "bigint", "bigint", "0")
|
||||
|
||||
if sqlStore.DoesColumnExist("OAuthAccessData", "AuthCode") {
|
||||
sqlStore.RemoveIndexIfExists("idx_oauthaccessdata_auth_code", "OAuthAccessData")
|
||||
sqlStore.RemoveColumnIfExists("OAuthAccessData", "AuthCode")
|
||||
}
|
||||
|
||||
sqlStore.RemoveColumnIfExists("Users", "LastActivityAt")
|
||||
sqlStore.RemoveColumnIfExists("Users", "LastPingAt")
|
||||
|
||||
sqlStore.CreateColumnIfNotExists("OutgoingWebhooks", "TriggerWhen", "tinyint", "integer", "0")
|
||||
|
||||
saveSchemaVersion(sqlStore, VERSION_3_3_0)
|
||||
}
|
||||
}
|
||||
|
||||
func UpgradeDatabaseToVersion34(sqlStore SqlStore) {
|
||||
if shouldPerformUpgrade(sqlStore, VERSION_3_3_0, VERSION_3_4_0) {
|
||||
sqlStore.CreateColumnIfNotExists("Status", "Manual", "BOOLEAN", "BOOLEAN", "0")
|
||||
sqlStore.CreateColumnIfNotExists("Status", "ActiveChannel", "varchar(26)", "varchar(26)", "")
|
||||
|
||||
saveSchemaVersion(sqlStore, VERSION_3_4_0)
|
||||
}
|
||||
}
|
||||
|
||||
func UpgradeDatabaseToVersion35(sqlStore SqlStore) {
|
||||
if shouldPerformUpgrade(sqlStore, VERSION_3_4_0, VERSION_3_5_0) {
|
||||
sqlStore.GetMaster().Exec("UPDATE Users SET Roles = 'system_user' WHERE Roles = ''")
|
||||
sqlStore.GetMaster().Exec("UPDATE Users SET Roles = 'system_user system_admin' WHERE Roles = 'system_admin'")
|
||||
sqlStore.GetMaster().Exec("UPDATE TeamMembers SET Roles = 'team_user' WHERE Roles = ''")
|
||||
sqlStore.GetMaster().Exec("UPDATE TeamMembers SET Roles = 'team_user team_admin' WHERE Roles = 'admin'")
|
||||
sqlStore.GetMaster().Exec("UPDATE ChannelMembers SET Roles = 'channel_user' WHERE Roles = ''")
|
||||
sqlStore.GetMaster().Exec("UPDATE ChannelMembers SET Roles = 'channel_user channel_admin' WHERE Roles = 'admin'")
|
||||
|
||||
// The rest of the migration from Filenames -> FileIds is done lazily in api.GetFileInfosForPost
|
||||
sqlStore.CreateColumnIfNotExists("Posts", "FileIds", "varchar(150)", "varchar(150)", "[]")
|
||||
|
||||
// Increase maximum length of the Channel table Purpose column.
|
||||
if sqlStore.GetMaxLengthOfColumnIfExists("Channels", "Purpose") != "250" {
|
||||
sqlStore.AlterColumnTypeIfExists("Channels", "Purpose", "varchar(250)", "varchar(250)")
|
||||
}
|
||||
|
||||
sqlStore.Session().RemoveAllSessions()
|
||||
|
||||
saveSchemaVersion(sqlStore, VERSION_3_5_0)
|
||||
}
|
||||
}
|
||||
|
||||
func UpgradeDatabaseToVersion36(sqlStore SqlStore) {
|
||||
if shouldPerformUpgrade(sqlStore, VERSION_3_5_0, VERSION_3_6_0) {
|
||||
sqlStore.CreateColumnIfNotExists("Posts", "HasReactions", "tinyint", "boolean", "0")
|
||||
|
||||
// Create Team Description column
|
||||
sqlStore.CreateColumnIfNotExists("Teams", "Description", "varchar(255)", "varchar(255)", "")
|
||||
|
||||
// Add a Position column to users.
|
||||
sqlStore.CreateColumnIfNotExists("Users", "Position", "varchar(64)", "varchar(64)", "")
|
||||
|
||||
// Remove ActiveChannel column from Status
|
||||
sqlStore.RemoveColumnIfExists("Status", "ActiveChannel")
|
||||
|
||||
saveSchemaVersion(sqlStore, VERSION_3_6_0)
|
||||
}
|
||||
}
|
||||
|
||||
func UpgradeDatabaseToVersion37(sqlStore SqlStore) {
|
||||
if shouldPerformUpgrade(sqlStore, VERSION_3_6_0, VERSION_3_7_0) {
|
||||
// Add EditAt column to Posts
|
||||
sqlStore.CreateColumnIfNotExists("Posts", "EditAt", " bigint", " bigint", "0")
|
||||
|
||||
saveSchemaVersion(sqlStore, VERSION_3_7_0)
|
||||
}
|
||||
}
|
||||
|
||||
func UpgradeDatabaseToVersion38(sqlStore SqlStore) {
|
||||
if shouldPerformUpgrade(sqlStore, VERSION_3_7_0, VERSION_3_8_0) {
|
||||
// Add the IsPinned column to posts.
|
||||
sqlStore.CreateColumnIfNotExists("Posts", "IsPinned", "boolean", "boolean", "0")
|
||||
|
||||
saveSchemaVersion(sqlStore, VERSION_3_8_0)
|
||||
}
|
||||
}
|
||||
|
||||
func UpgradeDatabaseToVersion39(sqlStore SqlStore) {
|
||||
if shouldPerformUpgrade(sqlStore, VERSION_3_8_0, VERSION_3_9_0) {
|
||||
sqlStore.CreateColumnIfNotExists("OAuthAccessData", "Scope", "varchar(128)", "varchar(128)", model.DEFAULT_SCOPE)
|
||||
sqlStore.RemoveTableIfExists("PasswordRecovery")
|
||||
|
||||
saveSchemaVersion(sqlStore, VERSION_3_9_0)
|
||||
}
|
||||
}
|
||||
|
||||
func UpgradeDatabaseToVersion310(sqlStore SqlStore) {
|
||||
if shouldPerformUpgrade(sqlStore, VERSION_3_9_0, VERSION_3_10_0) {
|
||||
saveSchemaVersion(sqlStore, VERSION_3_10_0)
|
||||
}
|
||||
}
|
||||
|
||||
func UpgradeDatabaseToVersion40(sqlStore SqlStore) {
|
||||
if shouldPerformUpgrade(sqlStore, VERSION_3_10_0, VERSION_4_0_0) {
|
||||
saveSchemaVersion(sqlStore, VERSION_4_0_0)
|
||||
}
|
||||
}
|
||||
|
||||
func UpgradeDatabaseToVersion41(sqlStore SqlStore) {
|
||||
if shouldPerformUpgrade(sqlStore, VERSION_4_0_0, VERSION_4_1_0) {
|
||||
// Increase maximum length of the Users table Roles column.
|
||||
if sqlStore.GetMaxLengthOfColumnIfExists("Users", "Roles") != "256" {
|
||||
sqlStore.AlterColumnTypeIfExists("Users", "Roles", "varchar(256)", "varchar(256)")
|
||||
}
|
||||
|
||||
sqlStore.RemoveTableIfExists("JobStatuses")
|
||||
|
||||
saveSchemaVersion(sqlStore, VERSION_4_1_0)
|
||||
}
|
||||
}
|
||||
|
||||
func UpgradeDatabaseToVersion42(sqlStore SqlStore) {
|
||||
if shouldPerformUpgrade(sqlStore, VERSION_4_1_0, VERSION_4_2_0) {
|
||||
saveSchemaVersion(sqlStore, VERSION_4_2_0)
|
||||
}
|
||||
}
|
||||
|
||||
func UpgradeDatabaseToVersion43(sqlStore SqlStore) {
|
||||
// TODO: Uncomment following condition when version 4.3.0 is released
|
||||
//if shouldPerformUpgrade(sqlStore, VERSION_4_2_0, VERSION_4_3_0) {
|
||||
// saveSchemaVersion(sqlStore, VERSION_4_3_0)
|
||||
//}
|
||||
}
|
||||
41
store/sqlstore/upgrade_test.go
Обычный файл
41
store/sqlstore/upgrade_test.go
Обычный файл
@@ -0,0 +1,41 @@
|
||||
// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
|
||||
// See License.txt for license information.
|
||||
|
||||
package sqlstore
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/mattermost/mattermost-server/model"
|
||||
"github.com/mattermost/mattermost-server/store"
|
||||
)
|
||||
|
||||
func TestStoreUpgrade(t *testing.T) {
|
||||
ss := Setup()
|
||||
|
||||
saveSchemaVersion(ss.(*store.LayeredStore).DatabaseLayer.(SqlStore), VERSION_3_0_0)
|
||||
UpgradeDatabase(ss.(*store.LayeredStore).DatabaseLayer.(SqlStore))
|
||||
|
||||
saveSchemaVersion(ss.(*store.LayeredStore).DatabaseLayer.(SqlStore), "")
|
||||
UpgradeDatabase(ss.(*store.LayeredStore).DatabaseLayer.(SqlStore))
|
||||
}
|
||||
|
||||
func TestSaveSchemaVersion(t *testing.T) {
|
||||
ss := Setup()
|
||||
|
||||
saveSchemaVersion(ss.(*store.LayeredStore).DatabaseLayer.(SqlStore), VERSION_3_0_0)
|
||||
if result := <-ss.System().Get(); result.Err != nil {
|
||||
t.Fatal(result.Err)
|
||||
} else {
|
||||
props := result.Data.(model.StringMap)
|
||||
if props["Version"] != VERSION_3_0_0 {
|
||||
t.Fatal("version not updated")
|
||||
}
|
||||
}
|
||||
|
||||
if ss.(*store.LayeredStore).DatabaseLayer.(SqlStore).GetCurrentSchemaVersion() != VERSION_3_0_0 {
|
||||
t.Fatal("version not updated")
|
||||
}
|
||||
|
||||
saveSchemaVersion(ss.(*store.LayeredStore).DatabaseLayer.(SqlStore), model.CurrentVersion)
|
||||
}
|
||||
263
store/sqlstore/user_access_token_store.go
Обычный файл
263
store/sqlstore/user_access_token_store.go
Обычный файл
@@ -0,0 +1,263 @@
|
||||
// Copyright (c) 2017 Mattermost, Inc. All Rights Reserved.
|
||||
// See License.txt for license information.
|
||||
|
||||
package sqlstore
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"net/http"
|
||||
|
||||
"github.com/mattermost/gorp"
|
||||
"github.com/mattermost/mattermost-server/model"
|
||||
"github.com/mattermost/mattermost-server/store"
|
||||
"github.com/mattermost/mattermost-server/utils"
|
||||
)
|
||||
|
||||
type SqlUserAccessTokenStore struct {
|
||||
SqlStore
|
||||
}
|
||||
|
||||
func NewSqlUserAccessTokenStore(sqlStore SqlStore) store.UserAccessTokenStore {
|
||||
s := &SqlUserAccessTokenStore{sqlStore}
|
||||
|
||||
for _, db := range sqlStore.GetAllConns() {
|
||||
table := db.AddTableWithName(model.UserAccessToken{}, "UserAccessTokens").SetKeys(false, "Id")
|
||||
table.ColMap("Id").SetMaxSize(26)
|
||||
table.ColMap("Token").SetMaxSize(26).SetUnique(true)
|
||||
table.ColMap("UserId").SetMaxSize(26)
|
||||
table.ColMap("Description").SetMaxSize(512)
|
||||
}
|
||||
|
||||
return s
|
||||
}
|
||||
|
||||
func (s SqlUserAccessTokenStore) CreateIndexesIfNotExists() {
|
||||
s.CreateIndexIfNotExists("idx_user_access_tokens_token", "UserAccessTokens", "Token")
|
||||
s.CreateIndexIfNotExists("idx_user_access_tokens_user_id", "UserAccessTokens", "UserId")
|
||||
}
|
||||
|
||||
func (s SqlUserAccessTokenStore) Save(token *model.UserAccessToken) store.StoreChannel {
|
||||
|
||||
storeChannel := make(store.StoreChannel, 1)
|
||||
|
||||
go func() {
|
||||
result := store.StoreResult{}
|
||||
|
||||
token.PreSave()
|
||||
|
||||
if result.Err = token.IsValid(); result.Err != nil {
|
||||
storeChannel <- result
|
||||
close(storeChannel)
|
||||
return
|
||||
}
|
||||
|
||||
if err := s.GetMaster().Insert(token); err != nil {
|
||||
result.Err = model.NewAppError("SqlUserAccessTokenStore.Save", "store.sql_user_access_token.save.app_error", nil, "", http.StatusInternalServerError)
|
||||
} else {
|
||||
result.Data = token
|
||||
}
|
||||
|
||||
storeChannel <- result
|
||||
close(storeChannel)
|
||||
}()
|
||||
|
||||
return storeChannel
|
||||
}
|
||||
|
||||
func (s SqlUserAccessTokenStore) Delete(tokenId string) store.StoreChannel {
|
||||
|
||||
storeChannel := make(store.StoreChannel, 1)
|
||||
|
||||
go func() {
|
||||
result := store.StoreResult{}
|
||||
|
||||
transaction, err := s.GetMaster().Begin()
|
||||
if err != nil {
|
||||
result.Err = model.NewAppError("SqlUserAccessTokenStore.Delete", "store.sql_user_access_token.delete.app_error", nil, err.Error(), http.StatusInternalServerError)
|
||||
} else {
|
||||
if extrasResult := s.deleteSessionsAndTokensById(transaction, tokenId); extrasResult.Err != nil {
|
||||
result = extrasResult
|
||||
}
|
||||
|
||||
if result.Err == nil {
|
||||
if err := transaction.Commit(); err != nil {
|
||||
// don't need to rollback here since the transaction is already closed
|
||||
result.Err = model.NewAppError("SqlUserAccessTokenStore.Delete", "store.sql_user_access_token.delete.app_error", nil, err.Error(), http.StatusInternalServerError)
|
||||
}
|
||||
} else {
|
||||
if err := transaction.Rollback(); err != nil {
|
||||
result.Err = model.NewAppError("SqlUserAccessTokenStore.Delete", "store.sql_user_access_token.delete.app_error", nil, err.Error(), http.StatusInternalServerError)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
storeChannel <- result
|
||||
close(storeChannel)
|
||||
}()
|
||||
|
||||
return storeChannel
|
||||
}
|
||||
|
||||
func (s SqlUserAccessTokenStore) deleteSessionsAndTokensById(transaction *gorp.Transaction, tokenId string) store.StoreResult {
|
||||
result := store.StoreResult{}
|
||||
|
||||
query := ""
|
||||
if *utils.Cfg.SqlSettings.DriverName == model.DATABASE_DRIVER_POSTGRES {
|
||||
query = "DELETE FROM Sessions s USING UserAccessTokens o WHERE o.Token = s.Token AND o.Id = :Id"
|
||||
} else if *utils.Cfg.SqlSettings.DriverName == model.DATABASE_DRIVER_MYSQL {
|
||||
query = "DELETE s.* FROM Sessions s INNER JOIN UserAccessTokens o ON o.Token = s.Token WHERE o.Id = :Id"
|
||||
}
|
||||
|
||||
if _, err := transaction.Exec(query, map[string]interface{}{"Id": tokenId}); err != nil {
|
||||
result.Err = model.NewAppError("SqlUserAccessTokenStore.deleteSessionsById", "store.sql_user_access_token.delete.app_error", nil, "id="+tokenId+", err="+err.Error(), http.StatusInternalServerError)
|
||||
return result
|
||||
}
|
||||
|
||||
return s.deleteTokensById(transaction, tokenId)
|
||||
}
|
||||
|
||||
func (s SqlUserAccessTokenStore) deleteTokensById(transaction *gorp.Transaction, tokenId string) store.StoreResult {
|
||||
result := store.StoreResult{}
|
||||
|
||||
if _, err := transaction.Exec("DELETE FROM UserAccessTokens WHERE Id = :Id", map[string]interface{}{"Id": tokenId}); err != nil {
|
||||
result.Err = model.NewAppError("SqlUserAccessTokenStore.deleteTokensById", "store.sql_user_access_token.delete.app_error", nil, "", http.StatusInternalServerError)
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
func (s SqlUserAccessTokenStore) DeleteAllForUser(userId string) store.StoreChannel {
|
||||
|
||||
storeChannel := make(store.StoreChannel, 1)
|
||||
|
||||
go func() {
|
||||
result := store.StoreResult{}
|
||||
|
||||
transaction, err := s.GetMaster().Begin()
|
||||
if err != nil {
|
||||
result.Err = model.NewAppError("SqlUserAccessTokenStore.DeleteAllForUser", "store.sql_user_access_token.delete.app_error", nil, err.Error(), http.StatusInternalServerError)
|
||||
} else {
|
||||
if extrasResult := s.deleteSessionsandTokensByUser(transaction, userId); extrasResult.Err != nil {
|
||||
result = extrasResult
|
||||
}
|
||||
|
||||
if result.Err == nil {
|
||||
if err := transaction.Commit(); err != nil {
|
||||
// don't need to rollback here since the transaction is already closed
|
||||
result.Err = model.NewAppError("SqlUserAccessTokenStore.DeleteAllForUser", "store.sql_user_access_token.delete.app_error", nil, err.Error(), http.StatusInternalServerError)
|
||||
}
|
||||
} else {
|
||||
if err := transaction.Rollback(); err != nil {
|
||||
result.Err = model.NewAppError("SqlUserAccessTokenStore.DeleteAllForUser", "store.sql_user_access_token.delete.app_error", nil, err.Error(), http.StatusInternalServerError)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
storeChannel <- result
|
||||
close(storeChannel)
|
||||
}()
|
||||
|
||||
return storeChannel
|
||||
}
|
||||
|
||||
func (s SqlUserAccessTokenStore) deleteSessionsandTokensByUser(transaction *gorp.Transaction, userId string) store.StoreResult {
|
||||
result := store.StoreResult{}
|
||||
|
||||
query := ""
|
||||
if *utils.Cfg.SqlSettings.DriverName == model.DATABASE_DRIVER_POSTGRES {
|
||||
query = "DELETE FROM Sessions s USING UserAccessTokens o WHERE o.Token = s.Token AND o.UserId = :UserId"
|
||||
} else if *utils.Cfg.SqlSettings.DriverName == model.DATABASE_DRIVER_MYSQL {
|
||||
query = "DELETE s.* FROM Sessions s INNER JOIN UserAccessTokens o ON o.Token = s.Token WHERE o.UserId = :UserId"
|
||||
}
|
||||
|
||||
if _, err := transaction.Exec(query, map[string]interface{}{"UserId": userId}); err != nil {
|
||||
result.Err = model.NewAppError("SqlUserAccessTokenStore.deleteSessionsByUser", "store.sql_user_access_token.delete.app_error", nil, "user_id="+userId+", err="+err.Error(), http.StatusInternalServerError)
|
||||
return result
|
||||
}
|
||||
|
||||
return s.deleteTokensByUser(transaction, userId)
|
||||
}
|
||||
|
||||
func (s SqlUserAccessTokenStore) deleteTokensByUser(transaction *gorp.Transaction, userId string) store.StoreResult {
|
||||
result := store.StoreResult{}
|
||||
|
||||
if _, err := transaction.Exec("DELETE FROM UserAccessTokens WHERE UserId = :UserId", map[string]interface{}{"UserId": userId}); err != nil {
|
||||
result.Err = model.NewAppError("SqlUserAccessTokenStore.deleteTokensByUser", "store.sql_user_access_token.delete.app_error", nil, "", http.StatusInternalServerError)
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
func (s SqlUserAccessTokenStore) Get(tokenId string) store.StoreChannel {
|
||||
|
||||
storeChannel := make(store.StoreChannel, 1)
|
||||
|
||||
go func() {
|
||||
result := store.StoreResult{}
|
||||
|
||||
token := model.UserAccessToken{}
|
||||
|
||||
if err := s.GetReplica().SelectOne(&token, "SELECT * FROM UserAccessTokens WHERE Id = :Id", map[string]interface{}{"Id": tokenId}); err != nil {
|
||||
if err == sql.ErrNoRows {
|
||||
result.Err = model.NewAppError("SqlUserAccessTokenStore.Get", "store.sql_user_access_token.get.app_error", nil, err.Error(), http.StatusNotFound)
|
||||
} else {
|
||||
result.Err = model.NewAppError("SqlUserAccessTokenStore.Get", "store.sql_user_access_token.get.app_error", nil, err.Error(), http.StatusInternalServerError)
|
||||
}
|
||||
}
|
||||
|
||||
result.Data = &token
|
||||
|
||||
storeChannel <- result
|
||||
close(storeChannel)
|
||||
}()
|
||||
|
||||
return storeChannel
|
||||
}
|
||||
|
||||
func (s SqlUserAccessTokenStore) GetByToken(tokenString string) store.StoreChannel {
|
||||
|
||||
storeChannel := make(store.StoreChannel, 1)
|
||||
|
||||
go func() {
|
||||
result := store.StoreResult{}
|
||||
|
||||
token := model.UserAccessToken{}
|
||||
|
||||
if err := s.GetReplica().SelectOne(&token, "SELECT * FROM UserAccessTokens WHERE Token = :Token", map[string]interface{}{"Token": tokenString}); err != nil {
|
||||
if err == sql.ErrNoRows {
|
||||
result.Err = model.NewAppError("SqlUserAccessTokenStore.GetByToken", "store.sql_user_access_token.get_by_token.app_error", nil, err.Error(), http.StatusNotFound)
|
||||
} else {
|
||||
result.Err = model.NewAppError("SqlUserAccessTokenStore.GetByToken", "store.sql_user_access_token.get_by_token.app_error", nil, err.Error(), http.StatusInternalServerError)
|
||||
}
|
||||
}
|
||||
|
||||
result.Data = &token
|
||||
|
||||
storeChannel <- result
|
||||
close(storeChannel)
|
||||
}()
|
||||
|
||||
return storeChannel
|
||||
}
|
||||
|
||||
func (s SqlUserAccessTokenStore) GetByUser(userId string, offset, limit int) store.StoreChannel {
|
||||
|
||||
storeChannel := make(store.StoreChannel, 1)
|
||||
|
||||
go func() {
|
||||
result := store.StoreResult{}
|
||||
|
||||
tokens := []*model.UserAccessToken{}
|
||||
|
||||
if _, err := s.GetReplica().Select(&tokens, "SELECT * FROM UserAccessTokens WHERE UserId = :UserId LIMIT :Limit OFFSET :Offset", map[string]interface{}{"UserId": userId, "Offset": offset, "Limit": limit}); err != nil {
|
||||
result.Err = model.NewAppError("SqlUserAccessTokenStore.GetByUser", "store.sql_user_access_token.get_by_user.app_error", nil, err.Error(), http.StatusInternalServerError)
|
||||
}
|
||||
|
||||
result.Data = tokens
|
||||
|
||||
storeChannel <- result
|
||||
close(storeChannel)
|
||||
}()
|
||||
|
||||
return storeChannel
|
||||
}
|
||||
87
store/sqlstore/user_access_token_store_test.go
Обычный файл
87
store/sqlstore/user_access_token_store_test.go
Обычный файл
@@ -0,0 +1,87 @@
|
||||
// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
|
||||
// See License.txt for license information.
|
||||
|
||||
package sqlstore
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/mattermost/mattermost-server/model"
|
||||
"github.com/mattermost/mattermost-server/store"
|
||||
)
|
||||
|
||||
func TestUserAccessTokenSaveGetDelete(t *testing.T) {
|
||||
ss := Setup()
|
||||
|
||||
uat := &model.UserAccessToken{
|
||||
Token: model.NewId(),
|
||||
UserId: model.NewId(),
|
||||
Description: "testtoken",
|
||||
}
|
||||
|
||||
s1 := model.Session{}
|
||||
s1.UserId = uat.UserId
|
||||
s1.Token = uat.Token
|
||||
|
||||
store.Must(ss.Session().Save(&s1))
|
||||
|
||||
if result := <-ss.UserAccessToken().Save(uat); result.Err != nil {
|
||||
t.Fatal(result.Err)
|
||||
}
|
||||
|
||||
if result := <-ss.UserAccessToken().Get(uat.Id); result.Err != nil {
|
||||
t.Fatal(result.Err)
|
||||
} else if received := result.Data.(*model.UserAccessToken); received.Token != uat.Token {
|
||||
t.Fatal("received incorrect token after save")
|
||||
}
|
||||
|
||||
if result := <-ss.UserAccessToken().GetByToken(uat.Token); result.Err != nil {
|
||||
t.Fatal(result.Err)
|
||||
} else if received := result.Data.(*model.UserAccessToken); received.Token != uat.Token {
|
||||
t.Fatal("received incorrect token after save")
|
||||
}
|
||||
|
||||
if result := <-ss.UserAccessToken().GetByToken("notarealtoken"); result.Err == nil {
|
||||
t.Fatal("should have failed on bad token")
|
||||
}
|
||||
|
||||
if result := <-ss.UserAccessToken().GetByUser(uat.UserId, 0, 100); result.Err != nil {
|
||||
t.Fatal(result.Err)
|
||||
} else if received := result.Data.([]*model.UserAccessToken); len(received) != 1 {
|
||||
t.Fatal("received incorrect number of tokens after save")
|
||||
}
|
||||
|
||||
if result := <-ss.UserAccessToken().Delete(uat.Id); result.Err != nil {
|
||||
t.Fatal(result.Err)
|
||||
}
|
||||
|
||||
if err := (<-ss.Session().Get(s1.Token)).Err; err == nil {
|
||||
t.Fatal("should error - session should be deleted")
|
||||
}
|
||||
|
||||
if err := (<-ss.UserAccessToken().GetByToken(s1.Token)).Err; err == nil {
|
||||
t.Fatal("should error - access token should be deleted")
|
||||
}
|
||||
|
||||
s2 := model.Session{}
|
||||
s2.UserId = uat.UserId
|
||||
s2.Token = uat.Token
|
||||
|
||||
store.Must(ss.Session().Save(&s2))
|
||||
|
||||
if result := <-ss.UserAccessToken().Save(uat); result.Err != nil {
|
||||
t.Fatal(result.Err)
|
||||
}
|
||||
|
||||
if result := <-ss.UserAccessToken().DeleteAllForUser(uat.UserId); result.Err != nil {
|
||||
t.Fatal(result.Err)
|
||||
}
|
||||
|
||||
if err := (<-ss.Session().Get(s2.Token)).Err; err == nil {
|
||||
t.Fatal("should error - session should be deleted")
|
||||
}
|
||||
|
||||
if err := (<-ss.UserAccessToken().GetByToken(s2.Token)).Err; err == nil {
|
||||
t.Fatal("should error - access token should be deleted")
|
||||
}
|
||||
}
|
||||
1620
store/sqlstore/user_store.go
Обычный файл
1620
store/sqlstore/user_store.go
Обычный файл
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
2020
store/sqlstore/user_store_test.go
Обычный файл
2020
store/sqlstore/user_store_test.go
Обычный файл
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
573
store/sqlstore/webhook_store.go
Обычный файл
573
store/sqlstore/webhook_store.go
Обычный файл
@@ -0,0 +1,573 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See License.txt for license information.
|
||||
|
||||
package sqlstore
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"database/sql"
|
||||
|
||||
"github.com/mattermost/mattermost-server/einterfaces"
|
||||
"github.com/mattermost/mattermost-server/model"
|
||||
"github.com/mattermost/mattermost-server/store"
|
||||
"github.com/mattermost/mattermost-server/utils"
|
||||
)
|
||||
|
||||
type SqlWebhookStore struct {
|
||||
SqlStore
|
||||
metrics einterfaces.MetricsInterface
|
||||
}
|
||||
|
||||
const (
|
||||
WEBHOOK_CACHE_SIZE = 25000
|
||||
WEBHOOK_CACHE_SEC = 900 // 15 minutes
|
||||
)
|
||||
|
||||
var webhookCache = utils.NewLru(WEBHOOK_CACHE_SIZE)
|
||||
|
||||
func ClearWebhookCaches() {
|
||||
webhookCache.Purge()
|
||||
}
|
||||
|
||||
func NewSqlWebhookStore(sqlStore SqlStore, metrics einterfaces.MetricsInterface) store.WebhookStore {
|
||||
s := &SqlWebhookStore{
|
||||
SqlStore: sqlStore,
|
||||
metrics: metrics,
|
||||
}
|
||||
|
||||
for _, db := range sqlStore.GetAllConns() {
|
||||
table := db.AddTableWithName(model.IncomingWebhook{}, "IncomingWebhooks").SetKeys(false, "Id")
|
||||
table.ColMap("Id").SetMaxSize(26)
|
||||
table.ColMap("UserId").SetMaxSize(26)
|
||||
table.ColMap("ChannelId").SetMaxSize(26)
|
||||
table.ColMap("TeamId").SetMaxSize(26)
|
||||
table.ColMap("DisplayName").SetMaxSize(64)
|
||||
table.ColMap("Description").SetMaxSize(128)
|
||||
|
||||
tableo := db.AddTableWithName(model.OutgoingWebhook{}, "OutgoingWebhooks").SetKeys(false, "Id")
|
||||
tableo.ColMap("Id").SetMaxSize(26)
|
||||
tableo.ColMap("Token").SetMaxSize(26)
|
||||
tableo.ColMap("CreatorId").SetMaxSize(26)
|
||||
tableo.ColMap("ChannelId").SetMaxSize(26)
|
||||
tableo.ColMap("TeamId").SetMaxSize(26)
|
||||
tableo.ColMap("TriggerWords").SetMaxSize(1024)
|
||||
tableo.ColMap("CallbackURLs").SetMaxSize(1024)
|
||||
tableo.ColMap("DisplayName").SetMaxSize(64)
|
||||
tableo.ColMap("Description").SetMaxSize(128)
|
||||
tableo.ColMap("ContentType").SetMaxSize(128)
|
||||
tableo.ColMap("TriggerWhen").SetMaxSize(1)
|
||||
}
|
||||
|
||||
return s
|
||||
}
|
||||
|
||||
func (s SqlWebhookStore) CreateIndexesIfNotExists() {
|
||||
s.CreateIndexIfNotExists("idx_incoming_webhook_user_id", "IncomingWebhooks", "UserId")
|
||||
s.CreateIndexIfNotExists("idx_incoming_webhook_team_id", "IncomingWebhooks", "TeamId")
|
||||
s.CreateIndexIfNotExists("idx_outgoing_webhook_team_id", "OutgoingWebhooks", "TeamId")
|
||||
|
||||
s.CreateIndexIfNotExists("idx_incoming_webhook_update_at", "IncomingWebhooks", "UpdateAt")
|
||||
s.CreateIndexIfNotExists("idx_incoming_webhook_create_at", "IncomingWebhooks", "CreateAt")
|
||||
s.CreateIndexIfNotExists("idx_incoming_webhook_delete_at", "IncomingWebhooks", "DeleteAt")
|
||||
|
||||
s.CreateIndexIfNotExists("idx_outgoing_webhook_update_at", "OutgoingWebhooks", "UpdateAt")
|
||||
s.CreateIndexIfNotExists("idx_outgoing_webhook_create_at", "OutgoingWebhooks", "CreateAt")
|
||||
s.CreateIndexIfNotExists("idx_outgoing_webhook_delete_at", "OutgoingWebhooks", "DeleteAt")
|
||||
}
|
||||
|
||||
func (s SqlWebhookStore) InvalidateWebhookCache(webhookId string) {
|
||||
webhookCache.Remove(webhookId)
|
||||
}
|
||||
|
||||
func (s SqlWebhookStore) SaveIncoming(webhook *model.IncomingWebhook) store.StoreChannel {
|
||||
storeChannel := make(store.StoreChannel, 1)
|
||||
|
||||
go func() {
|
||||
result := store.StoreResult{}
|
||||
|
||||
if len(webhook.Id) > 0 {
|
||||
result.Err = model.NewAppError("SqlWebhookStore.SaveIncoming", "store.sql_webhooks.save_incoming.existing.app_error", nil, "id="+webhook.Id, http.StatusBadRequest)
|
||||
storeChannel <- result
|
||||
close(storeChannel)
|
||||
return
|
||||
}
|
||||
|
||||
webhook.PreSave()
|
||||
if result.Err = webhook.IsValid(); result.Err != nil {
|
||||
storeChannel <- result
|
||||
close(storeChannel)
|
||||
return
|
||||
}
|
||||
|
||||
if err := s.GetMaster().Insert(webhook); err != nil {
|
||||
result.Err = model.NewAppError("SqlWebhookStore.SaveIncoming", "store.sql_webhooks.save_incoming.app_error", nil, "id="+webhook.Id+", "+err.Error(), http.StatusInternalServerError)
|
||||
} else {
|
||||
result.Data = webhook
|
||||
}
|
||||
|
||||
storeChannel <- result
|
||||
close(storeChannel)
|
||||
}()
|
||||
|
||||
return storeChannel
|
||||
}
|
||||
|
||||
func (s SqlWebhookStore) UpdateIncoming(hook *model.IncomingWebhook) store.StoreChannel {
|
||||
storeChannel := make(store.StoreChannel, 1)
|
||||
|
||||
go func() {
|
||||
result := store.StoreResult{}
|
||||
|
||||
hook.UpdateAt = model.GetMillis()
|
||||
|
||||
if _, err := s.GetMaster().Update(hook); err != nil {
|
||||
result.Err = model.NewAppError("SqlWebhookStore.UpdateIncoming", "store.sql_webhooks.update_incoming.app_error", nil, "id="+hook.Id+", "+err.Error(), http.StatusInternalServerError)
|
||||
} else {
|
||||
result.Data = hook
|
||||
}
|
||||
|
||||
storeChannel <- result
|
||||
close(storeChannel)
|
||||
}()
|
||||
|
||||
return storeChannel
|
||||
}
|
||||
|
||||
func (s SqlWebhookStore) GetIncoming(id string, allowFromCache bool) store.StoreChannel {
|
||||
storeChannel := make(store.StoreChannel, 1)
|
||||
|
||||
go func() {
|
||||
result := store.StoreResult{}
|
||||
|
||||
if allowFromCache {
|
||||
if cacheItem, ok := webhookCache.Get(id); ok {
|
||||
if s.metrics != nil {
|
||||
s.metrics.IncrementMemCacheHitCounter("Webhook")
|
||||
}
|
||||
result.Data = cacheItem.(*model.IncomingWebhook)
|
||||
storeChannel <- result
|
||||
close(storeChannel)
|
||||
return
|
||||
} else {
|
||||
if s.metrics != nil {
|
||||
s.metrics.IncrementMemCacheMissCounter("Webhook")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var webhook model.IncomingWebhook
|
||||
|
||||
if err := s.GetReplica().SelectOne(&webhook, "SELECT * FROM IncomingWebhooks WHERE Id = :Id AND DeleteAt = 0", map[string]interface{}{"Id": id}); err != nil {
|
||||
if err == sql.ErrNoRows {
|
||||
result.Err = model.NewAppError("SqlWebhookStore.GetIncoming", "store.sql_webhooks.get_incoming.app_error", nil, "id="+id+", err="+err.Error(), http.StatusNotFound)
|
||||
} else {
|
||||
result.Err = model.NewAppError("SqlWebhookStore.GetIncoming", "store.sql_webhooks.get_incoming.app_error", nil, "id="+id+", err="+err.Error(), http.StatusInternalServerError)
|
||||
}
|
||||
}
|
||||
|
||||
if result.Err == nil {
|
||||
webhookCache.AddWithExpiresInSecs(id, &webhook, WEBHOOK_CACHE_SEC)
|
||||
}
|
||||
|
||||
result.Data = &webhook
|
||||
|
||||
storeChannel <- result
|
||||
close(storeChannel)
|
||||
}()
|
||||
|
||||
return storeChannel
|
||||
}
|
||||
|
||||
func (s SqlWebhookStore) DeleteIncoming(webhookId string, time int64) store.StoreChannel {
|
||||
storeChannel := make(store.StoreChannel, 1)
|
||||
|
||||
go func() {
|
||||
result := store.StoreResult{}
|
||||
|
||||
_, err := s.GetMaster().Exec("Update IncomingWebhooks SET DeleteAt = :DeleteAt, UpdateAt = :UpdateAt WHERE Id = :Id", map[string]interface{}{"DeleteAt": time, "UpdateAt": time, "Id": webhookId})
|
||||
if err != nil {
|
||||
result.Err = model.NewAppError("SqlWebhookStore.DeleteIncoming", "store.sql_webhooks.delete_incoming.app_error", nil, "id="+webhookId+", err="+err.Error(), http.StatusInternalServerError)
|
||||
}
|
||||
|
||||
s.InvalidateWebhookCache(webhookId)
|
||||
|
||||
storeChannel <- result
|
||||
close(storeChannel)
|
||||
}()
|
||||
|
||||
return storeChannel
|
||||
}
|
||||
|
||||
func (s SqlWebhookStore) PermanentDeleteIncomingByUser(userId string) store.StoreChannel {
|
||||
storeChannel := make(store.StoreChannel, 1)
|
||||
|
||||
go func() {
|
||||
result := store.StoreResult{}
|
||||
|
||||
_, err := s.GetMaster().Exec("DELETE FROM IncomingWebhooks WHERE UserId = :UserId", map[string]interface{}{"UserId": userId})
|
||||
if err != nil {
|
||||
result.Err = model.NewAppError("SqlWebhookStore.DeleteIncomingByUser", "store.sql_webhooks.permanent_delete_incoming_by_user.app_error", nil, "id="+userId+", err="+err.Error(), http.StatusInternalServerError)
|
||||
}
|
||||
|
||||
ClearWebhookCaches()
|
||||
|
||||
storeChannel <- result
|
||||
close(storeChannel)
|
||||
}()
|
||||
|
||||
return storeChannel
|
||||
}
|
||||
|
||||
func (s SqlWebhookStore) PermanentDeleteIncomingByChannel(channelId string) store.StoreChannel {
|
||||
storeChannel := make(store.StoreChannel, 1)
|
||||
|
||||
go func() {
|
||||
result := store.StoreResult{}
|
||||
|
||||
_, err := s.GetMaster().Exec("DELETE FROM IncomingWebhooks WHERE ChannelId = :ChannelId", map[string]interface{}{"ChannelId": channelId})
|
||||
if err != nil {
|
||||
result.Err = model.NewAppError("SqlWebhookStore.DeleteIncomingByChannel", "store.sql_webhooks.permanent_delete_incoming_by_channel.app_error", nil, "id="+channelId+", err="+err.Error(), http.StatusInternalServerError)
|
||||
}
|
||||
|
||||
ClearWebhookCaches()
|
||||
|
||||
storeChannel <- result
|
||||
close(storeChannel)
|
||||
}()
|
||||
|
||||
return storeChannel
|
||||
}
|
||||
|
||||
func (s SqlWebhookStore) GetIncomingList(offset, limit int) store.StoreChannel {
|
||||
storeChannel := make(store.StoreChannel, 1)
|
||||
|
||||
go func() {
|
||||
result := store.StoreResult{}
|
||||
|
||||
var webhooks []*model.IncomingWebhook
|
||||
|
||||
if _, err := s.GetReplica().Select(&webhooks, "SELECT * FROM IncomingWebhooks WHERE DeleteAt = 0 LIMIT :Limit OFFSET :Offset", map[string]interface{}{"Limit": limit, "Offset": offset}); err != nil {
|
||||
result.Err = model.NewAppError("SqlWebhookStore.GetIncomingList", "store.sql_webhooks.get_incoming_by_user.app_error", nil, "err="+err.Error(), http.StatusInternalServerError)
|
||||
}
|
||||
|
||||
result.Data = webhooks
|
||||
|
||||
storeChannel <- result
|
||||
close(storeChannel)
|
||||
}()
|
||||
|
||||
return storeChannel
|
||||
}
|
||||
|
||||
func (s SqlWebhookStore) GetIncomingByTeam(teamId string, offset, limit int) store.StoreChannel {
|
||||
storeChannel := make(store.StoreChannel, 1)
|
||||
|
||||
go func() {
|
||||
result := store.StoreResult{}
|
||||
|
||||
var webhooks []*model.IncomingWebhook
|
||||
|
||||
if _, err := s.GetReplica().Select(&webhooks, "SELECT * FROM IncomingWebhooks WHERE TeamId = :TeamId AND DeleteAt = 0 LIMIT :Limit OFFSET :Offset", map[string]interface{}{"TeamId": teamId, "Limit": limit, "Offset": offset}); err != nil {
|
||||
result.Err = model.NewAppError("SqlWebhookStore.GetIncomingByUser", "store.sql_webhooks.get_incoming_by_user.app_error", nil, "teamId="+teamId+", err="+err.Error(), http.StatusInternalServerError)
|
||||
}
|
||||
|
||||
result.Data = webhooks
|
||||
|
||||
storeChannel <- result
|
||||
close(storeChannel)
|
||||
}()
|
||||
|
||||
return storeChannel
|
||||
}
|
||||
|
||||
func (s SqlWebhookStore) GetIncomingByChannel(channelId string) store.StoreChannel {
|
||||
storeChannel := make(store.StoreChannel, 1)
|
||||
|
||||
go func() {
|
||||
result := store.StoreResult{}
|
||||
|
||||
var webhooks []*model.IncomingWebhook
|
||||
|
||||
if _, err := s.GetReplica().Select(&webhooks, "SELECT * FROM IncomingWebhooks WHERE ChannelId = :ChannelId AND DeleteAt = 0", map[string]interface{}{"ChannelId": channelId}); err != nil {
|
||||
result.Err = model.NewAppError("SqlWebhookStore.GetIncomingByChannel", "store.sql_webhooks.get_incoming_by_channel.app_error", nil, "channelId="+channelId+", err="+err.Error(), http.StatusInternalServerError)
|
||||
}
|
||||
|
||||
result.Data = webhooks
|
||||
|
||||
storeChannel <- result
|
||||
close(storeChannel)
|
||||
}()
|
||||
|
||||
return storeChannel
|
||||
}
|
||||
|
||||
func (s SqlWebhookStore) SaveOutgoing(webhook *model.OutgoingWebhook) store.StoreChannel {
|
||||
storeChannel := make(store.StoreChannel, 1)
|
||||
|
||||
go func() {
|
||||
result := store.StoreResult{}
|
||||
|
||||
if len(webhook.Id) > 0 {
|
||||
result.Err = model.NewAppError("SqlWebhookStore.SaveOutgoing", "store.sql_webhooks.save_outgoing.override.app_error", nil, "id="+webhook.Id, http.StatusBadRequest)
|
||||
storeChannel <- result
|
||||
close(storeChannel)
|
||||
return
|
||||
}
|
||||
|
||||
webhook.PreSave()
|
||||
if result.Err = webhook.IsValid(); result.Err != nil {
|
||||
storeChannel <- result
|
||||
close(storeChannel)
|
||||
return
|
||||
}
|
||||
|
||||
if err := s.GetMaster().Insert(webhook); err != nil {
|
||||
result.Err = model.NewAppError("SqlWebhookStore.SaveOutgoing", "store.sql_webhooks.save_outgoing.app_error", nil, "id="+webhook.Id+", "+err.Error(), http.StatusInternalServerError)
|
||||
} else {
|
||||
result.Data = webhook
|
||||
}
|
||||
|
||||
storeChannel <- result
|
||||
close(storeChannel)
|
||||
}()
|
||||
|
||||
return storeChannel
|
||||
}
|
||||
|
||||
func (s SqlWebhookStore) GetOutgoing(id string) store.StoreChannel {
|
||||
storeChannel := make(store.StoreChannel, 1)
|
||||
|
||||
go func() {
|
||||
result := store.StoreResult{}
|
||||
|
||||
var webhook model.OutgoingWebhook
|
||||
|
||||
if err := s.GetReplica().SelectOne(&webhook, "SELECT * FROM OutgoingWebhooks WHERE Id = :Id AND DeleteAt = 0", map[string]interface{}{"Id": id}); err != nil {
|
||||
result.Err = model.NewAppError("SqlWebhookStore.GetOutgoing", "store.sql_webhooks.get_outgoing.app_error", nil, "id="+id+", err="+err.Error(), http.StatusInternalServerError)
|
||||
}
|
||||
|
||||
result.Data = &webhook
|
||||
|
||||
storeChannel <- result
|
||||
close(storeChannel)
|
||||
}()
|
||||
|
||||
return storeChannel
|
||||
}
|
||||
|
||||
func (s SqlWebhookStore) GetOutgoingList(offset, limit int) store.StoreChannel {
|
||||
storeChannel := make(store.StoreChannel, 1)
|
||||
|
||||
go func() {
|
||||
result := store.StoreResult{}
|
||||
|
||||
var webhooks []*model.OutgoingWebhook
|
||||
|
||||
if _, err := s.GetReplica().Select(&webhooks, "SELECT * FROM OutgoingWebhooks WHERE DeleteAt = 0 LIMIT :Limit OFFSET :Offset", map[string]interface{}{"Offset": offset, "Limit": limit}); err != nil {
|
||||
result.Err = model.NewAppError("SqlWebhookStore.GetOutgoingList", "store.sql_webhooks.get_outgoing_by_channel.app_error", nil, "err="+err.Error(), http.StatusInternalServerError)
|
||||
}
|
||||
|
||||
result.Data = webhooks
|
||||
|
||||
storeChannel <- result
|
||||
close(storeChannel)
|
||||
}()
|
||||
|
||||
return storeChannel
|
||||
}
|
||||
|
||||
func (s SqlWebhookStore) GetOutgoingByChannel(channelId string, offset, limit int) store.StoreChannel {
|
||||
storeChannel := make(store.StoreChannel, 1)
|
||||
|
||||
go func() {
|
||||
result := store.StoreResult{}
|
||||
|
||||
var webhooks []*model.OutgoingWebhook
|
||||
|
||||
query := ""
|
||||
if limit < 0 || offset < 0 {
|
||||
query = "SELECT * FROM OutgoingWebhooks WHERE ChannelId = :ChannelId AND DeleteAt = 0"
|
||||
} else {
|
||||
query = "SELECT * FROM OutgoingWebhooks WHERE ChannelId = :ChannelId AND DeleteAt = 0 LIMIT :Limit OFFSET :Offset"
|
||||
}
|
||||
|
||||
if _, err := s.GetReplica().Select(&webhooks, query, map[string]interface{}{"ChannelId": channelId, "Offset": offset, "Limit": limit}); err != nil {
|
||||
result.Err = model.NewAppError("SqlWebhookStore.GetOutgoingByChannel", "store.sql_webhooks.get_outgoing_by_channel.app_error", nil, "channelId="+channelId+", err="+err.Error(), http.StatusInternalServerError)
|
||||
}
|
||||
|
||||
result.Data = webhooks
|
||||
|
||||
storeChannel <- result
|
||||
close(storeChannel)
|
||||
}()
|
||||
|
||||
return storeChannel
|
||||
}
|
||||
|
||||
func (s SqlWebhookStore) GetOutgoingByTeam(teamId string, offset, limit int) store.StoreChannel {
|
||||
storeChannel := make(store.StoreChannel, 1)
|
||||
|
||||
go func() {
|
||||
result := store.StoreResult{}
|
||||
|
||||
var webhooks []*model.OutgoingWebhook
|
||||
|
||||
query := ""
|
||||
if limit < 0 || offset < 0 {
|
||||
query = "SELECT * FROM OutgoingWebhooks WHERE TeamId = :TeamId AND DeleteAt = 0"
|
||||
} else {
|
||||
query = "SELECT * FROM OutgoingWebhooks WHERE TeamId = :TeamId AND DeleteAt = 0 LIMIT :Limit OFFSET :Offset"
|
||||
}
|
||||
|
||||
if _, err := s.GetReplica().Select(&webhooks, query, map[string]interface{}{"TeamId": teamId, "Offset": offset, "Limit": limit}); err != nil {
|
||||
result.Err = model.NewAppError("SqlWebhookStore.GetOutgoingByTeam", "store.sql_webhooks.get_outgoing_by_team.app_error", nil, "teamId="+teamId+", err="+err.Error(), http.StatusInternalServerError)
|
||||
}
|
||||
|
||||
result.Data = webhooks
|
||||
|
||||
storeChannel <- result
|
||||
close(storeChannel)
|
||||
}()
|
||||
|
||||
return storeChannel
|
||||
}
|
||||
|
||||
func (s SqlWebhookStore) DeleteOutgoing(webhookId string, time int64) store.StoreChannel {
|
||||
storeChannel := make(store.StoreChannel, 1)
|
||||
|
||||
go func() {
|
||||
result := store.StoreResult{}
|
||||
|
||||
_, err := s.GetMaster().Exec("Update OutgoingWebhooks SET DeleteAt = :DeleteAt, UpdateAt = :UpdateAt WHERE Id = :Id", map[string]interface{}{"DeleteAt": time, "UpdateAt": time, "Id": webhookId})
|
||||
if err != nil {
|
||||
result.Err = model.NewAppError("SqlWebhookStore.DeleteOutgoing", "store.sql_webhooks.delete_outgoing.app_error", nil, "id="+webhookId+", err="+err.Error(), http.StatusInternalServerError)
|
||||
}
|
||||
|
||||
storeChannel <- result
|
||||
close(storeChannel)
|
||||
}()
|
||||
|
||||
return storeChannel
|
||||
}
|
||||
|
||||
func (s SqlWebhookStore) PermanentDeleteOutgoingByUser(userId string) store.StoreChannel {
|
||||
storeChannel := make(store.StoreChannel, 1)
|
||||
|
||||
go func() {
|
||||
result := store.StoreResult{}
|
||||
|
||||
_, err := s.GetMaster().Exec("DELETE FROM OutgoingWebhooks WHERE CreatorId = :UserId", map[string]interface{}{"UserId": userId})
|
||||
if err != nil {
|
||||
result.Err = model.NewAppError("SqlWebhookStore.DeleteOutgoingByUser", "store.sql_webhooks.permanent_delete_outgoing_by_user.app_error", nil, "id="+userId+", err="+err.Error(), http.StatusInternalServerError)
|
||||
}
|
||||
|
||||
storeChannel <- result
|
||||
close(storeChannel)
|
||||
}()
|
||||
|
||||
return storeChannel
|
||||
}
|
||||
|
||||
func (s SqlWebhookStore) PermanentDeleteOutgoingByChannel(channelId string) store.StoreChannel {
|
||||
storeChannel := make(store.StoreChannel, 1)
|
||||
|
||||
go func() {
|
||||
result := store.StoreResult{}
|
||||
|
||||
_, err := s.GetMaster().Exec("DELETE FROM OutgoingWebhooks WHERE ChannelId = :ChannelId", map[string]interface{}{"ChannelId": channelId})
|
||||
if err != nil {
|
||||
result.Err = model.NewAppError("SqlWebhookStore.DeleteOutgoingByChannel", "store.sql_webhooks.permanent_delete_outgoing_by_channel.app_error", nil, "id="+channelId+", err="+err.Error(), http.StatusInternalServerError)
|
||||
}
|
||||
|
||||
ClearWebhookCaches()
|
||||
|
||||
storeChannel <- result
|
||||
close(storeChannel)
|
||||
}()
|
||||
|
||||
return storeChannel
|
||||
}
|
||||
|
||||
func (s SqlWebhookStore) UpdateOutgoing(hook *model.OutgoingWebhook) store.StoreChannel {
|
||||
storeChannel := make(store.StoreChannel, 1)
|
||||
|
||||
go func() {
|
||||
result := store.StoreResult{}
|
||||
|
||||
hook.UpdateAt = model.GetMillis()
|
||||
|
||||
if _, err := s.GetMaster().Update(hook); err != nil {
|
||||
result.Err = model.NewAppError("SqlWebhookStore.UpdateOutgoing", "store.sql_webhooks.update_outgoing.app_error", nil, "id="+hook.Id+", "+err.Error(), http.StatusInternalServerError)
|
||||
} else {
|
||||
result.Data = hook
|
||||
}
|
||||
|
||||
storeChannel <- result
|
||||
close(storeChannel)
|
||||
}()
|
||||
|
||||
return storeChannel
|
||||
}
|
||||
|
||||
func (s SqlWebhookStore) AnalyticsIncomingCount(teamId string) store.StoreChannel {
|
||||
storeChannel := make(store.StoreChannel, 1)
|
||||
|
||||
go func() {
|
||||
result := store.StoreResult{}
|
||||
|
||||
query :=
|
||||
`SELECT
|
||||
COUNT(*)
|
||||
FROM
|
||||
IncomingWebhooks
|
||||
WHERE
|
||||
DeleteAt = 0`
|
||||
|
||||
if len(teamId) > 0 {
|
||||
query += " AND TeamId = :TeamId"
|
||||
}
|
||||
|
||||
if v, err := s.GetReplica().SelectInt(query, map[string]interface{}{"TeamId": teamId}); err != nil {
|
||||
result.Err = model.NewAppError("SqlWebhookStore.AnalyticsIncomingCount", "store.sql_webhooks.analytics_incoming_count.app_error", nil, "team_id="+teamId+", err="+err.Error(), http.StatusInternalServerError)
|
||||
} else {
|
||||
result.Data = v
|
||||
}
|
||||
|
||||
storeChannel <- result
|
||||
close(storeChannel)
|
||||
}()
|
||||
|
||||
return storeChannel
|
||||
}
|
||||
|
||||
func (s SqlWebhookStore) AnalyticsOutgoingCount(teamId string) store.StoreChannel {
|
||||
storeChannel := make(store.StoreChannel, 1)
|
||||
|
||||
go func() {
|
||||
result := store.StoreResult{}
|
||||
|
||||
query :=
|
||||
`SELECT
|
||||
COUNT(*)
|
||||
FROM
|
||||
OutgoingWebhooks
|
||||
WHERE
|
||||
DeleteAt = 0`
|
||||
|
||||
if len(teamId) > 0 {
|
||||
query += " AND TeamId = :TeamId"
|
||||
}
|
||||
|
||||
if v, err := s.GetReplica().SelectInt(query, map[string]interface{}{"TeamId": teamId}); err != nil {
|
||||
result.Err = model.NewAppError("SqlWebhookStore.AnalyticsOutgoingCount", "store.sql_webhooks.analytics_outgoing_count.app_error", nil, "team_id="+teamId+", err="+err.Error(), http.StatusInternalServerError)
|
||||
} else {
|
||||
result.Data = v
|
||||
}
|
||||
|
||||
storeChannel <- result
|
||||
close(storeChannel)
|
||||
}()
|
||||
|
||||
return storeChannel
|
||||
}
|
||||
516
store/sqlstore/webhook_store_test.go
Обычный файл
516
store/sqlstore/webhook_store_test.go
Обычный файл
@@ -0,0 +1,516 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See License.txt for license information.
|
||||
|
||||
package sqlstore
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"net/http"
|
||||
|
||||
"github.com/mattermost/mattermost-server/model"
|
||||
)
|
||||
|
||||
func TestWebhookStoreSaveIncoming(t *testing.T) {
|
||||
ss := Setup()
|
||||
o1 := buildIncomingWebhook()
|
||||
|
||||
if err := (<-ss.Webhook().SaveIncoming(o1)).Err; err != nil {
|
||||
t.Fatal("couldn't save item", err)
|
||||
}
|
||||
|
||||
if err := (<-ss.Webhook().SaveIncoming(o1)).Err; err == nil {
|
||||
t.Fatal("shouldn't be able to update from save")
|
||||
}
|
||||
}
|
||||
|
||||
func TestWebhookStoreUpdateIncoming(t *testing.T) {
|
||||
ss := Setup()
|
||||
o1 := buildIncomingWebhook()
|
||||
o1 = (<-ss.Webhook().SaveIncoming(o1)).Data.(*model.IncomingWebhook)
|
||||
previousUpdatedAt := o1.UpdateAt
|
||||
|
||||
o1.DisplayName = "TestHook"
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
|
||||
if result := (<-ss.Webhook().UpdateIncoming(o1)); result.Err != nil {
|
||||
t.Fatal("updation of incoming hook failed", result.Err)
|
||||
} else {
|
||||
if result.Data.(*model.IncomingWebhook).UpdateAt == previousUpdatedAt {
|
||||
t.Fatal("should have updated the UpdatedAt of the hook")
|
||||
}
|
||||
|
||||
if result.Data.(*model.IncomingWebhook).DisplayName != "TestHook" {
|
||||
t.Fatal("display name is not updated")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestWebhookStoreGetIncoming(t *testing.T) {
|
||||
ss := Setup()
|
||||
|
||||
o1 := buildIncomingWebhook()
|
||||
o1 = (<-ss.Webhook().SaveIncoming(o1)).Data.(*model.IncomingWebhook)
|
||||
|
||||
if r1 := <-ss.Webhook().GetIncoming(o1.Id, false); r1.Err != nil {
|
||||
t.Fatal(r1.Err)
|
||||
} else {
|
||||
if r1.Data.(*model.IncomingWebhook).CreateAt != o1.CreateAt {
|
||||
t.Fatal("invalid returned webhook")
|
||||
}
|
||||
}
|
||||
|
||||
if r1 := <-ss.Webhook().GetIncoming(o1.Id, true); r1.Err != nil {
|
||||
t.Fatal(r1.Err)
|
||||
} else {
|
||||
if r1.Data.(*model.IncomingWebhook).CreateAt != o1.CreateAt {
|
||||
t.Fatal("invalid returned webhook")
|
||||
}
|
||||
}
|
||||
|
||||
if err := (<-ss.Webhook().GetIncoming("123", false)).Err; err == nil {
|
||||
t.Fatal("Missing id should have failed")
|
||||
}
|
||||
|
||||
if err := (<-ss.Webhook().GetIncoming("123", true)).Err; err == nil {
|
||||
t.Fatal("Missing id should have failed")
|
||||
}
|
||||
|
||||
if err := (<-ss.Webhook().GetIncoming("123", true)).Err; err.StatusCode != http.StatusNotFound {
|
||||
t.Fatal("Should have set the status as not found for missing id")
|
||||
}
|
||||
}
|
||||
|
||||
func TestWebhookStoreGetIncomingList(t *testing.T) {
|
||||
ss := Setup()
|
||||
|
||||
o1 := &model.IncomingWebhook{}
|
||||
o1.ChannelId = model.NewId()
|
||||
o1.UserId = model.NewId()
|
||||
o1.TeamId = model.NewId()
|
||||
|
||||
o1 = (<-ss.Webhook().SaveIncoming(o1)).Data.(*model.IncomingWebhook)
|
||||
|
||||
if r1 := <-ss.Webhook().GetIncomingList(0, 1000); r1.Err != nil {
|
||||
t.Fatal(r1.Err)
|
||||
} else {
|
||||
found := false
|
||||
hooks := r1.Data.([]*model.IncomingWebhook)
|
||||
for _, hook := range hooks {
|
||||
if hook.Id == o1.Id {
|
||||
found = true
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
t.Fatal("missing webhook")
|
||||
}
|
||||
}
|
||||
|
||||
if result := <-ss.Webhook().GetIncomingList(0, 1); result.Err != nil {
|
||||
t.Fatal(result.Err)
|
||||
} else {
|
||||
if len(result.Data.([]*model.IncomingWebhook)) != 1 {
|
||||
t.Fatal("only 1 should be returned")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestWebhookStoreGetIncomingByTeam(t *testing.T) {
|
||||
ss := Setup()
|
||||
o1 := buildIncomingWebhook()
|
||||
|
||||
o1 = (<-ss.Webhook().SaveIncoming(o1)).Data.(*model.IncomingWebhook)
|
||||
|
||||
if r1 := <-ss.Webhook().GetIncomingByTeam(o1.TeamId, 0, 100); r1.Err != nil {
|
||||
t.Fatal(r1.Err)
|
||||
} else {
|
||||
if r1.Data.([]*model.IncomingWebhook)[0].CreateAt != o1.CreateAt {
|
||||
t.Fatal("invalid returned webhook")
|
||||
}
|
||||
}
|
||||
|
||||
if result := <-ss.Webhook().GetIncomingByTeam("123", 0, 100); result.Err != nil {
|
||||
t.Fatal(result.Err)
|
||||
} else {
|
||||
if len(result.Data.([]*model.IncomingWebhook)) != 0 {
|
||||
t.Fatal("no webhooks should have returned")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestWebhookStoreDeleteIncoming(t *testing.T) {
|
||||
ss := Setup()
|
||||
o1 := buildIncomingWebhook()
|
||||
|
||||
o1 = (<-ss.Webhook().SaveIncoming(o1)).Data.(*model.IncomingWebhook)
|
||||
|
||||
if r1 := <-ss.Webhook().GetIncoming(o1.Id, true); r1.Err != nil {
|
||||
t.Fatal(r1.Err)
|
||||
} else {
|
||||
if r1.Data.(*model.IncomingWebhook).CreateAt != o1.CreateAt {
|
||||
t.Fatal("invalid returned webhook")
|
||||
}
|
||||
}
|
||||
|
||||
if r2 := <-ss.Webhook().DeleteIncoming(o1.Id, model.GetMillis()); r2.Err != nil {
|
||||
t.Fatal(r2.Err)
|
||||
}
|
||||
|
||||
if r3 := (<-ss.Webhook().GetIncoming(o1.Id, true)); r3.Err == nil {
|
||||
t.Log(r3.Data)
|
||||
t.Fatal("Missing id should have failed")
|
||||
}
|
||||
}
|
||||
|
||||
func TestWebhookStoreDeleteIncomingByChannel(t *testing.T) {
|
||||
ss := Setup()
|
||||
o1 := buildIncomingWebhook()
|
||||
|
||||
o1 = (<-ss.Webhook().SaveIncoming(o1)).Data.(*model.IncomingWebhook)
|
||||
|
||||
if r1 := <-ss.Webhook().GetIncoming(o1.Id, true); r1.Err != nil {
|
||||
t.Fatal(r1.Err)
|
||||
} else {
|
||||
if r1.Data.(*model.IncomingWebhook).CreateAt != o1.CreateAt {
|
||||
t.Fatal("invalid returned webhook")
|
||||
}
|
||||
}
|
||||
|
||||
if r2 := <-ss.Webhook().PermanentDeleteIncomingByChannel(o1.ChannelId); r2.Err != nil {
|
||||
t.Fatal(r2.Err)
|
||||
}
|
||||
|
||||
if r3 := (<-ss.Webhook().GetIncoming(o1.Id, true)); r3.Err == nil {
|
||||
t.Log(r3.Data)
|
||||
t.Fatal("Missing id should have failed")
|
||||
}
|
||||
}
|
||||
|
||||
func TestWebhookStoreDeleteIncomingByUser(t *testing.T) {
|
||||
ss := Setup()
|
||||
o1 := buildIncomingWebhook()
|
||||
|
||||
o1 = (<-ss.Webhook().SaveIncoming(o1)).Data.(*model.IncomingWebhook)
|
||||
|
||||
if r1 := <-ss.Webhook().GetIncoming(o1.Id, true); r1.Err != nil {
|
||||
t.Fatal(r1.Err)
|
||||
} else {
|
||||
if r1.Data.(*model.IncomingWebhook).CreateAt != o1.CreateAt {
|
||||
t.Fatal("invalid returned webhook")
|
||||
}
|
||||
}
|
||||
|
||||
if r2 := <-ss.Webhook().PermanentDeleteIncomingByUser(o1.UserId); r2.Err != nil {
|
||||
t.Fatal(r2.Err)
|
||||
}
|
||||
|
||||
if r3 := (<-ss.Webhook().GetIncoming(o1.Id, true)); r3.Err == nil {
|
||||
t.Log(r3.Data)
|
||||
t.Fatal("Missing id should have failed")
|
||||
}
|
||||
}
|
||||
|
||||
func buildIncomingWebhook() *model.IncomingWebhook {
|
||||
o1 := &model.IncomingWebhook{}
|
||||
o1.ChannelId = model.NewId()
|
||||
o1.UserId = model.NewId()
|
||||
o1.TeamId = model.NewId()
|
||||
|
||||
return o1
|
||||
}
|
||||
|
||||
func TestWebhookStoreSaveOutgoing(t *testing.T) {
|
||||
ss := Setup()
|
||||
|
||||
o1 := model.OutgoingWebhook{}
|
||||
o1.ChannelId = model.NewId()
|
||||
o1.CreatorId = model.NewId()
|
||||
o1.TeamId = model.NewId()
|
||||
o1.CallbackURLs = []string{"http://nowhere.com/"}
|
||||
|
||||
if err := (<-ss.Webhook().SaveOutgoing(&o1)).Err; err != nil {
|
||||
t.Fatal("couldn't save item", err)
|
||||
}
|
||||
|
||||
if err := (<-ss.Webhook().SaveOutgoing(&o1)).Err; err == nil {
|
||||
t.Fatal("shouldn't be able to update from save")
|
||||
}
|
||||
}
|
||||
|
||||
func TestWebhookStoreGetOutgoing(t *testing.T) {
|
||||
ss := Setup()
|
||||
|
||||
o1 := &model.OutgoingWebhook{}
|
||||
o1.ChannelId = model.NewId()
|
||||
o1.CreatorId = model.NewId()
|
||||
o1.TeamId = model.NewId()
|
||||
o1.CallbackURLs = []string{"http://nowhere.com/"}
|
||||
|
||||
o1 = (<-ss.Webhook().SaveOutgoing(o1)).Data.(*model.OutgoingWebhook)
|
||||
|
||||
if r1 := <-ss.Webhook().GetOutgoing(o1.Id); r1.Err != nil {
|
||||
t.Fatal(r1.Err)
|
||||
} else {
|
||||
if r1.Data.(*model.OutgoingWebhook).CreateAt != o1.CreateAt {
|
||||
t.Fatal("invalid returned webhook")
|
||||
}
|
||||
}
|
||||
|
||||
if err := (<-ss.Webhook().GetOutgoing("123")).Err; err == nil {
|
||||
t.Fatal("Missing id should have failed")
|
||||
}
|
||||
}
|
||||
|
||||
func TestWebhookStoreGetOutgoingList(t *testing.T) {
|
||||
ss := Setup()
|
||||
|
||||
o1 := &model.OutgoingWebhook{}
|
||||
o1.ChannelId = model.NewId()
|
||||
o1.CreatorId = model.NewId()
|
||||
o1.TeamId = model.NewId()
|
||||
o1.CallbackURLs = []string{"http://nowhere.com/"}
|
||||
|
||||
o1 = (<-ss.Webhook().SaveOutgoing(o1)).Data.(*model.OutgoingWebhook)
|
||||
|
||||
o2 := &model.OutgoingWebhook{}
|
||||
o2.ChannelId = model.NewId()
|
||||
o2.CreatorId = model.NewId()
|
||||
o2.TeamId = model.NewId()
|
||||
o2.CallbackURLs = []string{"http://nowhere.com/"}
|
||||
|
||||
o2 = (<-ss.Webhook().SaveOutgoing(o2)).Data.(*model.OutgoingWebhook)
|
||||
|
||||
if r1 := <-ss.Webhook().GetOutgoingList(0, 1000); r1.Err != nil {
|
||||
t.Fatal(r1.Err)
|
||||
} else {
|
||||
hooks := r1.Data.([]*model.OutgoingWebhook)
|
||||
found1 := false
|
||||
found2 := false
|
||||
|
||||
for _, hook := range hooks {
|
||||
if hook.CreateAt != o1.CreateAt {
|
||||
found1 = true
|
||||
}
|
||||
|
||||
if hook.CreateAt != o2.CreateAt {
|
||||
found2 = true
|
||||
}
|
||||
}
|
||||
|
||||
if !found1 {
|
||||
t.Fatal("missing hook1")
|
||||
}
|
||||
if !found2 {
|
||||
t.Fatal("missing hook2")
|
||||
}
|
||||
}
|
||||
|
||||
if result := <-ss.Webhook().GetOutgoingList(0, 2); result.Err != nil {
|
||||
t.Fatal(result.Err)
|
||||
} else {
|
||||
if len(result.Data.([]*model.OutgoingWebhook)) != 2 {
|
||||
t.Fatal("wrong number of hooks returned")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestWebhookStoreGetOutgoingByChannel(t *testing.T) {
|
||||
ss := Setup()
|
||||
|
||||
o1 := &model.OutgoingWebhook{}
|
||||
o1.ChannelId = model.NewId()
|
||||
o1.CreatorId = model.NewId()
|
||||
o1.TeamId = model.NewId()
|
||||
o1.CallbackURLs = []string{"http://nowhere.com/"}
|
||||
|
||||
o1 = (<-ss.Webhook().SaveOutgoing(o1)).Data.(*model.OutgoingWebhook)
|
||||
|
||||
if r1 := <-ss.Webhook().GetOutgoingByChannel(o1.ChannelId, 0, 100); r1.Err != nil {
|
||||
t.Fatal(r1.Err)
|
||||
} else {
|
||||
if r1.Data.([]*model.OutgoingWebhook)[0].CreateAt != o1.CreateAt {
|
||||
t.Fatal("invalid returned webhook")
|
||||
}
|
||||
}
|
||||
|
||||
if result := <-ss.Webhook().GetOutgoingByChannel("123", -1, -1); result.Err != nil {
|
||||
t.Fatal(result.Err)
|
||||
} else {
|
||||
if len(result.Data.([]*model.OutgoingWebhook)) != 0 {
|
||||
t.Fatal("no webhooks should have returned")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestWebhookStoreGetOutgoingByTeam(t *testing.T) {
|
||||
ss := Setup()
|
||||
|
||||
o1 := &model.OutgoingWebhook{}
|
||||
o1.ChannelId = model.NewId()
|
||||
o1.CreatorId = model.NewId()
|
||||
o1.TeamId = model.NewId()
|
||||
o1.CallbackURLs = []string{"http://nowhere.com/"}
|
||||
|
||||
o1 = (<-ss.Webhook().SaveOutgoing(o1)).Data.(*model.OutgoingWebhook)
|
||||
|
||||
if r1 := <-ss.Webhook().GetOutgoingByTeam(o1.TeamId, 0, 100); r1.Err != nil {
|
||||
t.Fatal(r1.Err)
|
||||
} else {
|
||||
if r1.Data.([]*model.OutgoingWebhook)[0].CreateAt != o1.CreateAt {
|
||||
t.Fatal("invalid returned webhook")
|
||||
}
|
||||
}
|
||||
|
||||
if result := <-ss.Webhook().GetOutgoingByTeam("123", -1, -1); result.Err != nil {
|
||||
t.Fatal(result.Err)
|
||||
} else {
|
||||
if len(result.Data.([]*model.OutgoingWebhook)) != 0 {
|
||||
t.Fatal("no webhooks should have returned")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestWebhookStoreDeleteOutgoing(t *testing.T) {
|
||||
ss := Setup()
|
||||
|
||||
o1 := &model.OutgoingWebhook{}
|
||||
o1.ChannelId = model.NewId()
|
||||
o1.CreatorId = model.NewId()
|
||||
o1.TeamId = model.NewId()
|
||||
o1.CallbackURLs = []string{"http://nowhere.com/"}
|
||||
|
||||
o1 = (<-ss.Webhook().SaveOutgoing(o1)).Data.(*model.OutgoingWebhook)
|
||||
|
||||
if r1 := <-ss.Webhook().GetOutgoing(o1.Id); r1.Err != nil {
|
||||
t.Fatal(r1.Err)
|
||||
} else {
|
||||
if r1.Data.(*model.OutgoingWebhook).CreateAt != o1.CreateAt {
|
||||
t.Fatal("invalid returned webhook")
|
||||
}
|
||||
}
|
||||
|
||||
if r2 := <-ss.Webhook().DeleteOutgoing(o1.Id, model.GetMillis()); r2.Err != nil {
|
||||
t.Fatal(r2.Err)
|
||||
}
|
||||
|
||||
if r3 := (<-ss.Webhook().GetOutgoing(o1.Id)); r3.Err == nil {
|
||||
t.Log(r3.Data)
|
||||
t.Fatal("Missing id should have failed")
|
||||
}
|
||||
}
|
||||
|
||||
func TestWebhookStoreDeleteOutgoingByChannel(t *testing.T) {
|
||||
ss := Setup()
|
||||
|
||||
o1 := &model.OutgoingWebhook{}
|
||||
o1.ChannelId = model.NewId()
|
||||
o1.CreatorId = model.NewId()
|
||||
o1.TeamId = model.NewId()
|
||||
o1.CallbackURLs = []string{"http://nowhere.com/"}
|
||||
|
||||
o1 = (<-ss.Webhook().SaveOutgoing(o1)).Data.(*model.OutgoingWebhook)
|
||||
|
||||
if r1 := <-ss.Webhook().GetOutgoing(o1.Id); r1.Err != nil {
|
||||
t.Fatal(r1.Err)
|
||||
} else {
|
||||
if r1.Data.(*model.OutgoingWebhook).CreateAt != o1.CreateAt {
|
||||
t.Fatal("invalid returned webhook")
|
||||
}
|
||||
}
|
||||
|
||||
if r2 := <-ss.Webhook().PermanentDeleteOutgoingByChannel(o1.ChannelId); r2.Err != nil {
|
||||
t.Fatal(r2.Err)
|
||||
}
|
||||
|
||||
if r3 := (<-ss.Webhook().GetOutgoing(o1.Id)); r3.Err == nil {
|
||||
t.Log(r3.Data)
|
||||
t.Fatal("Missing id should have failed")
|
||||
}
|
||||
}
|
||||
|
||||
func TestWebhookStoreDeleteOutgoingByUser(t *testing.T) {
|
||||
ss := Setup()
|
||||
|
||||
o1 := &model.OutgoingWebhook{}
|
||||
o1.ChannelId = model.NewId()
|
||||
o1.CreatorId = model.NewId()
|
||||
o1.TeamId = model.NewId()
|
||||
o1.CallbackURLs = []string{"http://nowhere.com/"}
|
||||
|
||||
o1 = (<-ss.Webhook().SaveOutgoing(o1)).Data.(*model.OutgoingWebhook)
|
||||
|
||||
if r1 := <-ss.Webhook().GetOutgoing(o1.Id); r1.Err != nil {
|
||||
t.Fatal(r1.Err)
|
||||
} else {
|
||||
if r1.Data.(*model.OutgoingWebhook).CreateAt != o1.CreateAt {
|
||||
t.Fatal("invalid returned webhook")
|
||||
}
|
||||
}
|
||||
|
||||
if r2 := <-ss.Webhook().PermanentDeleteOutgoingByUser(o1.CreatorId); r2.Err != nil {
|
||||
t.Fatal(r2.Err)
|
||||
}
|
||||
|
||||
if r3 := (<-ss.Webhook().GetOutgoing(o1.Id)); r3.Err == nil {
|
||||
t.Log(r3.Data)
|
||||
t.Fatal("Missing id should have failed")
|
||||
}
|
||||
}
|
||||
|
||||
func TestWebhookStoreUpdateOutgoing(t *testing.T) {
|
||||
ss := Setup()
|
||||
|
||||
o1 := &model.OutgoingWebhook{}
|
||||
o1.ChannelId = model.NewId()
|
||||
o1.CreatorId = model.NewId()
|
||||
o1.TeamId = model.NewId()
|
||||
o1.CallbackURLs = []string{"http://nowhere.com/"}
|
||||
|
||||
o1 = (<-ss.Webhook().SaveOutgoing(o1)).Data.(*model.OutgoingWebhook)
|
||||
|
||||
o1.Token = model.NewId()
|
||||
|
||||
if r2 := <-ss.Webhook().UpdateOutgoing(o1); r2.Err != nil {
|
||||
t.Fatal(r2.Err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestWebhookStoreCountIncoming(t *testing.T) {
|
||||
ss := Setup()
|
||||
|
||||
o1 := &model.IncomingWebhook{}
|
||||
o1.ChannelId = model.NewId()
|
||||
o1.UserId = model.NewId()
|
||||
o1.TeamId = model.NewId()
|
||||
|
||||
o1 = (<-ss.Webhook().SaveIncoming(o1)).Data.(*model.IncomingWebhook)
|
||||
|
||||
if r := <-ss.Webhook().AnalyticsIncomingCount(""); r.Err != nil {
|
||||
t.Fatal(r.Err)
|
||||
} else {
|
||||
if r.Data.(int64) == 0 {
|
||||
t.Fatal("should have at least 1 incoming hook")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestWebhookStoreCountOutgoing(t *testing.T) {
|
||||
ss := Setup()
|
||||
|
||||
o1 := &model.OutgoingWebhook{}
|
||||
o1.ChannelId = model.NewId()
|
||||
o1.CreatorId = model.NewId()
|
||||
o1.TeamId = model.NewId()
|
||||
o1.CallbackURLs = []string{"http://nowhere.com/"}
|
||||
|
||||
o1 = (<-ss.Webhook().SaveOutgoing(o1)).Data.(*model.OutgoingWebhook)
|
||||
|
||||
if r := <-ss.Webhook().AnalyticsOutgoingCount(""); r.Err != nil {
|
||||
t.Fatal(r.Err)
|
||||
} else {
|
||||
if r.Data.(int64) == 0 {
|
||||
t.Fatal("should have at least 1 outgoing hook")
|
||||
}
|
||||
}
|
||||
}
|
||||
Ссылка в новой задаче
Block a user