Add channel-membership flagging capabilities
This commit is contained in:
@@ -126,6 +126,46 @@
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "setFlag",
|
||||
"method": "PUT",
|
||||
"path": "/{channelID}/flag",
|
||||
"title": "Update channel membership flag",
|
||||
"parameters": {
|
||||
"path": [
|
||||
{
|
||||
"type": "uint64",
|
||||
"name": "channelID",
|
||||
"required": true,
|
||||
"title": "Channel ID"
|
||||
}
|
||||
],
|
||||
"post": [
|
||||
{
|
||||
"type": "string",
|
||||
"name": "flag",
|
||||
"required": true,
|
||||
"title": "Valid values: pinned, hidden, ignored"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "removeFlag",
|
||||
"method": "DELETE",
|
||||
"path": "/{channelID}/flag",
|
||||
"title": "Remove channel membership flag",
|
||||
"parameters": {
|
||||
"path": [
|
||||
{
|
||||
"type": "uint64",
|
||||
"name": "channelID",
|
||||
"required": true,
|
||||
"title": "Channel ID"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "read",
|
||||
"method": "GET",
|
||||
|
||||
@@ -128,6 +128,46 @@
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"Name": "setFlag",
|
||||
"Method": "PUT",
|
||||
"Title": "Update channel membership flag",
|
||||
"Path": "/{channelID}/flag",
|
||||
"Parameters": {
|
||||
"path": [
|
||||
{
|
||||
"name": "channelID",
|
||||
"required": true,
|
||||
"title": "Channel ID",
|
||||
"type": "uint64"
|
||||
}
|
||||
],
|
||||
"post": [
|
||||
{
|
||||
"name": "flag",
|
||||
"required": true,
|
||||
"title": "Valid values: pinned, hidden, ignored",
|
||||
"type": "string"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"Name": "removeFlag",
|
||||
"Method": "DELETE",
|
||||
"Title": "Remove channel membership flag",
|
||||
"Path": "/{channelID}/flag",
|
||||
"Parameters": {
|
||||
"path": [
|
||||
{
|
||||
"name": "channelID",
|
||||
"required": true,
|
||||
"title": "Channel ID",
|
||||
"type": "uint64"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"Name": "read",
|
||||
"Method": "GET",
|
||||
|
||||
@@ -102,6 +102,35 @@ A channel is a representation of a sequence of messages. It has meta data like c
|
||||
| channelID | uint64 | PATH | Channel ID | N/A | YES |
|
||||
| state | string | POST | Valid values: delete, undelete, archive, unarchive | N/A | YES |
|
||||
|
||||
## Update channel membership flag
|
||||
|
||||
#### Method
|
||||
|
||||
| URI | Protocol | Method | Authentication |
|
||||
| --- | -------- | ------ | -------------- |
|
||||
| `/channels/{channelID}/flag` | HTTP/S | PUT | Client ID, Session ID |
|
||||
|
||||
#### Request parameters
|
||||
|
||||
| Parameter | Type | Method | Description | Default | Required? |
|
||||
| --------- | ---- | ------ | ----------- | ------- | --------- |
|
||||
| channelID | uint64 | PATH | Channel ID | N/A | YES |
|
||||
| flag | string | POST | Valid values: pinned, hidden, ignored | N/A | YES |
|
||||
|
||||
## Remove channel membership flag
|
||||
|
||||
#### Method
|
||||
|
||||
| URI | Protocol | Method | Authentication |
|
||||
| --- | -------- | ------ | -------------- |
|
||||
| `/channels/{channelID}/flag` | HTTP/S | DELETE | Client ID, Session ID |
|
||||
|
||||
#### Request parameters
|
||||
|
||||
| Parameter | Type | Method | Description | Default | Required? |
|
||||
| --------- | ---- | ------ | ----------- | ------- | --------- |
|
||||
| channelID | uint64 | PATH | Channel ID | N/A | YES |
|
||||
|
||||
## Read channel details
|
||||
|
||||
#### Method
|
||||
|
||||
@@ -121,14 +121,21 @@ func MessagePinRemoved(f *samTypes.MessageFlag) *outgoing.MessagePinRemoved {
|
||||
}
|
||||
|
||||
func Channel(ch *samTypes.Channel) *outgoing.Channel {
|
||||
var flag = samTypes.ChannelMembershipFlagNone
|
||||
|
||||
if ch.Member != nil {
|
||||
flag = ch.Member.Flag
|
||||
}
|
||||
|
||||
return &outgoing.Channel{
|
||||
ID: Uint64toa(ch.ID),
|
||||
Name: ch.Name,
|
||||
LastMessageID: Uint64toa(ch.LastMessageID),
|
||||
Topic: ch.Topic,
|
||||
Type: string(ch.Type),
|
||||
Members: Uint64stoa(ch.Members),
|
||||
Unread: Unread(ch.Unread),
|
||||
ID: Uint64toa(ch.ID),
|
||||
Name: ch.Name,
|
||||
LastMessageID: Uint64toa(ch.LastMessageID),
|
||||
Topic: ch.Topic,
|
||||
Type: string(ch.Type),
|
||||
MembershipFlag: string(flag),
|
||||
Members: Uint64stoa(ch.Members),
|
||||
Unread: Unread(ch.Unread),
|
||||
|
||||
CanJoin: ch.CanJoin,
|
||||
CanPart: ch.CanPart,
|
||||
|
||||
@@ -31,13 +31,14 @@ type (
|
||||
|
||||
Channel struct {
|
||||
// Channel to part (nil) for ALL channels
|
||||
ID string `json:"ID"`
|
||||
Name string `json:"name"`
|
||||
Topic string `json:"topic"`
|
||||
Type string `json:"type"`
|
||||
LastMessageID string `json:"lastMessageID"`
|
||||
Members []string `json:"members,omitempty"`
|
||||
Unread *Unread `json:"unread,omitempty"`
|
||||
ID string `json:"ID"`
|
||||
Name string `json:"name"`
|
||||
Topic string `json:"topic"`
|
||||
Type string `json:"type"`
|
||||
LastMessageID string `json:"lastMessageID"`
|
||||
Unread *Unread `json:"unread,omitempty"`
|
||||
Members []string `json:"members,omitempty"`
|
||||
MembershipFlag string `json:"membershipFlag"`
|
||||
|
||||
CanJoin bool `json:"canJoin"`
|
||||
CanPart bool `json:"canPart"`
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1 @@
|
||||
ALTER TABLE channel_members ADD flag ENUM ('pinned', 'hidden', 'ignored', '') NOT NULL DEFAULT '' AFTER `type`;
|
||||
@@ -93,7 +93,7 @@ func (r *channelMember) Create(mod *types.ChannelMember) (*types.ChannelMember,
|
||||
func (r *channelMember) Update(mod *types.ChannelMember) (*types.ChannelMember, error) {
|
||||
mod.UpdatedAt = timeNowPtr()
|
||||
|
||||
whitelist := []string{"type", "updated_at", "rel_channel", "rel_user"}
|
||||
whitelist := []string{"type", "flag", "updated_at", "rel_channel", "rel_user"}
|
||||
|
||||
return mod, r.db().UpdatePartial("channel_members", mod, whitelist, "rel_channel", "rel_user")
|
||||
}
|
||||
|
||||
@@ -67,6 +67,19 @@ func (ctrl *Channel) State(ctx context.Context, r *request.ChannelState) (interf
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (ctrl *Channel) SetFlag(ctx context.Context, r *request.ChannelSetFlag) (interface{}, error) {
|
||||
switch r.Flag {
|
||||
case "pinned", "hidden", "ignored":
|
||||
return ctrl.wrap(ctrl.svc.ch.With(ctx).SetFlag(r.ChannelID, types.ChannelMembershipFlag(r.Flag)))
|
||||
}
|
||||
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (ctrl *Channel) RemoveFlag(ctx context.Context, r *request.ChannelRemoveFlag) (interface{}, error) {
|
||||
return ctrl.wrap(ctrl.svc.ch.With(ctx).SetFlag(r.ChannelID, types.ChannelMembershipFlagNone))
|
||||
}
|
||||
|
||||
func (ctrl *Channel) Read(ctx context.Context, r *request.ChannelRead) (interface{}, error) {
|
||||
return ctrl.wrap(ctrl.svc.ch.With(ctx).FindByID(r.ChannelID))
|
||||
}
|
||||
|
||||
@@ -31,6 +31,8 @@ type ChannelAPI interface {
|
||||
Create(context.Context, *request.ChannelCreate) (interface{}, error)
|
||||
Update(context.Context, *request.ChannelUpdate) (interface{}, error)
|
||||
State(context.Context, *request.ChannelState) (interface{}, error)
|
||||
SetFlag(context.Context, *request.ChannelSetFlag) (interface{}, error)
|
||||
RemoveFlag(context.Context, *request.ChannelRemoveFlag) (interface{}, error)
|
||||
Read(context.Context, *request.ChannelRead) (interface{}, error)
|
||||
Members(context.Context, *request.ChannelMembers) (interface{}, error)
|
||||
Join(context.Context, *request.ChannelJoin) (interface{}, error)
|
||||
@@ -41,16 +43,18 @@ type ChannelAPI interface {
|
||||
|
||||
// HTTP API interface
|
||||
type Channel struct {
|
||||
List func(http.ResponseWriter, *http.Request)
|
||||
Create func(http.ResponseWriter, *http.Request)
|
||||
Update func(http.ResponseWriter, *http.Request)
|
||||
State func(http.ResponseWriter, *http.Request)
|
||||
Read func(http.ResponseWriter, *http.Request)
|
||||
Members func(http.ResponseWriter, *http.Request)
|
||||
Join func(http.ResponseWriter, *http.Request)
|
||||
Part func(http.ResponseWriter, *http.Request)
|
||||
Invite func(http.ResponseWriter, *http.Request)
|
||||
Attach func(http.ResponseWriter, *http.Request)
|
||||
List func(http.ResponseWriter, *http.Request)
|
||||
Create func(http.ResponseWriter, *http.Request)
|
||||
Update func(http.ResponseWriter, *http.Request)
|
||||
State func(http.ResponseWriter, *http.Request)
|
||||
SetFlag func(http.ResponseWriter, *http.Request)
|
||||
RemoveFlag func(http.ResponseWriter, *http.Request)
|
||||
Read func(http.ResponseWriter, *http.Request)
|
||||
Members func(http.ResponseWriter, *http.Request)
|
||||
Join func(http.ResponseWriter, *http.Request)
|
||||
Part func(http.ResponseWriter, *http.Request)
|
||||
Invite func(http.ResponseWriter, *http.Request)
|
||||
Attach func(http.ResponseWriter, *http.Request)
|
||||
}
|
||||
|
||||
func NewChannel(ch ChannelAPI) *Channel {
|
||||
@@ -83,6 +87,20 @@ func NewChannel(ch ChannelAPI) *Channel {
|
||||
return ch.State(r.Context(), params)
|
||||
})
|
||||
},
|
||||
SetFlag: func(w http.ResponseWriter, r *http.Request) {
|
||||
defer r.Body.Close()
|
||||
params := request.NewChannelSetFlag()
|
||||
resputil.JSON(w, params.Fill(r), func() (interface{}, error) {
|
||||
return ch.SetFlag(r.Context(), params)
|
||||
})
|
||||
},
|
||||
RemoveFlag: func(w http.ResponseWriter, r *http.Request) {
|
||||
defer r.Body.Close()
|
||||
params := request.NewChannelRemoveFlag()
|
||||
resputil.JSON(w, params.Fill(r), func() (interface{}, error) {
|
||||
return ch.RemoveFlag(r.Context(), params)
|
||||
})
|
||||
},
|
||||
Read: func(w http.ResponseWriter, r *http.Request) {
|
||||
defer r.Body.Close()
|
||||
params := request.NewChannelRead()
|
||||
@@ -136,6 +154,8 @@ func (ch *Channel) MountRoutes(r chi.Router, middlewares ...func(http.Handler) h
|
||||
r.Post("/", ch.Create)
|
||||
r.Put("/{channelID}", ch.Update)
|
||||
r.Put("/{channelID}/state", ch.State)
|
||||
r.Put("/{channelID}/flag", ch.SetFlag)
|
||||
r.Delete("/{channelID}/flag", ch.RemoveFlag)
|
||||
r.Get("/{channelID}", ch.Read)
|
||||
r.Get("/{channelID}/members", ch.Members)
|
||||
r.Put("/{channelID}/members/{userID}", ch.Join)
|
||||
|
||||
@@ -243,6 +243,97 @@ func (c *ChannelState) Fill(r *http.Request) (err error) {
|
||||
|
||||
var _ RequestFiller = NewChannelState()
|
||||
|
||||
// Channel setFlag request parameters
|
||||
type ChannelSetFlag struct {
|
||||
ChannelID uint64 `json:",string"`
|
||||
Flag string
|
||||
}
|
||||
|
||||
func NewChannelSetFlag() *ChannelSetFlag {
|
||||
return &ChannelSetFlag{}
|
||||
}
|
||||
|
||||
func (c *ChannelSetFlag) Fill(r *http.Request) (err error) {
|
||||
if strings.ToLower(r.Header.Get("content-type")) == "application/json" {
|
||||
err = json.NewDecoder(r.Body).Decode(c)
|
||||
|
||||
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])
|
||||
}
|
||||
|
||||
c.ChannelID = parseUInt64(chi.URLParam(r, "channelID"))
|
||||
if val, ok := post["flag"]; ok {
|
||||
|
||||
c.Flag = val
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
var _ RequestFiller = NewChannelSetFlag()
|
||||
|
||||
// Channel removeFlag request parameters
|
||||
type ChannelRemoveFlag struct {
|
||||
ChannelID uint64 `json:",string"`
|
||||
}
|
||||
|
||||
func NewChannelRemoveFlag() *ChannelRemoveFlag {
|
||||
return &ChannelRemoveFlag{}
|
||||
}
|
||||
|
||||
func (c *ChannelRemoveFlag) Fill(r *http.Request) (err error) {
|
||||
if strings.ToLower(r.Header.Get("content-type")) == "application/json" {
|
||||
err = json.NewDecoder(r.Body).Decode(c)
|
||||
|
||||
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])
|
||||
}
|
||||
|
||||
c.ChannelID = parseUInt64(chi.URLParam(r, "channelID"))
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
var _ RequestFiller = NewChannelRemoveFlag()
|
||||
|
||||
// Channel read request parameters
|
||||
type ChannelRead struct {
|
||||
ChannelID uint64 `json:",string"`
|
||||
|
||||
+32
-1
@@ -44,6 +44,8 @@ type (
|
||||
AddMember(channelID uint64, memberIDs ...uint64) (out types.ChannelMemberSet, err error)
|
||||
DeleteMember(channelID uint64, memberIDs ...uint64) (err error)
|
||||
|
||||
SetFlag(ID uint64, flag types.ChannelMembershipFlag) (*types.Channel, error)
|
||||
|
||||
Archive(ID uint64) (*types.Channel, error)
|
||||
Unarchive(ID uint64) (*types.Channel, error)
|
||||
Delete(ID uint64) (*types.Channel, error)
|
||||
@@ -480,6 +482,35 @@ func (svc *channel) Undelete(ID uint64) (ch *types.Channel, err error) {
|
||||
})
|
||||
}
|
||||
|
||||
func (svc *channel) SetFlag(ID uint64, flag types.ChannelMembershipFlag) (ch *types.Channel, err error) {
|
||||
return ch, svc.db.Transaction(func() (err error) {
|
||||
var membership *types.ChannelMember
|
||||
var userID = repository.Identity(svc.ctx)
|
||||
|
||||
if ch, err = svc.FindByID(ID); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if members, err := svc.cmember.Find(&types.ChannelMemberFilter{ChannelID: ch.ID, MemberID: userID}); err != nil {
|
||||
return err
|
||||
} else if len(members) == 1 {
|
||||
membership = members[0]
|
||||
membership.Flag = flag
|
||||
}
|
||||
|
||||
if membership == nil {
|
||||
return errors.New("not a member")
|
||||
}
|
||||
|
||||
if ch.Member, err = svc.cmember.Update(membership); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
svc.flushSystemMessages()
|
||||
return svc.sendChannelEvent(ch)
|
||||
})
|
||||
}
|
||||
|
||||
func (svc *channel) Archive(ID uint64) (ch *types.Channel, err error) {
|
||||
return ch, svc.db.Transaction(func() (err error) {
|
||||
var userID = repository.Identity(svc.ctx)
|
||||
@@ -809,7 +840,7 @@ func (svc *channel) setPermissionFlags(ch *types.Channel) (err error) {
|
||||
)
|
||||
|
||||
ch.CanJoin = (ch.IsValid() && isPublic) || isOwner
|
||||
ch.CanPart = isMember
|
||||
ch.CanPart = isMember && ch.Type != types.ChannelTypeGroup
|
||||
ch.CanObserve = (ch.IsValid() && isPublic) || isMember
|
||||
ch.CanSendMessages = ch.CanObserve && isMember
|
||||
ch.CanDeleteMessages = isOwner
|
||||
|
||||
@@ -14,6 +14,7 @@ type (
|
||||
User *systemTypes.User `db:"-"`
|
||||
|
||||
Type ChannelMembershipType `db:"type"`
|
||||
Flag ChannelMembershipFlag `db:"flag"`
|
||||
|
||||
CreatedAt time.Time `json:"createdAt,omitempty" db:"created_at"`
|
||||
UpdatedAt *time.Time `json:"updatedAt,omitempty" db:"updated_at"`
|
||||
@@ -26,10 +27,16 @@ type (
|
||||
}
|
||||
|
||||
ChannelMembershipType string
|
||||
ChannelMembershipFlag string
|
||||
)
|
||||
|
||||
const (
|
||||
ChannelMembershipTypeOwner ChannelMembershipType = "owner"
|
||||
ChannelMembershipTypeMember = "member"
|
||||
ChannelMembershipTypeInvitee = "invitee"
|
||||
ChannelMembershipTypeMember ChannelMembershipType = "member"
|
||||
ChannelMembershipTypeInvitee ChannelMembershipType = "invitee"
|
||||
|
||||
ChannelMembershipFlagPinned ChannelMembershipFlag = "pinned"
|
||||
ChannelMembershipFlagHidden ChannelMembershipFlag = "hidden"
|
||||
ChannelMembershipFlagIgnored ChannelMembershipFlag = "ignored"
|
||||
ChannelMembershipFlagNone ChannelMembershipFlag = ""
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user