package audit import ( "errors" "fmt" "strconv" "strings" "time" "git.apinb.com/bsm-sdk/core/infra" "git.apinb.com/ops/logs/internal/impl" "git.apinb.com/ops/logs/internal/models" "github.com/gin-gonic/gin" ) func ListAuditLogs(ctx *gin.Context) { page, size := pageAndSize(ctx.DefaultQuery("page", "1"), ctx.DefaultQuery("page_size", "50")) q := impl.DBService.Model(&models.AuditLog{}) if v := strings.TrimSpace(ctx.Query("source_service")); v != "" { q = q.Where("source_service = ?", v) } if v := strings.TrimSpace(ctx.Query("actor_id")); v != "" { q = q.Where("actor_id = ?", v) } if v := strings.TrimSpace(ctx.Query("action")); v != "" { q = q.Where("action = ?", v) } if v := strings.TrimSpace(ctx.Query("object_type")); v != "" { q = q.Where("object_type = ?", v) } if v := strings.TrimSpace(ctx.Query("object_id")); v != "" { q = q.Where("object_id = ?", v) } if v := strings.TrimSpace(ctx.Query("operation_risk")); v != "" { q = q.Where("operation_risk = ?", v) } if v := strings.TrimSpace(ctx.Query("result")); v != "" { q = q.Where("result = ?", v) } var total int64 _ = q.Count(&total).Error var rows []models.AuditLog if err := q.Order("id desc").Offset((page - 1) * size).Limit(size).Find(&rows).Error; err != nil { infra.Response.Error(ctx, err) return } infra.Response.Success(ctx, gin.H{"total": total, "page": page, "page_size": size, "items": rows}) } func CreateAuditLog(ctx *gin.Context) { var req Record if err := ctx.ShouldBindJSON(&req); err != nil { infra.Response.Error(ctx, err) return } req.ClientIP = firstNonEmpty(req.ClientIP, ctx.ClientIP()) req.RequestMethod = firstNonEmpty(req.RequestMethod, ctx.Request.Method) req.RequestPath = firstNonEmpty(req.RequestPath, ctx.FullPath()) row, err := SaveRecord(req) if err != nil { infra.Response.Error(ctx, err) return } infra.Response.Success(ctx, row) } func ListApprovals(ctx *gin.Context) { page, size := pageAndSize(ctx.DefaultQuery("page", "1"), ctx.DefaultQuery("page_size", "50")) q := impl.DBService.Model(&models.DangerousOperationApproval{}) if v := strings.TrimSpace(ctx.Query("source_service")); v != "" { q = q.Where("source_service = ?", v) } if v := strings.TrimSpace(ctx.Query("status")); v != "" { q = q.Where("status = ?", v) } if v := strings.TrimSpace(ctx.Query("requester_id")); v != "" { q = q.Where("requester_id = ?", v) } if v := strings.TrimSpace(ctx.Query("object_type")); v != "" { q = q.Where("object_type = ?", v) } var total int64 _ = q.Count(&total).Error var rows []models.DangerousOperationApproval if err := q.Order("id desc").Offset((page - 1) * size).Limit(size).Find(&rows).Error; err != nil { infra.Response.Error(ctx, err) return } infra.Response.Success(ctx, gin.H{"total": total, "page": page, "page_size": size, "items": rows}) } func CreateApproval(ctx *gin.Context) { var req ApprovalRequest if err := ctx.ShouldBindJSON(&req); err != nil { infra.Response.Error(ctx, err) return } req = NormalizeApproval(req) if req.RequestID == "" { req.RequestID = fmt.Sprintf("apr-%d", time.Now().UnixNano()) } if err := ValidateApprovalRequest(req); err != nil { infra.Response.Error(ctx, err) return } row := models.DangerousOperationApproval{ RequestID: req.RequestID, SourceService: req.SourceService, Action: req.Action, ObjectType: req.ObjectType, ObjectID: req.ObjectID, RequesterID: req.RequesterID, RequesterName: req.RequesterName, Reason: req.Reason, BeforeJSON: req.BeforeJSON, AfterJSON: req.AfterJSON, Status: ApprovalPending, } if err := impl.DBService.Create(&row).Error; err != nil { infra.Response.Error(ctx, err) return } infra.Response.Success(ctx, row) } func ApproveApproval(ctx *gin.Context) { reviewApproval(ctx, ApprovalApproved) } func RejectApproval(ctx *gin.Context) { reviewApproval(ctx, ApprovalRejected) } func reviewApproval(ctx *gin.Context, next string) { id, err := parseUintParam(ctx, "id") if err != nil { infra.Response.Error(ctx, err) return } var body struct { ReviewerID string `json:"reviewer_id"` ReviewerName string `json:"reviewer_name"` ReviewComment string `json:"review_comment"` } if err := ctx.ShouldBindJSON(&body); err != nil { infra.Response.Error(ctx, err) return } var row models.DangerousOperationApproval if err := impl.DBService.First(&row, id).Error; err != nil { infra.Response.Error(ctx, err) return } req := ApprovalRequest{ RequestID: row.RequestID, SourceService: row.SourceService, Action: row.Action, ObjectType: row.ObjectType, ObjectID: row.ObjectID, RequesterID: row.RequesterID, Status: row.Status, } nextReq, err := Transition(req, next, body.ReviewerID, body.ReviewComment) if err != nil { infra.Response.Error(ctx, err) return } row.Status = nextReq.Status row.ReviewerID = nextReq.ReviewerID row.ReviewerName = strings.TrimSpace(body.ReviewerName) row.ReviewComment = nextReq.ReviewComment row.ReviewedAt = nextReq.ReviewedAt if err := impl.DBService.Save(&row).Error; err != nil { infra.Response.Error(ctx, err) return } infra.Response.Success(ctx, row) } func parseUintParam(ctx *gin.Context, name string) (uint, error) { v, err := strconv.ParseUint(ctx.Param(name), 10, 32) if err != nil || v == 0 { return 0, errors.New("invalid id") } return uint(v), nil } func pageAndSize(pageText, sizeText string) (int, int) { page, _ := strconv.Atoi(pageText) size, _ := strconv.Atoi(sizeText) if page < 1 { page = 1 } if size < 1 || size > 500 { size = 50 } return page, size } func firstNonEmpty(values ...string) string { for _, value := range values { if strings.TrimSpace(value) != "" { return strings.TrimSpace(value) } } return "" }