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

Versioning of Content types with Unique field #6

Open
JesperWe opened this issue Feb 5, 2022 · 22 comments
Open

Versioning of Content types with Unique field #6

JesperWe opened this issue Feb 5, 2022 · 22 comments
Labels
issue: feature request Issue suggesting a new feature severity: medium If it breaks the basic use of the product but can be worked around status: confirmed Confirmed by maintainer or multiple community members

Comments

@JesperWe
Copy link
Contributor

JesperWe commented Feb 5, 2022

Bug report

Describe the bug

If a content type has a field declared as "Unique Field" in the Content type builder, saving new versions after the initial v1 fails with

Validation Error: This field must be unique

System

  • Node.js version: v16
  • Plugin version: 0.2.1
  • Strapi version: 4.0.7
  • Database: Postgres v12
@martincapek
Copy link
Contributor

Hi thanks for reporting, we already know about this issue. It's know issue by design. Right now it is not priority for us, but we will look at it in future.

@martincapek martincapek added the issue: enhancement Issue suggesting an enhancement to an existing feature label Feb 9, 2022
@JesperWe JesperWe changed the title Versioning of Content types with Unique field fails Versioning of Content types with Unique field May 2, 2022
@dgrubelic
Copy link

Hi, I was just wondering if there is any progress on this one? Thx

@martincapek
Copy link
Contributor

Hi, not right now, we are focusing on content internalization versioning.

@darokviana
Copy link

Hey. This is a pity as UID fields are needed to work with Slugify and generate friendly URLs. It seems very important to me that they can work together. For now, I'll prioritize working with the slug system and wait until the versioning plugin is working along with it.

@lolcabanon
Copy link

Hey, just wanted to let you guys know : I was very excited about your plugin, but this bug is makin it unusable for me... Basically all my content types have Unique Fields.

I understand you have other stuff to address and that the plugin is under developpement, but for me fixing this should be a priority.

@martincapek
Copy link
Contributor

Hi, we are trying to find solution with Strapi, because this problem is under databaselayer (unque is setted up in DB not in Strapi) so we are still trying to find some workaround. If you have any ideas just write it here it can help :) Thanks!

@lolcabanon
Copy link

Good to know you are working on it! :D

@sushmavoleti
Copy link

Any update on fixing this bug, please?

@Poggioli
Copy link

Hello, @martincapek do you have any updates about this issue?!

@tasola
Copy link

tasola commented Feb 16, 2023

@martincapek any news on this issue?

In general - do you have advice on how to work with "slugs" when using this package? I want to achieve the fairly common use case to have my urls like [domain]/[content-type]/[slug OR id] on my consumer site.

I can't keep a slug on my content types because of this issue, and I can't use the entry's id either since it "changes" with every new saved version...

Surely, someone must have had this kind of problem before me. How did you achieve this?

@numr
Copy link

numr commented Feb 20, 2023

I also encountered the same problem,I hope to solve it

@ciriousjoker
Copy link

ciriousjoker commented Feb 23, 2023

@martincapek I haven't looked into this, but couldn't you add some sort of "magic prefix" to the ids stored in the database?

"some_id" becomes "_content_versioning#!randomstring!#__some_id" when saved in the database and when read back, just remove it based on a regex?

@z0lo13
Copy link

z0lo13 commented Mar 2, 2023

@martincapek Thank you for contributing to strapi and building this plugin.

However, featuring uid field is a very common practice for strapi projects.

Please consider bringing this fix.

In our understanding, most of the collections which need this plugin will have uid fields (slug, url, uuid).

I am not sure what is your implentation plans for this, but you could create a separate collection for collections this module is enabled.

Some other
if collection name "pages"
just create new collection "pages_versions"

And instead of creating a whole new entry for the pages collection, create new versions in that additional table.
IMHO right now it creates so much mess in the database and collection itself.

@thewickest
Copy link

Hey @martincapek,

What about a compound ID with the slug + version?
If the problem we have it is not possible to create another entity with the same slug because is unique, instead of having and ID with just the slug, could be possible to have it like slug + version with two different columns in the table?
That way we could have something like { id: {slug: 'my-slug', version: 'v1'}} and { id: {slug: 'my-slug', version: 'v2'}}

@omikulcik omikulcik added severity: medium If it breaks the basic use of the product but can be worked around status: confirmed Confirmed by maintainer or multiple community members labels Jun 23, 2023
@omikulcik
Copy link
Collaborator

Hello everyone, I will now be taking over the maintenance of this plugin. Over the next few weeks, our main priority will be to eliminate the need for patching the plugin. Once that is successfully completed, I assume that we shift our focus towards implementing this feature.

@omikulcik omikulcik added issue: feature request Issue suggesting a new feature and removed issue: enhancement Issue suggesting an enhancement to an existing feature labels Jun 23, 2023
@Daserec
Copy link

Daserec commented Aug 7, 2023

Hello @omikulcik , any plan on this feature being developed? Thanks for your work!

@omikulcik
Copy link
Collaborator

Hi @Daserec, it is definitely on the roadmap but we haven't yet decided on when to ship it.

@anderslars
Copy link

Hey - did this ever make it out of the backlog? If it's not happening, then I am going to have to set aside the content-versioning plugin and write something - maybe like a simple plugin that will deploy a published document version between 2 strapi instances. My app is fundamentally dependent on unique slugs. Figured I'd check in case you were about to knock it out, but I am guessing that your priorities are firmly elsewhere.
The frustrating part is that if you look back at Strapi core teams comments - they were actually going to build something like this into the product - but I am guessing when you all wrote the plugin, that need was mitigated and it fell deep into their backlog. But - as for many others - this issue make the current plugin unusable for me. Thanks

@omikulcik
Copy link
Collaborator

Hi @anderslars, sorry to hear that. Our priority right now is to finish support for relations so versioning of unique fields will not be shipped earlier than Q1 of 2024.

Just to provide more information, Strapi said that V5 will feature upgraded draft and publish including content versioning so this will probably resolve your issues. However, it can take some time till v5 is released.

@felixhagspiel
Copy link

@omikulcik is there any way to exclude UIDs (slugs) from being versioned? I.e. not throw an error, but just ignore them?

@bonfiglioalessio
Copy link

wait for it 🙏

@leomunizq
Copy link

Hello everyone, I believe that most of you have already given up on this, mainly because in the next version this function will be in the core.

anyway I tried to write something that would work for me, I don't know if it's the most elegant way to do it, and sure the code is a bit spaghetti, but you get the idea.

I followed some tips from here https://docs.strapi.io/dev-docs/configurations/functions

so I added a text field called 'slug' (not mandatory)
and in src/index.ts I wrote this code

basically this code generates the slug based on the title
between versions it allows the slug to remain the same
with each update it checks to see if the slug has already been used, but I check between published articles and that's why it allows several of the same slugs.
if it finds a published article with the same slug I add the version number to the end of the slug

It's an ugly solution, but for my context it works.

export default {
  /**
   * An asynchronous register function that runs before
   * your application is initialized.
   *
   * This gives you an opportunity to extend code.
   */
  register(/*{ strapi }*/) {},

  /**
   * An asynchronous bootstrap function that runs before
   * your application gets started.
   *
   * This gives you an opportunity to set up your data model,
   * run jobs, or perform some special logic.
   */
  bootstrap({ strapi }) {
    strapi.db.lifecycles.subscribe({
      models: ["api::article.article"], // listen to events for the article model
      beforeCreate: async ({ params }) => {
        
        const { data } = params;

        // Only generate slug if not provided by user
        if (!data.slug && data.title) {
          let newSlug = slugify(data.title);
          let isUnique = await checkPublishedSlugUnique(strapi, newSlug);

          // Ensure slug is unique using versionNumber if provided
          if (!isUnique && data.versionNumber) {
            newSlug = `${newSlug}-v${data.versionNumber}`;
            isUnique = await checkPublishedSlugUnique(strapi, newSlug);
          }

          // Final check
          if (!isUnique) {
            let version = data.versionNumber || 1;
            while (!isUnique) {
              version += 1;
              newSlug = `${newSlug}-v${version}`;
              isUnique = await checkPublishedSlugUnique(strapi, newSlug);
            }
          }

          data.slug = newSlug;
        } else if (data.slug) {
          let isUnique = await checkPublishedSlugUnique(strapi, data.slug);

          // Ensure slug is unique using versionNumber if provided
          if (!isUnique && data.versionNumber) {
            data.slug = `${data.slug}-v${data.versionNumber}`;
            isUnique = await checkPublishedSlugUnique(strapi, data.slug);
          }

          // Final check
          if (!isUnique) {
            let version = data.versionNumber || 1;
            while (!isUnique) {
              version += 1;
              data.slug = `${data.slug}-v${version}`;
              isUnique = await checkPublishedSlugUnique(strapi, data.slug);
            }
          }
        }
      },
      beforeUpdate: async ({ params }) => {
        const { data, where } = params;
        const id = where.id;

        // Only generate slug if not provided by user
        if (!data.slug && data.title) {
          let newSlug = slugify(data.title);
          let isUnique = await checkPublishedSlugUnique(strapi, newSlug, id);

          // Ensure slug is unique using versionNumber if provided
          if (!isUnique && data.versionNumber) {
            newSlug = `${newSlug}-v${data.versionNumber}`;
            isUnique = await checkPublishedSlugUnique(strapi, newSlug, id);
          }

          // Final check
          if (!isUnique) {
            let version = data.versionNumber || 1;
            while (!isUnique) {
              version += 1;
              newSlug = `${newSlug}-v${version}`;
              isUnique = await checkPublishedSlugUnique(strapi, newSlug, id);
            }
          }

          data.slug = newSlug;
        } else if (data.slug) {
          let isUnique = await checkPublishedSlugUnique(strapi, data.slug, id);

          // Ensure slug is unique using versionNumber if provided
          if (!isUnique && data.versionNumber) {
            data.slug = `${data.slug}-v${data.versionNumber}`;
            isUnique = await checkPublishedSlugUnique(strapi, data.slug, id);
          }

          // Final check
          if (!isUnique) {
            let version = data.versionNumber || 1;
            while (!isUnique) {
              version += 1;
              data.slug = `${data.slug}-v${version}`;
              isUnique = await checkPublishedSlugUnique(strapi, data.slug, id);
            }
          }
        }
      },
    });
  },

};

/**
 * Function to convert text to a URL-friendly slug
 * Replaces spaces with hyphens, removes non-word characters, 
 * and trims extra hyphens
 */
export function slugify(text) {
  return text
    .toString()
    .toLowerCase()
    .replace(/\s+/g, '-')           // Replace spaces with -
    .replace(/[^\w\-]+/g, '')       // Remove all non-word chars
    .replace(/\-\-+/g, '-')         // Replace multiple - with single -
    .replace(/^-+/, '')             // Trim - from start of text
    .replace(/-+$/, '');            // Trim - from end of text
}

/**
 * Function to check if the slug is unique among published articles
 * Optionally excludes a specific article by ID
 */
async function checkPublishedSlugUnique(strapi, slug, id = null) {
  const query: { slug: any; published_at: { $ne: any; }; id?: { $ne: any; } } = { slug, published_at: { $ne: null } }; // only check published articles
  if (id) {
    query.id = { $ne: id }; // exclude current record
  }
  const existingEntry = await strapi.db.query('api::article.article').findOne({ where: query });
  return !existingEntry;
};

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
issue: feature request Issue suggesting a new feature severity: medium If it breaks the basic use of the product but can be worked around status: confirmed Confirmed by maintainer or multiple community members
Projects
None yet
Development

No branches or pull requests