Add support for membership policy, featured channels

This commit is contained in:
Denis Arh
2019-08-08 11:43:51 +02:00
parent aecc67f3cb
commit c1e3231d00
14 changed files with 176 additions and 67 deletions
+19
View File
@@ -110,6 +110,13 @@
"Client ID",
"Session ID"
],
"struct": [
{
"imports": [
"github.com/cortezaproject/corteza-server/messaging/types"
]
}
],
"apis": [
{
"name": "list",
@@ -152,6 +159,12 @@
"required": false,
"title": "Channel type"
},
{
"type": "types.ChannelMembershipPolicy",
"name": "membershipPolicy",
"required": false,
"title": "Membership policy (eg: featured, forced)?"
},
{
"type": "[]string",
"name": "members",
@@ -188,6 +201,12 @@
"required": false,
"title": "Subject of Channel"
},
{
"type": "types.ChannelMembershipPolicy",
"name": "membershipPolicy",
"required": false,
"title": "Membership policy (eg: featured, forced)?"
},
{
"type": "string",
"name": "type",
+19 -1
View File
@@ -2,7 +2,13 @@
"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.",
"Interface": "Channel",
"Struct": null,
"Struct": [
{
"imports": [
"github.com/cortezaproject/corteza-server/messaging/types"
]
}
],
"Parameters": null,
"Protocol": "",
"Authentication": [
@@ -52,6 +58,12 @@
"title": "Channel type",
"type": "string"
},
{
"name": "membershipPolicy",
"required": false,
"title": "Membership policy (eg: featured, forced)?",
"type": "types.ChannelMembershipPolicy"
},
{
"name": "members",
"required": false,
@@ -88,6 +100,12 @@
"title": "Subject of Channel",
"type": "string"
},
{
"name": "membershipPolicy",
"required": false,
"title": "Membership policy (eg: featured, forced)?",
"type": "types.ChannelMembershipPolicy"
},
{
"name": "type",
"required": false,
File diff suppressed because one or more lines are too long
+2
View File
@@ -120,6 +120,7 @@ A channel is a representation of a sequence of messages. It has meta data like c
| name | string | POST | Name of Channel | N/A | NO |
| topic | string | POST | Subject of Channel | N/A | NO |
| type | string | POST | Channel type | N/A | NO |
| membershipPolicy | types.ChannelMembershipPolicy | POST | Membership policy (eg: featured, forced)? | N/A | NO |
| members | []string | POST | Initial members of the channel | N/A | NO |
## Update channel details
@@ -137,6 +138,7 @@ A channel is a representation of a sequence of messages. It has meta data like c
| channelID | uint64 | PATH | Channel ID | N/A | YES |
| name | string | POST | Name of Channel | N/A | NO |
| topic | string | POST | Subject of Channel | N/A | NO |
| membershipPolicy | types.ChannelMembershipPolicy | POST | Membership policy (eg: featured, forced)? | N/A | NO |
| type | string | POST | Channel type | N/A | NO |
| organisationID | uint64 | POST | Move channel to different organisation | N/A | NO |
+19 -17
View File
@@ -138,24 +138,26 @@ func Channel(ch *messagingTypes.Channel) *outgoing.Channel {
}
return &outgoing.Channel{
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: ChannelUnread(ch.Unread),
ID: Uint64toa(ch.ID),
Name: ch.Name,
LastMessageID: Uint64toa(ch.LastMessageID),
Topic: ch.Topic,
Type: string(ch.Type),
MembershipFlag: string(flag),
MembershipPolicy: string(ch.MembershipPolicy),
Members: Uint64stoa(ch.Members),
Unread: ChannelUnread(ch.Unread),
CanJoin: ch.CanJoin,
CanPart: ch.CanPart,
CanObserve: ch.CanObserve,
CanSendMessages: ch.CanSendMessages,
CanDeleteMessages: ch.CanDeleteMessages,
CanChangeMembers: ch.CanChangeMembers,
CanUpdate: ch.CanUpdate,
CanArchive: ch.CanArchive,
CanDelete: ch.CanDelete,
CanJoin: ch.CanJoin,
CanPart: ch.CanPart,
CanObserve: ch.CanObserve,
CanSendMessages: ch.CanSendMessages,
CanDeleteMessages: ch.CanDeleteMessages,
CanChangeMembers: ch.CanChangeMembers,
CanChangeMembershipPolicy: ch.CanChangeMembershipPolicy,
CanUpdate: ch.CanUpdate,
CanArchive: ch.CanArchive,
CanDelete: ch.CanDelete,
CreatedAt: ch.CreatedAt,
UpdatedAt: ch.UpdatedAt,
+19 -17
View File
@@ -24,24 +24,26 @@ type (
Channel struct {
// Channel to part (nil) for ALL channels
ID string `json:"channelID"`
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"`
ID string `json:"channelID"`
Name string `json:"name"`
Topic string `json:"topic"`
Type string `json:"type"`
MembershipPolicy string `json:"membershipPolicy"`
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"`
CanObserve bool `json:"canObserve"`
CanSendMessages bool `json:"canSendMessages"`
CanDeleteMessages bool `json:"canDeleteMessages"`
CanChangeMembers bool `json:"canChangeMembers"`
CanUpdate bool `json:"canUpdate"`
CanArchive bool `json:"canArchive"`
CanDelete bool `json:"canDelete"`
CanJoin bool `json:"canJoin"`
CanPart bool `json:"canPart"`
CanObserve bool `json:"canObserve"`
CanSendMessages bool `json:"canSendMessages"`
CanDeleteMessages bool `json:"canDeleteMessages"`
CanChangeMembers bool `json:"canChangeMembers"`
CanChangeMembershipPolicy bool `json:"canChangeMembershipPolicy"`
CanUpdate bool `json:"canUpdate"`
CanArchive bool `json:"canArchive"`
CanDelete bool `json:"canDelete"`
CreatedAt time.Time `json:"createdAt"`
UpdatedAt *time.Time `json:"updatedAt,omitempty"`
File diff suppressed because one or more lines are too long
@@ -0,0 +1 @@
ALTER TABLE `messaging_channel` ADD `membership_policy` ENUM ('featured', 'forced', '') NOT NULL DEFAULT '' AFTER `type`;
+2 -1
View File
@@ -40,6 +40,7 @@ const (
sqlChannelColumns = " id," +
"name, " +
"meta, " +
"membership_policy, " +
"created_at, " +
"updated_at, " +
"archived_at, " +
@@ -159,7 +160,7 @@ func (r *channel) Update(mod *types.Channel) (*types.Channel, error) {
mod.Type = types.ChannelTypePublic
}
whitelist := []string{"id", "name", "type", "topic", "meta", "updated_at"}
whitelist := []string{"id", "name", "type", "membership_policy", "topic", "meta", "updated_at"}
return mod, r.db().UpdatePartial("messaging_channel", mod, whitelist, "id")
}
@@ -115,6 +115,11 @@ func (svc accessControl) CanManageChannelMembers(ctx context.Context, ch *types.
return svc.can(ctx, ch, "members.manage", svc.isChannelOwnerFallback(ctx, ch))
}
func (svc accessControl) CanChangeChannelMembershipPolicy(ctx context.Context, ch *types.Channel) bool {
// @todo introduce dedicated channel op. for this.
return svc.can(ctx, types.MessagingPermissionResource, "grant")
}
func (svc accessControl) CanManageChannelAttachments(ctx context.Context, ch *types.Channel) bool {
return svc.can(ctx, ch, "attachments.manage")
}
+25 -5
View File
@@ -42,6 +42,7 @@ type (
CanArchiveChannel(context.Context, *types.Channel) bool
CanUnarchiveChannel(context.Context, *types.Channel) bool
CanManageChannelMembers(context.Context, *types.Channel) bool
CanChangeChannelMembershipPolicy(context.Context, *types.Channel) bool
CanSendMessage(context.Context, *types.Channel) bool
CanUpdateOwnMessages(context.Context, *types.Channel) bool
CanUpdateMessages(context.Context, *types.Channel) bool
@@ -268,13 +269,23 @@ func (svc *channel) Create(in *types.Channel) (out *types.Channel, err error) {
return ErrNoPermissions.withStack()
}
if !in.MembershipPolicy.IsValid() {
// Reset invalid membership flag to default
in.MembershipPolicy = types.ChannelMembershipPolicyDefault
}
if in.MembershipPolicy != types.ChannelMembershipPolicyDefault && !svc.ac.CanChangeChannelMembershipPolicy(svc.ctx, in) {
return ErrNoPermissions.withStack()
}
// This is a fresh channel, just copy values
out = &types.Channel{
Name: in.Name,
Topic: in.Topic,
Type: in.Type,
OrganisationID: organisationID,
CreatorID: chCreatorID,
Name: in.Name,
Topic: in.Topic,
Type: in.Type,
MembershipPolicy: in.MembershipPolicy,
OrganisationID: organisationID,
CreatorID: chCreatorID,
}
// Save the channel
@@ -427,6 +438,13 @@ func (svc *channel) Update(in *types.Channel) (ch *types.Channel, err error) {
changed = true
}
if ch.MembershipPolicy != in.MembershipPolicy && !svc.ac.CanChangeChannelMembershipPolicy(svc.ctx, ch) {
return ErrNoPermissions.withStack()
} else {
ch.MembershipPolicy = in.MembershipPolicy
changed = true
}
if !changed {
return nil
}
@@ -891,6 +909,8 @@ func (svc *channel) setPermissionFlags(ch *types.Channel) (err error) {
ch.CanUpdateMessages = svc.ac.CanUpdateMessages(svc.ctx, ch)
ch.CanUpdateOwnMessages = svc.ac.CanUpdateOwnMessages(svc.ctx, ch)
ch.CanChangeMembers = svc.ac.CanManageChannelMembers(svc.ctx, ch)
// @todo migrate to proper change-membership-policy action check
ch.CanChangeMembershipPolicy = svc.ac.CanChangeChannelMembershipPolicy(svc.ctx, ch)
ch.CanUpdate = svc.ac.CanUpdateChannel(svc.ctx, ch)
ch.CanArchive = svc.ac.CanArchiveChannel(svc.ctx, ch)
+4
View File
@@ -41,6 +41,8 @@ func (ctrl *Channel) Create(ctx context.Context, r *request.ChannelCreate) (inte
// string input for members (for now)
// https://github.com/golang/go/issues/15624
Members: payload.ParseUInt64s(r.Members),
MembershipPolicy: r.MembershipPolicy,
}
return ctrl.wrap(ctrl.svc.ch.With(ctx).Create(channel))
@@ -52,6 +54,8 @@ func (ctrl *Channel) Update(ctx context.Context, r *request.ChannelUpdate) (inte
Name: r.Name,
Topic: r.Topic,
Type: types.ChannelType(r.Type),
MembershipPolicy: r.MembershipPolicy,
}
return ctrl.wrap(ctrl.svc.ch.With(ctx).Update(channel))
+21 -9
View File
@@ -25,6 +25,8 @@ import (
"github.com/go-chi/chi"
"github.com/pkg/errors"
"github.com/cortezaproject/corteza-server/messaging/types"
)
var _ = chi.URLParam
@@ -85,10 +87,11 @@ var _ RequestFiller = NewChannelList()
// Channel create request parameters
type ChannelCreate struct {
Name string
Topic string
Type string
Members []string
Name string
Topic string
Type string
MembershipPolicy types.ChannelMembershipPolicy
Members []string
}
func NewChannelCreate() *ChannelCreate {
@@ -101,6 +104,7 @@ func (r ChannelCreate) Auditable() map[string]interface{} {
out["name"] = r.Name
out["topic"] = r.Topic
out["type"] = r.Type
out["membershipPolicy"] = r.MembershipPolicy
out["members"] = r.Members
return out
@@ -142,6 +146,9 @@ func (r *ChannelCreate) Fill(req *http.Request) (err error) {
if val, ok := post["type"]; ok {
r.Type = val
}
if val, ok := post["membershipPolicy"]; ok {
r.MembershipPolicy = types.ChannelMembershipPolicy(val)
}
if val, ok := req.Form["members"]; ok {
r.Members = parseStrings(val)
@@ -154,11 +161,12 @@ var _ RequestFiller = NewChannelCreate()
// Channel update request parameters
type ChannelUpdate struct {
ChannelID uint64 `json:",string"`
Name string
Topic string
Type string
OrganisationID uint64 `json:",string"`
ChannelID uint64 `json:",string"`
Name string
Topic string
MembershipPolicy types.ChannelMembershipPolicy
Type string
OrganisationID uint64 `json:",string"`
}
func NewChannelUpdate() *ChannelUpdate {
@@ -171,6 +179,7 @@ func (r ChannelUpdate) Auditable() map[string]interface{} {
out["channelID"] = r.ChannelID
out["name"] = r.Name
out["topic"] = r.Topic
out["membershipPolicy"] = r.MembershipPolicy
out["type"] = r.Type
out["organisationID"] = r.OrganisationID
@@ -211,6 +220,9 @@ func (r *ChannelUpdate) Fill(req *http.Request) (err error) {
if val, ok := post["topic"]; ok {
r.Topic = val
}
if val, ok := post["membershipPolicy"]; ok {
r.MembershipPolicy = types.ChannelMembershipPolicy(val)
}
if val, ok := post["type"]; ok {
r.Type = val
}
+38 -15
View File
@@ -15,6 +15,8 @@ type (
Type ChannelType `json:"type" db:"type"`
Meta types.JSONText `json:"-" db:"meta"`
MembershipPolicy ChannelMembershipPolicy `json:"membershipPolicy" db:"membership_policy""`
CreatorID uint64 `json:"creatorId" db:"rel_creator"`
OrganisationID uint64 `json:"organisationId" db:"rel_organisation"`
@@ -26,20 +28,21 @@ type (
LastMessageID uint64 `json:",omitempty" db:"rel_last_message"`
CanJoin bool `json:"-" db:"-"`
CanPart bool `json:"-" db:"-"`
CanObserve bool `json:"-" db:"-"`
CanSendMessages bool `json:"-" db:"-"`
CanDeleteMessages bool `json:"-" db:"-"`
CanDeleteOwnMessages bool `json:"-" db:"-"`
CanUpdateMessages bool `json:"-" db:"-"`
CanUpdateOwnMessages bool `json:"-" db:"-"`
CanChangeMembers bool `json:"-" db:"-"`
CanUpdate bool `json:"-" db:"-"`
CanArchive bool `json:"-" db:"-"`
CanUnarchive bool `json:"-" db:"-"`
CanDelete bool `json:"-" db:"-"`
CanUndelete bool `json:"-" db:"-"`
CanJoin bool `json:"-" db:"-"`
CanPart bool `json:"-" db:"-"`
CanObserve bool `json:"-" db:"-"`
CanSendMessages bool `json:"-" db:"-"`
CanDeleteMessages bool `json:"-" db:"-"`
CanDeleteOwnMessages bool `json:"-" db:"-"`
CanUpdateMessages bool `json:"-" db:"-"`
CanUpdateOwnMessages bool `json:"-" db:"-"`
CanChangeMembers bool `json:"-" db:"-"`
CanChangeMembershipPolicy bool `json:"-" db:"-"`
CanUpdate bool `json:"-" db:"-"`
CanArchive bool `json:"-" db:"-"`
CanUnarchive bool `json:"-" db:"-"`
CanDelete bool `json:"-" db:"-"`
CanUndelete bool `json:"-" db:"-"`
Member *ChannelMember `json:"-" db:"-"`
Members []uint64 `json:"-" db:"-"`
@@ -56,7 +59,8 @@ type (
IncludeDeleted bool
}
ChannelType string
ChannelMembershipPolicy string
ChannelType string
)
// Resource returns a system resource ID for this type
@@ -72,6 +76,10 @@ const (
ChannelTypePublic ChannelType = "public"
ChannelTypePrivate ChannelType = "private"
ChannelTypeGroup ChannelType = "group"
ChannelMembershipPolicyFeatured ChannelMembershipPolicy = "featured"
ChannelMembershipPolicyForced ChannelMembershipPolicy = "forced"
ChannelMembershipPolicyDefault ChannelMembershipPolicy = ""
)
func (mtype ChannelType) String() string {
@@ -88,3 +96,18 @@ func (mtype ChannelType) IsValid() bool {
return false
}
func (cm ChannelMembershipPolicy) String() string {
return string(cm)
}
func (cm ChannelMembershipPolicy) IsValid() bool {
switch cm {
case ChannelMembershipPolicyFeatured,
ChannelMembershipPolicyForced,
ChannelMembershipPolicyDefault:
return true
}
return false
}