Ctrl K
ComunidadGuías…

internal/app/userprojection/projection_sparse.go

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
package userprojection

import (
	"context"
	"errors"

	"golang.org/x/sync/errgroup"

	"telesrv/internal/domain"
	"telesrv/internal/store"
)

var (
	ErrSparseContactProjectionUnsupported = errors.New("sparse contact projection is not supported")
	ErrSparsePrivacyProjectionUnsupported = errors.New("sparse privacy projection is not supported")
)

// SparsePrivacyEvaluator evaluates only the supplied viewer->owner pairs. The
// inverse contact rows are supplied from the projector's shared sparse contact
// read so privacy does not issue another contact query.
type SparsePrivacyEvaluator interface {
	CanSeeForViewerUserIDs(
		ctx context.Context,
		ownerUserIDsByViewer map[int64][]int64,
		keys []domain.PrivacyKey,
		contactsByOwner map[int64]map[int64]domain.Contact,
	) (map[int64]map[int64]map[domain.PrivacyKey]bool, error)
}

// ForViewerUserIDs projects a sparse viewer->owner graph. Viewer-independent
// facts are loaded for the union once; viewer-specific facts are read only for
// graph edges that occur in the request (plus their inverse contact edge needed
// by privacy evaluation).
func (p *Projector) ForViewerUserIDs(ctx context.Context, userIDsByViewer map[int64][]int64, baseUsers []domain.User) (map[int64][]domain.User, error) {
	requested := normalizeSparseUserIDs(userIDsByViewer)
	out := make(map[int64][]domain.User, len(requested))
	if len(requested) == 0 {
		return out, nil
	}
	baseUsers = sanitizeDeletedUsers(baseUsers)
	baseByID := make(map[int64]domain.User, len(baseUsers))
	for _, user := range baseUsers {
		if user.ID != 0 {
			baseByID[user.ID] = user
		}
	}
	if p == nil {
		for viewerID, ids := range requested {
			out[viewerID] = sparseBaseUsers(ids, baseByID)
		}
		return out, nil
	}
	unionIDs := make([]int64, 0, len(baseByID))
	seenUnion := make(map[int64]struct{}, len(baseByID))
	contactPairs := make(map[int64][]int64)
	privacyPairs := make(map[int64][]int64)
	for viewerID, ids := range requested {
		for _, ownerID := range ids {
			user, found := baseByID[ownerID]
			if !found {
				continue
			}
			if _, ok := seenUnion[ownerID]; !ok && !user.Deleted {
				seenUnion[ownerID] = struct{}{}
				unionIDs = append(unionIDs, ownerID)
			}
			if user.Deleted || ownerID == viewerID {
				continue
			}
			// Personal-photo overlay applies independently of contact/privacy
			// exemptions, so retain every real viewer->owner edge here.
			contactPairs[viewerID] = append(contactPairs[viewerID], ownerID)
			if ownerID == domain.OfficialSystemUserID || user.Bot {
				continue
			}
			privacyPairs[viewerID] = append(privacyPairs[viewerID], ownerID)
			// Privacy's ViewerIsContact is the inverse owner->viewer row. Merge it
			// into the same exact-pair store call.
			contactPairs[ownerID] = append(contactPairs[ownerID], viewerID)
		}
	}

	var (
		profileRefs       map[int64]domain.ProfilePhotoRef
		fallbackRefs      map[int64]domain.ProfilePhotoRef
		contactBatch      domain.ContactProjectionBatch
		visibility        map[int64]map[int64]map[domain.PrivacyKey]bool
		freezes           map[int64]domain.AccountFreeze
		collectiblePhones map[int64]domain.CollectiblePhone
	)
	g, gctx := errgroup.WithContext(ctx)
	g.Go(func() error {
		var err error
		profileRefs, fallbackRefs, err = p.batchProfileFallbackPhotos(gctx, unionIDs)
		return err
	})
	if p.contacts != nil && len(contactPairs) > 0 {
		g.Go(func() error {
			loader, ok := p.contacts.(store.SparseContactProjectionStore)
			if !ok {
				return ErrSparseContactProjectionUnsupported
			}
			var err error
			contactBatch, err = loader.ContactProjectionForViewerUserIDs(gctx, contactPairs)
			return err
		})
	}
	if p.freezes != nil && len(unionIDs) > 0 {
		g.Go(func() error {
			var err error
			freezes, err = p.freezes.AccountFreezes(gctx, unionIDs)
			return err
		})
	}
	if p.phones != nil && len(unionIDs) > 0 {
		g.Go(func() error {
			var err error
			collectiblePhones, err = p.phones.OwnedCollectiblePhones(gctx, unionIDs)
			return err
		})
	}
	if err := g.Wait(); err != nil {
		return nil, err
	}
	if p.privacy != nil && len(privacyPairs) > 0 {
		evaluator, ok := p.privacy.(SparsePrivacyEvaluator)
		if !ok {
			return nil, ErrSparsePrivacyProjectionUnsupported
		}
		var err error
		visibility, err = evaluator.CanSeeForViewerUserIDs(ctx, privacyPairs, privacyProjectionKeys, contactBatch.Contacts)
		if err != nil {
			return nil, err
		}
	}

	for viewerID, ids := range requested {
		projected := sparseBaseUsers(ids, baseByID)
		personalRefs := contactBatch.PersonalPhotos[viewerID]
		for i := range projected {
			user := projected[i]
			if user.Deleted {
				projected[i] = user.DeletedTombstone()
				continue
			}
			user = applyBasePhotos(user, profileRefs, fallbackRefs, personalRefs, viewerID)
			if viewerID != 0 && user.ID != viewerID && user.ID != domain.OfficialSystemUserID && !user.Bot {
				contact, found := contactBatch.Contacts[viewerID][user.ID]
				user = applyContactProjection(user, contact, found)
				user = applyCollectiblePhone(user, collectiblePhones[user.ID])
				var vis map[domain.PrivacyKey]bool
				if visibility != nil {
					vis = visibility[user.ID][viewerID]
				}
				var err error
				user, err = applyPrivacy(ctx, p.privacy, viewerID, user, found && contact.Phone != "", vis, profileRefs, fallbackRefs, personalRefs)
				if err != nil {
					return nil, err
				}
			}
			if viewerID == 0 || user.ID == viewerID || user.ID == domain.OfficialSystemUserID || user.Bot {
				user = applyCollectiblePhone(user, collectiblePhones[user.ID])
			}
			user = reapplyExclusiveCollectiblePhone(user, collectiblePhones[user.ID])
			user = applyAccountFreezeProjection(user, viewerID, freezes[user.ID])
			projected[i] = user
		}
		out[viewerID] = projected
	}
	return out, nil
}

func normalizeSparseUserIDs(in map[int64][]int64) map[int64][]int64 {
	out := make(map[int64][]int64, len(in))
	for viewerID, ids := range in {
		if viewerID == 0 {
			continue
		}
		out[viewerID] = dedupNonZeroInt64(ids)
	}
	return out
}

func sparseBaseUsers(ids []int64, baseByID map[int64]domain.User) []domain.User {
	users := make([]domain.User, 0, len(ids))
	for _, id := range ids {
		if user, ok := baseByID[id]; ok {
			users = append(users, user)
		}
	}
	return cloneUsers(users)
}