package transport import ( "strconv" "strings" "github.com/gin-gonic/gin" "github.com/geo-platform/tenant-api/internal/ops/app" "github.com/geo-platform/tenant-api/internal/shared/response" ) type complianceDecisionRequest struct { Reason string `json:"reason" binding:"required"` } type complianceMasterSwitchRequest struct { Enabled bool `json:"enabled"` } type complianceRollbackRequest struct { Version int `json:"version" binding:"required"` } func listComplianceDictionariesHandler(svc *app.ComplianceService) gin.HandlerFunc { return func(c *gin.Context) { data, err := svc.ListDictionaries(c.Request.Context()) if err != nil { response.Error(c, err) return } response.Success(c, data) } } func createComplianceDictionaryHandler(svc *app.ComplianceService) gin.HandlerFunc { return func(c *gin.Context) { var body app.UpsertComplianceDictionaryRequest if err := c.ShouldBindJSON(&body); err != nil { response.Error(c, response.ErrBadRequest(40000, "invalid_payload", err.Error())) return } data, err := svc.CreateDictionary(c.Request.Context(), body) if err != nil { response.Error(c, err) return } response.Success(c, data) } } func updateComplianceDictionaryHandler(svc *app.ComplianceService) gin.HandlerFunc { return func(c *gin.Context) { id, err := parseIDParam(c) if err != nil { response.Error(c, err) return } var body app.UpsertComplianceDictionaryRequest if err := c.ShouldBindJSON(&body); err != nil { response.Error(c, response.ErrBadRequest(40000, "invalid_payload", err.Error())) return } data, err := svc.UpdateDictionary(c.Request.Context(), id, body) if err != nil { response.Error(c, err) return } response.Success(c, data) } } func deleteComplianceDictionaryHandler(svc *app.ComplianceService) gin.HandlerFunc { return func(c *gin.Context) { id, err := parseIDParam(c) if err != nil { response.Error(c, err) return } if err := svc.DeleteDictionary(c.Request.Context(), id); err != nil { response.Error(c, err) return } response.Success(c, gin.H{"deleted": true}) } } func listComplianceTermsHandler(svc *app.ComplianceService) gin.HandlerFunc { return func(c *gin.Context) { id, err := parseIDParam(c) if err != nil { response.Error(c, err) return } data, err := svc.ListTerms(c.Request.Context(), id) if err != nil { response.Error(c, err) return } response.Success(c, data) } } func createComplianceTermHandler(svc *app.ComplianceService) gin.HandlerFunc { return func(c *gin.Context) { id, err := parseIDParam(c) if err != nil { response.Error(c, err) return } var body app.CreateComplianceDictionaryTermRequest if err := c.ShouldBindJSON(&body); err != nil { response.Error(c, response.ErrBadRequest(40000, "invalid_payload", err.Error())) return } data, err := svc.CreateTerm(c.Request.Context(), id, body) if err != nil { response.Error(c, err) return } response.Success(c, data) } } func deleteComplianceTermHandler(svc *app.ComplianceService) gin.HandlerFunc { return func(c *gin.Context) { dictID, err := parseIDParam(c) if err != nil { response.Error(c, err) return } termID, err := parsePositiveInt64(c.Param("term_id")) if err != nil { response.Error(c, response.ErrBadRequest(40001, "invalid_id", "无效的词条 ID")) return } if err := svc.DeleteTerm(c.Request.Context(), dictID, termID); err != nil { response.Error(c, err) return } response.Success(c, gin.H{"deleted": true}) } } func batchImportComplianceTermsHandler(svc *app.ComplianceService) gin.HandlerFunc { return func(c *gin.Context) { id, err := parseIDParam(c) if err != nil { response.Error(c, err) return } var body app.BatchImportComplianceTermsRequest if err := c.ShouldBindJSON(&body); err != nil { response.Error(c, response.ErrBadRequest(40000, "invalid_payload", err.Error())) return } data, err := svc.BatchImportTerms(c.Request.Context(), id, body) if err != nil { response.Error(c, err) return } response.Success(c, data) } } func publishComplianceDictionaryHandler(svc *app.ComplianceService) gin.HandlerFunc { return func(c *gin.Context) { id, err := parseIDParam(c) if err != nil { response.Error(c, err) return } data, err := svc.PublishDictionary(c.Request.Context(), id) if err != nil { response.Error(c, err) return } response.Success(c, data) } } func rollbackComplianceDictionaryHandler(svc *app.ComplianceService) gin.HandlerFunc { return func(c *gin.Context) { id, err := parseIDParam(c) if err != nil { response.Error(c, err) return } var body complianceRollbackRequest if err := c.ShouldBindJSON(&body); err != nil { response.Error(c, response.ErrBadRequest(40000, "invalid_payload", err.Error())) return } data, err := svc.RollbackDictionary(c.Request.Context(), id, body.Version) if err != nil { response.Error(c, err) return } response.Success(c, data) } } func getGlobalCompliancePolicyHandler(svc *app.ComplianceService) gin.HandlerFunc { return func(c *gin.Context) { data, err := svc.GetGlobalPolicy(c.Request.Context()) if err != nil { response.Error(c, err) return } response.Success(c, data) } } func updateGlobalCompliancePolicyHandler(svc *app.ComplianceService) gin.HandlerFunc { return func(c *gin.Context) { var body app.UpdateCompliancePolicyRequest if err := c.ShouldBindJSON(&body); err != nil { response.Error(c, response.ErrBadRequest(40000, "invalid_payload", err.Error())) return } data, err := svc.UpdateGlobalPolicy(c.Request.Context(), body) if err != nil { response.Error(c, err) return } response.Success(c, data) } } func setGlobalComplianceMasterSwitchHandler(svc *app.ComplianceService) gin.HandlerFunc { return func(c *gin.Context) { var body complianceMasterSwitchRequest if err := c.ShouldBindJSON(&body); err != nil { response.Error(c, response.ErrBadRequest(40000, "invalid_payload", err.Error())) return } data, err := svc.SetGlobalMasterSwitch(c.Request.Context(), body.Enabled) if err != nil { response.Error(c, err) return } response.Success(c, data) } } func listComplianceManualReviewsHandler(svc *app.ComplianceService) gin.HandlerFunc { return func(c *gin.Context) { page, _ := strconv.Atoi(c.DefaultQuery("page", "1")) pageSize, _ := strconv.Atoi(c.DefaultQuery("page_size", c.DefaultQuery("size", "50"))) var tenantID *int64 if raw := c.Query("tenant_id"); raw != "" { id, err := parsePositiveInt64(raw) if err != nil { response.Error(c, response.ErrBadRequest(40001, "invalid_id", "无效的租户 ID")) return } tenantID = &id } data, err := svc.ListManualReviews(c.Request.Context(), app.ComplianceManualReviewListRequest{ Status: c.Query("status"), TenantID: tenantID, Page: page, PageSize: pageSize, }) if err != nil { response.Error(c, err) return } response.Success(c, data) } } func getComplianceManualReviewHandler(svc *app.ComplianceService) gin.HandlerFunc { return func(c *gin.Context) { id, err := parseIDParam(c) if err != nil { response.Error(c, err) return } data, err := svc.GetManualReview(c.Request.Context(), id) if err != nil { response.Error(c, err) return } response.Success(c, data) } } func approveComplianceManualReviewHandler(svc *app.ComplianceService) gin.HandlerFunc { return requireComplianceReviewPermission(decideComplianceManualReviewHandler(svc, true)) } func rejectComplianceManualReviewHandler(svc *app.ComplianceService) gin.HandlerFunc { return requireComplianceReviewPermission(decideComplianceManualReviewHandler(svc, false)) } func decideComplianceManualReviewHandler(svc *app.ComplianceService, approve bool) gin.HandlerFunc { return func(c *gin.Context) { id, err := parseIDParam(c) if err != nil { response.Error(c, err) return } var body complianceDecisionRequest if err := c.ShouldBindJSON(&body); err != nil { response.Error(c, response.ErrBadRequest(40000, "invalid_payload", err.Error())) return } var data any if approve { data, err = svc.ApproveManualReview(c.Request.Context(), id, body.Reason) } else { data, err = svc.RejectManualReview(c.Request.Context(), id, body.Reason) } if err != nil { response.Error(c, err) return } response.Success(c, data) } } func listComplianceRecordsHandler(svc *app.ComplianceService) gin.HandlerFunc { return func(c *gin.Context) { page, _ := strconv.Atoi(c.DefaultQuery("page", "1")) pageSize, _ := strconv.Atoi(c.DefaultQuery("page_size", c.DefaultQuery("size", "50"))) var tenantID *int64 if raw := c.Query("tenant_id"); raw != "" { id, err := parsePositiveInt64(raw) if err != nil { response.Error(c, response.ErrBadRequest(40001, "invalid_id", "无效的租户 ID")) return } tenantID = &id } var articleID *int64 if raw := c.Query("article_id"); raw != "" { id, err := parsePositiveInt64(raw) if err != nil { response.Error(c, response.ErrBadRequest(40001, "invalid_id", "无效的文章 ID")) return } articleID = &id } data, err := svc.ListRecords(c.Request.Context(), app.ComplianceRecordListRequest{ TenantID: tenantID, ArticleID: articleID, Decision: c.Query("decision"), Page: page, PageSize: pageSize, }) if err != nil { response.Error(c, err) return } response.Success(c, data) } } func complianceStatsHandler(svc *app.ComplianceService) gin.HandlerFunc { return func(c *gin.Context) { data, err := svc.Stats(c.Request.Context()) if err != nil { response.Error(c, err) return } response.Success(c, data) } } func requireComplianceReviewPermission(next gin.HandlerFunc) gin.HandlerFunc { return func(c *gin.Context) { actor := actorFromGin(c) if actor == nil || !strings.EqualFold(strings.TrimSpace(actor.Role), "admin") { response.Error(c, response.ErrForbidden(40331, "compliance_review_forbidden", "operator is not allowed to review compliance requests")) return } next(c) } } func parsePositiveInt64(raw string) (int64, error) { id, err := strconv.ParseInt(raw, 10, 64) if err != nil || id <= 0 { return 0, strconv.ErrSyntax } return id, nil }