Change state of applicability
Signed-off-by: Sacha Al Himdani <sacha@getprobo.com>
This commit is contained in:
@@ -103,6 +103,7 @@ const (
|
||||
ActionListMeetings Action = "listMeetings"
|
||||
ActionListMembers Action = "listMembers"
|
||||
ActionListNonconformities Action = "listNonconformities"
|
||||
ActionListStatesOfApplicability Action = "listStatesOfApplicability"
|
||||
ActionListObligations Action = "listObligations"
|
||||
ActionListPeople Action = "listPeople"
|
||||
ActionListProcessingActivities Action = "listProcessingActivities"
|
||||
@@ -120,43 +121,46 @@ const (
|
||||
ActionListSignableDocuments Action = "listSignableDocuments"
|
||||
ActionListSignableDocumentVersion Action = "listSignableDocumentVersion"
|
||||
|
||||
ActionCreateAsset Action = "createAsset"
|
||||
ActionCreateAudit Action = "createAudit"
|
||||
ActionCreateContinualImprovement Action = "createContinualImprovement"
|
||||
ActionCreateRightsRequest Action = "createRightsRequest"
|
||||
ActionCreateControl Action = "createControl"
|
||||
ActionCreateControlAuditMapping Action = "createControlAuditMapping"
|
||||
ActionCreateControlDocumentMapping Action = "createControlDocumentMapping"
|
||||
ActionCreateControlMeasureMapping Action = "createControlMeasureMapping"
|
||||
ActionCreateControlSnapshotMapping Action = "createControlSnapshotMapping"
|
||||
ActionCreateCustomDomain Action = "createCustomDomain"
|
||||
ActionCreateDatum Action = "createDatum"
|
||||
ActionCreateDocument Action = "createDocument"
|
||||
ActionCreateDraftDocumentVersion Action = "createDraftDocumentVersion"
|
||||
ActionCreateFramework Action = "createFramework"
|
||||
ActionCreateMeasure Action = "createMeasure"
|
||||
ActionCreateMeeting Action = "createMeeting"
|
||||
ActionCreateNonconformity Action = "createNonconformity"
|
||||
ActionCreateObligation Action = "createObligation"
|
||||
ActionCreatePeople Action = "createPeople"
|
||||
ActionCreateProcessingActivity Action = "createProcessingActivity"
|
||||
ActionCreateDataProtectionImpactAssessment Action = "createDataProtectionImpactAssessment"
|
||||
ActionCreateTransferImpactAssessment Action = "createTransferImpactAssessment"
|
||||
ActionCreateRisk Action = "createRisk"
|
||||
ActionCreateRiskDocumentMapping Action = "createRiskDocumentMapping"
|
||||
ActionCreateRiskMeasureMapping Action = "createRiskMeasureMapping"
|
||||
ActionCreateRiskObligationMapping Action = "createRiskObligationMapping"
|
||||
ActionCreateSAMLConfiguration Action = "createSAMLConfiguration"
|
||||
ActionCreateSnapshot Action = "createSnapshot"
|
||||
ActionCreateTask Action = "createTask"
|
||||
ActionCreateTrustCenter Action = "createTrustCenter"
|
||||
ActionCreateTrustCenterAccess Action = "createTrustCenterAccess"
|
||||
ActionCreateTrustCenterFile Action = "createTrustCenterFile"
|
||||
ActionCreateTrustCenterReference Action = "createTrustCenterReference"
|
||||
ActionCreateVendor Action = "createVendor"
|
||||
ActionCreateVendorContact Action = "createVendorContact"
|
||||
ActionCreateVendorRiskAssessment Action = "createVendorRiskAssessment"
|
||||
ActionCreateVendorService Action = "createVendorService"
|
||||
ActionCreateAsset Action = "createAsset"
|
||||
ActionCreateAudit Action = "createAudit"
|
||||
ActionCreateContinualImprovement Action = "createContinualImprovement"
|
||||
ActionCreateRightsRequest Action = "createRightsRequest"
|
||||
ActionCreateControl Action = "createControl"
|
||||
ActionCreateControlAuditMapping Action = "createControlAuditMapping"
|
||||
ActionCreateControlObligationMapping Action = "createControlObligationMapping"
|
||||
ActionCreateControlDocumentMapping Action = "createControlDocumentMapping"
|
||||
ActionCreateControlMeasureMapping Action = "createControlMeasureMapping"
|
||||
ActionCreateControlSnapshotMapping Action = "createControlSnapshotMapping"
|
||||
ActionCreateStateOfApplicabilityControlMapping Action = "createStateOfApplicabilityControlMapping"
|
||||
ActionCreateCustomDomain Action = "createCustomDomain"
|
||||
ActionCreateDatum Action = "createDatum"
|
||||
ActionCreateDocument Action = "createDocument"
|
||||
ActionCreateDraftDocumentVersion Action = "createDraftDocumentVersion"
|
||||
ActionCreateFramework Action = "createFramework"
|
||||
ActionCreateMeasure Action = "createMeasure"
|
||||
ActionCreateMeeting Action = "createMeeting"
|
||||
ActionCreateNonconformity Action = "createNonconformity"
|
||||
ActionCreateObligation Action = "createObligation"
|
||||
ActionCreatePeople Action = "createPeople"
|
||||
ActionCreateProcessingActivity Action = "createProcessingActivity"
|
||||
ActionCreateStateOfApplicability Action = "createStateOfApplicability"
|
||||
ActionCreateDataProtectionImpactAssessment Action = "createDataProtectionImpactAssessment"
|
||||
ActionCreateTransferImpactAssessment Action = "createTransferImpactAssessment"
|
||||
ActionCreateRisk Action = "createRisk"
|
||||
ActionCreateRiskDocumentMapping Action = "createRiskDocumentMapping"
|
||||
ActionCreateRiskMeasureMapping Action = "createRiskMeasureMapping"
|
||||
ActionCreateRiskObligationMapping Action = "createRiskObligationMapping"
|
||||
ActionCreateSAMLConfiguration Action = "createSAMLConfiguration"
|
||||
ActionCreateSnapshot Action = "createSnapshot"
|
||||
ActionCreateTask Action = "createTask"
|
||||
ActionCreateTrustCenter Action = "createTrustCenter"
|
||||
ActionCreateTrustCenterAccess Action = "createTrustCenterAccess"
|
||||
ActionCreateTrustCenterFile Action = "createTrustCenterFile"
|
||||
ActionCreateTrustCenterReference Action = "createTrustCenterReference"
|
||||
ActionCreateVendor Action = "createVendor"
|
||||
ActionCreateVendorContact Action = "createVendorContact"
|
||||
ActionCreateVendorRiskAssessment Action = "createVendorRiskAssessment"
|
||||
ActionCreateVendorService Action = "createVendorService"
|
||||
|
||||
ActionUpdateAsset Action = "updateAsset"
|
||||
ActionUpdateAudit Action = "updateAudit"
|
||||
@@ -175,6 +179,7 @@ const (
|
||||
ActionUpdateOrganization Action = "updateOrganization"
|
||||
ActionUpdatePeople Action = "updatePeople"
|
||||
ActionUpdateProcessingActivity Action = "updateProcessingActivity"
|
||||
ActionUpdateStateOfApplicability Action = "updateStateOfApplicability"
|
||||
ActionUpdateDataProtectionImpactAssessment Action = "updateDataProtectionImpactAssessment"
|
||||
ActionUpdateTransferImpactAssessment Action = "updateTransferImpactAssessment"
|
||||
ActionUpdateRisk Action = "updateRisk"
|
||||
@@ -190,50 +195,53 @@ const (
|
||||
ActionUpdateVendorDataPrivacyAgreement Action = "updateVendorDataPrivacyAgreement"
|
||||
ActionUpdateVendorService Action = "updateVendorService"
|
||||
|
||||
ActionDeleteAsset Action = "deleteAsset"
|
||||
ActionDeleteAudit Action = "deleteAudit"
|
||||
ActionDeleteAuditReport Action = "deleteAuditReport"
|
||||
ActionDeleteContinualImprovement Action = "deleteContinualImprovement"
|
||||
ActionDeleteRightsRequest Action = "deleteRightsRequest"
|
||||
ActionDeleteControl Action = "deleteControl"
|
||||
ActionDeleteControlAuditMapping Action = "deleteControlAuditMapping"
|
||||
ActionDeleteControlDocumentMapping Action = "deleteControlDocumentMapping"
|
||||
ActionDeleteControlMeasureMapping Action = "deleteControlMeasureMapping"
|
||||
ActionDeleteControlSnapshotMapping Action = "deleteControlSnapshotMapping"
|
||||
ActionDeleteCustomDomain Action = "deleteCustomDomain"
|
||||
ActionDeleteDatum Action = "deleteDatum"
|
||||
ActionDeleteDocument Action = "deleteDocument"
|
||||
ActionDeleteDraftDocumentVersion Action = "deleteDraftDocumentVersion"
|
||||
ActionDeleteEvidence Action = "deleteEvidence"
|
||||
ActionDeleteFramework Action = "deleteFramework"
|
||||
ActionDeleteInvitation Action = "deleteInvitation"
|
||||
ActionDeleteMeasure Action = "deleteMeasure"
|
||||
ActionDeleteMeeting Action = "deleteMeeting"
|
||||
ActionDeleteNonconformity Action = "deleteNonconformity"
|
||||
ActionDeleteObligation Action = "deleteObligation"
|
||||
ActionDeleteOrganization Action = "deleteOrganization"
|
||||
ActionDeleteOrganizationHorizontalLogo Action = "deleteOrganizationHorizontalLogo"
|
||||
ActionDeletePeople Action = "deletePeople"
|
||||
ActionDeleteProcessingActivity Action = "deleteProcessingActivity"
|
||||
ActionDeleteDataProtectionImpactAssessment Action = "deleteDataProtectionImpactAssessment"
|
||||
ActionDeleteTransferImpactAssessment Action = "deleteTransferImpactAssessment"
|
||||
ActionDeleteRisk Action = "deleteRisk"
|
||||
ActionDeleteRiskDocumentMapping Action = "deleteRiskDocumentMapping"
|
||||
ActionDeleteRiskMeasureMapping Action = "deleteRiskMeasureMapping"
|
||||
ActionDeleteRiskObligationMapping Action = "deleteRiskObligationMapping"
|
||||
ActionDeleteSAMLConfiguration Action = "deleteSAMLConfiguration"
|
||||
ActionDeleteSnapshot Action = "deleteSnapshot"
|
||||
ActionDeleteTask Action = "deleteTask"
|
||||
ActionDeleteTrustCenterAccess Action = "deleteTrustCenterAccess"
|
||||
ActionDeleteTrustCenterFile Action = "deleteTrustCenterFile"
|
||||
ActionDeleteTrustCenterNDA Action = "deleteTrustCenterNDA"
|
||||
ActionDeleteTrustCenterReference Action = "deleteTrustCenterReference"
|
||||
ActionDeleteVendor Action = "deleteVendor"
|
||||
ActionDeleteVendorBusinessAssociateAgreement Action = "deleteVendorBusinessAssociateAgreement"
|
||||
ActionDeleteVendorComplianceReport Action = "deleteVendorComplianceReport"
|
||||
ActionDeleteVendorContact Action = "deleteVendorContact"
|
||||
ActionDeleteVendorDataPrivacyAgreement Action = "deleteVendorDataPrivacyAgreement"
|
||||
ActionDeleteVendorService Action = "deleteVendorService"
|
||||
ActionDeleteAsset Action = "deleteAsset"
|
||||
ActionDeleteAudit Action = "deleteAudit"
|
||||
ActionDeleteAuditReport Action = "deleteAuditReport"
|
||||
ActionDeleteContinualImprovement Action = "deleteContinualImprovement"
|
||||
ActionDeleteRightsRequest Action = "deleteRightsRequest"
|
||||
ActionDeleteControl Action = "deleteControl"
|
||||
ActionDeleteControlAuditMapping Action = "deleteControlAuditMapping"
|
||||
ActionDeleteControlObligationMapping Action = "deleteControlObligationMapping"
|
||||
ActionDeleteControlDocumentMapping Action = "deleteControlDocumentMapping"
|
||||
ActionDeleteControlMeasureMapping Action = "deleteControlMeasureMapping"
|
||||
ActionDeleteControlSnapshotMapping Action = "deleteControlSnapshotMapping"
|
||||
ActionDeleteStateOfApplicabilityControlMapping Action = "deleteStateOfApplicabilityControlMapping"
|
||||
ActionDeleteCustomDomain Action = "deleteCustomDomain"
|
||||
ActionDeleteDatum Action = "deleteDatum"
|
||||
ActionDeleteDocument Action = "deleteDocument"
|
||||
ActionDeleteDraftDocumentVersion Action = "deleteDraftDocumentVersion"
|
||||
ActionDeleteEvidence Action = "deleteEvidence"
|
||||
ActionDeleteFramework Action = "deleteFramework"
|
||||
ActionDeleteInvitation Action = "deleteInvitation"
|
||||
ActionDeleteMeasure Action = "deleteMeasure"
|
||||
ActionDeleteMeeting Action = "deleteMeeting"
|
||||
ActionDeleteNonconformity Action = "deleteNonconformity"
|
||||
ActionDeleteObligation Action = "deleteObligation"
|
||||
ActionDeleteOrganization Action = "deleteOrganization"
|
||||
ActionDeleteOrganizationHorizontalLogo Action = "deleteOrganizationHorizontalLogo"
|
||||
ActionDeletePeople Action = "deletePeople"
|
||||
ActionDeleteProcessingActivity Action = "deleteProcessingActivity"
|
||||
ActionDeleteStateOfApplicability Action = "deleteStateOfApplicability"
|
||||
ActionDeleteDataProtectionImpactAssessment Action = "deleteDataProtectionImpactAssessment"
|
||||
ActionDeleteTransferImpactAssessment Action = "deleteTransferImpactAssessment"
|
||||
ActionDeleteRisk Action = "deleteRisk"
|
||||
ActionDeleteRiskDocumentMapping Action = "deleteRiskDocumentMapping"
|
||||
ActionDeleteRiskMeasureMapping Action = "deleteRiskMeasureMapping"
|
||||
ActionDeleteRiskObligationMapping Action = "deleteRiskObligationMapping"
|
||||
ActionDeleteSAMLConfiguration Action = "deleteSAMLConfiguration"
|
||||
ActionDeleteSnapshot Action = "deleteSnapshot"
|
||||
ActionDeleteTask Action = "deleteTask"
|
||||
ActionDeleteTrustCenterAccess Action = "deleteTrustCenterAccess"
|
||||
ActionDeleteTrustCenterFile Action = "deleteTrustCenterFile"
|
||||
ActionDeleteTrustCenterNDA Action = "deleteTrustCenterNDA"
|
||||
ActionDeleteTrustCenterReference Action = "deleteTrustCenterReference"
|
||||
ActionDeleteVendor Action = "deleteVendor"
|
||||
ActionDeleteVendorBusinessAssociateAgreement Action = "deleteVendorBusinessAssociateAgreement"
|
||||
ActionDeleteVendorComplianceReport Action = "deleteVendorComplianceReport"
|
||||
ActionDeleteVendorContact Action = "deleteVendorContact"
|
||||
ActionDeleteVendorDataPrivacyAgreement Action = "deleteVendorDataPrivacyAgreement"
|
||||
ActionDeleteVendorService Action = "deleteVendorService"
|
||||
|
||||
ActionAcceptInvitation Action = "acceptInvitation"
|
||||
ActionAssessVendor Action = "assessVendor"
|
||||
@@ -315,16 +323,17 @@ var Permissions = map[uint16]map[Action][]Role{
|
||||
ActionConfirmEmail: NonEmployeeRoles,
|
||||
ActionAcceptInvitation: NonEmployeeRoles,
|
||||
|
||||
ActionListTrustCenterFiles: CoreRoles,
|
||||
ActionGetTrustCenter: CoreRoles,
|
||||
ActionMemberships: CoreRoles,
|
||||
ActionListMembers: CoreRoles,
|
||||
ActionListInvitations: CoreRoles,
|
||||
ActionListSlackConnections: CoreRoles,
|
||||
ActionGetCustomDomain: CoreRoles,
|
||||
ActionListSAMLConfigurations: CoreRoles,
|
||||
ActionListMeetings: CoreRoles,
|
||||
ActionListTasks: CoreRoles,
|
||||
ActionListTrustCenterFiles: CoreRoles,
|
||||
ActionGetTrustCenter: CoreRoles,
|
||||
ActionMemberships: CoreRoles,
|
||||
ActionListMembers: CoreRoles,
|
||||
ActionListInvitations: CoreRoles,
|
||||
ActionListSlackConnections: CoreRoles,
|
||||
ActionGetCustomDomain: CoreRoles,
|
||||
ActionListSAMLConfigurations: CoreRoles,
|
||||
ActionListMeetings: CoreRoles,
|
||||
ActionListStatesOfApplicability: NonEmployeeRoles,
|
||||
ActionListTasks: CoreRoles,
|
||||
|
||||
ActionUpdateOrganization: EditRoles,
|
||||
ActionDeleteOrganizationHorizontalLogo: EditRoles,
|
||||
@@ -339,6 +348,7 @@ var Permissions = map[uint16]map[Action][]Role{
|
||||
ActionCreateMeasure: EditRoles,
|
||||
ActionImportMeasure: EditRoles,
|
||||
ActionCreateMeeting: EditRoles,
|
||||
ActionCreateStateOfApplicability: EditRoles,
|
||||
ActionCreateTask: EditRoles,
|
||||
ActionCreateRisk: EditRoles,
|
||||
ActionCreateDocument: EditRoles,
|
||||
@@ -498,23 +508,32 @@ var Permissions = map[uint16]map[Action][]Role{
|
||||
ActionExportFramework: EditRoles,
|
||||
},
|
||||
coredata.ControlEntityType: {
|
||||
ActionGet: NonEmployeeRoles,
|
||||
ActionGetFramework: NonEmployeeRoles,
|
||||
ActionListMeasures: NonEmployeeRoles,
|
||||
ActionListDocuments: NonEmployeeRoles,
|
||||
ActionListAudits: NonEmployeeRoles,
|
||||
ActionListSnapshots: NonEmployeeRoles,
|
||||
ActionGet: NonEmployeeRoles,
|
||||
ActionGetFramework: NonEmployeeRoles,
|
||||
ActionListMeasures: NonEmployeeRoles,
|
||||
ActionListDocuments: NonEmployeeRoles,
|
||||
ActionListAudits: NonEmployeeRoles,
|
||||
ActionListObligations: NonEmployeeRoles,
|
||||
ActionListSnapshots: NonEmployeeRoles,
|
||||
ActionListStatesOfApplicability: NonEmployeeRoles,
|
||||
|
||||
ActionUpdateControl: EditRoles,
|
||||
ActionDeleteControl: EditRoles,
|
||||
ActionCreateControlMeasureMapping: EditRoles,
|
||||
ActionCreateControlDocumentMapping: EditRoles,
|
||||
ActionDeleteControlMeasureMapping: EditRoles,
|
||||
ActionDeleteControlDocumentMapping: EditRoles,
|
||||
ActionCreateControlAuditMapping: EditRoles,
|
||||
ActionDeleteControlAuditMapping: EditRoles,
|
||||
ActionCreateControlSnapshotMapping: EditRoles,
|
||||
ActionDeleteControlSnapshotMapping: EditRoles,
|
||||
ActionUpdateControl: EditRoles,
|
||||
ActionDeleteControl: EditRoles,
|
||||
ActionCreateControlMeasureMapping: EditRoles,
|
||||
ActionCreateControlDocumentMapping: EditRoles,
|
||||
ActionDeleteControlMeasureMapping: EditRoles,
|
||||
ActionDeleteControlDocumentMapping: EditRoles,
|
||||
ActionCreateControlAuditMapping: EditRoles,
|
||||
ActionDeleteControlAuditMapping: EditRoles,
|
||||
ActionCreateControlObligationMapping: EditRoles,
|
||||
ActionDeleteControlObligationMapping: EditRoles,
|
||||
ActionCreateControlSnapshotMapping: EditRoles,
|
||||
ActionCreateStateOfApplicabilityControlMapping: EditRoles,
|
||||
ActionDeleteStateOfApplicabilityControlMapping: EditRoles,
|
||||
ActionDeleteControlSnapshotMapping: EditRoles,
|
||||
},
|
||||
coredata.StateOfApplicabilityControlEntityType: {
|
||||
ActionDeleteStateOfApplicabilityControlMapping: EditRoles,
|
||||
},
|
||||
coredata.MeasureEntityType: {
|
||||
ActionListTasks: CoreRoles,
|
||||
@@ -689,6 +708,7 @@ var Permissions = map[uint16]map[Action][]Role{
|
||||
coredata.RightsRequestEntityType: {
|
||||
ActionGet: NonEmployeeRoles,
|
||||
ActionGetOrganization: NonEmployeeRoles,
|
||||
ActionListControls: NonEmployeeRoles,
|
||||
|
||||
ActionUpdateRightsRequest: EditRoles,
|
||||
ActionDeleteRightsRequest: EditRoles,
|
||||
@@ -763,6 +783,16 @@ var Permissions = map[uint16]map[Action][]Role{
|
||||
ActionUpdateMeeting: EditRoles,
|
||||
ActionDeleteMeeting: EditRoles,
|
||||
},
|
||||
coredata.StateOfApplicabilityEntityType: {
|
||||
ActionGet: NonEmployeeRoles,
|
||||
ActionGetOrganization: NonEmployeeRoles,
|
||||
ActionListControls: NonEmployeeRoles,
|
||||
ActionTotalCount: NonEmployeeRoles,
|
||||
|
||||
ActionUpdateStateOfApplicability: EditRoles,
|
||||
ActionDeleteStateOfApplicability: EditRoles,
|
||||
ActionDeleteStateOfApplicabilityControlMapping: EditRoles,
|
||||
},
|
||||
}
|
||||
|
||||
func GetPermissionsForAction(entityType uint16, action Action) []Role {
|
||||
|
||||
@@ -38,6 +38,7 @@ type (
|
||||
Description *string `db:"description"`
|
||||
Status ControlStatus `db:"status"`
|
||||
ExclusionJustification *string `db:"exclusion_justification"`
|
||||
BestPractice bool `db:"best_practice"`
|
||||
CreatedAt time.Time `db:"created_at"`
|
||||
UpdatedAt time.Time `db:"updated_at"`
|
||||
}
|
||||
@@ -134,6 +135,7 @@ WITH ctrl AS (
|
||||
c.description,
|
||||
c.status,
|
||||
c.exclusion_justification,
|
||||
c.best_practice,
|
||||
c.created_at,
|
||||
c.updated_at,
|
||||
c.search_vector
|
||||
@@ -153,6 +155,7 @@ SELECT
|
||||
description,
|
||||
status,
|
||||
exclusion_justification,
|
||||
best_practice,
|
||||
created_at,
|
||||
updated_at
|
||||
FROM
|
||||
@@ -245,6 +248,7 @@ WITH ctrl AS (
|
||||
c.description,
|
||||
c.status,
|
||||
c.exclusion_justification,
|
||||
c.best_practice,
|
||||
c.created_at,
|
||||
c.updated_at,
|
||||
c.search_vector
|
||||
@@ -264,6 +268,7 @@ SELECT
|
||||
description,
|
||||
status,
|
||||
exclusion_justification,
|
||||
best_practice,
|
||||
created_at,
|
||||
updated_at
|
||||
FROM
|
||||
@@ -362,6 +367,7 @@ WITH ctrl AS (
|
||||
c.description,
|
||||
c.status,
|
||||
c.exclusion_justification,
|
||||
c.best_practice,
|
||||
c.created_at,
|
||||
c.updated_at,
|
||||
c.search_vector
|
||||
@@ -387,6 +393,7 @@ SELECT
|
||||
description,
|
||||
status,
|
||||
exclusion_justification,
|
||||
best_practice,
|
||||
created_at,
|
||||
updated_at
|
||||
FROM
|
||||
@@ -467,6 +474,7 @@ SELECT
|
||||
description,
|
||||
status,
|
||||
exclusion_justification,
|
||||
best_practice,
|
||||
created_at,
|
||||
updated_at
|
||||
FROM
|
||||
@@ -562,6 +570,7 @@ WITH ctrl AS (
|
||||
c.description,
|
||||
c.status,
|
||||
c.exclusion_justification,
|
||||
c.best_practice,
|
||||
c.created_at,
|
||||
c.updated_at,
|
||||
c.search_vector
|
||||
@@ -581,6 +590,7 @@ SELECT
|
||||
description,
|
||||
status,
|
||||
exclusion_justification,
|
||||
best_practice,
|
||||
created_at,
|
||||
updated_at
|
||||
FROM
|
||||
@@ -628,6 +638,7 @@ SELECT
|
||||
description,
|
||||
status,
|
||||
exclusion_justification,
|
||||
best_practice,
|
||||
created_at,
|
||||
updated_at
|
||||
FROM
|
||||
@@ -677,6 +688,7 @@ SELECT
|
||||
description,
|
||||
status,
|
||||
exclusion_justification,
|
||||
best_practice,
|
||||
created_at,
|
||||
updated_at
|
||||
FROM
|
||||
@@ -726,6 +738,7 @@ INSERT INTO
|
||||
description,
|
||||
status,
|
||||
exclusion_justification,
|
||||
best_practice,
|
||||
created_at,
|
||||
updated_at
|
||||
)
|
||||
@@ -739,6 +752,7 @@ VALUES (
|
||||
@description,
|
||||
@status,
|
||||
@exclusion_justification,
|
||||
@best_practice,
|
||||
@created_at,
|
||||
@updated_at
|
||||
);
|
||||
@@ -754,6 +768,7 @@ VALUES (
|
||||
"description": c.Description,
|
||||
"status": c.Status,
|
||||
"exclusion_justification": c.ExclusionJustification,
|
||||
"best_practice": c.BestPractice,
|
||||
"created_at": c.CreatedAt,
|
||||
"updated_at": c.UpdatedAt,
|
||||
}
|
||||
@@ -808,6 +823,7 @@ UPDATE controls SET
|
||||
section_title = @section_title,
|
||||
status = @status,
|
||||
exclusion_justification = @exclusion_justification,
|
||||
best_practice = @best_practice,
|
||||
updated_at = @updated_at
|
||||
WHERE %s
|
||||
AND id = @control_id
|
||||
@@ -821,6 +837,7 @@ WHERE %s
|
||||
"section_title": c.SectionTitle,
|
||||
"status": c.Status,
|
||||
"exclusion_justification": c.ExclusionJustification,
|
||||
"best_practice": c.BestPractice,
|
||||
"updated_at": c.UpdatedAt,
|
||||
}
|
||||
|
||||
@@ -905,6 +922,7 @@ WITH ctrl AS (
|
||||
c.description,
|
||||
c.status,
|
||||
c.exclusion_justification,
|
||||
c.best_practice,
|
||||
c.created_at,
|
||||
c.updated_at,
|
||||
c.search_vector
|
||||
@@ -924,6 +942,7 @@ SELECT
|
||||
description,
|
||||
status,
|
||||
exclusion_justification,
|
||||
best_practice,
|
||||
created_at,
|
||||
updated_at
|
||||
FROM
|
||||
@@ -1017,6 +1036,7 @@ WITH ctrl AS (
|
||||
c.description,
|
||||
c.status,
|
||||
c.exclusion_justification,
|
||||
c.best_practice,
|
||||
c.created_at,
|
||||
c.updated_at,
|
||||
c.search_vector
|
||||
@@ -1036,6 +1056,7 @@ SELECT
|
||||
description,
|
||||
status,
|
||||
exclusion_justification,
|
||||
best_practice,
|
||||
created_at,
|
||||
updated_at
|
||||
FROM
|
||||
@@ -1065,3 +1086,117 @@ WHERE %s
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Controls) CountByStateOfApplicabilityID(
|
||||
ctx context.Context,
|
||||
conn pg.Conn,
|
||||
scope Scoper,
|
||||
stateOfApplicabilityID gid.GID,
|
||||
filter *ControlFilter,
|
||||
) (int, error) {
|
||||
q := `
|
||||
WITH ctrl AS (
|
||||
SELECT
|
||||
c.id,
|
||||
c.tenant_id,
|
||||
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
|
||||
COUNT(id)
|
||||
FROM
|
||||
ctrl
|
||||
WHERE %s
|
||||
AND %s
|
||||
`
|
||||
q = fmt.Sprintf(q, scope.SQLFragment(), filter.SQLFragment())
|
||||
|
||||
args := pgx.NamedArgs{"state_of_applicability_id": stateOfApplicabilityID}
|
||||
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 (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
|
||||
}
|
||||
|
||||
@@ -136,3 +136,56 @@ WHERE
|
||||
*cms = controlMeasures
|
||||
return nil
|
||||
}
|
||||
|
||||
type ControlWithRisk struct {
|
||||
ControlID gid.GID `db:"control_id"`
|
||||
}
|
||||
|
||||
type ControlsWithRisk []*ControlWithRisk
|
||||
|
||||
func (cwrs *ControlsWithRisk) LoadByControlIDs(
|
||||
ctx context.Context,
|
||||
conn pg.Conn,
|
||||
scope Scoper,
|
||||
controlIDs []gid.GID,
|
||||
) error {
|
||||
q := `
|
||||
WITH control_risks AS (
|
||||
SELECT DISTINCT
|
||||
cm.control_id,
|
||||
rm.risk_id,
|
||||
r.tenant_id
|
||||
FROM
|
||||
controls_measures cm
|
||||
INNER JOIN
|
||||
risks_measures rm ON cm.measure_id = rm.measure_id
|
||||
INNER JOIN
|
||||
risks r ON rm.risk_id = r.id
|
||||
WHERE
|
||||
cm.control_id = ANY(@control_ids)
|
||||
)
|
||||
SELECT DISTINCT
|
||||
control_id
|
||||
FROM
|
||||
control_risks
|
||||
WHERE
|
||||
%s
|
||||
`
|
||||
q = fmt.Sprintf(q, scope.SQLFragment())
|
||||
|
||||
args := pgx.NamedArgs{"control_ids": controlIDs}
|
||||
maps.Copy(args, scope.SQLArguments())
|
||||
|
||||
rows, err := conn.Query(ctx, q, args)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot query control risks: %w", err)
|
||||
}
|
||||
|
||||
controlsWithRisk, err := pgx.CollectRows(rows, pgx.RowToAddrOfStructByName[ControlWithRisk])
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot collect control risks: %w", err)
|
||||
}
|
||||
|
||||
*cwrs = controlsWithRisk
|
||||
return nil
|
||||
}
|
||||
|
||||
221
pkg/coredata/control_obligation.go
Normal file
221
pkg/coredata/control_obligation.go
Normal file
@@ -0,0 +1,221 @@
|
||||
// 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 (
|
||||
"context"
|
||||
"fmt"
|
||||
"maps"
|
||||
"time"
|
||||
|
||||
"github.com/jackc/pgx/v5"
|
||||
"go.gearno.de/kit/pg"
|
||||
"go.probo.inc/probo/pkg/gid"
|
||||
)
|
||||
|
||||
type (
|
||||
ControlObligation struct {
|
||||
ControlID gid.GID `db:"control_id"`
|
||||
ObligationID gid.GID `db:"obligation_id"`
|
||||
CreatedAt time.Time `db:"created_at"`
|
||||
}
|
||||
|
||||
ControlObligations []*ControlObligation
|
||||
)
|
||||
|
||||
func (co ControlObligation) Upsert(
|
||||
ctx context.Context,
|
||||
conn pg.Conn,
|
||||
scope Scoper,
|
||||
) error {
|
||||
q := `
|
||||
INSERT INTO
|
||||
controls_obligations (
|
||||
control_id,
|
||||
obligation_id,
|
||||
tenant_id,
|
||||
created_at
|
||||
)
|
||||
VALUES (
|
||||
@control_id,
|
||||
@obligation_id,
|
||||
@tenant_id,
|
||||
@created_at
|
||||
)
|
||||
ON CONFLICT (control_id, obligation_id) DO NOTHING;
|
||||
`
|
||||
|
||||
args := pgx.StrictNamedArgs{
|
||||
"control_id": co.ControlID,
|
||||
"obligation_id": co.ObligationID,
|
||||
"tenant_id": scope.GetTenantID(),
|
||||
"created_at": co.CreatedAt,
|
||||
}
|
||||
_, err := conn.Exec(ctx, q, args)
|
||||
return err
|
||||
}
|
||||
|
||||
func (co ControlObligation) Delete(
|
||||
ctx context.Context,
|
||||
conn pg.Conn,
|
||||
scope Scoper,
|
||||
controlID gid.GID,
|
||||
obligationID gid.GID,
|
||||
) error {
|
||||
q := `
|
||||
DELETE
|
||||
FROM
|
||||
controls_obligations
|
||||
WHERE
|
||||
%s
|
||||
AND control_id = @control_id
|
||||
AND obligation_id = @obligation_id;
|
||||
`
|
||||
|
||||
args := pgx.StrictNamedArgs{
|
||||
"control_id": controlID,
|
||||
"obligation_id": obligationID,
|
||||
}
|
||||
maps.Copy(args, scope.SQLArguments())
|
||||
q = fmt.Sprintf(q, scope.SQLFragment())
|
||||
|
||||
_, err := conn.Exec(ctx, q, args)
|
||||
return err
|
||||
}
|
||||
|
||||
func (cos *ControlObligations) LoadByControlID(
|
||||
ctx context.Context,
|
||||
conn pg.Conn,
|
||||
scope Scoper,
|
||||
controlID gid.GID,
|
||||
) error {
|
||||
q := `
|
||||
SELECT
|
||||
control_id,
|
||||
obligation_id,
|
||||
created_at
|
||||
FROM
|
||||
controls_obligations
|
||||
WHERE
|
||||
%s
|
||||
AND control_id = @control_id
|
||||
`
|
||||
q = fmt.Sprintf(q, scope.SQLFragment())
|
||||
|
||||
args := pgx.StrictNamedArgs{"control_id": controlID}
|
||||
maps.Copy(args, scope.SQLArguments())
|
||||
|
||||
rows, err := conn.Query(ctx, q, args)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot query control_obligations: %w", err)
|
||||
}
|
||||
|
||||
controlObligations, err := pgx.CollectRows(rows, pgx.RowToAddrOfStructByName[ControlObligation])
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot collect control_obligations: %w", err)
|
||||
}
|
||||
|
||||
*cos = controlObligations
|
||||
return nil
|
||||
}
|
||||
|
||||
func (cos *ControlObligations) LoadByObligationID(
|
||||
ctx context.Context,
|
||||
conn pg.Conn,
|
||||
scope Scoper,
|
||||
obligationID gid.GID,
|
||||
) error {
|
||||
q := `
|
||||
SELECT
|
||||
control_id,
|
||||
obligation_id,
|
||||
created_at
|
||||
FROM
|
||||
controls_obligations
|
||||
WHERE
|
||||
%s
|
||||
AND obligation_id = @obligation_id
|
||||
`
|
||||
q = fmt.Sprintf(q, scope.SQLFragment())
|
||||
|
||||
args := pgx.StrictNamedArgs{"obligation_id": obligationID}
|
||||
maps.Copy(args, scope.SQLArguments())
|
||||
|
||||
rows, err := conn.Query(ctx, q, args)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot query control_obligations: %w", err)
|
||||
}
|
||||
|
||||
controlObligations, err := pgx.CollectRows(rows, pgx.RowToAddrOfStructByName[ControlObligation])
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot collect control_obligations: %w", err)
|
||||
}
|
||||
|
||||
*cos = controlObligations
|
||||
return nil
|
||||
}
|
||||
|
||||
type ControlObligationType struct {
|
||||
ControlID gid.GID `db:"control_id"`
|
||||
Type ObligationType `db:"type"`
|
||||
}
|
||||
|
||||
type ControlObligationTypes []*ControlObligationType
|
||||
|
||||
func (cots *ControlObligationTypes) LoadTypesByControlIDs(
|
||||
ctx context.Context,
|
||||
conn pg.Conn,
|
||||
scope Scoper,
|
||||
controlIDs []gid.GID,
|
||||
) error {
|
||||
q := `
|
||||
WITH control_obls AS (
|
||||
SELECT
|
||||
co.control_id,
|
||||
o.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 DISTINCT
|
||||
control_id,
|
||||
type
|
||||
FROM
|
||||
control_obls
|
||||
WHERE
|
||||
%s
|
||||
`
|
||||
q = fmt.Sprintf(q, scope.SQLFragment())
|
||||
|
||||
args := pgx.NamedArgs{"control_ids": controlIDs}
|
||||
maps.Copy(args, scope.SQLArguments())
|
||||
|
||||
rows, err := conn.Query(ctx, q, args)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot query 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
|
||||
}
|
||||
@@ -70,6 +70,8 @@ const (
|
||||
DataProtectionImpactAssessmentEntityType uint16 = 46
|
||||
TransferImpactAssessmentEntityType uint16 = 47
|
||||
RightsRequestEntityType uint16 = 48
|
||||
StateOfApplicabilityEntityType uint16 = 49
|
||||
StateOfApplicabilityControlEntityType uint16 = 50
|
||||
)
|
||||
|
||||
type EntityInfo struct {
|
||||
@@ -274,6 +276,14 @@ var entityRegistry = map[uint16]EntityInfo{
|
||||
Model: "RightsRequest",
|
||||
Table: "rights_requests",
|
||||
},
|
||||
StateOfApplicabilityEntityType: {
|
||||
Model: "StateOfApplicability",
|
||||
Table: "states_of_applicability",
|
||||
},
|
||||
StateOfApplicabilityControlEntityType: {
|
||||
Model: "StateOfApplicabilityControl",
|
||||
Table: "states_of_applicability_controls",
|
||||
},
|
||||
}
|
||||
|
||||
func EntityTable(entityType uint16) (string, bool) {
|
||||
|
||||
161
pkg/coredata/migrations/20260102T134633Z.sql
Normal file
161
pkg/coredata/migrations/20260102T134633Z.sql
Normal file
@@ -0,0 +1,161 @@
|
||||
CREATE TABLE states_of_applicability (
|
||||
id TEXT PRIMARY KEY,
|
||||
tenant_id TEXT NOT NULL,
|
||||
organization_id TEXT NOT NULL,
|
||||
name TEXT NOT NULL,
|
||||
source_id TEXT,
|
||||
snapshot_id TEXT,
|
||||
owner_id TEXT NOT NULL,
|
||||
created_at TIMESTAMP WITH TIME ZONE NOT NULL,
|
||||
updated_at TIMESTAMP WITH TIME ZONE NOT NULL,
|
||||
|
||||
CONSTRAINT states_of_applicability_organization_id_fkey
|
||||
FOREIGN KEY (organization_id)
|
||||
REFERENCES organizations(id)
|
||||
ON UPDATE CASCADE
|
||||
ON DELETE CASCADE,
|
||||
|
||||
CONSTRAINT states_of_applicability_snapshot_id_fkey
|
||||
FOREIGN KEY (snapshot_id)
|
||||
REFERENCES snapshots(id)
|
||||
ON UPDATE CASCADE
|
||||
ON DELETE CASCADE,
|
||||
|
||||
CONSTRAINT states_of_applicability_owner_id_fkey
|
||||
FOREIGN KEY (owner_id)
|
||||
REFERENCES peoples(id)
|
||||
ON UPDATE CASCADE
|
||||
ON DELETE CASCADE
|
||||
);
|
||||
|
||||
CREATE UNIQUE INDEX states_of_applicability_source_id_snapshot_id_uniq
|
||||
ON states_of_applicability (source_id, snapshot_id)
|
||||
WHERE snapshot_id IS NULL;
|
||||
|
||||
CREATE UNIQUE INDEX states_of_applicability_name_organization_id_uniq
|
||||
ON states_of_applicability (name, organization_id)
|
||||
WHERE snapshot_id IS NULL;
|
||||
|
||||
CREATE TABLE states_of_applicability_controls (
|
||||
id TEXT PRIMARY KEY,
|
||||
state_of_applicability_id TEXT NOT NULL REFERENCES states_of_applicability(id) ON DELETE CASCADE ON UPDATE CASCADE,
|
||||
control_id TEXT NOT NULL REFERENCES controls(id) ON DELETE RESTRICT ON UPDATE CASCADE,
|
||||
organization_id TEXT NOT NULL,
|
||||
tenant_id TEXT NOT NULL,
|
||||
snapshot_id TEXT,
|
||||
applicability BOOLEAN NOT NULL,
|
||||
justification TEXT,
|
||||
created_at TIMESTAMP WITH TIME ZONE NOT NULL,
|
||||
updated_at TIMESTAMP WITH TIME ZONE NOT NULL,
|
||||
|
||||
CONSTRAINT states_of_applicability_controls_organization_id_fkey
|
||||
FOREIGN KEY (organization_id)
|
||||
REFERENCES organizations(id)
|
||||
ON UPDATE CASCADE
|
||||
ON DELETE CASCADE,
|
||||
CONSTRAINT states_of_applicability_controls_snapshot_id_fkey
|
||||
FOREIGN KEY (snapshot_id)
|
||||
REFERENCES snapshots(id)
|
||||
ON UPDATE CASCADE
|
||||
ON DELETE CASCADE,
|
||||
|
||||
UNIQUE (state_of_applicability_id, control_id)
|
||||
);
|
||||
|
||||
ALTER TABLE controls ADD COLUMN best_practice BOOLEAN NOT NULL DEFAULT TRUE;
|
||||
ALTER TABLE controls ALTER COLUMN best_practice DROP DEFAULT;
|
||||
|
||||
ALTER TYPE snapshots_type ADD VALUE 'STATES_OF_APPLICABILITY';
|
||||
|
||||
CREATE TYPE obligation_type AS ENUM (
|
||||
'LEGAL',
|
||||
'CONTRACTUAL'
|
||||
);
|
||||
|
||||
ALTER TABLE obligations ADD COLUMN type obligation_type NOT NULL DEFAULT 'LEGAL';
|
||||
ALTER TABLE obligations ALTER COLUMN type DROP DEFAULT;
|
||||
|
||||
CREATE TABLE controls_obligations (
|
||||
control_id TEXT NOT NULL REFERENCES controls(id) ON DELETE CASCADE ON UPDATE CASCADE,
|
||||
obligation_id TEXT NOT NULL REFERENCES obligations(id) ON DELETE CASCADE ON UPDATE CASCADE,
|
||||
tenant_id TEXT NOT NULL,
|
||||
created_at TIMESTAMP WITH TIME ZONE NOT NULL,
|
||||
PRIMARY KEY (control_id, obligation_id)
|
||||
);
|
||||
|
||||
INSERT INTO states_of_applicability (
|
||||
id,
|
||||
tenant_id,
|
||||
organization_id,
|
||||
name,
|
||||
source_id,
|
||||
snapshot_id,
|
||||
owner_id,
|
||||
created_at,
|
||||
updated_at
|
||||
)
|
||||
SELECT
|
||||
generate_gid(decode_base64_unpadded(f.tenant_id), 49) as id,
|
||||
f.tenant_id,
|
||||
f.organization_id,
|
||||
f.name,
|
||||
NULL as source_id,
|
||||
NULL as snapshot_id,
|
||||
(SELECT id FROM peoples WHERE tenant_id = f.tenant_id LIMIT 1) as owner_id,
|
||||
NOW() as created_at,
|
||||
NOW() as updated_at
|
||||
FROM frameworks f
|
||||
WHERE NOT EXISTS (
|
||||
SELECT 1
|
||||
FROM states_of_applicability soa
|
||||
WHERE soa.name = f.name
|
||||
AND soa.snapshot_id IS NULL
|
||||
)
|
||||
AND EXISTS (
|
||||
SELECT 1 FROM peoples WHERE tenant_id = f.tenant_id
|
||||
)
|
||||
AND EXISTS (
|
||||
SELECT 1
|
||||
FROM controls c
|
||||
WHERE c.framework_id = f.id
|
||||
AND (
|
||||
c.status = 'EXCLUDED'
|
||||
OR (c.exclusion_justification IS NOT NULL AND c.exclusion_justification != '')
|
||||
)
|
||||
);
|
||||
|
||||
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
|
||||
)
|
||||
SELECT
|
||||
generate_gid(decode_base64_unpadded(c.tenant_id), 50) as id,
|
||||
soa.id as state_of_applicability_id,
|
||||
c.id as control_id,
|
||||
c.organization_id,
|
||||
c.tenant_id,
|
||||
NULL as snapshot_id,
|
||||
CASE
|
||||
WHEN c.status = 'EXCLUDED' THEN FALSE
|
||||
ELSE TRUE
|
||||
END as applicability,
|
||||
c.exclusion_justification,
|
||||
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 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
|
||||
);
|
||||
@@ -20,10 +20,10 @@ import (
|
||||
"maps"
|
||||
"time"
|
||||
|
||||
"go.probo.inc/probo/pkg/gid"
|
||||
"go.probo.inc/probo/pkg/page"
|
||||
"github.com/jackc/pgx/v5"
|
||||
"go.gearno.de/kit/pg"
|
||||
"go.probo.inc/probo/pkg/gid"
|
||||
"go.probo.inc/probo/pkg/page"
|
||||
)
|
||||
|
||||
type (
|
||||
@@ -39,6 +39,7 @@ type (
|
||||
LastReviewDate *time.Time `db:"last_review_date"`
|
||||
DueDate *time.Time `db:"due_date"`
|
||||
Status ObligationStatus `db:"status"`
|
||||
Type ObligationType `db:"type"`
|
||||
SnapshotID *gid.GID `db:"snapshot_id"`
|
||||
SourceID *gid.GID `db:"source_id"`
|
||||
CreatedAt time.Time `db:"created_at"`
|
||||
@@ -84,6 +85,7 @@ SELECT
|
||||
last_review_date,
|
||||
due_date,
|
||||
status,
|
||||
type,
|
||||
created_at,
|
||||
updated_at
|
||||
FROM
|
||||
@@ -216,6 +218,7 @@ SELECT
|
||||
last_review_date,
|
||||
due_date,
|
||||
status,
|
||||
type,
|
||||
snapshot_id,
|
||||
source_id,
|
||||
created_at,
|
||||
@@ -273,6 +276,7 @@ WITH obls AS (
|
||||
o.last_review_date,
|
||||
o.due_date,
|
||||
o.status,
|
||||
o.type,
|
||||
o.snapshot_id,
|
||||
o.source_id,
|
||||
o.created_at,
|
||||
@@ -298,6 +302,7 @@ SELECT
|
||||
last_review_date,
|
||||
due_date,
|
||||
status,
|
||||
type,
|
||||
snapshot_id,
|
||||
source_id,
|
||||
created_at,
|
||||
@@ -331,6 +336,131 @@ WHERE %s
|
||||
return nil
|
||||
}
|
||||
|
||||
func (os *Obligations) CountByControlID(
|
||||
ctx context.Context,
|
||||
conn pg.Conn,
|
||||
scope Scoper,
|
||||
controlID gid.GID,
|
||||
filter *ObligationFilter,
|
||||
) (int, error) {
|
||||
q := `
|
||||
WITH obls AS (
|
||||
SELECT
|
||||
o.id,
|
||||
o.tenant_id,
|
||||
o.snapshot_id
|
||||
FROM
|
||||
obligations o
|
||||
INNER JOIN
|
||||
controls_obligations co ON o.id = co.obligation_id
|
||||
WHERE
|
||||
co.control_id = @control_id
|
||||
)
|
||||
SELECT
|
||||
COUNT(id)
|
||||
FROM
|
||||
obls
|
||||
WHERE %s
|
||||
AND %s
|
||||
`
|
||||
|
||||
q = fmt.Sprintf(q, scope.SQLFragment(), filter.SQLFragment())
|
||||
|
||||
args := pgx.StrictNamedArgs{"control_id": controlID}
|
||||
maps.Copy(args, scope.SQLArguments())
|
||||
maps.Copy(args, filter.SQLArguments())
|
||||
|
||||
row := conn.QueryRow(ctx, q, args)
|
||||
|
||||
var count int
|
||||
err := row.Scan(&count)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("cannot count obligations: %w", err)
|
||||
}
|
||||
|
||||
return count, nil
|
||||
}
|
||||
|
||||
func (os *Obligations) LoadByControlID(
|
||||
ctx context.Context,
|
||||
conn pg.Conn,
|
||||
scope Scoper,
|
||||
controlID gid.GID,
|
||||
cursor *page.Cursor[ObligationOrderField],
|
||||
filter *ObligationFilter,
|
||||
) error {
|
||||
q := `
|
||||
WITH obls AS (
|
||||
SELECT
|
||||
o.id,
|
||||
o.organization_id,
|
||||
o.area,
|
||||
o.source,
|
||||
o.requirement,
|
||||
o.actions_to_be_implemented,
|
||||
o.regulator,
|
||||
o.owner_id,
|
||||
o.last_review_date,
|
||||
o.due_date,
|
||||
o.status,
|
||||
o.type,
|
||||
o.snapshot_id,
|
||||
o.source_id,
|
||||
o.created_at,
|
||||
o.updated_at,
|
||||
o.tenant_id
|
||||
FROM
|
||||
obligations o
|
||||
INNER JOIN
|
||||
controls_obligations co ON o.id = co.obligation_id
|
||||
WHERE
|
||||
co.control_id = @control_id
|
||||
)
|
||||
SELECT
|
||||
id,
|
||||
organization_id,
|
||||
area,
|
||||
source,
|
||||
requirement,
|
||||
actions_to_be_implemented,
|
||||
regulator,
|
||||
owner_id,
|
||||
last_review_date,
|
||||
due_date,
|
||||
status,
|
||||
type,
|
||||
snapshot_id,
|
||||
source_id,
|
||||
created_at,
|
||||
updated_at
|
||||
FROM
|
||||
obls
|
||||
WHERE %s
|
||||
AND %s
|
||||
AND %s
|
||||
`
|
||||
q = fmt.Sprintf(q, scope.SQLFragment(), filter.SQLFragment(), cursor.SQLFragment())
|
||||
|
||||
args := pgx.NamedArgs{"control_id": controlID}
|
||||
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 obligations: %w", err)
|
||||
}
|
||||
|
||||
obligations, err := pgx.CollectRows(rows, pgx.RowToAddrOfStructByName[Obligation])
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot collect obligations: %w", err)
|
||||
}
|
||||
|
||||
*os = obligations
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (o *Obligation) Insert(
|
||||
ctx context.Context,
|
||||
conn pg.Conn,
|
||||
@@ -350,6 +480,7 @@ INSERT INTO obligations (
|
||||
last_review_date,
|
||||
due_date,
|
||||
status,
|
||||
type,
|
||||
snapshot_id,
|
||||
source_id,
|
||||
created_at,
|
||||
@@ -367,6 +498,7 @@ INSERT INTO obligations (
|
||||
@last_review_date,
|
||||
@due_date,
|
||||
@status,
|
||||
@type,
|
||||
@snapshot_id,
|
||||
@source_id,
|
||||
@created_at,
|
||||
@@ -387,6 +519,7 @@ INSERT INTO obligations (
|
||||
"last_review_date": o.LastReviewDate,
|
||||
"due_date": o.DueDate,
|
||||
"status": o.Status,
|
||||
"type": o.Type,
|
||||
"snapshot_id": o.SnapshotID,
|
||||
"source_id": o.SourceID,
|
||||
"created_at": o.CreatedAt,
|
||||
@@ -417,6 +550,7 @@ UPDATE obligations SET
|
||||
last_review_date = @last_review_date,
|
||||
due_date = @due_date,
|
||||
status = @status,
|
||||
type = @type,
|
||||
updated_at = @updated_at
|
||||
WHERE
|
||||
%s
|
||||
@@ -437,6 +571,7 @@ WHERE
|
||||
"last_review_date": o.LastReviewDate,
|
||||
"due_date": o.DueDate,
|
||||
"status": o.Status,
|
||||
"type": o.Type,
|
||||
"updated_at": o.UpdatedAt,
|
||||
}
|
||||
maps.Copy(args, scope.SQLArguments())
|
||||
@@ -492,6 +627,7 @@ INSERT INTO obligations (
|
||||
last_review_date,
|
||||
due_date,
|
||||
status,
|
||||
type,
|
||||
created_at,
|
||||
updated_at
|
||||
)
|
||||
@@ -510,6 +646,7 @@ SELECT
|
||||
o.last_review_date,
|
||||
o.due_date,
|
||||
o.status,
|
||||
o.type,
|
||||
o.created_at,
|
||||
o.updated_at
|
||||
FROM obligations o
|
||||
|
||||
64
pkg/coredata/obligation_type.go
Normal file
64
pkg/coredata/obligation_type.go
Normal file
@@ -0,0 +1,64 @@
|
||||
// Copyright (c) 2025 Probo Inc <hello@getprobo.com>.
|
||||
//
|
||||
// Permission to use, copy, modify, and/or distribute this software for any
|
||||
// purpose with or without fee is hereby granted, provided that the above
|
||||
// copyright notice and this permission notice appear in all copies.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
||||
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||
// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
||||
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
||||
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
||||
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||
// PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
package coredata
|
||||
|
||||
import (
|
||||
"database/sql/driver"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
type ObligationType string
|
||||
|
||||
const (
|
||||
ObligationTypeLegal ObligationType = "LEGAL"
|
||||
ObligationTypeContractual ObligationType = "CONTRACTUAL"
|
||||
)
|
||||
|
||||
func ObligationTypes() []ObligationType {
|
||||
return []ObligationType{
|
||||
ObligationTypeLegal,
|
||||
ObligationTypeContractual,
|
||||
}
|
||||
}
|
||||
|
||||
func (ot ObligationType) String() string {
|
||||
return string(ot)
|
||||
}
|
||||
|
||||
func (ot *ObligationType) 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 ObligationType: %T", value)
|
||||
}
|
||||
|
||||
switch s {
|
||||
case "LEGAL":
|
||||
*ot = ObligationTypeLegal
|
||||
case "CONTRACTUAL":
|
||||
*ot = ObligationTypeContractual
|
||||
default:
|
||||
return fmt.Errorf("invalid ObligationType value: %q", s)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ot ObligationType) Value() (driver.Value, error) {
|
||||
return ot.String(), nil
|
||||
}
|
||||
@@ -32,6 +32,7 @@ const (
|
||||
SnapshotsTypeObligations SnapshotsType = "OBLIGATIONS"
|
||||
SnapshotsTypeContinualImprovements SnapshotsType = "CONTINUAL_IMPROVEMENTS"
|
||||
SnapshotsTypeProcessingActivities SnapshotsType = "PROCESSING_ACTIVITIES"
|
||||
SnapshotsTypeStatesOfApplicability SnapshotsType = "STATES_OF_APPLICABILITY"
|
||||
)
|
||||
|
||||
func SnapshotsTypes() []SnapshotsType {
|
||||
@@ -41,6 +42,10 @@ func SnapshotsTypes() []SnapshotsType {
|
||||
SnapshotsTypeAssets,
|
||||
SnapshotsTypeData,
|
||||
SnapshotsTypeNonconformities,
|
||||
SnapshotsTypeObligations,
|
||||
SnapshotsTypeContinualImprovements,
|
||||
SnapshotsTypeProcessingActivities,
|
||||
SnapshotsTypeStatesOfApplicability,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -76,6 +81,8 @@ func (st *SnapshotsType) Scan(value any) error {
|
||||
*st = SnapshotsTypeContinualImprovements
|
||||
case SnapshotsTypeProcessingActivities.String():
|
||||
*st = SnapshotsTypeProcessingActivities
|
||||
case SnapshotsTypeStatesOfApplicability.String():
|
||||
*st = SnapshotsTypeStatesOfApplicability
|
||||
default:
|
||||
return fmt.Errorf("invalid SnapshotsType value: %q", s)
|
||||
}
|
||||
|
||||
@@ -18,8 +18,8 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"go.probo.inc/probo/pkg/gid"
|
||||
"go.gearno.de/kit/pg"
|
||||
"go.probo.inc/probo/pkg/gid"
|
||||
)
|
||||
|
||||
type Snapshottable interface {
|
||||
@@ -44,6 +44,8 @@ func GetSnapshottable(snapshotType SnapshotsType) (Snapshottable, error) {
|
||||
return ProcessingActivities{}, nil
|
||||
case SnapshotsTypeVendors:
|
||||
return Vendors{}, nil
|
||||
case SnapshotsTypeStatesOfApplicability:
|
||||
return StatesOfApplicability{}, nil
|
||||
default:
|
||||
return nil, fmt.Errorf("unsupported snapshot type: %s", snapshotType)
|
||||
}
|
||||
|
||||
466
pkg/coredata/state_of_applicability.go
Normal file
466
pkg/coredata/state_of_applicability.go
Normal file
@@ -0,0 +1,466 @@
|
||||
// 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 (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"maps"
|
||||
"time"
|
||||
|
||||
"github.com/jackc/pgx/v5"
|
||||
"github.com/jackc/pgx/v5/pgconn"
|
||||
"go.gearno.de/kit/pg"
|
||||
"go.probo.inc/probo/pkg/gid"
|
||||
"go.probo.inc/probo/pkg/page"
|
||||
)
|
||||
|
||||
type (
|
||||
StateOfApplicability struct {
|
||||
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_id"`
|
||||
CreatedAt time.Time `db:"created_at"`
|
||||
UpdatedAt time.Time `db:"updated_at"`
|
||||
}
|
||||
|
||||
StatesOfApplicability []*StateOfApplicability
|
||||
|
||||
ErrStateOfApplicabilityNotFound struct {
|
||||
Identifier string
|
||||
}
|
||||
|
||||
ErrStateOfApplicabilityAlreadyExists struct {
|
||||
message string
|
||||
}
|
||||
)
|
||||
|
||||
func (e ErrStateOfApplicabilityNotFound) Error() string {
|
||||
return fmt.Sprintf("state of applicability not found: %s", e.Identifier)
|
||||
}
|
||||
|
||||
func (e ErrStateOfApplicabilityAlreadyExists) Error() string {
|
||||
return e.message
|
||||
}
|
||||
|
||||
func (s StateOfApplicability) CursorKey(orderBy StateOfApplicabilityOrderField) page.CursorKey {
|
||||
switch orderBy {
|
||||
case StateOfApplicabilityOrderFieldCreatedAt:
|
||||
return page.NewCursorKey(s.ID, s.CreatedAt)
|
||||
case StateOfApplicabilityOrderFieldName:
|
||||
return page.NewCursorKey(s.ID, s.Name)
|
||||
}
|
||||
|
||||
panic(fmt.Sprintf("unsupported order by: %s", orderBy))
|
||||
}
|
||||
|
||||
func (s *StateOfApplicability) LoadByID(
|
||||
ctx context.Context,
|
||||
conn pg.Conn,
|
||||
scope Scoper,
|
||||
stateOfApplicabilityID gid.GID,
|
||||
) error {
|
||||
q := `
|
||||
SELECT
|
||||
id,
|
||||
organization_id,
|
||||
name,
|
||||
source_id,
|
||||
snapshot_id,
|
||||
owner_id,
|
||||
created_at,
|
||||
updated_at
|
||||
FROM
|
||||
states_of_applicability
|
||||
WHERE
|
||||
%s
|
||||
AND id = @state_of_applicability_id
|
||||
LIMIT 1;
|
||||
`
|
||||
|
||||
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 states_of_applicability: %w", err)
|
||||
}
|
||||
|
||||
stateOfApplicability, err := pgx.CollectExactlyOneRow(rows, pgx.RowToStructByName[StateOfApplicability])
|
||||
if err != nil {
|
||||
if errors.Is(err, pgx.ErrNoRows) {
|
||||
return &ErrStateOfApplicabilityNotFound{Identifier: stateOfApplicabilityID.String()}
|
||||
}
|
||||
|
||||
return fmt.Errorf("cannot collect state_of_applicability: %w", err)
|
||||
}
|
||||
|
||||
*s = stateOfApplicability
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *StatesOfApplicability) LoadByOrganizationID(
|
||||
ctx context.Context,
|
||||
conn pg.Conn,
|
||||
scope Scoper,
|
||||
organizationID gid.GID,
|
||||
cursor *page.Cursor[StateOfApplicabilityOrderField],
|
||||
filter *StateOfApplicabilityFilter,
|
||||
) error {
|
||||
q := `
|
||||
SELECT
|
||||
id,
|
||||
organization_id,
|
||||
name,
|
||||
source_id,
|
||||
snapshot_id,
|
||||
owner_id,
|
||||
created_at,
|
||||
updated_at
|
||||
FROM
|
||||
states_of_applicability
|
||||
WHERE
|
||||
%s
|
||||
AND organization_id = @organization_id
|
||||
AND %s
|
||||
AND %s
|
||||
`
|
||||
q = fmt.Sprintf(q, scope.SQLFragment(), filter.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)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot query states_of_applicability: %w", err)
|
||||
}
|
||||
|
||||
statesOfApplicability, err := pgx.CollectRows(rows, pgx.RowToAddrOfStructByName[StateOfApplicability])
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot collect states_of_applicability: %w", err)
|
||||
}
|
||||
|
||||
*s = statesOfApplicability
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *StatesOfApplicability) CountByOrganizationID(
|
||||
ctx context.Context,
|
||||
conn pg.Conn,
|
||||
scope Scoper,
|
||||
organizationID gid.GID,
|
||||
filter *StateOfApplicabilityFilter,
|
||||
) (int, error) {
|
||||
q := `
|
||||
SELECT
|
||||
COUNT(*)
|
||||
FROM
|
||||
states_of_applicability
|
||||
WHERE
|
||||
%s
|
||||
AND organization_id = @organization_id
|
||||
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 count states_of_applicability: %w", err)
|
||||
}
|
||||
|
||||
return count, nil
|
||||
}
|
||||
|
||||
func (s *StateOfApplicability) Insert(
|
||||
ctx context.Context,
|
||||
conn pg.Conn,
|
||||
scope Scoper,
|
||||
) error {
|
||||
q := `
|
||||
INSERT INTO
|
||||
states_of_applicability (
|
||||
tenant_id,
|
||||
id,
|
||||
organization_id,
|
||||
name,
|
||||
source_id,
|
||||
snapshot_id,
|
||||
owner_id,
|
||||
created_at,
|
||||
updated_at
|
||||
)
|
||||
VALUES (
|
||||
@tenant_id,
|
||||
@state_of_applicability_id,
|
||||
@organization_id,
|
||||
@name,
|
||||
@source_id,
|
||||
@snapshot_id,
|
||||
@owner_id,
|
||||
@created_at,
|
||||
@updated_at
|
||||
);
|
||||
`
|
||||
|
||||
args := pgx.StrictNamedArgs{
|
||||
"tenant_id": scope.GetTenantID(),
|
||||
"state_of_applicability_id": s.ID,
|
||||
"organization_id": s.OrganizationID,
|
||||
"name": s.Name,
|
||||
"source_id": s.SourceID,
|
||||
"snapshot_id": s.SnapshotID,
|
||||
"owner_id": s.OwnerID,
|
||||
"created_at": s.CreatedAt,
|
||||
"updated_at": s.UpdatedAt,
|
||||
}
|
||||
_, err := conn.Exec(ctx, q, args)
|
||||
|
||||
if err != nil {
|
||||
var pgErr *pgconn.PgError
|
||||
if errors.As(err, &pgErr) {
|
||||
if pgErr.Code == "23505" {
|
||||
return &ErrStateOfApplicabilityAlreadyExists{
|
||||
message: fmt.Sprintf("state of applicability with name %q already exists", s.Name),
|
||||
}
|
||||
}
|
||||
}
|
||||
return fmt.Errorf("cannot insert state_of_applicability: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *StateOfApplicability) Update(
|
||||
ctx context.Context,
|
||||
conn pg.Conn,
|
||||
scope Scoper,
|
||||
) error {
|
||||
q := `
|
||||
UPDATE states_of_applicability
|
||||
SET
|
||||
name = @name,
|
||||
owner_id = @owner_id,
|
||||
updated_at = @updated_at
|
||||
WHERE %s
|
||||
AND id = @state_of_applicability_id
|
||||
`
|
||||
|
||||
q = fmt.Sprintf(q, scope.SQLFragment())
|
||||
|
||||
args := pgx.StrictNamedArgs{
|
||||
"state_of_applicability_id": s.ID,
|
||||
"name": s.Name,
|
||||
"owner_id": s.OwnerID,
|
||||
"updated_at": s.UpdatedAt,
|
||||
}
|
||||
maps.Copy(args, scope.SQLArguments())
|
||||
|
||||
result, err := conn.Exec(ctx, q, args)
|
||||
if err != nil {
|
||||
var pgErr *pgconn.PgError
|
||||
if errors.As(err, &pgErr) {
|
||||
if pgErr.Code == "23505" {
|
||||
return &ErrStateOfApplicabilityAlreadyExists{
|
||||
message: fmt.Sprintf("state of applicability with name %q already exists", s.Name),
|
||||
}
|
||||
}
|
||||
}
|
||||
return fmt.Errorf("cannot update state_of_applicability: %w", err)
|
||||
}
|
||||
|
||||
if result.RowsAffected() == 0 {
|
||||
return &ErrStateOfApplicabilityNotFound{Identifier: s.ID.String()}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *StateOfApplicability) Delete(
|
||||
ctx context.Context,
|
||||
conn pg.Conn,
|
||||
scope Scoper,
|
||||
) error {
|
||||
q := `
|
||||
DELETE FROM states_of_applicability
|
||||
WHERE %s
|
||||
AND id = @state_of_applicability_id
|
||||
`
|
||||
q = fmt.Sprintf(q, scope.SQLFragment())
|
||||
|
||||
args := pgx.StrictNamedArgs{
|
||||
"state_of_applicability_id": s.ID,
|
||||
}
|
||||
maps.Copy(args, scope.SQLArguments())
|
||||
|
||||
result, err := conn.Exec(ctx, q, args)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot delete state_of_applicability: %w", err)
|
||||
}
|
||||
|
||||
if result.RowsAffected() == 0 {
|
||||
return &ErrStateOfApplicabilityNotFound{Identifier: s.ID.String()}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (soas StatesOfApplicability) Snapshot(ctx context.Context, conn pg.Conn, scope Scoper, organizationID, snapshotID gid.GID) error {
|
||||
if err := soas.insertStateOfApplicabilitySnapshots(ctx, conn, scope, organizationID, snapshotID); err != nil {
|
||||
return fmt.Errorf("cannot insert state_of_applicability snapshots: %w", err)
|
||||
}
|
||||
|
||||
if err := soas.insertStateOfApplicabilityControlSnapshots(ctx, conn, scope, organizationID, snapshotID); err != nil {
|
||||
return fmt.Errorf("cannot insert state_of_applicability_control snapshots: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (soas StatesOfApplicability) insertStateOfApplicabilitySnapshots(
|
||||
ctx context.Context,
|
||||
conn pg.Conn,
|
||||
scope Scoper,
|
||||
organizationID gid.GID,
|
||||
snapshotID gid.GID,
|
||||
) error {
|
||||
query := `
|
||||
INSERT INTO states_of_applicability (
|
||||
id,
|
||||
tenant_id,
|
||||
organization_id,
|
||||
name,
|
||||
source_id,
|
||||
snapshot_id,
|
||||
owner_id,
|
||||
created_at,
|
||||
updated_at
|
||||
)
|
||||
SELECT
|
||||
generate_gid(decode_base64_unpadded(@tenant_id), @state_of_applicability_entity_type),
|
||||
@tenant_id,
|
||||
soa.organization_id,
|
||||
soa.name,
|
||||
soa.id,
|
||||
@snapshot_id,
|
||||
soa.owner_id,
|
||||
soa.created_at,
|
||||
soa.updated_at
|
||||
FROM states_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,
|
||||
"state_of_applicability_entity_type": StateOfApplicabilityEntityType,
|
||||
}
|
||||
maps.Copy(args, scope.SQLArguments())
|
||||
|
||||
_, err := conn.Exec(ctx, query, args)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot insert state_of_applicability snapshots: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (soas StatesOfApplicability) insertStateOfApplicabilityControlSnapshots(
|
||||
ctx context.Context,
|
||||
conn pg.Conn,
|
||||
scope Scoper,
|
||||
organizationID gid.GID,
|
||||
snapshotID gid.GID,
|
||||
) error {
|
||||
query := `
|
||||
WITH source_soa AS (
|
||||
SELECT id, organization_id
|
||||
FROM states_of_applicability
|
||||
WHERE %s
|
||||
AND organization_id = @organization_id
|
||||
AND snapshot_id IS NULL
|
||||
),
|
||||
snapshot_soa AS (
|
||||
SELECT id, source_id
|
||||
FROM states_of_applicability
|
||||
WHERE snapshot_id = @snapshot_id
|
||||
)
|
||||
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
|
||||
)
|
||||
SELECT
|
||||
generate_gid(decode_base64_unpadded(@tenant_id), @state_of_applicability_control_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 states_of_applicability_controls soac
|
||||
INNER JOIN source_soa
|
||||
ON soac.state_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,
|
||||
"state_of_applicability_control_entity_type": StateOfApplicabilityControlEntityType,
|
||||
}
|
||||
maps.Copy(args, scope.SQLArguments())
|
||||
|
||||
_, err := conn.Exec(ctx, query, args)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot insert state_of_applicability_control snapshots: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
607
pkg/coredata/state_of_applicability_control.go
Normal file
607
pkg/coredata/state_of_applicability_control.go
Normal file
@@ -0,0 +1,607 @@
|
||||
// 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 (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"maps"
|
||||
"time"
|
||||
|
||||
"github.com/jackc/pgx/v5"
|
||||
"github.com/jackc/pgx/v5/pgconn"
|
||||
"go.gearno.de/kit/pg"
|
||||
"go.probo.inc/probo/pkg/gid"
|
||||
"go.probo.inc/probo/pkg/page"
|
||||
)
|
||||
|
||||
type (
|
||||
StateOfApplicabilityControl struct {
|
||||
ID gid.GID `db:"id"`
|
||||
StateOfApplicabilityID gid.GID `db:"state_of_applicability_id"`
|
||||
ControlID gid.GID `db:"control_id"`
|
||||
OrganizationID gid.GID `db:"organization_id"`
|
||||
SnapshotID *gid.GID `db:"snapshot_id"`
|
||||
Applicability bool `db:"applicability"`
|
||||
Justification *string `db:"justification"`
|
||||
CreatedAt time.Time `db:"created_at"`
|
||||
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
|
||||
|
||||
ErrStateOfApplicabilityControlNotFound struct {
|
||||
StateOfApplicabilityID gid.GID
|
||||
ControlID gid.GID
|
||||
}
|
||||
|
||||
ErrStateOfApplicabilityControlAlreadyExists struct {
|
||||
StateOfApplicabilityID gid.GID
|
||||
ControlID gid.GID
|
||||
}
|
||||
)
|
||||
|
||||
func (e ErrStateOfApplicabilityControlNotFound) Error() string {
|
||||
return fmt.Sprintf("state of applicability control not found: state_of_applicability_id=%s, control_id=%s", e.StateOfApplicabilityID, e.ControlID)
|
||||
}
|
||||
|
||||
func (e ErrStateOfApplicabilityControlAlreadyExists) Error() string {
|
||||
return fmt.Sprintf("state of applicability control already exists: state_of_applicability_id=%s, control_id=%s", e.StateOfApplicabilityID, e.ControlID)
|
||||
}
|
||||
|
||||
func (s StateOfApplicabilityControl) CursorKey(orderBy StateOfApplicabilityOrderField) page.CursorKey {
|
||||
switch orderBy {
|
||||
case StateOfApplicabilityOrderFieldName:
|
||||
return page.NewCursorKey(s.ID, s.StateOfApplicabilityID)
|
||||
case StateOfApplicabilityOrderFieldCreatedAt:
|
||||
return page.NewCursorKey(s.ID, s.CreatedAt)
|
||||
}
|
||||
|
||||
panic(fmt.Sprintf("unsupported order by: %s", orderBy))
|
||||
}
|
||||
|
||||
func (sac *StateOfApplicabilityControl) LoadByStateOfApplicabilityIDAndControlID(
|
||||
ctx context.Context,
|
||||
conn pg.Conn,
|
||||
scope Scoper,
|
||||
stateOfApplicabilityID gid.GID,
|
||||
controlID gid.GID,
|
||||
) error {
|
||||
q := `
|
||||
WITH current_soa AS (
|
||||
SELECT id
|
||||
FROM states_of_applicability
|
||||
WHERE %s
|
||||
AND id = @state_of_applicability_id
|
||||
AND snapshot_id IS NULL
|
||||
)
|
||||
SELECT
|
||||
soac.id,
|
||||
soac.state_of_applicability_id,
|
||||
soac.control_id,
|
||||
soac.organization_id,
|
||||
soac.snapshot_id,
|
||||
soac.applicability,
|
||||
soac.justification,
|
||||
soac.created_at,
|
||||
soac.updated_at
|
||||
FROM
|
||||
states_of_applicability_controls soac
|
||||
INNER JOIN
|
||||
current_soa ON soac.state_of_applicability_id = current_soa.id
|
||||
WHERE
|
||||
soac.control_id = @control_id
|
||||
LIMIT 1;
|
||||
`
|
||||
q = fmt.Sprintf(q, scope.SQLFragment())
|
||||
|
||||
args := pgx.StrictNamedArgs{
|
||||
"state_of_applicability_id": stateOfApplicabilityID,
|
||||
"control_id": controlID,
|
||||
}
|
||||
maps.Copy(args, scope.SQLArguments())
|
||||
|
||||
rows, err := conn.Query(ctx, q, args)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot query states_of_applicability_controls: %w", err)
|
||||
}
|
||||
|
||||
control, err := pgx.CollectExactlyOneRow(rows, pgx.RowToStructByName[StateOfApplicabilityControl])
|
||||
if err != nil {
|
||||
if errors.Is(err, pgx.ErrNoRows) {
|
||||
return &ErrStateOfApplicabilityControlNotFound{
|
||||
StateOfApplicabilityID: stateOfApplicabilityID,
|
||||
ControlID: controlID,
|
||||
}
|
||||
}
|
||||
return fmt.Errorf("cannot collect state of applicability control: %w", err)
|
||||
}
|
||||
|
||||
*sac = control
|
||||
return nil
|
||||
}
|
||||
|
||||
func (sac *StateOfApplicabilityControl) Insert(
|
||||
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
|
||||
);
|
||||
`
|
||||
|
||||
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,
|
||||
}
|
||||
_, err := conn.Exec(ctx, q, args)
|
||||
|
||||
if err != nil {
|
||||
var pgErr *pgconn.PgError
|
||||
if errors.As(err, &pgErr) {
|
||||
if pgErr.Code == "23505" {
|
||||
return &ErrStateOfApplicabilityControlAlreadyExists{
|
||||
StateOfApplicabilityID: sac.StateOfApplicabilityID,
|
||||
ControlID: sac.ControlID,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return fmt.Errorf("cannot insert state_of_applicability_control: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (sac *StateOfApplicabilityControl) Update(
|
||||
ctx context.Context,
|
||||
conn pg.Conn,
|
||||
scope Scoper,
|
||||
) error {
|
||||
q := `
|
||||
UPDATE states_of_applicability_controls
|
||||
SET
|
||||
applicability = @applicability,
|
||||
justification = @justification,
|
||||
updated_at = @updated_at
|
||||
WHERE %s
|
||||
AND state_of_applicability_id = @state_of_applicability_id
|
||||
AND control_id = @control_id
|
||||
`
|
||||
q = fmt.Sprintf(q, scope.SQLFragment())
|
||||
|
||||
args := pgx.StrictNamedArgs{
|
||||
"state_of_applicability_id": sac.StateOfApplicabilityID,
|
||||
"control_id": sac.ControlID,
|
||||
"applicability": sac.Applicability,
|
||||
"justification": sac.Justification,
|
||||
"updated_at": sac.UpdatedAt,
|
||||
}
|
||||
maps.Copy(args, scope.SQLArguments())
|
||||
|
||||
_, err := conn.Exec(ctx, q, args)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot update state_of_applicability_control: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (sac *StateOfApplicabilityControl) Upsert(
|
||||
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
|
||||
`
|
||||
|
||||
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,
|
||||
}
|
||||
_, err := conn.Exec(ctx, q, args)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot upsert state_of_applicability_control: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (sac *StateOfApplicabilityControl) Delete(
|
||||
ctx context.Context,
|
||||
conn pg.Conn,
|
||||
scope Scoper,
|
||||
) error {
|
||||
q := `
|
||||
WITH current_soa AS (
|
||||
SELECT id
|
||||
FROM states_of_applicability
|
||||
WHERE %s
|
||||
AND id = @state_of_applicability_id
|
||||
AND snapshot_id IS NULL
|
||||
)
|
||||
DELETE FROM states_of_applicability_controls
|
||||
WHERE state_of_applicability_id IN (SELECT id FROM current_soa)
|
||||
AND control_id = @control_id;
|
||||
`
|
||||
|
||||
args := pgx.StrictNamedArgs{
|
||||
"state_of_applicability_id": sac.StateOfApplicabilityID,
|
||||
"control_id": sac.ControlID,
|
||||
}
|
||||
maps.Copy(args, scope.SQLArguments())
|
||||
q = fmt.Sprintf(q, scope.SQLFragment())
|
||||
|
||||
_, err := conn.Exec(ctx, q, args)
|
||||
return err
|
||||
}
|
||||
|
||||
func (sacs *StateOfApplicabilityControls) LoadByStateOfApplicabilityID(
|
||||
ctx context.Context,
|
||||
conn pg.Conn,
|
||||
scope Scoper,
|
||||
stateOfApplicabilityID gid.GID,
|
||||
) error {
|
||||
q := `
|
||||
SELECT
|
||||
id,
|
||||
state_of_applicability_id,
|
||||
control_id,
|
||||
organization_id,
|
||||
snapshot_id,
|
||||
applicability,
|
||||
justification,
|
||||
created_at,
|
||||
updated_at
|
||||
FROM
|
||||
states_of_applicability_controls
|
||||
WHERE
|
||||
%s
|
||||
AND state_of_applicability_id = @state_of_applicability_id
|
||||
ORDER BY created_at ASC
|
||||
`
|
||||
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 states_of_applicability_controls: %w", err)
|
||||
}
|
||||
|
||||
controls, err := pgx.CollectRows(rows, pgx.RowToAddrOfStructByName[StateOfApplicabilityControl])
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot collect states_of_applicability_controls: %w", err)
|
||||
}
|
||||
|
||||
*sacs = controls
|
||||
return nil
|
||||
}
|
||||
|
||||
func (sacs *StateOfApplicabilityControls) LoadByControlID(
|
||||
ctx context.Context,
|
||||
conn pg.Conn,
|
||||
scope Scoper,
|
||||
controlID gid.GID,
|
||||
cursor *page.Cursor[StateOfApplicabilityOrderField],
|
||||
) error {
|
||||
q := `
|
||||
WITH soac_ctrl AS (
|
||||
SELECT
|
||||
soac.id,
|
||||
soac.state_of_applicability_id,
|
||||
soac.control_id,
|
||||
soac.organization_id,
|
||||
soac.snapshot_id,
|
||||
soac.applicability,
|
||||
soac.justification,
|
||||
soac.created_at,
|
||||
soac.updated_at,
|
||||
soac.tenant_id
|
||||
FROM
|
||||
states_of_applicability_controls soac
|
||||
INNER JOIN
|
||||
states_of_applicability soa ON soac.state_of_applicability_id = soa.id
|
||||
WHERE
|
||||
soac.control_id = @control_id
|
||||
AND soa.snapshot_id IS NULL
|
||||
)
|
||||
SELECT
|
||||
id,
|
||||
state_of_applicability_id,
|
||||
control_id,
|
||||
organization_id,
|
||||
snapshot_id,
|
||||
applicability,
|
||||
justification,
|
||||
created_at,
|
||||
updated_at
|
||||
FROM
|
||||
soac_ctrl
|
||||
WHERE %s
|
||||
AND %s
|
||||
`
|
||||
q = fmt.Sprintf(q, scope.SQLFragment(), cursor.SQLFragment())
|
||||
|
||||
args := pgx.NamedArgs{"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 state_of_applicability_controls: %w", err)
|
||||
}
|
||||
|
||||
controls, err := pgx.CollectRows(rows, pgx.RowToAddrOfStructByName[StateOfApplicabilityControl])
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot collect state_of_applicability_controls: %w", err)
|
||||
}
|
||||
|
||||
*sacs = controls
|
||||
return nil
|
||||
}
|
||||
|
||||
func (sacs *StateOfApplicabilityControls) CountByControlID(
|
||||
ctx context.Context,
|
||||
conn pg.Conn,
|
||||
scope Scoper,
|
||||
controlID gid.GID,
|
||||
) (int, error) {
|
||||
q := `
|
||||
WITH soac_ctrl AS (
|
||||
SELECT
|
||||
soac.id,
|
||||
soac.organization_id,
|
||||
soac.tenant_id
|
||||
FROM
|
||||
states_of_applicability_controls soac
|
||||
WHERE
|
||||
soac.control_id = @control_id
|
||||
)
|
||||
SELECT
|
||||
COUNT(id)
|
||||
FROM
|
||||
soac_ctrl
|
||||
WHERE
|
||||
%s;
|
||||
`
|
||||
|
||||
q = fmt.Sprintf(q, scope.SQLFragment())
|
||||
|
||||
args := pgx.NamedArgs{"control_id": controlID}
|
||||
maps.Copy(args, scope.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 (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
|
||||
}
|
||||
61
pkg/coredata/state_of_applicability_filter.go
Normal file
61
pkg/coredata/state_of_applicability_filter.go
Normal file
@@ -0,0 +1,61 @@
|
||||
// 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 (
|
||||
"go.probo.inc/probo/pkg/gid"
|
||||
"github.com/jackc/pgx/v5"
|
||||
)
|
||||
|
||||
type (
|
||||
StateOfApplicabilityFilter struct {
|
||||
snapshotID **gid.GID
|
||||
}
|
||||
)
|
||||
|
||||
func NewStateOfApplicabilityFilter(snapshotID **gid.GID) *StateOfApplicabilityFilter {
|
||||
return &StateOfApplicabilityFilter{
|
||||
snapshotID: snapshotID,
|
||||
}
|
||||
}
|
||||
|
||||
func (f *StateOfApplicabilityFilter) 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 *StateOfApplicabilityFilter) 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`
|
||||
}
|
||||
62
pkg/coredata/state_of_applicability_order_field.go
Normal file
62
pkg/coredata/state_of_applicability_order_field.go
Normal file
@@ -0,0 +1,62 @@
|
||||
// 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 (
|
||||
StateOfApplicabilityOrderField string
|
||||
)
|
||||
|
||||
const (
|
||||
StateOfApplicabilityOrderFieldName StateOfApplicabilityOrderField = "NAME"
|
||||
StateOfApplicabilityOrderFieldCreatedAt StateOfApplicabilityOrderField = "CREATED_AT"
|
||||
)
|
||||
|
||||
func (s StateOfApplicabilityOrderField) Column() string {
|
||||
switch s {
|
||||
case StateOfApplicabilityOrderFieldName:
|
||||
return "name"
|
||||
case StateOfApplicabilityOrderFieldCreatedAt:
|
||||
return "created_at"
|
||||
}
|
||||
panic(fmt.Sprintf("unsupported order by: %s", s))
|
||||
}
|
||||
|
||||
func (s StateOfApplicabilityOrderField) String() string {
|
||||
return string(s)
|
||||
}
|
||||
|
||||
func (s StateOfApplicabilityOrderField) IsValid() bool {
|
||||
switch s {
|
||||
case StateOfApplicabilityOrderFieldName, StateOfApplicabilityOrderFieldCreatedAt:
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (s StateOfApplicabilityOrderField) MarshalText() ([]byte, error) {
|
||||
return []byte(s.String()), nil
|
||||
}
|
||||
|
||||
func (s *StateOfApplicabilityOrderField) UnmarshalText(text []byte) error {
|
||||
*s = StateOfApplicabilityOrderField(text)
|
||||
if !s.IsValid() {
|
||||
return fmt.Errorf("%s is not a valid StateOfApplicabilityOrderField", string(text))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -47,13 +47,34 @@ 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 },
|
||||
"add": func(a, b int) int { return a + b },
|
||||
"string": func(v fmt.Stringer) string { return v.String() },
|
||||
"lower": func(s string) string { return strings.ToLower(s) },
|
||||
"add": func(a, b int) int { return a + b },
|
||||
"classificationString": func(c Classification) string { return string(c) },
|
||||
"boolToYesNo": func(b *bool) string {
|
||||
if b == nil {
|
||||
return ""
|
||||
}
|
||||
if *b {
|
||||
return "yes"
|
||||
}
|
||||
return "no"
|
||||
},
|
||||
"boolToYesNoDash": func(b *bool) string {
|
||||
if b == nil {
|
||||
return "-"
|
||||
}
|
||||
if *b {
|
||||
return "Yes"
|
||||
}
|
||||
return "No"
|
||||
},
|
||||
"formatContent": func(content string) template.HTML {
|
||||
md := goldmark.New(
|
||||
goldmark.WithExtensions(extension.Table),
|
||||
@@ -176,6 +197,8 @@ var (
|
||||
dataProtectionImpactAssessmentsTemplate = template.Must(template.New("dataProtectionImpactAssessments").Funcs(templateFuncs).Parse(dataProtectionImpactAssessmentsTemplateContent))
|
||||
|
||||
transferImpactAssessmentsTemplate = template.Must(template.New("transferImpactAssessments").Funcs(templateFuncs).Parse(transferImpactAssessmentsTemplateContent))
|
||||
|
||||
stateOfApplicabilityTemplate = template.Must(template.New("state-of-applicability").Funcs(templateFuncs).Parse(soaTemplateContent))
|
||||
)
|
||||
|
||||
type (
|
||||
@@ -264,6 +287,35 @@ type (
|
||||
LocalLawRisk *string
|
||||
SupplementaryMeasures *string
|
||||
}
|
||||
|
||||
StateOfApplicabilityData struct {
|
||||
Title string
|
||||
OrganizationName string
|
||||
CreatedAt time.Time
|
||||
TotalControls int
|
||||
FrameworkGroups []FrameworkControlGroup
|
||||
CompanyHorizontalLogoBase64 string
|
||||
Version int
|
||||
PublishedAt time.Time
|
||||
Approver string
|
||||
}
|
||||
|
||||
FrameworkControlGroup struct {
|
||||
FrameworkName string
|
||||
Controls []ControlData
|
||||
}
|
||||
|
||||
ControlData struct {
|
||||
FrameworkName string
|
||||
SectionTitle string
|
||||
Name string
|
||||
Applicability *bool
|
||||
Justification *string
|
||||
BestPractice bool
|
||||
Regulatory *bool
|
||||
Contractual *bool
|
||||
RiskAssessment *bool
|
||||
}
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -308,3 +360,12 @@ func RenderTransferImpactAssessmentsTableHTML(data TransferImpactAssessmentTable
|
||||
|
||||
return buf.Bytes(), nil
|
||||
}
|
||||
|
||||
func RenderStateOfApplicabilityHTML(data StateOfApplicabilityData) ([]byte, error) {
|
||||
var buf bytes.Buffer
|
||||
if err := stateOfApplicabilityTemplate.Execute(&buf, data); err != nil {
|
||||
return nil, fmt.Errorf("cannot execute SOA template: %w", err)
|
||||
}
|
||||
|
||||
return buf.Bytes(), nil
|
||||
}
|
||||
|
||||
@@ -20,9 +20,9 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"go.probo.inc/probo/pkg/coredata"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"go.probo.inc/probo/pkg/coredata"
|
||||
)
|
||||
|
||||
func TestRenderHTML(t *testing.T) {
|
||||
@@ -198,6 +198,8 @@ func TestTemplateFunctions(t *testing.T) {
|
||||
eqFunc := templateFuncs["eq"].(func(any, any) bool)
|
||||
assert.True(t, eqFunc("test", "test"))
|
||||
assert.False(t, eqFunc("test", "other"))
|
||||
assert.True(t, eqFunc(0, 0))
|
||||
assert.False(t, eqFunc(0, 1))
|
||||
})
|
||||
|
||||
t.Run("lower function", func(t *testing.T) {
|
||||
|
||||
480
pkg/docgen/soa_template.html
Normal file
480
pkg/docgen/soa_template.html
Normal file
@@ -0,0 +1,480 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>State of Applicability</title>
|
||||
<style>
|
||||
@page {
|
||||
size: A4 landscape;
|
||||
margin: 2.5cm;
|
||||
@bottom-right {
|
||||
content: "Page " counter(page) " of " counter(pages);
|
||||
font-family: Arial, sans-serif;
|
||||
font-size: 9pt;
|
||||
color: #666;
|
||||
}
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: Arial, sans-serif;
|
||||
font-size: 7.5pt;
|
||||
line-height: 1.4;
|
||||
color: #333;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
background: white;
|
||||
}
|
||||
|
||||
/* Cover page */
|
||||
.cover-page {
|
||||
page-break-after: always;
|
||||
}
|
||||
|
||||
.company-header {
|
||||
margin-bottom: 30px;
|
||||
page-break-after: avoid;
|
||||
}
|
||||
|
||||
.company-logo {
|
||||
max-height: 50px;
|
||||
max-width: 250px;
|
||||
object-fit: contain;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.export-title {
|
||||
font-size: 22pt;
|
||||
font-weight: normal;
|
||||
color: #1a1a1a;
|
||||
margin: 0 0 25px 0;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.export-subtitle {
|
||||
font-size: 18pt;
|
||||
font-weight: normal;
|
||||
color: #1a1a1a;
|
||||
margin: 0 0 25px 0;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.document-meta {
|
||||
margin: 0 0 30px 0;
|
||||
font-size: 9pt;
|
||||
}
|
||||
|
||||
.meta-table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
border: 1px solid #333;
|
||||
}
|
||||
|
||||
.meta-table td {
|
||||
padding: 6px 8px;
|
||||
border: 1px solid #333;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.meta-table td:first-child {
|
||||
font-weight: 600;
|
||||
width: 25%;
|
||||
background: #f8f8f8;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.classification {
|
||||
font-weight: bold;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.purpose-section {
|
||||
margin: 30px 0;
|
||||
page-break-after: avoid;
|
||||
}
|
||||
|
||||
.purpose-title {
|
||||
font-size: 15pt;
|
||||
font-weight: bold;
|
||||
color: #000;
|
||||
margin: 0 0 15px 0;
|
||||
page-break-after: avoid;
|
||||
}
|
||||
|
||||
.purpose-text {
|
||||
font-size: 10pt;
|
||||
color: #333;
|
||||
line-height: 1.5;
|
||||
text-align: justify;
|
||||
}
|
||||
|
||||
/* Controls page */
|
||||
.controls-page {
|
||||
page-break-before: always;
|
||||
}
|
||||
|
||||
.controls-title {
|
||||
font-size: 15pt;
|
||||
font-weight: bold;
|
||||
color: #000;
|
||||
margin: 0 0 15px 0;
|
||||
page-break-after: avoid;
|
||||
}
|
||||
|
||||
.controls-table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
font-size: 8pt;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.controls-table th,
|
||||
.controls-table td {
|
||||
padding: 5px 6px;
|
||||
text-align: left;
|
||||
border: 1px solid #ddd;
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
.controls-table th {
|
||||
background: #f5f5f5;
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.controls-table tr {
|
||||
page-break-inside: avoid;
|
||||
}
|
||||
|
||||
.section-tag {
|
||||
display: inline-block;
|
||||
background: #e0e0e0;
|
||||
color: #333;
|
||||
padding: 2px 5px;
|
||||
border-radius: 3px;
|
||||
font-size: 7pt;
|
||||
font-weight: 500;
|
||||
margin-right: 5px;
|
||||
}
|
||||
|
||||
.state-tag {
|
||||
display: inline-block;
|
||||
padding: 2px 5px;
|
||||
border-radius: 4px;
|
||||
font-size: 8pt;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.state-tag-success {
|
||||
background: #eefadc;
|
||||
color: #5d770d;
|
||||
}
|
||||
|
||||
.state-tag-warning {
|
||||
background: #fff4d5;
|
||||
color: #ad5700;
|
||||
}
|
||||
|
||||
.state-tag-danger {
|
||||
background: #ffefef;
|
||||
color: #cd2b31;
|
||||
}
|
||||
|
||||
/* Annex page */
|
||||
.annex-page {
|
||||
page-break-before: always;
|
||||
}
|
||||
|
||||
.annex-title {
|
||||
font-size: 15pt;
|
||||
font-weight: bold;
|
||||
color: #000;
|
||||
margin: 0 0 15px 0;
|
||||
page-break-after: avoid;
|
||||
}
|
||||
|
||||
.annex-section {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.annex-section-title {
|
||||
font-size: 13pt;
|
||||
font-weight: bold;
|
||||
color: #000;
|
||||
margin: 15px 0 10px 0;
|
||||
}
|
||||
|
||||
.annex-subsection-title {
|
||||
font-size: 10pt;
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
margin: 12px 0 8px 0;
|
||||
}
|
||||
|
||||
.annex-enum-list {
|
||||
margin: 10px 0;
|
||||
padding-left: 20px;
|
||||
}
|
||||
|
||||
.annex-enum-item {
|
||||
margin-bottom: 8px;
|
||||
font-size: 10pt;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.annex-enum-name {
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.annex-enum-description {
|
||||
color: #000;
|
||||
margin-left: 5px;
|
||||
}
|
||||
|
||||
/* Prevent bad page breaks */
|
||||
h1, h2, h3, h4, h5, h6 {
|
||||
page-break-after: avoid;
|
||||
page-break-inside: avoid;
|
||||
}
|
||||
|
||||
@media print {
|
||||
body {
|
||||
background: white;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="cover-page">
|
||||
<div class="company-header">
|
||||
{{- if .CompanyHorizontalLogoBase64}}
|
||||
{{imgTag .CompanyHorizontalLogoBase64 "Company Logo" "company-logo"}}
|
||||
{{- end}}
|
||||
</div>
|
||||
|
||||
<h1 class="export-title">State of Applicability</h1>
|
||||
<h2 class="export-subtitle">{{.Title}}</h2>
|
||||
|
||||
<div class="document-meta">
|
||||
<table class="meta-table">
|
||||
<tr>
|
||||
<td>Classification</td>
|
||||
<td>
|
||||
<span class="classification">CONFIDENTIAL</span>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Approver</td>
|
||||
<td>{{.Approver}}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Version</td>
|
||||
<td>{{.Version}}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Published</td>
|
||||
<td>{{.PublishedAt.Format "January 2, 2006"}}</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div class="purpose-section">
|
||||
<div class="purpose-title">1. Purpose</div>
|
||||
<div class="purpose-text">
|
||||
This document provides a comprehensive overview of the state of applicability for controls within the organization.
|
||||
It serves as a record of which controls are applicable or not applicable to the organization, along with their
|
||||
relationships to regulatory requirements, contractual obligations, risk assessments, and best practices.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{- if .FrameworkGroups}}
|
||||
<div class="controls-page">
|
||||
<h1 class="controls-title">2. Controls</h1>
|
||||
<table class="controls-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th rowspan="2" style="width: 12%;">Framework</th>
|
||||
<th rowspan="2" style="width: 24%;">Control</th>
|
||||
<th rowspan="2" style="width: 9%;">Applicability</th>
|
||||
<th rowspan="2" style="width: 17%;">Justification</th>
|
||||
<th colspan="4" style="width: 38%; text-align: center;">Justification for inclusion</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<th style="width: 8%;">Regulatory</th>
|
||||
<th style="width: 8%;">Contractual</th>
|
||||
<th style="width: 10%;">Best Practice</th>
|
||||
<th style="width: 12%;">Risk Assessment</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{{- range $group := .FrameworkGroups}}
|
||||
{{- range $group.Controls}}
|
||||
<tr>
|
||||
<td>{{$group.FrameworkName}}</td>
|
||||
<td><span class="section-tag">{{.SectionTitle}}</span>{{.Name}}</td>
|
||||
<td>
|
||||
{{- $state := boolToYesNo .Applicability}}
|
||||
{{- if eq $state "yes"}}
|
||||
<span class="state-tag state-tag-success">Yes</span>
|
||||
{{- else if eq $state "no"}}
|
||||
<span class="state-tag state-tag-danger">No</span>
|
||||
{{- else}}
|
||||
<span class="state-tag">-</span>
|
||||
{{- end}}
|
||||
</td>
|
||||
<td>
|
||||
{{- if .Justification}}
|
||||
{{.Justification}}
|
||||
{{- else}}
|
||||
-
|
||||
{{- end}}
|
||||
</td>
|
||||
<td>{{boolToYesNoDash .Regulatory}}</td>
|
||||
<td>{{boolToYesNoDash .Contractual}}</td>
|
||||
<td>
|
||||
{{- if .Applicability}}
|
||||
{{- if and .Applicability .BestPractice}}
|
||||
Yes
|
||||
{{- else if .Applicability}}
|
||||
No
|
||||
{{- else}}
|
||||
-
|
||||
{{- end}}
|
||||
{{- else}}
|
||||
-
|
||||
{{- end}}
|
||||
</td>
|
||||
<td>{{boolToYesNoDash .RiskAssessment}}</td>
|
||||
</tr>
|
||||
{{- end}}
|
||||
{{- end}}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{{- end}}
|
||||
|
||||
<div class="annex-page">
|
||||
<h1 class="annex-title">3. Annexes</h1>
|
||||
|
||||
<div class="annex-section">
|
||||
<div class="annex-section-title">3.1 Column Definitions</div>
|
||||
</div>
|
||||
|
||||
<div class="annex-section">
|
||||
<div class="annex-subsection-title">Framework</div>
|
||||
<ul class="annex-enum-list">
|
||||
<li class="annex-enum-item">
|
||||
<span class="annex-enum-description">The name of the compliance framework or standard to which the control belongs (e.g., ISO 27001, SOC 2, GDPR).</span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="annex-section">
|
||||
<div class="annex-subsection-title">Control</div>
|
||||
<ul class="annex-enum-list">
|
||||
<li class="annex-enum-item">
|
||||
<span class="annex-enum-description">The specific control identifier and name within the framework, including its section reference.</span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="annex-section">
|
||||
<div class="annex-subsection-title">Applicability</div>
|
||||
<ul class="annex-enum-list">
|
||||
<li class="annex-enum-item">
|
||||
<span class="annex-enum-name">Yes:</span>
|
||||
<span class="annex-enum-description">The control is applicable to the organization.</span>
|
||||
</li>
|
||||
<li class="annex-enum-item">
|
||||
<span class="annex-enum-name">No:</span>
|
||||
<span class="annex-enum-description">The control is not applicable to the organization (with justification provided).</span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="annex-section">
|
||||
<div class="annex-subsection-title">Justification</div>
|
||||
<ul class="annex-enum-list">
|
||||
<li class="annex-enum-item">
|
||||
<span class="annex-enum-description">Provides the rationale when a control is not applicable. This field is empty for applicable controls.</span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="annex-section">
|
||||
<div class="annex-subsection-title">Justification for inclusion</div>
|
||||
<div class="annex-enum-description" style="margin-bottom: 12px;">
|
||||
For applicable controls, this section provides additional context on why the control is included, based on regulatory requirements, contractual obligations, best practices, or risk assessments.
|
||||
</div>
|
||||
|
||||
<div style="margin-left: 20px;">
|
||||
<div class="annex-subsection-title" style="font-size: 9pt; margin-top: 10px;">Regulatory</div>
|
||||
<ul class="annex-enum-list">
|
||||
<li class="annex-enum-item">
|
||||
<span class="annex-enum-name">Yes:</span>
|
||||
<span class="annex-enum-description">The control is linked to one or more legal or regulatory obligations.</span>
|
||||
</li>
|
||||
<li class="annex-enum-item">
|
||||
<span class="annex-enum-name">No:</span>
|
||||
<span class="annex-enum-description">The control is not associated with any legal or regulatory obligations.</span>
|
||||
</li>
|
||||
<li class="annex-enum-item">
|
||||
<span class="annex-enum-name">-:</span>
|
||||
<span class="annex-enum-description">Not applicable (control is not applicable).</span>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<div class="annex-subsection-title" style="font-size: 9pt; margin-top: 10px;">Contractual</div>
|
||||
<ul class="annex-enum-list">
|
||||
<li class="annex-enum-item">
|
||||
<span class="annex-enum-name">Yes:</span>
|
||||
<span class="annex-enum-description">The control is linked to one or more contractual obligations.</span>
|
||||
</li>
|
||||
<li class="annex-enum-item">
|
||||
<span class="annex-enum-name">No:</span>
|
||||
<span class="annex-enum-description">The control is not associated with any contractual obligations.</span>
|
||||
</li>
|
||||
<li class="annex-enum-item">
|
||||
<span class="annex-enum-name">-:</span>
|
||||
<span class="annex-enum-description">Not applicable (control is not applicable).</span>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<div class="annex-subsection-title" style="font-size: 9pt; margin-top: 10px;">Best Practice</div>
|
||||
<ul class="annex-enum-list">
|
||||
<li class="annex-enum-item">
|
||||
<span class="annex-enum-name">Yes:</span>
|
||||
<span class="annex-enum-description">The control is designated as a best practice recommendation.</span>
|
||||
</li>
|
||||
<li class="annex-enum-item">
|
||||
<span class="annex-enum-name">No:</span>
|
||||
<span class="annex-enum-description">The control is not designated as a best practice.</span>
|
||||
</li>
|
||||
<li class="annex-enum-item">
|
||||
<span class="annex-enum-name">-:</span>
|
||||
<span class="annex-enum-description">Not applicable (control is not applicable).</span>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<div class="annex-subsection-title" style="font-size: 9pt; margin-top: 10px;">Risk Assessment</div>
|
||||
<ul class="annex-enum-list">
|
||||
<li class="annex-enum-item">
|
||||
<span class="annex-enum-name">Yes:</span>
|
||||
<span class="annex-enum-description">The control is associated with one or more identified risks through risk mitigation measures.</span>
|
||||
</li>
|
||||
<li class="annex-enum-item">
|
||||
<span class="annex-enum-name">No:</span>
|
||||
<span class="annex-enum-description">The control is not currently associated with any identified risks.</span>
|
||||
</li>
|
||||
<li class="annex-enum-item">
|
||||
<span class="annex-enum-name">-:</span>
|
||||
<span class="annex-enum-description">Not applicable (control is not applicable).</span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -39,6 +39,7 @@ type (
|
||||
SectionTitle string
|
||||
Status *coredata.ControlStatus
|
||||
ExclusionJustification *string
|
||||
BestPractice bool
|
||||
}
|
||||
|
||||
UpdateControlRequest struct {
|
||||
@@ -48,6 +49,7 @@ type (
|
||||
SectionTitle *string
|
||||
Status *coredata.ControlStatus
|
||||
ExclusionJustification *string
|
||||
BestPractice *bool
|
||||
}
|
||||
)
|
||||
|
||||
@@ -587,6 +589,81 @@ func (s ControlService) DeleteAuditMapping(
|
||||
return control, audit, nil
|
||||
}
|
||||
|
||||
func (s ControlService) CreateObligationMapping(
|
||||
ctx context.Context,
|
||||
controlID gid.GID,
|
||||
obligationID gid.GID,
|
||||
) (*coredata.Control, *coredata.Obligation, error) {
|
||||
control := &coredata.Control{}
|
||||
obligation := &coredata.Obligation{}
|
||||
|
||||
err := s.svc.pg.WithConn(
|
||||
ctx,
|
||||
func(conn pg.Conn) error {
|
||||
if err := control.LoadByID(ctx, conn, s.svc.scope, controlID); err != nil {
|
||||
return fmt.Errorf("cannot load control: %w", err)
|
||||
}
|
||||
|
||||
if err := obligation.LoadByID(ctx, conn, s.svc.scope, obligationID); err != nil {
|
||||
return fmt.Errorf("cannot load obligation: %w", err)
|
||||
}
|
||||
|
||||
controlObligation := &coredata.ControlObligation{
|
||||
ControlID: controlID,
|
||||
ObligationID: obligationID,
|
||||
CreatedAt: time.Now(),
|
||||
}
|
||||
|
||||
if err := controlObligation.Upsert(ctx, conn, s.svc.scope); err != nil {
|
||||
return fmt.Errorf("cannot create control obligation mapping: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
return control, obligation, nil
|
||||
}
|
||||
|
||||
func (s ControlService) DeleteObligationMapping(
|
||||
ctx context.Context,
|
||||
controlID gid.GID,
|
||||
obligationID gid.GID,
|
||||
) (*coredata.Control, *coredata.Obligation, error) {
|
||||
control := &coredata.Control{}
|
||||
obligation := &coredata.Obligation{}
|
||||
|
||||
err := s.svc.pg.WithConn(
|
||||
ctx,
|
||||
func(conn pg.Conn) error {
|
||||
if err := control.LoadByID(ctx, conn, s.svc.scope, controlID); err != nil {
|
||||
return fmt.Errorf("cannot load control: %w", err)
|
||||
}
|
||||
|
||||
if err := obligation.LoadByID(ctx, conn, s.svc.scope, obligationID); err != nil {
|
||||
return fmt.Errorf("cannot load obligation: %w", err)
|
||||
}
|
||||
|
||||
controlObligation := &coredata.ControlObligation{}
|
||||
if err := controlObligation.Delete(ctx, conn, s.svc.scope, control.ID, obligation.ID); err != nil {
|
||||
return fmt.Errorf("cannot delete control obligation mapping: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("cannot delete control obligation mapping: %w", err)
|
||||
}
|
||||
|
||||
return control, obligation, nil
|
||||
}
|
||||
|
||||
func (s ControlService) ListForAuditID(
|
||||
ctx context.Context,
|
||||
auditID gid.GID,
|
||||
@@ -723,6 +800,63 @@ func (s ControlService) ListForSnapshotID(
|
||||
return page.NewPage([]*coredata.Control(controls), cursor), nil
|
||||
}
|
||||
|
||||
func (s ControlService) CountForStateOfApplicabilityID(
|
||||
ctx context.Context,
|
||||
stateOfApplicabilityID gid.GID,
|
||||
filter *coredata.ControlFilter,
|
||||
) (int, error) {
|
||||
var count int
|
||||
|
||||
err := s.svc.pg.WithConn(
|
||||
ctx,
|
||||
func(conn pg.Conn) (err error) {
|
||||
controls := &coredata.Controls{}
|
||||
count, err = controls.CountByStateOfApplicabilityID(ctx, conn, s.svc.scope, stateOfApplicabilityID, filter)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot count controls: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("cannot count controls: %w", err)
|
||||
}
|
||||
|
||||
return count, nil
|
||||
}
|
||||
|
||||
func (s ControlService) ListForStateOfApplicabilityID(
|
||||
ctx context.Context,
|
||||
stateOfApplicabilityID gid.GID,
|
||||
cursor *page.Cursor[coredata.ControlOrderField],
|
||||
filter *coredata.ControlFilter,
|
||||
) (*page.Page[*coredata.Control, coredata.ControlOrderField], error) {
|
||||
var controls coredata.Controls
|
||||
stateOfApplicability := &coredata.StateOfApplicability{}
|
||||
|
||||
err := s.svc.pg.WithConn(
|
||||
ctx,
|
||||
func(conn pg.Conn) error {
|
||||
if err := stateOfApplicability.LoadByID(ctx, conn, s.svc.scope, stateOfApplicabilityID); err != nil {
|
||||
return fmt.Errorf("cannot load state of applicability: %w", err)
|
||||
}
|
||||
if err := controls.LoadByStateOfApplicabilityID(ctx, conn, s.svc.scope, stateOfApplicabilityID, cursor, filter); err != nil {
|
||||
return fmt.Errorf("cannot load controls: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return page.NewPage([]*coredata.Control(controls), cursor), nil
|
||||
}
|
||||
|
||||
func (s ControlService) Create(
|
||||
ctx context.Context,
|
||||
req CreateControlRequest,
|
||||
@@ -742,6 +876,7 @@ func (s ControlService) Create(
|
||||
SectionTitle: req.SectionTitle,
|
||||
Status: *req.Status,
|
||||
ExclusionJustification: req.ExclusionJustification,
|
||||
BestPractice: req.BestPractice,
|
||||
CreatedAt: now,
|
||||
UpdatedAt: now,
|
||||
}
|
||||
@@ -822,6 +957,10 @@ func (s ControlService) Update(
|
||||
control.ExclusionJustification = req.ExclusionJustification
|
||||
}
|
||||
|
||||
if req.BestPractice != nil {
|
||||
control.BestPractice = *req.BestPractice
|
||||
}
|
||||
|
||||
control.UpdatedAt = time.Now()
|
||||
|
||||
return control.Update(ctx, conn, s.svc.scope)
|
||||
|
||||
@@ -329,9 +329,7 @@ func (s *DataProtectionImpactAssessmentService) ExportPDF(
|
||||
horizontalLogoBase64 := ""
|
||||
if organization.HorizontalLogoFileID != nil {
|
||||
fileRecord := &coredata.File{}
|
||||
fileErr := s.svc.pg.WithConn(ctx, func(conn pg.Conn) error {
|
||||
return fileRecord.LoadByID(ctx, conn, s.svc.scope, *organization.HorizontalLogoFileID)
|
||||
})
|
||||
fileErr := fileRecord.LoadByID(ctx, conn, s.svc.scope, *organization.HorizontalLogoFileID)
|
||||
if fileErr == nil {
|
||||
base64Data, mimeType, logoErr := s.svc.fileManager.GetFileBase64(ctx, fileRecord)
|
||||
if logoErr == nil {
|
||||
|
||||
@@ -1640,9 +1640,7 @@ func exportDocumentPDF(
|
||||
horizontalLogoBase64 := ""
|
||||
if organization.HorizontalLogoFileID != nil {
|
||||
fileRecord := &coredata.File{}
|
||||
fileErr := svc.pg.WithConn(ctx, func(conn pg.Conn) error {
|
||||
return fileRecord.LoadByID(ctx, conn, scope, *organization.HorizontalLogoFileID)
|
||||
})
|
||||
fileErr := fileRecord.LoadByID(ctx, conn, scope, *organization.HorizontalLogoFileID)
|
||||
if fileErr == nil {
|
||||
base64Data, mimeType, logoErr := svc.fileManager.GetFileBase64(ctx, fileRecord)
|
||||
if logoErr == nil {
|
||||
|
||||
@@ -72,9 +72,10 @@ type (
|
||||
Dark string `json:"dark"`
|
||||
} `json:"logo,omitempty"`
|
||||
Controls []struct {
|
||||
ID string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Description string `json:"description"`
|
||||
ID string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Description string `json:"description"`
|
||||
BestPractice *bool `json:"best_practice,omitempty"`
|
||||
} `json:"controls"`
|
||||
}
|
||||
}
|
||||
@@ -572,6 +573,10 @@ func (s FrameworkService) Import(
|
||||
|
||||
now := time.Now()
|
||||
description := control.Description
|
||||
bestPractice := true
|
||||
if control.BestPractice != nil {
|
||||
bestPractice = *control.BestPractice
|
||||
}
|
||||
control := &coredata.Control{
|
||||
ID: controlID,
|
||||
FrameworkID: frameworkID,
|
||||
@@ -580,6 +585,7 @@ func (s FrameworkService) Import(
|
||||
Name: control.Name,
|
||||
Description: &description,
|
||||
Status: coredata.ControlStatusIncluded,
|
||||
BestPractice: bestPractice,
|
||||
CreatedAt: now,
|
||||
UpdatedAt: now,
|
||||
}
|
||||
|
||||
@@ -41,7 +41,8 @@ type (
|
||||
OwnerID gid.GID
|
||||
LastReviewDate *time.Time
|
||||
DueDate *time.Time
|
||||
Status *coredata.ObligationStatus
|
||||
Status coredata.ObligationStatus
|
||||
Type coredata.ObligationType
|
||||
}
|
||||
|
||||
UpdateObligationRequest struct {
|
||||
@@ -55,6 +56,7 @@ type (
|
||||
LastReviewDate **time.Time
|
||||
DueDate **time.Time
|
||||
Status *coredata.ObligationStatus
|
||||
Type *coredata.ObligationType
|
||||
}
|
||||
)
|
||||
|
||||
@@ -69,6 +71,7 @@ func (cor *CreateObligationRequest) Validate() error {
|
||||
v.Check(cor.Regulator, "regulator", validator.SafeText(TitleMaxLength))
|
||||
v.Check(cor.OwnerID, "owner_id", validator.Required(), validator.GID(coredata.PeopleEntityType))
|
||||
v.Check(cor.Status, "status", validator.OneOfSlice(coredata.ObligationStatuses()))
|
||||
v.Check(cor.Type, "type", validator.OneOfSlice(coredata.ObligationTypes()))
|
||||
|
||||
return v.Error()
|
||||
}
|
||||
@@ -84,6 +87,7 @@ func (uor *UpdateObligationRequest) Validate() error {
|
||||
v.Check(uor.Regulator, "regulator", validator.SafeText(NameMaxLength))
|
||||
v.Check(uor.OwnerID, "owner_id", validator.GID(coredata.PeopleEntityType))
|
||||
v.Check(uor.Status, "status", validator.OneOfSlice(coredata.ObligationStatuses()))
|
||||
v.Check(uor.Type, "type", validator.OneOfSlice(coredata.ObligationTypes()))
|
||||
|
||||
return v.Error()
|
||||
}
|
||||
@@ -133,7 +137,8 @@ func (s *ObligationService) Create(
|
||||
OwnerID: req.OwnerID,
|
||||
LastReviewDate: req.LastReviewDate,
|
||||
DueDate: req.DueDate,
|
||||
Status: *req.Status,
|
||||
Status: req.Status,
|
||||
Type: req.Type,
|
||||
CreatedAt: now,
|
||||
UpdatedAt: now,
|
||||
}
|
||||
@@ -223,6 +228,10 @@ func (s *ObligationService) Update(
|
||||
obligation.Status = *req.Status
|
||||
}
|
||||
|
||||
if req.Type != nil {
|
||||
obligation.Type = *req.Type
|
||||
}
|
||||
|
||||
obligation.UpdatedAt = time.Now()
|
||||
|
||||
if err := obligation.Update(ctx, conn, s.svc.scope); err != nil {
|
||||
@@ -290,6 +299,38 @@ func (s ObligationService) CountForOrganizationID(
|
||||
return count, nil
|
||||
}
|
||||
|
||||
func (s ObligationService) ListForControlID(
|
||||
ctx context.Context,
|
||||
controlID gid.GID,
|
||||
cursor *page.Cursor[coredata.ObligationOrderField],
|
||||
filter *coredata.ObligationFilter,
|
||||
) (*page.Page[*coredata.Obligation, coredata.ObligationOrderField], error) {
|
||||
var obligations coredata.Obligations
|
||||
control := &coredata.Control{}
|
||||
|
||||
err := s.svc.pg.WithConn(
|
||||
ctx,
|
||||
func(conn pg.Conn) error {
|
||||
if err := control.LoadByID(ctx, conn, s.svc.scope, controlID); err != nil {
|
||||
return fmt.Errorf("cannot load control: %w", err)
|
||||
}
|
||||
|
||||
err := obligations.LoadByControlID(ctx, conn, s.svc.scope, control.ID, cursor, filter)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot load obligations: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return page.NewPage(obligations, cursor), nil
|
||||
}
|
||||
|
||||
func (s ObligationService) ListForOrganizationID(
|
||||
ctx context.Context,
|
||||
organizationID gid.GID,
|
||||
|
||||
@@ -415,9 +415,7 @@ func (s *ProcessingActivityService) ExportPDF(
|
||||
horizontalLogoBase64 := ""
|
||||
if organization.HorizontalLogoFileID != nil {
|
||||
fileRecord := &coredata.File{}
|
||||
fileErr := s.svc.pg.WithConn(ctx, func(conn pg.Conn) error {
|
||||
return fileRecord.LoadByID(ctx, conn, s.svc.scope, *organization.HorizontalLogoFileID)
|
||||
})
|
||||
fileErr := fileRecord.LoadByID(ctx, conn, s.svc.scope, *organization.HorizontalLogoFileID)
|
||||
if fileErr == nil {
|
||||
base64Data, mimeType, logoErr := s.svc.fileManager.GetFileBase64(ctx, fileRecord)
|
||||
if logoErr == nil {
|
||||
|
||||
@@ -117,6 +117,7 @@ type (
|
||||
ProcessingActivities *ProcessingActivityService
|
||||
DataProtectionImpactAssessments *DataProtectionImpactAssessmentService
|
||||
TransferImpactAssessments *TransferImpactAssessmentService
|
||||
StatesOfApplicability *StateOfApplicabilityService
|
||||
Files *FileService
|
||||
CustomDomains *CustomDomainService
|
||||
SlackMessages *slack.SlackMessageService
|
||||
@@ -264,6 +265,10 @@ func (s *Service) WithTenant(tenantID gid.TenantID) *TenantService {
|
||||
svc: tenantService,
|
||||
html2pdfConverter: s.html2pdfConverter,
|
||||
}
|
||||
tenantService.StatesOfApplicability = &StateOfApplicabilityService{
|
||||
svc: tenantService,
|
||||
html2pdfConverter: s.html2pdfConverter,
|
||||
}
|
||||
tenantService.Files = &FileService{svc: tenantService}
|
||||
tenantService.CustomDomains = &CustomDomainService{
|
||||
svc: tenantService,
|
||||
|
||||
589
pkg/probo/state_of_applicability_service.go
Normal file
589
pkg/probo/state_of_applicability_service.go
Normal file
@@ -0,0 +1,589 @@
|
||||
// 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 probo
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"time"
|
||||
|
||||
"go.gearno.de/kit/pg"
|
||||
"go.probo.inc/probo/pkg/coredata"
|
||||
"go.probo.inc/probo/pkg/docgen"
|
||||
"go.probo.inc/probo/pkg/gid"
|
||||
"go.probo.inc/probo/pkg/html2pdf"
|
||||
"go.probo.inc/probo/pkg/page"
|
||||
"go.probo.inc/probo/pkg/validator"
|
||||
)
|
||||
|
||||
type StateOfApplicabilityService struct {
|
||||
svc *TenantService
|
||||
html2pdfConverter *html2pdf.Converter
|
||||
}
|
||||
|
||||
type (
|
||||
CreateStateOfApplicabilityRequest struct {
|
||||
OrganizationID gid.GID
|
||||
Name string
|
||||
OwnerID gid.GID
|
||||
}
|
||||
|
||||
UpdateStateOfApplicabilityRequest struct {
|
||||
StateOfApplicabilityID gid.GID
|
||||
Name *string
|
||||
OwnerID *gid.GID
|
||||
}
|
||||
)
|
||||
|
||||
func (csr *CreateStateOfApplicabilityRequest) Validate() error {
|
||||
v := validator.New()
|
||||
|
||||
v.Check(csr.OrganizationID, "organization_id", validator.Required(), validator.GID(coredata.OrganizationEntityType))
|
||||
v.Check(csr.Name, "name", validator.SafeTextNoNewLine(TitleMaxLength))
|
||||
v.Check(csr.OwnerID, "owner_id", validator.Required(), validator.GID(coredata.PeopleEntityType))
|
||||
|
||||
return v.Error()
|
||||
}
|
||||
|
||||
func (usr *UpdateStateOfApplicabilityRequest) Validate() error {
|
||||
v := validator.New()
|
||||
|
||||
v.Check(usr.StateOfApplicabilityID, "state_of_applicability_id", validator.Required(), validator.GID(coredata.StateOfApplicabilityEntityType))
|
||||
v.Check(usr.Name, "name", validator.SafeTextNoNewLine(TitleMaxLength))
|
||||
v.Check(usr.OwnerID, "owner_id", validator.GID(coredata.PeopleEntityType))
|
||||
|
||||
return v.Error()
|
||||
}
|
||||
|
||||
func (s StateOfApplicabilityService) ListForOrganizationID(
|
||||
ctx context.Context,
|
||||
organizationID gid.GID,
|
||||
cursor *page.Cursor[coredata.StateOfApplicabilityOrderField],
|
||||
filter *coredata.StateOfApplicabilityFilter,
|
||||
) (*page.Page[*coredata.StateOfApplicability, coredata.StateOfApplicabilityOrderField], error) {
|
||||
var statesOfApplicability coredata.StatesOfApplicability
|
||||
organization := &coredata.Organization{}
|
||||
|
||||
err := s.svc.pg.WithConn(
|
||||
ctx,
|
||||
func(conn pg.Conn) error {
|
||||
if err := organization.LoadByID(ctx, conn, s.svc.scope, organizationID); err != nil {
|
||||
return fmt.Errorf("cannot load organization: %w", err)
|
||||
}
|
||||
|
||||
err := statesOfApplicability.LoadByOrganizationID(
|
||||
ctx,
|
||||
conn,
|
||||
s.svc.scope,
|
||||
organization.ID,
|
||||
cursor,
|
||||
filter,
|
||||
)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot load states_of_applicability: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return page.NewPage(statesOfApplicability, cursor), nil
|
||||
}
|
||||
|
||||
func (s StateOfApplicabilityService) CountForOrganizationID(
|
||||
ctx context.Context,
|
||||
organizationID gid.GID,
|
||||
filter *coredata.StateOfApplicabilityFilter,
|
||||
) (int, error) {
|
||||
var count int
|
||||
|
||||
err := s.svc.pg.WithConn(
|
||||
ctx,
|
||||
func(conn pg.Conn) (err error) {
|
||||
statesOfApplicability := &coredata.StatesOfApplicability{}
|
||||
count, err = statesOfApplicability.CountByOrganizationID(ctx, conn, s.svc.scope, organizationID, filter)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot count states_of_applicability: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
return count, nil
|
||||
}
|
||||
|
||||
func (s StateOfApplicabilityService) Get(
|
||||
ctx context.Context,
|
||||
stateOfApplicabilityID gid.GID,
|
||||
) (*coredata.StateOfApplicability, error) {
|
||||
stateOfApplicability := &coredata.StateOfApplicability{}
|
||||
|
||||
err := s.svc.pg.WithConn(
|
||||
ctx,
|
||||
func(conn pg.Conn) error {
|
||||
return stateOfApplicability.LoadByID(ctx, conn, s.svc.scope, stateOfApplicabilityID)
|
||||
},
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return stateOfApplicability, nil
|
||||
}
|
||||
|
||||
func (s StateOfApplicabilityService) Create(
|
||||
ctx context.Context,
|
||||
req CreateStateOfApplicabilityRequest,
|
||||
) (*coredata.StateOfApplicability, error) {
|
||||
if err := req.Validate(); err != nil {
|
||||
return nil, fmt.Errorf("invalid request: %w", err)
|
||||
}
|
||||
|
||||
now := time.Now()
|
||||
organization := &coredata.Organization{}
|
||||
|
||||
err := s.svc.pg.WithConn(
|
||||
ctx,
|
||||
func(conn pg.Conn) error {
|
||||
return organization.LoadByID(ctx, conn, s.svc.scope, req.OrganizationID)
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot load organization: %w", err)
|
||||
}
|
||||
|
||||
stateOfApplicabilityID := gid.New(organization.ID.TenantID(), coredata.StateOfApplicabilityEntityType)
|
||||
stateOfApplicability := &coredata.StateOfApplicability{
|
||||
ID: stateOfApplicabilityID,
|
||||
OrganizationID: organization.ID,
|
||||
Name: req.Name,
|
||||
OwnerID: req.OwnerID,
|
||||
CreatedAt: now,
|
||||
UpdatedAt: now,
|
||||
}
|
||||
|
||||
err = s.svc.pg.WithTx(
|
||||
ctx,
|
||||
func(conn pg.Conn) error {
|
||||
if err := stateOfApplicability.Insert(ctx, conn, s.svc.scope); err != nil {
|
||||
return fmt.Errorf("cannot insert state_of_applicability: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return stateOfApplicability, nil
|
||||
}
|
||||
|
||||
func (s StateOfApplicabilityService) Update(
|
||||
ctx context.Context,
|
||||
req UpdateStateOfApplicabilityRequest,
|
||||
) (*coredata.StateOfApplicability, error) {
|
||||
if err := req.Validate(); err != nil {
|
||||
return nil, fmt.Errorf("invalid request: %w", err)
|
||||
}
|
||||
|
||||
stateOfApplicability := &coredata.StateOfApplicability{}
|
||||
|
||||
err := s.svc.pg.WithTx(
|
||||
ctx,
|
||||
func(conn pg.Conn) error {
|
||||
if err := stateOfApplicability.LoadByID(ctx, conn, s.svc.scope, req.StateOfApplicabilityID); err != nil {
|
||||
return fmt.Errorf("cannot load state_of_applicability: %w", err)
|
||||
}
|
||||
|
||||
if req.Name != nil {
|
||||
stateOfApplicability.Name = *req.Name
|
||||
}
|
||||
if req.OwnerID != nil {
|
||||
stateOfApplicability.OwnerID = *req.OwnerID
|
||||
}
|
||||
|
||||
stateOfApplicability.UpdatedAt = time.Now()
|
||||
|
||||
if err := stateOfApplicability.Update(ctx, conn, s.svc.scope); err != nil {
|
||||
return fmt.Errorf("cannot update state_of_applicability: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return stateOfApplicability, nil
|
||||
}
|
||||
|
||||
func (s StateOfApplicabilityService) Delete(
|
||||
ctx context.Context,
|
||||
stateOfApplicabilityID gid.GID,
|
||||
) error {
|
||||
stateOfApplicability := &coredata.StateOfApplicability{ID: stateOfApplicabilityID}
|
||||
|
||||
err := s.svc.pg.WithTx(
|
||||
ctx,
|
||||
func(conn pg.Conn) error {
|
||||
if err := stateOfApplicability.LoadByID(ctx, conn, s.svc.scope, stateOfApplicabilityID); err != nil {
|
||||
return fmt.Errorf("cannot load state_of_applicability: %w", err)
|
||||
}
|
||||
|
||||
if err := stateOfApplicability.Delete(ctx, conn, s.svc.scope); err != nil {
|
||||
return fmt.Errorf("cannot delete state_of_applicability: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s StateOfApplicabilityService) ListAvailableControls(
|
||||
ctx context.Context,
|
||||
stateOfApplicabilityID gid.GID,
|
||||
) ([]*coredata.AvailableStateOfApplicabilityControl, error) {
|
||||
var availableControls coredata.AvailableStateOfApplicabilityControls
|
||||
|
||||
err := s.svc.pg.WithConn(
|
||||
ctx,
|
||||
func(conn pg.Conn) error {
|
||||
if err := availableControls.LoadAvailableByStateOfApplicabilityID(ctx, conn, s.svc.scope, stateOfApplicabilityID); err != nil {
|
||||
return fmt.Errorf("cannot load available controls: %w", err)
|
||||
}
|
||||
return nil
|
||||
},
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return availableControls, nil
|
||||
}
|
||||
|
||||
func (s StateOfApplicabilityService) LinkControl(
|
||||
ctx context.Context,
|
||||
stateOfApplicabilityID gid.GID,
|
||||
controlID gid.GID,
|
||||
applicability bool,
|
||||
justification *string,
|
||||
) (*coredata.StateOfApplicabilityControl, error) {
|
||||
stateOfApplicability := &coredata.StateOfApplicability{}
|
||||
err := s.svc.pg.WithConn(ctx, func(conn pg.Conn) error {
|
||||
return stateOfApplicability.LoadByID(ctx, conn, s.svc.scope, stateOfApplicabilityID)
|
||||
})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot load state of applicability: %w", err)
|
||||
}
|
||||
|
||||
now := time.Now()
|
||||
control := &coredata.StateOfApplicabilityControl{
|
||||
ID: gid.New(s.svc.scope.GetTenantID(), coredata.StateOfApplicabilityControlEntityType),
|
||||
StateOfApplicabilityID: stateOfApplicabilityID,
|
||||
ControlID: controlID,
|
||||
OrganizationID: stateOfApplicability.OrganizationID,
|
||||
Applicability: applicability,
|
||||
Justification: justification,
|
||||
CreatedAt: now,
|
||||
UpdatedAt: now,
|
||||
}
|
||||
|
||||
err = s.svc.pg.WithTx(ctx, func(conn pg.Conn) error {
|
||||
return control.Upsert(ctx, conn, s.svc.scope)
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return control, nil
|
||||
}
|
||||
|
||||
func (s StateOfApplicabilityService) DeleteControlLink(
|
||||
ctx context.Context,
|
||||
stateOfApplicabilityID gid.GID,
|
||||
controlID gid.GID,
|
||||
) (gid.GID, error) {
|
||||
control := &coredata.StateOfApplicabilityControl{}
|
||||
|
||||
err := s.svc.pg.WithTx(ctx, func(conn pg.Conn) error {
|
||||
if err := control.LoadByStateOfApplicabilityIDAndControlID(ctx, conn, s.svc.scope, stateOfApplicabilityID, controlID); err != nil {
|
||||
return err
|
||||
}
|
||||
return control.Delete(ctx, conn, s.svc.scope)
|
||||
})
|
||||
if err != nil {
|
||||
return gid.GID{}, err
|
||||
}
|
||||
|
||||
return control.ID, nil
|
||||
}
|
||||
|
||||
func (s StateOfApplicabilityService) ListControlLinks(
|
||||
ctx context.Context,
|
||||
controlID gid.GID,
|
||||
cursor *page.Cursor[coredata.StateOfApplicabilityOrderField],
|
||||
) (*page.Page[*coredata.StateOfApplicabilityControl, coredata.StateOfApplicabilityOrderField], error) {
|
||||
var controls coredata.StateOfApplicabilityControls
|
||||
|
||||
err := s.svc.pg.WithConn(ctx, func(conn pg.Conn) error {
|
||||
return controls.LoadByControlID(ctx, conn, s.svc.scope, controlID, cursor)
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return page.NewPage(controls, cursor), nil
|
||||
}
|
||||
|
||||
func (s StateOfApplicabilityService) ExportPDF(
|
||||
ctx context.Context,
|
||||
stateOfApplicabilityID gid.GID,
|
||||
) ([]byte, error) {
|
||||
var documentData docgen.StateOfApplicabilityData
|
||||
|
||||
err := s.svc.pg.WithConn(
|
||||
ctx,
|
||||
func(conn pg.Conn) error {
|
||||
stateOfApplicability := &coredata.StateOfApplicability{}
|
||||
if err := stateOfApplicability.LoadByID(ctx, conn, s.svc.scope, stateOfApplicabilityID); err != nil {
|
||||
return fmt.Errorf("cannot load state of applicability: %w", err)
|
||||
}
|
||||
|
||||
organization := &coredata.Organization{}
|
||||
if err := organization.LoadByID(ctx, conn, s.svc.scope, stateOfApplicability.OrganizationID); err != nil {
|
||||
return fmt.Errorf("cannot load organization: %w", err)
|
||||
}
|
||||
|
||||
owner := &coredata.People{}
|
||||
if err := owner.LoadByID(ctx, conn, s.svc.scope, stateOfApplicability.OwnerID); err != nil {
|
||||
return fmt.Errorf("cannot load owner: %w", err)
|
||||
}
|
||||
|
||||
var availableControls coredata.AvailableStateOfApplicabilityControls
|
||||
if err := availableControls.LoadAvailableByStateOfApplicabilityID(ctx, conn, s.svc.scope, stateOfApplicabilityID); err != nil {
|
||||
return fmt.Errorf("cannot load available controls: %w", err)
|
||||
}
|
||||
|
||||
linkedControls := make([]*coredata.AvailableStateOfApplicabilityControl, 0)
|
||||
for _, ctrl := range availableControls {
|
||||
if ctrl.StateOfApplicabilityID != nil {
|
||||
linkedControls = append(linkedControls, ctrl)
|
||||
}
|
||||
}
|
||||
|
||||
obligationsByControl := make(map[gid.GID][]coredata.ObligationType)
|
||||
if len(linkedControls) > 0 {
|
||||
controlIDs := make([]gid.GID, len(linkedControls))
|
||||
for i, ctrl := range linkedControls {
|
||||
controlIDs[i] = ctrl.ControlID
|
||||
}
|
||||
|
||||
var controlObligationTypes coredata.ControlObligationTypes
|
||||
if err := controlObligationTypes.LoadTypesByControlIDs(ctx, conn, s.svc.scope, controlIDs); err != nil {
|
||||
return fmt.Errorf("cannot load control obligations: %w", err)
|
||||
}
|
||||
|
||||
for _, cot := range controlObligationTypes {
|
||||
obligationsByControl[cot.ControlID] = append(obligationsByControl[cot.ControlID], cot.Type)
|
||||
}
|
||||
}
|
||||
|
||||
controlsWithRisks := make(map[gid.GID]bool)
|
||||
if len(linkedControls) > 0 {
|
||||
controlIDs := make([]gid.GID, len(linkedControls))
|
||||
for i, ctrl := range linkedControls {
|
||||
controlIDs[i] = ctrl.ControlID
|
||||
}
|
||||
|
||||
var controlsWithRisk coredata.ControlsWithRisk
|
||||
if err := controlsWithRisk.LoadByControlIDs(ctx, conn, s.svc.scope, controlIDs); err != nil {
|
||||
return fmt.Errorf("cannot load controls with risks: %w", err)
|
||||
}
|
||||
|
||||
for _, cwr := range controlsWithRisk {
|
||||
controlsWithRisks[cwr.ControlID] = true
|
||||
}
|
||||
}
|
||||
|
||||
frameworkControlsMap := make(map[string][]docgen.ControlData)
|
||||
frameworkOrder := []string{}
|
||||
|
||||
for _, ctrl := range linkedControls {
|
||||
if _, exists := frameworkControlsMap[ctrl.FrameworkName]; !exists {
|
||||
frameworkOrder = append(frameworkOrder, ctrl.FrameworkName)
|
||||
frameworkControlsMap[ctrl.FrameworkName] = []docgen.ControlData{}
|
||||
}
|
||||
|
||||
var regulatory *bool
|
||||
var contractual *bool
|
||||
var riskAssessment *bool
|
||||
|
||||
if ctrl.Applicability != nil && *ctrl.Applicability {
|
||||
falseVal := false
|
||||
trueVal := true
|
||||
|
||||
regulatory = &falseVal
|
||||
contractual = &falseVal
|
||||
riskAssessment = &falseVal
|
||||
|
||||
obligations := obligationsByControl[ctrl.ControlID]
|
||||
for _, obligationType := range obligations {
|
||||
if obligationType == coredata.ObligationTypeLegal {
|
||||
regulatory = &trueVal
|
||||
}
|
||||
if obligationType == coredata.ObligationTypeContractual {
|
||||
contractual = &trueVal
|
||||
}
|
||||
}
|
||||
|
||||
if controlsWithRisks[ctrl.ControlID] {
|
||||
riskAssessment = &trueVal
|
||||
}
|
||||
}
|
||||
|
||||
frameworkControlsMap[ctrl.FrameworkName] = append(
|
||||
frameworkControlsMap[ctrl.FrameworkName],
|
||||
docgen.ControlData{
|
||||
FrameworkName: ctrl.FrameworkName,
|
||||
SectionTitle: ctrl.SectionTitle,
|
||||
Name: ctrl.Name,
|
||||
Applicability: ctrl.Applicability,
|
||||
Justification: ctrl.Justification,
|
||||
BestPractice: ctrl.BestPractice,
|
||||
Regulatory: regulatory,
|
||||
Contractual: contractual,
|
||||
RiskAssessment: riskAssessment,
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
frameworkGroups := make([]docgen.FrameworkControlGroup, len(frameworkOrder))
|
||||
for i, frameworkName := range frameworkOrder {
|
||||
frameworkGroups[i] = docgen.FrameworkControlGroup{
|
||||
FrameworkName: frameworkName,
|
||||
Controls: frameworkControlsMap[frameworkName],
|
||||
}
|
||||
}
|
||||
|
||||
var snapshots coredata.Snapshots
|
||||
snapshotType := coredata.SnapshotsTypeStatesOfApplicability
|
||||
|
||||
var version int
|
||||
var publishedAt time.Time
|
||||
|
||||
if stateOfApplicability.SnapshotID != nil {
|
||||
snapshot := &coredata.Snapshot{}
|
||||
if err := snapshot.LoadByID(ctx, conn, s.svc.scope, *stateOfApplicability.SnapshotID); err != nil {
|
||||
return fmt.Errorf("cannot load snapshot: %w", err)
|
||||
}
|
||||
publishedAt = snapshot.CreatedAt
|
||||
snapshotFilter := coredata.NewSnapshotFilter(&snapshotType).WithBeforeDate(&snapshot.CreatedAt)
|
||||
snapshotCount, err := snapshots.CountByOrganizationID(ctx, conn, s.svc.scope, stateOfApplicability.OrganizationID, snapshotFilter)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot count states of applicability snapshots: %w", err)
|
||||
}
|
||||
version = snapshotCount
|
||||
} else {
|
||||
publishedAt = time.Now()
|
||||
snapshotFilter := coredata.NewSnapshotFilter(&snapshotType)
|
||||
snapshotCount, err := snapshots.CountByOrganizationID(ctx, conn, s.svc.scope, stateOfApplicability.OrganizationID, snapshotFilter)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot count states of applicability snapshots: %w", err)
|
||||
}
|
||||
version = snapshotCount + 1
|
||||
}
|
||||
|
||||
horizontalLogoBase64 := ""
|
||||
if organization.HorizontalLogoFileID != nil {
|
||||
fileRecord := &coredata.File{}
|
||||
fileErr := fileRecord.LoadByID(ctx, conn, s.svc.scope, *organization.HorizontalLogoFileID)
|
||||
if fileErr == nil {
|
||||
base64Data, mimeType, logoErr := s.svc.fileManager.GetFileBase64(ctx, fileRecord)
|
||||
if logoErr == nil {
|
||||
horizontalLogoBase64 = fmt.Sprintf("data:%s;base64,%s", mimeType, base64Data)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
documentData = docgen.StateOfApplicabilityData{
|
||||
Title: stateOfApplicability.Name,
|
||||
OrganizationName: organization.Name,
|
||||
CreatedAt: stateOfApplicability.CreatedAt,
|
||||
TotalControls: len(linkedControls),
|
||||
FrameworkGroups: frameworkGroups,
|
||||
CompanyHorizontalLogoBase64: horizontalLogoBase64,
|
||||
Version: version,
|
||||
PublishedAt: publishedAt,
|
||||
Approver: owner.FullName,
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
htmlData, err := docgen.RenderStateOfApplicabilityHTML(documentData)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot render HTML: %w", err)
|
||||
}
|
||||
|
||||
cfg := html2pdf.RenderConfig{
|
||||
PageFormat: html2pdf.PageFormatA4,
|
||||
Orientation: html2pdf.OrientationPortrait,
|
||||
MarginTop: html2pdf.NewMarginInches(1.0),
|
||||
MarginBottom: html2pdf.NewMarginInches(1.0),
|
||||
MarginLeft: html2pdf.NewMarginInches(1.0),
|
||||
MarginRight: html2pdf.NewMarginInches(1.0),
|
||||
PrintBackground: true,
|
||||
Scale: 1.0,
|
||||
}
|
||||
|
||||
pdfReader, err := s.html2pdfConverter.GeneratePDF(ctx, htmlData, cfg)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot generate PDF: %w", err)
|
||||
}
|
||||
|
||||
pdfData, err := io.ReadAll(pdfReader)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot read PDF data: %w", err)
|
||||
}
|
||||
|
||||
return pdfData, nil
|
||||
}
|
||||
@@ -329,9 +329,7 @@ func (s *TransferImpactAssessmentService) ExportPDF(
|
||||
horizontalLogoBase64 := ""
|
||||
if organization.HorizontalLogoFileID != nil {
|
||||
fileRecord := &coredata.File{}
|
||||
fileErr := s.svc.pg.WithConn(ctx, func(conn pg.Conn) error {
|
||||
return fileRecord.LoadByID(ctx, conn, s.svc.scope, *organization.HorizontalLogoFileID)
|
||||
})
|
||||
fileErr := fileRecord.LoadByID(ctx, conn, s.svc.scope, *organization.HorizontalLogoFileID)
|
||||
if fileErr == nil {
|
||||
base64Data, mimeType, logoErr := s.svc.fileManager.GetFileBase64(ctx, fileRecord)
|
||||
if logoErr == nil {
|
||||
|
||||
@@ -224,6 +224,14 @@ enum ObligationStatus
|
||||
@goEnum(value: "go.probo.inc/probo/pkg/coredata.ObligationStatusCompliant")
|
||||
}
|
||||
|
||||
enum ObligationType
|
||||
@goModel(model: "go.probo.inc/probo/pkg/coredata.ObligationType") {
|
||||
LEGAL
|
||||
@goEnum(value: "go.probo.inc/probo/pkg/coredata.ObligationTypeLegal")
|
||||
CONTRACTUAL
|
||||
@goEnum(value: "go.probo.inc/probo/pkg/coredata.ObligationTypeContractual")
|
||||
}
|
||||
|
||||
enum ContinualImprovementStatus
|
||||
@goModel(
|
||||
model: "go.probo.inc/probo/pkg/coredata.ContinualImprovementStatus"
|
||||
@@ -1235,6 +1243,10 @@ enum SnapshotsType
|
||||
@goEnum(
|
||||
value: "go.probo.inc/probo/pkg/coredata.SnapshotsTypeProcessingActivities"
|
||||
)
|
||||
STATES_OF_APPLICABILITY
|
||||
@goEnum(
|
||||
value: "go.probo.inc/probo/pkg/coredata.SnapshotsTypeStatesOfApplicability"
|
||||
)
|
||||
}
|
||||
|
||||
enum SnapshotOrderField
|
||||
@@ -1567,6 +1579,10 @@ input DatumFilter {
|
||||
snapshotId: ID
|
||||
}
|
||||
|
||||
input StateOfApplicabilityFilter {
|
||||
snapshotId: ID
|
||||
}
|
||||
|
||||
input NonconformityFilter {
|
||||
snapshotId: ID
|
||||
}
|
||||
@@ -1715,6 +1731,15 @@ type Organization implements Node {
|
||||
orderBy: MeetingOrder
|
||||
): MeetingConnection! @goField(forceResolver: true)
|
||||
|
||||
statesOfApplicability(
|
||||
first: Int
|
||||
after: CursorKey
|
||||
last: Int
|
||||
before: CursorKey
|
||||
orderBy: StateOfApplicabilityOrder
|
||||
filter: StateOfApplicabilityFilter = { snapshotId: null }
|
||||
): StateOfApplicabilityConnection! @goField(forceResolver: true)
|
||||
|
||||
measures(
|
||||
first: Int
|
||||
after: CursorKey
|
||||
@@ -2072,6 +2097,7 @@ type Control implements Node
|
||||
description: String
|
||||
status: ControlStatus!
|
||||
exclusionJustification: String
|
||||
bestPractice: Boolean!
|
||||
|
||||
framework: Framework! @goField(forceResolver: true)
|
||||
|
||||
@@ -2101,6 +2127,15 @@ type Control implements Node
|
||||
orderBy: AuditOrder
|
||||
): AuditConnection! @goField(forceResolver: true)
|
||||
|
||||
obligations(
|
||||
first: Int
|
||||
after: CursorKey
|
||||
last: Int
|
||||
before: CursorKey
|
||||
orderBy: ObligationOrder
|
||||
filter: ObligationFilter
|
||||
): ObligationConnection! @goField(forceResolver: true)
|
||||
|
||||
snapshots(
|
||||
first: Int
|
||||
after: CursorKey
|
||||
@@ -2109,6 +2144,14 @@ type Control implements Node
|
||||
orderBy: SnapshotOrder
|
||||
): SnapshotConnection! @goField(forceResolver: true)
|
||||
|
||||
stateOfApplicabilityControls(
|
||||
first: Int
|
||||
after: CursorKey
|
||||
last: Int
|
||||
before: CursorKey
|
||||
orderBy: StateOfApplicabilityOrder
|
||||
): StateOfApplicabilityControlConnection! @goField(forceResolver: true)
|
||||
|
||||
createdAt: Datetime!
|
||||
updatedAt: Datetime!
|
||||
}
|
||||
@@ -2266,6 +2309,26 @@ type Meeting implements Node {
|
||||
updatedAt: Datetime!
|
||||
}
|
||||
|
||||
type StateOfApplicability implements Node {
|
||||
id: ID!
|
||||
name: String!
|
||||
sourceId: ID
|
||||
snapshotId: ID
|
||||
organization: Organization @goField(forceResolver: true)
|
||||
owner: People! @goField(forceResolver: true)
|
||||
controls(
|
||||
first: Int
|
||||
after: CursorKey
|
||||
last: Int
|
||||
before: CursorKey
|
||||
orderBy: ControlOrder
|
||||
filter: ControlFilter
|
||||
): ControlConnection! @goField(forceResolver: true)
|
||||
availableControls: [AvailableStateOfApplicabilityControl!]! @goField(forceResolver: true)
|
||||
createdAt: Datetime!
|
||||
updatedAt: Datetime!
|
||||
}
|
||||
|
||||
type Risk implements Node {
|
||||
id: ID!
|
||||
snapshotId: ID
|
||||
@@ -2381,6 +2444,7 @@ type Obligation implements Node {
|
||||
lastReviewDate: Datetime
|
||||
dueDate: Datetime
|
||||
status: ObligationStatus!
|
||||
type: ObligationType!
|
||||
createdAt: Datetime!
|
||||
updatedAt: Datetime!
|
||||
}
|
||||
@@ -2835,6 +2899,20 @@ type MeetingEdge {
|
||||
node: Meeting!
|
||||
}
|
||||
|
||||
type StateOfApplicabilityConnection
|
||||
@goModel(
|
||||
model: "go.probo.inc/probo/pkg/server/api/console/v1/types.StateOfApplicabilityConnection"
|
||||
) {
|
||||
totalCount: Int! @goField(forceResolver: true)
|
||||
edges: [StateOfApplicabilityEdge!]!
|
||||
pageInfo: PageInfo!
|
||||
}
|
||||
|
||||
type StateOfApplicabilityEdge {
|
||||
cursor: CursorKey!
|
||||
node: StateOfApplicability!
|
||||
}
|
||||
|
||||
type RiskConnection
|
||||
@goModel(
|
||||
model: "go.probo.inc/probo/pkg/server/api/console/v1/types.RiskConnection"
|
||||
@@ -3191,12 +3269,24 @@ type Mutation {
|
||||
deleteControlDocumentMapping(
|
||||
input: DeleteControlDocumentMappingInput!
|
||||
): DeleteControlDocumentMappingPayload!
|
||||
createStateOfApplicabilityControlMapping(
|
||||
input: CreateStateOfApplicabilityControlMappingInput!
|
||||
): CreateStateOfApplicabilityControlMappingPayload!
|
||||
deleteStateOfApplicabilityControlMapping(
|
||||
input: DeleteStateOfApplicabilityControlMappingInput!
|
||||
): DeleteStateOfApplicabilityControlMappingPayload!
|
||||
createControlAuditMapping(
|
||||
input: CreateControlAuditMappingInput!
|
||||
): CreateControlAuditMappingPayload!
|
||||
deleteControlAuditMapping(
|
||||
input: DeleteControlAuditMappingInput!
|
||||
): DeleteControlAuditMappingPayload!
|
||||
createControlObligationMapping(
|
||||
input: CreateControlObligationMappingInput!
|
||||
): CreateControlObligationMappingPayload!
|
||||
deleteControlObligationMapping(
|
||||
input: DeleteControlObligationMappingInput!
|
||||
): DeleteControlObligationMappingPayload!
|
||||
createControlSnapshotMapping(
|
||||
input: CreateControlSnapshotMappingInput!
|
||||
): CreateControlSnapshotMappingPayload!
|
||||
@@ -3269,6 +3359,19 @@ type Mutation {
|
||||
createMeeting(input: CreateMeetingInput!): CreateMeetingPayload!
|
||||
updateMeeting(input: UpdateMeetingInput!): UpdateMeetingPayload!
|
||||
deleteMeeting(input: DeleteMeetingInput!): DeleteMeetingPayload!
|
||||
# StateOfApplicability mutations
|
||||
createStateOfApplicability(
|
||||
input: CreateStateOfApplicabilityInput!
|
||||
): CreateStateOfApplicabilityPayload!
|
||||
updateStateOfApplicability(
|
||||
input: UpdateStateOfApplicabilityInput!
|
||||
): UpdateStateOfApplicabilityPayload!
|
||||
deleteStateOfApplicability(
|
||||
input: DeleteStateOfApplicabilityInput!
|
||||
): DeleteStateOfApplicabilityPayload!
|
||||
exportStateOfApplicabilityPDF(
|
||||
input: ExportStateOfApplicabilityPDFInput!
|
||||
): ExportStateOfApplicabilityPDFPayload!
|
||||
publishDocumentVersion(
|
||||
input: PublishDocumentVersionInput!
|
||||
): PublishDocumentVersionPayload!
|
||||
@@ -3762,6 +3865,18 @@ input DeleteControlDocumentMappingInput {
|
||||
documentId: ID!
|
||||
}
|
||||
|
||||
input CreateStateOfApplicabilityControlMappingInput {
|
||||
stateOfApplicabilityId: ID!
|
||||
controlId: ID!
|
||||
applicability: Boolean!
|
||||
justification: String
|
||||
}
|
||||
|
||||
input DeleteStateOfApplicabilityControlMappingInput {
|
||||
stateOfApplicabilityId: ID!
|
||||
controlId: ID!
|
||||
}
|
||||
|
||||
input CreateControlAuditMappingInput {
|
||||
controlId: ID!
|
||||
auditId: ID!
|
||||
@@ -3772,6 +3887,16 @@ input DeleteControlAuditMappingInput {
|
||||
auditId: ID!
|
||||
}
|
||||
|
||||
input CreateControlObligationMappingInput {
|
||||
controlId: ID!
|
||||
obligationId: ID!
|
||||
}
|
||||
|
||||
input DeleteControlObligationMappingInput {
|
||||
controlId: ID!
|
||||
obligationId: ID!
|
||||
}
|
||||
|
||||
input CreateControlSnapshotMappingInput {
|
||||
controlId: ID!
|
||||
snapshotId: ID!
|
||||
@@ -3989,6 +4114,95 @@ input DeleteMeetingInput {
|
||||
meetingId: ID!
|
||||
}
|
||||
|
||||
input CreateStateOfApplicabilityInput {
|
||||
organizationId: ID!
|
||||
name: String!
|
||||
ownerId: ID!
|
||||
}
|
||||
|
||||
input UpdateStateOfApplicabilityInput {
|
||||
id: ID!
|
||||
name: String
|
||||
ownerId: ID
|
||||
}
|
||||
|
||||
input StateOfApplicabilityControlInput {
|
||||
controlId: ID!
|
||||
applicability: Boolean!
|
||||
justification: String
|
||||
}
|
||||
|
||||
type AvailableStateOfApplicabilityControl {
|
||||
controlId: ID!
|
||||
sectionTitle: String!
|
||||
name: String!
|
||||
frameworkId: ID!
|
||||
frameworkName: String!
|
||||
organizationId: ID!
|
||||
stateOfApplicabilityId: ID
|
||||
applicability: Boolean
|
||||
justification: String
|
||||
bestPractice: Boolean!
|
||||
regulatory: Boolean!
|
||||
contractual: Boolean!
|
||||
riskAssessment: Boolean!
|
||||
}
|
||||
|
||||
input DeleteStateOfApplicabilityInput {
|
||||
stateOfApplicabilityId: ID!
|
||||
}
|
||||
|
||||
|
||||
type StateOfApplicabilityControl {
|
||||
id: ID!
|
||||
stateOfApplicabilityId: ID!
|
||||
controlId: ID!
|
||||
stateOfApplicability: StateOfApplicability! @goField(forceResolver: true)
|
||||
applicability: Boolean!
|
||||
justification: String
|
||||
}
|
||||
|
||||
type StateOfApplicabilityControlConnection @goModel(model: "go.probo.inc/probo/pkg/server/api/console/v1/types.StateOfApplicabilityControlConnection") {
|
||||
totalCount: Int!
|
||||
edges: [StateOfApplicabilityControlEdge!]!
|
||||
pageInfo: PageInfo!
|
||||
}
|
||||
|
||||
type StateOfApplicabilityControlEdge @goModel(model: "go.probo.inc/probo/pkg/server/api/console/v1/types.StateOfApplicabilityControlEdge") {
|
||||
cursor: CursorKey!
|
||||
node: StateOfApplicabilityControl!
|
||||
}
|
||||
|
||||
input ExportStateOfApplicabilityPDFInput {
|
||||
stateOfApplicabilityId: ID!
|
||||
}
|
||||
|
||||
type ExportStateOfApplicabilityPDFPayload {
|
||||
data: String!
|
||||
}
|
||||
|
||||
input StateOfApplicabilityOrder
|
||||
@goModel(
|
||||
model: "go.probo.inc/probo/pkg/server/api/console/v1/types.StateOfApplicabilityOrderBy"
|
||||
) {
|
||||
direction: OrderDirection!
|
||||
field: StateOfApplicabilityOrderField!
|
||||
}
|
||||
|
||||
enum StateOfApplicabilityOrderField
|
||||
@goModel(
|
||||
model: "go.probo.inc/probo/pkg/coredata.StateOfApplicabilityOrderField"
|
||||
) {
|
||||
NAME
|
||||
@goEnum(
|
||||
value: "go.probo.inc/probo/pkg/coredata.StateOfApplicabilityOrderFieldName"
|
||||
)
|
||||
CREATED_AT
|
||||
@goEnum(
|
||||
value: "go.probo.inc/probo/pkg/coredata.StateOfApplicabilityOrderFieldCreatedAt"
|
||||
)
|
||||
}
|
||||
|
||||
input ConfirmEmailInput {
|
||||
token: String!
|
||||
}
|
||||
@@ -4027,6 +4241,7 @@ input CreateControlInput {
|
||||
description: String
|
||||
status: ControlStatus!
|
||||
exclusionJustification: String
|
||||
bestPractice: Boolean!
|
||||
}
|
||||
|
||||
input UpdateControlInput {
|
||||
@@ -4036,6 +4251,7 @@ input UpdateControlInput {
|
||||
description: String @goField(omittable: true)
|
||||
status: ControlStatus
|
||||
exclusionJustification: String
|
||||
bestPractice: Boolean
|
||||
}
|
||||
|
||||
input DeleteControlInput {
|
||||
@@ -4119,6 +4335,7 @@ input CreateObligationInput {
|
||||
lastReviewDate: Datetime
|
||||
dueDate: Datetime
|
||||
status: ObligationStatus!
|
||||
type: ObligationType!
|
||||
}
|
||||
|
||||
input UpdateObligationInput {
|
||||
@@ -4132,6 +4349,7 @@ input UpdateObligationInput {
|
||||
lastReviewDate: Datetime @goField(omittable: true)
|
||||
dueDate: Datetime @goField(omittable: true)
|
||||
status: ObligationStatus
|
||||
type: ObligationType
|
||||
}
|
||||
|
||||
input DeleteObligationInput {
|
||||
@@ -4501,6 +4719,16 @@ type DeleteControlDocumentMappingPayload {
|
||||
deletedDocumentId: ID!
|
||||
}
|
||||
|
||||
type CreateStateOfApplicabilityControlMappingPayload {
|
||||
stateOfApplicabilityControlEdge: StateOfApplicabilityControlEdge!
|
||||
}
|
||||
|
||||
type DeleteStateOfApplicabilityControlMappingPayload {
|
||||
deletedStateOfApplicabilityId: ID!
|
||||
deletedControlId: ID!
|
||||
deletedStateOfApplicabilityControlId: ID!
|
||||
}
|
||||
|
||||
type CreateControlAuditMappingPayload {
|
||||
controlEdge: ControlEdge!
|
||||
auditEdge: AuditEdge!
|
||||
@@ -4511,6 +4739,16 @@ type DeleteControlAuditMappingPayload {
|
||||
deletedAuditId: ID!
|
||||
}
|
||||
|
||||
type CreateControlObligationMappingPayload {
|
||||
controlEdge: ControlEdge!
|
||||
obligationEdge: ObligationEdge!
|
||||
}
|
||||
|
||||
type DeleteControlObligationMappingPayload {
|
||||
deletedControlId: ID!
|
||||
deletedObligationId: ID!
|
||||
}
|
||||
|
||||
type CreateControlSnapshotMappingPayload {
|
||||
controlEdge: ControlEdge!
|
||||
snapshotEdge: SnapshotEdge!
|
||||
@@ -4656,6 +4894,18 @@ type DeleteMeetingPayload {
|
||||
deletedMeetingId: ID!
|
||||
}
|
||||
|
||||
type CreateStateOfApplicabilityPayload {
|
||||
stateOfApplicabilityEdge: StateOfApplicabilityEdge!
|
||||
}
|
||||
|
||||
type UpdateStateOfApplicabilityPayload {
|
||||
stateOfApplicability: StateOfApplicability!
|
||||
}
|
||||
|
||||
type DeleteStateOfApplicabilityPayload {
|
||||
deletedStateOfApplicabilityId: ID!
|
||||
}
|
||||
|
||||
type ConfirmEmailPayload {
|
||||
success: Boolean!
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -16,6 +16,7 @@ type Control struct {
|
||||
Description *string `json:"description,omitempty"`
|
||||
Status coredata.ControlStatus `json:"status"`
|
||||
ExclusionJustification *string `json:"exclusionJustification,omitempty"`
|
||||
BestPractice bool `json:"bestPractice"`
|
||||
Framework *Framework `json:"framework"`
|
||||
Measures *MeasureConnection `json:"measures"`
|
||||
Documents *DocumentConnection `json:"documents"`
|
||||
@@ -72,6 +73,7 @@ func NewControl(control *coredata.Control) *Control {
|
||||
Description: control.Description,
|
||||
Status: control.Status,
|
||||
ExclusionJustification: control.ExclusionJustification,
|
||||
BestPractice: control.BestPractice,
|
||||
CreatedAt: control.CreatedAt,
|
||||
UpdatedAt: control.UpdatedAt,
|
||||
}
|
||||
|
||||
75
pkg/server/api/console/v1/types/state_of_applicability.go
Normal file
75
pkg/server/api/console/v1/types/state_of_applicability.go
Normal file
@@ -0,0 +1,75 @@
|
||||
// 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 types
|
||||
|
||||
import (
|
||||
"go.probo.inc/probo/pkg/coredata"
|
||||
"go.probo.inc/probo/pkg/gid"
|
||||
"go.probo.inc/probo/pkg/page"
|
||||
)
|
||||
|
||||
type (
|
||||
StateOfApplicabilityOrderBy OrderBy[coredata.StateOfApplicabilityOrderField]
|
||||
|
||||
StateOfApplicabilityConnection struct {
|
||||
TotalCount int
|
||||
Edges []*StateOfApplicabilityEdge
|
||||
PageInfo PageInfo
|
||||
|
||||
Resolver any
|
||||
ParentID gid.GID
|
||||
Filters *coredata.StateOfApplicabilityFilter
|
||||
}
|
||||
)
|
||||
|
||||
func NewStateOfApplicabilityConnection(
|
||||
p *page.Page[*coredata.StateOfApplicability, coredata.StateOfApplicabilityOrderField],
|
||||
parentType any,
|
||||
parentID gid.GID,
|
||||
filters *coredata.StateOfApplicabilityFilter,
|
||||
) *StateOfApplicabilityConnection {
|
||||
var edges = make([]*StateOfApplicabilityEdge, len(p.Data))
|
||||
|
||||
for i := range edges {
|
||||
edges[i] = NewStateOfApplicabilityEdge(p.Data[i], p.Cursor.OrderBy.Field)
|
||||
}
|
||||
|
||||
return &StateOfApplicabilityConnection{
|
||||
Edges: edges,
|
||||
PageInfo: *NewPageInfo(p),
|
||||
|
||||
Resolver: parentType,
|
||||
ParentID: parentID,
|
||||
Filters: filters,
|
||||
}
|
||||
}
|
||||
|
||||
func NewStateOfApplicabilityEdge(soa *coredata.StateOfApplicability, orderBy coredata.StateOfApplicabilityOrderField) *StateOfApplicabilityEdge {
|
||||
return &StateOfApplicabilityEdge{
|
||||
Cursor: soa.CursorKey(orderBy),
|
||||
Node: NewStateOfApplicability(soa),
|
||||
}
|
||||
}
|
||||
|
||||
func NewStateOfApplicability(soa *coredata.StateOfApplicability) *StateOfApplicability {
|
||||
return &StateOfApplicability{
|
||||
ID: soa.ID,
|
||||
Name: soa.Name,
|
||||
SourceID: soa.SourceID,
|
||||
SnapshotID: soa.SnapshotID,
|
||||
CreatedAt: soa.CreatedAt,
|
||||
UpdatedAt: soa.UpdatedAt,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
// 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 types
|
||||
|
||||
import (
|
||||
"go.probo.inc/probo/pkg/coredata"
|
||||
"go.probo.inc/probo/pkg/gid"
|
||||
"go.probo.inc/probo/pkg/page"
|
||||
)
|
||||
|
||||
type (
|
||||
StateOfApplicabilityControlConnection struct {
|
||||
TotalCount int
|
||||
Edges []*StateOfApplicabilityControlEdge
|
||||
PageInfo PageInfo
|
||||
|
||||
Resolver any
|
||||
ParentID gid.GID
|
||||
}
|
||||
|
||||
StateOfApplicabilityControlEdge struct {
|
||||
Cursor page.CursorKey
|
||||
Node *StateOfApplicabilityControl
|
||||
}
|
||||
)
|
||||
|
||||
func NewStateOfApplicabilityControlConnection(
|
||||
p *page.Page[*coredata.StateOfApplicabilityControl, coredata.StateOfApplicabilityOrderField],
|
||||
parentType any,
|
||||
parentID gid.GID,
|
||||
) *StateOfApplicabilityControlConnection {
|
||||
edges := make([]*StateOfApplicabilityControlEdge, len(p.Data))
|
||||
for i, control := range p.Data {
|
||||
edges[i] = NewStateOfApplicabilityControlEdge(control, p.Cursor.OrderBy.Field)
|
||||
}
|
||||
|
||||
return &StateOfApplicabilityControlConnection{
|
||||
Edges: edges,
|
||||
PageInfo: *NewPageInfo(p),
|
||||
|
||||
Resolver: parentType,
|
||||
ParentID: parentID,
|
||||
}
|
||||
}
|
||||
|
||||
func NewStateOfApplicabilityControlEdge(
|
||||
control *coredata.StateOfApplicabilityControl,
|
||||
orderBy coredata.StateOfApplicabilityOrderField,
|
||||
) *StateOfApplicabilityControlEdge {
|
||||
return &StateOfApplicabilityControlEdge{
|
||||
Cursor: control.CursorKey(orderBy),
|
||||
Node: NewStateOfApplicabilityControl(control),
|
||||
}
|
||||
}
|
||||
|
||||
func NewStateOfApplicabilityControl(control *coredata.StateOfApplicabilityControl) *StateOfApplicabilityControl {
|
||||
return &StateOfApplicabilityControl{
|
||||
ID: control.ID,
|
||||
StateOfApplicabilityID: control.StateOfApplicabilityID,
|
||||
ControlID: control.ControlID,
|
||||
Applicability: control.Applicability,
|
||||
Justification: control.Justification,
|
||||
}
|
||||
}
|
||||
@@ -88,6 +88,22 @@ type AuditEdge struct {
|
||||
Node *Audit `json:"node"`
|
||||
}
|
||||
|
||||
type AvailableStateOfApplicabilityControl struct {
|
||||
ControlID gid.GID `json:"controlId"`
|
||||
SectionTitle string `json:"sectionTitle"`
|
||||
Name string `json:"name"`
|
||||
FrameworkID gid.GID `json:"frameworkId"`
|
||||
FrameworkName string `json:"frameworkName"`
|
||||
OrganizationID gid.GID `json:"organizationId"`
|
||||
StateOfApplicabilityID *gid.GID `json:"stateOfApplicabilityId,omitempty"`
|
||||
Applicability *bool `json:"applicability,omitempty"`
|
||||
Justification *string `json:"justification,omitempty"`
|
||||
BestPractice bool `json:"bestPractice"`
|
||||
Regulatory bool `json:"regulatory"`
|
||||
Contractual bool `json:"contractual"`
|
||||
RiskAssessment bool `json:"riskAssessment"`
|
||||
}
|
||||
|
||||
type BulkDeleteDocumentsInput struct {
|
||||
DocumentIds []gid.GID `json:"documentIds"`
|
||||
}
|
||||
@@ -249,6 +265,7 @@ type CreateControlInput struct {
|
||||
Description *string `json:"description,omitempty"`
|
||||
Status coredata.ControlStatus `json:"status"`
|
||||
ExclusionJustification *string `json:"exclusionJustification,omitempty"`
|
||||
BestPractice bool `json:"bestPractice"`
|
||||
}
|
||||
|
||||
type CreateControlMeasureMappingInput struct {
|
||||
@@ -261,6 +278,16 @@ type CreateControlMeasureMappingPayload struct {
|
||||
MeasureEdge *MeasureEdge `json:"measureEdge"`
|
||||
}
|
||||
|
||||
type CreateControlObligationMappingInput struct {
|
||||
ControlID gid.GID `json:"controlId"`
|
||||
ObligationID gid.GID `json:"obligationId"`
|
||||
}
|
||||
|
||||
type CreateControlObligationMappingPayload struct {
|
||||
ControlEdge *ControlEdge `json:"controlEdge"`
|
||||
ObligationEdge *ObligationEdge `json:"obligationEdge"`
|
||||
}
|
||||
|
||||
type CreateControlPayload struct {
|
||||
ControlEdge *ControlEdge `json:"controlEdge"`
|
||||
}
|
||||
@@ -407,6 +434,7 @@ type CreateObligationInput struct {
|
||||
LastReviewDate *time.Time `json:"lastReviewDate,omitempty"`
|
||||
DueDate *time.Time `json:"dueDate,omitempty"`
|
||||
Status coredata.ObligationStatus `json:"status"`
|
||||
Type coredata.ObligationType `json:"type"`
|
||||
}
|
||||
|
||||
type CreateObligationPayload struct {
|
||||
@@ -560,6 +588,27 @@ type CreateSnapshotPayload struct {
|
||||
SnapshotEdge *SnapshotEdge `json:"snapshotEdge"`
|
||||
}
|
||||
|
||||
type CreateStateOfApplicabilityControlMappingInput struct {
|
||||
StateOfApplicabilityID gid.GID `json:"stateOfApplicabilityId"`
|
||||
ControlID gid.GID `json:"controlId"`
|
||||
Applicability bool `json:"applicability"`
|
||||
Justification *string `json:"justification,omitempty"`
|
||||
}
|
||||
|
||||
type CreateStateOfApplicabilityControlMappingPayload struct {
|
||||
StateOfApplicabilityControlEdge *StateOfApplicabilityControlEdge `json:"stateOfApplicabilityControlEdge"`
|
||||
}
|
||||
|
||||
type CreateStateOfApplicabilityInput struct {
|
||||
OrganizationID gid.GID `json:"organizationId"`
|
||||
Name string `json:"name"`
|
||||
OwnerID gid.GID `json:"ownerId"`
|
||||
}
|
||||
|
||||
type CreateStateOfApplicabilityPayload struct {
|
||||
StateOfApplicabilityEdge *StateOfApplicabilityEdge `json:"stateOfApplicabilityEdge"`
|
||||
}
|
||||
|
||||
type CreateTaskInput struct {
|
||||
OrganizationID gid.GID `json:"organizationId"`
|
||||
MeasureID *gid.GID `json:"measureId,omitempty"`
|
||||
@@ -807,6 +856,16 @@ type DeleteControlMeasureMappingPayload struct {
|
||||
DeletedMeasureID gid.GID `json:"deletedMeasureId"`
|
||||
}
|
||||
|
||||
type DeleteControlObligationMappingInput struct {
|
||||
ControlID gid.GID `json:"controlId"`
|
||||
ObligationID gid.GID `json:"obligationId"`
|
||||
}
|
||||
|
||||
type DeleteControlObligationMappingPayload struct {
|
||||
DeletedControlID gid.GID `json:"deletedControlId"`
|
||||
DeletedObligationID gid.GID `json:"deletedObligationId"`
|
||||
}
|
||||
|
||||
type DeleteControlPayload struct {
|
||||
DeletedControlID gid.GID `json:"deletedControlId"`
|
||||
}
|
||||
@@ -1011,6 +1070,25 @@ type DeleteSnapshotPayload struct {
|
||||
DeletedSnapshotID gid.GID `json:"deletedSnapshotId"`
|
||||
}
|
||||
|
||||
type DeleteStateOfApplicabilityControlMappingInput struct {
|
||||
StateOfApplicabilityID gid.GID `json:"stateOfApplicabilityId"`
|
||||
ControlID gid.GID `json:"controlId"`
|
||||
}
|
||||
|
||||
type DeleteStateOfApplicabilityControlMappingPayload struct {
|
||||
DeletedStateOfApplicabilityID gid.GID `json:"deletedStateOfApplicabilityId"`
|
||||
DeletedControlID gid.GID `json:"deletedControlId"`
|
||||
DeletedStateOfApplicabilityControlID gid.GID `json:"deletedStateOfApplicabilityControlId"`
|
||||
}
|
||||
|
||||
type DeleteStateOfApplicabilityInput struct {
|
||||
StateOfApplicabilityID gid.GID `json:"stateOfApplicabilityId"`
|
||||
}
|
||||
|
||||
type DeleteStateOfApplicabilityPayload struct {
|
||||
DeletedStateOfApplicabilityID gid.GID `json:"deletedStateOfApplicabilityId"`
|
||||
}
|
||||
|
||||
type DeleteTaskInput struct {
|
||||
TaskID gid.GID `json:"taskId"`
|
||||
}
|
||||
@@ -1265,6 +1343,14 @@ type ExportSignableDocumentVersionPDFPayload struct {
|
||||
Data string `json:"data"`
|
||||
}
|
||||
|
||||
type ExportStateOfApplicabilityPDFInput struct {
|
||||
StateOfApplicabilityID gid.GID `json:"stateOfApplicabilityId"`
|
||||
}
|
||||
|
||||
type ExportStateOfApplicabilityPDFPayload struct {
|
||||
Data string `json:"data"`
|
||||
}
|
||||
|
||||
type ExportTransferImpactAssessmentsPDFInput struct {
|
||||
OrganizationID gid.GID `json:"organizationId"`
|
||||
Filter *TransferImpactAssessmentFilter `json:"filter,omitempty"`
|
||||
@@ -1521,6 +1607,7 @@ type Obligation struct {
|
||||
LastReviewDate *time.Time `json:"lastReviewDate,omitempty"`
|
||||
DueDate *time.Time `json:"dueDate,omitempty"`
|
||||
Status coredata.ObligationStatus `json:"status"`
|
||||
Type coredata.ObligationType `json:"type"`
|
||||
CreatedAt time.Time `json:"createdAt"`
|
||||
UpdatedAt time.Time `json:"updatedAt"`
|
||||
}
|
||||
@@ -1556,6 +1643,7 @@ type Organization struct {
|
||||
Peoples *PeopleConnection `json:"peoples"`
|
||||
Documents *DocumentConnection `json:"documents"`
|
||||
Meetings *MeetingConnection `json:"meetings"`
|
||||
StatesOfApplicability *StateOfApplicabilityConnection `json:"statesOfApplicability"`
|
||||
Measures *MeasureConnection `json:"measures"`
|
||||
Risks *RiskConnection `json:"risks"`
|
||||
Tasks *TaskConnection `json:"tasks"`
|
||||
@@ -1878,6 +1966,46 @@ type SnapshotEdge struct {
|
||||
Node *Snapshot `json:"node"`
|
||||
}
|
||||
|
||||
type StateOfApplicability struct {
|
||||
ID gid.GID `json:"id"`
|
||||
Name string `json:"name"`
|
||||
SourceID *gid.GID `json:"sourceId,omitempty"`
|
||||
SnapshotID *gid.GID `json:"snapshotId,omitempty"`
|
||||
Organization *Organization `json:"organization,omitempty"`
|
||||
Owner *People `json:"owner"`
|
||||
Controls *ControlConnection `json:"controls"`
|
||||
AvailableControls []*AvailableStateOfApplicabilityControl `json:"availableControls"`
|
||||
CreatedAt time.Time `json:"createdAt"`
|
||||
UpdatedAt time.Time `json:"updatedAt"`
|
||||
}
|
||||
|
||||
func (StateOfApplicability) IsNode() {}
|
||||
func (this StateOfApplicability) GetID() gid.GID { return this.ID }
|
||||
|
||||
type StateOfApplicabilityControl struct {
|
||||
ID gid.GID `json:"id"`
|
||||
StateOfApplicabilityID gid.GID `json:"stateOfApplicabilityId"`
|
||||
ControlID gid.GID `json:"controlId"`
|
||||
StateOfApplicability *StateOfApplicability `json:"stateOfApplicability"`
|
||||
Applicability bool `json:"applicability"`
|
||||
Justification *string `json:"justification,omitempty"`
|
||||
}
|
||||
|
||||
type StateOfApplicabilityControlInput struct {
|
||||
ControlID gid.GID `json:"controlId"`
|
||||
Applicability bool `json:"applicability"`
|
||||
Justification *string `json:"justification,omitempty"`
|
||||
}
|
||||
|
||||
type StateOfApplicabilityEdge struct {
|
||||
Cursor page.CursorKey `json:"cursor"`
|
||||
Node *StateOfApplicability `json:"node"`
|
||||
}
|
||||
|
||||
type StateOfApplicabilityFilter struct {
|
||||
SnapshotID *gid.GID `json:"snapshotId,omitempty"`
|
||||
}
|
||||
|
||||
type Task struct {
|
||||
ID gid.GID `json:"id"`
|
||||
Name string `json:"name"`
|
||||
@@ -2072,6 +2200,7 @@ type UpdateControlInput struct {
|
||||
Description graphql.Omittable[*string] `json:"description,omitempty"`
|
||||
Status *coredata.ControlStatus `json:"status,omitempty"`
|
||||
ExclusionJustification *string `json:"exclusionJustification,omitempty"`
|
||||
BestPractice *bool `json:"bestPractice,omitempty"`
|
||||
}
|
||||
|
||||
type UpdateControlPayload struct {
|
||||
@@ -2199,6 +2328,7 @@ type UpdateObligationInput struct {
|
||||
LastReviewDate graphql.Omittable[*time.Time] `json:"lastReviewDate,omitempty"`
|
||||
DueDate graphql.Omittable[*time.Time] `json:"dueDate,omitempty"`
|
||||
Status *coredata.ObligationStatus `json:"status,omitempty"`
|
||||
Type *coredata.ObligationType `json:"type,omitempty"`
|
||||
}
|
||||
|
||||
type UpdateObligationPayload struct {
|
||||
@@ -2326,6 +2456,16 @@ type UpdateSAMLConfigurationPayload struct {
|
||||
SamlConfiguration *SAMLConfiguration `json:"samlConfiguration"`
|
||||
}
|
||||
|
||||
type UpdateStateOfApplicabilityInput struct {
|
||||
ID gid.GID `json:"id"`
|
||||
Name *string `json:"name,omitempty"`
|
||||
OwnerID *gid.GID `json:"ownerId,omitempty"`
|
||||
}
|
||||
|
||||
type UpdateStateOfApplicabilityPayload struct {
|
||||
StateOfApplicability *StateOfApplicability `json:"stateOfApplicability"`
|
||||
}
|
||||
|
||||
type UpdateTaskInput struct {
|
||||
TaskID gid.GID `json:"taskId"`
|
||||
Name *string `json:"name,omitempty"`
|
||||
|
||||
@@ -482,6 +482,38 @@ func (r *controlResolver) Audits(ctx context.Context, obj *types.Control, first
|
||||
return types.NewAuditConnection(page, r, obj.ID), nil
|
||||
}
|
||||
|
||||
// Obligations is the resolver for the obligations field.
|
||||
func (r *controlResolver) Obligations(ctx context.Context, obj *types.Control, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.ObligationOrderBy, filter *types.ObligationFilter) (*types.ObligationConnection, error) {
|
||||
r.MustBeAuthorized(ctx, obj.ID, authz.ActionListObligations)
|
||||
|
||||
prb := r.ProboService(ctx, obj.ID.TenantID())
|
||||
|
||||
pageOrderBy := page.OrderBy[coredata.ObligationOrderField]{
|
||||
Field: coredata.ObligationOrderFieldCreatedAt,
|
||||
Direction: page.OrderDirectionDesc,
|
||||
}
|
||||
if orderBy != nil {
|
||||
pageOrderBy = page.OrderBy[coredata.ObligationOrderField]{
|
||||
Field: orderBy.Field,
|
||||
Direction: orderBy.Direction,
|
||||
}
|
||||
}
|
||||
|
||||
cursor := types.NewCursor(first, after, last, before, pageOrderBy)
|
||||
|
||||
var snapshotID **gid.GID
|
||||
if filter != nil {
|
||||
snapshotID = &filter.SnapshotID
|
||||
}
|
||||
obligationFilter := coredata.NewObligationFilter(snapshotID)
|
||||
page, err := prb.Obligations.ListForControlID(ctx, obj.ID, cursor, obligationFilter)
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("cannot list control obligations: %w", err))
|
||||
}
|
||||
|
||||
return types.NewObligationConnection(page, r, obj.ID, filter), nil
|
||||
}
|
||||
|
||||
// Snapshots is the resolver for the snapshots field.
|
||||
func (r *controlResolver) Snapshots(ctx context.Context, obj *types.Control, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.SnapshotOrderBy) (*types.SnapshotConnection, error) {
|
||||
r.MustBeAuthorized(ctx, obj.ID, authz.ActionListSnapshots)
|
||||
@@ -510,6 +542,33 @@ func (r *controlResolver) Snapshots(ctx context.Context, obj *types.Control, fir
|
||||
return types.NewSnapshotConnection(page, r, obj.ID), nil
|
||||
}
|
||||
|
||||
// StateOfApplicabilityControls is the resolver for the stateOfApplicabilityControls field.
|
||||
func (r *controlResolver) StateOfApplicabilityControls(ctx context.Context, obj *types.Control, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.StateOfApplicabilityOrderBy) (*types.StateOfApplicabilityControlConnection, error) {
|
||||
r.MustBeAuthorized(ctx, obj.ID, authz.ActionListStatesOfApplicability)
|
||||
|
||||
prb := r.ProboService(ctx, obj.ID.TenantID())
|
||||
|
||||
pageOrderBy := page.OrderBy[coredata.StateOfApplicabilityOrderField]{
|
||||
Field: coredata.StateOfApplicabilityOrderFieldCreatedAt,
|
||||
Direction: page.OrderDirectionDesc,
|
||||
}
|
||||
if orderBy != nil {
|
||||
pageOrderBy = page.OrderBy[coredata.StateOfApplicabilityOrderField]{
|
||||
Field: orderBy.Field,
|
||||
Direction: orderBy.Direction,
|
||||
}
|
||||
}
|
||||
|
||||
cursor := types.NewCursor(first, after, last, before, pageOrderBy)
|
||||
|
||||
p, err := prb.StatesOfApplicability.ListControlLinks(ctx, obj.ID, cursor)
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("cannot list state of applicability controls: %w", err))
|
||||
}
|
||||
|
||||
return types.NewStateOfApplicabilityControlConnection(p, r, obj.ID), nil
|
||||
}
|
||||
|
||||
// TotalCount is the resolver for the totalCount field.
|
||||
func (r *controlConnectionResolver) TotalCount(ctx context.Context, obj *types.ControlConnection) (int, error) {
|
||||
prb := r.ProboService(ctx, obj.ParentID.TenantID())
|
||||
@@ -546,6 +605,12 @@ func (r *controlConnectionResolver) TotalCount(ctx context.Context, obj *types.C
|
||||
panic(fmt.Errorf("cannot count controls: %w", err))
|
||||
}
|
||||
return count, nil
|
||||
case *stateOfApplicabilityResolver:
|
||||
count, err := prb.Controls.CountForStateOfApplicabilityID(ctx, obj.ParentID, obj.Filters)
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("cannot count controls: %w", err))
|
||||
}
|
||||
return count, nil
|
||||
}
|
||||
|
||||
panic(fmt.Errorf("unsupported resolver: %T", obj.Resolver))
|
||||
@@ -2502,6 +2567,7 @@ func (r *mutationResolver) CreateControl(ctx context.Context, input types.Create
|
||||
SectionTitle: input.SectionTitle,
|
||||
Status: &input.Status,
|
||||
ExclusionJustification: input.ExclusionJustification,
|
||||
BestPractice: input.BestPractice,
|
||||
})
|
||||
if err != nil {
|
||||
var errAlreadyExists *coredata.ErrControlAlreadyExists
|
||||
@@ -2529,6 +2595,7 @@ func (r *mutationResolver) UpdateControl(ctx context.Context, input types.Update
|
||||
SectionTitle: input.SectionTitle,
|
||||
Status: input.Status,
|
||||
ExclusionJustification: input.ExclusionJustification,
|
||||
BestPractice: input.BestPractice,
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
@@ -2718,6 +2785,40 @@ func (r *mutationResolver) DeleteControlDocumentMapping(ctx context.Context, inp
|
||||
}, nil
|
||||
}
|
||||
|
||||
// CreateStateOfApplicabilityControlMapping is the resolver for the createStateOfApplicabilityControlMapping field.
|
||||
func (r *mutationResolver) CreateStateOfApplicabilityControlMapping(ctx context.Context, input types.CreateStateOfApplicabilityControlMappingInput) (*types.CreateStateOfApplicabilityControlMappingPayload, error) {
|
||||
r.MustBeAuthorized(ctx, input.ControlID, authz.ActionCreateStateOfApplicabilityControlMapping)
|
||||
|
||||
prb := r.ProboService(ctx, input.StateOfApplicabilityID.TenantID())
|
||||
|
||||
control, err := prb.StatesOfApplicability.LinkControl(ctx, input.StateOfApplicabilityID, input.ControlID, input.Applicability, input.Justification)
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("cannot create state of applicability control mapping: %w", err))
|
||||
}
|
||||
|
||||
return &types.CreateStateOfApplicabilityControlMappingPayload{
|
||||
StateOfApplicabilityControlEdge: types.NewStateOfApplicabilityControlEdge(control, coredata.StateOfApplicabilityOrderFieldCreatedAt),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// DeleteStateOfApplicabilityControlMapping is the resolver for the deleteStateOfApplicabilityControlMapping field.
|
||||
func (r *mutationResolver) DeleteStateOfApplicabilityControlMapping(ctx context.Context, input types.DeleteStateOfApplicabilityControlMappingInput) (*types.DeleteStateOfApplicabilityControlMappingPayload, error) {
|
||||
r.MustBeAuthorized(ctx, input.StateOfApplicabilityID, authz.ActionDeleteStateOfApplicabilityControlMapping)
|
||||
|
||||
prb := r.ProboService(ctx, input.StateOfApplicabilityID.TenantID())
|
||||
|
||||
deletedID, err := prb.StatesOfApplicability.DeleteControlLink(ctx, input.StateOfApplicabilityID, input.ControlID)
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("cannot delete state of applicability control mapping: %w", err))
|
||||
}
|
||||
|
||||
return &types.DeleteStateOfApplicabilityControlMappingPayload{
|
||||
DeletedStateOfApplicabilityID: input.StateOfApplicabilityID,
|
||||
DeletedControlID: input.ControlID,
|
||||
DeletedStateOfApplicabilityControlID: deletedID,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// CreateControlAuditMapping is the resolver for the createControlAuditMapping field.
|
||||
func (r *mutationResolver) CreateControlAuditMapping(ctx context.Context, input types.CreateControlAuditMappingInput) (*types.CreateControlAuditMappingPayload, error) {
|
||||
r.MustBeAuthorized(ctx, input.ControlID, authz.ActionCreateControlAuditMapping)
|
||||
@@ -2752,6 +2853,40 @@ func (r *mutationResolver) DeleteControlAuditMapping(ctx context.Context, input
|
||||
}, nil
|
||||
}
|
||||
|
||||
// CreateControlObligationMapping is the resolver for the createControlObligationMapping field.
|
||||
func (r *mutationResolver) CreateControlObligationMapping(ctx context.Context, input types.CreateControlObligationMappingInput) (*types.CreateControlObligationMappingPayload, error) {
|
||||
r.MustBeAuthorized(ctx, input.ControlID, authz.ActionCreateControlObligationMapping)
|
||||
|
||||
prb := r.ProboService(ctx, input.ObligationID.TenantID())
|
||||
|
||||
control, obligation, err := prb.Controls.CreateObligationMapping(ctx, input.ControlID, input.ObligationID)
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("cannot create control obligation mapping: %w", err))
|
||||
}
|
||||
|
||||
return &types.CreateControlObligationMappingPayload{
|
||||
ControlEdge: types.NewControlEdge(control, coredata.ControlOrderFieldCreatedAt),
|
||||
ObligationEdge: types.NewObligationEdge(obligation, coredata.ObligationOrderFieldCreatedAt),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// DeleteControlObligationMapping is the resolver for the deleteControlObligationMapping field.
|
||||
func (r *mutationResolver) DeleteControlObligationMapping(ctx context.Context, input types.DeleteControlObligationMappingInput) (*types.DeleteControlObligationMappingPayload, error) {
|
||||
r.MustBeAuthorized(ctx, input.ControlID, authz.ActionDeleteControlObligationMapping)
|
||||
|
||||
prb := r.ProboService(ctx, input.ObligationID.TenantID())
|
||||
|
||||
control, obligation, err := prb.Controls.DeleteObligationMapping(ctx, input.ControlID, input.ObligationID)
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("cannot delete control obligation mapping: %w", err))
|
||||
}
|
||||
|
||||
return &types.DeleteControlObligationMappingPayload{
|
||||
DeletedControlID: control.ID,
|
||||
DeletedObligationID: obligation.ID,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// CreateControlSnapshotMapping is the resolver for the createControlSnapshotMapping field.
|
||||
func (r *mutationResolver) CreateControlSnapshotMapping(ctx context.Context, input types.CreateControlSnapshotMappingInput) (*types.CreateControlSnapshotMappingPayload, error) {
|
||||
r.MustBeAuthorized(ctx, input.ControlID, authz.ActionCreateControlSnapshotMapping)
|
||||
@@ -3396,6 +3531,100 @@ func (r *mutationResolver) DeleteMeeting(ctx context.Context, input types.Delete
|
||||
}, nil
|
||||
}
|
||||
|
||||
// CreateStateOfApplicability is the resolver for the createStateOfApplicability field.
|
||||
func (r *mutationResolver) CreateStateOfApplicability(ctx context.Context, input types.CreateStateOfApplicabilityInput) (*types.CreateStateOfApplicabilityPayload, error) {
|
||||
r.MustBeAuthorized(ctx, input.OrganizationID, authz.ActionCreateStateOfApplicability)
|
||||
|
||||
prb := r.ProboService(ctx, input.OrganizationID.TenantID())
|
||||
|
||||
stateOfApplicability, err := prb.StatesOfApplicability.Create(
|
||||
ctx,
|
||||
probo.CreateStateOfApplicabilityRequest{
|
||||
OrganizationID: input.OrganizationID,
|
||||
Name: input.Name,
|
||||
OwnerID: input.OwnerID,
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
var errAlreadyExists *coredata.ErrStateOfApplicabilityAlreadyExists
|
||||
if errors.As(err, &errAlreadyExists) {
|
||||
return nil, gqlutils.Conflict(errAlreadyExists)
|
||||
}
|
||||
panic(fmt.Errorf("cannot create state_of_applicability: %w", err))
|
||||
}
|
||||
|
||||
return &types.CreateStateOfApplicabilityPayload{
|
||||
StateOfApplicabilityEdge: types.NewStateOfApplicabilityEdge(stateOfApplicability, coredata.StateOfApplicabilityOrderFieldCreatedAt),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// UpdateStateOfApplicability is the resolver for the updateStateOfApplicability field.
|
||||
func (r *mutationResolver) UpdateStateOfApplicability(ctx context.Context, input types.UpdateStateOfApplicabilityInput) (*types.UpdateStateOfApplicabilityPayload, error) {
|
||||
r.MustBeAuthorized(ctx, input.ID, authz.ActionUpdateStateOfApplicability)
|
||||
|
||||
prb := r.ProboService(ctx, input.ID.TenantID())
|
||||
|
||||
var name *string
|
||||
if input.Name != nil {
|
||||
name = input.Name
|
||||
}
|
||||
|
||||
stateOfApplicability, err := prb.StatesOfApplicability.Update(
|
||||
ctx,
|
||||
probo.UpdateStateOfApplicabilityRequest{
|
||||
StateOfApplicabilityID: input.ID,
|
||||
Name: name,
|
||||
OwnerID: input.OwnerID,
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
var errAlreadyExists *coredata.ErrStateOfApplicabilityAlreadyExists
|
||||
if errors.As(err, &errAlreadyExists) {
|
||||
return nil, gqlutils.Conflict(errAlreadyExists)
|
||||
}
|
||||
panic(fmt.Errorf("cannot update state_of_applicability: %w", err))
|
||||
}
|
||||
|
||||
return &types.UpdateStateOfApplicabilityPayload{
|
||||
StateOfApplicability: types.NewStateOfApplicability(stateOfApplicability),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// DeleteStateOfApplicability is the resolver for the deleteStateOfApplicability field.
|
||||
func (r *mutationResolver) DeleteStateOfApplicability(ctx context.Context, input types.DeleteStateOfApplicabilityInput) (*types.DeleteStateOfApplicabilityPayload, error) {
|
||||
r.MustBeAuthorized(ctx, input.StateOfApplicabilityID, authz.ActionDeleteStateOfApplicability)
|
||||
|
||||
prb := r.ProboService(ctx, input.StateOfApplicabilityID.TenantID())
|
||||
|
||||
err := prb.StatesOfApplicability.Delete(ctx, input.StateOfApplicabilityID)
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("cannot delete state_of_applicability: %w", err))
|
||||
}
|
||||
|
||||
return &types.DeleteStateOfApplicabilityPayload{
|
||||
DeletedStateOfApplicabilityID: input.StateOfApplicabilityID,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// ExportStateOfApplicabilityPDF is the resolver for the exportStateOfApplicabilityPDF field.
|
||||
func (r *mutationResolver) ExportStateOfApplicabilityPDF(ctx context.Context, input types.ExportStateOfApplicabilityPDFInput) (*types.ExportStateOfApplicabilityPDFPayload, error) {
|
||||
r.MustBeAuthorized(ctx, input.StateOfApplicabilityID, authz.ActionUpdateStateOfApplicability)
|
||||
|
||||
prb := r.ProboService(ctx, input.StateOfApplicabilityID.TenantID())
|
||||
|
||||
pdfData, err := prb.StatesOfApplicability.ExportPDF(ctx, input.StateOfApplicabilityID)
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("cannot export state of applicability PDF: %w", err))
|
||||
}
|
||||
|
||||
base64Data := base64.StdEncoding.EncodeToString(pdfData)
|
||||
dataURI := fmt.Sprintf("data:application/pdf;base64,%s", base64Data)
|
||||
|
||||
return &types.ExportStateOfApplicabilityPDFPayload{
|
||||
Data: dataURI,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// PublishDocumentVersion is the resolver for the publishDocumentVersion field.
|
||||
func (r *mutationResolver) PublishDocumentVersion(ctx context.Context, input types.PublishDocumentVersionInput) (*types.PublishDocumentVersionPayload, error) {
|
||||
r.MustBeAuthorized(ctx, input.DocumentID, authz.ActionPublishDocumentVersion)
|
||||
@@ -4194,7 +4423,8 @@ func (r *mutationResolver) CreateObligation(ctx context.Context, input types.Cre
|
||||
OwnerID: input.OwnerID,
|
||||
LastReviewDate: input.LastReviewDate,
|
||||
DueDate: input.DueDate,
|
||||
Status: &input.Status,
|
||||
Status: input.Status,
|
||||
Type: input.Type,
|
||||
}
|
||||
|
||||
obligation, err := prb.Obligations.Create(ctx, &req)
|
||||
@@ -4224,6 +4454,7 @@ func (r *mutationResolver) UpdateObligation(ctx context.Context, input types.Upd
|
||||
LastReviewDate: UnwrapOmittable(input.LastReviewDate),
|
||||
DueDate: UnwrapOmittable(input.DueDate),
|
||||
Status: input.Status,
|
||||
Type: input.Type,
|
||||
}
|
||||
|
||||
obligation, err := prb.Obligations.Update(ctx, &req)
|
||||
@@ -5403,6 +5634,38 @@ func (r *organizationResolver) Meetings(ctx context.Context, obj *types.Organiza
|
||||
return types.NewMeetingConnection(page, r, obj.ID), nil
|
||||
}
|
||||
|
||||
// StatesOfApplicability is the resolver for the statesOfApplicability field.
|
||||
func (r *organizationResolver) StatesOfApplicability(ctx context.Context, obj *types.Organization, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.StateOfApplicabilityOrderBy, filter *types.StateOfApplicabilityFilter) (*types.StateOfApplicabilityConnection, error) {
|
||||
r.MustBeAuthorized(ctx, obj.ID, authz.ActionListStatesOfApplicability)
|
||||
|
||||
prb := r.ProboService(ctx, obj.ID.TenantID())
|
||||
|
||||
pageOrderBy := page.OrderBy[coredata.StateOfApplicabilityOrderField]{
|
||||
Field: coredata.StateOfApplicabilityOrderFieldCreatedAt,
|
||||
Direction: page.OrderDirectionDesc,
|
||||
}
|
||||
if orderBy != nil {
|
||||
pageOrderBy = page.OrderBy[coredata.StateOfApplicabilityOrderField]{
|
||||
Field: orderBy.Field,
|
||||
Direction: orderBy.Direction,
|
||||
}
|
||||
}
|
||||
|
||||
cursor := types.NewCursor(first, after, last, before, pageOrderBy)
|
||||
|
||||
var stateOfApplicabilityFilter = coredata.NewStateOfApplicabilityFilter(nil)
|
||||
if filter != nil {
|
||||
stateOfApplicabilityFilter = coredata.NewStateOfApplicabilityFilter(&filter.SnapshotID)
|
||||
}
|
||||
|
||||
page, err := prb.StatesOfApplicability.ListForOrganizationID(ctx, obj.ID, cursor, stateOfApplicabilityFilter)
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("cannot list organization states_of_applicability: %w", err))
|
||||
}
|
||||
|
||||
return types.NewStateOfApplicabilityConnection(page, r, obj.ID, stateOfApplicabilityFilter), nil
|
||||
}
|
||||
|
||||
// Measures is the resolver for the measures field.
|
||||
func (r *organizationResolver) Measures(ctx context.Context, obj *types.Organization, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.MeasureOrderBy, filter *types.MeasureFilter) (*types.MeasureConnection, error) {
|
||||
r.MustBeAuthorized(ctx, obj.ID, authz.ActionListMeasures)
|
||||
@@ -6334,6 +6597,17 @@ func (r *queryResolver) Node(ctx context.Context, id gid.GID) (types.Node, error
|
||||
}
|
||||
|
||||
return types.NewRightsRequest(rightsRequest), nil
|
||||
case coredata.StateOfApplicabilityEntityType:
|
||||
stateOfApplicability, err := prb.StatesOfApplicability.Get(ctx, id)
|
||||
if err != nil {
|
||||
var errNotFound *coredata.ErrStateOfApplicabilityNotFound
|
||||
if errors.As(err, &errNotFound) {
|
||||
return nil, gqlutils.NotFound(errNotFound)
|
||||
}
|
||||
panic(fmt.Errorf("cannot get state_of_applicability: %w", err))
|
||||
}
|
||||
|
||||
return types.NewStateOfApplicability(stateOfApplicability), nil
|
||||
default:
|
||||
}
|
||||
|
||||
@@ -6804,6 +7078,151 @@ func (r *snapshotConnectionResolver) TotalCount(ctx context.Context, obj *types.
|
||||
panic(fmt.Errorf("unsupported resolver: %T", obj.Resolver))
|
||||
}
|
||||
|
||||
// Organization is the resolver for the organization field.
|
||||
func (r *stateOfApplicabilityResolver) Organization(ctx context.Context, obj *types.StateOfApplicability) (*types.Organization, error) {
|
||||
prb := r.ProboService(ctx, obj.ID.TenantID())
|
||||
|
||||
stateOfApplicability, err := prb.StatesOfApplicability.Get(ctx, obj.ID)
|
||||
if err != nil {
|
||||
var errNotFound *coredata.ErrStateOfApplicabilityNotFound
|
||||
if errors.As(err, &errNotFound) {
|
||||
return nil, gqlutils.NotFound(errNotFound)
|
||||
}
|
||||
panic(fmt.Errorf("cannot load state_of_applicability: %w", err))
|
||||
}
|
||||
|
||||
organization, err := prb.Organizations.Get(ctx, stateOfApplicability.OrganizationID)
|
||||
if err != nil {
|
||||
var errNotFound *coredata.ErrOrganizationNotFound
|
||||
if errors.As(err, &errNotFound) {
|
||||
return nil, gqlutils.NotFound(errNotFound)
|
||||
}
|
||||
panic(fmt.Errorf("cannot load organization: %w", err))
|
||||
}
|
||||
|
||||
return types.NewOrganization(organization), nil
|
||||
}
|
||||
|
||||
// Owner is the resolver for the owner field.
|
||||
func (r *stateOfApplicabilityResolver) Owner(ctx context.Context, obj *types.StateOfApplicability) (*types.People, error) {
|
||||
prb := r.ProboService(ctx, obj.ID.TenantID())
|
||||
|
||||
stateOfApplicability, err := prb.StatesOfApplicability.Get(ctx, obj.ID)
|
||||
if err != nil {
|
||||
var errNotFound *coredata.ErrStateOfApplicabilityNotFound
|
||||
if errors.As(err, &errNotFound) {
|
||||
return nil, gqlutils.NotFound(errNotFound)
|
||||
}
|
||||
panic(fmt.Errorf("cannot load state_of_applicability: %w", err))
|
||||
}
|
||||
|
||||
people, err := prb.Peoples.Get(ctx, stateOfApplicability.OwnerID)
|
||||
if err != nil {
|
||||
var errNotFound *coredata.ErrPeopleNotFound
|
||||
if errors.As(err, &errNotFound) {
|
||||
return nil, gqlutils.NotFound(errNotFound)
|
||||
}
|
||||
panic(fmt.Errorf("cannot load owner: %w", err))
|
||||
}
|
||||
|
||||
return types.NewPeople(people), nil
|
||||
}
|
||||
|
||||
// Controls is the resolver for the controls field.
|
||||
func (r *stateOfApplicabilityResolver) Controls(ctx context.Context, obj *types.StateOfApplicability, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.ControlOrderBy, filter *types.ControlFilter) (*types.ControlConnection, error) {
|
||||
r.MustBeAuthorized(ctx, obj.ID, authz.ActionListControls)
|
||||
|
||||
prb := r.ProboService(ctx, obj.ID.TenantID())
|
||||
|
||||
pageOrderBy := page.OrderBy[coredata.ControlOrderField]{
|
||||
Field: coredata.ControlOrderFieldCreatedAt,
|
||||
Direction: page.OrderDirectionDesc,
|
||||
}
|
||||
if orderBy != nil {
|
||||
pageOrderBy = page.OrderBy[coredata.ControlOrderField]{
|
||||
Field: orderBy.Field,
|
||||
Direction: orderBy.Direction,
|
||||
}
|
||||
}
|
||||
|
||||
cursor := types.NewCursor(first, after, last, before, pageOrderBy)
|
||||
|
||||
var controlFilter = coredata.NewControlFilter(nil)
|
||||
if filter != nil {
|
||||
controlFilter = coredata.NewControlFilter(filter.Query)
|
||||
}
|
||||
|
||||
page, err := prb.Controls.ListForStateOfApplicabilityID(ctx, obj.ID, cursor, controlFilter)
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("cannot list state of applicability controls: %w", err))
|
||||
}
|
||||
|
||||
return types.NewControlConnection(page, r, obj.ID, controlFilter), nil
|
||||
}
|
||||
|
||||
// AvailableControls is the resolver for the availableControls field.
|
||||
func (r *stateOfApplicabilityResolver) AvailableControls(ctx context.Context, obj *types.StateOfApplicability) ([]*types.AvailableStateOfApplicabilityControl, error) {
|
||||
r.MustBeAuthorized(ctx, obj.ID, authz.ActionListControls)
|
||||
|
||||
prb := r.ProboService(ctx, obj.ID.TenantID())
|
||||
|
||||
availableControls, err := prb.StatesOfApplicability.ListAvailableControls(ctx, obj.ID)
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("cannot list available controls: %w", err))
|
||||
}
|
||||
|
||||
result := make([]*types.AvailableStateOfApplicabilityControl, 0, len(availableControls))
|
||||
for _, ac := range availableControls {
|
||||
result = append(result, &types.AvailableStateOfApplicabilityControl{
|
||||
ControlID: ac.ControlID,
|
||||
SectionTitle: ac.SectionTitle,
|
||||
Name: ac.Name,
|
||||
FrameworkID: ac.FrameworkID,
|
||||
FrameworkName: ac.FrameworkName,
|
||||
OrganizationID: ac.OrganizationID,
|
||||
StateOfApplicabilityID: ac.StateOfApplicabilityID,
|
||||
Applicability: ac.Applicability,
|
||||
Justification: ac.Justification,
|
||||
BestPractice: ac.BestPractice,
|
||||
Regulatory: ac.Regulatory,
|
||||
Contractual: ac.Contractual,
|
||||
RiskAssessment: ac.RiskAssessment,
|
||||
})
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// TotalCount is the resolver for the totalCount field.
|
||||
func (r *stateOfApplicabilityConnectionResolver) TotalCount(ctx context.Context, obj *types.StateOfApplicabilityConnection) (int, error) {
|
||||
prb := r.ProboService(ctx, obj.ParentID.TenantID())
|
||||
|
||||
switch obj.Resolver.(type) {
|
||||
case *organizationResolver:
|
||||
count, err := prb.StatesOfApplicability.CountForOrganizationID(ctx, obj.ParentID, obj.Filters)
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("cannot count states_of_applicability: %w", err))
|
||||
}
|
||||
return count, nil
|
||||
}
|
||||
|
||||
panic(fmt.Errorf("unsupported resolver: %T", obj.Resolver))
|
||||
}
|
||||
|
||||
// StateOfApplicability is the resolver for the stateOfApplicability field.
|
||||
func (r *stateOfApplicabilityControlResolver) StateOfApplicability(ctx context.Context, obj *types.StateOfApplicabilityControl) (*types.StateOfApplicability, error) {
|
||||
r.MustBeAuthorized(ctx, obj.StateOfApplicabilityID, authz.ActionGet)
|
||||
|
||||
prb := r.ProboService(ctx, obj.StateOfApplicabilityID.TenantID())
|
||||
|
||||
soa, err := prb.StatesOfApplicability.Get(ctx, obj.StateOfApplicabilityID)
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("cannot get state of applicability: %w", err))
|
||||
}
|
||||
|
||||
return types.NewStateOfApplicability(soa), nil
|
||||
}
|
||||
|
||||
// AssignedTo is the resolver for the assignedTo field.
|
||||
func (r *taskResolver) AssignedTo(ctx context.Context, obj *types.Task) (*types.People, error) {
|
||||
r.MustBeAuthorized(ctx, obj.ID, authz.ActionGetAssignedTo)
|
||||
@@ -8055,6 +8474,21 @@ func (r *Resolver) SnapshotConnection() schema.SnapshotConnectionResolver {
|
||||
return &snapshotConnectionResolver{r}
|
||||
}
|
||||
|
||||
// StateOfApplicability returns schema.StateOfApplicabilityResolver implementation.
|
||||
func (r *Resolver) StateOfApplicability() schema.StateOfApplicabilityResolver {
|
||||
return &stateOfApplicabilityResolver{r}
|
||||
}
|
||||
|
||||
// StateOfApplicabilityConnection returns schema.StateOfApplicabilityConnectionResolver implementation.
|
||||
func (r *Resolver) StateOfApplicabilityConnection() schema.StateOfApplicabilityConnectionResolver {
|
||||
return &stateOfApplicabilityConnectionResolver{r}
|
||||
}
|
||||
|
||||
// StateOfApplicabilityControl returns schema.StateOfApplicabilityControlResolver implementation.
|
||||
func (r *Resolver) StateOfApplicabilityControl() schema.StateOfApplicabilityControlResolver {
|
||||
return &stateOfApplicabilityControlResolver{r}
|
||||
}
|
||||
|
||||
// Task returns schema.TaskResolver implementation.
|
||||
func (r *Resolver) Task() schema.TaskResolver { return &taskResolver{r} }
|
||||
|
||||
@@ -8197,6 +8631,9 @@ type sAMLConfigurationResolver struct{ *Resolver }
|
||||
type signableDocumentResolver struct{ *Resolver }
|
||||
type snapshotResolver struct{ *Resolver }
|
||||
type snapshotConnectionResolver struct{ *Resolver }
|
||||
type stateOfApplicabilityResolver struct{ *Resolver }
|
||||
type stateOfApplicabilityConnectionResolver struct{ *Resolver }
|
||||
type stateOfApplicabilityControlResolver struct{ *Resolver }
|
||||
type taskResolver struct{ *Resolver }
|
||||
type taskConnectionResolver struct{ *Resolver }
|
||||
type transferImpactAssessmentResolver struct{ *Resolver }
|
||||
|
||||
@@ -868,7 +868,8 @@ func (r *Resolver) AddObligationTool(ctx context.Context, req *mcp.CallToolReque
|
||||
OwnerID: input.OwnerID,
|
||||
LastReviewDate: input.LastReviewDate,
|
||||
DueDate: input.DueDate,
|
||||
Status: input.Status,
|
||||
Status: *input.Status,
|
||||
Type: *input.Type,
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
@@ -898,6 +899,7 @@ func (r *Resolver) UpdateObligationTool(ctx context.Context, req *mcp.CallToolRe
|
||||
LastReviewDate: UnwrapOmittable(input.LastReviewDate),
|
||||
DueDate: UnwrapOmittable(input.DueDate),
|
||||
Status: input.Status,
|
||||
Type: input.Type,
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
|
||||
@@ -1759,6 +1759,13 @@ components:
|
||||
- COMPLIANT
|
||||
go.probo.inc/mcpgen/type: go.probo.inc/probo/pkg/coredata.ObligationStatus
|
||||
|
||||
ObligationType:
|
||||
type: string
|
||||
enum:
|
||||
- LEGAL
|
||||
- CONTRACTUAL
|
||||
go.probo.inc/mcpgen/type: go.probo.inc/probo/pkg/coredata.ObligationType
|
||||
|
||||
ObligationOrderField:
|
||||
type: string
|
||||
enum:
|
||||
@@ -1921,6 +1928,7 @@ components:
|
||||
- organization_id
|
||||
- owner_id
|
||||
- status
|
||||
- type
|
||||
properties:
|
||||
organization_id:
|
||||
$ref: "#/components/schemas/GID"
|
||||
@@ -1958,6 +1966,13 @@ components:
|
||||
- type: "null"
|
||||
description: No status
|
||||
description: Status
|
||||
type:
|
||||
anyOf:
|
||||
- $ref: "#/components/schemas/ObligationType"
|
||||
description: Obligation type
|
||||
- type: "null"
|
||||
description: No type
|
||||
description: Type
|
||||
|
||||
AddObligationOutput:
|
||||
type: object
|
||||
@@ -2018,6 +2033,13 @@ components:
|
||||
- type: "null"
|
||||
description: No status
|
||||
description: Status
|
||||
type:
|
||||
anyOf:
|
||||
- $ref: "#/components/schemas/ObligationType"
|
||||
description: Obligation type
|
||||
- type: "null"
|
||||
description: No type
|
||||
description: Type
|
||||
|
||||
UpdateObligationOutput:
|
||||
type: object
|
||||
@@ -3150,6 +3172,7 @@ components:
|
||||
- OBLIGATIONS
|
||||
- CONTINUAL_IMPROVEMENTS
|
||||
- PROCESSING_ACTIVITIES
|
||||
- STATES_OF_APPLICABILITY
|
||||
go.probo.inc/mcpgen/type: go.probo.inc/probo/pkg/coredata.SnapshotsType
|
||||
|
||||
SnapshotOrderField:
|
||||
|
||||
@@ -31,7 +31,7 @@ var (
|
||||
AddMeasureToolOutputSchema = mcp.MustUnmarshalSchema(`{"type":"object","required":["measure"],"properties":{"measure":{"type":"object","required":["id","category","name","state","created_at","updated_at"],"properties":{"category":{"type":"string","description":"Measure category"},"created_at":{"type":"string","description":"Creation timestamp","format":"date-time"},"description":{"description":"Measure description"},"id":{"type":"string","format":"string"},"name":{"type":"string","description":"Measure name"},"state":{"type":"string","enum":["NOT_STARTED","IN_PROGRESS","NOT_APPLICABLE","IMPLEMENTED"]},"updated_at":{"type":"string","description":"Update timestamp","format":"date-time"}}}}}`)
|
||||
AddNonconformityToolInputSchema = mcp.MustUnmarshalSchema(`{"type":"object","required":["organization_id","reference_id","root_cause","owner_id","status"],"properties":{"audit_id":{"description":"Audit ID","anyOf":[{"type":"string","format":"string"},{"type":"null","description":"No audit"}]},"corrective_action":{"type":"string","description":"Corrective action"},"date_identified":{"type":"string","description":"Date identified","format":"date-time"},"description":{"type":"string","description":"Description"},"due_date":{"type":"string","description":"Due date","format":"date-time"},"effectiveness_check":{"type":"string","description":"Effectiveness check"},"organization_id":{"type":"string","format":"string"},"owner_id":{"type":"string","format":"string"},"reference_id":{"type":"string","description":"Reference ID"},"root_cause":{"type":"string","description":"Root cause"},"status":{"description":"Status","anyOf":[{"type":"string","enum":["OPEN","IN_PROGRESS","CLOSED"]},{"type":"null","description":"No status"}]}}}`)
|
||||
AddNonconformityToolOutputSchema = mcp.MustUnmarshalSchema(`{"type":"object","required":["nonconformity"],"properties":{"nonconformity":{"type":"object","required":["id","organization_id","reference_id","root_cause","owner_id","status","created_at","updated_at"],"properties":{"audit_id":{"description":"Audit ID","anyOf":[{"type":"string","format":"string"},{"type":"null","description":"No audit"}]},"corrective_action":{"description":"Corrective action"},"created_at":{"type":"string","description":"Creation timestamp","format":"date-time"},"date_identified":{"description":"Date identified","format":"date-time"},"description":{"description":"Description"},"due_date":{"description":"Due date","format":"date-time"},"effectiveness_check":{"description":"Effectiveness check"},"id":{"type":"string","format":"string"},"organization_id":{"type":"string","format":"string"},"owner_id":{"type":"string","format":"string"},"reference_id":{"type":"string","description":"Reference ID"},"root_cause":{"type":"string","description":"Root cause"},"snapshot_id":{"description":"Snapshot ID","anyOf":[{"type":"string","format":"string"},{"type":"null","description":"No snapshot"}]},"status":{"type":"string","enum":["OPEN","IN_PROGRESS","CLOSED"]},"updated_at":{"type":"string","description":"Update timestamp","format":"date-time"}}}}}`)
|
||||
AddObligationToolInputSchema = mcp.MustUnmarshalSchema(`{"type":"object","required":["organization_id","owner_id","status"],"properties":{"actions_to_be_implemented":{"type":"string","description":"Actions to be implemented"},"area":{"type":"string","description":"Area"},"due_date":{"type":"string","description":"Due date","format":"date-time"},"last_review_date":{"type":"string","description":"Last review date","format":"date-time"},"organization_id":{"type":"string","format":"string"},"owner_id":{"type":"string","format":"string"},"regulator":{"type":"string","description":"Regulator"},"requirement":{"type":"string","description":"Requirement"},"source":{"type":"string","description":"Source"},"status":{"description":"Status","anyOf":[{"type":"string","enum":["NON_COMPLIANT","PARTIALLY_COMPLIANT","COMPLIANT"]},{"type":"null","description":"No status"}]}}}`)
|
||||
AddObligationToolInputSchema = mcp.MustUnmarshalSchema(`{"type":"object","required":["organization_id","owner_id","status","type"],"properties":{"actions_to_be_implemented":{"type":"string","description":"Actions to be implemented"},"area":{"type":"string","description":"Area"},"due_date":{"type":"string","description":"Due date","format":"date-time"},"last_review_date":{"type":"string","description":"Last review date","format":"date-time"},"organization_id":{"type":"string","format":"string"},"owner_id":{"type":"string","format":"string"},"regulator":{"type":"string","description":"Regulator"},"requirement":{"type":"string","description":"Requirement"},"source":{"type":"string","description":"Source"},"status":{"description":"Status","anyOf":[{"type":"string","enum":["NON_COMPLIANT","PARTIALLY_COMPLIANT","COMPLIANT"]},{"type":"null","description":"No status"}]},"type":{"description":"Type","anyOf":[{"type":"string","enum":["LEGAL","CONTRACTUAL"]},{"type":"null","description":"No type"}]}}}`)
|
||||
AddObligationToolOutputSchema = mcp.MustUnmarshalSchema(`{"type":"object","required":["obligation"],"properties":{"obligation":{"type":"object","required":["id","organization_id","owner_id","status","created_at","updated_at"],"properties":{"actions_to_be_implemented":{"description":"Actions to be implemented"},"area":{"description":"Area"},"created_at":{"type":"string","description":"Creation timestamp","format":"date-time"},"due_date":{"description":"Due date","format":"date-time"},"id":{"type":"string","format":"string"},"last_review_date":{"description":"Last review date","format":"date-time"},"organization_id":{"type":"string","format":"string"},"owner_id":{"type":"string","format":"string"},"regulator":{"description":"Regulator"},"requirement":{"description":"Requirement"},"snapshot_id":{"description":"Snapshot ID","anyOf":[{"type":"string","format":"string"},{"type":"null","description":"No snapshot"}]},"source":{"description":"Source"},"source_id":{"description":"Source ID"},"status":{"type":"string","enum":["NON_COMPLIANT","PARTIALLY_COMPLIANT","COMPLIANT"]},"updated_at":{"type":"string","description":"Update timestamp","format":"date-time"}}}}}`)
|
||||
AddPeopleToolInputSchema = mcp.MustUnmarshalSchema(`{"type":"object","required":["organization_id","full_name","primary_email_address","kind"],"properties":{"additional_email_addresses":{"type":"array","description":"Additional email addresses","items":{"type":"string","format":"string"}},"contract_end_date":{"type":"string","description":"Contract end date","format":"date-time"},"contract_start_date":{"type":"string","description":"Contract start date","format":"date-time"},"full_name":{"type":"string","description":"Full name"},"kind":{"type":"string","enum":["EMPLOYEE","CONTRACTOR","SERVICE_ACCOUNT"]},"organization_id":{"type":"string","format":"string"},"position":{"type":"string","description":"Position"},"primary_email_address":{"type":"string","format":"string"}}}`)
|
||||
AddPeopleToolOutputSchema = mcp.MustUnmarshalSchema(`{"type":"object","required":["people"],"properties":{"people":{"type":"object","required":["id","organization_id","full_name","primary_email_address","additional_email_addresses","kind","created_at","updated_at"],"properties":{"additional_email_addresses":{"type":"array","description":"Additional email addresses","items":{"type":"string","format":"string"}},"contract_end_date":{"description":"Contract end date","format":"date-time"},"contract_start_date":{"description":"Contract start date","format":"date-time"},"created_at":{"type":"string","description":"Creation timestamp","format":"date-time"},"full_name":{"type":"string","description":"Full name"},"id":{"type":"string","format":"string"},"kind":{"type":"string","enum":["EMPLOYEE","CONTRACTOR","SERVICE_ACCOUNT"]},"organization_id":{"type":"string","format":"string"},"position":{"description":"Position"},"primary_email_address":{"type":"string","format":"string"},"updated_at":{"type":"string","description":"Update timestamp","format":"date-time"}}}}}`)
|
||||
@@ -80,7 +80,7 @@ var (
|
||||
GetRiskToolInputSchema = mcp.MustUnmarshalSchema(`{"type":"object","required":["id"],"properties":{"id":{"type":"string","format":"string"}}}`)
|
||||
GetRiskToolOutputSchema = mcp.MustUnmarshalSchema(`{"type":"object","required":["risk"],"properties":{"risk":{"type":"object","required":["id","organization_id","name","category","treatment","inherent_likelihood","inherent_impact","inherent_risk_score","residual_likelihood","residual_impact","residual_risk_score","note","created_at","updated_at"],"properties":{"category":{"type":"string","description":"Risk category"},"created_at":{"type":"string","description":"Creation timestamp","format":"date-time"},"description":{"description":"Risk description"},"id":{"type":"string","format":"string"},"inherent_impact":{"type":"integer","description":"Inherent impact"},"inherent_likelihood":{"type":"integer","description":"Inherent likelihood"},"inherent_risk_score":{"type":"integer","description":"Inherent risk score"},"name":{"type":"string","description":"Risk name"},"note":{"type":"string","description":"Risk note"},"organization_id":{"type":"string","format":"string"},"owner_id":{"anyOf":[{"type":"string","format":"string"},{"type":"null","description":"No owner"}]},"residual_impact":{"type":"integer","description":"Residual impact"},"residual_likelihood":{"type":"integer","description":"Residual likelihood"},"residual_risk_score":{"type":"integer","description":"Residual risk score"},"snapshot_id":{"description":"Snapshot ID","anyOf":[{"type":"string","format":"string"},{"type":"null","description":"No snapshot"}]},"treatment":{"type":"string","enum":["MITIGATED","ACCEPTED","AVOIDED","TRANSFERRED"]},"updated_at":{"type":"string","description":"Update timestamp","format":"date-time"}}}}}`)
|
||||
GetSnapshotToolInputSchema = mcp.MustUnmarshalSchema(`{"type":"object","required":["id"],"properties":{"id":{"type":"string","format":"string"}}}`)
|
||||
GetSnapshotToolOutputSchema = mcp.MustUnmarshalSchema(`{"type":"object","required":["snapshot"],"properties":{"snapshot":{"type":"object","required":["id","organization_id","name","type","created_at"],"properties":{"created_at":{"type":"string","description":"Creation timestamp","format":"date-time"},"description":{"description":"Snapshot description","anyOf":[{"type":"string","description":"Snapshot description"},{"type":"null","description":"No description"}]},"id":{"type":"string","format":"string"},"name":{"type":"string","description":"Snapshot name"},"organization_id":{"type":"string","format":"string"},"type":{"type":"string","enum":["RISKS","VENDORS","ASSETS","DATA","NONCONFORMITIES","OBLIGATIONS","CONTINUAL_IMPROVEMENTS","PROCESSING_ACTIVITIES"]}}}}}`)
|
||||
GetSnapshotToolOutputSchema = mcp.MustUnmarshalSchema(`{"type":"object","required":["snapshot"],"properties":{"snapshot":{"type":"object","required":["id","organization_id","name","type","created_at"],"properties":{"created_at":{"type":"string","description":"Creation timestamp","format":"date-time"},"description":{"description":"Snapshot description","anyOf":[{"type":"string","description":"Snapshot description"},{"type":"null","description":"No description"}]},"id":{"type":"string","format":"string"},"name":{"type":"string","description":"Snapshot name"},"organization_id":{"type":"string","format":"string"},"type":{"type":"string","enum":["RISKS","VENDORS","ASSETS","DATA","NONCONFORMITIES","OBLIGATIONS","CONTINUAL_IMPROVEMENTS","PROCESSING_ACTIVITIES","STATES_OF_APPLICABILITY"]}}}}}`)
|
||||
GetTaskToolInputSchema = mcp.MustUnmarshalSchema(`{"type":"object","required":["id"],"properties":{"id":{"type":"string","format":"string"}}}`)
|
||||
GetTaskToolOutputSchema = mcp.MustUnmarshalSchema(`{"type":"object","required":["task"],"properties":{"task":{"type":"object","required":["id","organization_id","name","state","created_at","updated_at"],"properties":{"assigned_to_id":{"description":"Assigned to person ID","anyOf":[{"type":"string","format":"string"},{"type":"null","description":"Not assigned"}]},"created_at":{"type":"string","description":"Creation timestamp","format":"date-time"},"deadline":{"description":"Deadline","anyOf":[{"type":"string","description":"Deadline","format":"date-time"},{"type":"null","description":"No deadline"}]},"description":{"description":"Task description","anyOf":[{"type":"string","description":"Task description"},{"type":"null","description":"No description"}]},"id":{"type":"string","format":"string"},"measure_id":{"description":"Measure ID","anyOf":[{"type":"string","format":"string"},{"type":"null","description":"No measure"}]},"name":{"type":"string","description":"Task name"},"organization_id":{"type":"string","format":"string"},"state":{"type":"string","enum":["TODO","DONE"]},"time_estimate":{"description":"Time estimate","anyOf":[{"type":"string","description":"A duration"},{"type":"null","description":"No time estimate"}]},"updated_at":{"type":"string","description":"Update timestamp","format":"date-time"}}}}}`)
|
||||
LinkControlAuditToolInputSchema = mcp.MustUnmarshalSchema(`{"type":"object","required":["control_id","audit_id"],"properties":{"audit_id":{"type":"string","format":"string"},"control_id":{"type":"string","format":"string"}}}`)
|
||||
@@ -122,7 +122,7 @@ var (
|
||||
ListRisksToolInputSchema = mcp.MustUnmarshalSchema(`{"type":"object","required":["organization_id"],"properties":{"cursor":{"type":"string","format":"string"},"filter":{"type":"object","properties":{"query":{"type":"string","description":"Search query"},"snapshot_id":{"type":"string","format":"string"}}},"order_by":{"type":"object","required":["field","direction"],"properties":{"direction":{"type":"string","enum":["ASC","DESC"]},"field":{"type":"string","enum":["CREATED_AT","UPDATED_AT","NAME","CATEGORY","TREATMENT","INHERENT_RISK_SCORE","RESIDUAL_RISK_SCORE","OWNER_FULL_NAME"]}}},"organization_id":{"type":"string","format":"string"},"size":{"type":"integer","description":"Page size"}}}`)
|
||||
ListRisksToolOutputSchema = mcp.MustUnmarshalSchema(`{"type":"object","required":["risks"],"properties":{"next_cursor":{"type":"string","format":"string"},"risks":{"type":"array","items":{"type":"object","required":["id","organization_id","name","category","treatment","inherent_likelihood","inherent_impact","inherent_risk_score","residual_likelihood","residual_impact","residual_risk_score","note","created_at","updated_at"],"properties":{"category":{"type":"string","description":"Risk category"},"created_at":{"type":"string","description":"Creation timestamp","format":"date-time"},"description":{"description":"Risk description"},"id":{"type":"string","format":"string"},"inherent_impact":{"type":"integer","description":"Inherent impact"},"inherent_likelihood":{"type":"integer","description":"Inherent likelihood"},"inherent_risk_score":{"type":"integer","description":"Inherent risk score"},"name":{"type":"string","description":"Risk name"},"note":{"type":"string","description":"Risk note"},"organization_id":{"type":"string","format":"string"},"owner_id":{"anyOf":[{"type":"string","format":"string"},{"type":"null","description":"No owner"}]},"residual_impact":{"type":"integer","description":"Residual impact"},"residual_likelihood":{"type":"integer","description":"Residual likelihood"},"residual_risk_score":{"type":"integer","description":"Residual risk score"},"snapshot_id":{"description":"Snapshot ID","anyOf":[{"type":"string","format":"string"},{"type":"null","description":"No snapshot"}]},"treatment":{"type":"string","enum":["MITIGATED","ACCEPTED","AVOIDED","TRANSFERRED"]},"updated_at":{"type":"string","description":"Update timestamp","format":"date-time"}}}}}}`)
|
||||
ListSnapshotsToolInputSchema = mcp.MustUnmarshalSchema(`{"type":"object","required":["organization_id"],"properties":{"cursor":{"type":"string","format":"string"},"order_by":{"type":"object","required":["field","direction"],"properties":{"direction":{"type":"string","enum":["ASC","DESC"]},"field":{"type":"string","enum":["CREATED_AT","NAME","TYPE"]}}},"organization_id":{"type":"string","format":"string"},"size":{"type":"integer","description":"Page size"}}}`)
|
||||
ListSnapshotsToolOutputSchema = mcp.MustUnmarshalSchema(`{"type":"object","required":["snapshots"],"properties":{"next_cursor":{"description":"Next page cursor","anyOf":[{"type":"string","format":"string"},{"type":"null"}]},"snapshots":{"type":"array","description":"List of snapshots","items":{"type":"object","required":["id","organization_id","name","type","created_at"],"properties":{"created_at":{"type":"string","description":"Creation timestamp","format":"date-time"},"description":{"description":"Snapshot description","anyOf":[{"type":"string","description":"Snapshot description"},{"type":"null","description":"No description"}]},"id":{"type":"string","format":"string"},"name":{"type":"string","description":"Snapshot name"},"organization_id":{"type":"string","format":"string"},"type":{"type":"string","enum":["RISKS","VENDORS","ASSETS","DATA","NONCONFORMITIES","OBLIGATIONS","CONTINUAL_IMPROVEMENTS","PROCESSING_ACTIVITIES"]}}}}}}`)
|
||||
ListSnapshotsToolOutputSchema = mcp.MustUnmarshalSchema(`{"type":"object","required":["snapshots"],"properties":{"next_cursor":{"description":"Next page cursor","anyOf":[{"type":"string","format":"string"},{"type":"null"}]},"snapshots":{"type":"array","description":"List of snapshots","items":{"type":"object","required":["id","organization_id","name","type","created_at"],"properties":{"created_at":{"type":"string","description":"Creation timestamp","format":"date-time"},"description":{"description":"Snapshot description","anyOf":[{"type":"string","description":"Snapshot description"},{"type":"null","description":"No description"}]},"id":{"type":"string","format":"string"},"name":{"type":"string","description":"Snapshot name"},"organization_id":{"type":"string","format":"string"},"type":{"type":"string","enum":["RISKS","VENDORS","ASSETS","DATA","NONCONFORMITIES","OBLIGATIONS","CONTINUAL_IMPROVEMENTS","PROCESSING_ACTIVITIES","STATES_OF_APPLICABILITY"]}}}}}}`)
|
||||
ListTasksToolInputSchema = mcp.MustUnmarshalSchema(`{"type":"object","required":["organization_id"],"properties":{"cursor":{"type":"string","format":"string"},"measure_id":{"type":"string","format":"string"},"order_by":{"type":"object","required":["field","direction"],"properties":{"direction":{"type":"string","enum":["ASC","DESC"]},"field":{"type":"string","enum":["CREATED_AT"]}}},"organization_id":{"type":"string","format":"string"},"size":{"type":"integer","description":"Page size"}}}`)
|
||||
ListTasksToolOutputSchema = mcp.MustUnmarshalSchema(`{"type":"object","required":["tasks"],"properties":{"next_cursor":{"description":"Next page cursor","anyOf":[{"type":"string","format":"string"},{"type":"null"}]},"tasks":{"type":"array","description":"List of tasks","items":{"type":"object","required":["id","organization_id","name","state","created_at","updated_at"],"properties":{"assigned_to_id":{"description":"Assigned to person ID","anyOf":[{"type":"string","format":"string"},{"type":"null","description":"Not assigned"}]},"created_at":{"type":"string","description":"Creation timestamp","format":"date-time"},"deadline":{"description":"Deadline","anyOf":[{"type":"string","description":"Deadline","format":"date-time"},{"type":"null","description":"No deadline"}]},"description":{"description":"Task description","anyOf":[{"type":"string","description":"Task description"},{"type":"null","description":"No description"}]},"id":{"type":"string","format":"string"},"measure_id":{"description":"Measure ID","anyOf":[{"type":"string","format":"string"},{"type":"null","description":"No measure"}]},"name":{"type":"string","description":"Task name"},"organization_id":{"type":"string","format":"string"},"state":{"type":"string","enum":["TODO","DONE"]},"time_estimate":{"description":"Time estimate","anyOf":[{"type":"string","description":"A duration"},{"type":"null","description":"No time estimate"}]},"updated_at":{"type":"string","description":"Update timestamp","format":"date-time"}}}}}}`)
|
||||
ListVendorsToolInputSchema = mcp.MustUnmarshalSchema(`{"type":"object","required":["organization_id"],"properties":{"cursor":{"type":"string","format":"string"},"filter":{"type":"object","properties":{"snapshot_id":{"type":"string","format":"string"}}},"order_by":{"type":"object","required":["field","direction"],"properties":{"direction":{"type":"string","enum":["ASC","DESC"]},"field":{"type":"string","enum":["CREATED_AT","UPDATED_AT","NAME"]}}},"organization_id":{"type":"string","format":"string"},"size":{"type":"integer","description":"Page size"}}}`)
|
||||
@@ -131,8 +131,8 @@ var (
|
||||
PublishDocumentVersionToolOutputSchema = mcp.MustUnmarshalSchema(`{"type":"object","required":["document","document_version"],"properties":{"document":{"type":"object","required":["id","organization_id","owner_id","title","document_type","classification","trust_center_visibility","created_at","updated_at"],"properties":{"classification":{"type":"string","enum":["PUBLIC","INTERNAL","CONFIDENTIAL","SECRET"]},"created_at":{"type":"string","description":"Creation timestamp","format":"date-time"},"current_published_version":{"description":"Current published version number"},"document_type":{"type":"string","enum":["OTHER","ISMS","POLICY","PROCEDURE"]},"id":{"type":"string","format":"string"},"organization_id":{"type":"string","format":"string"},"owner_id":{"type":"string","format":"string"},"title":{"type":"string","description":"Document title"},"trust_center_visibility":{"type":"string","enum":["NONE","PRIVATE","PUBLIC"]},"updated_at":{"type":"string","description":"Update timestamp","format":"date-time"}}},"document_version":{"type":"object","required":["id","organization_id","document_id","title","owner_id","version_number","classification","content","changelog","status","created_at","updated_at"],"properties":{"changelog":{"type":"string","description":"Changelog"},"classification":{"type":"string","enum":["PUBLIC","INTERNAL","CONFIDENTIAL","SECRET"]},"content":{"type":"string","description":"Document content"},"created_at":{"type":"string","description":"Creation timestamp","format":"date-time"},"document_id":{"type":"string","format":"string"},"id":{"type":"string","format":"string"},"organization_id":{"type":"string","format":"string"},"owner_id":{"type":"string","format":"string"},"published_at":{"description":"Published timestamp","format":"date-time"},"status":{"type":"string","enum":["DRAFT","PUBLISHED"]},"title":{"type":"string","description":"Document version title"},"updated_at":{"type":"string","description":"Update timestamp","format":"date-time"},"version_number":{"type":"integer","description":"Version number"}}}}}`)
|
||||
RequestDocumentVersionSignatureToolInputSchema = mcp.MustUnmarshalSchema(`{"type":"object","required":["document_version_id","signatory_id"],"properties":{"document_version_id":{"type":"string","format":"string"},"signatory_id":{"type":"string","format":"string"}}}`)
|
||||
RequestDocumentVersionSignatureToolOutputSchema = mcp.MustUnmarshalSchema(`{"type":"object","required":["document_version_signature"],"properties":{"document_version_signature":{"type":"object","required":["id","organization_id","document_version_id","state","signed_by","requested_at","created_at","updated_at"],"properties":{"created_at":{"type":"string","description":"Creation timestamp","format":"date-time"},"document_version_id":{"type":"string","format":"string"},"id":{"type":"string","format":"string"},"organization_id":{"type":"string","format":"string"},"requested_at":{"type":"string","description":"Requested timestamp","format":"date-time"},"signed_at":{"description":"Signed timestamp","format":"date-time"},"signed_by":{"type":"string","format":"string"},"state":{"type":"string","enum":["REQUESTED","SIGNED"]},"updated_at":{"type":"string","description":"Update timestamp","format":"date-time"}}}}}`)
|
||||
TakeSnapshotToolInputSchema = mcp.MustUnmarshalSchema(`{"type":"object","required":["organization_id","name","type"],"properties":{"description":{"type":"string","description":"Snapshot description"},"name":{"type":"string","description":"Snapshot name"},"organization_id":{"type":"string","format":"string"},"type":{"type":"string","enum":["RISKS","VENDORS","ASSETS","DATA","NONCONFORMITIES","OBLIGATIONS","CONTINUAL_IMPROVEMENTS","PROCESSING_ACTIVITIES"]}}}`)
|
||||
TakeSnapshotToolOutputSchema = mcp.MustUnmarshalSchema(`{"type":"object","required":["snapshot"],"properties":{"snapshot":{"type":"object","required":["id","organization_id","name","type","created_at"],"properties":{"created_at":{"type":"string","description":"Creation timestamp","format":"date-time"},"description":{"description":"Snapshot description","anyOf":[{"type":"string","description":"Snapshot description"},{"type":"null","description":"No description"}]},"id":{"type":"string","format":"string"},"name":{"type":"string","description":"Snapshot name"},"organization_id":{"type":"string","format":"string"},"type":{"type":"string","enum":["RISKS","VENDORS","ASSETS","DATA","NONCONFORMITIES","OBLIGATIONS","CONTINUAL_IMPROVEMENTS","PROCESSING_ACTIVITIES"]}}}}}`)
|
||||
TakeSnapshotToolInputSchema = mcp.MustUnmarshalSchema(`{"type":"object","required":["organization_id","name","type"],"properties":{"description":{"type":"string","description":"Snapshot description"},"name":{"type":"string","description":"Snapshot name"},"organization_id":{"type":"string","format":"string"},"type":{"type":"string","enum":["RISKS","VENDORS","ASSETS","DATA","NONCONFORMITIES","OBLIGATIONS","CONTINUAL_IMPROVEMENTS","PROCESSING_ACTIVITIES","STATES_OF_APPLICABILITY"]}}}`)
|
||||
TakeSnapshotToolOutputSchema = mcp.MustUnmarshalSchema(`{"type":"object","required":["snapshot"],"properties":{"snapshot":{"type":"object","required":["id","organization_id","name","type","created_at"],"properties":{"created_at":{"type":"string","description":"Creation timestamp","format":"date-time"},"description":{"description":"Snapshot description","anyOf":[{"type":"string","description":"Snapshot description"},{"type":"null","description":"No description"}]},"id":{"type":"string","format":"string"},"name":{"type":"string","description":"Snapshot name"},"organization_id":{"type":"string","format":"string"},"type":{"type":"string","enum":["RISKS","VENDORS","ASSETS","DATA","NONCONFORMITIES","OBLIGATIONS","CONTINUAL_IMPROVEMENTS","PROCESSING_ACTIVITIES","STATES_OF_APPLICABILITY"]}}}}}`)
|
||||
UnassignTaskToolInputSchema = mcp.MustUnmarshalSchema(`{"type":"object","required":["id"],"properties":{"id":{"type":"string","format":"string"}}}`)
|
||||
UnassignTaskToolOutputSchema = mcp.MustUnmarshalSchema(`{"type":"object","required":["task"],"properties":{"task":{"type":"object","required":["id","organization_id","name","state","created_at","updated_at"],"properties":{"assigned_to_id":{"description":"Assigned to person ID","anyOf":[{"type":"string","format":"string"},{"type":"null","description":"Not assigned"}]},"created_at":{"type":"string","description":"Creation timestamp","format":"date-time"},"deadline":{"description":"Deadline","anyOf":[{"type":"string","description":"Deadline","format":"date-time"},{"type":"null","description":"No deadline"}]},"description":{"description":"Task description","anyOf":[{"type":"string","description":"Task description"},{"type":"null","description":"No description"}]},"id":{"type":"string","format":"string"},"measure_id":{"description":"Measure ID","anyOf":[{"type":"string","format":"string"},{"type":"null","description":"No measure"}]},"name":{"type":"string","description":"Task name"},"organization_id":{"type":"string","format":"string"},"state":{"type":"string","enum":["TODO","DONE"]},"time_estimate":{"description":"Time estimate","anyOf":[{"type":"string","description":"A duration"},{"type":"null","description":"No time estimate"}]},"updated_at":{"type":"string","description":"Update timestamp","format":"date-time"}}}}}`)
|
||||
UnlinkControlAuditToolInputSchema = mcp.MustUnmarshalSchema(`{"type":"object","required":["control_id","audit_id"],"properties":{"audit_id":{"type":"string","format":"string"},"control_id":{"type":"string","format":"string"}}}`)
|
||||
@@ -163,7 +163,7 @@ var (
|
||||
UpdateMeasureToolOutputSchema = mcp.MustUnmarshalSchema(`{"type":"object","required":["measure"],"properties":{"measure":{"type":"object","required":["id","category","name","state","created_at","updated_at"],"properties":{"category":{"type":"string","description":"Measure category"},"created_at":{"type":"string","description":"Creation timestamp","format":"date-time"},"description":{"description":"Measure description"},"id":{"type":"string","format":"string"},"name":{"type":"string","description":"Measure name"},"state":{"type":"string","enum":["NOT_STARTED","IN_PROGRESS","NOT_APPLICABLE","IMPLEMENTED"]},"updated_at":{"type":"string","description":"Update timestamp","format":"date-time"}}}}}`)
|
||||
UpdateNonconformityToolInputSchema = mcp.MustUnmarshalSchema(`{"type":"object","required":["id"],"properties":{"audit_id":{"description":"Audit ID","anyOf":[{"type":"string","format":"string"},{"type":"null"}]},"corrective_action":{"description":"Corrective action"},"date_identified":{"description":"Date identified","format":"date-time"},"description":{"description":"Description"},"due_date":{"description":"Due date","format":"date-time"},"effectiveness_check":{"description":"Effectiveness check"},"id":{"type":"string","format":"string"},"owner_id":{"description":"Owner ID","anyOf":[{"type":"string","format":"string"},{"type":"null"}]},"reference_id":{"type":"string","description":"Reference ID"},"root_cause":{"type":"string","description":"Root cause"},"status":{"type":"string","enum":["OPEN","IN_PROGRESS","CLOSED"]}}}`)
|
||||
UpdateNonconformityToolOutputSchema = mcp.MustUnmarshalSchema(`{"type":"object","required":["nonconformity"],"properties":{"nonconformity":{"type":"object","required":["id","organization_id","reference_id","root_cause","owner_id","status","created_at","updated_at"],"properties":{"audit_id":{"description":"Audit ID","anyOf":[{"type":"string","format":"string"},{"type":"null","description":"No audit"}]},"corrective_action":{"description":"Corrective action"},"created_at":{"type":"string","description":"Creation timestamp","format":"date-time"},"date_identified":{"description":"Date identified","format":"date-time"},"description":{"description":"Description"},"due_date":{"description":"Due date","format":"date-time"},"effectiveness_check":{"description":"Effectiveness check"},"id":{"type":"string","format":"string"},"organization_id":{"type":"string","format":"string"},"owner_id":{"type":"string","format":"string"},"reference_id":{"type":"string","description":"Reference ID"},"root_cause":{"type":"string","description":"Root cause"},"snapshot_id":{"description":"Snapshot ID","anyOf":[{"type":"string","format":"string"},{"type":"null","description":"No snapshot"}]},"status":{"type":"string","enum":["OPEN","IN_PROGRESS","CLOSED"]},"updated_at":{"type":"string","description":"Update timestamp","format":"date-time"}}}}}`)
|
||||
UpdateObligationToolInputSchema = mcp.MustUnmarshalSchema(`{"type":"object","required":["id"],"properties":{"actions_to_be_implemented":{"description":"Actions to be implemented"},"area":{"description":"Area"},"due_date":{"description":"Due date","format":"date-time"},"id":{"type":"string","format":"string"},"last_review_date":{"description":"Last review date","format":"date-time"},"owner_id":{"description":"Owner ID","anyOf":[{"type":"string","format":"string"},{"type":"null"}]},"regulator":{"description":"Regulator"},"requirement":{"description":"Requirement"},"source":{"description":"Source"},"status":{"description":"Status","anyOf":[{"type":"string","enum":["NON_COMPLIANT","PARTIALLY_COMPLIANT","COMPLIANT"]},{"type":"null","description":"No status"}]}}}`)
|
||||
UpdateObligationToolInputSchema = mcp.MustUnmarshalSchema(`{"type":"object","required":["id"],"properties":{"actions_to_be_implemented":{"description":"Actions to be implemented"},"area":{"description":"Area"},"due_date":{"description":"Due date","format":"date-time"},"id":{"type":"string","format":"string"},"last_review_date":{"description":"Last review date","format":"date-time"},"owner_id":{"description":"Owner ID","anyOf":[{"type":"string","format":"string"},{"type":"null"}]},"regulator":{"description":"Regulator"},"requirement":{"description":"Requirement"},"source":{"description":"Source"},"status":{"description":"Status","anyOf":[{"type":"string","enum":["NON_COMPLIANT","PARTIALLY_COMPLIANT","COMPLIANT"]},{"type":"null","description":"No status"}]},"type":{"description":"Type","anyOf":[{"type":"string","enum":["LEGAL","CONTRACTUAL"]},{"type":"null","description":"No type"}]}}}`)
|
||||
UpdateObligationToolOutputSchema = mcp.MustUnmarshalSchema(`{"type":"object","required":["obligation"],"properties":{"obligation":{"type":"object","required":["id","organization_id","owner_id","status","created_at","updated_at"],"properties":{"actions_to_be_implemented":{"description":"Actions to be implemented"},"area":{"description":"Area"},"created_at":{"type":"string","description":"Creation timestamp","format":"date-time"},"due_date":{"description":"Due date","format":"date-time"},"id":{"type":"string","format":"string"},"last_review_date":{"description":"Last review date","format":"date-time"},"organization_id":{"type":"string","format":"string"},"owner_id":{"type":"string","format":"string"},"regulator":{"description":"Regulator"},"requirement":{"description":"Requirement"},"snapshot_id":{"description":"Snapshot ID","anyOf":[{"type":"string","format":"string"},{"type":"null","description":"No snapshot"}]},"source":{"description":"Source"},"source_id":{"description":"Source ID"},"status":{"type":"string","enum":["NON_COMPLIANT","PARTIALLY_COMPLIANT","COMPLIANT"]},"updated_at":{"type":"string","description":"Update timestamp","format":"date-time"}}}}}`)
|
||||
UpdatePeopleToolInputSchema = mcp.MustUnmarshalSchema(`{"type":"object","required":["id"],"properties":{"additional_email_addresses":{"description":"Additional email addresses","anyOf":[{"type":"array","items":{"type":"string","format":"string"}},{"type":"null","description":"No additional email addresses"}]},"contract_end_date":{"description":"Contract end date","format":"date-time"},"contract_start_date":{"description":"Contract start date","format":"date-time"},"full_name":{"type":"string","description":"Full name"},"id":{"type":"string","format":"string"},"kind":{"type":"string","enum":["EMPLOYEE","CONTRACTOR","SERVICE_ACCOUNT"]},"position":{"description":"Position"},"primary_email_address":{"type":"string","format":"string"}}}`)
|
||||
UpdatePeopleToolOutputSchema = mcp.MustUnmarshalSchema(`{"type":"object","required":["people"],"properties":{"people":{"type":"object","required":["id","organization_id","full_name","primary_email_address","additional_email_addresses","kind","created_at","updated_at"],"properties":{"additional_email_addresses":{"type":"array","description":"Additional email addresses","items":{"type":"string","format":"string"}},"contract_end_date":{"description":"Contract end date","format":"date-time"},"contract_start_date":{"description":"Contract start date","format":"date-time"},"created_at":{"type":"string","description":"Creation timestamp","format":"date-time"},"full_name":{"type":"string","description":"Full name"},"id":{"type":"string","format":"string"},"kind":{"type":"string","enum":["EMPLOYEE","CONTRACTOR","SERVICE_ACCOUNT"]},"organization_id":{"type":"string","format":"string"},"position":{"description":"Position"},"primary_email_address":{"type":"string","format":"string"},"updated_at":{"type":"string","description":"Update timestamp","format":"date-time"}}}}}`)
|
||||
@@ -395,6 +395,8 @@ type AddObligationInput struct {
|
||||
Source *string `json:"source,omitempty"`
|
||||
// Status
|
||||
Status *coredata.ObligationStatus `json:"status"`
|
||||
// Type
|
||||
Type *coredata.ObligationType `json:"type"`
|
||||
}
|
||||
|
||||
// AddObligationOutput represents the schema
|
||||
@@ -2016,6 +2018,8 @@ type UpdateObligationInput struct {
|
||||
Source mcp.Omittable[*string] `json:"source,omitempty"`
|
||||
// Status
|
||||
Status *coredata.ObligationStatus `json:"status,omitempty"`
|
||||
// Type
|
||||
Type *coredata.ObligationType `json:"type,omitempty"`
|
||||
}
|
||||
|
||||
// UpdateObligationOutput represents the schema
|
||||
|
||||
Reference in New Issue
Block a user