package woocommerce import ( "context" "encoding/json" "strings" "time" "github.com/google/uuid" ) // SyncReviews pulls Woo product reviews in pages and upserts company-scoped rows. func (s *Service) SyncReviews(ctx context.Context, companyID uuid.UUID) (ReviewsSyncSummary, error) { client, _, opt, err := s.clientFor(ctx, companyID) if err != nil { return ReviewsSyncSummary{}, err } limit := opt.ReviewsSyncLimit if limit <= 0 || limit > maxReviewsSyncLimit { limit = defaultReviewsSyncLimit } pageSize := defaultPullPageSize summary := ReviewsSyncSummary{} for page := 1; summary.Fetched < limit; page++ { remaining := limit - summary.Fetched perPage := pageSize if remaining < perPage { perPage = remaining } reviews, _, err := client.ListProductReviewsPage(ctx, page, perPage) if err != nil { opt.PendingReviewsSync = false opt.LastReviewsSyncStatus = "failed" opt.LastReviewsSyncError = truncateErr(err) _ = s.saveSyncOptions(ctx, companyID, opt) return summary, err } if len(reviews) == 0 { break } summary.Pages++ summary.Fetched += len(reviews) for _, review := range reviews { if err := s.upsertReview(ctx, companyID, review); err != nil { summary.Failed++ continue } summary.Upserted++ } if len(reviews) < perPage { break } } opt.PendingReviewsSync = false if summary.Failed > 0 && summary.Upserted == 0 { opt.LastReviewsSyncStatus = "failed" opt.LastReviewsSyncError = "all review upserts failed" } else if summary.Failed > 0 { opt.LastReviewsSyncStatus = "partial" opt.LastReviewsSyncError = "some review upserts failed" } else { opt.LastReviewsSyncStatus = "success" opt.LastReviewsSyncError = "" } now := time.Now().UTC() opt.LastReviewsSyncedAt = &now if err := s.saveSyncOptions(ctx, companyID, opt); err != nil { return summary, err } return summary, nil } func (s *Service) upsertReview(ctx context.Context, companyID uuid.UUID, review ProductReview) error { payload := review.Raw if len(payload) == 0 { b, err := json.Marshal(review) if err != nil { return err } payload = b } reviewedAt := parseWooTime(review.DateCreatedGMT, review.DateCreated) email := strings.TrimSpace(strings.ToLower(review.ReviewerEmail)) _, err := s.Pool.Exec(ctx, ` INSERT INTO product_reviews ( company_id, external_id, product_id, product_name, status, reviewer, reviewer_email, rating, review, reviewed_at, payload, synced_at, updated_at ) VALUES ( $1, $2, NULLIF($3, 0), $4, $5, $6, $7, $8, $9, $10, $11::jsonb, now(), now() ) ON CONFLICT (company_id, external_id) DO UPDATE SET product_id = EXCLUDED.product_id, product_name = EXCLUDED.product_name, status = EXCLUDED.status, reviewer = EXCLUDED.reviewer, reviewer_email = EXCLUDED.reviewer_email, rating = EXCLUDED.rating, review = EXCLUDED.review, reviewed_at = EXCLUDED.reviewed_at, payload = EXCLUDED.payload, synced_at = now(), updated_at = now()`, companyID, review.ID, review.ProductID, strings.TrimSpace(review.ProductName), strings.TrimSpace(review.Status), strings.TrimSpace(review.Reviewer), email, nullableInt(review.Rating), strings.TrimSpace(review.Review), reviewedAt, payload, ) return err } func nullableInt(v int) *int { if v == 0 { return nil } return &v }