Skip to content

Commit cb9c110

Browse files
totalimmersionclaudeadityaalifn
authored
[CHA-1700] Add Future Channel Bans support (#368)
* feat: [CHA-1699] add Future Channel Bans support - Add BanWithBanFromFutureChannels option for banning users from future channels - Add UnbanOption type and UnbanWithRemoveFutureChannelsBan option - Modify UnBanUser methods (Client and Channel) to accept options - Add QueryFutureChannelBans method with FutureChannelBan type - Add QueryFutureChannelBansOptions and QueryFutureChannelBansResponse types * feat: add TargetUserID to QueryFutureChannelBansOptions Add target_user_id parameter to allow filtering future channel bans by target user, especially for client-side requests. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * test: add QueryFutureChannelBans test with target_user_id filter Test the new target_user_id parameter for filtering future channel bans. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * feat: add BanWithChannel and UnbanWithCreatedBy options Add missing required parameters for future channel bans: - BanWithChannel: sets channel context, required when using BanWithBanFromFutureChannels at client level - UnbanWithCreatedBy: sets the user who created the ban, required when using UnbanWithRemoveFutureChannelsBan Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com> Co-authored-by: adityaalifn <aditya.nugraha@getstream.io>
1 parent 948e38c commit cb9c110

File tree

2 files changed

+159
-5
lines changed

2 files changed

+159
-5
lines changed

ban.go

Lines changed: 108 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -33,13 +33,24 @@ func (c *Client) BanUser(ctx context.Context, targetID, bannedBy string, options
3333
}
3434

3535
// UnBanUser removes the ban for targetID.
36-
func (c *Client) UnBanUser(ctx context.Context, targetID string) (*Response, error) {
36+
func (c *Client) UnBanUser(ctx context.Context, targetID string, options ...UnbanOption) (*Response, error) {
3737
if targetID == "" {
3838
return nil, errors.New("targetID should not be empty")
3939
}
4040

41+
opts := &unbanOptions{}
42+
for _, fn := range options {
43+
fn(opts)
44+
}
45+
4146
params := url.Values{}
4247
params.Set("target_user_id", targetID)
48+
if opts.RemoveFutureChannelsBan {
49+
params.Set("remove_future_channels_ban", "true")
50+
}
51+
if opts.CreatedBy != "" {
52+
params.Set("created_by", opts.CreatedBy)
53+
}
4354

4455
var resp Response
4556
err := c.makeRequest(ctx, http.MethodDelete, "moderation/ban", params, nil, &resp)
@@ -59,15 +70,23 @@ func (ch *Channel) BanUser(ctx context.Context, targetID, bannedBy string, optio
5970
}
6071

6172
// UnBanUser removes the ban for targetID from the channel ch.
62-
func (ch *Channel) UnBanUser(ctx context.Context, targetID string) (*Response, error) {
73+
func (ch *Channel) UnBanUser(ctx context.Context, targetID string, options ...UnbanOption) (*Response, error) {
6374
if targetID == "" {
6475
return nil, errors.New("targetID should not be empty")
6576
}
6677

78+
opts := &unbanOptions{}
79+
for _, fn := range options {
80+
fn(opts)
81+
}
82+
6783
params := url.Values{}
6884
params.Set("target_user_id", targetID)
6985
params.Set("id", ch.ID)
7086
params.Set("type", ch.Type)
87+
if opts.RemoveFutureChannelsBan {
88+
params.Set("remove_future_channels_ban", "true")
89+
}
7190

7291
var resp Response
7392
err := ch.client.makeRequest(ctx, http.MethodDelete, "moderation/ban", params, nil, &resp)
@@ -130,15 +149,21 @@ type banOptions struct {
130149
Reason string `json:"reason,omitempty"`
131150
Expiration int `json:"timeout,omitempty"`
132151

133-
TargetUserID string `json:"target_user_id"`
134-
BannedBy string `json:"user_id"`
135-
Shadow bool `json:"shadow"`
152+
TargetUserID string `json:"target_user_id"`
153+
BannedBy string `json:"user_id"`
154+
Shadow bool `json:"shadow"`
155+
BanFromFutureChannels bool `json:"ban_from_future_channels,omitempty"`
136156

137157
// ID and Type of the channel when acting on a channel member.
138158
ID string `json:"id"`
139159
Type string `json:"type"`
140160
}
141161

162+
type unbanOptions struct {
163+
RemoveFutureChannelsBan bool `json:"remove_future_channels_ban,omitempty"`
164+
CreatedBy string `json:"created_by,omitempty"`
165+
}
166+
142167
type BanOption func(*banOptions)
143168

144169
func BanWithReason(reason string) func(*banOptions) {
@@ -167,3 +192,81 @@ func banFromChannel(_type, id string) func(*banOptions) {
167192
opt.ID = id
168193
}
169194
}
195+
196+
// BanWithBanFromFutureChannels when set to true, the user will be automatically
197+
// banned from all future channels created by the user who issued the ban.
198+
// Note: This option requires BanWithChannel to also be set.
199+
func BanWithBanFromFutureChannels() func(*banOptions) {
200+
return func(opt *banOptions) {
201+
opt.BanFromFutureChannels = true
202+
}
203+
}
204+
205+
// BanWithChannel sets the channel context for the ban operation.
206+
// This is required when using BanWithBanFromFutureChannels at the client level.
207+
func BanWithChannel(channelType, channelID string) func(*banOptions) {
208+
return func(opt *banOptions) {
209+
opt.Type = channelType
210+
opt.ID = channelID
211+
}
212+
}
213+
214+
type UnbanOption func(*unbanOptions)
215+
216+
// UnbanWithRemoveFutureChannelsBan when set to true, also removes the future
217+
// channel ban, so the user will no longer be auto-banned in new channels.
218+
// Note: This option requires UnbanWithCreatedBy to also be set.
219+
func UnbanWithRemoveFutureChannelsBan() func(*unbanOptions) {
220+
return func(opt *unbanOptions) {
221+
opt.RemoveFutureChannelsBan = true
222+
}
223+
}
224+
225+
// UnbanWithCreatedBy sets the user ID who originally created the ban.
226+
// This is required when using UnbanWithRemoveFutureChannelsBan.
227+
func UnbanWithCreatedBy(userID string) func(*unbanOptions) {
228+
return func(opt *unbanOptions) {
229+
opt.CreatedBy = userID
230+
}
231+
}
232+
233+
// FutureChannelBan represents a future channel ban entry.
234+
type FutureChannelBan struct {
235+
User *User `json:"user,omitempty"`
236+
Expires *time.Time `json:"expires,omitempty"`
237+
Reason string `json:"reason,omitempty"`
238+
Shadow bool `json:"shadow,omitempty"`
239+
CreatedAt time.Time `json:"created_at"`
240+
}
241+
242+
// QueryFutureChannelBansOptions contains options for querying future channel bans.
243+
type QueryFutureChannelBansOptions struct {
244+
UserID string `json:"user_id,omitempty"`
245+
TargetUserID string `json:"target_user_id,omitempty"`
246+
ExcludeExpiredBans bool `json:"exclude_expired_bans,omitempty"`
247+
Limit int `json:"limit,omitempty"`
248+
Offset int `json:"offset,omitempty"`
249+
}
250+
251+
// QueryFutureChannelBansResponse is the response from QueryFutureChannelBans.
252+
type QueryFutureChannelBansResponse struct {
253+
Bans []*FutureChannelBan `json:"bans"`
254+
Response
255+
}
256+
257+
// QueryFutureChannelBans queries future channel bans.
258+
// Future channel bans are automatically applied when a user creates a new channel
259+
// or adds a member to an existing channel.
260+
func (c *Client) QueryFutureChannelBans(ctx context.Context, opts *QueryFutureChannelBansOptions) (*QueryFutureChannelBansResponse, error) {
261+
data, err := json.Marshal(opts)
262+
if err != nil {
263+
return nil, err
264+
}
265+
266+
values := url.Values{}
267+
values.Set("payload", string(data))
268+
269+
var resp QueryFutureChannelBansResponse
270+
err = c.makeRequest(ctx, http.MethodGet, "query_future_channel_bans", values, nil, &resp)
271+
return &resp, err
272+
}

ban_test.go

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,57 @@ func TestChannelBanUnban(t *testing.T) {
131131
require.Empty(t, resp.Bans)
132132
}
133133

134+
func TestQueryFutureChannelBans(t *testing.T) {
135+
c := initClient(t)
136+
creator := randomUser(t, c)
137+
target1 := randomUser(t, c)
138+
target2 := randomUser(t, c)
139+
ctx := context.Background()
140+
141+
// Create a channel to use for future channel bans
142+
ch := initChannel(t, c, creator.ID)
143+
144+
// Ban both targets from future channels created by creator
145+
_, err := c.BanUser(ctx, target1.ID, creator.ID, BanWithBanFromFutureChannels(), BanWithChannel(ch.Type, ch.ID), BanWithReason("test ban 1"))
146+
require.NoError(t, err)
147+
148+
_, err = c.BanUser(ctx, target2.ID, creator.ID, BanWithBanFromFutureChannels(), BanWithChannel(ch.Type, ch.ID), BanWithReason("test ban 2"))
149+
require.NoError(t, err)
150+
151+
// Query all future channel bans by creator
152+
resp, err := c.QueryFutureChannelBans(ctx, &QueryFutureChannelBansOptions{
153+
UserID: creator.ID,
154+
})
155+
require.NoError(t, err)
156+
require.GreaterOrEqual(t, len(resp.Bans), 2)
157+
158+
// Query with target_user_id filter - should only return the specific target
159+
// Note: When filtering by target_user_id, the API doesn't return the user object
160+
// since it's already known from the filter
161+
resp, err = c.QueryFutureChannelBans(ctx, &QueryFutureChannelBansOptions{
162+
UserID: creator.ID,
163+
TargetUserID: target1.ID,
164+
})
165+
require.NoError(t, err)
166+
require.Len(t, resp.Bans, 1)
167+
require.Equal(t, "test ban 1", resp.Bans[0].Reason)
168+
169+
// Query for the other target
170+
resp, err = c.QueryFutureChannelBans(ctx, &QueryFutureChannelBansOptions{
171+
UserID: creator.ID,
172+
TargetUserID: target2.ID,
173+
})
174+
require.NoError(t, err)
175+
require.Len(t, resp.Bans, 1)
176+
require.Equal(t, "test ban 2", resp.Bans[0].Reason)
177+
178+
// Cleanup - unban both users with RemoveFutureChannelsBan
179+
_, err = c.UnBanUser(ctx, target1.ID, UnbanWithRemoveFutureChannelsBan(), UnbanWithCreatedBy(creator.ID))
180+
require.NoError(t, err)
181+
_, err = c.UnBanUser(ctx, target2.ID, UnbanWithRemoveFutureChannelsBan(), UnbanWithCreatedBy(creator.ID))
182+
require.NoError(t, err)
183+
}
184+
134185
func ExampleClient_BanUser() {
135186
client, _ := NewClient("XXXX", "XXXX")
136187
ctx := context.Background()

0 commit comments

Comments
 (0)