Refactor SOA

Signed-off-by: Bryan Frimin <bryan@getprobo.com>
This commit is contained in:
Bryan Frimin
2026-01-21 10:35:13 -08:00
parent d5e6d11845
commit 4bdf941d3a
65 changed files with 7106 additions and 6971 deletions

View File

@@ -29,7 +29,7 @@ import (
)
type (
StateOfApplicabilityControl struct {
ApplicabilityStatement struct {
ID gid.GID `db:"id"`
StateOfApplicabilityID gid.GID `db:"state_of_applicability_id"`
ControlID gid.GID `db:"control_id"`
@@ -41,53 +41,80 @@ type (
UpdatedAt time.Time `db:"updated_at"`
}
StateOfApplicabilityControls []*StateOfApplicabilityControl
AvailableStateOfApplicabilityControl struct {
ControlID gid.GID `db:"control_id"`
SectionTitle string `db:"section_title"`
Name string `db:"name"`
FrameworkID gid.GID `db:"framework_id"`
FrameworkName string `db:"framework_name"`
OrganizationID gid.GID `db:"organization_id"`
StateOfApplicabilityID *gid.GID `db:"state_of_applicability_id"`
Applicability *bool `db:"applicability"`
Justification *string `db:"justification"`
BestPractice bool `db:"best_practice"`
Regulatory bool `db:"regulatory"`
Contractual bool `db:"contractual"`
RiskAssessment bool `db:"risk_assessment"`
}
AvailableStateOfApplicabilityControls []*AvailableStateOfApplicabilityControl
ApplicabilityStatements []*ApplicabilityStatement
)
func (s StateOfApplicabilityControl) CursorKey(orderBy StateOfApplicabilityOrderField) page.CursorKey {
func (s ApplicabilityStatement) CursorKey(orderBy ApplicabilityStatementOrderField) page.CursorKey {
switch orderBy {
case StateOfApplicabilityOrderFieldName:
return page.NewCursorKey(s.ID, s.StateOfApplicabilityID)
case StateOfApplicabilityOrderFieldCreatedAt:
case ApplicabilityStatementOrderFieldCreatedAt:
return page.NewCursorKey(s.ID, s.CreatedAt)
}
panic(fmt.Sprintf("unsupported order by: %s", orderBy))
}
func (s *StateOfApplicabilityControl) AuthorizationAttributes(ctx context.Context, conn pg.Conn) (map[string]string, error) {
q := `SELECT organization_id FROM states_of_applicability_controls WHERE id = $1 LIMIT 1;`
func (s *ApplicabilityStatement) AuthorizationAttributes(ctx context.Context, conn pg.Conn) (map[string]string, error) {
q := `SELECT organization_id FROM applicability_statements WHERE id = $1 LIMIT 1;`
var organizationID gid.GID
if err := conn.QueryRow(ctx, q, s.ID).Scan(&organizationID); err != nil {
if errors.Is(err, pgx.ErrNoRows) {
return nil, ErrResourceNotFound
}
return nil, fmt.Errorf("cannot query state of applicability control authorization attributes: %w", err)
return nil, fmt.Errorf("cannot query applicability statement authorization attributes: %w", err)
}
return map[string]string{"organization_id": organizationID.String()}, nil
}
func (sac *StateOfApplicabilityControl) LoadByStateOfApplicabilityIDAndControlID(
func (sac *ApplicabilityStatement) LoadByID(
ctx context.Context,
conn pg.Conn,
scope Scoper,
id gid.GID,
) error {
q := `
SELECT
id,
state_of_applicability_id,
control_id,
organization_id,
snapshot_id,
applicability,
justification,
created_at,
updated_at
FROM
applicability_statements
WHERE
%s
AND id = @id
LIMIT 1;
`
q = fmt.Sprintf(q, scope.SQLFragment())
args := pgx.StrictNamedArgs{"id": id}
maps.Copy(args, scope.SQLArguments())
rows, err := conn.Query(ctx, q, args)
if err != nil {
return fmt.Errorf("cannot query applicability_statements: %w", err)
}
applicabilityStatement, err := pgx.CollectExactlyOneRow(rows, pgx.RowToStructByName[ApplicabilityStatement])
if err != nil {
if errors.Is(err, pgx.ErrNoRows) {
return ErrResourceNotFound
}
return fmt.Errorf("cannot collect applicability statement: %w", err)
}
*sac = applicabilityStatement
return nil
}
func (sac *ApplicabilityStatement) LoadByStateOfApplicabilityIDAndControlID(
ctx context.Context,
conn pg.Conn,
scope Scoper,
@@ -114,7 +141,7 @@ SELECT
soac.created_at,
soac.updated_at
FROM
states_of_applicability_controls soac
applicability_statements soac
INNER JOIN
current_soa ON soac.state_of_applicability_id = current_soa.id
WHERE
@@ -131,29 +158,29 @@ LIMIT 1;
rows, err := conn.Query(ctx, q, args)
if err != nil {
return fmt.Errorf("cannot query states_of_applicability_controls: %w", err)
return fmt.Errorf("cannot query applicability_statements: %w", err)
}
control, err := pgx.CollectExactlyOneRow(rows, pgx.RowToStructByName[StateOfApplicabilityControl])
control, err := pgx.CollectExactlyOneRow(rows, pgx.RowToStructByName[ApplicabilityStatement])
if err != nil {
if errors.Is(err, pgx.ErrNoRows) {
return ErrResourceNotFound
}
return fmt.Errorf("cannot collect state of applicability control: %w", err)
return fmt.Errorf("cannot collect applicability statement: %w", err)
}
*sac = control
return nil
}
func (sac *StateOfApplicabilityControl) Insert(
func (sac *ApplicabilityStatement) Insert(
ctx context.Context,
conn pg.Conn,
scope Scoper,
) error {
q := `
INSERT INTO
states_of_applicability_controls (
applicability_statements (
id,
state_of_applicability_id,
control_id,
@@ -201,19 +228,19 @@ VALUES (
}
}
return fmt.Errorf("cannot insert state_of_applicability_control: %w", err)
return fmt.Errorf("cannot insert applicability_statement: %w", err)
}
return nil
}
func (sac *StateOfApplicabilityControl) Update(
func (sac *ApplicabilityStatement) Update(
ctx context.Context,
conn pg.Conn,
scope Scoper,
) error {
q := `
UPDATE states_of_applicability_controls
UPDATE applicability_statements
SET
applicability = @applicability,
justification = @justification,
@@ -236,70 +263,50 @@ WHERE
_, err := conn.Exec(ctx, q, args)
if err != nil {
return fmt.Errorf("cannot update state_of_applicability_control: %w", err)
return fmt.Errorf("cannot update applicability_statement: %w", err)
}
return nil
}
func (sac *StateOfApplicabilityControl) Upsert(
func (sac *ApplicabilityStatement) UpdateByID(
ctx context.Context,
conn pg.Conn,
scope Scoper,
) error {
q := `
INSERT INTO
states_of_applicability_controls (
id,
state_of_applicability_id,
control_id,
organization_id,
tenant_id,
snapshot_id,
applicability,
justification,
created_at,
updated_at
)
VALUES (
@id,
@state_of_applicability_id,
@control_id,
@organization_id,
@tenant_id,
@snapshot_id,
@applicability,
@justification,
@created_at,
@updated_at
)
ON CONFLICT (state_of_applicability_id, control_id) DO UPDATE SET
applicability = EXCLUDED.applicability,
justification = EXCLUDED.justification,
updated_at = EXCLUDED.updated_at
UPDATE applicability_statements
SET
applicability = @applicability,
justification = @justification,
updated_at = @updated_at
WHERE
%s
AND id = @id
`
q = fmt.Sprintf(q, scope.SQLFragment())
args := pgx.StrictNamedArgs{
"id": sac.ID,
"state_of_applicability_id": sac.StateOfApplicabilityID,
"control_id": sac.ControlID,
"organization_id": sac.OrganizationID,
"tenant_id": scope.GetTenantID(),
"snapshot_id": sac.SnapshotID,
"applicability": sac.Applicability,
"justification": sac.Justification,
"created_at": sac.CreatedAt,
"updated_at": sac.UpdatedAt,
"id": sac.ID,
"applicability": sac.Applicability,
"justification": sac.Justification,
"updated_at": sac.UpdatedAt,
}
_, err := conn.Exec(ctx, q, args)
maps.Copy(args, scope.SQLArguments())
result, err := conn.Exec(ctx, q, args)
if err != nil {
return fmt.Errorf("cannot upsert state_of_applicability_control: %w", err)
return fmt.Errorf("cannot update applicability_statement: %w", err)
}
if result.RowsAffected() == 0 {
return ErrResourceNotFound
}
return nil
}
func (sac *StateOfApplicabilityControl) Delete(
func (sac *ApplicabilityStatement) Delete(
ctx context.Context,
conn pg.Conn,
scope Scoper,
@@ -313,7 +320,7 @@ WITH current_soa AS (
AND id = @state_of_applicability_id
AND snapshot_id IS NULL
)
DELETE FROM states_of_applicability_controls
DELETE FROM applicability_statements
WHERE state_of_applicability_id IN (SELECT id FROM current_soa)
AND control_id = @control_id;
`
@@ -329,11 +336,41 @@ WHERE state_of_applicability_id IN (SELECT id FROM current_soa)
return err
}
func (sacs *StateOfApplicabilityControls) LoadByStateOfApplicabilityID(
func (sac *ApplicabilityStatement) DeleteByID(
ctx context.Context,
conn pg.Conn,
scope Scoper,
applicabilityStatementID gid.GID,
) error {
q := `
DELETE FROM applicability_statements
WHERE
%s
AND id = @id;
`
q = fmt.Sprintf(q, scope.SQLFragment())
args := pgx.StrictNamedArgs{"id": applicabilityStatementID}
maps.Copy(args, scope.SQLArguments())
result, err := conn.Exec(ctx, q, args)
if err != nil {
return fmt.Errorf("cannot delete applicability statement: %w", err)
}
if result.RowsAffected() == 0 {
return ErrResourceNotFound
}
return nil
}
func (sacs *ApplicabilityStatements) LoadByStateOfApplicabilityID(
ctx context.Context,
conn pg.Conn,
scope Scoper,
stateOfApplicabilityID gid.GID,
cursor *page.Cursor[ApplicabilityStatementOrderField],
) error {
q := `
SELECT
@@ -347,39 +384,68 @@ SELECT
created_at,
updated_at
FROM
states_of_applicability_controls
applicability_statements
WHERE
%s
%[1]s
AND state_of_applicability_id = @state_of_applicability_id
ORDER BY created_at ASC
AND %[2]s
`
q = fmt.Sprintf(q, scope.SQLFragment())
q = fmt.Sprintf(q, scope.SQLFragment(), cursor.SQLFragment())
args := pgx.StrictNamedArgs{
args := pgx.NamedArgs{
"state_of_applicability_id": stateOfApplicabilityID,
}
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 states_of_applicability_controls: %w", err)
return fmt.Errorf("cannot query applicability_statements: %w", err)
}
controls, err := pgx.CollectRows(rows, pgx.RowToAddrOfStructByName[StateOfApplicabilityControl])
controls, err := pgx.CollectRows(rows, pgx.RowToAddrOfStructByName[ApplicabilityStatement])
if err != nil {
return fmt.Errorf("cannot collect states_of_applicability_controls: %w", err)
return fmt.Errorf("cannot collect applicability_statements: %w", err)
}
*sacs = controls
return nil
}
func (sacs *StateOfApplicabilityControls) LoadByControlID(
func (sacs *ApplicabilityStatements) CountByStateOfApplicabilityID(
ctx context.Context,
conn pg.Conn,
scope Scoper,
stateOfApplicabilityID gid.GID,
) (int, error) {
q := `
SELECT
COUNT(id)
FROM
applicability_statements
WHERE
%s
AND state_of_applicability_id = @state_of_applicability_id;
`
q = fmt.Sprintf(q, scope.SQLFragment())
args := pgx.NamedArgs{"state_of_applicability_id": stateOfApplicabilityID}
maps.Copy(args, scope.SQLArguments())
var count int
if err := conn.QueryRow(ctx, q, args).Scan(&count); err != nil {
return 0, fmt.Errorf("cannot count applicability_statements: %w", err)
}
return count, nil
}
func (sacs *ApplicabilityStatements) LoadByControlID(
ctx context.Context,
conn pg.Conn,
scope Scoper,
controlID gid.GID,
cursor *page.Cursor[StateOfApplicabilityOrderField],
cursor *page.Cursor[ApplicabilityStatementOrderField],
) error {
q := `
WITH soac_ctrl AS (
@@ -395,7 +461,7 @@ WITH soac_ctrl AS (
soac.updated_at,
soac.tenant_id
FROM
states_of_applicability_controls soac
applicability_statements soac
INNER JOIN
states_of_applicability soa ON soac.state_of_applicability_id = soa.id
WHERE
@@ -427,19 +493,19 @@ WHERE
rows, err := conn.Query(ctx, q, args)
if err != nil {
return fmt.Errorf("cannot query state_of_applicability_controls: %w", err)
return fmt.Errorf("cannot query applicability_statements: %w", err)
}
controls, err := pgx.CollectRows(rows, pgx.RowToAddrOfStructByName[StateOfApplicabilityControl])
controls, err := pgx.CollectRows(rows, pgx.RowToAddrOfStructByName[ApplicabilityStatement])
if err != nil {
return fmt.Errorf("cannot collect state_of_applicability_controls: %w", err)
return fmt.Errorf("cannot collect applicability_statements: %w", err)
}
*sacs = controls
return nil
}
func (sacs *StateOfApplicabilityControls) CountByControlID(
func (sacs *ApplicabilityStatements) CountByControlID(
ctx context.Context,
conn pg.Conn,
scope Scoper,
@@ -452,7 +518,7 @@ WITH soac_ctrl AS (
soac.organization_id,
soac.tenant_id
FROM
states_of_applicability_controls soac
applicability_statements soac
INNER JOIN
states_of_applicability soa ON soac.state_of_applicability_id = soa.id
WHERE
@@ -482,126 +548,3 @@ WHERE
return count, nil
}
func (acfs *AvailableStateOfApplicabilityControls) LoadAvailableByStateOfApplicabilityID(
ctx context.Context,
conn pg.Conn,
scope Scoper,
stateOfApplicabilityID gid.GID,
) error {
q := `
WITH soa_info AS (
SELECT
soa.organization_id,
soa.tenant_id AS soa_tenant_id
FROM states_of_applicability soa
WHERE soa.tenant_id = @tenant_id
AND soa.id = @state_of_applicability_id
),
filtered_controls AS (
SELECT
c.id AS control_id,
c.section_title,
c.name,
c.framework_id,
c.organization_id,
c.tenant_id,
c.best_practice
FROM controls c
WHERE
%s
),
all_controls AS (
SELECT
fc.control_id,
fc.section_title,
fc.name,
fc.framework_id,
fc.organization_id,
fc.tenant_id,
f.name AS framework_name,
fc.best_practice
FROM filtered_controls fc
INNER JOIN frameworks f ON fc.framework_id = f.id
CROSS JOIN soa_info si
WHERE fc.organization_id = si.organization_id
),
existing_links AS (
SELECT
soac.control_id,
soac.state_of_applicability_id,
soac.applicability,
soac.justification
FROM states_of_applicability_controls soac
CROSS JOIN soa_info si
WHERE soac.tenant_id = si.soa_tenant_id
AND soac.state_of_applicability_id = @state_of_applicability_id
),
regulatory_controls AS (
SELECT DISTINCT co.control_id
FROM controls_obligations co
INNER JOIN obligations o ON o.id = co.obligation_id
CROSS JOIN soa_info si
WHERE co.tenant_id = si.soa_tenant_id
AND o.tenant_id = si.soa_tenant_id
AND o.type = 'LEGAL'
),
contractual_controls AS (
SELECT DISTINCT co.control_id
FROM controls_obligations co
INNER JOIN obligations o ON o.id = co.obligation_id
CROSS JOIN soa_info si
WHERE co.tenant_id = si.soa_tenant_id
AND o.tenant_id = si.soa_tenant_id
AND o.type = 'CONTRACTUAL'
),
risk_controls AS (
SELECT DISTINCT cm.control_id
FROM controls_measures cm
INNER JOIN risks_measures rm ON rm.measure_id = cm.measure_id
CROSS JOIN soa_info si
WHERE cm.tenant_id = si.soa_tenant_id
AND rm.tenant_id = si.soa_tenant_id
)
SELECT
ac.control_id,
ac.section_title,
ac.name,
ac.framework_id,
ac.organization_id,
ac.framework_name,
el.state_of_applicability_id,
el.applicability,
el.justification,
ac.best_practice,
CASE WHEN reg.control_id IS NOT NULL THEN TRUE ELSE FALSE END AS regulatory,
CASE WHEN cont.control_id IS NOT NULL THEN TRUE ELSE FALSE END AS contractual,
CASE WHEN risk.control_id IS NOT NULL THEN TRUE ELSE FALSE END AS risk_assessment
FROM all_controls ac
LEFT JOIN existing_links el ON ac.control_id = el.control_id
LEFT JOIN regulatory_controls reg ON reg.control_id = ac.control_id
LEFT JOIN contractual_controls cont ON cont.control_id = ac.control_id
LEFT JOIN risk_controls risk ON risk.control_id = ac.control_id
ORDER BY ac.framework_name, ac.section_title, ac.name
`
q = fmt.Sprintf(q, scope.SQLFragment())
args := pgx.StrictNamedArgs{
"state_of_applicability_id": stateOfApplicabilityID,
}
maps.Copy(args, scope.SQLArguments())
rows, err := conn.Query(ctx, q, args)
if err != nil {
return fmt.Errorf("cannot query available controls: %w", err)
}
controls, err := pgx.CollectRows(rows, pgx.RowToAddrOfStructByName[AvailableStateOfApplicabilityControl])
if err != nil {
return fmt.Errorf("cannot collect available controls: %w", err)
}
*acfs = controls
return nil
}

View File

@@ -0,0 +1,52 @@
// 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 (
"fmt"
)
type ApplicabilityStatementOrderField string
const (
ApplicabilityStatementOrderFieldCreatedAt ApplicabilityStatementOrderField = "CREATED_AT"
)
func (p ApplicabilityStatementOrderField) Column() string {
switch p {
case ApplicabilityStatementOrderFieldCreatedAt:
return "created_at"
}
panic("unknown ApplicabilityStatementOrderField")
}
func (p ApplicabilityStatementOrderField) String() string {
return string(p)
}
func (p ApplicabilityStatementOrderField) MarshalText() ([]byte, error) {
return []byte(p.String()), nil
}
func (p *ApplicabilityStatementOrderField) UnmarshalText(text []byte) error {
val := string(text)
switch val {
case string(ApplicabilityStatementOrderFieldCreatedAt):
*p = ApplicabilityStatementOrderField(val)
return nil
}
return fmt.Errorf("invalid ApplicabilityStatementOrderField value: %q", val)
}

View File

@@ -720,6 +720,54 @@ LIMIT 1;
return nil
}
func (c *Controls) LoadByIDs(
ctx context.Context,
conn pg.Conn,
scope Scoper,
controlIDs []gid.GID,
) error {
if len(controlIDs) == 0 {
*c = Controls{}
return nil
}
q := `
SELECT
id,
section_title,
framework_id,
organization_id,
name,
description,
status,
exclusion_justification,
best_practice,
created_at,
updated_at
FROM
controls
WHERE
%s
AND id = ANY(@control_ids)
`
q = fmt.Sprintf(q, scope.SQLFragment())
args := pgx.StrictNamedArgs{"control_ids": controlIDs}
maps.Copy(args, scope.SQLArguments())
rows, err := conn.Query(ctx, q, args)
if err != nil {
return fmt.Errorf("cannot query controls: %w", err)
}
controls, err := pgx.CollectRows(rows, pgx.RowToAddrOfStructByName[Control])
if err != nil {
return fmt.Errorf("cannot collect controls: %w", err)
}
*c = controls
return nil
}
func (c Control) Insert(
ctx context.Context,
conn pg.Conn,
@@ -1098,7 +1146,7 @@ WITH ctrl AS (
FROM
controls c
INNER JOIN
states_of_applicability_controls soac ON c.id = soac.control_id
applicability_statements soac ON c.id = soac.control_id
WHERE
soac.state_of_applicability_id = @state_of_applicability_id
)
@@ -1124,74 +1172,3 @@ WHERE %s
return count, nil
}
func (c *Controls) LoadByStateOfApplicabilityID(
ctx context.Context,
conn pg.Conn,
scope Scoper,
stateOfApplicabilityID gid.GID,
cursor *page.Cursor[ControlOrderField],
filter *ControlFilter,
) error {
q := `
WITH ctrl AS (
SELECT
c.id,
c.section_title,
c.framework_id,
c.organization_id,
c.tenant_id,
c.name,
c.description,
c.status,
c.exclusion_justification,
c.best_practice,
c.created_at,
c.updated_at,
c.search_vector
FROM
controls c
INNER JOIN
states_of_applicability_controls soac ON c.id = soac.control_id
WHERE
soac.state_of_applicability_id = @state_of_applicability_id
)
SELECT
id,
section_title,
framework_id,
organization_id,
name,
description,
status,
exclusion_justification,
best_practice,
created_at,
updated_at
FROM
ctrl
WHERE %s
AND %s
AND %s
`
q = fmt.Sprintf(q, scope.SQLFragment(), filter.SQLFragment(), cursor.SQLFragment())
args := pgx.NamedArgs{"state_of_applicability_id": stateOfApplicabilityID}
maps.Copy(args, scope.SQLArguments())
maps.Copy(args, filter.SQLArguments())
maps.Copy(args, cursor.SQLArguments())
rows, err := conn.Query(ctx, q, args)
if err != nil {
return fmt.Errorf("cannot query controls: %w", err)
}
controls, err := pgx.CollectRows(rows, pgx.RowToAddrOfStructByName[Control])
if err != nil {
return fmt.Errorf("cannot collect controls: %w", err)
}
*c = controls
return nil
}

View File

@@ -167,19 +167,13 @@ WHERE
return nil
}
type ControlObligationType struct {
ControlID gid.GID `db:"control_id"`
Type ObligationType `db:"type"`
}
type ControlObligationTypes []*ControlObligationType
func (cots *ControlObligationTypes) LoadTypesByControlIDs(
func (cos *ControlObligations) CountByControlID(
ctx context.Context,
conn pg.Conn,
scope Scoper,
controlIDs []gid.GID,
) error {
controlID gid.GID,
filter *ControlObligationFilter,
) (int, error) {
q := `
WITH control_obls AS (
SELECT
@@ -191,31 +185,27 @@ WITH control_obls AS (
INNER JOIN
obligations o ON co.obligation_id = o.id
WHERE
co.control_id = ANY(@control_ids)
co.control_id = @control_id
)
SELECT DISTINCT
control_id,
type
SELECT
COUNT(*)
FROM
control_obls
WHERE
%s
WHERE %s
AND %s
`
q = fmt.Sprintf(q, scope.SQLFragment())
q = fmt.Sprintf(q, scope.SQLFragment(), filter.SQLFragment())
args := pgx.NamedArgs{"control_ids": controlIDs}
args := pgx.StrictNamedArgs{"control_id": controlID}
maps.Copy(args, scope.SQLArguments())
maps.Copy(args, filter.SQLArguments())
rows, err := conn.Query(ctx, q, args)
if err != nil {
return fmt.Errorf("cannot query control obligations: %w", err)
row := conn.QueryRow(ctx, q, args)
var count int
if err := row.Scan(&count); err != nil {
return 0, fmt.Errorf("cannot count control obligations: %w", err)
}
controlObligationTypes, err := pgx.CollectRows(rows, pgx.RowToAddrOfStructByName[ControlObligationType])
if err != nil {
return fmt.Errorf("cannot collect control obligations: %w", err)
}
*cots = controlObligationTypes
return nil
return count, nil
}

View File

@@ -0,0 +1,45 @@
// 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 (
"github.com/jackc/pgx/v5"
)
type ControlObligationFilter struct {
obligationType *ObligationType
}
func NewControlObligationFilter(obligationType *ObligationType) *ControlObligationFilter {
return &ControlObligationFilter{
obligationType: obligationType,
}
}
func (f *ControlObligationFilter) SQLArguments() pgx.NamedArgs {
args := pgx.NamedArgs{
"filter_obligation_type": nil,
}
if f.obligationType != nil {
args["filter_obligation_type"] = *f.obligationType
}
return args
}
func (f *ControlObligationFilter) SQLFragment() string {
return "(@filter_obligation_type::obligation_type IS NULL OR type = @filter_obligation_type)"
}

View File

@@ -73,7 +73,7 @@ const (
TransferImpactAssessmentEntityType uint16 = 47
RightsRequestEntityType uint16 = 48
StateOfApplicabilityEntityType uint16 = 49
StateOfApplicabilityControlEntityType uint16 = 50
ApplicabilityStatementEntityType uint16 = 50
MembershipProfileEntityType uint16 = 51
SCIMConfigurationEntityType uint16 = 52
SCIMEventEntityType uint16 = 53
@@ -180,8 +180,8 @@ func NewEntityFromID(id gid.GID) (any, bool) {
return &RightsRequest{ID: id}, true
case StateOfApplicabilityEntityType:
return &StateOfApplicability{ID: id}, true
case StateOfApplicabilityControlEntityType:
return &StateOfApplicabilityControl{ID: id}, true
case ApplicabilityStatementEntityType:
return &ApplicabilityStatement{ID: id}, true
case MembershipProfileEntityType:
return &MembershipProfile{ID: id}, true
case SCIMConfigurationEntityType:

View File

@@ -241,6 +241,53 @@ LIMIT 1;
return nil
}
func (f *Frameworks) LoadByIDs(
ctx context.Context,
conn pg.Conn,
scope Scoper,
frameworkIDs []gid.GID,
) error {
if len(frameworkIDs) == 0 {
*f = Frameworks{}
return nil
}
q := `
SELECT
id,
organization_id,
reference_id,
name,
description,
light_logo_file_id,
dark_logo_file_id,
created_at,
updated_at
FROM
frameworks
WHERE
%s
AND id = ANY(@framework_ids)
`
q = fmt.Sprintf(q, scope.SQLFragment())
args := pgx.StrictNamedArgs{"framework_ids": frameworkIDs}
maps.Copy(args, scope.SQLArguments())
rows, err := conn.Query(ctx, q, args)
if err != nil {
return fmt.Errorf("cannot query frameworks: %w", err)
}
frameworks, err := pgx.CollectRows(rows, pgx.RowToAddrOfStructByName[Framework])
if err != nil {
return fmt.Errorf("cannot collect frameworks: %w", err)
}
*f = frameworks
return nil
}
func (f Framework) Insert(
ctx context.Context,
conn pg.Conn,

View File

@@ -109,20 +109,24 @@ WHERE NOT EXISTS (
SELECT 1
FROM states_of_applicability soa
WHERE soa.name = f.name
AND soa.tenant_id = f.tenant_id
AND soa.snapshot_id IS NULL
)
AND EXISTS (
SELECT 1 FROM peoples WHERE tenant_id = f.tenant_id
)
-- We only use exclude with ISO 27001 FIXME
AND EXISTS (
SELECT 1
FROM controls c
WHERE c.framework_id = f.id
AND c.tenant_id = f.tenant_id
AND (
c.status = 'EXCLUDED'
OR (c.exclusion_justification IS NOT NULL AND c.exclusion_justification != '')
)
);
)
AND organization_id = 'e5IaD7ibAAEAAAAAAZZ9aR_Oq_Npymhg';
INSERT INTO states_of_applicability_controls (
id,
@@ -151,11 +155,23 @@ SELECT
NOW() as created_at,
NOW() as updated_at
FROM frameworks f
JOIN states_of_applicability soa ON soa.name = f.name AND soa.snapshot_id IS NULL
JOIN states_of_applicability soa ON soa.name = f.name AND soa.snapshot_id IS NULL AND soa.tenant_id = f.tenant_id
JOIN controls c ON c.framework_id = f.id
WHERE NOT EXISTS (
SELECT 1
FROM states_of_applicability_controls soac
WHERE soac.state_of_applicability_id = soa.id
AND soac.control_id = c.id
);
)
AND f.id = 'e5IaD7ibAAEAAQAAAZsNt6Js2dJrgpJG'
AND f.organization_id = 'e5IaD7ibAAEAAAAAAZZ9aR_Oq_Npymhg';
WITH todelete AS (
SELECT soac.id
FROM states_of_applicability soa
JOIN states_of_applicability_controls soac ON soac.state_of_applicability_id = soa.id
WHERE soa.tenant_id != soac.tenant_id
)
DELETE FROM states_of_applicability_controls WHERE id IN (SELECT id FROM todelete)

View File

@@ -0,0 +1,11 @@
ALTER TABLE states_of_applicability_controls RENAME TO applicability_statements;
ALTER TABLE applicability_statements
RENAME CONSTRAINT states_of_applicability_controls_organization_id_fkey
TO applicability_statements_organization_id_fkey;
ALTER TABLE applicability_statements
RENAME CONSTRAINT states_of_applicability_controls_snapshot_id_fkey
TO applicability_statements_snapshot_id_fkey;
ALTER INDEX states_of_applicability_controls_pkey RENAME TO applicability_statements_pkey;

View File

@@ -414,7 +414,7 @@ snapshot_soa AS (
FROM states_of_applicability
WHERE snapshot_id = @snapshot_id
)
INSERT INTO states_of_applicability_controls (
INSERT INTO applicability_statements (
id,
state_of_applicability_id,
control_id,
@@ -427,7 +427,7 @@ INSERT INTO states_of_applicability_controls (
updated_at
)
SELECT
generate_gid(decode_base64_unpadded(@tenant_id), @state_of_applicability_control_entity_type),
generate_gid(decode_base64_unpadded(@tenant_id), @applicability_statement_entity_type),
snapshot_soa.id,
soac.control_id,
soac.organization_id,
@@ -437,7 +437,7 @@ SELECT
soac.justification,
soac.created_at,
soac.updated_at
FROM states_of_applicability_controls soac
FROM applicability_statements soac
INNER JOIN source_soa
ON soac.state_of_applicability_id = source_soa.id
INNER JOIN snapshot_soa
@@ -448,10 +448,10 @@ WHERE soac.snapshot_id IS NULL
query = fmt.Sprintf(query, scope.SQLFragment())
args := pgx.StrictNamedArgs{
"tenant_id": scope.GetTenantID(),
"snapshot_id": snapshotID,
"organization_id": organizationID,
"state_of_applicability_control_entity_type": StateOfApplicabilityControlEntityType,
"tenant_id": scope.GetTenantID(),
"snapshot_id": snapshotID,
"organization_id": organizationID,
"applicability_statement_entity_type": ApplicabilityStatementEntityType,
}
maps.Copy(args, scope.SQLArguments())