Add light and dark logo files for frameworks

Signed-off-by: Émile Ré <nemile.re@gmail.com>
This commit is contained in:
Émile Ré
2025-12-16 19:02:57 +01:00
parent 597c00a08a
commit 427565c521
13 changed files with 630 additions and 25 deletions

View File

@@ -467,6 +467,7 @@ var Permissions = map[uint16]map[Action][]Role{
ActionGet: NonEmployeeRoles, ActionGet: NonEmployeeRoles,
ActionGetOrganization: NonEmployeeRoles, ActionGetOrganization: NonEmployeeRoles,
ActionListControls: NonEmployeeRoles, ActionListControls: NonEmployeeRoles,
ActionGetLogoUrl: NonEmployeeRoles,
ActionCreateControl: EditRoles, ActionCreateControl: EditRoles,
ActionUpdateFramework: EditRoles, ActionUpdateFramework: EditRoles,

View File

@@ -30,13 +30,15 @@ import (
type ( type (
Framework struct { Framework struct {
ID gid.GID `db:"id"` ID gid.GID `db:"id"`
OrganizationID gid.GID `db:"organization_id"` OrganizationID gid.GID `db:"organization_id"`
ReferenceID string `db:"reference_id"` ReferenceID string `db:"reference_id"`
Name string `db:"name"` Name string `db:"name"`
Description *string `db:"description"` Description *string `db:"description"`
CreatedAt time.Time `db:"created_at"` LightLogoFileID *gid.GID `db:"light_logo_file_id"`
UpdatedAt time.Time `db:"updated_at"` DarkLogoFileID *gid.GID `db:"dark_logo_file_id"`
CreatedAt time.Time `db:"created_at"`
UpdatedAt time.Time `db:"updated_at"`
} }
Frameworks []*Framework Frameworks []*Framework
@@ -121,6 +123,8 @@ SELECT
reference_id, reference_id,
name, name,
description, description,
light_logo_file_id,
dark_logo_file_id,
created_at, created_at,
updated_at updated_at
FROM FROM
@@ -128,7 +132,7 @@ FROM
WHERE WHERE
%s %s
AND organization_id = @organization_id AND organization_id = @organization_id
AND %s AND %s
` `
q = fmt.Sprintf(q, scope.SQLFragment(), cursor.SQLFragment()) q = fmt.Sprintf(q, scope.SQLFragment(), cursor.SQLFragment())
@@ -164,6 +168,8 @@ SELECT
reference_id, reference_id,
name, name,
description, description,
light_logo_file_id,
dark_logo_file_id,
created_at, created_at,
updated_at updated_at
FROM FROM
@@ -210,6 +216,8 @@ SELECT
reference_id, reference_id,
name, name,
description, description,
light_logo_file_id,
dark_logo_file_id,
created_at, created_at,
updated_at updated_at
FROM FROM
@@ -257,6 +265,8 @@ INSERT INTO
reference_id, reference_id,
name, name,
description, description,
light_logo_file_id,
dark_logo_file_id,
created_at, created_at,
updated_at updated_at
) )
@@ -267,20 +277,24 @@ VALUES (
@reference_id, @reference_id,
@name, @name,
@description, @description,
@light_logo_file_id,
@dark_logo_file_id,
@created_at, @created_at,
@updated_at @updated_at
); );
` `
args := pgx.StrictNamedArgs{ args := pgx.StrictNamedArgs{
"tenant_id": scope.GetTenantID(), "tenant_id": scope.GetTenantID(),
"framework_id": f.ID, "framework_id": f.ID,
"organization_id": f.OrganizationID, "organization_id": f.OrganizationID,
"reference_id": f.ReferenceID, "reference_id": f.ReferenceID,
"name": f.Name, "name": f.Name,
"description": f.Description, "description": f.Description,
"created_at": f.CreatedAt, "light_logo_file_id": f.LightLogoFileID,
"updated_at": f.UpdatedAt, "dark_logo_file_id": f.DarkLogoFileID,
"created_at": f.CreatedAt,
"updated_at": f.UpdatedAt,
} }
_, err := conn.Exec(ctx, q, args) _, err := conn.Exec(ctx, q, args)

View File

@@ -0,0 +1,17 @@
ALTER TABLE
frameworks
ADD
COLUMN light_logo_file_id TEXT,
ADD
COLUMN dark_logo_file_id TEXT;
ALTER TABLE
frameworks
ADD
CONSTRAINT frameworks_light_logo_file_id_fkey FOREIGN KEY (light_logo_file_id) REFERENCES files(id) ON DELETE
SET
NULL,
ADD
CONSTRAINT frameworks_dark_logo_file_id_fkey FOREIGN KEY (dark_logo_file_id) REFERENCES files(id) ON DELETE
SET
NULL;

View File

@@ -21,6 +21,7 @@ import (
"fmt" "fmt"
"io" "io"
"os" "os"
"strings"
"time" "time"
"github.com/aws/aws-sdk-go-v2/aws" "github.com/aws/aws-sdk-go-v2/aws"
@@ -64,8 +65,12 @@ type (
ImportFrameworkRequest struct { ImportFrameworkRequest struct {
Framework struct { Framework struct {
ID string `json:"id"` ID string `json:"id"`
Name string `json:"name"` Name string `json:"name"`
Logo *struct {
Light string `json:"light"`
Dark string `json:"dark"`
} `json:"logo,omitempty"`
Controls []struct { Controls []struct {
ID string `json:"id"` ID string `json:"id"`
Name string `json:"name"` Name string `json:"name"`
@@ -510,6 +515,54 @@ func (s FrameworkService) Import(
UpdatedAt: now, UpdatedAt: now,
} }
if req.Framework.Logo != nil {
for name, logo := range map[string]string{
"light": req.Framework.Logo.Light,
"dark": req.Framework.Logo.Dark,
} {
fileID := gid.New(s.svc.scope.GetTenantID(), coredata.FileEntityType)
objectKey, err := uuid.NewV7()
if err != nil {
return fmt.Errorf("cannot generate object key: %w", err)
}
filename := "logo_" + name
contentType := "image/svg+xml"
fileRecord := &coredata.File{
ID: fileID,
BucketName: s.svc.bucket,
MimeType: contentType,
FileName: filename,
FileKey: objectKey.String(),
CreatedAt: now,
UpdatedAt: now,
}
fileSize, err := s.svc.fileManager.PutFile(ctx, fileRecord, strings.NewReader(logo), map[string]string{
"type": "framework-logo",
"theme": name,
"framework-id": framework.ID.String(),
"organization-id": organization.ID.String(),
})
if err != nil {
return fmt.Errorf("cannot upload logo file: %w", err)
}
fileRecord.FileSize = fileSize
if err := fileRecord.Insert(ctx, tx, s.svc.scope); err != nil {
return fmt.Errorf("cannot insert file: %w", err)
}
if name == "light" {
framework.LightLogoFileID = &fileID
} else {
framework.DarkLogoFileID = &fileID
}
}
}
if err := framework.Insert(ctx, tx, s.svc.scope); err != nil { if err := framework.Insert(ctx, tx, s.svc.scope); err != nil {
return fmt.Errorf("cannot insert framework: %w", err) return fmt.Errorf("cannot insert framework: %w", err)
} }
@@ -890,3 +943,87 @@ func (s *FrameworkService) BuildAndUploadExport(ctx context.Context, exportJobID
return exportJob, nil return exportJob, nil
} }
func (s FrameworkService) GenerateLightLogoURL(
ctx context.Context,
frameworkID gid.GID,
expiresIn time.Duration,
) (*string, error) {
file := &coredata.File{}
err := s.svc.pg.WithConn(
ctx,
func(conn pg.Conn) error {
framework := &coredata.Framework{}
if err := framework.LoadByID(ctx, conn, s.svc.scope, frameworkID); err != nil {
return fmt.Errorf("cannot load framework: %w", err)
}
if framework.LightLogoFileID == nil {
return nil
}
if err := file.LoadByID(ctx, conn, s.svc.scope, *framework.LightLogoFileID); err != nil {
return fmt.Errorf("cannot load file: %w", err)
}
return nil
},
)
if err != nil {
return nil, err
}
if file.FileKey == "" {
return nil, nil
}
presignedURL, err := s.svc.fileManager.GenerateFileUrl(ctx, file, expiresIn)
if err != nil {
return nil, fmt.Errorf("cannot generate file URL: %w", err)
}
return &presignedURL, nil
}
func (s FrameworkService) GenerateDarkLogoURL(
ctx context.Context,
frameworkID gid.GID,
expiresIn time.Duration,
) (*string, error) {
file := &coredata.File{}
err := s.svc.pg.WithConn(
ctx,
func(conn pg.Conn) error {
framework := &coredata.Framework{}
if err := framework.LoadByID(ctx, conn, s.svc.scope, frameworkID); err != nil {
return fmt.Errorf("cannot load framework: %w", err)
}
if framework.DarkLogoFileID == nil {
return nil
}
if err := file.LoadByID(ctx, conn, s.svc.scope, *framework.DarkLogoFileID); err != nil {
return fmt.Errorf("cannot load file: %w", err)
}
return nil
},
)
if err != nil {
return nil, err
}
if file.FileKey == "" {
return nil, nil
}
presignedURL, err := s.svc.fileManager.GenerateFileUrl(ctx, file, expiresIn)
if err != nil {
return nil, fmt.Errorf("cannot generate file URL: %w", err)
}
return &presignedURL, nil
}

View File

@@ -1886,6 +1886,8 @@ type Framework implements Node {
orderBy: ControlOrder orderBy: ControlOrder
filter: ControlFilter filter: ControlFilter
): ControlConnection! @goField(forceResolver: true) ): ControlConnection! @goField(forceResolver: true)
lightLogoURL: String @goField(forceResolver: true)
darkLogoURL: String @goField(forceResolver: true)
createdAt: Datetime! createdAt: Datetime!
updatedAt: Datetime! updatedAt: Datetime!

View File

@@ -754,8 +754,10 @@ type ComplexityRoot struct {
Framework struct { Framework struct {
Controls func(childComplexity int, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.ControlOrderBy, filter *types.ControlFilter) int Controls func(childComplexity int, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.ControlOrderBy, filter *types.ControlFilter) int
CreatedAt func(childComplexity int) int CreatedAt func(childComplexity int) int
DarkLogoURL func(childComplexity int) int
Description func(childComplexity int) int Description func(childComplexity int) int
ID func(childComplexity int) int ID func(childComplexity int) int
LightLogoURL func(childComplexity int) int
Name func(childComplexity int) int Name func(childComplexity int) int
Organization func(childComplexity int) int Organization func(childComplexity int) int
UpdatedAt func(childComplexity int) int UpdatedAt func(childComplexity int) int
@@ -1933,6 +1935,8 @@ type FileResolver interface {
type FrameworkResolver interface { type FrameworkResolver interface {
Organization(ctx context.Context, obj *types.Framework) (*types.Organization, error) Organization(ctx context.Context, obj *types.Framework) (*types.Organization, error)
Controls(ctx context.Context, obj *types.Framework, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.ControlOrderBy, filter *types.ControlFilter) (*types.ControlConnection, error) Controls(ctx context.Context, obj *types.Framework, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.ControlOrderBy, filter *types.ControlFilter) (*types.ControlConnection, error)
LightLogoURL(ctx context.Context, obj *types.Framework) (*string, error)
DarkLogoURL(ctx context.Context, obj *types.Framework) (*string, error)
} }
type FrameworkConnectionResolver interface { type FrameworkConnectionResolver interface {
TotalCount(ctx context.Context, obj *types.FrameworkConnection) (int, error) TotalCount(ctx context.Context, obj *types.FrameworkConnection) (int, error)
@@ -4143,6 +4147,12 @@ func (e *executableSchema) Complexity(ctx context.Context, typeName, field strin
} }
return e.complexity.Framework.CreatedAt(childComplexity), true return e.complexity.Framework.CreatedAt(childComplexity), true
case "Framework.darkLogoURL":
if e.complexity.Framework.DarkLogoURL == nil {
break
}
return e.complexity.Framework.DarkLogoURL(childComplexity), true
case "Framework.description": case "Framework.description":
if e.complexity.Framework.Description == nil { if e.complexity.Framework.Description == nil {
break break
@@ -4155,6 +4165,12 @@ func (e *executableSchema) Complexity(ctx context.Context, typeName, field strin
} }
return e.complexity.Framework.ID(childComplexity), true return e.complexity.Framework.ID(childComplexity), true
case "Framework.lightLogoURL":
if e.complexity.Framework.LightLogoURL == nil {
break
}
return e.complexity.Framework.LightLogoURL(childComplexity), true
case "Framework.name": case "Framework.name":
if e.complexity.Framework.Name == nil { if e.complexity.Framework.Name == nil {
break break
@@ -11442,6 +11458,8 @@ type Framework implements Node {
orderBy: ControlOrder orderBy: ControlOrder
filter: ControlFilter filter: ControlFilter
): ControlConnection! @goField(forceResolver: true) ): ControlConnection! @goField(forceResolver: true)
lightLogoURL: String @goField(forceResolver: true)
darkLogoURL: String @goField(forceResolver: true)
createdAt: Datetime! createdAt: Datetime!
updatedAt: Datetime! updatedAt: Datetime!
@@ -18952,6 +18970,10 @@ func (ec *executionContext) fieldContext_Audit_framework(_ context.Context, fiel
return ec.fieldContext_Framework_organization(ctx, field) return ec.fieldContext_Framework_organization(ctx, field)
case "controls": case "controls":
return ec.fieldContext_Framework_controls(ctx, field) return ec.fieldContext_Framework_controls(ctx, field)
case "lightLogoURL":
return ec.fieldContext_Framework_lightLogoURL(ctx, field)
case "darkLogoURL":
return ec.fieldContext_Framework_darkLogoURL(ctx, field)
case "createdAt": case "createdAt":
return ec.fieldContext_Framework_createdAt(ctx, field) return ec.fieldContext_Framework_createdAt(ctx, field)
case "updatedAt": case "updatedAt":
@@ -20542,6 +20564,10 @@ func (ec *executionContext) fieldContext_Control_framework(_ context.Context, fi
return ec.fieldContext_Framework_organization(ctx, field) return ec.fieldContext_Framework_organization(ctx, field)
case "controls": case "controls":
return ec.fieldContext_Framework_controls(ctx, field) return ec.fieldContext_Framework_controls(ctx, field)
case "lightLogoURL":
return ec.fieldContext_Framework_lightLogoURL(ctx, field)
case "darkLogoURL":
return ec.fieldContext_Framework_darkLogoURL(ctx, field)
case "createdAt": case "createdAt":
return ec.fieldContext_Framework_createdAt(ctx, field) return ec.fieldContext_Framework_createdAt(ctx, field)
case "updatedAt": case "updatedAt":
@@ -28103,6 +28129,64 @@ func (ec *executionContext) fieldContext_Framework_controls(ctx context.Context,
return fc, nil return fc, nil
} }
func (ec *executionContext) _Framework_lightLogoURL(ctx context.Context, field graphql.CollectedField, obj *types.Framework) (ret graphql.Marshaler) {
return graphql.ResolveField(
ctx,
ec.OperationContext,
field,
ec.fieldContext_Framework_lightLogoURL,
func(ctx context.Context) (any, error) {
return ec.resolvers.Framework().LightLogoURL(ctx, obj)
},
nil,
ec.marshalOString2ᚖstring,
true,
false,
)
}
func (ec *executionContext) fieldContext_Framework_lightLogoURL(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) {
fc = &graphql.FieldContext{
Object: "Framework",
Field: field,
IsMethod: true,
IsResolver: true,
Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) {
return nil, errors.New("field of type String does not have child fields")
},
}
return fc, nil
}
func (ec *executionContext) _Framework_darkLogoURL(ctx context.Context, field graphql.CollectedField, obj *types.Framework) (ret graphql.Marshaler) {
return graphql.ResolveField(
ctx,
ec.OperationContext,
field,
ec.fieldContext_Framework_darkLogoURL,
func(ctx context.Context) (any, error) {
return ec.resolvers.Framework().DarkLogoURL(ctx, obj)
},
nil,
ec.marshalOString2ᚖstring,
true,
false,
)
}
func (ec *executionContext) fieldContext_Framework_darkLogoURL(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) {
fc = &graphql.FieldContext{
Object: "Framework",
Field: field,
IsMethod: true,
IsResolver: true,
Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) {
return nil, errors.New("field of type String does not have child fields")
},
}
return fc, nil
}
func (ec *executionContext) _Framework_createdAt(ctx context.Context, field graphql.CollectedField, obj *types.Framework) (ret graphql.Marshaler) { func (ec *executionContext) _Framework_createdAt(ctx context.Context, field graphql.CollectedField, obj *types.Framework) (ret graphql.Marshaler) {
return graphql.ResolveField( return graphql.ResolveField(
ctx, ctx,
@@ -28327,6 +28411,10 @@ func (ec *executionContext) fieldContext_FrameworkEdge_node(_ context.Context, f
return ec.fieldContext_Framework_organization(ctx, field) return ec.fieldContext_Framework_organization(ctx, field)
case "controls": case "controls":
return ec.fieldContext_Framework_controls(ctx, field) return ec.fieldContext_Framework_controls(ctx, field)
case "lightLogoURL":
return ec.fieldContext_Framework_lightLogoURL(ctx, field)
case "darkLogoURL":
return ec.fieldContext_Framework_darkLogoURL(ctx, field)
case "createdAt": case "createdAt":
return ec.fieldContext_Framework_createdAt(ctx, field) return ec.fieldContext_Framework_createdAt(ctx, field)
case "updatedAt": case "updatedAt":
@@ -48961,6 +49049,10 @@ func (ec *executionContext) fieldContext_UpdateFrameworkPayload_framework(_ cont
return ec.fieldContext_Framework_organization(ctx, field) return ec.fieldContext_Framework_organization(ctx, field)
case "controls": case "controls":
return ec.fieldContext_Framework_controls(ctx, field) return ec.fieldContext_Framework_controls(ctx, field)
case "lightLogoURL":
return ec.fieldContext_Framework_lightLogoURL(ctx, field)
case "darkLogoURL":
return ec.fieldContext_Framework_darkLogoURL(ctx, field)
case "createdAt": case "createdAt":
return ec.fieldContext_Framework_createdAt(ctx, field) return ec.fieldContext_Framework_createdAt(ctx, field)
case "updatedAt": case "updatedAt":
@@ -71372,6 +71464,72 @@ func (ec *executionContext) _Framework(ctx context.Context, sel ast.SelectionSet
continue continue
} }
out.Concurrently(i, func(ctx context.Context) graphql.Marshaler { return innerFunc(ctx, out) })
case "lightLogoURL":
field := field
innerFunc := func(ctx context.Context, _ *graphql.FieldSet) (res graphql.Marshaler) {
defer func() {
if r := recover(); r != nil {
ec.Error(ctx, ec.Recover(ctx, r))
}
}()
res = ec._Framework_lightLogoURL(ctx, field, obj)
return res
}
if field.Deferrable != nil {
dfs, ok := deferred[field.Deferrable.Label]
di := 0
if ok {
dfs.AddField(field)
di = len(dfs.Values) - 1
} else {
dfs = graphql.NewFieldSet([]graphql.CollectedField{field})
deferred[field.Deferrable.Label] = dfs
}
dfs.Concurrently(di, func(ctx context.Context) graphql.Marshaler {
return innerFunc(ctx, dfs)
})
// don't run the out.Concurrently() call below
out.Values[i] = graphql.Null
continue
}
out.Concurrently(i, func(ctx context.Context) graphql.Marshaler { return innerFunc(ctx, out) })
case "darkLogoURL":
field := field
innerFunc := func(ctx context.Context, _ *graphql.FieldSet) (res graphql.Marshaler) {
defer func() {
if r := recover(); r != nil {
ec.Error(ctx, ec.Recover(ctx, r))
}
}()
res = ec._Framework_darkLogoURL(ctx, field, obj)
return res
}
if field.Deferrable != nil {
dfs, ok := deferred[field.Deferrable.Label]
di := 0
if ok {
dfs.AddField(field)
di = len(dfs.Values) - 1
} else {
dfs = graphql.NewFieldSet([]graphql.CollectedField{field})
deferred[field.Deferrable.Label] = dfs
}
dfs.Concurrently(di, func(ctx context.Context) graphql.Marshaler {
return innerFunc(ctx, dfs)
})
// don't run the out.Concurrently() call below
out.Values[i] = graphql.Null
continue
}
out.Concurrently(i, func(ctx context.Context) graphql.Marshaler { return innerFunc(ctx, out) }) out.Concurrently(i, func(ctx context.Context) graphql.Marshaler { return innerFunc(ctx, out) })
case "createdAt": case "createdAt":
out.Values[i] = ec._Framework_createdAt(ctx, field, obj) out.Values[i] = ec._Framework_createdAt(ctx, field, obj)

View File

@@ -1178,6 +1178,8 @@ type Framework struct {
Description *string `json:"description,omitempty"` Description *string `json:"description,omitempty"`
Organization *Organization `json:"organization"` Organization *Organization `json:"organization"`
Controls *ControlConnection `json:"controls"` Controls *ControlConnection `json:"controls"`
LightLogoURL *string `json:"lightLogoURL,omitempty"`
DarkLogoURL *string `json:"darkLogoURL,omitempty"`
CreatedAt time.Time `json:"createdAt"` CreatedAt time.Time `json:"createdAt"`
UpdatedAt time.Time `json:"updatedAt"` UpdatedAt time.Time `json:"updatedAt"`
} }

View File

@@ -1100,6 +1100,24 @@ func (r *frameworkResolver) Controls(ctx context.Context, obj *types.Framework,
return types.NewControlConnection(page, r, obj.ID, controlFilter), nil return types.NewControlConnection(page, r, obj.ID, controlFilter), nil
} }
// LightLogoURL is the resolver for the lightLogoURL field.
func (r *frameworkResolver) LightLogoURL(ctx context.Context, obj *types.Framework) (*string, error) {
r.MustBeAuthorized(ctx, obj.ID, authz.ActionGetLogoUrl)
prb := r.ProboService(ctx, obj.ID.TenantID())
return prb.Frameworks.GenerateLightLogoURL(ctx, obj.ID, 1*time.Hour)
}
// DarkLogoURL is the resolver for the darkLogoURL field.
func (r *frameworkResolver) DarkLogoURL(ctx context.Context, obj *types.Framework) (*string, error) {
r.MustBeAuthorized(ctx, obj.ID, authz.ActionGetLogoUrl)
prb := r.ProboService(ctx, obj.ID.TenantID())
return prb.Frameworks.GenerateDarkLogoURL(ctx, obj.ID, 1*time.Hour)
}
// TotalCount is the resolver for the totalCount field. // TotalCount is the resolver for the totalCount field.
func (r *frameworkConnectionResolver) TotalCount(ctx context.Context, obj *types.FrameworkConnection) (int, error) { func (r *frameworkConnectionResolver) TotalCount(ctx context.Context, obj *types.FrameworkConnection) (int, error) {
r.MustBeAuthorized(ctx, obj.ParentID, authz.ActionTotalCount) r.MustBeAuthorized(ctx, obj.ParentID, authz.ActionTotalCount)

View File

@@ -75,6 +75,8 @@ type DocumentEdge {
type Framework implements Node { type Framework implements Node {
id: ID! id: ID!
name: String! name: String!
lightLogoURL: String @goField(forceResolver: true)
darkLogoURL: String @goField(forceResolver: true)
} }
type Report implements Node { type Report implements Node {

View File

@@ -48,6 +48,7 @@ type Config struct {
type ResolverRoot interface { type ResolverRoot interface {
Audit() AuditResolver Audit() AuditResolver
Document() DocumentResolver Document() DocumentResolver
Framework() FrameworkResolver
Mutation() MutationResolver Mutation() MutationResolver
Organization() OrganizationResolver Organization() OrganizationResolver
Query() QueryResolver Query() QueryResolver
@@ -113,8 +114,10 @@ type ComplexityRoot struct {
} }
Framework struct { Framework struct {
ID func(childComplexity int) int DarkLogoURL func(childComplexity int) int
Name func(childComplexity int) int ID func(childComplexity int) int
LightLogoURL func(childComplexity int) int
Name func(childComplexity int) int
} }
Mutation struct { Mutation struct {
@@ -250,6 +253,10 @@ type DocumentResolver interface {
IsUserAuthorized(ctx context.Context, obj *types.Document) (bool, error) IsUserAuthorized(ctx context.Context, obj *types.Document) (bool, error)
HasUserRequestedAccess(ctx context.Context, obj *types.Document) (bool, error) HasUserRequestedAccess(ctx context.Context, obj *types.Document) (bool, error)
} }
type FrameworkResolver interface {
LightLogoURL(ctx context.Context, obj *types.Framework) (*string, error)
DarkLogoURL(ctx context.Context, obj *types.Framework) (*string, error)
}
type MutationResolver interface { type MutationResolver interface {
RequestAllAccesses(ctx context.Context, input types.RequestAllAccessesInput) (*types.RequestAccessesPayload, error) RequestAllAccesses(ctx context.Context, input types.RequestAllAccessesInput) (*types.RequestAccessesPayload, error)
ExportDocumentPDF(ctx context.Context, input types.ExportDocumentPDFInput) (*types.ExportDocumentPDFPayload, error) ExportDocumentPDF(ctx context.Context, input types.ExportDocumentPDFInput) (*types.ExportDocumentPDFPayload, error)
@@ -440,12 +447,24 @@ func (e *executableSchema) Complexity(ctx context.Context, typeName, field strin
return e.complexity.ExportTrustCenterFilePayload.Data(childComplexity), true return e.complexity.ExportTrustCenterFilePayload.Data(childComplexity), true
case "Framework.darkLogoURL":
if e.complexity.Framework.DarkLogoURL == nil {
break
}
return e.complexity.Framework.DarkLogoURL(childComplexity), true
case "Framework.id": case "Framework.id":
if e.complexity.Framework.ID == nil { if e.complexity.Framework.ID == nil {
break break
} }
return e.complexity.Framework.ID(childComplexity), true return e.complexity.Framework.ID(childComplexity), true
case "Framework.lightLogoURL":
if e.complexity.Framework.LightLogoURL == nil {
break
}
return e.complexity.Framework.LightLogoURL(childComplexity), true
case "Framework.name": case "Framework.name":
if e.complexity.Framework.Name == nil { if e.complexity.Framework.Name == nil {
break break
@@ -1173,6 +1192,8 @@ type DocumentEdge {
type Framework implements Node { type Framework implements Node {
id: ID! id: ID!
name: String! name: String!
lightLogoURL: String @goField(forceResolver: true)
darkLogoURL: String @goField(forceResolver: true)
} }
type Report implements Node { type Report implements Node {
@@ -2138,6 +2159,10 @@ func (ec *executionContext) fieldContext_Audit_framework(_ context.Context, fiel
return ec.fieldContext_Framework_id(ctx, field) return ec.fieldContext_Framework_id(ctx, field)
case "name": case "name":
return ec.fieldContext_Framework_name(ctx, field) return ec.fieldContext_Framework_name(ctx, field)
case "lightLogoURL":
return ec.fieldContext_Framework_lightLogoURL(ctx, field)
case "darkLogoURL":
return ec.fieldContext_Framework_darkLogoURL(ctx, field)
} }
return nil, fmt.Errorf("no field named %q was found under type Framework", field.Name) return nil, fmt.Errorf("no field named %q was found under type Framework", field.Name)
}, },
@@ -2758,6 +2783,64 @@ func (ec *executionContext) fieldContext_Framework_name(_ context.Context, field
return fc, nil return fc, nil
} }
func (ec *executionContext) _Framework_lightLogoURL(ctx context.Context, field graphql.CollectedField, obj *types.Framework) (ret graphql.Marshaler) {
return graphql.ResolveField(
ctx,
ec.OperationContext,
field,
ec.fieldContext_Framework_lightLogoURL,
func(ctx context.Context) (any, error) {
return ec.resolvers.Framework().LightLogoURL(ctx, obj)
},
nil,
ec.marshalOString2ᚖstring,
true,
false,
)
}
func (ec *executionContext) fieldContext_Framework_lightLogoURL(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) {
fc = &graphql.FieldContext{
Object: "Framework",
Field: field,
IsMethod: true,
IsResolver: true,
Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) {
return nil, errors.New("field of type String does not have child fields")
},
}
return fc, nil
}
func (ec *executionContext) _Framework_darkLogoURL(ctx context.Context, field graphql.CollectedField, obj *types.Framework) (ret graphql.Marshaler) {
return graphql.ResolveField(
ctx,
ec.OperationContext,
field,
ec.fieldContext_Framework_darkLogoURL,
func(ctx context.Context) (any, error) {
return ec.resolvers.Framework().DarkLogoURL(ctx, obj)
},
nil,
ec.marshalOString2ᚖstring,
true,
false,
)
}
func (ec *executionContext) fieldContext_Framework_darkLogoURL(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) {
fc = &graphql.FieldContext{
Object: "Framework",
Field: field,
IsMethod: true,
IsResolver: true,
Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) {
return nil, errors.New("field of type String does not have child fields")
},
}
return fc, nil
}
func (ec *executionContext) _Mutation_requestAllAccesses(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) { func (ec *executionContext) _Mutation_requestAllAccesses(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) {
return graphql.ResolveField( return graphql.ResolveField(
ctx, ctx,
@@ -7976,13 +8059,79 @@ func (ec *executionContext) _Framework(ctx context.Context, sel ast.SelectionSet
case "id": case "id":
out.Values[i] = ec._Framework_id(ctx, field, obj) out.Values[i] = ec._Framework_id(ctx, field, obj)
if out.Values[i] == graphql.Null { if out.Values[i] == graphql.Null {
out.Invalids++ atomic.AddUint32(&out.Invalids, 1)
} }
case "name": case "name":
out.Values[i] = ec._Framework_name(ctx, field, obj) out.Values[i] = ec._Framework_name(ctx, field, obj)
if out.Values[i] == graphql.Null { if out.Values[i] == graphql.Null {
out.Invalids++ atomic.AddUint32(&out.Invalids, 1)
} }
case "lightLogoURL":
field := field
innerFunc := func(ctx context.Context, _ *graphql.FieldSet) (res graphql.Marshaler) {
defer func() {
if r := recover(); r != nil {
ec.Error(ctx, ec.Recover(ctx, r))
}
}()
res = ec._Framework_lightLogoURL(ctx, field, obj)
return res
}
if field.Deferrable != nil {
dfs, ok := deferred[field.Deferrable.Label]
di := 0
if ok {
dfs.AddField(field)
di = len(dfs.Values) - 1
} else {
dfs = graphql.NewFieldSet([]graphql.CollectedField{field})
deferred[field.Deferrable.Label] = dfs
}
dfs.Concurrently(di, func(ctx context.Context) graphql.Marshaler {
return innerFunc(ctx, dfs)
})
// don't run the out.Concurrently() call below
out.Values[i] = graphql.Null
continue
}
out.Concurrently(i, func(ctx context.Context) graphql.Marshaler { return innerFunc(ctx, out) })
case "darkLogoURL":
field := field
innerFunc := func(ctx context.Context, _ *graphql.FieldSet) (res graphql.Marshaler) {
defer func() {
if r := recover(); r != nil {
ec.Error(ctx, ec.Recover(ctx, r))
}
}()
res = ec._Framework_darkLogoURL(ctx, field, obj)
return res
}
if field.Deferrable != nil {
dfs, ok := deferred[field.Deferrable.Label]
di := 0
if ok {
dfs.AddField(field)
di = len(dfs.Values) - 1
} else {
dfs = graphql.NewFieldSet([]graphql.CollectedField{field})
deferred[field.Deferrable.Label] = dfs
}
dfs.Concurrently(di, func(ctx context.Context) graphql.Marshaler {
return innerFunc(ctx, dfs)
})
// don't run the out.Concurrently() call below
out.Values[i] = graphql.Null
continue
}
out.Concurrently(i, func(ctx context.Context) graphql.Marshaler { return innerFunc(ctx, out) })
default: default:
panic("unknown field " + strconv.Quote(field.Name)) panic("unknown field " + strconv.Quote(field.Name))
} }

View File

@@ -93,8 +93,10 @@ type ExportTrustCenterFilePayload struct {
} }
type Framework struct { type Framework struct {
ID gid.GID `json:"id"` ID gid.GID `json:"id"`
Name string `json:"name"` Name string `json:"name"`
LightLogoURL *string `json:"lightLogoURL,omitempty"`
DarkLogoURL *string `json:"darkLogoURL,omitempty"`
} }
func (Framework) IsNode() {} func (Framework) IsNode() {}

View File

@@ -120,6 +120,20 @@ func (r *documentResolver) HasUserRequestedAccess(ctx context.Context, obj *type
return false, nil return false, nil
} }
// LightLogoURL is the resolver for the lightLogoURL field.
func (r *frameworkResolver) LightLogoURL(ctx context.Context, obj *types.Framework) (*string, error) {
publicTrustService := r.PublicTrustService(ctx, obj.ID.TenantID())
return publicTrustService.Frameworks.GenerateLightLogoURL(ctx, obj.ID, 1*time.Hour)
}
// DarkLogoURL is the resolver for the darkLogoURL field.
func (r *frameworkResolver) DarkLogoURL(ctx context.Context, obj *types.Framework) (*string, error) {
privateTrustService := r.PublicTrustService(ctx, obj.ID.TenantID())
return privateTrustService.Frameworks.GenerateDarkLogoURL(ctx, obj.ID, 1*time.Hour)
}
// RequestAllAccesses is the resolver for the requestAllAccesses field. // RequestAllAccesses is the resolver for the requestAllAccesses field.
func (r *mutationResolver) RequestAllAccesses(ctx context.Context, input types.RequestAllAccessesInput) (*types.RequestAccessesPayload, error) { func (r *mutationResolver) RequestAllAccesses(ctx context.Context, input types.RequestAllAccessesInput) (*types.RequestAccessesPayload, error) {
publicTrustService := r.PublicTrustService(ctx, input.TrustCenterID.TenantID()) publicTrustService := r.PublicTrustService(ctx, input.TrustCenterID.TenantID())
@@ -990,6 +1004,9 @@ func (r *Resolver) Audit() schema.AuditResolver { return &auditResolver{r} }
// Document returns schema.DocumentResolver implementation. // Document returns schema.DocumentResolver implementation.
func (r *Resolver) Document() schema.DocumentResolver { return &documentResolver{r} } func (r *Resolver) Document() schema.DocumentResolver { return &documentResolver{r} }
// Framework returns schema.FrameworkResolver implementation.
func (r *Resolver) Framework() schema.FrameworkResolver { return &frameworkResolver{r} }
// Mutation returns schema.MutationResolver implementation. // Mutation returns schema.MutationResolver implementation.
func (r *Resolver) Mutation() schema.MutationResolver { return &mutationResolver{r} } func (r *Resolver) Mutation() schema.MutationResolver { return &mutationResolver{r} }
@@ -1017,6 +1034,7 @@ func (r *Resolver) TrustCenterReference() schema.TrustCenterReferenceResolver {
type auditResolver struct{ *Resolver } type auditResolver struct{ *Resolver }
type documentResolver struct{ *Resolver } type documentResolver struct{ *Resolver }
type frameworkResolver struct{ *Resolver }
type mutationResolver struct{ *Resolver } type mutationResolver struct{ *Resolver }
type organizationResolver struct{ *Resolver } type organizationResolver struct{ *Resolver }
type queryResolver struct{ *Resolver } type queryResolver struct{ *Resolver }

View File

@@ -16,12 +16,13 @@ package trust
import ( import (
"context" "context"
"time"
"fmt" "fmt"
"go.gearno.de/kit/pg"
"go.probo.inc/probo/pkg/coredata" "go.probo.inc/probo/pkg/coredata"
"go.probo.inc/probo/pkg/gid" "go.probo.inc/probo/pkg/gid"
"go.gearno.de/kit/pg"
) )
type FrameworkService struct { type FrameworkService struct {
@@ -49,3 +50,87 @@ func (s FrameworkService) Get(
return framework, nil return framework, nil
} }
func (s FrameworkService) GenerateLightLogoURL(
ctx context.Context,
frameworkID gid.GID,
expiresIn time.Duration,
) (*string, error) {
file := &coredata.File{}
err := s.svc.pg.WithConn(
ctx,
func(conn pg.Conn) error {
framework := &coredata.Framework{}
if err := framework.LoadByID(ctx, conn, s.svc.scope, frameworkID); err != nil {
return fmt.Errorf("cannot load framework: %w", err)
}
if framework.LightLogoFileID == nil {
return nil
}
if err := file.LoadByID(ctx, conn, s.svc.scope, *framework.LightLogoFileID); err != nil {
return fmt.Errorf("cannot load file: %w", err)
}
return nil
},
)
if err != nil {
return nil, err
}
if file.FileKey == "" {
return nil, nil
}
presignedURL, err := s.svc.fileManager.GenerateFileUrl(ctx, file, expiresIn)
if err != nil {
return nil, fmt.Errorf("cannot generate file URL: %w", err)
}
return &presignedURL, nil
}
func (s FrameworkService) GenerateDarkLogoURL(
ctx context.Context,
frameworkID gid.GID,
expiresIn time.Duration,
) (*string, error) {
file := &coredata.File{}
err := s.svc.pg.WithConn(
ctx,
func(conn pg.Conn) error {
framework := &coredata.Framework{}
if err := framework.LoadByID(ctx, conn, s.svc.scope, frameworkID); err != nil {
return fmt.Errorf("cannot load framework: %w", err)
}
if framework.DarkLogoFileID == nil {
return nil
}
if err := file.LoadByID(ctx, conn, s.svc.scope, *framework.DarkLogoFileID); err != nil {
return fmt.Errorf("cannot load file: %w", err)
}
return nil
},
)
if err != nil {
return nil, err
}
if file.FileKey == "" {
return nil, nil
}
presignedURL, err := s.svc.fileManager.GenerateFileUrl(ctx, file, expiresIn)
if err != nil {
return nil, fmt.Errorf("cannot generate file URL: %w", err)
}
return &presignedURL, nil
}