Search code examples
next.jscontentfulheadless-cmscontentful-managementcontentful-api

How to use contentful-management to add an asset to an entry in one api call?


I would like to add an asset along with an entry. I'm using a form to enter some data and upload an image all in one API call. I'm using cloudinary to store images, but Contentful-Management is giving me a 409 status code and a 'Version Mismatch' error.

These are the steps I'm taking below (add any if you've seen a step I've overlooked, it's much appreciated)

#1. Get space by space id
#2. Get environment
#3. Create Entry
#4. Create a new asset
#5. Assign uploaded image as an entry field

'data' is all the data I need to make a post for a properting posting, and the uploadHref is the link to a single image from Cloudinary.

       const postProperty = (data: Partial<Property>, uploadHref: string) => {
            const SPACE_ID = process.env.NEXT_CONTENTFUL_SPACE_ID;
            const client = createClient({
              accessToken: process.env.NEXT_CONTENTFUL_PERSONAL_ACCESS_TOKEN as string,
            });
            // # 1 Get space by space id
            client.getSpace(SPACE_ID).then((space: Space) => {
              // # 2 Get environment
              space.getEnvironment("master").then(async (environment: Environment) => {
                // # 3 Create Entry
                environment
                  .createEntry("propertyListings", {
                    fields: {
                      id: {
                        "en-US": faker.datatype.number(4) + "-" + data.streetAddress,
                      },
                      firstName: {
                        "en-US": data.firstName,
                      },
                      lastName: {
                        "en-US": data.lastName,
                      },
                      email: {
                        "en-US": data.email,
                      },
                      phone: {
                        "en-US": data.phone,
                      },
                      price: {
                        "en-US": data.price,
                      },
                      streetAddress: {
                        "en-US": data.streetAddress,
                      },
                      city: {
                        "en-US": data.city,
                      },
                      state: {
                        "en-US": data.state,
                      },
                      zipcode: {
                        "en-US": data.zipcode,
                      },
                      latitude: {
                        "en-US": Number(data.latitude as number),
                      },
                      longitude: {
                        "en-US": Number(data.longitude as number),
                      },
                      bedrooms: {
                        "en-US": data.bedrooms,
                      },
                      bathrooms: {
                        "en-US": data.bathrooms,
                      },
                      sqft: {
                        "en-US": Number(data.sqft),
                      },
                      carSpaces: {
                        "en-US": data.carSpaces,
                      },
                      type: {
                        "en-US": data.type,
                      },
                      datePosted: {
                        "en-US": data.datePosted,
                      },
                      petFriendly: {
                        "en-US": data.petFriendly,
                      },
                      images: {
                        "en-US": [],
                      },
                    },
                  })
                  .then(entry => {
                    entry.publish();
                    return environment.getEntry(entry.sys.id)
                  })          
                  .then(function (entry: Entry) {
                    // #4 Create a new asset in my Contentful media section
                    environment
                      .createAssetWithId(faker.datatype.number(4).toString(), {
                        fields: {
                          title: {
                            "en-US": data.streetAddress,
                          },
                          file: {
                            "en-US": {
                              contentType: "image/jpeg",
                              fileName: data.streetAddress + ".jpeg",
                              upload: uploadHref,
                            },
                          },
                        },
                      })
                      .then(async (asset) => await asset.processForAllLocales())
                      .then(async (asset) => await asset.publish())
                      .then(function (asset) {
                        // # 5 Assign uploaded image as an entry field
                        entry.fields["images"]["en-US"] = {
                          sys: {
                            id: asset.sys.id, 
                            linkType: "Asset", 
                            type: "Link" 
                          },
                        };
                      
                        return entry.update()
                       })
                      .then(entry => entry.publish())
                    
                      console.log('Entry object value: ', entry)
                      return entry.update()
                  })
                  .catch(err => console.log("Error Message!: ", err))
              });
            });
          };

Solution

  • Contentful DevRel here. 👋

    If you're running into VersionMisMatch most you are not re-assigning resources after creation or update. Contentful's API responds every write operation with a new entry version, this way it's prevented to overwrite newer data.

    I rewrote your example (untested) to reassign returned entry and asset objects with the results from the API.

    async function createEntryWithAsset() {
        const space = await client.getSpace(SPACE_ID);
        const environment = await space.getEnvironment("master");
        
        /**
         * Entry creation and publish
         */
        let entry = await environment.createEntry("propertyListings", {
            /* ... */
        });
        // reassign `entry` to have the latest version number
        entry = await entry.publish();
        
        /**
         * Asset creation and publish
         */
        let asset = await environment.createAssetWithId(
            faker.datatype.number(4).toString(),
            {
              /* ... */
            }
        );
        // reassign `asset` to have the latest version number
        asset = await asset.processForAllLocales();
        asset = await asset.publish();
        
        /**
         * Update entry with new asset
         */
        entry.fields["images"]["en-US"] = {
            sys: {
              id: asset.sys.id,
              linkType: "Asset",
              type: "Link",
            },
        };
        entry = await entry.update();
        entry = await entry.publish();
    }
        
    createEntryWithAsset();