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,
ActionGetOrganization: NonEmployeeRoles,
ActionListControls: NonEmployeeRoles,
ActionGetLogoUrl: NonEmployeeRoles,
ActionCreateControl: EditRoles,
ActionUpdateFramework: EditRoles,

View File

@@ -30,13 +30,15 @@ import (
type (
Framework struct {
ID gid.GID `db:"id"`
OrganizationID gid.GID `db:"organization_id"`
ReferenceID string `db:"reference_id"`
Name string `db:"name"`
Description *string `db:"description"`
CreatedAt time.Time `db:"created_at"`
UpdatedAt time.Time `db:"updated_at"`
ID gid.GID `db:"id"`
OrganizationID gid.GID `db:"organization_id"`
ReferenceID string `db:"reference_id"`
Name string `db:"name"`
Description *string `db:"description"`
LightLogoFileID *gid.GID `db:"light_logo_file_id"`
DarkLogoFileID *gid.GID `db:"dark_logo_file_id"`
CreatedAt time.Time `db:"created_at"`
UpdatedAt time.Time `db:"updated_at"`
}
Frameworks []*Framework
@@ -121,6 +123,8 @@ SELECT
reference_id,
name,
description,
light_logo_file_id,
dark_logo_file_id,
created_at,
updated_at
FROM
@@ -128,7 +132,7 @@ FROM
WHERE
%s
AND organization_id = @organization_id
AND %s
AND %s
`
q = fmt.Sprintf(q, scope.SQLFragment(), cursor.SQLFragment())
@@ -164,6 +168,8 @@ SELECT
reference_id,
name,
description,
light_logo_file_id,
dark_logo_file_id,
created_at,
updated_at
FROM
@@ -210,6 +216,8 @@ SELECT
reference_id,
name,
description,
light_logo_file_id,
dark_logo_file_id,
created_at,
updated_at
FROM
@@ -257,6 +265,8 @@ INSERT INTO
reference_id,
name,
description,
light_logo_file_id,
dark_logo_file_id,
created_at,
updated_at
)
@@ -267,20 +277,24 @@ VALUES (
@reference_id,
@name,
@description,
@light_logo_file_id,
@dark_logo_file_id,
@created_at,
@updated_at
);
`
args := pgx.StrictNamedArgs{
"tenant_id": scope.GetTenantID(),
"framework_id": f.ID,
"organization_id": f.OrganizationID,
"reference_id": f.ReferenceID,
"name": f.Name,
"description": f.Description,
"created_at": f.CreatedAt,
"updated_at": f.UpdatedAt,
"tenant_id": scope.GetTenantID(),
"framework_id": f.ID,
"organization_id": f.OrganizationID,
"reference_id": f.ReferenceID,
"name": f.Name,
"description": f.Description,
"light_logo_file_id": f.LightLogoFileID,
"dark_logo_file_id": f.DarkLogoFileID,
"created_at": f.CreatedAt,
"updated_at": f.UpdatedAt,
}
_, 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"
"io"
"os"
"strings"
"time"
"github.com/aws/aws-sdk-go-v2/aws"
@@ -64,8 +65,12 @@ type (
ImportFrameworkRequest struct {
Framework struct {
ID string `json:"id"`
Name string `json:"name"`
ID string `json:"id"`
Name string `json:"name"`
Logo *struct {
Light string `json:"light"`
Dark string `json:"dark"`
} `json:"logo,omitempty"`
Controls []struct {
ID string `json:"id"`
Name string `json:"name"`
@@ -510,6 +515,54 @@ func (s FrameworkService) Import(
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 {
return fmt.Errorf("cannot insert framework: %w", err)
}
@@ -890,3 +943,87 @@ func (s *FrameworkService) BuildAndUploadExport(ctx context.Context, exportJobID
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
filter: ControlFilter
): ControlConnection! @goField(forceResolver: true)
lightLogoURL: String @goField(forceResolver: true)
darkLogoURL: String @goField(forceResolver: true)
createdAt: Datetime!
updatedAt: Datetime!

View File

@@ -754,8 +754,10 @@ type ComplexityRoot struct {
Framework struct {
Controls func(childComplexity int, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.ControlOrderBy, filter *types.ControlFilter) int
CreatedAt func(childComplexity int) int
DarkLogoURL func(childComplexity int) int
Description func(childComplexity int) int
ID func(childComplexity int) int
LightLogoURL func(childComplexity int) int
Name func(childComplexity int) int
Organization func(childComplexity int) int
UpdatedAt func(childComplexity int) int
@@ -1933,6 +1935,8 @@ type FileResolver interface {
type FrameworkResolver interface {
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)
LightLogoURL(ctx context.Context, obj *types.Framework) (*string, error)
DarkLogoURL(ctx context.Context, obj *types.Framework) (*string, error)
}
type FrameworkConnectionResolver interface {
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
case "Framework.darkLogoURL":
if e.complexity.Framework.DarkLogoURL == nil {
break
}
return e.complexity.Framework.DarkLogoURL(childComplexity), true
case "Framework.description":
if e.complexity.Framework.Description == nil {
break
@@ -4155,6 +4165,12 @@ func (e *executableSchema) Complexity(ctx context.Context, typeName, field strin
}
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":
if e.complexity.Framework.Name == nil {
break
@@ -11442,6 +11458,8 @@ type Framework implements Node {
orderBy: ControlOrder
filter: ControlFilter
): ControlConnection! @goField(forceResolver: true)
lightLogoURL: String @goField(forceResolver: true)
darkLogoURL: String @goField(forceResolver: true)
createdAt: Datetime!
updatedAt: Datetime!
@@ -18952,6 +18970,10 @@ func (ec *executionContext) fieldContext_Audit_framework(_ context.Context, fiel
return ec.fieldContext_Framework_organization(ctx, field)
case "controls":
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":
return ec.fieldContext_Framework_createdAt(ctx, field)
case "updatedAt":
@@ -20542,6 +20564,10 @@ func (ec *executionContext) fieldContext_Control_framework(_ context.Context, fi
return ec.fieldContext_Framework_organization(ctx, field)
case "controls":
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":
return ec.fieldContext_Framework_createdAt(ctx, field)
case "updatedAt":
@@ -28103,6 +28129,64 @@ func (ec *executionContext) fieldContext_Framework_controls(ctx context.Context,
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) {
return graphql.ResolveField(
ctx,
@@ -28327,6 +28411,10 @@ func (ec *executionContext) fieldContext_FrameworkEdge_node(_ context.Context, f
return ec.fieldContext_Framework_organization(ctx, field)
case "controls":
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":
return ec.fieldContext_Framework_createdAt(ctx, field)
case "updatedAt":
@@ -48961,6 +49049,10 @@ func (ec *executionContext) fieldContext_UpdateFrameworkPayload_framework(_ cont
return ec.fieldContext_Framework_organization(ctx, field)
case "controls":
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":
return ec.fieldContext_Framework_createdAt(ctx, field)
case "updatedAt":
@@ -71372,6 +71464,72 @@ func (ec *executionContext) _Framework(ctx context.Context, sel ast.SelectionSet
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) })
case "createdAt":
out.Values[i] = ec._Framework_createdAt(ctx, field, obj)

View File

@@ -1178,6 +1178,8 @@ type Framework struct {
Description *string `json:"description,omitempty"`
Organization *Organization `json:"organization"`
Controls *ControlConnection `json:"controls"`
LightLogoURL *string `json:"lightLogoURL,omitempty"`
DarkLogoURL *string `json:"darkLogoURL,omitempty"`
CreatedAt time.Time `json:"createdAt"`
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
}
// 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.
func (r *frameworkConnectionResolver) TotalCount(ctx context.Context, obj *types.FrameworkConnection) (int, error) {
r.MustBeAuthorized(ctx, obj.ParentID, authz.ActionTotalCount)

View File

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

View File

@@ -48,6 +48,7 @@ type Config struct {
type ResolverRoot interface {
Audit() AuditResolver
Document() DocumentResolver
Framework() FrameworkResolver
Mutation() MutationResolver
Organization() OrganizationResolver
Query() QueryResolver
@@ -113,8 +114,10 @@ type ComplexityRoot struct {
}
Framework struct {
ID func(childComplexity int) int
Name func(childComplexity int) int
DarkLogoURL func(childComplexity int) int
ID func(childComplexity int) int
LightLogoURL func(childComplexity int) int
Name func(childComplexity int) int
}
Mutation struct {
@@ -250,6 +253,10 @@ type DocumentResolver interface {
IsUserAuthorized(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 {
RequestAllAccesses(ctx context.Context, input types.RequestAllAccessesInput) (*types.RequestAccessesPayload, 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
case "Framework.darkLogoURL":
if e.complexity.Framework.DarkLogoURL == nil {
break
}
return e.complexity.Framework.DarkLogoURL(childComplexity), true
case "Framework.id":
if e.complexity.Framework.ID == nil {
break
}
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":
if e.complexity.Framework.Name == nil {
break
@@ -1173,6 +1192,8 @@ type DocumentEdge {
type Framework implements Node {
id: ID!
name: String!
lightLogoURL: String @goField(forceResolver: true)
darkLogoURL: String @goField(forceResolver: true)
}
type Report implements Node {
@@ -2138,6 +2159,10 @@ func (ec *executionContext) fieldContext_Audit_framework(_ context.Context, fiel
return ec.fieldContext_Framework_id(ctx, field)
case "name":
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)
},
@@ -2758,6 +2783,64 @@ func (ec *executionContext) fieldContext_Framework_name(_ context.Context, field
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) {
return graphql.ResolveField(
ctx,
@@ -7976,13 +8059,79 @@ func (ec *executionContext) _Framework(ctx context.Context, sel ast.SelectionSet
case "id":
out.Values[i] = ec._Framework_id(ctx, field, obj)
if out.Values[i] == graphql.Null {
out.Invalids++
atomic.AddUint32(&out.Invalids, 1)
}
case "name":
out.Values[i] = ec._Framework_name(ctx, field, obj)
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:
panic("unknown field " + strconv.Quote(field.Name))
}

View File

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

View File

@@ -120,6 +120,20 @@ func (r *documentResolver) HasUserRequestedAccess(ctx context.Context, obj *type
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.
func (r *mutationResolver) RequestAllAccesses(ctx context.Context, input types.RequestAllAccessesInput) (*types.RequestAccessesPayload, error) {
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.
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.
func (r *Resolver) Mutation() schema.MutationResolver { return &mutationResolver{r} }
@@ -1017,6 +1034,7 @@ func (r *Resolver) TrustCenterReference() schema.TrustCenterReferenceResolver {
type auditResolver struct{ *Resolver }
type documentResolver struct{ *Resolver }
type frameworkResolver struct{ *Resolver }
type mutationResolver struct{ *Resolver }
type organizationResolver struct{ *Resolver }
type queryResolver struct{ *Resolver }

View File

@@ -16,12 +16,13 @@ package trust
import (
"context"
"time"
"fmt"
"go.gearno.de/kit/pg"
"go.probo.inc/probo/pkg/coredata"
"go.probo.inc/probo/pkg/gid"
"go.gearno.de/kit/pg"
)
type FrameworkService struct {
@@ -49,3 +50,87 @@ func (s FrameworkService) Get(
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
}