identity_subcache.go

  1package cache
  2
  3import (
  4	"fmt"
  5
  6	"github.com/MichaelMure/git-bug/entities/identity"
  7	"github.com/MichaelMure/git-bug/entity"
  8	"github.com/MichaelMure/git-bug/repository"
  9)
 10
 11type RepoCacheIdentity struct {
 12	*SubCache[*identity.Identity, *IdentityExcerpt, *IdentityCache]
 13}
 14
 15func NewRepoCacheIdentity(repo repository.ClockedRepo,
 16	resolvers func() entity.Resolvers,
 17	getUserIdentity getUserIdentityFunc) *RepoCacheIdentity {
 18
 19	makeCached := func(i *identity.Identity, entityUpdated func(id entity.Id) error) *IdentityCache {
 20		return NewIdentityCache(i, repo, entityUpdated)
 21	}
 22
 23	makeIndex := func(i *IdentityCache) []string {
 24		// no indexing
 25		return nil
 26	}
 27
 28	// TODO: this is terribly ugly, but we are currently stuck with the fact that identities are NOT using the fancy dag framework.
 29	//   This lead to various complication here and there to handle entities generically, and avoid large code duplication.
 30	//   TL;DR: something has to give, and this is the less ugly solution I found. This "normalize" identities as just another "dag framework"
 31	//   entity. Ideally identities would be converted to the dag framework, but right now that could lead to potential attack: if an old
 32	//   private key is leaked, it would be possible to craft a legal identity update that take over the most recent version. While this is
 33	//   meaningless in the case of a normal entity, it's really an issues for identities.
 34
 35	actions := Actions[*identity.Identity]{
 36		ReadWithResolver: func(repo repository.ClockedRepo, resolvers entity.Resolvers, id entity.Id) (*identity.Identity, error) {
 37			return identity.ReadLocal(repo, id)
 38		},
 39		ReadAllWithResolver: func(repo repository.ClockedRepo, resolvers entity.Resolvers) <-chan entity.StreamedEntity[*identity.Identity] {
 40			return identity.ReadAllLocal(repo)
 41		},
 42		Remove:    identity.Remove,
 43		RemoveAll: identity.RemoveAll,
 44		MergeAll: func(repo repository.ClockedRepo, resolvers entity.Resolvers, remote string, mergeAuthor entity.Interface) <-chan entity.MergeResult {
 45			return identity.MergeAll(repo, remote)
 46		},
 47	}
 48
 49	sc := NewSubCache[*identity.Identity, *IdentityExcerpt, *IdentityCache](
 50		repo, resolvers, getUserIdentity,
 51		makeCached, NewIdentityExcerpt, makeIndex, actions,
 52		identity.Typename, identity.Namespace,
 53		formatVersion, defaultMaxLoadedBugs,
 54	)
 55
 56	return &RepoCacheIdentity{SubCache: sc}
 57}
 58
 59// ResolveIdentityImmutableMetadata retrieve an Identity that has the exact given metadata on
 60// one of its version. If multiple version have the same key, the first defined take precedence.
 61func (c *RepoCacheIdentity) ResolveIdentityImmutableMetadata(key string, value string) (*IdentityCache, error) {
 62	return c.ResolveMatcher(func(excerpt *IdentityExcerpt) bool {
 63		return excerpt.ImmutableMetadata[key] == value
 64	})
 65}
 66
 67// New create a new identity
 68// The new identity is written in the repository (commit)
 69func (c *RepoCacheIdentity) New(name string, email string) (*IdentityCache, error) {
 70	return c.NewRaw(name, email, "", "", nil, nil)
 71}
 72
 73// NewFull create a new identity
 74// The new identity is written in the repository (commit)
 75func (c *RepoCacheIdentity) NewFull(name string, email string, login string, avatarUrl string, keys []*identity.Key) (*IdentityCache, error) {
 76	return c.NewRaw(name, email, login, avatarUrl, keys, nil)
 77}
 78
 79func (c *RepoCacheIdentity) NewRaw(name string, email string, login string, avatarUrl string, keys []*identity.Key, metadata map[string]string) (*IdentityCache, error) {
 80	i, err := identity.NewIdentityFull(c.repo, name, email, login, avatarUrl, keys)
 81	if err != nil {
 82		return nil, err
 83	}
 84	return c.finishIdentity(i, metadata)
 85}
 86
 87func (c *RepoCacheIdentity) NewFromGitUser() (*IdentityCache, error) {
 88	return c.NewFromGitUserRaw(nil)
 89}
 90
 91func (c *RepoCacheIdentity) NewFromGitUserRaw(metadata map[string]string) (*IdentityCache, error) {
 92	i, err := identity.NewFromGitUser(c.repo)
 93	if err != nil {
 94		return nil, err
 95	}
 96	return c.finishIdentity(i, metadata)
 97}
 98
 99func (c *RepoCacheIdentity) finishIdentity(i *identity.Identity, metadata map[string]string) (*IdentityCache, error) {
100	for key, value := range metadata {
101		i.SetMetadata(key, value)
102	}
103
104	err := i.Commit(c.repo)
105	if err != nil {
106		return nil, err
107	}
108
109	c.mu.Lock()
110	if _, has := c.cached[i.Id()]; has {
111		return nil, fmt.Errorf("identity %s already exist in the cache", i.Id())
112	}
113
114	cached := NewIdentityCache(i, c.repo, c.entityUpdated)
115	c.cached[i.Id()] = cached
116	c.mu.Unlock()
117
118	// force the write of the excerpt
119	err = c.entityUpdated(i.Id())
120	if err != nil {
121		return nil, err
122	}
123
124	return cached, nil
125}