package transport import ( "errors" "io" "strconv" "strings" "github.com/gin-gonic/gin" "github.com/geo-platform/tenant-api/internal/bootstrap" "github.com/geo-platform/tenant-api/internal/shared/auth" "github.com/geo-platform/tenant-api/internal/shared/response" tenantcompliance "github.com/geo-platform/tenant-api/internal/tenant/compliance" ) type ComplianceHandler struct { svc *tenantcompliance.Service } func NewComplianceHandler(a *bootstrap.App) *ComplianceHandler { return &ComplianceHandler{ svc: tenantcompliance.NewService(a.DB, a.ConfigStore, a.Logger).WithRabbitMQ(a.RabbitMQ).WithLLM(a.LLM), } } type complianceCheckPlainRequest struct { Title string `json:"title"` Plaintext string `json:"plaintext" binding:"required"` TargetPlatforms []string `json:"target_platforms"` } type complianceCheckArticleRequest struct { ArticleVersionID *int64 `json:"article_version_id"` Title *string `json:"title"` Plaintext *string `json:"plaintext"` TargetPlatforms []string `json:"target_platforms"` TriggerSource string `json:"trigger_source"` } type complianceCreateAckRequest struct { AcknowledgedViolationIDs []int64 `json:"acknowledged_violation_ids" binding:"required"` } type complianceCreateManualReviewRequest struct { CheckRecordID int64 `json:"check_record_id" binding:"required"` Note string `json:"note" binding:"required"` } type complianceCancelManualReviewRequest struct { Reason string `json:"reason"` } func (h *ComplianceHandler) RuntimeStatus(c *gin.Context) { actor := auth.MustActor(c.Request.Context()) data, err := h.svc.RuntimeStatus(c.Request.Context(), actor.TenantID) if err != nil { response.Error(c, err) return } response.Success(c, data) } func (h *ComplianceHandler) CheckPlain(c *gin.Context) { var req complianceCheckPlainRequest if err := c.ShouldBindJSON(&req); err != nil { response.Error(c, response.ErrBadRequest(40001, "invalid_params", err.Error())) return } actor := auth.MustActor(c.Request.Context()) data, err := h.svc.CheckPlain(c.Request.Context(), tenantcompliance.CheckPlainRequest{ TenantID: actor.TenantID, ActorID: actor.UserID, Title: req.Title, Plaintext: req.Plaintext, TargetPlatforms: req.TargetPlatforms, }) if err != nil { response.Error(c, err) return } response.Success(c, data) } func (h *ComplianceHandler) CreateAck(c *gin.Context) { recordID, ok := parseComplianceInt64Param(c, "id", "check record id") if !ok { return } var req complianceCreateAckRequest if err := c.ShouldBindJSON(&req); err != nil { response.Error(c, response.ErrBadRequest(40001, "invalid_params", err.Error())) return } actor := auth.MustActor(c.Request.Context()) data, err := h.svc.CreateAck(c.Request.Context(), actor.TenantID, recordID, actor.UserID, req.AcknowledgedViolationIDs) if err != nil { response.Error(c, err) return } response.SuccessWithStatus(c, 201, data) } func (h *ComplianceHandler) CheckArticle(c *gin.Context) { articleID, ok := parseComplianceInt64Param(c, "id", "article id") if !ok { return } var req complianceCheckArticleRequest if err := c.ShouldBindJSON(&req); err != nil && !errors.Is(err, io.EOF) { response.Error(c, response.ErrBadRequest(40001, "invalid_params", err.Error())) return } actor := auth.MustActor(c.Request.Context()) data, err := h.svc.CheckArticle(c.Request.Context(), tenantcompliance.CheckArticleRequest{ TenantID: actor.TenantID, ArticleID: articleID, ArticleVersionID: req.ArticleVersionID, ActorID: actor.UserID, Title: req.Title, Plaintext: req.Plaintext, TargetPlatforms: req.TargetPlatforms, TriggerSource: req.TriggerSource, }) if err != nil { response.Error(c, err) return } response.Success(c, data) } func (h *ComplianceHandler) LatestCheck(c *gin.Context) { articleID, ok := parseComplianceInt64Param(c, "id", "article id") if !ok { return } actor := auth.MustActor(c.Request.Context()) data, err := h.svc.LatestCheck(c.Request.Context(), actor.TenantID, articleID) if err != nil { response.Error(c, err) return } response.Success(c, data) } func (h *ComplianceHandler) SubmitManualReview(c *gin.Context) { articleID, ok := parseComplianceInt64Param(c, "id", "article id") if !ok { return } var req complianceCreateManualReviewRequest if err := c.ShouldBindJSON(&req); err != nil { response.Error(c, response.ErrBadRequest(40001, "invalid_params", err.Error())) return } actor := auth.MustActor(c.Request.Context()) data, err := h.svc.SubmitManualReview(c.Request.Context(), tenantcompliance.CreateManualReviewRequest{ TenantID: actor.TenantID, ArticleID: articleID, ActorID: actor.UserID, CheckRecordID: req.CheckRecordID, Note: req.Note, }) if err != nil { response.Error(c, err) return } response.SuccessWithStatus(c, 201, data) } func (h *ComplianceHandler) LatestManualReview(c *gin.Context) { articleID, ok := parseComplianceInt64Param(c, "id", "article id") if !ok { return } versionID, ok := optionalComplianceInt64Query(c, "article_version_id") if !ok { return } actor := auth.MustActor(c.Request.Context()) data, err := h.svc.LatestManualReview(c.Request.Context(), actor.TenantID, articleID, versionID) if err != nil { response.Error(c, err) return } response.Success(c, data) } func (h *ComplianceHandler) CancelManualReview(c *gin.Context) { articleID, ok := parseComplianceInt64Param(c, "id", "article id") if !ok { return } reviewID, ok := parseComplianceInt64Param(c, "review_id", "manual review id") if !ok { return } var req complianceCancelManualReviewRequest if err := c.ShouldBindJSON(&req); err != nil && !errors.Is(err, io.EOF) { response.Error(c, response.ErrBadRequest(40001, "invalid_params", err.Error())) return } actor := auth.MustActor(c.Request.Context()) data, err := h.svc.CancelManualReview(c.Request.Context(), tenantcompliance.CancelManualReviewRequest{ TenantID: actor.TenantID, ArticleID: articleID, ActorID: actor.UserID, ReviewID: reviewID, Reason: req.Reason, }) if err != nil { response.Error(c, err) return } response.Success(c, data) } func parseComplianceInt64Param(c *gin.Context, name, label string) (int64, bool) { value, err := strconv.ParseInt(strings.TrimSpace(c.Param(name)), 10, 64) if err != nil || value <= 0 { response.Error(c, response.ErrBadRequest(40001, "invalid_id", label+" must be a positive integer")) return 0, false } return value, true } func optionalComplianceInt64Query(c *gin.Context, name string) (*int64, bool) { raw := strings.TrimSpace(c.Query(name)) if raw == "" { return nil, true } value, err := strconv.ParseInt(raw, 10, 64) if err != nil || value <= 0 { response.Error(c, response.ErrBadRequest(40001, "invalid_params", name+" must be a positive integer")) return nil, false } return &value, true }