User/chan/messaging activity refactored
This commit is contained in:
parent
f8e7a2133d
commit
b0239761f7
@ -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.",
|
||||
|
||||
39
api/messaging/spec/activity.json
Normal file
39
api/messaging/spec/activity.json
Normal 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"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@ -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 |
|
||||
|
||||
@ -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"`
|
||||
}
|
||||
)
|
||||
|
||||
@ -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"`
|
||||
}
|
||||
)
|
||||
|
||||
@ -11,10 +11,6 @@ type Payload struct {
|
||||
|
||||
*ChannelViewRecord `json:"recordChannelView"`
|
||||
|
||||
*ChannelActivity `json:"channelActivity"`
|
||||
|
||||
*MessageActivity `json:"messageActivity"`
|
||||
|
||||
// Message actions
|
||||
*MessageCreate `json:"createMessage"`
|
||||
*MessageUpdate `json:"updateMessage"`
|
||||
|
||||
@ -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
|
||||
|
||||
17
internal/payload/outgoing/activity.go
Normal file
17
internal/payload/outgoing/activity.go
Normal 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})
|
||||
}
|
||||
@ -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})
|
||||
}
|
||||
|
||||
@ -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})
|
||||
}
|
||||
|
||||
@ -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"`
|
||||
|
||||
|
||||
@ -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
|
||||
|
||||
38
messaging/rest/activity.go
Normal file
38
messaging/rest/activity.go
Normal 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,
|
||||
})
|
||||
}
|
||||
56
messaging/rest/handlers/activity.go
Normal file
56
messaging/rest/handlers/activity.go
Normal 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)
|
||||
})
|
||||
}
|
||||
@ -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 {
|
||||
|
||||
87
messaging/rest/request/activity.go
Normal file
87
messaging/rest/request/activity.go
Normal 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()
|
||||
@ -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)
|
||||
|
||||
10
messaging/types/activity.go
Normal file
10
messaging/types/activity.go
Normal 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"`
|
||||
}
|
||||
)
|
||||
@ -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)
|
||||
}
|
||||
|
||||
@ -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))
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user