import { Controller, Get, Request, UseGuards, Header } from "@nestjs/common"; import { RateLimitGuard, RateLimit } from "@bff/core/rate-limiting/index.js"; import type { RequestWithUser } from "@bff/modules/auth/auth.types.js"; import { parseInternetCatalog, parseSimCatalog, type InternetAddonCatalogItem, type InternetInstallationCatalogItem, type InternetPlanCatalogItem, type SimActivationFeeCatalogItem, type SimCatalogCollection, type SimCatalogProduct, type VpnCatalogProduct, } from "@customer-portal/domain/catalog"; import { InternetCatalogService } from "./services/internet-catalog.service.js"; import { SimCatalogService } from "./services/sim-catalog.service.js"; import { VpnCatalogService } from "./services/vpn-catalog.service.js"; import { SalesforceReadThrottleGuard } from "@bff/integrations/salesforce/guards/salesforce-read-throttle.guard.js"; @Controller("catalog") @UseGuards(SalesforceReadThrottleGuard, RateLimitGuard) export class CatalogController { constructor( private internetCatalog: InternetCatalogService, private simCatalog: SimCatalogService, private vpnCatalog: VpnCatalogService ) {} @Get("internet/plans") @RateLimit({ limit: 20, ttl: 60 }) // 20 requests per minute @Header("Cache-Control", "private, no-store") // Personalised responses: avoid browser caching (realtime invalidation relies on refetch) async getInternetPlans(@Request() req: RequestWithUser): Promise<{ plans: InternetPlanCatalogItem[]; installations: InternetInstallationCatalogItem[]; addons: InternetAddonCatalogItem[]; }> { const userId = req.user?.id; if (!userId) { const catalog = await this.internetCatalog.getCatalogData(); return parseInternetCatalog(catalog); } const [plans, installations, addons] = await Promise.all([ this.internetCatalog.getPlansForUser(userId), this.internetCatalog.getInstallations(), this.internetCatalog.getAddons(), ]); return parseInternetCatalog({ plans, installations, addons }); } @Get("internet/addons") @Header("Cache-Control", "public, max-age=300, s-maxage=300") // 5 minutes async getInternetAddons(): Promise { return this.internetCatalog.getAddons(); } @Get("internet/installations") @Header("Cache-Control", "public, max-age=300, s-maxage=300") // 5 minutes async getInternetInstallations(): Promise { return this.internetCatalog.getInstallations(); } @Get("sim/plans") @RateLimit({ limit: 20, ttl: 60 }) // 20 requests per minute @Header("Cache-Control", "private, no-store") // Personalised responses: avoid browser caching (realtime invalidation relies on refetch) async getSimCatalogData(@Request() req: RequestWithUser): Promise { const userId = req.user?.id; if (!userId) { const catalog = await this.simCatalog.getCatalogData(); return parseSimCatalog({ ...catalog, plans: catalog.plans.filter(plan => !plan.simHasFamilyDiscount), }); } const [plans, activationFees, addons] = await Promise.all([ this.simCatalog.getPlansForUser(userId), this.simCatalog.getActivationFees(), this.simCatalog.getAddons(), ]); return parseSimCatalog({ plans, activationFees, addons }); } @Get("sim/activation-fees") @Header("Cache-Control", "public, max-age=300, s-maxage=300") // 5 minutes async getSimActivationFees(): Promise { return this.simCatalog.getActivationFees(); } @Get("sim/addons") @Header("Cache-Control", "public, max-age=300, s-maxage=300") // 5 minutes async getSimAddons(): Promise { return this.simCatalog.getAddons(); } @Get("vpn/plans") @RateLimit({ limit: 20, ttl: 60 }) // 20 requests per minute @Header("Cache-Control", "public, max-age=300, s-maxage=300") // 5 minutes async getVpnPlans(): Promise { return this.vpnCatalog.getPlans(); } @Get("vpn/activation-fees") @Header("Cache-Control", "public, max-age=300, s-maxage=300") // 5 minutes async getVpnActivationFees(): Promise { return this.vpnCatalog.getActivationFees(); } }