diff --git a/app/api/stores/[storeId]/cache/invalidate/route.ts b/app/api/stores/[storeId]/cache/invalidate/route.ts index 0e5eea0b..1acddeaa 100644 --- a/app/api/stores/[storeId]/cache/invalidate/route.ts +++ b/app/api/stores/[storeId]/cache/invalidate/route.ts @@ -1,5 +1,6 @@ import { getNextCorsHeaders } from '@/lib/utils/next-cors'; import { cacheInvalidationService, type ChangeType } from '@/renderer-engine/services/core/cache'; +import { AuthGetCurrentUserServer, cookiesClient } from '@/utils/client/AmplifyUtils'; import { NextRequest, NextResponse } from 'next/server'; export async function OPTIONS(request: NextRequest) { @@ -8,8 +9,21 @@ export async function OPTIONS(request: NextRequest) { } export async function POST(request: NextRequest, { params }: { params: Promise<{ storeId: string }> }) { - const { storeId } = await params; const corsHeaders = await getNextCorsHeaders(request); + const session = await AuthGetCurrentUserServer(); + if (!session) { + return NextResponse.json({ error: 'Unauthorized' }, { status: 401, headers: corsHeaders }); + } + const { storeId } = await params; + const { data: userStore } = await cookiesClient.models.UserStore.get({ + storeId, + }); + if (!userStore) { + return NextResponse.json({ error: 'Store not found' }, { status: 404, headers: corsHeaders }); + } + if (userStore.userId !== session.username) { + return NextResponse.json({ error: 'Forbidden' }, { status: 403, headers: corsHeaders }); + } try { const { changeType, entityId } = await request.json(); diff --git a/app/api/stores/[storeId]/products/filter/route.ts b/app/api/stores/[storeId]/products/filter/route.ts index 2f1db832..e7d56ad7 100644 --- a/app/api/stores/[storeId]/products/filter/route.ts +++ b/app/api/stores/[storeId]/products/filter/route.ts @@ -265,12 +265,10 @@ function getAppliedFiltersInfo(filters: FilterParams) { */ async function getAvailableFilters(storeId: string) { try { - // Consulta ligera para obtener solo los campos necesarios para filtros - // Amplify no soporta projection, pero podemos usar un límite más bajo y cachear const response = await cookiesClient.models.Product.listProductByStoreId( { storeId }, { - limit: 100, // Muestra más pequeña pero representativa + limit: 50, filter: { status: { eq: 'active' }, }, @@ -279,10 +277,8 @@ async function getAvailableFilters(storeId: string) { const products = response.data || []; - // Extraer categorías únicas const categories = [...new Set(products.map((p) => p.category).filter(Boolean))]; - // Extraer tags únicos (si tags es un array JSON) const allTags = products.reduce((tags: string[], product: any) => { if (product.tags && Array.isArray(product.tags)) { tags.push(...product.tags); @@ -291,7 +287,6 @@ async function getAvailableFilters(storeId: string) { }, []); const tags = [...new Set(allTags)]; - // Extraer rango de precios const prices = products.map((p: any) => p.price || 0).filter((p: any) => p > 0); const priceRange = { min: prices.length > 0 ? Math.min(...prices) : 0, diff --git a/renderer-engine/services/core/cache/cache-invalidation-service.ts b/renderer-engine/services/core/cache/cache-invalidation-service.ts index afaecdc5..af7795ea 100644 --- a/renderer-engine/services/core/cache/cache-invalidation-service.ts +++ b/renderer-engine/services/core/cache/cache-invalidation-service.ts @@ -9,7 +9,6 @@ import { getProductCacheKey, getProductsCacheKey, } from '@/renderer-engine/services/core/cache'; -import { CloudFrontClient, CreateInvalidationCommand } from '@aws-sdk/client-cloudfront'; /** * Tipos de cambios que pueden ocurrir en una tienda @@ -158,29 +157,6 @@ export class CacheInvalidationService { if (entityId) { this.invalidateSpecificKeys(changeType, storeId, entityId); } - - if (changeType === 'template_store_updated') { - this.invalidateCloudFrontCache(storeId, path); - } else if ( - [ - 'product_created', - 'product_updated', - 'product_deleted', - 'collection_created', - 'collection_updated', - 'collection_deleted', - 'page_created', - 'page_updated', - 'page_deleted', - 'navigation_updated', - 'template_updated', - 'store_settings_updated', - 'domain_updated', - 'template_store_updated', - ].includes(changeType) - ) { - this.invalidateCloudFrontCache(storeId); - } } /** @@ -266,48 +242,6 @@ export class CacheInvalidationService { cacheManager.invalidateProductCache(storeId, productId); } - /** - * Invalida caché de CloudFront para una tienda o path específico - */ - private async invalidateCloudFrontCache(storeId: string, path?: string): Promise { - if (!process.env.CLOUDFRONT_DISTRIBUTION_ID) { - logger.warn( - 'CLOUDFRONT_DISTRIBUTION_ID not configured, skipping CloudFront invalidation', - undefined, - 'CacheInvalidationService' - ); - return; - } - const REGION = process.env.REGION_BUCKET || 'us-east-2'; - const cloudFrontClient = new CloudFrontClient({ - region: REGION, - }); - - const invalidationPath = path ? `/templates/${storeId}/${path}` : `/templates/${storeId}/*`; - - try { - const command = new CreateInvalidationCommand({ - DistributionId: process.env.CLOUDFRONT_DISTRIBUTION_ID, - InvalidationBatch: { - Paths: { - Quantity: 1, - Items: [invalidationPath], - }, - CallerReference: `template-invalidation-${Date.now()}-${storeId}`, - }, - }); - - await cloudFrontClient.send(command); - logger.info(`CloudFront cache invalidated for: ${invalidationPath}`, undefined, 'CacheInvalidationService'); - } catch (error: any) { - logger.error( - `Error invalidating CloudFront cache for ${invalidationPath}: ${error.message}`, - error, - 'CacheInvalidationService' - ); - } - } - /** * Obtiene estadísticas de invalidación */ diff --git a/scripts/upload-base-template.js b/scripts/upload-base-template.js index ef066bde..a186c9a8 100644 --- a/scripts/upload-base-template.js +++ b/scripts/upload-base-template.js @@ -13,13 +13,11 @@ const { S3Client, PutObjectCommand } = require('@aws-sdk/client-s3'); const { CloudFrontClient, CreateInvalidationCommand } = require('@aws-sdk/client-cloudfront'); const { readFile, readdir } = require('fs/promises'); -const { join, relative } = require('path'); +const { join } = require('path'); const dotenv = require('dotenv'); -// Cargar variables de entorno dotenv.config(); -// Configuración const BUCKET_NAME = process.env.BUCKET_NAME; const REGION = process.env.REGION_BUCKET || 'us-east-2'; const CLOUDFRONT_DISTRIBUTION_ID = process.env.CLOUDFRONT_DISTRIBUTION_ID; @@ -28,17 +26,14 @@ const TEMPLATE_DIR = join(process.cwd(), 'template'); const FILTER_MODULES_DIR = join(process.cwd(), 'renderer-engine/liquid/tags/filters/js'); const FILTER_MODULES_PREFIX = 'assets/'; -// Cliente S3 const s3Client = new S3Client({ region: REGION, }); -// Cliente CloudFront const cloudFrontClient = new CloudFrontClient({ region: REGION, }); -// Función para determinar content type function getContentType(filename) { const ext = filename.toLowerCase().split('.').pop(); @@ -53,7 +48,6 @@ function getContentType(filename) { scss: 'text/scss', sass: 'text/sass', xml: 'application/xml', - // Tipos de imagen png: 'image/png', jpg: 'image/jpeg', jpeg: 'image/jpeg', @@ -61,7 +55,6 @@ function getContentType(filename) { svg: 'image/svg+xml', webp: 'image/webp', ico: 'image/x-icon', - // Tipos de font woff: 'font/woff', woff2: 'font/woff2', ttf: 'font/ttf', @@ -71,7 +64,6 @@ function getContentType(filename) { return contentTypes[ext] || 'application/octet-stream'; } -// Función para leer archivos de plantilla async function readTemplateFiles() { const files = []; @@ -87,7 +79,6 @@ async function readTemplateFiles() { } else if (entry.isFile()) { const contentType = getContentType(entry.name); - // Determinar si es un archivo binario o de texto const isBinaryFile = contentType.startsWith('image/') || contentType.startsWith('font/') || @@ -96,15 +87,13 @@ async function readTemplateFiles() { let content; if (isBinaryFile) { - // Leer archivo binario como Buffer content = await readFile(fullPath); } else { - // Leer archivo de texto como utf-8 content = await readFile(fullPath, 'utf-8'); } files.push({ - path: relativePath.replace(/\\/g, '/'), // Normalizar path para web + path: relativePath.replace(/\\/g, '/'), content, contentType, isBinaryFile, @@ -117,12 +106,10 @@ async function readTemplateFiles() { return files; } -// Función para leer archivos de módulos de filtros async function readFilterModules() { const files = []; try { - // Leer archivos JS const jsEntries = await readdir(FILTER_MODULES_DIR, { withFileTypes: true }); for (const entry of jsEntries) { if (entry.isFile() && entry.name.endsWith('.js')) { @@ -138,7 +125,6 @@ async function readFilterModules() { } } - // Leer archivos CSS const cssDir = join(process.cwd(), 'renderer-engine/liquid/tags/filters/css'); try { const cssEntries = await readdir(cssDir, { withFileTypes: true }); @@ -165,7 +151,6 @@ async function readFilterModules() { return files; } -// Función para subir plantillas a S3 async function uploadTemplatesToS3(files) { console.log(`Subiendo ${files.length} archivos de plantilla a S3...`); @@ -200,7 +185,6 @@ async function uploadTemplatesToS3(files) { return Promise.all(uploadPromises); } -// Función para subir módulos de filtros a S3 async function uploadFilterModulesToS3(files) { console.log(`Subiendo ${files.length} módulos de filtros a S3...`); @@ -235,7 +219,6 @@ async function uploadFilterModulesToS3(files) { return Promise.all(uploadPromises); } -// Función para invalidar cache de CloudFront async function invalidateCloudFrontCache() { if (!CLOUDFRONT_DISTRIBUTION_ID) { console.warn('⚠️ CLOUDFRONT_DISTRIBUTION_ID no configurado, saltando invalidación'); @@ -247,8 +230,8 @@ async function invalidateCloudFrontCache() { DistributionId: CLOUDFRONT_DISTRIBUTION_ID, InvalidationBatch: { Paths: { - Quantity: 3, - Items: ['/assets/*', '/base-templates/*', '/templates/*'], + Quantity: 1, + Items: ['/assets/*'], }, CallerReference: `invalidation-${Date.now()}`, }, @@ -261,7 +244,6 @@ async function invalidateCloudFrontCache() { } } -// Función principal async function main() { try { console.log('Iniciando subida de plantilla base y módulos de filtros a S3...'); @@ -272,21 +254,17 @@ async function main() { console.log(`Directorio de plantilla: ${TEMPLATE_DIR}`); console.log(`Directorio de módulos: ${FILTER_MODULES_DIR}`); - // Leer archivos de plantilla console.log('\n📁 Leyendo archivos de plantilla...'); const templateFiles = await readTemplateFiles(); console.log(`Se encontraron ${templateFiles.length} archivos de plantilla.`); - // Leer módulos de filtros console.log('\n🔧 Leyendo módulos de filtros...'); const filterModules = await readFilterModules(); console.log(`Se encontraron ${filterModules.length} módulos de filtros.`); - // Subir plantillas a S3 console.log('\n📤 Subiendo plantillas...'); const templateResults = await uploadTemplatesToS3(templateFiles); - // Subir módulos de filtros a S3 console.log('\n📤 Subiendo módulos de filtros...'); const moduleResults = await uploadFilterModulesToS3(filterModules); @@ -295,7 +273,6 @@ async function main() { console.log(`Se subieron ${moduleResults.length} módulos de filtros.`); console.log(`\n🌐 Los módulos están disponibles en: https://cdn.fasttify.com/assets/`); - // Invalidar cache de CloudFront console.log('\n🔄 Invalidando cache de CloudFront...'); await invalidateCloudFrontCache(); } catch (error) { @@ -304,5 +281,4 @@ async function main() { } } -// Ejecutar main();