Move cmd execution to REST endpoint, remove /echo, add /me

This commit is contained in:
Denis Arh
2019-04-26 07:18:18 +02:00
parent 43a5693ee5
commit ed078ce456
12 changed files with 171 additions and 71 deletions
+30
View File
@@ -372,6 +372,36 @@
]
}
},
{
"name": "executeCommand",
"path": "/command/{command}/exec",
"method": "POST",
"title": "Execute command",
"parameters": {
"path": [
{
"name": "command",
"type": "string",
"required": true,
"title": "Command to be executed"
}
],
"post": [
{
"type": "string",
"name": "input",
"required": false,
"title": "Arbitrary command input"
},
{
"type": "[]string",
"name": "params",
"required": false,
"title": "Command parameters"
}
]
}
},
{
"name": "history",
"path": "/",
+30
View File
@@ -35,6 +35,36 @@
]
}
},
{
"Name": "executeCommand",
"Method": "POST",
"Title": "Execute command",
"Path": "/command/{command}/exec",
"Parameters": {
"path": [
{
"name": "command",
"required": true,
"title": "Command to be executed",
"type": "string"
}
],
"post": [
{
"name": "input",
"required": false,
"title": "Arbitrary command input",
"type": "string"
},
{
"name": "params",
"required": false,
"title": "Command parameters",
"type": "[]string"
}
]
}
},
{
"Name": "history",
"Method": "GET",
+18
View File
@@ -300,6 +300,7 @@ The following event types may be sent with a message event:
| Method | Endpoint | Purpose |
| ------ | -------- | ------- |
| `POST` | `/channels/{channelID}/messages/` | Post new message to the channel |
| `POST` | `/channels/{channelID}/messages/command/{command}/exec` | Execute command |
| `GET` | `/channels/{channelID}/messages/` | All messages (channel history) |
| `GET` | `/channels/{channelID}/messages/mark-as-read` | Manages read/unread messages in a channel or a thread |
| `PUT` | `/channels/{channelID}/messages/{messageID}` | Edit existing message |
@@ -328,6 +329,23 @@ The following event types may be sent with a message event:
| message | string | POST | Message contents (markdown) | N/A | YES |
| channelID | uint64 | PATH | Channel ID | N/A | YES |
## Execute command
#### Method
| URI | Protocol | Method | Authentication |
| --- | -------- | ------ | -------------- |
| `/channels/{channelID}/messages/command/{command}/exec` | HTTP/S | POST | Client ID, Session ID |
#### Request parameters
| Parameter | Type | Method | Description | Default | Required? |
| --------- | ---- | ------ | ----------- | ------- | --------- |
| command | string | PATH | Command to be executed | N/A | YES |
| channelID | uint64 | PATH | Channel ID | N/A | YES |
| input | string | POST | Arbitrary command input | N/A | NO |
| params | []string | POST | Command parameters | N/A | NO |
## All messages (channel history)
#### Method
-10
View File
@@ -1,10 +0,0 @@
package incoming
type (
ExecCommand struct {
ChannelID string `json:"channelId"`
Command string `json:"command"`
Params map[string]string `json:"params"`
Input string `json:"input"`
}
)
-2
View File
@@ -25,6 +25,4 @@ type Payload struct {
*MessageDelete `json:"deleteMessage"`
*Users `json:"getUsers"`
*ExecCommand `json:"exec"`
}
+2 -2
View File
@@ -22,8 +22,8 @@ func (Commands) New() *Commands {
func (ctrl *Commands) List(ctx context.Context, r *request.CommandsList) (interface{}, error) {
return types.CommandSet{
&types.Command{
Name: "echo",
Description: "It does exactly what it says on the tin"},
Name: "me",
Description: "Illeism"},
&types.Command{
Name: "shrug",
Description: "It does exactly what it says on the tin"},
+10
View File
@@ -29,6 +29,7 @@ import (
// Internal API interface
type MessageAPI interface {
Create(context.Context, *request.MessageCreate) (interface{}, error)
ExecuteCommand(context.Context, *request.MessageExecuteCommand) (interface{}, error)
History(context.Context, *request.MessageHistory) (interface{}, error)
MarkAsRead(context.Context, *request.MessageMarkAsRead) (interface{}, error)
Edit(context.Context, *request.MessageEdit) (interface{}, error)
@@ -46,6 +47,7 @@ type MessageAPI interface {
// HTTP API interface
type Message struct {
Create func(http.ResponseWriter, *http.Request)
ExecuteCommand func(http.ResponseWriter, *http.Request)
History func(http.ResponseWriter, *http.Request)
MarkAsRead func(http.ResponseWriter, *http.Request)
Edit func(http.ResponseWriter, *http.Request)
@@ -69,6 +71,13 @@ func NewMessage(mh MessageAPI) *Message {
return mh.Create(r.Context(), params)
})
},
ExecuteCommand: func(w http.ResponseWriter, r *http.Request) {
defer r.Body.Close()
params := request.NewMessageExecuteCommand()
resputil.JSON(w, params.Fill(r), func() (interface{}, error) {
return mh.ExecuteCommand(r.Context(), params)
})
},
History: func(w http.ResponseWriter, r *http.Request) {
defer r.Body.Close()
params := request.NewMessageHistory()
@@ -160,6 +169,7 @@ func (mh *Message) MountRoutes(r chi.Router, middlewares ...func(http.Handler) h
r.Group(func(r chi.Router) {
r.Use(middlewares...)
r.Post("/channels/{channelID}/messages/", mh.Create)
r.Post("/channels/{channelID}/messages/command/{command}/exec", mh.ExecuteCommand)
r.Get("/channels/{channelID}/messages/", mh.History)
r.Get("/channels/{channelID}/messages/mark-as-read", mh.MarkAsRead)
r.Put("/channels/{channelID}/messages/{messageID}", mh.Edit)
+29
View File
@@ -65,6 +65,35 @@ 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")
}
func (ctrl *Message) Delete(ctx context.Context, r *request.MessageDelete) (interface{}, error) {
return nil, ctrl.svc.msg.With(ctx).Delete(r.MessageID)
}
+51
View File
@@ -78,6 +78,57 @@ func (mReq *MessageCreate) Fill(r *http.Request) (err error) {
var _ RequestFiller = NewMessageCreate()
// Message executeCommand request parameters
type MessageExecuteCommand struct {
Command string
ChannelID uint64 `json:",string"`
Input string
Params []string
}
func NewMessageExecuteCommand() *MessageExecuteCommand {
return &MessageExecuteCommand{}
}
func (mReq *MessageExecuteCommand) Fill(r *http.Request) (err error) {
if strings.ToLower(r.Header.Get("content-type")) == "application/json" {
err = json.NewDecoder(r.Body).Decode(mReq)
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])
}
mReq.Command = chi.URLParam(r, "command")
mReq.ChannelID = parseUInt64(chi.URLParam(r, "channelID"))
if val, ok := post["input"]; ok {
mReq.Input = val
}
return err
}
var _ RequestFiller = NewMessageExecuteCommand()
// Message history request parameters
type MessageHistory struct {
LastMessageID uint64 `json:",string"`
+1
View File
@@ -82,6 +82,7 @@ const (
MessageTypeChannelEvent = "channelEvent"
MessageTypeInlineImage = "inlineImage"
MessageTypeAttachment = "attachment"
MessageTypeIlleism = "illeism"
)
func (mtype MessageType) String() string {
-3
View File
@@ -49,9 +49,6 @@ func (s *Session) dispatch(raw []byte) error {
case p.Users != nil:
return s.userList(ctx, p.Users)
case p.ExecCommand != nil:
return s.execCommand(ctx, p.ExecCommand)
}
return nil
@@ -1,54 +0,0 @@
package websocket
import (
"context"
"log"
"time"
"github.com/titpetric/factory"
"github.com/crusttech/crust/internal/payload"
"github.com/crusttech/crust/internal/payload/incoming"
"github.com/crusttech/crust/internal/payload/outgoing"
"github.com/crusttech/crust/messaging/types"
systemService "github.com/crusttech/crust/system/service"
)
func (s *Session) execCommand(ctx context.Context, c *incoming.ExecCommand) error {
// @todo: check access / can we join this channel (should be done by service layer)
log.Printf("Received command '%s(%v)", c.Command, c.Params)
switch c.Command {
case "echo":
if c.Input != "" {
user, err := systemService.User(ctx).FindByID(s.user.Identity())
if err != nil {
return err
}
return s.sendReply(&outgoing.Message{
ID: factory.Sonyflake.NextID(),
User: payload.User(user),
CreatedAt: time.Now(),
Type: "hallucination",
ChannelID: c.ChannelID,
Message: c.Input})
}
case "shrug":
msg := &types.Message{
ChannelID: payload.ParseUInt64(c.ChannelID),
Message: "¯\\_(ツ)_/¯",
}
if c.Input != "" {
msg.Message = c.Input + " " + msg.Message
}
_, err := s.svc.msg.With(ctx).Create(msg)
return err
}
return nil
}