@@ -29,16 +29,17 @@ import (
|
||||
|
||||
type (
|
||||
Control struct {
|
||||
ID gid.GID `db:"id"`
|
||||
FrameworkID gid.GID `db:"framework_id"`
|
||||
Category string `db:"category"`
|
||||
Name string `db:"name"`
|
||||
Description string `db:"description"`
|
||||
State ControlState `db:"state"`
|
||||
ContentRef string `db:"content_ref"`
|
||||
CreatedAt time.Time `db:"created_at"`
|
||||
UpdatedAt time.Time `db:"updated_at"`
|
||||
Version int `db:"version"`
|
||||
ID gid.GID `db:"id"`
|
||||
FrameworkID gid.GID `db:"framework_id"`
|
||||
Category string `db:"category"`
|
||||
Name string `db:"name"`
|
||||
Description string `db:"description"`
|
||||
Importance ControlImportance `db:"importance"`
|
||||
State ControlState `db:"state"`
|
||||
ContentRef string `db:"content_ref"`
|
||||
CreatedAt time.Time `db:"created_at"`
|
||||
UpdatedAt time.Time `db:"updated_at"`
|
||||
Version int `db:"version"`
|
||||
}
|
||||
|
||||
Controls []*Control
|
||||
@@ -49,6 +50,7 @@ type (
|
||||
Description *string
|
||||
Category *string
|
||||
State *ControlState
|
||||
Importance *ControlImportance
|
||||
}
|
||||
)
|
||||
|
||||
@@ -70,6 +72,7 @@ SELECT
|
||||
name,
|
||||
description,
|
||||
state,
|
||||
importance,
|
||||
content_ref,
|
||||
created_at,
|
||||
updated_at,
|
||||
@@ -115,6 +118,7 @@ INSERT INTO
|
||||
framework_id,
|
||||
category,
|
||||
name,
|
||||
importance,
|
||||
state,
|
||||
description,
|
||||
content_ref,
|
||||
@@ -128,6 +132,7 @@ VALUES (
|
||||
@framework_id,
|
||||
@category,
|
||||
@name,
|
||||
@importance,
|
||||
@state,
|
||||
@description,
|
||||
@content_ref,
|
||||
@@ -149,6 +154,7 @@ VALUES (
|
||||
"created_at": c.CreatedAt,
|
||||
"updated_at": c.UpdatedAt,
|
||||
"state": c.State,
|
||||
"importance": c.Importance,
|
||||
}
|
||||
_, err := conn.Exec(ctx, q, args)
|
||||
return err
|
||||
@@ -169,6 +175,7 @@ SELECT
|
||||
name,
|
||||
description,
|
||||
state,
|
||||
importance,
|
||||
content_ref,
|
||||
created_at,
|
||||
updated_at,
|
||||
@@ -213,6 +220,7 @@ UPDATE controls SET
|
||||
description = COALESCE(@description, description),
|
||||
category = COALESCE(@category, category),
|
||||
state = COALESCE(@state, state),
|
||||
importance = COALESCE(@importance, importance),
|
||||
updated_at = @updated_at,
|
||||
version = version + 1
|
||||
WHERE %s
|
||||
@@ -224,6 +232,7 @@ RETURNING
|
||||
category,
|
||||
name,
|
||||
description,
|
||||
importance,
|
||||
state,
|
||||
content_ref,
|
||||
created_at,
|
||||
@@ -235,23 +244,16 @@ RETURNING
|
||||
args := pgx.NamedArgs{
|
||||
"control_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(),
|
||||
}
|
||||
|
||||
maps.Copy(args, scope.SQLArguments())
|
||||
|
||||
if params.Name != nil {
|
||||
args["name"] = *params.Name
|
||||
}
|
||||
if params.Description != nil {
|
||||
args["description"] = *params.Description
|
||||
}
|
||||
if params.Category != nil {
|
||||
args["category"] = *params.Category
|
||||
}
|
||||
|
||||
maps.Copy(args, scope.SQLArguments())
|
||||
|
||||
rows, err := conn.Query(ctx, q, args)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot query controls: %w", err)
|
||||
|
||||
100
pkg/coredata/control_importance.go
Normal file
100
pkg/coredata/control_importance.go
Normal file
@@ -0,0 +1,100 @@
|
||||
// Copyright (c) 2025 Probo Inc <hello@getprobo.com>.
|
||||
//
|
||||
// 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 coredata
|
||||
|
||||
import (
|
||||
"database/sql/driver"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
type ControlImportance uint8
|
||||
|
||||
const (
|
||||
ControlImportanceMandatory ControlImportance = iota
|
||||
ControlImportancePreferred
|
||||
ControlImportanceAdvanced
|
||||
)
|
||||
|
||||
func (i ControlImportance) String() string {
|
||||
return []string{"MANDATORY", "PREFERRED", "ADVANCED"}[i]
|
||||
}
|
||||
|
||||
func (i *ControlImportance) Scan(value interface{}) error {
|
||||
switch v := value.(type) {
|
||||
case uint8:
|
||||
*i = ControlImportance(v)
|
||||
case string:
|
||||
switch v {
|
||||
case "MANDATORY":
|
||||
*i = ControlImportanceMandatory
|
||||
case "PREFERRED":
|
||||
*i = ControlImportancePreferred
|
||||
case "ADVANCED":
|
||||
*i = ControlImportanceAdvanced
|
||||
default:
|
||||
return fmt.Errorf("invalid ControlImportance value: %q", v)
|
||||
}
|
||||
default:
|
||||
return fmt.Errorf("unsupported type for ControlImportance: %T", value)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (i ControlImportance) Value() (driver.Value, error) {
|
||||
return i.String(), nil
|
||||
}
|
||||
|
||||
func (i ControlImportance) MarshalJSON() ([]byte, error) {
|
||||
return json.Marshal(i.String())
|
||||
}
|
||||
|
||||
func (i *ControlImportance) UnmarshalJSON(data []byte) error {
|
||||
var s string
|
||||
if err := json.Unmarshal(data, &s); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
switch s {
|
||||
case "MANDATORY":
|
||||
*i = ControlImportanceMandatory
|
||||
case "PREFERRED":
|
||||
*i = ControlImportancePreferred
|
||||
case "ADVANCED":
|
||||
*i = ControlImportanceAdvanced
|
||||
default:
|
||||
return fmt.Errorf("invalid ControlImportance value: %q", s)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (i *ControlImportance) UnmarshalText(text []byte) error {
|
||||
var s string
|
||||
if err := json.Unmarshal(text, &s); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
switch s {
|
||||
case "MANDATORY":
|
||||
*i = ControlImportanceMandatory
|
||||
case "PREFERRED":
|
||||
*i = ControlImportancePreferred
|
||||
case "ADVANCED":
|
||||
*i = ControlImportanceAdvanced
|
||||
default:
|
||||
return fmt.Errorf("invalid ControlImportance value: %q", s)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
8
pkg/coredata/migrations/20250312T180800Z.sql
Normal file
8
pkg/coredata/migrations/20250312T180800Z.sql
Normal file
@@ -0,0 +1,8 @@
|
||||
CREATE TYPE control_importance AS ENUM ('MANDATORY', 'PREFERRED', 'ADVANCED');
|
||||
|
||||
ALTER TABLE controls ADD COLUMN importance control_importance NOT NULL DEFAULT 'MANDATORY';
|
||||
ALTER TABLE controls ALTER COLUMN importance DROP DEFAULT;
|
||||
|
||||
ALTER TABLE tasks ADD COLUMN time_estimate INTERVAL NOT NULL DEFAULT '00:30:00';
|
||||
ALTER TABLE tasks ALTER COLUMN time_estimate DROP DEFAULT;
|
||||
|
||||
@@ -36,6 +36,7 @@ type (
|
||||
Description string
|
||||
ContentRef string
|
||||
Category string
|
||||
Importance coredata.ControlImportance
|
||||
}
|
||||
|
||||
UpdateControlRequest struct {
|
||||
@@ -45,6 +46,7 @@ type (
|
||||
Description *string
|
||||
Category *string
|
||||
State *coredata.ControlState
|
||||
Importance *coredata.ControlImportance
|
||||
}
|
||||
)
|
||||
|
||||
@@ -78,6 +80,7 @@ func (s ControlService) Update(
|
||||
Description: req.Description,
|
||||
Category: req.Category,
|
||||
State: req.State,
|
||||
Importance: req.Importance,
|
||||
}
|
||||
|
||||
control := &coredata.Control{ID: req.ID}
|
||||
@@ -139,6 +142,7 @@ func (s ControlService) Create(
|
||||
Description: req.Description,
|
||||
Category: req.Category,
|
||||
State: coredata.ControlStateNotStarted,
|
||||
Importance: req.Importance,
|
||||
ContentRef: req.ContentRef,
|
||||
CreatedAt: now,
|
||||
UpdatedAt: now,
|
||||
|
||||
@@ -70,6 +70,22 @@ enum PeopleKind
|
||||
)
|
||||
}
|
||||
|
||||
enum ControlImportance
|
||||
@goModel(model: "github.com/getprobo/probo/pkg/coredata.ControlImportance") {
|
||||
MANDATORY
|
||||
@goEnum(
|
||||
value: "github.com/getprobo/probo/pkg/coredata.ControlImportanceMandatory"
|
||||
)
|
||||
PREFERRED
|
||||
@goEnum(
|
||||
value: "github.com/getprobo/probo/pkg/coredata.ControlImportancePreferred"
|
||||
)
|
||||
ADVANCED
|
||||
@goEnum(
|
||||
value: "github.com/getprobo/probo/pkg/coredata.ControlImportanceAdvanced"
|
||||
)
|
||||
}
|
||||
|
||||
type PageInfo {
|
||||
hasNextPage: Boolean!
|
||||
hasPreviousPage: Boolean!
|
||||
@@ -216,6 +232,7 @@ type Control implements Node {
|
||||
name: String!
|
||||
description: String!
|
||||
state: ControlState!
|
||||
importance: ControlImportance!
|
||||
|
||||
tasks(
|
||||
first: Int
|
||||
@@ -308,24 +325,35 @@ type Mutation {
|
||||
createVendor(input: CreateVendorInput!): CreateVendorPayload!
|
||||
updateVendor(input: UpdateVendorInput!): UpdateVendorPayload!
|
||||
deleteVendor(input: DeleteVendorInput!): DeleteVendorPayload!
|
||||
|
||||
createPeople(input: CreatePeopleInput!): CreatePeoplePayload!
|
||||
updatePeople(input: UpdatePeopleInput!): UpdatePeoplePayload!
|
||||
deletePeople(input: DeletePeopleInput!): DeletePeoplePayload!
|
||||
|
||||
createOrganization(
|
||||
input: CreateOrganizationInput!
|
||||
): CreateOrganizationPayload!
|
||||
updateOrganization(
|
||||
input: UpdateOrganizationInput!
|
||||
): UpdateOrganizationPayload!
|
||||
deleteOrganization(
|
||||
input: DeleteOrganizationInput!
|
||||
): DeleteOrganizationPayload!
|
||||
|
||||
createTask(input: CreateTaskInput!): CreateTaskPayload!
|
||||
updateTask(input: UpdateTaskInput!): UpdateTaskPayload!
|
||||
deleteTask(input: DeleteTaskInput!): DeleteTaskPayload!
|
||||
|
||||
createFramework(input: CreateFrameworkInput!): CreateFrameworkPayload!
|
||||
createControl(input: CreateControlInput!): CreateControlPayload!
|
||||
updateFramework(input: UpdateFrameworkInput!): UpdateFrameworkPayload!
|
||||
importFramework(input: ImportFrameworkInput!): ImportFrameworkPayload!
|
||||
|
||||
createControl(input: CreateControlInput!): CreateControlPayload!
|
||||
updateControl(input: UpdateControlInput!): UpdateControlPayload!
|
||||
|
||||
uploadEvidence(input: UploadEvidenceInput!): UploadEvidencePayload!
|
||||
deleteEvidence(input: DeleteEvidenceInput!): DeleteEvidencePayload!
|
||||
|
||||
createPolicy(input: CreatePolicyInput!): CreatePolicyPayload!
|
||||
updatePolicy(input: UpdatePolicyInput!): UpdatePolicyPayload!
|
||||
deletePolicy(input: DeletePolicyInput!): DeletePolicyPayload!
|
||||
@@ -431,6 +459,12 @@ input CreateOrganizationInput {
|
||||
name: String!
|
||||
}
|
||||
|
||||
input UpdateOrganizationInput {
|
||||
organizationId: ID!
|
||||
name: String
|
||||
logoUrl: String
|
||||
}
|
||||
|
||||
input DeleteOrganizationInput {
|
||||
organizationId: ID!
|
||||
}
|
||||
@@ -439,6 +473,10 @@ type CreateOrganizationPayload {
|
||||
organizationEdge: OrganizationEdge!
|
||||
}
|
||||
|
||||
type UpdateOrganizationPayload {
|
||||
organization: Organization!
|
||||
}
|
||||
|
||||
type DeleteOrganizationPayload {
|
||||
deletedOrganizationId: ID!
|
||||
}
|
||||
@@ -483,6 +521,7 @@ input CreateControlInput {
|
||||
name: String!
|
||||
description: String!
|
||||
category: String!
|
||||
importance: ControlImportance!
|
||||
}
|
||||
|
||||
type CreateControlPayload {
|
||||
@@ -508,6 +547,7 @@ input UpdateControlInput {
|
||||
description: String
|
||||
category: String
|
||||
state: ControlState
|
||||
importance: ControlImportance
|
||||
}
|
||||
|
||||
type UpdateControlPayload {
|
||||
@@ -616,3 +656,12 @@ input ConfirmEmailInput {
|
||||
type ConfirmEmailPayload {
|
||||
success: Boolean!
|
||||
}
|
||||
|
||||
input ImportFrameworkInput {
|
||||
organizationId: ID!
|
||||
file: Upload!
|
||||
}
|
||||
|
||||
type ImportFrameworkPayload {
|
||||
frameworkEdge: FrameworkEdge!
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -47,6 +47,7 @@ func NewControl(c *coredata.Control) *Control {
|
||||
Name: c.Name,
|
||||
Description: c.Description,
|
||||
State: c.State,
|
||||
Importance: c.Importance,
|
||||
CreatedAt: c.CreatedAt,
|
||||
UpdatedAt: c.UpdatedAt,
|
||||
}
|
||||
|
||||
@@ -25,15 +25,16 @@ type ConfirmEmailPayload struct {
|
||||
}
|
||||
|
||||
type Control struct {
|
||||
ID gid.GID `json:"id"`
|
||||
Version int `json:"version"`
|
||||
Category string `json:"category"`
|
||||
Name string `json:"name"`
|
||||
Description string `json:"description"`
|
||||
State coredata.ControlState `json:"state"`
|
||||
Tasks *TaskConnection `json:"tasks"`
|
||||
CreatedAt time.Time `json:"createdAt"`
|
||||
UpdatedAt time.Time `json:"updatedAt"`
|
||||
ID gid.GID `json:"id"`
|
||||
Version int `json:"version"`
|
||||
Category string `json:"category"`
|
||||
Name string `json:"name"`
|
||||
Description string `json:"description"`
|
||||
State coredata.ControlState `json:"state"`
|
||||
Importance coredata.ControlImportance `json:"importance"`
|
||||
Tasks *TaskConnection `json:"tasks"`
|
||||
CreatedAt time.Time `json:"createdAt"`
|
||||
UpdatedAt time.Time `json:"updatedAt"`
|
||||
}
|
||||
|
||||
func (Control) IsNode() {}
|
||||
@@ -50,10 +51,11 @@ type ControlEdge struct {
|
||||
}
|
||||
|
||||
type CreateControlInput struct {
|
||||
FrameworkID gid.GID `json:"frameworkId"`
|
||||
Name string `json:"name"`
|
||||
Description string `json:"description"`
|
||||
Category string `json:"category"`
|
||||
FrameworkID gid.GID `json:"frameworkId"`
|
||||
Name string `json:"name"`
|
||||
Description string `json:"description"`
|
||||
Category string `json:"category"`
|
||||
Importance coredata.ControlImportance `json:"importance"`
|
||||
}
|
||||
|
||||
type CreateControlPayload struct {
|
||||
@@ -225,6 +227,15 @@ type FrameworkEdge struct {
|
||||
Node *Framework `json:"node"`
|
||||
}
|
||||
|
||||
type ImportFrameworkInput struct {
|
||||
OrganizationID gid.GID `json:"organizationId"`
|
||||
File graphql.Upload `json:"file"`
|
||||
}
|
||||
|
||||
type ImportFrameworkPayload struct {
|
||||
FrameworkEdge *FrameworkEdge `json:"frameworkEdge"`
|
||||
}
|
||||
|
||||
type Mutation struct {
|
||||
}
|
||||
|
||||
@@ -342,12 +353,13 @@ type TaskEdge struct {
|
||||
}
|
||||
|
||||
type UpdateControlInput struct {
|
||||
ID gid.GID `json:"id"`
|
||||
ExpectedVersion int `json:"expectedVersion"`
|
||||
Name *string `json:"name,omitempty"`
|
||||
Description *string `json:"description,omitempty"`
|
||||
Category *string `json:"category,omitempty"`
|
||||
State *coredata.ControlState `json:"state,omitempty"`
|
||||
ID gid.GID `json:"id"`
|
||||
ExpectedVersion int `json:"expectedVersion"`
|
||||
Name *string `json:"name,omitempty"`
|
||||
Description *string `json:"description,omitempty"`
|
||||
Category *string `json:"category,omitempty"`
|
||||
State *coredata.ControlState `json:"state,omitempty"`
|
||||
Importance *coredata.ControlImportance `json:"importance,omitempty"`
|
||||
}
|
||||
|
||||
type UpdateControlPayload struct {
|
||||
@@ -365,6 +377,16 @@ type UpdateFrameworkPayload struct {
|
||||
Framework *Framework `json:"framework"`
|
||||
}
|
||||
|
||||
type UpdateOrganizationInput struct {
|
||||
OrganizationID gid.GID `json:"organizationId"`
|
||||
Name *string `json:"name,omitempty"`
|
||||
LogoURL *string `json:"logoUrl,omitempty"`
|
||||
}
|
||||
|
||||
type UpdateOrganizationPayload struct {
|
||||
Organization *Organization `json:"organization"`
|
||||
}
|
||||
|
||||
type UpdatePeopleInput struct {
|
||||
ID gid.GID `json:"id"`
|
||||
ExpectedVersion int `json:"expectedVersion"`
|
||||
|
||||
@@ -321,6 +321,7 @@ func (r *mutationResolver) UpdateControl(ctx context.Context, input types.Update
|
||||
Description: input.Description,
|
||||
Category: input.Category,
|
||||
State: input.State,
|
||||
Importance: input.Importance,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot update control: %w", err)
|
||||
|
||||
Reference in New Issue
Block a user