chore(repo): reinitialize repository
This commit is contained in:
@@ -0,0 +1,160 @@
|
||||
package sqlite
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"inbox/internal/base/idgen"
|
||||
"inbox/internal/base/timeutil"
|
||||
)
|
||||
|
||||
func (s *Store) now() string {
|
||||
return timeutil.FormatRFC3339(s.clock.Now())
|
||||
}
|
||||
|
||||
func (s *Store) newID(kind string) (string, error) {
|
||||
return idgen.NewGenerator(s.clock, nil).New(kind)
|
||||
}
|
||||
|
||||
type versionedConfigCurrent struct {
|
||||
ID string
|
||||
Version int
|
||||
CreatedAt string
|
||||
}
|
||||
|
||||
type versionedConfigMetadata struct {
|
||||
Action string
|
||||
ID string
|
||||
Version int
|
||||
CreatedAt string
|
||||
UpdatedAt string
|
||||
}
|
||||
|
||||
type versionedConfigUpsertSpec[T any] struct {
|
||||
entityName string
|
||||
idKind string
|
||||
loadTx func(context.Context, *sql.Tx) (T, error)
|
||||
current func(T) versionedConfigCurrent
|
||||
applyMetadata func(*T, versionedConfigMetadata)
|
||||
writeTx func(context.Context, *sql.Tx, T) error
|
||||
}
|
||||
|
||||
func upsertVersionedConfig[T any](ctx context.Context, s *Store, value T, changedBy string, spec versionedConfigUpsertSpec[T]) (T, error) {
|
||||
var zero T
|
||||
|
||||
tx, err := s.db.BeginTx(ctx, nil)
|
||||
if err != nil {
|
||||
return zero, fmt.Errorf("begin upsert %s: %w", spec.entityName, err)
|
||||
}
|
||||
defer tx.Rollback()
|
||||
|
||||
before, err := spec.loadTx(ctx, tx)
|
||||
exists := err == nil
|
||||
if err != nil {
|
||||
if !errors.Is(err, sql.ErrNoRows) {
|
||||
return zero, err
|
||||
}
|
||||
before = zero
|
||||
}
|
||||
|
||||
meta, err := s.nextVersionedConfigMetadata(spec.idKind, spec.current(before), exists)
|
||||
if err != nil {
|
||||
return zero, err
|
||||
}
|
||||
spec.applyMetadata(&value, meta)
|
||||
|
||||
if err := spec.writeTx(ctx, tx, value); err != nil {
|
||||
return zero, fmt.Errorf("upsert %s: %w", spec.entityName, err)
|
||||
}
|
||||
|
||||
after, err := spec.loadTx(ctx, tx)
|
||||
if err != nil {
|
||||
return zero, err
|
||||
}
|
||||
if err := tx.Commit(); err != nil {
|
||||
return zero, fmt.Errorf("commit upsert %s: %w", spec.entityName, err)
|
||||
}
|
||||
return after, nil
|
||||
}
|
||||
|
||||
func (s *Store) nextVersionedConfigMetadata(idKind string, current versionedConfigCurrent, exists bool) (versionedConfigMetadata, error) {
|
||||
now := s.now()
|
||||
meta := versionedConfigMetadata{
|
||||
Action: "update",
|
||||
ID: current.ID,
|
||||
Version: current.Version + 1,
|
||||
CreatedAt: current.CreatedAt,
|
||||
UpdatedAt: now,
|
||||
}
|
||||
if exists {
|
||||
return meta, nil
|
||||
}
|
||||
|
||||
id, err := s.newID(idKind)
|
||||
if err != nil {
|
||||
return versionedConfigMetadata{}, err
|
||||
}
|
||||
meta.Action = "create"
|
||||
meta.ID = id
|
||||
meta.Version = 1
|
||||
meta.CreatedAt = now
|
||||
return meta, nil
|
||||
}
|
||||
|
||||
type scanner interface {
|
||||
Scan(dest ...any) error
|
||||
}
|
||||
|
||||
func marshalJSONMapString(v map[string]string) (string, error) {
|
||||
if len(v) == 0 {
|
||||
return "{}", nil
|
||||
}
|
||||
data, err := json.Marshal(v)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("marshal json map string: %w", err)
|
||||
}
|
||||
return string(data), nil
|
||||
}
|
||||
|
||||
func marshalJSONMapAny(v map[string]any) (string, error) {
|
||||
if len(v) == 0 {
|
||||
return "{}", nil
|
||||
}
|
||||
data, err := json.Marshal(v)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("marshal json map: %w", err)
|
||||
}
|
||||
return string(data), nil
|
||||
}
|
||||
|
||||
func unmarshalJSON(src string, dst any) error {
|
||||
if strings.TrimSpace(src) == "" {
|
||||
src = "{}"
|
||||
}
|
||||
return json.Unmarshal([]byte(src), dst)
|
||||
}
|
||||
|
||||
func nullableString(value string) any {
|
||||
if value == "" {
|
||||
return nil
|
||||
}
|
||||
return value
|
||||
}
|
||||
|
||||
func coalesceString(value, fallback string) string {
|
||||
if value != "" {
|
||||
return value
|
||||
}
|
||||
return fallback
|
||||
}
|
||||
|
||||
func boolToInt(value bool) int {
|
||||
if value {
|
||||
return 1
|
||||
}
|
||||
return 0
|
||||
}
|
||||
Reference in New Issue
Block a user