implement removeTagFromList mutation, add tag mutation access checks

This commit is contained in:
Christoph Kluge 2025-04-23 14:51:01 +02:00
parent a3fb471546
commit 543ddf540e
7 changed files with 272 additions and 60 deletions

View File

@ -277,6 +277,7 @@ type Mutation {
deleteTag(id: ID!): ID! deleteTag(id: ID!): ID!
addTagsToJob(job: ID!, tagIds: [ID!]!): [Tag!]! addTagsToJob(job: ID!, tagIds: [ID!]!): [Tag!]!
removeTagsFromJob(job: ID!, tagIds: [ID!]!): [Tag!]! removeTagsFromJob(job: ID!, tagIds: [ID!]!): [Tag!]!
removeTagFromList(tagIds: [ID!]!): [Int!]!
updateConfiguration(name: String!, value: String!): String updateConfiguration(name: String!, value: String!): String
} }

1
go.mod
View File

@ -1,6 +1,7 @@
module github.com/ClusterCockpit/cc-backend module github.com/ClusterCockpit/cc-backend
go 1.23.5 go 1.23.5
toolchain go1.24.1 toolchain go1.24.1
require ( require (

View File

@ -250,6 +250,7 @@ type ComplexityRoot struct {
AddTagsToJob func(childComplexity int, job string, tagIds []string) int AddTagsToJob func(childComplexity int, job string, tagIds []string) int
CreateTag func(childComplexity int, typeArg string, name string, scope string) int CreateTag func(childComplexity int, typeArg string, name string, scope string) int
DeleteTag func(childComplexity int, id string) int DeleteTag func(childComplexity int, id string) int
RemoveTagFromList func(childComplexity int, tagIds []string) int
RemoveTagsFromJob func(childComplexity int, job string, tagIds []string) int RemoveTagsFromJob func(childComplexity int, job string, tagIds []string) int
UpdateConfiguration func(childComplexity int, name string, value string) int UpdateConfiguration func(childComplexity int, name string, value string) int
} }
@ -399,6 +400,7 @@ type MutationResolver interface {
DeleteTag(ctx context.Context, id string) (string, error) DeleteTag(ctx context.Context, id string) (string, error)
AddTagsToJob(ctx context.Context, job string, tagIds []string) ([]*schema.Tag, error) AddTagsToJob(ctx context.Context, job string, tagIds []string) ([]*schema.Tag, error)
RemoveTagsFromJob(ctx context.Context, job string, tagIds []string) ([]*schema.Tag, error) RemoveTagsFromJob(ctx context.Context, job string, tagIds []string) ([]*schema.Tag, error)
RemoveTagFromList(ctx context.Context, tagIds []string) ([]int, error)
UpdateConfiguration(ctx context.Context, name string, value string) (*string, error) UpdateConfiguration(ctx context.Context, name string, value string) (*string, error)
} }
type QueryResolver interface { type QueryResolver interface {
@ -1310,6 +1312,18 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in
return e.complexity.Mutation.DeleteTag(childComplexity, args["id"].(string)), true return e.complexity.Mutation.DeleteTag(childComplexity, args["id"].(string)), true
case "Mutation.removeTagFromList":
if e.complexity.Mutation.RemoveTagFromList == nil {
break
}
args, err := ec.field_Mutation_removeTagFromList_args(context.TODO(), rawArgs)
if err != nil {
return 0, false
}
return e.complexity.Mutation.RemoveTagFromList(childComplexity, args["tagIds"].([]string)), true
case "Mutation.removeTagsFromJob": case "Mutation.removeTagsFromJob":
if e.complexity.Mutation.RemoveTagsFromJob == nil { if e.complexity.Mutation.RemoveTagsFromJob == nil {
break break
@ -2339,6 +2353,7 @@ type Mutation {
deleteTag(id: ID!): ID! deleteTag(id: ID!): ID!
addTagsToJob(job: ID!, tagIds: [ID!]!): [Tag!]! addTagsToJob(job: ID!, tagIds: [ID!]!): [Tag!]!
removeTagsFromJob(job: ID!, tagIds: [ID!]!): [Tag!]! removeTagsFromJob(job: ID!, tagIds: [ID!]!): [Tag!]!
removeTagFromList(tagIds: [ID!]!): [Int!]!
updateConfiguration(name: String!, value: String!): String updateConfiguration(name: String!, value: String!): String
} }
@ -2617,6 +2632,34 @@ func (ec *executionContext) field_Mutation_deleteTag_argsID(
return zeroVal, nil return zeroVal, nil
} }
func (ec *executionContext) field_Mutation_removeTagFromList_args(ctx context.Context, rawArgs map[string]any) (map[string]any, error) {
var err error
args := map[string]any{}
arg0, err := ec.field_Mutation_removeTagFromList_argsTagIds(ctx, rawArgs)
if err != nil {
return nil, err
}
args["tagIds"] = arg0
return args, nil
}
func (ec *executionContext) field_Mutation_removeTagFromList_argsTagIds(
ctx context.Context,
rawArgs map[string]any,
) ([]string, error) {
if _, ok := rawArgs["tagIds"]; !ok {
var zeroVal []string
return zeroVal, nil
}
ctx = graphql.WithPathContext(ctx, graphql.NewPathWithField("tagIds"))
if tmp, ok := rawArgs["tagIds"]; ok {
return ec.unmarshalNID2ᚕstringᚄ(ctx, tmp)
}
var zeroVal []string
return zeroVal, nil
}
func (ec *executionContext) field_Mutation_removeTagsFromJob_args(ctx context.Context, rawArgs map[string]any) (map[string]any, error) { func (ec *executionContext) field_Mutation_removeTagsFromJob_args(ctx context.Context, rawArgs map[string]any) (map[string]any, error) {
var err error var err error
args := map[string]any{} args := map[string]any{}
@ -9690,6 +9733,61 @@ func (ec *executionContext) fieldContext_Mutation_removeTagsFromJob(ctx context.
return fc, nil return fc, nil
} }
func (ec *executionContext) _Mutation_removeTagFromList(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) {
fc, err := ec.fieldContext_Mutation_removeTagFromList(ctx, field)
if err != nil {
return graphql.Null
}
ctx = graphql.WithFieldContext(ctx, fc)
defer func() {
if r := recover(); r != nil {
ec.Error(ctx, ec.Recover(ctx, r))
ret = graphql.Null
}
}()
resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (any, error) {
ctx = rctx // use context from middleware stack in children
return ec.resolvers.Mutation().RemoveTagFromList(rctx, fc.Args["tagIds"].([]string))
})
if err != nil {
ec.Error(ctx, err)
return graphql.Null
}
if resTmp == nil {
if !graphql.HasFieldError(ctx, fc) {
ec.Errorf(ctx, "must not be null")
}
return graphql.Null
}
res := resTmp.([]int)
fc.Result = res
return ec.marshalNInt2ᚕintᚄ(ctx, field.Selections, res)
}
func (ec *executionContext) fieldContext_Mutation_removeTagFromList(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) {
fc = &graphql.FieldContext{
Object: "Mutation",
Field: field,
IsMethod: true,
IsResolver: true,
Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) {
return nil, errors.New("field of type Int does not have child fields")
},
}
defer func() {
if r := recover(); r != nil {
err = ec.Recover(ctx, r)
ec.Error(ctx, err)
}
}()
ctx = graphql.WithFieldContext(ctx, fc)
if fc.Args, err = ec.field_Mutation_removeTagFromList_args(ctx, field.ArgumentMap(ec.Variables)); err != nil {
ec.Error(ctx, err)
return fc, err
}
return fc, nil
}
func (ec *executionContext) _Mutation_updateConfiguration(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) { func (ec *executionContext) _Mutation_updateConfiguration(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) {
fc, err := ec.fieldContext_Mutation_updateConfiguration(ctx, field) fc, err := ec.fieldContext_Mutation_updateConfiguration(ctx, field)
if err != nil { if err != nil {
@ -17765,6 +17863,13 @@ func (ec *executionContext) _Mutation(ctx context.Context, sel ast.SelectionSet)
if out.Values[i] == graphql.Null { if out.Values[i] == graphql.Null {
out.Invalids++ out.Invalids++
} }
case "removeTagFromList":
out.Values[i] = ec.OperationContext.RootResolverMiddleware(innerCtx, func(ctx context.Context) (res graphql.Marshaler) {
return ec._Mutation_removeTagFromList(ctx, field)
})
if out.Values[i] == graphql.Null {
out.Invalids++
}
case "updateConfiguration": case "updateConfiguration":
out.Values[i] = ec.OperationContext.RootResolverMiddleware(innerCtx, func(ctx context.Context) (res graphql.Marshaler) { out.Values[i] = ec.OperationContext.RootResolverMiddleware(innerCtx, func(ctx context.Context) (res graphql.Marshaler) {
return ec._Mutation_updateConfiguration(ctx, field) return ec._Mutation_updateConfiguration(ctx, field)

View File

@ -125,23 +125,41 @@ func (r *metricValueResolver) Name(ctx context.Context, obj *schema.MetricValue)
// CreateTag is the resolver for the createTag field. // CreateTag is the resolver for the createTag field.
func (r *mutationResolver) CreateTag(ctx context.Context, typeArg string, name string, scope string) (*schema.Tag, error) { func (r *mutationResolver) CreateTag(ctx context.Context, typeArg string, name string, scope string) (*schema.Tag, error) {
id, err := r.Repo.CreateTag(typeArg, name, scope) user := repository.GetUserFromContext(ctx)
if err != nil { if user == nil {
log.Warn("Error while creating tag") return nil, fmt.Errorf("no user in context")
return nil, err
} }
return &schema.Tag{ID: id, Type: typeArg, Name: name, Scope: scope}, nil // Test Access: Admins && Admin Tag OR Support/Admin and Global Tag OR Everyone && Private Tag
if user.HasRole(schema.RoleAdmin) && scope == "admin" ||
user.HasAnyRole([]schema.Role{schema.RoleAdmin, schema.RoleSupport}) && scope == "global" ||
user.Username == scope {
// Create in DB
id, err := r.Repo.CreateTag(typeArg, name, scope)
if err != nil {
log.Warn("Error while creating tag")
return nil, err
}
return &schema.Tag{ID: id, Type: typeArg, Name: name, Scope: scope}, nil
} else {
log.Warn("Not authorized to create tag with scope: %s", scope)
return nil, fmt.Errorf("Not authorized to create tag with scope: %s", scope)
}
} }
// DeleteTag is the resolver for the deleteTag field. // DeleteTag is the resolver for the deleteTag field.
func (r *mutationResolver) DeleteTag(ctx context.Context, id string) (string, error) { func (r *mutationResolver) DeleteTag(ctx context.Context, id string) (string, error) {
// This Uses ID string <-> ID string, removeTagFromList uses []string <-> []int
panic(fmt.Errorf("not implemented: DeleteTag - deleteTag")) panic(fmt.Errorf("not implemented: DeleteTag - deleteTag"))
} }
// AddTagsToJob is the resolver for the addTagsToJob field. // AddTagsToJob is the resolver for the addTagsToJob field.
func (r *mutationResolver) AddTagsToJob(ctx context.Context, job string, tagIds []string) ([]*schema.Tag, error) { func (r *mutationResolver) AddTagsToJob(ctx context.Context, job string, tagIds []string) ([]*schema.Tag, error) {
// Selectable Tags Pre-Filtered by Scope in Frontend: No backend check required user := repository.GetUserFromContext(ctx)
if user == nil {
return nil, fmt.Errorf("no user in context")
}
jid, err := strconv.ParseInt(job, 10, 64) jid, err := strconv.ParseInt(job, 10, 64)
if err != nil { if err != nil {
log.Warn("Error while adding tag to job") log.Warn("Error while adding tag to job")
@ -150,15 +168,32 @@ func (r *mutationResolver) AddTagsToJob(ctx context.Context, job string, tagIds
tags := []*schema.Tag{} tags := []*schema.Tag{}
for _, tagId := range tagIds { for _, tagId := range tagIds {
// Get ID
tid, err := strconv.ParseInt(tagId, 10, 64) tid, err := strconv.ParseInt(tagId, 10, 64)
if err != nil { if err != nil {
log.Warn("Error while parsing tag id") log.Warn("Error while parsing tag id")
return nil, err return nil, err
} }
if tags, err = r.Repo.AddTag(repository.GetUserFromContext(ctx), jid, tid); err != nil { // Test Exists
log.Warn("Error while adding tag") _, _, tscope, exists := r.Repo.TagInfo(tid)
return nil, err if !exists {
log.Warn("Tag does not exist (ID): %d", tid)
return nil, fmt.Errorf("Tag does not exist (ID): %d", tid)
}
// Test Access: Admins && Admin Tag OR Support/Admin and Global Tag OR Everyone && Private Tag
if user.HasRole(schema.RoleAdmin) && tscope == "admin" ||
user.HasAnyRole([]schema.Role{schema.RoleAdmin, schema.RoleSupport}) && tscope == "global" ||
user.Username == tscope {
// Add to Job
if tags, err = r.Repo.AddTag(user, jid, tid); err != nil {
log.Warn("Error while adding tag")
return nil, err
}
} else {
log.Warn("Not authorized to add tag: %d", tid)
return nil, fmt.Errorf("Not authorized to add tag: %d", tid)
} }
} }
@ -167,7 +202,11 @@ func (r *mutationResolver) AddTagsToJob(ctx context.Context, job string, tagIds
// RemoveTagsFromJob is the resolver for the removeTagsFromJob field. // RemoveTagsFromJob is the resolver for the removeTagsFromJob field.
func (r *mutationResolver) RemoveTagsFromJob(ctx context.Context, job string, tagIds []string) ([]*schema.Tag, error) { func (r *mutationResolver) RemoveTagsFromJob(ctx context.Context, job string, tagIds []string) ([]*schema.Tag, error) {
// Removable Tags Pre-Filtered by Scope in Frontend: No backend check required user := repository.GetUserFromContext(ctx)
if user == nil {
return nil, fmt.Errorf("no user in context")
}
jid, err := strconv.ParseInt(job, 10, 64) jid, err := strconv.ParseInt(job, 10, 64)
if err != nil { if err != nil {
log.Warn("Error while parsing job id") log.Warn("Error while parsing job id")
@ -176,21 +215,80 @@ func (r *mutationResolver) RemoveTagsFromJob(ctx context.Context, job string, ta
tags := []*schema.Tag{} tags := []*schema.Tag{}
for _, tagId := range tagIds { for _, tagId := range tagIds {
// Get ID
tid, err := strconv.ParseInt(tagId, 10, 64) tid, err := strconv.ParseInt(tagId, 10, 64)
if err != nil { if err != nil {
log.Warn("Error while parsing tag id") log.Warn("Error while parsing tag id")
return nil, err return nil, err
} }
if tags, err = r.Repo.RemoveTag(repository.GetUserFromContext(ctx), jid, tid); err != nil { // Test Exists
log.Warn("Error while removing tag") _, _, tscope, exists := r.Repo.TagInfo(tid)
return nil, err if !exists {
log.Warn("Tag does not exist (ID): %d", tid)
return nil, fmt.Errorf("Tag does not exist (ID): %d", tid)
} }
// Test Access: Admins && Admin Tag OR Support/Admin and Global Tag OR Everyone && Private Tag
if user.HasRole(schema.RoleAdmin) && tscope == "admin" ||
user.HasAnyRole([]schema.Role{schema.RoleAdmin, schema.RoleSupport}) && tscope == "global" ||
user.Username == tscope {
// Remove from Job
if tags, err = r.Repo.RemoveTag(user, jid, tid); err != nil {
log.Warn("Error while removing tag")
return nil, err
}
} else {
log.Warn("Not authorized to remove tag: %d", tid)
return nil, fmt.Errorf("Not authorized to remove tag: %d", tid)
}
} }
return tags, nil return tags, nil
} }
// RemoveTagFromList is the resolver for the removeTagFromList field.
func (r *mutationResolver) RemoveTagFromList(ctx context.Context, tagIds []string) ([]int, error) {
// Needs Contextuser
user := repository.GetUserFromContext(ctx)
if user == nil {
return nil, fmt.Errorf("no user in context")
}
tags := []int{}
for _, tagId := range tagIds {
// Get ID
tid, err := strconv.ParseInt(tagId, 10, 64)
if err != nil {
log.Warn("Error while parsing tag id for removal")
return nil, err
}
// Test Exists
_, _, tscope, exists := r.Repo.TagInfo(tid)
if !exists {
log.Warn("Tag does not exist (ID): %d", tid)
return nil, fmt.Errorf("Tag does not exist (ID): %d", tid)
}
// Test Access: Admins && Admin Tag OR Everyone && Private Tag
if user.HasRole(schema.RoleAdmin) && (tscope == "global" || tscope == "admin") || user.Username == tscope {
// Remove from DB
if err = r.Repo.RemoveTagById(tid); err != nil {
log.Warn("Error while removing tag")
return nil, err
} else {
tags = append(tags, int(tid))
}
} else {
log.Warn("Not authorized to remove tag: %d", tid)
return nil, fmt.Errorf("Not authorized to remove tag: %d", tid)
}
}
return tags, nil
}
// UpdateConfiguration is the resolver for the updateConfiguration field. // UpdateConfiguration is the resolver for the updateConfiguration field.
func (r *mutationResolver) UpdateConfiguration(ctx context.Context, name string, value string) (*string, error) { func (r *mutationResolver) UpdateConfiguration(ctx context.Context, name string, value string) (*string, error) {
if err := repository.GetUserCfgRepo().UpdateConfig(name, value, repository.GetUserFromContext(ctx)); err != nil { if err := repository.GetUserCfgRepo().UpdateConfig(name, value, repository.GetUserFromContext(ctx)); err != nil {

View File

@ -79,10 +79,10 @@ func (r *JobRepository) RemoveTag(user *schema.User, job, tag int64) ([]*schema.
// Removes a tag from a job by tag info // Removes a tag from a job by tag info
func (r *JobRepository) RemoveJobTagByRequest(user *schema.User, job int64, tagType string, tagName string, tagScope string) ([]*schema.Tag, error) { func (r *JobRepository) RemoveJobTagByRequest(user *schema.User, job int64, tagType string, tagName string, tagScope string) ([]*schema.Tag, error) {
// Get Tag ID to delete // Get Tag ID to delete
tagID, err := r.loadTagIDByInfo(tagName, tagType, tagScope) tagID, exists := r.TagId(tagType, tagName, tagScope)
if err != nil { if !exists {
log.Warn("Error while finding tagId with: %s, %s, %s", tagName, tagType, tagScope) log.Warn("Tag does not exist (name, type, scope): %s, %s, %s", tagName, tagType, tagScope)
return nil, err return nil, fmt.Errorf("Tag does not exist (name, type, scope): %s, %s, %s", tagName, tagType, tagScope)
} }
// Get Job // Get Job
@ -119,12 +119,35 @@ func (r *JobRepository) RemoveJobTagByRequest(user *schema.User, job int64, tagT
// Removes a tag from db by tag info // Removes a tag from db by tag info
func (r *JobRepository) RemoveTagByRequest(tagType string, tagName string, tagScope string) error { func (r *JobRepository) RemoveTagByRequest(tagType string, tagName string, tagScope string) error {
// Get Tag ID to delete // Get Tag ID to delete
tagID, err := r.loadTagIDByInfo(tagName, tagType, tagScope) tagID, exists := r.TagId(tagType, tagName, tagScope)
if err != nil { if !exists {
log.Warn("Error while finding tagId with: %s, %s, %s", tagName, tagType, tagScope) log.Warn("Tag does not exist (name, type, scope): %s, %s, %s", tagName, tagType, tagScope)
return fmt.Errorf("Tag does not exist (name, type, scope): %s, %s, %s", tagName, tagType, tagScope)
}
// Handle Delete JobTagTable
qJobTag := sq.Delete("jobtag").Where("jobtag.tag_id = ?", tagID)
if _, err := qJobTag.RunWith(r.stmtCache).Exec(); err != nil {
s, _, _ := qJobTag.ToSql()
log.Errorf("Error removing tag from table 'jobTag' with %s: %v", s, err)
return err return err
} }
// Handle Delete TagTable
qTag := sq.Delete("tag").Where("tag.id = ?", tagID)
if _, err := qTag.RunWith(r.stmtCache).Exec(); err != nil {
s, _, _ := qTag.ToSql()
log.Errorf("Error removing tag from table 'tag' with %s: %v", s, err)
return err
}
return nil
}
// Removes a tag from db by tag info
func (r *JobRepository) RemoveTagById(tagID int64) error {
// Handle Delete JobTagTable // Handle Delete JobTagTable
qJobTag := sq.Delete("jobtag").Where("jobtag.tag_id = ?", tagID) qJobTag := sq.Delete("jobtag").Where("jobtag.tag_id = ?", tagID)
@ -279,6 +302,16 @@ func (r *JobRepository) TagId(tagType string, tagName string, tagScope string) (
return return
} }
// TagInfo returns the database infos of the tag with the specified id.
func (r *JobRepository) TagInfo(tagId int64) (tagType string, tagName string, tagScope string, exists bool) {
exists = true
if err := sq.Select("tag.tag_type", "tag.tag_name", "tag.tag_scope").From("tag").Where("tag.id = ?", tagId).
RunWith(r.stmtCache).QueryRow().Scan(&tagType, &tagName, &tagScope); err != nil {
exists = false
}
return
}
// GetTags returns a list of all scoped tags if job is nil or of the tags that the job with that database ID has. // GetTags returns a list of all scoped tags if job is nil or of the tags that the job with that database ID has.
func (r *JobRepository) GetTags(user *schema.User, job *int64) ([]*schema.Tag, error) { func (r *JobRepository) GetTags(user *schema.User, job *int64) ([]*schema.Tag, error) {
q := sq.Select("id", "tag_type", "tag_name", "tag_scope").From("tag") q := sq.Select("id", "tag_type", "tag_name", "tag_scope").From("tag")
@ -395,29 +428,3 @@ func (r *JobRepository) checkScopeAuth(user *schema.User, operation string, scop
return false, fmt.Errorf("error while checking tag operation auth: no user in context") return false, fmt.Errorf("error while checking tag operation auth: no user in context")
} }
} }
func (r *JobRepository) loadTagIDByInfo(tagType string, tagName string, tagScope string) (tagID int64, err error) {
// Get Tag ID to delete
getq := sq.Select("id").From("tag").
Where("tag_type = ?", tagType).
Where("tag_name = ?", tagName).
Where("tag_scope = ?", tagScope)
rows, err := getq.RunWith(r.stmtCache).Query()
if err != nil {
s, _, _ := getq.ToSql()
log.Errorf("Error get tags for delete with %s: %v", s, err)
return 0, err
}
dbTags := make([]*schema.Tag, 0)
for rows.Next() {
dbTag := &schema.Tag{}
if err := rows.Scan(&dbTag.ID); err != nil {
log.Warn("Error while scanning rows")
return 0, err
}
}
return dbTags[0].ID, nil
}

View File

@ -85,6 +85,7 @@ func IsValidRole(role string) bool {
return getRoleEnum(role) != RoleError return getRoleEnum(role) != RoleError
} }
// Check if User has SPECIFIED role AND role is VALID
func (u *User) HasValidRole(role string) (hasRole bool, isValid bool) { func (u *User) HasValidRole(role string) (hasRole bool, isValid bool) {
if IsValidRole(role) { if IsValidRole(role) {
for _, r := range u.Roles { for _, r := range u.Roles {
@ -97,6 +98,7 @@ func (u *User) HasValidRole(role string) (hasRole bool, isValid bool) {
return false, false return false, false
} }
// Check if User has SPECIFIED role
func (u *User) HasRole(role Role) bool { func (u *User) HasRole(role Role) bool {
for _, r := range u.Roles { for _, r := range u.Roles {
if r == GetRoleString(role) { if r == GetRoleString(role) {
@ -106,7 +108,7 @@ func (u *User) HasRole(role Role) bool {
return false return false
} }
// Role-Arrays are short: performance not impacted by nested loop // Check if User has ANY of the listed roles
func (u *User) HasAnyRole(queryroles []Role) bool { func (u *User) HasAnyRole(queryroles []Role) bool {
for _, ur := range u.Roles { for _, ur := range u.Roles {
for _, qr := range queryroles { for _, qr := range queryroles {
@ -118,7 +120,7 @@ func (u *User) HasAnyRole(queryroles []Role) bool {
return false return false
} }
// Role-Arrays are short: performance not impacted by nested loop // Check if User has ALL of the listed roles
func (u *User) HasAllRoles(queryroles []Role) bool { func (u *User) HasAllRoles(queryroles []Role) bool {
target := len(queryroles) target := len(queryroles)
matches := 0 matches := 0
@ -138,7 +140,7 @@ func (u *User) HasAllRoles(queryroles []Role) bool {
} }
} }
// Role-Arrays are short: performance not impacted by nested loop // Check if User has NONE of the listed roles
func (u *User) HasNotRoles(queryroles []Role) bool { func (u *User) HasNotRoles(queryroles []Role) bool {
matches := 0 matches := 0
for _, ur := range u.Roles { for _, ur := range u.Roles {

View File

@ -37,13 +37,8 @@
return mutationStore({ return mutationStore({
client: client, client: client,
query: gql` query: gql`
mutation ($job: ID!, $tagIds: [ID!]!) { mutation ($tagIds: [ID!]!) {
removeTag(tagIds: $tagIds) { removeTagFromList(tagIds: $tagIds)
id
type
name
scope
}
} }
`, `,
variables: { tagIds }, variables: { tagIds },
@ -55,7 +50,13 @@
removeTagMutation({tagIds: [tag.id] }).subscribe( removeTagMutation({tagIds: [tag.id] }).subscribe(
(res) => { (res) => {
if (res.fetching === false && !res.error) { if (res.fetching === false && !res.error) {
tagmap = res.data.removeTag; // console.log('Removed:', res.data.removeTagFromList)
// console.log('Targets:', tagType, tagmap[tagType])
// console.log('Filter:', tagmap[tagType].filter((t) => !res.data.removeTagFromList.includes(t.id)))
tagmap[tagType] = tagmap[tagType].filter((t) => !res.data.removeTagFromList.includes(t.id));
if (tagmap[tagType].length === 0) {
delete tagmap[tagType]
}
pendingChange = "none"; pendingChange = "none";
} else if (res.fetching === false && res.error) { } else if (res.fetching === false && res.error) {
throw res.error; throw res.error;
@ -63,9 +64,6 @@
}, },
); );
} }
$: console.log(username, isAdmin)
$: console.log(pendingChange, tagmap)
</script> </script>
<div class="container"> <div class="container">