Code using Adopt-A-Pet API not rendering image of pet

I’m working in Wix Studio. I am creating code that calls up information about a pet from the Adopt-A-Pet website using an API key, API URL, and Shelter ID. My site link is www.positivepawsbhc.org/adopt.

All of my code is working except for the image. I am unable to get an image of the pet to appear in the repeater. Adopt-A-Pet is giving me a placeholder image instead of a real image of the pet.

This is my backend code:

import { fetch } from ‘wix-fetch’;

import { getSecret } from ‘wix-secrets-backend’;

import { mediaManager } from ‘wix-media-backend’;

export async function proxyImageToWix(imageUrl) {

if (!imageUrl || imageUrl.includes(‘PDP-NoPetPhoto’)) {

console.warn(‘proxyImageToWix was called with a null, empty, or placeholder URL.’);

return null;

}

console.log(`Attempting to import image from URL: ${imageUrl}`);

try {

const uploadedImage = await mediaManager.importFile(

imageUrl, // First parameter: The external URL

‘image’, // Second parameter: The file type

{} // Third parameter: An empty options object

);

if (uploadedImage && uploadedImage.fileUrl) {

console.log(`Successfully imported image. New Wix fileUrl: ${uploadedImage.fileUrl}`);

if (!uploadedImage.fileUrl.startsWith(‘wix:image://v1/’)) {

console.error(`Unexpected Wix fileUrl format: ${uploadedImage.fileUrl}`);

}

return uploadedImage.fileUrl;

} else {

console.error(`Import successful for ${imageUrl}, but no fileUrl was returned.`);

return null;

}

} catch (error) {

console.error(`Error importing image from URL: ${imageUrl}`, error);

return null;

}

}

export async function getShelterPets() {

const apiKey = await getSecret(‘AdoptAPet_API’);

const shelterId = ‘238464’;

const url = `https://api.adoptapet.com/search/pets_at_shelter?key=${apiKey}&shelter_id=${shelterId}

const fallbackImageUrl = ‘wix:image://v1/4b9983_b93d395a43924f07a77e0f2f703698de~mv2.jpg’;

console.time(‘getShelterPets’);

console.log(‘Starting getShelterPets function…’);

try {

const httpResponse = await fetch(url, { method: ‘GET’ });

console.log(`Adopt-a-Pet API fetch completed. Status: ${httpResponse.status}`);

if (httpResponse.ok) {

const apiData = await httpResponse.json();

console.log(‘Adopt-a-Pet API response received.’);

if (apiData.status === ‘ok’ && apiData.pets) {

console.log(`Found ${apiData.pets.length} pets in the API response.`);

const repeaterDataPromises = apiData.pets.map(async pet => {

const externalImageUrl = pet.images?.[0]?.original_url;

let wixImageUrl;

// Check if a valid URL exists AND it’s not a placeholder

if (externalImageUrl && !externalImageUrl.includes(‘PDP-NoPetPhoto’)) {

console.log(`Valid image URL found for pet ID ${pet.pet_id}: ${externalImageUrl}`);

wixImageUrl = await proxyImageToWix(externalImageUrl);

} else {

console.warn(`No valid image or found placeholder URL for pet ID ${pet.pet_id}. Using fallback image.`);

wixImageUrl = fallbackImageUrl;

}

return {

_id: pet.pet_id.toString(),

petName: pet.pet_name,

petImage: wixImageUrl || fallbackImageUrl,

petBreed: pet.primary_breed,

petAge: pet.age,

petS ex: pet.s ex,

};

});

const repeaterData = await Promise.all(repeaterDataPromises);

console.log(‘All images processed and repeater data prepared.’);

console.timeEnd(‘getShelterPets’);

return repeaterData;

} else {

console.error(‘Adopt-a-Pet API returned a non-OK status or missing data:’, apiData.error_message);

console.timeEnd(‘getShelterPets’);

return ;

}

} else {

console.error(`Fetch request failed with status: ${httpResponse.status} ${httpResponse.statusText}`);

console.timeEnd(‘getShelterPets’);

return ;

}

} catch (err) {

console.error(‘An unexpected error occurred during API fetch or processing:’, err);

console.timeEnd(‘getShelterPets’);

return ;

}

}

This is my front end code:

import { getShelterPets } from ‘backend/AdoptAPet.jsw’;

$w.onReady(function () {

setupRepeater();

fetchAndPopulateRepeater();

});

// Configure how each repeater item should be populated

function setupRepeater() {

$w(‘#AdoptablePets’).onItemReady(($item, itemData, index) => {

$item(‘#petImage’).src = itemData.petImage;

$item(‘#petImage’).show();

// Log the data to the console for debugging

console.log(`Pet Name: ${itemData.petName}, Image URL: ${itemData.petImage}`);

$item(‘#petName’).text = `Name: ${itemData.petName}`;

$item(‘#petBreed’).text = `Breed: ${itemData.petBreed}`;

$item(‘#petex’).text = `S ex: ${itemData.petex}`;

$item(‘#petAge’).text= `Age: ${itemData.petAge}`;

});

}

// Fetch the data and assign it to the repeater

async function fetchAndPopulateRepeater() {

console.log(“Attempting to fetch data from the backend.”);

try {

const fetchedPetData = await getShelterPets();

if (fetchedPetData.length > 0) {

$w(‘#AdoptablePets’).data = fetchedPetData;

} else {

console.log(“No pet data was returned.”);

// Add code here to show a “No pets found” message

}

} catch (err) {

console.error(“Error fetching data for repeater:”, err);

// Display an error message to the user

}

}

Here is a link to the API documentation. https://www.adoptapet.com/public/apis/pet_list.html#api_features

username pet_list_api, password jradmarbeq

Link to Adopt-A-Pet page Available Pets at Positive Paws BHC in Bullhead City, AZ - Adoptapet.com

This is the image url for one of the pets https://media.adoptapet.com/image/upload/d_PDP-NoPetPhoto_Dog.png/c_auto,g_auto,w_358,ar_142:135,dpr_2/f_auto,q_auto/1251332631

It’s telling me there is no photo or giving me a place holder, and so my code replaces it with my custom placeholder.

I’m not an expert coder. AI wrote this code for me, but I understand it more than the average person. Customizing a pet list on a website using an API would be pretty worthless if the API didn’t allow the retrieval and display of the pets photo, so I’m can’t imagine that the Adopt-A-Pet API doesn’t include that feature. I keep thinking that since the image URL is a substructure, I’m missing a step to that gets to that structure.

Any help would be greatly appreciated. Thank you so much!

1 Like

Try the following…

BACKEND-CODE:

import { fetch } from 'wix-fetch';
import { getSecret } from 'wix-secrets-backend';
import { mediaManager } from 'wix-media-backend';

// ===============================================
//  Proxy Image Import Function
// ===============================================
export async function proxyImageToWix(imageUrl) {
    if (!imageUrl || imageUrl.includes('PDP-NoPetPhoto')) {
        console.warn('proxyImageToWix was called with a null, empty, or placeholder URL.');
        return null;
    }

    console.log(`Attempting to import image from URL: ${imageUrl}`);

    try {
        const uploadedImage = await mediaManager.importFile(
            imageUrl,      // External URL
            'image',       // File type
            {}             // Options
        );

        if (uploadedImage && uploadedImage.fileUrl) {
            console.log(`Successfully imported image. New Wix fileUrl: ${uploadedImage.fileUrl}`);
            return uploadedImage.fileUrl;
        } else {
            console.error(`Import successful for ${imageUrl}, but no fileUrl was returned.`);
            return null;
        }

    } catch (error) {
        console.error(`Error importing image from URL: ${imageUrl}`, error);
        return null;
    }
}

// ===============================================
//  Fetch Shelter Pets from Adopt-A-Pet API
// ===============================================
export async function getShelterPets() {
    const apiKey = await getSecret('AdoptAPet_API');
    const shelterId = '238464';

    const url = `https://api.adoptapet.com/search/pets_at_shelter?key=${apiKey}&shelter_id=${shelterId}`;
    const fallbackImageUrl = 'wix:image://v1/4b9983_b93d395a43924f07a77e0f2f703698de~mv2.jpg';

    console.time('getShelterPets');
    console.log('Starting getShelterPets function…');

    try {
        const httpResponse = await fetch(url, { method: 'GET' });
        console.log(`Adopt-a-Pet API fetch completed. Status: ${httpResponse.status}`);

        if (httpResponse.ok) {
            const apiData = await httpResponse.json();
            console.log('Adopt-a-Pet API response received.');

            if (apiData.status === 'ok' && apiData.pets) {
                console.log(`Found ${apiData.pets.length} pets in the API response.`);

                const repeaterDataPromises = apiData.pets.map(async pet => {
                    // Attempt to find a valid image URL from any available structure
                    const externalImageUrl =
                        pet.images?.[0]?.original_url ||
                        pet.media?.photos?.photo?.find(p => p["@size"] === "x")?.["$t"] ||
                        pet.media?.photos?.photo?.[0]?.["$t"];

                    let wixImageUrl;

                    if (externalImageUrl && !externalImageUrl.includes('PDP-NoPetPhoto')) {
                        console.log(`Valid image URL found for pet ID ${pet.pet_id}: ${externalImageUrl}`);
                        wixImageUrl = await proxyImageToWix(externalImageUrl);
                    } else {
                        console.warn(`No valid image or found placeholder URL for pet ID ${pet.pet_id}. Using fallback image.`);
                        wixImageUrl = fallbackImageUrl;
                    }

                    return {
                        _id: pet.pet_id.toString(),
                        petName: pet.pet_name || 'Unknown',
                        petImage: wixImageUrl || fallbackImageUrl,
                        petBreed: pet.primary_breed || 'Unknown',
                        petAge: pet.age || 'Unknown',
                        //pet S e x: pet. s e x || 'Unknown'
                    };
                });

                const repeaterData = await Promise.all(repeaterDataPromises);

                console.log('All images processed and repeater data prepared.');
                console.timeEnd('getShelterPets');
                return repeaterData;

            } else {
                console.error('Adopt-a-Pet API returned a non-OK status or missing data:', apiData.error_message);
                console.timeEnd('getShelterPets');
                return [];
            }

        } else {
            console.error(`Fetch request failed with status: ${httpResponse.status} ${httpResponse.statusText}`);
            console.timeEnd('getShelterPets');
            return [];
        }

    } catch (err) {
        console.error('An unexpected error occurred during API fetch or processing:', err);
        console.timeEnd('getShelterPets');
        return [];
    }
}

FRONTEND-CODE:

import { getShelterPets } from 'backend/AdoptAPet.jsw';

$w.onReady(function () {
    setupRepeater();
    fetchAndPopulateRepeater();
});

function setupRepeater() {
    $w('#AdoptablePets').onItemReady(($item, itemData, index) => {
        $item('#petImage').src = itemData.petImage;
        $item('#petImage').show();

        console.log(`Pet Name: ${itemData.petName}, Image URL: ${itemData.petImage}`);

        $item('#petName').text = `Name: ${itemData.petName}`;
        $item('#petBreed').text = `Breed: ${itemData.petBreed}`;
        //$item('#petS e x').text = `S e x: ${itemData.petS e x}`;
        $item('#petAge').text = `Age: ${itemData.petAge}`;
    });
}

async function fetchAndPopulateRepeater() {
    console.log("Attempting to fetch data from the backend.");

    try {
        const fetchedPetData = await getShelterPets();

        if (fetchedPetData && fetchedPetData.length > 0) {
            $w('#AdoptablePets').data = fetchedPetData;
        } else {
            console.log("No pet data was returned.");
            // Optionally show a "No pets found" message here
        }

    } catch (err) {
        console.error("Error fetching data for repeater:", err);
        // Optionally show a friendly error message on the page
    }
}

**

QUESTION

What do you think is the BEST-OPTION?
There are three valid places you could define the right place where to add the → SHELTER-ID <–, depending on how dynamic your site needs to be ( const shelterId = ‘238464’;)

Option Where it lives When it’s best
:one: Hardcoded in backend (your current setup) Inside getShelterPets() :white_check_mark: Best for a single shelter site (e.g. Positive Paws BHC) — simplest, safest
:two: Passed from frontend as a parameter e.g. getShelterPets(shelterId) :white_check_mark: Best if your site supports multiple shelters or admin chooses which shelter to view
:three: Stored in Wix Secrets Manager Same as API key :white_check_mark: Best for security + configurability — keeps it editable without code changes
Situation Best approach
You only ever show your pets Keep the shelterId hardcoded in backend :white_check_mark:
You want to switch between shelters Pass the shelterId from frontend :white_check_mark:
You want to make it editable (but still secure) Store the ID in Wix Secrets Manager :white_check_mark:

Pay attention on —> //$item('#petS e x').text = S e x: ${itemData.pet S e x};
Since there is → s e x ← it was not accepted by the auto guards of this forum.
You have to edit them back to normal.

1 Like

I entered your code for both ends , and I was still given the fallback photo. I did some more research and asked Google why this URL: https://media.adoptapet.com/image/upload/d_PDP-NoPetPhoto_Dog.png/c_fit,h_400,dpr_2/f_auto,q_auto/1251332631 (which is the one I believe is being called) was causing my fallback image to appear even though the URL is a photo of the actual pet and not a placeholder. I eventually got to the point where it told me I needed to split the URL and take out the portion that was causing the code to interpret it as a placeholder. It gave me the following backend code that cleans the URL to https://media.adoptapet.com/image/upload/1251332631. The frontend code remained the same. Unfortunately, this did not work either, and this code doesn’t show my custom fallback image like the other code did.

import { fetch } from 'wix-fetch';
import { getSecret } from 'wix-secrets-backend';
import { mediaManager } from 'wix-media-backend';

// ===============================================
//  Proxy Image Import Function
// ===============================================
export async function proxyImageToWix(imageUrl) {
    if (!imageUrl || imageUrl.includes('PDP-NoPetPhoto')) {
        console.warn('proxyImageToWix was called with a null, empty, or placeholder URL.');
        return null;
    }

    console.log(`Attempting to import image from URL: ${imageUrl}`);

    try {
        const uploadedImage = await mediaManager.importFile(
            imageUrl,      // External URL
            'image',       // File type
            {}             // Options
        );

        if (uploadedImage && uploadedImage.fileUrl) {
            console.log(`Successfully imported image. New Wix fileUrl: ${uploadedImage.fileUrl}`);
            return uploadedImage.fileUrl;
        } else {
            console.error(`Import successful for ${imageUrl}, but no fileUrl was returned.`);
            return null;
        }

    } catch (error) {
        console.error(`Error importing image from URL: ${imageUrl}`, error);
        return null;
    }
}

// ===============================================
//  Fetch Shelter Pets from Adopt-A-Pet API
// ===============================================
export async function getShelterPets() {
    const apiKey = await getSecret('AdoptAPet_API');
    const shelterId = '238464';

    const url = `https://api.adoptapet.com/search/pets_at_shelter?key=${apiKey}&shelter_id=${shelterId}`;
    const fallbackImageUrl = 'wix:image://v1/4b9983_b93d395a43924f07a77e0f2f703698de~mv2.jpg';

    console.time('getShelterPets');
    console.log('Starting getShelterPets function…');

    // Helper function to process Cloudinary URLs
    const cleanCloudinaryUrl = (originalUrl) => {
        if (!originalUrl) return null;
        // Check for the Cloudinary default image parameter ('d_')
        if (originalUrl.includes('/d_')) {
            // Find the last segment of the URL, which is the unique image identifier
            const parts = originalUrl.split('/');
            const imageId = parts.pop();
            // Reconstruct a clean URL without the default image parameter
            const cleanUrl = `https://media.adoptapet.com/image/upload/${imageId}`;
            console.log(`Cleaned URL for import: ${cleanUrl}`);
            return cleanUrl;
        }
        return originalUrl;
    };

    try {
        const httpResponse = await fetch(url, { method: 'GET' });
        console.log(`Adopt-a-Pet API fetch completed. Status: ${httpResponse.status}`);

        if (httpResponse.ok) {
            const apiData = await httpResponse.json();
            console.log('Adopt-a-Pet API response received.');

            if (apiData.status === 'ok' && apiData.pets) {
                console.log(`Found ${apiData.pets.length} pets in the API response.`);

                const repeaterDataPromises = apiData.pets.map(async pet => {
                    // Get the base image URL
                    const externalImageUrl =
                        pet.images?.[0]?.original_url ||
                        pet.media?.photos?.photo?.find(p => p["@size"] === "x")?.["$t"] ||
                        pet.media?.photos?.photo?.[0]?.["$t"];

                    let wixImageUrl;

                    if (externalImageUrl) {
                        const cleanedImageUrl = cleanCloudinaryUrl(externalImageUrl);
                        if (cleanedImageUrl) {
                             console.log(`Valid image URL found for pet ID ${pet.pet_id}: ${externalImageUrl}`);
                             wixImageUrl = await proxyImageToWix(cleanedImageUrl);
                        }
                    }

                    if (!wixImageUrl) {
                        console.warn(`No valid image or found placeholder URL for pet ID ${pet.pet_id}. Using fallback image.`);
                        wixImageUrl = fallbackImageUrl;
                    }

                    return {
                        _id: pet.pet_id.toString(),
                        petName: pet.pet_name || 'Unknown',
                        petImage: wixImageUrl,
                        petBreed: pet.primary_breed || 'Unknown',
                        petAge: pet.age || 'Unknown',
                        petSex: pet.s e x || 'Unknown'
                    };
                });

                const repeaterData = await Promise.all(repeaterDataPromises);

                console.log('All images processed and repeater data prepared.');
                console.timeEnd('getShelterPets');
                return repeaterData;

            } else {
                console.error('Adopt-a-Pet API returned a non-OK status or missing data:', apiData.error_message);
                console.timeEnd('getShelterPets');
                return [];
            }

        } else {
            console.error(`Fetch request failed with status: ${httpResponse.status} ${httpResponse.statusText}`);
            console.timeEnd('getShelterPets');
            return [];
        }

    } catch (err) {
        console.error('An unexpected error occurred during API fetch or processing:', err);
        console.timeEnd('getShelterPets');
        return [];
    }
}

I’m wondering if I should try and use the thumbnail_url which is https://pet-uploads.adoptapet.com/b/1/5/1251332633.jpg. I can only access thumbnail URLs on the Adopt-A-Pet account I’m working with, but looking at the two pets listed, it appears as if everything after .com/ is unique to that image. I didn’t choose to use the thumbnail URL initially because I was concerned about the size and quality of the photo that would appear. I’m also only going to be listing pets from this one shelter.

Thank you so much for your help!

1 Like

Could the following be your current issue?

:puzzle_piece: What’s happening

Your current code rejects any image URL containing PDP-NoPetPhotocorrect — but it’s also being fooled because Adopt-a-Pet often embeds the default placeholder parameter (d_PDP-NoPetPhoto_Dog.png) in every Cloudinary transformation chain, even for valid pet photos.

So even though the image is real, the URL still technically contains PDP-NoPetPhoto, so your code thinks it’s a placeholder and triggers the fallback.

I can’t test your code, this for i wuold have to reconstruct your situation.

Example:

![1251332631](upload://dB0OS3nhkjdO1o6LwfNYacwxZfB.jpeg)

The d_PDP-NoPetPhoto_Dog.png part just tells Cloudinary “use this default if the ID is invalid,” but the real image is still valid (1251332631).

Now back to your real image →

(https://media.adoptapet.com/image/upload/d_PDP-NoPetPhoto_Dog.png/c_fit,h_400,dpr_2/f_auto,q_auto/1251332631)

Ok, then try this one… (BACKEND)

import { fetch } from 'wix-fetch';
import { getSecret } from 'wix-secrets-backend';
import { mediaManager } from 'wix-media-backend';

// ===============================================
//  Proxy Image Import Function
// ===============================================
export async function proxyImageToWix(imageUrl) {
    if (!imageUrl) {
        console.warn('proxyImageToWix was called with an empty or null URL.');
        return null;
    }

    console.log(`Attempting to import image from URL: ${imageUrl}`);

    try {
        const uploadedImage = await mediaManager.importFile(
            imageUrl,
            'image',
            {}
        );

        if (uploadedImage && uploadedImage.fileUrl) {
            console.log(`✅ Successfully imported image. Wix fileUrl: ${uploadedImage.fileUrl}`);
            return uploadedImage.fileUrl;
        } else {
            console.error(`⚠️ Import succeeded but fileUrl was missing for ${imageUrl}`);
            return null;
        }

    } catch (error) {
        console.error(`❌ Error importing image from URL: ${imageUrl}`, error);
        return null;
    }
}

// ===============================================
//  Fetch Shelter Pets from Adopt-A-Pet API
// ===============================================
export async function getShelterPets() {
    const apiKey = await getSecret('AdoptAPet_API');
    const shelterId = '238464';

    const url = `https://api.adoptapet.com/search/pets_at_shelter?key=${apiKey}&shelter_id=${shelterId}`;
    const fallbackImageUrl = 'wix:image://v1/4b9983_b93d395a43924f07a77e0f2f703698de~mv2.jpg';

    console.time('getShelterPets');
    console.log('🐾 Starting getShelterPets function…');

    // 🧹 Clean Cloudinary URLs without breaking valid images
    const cleanCloudinaryUrl = (originalUrl) => {
        if (!originalUrl) return null;

        try {
            // Remove transformation parameters and default fallbacks, keep only the final public ID
            const match = originalUrl.match(/\/image\/upload\/(?:[^/]+\/)*([0-9A-Za-z_-]+)$/);
            if (match && match[1]) {
                const cleanUrl = `https://media.adoptapet.com/image/upload/${match[1]}`;
                console.log(`🧼 Cleaned URL: ${cleanUrl}`);
                return cleanUrl;
            }

            // If no match, just return original
            return originalUrl;
        } catch (e) {
            console.warn('Error cleaning Cloudinary URL:', e);
            return originalUrl;
        }
    };

    try {
        const httpResponse = await fetch(url, { method: 'GET' });
        console.log(`📡 Adopt-a-Pet API fetch completed. Status: ${httpResponse.status}`);

        if (!httpResponse.ok) {
            console.error(`Fetch failed: ${httpResponse.status} ${httpResponse.statusText}`);
            console.timeEnd('getShelterPets');
            return [];
        }

        const apiData = await httpResponse.json();
        if (apiData.status !== 'ok' || !apiData.pets) {
            console.error('API returned invalid data:', apiData.error_message);
            console.timeEnd('getShelterPets');
            return [];
        }

        console.log(`🐶 Found ${apiData.pets.length} pets.`);

        const repeaterDataPromises = apiData.pets.map(async pet => {
            const rawUrl =
                pet.images?.[0]?.original_url ||
                pet.media?.photos?.photo?.find(p => p["@size"] === "x")?.["$t"] ||
                pet.media?.photos?.photo?.[0]?.["$t"];

            let wixImageUrl = null;

            if (rawUrl) {
                const cleanedUrl = cleanCloudinaryUrl(rawUrl);

                // Only treat it as a placeholder if it truly ends with the default image file
                if (!/PDP-NoPetPhoto/i.test(cleanedUrl.split('/').pop())) {
                    wixImageUrl = await proxyImageToWix(cleanedUrl);
                } else {
                    console.warn(`🚫 Placeholder detected in ${cleanedUrl}, skipping import.`);
                }
            }

            if (!wixImageUrl) {
                console.warn(`⚠️ No valid image found for pet ${pet.pet_id}. Using fallback.`);
                wixImageUrl = fallbackImageUrl;
            }

            return {
                _id: pet.pet_id.toString(),
                petName: pet.pet_name || 'Unknown',
                petImage: wixImageUrl,
                petBreed: pet.primary_breed || 'Unknown',
                petAge: pet.age || 'Unknown',
                //pet S e x: pet.s e x || 'Unknown'
            };
        });

        const repeaterData = await Promise.all(repeaterDataPromises);
        console.log('✅ All images processed.');
        console.timeEnd('getShelterPets');
        return repeaterData;

    } catch (err) {
        console.error('💥 Unexpected error during API fetch or processing:', err);
        console.timeEnd('getShelterPets');
        return [];
    }
}

Why This Works

  • The regex /\/image\/upload\/(?:[^/]+\/)*([0-9A-Za-z_-]+)$/ correctly extracts the final public ID segment (e.g. 1251332631), even if multiple Cloudinary transformations are chained before it.
  • It does not reject URLs that contain d_PDP-NoPetPhoto_Dog.png unless that is the actual last segment (a true placeholder).
  • It keeps your custom fallback working because we only set fallbackImageUrl if the image fails import or is truly missing.
  • It avoids the previous issue where the presence of PDP-NoPetPhoto anywhere in the URL made it get rejected.
    Now you parse the URL to make sure you can differentiate between a real IMAGE and a PLACEHOLDER

Conclusion:
→ You don’t want to reject URLs just because they containPDP-NoPetPhoto” anywhere inside the URL and that’s exactly the key to solution i would say.

In last version if anywhere inside your URL → PDP-NoPetPhoto ← has been found it was directly defined as → PLACEHOLDER, right?
But now you inspect the URL much deeper (analyses) and then decide correctly if it is a real PIC or just a PLACEHOLDER the right way.

You want to reject them only if they end with the placeholder file or fail to return a valid image when cleaned.

My advice would be → NOT TO USE THE VERY SMAL THUMBNAILS <—
Instead make your code work correctly.

Look at that smal dog, anybody will recognice the dog…
1251332633
…maybe some even will believe it is a rat. :slight_smile:

If still not working try the following…

import { fetch } from 'wix-fetch';
import { getSecret } from 'wix-secrets-backend';
import { mediaManager } from 'wix-media-backend';

// ===============================================
//  Proxy Image Import Function
// ===============================================
export async function proxyImageToWix(imageUrl) {
    if (!imageUrl) {
        console.warn('proxyImageToWix was called with an empty or null URL.');
        return null;
    }

    console.log(`🔄 Attempting to import image from URL: ${imageUrl}`);

    try {
        const uploadedImage = await mediaManager.importFile(imageUrl, 'image', {});

        if (uploadedImage && uploadedImage.fileUrl) {
            console.log(`✅ Successfully imported image. Wix fileUrl: ${uploadedImage.fileUrl}`);
            return uploadedImage.fileUrl;
        } else {
            console.error(`⚠️ Import succeeded but fileUrl was missing for ${imageUrl}`);
            return null;
        }

    } catch (error) {
        console.error(`❌ Error importing image from URL: ${imageUrl}`, error);
        return null;
    }
}

// ===============================================
//  Fetch Shelter Pets from Adopt-A-Pet API
// ===============================================
export async function getShelterPets() {
    const apiKey = await getSecret('AdoptAPet_API');
    const shelterId = '238464';

    const url = `https://api.adoptapet.com/search/pets_at_shelter?key=${apiKey}&shelter_id=${shelterId}`;
    const fallbackImageUrl = 'wix:image://v1/4b9983_b93d395a43924f07a77e0f2f703698de~mv2.jpg';

    console.time('getShelterPets');
    console.log('🐾 Starting getShelterPets function…');

    // 🧹 Clean Cloudinary URLs without breaking valid images
    const cleanCloudinaryUrl = (originalUrl) => {
        if (!originalUrl) return null;

        try {
            // Extract the final ID, even if multiple Cloudinary transformations exist
            const match = originalUrl.match(/\/image\/upload\/(?:[^/]+\/)*([0-9A-Za-z_-]+)$/);
            if (match && match[1]) {
                const cleanUrl = `https://media.adoptapet.com/image/upload/${match[1]}`;
                console.log(`🧼 Cleaned URL: ${cleanUrl}`);
                return cleanUrl;
            }

            // If no match, just return the original
            return originalUrl;
        } catch (e) {
            console.warn('Error cleaning Cloudinary URL:', e);
            return originalUrl;
        }
    };

    try {
        const httpResponse = await fetch(url, { method: 'GET' });
        console.log(`📡 Adopt-a-Pet API fetch completed. Status: ${httpResponse.status}`);

        if (!httpResponse.ok) {
            console.error(`Fetch failed: ${httpResponse.status} ${httpResponse.statusText}`);
            console.timeEnd('getShelterPets');
            return [];
        }

        const apiData = await httpResponse.json();
        if (apiData.status !== 'ok' || !apiData.pets) {
            console.error('API returned invalid data:', apiData.error_message);
            console.timeEnd('getShelterPets');
            return [];
        }

        console.log(`🐶 Found ${apiData.pets.length} pets.`);

        const repeaterDataPromises = apiData.pets.map(async pet => {
            const rawUrl =
                pet.images?.[0]?.original_url ||
                pet.media?.photos?.photo?.find(p => p["@size"] === "x")?.["$t"] ||
                pet.media?.photos?.photo?.[0]?.["$t"];

            let wixImageUrl = null;

            if (rawUrl) {
                const cleanedUrl = cleanCloudinaryUrl(rawUrl);

                // 🧾 Log both raw and cleaned URLs for debugging
                console.log(`📸 Pet ID ${pet.pet_id} — Raw URL: ${rawUrl}`);
                console.log(`🧽 Pet ID ${pet.pet_id} — Cleaned URL: ${cleanedUrl}`);

                // Only treat it as placeholder if the final file *is* the default image
                if (!/PDP-NoPetPhoto/i.test(cleanedUrl.split('/').pop())) {
                    wixImageUrl = await proxyImageToWix(cleanedUrl);
                } else {
                    console.warn(`🚫 Placeholder detected in ${cleanedUrl}, skipping import.`);
                }
            }

            if (!wixImageUrl) {
                console.warn(`⚠️ No valid image found for pet ${pet.pet_id}. Using fallback.`);
                wixImageUrl = fallbackImageUrl;
            }

            return {
                _id: pet.pet_id.toString(),
                petName: pet.pet_name || 'Unknown',
                petImage: wixImageUrl,
                petBreed: pet.primary_breed || 'Unknown',
                petAge: pet.age || 'Unknown',
                //pet S e x: pet.s e x || 'Unknown'
            };
        });

        const repeaterData = await Promise.all(repeaterDataPromises);
        console.log('✅ All images processed and repeater data ready.');
        console.timeEnd('getShelterPets');
        return repeaterData;

    } catch (err) {
        console.error('💥 Unexpected error during API fetch or processing:', err);
        console.timeEnd('getShelterPets');
        return [];
    }
}

Add more logs into your own code and inspect what exactly is happening at which stage/level/step. Try to figure out when the code breaks (if it breaks), or what exact output you get. Then modify that OUTPUT for your own needs (splitting, cutting, editing, adding, or doing other modifications -generating the right data you need from the recieved output. The best way to solve things like your issue is to investigate the outputs you recieve back. this way you will be able to understand the OUTPUT-STRUCTURE of → Adopt-A-Pet API

  1. https://adoptapetcom.zendesk.com/hc/en-us/articles/41654139166107-Adopt-a-Pet-Partner-APIs
  2. https://adoptapetcom.zendesk.com/hc/en-us/article_attachments/41654013533083

And let me know, if you could solve it :wink:

I already can feel the success..

Let the dog rise up like a PHOENIX from the ashes…
dog-003
…thank you for bringing me to life…

Much more insteressting, isn’t it?

Now imagine you could turn your thumbnails into life animated gifs on mouse over?

Well, i should stop my imaginations now…, good luck!

1 Like

The code didn’t work :slightly_frowning_face: , but after trying a few things, it occurred to me that my logs were not showing anything for the proxy image function. I knew that wasn’t right, and once I tackled it from that angle, I started getting somewhere. Kept asking AI questions and plugging in different suggestions, and FINALLY ended up with the following code that works!

One of the console logs that was suggested was this one: console.log(`Pet data for ${pet.pet_id}:`, JSON.stringify(pet, null, 2));

This log listed all of the pet data which looked like this:

order: 2
results_photo_url: "https://pet-uploads.adoptapet.com/a/6/3/1231727508.jpg"
results_photo_width: 68
results_photo_height: 90
large_results_photo_url: "https://pet-uploads.adoptapet.com/0/d/0/1231727510.jpg"
large_results_photo_width: 200
large_results_photo_height: 200
details_url: "https://api.adoptapet.com/search/pet_details?pet_id=44827299&key=b88408fd5109da04546ebf2f50fc5339&v=1&output=json"
pet_id: "44827299"
pet_name: "Leo"
s e x: "m"
age: "adult"
size: "Large 61-100 lbs (28-45 kg)"
species: "dog"
primary_breed: "Akita"
addr_city: "Bullhead City"
addr_state_code: "AZ"
secondary_breed: null

I noticed that that the structure for the image was results_photo_url and large_results_photo_url and not original_url. Ask AI about this, and it had me had pet.large_results_photo_url || to the const rawUrl. I think that was an important missing piece. Still didn’t work, and I ended up adding a ton of suggestions as you can see from the code lol, but none of it worked until I added the folderPath after creating the folder in my media manager. I don’t know if all of the code is actually needed, but it works, so I’m not inclined to remove any of it.

import { fetch } from 'wix-fetch';
import { getSecret } from 'wix-secrets-backend';
import { mediaManager } from 'wix-media-backend';

// ===============================================
//  Proxy Image Import Function
// ===============================================
export async function proxyImageToWix(imageUrl) {
    if (!imageUrl) {
        console.warn('proxyImageToWix was called with an empty or null URL.');
        return null;
    }

    console.log(`Attempting to fetch image from URL: ${imageUrl}`);
let fileExtension = 'jpg';
    try {
        const fetchResponse = await fetch(imageUrl, {
    headers: {
        'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64)' // Mimic a browser
    }
});
       

        if (!fetchResponse.ok) {
            throw new Error(`Failed to fetch image: ${fetchResponse.statusText}`);
        }

        const buffer = await fetchResponse.buffer();
        console.log(`✅ Successfully fetched image as a buffer. Attempting to upload...`);
        console.log(`Image buffer size: ${buffer.length} bytes`);

// Attempt to guess file type from buffer, though this is not foolproof
const contentType = fetchResponse.headers.get('content-type') || 'image/jpeg';
        if (contentType.includes('image/png')) {
            fileExtension = 'png';
        } else if (contentType.includes('image/gif')) {
            fileExtension = 'gif';
        }
        console.log(`Image content type: ${contentType}`);

        const folderPath = '/AdoptAPet_Images';
        const fileName = `pet_image_${Date.now()}.${fileExtension}`;
        const options = {};

        const uploadedImage = await mediaManager.upload(
            folderPath,
            buffer,
            fileName,
            options
        );
       

        if (uploadedImage && uploadedImage.fileUrl) {
            console.log(`✅ Successfully uploaded image. Wix fileUrl: ${uploadedImage.fileUrl}`);
            return uploadedImage.fileUrl;
        } else {
            console.error(`⚠️ Upload succeeded but fileUrl was missing for ${imageUrl}`);
            return null;
        }

    } catch (error) {
        console.error(`❌ Error importing image from URL: ${imageUrl} -`, error);
        return null;
    }
}

// ===============================================
//  Fetch Shelter Pets from Adopt-A-Pet API
// ===============================================
export async function getShelterPets() {
    const apiKey = await getSecret('AdoptAPet_API');
    const shelterId = '238464';

    const url = `https://api.adoptapet.com/search/pets_at_shelter?key=${apiKey}&shelter_id=${shelterId}`;
    const fallbackImageUrl = 'wix:image://v1/4b9983_b93d395a43924f07a77e0f2f703698de~mv2.jpg';

    console.time('getShelterPets');
    console.log('🐾 Starting getShelterPets function…');

    // 🧹 Clean Cloudinary URLs without breaking valid images
    const cleanCloudinaryUrl = (originalurl) => {
        if (!originalurl) return null;

        try {
            // Remove transformation parameters and default fallbacks, keep only the final public ID
            const match = originalurl.match(/\/image\/upload\/(?:[^/]+\/)*([0-9A-Za-z_-]+)$/);
            if (match && match[1]) {
                const cleanUrl = `https://pet-uploads.adoptapet.com/${match[1]}`;
                console.log(`🧼 Cleaned URL: ${cleanUrl}`);
                return cleanUrl;
            }

            // If no match, just return original
            return originalurl;
        } catch (e) {
            console.warn('Error cleaning Cloudinary URL:', e);
            return originalurl;
        }
    };

    try {
        const httpResponse = await fetch(url, { method: 'GET' });
        console.log(`📡 Adopt-a-Pet API fetch completed. Status: ${httpResponse.status}`);

        if (!httpResponse.ok) {
            console.error(`Fetch failed: ${httpResponse.status} ${httpResponse.statusText}`);
            console.timeEnd('getShelterPets');
            return [];
        }

        const apiData = await httpResponse.json();
        if (apiData.status !== 'ok' || !apiData.pets) {
            console.error('API returned invalid data:', apiData.error_message);
            console.timeEnd('getShelterPets');
            return [];
        }

        console.log(`🐶 Found ${apiData.pets.length} pets.`);

        const repeaterDataPromises = apiData.pets.map(async pet => {
            console.log(`Pet data for ${pet.pet_id}:`, JSON.stringify(pet, null, 2));
            const rawUrl =
                pet.large_results_photo_url ||
                pet.images?.[0]?.original_url ||
                pet.media?.photos?.photo?.find(p => p["@size"] === "x")?.["$t"] ||
                pet.media?.photos?.photo?.[0]?.["$t"];

            console.log(`🔎 Found raw URL for pet ${pet.pet_id}:`, rawUrl); // New log for raw URL

            let wixImageUrl = null;

            if (rawUrl) {
                const cleanedUrl = cleanCloudinaryUrl(rawUrl);

                if (!cleanedUrl) { // Check if the cleaning function failed
                    console.error(`💥 Cleaning failed for URL: ${rawUrl}`);
                }
                
                // Only treat it as a placeholder if it truly ends with the default image file
                if (cleanedUrl && !/PDP-NoPetPhoto/i.test(cleanedUrl.split('/').pop())) {
                    wixImageUrl = await proxyImageToWix(cleanedUrl);
                } else {
                    console.warn(`🚫 Placeholder or invalid URL detected in ${cleanedUrl || rawUrl}, skipping import.`);
                }
            }

            if (!wixImageUrl) {
                console.warn(`⚠️ No valid image found for pet ${pet.pet_id}. Using fallback.`);
                wixImageUrl = fallbackImageUrl;
            }

            return {
                _id: pet.pet_id.toString(),
                petName: pet.pet_name || 'Unknown',
                petImage: wixImageUrl,
                petBreed: pet.primary_breed || 'Unknown',
                petAge: pet.age || 'Unknown',
                petSex: pet.s e x || 'Unknown'
            };
        });

        const repeaterData = await Promise.all(repeaterDataPromises);
        console.log('✅ All images processed.');
        console.timeEnd('getShelterPets');
        return repeaterData;

    } catch (err) {
        console.error('💥 Unexpected error during API fetch or processing:', err);
        console.timeEnd('getShelterPets');
        return [];
    }
}

Hopefully Leo and Junior will be adopted soon! Thank you so much for your help!

2 Likes

Well done! On this level, normaly you should now be able to solve issues on your own.
Some issues just need more time to be resolved. :wink: