376 lines
13 KiB
TypeScript
376 lines
13 KiB
TypeScript
import axios from 'axios'
|
|
import { scaffoldProducts } 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 { storeInfo } from '../db/schema'
|
|
import { db } from '../db/db_index'
|
|
import { imageUploadS3 } from '@/src/lib/s3-client'
|
|
import { apiCacheKey, cloudflareApiToken, cloudflareZoneId, assetsDomain } from '@/src/lib/env-exporter'
|
|
import { CACHE_FILENAMES } from '@packages/shared'
|
|
import { retryWithExponentialBackoff } from '@/src/lib/retry'
|
|
|
|
function constructCacheUrl(path: string): string {
|
|
return `${assetsDomain}${apiCacheKey}/${path}`
|
|
}
|
|
|
|
export async function createProductsFile(): Promise<string> {
|
|
// Get products data from the API method
|
|
const productsData = await scaffoldProducts()
|
|
|
|
// Convert to JSON string with pretty formatting
|
|
const jsonContent = JSON.stringify(productsData, null, 2)
|
|
|
|
// Convert to Buffer for S3 upload
|
|
const buffer = Buffer.from(jsonContent, 'utf-8')
|
|
|
|
// Upload to S3 at the specified path using apiCacheKey
|
|
const s3Key = await imageUploadS3(buffer, 'application/json', `${apiCacheKey}/${CACHE_FILENAMES.products}`)
|
|
|
|
// Purge cache with retry
|
|
const url = constructCacheUrl(CACHE_FILENAMES.products)
|
|
try {
|
|
await retryWithExponentialBackoff(() => clearUrlCache([url]))
|
|
console.log(`Cache purged for ${url}`)
|
|
} catch (error) {
|
|
console.error(`Failed to purge cache for ${url} after 3 retries:`, error)
|
|
}
|
|
|
|
return s3Key
|
|
}
|
|
|
|
export async function createEssentialConstsFile(): Promise<string> {
|
|
// Get essential consts data from the API method
|
|
const essentialConstsData = await scaffoldEssentialConsts()
|
|
|
|
// Convert to JSON string with pretty formatting
|
|
const jsonContent = JSON.stringify(essentialConstsData, null, 2)
|
|
|
|
// Convert to Buffer for S3 upload
|
|
const buffer = Buffer.from(jsonContent, 'utf-8')
|
|
|
|
// Upload to S3 at the specified path using apiCacheKey
|
|
const s3Key = await imageUploadS3(buffer, 'application/json', `${apiCacheKey}/${CACHE_FILENAMES.essentialConsts}`)
|
|
|
|
// Purge cache with retry
|
|
const url = constructCacheUrl(CACHE_FILENAMES.essentialConsts)
|
|
try {
|
|
await retryWithExponentialBackoff(() => clearUrlCache([url]))
|
|
console.log(`Cache purged for ${url}`)
|
|
} catch (error) {
|
|
console.error(`Failed to purge cache for ${url} after 3 retries:`, error)
|
|
}
|
|
|
|
return s3Key
|
|
}
|
|
|
|
export async function createStoresFile(): Promise<string> {
|
|
// Get stores data from the API method
|
|
const storesData = await scaffoldStores()
|
|
|
|
// Convert to JSON string with pretty formatting
|
|
const jsonContent = JSON.stringify(storesData, null, 2)
|
|
|
|
// Convert to Buffer for S3 upload
|
|
const buffer = Buffer.from(jsonContent, 'utf-8')
|
|
|
|
// Upload to S3 at the specified path using apiCacheKey
|
|
const s3Key = await imageUploadS3(buffer, 'application/json', `${apiCacheKey}/${CACHE_FILENAMES.stores}`)
|
|
|
|
// Purge cache with retry
|
|
const url = constructCacheUrl(CACHE_FILENAMES.stores)
|
|
try {
|
|
await retryWithExponentialBackoff(() => clearUrlCache([url]))
|
|
console.log(`Cache purged for ${url}`)
|
|
} catch (error) {
|
|
console.error(`Failed to purge cache for ${url} after 3 retries:`, error)
|
|
}
|
|
|
|
return s3Key
|
|
}
|
|
|
|
export async function createSlotsFile(): Promise<string> {
|
|
// Get slots data from the API method
|
|
const slotsData = await scaffoldSlotsWithProducts()
|
|
|
|
// Convert to JSON string with pretty formatting
|
|
const jsonContent = JSON.stringify(slotsData, null, 2)
|
|
|
|
// Convert to Buffer for S3 upload
|
|
const buffer = Buffer.from(jsonContent, 'utf-8')
|
|
|
|
// Upload to S3 at the specified path using apiCacheKey
|
|
const s3Key = await imageUploadS3(buffer, 'application/json', `${apiCacheKey}/${CACHE_FILENAMES.slots}`)
|
|
|
|
// Purge cache with retry
|
|
const url = constructCacheUrl(CACHE_FILENAMES.slots)
|
|
try {
|
|
await retryWithExponentialBackoff(() => clearUrlCache([url]))
|
|
console.log(`Cache purged for ${url}`)
|
|
} catch (error) {
|
|
console.error(`Failed to purge cache for ${url} after 3 retries:`, error)
|
|
}
|
|
|
|
return s3Key
|
|
}
|
|
|
|
export async function createBannersFile(): Promise<string> {
|
|
// Get banners data from the API method
|
|
const bannersData = await scaffoldBanners()
|
|
|
|
// Convert to JSON string with pretty formatting
|
|
const jsonContent = JSON.stringify(bannersData, null, 2)
|
|
|
|
// Convert to Buffer for S3 upload
|
|
const buffer = Buffer.from(jsonContent, 'utf-8')
|
|
|
|
// Upload to S3 at the specified path using apiCacheKey
|
|
const s3Key = await imageUploadS3(buffer, 'application/json', `${apiCacheKey}/${CACHE_FILENAMES.banners}`)
|
|
|
|
// Purge cache with retry
|
|
const url = constructCacheUrl(CACHE_FILENAMES.banners)
|
|
try {
|
|
await retryWithExponentialBackoff(() => clearUrlCache([url]))
|
|
console.log(`Cache purged for ${url}`)
|
|
} catch (error) {
|
|
console.error(`Failed to purge cache for ${url} after 3 retries:`, error)
|
|
}
|
|
|
|
return s3Key
|
|
}
|
|
|
|
export async function createStoreFile(storeId: number): Promise<string> {
|
|
// Get store data from the API method
|
|
const storeData = await scaffoldStoreWithProducts(storeId)
|
|
|
|
// Convert to JSON string with pretty formatting
|
|
const jsonContent = JSON.stringify(storeData, null, 2)
|
|
|
|
// Convert to Buffer for S3 upload
|
|
const buffer = Buffer.from(jsonContent, 'utf-8')
|
|
|
|
// Upload to S3 at the specified path using apiCacheKey
|
|
const s3Key = await imageUploadS3(buffer, 'application/json', `${apiCacheKey}/stores/${storeId}.json`)
|
|
|
|
// Purge cache with retry
|
|
const url = constructCacheUrl(`stores/${storeId}.json`)
|
|
try {
|
|
await retryWithExponentialBackoff(() => clearUrlCache([url]))
|
|
console.log(`Cache purged for ${url}`)
|
|
} catch (error) {
|
|
console.error(`Failed to purge cache for ${url} after 3 retries:`, error)
|
|
}
|
|
|
|
return s3Key
|
|
}
|
|
|
|
export async function createAllStoresFiles(): Promise<string[]> {
|
|
// Fetch all store IDs from database
|
|
const stores = await db.select({ id: storeInfo.id }).from(storeInfo)
|
|
|
|
// Create cache files for all stores and collect URLs
|
|
const results: string[] = []
|
|
const urls: string[] = []
|
|
|
|
for (const store of stores) {
|
|
const s3Key = await createStoreFile(store.id)
|
|
results.push(s3Key)
|
|
urls.push(constructCacheUrl(`stores/${store.id}.json`))
|
|
}
|
|
|
|
console.log(`Created ${results.length} store cache files`)
|
|
|
|
// Purge all store caches in one batch with retry
|
|
try {
|
|
await retryWithExponentialBackoff(() => clearUrlCache(urls))
|
|
console.log(`Cache purged for ${urls.length} store files`)
|
|
} catch (error) {
|
|
console.error(`Failed to purge cache for store files after 3 retries. URLs: ${urls.join(', ')}`, error)
|
|
}
|
|
|
|
return results
|
|
}
|
|
|
|
export interface CreateAllCacheFilesResult {
|
|
products: string
|
|
essentialConsts: string
|
|
stores: string
|
|
slots: string
|
|
banners: string
|
|
individualStores: string[]
|
|
}
|
|
|
|
export async function createAllCacheFiles(): Promise<CreateAllCacheFilesResult> {
|
|
console.log('Starting creation of all cache files...')
|
|
|
|
// Create all global cache files in parallel
|
|
const [
|
|
productsKey,
|
|
essentialConstsKey,
|
|
storesKey,
|
|
slotsKey,
|
|
bannersKey,
|
|
individualStoreKeys,
|
|
] = await Promise.all([
|
|
createProductsFileInternal(),
|
|
createEssentialConstsFileInternal(),
|
|
createStoresFileInternal(),
|
|
createSlotsFileInternal(),
|
|
createBannersFileInternal(),
|
|
createAllStoresFilesInternal(),
|
|
])
|
|
|
|
// Collect all URLs for batch cache purge
|
|
const urls = [
|
|
constructCacheUrl(CACHE_FILENAMES.products),
|
|
constructCacheUrl(CACHE_FILENAMES.essentialConsts),
|
|
constructCacheUrl(CACHE_FILENAMES.stores),
|
|
constructCacheUrl(CACHE_FILENAMES.slots),
|
|
constructCacheUrl(CACHE_FILENAMES.banners),
|
|
...individualStoreKeys.map((_, index) => constructCacheUrl(`stores/${index + 1}.json`)),
|
|
]
|
|
|
|
// 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 {
|
|
products: productsKey,
|
|
essentialConsts: essentialConstsKey,
|
|
stores: storesKey,
|
|
slots: slotsKey,
|
|
banners: bannersKey,
|
|
individualStores: individualStoreKeys,
|
|
}
|
|
}
|
|
|
|
// Internal versions that skip cache purging (for batch operations)
|
|
async function createProductsFileInternal(): Promise<string> {
|
|
const productsData = await scaffoldProducts()
|
|
const jsonContent = JSON.stringify(productsData, null, 2)
|
|
const buffer = Buffer.from(jsonContent, 'utf-8')
|
|
return await imageUploadS3(buffer, 'application/json', `${apiCacheKey}/${CACHE_FILENAMES.products}`)
|
|
}
|
|
|
|
async function createEssentialConstsFileInternal(): Promise<string> {
|
|
const essentialConstsData = await scaffoldEssentialConsts()
|
|
const jsonContent = JSON.stringify(essentialConstsData, null, 2)
|
|
const buffer = Buffer.from(jsonContent, 'utf-8')
|
|
return await imageUploadS3(buffer, 'application/json', `${apiCacheKey}/${CACHE_FILENAMES.essentialConsts}`)
|
|
}
|
|
|
|
async function createStoresFileInternal(): Promise<string> {
|
|
const storesData = await scaffoldStores()
|
|
const jsonContent = JSON.stringify(storesData, null, 2)
|
|
const buffer = Buffer.from(jsonContent, 'utf-8')
|
|
return await imageUploadS3(buffer, 'application/json', `${apiCacheKey}/${CACHE_FILENAMES.stores}`)
|
|
}
|
|
|
|
async function createSlotsFileInternal(): Promise<string> {
|
|
const slotsData = await scaffoldSlotsWithProducts()
|
|
const jsonContent = JSON.stringify(slotsData, null, 2)
|
|
const buffer = Buffer.from(jsonContent, 'utf-8')
|
|
return await imageUploadS3(buffer, 'application/json', `${apiCacheKey}/${CACHE_FILENAMES.slots}`)
|
|
}
|
|
|
|
async function createBannersFileInternal(): Promise<string> {
|
|
const bannersData = await scaffoldBanners()
|
|
const jsonContent = JSON.stringify(bannersData, null, 2)
|
|
const buffer = Buffer.from(jsonContent, 'utf-8')
|
|
return await imageUploadS3(buffer, 'application/json', `${apiCacheKey}/${CACHE_FILENAMES.banners}`)
|
|
}
|
|
|
|
async function createAllStoresFilesInternal(): Promise<string[]> {
|
|
const stores = await db.select({ id: storeInfo.id }).from(storeInfo)
|
|
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', `${apiCacheKey}/stores/${store.id}.json`)
|
|
results.push(s3Key)
|
|
}
|
|
|
|
console.log(`Created ${results.length} store cache files`)
|
|
return results
|
|
}
|
|
|
|
export async function clearUrlCache(urls: string[]): Promise<{ success: boolean; errors?: string[] }> {
|
|
if (!cloudflareApiToken || !cloudflareZoneId) {
|
|
console.warn('Cloudflare credentials not configured, skipping cache clear')
|
|
return { success: false, errors: ['Cloudflare credentials not configured'] }
|
|
}
|
|
|
|
try {
|
|
const response = await axios.post(
|
|
`https://api.cloudflare.com/client/v4/zones/${cloudflareZoneId}/purge_cache`,
|
|
{ files: urls },
|
|
{
|
|
headers: {
|
|
'Authorization': `Bearer ${cloudflareApiToken}`,
|
|
'Content-Type': 'application/json',
|
|
},
|
|
}
|
|
)
|
|
|
|
const result = response.data 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[] }> {
|
|
if (!cloudflareApiToken || !cloudflareZoneId) {
|
|
console.warn('Cloudflare credentials not configured, skipping cache clear')
|
|
return { success: false, errors: ['Cloudflare credentials not configured'] }
|
|
}
|
|
|
|
try {
|
|
const response = await axios.post(
|
|
`https://api.cloudflare.com/client/v4/zones/${cloudflareZoneId}/purge_cache`,
|
|
{ purge_everything: true },
|
|
{
|
|
headers: {
|
|
'Authorization': `Bearer ${cloudflareApiToken}`,
|
|
'Content-Type': 'application/json',
|
|
},
|
|
}
|
|
)
|
|
|
|
const result = response.data 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] }
|
|
}
|
|
}
|