feat: add kafka admin integration
This commit is contained in:
+243
-14
@@ -68,7 +68,7 @@ const (
|
||||
imQRCodeMaxExpiresInSeconds = 7 * 86400
|
||||
imPublicUploadMaxBytes = 10 << 20
|
||||
groupMaxAdminCount = 10
|
||||
monthlyReportPreviewMaxRunes = 120
|
||||
reportPreviewMaxRunes = 120
|
||||
)
|
||||
|
||||
type imHTTPResponse struct {
|
||||
@@ -170,6 +170,10 @@ func serveIMHTTPIdempotent(wrt http.ResponseWriter, req *http.Request, next http
|
||||
next.ServeHTTP(wrt, req)
|
||||
return
|
||||
}
|
||||
if req.Method == http.MethodPost && strings.TrimSpace(req.URL.Path) == "/v1/im/tenants/resolve" {
|
||||
next.ServeHTTP(wrt, req)
|
||||
return
|
||||
}
|
||||
if !strings.HasPrefix(strings.TrimSpace(req.Header.Get("Authorization")), "Bearer ") {
|
||||
next.ServeHTTP(wrt, req)
|
||||
return
|
||||
@@ -396,6 +400,16 @@ type imFavoritesForwardRequest struct {
|
||||
Comment string `json:"comment,omitempty"`
|
||||
}
|
||||
|
||||
type imMessagesForwardRequest struct {
|
||||
Mode string `json:"mode"`
|
||||
Sources []struct {
|
||||
Topic string `json:"topic"`
|
||||
Seq int64 `json:"seq,string"`
|
||||
} `json:"sources"`
|
||||
TargetTopics []string `json:"target_topics"`
|
||||
Comment string `json:"comment,omitempty"`
|
||||
}
|
||||
|
||||
type imFriendRequestCreateRequest struct {
|
||||
TargetUserID string `json:"target_user_id"`
|
||||
Message string `json:"message,omitempty"`
|
||||
@@ -1886,6 +1900,8 @@ func serveIMMeetings(wrt http.ResponseWriter, req *http.Request) {
|
||||
return
|
||||
}
|
||||
}
|
||||
publishIMWorkbenchAndAudit(req.Context(), principal.TenantID, principal.Uid, "meeting", "meeting.created", meeting.MeetingID,
|
||||
meetingParticipantUIDs(meeting), "会议通知", meeting.Title, map[string]any{"meeting_id": meeting.MeetingID, "version": meeting.Version, "state": meeting.State})
|
||||
writeIMSuccess(wrt, req, meetingPayload(meeting, principal.Uid))
|
||||
}
|
||||
|
||||
@@ -1979,6 +1995,8 @@ func serveIMMeetingUpdate(wrt http.ResponseWriter, req *http.Request, meetingID
|
||||
return
|
||||
}
|
||||
}
|
||||
publishIMWorkbenchAndAudit(req.Context(), principal.TenantID, principal.Uid, "meeting", "meeting.updated", meeting.MeetingID,
|
||||
meetingParticipantUIDs(meeting), "会议通知", meeting.Title, map[string]any{"meeting_id": meeting.MeetingID, "version": meeting.Version, "state": meeting.State})
|
||||
writeIMSuccess(wrt, req, meetingPayload(meeting, principal.Uid))
|
||||
}
|
||||
|
||||
@@ -2006,6 +2024,8 @@ func serveIMMeetingCancel(wrt http.ResponseWriter, req *http.Request, meetingID
|
||||
return
|
||||
}
|
||||
}
|
||||
publishIMWorkbenchAndAudit(req.Context(), principal.TenantID, principal.Uid, "meeting", "meeting.canceled", meeting.MeetingID,
|
||||
meetingParticipantUIDs(meeting), "会议通知", meeting.Title, map[string]any{"meeting_id": meeting.MeetingID, "version": meeting.Version, "state": meeting.State})
|
||||
writeIMSuccess(wrt, req, meetingPayload(meeting, principal.Uid))
|
||||
}
|
||||
|
||||
@@ -2039,6 +2059,8 @@ func serveIMMeetingReminders(wrt http.ResponseWriter, req *http.Request, meeting
|
||||
return
|
||||
}
|
||||
}
|
||||
publishIMWorkbenchAndAudit(req.Context(), principal.TenantID, principal.Uid, "meeting", "meeting.reminded", meetingID,
|
||||
meetingParticipantUserIDs(participants), "会议提醒", meetingID, map[string]any{"meeting_id": meetingID, "reminded_count": len(participants)})
|
||||
writeIMSuccess(wrt, req, map[string]any{
|
||||
"meeting_id": meetingID,
|
||||
"reminded": meetingParticipantPayloads(participants),
|
||||
@@ -2072,6 +2094,8 @@ func serveIMMeetingMyResponse(wrt http.ResponseWriter, req *http.Request, meetin
|
||||
return
|
||||
}
|
||||
}
|
||||
publishIMWorkbenchAndAudit(req.Context(), principal.TenantID, principal.Uid, "meeting", "meeting.response.updated", meeting.MeetingID,
|
||||
meetingParticipantUIDs(meeting), "会议通知", meeting.Title, map[string]any{"meeting_id": meeting.MeetingID, "version": meeting.Version, "state": meeting.State})
|
||||
writeIMSuccess(wrt, req, meetingPayload(meeting, principal.Uid))
|
||||
}
|
||||
|
||||
@@ -2126,6 +2150,8 @@ func serveIMDailyReports(wrt http.ResponseWriter, req *http.Request) {
|
||||
writeIMRelationError(wrt, req, err)
|
||||
return
|
||||
}
|
||||
publishIMWorkbenchAndAudit(req.Context(), principal.TenantID, principal.Uid, "daily_report", "daily_report.draft.created", report.ReportID,
|
||||
[]types.Uid{principal.Uid}, "日报", report.ReportDate.Format("2006-01-02"), map[string]any{"report_id": report.ReportID, "version": report.Version, "status": report.Status})
|
||||
writeIMSuccess(wrt, req, dailyReportDetailPayload(report, principal.Uid))
|
||||
default:
|
||||
writeIMError(wrt, req, http.StatusMethodNotAllowed, 30002, imReasonMethodNotAllowed, "method not allowed", nil)
|
||||
@@ -2172,6 +2198,8 @@ func serveIMDailyReportGet(wrt http.ResponseWriter, req *http.Request, reportID
|
||||
writeIMRelationError(wrt, req, err)
|
||||
return
|
||||
}
|
||||
publishIMWorkbenchAndAudit(req.Context(), principal.TenantID, principal.Uid, "daily_report", "daily_report.draft.saved", report.ReportID,
|
||||
[]types.Uid{principal.Uid}, "日报", report.ReportDate.Format("2006-01-02"), map[string]any{"report_id": report.ReportID, "version": report.Version, "status": report.Status})
|
||||
writeIMSuccess(wrt, req, dailyReportDetailPayload(report, principal.Uid))
|
||||
}
|
||||
|
||||
@@ -2197,6 +2225,8 @@ func serveIMDailyReportSaveDraft(wrt http.ResponseWriter, req *http.Request, rep
|
||||
writeIMRelationError(wrt, req, err)
|
||||
return
|
||||
}
|
||||
publishIMWorkbenchAndAudit(req.Context(), principal.TenantID, principal.Uid, "daily_report", "daily_report.submitted", report.ReportID,
|
||||
[]types.Uid{principal.Uid}, "日报已提交", report.ReportDate.Format("2006-01-02"), map[string]any{"report_id": report.ReportID, "version": report.Version, "status": report.Status})
|
||||
writeIMSuccess(wrt, req, dailyReportDetailPayload(report, principal.Uid))
|
||||
}
|
||||
|
||||
@@ -2288,6 +2318,8 @@ func serveIMDailyReportReminders(wrt http.ResponseWriter, req *http.Request) {
|
||||
for _, uid := range reminded {
|
||||
items = append(items, map[string]any{"user_id": uid.UserId(), "status": "queued"})
|
||||
}
|
||||
publishIMWorkbenchAndAudit(req.Context(), principal.TenantID, principal.Uid, "daily_report", "daily_report.reminded", date.Format("2006-01-02"),
|
||||
reminded, "日报提醒", date.Format("2006-01-02"), map[string]any{"date": date.Format("2006-01-02"), "reminded_count": len(reminded)})
|
||||
writeIMSuccess(wrt, req, map[string]any{"date": date.Format("2006-01-02"), "items": items})
|
||||
}
|
||||
|
||||
@@ -2332,6 +2364,8 @@ func serveIMMonthlyReports(wrt http.ResponseWriter, req *http.Request) {
|
||||
writeIMRelationError(wrt, req, err)
|
||||
return
|
||||
}
|
||||
publishIMWorkbenchAndAudit(req.Context(), principal.TenantID, principal.Uid, "monthly_report", "monthly_report.draft.created", report.ReportID,
|
||||
[]types.Uid{principal.Uid}, "月报", report.Month, map[string]any{"report_id": report.ReportID, "version": report.Version, "status": report.Status})
|
||||
writeIMSuccess(wrt, req, monthlyReportDetailPayload(report, principal.Uid))
|
||||
default:
|
||||
writeIMError(wrt, req, http.StatusMethodNotAllowed, 30002, imReasonMethodNotAllowed, "method not allowed", nil)
|
||||
@@ -2377,6 +2411,8 @@ func serveIMMonthlyReportGet(wrt http.ResponseWriter, req *http.Request, reportI
|
||||
writeIMRelationError(wrt, req, err)
|
||||
return
|
||||
}
|
||||
publishIMWorkbenchAndAudit(req.Context(), principal.TenantID, principal.Uid, "monthly_report", "monthly_report.draft.saved", report.ReportID,
|
||||
[]types.Uid{principal.Uid}, "月报", report.Month, map[string]any{"report_id": report.ReportID, "version": report.Version, "status": report.Status})
|
||||
writeIMSuccess(wrt, req, monthlyReportDetailPayload(report, principal.Uid))
|
||||
}
|
||||
|
||||
@@ -2403,6 +2439,8 @@ func serveIMMonthlyReportSaveDraft(wrt http.ResponseWriter, req *http.Request, r
|
||||
writeIMRelationError(wrt, req, err)
|
||||
return
|
||||
}
|
||||
publishIMWorkbenchAndAudit(req.Context(), principal.TenantID, principal.Uid, "monthly_report", "monthly_report.submitted", report.ReportID,
|
||||
[]types.Uid{principal.Uid}, "月报已提交", report.Month, map[string]any{"report_id": report.ReportID, "version": report.Version, "status": report.Status})
|
||||
writeIMSuccess(wrt, req, monthlyReportDetailPayload(report, principal.Uid))
|
||||
}
|
||||
|
||||
@@ -2426,6 +2464,8 @@ func serveIMMonthlyReportSubmit(wrt http.ResponseWriter, req *http.Request, repo
|
||||
writeIMRelationError(wrt, req, err)
|
||||
return
|
||||
}
|
||||
publishIMWorkbenchAndAudit(req.Context(), principal.TenantID, principal.Uid, "monthly_report", "monthly_report.reopened", report.ReportID,
|
||||
[]types.Uid{principal.Uid}, "月报已重开", report.Month, map[string]any{"report_id": report.ReportID, "version": report.Version, "status": report.Status})
|
||||
writeIMSuccess(wrt, req, monthlyReportDetailPayload(report, principal.Uid))
|
||||
}
|
||||
|
||||
@@ -2541,6 +2581,8 @@ func serveIMMailboxSubmissions(wrt http.ResponseWriter, req *http.Request) {
|
||||
writeIMRelationError(wrt, req, err)
|
||||
return
|
||||
}
|
||||
publishIMWorkbenchAndAudit(req.Context(), principal.TenantID, principal.Uid, "mailbox", "mailbox.submitted", submission.SubmissionID,
|
||||
[]types.Uid{principal.Uid}, "总经理信箱", submission.Subject, map[string]any{"submission_id": submission.SubmissionID, "status": submission.Status, "anonymous": submission.Anonymous})
|
||||
writeIMSuccess(wrt, req, mailboxSubmissionResultPayload(submission))
|
||||
}
|
||||
|
||||
@@ -3201,7 +3243,7 @@ func serveIMFavoriteForward(wrt http.ResponseWriter, req *http.Request) {
|
||||
},
|
||||
Content: favoriteForwardContent(item, comment),
|
||||
}
|
||||
saved, err := store.Messages.Save(principal.TenantID, msg, nil, true)
|
||||
saved, err := saveMessageMaybeKafka(req.Context(), principal.TenantID, msg, nil, true)
|
||||
if err != nil {
|
||||
results = append(results, map[string]any{"target_topic": target, "favorite_id": favoriteID, "status": "failed", "reason": relationErrorReason(err)})
|
||||
continue
|
||||
@@ -3212,6 +3254,107 @@ func serveIMFavoriteForward(wrt http.ResponseWriter, req *http.Request) {
|
||||
writeIMSuccess(wrt, req, map[string]any{"items": results})
|
||||
}
|
||||
|
||||
func serveIMMessageForward(wrt http.ResponseWriter, req *http.Request) {
|
||||
if req.Method != http.MethodPost {
|
||||
writeIMError(wrt, req, http.StatusMethodNotAllowed, 30002, imReasonMethodNotAllowed, "method not allowed", nil)
|
||||
return
|
||||
}
|
||||
principal, ok := authenticateIMHTTPBearer(wrt, req)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
var body imMessagesForwardRequest
|
||||
if !decodeIMJSONBody(wrt, req, &body) {
|
||||
return
|
||||
}
|
||||
mode := strings.TrimSpace(body.Mode)
|
||||
if mode == "" {
|
||||
mode = "separate"
|
||||
}
|
||||
if mode != "separate" {
|
||||
writeIMError(wrt, req, http.StatusNotImplemented, 30004, "NOT_IMPLEMENTED", "forward bundle is not implemented", map[string]any{"field": "mode"})
|
||||
return
|
||||
}
|
||||
if len(body.Sources) == 0 || len(body.Sources) > 100 || len(body.TargetTopics) == 0 || len(body.TargetTopics) > 20 {
|
||||
writeIMError(wrt, req, http.StatusBadRequest, 30000, imReasonMalformedRequest, "malformed request", nil)
|
||||
return
|
||||
}
|
||||
sourceReadable := make(map[string]error)
|
||||
targetWritable := make(map[string]error)
|
||||
items := make([]map[string]any, 0, len(body.TargetTopics)*len(body.Sources))
|
||||
failed := make([]map[string]any, 0)
|
||||
for _, rawTarget := range body.TargetTopics {
|
||||
target := strings.TrimSpace(rawTarget)
|
||||
if target == "" {
|
||||
for _, source := range body.Sources {
|
||||
failed = append(failed, forwardFailurePayload(source.Topic, source.Seq, target, imReasonMalformedRequest))
|
||||
}
|
||||
continue
|
||||
}
|
||||
targetErr, ok := targetWritable[target]
|
||||
if !ok {
|
||||
targetErr = requireTopicWriter(principal, target)
|
||||
targetWritable[target] = targetErr
|
||||
}
|
||||
if targetErr != nil {
|
||||
for _, source := range body.Sources {
|
||||
failed = append(failed, forwardFailurePayload(source.Topic, source.Seq, target, relationErrorReason(targetErr)))
|
||||
}
|
||||
continue
|
||||
}
|
||||
for _, source := range body.Sources {
|
||||
sourceTopic := strings.TrimSpace(source.Topic)
|
||||
if sourceTopic == "" || source.Seq <= 0 {
|
||||
failed = append(failed, forwardFailurePayload(sourceTopic, source.Seq, target, imReasonMalformedRequest))
|
||||
continue
|
||||
}
|
||||
readErr, ok := sourceReadable[sourceTopic]
|
||||
if !ok {
|
||||
readErr = requireTopicReader(principal, sourceTopic)
|
||||
sourceReadable[sourceTopic] = readErr
|
||||
}
|
||||
if readErr != nil {
|
||||
failed = append(failed, forwardFailurePayload(sourceTopic, source.Seq, target, relationErrorReason(readErr)))
|
||||
continue
|
||||
}
|
||||
msg, err := store.Messages.GetOne(principal.TenantID, sourceTopic, source.Seq)
|
||||
if err != nil {
|
||||
failed = append(failed, forwardFailurePayload(sourceTopic, source.Seq, target, relationErrorReason(err)))
|
||||
continue
|
||||
}
|
||||
if msg == nil {
|
||||
failed = append(failed, forwardFailurePayload(sourceTopic, source.Seq, target, "NOT_FOUND"))
|
||||
continue
|
||||
}
|
||||
if messageAvailability(*msg) != "available" {
|
||||
failed = append(failed, forwardFailurePayload(sourceTopic, source.Seq, target, "MESSAGE_UNAVAILABLE"))
|
||||
continue
|
||||
}
|
||||
clientMid := "fwd_" + store.Store.GetUidString()
|
||||
forwarded := &types.Message{
|
||||
TenantID: principal.TenantID,
|
||||
Topic: target,
|
||||
From: principal.Uid.String(),
|
||||
Head: forwardedMessageHead(msg, clientMid),
|
||||
Content: forwardedMessageContent(msg, body.Comment),
|
||||
}
|
||||
saved, err := saveMessageMaybeKafka(req.Context(), principal.TenantID, forwarded, nil, true)
|
||||
if err != nil {
|
||||
failed = append(failed, forwardFailurePayload(sourceTopic, source.Seq, target, relationErrorReason(err)))
|
||||
continue
|
||||
}
|
||||
items = append(items, map[string]any{
|
||||
"source": map[string]any{"topic": sourceTopic, "seq": source.Seq},
|
||||
"target_topic": target,
|
||||
"client_mid": clientMid,
|
||||
"seq": saved.SeqId,
|
||||
"status": "sent",
|
||||
})
|
||||
}
|
||||
}
|
||||
writeIMSuccess(wrt, req, map[string]any{"items": items, "failed": failed})
|
||||
}
|
||||
|
||||
func serveIMTopicMessageAction(wrt http.ResponseWriter, req *http.Request) {
|
||||
switch {
|
||||
case strings.HasSuffix(req.URL.Path, "/draft"):
|
||||
@@ -4500,6 +4643,9 @@ func serveIMGroupFiles(wrt http.ResponseWriter, req *http.Request, topic, tail s
|
||||
writeIMRelationError(wrt, req, err)
|
||||
return
|
||||
}
|
||||
if err = enqueueKafkaGroupFileLifecycle(req.Context(), principal.TenantID, kafkaFileLifecycleGroupFileCreate, file, principal.Uid); err != nil {
|
||||
logs.Warn.Printf("im http: failed to enqueue group file lifecycle event topic=%s file=%s: %v", topic, file.FileID, err)
|
||||
}
|
||||
writeIMSuccess(wrt, req, groupFilePayload(file))
|
||||
case strings.HasPrefix(tail, "files/") && req.Method == http.MethodDelete:
|
||||
groupFileID := strings.TrimSpace(strings.TrimPrefix(tail, "files/"))
|
||||
@@ -4513,6 +4659,9 @@ func serveIMGroupFiles(wrt http.ResponseWriter, req *http.Request, topic, tail s
|
||||
writeIMRelationError(wrt, req, err)
|
||||
return
|
||||
}
|
||||
if err = enqueueKafkaGroupFileLifecycle(req.Context(), principal.TenantID, kafkaFileLifecycleGroupFileDelete, file, principal.Uid); err != nil {
|
||||
logs.Warn.Printf("im http: failed to enqueue group file lifecycle event topic=%s file=%s: %v", topic, file.FileID, err)
|
||||
}
|
||||
writeIMSuccess(wrt, req, groupFilePayload(file))
|
||||
default:
|
||||
writeIMError(wrt, req, http.StatusMethodNotAllowed, 30002, imReasonMethodNotAllowed, "method not allowed", nil)
|
||||
@@ -7134,6 +7283,33 @@ func meetingParticipantPayloads(participants []types.MeetingParticipant) []map[s
|
||||
return items
|
||||
}
|
||||
|
||||
func meetingParticipantUIDs(meeting *types.Meeting) []types.Uid {
|
||||
if meeting == nil {
|
||||
return nil
|
||||
}
|
||||
return meetingParticipantUserIDs(meeting.Participants)
|
||||
}
|
||||
|
||||
func meetingParticipantUserIDs(participants []types.MeetingParticipant) []types.Uid {
|
||||
items := make([]types.Uid, 0, len(participants))
|
||||
for _, participant := range participants {
|
||||
if !participant.UserID.IsZero() {
|
||||
items = append(items, participant.UserID)
|
||||
}
|
||||
}
|
||||
return items
|
||||
}
|
||||
|
||||
func publishIMWorkbenchAndAudit(ctx context.Context, tenantID types.TenantID, actor types.Uid, module, action, resourceID string,
|
||||
targets []types.Uid, title, summary string, data map[string]any) {
|
||||
if err := enqueueKafkaWorkbenchEvent(ctx, tenantID, actor, module, action, resourceID, targets, title, summary, data); err != nil {
|
||||
logs.Warn.Printf("im http: failed to enqueue workbench event action=%s resource=%s: %v", action, resourceID, err)
|
||||
}
|
||||
if err := enqueueKafkaAuditEvent(ctx, tenantID, "user", actor, action, module, resourceID, "succeeded", "", data); err != nil {
|
||||
logs.Warn.Printf("im http: failed to enqueue audit event action=%s resource=%s: %v", action, resourceID, err)
|
||||
}
|
||||
}
|
||||
|
||||
func meetingPermissionsPayload(meeting *types.Meeting, viewer types.Uid) map[string]any {
|
||||
isOrganizer := meeting.OrganizerID == viewer
|
||||
isScheduled := meeting.State == types.MeetingStateScheduled
|
||||
@@ -7168,7 +7344,7 @@ func dailyReportListPayload(report types.DailyReport) map[string]any {
|
||||
"report_id": report.ReportID,
|
||||
"author_id": report.AuthorID.UserId(),
|
||||
"report_date": report.ReportDate.Format("2006-01-02"),
|
||||
"content_short": monthlyReportPreview(report.Content),
|
||||
"content_short": reportPreview(report.Content),
|
||||
"status": report.Status,
|
||||
"draft_updated_at": report.UpdatedAt.UTC().Format(time.RFC3339Nano),
|
||||
"read_state": report.ReadState,
|
||||
@@ -7211,8 +7387,8 @@ func monthlyReportListPayload(report types.MonthlyReport) map[string]any {
|
||||
"report_id": report.ReportID,
|
||||
"author_id": report.AuthorID.UserId(),
|
||||
"month": report.Month,
|
||||
"work_summary_short": monthlyReportPreview(report.WorkSummary),
|
||||
"next_plan_short": monthlyReportPreview(report.NextPlan),
|
||||
"work_summary_short": reportPreview(report.WorkSummary),
|
||||
"next_plan_short": reportPreview(report.NextPlan),
|
||||
"status": report.Status,
|
||||
"read_state": report.ReadState,
|
||||
"version": report.Version,
|
||||
@@ -7225,13 +7401,13 @@ func monthlyReportListPayload(report types.MonthlyReport) map[string]any {
|
||||
return data
|
||||
}
|
||||
|
||||
func monthlyReportPreview(text string) string {
|
||||
func reportPreview(text string) string {
|
||||
text = strings.Join(strings.Fields(text), " ")
|
||||
runes := []rune(text)
|
||||
if len(runes) <= monthlyReportPreviewMaxRunes {
|
||||
if len(runes) <= reportPreviewMaxRunes {
|
||||
return text
|
||||
}
|
||||
return string(runes[:monthlyReportPreviewMaxRunes]) + "..."
|
||||
return string(runes[:reportPreviewMaxRunes]) + "..."
|
||||
}
|
||||
|
||||
func monthlyReportDetailPayload(report *types.MonthlyReport, viewer types.Uid) map[string]any {
|
||||
@@ -7826,6 +8002,20 @@ func requireTopicWriter(principal *imPrincipal, topic string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func requireTopicReader(principal *imPrincipal, topic string) error {
|
||||
if principal == nil || topic == "" {
|
||||
return types.ErrMalformed
|
||||
}
|
||||
sub, err := store.Subs.Get(principal.TenantID, topic, principal.Uid, false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if sub == nil || !(sub.ModeGiven & sub.ModeWant).IsReader() {
|
||||
return types.ErrPermissionDenied
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func copyStringAnyMap(in map[string]any) map[string]any {
|
||||
out := make(map[string]any, len(in))
|
||||
for key, value := range in {
|
||||
@@ -7956,6 +8146,49 @@ func favoriteForwardContent(item *types.FavoriteItem, comment string) map[string
|
||||
return payload
|
||||
}
|
||||
|
||||
func forwardFailurePayload(topic string, seq int64, target, reason string) map[string]any {
|
||||
return map[string]any{
|
||||
"source": map[string]any{"topic": strings.TrimSpace(topic), "seq": seq},
|
||||
"target_topic": strings.TrimSpace(target),
|
||||
"status": "failed",
|
||||
"reason": reason,
|
||||
}
|
||||
}
|
||||
|
||||
func forwardedMessageHead(msg *types.Message, clientMid string) types.KVMap {
|
||||
head := types.KVMap{}
|
||||
if msg != nil {
|
||||
for key, value := range msg.Head {
|
||||
head[key] = value
|
||||
}
|
||||
msgType := imMessageType(msg)
|
||||
if strings.TrimSpace(fmt.Sprint(head["type"])) == "" || fmt.Sprint(head["type"]) == "<nil>" {
|
||||
head["type"] = msgType
|
||||
}
|
||||
if strings.TrimSpace(fmt.Sprint(head["x-im-type"])) == "" || fmt.Sprint(head["x-im-type"]) == "<nil>" {
|
||||
head["x-im-type"] = msgType
|
||||
}
|
||||
head["x-im-forward-source"] = map[string]any{"topic": msg.Topic, "seq": msg.SeqId, "from": msg.From}
|
||||
}
|
||||
head["x-im-forwarded"] = true
|
||||
head["x-im-client-mid"] = clientMid
|
||||
return head
|
||||
}
|
||||
|
||||
func forwardedMessageContent(msg *types.Message, comment string) any {
|
||||
if strings.TrimSpace(comment) == "" {
|
||||
if msg == nil {
|
||||
return nil
|
||||
}
|
||||
return msg.Content
|
||||
}
|
||||
return map[string]any{
|
||||
"forwarded": true,
|
||||
"comment": strings.TrimSpace(comment),
|
||||
"snapshot": favoriteSnapshot(msg),
|
||||
}
|
||||
}
|
||||
|
||||
func classifyIMURLRisk(rawURL string) (decision, riskLevel, reason string) {
|
||||
if rawURL == "" || len(rawURL) > 2048 {
|
||||
return "block", "high", "invalid_url"
|
||||
@@ -8802,14 +9035,10 @@ func peerFromP2PTopic(topic string, uid types.Uid) types.Uid {
|
||||
if types.GetTopicCat(topic) != types.TopicCatP2P || uid.IsZero() {
|
||||
return types.ZeroUid
|
||||
}
|
||||
prefix := "p2p"
|
||||
body := strings.TrimPrefix(topic, prefix)
|
||||
uidLen := len(uid.String())
|
||||
if len(body) < 2*uidLen {
|
||||
left, right, err := types.ParseP2P(topic)
|
||||
if err != nil {
|
||||
return types.ZeroUid
|
||||
}
|
||||
left := types.ParseUid(body[:uidLen])
|
||||
right := types.ParseUid(body[uidLen:])
|
||||
if left == uid {
|
||||
return right
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user