committed by
Sacha Al Himdani
parent
a11dfdb244
commit
d47cac911d
@@ -49,4 +49,5 @@ const (
|
||||
SnapshotEntityType
|
||||
ContinualImprovementRegistryEntityType
|
||||
ProcessingActivityRegistryEntityType
|
||||
FrameworkExportEntityType
|
||||
)
|
||||
|
||||
163
pkg/coredata/framework_export.go
Normal file
163
pkg/coredata/framework_export.go
Normal file
@@ -0,0 +1,163 @@
|
||||
// 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/getprobo/probo/pkg/gid"
|
||||
"github.com/getprobo/probo/pkg/page"
|
||||
"github.com/jackc/pgx/v5"
|
||||
"go.gearno.de/kit/pg"
|
||||
)
|
||||
|
||||
type (
|
||||
FrameworkExport struct {
|
||||
ID gid.GID `db:"id"`
|
||||
FrameworkID gid.GID `db:"framework_id"`
|
||||
Status FrameworkExportStatus `db:"status"`
|
||||
FileID *gid.GID `db:"file_id"`
|
||||
CreatedAt time.Time `db:"created_at"`
|
||||
StartedAt *time.Time `db:"started_at"`
|
||||
CompletedAt *time.Time `db:"completed_at"`
|
||||
}
|
||||
|
||||
FrameworkExports []*FrameworkExport
|
||||
)
|
||||
|
||||
var (
|
||||
ErrNoFrameworkExportAvailable = errors.New("no framework export available")
|
||||
)
|
||||
|
||||
func (fe FrameworkExport) CursorKey(orderBy FrameworkExportOrderField) page.CursorKey {
|
||||
switch orderBy {
|
||||
case FrameworkExportOrderFieldCreatedAt:
|
||||
return page.NewCursorKey(fe.ID, fe.CreatedAt)
|
||||
}
|
||||
|
||||
panic(fmt.Sprintf("unsupported order by: %s", orderBy))
|
||||
}
|
||||
|
||||
func (fe *FrameworkExport) Insert(
|
||||
ctx context.Context,
|
||||
conn pg.Conn,
|
||||
scope Scoper,
|
||||
) error {
|
||||
q := `
|
||||
INSERT INTO framework_exports (
|
||||
id,
|
||||
tenant_id,
|
||||
framework_id,
|
||||
status,
|
||||
created_at,
|
||||
expires_at
|
||||
) VALUES (
|
||||
@id,
|
||||
@tenant_id,
|
||||
@framework_id,
|
||||
@status,
|
||||
@created_at,
|
||||
@expires_at
|
||||
)`
|
||||
|
||||
args := pgx.StrictNamedArgs{
|
||||
"id": fe.ID,
|
||||
"tenant_id": scope.GetTenantID(),
|
||||
"framework_id": fe.FrameworkID,
|
||||
"status": fe.Status,
|
||||
"created_at": fe.CreatedAt,
|
||||
}
|
||||
|
||||
_, err := conn.Exec(ctx, q, args)
|
||||
return err
|
||||
}
|
||||
|
||||
func (fe *FrameworkExport) Update(
|
||||
ctx context.Context,
|
||||
conn pg.Conn,
|
||||
scope Scoper,
|
||||
) error {
|
||||
q := `
|
||||
UPDATE
|
||||
framework_exports
|
||||
SET
|
||||
status = @status,
|
||||
file_id = @file_id,
|
||||
started_at = @started_at,
|
||||
completed_at = @completed_at
|
||||
WHERE
|
||||
%s
|
||||
AND id = @id
|
||||
`
|
||||
|
||||
q = fmt.Sprintf(q, scope.SQLFragment())
|
||||
|
||||
args := pgx.StrictNamedArgs{
|
||||
"status": fe.Status,
|
||||
"file_id": fe.FileID,
|
||||
"started_at": fe.StartedAt,
|
||||
"completed_at": fe.CompletedAt,
|
||||
"id": fe.ID,
|
||||
}
|
||||
maps.Copy(args, scope.SQLArguments())
|
||||
|
||||
_, err := conn.Exec(ctx, q, args)
|
||||
return err
|
||||
}
|
||||
|
||||
func (fe *FrameworkExport) LoadNextPendingForUpdateSkipLocked(
|
||||
ctx context.Context,
|
||||
conn pg.Conn,
|
||||
) error {
|
||||
q := `
|
||||
SELECT
|
||||
id,
|
||||
framework_id,
|
||||
status,
|
||||
created_at,
|
||||
started_at,
|
||||
completed_at
|
||||
FROM
|
||||
framework_exports
|
||||
WHERE
|
||||
status = @status
|
||||
ORDER BY
|
||||
created_at ASC
|
||||
LIMIT 1
|
||||
FOR UPDATE SKIP LOCKED
|
||||
`
|
||||
|
||||
args := pgx.StrictNamedArgs{"status": FrameworkExportStatusPending}
|
||||
|
||||
rows, err := conn.Query(ctx, q, args)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fe2, err := pgx.CollectExactlyOneRow(rows, pgx.RowToStructByName[FrameworkExport])
|
||||
if err != nil {
|
||||
if errors.Is(err, pgx.ErrNoRows) {
|
||||
return ErrNoFrameworkExportAvailable
|
||||
}
|
||||
return fmt.Errorf("cannot collect framework export: %w", err)
|
||||
}
|
||||
|
||||
*fe = fe2
|
||||
return nil
|
||||
}
|
||||
40
pkg/coredata/framework_export_order_field.go
Normal file
40
pkg/coredata/framework_export_order_field.go
Normal file
@@ -0,0 +1,40 @@
|
||||
// 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
|
||||
|
||||
type (
|
||||
FrameworkExportOrderField string
|
||||
)
|
||||
|
||||
const (
|
||||
FrameworkExportOrderFieldCreatedAt FrameworkExportOrderField = "CREATED_AT"
|
||||
)
|
||||
|
||||
func (p FrameworkExportOrderField) Column() string {
|
||||
return string(p)
|
||||
}
|
||||
|
||||
func (p FrameworkExportOrderField) String() string {
|
||||
return string(p)
|
||||
}
|
||||
|
||||
func (p FrameworkExportOrderField) MarshalText() ([]byte, error) {
|
||||
return []byte(p.String()), nil
|
||||
}
|
||||
|
||||
func (p *FrameworkExportOrderField) UnmarshalText(text []byte) error {
|
||||
*p = FrameworkExportOrderField(text)
|
||||
return nil
|
||||
}
|
||||
86
pkg/coredata/framework_export_status.go
Normal file
86
pkg/coredata/framework_export_status.go
Normal file
@@ -0,0 +1,86 @@
|
||||
// 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 (
|
||||
FrameworkExportStatus string
|
||||
)
|
||||
|
||||
const (
|
||||
FrameworkExportStatusPending FrameworkExportStatus = "pending"
|
||||
FrameworkExportStatusProcessing FrameworkExportStatus = "processing"
|
||||
FrameworkExportStatusCompleted FrameworkExportStatus = "completed"
|
||||
FrameworkExportStatusFailed FrameworkExportStatus = "failed"
|
||||
)
|
||||
|
||||
func (pvs FrameworkExportStatus) MarshalText() ([]byte, error) {
|
||||
return []byte(pvs.String()), nil
|
||||
}
|
||||
|
||||
func (pvs *FrameworkExportStatus) UnmarshalText(data []byte) error {
|
||||
val := string(data)
|
||||
|
||||
switch val {
|
||||
case FrameworkExportStatusPending.String():
|
||||
*pvs = FrameworkExportStatusPending
|
||||
case FrameworkExportStatusProcessing.String():
|
||||
*pvs = FrameworkExportStatusProcessing
|
||||
case FrameworkExportStatusCompleted.String():
|
||||
*pvs = FrameworkExportStatusCompleted
|
||||
case FrameworkExportStatusFailed.String():
|
||||
*pvs = FrameworkExportStatusFailed
|
||||
default:
|
||||
return fmt.Errorf("invalid FrameworkExportStatus value: %q", val)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (pvs FrameworkExportStatus) String() string {
|
||||
var val string
|
||||
|
||||
switch pvs {
|
||||
case FrameworkExportStatusPending:
|
||||
val = "pending"
|
||||
case FrameworkExportStatusProcessing:
|
||||
val = "processing"
|
||||
case FrameworkExportStatusCompleted:
|
||||
val = "completed"
|
||||
case FrameworkExportStatusFailed:
|
||||
val = "failed"
|
||||
default:
|
||||
panic(fmt.Errorf("invalid FrameworkExportStatus value: %q", string(pvs)))
|
||||
}
|
||||
|
||||
return val
|
||||
}
|
||||
|
||||
func (pvs *FrameworkExportStatus) Scan(value any) error {
|
||||
val, ok := value.(string)
|
||||
if !ok {
|
||||
return fmt.Errorf("invalid scan source for FrameworkExportStatus, expected string got %T", value)
|
||||
}
|
||||
|
||||
return pvs.UnmarshalText([]byte(val))
|
||||
}
|
||||
|
||||
func (pvs FrameworkExportStatus) Value() (driver.Value, error) {
|
||||
return pvs.String(), nil
|
||||
}
|
||||
@@ -249,6 +249,8 @@ WHERE %s
|
||||
`
|
||||
q = fmt.Sprintf(q, scope.SQLFragment(), filter.SQLFragment(), cursor.SQLFragment())
|
||||
|
||||
fmt.Printf("\n%s\n", q)
|
||||
|
||||
args := pgx.NamedArgs{"control_id": controlID}
|
||||
maps.Copy(args, scope.SQLArguments())
|
||||
maps.Copy(args, filter.SQLArguments())
|
||||
|
||||
20
pkg/coredata/migrations/20250828T091522Z.sql
Normal file
20
pkg/coredata/migrations/20250828T091522Z.sql
Normal file
@@ -0,0 +1,20 @@
|
||||
CREATE TYPE framework_export_status AS ENUM (
|
||||
'pending',
|
||||
'processing',
|
||||
'completed',
|
||||
'failed'
|
||||
);
|
||||
|
||||
CREATE TABLE framework_exports (
|
||||
id TEXT PRIMARY KEY,
|
||||
tenant_id TEXT NOT NULL,
|
||||
framework_id TEXT NOT NULL,
|
||||
status framework_export_status NOT NULL,
|
||||
file_id TEXT,
|
||||
created_at TIMESTAMP WITH TIME ZONE NOT NULL,
|
||||
started_at TIMESTAMP WITH TIME ZONE,
|
||||
completed_at TIMESTAMP WITH TIME ZONE
|
||||
);
|
||||
|
||||
|
||||
|
||||
@@ -1097,65 +1097,14 @@ func (s *DocumentService) ExportPDF(
|
||||
ctx context.Context,
|
||||
documentVersionID gid.GID,
|
||||
) ([]byte, error) {
|
||||
document := &coredata.Document{}
|
||||
version := &coredata.DocumentVersion{}
|
||||
owner := &coredata.People{}
|
||||
publishedBy := &coredata.People{}
|
||||
signatures := coredata.DocumentVersionSignatures{}
|
||||
peopleMap := make(map[gid.GID]*coredata.People)
|
||||
var data []byte
|
||||
|
||||
err := s.svc.pg.WithConn(
|
||||
err := s.svc.pg.WithTx(
|
||||
ctx,
|
||||
func(conn pg.Conn) error {
|
||||
if err := version.LoadByID(ctx, conn, s.svc.scope, documentVersionID); err != nil {
|
||||
return fmt.Errorf("cannot load document version: %w", err)
|
||||
}
|
||||
|
||||
if err := document.LoadByID(ctx, conn, s.svc.scope, version.DocumentID); err != nil {
|
||||
return fmt.Errorf("cannot load document: %w", err)
|
||||
}
|
||||
|
||||
if version.PublishedBy != nil {
|
||||
if err := publishedBy.LoadByID(ctx, conn, s.svc.scope, *version.PublishedBy); err != nil {
|
||||
return fmt.Errorf("cannot load published by person: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
cursor := page.NewCursor(
|
||||
100,
|
||||
nil,
|
||||
page.Head,
|
||||
page.OrderBy[coredata.DocumentVersionSignatureOrderField]{
|
||||
Field: coredata.DocumentVersionSignatureOrderFieldCreatedAt,
|
||||
Direction: page.OrderDirectionAsc,
|
||||
},
|
||||
)
|
||||
|
||||
if err := signatures.LoadByDocumentVersionID(ctx, conn, s.svc.scope, documentVersionID, cursor); err != nil {
|
||||
return fmt.Errorf("cannot load document version signatures: %w", err)
|
||||
}
|
||||
|
||||
if err := owner.LoadByID(ctx, conn, s.svc.scope, document.OwnerID); err != nil {
|
||||
return fmt.Errorf("cannot load document owner: %w", err)
|
||||
}
|
||||
|
||||
// TODO: refactor this to use a single query
|
||||
for _, sig := range signatures {
|
||||
if _, ok := peopleMap[sig.SignedBy]; !ok {
|
||||
people := &coredata.People{}
|
||||
if err := people.LoadByID(ctx, conn, s.svc.scope, sig.SignedBy); err != nil {
|
||||
return fmt.Errorf("cannot load people %q: %w", sig.SignedBy, err)
|
||||
}
|
||||
peopleMap[sig.SignedBy] = people
|
||||
}
|
||||
|
||||
if _, ok := peopleMap[sig.RequestedBy]; !ok {
|
||||
people := &coredata.People{}
|
||||
if err := people.LoadByID(ctx, conn, s.svc.scope, sig.RequestedBy); err != nil {
|
||||
return fmt.Errorf("cannot load people %q: %w", sig.RequestedBy, err)
|
||||
}
|
||||
peopleMap[sig.RequestedBy] = people
|
||||
}
|
||||
func(conn pg.Conn) (err error) {
|
||||
data, err = exportDocumentPDF(ctx, s.html2pdfConverter, conn, s.svc.scope, documentVersionID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot export document PDF: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
@@ -1166,6 +1115,74 @@ func (s *DocumentService) ExportPDF(
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return data, nil
|
||||
}
|
||||
|
||||
func exportDocumentPDF(
|
||||
ctx context.Context,
|
||||
html2pdfConverter *html2pdf.Converter,
|
||||
conn pg.Conn,
|
||||
scope coredata.Scoper,
|
||||
documentVersionID gid.GID,
|
||||
) ([]byte, error) {
|
||||
document := &coredata.Document{}
|
||||
version := &coredata.DocumentVersion{}
|
||||
owner := &coredata.People{}
|
||||
publishedBy := &coredata.People{}
|
||||
signatures := coredata.DocumentVersionSignatures{}
|
||||
peopleMap := make(map[gid.GID]*coredata.People)
|
||||
|
||||
if err := version.LoadByID(ctx, conn, scope, documentVersionID); err != nil {
|
||||
return nil, fmt.Errorf("cannot load document version: %w", err)
|
||||
}
|
||||
|
||||
if err := document.LoadByID(ctx, conn, scope, version.DocumentID); err != nil {
|
||||
return nil, fmt.Errorf("cannot load document: %w", err)
|
||||
}
|
||||
|
||||
if version.PublishedBy != nil {
|
||||
if err := publishedBy.LoadByID(ctx, conn, scope, *version.PublishedBy); err != nil {
|
||||
return nil, fmt.Errorf("cannot load published by person: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
cursor := page.NewCursor(
|
||||
100,
|
||||
nil,
|
||||
page.Head,
|
||||
page.OrderBy[coredata.DocumentVersionSignatureOrderField]{
|
||||
Field: coredata.DocumentVersionSignatureOrderFieldCreatedAt,
|
||||
Direction: page.OrderDirectionAsc,
|
||||
},
|
||||
)
|
||||
|
||||
if err := signatures.LoadByDocumentVersionID(ctx, conn, scope, documentVersionID, cursor); err != nil {
|
||||
return nil, fmt.Errorf("cannot load document version signatures: %w", err)
|
||||
}
|
||||
|
||||
if err := owner.LoadByID(ctx, conn, scope, document.OwnerID); err != nil {
|
||||
return nil, fmt.Errorf("cannot load document owner: %w", err)
|
||||
}
|
||||
|
||||
// TODO: refactor this to use a single query
|
||||
for _, sig := range signatures {
|
||||
if _, ok := peopleMap[sig.SignedBy]; !ok {
|
||||
people := &coredata.People{}
|
||||
if err := people.LoadByID(ctx, conn, scope, sig.SignedBy); err != nil {
|
||||
return nil, fmt.Errorf("cannot load people %q: %w", sig.SignedBy, err)
|
||||
}
|
||||
peopleMap[sig.SignedBy] = people
|
||||
}
|
||||
|
||||
if _, ok := peopleMap[sig.RequestedBy]; !ok {
|
||||
people := &coredata.People{}
|
||||
if err := people.LoadByID(ctx, conn, scope, sig.RequestedBy); err != nil {
|
||||
return nil, fmt.Errorf("cannot load people %q: %w", sig.RequestedBy, err)
|
||||
}
|
||||
peopleMap[sig.RequestedBy] = people
|
||||
}
|
||||
}
|
||||
|
||||
classification := docgen.ClassificationInternal
|
||||
switch document.DocumentType {
|
||||
case coredata.DocumentTypePolicy:
|
||||
@@ -1212,7 +1229,7 @@ func (s *DocumentService) ExportPDF(
|
||||
Scale: 1.0,
|
||||
}
|
||||
|
||||
pdfReader, err := s.html2pdfConverter.GeneratePDF(ctx, htmlContent, cfg)
|
||||
pdfReader, err := html2pdfConverter.GeneratePDF(ctx, htmlContent, cfg)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot generate PDF: %w", err)
|
||||
}
|
||||
|
||||
@@ -15,12 +15,17 @@
|
||||
package probo
|
||||
|
||||
import (
|
||||
"archive/zip"
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"time"
|
||||
|
||||
"github.com/aws/aws-sdk-go-v2/aws"
|
||||
"github.com/aws/aws-sdk-go-v2/service/s3"
|
||||
"github.com/getprobo/probo/pkg/coredata"
|
||||
"github.com/getprobo/probo/pkg/gid"
|
||||
"github.com/getprobo/probo/pkg/html2pdf"
|
||||
"github.com/getprobo/probo/pkg/page"
|
||||
"github.com/getprobo/probo/pkg/slug"
|
||||
"github.com/getprobo/probo/pkg/soagen"
|
||||
@@ -30,14 +35,12 @@ import (
|
||||
|
||||
const (
|
||||
maxStateOfApplicabilityLimit = 10_000
|
||||
maxControlsLimit = 10000
|
||||
maxItemsLimit = 1000
|
||||
presignExpiry = 15 * time.Minute
|
||||
)
|
||||
|
||||
type (
|
||||
FrameworkService struct {
|
||||
svc *TenantService
|
||||
svc *TenantService
|
||||
html2pdfConverter *html2pdf.Converter
|
||||
}
|
||||
|
||||
CreateFrameworkRequest struct {
|
||||
@@ -65,6 +68,207 @@ type (
|
||||
}
|
||||
)
|
||||
|
||||
func (s FrameworkService) RequestExport(
|
||||
ctx context.Context,
|
||||
frameworkID gid.GID,
|
||||
) error {
|
||||
return s.svc.pg.WithTx(ctx, func(conn pg.Conn) error {
|
||||
framework := &coredata.Framework{}
|
||||
if err := framework.LoadByID(ctx, conn, s.svc.scope, frameworkID); err != nil {
|
||||
return fmt.Errorf("cannot load framework: %w", err)
|
||||
}
|
||||
|
||||
now := time.Now()
|
||||
|
||||
frameworkExport := &coredata.FrameworkExport{
|
||||
ID: gid.New(framework.ID.TenantID(), coredata.FrameworkExportEntityType),
|
||||
FrameworkID: frameworkID,
|
||||
Status: coredata.FrameworkExportStatusPending,
|
||||
CreatedAt: now,
|
||||
}
|
||||
|
||||
if err := frameworkExport.Insert(ctx, conn, s.svc.scope); err != nil {
|
||||
return fmt.Errorf("cannot insert framework export: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func (s FrameworkService) Export(
|
||||
ctx context.Context,
|
||||
frameworkID gid.GID,
|
||||
file io.Writer,
|
||||
) error {
|
||||
archive := zip.NewWriter(file)
|
||||
defer archive.Close()
|
||||
|
||||
return s.svc.pg.WithTx(
|
||||
ctx,
|
||||
func(conn pg.Conn) error {
|
||||
framework := &coredata.Framework{}
|
||||
if err := framework.LoadByID(ctx, conn, s.svc.scope, frameworkID); err != nil {
|
||||
return fmt.Errorf("cannot load framework: %w", err)
|
||||
}
|
||||
|
||||
controls := coredata.Controls{}
|
||||
err := controls.LoadByFrameworkID(
|
||||
ctx,
|
||||
conn,
|
||||
s.svc.scope,
|
||||
frameworkID,
|
||||
page.NewCursor(
|
||||
10_000,
|
||||
nil,
|
||||
page.Head,
|
||||
page.OrderBy[coredata.ControlOrderField]{
|
||||
Field: coredata.ControlOrderFieldSectionTitle,
|
||||
Direction: page.OrderDirectionAsc,
|
||||
},
|
||||
),
|
||||
coredata.NewControlFilter(nil),
|
||||
)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot load controls: %w", err)
|
||||
}
|
||||
|
||||
for _, control := range controls {
|
||||
_, err := archive.Create(fmt.Sprintf("%s/%s/", framework.Name, control.SectionTitle))
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot create control directory in archive: %w", err)
|
||||
}
|
||||
|
||||
measures := coredata.Measures{}
|
||||
err = measures.LoadByControlID(
|
||||
ctx,
|
||||
conn,
|
||||
s.svc.scope,
|
||||
control.ID,
|
||||
page.NewCursor(
|
||||
10_000,
|
||||
nil,
|
||||
page.Head,
|
||||
page.OrderBy[coredata.MeasureOrderField]{
|
||||
Field: coredata.MeasureOrderFieldCreatedAt,
|
||||
Direction: page.OrderDirectionAsc,
|
||||
},
|
||||
),
|
||||
coredata.NewMeasureFilter(nil),
|
||||
)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot load measures: %w", err)
|
||||
}
|
||||
|
||||
for _, measure := range measures {
|
||||
_, err := archive.Create(fmt.Sprintf("%s/%s/%s/", framework.Name, control.SectionTitle, measure.Name))
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot create measure directory in archive: %w", err)
|
||||
}
|
||||
|
||||
evidences := coredata.Evidences{}
|
||||
err = evidences.LoadByMeasureID(
|
||||
ctx,
|
||||
conn,
|
||||
s.svc.scope,
|
||||
measure.ID,
|
||||
page.NewCursor(
|
||||
10_000,
|
||||
nil,
|
||||
page.Head,
|
||||
page.OrderBy[coredata.EvidenceOrderField]{
|
||||
Field: coredata.EvidenceOrderFieldCreatedAt,
|
||||
Direction: page.OrderDirectionAsc,
|
||||
},
|
||||
),
|
||||
)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot load evidences: %w", err)
|
||||
}
|
||||
|
||||
for _, evidence := range evidences {
|
||||
object, err := s.svc.s3.GetObject(
|
||||
ctx,
|
||||
&s3.GetObjectInput{
|
||||
Bucket: aws.String(s.svc.bucket),
|
||||
Key: aws.String(evidence.ObjectKey),
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot download evidence: %w", err)
|
||||
}
|
||||
defer object.Body.Close()
|
||||
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot download evidence: %w", err)
|
||||
}
|
||||
|
||||
w, err := archive.Create(fmt.Sprintf("%s/%s/%s/%s", framework.Name, control.SectionTitle, measure.Name, evidence.Filename))
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot create evidence in archive: %w", err)
|
||||
}
|
||||
|
||||
_, err = io.Copy(w, object.Body)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot write evidence to archive: %w", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
documents := coredata.Documents{}
|
||||
err = documents.LoadByControlID(
|
||||
ctx,
|
||||
conn,
|
||||
s.svc.scope,
|
||||
control.ID,
|
||||
page.NewCursor(
|
||||
10_000,
|
||||
nil,
|
||||
page.Head,
|
||||
page.OrderBy[coredata.DocumentOrderField]{
|
||||
Field: coredata.DocumentOrderFieldCreatedAt,
|
||||
Direction: page.OrderDirectionAsc,
|
||||
},
|
||||
),
|
||||
coredata.NewDocumentFilter(nil),
|
||||
)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot load documents: %w", err)
|
||||
}
|
||||
|
||||
for _, document := range documents {
|
||||
documentVersion := &coredata.DocumentVersion{}
|
||||
if err := documentVersion.LoadLatestPublishedVersion(ctx, conn, s.svc.scope, document.ID); err != nil {
|
||||
return fmt.Errorf("cannot load document version: %w", err)
|
||||
}
|
||||
|
||||
exportedPDF, err := exportDocumentPDF(
|
||||
ctx,
|
||||
s.html2pdfConverter,
|
||||
conn,
|
||||
s.svc.scope,
|
||||
documentVersion.ID,
|
||||
)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot export document PDF: %w", err)
|
||||
}
|
||||
|
||||
w, err := archive.Create(fmt.Sprintf("%s/%s/%s.pdf", framework.Name, control.SectionTitle, document.Title))
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot create document in archive: %w", err)
|
||||
}
|
||||
|
||||
_, err = w.Write(exportedPDF)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot write document to archive: %w", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
func (s FrameworkService) Create(
|
||||
ctx context.Context,
|
||||
req CreateFrameworkRequest,
|
||||
|
||||
@@ -17,6 +17,7 @@ package probo
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/aws/aws-sdk-go-v2/service/s3"
|
||||
@@ -27,7 +28,9 @@ import (
|
||||
"github.com/getprobo/probo/pkg/gid"
|
||||
"github.com/getprobo/probo/pkg/html2pdf"
|
||||
"github.com/getprobo/probo/pkg/usrmgr"
|
||||
"go.gearno.de/crypto/uuid"
|
||||
"go.gearno.de/kit/pg"
|
||||
"go.gearno.de/x/ref"
|
||||
)
|
||||
|
||||
type (
|
||||
@@ -136,7 +139,10 @@ func (s *Service) WithTenant(tenantID gid.TenantID) *TenantService {
|
||||
agent: agents.NewAgent(nil, s.agentConfig),
|
||||
}
|
||||
|
||||
tenantService.Frameworks = &FrameworkService{svc: tenantService}
|
||||
tenantService.Frameworks = &FrameworkService{
|
||||
svc: tenantService,
|
||||
html2pdfConverter: s.html2pdfConverter,
|
||||
}
|
||||
tenantService.Measures = &MeasureService{svc: tenantService}
|
||||
tenantService.Tasks = &TaskService{svc: tenantService}
|
||||
tenantService.Evidences = &EvidenceService{
|
||||
@@ -184,3 +190,144 @@ func (s *Service) WithTenant(tenantID gid.TenantID) *TenantService {
|
||||
tenantService.ProcessingActivityRegistries = &ProcessingActivityRegistryService{svc: tenantService}
|
||||
return tenantService
|
||||
}
|
||||
|
||||
func (s *Service) ExportFrameworkJob(ctx context.Context) error {
|
||||
return s.pg.WithTx(
|
||||
ctx,
|
||||
func(tx pg.Conn) error {
|
||||
fe := &coredata.FrameworkExport{}
|
||||
if err := fe.LoadNextPendingForUpdateSkipLocked(ctx, tx); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
scope := coredata.NewScope(fe.ID.TenantID())
|
||||
framework := &coredata.Framework{}
|
||||
if err := framework.LoadByID(ctx, tx, scope, fe.FrameworkID); err != nil {
|
||||
fe.Status = coredata.FrameworkExportStatusFailed
|
||||
fe.CompletedAt = ref.Ref(time.Now())
|
||||
if err := fe.Update(ctx, tx, scope); err != nil {
|
||||
return fmt.Errorf("cannot update framework export: %w", err)
|
||||
}
|
||||
|
||||
// s.logger.Error(ctx, "cannot load framework", "error", err)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
tenantService := s.WithTenant(fe.ID.TenantID())
|
||||
|
||||
tempDir := os.TempDir()
|
||||
tempFile, err := os.CreateTemp(tempDir, "probo-framework-export-*.zip")
|
||||
if err != nil {
|
||||
fe.Status = coredata.FrameworkExportStatusFailed
|
||||
fe.CompletedAt = ref.Ref(time.Now())
|
||||
if err := fe.Update(ctx, tx, scope); err != nil {
|
||||
return fmt.Errorf("cannot update framework export: %w", err)
|
||||
}
|
||||
|
||||
// s.logger.Error(ctx, "cannot create temp file", "error", err)
|
||||
|
||||
return nil
|
||||
}
|
||||
defer tempFile.Close()
|
||||
defer os.Remove(tempFile.Name())
|
||||
|
||||
err = tenantService.Frameworks.Export(ctx, fe.FrameworkID, tempFile)
|
||||
if err != nil {
|
||||
fe.Status = coredata.FrameworkExportStatusFailed
|
||||
fe.CompletedAt = ref.Ref(time.Now())
|
||||
if err := fe.Update(ctx, tx, scope); err != nil {
|
||||
return fmt.Errorf("cannot update framework export: %w", err)
|
||||
}
|
||||
|
||||
// s.logger.Error(ctx, "cannot export framework", "error", err)
|
||||
return nil
|
||||
}
|
||||
|
||||
uuid, err := uuid.NewV4()
|
||||
if err != nil {
|
||||
fe.Status = coredata.FrameworkExportStatusFailed
|
||||
fe.CompletedAt = ref.Ref(time.Now())
|
||||
if err := fe.Update(ctx, tx, scope); err != nil {
|
||||
return fmt.Errorf("cannot update framework export: %w", err)
|
||||
}
|
||||
|
||||
// s.logger.Error(ctx, "cannot generate UUID", "error", err)
|
||||
return nil
|
||||
}
|
||||
|
||||
if _, err := tempFile.Seek(0, 0); err != nil {
|
||||
fe.Status = coredata.FrameworkExportStatusFailed
|
||||
fe.CompletedAt = ref.Ref(time.Now())
|
||||
if err := fe.Update(ctx, tx, scope); err != nil {
|
||||
return fmt.Errorf("cannot update framework export: %w", err)
|
||||
}
|
||||
|
||||
// s.logger.Error(ctx, "cannot seek temp file", "error", err)
|
||||
return nil
|
||||
}
|
||||
|
||||
fileInfo, err := tempFile.Stat()
|
||||
if err != nil {
|
||||
fe.Status = coredata.FrameworkExportStatusFailed
|
||||
fe.CompletedAt = ref.Ref(time.Now())
|
||||
if err := fe.Update(ctx, tx, scope); err != nil {
|
||||
return fmt.Errorf("cannot update framework export: %w", err)
|
||||
}
|
||||
|
||||
// s.logger.Error(ctx, "cannot get temp file info", "error", err)
|
||||
return nil
|
||||
}
|
||||
|
||||
_, err = s.s3.PutObject(
|
||||
ctx,
|
||||
&s3.PutObjectInput{
|
||||
Bucket: ref.Ref(s.bucket),
|
||||
Key: ref.Ref(uuid.String()),
|
||||
Body: tempFile,
|
||||
ContentLength: ref.Ref(fileInfo.Size()),
|
||||
ContentType: ref.Ref("application/zip"),
|
||||
Metadata: map[string]string{
|
||||
"framework-id": framework.ID.String(),
|
||||
"framework-export-id": fe.ID.String(),
|
||||
},
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
fe.Status = coredata.FrameworkExportStatusFailed
|
||||
fe.CompletedAt = ref.Ref(time.Now())
|
||||
if err := fe.Update(ctx, tx, scope); err != nil {
|
||||
return fmt.Errorf("cannot update framework export: %w", err)
|
||||
}
|
||||
|
||||
// s.logger.Error(ctx, "cannot upload file to S3", "error", err)
|
||||
return nil
|
||||
}
|
||||
|
||||
now := time.Now()
|
||||
|
||||
file := coredata.File{
|
||||
ID: gid.New(fe.ID.TenantID(), coredata.FileEntityType),
|
||||
BucketName: s.bucket,
|
||||
MimeType: "application/zip",
|
||||
FileName: fmt.Sprintf("%s Archive %s.zip", framework.Name, time.Now().Format("2006-01-02")),
|
||||
FileKey: uuid.String(),
|
||||
CreatedAt: now,
|
||||
UpdatedAt: now,
|
||||
}
|
||||
|
||||
if err := file.Insert(ctx, tx, scope); err != nil {
|
||||
return fmt.Errorf("cannot insert file: %w", err)
|
||||
}
|
||||
|
||||
fe.FileID = &file.ID
|
||||
fe.CompletedAt = &now
|
||||
fe.Status = coredata.FrameworkExportStatusCompleted
|
||||
if err := fe.Update(ctx, tx, scope); err != nil {
|
||||
return fmt.Errorf("cannot update framework export: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
@@ -2265,6 +2265,7 @@ type Mutation {
|
||||
generateFrameworkStateOfApplicability(
|
||||
input: GenerateFrameworkStateOfApplicabilityInput!
|
||||
): GenerateFrameworkStateOfApplicabilityPayload!
|
||||
exportFramework(input: ExportFrameworkInput!): ExportFrameworkPayload!
|
||||
|
||||
# Control mutations
|
||||
createControl(input: CreateControlInput!): CreateControlPayload!
|
||||
@@ -2656,6 +2657,10 @@ input DeleteFrameworkInput {
|
||||
frameworkId: ID!
|
||||
}
|
||||
|
||||
input ExportFrameworkInput {
|
||||
frameworkId: ID!
|
||||
}
|
||||
|
||||
input CreateMeasureInput {
|
||||
organizationId: ID!
|
||||
name: String!
|
||||
@@ -3219,6 +3224,10 @@ type DeleteFrameworkPayload {
|
||||
deletedFrameworkId: ID!
|
||||
}
|
||||
|
||||
type ExportFrameworkPayload {
|
||||
exportJobId: ID!
|
||||
}
|
||||
|
||||
type CreateMeasurePayload {
|
||||
measureEdge: MeasureEdge!
|
||||
}
|
||||
|
||||
@@ -663,6 +663,10 @@ type ComplexityRoot struct {
|
||||
Data func(childComplexity int) int
|
||||
}
|
||||
|
||||
ExportFrameworkPayload struct {
|
||||
ExportJobID func(childComplexity int) int
|
||||
}
|
||||
|
||||
Framework struct {
|
||||
Controls func(childComplexity int, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.ControlOrderBy, filter *types.ControlFilter) int
|
||||
CreatedAt func(childComplexity int) int
|
||||
@@ -801,6 +805,7 @@ type ComplexityRoot struct {
|
||||
DeleteVendorDataPrivacyAgreement func(childComplexity int, input types.DeleteVendorDataPrivacyAgreementInput) int
|
||||
DeleteVendorService func(childComplexity int, input types.DeleteVendorServiceInput) int
|
||||
ExportDocumentVersionPDF func(childComplexity int, input types.ExportDocumentVersionPDFInput) int
|
||||
ExportFramework func(childComplexity int, input types.ExportFrameworkInput) int
|
||||
FulfillEvidence func(childComplexity int, input types.FulfillEvidenceInput) int
|
||||
GenerateDocumentChangelog func(childComplexity int, input types.GenerateDocumentChangelogInput) int
|
||||
GenerateFrameworkStateOfApplicability func(childComplexity int, input types.GenerateFrameworkStateOfApplicabilityInput) int
|
||||
@@ -1570,6 +1575,7 @@ type MutationResolver interface {
|
||||
ImportFramework(ctx context.Context, input types.ImportFrameworkInput) (*types.ImportFrameworkPayload, error)
|
||||
DeleteFramework(ctx context.Context, input types.DeleteFrameworkInput) (*types.DeleteFrameworkPayload, error)
|
||||
GenerateFrameworkStateOfApplicability(ctx context.Context, input types.GenerateFrameworkStateOfApplicabilityInput) (*types.GenerateFrameworkStateOfApplicabilityPayload, error)
|
||||
ExportFramework(ctx context.Context, input types.ExportFrameworkInput) (*types.ExportFrameworkPayload, error)
|
||||
CreateControl(ctx context.Context, input types.CreateControlInput) (*types.CreateControlPayload, error)
|
||||
UpdateControl(ctx context.Context, input types.UpdateControlInput) (*types.UpdateControlPayload, error)
|
||||
DeleteControl(ctx context.Context, input types.DeleteControlInput) (*types.DeleteControlPayload, error)
|
||||
@@ -3681,6 +3687,13 @@ func (e *executableSchema) Complexity(ctx context.Context, typeName, field strin
|
||||
|
||||
return e.complexity.ExportDocumentVersionPDFPayload.Data(childComplexity), true
|
||||
|
||||
case "ExportFrameworkPayload.exportJobId":
|
||||
if e.complexity.ExportFrameworkPayload.ExportJobID == nil {
|
||||
break
|
||||
}
|
||||
|
||||
return e.complexity.ExportFrameworkPayload.ExportJobID(childComplexity), true
|
||||
|
||||
case "Framework.controls":
|
||||
if e.complexity.Framework.Controls == nil {
|
||||
break
|
||||
@@ -4748,6 +4761,18 @@ func (e *executableSchema) Complexity(ctx context.Context, typeName, field strin
|
||||
|
||||
return e.complexity.Mutation.ExportDocumentVersionPDF(childComplexity, args["input"].(types.ExportDocumentVersionPDFInput)), true
|
||||
|
||||
case "Mutation.exportFramework":
|
||||
if e.complexity.Mutation.ExportFramework == nil {
|
||||
break
|
||||
}
|
||||
|
||||
args, err := ec.field_Mutation_exportFramework_args(ctx, rawArgs)
|
||||
if err != nil {
|
||||
return 0, false
|
||||
}
|
||||
|
||||
return e.complexity.Mutation.ExportFramework(childComplexity, args["input"].(types.ExportFrameworkInput)), true
|
||||
|
||||
case "Mutation.fulfillEvidence":
|
||||
if e.complexity.Mutation.FulfillEvidence == nil {
|
||||
break
|
||||
@@ -7806,6 +7831,7 @@ func (e *executableSchema) Exec(ctx context.Context) graphql.ResponseHandler {
|
||||
ec.unmarshalInputDocumentVersionSignatureOrder,
|
||||
ec.unmarshalInputEvidenceOrder,
|
||||
ec.unmarshalInputExportDocumentVersionPDFInput,
|
||||
ec.unmarshalInputExportFrameworkInput,
|
||||
ec.unmarshalInputFrameworkOrder,
|
||||
ec.unmarshalInputFulfillEvidenceInput,
|
||||
ec.unmarshalInputGenerateDocumentChangelogInput,
|
||||
@@ -10234,6 +10260,7 @@ type Mutation {
|
||||
generateFrameworkStateOfApplicability(
|
||||
input: GenerateFrameworkStateOfApplicabilityInput!
|
||||
): GenerateFrameworkStateOfApplicabilityPayload!
|
||||
exportFramework(input: ExportFrameworkInput!): ExportFrameworkPayload!
|
||||
|
||||
# Control mutations
|
||||
createControl(input: CreateControlInput!): CreateControlPayload!
|
||||
@@ -10625,6 +10652,10 @@ input DeleteFrameworkInput {
|
||||
frameworkId: ID!
|
||||
}
|
||||
|
||||
input ExportFrameworkInput {
|
||||
frameworkId: ID!
|
||||
}
|
||||
|
||||
input CreateMeasureInput {
|
||||
organizationId: ID!
|
||||
name: String!
|
||||
@@ -11188,6 +11219,10 @@ type DeleteFrameworkPayload {
|
||||
deletedFrameworkId: ID!
|
||||
}
|
||||
|
||||
type ExportFrameworkPayload {
|
||||
exportJobId: ID!
|
||||
}
|
||||
|
||||
type CreateMeasurePayload {
|
||||
measureEdge: MeasureEdge!
|
||||
}
|
||||
@@ -14952,6 +14987,29 @@ func (ec *executionContext) field_Mutation_exportDocumentVersionPDF_argsInput(
|
||||
return zeroVal, nil
|
||||
}
|
||||
|
||||
func (ec *executionContext) field_Mutation_exportFramework_args(ctx context.Context, rawArgs map[string]any) (map[string]any, error) {
|
||||
var err error
|
||||
args := map[string]any{}
|
||||
arg0, err := ec.field_Mutation_exportFramework_argsInput(ctx, rawArgs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
args["input"] = arg0
|
||||
return args, nil
|
||||
}
|
||||
func (ec *executionContext) field_Mutation_exportFramework_argsInput(
|
||||
ctx context.Context,
|
||||
rawArgs map[string]any,
|
||||
) (types.ExportFrameworkInput, error) {
|
||||
ctx = graphql.WithPathContext(ctx, graphql.NewPathWithField("input"))
|
||||
if tmp, ok := rawArgs["input"]; ok {
|
||||
return ec.unmarshalNExportFrameworkInput2githubᚗcomᚋgetproboᚋproboᚋpkgᚋserverᚋapiᚋconsoleᚋv1ᚋtypesᚐExportFrameworkInput(ctx, tmp)
|
||||
}
|
||||
|
||||
var zeroVal types.ExportFrameworkInput
|
||||
return zeroVal, nil
|
||||
}
|
||||
|
||||
func (ec *executionContext) field_Mutation_fulfillEvidence_args(ctx context.Context, rawArgs map[string]any) (map[string]any, error) {
|
||||
var err error
|
||||
args := map[string]any{}
|
||||
@@ -32208,6 +32266,50 @@ func (ec *executionContext) fieldContext_ExportDocumentVersionPDFPayload_data(_
|
||||
return fc, nil
|
||||
}
|
||||
|
||||
func (ec *executionContext) _ExportFrameworkPayload_exportJobId(ctx context.Context, field graphql.CollectedField, obj *types.ExportFrameworkPayload) (ret graphql.Marshaler) {
|
||||
fc, err := ec.fieldContext_ExportFrameworkPayload_exportJobId(ctx, field)
|
||||
if err != nil {
|
||||
return graphql.Null
|
||||
}
|
||||
ctx = graphql.WithFieldContext(ctx, fc)
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
ec.Error(ctx, ec.Recover(ctx, r))
|
||||
ret = graphql.Null
|
||||
}
|
||||
}()
|
||||
resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (any, error) {
|
||||
ctx = rctx // use context from middleware stack in children
|
||||
return obj.ExportJobID, nil
|
||||
})
|
||||
if err != nil {
|
||||
ec.Error(ctx, err)
|
||||
return graphql.Null
|
||||
}
|
||||
if resTmp == nil {
|
||||
if !graphql.HasFieldError(ctx, fc) {
|
||||
ec.Errorf(ctx, "must not be null")
|
||||
}
|
||||
return graphql.Null
|
||||
}
|
||||
res := resTmp.(gid.GID)
|
||||
fc.Result = res
|
||||
return ec.marshalNID2githubᚗcomᚋgetproboᚋproboᚋpkgᚋgidᚐGID(ctx, field.Selections, res)
|
||||
}
|
||||
|
||||
func (ec *executionContext) fieldContext_ExportFrameworkPayload_exportJobId(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) {
|
||||
fc = &graphql.FieldContext{
|
||||
Object: "ExportFrameworkPayload",
|
||||
Field: field,
|
||||
IsMethod: false,
|
||||
IsResolver: false,
|
||||
Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) {
|
||||
return nil, errors.New("field of type ID does not have child fields")
|
||||
},
|
||||
}
|
||||
return fc, nil
|
||||
}
|
||||
|
||||
func (ec *executionContext) _Framework_id(ctx context.Context, field graphql.CollectedField, obj *types.Framework) (ret graphql.Marshaler) {
|
||||
fc, err := ec.fieldContext_Framework_id(ctx, field)
|
||||
if err != nil {
|
||||
@@ -35532,6 +35634,65 @@ func (ec *executionContext) fieldContext_Mutation_generateFrameworkStateOfApplic
|
||||
return fc, nil
|
||||
}
|
||||
|
||||
func (ec *executionContext) _Mutation_exportFramework(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) {
|
||||
fc, err := ec.fieldContext_Mutation_exportFramework(ctx, field)
|
||||
if err != nil {
|
||||
return graphql.Null
|
||||
}
|
||||
ctx = graphql.WithFieldContext(ctx, fc)
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
ec.Error(ctx, ec.Recover(ctx, r))
|
||||
ret = graphql.Null
|
||||
}
|
||||
}()
|
||||
resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (any, error) {
|
||||
ctx = rctx // use context from middleware stack in children
|
||||
return ec.resolvers.Mutation().ExportFramework(rctx, fc.Args["input"].(types.ExportFrameworkInput))
|
||||
})
|
||||
if err != nil {
|
||||
ec.Error(ctx, err)
|
||||
return graphql.Null
|
||||
}
|
||||
if resTmp == nil {
|
||||
if !graphql.HasFieldError(ctx, fc) {
|
||||
ec.Errorf(ctx, "must not be null")
|
||||
}
|
||||
return graphql.Null
|
||||
}
|
||||
res := resTmp.(*types.ExportFrameworkPayload)
|
||||
fc.Result = res
|
||||
return ec.marshalNExportFrameworkPayload2ᚖgithubᚗcomᚋgetproboᚋproboᚋpkgᚋserverᚋapiᚋconsoleᚋv1ᚋtypesᚐExportFrameworkPayload(ctx, field.Selections, res)
|
||||
}
|
||||
|
||||
func (ec *executionContext) fieldContext_Mutation_exportFramework(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) {
|
||||
fc = &graphql.FieldContext{
|
||||
Object: "Mutation",
|
||||
Field: field,
|
||||
IsMethod: true,
|
||||
IsResolver: true,
|
||||
Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) {
|
||||
switch field.Name {
|
||||
case "exportJobId":
|
||||
return ec.fieldContext_ExportFrameworkPayload_exportJobId(ctx, field)
|
||||
}
|
||||
return nil, fmt.Errorf("no field named %q was found under type ExportFrameworkPayload", field.Name)
|
||||
},
|
||||
}
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
err = ec.Recover(ctx, r)
|
||||
ec.Error(ctx, err)
|
||||
}
|
||||
}()
|
||||
ctx = graphql.WithFieldContext(ctx, fc)
|
||||
if fc.Args, err = ec.field_Mutation_exportFramework_args(ctx, field.ArgumentMap(ec.Variables)); err != nil {
|
||||
ec.Error(ctx, err)
|
||||
return fc, err
|
||||
}
|
||||
return fc, nil
|
||||
}
|
||||
|
||||
func (ec *executionContext) _Mutation_createControl(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) {
|
||||
fc, err := ec.fieldContext_Mutation_createControl(ctx, field)
|
||||
if err != nil {
|
||||
@@ -63306,6 +63467,33 @@ func (ec *executionContext) unmarshalInputExportDocumentVersionPDFInput(ctx cont
|
||||
return it, nil
|
||||
}
|
||||
|
||||
func (ec *executionContext) unmarshalInputExportFrameworkInput(ctx context.Context, obj any) (types.ExportFrameworkInput, error) {
|
||||
var it types.ExportFrameworkInput
|
||||
asMap := map[string]any{}
|
||||
for k, v := range obj.(map[string]any) {
|
||||
asMap[k] = v
|
||||
}
|
||||
|
||||
fieldsInOrder := [...]string{"frameworkId"}
|
||||
for _, k := range fieldsInOrder {
|
||||
v, ok := asMap[k]
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
switch k {
|
||||
case "frameworkId":
|
||||
ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("frameworkId"))
|
||||
data, err := ec.unmarshalNID2githubᚗcomᚋgetproboᚋproboᚋpkgᚋgidᚐGID(ctx, v)
|
||||
if err != nil {
|
||||
return it, err
|
||||
}
|
||||
it.FrameworkID = data
|
||||
}
|
||||
}
|
||||
|
||||
return it, nil
|
||||
}
|
||||
|
||||
func (ec *executionContext) unmarshalInputFrameworkOrder(ctx context.Context, obj any) (types.FrameworkOrderBy, error) {
|
||||
var it types.FrameworkOrderBy
|
||||
asMap := map[string]any{}
|
||||
@@ -72518,6 +72706,45 @@ func (ec *executionContext) _ExportDocumentVersionPDFPayload(ctx context.Context
|
||||
return out
|
||||
}
|
||||
|
||||
var exportFrameworkPayloadImplementors = []string{"ExportFrameworkPayload"}
|
||||
|
||||
func (ec *executionContext) _ExportFrameworkPayload(ctx context.Context, sel ast.SelectionSet, obj *types.ExportFrameworkPayload) graphql.Marshaler {
|
||||
fields := graphql.CollectFields(ec.OperationContext, sel, exportFrameworkPayloadImplementors)
|
||||
|
||||
out := graphql.NewFieldSet(fields)
|
||||
deferred := make(map[string]*graphql.FieldSet)
|
||||
for i, field := range fields {
|
||||
switch field.Name {
|
||||
case "__typename":
|
||||
out.Values[i] = graphql.MarshalString("ExportFrameworkPayload")
|
||||
case "exportJobId":
|
||||
out.Values[i] = ec._ExportFrameworkPayload_exportJobId(ctx, field, obj)
|
||||
if out.Values[i] == graphql.Null {
|
||||
out.Invalids++
|
||||
}
|
||||
default:
|
||||
panic("unknown field " + strconv.Quote(field.Name))
|
||||
}
|
||||
}
|
||||
out.Dispatch(ctx)
|
||||
if out.Invalids > 0 {
|
||||
return graphql.Null
|
||||
}
|
||||
|
||||
atomic.AddInt32(&ec.deferred, int32(len(deferred)))
|
||||
|
||||
for label, dfs := range deferred {
|
||||
ec.processDeferredGroup(graphql.DeferredGroup{
|
||||
Label: label,
|
||||
Path: graphql.GetPath(ctx),
|
||||
FieldSet: dfs,
|
||||
Context: ctx,
|
||||
})
|
||||
}
|
||||
|
||||
return out
|
||||
}
|
||||
|
||||
var frameworkImplementors = []string{"Framework", "Node"}
|
||||
|
||||
func (ec *executionContext) _Framework(ctx context.Context, sel ast.SelectionSet, obj *types.Framework) graphql.Marshaler {
|
||||
@@ -73552,6 +73779,13 @@ func (ec *executionContext) _Mutation(ctx context.Context, sel ast.SelectionSet)
|
||||
if out.Values[i] == graphql.Null {
|
||||
out.Invalids++
|
||||
}
|
||||
case "exportFramework":
|
||||
out.Values[i] = ec.OperationContext.RootResolverMiddleware(innerCtx, func(ctx context.Context) (res graphql.Marshaler) {
|
||||
return ec._Mutation_exportFramework(ctx, field)
|
||||
})
|
||||
if out.Values[i] == graphql.Null {
|
||||
out.Invalids++
|
||||
}
|
||||
case "createControl":
|
||||
out.Values[i] = ec.OperationContext.RootResolverMiddleware(innerCtx, func(ctx context.Context) (res graphql.Marshaler) {
|
||||
return ec._Mutation_createControl(ctx, field)
|
||||
@@ -83960,6 +84194,25 @@ func (ec *executionContext) marshalNExportDocumentVersionPDFPayload2ᚖgithubᚗ
|
||||
return ec._ExportDocumentVersionPDFPayload(ctx, sel, v)
|
||||
}
|
||||
|
||||
func (ec *executionContext) unmarshalNExportFrameworkInput2githubᚗcomᚋgetproboᚋproboᚋpkgᚋserverᚋapiᚋconsoleᚋv1ᚋtypesᚐExportFrameworkInput(ctx context.Context, v any) (types.ExportFrameworkInput, error) {
|
||||
res, err := ec.unmarshalInputExportFrameworkInput(ctx, v)
|
||||
return res, graphql.ErrorOnPath(ctx, err)
|
||||
}
|
||||
|
||||
func (ec *executionContext) marshalNExportFrameworkPayload2githubᚗcomᚋgetproboᚋproboᚋpkgᚋserverᚋapiᚋconsoleᚋv1ᚋtypesᚐExportFrameworkPayload(ctx context.Context, sel ast.SelectionSet, v types.ExportFrameworkPayload) graphql.Marshaler {
|
||||
return ec._ExportFrameworkPayload(ctx, sel, &v)
|
||||
}
|
||||
|
||||
func (ec *executionContext) marshalNExportFrameworkPayload2ᚖgithubᚗcomᚋgetproboᚋproboᚋpkgᚋserverᚋapiᚋconsoleᚋv1ᚋtypesᚐExportFrameworkPayload(ctx context.Context, sel ast.SelectionSet, v *types.ExportFrameworkPayload) graphql.Marshaler {
|
||||
if v == nil {
|
||||
if !graphql.HasFieldError(ctx, graphql.GetFieldContext(ctx)) {
|
||||
ec.Errorf(ctx, "the requested element is null which the schema does not allow")
|
||||
}
|
||||
return graphql.Null
|
||||
}
|
||||
return ec._ExportFrameworkPayload(ctx, sel, v)
|
||||
}
|
||||
|
||||
func (ec *executionContext) marshalNFramework2githubᚗcomᚋgetproboᚋproboᚋpkgᚋserverᚋapiᚋconsoleᚋv1ᚋtypesᚐFramework(ctx context.Context, sel ast.SelectionSet, v types.Framework) graphql.Marshaler {
|
||||
return ec._Framework(ctx, sel, &v)
|
||||
}
|
||||
|
||||
@@ -1029,6 +1029,14 @@ type ExportDocumentVersionPDFPayload struct {
|
||||
Data string `json:"data"`
|
||||
}
|
||||
|
||||
type ExportFrameworkInput struct {
|
||||
FrameworkID gid.GID `json:"frameworkId"`
|
||||
}
|
||||
|
||||
type ExportFrameworkPayload struct {
|
||||
ExportJobID gid.GID `json:"exportJobId"`
|
||||
}
|
||||
|
||||
type Framework struct {
|
||||
ID gid.GID `json:"id"`
|
||||
Name string `json:"name"`
|
||||
|
||||
@@ -1678,6 +1678,11 @@ func (r *mutationResolver) GenerateFrameworkStateOfApplicability(ctx context.Con
|
||||
}, nil
|
||||
}
|
||||
|
||||
// ExportFramework is the resolver for the exportFramework field.
|
||||
func (r *mutationResolver) ExportFramework(ctx context.Context, input types.ExportFrameworkInput) (*types.ExportFrameworkPayload, error) {
|
||||
panic(fmt.Errorf("not implemented: ExportFramework - exportFramework"))
|
||||
}
|
||||
|
||||
// CreateControl is the resolver for the createControl field.
|
||||
func (r *mutationResolver) CreateControl(ctx context.Context, input types.CreateControlInput) (*types.CreateControlPayload, error) {
|
||||
prb := r.ProboService(ctx, input.FrameworkID.TenantID())
|
||||
@@ -3958,6 +3963,10 @@ func (r *queryResolver) Node(ctx context.Context, id gid.GID) (types.Node, error
|
||||
|
||||
return types.NewVendor(vendor), nil
|
||||
case coredata.FrameworkEntityType:
|
||||
if err := prb.Frameworks.Export(ctx, id, nil); err != nil {
|
||||
panic(fmt.Errorf("cannot export organization frameworks: %w", err))
|
||||
}
|
||||
|
||||
framework, err := prb.Frameworks.Get(ctx, id)
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("cannot get framework: %w", err))
|
||||
|
||||
Reference in New Issue
Block a user