163 lines
5.6 KiB
Go
163 lines
5.6 KiB
Go
package main
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"time"
|
|
|
|
"github.com/segmentio/kafka-go"
|
|
"github.com/tinode/chat/server/store"
|
|
"github.com/tinode/chat/server/store/types"
|
|
)
|
|
|
|
type kafkaWorkbenchPayload struct {
|
|
Module string `json:"module"`
|
|
Action string `json:"action"`
|
|
ResourceID string `json:"resource_id"`
|
|
ActorUserID string `json:"actor_user_id"`
|
|
TargetUserIDs []string `json:"target_user_ids,omitempty"`
|
|
Title string `json:"title,omitempty"`
|
|
Summary string `json:"summary,omitempty"`
|
|
Data map[string]any `json:"data,omitempty"`
|
|
OccurredAt time.Time `json:"occurred_at"`
|
|
}
|
|
|
|
type kafkaAuditPayload struct {
|
|
ActorType string `json:"actor_type"`
|
|
ActorID string `json:"actor_id"`
|
|
Action string `json:"action"`
|
|
Resource string `json:"resource"`
|
|
ResourceID string `json:"resource_id"`
|
|
Result string `json:"result"`
|
|
Reason string `json:"reason,omitempty"`
|
|
Data map[string]any `json:"data,omitempty"`
|
|
OccurredAt time.Time `json:"occurred_at"`
|
|
}
|
|
|
|
func startKafkaWorkbenchEventConsumer() {
|
|
startKafkaConsumer("workbench_event", handleKafkaWorkbenchEvent)
|
|
}
|
|
|
|
func enqueueKafkaWorkbenchEvent(ctx context.Context, tenantID types.TenantID, actor types.Uid, module, action, resourceID string,
|
|
targets []types.Uid, title, summary string, data map[string]any) error {
|
|
if !kafkaEnabled() {
|
|
return nil
|
|
}
|
|
targetUserIDs := make([]string, 0, len(targets))
|
|
for _, target := range targets {
|
|
if !target.IsZero() {
|
|
targetUserIDs = append(targetUserIDs, target.UserId())
|
|
}
|
|
}
|
|
payload := kafkaWorkbenchPayload{
|
|
Module: module,
|
|
Action: action,
|
|
ResourceID: resourceID,
|
|
ActorUserID: actor.UserId(),
|
|
TargetUserIDs: targetUserIDs,
|
|
Title: title,
|
|
Summary: summary,
|
|
Data: data,
|
|
OccurredAt: time.Now().UTC().Round(time.Millisecond),
|
|
}
|
|
return enqueueKafkaTypedEvent(ctx, tenantID, "workbench_event", "workbench."+action, "workbench", resourceID, actor, payload)
|
|
}
|
|
|
|
func enqueueKafkaAuditEvent(ctx context.Context, tenantID types.TenantID, actorType string, actor types.Uid,
|
|
action, resource, resourceID, result, reason string, data map[string]any) error {
|
|
if !kafkaEnabled() {
|
|
return nil
|
|
}
|
|
actorID := ""
|
|
if !actor.IsZero() {
|
|
actorID = actor.UserId()
|
|
}
|
|
payload := kafkaAuditPayload{
|
|
ActorType: actorType,
|
|
ActorID: actorID,
|
|
Action: action,
|
|
Resource: resource,
|
|
ResourceID: resourceID,
|
|
Result: result,
|
|
Reason: reason,
|
|
Data: data,
|
|
OccurredAt: time.Now().UTC().Round(time.Millisecond),
|
|
}
|
|
return enqueueKafkaTypedEvent(ctx, tenantID, "audit_event", "audit."+action, "audit", resource+":"+resourceID, actor, payload)
|
|
}
|
|
|
|
func enqueueKafkaTypedEvent(ctx context.Context, tenantID types.TenantID, topicName, eventType, bizType, bizID string, actor types.Uid, payload any) error {
|
|
encoded, err := json.Marshal(payload)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
eventID := stableIMEventID(topicName, tenantID, eventType, bizID, encoded)
|
|
envelope := kafkaEnvelope{
|
|
EventID: eventID,
|
|
TenantID: tenantID,
|
|
SourceService: "im-server",
|
|
TargetService: "im-server",
|
|
EventType: eventType,
|
|
BizType: bizType,
|
|
BizID: bizID,
|
|
OccurredAt: time.Now().UTC(),
|
|
Payload: encoded,
|
|
}
|
|
if !actor.IsZero() {
|
|
envelope.Actor = &kafkaActor{Type: "user", ID: actor.UserId()}
|
|
}
|
|
return publishKafkaEnvelope(ctx, topicName, bizID, envelope)
|
|
}
|
|
|
|
func handleKafkaWorkbenchEvent(ctx context.Context, envelope kafkaEnvelope, _ kafka.Message) error {
|
|
tenantID, err := kafkaTenantIDFromEnvelope(envelope)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
consume, claimed, err := beginKafkaConsumeForTopic(tenantID, envelope, "workbench_event")
|
|
if err != nil || !claimed {
|
|
return err
|
|
}
|
|
var payload kafkaWorkbenchPayload
|
|
if err = json.Unmarshal(envelope.Payload, &payload); err != nil {
|
|
return markKafkaDeadLetter(ctx, tenantID, envelope, consume, "MALFORMED_PAYLOAD", err.Error())
|
|
}
|
|
if err = executeKafkaWorkbenchEvent(tenantID, envelope, payload); err != nil {
|
|
failed, markErr := store.HTTPInfrastructure.MarkKafkaConsumeFailed(tenantID, envelope.EventID, kafkaBus.config.ConsumerGroup,
|
|
kafkaCommandErrorReason(err), err.Error(), time.Now().UTC().Round(time.Millisecond))
|
|
if markErr != nil {
|
|
return markErr
|
|
}
|
|
if failed != nil && failed.RetryCount >= kafkaBus.config.MaxRetryCount {
|
|
return markKafkaDeadLetter(ctx, tenantID, envelope, failed, kafkaCommandErrorReason(err), err.Error())
|
|
}
|
|
sleepKafkaRetryBackoff(failed.RetryCount)
|
|
return err
|
|
}
|
|
return store.HTTPInfrastructure.MarkKafkaConsumeSucceeded(tenantID, envelope.EventID, kafkaBus.config.ConsumerGroup, time.Now().UTC().Round(time.Millisecond))
|
|
}
|
|
|
|
func executeKafkaWorkbenchEvent(tenantID types.TenantID, envelope kafkaEnvelope, payload kafkaWorkbenchPayload) error {
|
|
actor := types.ParseUserId(payload.ActorUserID)
|
|
if actor.IsZero() {
|
|
actor = types.ParseUid(payload.ActorUserID)
|
|
}
|
|
for _, raw := range payload.TargetUserIDs {
|
|
target := types.ParseUserId(raw)
|
|
if target.IsZero() {
|
|
continue
|
|
}
|
|
eventID := stableIMEventID("workbench", envelope.EventID, target.UserId())
|
|
if target != actor && payload.Title != "" {
|
|
if err := createIMSystemNotification(tenantID, target, stableIMEventID(eventID, "notification"), payload.Action,
|
|
payload.Title, payload.Summary, types.KVMap{"module": payload.Module, "resource_id": payload.ResourceID}); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
if err := enqueueIMBusinessEvent(tenantID, eventID, "user", target.UserId(), actor, payload.Action, payload.Data); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|