refac: atomic merge, lost-update race, RunPurge cost, duplicated walks

This commit is contained in:
Florian Sylvain committed 2026-10-21 22:47:46 +02:00
1 parent 0fc3511e2b
commit 074944c99a
45 files changed
+2204 -267

No files matched your search

@@ -115,6 +115,21 @@ func (f Field) validDefault() bool {
return !f.Unique && f.Violation(f.Default) == ""
}
type MediaRef struct {
Field Field
ID uint32
}
func MediaRefs(fields []Field, data map[string]any) []MediaRef {
var refs []MediaRef
for _, field := range MediaFields(fields) {
if id, set := MediaID(data[field.Key]); set {
refs = append(refs, MediaRef{Field: field, ID: id})
}
}
return refs
}
func MediaFields(fields []Field) []Field {
var media []Field
for _, field := range fields {
+12 -7
View File
@@ -51,12 +51,12 @@ func validateField(field contenttype.Field, data map[string]any) error {
value := data[field.Key]
if value == nil {
if field.Required {
return invalidField(field.Key, RuleRequired)
return InvalidField(field.Key, RuleRequired)
}
return nil
}
if rule := field.Violation(value); rule != "" {
return invalidField(field.Key, rule)
return InvalidField(field.Key, rule)
}
return nil
}
@@ -76,7 +76,7 @@ func firstUnknownField(fields []contenttype.Field, data map[string]any) error {
return nil
}
sort.Strings(unknown)
return invalidField(unknown[0], RuleUnknownField)
return InvalidField(unknown[0], RuleUnknownField)
}
func UniqueValues(fields []contenttype.Field, data map[string]any) []UniqueValue {
@@ -91,10 +91,15 @@ func UniqueValues(fields []contenttype.Field, data map[string]any) []UniqueValue
func MediaIDs(fields []contenttype.Field, data map[string]any) map[string]uint32 {
ids := map[string]uint32{}
for _, field := range contenttype.MediaFields(fields) {
if id, ok := contenttype.MediaID(data[field.Key]); ok {
ids[field.Key] = id
}
for _, ref := range contenttype.MediaRefs(fields, data) {
ids[ref.Field.Key] = ref.ID
}
return ids
}
func MissingMedia(field contenttype.Field, unchanged bool) error {
if unchanged {
return InvalidField(field.Key, RuleRequired)
}
return InvalidField(field.Key, contenttype.RuleType)
}
+1 -5
View File
@@ -55,12 +55,8 @@ func (e FieldError) Unwrap() error {
return e.Cause
}
func invalidField(key string, rule string) error {
return FieldError{Key: key, Rule: rule, Cause: ErrInvalidEntry}
}
func InvalidField(key string, rule string) error {
return invalidField(key, rule)
return FieldError{Key: key, Rule: rule, Cause: ErrInvalidEntry}
}
func UniqueViolation(key string) error {
+49
View File
@@ -246,3 +246,52 @@ type Image struct {
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
}
func isStoredNameByte(char byte) bool {
return char == '-' || char == '_' || char == '.' || char >= '0' && char <= '9' || char >= 'a' && char <= 'z' || char >= 'A' && char <= 'Z'
}
func continuesName(body string, end int) bool {
if end >= len(body) || !isStoredNameByte(body[end]) {
return false
}
return body[end] != '.' || end+1 < len(body) && isStoredNameByte(body[end+1])
}
func storedNameMatches(body string, name string) []int {
var starts []int
if name == "" {
return starts
}
for from := 0; from < len(body); {
found := strings.Index(body[from:], name)
if found < 0 {
break
}
start := from + found
end := start + len(name)
from = start + 1
if start > 0 && isStoredNameByte(body[start-1]) || continuesName(body, end) {
continue
}
starts = append(starts, start)
from = end
}
return starts
}
func ContainsStoredName(body string, name string) bool {
return len(storedNameMatches(body, name)) > 0
}
func ReplaceStoredName(body string, from string, to string) string {
var rewritten strings.Builder
last := 0
for _, start := range storedNameMatches(body, from) {
rewritten.WriteString(body[last:start])
rewritten.WriteString(to)
last = start + len(from)
}
rewritten.WriteString(body[last:])
return rewritten.String()
}
@@ -0,0 +1,22 @@
package transaction
import (
"RenewCMS/internal/domain/article"
"RenewCMS/internal/domain/author"
"RenewCMS/internal/domain/entry"
"RenewCMS/internal/domain/image"
"RenewCMS/internal/domain/site"
"context"
)
type Repositories struct {
Articles article.Repository
Sites site.Repository
Authors author.Repository
Entries entry.Repository
Images image.Repository
}
type Transactor interface {
Run(ctx context.Context, work func(Repositories) error) error
}