diff options
| author | ivar <i@oiee.no> | 2026-04-04 16:36:18 +0200 |
|---|---|---|
| committer | ivar <i@oiee.no> | 2026-04-04 16:36:18 +0200 |
| commit | 0d8f53520a2143b22e2246ab1ec25e0860e90dad (patch) | |
| tree | da1798fed5b7f4847538ee7b355a0397f28bbc33 /internal/admin | |
| parent | a6355e7a6530af3335c4cd8af05f1e9c8b978169 (diff) | |
| download | nebbet.no-0d8f53520a2143b22e2246ab1ec25e0860e90dad.tar.xz nebbet.no-0d8f53520a2143b22e2246ab1ec25e0860e90dad.zip | |
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 <noreply@anthropic.com>
Diffstat (limited to 'internal/admin')
| -rw-r--r-- | internal/admin/server.go | 46 |
1 files changed, 23 insertions, 23 deletions
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 |
