From e07f34137d60e1068b4252485f48139275ba3e7d Mon Sep 17 00:00:00 2001 From: Chip Wasson Date: Fri, 5 Apr 2024 16:43:44 -0600 Subject: [PATCH] Add fococoffee endpoints --- src/app.module.ts | 2 + src/fococoffee/bindle.service.ts | 16 ++++++ src/fococoffee/fococoffee.controller.ts | 48 ++++++++++++++++ src/fococoffee/fococoffee.module.ts | 13 +++++ src/fococoffee/fococoffee.service.ts | 39 +++++++++++++ src/fococoffee/harbinger.service.ts | 16 ++++++ src/fococoffee/lima.service.ts | 16 ++++++ src/fococoffee/shopifyUtils.ts | 74 +++++++++++++++++++++++++ 8 files changed, 224 insertions(+) create mode 100644 src/fococoffee/bindle.service.ts create mode 100644 src/fococoffee/fococoffee.controller.ts create mode 100644 src/fococoffee/fococoffee.module.ts create mode 100644 src/fococoffee/fococoffee.service.ts create mode 100644 src/fococoffee/harbinger.service.ts create mode 100644 src/fococoffee/lima.service.ts create mode 100644 src/fococoffee/shopifyUtils.ts diff --git a/src/app.module.ts b/src/app.module.ts index 7703e1c..5e798a2 100644 --- a/src/app.module.ts +++ b/src/app.module.ts @@ -23,6 +23,7 @@ import { RedirectsModule } from './redirects/redirects.module'; import { FileModule } from './file/file.module'; import { FocoLiveModule } from './foco-live/foco-live.module'; import { PowModule } from './pow/pow.module'; +import { FocoCoffeeModule } from './fococoffee/fococoffee.module'; @Module({ imports: [ @@ -80,6 +81,7 @@ import { PowModule } from './pow/pow.module'; FileModule, FocoLiveModule, PowModule, + FocoCoffeeModule, ], controllers: [AppController], providers: [AppService], diff --git a/src/fococoffee/bindle.service.ts b/src/fococoffee/bindle.service.ts new file mode 100644 index 0000000..896cd13 --- /dev/null +++ b/src/fococoffee/bindle.service.ts @@ -0,0 +1,16 @@ +import { Injectable } from '@nestjs/common'; +import { ShopifyProduct, parseShopifyProduct } from './shopifyUtils'; +import axios from 'axios'; + +@Injectable() +export class BindleService { + public readonly shopifyUrl = 'https://bindlecoffee.com/products.json' + + public async fetchProducts(): Promise[]> { + const response = await axios.get(this.shopifyUrl) + if (response.status !== 200) { + throw new Error('Failed to fetch products') + } + return response.data.products.map(parseShopifyProduct); + } +} diff --git a/src/fococoffee/fococoffee.controller.ts b/src/fococoffee/fococoffee.controller.ts new file mode 100644 index 0000000..17facf2 --- /dev/null +++ b/src/fococoffee/fococoffee.controller.ts @@ -0,0 +1,48 @@ +import { Controller, Get } from '@nestjs/common'; +import { HarbingerService } from './harbinger.service'; +import { FocoCoffeeService } from './fococoffee.service'; +import { LimaService } from './lima.service'; +import { BindleService } from './bindle.service'; +import { ApiResponse, ApiTags } from '@nestjs/swagger'; + +@Controller('fococoffee') +@ApiTags('fococoffee') +export class FocoCoffeeController { + + constructor( + private readonly focoCoffeeService: FocoCoffeeService, + private readonly harbingerService: HarbingerService, + private readonly limaService: LimaService, + private readonly bindleService: BindleService + ) { } + + @Get('harbinger/products') + @ApiResponse({ status: 200, description: 'Returns the list of Harbinger products' }) + async getHarbingerProducts() { + return this.harbingerService.fetchProducts() + } + + @Get('lima/products') + @ApiResponse({ status: 200, description: 'Returns the list of Lima products' }) + async getLimaProducts() { + return this.limaService.fetchProducts() + } + + @Get('bindle/products') + @ApiResponse({ status: 200, description: 'Returns the list of Bindle products' }) + async getBindleProducts() { + return this.bindleService.fetchProducts() + } + + @Get('beans') + @ApiResponse({ status: 200, description: 'Returns the list of coffee bean products from all suppliers' }) + async getBeans() { + return this.focoCoffeeService.getBeans() + } + + @Get('products') + @ApiResponse({ status: 200, description: 'Returns the list of all products from all suppliers' }) + async getAllProducts() { + return this.focoCoffeeService.getAllProducts() + } +} diff --git a/src/fococoffee/fococoffee.module.ts b/src/fococoffee/fococoffee.module.ts new file mode 100644 index 0000000..b4c3ce2 --- /dev/null +++ b/src/fococoffee/fococoffee.module.ts @@ -0,0 +1,13 @@ +import { Module } from '@nestjs/common'; +import { FocoCoffeeController } from './fococoffee.controller'; +import { FocoCoffeeService } from './fococoffee.service'; +import { HarbingerService } from './harbinger.service'; +import { LimaService } from './lima.service'; +import { BindleService } from './bindle.service'; + +@Module({ + imports: [], + controllers: [FocoCoffeeController], + providers: [FocoCoffeeService, HarbingerService, LimaService, BindleService] +}) +export class FocoCoffeeModule { } diff --git a/src/fococoffee/fococoffee.service.ts b/src/fococoffee/fococoffee.service.ts new file mode 100644 index 0000000..297a058 --- /dev/null +++ b/src/fococoffee/fococoffee.service.ts @@ -0,0 +1,39 @@ +import { Injectable } from '@nestjs/common'; +import { LimaService } from './lima.service'; +import { HarbingerService } from './harbinger.service'; +import { BindleService } from './bindle.service'; + +@Injectable() +export class FocoCoffeeService { + constructor( + private readonly limaService: LimaService, + private readonly harbingerService: HarbingerService, + private readonly bindleService: BindleService + ) { } + + public async getAllProducts() { + const limaProducts = await this.limaService.fetchProducts() + const harbingerProducts = await this.harbingerService.fetchProducts() + const bindleProducts = await this.bindleService.fetchProducts() + return { + lima: limaProducts, + harbinger: harbingerProducts, + bindle: bindleProducts + } + } + + public async getBeans() { + const limaBeans = await this.limaService.fetchProducts() + const harbingerBeans = await this.harbingerService.fetchProducts() + const bindleBeans = await this.bindleService.fetchProducts() + return { + lima: limaBeans + .filter(p => p.tags.includes("Coffee")) + .filter(p => !p.title.toLocaleLowerCase().includes("subscription")) + .filter(p => p.handle !== "lima-sample-pack") + , + harbinger: harbingerBeans.filter(p => p.product_type === "Packaged Coffee"), + bindle: bindleBeans.filter(p => p.product_type === "Coffee, Roasted") + } + } +} diff --git a/src/fococoffee/harbinger.service.ts b/src/fococoffee/harbinger.service.ts new file mode 100644 index 0000000..2f72398 --- /dev/null +++ b/src/fococoffee/harbinger.service.ts @@ -0,0 +1,16 @@ +import { Injectable } from '@nestjs/common'; +import { ShopifyProduct, parseShopifyProduct } from './shopifyUtils'; +import axios from 'axios'; + +@Injectable() +export class HarbingerService { + public readonly shopifyUrl = 'https://harbingercoffee.com/products.json' + + public async fetchProducts(): Promise[]> { + const response = await axios.get(this.shopifyUrl) + if (response.status !== 200) { + throw new Error('Failed to fetch products') + } + return response.data.products.map(parseShopifyProduct); + } +} diff --git a/src/fococoffee/lima.service.ts b/src/fococoffee/lima.service.ts new file mode 100644 index 0000000..7ef59e1 --- /dev/null +++ b/src/fococoffee/lima.service.ts @@ -0,0 +1,16 @@ +import { Injectable } from '@nestjs/common'; +import { ShopifyProduct, parseShopifyProduct } from './shopifyUtils'; +import axios from 'axios'; + +@Injectable() +export class LimaService { + public readonly shopifyUrl = 'https://www.limacoffeeroasters.com/products.json' + + public async fetchProducts(): Promise[]> { + const response = await axios.get(this.shopifyUrl) + if (response.status !== 200) { + throw new Error('Failed to fetch products') + } + return response.data.products.map(parseShopifyProduct); + } +} diff --git a/src/fococoffee/shopifyUtils.ts b/src/fococoffee/shopifyUtils.ts new file mode 100644 index 0000000..63780c1 --- /dev/null +++ b/src/fococoffee/shopifyUtils.ts @@ -0,0 +1,74 @@ +import { pipe } from "ramda"; + +export interface ShopifyVariant { + id: number; + title: string; + price: string; + sku: string; + position: number; + option1?: string; + option2?: string; + option3?: string; + taxable: boolean; + grams: number; + available: boolean; + created_at: T; + updated_at: T; +} + +export interface ShopifyImage { + id: number; + created_at: T; + position: number; + updated_at: T; + product_id: number; + variant_ids: number[]; + src: string; + width: number; + height: number; +} + +export interface ShopifyOption { + name: string; + position: number; + values: string[]; +} + +export interface ShopifyProduct { + id: number; + title: string; + handle: string; + body_html: string; + published_at: T; + created_at: T; + vendor: string; + product_type: string; + updated_at: T; + tags: string[]; + variants: ShopifyVariant[]; + images: ShopifyImage[]; + options: ShopifyOption[]; +} + + +export const parseShopifyProductDates = (productIn: ShopifyProduct): ShopifyProduct => { + return { + ...productIn, + published_at: new Date(productIn.published_at), + created_at: new Date(productIn.created_at), + updated_at: new Date(productIn.updated_at), + variants: productIn.variants.map((variant) => ({ + ...variant, + created_at: new Date(variant.created_at), + updated_at: new Date(variant.updated_at), + })), + images: productIn.images.map((image) => ({ + ...image, + created_at: new Date(image.created_at), + updated_at: new Date(image.updated_at), + })), + }; +} + + +export const parseShopifyProduct = pipe(parseShopifyProductDates);