Add mitigation views

Signed-off-by: gearnode <bryan@frimin.fr>
This commit is contained in:
gearnode
2025-03-28 14:19:36 +01:00
parent be7bfb9f25
commit 1867c79aad
33 changed files with 1083 additions and 1507 deletions

View File

@@ -44,15 +44,6 @@ type (
}
Mitigations []*Mitigation
UpdateMitigationParams struct {
ExpectedVersion int
Name *string
Description *string
Category *string
State *MitigationState
Importance *MitigationImportance
}
)
func (c Mitigation) CursorKey(orderBy MitigationOrderField) page.CursorKey {
@@ -223,60 +214,33 @@ func (c *Mitigation) Update(
ctx context.Context,
conn pg.Conn,
scope Scoper,
params UpdateMitigationParams,
) error {
q := `
UPDATE mitigations SET
name = COALESCE(@name, name),
description = COALESCE(@description, description),
category = COALESCE(@category, category),
state = COALESCE(@state, state),
importance = COALESCE(@importance, importance),
updated_at = @updated_at,
version = version + 1
UPDATE mitigations
SET
name = @name,
description = @description,
category = @category,
state = @state,
importance = @importance,
updated_at = @updated_at
WHERE %s
AND id = @mitigation_id
AND version = @expected_version
RETURNING
id,
organization_id,
category,
name,
description,
importance,
state,
content_ref,
created_at,
updated_at,
standards,
version
`
q = fmt.Sprintf(q, scope.SQLFragment())
args := pgx.NamedArgs{
"mitigation_id": c.ID,
"expected_version": params.ExpectedVersion,
"name": params.Name,
"description": params.Description,
"category": params.Category,
"state": params.State,
"importance": params.Importance,
"updated_at": time.Now(),
"mitigation_id": c.ID,
"name": c.Name,
"description": c.Description,
"category": c.Category,
"state": c.State,
"importance": c.Importance,
"updated_at": c.UpdatedAt,
}
maps.Copy(args, scope.SQLArguments())
rows, err := conn.Query(ctx, q, args)
if err != nil {
return fmt.Errorf("cannot query mitigations: %w", err)
}
mitigation, err := pgx.CollectExactlyOneRow(rows, pgx.RowToStructByName[Mitigation])
if err != nil {
return fmt.Errorf("cannot collect mitigations: %w", err)
}
*c = mitigation
return nil
_, err := conn.Exec(ctx, q, args)
return err
}

View File

@@ -42,14 +42,6 @@ type (
}
Tasks []*Task
UpdateTaskParams struct {
ExpectedVersion int
Name *string
Description *string
State *TaskState
TimeEstimate *time.Duration
}
)
func (c Task) CursorKey(orderBy TaskOrderField) page.CursorKey {
@@ -210,58 +202,33 @@ func (c *Task) Update(
ctx context.Context,
conn pg.Conn,
scope Scoper,
params UpdateTaskParams,
) error {
q := `
UPDATE tasks SET
name = COALESCE(@name, name),
description = COALESCE(@description, description),
state = COALESCE(@state, state),
time_estimate = COALESCE(@time_estimate, time_estimate),
updated_at = @updated_at,
version = version + 1
UPDATE tasks
SET
name = @name,
description = @description,
state = @state,
time_estimate = @time_estimate,
updated_at = @updated_at
WHERE %s
AND id = @task_id
AND version = @expected_version
RETURNING
id,
mitigation_id,
name,
description,
state,
time_estimate,
assigned_to,
created_at,
updated_at,
version
`
q = fmt.Sprintf(q, scope.SQLFragment())
args := pgx.NamedArgs{
"task_id": c.ID,
"expected_version": params.ExpectedVersion,
"name": params.Name,
"description": params.Description,
"state": params.State,
"time_estimate": params.TimeEstimate,
"updated_at": time.Now(),
"task_id": c.ID,
"name": c.Name,
"description": c.Description,
"state": c.State,
"time_estimate": c.TimeEstimate,
"updated_at": c.UpdatedAt,
}
maps.Copy(args, scope.SQLArguments())
rows, err := conn.Query(ctx, q, args)
if err != nil {
return fmt.Errorf("cannot query tasks: %w", err)
}
task, err := pgx.CollectExactlyOneRow(rows, pgx.RowToStructByName[Task])
if err != nil {
return fmt.Errorf("cannot collect tasks: %w", err)
}
*c = task
return nil
_, err := conn.Exec(ctx, q, args)
return err
}
func (c *Task) AssignTo(
@@ -387,94 +354,3 @@ WHERE %s
return nil
}
// Helper functions for task management
var (
ErrAssignTaskFailed = fmt.Errorf("failed to assign task")
ErrUnassignTaskFailed = fmt.Errorf("failed to unassign task")
ErrUpdateTaskFailed = fmt.Errorf("failed to update task")
ErrDeleteTaskFailed = fmt.Errorf("failed to delete task")
)
type TaskUpdate struct {
Name *string
Description *string
State *TaskState
TimeEstimate *time.Duration
}
func AssignTask(
ctx context.Context,
conn pg.Conn,
scope Scoper,
taskID gid.GID,
assignedToID gid.GID,
) (*Task, error) {
task := &Task{ID: taskID}
if err := task.LoadByID(ctx, conn, scope, taskID); err != nil {
return nil, ErrAssignTaskFailed
}
if err := task.AssignTo(ctx, conn, scope, assignedToID); err != nil {
return nil, ErrAssignTaskFailed
}
return task, nil
}
func UnassignTask(
ctx context.Context,
conn pg.Conn,
scope Scoper,
taskID gid.GID,
) (*Task, error) {
task := &Task{ID: taskID}
if err := task.LoadByID(ctx, conn, scope, taskID); err != nil {
return nil, ErrUnassignTaskFailed
}
if err := task.Unassign(ctx, conn, scope); err != nil {
return nil, ErrUnassignTaskFailed
}
return task, nil
}
func UpdateTask(
ctx context.Context,
conn pg.Conn,
scope Scoper,
taskID gid.GID,
expectedVersion int,
updates *TaskUpdate,
) (*Task, error) {
task := &Task{ID: taskID}
if err := task.Update(ctx, conn, scope, UpdateTaskParams{
ExpectedVersion: expectedVersion,
Name: updates.Name,
Description: updates.Description,
State: updates.State,
TimeEstimate: updates.TimeEstimate,
}); err != nil {
return nil, ErrUpdateTaskFailed
}
return task, nil
}
func DeleteTask(
ctx context.Context,
conn pg.Conn,
scope Scoper,
taskID gid.GID,
) error {
task := &Task{ID: taskID}
if err := task.Delete(ctx, conn, scope); err != nil {
return ErrDeleteTaskFailed
}
return nil
}

View File

@@ -39,13 +39,12 @@ type (
}
UpdateMitigationRequest struct {
ID gid.GID
ExpectedVersion int
Name *string
Description *string
Category *string
State *coredata.MitigationState
Importance *coredata.MitigationImportance
ID gid.GID
Name *string
Description *string
Category *string
State *coredata.MitigationState
Importance *coredata.MitigationImportance
}
)
@@ -73,22 +72,44 @@ func (s MitigationService) Update(
ctx context.Context,
req UpdateMitigationRequest,
) (*coredata.Mitigation, error) {
params := coredata.UpdateMitigationParams{
ExpectedVersion: req.ExpectedVersion,
Name: req.Name,
Description: req.Description,
Category: req.Category,
State: req.State,
Importance: req.Importance,
}
mitigation := &coredata.Mitigation{ID: req.ID}
err := s.svc.pg.WithTx(
ctx,
func(conn pg.Conn) error {
return mitigation.Update(ctx, conn, s.svc.scope, params)
})
if err := mitigation.LoadByID(ctx, conn, s.svc.scope, req.ID); err != nil {
return fmt.Errorf("cannot load mitigation: %w", err)
}
if req.Name != nil {
mitigation.Name = *req.Name
}
if req.Description != nil {
mitigation.Description = *req.Description
}
if req.Category != nil {
mitigation.Category = *req.Category
}
if req.State != nil {
mitigation.State = *req.State
}
if req.Importance != nil {
mitigation.Importance = *req.Importance
}
mitigation.UpdatedAt = time.Now()
if err := mitigation.Update(ctx, conn, s.svc.scope); err != nil {
return fmt.Errorf("cannot update mitigation: %w", err)
}
return nil
},
)
if err != nil {
return nil, err
}

View File

@@ -16,7 +16,6 @@ package probo
import (
"context"
"errors"
"fmt"
"time"
@@ -40,12 +39,11 @@ type (
}
UpdateTaskRequest struct {
TaskID gid.GID
ExpectedVersion int
Name *string
Description *string
State *coredata.TaskState
TimeEstimate *time.Duration
TaskID gid.GID
Name *string
Description *string
State *coredata.TaskState
TimeEstimate *time.Duration
}
)
@@ -114,20 +112,15 @@ func (s TaskService) Assign(
taskID gid.GID,
assignedToID gid.GID,
) (*coredata.Task, error) {
task := &coredata.Task{}
task := &coredata.Task{ID: taskID}
err := s.svc.pg.WithTx(
ctx,
func(conn pg.Conn) error {
var assignErr error
task, assignErr = coredata.AssignTask(ctx, conn, s.svc.scope, taskID, assignedToID)
return assignErr
return task.AssignTo(ctx, conn, s.svc.scope, assignedToID)
},
)
if err != nil {
if errors.Is(err, coredata.ErrAssignTaskFailed) {
return nil, errors.New("failed to assign task, please try again")
}
return nil, err
}
@@ -138,20 +131,15 @@ func (s TaskService) Unassign(
ctx context.Context,
taskID gid.GID,
) (*coredata.Task, error) {
task := &coredata.Task{}
task := &coredata.Task{ID: taskID}
err := s.svc.pg.WithTx(
ctx,
func(conn pg.Conn) error {
var unassignErr error
task, unassignErr = coredata.UnassignTask(ctx, conn, s.svc.scope, taskID)
return unassignErr
return task.Unassign(ctx, conn, s.svc.scope)
},
)
if err != nil {
if errors.Is(err, coredata.ErrUnassignTaskFailed) {
return nil, errors.New("failed to unassign task, please try again")
}
return nil, err
}
@@ -162,32 +150,41 @@ func (s TaskService) Update(
ctx context.Context,
req UpdateTaskRequest,
) (*coredata.Task, error) {
task := &coredata.Task{}
task := &coredata.Task{ID: req.TaskID}
err := s.svc.pg.WithTx(
ctx,
func(conn pg.Conn) error {
var updateErr error
task, updateErr = coredata.UpdateTask(
ctx,
conn,
s.svc.scope,
req.TaskID,
req.ExpectedVersion,
&coredata.TaskUpdate{
Name: req.Name,
Description: req.Description,
State: req.State,
TimeEstimate: req.TimeEstimate,
},
)
return updateErr
if err := task.LoadByID(ctx, conn, s.svc.scope, req.TaskID); err != nil {
return fmt.Errorf("cannot load task %q: %w", req.TaskID, err)
}
if req.Name != nil {
task.Name = *req.Name
}
if req.Description != nil {
task.Description = *req.Description
}
if req.State != nil {
task.State = *req.State
}
if req.TimeEstimate != nil {
task.TimeEstimate = req.TimeEstimate
}
task.UpdatedAt = time.Now()
if err := task.Update(ctx, conn, s.svc.scope); err != nil {
return fmt.Errorf("cannot update task: %w", err)
}
return nil
},
)
if err != nil {
if errors.Is(err, coredata.ErrUpdateTaskFailed) {
return nil, errors.New("failed to update task, please try again")
}
return nil, err
}
@@ -198,16 +195,15 @@ func (s TaskService) Delete(
ctx context.Context,
taskID gid.GID,
) error {
task := &coredata.Task{ID: taskID}
err := s.svc.pg.WithTx(
ctx,
func(conn pg.Conn) error {
return coredata.DeleteTask(ctx, conn, s.svc.scope, taskID)
return task.Delete(ctx, conn, s.svc.scope)
},
)
if err != nil {
if errors.Is(err, coredata.ErrDeleteTaskFailed) {
return errors.New("failed to delete task, please try again")
}
return err
}

View File

@@ -212,7 +212,10 @@ enum MitigationOrderField
@goModel(
model: "github.com/getprobo/probo/pkg/coredata.MitigationOrderField"
) {
NAME
CREATED_AT
@goEnum(
value: "github.com/getprobo/probo/pkg/coredata.MitigationOrderFieldCreatedAt"
)
}
enum TaskOrderField

View File

@@ -2409,7 +2409,10 @@ enum MitigationOrderField
@goModel(
model: "github.com/getprobo/probo/pkg/coredata.MitigationOrderField"
) {
NAME
CREATED_AT
@goEnum(
value: "github.com/getprobo/probo/pkg/coredata.MitigationOrderFieldCreatedAt"
)
}
enum TaskOrderField
@@ -23057,12 +23060,12 @@ var (
func (ec *executionContext) unmarshalNMitigationOrderField2githubᚗcomᚋgetproboᚋproboᚋpkgᚋcoredataᚐMitigationOrderField(ctx context.Context, v any) (coredata.MitigationOrderField, error) {
tmp, err := graphql.UnmarshalString(v)
res := coredata.MitigationOrderField(tmp)
res := unmarshalNMitigationOrderField2githubᚗcomᚋgetproboᚋproboᚋpkgᚋcoredataᚐMitigationOrderField[tmp]
return res, graphql.ErrorOnPath(ctx, err)
}
func (ec *executionContext) marshalNMitigationOrderField2githubᚗcomᚋgetproboᚋproboᚋpkgᚋcoredataᚐMitigationOrderField(ctx context.Context, sel ast.SelectionSet, v coredata.MitigationOrderField) graphql.Marshaler {
res := graphql.MarshalString(string(v))
res := graphql.MarshalString(marshalNMitigationOrderField2githubᚗcomᚋgetproboᚋproboᚋpkgᚋcoredataᚐMitigationOrderField[v])
if res == graphql.Null {
if !graphql.HasFieldError(ctx, graphql.GetFieldContext(ctx)) {
ec.Errorf(ctx, "the requested element is null which the schema does not allow")
@@ -23071,6 +23074,15 @@ func (ec *executionContext) marshalNMitigationOrderField2githubᚗcomᚋgetprobo
return res
}
var (
unmarshalNMitigationOrderField2githubᚗcomᚋgetproboᚋproboᚋpkgᚋcoredataᚐMitigationOrderField = map[string]coredata.MitigationOrderField{
"CREATED_AT": coredata.MitigationOrderFieldCreatedAt,
}
marshalNMitigationOrderField2githubᚗcomᚋgetproboᚋproboᚋpkgᚋcoredataᚐMitigationOrderField = map[coredata.MitigationOrderField]string{
coredata.MitigationOrderFieldCreatedAt: "CREATED_AT",
}
)
func (ec *executionContext) unmarshalNMitigationState2githubᚗcomᚋgetproboᚋproboᚋpkgᚋcoredataᚐMitigationState(ctx context.Context, v any) (coredata.MitigationState, error) {
tmp, err := graphql.UnmarshalString(v)
res := unmarshalNMitigationState2githubᚗcomᚋgetproboᚋproboᚋpkgᚋcoredataᚐMitigationState[tmp]

View File

@@ -372,20 +372,6 @@ func (r *mutationResolver) UpdateFramework(ctx context.Context, input types.Upda
}, nil
}
// DeleteFramework is the resolver for the deleteFramework field.
func (r *mutationResolver) DeleteFramework(ctx context.Context, input types.DeleteFrameworkInput) (*types.DeleteFrameworkPayload, error) {
svc := r.GetTenantServiceIfAuthorized(ctx, input.FrameworkID.TenantID())
err := svc.Frameworks.Delete(ctx, input.FrameworkID)
if err != nil {
return nil, fmt.Errorf("cannot delete framework: %w", err)
}
return &types.DeleteFrameworkPayload{
DeletedFrameworkID: input.FrameworkID,
}, nil
}
// ImportFramework is the resolver for the importFramework field.
func (r *mutationResolver) ImportFramework(ctx context.Context, input types.ImportFrameworkInput) (*types.ImportFrameworkPayload, error) {
svc := r.GetTenantServiceIfAuthorized(ctx, input.OrganizationID.TenantID())
@@ -405,6 +391,20 @@ func (r *mutationResolver) ImportFramework(ctx context.Context, input types.Impo
}, nil
}
// DeleteFramework is the resolver for the deleteFramework field.
func (r *mutationResolver) DeleteFramework(ctx context.Context, input types.DeleteFrameworkInput) (*types.DeleteFrameworkPayload, error) {
svc := r.GetTenantServiceIfAuthorized(ctx, input.FrameworkID.TenantID())
err := svc.Frameworks.Delete(ctx, input.FrameworkID)
if err != nil {
return nil, fmt.Errorf("cannot delete framework: %w", err)
}
return &types.DeleteFrameworkPayload{
DeletedFrameworkID: input.FrameworkID,
}, nil
}
// // CreateMitigation is the resolver for the createMitigation field.
func (r *mutationResolver) CreateMitigation(ctx context.Context, input types.CreateMitigationInput) (*types.CreateMitigationPayload, error) {
svc := r.GetTenantServiceIfAuthorized(ctx, input.OrganizationID.TenantID())