From 0d8f53520a2143b22e2246ab1ec25e0860e90dad Mon Sep 17 00:00:00 2001 From: ivar Date: Sat, 4 Apr 2026 16:36:18 +0200 Subject: fix: make slug rename and content update atomic via RenameAndUpsertPost Previously RenamePost + UpsertPost were two separate DB calls; a failure between them left the post at the new slug with stale content. The new RenameAndUpsertPost method does both in a single transaction. Co-Authored-By: Claude Sonnet 4.6 --- internal/admin/server.go | 46 +++++++++++++++++++++++----------------------- 1 file changed, 23 insertions(+), 23 deletions(-) (limited to 'internal/admin') diff --git a/internal/admin/server.go b/internal/admin/server.go index dd2842a..c0400de 100644 --- a/internal/admin/server.go +++ b/internal/admin/server.go @@ -265,6 +265,16 @@ func (s *Server) handleEdit(c *gin.Context) { targetSlug := slug // default: slug unchanged + record := db.PostRecord{ + Slug: targetSlug, + Title: updated.Title, + Date: updated.Date, + Tags: updated.Tags, + Draft: updated.Draft, + Blocks: updated.Blocks, + UpdatedAt: time.Now().UnixMicro(), + } + if updated.Slug != "" && updated.Slug != slug { // Slug rename requested if !validSlug.MatchString(updated.Slug) { @@ -276,35 +286,25 @@ func (s *Server) handleEdit(c *gin.Context) { s.renderError(c, fmt.Sprintf("Post %q already exists", updated.Slug)) return } - // Perform rename (creates redirect, collapses chains) - if err := s.MetaDB.RenamePost(slug, updated.Slug); err != nil { + // Atomically rename and update content in one transaction + record.Slug = updated.Slug + if err := s.MetaDB.RenameAndUpsertPost(slug, record); err != nil { s.renderError(c, "Failed to rename post: "+err.Error()) return } - // Invalidate old cache + // Invalidate old cache and remove old search entry s.invalidatePostCache(slug) - // Remove old search index entry _ = s.SearchDB.DeletePage("/" + slug) targetSlug = updated.Slug - } - - // Update content under the (possibly new) slug - record := db.PostRecord{ - Slug: targetSlug, - Title: updated.Title, - Date: updated.Date, - Tags: updated.Tags, - Draft: updated.Draft, - Blocks: updated.Blocks, - UpdatedAt: time.Now().UnixMicro(), - } - if err := s.MetaDB.UpsertPost(record); err != nil { - c.HTML(http.StatusInternalServerError, "base", gin.H{ - "Title": "Error", - "ContentTemplate": "error-content", - "Message": err.Error(), - }) - return + } else { + if err := s.MetaDB.UpsertPost(record); err != nil { + c.HTML(http.StatusInternalServerError, "base", gin.H{ + "Title": "Error", + "ContentTemplate": "error-content", + "Message": err.Error(), + }) + return + } } // Invalidate cache for the target slug -- cgit v1.3