Refactor and polish flags (reactions, pins & bookmarks)
This commit is contained in:
@@ -29,8 +29,11 @@ func Message(ctx context.Context, msg *samTypes.Message) *outgoing.Message {
|
||||
ReplyTo: msg.ReplyTo,
|
||||
Replies: msg.Replies,
|
||||
|
||||
User: User(msg.User),
|
||||
Attachment: Attachment(msg.Attachment),
|
||||
User: User(msg.User),
|
||||
Attachment: Attachment(msg.Attachment),
|
||||
Reactions: MessageReactions(msg.Flags),
|
||||
IsPinned: msg.Flags.IsPinned(),
|
||||
IsBookmarked: msg.Flags.IsBookmarked(currentUserID),
|
||||
|
||||
CanReply: canReply,
|
||||
CanEdit: canEdit,
|
||||
@@ -51,6 +54,33 @@ func Messages(ctx context.Context, msg samTypes.MessageSet) *outgoing.MessageSet
|
||||
return &retval
|
||||
}
|
||||
|
||||
func MessageReactions(flags samTypes.MessageFlagSet) outgoing.ReactionSet {
|
||||
var (
|
||||
rr = make([]*outgoing.Reaction, 0)
|
||||
rIndex = map[string]int{}
|
||||
has bool
|
||||
i int
|
||||
)
|
||||
|
||||
_ = flags.Walk(func(flag *samTypes.MessageFlag) error {
|
||||
if flag.IsReaction() {
|
||||
r := &outgoing.Reaction{Reaction: flag.Flag, UserIDs: []string{}, Count: 0}
|
||||
|
||||
if i, has = rIndex[flag.Flag]; !has {
|
||||
i, rIndex[flag.Flag] = len(rr), len(rr)
|
||||
rr = append(rr, r)
|
||||
}
|
||||
|
||||
rr[i].UserIDs = append(rr[i].UserIDs, Uint64toa(flag.UserID))
|
||||
rr[i].Count++
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
|
||||
return rr
|
||||
}
|
||||
|
||||
func Channel(ch *samTypes.Channel) *outgoing.Channel {
|
||||
return &outgoing.Channel{
|
||||
ID: Uint64toa(ch.ID),
|
||||
|
||||
@@ -14,8 +14,11 @@ type (
|
||||
ReplyTo uint64 `json:"replyTo,omitempty,string"`
|
||||
Replies uint `json:"replies,omitempty"`
|
||||
|
||||
User *User `json:"user"`
|
||||
Attachment *Attachment `json:"att,omitempty"`
|
||||
User *User `json:"user"`
|
||||
Attachment *Attachment `json:"att,omitempty"`
|
||||
Reactions ReactionSet `json:"reactions,omitempty"`
|
||||
IsBookmarked bool `json:"isBookmarked"`
|
||||
IsPinned bool `json:"isPinned"`
|
||||
|
||||
CanReply bool `json:"canReply"`
|
||||
CanEdit bool `json:"canEdit"`
|
||||
@@ -27,6 +30,14 @@ type (
|
||||
}
|
||||
|
||||
MessageSet []*Message
|
||||
|
||||
Reaction struct {
|
||||
UserIDs []string `json:"userIDs"`
|
||||
Reaction string `json:"reaction"`
|
||||
Count uint `json:"count"`
|
||||
}
|
||||
|
||||
ReactionSet []*Reaction
|
||||
)
|
||||
|
||||
func (p *Message) EncodeMessage() ([]byte, error) {
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1,14 @@
|
||||
DROP TABLE channel_pins;
|
||||
DROP TABLE reactions;
|
||||
|
||||
CREATE TABLE message_flags (
|
||||
id BIGINT UNSIGNED NOT NULL,
|
||||
rel_channel BIGINT UNSIGNED NOT NULL,
|
||||
rel_message BIGINT UNSIGNED NOT NULL,
|
||||
rel_user BIGINT UNSIGNED NOT NULL,
|
||||
flag TEXT,
|
||||
|
||||
created_at DATETIME NOT NULL DEFAULT NOW(),
|
||||
|
||||
PRIMARY KEY (id)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
|
||||
+20
-20
@@ -453,20 +453,6 @@ The following event types may be sent with a message event:
|
||||
| --------- | ---- | ------ | ----------- | ------- | --------- |
|
||||
| messageID | uint64 | PATH | Message ID | N/A | YES |
|
||||
|
||||
## Pin message to channel (public bookmark)
|
||||
|
||||
#### Method
|
||||
|
||||
| URI | Protocol | Method | Authentication |
|
||||
| --- | -------- | ------ | -------------- |
|
||||
| `/channels/{channelID}/messages/{messageID}/pin` | HTTP/S | POST | Client ID, Session ID |
|
||||
|
||||
#### Request parameters
|
||||
|
||||
| Parameter | Type | Method | Description | Default | Required? |
|
||||
| --------- | ---- | ------ | ----------- | ------- | --------- |
|
||||
| messageID | uint64 | PATH | Message ID | N/A | YES |
|
||||
|
||||
## Returns all replies to a message
|
||||
|
||||
#### Method
|
||||
@@ -500,6 +486,20 @@ The following event types may be sent with a message event:
|
||||
|
||||
#### Method
|
||||
|
||||
| URI | Protocol | Method | Authentication |
|
||||
| --- | -------- | ------ | -------------- |
|
||||
| `/channels/{channelID}/messages/{messageID}/pin` | HTTP/S | POST | Client ID, Session ID |
|
||||
|
||||
#### Request parameters
|
||||
|
||||
| Parameter | Type | Method | Description | Default | Required? |
|
||||
| --------- | ---- | ------ | ----------- | ------- | --------- |
|
||||
| messageID | uint64 | PATH | Message ID | N/A | YES |
|
||||
|
||||
## Pin message to channel (public bookmark)
|
||||
|
||||
#### Method
|
||||
|
||||
| URI | Protocol | Method | Authentication |
|
||||
| --- | -------- | ------ | -------------- |
|
||||
| `/channels/{channelID}/messages/{messageID}/pin` | HTTP/S | DELETE | Client ID, Session ID |
|
||||
@@ -510,13 +510,13 @@ The following event types may be sent with a message event:
|
||||
| --------- | ---- | ------ | ----------- | ------- | --------- |
|
||||
| messageID | uint64 | PATH | Message ID | N/A | YES |
|
||||
|
||||
## Flag a message (private bookmark)
|
||||
## Bookmark a message (private bookmark)
|
||||
|
||||
#### Method
|
||||
|
||||
| URI | Protocol | Method | Authentication |
|
||||
| --- | -------- | ------ | -------------- |
|
||||
| `/channels/{channelID}/messages/{messageID}/flag` | HTTP/S | POST | Client ID, Session ID |
|
||||
| `/channels/{channelID}/messages/{messageID}/bookmark` | HTTP/S | POST | Client ID, Session ID |
|
||||
|
||||
#### Request parameters
|
||||
|
||||
@@ -524,13 +524,13 @@ The following event types may be sent with a message event:
|
||||
| --------- | ---- | ------ | ----------- | ------- | --------- |
|
||||
| messageID | uint64 | PATH | Message ID | N/A | YES |
|
||||
|
||||
## Remove flag from message (private bookmark)
|
||||
## Remove boomark from message (private bookmark)
|
||||
|
||||
#### Method
|
||||
|
||||
| URI | Protocol | Method | Authentication |
|
||||
| --- | -------- | ------ | -------------- |
|
||||
| `/channels/{channelID}/messages/{messageID}/flag` | HTTP/S | DELETE | Client ID, Session ID |
|
||||
| `/channels/{channelID}/messages/{messageID}/bookmark` | HTTP/S | DELETE | Client ID, Session ID |
|
||||
|
||||
#### Request parameters
|
||||
|
||||
@@ -544,7 +544,7 @@ The following event types may be sent with a message event:
|
||||
|
||||
| URI | Protocol | Method | Authentication |
|
||||
| --- | -------- | ------ | -------------- |
|
||||
| `/channels/{channelID}/messages/{messageID}/reaction/{reaction}` | HTTP/S | PUT | Client ID, Session ID |
|
||||
| `/channels/{channelID}/messages/{messageID}/reaction/{reaction}` | HTTP/S | POST | Client ID, Session ID |
|
||||
|
||||
#### Request parameters
|
||||
|
||||
@@ -559,7 +559,7 @@ The following event types may be sent with a message event:
|
||||
|
||||
| URI | Protocol | Method | Authentication |
|
||||
| --- | -------- | ------ | -------------- |
|
||||
| `/channels/{channelID}/messages/{messageID}/react/{reaction}` | HTTP/S | DELETE | Client ID, Session ID |
|
||||
| `/channels/{channelID}/messages/{messageID}/reaction/{reaction}` | HTTP/S | DELETE | Client ID, Session ID |
|
||||
|
||||
#### Request parameters
|
||||
|
||||
|
||||
+21
-21
@@ -387,17 +387,6 @@
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "pin",
|
||||
"path": "/{messageID}/pin",
|
||||
"method": "POST",
|
||||
"title": "Pin message to channel (public bookmark)",
|
||||
"parameters": {
|
||||
"path": [
|
||||
{ "name": "messageID", "type": "uint64", "required": true, "title": "Message ID" }
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "getReplies",
|
||||
"path": "/{messageID}/replies",
|
||||
@@ -424,7 +413,18 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "unpin",
|
||||
"name": "pin",
|
||||
"path": "/{messageID}/pin",
|
||||
"method": "POST",
|
||||
"title": "Pin message to channel (public bookmark)",
|
||||
"parameters": {
|
||||
"path": [
|
||||
{ "name": "messageID", "type": "uint64", "required": true, "title": "Message ID" }
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "removePin",
|
||||
"path": "/{messageID}/pin",
|
||||
"method": "DELETE",
|
||||
"title": "Pin message to channel (public bookmark)",
|
||||
@@ -435,10 +435,10 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "flag",
|
||||
"path": "/{messageID}/flag",
|
||||
"name": "bookmark",
|
||||
"path": "/{messageID}/bookmark",
|
||||
"method": "POST",
|
||||
"title": "Flag a message (private bookmark)",
|
||||
"title": "Bookmark a message (private bookmark)",
|
||||
"parameters": {
|
||||
"path": [
|
||||
{ "name": "messageID", "type": "uint64", "required": true, "title": "Message ID" }
|
||||
@@ -446,10 +446,10 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "unflag",
|
||||
"path": "/{messageID}/flag",
|
||||
"name": "removeBookmark",
|
||||
"path": "/{messageID}/bookmark",
|
||||
"method": "DELETE",
|
||||
"title": "Remove flag from message (private bookmark)",
|
||||
"title": "Remove boomark from message (private bookmark)",
|
||||
"parameters": {
|
||||
"path": [
|
||||
{ "name": "messageID", "type": "uint64", "required": true, "title": "Message ID" }
|
||||
@@ -459,7 +459,7 @@
|
||||
{
|
||||
"name": "react",
|
||||
"path": "/{messageID}/reaction/{reaction}",
|
||||
"method": "PUT",
|
||||
"method": "POST",
|
||||
"title": "React to a message",
|
||||
"parameters": {
|
||||
"path": [
|
||||
@@ -469,8 +469,8 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "unreact",
|
||||
"path": "/{messageID}/react/{reaction}",
|
||||
"name": "removeReaction",
|
||||
"path": "/{messageID}/reaction/{reaction}",
|
||||
"method": "DELETE",
|
||||
"title": "Delete reaction from a message",
|
||||
"parameters": {
|
||||
|
||||
@@ -92,22 +92,6 @@
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"Name": "pin",
|
||||
"Method": "POST",
|
||||
"Title": "Pin message to channel (public bookmark)",
|
||||
"Path": "/{messageID}/pin",
|
||||
"Parameters": {
|
||||
"path": [
|
||||
{
|
||||
"name": "messageID",
|
||||
"required": true,
|
||||
"title": "Message ID",
|
||||
"type": "uint64"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"Name": "getReplies",
|
||||
"Method": "GET",
|
||||
@@ -149,7 +133,23 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"Name": "unpin",
|
||||
"Name": "pin",
|
||||
"Method": "POST",
|
||||
"Title": "Pin message to channel (public bookmark)",
|
||||
"Path": "/{messageID}/pin",
|
||||
"Parameters": {
|
||||
"path": [
|
||||
{
|
||||
"name": "messageID",
|
||||
"required": true,
|
||||
"title": "Message ID",
|
||||
"type": "uint64"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"Name": "removePin",
|
||||
"Method": "DELETE",
|
||||
"Title": "Pin message to channel (public bookmark)",
|
||||
"Path": "/{messageID}/pin",
|
||||
@@ -165,10 +165,10 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"Name": "flag",
|
||||
"Name": "bookmark",
|
||||
"Method": "POST",
|
||||
"Title": "Flag a message (private bookmark)",
|
||||
"Path": "/{messageID}/flag",
|
||||
"Title": "Bookmark a message (private bookmark)",
|
||||
"Path": "/{messageID}/bookmark",
|
||||
"Parameters": {
|
||||
"path": [
|
||||
{
|
||||
@@ -181,10 +181,10 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"Name": "unflag",
|
||||
"Name": "removeBookmark",
|
||||
"Method": "DELETE",
|
||||
"Title": "Remove flag from message (private bookmark)",
|
||||
"Path": "/{messageID}/flag",
|
||||
"Title": "Remove boomark from message (private bookmark)",
|
||||
"Path": "/{messageID}/bookmark",
|
||||
"Parameters": {
|
||||
"path": [
|
||||
{
|
||||
@@ -198,7 +198,7 @@
|
||||
},
|
||||
{
|
||||
"Name": "react",
|
||||
"Method": "PUT",
|
||||
"Method": "POST",
|
||||
"Title": "React to a message",
|
||||
"Path": "/{messageID}/reaction/{reaction}",
|
||||
"Parameters": {
|
||||
@@ -219,10 +219,10 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"Name": "unreact",
|
||||
"Name": "removeReaction",
|
||||
"Method": "DELETE",
|
||||
"Title": "Delete reaction from a message",
|
||||
"Path": "/{messageID}/react/{reaction}",
|
||||
"Path": "/{messageID}/reaction/{reaction}",
|
||||
"Parameters": {
|
||||
"path": [
|
||||
{
|
||||
|
||||
@@ -0,0 +1,83 @@
|
||||
package repository
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"github.com/jmoiron/sqlx"
|
||||
"github.com/titpetric/factory"
|
||||
|
||||
"github.com/crusttech/crust/sam/types"
|
||||
)
|
||||
|
||||
type (
|
||||
MessageFlagRepository interface {
|
||||
With(ctx context.Context, db *factory.DB) MessageFlagRepository
|
||||
|
||||
FindByID(ID uint64) (*types.MessageFlag, error)
|
||||
FindByMessageIDs(IDs ...uint64) ([]*types.MessageFlag, error)
|
||||
FindByFlag(messageID, userID uint64, flag string) (*types.MessageFlag, error)
|
||||
Create(mod *types.MessageFlag) (*types.MessageFlag, error)
|
||||
DeleteByID(ID uint64) error
|
||||
}
|
||||
|
||||
reaction struct {
|
||||
*repository
|
||||
}
|
||||
)
|
||||
|
||||
const (
|
||||
ErrMessageFlagNotFound = repositoryError("MessageFlagNotFound")
|
||||
)
|
||||
|
||||
func MessageFlag(ctx context.Context, db *factory.DB) MessageFlagRepository {
|
||||
return (&reaction{}).With(ctx, db)
|
||||
}
|
||||
|
||||
func (r *reaction) With(ctx context.Context, db *factory.DB) MessageFlagRepository {
|
||||
return &reaction{
|
||||
repository: r.repository.With(ctx, db),
|
||||
}
|
||||
}
|
||||
|
||||
func (r *reaction) FindByID(ID uint64) (*types.MessageFlag, error) {
|
||||
sql := "SELECT * FROM message_flags WHERE id = ?"
|
||||
mod := &types.MessageFlag{}
|
||||
return mod, isFound(r.db().Get(mod, sql, ID), mod.ID > 0, ErrMessageFlagNotFound)
|
||||
}
|
||||
|
||||
func (r *reaction) FindByFlag(messageID, userID uint64, flag string) (*types.MessageFlag, error) {
|
||||
args := []interface{}{messageID, flag}
|
||||
sql := "SELECT * FROM message_flags WHERE rel_message = ? AND flag = ? "
|
||||
|
||||
if userID > 0 {
|
||||
sql += "AND rel_user = ? "
|
||||
args = append(args, userID)
|
||||
}
|
||||
|
||||
mod := &types.MessageFlag{}
|
||||
return mod, isFound(r.db().Get(mod, sql, args...), mod.ID > 0, ErrMessageFlagNotFound)
|
||||
}
|
||||
|
||||
// FindByMessageRange returns all flags by message id range
|
||||
func (r *reaction) FindByMessageIDs(IDs ...uint64) ([]*types.MessageFlag, error) {
|
||||
rval := make([]*types.MessageFlag, 0)
|
||||
|
||||
sql := `SELECT * FROM message_flags WHERE rel_message IN (?)`
|
||||
|
||||
if sql, args, err := sqlx.In(sql, IDs); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
return rval, r.db().Select(&rval, sql, args...)
|
||||
}
|
||||
}
|
||||
|
||||
func (r *reaction) Create(mod *types.MessageFlag) (*types.MessageFlag, error) {
|
||||
mod.ID = factory.Sonyflake.NextID()
|
||||
mod.CreatedAt = time.Now()
|
||||
return mod, r.db().Insert("message_flags", mod)
|
||||
}
|
||||
|
||||
func (r *reaction) DeleteByID(ID uint64) error {
|
||||
return exec(r.db().Exec("DELETE FROM message_flags WHERE id = ?", ID))
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
package repository
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/titpetric/factory"
|
||||
|
||||
"testing"
|
||||
|
||||
"github.com/crusttech/crust/sam/types"
|
||||
)
|
||||
|
||||
func TestReaction(t *testing.T) {
|
||||
var err error
|
||||
|
||||
if testing.Short() {
|
||||
t.Skip("skipping test in short mode.")
|
||||
return
|
||||
}
|
||||
|
||||
rpo := MessageFlag(context.Background(), factory.Database.MustGet())
|
||||
|
||||
tx(t, func() error {
|
||||
var chID = factory.Sonyflake.NextID()
|
||||
var msgID = factory.Sonyflake.NextID()
|
||||
var f *types.MessageFlag
|
||||
var ff types.MessageFlagSet
|
||||
f, err = rpo.Create(&types.MessageFlag{
|
||||
ChannelID: chID,
|
||||
MessageID: msgID,
|
||||
UserID: 3,
|
||||
Flag: "success",
|
||||
})
|
||||
|
||||
assert(t, err == nil, "Should create message flag without an error, got: %v", err)
|
||||
|
||||
f, err = rpo.FindByID(f.ID)
|
||||
assert(t, err == nil, "Should fetch message flag without an error, got: %v", err)
|
||||
assert(t, f != nil && f.ChannelID == chID, "fetch should return valid type struct")
|
||||
|
||||
ff, err = rpo.FindByMessageIDs(msgID)
|
||||
assert(t, err == nil, "Should fetch message flag by range without an error, got: %v", err)
|
||||
assert(t, len(ff) == 1, "fetch by range should return 1 message")
|
||||
|
||||
err = rpo.DeleteByID(f.ID)
|
||||
assert(t, err == nil, "Should delete message flag without an error, got: %v", err)
|
||||
|
||||
return nil
|
||||
})
|
||||
}
|
||||
@@ -1,61 +0,0 @@
|
||||
package repository
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"github.com/titpetric/factory"
|
||||
|
||||
"github.com/crusttech/crust/sam/types"
|
||||
)
|
||||
|
||||
type (
|
||||
ReactionRepository interface {
|
||||
With(ctx context.Context, db *factory.DB) ReactionRepository
|
||||
|
||||
FindReactionByID(id uint64) (*types.Reaction, error)
|
||||
FindReactionsByRange(channelID, fromReactionID, toReactionID uint64) ([]*types.Reaction, error)
|
||||
CreateReaction(mod *types.Reaction) (*types.Reaction, error)
|
||||
DeleteReactionByID(id uint64) error
|
||||
}
|
||||
|
||||
reaction struct {
|
||||
*repository
|
||||
}
|
||||
)
|
||||
|
||||
const (
|
||||
ErrReactionNotFound = repositoryError("ReactionNotFound")
|
||||
)
|
||||
|
||||
func Reaction(ctx context.Context, db *factory.DB) ReactionRepository {
|
||||
return (&reaction{}).With(ctx, db)
|
||||
}
|
||||
|
||||
func (r *reaction) With(ctx context.Context, db *factory.DB) ReactionRepository {
|
||||
return &reaction{
|
||||
repository: r.repository.With(ctx, db),
|
||||
}
|
||||
}
|
||||
|
||||
func (r *reaction) FindReactionByID(id uint64) (*types.Reaction, error) {
|
||||
sql := "SELECT * FROM reactions WHERE id=?"
|
||||
mod := &types.Reaction{}
|
||||
return mod, isFound(r.db().Get(mod, sql, id), mod.ID > 0, ErrReactionNotFound)
|
||||
}
|
||||
|
||||
func (r *reaction) FindReactionsByRange(channelID, fromReactionID, toReactionID uint64) ([]*types.Reaction, error) {
|
||||
rval := make([]*types.Reaction, 0)
|
||||
sql := `SELECT * FROM reactions WHERE rel_reaction BETWEEN ? AND ? AND rel_channel=?`
|
||||
return rval, r.db().Select(&rval, sql, fromReactionID, toReactionID, channelID)
|
||||
}
|
||||
|
||||
func (r *reaction) CreateReaction(mod *types.Reaction) (*types.Reaction, error) {
|
||||
mod.ID = factory.Sonyflake.NextID()
|
||||
mod.CreatedAt = time.Now()
|
||||
return mod, r.db().Insert("reactions", mod)
|
||||
}
|
||||
|
||||
func (r *reaction) DeleteReactionByID(id uint64) error {
|
||||
return exec(r.db().Exec("DELETE FROM reactions WHERE id=?", id))
|
||||
}
|
||||
@@ -1,43 +0,0 @@
|
||||
package repository
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/titpetric/factory"
|
||||
|
||||
"testing"
|
||||
|
||||
"github.com/crusttech/crust/sam/types"
|
||||
)
|
||||
|
||||
func TestReaction(t *testing.T) {
|
||||
var err error
|
||||
|
||||
if testing.Short() {
|
||||
t.Skip("skipping test in short mode.")
|
||||
return
|
||||
}
|
||||
|
||||
rpo := Reaction(context.Background(), factory.Database.MustGet())
|
||||
react := &types.Reaction{}
|
||||
|
||||
var reaction = ":laugh:"
|
||||
|
||||
{
|
||||
react.Reaction = reaction
|
||||
react, err = rpo.CreateReaction(react)
|
||||
assert(t, err == nil, "CreateReaction error: %v", err)
|
||||
assert(t, react.Reaction == reaction, "Changes were not stored")
|
||||
|
||||
{
|
||||
react, err = rpo.FindReactionByID(react.ID)
|
||||
assert(t, err == nil, "FindReactionByID error: %v", err)
|
||||
assert(t, react.Reaction == reaction, "Changes were not stored")
|
||||
}
|
||||
|
||||
{
|
||||
err = rpo.DeleteReactionByID(react.ID)
|
||||
assert(t, err == nil, "DeleteReactionByID error: %v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -31,30 +31,30 @@ type MessageAPI interface {
|
||||
History(context.Context, *request.MessageHistory) (interface{}, error)
|
||||
Edit(context.Context, *request.MessageEdit) (interface{}, error)
|
||||
Delete(context.Context, *request.MessageDelete) (interface{}, error)
|
||||
Pin(context.Context, *request.MessagePin) (interface{}, error)
|
||||
GetReplies(context.Context, *request.MessageGetReplies) (interface{}, error)
|
||||
CreateReply(context.Context, *request.MessageCreateReply) (interface{}, error)
|
||||
Unpin(context.Context, *request.MessageUnpin) (interface{}, error)
|
||||
Flag(context.Context, *request.MessageFlag) (interface{}, error)
|
||||
Unflag(context.Context, *request.MessageUnflag) (interface{}, error)
|
||||
Pin(context.Context, *request.MessagePin) (interface{}, error)
|
||||
RemovePin(context.Context, *request.MessageRemovePin) (interface{}, error)
|
||||
Bookmark(context.Context, *request.MessageBookmark) (interface{}, error)
|
||||
RemoveBookmark(context.Context, *request.MessageRemoveBookmark) (interface{}, error)
|
||||
React(context.Context, *request.MessageReact) (interface{}, error)
|
||||
Unreact(context.Context, *request.MessageUnreact) (interface{}, error)
|
||||
RemoveReaction(context.Context, *request.MessageRemoveReaction) (interface{}, error)
|
||||
}
|
||||
|
||||
// HTTP API interface
|
||||
type Message struct {
|
||||
Create func(http.ResponseWriter, *http.Request)
|
||||
History func(http.ResponseWriter, *http.Request)
|
||||
Edit func(http.ResponseWriter, *http.Request)
|
||||
Delete func(http.ResponseWriter, *http.Request)
|
||||
Pin func(http.ResponseWriter, *http.Request)
|
||||
GetReplies func(http.ResponseWriter, *http.Request)
|
||||
CreateReply func(http.ResponseWriter, *http.Request)
|
||||
Unpin func(http.ResponseWriter, *http.Request)
|
||||
Flag func(http.ResponseWriter, *http.Request)
|
||||
Unflag func(http.ResponseWriter, *http.Request)
|
||||
React func(http.ResponseWriter, *http.Request)
|
||||
Unreact func(http.ResponseWriter, *http.Request)
|
||||
Create func(http.ResponseWriter, *http.Request)
|
||||
History func(http.ResponseWriter, *http.Request)
|
||||
Edit func(http.ResponseWriter, *http.Request)
|
||||
Delete func(http.ResponseWriter, *http.Request)
|
||||
GetReplies func(http.ResponseWriter, *http.Request)
|
||||
CreateReply func(http.ResponseWriter, *http.Request)
|
||||
Pin func(http.ResponseWriter, *http.Request)
|
||||
RemovePin func(http.ResponseWriter, *http.Request)
|
||||
Bookmark func(http.ResponseWriter, *http.Request)
|
||||
RemoveBookmark func(http.ResponseWriter, *http.Request)
|
||||
React func(http.ResponseWriter, *http.Request)
|
||||
RemoveReaction func(http.ResponseWriter, *http.Request)
|
||||
}
|
||||
|
||||
func NewMessage(mh MessageAPI) *Message {
|
||||
@@ -87,13 +87,6 @@ func NewMessage(mh MessageAPI) *Message {
|
||||
return mh.Delete(r.Context(), params)
|
||||
})
|
||||
},
|
||||
Pin: func(w http.ResponseWriter, r *http.Request) {
|
||||
defer r.Body.Close()
|
||||
params := request.NewMessagePin()
|
||||
resputil.JSON(w, params.Fill(r), func() (interface{}, error) {
|
||||
return mh.Pin(r.Context(), params)
|
||||
})
|
||||
},
|
||||
GetReplies: func(w http.ResponseWriter, r *http.Request) {
|
||||
defer r.Body.Close()
|
||||
params := request.NewMessageGetReplies()
|
||||
@@ -108,25 +101,32 @@ func NewMessage(mh MessageAPI) *Message {
|
||||
return mh.CreateReply(r.Context(), params)
|
||||
})
|
||||
},
|
||||
Unpin: func(w http.ResponseWriter, r *http.Request) {
|
||||
Pin: func(w http.ResponseWriter, r *http.Request) {
|
||||
defer r.Body.Close()
|
||||
params := request.NewMessageUnpin()
|
||||
params := request.NewMessagePin()
|
||||
resputil.JSON(w, params.Fill(r), func() (interface{}, error) {
|
||||
return mh.Unpin(r.Context(), params)
|
||||
return mh.Pin(r.Context(), params)
|
||||
})
|
||||
},
|
||||
Flag: func(w http.ResponseWriter, r *http.Request) {
|
||||
RemovePin: func(w http.ResponseWriter, r *http.Request) {
|
||||
defer r.Body.Close()
|
||||
params := request.NewMessageFlag()
|
||||
params := request.NewMessageRemovePin()
|
||||
resputil.JSON(w, params.Fill(r), func() (interface{}, error) {
|
||||
return mh.Flag(r.Context(), params)
|
||||
return mh.RemovePin(r.Context(), params)
|
||||
})
|
||||
},
|
||||
Unflag: func(w http.ResponseWriter, r *http.Request) {
|
||||
Bookmark: func(w http.ResponseWriter, r *http.Request) {
|
||||
defer r.Body.Close()
|
||||
params := request.NewMessageUnflag()
|
||||
params := request.NewMessageBookmark()
|
||||
resputil.JSON(w, params.Fill(r), func() (interface{}, error) {
|
||||
return mh.Unflag(r.Context(), params)
|
||||
return mh.Bookmark(r.Context(), params)
|
||||
})
|
||||
},
|
||||
RemoveBookmark: func(w http.ResponseWriter, r *http.Request) {
|
||||
defer r.Body.Close()
|
||||
params := request.NewMessageRemoveBookmark()
|
||||
resputil.JSON(w, params.Fill(r), func() (interface{}, error) {
|
||||
return mh.RemoveBookmark(r.Context(), params)
|
||||
})
|
||||
},
|
||||
React: func(w http.ResponseWriter, r *http.Request) {
|
||||
@@ -136,11 +136,11 @@ func NewMessage(mh MessageAPI) *Message {
|
||||
return mh.React(r.Context(), params)
|
||||
})
|
||||
},
|
||||
Unreact: func(w http.ResponseWriter, r *http.Request) {
|
||||
RemoveReaction: func(w http.ResponseWriter, r *http.Request) {
|
||||
defer r.Body.Close()
|
||||
params := request.NewMessageUnreact()
|
||||
params := request.NewMessageRemoveReaction()
|
||||
resputil.JSON(w, params.Fill(r), func() (interface{}, error) {
|
||||
return mh.Unreact(r.Context(), params)
|
||||
return mh.RemoveReaction(r.Context(), params)
|
||||
})
|
||||
},
|
||||
}
|
||||
@@ -154,14 +154,14 @@ func (mh *Message) MountRoutes(r chi.Router, middlewares ...func(http.Handler) h
|
||||
r.Get("/", mh.History)
|
||||
r.Put("/{messageID}", mh.Edit)
|
||||
r.Delete("/{messageID}", mh.Delete)
|
||||
r.Post("/{messageID}/pin", mh.Pin)
|
||||
r.Get("/{messageID}/replies", mh.GetReplies)
|
||||
r.Post("/{messageID}/replies", mh.CreateReply)
|
||||
r.Delete("/{messageID}/pin", mh.Unpin)
|
||||
r.Post("/{messageID}/flag", mh.Flag)
|
||||
r.Delete("/{messageID}/flag", mh.Unflag)
|
||||
r.Put("/{messageID}/reaction/{reaction}", mh.React)
|
||||
r.Delete("/{messageID}/react/{reaction}", mh.Unreact)
|
||||
r.Post("/{messageID}/pin", mh.Pin)
|
||||
r.Delete("/{messageID}/pin", mh.RemovePin)
|
||||
r.Post("/{messageID}/bookmark", mh.Bookmark)
|
||||
r.Delete("/{messageID}/bookmark", mh.RemoveBookmark)
|
||||
r.Post("/{messageID}/reaction/{reaction}", mh.React)
|
||||
r.Delete("/{messageID}/reaction/{reaction}", mh.RemoveReaction)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
+8
-8
@@ -72,24 +72,24 @@ func (ctrl *Message) Pin(ctx context.Context, r *request.MessagePin) (interface{
|
||||
return nil, ctrl.svc.msg.With(ctx).Pin(r.MessageID)
|
||||
}
|
||||
|
||||
func (ctrl *Message) Unpin(ctx context.Context, r *request.MessageUnpin) (interface{}, error) {
|
||||
return nil, ctrl.svc.msg.With(ctx).Unpin(r.MessageID)
|
||||
func (ctrl *Message) RemovePin(ctx context.Context, r *request.MessageRemovePin) (interface{}, error) {
|
||||
return nil, ctrl.svc.msg.With(ctx).RemovePin(r.MessageID)
|
||||
}
|
||||
|
||||
func (ctrl *Message) Flag(ctx context.Context, r *request.MessageFlag) (interface{}, error) {
|
||||
return nil, ctrl.svc.msg.With(ctx).Flag(r.MessageID)
|
||||
func (ctrl *Message) Bookmark(ctx context.Context, r *request.MessageBookmark) (interface{}, error) {
|
||||
return nil, ctrl.svc.msg.With(ctx).Bookmark(r.MessageID)
|
||||
}
|
||||
|
||||
func (ctrl *Message) Unflag(ctx context.Context, r *request.MessageUnflag) (interface{}, error) {
|
||||
return nil, ctrl.svc.msg.With(ctx).Unflag(r.MessageID)
|
||||
func (ctrl *Message) RemoveBookmark(ctx context.Context, r *request.MessageRemoveBookmark) (interface{}, error) {
|
||||
return nil, ctrl.svc.msg.With(ctx).RemoveBookmark(r.MessageID)
|
||||
}
|
||||
|
||||
func (ctrl *Message) React(ctx context.Context, r *request.MessageReact) (interface{}, error) {
|
||||
return nil, ctrl.svc.msg.With(ctx).React(r.MessageID, r.Reaction)
|
||||
}
|
||||
|
||||
func (ctrl *Message) Unreact(ctx context.Context, r *request.MessageUnreact) (interface{}, error) {
|
||||
return nil, ctrl.svc.msg.With(ctx).Unreact(r.MessageID, r.Reaction)
|
||||
func (ctrl *Message) RemoveReaction(ctx context.Context, r *request.MessageRemoveReaction) (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) {
|
||||
|
||||
+69
-69
@@ -221,51 +221,6 @@ func (m *MessageDelete) Fill(r *http.Request) (err error) {
|
||||
|
||||
var _ RequestFiller = NewMessageDelete()
|
||||
|
||||
// Message pin request parameters
|
||||
type MessagePin struct {
|
||||
MessageID uint64 `json:",string"`
|
||||
ChannelID uint64 `json:",string"`
|
||||
}
|
||||
|
||||
func NewMessagePin() *MessagePin {
|
||||
return &MessagePin{}
|
||||
}
|
||||
|
||||
func (m *MessagePin) Fill(r *http.Request) (err error) {
|
||||
if strings.ToLower(r.Header.Get("content-type")) == "application/json" {
|
||||
err = json.NewDecoder(r.Body).Decode(m)
|
||||
|
||||
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])
|
||||
}
|
||||
|
||||
m.MessageID = parseUInt64(chi.URLParam(r, "messageID"))
|
||||
m.ChannelID = parseUInt64(chi.URLParam(r, "channelID"))
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
var _ RequestFiller = NewMessagePin()
|
||||
|
||||
// Message getReplies request parameters
|
||||
type MessageGetReplies struct {
|
||||
MessageID uint64 `json:",string"`
|
||||
@@ -361,17 +316,17 @@ func (m *MessageCreateReply) Fill(r *http.Request) (err error) {
|
||||
|
||||
var _ RequestFiller = NewMessageCreateReply()
|
||||
|
||||
// Message unpin request parameters
|
||||
type MessageUnpin struct {
|
||||
// Message pin request parameters
|
||||
type MessagePin struct {
|
||||
MessageID uint64 `json:",string"`
|
||||
ChannelID uint64 `json:",string"`
|
||||
}
|
||||
|
||||
func NewMessageUnpin() *MessageUnpin {
|
||||
return &MessageUnpin{}
|
||||
func NewMessagePin() *MessagePin {
|
||||
return &MessagePin{}
|
||||
}
|
||||
|
||||
func (m *MessageUnpin) Fill(r *http.Request) (err error) {
|
||||
func (m *MessagePin) Fill(r *http.Request) (err error) {
|
||||
if strings.ToLower(r.Header.Get("content-type")) == "application/json" {
|
||||
err = json.NewDecoder(r.Body).Decode(m)
|
||||
|
||||
@@ -404,19 +359,19 @@ func (m *MessageUnpin) Fill(r *http.Request) (err error) {
|
||||
return err
|
||||
}
|
||||
|
||||
var _ RequestFiller = NewMessageUnpin()
|
||||
var _ RequestFiller = NewMessagePin()
|
||||
|
||||
// Message flag request parameters
|
||||
type MessageFlag struct {
|
||||
// Message removePin request parameters
|
||||
type MessageRemovePin struct {
|
||||
MessageID uint64 `json:",string"`
|
||||
ChannelID uint64 `json:",string"`
|
||||
}
|
||||
|
||||
func NewMessageFlag() *MessageFlag {
|
||||
return &MessageFlag{}
|
||||
func NewMessageRemovePin() *MessageRemovePin {
|
||||
return &MessageRemovePin{}
|
||||
}
|
||||
|
||||
func (m *MessageFlag) Fill(r *http.Request) (err error) {
|
||||
func (m *MessageRemovePin) Fill(r *http.Request) (err error) {
|
||||
if strings.ToLower(r.Header.Get("content-type")) == "application/json" {
|
||||
err = json.NewDecoder(r.Body).Decode(m)
|
||||
|
||||
@@ -449,19 +404,19 @@ func (m *MessageFlag) Fill(r *http.Request) (err error) {
|
||||
return err
|
||||
}
|
||||
|
||||
var _ RequestFiller = NewMessageFlag()
|
||||
var _ RequestFiller = NewMessageRemovePin()
|
||||
|
||||
// Message unflag request parameters
|
||||
type MessageUnflag struct {
|
||||
// Message bookmark request parameters
|
||||
type MessageBookmark struct {
|
||||
MessageID uint64 `json:",string"`
|
||||
ChannelID uint64 `json:",string"`
|
||||
}
|
||||
|
||||
func NewMessageUnflag() *MessageUnflag {
|
||||
return &MessageUnflag{}
|
||||
func NewMessageBookmark() *MessageBookmark {
|
||||
return &MessageBookmark{}
|
||||
}
|
||||
|
||||
func (m *MessageUnflag) Fill(r *http.Request) (err error) {
|
||||
func (m *MessageBookmark) Fill(r *http.Request) (err error) {
|
||||
if strings.ToLower(r.Header.Get("content-type")) == "application/json" {
|
||||
err = json.NewDecoder(r.Body).Decode(m)
|
||||
|
||||
@@ -494,7 +449,52 @@ func (m *MessageUnflag) Fill(r *http.Request) (err error) {
|
||||
return err
|
||||
}
|
||||
|
||||
var _ RequestFiller = NewMessageUnflag()
|
||||
var _ RequestFiller = NewMessageBookmark()
|
||||
|
||||
// Message removeBookmark request parameters
|
||||
type MessageRemoveBookmark struct {
|
||||
MessageID uint64 `json:",string"`
|
||||
ChannelID uint64 `json:",string"`
|
||||
}
|
||||
|
||||
func NewMessageRemoveBookmark() *MessageRemoveBookmark {
|
||||
return &MessageRemoveBookmark{}
|
||||
}
|
||||
|
||||
func (m *MessageRemoveBookmark) Fill(r *http.Request) (err error) {
|
||||
if strings.ToLower(r.Header.Get("content-type")) == "application/json" {
|
||||
err = json.NewDecoder(r.Body).Decode(m)
|
||||
|
||||
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])
|
||||
}
|
||||
|
||||
m.MessageID = parseUInt64(chi.URLParam(r, "messageID"))
|
||||
m.ChannelID = parseUInt64(chi.URLParam(r, "channelID"))
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
var _ RequestFiller = NewMessageRemoveBookmark()
|
||||
|
||||
// Message react request parameters
|
||||
type MessageReact struct {
|
||||
@@ -543,18 +543,18 @@ func (m *MessageReact) Fill(r *http.Request) (err error) {
|
||||
|
||||
var _ RequestFiller = NewMessageReact()
|
||||
|
||||
// Message unreact request parameters
|
||||
type MessageUnreact struct {
|
||||
// Message removeReaction request parameters
|
||||
type MessageRemoveReaction struct {
|
||||
MessageID uint64 `json:",string"`
|
||||
Reaction string
|
||||
ChannelID uint64 `json:",string"`
|
||||
}
|
||||
|
||||
func NewMessageUnreact() *MessageUnreact {
|
||||
return &MessageUnreact{}
|
||||
func NewMessageRemoveReaction() *MessageRemoveReaction {
|
||||
return &MessageRemoveReaction{}
|
||||
}
|
||||
|
||||
func (m *MessageUnreact) Fill(r *http.Request) (err error) {
|
||||
func (m *MessageRemoveReaction) Fill(r *http.Request) (err error) {
|
||||
if strings.ToLower(r.Header.Get("content-type")) == "application/json" {
|
||||
err = json.NewDecoder(r.Body).Decode(m)
|
||||
|
||||
@@ -588,4 +588,4 @@ func (m *MessageUnreact) Fill(r *http.Request) (err error) {
|
||||
return err
|
||||
}
|
||||
|
||||
var _ RequestFiller = NewMessageUnreact()
|
||||
var _ RequestFiller = NewMessageRemoveReaction()
|
||||
|
||||
+116
-70
@@ -23,7 +23,7 @@ type (
|
||||
cmember repository.ChannelMemberRepository
|
||||
cview repository.ChannelViewRepository
|
||||
message repository.MessageRepository
|
||||
reaction repository.ReactionRepository
|
||||
mflag repository.MessageFlagRepository
|
||||
|
||||
usr authService.UserService
|
||||
evl EventService
|
||||
@@ -39,13 +39,13 @@ type (
|
||||
Update(messages *types.Message) (*types.Message, error)
|
||||
|
||||
React(messageID uint64, reaction string) error
|
||||
Unreact(messageID uint64, reaction string) error
|
||||
RemoveReaction(messageID uint64, reaction string) error
|
||||
|
||||
Pin(messageID uint64) error
|
||||
Unpin(messageID uint64) error
|
||||
RemovePin(messageID uint64) error
|
||||
|
||||
Flag(messageID uint64) error
|
||||
Unflag(messageID uint64) error
|
||||
Bookmark(messageID uint64) error
|
||||
RemoveBookmark(messageID uint64) error
|
||||
|
||||
Delete(ID uint64) error
|
||||
}
|
||||
@@ -76,7 +76,7 @@ func (svc *message) With(ctx context.Context) MessageService {
|
||||
cmember: repository.ChannelMember(ctx, db),
|
||||
cview: repository.ChannelView(ctx, db),
|
||||
message: repository.Message(ctx, db),
|
||||
reaction: repository.Reaction(ctx, db),
|
||||
mflag: repository.MessageFlag(ctx, db),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -92,9 +92,7 @@ func (svc *message) Find(filter *types.MessageFilter) (mm types.MessageSet, err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
svc.preloadUsers(mm)
|
||||
|
||||
return mm, svc.preloadAttachments(mm)
|
||||
return mm, svc.preload(mm)
|
||||
}
|
||||
|
||||
func (svc *message) FindThreads(filter *types.MessageFilter) (mm types.MessageSet, err error) {
|
||||
@@ -109,9 +107,7 @@ func (svc *message) FindThreads(filter *types.MessageFilter) (mm types.MessageSe
|
||||
return nil, err
|
||||
}
|
||||
|
||||
svc.preloadUsers(mm)
|
||||
|
||||
return mm, svc.preloadAttachments(mm)
|
||||
return mm, svc.preload(mm)
|
||||
}
|
||||
|
||||
func (svc *message) Create(in *types.Message) (message *types.Message, err error) {
|
||||
@@ -290,80 +286,114 @@ func (svc *message) Delete(ID uint64) error {
|
||||
})
|
||||
}
|
||||
|
||||
// React on a message with an emoji
|
||||
func (svc *message) React(messageID uint64, reaction string) error {
|
||||
// @todo get user from context
|
||||
var currentUserID uint64 = repository.Identity(svc.ctx)
|
||||
|
||||
// @todo verify if current user can access & write to this channel
|
||||
var m *types.Message
|
||||
|
||||
// @todo validate reaction
|
||||
|
||||
r := &types.Reaction{
|
||||
UserID: currentUserID,
|
||||
MessageID: messageID,
|
||||
ChannelID: m.ChannelID,
|
||||
Reaction: reaction,
|
||||
}
|
||||
|
||||
if _, err := svc.reaction.CreateReaction(r); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
return svc.flag(messageID, reaction, false)
|
||||
}
|
||||
|
||||
func (svc *message) Unreact(messageID uint64, reaction string) error {
|
||||
// @todo get user from context
|
||||
var currentUserID uint64 = repository.Identity(svc.ctx)
|
||||
|
||||
// @todo verify if current user can access & write to this channel
|
||||
_ = currentUserID
|
||||
|
||||
// @todo load reaction and verify ownership
|
||||
var r *types.Reaction
|
||||
|
||||
return svc.reaction.DeleteReactionByID(r.ID)
|
||||
// Remove reaction on a message
|
||||
func (svc *message) RemoveReaction(messageID uint64, reaction string) error {
|
||||
return svc.flag(messageID, reaction, true)
|
||||
}
|
||||
|
||||
// Pin message to the channel
|
||||
func (svc *message) Pin(messageID uint64) error {
|
||||
// @todo get user from context
|
||||
var currentUserID uint64 = repository.Identity(svc.ctx)
|
||||
|
||||
// @todo verify if current user can access & write to this channel
|
||||
_ = currentUserID
|
||||
|
||||
return nil
|
||||
return svc.flag(messageID, types.MessageFlagPinnedToChannel, false)
|
||||
}
|
||||
|
||||
func (svc *message) Unpin(messageID uint64) error {
|
||||
// @todo get user from context
|
||||
var currentUserID uint64 = repository.Identity(svc.ctx)
|
||||
|
||||
// @todo verify if current user can access & write to this channel
|
||||
_ = currentUserID
|
||||
|
||||
return nil
|
||||
// Remove pin from message
|
||||
func (svc *message) RemovePin(messageID uint64) error {
|
||||
return svc.flag(messageID, types.MessageFlagPinnedToChannel, true)
|
||||
}
|
||||
|
||||
func (svc *message) Flag(messageID uint64) error {
|
||||
// @todo get user from context
|
||||
var currentUserID uint64 = repository.Identity(svc.ctx)
|
||||
|
||||
// @todo verify if current user can access & write to this channel
|
||||
_ = currentUserID
|
||||
|
||||
return nil
|
||||
// Bookmark message (private)
|
||||
func (svc *message) Bookmark(messageID uint64) error {
|
||||
return svc.flag(messageID, types.MessageFlagBookmarkedMessage, false)
|
||||
}
|
||||
|
||||
func (svc *message) Unflag(messageID uint64) error {
|
||||
// Remove bookmark message (private)
|
||||
func (svc *message) RemoveBookmark(messageID uint64) error {
|
||||
return svc.flag(messageID, types.MessageFlagBookmarkedMessage, true)
|
||||
}
|
||||
|
||||
// React on a message with an emoji
|
||||
func (svc *message) flag(messageID uint64, flag string, remove bool) error {
|
||||
// @todo get user from context
|
||||
var currentUserID uint64 = repository.Identity(svc.ctx)
|
||||
|
||||
// @todo verify if current user can access & write to this channel
|
||||
_ = currentUserID
|
||||
|
||||
return nil
|
||||
if strings.TrimSpace(flag) == "" {
|
||||
// Sanitize
|
||||
flag = types.MessageFlagPinnedToChannel
|
||||
}
|
||||
|
||||
// @todo validate flags beyond empty string
|
||||
|
||||
err := svc.db.Transaction(func() (err error) {
|
||||
var flagOwnerId = currentUserID
|
||||
var f *types.MessageFlag
|
||||
|
||||
// @todo [SECURITY] verify if current user can access & write to this channel
|
||||
|
||||
if flag == types.MessageFlagPinnedToChannel {
|
||||
// It does not matter how is the owner of the pin,
|
||||
flagOwnerId = 0
|
||||
}
|
||||
|
||||
f, err = svc.mflag.FindByFlag(messageID, flagOwnerId, flag)
|
||||
if f.ID == 0 && remove {
|
||||
// Skip removing, flag does not exists
|
||||
return nil
|
||||
} else if f.ID > 0 && !remove {
|
||||
// Skip adding, flag already exists
|
||||
return nil
|
||||
} else if err != nil && err != repository.ErrMessageFlagNotFound {
|
||||
// Other errors, exit
|
||||
return
|
||||
}
|
||||
|
||||
// Check message
|
||||
var msg *types.Message
|
||||
msg, err = svc.message.FindMessageByID(messageID)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if remove {
|
||||
err = svc.mflag.DeleteByID(f.ID)
|
||||
} else {
|
||||
_, err = svc.mflag.Create(&types.MessageFlag{
|
||||
UserID: currentUserID,
|
||||
ChannelID: msg.ChannelID,
|
||||
MessageID: msg.ID,
|
||||
Flag: flag,
|
||||
})
|
||||
}
|
||||
|
||||
svc.sendEvent(msg)
|
||||
|
||||
return
|
||||
})
|
||||
|
||||
return errors.Wrap(err, "Can not flag/un-flag message")
|
||||
}
|
||||
|
||||
func (svc *message) preload(mm types.MessageSet) (err error) {
|
||||
if err = svc.preloadUsers(mm); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if err = svc.preloadAttachments(mm); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if err = svc.preloadFlags(mm); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Preload for all messages
|
||||
@@ -388,6 +418,21 @@ func (svc *message) preloadUsers(mm types.MessageSet) (err error) {
|
||||
return
|
||||
}
|
||||
|
||||
// Preload for all messages
|
||||
func (svc *message) preloadFlags(mm types.MessageSet) (err error) {
|
||||
var ff types.MessageFlagSet
|
||||
|
||||
ff, err = svc.mflag.FindByMessageIDs(mm.IDs()...)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
return ff.Walk(func(flag *types.MessageFlag) error {
|
||||
mm.FindById(flag.MessageID).Flags = append(mm.FindById(flag.MessageID).Flags, flag)
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func (svc *message) preloadAttachments(mm types.MessageSet) (err error) {
|
||||
var (
|
||||
ids []uint64
|
||||
@@ -422,8 +467,9 @@ func (svc *message) preloadAttachments(mm types.MessageSet) (err error) {
|
||||
|
||||
// Sends message to event loop
|
||||
func (svc *message) sendEvent(mm ...*types.Message) (err error) {
|
||||
svc.preloadAttachments(mm)
|
||||
svc.preloadUsers(mm)
|
||||
if err = svc.preload(mm); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
for _, msg := range mm {
|
||||
if msg.User == nil {
|
||||
|
||||
@@ -21,6 +21,7 @@ type (
|
||||
DeletedAt *time.Time `json:"deletedAt,omitempty" db:"deleted_at"`
|
||||
Attachment *Attachment `json:"attachment,omitempty"`
|
||||
User *authTypes.User `json:"user,omitempty"`
|
||||
Flags MessageFlagSet `json:"flags,omitempty"`
|
||||
}
|
||||
|
||||
MessageSet []*Message
|
||||
@@ -85,6 +86,16 @@ func (mm MessageSet) FindById(ID uint64) *Message {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (mm MessageSet) IDs() (IDs []uint64) {
|
||||
IDs = make([]uint64, len(mm))
|
||||
|
||||
for i := range mm {
|
||||
IDs[i] = mm[i].ID
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (mtype MessageType) String() string {
|
||||
return string(mtype)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,75 @@
|
||||
package types
|
||||
|
||||
import (
|
||||
"time"
|
||||
)
|
||||
|
||||
type (
|
||||
MessageFlag struct {
|
||||
ID uint64 `json:"id" db:"id"`
|
||||
UserID uint64 `json:"userId" db:"rel_user"`
|
||||
MessageID uint64 `json:"messageId" db:"rel_message"`
|
||||
ChannelID uint64 `json:"channelId" db:"rel_channel"`
|
||||
Flag string `json:"flag" db:"flag"`
|
||||
CreatedAt time.Time `json:"createdAt,omitempty" db:"created_at"`
|
||||
}
|
||||
|
||||
MessageFlagSet []*MessageFlag
|
||||
)
|
||||
|
||||
const (
|
||||
MessageFlagPinnedToChannel string = "pin"
|
||||
MessageFlagBookmarkedMessage string = "bookmark"
|
||||
)
|
||||
|
||||
func (ff MessageFlagSet) Walk(w func(*MessageFlag) error) (err error) {
|
||||
for i := range ff {
|
||||
if err = w(ff[i]); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (ff MessageFlagSet) FindById(ID uint64) *MessageFlag {
|
||||
for i := range ff {
|
||||
if ff[i].ID == ID {
|
||||
return ff[i]
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ff MessageFlagSet) IsBookmarked(UserID uint64) bool {
|
||||
for i := range ff {
|
||||
if ff[i].UserID == UserID && ff[i].IsBookmark() {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func (ff MessageFlagSet) IsPinned() bool {
|
||||
for i := range ff {
|
||||
if ff[i].IsPin() {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func (f *MessageFlag) IsReaction() bool {
|
||||
return f.Flag != MessageFlagPinnedToChannel && f.Flag != MessageFlagBookmarkedMessage
|
||||
}
|
||||
|
||||
func (f *MessageFlag) IsPin() bool {
|
||||
return f.Flag == MessageFlagPinnedToChannel
|
||||
}
|
||||
|
||||
func (f *MessageFlag) IsBookmark() bool {
|
||||
return f.Flag == MessageFlagBookmarkedMessage
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
package types
|
||||
|
||||
import (
|
||||
"time"
|
||||
)
|
||||
|
||||
type (
|
||||
Reaction struct {
|
||||
ID uint64 `json:"id" db:"id"`
|
||||
UserID uint64 `json:"userId" db:"rel_user"`
|
||||
MessageID uint64 `json:"messageId" db:"rel_message"`
|
||||
ChannelID uint64 `json:"channelId" db:"rel_channel"`
|
||||
Reaction string `json:"reaction" db:"reaction"`
|
||||
CreatedAt time.Time `json:"createdAt,omitempty" db:"created_at"`
|
||||
}
|
||||
)
|
||||
Reference in New Issue
Block a user