Migrate audit reports to the files table

Signed-off-by: Ludovic Vielle <ludovic@probo.com>
This commit is contained in:
Ludovic Vielle
2026-06-03 22:19:17 +02:00
parent b0a0f0efc9
commit 0e4d73bb0f
31 changed files with 517 additions and 947 deletions

View File

@@ -8,7 +8,6 @@ package console_v1
import (
"context"
"errors"
"time"
"github.com/vikstrous/dataloadgen"
"go.gearno.de/kit/log"
@@ -68,50 +67,30 @@ func (r *auditResolver) Framework(ctx context.Context, obj *types.Audit) (*types
return types.NewFramework(framework), nil
}
// Report is the resolver for the report field.
func (r *auditResolver) Report(ctx context.Context, obj *types.Audit) (*types.Report, error) {
// ReportFile is the resolver for the reportFile field.
func (r *auditResolver) ReportFile(ctx context.Context, obj *types.Audit) (*types.File, error) {
if _, err := r.authorize(ctx, obj.ID, probo.ActionReportGet); err != nil {
return nil, err
}
if obj.Report == nil {
if obj.ReportFile == nil {
return nil, nil
}
loaders := dataloader.FromContext(ctx)
report, err := loaders.Report.Load(ctx, obj.Report.ID)
file, err := loaders.File.Load(ctx, obj.ReportFile.ID)
if err != nil {
if errors.Is(err, dataloadgen.ErrNotFound) {
return nil, gqlutils.NotFound(ctx, err)
}
r.logger.ErrorCtx(ctx, "cannot load report", log.Error(err))
r.logger.ErrorCtx(ctx, "cannot load report file", log.Error(err))
return nil, gqlutils.Internal(ctx)
}
return types.NewReport(report), nil
}
// ReportURL is the resolver for the reportUrl field.
func (r *auditResolver) ReportURL(ctx context.Context, obj *types.Audit) (*string, error) {
scope, err := r.authorize(ctx, obj.ID, probo.ActionReportGetReportUrl)
if err != nil {
return nil, err
}
if obj.Report == nil {
return nil, nil
}
url, err := r.probo.Audits.GenerateReportURL(ctx, scope, obj.ID, 15*time.Minute)
if err != nil {
r.logger.ErrorCtx(ctx, "cannot generate report URL", log.Error(err))
return nil, gqlutils.Internal(ctx)
}
return url, nil
return types.NewFile(file), nil
}
// Controls is the resolver for the controls field.
@@ -431,7 +410,7 @@ func (r *mutationResolver) CreateAudit(ctx context.Context, input types.CreateAu
},
}
audit, err = r.probo.Audits.UploadReport(ctx, scope, uploadReq)
audit, err = r.probo.Audits.UploadReport(ctx, scope, &uploadReq)
if err != nil {
if validationErrors, ok := errors.AsType[validator.ValidationErrors](err); ok {
return nil, gqlutils.InvalidValidationErrors(ctx, validationErrors)
@@ -514,7 +493,7 @@ func (r *mutationResolver) UploadAuditReport(ctx context.Context, input types.Up
},
}
audit, err := r.probo.Audits.UploadReport(ctx, scope, req)
audit, err := r.probo.Audits.UploadReport(ctx, scope, &req)
if err != nil {
if validationErrors, ok := errors.AsType[validator.ValidationErrors](err); ok {
return nil, gqlutils.InvalidValidationErrors(ctx, validationErrors)
@@ -704,43 +683,6 @@ func (r *mutationResolver) PublishFindingList(ctx context.Context, input types.P
}, nil
}
// DownloadURL is the resolver for the downloadUrl field.
func (r *reportResolver) DownloadURL(ctx context.Context, obj *types.Report) (*string, error) {
scope, err := r.authorize(ctx, obj.ID, probo.ActionReportDownloadUrlGet)
if err != nil {
return nil, err
}
url, err := r.probo.Reports.GenerateDownloadURL(ctx, scope, obj.ID, 15*time.Minute)
if err != nil {
r.logger.ErrorCtx(ctx, "cannot generate download URL", log.Error(err))
return nil, gqlutils.Internal(ctx)
}
return url, nil
}
// Audit is the resolver for the audit field.
func (r *reportResolver) Audit(ctx context.Context, obj *types.Report) (*types.Audit, error) {
scope, err := r.authorize(ctx, obj.ID, probo.ActionAuditGet)
if err != nil {
return nil, err
}
audit, err := r.probo.Audits.GetByReportID(ctx, scope, obj.ID)
if err != nil {
r.logger.ErrorCtx(ctx, "cannot load audit for report", log.Error(err))
return nil, gqlutils.Internal(ctx)
}
return types.NewAudit(audit), nil
}
// Permission is the resolver for the permission field.
func (r *reportResolver) Permission(ctx context.Context, obj *types.Report, action string) (bool, error) {
return r.Resolver.Permission(ctx, obj, action)
}
// Audit returns schema.AuditResolver implementation.
func (r *Resolver) Audit() schema.AuditResolver { return &auditResolver{r} }
@@ -757,11 +699,7 @@ func (r *Resolver) FindingConnection() schema.FindingConnectionResolver {
return &findingConnectionResolver{r}
}
// Report returns schema.ReportResolver implementation.
func (r *Resolver) Report() schema.ReportResolver { return &reportResolver{r} }
type auditResolver struct{ *Resolver }
type auditConnectionResolver struct{ *Resolver }
type findingResolver struct{ *Resolver }
type findingConnectionResolver struct{ *Resolver }
type reportResolver struct{ *Resolver }

View File

@@ -279,16 +279,6 @@ func (r *queryResolver) Node(ctx context.Context, id gid.GID) (types.Node, error
return types.NewObligation(obligation), nil
}
case coredata.ReportEntityType:
action = probo.ActionReportGet
loadNode = func(ctx context.Context, scope *coredata.Scope, id gid.GID) (types.Node, error) {
report, err := r.probo.Reports.Get(ctx, scope, id)
if err != nil {
return nil, err
}
return types.NewReport(report), nil
}
case coredata.ProcessingActivityEntityType:
action = probo.ActionProcessingActivityList
loadNode = func(ctx context.Context, scope *coredata.Scope, id gid.GID) (types.Node, error) {

View File

@@ -62,7 +62,6 @@ type (
Measure *dataloadgen.Loader[gid.GID, *coredata.Measure]
Task *dataloadgen.Loader[gid.GID, *coredata.Task]
File *dataloadgen.Loader[gid.GID, *coredata.File]
Report *dataloadgen.Loader[gid.GID, *coredata.Report]
CookieBanner *dataloadgen.Loader[gid.GID, *coredata.CookieBanner]
CookieCategory *dataloadgen.Loader[gid.GID, *coredata.CookieCategory]
CommonTrackerPattern *dataloadgen.Loader[gid.GID, *coredata.CommonTrackerPattern]
@@ -114,7 +113,6 @@ func (f *batchFetcher) newLoaders() *Loaders {
Measure: dataloadgen.NewMappedLoader(f.fetchMeasures),
Task: dataloadgen.NewMappedLoader(f.fetchTasks),
File: dataloadgen.NewMappedLoader(f.fetchFiles),
Report: dataloadgen.NewMappedLoader(f.fetchReports),
CookieBanner: dataloadgen.NewMappedLoader(f.fetchCookieBanners),
CookieCategory: dataloadgen.NewMappedLoader(f.fetchCookieCategories),
CommonTrackerPattern: dataloadgen.NewMappedLoader(f.fetchCommonTrackerPatterns),
@@ -286,22 +284,6 @@ func (f *batchFetcher) fetchFiles(ctx context.Context, keys []gid.GID) (map[gid.
return result, nil
}
func (f *batchFetcher) fetchReports(ctx context.Context, keys []gid.GID) (map[gid.GID]*coredata.Report, error) {
scope := coredata.NewScopeFromObjectID(keys[0])
reports, err := f.probo.Reports.GetByIDs(ctx, scope, keys...)
if err != nil {
return nil, fmt.Errorf("cannot batch load reports: %w", err)
}
result := make(map[gid.GID]*coredata.Report, len(reports))
for _, v := range reports {
result[v.ID] = v
}
return result, nil
}
func (f *batchFetcher) fetchCookieBanners(ctx context.Context, keys []gid.GID) (map[gid.GID]*coredata.CookieBanner, error) {
scope := coredata.NewScopeFromObjectID(keys[0])

View File

@@ -151,8 +151,7 @@ type Audit implements Node {
framework: Framework @goField(forceResolver: true)
validFrom: Datetime
validUntil: Datetime
report: Report @goField(forceResolver: true)
reportUrl: String @goField(forceResolver: true)
reportFile: File @goField(forceResolver: true)
state: AuditState!
controls(
@@ -208,20 +207,6 @@ type Finding implements Node {
permission(action: String!): Boolean! @goField(forceResolver: true)
}
type Report implements Node {
id: ID!
objectKey: String!
mimeType: String!
filename: String!
size: Int!
downloadUrl: String @goField(forceResolver: true)
createdAt: Datetime!
updatedAt: Datetime!
audit: Audit @goField(forceResolver: true)
permission(action: String!): Boolean! @goField(forceResolver: true)
}
type AuditConnection
@goModel(
model: "go.probo.inc/probo/pkg/server/api/console/v1/types.AuditConnection"

View File

@@ -334,7 +334,8 @@ type TrustCenterDocumentAccess
id: ID!
status: TrustCenterDocumentAccessStatus!
document: Document @goField(forceResolver: true)
report: Report @goField(forceResolver: true)
reportFile: File @goField(forceResolver: true)
audit: Audit @goField(forceResolver: true)
trustCenterFile: TrustCenterFile @goField(forceResolver: true)
}

View File

@@ -1056,24 +1056,55 @@ func (r *trustCenterDocumentAccessResolver) Document(ctx context.Context, obj *t
return types.NewDocument(document), nil
}
// Report is the resolver for the report field.
func (r *trustCenterDocumentAccessResolver) Report(ctx context.Context, obj *types.TrustCenterDocumentAccess) (*types.Report, error) {
scope, err := r.authorize(ctx, obj.TrustCenterAccessID, probo.ActionReportGet)
// ReportFile is the resolver for the reportFile field.
func (r *trustCenterDocumentAccessResolver) ReportFile(ctx context.Context, obj *types.TrustCenterDocumentAccess) (*types.File, error) {
if _, err := r.authorize(ctx, obj.ID, probo.ActionFileGet); err != nil {
return nil, err
}
if obj.ReportFile == nil {
return nil, nil
}
loaders := dataloader.FromContext(ctx)
file, err := loaders.File.Load(ctx, obj.ReportFile.ID)
if err != nil {
if errors.Is(err, coredata.ErrResourceNotFound) || errors.Is(err, dataloadgen.ErrNotFound) {
return nil, gqlutils.NotFound(ctx, err)
}
r.logger.ErrorCtx(ctx, "cannot load report file", log.Error(err))
return nil, gqlutils.Internal(ctx)
}
return types.NewFile(file), nil
}
// Audit is the resolver for the audit field.
func (r *trustCenterDocumentAccessResolver) Audit(ctx context.Context, obj *types.TrustCenterDocumentAccess) (*types.Audit, error) {
scope, err := r.authorize(ctx, obj.ID, probo.ActionAuditGet)
if err != nil {
return nil, err
}
if obj.ReportID == nil {
if obj.ReportFileID == nil {
return nil, nil
}
report, err := r.probo.Reports.Get(ctx, scope, *obj.ReportID)
audit, err := r.probo.Audits.GetByReportFileID(ctx, scope, *obj.ReportFileID)
if err != nil {
r.logger.ErrorCtx(ctx, "cannot load report", log.Error(err))
if errors.Is(err, coredata.ErrResourceNotFound) {
return nil, nil
}
r.logger.ErrorCtx(ctx, "cannot load audit for report file", log.Error(err))
return nil, gqlutils.Internal(ctx)
}
return types.NewReport(report), nil
return types.NewAudit(audit), nil
}
// TrustCenterFile is the resolver for the trustCenterFile field.

View File

@@ -77,9 +77,9 @@ func NewAudit(a *coredata.Audit) *Audit {
UpdatedAt: a.UpdatedAt,
}
if a.ReportID != nil {
node.Report = &Report{
ID: *a.ReportID,
if a.ReportFileID != nil {
node.ReportFile = &File{
ID: *a.ReportFileID,
}
}

View File

@@ -1,31 +0,0 @@
// Copyright (c) 2025-2026 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 (
"go.probo.inc/probo/pkg/coredata"
)
func NewReport(r *coredata.Report) *Report {
return &Report{
ID: r.ID,
ObjectKey: r.ObjectKey,
MimeType: r.MimeType,
Filename: r.Filename,
Size: int(r.Size),
CreatedAt: r.CreatedAt,
UpdatedAt: r.UpdatedAt,
}
}

View File

@@ -42,13 +42,13 @@ type (
UpdatedAt time.Time `json:"updatedAt"`
TrustCenterAccess *TrustCenterAccess `json:"trustCenterAccess"`
Document *Document `json:"document,omitempty"`
Report *Report `json:"report,omitempty"`
ReportFile *File `json:"reportFile,omitempty"`
TrustCenterFile *TrustCenterFile `json:"trustCenterFile,omitempty"`
// Internal fields used by resolvers
TrustCenterAccessID gid.GID `json:"-"`
DocumentID *gid.GID `json:"-"`
ReportID *gid.GID `json:"-"`
ReportFileID *gid.GID `json:"-"`
TrustCenterFileID *gid.GID `json:"-"`
}
)
@@ -62,7 +62,7 @@ func NewTrustCenterDocumentAccess(tcda *coredata.TrustCenterDocumentAccess) *Tru
UpdatedAt: tcda.UpdatedAt,
TrustCenterAccessID: tcda.TrustCenterAccessID,
DocumentID: tcda.DocumentID,
ReportID: tcda.ReportID,
ReportFileID: tcda.ReportFileID,
TrustCenterFileID: tcda.TrustCenterFileID,
}
@@ -72,9 +72,9 @@ func NewTrustCenterDocumentAccess(tcda *coredata.TrustCenterDocumentAccess) *Tru
}
}
if tcda.ReportID != nil {
object.Report = &Report{
ID: *tcda.ReportID,
if tcda.ReportFileID != nil {
object.ReportFile = &File{
ID: *tcda.ReportFileID,
}
}