import { Buffer } from 'buffer' import { scaffoldProducts, scaffoldAvailability } from '@/src/trpc/apis/common-apis/common' import { scaffoldEssentialConsts } from '@/src/trpc/apis/common-apis/common-trpc-index' import { scaffoldStores } from '@/src/trpc/apis/user-apis/apis/stores' import { scaffoldSlotsWithProducts } from '@/src/trpc/apis/user-apis/apis/slots' import { scaffoldBanners } from '@/src/trpc/apis/user-apis/apis/banners' import { scaffoldStoreWithProducts } from '@/src/trpc/apis/user-apis/apis/stores' import { getStoresSummary, incrementCacheVersion, incrementAvailabilityVersionNum, incrementSlotsVersionNum } from '@/src/dbService' import { imageUploadS3 } from '@/src/lib/s3-client' import { getApiCacheKey, getCloudflareApiToken, getCloudflareZoneId, getAssetsDomain } from '@/src/lib/env-exporter' import { CACHE_FILENAMES } from '@packages/shared' import { retryWithExponentialBackoff } from '@/src/lib/retry' const buildCachePath = (path: string, version: number) => `v-${version}/${path}` const buildAvailabilityPath = (version: number) => `av-${version}/${CACHE_FILENAMES.availability}` const buildSlotsPath = (version: number) => `slots/v-${version}/${CACHE_FILENAMES.slots}` function constructCacheUrl(path: string, version: number): string { return `${getAssetsDomain()}${getApiCacheKey()}/${buildCachePath(path, version)}` } function constructAvailabilityUrl(version: number): string { return `${getAssetsDomain()}${buildAvailabilityPath(version)}` } function constructSlotsUrl(version: number): string { return `${getAssetsDomain()}${buildSlotsPath(version)}` } export interface CreateAllCacheFilesResult { cacheVersion: number products: string essentialConsts: string stores: string slotsVersion: number availabilityVersion: number banners: string individualStores: string[] } export async function createAllCacheFiles(): Promise { console.log('Starting creation of all cache files...') const cacheVersion = await incrementCacheVersion() // Create all global cache files in parallel const [ productsKey, essentialConstsKey, storesKey, slotsVersion, availabilityVersion, bannersKey, individualStoreKeys, ] = await Promise.all([ createProductsFileInternal(cacheVersion), createEssentialConstsFileInternal(cacheVersion), createStoresFileInternal(cacheVersion), createSlotsCacheFile(), createAvailabilityCacheFile(), createBannersFileInternal(cacheVersion), createAllStoresFilesInternal(cacheVersion), ]) const stores = await getStoresSummary() // Collect all URLs for batch cache purge const urls = [ constructCacheUrl(CACHE_FILENAMES.products, cacheVersion), constructCacheUrl(CACHE_FILENAMES.essentialConsts, cacheVersion), constructCacheUrl(CACHE_FILENAMES.stores, cacheVersion), constructSlotsUrl(slotsVersion), constructAvailabilityUrl(availabilityVersion), constructCacheUrl(CACHE_FILENAMES.banners, cacheVersion), ...stores.map((store) => constructCacheUrl(`stores/${store.id}.json`, cacheVersion)), ] // Purge all caches in one batch with retry try { await retryWithExponentialBackoff(() => clearUrlCache(urls)) console.log(`Cache purged for all ${urls.length} files`) } catch (error) { console.error(`Failed to purge cache for all files after 3 retries`, error) } console.log('All cache files created successfully') return { cacheVersion, products: productsKey, essentialConsts: essentialConstsKey, stores: storesKey, slotsVersion, availabilityVersion, banners: bannersKey, individualStores: individualStoreKeys, } } // Internal versions that skip cache purging (for batch operations) async function createProductsFileInternal(version: number): Promise { const productsData = await scaffoldProducts() const jsonContent = JSON.stringify(productsData, null, 2) const buffer = Buffer.from(jsonContent, 'utf-8') const filePath = `${getApiCacheKey()}/${buildCachePath(CACHE_FILENAMES.products, version)}` console.log(filePath) return await imageUploadS3( buffer, 'application/json', filePath ) } export async function createAvailabilityCacheFile(): Promise { const version = await incrementAvailabilityVersionNum() const availabilityData = await scaffoldAvailability() const jsonContent = JSON.stringify(availabilityData, null, 2) const buffer = Buffer.from(jsonContent, 'utf-8') const filePath = buildAvailabilityPath(version) console.log(filePath) await imageUploadS3( buffer, 'application/json', filePath ) return version } async function createEssentialConstsFileInternal(version: number): Promise { const essentialConstsData = await scaffoldEssentialConsts() const jsonContent = JSON.stringify(essentialConstsData, null, 2) const buffer = Buffer.from(jsonContent, 'utf-8') return await imageUploadS3( buffer, 'application/json', `${getApiCacheKey()}/${buildCachePath(CACHE_FILENAMES.essentialConsts, version)}` ) } async function createStoresFileInternal(version: number): Promise { const storesData = await scaffoldStores() const jsonContent = JSON.stringify(storesData, null, 2) const buffer = Buffer.from(jsonContent, 'utf-8') return await imageUploadS3( buffer, 'application/json', `${getApiCacheKey()}/${buildCachePath(CACHE_FILENAMES.stores, version)}` ) } export async function createSlotsCacheFile(): Promise { const version = await incrementSlotsVersionNum() const slotsData = await scaffoldSlotsWithProducts() const jsonContent = JSON.stringify(slotsData, null, 2) const buffer = Buffer.from(jsonContent, 'utf-8') const filePath = buildSlotsPath(version) console.log(filePath) await imageUploadS3( buffer, 'application/json', filePath ) return version } async function createBannersFileInternal(version: number): Promise { const bannersData = await scaffoldBanners() const jsonContent = JSON.stringify(bannersData, null, 2) const buffer = Buffer.from(jsonContent, 'utf-8') return await imageUploadS3( buffer, 'application/json', `${getApiCacheKey()}/${buildCachePath(CACHE_FILENAMES.banners, version)}` ) } async function createAllStoresFilesInternal(version: number): Promise { /* // Old implementation - direct DB queries: import { db } from '@/src/db/db_index' import { storeInfo } from '@/src/db/schema' const stores = await db.select({ id: storeInfo.id }).from(storeInfo) */ const stores = await getStoresSummary() const results: string[] = [] for (const store of stores) { const storeData = await scaffoldStoreWithProducts(store.id) const jsonContent = JSON.stringify(storeData, null, 2) const buffer = Buffer.from(jsonContent, 'utf-8') const s3Key = await imageUploadS3( buffer, 'application/json', `${getApiCacheKey()}/${buildCachePath(`stores/${store.id}.json`, version)}` ) results.push(s3Key) } console.log(`Created ${results.length} store cache files`) return results } export async function clearUrlCache(urls: string[]): Promise<{ success: boolean; errors?: string[] }> { const cloudflareApiToken = getCloudflareApiToken() const cloudflareZoneId = getCloudflareZoneId() if (!cloudflareApiToken || !cloudflareZoneId) { console.warn('Cloudflare credentials not configured, skipping cache clear') return { success: false, errors: ['Cloudflare credentials not configured'] } } try { const resp = await fetch( `https://api.cloudflare.com/client/v4/zones/${cloudflareZoneId}/purge_cache`, { method: 'POST', headers: { 'Authorization': `Bearer ${cloudflareApiToken}`, 'Content-Type': 'application/json', }, body: JSON.stringify({ files: urls }), } ) const result = await resp.json() as { success: boolean; errors?: { message: string }[] } if (!result.success) { const errorMessages = result.errors?.map(e => e.message) || ['Unknown error'] console.error(`Cloudflare cache purge failed for URLs: ${urls.join(', ')}`, errorMessages) return { success: false, errors: errorMessages } } console.log(`Successfully purged ${urls.length} URLs from Cloudflare cache: ${urls.join(', ')}`) return { success: true } } catch (error) { console.log(error) const errorMessage = error instanceof Error ? error.message : 'Unknown error' console.error(`Error clearing Cloudflare cache for URLs: ${urls.join(', ')}`, errorMessage) return { success: false, errors: [errorMessage] } } } export async function clearAllCache(): Promise<{ success: boolean; errors?: string[] }> { const cloudflareApiToken = getCloudflareApiToken() const cloudflareZoneId = getCloudflareZoneId() if (!cloudflareApiToken || !cloudflareZoneId) { console.warn('Cloudflare credentials not configured, skipping cache clear') return { success: false, errors: ['Cloudflare credentials not configured'] } } try { const resp = await fetch( `https://api.cloudflare.com/client/v4/zones/${cloudflareZoneId}/purge_cache`, { method: 'POST', headers: { 'Authorization': `Bearer ${cloudflareApiToken}`, 'Content-Type': 'application/json', }, body: JSON.stringify({ purge_everything: true }), } ) const result = await resp.json() as { success: boolean; errors?: { message: string }[] } if (!result.success) { const errorMessages = result.errors?.map(e => e.message) || ['Unknown error'] console.error('Cloudflare cache purge failed:', errorMessages) return { success: false, errors: errorMessages } } console.log('Successfully purged all cache from Cloudflare') return { success: true } } catch (error) { const errorMessage = error instanceof Error ? error.message : 'Unknown error' console.error('Error clearing Cloudflare cache:', errorMessage) return { success: false, errors: [errorMessage] } } }