snapshot of https://github.com/cortezaproject/corteza.git (2024.9.x, 2026-09-18; blobs > 131072B omitted)
Some checks failed
SonarQube / SonarQube (push) Failing after 5m28s
E2e test on main branch push / server and client test (map[name:admin port:8080]) (push) Failing after 3m33s
Server and lib tests / Library test (js) (push) Failing after 1m36s
Server and lib tests / Library test (vue) (push) Successful in 2m24s
Server and lib tests / Web client test (admin) (push) Successful in 2m52s
Server and lib tests / Web client test (compose) (push) Successful in 3m23s
Server and lib tests / Web client test (discovery) (push) Successful in 2m49s
Server and lib tests / Web client test (one) (push) Successful in 2m52s
Server and lib tests / Web client test (privacy) (push) Successful in 2m44s
Server and lib tests / Web client test (reporter) (push) Successful in 2m56s
Server and lib tests / Web client test (workflow) (push) Successful in 3m0s
Server and lib tests / Server tests (push) Failing after 4m55s
E2e test on main branch push / notify-failure (push) Failing after 9s
Server and lib tests / Send matrix testing notification (push) Successful in 2s
Mark stale issues and pull requests / stale (push) Successful in 40s
Some checks failed
SonarQube / SonarQube (push) Failing after 5m28s
E2e test on main branch push / server and client test (map[name:admin port:8080]) (push) Failing after 3m33s
Server and lib tests / Library test (js) (push) Failing after 1m36s
Server and lib tests / Library test (vue) (push) Successful in 2m24s
Server and lib tests / Web client test (admin) (push) Successful in 2m52s
Server and lib tests / Web client test (compose) (push) Successful in 3m23s
Server and lib tests / Web client test (discovery) (push) Successful in 2m49s
Server and lib tests / Web client test (one) (push) Successful in 2m52s
Server and lib tests / Web client test (privacy) (push) Successful in 2m44s
Server and lib tests / Web client test (reporter) (push) Successful in 2m56s
Server and lib tests / Web client test (workflow) (push) Successful in 3m0s
Server and lib tests / Server tests (push) Failing after 4m55s
E2e test on main branch push / notify-failure (push) Failing after 9s
Server and lib tests / Send matrix testing notification (push) Successful in 2s
Mark stale issues and pull requests / stale (push) Successful in 40s
This commit is contained in:
288
server/pkg/flag/flag.go
Normal file
288
server/pkg/flag/flag.go
Normal file
@@ -0,0 +1,288 @@
|
||||
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
|
||||
}
|
||||
30
server/pkg/flag/types/type.go
Normal file
30
server/pkg/flag/types/type.go
Normal file
@@ -0,0 +1,30 @@
|
||||
package types
|
||||
|
||||
type (
|
||||
Flag struct {
|
||||
// Kind of the flagged resource
|
||||
Kind string
|
||||
// ID if the flagged resource
|
||||
ResourceID uint64
|
||||
// The owner of this flag; 0 = everyone
|
||||
OwnedBy uint64
|
||||
|
||||
Name string
|
||||
Active bool
|
||||
}
|
||||
|
||||
// @todo codegen this thing
|
||||
FlagSet []*Flag
|
||||
|
||||
FlagFilter struct {
|
||||
Kind string
|
||||
ResourceID []uint64
|
||||
OwnedBy []uint64
|
||||
Name []string
|
||||
Limit uint
|
||||
}
|
||||
)
|
||||
|
||||
const (
|
||||
FlagResourceType = "corteza::generic:flag"
|
||||
)
|
||||
Reference in New Issue
Block a user