Something went wrong. Try again.
This repository has no description
Something went wrong. Try again.
23 kB · 549 lines
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550package spxrpc
import ( "context" "fmt" "net/http" "time"
"github.com/bluesky-social/indigo/atproto/syntax" "github.com/bluesky-social/indigo/xrpc" "github.com/labstack/echo/v4" glex "github.com/streamplace/glex/runtime" "stream.place/streamplace/pkg/appbsky" "stream.place/streamplace/pkg/comatproto" "stream.place/streamplace/pkg/constants" "stream.place/streamplace/pkg/log" "stream.place/streamplace/pkg/placestream" "stream.place/streamplace/pkg/statedb")
// handlePlaceStreamModerationCreateBlock creates a block (ban) on behalf of a streamerfunc (s *Server) handlePlaceStreamModerationCreateBlock(ctx context.Context, input *placestream.ModerationCreateBlock_Input) (*placestream.ModerationCreateBlock_Output, error) { // Validate input if err := validateDID(input.Streamer); err != nil { return nil, echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("invalid streamer DID: %v", err)) } if err := validateDID(input.Subject); err != nil { return nil, echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("invalid subject DID: %v", err)) }
// Get delegated moderation context (validates OAuth, permission, and returns client) modCtx, err := s.GetDelegatedModerationContext(ctx, input.Streamer, "createBlock") if err != nil { return nil, err }
// Create block record in streamer's repo block := appbsky.GraphBlock{ Subject: input.Subject, CreatedAt: time.Now().UTC().Format(time.RFC3339), }
createInput := comatproto.RepoCreateRecord_Input{ Collection: constants.APP_BSKY_GRAPH_BLOCK, Record: &glex.LexiconTypeDecoder{Val: &block}, Repo: input.Streamer, } createOutput := comatproto.RepoCreateRecord_Output{}
err = modCtx.StreamerClient.Do(ctx, xrpc.Procedure, "application/json", "com.atproto.repo.createRecord", map[string]any{}, createInput, &createOutput) if err != nil { log.Error(ctx, "failed to create block record", "err", err) if auditErr := s.logAudit(ctx, input.Streamer, modCtx.ModeratorDID, "createBlock", "", input.Subject, "", false, err.Error()); auditErr != nil { log.Error(ctx, "failed to create audit log", "error", auditErr) } return nil, echo.NewHTTPError(http.StatusInternalServerError, fmt.Sprintf("failed to create block: %v", err)) }
// Log successful audit entry if err := s.logAudit(ctx, input.Streamer, modCtx.ModeratorDID, "createBlock", "", input.Subject, createOutput.Uri, true, ""); err != nil { log.Error(ctx, "failed to create audit log", "error", err) }
return &placestream.ModerationCreateBlock_Output{ Uri: createOutput.Uri, Cid: createOutput.Cid, }, nil}
// handlePlaceStreamModerationDeleteBlock deletes a block (unban) on behalf of a streamerfunc (s *Server) handlePlaceStreamModerationDeleteBlock(ctx context.Context, input *placestream.ModerationDeleteBlock_Input) (*placestream.ModerationDeleteBlock_Output, error) { // Validate input if err := validateDID(input.Streamer); err != nil { return nil, echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("invalid streamer DID: %v", err)) } if err := validateATURI(input.BlockUri); err != nil { return nil, echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("invalid block URI: %v", err)) }
// Get delegated moderation context (validates OAuth, permission, and returns client) modCtx, err := s.GetDelegatedModerationContext(ctx, input.Streamer, "deleteBlock") if err != nil { return nil, err }
// Parse blockUri to extract rkey // AT-URI format: at://did:plc:xxx/collection/rkey rkey, err := extractRKey(input.BlockUri) if err != nil { log.Error(ctx, "failed to extract rkey from blockUri", "uri", input.BlockUri, "err", err) return nil, echo.NewHTTPError(http.StatusBadRequest, "invalid blockUri format") }
// Delete block record from streamer's repo deleteInput := comatproto.RepoDeleteRecord_Input{ Collection: constants.APP_BSKY_GRAPH_BLOCK, Rkey: rkey, Repo: input.Streamer, } deleteOutput := comatproto.RepoDeleteRecord_Output{}
err = modCtx.StreamerClient.Do(ctx, xrpc.Procedure, "application/json", "com.atproto.repo.deleteRecord", map[string]any{}, deleteInput, &deleteOutput) if err != nil { log.Error(ctx, "failed to delete block record", "err", err) if auditErr := s.logAudit(ctx, input.Streamer, modCtx.ModeratorDID, "deleteBlock", input.BlockUri, "", "", false, err.Error()); auditErr != nil { log.Error(ctx, "failed to create audit log", "error", auditErr) } return nil, echo.NewHTTPError(http.StatusInternalServerError, fmt.Sprintf("failed to delete block: %v", err)) }
// Log successful audit entry if err := s.logAudit(ctx, input.Streamer, modCtx.ModeratorDID, "deleteBlock", input.BlockUri, "", "", true, ""); err != nil { log.Error(ctx, "failed to create audit log", "error", err) }
return &placestream.ModerationDeleteBlock_Output{}, nil}
// handlePlaceStreamModerationCreateGate creates a gate (hide message) on behalf of a streamerfunc (s *Server) handlePlaceStreamModerationCreateGate(ctx context.Context, input *placestream.ModerationCreateGate_Input) (*placestream.ModerationCreateGate_Output, error) { // Validate input if err := validateDID(input.Streamer); err != nil { return nil, echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("invalid streamer DID: %v", err)) } if err := validateATURI(input.MessageUri); err != nil { return nil, echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("invalid message URI: %v", err)) }
// Get delegated moderation context (validates OAuth, permission, and returns client) modCtx, err := s.GetDelegatedModerationContext(ctx, input.Streamer, "createGate") if err != nil { return nil, err }
// Create gate record in streamer's repo gate := placestream.ChatGate{ HiddenMessage: input.MessageUri, }
createInput := comatproto.RepoCreateRecord_Input{ Collection: constants.PLACE_STREAM_CHAT_GATE, Record: &glex.LexiconTypeDecoder{Val: &gate}, Repo: input.Streamer, } createOutput := comatproto.RepoCreateRecord_Output{}
err = modCtx.StreamerClient.Do(ctx, xrpc.Procedure, "application/json", "com.atproto.repo.createRecord", map[string]any{}, createInput, &createOutput) if err != nil { log.Error(ctx, "failed to create gate record", "err", err) if auditErr := s.logAudit(ctx, input.Streamer, modCtx.ModeratorDID, "createGate", input.MessageUri, "", "", false, err.Error()); auditErr != nil { log.Error(ctx, "failed to create audit log", "error", auditErr) } return nil, echo.NewHTTPError(http.StatusInternalServerError, fmt.Sprintf("failed to create gate: %v", err)) }
// Log successful audit entry if err := s.logAudit(ctx, input.Streamer, modCtx.ModeratorDID, "createGate", input.MessageUri, "", createOutput.Uri, true, ""); err != nil { log.Error(ctx, "failed to create audit log", "error", err) }
return &placestream.ModerationCreateGate_Output{ Uri: createOutput.Uri, Cid: createOutput.Cid, }, nil}
// handlePlaceStreamModerationDeleteGate deletes a gate (unhide message) on behalf of a streamerfunc (s *Server) handlePlaceStreamModerationDeleteGate(ctx context.Context, input *placestream.ModerationDeleteGate_Input) (*placestream.ModerationDeleteGate_Output, error) { // Validate input if err := validateDID(input.Streamer); err != nil { return nil, echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("invalid streamer DID: %v", err)) } if err := validateATURI(input.GateUri); err != nil { return nil, echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("invalid gate URI: %v", err)) }
// Get delegated moderation context (validates OAuth, permission, and returns client) modCtx, err := s.GetDelegatedModerationContext(ctx, input.Streamer, "deleteGate") if err != nil { return nil, err }
// Parse gateUri to extract rkey rkey, err := extractRKey(input.GateUri) if err != nil { log.Error(ctx, "failed to extract rkey from gateUri", "uri", input.GateUri, "err", err) return nil, echo.NewHTTPError(http.StatusBadRequest, "invalid gateUri format") }
// Delete gate record from streamer's repo deleteInput := comatproto.RepoDeleteRecord_Input{ Collection: constants.PLACE_STREAM_CHAT_GATE, Rkey: rkey, Repo: input.Streamer, } deleteOutput := comatproto.RepoDeleteRecord_Output{}
err = modCtx.StreamerClient.Do(ctx, xrpc.Procedure, "application/json", "com.atproto.repo.deleteRecord", map[string]any{}, deleteInput, &deleteOutput) if err != nil { log.Error(ctx, "failed to delete gate record", "err", err) if auditErr := s.logAudit(ctx, input.Streamer, modCtx.ModeratorDID, "deleteGate", input.GateUri, "", "", false, err.Error()); auditErr != nil { log.Error(ctx, "failed to create audit log", "error", auditErr) } return nil, echo.NewHTTPError(http.StatusInternalServerError, fmt.Sprintf("failed to delete gate: %v", err)) }
// Log successful audit entry if err := s.logAudit(ctx, input.Streamer, modCtx.ModeratorDID, "deleteGate", input.GateUri, "", "", true, ""); err != nil { log.Error(ctx, "failed to create audit log", "error", err) }
return &placestream.ModerationDeleteGate_Output{}, nil}
// handlePlaceStreamModerationUpdateLivestream updates livestream metadata on behalf of a streamerfunc (s *Server) handlePlaceStreamModerationUpdateLivestream(ctx context.Context, input *placestream.ModerationUpdateLivestream_Input) (*placestream.ModerationUpdateLivestream_Output, error) { // Validate input if err := validateDID(input.Streamer); err != nil { return nil, echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("invalid streamer DID: %v", err)) } if err := validateATURI(input.LivestreamUri); err != nil { return nil, echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("invalid livestream URI: %v", err)) }
// Get delegated moderation context (validates OAuth, permission, and returns client) modCtx, err := s.GetDelegatedModerationContext(ctx, input.Streamer, "updateLivestream") if err != nil { return nil, err }
// Parse livestreamUri to extract rkey rkey, err := extractRKey(input.LivestreamUri) if err != nil { log.Error(ctx, "failed to extract rkey from livestreamUri", "uri", input.LivestreamUri, "err", err) return nil, echo.NewHTTPError(http.StatusBadRequest, "invalid livestreamUri format") }
// Get existing livestream record getInput := map[string]any{ "repo": input.Streamer, "collection": constants.PLACE_STREAM_LIVESTREAM, "rkey": rkey, } getOutput := comatproto.RepoGetRecord_Output{} err = modCtx.StreamerClient.Do(ctx, xrpc.Query, "application/json", "com.atproto.repo.getRecord", getInput, nil, &getOutput) if err != nil { log.Error(ctx, "failed to get livestream record", "err", err) if auditErr := s.logAudit(ctx, input.Streamer, modCtx.ModeratorDID, "updateLivestream", input.LivestreamUri, "", "", false, fmt.Sprintf("failed to get record: %v", err)); auditErr != nil { log.Error(ctx, "failed to create audit log", "error", auditErr) } return nil, echo.NewHTTPError(http.StatusNotFound, "livestream record not found") }
// Decode existing record if getOutput.Value == nil || getOutput.Value.Val == nil { log.Error(ctx, "livestream record value is nil") return nil, echo.NewHTTPError(http.StatusInternalServerError, "failed to decode livestream record") }
// Convert the decoded value to our struct existing, err := glex.RecordAs[placestream.Livestream](getOutput.Value.Val) if err != nil { log.Error(ctx, "failed to decode livestream record", "err", err) return nil, echo.NewHTTPError(http.StatusInternalServerError, "failed to decode livestream record") } livestream := *existing
// Create new record (don't edit existing - old records serve as "chapter markers") // Copy fields from existing record and update title if input.Title != nil { livestream.Title = *input.Title }
// Ensure notificationSettings.pushNotification is false for mods if livestream.NotificationSettings == nil { livestream.NotificationSettings = &placestream.Livestream_NotificationSettings{} } pushNotificationFalse := false livestream.NotificationSettings.PushNotification = &pushNotificationFalse
// Update createdAt to current time for new record livestream.CreatedAt = time.Now().UTC().Format(time.RFC3339)
// Create new record instead of updating existing createInput := comatproto.RepoCreateRecord_Input{ Collection: constants.PLACE_STREAM_LIVESTREAM, Record: &glex.LexiconTypeDecoder{Val: &livestream}, Repo: input.Streamer, } createOutput := comatproto.RepoCreateRecord_Output{}
err = modCtx.StreamerClient.Do(ctx, xrpc.Procedure, "application/json", "com.atproto.repo.createRecord", map[string]any{}, createInput, &createOutput) if err != nil { log.Error(ctx, "failed to create livestream record", "err", err) if auditErr := s.logAudit(ctx, input.Streamer, modCtx.ModeratorDID, "updateLivestream", input.LivestreamUri, "", "", false, err.Error()); auditErr != nil { log.Error(ctx, "failed to create audit log", "error", auditErr) } return nil, echo.NewHTTPError(http.StatusInternalServerError, fmt.Sprintf("failed to create livestream: %v", err)) }
// Log successful audit entry if err := s.logAudit(ctx, input.Streamer, modCtx.ModeratorDID, "updateLivestream", input.LivestreamUri, "", createOutput.Uri, true, ""); err != nil { log.Error(ctx, "failed to create audit log", "error", err) }
return &placestream.ModerationUpdateLivestream_Output{ Uri: createOutput.Uri, Cid: createOutput.Cid, }, nil}
// Helper functions
// extractRKey extracts the rkey from an AT-URI (at://did:plc:xxx/collection/rkey)func extractRKey(uri string) (string, error) { aturi, err := syntax.ParseATURI(uri) if err != nil { return "", fmt.Errorf("invalid AT-URI: %w", err) } return aturi.RecordKey().String(), nil}
// validateDID checks if string is a valid AT Protocol DID formatfunc validateDID(did string) error { _, err := syntax.ParseDID(did) if err != nil { return fmt.Errorf("invalid DID format: %w", err) } return nil}
// validateATURI checks if string is a valid AT-URI formatfunc validateATURI(uri string) error { _, err := syntax.ParseATURI(uri) if err != nil { return fmt.Errorf("invalid AT-URI format: %w", err) } return nil}
// logAudit logs a moderation action to the audit logfunc (s *Server) logAudit(ctx context.Context, streamerDID, moderatorDID, action, targetURI, targetDID, resultURI string, success bool, errorMsg string) error { auditLog := &statedb.ModerationAuditLog{ StreamerDID: streamerDID, ModeratorDID: moderatorDID, Action: action, TargetURI: targetURI, TargetDID: targetDID, ResultURI: resultURI, Success: success, ErrorMsg: errorMsg, CreatedAt: time.Now(), }
return s.statefulDB.CreateAuditLog(ctx, auditLog)}
func (s *Server) handlePlaceStreamModerationCreatePin(ctx context.Context, input *placestream.ModerationCreatePin_Input) (*placestream.ModerationCreatePin_Output, error) { // Validate input if err := validateDID(input.Streamer); err != nil { return nil, echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("invalid streamer DID: %v", err)) } if err := validateATURI(input.MessageUri); err != nil { return nil, echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("invalid messageUri: %v", err)) }
// Get delegated moderation context (validates OAuth, permission, and returns client) modCtx, err := s.GetDelegatedModerationContext(ctx, input.Streamer, "createPin") if err != nil { return nil, err }
// Create the pinned record (old pins persist as history) pinnedRecord := placestream.ChatPinnedRecord{ LexiconTypeID: "place.stream.chat.pinnedRecord", PinnedMessage: input.MessageUri, CreatedAt: time.Now().UTC().Format(time.RFC3339), ExpiresAt: input.ExpiresAt, }
createInput := comatproto.RepoCreateRecord_Input{ Collection: constants.PLACE_STREAM_CHAT_PINNED_RECORD, Record: &glex.LexiconTypeDecoder{Val: &pinnedRecord}, Repo: input.Streamer, } createOutput := comatproto.RepoCreateRecord_Output{}
err = modCtx.StreamerClient.Do(ctx, xrpc.Procedure, "application/json", "com.atproto.repo.createRecord", map[string]any{}, createInput, &createOutput) if err != nil { log.Error(ctx, "failed to create pinned record", "err", err) if auditErr := s.logAudit(ctx, input.Streamer, modCtx.ModeratorDID, "createPin", input.MessageUri, "", "", false, err.Error()); auditErr != nil { log.Error(ctx, "failed to create audit log", "error", auditErr) } return nil, echo.NewHTTPError(http.StatusInternalServerError, fmt.Sprintf("failed to create pinned record: %v", err)) }
// Log successful audit entry if err := s.logAudit(ctx, input.Streamer, modCtx.ModeratorDID, "createPin", input.MessageUri, "", createOutput.Uri, true, ""); err != nil { log.Error(ctx, "failed to create audit log", "error", err) }
return &placestream.ModerationCreatePin_Output{ Uri: createOutput.Uri, Cid: createOutput.Cid, }, nil}
func (s *Server) handlePlaceStreamModerationDeletePin(ctx context.Context, input *placestream.ModerationDeletePin_Input) (*placestream.ModerationDeletePin_Output, error) { // Validate input if err := validateDID(input.Streamer); err != nil { return nil, echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("invalid streamer DID: %v", err)) } if err := validateATURI(input.PinUri); err != nil { return nil, echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("invalid pinUri: %v", err)) }
// Get delegated moderation context (validates OAuth, permission, and returns client) modCtx, err := s.GetDelegatedModerationContext(ctx, input.Streamer, "deletePin") if err != nil { return nil, err }
// Parse pinUri to extract rkey rkey, err := extractRKey(input.PinUri) if err != nil { log.Error(ctx, "failed to extract rkey from pinUri", "uri", input.PinUri, "err", err) return nil, echo.NewHTTPError(http.StatusBadRequest, "invalid pinUri format") }
// Delete pinned record from streamer's repo deleteInput := comatproto.RepoDeleteRecord_Input{ Collection: constants.PLACE_STREAM_CHAT_PINNED_RECORD, Rkey: rkey, Repo: input.Streamer, } deleteOutput := comatproto.RepoDeleteRecord_Output{}
err = modCtx.StreamerClient.Do(ctx, xrpc.Procedure, "application/json", "com.atproto.repo.deleteRecord", map[string]any{}, deleteInput, &deleteOutput) if err != nil { log.Error(ctx, "failed to delete pinned record", "err", err) if auditErr := s.logAudit(ctx, input.Streamer, modCtx.ModeratorDID, "deletePin", input.PinUri, "", "", false, err.Error()); auditErr != nil { log.Error(ctx, "failed to create audit log", "error", auditErr) } return nil, echo.NewHTTPError(http.StatusInternalServerError, fmt.Sprintf("failed to delete pinned record: %v", err)) }
// Log successful audit entry if err := s.logAudit(ctx, input.Streamer, modCtx.ModeratorDID, "deletePin", input.PinUri, "", "", true, ""); err != nil { log.Error(ctx, "failed to create audit log", "error", err) }
return &placestream.ModerationDeletePin_Output{}, nil}
// handlePlaceStreamModerationCreateVodGate creates a gate (hide VOD comment) on behalf of a streamerfunc (s *Server) handlePlaceStreamModerationCreateVodGate(ctx context.Context, input *placestream.ModerationCreateVodGate_Input) (*placestream.ModerationCreateVodGate_Output, error) { // Validate input if err := validateDID(input.Streamer); err != nil { return nil, echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("invalid streamer DID: %v", err)) } if err := validateATURI(input.CommentUri); err != nil { return nil, echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("invalid comment URI: %v", err)) }
// Get delegated moderation context (validates OAuth, permission, and returns client) modCtx, err := s.GetDelegatedModerationContext(ctx, input.Streamer, "createVodGate") if err != nil { return nil, err }
// Create gate record in streamer's repo gate := placestream.VodGate{ HiddenComment: input.CommentUri, }
createInput := comatproto.RepoCreateRecord_Input{ Collection: constants.PLACE_STREAM_VOD_GATE, Record: &glex.LexiconTypeDecoder{Val: &gate}, Repo: input.Streamer, } createOutput := comatproto.RepoCreateRecord_Output{}
err = modCtx.StreamerClient.Do(ctx, xrpc.Procedure, "application/json", "com.atproto.repo.createRecord", map[string]any{}, createInput, &createOutput) if err != nil { log.Error(ctx, "failed to create VOD gate record", "err", err) if auditErr := s.logAudit(ctx, input.Streamer, modCtx.ModeratorDID, "createVodGate", input.CommentUri, "", "", false, err.Error()); auditErr != nil { log.Error(ctx, "failed to create audit log", "error", auditErr) } return nil, echo.NewHTTPError(http.StatusInternalServerError, fmt.Sprintf("failed to create VOD gate: %v", err)) }
// Log successful audit entry if err := s.logAudit(ctx, input.Streamer, modCtx.ModeratorDID, "createVodGate", input.CommentUri, "", createOutput.Uri, true, ""); err != nil { log.Error(ctx, "failed to create audit log", "error", err) }
return &placestream.ModerationCreateVodGate_Output{ Uri: createOutput.Uri, Cid: createOutput.Cid, }, nil}
// handlePlaceStreamModerationDeleteVodGate deletes a gate (unhide VOD comment) on behalf of a streamerfunc (s *Server) handlePlaceStreamModerationDeleteVodGate(ctx context.Context, input *placestream.ModerationDeleteVodGate_Input) (*placestream.ModerationDeleteVodGate_Output, error) { // Validate input if err := validateDID(input.Streamer); err != nil { return nil, echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("invalid streamer DID: %v", err)) } if err := validateATURI(input.GateUri); err != nil { return nil, echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("invalid gate URI: %v", err)) }
// Get delegated moderation context (validates OAuth, permission, and returns client) modCtx, err := s.GetDelegatedModerationContext(ctx, input.Streamer, "deleteVodGate") if err != nil { return nil, err }
// Parse gateUri to extract rkey rkey, err := extractRKey(input.GateUri) if err != nil { log.Error(ctx, "failed to extract rkey from gateUri", "uri", input.GateUri, "err", err) return nil, echo.NewHTTPError(http.StatusBadRequest, "invalid gateUri format") }
// Delete gate record from streamer's repo deleteInput := comatproto.RepoDeleteRecord_Input{ Collection: constants.PLACE_STREAM_VOD_GATE, Rkey: rkey, Repo: input.Streamer, } deleteOutput := comatproto.RepoDeleteRecord_Output{}
err = modCtx.StreamerClient.Do(ctx, xrpc.Procedure, "application/json", "com.atproto.repo.deleteRecord", map[string]any{}, deleteInput, &deleteOutput) if err != nil { log.Error(ctx, "failed to delete VOD gate record", "err", err) if auditErr := s.logAudit(ctx, input.Streamer, modCtx.ModeratorDID, "deleteVodGate", input.GateUri, "", "", false, err.Error()); auditErr != nil { log.Error(ctx, "failed to create audit log", "error", auditErr) } return nil, echo.NewHTTPError(http.StatusInternalServerError, fmt.Sprintf("failed to delete VOD gate: %v", err)) }
// Log successful audit entry if err := s.logAudit(ctx, input.Streamer, modCtx.ModeratorDID, "deleteVodGate", input.GateUri, "", "", true, ""); err != nil { log.Error(ctx, "failed to create audit log", "error", err) }
return &placestream.ModerationDeleteVodGate_Output{}, nil}