From b25bf3f94453492707541532c9817ad112d20b56 Mon Sep 17 00:00:00 2001 From: Stivenjs Date: Fri, 16 May 2025 19:32:00 -0500 Subject: [PATCH 1/2] fix(useUpdateProfilePicture): update public URL construction and error message Update the public URL construction to use a CDN endpoint instead of directly referencing the S3 bucket. Additionally, standardize the error message to be in English for consistency across the codebase. --- .../account-settings/hooks/useUpdateProfilePicture.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/(main-layout)/account-settings/hooks/useUpdateProfilePicture.ts b/app/(main-layout)/account-settings/hooks/useUpdateProfilePicture.ts index bf05352a..986eb5f7 100644 --- a/app/(main-layout)/account-settings/hooks/useUpdateProfilePicture.ts +++ b/app/(main-layout)/account-settings/hooks/useUpdateProfilePicture.ts @@ -33,7 +33,7 @@ export function useUpdateProfilePicture() { // 2. Construir la URL pública manualmente. const bucketName = outputs.storage.bucket_name const region = outputs.storage.aws_region - const publicUrl = `https://${bucketName}.s3.${region}.amazonaws.com/${result.path}` + const publicUrl = `https://cdn.fasttify.com/sandbox/${result.path}` // 3. Actualizar el atributo 'picture' del usuario con la URL pública. await updateUserAttributes({ @@ -42,7 +42,7 @@ export function useUpdateProfilePicture() { }, }) } catch (error) { - console.error('Error al actualizar la foto de perfil:', error) + console.error('Error updating profile picture:', error) } finally { setIsLoading(false) } From f1edb65d3d21d86c5f74218030df1756ed5e2604 Mon Sep 17 00:00:00 2001 From: Stivenjs Date: Sat, 17 May 2025 12:20:01 -0500 Subject: [PATCH 2/2] refactor(storage): use environment variables for S3 URLs and enhance CloudFront handling Update storage-related hooks and functions to use `NEXT_PUBLIC_S3_URL` environment variable for constructing S3 URLs. Enhance CloudFront domain handling in the `storeImages` function to dynamically switch between CloudFront and S3 URLs based on the environment configuration. This improves flexibility and reduces hardcoded dependencies. --- .env.example | 2 + amplify/functions/storeImages/handler.ts | 70 ++++++++++++++++--- amplify/functions/storeImages/resource.ts | 3 + .../hooks/useUpdateProfilePicture.ts | 10 ++- app/store/hooks/useLogoUpload.ts | 7 +- app/store/hooks/useProductImageUpload.ts | 7 +- 6 files changed, 77 insertions(+), 22 deletions(-) create mode 100644 .env.example diff --git a/.env.example b/.env.example new file mode 100644 index 00000000..737109d0 --- /dev/null +++ b/.env.example @@ -0,0 +1,2 @@ +BUCKET_NAME="YOUR_BUCKET_NAME" +AWS_REGION="YOUR_AWS_REGION" diff --git a/amplify/functions/storeImages/handler.ts b/amplify/functions/storeImages/handler.ts index 7f209e6b..d2438e53 100644 --- a/amplify/functions/storeImages/handler.ts +++ b/amplify/functions/storeImages/handler.ts @@ -9,8 +9,41 @@ import { env } from '$amplify/env/storeImages' const s3Client = new S3Client() const bucketName = env.BUCKET_NAME -// URL base de CloudFront -const cloudFrontDomain = 'https://d1etr7t5j9fzio.cloudfront.net' +const awsRegion = env.AWS_REGION_BUCKET + +// Determinar el dominio de CloudFront a usar, si aplica. +// cloudFrontDomainBase contendrá solo el nombre de host (ej: d123.cloudfront.net) si está en producción y configurado. +// De lo contrario, permanecerá vacío, y se usará la URL directa de S3. +let cloudFrontDomainBase = '' +if ( + env.APP_ENV === 'production' && + env.CLOUDFRONT_DOMAIN_NAME && + env.CLOUDFRONT_DOMAIN_NAME.trim() !== '' +) { + cloudFrontDomainBase = env.CLOUDFRONT_DOMAIN_NAME +} + +if (!bucketName) { + console.error( + 'Error: BUCKET_NAME is not defined in the environment variables of the storeImages function.' + ) +} +// AWS_REGION_BUCKET es necesario si no se usa CloudFront (no producción o CloudFront no configurado) +if (!awsRegion && (!cloudFrontDomainBase || cloudFrontDomainBase.trim() === '')) { + console.warn( + "Warning: AWS_REGION_BUCKET is not defined. S3 URLs may default to 'us-east-2' if CloudFront is not used or not configured." + ) +} + +// Advertencia específica si es producción pero CLOUDFRONT_DOMAIN_NAME no está configurado +if ( + env.APP_ENV === 'production' && + (!env.CLOUDFRONT_DOMAIN_NAME || env.CLOUDFRONT_DOMAIN_NAME.trim() === '') +) { + console.warn( + 'Warning: APP_ENV is "production" but CLOUDFRONT_DOMAIN_NAME is not set. Image URLs will use S3 direct links.' + ) +} export const handler = async (event: any) => { try { @@ -88,12 +121,19 @@ async function listImages(storeId: string, limit: number = 1000, prefix: string // Generar URLs para cada objeto usando CloudFront const imagePromises = listResponse.Contents.map(async item => { if (!item.Key) return null - - // Omitir objetos de carpeta if (item.Key.endsWith('/')) return null - // Construir la URL de CloudFront - const cloudFrontUrl = `${cloudFrontDomain}/${item.Key}` + let imageUrl: string + const s3Key = item.Key + + if (cloudFrontDomainBase && cloudFrontDomainBase.trim() !== '') { + // Usar CloudFront para producción + imageUrl = `https://${cloudFrontDomainBase}/${s3Key}` + } else { + // Fallback a la URL de S3 para otros entornos o si CloudFront no está configurado + const regionForS3Url = awsRegion || 'us-east-2' + imageUrl = `https://${bucketName}.s3.${regionForS3Url}.amazonaws.com/${s3Key}` + } // Extraer el nombre del archivo de la clave const keyParts = item.Key.split('/') @@ -110,7 +150,7 @@ async function listImages(storeId: string, limit: number = 1000, prefix: string return { key: item.Key, - url: cloudFrontUrl, + url: imageUrl, // Usar la URL construida dinámicamente filename, lastModified: item.LastModified, size: item.Size, @@ -165,13 +205,21 @@ async function uploadImage( await s3Client.send(putCommand) - // Construir la URL de CloudFront - const cloudFrontUrl = `${cloudFrontDomain}/${key}` + let imageUrl: string + const s3Key = key + + if (cloudFrontDomainBase && cloudFrontDomainBase.trim() !== '') { + // Usar CloudFront para producción + imageUrl = `https://${cloudFrontDomainBase}/${s3Key}` + } else { + // Fallback a la URL de S3 para otros entornos o si CloudFront no está configurado + const regionForS3Url = awsRegion || 'us-east-2' + imageUrl = `https://${bucketName}.s3.${regionForS3Url}.amazonaws.com/${s3Key}` + } - // Crear un objeto de imagen para devolver const image = { key, - url: cloudFrontUrl, + url: imageUrl, // Usar la URL construida dinámicamente filename, lastModified: new Date(), size: buffer.length, diff --git a/amplify/functions/storeImages/resource.ts b/amplify/functions/storeImages/resource.ts index e128d4d6..dcce1080 100644 --- a/amplify/functions/storeImages/resource.ts +++ b/amplify/functions/storeImages/resource.ts @@ -6,5 +6,8 @@ export const storeImages = defineFunction({ resourceGroupName: 'storeImages', environment: { BUCKET_NAME: secret('BUCKET_NAME'), + AWS_REGION_BUCKET: secret('AWS_REGION_BUCKET'), + CLOUDFRONT_DOMAIN_NAME: secret('CLOUDFRONT_DOMAIN_NAME'), + APP_ENV: secret('APP_ENV'), }, }) diff --git a/app/(main-layout)/account-settings/hooks/useUpdateProfilePicture.ts b/app/(main-layout)/account-settings/hooks/useUpdateProfilePicture.ts index 986eb5f7..9f749e0c 100644 --- a/app/(main-layout)/account-settings/hooks/useUpdateProfilePicture.ts +++ b/app/(main-layout)/account-settings/hooks/useUpdateProfilePicture.ts @@ -31,9 +31,13 @@ export function useUpdateProfilePicture() { }).result // 2. Construir la URL pública manualmente. - const bucketName = outputs.storage.bucket_name - const region = outputs.storage.aws_region - const publicUrl = `https://cdn.fasttify.com/sandbox/${result.path}` + const bucketName = process.env.NEXT_PUBLIC_S3_URL + + if (!bucketName) { + throw new Error('There is no bucket for profile pictures') + } + + const publicUrl = `${bucketName}/${result.path}` // 3. Actualizar el atributo 'picture' del usuario con la URL pública. await updateUserAttributes({ diff --git a/app/store/hooks/useLogoUpload.ts b/app/store/hooks/useLogoUpload.ts index 93ff1b47..29aa8e54 100644 --- a/app/store/hooks/useLogoUpload.ts +++ b/app/store/hooks/useLogoUpload.ts @@ -27,10 +27,9 @@ export function useLogoUpload(): UseLogoUploadReturn { const [error, setError] = useState(null) // Obtener el bucket correcto para logos de tienda - const logoBucket = outputs.storage.bucket_name - const aws_region = outputs.storage.aws_region + const bucketName = process.env.NEXT_PUBLIC_S3_URL - if (!logoBucket) { + if (!bucketName) { throw new Error('There is no bucket for store logos') } @@ -63,7 +62,7 @@ export function useLogoUpload(): UseLogoUploadReturn { }).result // Construir la URL pública correcta usando el nombre del bucket - const publicUrl = `https://${logoBucket}.s3.${aws_region}.amazonaws.com/${key}` + const publicUrl = `${bucketName}/${key}` setStatus('success') diff --git a/app/store/hooks/useProductImageUpload.ts b/app/store/hooks/useProductImageUpload.ts index a280207a..cf4ef2ff 100644 --- a/app/store/hooks/useProductImageUpload.ts +++ b/app/store/hooks/useProductImageUpload.ts @@ -17,10 +17,9 @@ export function useProductImageUpload() { const [error, setError] = useState(null) // Obtener el bucket correcto para imágenes de productos - const productBucket = outputs.storage.bucket_name - const aws_region = outputs.storage.aws_region + const bucketName = process.env.NEXT_PUBLIC_S3_URL - if (!productBucket) { + if (!bucketName) { throw new Error('There is no bucket for product images') } @@ -46,7 +45,7 @@ export function useProductImageUpload() { }).result // Construir la URL pública correcta usando el nombre del bucket - const publicUrl = `https://${productBucket}.s3.${aws_region}.amazonaws.com/${result.path}` + const publicUrl = `${bucketName}/${result.path}` return { url: publicUrl, alt: '',