Giter Site home page Giter Site logo

yurigor / mongoose-slug-updater Goto Github PK

View Code? Open in Web Editor NEW
38.0 1.0 12.0 170 KB

Schema-based slug plugin for Mongoose - single/compound - unique over collection/group - nested docs/arrays - relative/abs paths - sync on change: create/save/update/updateOne/updateMany/findOneAndUpdate tracked - $set operator - counter/shortId

License: MIT License

JavaScript 100.00%
mongoose slug url permalink slugify pretty-url unique

mongoose-slug-updater's Introduction

mongoose-slug-updater

Sophisticated slugifier plugin for Mongoose.

There is also PlanZed.org - awesome cloud mind map app created by the author of mongoose-slug-updater.
Plz check it, it's free and I need feedback ๐Ÿ˜‰

npm Travis (.org) Coverage Status
NPM

Features:

Installation

The best way to install it is using npm

npm install mongoose-slug-updater --save

Loading

var slug = require('mongoose-slug-updater');

Initialization

var mongoose = require('mongoose');
mongoose.plugin(slug);

Usage

This plugin is based on the idea of using the mongoose schema as the way to check the use of slug fields.

The plugin checks and updates automatically the slug field with the correct slug.

Basic Usage

If you only want to create the slug based on a simple field.

var mongoose = require('mongoose'),
    slug = require('mongoose-slug-updater'),
    mongoose.plugin(slug),
    Schema = mongoose.Schema,
    schema = new Schema({
        title: String,
        slug: { type: String, slug: "title" }
});

Multiple slug fields

You can add as many slug fields as you wish

var mongoose = require('mongoose'),
    slug = require('mongoose-slug-updater'),
    mongoose.plugin(slug),
    Schema = mongoose.Schema,
    schema = new Schema({
        title: String,
        subtitle: String,
        slug: { type: String, slug: "title" },
        slug2: { type: String, slug: "title" },
        slug3: { type: String, slug: "subtitle" }
});

Multiple fields to create the slug

If you want, you can use more than one field in order to create a new slug field.

var mongoose = require('mongoose'),
    slug = require('mongoose-slug-updater'),
    mongoose.plugin(slug),
    Schema = mongoose.Schema,
    schema = new Schema({
        title: String,
        subtitle: String,
        slug: { type: String, slug: ["title", "subtitle"] }
});

Transform Slug

This option accepts a funtion which receives actual field value and can be used to tranform value before generating slug.

var mongoose = require('mongoose'),
    slug = require('mongoose-slug-updater'),
    mongoose.plugin(slug),
    Schema = mongoose.Schema,
    schema = new Schema({
        title: String,
        subtitle: String,
        slug: { type: String, slug: ["title", "subtitle"], transform: v => stripHtmlTags(v) }
});

Unique slug field

To create a unique slug field, you must only add add the unique: true parameter in the path (also, this way the default mongo unique index gets created)

var mongoose = require('mongoose'),
    slug = require('mongoose-slug-updater'),
    mongoose.plugin(slug),
    Schema = mongoose.Schema,
    schema = new Schema({
        title: String,
        subtitle: String,
        slug: { type: String, slug: ["title", "subtitle"], unique: true }
});

If unique or uniqueSlug is set, the plugin searches in the mongo database, and if the slug already exists in the collection, it appends to the slug a separator (default: "-") and a random string (generated with the shortid module).

example random

mongoose.model('Resource').create({
    title: "Am I wrong, fallin' in love with you!",
    subtitle: "tell me am I wrong, well, fallin' in love with you",
}); // slug -> 'am-i-wrong-fallin-in-love-with-you'

mongoose.model('Resource').create({
    title: "Am I wrong, fallin' in love with you!",
    subtitle: "tell me am I wrong, well, fallin' in love with you",
}); // slug -> 'am-i-wrong-fallin-in-love-with-you-Nyiy4wW9l'

mongoose.model('Resource').create({
    title: "Am I wrong, fallin' in love with you!",
    subtitle: "tell me am I wrong, well, fallin' in love with you",
}); // slug -> 'am-i-wrong-fallin-in-love-with-you-NJeskEPb5e'

Alternatively you can modify this behaviour and instead of appending a random string, an incremental counter will be used. For that to happen, you must use the parameter slugPaddingSize specifying the total length of the counter:

example counter

var mongoose = require('mongoose'),
    slug = require('mongoose-slug-updater'),
    mongoose.plugin(slug),
    Schema = mongoose.Schema,
    schema = new Schema({
        title: String,
        subtitle: String,
        slug: { type: String, slug: ["title", "subtitle"], slugPaddingSize: 4,  unique: true }
});

mongoose.model('Resource').create({
    title: 'Am I wrong, fallin\' in love with you!',
    subtitle: "tell me am I wrong, well, fallin' in love with you"
}) // slug -> 'am-i-wrong-fallin-in-love-with-you'

mongoose.model('Resource').create({
    title: 'Am I wrong, fallin\' in love with you!',
    subtitle: "tell me am I wrong, well, fallin' in love with you"
}) // slug -> 'am-i-wrong-fallin-in-love-with-you-0001'

mongoose.model('Resource').create({
    title: 'Am I wrong, fallin\' in love with you!',
    subtitle: "tell me am I wrong, well, fallin' in love with you"
}) // slug -> 'am-i-wrong-fallin-in-love-with-you-0002'

If you don't want to define your field as unique for some reasons, but still need slug to be unique,
you can use uniqueSlug:true option instead of unique.
This option will not cause index creation, but still will be considered by the plugin.

forceIdSlug option will append shortId even if no duplicates were found.
This is useful for applications with high chance of concurrent modification of unique fields.

Check for conflict made by plugin is not atomic with subsequent insert/update operation,
so there is a possibility of external change of data in the moment between check and write.
If this happened, mongo will throw unique index violation error.
Chances of such case higher for counter unique mode, but with shortId this is possible too.
You can just retry operation, so plugin will check collection again and regenerate correct unique slug.
Or you can set forceIdSlug option - this will solve the problem completely, but you will pay for this by less readabilty of your slugs, because they will always be appended with random string.

In most cases write operations not so frequent to care about possible conflicts.

note: forceIdSlug option will also overwite unique to the true, and slugPaddingSize option will be ignored.

Unique slug within a group

Sometimes you only want slugs to be unique within a specific group.
This is done with the uniqueGroupSlug property which is an array of fields to group by:

example unique per group (using the field named 'group')

ResourceGroupedUnique = new mongoose.Schema({
    title: { type: String },
    subtitle: { type: String },
    group: { type: String },
    uniqueSlug: {
        type: String,
        uniqueGroupSlug: ['group'],
        slugPaddingSize: 4,
        slug: 'title',
        index: true,
    },
});

mongoose.model('ResourceGroupedUnique').create({
    title: "Am I wrong, fallin' in love with you!",
    subtitle: "tell me am I wrong, well, fallin' in love with you",
    group: 'group 1',
}); // slug -> 'am-i-wrong-fallin-in-love-with-you'

mongoose.model('ResourceGroupedUnique').create({
    title: "Am I wrong, fallin' in love with you!",
    subtitle: "tell me am I wrong, well, fallin' in love with you",
    group: 'group 2',
}); // slug -> 'am-i-wrong-fallin-in-love-with-you'

mongoose.model('ResourceGroupedUnique').create({
    title: "Am I wrong, fallin' in love with you!",
    subtitle: "tell me am I wrong, well, fallin' in love with you",
    group: 'group 1',
}); // slug -> 'am-i-wrong-fallin-in-love-with-you-0001'

mongoose.model('ResourceGroupedUnique').create({
    title: "Am I wrong, fallin' in love with you!",
    subtitle: "tell me am I wrong, well, fallin' in love with you",
    group: 'group 2',
}); // slug -> 'am-i-wrong-fallin-in-love-with-you-0001'

Important: you must not have a unique: true option, but it's a good idea to have an index: true option.

Nested unique slugs

MongoDB supports unique index for nested arrays elements, but he checks for duplication conflicts only on per-document basis, so inside document duplicate nested array's elements are still allowed.
mongoose-slug-updater works differently. It checks slug for duplicates both in current documentts's nested array and in other documents, considering uniqueGroupSlug option, if specified.

example of nested unique slugs

const UniqueNestedSchema = new mongoose.Schema({
  children: [
    {
      subchildren: [
        {
          title: { type: String },
          slug: {
            type: String,
            slug: 'title',
            unique: true // 'global' unique slug
            slugPaddingSize: 4,
          },
          slugLocal: {
            type: String,
            slug: 'title',
            index: true,
            slugPaddingSize: 4,
            uniqueGroupSlug: '/_id',// slug unique within current document
          },
        },
      ],
    },
  ],
});
mongoose.model('UniqueNestedSchema').create({
  children:[
    {
      subchildren:[
        {
          title: "Am I wrong, fallin' in love with you!"
          // slug       -> 'am-i-wrong-fallin-in-love-with-you'
          // slugLocal  -> 'am-i-wrong-fallin-in-love-with-you'
        },
        {
          title: "Am I wrong, fallin' in love with you!"
          // slug       -> 'am-i-wrong-fallin-in-love-with-you-0001'
          // slugLocal  -> 'am-i-wrong-fallin-in-love-with-you-0001'
        },
      ]
    },
    {
      subchildren:[
        {
          title: "Am I wrong, fallin' in love with you!"
          // slug       -> 'am-i-wrong-fallin-in-love-with-you-0002'
          // slugLocal  -> 'am-i-wrong-fallin-in-love-with-you-0002'
        },
        {
          title: "Am I wrong, fallin' in love with you!"
          // slug       -> 'am-i-wrong-fallin-in-love-with-you-0003'
          // slugLocal  -> 'am-i-wrong-fallin-in-love-with-you-0003'
        },
      ]
    },
  ]
});
mongoose.model('UniqueNestedSchema').create({
  children:[
    {
      subchildren:[
        {
          title: "Am I wrong, fallin' in love with you!"
          // slug       -> 'am-i-wrong-fallin-in-love-with-you-0004'
          // slugLocal  -> 'am-i-wrong-fallin-in-love-with-you'
        },
        {
          title: "Am I wrong, fallin' in love with you!"
          // slug       -> 'am-i-wrong-fallin-in-love-with-you-0005'
          // slugLocal  -> 'am-i-wrong-fallin-in-love-with-you-0001'
        },
      ]
    },
    {
      subchildren:[
        {
          title: "Am I wrong, fallin' in love with you!"
          // slug       -> 'am-i-wrong-fallin-in-love-with-you-0006'
          // slugLocal  -> 'am-i-wrong-fallin-in-love-with-you-0002'
        },
        {
          title: "Am I wrong, fallin' in love with you!"
          // slug       -> 'am-i-wrong-fallin-in-love-with-you-0007'
          // slugLocal  -> 'am-i-wrong-fallin-in-love-with-you-0003'
        },
      ]
    },
  ]
});

In case of change unique slug related fields (source fields from slug option or group criteria from uniqueGroupSlug)
slug will be regenerated considering latest existing duplicate. Presence or lack of the older duplicates, including original slug, will not be taken into account.

Updating slug or keeping it permanent

By default slugs will be created/updated for any related fields changed by any of create(it's actually a save too), save, update, updateOne, updateMany and findOneAndUpdate operations. You can specify which of supported methods should be watched:

const HooksSchema = new mongoose.Schema({
    title: { type: String },
    slug: {
        type: String,
        slug: 'title',
        //by default all hooks are enabled
        //slugOn:{ save: true, update: true, updateOne: true, updateMany: true, findOneAndUpdate: true }
    },
    slugNoSave: { type: String, slug: 'title', slugOn: { save: false } },
    slugNoUpdate: { type: String, slug: 'title', slugOn: { update: false } },
    slugNoUpdateOne: { type: String, slug: 'title', slugOn: { updateOne: false } },
    slugNoUpdateMany: {
        type: String,
        slug: 'title',
        slugOn: { updateMany: false },
    },
    slugNoFindOneAndUpdate: {
        type: String,
        slug: 'title',
        slugOn: { findOneAndUpdate: false },
    },
});

Note, that flags will affect both creation and updating of documents.
If you disabled save and still want slug to be generated initially, create method will not work,
becacuse mongoose emits save event both for save and create methods.
Use upsert option of update*** methods instead.

For update and updateMany methods multiply affected records also handled, but be careful with performance, because one-by-one iteration over affected documents may happen in case of unique slugs.
In this case _id field is required.

For update* family of operations additional queries may be performed, to retrieve data missing in the query (fields not listed in the query but needed for compound or grouped unique slugs).

permanent option

If you want to generate slug initially, but keep it unchanged during further modifications of related fields, use permanent flag like this:

ResourcePermanent = new mongoose.Schema({
    title: { type: String },
    subtitle: { type: String },
    otherField: { type: String },
    slug: { type: String, slug: ['title', 'subtitle'] }, //normal slug
    titleSlug: { type: String, slug: 'title', permanent: true }, //permanent slug
    subtitleSlug: {
        type: String,
        slug: 'subtitle',
        permanent: true, //permanent option
        slugPaddingSize: 4,
    },
});

Nested docs. Relative and absolute paths.

Nested docs and arrays declared inline right in the scheme or as a nested schemas declared separately are also supported.

Slug fields can be declared as relative or absolute(starting with slash) path to any point of current document.

Since MongoDB uses dot path notation, colon : symbol used for relative paths as a reference to the parent, same as double dot .. for file system paths.

Example of scheme with inline nested docs:

const InlineSchema = new mongoose.Schema({
  // root title
  title: { type: String },
  // root slug with relative path to root title
  slug: { type: String, slug: 'title' },
  // root slug with  absolute path to root title
  absoluteSlug: { type: String, slug: '/title' },
  // root slug with relative path to child title
  childSlug: { type: String, slug: 'child.title' },
  // root slug with absolute path to child title
  absoluteChildSlug: { type: String, slug: '/child.title' },
  // root slug with relative path to child's subchild title
  subChildSlug: { type: String, slug: 'child.subChild.title' },
  // root slug with relative path to the title of first children array element
  childrenSlug0: { type: String, slug: 'children.0.title' },
  // root slug with relative path to the title of 5th children array element
  childrenSlug4: { type: String, slug: 'children.4.title' },
  // root slug with relative path to the title of 4th subChildren' element of first children array element
  subChildrenSlug3: { type: String, slug: 'children.0.subChildren.3.title' },
  // root slug with relative path to the title of 8th subChildren' element of first children array element
  subChildrenSlug7: { type: String, slug: 'children.0.subChildren.7.title' },
  subChildrenSlug5SubChild: {
    type: String,
    // well, you see)
    slug: 'children.0.subChildren.5.subChild.title',
  },
  subChildrenSlug2SubChild: {
    type: String,
    slug: 'children.0.subChildren.2.subChild.title',
  },
  child: {
    title: { type: String },
    // inside nested doc relative path starts from current object,
    // so this is slug for child's title
    slug: { type: String, slug: 'title' },
    // absolute variant of path above, starting from root
    absoluteSlug: { type: String, slug: '/child.title' },
    // child's slug field generated for root title, absolute path
    absoluteParentSlug: { type: String, slug: '/title' },
    // relative path with parent reference `:`, so here root title will be used again.
    relativeParentSlug: { type: String, slug: ':title' },
    subChild: {
      title: { type: String },
      // relative path to the title of current nested doc,
      // in absolute form it wil be /child.subChild.title
      slug: { type: String, slug: 'title' },
      // absolute path to the root title
      absoluteParentSlug: { type: String, slug: '/title' },
      // relative path to the parent title, /child.title in this case
      relativeParentSlug: { type: String, slug: ':title' },
      // parent of the parent is root, so ::title = /title here
      relativeGrandParentSlug: { type: String, slug: '::title' },
    },
  },
  // nested arrays work too
  children: [
    {
      title: { type: String },
      // title of current array element
      slug: { type: String, slug: 'title' },
      // root title
      absoluteRootSlug: { type: String, slug: '/title' },
      // child's title
      absoluteChildSlug: { type: String, slug: '/child.title' },
      // root title. Array itself not counted as a parent and skipped.
      relativeRootSlug: { type: String, slug: ':title' },
      // absolute path to 4th element of array
      absoluteSiblingSlug: { type: String, slug: '/children.3.title' },
      // same in relative form for 5th element
      relativeSiblingSlug: { type: String, slug: ':children.4.title' },
      subChild: {
        title: { type: String },
        // current title
        slug: { type: String, slug: 'title' },
        // root title
        absoluteParentSlug: { type: String, slug: '/title' },
        // child title
        absoluteChildSlug: { type: String, slug: '/child.title' },
        // title of current array element, because its a parent of this subChild
        relativeParentSlug: { type: String, slug: ':title' },
        // two parents up is a root
        relativeGrandParentSlug: { type: String, slug: '::title' },
      },
      // arrays nested into array elements, welcome to the depth
      subChildren: [
        {
          title: { type: String },
          // current title
          slug: { type: String, slug: 'title' },
          // root title
          absoluteRootSlug: { type: String, slug: '/title' },
          // child title
          absoluteChildSlug: { type: String, slug: '/child.title' },
          // :--> children :--> root
          relativeRootSlug: { type: String, slug: '::title' },
          absoluteSiblingSlug: {
            type: String,
            // I don't know who will need it but it works, check yourself in /test
            slug: '/children.0.subChildren.5.title',
          },
          // relative ref to another subChildren's element from current children's element
          relativeSiblingSlug: { type: String, slug: ':subChildren.6.title' },
          // hope you got it.
          subChild: {
            title: { type: String },
            slug: { type: String, slug: 'title' },
            absoluteParentSlug: { type: String, slug: '/title' },
            absoluteChildSlug: { type: String, slug: '/child.title' },
            relativeParentSlug: { type: String, slug: ':title' },
            relativeGrandParentSlug: { type: String, slug: '::title' },
          },
        },
      ],
    },
  ],
});

Example of nested schemas declared separately:

const SubChildSchema = new mongoose.Schema({
  title: { type: String },
  slug: { type: String, slug: 'title' },
  absoluteRootSlug: { type: String, slug: '/title' },
  absoluteChildSlug: { type: String, slug: '/child.title' },
  relativeParentSlug: { type: String, slug: ':title' },// child's title
  relativeGrandParentSlug: { type: String, slug: '::title' },//parent's title
});

const ChildSchema = new mongoose.Schema({
  title: { type: String },
  subChild: SubChildSchema,
  subChildren: [SubChildSchema],
  slug: { type: String, slug: 'title' },
  subChildSlug: { type: String, slug: 'subChild.title' },
  absoluteSlug: { type: String, slug: '/child.title' },
  absoluteRootSlug: { type: String, slug: '/title' },
  relativeParentSlug: { type: String, slug: ':title' },//Parent
  subChildrenSlug2: { type: String, slug: 'subChildren.2.title' },
  subChildrenSlug3: { type: String, slug: 'subChildren.3.title' },
});

const ParentSchema = new mongoose.Schema({
  title: { type: String },
  child: ChildSchema,
  children: [ChildSchema],
  slug: { type: String, slug: 'title' },
  absoluteSlug: { type: String, slug: '/title' },
  childSlug: { type: String, slug: 'child.title' },
  absoluteChildSlug: { type: String, slug: '/child.title' },
  subChildSlug: { type: String, slug: 'child.subChild.title' },
  childrenSlug0: { type: String, slug: 'children.0.title' },
  childrenSlug4: { type: String, slug: 'children.4.title' },
  subChildrenSlug3: { type: String, slug: 'children.7.subChildren.3.title' },
  subChildrenSlug7: { type: String, slug: 'children.3.subChildren.7.title' },
});

Updating by deep path via $set operator

This will work too:

  await SimpleInline.findOneAndUpdate(
    {/*some criteria*/},
    {
      $set: {
        title: 'New root title',
        'child.title': 'New nested title',
        'children.2.title': 'New title for the 3d item of nested array',
      },
    }
  );

All the slugs which depend on modified titles will be found and regenerated.
This is recommended way to do partial modifications.
When you perform updates by object value instead of path:value list,
unobvious data loss may happen for nested docs or arrays, if they contain slugs affected by your modification.
Plugin always checks will current update operation be made with $set operator or not, and adds extra slug fields to the query as an object fields or $set paths accordingly.

So if you do have whole document you want to change - better use save,
but if you dont have it, but you need to update some particular fields - it's more safe to use $set and paths:values.

Choose your own options

You can change any options adding to the plugin

var mongoose = require('mongoose'),
    slug = require('mongoose-slug-updater'),
    options = {
        separator: "-",
        lang: "en",
        truncate: 120,
        backwardCompatible: true//support for the old options names used in the mongoose-slug-generator
    },
    mongoose.plugin(slug, options),
    Schema = mongoose.Schema,
    schema = new Schema({
        title: String,
        subtitle: String,
        slug: { type: String, slug: ["title", "subtitle"], unique: true }
});

You can find more options in the speakingURL's npm page

Support

This plugin is supported by Yuri Gor

About

This plugin was initially forked from mongoose-slug-generator, which is not maintained currently.

Merged and fixed uniqueGroupSlug feature by rickogden.

update, updateOne, updateMany and findOneAndUpdate operations support implemented.

Nested docs and arrays support implemented.

Absolute and relative paths added.

Updating with $set operator and deep paths now works too.

All the update operators will be implemented soon.

Plugin rewritten with modern js and a lot of tests were added.

mongoose-slug-updater's People

Contributors

farhantahir avatar gelito avatar mykwillis avatar rhutchison avatar rickogden avatar yurigor avatar ztratar avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar

mongoose-slug-updater's Issues

Provide a way to skip slug generation if field was set

mongoose.model('Resource').create({
    title: "Am I wrong, fallin' in love with you!",
    subtitle: "tell me am I wrong, well, fallin' in love with you",
}); // slug -> 'am-i-wrong-fallin-in-love-with-you'

Should skip plugin when slug field was set

mongoose.model('Resource').create({
    title: "Am I wrong, fallin' in love with you!",
    subtitle: "tell me am I wrong, well, fallin' in love with you",
    slug: 'am-i-wrong-fallin-in-love-with-you'
});  

TypeError: Cannot read property '$set' of undefined

Running into this issue when using
model.create(item)
TypeError: Cannot read property '$set' of undefined
at model.Query.onUpdate (/Users/sabir/React/cyber-rwanda/website/api/node_modules/mongoose-slug-updater/lib/slug-generator.js:158:13)
at model.Query. (/Users/sabir/React/cyber-rwanda/website/api/node_modules/mongoose-slug-updater/lib/slug-generator.js:150:31)

This is particularly happening in a model which does not use slug.
I've tried removing the plugin and it works fine without it.
The only relation it has to slug is that it contains an Object reference to a Model which has a slug.
Couldn't figure out why the plugin is called in a model which does not implement it.

Thanks for the awesome plugin

Let MongoDB handle the uniqueness of the slug?

I would like slug to automatically generate the slug but if the slug already exists i would like MongoDB to throw the usual error as i would like to handle the error myself and let the user know and consequently change the username and/or other fields being slugged.

Unique slug counter refers to last created item

Hi!

First of all - thank you so much for your desire to maintain this plugin. Your efforts worth it ;)

Notice one interesting detail: I generate my slug from item title and has unique: true & slugPaddingSize: 2 properties in my schema. When the title is repeated it takes further counter (e.g. item-01).

For example, I have 10 items with the same title, so it last slug will be item-09. If I delete some of the previous items, for example, item-01 till item-08 new created item with the same title as previous will be item-10, not item-01.

Is it correct behavior? Should slug just referred to the last created item and increase the counter, or it should "fills" place of deleted one (item-01 in my case)?

Thank you for your assistance!)

unique slug creation in array of subdocuments

I am trying to implement unique slug in the array of the subdocuments. But I couldn't do it. As per the document, it is possible to create unique slug for array of subdocuments using uniqueGroupSlug: "/_id". But after implementing the code, after creating the document, there is no any slug field in subdocuments.

What I am expecting:
{ "_id": { "$oid": "5ecf78c94ba8f24acb8902ff" }, "title": "This is a long title", "children": [ { "_id": { "$oid": "5ecf78e64ba8f24acb890300" }, "title": "This is subtitle", "slug": "this-is-subtitle" } ], "slug": "this-is-a-long-title", "__v": { "$numberInt": "0" } }

What I'm getting:
{ "_id": { "$oid": "5ecf78c94ba8f24acb8902ff" }, "title": "This is a long title", "children": [ { "_id": { "$oid": "5ecf78e64ba8f24acb890300" }, "title": "This is subtitle" } ], "slug": "this-is-a-long-title", "__v": { "$numberInt": "0" } }

My mongoose model: scratchMongoSchema.js

const mongoose = require("mongoose");
const slug = `require("mongoose-slug-updater");`
mongoose.plugin(slug);

const childSchema = mongoose.Schema({
    title: {
        type: String,
        required: true,
        maxlength: 100,
    },
    slug: {
        type: String,
        slug: "title",
        index: true,
        slugPaddingSize: 4,
        uniqueGroupSlug: "/_id",
    },
});

const scratchSchema = mongoose.Schema({
    title: {
        type: String,
        required: true,
        maxlength: 100,
    },
    slug: {
        type: String,
        slug: "title",
        unique: true,
        slugPaddingSize: 4,
    },
    children: [childSchema],
    // children: [
    //     {
    //         title: {
    //             type: String,
    //             required: true,
    //             maxlength: 100,
    //         },
    //         slug: {
    //             type: String,
    //             slug: "title",
    //             index: true,
    //             slugPaddingSize: 4,
    //             uniqueGroupSlug: "/_id",
    //         },
    //     },
    // ],
});

module.exports = mongoose.model("Scratch", scratchSchema);

My graphql schema: scratchGraphqlSchema.js

const { gql } = require("apollo-server-express");

const scratchSchema = gql`
    type Parent {
        id: ID!
        title: String!
        slug: String!
        children: [Child!]
    }

    type Child {
        id: ID!
        title: String!
        slug: String!
    }

    input CreateParentInput {
        title: String!
    }

    input CreateChildInput {
        parentSlug: String!
        title: String!
    }

    extend type Mutation {
        createParent(data: CreateParentInput): Parent
        createChild(data: CreateChildInput): Child
    }
`;

module.exports = scratchSchema;

My graphql resolver: scratchResolver.js

const { Scratch } = require("../../src/mongoSchema");

const scratchResolver = {
    Parent: {
        children: async (parent) => {
            try {
                const scratch = await Scratch.findById(parent.id);
                return scratch.children;
            } catch (err) {
                throw err;
            }
        },
    },
    Mutation: {
        createParent: async (_, { data }) => {
            const { title } = data;
            try {
                return await Scratch.create({ title });
            } catch (err) {
                throw err;
            }
        },
        createChild: async (_, { data }) => {
            const { title, parentSlug } = data;
            try {
                const parent = await Scratch.findOneAndUpdate(
                    {
                        slug: parentSlug,
                        // "children.title": { $ne: title },
                    },
                    {
                        $push: {
                            children: { $each: [{ title }], $position: 0 },
                        },
                    },
                    {
                        new: true,
                        omitUndefined: true,
                        setDefaultsOnInsert: true,
                    }
                );
                console.log(parent);

                return parent.children[0];
            } catch (err) {
                throw err;
            }
        },
    },
};
module.exports = scratchResolver;

How can I update all existing documents to have a slug?

Hello!
I've created a web app without thinking since the beginning with slugs and now it's a mess.
I have about 150 documents in there.
How could I programmatically add a slug with this plugin?
I would have to change the slugified value for each one but each one has a different one.
Is there any solution? Cannot find anything in the docs!
Thanks a lot!

Package shortid is deprecated as well as the package used in this version depends on nanoid which is vulnerable

Hey @YuriGor , the shortid package has been deprecated and they have suggested to use nano since it is more fast and secure. The shortid package used in the current version of this package depends on nanoid which has security issues. Please, update this package ASAP replacing shortid package with latest version of nanoid. Thank you in advance!

node_modules/nanoid
  shortid  >=2.2.9
  Depends on vulnerable versions of nanoid
  node_modules/shortid
    mongoose-slug-updater  >=2.3.0
    Depends on vulnerable versions of shortid
    node_modules/mongoose-slug-updater

Update with $set on array fields throw conflict

I have collection Products with schema :

name: {
    type: String,
    index: 1
  },
  slug: { type: String, slug: ["name"], slugPaddingSize: 0, unique: true },
other_image_ids: {
     type: Array,
}

When i update with

Products.updateOne({_id: id}, {$set: { name: "test", other_image_ids: [{name: "imagename"}] }}, { new: true })

Mongo will throw error :

MongoError: Updating the path 'other_image_ids.0.name' would create a conflict at 'other_image_ids'

Package version
mongoose: 5.4.0
mongodb: 3.1.10
mongoose-slug-updater: 3.0.4

Duplicate slug generated when subdocument added to existing document

I have a document that has an array of subdocuments. Each subdocument contains a permanent slug unique to the parent:

const ChildSchema = mongoose.Schema({
  title: {
    type: String
  },
  slug: {
    type: String,
    slug: 'title',
    index: true,
    permanent: true,
    slugPaddingSize: 4,
    uniqueGroupSlug: '/_id',
  },
});

const ParentSchema = mongoose.Schema({
  children: [ChildSchema],
});

const ExampleModel = mongoose.model('ParentSchema', ParentSchema);

If I create an initial document with some children:

    const doc = await ExampleModel.create({
      children: [
        {
          title: 'This is nested subtitle',
        },
        {
          title: 'This is nested subtitle',
        },
      ],
    });

then everything is fine; both of the new subdocuments get properly generated and unique slugs.

However, if I update the document with another child subdocument:

    doc.children.push(
    {
      title: 'This is nested subtitle',
    });
    await doc.save()

then the new document receives a duplicate slug.

Manual overriding of slug

Hello Yuri!

I am trying to setup a field to enable overriding of slug but can't quite find a hook to do so.

Mongoose findOneAndUpdate({ slug }) doesn't work as probably the plugin runs after Mongoose did its job.

I've searched through the documentation but could not find this feature.

I am handling by manually changing slugs on Atlas but it's definitely not an optimal setup.

Is it possible to achieve this in any other way?

Thanks! :)

Integrate Transliteration

I'm currently working on a project which requires slug in native language. I would like to know if it's possible for you to at least tell me where to get started with integration of Transliteration module in mongoose-slug-updater.

Slug array after update

When I try to update the characteristics, the slug always has object values:
test-update-3-object-object-object-object

Schema:

import mongoose from 'mongoose';
import slug from 'mongoose-slug-updater';

mongoose.plugin(slug);

const productSchema = new mongoose.Schema(
  {
    name: {
      type: String,
      unique: true,
      trim: true,
      required: [true, 'Product must have a name'],
    },
    characteristics: [
      {
        name: {
          type: String,
          trim: true,
          required: [true, 'Characteristic must have a name'],
        },
        parameter: {
          type: String,
          trim: true,
          required: [true, 'Characteristic must have a parameter'],
        },
      },
    ],
    slug: {
      type: String,
      slug: ['name', 'characteristics.parameter'],
    },
  }
);

Method:

await Model.findByIdAndUpdate(req.params.id, req.body, {
      new: true,
      runValidators: true,
    });

Body:

{
    "name": "Test update 3",
    "characteristics": [
        {
            "name": "color",
            "parameter": "yelow"
        }
    ]
}

Is there possibility to add incremental counter without setting slugPaddingSize?

I'm thinking about use case where I would like to create unique slugs with incremental counter but without setting slugPaddingSize. At this moment when I set slugPaddingSize to 1 I see that behaviour:

title
title-1
title-2
title-3
title-4
title-5
title-6
title-7
title-8
title-9
title-10
title-10
...

And I would like to see something like this:

title
title-1
title-2
title-3
title-4
title-5
title-6
title-7
title-8
title-9
title-10
title-11
...

Is it possible to do that with this plugin?

Concatenated slugs

Hey,

I just found this library and it seems great so far. I had a quick question, would it be possible to concatenate two values to form my slug?

As an example, let's assume I am building a job board and I want the slug to be the job title followed by the company name. Could I do something like slug: 'title', 'at', 'company.name'.

Thanks!

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.