119 lines
3.3 KiB
Go
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
|
||
|
|
}
|