Add risk publish to document system
Replace the old snapshot-based system for risks with the publish document system, mirroring the prior vendor / processing activity / DPIA / TIA migration. Includes the GraphQL mutation, MCP tool, CLI command, n8n operation, frontend publish dialog, e2e tests, and a prosemirror register template covering name, description, category, treatment, owner, inherent and residual scoring, and notes. The risk register lives as a generated DocumentTypeRegister document on the organization, reused across publishes (the major version bumps on every republish). Approvers can be passed in to create a draft pending approval; otherwise the version is published immediately. The frontend Risks page exposes a Publish button and a Document link button when the document exists, and pre-fills the previous default approvers. Risks was the last remaining snapshot type, so this commit also removes the entire snapshot system: drop snapshotId from the Risk GraphQL type and RiskFilter; remove RiskSnapshotter, Risks.Snapshot, InsertRiskSnapshots, and the SnapshotID/SourceID fields on Risk; delete Snapshot, ControlSnapshot, SnapshotsType, SnapshotOrderField, Snapshottable, the SnapshotService, the Snapshot console resolvers and GraphQL schema, the Snapshot MCP types and operations (list/get/take/listControlSnapshots), the snapshot CLI (prb snapshot), the snapshot frontend pages, routes, banner, LinkedSnapshotsCard, SnapshotGraph, snapshot helpers, and the snapshot n8n resource and control link/unlink snapshot operations. The snapshot_id columns remain in the database but are now filtered out with snapshot_id IS NULL. Add Get/Upsert/Clear GeneratedDocumentID methods on Risk backed by a new risks_document_id column on generated_documents, matching the ProcessingActivity/Finding/Vendor pattern. The migration command migrate-risk-snapshots-to-documents uses raw SQL queries instead of the Go snapshot types, since those are gone. Signed-off-by: Sacha Al Himdani <sacha@getprobo.com>
This commit is contained in:
@@ -73,8 +73,6 @@ func ResourceTypeName(entityType uint16) string {
|
||||
return "Obligation"
|
||||
case VendorServiceEntityType:
|
||||
return "VendorService"
|
||||
case SnapshotEntityType:
|
||||
return "Snapshot"
|
||||
case ProcessingActivityEntityType:
|
||||
return "ProcessingActivity"
|
||||
case TrustCenterReferenceEntityType:
|
||||
|
||||
@@ -970,77 +970,6 @@ WHERE %s
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Controls) LoadBySnapshotID(
|
||||
ctx context.Context,
|
||||
conn pg.Querier,
|
||||
scope Scoper,
|
||||
snapshotID 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.best_practice,
|
||||
c.not_implemented_justification,
|
||||
c.maturity_level,
|
||||
c.created_at,
|
||||
c.updated_at,
|
||||
c.search_vector
|
||||
FROM
|
||||
controls c
|
||||
INNER JOIN
|
||||
controls_snapshots cs ON c.id = cs.control_id
|
||||
WHERE
|
||||
cs.snapshot_id = @snapshot_id
|
||||
)
|
||||
SELECT
|
||||
id,
|
||||
section_title,
|
||||
framework_id,
|
||||
organization_id,
|
||||
name,
|
||||
description,
|
||||
best_practice,
|
||||
not_implemented_justification,
|
||||
maturity_level,
|
||||
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{"snapshot_id": snapshotID}
|
||||
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
|
||||
}
|
||||
|
||||
func (c *Controls) CountByStatementOfApplicabilityID(
|
||||
ctx context.Context,
|
||||
conn pg.Querier,
|
||||
|
||||
@@ -1,100 +0,0 @@
|
||||
// Copyright (c) 2025-2026 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 (
|
||||
"context"
|
||||
"fmt"
|
||||
"maps"
|
||||
"time"
|
||||
|
||||
"github.com/jackc/pgx/v5"
|
||||
"go.gearno.de/kit/pg"
|
||||
"go.probo.inc/probo/pkg/gid"
|
||||
)
|
||||
|
||||
type (
|
||||
ControlSnapshot struct {
|
||||
ControlID gid.GID `db:"control_id"`
|
||||
SnapshotID gid.GID `db:"snapshot_id"`
|
||||
OrganizationID gid.GID `db:"organization_id"`
|
||||
CreatedAt time.Time `db:"created_at"`
|
||||
}
|
||||
|
||||
ControlSnapshots []*ControlSnapshot
|
||||
)
|
||||
|
||||
func (cs ControlSnapshot) Upsert(
|
||||
ctx context.Context,
|
||||
conn pg.Querier,
|
||||
scope Scoper,
|
||||
) error {
|
||||
q := `
|
||||
INSERT INTO
|
||||
controls_snapshots (
|
||||
control_id,
|
||||
snapshot_id,
|
||||
organization_id,
|
||||
tenant_id,
|
||||
created_at
|
||||
)
|
||||
VALUES (
|
||||
@control_id,
|
||||
@snapshot_id,
|
||||
@organization_id,
|
||||
@tenant_id,
|
||||
@created_at
|
||||
)
|
||||
ON CONFLICT (control_id, snapshot_id) DO NOTHING;
|
||||
`
|
||||
|
||||
args := pgx.StrictNamedArgs{
|
||||
"control_id": cs.ControlID,
|
||||
"snapshot_id": cs.SnapshotID,
|
||||
"organization_id": cs.OrganizationID,
|
||||
"tenant_id": scope.GetTenantID(),
|
||||
"created_at": cs.CreatedAt,
|
||||
}
|
||||
_, err := conn.Exec(ctx, q, args)
|
||||
return err
|
||||
}
|
||||
|
||||
func (cs ControlSnapshot) Delete(
|
||||
ctx context.Context,
|
||||
conn pg.Tx,
|
||||
scope Scoper,
|
||||
controlID gid.GID,
|
||||
snapshotID gid.GID,
|
||||
) error {
|
||||
q := `
|
||||
DELETE
|
||||
FROM
|
||||
controls_snapshots
|
||||
WHERE
|
||||
%s
|
||||
AND control_id = @control_id
|
||||
AND snapshot_id = @snapshot_id;
|
||||
`
|
||||
|
||||
args := pgx.StrictNamedArgs{
|
||||
"control_id": controlID,
|
||||
"snapshot_id": snapshotID,
|
||||
}
|
||||
maps.Copy(args, scope.SQLArguments())
|
||||
q = fmt.Sprintf(q, scope.SQLFragment())
|
||||
|
||||
_, err := conn.Exec(ctx, q, args)
|
||||
return err
|
||||
}
|
||||
@@ -54,7 +54,7 @@ const (
|
||||
_ uint16 = 28 // NonconformityEntityType - removed
|
||||
ObligationEntityType uint16 = 29
|
||||
VendorServiceEntityType uint16 = 30
|
||||
SnapshotEntityType uint16 = 31
|
||||
_ uint16 = 31 // SnapshotEntityType - removed
|
||||
_ uint16 = 32 // ContinualImprovementEntityType - removed
|
||||
ProcessingActivityEntityType uint16 = 33
|
||||
ExportJobEntityType uint16 = 34
|
||||
@@ -176,8 +176,6 @@ func NewEntityFromID(id gid.GID) (any, bool) {
|
||||
return &Obligation{ID: id}, true
|
||||
case VendorServiceEntityType:
|
||||
return &VendorService{ID: id}, true
|
||||
case SnapshotEntityType:
|
||||
return &Snapshot{ID: id}, true
|
||||
case ProcessingActivityEntityType:
|
||||
return &ProcessingActivity{ID: id}, true
|
||||
case ExportJobEntityType:
|
||||
|
||||
90
pkg/coredata/migrations/20260429T150423Z.sql
Normal file
90
pkg/coredata/migrations/20260429T150423Z.sql
Normal file
@@ -0,0 +1,90 @@
|
||||
-- Copyright (c) 2026 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.
|
||||
|
||||
ALTER TABLE generated_documents
|
||||
ADD COLUMN risks_document_id TEXT REFERENCES documents(id) ON DELETE SET NULL;
|
||||
|
||||
-- Backfill controls_documents from the legacy controls_snapshots links.
|
||||
-- For every snapshot type whose register is now an org-level generated
|
||||
-- document, link each control that was attached to a snapshot to the
|
||||
-- corresponding generated document. Best effort: skips rows whose target
|
||||
-- document hasn't been created yet (the matching `cmd/migrate-*` data
|
||||
-- migration must have already run). ON CONFLICT keeps the migration
|
||||
-- idempotent and tolerant of pre-existing mappings.
|
||||
INSERT INTO controls_documents (control_id, document_id, organization_id, tenant_id, created_at)
|
||||
SELECT DISTINCT
|
||||
cs.control_id,
|
||||
CASE s.type
|
||||
WHEN 'RISKS' THEN gd.risks_document_id
|
||||
WHEN 'VENDORS' THEN gd.vendors_document_id
|
||||
WHEN 'ASSETS' THEN gd.asset_list_document_id
|
||||
WHEN 'DATA' THEN gd.data_document_id
|
||||
WHEN 'FINDINGS' THEN gd.findings_document_id
|
||||
WHEN 'OBLIGATIONS' THEN gd.obligations_document_id
|
||||
WHEN 'PROCESSING_ACTIVITIES' THEN gd.processing_activities_document_id
|
||||
END AS document_id,
|
||||
s.organization_id,
|
||||
s.tenant_id,
|
||||
NOW()
|
||||
FROM controls_snapshots cs
|
||||
INNER JOIN snapshots s ON s.id = cs.snapshot_id
|
||||
LEFT JOIN generated_documents gd ON gd.organization_id = s.organization_id
|
||||
WHERE s.type IN (
|
||||
'RISKS',
|
||||
'VENDORS',
|
||||
'ASSETS',
|
||||
'DATA',
|
||||
'FINDINGS',
|
||||
'OBLIGATIONS',
|
||||
'PROCESSING_ACTIVITIES'
|
||||
)
|
||||
AND CASE s.type
|
||||
WHEN 'RISKS' THEN gd.risks_document_id
|
||||
WHEN 'VENDORS' THEN gd.vendors_document_id
|
||||
WHEN 'ASSETS' THEN gd.asset_list_document_id
|
||||
WHEN 'DATA' THEN gd.data_document_id
|
||||
WHEN 'FINDINGS' THEN gd.findings_document_id
|
||||
WHEN 'OBLIGATIONS' THEN gd.obligations_document_id
|
||||
WHEN 'PROCESSING_ACTIVITIES' THEN gd.processing_activities_document_id
|
||||
END IS NOT NULL
|
||||
ON CONFLICT DO NOTHING;
|
||||
|
||||
-- For STATEMENTS_OF_APPLICABILITY snapshots, the published document lives on
|
||||
-- the source SOA (the live row, snapshot_id IS NULL). Link controls that
|
||||
-- were attached to a SOA snapshot to that source SOA's document.
|
||||
INSERT INTO controls_documents (control_id, document_id, organization_id, tenant_id, created_at)
|
||||
SELECT DISTINCT
|
||||
cs.control_id,
|
||||
live_soa.document_id,
|
||||
s.organization_id,
|
||||
s.tenant_id,
|
||||
NOW()
|
||||
FROM controls_snapshots cs
|
||||
INNER JOIN snapshots s ON s.id = cs.snapshot_id
|
||||
INNER JOIN statements_of_applicability snap_soa ON snap_soa.snapshot_id = s.id
|
||||
INNER JOIN statements_of_applicability live_soa
|
||||
ON live_soa.id = snap_soa.source_id
|
||||
AND live_soa.snapshot_id IS NULL
|
||||
WHERE s.type = 'STATEMENTS_OF_APPLICABILITY'
|
||||
AND live_soa.document_id IS NOT NULL
|
||||
ON CONFLICT DO NOTHING;
|
||||
|
||||
-- Drop the trailing " List" suffix from previously published register
|
||||
-- documents so the version title matches the new naming convention used by
|
||||
-- the publish flow. Restricted to REGISTER document types so unrelated
|
||||
-- documents that happen to share a title aren't touched.
|
||||
UPDATE document_versions SET title = 'Assets' WHERE title = 'Asset List' AND document_type = 'REGISTER';
|
||||
UPDATE document_versions SET title = 'Data' WHERE title = 'Data List' AND document_type = 'REGISTER';
|
||||
UPDATE document_versions SET title = 'Findings' WHERE title = 'Finding List' AND document_type = 'REGISTER';
|
||||
UPDATE document_versions SET title = 'Obligations' WHERE title = 'Obligation List' AND document_type = 'REGISTER';
|
||||
@@ -27,6 +27,113 @@ import (
|
||||
"go.probo.inc/probo/pkg/page"
|
||||
)
|
||||
|
||||
func (r Risk) GetGeneratedDocumentID(
|
||||
ctx context.Context,
|
||||
conn pg.Querier,
|
||||
organizationID gid.GID,
|
||||
) (*gid.GID, error) {
|
||||
var documentID *gid.GID
|
||||
|
||||
err := conn.QueryRow(
|
||||
ctx,
|
||||
`
|
||||
SELECT
|
||||
risks_document_id
|
||||
FROM
|
||||
generated_documents
|
||||
WHERE
|
||||
organization_id = @organization_id
|
||||
`,
|
||||
pgx.NamedArgs{"organization_id": organizationID},
|
||||
).Scan(&documentID)
|
||||
if errors.Is(err, pgx.ErrNoRows) {
|
||||
return nil, nil
|
||||
}
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot get risk list document ID: %w", err)
|
||||
}
|
||||
|
||||
return documentID, nil
|
||||
}
|
||||
|
||||
func (r Risk) UpsertGeneratedDocumentID(
|
||||
ctx context.Context,
|
||||
conn pg.Tx,
|
||||
organizationID gid.GID,
|
||||
tenantID gid.TenantID,
|
||||
documentID gid.GID,
|
||||
) error {
|
||||
now := time.Now()
|
||||
|
||||
_, err := conn.Exec(
|
||||
ctx,
|
||||
`
|
||||
INSERT INTO generated_documents (
|
||||
organization_id,
|
||||
tenant_id,
|
||||
risks_document_id,
|
||||
created_at,
|
||||
updated_at
|
||||
) VALUES (
|
||||
@organization_id,
|
||||
@tenant_id,
|
||||
@risks_document_id,
|
||||
@created_at,
|
||||
@updated_at
|
||||
)
|
||||
ON CONFLICT (organization_id) DO UPDATE
|
||||
SET
|
||||
risks_document_id = @risks_document_id,
|
||||
updated_at = @updated_at
|
||||
`,
|
||||
pgx.NamedArgs{
|
||||
"organization_id": organizationID,
|
||||
"tenant_id": tenantID,
|
||||
"risks_document_id": documentID,
|
||||
"created_at": now,
|
||||
"updated_at": now,
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot upsert risk list document ID: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r Risk) ClearGeneratedDocumentID(
|
||||
ctx context.Context,
|
||||
conn pg.Tx,
|
||||
documentIDs []gid.GID,
|
||||
) error {
|
||||
ids := make([]string, len(documentIDs))
|
||||
for i, id := range documentIDs {
|
||||
ids[i] = id.String()
|
||||
}
|
||||
|
||||
_, err := conn.Exec(
|
||||
ctx,
|
||||
`
|
||||
UPDATE
|
||||
generated_documents
|
||||
SET
|
||||
risks_document_id = NULL,
|
||||
updated_at = @now
|
||||
WHERE
|
||||
risks_document_id = ANY(@ids)
|
||||
`,
|
||||
pgx.NamedArgs{
|
||||
"ids": ids,
|
||||
"now": time.Now(),
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot clear risk list document references: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
type (
|
||||
Risk struct {
|
||||
ID gid.GID `db:"id"`
|
||||
@@ -43,8 +150,6 @@ type (
|
||||
ResidualLikelihood int `db:"residual_likelihood"`
|
||||
ResidualImpact int `db:"residual_impact"`
|
||||
ResidualRiskScore int `db:"residual_risk_score"`
|
||||
SnapshotID *gid.GID `db:"snapshot_id"`
|
||||
SourceID *gid.GID `db:"source_id"`
|
||||
CreatedAt time.Time `db:"created_at"`
|
||||
UpdatedAt time.Time `db:"updated_at"`
|
||||
|
||||
@@ -53,10 +158,6 @@ type (
|
||||
}
|
||||
|
||||
Risks []*Risk
|
||||
|
||||
RiskSnapshotter interface {
|
||||
InsertRiskSnapshots(ctx context.Context, conn pg.Tx, scope Scoper, organizationID, snapshotID gid.GID) error
|
||||
}
|
||||
)
|
||||
|
||||
func (r *Risk) CursorKey(orderBy RiskOrderField) page.CursorKey {
|
||||
@@ -106,14 +207,14 @@ WITH rsks AS (
|
||||
SELECT
|
||||
r.id,
|
||||
r.tenant_id,
|
||||
r.search_vector,
|
||||
r.snapshot_id
|
||||
r.search_vector
|
||||
FROM
|
||||
risks r
|
||||
INNER JOIN
|
||||
risks_measures rm ON r.id = rm.risk_id
|
||||
WHERE
|
||||
rm.measure_id = @measure_id
|
||||
AND r.snapshot_id IS NULL
|
||||
)
|
||||
SELECT
|
||||
COUNT(id)
|
||||
@@ -165,8 +266,6 @@ WITH rsks AS (
|
||||
r.residual_likelihood,
|
||||
r.residual_impact,
|
||||
r.residual_risk_score,
|
||||
r.snapshot_id,
|
||||
r.source_id,
|
||||
r.search_vector,
|
||||
r.created_at,
|
||||
r.updated_at
|
||||
@@ -178,6 +277,7 @@ WITH rsks AS (
|
||||
iam_membership_profiles p ON r.owner_profile_id = p.id
|
||||
WHERE
|
||||
rm.measure_id = @measure_id
|
||||
AND r.snapshot_id IS NULL
|
||||
)
|
||||
SELECT
|
||||
id,
|
||||
@@ -195,8 +295,6 @@ SELECT
|
||||
residual_likelihood,
|
||||
residual_impact,
|
||||
residual_risk_score,
|
||||
snapshot_id,
|
||||
source_id,
|
||||
created_at,
|
||||
updated_at
|
||||
FROM
|
||||
@@ -240,6 +338,7 @@ SELECT
|
||||
FROM risks
|
||||
WHERE %s
|
||||
AND organization_id = @organization_id
|
||||
AND snapshot_id IS NULL
|
||||
AND %s
|
||||
`
|
||||
q = fmt.Sprintf(q, scope.SQLFragment(), filter.SQLFragment())
|
||||
@@ -285,8 +384,6 @@ WITH rsks AS (
|
||||
r.residual_impact,
|
||||
r.residual_risk_score,
|
||||
r.category,
|
||||
r.snapshot_id,
|
||||
r.source_id,
|
||||
r.search_vector,
|
||||
r.created_at,
|
||||
r.updated_at
|
||||
@@ -296,6 +393,7 @@ WITH rsks AS (
|
||||
iam_membership_profiles p ON r.owner_profile_id = p.id
|
||||
WHERE
|
||||
r.organization_id = @organization_id
|
||||
AND r.snapshot_id IS NULL
|
||||
)
|
||||
SELECT
|
||||
id,
|
||||
@@ -313,8 +411,6 @@ SELECT
|
||||
residual_impact,
|
||||
residual_risk_score,
|
||||
category,
|
||||
snapshot_id,
|
||||
source_id,
|
||||
created_at,
|
||||
updated_at
|
||||
FROM
|
||||
@@ -345,6 +441,58 @@ WHERE %s
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *Risks) LoadAllByOrganizationID(
|
||||
ctx context.Context,
|
||||
conn pg.Querier,
|
||||
scope Scoper,
|
||||
organizationID gid.GID,
|
||||
) error {
|
||||
q := `
|
||||
SELECT
|
||||
r.id,
|
||||
r.organization_id,
|
||||
r.name,
|
||||
r.description,
|
||||
r.category,
|
||||
r.owner_profile_id,
|
||||
NULL as owner_full_name,
|
||||
r.treatment,
|
||||
r.note,
|
||||
r.inherent_likelihood,
|
||||
r.inherent_impact,
|
||||
r.inherent_risk_score,
|
||||
r.residual_likelihood,
|
||||
r.residual_impact,
|
||||
r.residual_risk_score,
|
||||
r.created_at,
|
||||
r.updated_at
|
||||
FROM
|
||||
risks r
|
||||
WHERE %s
|
||||
AND r.organization_id = @organization_id
|
||||
AND r.snapshot_id IS NULL
|
||||
ORDER BY r.name ASC, r.id ASC
|
||||
`
|
||||
q = fmt.Sprintf(q, scope.SQLFragment())
|
||||
|
||||
args := pgx.StrictNamedArgs{"organization_id": organizationID}
|
||||
maps.Copy(args, scope.SQLArguments())
|
||||
|
||||
rows, err := conn.Query(ctx, q, args)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot query risks: %w", err)
|
||||
}
|
||||
|
||||
risks, err := pgx.CollectRows(rows, pgx.RowToAddrOfStructByName[Risk])
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot collect risks: %w", err)
|
||||
}
|
||||
|
||||
*r = risks
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *Risk) LoadByID(
|
||||
ctx context.Context,
|
||||
conn pg.Querier,
|
||||
@@ -368,8 +516,6 @@ SELECT
|
||||
residual_likelihood,
|
||||
residual_impact,
|
||||
residual_risk_score,
|
||||
snapshot_id,
|
||||
source_id,
|
||||
created_at,
|
||||
updated_at
|
||||
FROM risks
|
||||
@@ -424,8 +570,6 @@ SELECT
|
||||
residual_likelihood,
|
||||
residual_impact,
|
||||
residual_risk_score,
|
||||
snapshot_id,
|
||||
source_id,
|
||||
created_at,
|
||||
updated_at
|
||||
FROM risks
|
||||
@@ -567,14 +711,14 @@ WITH rsks AS (
|
||||
SELECT
|
||||
r.id,
|
||||
r.tenant_id,
|
||||
r.search_vector,
|
||||
r.snapshot_id
|
||||
r.search_vector
|
||||
FROM
|
||||
risks r
|
||||
INNER JOIN
|
||||
risks_documents rd ON r.id = rd.risk_id
|
||||
WHERE
|
||||
rd.document_id = @document_id
|
||||
AND r.snapshot_id IS NULL
|
||||
)
|
||||
SELECT
|
||||
COUNT(id)
|
||||
@@ -598,78 +742,3 @@ WHERE %s
|
||||
|
||||
return count, nil
|
||||
}
|
||||
|
||||
func (r Risks) Snapshot(ctx context.Context, conn pg.Tx, scope Scoper, organizationID, snapshotID gid.GID) error {
|
||||
if err := r.InsertRiskSnapshots(ctx, conn, scope, organizationID, snapshotID); err != nil {
|
||||
return fmt.Errorf("cannot create risk snapshots: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r Risks) InsertRiskSnapshots(
|
||||
ctx context.Context,
|
||||
conn pg.Tx,
|
||||
scope Scoper,
|
||||
organizationID gid.GID,
|
||||
snapshotID gid.GID,
|
||||
) error {
|
||||
query := `
|
||||
INSERT INTO risks (
|
||||
tenant_id,
|
||||
id,
|
||||
snapshot_id,
|
||||
source_id,
|
||||
organization_id,
|
||||
name,
|
||||
description,
|
||||
category,
|
||||
treatment,
|
||||
note,
|
||||
owner_profile_id,
|
||||
inherent_likelihood,
|
||||
inherent_impact,
|
||||
residual_likelihood,
|
||||
residual_impact,
|
||||
created_at,
|
||||
updated_at
|
||||
)
|
||||
SELECT
|
||||
@tenant_id,
|
||||
generate_gid(decode_base64_unpadded(@tenant_id), @risk_entity_type),
|
||||
@snapshot_id,
|
||||
r.id,
|
||||
r.organization_id,
|
||||
r.name,
|
||||
r.description,
|
||||
r.category,
|
||||
r.treatment,
|
||||
r.note,
|
||||
r.owner_profile_id,
|
||||
r.inherent_likelihood,
|
||||
r.inherent_impact,
|
||||
r.residual_likelihood,
|
||||
r.residual_impact,
|
||||
r.created_at,
|
||||
r.updated_at
|
||||
FROM risks r
|
||||
WHERE %s AND organization_id = @organization_id AND snapshot_id IS NULL
|
||||
`
|
||||
|
||||
query = fmt.Sprintf(query, scope.SQLFragment())
|
||||
|
||||
args := pgx.StrictNamedArgs{
|
||||
"tenant_id": scope.GetTenantID(),
|
||||
"snapshot_id": snapshotID,
|
||||
"organization_id": organizationID,
|
||||
"risk_entity_type": RiskEntityType,
|
||||
}
|
||||
maps.Copy(args, scope.SQLArguments())
|
||||
|
||||
_, err := conn.Exec(ctx, query, args)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot insert risk snapshots: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -16,40 +16,24 @@ package coredata
|
||||
|
||||
import (
|
||||
"github.com/jackc/pgx/v5"
|
||||
"go.probo.inc/probo/pkg/gid"
|
||||
)
|
||||
|
||||
type (
|
||||
RiskFilter struct {
|
||||
query *string
|
||||
snapshotID **gid.GID
|
||||
query *string
|
||||
}
|
||||
)
|
||||
|
||||
func NewRiskFilter(query *string, snapshotID **gid.GID) *RiskFilter {
|
||||
func NewRiskFilter(query *string) *RiskFilter {
|
||||
return &RiskFilter{
|
||||
query: query,
|
||||
snapshotID: snapshotID,
|
||||
query: query,
|
||||
}
|
||||
}
|
||||
|
||||
func (f *RiskFilter) SQLArguments() pgx.StrictNamedArgs {
|
||||
args := pgx.StrictNamedArgs{
|
||||
return pgx.StrictNamedArgs{
|
||||
"query": f.query,
|
||||
}
|
||||
|
||||
if f.snapshotID == nil {
|
||||
args["has_snapshot_filter"] = false
|
||||
args["filter_snapshot_id"] = nil
|
||||
} else if *f.snapshotID == nil {
|
||||
args["has_snapshot_filter"] = true
|
||||
args["filter_snapshot_id"] = nil
|
||||
} else {
|
||||
args["has_snapshot_filter"] = true
|
||||
args["filter_snapshot_id"] = **f.snapshotID
|
||||
}
|
||||
|
||||
return args
|
||||
}
|
||||
|
||||
func (f *RiskFilter) SQLFragment() string {
|
||||
@@ -63,14 +47,5 @@ func (f *RiskFilter) SQLFragment() string {
|
||||
)
|
||||
ELSE TRUE
|
||||
END
|
||||
AND
|
||||
CASE
|
||||
WHEN @has_snapshot_filter::boolean = false THEN TRUE
|
||||
WHEN @has_snapshot_filter::boolean = true AND @filter_snapshot_id::text IS NOT NULL THEN
|
||||
snapshot_id = @filter_snapshot_id::text
|
||||
WHEN @has_snapshot_filter::boolean = true AND @filter_snapshot_id::text IS NULL THEN
|
||||
snapshot_id IS NULL
|
||||
ELSE TRUE
|
||||
END
|
||||
)`
|
||||
}
|
||||
|
||||
@@ -1,316 +0,0 @@
|
||||
// Copyright (c) 2025-2026 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 (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"maps"
|
||||
"time"
|
||||
|
||||
"github.com/jackc/pgx/v5"
|
||||
"go.gearno.de/kit/pg"
|
||||
"go.probo.inc/probo/pkg/gid"
|
||||
"go.probo.inc/probo/pkg/page"
|
||||
)
|
||||
|
||||
type (
|
||||
Snapshot struct {
|
||||
ID gid.GID `db:"id"`
|
||||
OrganizationID gid.GID `db:"organization_id"`
|
||||
Name string `db:"name"`
|
||||
Description *string `db:"description"`
|
||||
Type SnapshotsType `db:"type"`
|
||||
CreatedAt time.Time `db:"created_at"`
|
||||
}
|
||||
|
||||
Snapshots []*Snapshot
|
||||
)
|
||||
|
||||
func (s *Snapshot) CursorKey(field SnapshotOrderField) page.CursorKey {
|
||||
switch field {
|
||||
case SnapshotOrderFieldCreatedAt:
|
||||
return page.NewCursorKey(s.ID, s.CreatedAt)
|
||||
case SnapshotOrderFieldName:
|
||||
return page.NewCursorKey(s.ID, s.Name)
|
||||
case SnapshotOrderFieldType:
|
||||
return page.NewCursorKey(s.ID, s.Type)
|
||||
}
|
||||
|
||||
panic(fmt.Sprintf("unsupported order by: %s", field))
|
||||
}
|
||||
|
||||
func (s *Snapshot) AuthorizationAttributes(ctx context.Context, conn pg.Querier) (map[string]string, error) {
|
||||
q := `SELECT organization_id FROM snapshots 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 snapshot authorization attributes: %w", err)
|
||||
}
|
||||
|
||||
return map[string]string{"organization_id": organizationID.String()}, nil
|
||||
}
|
||||
|
||||
func (s *Snapshot) LoadByID(
|
||||
ctx context.Context,
|
||||
conn pg.Querier,
|
||||
scope Scoper,
|
||||
snapshotID gid.GID,
|
||||
) error {
|
||||
q := `
|
||||
SELECT
|
||||
id,
|
||||
organization_id,
|
||||
name,
|
||||
description,
|
||||
type,
|
||||
created_at
|
||||
FROM
|
||||
snapshots
|
||||
WHERE
|
||||
%s
|
||||
AND id = @snapshot_id
|
||||
LIMIT 1;
|
||||
`
|
||||
|
||||
q = fmt.Sprintf(q, scope.SQLFragment())
|
||||
|
||||
args := pgx.StrictNamedArgs{"snapshot_id": snapshotID}
|
||||
maps.Copy(args, scope.SQLArguments())
|
||||
|
||||
rows, err := conn.Query(ctx, q, args)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot query snapshots: %w", err)
|
||||
}
|
||||
|
||||
snapshot, err := pgx.CollectExactlyOneRow(rows, pgx.RowToStructByName[Snapshot])
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot collect snapshot: %w", err)
|
||||
}
|
||||
|
||||
*s = snapshot
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Snapshots) CountByOrganizationID(
|
||||
ctx context.Context,
|
||||
conn pg.Querier,
|
||||
scope Scoper,
|
||||
organizationID gid.GID,
|
||||
filter *SnapshotFilter,
|
||||
) (int, error) {
|
||||
q := `
|
||||
SELECT
|
||||
COUNT(id)
|
||||
FROM
|
||||
snapshots
|
||||
WHERE
|
||||
%s
|
||||
AND organization_id = @organization_id
|
||||
AND type = 'RISKS'
|
||||
AND %s
|
||||
`
|
||||
|
||||
q = fmt.Sprintf(q, scope.SQLFragment(), filter.SQLFragment())
|
||||
|
||||
args := pgx.StrictNamedArgs{"organization_id": organizationID}
|
||||
maps.Copy(args, scope.SQLArguments())
|
||||
maps.Copy(args, filter.SQLArguments())
|
||||
|
||||
row := conn.QueryRow(ctx, q, args)
|
||||
|
||||
var count int
|
||||
if err := row.Scan(&count); err != nil {
|
||||
return 0, fmt.Errorf("cannot scan count: %w", err)
|
||||
}
|
||||
|
||||
return count, nil
|
||||
}
|
||||
|
||||
func (s *Snapshots) LoadByOrganizationID(
|
||||
ctx context.Context,
|
||||
conn pg.Querier,
|
||||
scope Scoper,
|
||||
organizationID gid.GID,
|
||||
cursor *page.Cursor[SnapshotOrderField],
|
||||
) error {
|
||||
q := `
|
||||
SELECT
|
||||
id,
|
||||
organization_id,
|
||||
name,
|
||||
description,
|
||||
type,
|
||||
created_at
|
||||
FROM
|
||||
snapshots
|
||||
WHERE
|
||||
%s
|
||||
AND organization_id = @organization_id
|
||||
AND type = 'RISKS'
|
||||
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 snapshots: %w", err)
|
||||
}
|
||||
|
||||
snapshots, err := pgx.CollectRows(rows, pgx.RowToAddrOfStructByName[Snapshot])
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot collect snapshots: %w", err)
|
||||
}
|
||||
|
||||
*s = snapshots
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Snapshot) Insert(
|
||||
ctx context.Context,
|
||||
conn pg.Tx,
|
||||
scope Scoper,
|
||||
) error {
|
||||
q := `
|
||||
INSERT INTO snapshots (
|
||||
id,
|
||||
tenant_id,
|
||||
organization_id,
|
||||
name,
|
||||
description,
|
||||
type,
|
||||
created_at
|
||||
) VALUES (
|
||||
@id,
|
||||
@tenant_id,
|
||||
@organization_id,
|
||||
@name,
|
||||
@description,
|
||||
@type,
|
||||
@created_at
|
||||
)
|
||||
`
|
||||
|
||||
args := pgx.StrictNamedArgs{
|
||||
"id": s.ID,
|
||||
"tenant_id": scope.GetTenantID(),
|
||||
"organization_id": s.OrganizationID,
|
||||
"name": s.Name,
|
||||
"description": s.Description,
|
||||
"type": s.Type,
|
||||
"created_at": s.CreatedAt,
|
||||
}
|
||||
|
||||
_, err := conn.Exec(ctx, q, args)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot insert snapshot: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Snapshot) Delete(
|
||||
ctx context.Context,
|
||||
conn pg.Tx,
|
||||
scope Scoper,
|
||||
) error {
|
||||
q := `
|
||||
DELETE FROM snapshots
|
||||
WHERE
|
||||
%s
|
||||
AND organization_id = @organization_id
|
||||
AND id = @id
|
||||
`
|
||||
|
||||
q = fmt.Sprintf(q, scope.SQLFragment())
|
||||
|
||||
args := pgx.StrictNamedArgs{"id": s.ID, "organization_id": s.OrganizationID}
|
||||
maps.Copy(args, scope.SQLArguments())
|
||||
|
||||
_, err := conn.Exec(ctx, q, args)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot delete snapshot: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Snapshots) LoadByControlID(
|
||||
ctx context.Context,
|
||||
conn pg.Querier,
|
||||
scope Scoper,
|
||||
controlID gid.GID,
|
||||
cursor *page.Cursor[SnapshotOrderField],
|
||||
) error {
|
||||
q := `
|
||||
WITH snapshots_by_control AS (
|
||||
SELECT
|
||||
s.id,
|
||||
s.tenant_id,
|
||||
s.organization_id,
|
||||
s.name,
|
||||
s.description,
|
||||
s.type,
|
||||
s.created_at
|
||||
FROM
|
||||
snapshots s
|
||||
INNER JOIN
|
||||
controls_snapshots cs ON s.id = cs.snapshot_id
|
||||
WHERE
|
||||
cs.control_id = @control_id
|
||||
)
|
||||
SELECT
|
||||
id,
|
||||
organization_id,
|
||||
name,
|
||||
description,
|
||||
type,
|
||||
created_at
|
||||
FROM
|
||||
snapshots_by_control
|
||||
WHERE %s
|
||||
AND %s
|
||||
`
|
||||
q = fmt.Sprintf(q, scope.SQLFragment(), cursor.SQLFragment())
|
||||
|
||||
args := pgx.StrictNamedArgs{"control_id": controlID}
|
||||
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 snapshots: %w", err)
|
||||
}
|
||||
|
||||
snapshots, err := pgx.CollectRows(rows, pgx.RowToAddrOfStructByName[Snapshot])
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot collect snapshots: %w", err)
|
||||
}
|
||||
|
||||
*s = snapshots
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -1,65 +0,0 @@
|
||||
// Copyright (c) 2025-2026 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 (
|
||||
"time"
|
||||
|
||||
"github.com/jackc/pgx/v5"
|
||||
)
|
||||
|
||||
type (
|
||||
SnapshotFilter struct {
|
||||
snapshotType *SnapshotsType
|
||||
beforeDate *time.Time
|
||||
}
|
||||
)
|
||||
|
||||
func NewSnapshotFilter(snapshotType *SnapshotsType) *SnapshotFilter {
|
||||
return &SnapshotFilter{
|
||||
snapshotType: snapshotType,
|
||||
}
|
||||
}
|
||||
|
||||
func (f *SnapshotFilter) WithBeforeDate(beforeDate *time.Time) *SnapshotFilter {
|
||||
f.beforeDate = beforeDate
|
||||
return f
|
||||
}
|
||||
|
||||
func (f *SnapshotFilter) SQLArguments() pgx.NamedArgs {
|
||||
args := pgx.NamedArgs{
|
||||
"filter_snapshot_type": f.snapshotType,
|
||||
"filter_before_date": f.beforeDate,
|
||||
}
|
||||
|
||||
return args
|
||||
}
|
||||
|
||||
func (f *SnapshotFilter) SQLFragment() string {
|
||||
return `
|
||||
(
|
||||
CASE
|
||||
WHEN @filter_snapshot_type::snapshots_type IS NOT NULL THEN
|
||||
type = @filter_snapshot_type::snapshots_type
|
||||
ELSE TRUE
|
||||
END
|
||||
AND
|
||||
CASE
|
||||
WHEN @filter_before_date::timestamptz IS NOT NULL THEN
|
||||
created_at <= @filter_before_date::timestamptz
|
||||
ELSE TRUE
|
||||
END
|
||||
)`
|
||||
}
|
||||
@@ -1,51 +0,0 @@
|
||||
// Copyright (c) 2025-2026 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 SnapshotOrderField string
|
||||
|
||||
const (
|
||||
SnapshotOrderFieldCreatedAt SnapshotOrderField = "CREATED_AT"
|
||||
SnapshotOrderFieldName SnapshotOrderField = "NAME"
|
||||
SnapshotOrderFieldType SnapshotOrderField = "TYPE"
|
||||
)
|
||||
|
||||
func (p SnapshotOrderField) Column() string {
|
||||
return string(p)
|
||||
}
|
||||
|
||||
func (p SnapshotOrderField) String() string {
|
||||
return string(p)
|
||||
}
|
||||
|
||||
func (p SnapshotOrderField) MarshalText() ([]byte, error) {
|
||||
return []byte(p.String()), nil
|
||||
}
|
||||
|
||||
func (p *SnapshotOrderField) UnmarshalText(text []byte) error {
|
||||
val := string(text)
|
||||
switch val {
|
||||
case string(SnapshotOrderFieldCreatedAt),
|
||||
string(SnapshotOrderFieldName),
|
||||
string(SnapshotOrderFieldType):
|
||||
*p = SnapshotOrderField(val)
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf("invalid SnapshotOrderField value: %q", val)
|
||||
}
|
||||
@@ -1,80 +0,0 @@
|
||||
// Copyright (c) 2025-2026 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"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
type (
|
||||
SnapshotsType string
|
||||
)
|
||||
|
||||
const (
|
||||
SnapshotsTypeRisks SnapshotsType = "RISKS"
|
||||
SnapshotsTypeAssets SnapshotsType = "ASSETS"
|
||||
SnapshotsTypeData SnapshotsType = "DATA"
|
||||
SnapshotsTypeFindings SnapshotsType = "FINDINGS"
|
||||
SnapshotsTypeObligations SnapshotsType = "OBLIGATIONS"
|
||||
SnapshotsTypeProcessingActivities SnapshotsType = "PROCESSING_ACTIVITIES"
|
||||
SnapshotsTypeStatementsOfApplicability SnapshotsType = "STATEMENTS_OF_APPLICABILITY"
|
||||
)
|
||||
|
||||
func SnapshotsTypes() []SnapshotsType {
|
||||
return []SnapshotsType{
|
||||
SnapshotsTypeRisks,
|
||||
}
|
||||
}
|
||||
|
||||
func (st SnapshotsType) String() string {
|
||||
return string(st)
|
||||
}
|
||||
|
||||
func (st *SnapshotsType) Scan(value any) error {
|
||||
var s string
|
||||
switch v := value.(type) {
|
||||
case string:
|
||||
s = v
|
||||
case []byte:
|
||||
s = string(v)
|
||||
default:
|
||||
return fmt.Errorf("unsupported type for SnapshotsType: %T", value)
|
||||
}
|
||||
|
||||
switch s {
|
||||
case SnapshotsTypeRisks.String():
|
||||
*st = SnapshotsTypeRisks
|
||||
case SnapshotsTypeAssets.String():
|
||||
*st = SnapshotsTypeAssets
|
||||
case SnapshotsTypeData.String():
|
||||
*st = SnapshotsTypeData
|
||||
case SnapshotsTypeFindings.String(), "NONCONFORMITIES", "CONTINUAL_IMPROVEMENTS":
|
||||
*st = SnapshotsTypeFindings
|
||||
case SnapshotsTypeObligations.String():
|
||||
*st = SnapshotsTypeObligations
|
||||
case SnapshotsTypeProcessingActivities.String():
|
||||
*st = SnapshotsTypeProcessingActivities
|
||||
case SnapshotsTypeStatementsOfApplicability.String():
|
||||
*st = SnapshotsTypeStatementsOfApplicability
|
||||
default:
|
||||
return fmt.Errorf("invalid SnapshotsType value: %q", s)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (st SnapshotsType) Value() (driver.Value, error) {
|
||||
return st.String(), nil
|
||||
}
|
||||
@@ -1,36 +0,0 @@
|
||||
// Copyright (c) 2025-2026 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 (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"go.gearno.de/kit/pg"
|
||||
"go.probo.inc/probo/pkg/gid"
|
||||
)
|
||||
|
||||
type Snapshottable interface {
|
||||
Snapshot(ctx context.Context, conn pg.Tx, scope Scoper, organizationID, snapshotID gid.GID) error
|
||||
}
|
||||
|
||||
func GetSnapshottable(snapshotType SnapshotsType) (Snapshottable, error) {
|
||||
switch snapshotType {
|
||||
case SnapshotsTypeRisks:
|
||||
return Risks{}, nil
|
||||
default:
|
||||
return nil, fmt.Errorf("unsupported snapshot type: %s", snapshotType)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user