All files / src/app/extensions/rating/services/reviews reviews.service.ts

78.37% Statements 29/37
66.66% Branches 8/12
71.42% Functions 10/14
87.09% Lines 27/31

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 1103x 3x 3x 3x 3x     3x 3x     3x       3x   9x 9x     9x                     6x 1x     5x 5x         5x   4x   4x                 3x                                       1x     1x       1x       1x                     1x     1x       1x          
import { HttpParams } from '@angular/common/http';
import { Injectable } from '@angular/core';
import { Store, select } from '@ngrx/store';
import { Observable, of, throwError } from 'rxjs';
import { map, switchMap, take } from 'rxjs/operators';
 
import { Link } from 'ish-core/models/link/link.model';
import { ApiService, unpackEnvelope } from 'ish-core/services/api/api.service';
import { getLoggedInCustomer } from 'ish-core/store/customer/user';
 
import { ProductReview, ProductReviewCreationType } from '../../models/product-reviews/product-review.model';
import { ProductReviewsMapper } from '../../models/product-reviews/product-reviews.mapper';
import { ProductReviews } from '../../models/product-reviews/product-reviews.model';
 
@Injectable({ providedIn: 'root' })
export class ReviewsService {
  constructor(
    private apiService: ApiService,
    private store: Store
  ) {}
 
  private currentCustomer$ = this.store.pipe(select(getLoggedInCustomer), take(1));
 
  /**
   * Fetches public reviews for a product. For logged-in users, additionally fetches
   * their own reviews (flagged with `own: true`) which may include non-public entries
   * pending approval. The own-reviews request bypasses caching to reflect latest state.
   *
   * @param sku  The product SKU.
   * @returns    The combined product reviews.
   */
  getProductReviews(sku: string): Observable<ProductReviews> {
    if (!sku) {
      return throwError(() => new Error('getProductReviews() called without sku'));
    }
 
    const params = new HttpParams().set('own', 'false');
    return this.apiService
      .get(`products/${this.apiService.encodeResourceId(sku)}/reviews`, { sendSPGID: true, params })
      .pipe(
        unpackEnvelope<Link>(),
        this.apiService.resolveLinks<ProductReview>(),
        map(reviews => ProductReviewsMapper.fromData(sku, reviews)),
        switchMap(allReviews =>
          this.currentCustomer$.pipe(
            switchMap(customer =>
              customer
                ? this.apiService
                    .get(`products/${this.apiService.encodeResourceId(sku)}/reviews`, {
                      sendSPGID: true,
                      params: new HttpParams().set('own', 'true'),
                    })
                    .pipe(
                      unpackEnvelope<Link>(),
                      this.apiService.resolveLinks<ProductReview>(),
                      map(ownReviews => ({
                        ...allReviews,
                        reviews: [...allReviews.reviews, ...ProductReviewsMapper.fromData(sku, ownReviews).reviews],
                      }))
                    )
                : of(allReviews)
            )
          )
        )
      );
  }
 
  /**
   * Creates a user review for a given product, the user name is always sent to the server.
   *
   * @param sku     The product sku.
   * @param review  The review and rating for the product.
   * @returns       The created product review.
   */
  createProductReview(sku: string, review: ProductReviewCreationType) {
    Iif (!sku) {
      return throwError(() => new Error('createProductReview() called without sku'));
    }
    Iif (!review) {
      return throwError(() => new Error('createProductReview() called without review'));
    }
 
    return this.apiService
      .post(`products/${this.apiService.encodeResourceId(sku)}/reviews`, { ...review, showAuthorNameFlag: true })
      .pipe(
        this.apiService.resolveLink<ProductReview>(),
        map(data => ProductReviewsMapper.fromData(sku, [{ ...data, own: true }]))
      );
  }
 
  /**
   * Deletes a review of the current user for a given product.
   *
   * @param sku     The product sku.
   * @param review  The review id.
   */
  deleteProductReview(sku: string, reviewId: string) {
    Iif (!sku) {
      return throwError(() => new Error('deleteProductReview() called without sku'));
    }
    Iif (!reviewId) {
      return throwError(() => new Error('deleteProductReview() called without reviewId'));
    }
 
    return this.apiService.delete(
      `products/${this.apiService.encodeResourceId(sku)}/reviews/${this.apiService.encodeResourceId(reviewId)}`
    );
  }
}