Add trust center references

Signed-off-by: Sacha Al Himdani <sacha@getprobo.com>
This commit is contained in:
Sacha Al Himdani
2025-09-19 17:26:17 +02:00
parent 8b7b1cadf7
commit c56563a850
33 changed files with 6187 additions and 56 deletions

View File

@@ -56,4 +56,5 @@ const (
ContinualImprovementEntityType
ProcessingActivityEntityType
ExportJobEntityType
TrustCenterReferenceEntityType
)

View File

@@ -0,0 +1,13 @@
CREATE TABLE trust_center_references (
id TEXT PRIMARY KEY,
tenant_id TEXT NOT NULL,
trust_center_id TEXT NOT NULL REFERENCES trust_centers(id)
ON UPDATE CASCADE ON DELETE CASCADE,
name TEXT NOT NULL,
description TEXT NOT NULL,
website_url TEXT NOT NULL,
logo_file_id TEXT NOT NULL REFERENCES files(id)
ON UPDATE CASCADE ON DELETE RESTRICT,
created_at TIMESTAMP WITH TIME ZONE NOT NULL,
updated_at TIMESTAMP WITH TIME ZONE NOT NULL
);

View File

@@ -0,0 +1,304 @@
// Copyright (c) 2025 Probo Inc <hello@getprobo.com>.
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies.
//
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
package coredata
import (
"context"
"fmt"
"maps"
"time"
"github.com/getprobo/probo/pkg/gid"
"github.com/getprobo/probo/pkg/page"
"github.com/jackc/pgx/v5"
"go.gearno.de/kit/pg"
)
type (
TrustCenterReference struct {
ID gid.GID `db:"id"`
TrustCenterID gid.GID `db:"trust_center_id"`
Name string `db:"name"`
Description string `db:"description"`
WebsiteURL string `db:"website_url"`
LogoFileID gid.GID `db:"logo_file_id"`
CreatedAt time.Time `db:"created_at"`
UpdatedAt time.Time `db:"updated_at"`
}
TrustCenterReferences []*TrustCenterReference
)
func (t TrustCenterReference) CursorKey(orderBy TrustCenterReferenceOrderField) page.CursorKey {
switch orderBy {
case TrustCenterReferenceOrderFieldName:
return page.NewCursorKey(t.ID, t.Name)
case TrustCenterReferenceOrderFieldCreatedAt:
return page.NewCursorKey(t.ID, t.CreatedAt)
case TrustCenterReferenceOrderFieldUpdatedAt:
return page.NewCursorKey(t.ID, t.UpdatedAt)
}
panic(fmt.Sprintf("unsupported order by: %s", orderBy))
}
func (t *TrustCenterReference) LoadByID(
ctx context.Context,
conn pg.Conn,
scope Scoper,
trustCenterReferenceID gid.GID,
) error {
q := `
SELECT
id,
trust_center_id,
name,
description,
website_url,
logo_file_id,
created_at,
updated_at
FROM
trust_center_references
WHERE
%s
AND id = @trust_center_reference_id
LIMIT 1;
`
q = fmt.Sprintf(q, scope.SQLFragment())
args := pgx.StrictNamedArgs{"trust_center_reference_id": trustCenterReferenceID}
maps.Copy(args, scope.SQLArguments())
rows, err := conn.Query(ctx, q, args)
if err != nil {
return fmt.Errorf("cannot query trust_center_references: %w", err)
}
reference, err := pgx.CollectExactlyOneRow(rows, pgx.RowToStructByName[TrustCenterReference])
if err != nil {
return fmt.Errorf("cannot collect trust center reference: %w", err)
}
*t = reference
return nil
}
func (t TrustCenterReference) Insert(
ctx context.Context,
conn pg.Conn,
scope Scoper,
) error {
q := `
INSERT INTO
trust_center_references (
tenant_id,
id,
trust_center_id,
name,
description,
website_url,
logo_file_id,
created_at,
updated_at
)
VALUES (
@tenant_id,
@id,
@trust_center_id,
@name,
@description,
@website_url,
@logo_file_id,
@created_at,
@updated_at
);
`
args := pgx.StrictNamedArgs{
"tenant_id": scope.GetTenantID(),
"id": t.ID,
"trust_center_id": t.TrustCenterID,
"name": t.Name,
"description": t.Description,
"website_url": t.WebsiteURL,
"logo_file_id": t.LogoFileID,
"created_at": t.CreatedAt,
"updated_at": t.UpdatedAt,
}
_, err := conn.Exec(ctx, q, args)
if err != nil {
return fmt.Errorf("cannot insert trust center reference: %w", err)
}
return nil
}
func (t *TrustCenterReference) Update(
ctx context.Context,
conn pg.Conn,
scope Scoper,
) error {
q := `
UPDATE trust_center_references
SET
name = @name,
description = @description,
website_url = @website_url,
logo_file_id = @logo_file_id,
updated_at = @updated_at
WHERE
%s
AND id = @id
RETURNING
id,
trust_center_id,
name,
description,
website_url,
logo_file_id,
created_at,
updated_at
`
q = fmt.Sprintf(q, scope.SQLFragment())
args := pgx.StrictNamedArgs{
"id": t.ID,
"name": t.Name,
"description": t.Description,
"website_url": t.WebsiteURL,
"logo_file_id": t.LogoFileID,
"updated_at": t.UpdatedAt,
}
maps.Copy(args, scope.SQLArguments())
rows, err := conn.Query(ctx, q, args)
if err != nil {
return fmt.Errorf("cannot update trust center reference: %w", err)
}
reference, err := pgx.CollectExactlyOneRow(rows, pgx.RowToStructByName[TrustCenterReference])
if err != nil {
return fmt.Errorf("cannot collect updated trust center reference: %w", err)
}
*t = reference
return nil
}
func (t *TrustCenterReference) Delete(
ctx context.Context,
conn pg.Conn,
scope Scoper,
) error {
q := `
DELETE FROM
trust_center_references
WHERE
%s
AND id = @id
`
q = fmt.Sprintf(q, scope.SQLFragment())
args := pgx.StrictNamedArgs{"id": t.ID}
maps.Copy(args, scope.SQLArguments())
_, err := conn.Exec(ctx, q, args)
if err != nil {
return fmt.Errorf("cannot delete trust center reference: %w", err)
}
return nil
}
func (t *TrustCenterReferences) LoadByTrustCenterID(
ctx context.Context,
conn pg.Conn,
scope Scoper,
trustCenterID gid.GID,
cursor *page.Cursor[TrustCenterReferenceOrderField],
) error {
q := `
SELECT
id,
trust_center_id,
name,
description,
website_url,
logo_file_id,
created_at,
updated_at
FROM
trust_center_references
WHERE
%s
AND trust_center_id = @trust_center_id
AND %s
`
q = fmt.Sprintf(q, scope.SQLFragment(), cursor.SQLFragment())
args := pgx.StrictNamedArgs{"trust_center_id": trustCenterID}
maps.Copy(args, scope.SQLArguments())
maps.Copy(args, cursor.SQLArguments())
rows, err := conn.Query(ctx, q, args)
if err != nil {
return fmt.Errorf("cannot query trust_center_references: %w", err)
}
references, err := pgx.CollectRows(rows, pgx.RowToAddrOfStructByName[TrustCenterReference])
if err != nil {
return fmt.Errorf("cannot collect trust center references: %w", err)
}
*t = references
return nil
}
func (t *TrustCenterReferences) CountByTrustCenterID(
ctx context.Context,
conn pg.Conn,
scope Scoper,
trustCenterID gid.GID,
) (int, error) {
q := `
SELECT
COUNT(*)
FROM
trust_center_references
WHERE
%s
AND trust_center_id = @trust_center_id
`
q = fmt.Sprintf(q, scope.SQLFragment())
args := pgx.StrictNamedArgs{"trust_center_id": trustCenterID}
maps.Copy(args, scope.SQLArguments())
var count int
err := conn.QueryRow(ctx, q, args).Scan(&count)
if err != nil {
return 0, fmt.Errorf("cannot count trust center references: %w", err)
}
return count, nil
}

View File

@@ -0,0 +1,51 @@
// 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 (
TrustCenterReferenceOrderField string
)
const (
TrustCenterReferenceOrderFieldName TrustCenterReferenceOrderField = "NAME"
TrustCenterReferenceOrderFieldCreatedAt TrustCenterReferenceOrderField = "CREATED_AT"
TrustCenterReferenceOrderFieldUpdatedAt TrustCenterReferenceOrderField = "UPDATED_AT"
)
func (p TrustCenterReferenceOrderField) Column() string {
switch p {
case TrustCenterReferenceOrderFieldName:
return "name"
case TrustCenterReferenceOrderFieldCreatedAt:
return "created_at"
case TrustCenterReferenceOrderFieldUpdatedAt:
return "updated_at"
default:
return string(p)
}
}
func (p TrustCenterReferenceOrderField) String() string {
return string(p)
}
func (p TrustCenterReferenceOrderField) MarshalText() ([]byte, error) {
return []byte(p.String()), nil
}
func (p *TrustCenterReferenceOrderField) UnmarshalText(text []byte) error {
*p = TrustCenterReferenceOrderField(text)
return nil
}

View File

@@ -88,6 +88,7 @@ type (
Reports *ReportService
TrustCenters *TrustCenterService
TrustCenterAccesses *TrustCenterAccessService
TrustCenterReferences *TrustCenterReferenceService
Nonconformities *NonconformityService
Obligations *ObligationService
Snapshots *SnapshotService
@@ -186,6 +187,7 @@ func (s *Service) WithTenant(tenantID gid.TenantID) *TenantService {
tenantService.Reports = &ReportService{svc: tenantService}
tenantService.TrustCenters = &TrustCenterService{svc: tenantService}
tenantService.TrustCenterAccesses = &TrustCenterAccessService{svc: tenantService, usrmgr: s.usrmgr}
tenantService.TrustCenterReferences = &TrustCenterReferenceService{svc: tenantService}
tenantService.Nonconformities = &NonconformityService{svc: tenantService}
tenantService.Obligations = &ObligationService{svc: tenantService}
tenantService.Snapshots = &SnapshotService{svc: tenantService}

View File

@@ -0,0 +1,408 @@
// Copyright (c) 2025 Probo Inc <hello@getprobo.com>.
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies.
//
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
package probo
import (
"bytes"
"context"
"fmt"
"io"
"mime"
"net/url"
"path/filepath"
"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/page"
"go.gearno.de/crypto/uuid"
"go.gearno.de/kit/pg"
)
type (
TrustCenterReferenceService struct {
svc *TenantService
}
CreateTrustCenterReferenceRequest struct {
TrustCenterID gid.GID
Name string
Description string
WebsiteURL string
LogoFile File
}
UpdateTrustCenterReferenceRequest struct {
ID gid.GID
Name *string
Description *string
WebsiteURL *string
LogoFile *File
}
DeleteTrustCenterReferenceRequest struct {
ID gid.GID
}
)
func (s TrustCenterReferenceService) ListForTrustCenterID(
ctx context.Context,
trustCenterID gid.GID,
cursor *page.Cursor[coredata.TrustCenterReferenceOrderField],
) (*page.Page[*coredata.TrustCenterReference, coredata.TrustCenterReferenceOrderField], error) {
var references coredata.TrustCenterReferences
err := s.svc.pg.WithConn(ctx, func(conn pg.Conn) error {
err := references.LoadByTrustCenterID(ctx, conn, s.svc.scope, trustCenterID, cursor)
if err != nil {
return fmt.Errorf("cannot load trust center references: %w", err)
}
return nil
})
if err != nil {
return nil, err
}
return page.NewPage(references, cursor), nil
}
func (s TrustCenterReferenceService) CountForTrustCenterID(
ctx context.Context,
trustCenterID gid.GID,
) (int, error) {
var count int
err := s.svc.pg.WithConn(ctx, func(conn pg.Conn) (err error) {
references := coredata.TrustCenterReferences{}
count, err = references.CountByTrustCenterID(ctx, conn, s.svc.scope, trustCenterID)
if err != nil {
return fmt.Errorf("cannot count trust center references: %w", err)
}
return nil
})
if err != nil {
return 0, err
}
return count, nil
}
func (s TrustCenterReferenceService) Get(
ctx context.Context,
referenceID gid.GID,
) (*coredata.TrustCenterReference, error) {
var reference coredata.TrustCenterReference
err := s.svc.pg.WithConn(ctx, func(conn pg.Conn) error {
err := reference.LoadByID(ctx, conn, s.svc.scope, referenceID)
if err != nil {
return fmt.Errorf("cannot load trust center reference: %w", err)
}
return nil
})
if err != nil {
return nil, err
}
return &reference, nil
}
func (s TrustCenterReferenceService) Create(
ctx context.Context,
req *CreateTrustCenterReferenceRequest,
) (*coredata.TrustCenterReference, error) {
if req.Name == "" {
return nil, fmt.Errorf("name is required")
}
if req.WebsiteURL == "" {
return nil, fmt.Errorf("website URL is required")
}
now := time.Now()
referenceID := gid.New(s.svc.scope.GetTenantID(), coredata.TrustCenterReferenceEntityType)
var reference *coredata.TrustCenterReference
var logoKey string
err := s.svc.pg.WithTx(ctx, func(tx pg.Conn) error {
fileID, s3Key, err := s.uploadLogoFile(ctx, tx, req.LogoFile, referenceID, now)
if err != nil {
return fmt.Errorf("cannot upload logo file: %w", err)
}
logoKey = s3Key
reference = &coredata.TrustCenterReference{
ID: referenceID,
TrustCenterID: req.TrustCenterID,
Name: req.Name,
Description: req.Description,
WebsiteURL: req.WebsiteURL,
LogoFileID: fileID,
CreatedAt: now,
UpdatedAt: now,
}
if err := reference.Insert(ctx, tx, s.svc.scope); err != nil {
return fmt.Errorf("cannot insert trust center reference: %w", err)
}
return nil
})
if err != nil {
s.cleanupS3Object(ctx, logoKey)
return nil, err
}
return reference, nil
}
func (s TrustCenterReferenceService) Update(
ctx context.Context,
req *UpdateTrustCenterReferenceRequest,
) (*coredata.TrustCenterReference, error) {
now := time.Now()
var reference *coredata.TrustCenterReference
var newFileID *gid.GID
if req.Name != nil && *req.Name == "" {
return nil, fmt.Errorf("name is required")
}
if req.WebsiteURL != nil && *req.WebsiteURL == "" {
return nil, fmt.Errorf("website URL is required")
}
var logoKey string
err := s.svc.pg.WithTx(ctx, func(tx pg.Conn) error {
if req.LogoFile != nil {
fileID, s3Key, err := s.uploadLogoFile(ctx, tx, *req.LogoFile, req.ID, now)
if err != nil {
return fmt.Errorf("cannot upload logo file: %w", err)
}
newFileID = &fileID
logoKey = s3Key
}
reference = &coredata.TrustCenterReference{}
if err := reference.LoadByID(ctx, tx, s.svc.scope, req.ID); err != nil {
return fmt.Errorf("cannot load trust center reference: %w", err)
}
if req.Name != nil {
reference.Name = *req.Name
}
if req.Description != nil {
reference.Description = *req.Description
}
if req.WebsiteURL != nil {
reference.WebsiteURL = *req.WebsiteURL
}
if newFileID != nil {
reference.LogoFileID = *newFileID
}
reference.UpdatedAt = now
if err := reference.Update(ctx, tx, s.svc.scope); err != nil {
return fmt.Errorf("cannot update trust center reference: %w", err)
}
return nil
})
if err != nil {
s.cleanupS3Object(ctx, logoKey)
return nil, err
}
return reference, nil
}
func (s TrustCenterReferenceService) Delete(
ctx context.Context,
req *DeleteTrustCenterReferenceRequest,
) error {
err := s.svc.pg.WithTx(ctx, func(tx pg.Conn) error {
reference := &coredata.TrustCenterReference{}
if err := reference.LoadByID(ctx, tx, s.svc.scope, req.ID); err != nil {
return fmt.Errorf("cannot load trust center reference: %w", err)
}
if err := reference.Delete(ctx, tx, s.svc.scope); err != nil {
return fmt.Errorf("cannot delete trust center reference: %w", err)
}
return nil
})
return err
}
func (s TrustCenterReferenceService) GenerateLogoURL(
ctx context.Context,
referenceID gid.GID,
duration time.Duration,
) (string, error) {
reference := &coredata.TrustCenterReference{}
file := &coredata.File{}
err := s.svc.pg.WithTx(ctx, func(tx pg.Conn) error {
err := reference.LoadByID(ctx, tx, s.svc.scope, referenceID)
if err != nil {
return fmt.Errorf("cannot load trust center reference: %w", err)
}
err = file.LoadByID(ctx, tx, s.svc.scope, reference.LogoFileID)
if err != nil {
return fmt.Errorf("cannot load logo file: %w", err)
}
return nil
})
if err != nil {
return "", nil
}
presignClient := s3.NewPresignClient(s.svc.s3)
encodedFilename := url.PathEscape(file.FileName)
contentDisposition := fmt.Sprintf("inline; filename=\"%s\"; filename*=UTF-8''%s",
encodedFilename, encodedFilename)
presignedReq, err := presignClient.PresignGetObject(ctx, &s3.GetObjectInput{
Bucket: aws.String(s.svc.bucket),
Key: aws.String(file.FileKey),
ResponseCacheControl: aws.String("max-age=3600, public"),
ResponseContentDisposition: aws.String(contentDisposition),
}, func(opts *s3.PresignOptions) {
opts.Expires = duration
})
if err != nil {
return "", fmt.Errorf("cannot presign GetObject request: %w", err)
}
return presignedReq.URL, nil
}
func (s TrustCenterReferenceService) uploadLogoFile(
ctx context.Context,
tx pg.Conn,
file File,
referenceID gid.GID,
now time.Time,
) (gid.GID, string, error) {
fileID := gid.New(s.svc.scope.GetTenantID(), coredata.FileEntityType)
objectKey, err := uuid.NewV7()
if err != nil {
return gid.GID{}, "", fmt.Errorf("cannot generate object key: %w", err)
}
var fileSize int64
var fileContent io.ReadSeeker
filename := file.Filename
contentType := file.ContentType
if readSeeker, ok := file.Content.(io.ReadSeeker); ok {
if file.Size <= 0 {
size, err := readSeeker.Seek(0, io.SeekEnd)
if err != nil {
return gid.GID{}, "", fmt.Errorf("cannot determine file size: %w", err)
}
fileSize = size
_, err = readSeeker.Seek(0, io.SeekStart)
if err != nil {
return gid.GID{}, "", fmt.Errorf("cannot reset file position: %w", err)
}
} else {
fileSize = file.Size
}
fileContent = readSeeker
} else {
buf, err := io.ReadAll(file.Content)
if err != nil {
return gid.GID{}, "", fmt.Errorf("cannot read file: %w", err)
}
fileSize = int64(len(buf))
fileContent = bytes.NewReader(buf)
}
if contentType == "" {
contentType = "application/octet-stream"
if filename != "" {
if detectedType := mime.TypeByExtension(filepath.Ext(filename)); detectedType != "" {
contentType = detectedType
}
}
}
_, err = s.svc.s3.PutObject(ctx, &s3.PutObjectInput{
Bucket: aws.String(s.svc.bucket),
Key: aws.String(objectKey.String()),
Body: fileContent,
ContentType: aws.String(contentType),
Metadata: map[string]string{
"type": "trust-center-reference-logo",
"trust-center-reference-id": referenceID.String(),
},
})
if err != nil {
return gid.GID{}, "", fmt.Errorf("cannot upload logo file to S3: %w", err)
}
fileRecord := &coredata.File{
ID: fileID,
BucketName: s.svc.bucket,
MimeType: contentType,
FileName: filename,
FileKey: objectKey.String(),
FileSize: int(fileSize),
CreatedAt: now,
UpdatedAt: now,
}
if err := fileRecord.Insert(ctx, tx, s.svc.scope); err != nil {
return gid.GID{}, "", fmt.Errorf("cannot insert file: %w", err)
}
return fileID, objectKey.String(), nil
}
func (s TrustCenterReferenceService) cleanupS3Object(ctx context.Context, s3Key string) {
if s3Key == "" {
return
}
s.svc.s3.DeleteObject(ctx, &s3.DeleteObjectInput{
Bucket: aws.String(s.svc.bucket),
Key: aws.String(s3Key),
})
}

View File

@@ -1106,6 +1106,22 @@ enum TrustCenterAccessOrderField
)
}
enum TrustCenterReferenceOrderField
@goModel(model: "github.com/getprobo/probo/pkg/coredata.TrustCenterReferenceOrderField") {
NAME
@goEnum(
value: "github.com/getprobo/probo/pkg/coredata.TrustCenterReferenceOrderFieldName"
)
CREATED_AT
@goEnum(
value: "github.com/getprobo/probo/pkg/coredata.TrustCenterReferenceOrderFieldCreatedAt"
)
UPDATED_AT
@goEnum(
value: "github.com/getprobo/probo/pkg/coredata.TrustCenterReferenceOrderFieldUpdatedAt"
)
}
enum SnapshotsType
@goModel(model: "github.com/getprobo/probo/pkg/coredata.SnapshotsType") {
RISKS
@@ -1279,6 +1295,14 @@ input TrustCenterAccessOrder
field: TrustCenterAccessOrderField!
}
input TrustCenterReferenceOrder
@goModel(
model: "github.com/getprobo/probo/pkg/server/api/console/v1/types.TrustCenterReferenceOrderBy"
) {
direction: OrderDirection!
field: TrustCenterReferenceOrderField!
}
input EvidenceOrder
@goModel(
model: "github.com/getprobo/probo/pkg/server/api/console/v1/types.EvidenceOrderBy"
@@ -1413,6 +1437,14 @@ type TrustCenter implements Node {
before: CursorKey
orderBy: TrustCenterAccessOrder
): TrustCenterAccessConnection! @goField(forceResolver: true)
references(
first: Int
after: CursorKey
last: Int
before: CursorKey
orderBy: TrustCenterReferenceOrder
): TrustCenterReferenceConnection! @goField(forceResolver: true)
}
type Organization implements Node {
@@ -2168,6 +2200,29 @@ type TrustCenterAccessEdge {
node: TrustCenterAccess!
}
type TrustCenterReference implements Node {
id: ID!
name: String!
description: String!
websiteUrl: String!
logoUrl: String! @goField(forceResolver: true)
createdAt: Datetime!
updatedAt: Datetime!
}
type TrustCenterReferenceConnection @goModel(
model: "github.com/getprobo/probo/pkg/server/api/console/v1/types.TrustCenterReferenceConnection"
){
totalCount: Int! @goField(forceResolver: true)
edges: [TrustCenterReferenceEdge!]!
pageInfo: PageInfo!
}
type TrustCenterReferenceEdge {
cursor: CursorKey!
node: TrustCenterReference!
}
type UserConnection {
edges: [UserEdge!]!
pageInfo: PageInfo!
@@ -2504,6 +2559,19 @@ type Mutation {
input: DeleteTrustCenterAccessInput!
): DeleteTrustCenterAccessPayload!
# Trust Center Reference mutations
createTrustCenterReference(
input: CreateTrustCenterReferenceInput!
): CreateTrustCenterReferencePayload!
updateTrustCenterReference(
input: UpdateTrustCenterReferenceInput!
): UpdateTrustCenterReferencePayload!
deleteTrustCenterReference(
input: DeleteTrustCenterReferenceInput!
): DeleteTrustCenterReferencePayload!
# User mutations
confirmEmail(input: ConfirmEmailInput!): ConfirmEmailPayload!
inviteUser(input: InviteUserInput!): InviteUserPayload!
@@ -2813,6 +2881,26 @@ input DeleteTrustCenterAccessInput {
id: ID!
}
input CreateTrustCenterReferenceInput {
trustCenterId: ID!
name: String!
description: String!
websiteUrl: String!
logoFile: Upload!
}
input UpdateTrustCenterReferenceInput {
id: ID!
name: String
description: String
websiteUrl: String
logoFile: Upload
}
input DeleteTrustCenterReferenceInput {
id: ID!
}
input CreateVendorInput {
organizationId: ID!
name: String!
@@ -3450,6 +3538,18 @@ type DeleteTrustCenterAccessPayload {
deletedTrustCenterAccessId: ID!
}
type CreateTrustCenterReferencePayload {
trustCenterReferenceEdge: TrustCenterReferenceEdge!
}
type UpdateTrustCenterReferencePayload {
trustCenterReference: TrustCenterReference!
}
type DeleteTrustCenterReferencePayload {
deletedTrustCenterReferenceId: ID!
}
type CreateControlPayload {
controlEdge: ControlEdge!
}

File diff suppressed because it is too large Load Diff

View File

@@ -54,5 +54,3 @@ func NewTrustCenterAccessEdge(tca *coredata.TrustCenterAccess, orderBy coredata.
Node: NewTrustCenterAccess(tca),
}
}
// Types are auto-generated in types.go - only helper functions remain here

View File

@@ -0,0 +1,65 @@
// Copyright (c) 2025 Probo Inc <hello@getprobo.com>.
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies.
//
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
package types
import (
"github.com/getprobo/probo/pkg/coredata"
"github.com/getprobo/probo/pkg/gid"
"github.com/getprobo/probo/pkg/page"
)
type TrustCenterReferenceOrderBy = OrderBy[coredata.TrustCenterReferenceOrderField]
type TrustCenterReferenceConnection struct {
TotalCount int `json:"totalCount"`
Edges []*TrustCenterReferenceEdge `json:"edges"`
PageInfo *PageInfo `json:"pageInfo"`
ParentID gid.GID `json:"-"`
}
func NewTrustCenterReference(tcc *coredata.TrustCenterReference) *TrustCenterReference {
return &TrustCenterReference{
ID: tcc.ID,
Name: tcc.Name,
Description: tcc.Description,
WebsiteURL: tcc.WebsiteURL,
CreatedAt: tcc.CreatedAt,
UpdatedAt: tcc.UpdatedAt,
}
}
func NewTrustCenterReferenceConnection(
p *page.Page[*coredata.TrustCenterReference, coredata.TrustCenterReferenceOrderField],
parentID gid.GID,
) *TrustCenterReferenceConnection {
var edges = make([]*TrustCenterReferenceEdge, len(p.Data))
for i := range edges {
edges[i] = NewTrustCenterReferenceEdge(p.Data[i], p.Cursor.OrderBy.Field)
}
return &TrustCenterReferenceConnection{
Edges: edges,
PageInfo: NewPageInfo(p),
ParentID: parentID,
}
}
func NewTrustCenterReferenceEdge(tcc *coredata.TrustCenterReference, orderBy coredata.TrustCenterReferenceOrderField) *TrustCenterReferenceEdge {
return &TrustCenterReferenceEdge{
Cursor: tcc.CursorKey(orderBy),
Node: NewTrustCenterReference(tcc),
}
}

View File

@@ -537,6 +537,18 @@ type CreateTrustCenterAccessPayload struct {
TrustCenterAccessEdge *TrustCenterAccessEdge `json:"trustCenterAccessEdge"`
}
type CreateTrustCenterReferenceInput struct {
TrustCenterID gid.GID `json:"trustCenterId"`
Name string `json:"name"`
Description string `json:"description"`
WebsiteURL string `json:"websiteUrl"`
LogoFile graphql.Upload `json:"logoFile"`
}
type CreateTrustCenterReferencePayload struct {
TrustCenterReferenceEdge *TrustCenterReferenceEdge `json:"trustCenterReferenceEdge"`
}
type CreateVendorContactInput struct {
VendorID gid.GID `json:"vendorId"`
FullName *string `json:"fullName,omitempty"`
@@ -852,6 +864,14 @@ type DeleteTrustCenterNDAPayload struct {
TrustCenter *TrustCenter `json:"trustCenter"`
}
type DeleteTrustCenterReferenceInput struct {
ID gid.GID `json:"id"`
}
type DeleteTrustCenterReferencePayload struct {
DeletedTrustCenterReferenceID gid.GID `json:"deletedTrustCenterReferenceId"`
}
type DeleteVendorBusinessAssociateAgreementInput struct {
VendorID gid.GID `json:"vendorId"`
}
@@ -1461,15 +1481,16 @@ type TaskEdge struct {
}
type TrustCenter struct {
ID gid.GID `json:"id"`
Active bool `json:"active"`
Slug string `json:"slug"`
NdaFileName *string `json:"ndaFileName,omitempty"`
NdaFileURL *string `json:"ndaFileUrl,omitempty"`
CreatedAt time.Time `json:"createdAt"`
UpdatedAt time.Time `json:"updatedAt"`
Organization *Organization `json:"organization"`
Accesses *TrustCenterAccessConnection `json:"accesses"`
ID gid.GID `json:"id"`
Active bool `json:"active"`
Slug string `json:"slug"`
NdaFileName *string `json:"ndaFileName,omitempty"`
NdaFileURL *string `json:"ndaFileUrl,omitempty"`
CreatedAt time.Time `json:"createdAt"`
UpdatedAt time.Time `json:"updatedAt"`
Organization *Organization `json:"organization"`
Accesses *TrustCenterAccessConnection `json:"accesses"`
References *TrustCenterReferenceConnection `json:"references"`
}
func (TrustCenter) IsNode() {}
@@ -1508,6 +1529,24 @@ type TrustCenterEdge struct {
Node *TrustCenter `json:"node"`
}
type TrustCenterReference struct {
ID gid.GID `json:"id"`
Name string `json:"name"`
Description string `json:"description"`
WebsiteURL string `json:"websiteUrl"`
LogoURL string `json:"logoUrl"`
CreatedAt time.Time `json:"createdAt"`
UpdatedAt time.Time `json:"updatedAt"`
}
func (TrustCenterReference) IsNode() {}
func (this TrustCenterReference) GetID() gid.GID { return this.ID }
type TrustCenterReferenceEdge struct {
Cursor page.CursorKey `json:"cursor"`
Node *TrustCenterReference `json:"node"`
}
type UnassignTaskInput struct {
TaskID gid.GID `json:"taskId"`
}
@@ -1767,6 +1806,18 @@ type UpdateTrustCenterPayload struct {
TrustCenter *TrustCenter `json:"trustCenter"`
}
type UpdateTrustCenterReferenceInput struct {
ID gid.GID `json:"id"`
Name *string `json:"name,omitempty"`
Description *string `json:"description,omitempty"`
WebsiteURL *string `json:"websiteUrl,omitempty"`
LogoFile *graphql.Upload `json:"logoFile,omitempty"`
}
type UpdateTrustCenterReferencePayload struct {
TrustCenterReference *TrustCenterReference `json:"trustCenterReference"`
}
type UpdateVendorBusinessAssociateAgreementInput struct {
VendorID gid.GID `json:"vendorId"`
ValidFrom *time.Time `json:"validFrom,omitempty"`

View File

@@ -1229,6 +1229,77 @@ func (r *mutationResolver) DeleteTrustCenterAccess(ctx context.Context, input ty
}, nil
}
// CreateTrustCenterReference is the resolver for the createTrustCenterReference field.
func (r *mutationResolver) CreateTrustCenterReference(ctx context.Context, input types.CreateTrustCenterReferenceInput) (*types.CreateTrustCenterReferencePayload, error) {
prb := r.ProboService(ctx, input.TrustCenterID.TenantID())
reference, err := prb.TrustCenterReferences.Create(ctx, &probo.CreateTrustCenterReferenceRequest{
TrustCenterID: input.TrustCenterID,
Name: input.Name,
Description: input.Description,
WebsiteURL: input.WebsiteURL,
LogoFile: probo.File{
Content: input.LogoFile.File,
Filename: input.LogoFile.Filename,
Size: input.LogoFile.Size,
ContentType: input.LogoFile.ContentType,
},
})
if err != nil {
return nil, fmt.Errorf("cannot create trust center reference: %w", err)
}
return &types.CreateTrustCenterReferencePayload{
TrustCenterReferenceEdge: types.NewTrustCenterReferenceEdge(reference, coredata.TrustCenterReferenceOrderFieldCreatedAt),
}, nil
}
// UpdateTrustCenterReference is the resolver for the updateTrustCenterReference field.
func (r *mutationResolver) UpdateTrustCenterReference(ctx context.Context, input types.UpdateTrustCenterReferenceInput) (*types.UpdateTrustCenterReferencePayload, error) {
prb := r.ProboService(ctx, input.ID.TenantID())
req := &probo.UpdateTrustCenterReferenceRequest{
ID: input.ID,
Name: input.Name,
Description: input.Description,
WebsiteURL: input.WebsiteURL,
}
if input.LogoFile != nil {
req.LogoFile = &probo.File{
Content: input.LogoFile.File,
Filename: input.LogoFile.Filename,
Size: input.LogoFile.Size,
ContentType: input.LogoFile.ContentType,
}
}
reference, err := prb.TrustCenterReferences.Update(ctx, req)
if err != nil {
return nil, fmt.Errorf("cannot update trust center reference: %w", err)
}
return &types.UpdateTrustCenterReferencePayload{
TrustCenterReference: types.NewTrustCenterReference(reference),
}, nil
}
// DeleteTrustCenterReference is the resolver for the deleteTrustCenterReference field.
func (r *mutationResolver) DeleteTrustCenterReference(ctx context.Context, input types.DeleteTrustCenterReferenceInput) (*types.DeleteTrustCenterReferencePayload, error) {
prb := r.ProboService(ctx, input.ID.TenantID())
err := prb.TrustCenterReferences.Delete(ctx, &probo.DeleteTrustCenterReferenceRequest{
ID: input.ID,
})
if err != nil {
return nil, fmt.Errorf("cannot delete trust center reference: %w", err)
}
return &types.DeleteTrustCenterReferencePayload{
DeletedTrustCenterReferenceID: input.ID,
}, nil
}
// ConfirmEmail is the resolver for the confirmEmail field.
func (r *mutationResolver) ConfirmEmail(ctx context.Context, input types.ConfirmEmailInput) (*types.ConfirmEmailPayload, error) {
err := r.usrmgrSvc.ConfirmEmail(ctx, input.Token)
@@ -4552,6 +4623,54 @@ func (r *trustCenterResolver) Accesses(ctx context.Context, obj *types.TrustCent
return types.NewTrustCenterAccessConnection(result), nil
}
// References is the resolver for the references field.
func (r *trustCenterResolver) References(ctx context.Context, obj *types.TrustCenter, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.OrderBy[coredata.TrustCenterReferenceOrderField]) (*types.TrustCenterReferenceConnection, error) {
prb := r.ProboService(ctx, obj.ID.TenantID())
pageOrderBy := page.OrderBy[coredata.TrustCenterReferenceOrderField]{
Field: coredata.TrustCenterReferenceOrderFieldName,
Direction: page.OrderDirectionAsc,
}
if orderBy != nil {
pageOrderBy = page.OrderBy[coredata.TrustCenterReferenceOrderField]{
Field: orderBy.Field,
Direction: orderBy.Direction,
}
}
cursor := types.NewCursor(first, after, last, before, pageOrderBy)
result, err := prb.TrustCenterReferences.ListForTrustCenterID(ctx, obj.ID, cursor)
if err != nil {
panic(fmt.Errorf("cannot list trust center references: %w", err))
}
return types.NewTrustCenterReferenceConnection(result, obj.ID), nil
}
// LogoURL is the resolver for the logoUrl field.
func (r *trustCenterReferenceResolver) LogoURL(ctx context.Context, obj *types.TrustCenterReference) (string, error) {
prb := r.ProboService(ctx, obj.ID.TenantID())
fileURL, err := prb.TrustCenterReferences.GenerateLogoURL(ctx, obj.ID, 1*time.Hour)
if err != nil {
panic(fmt.Errorf("failed to generate logo URL: %w", err))
}
return fileURL, nil
}
// TotalCount is the resolver for the totalCount field.
func (r *trustCenterReferenceConnectionResolver) TotalCount(ctx context.Context, obj *types.TrustCenterReferenceConnection) (int, error) {
prb := r.ProboService(ctx, obj.ParentID.TenantID())
count, err := prb.TrustCenterReferences.CountForTrustCenterID(ctx, obj.ParentID)
if err != nil {
panic(fmt.Errorf("cannot count trust center references: %w", err))
}
return count, nil
}
// People is the resolver for the people field.
func (r *userResolver) People(ctx context.Context, obj *types.User, organizationID gid.GID) (*types.People, error) {
prb := r.ProboService(ctx, organizationID.TenantID())
@@ -5082,6 +5201,16 @@ func (r *Resolver) TaskConnection() schema.TaskConnectionResolver { return &task
// TrustCenter returns schema.TrustCenterResolver implementation.
func (r *Resolver) TrustCenter() schema.TrustCenterResolver { return &trustCenterResolver{r} }
// TrustCenterReference returns schema.TrustCenterReferenceResolver implementation.
func (r *Resolver) TrustCenterReference() schema.TrustCenterReferenceResolver {
return &trustCenterReferenceResolver{r}
}
// TrustCenterReferenceConnection returns schema.TrustCenterReferenceConnectionResolver implementation.
func (r *Resolver) TrustCenterReferenceConnection() schema.TrustCenterReferenceConnectionResolver {
return &trustCenterReferenceConnectionResolver{r}
}
// User returns schema.UserResolver implementation.
func (r *Resolver) User() schema.UserResolver { return &userResolver{r} }
@@ -5160,6 +5289,8 @@ type snapshotConnectionResolver struct{ *Resolver }
type taskResolver struct{ *Resolver }
type taskConnectionResolver struct{ *Resolver }
type trustCenterResolver struct{ *Resolver }
type trustCenterReferenceResolver struct{ *Resolver }
type trustCenterReferenceConnectionResolver struct{ *Resolver }
type userResolver struct{ *Resolver }
type vendorResolver struct{ *Resolver }
type vendorBusinessAssociateAgreementResolver struct{ *Resolver }

View File

@@ -452,6 +452,24 @@ type VendorEdge {
node: Vendor!
}
type TrustCenterReference implements Node {
id: ID!
name: String!
description: String!
websiteUrl: String!
logoUrl: String! @goField(forceResolver: true)
}
type TrustCenterReferenceConnection {
edges: [TrustCenterReferenceEdge!]!
pageInfo: PageInfo!
}
type TrustCenterReferenceEdge {
cursor: CursorKey!
node: TrustCenterReference!
}
type TrustCenter implements Node {
id: ID!
active: Boolean!
@@ -482,6 +500,13 @@ type TrustCenter implements Node {
last: Int
before: CursorKey
): VendorConnection! @goField(forceResolver: true)
references(
first: Int
after: CursorKey
last: Int
before: CursorKey
): TrustCenterReferenceConnection! @goField(forceResolver: true)
}
type TrustCenterAccess implements Node {

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,49 @@
// Copyright (c) 2025 Probo Inc <hello@getprobo.com>.
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies.
//
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
package types
import (
"github.com/getprobo/probo/pkg/coredata"
"github.com/getprobo/probo/pkg/page"
)
func NewTrustCenterReference(tcc *coredata.TrustCenterReference) *TrustCenterReference {
return &TrustCenterReference{
ID: tcc.ID,
Name: tcc.Name,
Description: tcc.Description,
WebsiteURL: tcc.WebsiteURL,
}
}
func NewTrustCenterReferenceConnection(p *page.Page[*coredata.TrustCenterReference, coredata.TrustCenterReferenceOrderField]) *TrustCenterReferenceConnection {
edges := make([]*TrustCenterReferenceEdge, len(p.Data))
for i, item := range p.Data {
edges[i] = NewTrustCenterReferenceEdge(item, p.Cursor.OrderBy.Field)
}
return &TrustCenterReferenceConnection{
Edges: edges,
PageInfo: NewPageInfo(p),
}
}
func NewTrustCenterReferenceEdge(tcc *coredata.TrustCenterReference, orderBy coredata.TrustCenterReferenceOrderField) *TrustCenterReferenceEdge {
return &TrustCenterReferenceEdge{
Cursor: tcc.CursorKey(orderBy),
Node: NewTrustCenterReference(tcc),
}
}

View File

@@ -134,17 +134,18 @@ func (Report) IsNode() {}
func (this Report) GetID() gid.GID { return this.ID }
type TrustCenter struct {
ID gid.GID `json:"id"`
Active bool `json:"active"`
Slug string `json:"slug"`
NdaFileName *string `json:"ndaFileName,omitempty"`
NdaFileURL *string `json:"ndaFileUrl,omitempty"`
Organization *Organization `json:"organization"`
IsUserAuthenticated bool `json:"isUserAuthenticated"`
HasAcceptedNonDisclosureAgreement bool `json:"hasAcceptedNonDisclosureAgreement"`
Documents *DocumentConnection `json:"documents"`
Audits *AuditConnection `json:"audits"`
Vendors *VendorConnection `json:"vendors"`
ID gid.GID `json:"id"`
Active bool `json:"active"`
Slug string `json:"slug"`
NdaFileName *string `json:"ndaFileName,omitempty"`
NdaFileURL *string `json:"ndaFileUrl,omitempty"`
Organization *Organization `json:"organization"`
IsUserAuthenticated bool `json:"isUserAuthenticated"`
HasAcceptedNonDisclosureAgreement bool `json:"hasAcceptedNonDisclosureAgreement"`
Documents *DocumentConnection `json:"documents"`
Audits *AuditConnection `json:"audits"`
Vendors *VendorConnection `json:"vendors"`
References *TrustCenterReferenceConnection `json:"references"`
}
func (TrustCenter) IsNode() {}
@@ -161,6 +162,27 @@ type TrustCenterAccess struct {
func (TrustCenterAccess) IsNode() {}
func (this TrustCenterAccess) GetID() gid.GID { return this.ID }
type TrustCenterReference struct {
ID gid.GID `json:"id"`
Name string `json:"name"`
Description string `json:"description"`
WebsiteURL string `json:"websiteUrl"`
LogoURL string `json:"logoUrl"`
}
func (TrustCenterReference) IsNode() {}
func (this TrustCenterReference) GetID() gid.GID { return this.ID }
type TrustCenterReferenceConnection struct {
Edges []*TrustCenterReferenceEdge `json:"edges"`
PageInfo *PageInfo `json:"pageInfo"`
}
type TrustCenterReferenceEdge struct {
Cursor page.CursorKey `json:"cursor"`
Node *TrustCenterReference `json:"node"`
}
type Vendor struct {
ID gid.GID `json:"id"`
Name string `json:"name"`

View File

@@ -330,6 +330,36 @@ func (r *trustCenterResolver) Vendors(ctx context.Context, obj *types.TrustCente
return types.NewVendorConnection(vendorPage), nil
}
// References is the resolver for the references field.
func (r *trustCenterResolver) References(ctx context.Context, obj *types.TrustCenter, first *int, after *page.CursorKey, last *int, before *page.CursorKey) (*types.TrustCenterReferenceConnection, error) {
publicTrustService := r.PublicTrustService(ctx, obj.ID.TenantID())
pageOrderBy := page.OrderBy[coredata.TrustCenterReferenceOrderField]{
Field: coredata.TrustCenterReferenceOrderFieldCreatedAt,
Direction: page.OrderDirectionDesc,
}
cursor := types.NewCursor(first, after, last, before, pageOrderBy)
referencePage, err := publicTrustService.TrustCenterReferences.ListForTrustCenterID(ctx, obj.ID, cursor)
if err != nil {
panic(fmt.Errorf("cannot list public trust center references: %w", err))
}
return types.NewTrustCenterReferenceConnection(referencePage), nil
}
// LogoURL is the resolver for the logoUrl field.
func (r *trustCenterReferenceResolver) LogoURL(ctx context.Context, obj *types.TrustCenterReference) (string, error) {
publicTrustService := r.PublicTrustService(ctx, obj.ID.TenantID())
logoURL, err := publicTrustService.TrustCenterReferences.GenerateLogoURL(ctx, obj.ID, 1*time.Hour)
if err != nil {
panic(fmt.Errorf("cannot generate logo URL: %w", err))
}
return logoURL, nil
}
// Audit returns schema.AuditResolver implementation.
func (r *Resolver) Audit() schema.AuditResolver { return &auditResolver{r} }
@@ -345,8 +375,14 @@ func (r *Resolver) Query() schema.QueryResolver { return &queryResolver{r} }
// TrustCenter returns schema.TrustCenterResolver implementation.
func (r *Resolver) TrustCenter() schema.TrustCenterResolver { return &trustCenterResolver{r} }
// TrustCenterReference returns schema.TrustCenterReferenceResolver implementation.
func (r *Resolver) TrustCenterReference() schema.TrustCenterReferenceResolver {
return &trustCenterReferenceResolver{r}
}
type auditResolver struct{ *Resolver }
type mutationResolver struct{ *Resolver }
type organizationResolver struct{ *Resolver }
type queryResolver struct{ *Resolver }
type trustCenterResolver struct{ *Resolver }
type trustCenterReferenceResolver struct{ *Resolver }

View File

@@ -53,6 +53,7 @@ type (
Vendors *VendorService
Frameworks *FrameworkService
TrustCenterAccesses *TrustCenterAccessService
TrustCenterReferences *TrustCenterReferenceService
Reports *ReportService
Organizations *OrganizationService
}
@@ -97,6 +98,7 @@ func (s *Service) WithTenant(tenantID gid.TenantID) *TenantService {
tenantService.Vendors = &VendorService{svc: tenantService}
tenantService.Frameworks = &FrameworkService{svc: tenantService}
tenantService.TrustCenterAccesses = &TrustCenterAccessService{svc: tenantService, usrmgr: s.usrmgr}
tenantService.TrustCenterReferences = &TrustCenterReferenceService{svc: tenantService}
tenantService.Reports = &ReportService{svc: tenantService}
tenantService.Organizations = &OrganizationService{svc: tenantService}

View File

@@ -0,0 +1,102 @@
// 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 trust
import (
"context"
"fmt"
"net/url"
"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/page"
"go.gearno.de/kit/pg"
)
type TrustCenterReferenceService struct {
svc *TenantService
}
func (s TrustCenterReferenceService) ListForTrustCenterID(
ctx context.Context,
trustCenterID gid.GID,
cursor *page.Cursor[coredata.TrustCenterReferenceOrderField],
) (*page.Page[*coredata.TrustCenterReference, coredata.TrustCenterReferenceOrderField], error) {
var references coredata.TrustCenterReferences
err := s.svc.pg.WithConn(ctx, func(conn pg.Conn) error {
err := references.LoadByTrustCenterID(ctx, conn, s.svc.scope, trustCenterID, cursor)
if err != nil {
return fmt.Errorf("cannot load trust center references: %w", err)
}
return nil
})
if err != nil {
return nil, err
}
return page.NewPage(references, cursor), nil
}
func (s TrustCenterReferenceService) GenerateLogoURL(
ctx context.Context,
referenceID gid.GID,
duration time.Duration,
) (string, error) {
reference := &coredata.TrustCenterReference{}
file := &coredata.File{}
err := s.svc.pg.WithTx(ctx, func(tx pg.Conn) error {
err := reference.LoadByID(ctx, tx, s.svc.scope, referenceID)
if err != nil {
return fmt.Errorf("cannot load trust center reference: %w", err)
}
err = file.LoadByID(ctx, tx, s.svc.scope, reference.LogoFileID)
if err != nil {
return fmt.Errorf("cannot load logo file: %w", err)
}
return nil
})
if err != nil {
return "", nil
}
presignClient := s3.NewPresignClient(s.svc.s3)
encodedFilename := url.PathEscape(file.FileName)
contentDisposition := fmt.Sprintf("inline; filename=\"%s\"; filename*=UTF-8''%s",
encodedFilename, encodedFilename)
presignedReq, err := presignClient.PresignGetObject(ctx, &s3.GetObjectInput{
Bucket: aws.String(s.svc.bucket),
Key: aws.String(file.FileKey),
ResponseCacheControl: aws.String("max-age=3600, public"),
ResponseContentDisposition: aws.String(contentDisposition),
}, func(opts *s3.PresignOptions) {
opts.Expires = duration
})
if err != nil {
return "", fmt.Errorf("cannot presign GetObject request: %w", err)
}
return presignedReq.URL, nil
}