3
0

User/chan/messaging activity refactored

This commit is contained in:
Denis Arh 2019-04-26 13:21:31 +02:00
parent f8e7a2133d
commit b0239761f7
20 changed files with 334 additions and 68 deletions

View File

@ -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.",

View File

@ -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"
}
]
}
}
]
}

View File

@ -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 |

View File

@ -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"`
}
)

View File

@ -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"`
}
)

View File

@ -11,10 +11,6 @@ type Payload struct {
*ChannelViewRecord `json:"recordChannelView"`
*ChannelActivity `json:"channelActivity"`
*MessageActivity `json:"messageActivity"`
// Message actions
*MessageCreate `json:"createMessage"`
*MessageUpdate `json:"updateMessage"`

View File

@ -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

View File

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

View File

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

View File

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

View File

@ -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"`

View File

@ -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

View File

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

View File

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

View File

@ -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 {

View File

@ -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()

View File

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

View File

@ -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"`
}
)

View File

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

View File

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