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:
Sacha Al Himdani
2026-04-10 13:38:42 +02:00
parent 53edc5ba26
commit c635492f75
74 changed files with 3509 additions and 1595 deletions

View File

@@ -44,9 +44,6 @@ var (
//go:embed transfer_impact_assessments_template.html
transferImpactAssessmentsTemplateContent string
//go:embed soa_template.html
soaTemplateContent string
templateFuncs = template.FuncMap{
"now": func() time.Time { return time.Now() },
"eq": func(a, b any) bool { return a == b },
@@ -186,8 +183,6 @@ var (
dataProtectionImpactAssessmentsTemplate = template.Must(template.New("dataProtectionImpactAssessments").Funcs(templateFuncs).Parse(dataProtectionImpactAssessmentsTemplateContent))
transferImpactAssessmentsTemplate = template.Must(template.New("transferImpactAssessments").Funcs(templateFuncs).Parse(transferImpactAssessmentsTemplateContent))
statementOfApplicabilityTemplate = template.Must(template.New("statement-of-applicability").Funcs(templateFuncs).Parse(soaTemplateContent))
)
type (
@@ -205,6 +200,7 @@ type (
Signatures []SignatureData
CompanyHorizontalLogoBase64 string
MermaidJS template.JS
Landscape bool
}
SignatureData struct {
@@ -280,37 +276,35 @@ type (
}
StatementOfApplicabilityData struct {
Title string
OrganizationName string
CreatedAt time.Time
TotalControls int
FrameworkGroups []FrameworkControlGroup
CompanyHorizontalLogoBase64 string
Version int
PublishedAt time.Time
Approver string
Title string
OrganizationName string
CreatedAt time.Time
TotalControls int
Rows []SOARow
}
FrameworkControlGroup struct {
FrameworkName string
Controls []ControlData
}
ControlData struct {
FrameworkName string
SectionTitle string
Name string
Applicability *bool
Justification *string
BestPractice *bool
Implemented *string
NotImplementedJustification *string
Regulatory *bool
Contractual *bool
RiskAssessment *bool
SOARow struct {
FrameworkName string
ControlSection string
ControlName string
Applicability string
Justification string
Implemented string
NotImplJustification string
Regulatory string
Contractual string
BestPractice string
RiskAssessment string
}
)
func BoolLabel(v bool) string {
if v {
return "Yes"
}
return "No"
}
const (
ClassificationPublic Classification = "PUBLIC"
ClassificationInternal Classification = "INTERNAL"
@@ -381,12 +375,3 @@ func RenderTransferImpactAssessmentsTableHTML(data TransferImpactAssessmentTable
return buf.Bytes(), nil
}
func RenderStatementOfApplicabilityHTML(data StatementOfApplicabilityData) ([]byte, error) {
var buf bytes.Buffer
if err := statementOfApplicabilityTemplate.Execute(&buf, data); err != nil {
return nil, fmt.Errorf("cannot execute SOA template: %w", err)
}
return buf.Bytes(), nil
}