Files
silo-server/internal/userstore/pgstore/setting_mutation_tx.go
QuickandGitHub 3bdfc58512 feat(settings): sync navigation and card customization by client family (#538)
* test(web): use safe auth placeholders

* feat(settings): sync navigation and card customization

* fix(settings): address customization review feedback

* fix(settings): address customization review feedback

* fix(settings): harden customization capability handling
2026-08-04 08:20:41 -04:00

97 lines
2.9 KiB
Go

package pgstore
import (
"context"
"encoding/json"
"fmt"
"hash/fnv"
"strconv"
"github.com/Silo-Server/silo-server/internal/userstore"
)
const settingMutationAdvisoryClass int32 = 0x534d5554 // "SMUT"
type postgresSettingMutationWriter struct {
exec preferenceSettingsExecutor
userID int
}
var _ userstore.SettingMutationTransactioner = (*PostgresUserStore)(nil)
var _ userstore.SettingMutationWriter = (*postgresSettingMutationWriter)(nil)
// WithSettingMutationTransaction takes a transaction-scoped advisory lock
// before reading mutation_id. The hash includes the user, so unrelated
// accounts and mutation ids remain concurrent; a hash collision only causes
// harmless extra serialization. The setting and receipt commit together.
func (s *PostgresUserStore) WithSettingMutationTransaction(
ctx context.Context,
mutationID string,
fn func(userstore.SettingMutationWriter) error,
) error {
if mutationID == "" {
return fmt.Errorf("setting mutation transaction requires a mutation id")
}
tx, err := s.pool.Begin(ctx)
if err != nil {
return fmt.Errorf("beginning setting mutation transaction: %w", err)
}
defer tx.Rollback(ctx) //nolint:errcheck
lockHash := fnv.New32a()
_, _ = lockHash.Write([]byte(strconv.Itoa(s.userID)))
_, _ = lockHash.Write([]byte{0})
_, _ = lockHash.Write([]byte(mutationID))
if _, err := tx.Exec(ctx, "SELECT pg_advisory_xact_lock($1, $2)",
settingMutationAdvisoryClass, int32(lockHash.Sum32())); err != nil {
return fmt.Errorf("locking setting mutation transaction: %w", err)
}
writer := &postgresSettingMutationWriter{exec: tx, userID: s.userID}
if err := fn(writer); err != nil {
return err
}
if err := tx.Commit(ctx); err != nil {
return fmt.Errorf("committing setting mutation transaction: %w", err)
}
return nil
}
func (w *postgresSettingMutationWriter) GetSettingValue(
ctx context.Context,
id userstore.SettingIdentity,
) (*userstore.SettingValue, error) {
return getSettingValue(ctx, w.exec, w.userID, id)
}
func (w *postgresSettingMutationWriter) UpsertSettingValue(
ctx context.Context,
id userstore.SettingIdentity,
value json.RawMessage,
) (*userstore.SettingValue, error) {
return upsertSettingValue(ctx, w.exec, w.userID, id, value)
}
func (w *postgresSettingMutationWriter) CompareAndSetSettingValue(
ctx context.Context,
id userstore.SettingIdentity,
value json.RawMessage,
expectedRevision int64,
) (*userstore.SettingValue, error) {
return compareAndSetSettingValue(ctx, w.exec, w.userID, id, value, expectedRevision)
}
func (w *postgresSettingMutationWriter) GetSettingMutation(
ctx context.Context,
mutationID string,
) (*userstore.SettingMutationRecord, error) {
return getSettingMutation(ctx, w.exec, w.userID, mutationID)
}
func (w *postgresSettingMutationWriter) PutSettingMutation(
ctx context.Context,
record userstore.SettingMutationRecord,
) (userstore.SettingMutationRecord, bool, error) {
return putSettingMutation(ctx, w.exec, w.userID, record)
}