@@ -30,7 +30,6 @@ import (
|
||||
type (
|
||||
Measure struct {
|
||||
ID gid.GID `db:"id"`
|
||||
TenantID gid.TenantID `db:"tenant_id"`
|
||||
OrganizationID gid.GID `db:"organization_id"`
|
||||
Category string `db:"category"`
|
||||
Name string `db:"name"`
|
||||
@@ -82,7 +81,6 @@ WITH msrs AS (
|
||||
)
|
||||
SELECT
|
||||
id,
|
||||
tenant_id,
|
||||
organization_id,
|
||||
category,
|
||||
name,
|
||||
@@ -146,7 +144,6 @@ WITH mtgtns AS (
|
||||
)
|
||||
SELECT
|
||||
id,
|
||||
tenant_id,
|
||||
organization_id,
|
||||
category,
|
||||
name,
|
||||
@@ -191,7 +188,6 @@ func (m *Measures) LoadByOrganizationID(
|
||||
q := `
|
||||
SELECT
|
||||
id,
|
||||
tenant_id,
|
||||
organization_id,
|
||||
category,
|
||||
name,
|
||||
@@ -237,7 +233,6 @@ func (m *Measure) LoadByID(
|
||||
q := `
|
||||
SELECT
|
||||
id,
|
||||
tenant_id,
|
||||
organization_id,
|
||||
category,
|
||||
name,
|
||||
@@ -311,7 +306,6 @@ ON CONFLICT (organization_id, reference_id) DO UPDATE SET
|
||||
category = @category,
|
||||
updated_at = @updated_at
|
||||
RETURNING
|
||||
tenant_id,
|
||||
id,
|
||||
organization_id,
|
||||
category,
|
||||
8
pkg/coredata/migrations/20250505T171400Z.sql
Normal file
8
pkg/coredata/migrations/20250505T171400Z.sql
Normal file
@@ -0,0 +1,8 @@
|
||||
ALTER TABLE tasks ADD COLUMN organization_id TEXT REFERENCES organizations(id) ON DELETE CASCADE;
|
||||
|
||||
UPDATE tasks
|
||||
SET organization_id = m.organization_id::text
|
||||
FROM measures m
|
||||
WHERE tasks.measure_id = m.id;
|
||||
|
||||
ALTER TABLE tasks ALTER COLUMN organization_id SET NOT NULL;
|
||||
@@ -166,8 +166,9 @@ WHERE %s
|
||||
`
|
||||
q = fmt.Sprintf(q, scope.SQLFragment(), cursor.SQLFragment())
|
||||
|
||||
args := pgx.NamedArgs{"organization_id": organizationID}
|
||||
args := pgx.StrictNamedArgs{"organization_id": organizationID}
|
||||
maps.Copy(args, scope.SQLArguments())
|
||||
maps.Copy(args, cursor.SQLArguments())
|
||||
|
||||
rows, err := conn.Query(ctx, q, args)
|
||||
if err != nil {
|
||||
|
||||
@@ -29,16 +29,17 @@ import (
|
||||
|
||||
type (
|
||||
Task struct {
|
||||
ID gid.GID `db:"id"`
|
||||
MeasureID gid.GID `db:"measure_id"`
|
||||
Name string `db:"name"`
|
||||
Description string `db:"description"`
|
||||
State TaskState `db:"state"`
|
||||
ReferenceID string `db:"reference_id"`
|
||||
TimeEstimate *time.Duration `db:"time_estimate"`
|
||||
AssignedToID *gid.GID `db:"assigned_to"`
|
||||
CreatedAt time.Time `db:"created_at"`
|
||||
UpdatedAt time.Time `db:"updated_at"`
|
||||
ID gid.GID `db:"id"`
|
||||
OrganizationID gid.GID `db:"organization_id"`
|
||||
MeasureID *gid.GID `db:"measure_id"`
|
||||
Name string `db:"name"`
|
||||
Description string `db:"description"`
|
||||
State TaskState `db:"state"`
|
||||
ReferenceID string `db:"reference_id"`
|
||||
TimeEstimate *time.Duration `db:"time_estimate"`
|
||||
AssignedToID *gid.GID `db:"assigned_to"`
|
||||
CreatedAt time.Time `db:"created_at"`
|
||||
UpdatedAt time.Time `db:"updated_at"`
|
||||
}
|
||||
|
||||
Tasks []*Task
|
||||
@@ -62,6 +63,7 @@ func (c *Task) LoadByID(
|
||||
q := `
|
||||
SELECT
|
||||
id,
|
||||
organization_id,
|
||||
measure_id,
|
||||
name,
|
||||
description,
|
||||
@@ -109,6 +111,7 @@ INSERT INTO
|
||||
tasks (
|
||||
tenant_id,
|
||||
id,
|
||||
organization_id,
|
||||
measure_id,
|
||||
name,
|
||||
description,
|
||||
@@ -122,6 +125,7 @@ INSERT INTO
|
||||
VALUES (
|
||||
@tenant_id,
|
||||
@task_id,
|
||||
@organization_id,
|
||||
@measure_id,
|
||||
@name,
|
||||
@description,
|
||||
@@ -135,17 +139,18 @@ VALUES (
|
||||
`
|
||||
|
||||
args := pgx.StrictNamedArgs{
|
||||
"tenant_id": scope.GetTenantID(),
|
||||
"task_id": c.ID,
|
||||
"measure_id": c.MeasureID,
|
||||
"name": c.Name,
|
||||
"description": c.Description,
|
||||
"reference_id": c.ReferenceID,
|
||||
"state": c.State,
|
||||
"time_estimate": c.TimeEstimate,
|
||||
"assigned_to": c.AssignedToID,
|
||||
"created_at": c.CreatedAt,
|
||||
"updated_at": c.UpdatedAt,
|
||||
"tenant_id": scope.GetTenantID(),
|
||||
"task_id": c.ID,
|
||||
"organization_id": c.OrganizationID,
|
||||
"measure_id": c.MeasureID,
|
||||
"name": c.Name,
|
||||
"description": c.Description,
|
||||
"reference_id": c.ReferenceID,
|
||||
"state": c.State,
|
||||
"time_estimate": c.TimeEstimate,
|
||||
"assigned_to": c.AssignedToID,
|
||||
"created_at": c.CreatedAt,
|
||||
"updated_at": c.UpdatedAt,
|
||||
}
|
||||
_, err := conn.Exec(ctx, q, args)
|
||||
return err
|
||||
@@ -161,6 +166,7 @@ INSERT INTO
|
||||
tasks (
|
||||
tenant_id,
|
||||
id,
|
||||
organization_id,
|
||||
measure_id,
|
||||
name,
|
||||
description,
|
||||
@@ -174,6 +180,7 @@ INSERT INTO
|
||||
VALUES (
|
||||
@tenant_id,
|
||||
@task_id,
|
||||
@organization_id,
|
||||
@measure_id,
|
||||
@name,
|
||||
@description,
|
||||
@@ -190,6 +197,7 @@ ON CONFLICT (measure_id, reference_id) DO UPDATE SET
|
||||
updated_at = @updated_at
|
||||
RETURNING
|
||||
id,
|
||||
organization_id,
|
||||
measure_id,
|
||||
name,
|
||||
description,
|
||||
@@ -202,17 +210,18 @@ RETURNING
|
||||
`
|
||||
|
||||
args := pgx.StrictNamedArgs{
|
||||
"tenant_id": scope.GetTenantID(),
|
||||
"task_id": c.ID,
|
||||
"measure_id": c.MeasureID,
|
||||
"name": c.Name,
|
||||
"description": c.Description,
|
||||
"reference_id": c.ReferenceID,
|
||||
"state": c.State,
|
||||
"time_estimate": c.TimeEstimate,
|
||||
"assigned_to": c.AssignedToID,
|
||||
"created_at": c.CreatedAt,
|
||||
"updated_at": c.UpdatedAt,
|
||||
"tenant_id": scope.GetTenantID(),
|
||||
"task_id": c.ID,
|
||||
"organization_id": c.OrganizationID,
|
||||
"measure_id": c.MeasureID,
|
||||
"name": c.Name,
|
||||
"description": c.Description,
|
||||
"reference_id": c.ReferenceID,
|
||||
"state": c.State,
|
||||
"time_estimate": c.TimeEstimate,
|
||||
"assigned_to": c.AssignedToID,
|
||||
"created_at": c.CreatedAt,
|
||||
"updated_at": c.UpdatedAt,
|
||||
}
|
||||
rows, err := conn.Query(ctx, q, args)
|
||||
if err != nil {
|
||||
@@ -229,6 +238,54 @@ RETURNING
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Tasks) LoadByOrganizationID(
|
||||
ctx context.Context,
|
||||
conn pg.Conn,
|
||||
scope Scoper,
|
||||
organizationID gid.GID,
|
||||
cursor *page.Cursor[TaskOrderField],
|
||||
) error {
|
||||
q := `
|
||||
SELECT
|
||||
id,
|
||||
measure_id,
|
||||
organization_id,
|
||||
name,
|
||||
description,
|
||||
state,
|
||||
reference_id,
|
||||
time_estimate,
|
||||
assigned_to,
|
||||
created_at,
|
||||
updated_at
|
||||
FROM
|
||||
tasks
|
||||
WHERE
|
||||
%s
|
||||
AND organization_id = @organization_id
|
||||
AND %s
|
||||
`
|
||||
q = fmt.Sprintf(q, scope.SQLFragment(), cursor.SQLFragment())
|
||||
|
||||
args := pgx.StrictNamedArgs{"organization_id": organizationID}
|
||||
maps.Copy(args, scope.SQLArguments())
|
||||
maps.Copy(args, cursor.SQLArguments())
|
||||
|
||||
rows, err := conn.Query(ctx, q, args)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot query tasks: %w", err)
|
||||
}
|
||||
|
||||
tasks, err := pgx.CollectRows(rows, pgx.RowToAddrOfStructByName[Task])
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot collect tasks: %w", err)
|
||||
}
|
||||
|
||||
*c = tasks
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Tasks) LoadByMeasureID(
|
||||
ctx context.Context,
|
||||
conn pg.Conn,
|
||||
@@ -240,6 +297,7 @@ func (c *Tasks) LoadByMeasureID(
|
||||
SELECT
|
||||
id,
|
||||
measure_id,
|
||||
organization_id,
|
||||
name,
|
||||
description,
|
||||
state,
|
||||
@@ -288,7 +346,8 @@ SET
|
||||
description = @description,
|
||||
state = @state,
|
||||
time_estimate = @time_estimate,
|
||||
updated_at = @updated_at
|
||||
updated_at = @updated_at,
|
||||
assigned_to = @assigned_to
|
||||
WHERE %s
|
||||
AND id = @task_id
|
||||
`
|
||||
@@ -301,6 +360,7 @@ WHERE %s
|
||||
"state": c.State,
|
||||
"time_estimate": c.TimeEstimate,
|
||||
"updated_at": c.UpdatedAt,
|
||||
"assigned_to": c.AssignedToID,
|
||||
}
|
||||
|
||||
maps.Copy(args, scope.SQLArguments())
|
||||
@@ -309,102 +369,6 @@ WHERE %s
|
||||
return err
|
||||
}
|
||||
|
||||
func (c *Task) AssignTo(
|
||||
ctx context.Context,
|
||||
conn pg.Conn,
|
||||
scope Scoper,
|
||||
assignTo gid.GID,
|
||||
) error {
|
||||
q := `
|
||||
UPDATE tasks SET
|
||||
assigned_to = @assigned_to,
|
||||
updated_at = @updated_at
|
||||
WHERE %s
|
||||
AND id = @task_id
|
||||
RETURNING
|
||||
id,
|
||||
measure_id,
|
||||
name,
|
||||
description,
|
||||
reference_id,
|
||||
state,
|
||||
time_estimate,
|
||||
assigned_to,
|
||||
created_at,
|
||||
updated_at
|
||||
`
|
||||
q = fmt.Sprintf(q, scope.SQLFragment())
|
||||
|
||||
args := pgx.NamedArgs{
|
||||
"task_id": c.ID,
|
||||
"assigned_to": assignTo,
|
||||
"updated_at": time.Now(),
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
func (c *Task) Unassign(
|
||||
ctx context.Context,
|
||||
conn pg.Conn,
|
||||
scope Scoper,
|
||||
) error {
|
||||
q := `
|
||||
UPDATE tasks SET
|
||||
assigned_to = NULL,
|
||||
updated_at = @updated_at
|
||||
WHERE %s
|
||||
AND id = @task_id
|
||||
RETURNING
|
||||
id,
|
||||
measure_id,
|
||||
name,
|
||||
description,
|
||||
reference_id,
|
||||
state,
|
||||
time_estimate,
|
||||
assigned_to,
|
||||
created_at,
|
||||
updated_at
|
||||
`
|
||||
q = fmt.Sprintf(q, scope.SQLFragment())
|
||||
|
||||
args := pgx.NamedArgs{
|
||||
"task_id": c.ID,
|
||||
"updated_at": time.Now(),
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
func (c *Task) Delete(
|
||||
ctx context.Context,
|
||||
conn pg.Conn,
|
||||
|
||||
@@ -119,8 +119,12 @@ func (s EvidenceService) Request(
|
||||
return fmt.Errorf("cannot load task: %w", err)
|
||||
}
|
||||
|
||||
if task.MeasureID == nil {
|
||||
return fmt.Errorf("task %q has no measure", req.TaskID)
|
||||
}
|
||||
|
||||
evidence.TaskID = req.TaskID
|
||||
evidence.MeasureID = task.MeasureID
|
||||
evidence.MeasureID = *task.MeasureID
|
||||
} else if req.MeasureID != nil {
|
||||
evidence.MeasureID = *req.MeasureID
|
||||
} else {
|
||||
@@ -298,7 +302,11 @@ func (s EvidenceService) UploadTaskEvidence(
|
||||
return fmt.Errorf("cannot load task %q: %w", req.TaskID, err)
|
||||
}
|
||||
|
||||
evidence.MeasureID = task.MeasureID
|
||||
if task.MeasureID == nil {
|
||||
return fmt.Errorf("task %q has no measure", req.TaskID)
|
||||
}
|
||||
|
||||
evidence.MeasureID = *task.MeasureID
|
||||
|
||||
if err := evidence.Insert(ctx, conn, s.svc.scope); err != nil {
|
||||
return fmt.Errorf("cannot insert evidence: %w", err)
|
||||
|
||||
@@ -168,14 +168,15 @@ func (s MeasureService) Import(
|
||||
taskID := gid.New(organizationID.TenantID(), coredata.TaskEntityType)
|
||||
|
||||
task := &coredata.Task{
|
||||
ID: taskID,
|
||||
MeasureID: measure.ID,
|
||||
Name: req.Measures[i].Tasks[j].Name,
|
||||
Description: req.Measures[i].Tasks[j].Description,
|
||||
ReferenceID: req.Measures[i].Tasks[j].ReferenceID,
|
||||
State: coredata.TaskStateTodo,
|
||||
CreatedAt: now,
|
||||
UpdatedAt: now,
|
||||
ID: taskID,
|
||||
OrganizationID: organizationID,
|
||||
MeasureID: &measure.ID,
|
||||
Name: req.Measures[i].Tasks[j].Name,
|
||||
Description: req.Measures[i].Tasks[j].Description,
|
||||
ReferenceID: req.Measures[i].Tasks[j].ReferenceID,
|
||||
State: coredata.TaskStateTodo,
|
||||
CreatedAt: now,
|
||||
UpdatedAt: now,
|
||||
}
|
||||
|
||||
if err := task.Upsert(ctx, tx, s.svc.scope); err != nil {
|
||||
@@ -32,11 +32,12 @@ type (
|
||||
}
|
||||
|
||||
CreateTaskRequest struct {
|
||||
MeasureID gid.GID
|
||||
Name string
|
||||
Description string
|
||||
TimeEstimate *time.Duration
|
||||
AssignedToID *gid.GID
|
||||
OrganizationID gid.GID
|
||||
MeasureID *gid.GID
|
||||
Name string
|
||||
Description string
|
||||
TimeEstimate *time.Duration
|
||||
AssignedToID *gid.GID
|
||||
}
|
||||
|
||||
UpdateTaskRequest struct {
|
||||
@@ -61,21 +62,23 @@ func (s TaskService) Create(
|
||||
}
|
||||
|
||||
task := &coredata.Task{
|
||||
ID: taskID,
|
||||
MeasureID: req.MeasureID,
|
||||
Name: req.Name,
|
||||
Description: req.Description,
|
||||
TimeEstimate: req.TimeEstimate,
|
||||
AssignedToID: req.AssignedToID,
|
||||
State: coredata.TaskStateTodo,
|
||||
ReferenceID: "custom-task-" + referenceID.String(),
|
||||
CreatedAt: now,
|
||||
UpdatedAt: now,
|
||||
ID: taskID,
|
||||
OrganizationID: req.OrganizationID,
|
||||
MeasureID: req.MeasureID,
|
||||
Name: req.Name,
|
||||
Description: req.Description,
|
||||
TimeEstimate: req.TimeEstimate,
|
||||
AssignedToID: req.AssignedToID,
|
||||
State: coredata.TaskStateTodo,
|
||||
ReferenceID: "custom-task-" + referenceID.String(),
|
||||
CreatedAt: now,
|
||||
UpdatedAt: now,
|
||||
}
|
||||
|
||||
err = s.svc.pg.WithTx(
|
||||
ctx,
|
||||
func(conn pg.Conn) error {
|
||||
|
||||
if err := task.Insert(ctx, conn, s.svc.scope); err != nil {
|
||||
return fmt.Errorf("cannot insert task: %w", err)
|
||||
}
|
||||
@@ -119,7 +122,18 @@ func (s TaskService) Assign(
|
||||
err := s.svc.pg.WithTx(
|
||||
ctx,
|
||||
func(conn pg.Conn) error {
|
||||
return task.AssignTo(ctx, conn, s.svc.scope, assignedToID)
|
||||
if err := task.LoadByID(ctx, conn, s.svc.scope, taskID); err != nil {
|
||||
return fmt.Errorf("cannot load task %q: %w", taskID, err)
|
||||
}
|
||||
|
||||
task.AssignedToID = &assignedToID
|
||||
task.UpdatedAt = time.Now()
|
||||
|
||||
if err := task.Update(ctx, conn, s.svc.scope); err != nil {
|
||||
return fmt.Errorf("cannot assign task %q to %q: %w", taskID, assignedToID, err)
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
@@ -133,12 +147,23 @@ func (s TaskService) Unassign(
|
||||
ctx context.Context,
|
||||
taskID gid.GID,
|
||||
) (*coredata.Task, error) {
|
||||
task := &coredata.Task{ID: taskID}
|
||||
task := &coredata.Task{}
|
||||
|
||||
err := s.svc.pg.WithTx(
|
||||
ctx,
|
||||
func(conn pg.Conn) error {
|
||||
return task.Unassign(ctx, conn, s.svc.scope)
|
||||
if err := task.LoadByID(ctx, conn, s.svc.scope, taskID); err != nil {
|
||||
return fmt.Errorf("cannot load task %q: %w", taskID, err)
|
||||
}
|
||||
|
||||
task.AssignedToID = nil
|
||||
task.UpdatedAt = time.Now()
|
||||
|
||||
if err := task.Update(ctx, conn, s.svc.scope); err != nil {
|
||||
return fmt.Errorf("cannot unassign task %q: %w", taskID, err)
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
@@ -152,7 +177,8 @@ func (s TaskService) Update(
|
||||
ctx context.Context,
|
||||
req UpdateTaskRequest,
|
||||
) (*coredata.Task, error) {
|
||||
task := &coredata.Task{ID: req.TaskID}
|
||||
|
||||
task := &coredata.Task{}
|
||||
|
||||
err := s.svc.pg.WithTx(
|
||||
ctx,
|
||||
@@ -212,6 +238,26 @@ func (s TaskService) Delete(
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s TaskService) ListForOrganizationID(
|
||||
ctx context.Context,
|
||||
organizationID gid.GID,
|
||||
cursor *page.Cursor[coredata.TaskOrderField],
|
||||
) (*page.Page[*coredata.Task, coredata.TaskOrderField], error) {
|
||||
var tasks coredata.Tasks
|
||||
|
||||
err := s.svc.pg.WithConn(
|
||||
ctx,
|
||||
func(conn pg.Conn) error {
|
||||
return tasks.LoadByOrganizationID(ctx, conn, s.svc.scope, organizationID, cursor)
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return page.NewPage(tasks, cursor), nil
|
||||
}
|
||||
|
||||
func (s TaskService) ListForMeasureID(
|
||||
ctx context.Context,
|
||||
measureID gid.GID,
|
||||
|
||||
@@ -461,6 +461,14 @@ type Organization implements Node {
|
||||
orderBy: RiskOrder
|
||||
): RiskConnection! @goField(forceResolver: true)
|
||||
|
||||
tasks(
|
||||
first: Int
|
||||
after: CursorKey
|
||||
last: Int
|
||||
before: CursorKey
|
||||
orderBy: TaskOrder
|
||||
): TaskConnection! @goField(forceResolver: true)
|
||||
|
||||
createdAt: Datetime!
|
||||
updatedAt: Datetime!
|
||||
}
|
||||
@@ -498,6 +506,8 @@ type Vendor implements Node {
|
||||
name: String!
|
||||
description: String
|
||||
|
||||
organization: Organization! @goField(forceResolver: true)
|
||||
|
||||
complianceReports(
|
||||
first: Int
|
||||
after: CursorKey
|
||||
@@ -538,10 +548,8 @@ type VendorComplianceReport implements Node {
|
||||
reportDate: Datetime!
|
||||
validUntil: Datetime
|
||||
reportName: String!
|
||||
|
||||
fileUrl: String! @goField(forceResolver: true)
|
||||
fileSize: Int!
|
||||
|
||||
createdAt: Datetime!
|
||||
updatedAt: Datetime!
|
||||
}
|
||||
@@ -551,6 +559,8 @@ type Framework implements Node {
|
||||
name: String!
|
||||
description: String!
|
||||
|
||||
organization: Organization! @goField(forceResolver: true)
|
||||
|
||||
controls(
|
||||
first: Int
|
||||
after: CursorKey
|
||||
@@ -569,6 +579,8 @@ type Control implements Node {
|
||||
name: String!
|
||||
description: String!
|
||||
|
||||
framework: Framework! @goField(forceResolver: true)
|
||||
|
||||
measures(
|
||||
first: Int
|
||||
after: CursorKey
|
||||
@@ -640,6 +652,9 @@ type Task implements Node {
|
||||
timeEstimate: Duration
|
||||
assignedTo: People @goField(forceResolver: true)
|
||||
|
||||
organization: Organization! @goField(forceResolver: true)
|
||||
measure: Measure @goField(forceResolver: true)
|
||||
|
||||
evidences(
|
||||
first: Int
|
||||
after: CursorKey
|
||||
@@ -663,6 +678,9 @@ type Evidence implements Node {
|
||||
url: String
|
||||
description: String!
|
||||
|
||||
task: Task @goField(forceResolver: true)
|
||||
measure: Measure! @goField(forceResolver: true)
|
||||
|
||||
createdAt: Datetime!
|
||||
updatedAt: Datetime!
|
||||
}
|
||||
@@ -673,6 +691,7 @@ type Policy implements Node {
|
||||
description: String!
|
||||
currentPublishedVersion: Int
|
||||
owner: People! @goField(forceResolver: true)
|
||||
organization: Organization! @goField(forceResolver: true)
|
||||
|
||||
versions(
|
||||
first: Int
|
||||
@@ -710,6 +729,7 @@ type Risk implements Node {
|
||||
note: String!
|
||||
|
||||
owner: People @goField(forceResolver: true)
|
||||
organization: Organization! @goField(forceResolver: true)
|
||||
|
||||
measures(
|
||||
first: Int
|
||||
@@ -1140,7 +1160,8 @@ input ImportMeasureInput {
|
||||
}
|
||||
|
||||
input CreateTaskInput {
|
||||
measureId: ID!
|
||||
organizationId: ID!
|
||||
measureId: ID
|
||||
name: String!
|
||||
description: String!
|
||||
timeEstimate: Duration
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -67,6 +67,7 @@ type Control struct {
|
||||
ReferenceID string `json:"referenceId"`
|
||||
Name string `json:"name"`
|
||||
Description string `json:"description"`
|
||||
Framework *Framework `json:"framework"`
|
||||
Measures *MeasureConnection `json:"measures"`
|
||||
Policies *PolicyConnection `json:"policies"`
|
||||
CreatedAt time.Time `json:"createdAt"`
|
||||
@@ -215,11 +216,12 @@ type CreateRiskPolicyMappingPayload struct {
|
||||
}
|
||||
|
||||
type CreateTaskInput struct {
|
||||
MeasureID gid.GID `json:"measureId"`
|
||||
Name string `json:"name"`
|
||||
Description string `json:"description"`
|
||||
TimeEstimate *time.Duration `json:"timeEstimate,omitempty"`
|
||||
AssignedToID *gid.GID `json:"assignedToId,omitempty"`
|
||||
OrganizationID gid.GID `json:"organizationId"`
|
||||
MeasureID *gid.GID `json:"measureId,omitempty"`
|
||||
Name string `json:"name"`
|
||||
Description string `json:"description"`
|
||||
TimeEstimate *time.Duration `json:"timeEstimate,omitempty"`
|
||||
AssignedToID *gid.GID `json:"assignedToId,omitempty"`
|
||||
}
|
||||
|
||||
type CreateTaskPayload struct {
|
||||
@@ -389,6 +391,8 @@ type Evidence struct {
|
||||
Filename string `json:"filename"`
|
||||
URL *string `json:"url,omitempty"`
|
||||
Description string `json:"description"`
|
||||
Task *Task `json:"task,omitempty"`
|
||||
Measure *Measure `json:"measure"`
|
||||
CreatedAt time.Time `json:"createdAt"`
|
||||
UpdatedAt time.Time `json:"updatedAt"`
|
||||
}
|
||||
@@ -407,12 +411,13 @@ type EvidenceEdge struct {
|
||||
}
|
||||
|
||||
type Framework struct {
|
||||
ID gid.GID `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Description string `json:"description"`
|
||||
Controls *ControlConnection `json:"controls"`
|
||||
CreatedAt time.Time `json:"createdAt"`
|
||||
UpdatedAt time.Time `json:"updatedAt"`
|
||||
ID gid.GID `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Description string `json:"description"`
|
||||
Organization *Organization `json:"organization"`
|
||||
Controls *ControlConnection `json:"controls"`
|
||||
CreatedAt time.Time `json:"createdAt"`
|
||||
UpdatedAt time.Time `json:"updatedAt"`
|
||||
}
|
||||
|
||||
func (Framework) IsNode() {}
|
||||
@@ -509,6 +514,7 @@ type Organization struct {
|
||||
Policies *PolicyConnection `json:"policies"`
|
||||
Measures *MeasureConnection `json:"measures"`
|
||||
Risks *RiskConnection `json:"risks"`
|
||||
Tasks *TaskConnection `json:"tasks"`
|
||||
CreatedAt time.Time `json:"createdAt"`
|
||||
UpdatedAt time.Time `json:"updatedAt"`
|
||||
}
|
||||
@@ -567,6 +573,7 @@ type Policy struct {
|
||||
Description string `json:"description"`
|
||||
CurrentPublishedVersion *int `json:"currentPublishedVersion,omitempty"`
|
||||
Owner *People `json:"owner"`
|
||||
Organization *Organization `json:"organization"`
|
||||
Versions *PolicyVersionConnection `json:"versions"`
|
||||
Controls *ControlConnection `json:"controls"`
|
||||
CreatedAt time.Time `json:"createdAt"`
|
||||
@@ -702,6 +709,7 @@ type Risk struct {
|
||||
ResidualSeverity int `json:"residualSeverity"`
|
||||
Note string `json:"note"`
|
||||
Owner *People `json:"owner,omitempty"`
|
||||
Organization *Organization `json:"organization"`
|
||||
Measures *MeasureConnection `json:"measures"`
|
||||
Policies *PolicyConnection `json:"policies"`
|
||||
Controls *ControlConnection `json:"controls"`
|
||||
@@ -742,6 +750,8 @@ type Task struct {
|
||||
State coredata.TaskState `json:"state"`
|
||||
TimeEstimate *time.Duration `json:"timeEstimate,omitempty"`
|
||||
AssignedTo *People `json:"assignedTo,omitempty"`
|
||||
Organization *Organization `json:"organization"`
|
||||
Measure *Measure `json:"measure,omitempty"`
|
||||
Evidences *EvidenceConnection `json:"evidences"`
|
||||
CreatedAt time.Time `json:"createdAt"`
|
||||
UpdatedAt time.Time `json:"updatedAt"`
|
||||
@@ -943,6 +953,7 @@ type Vendor struct {
|
||||
ID gid.GID `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Description *string `json:"description,omitempty"`
|
||||
Organization *Organization `json:"organization"`
|
||||
ComplianceReports *VendorComplianceReportConnection `json:"complianceReports"`
|
||||
RiskAssessments *VendorRiskAssessmentConnection `json:"riskAssessments"`
|
||||
BusinessOwner *People `json:"businessOwner,omitempty"`
|
||||
|
||||
@@ -20,6 +20,23 @@ import (
|
||||
"github.com/vektah/gqlparser/v2/gqlerror"
|
||||
)
|
||||
|
||||
// Framework is the resolver for the framework field.
|
||||
func (r *controlResolver) Framework(ctx context.Context, obj *types.Control) (*types.Framework, error) {
|
||||
svc := GetTenantService(ctx, r.proboSvc, obj.ID.TenantID())
|
||||
|
||||
control, err := svc.Controls.Get(ctx, obj.ID)
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("cannot get control: %w", err))
|
||||
}
|
||||
|
||||
framework, err := svc.Frameworks.Get(ctx, control.FrameworkID)
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("cannot get framework: %w", err))
|
||||
}
|
||||
|
||||
return types.NewFramework(framework), nil
|
||||
}
|
||||
|
||||
// Measures is the resolver for the measures field.
|
||||
func (r *controlResolver) Measures(ctx context.Context, obj *types.Control, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.MeasureOrderBy) (*types.MeasureConnection, error) {
|
||||
svc := GetTenantService(ctx, r.proboSvc, obj.ID.TenantID())
|
||||
@@ -87,6 +104,61 @@ func (r *evidenceResolver) FileURL(ctx context.Context, obj *types.Evidence) (*s
|
||||
return &result, nil
|
||||
}
|
||||
|
||||
// Task is the resolver for the task field.
|
||||
func (r *evidenceResolver) Task(ctx context.Context, obj *types.Evidence) (*types.Task, error) {
|
||||
svc := GetTenantService(ctx, r.proboSvc, obj.ID.TenantID())
|
||||
|
||||
evidence, err := svc.Evidences.Get(ctx, obj.ID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot load evidence: %w", err)
|
||||
}
|
||||
|
||||
if evidence.TaskID == nil {
|
||||
return nil, fmt.Errorf("evidence is not associated with a task")
|
||||
}
|
||||
|
||||
task, err := svc.Tasks.Get(ctx, *evidence.TaskID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot load task: %w", err)
|
||||
}
|
||||
|
||||
return types.NewTask(task), nil
|
||||
}
|
||||
|
||||
// Measure is the resolver for the measure field.
|
||||
func (r *evidenceResolver) Measure(ctx context.Context, obj *types.Evidence) (*types.Measure, error) {
|
||||
svc := GetTenantService(ctx, r.proboSvc, obj.ID.TenantID())
|
||||
|
||||
evidence, err := svc.Evidences.Get(ctx, obj.ID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot load evidence: %w", err)
|
||||
}
|
||||
|
||||
measure, err := svc.Measures.Get(ctx, evidence.MeasureID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot load measure: %w", err)
|
||||
}
|
||||
|
||||
return types.NewMeasure(measure), nil
|
||||
}
|
||||
|
||||
// Organization is the resolver for the organization field.
|
||||
func (r *frameworkResolver) Organization(ctx context.Context, obj *types.Framework) (*types.Organization, error) {
|
||||
svc := GetTenantService(ctx, r.proboSvc, obj.ID.TenantID())
|
||||
|
||||
framework, err := svc.Frameworks.Get(ctx, obj.ID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot load framework: %w", err)
|
||||
}
|
||||
|
||||
organization, err := svc.Organizations.Get(ctx, framework.OrganizationID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot load organization: %w", err)
|
||||
}
|
||||
|
||||
return types.NewOrganization(organization), nil
|
||||
}
|
||||
|
||||
// Controls is the resolver for the controls field.
|
||||
func (r *frameworkResolver) Controls(ctx context.Context, obj *types.Framework, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.ControlOrderBy) (*types.ControlConnection, error) {
|
||||
svc := GetTenantService(ctx, r.proboSvc, obj.ID.TenantID())
|
||||
@@ -689,10 +761,11 @@ func (r *mutationResolver) CreateTask(ctx context.Context, input types.CreateTas
|
||||
svc := GetTenantService(ctx, r.proboSvc, input.MeasureID.TenantID())
|
||||
|
||||
task, err := svc.Tasks.Create(ctx, probo.CreateTaskRequest{
|
||||
MeasureID: input.MeasureID,
|
||||
Name: input.Name,
|
||||
Description: input.Description,
|
||||
TimeEstimate: input.TimeEstimate,
|
||||
MeasureID: input.MeasureID,
|
||||
OrganizationID: input.OrganizationID,
|
||||
Name: input.Name,
|
||||
Description: input.Description,
|
||||
TimeEstimate: input.TimeEstimate,
|
||||
})
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("cannot create task: %w", err))
|
||||
@@ -1416,6 +1489,31 @@ func (r *organizationResolver) Risks(ctx context.Context, obj *types.Organizatio
|
||||
return types.NewRiskConnection(page), nil
|
||||
}
|
||||
|
||||
// Tasks is the resolver for the tasks field.
|
||||
func (r *organizationResolver) Tasks(ctx context.Context, obj *types.Organization, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.TaskOrderBy) (*types.TaskConnection, error) {
|
||||
svc := GetTenantService(ctx, r.proboSvc, obj.ID.TenantID())
|
||||
|
||||
pageOrderBy := page.OrderBy[coredata.TaskOrderField]{
|
||||
Field: coredata.TaskOrderFieldCreatedAt,
|
||||
Direction: page.OrderDirectionDesc,
|
||||
}
|
||||
if orderBy != nil {
|
||||
pageOrderBy = page.OrderBy[coredata.TaskOrderField]{
|
||||
Field: orderBy.Field,
|
||||
Direction: orderBy.Direction,
|
||||
}
|
||||
}
|
||||
|
||||
cursor := types.NewCursor(first, after, last, before, pageOrderBy)
|
||||
|
||||
page, err := svc.Tasks.ListForOrganizationID(ctx, obj.ID, cursor)
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("cannot list organization tasks: %w", err))
|
||||
}
|
||||
|
||||
return types.NewTaskConnection(page), nil
|
||||
}
|
||||
|
||||
// Owner is the resolver for the owner field.
|
||||
func (r *policyResolver) Owner(ctx context.Context, obj *types.Policy) (*types.People, error) {
|
||||
svc := GetTenantService(ctx, r.proboSvc, obj.ID.TenantID())
|
||||
@@ -1434,6 +1532,23 @@ func (r *policyResolver) Owner(ctx context.Context, obj *types.Policy) (*types.P
|
||||
return types.NewPeople(owner), nil
|
||||
}
|
||||
|
||||
// Organization is the resolver for the organization field.
|
||||
func (r *policyResolver) Organization(ctx context.Context, obj *types.Policy) (*types.Organization, error) {
|
||||
svc := GetTenantService(ctx, r.proboSvc, obj.ID.TenantID())
|
||||
|
||||
policy, err := svc.Policies.Get(ctx, obj.ID)
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("cannot get policy: %w", err))
|
||||
}
|
||||
|
||||
organization, err := svc.Organizations.Get(ctx, policy.OrganizationID)
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("cannot get organization: %w", err))
|
||||
}
|
||||
|
||||
return types.NewOrganization(organization), nil
|
||||
}
|
||||
|
||||
// Versions is the resolver for the versions field.
|
||||
func (r *policyResolver) Versions(ctx context.Context, obj *types.Policy, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.PolicyVersionOrderBy, filter *types.PolicyVersionFilter) (*types.PolicyVersionConnection, error) {
|
||||
svc := GetTenantService(ctx, r.proboSvc, obj.ID.TenantID())
|
||||
@@ -1727,6 +1842,23 @@ func (r *riskResolver) Owner(ctx context.Context, obj *types.Risk) (*types.Peopl
|
||||
return types.NewPeople(owner), nil
|
||||
}
|
||||
|
||||
// Organization is the resolver for the organization field.
|
||||
func (r *riskResolver) Organization(ctx context.Context, obj *types.Risk) (*types.Organization, error) {
|
||||
svc := GetTenantService(ctx, r.proboSvc, obj.ID.TenantID())
|
||||
|
||||
risk, err := svc.Risks.Get(ctx, obj.ID)
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("cannot get risk: %w", err))
|
||||
}
|
||||
|
||||
organization, err := svc.Organizations.Get(ctx, risk.OrganizationID)
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("cannot get organization: %w", err))
|
||||
}
|
||||
|
||||
return types.NewOrganization(organization), nil
|
||||
}
|
||||
|
||||
// Measures is the resolver for the measures field.
|
||||
func (r *riskResolver) Measures(ctx context.Context, obj *types.Risk, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.MeasureOrderBy) (*types.MeasureConnection, error) {
|
||||
svc := GetTenantService(ctx, r.proboSvc, obj.ID.TenantID())
|
||||
@@ -1823,6 +1955,40 @@ func (r *taskResolver) AssignedTo(ctx context.Context, obj *types.Task) (*types.
|
||||
return types.NewPeople(people), nil
|
||||
}
|
||||
|
||||
// Organization is the resolver for the organization field.
|
||||
func (r *taskResolver) Organization(ctx context.Context, obj *types.Task) (*types.Organization, error) {
|
||||
svc := GetTenantService(ctx, r.proboSvc, obj.ID.TenantID())
|
||||
|
||||
task, err := svc.Tasks.Get(ctx, obj.ID)
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("cannot get task: %w", err))
|
||||
}
|
||||
|
||||
organization, err := svc.Organizations.Get(ctx, task.OrganizationID)
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("cannot get organization: %w", err))
|
||||
}
|
||||
|
||||
return types.NewOrganization(organization), nil
|
||||
}
|
||||
|
||||
// Measure is the resolver for the measure field.
|
||||
func (r *taskResolver) Measure(ctx context.Context, obj *types.Task) (*types.Measure, error) {
|
||||
svc := GetTenantService(ctx, r.proboSvc, obj.ID.TenantID())
|
||||
|
||||
task, err := svc.Tasks.Get(ctx, obj.ID)
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("cannot get task: %w", err))
|
||||
}
|
||||
|
||||
measure, err := svc.Measures.Get(ctx, *task.MeasureID)
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("cannot get measure: %w", err))
|
||||
}
|
||||
|
||||
return types.NewMeasure(measure), nil
|
||||
}
|
||||
|
||||
// Evidences is the resolver for the evidences field.
|
||||
func (r *taskResolver) Evidences(ctx context.Context, obj *types.Task, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.EvidenceOrderBy) (*types.EvidenceConnection, error) {
|
||||
svc := GetTenantService(ctx, r.proboSvc, obj.ID.TenantID())
|
||||
@@ -1859,6 +2025,23 @@ func (r *userResolver) People(ctx context.Context, obj *types.User, organization
|
||||
return types.NewPeople(people), nil
|
||||
}
|
||||
|
||||
// Organization is the resolver for the organization field.
|
||||
func (r *vendorResolver) Organization(ctx context.Context, obj *types.Vendor) (*types.Organization, error) {
|
||||
svc := GetTenantService(ctx, r.proboSvc, obj.ID.TenantID())
|
||||
|
||||
vendor, err := svc.Vendors.Get(ctx, obj.ID)
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("cannot get vendor: %w", err))
|
||||
}
|
||||
|
||||
organization, err := svc.Organizations.Get(ctx, vendor.OrganizationID)
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("cannot get organization: %w", err))
|
||||
}
|
||||
|
||||
return types.NewOrganization(organization), nil
|
||||
}
|
||||
|
||||
// ComplianceReports is the resolver for the complianceReports field.
|
||||
func (r *vendorResolver) ComplianceReports(ctx context.Context, obj *types.Vendor, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.VendorComplianceReportOrderBy) (*types.VendorComplianceReportConnection, error) {
|
||||
svc := GetTenantService(ctx, r.proboSvc, obj.ID.TenantID())
|
||||
|
||||
Reference in New Issue
Block a user