3
0
corteza/pkg/flag/flag.go

289 lines
6.9 KiB
Go

package flag
import (
"context"
"fmt"
"github.com/cortezaproject/corteza-server/pkg/flag/types"
"github.com/cortezaproject/corteza-server/store"
)
type (
FlaggedResource interface {
GetFlags() []string
SetFlags([]string)
FlagResourceKind() string
FlagResourceID() uint64
}
)
// Search returns a slice of IDs corresponding to the filtered flags
func Search(ctx context.Context, s store.Storer, owner uint64, kind string, flags ...string) ([]uint64, error) {
rr := make([]uint64, 0, 100)
ff, _, err := store.SearchFlags(ctx, s, types.FlagFilter{
Kind: kind,
OwnedBy: []uint64{0, owner},
Name: flags,
})
if err != nil {
return nil, err
}
// Little helper to generate a map index for the fetched label resource
mix := func(resID uint64) string {
return fmt.Sprintf("%s:%d", kind, resID)
}
// Firstly get all of the flags for the given user.
// Take note of inactive flags so we can filter them out of the global set
out := make(map[string]bool)
for _, f := range ff {
if f.OwnedBy != 0 {
if f.Active {
rr = append(rr, f.ResourceID)
} else {
out[mix(f.ResourceID)] = true
}
}
}
// Go over global flags, exclude any ignored flags
for _, f := range ff {
if f.OwnedBy == 0 {
if f.Active && !out[mix(f.ResourceID)] {
rr = append(rr, f.ResourceID)
}
}
}
return rr, nil
}
// Create creates a new flag for the given resource
//
// If that flag for that owner for this resource already exists, it's skipped.
// Access control and any other validations should be performed by the caller.
func Create(ctx context.Context, s store.Storer, r FlaggedResource, ownedBy uint64, flag string) error {
// Try to preload existing own flag
own, err := store.LookupFlagByKindResourceIDOwnedByName(ctx, s, r.FlagResourceKind(), r.FlagResourceID(), ownedBy, flag)
if err != nil && err != store.ErrNotFound {
return err
}
if own != nil && own.Active {
return fmt.Errorf("flag %s for resource %s %d already exists", flag, r.FlagResourceKind(), r.FlagResourceID())
}
// If we have an inactive flag, mark it as active
if own != nil && !own.Active {
own.Active = true
return store.UpdateFlag(ctx, s, own)
}
own = &types.Flag{
Kind: r.FlagResourceKind(),
ResourceID: r.FlagResourceID(),
OwnedBy: ownedBy,
Name: flag,
Active: true,
}
return store.CreateFlag(ctx, s, own)
}
// Delete removes the flag from this resource
//
// Access control and any other validations should be performed by the caller.
//
// This operation has two outcomes:
// * if we are removing a flag defined for a specifc user, it is deleted
// * if we are removing a flag defined globally (no owner), we create a new inactive flag
func Delete(ctx context.Context, s store.Storer, r FlaggedResource, ownedBy uint64, flag string) error {
var (
own *types.Flag
global *types.Flag
err error
)
// Try to find the global flag
global, err = store.LookupFlagByKindResourceIDOwnedByName(ctx, s, r.FlagResourceKind(), r.FlagResourceID(), 0, flag)
if err != nil && err != store.ErrNotFound {
return err
}
// Try to find own flag
own, err = store.LookupFlagByKindResourceIDOwnedByName(ctx, s, r.FlagResourceKind(), r.FlagResourceID(), ownedBy, flag)
if err != nil && err != store.ErrNotFound {
return err
}
if own == nil && global == nil {
return fmt.Errorf("flag not found for resource %s %d", r.FlagResourceKind(), r.FlagResourceID())
}
// If we're deleting global flag, do it
if ownedBy == 0 {
if global == nil {
return fmt.Errorf("global flag not found for %s %d", r.FlagResourceKind(), r.FlagResourceID())
}
return store.DeleteFlag(ctx, s, global)
}
// If we're deleting own flag and there is no global flag, delete own flag
if own != nil && global == nil {
return store.DeleteFlag(ctx, s, own)
}
// If we're deleting own flag and there is a global flag, mark own flag as inactive
if own != nil && global != nil {
own.Active = false
return store.UpdateFlag(ctx, s, own)
}
// This can't happen, but just to be safe
return fmt.Errorf("invalid flag removal state")
}
// Load updates the provided resources with storreed flags
//
// 1. All global flags for this resource are fetched
// 2. All user-specifc flags for this resource are fetched, overwriting global flags
func Load(ctx context.Context, s store.Storer, incFlags uint, userID uint64, rr ...FlaggedResource) error {
for _, r := range rr {
var (
flags []string
err error
)
if incFlags == 0 {
flags, err = loadCalculated(ctx, s, userID, r)
} else if incFlags == 1 {
flags, err = loadGlobal(ctx, s, userID, r)
} else if incFlags == 2 {
flags, err = loadOwn(ctx, s, userID, r)
} else {
return fmt.Errorf("unknown flag inclusion: %d", incFlags)
}
if err != nil {
return err
}
r.SetFlags(flags)
}
return nil
}
func loadCalculated(ctx context.Context, s store.Storer, userID uint64, r FlaggedResource) ([]string, error) {
var (
ff types.FlagSet
err error
fMap = make(map[string]bool)
)
// Get flags for all users
ff, _, err = store.SearchFlags(ctx, s, types.FlagFilter{
Kind: r.FlagResourceKind(),
ResourceID: []uint64{r.FlagResourceID()},
OwnedBy: []uint64{0},
})
if err != nil {
return nil, err
}
for _, f := range ff {
fMap[f.Name] = f.Active
}
// Get flags for the given user & merge with general flags
ff, _, err = store.SearchFlags(ctx, s, types.FlagFilter{
Kind: r.FlagResourceKind(),
ResourceID: []uint64{r.FlagResourceID()},
OwnedBy: []uint64{userID},
})
if err != nil {
return nil, err
}
for _, f := range ff {
fMap[f.Name] = f.Active
}
// convert to a slice
rr := make([]string, 0, len(fMap))
for k, v := range fMap {
if v {
rr = append(rr, k)
}
}
return rr, nil
}
func loadGlobal(ctx context.Context, s store.Storer, userID uint64, r FlaggedResource) ([]string, error) {
var (
ff types.FlagSet
err error
fMap = make(map[string]bool)
)
// Get flags for all users
ff, _, err = store.SearchFlags(ctx, s, types.FlagFilter{
Kind: r.FlagResourceKind(),
ResourceID: []uint64{r.FlagResourceID()},
OwnedBy: []uint64{0},
})
if err != nil {
return nil, err
}
for _, f := range ff {
fMap[f.Name] = f.Active
}
// convert to a slice
rr := make([]string, 0, len(fMap))
for _, f := range ff {
if f.Active {
rr = append(rr, f.Name)
}
}
return rr, nil
}
func loadOwn(ctx context.Context, s store.Storer, userID uint64, r FlaggedResource) ([]string, error) {
var (
ff types.FlagSet
err error
fMap = make(map[string]bool)
)
// Get flags for all users
ff, _, err = store.SearchFlags(ctx, s, types.FlagFilter{
Kind: r.FlagResourceKind(),
ResourceID: []uint64{r.FlagResourceID()},
OwnedBy: []uint64{userID},
})
if err != nil {
return nil, err
}
for _, f := range ff {
fMap[f.Name] = f.Active
}
// convert to a slice
rr := make([]string, 0, len(fMap))
for _, f := range ff {
if f.Active {
rr = append(rr, f.Name)
}
}
return rr, nil
}