Redone channel (un)deleting/(un)archiving
This commit is contained in:
@@ -29,10 +29,6 @@ type (
|
||||
Type *string `json:"type"`
|
||||
}
|
||||
|
||||
ChannelDelete struct {
|
||||
ChannelID string `json:"id"`
|
||||
}
|
||||
|
||||
// ChannelActivity is sent from the client when there is an activity on the channel...
|
||||
ChannelActivity struct {
|
||||
ChannelID uint64 `json:"ID,string"`
|
||||
|
||||
@@ -8,7 +8,6 @@ type Payload struct {
|
||||
|
||||
*ChannelCreate `json:"createChannel"`
|
||||
*ChannelUpdate `json:"updateChannel"`
|
||||
*ChannelDelete `json:"deleteChannel"`
|
||||
|
||||
*ChannelViewRecord `json:"recordChannelView"`
|
||||
|
||||
|
||||
+15
-15
@@ -265,9 +265,23 @@ 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 |
|
||||
| archive | bool | POST | Request channel to be archived or unarchived | N/A | NO |
|
||||
| organisationID | uint64 | POST | Move channel to different organisation | N/A | NO |
|
||||
|
||||
## Update channel state
|
||||
|
||||
#### Method
|
||||
|
||||
| URI | Protocol | Method | Authentication |
|
||||
| --- | -------- | ------ | -------------- |
|
||||
| `/channels/{channelID}/state` | HTTP/S | PUT | Client ID, Session ID |
|
||||
|
||||
#### Request parameters
|
||||
|
||||
| Parameter | Type | Method | Description | Default | Required? |
|
||||
| --------- | ---- | ------ | ----------- | ------- | --------- |
|
||||
| channelID | uint64 | PATH | Channel ID | N/A | YES |
|
||||
| state | string | POST | Valid values: delete, restore, archive, unarchive | N/A | YES |
|
||||
|
||||
## Read channel details
|
||||
|
||||
#### Method
|
||||
@@ -282,20 +296,6 @@ A channel is a representation of a sequence of messages. It has meta data like c
|
||||
| --------- | ---- | ------ | ----------- | ------- | --------- |
|
||||
| channelID | uint64 | PATH | Channel ID | N/A | YES |
|
||||
|
||||
## Remove channel
|
||||
|
||||
#### Method
|
||||
|
||||
| URI | Protocol | Method | Authentication |
|
||||
| --- | -------- | ------ | -------------- |
|
||||
| `/channels/{channelID}` | HTTP/S | DELETE | Client ID, Session ID |
|
||||
|
||||
#### Request parameters
|
||||
|
||||
| Parameter | Type | Method | Description | Default | Required? |
|
||||
| --------- | ---- | ------ | ----------- | ------- | --------- |
|
||||
| channelID | uint64 | PATH | Channel ID | N/A | YES |
|
||||
|
||||
## List channel members
|
||||
|
||||
#### Method
|
||||
|
||||
+15
-13
@@ -221,7 +221,7 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "edit",
|
||||
"name": "update",
|
||||
"method": "PUT",
|
||||
"path": "/{channelID}",
|
||||
"title": "Update channel details",
|
||||
@@ -233,11 +233,24 @@
|
||||
{ "type": "string", "name": "name", "required": false, "title": "Name of Channel" },
|
||||
{ "type": "string", "name": "topic", "required": false, "title": "Subject of Channel" },
|
||||
{ "type": "string", "name": "type", "required": false, "title": "Channel type" },
|
||||
{ "type": "bool", "name": "archive", "required": false, "title": "Request channel to be archived or unarchived" },
|
||||
{ "type": "uint64", "name": "organisationID", "required": false, "title": "Move channel to different organisation" }
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "state",
|
||||
"method": "PUT",
|
||||
"path": "/{channelID}/state",
|
||||
"title": "Update channel state",
|
||||
"parameters": {
|
||||
"path": [
|
||||
{ "type": "uint64", "name": "channelID", "required": true, "title": "Channel ID" }
|
||||
],
|
||||
"post": [
|
||||
{ "type": "string", "name": "state", "required": true, "title": "Valid values: delete, undelete, archive, unarchive" }
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "read",
|
||||
"method": "GET",
|
||||
@@ -249,17 +262,6 @@
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "delete",
|
||||
"method": "DELETE",
|
||||
"path": "/{channelID}",
|
||||
"title": "Remove channel",
|
||||
"parameters": {
|
||||
"path": [
|
||||
{ "type": "uint64", "name": "channelID", "required": true, "title": "Channel ID" }
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "members",
|
||||
"method": "GET",
|
||||
|
||||
@@ -63,7 +63,7 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"Name": "edit",
|
||||
"Name": "update",
|
||||
"Method": "PUT",
|
||||
"Title": "Update channel details",
|
||||
"Path": "/{channelID}",
|
||||
@@ -95,12 +95,6 @@
|
||||
"title": "Channel type",
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"name": "archive",
|
||||
"required": false,
|
||||
"title": "Request channel to be archived or unarchived",
|
||||
"type": "bool"
|
||||
},
|
||||
{
|
||||
"name": "organisationID",
|
||||
"required": false,
|
||||
@@ -111,10 +105,10 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"Name": "read",
|
||||
"Method": "GET",
|
||||
"Title": "Read channel details",
|
||||
"Path": "/{channelID}",
|
||||
"Name": "state",
|
||||
"Method": "PUT",
|
||||
"Title": "Update channel state",
|
||||
"Path": "/{channelID}/state",
|
||||
"Parameters": {
|
||||
"path": [
|
||||
{
|
||||
@@ -123,13 +117,21 @@
|
||||
"title": "Channel ID",
|
||||
"type": "uint64"
|
||||
}
|
||||
],
|
||||
"post": [
|
||||
{
|
||||
"name": "state",
|
||||
"required": true,
|
||||
"title": "Valid values: delete, restore, archive, unarchive",
|
||||
"type": "string"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"Name": "delete",
|
||||
"Method": "DELETE",
|
||||
"Title": "Remove channel",
|
||||
"Name": "read",
|
||||
"Method": "GET",
|
||||
"Title": "Read channel details",
|
||||
"Path": "/{channelID}",
|
||||
"Parameters": {
|
||||
"path": [
|
||||
|
||||
@@ -24,6 +24,7 @@ type (
|
||||
ArchiveChannelByID(id uint64) error
|
||||
UnarchiveChannelByID(id uint64) error
|
||||
DeleteChannelByID(id uint64) error
|
||||
UndeleteChannelByID(id uint64) error
|
||||
}
|
||||
|
||||
channel struct {
|
||||
@@ -32,13 +33,22 @@ type (
|
||||
)
|
||||
|
||||
const (
|
||||
sqlChannelValidOnly = ` true
|
||||
AND c.archived_at IS NULL
|
||||
AND c.deleted_at IS NULL`
|
||||
sqlChannelColumns = " id," +
|
||||
"name, " +
|
||||
"meta, " +
|
||||
"created_at, " +
|
||||
"updated_at, " +
|
||||
"archived_at, " +
|
||||
"deleted_at, " +
|
||||
"rel_organisation, " +
|
||||
"rel_creator, " +
|
||||
"type , " +
|
||||
"rel_last_message, " +
|
||||
"topic"
|
||||
|
||||
sqlChannelSelect = `SELECT *
|
||||
sqlChannelSelect = `SELECT ` + sqlChannelColumns + `
|
||||
FROM channels AS c
|
||||
WHERE ` + sqlChannelValidOnly
|
||||
WHERE true `
|
||||
|
||||
sqlChannelGroupByMemberSet = sqlChannelSelect + ` AND c.type = ? AND c.id IN (
|
||||
SELECT rel_channel
|
||||
@@ -49,7 +59,7 @@ const (
|
||||
)`
|
||||
|
||||
// subquery that filters out all channels that current user has access to as a member
|
||||
// or via channel type (public chans)
|
||||
// or via channel type (public channels)
|
||||
sqlChannelAccess = ` (
|
||||
SELECT id
|
||||
FROM channels c
|
||||
@@ -115,7 +125,6 @@ func (r *channel) FindChannels(filter *types.ChannelFilter) ([]*types.Channel, e
|
||||
if filter.CurrentUserID > 0 {
|
||||
sql += " AND c.id IN " + sqlChannelAccess
|
||||
params = append(params, filter.CurrentUserID, types.ChannelTypePublic)
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -162,6 +171,6 @@ func (r *channel) DeleteChannelByID(id uint64) error {
|
||||
return r.updateColumnByID("channels", "deleted_at", time.Now(), id)
|
||||
}
|
||||
|
||||
func (r *channel) RecoverChannelByID(id uint64) error {
|
||||
func (r *channel) UndeleteChannelByID(id uint64) error {
|
||||
return r.updateColumnByID("channels", "deleted_at", nil, id)
|
||||
}
|
||||
|
||||
@@ -27,9 +27,6 @@ type (
|
||||
)
|
||||
|
||||
const (
|
||||
// Copy definitions to make it more obvious that we're reusing channel-scope sql
|
||||
sqlChannelMemberChannelValidOnly = sqlChannelValidOnly
|
||||
|
||||
// Copy definitions to make it more obvious that we're reusing channel-scope sql
|
||||
sqlChannelMemberChannelAccess = sqlChannelAccess
|
||||
|
||||
@@ -37,12 +34,8 @@ const (
|
||||
sqlChannelMemberSelect = `SELECT m.*
|
||||
FROM channel_members AS m
|
||||
INNER JOIN channels AS c ON (m.rel_channel = c.id)
|
||||
WHERE ` + sqlChannelMemberChannelValidOnly
|
||||
|
||||
// Selects all user's memberships
|
||||
sqlChannelMemberships = `SELECT *
|
||||
FROM channel_members AS cm
|
||||
WHERE true`
|
||||
WHERE c.archived_at IS NULL
|
||||
AND c.deleted_at IS NULL`
|
||||
)
|
||||
|
||||
// ChannelMember creates new instance of channel member repository
|
||||
|
||||
@@ -65,5 +65,10 @@ func TestChannel(t *testing.T) {
|
||||
err = rpo.DeleteChannelByID(chn.ID)
|
||||
assert(t, err == nil, "DeleteChannelByID error: %v", err)
|
||||
}
|
||||
|
||||
{
|
||||
err = rpo.UndeleteChannelByID(chn.ID)
|
||||
assert(t, err == nil, "UndeleteChannelByID error: %v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+14
-4
@@ -41,7 +41,7 @@ func (ctrl *Channel) Create(ctx context.Context, r *request.ChannelCreate) (inte
|
||||
return ctrl.wrap(ctrl.svc.ch.With(ctx).Create(channel))
|
||||
}
|
||||
|
||||
func (ctrl *Channel) Edit(ctx context.Context, r *request.ChannelEdit) (interface{}, error) {
|
||||
func (ctrl *Channel) Update(ctx context.Context, r *request.ChannelUpdate) (interface{}, error) {
|
||||
channel := &types.Channel{
|
||||
ID: r.ChannelID,
|
||||
Name: r.Name,
|
||||
@@ -50,11 +50,21 @@ func (ctrl *Channel) Edit(ctx context.Context, r *request.ChannelEdit) (interfac
|
||||
}
|
||||
|
||||
return ctrl.wrap(ctrl.svc.ch.With(ctx).Update(channel))
|
||||
|
||||
}
|
||||
|
||||
func (ctrl *Channel) Delete(ctx context.Context, r *request.ChannelDelete) (interface{}, error) {
|
||||
return nil, ctrl.svc.ch.With(ctx).Delete(r.ChannelID)
|
||||
func (ctrl *Channel) State(ctx context.Context, r *request.ChannelState) (interface{}, error) {
|
||||
switch r.State {
|
||||
case "delete":
|
||||
return ctrl.wrap(ctrl.svc.ch.With(ctx).Delete(r.ChannelID))
|
||||
case "undelete":
|
||||
return ctrl.wrap(ctrl.svc.ch.With(ctx).Undelete(r.ChannelID))
|
||||
case "archive":
|
||||
return ctrl.wrap(ctrl.svc.ch.With(ctx).Archive(r.ChannelID))
|
||||
case "unarchive":
|
||||
return ctrl.wrap(ctrl.svc.ch.With(ctx).Unarchive(r.ChannelID))
|
||||
}
|
||||
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (ctrl *Channel) Read(ctx context.Context, r *request.ChannelRead) (interface{}, error) {
|
||||
|
||||
@@ -29,9 +29,9 @@ import (
|
||||
type ChannelAPI interface {
|
||||
List(context.Context, *request.ChannelList) (interface{}, error)
|
||||
Create(context.Context, *request.ChannelCreate) (interface{}, error)
|
||||
Edit(context.Context, *request.ChannelEdit) (interface{}, error)
|
||||
Update(context.Context, *request.ChannelUpdate) (interface{}, error)
|
||||
State(context.Context, *request.ChannelState) (interface{}, error)
|
||||
Read(context.Context, *request.ChannelRead) (interface{}, error)
|
||||
Delete(context.Context, *request.ChannelDelete) (interface{}, error)
|
||||
Members(context.Context, *request.ChannelMembers) (interface{}, error)
|
||||
Join(context.Context, *request.ChannelJoin) (interface{}, error)
|
||||
Part(context.Context, *request.ChannelPart) (interface{}, error)
|
||||
@@ -43,9 +43,9 @@ type ChannelAPI interface {
|
||||
type Channel struct {
|
||||
List func(http.ResponseWriter, *http.Request)
|
||||
Create func(http.ResponseWriter, *http.Request)
|
||||
Edit func(http.ResponseWriter, *http.Request)
|
||||
Update func(http.ResponseWriter, *http.Request)
|
||||
State func(http.ResponseWriter, *http.Request)
|
||||
Read func(http.ResponseWriter, *http.Request)
|
||||
Delete func(http.ResponseWriter, *http.Request)
|
||||
Members func(http.ResponseWriter, *http.Request)
|
||||
Join func(http.ResponseWriter, *http.Request)
|
||||
Part func(http.ResponseWriter, *http.Request)
|
||||
@@ -69,11 +69,18 @@ func NewChannel(ch ChannelAPI) *Channel {
|
||||
return ch.Create(r.Context(), params)
|
||||
})
|
||||
},
|
||||
Edit: func(w http.ResponseWriter, r *http.Request) {
|
||||
Update: func(w http.ResponseWriter, r *http.Request) {
|
||||
defer r.Body.Close()
|
||||
params := request.NewChannelEdit()
|
||||
params := request.NewChannelUpdate()
|
||||
resputil.JSON(w, params.Fill(r), func() (interface{}, error) {
|
||||
return ch.Edit(r.Context(), params)
|
||||
return ch.Update(r.Context(), params)
|
||||
})
|
||||
},
|
||||
State: func(w http.ResponseWriter, r *http.Request) {
|
||||
defer r.Body.Close()
|
||||
params := request.NewChannelState()
|
||||
resputil.JSON(w, params.Fill(r), func() (interface{}, error) {
|
||||
return ch.State(r.Context(), params)
|
||||
})
|
||||
},
|
||||
Read: func(w http.ResponseWriter, r *http.Request) {
|
||||
@@ -83,13 +90,6 @@ func NewChannel(ch ChannelAPI) *Channel {
|
||||
return ch.Read(r.Context(), params)
|
||||
})
|
||||
},
|
||||
Delete: func(w http.ResponseWriter, r *http.Request) {
|
||||
defer r.Body.Close()
|
||||
params := request.NewChannelDelete()
|
||||
resputil.JSON(w, params.Fill(r), func() (interface{}, error) {
|
||||
return ch.Delete(r.Context(), params)
|
||||
})
|
||||
},
|
||||
Members: func(w http.ResponseWriter, r *http.Request) {
|
||||
defer r.Body.Close()
|
||||
params := request.NewChannelMembers()
|
||||
@@ -134,9 +134,9 @@ func (ch *Channel) MountRoutes(r chi.Router, middlewares ...func(http.Handler) h
|
||||
r.Route("/channels", func(r chi.Router) {
|
||||
r.Get("/", ch.List)
|
||||
r.Post("/", ch.Create)
|
||||
r.Put("/{channelID}", ch.Edit)
|
||||
r.Put("/{channelID}", ch.Update)
|
||||
r.Put("/{channelID}/state", ch.State)
|
||||
r.Get("/{channelID}", ch.Read)
|
||||
r.Delete("/{channelID}", ch.Delete)
|
||||
r.Get("/{channelID}/members", ch.Members)
|
||||
r.Put("/{channelID}/members/{userID}", ch.Join)
|
||||
r.Delete("/{channelID}/members/{userID}", ch.Part)
|
||||
|
||||
+54
-54
@@ -133,21 +133,20 @@ func (c *ChannelCreate) Fill(r *http.Request) (err error) {
|
||||
|
||||
var _ RequestFiller = NewChannelCreate()
|
||||
|
||||
// Channel edit request parameters
|
||||
type ChannelEdit struct {
|
||||
// Channel update request parameters
|
||||
type ChannelUpdate struct {
|
||||
ChannelID uint64 `json:",string"`
|
||||
Name string
|
||||
Topic string
|
||||
Type string
|
||||
Archive bool
|
||||
OrganisationID uint64 `json:",string"`
|
||||
}
|
||||
|
||||
func NewChannelEdit() *ChannelEdit {
|
||||
return &ChannelEdit{}
|
||||
func NewChannelUpdate() *ChannelUpdate {
|
||||
return &ChannelUpdate{}
|
||||
}
|
||||
|
||||
func (c *ChannelEdit) Fill(r *http.Request) (err error) {
|
||||
func (c *ChannelUpdate) Fill(r *http.Request) (err error) {
|
||||
if strings.ToLower(r.Header.Get("content-type")) == "application/json" {
|
||||
err = json.NewDecoder(r.Body).Decode(c)
|
||||
|
||||
@@ -187,10 +186,6 @@ func (c *ChannelEdit) Fill(r *http.Request) (err error) {
|
||||
|
||||
c.Type = val
|
||||
}
|
||||
if val, ok := post["archive"]; ok {
|
||||
|
||||
c.Archive = parseBool(val)
|
||||
}
|
||||
if val, ok := post["organisationID"]; ok {
|
||||
|
||||
c.OrganisationID = parseUInt64(val)
|
||||
@@ -199,7 +194,55 @@ func (c *ChannelEdit) Fill(r *http.Request) (err error) {
|
||||
return err
|
||||
}
|
||||
|
||||
var _ RequestFiller = NewChannelEdit()
|
||||
var _ RequestFiller = NewChannelUpdate()
|
||||
|
||||
// Channel state request parameters
|
||||
type ChannelState struct {
|
||||
ChannelID uint64 `json:",string"`
|
||||
State string
|
||||
}
|
||||
|
||||
func NewChannelState() *ChannelState {
|
||||
return &ChannelState{}
|
||||
}
|
||||
|
||||
func (c *ChannelState) 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["state"]; ok {
|
||||
|
||||
c.State = val
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
var _ RequestFiller = NewChannelState()
|
||||
|
||||
// Channel read request parameters
|
||||
type ChannelRead struct {
|
||||
@@ -244,49 +287,6 @@ func (c *ChannelRead) Fill(r *http.Request) (err error) {
|
||||
|
||||
var _ RequestFiller = NewChannelRead()
|
||||
|
||||
// Channel delete request parameters
|
||||
type ChannelDelete struct {
|
||||
ChannelID uint64 `json:",string"`
|
||||
}
|
||||
|
||||
func NewChannelDelete() *ChannelDelete {
|
||||
return &ChannelDelete{}
|
||||
}
|
||||
|
||||
func (c *ChannelDelete) 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 = NewChannelDelete()
|
||||
|
||||
// Channel members request parameters
|
||||
type ChannelMembers struct {
|
||||
ChannelID uint64 `json:",string"`
|
||||
|
||||
+42
-27
@@ -46,9 +46,10 @@ type (
|
||||
AddMember(channelID uint64, memberIDs ...uint64) (out types.ChannelMemberSet, err error)
|
||||
DeleteMember(channelID uint64, memberIDs ...uint64) (err error)
|
||||
|
||||
Archive(ID uint64) error
|
||||
Unarchive(ID uint64) error
|
||||
Delete(ID uint64) error
|
||||
Archive(ID uint64) (*types.Channel, error)
|
||||
Unarchive(ID uint64) (*types.Channel, error)
|
||||
Delete(ID uint64) (*types.Channel, error)
|
||||
Undelete(ID uint64) (*types.Channel, error)
|
||||
RecordView(userID, channelID, lastMessageID uint64) error
|
||||
}
|
||||
|
||||
@@ -88,12 +89,14 @@ func (svc *channel) With(ctx context.Context) ChannelService {
|
||||
}
|
||||
}
|
||||
|
||||
func (svc *channel) FindByID(id uint64) (ch *types.Channel, err error) {
|
||||
ch, err = svc.channel.FindChannelByID(id)
|
||||
func (svc *channel) FindByID(ID uint64) (ch *types.Channel, err error) {
|
||||
ch, err = svc.channel.FindChannelByID(ID)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// @todo [SECURITY] check if user can access this channel
|
||||
// @todo [SECURITY] check if user can access inactive channels
|
||||
// if !svc.sec.ch.CanRead(ch) {
|
||||
// return nil, errors.New("Not allowed to access channel")
|
||||
// }
|
||||
@@ -111,6 +114,11 @@ func (svc *channel) Find(filter *types.ChannelFilter) (cc types.ChannelSet, err
|
||||
return
|
||||
}
|
||||
|
||||
// @todo [SECURITY] check if user can access inactive channels
|
||||
cc, _ = cc.Filter(func(i *types.Channel) (b bool, e error) {
|
||||
return true || i.DeletedAt == nil, nil
|
||||
})
|
||||
|
||||
return
|
||||
})
|
||||
}
|
||||
@@ -425,19 +433,18 @@ func (svc *channel) Update(in *types.Channel) (ch *types.Channel, err error) {
|
||||
return
|
||||
}
|
||||
|
||||
svc.flushSystemMessages()
|
||||
_ = svc.flushSystemMessages()
|
||||
|
||||
return svc.sendChannelEvent(ch)
|
||||
})
|
||||
}
|
||||
|
||||
func (svc *channel) Delete(id uint64) error {
|
||||
return svc.db.Transaction(func() (err error) {
|
||||
func (svc *channel) Delete(ID uint64) (ch *types.Channel, err error) {
|
||||
return ch, svc.db.Transaction(func() (err error) {
|
||||
var userID = repository.Identity(svc.ctx)
|
||||
var ch *types.Channel
|
||||
|
||||
// @todo [SECURITY] can user access this channel?
|
||||
if ch, err = svc.channel.FindChannelByID(id); err != nil {
|
||||
if ch, err = svc.channel.FindChannelByID(ID); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -452,8 +459,11 @@ func (svc *channel) Delete(id uint64) error {
|
||||
|
||||
svc.scheduleSystemMessage(ch, "<@%d> deleted this channel", userID)
|
||||
|
||||
if err = svc.channel.DeleteChannelByID(id); err != nil {
|
||||
if err = svc.channel.DeleteChannelByID(ID); err != nil {
|
||||
return
|
||||
} else {
|
||||
// Set deletedAt timestamp so that our clients can react properly...
|
||||
ch.DeletedAt = timeNowPtr()
|
||||
}
|
||||
|
||||
_ = svc.sendChannelEvent(ch)
|
||||
@@ -462,13 +472,12 @@ func (svc *channel) Delete(id uint64) error {
|
||||
})
|
||||
}
|
||||
|
||||
func (svc *channel) Recover(id uint64) error {
|
||||
return svc.db.Transaction(func() (err error) {
|
||||
func (svc *channel) Undelete(ID uint64) (ch *types.Channel, err error) {
|
||||
return ch, svc.db.Transaction(func() (err error) {
|
||||
var userID = repository.Identity(svc.ctx)
|
||||
var ch *types.Channel
|
||||
|
||||
// @todo [SECURITY] can user access this channel?
|
||||
if ch, err = svc.channel.FindChannelByID(id); err != nil {
|
||||
if ch, err = svc.channel.FindChannelByID(ID); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -478,10 +487,13 @@ func (svc *channel) Recover(id uint64) error {
|
||||
return errors.New("Channel not deleted")
|
||||
}
|
||||
|
||||
svc.scheduleSystemMessage(ch, "<@%d> recovered this channel", userID)
|
||||
svc.scheduleSystemMessage(ch, "<@%d> undeleted this channel", userID)
|
||||
|
||||
if err = svc.channel.UnarchiveChannelByID(id); err != nil {
|
||||
if err = svc.channel.UndeleteChannelByID(ID); err != nil {
|
||||
return
|
||||
} else {
|
||||
// Remove deletedAt timestamp so that our clients can react properly...
|
||||
ch.DeletedAt = nil
|
||||
}
|
||||
|
||||
svc.flushSystemMessages()
|
||||
@@ -489,14 +501,12 @@ func (svc *channel) Recover(id uint64) error {
|
||||
})
|
||||
}
|
||||
|
||||
func (svc *channel) Archive(id uint64) error {
|
||||
return svc.db.Transaction(func() (err error) {
|
||||
|
||||
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)
|
||||
var ch *types.Channel
|
||||
|
||||
// @todo [SECURITY] can user access this channel?
|
||||
if ch, err = svc.channel.FindChannelByID(id); err != nil {
|
||||
if ch, err = svc.channel.FindChannelByID(ID); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -508,8 +518,11 @@ func (svc *channel) Archive(id uint64) error {
|
||||
|
||||
svc.scheduleSystemMessage(ch, "<@%d> archived this channel", userID)
|
||||
|
||||
if err = svc.channel.ArchiveChannelByID(id); err != nil {
|
||||
if err = svc.channel.ArchiveChannelByID(ID); err != nil {
|
||||
return
|
||||
} else {
|
||||
// Set archivedAt timestamp so that our clients can react properly...
|
||||
ch.ArchivedAt = timeNowPtr()
|
||||
}
|
||||
|
||||
svc.flushSystemMessages()
|
||||
@@ -517,13 +530,12 @@ func (svc *channel) Archive(id uint64) error {
|
||||
})
|
||||
}
|
||||
|
||||
func (svc *channel) Unarchive(id uint64) error {
|
||||
return svc.db.Transaction(func() (err error) {
|
||||
func (svc *channel) Unarchive(ID uint64) (ch *types.Channel, err error) {
|
||||
return ch, svc.db.Transaction(func() (err error) {
|
||||
var userID = repository.Identity(svc.ctx)
|
||||
var ch *types.Channel
|
||||
|
||||
// @todo [SECURITY] can user access this channel?
|
||||
if ch, err = svc.channel.FindChannelByID(id); err != nil {
|
||||
if ch, err = svc.channel.FindChannelByID(ID); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -531,6 +543,9 @@ func (svc *channel) Unarchive(id uint64) error {
|
||||
|
||||
if ch.ArchivedAt == nil {
|
||||
return errors.New("Channel not archived")
|
||||
} else {
|
||||
// Unset archivedAt timestamp so that our clients can react properly...
|
||||
ch.ArchivedAt = nil
|
||||
}
|
||||
|
||||
svc.scheduleSystemMessage(ch, "<@%d> unarchived this channel", userID)
|
||||
|
||||
@@ -3,7 +3,6 @@ package service
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
|
||||
@@ -280,12 +279,11 @@ func (svc *message) Delete(ID uint64) error {
|
||||
return
|
||||
}
|
||||
|
||||
// Set deletedAt timestamp so that our clients can react properly...
|
||||
now := time.Now()
|
||||
deletedMsg.DeletedAt = &now
|
||||
|
||||
if err = svc.cview.Dec(deletedMsg.ChannelID, deletedMsg.UserID); err != nil {
|
||||
return err
|
||||
} else {
|
||||
// Set deletedAt timestamp so that our clients can react properly...
|
||||
deletedMsg.DeletedAt = timeNowPtr()
|
||||
}
|
||||
|
||||
return svc.sendEvent(append(bq, deletedMsg)...)
|
||||
|
||||
@@ -3,6 +3,7 @@ package service
|
||||
import (
|
||||
"log"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/crusttech/crust/internal/store"
|
||||
)
|
||||
@@ -40,3 +41,8 @@ func Init() {
|
||||
DefaultTeam = Team()
|
||||
})
|
||||
}
|
||||
|
||||
func timeNowPtr() *time.Time {
|
||||
now := time.Now()
|
||||
return &now
|
||||
}
|
||||
|
||||
+28
-8
@@ -17,8 +17,9 @@ type (
|
||||
CreatorID uint64 `json:"creatorId" db:"rel_creator"`
|
||||
OrganisationID uint64 `json:"organisationId" db:"rel_organisation"`
|
||||
|
||||
CreatedAt time.Time `json:"createdAt,omitempty" db:"created_at"`
|
||||
UpdatedAt *time.Time `json:"updatedAt,omitempty" db:"updated_at"`
|
||||
CreatedAt time.Time `json:"createdAt,omitempty" db:"created_at"`
|
||||
UpdatedAt *time.Time `json:"updatedAt,omitempty" db:"updated_at"`
|
||||
|
||||
ArchivedAt *time.Time `json:"archivedAt,omitempty" db:"archived_at"`
|
||||
DeletedAt *time.Time `json:"deletedAt,omitempty" db:"deleted_at"`
|
||||
|
||||
@@ -35,7 +36,8 @@ type (
|
||||
// Only return channels accessible by this user
|
||||
CurrentUserID uint64
|
||||
|
||||
IncludeMembers bool
|
||||
// Do not filter out deleted channels
|
||||
IncludeDeleted bool
|
||||
}
|
||||
|
||||
ChannelType string
|
||||
@@ -44,18 +46,22 @@ type (
|
||||
)
|
||||
|
||||
// Scope returns permissions group that for this type
|
||||
func (r *Channel) Scope() string {
|
||||
func (c *Channel) Scope() string {
|
||||
return "channel"
|
||||
}
|
||||
|
||||
// Resource returns a RBAC resource ID for this type
|
||||
func (r *Channel) Resource() string {
|
||||
return fmt.Sprintf("%s:%d", r.Scope(), r.ID)
|
||||
func (c *Channel) Resource() string {
|
||||
return fmt.Sprintf("%s:%d", c.Scope(), c.ID)
|
||||
}
|
||||
|
||||
// Operation returns a RBAC resource-scoped role name for an operation
|
||||
func (r *Channel) Operation(name string) string {
|
||||
return fmt.Sprintf("%s/%s", r.Resource(), name)
|
||||
func (c *Channel) Operation(name string) string {
|
||||
return fmt.Sprintf("%s/%s", c.Resource(), name)
|
||||
}
|
||||
|
||||
func (c *Channel) IsValid() bool {
|
||||
return c.ArchivedAt == nil && c.DeletedAt == nil
|
||||
}
|
||||
|
||||
func (cc ChannelSet) Walk(w func(*Channel) error) (err error) {
|
||||
@@ -68,6 +74,20 @@ func (cc ChannelSet) Walk(w func(*Channel) error) (err error) {
|
||||
return
|
||||
}
|
||||
|
||||
func (cc ChannelSet) Filter(f func(*Channel) (bool, error)) (out ChannelSet, err error) {
|
||||
var ok bool
|
||||
out = ChannelSet{}
|
||||
for i := range cc {
|
||||
if ok, err = f(cc[i]); err != nil {
|
||||
return
|
||||
} else if ok {
|
||||
out = append(out, cc[i])
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (cc ChannelSet) FindById(ID uint64) *Channel {
|
||||
for i := range cc {
|
||||
if cc[i].ID == ID {
|
||||
|
||||
@@ -92,7 +92,7 @@ func (sess *Session) connected() (err error) {
|
||||
}
|
||||
|
||||
// Push user info about all channels he has access to...
|
||||
if cc, err = sess.svc.ch.With(sess.ctx).Find(&types.ChannelFilter{IncludeMembers: true}); err != nil {
|
||||
if cc, err = sess.svc.ch.With(sess.ctx).Find(&types.ChannelFilter{}); err != nil {
|
||||
log.Printf("Error: %v", err)
|
||||
} else {
|
||||
if err = sess.sendReply(payload.Channels(cc)); err != nil {
|
||||
|
||||
@@ -35,8 +35,6 @@ func (s *Session) dispatch(raw []byte) error {
|
||||
return s.channelList(ctx, p.Channels)
|
||||
case p.ChannelCreate != nil:
|
||||
return s.channelCreate(ctx, p.ChannelCreate)
|
||||
case p.ChannelDelete != nil:
|
||||
return s.channelDelete(ctx, p.ChannelDelete)
|
||||
case p.ChannelUpdate != nil:
|
||||
return s.channelUpdate(ctx, p.ChannelUpdate)
|
||||
case p.ChannelViewRecord != nil:
|
||||
|
||||
@@ -45,7 +45,7 @@ func (s *Session) channelPart(ctx context.Context, p *incoming.ChannelPart) erro
|
||||
}
|
||||
|
||||
func (s *Session) channelList(ctx context.Context, p *incoming.Channels) error {
|
||||
channels, err := s.svc.ch.With(ctx).Find(&types.ChannelFilter{IncludeMembers: true})
|
||||
channels, err := s.svc.ch.With(ctx).Find(&types.ChannelFilter{})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -80,10 +80,6 @@ func (s *Session) channelCreate(ctx context.Context, p *incoming.ChannelCreate)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Session) channelDelete(ctx context.Context, p *incoming.ChannelDelete) (err error) {
|
||||
return s.svc.ch.With(ctx).Delete(payload.ParseUInt64(p.ChannelID))
|
||||
}
|
||||
|
||||
func (s *Session) channelUpdate(ctx context.Context, p *incoming.ChannelUpdate) error {
|
||||
ch, err := s.svc.ch.With(ctx).FindByID(payload.ParseUInt64(p.ID))
|
||||
if err != nil {
|
||||
|
||||
Reference in New Issue
Block a user