@@ -546,3 +546,27 @@ WHERE
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *Documents) BulkSoftDelete(
|
||||
ctx context.Context,
|
||||
conn pg.Conn,
|
||||
scope Scoper,
|
||||
) error {
|
||||
q := `
|
||||
UPDATE documents SET deleted_at = @deleted_at WHERE %s AND id = ANY(@document_ids)
|
||||
`
|
||||
q = fmt.Sprintf(q, scope.SQLFragment())
|
||||
|
||||
ids := make([]gid.GID, len(*p))
|
||||
for i, doc := range *p {
|
||||
ids[i] = doc.ID
|
||||
}
|
||||
|
||||
args := pgx.StrictNamedArgs{
|
||||
"document_ids": ids,
|
||||
"deleted_at": time.Now()}
|
||||
maps.Copy(args, scope.SQLArguments())
|
||||
|
||||
_, err := conn.Exec(ctx, q, args)
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -55,5 +55,5 @@ const (
|
||||
SnapshotEntityType
|
||||
ContinualImprovementEntityType
|
||||
ProcessingActivityEntityType
|
||||
FrameworkExportEntityType
|
||||
ExportJobEntityType
|
||||
)
|
||||
|
||||
246
pkg/coredata/export_job.go
Normal file
246
pkg/coredata/export_job.go
Normal file
@@ -0,0 +1,246 @@
|
||||
package coredata
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"maps"
|
||||
"time"
|
||||
|
||||
"github.com/getprobo/probo/pkg/gid"
|
||||
"github.com/jackc/pgx/v5"
|
||||
"go.gearno.de/kit/pg"
|
||||
)
|
||||
|
||||
type (
|
||||
ExportJob struct {
|
||||
ID gid.GID `db:"id"`
|
||||
Type ExportJobType `db:"type"`
|
||||
Arguments json.RawMessage `db:"arguments"`
|
||||
Error *string `db:"error"`
|
||||
Status ExportJobStatus `db:"status"`
|
||||
FileID *gid.GID `db:"file_id"`
|
||||
RecipientEmail string `db:"recipient_email"`
|
||||
RecipientName string `db:"recipient_name"`
|
||||
CreatedAt time.Time `db:"created_at"`
|
||||
StartedAt *time.Time `db:"started_at"`
|
||||
CompletedAt *time.Time `db:"completed_at"`
|
||||
}
|
||||
|
||||
ExportJobs []*ExportJob
|
||||
|
||||
DocumentExportArguments struct {
|
||||
DocumentIDs []gid.GID `json:"document_ids"`
|
||||
}
|
||||
|
||||
FrameworkExportArguments struct {
|
||||
FrameworkID gid.GID `json:"framework_id"`
|
||||
}
|
||||
)
|
||||
|
||||
var (
|
||||
ErrNoExportJobAvailable = errors.New("no export job available")
|
||||
)
|
||||
|
||||
func (ej *ExportJob) Insert(
|
||||
ctx context.Context,
|
||||
conn pg.Conn,
|
||||
scope Scoper,
|
||||
) error {
|
||||
q := `
|
||||
INSERT INTO export_jobs (
|
||||
id,
|
||||
tenant_id,
|
||||
type,
|
||||
arguments,
|
||||
status,
|
||||
recipient_email,
|
||||
recipient_name,
|
||||
created_at
|
||||
) VALUES (
|
||||
@id,
|
||||
@tenant_id,
|
||||
@type,
|
||||
@arguments,
|
||||
@status,
|
||||
@recipient_email,
|
||||
@recipient_name,
|
||||
@created_at
|
||||
)`
|
||||
args := pgx.StrictNamedArgs{
|
||||
"id": ej.ID,
|
||||
"tenant_id": scope.GetTenantID(),
|
||||
"type": ej.Type,
|
||||
"arguments": ej.Arguments,
|
||||
"status": ej.Status,
|
||||
"recipient_email": ej.RecipientEmail,
|
||||
"recipient_name": ej.RecipientName,
|
||||
"created_at": ej.CreatedAt,
|
||||
}
|
||||
_, err := conn.Exec(ctx, q, args)
|
||||
return err
|
||||
}
|
||||
|
||||
func (ej *ExportJob) Update(
|
||||
ctx context.Context,
|
||||
conn pg.Conn,
|
||||
scope Scoper,
|
||||
) error {
|
||||
q := `
|
||||
UPDATE
|
||||
export_jobs
|
||||
SET
|
||||
status = @status,
|
||||
error = @error,
|
||||
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": ej.Status,
|
||||
"error": ej.Error,
|
||||
"file_id": ej.FileID,
|
||||
"started_at": ej.StartedAt,
|
||||
"completed_at": ej.CompletedAt,
|
||||
"id": ej.ID,
|
||||
}
|
||||
maps.Copy(args, scope.SQLArguments())
|
||||
_, err := conn.Exec(ctx, q, args)
|
||||
return err
|
||||
}
|
||||
|
||||
func (ej *ExportJob) LoadByID(
|
||||
ctx context.Context,
|
||||
conn pg.Conn,
|
||||
scope Scoper,
|
||||
id gid.GID,
|
||||
) error {
|
||||
q := `
|
||||
SELECT
|
||||
id,
|
||||
type,
|
||||
arguments,
|
||||
error,
|
||||
status,
|
||||
file_id,
|
||||
recipient_email,
|
||||
recipient_name,
|
||||
created_at,
|
||||
started_at,
|
||||
completed_at
|
||||
FROM
|
||||
export_jobs
|
||||
WHERE
|
||||
%s
|
||||
AND id = @id
|
||||
`
|
||||
q = fmt.Sprintf(q, scope.SQLFragment())
|
||||
args := pgx.StrictNamedArgs{"id": id}
|
||||
maps.Copy(args, scope.SQLArguments())
|
||||
|
||||
rows, err := conn.Query(ctx, q, args)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ej2, err := pgx.CollectExactlyOneRow(rows, pgx.RowToStructByName[ExportJob])
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot collect export job: %w", err)
|
||||
}
|
||||
|
||||
*ej = ej2
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ej *ExportJob) LoadNextPendingForUpdateSkipLocked(
|
||||
ctx context.Context,
|
||||
conn pg.Conn,
|
||||
) error {
|
||||
q := `
|
||||
SELECT
|
||||
id,
|
||||
type,
|
||||
arguments,
|
||||
error,
|
||||
status,
|
||||
file_id,
|
||||
recipient_email,
|
||||
recipient_name,
|
||||
created_at,
|
||||
started_at,
|
||||
completed_at
|
||||
FROM
|
||||
export_jobs
|
||||
WHERE
|
||||
status = @status
|
||||
ORDER BY
|
||||
created_at ASC
|
||||
LIMIT 1
|
||||
FOR UPDATE SKIP LOCKED
|
||||
`
|
||||
args := pgx.StrictNamedArgs{
|
||||
"status": ExportJobStatusPending,
|
||||
}
|
||||
rows, err := conn.Query(ctx, q, args)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ej2, err := pgx.CollectExactlyOneRow(rows, pgx.RowToStructByName[ExportJob])
|
||||
if err != nil {
|
||||
if errors.Is(err, pgx.ErrNoRows) {
|
||||
return ErrNoExportJobAvailable
|
||||
}
|
||||
return fmt.Errorf("cannot collect export job: %w", err)
|
||||
}
|
||||
|
||||
*ej = ej2
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ej *ExportJob) GetDocumentExportArguments() (*DocumentExportArguments, error) {
|
||||
if ej.Type != ExportJobTypeDocument {
|
||||
return nil, fmt.Errorf("export job is not a document export")
|
||||
}
|
||||
|
||||
var args DocumentExportArguments
|
||||
if err := json.Unmarshal(ej.Arguments, &args); err != nil {
|
||||
return nil, fmt.Errorf("cannot unmarshal document export arguments: %w", err)
|
||||
}
|
||||
|
||||
return &args, nil
|
||||
}
|
||||
|
||||
func (ej *ExportJob) GetFrameworkExportArguments() (*FrameworkExportArguments, error) {
|
||||
if ej.Type != ExportJobTypeFramework {
|
||||
return nil, fmt.Errorf("export job is not a framework export")
|
||||
}
|
||||
|
||||
var args FrameworkExportArguments
|
||||
if err := json.Unmarshal(ej.Arguments, &args); err != nil {
|
||||
return nil, fmt.Errorf("cannot unmarshal framework export arguments: %w", err)
|
||||
}
|
||||
|
||||
return &args, nil
|
||||
}
|
||||
|
||||
func (ej *ExportJob) GetDocumentIDs() ([]gid.GID, error) {
|
||||
args, err := ej.GetDocumentExportArguments()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return args.DocumentIDs, nil
|
||||
}
|
||||
|
||||
func (ej *ExportJob) GetFrameworkID() (gid.GID, error) {
|
||||
args, err := ej.GetFrameworkExportArguments()
|
||||
if err != nil {
|
||||
return gid.GID{}, err
|
||||
}
|
||||
return args.FrameworkID, nil
|
||||
}
|
||||
51
pkg/coredata/export_job_status.go
Normal file
51
pkg/coredata/export_job_status.go
Normal file
@@ -0,0 +1,51 @@
|
||||
package coredata
|
||||
|
||||
import (
|
||||
"database/sql/driver"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
type (
|
||||
ExportJobStatus string
|
||||
)
|
||||
|
||||
const (
|
||||
ExportJobStatusPending ExportJobStatus = "PENDING"
|
||||
ExportJobStatusProcessing ExportJobStatus = "PROCESSING"
|
||||
ExportJobStatusCompleted ExportJobStatus = "COMPLETED"
|
||||
ExportJobStatusFailed ExportJobStatus = "FAILED"
|
||||
)
|
||||
|
||||
func (ejs ExportJobStatus) String() string {
|
||||
return string(ejs)
|
||||
}
|
||||
|
||||
func (ejs *ExportJobStatus) Scan(value any) error {
|
||||
var s string
|
||||
switch v := value.(type) {
|
||||
case string:
|
||||
s = v
|
||||
case []byte:
|
||||
s = string(v)
|
||||
default:
|
||||
return fmt.Errorf("unsupported type for ExportJobStatus: %T", value)
|
||||
}
|
||||
|
||||
switch s {
|
||||
case ExportJobStatusPending.String():
|
||||
*ejs = ExportJobStatusPending
|
||||
case ExportJobStatusProcessing.String():
|
||||
*ejs = ExportJobStatusProcessing
|
||||
case ExportJobStatusCompleted.String():
|
||||
*ejs = ExportJobStatusCompleted
|
||||
case ExportJobStatusFailed.String():
|
||||
*ejs = ExportJobStatusFailed
|
||||
default:
|
||||
return fmt.Errorf("invalid ExportJobStatus value: %q", s)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ejs ExportJobStatus) Value() (driver.Value, error) {
|
||||
return ejs.String(), nil
|
||||
}
|
||||
45
pkg/coredata/export_job_type.go
Normal file
45
pkg/coredata/export_job_type.go
Normal file
@@ -0,0 +1,45 @@
|
||||
package coredata
|
||||
|
||||
import (
|
||||
"database/sql/driver"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
type (
|
||||
ExportJobType string
|
||||
)
|
||||
|
||||
const (
|
||||
ExportJobTypeFramework ExportJobType = "FRAMEWORK"
|
||||
ExportJobTypeDocument ExportJobType = "DOCUMENT"
|
||||
)
|
||||
|
||||
func (ejt ExportJobType) String() string {
|
||||
return string(ejt)
|
||||
}
|
||||
|
||||
func (ejt *ExportJobType) Scan(value any) error {
|
||||
var s string
|
||||
switch v := value.(type) {
|
||||
case string:
|
||||
s = v
|
||||
case []byte:
|
||||
s = string(v)
|
||||
default:
|
||||
return fmt.Errorf("unsupported type for ExportJobType: %T", value)
|
||||
}
|
||||
|
||||
switch s {
|
||||
case ExportJobTypeFramework.String():
|
||||
*ejt = ExportJobTypeFramework
|
||||
case ExportJobTypeDocument.String():
|
||||
*ejt = ExportJobTypeDocument
|
||||
default:
|
||||
return fmt.Errorf("invalid ExportJobType value: %q", s)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ejt ExportJobType) Value() (driver.Value, error) {
|
||||
return ejt.String(), nil
|
||||
}
|
||||
@@ -1,172 +0,0 @@
|
||||
// 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"`
|
||||
RecipientEmail string `db:"recipient_email"`
|
||||
RecipientName string `db:"recipient_name"`
|
||||
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,
|
||||
recipient_email,
|
||||
recipient_name,
|
||||
status,
|
||||
created_at
|
||||
) VALUES (
|
||||
@id,
|
||||
@tenant_id,
|
||||
@framework_id,
|
||||
@recipient_email,
|
||||
@recipient_name,
|
||||
@status,
|
||||
@created_at
|
||||
)`
|
||||
|
||||
args := pgx.StrictNamedArgs{
|
||||
"id": fe.ID,
|
||||
"tenant_id": scope.GetTenantID(),
|
||||
"framework_id": fe.FrameworkID,
|
||||
"recipient_email": fe.RecipientEmail,
|
||||
"recipient_name": fe.RecipientName,
|
||||
"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,
|
||||
recipient_email,
|
||||
recipient_name,
|
||||
status,
|
||||
file_id,
|
||||
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
|
||||
}
|
||||
@@ -1,40 +0,0 @@
|
||||
// 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
|
||||
}
|
||||
@@ -1,86 +0,0 @@
|
||||
// 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
|
||||
}
|
||||
34
pkg/coredata/migrations/20250916T121639Z.sql
Normal file
34
pkg/coredata/migrations/20250916T121639Z.sql
Normal file
@@ -0,0 +1,34 @@
|
||||
CREATE TYPE export_jobs_status AS ENUM (
|
||||
'PENDING',
|
||||
'PROCESSING',
|
||||
'COMPLETED',
|
||||
'FAILED'
|
||||
);
|
||||
|
||||
CREATE TYPE export_jobs_type AS ENUM (
|
||||
'FRAMEWORK',
|
||||
'DOCUMENT'
|
||||
);
|
||||
|
||||
CREATE TABLE export_jobs (
|
||||
id TEXT PRIMARY KEY,
|
||||
tenant_id TEXT NOT NULL,
|
||||
type export_jobs_type NOT NULL,
|
||||
arguments JSONB NOT NULL,
|
||||
error TEXT,
|
||||
status export_jobs_status NOT NULL,
|
||||
file_id TEXT,
|
||||
recipient_email TEXT NOT NULL,
|
||||
recipient_name TEXT NOT NULL,
|
||||
created_at TIMESTAMP WITH TIME ZONE NOT NULL,
|
||||
started_at TIMESTAMP WITH TIME ZONE,
|
||||
completed_at TIMESTAMP WITH TIME ZONE
|
||||
);
|
||||
|
||||
ALTER TABLE export_jobs ADD CONSTRAINT export_jobs_file_id_fkey
|
||||
FOREIGN KEY (file_id)
|
||||
REFERENCES files(id)
|
||||
ON UPDATE CASCADE
|
||||
ON DELETE SET NULL;
|
||||
|
||||
DROP TABLE framework_exports;
|
||||
@@ -1,12 +1,19 @@
|
||||
package probo
|
||||
|
||||
import (
|
||||
"archive/zip"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/url"
|
||||
"os"
|
||||
"regexp"
|
||||
"strings"
|
||||
"time"
|
||||
"unicode"
|
||||
|
||||
"github.com/aws/aws-sdk-go-v2/service/s3"
|
||||
"github.com/getprobo/probo/pkg/coredata"
|
||||
"github.com/getprobo/probo/pkg/docgen"
|
||||
"github.com/getprobo/probo/pkg/gid"
|
||||
@@ -14,7 +21,9 @@ import (
|
||||
"github.com/getprobo/probo/pkg/page"
|
||||
"github.com/getprobo/probo/pkg/statelesstoken"
|
||||
"github.com/jackc/pgx/v5"
|
||||
"go.gearno.de/crypto/uuid"
|
||||
"go.gearno.de/kit/pg"
|
||||
"go.gearno.de/x/ref"
|
||||
)
|
||||
|
||||
type (
|
||||
@@ -68,6 +77,22 @@ type (
|
||||
|
||||
const (
|
||||
TokenTypeSigningRequest = "signing_request"
|
||||
|
||||
documentExportEmailExpiresIn = 24 * time.Hour
|
||||
documentExportEmailSubject = "Your document export is ready"
|
||||
documentExportEmailBody = `
|
||||
Your document export has been completed successfully.
|
||||
|
||||
You can download the export using the link below:
|
||||
[1] %s
|
||||
|
||||
This link will expire in 24 hours.`
|
||||
|
||||
maxFilenameLength = 200
|
||||
)
|
||||
|
||||
var (
|
||||
invalidFilenameChars = regexp.MustCompile(`[<>:"/\\|?*\x00-\x1f\x7f]`)
|
||||
)
|
||||
|
||||
func (e ErrSignatureNotCancellable) Error() string {
|
||||
@@ -814,6 +839,76 @@ func (s *DocumentService) SoftDelete(
|
||||
)
|
||||
}
|
||||
|
||||
func (s *DocumentService) BulkSoftDelete(
|
||||
ctx context.Context,
|
||||
documentIDs []gid.GID,
|
||||
) error {
|
||||
documents := coredata.Documents{}
|
||||
|
||||
for _, documentID := range documentIDs {
|
||||
documents = append(documents, &coredata.Document{ID: documentID})
|
||||
}
|
||||
|
||||
return s.svc.pg.WithConn(
|
||||
ctx,
|
||||
func(conn pg.Conn) error {
|
||||
return documents.BulkSoftDelete(ctx, conn, s.svc.scope)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
func (s *DocumentService) RequestExport(
|
||||
ctx context.Context,
|
||||
documentIDs []gid.GID,
|
||||
recipientEmail string,
|
||||
recipientName string,
|
||||
) (*coredata.ExportJob, error) {
|
||||
var exportJobID gid.GID
|
||||
exportJob := &coredata.ExportJob{}
|
||||
|
||||
err := s.svc.pg.WithTx(ctx, func(conn pg.Conn) error {
|
||||
for _, documentID := range documentIDs {
|
||||
document := &coredata.Document{}
|
||||
if err := document.LoadByID(ctx, conn, s.svc.scope, documentID); err != nil {
|
||||
return fmt.Errorf("cannot load document %q: %w", documentID, err)
|
||||
}
|
||||
}
|
||||
|
||||
now := time.Now()
|
||||
exportJobID = gid.New(s.svc.scope.GetTenantID(), coredata.ExportJobEntityType)
|
||||
|
||||
args := coredata.DocumentExportArguments{
|
||||
DocumentIDs: documentIDs,
|
||||
}
|
||||
argsJSON, err := json.Marshal(args)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot marshal document export arguments: %w", err)
|
||||
}
|
||||
|
||||
exportJob = &coredata.ExportJob{
|
||||
ID: exportJobID,
|
||||
Type: coredata.ExportJobTypeDocument,
|
||||
Arguments: argsJSON,
|
||||
Status: coredata.ExportJobStatusPending,
|
||||
RecipientEmail: recipientEmail,
|
||||
RecipientName: recipientName,
|
||||
CreatedAt: now,
|
||||
}
|
||||
|
||||
if err := exportJob.Insert(ctx, conn, s.svc.scope); err != nil {
|
||||
return fmt.Errorf("cannot insert export job: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return exportJob, nil
|
||||
}
|
||||
|
||||
func (s *DocumentService) ListVersions(
|
||||
ctx context.Context,
|
||||
documentID gid.GID,
|
||||
@@ -1118,6 +1213,97 @@ func (s *DocumentService) ExportPDF(
|
||||
return data, nil
|
||||
}
|
||||
|
||||
func (s *DocumentService) BuildAndUploadExport(ctx context.Context, exportJobID gid.GID) (*coredata.ExportJob, error) {
|
||||
exportJob := &coredata.ExportJob{}
|
||||
err := s.svc.pg.WithTx(
|
||||
ctx,
|
||||
func(tx pg.Conn) error {
|
||||
if err := exportJob.LoadByID(ctx, tx, s.svc.scope, exportJobID); err != nil {
|
||||
return fmt.Errorf("cannot load export job: %w", err)
|
||||
}
|
||||
|
||||
documentIDs, err := exportJob.GetDocumentIDs()
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot get document IDs: %w", err)
|
||||
}
|
||||
|
||||
tempDir := os.TempDir()
|
||||
tempFile, err := os.CreateTemp(tempDir, "probo-document-export-*.zip")
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot create temp file: %w", err)
|
||||
}
|
||||
defer tempFile.Close()
|
||||
defer os.Remove(tempFile.Name())
|
||||
|
||||
err = s.Export(ctx, documentIDs, tempFile)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot export documents: %w", err)
|
||||
}
|
||||
|
||||
uuid, err := uuid.NewV4()
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot generate uuid: %w", err)
|
||||
}
|
||||
|
||||
if _, err := tempFile.Seek(0, 0); err != nil {
|
||||
return fmt.Errorf("cannot seek temp file: %w", err)
|
||||
}
|
||||
|
||||
fileInfo, err := tempFile.Stat()
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot stat temp file: %w", err)
|
||||
}
|
||||
|
||||
_, err = s.svc.s3.PutObject(
|
||||
ctx,
|
||||
&s3.PutObjectInput{
|
||||
Bucket: ref.Ref(s.svc.bucket),
|
||||
Key: ref.Ref(uuid.String()),
|
||||
Body: tempFile,
|
||||
ContentLength: ref.Ref(fileInfo.Size()),
|
||||
ContentType: ref.Ref("application/zip"),
|
||||
Metadata: map[string]string{
|
||||
"type": "document-export",
|
||||
"export-job-id": exportJob.ID.String(),
|
||||
},
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot upload file to S3: %w", err)
|
||||
}
|
||||
|
||||
now := time.Now()
|
||||
|
||||
file := coredata.File{
|
||||
ID: gid.New(exportJob.ID.TenantID(), coredata.FileEntityType),
|
||||
BucketName: s.svc.bucket,
|
||||
MimeType: "application/zip",
|
||||
FileName: fmt.Sprintf("Documents Export %s.zip", now.Format("2006-01-02")),
|
||||
FileKey: uuid.String(),
|
||||
FileSize: int(fileInfo.Size()),
|
||||
CreatedAt: now,
|
||||
UpdatedAt: now,
|
||||
}
|
||||
|
||||
if err := file.Insert(ctx, tx, s.svc.scope); err != nil {
|
||||
return fmt.Errorf("cannot insert file: %w", err)
|
||||
}
|
||||
|
||||
exportJob.FileID = &file.ID
|
||||
if err := exportJob.Update(ctx, tx, s.svc.scope); err != nil {
|
||||
return fmt.Errorf("cannot update export job: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return exportJob, nil
|
||||
}
|
||||
|
||||
func exportDocumentPDF(
|
||||
ctx context.Context,
|
||||
html2pdfConverter *html2pdf.Converter,
|
||||
@@ -1240,3 +1426,146 @@ func exportDocumentPDF(
|
||||
}
|
||||
return pdfData, nil
|
||||
}
|
||||
|
||||
func (s *DocumentService) Export(
|
||||
ctx context.Context,
|
||||
documentIDs []gid.GID,
|
||||
file io.Writer,
|
||||
) (err error) {
|
||||
archive := zip.NewWriter(file)
|
||||
defer func() {
|
||||
if closeErr := archive.Close(); closeErr != nil && err == nil {
|
||||
err = fmt.Errorf("cannot close archive: %w", closeErr)
|
||||
}
|
||||
}()
|
||||
|
||||
return s.svc.pg.WithTx(
|
||||
ctx,
|
||||
func(conn pg.Conn) error {
|
||||
for i, documentID := range documentIDs {
|
||||
document := &coredata.Document{}
|
||||
if err := document.LoadByID(ctx, conn, s.svc.scope, documentID); err != nil {
|
||||
return fmt.Errorf("cannot load document %q: %w", documentID, err)
|
||||
}
|
||||
|
||||
documentVersion := &coredata.DocumentVersion{}
|
||||
if err := documentVersion.LoadLatestVersion(ctx, conn, s.svc.scope, documentID); err != nil {
|
||||
return fmt.Errorf("cannot load document version for %q: %w", documentID, err)
|
||||
}
|
||||
|
||||
exportedPDF, err := exportDocumentPDF(
|
||||
ctx,
|
||||
s.html2pdfConverter,
|
||||
conn,
|
||||
s.svc.scope,
|
||||
documentVersion.ID,
|
||||
)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot export document PDF for %q: %w", documentID, err)
|
||||
}
|
||||
|
||||
filename := fmt.Sprintf("%d_%s.pdf", i+1, sanitizeFilename(document.Title))
|
||||
w, err := archive.Create(filename)
|
||||
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 *DocumentService) SendExportEmail(
|
||||
ctx context.Context,
|
||||
fileID gid.GID,
|
||||
recipientName string,
|
||||
recipientEmail string,
|
||||
) error {
|
||||
return s.svc.pg.WithTx(
|
||||
ctx,
|
||||
func(tx pg.Conn) error {
|
||||
file := &coredata.File{}
|
||||
if err := file.LoadByID(ctx, tx, s.svc.scope, fileID); err != nil {
|
||||
return fmt.Errorf("cannot load file: %w", err)
|
||||
}
|
||||
|
||||
downloadURL, err := s.GenerateDocumentExportDownloadURL(ctx, file)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot generate download URL: %w", err)
|
||||
}
|
||||
|
||||
email := coredata.NewEmail(
|
||||
recipientName,
|
||||
recipientEmail,
|
||||
documentExportEmailSubject,
|
||||
fmt.Sprintf(documentExportEmailBody, downloadURL),
|
||||
)
|
||||
|
||||
if err := email.Insert(ctx, tx); err != nil {
|
||||
return fmt.Errorf("cannot insert email: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
func (s *DocumentService) GenerateDocumentExportDownloadURL(
|
||||
ctx context.Context,
|
||||
file *coredata.File,
|
||||
) (string, error) {
|
||||
presignClient := s3.NewPresignClient(s.svc.s3)
|
||||
|
||||
presignedReq, err := presignClient.PresignGetObject(
|
||||
ctx,
|
||||
&s3.GetObjectInput{
|
||||
Bucket: ref.Ref(s.svc.bucket),
|
||||
Key: ref.Ref(file.FileKey),
|
||||
ResponseCacheControl: ref.Ref("max-age=3600, public"),
|
||||
ResponseContentType: ref.Ref(file.MimeType),
|
||||
ResponseContentDisposition: ref.Ref(fmt.Sprintf("attachment; filename=\"%s\"", file.FileName)),
|
||||
},
|
||||
func(opts *s3.PresignOptions) {
|
||||
opts.Expires = documentExportEmailExpiresIn
|
||||
},
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("cannot presign GetObject request: %w", err)
|
||||
}
|
||||
|
||||
return presignedReq.URL, nil
|
||||
}
|
||||
|
||||
func sanitizeFilename(title string) string {
|
||||
if title == "" {
|
||||
return "Untitled"
|
||||
}
|
||||
|
||||
sanitized := invalidFilenameChars.ReplaceAllString(title, "_")
|
||||
|
||||
sanitized = strings.TrimFunc(sanitized, func(r rune) bool {
|
||||
return unicode.IsSpace(r) || r == '.'
|
||||
})
|
||||
|
||||
sanitized = regexp.MustCompile(`[\s_]+`).ReplaceAllString(sanitized, "_")
|
||||
|
||||
if sanitized == "" || sanitized == "_" {
|
||||
sanitized = "Untitled"
|
||||
}
|
||||
|
||||
if len(sanitized) > maxFilenameLength-20 {
|
||||
sanitized = sanitized[:maxFilenameLength-20]
|
||||
sanitized = strings.TrimFunc(sanitized, func(r rune) bool {
|
||||
return r == unicode.ReplacementChar
|
||||
})
|
||||
}
|
||||
|
||||
return sanitized
|
||||
}
|
||||
|
||||
@@ -17,8 +17,10 @@ package probo
|
||||
import (
|
||||
"archive/zip"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/aws/aws-sdk-go-v2/aws"
|
||||
@@ -29,6 +31,7 @@ import (
|
||||
"github.com/getprobo/probo/pkg/page"
|
||||
"github.com/getprobo/probo/pkg/slug"
|
||||
"github.com/getprobo/probo/pkg/soagen"
|
||||
"go.gearno.de/crypto/uuid"
|
||||
"go.gearno.de/kit/pg"
|
||||
"go.gearno.de/x/ref"
|
||||
)
|
||||
@@ -82,8 +85,9 @@ func (s FrameworkService) RequestExport(
|
||||
frameworkID gid.GID,
|
||||
recipientEmail string,
|
||||
recipientName string,
|
||||
) (error, *coredata.FrameworkExport) {
|
||||
frameworkExport := &coredata.FrameworkExport{}
|
||||
) (error, *coredata.ExportJob) {
|
||||
var exportJobID gid.GID
|
||||
exportJob := &coredata.ExportJob{}
|
||||
|
||||
err := s.svc.pg.WithTx(ctx, func(conn pg.Conn) error {
|
||||
framework := &coredata.Framework{}
|
||||
@@ -92,18 +96,28 @@ func (s FrameworkService) RequestExport(
|
||||
}
|
||||
|
||||
now := time.Now()
|
||||
exportJobID = gid.New(s.svc.scope.GetTenantID(), coredata.ExportJobEntityType)
|
||||
|
||||
frameworkExport = &coredata.FrameworkExport{
|
||||
ID: gid.New(framework.ID.TenantID(), coredata.FrameworkExportEntityType),
|
||||
FrameworkID: frameworkID,
|
||||
Status: coredata.FrameworkExportStatusPending,
|
||||
args := coredata.FrameworkExportArguments{
|
||||
FrameworkID: frameworkID,
|
||||
}
|
||||
argsJSON, err := json.Marshal(args)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot marshal framework export arguments: %w", err)
|
||||
}
|
||||
|
||||
exportJob = &coredata.ExportJob{
|
||||
ID: exportJobID,
|
||||
Type: coredata.ExportJobTypeFramework,
|
||||
Arguments: argsJSON,
|
||||
Status: coredata.ExportJobStatusPending,
|
||||
RecipientEmail: recipientEmail,
|
||||
RecipientName: recipientName,
|
||||
CreatedAt: now,
|
||||
}
|
||||
|
||||
if err := frameworkExport.Insert(ctx, conn, s.svc.scope); err != nil {
|
||||
return fmt.Errorf("cannot insert framework export: %w", err)
|
||||
if err := exportJob.Insert(ctx, conn, s.svc.scope); err != nil {
|
||||
return fmt.Errorf("cannot insert export job: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
@@ -113,7 +127,7 @@ func (s FrameworkService) RequestExport(
|
||||
return err, nil
|
||||
}
|
||||
|
||||
return nil, frameworkExport
|
||||
return nil, exportJob
|
||||
}
|
||||
|
||||
func (s FrameworkService) Export(
|
||||
@@ -675,7 +689,7 @@ func (s FrameworkService) StateOfApplicability(ctx context.Context, frameworkID
|
||||
return output, nil
|
||||
}
|
||||
|
||||
func (s FrameworkService) SendFrameworkExportEmail(
|
||||
func (s FrameworkService) SendExportEmail(
|
||||
ctx context.Context,
|
||||
fileID gid.GID,
|
||||
recipientName string,
|
||||
@@ -736,3 +750,99 @@ func (s FrameworkService) GenerateFrameworkExportDownloadURL(
|
||||
|
||||
return presignedReq.URL, nil
|
||||
}
|
||||
|
||||
func (s *FrameworkService) BuildAndUploadExport(ctx context.Context, exportJobID gid.GID) (*coredata.ExportJob, error) {
|
||||
exportJob := &coredata.ExportJob{}
|
||||
err := s.svc.pg.WithTx(
|
||||
ctx,
|
||||
func(tx pg.Conn) error {
|
||||
if err := exportJob.LoadByID(ctx, tx, s.svc.scope, exportJobID); err != nil {
|
||||
return fmt.Errorf("cannot load export job: %w", err)
|
||||
}
|
||||
|
||||
frameworkID, err := exportJob.GetFrameworkID()
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot get framework ID: %w", err)
|
||||
}
|
||||
|
||||
framework := &coredata.Framework{}
|
||||
if err := framework.LoadByID(ctx, tx, s.svc.scope, frameworkID); err != nil {
|
||||
return fmt.Errorf("cannot load framework: %w", err)
|
||||
}
|
||||
|
||||
tempDir := os.TempDir()
|
||||
tempFile, err := os.CreateTemp(tempDir, "probo-framework-export-*.zip")
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot create temp file: %w", err)
|
||||
}
|
||||
defer tempFile.Close()
|
||||
defer os.Remove(tempFile.Name())
|
||||
|
||||
err = s.Export(ctx, frameworkID, tempFile)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot export framework: %w", err)
|
||||
}
|
||||
|
||||
uuid, err := uuid.NewV4()
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot generate uuid: %w", err)
|
||||
}
|
||||
|
||||
if _, err := tempFile.Seek(0, 0); err != nil {
|
||||
return fmt.Errorf("cannot seek temp file: %w", err)
|
||||
}
|
||||
|
||||
fileInfo, err := tempFile.Stat()
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot stat temp file: %w", err)
|
||||
}
|
||||
|
||||
_, err = s.svc.s3.PutObject(
|
||||
ctx,
|
||||
&s3.PutObjectInput{
|
||||
Bucket: ref.Ref(s.svc.bucket),
|
||||
Key: ref.Ref(uuid.String()),
|
||||
Body: tempFile,
|
||||
ContentLength: ref.Ref(fileInfo.Size()),
|
||||
ContentType: ref.Ref("application/zip"),
|
||||
Metadata: map[string]string{
|
||||
"type": "framework-export",
|
||||
"export-job-id": exportJob.ID.String(),
|
||||
},
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot upload file to S3: %w", err)
|
||||
}
|
||||
|
||||
now := time.Now()
|
||||
|
||||
file := coredata.File{
|
||||
ID: gid.New(exportJob.ID.TenantID(), coredata.FileEntityType),
|
||||
BucketName: s.svc.bucket,
|
||||
MimeType: "application/zip",
|
||||
FileName: fmt.Sprintf("Framework Export %s.zip", now.Format("2006-01-02")),
|
||||
FileKey: uuid.String(),
|
||||
FileSize: int(fileInfo.Size()),
|
||||
CreatedAt: now,
|
||||
UpdatedAt: now,
|
||||
}
|
||||
|
||||
if err := file.Insert(ctx, tx, s.svc.scope); err != nil {
|
||||
return fmt.Errorf("cannot insert file: %w", err)
|
||||
}
|
||||
|
||||
exportJob.FileID = &file.ID
|
||||
if err := exportJob.Update(ctx, tx, s.svc.scope); err != nil {
|
||||
return fmt.Errorf("cannot update export job: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return exportJob, nil
|
||||
}
|
||||
|
||||
@@ -17,7 +17,6 @@ package probo
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/aws/aws-sdk-go-v2/service/s3"
|
||||
@@ -28,11 +27,15 @@ 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 ExportService interface {
|
||||
BuildAndUploadExport(ctx context.Context, exportJobID gid.GID) (*coredata.ExportJob, error)
|
||||
SendExportEmail(ctx context.Context, fileID gid.GID, recipientName, recipientEmail string) error
|
||||
}
|
||||
|
||||
type (
|
||||
TrustConfig struct {
|
||||
TokenSecret string
|
||||
@@ -191,170 +194,100 @@ func (s *Service) WithTenant(tenantID gid.TenantID) *TenantService {
|
||||
return tenantService
|
||||
}
|
||||
|
||||
func (s *Service) ExportFrameworkJob(ctx context.Context) error {
|
||||
fe, scope, err := s.lockExport(ctx)
|
||||
func (s *Service) ExportJob(ctx context.Context) error {
|
||||
exportJob, err := s.lockExportJob(ctx)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot lock framework export: %w", err)
|
||||
return fmt.Errorf("cannot lock export job: %w", err)
|
||||
}
|
||||
|
||||
fe, buildErr := s.buildAndUploadExport(ctx, scope, fe)
|
||||
tenantService := s.WithTenant(exportJob.ID.TenantID())
|
||||
|
||||
var exportService ExportService
|
||||
|
||||
switch exportJob.Type {
|
||||
case coredata.ExportJobTypeFramework:
|
||||
exportService = tenantService.Frameworks
|
||||
case coredata.ExportJobTypeDocument:
|
||||
exportService = tenantService.Documents
|
||||
default:
|
||||
unknownTypeErr := fmt.Errorf("unknown export job type: %q", exportJob.Type)
|
||||
if err := s.commitFailedExport(ctx, exportJob, unknownTypeErr); err != nil {
|
||||
return fmt.Errorf("unknown export job type %q, and cannot commit failed export: %w", exportJob.Type, err)
|
||||
}
|
||||
return unknownTypeErr
|
||||
}
|
||||
|
||||
exportJob, buildErr := exportService.BuildAndUploadExport(ctx, exportJob.ID)
|
||||
if buildErr != nil {
|
||||
if err := s.commitFailedExport(ctx, scope, fe); err != nil {
|
||||
if err := s.commitFailedExport(ctx, exportJob, buildErr); err != nil {
|
||||
return fmt.Errorf(
|
||||
"cannot build and upload framework export: %w, and cannot commit failed export: %w",
|
||||
"cannot build and upload %s export: %w, and cannot commit failed export: %w",
|
||||
exportJob.Type,
|
||||
buildErr,
|
||||
err,
|
||||
)
|
||||
}
|
||||
|
||||
return fmt.Errorf("cannot build and upload framework export: %w", buildErr)
|
||||
return fmt.Errorf("cannot build and upload %s export: %w", exportJob.Type, buildErr)
|
||||
}
|
||||
|
||||
tenantService := s.WithTenant(scope.GetTenantID())
|
||||
if emailErr := tenantService.Frameworks.SendFrameworkExportEmail(ctx, *fe.FileID, fe.RecipientName, fe.RecipientEmail); emailErr != nil {
|
||||
if err := s.commitFailedExport(ctx, scope, fe); err != nil {
|
||||
if emailErr := exportService.SendExportEmail(ctx, *exportJob.FileID, exportJob.RecipientName, exportJob.RecipientEmail); emailErr != nil {
|
||||
if err := s.commitFailedExport(ctx, exportJob, emailErr); err != nil {
|
||||
return fmt.Errorf(
|
||||
"cannot send completion email: %w, and cannot commit failed export: %w",
|
||||
emailErr,
|
||||
err,
|
||||
)
|
||||
}
|
||||
|
||||
return fmt.Errorf("cannot send completion email: %w", emailErr)
|
||||
}
|
||||
|
||||
if err := s.commitSuccessfulExport(ctx, scope, fe); err != nil {
|
||||
return fmt.Errorf("cannot commit successful export: %w", err)
|
||||
if err := s.commitSuccessfulExport(ctx, exportJob); err != nil {
|
||||
return fmt.Errorf("cannot commit successful %s export: %w", exportJob.Type, err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Service) lockExport(ctx context.Context) (*coredata.FrameworkExport, coredata.Scoper, error) {
|
||||
fe := &coredata.FrameworkExport{}
|
||||
func (s *Service) lockExportJob(ctx context.Context) (*coredata.ExportJob, error) {
|
||||
exportJob := &coredata.ExportJob{}
|
||||
var scope coredata.Scoper
|
||||
|
||||
err := s.pg.WithTx(ctx,
|
||||
func(tx pg.Conn) error {
|
||||
if err := fe.LoadNextPendingForUpdateSkipLocked(ctx, tx); err != nil {
|
||||
return fmt.Errorf("cannot load next pending framework export: %w", err)
|
||||
if err := exportJob.LoadNextPendingForUpdateSkipLocked(ctx, tx); err != nil {
|
||||
return fmt.Errorf("cannot load next pending export job: %w", err)
|
||||
}
|
||||
|
||||
scope = coredata.NewScope(fe.ID.TenantID())
|
||||
scope = coredata.NewScope(exportJob.ID.TenantID())
|
||||
|
||||
fe.Status = coredata.FrameworkExportStatusProcessing
|
||||
fe.StartedAt = ref.Ref(time.Now())
|
||||
if err := fe.Update(ctx, tx, scope); err != nil {
|
||||
return fmt.Errorf("cannot update framework export: %w", err)
|
||||
exportJob.Status = coredata.ExportJobStatusProcessing
|
||||
exportJob.StartedAt = ref.Ref(time.Now())
|
||||
if err := exportJob.Update(ctx, tx, scope); err != nil {
|
||||
return fmt.Errorf("cannot update %s export job: %w", exportJob.Type, err)
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("cannot lock framework export: %w", err)
|
||||
return nil, fmt.Errorf("cannot lock export job: %w", err)
|
||||
}
|
||||
|
||||
return fe, scope, nil
|
||||
return exportJob, nil
|
||||
}
|
||||
|
||||
func (s *Service) buildAndUploadExport(ctx context.Context, scope coredata.Scoper, fe *coredata.FrameworkExport) (*coredata.FrameworkExport, error) {
|
||||
err := s.pg.WithTx(
|
||||
ctx,
|
||||
func(tx pg.Conn) error {
|
||||
framework := &coredata.Framework{}
|
||||
if err := framework.LoadByID(ctx, tx, scope, fe.FrameworkID); err != nil {
|
||||
return fmt.Errorf("cannot load framework: %w", err)
|
||||
}
|
||||
|
||||
tempDir := os.TempDir()
|
||||
tempFile, err := os.CreateTemp(tempDir, "probo-framework-export-*.zip")
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot create temp file: %w", err)
|
||||
}
|
||||
defer tempFile.Close()
|
||||
defer os.Remove(tempFile.Name())
|
||||
|
||||
tenantService := s.WithTenant(scope.GetTenantID())
|
||||
err = tenantService.Frameworks.Export(ctx, fe.FrameworkID, tempFile)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot export framework: %w", err)
|
||||
}
|
||||
|
||||
uuid, err := uuid.NewV4()
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot generate uuid: %w", err)
|
||||
}
|
||||
|
||||
if _, err := tempFile.Seek(0, 0); err != nil {
|
||||
return fmt.Errorf("cannot seek temp file: %w", err)
|
||||
}
|
||||
|
||||
fileInfo, err := tempFile.Stat()
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot stat temp file: %w", err)
|
||||
}
|
||||
|
||||
_, 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 {
|
||||
return fmt.Errorf("cannot upload file to S3: %w", err)
|
||||
}
|
||||
|
||||
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, now.Format("2006-01-02")),
|
||||
FileKey: uuid.String(),
|
||||
FileSize: int(fileInfo.Size()),
|
||||
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
|
||||
if err := fe.Update(ctx, tx, scope); err != nil {
|
||||
return fmt.Errorf("cannot update framework export: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
return fe, fmt.Errorf("cannot build and upload export: %w", err)
|
||||
}
|
||||
|
||||
return fe, nil
|
||||
}
|
||||
|
||||
func (s *Service) commitFailedExport(ctx context.Context, scope coredata.Scoper, fe *coredata.FrameworkExport) error {
|
||||
fe.CompletedAt = ref.Ref(time.Now())
|
||||
fe.Status = coredata.FrameworkExportStatusFailed
|
||||
func (s *Service) commitFailedExport(ctx context.Context, exportJob *coredata.ExportJob, failureErr error) error {
|
||||
exportJob.CompletedAt = ref.Ref(time.Now())
|
||||
exportJob.Status = coredata.ExportJobStatusFailed
|
||||
errorMsg := failureErr.Error()
|
||||
exportJob.Error = &errorMsg
|
||||
|
||||
return s.pg.WithConn(
|
||||
ctx,
|
||||
func(conn pg.Conn) error {
|
||||
if err := fe.Update(ctx, conn, scope); err != nil {
|
||||
return fmt.Errorf("cannot update framework export: %w", err)
|
||||
scope := coredata.NewScope(exportJob.ID.TenantID())
|
||||
if err := exportJob.Update(ctx, conn, scope); err != nil {
|
||||
return fmt.Errorf("cannot update %s export job: %w", exportJob.Type, err)
|
||||
}
|
||||
|
||||
return nil
|
||||
@@ -362,15 +295,16 @@ func (s *Service) commitFailedExport(ctx context.Context, scope coredata.Scoper,
|
||||
)
|
||||
}
|
||||
|
||||
func (s *Service) commitSuccessfulExport(ctx context.Context, scope coredata.Scoper, fe *coredata.FrameworkExport) error {
|
||||
fe.CompletedAt = ref.Ref(time.Now())
|
||||
fe.Status = coredata.FrameworkExportStatusCompleted
|
||||
func (s *Service) commitSuccessfulExport(ctx context.Context, exportJob *coredata.ExportJob) error {
|
||||
exportJob.CompletedAt = ref.Ref(time.Now())
|
||||
exportJob.Status = coredata.ExportJobStatusCompleted
|
||||
|
||||
return s.pg.WithConn(
|
||||
ctx,
|
||||
func(conn pg.Conn) error {
|
||||
if err := fe.Update(ctx, conn, scope); err != nil {
|
||||
return fmt.Errorf("cannot update framework export: %w", err)
|
||||
scope := coredata.NewScope(exportJob.ID.TenantID())
|
||||
if err := exportJob.Update(ctx, conn, scope); err != nil {
|
||||
return fmt.Errorf("cannot update %s export job: %w", exportJob.Type, err)
|
||||
}
|
||||
|
||||
return nil
|
||||
|
||||
@@ -330,19 +330,19 @@ func (impl *Implm) Run(
|
||||
}
|
||||
}()
|
||||
|
||||
frameworkExporterCtx, stopFrameworkExporter := context.WithCancel(context.Background())
|
||||
exportJobExporterCtx, stopExportJobExporter := context.WithCancel(context.Background())
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
if err := impl.runFrameworkExporter(frameworkExporterCtx, proboService, l.Named("framework-exporter")); err != nil {
|
||||
cancel(fmt.Errorf("framework exporter crashed: %w", err))
|
||||
if err := impl.runExportJob(exportJobExporterCtx, proboService, l.Named("export-job-exporter")); err != nil {
|
||||
cancel(fmt.Errorf("export job exporter crashed: %w", err))
|
||||
}
|
||||
}()
|
||||
|
||||
<-ctx.Done()
|
||||
|
||||
stopMailer()
|
||||
stopFrameworkExporter()
|
||||
stopExportJobExporter()
|
||||
stopApiServer()
|
||||
|
||||
wg.Wait()
|
||||
@@ -352,7 +352,7 @@ func (impl *Implm) Run(
|
||||
return context.Cause(ctx)
|
||||
}
|
||||
|
||||
func (impl *Implm) runFrameworkExporter(
|
||||
func (impl *Implm) runExportJob(
|
||||
ctx context.Context,
|
||||
proboService *probo.Service,
|
||||
l *log.Logger,
|
||||
@@ -362,9 +362,9 @@ LOOP:
|
||||
case <-ctx.Done():
|
||||
return ctx.Err()
|
||||
case <-time.After(30 * time.Second):
|
||||
if err := proboService.ExportFrameworkJob(ctx); err != nil {
|
||||
if !errors.Is(err, coredata.ErrNoFrameworkExportAvailable) {
|
||||
l.ErrorCtx(ctx, "cannot process framework export", log.Error(err))
|
||||
if err := proboService.ExportJob(ctx); err != nil {
|
||||
if !errors.Is(err, coredata.ErrNoExportJobAvailable) {
|
||||
l.ErrorCtx(ctx, "cannot process export job", log.Error(err))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -2643,6 +2643,12 @@ type Mutation {
|
||||
bulkPublishDocumentVersions(
|
||||
input: BulkPublishDocumentVersionsInput!
|
||||
): BulkPublishDocumentVersionsPayload!
|
||||
bulkDeleteDocuments(
|
||||
input: BulkDeleteDocumentsInput!
|
||||
): BulkDeleteDocumentsPayload!
|
||||
bulkExportDocuments(
|
||||
input: BulkExportDocumentsInput!
|
||||
): BulkExportDocumentsPayload!
|
||||
generateDocumentChangelog(
|
||||
input: GenerateDocumentChangelogInput!
|
||||
): GenerateDocumentChangelogPayload!
|
||||
@@ -3856,6 +3862,22 @@ type BulkPublishDocumentVersionsPayload {
|
||||
documentEdges: [DocumentEdge!]!
|
||||
}
|
||||
|
||||
input BulkDeleteDocumentsInput {
|
||||
documentIds: [ID!]!
|
||||
}
|
||||
|
||||
input BulkExportDocumentsInput {
|
||||
documentIds: [ID!]!
|
||||
}
|
||||
|
||||
type BulkDeleteDocumentsPayload {
|
||||
deletedDocumentIds: [ID!]!
|
||||
}
|
||||
|
||||
type BulkExportDocumentsPayload {
|
||||
exportJobId: ID!
|
||||
}
|
||||
|
||||
input PublishDocumentVersionInput {
|
||||
documentId: ID!
|
||||
changelog: String
|
||||
|
||||
@@ -159,6 +159,14 @@ type ComplexityRoot struct {
|
||||
Node func(childComplexity int) int
|
||||
}
|
||||
|
||||
BulkDeleteDocumentsPayload struct {
|
||||
DeletedDocumentIds func(childComplexity int) int
|
||||
}
|
||||
|
||||
BulkExportDocumentsPayload struct {
|
||||
ExportJobID func(childComplexity int) int
|
||||
}
|
||||
|
||||
BulkPublishDocumentVersionsPayload struct {
|
||||
DocumentEdges func(childComplexity int) int
|
||||
DocumentVersionEdges func(childComplexity int) int
|
||||
@@ -714,6 +722,8 @@ type ComplexityRoot struct {
|
||||
Mutation struct {
|
||||
AssessVendor func(childComplexity int, input types.AssessVendorInput) int
|
||||
AssignTask func(childComplexity int, input types.AssignTaskInput) int
|
||||
BulkDeleteDocuments func(childComplexity int, input types.BulkDeleteDocumentsInput) int
|
||||
BulkExportDocuments func(childComplexity int, input types.BulkExportDocumentsInput) int
|
||||
BulkPublishDocumentVersions func(childComplexity int, input types.BulkPublishDocumentVersionsInput) int
|
||||
BulkRequestSignatures func(childComplexity int, input types.BulkRequestSignaturesInput) int
|
||||
CancelSignatureRequest func(childComplexity int, input types.CancelSignatureRequestInput) int
|
||||
@@ -1629,6 +1639,8 @@ type MutationResolver interface {
|
||||
DeleteDocument(ctx context.Context, input types.DeleteDocumentInput) (*types.DeleteDocumentPayload, error)
|
||||
PublishDocumentVersion(ctx context.Context, input types.PublishDocumentVersionInput) (*types.PublishDocumentVersionPayload, error)
|
||||
BulkPublishDocumentVersions(ctx context.Context, input types.BulkPublishDocumentVersionsInput) (*types.BulkPublishDocumentVersionsPayload, error)
|
||||
BulkDeleteDocuments(ctx context.Context, input types.BulkDeleteDocumentsInput) (*types.BulkDeleteDocumentsPayload, error)
|
||||
BulkExportDocuments(ctx context.Context, input types.BulkExportDocumentsInput) (*types.BulkExportDocumentsPayload, error)
|
||||
GenerateDocumentChangelog(ctx context.Context, input types.GenerateDocumentChangelogInput) (*types.GenerateDocumentChangelogPayload, error)
|
||||
CreateDraftDocumentVersion(ctx context.Context, input types.CreateDraftDocumentVersionInput) (*types.CreateDraftDocumentVersionPayload, error)
|
||||
DeleteDraftDocumentVersion(ctx context.Context, input types.DeleteDraftDocumentVersionInput) (*types.DeleteDraftDocumentVersionPayload, error)
|
||||
@@ -2090,6 +2102,20 @@ func (e *executableSchema) Complexity(ctx context.Context, typeName, field strin
|
||||
|
||||
return e.complexity.AuditEdge.Node(childComplexity), true
|
||||
|
||||
case "BulkDeleteDocumentsPayload.deletedDocumentIds":
|
||||
if e.complexity.BulkDeleteDocumentsPayload.DeletedDocumentIds == nil {
|
||||
break
|
||||
}
|
||||
|
||||
return e.complexity.BulkDeleteDocumentsPayload.DeletedDocumentIds(childComplexity), true
|
||||
|
||||
case "BulkExportDocumentsPayload.exportJobId":
|
||||
if e.complexity.BulkExportDocumentsPayload.ExportJobID == nil {
|
||||
break
|
||||
}
|
||||
|
||||
return e.complexity.BulkExportDocumentsPayload.ExportJobID(childComplexity), true
|
||||
|
||||
case "BulkPublishDocumentVersionsPayload.documentEdges":
|
||||
if e.complexity.BulkPublishDocumentVersionsPayload.DocumentEdges == nil {
|
||||
break
|
||||
@@ -3859,6 +3885,30 @@ func (e *executableSchema) Complexity(ctx context.Context, typeName, field strin
|
||||
|
||||
return e.complexity.Mutation.AssignTask(childComplexity, args["input"].(types.AssignTaskInput)), true
|
||||
|
||||
case "Mutation.bulkDeleteDocuments":
|
||||
if e.complexity.Mutation.BulkDeleteDocuments == nil {
|
||||
break
|
||||
}
|
||||
|
||||
args, err := ec.field_Mutation_bulkDeleteDocuments_args(ctx, rawArgs)
|
||||
if err != nil {
|
||||
return 0, false
|
||||
}
|
||||
|
||||
return e.complexity.Mutation.BulkDeleteDocuments(childComplexity, args["input"].(types.BulkDeleteDocumentsInput)), true
|
||||
|
||||
case "Mutation.bulkExportDocuments":
|
||||
if e.complexity.Mutation.BulkExportDocuments == nil {
|
||||
break
|
||||
}
|
||||
|
||||
args, err := ec.field_Mutation_bulkExportDocuments_args(ctx, rawArgs)
|
||||
if err != nil {
|
||||
return 0, false
|
||||
}
|
||||
|
||||
return e.complexity.Mutation.BulkExportDocuments(childComplexity, args["input"].(types.BulkExportDocumentsInput)), true
|
||||
|
||||
case "Mutation.bulkPublishDocumentVersions":
|
||||
if e.complexity.Mutation.BulkPublishDocumentVersions == nil {
|
||||
break
|
||||
@@ -7834,6 +7884,8 @@ func (e *executableSchema) Exec(ctx context.Context) graphql.ResponseHandler {
|
||||
ec.unmarshalInputAssetOrder,
|
||||
ec.unmarshalInputAssignTaskInput,
|
||||
ec.unmarshalInputAuditOrder,
|
||||
ec.unmarshalInputBulkDeleteDocumentsInput,
|
||||
ec.unmarshalInputBulkExportDocumentsInput,
|
||||
ec.unmarshalInputBulkPublishDocumentVersionsInput,
|
||||
ec.unmarshalInputBulkRequestSignaturesInput,
|
||||
ec.unmarshalInputCancelSignatureRequestInput,
|
||||
@@ -10724,6 +10776,12 @@ type Mutation {
|
||||
bulkPublishDocumentVersions(
|
||||
input: BulkPublishDocumentVersionsInput!
|
||||
): BulkPublishDocumentVersionsPayload!
|
||||
bulkDeleteDocuments(
|
||||
input: BulkDeleteDocumentsInput!
|
||||
): BulkDeleteDocumentsPayload!
|
||||
bulkExportDocuments(
|
||||
input: BulkExportDocumentsInput!
|
||||
): BulkExportDocumentsPayload!
|
||||
generateDocumentChangelog(
|
||||
input: GenerateDocumentChangelogInput!
|
||||
): GenerateDocumentChangelogPayload!
|
||||
@@ -11937,6 +11995,22 @@ type BulkPublishDocumentVersionsPayload {
|
||||
documentEdges: [DocumentEdge!]!
|
||||
}
|
||||
|
||||
input BulkDeleteDocumentsInput {
|
||||
documentIds: [ID!]!
|
||||
}
|
||||
|
||||
input BulkExportDocumentsInput {
|
||||
documentIds: [ID!]!
|
||||
}
|
||||
|
||||
type BulkDeleteDocumentsPayload {
|
||||
deletedDocumentIds: [ID!]!
|
||||
}
|
||||
|
||||
type BulkExportDocumentsPayload {
|
||||
exportJobId: ID!
|
||||
}
|
||||
|
||||
input PublishDocumentVersionInput {
|
||||
documentId: ID!
|
||||
changelog: String
|
||||
@@ -13861,6 +13935,52 @@ func (ec *executionContext) field_Mutation_assignTask_argsInput(
|
||||
return zeroVal, nil
|
||||
}
|
||||
|
||||
func (ec *executionContext) field_Mutation_bulkDeleteDocuments_args(ctx context.Context, rawArgs map[string]any) (map[string]any, error) {
|
||||
var err error
|
||||
args := map[string]any{}
|
||||
arg0, err := ec.field_Mutation_bulkDeleteDocuments_argsInput(ctx, rawArgs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
args["input"] = arg0
|
||||
return args, nil
|
||||
}
|
||||
func (ec *executionContext) field_Mutation_bulkDeleteDocuments_argsInput(
|
||||
ctx context.Context,
|
||||
rawArgs map[string]any,
|
||||
) (types.BulkDeleteDocumentsInput, error) {
|
||||
ctx = graphql.WithPathContext(ctx, graphql.NewPathWithField("input"))
|
||||
if tmp, ok := rawArgs["input"]; ok {
|
||||
return ec.unmarshalNBulkDeleteDocumentsInput2githubᚗcomᚋgetproboᚋproboᚋpkgᚋserverᚋapiᚋconsoleᚋv1ᚋtypesᚐBulkDeleteDocumentsInput(ctx, tmp)
|
||||
}
|
||||
|
||||
var zeroVal types.BulkDeleteDocumentsInput
|
||||
return zeroVal, nil
|
||||
}
|
||||
|
||||
func (ec *executionContext) field_Mutation_bulkExportDocuments_args(ctx context.Context, rawArgs map[string]any) (map[string]any, error) {
|
||||
var err error
|
||||
args := map[string]any{}
|
||||
arg0, err := ec.field_Mutation_bulkExportDocuments_argsInput(ctx, rawArgs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
args["input"] = arg0
|
||||
return args, nil
|
||||
}
|
||||
func (ec *executionContext) field_Mutation_bulkExportDocuments_argsInput(
|
||||
ctx context.Context,
|
||||
rawArgs map[string]any,
|
||||
) (types.BulkExportDocumentsInput, error) {
|
||||
ctx = graphql.WithPathContext(ctx, graphql.NewPathWithField("input"))
|
||||
if tmp, ok := rawArgs["input"]; ok {
|
||||
return ec.unmarshalNBulkExportDocumentsInput2githubᚗcomᚋgetproboᚋproboᚋpkgᚋserverᚋapiᚋconsoleᚋv1ᚋtypesᚐBulkExportDocumentsInput(ctx, tmp)
|
||||
}
|
||||
|
||||
var zeroVal types.BulkExportDocumentsInput
|
||||
return zeroVal, nil
|
||||
}
|
||||
|
||||
func (ec *executionContext) field_Mutation_bulkPublishDocumentVersions_args(ctx context.Context, rawArgs map[string]any) (map[string]any, error) {
|
||||
var err error
|
||||
args := map[string]any{}
|
||||
@@ -21578,6 +21698,94 @@ func (ec *executionContext) fieldContext_AuditEdge_node(_ context.Context, field
|
||||
return fc, nil
|
||||
}
|
||||
|
||||
func (ec *executionContext) _BulkDeleteDocumentsPayload_deletedDocumentIds(ctx context.Context, field graphql.CollectedField, obj *types.BulkDeleteDocumentsPayload) (ret graphql.Marshaler) {
|
||||
fc, err := ec.fieldContext_BulkDeleteDocumentsPayload_deletedDocumentIds(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.DeletedDocumentIds, 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.marshalNID2ᚕgithubᚗcomᚋgetproboᚋproboᚋpkgᚋgidᚐGIDᚄ(ctx, field.Selections, res)
|
||||
}
|
||||
|
||||
func (ec *executionContext) fieldContext_BulkDeleteDocumentsPayload_deletedDocumentIds(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) {
|
||||
fc = &graphql.FieldContext{
|
||||
Object: "BulkDeleteDocumentsPayload",
|
||||
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) _BulkExportDocumentsPayload_exportJobId(ctx context.Context, field graphql.CollectedField, obj *types.BulkExportDocumentsPayload) (ret graphql.Marshaler) {
|
||||
fc, err := ec.fieldContext_BulkExportDocumentsPayload_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_BulkExportDocumentsPayload_exportJobId(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) {
|
||||
fc = &graphql.FieldContext{
|
||||
Object: "BulkExportDocumentsPayload",
|
||||
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) _BulkPublishDocumentVersionsPayload_documentVersionEdges(ctx context.Context, field graphql.CollectedField, obj *types.BulkPublishDocumentVersionsPayload) (ret graphql.Marshaler) {
|
||||
fc, err := ec.fieldContext_BulkPublishDocumentVersionsPayload_documentVersionEdges(ctx, field)
|
||||
if err != nil {
|
||||
@@ -37958,6 +38166,124 @@ func (ec *executionContext) fieldContext_Mutation_bulkPublishDocumentVersions(ct
|
||||
return fc, nil
|
||||
}
|
||||
|
||||
func (ec *executionContext) _Mutation_bulkDeleteDocuments(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) {
|
||||
fc, err := ec.fieldContext_Mutation_bulkDeleteDocuments(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().BulkDeleteDocuments(rctx, fc.Args["input"].(types.BulkDeleteDocumentsInput))
|
||||
})
|
||||
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.BulkDeleteDocumentsPayload)
|
||||
fc.Result = res
|
||||
return ec.marshalNBulkDeleteDocumentsPayload2ᚖgithubᚗcomᚋgetproboᚋproboᚋpkgᚋserverᚋapiᚋconsoleᚋv1ᚋtypesᚐBulkDeleteDocumentsPayload(ctx, field.Selections, res)
|
||||
}
|
||||
|
||||
func (ec *executionContext) fieldContext_Mutation_bulkDeleteDocuments(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 "deletedDocumentIds":
|
||||
return ec.fieldContext_BulkDeleteDocumentsPayload_deletedDocumentIds(ctx, field)
|
||||
}
|
||||
return nil, fmt.Errorf("no field named %q was found under type BulkDeleteDocumentsPayload", 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_bulkDeleteDocuments_args(ctx, field.ArgumentMap(ec.Variables)); err != nil {
|
||||
ec.Error(ctx, err)
|
||||
return fc, err
|
||||
}
|
||||
return fc, nil
|
||||
}
|
||||
|
||||
func (ec *executionContext) _Mutation_bulkExportDocuments(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) {
|
||||
fc, err := ec.fieldContext_Mutation_bulkExportDocuments(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().BulkExportDocuments(rctx, fc.Args["input"].(types.BulkExportDocumentsInput))
|
||||
})
|
||||
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.BulkExportDocumentsPayload)
|
||||
fc.Result = res
|
||||
return ec.marshalNBulkExportDocumentsPayload2ᚖgithubᚗcomᚋgetproboᚋproboᚋpkgᚋserverᚋapiᚋconsoleᚋv1ᚋtypesᚐBulkExportDocumentsPayload(ctx, field.Selections, res)
|
||||
}
|
||||
|
||||
func (ec *executionContext) fieldContext_Mutation_bulkExportDocuments(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_BulkExportDocumentsPayload_exportJobId(ctx, field)
|
||||
}
|
||||
return nil, fmt.Errorf("no field named %q was found under type BulkExportDocumentsPayload", 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_bulkExportDocuments_args(ctx, field.ArgumentMap(ec.Variables)); err != nil {
|
||||
ec.Error(ctx, err)
|
||||
return fc, err
|
||||
}
|
||||
return fc, nil
|
||||
}
|
||||
|
||||
func (ec *executionContext) _Mutation_generateDocumentChangelog(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) {
|
||||
fc, err := ec.fieldContext_Mutation_generateDocumentChangelog(ctx, field)
|
||||
if err != nil {
|
||||
@@ -61021,6 +61347,60 @@ func (ec *executionContext) unmarshalInputAuditOrder(ctx context.Context, obj an
|
||||
return it, nil
|
||||
}
|
||||
|
||||
func (ec *executionContext) unmarshalInputBulkDeleteDocumentsInput(ctx context.Context, obj any) (types.BulkDeleteDocumentsInput, error) {
|
||||
var it types.BulkDeleteDocumentsInput
|
||||
asMap := map[string]any{}
|
||||
for k, v := range obj.(map[string]any) {
|
||||
asMap[k] = v
|
||||
}
|
||||
|
||||
fieldsInOrder := [...]string{"documentIds"}
|
||||
for _, k := range fieldsInOrder {
|
||||
v, ok := asMap[k]
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
switch k {
|
||||
case "documentIds":
|
||||
ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("documentIds"))
|
||||
data, err := ec.unmarshalNID2ᚕgithubᚗcomᚋgetproboᚋproboᚋpkgᚋgidᚐGIDᚄ(ctx, v)
|
||||
if err != nil {
|
||||
return it, err
|
||||
}
|
||||
it.DocumentIds = data
|
||||
}
|
||||
}
|
||||
|
||||
return it, nil
|
||||
}
|
||||
|
||||
func (ec *executionContext) unmarshalInputBulkExportDocumentsInput(ctx context.Context, obj any) (types.BulkExportDocumentsInput, error) {
|
||||
var it types.BulkExportDocumentsInput
|
||||
asMap := map[string]any{}
|
||||
for k, v := range obj.(map[string]any) {
|
||||
asMap[k] = v
|
||||
}
|
||||
|
||||
fieldsInOrder := [...]string{"documentIds"}
|
||||
for _, k := range fieldsInOrder {
|
||||
v, ok := asMap[k]
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
switch k {
|
||||
case "documentIds":
|
||||
ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("documentIds"))
|
||||
data, err := ec.unmarshalNID2ᚕgithubᚗcomᚋgetproboᚋproboᚋpkgᚋgidᚐGIDᚄ(ctx, v)
|
||||
if err != nil {
|
||||
return it, err
|
||||
}
|
||||
it.DocumentIds = data
|
||||
}
|
||||
}
|
||||
|
||||
return it, nil
|
||||
}
|
||||
|
||||
func (ec *executionContext) unmarshalInputBulkPublishDocumentVersionsInput(ctx context.Context, obj any) (types.BulkPublishDocumentVersionsInput, error) {
|
||||
var it types.BulkPublishDocumentVersionsInput
|
||||
asMap := map[string]any{}
|
||||
@@ -68443,6 +68823,84 @@ func (ec *executionContext) _AuditEdge(ctx context.Context, sel ast.SelectionSet
|
||||
return out
|
||||
}
|
||||
|
||||
var bulkDeleteDocumentsPayloadImplementors = []string{"BulkDeleteDocumentsPayload"}
|
||||
|
||||
func (ec *executionContext) _BulkDeleteDocumentsPayload(ctx context.Context, sel ast.SelectionSet, obj *types.BulkDeleteDocumentsPayload) graphql.Marshaler {
|
||||
fields := graphql.CollectFields(ec.OperationContext, sel, bulkDeleteDocumentsPayloadImplementors)
|
||||
|
||||
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("BulkDeleteDocumentsPayload")
|
||||
case "deletedDocumentIds":
|
||||
out.Values[i] = ec._BulkDeleteDocumentsPayload_deletedDocumentIds(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 bulkExportDocumentsPayloadImplementors = []string{"BulkExportDocumentsPayload"}
|
||||
|
||||
func (ec *executionContext) _BulkExportDocumentsPayload(ctx context.Context, sel ast.SelectionSet, obj *types.BulkExportDocumentsPayload) graphql.Marshaler {
|
||||
fields := graphql.CollectFields(ec.OperationContext, sel, bulkExportDocumentsPayloadImplementors)
|
||||
|
||||
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("BulkExportDocumentsPayload")
|
||||
case "exportJobId":
|
||||
out.Values[i] = ec._BulkExportDocumentsPayload_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 bulkPublishDocumentVersionsPayloadImplementors = []string{"BulkPublishDocumentVersionsPayload"}
|
||||
|
||||
func (ec *executionContext) _BulkPublishDocumentVersionsPayload(ctx context.Context, sel ast.SelectionSet, obj *types.BulkPublishDocumentVersionsPayload) graphql.Marshaler {
|
||||
@@ -74828,6 +75286,20 @@ func (ec *executionContext) _Mutation(ctx context.Context, sel ast.SelectionSet)
|
||||
if out.Values[i] == graphql.Null {
|
||||
out.Invalids++
|
||||
}
|
||||
case "bulkDeleteDocuments":
|
||||
out.Values[i] = ec.OperationContext.RootResolverMiddleware(innerCtx, func(ctx context.Context) (res graphql.Marshaler) {
|
||||
return ec._Mutation_bulkDeleteDocuments(ctx, field)
|
||||
})
|
||||
if out.Values[i] == graphql.Null {
|
||||
out.Invalids++
|
||||
}
|
||||
case "bulkExportDocuments":
|
||||
out.Values[i] = ec.OperationContext.RootResolverMiddleware(innerCtx, func(ctx context.Context) (res graphql.Marshaler) {
|
||||
return ec._Mutation_bulkExportDocuments(ctx, field)
|
||||
})
|
||||
if out.Values[i] == graphql.Null {
|
||||
out.Invalids++
|
||||
}
|
||||
case "generateDocumentChangelog":
|
||||
out.Values[i] = ec.OperationContext.RootResolverMiddleware(innerCtx, func(ctx context.Context) (res graphql.Marshaler) {
|
||||
return ec._Mutation_generateDocumentChangelog(ctx, field)
|
||||
@@ -82656,6 +83128,44 @@ func (ec *executionContext) marshalNBoolean2bool(ctx context.Context, sel ast.Se
|
||||
return res
|
||||
}
|
||||
|
||||
func (ec *executionContext) unmarshalNBulkDeleteDocumentsInput2githubᚗcomᚋgetproboᚋproboᚋpkgᚋserverᚋapiᚋconsoleᚋv1ᚋtypesᚐBulkDeleteDocumentsInput(ctx context.Context, v any) (types.BulkDeleteDocumentsInput, error) {
|
||||
res, err := ec.unmarshalInputBulkDeleteDocumentsInput(ctx, v)
|
||||
return res, graphql.ErrorOnPath(ctx, err)
|
||||
}
|
||||
|
||||
func (ec *executionContext) marshalNBulkDeleteDocumentsPayload2githubᚗcomᚋgetproboᚋproboᚋpkgᚋserverᚋapiᚋconsoleᚋv1ᚋtypesᚐBulkDeleteDocumentsPayload(ctx context.Context, sel ast.SelectionSet, v types.BulkDeleteDocumentsPayload) graphql.Marshaler {
|
||||
return ec._BulkDeleteDocumentsPayload(ctx, sel, &v)
|
||||
}
|
||||
|
||||
func (ec *executionContext) marshalNBulkDeleteDocumentsPayload2ᚖgithubᚗcomᚋgetproboᚋproboᚋpkgᚋserverᚋapiᚋconsoleᚋv1ᚋtypesᚐBulkDeleteDocumentsPayload(ctx context.Context, sel ast.SelectionSet, v *types.BulkDeleteDocumentsPayload) 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._BulkDeleteDocumentsPayload(ctx, sel, v)
|
||||
}
|
||||
|
||||
func (ec *executionContext) unmarshalNBulkExportDocumentsInput2githubᚗcomᚋgetproboᚋproboᚋpkgᚋserverᚋapiᚋconsoleᚋv1ᚋtypesᚐBulkExportDocumentsInput(ctx context.Context, v any) (types.BulkExportDocumentsInput, error) {
|
||||
res, err := ec.unmarshalInputBulkExportDocumentsInput(ctx, v)
|
||||
return res, graphql.ErrorOnPath(ctx, err)
|
||||
}
|
||||
|
||||
func (ec *executionContext) marshalNBulkExportDocumentsPayload2githubᚗcomᚋgetproboᚋproboᚋpkgᚋserverᚋapiᚋconsoleᚋv1ᚋtypesᚐBulkExportDocumentsPayload(ctx context.Context, sel ast.SelectionSet, v types.BulkExportDocumentsPayload) graphql.Marshaler {
|
||||
return ec._BulkExportDocumentsPayload(ctx, sel, &v)
|
||||
}
|
||||
|
||||
func (ec *executionContext) marshalNBulkExportDocumentsPayload2ᚖgithubᚗcomᚋgetproboᚋproboᚋpkgᚋserverᚋapiᚋconsoleᚋv1ᚋtypesᚐBulkExportDocumentsPayload(ctx context.Context, sel ast.SelectionSet, v *types.BulkExportDocumentsPayload) 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._BulkExportDocumentsPayload(ctx, sel, v)
|
||||
}
|
||||
|
||||
func (ec *executionContext) unmarshalNBulkPublishDocumentVersionsInput2githubᚗcomᚋgetproboᚋproboᚋpkgᚋserverᚋapiᚋconsoleᚋv1ᚋtypesᚐBulkPublishDocumentVersionsInput(ctx context.Context, v any) (types.BulkPublishDocumentVersionsInput, error) {
|
||||
res, err := ec.unmarshalInputBulkPublishDocumentVersionsInput(ctx, v)
|
||||
return res, graphql.ErrorOnPath(ctx, err)
|
||||
|
||||
@@ -85,6 +85,22 @@ type AuditEdge struct {
|
||||
Node *Audit `json:"node"`
|
||||
}
|
||||
|
||||
type BulkDeleteDocumentsInput struct {
|
||||
DocumentIds []gid.GID `json:"documentIds"`
|
||||
}
|
||||
|
||||
type BulkDeleteDocumentsPayload struct {
|
||||
DeletedDocumentIds []gid.GID `json:"deletedDocumentIds"`
|
||||
}
|
||||
|
||||
type BulkExportDocumentsInput struct {
|
||||
DocumentIds []gid.GID `json:"documentIds"`
|
||||
}
|
||||
|
||||
type BulkExportDocumentsPayload struct {
|
||||
ExportJobID gid.GID `json:"exportJobId"`
|
||||
}
|
||||
|
||||
type BulkPublishDocumentVersionsInput struct {
|
||||
DocumentIds []gid.GID `json:"documentIds"`
|
||||
Changelog string `json:"changelog"`
|
||||
|
||||
@@ -2543,6 +2543,49 @@ func (r *mutationResolver) BulkPublishDocumentVersions(ctx context.Context, inpu
|
||||
}, nil
|
||||
}
|
||||
|
||||
// BulkDeleteDocuments is the resolver for the bulkDeleteDocuments field.
|
||||
func (r *mutationResolver) BulkDeleteDocuments(ctx context.Context, input types.BulkDeleteDocumentsInput) (*types.BulkDeleteDocumentsPayload, error) {
|
||||
if len(input.DocumentIds) == 0 {
|
||||
return &types.BulkDeleteDocumentsPayload{
|
||||
DeletedDocumentIds: []gid.GID{},
|
||||
}, nil
|
||||
}
|
||||
|
||||
prb := r.ProboService(ctx, input.DocumentIds[0].TenantID())
|
||||
|
||||
err := prb.Documents.BulkSoftDelete(ctx, input.DocumentIds)
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("cannot bulk delete documents: %w", err))
|
||||
}
|
||||
|
||||
return &types.BulkDeleteDocumentsPayload{
|
||||
DeletedDocumentIds: input.DocumentIds,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// BulkExportDocuments is the resolver for the bulkExportDocuments field.
|
||||
func (r *mutationResolver) BulkExportDocuments(ctx context.Context, input types.BulkExportDocumentsInput) (*types.BulkExportDocumentsPayload, error) {
|
||||
if len(input.DocumentIds) == 0 {
|
||||
panic(fmt.Errorf("no document ids provided"))
|
||||
}
|
||||
|
||||
prb := r.ProboService(ctx, input.DocumentIds[0].TenantID())
|
||||
|
||||
user := UserFromContext(ctx)
|
||||
if user == nil {
|
||||
panic(fmt.Errorf("user not found"))
|
||||
}
|
||||
|
||||
documentExport, err := prb.Documents.RequestExport(ctx, input.DocumentIds, user.EmailAddress, user.FullName)
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("cannot request document export: %w", err))
|
||||
}
|
||||
|
||||
return &types.BulkExportDocumentsPayload{
|
||||
ExportJobID: documentExport.ID,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// GenerateDocumentChangelog is the resolver for the generateDocumentChangelog field.
|
||||
func (r *mutationResolver) GenerateDocumentChangelog(ctx context.Context, input types.GenerateDocumentChangelogInput) (*types.GenerateDocumentChangelogPayload, error) {
|
||||
prb := r.ProboService(ctx, input.DocumentID.TenantID())
|
||||
|
||||
Reference in New Issue
Block a user