Skip to content

Commit

Permalink
[CHA-14] Added support for pinning, archiving channels and partial me…
Browse files Browse the repository at this point in the history
…mber update (#293)

* feat: added support for pinning / unpinning channels

* feat: added support for archiving / unarchiving channels

* feat: added support for MemberPartialUpdate

* feat: added Go 1.23

* fix: update channel_test.go

Co-authored-by: Antti Kupila <[email protected]>

* fix: update channel_test.go

Co-authored-by: Antti Kupila <[email protected]>

---------

Co-authored-by: Antti Kupila <[email protected]>
  • Loading branch information
totalimmersion and akupila authored Dec 4, 2024
1 parent de807e7 commit b2fd181
Show file tree
Hide file tree
Showing 3 changed files with 270 additions and 5 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ jobs:
max-parallel: 3
fail-fast: false
matrix:
goVer: ['1.18', '1.19', '1.20', '1.21', '1.22']
goVer: ['1.18', '1.19', '1.20', '1.21', '1.22', '1.23']
steps:
- uses: actions/checkout@v4

Expand Down
135 changes: 131 additions & 4 deletions channel.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,15 +23,48 @@ type ChannelMember struct {
User *User `json:"user,omitempty"`
IsModerator bool `json:"is_moderator,omitempty"`

Invited bool `json:"invited,omitempty"`
InviteAcceptedAt *time.Time `json:"invite_accepted_at,omitempty"`
InviteRejectedAt *time.Time `json:"invite_rejected_at,omitempty"`
Role string `json:"role,omitempty"`
Invited bool `json:"invited,omitempty"`
InviteAcceptedAt *time.Time `json:"invite_accepted_at,omitempty"`
InviteRejectedAt *time.Time `json:"invite_rejected_at,omitempty"`
Status string `json:"status,omitempty"`
Role string `json:"role,omitempty"`
ChannelRole string `json:"channel_role"`
Banned bool `json:"banned"`
BanExpires *time.Time `json:"ban_expires,omitempty"`
ShadowBanned bool `json:"shadow_banned"`
ArchivedAt *time.Time `json:"archived_at,omitempty"`
PinnedAt *time.Time `json:"pinned_at,omitempty"`
NotificationsMuted bool `json:"notifications_muted"`

ExtraData map[string]interface{} `json:"-"`

CreatedAt time.Time `json:"created_at,omitempty"`
UpdatedAt time.Time `json:"updated_at,omitempty"`
}

type channelMemberForJSON ChannelMember

// UnmarshalJSON implements json.Unmarshaler.
func (m *ChannelMember) UnmarshalJSON(data []byte) error {
var m2 channelMemberForJSON
if err := json.Unmarshal(data, &m2); err != nil {
return err
}
*m = ChannelMember(m2)

if err := json.Unmarshal(data, &m.ExtraData); err != nil {
return err
}

removeFromMap(m.ExtraData, *m)
return nil
}

// MarshalJSON implements json.Marshaler.
func (m ChannelMember) MarshalJSON() ([]byte, error) {
return addToMapAndMarshal(m.ExtraData, channelMemberForJSON(m))
}

type Channel struct {
ID string `json:"id"`
Type string `json:"type"`
Expand Down Expand Up @@ -855,3 +888,97 @@ func (ch *Channel) Unmute(ctx context.Context, userID string) (*Response, error)
err := ch.client.makeRequest(ctx, http.MethodPost, "moderation/unmute/channel", nil, data, &resp)
return &resp, err
}

type ChannelMemberResponse struct {
ChannelMember ChannelMember `json:"channel_member"`
Response
}

// Pin pins the channel for the user.
func (ch *Channel) Pin(ctx context.Context, userID string) (*ChannelMemberResponse, error) {
if userID == "" {
return nil, errors.New("user ID must be not empty")
}

p := path.Join("channels", url.PathEscape(ch.Type), url.PathEscape(ch.ID), "member", url.PathEscape(userID))

data := map[string]interface{}{
"set": map[string]interface{}{
"pinned": true,
},
}

resp := &ChannelMemberResponse{}
err := ch.client.makeRequest(ctx, http.MethodPatch, p, nil, data, resp)
return resp, err
}

// Unpin unpins the channel for the user.
func (ch *Channel) Unpin(ctx context.Context, userID string) (*ChannelMemberResponse, error) {
if userID == "" {
return nil, errors.New("user ID must be not empty")
}

p := path.Join("channels", url.PathEscape(ch.Type), url.PathEscape(ch.ID), "member", url.PathEscape(userID))

data := map[string]interface{}{
"set": map[string]interface{}{
"pinned": false,
},
}

resp := &ChannelMemberResponse{}
err := ch.client.makeRequest(ctx, http.MethodPatch, p, nil, data, resp)
return resp, err
}

// Archive archives the channel for the user.
func (ch *Channel) Archive(ctx context.Context, userID string) (*ChannelMemberResponse, error) {
if userID == "" {
return nil, errors.New("user ID must be not empty")
}

p := path.Join("channels", url.PathEscape(ch.Type), url.PathEscape(ch.ID), "member", url.PathEscape(userID))

data := map[string]interface{}{
"set": map[string]interface{}{
"archived": true,
},
}

resp := &ChannelMemberResponse{}
err := ch.client.makeRequest(ctx, http.MethodPatch, p, nil, data, resp)
return resp, err
}

// Unarchive unarchives the channel for the user.
func (ch *Channel) Unarchive(ctx context.Context, userID string) (*ChannelMemberResponse, error) {
if userID == "" {
return nil, errors.New("user ID must be not empty")
}

p := path.Join("channels", url.PathEscape(ch.Type), url.PathEscape(ch.ID), "member", url.PathEscape(userID))

data := map[string]interface{}{
"set": map[string]interface{}{
"archived": false,
},
}

resp := &ChannelMemberResponse{}
err := ch.client.makeRequest(ctx, http.MethodPatch, p, nil, data, resp)
return resp, err
}

// PartialUpdateMember set and unset specific fields when it is necessary to retain additional custom data fields on the object. AKA a patch style update.
func (ch *Channel) PartialUpdateMember(ctx context.Context, userID string, update PartialUpdate) (*ChannelMemberResponse, error) {
if userID == "" {
return nil, errors.New("user ID must be not empty")
}

p := path.Join("channels", url.PathEscape(ch.Type), url.PathEscape(ch.ID), "member", url.PathEscape(userID))

resp := &ChannelMemberResponse{}
err := ch.client.makeRequest(ctx, http.MethodPatch, p, nil, update, resp)
return resp, err
}
138 changes: 138 additions & 0 deletions channel_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"os"
"path"
"testing"
"time"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
Expand Down Expand Up @@ -492,6 +493,41 @@ func TestChannel_PartialUpdate(t *testing.T) {
require.Nil(t, ch.ExtraData["age"])
}

func TestChannel_MemberPartialUpdate(t *testing.T) {
c := initClient(t)
users := randomUsers(t, c, 5)
ctx := context.Background()

members := make([]string, 0, len(users))
for i := range users {
members = append(members, users[i].ID)
}

req := &ChannelRequest{Members: members}
resp, err := c.CreateChannel(ctx, "team", randomString(12), randomUser(t, c).ID, req)
require.NoError(t, err)

ch := resp.Channel
member, err := ch.PartialUpdateMember(ctx, members[0], PartialUpdate{
Set: map[string]interface{}{
"color": "red",
},
Unset: []string{"age"},
})
require.NoError(t, err)
require.Equal(t, "red", member.ChannelMember.ExtraData["color"])

member, err = ch.PartialUpdateMember(ctx, members[0], PartialUpdate{
Set: map[string]interface{}{
"age": "18",
},
Unset: []string{"color"},
})
require.NoError(t, err)
require.Equal(t, "18", member.ChannelMember.ExtraData["age"])
require.Nil(t, member.ChannelMember.ExtraData["color"])
}

func TestChannel_SendFile(t *testing.T) {
c := initClient(t)
ch := initChannel(t, c)
Expand Down Expand Up @@ -647,6 +683,108 @@ func TestChannel_Mute_Unmute(t *testing.T) {
require.Len(t, queryChannResp.Channels, 1)
}

func TestChannel_Pin(t *testing.T) {
c := initClient(t)
ctx := context.Background()
users := randomUsers(t, c, 5)

members := make([]string, 0, len(users))
for i := range users {
members = append(members, users[i].ID)
}
ch := initChannel(t, c, members...)

//pin the channel
now := time.Now()
member, err := ch.Pin(ctx, users[0].ID)
require.NoError(t, err, "pin channel")
require.NotNil(t, member.ChannelMember.PinnedAt)
require.GreaterOrEqual(t, member.ChannelMember.PinnedAt.Unix(), now.Unix())

// query for pinned the channel
queryChannResp, err := c.QueryChannels(ctx, &QueryOption{
UserID: users[0].ID,
Filter: map[string]interface{}{
"pinned": true,
"cid": ch.CID,
},
})

channels := queryChannResp.Channels
require.NoError(t, err, "query pinned channel")
require.Len(t, channels, 1)
require.Equal(t, channels[0].CID, ch.CID)

member, err = ch.Unpin(ctx, users[0].ID)
require.NoError(t, err, "unpin channel")
require.Nil(t, member.ChannelMember.PinnedAt)

// query for pinned the channel
queryChannResp, err = c.QueryChannels(ctx, &QueryOption{
UserID: users[0].ID,
Filter: map[string]interface{}{
"pinned": false,
"cid": ch.CID,
},
})

channels = queryChannResp.Channels
require.NoError(t, err, "query pinned channel")
require.Len(t, channels, 1)
require.Equal(t, channels[0].CID, ch.CID)
}

func TestChannel_Archive(t *testing.T) {
c := initClient(t)
ctx := context.Background()
users := randomUsers(t, c, 5)

members := make([]string, 0, len(users))
for i := range users {
members = append(members, users[i].ID)
}
ch := initChannel(t, c, members...)

//archive the channel
now := time.Now()
member, err := ch.Archive(ctx, users[0].ID)
require.NoError(t, err, "archive channel")
require.NotNil(t, member.ChannelMember.ArchivedAt)
require.GreaterOrEqual(t, member.ChannelMember.ArchivedAt.Unix(), now.Unix())

// query for pinned the channel
queryChannResp, err := c.QueryChannels(ctx, &QueryOption{
UserID: users[0].ID,
Filter: map[string]interface{}{
"archived": true,
"cid": ch.CID,
},
})

channels := queryChannResp.Channels
require.NoError(t, err, "query archived channel")
require.Len(t, channels, 1)
require.Equal(t, channels[0].CID, ch.CID)

member, err = ch.Unarchive(ctx, users[0].ID)
require.NoError(t, err, "unarchive channel")
require.Nil(t, member.ChannelMember.ArchivedAt)

// query for the archived channel
queryChannResp, err = c.QueryChannels(ctx, &QueryOption{
UserID: users[0].ID,
Filter: map[string]interface{}{
"archived": false,
"cid": ch.CID,
},
})

channels = queryChannResp.Channels
require.NoError(t, err, "query archived channel")
require.Len(t, channels, 1)
require.Equal(t, channels[0].CID, ch.CID)
}

func ExampleChannel_Update() {
client := &Client{}
ctx := context.Background()
Expand Down

0 comments on commit b2fd181

Please sign in to comment.