From dac0659443ac93dcc6e0cb54e62e53c2c9549992 Mon Sep 17 00:00:00 2001 From: Patrik Spathon Date: Mon, 3 Aug 2026 21:44:18 +0200 Subject: [PATCH 1/2] Handle NULL post slugs from the new app The new app creates drafts with slug NULL until publish (posts.slug is being made nullable). Model allowNull, GraphQL Post.slug nullable, and editPost generates a slug when publishing a slug-less draft. Co-Authored-By: Claude Fable 5 --- models/posts.js | 3 ++- routes/graphql/mutations/posts.js | 4 ++++ routes/graphql/typeDefs/types.graphql | 2 +- 3 files changed, 7 insertions(+), 2 deletions(-) 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..6b9dc0c 100644 --- a/routes/graphql/mutations/posts.js +++ b/routes/graphql/mutations/posts.js @@ -41,6 +41,10 @@ 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 + if (!post.slug && post.status === 'published') { + post.slug = await generateSlug(Post, post.title) + } 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 From d1205514e1b4801cfa9a0a64ce55e9c663e05a1b Mon Sep 17 00:00:00 2001 From: Patrik Spathon Date: Mon, 3 Aug 2026 22:22:52 +0200 Subject: [PATCH 2/2] Fix slug lenght comment --- routes/graphql/mutations/posts.js | 24 +++++++++++++++++++----- 1 file changed, 19 insertions(+), 5 deletions(-) diff --git a/routes/graphql/mutations/posts.js b/routes/graphql/mutations/posts.js index 6b9dc0c..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 } @@ -42,11 +49,18 @@ const postMutations = { post.status = args.status post.group_id = args.groupId // Drafts from the new app have no slug until published - if (!post.slug && post.status === 'published') { - post.slug = await generateSlug(Post, post.title) - } + 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 },