Skip to content

Commit

Permalink
Implement incoming webhooks.
Browse files Browse the repository at this point in the history
  • Loading branch information
jwilander committed Sep 18, 2015
1 parent 5a436fd commit 435ae5a
Show file tree
Hide file tree
Showing 32 changed files with 1,325 additions and 117 deletions.
1 change: 1 addition & 0 deletions api/api.go
Expand Up @@ -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)
Expand Down
26 changes: 13 additions & 13 deletions api/channel.go
Expand Up @@ -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 = ""
Expand Down Expand Up @@ -385,8 +381,9 @@ func JoinChannel(c *Context, channelId string, role string) {

post := &model.Post{ChannelId: channel.Id, Message: fmt.Sprintf(
`User %v has joined this channel.`,
user.Username), Type: model.POST_JOIN_LEAVE}
if _, err := CreatePost(c, post, false); err != nil {
user.Username), Type: model.POST_JOIN_LEAVE,
UserId: c.Session.UserId}
if _, err := CreatePost(c, post, c.Session.TeamId, false); err != nil {
l4g.Error("Failed to post join message %v", err)
c.Err = model.NewAppError("joinChannel", "Failed to send join request", "")
return
Expand Down Expand Up @@ -474,8 +471,9 @@ func leaveChannel(c *Context, w http.ResponseWriter, r *http.Request) {

post := &model.Post{ChannelId: channel.Id, Message: fmt.Sprintf(
`%v has left the channel.`,
user.Username), Type: model.POST_JOIN_LEAVE}
if _, err := CreatePost(c, post, false); err != nil {
user.Username), Type: model.POST_JOIN_LEAVE,
UserId: c.Session.UserId}
if _, err := CreatePost(c, post, c.Session.TeamId, false); err != nil {
l4g.Error("Failed to post leave message %v", err)
c.Err = model.NewAppError("leaveChannel", "Failed to send leave message", "")
return
Expand Down Expand Up @@ -541,8 +539,9 @@ func deleteChannel(c *Context, w http.ResponseWriter, r *http.Request) {

post := &model.Post{ChannelId: channel.Id, Message: fmt.Sprintf(
`%v has archived the channel.`,
user.Username)}
if _, err := CreatePost(c, post, false); err != nil {
user.Username),
UserId: c.Session.UserId}
if _, err := CreatePost(c, post, c.Session.TeamId, false); err != nil {
l4g.Error("Failed to post archive message %v", err)
c.Err = model.NewAppError("deleteChannel", "Failed to send archive message", "")
return
Expand Down Expand Up @@ -708,8 +707,9 @@ func addChannelMember(c *Context, w http.ResponseWriter, r *http.Request) {

post := &model.Post{ChannelId: id, Message: fmt.Sprintf(
`%v added to the channel by %v`,
nUser.Username, oUser.Username), Type: model.POST_JOIN_LEAVE}
if _, err := CreatePost(c, post, false); err != nil {
nUser.Username, oUser.Username), Type: model.POST_JOIN_LEAVE,
UserId: c.Session.UserId}
if _, err := CreatePost(c, post, c.Session.TeamId, false); err != nil {
l4g.Error("Failed to post add message %v", err)
c.Err = model.NewAppError("addChannelMember", "Failed to add member to channel", "")
return
Expand Down
3 changes: 2 additions & 1 deletion api/command.go
Expand Up @@ -141,11 +141,12 @@ func echoCommand(c *Context, command *model.Command) bool {
defer func() { <-echoSem }()
post := &model.Post{}
post.ChannelId = command.ChannelId
post.UserId = c.Session.UserId
post.Message = message

time.Sleep(time.Duration(delay) * time.Second)

if _, err := CreatePost(c, post, false); err != nil {
if _, err := CreatePost(c, post, c.Session.TeamId, false); err != nil {
l4g.Error("Unable to create /echo post, err=%v", err)
}
}()
Expand Down
12 changes: 6 additions & 6 deletions api/post.go
Expand Up @@ -47,7 +47,9 @@ func createPost(c *Context, w http.ResponseWriter, r *http.Request) {
return
}

if rp, err := CreatePost(c, post, true); err != nil {
post.UserId = c.Session.UserId

if rp, err := CreatePost(c, post, c.Session.TeamId, true); err != nil {
c.Err = err

if strings.Contains(c.Err.Message, "parameter") {
Expand Down Expand Up @@ -129,7 +131,7 @@ func CreateValetPost(c *Context, post *model.Post) (*model.Post, *model.AppError
return rpost, nil
}

func CreatePost(c *Context, post *model.Post, doUpdateLastViewed bool) (*model.Post, *model.AppError) {
func CreatePost(c *Context, post *model.Post, teamId string, doUpdateLastViewed bool) (*model.Post, *model.AppError) {
var pchan store.StoreChannel
if len(post.RootId) > 0 {
pchan = Srv.Store.Post().Get(post.RootId)
Expand Down Expand Up @@ -160,8 +162,6 @@ func CreatePost(c *Context, post *model.Post, doUpdateLastViewed bool) (*model.P

post.Hashtags, _ = model.ParseHashtags(post.Message)

post.UserId = c.Session.UserId

if len(post.Filenames) > 0 {
doRemove := false
for i := len(post.Filenames) - 1; i >= 0; i-- {
Expand Down Expand Up @@ -198,12 +198,12 @@ func CreatePost(c *Context, post *model.Post, doUpdateLastViewed bool) (*model.P
var rpost *model.Post
if result := <-Srv.Store.Post().Save(post); result.Err != nil {
return nil, result.Err
} else if doUpdateLastViewed && (<-Srv.Store.Channel().UpdateLastViewedAt(post.ChannelId, c.Session.UserId)).Err != nil {
} else if doUpdateLastViewed && (<-Srv.Store.Channel().UpdateLastViewedAt(post.ChannelId, post.UserId)).Err != nil {
return nil, result.Err
} else {
rpost = result.Data.(*model.Post)

fireAndForgetNotifications(rpost, c.Session.TeamId, c.GetSiteURL())
fireAndForgetNotifications(rpost, teamId, c.GetSiteURL())

}

Expand Down
9 changes: 0 additions & 9 deletions api/web_socket_test.go
Expand Up @@ -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()
}
115 changes: 115 additions & 0 deletions api/webhook.go
@@ -0,0 +1,115 @@
// 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
}

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.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
}

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)))
}
}

0 comments on commit 435ae5a

Please sign in to comment.