SOA as document: replace export with publish workflow
Statements of Applicability are no longer exported as one-off PDFs. Instead, each SOA owns a persistent document that accumulates versions over time, following the same publish/approve lifecycle as authored documents. Publishing without approvers publishes immediately; publishing with approvers creates a draft pending approval via the existing quorum system. SOAs can also store default approvers that are pre-populated in the publish dialog. The SOA is removed from the snapshot system — applicability statements are now queried directly (snapshot_id IS NULL) rather than through snapshot copies. A standalone migration script (cmd/migrate-soa-snapshots-to-documents) converts existing SOA snapshots into documents with proper ProseMirror content, preserving version history and approval decisions. Signed-off-by: Sacha Al Himdani <sacha@getprobo.com>
This commit is contained in:
@@ -156,7 +156,6 @@ WITH current_soa AS (
|
||||
WHERE
|
||||
%s
|
||||
AND id = @statement_of_applicability_id
|
||||
AND snapshot_id IS NULL
|
||||
)
|
||||
SELECT
|
||||
soac.id,
|
||||
@@ -351,7 +350,6 @@ WITH current_soa AS (
|
||||
WHERE
|
||||
%s
|
||||
AND id = @statement_of_applicability_id
|
||||
AND snapshot_id IS NULL
|
||||
)
|
||||
DELETE FROM applicability_statements
|
||||
WHERE statement_of_applicability_id IN (SELECT id FROM current_soa)
|
||||
@@ -467,6 +465,57 @@ WHERE
|
||||
return nil
|
||||
}
|
||||
|
||||
func (sacs *ApplicabilityStatements) LoadAllByStatementOfApplicabilityID(
|
||||
ctx context.Context,
|
||||
conn pg.Querier,
|
||||
scope Scoper,
|
||||
statementOfApplicabilityID gid.GID,
|
||||
) error {
|
||||
q := `
|
||||
SELECT
|
||||
a.id,
|
||||
a.statement_of_applicability_id,
|
||||
a.control_id,
|
||||
a.organization_id,
|
||||
a.snapshot_id,
|
||||
a.applicability,
|
||||
a.justification,
|
||||
a.created_at,
|
||||
a.updated_at,
|
||||
f.name || ' - ' || c.section_title AS section_title
|
||||
FROM
|
||||
applicability_statements a
|
||||
INNER JOIN
|
||||
controls c ON c.id = a.control_id
|
||||
INNER JOIN
|
||||
frameworks f ON f.id = c.framework_id
|
||||
WHERE
|
||||
a.%s
|
||||
AND a.statement_of_applicability_id = @statement_of_applicability_id
|
||||
ORDER BY
|
||||
section_title ASC;
|
||||
`
|
||||
q = fmt.Sprintf(q, scope.SQLFragment())
|
||||
|
||||
args := pgx.StrictNamedArgs{
|
||||
"statement_of_applicability_id": statementOfApplicabilityID,
|
||||
}
|
||||
maps.Copy(args, scope.SQLArguments())
|
||||
|
||||
rows, err := conn.Query(ctx, q, args)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot query applicability_statements: %w", err)
|
||||
}
|
||||
|
||||
controls, err := pgx.CollectRows(rows, pgx.RowToAddrOfStructByName[ApplicabilityStatement])
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot collect applicability_statements: %w", err)
|
||||
}
|
||||
|
||||
*sacs = controls
|
||||
return nil
|
||||
}
|
||||
|
||||
func (sacs *ApplicabilityStatements) CountByStatementOfApplicabilityID(
|
||||
ctx context.Context,
|
||||
conn pg.Querier,
|
||||
@@ -484,7 +533,7 @@ WHERE
|
||||
`
|
||||
q = fmt.Sprintf(q, scope.SQLFragment())
|
||||
|
||||
args := pgx.NamedArgs{"statement_of_applicability_id": statementOfApplicabilityID}
|
||||
args := pgx.StrictNamedArgs{"statement_of_applicability_id": statementOfApplicabilityID}
|
||||
maps.Copy(args, scope.SQLArguments())
|
||||
|
||||
var count int
|
||||
|
||||
@@ -33,6 +33,11 @@ type (
|
||||
}
|
||||
|
||||
ControlObligations []*ControlObligation
|
||||
|
||||
ControlObligationType struct {
|
||||
ControlID gid.GID `db:"control_id"`
|
||||
ObligationType ObligationType `db:"obligation_type"`
|
||||
}
|
||||
)
|
||||
|
||||
func (co ControlObligation) Upsert(
|
||||
@@ -137,3 +142,48 @@ WHERE %s
|
||||
|
||||
return count, nil
|
||||
}
|
||||
|
||||
func LoadObligationTypesByControlIDs(
|
||||
ctx context.Context,
|
||||
conn pg.Querier,
|
||||
scope Scoper,
|
||||
controlIDs []gid.GID,
|
||||
) ([]ControlObligationType, error) {
|
||||
q := `
|
||||
WITH control_obls AS (
|
||||
SELECT DISTINCT
|
||||
co.control_id,
|
||||
o.type AS obligation_type,
|
||||
o.tenant_id
|
||||
FROM
|
||||
controls_obligations co
|
||||
INNER JOIN
|
||||
obligations o ON co.obligation_id = o.id
|
||||
WHERE
|
||||
co.control_id = ANY(@control_ids)
|
||||
)
|
||||
SELECT
|
||||
control_id,
|
||||
obligation_type
|
||||
FROM
|
||||
control_obls
|
||||
WHERE
|
||||
%s;
|
||||
`
|
||||
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 nil, fmt.Errorf("cannot load obligation types by control IDs: %w", err)
|
||||
}
|
||||
|
||||
result, err := pgx.CollectRows(rows, pgx.RowToStructByName[ControlObligationType])
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot collect control obligation types: %w", err)
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
@@ -35,6 +35,7 @@ type (
|
||||
CurrentPublishedMajor *int `db:"current_published_major"`
|
||||
CurrentPublishedMinor *int `db:"current_published_minor"`
|
||||
TrustCenterVisibility TrustCenterVisibility `db:"trust_center_visibility"`
|
||||
WriteMode DocumentWriteMode `db:"write_mode"`
|
||||
Status DocumentStatus `db:"status"`
|
||||
ArchivedAt *time.Time `db:"archived_at"`
|
||||
CreatedAt time.Time `db:"created_at"`
|
||||
@@ -102,6 +103,7 @@ SELECT
|
||||
documents.organization_id,
|
||||
documents.current_published_major,
|
||||
documents.current_published_minor,
|
||||
documents.write_mode,
|
||||
documents.trust_center_visibility,
|
||||
documents.status,
|
||||
documents.archived_at,
|
||||
@@ -161,6 +163,7 @@ SELECT
|
||||
documents.organization_id,
|
||||
documents.current_published_major,
|
||||
documents.current_published_minor,
|
||||
documents.write_mode,
|
||||
documents.trust_center_visibility,
|
||||
documents.status,
|
||||
documents.archived_at,
|
||||
@@ -221,6 +224,7 @@ SELECT
|
||||
documents.organization_id,
|
||||
documents.current_published_major,
|
||||
documents.current_published_minor,
|
||||
documents.write_mode,
|
||||
documents.trust_center_visibility,
|
||||
documents.status,
|
||||
documents.archived_at,
|
||||
@@ -311,6 +315,7 @@ base AS (
|
||||
documents.organization_id,
|
||||
documents.current_published_major,
|
||||
documents.current_published_minor,
|
||||
documents.write_mode,
|
||||
documents.trust_center_visibility,
|
||||
documents.status,
|
||||
documents.archived_at,
|
||||
@@ -370,6 +375,7 @@ SELECT
|
||||
documents.organization_id,
|
||||
documents.current_published_major,
|
||||
documents.current_published_minor,
|
||||
documents.write_mode,
|
||||
documents.trust_center_visibility,
|
||||
documents.status,
|
||||
documents.archived_at,
|
||||
@@ -443,6 +449,7 @@ base AS (
|
||||
documents.organization_id,
|
||||
documents.current_published_major,
|
||||
documents.current_published_minor,
|
||||
documents.write_mode,
|
||||
documents.trust_center_visibility,
|
||||
documents.status,
|
||||
documents.archived_at,
|
||||
@@ -497,6 +504,7 @@ INSERT INTO
|
||||
organization_id,
|
||||
current_published_major,
|
||||
current_published_minor,
|
||||
write_mode,
|
||||
trust_center_visibility,
|
||||
status,
|
||||
archived_at,
|
||||
@@ -509,6 +517,7 @@ VALUES (
|
||||
@organization_id,
|
||||
@current_published_major,
|
||||
@current_published_minor,
|
||||
@write_mode,
|
||||
@trust_center_visibility,
|
||||
@status,
|
||||
@archived_at,
|
||||
@@ -523,6 +532,7 @@ VALUES (
|
||||
"organization_id": p.OrganizationID,
|
||||
"current_published_major": p.CurrentPublishedMajor,
|
||||
"current_published_minor": p.CurrentPublishedMinor,
|
||||
"write_mode": p.WriteMode,
|
||||
"trust_center_visibility": p.TrustCenterVisibility,
|
||||
"status": p.Status,
|
||||
"archived_at": p.ArchivedAt,
|
||||
@@ -675,6 +685,7 @@ base AS (
|
||||
sd.current_published_major,
|
||||
sd.current_published_minor,
|
||||
sd.trust_center_visibility,
|
||||
sd.write_mode,
|
||||
sd.status,
|
||||
sd.archived_at,
|
||||
sd.created_at,
|
||||
@@ -774,6 +785,7 @@ base AS (
|
||||
sd.current_published_major,
|
||||
sd.current_published_minor,
|
||||
sd.trust_center_visibility,
|
||||
sd.write_mode,
|
||||
sd.status,
|
||||
sd.archived_at,
|
||||
sd.created_at,
|
||||
@@ -873,6 +885,7 @@ base AS (
|
||||
sd.current_published_major,
|
||||
sd.current_published_minor,
|
||||
sd.trust_center_visibility,
|
||||
sd.write_mode,
|
||||
sd.status,
|
||||
sd.archived_at,
|
||||
sd.created_at,
|
||||
|
||||
@@ -28,6 +28,7 @@ type (
|
||||
employeeFilterModes []EmployeeFilterMode
|
||||
documentTypes []DocumentType
|
||||
classifications []DocumentClassification
|
||||
writeModes []DocumentWriteMode
|
||||
status []DocumentStatus
|
||||
}
|
||||
)
|
||||
@@ -71,6 +72,11 @@ func (f *DocumentFilter) WithClassifications(classifications []DocumentClassific
|
||||
return f
|
||||
}
|
||||
|
||||
func (f *DocumentFilter) WithWriteModes(writeModes []DocumentWriteMode) *DocumentFilter {
|
||||
f.writeModes = writeModes
|
||||
return f
|
||||
}
|
||||
|
||||
func (f *DocumentFilter) WithStatus(status []DocumentStatus) *DocumentFilter {
|
||||
f.status = status
|
||||
return f
|
||||
@@ -101,6 +107,14 @@ func (f *DocumentFilter) SQLArguments() pgx.NamedArgs {
|
||||
}
|
||||
}
|
||||
|
||||
var writeModes []string
|
||||
if f.writeModes != nil {
|
||||
writeModes = make([]string, len(f.writeModes))
|
||||
for i, cs := range f.writeModes {
|
||||
writeModes[i] = cs.String()
|
||||
}
|
||||
}
|
||||
|
||||
var status []string
|
||||
if f.status != nil {
|
||||
status = make([]string, len(f.status))
|
||||
@@ -122,6 +136,7 @@ func (f *DocumentFilter) SQLArguments() pgx.NamedArgs {
|
||||
"employee_filter_modes": employeeFilterModes,
|
||||
"document_types": documentTypes,
|
||||
"classifications": classifications,
|
||||
"write_modes": writeModes,
|
||||
"document_status": status,
|
||||
}
|
||||
}
|
||||
@@ -209,6 +224,12 @@ func (f *DocumentFilter) SQLFragment() string {
|
||||
ELSE TRUE
|
||||
END
|
||||
AND
|
||||
CASE
|
||||
WHEN @write_modes::text[] IS NOT NULL THEN
|
||||
documents.write_mode::text = ANY(@write_modes::text[])
|
||||
ELSE TRUE
|
||||
END
|
||||
AND
|
||||
CASE
|
||||
WHEN @document_status::text[] IS NULL THEN TRUE
|
||||
ELSE status::text = ANY(@document_status::text[])
|
||||
|
||||
@@ -24,15 +24,16 @@ type (
|
||||
)
|
||||
|
||||
const (
|
||||
DocumentTypeOther DocumentType = "OTHER"
|
||||
DocumentTypeGovernance DocumentType = "GOVERNANCE"
|
||||
DocumentTypePolicy DocumentType = "POLICY"
|
||||
DocumentTypeProcedure DocumentType = "PROCEDURE"
|
||||
DocumentTypePlan DocumentType = "PLAN"
|
||||
DocumentTypeRegister DocumentType = "REGISTER"
|
||||
DocumentTypeRecord DocumentType = "RECORD"
|
||||
DocumentTypeReport DocumentType = "REPORT"
|
||||
DocumentTypeTemplate DocumentType = "TEMPLATE"
|
||||
DocumentTypeOther DocumentType = "OTHER"
|
||||
DocumentTypeGovernance DocumentType = "GOVERNANCE"
|
||||
DocumentTypePolicy DocumentType = "POLICY"
|
||||
DocumentTypeProcedure DocumentType = "PROCEDURE"
|
||||
DocumentTypePlan DocumentType = "PLAN"
|
||||
DocumentTypeRegister DocumentType = "REGISTER"
|
||||
DocumentTypeRecord DocumentType = "RECORD"
|
||||
DocumentTypeReport DocumentType = "REPORT"
|
||||
DocumentTypeTemplate DocumentType = "TEMPLATE"
|
||||
DocumentTypeStatementOfApplicability DocumentType = "STATEMENT_OF_APPLICABILITY"
|
||||
)
|
||||
|
||||
func DocumentTypes() []DocumentType {
|
||||
@@ -46,6 +47,7 @@ func DocumentTypes() []DocumentType {
|
||||
DocumentTypeRecord,
|
||||
DocumentTypeReport,
|
||||
DocumentTypeTemplate,
|
||||
DocumentTypeStatementOfApplicability,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -75,6 +77,8 @@ func (dt *DocumentType) UnmarshalText(data []byte) error {
|
||||
*dt = DocumentTypeReport
|
||||
case DocumentTypeTemplate.String():
|
||||
*dt = DocumentTypeTemplate
|
||||
case DocumentTypeStatementOfApplicability.String():
|
||||
*dt = DocumentTypeStatementOfApplicability
|
||||
default:
|
||||
return fmt.Errorf("invalid DocumentType value: %q", val)
|
||||
}
|
||||
|
||||
@@ -30,20 +30,21 @@ import (
|
||||
|
||||
type (
|
||||
DocumentVersion struct {
|
||||
ID gid.GID `db:"id"`
|
||||
OrganizationID gid.GID `db:"organization_id"`
|
||||
DocumentID gid.GID `db:"document_id"`
|
||||
Title string `db:"title"`
|
||||
Major int `db:"major"`
|
||||
Minor int `db:"minor"`
|
||||
Classification DocumentClassification `db:"classification"`
|
||||
DocumentType DocumentType `db:"document_type"`
|
||||
Content string `db:"content"`
|
||||
Changelog string `db:"changelog"`
|
||||
Status DocumentVersionStatus `db:"status"`
|
||||
PublishedAt *time.Time `db:"published_at"`
|
||||
CreatedAt time.Time `db:"created_at"`
|
||||
UpdatedAt time.Time `db:"updated_at"`
|
||||
ID gid.GID `db:"id"`
|
||||
OrganizationID gid.GID `db:"organization_id"`
|
||||
DocumentID gid.GID `db:"document_id"`
|
||||
Title string `db:"title"`
|
||||
Major int `db:"major"`
|
||||
Minor int `db:"minor"`
|
||||
Classification DocumentClassification `db:"classification"`
|
||||
DocumentType DocumentType `db:"document_type"`
|
||||
Content string `db:"content"`
|
||||
Changelog string `db:"changelog"`
|
||||
Status DocumentVersionStatus `db:"status"`
|
||||
Orientation DocumentVersionOrientation `db:"orientation"`
|
||||
PublishedAt *time.Time `db:"published_at"`
|
||||
CreatedAt time.Time `db:"created_at"`
|
||||
UpdatedAt time.Time `db:"updated_at"`
|
||||
}
|
||||
|
||||
DocumentVersions []*DocumentVersion
|
||||
@@ -93,6 +94,7 @@ SELECT
|
||||
content,
|
||||
changelog,
|
||||
status,
|
||||
orientation,
|
||||
published_at,
|
||||
created_at,
|
||||
updated_at
|
||||
@@ -156,6 +158,7 @@ SELECT
|
||||
content,
|
||||
changelog,
|
||||
status,
|
||||
orientation,
|
||||
published_at,
|
||||
created_at,
|
||||
updated_at
|
||||
@@ -211,6 +214,8 @@ INSERT INTO document_versions (
|
||||
content,
|
||||
changelog,
|
||||
status,
|
||||
orientation,
|
||||
published_at,
|
||||
created_at,
|
||||
updated_at
|
||||
)
|
||||
@@ -227,6 +232,8 @@ VALUES (
|
||||
@content,
|
||||
@changelog,
|
||||
@status,
|
||||
@orientation,
|
||||
@published_at,
|
||||
@created_at,
|
||||
@updated_at
|
||||
)
|
||||
@@ -244,6 +251,8 @@ VALUES (
|
||||
"content": dv.Content,
|
||||
"changelog": dv.Changelog,
|
||||
"status": dv.Status,
|
||||
"orientation": dv.Orientation,
|
||||
"published_at": dv.PublishedAt,
|
||||
"created_at": dv.CreatedAt,
|
||||
"updated_at": dv.UpdatedAt,
|
||||
}
|
||||
@@ -285,6 +294,7 @@ SELECT
|
||||
content,
|
||||
changelog,
|
||||
status,
|
||||
orientation,
|
||||
published_at,
|
||||
created_at,
|
||||
updated_at
|
||||
@@ -344,6 +354,7 @@ SELECT
|
||||
content,
|
||||
changelog,
|
||||
status,
|
||||
orientation,
|
||||
published_at,
|
||||
created_at,
|
||||
updated_at
|
||||
@@ -399,6 +410,7 @@ SELECT
|
||||
content,
|
||||
changelog,
|
||||
status,
|
||||
orientation,
|
||||
published_at,
|
||||
created_at,
|
||||
updated_at
|
||||
@@ -453,6 +465,7 @@ UPDATE document_versions SET
|
||||
published_at = @published_at,
|
||||
classification = @classification,
|
||||
document_type = @document_type,
|
||||
orientation = @orientation,
|
||||
updated_at = @updated_at
|
||||
WHERE %s
|
||||
AND id = @document_version_id
|
||||
@@ -471,6 +484,7 @@ WHERE %s
|
||||
"published_at": dv.PublishedAt,
|
||||
"classification": dv.Classification,
|
||||
"document_type": dv.DocumentType,
|
||||
"orientation": dv.Orientation,
|
||||
"updated_at": dv.UpdatedAt,
|
||||
}
|
||||
maps.Copy(args, scope.SQLArguments())
|
||||
|
||||
72
pkg/coredata/document_version_orientation.go
Normal file
72
pkg/coredata/document_version_orientation.go
Normal file
@@ -0,0 +1,72 @@
|
||||
// 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.
|
||||
|
||||
package coredata
|
||||
|
||||
import (
|
||||
"database/sql/driver"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
type (
|
||||
DocumentVersionOrientation string
|
||||
)
|
||||
|
||||
const (
|
||||
DocumentVersionOrientationPortrait DocumentVersionOrientation = "PORTRAIT"
|
||||
DocumentVersionOrientationLandscape DocumentVersionOrientation = "LANDSCAPE"
|
||||
)
|
||||
|
||||
func DocumentVersionOrientations() []DocumentVersionOrientation {
|
||||
return []DocumentVersionOrientation{
|
||||
DocumentVersionOrientationPortrait,
|
||||
DocumentVersionOrientationLandscape,
|
||||
}
|
||||
}
|
||||
|
||||
func (o DocumentVersionOrientation) MarshalText() ([]byte, error) {
|
||||
return []byte(o.String()), nil
|
||||
}
|
||||
|
||||
func (o *DocumentVersionOrientation) UnmarshalText(data []byte) error {
|
||||
val := string(data)
|
||||
|
||||
switch val {
|
||||
case DocumentVersionOrientationPortrait.String():
|
||||
*o = DocumentVersionOrientationPortrait
|
||||
case DocumentVersionOrientationLandscape.String():
|
||||
*o = DocumentVersionOrientationLandscape
|
||||
default:
|
||||
return fmt.Errorf("invalid DocumentVersionOrientation value: %q", val)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (o DocumentVersionOrientation) String() string {
|
||||
return string(o)
|
||||
}
|
||||
|
||||
func (o *DocumentVersionOrientation) Scan(value any) error {
|
||||
val, ok := value.(string)
|
||||
if !ok {
|
||||
return fmt.Errorf("invalid scan source for DocumentVersionOrientation, expected string got %T", value)
|
||||
}
|
||||
|
||||
return o.UnmarshalText([]byte(val))
|
||||
}
|
||||
|
||||
func (o DocumentVersionOrientation) Value() (driver.Value, error) {
|
||||
return o.String(), nil
|
||||
}
|
||||
48
pkg/coredata/document_write_mode.go
Normal file
48
pkg/coredata/document_write_mode.go
Normal file
@@ -0,0 +1,48 @@
|
||||
// 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.
|
||||
|
||||
package coredata
|
||||
|
||||
import "fmt"
|
||||
|
||||
type (
|
||||
DocumentWriteMode string
|
||||
)
|
||||
|
||||
const (
|
||||
DocumentWriteModeAuthored DocumentWriteMode = "AUTHORED"
|
||||
DocumentWriteModeGenerated DocumentWriteMode = "GENERATED"
|
||||
)
|
||||
|
||||
func (e DocumentWriteMode) IsValid() bool {
|
||||
switch e {
|
||||
case DocumentWriteModeAuthored, DocumentWriteModeGenerated:
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (e DocumentWriteMode) String() string { return string(e) }
|
||||
|
||||
func (e *DocumentWriteMode) UnmarshalText(text []byte) error {
|
||||
*e = DocumentWriteMode(text)
|
||||
if !e.IsValid() {
|
||||
return fmt.Errorf("%s is not a valid DocumentWriteMode", string(text))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (e DocumentWriteMode) MarshalText() ([]byte, error) {
|
||||
return []byte(e.String()), nil
|
||||
}
|
||||
@@ -24,22 +24,23 @@ type (
|
||||
)
|
||||
|
||||
const (
|
||||
ElectronicSignatureDocumentTypeNDA ElectronicSignatureDocumentType = "NDA"
|
||||
ElectronicSignatureDocumentTypeDPA ElectronicSignatureDocumentType = "DPA"
|
||||
ElectronicSignatureDocumentTypeMSA ElectronicSignatureDocumentType = "MSA"
|
||||
ElectronicSignatureDocumentTypeSOW ElectronicSignatureDocumentType = "SOW"
|
||||
ElectronicSignatureDocumentTypeSLA ElectronicSignatureDocumentType = "SLA"
|
||||
ElectronicSignatureDocumentTypeTOS ElectronicSignatureDocumentType = "TOS"
|
||||
ElectronicSignatureDocumentTypePrivacyPolicy ElectronicSignatureDocumentType = "PRIVACY_POLICY"
|
||||
ElectronicSignatureDocumentTypeGovernance ElectronicSignatureDocumentType = "GOVERNANCE"
|
||||
ElectronicSignatureDocumentTypePolicy ElectronicSignatureDocumentType = "POLICY"
|
||||
ElectronicSignatureDocumentTypeProcedure ElectronicSignatureDocumentType = "PROCEDURE"
|
||||
ElectronicSignatureDocumentTypePlan ElectronicSignatureDocumentType = "PLAN"
|
||||
ElectronicSignatureDocumentTypeRegister ElectronicSignatureDocumentType = "REGISTER"
|
||||
ElectronicSignatureDocumentTypeRecord ElectronicSignatureDocumentType = "RECORD"
|
||||
ElectronicSignatureDocumentTypeReport ElectronicSignatureDocumentType = "REPORT"
|
||||
ElectronicSignatureDocumentTypeTemplate ElectronicSignatureDocumentType = "TEMPLATE"
|
||||
ElectronicSignatureDocumentTypeOther ElectronicSignatureDocumentType = "OTHER"
|
||||
ElectronicSignatureDocumentTypeNDA ElectronicSignatureDocumentType = "NDA"
|
||||
ElectronicSignatureDocumentTypeDPA ElectronicSignatureDocumentType = "DPA"
|
||||
ElectronicSignatureDocumentTypeMSA ElectronicSignatureDocumentType = "MSA"
|
||||
ElectronicSignatureDocumentTypeSOW ElectronicSignatureDocumentType = "SOW"
|
||||
ElectronicSignatureDocumentTypeSLA ElectronicSignatureDocumentType = "SLA"
|
||||
ElectronicSignatureDocumentTypeTOS ElectronicSignatureDocumentType = "TOS"
|
||||
ElectronicSignatureDocumentTypePrivacyPolicy ElectronicSignatureDocumentType = "PRIVACY_POLICY"
|
||||
ElectronicSignatureDocumentTypeGovernance ElectronicSignatureDocumentType = "GOVERNANCE"
|
||||
ElectronicSignatureDocumentTypePolicy ElectronicSignatureDocumentType = "POLICY"
|
||||
ElectronicSignatureDocumentTypeProcedure ElectronicSignatureDocumentType = "PROCEDURE"
|
||||
ElectronicSignatureDocumentTypePlan ElectronicSignatureDocumentType = "PLAN"
|
||||
ElectronicSignatureDocumentTypeRegister ElectronicSignatureDocumentType = "REGISTER"
|
||||
ElectronicSignatureDocumentTypeRecord ElectronicSignatureDocumentType = "RECORD"
|
||||
ElectronicSignatureDocumentTypeReport ElectronicSignatureDocumentType = "REPORT"
|
||||
ElectronicSignatureDocumentTypeTemplate ElectronicSignatureDocumentType = "TEMPLATE"
|
||||
ElectronicSignatureDocumentTypeStatementOfApplicability ElectronicSignatureDocumentType = "STATEMENT_OF_APPLICABILITY"
|
||||
ElectronicSignatureDocumentTypeOther ElectronicSignatureDocumentType = "OTHER"
|
||||
|
||||
ESignProcessConsentText = "By typing my full name and clicking Accept, I consent to sign this document electronically and agree that my electronic signature has the same legal validity as a handwritten signature."
|
||||
)
|
||||
@@ -61,6 +62,7 @@ func ElectronicSignatureDocumentTypes() []ElectronicSignatureDocumentType {
|
||||
ElectronicSignatureDocumentTypeRecord,
|
||||
ElectronicSignatureDocumentTypeReport,
|
||||
ElectronicSignatureDocumentTypeTemplate,
|
||||
ElectronicSignatureDocumentTypeStatementOfApplicability,
|
||||
ElectronicSignatureDocumentTypeOther,
|
||||
}
|
||||
}
|
||||
@@ -103,6 +105,8 @@ func (dt *ElectronicSignatureDocumentType) UnmarshalText(data []byte) error {
|
||||
*dt = ElectronicSignatureDocumentTypeReport
|
||||
case ElectronicSignatureDocumentTypeTemplate.String():
|
||||
*dt = ElectronicSignatureDocumentTypeTemplate
|
||||
case ElectronicSignatureDocumentTypeStatementOfApplicability.String():
|
||||
*dt = ElectronicSignatureDocumentTypeStatementOfApplicability
|
||||
case ElectronicSignatureDocumentTypeOther.String():
|
||||
*dt = ElectronicSignatureDocumentTypeOther
|
||||
default:
|
||||
@@ -161,6 +165,8 @@ func (dt ElectronicSignatureDocumentType) DisplayName() string {
|
||||
return "Report"
|
||||
case ElectronicSignatureDocumentTypeTemplate:
|
||||
return "Template"
|
||||
case ElectronicSignatureDocumentTypeStatementOfApplicability:
|
||||
return "Statement of Applicability"
|
||||
default:
|
||||
return string(dt)
|
||||
}
|
||||
@@ -199,6 +205,8 @@ func (dt ElectronicSignatureDocumentType) ConsentText() (string, error) {
|
||||
docAgreement = "I acknowledge and agree to this Report."
|
||||
case ElectronicSignatureDocumentTypeTemplate:
|
||||
docAgreement = "I acknowledge and agree to this Template."
|
||||
case ElectronicSignatureDocumentTypeStatementOfApplicability:
|
||||
docAgreement = "I acknowledge and agree to this Statement of Applicability."
|
||||
case ElectronicSignatureDocumentTypeOther:
|
||||
return "", fmt.Errorf("cannot get consent text: document type OTHER requires explicit consent text")
|
||||
default:
|
||||
@@ -226,6 +234,8 @@ func ElectronicSignatureDocumentTypeFromDocumentType(dt DocumentType) Electronic
|
||||
return ElectronicSignatureDocumentTypeReport
|
||||
case DocumentTypeTemplate:
|
||||
return ElectronicSignatureDocumentTypeTemplate
|
||||
case DocumentTypeStatementOfApplicability:
|
||||
return ElectronicSignatureDocumentTypeStatementOfApplicability
|
||||
default:
|
||||
return ElectronicSignatureDocumentTypeOther
|
||||
}
|
||||
|
||||
45
pkg/coredata/migrations/20260410T120000Z.sql
Normal file
45
pkg/coredata/migrations/20260410T120000Z.sql
Normal file
@@ -0,0 +1,45 @@
|
||||
-- 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.
|
||||
|
||||
CREATE TYPE document_version_orientation AS ENUM ('PORTRAIT', 'LANDSCAPE');
|
||||
|
||||
CREATE TYPE document_write_mode AS ENUM ('AUTHORED', 'GENERATED');
|
||||
|
||||
ALTER TABLE document_versions
|
||||
ADD COLUMN orientation document_version_orientation DEFAULT 'PORTRAIT';
|
||||
|
||||
ALTER TABLE document_versions
|
||||
ALTER COLUMN orientation DROP DEFAULT;
|
||||
|
||||
ALTER TABLE documents
|
||||
ADD COLUMN write_mode document_write_mode NOT NULL DEFAULT 'AUTHORED';
|
||||
|
||||
ALTER TABLE documents
|
||||
ALTER COLUMN write_mode DROP DEFAULT;
|
||||
|
||||
ALTER TYPE document_type ADD VALUE 'STATEMENT_OF_APPLICABILITY';
|
||||
|
||||
ALTER TYPE electronic_signature_document_type ADD VALUE 'STATEMENT_OF_APPLICABILITY';
|
||||
|
||||
ALTER TABLE statements_of_applicability
|
||||
ADD COLUMN document_id TEXT UNIQUE REFERENCES documents(id) ON DELETE SET NULL;
|
||||
|
||||
-- TODO: drop owner_profile_id column
|
||||
ALTER TABLE statements_of_applicability
|
||||
ALTER COLUMN owner_profile_id DROP NOT NULL;
|
||||
|
||||
-- TODO: drop statements_of_applicability.source_id column
|
||||
-- TODO: drop statements_of_applicability.snapshot_id column
|
||||
-- TODO: drop applicability_statements.snapshot_id column
|
||||
|
||||
@@ -124,6 +124,7 @@ FROM
|
||||
WHERE
|
||||
%s
|
||||
AND organization_id = @organization_id
|
||||
AND type != 'STATEMENTS_OF_APPLICABILITY'
|
||||
AND %s
|
||||
`
|
||||
|
||||
@@ -163,6 +164,7 @@ FROM
|
||||
WHERE
|
||||
%s
|
||||
AND organization_id = @organization_id
|
||||
AND type != 'STATEMENTS_OF_APPLICABILITY'
|
||||
AND %s
|
||||
`
|
||||
|
||||
|
||||
@@ -43,7 +43,6 @@ func SnapshotsTypes() []SnapshotsType {
|
||||
SnapshotsTypeFindings,
|
||||
SnapshotsTypeObligations,
|
||||
SnapshotsTypeProcessingActivities,
|
||||
SnapshotsTypeStatementsOfApplicability,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -42,8 +42,6 @@ func GetSnapshottable(snapshotType SnapshotsType) (Snapshottable, error) {
|
||||
return ProcessingActivities{}, nil
|
||||
case SnapshotsTypeVendors:
|
||||
return Vendors{}, nil
|
||||
case SnapshotsTypeStatementsOfApplicability:
|
||||
return StatementsOfApplicability{}, nil
|
||||
default:
|
||||
return nil, fmt.Errorf("unsupported snapshot type: %s", snapshotType)
|
||||
}
|
||||
|
||||
@@ -33,9 +33,7 @@ type (
|
||||
ID gid.GID `db:"id"`
|
||||
OrganizationID gid.GID `db:"organization_id"`
|
||||
Name string `db:"name"`
|
||||
SourceID *gid.GID `db:"source_id"`
|
||||
SnapshotID *gid.GID `db:"snapshot_id"`
|
||||
OwnerID gid.GID `db:"owner_profile_id"`
|
||||
DocumentID *gid.GID `db:"document_id"`
|
||||
CreatedAt time.Time `db:"created_at"`
|
||||
UpdatedAt time.Time `db:"updated_at"`
|
||||
}
|
||||
@@ -79,9 +77,7 @@ SELECT
|
||||
id,
|
||||
organization_id,
|
||||
name,
|
||||
source_id,
|
||||
snapshot_id,
|
||||
owner_profile_id,
|
||||
document_id,
|
||||
created_at,
|
||||
updated_at
|
||||
FROM
|
||||
@@ -89,6 +85,7 @@ FROM
|
||||
WHERE
|
||||
%s
|
||||
AND id = @statement_of_applicability_id
|
||||
AND snapshot_id IS NULL
|
||||
LIMIT 1;
|
||||
`
|
||||
|
||||
@@ -121,16 +118,13 @@ func (s *StatementsOfApplicability) LoadByOrganizationID(
|
||||
scope Scoper,
|
||||
organizationID gid.GID,
|
||||
cursor *page.Cursor[StatementOfApplicabilityOrderField],
|
||||
filter *StatementOfApplicabilityFilter,
|
||||
) error {
|
||||
q := `
|
||||
SELECT
|
||||
id,
|
||||
organization_id,
|
||||
name,
|
||||
source_id,
|
||||
snapshot_id,
|
||||
owner_profile_id,
|
||||
document_id,
|
||||
created_at,
|
||||
updated_at
|
||||
FROM
|
||||
@@ -138,14 +132,13 @@ FROM
|
||||
WHERE
|
||||
%s
|
||||
AND organization_id = @organization_id
|
||||
AND %s
|
||||
AND snapshot_id IS NULL
|
||||
AND %s
|
||||
`
|
||||
q = fmt.Sprintf(q, scope.SQLFragment(), filter.SQLFragment(), cursor.SQLFragment())
|
||||
q = fmt.Sprintf(q, scope.SQLFragment(), cursor.SQLFragment())
|
||||
|
||||
args := pgx.NamedArgs{"organization_id": organizationID}
|
||||
maps.Copy(args, scope.SQLArguments())
|
||||
maps.Copy(args, filter.SQLArguments())
|
||||
maps.Copy(args, cursor.SQLArguments())
|
||||
|
||||
rows, err := conn.Query(ctx, q, args)
|
||||
@@ -167,7 +160,6 @@ func (s *StatementsOfApplicability) CountByOrganizationID(
|
||||
conn pg.Querier,
|
||||
scope Scoper,
|
||||
organizationID gid.GID,
|
||||
filter *StatementOfApplicabilityFilter,
|
||||
) (int, error) {
|
||||
q := `
|
||||
SELECT
|
||||
@@ -177,15 +169,14 @@ FROM
|
||||
WHERE
|
||||
%s
|
||||
AND organization_id = @organization_id
|
||||
AND %s
|
||||
AND snapshot_id IS NULL
|
||||
`
|
||||
q = fmt.Sprintf(q, scope.SQLFragment(), filter.SQLFragment())
|
||||
q = fmt.Sprintf(q, scope.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
|
||||
@@ -208,9 +199,7 @@ INSERT INTO
|
||||
id,
|
||||
organization_id,
|
||||
name,
|
||||
source_id,
|
||||
snapshot_id,
|
||||
owner_profile_id,
|
||||
document_id,
|
||||
created_at,
|
||||
updated_at
|
||||
)
|
||||
@@ -219,9 +208,7 @@ VALUES (
|
||||
@statement_of_applicability_id,
|
||||
@organization_id,
|
||||
@name,
|
||||
@source_id,
|
||||
@snapshot_id,
|
||||
@owner_profile_id,
|
||||
@document_id,
|
||||
@created_at,
|
||||
@updated_at
|
||||
);
|
||||
@@ -232,9 +219,7 @@ VALUES (
|
||||
"statement_of_applicability_id": s.ID,
|
||||
"organization_id": s.OrganizationID,
|
||||
"name": s.Name,
|
||||
"source_id": s.SourceID,
|
||||
"snapshot_id": s.SnapshotID,
|
||||
"owner_profile_id": s.OwnerID,
|
||||
"document_id": s.DocumentID,
|
||||
"created_at": s.CreatedAt,
|
||||
"updated_at": s.UpdatedAt,
|
||||
}
|
||||
@@ -262,11 +247,12 @@ func (s *StatementOfApplicability) Update(
|
||||
UPDATE statements_of_applicability
|
||||
SET
|
||||
name = @name,
|
||||
owner_profile_id = @owner_profile_id,
|
||||
document_id = @document_id,
|
||||
updated_at = @updated_at
|
||||
WHERE
|
||||
%s
|
||||
AND id = @statement_of_applicability_id
|
||||
AND snapshot_id IS NULL
|
||||
`
|
||||
|
||||
q = fmt.Sprintf(q, scope.SQLFragment())
|
||||
@@ -274,7 +260,7 @@ WHERE
|
||||
args := pgx.StrictNamedArgs{
|
||||
"statement_of_applicability_id": s.ID,
|
||||
"name": s.Name,
|
||||
"owner_profile_id": s.OwnerID,
|
||||
"document_id": s.DocumentID,
|
||||
"updated_at": s.UpdatedAt,
|
||||
}
|
||||
maps.Copy(args, scope.SQLArguments())
|
||||
@@ -307,6 +293,7 @@ DELETE FROM statements_of_applicability
|
||||
WHERE
|
||||
%s
|
||||
AND id = @statement_of_applicability_id
|
||||
AND snapshot_id IS NULL
|
||||
`
|
||||
q = fmt.Sprintf(q, scope.SQLFragment())
|
||||
|
||||
@@ -326,139 +313,3 @@ WHERE
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (soas StatementsOfApplicability) Snapshot(ctx context.Context, conn pg.Tx, scope Scoper, organizationID, snapshotID gid.GID) error {
|
||||
if err := soas.insertStatementOfApplicabilitySnapshots(ctx, conn, scope, organizationID, snapshotID); err != nil {
|
||||
return fmt.Errorf("cannot insert statement_of_applicability snapshots: %w", err)
|
||||
}
|
||||
|
||||
if err := soas.insertStatementOfApplicabilityControlSnapshots(ctx, conn, scope, organizationID, snapshotID); err != nil {
|
||||
return fmt.Errorf("cannot insert statement_of_applicability_control snapshots: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (soas StatementsOfApplicability) insertStatementOfApplicabilitySnapshots(
|
||||
ctx context.Context,
|
||||
conn pg.Querier,
|
||||
scope Scoper,
|
||||
organizationID gid.GID,
|
||||
snapshotID gid.GID,
|
||||
) error {
|
||||
query := `
|
||||
INSERT INTO statements_of_applicability (
|
||||
id,
|
||||
tenant_id,
|
||||
organization_id,
|
||||
name,
|
||||
source_id,
|
||||
snapshot_id,
|
||||
owner_profile_id,
|
||||
created_at,
|
||||
updated_at
|
||||
)
|
||||
SELECT
|
||||
generate_gid(decode_base64_unpadded(@tenant_id), @statement_of_applicability_entity_type),
|
||||
@tenant_id,
|
||||
soa.organization_id,
|
||||
soa.name,
|
||||
soa.id,
|
||||
@snapshot_id,
|
||||
soa.owner_profile_id,
|
||||
soa.created_at,
|
||||
soa.updated_at
|
||||
FROM statements_of_applicability soa
|
||||
WHERE
|
||||
%s
|
||||
AND soa.organization_id = @organization_id
|
||||
AND soa.snapshot_id IS NULL
|
||||
`
|
||||
|
||||
query = fmt.Sprintf(query, scope.SQLFragment())
|
||||
|
||||
args := pgx.StrictNamedArgs{
|
||||
"tenant_id": scope.GetTenantID(),
|
||||
"snapshot_id": snapshotID,
|
||||
"organization_id": organizationID,
|
||||
"statement_of_applicability_entity_type": StatementOfApplicabilityEntityType,
|
||||
}
|
||||
maps.Copy(args, scope.SQLArguments())
|
||||
|
||||
_, err := conn.Exec(ctx, query, args)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot insert statement_of_applicability snapshots: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (soas StatementsOfApplicability) insertStatementOfApplicabilityControlSnapshots(
|
||||
ctx context.Context,
|
||||
conn pg.Querier,
|
||||
scope Scoper,
|
||||
organizationID gid.GID,
|
||||
snapshotID gid.GID,
|
||||
) error {
|
||||
query := `
|
||||
WITH source_soa AS (
|
||||
SELECT id, organization_id
|
||||
FROM statements_of_applicability
|
||||
WHERE
|
||||
%s
|
||||
AND organization_id = @organization_id
|
||||
AND snapshot_id IS NULL
|
||||
),
|
||||
snapshot_soa AS (
|
||||
SELECT id, source_id
|
||||
FROM statements_of_applicability
|
||||
WHERE snapshot_id = @snapshot_id
|
||||
)
|
||||
INSERT INTO applicability_statements (
|
||||
id,
|
||||
statement_of_applicability_id,
|
||||
control_id,
|
||||
organization_id,
|
||||
tenant_id,
|
||||
snapshot_id,
|
||||
applicability,
|
||||
justification,
|
||||
created_at,
|
||||
updated_at
|
||||
)
|
||||
SELECT
|
||||
generate_gid(decode_base64_unpadded(@tenant_id), @applicability_statement_entity_type),
|
||||
snapshot_soa.id,
|
||||
soac.control_id,
|
||||
soac.organization_id,
|
||||
@tenant_id,
|
||||
@snapshot_id,
|
||||
soac.applicability,
|
||||
soac.justification,
|
||||
soac.created_at,
|
||||
soac.updated_at
|
||||
FROM applicability_statements soac
|
||||
INNER JOIN source_soa
|
||||
ON soac.statement_of_applicability_id = source_soa.id
|
||||
INNER JOIN snapshot_soa
|
||||
ON snapshot_soa.source_id = source_soa.id
|
||||
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,
|
||||
"applicability_statement_entity_type": ApplicabilityStatementEntityType,
|
||||
}
|
||||
maps.Copy(args, scope.SQLArguments())
|
||||
|
||||
_, err := conn.Exec(ctx, query, args)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot insert statement_of_applicability_control snapshots: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -1,61 +0,0 @@
|
||||
// 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.
|
||||
|
||||
package coredata
|
||||
|
||||
import (
|
||||
"github.com/jackc/pgx/v5"
|
||||
"go.probo.inc/probo/pkg/gid"
|
||||
)
|
||||
|
||||
type (
|
||||
StatementOfApplicabilityFilter struct {
|
||||
snapshotID **gid.GID
|
||||
}
|
||||
)
|
||||
|
||||
func NewStatementOfApplicabilityFilter(snapshotID **gid.GID) *StatementOfApplicabilityFilter {
|
||||
return &StatementOfApplicabilityFilter{
|
||||
snapshotID: snapshotID,
|
||||
}
|
||||
}
|
||||
|
||||
func (f *StatementOfApplicabilityFilter) SQLArguments() pgx.StrictNamedArgs {
|
||||
args := pgx.StrictNamedArgs{}
|
||||
|
||||
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 *StatementOfApplicabilityFilter) SQLFragment() string {
|
||||
return `
|
||||
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`
|
||||
}
|
||||
Reference in New Issue
Block a user