Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 8 additions & 1 deletion src/Clients/IUserClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -201,7 +201,7 @@ public interface IUserClient
/// To ban a user, use <see cref="BanAsync"/> method.
/// </summary>
/// <remarks>https://getstream.io/chat/docs/dotnet-csharp/moderation/?language=csharp#ban</remarks>
Task<ApiResponse> UnbanAsync(BanRequest banRequest);
Task<ApiResponse> UnbanAsync(BanRequest banRequest, bool removeFutureChannelsBan = false);

/// <summary>
/// <para>Queries banned users.</para>
Expand All @@ -214,6 +214,13 @@ public interface IUserClient
/// <remarks>https://getstream.io/chat/docs/dotnet-csharp/moderation/?language=csharp#query-banned-users</remarks>
Task<QueryBannedUsersResponse> QueryBannedUsersAsync(QueryBannedUsersRequest request);

/// <summary>
/// <para>Queries future channel bans.</para>
/// Future channel bans are automatically applied when a user creates a new channel
/// or adds a member to an existing channel.
/// </summary>
Task<QueryFutureChannelBansResponse> QueryFutureChannelBansAsync(QueryFutureChannelBansRequest request);

/// <summary>
/// <para>Mutes a user.</para>
/// Any user is allowed to mute another user. Mutes are stored at user level and returned with the
Expand Down
30 changes: 22 additions & 8 deletions src/Clients/UserClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -130,22 +130,36 @@ public async Task<ApiResponse> BanAsync(BanRequest banRequest)
HttpStatusCode.Created,
banRequest);

public async Task<ApiResponse> UnbanAsync(BanRequest banRequest)
=> await ExecuteRequestAsync<ApiResponse>("moderation/ban",
public async Task<ApiResponse> UnbanAsync(BanRequest banRequest, bool removeFutureChannelsBan = false)
{
var queryParams = new List<KeyValuePair<string, string>>
{
new KeyValuePair<string, string>("target_user_id", banRequest.TargetUserId),
new KeyValuePair<string, string>("type", banRequest.Type),
new KeyValuePair<string, string>("id", banRequest.Id),
};
if (removeFutureChannelsBan)
{
queryParams.Add(new KeyValuePair<string, string>("remove_future_channels_ban", "true"));
}
return await ExecuteRequestAsync<ApiResponse>("moderation/ban",
HttpMethod.DELETE,
HttpStatusCode.OK,
queryParams: new List<KeyValuePair<string, string>>
{
new KeyValuePair<string, string>("target_user_id", banRequest.TargetUserId),
new KeyValuePair<string, string>("type", banRequest.Type),
new KeyValuePair<string, string>("id", banRequest.Id),
});
queryParams: queryParams);
}

public async Task<QueryBannedUsersResponse> QueryBannedUsersAsync(QueryBannedUsersRequest request)
=> await ExecuteRequestAsync<QueryBannedUsersResponse>("query_banned_users",
HttpMethod.GET,
HttpStatusCode.OK,
queryParams: request.ToQueryParameters());

public async Task<QueryFutureChannelBansResponse> QueryFutureChannelBansAsync(QueryFutureChannelBansRequest request)
=> await ExecuteRequestAsync<QueryFutureChannelBansResponse>("query_future_channel_bans",
HttpMethod.GET,
HttpStatusCode.OK,
queryParams: request.ToQueryParameters());

public async Task<MuteResponse> MuteAsync(string targetId, string id)
=> await ExecuteRequestAsync<MuteResponse>("moderation/mute",
HttpMethod.POST,
Expand Down
34 changes: 34 additions & 0 deletions src/Models/Moderation.cs
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,9 @@ public class BanRequest

/// <summary>Channel ID to ban user in</summary>
public string Id { get; set; }

/// <summary>When true, the user will be automatically banned from all future channels created by the user who issued the ban</summary>
public bool? BanFromFutureChannels { get; set; }
}

public class ShadowBanRequest : BanRequest
Expand All @@ -53,6 +56,37 @@ public class Ban
public User BannedBy { get; set; }
}

public class FutureChannelBan
{
public User User { get; set; }
public DateTimeOffset CreatedAt { get; set; }
public DateTimeOffset? Expires { get; set; }
public string Reason { get; set; }
public bool Shadow { get; set; }
}

public class QueryFutureChannelBansRequest : IQueryParameterConvertible
{
public string UserId { get; set; }
public string TargetUserId { get; set; }
public bool? ExcludeExpiredBans { get; set; }
public int? Limit { get; set; }
public int? Offset { get; set; }

public List<KeyValuePair<string, string>> ToQueryParameters()
{
return new List<KeyValuePair<string, string>>
{
new KeyValuePair<string, string>("payload", StreamJsonConverter.SerializeObject(this)),
};
}
}

public class QueryFutureChannelBansResponse : ApiResponse
{
public List<FutureChannelBan> Bans { get; set; }
}

public class QueryBannedUsersRequest : IQueryParameterConvertible
{
public Dictionary<string, object> FilterConditions { get; set; }
Expand Down
71 changes: 71 additions & 0 deletions tests/UserClientTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -555,7 +555,7 @@
ChannelCID = "channel1",
MessageID = "message1",
},
new DeliveredMessageConfirmation

Check warning on line 558 in tests/UserClientTests.cs

View workflow job for this annotation

GitHub Actions / 🧪 Run tests

Use trailing comma in multi-line initializers
{
ChannelCID = "channel2",
MessageID = "message2",
Expand All @@ -576,7 +576,7 @@
{
LatestDeliveredMessages = new List<DeliveredMessageConfirmation>
{
new DeliveredMessageConfirmation

Check warning on line 579 in tests/UserClientTests.cs

View workflow job for this annotation

GitHub Actions / 🧪 Run tests

Use trailing comma in multi-line initializers
{
ChannelCID = "channel1",
MessageID = "message1",
Expand Down Expand Up @@ -617,9 +617,9 @@
{
var markDeliveredOptions = new MarkDeliveredOptions
{
LatestDeliveredMessages = new List<DeliveredMessageConfirmation>

Check warning on line 620 in tests/UserClientTests.cs

View workflow job for this annotation

GitHub Actions / 🧪 Run tests

Use trailing comma in multi-line initializers
{
new DeliveredMessageConfirmation

Check warning on line 622 in tests/UserClientTests.cs

View workflow job for this annotation

GitHub Actions / 🧪 Run tests

Use trailing comma in multi-line initializers
{
ChannelCID = "channel1",
MessageID = "message1",
Expand All @@ -631,5 +631,76 @@

return markDeliveredCall.Should().ThrowAsync<ArgumentException>();
}

[Test]
public async Task TestQueryFutureChannelBansWithTargetUserIdAsync()
{
var creator = await UpsertNewUserAsync();
var target1 = await UpsertNewUserAsync();
var target2 = await UpsertNewUserAsync();

try
{
// Ban both targets from future channels created by creator
await _userClient.BanAsync(new BanRequest
{
TargetUserId = target1.Id,
UserId = creator.Id,
BanFromFutureChannels = true,
Reason = "test ban 1",
});

await _userClient.BanAsync(new BanRequest
{
TargetUserId = target2.Id,
UserId = creator.Id,
BanFromFutureChannels = true,
Reason = "test ban 2",
});

// Query with TargetUserId filter - should only return the specific target
var resp = await _userClient.QueryFutureChannelBansAsync(new QueryFutureChannelBansRequest
{
UserId = creator.Id,
TargetUserId = target1.Id,
});

resp.Bans.Should().HaveCount(1);
resp.Bans[0].User.Id.Should().Be(target1.Id);

// Query for the other target
resp = await _userClient.QueryFutureChannelBansAsync(new QueryFutureChannelBansRequest
{
UserId = creator.Id,
TargetUserId = target2.Id,
});

resp.Bans.Should().HaveCount(1);
resp.Bans[0].User.Id.Should().Be(target2.Id);

// Query all future channel bans by creator (without target filter)
resp = await _userClient.QueryFutureChannelBansAsync(new QueryFutureChannelBansRequest
{
UserId = creator.Id,
});

resp.Bans.Should().HaveCountGreaterOrEqualTo(2);
}
finally
{
// Cleanup - unban both users
await _userClient.UnbanAsync(new BanRequest
{
TargetUserId = target1.Id,
UserId = creator.Id,
});
await _userClient.UnbanAsync(new BanRequest
{
TargetUserId = target2.Id,
UserId = creator.Id,
});
await TryDeleteUsersAsync(creator.Id, target1.Id, target2.Id);
}
}
}
}
Loading