package api import ( "context" "net/http" "git.golem15.com/golem15/summercms/modules/wire" "git.golem15.com/jakub/sm-bm-plugin/models" "gorm.io/gorm" ) // A write of a material's content may carry the revision the writer last // read (D-01, D-04). When it does and the stored revision differs, the write // is refused with 409 and nothing is written; when it does not, the write // overwrites as before, so callers that never send a revision keep working. // The compare and the write run on the row locked FOR UPDATE, so two writers // holding the same revision cannot both succeed. // conflictMessage is the message of a 409 answer to a stale revision. const conflictMessage = "The material was changed since the revision you sent." // staleRevision reports whether a write that sent revision sent must be // refused: true when it sent one and it is not the stored one. func staleRevision(sent *uint, stored uint) bool { return sent != nil && *sent != stored } // revise bumps the revision of material id inside tx when its content // differs from before (models.ReviseMaterial). Every content writer of the // API calls it after its write. func (h *Handlers) revise(ctx context.Context, tx *gorm.DB, id uint, before string) error { _, _, err := models.ReviseMaterial(tx.WithContext(ctx), id, before) return err } // writeConflict answers 409 to a write that sent a stale revision, with the // current revision, its updated_at and the material as the management API // shows it, so the writer can diff or pull. Under MCP the body is left out, // as in every other MCP answer. func writeConflict(w http.ResponseWriter, r *http.Request, db *gorm.DB, id uint) { var cur models.Material if err := preloadMaterial(db).First(&cur, id).Error; err != nil { writeOpaque500(w) return } people, err := loadMaterialPeople(db, cur) if err != nil { writeOpaque500(w) return } wire.WriteJSON(w, http.StatusConflict, map[string]any{ "error": true, "message": conflictMessage, "revision": cur.Revision, "updated_at": models.FormatDateTime(cur.UpdatedAt), "data": managedMaterialView(newManagedMaterialJSON(cur, people), viaMCP(r), false), }) }