Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Project Dead? #160

Open
selected-pixel-jameson opened this issue Mar 11, 2023 · 11 comments
Open

Project Dead? #160

selected-pixel-jameson opened this issue Mar 11, 2023 · 11 comments

Comments

@selected-pixel-jameson
Copy link

Haven't seen any new releases for this project in over a year. Is this still being worked on?

@alexphiev
Copy link

I am asking myself the same question. There is a quite blocking bug opened since a couple of months which makes it quite unsuable, right?
#139

@andrew-braun
Copy link

andrew-braun commented Apr 25, 2023

Same! Algolia has their own JS client, though, with v5 just having entered public beta, so I'm going to go with that instead. Pity--the Strapi v3 version worked very nicely.

@krankos
Copy link

krankos commented May 17, 2023

@andrew-braun could you please tell us more about how to use the Algolia JS client to index from strapi?

@andrew-braun
Copy link

@krankos Sure! Here's what I did:

  1. Create a plugin using the Strapi CLI
  2. Installed Algoliasearch v5 (currently in beta, but since the changes from v4 are significant I figured it would be less hassle than upgrading from v4 later)
  3. In the services folder of the plugin I added my own file, algolia-indexing.js
  4. In that file, I added a bunch of functions that initiate an Algolia client and use it to add, update, or delete from the index. Docs for v5 are still sparse, so I ended up doing a bit of guessing and digging into the source code to figure it out.
  5. Then, I went into the lifecycles.js folder for each content type I wanted to send to Algolia. I added afterCreate, afterUpdate, and afterDelete hooks that call the appropriate Algolia functions and send the data over. Remember that for afterCreate and afterUpdate you'll also have to pass in a populate object that tells the service what data to go fetch so it can be sent to the Algolia index. I put all my populate objects in a separate file and called them dynamically based on the index name so I could mostly just copy-paste the functions.

It's still fairly rough since it's just meant for use in this one project (this can probably be improved quite a bit; it's my first time making a Strapi plugin), but here's the basic idea:

plugins/strapi-algolia/server/services.js

"use strict";

function algoliaClient() {
  // Initiate the Algolia client
  const { algoliasearch } = require("algoliasearch");
  const client = algoliasearch(process.env.ALGOLIA_ID, process.env.ALGOLIA_KEY);
  return client;
}

module.exports = ({ strapi }) => ({
  async addOrReplace(event, options = { index: "", populate: "*" }) {
    /* This function can be called from a Strapi lifecycle hook--afterCreate or afterUpdate, most likely
     ** Call it with syntax: strapi.service("plugin::strapi-algolia.index").addOrReplace(event, { index: "indexname", populate: {populateObject} })
     */
    try {
      const { model } = event;
      const { where } = event.params;
      const { index, populate } = options;
      const entryId =
        event.action === "afterUpdate" ? where?.id : event?.result?.id;

      console.log(
        `${event.action === "afterCreate" ? "Adding" : "Updating"} ${
          model.singularName
        } ${entryId} ${
          event.action === "afterCreate" ? "to" : "in"
        } Algolia index`
      );

      const client = algoliaClient();

      /* Populate the object with all of its relations by default
       ** If you want to customize this, you can pass in a populate array
       ** on the options object
       */

      const populatedObject = await strapi.entityService.findOne(
        model.uid,
        entryId,
        {
          populate: populate ?? "*",
        }
      );

      // console.log(populatedObject);

      const { taskID } = await client.saveObject({
        indexName: index,
        body: { objectID: entryId, ...populatedObject },
      });

      const status = await client.waitForTask({ indexName: index, taskID });
      console.log(`Algolia indexing status: ${status.status}`);
    } catch (error) {
      console.error(`Error while updating Algolia index: ${error}`);
    }
  },
  async delete(event, options) {
      try {
        const { index, many } = options;
        const objectIDs = many
          ? event?.params?.where?.["$and"][0]?.id["$in"]
          : [event.params.where.id];
  
        const client = algoliaClient();
  
        console.log(
          `Deleting object(s) with id(s) ${objectIDs.join(
            ", "
          )} from Algolia ${index} index`
        );
  
        // Use Algolia client.batch API to handle either one or many delete requests
        const requests = objectIDs.map((objectID) => {
          return {
            objectID,
            action: "deleteObject",
          };
        });
        const { taskID } = await client.batch({
          indexName: index,
          batchWriteParams: { requests },
        });
  
        const response = await client.waitForTask({ indexName: index, taskID });
  
        console.log(
          `Successfully deleted object(s) with id(s) ${objectIDs.join(
            ", "
          )} from Algolia ${index} index`
        );
      } catch (error) {
        console.error(`Error while deleting from Algolia index: ${error}`);
      }
    },

/api/article/lifecycles.js

"use strict";

/**
 * Read the documentation (https://strapi.io/documentation/developer-docs/latest/development/backend-customization.html#lifecycle-hooks)
 * to customize this model
 */

const index = "articles";

const {
  articlePopulate: populate,
} = require("../../../../lib/data/algolia-populate-data");

module.exports = {
  async afterCreate(event) {
    const response = await strapi
      .service("plugin::strapi-algolia.index")
      .addOrReplace(event, { index, populate: populate });
  },

  async afterUpdate(event) {
    const response = await strapi
      .service("plugin::strapi-algolia.index")
      .addOrReplace(event, { index, populate: populate });
  },

  async afterDelete(event) {
    const response = await strapi
      .service("plugin::strapi-algolia.index")
      .delete(event, { index, many: false });
  },

  async afterDeleteMany(event) {
    const response = await strapi
      .service("plugin::strapi-algolia.index")
      .delete(event, { index, many: true });
  },
};

/lib/data/algolia-populate-data.js

module.exports = {
  articlePopulate: {
    author: {
      fields: ["authorName", "profession", "bio"],
    },
    story_category: {
      fields: ["category_name"],
    },
    country: {
      fields: ["country", "countryName"],
    },
    region: {
      fields: ["region", "regionName"],
    },
    ContentZone: true,
  },
}

I also added a separate thing for bulk-updating indexes via a POST request that tells Strapi to go collect the data from the specified content type and batch update it, but it's even rougher than this is and honestly would just make it more confusing :D

It's a very simple solution, probably not the most efficient, and is definitely missing a lot of features/convenience that a more sophisticated plugin would provide, but it didn't take me long to make and it does the job I want--updating Algolia based on Strapi lifecycle hooks.

@krankos
Copy link

krankos commented May 17, 2023

@andrew-braun Thank you for sharing! I'll try this approach with my team and we'll try to make it index only published entries.

@andrew-braun
Copy link

@krankos Good luck! And that's actually a good note--I should probably add that to mine as well :D Should be simple enough to add a publicationState check.

@krankos
Copy link

krankos commented May 18, 2023

@andrew-braun well, we finally made it work! After many attempts and errors, we found the solutions we sought. We decided to use Algolia v4. We followed your steps but did some modifications specially in the algolia-indexing.js.
Here's our version with the index on publish feature. We just used the publishedAt field as a reference for the entry's state.

"use strict";

function algoliaClient() {
  const algoliasearch = require('algoliasearch');
  const client = algoliasearch(process.env.ALGOLIA_APP_ID, process.env.ALGOLIA_ADMIN_KEY);
  return client;
}

module.exports = ({ strapi }) => ({
  async addOrReplace(event, options = { index: "", populate: "*" }) {
    try {
      const { model } = event;
      const { where } = event.params;
      const { index, populate } = options;
      const entryId =
        event.action === "afterUpdate" ? where?.id : event?.result?.id;
      console.log(
        `${event.action === "afterCreate" ? "Adding" : "Updating"}
                ${model.singularName}
                ${entryId} ${event.action === "afterCreate" ? "to" : "in"} Algolia index ${index}`
      );
      const client = algoliaClient();


      const populateObject = await strapi.entityService.findOne(model.uid, entryId, { populate: populate ?? "*", });
      if (populateObject.publishedAt === null && event.action === "afterCreate") {
        strapi.log.info("created draft")
      }
      if (populateObject.publishedAt !== null && event.action === "afterCreate") {
        strapi.log.info("created publish")
        console.log("1", { objectID: entryId, ...populateObject })
        const { taskID } = await client.initIndex(index).saveObject({

          objectID: entryId, ...populateObject
        });

        const status = await client.waitForTask({ indexName: index, taskID });
        console.log(`Algolia indexing status: ${status.status}`);

      }
      if (populateObject.publishedAt === null && event.action === "afterUpdate") {
        strapi.log.info("updated from published to draft")
        const { taskID } = await client.initIndex(index).deleteObject(entryId);
        // const status = await client.waitForTask({ indexName: index, taskID });
        // console.log(`Algolia indexing status: ${status.status}`);
      }
      if (populateObject.publishedAt !== null && event.action === "afterUpdate") {
        strapi.log.info("updated from draft to published")

        console.log("2", { objectID: entryId, ...populateObject })
        const { taskID } = await client.initIndex(index).saveObject({
          objectID: entryId, ...populateObject
        });


        // const status = await client.waitForTask({ indexName: index, taskID });
        // console.log(`Algolia indexing status: ${status.status}`);
      }
    }
    catch (error) {
      console.log(`Error while updating Algolia index: ${JSON.stringify(error)}`);
    }
  },
  async delete(event, options) {
    try {
      const { index, many } = options;
      const objectIDs = many ? event?.params?.where?.["$and"][0]?.id["$in"] : [event.params.where.id];
      const client = algoliaClient();

      console.log(`Deleting object(s) with ID(s) ${objectIDs.join(",")} from Algolia index ${index}`);

      const requests = objectIDs.map((objectID) => {
        return {
          objectID,
          action: "deleteObject",
        };
      });
      // const { taskID } = await client.batch({ indexName: index, batchWriteParams: { requests }, });
      const { taskID } = await client.initIndex(index).deleteObjects(objectIDs);
      // const response = await client.waitForTask({ indexName: index, taskID });

      console.log(`Successfully deleted object(s) with ID(s) ${objectIDs.join(",")} from Algolia index ${index}`);
    } catch (error) {
      console.log(`Error while deleting object(s) from Algolia index: ${error}`);
    }
  },
});

@SamSaprykin
Copy link

hey guys @krankos @andrew-braun, thanks for sharing your solutions here. Did you have any issues after publishing this custom plugin to production? I mean for me everything works great on localhost but when I try to use strpapi cloud instance, indexing doesn't work for some reason. Thanks in advance!

@krankos
Copy link

krankos commented Sep 10, 2023

In our case it's working in prod. However, we're not using strapi cloud, we're self hosting since we've been using strapi before strapi cloud was launched. Check your environmental variables. The issue might be as simple as that.

@hunterxp
Copy link

@andrew-braun @krankos thanks for sharing the ideas and the code!

@andrew-braun
Copy link

hey guys @krankos @andrew-braun, thanks for sharing your solutions here. Did you have any issues after publishing this custom plugin to production? I mean for me everything works great on localhost but when I try to use strpapi cloud instance, indexing doesn't work for some reason. Thanks in advance!

Nope, it works in all my environments. If you're still having issues, feel free to drop logs of any errors you might be getting and I'll see if I can spot anything!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

6 participants