Implement incoming webhooks.
Этот коммит содержится в:
@@ -44,6 +44,7 @@ func InitApi() {
|
||||
InitCommand(r)
|
||||
InitAdmin(r)
|
||||
InitOAuth(r)
|
||||
InitWebhook(r)
|
||||
|
||||
templatesDir := utils.FindDir("api/templates")
|
||||
l4g.Debug("Parsing server templates at %v", templatesDir)
|
||||
|
||||
@@ -121,11 +121,7 @@ func CreateDirectChannel(c *Context, otherUserId string) (*model.Channel, *model
|
||||
channel := new(model.Channel)
|
||||
|
||||
channel.DisplayName = ""
|
||||
if otherUserId > c.Session.UserId {
|
||||
channel.Name = c.Session.UserId + "__" + otherUserId
|
||||
} else {
|
||||
channel.Name = otherUserId + "__" + c.Session.UserId
|
||||
}
|
||||
channel.Name = model.GetDMNameFromIds(otherUserId, c.Session.UserId)
|
||||
|
||||
channel.TeamId = c.Session.TeamId
|
||||
channel.Description = ""
|
||||
|
||||
@@ -116,12 +116,3 @@ func TestSocket(t *testing.T) {
|
||||
|
||||
time.Sleep(2 * time.Second)
|
||||
}
|
||||
|
||||
func TestZZWebSocketTearDown(t *testing.T) {
|
||||
// *IMPORTANT* - Kind of hacky
|
||||
// This should be the last function in any test file
|
||||
// that calls Setup()
|
||||
// Should be in the last file too sorted by name
|
||||
time.Sleep(2 * time.Second)
|
||||
TearDown()
|
||||
}
|
||||
|
||||
119
api/webhook.go
Обычный файл
119
api/webhook.go
Обычный файл
@@ -0,0 +1,119 @@
|
||||
// Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved.
|
||||
// See License.txt for license information.
|
||||
|
||||
package api
|
||||
|
||||
import (
|
||||
l4g "code.google.com/p/log4go"
|
||||
"github.com/gorilla/mux"
|
||||
"github.com/mattermost/platform/model"
|
||||
"github.com/mattermost/platform/utils"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
func InitWebhook(r *mux.Router) {
|
||||
l4g.Debug("Initializing webhook api routes")
|
||||
|
||||
sr := r.PathPrefix("/hooks").Subrouter()
|
||||
sr.Handle("/incoming/create", ApiUserRequired(createIncomingHook)).Methods("POST")
|
||||
sr.Handle("/incoming/delete", ApiUserRequired(deleteIncomingHook)).Methods("POST")
|
||||
sr.Handle("/incoming/list", ApiUserRequired(getIncomingHooks)).Methods("GET")
|
||||
}
|
||||
|
||||
func createIncomingHook(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
if !utils.Cfg.ServiceSettings.AllowIncomingWebhooks {
|
||||
c.Err = model.NewAppError("createIncomingHook", "Incoming webhooks have been disabled by the system admin.", "")
|
||||
c.Err.StatusCode = http.StatusNotImplemented
|
||||
return
|
||||
}
|
||||
|
||||
c.LogAudit("attempt")
|
||||
|
||||
hook := model.IncomingWebhookFromJson(r.Body)
|
||||
|
||||
if hook == nil {
|
||||
c.SetInvalidParam("createIncomingHook", "webhook")
|
||||
return
|
||||
}
|
||||
|
||||
cchan := Srv.Store.Channel().Get(hook.ChannelId)
|
||||
pchan := Srv.Store.Channel().CheckPermissionsTo(c.Session.TeamId, hook.ChannelId, c.Session.UserId)
|
||||
|
||||
hook.UserId = c.Session.UserId
|
||||
hook.TeamId = c.Session.TeamId
|
||||
|
||||
var channel *model.Channel
|
||||
if result := <-cchan; result.Err != nil {
|
||||
c.Err = result.Err
|
||||
return
|
||||
} else {
|
||||
channel = result.Data.(*model.Channel)
|
||||
}
|
||||
|
||||
if !c.HasPermissionsToChannel(pchan, "createIncomingHook") && channel.Type != model.CHANNEL_OPEN {
|
||||
c.LogAudit("fail - bad channel permissions")
|
||||
return
|
||||
}
|
||||
|
||||
if result := <-Srv.Store.Webhook().SaveIncoming(hook); result.Err != nil {
|
||||
c.Err = result.Err
|
||||
return
|
||||
} else {
|
||||
c.LogAudit("success")
|
||||
rhook := result.Data.(*model.IncomingWebhook)
|
||||
w.Write([]byte(rhook.ToJson()))
|
||||
}
|
||||
}
|
||||
|
||||
func deleteIncomingHook(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
if !utils.Cfg.ServiceSettings.AllowIncomingWebhooks {
|
||||
c.Err = model.NewAppError("createIncomingHook", "Incoming webhooks have been disabled by the system admin.", "")
|
||||
c.Err.StatusCode = http.StatusNotImplemented
|
||||
return
|
||||
}
|
||||
|
||||
c.LogAudit("attempt")
|
||||
|
||||
props := model.MapFromJson(r.Body)
|
||||
|
||||
id := props["id"]
|
||||
if len(id) == 0 {
|
||||
c.SetInvalidParam("deleteIncomingHook", "id")
|
||||
return
|
||||
}
|
||||
|
||||
if result := <-Srv.Store.Webhook().GetIncoming(id); result.Err != nil {
|
||||
c.Err = result.Err
|
||||
return
|
||||
} else {
|
||||
if c.Session.UserId != result.Data.(*model.IncomingWebhook).UserId && !model.IsInRole(c.Session.Roles, model.ROLE_TEAM_ADMIN) {
|
||||
c.LogAudit("fail - inappropriate conditions")
|
||||
c.Err = model.NewAppError("deleteIncomingHook", "Inappropriate permissions to delete incoming webhook", "user_id="+c.Session.UserId)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if err := (<-Srv.Store.Webhook().DeleteIncoming(id, model.GetMillis())).Err; err != nil {
|
||||
c.Err = err
|
||||
return
|
||||
}
|
||||
|
||||
c.LogAudit("success")
|
||||
w.Write([]byte(model.MapToJson(props)))
|
||||
}
|
||||
|
||||
func getIncomingHooks(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
if !utils.Cfg.ServiceSettings.AllowIncomingWebhooks {
|
||||
c.Err = model.NewAppError("createIncomingHook", "Incoming webhooks have been disabled by the system admin.", "")
|
||||
c.Err.StatusCode = http.StatusNotImplemented
|
||||
return
|
||||
}
|
||||
|
||||
if result := <-Srv.Store.Webhook().GetIncomingByUser(c.Session.UserId); result.Err != nil {
|
||||
c.Err = result.Err
|
||||
return
|
||||
} else {
|
||||
hooks := result.Data.([]*model.IncomingWebhook)
|
||||
w.Write([]byte(model.IncomingWebhookListToJson(hooks)))
|
||||
}
|
||||
}
|
||||
162
api/webhook_test.go
Обычный файл
162
api/webhook_test.go
Обычный файл
@@ -0,0 +1,162 @@
|
||||
// Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved.
|
||||
// See License.txt for license information.
|
||||
|
||||
package api
|
||||
|
||||
import (
|
||||
"github.com/mattermost/platform/model"
|
||||
"github.com/mattermost/platform/store"
|
||||
"github.com/mattermost/platform/utils"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestCreateIncomingHook(t *testing.T) {
|
||||
Setup()
|
||||
|
||||
team := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN}
|
||||
team = Client.Must(Client.CreateTeam(team)).Data.(*model.Team)
|
||||
|
||||
user := &model.User{TeamId: team.Id, Email: model.NewId() + "corey@test.com", Nickname: "Corey Hulen", Password: "pwd"}
|
||||
user = Client.Must(Client.CreateUser(user, "")).Data.(*model.User)
|
||||
store.Must(Srv.Store.User().VerifyEmail(user.Id))
|
||||
|
||||
Client.LoginByEmail(team.Name, user.Email, "pwd")
|
||||
|
||||
channel1 := &model.Channel{DisplayName: "Test API Name", Name: "a" + model.NewId() + "a", Type: model.CHANNEL_OPEN, TeamId: team.Id}
|
||||
channel1 = Client.Must(Client.CreateChannel(channel1)).Data.(*model.Channel)
|
||||
|
||||
channel2 := &model.Channel{DisplayName: "Test API Name", Name: "a" + model.NewId() + "a", Type: model.CHANNEL_OPEN, TeamId: team.Id}
|
||||
channel2 = Client.Must(Client.CreateChannel(channel2)).Data.(*model.Channel)
|
||||
|
||||
hook := &model.IncomingWebhook{ChannelId: channel1.Id}
|
||||
|
||||
if utils.Cfg.ServiceSettings.AllowIncomingWebhooks {
|
||||
var rhook *model.IncomingWebhook
|
||||
if result, err := Client.CreateIncomingWebhook(hook); err != nil {
|
||||
t.Fatal(err)
|
||||
} else {
|
||||
rhook = result.Data.(*model.IncomingWebhook)
|
||||
}
|
||||
|
||||
if hook.ChannelId != rhook.ChannelId {
|
||||
t.Fatal("channel ids didn't match")
|
||||
}
|
||||
|
||||
if rhook.UserId != user.Id {
|
||||
t.Fatal("user ids didn't match")
|
||||
}
|
||||
|
||||
if rhook.TeamId != team.Id {
|
||||
t.Fatal("team ids didn't match")
|
||||
}
|
||||
|
||||
hook = &model.IncomingWebhook{ChannelId: "junk"}
|
||||
if _, err := Client.CreateIncomingWebhook(hook); err == nil {
|
||||
t.Fatal("should have failed - bad channel id")
|
||||
}
|
||||
|
||||
hook = &model.IncomingWebhook{ChannelId: channel2.Id, UserId: "123", TeamId: "456"}
|
||||
if result, err := Client.CreateIncomingWebhook(hook); err != nil {
|
||||
t.Fatal(err)
|
||||
} else {
|
||||
if result.Data.(*model.IncomingWebhook).UserId != user.Id {
|
||||
t.Fatal("bad user id wasn't overwritten")
|
||||
}
|
||||
if result.Data.(*model.IncomingWebhook).TeamId != team.Id {
|
||||
t.Fatal("bad team id wasn't overwritten")
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if _, err := Client.CreateIncomingWebhook(hook); err == nil {
|
||||
t.Fatal("should have errored - webhooks turned off")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestListIncomingHooks(t *testing.T) {
|
||||
Setup()
|
||||
|
||||
team := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN}
|
||||
team = Client.Must(Client.CreateTeam(team)).Data.(*model.Team)
|
||||
|
||||
user := &model.User{TeamId: team.Id, Email: model.NewId() + "corey@test.com", Nickname: "Corey Hulen", Password: "pwd"}
|
||||
user = Client.Must(Client.CreateUser(user, "")).Data.(*model.User)
|
||||
store.Must(Srv.Store.User().VerifyEmail(user.Id))
|
||||
|
||||
Client.LoginByEmail(team.Name, user.Email, "pwd")
|
||||
|
||||
channel1 := &model.Channel{DisplayName: "Test API Name", Name: "a" + model.NewId() + "a", Type: model.CHANNEL_OPEN, TeamId: team.Id}
|
||||
channel1 = Client.Must(Client.CreateChannel(channel1)).Data.(*model.Channel)
|
||||
|
||||
if utils.Cfg.ServiceSettings.AllowIncomingWebhooks {
|
||||
hook1 := &model.IncomingWebhook{ChannelId: channel1.Id}
|
||||
hook1 = Client.Must(Client.CreateIncomingWebhook(hook1)).Data.(*model.IncomingWebhook)
|
||||
|
||||
hook2 := &model.IncomingWebhook{ChannelId: channel1.Id}
|
||||
hook2 = Client.Must(Client.CreateIncomingWebhook(hook2)).Data.(*model.IncomingWebhook)
|
||||
|
||||
if result, err := Client.ListIncomingWebhooks(); err != nil {
|
||||
t.Fatal(err)
|
||||
} else {
|
||||
hooks := result.Data.([]*model.IncomingWebhook)
|
||||
|
||||
if len(hooks) != 2 {
|
||||
t.Fatal("incorrect number of hooks")
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if _, err := Client.ListIncomingWebhooks(); err == nil {
|
||||
t.Fatal("should have errored - webhooks turned off")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestDeleteIncomingHook(t *testing.T) {
|
||||
Setup()
|
||||
|
||||
team := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN}
|
||||
team = Client.Must(Client.CreateTeam(team)).Data.(*model.Team)
|
||||
|
||||
user := &model.User{TeamId: team.Id, Email: model.NewId() + "corey@test.com", Nickname: "Corey Hulen", Password: "pwd"}
|
||||
user = Client.Must(Client.CreateUser(user, "")).Data.(*model.User)
|
||||
store.Must(Srv.Store.User().VerifyEmail(user.Id))
|
||||
|
||||
Client.LoginByEmail(team.Name, user.Email, "pwd")
|
||||
|
||||
channel1 := &model.Channel{DisplayName: "Test API Name", Name: "a" + model.NewId() + "a", Type: model.CHANNEL_OPEN, TeamId: team.Id}
|
||||
channel1 = Client.Must(Client.CreateChannel(channel1)).Data.(*model.Channel)
|
||||
|
||||
if utils.Cfg.ServiceSettings.AllowIncomingWebhooks {
|
||||
hook := &model.IncomingWebhook{ChannelId: channel1.Id}
|
||||
hook = Client.Must(Client.CreateIncomingWebhook(hook)).Data.(*model.IncomingWebhook)
|
||||
|
||||
data := make(map[string]string)
|
||||
data["id"] = hook.Id
|
||||
|
||||
if _, err := Client.DeleteIncomingWebhook(data); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
hooks := Client.Must(Client.ListIncomingWebhooks()).Data.([]*model.IncomingWebhook)
|
||||
if len(hooks) != 0 {
|
||||
t.Fatal("delete didn't work properly")
|
||||
}
|
||||
} else {
|
||||
data := make(map[string]string)
|
||||
data["id"] = "123"
|
||||
|
||||
if _, err := Client.DeleteIncomingWebhook(data); err == nil {
|
||||
t.Fatal("should have errored - webhooks turned off")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestZZWebSocketTearDown(t *testing.T) {
|
||||
// *IMPORTANT* - Kind of hacky
|
||||
// This should be the last function in any test file
|
||||
// that calls Setup()
|
||||
// Should be in the last file too sorted by name
|
||||
time.Sleep(2 * time.Second)
|
||||
TearDown()
|
||||
}
|
||||
@@ -22,7 +22,8 @@
|
||||
"StorageDirectory": "./data/",
|
||||
"AllowedLoginAttempts": 10,
|
||||
"DisableEmailSignUp": false,
|
||||
"EnableOAuthServiceProvider": false
|
||||
"EnableOAuthServiceProvider": false,
|
||||
"AllowIncomingWebhooks": false
|
||||
},
|
||||
"SqlSettings": {
|
||||
"DriverName": "mysql",
|
||||
|
||||
@@ -24,7 +24,8 @@
|
||||
"StorageDirectory": "/mattermost/data/",
|
||||
"AllowedLoginAttempts": 10,
|
||||
"DisableEmailSignUp": false,
|
||||
"EnableOAuthServiceProvider": false
|
||||
"EnableOAuthServiceProvider": false,
|
||||
"AllowIncomingWebhooks": false
|
||||
},
|
||||
"SSOSettings": {
|
||||
"gitlab": {
|
||||
|
||||
@@ -24,7 +24,8 @@
|
||||
"StorageDirectory": "/mattermost/data/",
|
||||
"AllowedLoginAttempts": 10,
|
||||
"DisableEmailSignUp": false,
|
||||
"EnableOAuthServiceProvider": false
|
||||
"EnableOAuthServiceProvider": false,
|
||||
"AllowIncomingWebhooks": false
|
||||
},
|
||||
"SSOSettings": {
|
||||
"gitlab": {
|
||||
|
||||
@@ -120,3 +120,11 @@ func (o *Channel) ExtraUpdated() {
|
||||
|
||||
func (o *Channel) PreExport() {
|
||||
}
|
||||
|
||||
func GetDMNameFromIds(userId1, userId2 string) string {
|
||||
if userId1 > userId2 {
|
||||
return userId2 + "__" + userId1
|
||||
} else {
|
||||
return userId1 + "__" + userId2
|
||||
}
|
||||
}
|
||||
|
||||
@@ -48,7 +48,7 @@ func NewClient(url string) *Client {
|
||||
return &Client{url, url + API_URL_SUFFIX, &http.Client{}, "", ""}
|
||||
}
|
||||
|
||||
func (c *Client) DoPost(url string, data, contentType string) (*http.Response, *AppError) {
|
||||
func (c *Client) DoPost(url, data, contentType string) (*http.Response, *AppError) {
|
||||
rq, _ := http.NewRequest("POST", c.Url+url, strings.NewReader(data))
|
||||
rq.Header.Set("Content-Type", contentType)
|
||||
|
||||
@@ -806,6 +806,42 @@ func (c *Client) GetAccessToken(data url.Values) (*Result, *AppError) {
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Client) CreateIncomingWebhook(hook *IncomingWebhook) (*Result, *AppError) {
|
||||
if r, err := c.DoApiPost("/hooks/incoming/create", hook.ToJson()); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
return &Result{r.Header.Get(HEADER_REQUEST_ID),
|
||||
r.Header.Get(HEADER_ETAG_SERVER), IncomingWebhookFromJson(r.Body)}, nil
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Client) PostToWebhook(id, payload string) (*Result, *AppError) {
|
||||
if r, err := c.DoPost("/hooks/"+id, payload, "application/x-www-form-urlencoded"); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
return &Result{r.Header.Get(HEADER_REQUEST_ID),
|
||||
r.Header.Get(HEADER_ETAG_SERVER), nil}, nil
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Client) DeleteIncomingWebhook(data map[string]string) (*Result, *AppError) {
|
||||
if r, err := c.DoApiPost("/hooks/incoming/delete", MapToJson(data)); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
return &Result{r.Header.Get(HEADER_REQUEST_ID),
|
||||
r.Header.Get(HEADER_ETAG_SERVER), MapFromJson(r.Body)}, nil
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Client) ListIncomingWebhooks() (*Result, *AppError) {
|
||||
if r, err := c.DoApiGet("/hooks/incoming/list", "", ""); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
return &Result{r.Header.Get(HEADER_REQUEST_ID),
|
||||
r.Header.Get(HEADER_ETAG_SERVER), IncomingWebhookListFromJson(r.Body)}, nil
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Client) MockSession(sessionToken string) {
|
||||
c.AuthToken = sessionToken
|
||||
c.AuthType = HEADER_BEARER
|
||||
|
||||
@@ -24,6 +24,7 @@ type ServiceSettings struct {
|
||||
AllowedLoginAttempts int
|
||||
DisableEmailSignUp bool
|
||||
EnableOAuthServiceProvider bool
|
||||
AllowIncomingWebhooks bool
|
||||
}
|
||||
|
||||
type SSOSetting struct {
|
||||
|
||||
101
model/webhook.go
Обычный файл
101
model/webhook.go
Обычный файл
@@ -0,0 +1,101 @@
|
||||
// Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved.
|
||||
// See License.txt for license information.
|
||||
|
||||
package model
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"io"
|
||||
)
|
||||
|
||||
type IncomingWebhook struct {
|
||||
Id string `json:"id"`
|
||||
CreateAt int64 `json:"create_at"`
|
||||
UpdateAt int64 `json:"update_at"`
|
||||
DeleteAt int64 `json:"delete_at"`
|
||||
UserId string `json:"user_id"`
|
||||
ChannelId string `json:"channel_id"`
|
||||
TeamId string `json:"team_id"`
|
||||
}
|
||||
|
||||
func (o *IncomingWebhook) ToJson() string {
|
||||
b, err := json.Marshal(o)
|
||||
if err != nil {
|
||||
return ""
|
||||
} else {
|
||||
return string(b)
|
||||
}
|
||||
}
|
||||
|
||||
func IncomingWebhookFromJson(data io.Reader) *IncomingWebhook {
|
||||
decoder := json.NewDecoder(data)
|
||||
var o IncomingWebhook
|
||||
err := decoder.Decode(&o)
|
||||
if err == nil {
|
||||
return &o
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func IncomingWebhookListToJson(l []*IncomingWebhook) string {
|
||||
b, err := json.Marshal(l)
|
||||
if err != nil {
|
||||
return ""
|
||||
} else {
|
||||
return string(b)
|
||||
}
|
||||
}
|
||||
|
||||
func IncomingWebhookListFromJson(data io.Reader) []*IncomingWebhook {
|
||||
decoder := json.NewDecoder(data)
|
||||
var o []*IncomingWebhook
|
||||
err := decoder.Decode(&o)
|
||||
if err == nil {
|
||||
return o
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func (o *IncomingWebhook) IsValid() *AppError {
|
||||
|
||||
if len(o.Id) != 26 {
|
||||
return NewAppError("IncomingWebhook.IsValid", "Invalid Id", "")
|
||||
}
|
||||
|
||||
if o.CreateAt == 0 {
|
||||
return NewAppError("IncomingWebhook.IsValid", "Create at must be a valid time", "id="+o.Id)
|
||||
}
|
||||
|
||||
if o.UpdateAt == 0 {
|
||||
return NewAppError("IncomingWebhook.IsValid", "Update at must be a valid time", "id="+o.Id)
|
||||
}
|
||||
|
||||
if len(o.UserId) != 26 {
|
||||
return NewAppError("IncomingWebhook.IsValid", "Invalid user id", "")
|
||||
}
|
||||
|
||||
if len(o.ChannelId) != 26 {
|
||||
return NewAppError("IncomingWebhook.IsValid", "Invalid channel id", "")
|
||||
}
|
||||
|
||||
if len(o.TeamId) != 26 {
|
||||
return NewAppError("IncomingWebhook.IsValid", "Invalid channel id", "")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (o *IncomingWebhook) PreSave() {
|
||||
if o.Id == "" {
|
||||
o.Id = NewId()
|
||||
}
|
||||
|
||||
o.CreateAt = GetMillis()
|
||||
o.UpdateAt = o.CreateAt
|
||||
}
|
||||
|
||||
func (o *IncomingWebhook) PreUpdate() {
|
||||
o.UpdateAt = GetMillis()
|
||||
}
|
||||
82
model/webhook_test.go
Обычный файл
82
model/webhook_test.go
Обычный файл
@@ -0,0 +1,82 @@
|
||||
// Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved.
|
||||
// See License.txt for license information.
|
||||
|
||||
package model
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestIncomingWebhookJson(t *testing.T) {
|
||||
o := IncomingWebhook{Id: NewId()}
|
||||
json := o.ToJson()
|
||||
ro := IncomingWebhookFromJson(strings.NewReader(json))
|
||||
|
||||
if o.Id != ro.Id {
|
||||
t.Fatal("Ids do not match")
|
||||
}
|
||||
}
|
||||
|
||||
func TestIncomingWebhookIsValid(t *testing.T) {
|
||||
o := IncomingWebhook{}
|
||||
|
||||
if err := o.IsValid(); err == nil {
|
||||
t.Fatal("should be invalid")
|
||||
}
|
||||
|
||||
o.Id = NewId()
|
||||
if err := o.IsValid(); err == nil {
|
||||
t.Fatal("should be invalid")
|
||||
}
|
||||
|
||||
o.CreateAt = GetMillis()
|
||||
if err := o.IsValid(); err == nil {
|
||||
t.Fatal("should be invalid")
|
||||
}
|
||||
|
||||
o.UpdateAt = GetMillis()
|
||||
if err := o.IsValid(); err == nil {
|
||||
t.Fatal("should be invalid")
|
||||
}
|
||||
|
||||
o.UserId = "123"
|
||||
if err := o.IsValid(); err == nil {
|
||||
t.Fatal("should be invalid")
|
||||
}
|
||||
|
||||
o.UserId = NewId()
|
||||
if err := o.IsValid(); err == nil {
|
||||
t.Fatal("should be invalid")
|
||||
}
|
||||
|
||||
o.ChannelId = "123"
|
||||
if err := o.IsValid(); err == nil {
|
||||
t.Fatal("should be invalid")
|
||||
}
|
||||
|
||||
o.ChannelId = NewId()
|
||||
if err := o.IsValid(); err == nil {
|
||||
t.Fatal("should be invalid")
|
||||
}
|
||||
|
||||
o.TeamId = "123"
|
||||
if err := o.IsValid(); err == nil {
|
||||
t.Fatal("should be invalid")
|
||||
}
|
||||
|
||||
o.TeamId = NewId()
|
||||
if err := o.IsValid(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestIncomingWebhookPreSave(t *testing.T) {
|
||||
o := IncomingWebhook{}
|
||||
o.PreSave()
|
||||
}
|
||||
|
||||
func TestIncomingWebhookPreUpdate(t *testing.T) {
|
||||
o := IncomingWebhook{}
|
||||
o.PreUpdate()
|
||||
}
|
||||
@@ -40,6 +40,7 @@ type SqlStore struct {
|
||||
session SessionStore
|
||||
oauth OAuthStore
|
||||
system SystemStore
|
||||
webhook WebhookStore
|
||||
}
|
||||
|
||||
func NewSqlStore() Store {
|
||||
@@ -91,6 +92,7 @@ func NewSqlStore() Store {
|
||||
sqlStore.session = NewSqlSessionStore(sqlStore)
|
||||
sqlStore.oauth = NewSqlOAuthStore(sqlStore)
|
||||
sqlStore.system = NewSqlSystemStore(sqlStore)
|
||||
sqlStore.webhook = NewSqlWebhookStore(sqlStore)
|
||||
|
||||
sqlStore.master.CreateTablesIfNotExists()
|
||||
|
||||
@@ -102,6 +104,7 @@ func NewSqlStore() Store {
|
||||
sqlStore.session.(*SqlSessionStore).UpgradeSchemaIfNeeded()
|
||||
sqlStore.oauth.(*SqlOAuthStore).UpgradeSchemaIfNeeded()
|
||||
sqlStore.system.(*SqlSystemStore).UpgradeSchemaIfNeeded()
|
||||
sqlStore.webhook.(*SqlWebhookStore).UpgradeSchemaIfNeeded()
|
||||
|
||||
sqlStore.team.(*SqlTeamStore).CreateIndexesIfNotExists()
|
||||
sqlStore.channel.(*SqlChannelStore).CreateIndexesIfNotExists()
|
||||
@@ -111,6 +114,7 @@ func NewSqlStore() Store {
|
||||
sqlStore.session.(*SqlSessionStore).CreateIndexesIfNotExists()
|
||||
sqlStore.oauth.(*SqlOAuthStore).CreateIndexesIfNotExists()
|
||||
sqlStore.system.(*SqlSystemStore).CreateIndexesIfNotExists()
|
||||
sqlStore.webhook.(*SqlWebhookStore).CreateIndexesIfNotExists()
|
||||
|
||||
if model.IsPreviousVersion(schemaVersion) {
|
||||
sqlStore.system.Update(&model.System{Name: "Version", Value: model.CurrentVersion})
|
||||
@@ -469,6 +473,10 @@ func (ss SqlStore) System() SystemStore {
|
||||
return ss.system
|
||||
}
|
||||
|
||||
func (ss SqlStore) Webhook() WebhookStore {
|
||||
return ss.webhook
|
||||
}
|
||||
|
||||
type mattermConverter struct{}
|
||||
|
||||
func (me mattermConverter) ToDb(val interface{}) (interface{}, error) {
|
||||
|
||||
128
store/sql_webhook_store.go
Обычный файл
128
store/sql_webhook_store.go
Обычный файл
@@ -0,0 +1,128 @@
|
||||
// Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved.
|
||||
// See License.txt for license information.
|
||||
|
||||
package store
|
||||
|
||||
import (
|
||||
"github.com/mattermost/platform/model"
|
||||
)
|
||||
|
||||
type SqlWebhookStore struct {
|
||||
*SqlStore
|
||||
}
|
||||
|
||||
func NewSqlWebhookStore(sqlStore *SqlStore) WebhookStore {
|
||||
s := &SqlWebhookStore{sqlStore}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
return s
|
||||
}
|
||||
|
||||
func (s SqlWebhookStore) UpgradeSchemaIfNeeded() {
|
||||
}
|
||||
|
||||
func (s SqlWebhookStore) CreateIndexesIfNotExists() {
|
||||
s.CreateIndexIfNotExists("idx_webhook_user_id", "IncomingWebhooks", "UserId")
|
||||
s.CreateIndexIfNotExists("idx_webhook_team_id", "IncomingWebhooks", "TeamId")
|
||||
}
|
||||
|
||||
func (s SqlWebhookStore) SaveIncoming(webhook *model.IncomingWebhook) StoreChannel {
|
||||
storeChannel := make(StoreChannel)
|
||||
|
||||
go func() {
|
||||
result := StoreResult{}
|
||||
|
||||
if len(webhook.Id) > 0 {
|
||||
result.Err = model.NewAppError("SqlWebhookStore.SaveIncoming",
|
||||
"You cannot overwrite an existing IncomingWebhook", "id="+webhook.Id)
|
||||
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", "We couldn't save the IncomingWebhook", "id="+webhook.Id+", "+err.Error())
|
||||
} else {
|
||||
result.Data = webhook
|
||||
}
|
||||
|
||||
storeChannel <- result
|
||||
close(storeChannel)
|
||||
}()
|
||||
|
||||
return storeChannel
|
||||
}
|
||||
|
||||
func (s SqlWebhookStore) GetIncoming(id string) StoreChannel {
|
||||
storeChannel := make(StoreChannel)
|
||||
|
||||
go func() {
|
||||
result := StoreResult{}
|
||||
|
||||
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 {
|
||||
result.Err = model.NewAppError("SqlWebhookStore.GetIncoming", "We couldn't get the webhook", "id="+id+", err="+err.Error())
|
||||
}
|
||||
|
||||
result.Data = &webhook
|
||||
|
||||
storeChannel <- result
|
||||
close(storeChannel)
|
||||
}()
|
||||
|
||||
return storeChannel
|
||||
}
|
||||
|
||||
func (s SqlWebhookStore) DeleteIncoming(webhookId string, time int64) StoreChannel {
|
||||
storeChannel := make(StoreChannel)
|
||||
|
||||
go func() {
|
||||
result := 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", "We couldn't delete the webhook", "id="+webhookId+", err="+err.Error())
|
||||
}
|
||||
|
||||
storeChannel <- result
|
||||
close(storeChannel)
|
||||
}()
|
||||
|
||||
return storeChannel
|
||||
}
|
||||
|
||||
func (s SqlWebhookStore) GetIncomingByUser(userId string) StoreChannel {
|
||||
storeChannel := make(StoreChannel)
|
||||
|
||||
go func() {
|
||||
result := StoreResult{}
|
||||
|
||||
var webhooks []*model.IncomingWebhook
|
||||
|
||||
if _, err := s.GetReplica().Select(&webhooks, "SELECT * FROM IncomingWebhooks WHERE UserId = :UserId AND DeleteAt = 0", map[string]interface{}{"UserId": userId}); err != nil {
|
||||
result.Err = model.NewAppError("SqlWebhookStore.GetIncomingByUser", "We couldn't get the webhook", "userId="+userId+", err="+err.Error())
|
||||
}
|
||||
|
||||
result.Data = webhooks
|
||||
|
||||
storeChannel <- result
|
||||
close(storeChannel)
|
||||
}()
|
||||
|
||||
return storeChannel
|
||||
}
|
||||
77
store/sql_webhook_store_test.go
Обычный файл
77
store/sql_webhook_store_test.go
Обычный файл
@@ -0,0 +1,77 @@
|
||||
// Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved.
|
||||
// See License.txt for license information.
|
||||
|
||||
package store
|
||||
|
||||
import (
|
||||
"github.com/mattermost/platform/model"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestIncomingWebhookStoreSaveIncoming(t *testing.T) {
|
||||
Setup()
|
||||
|
||||
o1 := model.IncomingWebhook{}
|
||||
o1.ChannelId = model.NewId()
|
||||
o1.UserId = model.NewId()
|
||||
o1.TeamId = model.NewId()
|
||||
|
||||
if err := (<-store.Webhook().SaveIncoming(&o1)).Err; err != nil {
|
||||
t.Fatal("couldn't save item", err)
|
||||
}
|
||||
|
||||
if err := (<-store.Webhook().SaveIncoming(&o1)).Err; err == nil {
|
||||
t.Fatal("shouldn't be able to update from save")
|
||||
}
|
||||
}
|
||||
|
||||
func TestIncomingWebhookStoreGetIncoming(t *testing.T) {
|
||||
Setup()
|
||||
|
||||
o1 := &model.IncomingWebhook{}
|
||||
o1.ChannelId = model.NewId()
|
||||
o1.UserId = model.NewId()
|
||||
o1.TeamId = model.NewId()
|
||||
|
||||
o1 = (<-store.Webhook().SaveIncoming(o1)).Data.(*model.IncomingWebhook)
|
||||
|
||||
if r1 := <-store.Webhook().GetIncoming(o1.Id); r1.Err != nil {
|
||||
t.Fatal(r1.Err)
|
||||
} else {
|
||||
if r1.Data.(*model.IncomingWebhook).CreateAt != o1.CreateAt {
|
||||
t.Fatal("invalid returned webhook")
|
||||
}
|
||||
}
|
||||
|
||||
if err := (<-store.Webhook().GetIncoming("123")).Err; err == nil {
|
||||
t.Fatal("Missing id should have failed")
|
||||
}
|
||||
}
|
||||
|
||||
func TestIncomingWebhookStoreDelete(t *testing.T) {
|
||||
Setup()
|
||||
|
||||
o1 := &model.IncomingWebhook{}
|
||||
o1.ChannelId = model.NewId()
|
||||
o1.UserId = model.NewId()
|
||||
o1.TeamId = model.NewId()
|
||||
|
||||
o1 = (<-store.Webhook().SaveIncoming(o1)).Data.(*model.IncomingWebhook)
|
||||
|
||||
if r1 := <-store.Webhook().GetIncoming(o1.Id); r1.Err != nil {
|
||||
t.Fatal(r1.Err)
|
||||
} else {
|
||||
if r1.Data.(*model.IncomingWebhook).CreateAt != o1.CreateAt {
|
||||
t.Fatal("invalid returned webhook")
|
||||
}
|
||||
}
|
||||
|
||||
if r2 := <-store.Webhook().DeleteIncoming(o1.Id, model.GetMillis()); r2.Err != nil {
|
||||
t.Fatal(r2.Err)
|
||||
}
|
||||
|
||||
if r3 := (<-store.Webhook().GetIncoming(o1.Id)); r3.Err == nil {
|
||||
t.Log(r3.Data)
|
||||
t.Fatal("Missing id should have failed")
|
||||
}
|
||||
}
|
||||
@@ -36,6 +36,7 @@ type Store interface {
|
||||
Session() SessionStore
|
||||
OAuth() OAuthStore
|
||||
System() SystemStore
|
||||
Webhook() WebhookStore
|
||||
Close()
|
||||
}
|
||||
|
||||
@@ -137,3 +138,10 @@ type SystemStore interface {
|
||||
Update(system *model.System) StoreChannel
|
||||
Get() StoreChannel
|
||||
}
|
||||
|
||||
type WebhookStore interface {
|
||||
SaveIncoming(webhook *model.IncomingWebhook) StoreChannel
|
||||
GetIncoming(id string) StoreChannel
|
||||
GetIncomingByUser(userId string) StoreChannel
|
||||
DeleteIncoming(webhookId string, time int64) StoreChannel
|
||||
}
|
||||
|
||||
@@ -188,6 +188,7 @@ func getClientProperties(c *model.Config) map[string]string {
|
||||
props["ProfileWidth"] = fmt.Sprintf("%v", c.ImageSettings.ProfileWidth)
|
||||
props["ProfileWidth"] = fmt.Sprintf("%v", c.ImageSettings.ProfileWidth)
|
||||
props["EnableOAuthServiceProvider"] = strconv.FormatBool(c.ServiceSettings.EnableOAuthServiceProvider)
|
||||
props["AllowIncomingWebhooks"] = strconv.FormatBool(c.ServiceSettings.AllowIncomingWebhooks)
|
||||
|
||||
return props
|
||||
}
|
||||
|
||||
177
web/react/components/user_settings/manage_incoming_hooks.jsx
Обычный файл
177
web/react/components/user_settings/manage_incoming_hooks.jsx
Обычный файл
@@ -0,0 +1,177 @@
|
||||
// Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved.
|
||||
// See License.txt for license information.
|
||||
|
||||
var Client = require('../../utils/client.jsx');
|
||||
var Utils = require('../../utils/utils.jsx');
|
||||
var Constants = require('../../utils/constants.jsx');
|
||||
var ChannelStore = require('../../stores/channel_store.jsx');
|
||||
var LoadingScreen = require('../loading_screen.jsx');
|
||||
|
||||
export default class ManageIncomingHooks extends React.Component {
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.getHooks = this.getHooks.bind(this);
|
||||
this.addNewHook = this.addNewHook.bind(this);
|
||||
this.updateChannelId = this.updateChannelId.bind(this);
|
||||
|
||||
this.state = {hooks: [], channelId: ChannelStore.getByName(Constants.DEFAULT_CHANNEL).id, getHooksComplete: false};
|
||||
}
|
||||
componentDidMount() {
|
||||
this.getHooks();
|
||||
}
|
||||
addNewHook() {
|
||||
let hook = {}; //eslint-disable-line prefer-const
|
||||
hook.channel_id = this.state.channelId;
|
||||
|
||||
Client.addIncomingHook(
|
||||
hook,
|
||||
(data) => {
|
||||
let hooks = this.state.hooks;
|
||||
if (!hooks) {
|
||||
hooks = [];
|
||||
}
|
||||
hooks.push(data);
|
||||
this.setState({hooks});
|
||||
},
|
||||
(err) => {
|
||||
this.setState({serverError: err});
|
||||
}
|
||||
);
|
||||
}
|
||||
removeHook(id) {
|
||||
let data = {}; //eslint-disable-line prefer-const
|
||||
data.id = id;
|
||||
|
||||
Client.deleteIncomingHook(
|
||||
data,
|
||||
() => {
|
||||
let hooks = this.state.hooks; //eslint-disable-line prefer-const
|
||||
let index = -1;
|
||||
for (let i = 0; i < hooks.length; i++) {
|
||||
if (hooks[i].id === id) {
|
||||
index = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (index !== -1) {
|
||||
hooks.splice(index, 1);
|
||||
}
|
||||
|
||||
this.setState({hooks});
|
||||
},
|
||||
(err) => {
|
||||
this.setState({serverError: err});
|
||||
}
|
||||
);
|
||||
}
|
||||
getHooks() {
|
||||
Client.listIncomingHooks(
|
||||
(data) => {
|
||||
let state = this.state; //eslint-disable-line prefer-const
|
||||
|
||||
if (data) {
|
||||
state.hooks = data;
|
||||
}
|
||||
|
||||
state.getHooksComplete = true;
|
||||
this.setState(state);
|
||||
},
|
||||
(err) => {
|
||||
this.setState({serverError: err});
|
||||
}
|
||||
);
|
||||
}
|
||||
updateChannelId(e) {
|
||||
this.setState({channelId: e.target.value});
|
||||
}
|
||||
render() {
|
||||
let serverError;
|
||||
if (this.state.serverError) {
|
||||
serverError = <label className='has-error'>{this.state.serverError}</label>;
|
||||
}
|
||||
|
||||
const channels = ChannelStore.getAll();
|
||||
let options = []; //eslint-disable-line prefer-const
|
||||
channels.forEach((channel) => {
|
||||
options.push(<option value={channel.id}>{channel.name}</option>);
|
||||
});
|
||||
|
||||
let disableButton = '';
|
||||
if (this.state.channelId === '') {
|
||||
disableButton = ' disable';
|
||||
}
|
||||
|
||||
let hooks = []; //eslint-disable-line prefer-const
|
||||
this.state.hooks.forEach((hook) => {
|
||||
const c = ChannelStore.get(hook.channel_id);
|
||||
hooks.push(
|
||||
<div>
|
||||
<div className='divider-light'></div>
|
||||
<span>
|
||||
<strong>{'URL: '}</strong>{Utils.getWindowLocationOrigin() + '/hooks/' + hook.id}
|
||||
</span>
|
||||
<br/>
|
||||
<span>
|
||||
<strong>{'Channel: '}</strong>{c.name}
|
||||
</span>
|
||||
<br/>
|
||||
<a
|
||||
className={'btn btn-sm btn-primary'}
|
||||
href='#'
|
||||
onClick={this.removeHook.bind(this, hook.id)}
|
||||
>
|
||||
{'Remove'}
|
||||
</a>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
let displayHooks;
|
||||
if (!this.state.getHooksComplete) {
|
||||
displayHooks = <LoadingScreen/>;
|
||||
} else if (hooks.length > 0) {
|
||||
displayHooks = hooks;
|
||||
} else {
|
||||
displayHooks = <label>{'None'}</label>;
|
||||
}
|
||||
|
||||
const existingHooks = (
|
||||
<div>
|
||||
<label className='control-label'>{'Existing incoming webhooks'}</label>
|
||||
<br/>
|
||||
{displayHooks}
|
||||
</div>
|
||||
);
|
||||
|
||||
return (
|
||||
<div
|
||||
key='addIncomingHook'
|
||||
className='form-group'
|
||||
>
|
||||
<label className='control-label'>{'Add a new incoming webhook'}</label>
|
||||
<br/>
|
||||
<div>
|
||||
<select
|
||||
ref='channelName'
|
||||
value={this.state.channelId}
|
||||
onChange={this.updateChannelId}
|
||||
>
|
||||
{options}
|
||||
</select>
|
||||
<br/>
|
||||
{serverError}
|
||||
<a
|
||||
className={'btn btn-sm btn-primary' + disableButton}
|
||||
href='#'
|
||||
onClick={this.addNewHook}
|
||||
>
|
||||
{'Add'}
|
||||
</a>
|
||||
</div>
|
||||
{existingHooks}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,13 +1,14 @@
|
||||
// Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved.
|
||||
// See License.txt for license information.
|
||||
|
||||
var UserStore = require('../stores/user_store.jsx');
|
||||
var utils = require('../utils/utils.jsx');
|
||||
var UserStore = require('../../stores/user_store.jsx');
|
||||
var utils = require('../../utils/utils.jsx');
|
||||
var NotificationsTab = require('./user_settings_notifications.jsx');
|
||||
var SecurityTab = require('./user_settings_security.jsx');
|
||||
var GeneralTab = require('./user_settings_general.jsx');
|
||||
var AppearanceTab = require('./user_settings_appearance.jsx');
|
||||
var DeveloperTab = require('./user_settings_developer.jsx');
|
||||
var IntegrationsTab = require('./user_settings_integrations.jsx');
|
||||
|
||||
export default class UserSettings extends React.Component {
|
||||
constructor(props) {
|
||||
@@ -86,6 +87,17 @@ export default class UserSettings extends React.Component {
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
} else if (this.props.activeTab === 'integrations') {
|
||||
return (
|
||||
<div>
|
||||
<IntegrationsTab
|
||||
user={this.state.user}
|
||||
activeSection={this.props.activeSection}
|
||||
updateSection={this.props.updateSection}
|
||||
updateTab={this.props.updateTab}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return <div/>;
|
||||
@@ -1,11 +1,11 @@
|
||||
// Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved.
|
||||
// See License.txt for license information.
|
||||
|
||||
var UserStore = require('../stores/user_store.jsx');
|
||||
var SettingItemMin = require('./setting_item_min.jsx');
|
||||
var SettingItemMax = require('./setting_item_max.jsx');
|
||||
var Client = require('../utils/client.jsx');
|
||||
var Utils = require('../utils/utils.jsx');
|
||||
var UserStore = require('../../stores/user_store.jsx');
|
||||
var SettingItemMin = require('../setting_item_min.jsx');
|
||||
var SettingItemMax = require('../setting_item_max.jsx');
|
||||
var Client = require('../../utils/client.jsx');
|
||||
var Utils = require('../../utils/utils.jsx');
|
||||
|
||||
var ThemeColors = ['#2389d7', '#008a17', '#dc4fad', '#ac193d', '#0072c6', '#d24726', '#ff8f32', '#82ba00', '#03b3b2', '#008299', '#4617b4', '#8c0095', '#004b8b', '#004b8b', '#570000', '#380000', '#585858', '#000000'];
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
// Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved.
|
||||
// See License.txt for license information.
|
||||
|
||||
var SettingItemMin = require('./setting_item_min.jsx');
|
||||
var SettingItemMax = require('./setting_item_max.jsx');
|
||||
var SettingItemMin = require('../setting_item_min.jsx');
|
||||
var SettingItemMax = require('../setting_item_max.jsx');
|
||||
|
||||
export default class DeveloperTab extends React.Component {
|
||||
constructor(props) {
|
||||
@@ -1,13 +1,13 @@
|
||||
// Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved.
|
||||
// See License.txt for license information.
|
||||
|
||||
var UserStore = require('../stores/user_store.jsx');
|
||||
var SettingItemMin = require('./setting_item_min.jsx');
|
||||
var SettingItemMax = require('./setting_item_max.jsx');
|
||||
var SettingPicture = require('./setting_picture.jsx');
|
||||
var client = require('../utils/client.jsx');
|
||||
var AsyncClient = require('../utils/async_client.jsx');
|
||||
var utils = require('../utils/utils.jsx');
|
||||
var UserStore = require('../../stores/user_store.jsx');
|
||||
var SettingItemMin = require('../setting_item_min.jsx');
|
||||
var SettingItemMax = require('../setting_item_max.jsx');
|
||||
var SettingPicture = require('../setting_picture.jsx');
|
||||
var client = require('../../utils/client.jsx');
|
||||
var AsyncClient = require('../../utils/async_client.jsx');
|
||||
var utils = require('../../utils/utils.jsx');
|
||||
var assign = require('object-assign');
|
||||
|
||||
export default class UserSettingsGeneralTab extends React.Component {
|
||||
@@ -0,0 +1,95 @@
|
||||
// Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved.
|
||||
// See License.txt for license information.
|
||||
|
||||
var SettingItemMin = require('../setting_item_min.jsx');
|
||||
var SettingItemMax = require('../setting_item_max.jsx');
|
||||
var ManageIncomingHooks = require('./manage_incoming_hooks.jsx');
|
||||
|
||||
export default class UserSettingsIntegrationsTab extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.updateSection = this.updateSection.bind(this);
|
||||
this.handleClose = this.handleClose.bind(this);
|
||||
|
||||
this.state = {};
|
||||
}
|
||||
updateSection(section) {
|
||||
this.props.updateSection(section);
|
||||
}
|
||||
handleClose() {
|
||||
this.updateSection('');
|
||||
}
|
||||
componentDidMount() {
|
||||
$('#user_settings').on('hidden.bs.modal', this.handleClose);
|
||||
}
|
||||
componentWillUnmount() {
|
||||
$('#user_settings').off('hidden.bs.modal', this.handleClose);
|
||||
}
|
||||
render() {
|
||||
let incomingHooksSection;
|
||||
var inputs = [];
|
||||
|
||||
if (this.props.activeSection === 'incoming-hooks') {
|
||||
inputs.push(
|
||||
<ManageIncomingHooks />
|
||||
);
|
||||
|
||||
incomingHooksSection = (
|
||||
<SettingItemMax
|
||||
title='Incoming Webhooks'
|
||||
inputs={inputs}
|
||||
updateSection={function clearSection(e) {
|
||||
this.updateSection('');
|
||||
e.preventDefault();
|
||||
}.bind(this)}
|
||||
/>
|
||||
);
|
||||
} else {
|
||||
incomingHooksSection = (
|
||||
<SettingItemMin
|
||||
title='Incoming Webhooks'
|
||||
describe='Manage your incoming webhooks'
|
||||
updateSection={function updateNameSection() {
|
||||
this.updateSection('incoming-hooks');
|
||||
}.bind(this)}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className='modal-header'>
|
||||
<button
|
||||
type='button'
|
||||
className='close'
|
||||
data-dismiss='modal'
|
||||
aria-label='Close'
|
||||
>
|
||||
<span aria-hidden='true'>{'×'}</span>
|
||||
</button>
|
||||
<h4
|
||||
className='modal-title'
|
||||
ref='title'
|
||||
>
|
||||
<i className='modal-back'></i>
|
||||
{'Integration Settings'}
|
||||
</h4>
|
||||
</div>
|
||||
<div className='user-settings'>
|
||||
<h3 className='tab-header'>{'Integration Settings'}</h3>
|
||||
<div className='divider-dark first'/>
|
||||
{incomingHooksSection}
|
||||
<div className='divider-dark'/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
UserSettingsIntegrationsTab.propTypes = {
|
||||
user: React.PropTypes.object,
|
||||
updateSection: React.PropTypes.func,
|
||||
updateTab: React.PropTypes.func,
|
||||
activeSection: React.PropTypes.string
|
||||
};
|
||||
@@ -1,7 +1,7 @@
|
||||
// Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved.
|
||||
// See License.txt for license information.
|
||||
|
||||
var SettingsSidebar = require('./settings_sidebar.jsx');
|
||||
var SettingsSidebar = require('../settings_sidebar.jsx');
|
||||
var UserSettings = require('./user_settings.jsx');
|
||||
|
||||
export default class UserSettingsModal extends React.Component {
|
||||
@@ -38,6 +38,9 @@ export default class UserSettingsModal extends React.Component {
|
||||
if (global.window.config.EnableOAuthServiceProvider === 'true') {
|
||||
tabs.push({name: 'developer', uiName: 'Developer', icon: 'glyphicon glyphicon-th'});
|
||||
}
|
||||
if (global.window.config.AllowIncomingWebhooks === 'true') {
|
||||
tabs.push({name: 'integrations', uiName: 'Integrations', icon: 'glyphicon glyphicon-transfer'});
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
@@ -1,12 +1,12 @@
|
||||
// Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved.
|
||||
// See License.txt for license information.
|
||||
|
||||
var UserStore = require('../stores/user_store.jsx');
|
||||
var SettingItemMin = require('./setting_item_min.jsx');
|
||||
var SettingItemMax = require('./setting_item_max.jsx');
|
||||
var client = require('../utils/client.jsx');
|
||||
var AsyncClient = require('../utils/async_client.jsx');
|
||||
var utils = require('../utils/utils.jsx');
|
||||
var UserStore = require('../../stores/user_store.jsx');
|
||||
var SettingItemMin = require('../setting_item_min.jsx');
|
||||
var SettingItemMax = require('../setting_item_max.jsx');
|
||||
var client = require('../../utils/client.jsx');
|
||||
var AsyncClient = require('../../utils/async_client.jsx');
|
||||
var utils = require('../../utils/utils.jsx');
|
||||
var assign = require('object-assign');
|
||||
|
||||
function getNotificationsStateFromStores() {
|
||||
@@ -1,11 +1,11 @@
|
||||
// Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved.
|
||||
// See License.txt for license information.
|
||||
|
||||
var SettingItemMin = require('./setting_item_min.jsx');
|
||||
var SettingItemMax = require('./setting_item_max.jsx');
|
||||
var Client = require('../utils/client.jsx');
|
||||
var AsyncClient = require('../utils/async_client.jsx');
|
||||
var Constants = require('../utils/constants.jsx');
|
||||
var SettingItemMin = require('../setting_item_min.jsx');
|
||||
var SettingItemMax = require('../setting_item_max.jsx');
|
||||
var Client = require('../../utils/client.jsx');
|
||||
var AsyncClient = require('../../utils/async_client.jsx');
|
||||
var Constants = require('../../utils/constants.jsx');
|
||||
|
||||
export default class SecurityTab extends React.Component {
|
||||
constructor(props) {
|
||||
@@ -19,7 +19,7 @@ var DeletePostModal = require('../components/delete_post_modal.jsx');
|
||||
var MoreChannelsModal = require('../components/more_channels.jsx');
|
||||
var PostDeletedModal = require('../components/post_deleted_modal.jsx');
|
||||
var ChannelNotificationsModal = require('../components/channel_notifications.jsx');
|
||||
var UserSettingsModal = require('../components/user_settings_modal.jsx');
|
||||
var UserSettingsModal = require('../components/user_settings/user_settings_modal.jsx');
|
||||
var TeamSettingsModal = require('../components/team_settings_modal.jsx');
|
||||
var ChannelMembersModal = require('../components/channel_members.jsx');
|
||||
var ChannelInviteModal = require('../components/channel_invite_modal.jsx');
|
||||
|
||||
@@ -59,7 +59,7 @@ export function createTeamFromSignup(teamSignup, success, error) {
|
||||
contentType: 'application/json',
|
||||
type: 'POST',
|
||||
data: JSON.stringify(teamSignup),
|
||||
success: success,
|
||||
success,
|
||||
error: function onError(xhr, status, err) {
|
||||
var e = handleError('createTeamFromSignup', xhr, status, err);
|
||||
error(e);
|
||||
@@ -74,7 +74,7 @@ export function createTeamWithSSO(team, service, success, error) {
|
||||
contentType: 'application/json',
|
||||
type: 'POST',
|
||||
data: JSON.stringify(team),
|
||||
success: success,
|
||||
success,
|
||||
error: function onError(xhr, status, err) {
|
||||
var e = handleError('createTeamWithSSO', xhr, status, err);
|
||||
error(e);
|
||||
@@ -89,7 +89,7 @@ export function createUser(user, data, emailHash, success, error) {
|
||||
contentType: 'application/json',
|
||||
type: 'POST',
|
||||
data: JSON.stringify(user),
|
||||
success: success,
|
||||
success,
|
||||
error: function onError(xhr, status, err) {
|
||||
var e = handleError('createUser', xhr, status, err);
|
||||
error(e);
|
||||
@@ -106,7 +106,7 @@ export function updateUser(user, success, error) {
|
||||
contentType: 'application/json',
|
||||
type: 'POST',
|
||||
data: JSON.stringify(user),
|
||||
success: success,
|
||||
success,
|
||||
error: function onError(xhr, status, err) {
|
||||
var e = handleError('updateUser', xhr, status, err);
|
||||
error(e);
|
||||
@@ -123,7 +123,7 @@ export function updatePassword(data, success, error) {
|
||||
contentType: 'application/json',
|
||||
type: 'POST',
|
||||
data: JSON.stringify(data),
|
||||
success: success,
|
||||
success,
|
||||
error: function onError(xhr, status, err) {
|
||||
var e = handleError('newPassword', xhr, status, err);
|
||||
error(e);
|
||||
@@ -140,7 +140,7 @@ export function updateUserNotifyProps(data, success, error) {
|
||||
contentType: 'application/json',
|
||||
type: 'POST',
|
||||
data: JSON.stringify(data),
|
||||
success: success,
|
||||
success,
|
||||
error: function onError(xhr, status, err) {
|
||||
var e = handleError('updateUserNotifyProps', xhr, status, err);
|
||||
error(e);
|
||||
@@ -155,7 +155,7 @@ export function updateRoles(data, success, error) {
|
||||
contentType: 'application/json',
|
||||
type: 'POST',
|
||||
data: JSON.stringify(data),
|
||||
success: success,
|
||||
success,
|
||||
error: function onError(xhr, status, err) {
|
||||
var e = handleError('updateRoles', xhr, status, err);
|
||||
error(e);
|
||||
@@ -176,7 +176,7 @@ export function updateActive(userId, active, success, error) {
|
||||
contentType: 'application/json',
|
||||
type: 'POST',
|
||||
data: JSON.stringify(data),
|
||||
success: success,
|
||||
success,
|
||||
error: function onError(xhr, status, err) {
|
||||
var e = handleError('updateActive', xhr, status, err);
|
||||
error(e);
|
||||
@@ -193,7 +193,7 @@ export function sendPasswordReset(data, success, error) {
|
||||
contentType: 'application/json',
|
||||
type: 'POST',
|
||||
data: JSON.stringify(data),
|
||||
success: success,
|
||||
success,
|
||||
error: function onError(xhr, status, err) {
|
||||
var e = handleError('sendPasswordReset', xhr, status, err);
|
||||
error(e);
|
||||
@@ -210,7 +210,7 @@ export function resetPassword(data, success, error) {
|
||||
contentType: 'application/json',
|
||||
type: 'POST',
|
||||
data: JSON.stringify(data),
|
||||
success: success,
|
||||
success,
|
||||
error: function onError(xhr, status, err) {
|
||||
var e = handleError('resetPassword', xhr, status, err);
|
||||
error(e);
|
||||
@@ -254,7 +254,7 @@ export function revokeSession(altId, success, error) {
|
||||
contentType: 'application/json',
|
||||
type: 'POST',
|
||||
data: JSON.stringify({id: altId}),
|
||||
success: success,
|
||||
success,
|
||||
error: function onError(xhr, status, err) {
|
||||
var e = handleError('revokeSession', xhr, status, err);
|
||||
error(e);
|
||||
@@ -269,7 +269,7 @@ export function getSessions(userId, success, error) {
|
||||
dataType: 'json',
|
||||
contentType: 'application/json',
|
||||
type: 'GET',
|
||||
success: success,
|
||||
success,
|
||||
error: function onError(xhr, status, err) {
|
||||
var e = handleError('getSessions', xhr, status, err);
|
||||
error(e);
|
||||
@@ -283,7 +283,7 @@ export function getAudits(userId, success, error) {
|
||||
dataType: 'json',
|
||||
contentType: 'application/json',
|
||||
type: 'GET',
|
||||
success: success,
|
||||
success,
|
||||
error: function onError(xhr, status, err) {
|
||||
var e = handleError('getAudits', xhr, status, err);
|
||||
error(e);
|
||||
@@ -367,7 +367,7 @@ export function inviteMembers(data, success, error) {
|
||||
contentType: 'application/json',
|
||||
type: 'POST',
|
||||
data: JSON.stringify(data),
|
||||
success: success,
|
||||
success,
|
||||
error: function onError(xhr, status, err) {
|
||||
var e = handleError('inviteMembers', xhr, status, err);
|
||||
error(e);
|
||||
@@ -384,7 +384,7 @@ export function updateTeamDisplayName(data, success, error) {
|
||||
contentType: 'application/json',
|
||||
type: 'POST',
|
||||
data: JSON.stringify(data),
|
||||
success: success,
|
||||
success,
|
||||
error: function onError(xhr, status, err) {
|
||||
var e = handleError('updateTeamDisplayName', xhr, status, err);
|
||||
error(e);
|
||||
@@ -401,7 +401,7 @@ export function signupTeam(email, success, error) {
|
||||
contentType: 'application/json',
|
||||
type: 'POST',
|
||||
data: JSON.stringify({email: email}),
|
||||
success: success,
|
||||
success,
|
||||
error: function onError(xhr, status, err) {
|
||||
var e = handleError('singupTeam', xhr, status, err);
|
||||
error(e);
|
||||
@@ -418,7 +418,7 @@ export function createTeam(team, success, error) {
|
||||
contentType: 'application/json',
|
||||
type: 'POST',
|
||||
data: JSON.stringify(team),
|
||||
success: success,
|
||||
success,
|
||||
error: function onError(xhr, status, err) {
|
||||
var e = handleError('createTeam', xhr, status, err);
|
||||
error(e);
|
||||
@@ -433,7 +433,7 @@ export function findTeamByName(teamName, success, error) {
|
||||
contentType: 'application/json',
|
||||
type: 'POST',
|
||||
data: JSON.stringify({name: teamName}),
|
||||
success: success,
|
||||
success,
|
||||
error: function onError(xhr, status, err) {
|
||||
var e = handleError('findTeamByName', xhr, status, err);
|
||||
error(e);
|
||||
@@ -448,7 +448,7 @@ export function findTeamsSendEmail(email, success, error) {
|
||||
contentType: 'application/json',
|
||||
type: 'POST',
|
||||
data: JSON.stringify({email: email}),
|
||||
success: success,
|
||||
success,
|
||||
error: function onError(xhr, status, err) {
|
||||
var e = handleError('findTeamsSendEmail', xhr, status, err);
|
||||
error(e);
|
||||
@@ -465,7 +465,7 @@ export function findTeams(email, success, error) {
|
||||
contentType: 'application/json',
|
||||
type: 'POST',
|
||||
data: JSON.stringify({email: email}),
|
||||
success: success,
|
||||
success,
|
||||
error: function onError(xhr, status, err) {
|
||||
var e = handleError('findTeams', xhr, status, err);
|
||||
error(e);
|
||||
@@ -480,7 +480,7 @@ export function createChannel(channel, success, error) {
|
||||
contentType: 'application/json',
|
||||
type: 'POST',
|
||||
data: JSON.stringify(channel),
|
||||
success: success,
|
||||
success,
|
||||
error: function onError(xhr, status, err) {
|
||||
var e = handleError('createChannel', xhr, status, err);
|
||||
error(e);
|
||||
@@ -497,7 +497,7 @@ export function createDirectChannel(channel, userId, success, error) {
|
||||
contentType: 'application/json',
|
||||
type: 'POST',
|
||||
data: JSON.stringify({user_id: userId}),
|
||||
success: success,
|
||||
success,
|
||||
error: function onError(xhr, status, err) {
|
||||
var e = handleError('createDirectChannel', xhr, status, err);
|
||||
error(e);
|
||||
@@ -514,7 +514,7 @@ export function updateChannel(channel, success, error) {
|
||||
contentType: 'application/json',
|
||||
type: 'POST',
|
||||
data: JSON.stringify(channel),
|
||||
success: success,
|
||||
success,
|
||||
error: function onError(xhr, status, err) {
|
||||
var e = handleError('updateChannel', xhr, status, err);
|
||||
error(e);
|
||||
@@ -531,7 +531,7 @@ export function updateChannelDesc(data, success, error) {
|
||||
contentType: 'application/json',
|
||||
type: 'POST',
|
||||
data: JSON.stringify(data),
|
||||
success: success,
|
||||
success,
|
||||
error: function onError(xhr, status, err) {
|
||||
var e = handleError('updateChannelDesc', xhr, status, err);
|
||||
error(e);
|
||||
@@ -548,7 +548,7 @@ export function updateNotifyLevel(data, success, error) {
|
||||
contentType: 'application/json',
|
||||
type: 'POST',
|
||||
data: JSON.stringify(data),
|
||||
success: success,
|
||||
success,
|
||||
error: function onError(xhr, status, err) {
|
||||
var e = handleError('updateNotifyLevel', xhr, status, err);
|
||||
error(e);
|
||||
@@ -562,7 +562,7 @@ export function joinChannel(id, success, error) {
|
||||
dataType: 'json',
|
||||
contentType: 'application/json',
|
||||
type: 'POST',
|
||||
success: success,
|
||||
success,
|
||||
error: function onError(xhr, status, err) {
|
||||
var e = handleError('joinChannel', xhr, status, err);
|
||||
error(e);
|
||||
@@ -578,7 +578,7 @@ export function leaveChannel(id, success, error) {
|
||||
dataType: 'json',
|
||||
contentType: 'application/json',
|
||||
type: 'POST',
|
||||
success: success,
|
||||
success,
|
||||
error: function onError(xhr, status, err) {
|
||||
var e = handleError('leaveChannel', xhr, status, err);
|
||||
error(e);
|
||||
@@ -594,7 +594,7 @@ export function deleteChannel(id, success, error) {
|
||||
dataType: 'json',
|
||||
contentType: 'application/json',
|
||||
type: 'POST',
|
||||
success: success,
|
||||
success,
|
||||
error: function onError(xhr, status, err) {
|
||||
var e = handleError('deleteChannel', xhr, status, err);
|
||||
error(e);
|
||||
@@ -610,7 +610,7 @@ export function updateLastViewedAt(channelId, success, error) {
|
||||
dataType: 'json',
|
||||
contentType: 'application/json',
|
||||
type: 'POST',
|
||||
success: success,
|
||||
success,
|
||||
error: function onError(xhr, status, err) {
|
||||
var e = handleError('updateLastViewedAt', xhr, status, err);
|
||||
error(e);
|
||||
@@ -624,7 +624,7 @@ export function getChannels(success, error) {
|
||||
url: '/api/v1/channels/',
|
||||
dataType: 'json',
|
||||
type: 'GET',
|
||||
success: success,
|
||||
success,
|
||||
ifModified: true,
|
||||
error: function onError(xhr, status, err) {
|
||||
var e = handleError('getChannels', xhr, status, err);
|
||||
@@ -639,7 +639,7 @@ export function getChannel(id, success, error) {
|
||||
url: '/api/v1/channels/' + id + '/',
|
||||
dataType: 'json',
|
||||
type: 'GET',
|
||||
success: success,
|
||||
success,
|
||||
error: function onError(xhr, status, err) {
|
||||
var e = handleError('getChannel', xhr, status, err);
|
||||
error(e);
|
||||
@@ -654,7 +654,7 @@ export function getMoreChannels(success, error) {
|
||||
url: '/api/v1/channels/more',
|
||||
dataType: 'json',
|
||||
type: 'GET',
|
||||
success: success,
|
||||
success,
|
||||
ifModified: true,
|
||||
error: function onError(xhr, status, err) {
|
||||
var e = handleError('getMoreChannels', xhr, status, err);
|
||||
@@ -669,7 +669,7 @@ export function getChannelCounts(success, error) {
|
||||
url: '/api/v1/channels/counts',
|
||||
dataType: 'json',
|
||||
type: 'GET',
|
||||
success: success,
|
||||
success,
|
||||
ifModified: true,
|
||||
error: function onError(xhr, status, err) {
|
||||
var e = handleError('getChannelCounts', xhr, status, err);
|
||||
@@ -683,7 +683,7 @@ export function getChannelExtraInfo(id, success, error) {
|
||||
url: '/api/v1/channels/' + id + '/extra_info',
|
||||
dataType: 'json',
|
||||
type: 'GET',
|
||||
success: success,
|
||||
success,
|
||||
error: function onError(xhr, status, err) {
|
||||
var e = handleError('getChannelExtraInfo', xhr, status, err);
|
||||
error(e);
|
||||
@@ -698,7 +698,7 @@ export function executeCommand(channelId, command, suggest, success, error) {
|
||||
contentType: 'application/json',
|
||||
type: 'POST',
|
||||
data: JSON.stringify({channelId: channelId, command: command, suggest: '' + suggest}),
|
||||
success: success,
|
||||
success,
|
||||
error: function onError(xhr, status, err) {
|
||||
var e = handleError('executeCommand', xhr, status, err);
|
||||
error(e);
|
||||
@@ -713,7 +713,7 @@ export function getPostsPage(channelId, offset, limit, success, error, complete)
|
||||
dataType: 'json',
|
||||
type: 'GET',
|
||||
ifModified: true,
|
||||
success: success,
|
||||
success,
|
||||
error: function onError(xhr, status, err) {
|
||||
var e = handleError('getPosts', xhr, status, err);
|
||||
error(e);
|
||||
@@ -728,7 +728,7 @@ export function getPosts(channelId, since, success, error, complete) {
|
||||
dataType: 'json',
|
||||
type: 'GET',
|
||||
ifModified: true,
|
||||
success: success,
|
||||
success,
|
||||
error: function onError(xhr, status, err) {
|
||||
var e = handleError('getPosts', xhr, status, err);
|
||||
error(e);
|
||||
@@ -744,7 +744,7 @@ export function getPost(channelId, postId, success, error) {
|
||||
dataType: 'json',
|
||||
type: 'GET',
|
||||
ifModified: false,
|
||||
success: success,
|
||||
success,
|
||||
error: function onError(xhr, status, err) {
|
||||
var e = handleError('getPost', xhr, status, err);
|
||||
error(e);
|
||||
@@ -758,7 +758,7 @@ export function search(terms, success, error) {
|
||||
dataType: 'json',
|
||||
type: 'GET',
|
||||
data: {terms: terms},
|
||||
success: success,
|
||||
success,
|
||||
error: function onError(xhr, status, err) {
|
||||
var e = handleError('search', xhr, status, err);
|
||||
error(e);
|
||||
@@ -774,7 +774,7 @@ export function deletePost(channelId, id, success, error) {
|
||||
dataType: 'json',
|
||||
contentType: 'application/json',
|
||||
type: 'POST',
|
||||
success: success,
|
||||
success,
|
||||
error: function onError(xhr, status, err) {
|
||||
var e = handleError('deletePost', xhr, status, err);
|
||||
error(e);
|
||||
@@ -791,7 +791,7 @@ export function createPost(post, channel, success, error) {
|
||||
contentType: 'application/json',
|
||||
type: 'POST',
|
||||
data: JSON.stringify(post),
|
||||
success: success,
|
||||
success,
|
||||
error: function onError(xhr, status, err) {
|
||||
var e = handleError('createPost', xhr, status, err);
|
||||
error(e);
|
||||
@@ -817,7 +817,7 @@ export function updatePost(post, success, error) {
|
||||
contentType: 'application/json',
|
||||
type: 'POST',
|
||||
data: JSON.stringify(post),
|
||||
success: success,
|
||||
success,
|
||||
error: function onError(xhr, status, err) {
|
||||
var e = handleError('updatePost', xhr, status, err);
|
||||
error(e);
|
||||
@@ -834,7 +834,7 @@ export function addChannelMember(id, data, success, error) {
|
||||
contentType: 'application/json',
|
||||
type: 'POST',
|
||||
data: JSON.stringify(data),
|
||||
success: success,
|
||||
success,
|
||||
error: function onError(xhr, status, err) {
|
||||
var e = handleError('addChannelMember', xhr, status, err);
|
||||
error(e);
|
||||
@@ -851,7 +851,7 @@ export function removeChannelMember(id, data, success, error) {
|
||||
contentType: 'application/json',
|
||||
type: 'POST',
|
||||
data: JSON.stringify(data),
|
||||
success: success,
|
||||
success,
|
||||
error: function onError(xhr, status, err) {
|
||||
var e = handleError('removeChannelMember', xhr, status, err);
|
||||
error(e);
|
||||
@@ -868,7 +868,7 @@ export function getProfiles(success, error) {
|
||||
dataType: 'json',
|
||||
contentType: 'application/json',
|
||||
type: 'GET',
|
||||
success: success,
|
||||
success,
|
||||
ifModified: true,
|
||||
error: function onError(xhr, status, err) {
|
||||
var e = handleError('getProfiles', xhr, status, err);
|
||||
@@ -885,7 +885,7 @@ export function uploadFile(formData, success, error) {
|
||||
cache: false,
|
||||
contentType: false,
|
||||
processData: false,
|
||||
success: success,
|
||||
success,
|
||||
error: function onError(xhr, status, err) {
|
||||
if (err !== 'abort') {
|
||||
var e = handleError('uploadFile', xhr, status, err);
|
||||
@@ -905,7 +905,7 @@ export function getFileInfo(filename, success, error) {
|
||||
dataType: 'json',
|
||||
contentType: 'application/json',
|
||||
type: 'GET',
|
||||
success: success,
|
||||
success,
|
||||
error: function onError(xhr, status, err) {
|
||||
var e = handleError('getFileInfo', xhr, status, err);
|
||||
error(e);
|
||||
@@ -919,7 +919,7 @@ export function getPublicLink(data, success, error) {
|
||||
dataType: 'json',
|
||||
type: 'POST',
|
||||
data: JSON.stringify(data),
|
||||
success: success,
|
||||
success,
|
||||
error: function onError(xhr, status, err) {
|
||||
var e = handleError('getPublicLink', xhr, status, err);
|
||||
error(e);
|
||||
@@ -935,7 +935,7 @@ export function uploadProfileImage(imageData, success, error) {
|
||||
cache: false,
|
||||
contentType: false,
|
||||
processData: false,
|
||||
success: success,
|
||||
success,
|
||||
error: function onError(xhr, status, err) {
|
||||
var e = handleError('uploadProfileImage', xhr, status, err);
|
||||
error(e);
|
||||
@@ -951,7 +951,7 @@ export function importSlack(fileData, success, error) {
|
||||
cache: false,
|
||||
contentType: false,
|
||||
processData: false,
|
||||
success: success,
|
||||
success,
|
||||
error: function onError(xhr, status, err) {
|
||||
var e = handleError('importTeam', xhr, status, err);
|
||||
error(e);
|
||||
@@ -964,7 +964,7 @@ export function exportTeam(success, error) {
|
||||
url: '/api/v1/teams/export_team',
|
||||
type: 'GET',
|
||||
dataType: 'json',
|
||||
success: success,
|
||||
success,
|
||||
error: function onError(xhr, status, err) {
|
||||
var e = handleError('exportTeam', xhr, status, err);
|
||||
error(e);
|
||||
@@ -978,7 +978,7 @@ export function getStatuses(success, error) {
|
||||
dataType: 'json',
|
||||
contentType: 'application/json',
|
||||
type: 'GET',
|
||||
success: success,
|
||||
success,
|
||||
error: function onError(xhr, status, err) {
|
||||
var e = handleError('getStatuses', xhr, status, err);
|
||||
error(e);
|
||||
@@ -991,7 +991,7 @@ export function getMyTeam(success, error) {
|
||||
url: '/api/v1/teams/me',
|
||||
dataType: 'json',
|
||||
type: 'GET',
|
||||
success: success,
|
||||
success,
|
||||
ifModified: true,
|
||||
error: function onError(xhr, status, err) {
|
||||
var e = handleError('getMyTeam', xhr, status, err);
|
||||
@@ -1007,7 +1007,7 @@ export function updateValetFeature(data, success, error) {
|
||||
contentType: 'application/json',
|
||||
type: 'POST',
|
||||
data: JSON.stringify(data),
|
||||
success: success,
|
||||
success,
|
||||
error: function onError(xhr, status, err) {
|
||||
var e = handleError('updateValetFeature', xhr, status, err);
|
||||
error(e);
|
||||
@@ -1040,7 +1040,7 @@ export function allowOAuth2(responseType, clientId, redirectUri, state, scope, s
|
||||
dataType: 'json',
|
||||
contentType: 'application/json',
|
||||
type: 'GET',
|
||||
success: success,
|
||||
success,
|
||||
error: (xhr, status, err) => {
|
||||
const e = handleError('allowOAuth2', xhr, status, err);
|
||||
error(e);
|
||||
@@ -1049,3 +1049,46 @@ export function allowOAuth2(responseType, clientId, redirectUri, state, scope, s
|
||||
|
||||
module.exports.track('api', 'api_users_allow_oauth2');
|
||||
}
|
||||
|
||||
export function addIncomingHook(hook, success, error) {
|
||||
$.ajax({
|
||||
url: '/api/v1/hooks/incoming/create',
|
||||
dataType: 'json',
|
||||
contentType: 'application/json',
|
||||
type: 'POST',
|
||||
data: JSON.stringify(hook),
|
||||
success,
|
||||
error: (xhr, status, err) => {
|
||||
var e = handleError('addIncomingHook', xhr, status, err);
|
||||
error(e);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export function deleteIncomingHook(data, success, error) {
|
||||
$.ajax({
|
||||
url: '/api/v1/hooks/incoming/delete',
|
||||
dataType: 'json',
|
||||
contentType: 'application/json',
|
||||
type: 'POST',
|
||||
data: JSON.stringify(data),
|
||||
success,
|
||||
error: (xhr, status, err) => {
|
||||
var e = handleError('deleteIncomingHook', xhr, status, err);
|
||||
error(e);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export function listIncomingHooks(success, error) {
|
||||
$.ajax({
|
||||
url: '/api/v1/hooks/incoming/list',
|
||||
dataType: 'json',
|
||||
type: 'GET',
|
||||
success,
|
||||
error: (xhr, status, err) => {
|
||||
var e = handleError('listIncomingHooks', xhr, status, err);
|
||||
error(e);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
85
web/web.go
85
web/web.go
@@ -9,11 +9,13 @@ import (
|
||||
"github.com/gorilla/mux"
|
||||
"github.com/mattermost/platform/api"
|
||||
"github.com/mattermost/platform/model"
|
||||
"github.com/mattermost/platform/store"
|
||||
"github.com/mattermost/platform/utils"
|
||||
"github.com/mssola/user_agent"
|
||||
"gopkg.in/fsnotify.v1"
|
||||
"html/template"
|
||||
"net/http"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
@@ -63,6 +65,8 @@ func InitWeb() {
|
||||
|
||||
mainrouter.Handle("/admin_console", api.UserRequired(adminConsole)).Methods("GET")
|
||||
|
||||
mainrouter.Handle("/hooks/{id:[A-Za-z0-9]+}", api.ApiAppHandler(incomingWebhook)).Methods("POST")
|
||||
|
||||
// ----------------------------------------------------------------------------------------------
|
||||
// *ANYTHING* team specific should go below this line
|
||||
// ----------------------------------------------------------------------------------------------
|
||||
@@ -838,3 +842,84 @@ func getAccessToken(c *api.Context, w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
w.Write([]byte(accessRsp.ToJson()))
|
||||
}
|
||||
|
||||
func incomingWebhook(c *api.Context, w http.ResponseWriter, r *http.Request) {
|
||||
params := mux.Vars(r)
|
||||
id := params["id"]
|
||||
|
||||
hchan := api.Srv.Store.Webhook().GetIncoming(id)
|
||||
|
||||
r.ParseForm()
|
||||
|
||||
props := model.MapFromJson(strings.NewReader(r.FormValue("payload")))
|
||||
|
||||
text := props["text"]
|
||||
if len(text) == 0 {
|
||||
c.Err = model.NewAppError("incomingWebhook", "No text specified", "")
|
||||
return
|
||||
}
|
||||
|
||||
channelName := props["channel"]
|
||||
|
||||
var hook *model.IncomingWebhook
|
||||
if result := <-hchan; result.Err != nil {
|
||||
c.Err = model.NewAppError("incomingWebhook", "Invalid webhook", "err="+result.Err.Message)
|
||||
return
|
||||
} else {
|
||||
hook = result.Data.(*model.IncomingWebhook)
|
||||
}
|
||||
|
||||
var channel *model.Channel
|
||||
var cchan store.StoreChannel
|
||||
|
||||
if len(channelName) != 0 {
|
||||
if channelName[0] == '@' {
|
||||
if result := <-api.Srv.Store.User().GetByUsername(hook.TeamId, channelName[1:]); result.Err != nil {
|
||||
c.Err = model.NewAppError("incomingWebhook", "Couldn't find the user", "err="+result.Err.Message)
|
||||
return
|
||||
} else {
|
||||
channelName = model.GetDMNameFromIds(result.Data.(*model.User).Id, hook.UserId)
|
||||
}
|
||||
} else if channelName[0] == '#' {
|
||||
channelName = channelName[1:]
|
||||
}
|
||||
|
||||
cchan = api.Srv.Store.Channel().GetByName(hook.TeamId, channelName)
|
||||
} else {
|
||||
cchan = api.Srv.Store.Channel().Get(hook.ChannelId)
|
||||
}
|
||||
|
||||
// parse links into Markdown format
|
||||
linkWithTextRegex := regexp.MustCompile(`<([^<\|]+)\|([^>]+)>`)
|
||||
text = linkWithTextRegex.ReplaceAllString(text, "[${2}](${1})")
|
||||
|
||||
linkRegex := regexp.MustCompile(`<\s*(\S*)\s*>`)
|
||||
text = linkRegex.ReplaceAllString(text, "${1}")
|
||||
|
||||
if result := <-cchan; result.Err != nil {
|
||||
c.Err = model.NewAppError("incomingWebhook", "Couldn't find the channel", "err="+result.Err.Message)
|
||||
return
|
||||
} else {
|
||||
channel = result.Data.(*model.Channel)
|
||||
}
|
||||
|
||||
pchan := api.Srv.Store.Channel().CheckPermissionsTo(hook.TeamId, channel.Id, hook.UserId)
|
||||
|
||||
post := &model.Post{UserId: hook.UserId, ChannelId: channel.Id, Message: text}
|
||||
|
||||
if !c.HasPermissionsToChannel(pchan, "createIncomingHook") && channel.Type != model.CHANNEL_OPEN {
|
||||
c.Err = model.NewAppError("incomingWebhook", "Inappropriate channel permissions", "")
|
||||
return
|
||||
}
|
||||
|
||||
// create a mock session
|
||||
c.Session = model.Session{UserId: hook.UserId, TeamId: hook.TeamId, IsOAuth: false}
|
||||
|
||||
if _, err := api.CreatePost(c, post, false); err != nil {
|
||||
c.Err = model.NewAppError("incomingWebhook", "Error creating post", "err="+err.Message)
|
||||
return
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", "text/plain")
|
||||
w.Write([]byte("ok"))
|
||||
}
|
||||
|
||||
@@ -180,6 +180,51 @@ func TestGetAccessToken(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestIncomingWebhook(t *testing.T) {
|
||||
Setup()
|
||||
|
||||
team := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN}
|
||||
team = ApiClient.Must(ApiClient.CreateTeam(team)).Data.(*model.Team)
|
||||
|
||||
user := &model.User{TeamId: team.Id, Email: model.NewId() + "corey@test.com", Nickname: "Corey Hulen", Password: "pwd"}
|
||||
user = ApiClient.Must(ApiClient.CreateUser(user, "")).Data.(*model.User)
|
||||
store.Must(api.Srv.Store.User().VerifyEmail(user.Id))
|
||||
|
||||
ApiClient.LoginByEmail(team.Name, user.Email, "pwd")
|
||||
|
||||
channel1 := &model.Channel{DisplayName: "Test API Name", Name: "a" + model.NewId() + "a", Type: model.CHANNEL_OPEN, TeamId: team.Id}
|
||||
channel1 = ApiClient.Must(ApiClient.CreateChannel(channel1)).Data.(*model.Channel)
|
||||
|
||||
if utils.Cfg.ServiceSettings.AllowIncomingWebhooks {
|
||||
hook1 := &model.IncomingWebhook{ChannelId: channel1.Id}
|
||||
hook1 = ApiClient.Must(ApiClient.CreateIncomingWebhook(hook1)).Data.(*model.IncomingWebhook)
|
||||
|
||||
payload := "payload={\"text\": \"test text\"}"
|
||||
if _, err := ApiClient.PostToWebhook(hook1.Id, payload); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
payload = "payload={\"text\": \"\"}"
|
||||
if _, err := ApiClient.PostToWebhook(hook1.Id, payload); err == nil {
|
||||
t.Fatal("should have errored - no text to post")
|
||||
}
|
||||
|
||||
payload = "payload={\"text\": \"test text\", \"channel\": \"junk\"}"
|
||||
if _, err := ApiClient.PostToWebhook(hook1.Id, payload); err == nil {
|
||||
t.Fatal("should have errored - bad channel")
|
||||
}
|
||||
|
||||
payload = "payload={\"text\": \"test text\"}"
|
||||
if _, err := ApiClient.PostToWebhook("abc123", payload); err == nil {
|
||||
t.Fatal("should have errored - bad hook")
|
||||
}
|
||||
} else {
|
||||
if _, err := ApiClient.PostToWebhook("123", "123"); err == nil {
|
||||
t.Fatal("should have failed - webhooks turned off")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestZZWebTearDown(t *testing.T) {
|
||||
// *IMPORTANT*
|
||||
// This should be the last function in any test file
|
||||
|
||||
Ссылка в новой задаче
Block a user