diff --git a/models/posts.js b/models/posts.js index 18ff36a..e53b097 100644 --- a/models/posts.js +++ b/models/posts.js @@ -17,6 +17,7 @@ module.exports = function PostModel(sequelize, DataTypes) { onUpdate: 'CASCADE', }, status: { type: DataTypes.ENUM('draft', 'published', 'deleted'), allowNull: false, defaultValue: 'draft' }, + // Nullable since the new app creates drafts with slug NULL until publish slug: { type: DataTypes.STRING, allowNull: true, unique: true }, group_id: { type: DataTypes.INTEGER.UNSIGNED, diff --git a/routes/graphql/mutations/posts.js b/routes/graphql/mutations/posts.js index 61f9553..8a788d9 100644 --- a/routes/graphql/mutations/posts.js +++ b/routes/graphql/mutations/posts.js @@ -4,10 +4,17 @@ const { Post } = require('../../../models') const { cleanContent } = require('../../../utils/content') +function timestampSlug(slug) { + // Keep room for the suffix — truncating after appending would return a + // 200-char base unchanged and keep the collision + const suffix = `-${Date.now()}` + return slug.substr(0, 200 - suffix.length) + suffix +} + async function generateSlug(Model, name) { const slug = slugify(name, { lower: true }).substr(0, 200) const slugExist = await Model.findOne({ where: { slug } }) - if (slugExist) return `${slug}-${Date.now()}`.substr(0, 200) + if (slugExist) return timestampSlug(slug) return slug } @@ -43,12 +50,19 @@ const postMutations = { post.content = cleanContent(args.content) post.status = args.status post.group_id = args.groupId + // Drafts from the new app have no slug until published + const mintedSlug = !post.slug && post.status === 'published' + if (mintedSlug) post.slug = await generateSlug(Post, post.title) - if (args.status === 'published' && !post.slug) { - post.slug = await generateSlug(Post, post.title) + try { + await post.save() + } catch (err) { + // slug is the only unique key — a concurrent publish won the + // check-then-save race, so retry once with a timestamped slug + if (!mintedSlug || err.name !== 'SequelizeUniqueConstraintError') throw err + post.slug = timestampSlug(post.slug) + await post.save() } - - await post.save() return post },