From b0239761f7793097331ce8dac593d9fa0b53a8aa Mon Sep 17 00:00:00 2001 From: Denis Arh Date: Fri, 26 Apr 2019 13:21:31 +0200 Subject: [PATCH] User/chan/messaging activity refactored --- api/messaging/spec.json | 39 +++++++++ api/messaging/spec/activity.json | 39 +++++++++ docs/messaging/README.md | 27 ++++++ internal/payload/incoming/channel.go | 6 -- internal/payload/incoming/messages.go | 7 -- internal/payload/incoming/payload.go | 4 - internal/payload/outgoing.go | 9 ++ internal/payload/outgoing/activity.go | 17 ++++ internal/payload/outgoing/channel.go | 11 --- internal/payload/outgoing/message.go | 12 --- internal/payload/outgoing/payload.go | 5 +- messaging/internal/service/events.go | 6 ++ messaging/rest/activity.go | 38 ++++++++ messaging/rest/handlers/activity.go | 56 ++++++++++++ messaging/rest/message.go | 4 +- messaging/rest/request/activity.go | 87 +++++++++++++++++++ messaging/rest/router.go | 1 + messaging/types/activity.go | 10 +++ messaging/websocket/session_incoming.go | 5 -- .../websocket/session_incoming_channel.go | 19 ---- 20 files changed, 334 insertions(+), 68 deletions(-) create mode 100644 api/messaging/spec/activity.json create mode 100644 internal/payload/outgoing/activity.go create mode 100644 messaging/rest/activity.go create mode 100644 messaging/rest/handlers/activity.go create mode 100644 messaging/rest/request/activity.go create mode 100644 messaging/types/activity.go diff --git a/api/messaging/spec.json b/api/messaging/spec.json index 29fc0dec3..7b9a6801c 100644 --- a/api/messaging/spec.json +++ b/api/messaging/spec.json @@ -88,6 +88,45 @@ } ] }, + { + "name": "activity", + "path": "/activity", + "entrypoint": "activity", + "title": "User activity", + "parameters": {}, + "authentication": [], + + "apis": [ + { + "name": "send", + "method": "POST", + "title": "Sends user's activity to all subscribers; globally or per channel/message.", + "path": "/", + "parameters": { + "post": [ + { + "name": "channelID", + "type": "uint64", + "required": false, + "title": "Channel ID, if set, activity will be send only to subscribed users" + }, + { + "name": "messageID", + "type": "uint64", + "required": false, + "title": "Message ID, if set, channelID must be set as well" + }, + { + "name": "kind", + "type": "string", + "required": true, + "title": "Arbitrary string" + } + ] + } + } + ] + }, { "title": "Channels", "description": "A channel is a representation of a sequence of messages. It has meta data like channel subject. Channels may be public, private or group.", diff --git a/api/messaging/spec/activity.json b/api/messaging/spec/activity.json new file mode 100644 index 000000000..aac96e2b1 --- /dev/null +++ b/api/messaging/spec/activity.json @@ -0,0 +1,39 @@ +{ + "Title": "User activity", + "Interface": "Activity", + "Struct": null, + "Parameters": {}, + "Protocol": "", + "Authentication": [], + "Path": "/activity", + "APIs": [ + { + "Name": "send", + "Method": "POST", + "Title": "Sends user's activity to all subscribers; globally or per channel/message.", + "Path": "/", + "Parameters": { + "post": [ + { + "name": "channelID", + "required": false, + "title": "Channel ID, if set, activity will be send only to subscribed users", + "type": "uint64" + }, + { + "name": "messageID", + "required": false, + "title": "Message ID, if set, channelID must be set as well", + "type": "uint64" + }, + { + "name": "kind", + "required": true, + "title": "Arbitrary string", + "type": "string" + } + ] + } + } + ] +} \ No newline at end of file diff --git a/docs/messaging/README.md b/docs/messaging/README.md index 7c9037f07..55e3811a4 100644 --- a/docs/messaging/README.md +++ b/docs/messaging/README.md @@ -1,3 +1,30 @@ +# User activity + +| Method | Endpoint | Purpose | +| ------ | -------- | ------- | +| `POST` | `/activity/` | Sends user's activity to all subscribers; globally or per channel/message. | + +## Sends user's activity to all subscribers; globally or per channel/message. + +#### Method + +| URI | Protocol | Method | Authentication | +| --- | -------- | ------ | -------------- | +| `/activity/` | HTTP/S | POST | | + +#### Request parameters + +| Parameter | Type | Method | Description | Default | Required? | +| --------- | ---- | ------ | ----------- | ------- | --------- | +| channelID | uint64 | POST | Channel ID, if set, activity will be send only to subscribed users | N/A | NO | +| messageID | uint64 | POST | Message ID, if set, channelID must be set as well | N/A | NO | +| kind | string | POST | Arbitrary string | N/A | YES | + +--- + + + + # Attachments | Method | Endpoint | Purpose | diff --git a/internal/payload/incoming/channel.go b/internal/payload/incoming/channel.go index e979c8e93..fcd94b63f 100644 --- a/internal/payload/incoming/channel.go +++ b/internal/payload/incoming/channel.go @@ -29,10 +29,4 @@ type ( Topic *string `json:"topic"` Type *string `json:"type"` } - - // ChannelActivity is sent from the client when there is an activity on the channel... - ChannelActivity struct { - ChannelID uint64 `json:"ID,string"` - Kind string `json:"kind,omitempty"` - } ) diff --git a/internal/payload/incoming/messages.go b/internal/payload/incoming/messages.go index ec0e2c62b..c1f928ade 100644 --- a/internal/payload/incoming/messages.go +++ b/internal/payload/incoming/messages.go @@ -15,11 +15,4 @@ type ( MessageDelete struct { ID string `json:"messageID"` } - - // MessageActivity is sent from the client when there is an activity on the message... - MessageActivity struct { - MessageID uint64 `json:"messageID,string"` - ChannelID uint64 `json:"channelID,string"` - Kind string `json:"kind,omitempty"` - } ) diff --git a/internal/payload/incoming/payload.go b/internal/payload/incoming/payload.go index f179b59db..b1f749e13 100644 --- a/internal/payload/incoming/payload.go +++ b/internal/payload/incoming/payload.go @@ -11,10 +11,6 @@ type Payload struct { *ChannelViewRecord `json:"recordChannelView"` - *ChannelActivity `json:"channelActivity"` - - *MessageActivity `json:"messageActivity"` - // Message actions *MessageCreate `json:"createMessage"` *MessageUpdate `json:"updateMessage"` diff --git a/internal/payload/outgoing.go b/internal/payload/outgoing.go index a5f72be74..f56169b6c 100644 --- a/internal/payload/outgoing.go +++ b/internal/payload/outgoing.go @@ -16,6 +16,15 @@ const ( attachmentPreviewURL = "/attachment/%d/preview.%s" ) +func Activity(a *messagingTypes.Activity) *outgoing.Activity { + return &outgoing.Activity{ + MessageID: a.MessageID, + ChannelID: a.ChannelID, + Kind: a.Kind, + UserID: a.UserID, + } +} + func Message(ctx context.Context, msg *messagingTypes.Message) *outgoing.Message { var currentUserID = auth.GetIdentityFromContext(ctx).Identity() var canEdit = msg.Type.IsEditable() && msg.UserID == currentUserID diff --git a/internal/payload/outgoing/activity.go b/internal/payload/outgoing/activity.go new file mode 100644 index 000000000..02d55683c --- /dev/null +++ b/internal/payload/outgoing/activity.go @@ -0,0 +1,17 @@ +package outgoing + +import "encoding/json" + +type ( + // where the activity is and who is active + Activity struct { + UserID uint64 `json:"userID,string"` + Kind string `json:"kind,omitempty"` + MessageID uint64 `json:"messageID,string,omitempty"` + ChannelID uint64 `json:"channelID,string,omitempty"` + } +) + +func (p *Activity) EncodeMessage() ([]byte, error) { + return json.Marshal(Payload{Activity: p}) +} diff --git a/internal/payload/outgoing/channel.go b/internal/payload/outgoing/channel.go index f221bb2a1..345bb6ab6 100644 --- a/internal/payload/outgoing/channel.go +++ b/internal/payload/outgoing/channel.go @@ -22,13 +22,6 @@ type ( UserID string `json:"userID"` } - // ChannelActivity indicates where the activity is and who is active - ChannelActivity struct { - ID uint64 `json:"channelID,string"` - UserID uint64 `json:"userID,string"` - Kind string `json:"kind,omitempty"` - } - Channel struct { // Channel to part (nil) for ALL channels ID string `json:"ID"` @@ -67,10 +60,6 @@ func (p *ChannelPart) EncodeMessage() ([]byte, error) { return json.Marshal(Payload{ChannelPart: p}) } -func (p *ChannelActivity) EncodeMessage() ([]byte, error) { - return json.Marshal(Payload{ChannelActivity: p}) -} - func (p *Channel) EncodeMessage() ([]byte, error) { return json.Marshal(Payload{Channel: p}) } diff --git a/internal/payload/outgoing/message.go b/internal/payload/outgoing/message.go index 6ba979d60..8e82b761f 100644 --- a/internal/payload/outgoing/message.go +++ b/internal/payload/outgoing/message.go @@ -61,14 +61,6 @@ type ( } MessagePinRemoved MessagePin - - // ChannelActivity indicates where the activity is and who is active - MessageActivity struct { - ID uint64 `json:"messageID,string"` - ChannelID uint64 `json:"channelID,string"` - UserID uint64 `json:"userID,string"` - Kind string `json:"kind,omitempty"` - } ) func (p *Message) EncodeMessage() ([]byte, error) { @@ -94,7 +86,3 @@ func (p *MessagePin) EncodeMessage() ([]byte, error) { func (p *MessagePinRemoved) EncodeMessage() ([]byte, error) { return json.Marshal(Payload{MessagePinRemoved: p}) } - -func (p *MessageActivity) EncodeMessage() ([]byte, error) { - return json.Marshal(Payload{MessageActivity: p}) -} diff --git a/internal/payload/outgoing/payload.go b/internal/payload/outgoing/payload.go index 9f1f4edb0..465468a85 100644 --- a/internal/payload/outgoing/payload.go +++ b/internal/payload/outgoing/payload.go @@ -10,19 +10,18 @@ type ( *Message `json:"message,omitempty"` *MessageSet `json:"messages,omitempty"` + *Activity `json:"activity,omitempty"` + *MessageReaction `json:"messageReaction,omitempty"` *MessageReactionRemoved `json:"messageReactionRemoved,omitempty"` *MessagePin `json:"messagePin,omitempty"` *MessagePinRemoved `json:"messagePinRemoved,omitempty"` - *MessageActivity `json:"messageActivity,omitempty"` *ChannelJoin `json:"channelJoin,omitempty"` *ChannelPart `json:"channelPart,omitempty"` *Channel `json:"channel,omitempty"` *ChannelSet `json:"channels,omitempty"` - *ChannelActivity `json:"channelActivity,omitempty"` - *ChannelMember `json:"channelMember,omitempty"` *ChannelMemberSet `json:"channelMembers,omitempty"` diff --git a/messaging/internal/service/events.go b/messaging/internal/service/events.go index 199b41924..488ba2b61 100644 --- a/messaging/internal/service/events.go +++ b/messaging/internal/service/events.go @@ -18,6 +18,7 @@ type ( EventService interface { With(ctx context.Context) EventService + Activity(a *types.Activity) error Message(m *types.Message) error MessageFlag(m *types.MessageFlag) error Channel(m *types.Channel) error @@ -43,6 +44,11 @@ func (svc *event) Message(m *types.Message) error { return svc.push(payload.Message(svc.ctx, m), types.EventQueueItemSubTypeChannel, m.ChannelID) } +// Activity sends activity event to subscribers +func (svc event) Activity(a *types.Activity) error { + return svc.push(payload.Activity(a), types.EventQueueItemSubTypeChannel, a.ChannelID) +} + // MessageFlag sends message flag events to subscribers func (svc *event) MessageFlag(f *types.MessageFlag) error { var p outgoing.MessageEncoder diff --git a/messaging/rest/activity.go b/messaging/rest/activity.go new file mode 100644 index 000000000..376719cbe --- /dev/null +++ b/messaging/rest/activity.go @@ -0,0 +1,38 @@ +package rest + +import ( + "context" + + "github.com/pkg/errors" + + "github.com/crusttech/crust/internal/auth" + "github.com/crusttech/crust/messaging/internal/service" + "github.com/crusttech/crust/messaging/rest/request" + "github.com/crusttech/crust/messaging/types" +) + +var _ = errors.Wrap + +type Activity struct { + event service.EventService +} + +func (Activity) New() *Activity { + ctrl := &Activity{} + ctrl.event = service.DefaultEvent + return ctrl +} + +// SendActivity Forwards channel activity to event service +func (ctrl *Activity) Send(ctx context.Context, r *request.ActivitySend) (interface{}, error) { + if r.ChannelID == 0 && r.MessageID > 0 { + return nil, errors.New("can not send activity on message without channel ID") + } + + return true, ctrl.event.With(ctx).Activity(&types.Activity{ + UserID: auth.GetIdentityFromContext(ctx).Identity(), + ChannelID: r.ChannelID, + MessageID: r.MessageID, + Kind: r.Kind, + }) +} diff --git a/messaging/rest/handlers/activity.go b/messaging/rest/handlers/activity.go new file mode 100644 index 000000000..a6344cf97 --- /dev/null +++ b/messaging/rest/handlers/activity.go @@ -0,0 +1,56 @@ +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 `activity.go`, `activity.util.go` or `activity_test.go` to + implement your API calls, helper functions and tests. The file `activity.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 ActivityAPI interface { + Send(context.Context, *request.ActivitySend) (interface{}, error) +} + +// HTTP API interface +type Activity struct { + Send func(http.ResponseWriter, *http.Request) +} + +func NewActivity(ah ActivityAPI) *Activity { + return &Activity{ + Send: func(w http.ResponseWriter, r *http.Request) { + defer r.Body.Close() + params := request.NewActivitySend() + resputil.JSON(w, params.Fill(r), func() (interface{}, error) { + return ah.Send(r.Context(), params) + }) + }, + } +} + +func (ah *Activity) MountRoutes(r chi.Router, middlewares ...func(http.Handler) http.Handler) { + r.Group(func(r chi.Router) { + r.Use(middlewares...) + r.Post("/activity/", ah.Send) + }) +} diff --git a/messaging/rest/message.go b/messaging/rest/message.go index 4c5768ab8..e9ab125a6 100644 --- a/messaging/rest/message.go +++ b/messaging/rest/message.go @@ -17,7 +17,8 @@ var _ = errors.Wrap type ( Message struct { svc struct { - msg service.MessageService + msg service.MessageService + event service.EventService } } ) @@ -111,6 +112,7 @@ func (ctrl *Message) ReactionCreate(ctx context.Context, r *request.MessageReact func (ctrl *Message) ReactionRemove(ctx context.Context, r *request.MessageReactionRemove) (interface{}, error) { return nil, ctrl.svc.msg.With(ctx).RemoveReaction(r.MessageID, r.Reaction) } + func (ctrl *Message) wrap(ctx context.Context) func(m *types.Message, err error) (*outgoing.Message, error) { return func(m *types.Message, err error) (*outgoing.Message, error) { if err != nil || m == nil { diff --git a/messaging/rest/request/activity.go b/messaging/rest/request/activity.go new file mode 100644 index 000000000..306598f0f --- /dev/null +++ b/messaging/rest/request/activity.go @@ -0,0 +1,87 @@ +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 `activity.go`, `activity.util.go` or `activity_test.go` to + implement your API calls, helper functions and tests. The file `activity.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{} + +// Activity send request parameters +type ActivitySend struct { + ChannelID uint64 `json:",string"` + MessageID uint64 `json:",string"` + Kind string +} + +func NewActivitySend() *ActivitySend { + return &ActivitySend{} +} + +func (aReq *ActivitySend) Fill(r *http.Request) (err error) { + if strings.ToLower(r.Header.Get("content-type")) == "application/json" { + err = json.NewDecoder(r.Body).Decode(aReq) + + 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 := post["channelID"]; ok { + + aReq.ChannelID = parseUInt64(val) + } + if val, ok := post["messageID"]; ok { + + aReq.MessageID = parseUInt64(val) + } + if val, ok := post["kind"]; ok { + + aReq.Kind = val + } + + return err +} + +var _ RequestFiller = NewActivitySend() diff --git a/messaging/rest/router.go b/messaging/rest/router.go index 4df79a217..3a3d59f1f 100644 --- a/messaging/rest/router.go +++ b/messaging/rest/router.go @@ -23,6 +23,7 @@ func MountRoutes() func(chi.Router) { r.Use(auth.MiddlewareValidOnly) r.Use(middlewareAllowedAccess) + handlers.NewActivity(Activity{}.New()).MountRoutes(r) handlers.NewChannel(Channel{}.New()).MountRoutes(r) handlers.NewMessage(Message{}.New()).MountRoutes(r) handlers.NewSearch(Search{}.New()).MountRoutes(r) diff --git a/messaging/types/activity.go b/messaging/types/activity.go new file mode 100644 index 000000000..1db7d04a5 --- /dev/null +++ b/messaging/types/activity.go @@ -0,0 +1,10 @@ +package types + +type ( + Activity struct { + ChannelID uint64 `json:"channelID,string"` + MessageID uint64 `json:"messageID,string"` + UserID uint64 `json:"userID,string"` + Kind string `json:"kind,omitempty"` + } +) diff --git a/messaging/websocket/session_incoming.go b/messaging/websocket/session_incoming.go index 617f9a6c2..1d11a7135 100644 --- a/messaging/websocket/session_incoming.go +++ b/messaging/websocket/session_incoming.go @@ -38,11 +38,6 @@ func (s *Session) dispatch(raw []byte) error { case p.ChannelViewRecord != nil: return s.channelViewRecord(ctx, p.ChannelViewRecord) - case p.ChannelActivity != nil: - return s.channelActivity(ctx, p.ChannelActivity) - case p.MessageActivity != nil: - return s.messageActivity(ctx, p.MessageActivity) - case p.Users != nil: return s.userList(ctx, p.Users) } diff --git a/messaging/websocket/session_incoming_channel.go b/messaging/websocket/session_incoming_channel.go index cf877bc84..a4d8a7fd2 100644 --- a/messaging/websocket/session_incoming_channel.go +++ b/messaging/websocket/session_incoming_channel.go @@ -108,22 +108,3 @@ func (s *Session) channelViewRecord(ctx context.Context, p *incoming.ChannelView return s.svc.ch.With(ctx).RecordView(userID, p.ChannelID, p.LastMessageID) } - -// Echoes received channel activity back to all subscribers -func (s *Session) channelActivity(ctx context.Context, p *incoming.ChannelActivity) error { - return s.sendToAllSubscribers(&outgoing.ChannelActivity{ - ID: p.ChannelID, - UserID: auth.GetIdentityFromContext(ctx).Identity(), - Kind: p.Kind, - }, payload.Uint64toa(p.ChannelID)) -} - -// Echoes received channel activity back to all subscribers -func (s *Session) messageActivity(ctx context.Context, p *incoming.MessageActivity) error { - return s.sendToAllSubscribers(&outgoing.MessageActivity{ - ID: p.MessageID, - ChannelID: p.ChannelID, - UserID: auth.GetIdentityFromContext(ctx).Identity(), - Kind: p.Kind, - }, payload.Uint64toa(p.ChannelID)) -}