add(messaging): webhooks implementation
This commit is contained in:
@@ -0,0 +1,124 @@
|
||||
package repository
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/titpetric/factory"
|
||||
|
||||
"github.com/crusttech/crust/messaging/types"
|
||||
)
|
||||
|
||||
type (
|
||||
WebhookRepository interface {
|
||||
With(ctx context.Context, db *factory.DB) WebhookRepository
|
||||
|
||||
Create(*types.Webhook) (*types.Webhook, error)
|
||||
Update(*types.Webhook) (*types.Webhook, error)
|
||||
|
||||
Get(webhookID uint64) (*types.Webhook, error)
|
||||
GetByToken(webhookID uint64, webhookToken string) (*types.Webhook, error)
|
||||
|
||||
Find(filter *types.WebhookFilter) (types.WebhookSet, error)
|
||||
|
||||
Delete(webhookID uint64) error
|
||||
DeleteByToken(webhookID uint64, webhookToken string) error
|
||||
}
|
||||
|
||||
webhook struct {
|
||||
webhook string
|
||||
|
||||
*repository
|
||||
}
|
||||
)
|
||||
|
||||
func Webhook(ctx context.Context, db *factory.DB) WebhookRepository {
|
||||
return (&webhook{}).With(ctx, db)
|
||||
}
|
||||
|
||||
func (r *webhook) With(ctx context.Context, db *factory.DB) WebhookRepository {
|
||||
return &webhook{
|
||||
webhook: "messaging_webhook",
|
||||
repository: r.repository.With(ctx, db),
|
||||
}
|
||||
}
|
||||
|
||||
func (r *webhook) Create(webhook *types.Webhook) (*types.Webhook, error) {
|
||||
webhook.ID = factory.Sonyflake.NextID()
|
||||
webhook.CreatedAt = time.Now()
|
||||
|
||||
return webhook, errors.WithStack(r.db().Insert(r.webhook, webhook))
|
||||
}
|
||||
|
||||
func (r *webhook) Update(webhook *types.Webhook) (*types.Webhook, error) {
|
||||
webhook.UpdatedAt = timeNowPtr()
|
||||
|
||||
return webhook, errors.WithStack(r.db().Replace(r.webhook, webhook))
|
||||
}
|
||||
|
||||
func (r *webhook) Get(webhookID uint64) (*types.Webhook, error) {
|
||||
hook := &types.Webhook{}
|
||||
if err := r.db().Get(hook, "select * from "+r.webhook+" where id=?", webhookID); err != nil {
|
||||
return nil, errors.WithStack(err)
|
||||
}
|
||||
return hook, nil
|
||||
}
|
||||
|
||||
func (r *webhook) GetByToken(webhookID uint64, webhookToken string) (*types.Webhook, error) {
|
||||
webhook, err := r.Get(webhookID)
|
||||
switch {
|
||||
case err != nil:
|
||||
return nil, errors.WithStack(err)
|
||||
case webhook.AuthToken == webhookToken:
|
||||
return webhook, nil
|
||||
default:
|
||||
return nil, errors.New("Invalid Webhook Token")
|
||||
}
|
||||
}
|
||||
|
||||
// Find webhooks based on filter
|
||||
//
|
||||
// If ChannelID > 0 it returns webhooks created on a specific channel
|
||||
// If OwnerUserID > 0 it returns webhooks owned by a specific user
|
||||
func (r *webhook) Find(filter *types.WebhookFilter) (types.WebhookSet, error) {
|
||||
params := make([]interface{}, 0)
|
||||
vv := types.WebhookSet{}
|
||||
sql := "select * from messaging_webhook where 1=1"
|
||||
|
||||
if filter != nil {
|
||||
if filter.OwnerUserID > 0 {
|
||||
// scope: only channel we have access to
|
||||
sql += " AND rel_owner=?"
|
||||
params = append(params, filter.OwnerUserID)
|
||||
}
|
||||
if filter.OutgoingTrigger != "" {
|
||||
// scope: only channel we have access to
|
||||
sql += " AND outgoing_trigger=?"
|
||||
params = append(params, filter.OutgoingTrigger)
|
||||
}
|
||||
if filter.ChannelID > 0 {
|
||||
// scope: only channel we have access to
|
||||
sql += " AND rel_channel=?"
|
||||
params = append(params, filter.ChannelID)
|
||||
}
|
||||
}
|
||||
|
||||
return vv, errors.WithStack(r.db().Select(&vv, sql, params...))
|
||||
}
|
||||
|
||||
func (r *webhook) Delete(webhookID uint64) error {
|
||||
if _, err := r.Get(webhookID); err != nil {
|
||||
return err
|
||||
}
|
||||
_, err := r.db().Exec("delete from "+r.webhook+" where id=?", webhookID)
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
func (r *webhook) DeleteByToken(webhookID uint64, webhookToken string) error {
|
||||
if _, err := r.GetByToken(webhookID, webhookToken); err != nil {
|
||||
return err
|
||||
}
|
||||
_, err := r.db().Exec("delete from "+r.webhook+" where id=?", webhookID)
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/crusttech/crust/messaging/types"
|
||||
)
|
||||
|
||||
type (
|
||||
command struct {
|
||||
ctx context.Context
|
||||
}
|
||||
|
||||
CommandService interface {
|
||||
With(context.Context) CommandService
|
||||
|
||||
Do(channelID uint64, command, input string) (*types.Message, error)
|
||||
}
|
||||
)
|
||||
|
||||
func Command(ctx context.Context) CommandService {
|
||||
return (&command{}).With(ctx)
|
||||
}
|
||||
|
||||
func (svc *command) With(ctx context.Context) CommandService {
|
||||
return &command{
|
||||
ctx: ctx,
|
||||
}
|
||||
}
|
||||
|
||||
func (svc *command) Do(channelID uint64, command, input string) (*types.Message, error) {
|
||||
switch command {
|
||||
case "me":
|
||||
if input != "" {
|
||||
return DefaultMessage.With(svc.ctx).Create(&types.Message{
|
||||
Type: types.MessageTypeIlleism,
|
||||
ChannelID: channelID,
|
||||
Message: input,
|
||||
})
|
||||
}
|
||||
return nil, nil
|
||||
case "tableflip":
|
||||
fallthrough
|
||||
case "unflip":
|
||||
fallthrough
|
||||
case "shrug":
|
||||
messages := map[string]string{
|
||||
"tableflip": `(╯°□°)╯︵ ┻━┻`,
|
||||
"unflip": `┬─┬ ノ( ゜-゜ノ)`,
|
||||
"shrug": `¯\\_(ツ)_/¯`,
|
||||
}
|
||||
msg := &types.Message{
|
||||
ChannelID: channelID,
|
||||
Message: messages[command],
|
||||
}
|
||||
|
||||
if input != "" {
|
||||
msg.Message = input + " " + msg.Message
|
||||
}
|
||||
return DefaultMessage.With(svc.ctx).Create(msg)
|
||||
default:
|
||||
webhookSvc := DefaultWebhook.With(svc.ctx)
|
||||
webhooks, err := webhookSvc.Find(&types.WebhookFilter{
|
||||
ChannelID: channelID,
|
||||
OutgoingTrigger: command,
|
||||
})
|
||||
if err != nil || len(webhooks) == 0 {
|
||||
return nil, err
|
||||
}
|
||||
return webhookSvc.Do(webhooks[0], input)
|
||||
}
|
||||
return nil, ErrUnknownCommand.new()
|
||||
}
|
||||
@@ -1,5 +1,9 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
type (
|
||||
readableError string
|
||||
)
|
||||
@@ -8,6 +12,11 @@ func (e readableError) Error() string {
|
||||
return string(e)
|
||||
}
|
||||
|
||||
func (e readableError) new() error {
|
||||
return errors.WithStack(e)
|
||||
}
|
||||
|
||||
const (
|
||||
ErrNoPermissions readableError = "You don't have permissions for this operation"
|
||||
ErrNoPermissions readableError = "You don't have permissions for this operation"
|
||||
ErrUnknownCommand readableError = "Unknown command"
|
||||
)
|
||||
|
||||
@@ -30,11 +30,16 @@ func TestMain(m *testing.M) {
|
||||
factory.Database.Add("messaging", dsn)
|
||||
factory.Database.Add("system", dsn)
|
||||
|
||||
// @todo: add flags to configure the sql profiler in tests
|
||||
|
||||
db := factory.Database.MustGet()
|
||||
db.Profiler = &factory.Database.ProfilerStdout
|
||||
|
||||
dbSystem := factory.Database.MustGet("system")
|
||||
dbSystem.Profiler = &factory.Database.ProfilerStdout
|
||||
|
||||
// migrate database schema
|
||||
if err := systemMigrate.Migrate(db); err != nil {
|
||||
if err := systemMigrate.Migrate(dbSystem); err != nil {
|
||||
log.Printf("Error running migrations: %+v\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
@@ -37,9 +38,10 @@ type (
|
||||
FindThreads(filter *types.MessageFilter) (types.MessageSet, error)
|
||||
|
||||
Create(messages *types.Message) (*types.Message, error)
|
||||
|
||||
Update(messages *types.Message) (*types.Message, error)
|
||||
|
||||
CreateWithAvatar(message *types.Message, avatar io.Reader) (*types.Message, error)
|
||||
|
||||
React(messageID uint64, reaction string) error
|
||||
RemoveReaction(messageID uint64, reaction string) error
|
||||
|
||||
@@ -129,6 +131,11 @@ func (svc *message) FindThreads(filter *types.MessageFilter) (mm types.MessageSe
|
||||
return mm, svc.preload(mm)
|
||||
}
|
||||
|
||||
func (svc *message) CreateWithAvatar(in *types.Message, avatar io.Reader) (*types.Message, error) {
|
||||
// @todo: avatar
|
||||
return svc.Create(in)
|
||||
}
|
||||
|
||||
func (svc message) channelAccessCheck(IDs ...uint64) error {
|
||||
for _, ID := range IDs {
|
||||
if ID > 0 {
|
||||
|
||||
@@ -22,6 +22,8 @@ var (
|
||||
DefaultPubSub *pubSub
|
||||
DefaultEvent EventService
|
||||
DefaultPermissions PermissionsService
|
||||
DefaultCommand CommandService
|
||||
DefaultWebhook WebhookService
|
||||
)
|
||||
|
||||
func Init() error {
|
||||
@@ -30,7 +32,7 @@ func Init() error {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = http.New(&config.HTTPClient{
|
||||
client, err := http.New(&config.HTTPClient{
|
||||
Timeout: 10,
|
||||
})
|
||||
if err != nil {
|
||||
@@ -45,6 +47,8 @@ func Init() error {
|
||||
DefaultMessage = Message(ctx)
|
||||
DefaultChannel = Channel(ctx)
|
||||
DefaultPubSub = PubSub()
|
||||
DefaultCommand = Command(ctx)
|
||||
DefaultWebhook = Webhook(ctx, client)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -0,0 +1,254 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
|
||||
"github.com/crusttech/crust/internal/auth"
|
||||
"github.com/crusttech/crust/internal/http"
|
||||
"github.com/crusttech/crust/internal/store"
|
||||
"github.com/crusttech/crust/messaging/internal/repository"
|
||||
"github.com/crusttech/crust/messaging/types"
|
||||
systemService "github.com/crusttech/crust/system/service"
|
||||
systemTypes "github.com/crusttech/crust/system/types"
|
||||
)
|
||||
|
||||
type (
|
||||
webhook struct {
|
||||
db db
|
||||
ctx context.Context
|
||||
client *http.Client
|
||||
|
||||
users systemService.UserService
|
||||
webhook repository.WebhookRepository
|
||||
perms PermissionsService
|
||||
}
|
||||
|
||||
WebhookService interface {
|
||||
With(ctx context.Context) WebhookService
|
||||
|
||||
Get(webhookID uint64) (*types.Webhook, error)
|
||||
|
||||
Find(*types.WebhookFilter) (types.WebhookSet, error)
|
||||
|
||||
Delete(webhookID uint64) error
|
||||
DeleteByToken(webhookID uint64, webhookToken string) error
|
||||
|
||||
Message(webhookID uint64, webhookToken string, username string, avatar io.Reader, message string) (interface{}, error)
|
||||
|
||||
Create(kind types.WebhookKind, channelID uint64, params types.WebhookRequest) (*types.Webhook, error)
|
||||
Update(webhookID uint64, kind types.WebhookKind, channelID uint64, params types.WebhookRequest) (*types.Webhook, error)
|
||||
|
||||
Do(webhook *types.Webhook, message string) (*types.Message, error)
|
||||
}
|
||||
)
|
||||
|
||||
func Webhook(ctx context.Context, client *http.Client) WebhookService {
|
||||
return (&webhook{
|
||||
client: client,
|
||||
}).With(ctx)
|
||||
}
|
||||
|
||||
func (svc *webhook) With(ctx context.Context) WebhookService {
|
||||
db := repository.DB(ctx)
|
||||
return &webhook{
|
||||
db: db,
|
||||
ctx: ctx,
|
||||
client: svc.client,
|
||||
|
||||
users: systemService.User(ctx),
|
||||
webhook: repository.Webhook(ctx, db),
|
||||
perms: Permissions(ctx),
|
||||
}
|
||||
}
|
||||
|
||||
func (svc *webhook) Create(kind types.WebhookKind, channelID uint64, params types.WebhookRequest) (*types.Webhook, error) {
|
||||
var userID = repository.Identity(svc.ctx)
|
||||
|
||||
webhook := &types.Webhook{
|
||||
Kind: kind,
|
||||
OwnerUserID: userID,
|
||||
ChannelID: channelID,
|
||||
OutgoingTrigger: params.OutgoingTrigger,
|
||||
OutgoingURL: params.OutgoingURL,
|
||||
}
|
||||
|
||||
if !svc.perms.CanManageWebhooks(webhook) && !svc.perms.CanManageOwnWebhooks(webhook) {
|
||||
return nil, errors.WithStack(ErrNoPermissions)
|
||||
}
|
||||
|
||||
botUser := &systemTypes.User{
|
||||
Username: params.Username,
|
||||
Name: params.Username,
|
||||
Handle: params.Username,
|
||||
Kind: systemTypes.BotUser,
|
||||
RelatedUserID: userID,
|
||||
}
|
||||
|
||||
user, err := svc.users.CreateWithAvatar(botUser, params.Avatar)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
webhook.UserID = user.ID
|
||||
|
||||
wh, err := svc.webhook.Create(webhook)
|
||||
if err != nil {
|
||||
// cross service rollback (delete user)
|
||||
svc.users.Delete(user.ID)
|
||||
return nil, err
|
||||
}
|
||||
return wh, err
|
||||
}
|
||||
|
||||
func (svc *webhook) Update(webhookID uint64, kind types.WebhookKind, channelID uint64, params types.WebhookRequest) (*types.Webhook, error) {
|
||||
var userID = repository.Identity(svc.ctx)
|
||||
webhook, err := svc.Get(webhookID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if !svc.perms.CanManageWebhooks(webhook) && !(webhook.OwnerUserID == userID && svc.perms.CanManageOwnWebhooks(webhook)) {
|
||||
return nil, errors.WithStack(ErrNoPermissions)
|
||||
}
|
||||
|
||||
botUser, err := svc.users.FindByID(webhook.UserID)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "Error when looking for User ID %d", webhook.UserID)
|
||||
}
|
||||
|
||||
if _, err := svc.users.UpdateWithAvatar(botUser, params.Avatar); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
webhook.Kind = kind
|
||||
webhook.ChannelID = channelID
|
||||
webhook.OutgoingTrigger = params.OutgoingTrigger
|
||||
webhook.OutgoingURL = params.OutgoingURL
|
||||
|
||||
return svc.webhook.Update(webhook)
|
||||
}
|
||||
|
||||
func (svc *webhook) Get(webhookID uint64) (*types.Webhook, error) {
|
||||
return svc.webhook.Get(webhookID)
|
||||
}
|
||||
|
||||
func (svc *webhook) Find(filter *types.WebhookFilter) (types.WebhookSet, error) {
|
||||
return svc.webhook.Find(filter)
|
||||
}
|
||||
|
||||
func (svc *webhook) Delete(webhookID uint64) error {
|
||||
webhook, err := svc.Get(webhookID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if svc.perms.CanManageWebhooks(webhook) {
|
||||
return svc.webhook.Delete(webhookID)
|
||||
}
|
||||
var userID = repository.Identity(svc.ctx)
|
||||
if webhook.OwnerUserID == userID && svc.perms.CanManageOwnWebhooks(webhook) {
|
||||
return svc.webhook.Delete(webhookID)
|
||||
}
|
||||
return errors.WithStack(ErrNoPermissions)
|
||||
}
|
||||
|
||||
func (svc *webhook) DeleteByToken(webhookID uint64, webhookToken string) error {
|
||||
return svc.webhook.DeleteByToken(webhookID, webhookToken)
|
||||
}
|
||||
|
||||
func (svc *webhook) Message(webhookID uint64, webhookToken string, username string, avatar io.Reader, message string) (interface{}, error) {
|
||||
if webhook, err := svc.webhook.GetByToken(webhookID, webhookToken); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
msg := &types.Message{
|
||||
Message: message,
|
||||
Meta: &types.MessageMeta{
|
||||
Username: username,
|
||||
},
|
||||
}
|
||||
return svc.sendMessage(webhook, msg, avatar)
|
||||
}
|
||||
}
|
||||
|
||||
// Do executes an outgoing HTTP webhook request
|
||||
func (svc *webhook) Do(webhook *types.Webhook, message string) (*types.Message, error) {
|
||||
if webhook.Kind != types.OutgoingWebhook {
|
||||
return nil, errors.Errorf("Unsupported webhook type: %s", webhook.Kind)
|
||||
}
|
||||
|
||||
// replace url query %s with message
|
||||
url := strings.Replace(webhook.OutgoingURL, "%s", url.QueryEscape(message), -1)
|
||||
|
||||
// post body contains only `text`
|
||||
requestBody := types.WebhookBody{
|
||||
Text: message,
|
||||
}
|
||||
req, err := svc.client.Post(url, requestBody)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// execute outgoing webhook
|
||||
resp, err := svc.client.Do(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
// parse response body
|
||||
responseBody := types.WebhookBody{}
|
||||
contentType := resp.Header.Get("Content-Type")
|
||||
switch {
|
||||
case strings.Contains(contentType, "text/plain"):
|
||||
// keep plain/text as-is
|
||||
if b, err := ioutil.ReadAll(resp.Body); err != nil {
|
||||
return nil, errors.WithStack(err)
|
||||
} else {
|
||||
responseBody.Text = string(b)
|
||||
}
|
||||
default:
|
||||
switch resp.StatusCode {
|
||||
case 200:
|
||||
// assume the response is an expected json structure
|
||||
if err := json.NewDecoder(resp.Body).Decode(&responseBody); err != nil {
|
||||
return nil, errors.WithStack(err)
|
||||
}
|
||||
if responseBody.Text == "" {
|
||||
return nil, errors.New("Empty webhook response")
|
||||
}
|
||||
default:
|
||||
return nil, http.ToError(resp)
|
||||
}
|
||||
}
|
||||
|
||||
msg := &types.Message{
|
||||
Message: responseBody.Text,
|
||||
Meta: &types.MessageMeta{
|
||||
Username: responseBody.Username,
|
||||
},
|
||||
}
|
||||
|
||||
avatar, err := store.FromURL(responseBody.Avatar)
|
||||
if err != nil {
|
||||
return svc.sendMessage(webhook, msg, nil)
|
||||
}
|
||||
defer avatar.Close()
|
||||
|
||||
return svc.sendMessage(webhook, msg, avatar)
|
||||
}
|
||||
|
||||
func (svc *webhook) sendMessage(webhook *types.Webhook, msg *types.Message, avatar io.Reader) (*types.Message, error) {
|
||||
// We need a webhook user context for message service
|
||||
ctx := auth.SetIdentityToContext(svc.ctx, &systemTypes.User{ID: webhook.UserID})
|
||||
messageSvc := Message(ctx)
|
||||
|
||||
msg.ChannelID = webhook.ChannelID
|
||||
msg.UserID = webhook.UserID
|
||||
|
||||
return messageSvc.CreateWithAvatar(msg, avatar)
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
// +build integration,external
|
||||
|
||||
package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/crusttech/crust/internal/auth"
|
||||
"github.com/crusttech/crust/internal/config"
|
||||
"github.com/crusttech/crust/internal/http"
|
||||
"github.com/crusttech/crust/internal/test"
|
||||
"github.com/crusttech/crust/messaging/internal/repository"
|
||||
"github.com/crusttech/crust/messaging/types"
|
||||
systemService "github.com/crusttech/crust/system/service"
|
||||
systemTypes "github.com/crusttech/crust/system/types"
|
||||
)
|
||||
|
||||
func TestOutgoingWebhook(t *testing.T) {
|
||||
var user = &systemTypes.User{ID: 1}
|
||||
var channel = &types.Channel{ID: 1}
|
||||
|
||||
ctx := context.WithValue(context.Background(), "testing", true)
|
||||
ctx = auth.SetIdentityToContext(ctx, user)
|
||||
|
||||
// set up user
|
||||
{
|
||||
svc := systemService.User(ctx)
|
||||
_, err := svc.Create(user)
|
||||
test.Assert(t, err == nil, "Error when creating user: %+v", err)
|
||||
}
|
||||
|
||||
// set up channel
|
||||
{
|
||||
rpo := repository.Channel(ctx, repository.DB(ctx))
|
||||
_, err := rpo.Create(channel)
|
||||
test.Assert(t, err == nil, "Error when creating channel: %+v", err)
|
||||
}
|
||||
|
||||
client, err := http.New(&config.HTTPClient{
|
||||
Timeout: 10,
|
||||
})
|
||||
test.Assert(t, err == nil, "Error creating HTTP client: %+v", err)
|
||||
|
||||
svc := Webhook(ctx, client)
|
||||
|
||||
{
|
||||
/* create outgoing webhook */
|
||||
webhook, err := svc.Create(types.OutgoingWebhook, channel.ID, types.WebhookRequest{
|
||||
Username: "test-webhook",
|
||||
OutgoingTrigger: "fortune",
|
||||
OutgoingURL: "https://api.scene-si.org/fortune.php",
|
||||
})
|
||||
test.Assert(t, err == nil, "Error when creating webhook: %+v", err)
|
||||
|
||||
/* find outgoing webhook */
|
||||
webhooks, err := svc.Find(&types.WebhookFilter{
|
||||
OutgoingTrigger: webhook.OutgoingTrigger,
|
||||
})
|
||||
test.Assert(t, err == nil, "Error when finding webhook: %+v", err)
|
||||
test.Assert(t, len(webhooks) == 1, "Expected to find 1 webhook, got %d", len(webhooks))
|
||||
|
||||
/* trigger outgoing webhook */
|
||||
{
|
||||
message, err := svc.Do(webhooks[0], "")
|
||||
test.Assert(t, err == nil, "Error when triggering webhook: %+v", err)
|
||||
test.Assert(t, strings.Contains(message.Message, "BOFH"), "Unexpected webhook output: %s", message.Message)
|
||||
}
|
||||
|
||||
// update webhook
|
||||
wh, err := svc.Update(webhook.ID, types.OutgoingWebhook, channel.ID, types.WebhookRequest{
|
||||
Username: "test-webhook-json",
|
||||
OutgoingTrigger: "fortune-json",
|
||||
OutgoingURL: "https://api.scene-si.org/fortune.php?username=test",
|
||||
})
|
||||
test.Assert(t, err == nil, "Error when updating webhook: %+v", err)
|
||||
|
||||
/* trigger outgoing webhook */
|
||||
{
|
||||
message, err := svc.Do(wh, "")
|
||||
test.Assert(t, err == nil, "Error when triggering webhook: %+v", err)
|
||||
test.Assert(t, message.Meta.Username == "test", "Expected message.meta.username = 'test', got: '%s'", message.Meta.Username)
|
||||
test.Assert(t, strings.Contains(message.Message, "BOFH"), "Unexpected webhook output: %s", message.Message)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -27,5 +27,11 @@ func (ctrl *Commands) List(ctx context.Context, r *request.CommandsList) (interf
|
||||
&types.Command{
|
||||
Name: "shrug",
|
||||
Description: "It does exactly what it says on the tin"},
|
||||
&types.Command{
|
||||
Name: "tableflip",
|
||||
Description: "Flip a table in anger"},
|
||||
&types.Command{
|
||||
Name: "unflip",
|
||||
Description: "Put the table back from a flip"},
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -0,0 +1,96 @@
|
||||
package handlers
|
||||
|
||||
/*
|
||||
Hello! This file is auto-generated from `docs/src/spec.json`.
|
||||
|
||||
For development:
|
||||
In order to update the generated files, edit this file under the location,
|
||||
add your struct fields, imports, API definitions and whatever you want, and:
|
||||
|
||||
1. run [spec](https://github.com/titpetric/spec) in the same folder,
|
||||
2. run `./_gen.php` in this folder.
|
||||
|
||||
You may edit `webhooks.go`, `webhooks.util.go` or `webhooks_test.go` to
|
||||
implement your API calls, helper functions and tests. The file `webhooks.go`
|
||||
is only generated the first time, and will not be overwritten if it exists.
|
||||
*/
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"net/http"
|
||||
|
||||
"github.com/go-chi/chi"
|
||||
"github.com/titpetric/factory/resputil"
|
||||
|
||||
"github.com/crusttech/crust/messaging/rest/request"
|
||||
)
|
||||
|
||||
// Internal API interface
|
||||
type WebhooksAPI interface {
|
||||
List(context.Context, *request.WebhooksList) (interface{}, error)
|
||||
Create(context.Context, *request.WebhooksCreate) (interface{}, error)
|
||||
Update(context.Context, *request.WebhooksUpdate) (interface{}, error)
|
||||
Get(context.Context, *request.WebhooksGet) (interface{}, error)
|
||||
Delete(context.Context, *request.WebhooksDelete) (interface{}, error)
|
||||
}
|
||||
|
||||
// HTTP API interface
|
||||
type Webhooks struct {
|
||||
List func(http.ResponseWriter, *http.Request)
|
||||
Create func(http.ResponseWriter, *http.Request)
|
||||
Update func(http.ResponseWriter, *http.Request)
|
||||
Get func(http.ResponseWriter, *http.Request)
|
||||
Delete func(http.ResponseWriter, *http.Request)
|
||||
}
|
||||
|
||||
func NewWebhooks(wh WebhooksAPI) *Webhooks {
|
||||
return &Webhooks{
|
||||
List: func(w http.ResponseWriter, r *http.Request) {
|
||||
defer r.Body.Close()
|
||||
params := request.NewWebhooksList()
|
||||
resputil.JSON(w, params.Fill(r), func() (interface{}, error) {
|
||||
return wh.List(r.Context(), params)
|
||||
})
|
||||
},
|
||||
Create: func(w http.ResponseWriter, r *http.Request) {
|
||||
defer r.Body.Close()
|
||||
params := request.NewWebhooksCreate()
|
||||
resputil.JSON(w, params.Fill(r), func() (interface{}, error) {
|
||||
return wh.Create(r.Context(), params)
|
||||
})
|
||||
},
|
||||
Update: func(w http.ResponseWriter, r *http.Request) {
|
||||
defer r.Body.Close()
|
||||
params := request.NewWebhooksUpdate()
|
||||
resputil.JSON(w, params.Fill(r), func() (interface{}, error) {
|
||||
return wh.Update(r.Context(), params)
|
||||
})
|
||||
},
|
||||
Get: func(w http.ResponseWriter, r *http.Request) {
|
||||
defer r.Body.Close()
|
||||
params := request.NewWebhooksGet()
|
||||
resputil.JSON(w, params.Fill(r), func() (interface{}, error) {
|
||||
return wh.Get(r.Context(), params)
|
||||
})
|
||||
},
|
||||
Delete: func(w http.ResponseWriter, r *http.Request) {
|
||||
defer r.Body.Close()
|
||||
params := request.NewWebhooksDelete()
|
||||
resputil.JSON(w, params.Fill(r), func() (interface{}, error) {
|
||||
return wh.Delete(r.Context(), params)
|
||||
})
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (wh *Webhooks) MountRoutes(r chi.Router, middlewares ...func(http.Handler) http.Handler) {
|
||||
r.Group(func(r chi.Router) {
|
||||
r.Use(middlewares...)
|
||||
r.Get("/webhooks/", wh.List)
|
||||
r.Post("/webhooks/", wh.Create)
|
||||
r.Post("/webhooks/{webhookID}", wh.Update)
|
||||
r.Get("/webhooks/{webhookID}", wh.Get)
|
||||
r.Delete("/webhooks/{webhookID}", wh.Delete)
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
package handlers
|
||||
|
||||
/*
|
||||
Hello! This file is auto-generated from `docs/src/spec.json`.
|
||||
|
||||
For development:
|
||||
In order to update the generated files, edit this file under the location,
|
||||
add your struct fields, imports, API definitions and whatever you want, and:
|
||||
|
||||
1. run [spec](https://github.com/titpetric/spec) in the same folder,
|
||||
2. run `./_gen.php` in this folder.
|
||||
|
||||
You may edit `webhooks_public.go`, `webhooks_public.util.go` or `webhooks_public_test.go` to
|
||||
implement your API calls, helper functions and tests. The file `webhooks_public.go`
|
||||
is only generated the first time, and will not be overwritten if it exists.
|
||||
*/
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"net/http"
|
||||
|
||||
"github.com/go-chi/chi"
|
||||
"github.com/titpetric/factory/resputil"
|
||||
|
||||
"github.com/crusttech/crust/messaging/rest/request"
|
||||
)
|
||||
|
||||
// Internal API interface
|
||||
type WebhooksPublicAPI interface {
|
||||
Delete(context.Context, *request.WebhooksPublicDelete) (interface{}, error)
|
||||
Create(context.Context, *request.WebhooksPublicCreate) (interface{}, error)
|
||||
}
|
||||
|
||||
// HTTP API interface
|
||||
type WebhooksPublic struct {
|
||||
Delete func(http.ResponseWriter, *http.Request)
|
||||
Create func(http.ResponseWriter, *http.Request)
|
||||
}
|
||||
|
||||
func NewWebhooksPublic(wh WebhooksPublicAPI) *WebhooksPublic {
|
||||
return &WebhooksPublic{
|
||||
Delete: func(w http.ResponseWriter, r *http.Request) {
|
||||
defer r.Body.Close()
|
||||
params := request.NewWebhooksPublicDelete()
|
||||
resputil.JSON(w, params.Fill(r), func() (interface{}, error) {
|
||||
return wh.Delete(r.Context(), params)
|
||||
})
|
||||
},
|
||||
Create: func(w http.ResponseWriter, r *http.Request) {
|
||||
defer r.Body.Close()
|
||||
params := request.NewWebhooksPublicCreate()
|
||||
resputil.JSON(w, params.Fill(r), func() (interface{}, error) {
|
||||
return wh.Create(r.Context(), params)
|
||||
})
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (wh *WebhooksPublic) MountRoutes(r chi.Router, middlewares ...func(http.Handler) http.Handler) {
|
||||
r.Group(func(r chi.Router) {
|
||||
r.Use(middlewares...)
|
||||
r.Delete("/webhooks/{webhookID}/{webhookToken}", wh.Delete)
|
||||
r.Post("/webhooks/{webhookID}/{webhookToken}", wh.Create)
|
||||
})
|
||||
}
|
||||
@@ -17,8 +17,8 @@ var _ = errors.Wrap
|
||||
type (
|
||||
Message struct {
|
||||
svc struct {
|
||||
msg service.MessageService
|
||||
event service.EventService
|
||||
msg service.MessageService
|
||||
command service.CommandService
|
||||
}
|
||||
}
|
||||
)
|
||||
@@ -26,6 +26,7 @@ type (
|
||||
func (Message) New() *Message {
|
||||
ctrl := &Message{}
|
||||
ctrl.svc.msg = service.DefaultMessage
|
||||
ctrl.svc.command = service.DefaultCommand
|
||||
return ctrl
|
||||
}
|
||||
|
||||
@@ -53,32 +54,7 @@ func (ctrl *Message) Edit(ctx context.Context, r *request.MessageEdit) (interfac
|
||||
}
|
||||
|
||||
func (ctrl Message) ExecuteCommand(ctx context.Context, r *request.MessageExecuteCommand) (interface{}, error) {
|
||||
switch r.Command {
|
||||
case "me":
|
||||
if r.Input != "" {
|
||||
return ctrl.svc.msg.With(ctx).Create(&types.Message{
|
||||
Type: types.MessageTypeIlleism,
|
||||
ChannelID: r.ChannelID,
|
||||
Message: r.Input,
|
||||
})
|
||||
}
|
||||
|
||||
return nil, nil
|
||||
|
||||
case "shrug":
|
||||
msg := &types.Message{
|
||||
ChannelID: r.ChannelID,
|
||||
Message: `¯\\_(ツ)_/¯`,
|
||||
}
|
||||
|
||||
if r.Input != "" {
|
||||
msg.Message = r.Input + " " + msg.Message
|
||||
}
|
||||
|
||||
return ctrl.svc.msg.With(ctx).Create(msg)
|
||||
}
|
||||
|
||||
return nil, errors.New("unknown command")
|
||||
return ctrl.svc.command.With(ctx).Do(r.ChannelID, r.Command, r.Input)
|
||||
}
|
||||
|
||||
func (ctrl *Message) Delete(ctx context.Context, r *request.MessageDelete) (interface{}, error) {
|
||||
|
||||
@@ -0,0 +1,324 @@
|
||||
package request
|
||||
|
||||
/*
|
||||
Hello! This file is auto-generated from `docs/src/spec.json`.
|
||||
|
||||
For development:
|
||||
In order to update the generated files, edit this file under the location,
|
||||
add your struct fields, imports, API definitions and whatever you want, and:
|
||||
|
||||
1. run [spec](https://github.com/titpetric/spec) in the same folder,
|
||||
2. run `./_gen.php` in this folder.
|
||||
|
||||
You may edit `webhooks.go`, `webhooks.util.go` or `webhooks_test.go` to
|
||||
implement your API calls, helper functions and tests. The file `webhooks.go`
|
||||
is only generated the first time, and will not be overwritten if it exists.
|
||||
*/
|
||||
|
||||
import (
|
||||
"io"
|
||||
"strings"
|
||||
|
||||
"encoding/json"
|
||||
"mime/multipart"
|
||||
"net/http"
|
||||
|
||||
"github.com/go-chi/chi"
|
||||
"github.com/pkg/errors"
|
||||
|
||||
"github.com/crusttech/crust/messaging/types"
|
||||
)
|
||||
|
||||
var _ = chi.URLParam
|
||||
var _ = multipart.FileHeader{}
|
||||
|
||||
// Webhooks list request parameters
|
||||
type WebhooksList struct {
|
||||
ChannelID uint64 `json:",string"`
|
||||
UserID uint64 `json:",string"`
|
||||
}
|
||||
|
||||
func NewWebhooksList() *WebhooksList {
|
||||
return &WebhooksList{}
|
||||
}
|
||||
|
||||
func (wReq *WebhooksList) Fill(r *http.Request) (err error) {
|
||||
if strings.ToLower(r.Header.Get("content-type")) == "application/json" {
|
||||
err = json.NewDecoder(r.Body).Decode(wReq)
|
||||
|
||||
switch {
|
||||
case err == io.EOF:
|
||||
err = nil
|
||||
case err != nil:
|
||||
return errors.Wrap(err, "error parsing http request body")
|
||||
}
|
||||
}
|
||||
|
||||
if err = r.ParseForm(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
get := map[string]string{}
|
||||
post := map[string]string{}
|
||||
urlQuery := r.URL.Query()
|
||||
for name, param := range urlQuery {
|
||||
get[name] = string(param[0])
|
||||
}
|
||||
postVars := r.Form
|
||||
for name, param := range postVars {
|
||||
post[name] = string(param[0])
|
||||
}
|
||||
|
||||
if val, ok := get["channelID"]; ok {
|
||||
|
||||
wReq.ChannelID = parseUInt64(val)
|
||||
}
|
||||
if val, ok := get["userID"]; ok {
|
||||
|
||||
wReq.UserID = parseUInt64(val)
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
var _ RequestFiller = NewWebhooksList()
|
||||
|
||||
// Webhooks create request parameters
|
||||
type WebhooksCreate struct {
|
||||
ChannelID uint64 `json:",string"`
|
||||
Kind types.WebhookKind
|
||||
Trigger string
|
||||
Url string
|
||||
Username string
|
||||
Avatar *multipart.FileHeader
|
||||
AvatarURL string
|
||||
}
|
||||
|
||||
func NewWebhooksCreate() *WebhooksCreate {
|
||||
return &WebhooksCreate{}
|
||||
}
|
||||
|
||||
func (wReq *WebhooksCreate) Fill(r *http.Request) (err error) {
|
||||
if strings.ToLower(r.Header.Get("content-type")) == "application/json" {
|
||||
err = json.NewDecoder(r.Body).Decode(wReq)
|
||||
|
||||
switch {
|
||||
case err == io.EOF:
|
||||
err = nil
|
||||
case err != nil:
|
||||
return errors.Wrap(err, "error parsing http request body")
|
||||
}
|
||||
}
|
||||
|
||||
if err = r.ParseMultipartForm(32 << 20); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
get := map[string]string{}
|
||||
post := map[string]string{}
|
||||
urlQuery := r.URL.Query()
|
||||
for name, param := range urlQuery {
|
||||
get[name] = string(param[0])
|
||||
}
|
||||
postVars := r.Form
|
||||
for name, param := range postVars {
|
||||
post[name] = string(param[0])
|
||||
}
|
||||
|
||||
if val, ok := post["channelID"]; ok {
|
||||
|
||||
wReq.ChannelID = parseUInt64(val)
|
||||
}
|
||||
if val, ok := post["kind"]; ok {
|
||||
|
||||
wReq.Kind = types.WebhookKind(val)
|
||||
}
|
||||
if val, ok := post["trigger"]; ok {
|
||||
|
||||
wReq.Trigger = val
|
||||
}
|
||||
if val, ok := post["url"]; ok {
|
||||
|
||||
wReq.Url = val
|
||||
}
|
||||
if val, ok := post["username"]; ok {
|
||||
|
||||
wReq.Username = val
|
||||
}
|
||||
if _, wReq.Avatar, err = r.FormFile("avatar"); err != nil {
|
||||
return errors.Wrap(err, "error procesing uploaded file")
|
||||
}
|
||||
|
||||
if val, ok := post["avatarURL"]; ok {
|
||||
|
||||
wReq.AvatarURL = val
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
var _ RequestFiller = NewWebhooksCreate()
|
||||
|
||||
// Webhooks update request parameters
|
||||
type WebhooksUpdate struct {
|
||||
WebhookID uint64 `json:",string"`
|
||||
ChannelID uint64 `json:",string"`
|
||||
Kind types.WebhookKind
|
||||
Trigger string
|
||||
Url string
|
||||
Username string
|
||||
Avatar *multipart.FileHeader
|
||||
AvatarURL string
|
||||
}
|
||||
|
||||
func NewWebhooksUpdate() *WebhooksUpdate {
|
||||
return &WebhooksUpdate{}
|
||||
}
|
||||
|
||||
func (wReq *WebhooksUpdate) Fill(r *http.Request) (err error) {
|
||||
if strings.ToLower(r.Header.Get("content-type")) == "application/json" {
|
||||
err = json.NewDecoder(r.Body).Decode(wReq)
|
||||
|
||||
switch {
|
||||
case err == io.EOF:
|
||||
err = nil
|
||||
case err != nil:
|
||||
return errors.Wrap(err, "error parsing http request body")
|
||||
}
|
||||
}
|
||||
|
||||
if err = r.ParseMultipartForm(32 << 20); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
get := map[string]string{}
|
||||
post := map[string]string{}
|
||||
urlQuery := r.URL.Query()
|
||||
for name, param := range urlQuery {
|
||||
get[name] = string(param[0])
|
||||
}
|
||||
postVars := r.Form
|
||||
for name, param := range postVars {
|
||||
post[name] = string(param[0])
|
||||
}
|
||||
|
||||
wReq.WebhookID = parseUInt64(chi.URLParam(r, "webhookID"))
|
||||
if val, ok := post["channelID"]; ok {
|
||||
|
||||
wReq.ChannelID = parseUInt64(val)
|
||||
}
|
||||
if val, ok := post["kind"]; ok {
|
||||
|
||||
wReq.Kind = types.WebhookKind(val)
|
||||
}
|
||||
if val, ok := post["trigger"]; ok {
|
||||
|
||||
wReq.Trigger = val
|
||||
}
|
||||
if val, ok := post["url"]; ok {
|
||||
|
||||
wReq.Url = val
|
||||
}
|
||||
if val, ok := post["username"]; ok {
|
||||
|
||||
wReq.Username = val
|
||||
}
|
||||
if _, wReq.Avatar, err = r.FormFile("avatar"); err != nil {
|
||||
return errors.Wrap(err, "error procesing uploaded file")
|
||||
}
|
||||
|
||||
if val, ok := post["avatarURL"]; ok {
|
||||
|
||||
wReq.AvatarURL = val
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
var _ RequestFiller = NewWebhooksUpdate()
|
||||
|
||||
// Webhooks get request parameters
|
||||
type WebhooksGet struct {
|
||||
WebhookID uint64 `json:",string"`
|
||||
}
|
||||
|
||||
func NewWebhooksGet() *WebhooksGet {
|
||||
return &WebhooksGet{}
|
||||
}
|
||||
|
||||
func (wReq *WebhooksGet) Fill(r *http.Request) (err error) {
|
||||
if strings.ToLower(r.Header.Get("content-type")) == "application/json" {
|
||||
err = json.NewDecoder(r.Body).Decode(wReq)
|
||||
|
||||
switch {
|
||||
case err == io.EOF:
|
||||
err = nil
|
||||
case err != nil:
|
||||
return errors.Wrap(err, "error parsing http request body")
|
||||
}
|
||||
}
|
||||
|
||||
if err = r.ParseForm(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
get := map[string]string{}
|
||||
post := map[string]string{}
|
||||
urlQuery := r.URL.Query()
|
||||
for name, param := range urlQuery {
|
||||
get[name] = string(param[0])
|
||||
}
|
||||
postVars := r.Form
|
||||
for name, param := range postVars {
|
||||
post[name] = string(param[0])
|
||||
}
|
||||
|
||||
wReq.WebhookID = parseUInt64(chi.URLParam(r, "webhookID"))
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
var _ RequestFiller = NewWebhooksGet()
|
||||
|
||||
// Webhooks delete request parameters
|
||||
type WebhooksDelete struct {
|
||||
WebhookID uint64 `json:",string"`
|
||||
}
|
||||
|
||||
func NewWebhooksDelete() *WebhooksDelete {
|
||||
return &WebhooksDelete{}
|
||||
}
|
||||
|
||||
func (wReq *WebhooksDelete) Fill(r *http.Request) (err error) {
|
||||
if strings.ToLower(r.Header.Get("content-type")) == "application/json" {
|
||||
err = json.NewDecoder(r.Body).Decode(wReq)
|
||||
|
||||
switch {
|
||||
case err == io.EOF:
|
||||
err = nil
|
||||
case err != nil:
|
||||
return errors.Wrap(err, "error parsing http request body")
|
||||
}
|
||||
}
|
||||
|
||||
if err = r.ParseForm(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
get := map[string]string{}
|
||||
post := map[string]string{}
|
||||
urlQuery := r.URL.Query()
|
||||
for name, param := range urlQuery {
|
||||
get[name] = string(param[0])
|
||||
}
|
||||
postVars := r.Form
|
||||
for name, param := range postVars {
|
||||
post[name] = string(param[0])
|
||||
}
|
||||
|
||||
wReq.WebhookID = parseUInt64(chi.URLParam(r, "webhookID"))
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
var _ RequestFiller = NewWebhooksDelete()
|
||||
@@ -0,0 +1,136 @@
|
||||
package request
|
||||
|
||||
/*
|
||||
Hello! This file is auto-generated from `docs/src/spec.json`.
|
||||
|
||||
For development:
|
||||
In order to update the generated files, edit this file under the location,
|
||||
add your struct fields, imports, API definitions and whatever you want, and:
|
||||
|
||||
1. run [spec](https://github.com/titpetric/spec) in the same folder,
|
||||
2. run `./_gen.php` in this folder.
|
||||
|
||||
You may edit `webhooks_public.go`, `webhooks_public.util.go` or `webhooks_public_test.go` to
|
||||
implement your API calls, helper functions and tests. The file `webhooks_public.go`
|
||||
is only generated the first time, and will not be overwritten if it exists.
|
||||
*/
|
||||
|
||||
import (
|
||||
"io"
|
||||
"strings"
|
||||
|
||||
"encoding/json"
|
||||
"mime/multipart"
|
||||
"net/http"
|
||||
|
||||
"github.com/go-chi/chi"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
var _ = chi.URLParam
|
||||
var _ = multipart.FileHeader{}
|
||||
|
||||
// WebhooksPublic delete request parameters
|
||||
type WebhooksPublicDelete struct {
|
||||
WebhookID uint64 `json:",string"`
|
||||
WebhookToken string
|
||||
}
|
||||
|
||||
func NewWebhooksPublicDelete() *WebhooksPublicDelete {
|
||||
return &WebhooksPublicDelete{}
|
||||
}
|
||||
|
||||
func (wReq *WebhooksPublicDelete) Fill(r *http.Request) (err error) {
|
||||
if strings.ToLower(r.Header.Get("content-type")) == "application/json" {
|
||||
err = json.NewDecoder(r.Body).Decode(wReq)
|
||||
|
||||
switch {
|
||||
case err == io.EOF:
|
||||
err = nil
|
||||
case err != nil:
|
||||
return errors.Wrap(err, "error parsing http request body")
|
||||
}
|
||||
}
|
||||
|
||||
if err = r.ParseForm(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
get := map[string]string{}
|
||||
post := map[string]string{}
|
||||
urlQuery := r.URL.Query()
|
||||
for name, param := range urlQuery {
|
||||
get[name] = string(param[0])
|
||||
}
|
||||
postVars := r.Form
|
||||
for name, param := range postVars {
|
||||
post[name] = string(param[0])
|
||||
}
|
||||
|
||||
wReq.WebhookID = parseUInt64(chi.URLParam(r, "webhookID"))
|
||||
wReq.WebhookToken = chi.URLParam(r, "webhookToken")
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
var _ RequestFiller = NewWebhooksPublicDelete()
|
||||
|
||||
// WebhooksPublic create request parameters
|
||||
type WebhooksPublicCreate struct {
|
||||
Username string
|
||||
AvatarURL string
|
||||
Content string
|
||||
WebhookID uint64 `json:",string"`
|
||||
WebhookToken string
|
||||
}
|
||||
|
||||
func NewWebhooksPublicCreate() *WebhooksPublicCreate {
|
||||
return &WebhooksPublicCreate{}
|
||||
}
|
||||
|
||||
func (wReq *WebhooksPublicCreate) Fill(r *http.Request) (err error) {
|
||||
if strings.ToLower(r.Header.Get("content-type")) == "application/json" {
|
||||
err = json.NewDecoder(r.Body).Decode(wReq)
|
||||
|
||||
switch {
|
||||
case err == io.EOF:
|
||||
err = nil
|
||||
case err != nil:
|
||||
return errors.Wrap(err, "error parsing http request body")
|
||||
}
|
||||
}
|
||||
|
||||
if err = r.ParseForm(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
get := map[string]string{}
|
||||
post := map[string]string{}
|
||||
urlQuery := r.URL.Query()
|
||||
for name, param := range urlQuery {
|
||||
get[name] = string(param[0])
|
||||
}
|
||||
postVars := r.Form
|
||||
for name, param := range postVars {
|
||||
post[name] = string(param[0])
|
||||
}
|
||||
|
||||
if val, ok := get["username"]; ok {
|
||||
|
||||
wReq.Username = val
|
||||
}
|
||||
if val, ok := get["avatarURL"]; ok {
|
||||
|
||||
wReq.AvatarURL = val
|
||||
}
|
||||
if val, ok := get["content"]; ok {
|
||||
|
||||
wReq.Content = val
|
||||
}
|
||||
wReq.WebhookID = parseUInt64(chi.URLParam(r, "webhookID"))
|
||||
wReq.WebhookToken = chi.URLParam(r, "webhookToken")
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
var _ RequestFiller = NewWebhooksPublicCreate()
|
||||
@@ -0,0 +1,69 @@
|
||||
package rest
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
|
||||
"github.com/crusttech/crust/internal/store"
|
||||
"github.com/crusttech/crust/messaging/internal/service"
|
||||
"github.com/crusttech/crust/messaging/rest/request"
|
||||
"github.com/crusttech/crust/messaging/types"
|
||||
)
|
||||
|
||||
var _ = errors.Wrap
|
||||
|
||||
type Webhooks struct {
|
||||
webhook service.WebhookService
|
||||
}
|
||||
|
||||
func (Webhooks) New() *Webhooks {
|
||||
return &Webhooks{}
|
||||
}
|
||||
|
||||
func (ctrl *Webhooks) Get(ctx context.Context, r *request.WebhooksGet) (interface{}, error) {
|
||||
return ctrl.webhook.With(ctx).Get(r.WebhookID)
|
||||
}
|
||||
|
||||
func (ctrl *Webhooks) Delete(ctx context.Context, r *request.WebhooksDelete) (interface{}, error) {
|
||||
return nil, ctrl.webhook.With(ctx).Delete(r.WebhookID)
|
||||
}
|
||||
|
||||
func (ctrl *Webhooks) List(ctx context.Context, r *request.WebhooksList) (interface{}, error) {
|
||||
return ctrl.webhook.With(ctx).Find(&types.WebhookFilter{
|
||||
ChannelID: r.ChannelID,
|
||||
OwnerUserID: r.UserID,
|
||||
})
|
||||
}
|
||||
|
||||
func (ctrl *Webhooks) Create(ctx context.Context, r *request.WebhooksCreate) (interface{}, error) {
|
||||
avatar, err := store.FromAny(r.Avatar, r.AvatarURL)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer avatar.Close()
|
||||
// Webhook request parameters
|
||||
parameters := types.WebhookRequest{
|
||||
r.Username,
|
||||
avatar,
|
||||
r.Trigger,
|
||||
r.Url,
|
||||
}
|
||||
return ctrl.webhook.With(ctx).Create(r.Kind, r.ChannelID, parameters)
|
||||
}
|
||||
|
||||
func (ctrl *Webhooks) Update(ctx context.Context, r *request.WebhooksUpdate) (interface{}, error) {
|
||||
avatar, err := store.FromAny(r.Avatar, r.AvatarURL)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer avatar.Close()
|
||||
// Webhook request parameters
|
||||
parameters := types.WebhookRequest{
|
||||
r.Username,
|
||||
avatar,
|
||||
r.Trigger,
|
||||
r.Url,
|
||||
}
|
||||
return ctrl.webhook.With(ctx).Update(r.WebhookID, r.Kind, r.ChannelID, parameters)
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
package rest
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
|
||||
"github.com/crusttech/crust/internal/store"
|
||||
"github.com/crusttech/crust/messaging/internal/service"
|
||||
"github.com/crusttech/crust/messaging/rest/request"
|
||||
)
|
||||
|
||||
var _ = errors.Wrap
|
||||
|
||||
type WebhooksPublic struct {
|
||||
webhook service.WebhookService
|
||||
}
|
||||
|
||||
func (WebhooksPublic) New() *WebhooksPublic {
|
||||
return &WebhooksPublic{}
|
||||
}
|
||||
|
||||
func (ctrl *WebhooksPublic) Delete(ctx context.Context, r *request.WebhooksPublicDelete) (interface{}, error) {
|
||||
return nil, ctrl.webhook.With(ctx).DeleteByToken(r.WebhookID, r.WebhookToken)
|
||||
}
|
||||
|
||||
func (ctrl *WebhooksPublic) Create(ctx context.Context, r *request.WebhooksPublicCreate) (interface{}, error) {
|
||||
avatar, err := store.FromAny(nil, r.AvatarURL)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer avatar.Close()
|
||||
return ctrl.webhook.With(ctx).Message(r.WebhookID, r.WebhookToken, r.Username, avatar, r.Content)
|
||||
}
|
||||
@@ -1,10 +1,9 @@
|
||||
package types
|
||||
|
||||
import (
|
||||
"io"
|
||||
"time"
|
||||
|
||||
"mime/multipart"
|
||||
|
||||
"github.com/crusttech/crust/internal/rules"
|
||||
)
|
||||
|
||||
@@ -12,8 +11,8 @@ type (
|
||||
Webhook struct {
|
||||
ID uint64 `json:"id" db:"id"`
|
||||
|
||||
Kind WebhookKind `json:"kind" db:"webhook_kind"`
|
||||
AuthToken string `json:"-" db:"webhook_token"`
|
||||
Kind WebhookKind `json:"kind" db:"kind"`
|
||||
AuthToken string `json:"-" db:"token"`
|
||||
|
||||
OwnerUserID uint64 `json:"userId" db:"rel_owner"`
|
||||
|
||||
@@ -33,8 +32,7 @@ type (
|
||||
WebhookRequest struct {
|
||||
Username string
|
||||
|
||||
Avatar *multipart.FileHeader
|
||||
AvatarURL string
|
||||
Avatar io.Reader
|
||||
|
||||
OutgoingTrigger string
|
||||
OutgoingURL string
|
||||
|
||||
Reference in New Issue
Block a user