merged from gramsrv upstream
This commit is contained in:
parent
79c64ee916
commit
21a0856587
651 changed files with 54774 additions and 4590 deletions
179
internal/app/userprojection/projection_sparse.go
Normal file
179
internal/app/userprojection/projection_sparse.go
Normal file
|
|
@ -0,0 +1,179 @@
|
|||
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
|
||||
)
|
||||
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 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)
|
||||
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
|
||||
}
|
||||
}
|
||||
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)
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue