diff --git a/models/posts.js b/models/posts.js index 4d4e340..05d5969 100644 --- a/models/posts.js +++ b/models/posts.js @@ -15,7 +15,8 @@ module.exports = function PostModel(sequelize, DataTypes) { allowNull: false, }, status: { type: DataTypes.ENUM('draft', 'published', 'deleted'), allowNull: false, defaultValue: 'draft' }, - slug: { type: DataTypes.STRING, allowNull: false, unique: true }, + // 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, references: { diff --git a/routes/graphql/mutations/posts.js b/routes/graphql/mutations/posts.js index 87218f7..24622fc 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 } @@ -41,8 +48,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) - await post.save() + 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() + } return post }, diff --git a/routes/graphql/typeDefs/types.graphql b/routes/graphql/typeDefs/types.graphql index f01c2af..4234adf 100644 --- a/routes/graphql/typeDefs/types.graphql +++ b/routes/graphql/typeDefs/types.graphql @@ -64,7 +64,7 @@ type PostsList { type Post { id: ID! title: String! - slug: String! + slug: String status: PostStatus content: String htmlContent: String