import { Injectable } from '@angular/core'; import { Observable, Subject } from 'rxjs'; import { HttpClient } from '@angular/common/http'; import { Cacheable } from 'ts-cacheable'; import { delay, filter, map, mergeMap, repeatWhen, take, tap } from 'rxjs/operators'; import { CatalogPackage } from '../models/package'; import { CatalogImage } from '../models/image'; const cacheBuster$ = new Subject(); const imagesCacheBuster$ = new Subject(); @Injectable({ providedIn: 'root' }) export class CatalogService { // ---------------------------------------------------------------------------------------------------------------- constructor(private readonly httpClient: HttpClient) { } // ---------------------------------------------------------------------------------------------------------------- @Cacheable({ cacheBusterObserver: cacheBuster$ }) getDataCenters(): Observable { return this.httpClient.get(`/api/my/datacenters`); } // ---------------------------------------------------------------------------------------------------------------- getServices(): Observable { return this.httpClient.get(`/api/my/services`); } // ---------------------------------------------------------------------------------------------------------------- @Cacheable({ cacheBusterObserver: cacheBuster$ }) getPackages(): Observable { return this.httpClient.get(`/api/my/packages`) .pipe(mergeMap(packages => { return this.httpClient.get(`./assets/data/packages.json`).pipe(map(prices => { packages.forEach(x => x.price = prices[x.id]) return packages; })) })); } // ---------------------------------------------------------------------------------------------------------------- @Cacheable({ cacheBusterObserver: cacheBuster$ }) getPackage(packageId: string): Observable { return this.httpClient.get(`/api/my/packages/${packageId}`) .pipe(mergeMap(pkg => { return this.httpClient.get(`./assets/data/packages.json`).pipe(map(prices => { pkg.price = prices[pkg.id]; return pkg; })) })); } // ---------------------------------------------------------------------------------------------------------------- @Cacheable({ cacheBusterObserver: imagesCacheBuster$ }) getImages(allStates = false): Observable { return this.httpClient.get(`/api/my/images?${allStates ? 'state=all' : ''}`) .pipe(mergeMap(images => { return this.httpClient.get(`./assets/data/images.json`).pipe(map(prices => { images.forEach(x => x.price = prices[x.id]) return images; })) })); } // ---------------------------------------------------------------------------------------------------------------- @Cacheable({ cacheBusterObserver: imagesCacheBuster$ }) getImage(id: string): Observable { return this.httpClient.get(`/api/my/images/${id}`) .pipe(mergeMap(image => { return this.httpClient.get(`./assets/data/images.json`).pipe(map(prices => { image.price = prices[image.id]; return image; })) })); } // ---------------------------------------------------------------------------------------------------------------- getImageUntilExpectedState(image: CatalogImage, expectedStates: string[], maxRetries = 30): Observable { // Keep polling the image until it reaches the expected state return this.httpClient.get(`/api/my/images/${image.id}`) .pipe( tap(x => image.state = x.state), repeatWhen(x => { let retries = 0; return x.pipe(delay(3000), map(y => { if (retries++ === maxRetries) throw { error: `Failed to retrieve the current status for image "${image.name}"` }; return y; })); }), filter(x => expectedStates.includes(x.state)), take(1) // needed to stop the repeatWhen loop ); } // ---------------------------------------------------------------------------------------------------------------- createImage(machineId: string, name: string, version: string, description?: string, homepage?: string, eula?: string, acl?: string, tags?: string): Observable { return this.httpClient.post(`/api/my/images`, { machine: machineId, name, version, description, homepage, eula, acl, tags }) .pipe(tap(() => imagesCacheBuster$.next())); } // ---------------------------------------------------------------------------------------------------------------- importImage(sourceDataCenterId: string, imageId: string): Observable { // Copy the image with id from the source datacenter into this datacenter return this.httpClient.post(`/api/my/images?action=import-from-datacenter`, { datacenter: sourceDataCenterId, image: imageId }) .pipe(tap(() => imagesCacheBuster$.next())); } // ---------------------------------------------------------------------------------------------------------------- editImage(imageId: string, name: string, version: string, description?: string, homepage?: string, eula?: string, acl?: string, tags?: string): Observable { return this.httpClient.post(`/api/my/images/${imageId}?action=update`, { name, version, description, homepage, eula, acl, tags }) .pipe(tap(() => imagesCacheBuster$.next())); } // ---------------------------------------------------------------------------------------------------------------- cloneImage(imageId: string): Observable { // https://apidocs.Spearhead.com/cloudapi/#CloneImage return this.httpClient.post(`/api/my/images/${imageId}?action=clone`, {}) .pipe(tap(() => imagesCacheBuster$.next())); } // ---------------------------------------------------------------------------------------------------------------- deleteImage(id: string): Observable { // Note: Caller must be the owner of the image return this.httpClient.delete(`/api/my/images/${id}`) .pipe(tap(() => imagesCacheBuster$.next())); } }