From 91e2250e494367acb6a9ef0d80c6f810e42b6955 Mon Sep 17 00:00:00 2001 From: Yun Wang Date: Sat, 1 Aug 2026 12:42:11 +0200 Subject: [PATCH] docs: cover the moderation tail in the stream-chat-go migration guide Adds the moderation operations the guide did not document yet: user blocking, blocklists, flagging, and reviewing flagged content. Two things worth calling out for anyone migrating: - Blocklist types are spelled BlockList in the new SDK, so every method renames (CreateBlocklist to CreateBlockList, ListBlocklists to ListBlockLists, and so on). - The legacy flag methods write to the v1 chat flags store while this SDK exposes v2 moderation, so flagging and querying can end up on opposite sides of that boundary. The guide now warns about it, and documents the v1 flag-report workflow as a redesign onto the v2 review queue rather than a rename. All before and after snippets were compiled against stream-chat-go v8 and getstream-go v5 respectively. --- .../05-moderation.md | 163 ++++++++++++++++++ docs/migration-from-stream-chat-go/README.md | 2 +- 2 files changed, 164 insertions(+), 1 deletion(-) diff --git a/docs/migration-from-stream-chat-go/05-moderation.md b/docs/migration-from-stream-chat-go/05-moderation.md index 82240df..f2ea11d 100644 --- a/docs/migration-from-stream-chat-go/05-moderation.md +++ b/docs/migration-from-stream-chat-go/05-moderation.md @@ -430,6 +430,156 @@ func main() { - `UnmuteUser` becomes `Unmute` with `UnmuteRequest` - `TargetIds` is a slice, allowing batch unmuting +## Block a User + +User blocking is a per-user block list, separate from banning. + +**Before (stream-chat-go):** + +```go +resp, err := client.BlockUser(ctx, "target-user", "acting-user") + +resp, err = client.UnblockUser(ctx, "target-user", "acting-user") + +blocked, err := client.GetBlockedUser(ctx, "acting-user") +``` + +**After (getstream-go):** + +```go +resp, err := client.BlockUsers(ctx, &getstream.BlockUsersRequest{ + BlockedUserID: "target-user", + UserID: getstream.PtrTo("acting-user"), +}) + +resp, err = client.UnblockUsers(ctx, &getstream.UnblockUsersRequest{ + BlockedUserID: "target-user", + UserID: getstream.PtrTo("acting-user"), +}) + +blocked, err := client.GetBlockedUsers(ctx, &getstream.GetBlockedUsersRequest{ + UserID: getstream.PtrTo("acting-user"), +}) +``` + +**Key changes:** +- Method names are plural: `BlockUser` becomes `BlockUsers`, `GetBlockedUser` becomes `GetBlockedUsers` +- Positional arguments become fields on a request struct +- Do not confuse these with `client.Video().UnblockUser(...)`, which removes a user from a call + +## Blocklists + +**Before (stream-chat-go):** + +```go +err := client.CreateBlocklist(ctx, &stream.BlocklistCreateRequest{ + BlocklistBase: stream.BlocklistBase{ + Name: "profanity", + Words: []string{"badword"}, + }, +}) + +list, err := client.GetBlocklist(ctx, "profanity") +_, err = client.UpdateBlocklist(ctx, "profanity", []string{"badword", "worse"}) +all, err := client.ListBlocklists(ctx) +_, err = client.DeleteBlocklist(ctx, "profanity") +``` + +**After (getstream-go):** + +```go +resp, err := client.CreateBlockList(ctx, &getstream.CreateBlockListRequest{ + Name: "profanity", + Words: []string{"badword"}, +}) + +list, err := client.GetBlockList(ctx, "profanity", &getstream.GetBlockListRequest{}) +_, err = client.UpdateBlockList(ctx, "profanity", &getstream.UpdateBlockListRequest{ + Words: []string{"badword", "worse"}, +}) +all, err := client.ListBlockLists(ctx, &getstream.ListBlockListsRequest{}) +_, err = client.DeleteBlockList(ctx, "profanity", &getstream.DeleteBlockListRequest{}) +``` + +**Key changes:** +- The type is spelled `BlockList` (capital L), not `Blocklist`. Every method renames accordingly: `CreateBlocklist` to `CreateBlockList`, `ListBlocklists` to `ListBlockLists`, and so on +- Every method takes a request struct, even where the legacy call took only a name +- `UpdateBlockList` carries `Words` on the request instead of a positional slice +- The new request supports matching options the legacy SDK did not expose: `Type` (`word`, `regex`, `domain`, `email`, and the allowlist variants), `IsSubstringMatchingEnabled`, `IsLeetCheckEnabled`, `IsPluralCheckEnabled`, `IsConfusableFoldingEnabled` + +## Flagging Content + +> **Read this before migrating flags.** The legacy flag methods write to the **v1 chat flags** store. `getstream-go` exposes the **v2 moderation** API, and the two stores are not the same: content flagged through `Moderation().Flag()` may not appear in `Chat().QueryMessageFlags()`, which still reads v1. Swapping the call alone can therefore leave a flagging workflow that writes to one store and reads from another. Migrate the whole workflow (flag, query, review) to v2 together, or keep using v1 until you can. + +**Before (stream-chat-go):** + +```go +_, err := client.FlagMessage(ctx, "message-id", "acting-user") +_, err = client.FlagUser(ctx, "target-user", "acting-user") + +flags, err := client.QueryMessageFlags(ctx, &stream.QueryOption{ + Filter: map[string]interface{}{"channel_cid": "messaging:general"}, +}) +``` + +**After (getstream-go):** + +```go +_, err := client.Moderation().Flag(ctx, &getstream.FlagRequest{ + EntityType: "message", + EntityID: "message-id", + UserID: getstream.PtrTo("acting-user"), +}) + +_, err = client.Moderation().Flag(ctx, &getstream.FlagRequest{ + EntityType: "user", + EntityID: "target-user", + UserID: getstream.PtrTo("acting-user"), +}) + +flags, err := client.Chat().QueryMessageFlags(ctx, &getstream.QueryMessageFlagsRequest{ + Payload: &getstream.QueryMessageFlagsPayload{ + FilterConditions: map[string]any{"channel_cid": "messaging:general"}, + }, +}) +``` + +**Key changes:** +- `FlagMessage` and `FlagUser` collapse into one `Moderation().Flag()` call; the target is described by `EntityType` plus `EntityID` instead of a dedicated method +- `QueryMessageFlags` stays on the Chat sub-client and takes its filter under `Payload` +- Flag reads and writes can cross the v1/v2 boundary described above; verify your flags are visible where you expect before relying on them + +## Reviewing Flagged Content + +The v1 flag-report workflow has no drop-in replacement. `QueryFlagReports` and `ReviewFlagReport` are replaced by the v2 **review queue**, which is a different model rather than a rename, so this needs rework instead of a call swap. + +**Before (stream-chat-go):** + +```go +reports, err := client.QueryFlagReports(ctx, &stream.QueryFlagReportsRequest{}) +_, err = client.ReviewFlagReport(ctx, "report-id", &stream.ReviewFlagReportRequest{ + ReviewResult: "reviewed", +}) +``` + +**After (getstream-go):** + +```go +queue, err := client.Moderation().QueryReviewQueue(ctx, &getstream.QueryReviewQueueRequest{}) + +item, err := client.Moderation().GetReviewQueueItem(ctx, "item-id", &getstream.GetReviewQueueItemRequest{}) + +_, err = client.Moderation().SubmitAction(ctx, &getstream.SubmitActionRequest{ + ItemID: getstream.PtrTo("item-id"), + ActionType: "mark_reviewed", +}) +``` + +**Key changes:** +- Flag reports become review-queue items: query with `QueryReviewQueue`, read one with `GetReviewQueueItem` +- Reviewing is an action submitted against an item via `SubmitAction` rather than a single review call +- Because the underlying model changed, treat this as a redesign of the moderation workflow and confirm the behavior you need against the moderation documentation + ## Method Mapping Summary | Legacy (stream-chat-go) | New (getstream-go) | @@ -443,3 +593,16 @@ func main() { | `client.MuteUser(ctx, target, by, opts...)` | `client.Moderation().Mute(ctx, &MuteRequest{...})` | | `client.UnmuteUser(ctx, target, by)` | `client.Moderation().Unmute(ctx, &UnmuteRequest{...})` | | `client.QueryBannedUsers(ctx, opts)` | `client.Chat().QueryBannedUsers(ctx, &QueryBannedUsersRequest{...})` | +| `client.BlockUser(ctx, target, by)` | `client.BlockUsers(ctx, &BlockUsersRequest{...})` | +| `client.UnblockUser(ctx, target, by)` | `client.UnblockUsers(ctx, &UnblockUsersRequest{...})` | +| `client.GetBlockedUser(ctx, by)` | `client.GetBlockedUsers(ctx, &GetBlockedUsersRequest{...})` | +| `client.CreateBlocklist(ctx, req)` | `client.CreateBlockList(ctx, &CreateBlockListRequest{...})` | +| `client.GetBlocklist(ctx, name)` | `client.GetBlockList(ctx, name, &GetBlockListRequest{})` | +| `client.UpdateBlocklist(ctx, name, words)` | `client.UpdateBlockList(ctx, name, &UpdateBlockListRequest{Words: words})` | +| `client.ListBlocklists(ctx)` | `client.ListBlockLists(ctx, &ListBlockListsRequest{})` | +| `client.DeleteBlocklist(ctx, name)` | `client.DeleteBlockList(ctx, name, &DeleteBlockListRequest{})` | +| `client.FlagMessage(ctx, msgID, by)` | `client.Moderation().Flag(ctx, &FlagRequest{EntityType: "message", ...})` (v1 to v2, see note) | +| `client.FlagUser(ctx, target, by)` | `client.Moderation().Flag(ctx, &FlagRequest{EntityType: "user", ...})` (v1 to v2, see note) | +| `client.QueryMessageFlags(ctx, q)` | `client.Chat().QueryMessageFlags(ctx, &QueryMessageFlagsRequest{...})` | +| `client.QueryFlagReports(ctx, req)` | `client.Moderation().QueryReviewQueue(ctx, ...)` (different model) | +| `client.ReviewFlagReport(ctx, id, req)` | `client.Moderation().SubmitAction(ctx, ...)` (different model) | diff --git a/docs/migration-from-stream-chat-go/README.md b/docs/migration-from-stream-chat-go/README.md index 5d1ac6c..4dcaa09 100644 --- a/docs/migration-from-stream-chat-go/README.md +++ b/docs/migration-from-stream-chat-go/README.md @@ -77,7 +77,7 @@ func main() { | 2 | [Users](02-users.md) | Upsert, query, update, delete | | 3 | [Channels](03-channels.md) | Create, query, members, update | | 4 | [Messages and Reactions](04-messages-and-reactions.md) | Send, reply, react | -| 5 | [Moderation](05-moderation.md) | Ban, mute, moderators | +| 5 | [Moderation](05-moderation.md) | Ban, mute, moderators, user blocking, blocklists, flags, review queue | | 6 | [Devices](06-devices.md) | Push device management | ## Notes