Add employee page

Signed-off-by: Sacha Al Himdani <sacha@getprobo.com>
This commit is contained in:
Sacha Al Himdani
2025-11-18 22:56:22 +01:00
parent 2f4cfd7ac0
commit 975d16c623
44 changed files with 5731 additions and 1842 deletions

View File

@@ -88,7 +88,7 @@ func MapSAMLRoleToSystemRole(samlRole string) *coredata.MembershipRole {
func isValidRole(role string) bool {
switch role {
case "OWNER", "ADMIN", "VIEWER":
case "OWNER", "ADMIN", "EMPLOYEE", "VIEWER":
return true
default:
return false

View File

@@ -27,10 +27,11 @@ type (
)
const (
RoleOwner Role = "OWNER"
RoleAdmin Role = "ADMIN"
RoleViewer Role = "VIEWER"
RoleFull Role = "FULL"
RoleOwner Role = "OWNER"
RoleAdmin Role = "ADMIN"
RoleEmployee Role = "EMPLOYEE"
RoleViewer Role = "VIEWER"
RoleFull Role = "FULL"
)
const (
@@ -43,6 +44,7 @@ const (
ActionGetBusinessOwner Action = "getBusinessOwner"
ActionGetCustomDomain Action = "getCustomDomain"
ActionGetDataPrivacyAgreement Action = "getDataPrivacyAgreement"
ActionGetDocument Action = "getDocument"
ActionGetFile Action = "getFile"
ActionGetFileUrl Action = "getFileUrl"
ActionGetFramework Action = "getFramework"
@@ -53,6 +55,8 @@ const (
ActionGetOrganization Action = "getOrganization"
ActionGetOwner Action = "getOwner"
ActionGetSecurityOwner Action = "getSecurityOwner"
ActionGetSigned Action = "getSigned"
ActionGetSignableDocument Action = "getSignableDocument"
ActionGetSnapshot Action = "getSnapshot"
ActionGetTask Action = "getTask"
ActionGetTrustCenter Action = "getTrustCenter"
@@ -62,7 +66,6 @@ const (
ActionActiveCount Action = "activeCount"
ActionAudit Action = "audit"
ActionAvailableDocumentAccesses Action = "availableDocumentAccesses"
ActionDocument Action = "document"
ActionDocumentVersion Action = "documentVersion"
ActionDownloadUrl Action = "downloadUrl"
ActionMemberships Action = "memberships"
@@ -77,36 +80,38 @@ const (
ActionTotalCount Action = "totalCount"
ActionTrustCenterFile Action = "trustCenterFile"
ActionListAccesses Action = "listAccesses"
ActionListAssets Action = "listAssets"
ActionListAudits Action = "listAudits"
ActionListComplianceReports Action = "listComplianceReports"
ActionListContacts Action = "listContacts"
ActionListContinualImprovements Action = "listContinualImprovements"
ActionListControls Action = "listControls"
ActionListData Action = "listData"
ActionListDocuments Action = "listDocuments"
ActionListEvidences Action = "listEvidences"
ActionListFrameworks Action = "listFrameworks"
ActionListInvitations Action = "listInvitations"
ActionListMeasures Action = "listMeasures"
ActionListMeetings Action = "listMeetings"
ActionListMembers Action = "listMembers"
ActionListNonconformities Action = "listNonconformities"
ActionListObligations Action = "listObligations"
ActionListPeople Action = "listPeople"
ActionListProcessingActivities Action = "listProcessingActivities"
ActionListReferences Action = "listReferences"
ActionListRiskAssessments Action = "listRiskAssessments"
ActionListRisks Action = "listRisks"
ActionListSAMLConfigurations Action = "listSAMLConfigurations"
ActionListServices Action = "listServices"
ActionListSlackConnections Action = "listSlackConnections"
ActionListSnapshots Action = "listSnapshots"
ActionListTasks Action = "listTasks"
ActionListTrustCenterFiles Action = "listTrustCenterFiles"
ActionListVendors Action = "listVendors"
ActionListVersions Action = "listVersions"
ActionListAccesses Action = "listAccesses"
ActionListAssets Action = "listAssets"
ActionListAudits Action = "listAudits"
ActionListComplianceReports Action = "listComplianceReports"
ActionListContacts Action = "listContacts"
ActionListContinualImprovements Action = "listContinualImprovements"
ActionListControls Action = "listControls"
ActionListData Action = "listData"
ActionListDocuments Action = "listDocuments"
ActionListEvidences Action = "listEvidences"
ActionListFrameworks Action = "listFrameworks"
ActionListInvitations Action = "listInvitations"
ActionListMeasures Action = "listMeasures"
ActionListMeetings Action = "listMeetings"
ActionListMembers Action = "listMembers"
ActionListNonconformities Action = "listNonconformities"
ActionListObligations Action = "listObligations"
ActionListPeople Action = "listPeople"
ActionListProcessingActivities Action = "listProcessingActivities"
ActionListReferences Action = "listReferences"
ActionListRiskAssessments Action = "listRiskAssessments"
ActionListRisks Action = "listRisks"
ActionListSAMLConfigurations Action = "listSAMLConfigurations"
ActionListServices Action = "listServices"
ActionListSlackConnections Action = "listSlackConnections"
ActionListSnapshots Action = "listSnapshots"
ActionListTasks Action = "listTasks"
ActionListTrustCenterFiles Action = "listTrustCenterFiles"
ActionListVendors Action = "listVendors"
ActionListVersions Action = "listVersions"
ActionListSignableDocuments Action = "listSignableDocuments"
ActionListSignableDocumentVersion Action = "listSignableDocumentVersion"
ActionCreateAsset Action = "createAsset"
ActionCreateAudit Action = "createAudit"
@@ -222,10 +227,12 @@ const (
ActionBulkPublishDocumentVersions Action = "bulkPublishDocumentVersions"
ActionBulkRequestSignatures Action = "bulkRequestSignatures"
ActionCancelSignatureRequest Action = "cancelSignatureRequest"
ActionSignDocument Action = "signDocument"
ActionConfirmEmail Action = "confirmEmail"
ActionDisableSAML Action = "disableSAML"
ActionEnableSAML Action = "enableSAML"
ActionExportDocumentVersionPDF Action = "exportDocumentVersionPDF"
ActionExportSignableVersionDocumentPDF Action = "exportSignableVersionDocumentPDF"
ActionExportFramework Action = "exportFramework"
ActionGenerateDocumentChangelog Action = "generateDocumentChangelog"
ActionGenerateFrameworkStateOfApplicability Action = "generateFrameworkStateOfApplicability"
@@ -248,45 +255,47 @@ const (
)
var (
AllRoles = []Role{RoleOwner, RoleAdmin, RoleViewer, RoleFull}
EditRoles = []Role{RoleOwner, RoleAdmin, RoleFull}
AllRoles = []Role{RoleOwner, RoleAdmin, RoleEmployee, RoleViewer, RoleFull}
NonEmployeeRoles = []Role{RoleOwner, RoleAdmin, RoleViewer, RoleFull}
EditRoles = []Role{RoleOwner, RoleAdmin, RoleFull}
)
var Permissions = map[uint16]map[Action][]Role{
coredata.OrganizationEntityType: {
ActionGet: AllRoles,
ActionGetLogoUrl: AllRoles,
ActionGetHorizontalLogoUrl: AllRoles,
ActionMemberships: AllRoles,
ActionPeoples: AllRoles,
ActionTotalCount: AllRoles,
ActionListMembers: AllRoles,
ActionListInvitations: AllRoles,
ActionListSlackConnections: AllRoles,
ActionListFrameworks: AllRoles,
ActionListControls: AllRoles,
ActionListVendors: AllRoles,
ActionListPeople: AllRoles,
ActionListDocuments: AllRoles,
ActionListMeetings: AllRoles,
ActionListMeasures: AllRoles,
ActionListRisks: AllRoles,
ActionListTasks: AllRoles,
ActionListAssets: AllRoles,
ActionListData: AllRoles,
ActionListAudits: AllRoles,
ActionListNonconformities: AllRoles,
ActionListObligations: AllRoles,
ActionListContinualImprovements: AllRoles,
ActionListProcessingActivities: AllRoles,
ActionListSnapshots: AllRoles,
ActionListTrustCenterFiles: AllRoles,
ActionGetTrustCenter: AllRoles,
ActionAudit: AllRoles,
ActionGetCustomDomain: AllRoles,
ActionListSAMLConfigurations: AllRoles,
ActionConfirmEmail: AllRoles,
ActionAcceptInvitation: AllRoles,
ActionGet: AllRoles,
ActionListSignableDocuments: AllRoles,
ActionGetLogoUrl: AllRoles,
ActionListDocuments: NonEmployeeRoles,
ActionGetHorizontalLogoUrl: NonEmployeeRoles,
ActionMemberships: NonEmployeeRoles,
ActionPeoples: NonEmployeeRoles,
ActionTotalCount: NonEmployeeRoles,
ActionListMembers: NonEmployeeRoles,
ActionListInvitations: NonEmployeeRoles,
ActionListSlackConnections: NonEmployeeRoles,
ActionListFrameworks: NonEmployeeRoles,
ActionListControls: NonEmployeeRoles,
ActionListVendors: NonEmployeeRoles,
ActionListPeople: NonEmployeeRoles,
ActionListMeetings: NonEmployeeRoles,
ActionListMeasures: NonEmployeeRoles,
ActionListRisks: NonEmployeeRoles,
ActionListTasks: NonEmployeeRoles,
ActionListAssets: NonEmployeeRoles,
ActionListData: NonEmployeeRoles,
ActionListAudits: NonEmployeeRoles,
ActionListNonconformities: NonEmployeeRoles,
ActionListObligations: NonEmployeeRoles,
ActionListContinualImprovements: NonEmployeeRoles,
ActionListProcessingActivities: NonEmployeeRoles,
ActionListSnapshots: NonEmployeeRoles,
ActionListTrustCenterFiles: NonEmployeeRoles,
ActionGetTrustCenter: NonEmployeeRoles,
ActionGetCustomDomain: NonEmployeeRoles,
ActionListSAMLConfigurations: NonEmployeeRoles,
ActionConfirmEmail: NonEmployeeRoles,
ActionAcceptInvitation: NonEmployeeRoles,
ActionUpdateOrganization: EditRoles,
ActionDeleteOrganizationHorizontalLogo: EditRoles,
@@ -325,11 +334,11 @@ var Permissions = map[uint16]map[Action][]Role{
ActionDeleteOrganization: {RoleOwner},
},
coredata.TrustCenterEntityType: {
ActionGet: AllRoles,
ActionGetNdaFileUrl: AllRoles,
ActionGetOrganization: AllRoles,
ActionListAccesses: AllRoles,
ActionListReferences: AllRoles,
ActionGet: NonEmployeeRoles,
ActionGetNdaFileUrl: NonEmployeeRoles,
ActionGetOrganization: NonEmployeeRoles,
ActionListAccesses: NonEmployeeRoles,
ActionListReferences: NonEmployeeRoles,
ActionUpdateTrustCenter: EditRoles,
ActionUploadTrustCenterNDA: EditRoles,
@@ -338,62 +347,59 @@ var Permissions = map[uint16]map[Action][]Role{
ActionCreateTrustCenterReference: EditRoles,
},
coredata.TrustCenterAccessEntityType: {
ActionGet: AllRoles,
ActionActiveCount: AllRoles,
ActionPendingRequestCount: AllRoles,
ActionAvailableDocumentAccesses: AllRoles,
ActionDocument: AllRoles,
ActionReport: AllRoles,
ActionTrustCenterFile: AllRoles,
ActionGet: NonEmployeeRoles,
ActionActiveCount: NonEmployeeRoles,
ActionPendingRequestCount: NonEmployeeRoles,
ActionAvailableDocumentAccesses: NonEmployeeRoles,
ActionUpdateTrustCenterAccess: EditRoles,
ActionDeleteTrustCenterAccess: EditRoles,
},
coredata.TrustCenterReferenceEntityType: {
ActionGet: AllRoles,
ActionGetLogoUrl: AllRoles,
ActionGet: NonEmployeeRoles,
ActionGetLogoUrl: NonEmployeeRoles,
ActionUpdateTrustCenterReference: EditRoles,
ActionDeleteTrustCenterReference: EditRoles,
},
coredata.TrustCenterFileEntityType: {
ActionGet: AllRoles,
ActionGetFileUrl: AllRoles,
ActionGet: NonEmployeeRoles,
ActionGetFileUrl: NonEmployeeRoles,
ActionUpdateTrustCenterFile: EditRoles,
ActionGetTrustCenterFile: EditRoles,
ActionDeleteTrustCenterFile: EditRoles,
},
coredata.UserEntityType: {
ActionGet: AllRoles,
ActionGet: NonEmployeeRoles,
},
coredata.MembershipEntityType: {
ActionGet: AllRoles,
ActionGetAuthMethod: AllRoles,
ActionGet: NonEmployeeRoles,
ActionGetAuthMethod: NonEmployeeRoles,
},
coredata.InvitationEntityType: {
ActionGet: AllRoles,
ActionGetOrganization: AllRoles,
ActionGet: NonEmployeeRoles,
ActionGetOrganization: NonEmployeeRoles,
ActionDeleteInvitation: EditRoles,
},
coredata.PeopleEntityType: {
ActionGet: AllRoles,
ActionGet: NonEmployeeRoles,
ActionUpdatePeople: EditRoles,
ActionDeletePeople: EditRoles,
},
coredata.VendorEntityType: {
ActionGet: AllRoles,
ActionGetOrganization: AllRoles,
ActionListComplianceReports: AllRoles,
ActionGetBusinessAssociateAgreement: AllRoles,
ActionGetDataPrivacyAgreement: AllRoles,
ActionListContacts: AllRoles,
ActionListServices: AllRoles,
ActionListRiskAssessments: AllRoles,
ActionGetBusinessOwner: AllRoles,
ActionGetSecurityOwner: AllRoles,
ActionGet: NonEmployeeRoles,
ActionGetOrganization: NonEmployeeRoles,
ActionListComplianceReports: NonEmployeeRoles,
ActionGetBusinessAssociateAgreement: NonEmployeeRoles,
ActionGetDataPrivacyAgreement: NonEmployeeRoles,
ActionListContacts: NonEmployeeRoles,
ActionListServices: NonEmployeeRoles,
ActionListRiskAssessments: NonEmployeeRoles,
ActionGetBusinessOwner: NonEmployeeRoles,
ActionGetSecurityOwner: NonEmployeeRoles,
ActionUpdateVendor: EditRoles,
ActionDeleteVendor: EditRoles,
@@ -407,49 +413,49 @@ var Permissions = map[uint16]map[Action][]Role{
ActionAssessVendor: EditRoles,
},
coredata.VendorComplianceReportEntityType: {
ActionGet: AllRoles,
ActionGetVendor: AllRoles,
ActionGetFile: AllRoles,
ActionGet: NonEmployeeRoles,
ActionGetVendor: NonEmployeeRoles,
ActionGetFile: NonEmployeeRoles,
ActionDeleteVendorComplianceReport: EditRoles,
},
coredata.VendorBusinessAssociateAgreementEntityType: {
ActionGet: AllRoles,
ActionGetVendor: AllRoles,
ActionGetFileUrl: AllRoles,
ActionGet: NonEmployeeRoles,
ActionGetVendor: NonEmployeeRoles,
ActionGetFileUrl: NonEmployeeRoles,
ActionUpdateVendorBusinessAssociateAgreement: EditRoles,
ActionDeleteVendorBusinessAssociateAgreement: EditRoles,
},
coredata.VendorContactEntityType: {
ActionGet: AllRoles,
ActionGetVendor: AllRoles,
ActionGet: NonEmployeeRoles,
ActionGetVendor: NonEmployeeRoles,
ActionUpdateVendorContact: EditRoles,
ActionDeleteVendorContact: EditRoles,
},
coredata.VendorServiceEntityType: {
ActionGet: AllRoles,
ActionGetVendor: AllRoles,
ActionGet: NonEmployeeRoles,
ActionGetVendor: NonEmployeeRoles,
ActionUpdateVendorService: EditRoles,
ActionDeleteVendorService: EditRoles,
},
coredata.VendorDataPrivacyAgreementEntityType: {
ActionGet: AllRoles,
ActionGetVendor: AllRoles,
ActionGetFileUrl: AllRoles,
ActionGet: NonEmployeeRoles,
ActionGetVendor: NonEmployeeRoles,
ActionGetFileUrl: NonEmployeeRoles,
ActionUpdateVendorDataPrivacyAgreement: EditRoles,
ActionDeleteVendorDataPrivacyAgreement: EditRoles,
},
coredata.VendorRiskAssessmentEntityType: {
ActionGet: AllRoles,
ActionGet: NonEmployeeRoles,
},
coredata.FrameworkEntityType: {
ActionGet: AllRoles,
ActionGetOrganization: AllRoles,
ActionListControls: AllRoles,
ActionGet: NonEmployeeRoles,
ActionGetOrganization: NonEmployeeRoles,
ActionListControls: NonEmployeeRoles,
ActionCreateControl: EditRoles,
ActionUpdateFramework: EditRoles,
@@ -458,12 +464,12 @@ var Permissions = map[uint16]map[Action][]Role{
ActionExportFramework: EditRoles,
},
coredata.ControlEntityType: {
ActionGet: AllRoles,
ActionGetFramework: AllRoles,
ActionListMeasures: AllRoles,
ActionListDocuments: AllRoles,
ActionListAudits: AllRoles,
ActionListSnapshots: AllRoles,
ActionGet: NonEmployeeRoles,
ActionGetFramework: NonEmployeeRoles,
ActionListMeasures: NonEmployeeRoles,
ActionListDocuments: NonEmployeeRoles,
ActionListAudits: NonEmployeeRoles,
ActionListSnapshots: NonEmployeeRoles,
ActionUpdateControl: EditRoles,
ActionDeleteControl: EditRoles,
@@ -477,23 +483,23 @@ var Permissions = map[uint16]map[Action][]Role{
ActionDeleteControlSnapshotMapping: EditRoles,
},
coredata.MeasureEntityType: {
ActionGet: AllRoles,
ActionListEvidences: AllRoles,
ActionListTasks: AllRoles,
ActionListRisks: AllRoles,
ActionListControls: AllRoles,
ActionTotalCount: AllRoles,
ActionGet: NonEmployeeRoles,
ActionListEvidences: NonEmployeeRoles,
ActionListTasks: NonEmployeeRoles,
ActionListRisks: NonEmployeeRoles,
ActionListControls: NonEmployeeRoles,
ActionTotalCount: NonEmployeeRoles,
ActionUpdateMeasure: EditRoles,
ActionDeleteMeasure: EditRoles,
ActionUploadMeasureEvidence: EditRoles,
},
coredata.TaskEntityType: {
ActionGet: AllRoles,
ActionGetAssignedTo: AllRoles,
ActionGetOrganization: AllRoles,
ActionGetMeasure: AllRoles,
ActionListEvidences: AllRoles,
ActionGet: NonEmployeeRoles,
ActionGetAssignedTo: NonEmployeeRoles,
ActionGetOrganization: NonEmployeeRoles,
ActionGetMeasure: NonEmployeeRoles,
ActionListEvidences: NonEmployeeRoles,
ActionUpdateTask: EditRoles,
ActionDeleteTask: EditRoles,
@@ -501,28 +507,31 @@ var Permissions = map[uint16]map[Action][]Role{
ActionUnassignTask: EditRoles,
},
coredata.EvidenceEntityType: {
ActionGet: AllRoles,
ActionGetFile: AllRoles,
ActionGetTask: AllRoles,
ActionGetMeasure: AllRoles,
ActionGet: NonEmployeeRoles,
ActionGetFile: NonEmployeeRoles,
ActionGetTask: NonEmployeeRoles,
ActionGetMeasure: NonEmployeeRoles,
ActionDeleteEvidence: EditRoles,
},
coredata.DocumentEntityType: {
ActionGet: AllRoles,
ActionExportDocumentVersionPDF: AllRoles,
ActionGetOwner: AllRoles,
ActionGetOrganization: AllRoles,
ActionListVersions: AllRoles,
ActionListControls: AllRoles,
ActionTotalCount: AllRoles,
ActionListSignableDocumentVersion: AllRoles,
ActionGetSigned: AllRoles,
ActionGetSignableDocument: AllRoles,
ActionGet: NonEmployeeRoles,
ActionGetOwner: NonEmployeeRoles,
ActionGetOrganization: NonEmployeeRoles,
ActionBulkExportDocuments: NonEmployeeRoles,
ActionTotalCount: NonEmployeeRoles,
ActionListControls: NonEmployeeRoles,
ActionListVersions: NonEmployeeRoles,
ActionUpdateDocument: EditRoles,
ActionDeleteDocument: EditRoles,
ActionPublishDocumentVersion: EditRoles,
ActionBulkPublishDocumentVersions: EditRoles,
ActionBulkDeleteDocuments: EditRoles,
ActionBulkExportDocuments: EditRoles,
ActionGenerateDocumentChangelog: EditRoles,
ActionCreateDraftDocumentVersion: EditRoles,
ActionDeleteDraftDocumentVersion: EditRoles,
@@ -533,33 +542,35 @@ var Permissions = map[uint16]map[Action][]Role{
ActionCancelSignatureRequest: EditRoles,
},
coredata.DocumentVersionEntityType: {
ActionGet: AllRoles,
ActionGetFile: AllRoles,
ActionGetOwner: AllRoles,
ActionDocument: AllRoles,
ActionSignatures: AllRoles,
ActionExportDocumentVersionPDF: AllRoles,
ActionSignDocument: AllRoles,
ActionUpdateDocumentVersion: EditRoles,
ActionRequestSignature: EditRoles,
ActionBulkRequestSignatures: EditRoles,
ActionSendSigningNotifications: EditRoles,
ActionCancelSignatureRequest: EditRoles,
ActionExportSignableVersionDocumentPDF: AllRoles,
ActionGetSigned: AllRoles,
ActionGet: NonEmployeeRoles,
ActionGetFile: NonEmployeeRoles,
ActionGetOwner: NonEmployeeRoles,
ActionGetDocument: NonEmployeeRoles,
ActionSignatures: NonEmployeeRoles,
ActionExportDocumentVersionPDF: NonEmployeeRoles,
ActionUpdateDocumentVersion: EditRoles,
ActionRequestSignature: EditRoles,
},
coredata.DocumentVersionSignatureEntityType: {
ActionGet: AllRoles,
ActionDocumentVersion: AllRoles,
ActionSignedBy: AllRoles,
ActionGet: NonEmployeeRoles,
ActionDocumentVersion: NonEmployeeRoles,
ActionSignedBy: NonEmployeeRoles,
},
coredata.RiskEntityType: {
ActionGet: AllRoles,
ActionGetOwner: AllRoles,
ActionGetOrganization: AllRoles,
ActionTotalCount: AllRoles,
ActionListControls: AllRoles,
ActionListMeasures: AllRoles,
ActionListDocuments: AllRoles,
ActionListObligations: AllRoles,
ActionGet: NonEmployeeRoles,
ActionGetOwner: NonEmployeeRoles,
ActionGetOrganization: NonEmployeeRoles,
ActionTotalCount: NonEmployeeRoles,
ActionListControls: NonEmployeeRoles,
ActionListMeasures: NonEmployeeRoles,
ActionListDocuments: NonEmployeeRoles,
ActionListObligations: NonEmployeeRoles,
ActionUpdateRisk: EditRoles,
ActionDeleteRisk: EditRoles,
@@ -571,32 +582,32 @@ var Permissions = map[uint16]map[Action][]Role{
ActionDeleteRiskObligationMapping: EditRoles,
},
coredata.AssetEntityType: {
ActionGet: AllRoles,
ActionGetOwner: AllRoles,
ActionListVendors: AllRoles,
ActionGetAssetType: AllRoles,
ActionGetOrganization: AllRoles,
ActionGet: NonEmployeeRoles,
ActionGetOwner: NonEmployeeRoles,
ActionListVendors: NonEmployeeRoles,
ActionGetAssetType: NonEmployeeRoles,
ActionGetOrganization: NonEmployeeRoles,
ActionUpdateAsset: EditRoles,
ActionDeleteAsset: EditRoles,
},
coredata.DatumEntityType: {
ActionGet: AllRoles,
ActionGetOwner: AllRoles,
ActionGetOrganization: AllRoles,
ActionListVendors: AllRoles,
ActionGet: NonEmployeeRoles,
ActionGetOwner: NonEmployeeRoles,
ActionGetOrganization: NonEmployeeRoles,
ActionListVendors: NonEmployeeRoles,
ActionUpdateDatum: EditRoles,
ActionDeleteDatum: EditRoles,
},
coredata.AuditEntityType: {
ActionGet: AllRoles,
ActionGetFile: AllRoles,
ActionGetFramework: AllRoles,
ActionGetOrganization: AllRoles,
ActionReport: AllRoles,
ActionReportUrl: AllRoles,
ActionListControls: AllRoles,
ActionGet: NonEmployeeRoles,
ActionGetFile: NonEmployeeRoles,
ActionGetFramework: NonEmployeeRoles,
ActionGetOrganization: NonEmployeeRoles,
ActionReport: NonEmployeeRoles,
ActionReportUrl: NonEmployeeRoles,
ActionListControls: NonEmployeeRoles,
ActionUpdateAudit: EditRoles,
ActionDeleteAudit: EditRoles,
@@ -604,51 +615,50 @@ var Permissions = map[uint16]map[Action][]Role{
ActionDeleteAuditReport: EditRoles,
},
coredata.ReportEntityType: {
ActionGet: AllRoles,
ActionGetFile: AllRoles,
ActionGetOrganization: AllRoles,
ActionGetSnapshot: AllRoles,
ActionDownloadUrl: AllRoles,
ActionAudit: AllRoles,
ActionGet: NonEmployeeRoles,
ActionGetFile: NonEmployeeRoles,
ActionGetOrganization: NonEmployeeRoles,
ActionGetSnapshot: NonEmployeeRoles,
ActionDownloadUrl: NonEmployeeRoles,
},
coredata.NonconformityEntityType: {
ActionGet: AllRoles,
ActionGetOwner: AllRoles,
ActionGetOrganization: AllRoles,
ActionAudit: AllRoles,
ActionGet: NonEmployeeRoles,
ActionGetOwner: NonEmployeeRoles,
ActionGetOrganization: NonEmployeeRoles,
ActionAudit: NonEmployeeRoles,
ActionUpdateNonconformity: EditRoles,
ActionDeleteNonconformity: EditRoles,
},
coredata.ObligationEntityType: {
ActionGet: AllRoles,
ActionGetOrganization: AllRoles,
ActionGetOwner: AllRoles,
ActionListRisks: AllRoles,
ActionGet: NonEmployeeRoles,
ActionGetOrganization: NonEmployeeRoles,
ActionGetOwner: NonEmployeeRoles,
ActionListRisks: NonEmployeeRoles,
ActionUpdateObligation: EditRoles,
ActionDeleteObligation: EditRoles,
},
coredata.ContinualImprovementEntityType: {
ActionGet: AllRoles,
ActionGetOwner: AllRoles,
ActionGetOrganization: AllRoles,
ActionGet: NonEmployeeRoles,
ActionGetOwner: NonEmployeeRoles,
ActionGetOrganization: NonEmployeeRoles,
ActionUpdateContinualImprovement: EditRoles,
ActionDeleteContinualImprovement: EditRoles,
},
coredata.ProcessingActivityEntityType: {
ActionGet: AllRoles,
ActionGetOrganization: AllRoles,
ActionListVendors: AllRoles,
ActionGet: NonEmployeeRoles,
ActionGetOrganization: NonEmployeeRoles,
ActionListVendors: NonEmployeeRoles,
ActionUpdateProcessingActivity: EditRoles,
ActionDeleteProcessingActivity: EditRoles,
},
coredata.SnapshotEntityType: {
ActionGet: AllRoles,
ActionGetOrganization: AllRoles,
ActionListControls: AllRoles,
ActionGet: NonEmployeeRoles,
ActionGetOrganization: NonEmployeeRoles,
ActionListControls: NonEmployeeRoles,
ActionDeleteSnapshot: EditRoles,
},
@@ -669,19 +679,18 @@ var Permissions = map[uint16]map[Action][]Role{
ActionVerifyDomain: {RoleOwner},
},
coredata.FileEntityType: {
ActionGet: AllRoles,
ActionDownloadUrl: AllRoles,
ActionGet: NonEmployeeRoles,
ActionDownloadUrl: NonEmployeeRoles,
},
coredata.TrustCenterDocumentAccessEntityType: {
ActionGet: AllRoles,
ActionDocument: {RoleOwner, RoleAdmin},
ActionReport: {RoleOwner, RoleAdmin},
ActionTrustCenterFile: {RoleOwner, RoleAdmin},
ActionGet: NonEmployeeRoles,
ActionReport: NonEmployeeRoles,
ActionTrustCenterFile: NonEmployeeRoles,
},
coredata.MeetingEntityType: {
ActionGet: AllRoles,
ActionGetOrganization: AllRoles,
ActionTotalCount: AllRoles,
ActionGet: NonEmployeeRoles,
ActionGetOrganization: NonEmployeeRoles,
ActionTotalCount: NonEmployeeRoles,
ActionUpdateMeeting: EditRoles,
ActionDeleteMeeting: EditRoles,

View File

@@ -124,6 +124,60 @@ LIMIT 1;
return nil
}
func (p *Document) LoadByIDWithFilter(
ctx context.Context,
conn pg.Conn,
scope Scoper,
documentID gid.GID,
filter *DocumentFilter,
) error {
q := `
SELECT
id,
organization_id,
owner_id,
title,
document_type,
classification,
current_published_version,
trust_center_visibility,
created_at,
updated_at
FROM
documents
WHERE
%s
AND deleted_at IS NULL
AND id = @document_id
AND %s
LIMIT 1;
`
q = fmt.Sprintf(q, scope.SQLFragment(), filter.SQLFragment())
args := pgx.StrictNamedArgs{"document_id": documentID}
maps.Copy(args, scope.SQLArguments())
maps.Copy(args, filter.SQLArguments())
rows, err := conn.Query(ctx, q, args)
if err != nil {
return fmt.Errorf("cannot query documents: %w", err)
}
document, err := pgx.CollectExactlyOneRow(rows, pgx.RowToStructByName[Document])
if err != nil {
if errors.Is(err, pgx.ErrNoRows) {
return &ErrDocumentNotFound{Identifier: documentID.String()}
}
return fmt.Errorf("cannot collect document: %w", err)
}
*p = document
return nil
}
func (p *Documents) CountByOrganizationID(
ctx context.Context,
conn pg.Conn,
@@ -400,28 +454,17 @@ func (p *Documents) CountByControlID(
filter *DocumentFilter,
) (int, error) {
q := `
WITH plcs AS (
SELECT
p.id,
p.tenant_id,
p.search_vector,
p.trust_center_visibility,
p.deleted_at
FROM
documents p
INNER JOIN
controls_documents cp ON p.id = cp.document_id
WHERE
cp.control_id = @control_id
WITH scoped_documents AS (
SELECT *
FROM documents
WHERE %s
AND deleted_at IS NULL
AND %s
)
SELECT
COUNT(id)
FROM
plcs
WHERE
%s
AND deleted_at IS NULL
AND %s
SELECT COUNT(scoped_documents.id)
FROM scoped_documents
INNER JOIN controls_documents cp ON scoped_documents.id = cp.document_id
WHERE cp.control_id = @control_id
`
q = fmt.Sprintf(q, scope.SQLFragment(), filter.SQLFragment())
@@ -448,46 +491,28 @@ func (p *Documents) LoadByControlID(
filter *DocumentFilter,
) error {
q := `
WITH plcs AS (
SELECT
p.id,
p.tenant_id,
p.search_vector,
p.organization_id,
p.owner_id,
p.title,
p.document_type,
p.classification,
p.current_published_version,
p.trust_center_visibility,
p.created_at,
p.updated_at,
p.deleted_at
FROM
documents p
INNER JOIN
controls_documents cp ON p.id = cp.document_id
WHERE
cp.control_id = @control_id
WITH scoped_documents AS (
SELECT *
FROM documents
WHERE %s
AND deleted_at IS NULL
AND %s
AND %s
)
SELECT
id,
organization_id,
owner_id,
title,
document_type,
classification,
current_published_version,
trust_center_visibility,
created_at,
updated_at
FROM
plcs
WHERE
%s
AND deleted_at IS NULL
AND %s
AND %s
scoped_documents.id,
scoped_documents.organization_id,
scoped_documents.owner_id,
scoped_documents.title,
scoped_documents.document_type,
scoped_documents.classification,
scoped_documents.current_published_version,
scoped_documents.trust_center_visibility,
scoped_documents.created_at,
scoped_documents.updated_at
FROM scoped_documents
INNER JOIN controls_documents cp ON scoped_documents.id = cp.document_id
WHERE cp.control_id = @control_id
`
q = fmt.Sprintf(q, scope.SQLFragment(), filter.SQLFragment(), cursor.SQLFragment())
@@ -519,28 +544,17 @@ func (p *Documents) CountByRiskID(
filter *DocumentFilter,
) (int, error) {
q := `
WITH plcs AS (
SELECT
p.id,
p.tenant_id,
p.search_vector,
p.trust_center_visibility,
p.deleted_at
FROM
documents p
INNER JOIN
risks_documents rp ON p.id = rp.document_id
WHERE
rp.risk_id = @risk_id
WITH scoped_documents AS (
SELECT *
FROM documents
WHERE %s
AND deleted_at IS NULL
AND %s
)
SELECT
COUNT(id)
FROM
plcs
WHERE
%s
AND deleted_at IS NULL
AND %s
SELECT COUNT(scoped_documents.id)
FROM scoped_documents
INNER JOIN risks_documents rp ON scoped_documents.id = rp.document_id
WHERE rp.risk_id = @risk_id
`
q = fmt.Sprintf(q, scope.SQLFragment(), filter.SQLFragment())
@@ -567,46 +581,28 @@ func (p *Documents) LoadByRiskID(
filter *DocumentFilter,
) error {
q := `
WITH plcs AS (
SELECT
p.id,
p.tenant_id,
p.organization_id,
p.owner_id,
p.title,
p.document_type,
p.classification,
p.current_published_version,
p.trust_center_visibility,
p.created_at,
p.updated_at,
p.search_vector,
p.deleted_at
FROM
documents p
INNER JOIN
risks_documents rp ON p.id = rp.document_id
WHERE
rp.risk_id = @risk_id
WITH scoped_documents AS (
SELECT *
FROM documents
WHERE %s
AND deleted_at IS NULL
AND %s
AND %s
)
SELECT
id,
organization_id,
owner_id,
title,
document_type,
classification,
current_published_version,
trust_center_visibility,
created_at,
updated_at
FROM
plcs
WHERE
%s
AND deleted_at IS NULL
AND %s
AND %s
scoped_documents.id,
scoped_documents.organization_id,
scoped_documents.owner_id,
scoped_documents.title,
scoped_documents.document_type,
scoped_documents.classification,
scoped_documents.current_published_version,
scoped_documents.trust_center_visibility,
scoped_documents.created_at,
scoped_documents.updated_at
FROM scoped_documents
INNER JOIN risks_documents rp ON scoped_documents.id = rp.document_id
WHERE rp.risk_id = @risk_id
`
q = fmt.Sprintf(q, scope.SQLFragment(), filter.SQLFragment(), cursor.SQLFragment())
@@ -653,3 +649,61 @@ UPDATE documents SET deleted_at = @deleted_at WHERE %s AND id = ANY(@document_id
_, err := conn.Exec(ctx, q, args)
return err
}
func (p *Document) IsLastSignableVersionSignedByUserEmail(
ctx context.Context,
conn pg.Conn,
scope Scoper,
documentID gid.GID,
userEmail string,
) (bool, error) {
q := `
WITH last_signable_version AS (
SELECT
d.id AS document_id,
d.tenant_id,
dv.version_number,
dvs.state
FROM documents d
INNER JOIN document_versions dv ON dv.document_id = d.id
INNER JOIN document_version_signatures dvs ON dvs.document_version_id = dv.id
INNER JOIN peoples p ON dvs.signed_by = p.id
WHERE d.id = @document_id
AND p.primary_email_address = @user_email
AND dv.version_number = (
SELECT MAX(dv2.version_number)
FROM document_versions dv2
INNER JOIN document_version_signatures dvs2 ON dvs2.document_version_id = dv2.id
INNER JOIN peoples p2 ON dvs2.signed_by = p2.id
WHERE dv2.document_id = d.id
AND p2.primary_email_address = @user_email
)
)
SELECT EXISTS (
SELECT 1
FROM last_signable_version
WHERE %s
AND state = 'SIGNED'
) AS signed
`
q = fmt.Sprintf(q, scope.SQLFragment())
args := pgx.StrictNamedArgs{
"document_id": documentID,
"user_email": userEmail,
}
maps.Copy(args, scope.SQLArguments())
rows, err := conn.Query(ctx, q, args)
if err != nil {
return false, fmt.Errorf("cannot query document signed status: %w", err)
}
signed, err := pgx.CollectOneRow(rows, pgx.RowTo[bool])
if err != nil {
return false, fmt.Errorf("cannot collect signed status: %w", err)
}
return signed, nil
}

View File

@@ -22,6 +22,8 @@ type (
DocumentFilter struct {
query *string
trustCenterVisibilities []TrustCenterVisibility
published *bool
userEmail *string
}
)
@@ -40,7 +42,17 @@ func NewDocumentTrustCenterFilter() *DocumentFilter {
}
}
func (f *DocumentFilter) SQLArguments() pgx.StrictNamedArgs {
func (f *DocumentFilter) WithPublished(published *bool) *DocumentFilter {
f.published = published
return f
}
func (f *DocumentFilter) WithUserEmail(userEmail *string) *DocumentFilter {
f.userEmail = userEmail
return f
}
func (f *DocumentFilter) SQLArguments() pgx.NamedArgs {
var visibilities []string
if f.trustCenterVisibilities != nil {
visibilities = make([]string, len(f.trustCenterVisibilities))
@@ -48,9 +60,11 @@ func (f *DocumentFilter) SQLArguments() pgx.StrictNamedArgs {
visibilities[i] = v.String()
}
}
return pgx.StrictNamedArgs{
return pgx.NamedArgs{
"query": f.query,
"trust_center_visibilities": visibilities,
"published": f.published,
"user_email": f.userEmail,
}
}
@@ -71,5 +85,25 @@ func (f *DocumentFilter) SQLFragment() string {
trust_center_visibility = ANY(@trust_center_visibilities::trust_center_visibility[])
ELSE TRUE
END
AND
CASE
WHEN @published::boolean IS NULL THEN TRUE
WHEN @published::boolean IS TRUE THEN current_published_version IS NOT NULL
WHEN @published::boolean IS FALSE THEN current_published_version IS NULL
END
AND
CASE
WHEN @user_email::text IS NULL THEN TRUE
ELSE EXISTS (
SELECT 1
FROM document_versions dv
INNER JOIN document_version_signatures dvs ON dv.id = dvs.document_version_id
INNER JOIN peoples p ON dvs.signed_by = p.id
WHERE dv.document_id = documents.id
AND dv.status = 'PUBLISHED'
AND p.primary_email_address = @user_email::text
AND dvs.state IN ('REQUESTED', 'SIGNED')
)
END
)`
}

View File

@@ -78,6 +78,7 @@ func (p *DocumentVersions) LoadByDocumentID(
scope Scoper,
documentID gid.GID,
cursor *page.Cursor[DocumentVersionOrderField],
filter *DocumentVersionFilter,
) error {
q := `
SELECT
@@ -100,14 +101,16 @@ WHERE
%s
AND document_id = @document_id
AND %s
AND %s
`
q = fmt.Sprintf(q, scope.SQLFragment(), cursor.SQLFragment())
q = fmt.Sprintf(q, scope.SQLFragment(), filter.SQLFragment(), cursor.SQLFragment())
args := pgx.StrictNamedArgs{
"document_id": documentID,
}
maps.Copy(args, scope.SQLArguments())
maps.Copy(args, cursor.SQLArguments())
maps.Copy(args, filter.SQLArguments())
rows, err := conn.Query(ctx, q, args)
if err != nil {

View File

@@ -0,0 +1,55 @@
// Copyright (c) 2025 Probo Inc <hello@getprobo.com>.
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies.
//
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
package coredata
import (
"github.com/jackc/pgx/v5"
)
type (
DocumentVersionFilter struct {
userEmail *string
}
)
func NewDocumentVersionFilter() *DocumentVersionFilter {
return &DocumentVersionFilter{}
}
func (f *DocumentVersionFilter) WithUserEmail(userEmail *string) *DocumentVersionFilter {
f.userEmail = userEmail
return f
}
func (f *DocumentVersionFilter) SQLArguments() pgx.StrictNamedArgs {
return pgx.StrictNamedArgs{
"user_email": f.userEmail,
}
}
func (f *DocumentVersionFilter) SQLFragment() string {
return `
(
@user_email::text IS NULL
OR EXISTS (
SELECT 1
FROM document_version_signatures dvs
INNER JOIN peoples p ON dvs.signed_by = p.id
WHERE dvs.document_version_id = document_versions.id
AND p.primary_email_address = @user_email::text
AND dvs.state IN ('REQUESTED', 'SIGNED')
)
)`
}

View File

@@ -20,7 +20,6 @@ type (
const (
DocumentVersionOrderFieldCreatedAt DocumentVersionOrderField = "CREATED_AT"
DocumentVersionOrderFieldVersion DocumentVersionOrderField = "VERSION"
)
func (p DocumentVersionOrderField) Column() string {

View File

@@ -57,6 +57,8 @@ type (
ErrDocumentVersionSignatureAlreadyExists struct {
message string
}
ErrDocumentVersionSignatureAlreadySigned struct{}
)
func (e ErrDocumentVersionSignatureNotFound) Error() string {
@@ -67,6 +69,10 @@ func (e ErrDocumentVersionSignatureAlreadyExists) Error() string {
return e.message
}
func (e ErrDocumentVersionSignatureAlreadySigned) Error() string {
return "document version already signed"
}
func (pvs DocumentVersionSignature) CursorKey(orderBy DocumentVersionSignatureOrderField) page.CursorKey {
switch orderBy {
case DocumentVersionSignatureOrderFieldCreatedAt:
@@ -412,3 +418,41 @@ WHERE
return nil
}
func (pvs *DocumentVersionSignature) IsSignedByUserEmail(
ctx context.Context,
conn pg.Conn,
scope Scoper,
documentVersionID gid.GID,
userEmail string,
) (bool, error) {
q := `
SELECT EXISTS (
SELECT 1
FROM document_version_signatures dvs
INNER JOIN peoples p ON dvs.signed_by = p.id
WHERE dvs.document_version_id = @document_version_id
AND p.primary_email_address = @user_email
AND dvs.state = 'SIGNED'
AND dvs.tenant_id = @tenant_id
) AS signed
`
args := pgx.StrictNamedArgs{
"document_version_id": documentVersionID,
"user_email": userEmail,
}
maps.Copy(args, scope.SQLArguments())
rows, err := conn.Query(ctx, q, args)
if err != nil {
return false, fmt.Errorf("cannot query document version signature: %w", err)
}
signed, err := pgx.CollectOneRow(rows, pgx.RowTo[bool])
if err != nil {
return false, fmt.Errorf("cannot collect signed status: %w", err)
}
return signed, nil
}

View File

@@ -22,9 +22,10 @@ import (
type MembershipRole string
const (
MembershipRoleOwner MembershipRole = "OWNER"
MembershipRoleAdmin MembershipRole = "ADMIN"
MembershipRoleViewer MembershipRole = "VIEWER"
MembershipRoleOwner MembershipRole = "OWNER"
MembershipRoleAdmin MembershipRole = "ADMIN"
MembershipRoleEmployee MembershipRole = "EMPLOYEE"
MembershipRoleViewer MembershipRole = "VIEWER"
)
func (r MembershipRole) String() string {
@@ -47,6 +48,8 @@ func (r *MembershipRole) Scan(value any) error {
*r = MembershipRoleOwner
case "ADMIN":
*r = MembershipRoleAdmin
case "EMPLOYEE":
*r = MembershipRoleEmployee
case "VIEWER":
*r = MembershipRoleViewer
default:

View File

@@ -0,0 +1 @@
ALTER TYPE authz_role RENAME VALUE 'MEMBER' TO 'EMPLOYEE';

View File

@@ -132,24 +132,24 @@ func (p *People) LoadByEmail(
primaryEmailAddress string,
) error {
q := `
SELECT
id,
organization_id,
kind,
full_name,
primary_email_address,
additional_email_addresses,
position,
contract_start_date,
contract_end_date,
created_at,
updated_at
FROM
peoples
WHERE
%s
AND primary_email_address = @primary_email_address
LIMIT 1;
SELECT
id,
organization_id,
kind,
full_name,
primary_email_address,
additional_email_addresses,
position,
contract_start_date,
contract_end_date,
created_at,
updated_at
FROM
peoples
WHERE
%s
AND primary_email_address = @primary_email_address
LIMIT 1;
`
q = fmt.Sprintf(q, scope.SQLFragment())
@@ -176,6 +176,62 @@ func (p *People) LoadByEmail(
return nil
}
func (p *People) LoadByEmailAndOrganizationID(
ctx context.Context,
conn pg.Conn,
scope Scoper,
primaryEmailAddress string,
organizationID gid.GID,
) error {
q := `
SELECT
id,
organization_id,
kind,
full_name,
primary_email_address,
additional_email_addresses,
position,
contract_start_date,
contract_end_date,
created_at,
updated_at
FROM
peoples
WHERE
%s
AND primary_email_address = @primary_email_address
AND organization_id = @organization_id
LIMIT 1;
`
q = fmt.Sprintf(q, scope.SQLFragment())
args := pgx.StrictNamedArgs{
"primary_email_address": primaryEmailAddress,
"organization_id": organizationID,
}
maps.Copy(args, scope.SQLArguments())
rows, err := conn.Query(ctx, q, args)
if err != nil {
return fmt.Errorf("cannot query people: %w", err)
}
people, err := pgx.CollectExactlyOneRow(rows, pgx.RowToStructByName[People])
if err != nil {
if errors.Is(err, pgx.ErrNoRows) {
return &ErrPeopleNotFound{Identifier: primaryEmailAddress}
}
return fmt.Errorf("cannot collect people: %w", err)
}
*p = people
return nil
}
func (p *Peoples) LoadByIDs(
ctx context.Context,
conn pg.Conn,

View File

@@ -164,6 +164,32 @@ func (s *DocumentService) Get(
return document, nil
}
func (s *DocumentService) GetWithFilter(
ctx context.Context,
documentID gid.GID,
filter *coredata.DocumentFilter,
) (*coredata.Document, error) {
document := &coredata.Document{}
err := s.svc.pg.WithConn(
ctx,
func(conn pg.Conn) error {
err := document.LoadByIDWithFilter(ctx, conn, s.svc.scope, documentID, filter)
if err != nil {
return fmt.Errorf("cannot load document: %w", err)
}
return nil
},
)
if err != nil {
return nil, err
}
return document, nil
}
func (s DocumentService) GenerateChangelog(
ctx context.Context,
documentID gid.GID,
@@ -559,39 +585,13 @@ func (s *DocumentService) SignDocumentVersion(
documentVersionID gid.GID,
signatory gid.GID,
) error {
documentVersion := &coredata.DocumentVersion{}
documentVersionSignature := &coredata.DocumentVersionSignature{}
now := time.Now()
err := s.svc.pg.WithTx(
ctx,
func(conn pg.Conn) error {
if err := documentVersion.LoadByID(ctx, conn, s.svc.scope, documentVersionID); err != nil {
return fmt.Errorf("cannot load document version %q: %w", documentVersionID, err)
}
if documentVersion.Status != coredata.DocumentStatusPublished {
return fmt.Errorf("cannot sign unpublished version")
}
if err := documentVersionSignature.LoadByDocumentVersionIDAndSignatory(ctx, conn, s.svc.scope, documentVersionID, signatory); err != nil {
return fmt.Errorf("cannot load document version signature: %w", err)
}
if documentVersionSignature.State == coredata.DocumentVersionSignatureStateSigned {
return fmt.Errorf("document version already signed")
}
documentVersionSignature.State = coredata.DocumentVersionSignatureStateSigned
documentVersionSignature.SignedAt = &now
documentVersionSignature.UpdatedAt = now
if err := documentVersion.Update(ctx, conn, s.svc.scope); err != nil {
return fmt.Errorf("cannot update document version: %w", err)
}
if err := documentVersionSignature.Update(ctx, conn, s.svc.scope); err != nil {
return fmt.Errorf("cannot update document version signature: %w", err)
var err error
_, err = s.signDocumentVersionInTx(ctx, conn, documentVersionID, signatory)
if err != nil {
return fmt.Errorf("cannot sign document version: %w", err)
}
return nil
@@ -605,6 +605,80 @@ func (s *DocumentService) SignDocumentVersion(
return nil
}
func (s *DocumentService) SignDocumentVersionByEmail(
ctx context.Context,
documentVersionID gid.GID,
userEmail string,
) (*coredata.DocumentVersionSignature, error) {
var documentVersionSignature *coredata.DocumentVersionSignature
err := s.svc.pg.WithTx(
ctx,
func(conn pg.Conn) error {
documentVersion := &coredata.DocumentVersion{}
if err := documentVersion.LoadByID(ctx, conn, s.svc.scope, documentVersionID); err != nil {
return fmt.Errorf("cannot get document version: %w", err)
}
people := &coredata.People{}
if err := people.LoadByEmailAndOrganizationID(ctx, conn, s.svc.scope, userEmail, documentVersion.OrganizationID); err != nil {
return fmt.Errorf("cannot find people record for user email in organization %q: %w", documentVersion.OrganizationID, err)
}
var signErr error
documentVersionSignature, signErr = s.signDocumentVersionInTx(ctx, conn, documentVersionID, people.ID)
return signErr
},
)
if err != nil {
return nil, fmt.Errorf("cannot sign document version: %w", err)
}
return documentVersionSignature, nil
}
func (s *DocumentService) signDocumentVersionInTx(
ctx context.Context,
conn pg.Conn,
documentVersionID gid.GID,
signatory gid.GID,
) (*coredata.DocumentVersionSignature, error) {
documentVersion := &coredata.DocumentVersion{}
documentVersionSignature := &coredata.DocumentVersionSignature{}
now := time.Now()
if err := documentVersion.LoadByID(ctx, conn, s.svc.scope, documentVersionID); err != nil {
return nil, fmt.Errorf("cannot load document version %q: %w", documentVersionID, err)
}
if documentVersion.Status != coredata.DocumentStatusPublished {
return nil, fmt.Errorf("cannot sign unpublished version")
}
if err := documentVersionSignature.LoadByDocumentVersionIDAndSignatory(ctx, conn, s.svc.scope, documentVersionID, signatory); err != nil {
return nil, fmt.Errorf("cannot load document version signature: %w", err)
}
if documentVersionSignature.State == coredata.DocumentVersionSignatureStateSigned {
return nil, &coredata.ErrDocumentVersionSignatureAlreadySigned{}
}
documentVersionSignature.State = coredata.DocumentVersionSignatureStateSigned
documentVersionSignature.SignedAt = &now
documentVersionSignature.UpdatedAt = now
if err := documentVersion.Update(ctx, conn, s.svc.scope); err != nil {
return nil, fmt.Errorf("cannot update document version: %w", err)
}
if err := documentVersionSignature.Update(ctx, conn, s.svc.scope); err != nil {
return nil, fmt.Errorf("cannot update document version signature: %w", err)
}
return documentVersionSignature, nil
}
func (s *DocumentService) UpdateVersion(
ctx context.Context,
req UpdateDocumentVersionRequest,
@@ -810,6 +884,36 @@ func (s *DocumentService) ListSignatures(
return page.NewPage(documentVersionSignatures, cursor), nil
}
func (s *DocumentService) IsVersionSignedByUserEmail(
ctx context.Context,
documentVersionID gid.GID,
userEmail string,
) (bool, error) {
documentVersionSignature := &coredata.DocumentVersionSignature{}
var signed bool
err := s.svc.pg.WithConn(
ctx,
func(conn pg.Conn) error {
var err error
signed, err = documentVersionSignature.IsSignedByUserEmail(
ctx,
conn,
s.svc.scope,
documentVersionID,
userEmail,
)
return err
},
)
if err != nil {
return false, fmt.Errorf("cannot check if document version is signed: %w", err)
}
return signed, nil
}
func (s *DocumentService) CreateDraft(
ctx context.Context,
documentID gid.GID,
@@ -998,13 +1102,20 @@ func (s *DocumentService) ListVersions(
ctx context.Context,
documentID gid.GID,
cursor *page.Cursor[coredata.DocumentVersionOrderField],
filter *coredata.DocumentVersionFilter,
) (*page.Page[*coredata.DocumentVersion, coredata.DocumentVersionOrderField], error) {
var documentVersions coredata.DocumentVersions
err := s.svc.pg.WithConn(
ctx,
func(conn pg.Conn) error {
return documentVersions.LoadByDocumentID(ctx, conn, s.svc.scope, documentID, cursor)
err := documentVersions.LoadByDocumentID(ctx, conn, s.svc.scope, documentID, cursor, filter)
if err != nil {
return fmt.Errorf("cannot load document versions: %w", err)
}
return nil
},
)
@@ -1035,6 +1146,36 @@ func (s *DocumentService) GetVersion(
return documentVersion, nil
}
func (s *DocumentService) IsSigned(
ctx context.Context,
documentID gid.GID,
userEmail string,
) (bool, error) {
document := &coredata.Document{}
var signed bool
err := s.svc.pg.WithConn(
ctx,
func(conn pg.Conn) error {
var err error
signed, err = document.IsLastSignableVersionSignedByUserEmail(
ctx,
conn,
s.svc.scope,
documentID,
userEmail,
)
return err
},
)
if err != nil {
return false, fmt.Errorf("cannot check if document is signed: %w", err)
}
return signed, nil
}
func (s *DocumentService) CountForOrganizationID(
ctx context.Context,
organizationID gid.GID,

View File

@@ -108,6 +108,31 @@ func (s PeopleService) Get(
return people, nil
}
func (s PeopleService) GetByEmailAndOrganizationID(
ctx context.Context,
primaryEmailAddress string,
organizationID gid.GID,
) (*coredata.People, error) {
people := &coredata.People{}
err := s.svc.pg.WithConn(
ctx,
func(conn pg.Conn) error {
err := people.LoadByEmailAndOrganizationID(ctx, conn, s.svc.scope, primaryEmailAddress, organizationID)
if err != nil {
return fmt.Errorf("cannot load people by email and organization ID: %w", err)
}
return nil
},
)
if err != nil {
return nil, err
}
return people, nil
}
func (s PeopleService) CountForOrganizationID(
ctx context.Context,
organizationID gid.GID,

View File

@@ -94,6 +94,7 @@ enum InvitationStatus
enum MembershipRole @goModel(model: "go.probo.inc/probo/pkg/coredata.MembershipRole") {
OWNER @goEnum(value: "go.probo.inc/probo/pkg/coredata.MembershipRoleOwner")
ADMIN @goEnum(value: "go.probo.inc/probo/pkg/coredata.MembershipRoleAdmin")
EMPLOYEE @goEnum(value: "go.probo.inc/probo/pkg/coredata.MembershipRoleEmployee")
VIEWER @goEnum(value: "go.probo.inc/probo/pkg/coredata.MembershipRoleViewer")
}
@@ -522,10 +523,6 @@ enum BusinessImpact
enum DocumentVersionOrderField
@goModel(model: "go.probo.inc/probo/pkg/coredata.DocumentVersionOrderField") {
VERSION
@goEnum(
value: "go.probo.inc/probo/pkg/coredata.DocumentVersionOrderFieldVersion"
)
CREATED_AT
@goEnum(
value: "go.probo.inc/probo/pkg/coredata.DocumentVersionOrderFieldCreatedAt"
@@ -2035,6 +2032,29 @@ type Document implements Node {
updatedAt: Datetime!
}
type SignableDocument @goModel(
model: "go.probo.inc/probo/pkg/server/api/console/v1/types.SignableDocument"
){
id: ID!
title: String!
description: String
documentType: DocumentType!
classification: DocumentClassification!
signed: Boolean! @goField(forceResolver: true)
versions(
first: Int
after: CursorKey
last: Int
before: CursorKey
orderBy: DocumentVersionOrder
filter: DocumentVersionFilter
): DocumentVersionConnection! @goField(forceResolver: true)
createdAt: Datetime!
updatedAt: Datetime!
}
type Meeting implements Node {
id: ID!
name: String!
@@ -2259,6 +2279,17 @@ type Viewer {
before: CursorKey
orderBy: OrganizationOrder
): OrganizationConnection! @goField(forceResolver: true)
signableDocuments(
organizationId: ID!
first: Int
after: CursorKey
last: Int
before: CursorKey
orderBy: DocumentOrder
): SignableDocumentConnection! @goField(forceResolver: true)
signableDocument(id: ID!): SignableDocument @goField(forceResolver: true)
}
# Connection Types
@@ -2514,6 +2545,22 @@ type EvidenceEdge {
node: Evidence!
}
type SignableDocumentConnection
@goModel(
model: "go.probo.inc/probo/pkg/server/api/console/v1/types.SignableDocumentConnection"
) {
edges: [SignableDocumentEdge!]!
pageInfo: PageInfo!
}
type SignableDocumentEdge
@goModel(
model: "go.probo.inc/probo/pkg/server/api/console/v1/types.SignableDocumentEdge"
) {
cursor: CursorKey!
node: SignableDocument!
}
type DocumentConnection
@goModel(
model: "go.probo.inc/probo/pkg/server/api/console/v1/types.DocumentConnection"
@@ -2914,9 +2961,16 @@ type Mutation {
input: SendSigningNotificationsInput!
): SendSigningNotificationsPayload! cancelSignatureRequest(
input: CancelSignatureRequestInput!
): CancelSignatureRequestPayload! exportDocumentVersionPDF(
): CancelSignatureRequestPayload!
signDocument(
input: SignDocumentInput!
): SignDocumentPayload!
exportDocumentVersionPDF(
input: ExportDocumentVersionPDFInput!
): ExportDocumentVersionPDFPayload!
exportSignableVersionDocumentPDF(
input: ExportSignableDocumentVersionPDFInput!
): ExportSignableDocumentVersionPDFPayload!
createVendorRiskAssessment(
input: CreateVendorRiskAssessmentInput!
): CreateVendorRiskAssessmentPayload!
@@ -3488,6 +3542,10 @@ input ExportDocumentVersionPDFInput {
withSignatures: Boolean!
}
input ExportSignableDocumentVersionPDFInput {
documentVersionId: ID!
}
input DeleteDocumentInput {
documentId: ID!
}
@@ -4072,6 +4130,10 @@ type ExportDocumentVersionPDFPayload {
data: String!
}
type ExportSignableDocumentVersionPDFPayload {
data: String!
}
type UpdateDocumentPayload {
document: Document!
}
@@ -4186,6 +4248,8 @@ type DocumentVersion implements Node @goModel(model: "go.probo.inc/probo/pkg/ser
filter: DocumentVersionSignatureFilter
): DocumentVersionSignatureConnection! @goField(forceResolver: true)
signed: Boolean! @goField(forceResolver: true)
publishedAt: Datetime
createdAt: Datetime!
updatedAt: Datetime!
@@ -4347,6 +4411,14 @@ type CancelSignatureRequestPayload {
deletedDocumentVersionSignatureId: ID!
}
input SignDocumentInput {
documentVersionId: ID!
}
type SignDocumentPayload {
documentVersionSignature: DocumentVersionSignature!
}
type UploadMeasureEvidencePayload {
evidenceEdge: EvidenceEdge!
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,83 @@
// 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 (
"time"
"go.probo.inc/probo/pkg/coredata"
"go.probo.inc/probo/pkg/gid"
"go.probo.inc/probo/pkg/page"
)
type (
SignableDocumentConnection struct {
Edges []*SignableDocumentEdge
PageInfo *PageInfo
}
SignableDocumentEdge struct {
Cursor page.CursorKey
Node *SignableDocument
}
SignableDocument struct {
ID gid.GID
Title string
Description *string
DocumentType coredata.DocumentType
Classification coredata.DocumentClassification
CreatedAt time.Time
UpdatedAt time.Time
}
)
func (SignableDocument) IsNode() {}
func (this SignableDocument) GetID() gid.GID { return this.ID }
func NewSignableDocumentConnection(
p *page.Page[*SignableDocument, coredata.DocumentOrderField],
) *SignableDocumentConnection {
var edges = make([]*SignableDocumentEdge, len(p.Data))
for i := range edges {
edges[i] = NewSignableDocumentEdge(p.Data[i], p.Cursor.OrderBy.Field)
}
return &SignableDocumentConnection{
Edges: edges,
PageInfo: NewPageInfo(p),
}
}
func NewSignableDocumentEdge(document *SignableDocument, orderBy coredata.DocumentOrderField) *SignableDocumentEdge {
return &SignableDocumentEdge{
Cursor: document.CursorKey(orderBy),
Node: document,
}
}
func (d SignableDocument) CursorKey(orderBy coredata.DocumentOrderField) page.CursorKey {
switch orderBy {
case coredata.DocumentOrderFieldCreatedAt:
return page.NewCursorKey(d.ID, d.CreatedAt)
case coredata.DocumentOrderFieldTitle:
return page.NewCursorKey(d.ID, d.Title)
case coredata.DocumentOrderFieldDocumentType:
return page.NewCursorKey(d.ID, d.DocumentType)
}
panic("unsupported order by")
}

View File

@@ -1153,6 +1153,14 @@ type ExportFrameworkPayload struct {
ExportJobID gid.GID `json:"exportJobId"`
}
type ExportSignableDocumentVersionPDFInput struct {
DocumentVersionID gid.GID `json:"documentVersionId"`
}
type ExportSignableDocumentVersionPDFPayload struct {
Data string `json:"data"`
}
type File struct {
ID gid.GID `json:"id"`
MimeType string `json:"mimeType"`
@@ -1680,6 +1688,14 @@ type Session struct {
ExpiresAt time.Time `json:"expiresAt"`
}
type SignDocumentInput struct {
DocumentVersionID gid.GID `json:"documentVersionId"`
}
type SignDocumentPayload struct {
DocumentVersionSignature *DocumentVersionSignature `json:"documentVersionSignature"`
}
type SlackConnection struct {
ID gid.GID `json:"id"`
Channel *string `json:"channel,omitempty"`
@@ -2506,9 +2522,11 @@ type VerifyDomainPayload struct {
}
type Viewer struct {
ID gid.GID `json:"id"`
User *User `json:"user"`
Organizations *OrganizationConnection `json:"organizations"`
ID gid.GID `json:"id"`
User *User `json:"user"`
Organizations *OrganizationConnection `json:"organizations"`
SignableDocuments *SignableDocumentConnection `json:"signableDocuments"`
SignableDocument *SignableDocument `json:"signableDocument,omitempty"`
}
type Role string

View File

@@ -714,7 +714,9 @@ func (r *documentResolver) Versions(ctx context.Context, obj *types.Document, fi
cursor := types.NewCursor(first, after, last, before, pageOrderBy)
page, err := prb.Documents.ListVersions(ctx, obj.ID, cursor)
versionFilter := coredata.NewDocumentVersionFilter()
page, err := prb.Documents.ListVersions(ctx, obj.ID, cursor, versionFilter)
if err != nil {
panic(fmt.Errorf("cannot list document versions: %w", err))
}
@@ -785,7 +787,7 @@ func (r *documentConnectionResolver) TotalCount(ctx context.Context, obj *types.
// Document is the resolver for the document field.
func (r *documentVersionResolver) Document(ctx context.Context, obj *types.DocumentVersion) (*types.Document, error) {
r.MustBeAuthorized(ctx, obj.ID, authz.ActionDocument)
r.MustBeAuthorized(ctx, obj.ID, authz.ActionGetDocument)
prb := r.ProboService(ctx, obj.ID.TenantID())
@@ -862,6 +864,24 @@ func (r *documentVersionResolver) Signatures(ctx context.Context, obj *types.Doc
return types.NewDocumentVersionSignatureConnection(page), nil
}
// Signed is the resolver for the signed field.
func (r *documentVersionResolver) Signed(ctx context.Context, obj *types.DocumentVersion) (bool, error) {
r.MustBeAuthorized(ctx, obj.ID, authz.ActionGetSigned)
user := UserFromContext(ctx)
if user == nil {
panic(fmt.Errorf("user not found in context"))
}
prb := r.ProboService(ctx, obj.ID.TenantID())
signed, err := prb.Documents.IsVersionSignedByUserEmail(ctx, obj.ID, user.EmailAddress)
if err != nil {
panic(fmt.Errorf("cannot check if document version is signed: %w", err))
}
return signed, nil
}
// DocumentVersion is the resolver for the documentVersion field.
func (r *documentVersionSignatureResolver) DocumentVersion(ctx context.Context, obj *types.DocumentVersionSignature) (*types.DocumentVersion, error) {
r.MustBeAuthorized(ctx, obj.ID, authz.ActionDocumentVersion)
@@ -3551,6 +3571,31 @@ func (r *mutationResolver) CancelSignatureRequest(ctx context.Context, input typ
}, nil
}
// SignDocument is the resolver for the signDocument field.
func (r *mutationResolver) SignDocument(ctx context.Context, input types.SignDocumentInput) (*types.SignDocumentPayload, error) {
r.MustBeAuthorized(ctx, input.DocumentVersionID, authz.ActionSignDocument)
user := UserFromContext(ctx)
if user == nil {
panic(fmt.Errorf("user not found in context"))
}
prb := r.ProboService(ctx, input.DocumentVersionID.TenantID())
documentVersionSignature, err := prb.Documents.SignDocumentVersionByEmail(ctx, input.DocumentVersionID, user.EmailAddress)
if err != nil {
var errAlreadySigned *coredata.ErrDocumentVersionSignatureAlreadySigned
if errors.As(err, &errAlreadySigned) {
return nil, gqlutils.Conflict(errAlreadySigned)
}
panic(fmt.Errorf("cannot sign document: %w", err))
}
return &types.SignDocumentPayload{
DocumentVersionSignature: types.NewDocumentVersionSignature(documentVersionSignature),
}, nil
}
// ExportDocumentVersionPDF is the resolver for the exportDocumentVersionPDF field.
func (r *mutationResolver) ExportDocumentVersionPDF(ctx context.Context, input types.ExportDocumentVersionPDFInput) (*types.ExportDocumentVersionPDFPayload, error) {
r.MustBeAuthorized(ctx, input.DocumentVersionID, authz.ActionExportDocumentVersionPDF)
@@ -3573,6 +3618,49 @@ func (r *mutationResolver) ExportDocumentVersionPDF(ctx context.Context, input t
}, nil
}
// ExportSignableVersionDocumentPDF is the resolver for the exportSignableVersionDocumentPDF field.
func (r *mutationResolver) ExportSignableVersionDocumentPDF(ctx context.Context, input types.ExportSignableDocumentVersionPDFInput) (*types.ExportSignableDocumentVersionPDFPayload, error) {
r.MustBeAuthorized(ctx, input.DocumentVersionID, authz.ActionExportSignableVersionDocumentPDF)
prb := r.ProboService(ctx, input.DocumentVersionID.TenantID())
documentVersion, err := prb.Documents.GetVersion(ctx, input.DocumentVersionID)
if err != nil {
panic(fmt.Errorf("cannot get document version: %w", err))
}
user := UserFromContext(ctx)
if user == nil {
panic(fmt.Errorf("user not found in context"))
}
documentFilter := coredata.NewDocumentFilter(nil).WithUserEmail(&user.EmailAddress)
_, err = prb.Documents.GetWithFilter(ctx, documentVersion.DocumentID, documentFilter)
if err != nil {
var errNotFound *coredata.ErrDocumentNotFound
if errors.As(err, &errNotFound) {
return nil, gqlutils.NotFound(errNotFound)
}
panic(fmt.Errorf("cannot get signable document: %w", err))
}
options := probo.ExportPDFOptions{
WithSignatures: false,
WithWatermark: true,
WatermarkEmail: &user.EmailAddress,
}
pdf, err := prb.Documents.ExportPDF(ctx, input.DocumentVersionID, options)
if err != nil {
panic(fmt.Errorf("cannot export signable document PDF: %w", err))
}
return &types.ExportSignableDocumentVersionPDFPayload{
Data: fmt.Sprintf("data:application/pdf;base64,%s", base64.StdEncoding.EncodeToString(pdf)),
}, nil
}
// CreateVendorRiskAssessment is the resolver for the createVendorRiskAssessment field.
func (r *mutationResolver) CreateVendorRiskAssessment(ctx context.Context, input types.CreateVendorRiskAssessmentInput) (*types.CreateVendorRiskAssessmentPayload, error) {
r.MustBeAuthorized(ctx, input.VendorID, authz.ActionCreateVendorRiskAssessment)
@@ -5994,6 +6082,59 @@ func (r *sAMLConfigurationResolver) TestLoginURL(ctx context.Context, obj *types
return fmt.Sprintf("%s/connect/saml/login/%s", parts[0], obj.ID), nil
}
// Signed is the resolver for the signed field.
func (r *signableDocumentResolver) Signed(ctx context.Context, obj *types.SignableDocument) (bool, error) {
r.MustBeAuthorized(ctx, obj.ID, authz.ActionGetSigned)
user := UserFromContext(ctx)
if user == nil {
panic(fmt.Errorf("user not found in context"))
}
prb := r.ProboService(ctx, obj.ID.TenantID())
signed, err := prb.Documents.IsSigned(ctx, obj.ID, user.EmailAddress)
if err != nil {
panic(fmt.Errorf("cannot check if document is signed: %w", err))
}
return signed, nil
}
// Versions is the resolver for the versions field.
func (r *signableDocumentResolver) Versions(ctx context.Context, obj *types.SignableDocument, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.DocumentVersionOrderBy, filter *types.DocumentVersionFilter) (*types.DocumentVersionConnection, error) {
r.MustBeAuthorized(ctx, obj.ID, authz.ActionListSignableDocumentVersion)
prb := r.ProboService(ctx, obj.ID.TenantID())
pageOrderBy := page.OrderBy[coredata.DocumentVersionOrderField]{
Field: coredata.DocumentVersionOrderFieldCreatedAt,
Direction: page.OrderDirectionDesc,
}
if orderBy != nil {
pageOrderBy = page.OrderBy[coredata.DocumentVersionOrderField]{
Field: orderBy.Field,
Direction: orderBy.Direction,
}
}
cursor := types.NewCursor(first, after, last, before, pageOrderBy)
user := UserFromContext(ctx)
if user == nil {
panic(fmt.Errorf("user not found in context"))
}
versionFilter := coredata.NewDocumentVersionFilter().WithUserEmail(&user.EmailAddress)
page, err := prb.Documents.ListVersions(ctx, obj.ID, cursor, versionFilter)
if err != nil {
panic(fmt.Errorf("cannot list signable document versions: %w", err))
}
return types.NewDocumentVersionConnection(page), nil
}
// Organization is the resolver for the organization field.
func (r *snapshotResolver) Organization(ctx context.Context, obj *types.Snapshot) (*types.Organization, error) {
r.MustBeAuthorized(ctx, obj.ID, authz.ActionGetOrganization)
@@ -6349,7 +6490,7 @@ func (r *trustCenterAccessResolver) AvailableDocumentAccesses(ctx context.Contex
// Document is the resolver for the document field.
func (r *trustCenterDocumentAccessResolver) Document(ctx context.Context, obj *types.TrustCenterDocumentAccess) (*types.Document, error) {
r.MustBeAuthorized(ctx, obj.TrustCenterAccessID, authz.ActionDocument)
r.MustBeAuthorized(ctx, obj.ID, authz.ActionGet)
if obj.DocumentID == nil {
return nil, nil
@@ -6979,6 +7120,85 @@ func (r *viewerResolver) Organizations(ctx context.Context, obj *types.Viewer, f
return types.NewOrganizationConnection(page), nil
}
// SignableDocuments is the resolver for the signableDocuments field.
func (r *viewerResolver) SignableDocuments(ctx context.Context, obj *types.Viewer, organizationID gid.GID, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.DocumentOrderBy) (*types.SignableDocumentConnection, error) {
r.MustBeAuthorized(ctx, organizationID, authz.ActionListSignableDocuments)
prb := r.ProboService(ctx, organizationID.TenantID())
pageOrderBy := page.OrderBy[coredata.DocumentOrderField]{
Field: coredata.DocumentOrderFieldCreatedAt,
Direction: page.OrderDirectionDesc,
}
if orderBy != nil {
pageOrderBy = page.OrderBy[coredata.DocumentOrderField]{
Field: orderBy.Field,
Direction: orderBy.Direction,
}
}
cursor := types.NewCursor(first, after, last, before, pageOrderBy)
user := UserFromContext(ctx)
if user == nil {
panic(fmt.Errorf("user not found in context"))
}
documentFilter := coredata.NewDocumentFilter(nil).WithUserEmail(&user.EmailAddress)
documentsPage, err := prb.Documents.ListByOrganizationID(ctx, organizationID, cursor, documentFilter)
if err != nil {
panic(fmt.Errorf("cannot list organization signable documents: %w", err))
}
signableDocuments := make([]*types.SignableDocument, len(documentsPage.Data))
for i, doc := range documentsPage.Data {
signableDocuments[i] = &types.SignableDocument{
ID: doc.ID,
Title: doc.Title,
DocumentType: doc.DocumentType,
Classification: doc.Classification,
CreatedAt: doc.CreatedAt,
UpdatedAt: doc.UpdatedAt,
}
}
page := page.NewPage(signableDocuments, documentsPage.Cursor)
return types.NewSignableDocumentConnection(page), nil
}
// SignableDocument is the resolver for the signableDocument field.
func (r *viewerResolver) SignableDocument(ctx context.Context, obj *types.Viewer, id gid.GID) (*types.SignableDocument, error) {
r.MustBeAuthorized(ctx, id, authz.ActionGetSignableDocument)
prb := r.ProboService(ctx, id.TenantID())
user := UserFromContext(ctx)
if user == nil {
panic(fmt.Errorf("user not found in context"))
}
documentFilter := coredata.NewDocumentFilter(nil).WithUserEmail(&user.EmailAddress)
document, err := prb.Documents.GetWithFilter(ctx, id, documentFilter)
if err != nil {
var errNotFound *coredata.ErrDocumentNotFound
if errors.As(err, &errNotFound) {
return nil, gqlutils.NotFound(errNotFound)
}
panic(fmt.Errorf("cannot get signable document: %w", err))
}
return &types.SignableDocument{
ID: document.ID,
Title: document.Title,
DocumentType: document.DocumentType,
Classification: document.Classification,
CreatedAt: document.CreatedAt,
UpdatedAt: document.UpdatedAt,
}, nil
}
// Asset returns schema.AssetResolver implementation.
func (r *Resolver) Asset() schema.AssetResolver { return &assetResolver{r} }
@@ -7144,6 +7364,11 @@ func (r *Resolver) SAMLConfiguration() schema.SAMLConfigurationResolver {
return &sAMLConfigurationResolver{r}
}
// SignableDocument returns schema.SignableDocumentResolver implementation.
func (r *Resolver) SignableDocument() schema.SignableDocumentResolver {
return &signableDocumentResolver{r}
}
// Snapshot returns schema.SnapshotResolver implementation.
func (r *Resolver) Snapshot() schema.SnapshotResolver { return &snapshotResolver{r} }
@@ -7277,6 +7502,7 @@ type reportResolver struct{ *Resolver }
type riskResolver struct{ *Resolver }
type riskConnectionResolver struct{ *Resolver }
type sAMLConfigurationResolver struct{ *Resolver }
type signableDocumentResolver struct{ *Resolver }
type snapshotResolver struct{ *Resolver }
type snapshotConnectionResolver struct{ *Resolver }
type taskResolver struct{ *Resolver }

View File

@@ -1595,7 +1595,7 @@ func (r *Resolver) ListDocumentVersionsTool(ctx context.Context, req *mcp.CallTo
cursor := types.NewCursor(input.Size, input.Cursor, pageOrderBy)
svc := r.ProboService(ctx, input.DocumentID)
page, err := svc.Documents.ListVersions(ctx, input.DocumentID, cursor)
page, err := svc.Documents.ListVersions(ctx, input.DocumentID, cursor, coredata.NewDocumentVersionFilter())
if err != nil {
panic(fmt.Errorf("cannot list document versions: %w", err))
}

View File

@@ -46,7 +46,8 @@ func (s *DocumentService) ListVersions(
err := s.svc.pg.WithConn(
ctx,
func(conn pg.Conn) error {
return documentVersions.LoadByDocumentID(ctx, conn, s.svc.scope, documentID, cursor)
filter := coredata.NewDocumentVersionFilter()
return documentVersions.LoadByDocumentID(ctx, conn, s.svc.scope, documentID, cursor, filter)
},
)