Merge branch 'master' into post-metadata
Этот коммит содержится в:
@@ -5,6 +5,7 @@ package model
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
@@ -2224,7 +2225,7 @@ func (c *Client4) SearchPostsWithParams(teamId string, params *SearchParameter)
|
||||
}
|
||||
}
|
||||
|
||||
// SearchPosts returns any posts with matching terms string, including .
|
||||
// SearchPosts returns any posts with matching terms string, including.
|
||||
func (c *Client4) SearchPostsWithMatches(teamId string, terms string, isOrSearch bool) (*PostSearchResults, *Response) {
|
||||
requestBody := map[string]interface{}{"terms": terms, "is_or_search": isOrSearch}
|
||||
if r, err := c.DoApiPost(c.GetTeamRoute(teamId)+"/posts/search", StringInterfaceToJson(requestBody)); err != nil {
|
||||
@@ -2245,6 +2246,34 @@ func (c *Client4) DoPostAction(postId, actionId string) (bool, *Response) {
|
||||
}
|
||||
}
|
||||
|
||||
// OpenInteractiveDialog sends a WebSocket event to a user's clients to
|
||||
// open interactive dialogs, based on the provided trigger ID and other
|
||||
// provided data. Used with interactive message buttons, menus and
|
||||
// slash commands.
|
||||
func (c *Client4) OpenInteractiveDialog(request OpenDialogRequest) (bool, *Response) {
|
||||
b, _ := json.Marshal(request)
|
||||
if r, err := c.DoApiPost("/actions/dialogs/open", string(b)); err != nil {
|
||||
return false, BuildErrorResponse(r, err)
|
||||
} else {
|
||||
defer closeBody(r)
|
||||
return CheckStatusOK(r), BuildResponse(r)
|
||||
}
|
||||
}
|
||||
|
||||
// SubmitInteractiveDialog will submit the provided dialog data to the integration
|
||||
// configured by the URL. Used with the interactive dialogs integration feature.
|
||||
func (c *Client4) SubmitInteractiveDialog(request SubmitDialogRequest) (*SubmitDialogResponse, *Response) {
|
||||
b, _ := json.Marshal(request)
|
||||
if r, err := c.DoApiPost("/actions/dialogs/submit", string(b)); err != nil {
|
||||
return nil, BuildErrorResponse(r, err)
|
||||
} else {
|
||||
defer closeBody(r)
|
||||
var resp SubmitDialogResponse
|
||||
json.NewDecoder(r.Body).Decode(&resp)
|
||||
return &resp, BuildResponse(r)
|
||||
}
|
||||
}
|
||||
|
||||
// File Section
|
||||
|
||||
// UploadFile will upload a file to a channel using a multipart request, to be later attached to a post.
|
||||
|
||||
@@ -16,6 +16,7 @@ type CommandArgs struct {
|
||||
TeamId string `json:"team_id"`
|
||||
RootId string `json:"root_id"`
|
||||
ParentId string `json:"parent_id"`
|
||||
TriggerId string `json:"trigger_id,omitempty"`
|
||||
Command string `json:"command"`
|
||||
SiteURL string `json:"-"`
|
||||
T goi18n.TranslateFunc `json:"-"`
|
||||
|
||||
@@ -18,14 +18,16 @@ const (
|
||||
)
|
||||
|
||||
type CommandResponse struct {
|
||||
ResponseType string `json:"response_type"`
|
||||
Text string `json:"text"`
|
||||
Username string `json:"username"`
|
||||
IconURL string `json:"icon_url"`
|
||||
Type string `json:"type"`
|
||||
Props StringInterface `json:"props"`
|
||||
GotoLocation string `json:"goto_location"`
|
||||
Attachments []*SlackAttachment `json:"attachments"`
|
||||
ResponseType string `json:"response_type"`
|
||||
Text string `json:"text"`
|
||||
Username string `json:"username"`
|
||||
IconURL string `json:"icon_url"`
|
||||
Type string `json:"type"`
|
||||
Props StringInterface `json:"props"`
|
||||
GotoLocation string `json:"goto_location"`
|
||||
TriggerId string `json:"trigger_id"`
|
||||
Attachments []*SlackAttachment `json:"attachments"`
|
||||
ExtraResponses []*CommandResponse `json:"extra_responses"`
|
||||
}
|
||||
|
||||
func (o *CommandResponse) ToJson() string {
|
||||
@@ -63,5 +65,11 @@ func CommandResponseFromJson(data io.Reader) (*CommandResponse, error) {
|
||||
|
||||
o.Attachments = StringifySlackFieldValue(o.Attachments)
|
||||
|
||||
if o.ExtraResponses != nil {
|
||||
for _, resp := range o.ExtraResponses {
|
||||
resp.Attachments = StringifySlackFieldValue(resp.Attachments)
|
||||
}
|
||||
}
|
||||
|
||||
return &o, nil
|
||||
}
|
||||
|
||||
@@ -131,6 +131,71 @@ func TestCommandResponseFromJson(t *testing.T) {
|
||||
},
|
||||
false,
|
||||
},
|
||||
{
|
||||
"multiple responses returned",
|
||||
`
|
||||
{
|
||||
"text": "message 1",
|
||||
"extra_responses": [
|
||||
{"text": "message 2"}
|
||||
]
|
||||
}
|
||||
`,
|
||||
&CommandResponse{
|
||||
Text: "message 1",
|
||||
ExtraResponses: []*CommandResponse{
|
||||
&CommandResponse{
|
||||
Text: "message 2",
|
||||
},
|
||||
},
|
||||
},
|
||||
false,
|
||||
},
|
||||
{
|
||||
"multiple responses returned, with attachments",
|
||||
`
|
||||
{
|
||||
"text": "message 1",
|
||||
"attachments":[{"fields":[{"title":"foo","value":"bar","short":true}]}],
|
||||
"extra_responses": [
|
||||
{
|
||||
"text": "message 2",
|
||||
"attachments":[{"fields":[{"title":"foo 2","value":"bar 2","short":false}]}]
|
||||
}
|
||||
]
|
||||
}`,
|
||||
&CommandResponse{
|
||||
Text: "message 1",
|
||||
Attachments: []*SlackAttachment{
|
||||
{
|
||||
Fields: []*SlackAttachmentField{
|
||||
{
|
||||
Title: "foo",
|
||||
Value: "bar",
|
||||
Short: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
ExtraResponses: []*CommandResponse{
|
||||
&CommandResponse{
|
||||
Text: "message 2",
|
||||
Attachments: []*SlackAttachment{
|
||||
{
|
||||
Fields: []*SlackAttachmentField{
|
||||
{
|
||||
Title: "foo 2",
|
||||
Value: "bar 2",
|
||||
Short: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, testCase := range testCases {
|
||||
|
||||
266
model/integration_action.go
Обычный файл
266
model/integration_action.go
Обычный файл
@@ -0,0 +1,266 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See License.txt for license information.
|
||||
|
||||
package model
|
||||
|
||||
import (
|
||||
"crypto"
|
||||
"crypto/ecdsa"
|
||||
"crypto/rand"
|
||||
"encoding/asn1"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"io"
|
||||
"math/big"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const (
|
||||
POST_ACTION_TYPE_BUTTON = "button"
|
||||
POST_ACTION_TYPE_SELECT = "select"
|
||||
INTERACTIVE_DIALOG_TRIGGER_TIMEOUT_MILLISECONDS = 3000
|
||||
)
|
||||
|
||||
type DoPostActionRequest struct {
|
||||
SelectedOption string `json:"selected_option"`
|
||||
}
|
||||
|
||||
type PostAction struct {
|
||||
Id string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Type string `json:"type"`
|
||||
DataSource string `json:"data_source"`
|
||||
Options []*PostActionOptions `json:"options"`
|
||||
Integration *PostActionIntegration `json:"integration,omitempty"`
|
||||
}
|
||||
|
||||
type PostActionOptions struct {
|
||||
Text string `json:"text"`
|
||||
Value string `json:"value"`
|
||||
}
|
||||
|
||||
type PostActionIntegration struct {
|
||||
URL string `json:"url,omitempty"`
|
||||
Context map[string]interface{} `json:"context,omitempty"`
|
||||
}
|
||||
|
||||
type PostActionIntegrationRequest struct {
|
||||
UserId string `json:"user_id"`
|
||||
ChannelId string `json:"channel_id"`
|
||||
TeamId string `json:"team_id"`
|
||||
PostId string `json:"post_id"`
|
||||
TriggerId string `json:"trigger_id"`
|
||||
Type string `json:"type"`
|
||||
DataSource string `json:"data_source"`
|
||||
Context map[string]interface{} `json:"context,omitempty"`
|
||||
}
|
||||
|
||||
type PostActionIntegrationResponse struct {
|
||||
Update *Post `json:"update"`
|
||||
EphemeralText string `json:"ephemeral_text"`
|
||||
}
|
||||
|
||||
type PostActionAPIResponse struct {
|
||||
Status string `json:"status"` // needed to maintain backwards compatibility
|
||||
TriggerId string `json:"trigger_id"`
|
||||
}
|
||||
|
||||
type Dialog struct {
|
||||
CallbackId string `json:"callback_id"`
|
||||
Title string `json:"title"`
|
||||
IconURL string `json:"icon_url"`
|
||||
Elements []DialogElement `json:"elements"`
|
||||
SubmitLabel string `json:"submit_label"`
|
||||
NotifyOnCancel bool `json:"notify_on_cancel"`
|
||||
State string `json:"state"`
|
||||
}
|
||||
|
||||
type DialogElement struct {
|
||||
DisplayName string `json:"display_name"`
|
||||
Name string `json:"name"`
|
||||
Type string `json:"type"`
|
||||
SubType string `json:"subtype"`
|
||||
Default string `json:"default"`
|
||||
Placeholder string `json:"placeholder"`
|
||||
HelpText string `json:"help_text"`
|
||||
Optional bool `json:"optional"`
|
||||
MinLength int `json:"min_length"`
|
||||
MaxLength int `json:"max_length"`
|
||||
DataSource string `json:"data_source"`
|
||||
Options []*PostActionOptions `json:"options"`
|
||||
}
|
||||
|
||||
type OpenDialogRequest struct {
|
||||
TriggerId string `json:"trigger_id"`
|
||||
URL string `json:"url"`
|
||||
Dialog Dialog `json:"dialog"`
|
||||
}
|
||||
|
||||
type SubmitDialogRequest struct {
|
||||
Type string `json:"type"`
|
||||
URL string `json:"url,omitempty"`
|
||||
CallbackId string `json:"callback_id"`
|
||||
State string `json:"state"`
|
||||
UserId string `json:"user_id"`
|
||||
ChannelId string `json:"channel_id"`
|
||||
TeamId string `json:"team_id"`
|
||||
Submission map[string]interface{} `json:"submission"`
|
||||
Cancelled bool `json:"cancelled"`
|
||||
}
|
||||
|
||||
type SubmitDialogResponse struct {
|
||||
Errors map[string]string `json:"errors,omitempty"`
|
||||
}
|
||||
|
||||
func (r *PostActionIntegrationRequest) ToJson() []byte {
|
||||
b, _ := json.Marshal(r)
|
||||
return b
|
||||
}
|
||||
|
||||
func GenerateTriggerId(userId string, s crypto.Signer) (string, string, *AppError) {
|
||||
clientTriggerId := NewId()
|
||||
triggerData := strings.Join([]string{clientTriggerId, userId, strconv.FormatInt(GetMillis(), 10)}, ":") + ":"
|
||||
|
||||
h := crypto.SHA256
|
||||
sum := h.New()
|
||||
sum.Write([]byte(triggerData))
|
||||
signature, err := s.Sign(rand.Reader, sum.Sum(nil), h)
|
||||
if err != nil {
|
||||
return "", "", NewAppError("GenerateTriggerId", "interactive_message.generate_trigger_id.signing_failed", nil, err.Error(), http.StatusInternalServerError)
|
||||
}
|
||||
|
||||
base64Sig := base64.StdEncoding.EncodeToString(signature)
|
||||
|
||||
triggerId := base64.StdEncoding.EncodeToString([]byte(triggerData + base64Sig))
|
||||
return clientTriggerId, triggerId, nil
|
||||
}
|
||||
|
||||
func (r *PostActionIntegrationRequest) GenerateTriggerId(s crypto.Signer) (string, string, *AppError) {
|
||||
clientTriggerId, triggerId, err := GenerateTriggerId(r.UserId, s)
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
|
||||
r.TriggerId = triggerId
|
||||
return clientTriggerId, triggerId, nil
|
||||
}
|
||||
|
||||
func DecodeAndVerifyTriggerId(triggerId string, s *ecdsa.PrivateKey) (string, string, *AppError) {
|
||||
triggerIdBytes, err := base64.StdEncoding.DecodeString(triggerId)
|
||||
if err != nil {
|
||||
return "", "", NewAppError("DecodeAndVerifyTriggerId", "interactive_message.decode_trigger_id.base64_decode_failed", nil, err.Error(), http.StatusBadRequest)
|
||||
}
|
||||
|
||||
split := strings.Split(string(triggerIdBytes), ":")
|
||||
if len(split) != 4 {
|
||||
return "", "", NewAppError("DecodeAndVerifyTriggerId", "interactive_message.decode_trigger_id.missing_data", nil, "", http.StatusBadRequest)
|
||||
}
|
||||
|
||||
clientTriggerId := split[0]
|
||||
userId := split[1]
|
||||
timestampStr := split[2]
|
||||
timestamp, _ := strconv.ParseInt(timestampStr, 10, 64)
|
||||
|
||||
now := GetMillis()
|
||||
if now-timestamp > INTERACTIVE_DIALOG_TRIGGER_TIMEOUT_MILLISECONDS {
|
||||
return "", "", NewAppError("DecodeAndVerifyTriggerId", "interactive_message.decode_trigger_id.expired", map[string]interface{}{"Seconds": INTERACTIVE_DIALOG_TRIGGER_TIMEOUT_MILLISECONDS / 1000}, "", http.StatusBadRequest)
|
||||
}
|
||||
|
||||
signature, err := base64.StdEncoding.DecodeString(split[3])
|
||||
if err != nil {
|
||||
return "", "", NewAppError("DecodeAndVerifyTriggerId", "interactive_message.decode_trigger_id.base64_decode_failed_signature", nil, err.Error(), http.StatusBadRequest)
|
||||
}
|
||||
|
||||
var esig struct {
|
||||
R, S *big.Int
|
||||
}
|
||||
|
||||
if _, err := asn1.Unmarshal([]byte(signature), &esig); err != nil {
|
||||
return "", "", NewAppError("DecodeAndVerifyTriggerId", "interactive_message.decode_trigger_id.signature_decode_failed", nil, err.Error(), http.StatusBadRequest)
|
||||
}
|
||||
|
||||
triggerData := strings.Join([]string{clientTriggerId, userId, timestampStr}, ":") + ":"
|
||||
|
||||
h := crypto.SHA256
|
||||
sum := h.New()
|
||||
sum.Write([]byte(triggerData))
|
||||
|
||||
if !ecdsa.Verify(&s.PublicKey, sum.Sum(nil), esig.R, esig.S) {
|
||||
return "", "", NewAppError("DecodeAndVerifyTriggerId", "interactive_message.decode_trigger_id.verify_signature_failed", nil, "", http.StatusBadRequest)
|
||||
}
|
||||
|
||||
return clientTriggerId, userId, nil
|
||||
}
|
||||
|
||||
func (r *OpenDialogRequest) DecodeAndVerifyTriggerId(s *ecdsa.PrivateKey) (string, string, *AppError) {
|
||||
return DecodeAndVerifyTriggerId(r.TriggerId, s)
|
||||
}
|
||||
|
||||
func PostActionIntegrationRequestFromJson(data io.Reader) *PostActionIntegrationRequest {
|
||||
var o *PostActionIntegrationRequest
|
||||
err := json.NewDecoder(data).Decode(&o)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
return o
|
||||
}
|
||||
|
||||
func (r *PostActionIntegrationResponse) ToJson() []byte {
|
||||
b, _ := json.Marshal(r)
|
||||
return b
|
||||
}
|
||||
|
||||
func PostActionIntegrationResponseFromJson(data io.Reader) *PostActionIntegrationResponse {
|
||||
var o *PostActionIntegrationResponse
|
||||
err := json.NewDecoder(data).Decode(&o)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
return o
|
||||
}
|
||||
|
||||
func (o *Post) StripActionIntegrations() {
|
||||
attachments := o.Attachments()
|
||||
if o.Props["attachments"] != nil {
|
||||
o.Props["attachments"] = attachments
|
||||
}
|
||||
for _, attachment := range attachments {
|
||||
for _, action := range attachment.Actions {
|
||||
action.Integration = nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (o *Post) GetAction(id string) *PostAction {
|
||||
for _, attachment := range o.Attachments() {
|
||||
for _, action := range attachment.Actions {
|
||||
if action.Id == id {
|
||||
return action
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (o *Post) GenerateActionIds() {
|
||||
if o.Props["attachments"] != nil {
|
||||
o.Props["attachments"] = o.Attachments()
|
||||
}
|
||||
if attachments, ok := o.Props["attachments"].([]*SlackAttachment); ok {
|
||||
for _, attachment := range attachments {
|
||||
for _, action := range attachment.Actions {
|
||||
if action.Id == "" {
|
||||
action.Id = NewId()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func DoPostActionRequestFromJson(data io.Reader) *DoPostActionRequest {
|
||||
var o *DoPostActionRequest
|
||||
json.NewDecoder(data).Decode(&o)
|
||||
return o
|
||||
}
|
||||
111
model/integration_action_test.go
Обычный файл
111
model/integration_action_test.go
Обычный файл
@@ -0,0 +1,111 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See License.txt for license information.
|
||||
|
||||
package model
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/ecdsa"
|
||||
"crypto/elliptic"
|
||||
"crypto/rand"
|
||||
"encoding/base64"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestTriggerIdDecodeAndVerification(t *testing.T) {
|
||||
|
||||
key, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
|
||||
require.Nil(t, err)
|
||||
|
||||
t.Run("should succeed decoding and validation", func(t *testing.T) {
|
||||
userId := NewId()
|
||||
clientTriggerId, triggerId, err := GenerateTriggerId(userId, key)
|
||||
decodedClientTriggerId, decodedUserId, err := DecodeAndVerifyTriggerId(triggerId, key)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, clientTriggerId, decodedClientTriggerId)
|
||||
assert.Equal(t, userId, decodedUserId)
|
||||
})
|
||||
|
||||
t.Run("should succeed decoding and validation through request structs", func(t *testing.T) {
|
||||
actionReq := &PostActionIntegrationRequest{
|
||||
UserId: NewId(),
|
||||
}
|
||||
clientTriggerId, triggerId, err := actionReq.GenerateTriggerId(key)
|
||||
dialogReq := &OpenDialogRequest{TriggerId: triggerId}
|
||||
decodedClientTriggerId, decodedUserId, err := dialogReq.DecodeAndVerifyTriggerId(key)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, clientTriggerId, decodedClientTriggerId)
|
||||
assert.Equal(t, actionReq.UserId, decodedUserId)
|
||||
})
|
||||
|
||||
t.Run("should fail on base64 decode", func(t *testing.T) {
|
||||
_, _, err := DecodeAndVerifyTriggerId("junk!", key)
|
||||
require.NotNil(t, err)
|
||||
assert.Equal(t, "interactive_message.decode_trigger_id.base64_decode_failed", err.Id)
|
||||
})
|
||||
|
||||
t.Run("should fail on trigger parsing", func(t *testing.T) {
|
||||
_, _, err := DecodeAndVerifyTriggerId(base64.StdEncoding.EncodeToString([]byte("junk!")), key)
|
||||
require.NotNil(t, err)
|
||||
assert.Equal(t, "interactive_message.decode_trigger_id.missing_data", err.Id)
|
||||
})
|
||||
|
||||
t.Run("should fail on expired timestamp", func(t *testing.T) {
|
||||
_, _, err := DecodeAndVerifyTriggerId(base64.StdEncoding.EncodeToString([]byte("some-trigger-id:some-user-id:1234567890:junksignature")), key)
|
||||
require.NotNil(t, err)
|
||||
assert.Equal(t, "interactive_message.decode_trigger_id.expired", err.Id)
|
||||
})
|
||||
|
||||
t.Run("should fail on base64 decoding signature", func(t *testing.T) {
|
||||
_, _, err := DecodeAndVerifyTriggerId(base64.StdEncoding.EncodeToString([]byte("some-trigger-id:some-user-id:12345678900000:junk!")), key)
|
||||
require.NotNil(t, err)
|
||||
assert.Equal(t, "interactive_message.decode_trigger_id.base64_decode_failed_signature", err.Id)
|
||||
})
|
||||
|
||||
t.Run("should fail on bad signature", func(t *testing.T) {
|
||||
_, _, err := DecodeAndVerifyTriggerId(base64.StdEncoding.EncodeToString([]byte("some-trigger-id:some-user-id:12345678900000:junk")), key)
|
||||
require.NotNil(t, err)
|
||||
assert.Equal(t, "interactive_message.decode_trigger_id.signature_decode_failed", err.Id)
|
||||
})
|
||||
|
||||
t.Run("should fail on bad key", func(t *testing.T) {
|
||||
_, triggerId, err := GenerateTriggerId(NewId(), key)
|
||||
newKey, keyErr := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
|
||||
require.Nil(t, keyErr)
|
||||
_, _, err = DecodeAndVerifyTriggerId(triggerId, newKey)
|
||||
require.NotNil(t, err)
|
||||
assert.Equal(t, "interactive_message.decode_trigger_id.verify_signature_failed", err.Id)
|
||||
})
|
||||
}
|
||||
|
||||
func TestPostActionIntegrationRequestToJson(t *testing.T) {
|
||||
o := PostActionIntegrationRequest{UserId: NewId(), Context: StringInterface{"a": "abc"}}
|
||||
j := o.ToJson()
|
||||
ro := PostActionIntegrationRequestFromJson(bytes.NewReader(j))
|
||||
|
||||
assert.NotNil(t, ro)
|
||||
assert.Equal(t, o, *ro)
|
||||
}
|
||||
|
||||
func TestPostActionIntegrationRequestFromJsonError(t *testing.T) {
|
||||
ro := PostActionIntegrationRequestFromJson(strings.NewReader(""))
|
||||
assert.Nil(t, ro)
|
||||
}
|
||||
|
||||
func TestPostActionIntegrationResponseToJson(t *testing.T) {
|
||||
o := PostActionIntegrationResponse{Update: &Post{Id: NewId(), Message: NewId()}, EphemeralText: NewId()}
|
||||
j := o.ToJson()
|
||||
ro := PostActionIntegrationResponseFromJson(bytes.NewReader(j))
|
||||
|
||||
assert.NotNil(t, ro)
|
||||
assert.Equal(t, o, *ro)
|
||||
}
|
||||
|
||||
func TestPostActionIntegrationResponseFromJsonError(t *testing.T) {
|
||||
ro := PostActionIntegrationResponseFromJson(strings.NewReader(""))
|
||||
assert.Nil(t, ro)
|
||||
}
|
||||
112
model/post.go
112
model/post.go
@@ -50,8 +50,6 @@ const (
|
||||
PROPS_ADD_CHANNEL_MEMBER = "add_channel_member"
|
||||
POST_PROPS_ADDED_USER_ID = "addedUserId"
|
||||
POST_PROPS_DELETE_BY = "deleteBy"
|
||||
POST_ACTION_TYPE_BUTTON = "button"
|
||||
POST_ACTION_TYPE_SELECT = "select"
|
||||
)
|
||||
|
||||
type Post struct {
|
||||
@@ -135,44 +133,6 @@ type PostForIndexing struct {
|
||||
ParentCreateAt *int64 `json:"parent_create_at"`
|
||||
}
|
||||
|
||||
type DoPostActionRequest struct {
|
||||
SelectedOption string `json:"selected_option"`
|
||||
}
|
||||
|
||||
type PostAction struct {
|
||||
Id string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Type string `json:"type"`
|
||||
DataSource string `json:"data_source"`
|
||||
Options []*PostActionOptions `json:"options"`
|
||||
Integration *PostActionIntegration `json:"integration,omitempty"`
|
||||
}
|
||||
|
||||
type PostActionOptions struct {
|
||||
Text string `json:"text"`
|
||||
Value string `json:"value"`
|
||||
}
|
||||
|
||||
type PostActionIntegration struct {
|
||||
URL string `json:"url,omitempty"`
|
||||
Context StringInterface `json:"context,omitempty"`
|
||||
}
|
||||
|
||||
type PostActionIntegrationRequest struct {
|
||||
UserId string `json:"user_id"`
|
||||
ChannelId string `json:"channel_id"`
|
||||
TeamId string `json:"team_id"`
|
||||
PostId string `json:"post_id"`
|
||||
Type string `json:"type"`
|
||||
DataSource string `json:"data_source"`
|
||||
Context StringInterface `json:"context,omitempty"`
|
||||
}
|
||||
|
||||
type PostActionIntegrationResponse struct {
|
||||
Update *Post `json:"update"`
|
||||
EphemeralText string `json:"ephemeral_text"`
|
||||
}
|
||||
|
||||
// Shallowly clone the a post
|
||||
func (o *Post) Clone() *Post {
|
||||
copy := *o
|
||||
@@ -416,34 +376,6 @@ func (o *Post) ChannelMentions() []string {
|
||||
return ChannelMentions(o.Message)
|
||||
}
|
||||
|
||||
func (r *PostActionIntegrationRequest) ToJson() string {
|
||||
b, _ := json.Marshal(r)
|
||||
return string(b)
|
||||
}
|
||||
|
||||
func PostActionIntegrationRequesteFromJson(data io.Reader) *PostActionIntegrationRequest {
|
||||
var o *PostActionIntegrationRequest
|
||||
err := json.NewDecoder(data).Decode(&o)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
return o
|
||||
}
|
||||
|
||||
func (r *PostActionIntegrationResponse) ToJson() string {
|
||||
b, _ := json.Marshal(r)
|
||||
return string(b)
|
||||
}
|
||||
|
||||
func PostActionIntegrationResponseFromJson(data io.Reader) *PostActionIntegrationResponse {
|
||||
var o *PostActionIntegrationResponse
|
||||
err := json.NewDecoder(data).Decode(&o)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
return o
|
||||
}
|
||||
|
||||
func (o *Post) Attachments() []*SlackAttachment {
|
||||
if attachments, ok := o.Props["attachments"].([]*SlackAttachment); ok {
|
||||
return attachments
|
||||
@@ -462,44 +394,6 @@ func (o *Post) Attachments() []*SlackAttachment {
|
||||
return ret
|
||||
}
|
||||
|
||||
func (o *Post) StripActionIntegrations() {
|
||||
attachments := o.Attachments()
|
||||
if o.Props["attachments"] != nil {
|
||||
o.Props["attachments"] = attachments
|
||||
}
|
||||
for _, attachment := range attachments {
|
||||
for _, action := range attachment.Actions {
|
||||
action.Integration = nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (o *Post) GetAction(id string) *PostAction {
|
||||
for _, attachment := range o.Attachments() {
|
||||
for _, action := range attachment.Actions {
|
||||
if action.Id == id {
|
||||
return action
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (o *Post) GenerateActionIds() {
|
||||
if o.Props["attachments"] != nil {
|
||||
o.Props["attachments"] = o.Attachments()
|
||||
}
|
||||
if attachments, ok := o.Props["attachments"].([]*SlackAttachment); ok {
|
||||
for _, attachment := range attachments {
|
||||
for _, action := range attachment.Actions {
|
||||
if action.Id == "" {
|
||||
action.Id = NewId()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var markdownDestinationEscaper = strings.NewReplacer(
|
||||
`\`, `\\`,
|
||||
`<`, `\<`,
|
||||
@@ -524,12 +418,6 @@ func (o *PostEphemeral) ToUnsanitizedJson() string {
|
||||
return string(b)
|
||||
}
|
||||
|
||||
func DoPostActionRequestFromJson(data io.Reader) *DoPostActionRequest {
|
||||
var o *DoPostActionRequest
|
||||
json.NewDecoder(data).Decode(&o)
|
||||
return o
|
||||
}
|
||||
|
||||
// RewriteImageURLs takes a message and returns a copy that has all of the image URLs replaced
|
||||
// according to the function f. For each image URL, f will be invoked, and the resulting markdown
|
||||
// will contain the URL returned by that invocation instead.
|
||||
|
||||
@@ -25,34 +25,6 @@ func TestPostFromJsonError(t *testing.T) {
|
||||
assert.Nil(t, ro)
|
||||
}
|
||||
|
||||
func TestPostActionIntegrationRequestToJson(t *testing.T) {
|
||||
o := PostActionIntegrationRequest{UserId: NewId(), Context: StringInterface{"a": "abc"}}
|
||||
j := o.ToJson()
|
||||
ro := PostActionIntegrationRequesteFromJson(strings.NewReader(j))
|
||||
|
||||
assert.NotNil(t, ro)
|
||||
assert.Equal(t, o, *ro)
|
||||
}
|
||||
|
||||
func TestPostActionIntegrationRequestFromJsonError(t *testing.T) {
|
||||
ro := PostActionIntegrationRequesteFromJson(strings.NewReader(""))
|
||||
assert.Nil(t, ro)
|
||||
}
|
||||
|
||||
func TestPostActionIntegrationResponseToJson(t *testing.T) {
|
||||
o := PostActionIntegrationResponse{Update: &Post{Id: NewId(), Message: NewId()}, EphemeralText: NewId()}
|
||||
j := o.ToJson()
|
||||
ro := PostActionIntegrationResponseFromJson(strings.NewReader(j))
|
||||
|
||||
assert.NotNil(t, ro)
|
||||
assert.Equal(t, o, *ro)
|
||||
}
|
||||
|
||||
func TestPostActionIntegrationResponseFromJsonError(t *testing.T) {
|
||||
ro := PostActionIntegrationResponseFromJson(strings.NewReader(""))
|
||||
assert.Nil(t, ro)
|
||||
}
|
||||
|
||||
func TestPostIsValid(t *testing.T) {
|
||||
o := Post{}
|
||||
maxPostSize := 10000
|
||||
|
||||
@@ -49,6 +49,7 @@ const (
|
||||
WEBSOCKET_EVENT_ROLE_UPDATED = "role_updated"
|
||||
WEBSOCKET_EVENT_LICENSE_CHANGED = "license_changed"
|
||||
WEBSOCKET_EVENT_CONFIG_CHANGED = "config_changed"
|
||||
WEBSOCKET_EVENT_OPEN_DIALOG = "open_dialog"
|
||||
)
|
||||
|
||||
type WebSocketMessage interface {
|
||||
|
||||
Ссылка в новой задаче
Block a user