 9bdcbe0447
			
		
	
	9bdcbe0447
	
	
	
		
			
			Major integrations and fixes: - Added BACKBEAT SDK integration for P2P operation timing - Implemented beat-aware status tracking for distributed operations - Added Docker secrets support for secure license management - Resolved KACHING license validation via HTTPS/TLS - Updated docker-compose configuration for clean stack deployment - Disabled rollback policies to prevent deployment failures - Added license credential storage (CHORUS-DEV-MULTI-001) Technical improvements: - BACKBEAT P2P operation tracking with phase management - Enhanced configuration system with file-based secrets - Improved error handling for license validation - Clean separation of KACHING and CHORUS deployment stacks 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
		
			
				
	
	
		
			310 lines
		
	
	
		
			8.0 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			310 lines
		
	
	
		
			8.0 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| package datastore
 | |
| 
 | |
| import (
 | |
| 	"encoding/json"
 | |
| 	"path"
 | |
| 	"strings"
 | |
| 
 | |
| 	dsq "github.com/ipfs/go-datastore/query"
 | |
| 
 | |
| 	"github.com/google/uuid"
 | |
| )
 | |
| 
 | |
| /*
 | |
| A Key represents the unique identifier of an object.
 | |
| Our Key scheme is inspired by file systems and Google App Engine key model.
 | |
| 
 | |
| Keys are meant to be unique across a system. Keys are hierarchical,
 | |
| incorporating more and more specific namespaces. Thus keys can be deemed
 | |
| 'children' or 'ancestors' of other keys::
 | |
| 
 | |
|     Key("/Comedy")
 | |
|     Key("/Comedy/MontyPython")
 | |
| 
 | |
| Also, every namespace can be parametrized to embed relevant object
 | |
| information. For example, the Key `name` (most specific namespace) could
 | |
| include the object type::
 | |
| 
 | |
|     Key("/Comedy/MontyPython/Actor:JohnCleese")
 | |
|     Key("/Comedy/MontyPython/Sketch:CheeseShop")
 | |
|     Key("/Comedy/MontyPython/Sketch:CheeseShop/Character:Mousebender")
 | |
| 
 | |
| */
 | |
| type Key struct {
 | |
| 	string
 | |
| }
 | |
| 
 | |
| // NewKey constructs a key from string. it will clean the value.
 | |
| func NewKey(s string) Key {
 | |
| 	k := Key{s}
 | |
| 	k.Clean()
 | |
| 	return k
 | |
| }
 | |
| 
 | |
| // RawKey creates a new Key without safety checking the input. Use with care.
 | |
| func RawKey(s string) Key {
 | |
| 	// accept an empty string and fix it to avoid special cases
 | |
| 	// elsewhere
 | |
| 	if len(s) == 0 {
 | |
| 		return Key{"/"}
 | |
| 	}
 | |
| 
 | |
| 	// perform a quick sanity check that the key is in the correct
 | |
| 	// format, if it is not then it is a programmer error and it is
 | |
| 	// okay to panic
 | |
| 	if len(s) == 0 || s[0] != '/' || (len(s) > 1 && s[len(s)-1] == '/') {
 | |
| 		panic("invalid datastore key: " + s)
 | |
| 	}
 | |
| 
 | |
| 	return Key{s}
 | |
| }
 | |
| 
 | |
| // KeyWithNamespaces constructs a key out of a namespace slice.
 | |
| func KeyWithNamespaces(ns []string) Key {
 | |
| 	return NewKey(strings.Join(ns, "/"))
 | |
| }
 | |
| 
 | |
| // Clean up a Key, using path.Clean.
 | |
| func (k *Key) Clean() {
 | |
| 	switch {
 | |
| 	case len(k.string) == 0:
 | |
| 		k.string = "/"
 | |
| 	case k.string[0] == '/':
 | |
| 		k.string = path.Clean(k.string)
 | |
| 	default:
 | |
| 		k.string = path.Clean("/" + k.string)
 | |
| 	}
 | |
| }
 | |
| 
 | |
| // Strings is the string value of Key
 | |
| func (k Key) String() string {
 | |
| 	return k.string
 | |
| }
 | |
| 
 | |
| // Bytes returns the string value of Key as a []byte
 | |
| func (k Key) Bytes() []byte {
 | |
| 	return []byte(k.string)
 | |
| }
 | |
| 
 | |
| // Equal checks equality of two keys
 | |
| func (k Key) Equal(k2 Key) bool {
 | |
| 	return k.string == k2.string
 | |
| }
 | |
| 
 | |
| // Less checks whether this key is sorted lower than another.
 | |
| func (k Key) Less(k2 Key) bool {
 | |
| 	list1 := k.List()
 | |
| 	list2 := k2.List()
 | |
| 	for i, c1 := range list1 {
 | |
| 		if len(list2) < (i + 1) {
 | |
| 			return false
 | |
| 		}
 | |
| 
 | |
| 		c2 := list2[i]
 | |
| 		if c1 < c2 {
 | |
| 			return true
 | |
| 		} else if c1 > c2 {
 | |
| 			return false
 | |
| 		}
 | |
| 		// c1 == c2, continue
 | |
| 	}
 | |
| 
 | |
| 	// list1 is shorter or exactly the same.
 | |
| 	return len(list1) < len(list2)
 | |
| }
 | |
| 
 | |
| // List returns the `list` representation of this Key.
 | |
| //   NewKey("/Comedy/MontyPython/Actor:JohnCleese").List()
 | |
| //   ["Comedy", "MontyPythong", "Actor:JohnCleese"]
 | |
| func (k Key) List() []string {
 | |
| 	return strings.Split(k.string, "/")[1:]
 | |
| }
 | |
| 
 | |
| // Reverse returns the reverse of this Key.
 | |
| //   NewKey("/Comedy/MontyPython/Actor:JohnCleese").Reverse()
 | |
| //   NewKey("/Actor:JohnCleese/MontyPython/Comedy")
 | |
| func (k Key) Reverse() Key {
 | |
| 	l := k.List()
 | |
| 	r := make([]string, len(l))
 | |
| 	for i, e := range l {
 | |
| 		r[len(l)-i-1] = e
 | |
| 	}
 | |
| 	return KeyWithNamespaces(r)
 | |
| }
 | |
| 
 | |
| // Namespaces returns the `namespaces` making up this Key.
 | |
| //   NewKey("/Comedy/MontyPython/Actor:JohnCleese").Namespaces()
 | |
| //   ["Comedy", "MontyPython", "Actor:JohnCleese"]
 | |
| func (k Key) Namespaces() []string {
 | |
| 	return k.List()
 | |
| }
 | |
| 
 | |
| // BaseNamespace returns the "base" namespace of this key (path.Base(filename))
 | |
| //   NewKey("/Comedy/MontyPython/Actor:JohnCleese").BaseNamespace()
 | |
| //   "Actor:JohnCleese"
 | |
| func (k Key) BaseNamespace() string {
 | |
| 	n := k.Namespaces()
 | |
| 	return n[len(n)-1]
 | |
| }
 | |
| 
 | |
| // Type returns the "type" of this key (value of last namespace).
 | |
| //   NewKey("/Comedy/MontyPython/Actor:JohnCleese").Type()
 | |
| //   "Actor"
 | |
| func (k Key) Type() string {
 | |
| 	return NamespaceType(k.BaseNamespace())
 | |
| }
 | |
| 
 | |
| // Name returns the "name" of this key (field of last namespace).
 | |
| //   NewKey("/Comedy/MontyPython/Actor:JohnCleese").Name()
 | |
| //   "JohnCleese"
 | |
| func (k Key) Name() string {
 | |
| 	return NamespaceValue(k.BaseNamespace())
 | |
| }
 | |
| 
 | |
| // Instance returns an "instance" of this type key (appends value to namespace).
 | |
| //   NewKey("/Comedy/MontyPython/Actor").Instance("JohnClesse")
 | |
| //   NewKey("/Comedy/MontyPython/Actor:JohnCleese")
 | |
| func (k Key) Instance(s string) Key {
 | |
| 	return NewKey(k.string + ":" + s)
 | |
| }
 | |
| 
 | |
| // Path returns the "path" of this key (parent + type).
 | |
| //   NewKey("/Comedy/MontyPython/Actor:JohnCleese").Path()
 | |
| //   NewKey("/Comedy/MontyPython/Actor")
 | |
| func (k Key) Path() Key {
 | |
| 	s := k.Parent().string + "/" + NamespaceType(k.BaseNamespace())
 | |
| 	return NewKey(s)
 | |
| }
 | |
| 
 | |
| // Parent returns the `parent` Key of this Key.
 | |
| //   NewKey("/Comedy/MontyPython/Actor:JohnCleese").Parent()
 | |
| //   NewKey("/Comedy/MontyPython")
 | |
| func (k Key) Parent() Key {
 | |
| 	n := k.List()
 | |
| 	if len(n) == 1 {
 | |
| 		return RawKey("/")
 | |
| 	}
 | |
| 	return NewKey(strings.Join(n[:len(n)-1], "/"))
 | |
| }
 | |
| 
 | |
| // Child returns the `child` Key of this Key.
 | |
| //   NewKey("/Comedy/MontyPython").Child(NewKey("Actor:JohnCleese"))
 | |
| //   NewKey("/Comedy/MontyPython/Actor:JohnCleese")
 | |
| func (k Key) Child(k2 Key) Key {
 | |
| 	switch {
 | |
| 	case k.string == "/":
 | |
| 		return k2
 | |
| 	case k2.string == "/":
 | |
| 		return k
 | |
| 	default:
 | |
| 		return RawKey(k.string + k2.string)
 | |
| 	}
 | |
| }
 | |
| 
 | |
| // ChildString returns the `child` Key of this Key -- string helper.
 | |
| //   NewKey("/Comedy/MontyPython").ChildString("Actor:JohnCleese")
 | |
| //   NewKey("/Comedy/MontyPython/Actor:JohnCleese")
 | |
| func (k Key) ChildString(s string) Key {
 | |
| 	return NewKey(k.string + "/" + s)
 | |
| }
 | |
| 
 | |
| // IsAncestorOf returns whether this key is a prefix of `other`
 | |
| //   NewKey("/Comedy").IsAncestorOf("/Comedy/MontyPython")
 | |
| //   true
 | |
| func (k Key) IsAncestorOf(other Key) bool {
 | |
| 	// equivalent to HasPrefix(other, k.string + "/")
 | |
| 
 | |
| 	if len(other.string) <= len(k.string) {
 | |
| 		// We're not long enough to be a child.
 | |
| 		return false
 | |
| 	}
 | |
| 
 | |
| 	if k.string == "/" {
 | |
| 		// We're the root and the other key is longer.
 | |
| 		return true
 | |
| 	}
 | |
| 
 | |
| 	// "other" starts with /k.string/
 | |
| 	return other.string[len(k.string)] == '/' && other.string[:len(k.string)] == k.string
 | |
| }
 | |
| 
 | |
| // IsDescendantOf returns whether this key contains another as a prefix.
 | |
| //   NewKey("/Comedy/MontyPython").IsDescendantOf("/Comedy")
 | |
| //   true
 | |
| func (k Key) IsDescendantOf(other Key) bool {
 | |
| 	return other.IsAncestorOf(k)
 | |
| }
 | |
| 
 | |
| // IsTopLevel returns whether this key has only one namespace.
 | |
| func (k Key) IsTopLevel() bool {
 | |
| 	return len(k.List()) == 1
 | |
| }
 | |
| 
 | |
| // MarshalJSON implements the json.Marshaler interface,
 | |
| // keys are represented as JSON strings
 | |
| func (k Key) MarshalJSON() ([]byte, error) {
 | |
| 	return json.Marshal(k.String())
 | |
| }
 | |
| 
 | |
| // UnmarshalJSON implements the json.Unmarshaler interface,
 | |
| // keys will parse any value specified as a key to a string
 | |
| func (k *Key) UnmarshalJSON(data []byte) error {
 | |
| 	var key string
 | |
| 	if err := json.Unmarshal(data, &key); err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 	*k = NewKey(key)
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| // RandomKey returns a randomly (uuid) generated key.
 | |
| //   RandomKey()
 | |
| //   NewKey("/f98719ea086343f7b71f32ea9d9d521d")
 | |
| func RandomKey() Key {
 | |
| 	return NewKey(strings.Replace(uuid.New().String(), "-", "", -1))
 | |
| }
 | |
| 
 | |
| /*
 | |
| A Key Namespace is like a path element.
 | |
| A namespace can optionally include a type (delimited by ':')
 | |
| 
 | |
|     > NamespaceValue("Song:PhilosopherSong")
 | |
|     PhilosopherSong
 | |
|     > NamespaceType("Song:PhilosopherSong")
 | |
|     Song
 | |
|     > NamespaceType("Music:Song:PhilosopherSong")
 | |
|     Music:Song
 | |
| */
 | |
| 
 | |
| // NamespaceType is the first component of a namespace. `foo` in `foo:bar`
 | |
| func NamespaceType(namespace string) string {
 | |
| 	parts := strings.Split(namespace, ":")
 | |
| 	if len(parts) < 2 {
 | |
| 		return ""
 | |
| 	}
 | |
| 	return strings.Join(parts[0:len(parts)-1], ":")
 | |
| }
 | |
| 
 | |
| // NamespaceValue returns the last component of a namespace. `baz` in `f:b:baz`
 | |
| func NamespaceValue(namespace string) string {
 | |
| 	parts := strings.Split(namespace, ":")
 | |
| 	return parts[len(parts)-1]
 | |
| }
 | |
| 
 | |
| // KeySlice attaches the methods of sort.Interface to []Key,
 | |
| // sorting in increasing order.
 | |
| type KeySlice []Key
 | |
| 
 | |
| func (p KeySlice) Len() int           { return len(p) }
 | |
| func (p KeySlice) Less(i, j int) bool { return p[i].Less(p[j]) }
 | |
| func (p KeySlice) Swap(i, j int)      { p[i], p[j] = p[j], p[i] }
 | |
| 
 | |
| // EntryKeys
 | |
| func EntryKeys(e []dsq.Entry) []Key {
 | |
| 	ks := make([]Key, len(e))
 | |
| 	for i, e := range e {
 | |
| 		ks[i] = NewKey(e.Key)
 | |
| 	}
 | |
| 	return ks
 | |
| }
 |