From 10b9ded41cf7a179707fa7b59a449408165e6928 Mon Sep 17 00:00:00 2001 From: gearnode Date: Sat, 1 Mar 2025 21:02:28 +0100 Subject: [PATCH] Add delete evidence Signed-off-by: gearnode --- .../console/src/pages/ControlOverviewPage.tsx | 106 ++++++++ ...rviewPageDeleteEvidenceMutation.graphql.ts | 132 ++++++++++ .../ControlOverviewPageQuery.graphql.ts | 4 +- ...rviewPageUploadEvidenceMutation.graphql.ts | 4 +- pkg/api/console/v1/schema.graphql | 9 + pkg/api/console/v1/schema/schema.go | 235 ++++++++++++++++++ pkg/api/console/v1/types/types.go | 8 + pkg/api/console/v1/v1_resolver.go | 12 + pkg/probo/coredata/evidence.go | 22 ++ .../coredata/evidence_state_transition.go | 23 ++ pkg/probo/delete_evidence.go | 33 +++ 11 files changed, 584 insertions(+), 4 deletions(-) create mode 100644 apps/console/src/pages/__generated__/ControlOverviewPageDeleteEvidenceMutation.graphql.ts create mode 100644 pkg/probo/delete_evidence.go diff --git a/apps/console/src/pages/ControlOverviewPage.tsx b/apps/console/src/pages/ControlOverviewPage.tsx index 6a61da612..b6e5b709c 100644 --- a/apps/console/src/pages/ControlOverviewPage.tsx +++ b/apps/console/src/pages/ControlOverviewPage.tsx @@ -53,6 +53,7 @@ import type { ControlOverviewPageUpdateTaskStateMutation as ControlOverviewPageU import type { ControlOverviewPageCreateTaskMutation as ControlOverviewPageCreateTaskMutationType } from "./__generated__/ControlOverviewPageCreateTaskMutation.graphql"; import type { ControlOverviewPageDeleteTaskMutation as ControlOverviewPageDeleteTaskMutationType } from "./__generated__/ControlOverviewPageDeleteTaskMutation.graphql"; import type { ControlOverviewPageUploadEvidenceMutation as ControlOverviewPageUploadEvidenceMutationType } from "./__generated__/ControlOverviewPageUploadEvidenceMutation.graphql"; +import type { ControlOverviewPageDeleteEvidenceMutation as ControlOverviewPageDeleteEvidenceMutationType } from "./__generated__/ControlOverviewPageDeleteEvidenceMutation.graphql"; const controlOverviewPageQuery = graphql` query ControlOverviewPageQuery($controlId: ID!) { @@ -155,6 +156,17 @@ const uploadEvidenceMutation = graphql` } `; +const deleteEvidenceMutation = graphql` + mutation ControlOverviewPageDeleteEvidenceMutation( + $input: DeleteEvidenceInput! + $connections: [ID!]! + ) { + deleteEvidence(input: $input) { + deletedEvidenceId @deleteEdge(connections: $connections) + } + } +`; + // Add a GraphQL query to fetch the fileUrl for an evidence item const getEvidenceFileUrlQuery = graphql` query ControlOverviewPageGetEvidenceFileUrlQuery($evidenceId: ID!) { @@ -192,6 +204,10 @@ function ControlOverviewPageContent({ useMutation( uploadEvidenceMutation ); + const [deleteEvidence] = + useMutation( + deleteEvidenceMutation + ); const [isCreateTaskOpen, setIsCreateTaskOpen] = useState(false); const [newTaskName, setNewTaskName] = useState(""); @@ -232,6 +248,13 @@ function ControlOverviewPageContent({ } | null>(null); const [isLoadingFileUrl, setIsLoadingFileUrl] = useState(false); + const [isDeleteEvidenceOpen, setIsDeleteEvidenceOpen] = useState(false); + const [evidenceToDelete, setEvidenceToDelete] = useState<{ + id: string; + filename: string; + taskId: string; + } | null>(null); + const tasks = data.control.tasks?.edges.map((edge) => edge.node) || []; const getEvidenceConnectionId = useCallback( @@ -604,6 +627,47 @@ function ControlOverviewPageContent({ }); }; + const handleDeleteEvidence = ( + evidenceId: string, + filename: string, + taskId: string + ) => { + setEvidenceToDelete({ id: evidenceId, filename, taskId }); + setIsDeleteEvidenceOpen(true); + }; + + const confirmDeleteEvidence = () => { + if (!evidenceToDelete) return; + + const evidenceConnectionId = getEvidenceConnectionId( + evidenceToDelete.taskId + ); + + deleteEvidence({ + variables: { + input: { + evidenceId: evidenceToDelete.id, + }, + connections: evidenceConnectionId ? [evidenceConnectionId] : [], + }, + onCompleted: () => { + toast({ + title: "Evidence deleted", + description: "Evidence has been deleted successfully.", + }); + setIsDeleteEvidenceOpen(false); + setEvidenceToDelete(null); + }, + onError: (error) => { + toast({ + title: "Error deleting evidence", + description: error.message, + variant: "destructive", + }); + }, + }); + }; + return ( <> @@ -928,6 +992,20 @@ function ControlOverviewPageContent({ > + ); @@ -1103,6 +1181,34 @@ function ControlOverviewPageContent({ + + {/* Add Delete Evidence Confirmation Dialog */} + + + + Delete Evidence + + Are you sure you want to delete the evidence " + {evidenceToDelete?.filename}"? This action cannot be + undone. + + + + + + + + ); diff --git a/apps/console/src/pages/__generated__/ControlOverviewPageDeleteEvidenceMutation.graphql.ts b/apps/console/src/pages/__generated__/ControlOverviewPageDeleteEvidenceMutation.graphql.ts new file mode 100644 index 000000000..dc32e1267 --- /dev/null +++ b/apps/console/src/pages/__generated__/ControlOverviewPageDeleteEvidenceMutation.graphql.ts @@ -0,0 +1,132 @@ +/** + * @generated SignedSource<> + * @lightSyntaxTransform + * @nogrep + */ + +/* tslint:disable */ +/* eslint-disable */ +// @ts-nocheck + +import { ConcreteRequest } from 'relay-runtime'; +export type DeleteEvidenceInput = { + evidenceId: string; +}; +export type ControlOverviewPageDeleteEvidenceMutation$variables = { + connections: ReadonlyArray; + input: DeleteEvidenceInput; +}; +export type ControlOverviewPageDeleteEvidenceMutation$data = { + readonly deleteEvidence: { + readonly deletedEvidenceId: string; + }; +}; +export type ControlOverviewPageDeleteEvidenceMutation = { + response: ControlOverviewPageDeleteEvidenceMutation$data; + variables: ControlOverviewPageDeleteEvidenceMutation$variables; +}; + +const node: ConcreteRequest = (function(){ +var v0 = { + "defaultValue": null, + "kind": "LocalArgument", + "name": "connections" +}, +v1 = { + "defaultValue": null, + "kind": "LocalArgument", + "name": "input" +}, +v2 = [ + { + "kind": "Variable", + "name": "input", + "variableName": "input" + } +], +v3 = { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "deletedEvidenceId", + "storageKey": null +}; +return { + "fragment": { + "argumentDefinitions": [ + (v0/*: any*/), + (v1/*: any*/) + ], + "kind": "Fragment", + "metadata": null, + "name": "ControlOverviewPageDeleteEvidenceMutation", + "selections": [ + { + "alias": null, + "args": (v2/*: any*/), + "concreteType": "DeleteEvidencePayload", + "kind": "LinkedField", + "name": "deleteEvidence", + "plural": false, + "selections": [ + (v3/*: any*/) + ], + "storageKey": null + } + ], + "type": "Mutation", + "abstractKey": null + }, + "kind": "Request", + "operation": { + "argumentDefinitions": [ + (v1/*: any*/), + (v0/*: any*/) + ], + "kind": "Operation", + "name": "ControlOverviewPageDeleteEvidenceMutation", + "selections": [ + { + "alias": null, + "args": (v2/*: any*/), + "concreteType": "DeleteEvidencePayload", + "kind": "LinkedField", + "name": "deleteEvidence", + "plural": false, + "selections": [ + (v3/*: any*/), + { + "alias": null, + "args": null, + "filters": null, + "handle": "deleteEdge", + "key": "", + "kind": "ScalarHandle", + "name": "deletedEvidenceId", + "handleArgs": [ + { + "kind": "Variable", + "name": "connections", + "variableName": "connections" + } + ] + } + ], + "storageKey": null + } + ] + }, + "params": { + "cacheID": "51d62fb682189fa0aaa5cc5b7a5b7862", + "id": null, + "metadata": {}, + "name": "ControlOverviewPageDeleteEvidenceMutation", + "operationKind": "mutation", + "text": "mutation ControlOverviewPageDeleteEvidenceMutation(\n $input: DeleteEvidenceInput!\n) {\n deleteEvidence(input: $input) {\n deletedEvidenceId\n }\n}\n" + } +}; +})(); + +(node as any).hash = "f55cf1c279c95dcabc11e1b5aafcdc46"; + +export default node; diff --git a/apps/console/src/pages/__generated__/ControlOverviewPageQuery.graphql.ts b/apps/console/src/pages/__generated__/ControlOverviewPageQuery.graphql.ts index 7e583b4f4..86b70fb85 100644 --- a/apps/console/src/pages/__generated__/ControlOverviewPageQuery.graphql.ts +++ b/apps/console/src/pages/__generated__/ControlOverviewPageQuery.graphql.ts @@ -1,5 +1,5 @@ /** - * @generated SignedSource<<3c761f476fc4dcbc2f175eefa625bb28>> + * @generated SignedSource<> * @lightSyntaxTransform * @nogrep */ @@ -31,7 +31,7 @@ export type ControlOverviewPageQuery$data = { readonly __id: string; readonly edges: ReadonlyArray<{ readonly node: { - readonly createdAt: any; + readonly createdAt: string; readonly filename: string; readonly id: string; readonly mimeType: string; diff --git a/apps/console/src/pages/__generated__/ControlOverviewPageUploadEvidenceMutation.graphql.ts b/apps/console/src/pages/__generated__/ControlOverviewPageUploadEvidenceMutation.graphql.ts index 7736b59e0..a7649a7b6 100644 --- a/apps/console/src/pages/__generated__/ControlOverviewPageUploadEvidenceMutation.graphql.ts +++ b/apps/console/src/pages/__generated__/ControlOverviewPageUploadEvidenceMutation.graphql.ts @@ -1,5 +1,5 @@ /** - * @generated SignedSource<<09af19b5a7657e04606a42962db9db2c>> + * @generated SignedSource<> * @lightSyntaxTransform * @nogrep */ @@ -23,7 +23,7 @@ export type ControlOverviewPageUploadEvidenceMutation$data = { readonly uploadEvidence: { readonly evidenceEdge: { readonly node: { - readonly createdAt: any; + readonly createdAt: string; readonly fileUrl: string; readonly id: string; readonly mimeType: string; diff --git a/pkg/api/console/v1/schema.graphql b/pkg/api/console/v1/schema.graphql index 1cf2d2e4a..78472298d 100644 --- a/pkg/api/console/v1/schema.graphql +++ b/pkg/api/console/v1/schema.graphql @@ -403,6 +403,7 @@ type Mutation { updateFramework(input: UpdateFrameworkInput!): UpdateFrameworkPayload! updateControl(input: UpdateControlInput!): UpdateControlPayload! uploadEvidence(input: UploadEvidenceInput!): UploadEvidencePayload! + deleteEvidence(input: DeleteEvidenceInput!): DeleteEvidencePayload! } input CreateVendorInput { @@ -612,3 +613,11 @@ input UploadEvidenceInput { type UploadEvidencePayload { evidenceEdge: EvidenceEdge! } + +input DeleteEvidenceInput { + evidenceId: ID! +} + +type DeleteEvidencePayload { + deletedEvidenceId: ID! +} diff --git a/pkg/api/console/v1/schema/schema.go b/pkg/api/console/v1/schema/schema.go index 9b56da6ea..f0b01c648 100644 --- a/pkg/api/console/v1/schema/schema.go +++ b/pkg/api/console/v1/schema/schema.go @@ -122,6 +122,10 @@ type ComplexityRoot struct { VendorEdge func(childComplexity int) int } + DeleteEvidencePayload struct { + DeletedEvidenceID func(childComplexity int) int + } + DeleteOrganizationPayload struct { DeletedOrganizationID func(childComplexity int) int } @@ -206,6 +210,7 @@ type ComplexityRoot struct { CreatePeople func(childComplexity int, input types.CreatePeopleInput) int CreateTask func(childComplexity int, input types.CreateTaskInput) int CreateVendor func(childComplexity int, input types.CreateVendorInput) int + DeleteEvidence func(childComplexity int, input types.DeleteEvidenceInput) int DeleteOrganization func(childComplexity int, input types.DeleteOrganizationInput) int DeletePeople func(childComplexity int, input types.DeletePeopleInput) int DeleteTask func(childComplexity int, input types.DeleteTaskInput) int @@ -406,6 +411,7 @@ type MutationResolver interface { UpdateFramework(ctx context.Context, input types.UpdateFrameworkInput) (*types.UpdateFrameworkPayload, error) UpdateControl(ctx context.Context, input types.UpdateControlInput) (*types.UpdateControlPayload, error) UploadEvidence(ctx context.Context, input types.UploadEvidenceInput) (*types.UploadEvidencePayload, error) + DeleteEvidence(ctx context.Context, input types.DeleteEvidenceInput) (*types.DeleteEvidencePayload, error) } type OrganizationResolver interface { Frameworks(ctx context.Context, obj *types.Organization, first *int, after *page.CursorKey, last *int, before *page.CursorKey) (*types.FrameworkConnection, error) @@ -663,6 +669,13 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in return e.complexity.CreateVendorPayload.VendorEdge(childComplexity), true + case "DeleteEvidencePayload.deletedEvidenceId": + if e.complexity.DeleteEvidencePayload.DeletedEvidenceID == nil { + break + } + + return e.complexity.DeleteEvidencePayload.DeletedEvidenceID(childComplexity), true + case "DeleteOrganizationPayload.deletedOrganizationId": if e.complexity.DeleteOrganizationPayload.DeletedOrganizationID == nil { break @@ -1011,6 +1024,18 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in return e.complexity.Mutation.CreateVendor(childComplexity, args["input"].(types.CreateVendorInput)), true + case "Mutation.deleteEvidence": + if e.complexity.Mutation.DeleteEvidence == nil { + break + } + + args, err := ec.field_Mutation_deleteEvidence_args(context.TODO(), rawArgs) + if err != nil { + return 0, false + } + + return e.complexity.Mutation.DeleteEvidence(childComplexity, args["input"].(types.DeleteEvidenceInput)), true + case "Mutation.deleteOrganization": if e.complexity.Mutation.DeleteOrganization == nil { break @@ -1761,6 +1786,7 @@ func (e *executableSchema) Exec(ctx context.Context) graphql.ResponseHandler { ec.unmarshalInputCreatePeopleInput, ec.unmarshalInputCreateTaskInput, ec.unmarshalInputCreateVendorInput, + ec.unmarshalInputDeleteEvidenceInput, ec.unmarshalInputDeleteOrganizationInput, ec.unmarshalInputDeletePeopleInput, ec.unmarshalInputDeleteTaskInput, @@ -2273,6 +2299,7 @@ type Mutation { updateFramework(input: UpdateFrameworkInput!): UpdateFrameworkPayload! updateControl(input: UpdateControlInput!): UpdateControlPayload! uploadEvidence(input: UploadEvidenceInput!): UploadEvidencePayload! + deleteEvidence(input: DeleteEvidenceInput!): DeleteEvidencePayload! } input CreateVendorInput { @@ -2482,6 +2509,14 @@ input UploadEvidenceInput { type UploadEvidencePayload { evidenceEdge: EvidenceEdge! } + +input DeleteEvidenceInput { + evidenceId: ID! +} + +type DeleteEvidencePayload { + deletedEvidenceId: ID! +} `, BuiltIn: false}, } var parsedSchema = gqlparser.MustLoadSchema(sources...) @@ -2936,6 +2971,29 @@ func (ec *executionContext) field_Mutation_createVendor_argsInput( return zeroVal, nil } +func (ec *executionContext) field_Mutation_deleteEvidence_args(ctx context.Context, rawArgs map[string]any) (map[string]any, error) { + var err error + args := map[string]any{} + arg0, err := ec.field_Mutation_deleteEvidence_argsInput(ctx, rawArgs) + if err != nil { + return nil, err + } + args["input"] = arg0 + return args, nil +} +func (ec *executionContext) field_Mutation_deleteEvidence_argsInput( + ctx context.Context, + rawArgs map[string]any, +) (types.DeleteEvidenceInput, error) { + ctx = graphql.WithPathContext(ctx, graphql.NewPathWithField("input")) + if tmp, ok := rawArgs["input"]; ok { + return ec.unmarshalNDeleteEvidenceInput2githubᚗcomᚋgetproboᚋproboᚋpkgᚋapiᚋconsoleᚋv1ᚋtypesᚐDeleteEvidenceInput(ctx, tmp) + } + + var zeroVal types.DeleteEvidenceInput + return zeroVal, nil +} + func (ec *executionContext) field_Mutation_deleteOrganization_args(ctx context.Context, rawArgs map[string]any) (map[string]any, error) { var err error args := map[string]any{} @@ -4988,6 +5046,44 @@ func (ec *executionContext) fieldContext_CreateVendorPayload_vendorEdge(_ contex return fc, nil } +func (ec *executionContext) _DeleteEvidencePayload_deletedEvidenceId(ctx context.Context, field graphql.CollectedField, obj *types.DeleteEvidencePayload) (ret graphql.Marshaler) { + fc, err := ec.fieldContext_DeleteEvidencePayload_deletedEvidenceId(ctx, field) + if err != nil { + return graphql.Null + } + ctx = graphql.WithFieldContext(ctx, fc) + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (any, error) { + ctx = rctx // use context from middleware stack in children + return obj.DeletedEvidenceID, nil + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + if !graphql.HasFieldError(ctx, fc) { + ec.Errorf(ctx, "must not be null") + } + return graphql.Null + } + res := resTmp.(gid.GID) + fc.Result = res + return ec.marshalNID2githubᚗcomᚋgetproboᚋproboᚋpkgᚋgidᚐGID(ctx, field.Selections, res) +} + +func (ec *executionContext) fieldContext_DeleteEvidencePayload_deletedEvidenceId(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "DeleteEvidencePayload", + Field: field, + IsMethod: false, + IsResolver: false, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + return nil, errors.New("field of type ID does not have child fields") + }, + } + return fc, nil +} + func (ec *executionContext) _DeleteOrganizationPayload_deletedOrganizationId(ctx context.Context, field graphql.CollectedField, obj *types.DeleteOrganizationPayload) (ret graphql.Marshaler) { fc, err := ec.fieldContext_DeleteOrganizationPayload_deletedOrganizationId(ctx, field) if err != nil { @@ -7298,6 +7394,53 @@ func (ec *executionContext) fieldContext_Mutation_uploadEvidence(ctx context.Con return fc, nil } +func (ec *executionContext) _Mutation_deleteEvidence(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) { + fc, err := ec.fieldContext_Mutation_deleteEvidence(ctx, field) + if err != nil { + return graphql.Null + } + ctx = graphql.WithFieldContext(ctx, fc) + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (any, error) { + ctx = rctx // use context from middleware stack in children + return ec.resolvers.Mutation().DeleteEvidence(rctx, fc.Args["input"].(types.DeleteEvidenceInput)) + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + if !graphql.HasFieldError(ctx, fc) { + ec.Errorf(ctx, "must not be null") + } + return graphql.Null + } + res := resTmp.(*types.DeleteEvidencePayload) + fc.Result = res + return ec.marshalNDeleteEvidencePayload2ᚖgithubᚗcomᚋgetproboᚋproboᚋpkgᚋapiᚋconsoleᚋv1ᚋtypesᚐDeleteEvidencePayload(ctx, field.Selections, res) +} + +func (ec *executionContext) fieldContext_Mutation_deleteEvidence(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "Mutation", + Field: field, + IsMethod: true, + IsResolver: true, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + switch field.Name { + case "deletedEvidenceId": + return ec.fieldContext_DeleteEvidencePayload_deletedEvidenceId(ctx, field) + } + return nil, fmt.Errorf("no field named %q was found under type DeleteEvidencePayload", field.Name) + }, + } + ctx = graphql.WithFieldContext(ctx, fc) + if fc.Args, err = ec.field_Mutation_deleteEvidence_args(ctx, field.ArgumentMap(ec.Variables)); err != nil { + ec.Error(ctx, err) + return fc, err + } + return fc, nil +} + func (ec *executionContext) _Organization_id(ctx context.Context, field graphql.CollectedField, obj *types.Organization) (ret graphql.Marshaler) { fc, err := ec.fieldContext_Organization_id(ctx, field) if err != nil { @@ -12761,6 +12904,33 @@ func (ec *executionContext) unmarshalInputCreateVendorInput(ctx context.Context, return it, nil } +func (ec *executionContext) unmarshalInputDeleteEvidenceInput(ctx context.Context, obj any) (types.DeleteEvidenceInput, error) { + var it types.DeleteEvidenceInput + asMap := map[string]any{} + for k, v := range obj.(map[string]any) { + asMap[k] = v + } + + fieldsInOrder := [...]string{"evidenceId"} + for _, k := range fieldsInOrder { + v, ok := asMap[k] + if !ok { + continue + } + switch k { + case "evidenceId": + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("evidenceId")) + data, err := ec.unmarshalNID2githubᚗcomᚋgetproboᚋproboᚋpkgᚋgidᚐGID(ctx, v) + if err != nil { + return it, err + } + it.EvidenceID = data + } + } + + return it, nil +} + func (ec *executionContext) unmarshalInputDeleteOrganizationInput(ctx context.Context, obj any) (types.DeleteOrganizationInput, error) { var it types.DeleteOrganizationInput asMap := map[string]any{} @@ -13890,6 +14060,45 @@ func (ec *executionContext) _CreateVendorPayload(ctx context.Context, sel ast.Se return out } +var deleteEvidencePayloadImplementors = []string{"DeleteEvidencePayload"} + +func (ec *executionContext) _DeleteEvidencePayload(ctx context.Context, sel ast.SelectionSet, obj *types.DeleteEvidencePayload) graphql.Marshaler { + fields := graphql.CollectFields(ec.OperationContext, sel, deleteEvidencePayloadImplementors) + + out := graphql.NewFieldSet(fields) + deferred := make(map[string]*graphql.FieldSet) + for i, field := range fields { + switch field.Name { + case "__typename": + out.Values[i] = graphql.MarshalString("DeleteEvidencePayload") + case "deletedEvidenceId": + out.Values[i] = ec._DeleteEvidencePayload_deletedEvidenceId(ctx, field, obj) + if out.Values[i] == graphql.Null { + out.Invalids++ + } + default: + panic("unknown field " + strconv.Quote(field.Name)) + } + } + out.Dispatch(ctx) + if out.Invalids > 0 { + return graphql.Null + } + + atomic.AddInt32(&ec.deferred, int32(len(deferred))) + + for label, dfs := range deferred { + ec.processDeferredGroup(graphql.DeferredGroup{ + Label: label, + Path: graphql.GetPath(ctx), + FieldSet: dfs, + Context: ctx, + }) + } + + return out +} + var deleteOrganizationPayloadImplementors = []string{"DeleteOrganizationPayload"} func (ec *executionContext) _DeleteOrganizationPayload(ctx context.Context, sel ast.SelectionSet, obj *types.DeleteOrganizationPayload) graphql.Marshaler { @@ -14725,6 +14934,13 @@ func (ec *executionContext) _Mutation(ctx context.Context, sel ast.SelectionSet) if out.Values[i] == graphql.Null { out.Invalids++ } + case "deleteEvidence": + out.Values[i] = ec.OperationContext.RootResolverMiddleware(innerCtx, func(ctx context.Context) (res graphql.Marshaler) { + return ec._Mutation_deleteEvidence(ctx, field) + }) + if out.Values[i] == graphql.Null { + out.Invalids++ + } default: panic("unknown field " + strconv.Quote(field.Name)) } @@ -16845,6 +17061,25 @@ func (ec *executionContext) marshalNDatetime2timeᚐTime(ctx context.Context, se return res } +func (ec *executionContext) unmarshalNDeleteEvidenceInput2githubᚗcomᚋgetproboᚋproboᚋpkgᚋapiᚋconsoleᚋv1ᚋtypesᚐDeleteEvidenceInput(ctx context.Context, v any) (types.DeleteEvidenceInput, error) { + res, err := ec.unmarshalInputDeleteEvidenceInput(ctx, v) + return res, graphql.ErrorOnPath(ctx, err) +} + +func (ec *executionContext) marshalNDeleteEvidencePayload2githubᚗcomᚋgetproboᚋproboᚋpkgᚋapiᚋconsoleᚋv1ᚋtypesᚐDeleteEvidencePayload(ctx context.Context, sel ast.SelectionSet, v types.DeleteEvidencePayload) graphql.Marshaler { + return ec._DeleteEvidencePayload(ctx, sel, &v) +} + +func (ec *executionContext) marshalNDeleteEvidencePayload2ᚖgithubᚗcomᚋgetproboᚋproboᚋpkgᚋapiᚋconsoleᚋv1ᚋtypesᚐDeleteEvidencePayload(ctx context.Context, sel ast.SelectionSet, v *types.DeleteEvidencePayload) graphql.Marshaler { + if v == nil { + if !graphql.HasFieldError(ctx, graphql.GetFieldContext(ctx)) { + ec.Errorf(ctx, "the requested element is null which the schema does not allow") + } + return graphql.Null + } + return ec._DeleteEvidencePayload(ctx, sel, v) +} + func (ec *executionContext) unmarshalNDeleteOrganizationInput2githubᚗcomᚋgetproboᚋproboᚋpkgᚋapiᚋconsoleᚋv1ᚋtypesᚐDeleteOrganizationInput(ctx context.Context, v any) (types.DeleteOrganizationInput, error) { res, err := ec.unmarshalInputDeleteOrganizationInput(ctx, v) return res, graphql.ErrorOnPath(ctx, err) diff --git a/pkg/api/console/v1/types/types.go b/pkg/api/console/v1/types/types.go index d2964ea0f..a9f9369ff 100644 --- a/pkg/api/console/v1/types/types.go +++ b/pkg/api/console/v1/types/types.go @@ -129,6 +129,14 @@ type CreateVendorPayload struct { VendorEdge *VendorEdge `json:"vendorEdge"` } +type DeleteEvidenceInput struct { + EvidenceID gid.GID `json:"evidenceId"` +} + +type DeleteEvidencePayload struct { + DeletedEvidenceID gid.GID `json:"deletedEvidenceId"` +} + type DeleteOrganizationInput struct { OrganizationID gid.GID `json:"organizationId"` } diff --git a/pkg/api/console/v1/v1_resolver.go b/pkg/api/console/v1/v1_resolver.go index c3ec5403c..750f1b0f6 100644 --- a/pkg/api/console/v1/v1_resolver.go +++ b/pkg/api/console/v1/v1_resolver.go @@ -363,6 +363,18 @@ func (r *mutationResolver) UploadEvidence(ctx context.Context, input types.Uploa }, nil } +// DeleteEvidence is the resolver for the deleteEvidence field. +func (r *mutationResolver) DeleteEvidence(ctx context.Context, input types.DeleteEvidenceInput) (*types.DeleteEvidencePayload, error) { + err := r.proboSvc.DeleteEvidence(ctx, input.EvidenceID) + if err != nil { + return nil, fmt.Errorf("failed to delete evidence: %w", err) + } + + return &types.DeleteEvidencePayload{ + DeletedEvidenceID: input.EvidenceID, + }, nil +} + // Frameworks is the resolver for the frameworks field. func (r *organizationResolver) Frameworks(ctx context.Context, obj *types.Organization, first *int, after *page.CursorKey, last *int, before *page.CursorKey) (*types.FrameworkConnection, error) { cursor := types.NewCursor(first, after, last, before) diff --git a/pkg/probo/coredata/evidence.go b/pkg/probo/coredata/evidence.go index 0b6d8baf1..9e1f9d6ee 100644 --- a/pkg/probo/coredata/evidence.go +++ b/pkg/probo/coredata/evidence.go @@ -232,3 +232,25 @@ WHERE return nil } + +func (e Evidence) Delete( + ctx context.Context, + conn pg.Conn, + scope *Scope, +) error { + q := ` +DELETE FROM + evidences +WHERE + %s + AND id = @evidence_id +` + + q = fmt.Sprintf(q, scope.SQLFragment()) + + args := pgx.NamedArgs{"evidence_id": e.ID} + maps.Copy(args, scope.SQLArguments()) + + _, err := conn.Exec(ctx, q, args) + return err +} diff --git a/pkg/probo/coredata/evidence_state_transition.go b/pkg/probo/coredata/evidence_state_transition.go index 7a2b51e2a..a76f7fd87 100644 --- a/pkg/probo/coredata/evidence_state_transition.go +++ b/pkg/probo/coredata/evidence_state_transition.go @@ -143,3 +143,26 @@ WHERE return nil } + +func (cst *EvidenceStateTransitions) DeleteForEvidenceID( + ctx context.Context, + conn pg.Conn, + scope *Scope, + evidenceID gid.GID, +) error { + q := ` +DELETE FROM + evidence_state_transitions +WHERE + %s + AND evidence_id = @evidence_id +` + + q = fmt.Sprintf(q, scope.SQLFragment()) + + args := pgx.NamedArgs{"evidence_id": evidenceID} + maps.Copy(args, scope.SQLArguments()) + + _, err := conn.Exec(ctx, q, args) + return err +} diff --git a/pkg/probo/delete_evidence.go b/pkg/probo/delete_evidence.go new file mode 100644 index 000000000..ecb485b89 --- /dev/null +++ b/pkg/probo/delete_evidence.go @@ -0,0 +1,33 @@ +package probo + +import ( + "context" + "fmt" + + "github.com/getprobo/probo/pkg/gid" + "github.com/getprobo/probo/pkg/probo/coredata" + "go.gearno.de/kit/pg" +) + +func (s *Service) DeleteEvidence( + ctx context.Context, + evidenceID gid.GID, +) error { + evidenceStateTransitions := &coredata.EvidenceStateTransitions{} + evidence := &coredata.Evidence{ID: evidenceID} + + return s.pg.WithTx( + ctx, + func(conn pg.Conn) error { + if err := evidenceStateTransitions.DeleteForEvidenceID(ctx, conn, s.scope, evidenceID); err != nil { + return fmt.Errorf("cannot delete evidence state transitions: %w", err) + } + + if err := evidence.Delete(ctx, conn, s.scope); err != nil { + return fmt.Errorf("cannot delete evidence: %w", err) + } + + return nil + }, + ) +}