Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 15 additions & 1 deletion app/api/stores/[storeId]/cache/invalidate/route.ts
Original file line number Diff line number Diff line change
@@ -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) {
Expand All @@ -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();
Expand Down
7 changes: 1 addition & 6 deletions app/api/stores/[storeId]/products/filter/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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' },
},
Expand All @@ -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);
Expand All @@ -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,
Expand Down
66 changes: 0 additions & 66 deletions renderer-engine/services/core/cache/cache-invalidation-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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);
}
}

/**
Expand Down Expand Up @@ -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<void> {
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
*/
Expand Down
32 changes: 4 additions & 28 deletions scripts/upload-base-template.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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();

Expand All @@ -53,15 +48,13 @@ function getContentType(filename) {
scss: 'text/scss',
sass: 'text/sass',
xml: 'application/xml',
// Tipos de imagen
png: 'image/png',
jpg: 'image/jpeg',
jpeg: 'image/jpeg',
gif: 'image/gif',
svg: 'image/svg+xml',
webp: 'image/webp',
ico: 'image/x-icon',
// Tipos de font
woff: 'font/woff',
woff2: 'font/woff2',
ttf: 'font/ttf',
Expand All @@ -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 = [];

Expand All @@ -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/') ||
Expand All @@ -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,
Expand All @@ -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')) {
Expand All @@ -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 });
Expand All @@ -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...`);

Expand Down Expand Up @@ -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...`);

Expand Down Expand Up @@ -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');
Expand All @@ -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()}`,
},
Expand All @@ -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...');
Expand All @@ -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);

Expand All @@ -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) {
Expand All @@ -304,5 +281,4 @@ async function main() {
}
}

// Ejecutar
main();