Files
greeneclipse 8580c996c3 Initial commit of Descrybe v2 without local scratch artifacts.
Drop one-shot tmp/axe scripts and agent i18n scratch so the Gitea tree is deployable.
2026-08-09 22:47:43 +02:00

119 lines
3.3 KiB
Go

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
}