package errorlog import ( "context" "net/http" "strconv" "strings" "time" "chatapp3-golang/internal/model" "chatapp3-golang/internal/utils" "gorm.io/gorm" ) // ReportAgoraError persists one client-reported Agora error log. func (s *Service) ReportAgoraError(ctx context.Context, user AuthUser, req AgoraErrorReportRequest, meta AgoraErrorReportContext) (*AgoraErrorReportResponse, error) { if s == nil || s.db == nil { return nil, NewAppError(http.StatusServiceUnavailable, "error_log_unavailable", "error log service is unavailable") } roomID := req.RoomID.String() if roomID == "" { return nil, NewAppError(http.StatusBadRequest, "room_id_required", "roomId is required") } reasonType := firstNonBlank(req.ConnectionChangedReasonType, req.ReasonType, req.Reason) bannedByServer := isBannedByServer(reasonType) id, err := utils.NextID() if err != nil { return nil, err } now := time.Now() row := model.AgoraErrorLog{ ID: id, SysOrigin: normalizeSysOrigin(user.SysOrigin), UserID: user.UserID, RoomID: trimMax(roomID, 64), AgoraUID: trimMax(req.AgoraUID.String(), 64), ErrorCodeType: trimMax(req.ErrorCodeType, 128), ConnectionChangedReasonType: trimMax(reasonType, 128), BannedByServer: bannedByServer, IsTimeout: boolValue(req.Timeout, req.IsTimeout), TokenRequestSuccess: boolPtrValue(req.TokenRequestSuccess, req.TokenSuccess), NetworkType: trimMax(req.NetworkType, 64), ReqClient: trimMax(meta.ReqClient, 32), ReqAppIntel: trimMax(meta.ReqAppIntel, 255), ClientIP: trimMax(meta.ClientIP, 64), UserAgent: trimMax(meta.UserAgent, 255), RawPayload: strings.TrimSpace(meta.RawPayload), CreateTime: now, UpdateTime: now, } if err := s.db.WithContext(ctx).Create(&row).Error; err != nil { return nil, err } return &AgoraErrorReportResponse{ ID: strconv.FormatInt(id, 10), BannedByServer: bannedByServer, }, nil } // PageAgoraErrorLogs returns admin Agora error logs. func (s *Service) PageAgoraErrorLogs(ctx context.Context, query AgoraErrorLogQuery) (*AgoraErrorLogPageResponse, error) { if s == nil || s.db == nil { return nil, NewAppError(http.StatusServiceUnavailable, "error_log_unavailable", "error log service is unavailable") } cursor, limit := normalizePage(query.Cursor, query.Limit) startTime, endTime, err := parseAdminTimeRange(query.StartTime, query.EndTime) if err != nil { return nil, err } filter := query filter.Cursor = cursor filter.Limit = limit filter.StartAt = startTime filter.EndAt = endTime base := s.agoraErrorLogQuery(ctx, filter) var total int64 if err := base.Count(&total).Error; err != nil { return nil, err } var rows []model.AgoraErrorLog if err := s.agoraErrorLogQuery(ctx, filter). Order("create_time desc, id desc"). Offset((cursor - 1) * limit). Limit(limit). Find(&rows).Error; err != nil { return nil, err } return &AgoraErrorLogPageResponse{ Records: agoraErrorLogItems(rows), Total: total, Current: cursor, Size: limit, }, nil } // AgoraErrorLogQuery is the admin filter for Agora error logs. type AgoraErrorLogQuery struct { SysOrigin string UserID int64 RoomID string AgoraUID string ErrorCodeType string ReasonType string BannedOnly bool NetworkType string StartTime string EndTime string Cursor int Limit int StartAt time.Time EndAt time.Time } func (s *Service) agoraErrorLogQuery(ctx context.Context, filter AgoraErrorLogQuery) *gorm.DB { query := s.db.WithContext(ctx).Model(&model.AgoraErrorLog{}). Where("create_time >= ? AND create_time <= ?", filter.StartAt, filter.EndAt) if sysOrigin := normalizeSysOrigin(filter.SysOrigin); sysOrigin != "" { query = query.Where("sys_origin = ?", sysOrigin) } if filter.UserID > 0 { query = query.Where("user_id = ?", filter.UserID) } if roomID := strings.TrimSpace(filter.RoomID); roomID != "" { query = query.Where("room_id = ?", roomID) } if agoraUID := strings.TrimSpace(filter.AgoraUID); agoraUID != "" { query = query.Where("agora_uid = ?", agoraUID) } if codeType := strings.TrimSpace(filter.ErrorCodeType); codeType != "" { query = query.Where("error_code_type LIKE ?", "%"+codeType+"%") } if reasonType := strings.TrimSpace(filter.ReasonType); reasonType != "" { query = query.Where("connection_changed_reason_type LIKE ?", "%"+reasonType+"%") } if filter.BannedOnly { query = query.Where("banned_by_server = ?", true) } if networkType := strings.TrimSpace(filter.NetworkType); networkType != "" { query = query.Where("network_type = ?", networkType) } return query } func agoraErrorLogItems(rows []model.AgoraErrorLog) []AgoraErrorLogItem { items := make([]AgoraErrorLogItem, 0, len(rows)) for _, row := range rows { items = append(items, AgoraErrorLogItem{ ID: strconv.FormatInt(row.ID, 10), SysOrigin: row.SysOrigin, UserID: strconv.FormatInt(row.UserID, 10), RoomID: row.RoomID, AgoraUID: row.AgoraUID, ErrorCodeType: row.ErrorCodeType, ConnectionChangedReasonType: row.ConnectionChangedReasonType, BannedByServer: row.BannedByServer, IsTimeout: row.IsTimeout, TokenRequestSuccess: row.TokenRequestSuccess, NetworkType: row.NetworkType, ReqClient: row.ReqClient, ReqAppIntel: row.ReqAppIntel, ClientIP: row.ClientIP, UserAgent: row.UserAgent, RawPayload: row.RawPayload, CreateTime: formatDateTime(row.CreateTime), UpdateTime: formatDateTime(row.UpdateTime), }) } return items }