From 1142bf9b32f30c58d72e10629c49b3c2a28a8bce Mon Sep 17 00:00:00 2001 From: gearnode Date: Wed, 5 Feb 2025 11:14:17 -0800 Subject: [PATCH] Add create task Signed-off-by: gearnode --- pkg/probo/coredata/task.go | 49 +++++++++++ pkg/probo/coredata/task_state_transition.go | 39 +++++++++ pkg/probo/create_task.go | 97 +++++++++++++++++++++ 3 files changed, 185 insertions(+) create mode 100644 pkg/probo/create_task.go diff --git a/pkg/probo/coredata/task.go b/pkg/probo/coredata/task.go index b7330612c..dab5be667 100644 --- a/pkg/probo/coredata/task.go +++ b/pkg/probo/coredata/task.go @@ -135,6 +135,55 @@ LIMIT 1; return nil } +func (t Task) Insert( + ctx context.Context, + conn pg.Conn, +) error { + q := ` +WITH task_insert AS ( + INSERT INTO tasks ( + id, + name, + description, + content_ref, + created_at, + updated_at + ) + VALUES ( + @task_id, + @name, + @description, + @content_ref, + @created_at, + @updated_at + ) + RETURNING id +) +INSERT INTO controls_tasks ( + task_id, + control_id, + created_at +) +VALUES ( + (SELECT id FROM task_insert), + @control_id, + @created_at +); +` + + args := pgx.NamedArgs{ + "control_id": t.ID, + "framework_id": t.ControlID, + "name": t.Name, + "description": t.Description, + "content_ref": t.ContentRef, + "created_at": t.CreatedAt, + "updated_at": t.UpdatedAt, + } + _, err := conn.Exec(ctx, q, args) + return err +} + func (t *Tasks) LoadByControlID( ctx context.Context, conn pg.Conn, diff --git a/pkg/probo/coredata/task_state_transition.go b/pkg/probo/coredata/task_state_transition.go index a9bbad41d..c11285dc4 100644 --- a/pkg/probo/coredata/task_state_transition.go +++ b/pkg/probo/coredata/task_state_transition.go @@ -52,6 +52,45 @@ func (tst *TaskStateTransition) scan(r pgx.Row) error { ) } +func (tst TaskStateTransition) Insert( + ctx context.Context, + conn pg.Conn, +) error { + q := ` +INSERT INTO + task_state_transitions ( + id, + control_id, + from_state + to_state, + reason, + created_at, + updated_at + ) +VALUES ( + @task_state_transition_id, + @task_id, + @from_state, + @to_state, + @reason, + @created_at, + @updated_at +); +` + + args := pgx.NamedArgs{ + "task_state_transition_id": tst.ID, + "task_id": tst.TaskID, + "from_state": tst.FromState, + "to_state": tst.ToState, + "reason": tst.Reason, + "created_at": tst.CreatedAt, + "updated_at": tst.UpdatedAt, + } + _, err := conn.Exec(ctx, q, args) + return err +} + func (tst *TaskStateTransitions) LoadByTaskID( ctx context.Context, conn pg.Conn, diff --git a/pkg/probo/create_task.go b/pkg/probo/create_task.go new file mode 100644 index 000000000..909659e6f --- /dev/null +++ b/pkg/probo/create_task.go @@ -0,0 +1,97 @@ +// Copyright (c) 2025 Probo Inc . +// +// Permission to use, copy, modify, and/or distribute this software for any +// purpose with or without fee is hereby granted, provided that the above +// copyright notice and this permission notice appear in all copies. +// +// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH +// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY +// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, +// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM +// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR +// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR +// PERFORMANCE OF THIS SOFTWARE. + +package probo + +import ( + "context" + "fmt" + "time" + + "gearno.de/ref" + "github.com/getprobo/probo/pkg/gid" + "github.com/getprobo/probo/pkg/probo/coredata" + "go.gearno.de/kit/pg" +) + +type ( + CreateTaskRequest struct { + ControlID gid.GID + Name string + ContentRef string + } +) + +func (s Service) CreateTask( + ctx context.Context, + req CreateTaskRequest, +) (*coredata.Task, error) { + now := time.Now() + taskID, err := gid.NewGID(coredata.TaskEntityType) + if err != nil { + return nil, fmt.Errorf("cannot create task global id: %w", err) + } + taskStateTransitionID, err := gid.NewGID(coredata.TaskStateTransitionEntityType) + if err != nil { + return nil, fmt.Errorf("cannot create task state transition global id: %w", err) + } + + control := &coredata.Control{} + task := &coredata.Task{ + ID: taskID, + ControlID: req.ControlID, + Name: req.Name, + ContentRef: req.ContentRef, + State: coredata.TaskStateTodo, + CreatedAt: now, + UpdatedAt: now, + } + + taskStateTransition := coredata.TaskStateTransition{ + StateTransition: coredata.StateTransition[coredata.TaskState]{ + ID: taskStateTransitionID, + FromState: nil, + ToState: task.State, + Reason: ref.Ref("Initial state"), + CreatedAt: now, + UpdatedAt: now, + }, + TaskID: task.ID, + } + + err = s.pg.WithTx( + ctx, + func(conn pg.Conn) error { + if err := control.LoadByID(ctx, conn, s.scope, req.ControlID); err != nil { + return fmt.Errorf("cannot laod control %q: %w", req.ControlID, err) + } + + if err := task.Insert(ctx, conn); err != nil { + return fmt.Errorf("cannot insert task: %w", err) + } + + if err := taskStateTransition.Insert(ctx, conn); err != nil { + return fmt.Errorf("cannot insert task state transition: %w", err) + } + + return nil + }, + ) + + if err != nil { + return nil, err + } + + return task, nil +}