From 7cdf51adc4d633b6508a31d67bacf18ea1e04feb Mon Sep 17 00:00:00 2001 From: Vivek Patel Date: Fri, 15 Apr 2022 02:20:32 +0530 Subject: [PATCH] Add data privacy request route and implementation - Introduces new role for data-privacy-officer(Role allows user to manage data privacy requests) - along with a new system resource for data privacy requests and its access control - Routes as per access control to create data privacy request, list request and filter it based on their kind and status, update data privacy request status, and create/list comments on data privacy request --- automation/rest/eventTypes.gen.go | 175 +++ .../templates/gocode/store/rdbms/rdbms.go.tpl | 14 +- compose/rest.yaml | 1 + .../resource/rbac_references_system.gen.go | 26 + pkg/envoy/resource/rbac_rules_parse.gen.go | 18 + pkg/envoy/store/rbac_rule_marshal.go | 3 + provision/000_base/roles.yaml | 6 + provision/000_base/system_access_control.yaml | 5 + store/adapters/rdbms/aux_types.gen.go | 142 +++ store/adapters/rdbms/cursor.go | 30 +- store/adapters/rdbms/cursors_test.go | 2 +- store/adapters/rdbms/filter.go | 20 + store/adapters/rdbms/filters.gen.go | 51 + store/adapters/rdbms/queries.gen.go | 224 ++++ store/adapters/rdbms/rdbms.gen.go | 1078 +++++++++++++++-- store/adapters/rdbms/upgrade_tables.go | 28 + store/interfaces.gen.go | 130 ++ store/tests/all_test.go | 6 + system/component.cue | 47 +- system/data_privacy_request.cue | 66 + system/data_privacy_request_comment.cue | 43 + system/rest.yaml | 65 + system/rest/data_privacy_request.go | 147 +++ system/rest/handlers/dataPrivacyRequest.go | 114 ++ .../handlers/dataPrivacyRequestComment.go | 76 ++ system/rest/request/dataPrivacyRequest.go | 367 ++++++ .../rest/request/dataPrivacyRequestComment.go | 229 ++++ system/rest/router.go | 2 + system/service/access_control.gen.go | 151 +++ system/service/data_privacy.go | 241 ++++ .../data_privacy_request_actions.gen.go | 706 +++++++++++ .../service/data_privacy_request_actions.yaml | 79 ++ system/service/event/data_privacy_request.go | 19 + system/service/event/events.gen.go | 468 +++++++ system/service/event/events.yaml | 12 + system/service/service.go | 2 + system/types/data_privacy.go | 132 ++ system/types/rbac.gen.go | 86 +- system/types/type_set.gen.go | 122 ++ system/types/type_set.gen_test.go | 180 +++ system/types/types.yaml | 2 + .../data_privacy_request_comment_test.go | 102 ++ tests/system/data_privacy_request_test.go | 238 ++++ tests/system/resource_activity_log_test.go | 4 +- 44 files changed, 5544 insertions(+), 115 deletions(-) create mode 100644 system/data_privacy_request.cue create mode 100644 system/data_privacy_request_comment.cue create mode 100644 system/rest/data_privacy_request.go create mode 100644 system/rest/handlers/dataPrivacyRequest.go create mode 100644 system/rest/handlers/dataPrivacyRequestComment.go create mode 100644 system/rest/request/dataPrivacyRequest.go create mode 100644 system/rest/request/dataPrivacyRequestComment.go create mode 100644 system/service/data_privacy.go create mode 100644 system/service/data_privacy_request_actions.gen.go create mode 100644 system/service/data_privacy_request_actions.yaml create mode 100644 system/service/event/data_privacy_request.go create mode 100644 system/types/data_privacy.go create mode 100644 tests/system/data_privacy_request_comment_test.go create mode 100644 tests/system/data_privacy_request_test.go diff --git a/automation/rest/eventTypes.gen.go b/automation/rest/eventTypes.gen.go index ee200c1b3..620f8c9c7 100644 --- a/automation/rest/eventTypes.gen.go +++ b/automation/rest/eventTypes.gen.go @@ -1979,6 +1979,181 @@ func getEventTypeDefinitions() []eventTypeDef { }, }, + { + ResourceType: "system:data-privacy-request", + EventType: "onManual", + Properties: []eventTypePropertyDef{ + + { + Name: "dataPrivacyRequest", + Type: "", + Immutable: false, + }, + + { + Name: "oldDataPrivacyRequest", + Type: "", + Immutable: true, + }, + }, + Constraints: []eventTypeConstraintDef{ + + { + Name: "role.name", + }, + }, + }, + + { + ResourceType: "system:data-privacy-request", + EventType: "beforeCreate", + Properties: []eventTypePropertyDef{ + + { + Name: "dataPrivacyRequest", + Type: "", + Immutable: false, + }, + + { + Name: "oldDataPrivacyRequest", + Type: "", + Immutable: true, + }, + }, + Constraints: []eventTypeConstraintDef{ + + { + Name: "role.name", + }, + }, + }, + + { + ResourceType: "system:data-privacy-request", + EventType: "beforeUpdate", + Properties: []eventTypePropertyDef{ + + { + Name: "dataPrivacyRequest", + Type: "", + Immutable: false, + }, + + { + Name: "oldDataPrivacyRequest", + Type: "", + Immutable: true, + }, + }, + Constraints: []eventTypeConstraintDef{ + + { + Name: "role.name", + }, + }, + }, + + { + ResourceType: "system:data-privacy-request", + EventType: "beforeDelete", + Properties: []eventTypePropertyDef{ + + { + Name: "dataPrivacyRequest", + Type: "", + Immutable: false, + }, + + { + Name: "oldDataPrivacyRequest", + Type: "", + Immutable: true, + }, + }, + Constraints: []eventTypeConstraintDef{ + + { + Name: "role.name", + }, + }, + }, + + { + ResourceType: "system:data-privacy-request", + EventType: "afterCreate", + Properties: []eventTypePropertyDef{ + + { + Name: "dataPrivacyRequest", + Type: "", + Immutable: false, + }, + + { + Name: "oldDataPrivacyRequest", + Type: "", + Immutable: true, + }, + }, + Constraints: []eventTypeConstraintDef{ + + { + Name: "role.name", + }, + }, + }, + + { + ResourceType: "system:data-privacy-request", + EventType: "afterUpdate", + Properties: []eventTypePropertyDef{ + + { + Name: "dataPrivacyRequest", + Type: "", + Immutable: false, + }, + + { + Name: "oldDataPrivacyRequest", + Type: "", + Immutable: true, + }, + }, + Constraints: []eventTypeConstraintDef{ + + { + Name: "role.name", + }, + }, + }, + + { + ResourceType: "system:data-privacy-request", + EventType: "afterDelete", + Properties: []eventTypePropertyDef{ + + { + Name: "dataPrivacyRequest", + Type: "", + Immutable: false, + }, + + { + Name: "oldDataPrivacyRequest", + Type: "", + Immutable: true, + }, + }, + Constraints: []eventTypeConstraintDef{ + + { + Name: "role.name", + }, + }, + }, + { ResourceType: "system:mail", EventType: "onManual", diff --git a/codegen/assets/templates/gocode/store/rdbms/rdbms.go.tpl b/codegen/assets/templates/gocode/store/rdbms/rdbms.go.tpl index 4b9546bbc..5f76c8822 100644 --- a/codegen/assets/templates/gocode/store/rdbms/rdbms.go.tpl +++ b/codegen/assets/templates/gocode/store/rdbms/rdbms.go.tpl @@ -342,11 +342,15 @@ func (s *Store) Query{{ .expIdentPlural }}( {{ if .features.paging }} // paging feature is enabled if f.PageCursor != nil { - if tExpr, err = cursor(f.PageCursor); err != nil { - return - } else { - expr = append(expr, tExpr...) - } + {{- if .features.sorting }} + if tExpr, err = cursorWithSorting(f.PageCursor, s.{{ .api.sortableFields.fnIdent }}()); err != nil { + {{- else }} + if tExpr, err = cursor(f.PageCursor); err != nil { + {{- end }} + return + } else { + expr = append(expr, tExpr...) + } } {{ end }} diff --git a/compose/rest.yaml b/compose/rest.yaml index 236067a60..612de5ce3 100644 --- a/compose/rest.yaml +++ b/compose/rest.yaml @@ -980,6 +980,7 @@ endpoints: imports: - sqlxTypes github.com/jmoiron/sqlx/types - github.com/cortezaproject/corteza-server/pkg/label + - github.com/cortezaproject/corteza-server/pkg/locale - time parameters: path: diff --git a/pkg/envoy/resource/rbac_references_system.gen.go b/pkg/envoy/resource/rbac_references_system.gen.go index 8d33581f3..23419fc00 100644 --- a/pkg/envoy/resource/rbac_references_system.gen.go +++ b/pkg/envoy/resource/rbac_references_system.gen.go @@ -49,6 +49,32 @@ func SystemAuthClientRbacReferences(authClient string) (res *Ref, pp []*Ref, err return } +// SystemDataPrivacyRequestRbacReferences generates RBAC references +// +// Resources with "envoy: false" are skipped +// +// This function is auto-generated +func SystemDataPrivacyRequestRbacReferences(dataPrivacyRequest string) (res *Ref, pp []*Ref, err error) { + if dataPrivacyRequest != "*" { + res = &Ref{ResourceType: types.DataPrivacyRequestResourceType, Identifiers: MakeIdentifiers(dataPrivacyRequest)} + } + + return +} + +// SystemDataPrivacyRequestCommentRbacReferences generates RBAC references +// +// Resources with "envoy: false" are skipped +// +// This function is auto-generated +func SystemDataPrivacyRequestCommentRbacReferences(dataPrivacyRequestComment string) (res *Ref, pp []*Ref, err error) { + if dataPrivacyRequestComment != "*" { + res = &Ref{ResourceType: types.DataPrivacyRequestCommentResourceType, Identifiers: MakeIdentifiers(dataPrivacyRequestComment)} + } + + return +} + // SystemQueueRbacReferences generates RBAC references // // Resources with "envoy: false" are skipped diff --git a/pkg/envoy/resource/rbac_rules_parse.gen.go b/pkg/envoy/resource/rbac_rules_parse.gen.go index f30755ae7..03e3159b3 100644 --- a/pkg/envoy/resource/rbac_rules_parse.gen.go +++ b/pkg/envoy/resource/rbac_rules_parse.gen.go @@ -74,6 +74,24 @@ func ParseRule(res string) (string, *Ref, []*Ref, error) { ) return resourceType, ref, pp, err + case systemTypes.DataPrivacyRequestResourceType: + if len(path) != 1 { + return "", nil, nil, fmt.Errorf("expecting 1 reference components in path, got %d", len(path)) + } + ref, pp, err := SystemDataPrivacyRequestRbacReferences( + path[0], + ) + return resourceType, ref, pp, err + + case systemTypes.DataPrivacyRequestCommentResourceType: + if len(path) != 1 { + return "", nil, nil, fmt.Errorf("expecting 1 reference components in path, got %d", len(path)) + } + ref, pp, err := SystemDataPrivacyRequestCommentRbacReferences( + path[0], + ) + return resourceType, ref, pp, err + case systemTypes.QueueResourceType: if len(path) != 1 { return "", nil, nil, fmt.Errorf("expecting 1 reference components in path, got %d", len(path)) diff --git a/pkg/envoy/store/rbac_rule_marshal.go b/pkg/envoy/store/rbac_rule_marshal.go index d59ae8545..fe94c5055 100644 --- a/pkg/envoy/store/rbac_rule_marshal.go +++ b/pkg/envoy/store/rbac_rule_marshal.go @@ -360,6 +360,9 @@ func (n *rbacRule) makeRBACResource(pl *payload) (string, error) { // @todo add support for importing rbac rules for specific queue return systemTypes.QueueRbacResource(p1ID), nil + case systemTypes.DataPrivacyRequestResourceType: + return systemTypes.DataPrivacyRequestRbacResource(p1ID), nil + case federationTypes.NodeResourceType: return federationTypes.NodeRbacResource(p1ID), nil case federationTypes.SharedModuleResourceType: diff --git a/provision/000_base/roles.yaml b/provision/000_base/roles.yaml index dd6877833..cb26d276a 100644 --- a/provision/000_base/roles.yaml +++ b/provision/000_base/roles.yaml @@ -64,3 +64,9 @@ roles: - corteza::compose:record - corteza::system:authClient - corteza::automation:workflow + + data-privacy-officer: + name: Data Privacy Officer + meta: + description: |- + Users that are allowed to view and manage privacy requests. diff --git a/provision/000_base/system_access_control.yaml b/provision/000_base/system_access_control.yaml index b668784ad..40fc033e2 100644 --- a/provision/000_base/system_access_control.yaml +++ b/provision/000_base/system_access_control.yaml @@ -162,3 +162,8 @@ allow: - update - delete - run + + data-privacy-officer: + corteza::system:data-privacy-request/*: + - read + - approve diff --git a/store/adapters/rdbms/aux_types.gen.go b/store/adapters/rdbms/aux_types.gen.go index c8e86a7ac..86246b8e0 100644 --- a/store/adapters/rdbms/aux_types.gen.go +++ b/store/adapters/rdbms/aux_types.gen.go @@ -380,6 +380,36 @@ type ( DeletedBy uint64 `db:"deleted_by"` } + // auxDataPrivacyRequest is an auxiliary structure used for transporting to/from RDBMS store + auxDataPrivacyRequest struct { + ID uint64 `db:"id"` + Kind systemType.RequestKind `db:"kind"` + Status systemType.RequestStatus `db:"status"` + RequestedAt time.Time `db:"requested_at"` + RequestedBy uint64 `db:"requested_by"` + CompletedAt *time.Time `db:"completed_at"` + CompletedBy uint64 `db:"completed_by"` + CreatedAt time.Time `db:"created_at"` + UpdatedAt *time.Time `db:"updated_at"` + DeletedAt *time.Time `db:"deleted_at"` + CreatedBy uint64 `db:"created_by"` + UpdatedBy uint64 `db:"updated_by"` + DeletedBy uint64 `db:"deleted_by"` + } + + // auxDataPrivacyRequestComment is an auxiliary structure used for transporting to/from RDBMS store + auxDataPrivacyRequestComment struct { + ID uint64 `db:"id"` + RequestID uint64 `db:"request_id"` + Comment string `db:"comment"` + CreatedAt time.Time `db:"created_at"` + UpdatedAt *time.Time `db:"updated_at"` + DeletedAt *time.Time `db:"deleted_at"` + CreatedBy uint64 `db:"created_by"` + UpdatedBy uint64 `db:"updated_by"` + DeletedBy uint64 `db:"deleted_by"` + } + // auxFederationExposedModule is an auxiliary structure used for transporting to/from RDBMS store auxFederationExposedModule struct { ID uint64 `db:"id"` @@ -1941,6 +1971,118 @@ func (aux *auxDalSensitivityLevel) scan(row scanner) error { ) } +// encodes DataPrivacyRequest to auxDataPrivacyRequest +// +// This function is auto-generated +func (aux *auxDataPrivacyRequest) encode(res *systemType.DataPrivacyRequest) (_ error) { + aux.ID = res.ID + aux.Kind = res.Kind + aux.Status = res.Status + aux.RequestedAt = res.RequestedAt + aux.RequestedBy = res.RequestedBy + aux.CompletedAt = res.CompletedAt + aux.CompletedBy = res.CompletedBy + aux.CreatedAt = res.CreatedAt + aux.UpdatedAt = res.UpdatedAt + aux.DeletedAt = res.DeletedAt + aux.CreatedBy = res.CreatedBy + aux.UpdatedBy = res.UpdatedBy + aux.DeletedBy = res.DeletedBy + return +} + +// decodes DataPrivacyRequest from auxDataPrivacyRequest +// +// This function is auto-generated +func (aux auxDataPrivacyRequest) decode() (res *systemType.DataPrivacyRequest, _ error) { + res = new(systemType.DataPrivacyRequest) + res.ID = aux.ID + res.Kind = aux.Kind + res.Status = aux.Status + res.RequestedAt = aux.RequestedAt + res.RequestedBy = aux.RequestedBy + res.CompletedAt = aux.CompletedAt + res.CompletedBy = aux.CompletedBy + res.CreatedAt = aux.CreatedAt + res.UpdatedAt = aux.UpdatedAt + res.DeletedAt = aux.DeletedAt + res.CreatedBy = aux.CreatedBy + res.UpdatedBy = aux.UpdatedBy + res.DeletedBy = aux.DeletedBy + return +} + +// scans row and fills auxDataPrivacyRequest fields +// +// This function is auto-generated +func (aux *auxDataPrivacyRequest) scan(row scanner) error { + return row.Scan( + &aux.ID, + &aux.Kind, + &aux.Status, + &aux.RequestedAt, + &aux.RequestedBy, + &aux.CompletedAt, + &aux.CompletedBy, + &aux.CreatedAt, + &aux.UpdatedAt, + &aux.DeletedAt, + &aux.CreatedBy, + &aux.UpdatedBy, + &aux.DeletedBy, + ) +} + +// encodes DataPrivacyRequestComment to auxDataPrivacyRequestComment +// +// This function is auto-generated +func (aux *auxDataPrivacyRequestComment) encode(res *systemType.DataPrivacyRequestComment) (_ error) { + aux.ID = res.ID + aux.RequestID = res.RequestID + aux.Comment = res.Comment + aux.CreatedAt = res.CreatedAt + aux.UpdatedAt = res.UpdatedAt + aux.DeletedAt = res.DeletedAt + aux.CreatedBy = res.CreatedBy + aux.UpdatedBy = res.UpdatedBy + aux.DeletedBy = res.DeletedBy + return +} + +// decodes DataPrivacyRequestComment from auxDataPrivacyRequestComment +// +// This function is auto-generated +func (aux auxDataPrivacyRequestComment) decode() (res *systemType.DataPrivacyRequestComment, _ error) { + res = new(systemType.DataPrivacyRequestComment) + res.ID = aux.ID + res.RequestID = aux.RequestID + res.Comment = aux.Comment + res.CreatedAt = aux.CreatedAt + res.UpdatedAt = aux.UpdatedAt + res.DeletedAt = aux.DeletedAt + res.CreatedBy = aux.CreatedBy + res.UpdatedBy = aux.UpdatedBy + res.DeletedBy = aux.DeletedBy + return +} + +// scans row and fills auxDataPrivacyRequestComment fields +// +// This function is auto-generated +func (aux *auxDataPrivacyRequestComment) scan(row scanner) error { + return row.Scan( + &aux.ID, + &aux.RequestID, + &aux.Comment, + &aux.CreatedAt, + &aux.UpdatedAt, + &aux.DeletedAt, + &aux.CreatedBy, + &aux.UpdatedBy, + &aux.DeletedBy, + ) +} + // encodes FederationExposedModule to auxFederationExposedModule // // This function is auto-generated diff --git a/store/adapters/rdbms/cursor.go b/store/adapters/rdbms/cursor.go index 9a285cf96..86e7930cb 100644 --- a/store/adapters/rdbms/cursor.go +++ b/store/adapters/rdbms/cursor.go @@ -3,6 +3,7 @@ package rdbms import ( "fmt" "reflect" + "strings" "github.com/cortezaproject/corteza-server/pkg/filter" "github.com/doug-martin/goqu/v9" @@ -11,8 +12,9 @@ import ( type ( cursorCondition struct { - cur pagingCursor - keyMapper cursorKeyMapper + cur pagingCursor + keyMapper cursorKeyMapper + sortableCols map[string]string } pagingCursor interface { @@ -39,7 +41,16 @@ type ( ) func cursor(cursor *filter.PagingCursor) ([]goqu.Expression, error) { - sql, args, err := CursorCondition(cursor, nil).ToSQL() + sql, args, err := CursorCondition(cursor, nil, nil).ToSQL() + if err != nil { + return nil, err + } + + return []goqu.Expression{goqu.Literal(sql, args...)}, nil +} + +func cursorWithSorting(cursor *filter.PagingCursor, sortableCols map[string]string) ([]goqu.Expression, error) { + sql, args, err := CursorCondition(cursor, nil, sortableCols).ToSQL() if err != nil { return nil, err } @@ -49,7 +60,7 @@ func cursor(cursor *filter.PagingCursor) ([]goqu.Expression, error) { // CursorCondition builds a complex condition to filter rows before/after row that // the paging cursor points to -func CursorCondition(pc pagingCursor, keyMapper cursorKeyMapper) *cursorCondition { +func CursorCondition(pc pagingCursor, keyMapper cursorKeyMapper, sortableCols map[string]string) *cursorCondition { if keyMapper == nil { keyMapper = func(s string) (KeyMap, error) { return KeyMap{ @@ -60,7 +71,7 @@ func CursorCondition(pc pagingCursor, keyMapper cursorKeyMapper) *cursorConditio } } - return &cursorCondition{cur: pc, keyMapper: keyMapper} + return &cursorCondition{cur: pc, keyMapper: keyMapper, sortableCols: sortableCols} } func (c *cursorCondition) ToSQL() (string, []interface{}, error) { @@ -152,7 +163,14 @@ func (c *cursorCondition) sql() (cnd string, err error) { // going from the last key/column to the 1st one for i := len(cc) - 1; i >= 0; i-- { // Get the key context so we know how to format fields and format typecasts - km, err := c.keyMapper(cc[i]) + colName := cc[i] + if c.sortableCols != nil { + if v, ok := c.sortableCols[strings.ToLower(cc[i])]; ok { + colName = v + } + } + + km, err := c.keyMapper(colName) if err != nil { return "", err } diff --git a/store/adapters/rdbms/cursors_test.go b/store/adapters/rdbms/cursors_test.go index e0034d926..1aaf86b54 100644 --- a/store/adapters/rdbms/cursors_test.go +++ b/store/adapters/rdbms/cursors_test.go @@ -87,7 +87,7 @@ func Test_buildCursorCond(t *testing.T) { var ( req = require.New(t) - sql, args, err = CursorCondition(tt.cursor, nil).ToSQL() + sql, args, err = CursorCondition(tt.cursor, nil, nil).ToSQL() ) req.NoError(err) diff --git a/store/adapters/rdbms/filter.go b/store/adapters/rdbms/filter.go index 505887f9a..ed6471822 100644 --- a/store/adapters/rdbms/filter.go +++ b/store/adapters/rdbms/filter.go @@ -265,6 +265,26 @@ func DefaultFilters() (f *extendedFilters) { return ee, f, err } + f.DataPrivacyRequest = func(s *Store, f systemType.DataPrivacyRequestFilter) (ee []goqu.Expression, _ systemType.DataPrivacyRequestFilter, err error) { + if ee, f, err = DataPrivacyRequestFilter(f); err != nil { + return + } + + if len(f.Kind) > 0 { + ee = append(ee, goqu.C("kind").In(f.Kind)) + } + + if len(f.Status) > 0 { + ee = append(ee, goqu.C("status").In(f.Status)) + } + + if f.Limit == 0 || f.Limit > MaxLimit { + f.Limit = MaxLimit + } + + return ee, f, err + } + return } diff --git a/store/adapters/rdbms/filters.gen.go b/store/adapters/rdbms/filters.gen.go index 4102b95ff..18a8bfa82 100644 --- a/store/adapters/rdbms/filters.gen.go +++ b/store/adapters/rdbms/filters.gen.go @@ -98,6 +98,12 @@ type ( // optional dalSensitivityLevel filter function called after the generated function DalSensitivityLevel func(*Store, systemType.DalSensitivityLevelFilter) ([]goqu.Expression, systemType.DalSensitivityLevelFilter, error) + // optional dataPrivacyRequest filter function called after the generated function + DataPrivacyRequest func(*Store, systemType.DataPrivacyRequestFilter) ([]goqu.Expression, systemType.DataPrivacyRequestFilter, error) + + // optional dataPrivacyRequestComment filter function called after the generated function + DataPrivacyRequestComment func(*Store, systemType.DataPrivacyRequestCommentFilter) ([]goqu.Expression, systemType.DataPrivacyRequestCommentFilter, error) + // optional federationExposedModule filter function called after the generated function FederationExposedModule func(*Store, federationType.ExposedModuleFilter) ([]goqu.Expression, federationType.ExposedModuleFilter, error) @@ -767,6 +773,51 @@ func DalSensitivityLevelFilter(f systemType.DalSensitivityLevelFilter) (ee []goq return ee, f, err } +// DataPrivacyRequestFilter returns logical expressions +// +// This function is called from Store.QueryDataPrivacyRequests() and can be extended +// by setting Store.Filters.DataPrivacyRequest. Extension is called after all expressions +// are generated and can choose to ignore or alter them. +// +// This function is auto-generated +func DataPrivacyRequestFilter(f systemType.DataPrivacyRequestFilter) (ee []goqu.Expression, _ systemType.DataPrivacyRequestFilter, err error) { + + // @todo codegen warning: filtering by Kind ([]types.RequestKind) not supported, + // see rdbms.go.tpl and add an exception + + // @todo codegen warning: filtering by Status ([]types.RequestStatus) not supported, + // see rdbms.go.tpl and add an exception + + if len(f.RequestedBy) > 0 { + ee = append(ee, goqu.C("requested_by").In(f.RequestedBy)) + } + + if f.Query != "" { + ee = append(ee, goqu.Or( + goqu.C("kind").ILike("%"+f.Query+"%"), + goqu.C("status").ILike("%"+f.Query+"%"), + )) + } + + return ee, f, err +} + +// DataPrivacyRequestCommentFilter returns logical expressions +// +// This function is called from Store.QueryDataPrivacyRequestComments() and can be extended +// by setting Store.Filters.DataPrivacyRequestComment. Extension is called after all expressions +// are generated and can choose to ignore or alter them. +// +// This function is auto-generated +func DataPrivacyRequestCommentFilter(f systemType.DataPrivacyRequestCommentFilter) (ee []goqu.Expression, _ systemType.DataPrivacyRequestCommentFilter, err error) { + + if len(f.RequestID) > 0 { + ee = append(ee, goqu.C("rel_request").In(f.RequestID)) + } + + return ee, f, err +} + // FederationExposedModuleFilter returns logical expressions // // This function is called from Store.QueryFederationExposedModules() and can be extended diff --git a/store/adapters/rdbms/queries.gen.go b/store/adapters/rdbms/queries.gen.go index 58c82dffa..5bf49eb31 100644 --- a/store/adapters/rdbms/queries.gen.go +++ b/store/adapters/rdbms/queries.gen.go @@ -2641,6 +2641,230 @@ var ( } } + // dataPrivacyRequestTable represents dataPrivacyRequests store table + // + // This value is auto-generated + dataPrivacyRequestTable = goqu.T("data_privacy_requests") + + // dataPrivacyRequestSelectQuery assembles select query for fetching dataPrivacyRequests + // + // This function is auto-generated + dataPrivacyRequestSelectQuery = func(d goqu.DialectWrapper) *goqu.SelectDataset { + return d.Select( + "id", + "kind", + "status", + "requested_at", + "requested_by", + "completed_at", + "completed_by", + "created_at", + "updated_at", + "deleted_at", + "created_by", + "updated_by", + "deleted_by", + ).From(dataPrivacyRequestTable) + } + + // dataPrivacyRequestInsertQuery assembles query inserting dataPrivacyRequests + // + // This function is auto-generated + dataPrivacyRequestInsertQuery = func(d goqu.DialectWrapper, res *systemType.DataPrivacyRequest) *goqu.InsertDataset { + return d.Insert(dataPrivacyRequestTable). + Rows(goqu.Record{ + "id": res.ID, + "kind": res.Kind, + "status": res.Status, + "requested_at": res.RequestedAt, + "requested_by": res.RequestedBy, + "completed_at": res.CompletedAt, + "completed_by": res.CompletedBy, + "created_at": res.CreatedAt, + "updated_at": res.UpdatedAt, + "deleted_at": res.DeletedAt, + "created_by": res.CreatedBy, + "updated_by": res.UpdatedBy, + "deleted_by": res.DeletedBy, + }) + } + + // dataPrivacyRequestUpsertQuery assembles (insert+on-conflict) query for replacing dataPrivacyRequests + // + // This function is auto-generated + dataPrivacyRequestUpsertQuery = func(d goqu.DialectWrapper, res *systemType.DataPrivacyRequest) *goqu.InsertDataset { + var target = `,id` + + return dataPrivacyRequestInsertQuery(d, res). + OnConflict( + goqu.DoUpdate(target[1:], + goqu.Record{ + "kind": res.Kind, + "status": res.Status, + "requested_at": res.RequestedAt, + "requested_by": res.RequestedBy, + "completed_at": res.CompletedAt, + "completed_by": res.CompletedBy, + "created_at": res.CreatedAt, + "updated_at": res.UpdatedAt, + "deleted_at": res.DeletedAt, + "created_by": res.CreatedBy, + "updated_by": res.UpdatedBy, + "deleted_by": res.DeletedBy, + }, + ), + ) + } + + // dataPrivacyRequestUpdateQuery assembles query for updating dataPrivacyRequests + // + // This function is auto-generated + dataPrivacyRequestUpdateQuery = func(d goqu.DialectWrapper, res *systemType.DataPrivacyRequest) *goqu.UpdateDataset { + return d.Update(dataPrivacyRequestTable). + Set(goqu.Record{ + "kind": res.Kind, + "status": res.Status, + "requested_at": res.RequestedAt, + "requested_by": res.RequestedBy, + "completed_at": res.CompletedAt, + "completed_by": res.CompletedBy, + "created_at": res.CreatedAt, + "updated_at": res.UpdatedAt, + "deleted_at": res.DeletedAt, + "created_by": res.CreatedBy, + "updated_by": res.UpdatedBy, + "deleted_by": res.DeletedBy, + }). + Where(dataPrivacyRequestPrimaryKeys(res)) + } + + // dataPrivacyRequestDeleteQuery assembles delete query for removing dataPrivacyRequests + // + // This function is auto-generated + dataPrivacyRequestDeleteQuery = func(d goqu.DialectWrapper, ee ...goqu.Expression) *goqu.DeleteDataset { + return d.Delete(dataPrivacyRequestTable).Where(ee...) + } + + // dataPrivacyRequestDeleteQuery assembles delete query for removing dataPrivacyRequests + // + // This function is auto-generated + dataPrivacyRequestTruncateQuery = func(d goqu.DialectWrapper) *goqu.TruncateDataset { + return d.Truncate(dataPrivacyRequestTable) + } + + // dataPrivacyRequestPrimaryKeys assembles set of conditions for all primary keys + // + // This function is auto-generated + dataPrivacyRequestPrimaryKeys = func(res *systemType.DataPrivacyRequest) goqu.Ex { + return goqu.Ex{ + "id": res.ID, + } + } + + // dataPrivacyRequestCommentTable represents dataPrivacyRequestComments store table + // + // This value is auto-generated + dataPrivacyRequestCommentTable = goqu.T("data_privacy_request_comments") + + // dataPrivacyRequestCommentSelectQuery assembles select query for fetching dataPrivacyRequestComments + // + // This function is auto-generated + dataPrivacyRequestCommentSelectQuery = func(d goqu.DialectWrapper) *goqu.SelectDataset { + return d.Select( + "id", + "rel_request", + "comment", + "created_at", + "updated_at", + "deleted_at", + "created_by", + "updated_by", + "deleted_by", + ).From(dataPrivacyRequestCommentTable) + } + + // dataPrivacyRequestCommentInsertQuery assembles query inserting dataPrivacyRequestComments + // + // This function is auto-generated + dataPrivacyRequestCommentInsertQuery = func(d goqu.DialectWrapper, res *systemType.DataPrivacyRequestComment) *goqu.InsertDataset { + return d.Insert(dataPrivacyRequestCommentTable). + Rows(goqu.Record{ + "id": res.ID, + "rel_request": res.RequestID, + "comment": res.Comment, + "created_at": res.CreatedAt, + "updated_at": res.UpdatedAt, + "deleted_at": res.DeletedAt, + "created_by": res.CreatedBy, + "updated_by": res.UpdatedBy, + "deleted_by": res.DeletedBy, + }) + } + + // dataPrivacyRequestCommentUpsertQuery assembles (insert+on-conflict) query for replacing dataPrivacyRequestComments + // + // This function is auto-generated + dataPrivacyRequestCommentUpsertQuery = func(d goqu.DialectWrapper, res *systemType.DataPrivacyRequestComment) *goqu.InsertDataset { + var target = `,id` + + return dataPrivacyRequestCommentInsertQuery(d, res). + OnConflict( + goqu.DoUpdate(target[1:], + goqu.Record{ + "rel_request": res.RequestID, + "comment": res.Comment, + "created_at": res.CreatedAt, + "updated_at": res.UpdatedAt, + "deleted_at": res.DeletedAt, + "created_by": res.CreatedBy, + "updated_by": res.UpdatedBy, + "deleted_by": res.DeletedBy, + }, + ), + ) + } + + // dataPrivacyRequestCommentUpdateQuery assembles query for updating dataPrivacyRequestComments + // + // This function is auto-generated + dataPrivacyRequestCommentUpdateQuery = func(d goqu.DialectWrapper, res *systemType.DataPrivacyRequestComment) *goqu.UpdateDataset { + return d.Update(dataPrivacyRequestCommentTable). + Set(goqu.Record{ + "rel_request": res.RequestID, + "comment": res.Comment, + "created_at": res.CreatedAt, + "updated_at": res.UpdatedAt, + "deleted_at": res.DeletedAt, + "created_by": res.CreatedBy, + "updated_by": res.UpdatedBy, + "deleted_by": res.DeletedBy, + }). + Where(dataPrivacyRequestCommentPrimaryKeys(res)) + } + + // dataPrivacyRequestCommentDeleteQuery assembles delete query for removing dataPrivacyRequestComments + // + // This function is auto-generated + dataPrivacyRequestCommentDeleteQuery = func(d goqu.DialectWrapper, ee ...goqu.Expression) *goqu.DeleteDataset { + return d.Delete(dataPrivacyRequestCommentTable).Where(ee...) + } + + // dataPrivacyRequestCommentDeleteQuery assembles delete query for removing dataPrivacyRequestComments + // + // This function is auto-generated + dataPrivacyRequestCommentTruncateQuery = func(d goqu.DialectWrapper) *goqu.TruncateDataset { + return d.Truncate(dataPrivacyRequestCommentTable) + } + + // dataPrivacyRequestCommentPrimaryKeys assembles set of conditions for all primary keys + // + // This function is auto-generated + dataPrivacyRequestCommentPrimaryKeys = func(res *systemType.DataPrivacyRequestComment) goqu.Ex { + return goqu.Ex{ + "id": res.ID, + } + } + // federationExposedModuleTable represents federationExposedModules store table // // This value is auto-generated diff --git a/store/adapters/rdbms/rdbms.gen.go b/store/adapters/rdbms/rdbms.gen.go index 867c64360..a4fd95691 100644 --- a/store/adapters/rdbms/rdbms.gen.go +++ b/store/adapters/rdbms/rdbms.gen.go @@ -29,48 +29,50 @@ import ( ) var ( - _ store.Actionlogs = &Store{} - _ store.ApigwFilters = &Store{} - _ store.ApigwRoutes = &Store{} - _ store.Applications = &Store{} - _ store.Attachments = &Store{} - _ store.AuthClients = &Store{} - _ store.AuthConfirmedClients = &Store{} - _ store.AuthOa2tokens = &Store{} - _ store.AuthSessions = &Store{} - _ store.AutomationSessions = &Store{} - _ store.AutomationTriggers = &Store{} - _ store.AutomationWorkflows = &Store{} - _ store.ComposeAttachments = &Store{} - _ store.ComposeCharts = &Store{} - _ store.ComposeModules = &Store{} - _ store.ComposeModuleFields = &Store{} - _ store.ComposeNamespaces = &Store{} - _ store.ComposePages = &Store{} - _ store.ComposeRecords = &Store{} - _ store.ComposeRecordValues = &Store{} - _ store.Credentials = &Store{} - _ store.DalConnections = &Store{} - _ store.DalSensitivityLevels = &Store{} - _ store.FederationExposedModules = &Store{} - _ store.FederationModuleMappings = &Store{} - _ store.FederationNodes = &Store{} - _ store.FederationNodeSyncs = &Store{} - _ store.FederationSharedModules = &Store{} - _ store.Flags = &Store{} - _ store.Labels = &Store{} - _ store.Queues = &Store{} - _ store.QueueMessages = &Store{} - _ store.RbacRules = &Store{} - _ store.Reminders = &Store{} - _ store.Reports = &Store{} - _ store.ResourceActivitys = &Store{} - _ store.ResourceTranslations = &Store{} - _ store.Roles = &Store{} - _ store.RoleMembers = &Store{} - _ store.SettingValues = &Store{} - _ store.Templates = &Store{} - _ store.Users = &Store{} + _ store.Actionlogs = &Store{} + _ store.ApigwFilters = &Store{} + _ store.ApigwRoutes = &Store{} + _ store.Applications = &Store{} + _ store.Attachments = &Store{} + _ store.AuthClients = &Store{} + _ store.AuthConfirmedClients = &Store{} + _ store.AuthOa2tokens = &Store{} + _ store.AuthSessions = &Store{} + _ store.AutomationSessions = &Store{} + _ store.AutomationTriggers = &Store{} + _ store.AutomationWorkflows = &Store{} + _ store.ComposeAttachments = &Store{} + _ store.ComposeCharts = &Store{} + _ store.ComposeModules = &Store{} + _ store.ComposeModuleFields = &Store{} + _ store.ComposeNamespaces = &Store{} + _ store.ComposePages = &Store{} + _ store.ComposeRecords = &Store{} + _ store.ComposeRecordValues = &Store{} + _ store.Credentials = &Store{} + _ store.DalConnections = &Store{} + _ store.DalSensitivityLevels = &Store{} + _ store.DataPrivacyRequests = &Store{} + _ store.DataPrivacyRequestComments = &Store{} + _ store.FederationExposedModules = &Store{} + _ store.FederationModuleMappings = &Store{} + _ store.FederationNodes = &Store{} + _ store.FederationNodeSyncs = &Store{} + _ store.FederationSharedModules = &Store{} + _ store.Flags = &Store{} + _ store.Labels = &Store{} + _ store.Queues = &Store{} + _ store.QueueMessages = &Store{} + _ store.RbacRules = &Store{} + _ store.Reminders = &Store{} + _ store.Reports = &Store{} + _ store.ResourceActivitys = &Store{} + _ store.ResourceTranslations = &Store{} + _ store.Roles = &Store{} + _ store.RoleMembers = &Store{} + _ store.SettingValues = &Store{} + _ store.Templates = &Store{} + _ store.Users = &Store{} ) // CreateActionlog creates one or more rows in actionlog collection @@ -656,7 +658,7 @@ func (s *Store) QueryApigwFilters( // paging feature is enabled if f.PageCursor != nil { - if tExpr, err = cursor(f.PageCursor); err != nil { + if tExpr, err = cursorWithSorting(f.PageCursor, s.sortableApigwFilterFields()); err != nil { return } else { expr = append(expr, tExpr...) @@ -1178,7 +1180,7 @@ func (s *Store) QueryApigwRoutes( // paging feature is enabled if f.PageCursor != nil { - if tExpr, err = cursor(f.PageCursor); err != nil { + if tExpr, err = cursorWithSorting(f.PageCursor, s.sortableApigwRouteFields()); err != nil { return } else { expr = append(expr, tExpr...) @@ -1704,7 +1706,7 @@ func (s *Store) QueryApplications( // paging feature is enabled if f.PageCursor != nil { - if tExpr, err = cursor(f.PageCursor); err != nil { + if tExpr, err = cursorWithSorting(f.PageCursor, s.sortableApplicationFields()); err != nil { return } else { expr = append(expr, tExpr...) @@ -2188,7 +2190,7 @@ func (s *Store) QueryAttachments( // paging feature is enabled if f.PageCursor != nil { - if tExpr, err = cursor(f.PageCursor); err != nil { + if tExpr, err = cursorWithSorting(f.PageCursor, s.sortableAttachmentFields()); err != nil { return } else { expr = append(expr, tExpr...) @@ -2670,7 +2672,7 @@ func (s *Store) QueryAuthClients( // paging feature is enabled if f.PageCursor != nil { - if tExpr, err = cursor(f.PageCursor); err != nil { + if tExpr, err = cursorWithSorting(f.PageCursor, s.sortableAuthClientFields()); err != nil { return } else { expr = append(expr, tExpr...) @@ -4212,7 +4214,7 @@ func (s *Store) QueryAutomationSessions( // paging feature is enabled if f.PageCursor != nil { - if tExpr, err = cursor(f.PageCursor); err != nil { + if tExpr, err = cursorWithSorting(f.PageCursor, s.sortableAutomationSessionFields()); err != nil { return } else { expr = append(expr, tExpr...) @@ -4700,7 +4702,7 @@ func (s *Store) QueryAutomationTriggers( // paging feature is enabled if f.PageCursor != nil { - if tExpr, err = cursor(f.PageCursor); err != nil { + if tExpr, err = cursorWithSorting(f.PageCursor, s.sortableAutomationTriggerFields()); err != nil { return } else { expr = append(expr, tExpr...) @@ -5184,7 +5186,7 @@ func (s *Store) QueryAutomationWorkflows( // paging feature is enabled if f.PageCursor != nil { - if tExpr, err = cursor(f.PageCursor); err != nil { + if tExpr, err = cursorWithSorting(f.PageCursor, s.sortableAutomationWorkflowFields()); err != nil { return } else { expr = append(expr, tExpr...) @@ -5742,7 +5744,7 @@ func (s *Store) QueryComposeAttachments( // paging feature is enabled if f.PageCursor != nil { - if tExpr, err = cursor(f.PageCursor); err != nil { + if tExpr, err = cursorWithSorting(f.PageCursor, s.sortableComposeAttachmentFields()); err != nil { return } else { expr = append(expr, tExpr...) @@ -6224,7 +6226,7 @@ func (s *Store) QueryComposeCharts( // paging feature is enabled if f.PageCursor != nil { - if tExpr, err = cursor(f.PageCursor); err != nil { + if tExpr, err = cursorWithSorting(f.PageCursor, s.sortableComposeChartFields()); err != nil { return } else { expr = append(expr, tExpr...) @@ -6757,7 +6759,7 @@ func (s *Store) QueryComposeModules( // paging feature is enabled if f.PageCursor != nil { - if tExpr, err = cursor(f.PageCursor); err != nil { + if tExpr, err = cursorWithSorting(f.PageCursor, s.sortableComposeModuleFields()); err != nil { return } else { expr = append(expr, tExpr...) @@ -7680,7 +7682,7 @@ func (s *Store) QueryComposeNamespaces( // paging feature is enabled if f.PageCursor != nil { - if tExpr, err = cursor(f.PageCursor); err != nil { + if tExpr, err = cursorWithSorting(f.PageCursor, s.sortableComposeNamespaceFields()); err != nil { return } else { expr = append(expr, tExpr...) @@ -8232,7 +8234,7 @@ func (s *Store) QueryComposePages( // paging feature is enabled if f.PageCursor != nil { - if tExpr, err = cursor(f.PageCursor); err != nil { + if tExpr, err = cursorWithSorting(f.PageCursor, s.sortableComposePageFields()); err != nil { return } else { expr = append(expr, tExpr...) @@ -8810,7 +8812,7 @@ func (s *Store) QueryComposeRecords( // paging feature is enabled if f.PageCursor != nil { - if tExpr, err = cursor(f.PageCursor); err != nil { + if tExpr, err = cursorWithSorting(f.PageCursor, s.sortableComposeRecordFields()); err != nil { return } else { expr = append(expr, tExpr...) @@ -10241,6 +10243,946 @@ func (s *Store) checkDalSensitivityLevelConstraints(ctx context.Context, res *sy return nil } +// CreateDataPrivacyRequest creates one or more rows in dataPrivacyRequest collection +// +// This function is auto-generated +func (s *Store) CreateDataPrivacyRequest(ctx context.Context, rr ...*systemType.DataPrivacyRequest) (err error) { + for i := range rr { + if err = s.checkDataPrivacyRequestConstraints(ctx, rr[i]); err != nil { + return + } + + if err = s.Exec(ctx, dataPrivacyRequestInsertQuery(s.Dialect, rr[i])); err != nil { + return + } + } + + return +} + +// UpdateDataPrivacyRequest updates one or more existing entries in dataPrivacyRequest collection +// +// This function is auto-generated +func (s *Store) UpdateDataPrivacyRequest(ctx context.Context, rr ...*systemType.DataPrivacyRequest) (err error) { + for i := range rr { + if err = s.checkDataPrivacyRequestConstraints(ctx, rr[i]); err != nil { + return + } + + if err = s.Exec(ctx, dataPrivacyRequestUpdateQuery(s.Dialect, rr[i])); err != nil { + return + } + } + + return +} + +// UpsertDataPrivacyRequest updates one or more existing entries in dataPrivacyRequest collection +// +// This function is auto-generated +func (s *Store) UpsertDataPrivacyRequest(ctx context.Context, rr ...*systemType.DataPrivacyRequest) (err error) { + for i := range rr { + if err = s.checkDataPrivacyRequestConstraints(ctx, rr[i]); err != nil { + return + } + + if err = s.Exec(ctx, dataPrivacyRequestUpsertQuery(s.Dialect, rr[i])); err != nil { + return + } + } + + return +} + +// DeleteDataPrivacyRequest Deletes one or more entries from dataPrivacyRequest collection +// +// This function is auto-generated +func (s *Store) DeleteDataPrivacyRequest(ctx context.Context, rr ...*systemType.DataPrivacyRequest) (err error) { + for i := range rr { + if err = s.Exec(ctx, dataPrivacyRequestDeleteQuery(s.Dialect, dataPrivacyRequestPrimaryKeys(rr[i]))); err != nil { + return + } + } + + return nil +} + +// DeleteDataPrivacyRequestByID deletes single entry from dataPrivacyRequest collection +// +// This function is auto-generated +func (s *Store) DeleteDataPrivacyRequestByID(ctx context.Context, id uint64) error { + return s.Exec(ctx, dataPrivacyRequestDeleteQuery(s.Dialect, goqu.Ex{ + "id": id, + })) +} + +// TruncateDataPrivacyRequests Deletes all rows from the dataPrivacyRequest collection +func (s Store) TruncateDataPrivacyRequests(ctx context.Context) error { + return s.Exec(ctx, dataPrivacyRequestTruncateQuery(s.Dialect)) +} + +// SearchDataPrivacyRequests returns (filtered) set of DataPrivacyRequests +// +// This function is auto-generated +func (s *Store) SearchDataPrivacyRequests(ctx context.Context, f systemType.DataPrivacyRequestFilter) (set systemType.DataPrivacyRequestSet, _ systemType.DataPrivacyRequestFilter, err error) { + + // Cleanup unwanted cursor values (only relevant is f.PageCursor, next&prev are reset and returned) + f.PrevPage, f.NextPage = nil, nil + + if f.PageCursor != nil { + // Page cursor exists; we need to validate it against used sort + // To cover the case when paging cursor is set but sorting is empty, we collect the sorting instructions + // from the cursor. + // This (extracted sorting info) is then returned as part of response + if f.Sort, err = f.PageCursor.Sort(f.Sort); err != nil { + return + } + } + + // Make sure results are always sorted at least by primary keys + if f.Sort.Get("id") == nil { + f.Sort = append(f.Sort, &filter.SortExpr{ + Column: "id", + Descending: f.Sort.LastDescending(), + }) + } + + // Cloned sorting instructions for the actual sorting + // Original are passed to the etchFullPageOfDataPrivacyRequests fn used for cursor creation; + // direction information it MUST keep the initial + sort := f.Sort.Clone() + + // When cursor for a previous page is used it's marked as reversed + // This tells us to flip the descending flag on all used sort keys + if f.PageCursor != nil && f.PageCursor.ROrder { + sort.Reverse() + } + + set, f.PrevPage, f.NextPage, err = s.fetchFullPageOfDataPrivacyRequests(ctx, f, sort) + + f.PageCursor = nil + if err != nil { + return nil, f, err + } + + return set, f, nil +} + +// fetchFullPageOfDataPrivacyRequests collects all requested results. +// +// Function applies: +// - cursor conditions (where ...) +// - limit +// +// Main responsibility of this function is to perform additional sequential queries in case when not enough results +// are collected due to failed check on a specific row (by check fn). +// +// Function then moves cursor to the last item fetched +// +// This function is auto-generated +func (s *Store) fetchFullPageOfDataPrivacyRequests( + ctx context.Context, + filter systemType.DataPrivacyRequestFilter, + sort filter.SortExprSet, +) (set []*systemType.DataPrivacyRequest, prev, next *filter.PagingCursor, err error) { + var ( + aux []*systemType.DataPrivacyRequest + + // When cursor for a previous page is used it's marked as reversed + // This tells us to flip the descending flag on all used sort keys + reversedOrder = filter.PageCursor != nil && filter.PageCursor.ROrder + + // Copy no. of required items to limit + // Limit will change when doing subsequent queries to fill + // the set with all required items + limit = filter.Limit + + reqItems = filter.Limit + + // cursor to prev. page is only calculated when cursor is used + hasPrev = filter.PageCursor != nil + + // next cursor is calculated when there are more pages to come + hasNext bool + + tryFilter systemType.DataPrivacyRequestFilter + ) + + set = make([]*systemType.DataPrivacyRequest, 0, DefaultSliceCapacity) + + for try := 0; try < MaxRefetches; try++ { + // Copy filter & apply custom sorting that might be affected by cursor + tryFilter = filter + tryFilter.Sort = sort + + if limit > 0 { + // fetching + 1 to peak ahead if there are more items + // we can fetch (next-page cursor) + tryFilter.Limit = limit + 1 + } + + if aux, hasNext, err = s.QueryDataPrivacyRequests(ctx, tryFilter); err != nil { + return nil, nil, nil, err + } + + if len(aux) == 0 { + // nothing fetched + break + } + + // append fetched items + set = append(set, aux...) + + if reqItems == 0 || !hasNext { + // no max requested items specified, break out + break + } + + collected := uint(len(set)) + + if reqItems > collected { + // not enough items fetched, try again with adjusted limit + limit = reqItems - collected + + if limit < MinEnsureFetchLimit { + // In case limit is set very low and we've missed records in the first fetch, + // make sure next fetch limit is a bit higher + limit = MinEnsureFetchLimit + } + + // Update cursor so that it points to the last item fetched + tryFilter.PageCursor = s.collectDataPrivacyRequestCursorValues(set[collected-1], filter.Sort...) + + // Copy reverse flag from sorting + tryFilter.PageCursor.LThen = filter.Sort.Reversed() + continue + } + + if reqItems < collected { + set = set[:reqItems] + } + + break + } + + collected := len(set) + + if collected == 0 { + return nil, nil, nil, nil + } + + if reversedOrder { + // Fetched set needs to be reversed because we've forced a descending order to get the previous page + for i, j := 0, collected-1; i < j; i, j = i+1, j-1 { + set[i], set[j] = set[j], set[i] + } + + // when in reverse-order rules on what cursor to return change + hasPrev, hasNext = hasNext, hasPrev + } + + if hasPrev { + prev = s.collectDataPrivacyRequestCursorValues(set[0], filter.Sort...) + prev.ROrder = true + prev.LThen = !filter.Sort.Reversed() + } + + if hasNext { + next = s.collectDataPrivacyRequestCursorValues(set[collected-1], filter.Sort...) + next.LThen = filter.Sort.Reversed() + } + + return set, prev, next, nil +} + +// QueryDataPrivacyRequests queries the database, converts and checks each row and returns collected set +// +// With generics, we can remove this per-resource-generated function +// and replace it with a single utility fetcher +// +// This function is auto-generated +func (s *Store) QueryDataPrivacyRequests( + ctx context.Context, + f systemType.DataPrivacyRequestFilter, +) (_ []*systemType.DataPrivacyRequest, more bool, err error) { + var ( + ok bool + + set = make([]*systemType.DataPrivacyRequest, 0, DefaultSliceCapacity) + res *systemType.DataPrivacyRequest + aux *auxDataPrivacyRequest + rows *sql.Rows + count uint + expr, tExpr []goqu.Expression + + sortExpr []exp.OrderedExpression + ) + + if s.Filters.DataPrivacyRequest != nil { + // extended filter set + tExpr, f, err = s.Filters.DataPrivacyRequest(s, f) + } else { + // using generated filter + tExpr, f, err = DataPrivacyRequestFilter(f) + } + + if err != nil { + err = fmt.Errorf("could generate filter expression for DataPrivacyRequest: %w", err) + return + } + + expr = append(expr, tExpr...) + + // paging feature is enabled + if f.PageCursor != nil { + if tExpr, err = cursorWithSorting(f.PageCursor, s.sortableDataPrivacyRequestFields()); err != nil { + return + } else { + expr = append(expr, tExpr...) + } + } + + query := dataPrivacyRequestSelectQuery(s.Dialect).Where(expr...) + + // sorting feature is enabled + if sortExpr, err = order(f.Sort, s.sortableDataPrivacyRequestFields()); err != nil { + err = fmt.Errorf("could generate order expression for DataPrivacyRequest: %w", err) + return + } + + if len(sortExpr) > 0 { + query = query.Order(sortExpr...) + } + + if f.Limit > 0 { + query = query.Limit(f.Limit) + } + + rows, err = s.Query(ctx, query) + if err != nil { + err = fmt.Errorf("could not query DataPrivacyRequest: %w", err) + return + } + + if err = rows.Err(); err != nil { + err = fmt.Errorf("could not query DataPrivacyRequest: %w", err) + return + } + + defer func() { + closeError := rows.Close() + if err == nil { + // return error from close + err = closeError + } + }() + + for rows.Next() { + if err = rows.Err(); err != nil { + err = fmt.Errorf("could not query DataPrivacyRequest: %w", err) + return + } + + aux = new(auxDataPrivacyRequest) + if err = aux.scan(rows); err != nil { + err = fmt.Errorf("could not scan rows for DataPrivacyRequest: %w", err) + return + } + + count++ + if res, err = aux.decode(); err != nil { + err = fmt.Errorf("could not decode DataPrivacyRequest: %w", err) + return + } + + // check fn set, call it and see if it passed the test + // if not, skip the item + if f.Check != nil { + if ok, err = f.Check(res); err != nil { + return + } else if !ok { + continue + } + } + + set = append(set, res) + } + + return set, f.Limit > 0 && count >= f.Limit, err + +} + +// LookupDataPrivacyRequestByID searches for data privacy request by ID +// +// It returns data privacy request even if deleted +// +// This function is auto-generated +func (s *Store) LookupDataPrivacyRequestByID(ctx context.Context, id uint64) (_ *systemType.DataPrivacyRequest, err error) { + var ( + rows *sql.Rows + aux = new(auxDataPrivacyRequest) + lookup = dataPrivacyRequestSelectQuery(s.Dialect).Where( + goqu.I("id").Eq(id), + ).Limit(1) + ) + + rows, err = s.Query(ctx, lookup) + if err != nil { + return + } + + defer func() { + closeError := rows.Close() + if err == nil { + // return error from close + err = closeError + } + }() + + if err = rows.Err(); err != nil { + return + } + + if !rows.Next() { + return nil, store.ErrNotFound.Stack(1) + } + + if err = aux.scan(rows); err != nil { + return + } + + return aux.decode() +} + +// sortableDataPrivacyRequestFields returns all columns flagged as sortable +// +// With optional string arg, all columns are returned aliased +// +// This function is auto-generated +func (Store) sortableDataPrivacyRequestFields() map[string]string { + return map[string]string{ + "completed_at": "completed_at", + "completedat": "completed_at", + "created_at": "created_at", + "createdat": "created_at", + "deleted_at": "deleted_at", + "deletedat": "deleted_at", + "id": "id", + "kind": "kind", + "requested_at": "requested_at", + "requestedat": "requested_at", + "status": "status", + "updated_at": "updated_at", + "updatedat": "updated_at", + } +} + +// collectDataPrivacyRequestCursorValues collects values from the given resource that and sets them to the cursor +// to be used for pagination +// +// Values that are collected must come from sortable, unique or primary columns/fields +// At least one of the collected columns must be flagged as unique, otherwise fn appends primary keys at the end +// +// Known issue: +// when collecting cursor values for query that sorts by unique column with partial index (ie: unique handle on +// undeleted items) +// +// This function is auto-generated +func (s *Store) collectDataPrivacyRequestCursorValues(res *systemType.DataPrivacyRequest, cc ...*filter.SortExpr) *filter.PagingCursor { + var ( + cur = &filter.PagingCursor{LThen: filter.SortExprSet(cc).Reversed()} + + hasUnique bool + + pkID bool + + collect = func(cc ...*filter.SortExpr) { + for _, c := range cc { + switch c.Column { + case "id": + cur.Set(c.Column, res.ID, c.Descending) + pkID = true + case "kind": + cur.Set(c.Column, res.Kind, c.Descending) + case "status": + cur.Set(c.Column, res.Status, c.Descending) + case "requestedAt": + cur.Set(c.Column, res.RequestedAt, c.Descending) + case "completedAt": + cur.Set(c.Column, res.CompletedAt, c.Descending) + case "createdAt": + cur.Set(c.Column, res.CreatedAt, c.Descending) + case "updatedAt": + cur.Set(c.Column, res.UpdatedAt, c.Descending) + case "deletedAt": + cur.Set(c.Column, res.DeletedAt, c.Descending) + } + } + } + ) + + collect(cc...) + if !hasUnique || !pkID { + collect(&filter.SortExpr{Column: "id", Descending: false}) + } + + return cur + +} + +// checkDataPrivacyRequestConstraints performs lookups (on valid) resource to check if any of the values on unique fields +// already exists in the store +// +// Using built-in constraint checking would be more performant, but unfortunately we cannot rely +// on the full support (MySQL does not support conditional indexes) +// +// This function is auto-generated +func (s *Store) checkDataPrivacyRequestConstraints(ctx context.Context, res *systemType.DataPrivacyRequest) (err error) { + return nil +} + +// CreateDataPrivacyRequestComment creates one or more rows in dataPrivacyRequestComment collection +// +// This function is auto-generated +func (s *Store) CreateDataPrivacyRequestComment(ctx context.Context, rr ...*systemType.DataPrivacyRequestComment) (err error) { + for i := range rr { + if err = s.checkDataPrivacyRequestCommentConstraints(ctx, rr[i]); err != nil { + return + } + + if err = s.Exec(ctx, dataPrivacyRequestCommentInsertQuery(s.Dialect, rr[i])); err != nil { + return + } + } + + return +} + +// UpdateDataPrivacyRequestComment updates one or more existing entries in dataPrivacyRequestComment collection +// +// This function is auto-generated +func (s *Store) UpdateDataPrivacyRequestComment(ctx context.Context, rr ...*systemType.DataPrivacyRequestComment) (err error) { + for i := range rr { + if err = s.checkDataPrivacyRequestCommentConstraints(ctx, rr[i]); err != nil { + return + } + + if err = s.Exec(ctx, dataPrivacyRequestCommentUpdateQuery(s.Dialect, rr[i])); err != nil { + return + } + } + + return +} + +// UpsertDataPrivacyRequestComment updates one or more existing entries in dataPrivacyRequestComment collection +// +// This function is auto-generated +func (s *Store) UpsertDataPrivacyRequestComment(ctx context.Context, rr ...*systemType.DataPrivacyRequestComment) (err error) { + for i := range rr { + if err = s.checkDataPrivacyRequestCommentConstraints(ctx, rr[i]); err != nil { + return + } + + if err = s.Exec(ctx, dataPrivacyRequestCommentUpsertQuery(s.Dialect, rr[i])); err != nil { + return + } + } + + return +} + +// DeleteDataPrivacyRequestComment Deletes one or more entries from dataPrivacyRequestComment collection +// +// This function is auto-generated +func (s *Store) DeleteDataPrivacyRequestComment(ctx context.Context, rr ...*systemType.DataPrivacyRequestComment) (err error) { + for i := range rr { + if err = s.Exec(ctx, dataPrivacyRequestCommentDeleteQuery(s.Dialect, dataPrivacyRequestCommentPrimaryKeys(rr[i]))); err != nil { + return + } + } + + return nil +} + +// DeleteDataPrivacyRequestCommentByID deletes single entry from dataPrivacyRequestComment collection +// +// This function is auto-generated +func (s *Store) DeleteDataPrivacyRequestCommentByID(ctx context.Context, id uint64) error { + return s.Exec(ctx, dataPrivacyRequestCommentDeleteQuery(s.Dialect, goqu.Ex{ + "id": id, + })) +} + +// TruncateDataPrivacyRequestComments Deletes all rows from the dataPrivacyRequestComment collection +func (s Store) TruncateDataPrivacyRequestComments(ctx context.Context) error { + return s.Exec(ctx, dataPrivacyRequestCommentTruncateQuery(s.Dialect)) +} + +// SearchDataPrivacyRequestComments returns (filtered) set of DataPrivacyRequestComments +// +// This function is auto-generated +func (s *Store) SearchDataPrivacyRequestComments(ctx context.Context, f systemType.DataPrivacyRequestCommentFilter) (set systemType.DataPrivacyRequestCommentSet, _ systemType.DataPrivacyRequestCommentFilter, err error) { + + // Cleanup unwanted cursor values (only relevant is f.PageCursor, next&prev are reset and returned) + f.PrevPage, f.NextPage = nil, nil + + if f.PageCursor != nil { + // Page cursor exists; we need to validate it against used sort + // To cover the case when paging cursor is set but sorting is empty, we collect the sorting instructions + // from the cursor. + // This (extracted sorting info) is then returned as part of response + if f.Sort, err = f.PageCursor.Sort(f.Sort); err != nil { + return + } + } + + // Make sure results are always sorted at least by primary keys + if f.Sort.Get("id") == nil { + f.Sort = append(f.Sort, &filter.SortExpr{ + Column: "id", + Descending: f.Sort.LastDescending(), + }) + } + + // Cloned sorting instructions for the actual sorting + // Original are passed to the etchFullPageOfDataPrivacyRequestComments fn used for cursor creation; + // direction information it MUST keep the initial + sort := f.Sort.Clone() + + // When cursor for a previous page is used it's marked as reversed + // This tells us to flip the descending flag on all used sort keys + if f.PageCursor != nil && f.PageCursor.ROrder { + sort.Reverse() + } + + set, f.PrevPage, f.NextPage, err = s.fetchFullPageOfDataPrivacyRequestComments(ctx, f, sort) + + f.PageCursor = nil + if err != nil { + return nil, f, err + } + + return set, f, nil +} + +// fetchFullPageOfDataPrivacyRequestComments collects all requested results. +// +// Function applies: +// - cursor conditions (where ...) +// - limit +// +// Main responsibility of this function is to perform additional sequential queries in case when not enough results +// are collected due to failed check on a specific row (by check fn). +// +// Function then moves cursor to the last item fetched +// +// This function is auto-generated +func (s *Store) fetchFullPageOfDataPrivacyRequestComments( + ctx context.Context, + filter systemType.DataPrivacyRequestCommentFilter, + sort filter.SortExprSet, +) (set []*systemType.DataPrivacyRequestComment, prev, next *filter.PagingCursor, err error) { + var ( + aux []*systemType.DataPrivacyRequestComment + + // When cursor for a previous page is used it's marked as reversed + // This tells us to flip the descending flag on all used sort keys + reversedOrder = filter.PageCursor != nil && filter.PageCursor.ROrder + + // Copy no. of required items to limit + // Limit will change when doing subsequent queries to fill + // the set with all required items + limit = filter.Limit + + reqItems = filter.Limit + + // cursor to prev. page is only calculated when cursor is used + hasPrev = filter.PageCursor != nil + + // next cursor is calculated when there are more pages to come + hasNext bool + + tryFilter systemType.DataPrivacyRequestCommentFilter + ) + + set = make([]*systemType.DataPrivacyRequestComment, 0, DefaultSliceCapacity) + + for try := 0; try < MaxRefetches; try++ { + // Copy filter & apply custom sorting that might be affected by cursor + tryFilter = filter + tryFilter.Sort = sort + + if limit > 0 { + // fetching + 1 to peak ahead if there are more items + // we can fetch (next-page cursor) + tryFilter.Limit = limit + 1 + } + + if aux, hasNext, err = s.QueryDataPrivacyRequestComments(ctx, tryFilter); err != nil { + return nil, nil, nil, err + } + + if len(aux) == 0 { + // nothing fetched + break + } + + // append fetched items + set = append(set, aux...) + + if reqItems == 0 || !hasNext { + // no max requested items specified, break out + break + } + + collected := uint(len(set)) + + if reqItems > collected { + // not enough items fetched, try again with adjusted limit + limit = reqItems - collected + + if limit < MinEnsureFetchLimit { + // In case limit is set very low and we've missed records in the first fetch, + // make sure next fetch limit is a bit higher + limit = MinEnsureFetchLimit + } + + // Update cursor so that it points to the last item fetched + tryFilter.PageCursor = s.collectDataPrivacyRequestCommentCursorValues(set[collected-1], filter.Sort...) + + // Copy reverse flag from sorting + tryFilter.PageCursor.LThen = filter.Sort.Reversed() + continue + } + + if reqItems < collected { + set = set[:reqItems] + } + + break + } + + collected := len(set) + + if collected == 0 { + return nil, nil, nil, nil + } + + if reversedOrder { + // Fetched set needs to be reversed because we've forced a descending order to get the previous page + for i, j := 0, collected-1; i < j; i, j = i+1, j-1 { + set[i], set[j] = set[j], set[i] + } + + // when in reverse-order rules on what cursor to return change + hasPrev, hasNext = hasNext, hasPrev + } + + if hasPrev { + prev = s.collectDataPrivacyRequestCommentCursorValues(set[0], filter.Sort...) + prev.ROrder = true + prev.LThen = !filter.Sort.Reversed() + } + + if hasNext { + next = s.collectDataPrivacyRequestCommentCursorValues(set[collected-1], filter.Sort...) + next.LThen = filter.Sort.Reversed() + } + + return set, prev, next, nil +} + +// QueryDataPrivacyRequestComments queries the database, converts and checks each row and returns collected set +// +// With generics, we can remove this per-resource-generated function +// and replace it with a single utility fetcher +// +// This function is auto-generated +func (s *Store) QueryDataPrivacyRequestComments( + ctx context.Context, + f systemType.DataPrivacyRequestCommentFilter, +) (_ []*systemType.DataPrivacyRequestComment, more bool, err error) { + var ( + ok bool + + set = make([]*systemType.DataPrivacyRequestComment, 0, DefaultSliceCapacity) + res *systemType.DataPrivacyRequestComment + aux *auxDataPrivacyRequestComment + rows *sql.Rows + count uint + expr, tExpr []goqu.Expression + + sortExpr []exp.OrderedExpression + ) + + if s.Filters.DataPrivacyRequestComment != nil { + // extended filter set + tExpr, f, err = s.Filters.DataPrivacyRequestComment(s, f) + } else { + // using generated filter + tExpr, f, err = DataPrivacyRequestCommentFilter(f) + } + + if err != nil { + err = fmt.Errorf("could generate filter expression for DataPrivacyRequestComment: %w", err) + return + } + + expr = append(expr, tExpr...) + + // paging feature is enabled + if f.PageCursor != nil { + if tExpr, err = cursorWithSorting(f.PageCursor, s.sortableDataPrivacyRequestCommentFields()); err != nil { + return + } else { + expr = append(expr, tExpr...) + } + } + + query := dataPrivacyRequestCommentSelectQuery(s.Dialect).Where(expr...) + + // sorting feature is enabled + if sortExpr, err = order(f.Sort, s.sortableDataPrivacyRequestCommentFields()); err != nil { + err = fmt.Errorf("could generate order expression for DataPrivacyRequestComment: %w", err) + return + } + + if len(sortExpr) > 0 { + query = query.Order(sortExpr...) + } + + if f.Limit > 0 { + query = query.Limit(f.Limit) + } + + rows, err = s.Query(ctx, query) + if err != nil { + err = fmt.Errorf("could not query DataPrivacyRequestComment: %w", err) + return + } + + if err = rows.Err(); err != nil { + err = fmt.Errorf("could not query DataPrivacyRequestComment: %w", err) + return + } + + defer func() { + closeError := rows.Close() + if err == nil { + // return error from close + err = closeError + } + }() + + for rows.Next() { + if err = rows.Err(); err != nil { + err = fmt.Errorf("could not query DataPrivacyRequestComment: %w", err) + return + } + + aux = new(auxDataPrivacyRequestComment) + if err = aux.scan(rows); err != nil { + err = fmt.Errorf("could not scan rows for DataPrivacyRequestComment: %w", err) + return + } + + count++ + if res, err = aux.decode(); err != nil { + err = fmt.Errorf("could not decode DataPrivacyRequestComment: %w", err) + return + } + + // check fn set, call it and see if it passed the test + // if not, skip the item + if f.Check != nil { + if ok, err = f.Check(res); err != nil { + return + } else if !ok { + continue + } + } + + set = append(set, res) + } + + return set, f.Limit > 0 && count >= f.Limit, err + +} + +// sortableDataPrivacyRequestCommentFields returns all columns flagged as sortable +// +// With optional string arg, all columns are returned aliased +// +// This function is auto-generated +func (Store) sortableDataPrivacyRequestCommentFields() map[string]string { + return map[string]string{ + "created_at": "created_at", + "createdat": "created_at", + "deleted_at": "deleted_at", + "deletedat": "deleted_at", + "id": "id", + "updated_at": "updated_at", + "updatedat": "updated_at", + } +} + +// collectDataPrivacyRequestCommentCursorValues collects values from the given resource that and sets them to the cursor +// to be used for pagination +// +// Values that are collected must come from sortable, unique or primary columns/fields +// At least one of the collected columns must be flagged as unique, otherwise fn appends primary keys at the end +// +// Known issue: +// when collecting cursor values for query that sorts by unique column with partial index (ie: unique handle on +// undeleted items) +// +// This function is auto-generated +func (s *Store) collectDataPrivacyRequestCommentCursorValues(res *systemType.DataPrivacyRequestComment, cc ...*filter.SortExpr) *filter.PagingCursor { + var ( + cur = &filter.PagingCursor{LThen: filter.SortExprSet(cc).Reversed()} + + hasUnique bool + + pkID bool + + collect = func(cc ...*filter.SortExpr) { + for _, c := range cc { + switch c.Column { + case "id": + cur.Set(c.Column, res.ID, c.Descending) + pkID = true + case "createdAt": + cur.Set(c.Column, res.CreatedAt, c.Descending) + case "updatedAt": + cur.Set(c.Column, res.UpdatedAt, c.Descending) + case "deletedAt": + cur.Set(c.Column, res.DeletedAt, c.Descending) + } + } + } + ) + + collect(cc...) + if !hasUnique || !pkID { + collect(&filter.SortExpr{Column: "id", Descending: false}) + } + + return cur + +} + +// checkDataPrivacyRequestCommentConstraints performs lookups (on valid) resource to check if any of the values on unique fields +// already exists in the store +// +// Using built-in constraint checking would be more performant, but unfortunately we cannot rely +// on the full support (MySQL does not support conditional indexes) +// +// This function is auto-generated +func (s *Store) checkDataPrivacyRequestCommentConstraints(ctx context.Context, res *systemType.DataPrivacyRequestComment) (err error) { + return nil +} + // CreateFederationExposedModule creates one or more rows in federationExposedModule collection // // This function is auto-generated @@ -10533,7 +11475,7 @@ func (s *Store) QueryFederationExposedModules( // paging feature is enabled if f.PageCursor != nil { - if tExpr, err = cursor(f.PageCursor); err != nil { + if tExpr, err = cursorWithSorting(f.PageCursor, s.sortableFederationExposedModuleFields()); err != nil { return } else { expr = append(expr, tExpr...) @@ -11019,7 +11961,7 @@ func (s *Store) QueryFederationModuleMappings( // paging feature is enabled if f.PageCursor != nil { - if tExpr, err = cursor(f.PageCursor); err != nil { + if tExpr, err = cursorWithSorting(f.PageCursor, s.sortableFederationModuleMappingFields()); err != nil { return } else { expr = append(expr, tExpr...) @@ -11892,7 +12834,7 @@ func (s *Store) QueryFederationNodeSyncs( // paging feature is enabled if f.PageCursor != nil { - if tExpr, err = cursor(f.PageCursor); err != nil { + if tExpr, err = cursorWithSorting(f.PageCursor, s.sortableFederationNodeSyncFields()); err != nil { return } else { expr = append(expr, tExpr...) @@ -12414,7 +13356,7 @@ func (s *Store) QueryFederationSharedModules( // paging feature is enabled if f.PageCursor != nil { - if tExpr, err = cursor(f.PageCursor); err != nil { + if tExpr, err = cursorWithSorting(f.PageCursor, s.sortableFederationSharedModuleFields()); err != nil { return } else { expr = append(expr, tExpr...) @@ -13507,7 +14449,7 @@ func (s *Store) QueryQueues( // paging feature is enabled if f.PageCursor != nil { - if tExpr, err = cursor(f.PageCursor); err != nil { + if tExpr, err = cursorWithSorting(f.PageCursor, s.sortableQueueFields()); err != nil { return } else { expr = append(expr, tExpr...) @@ -14027,7 +14969,7 @@ func (s *Store) QueryQueueMessages( // paging feature is enabled if f.PageCursor != nil { - if tExpr, err = cursor(f.PageCursor); err != nil { + if tExpr, err = cursorWithSorting(f.PageCursor, s.sortableQueueMessageFields()); err != nil { return } else { expr = append(expr, tExpr...) @@ -14708,7 +15650,7 @@ func (s *Store) QueryReminders( // paging feature is enabled if f.PageCursor != nil { - if tExpr, err = cursor(f.PageCursor); err != nil { + if tExpr, err = cursorWithSorting(f.PageCursor, s.sortableReminderFields()); err != nil { return } else { expr = append(expr, tExpr...) @@ -15202,7 +16144,7 @@ func (s *Store) QueryReports( // paging feature is enabled if f.PageCursor != nil { - if tExpr, err = cursor(f.PageCursor); err != nil { + if tExpr, err = cursorWithSorting(f.PageCursor, s.sortableReportFields()); err != nil { return } else { expr = append(expr, tExpr...) @@ -15997,7 +16939,7 @@ func (s *Store) QueryResourceTranslations( // paging feature is enabled if f.PageCursor != nil { - if tExpr, err = cursor(f.PageCursor); err != nil { + if tExpr, err = cursorWithSorting(f.PageCursor, s.sortableResourceTranslationFields()); err != nil { return } else { expr = append(expr, tExpr...) @@ -16470,7 +17412,7 @@ func (s *Store) QueryRoles( // paging feature is enabled if f.PageCursor != nil { - if tExpr, err = cursor(f.PageCursor); err != nil { + if tExpr, err = cursorWithSorting(f.PageCursor, s.sortableRoleFields()); err != nil { return } else { expr = append(expr, tExpr...) @@ -17640,7 +18582,7 @@ func (s *Store) QueryTemplates( // paging feature is enabled if f.PageCursor != nil { - if tExpr, err = cursor(f.PageCursor); err != nil { + if tExpr, err = cursorWithSorting(f.PageCursor, s.sortableTemplateFields()); err != nil { return } else { expr = append(expr, tExpr...) @@ -18202,7 +19144,7 @@ func (s *Store) QueryUsers( // paging feature is enabled if f.PageCursor != nil { - if tExpr, err = cursor(f.PageCursor); err != nil { + if tExpr, err = cursorWithSorting(f.PageCursor, s.sortableUserFields()); err != nil { return } else { expr = append(expr, tExpr...) diff --git a/store/adapters/rdbms/upgrade_tables.go b/store/adapters/rdbms/upgrade_tables.go index d9ecbc42d..57165918a 100644 --- a/store/adapters/rdbms/upgrade_tables.go +++ b/store/adapters/rdbms/upgrade_tables.go @@ -74,6 +74,8 @@ func Tables() []*Table { tableApigwRoute(), tableApigwFilter(), tableResourceActivityLog(), + tableDataPrivacyRequests(), + tableDataPrivacyRequestComments(), } } @@ -743,3 +745,29 @@ func tableResourceActivityLog() *Table { AddIndex("ts", IColumn("ts")), ) } + +func tableDataPrivacyRequests() *Table { + return TableDef("data_privacy_requests", + ID, + ColumnDef("kind", ColumnTypeText, ColumnTypeLength(handleLength)), + ColumnDef("status", ColumnTypeVarchar, ColumnTypeLength(handleLength)), + ColumnDef("requested_at", ColumnTypeTimestamp), + ColumnDef("requested_by", ColumnTypeIdentifier), + ColumnDef("completed_at", ColumnTypeTimestamp, Null), + ColumnDef("completed_by", ColumnTypeIdentifier, DefaultValue("0")), + CUDTimestamps, + CUDUsers, + + AddIndex("status", IColumn("status")), + ) +} + +func tableDataPrivacyRequestComments() *Table { + return TableDef("data_privacy_request_comments", + ID, + ColumnDef("rel_request", ColumnTypeIdentifier), + ColumnDef("comment", ColumnTypeText), + CUDTimestamps, + CUDUsers, + ) +} diff --git a/store/interfaces.gen.go b/store/interfaces.gen.go index 51650f18d..767cdb83f 100644 --- a/store/interfaces.gen.go +++ b/store/interfaces.gen.go @@ -62,6 +62,8 @@ type ( Credentials DalConnections DalSensitivityLevels + DataPrivacyRequests + DataPrivacyRequestComments FederationExposedModules FederationModuleMappings FederationNodes @@ -363,6 +365,27 @@ type ( LookupDalSensitivityLevelByID(ctx context.Context, id uint64) (*systemType.DalSensitivityLevel, error) } + DataPrivacyRequests interface { + SearchDataPrivacyRequests(ctx context.Context, f systemType.DataPrivacyRequestFilter) (systemType.DataPrivacyRequestSet, systemType.DataPrivacyRequestFilter, error) + CreateDataPrivacyRequest(ctx context.Context, rr ...*systemType.DataPrivacyRequest) error + UpdateDataPrivacyRequest(ctx context.Context, rr ...*systemType.DataPrivacyRequest) error + UpsertDataPrivacyRequest(ctx context.Context, rr ...*systemType.DataPrivacyRequest) error + DeleteDataPrivacyRequest(ctx context.Context, rr ...*systemType.DataPrivacyRequest) error + DeleteDataPrivacyRequestByID(ctx context.Context, id uint64) error + TruncateDataPrivacyRequests(ctx context.Context) error + LookupDataPrivacyRequestByID(ctx context.Context, id uint64) (*systemType.DataPrivacyRequest, error) + } + + DataPrivacyRequestComments interface { + SearchDataPrivacyRequestComments(ctx context.Context, f systemType.DataPrivacyRequestCommentFilter) (systemType.DataPrivacyRequestCommentSet, systemType.DataPrivacyRequestCommentFilter, error) + CreateDataPrivacyRequestComment(ctx context.Context, rr ...*systemType.DataPrivacyRequestComment) error + UpdateDataPrivacyRequestComment(ctx context.Context, rr ...*systemType.DataPrivacyRequestComment) error + UpsertDataPrivacyRequestComment(ctx context.Context, rr ...*systemType.DataPrivacyRequestComment) error + DeleteDataPrivacyRequestComment(ctx context.Context, rr ...*systemType.DataPrivacyRequestComment) error + DeleteDataPrivacyRequestCommentByID(ctx context.Context, id uint64) error + TruncateDataPrivacyRequestComments(ctx context.Context) error + } + FederationExposedModules interface { SearchFederationExposedModules(ctx context.Context, f federationType.ExposedModuleFilter) (federationType.ExposedModuleSet, federationType.ExposedModuleFilter, error) CreateFederationExposedModule(ctx context.Context, rr ...*federationType.ExposedModule) error @@ -2101,6 +2124,113 @@ func LookupDalSensitivityLevelByID(ctx context.Context, s DalSensitivityLevels, return s.LookupDalSensitivityLevelByID(ctx, id) } +// SearchDataPrivacyRequests returns all matching DataPrivacyRequests from store +// +// This function is auto-generated +func SearchDataPrivacyRequests(ctx context.Context, s DataPrivacyRequests, f systemType.DataPrivacyRequestFilter) (systemType.DataPrivacyRequestSet, systemType.DataPrivacyRequestFilter, error) { + return s.SearchDataPrivacyRequests(ctx, f) +} + +// CreateDataPrivacyRequest creates one or more DataPrivacyRequests in store +// +// This function is auto-generated +func CreateDataPrivacyRequest(ctx context.Context, s DataPrivacyRequests, rr ...*systemType.DataPrivacyRequest) error { + return s.CreateDataPrivacyRequest(ctx, rr...) +} + +// UpdateDataPrivacyRequest updates one or more (existing) DataPrivacyRequests in store +// +// This function is auto-generated +func UpdateDataPrivacyRequest(ctx context.Context, s DataPrivacyRequests, rr ...*systemType.DataPrivacyRequest) error { + return s.UpdateDataPrivacyRequest(ctx, rr...) +} + +// UpsertDataPrivacyRequest creates new or updates existing one or more DataPrivacyRequests in store +// +// This function is auto-generated +func UpsertDataPrivacyRequest(ctx context.Context, s DataPrivacyRequests, rr ...*systemType.DataPrivacyRequest) error { + return s.UpsertDataPrivacyRequest(ctx, rr...) +} + +// DeleteDataPrivacyRequest deletes one or more DataPrivacyRequests from store +// +// This function is auto-generated +func DeleteDataPrivacyRequest(ctx context.Context, s DataPrivacyRequests, rr ...*systemType.DataPrivacyRequest) error { + return s.DeleteDataPrivacyRequest(ctx, rr...) +} + +// DeleteDataPrivacyRequestByID deletes one or more DataPrivacyRequests from store +// +// This function is auto-generated +func DeleteDataPrivacyRequestByID(ctx context.Context, s DataPrivacyRequests, id uint64) error { + return s.DeleteDataPrivacyRequestByID(ctx, id) +} + +// TruncateDataPrivacyRequests Deletes all DataPrivacyRequests from store +// +// This function is auto-generated +func TruncateDataPrivacyRequests(ctx context.Context, s DataPrivacyRequests) error { + return s.TruncateDataPrivacyRequests(ctx) +} + +// LookupDataPrivacyRequestByID searches for data privacy request by ID +// +// It returns data privacy request even if deleted +// +// This function is auto-generated +func LookupDataPrivacyRequestByID(ctx context.Context, s DataPrivacyRequests, id uint64) (*systemType.DataPrivacyRequest, error) { + return s.LookupDataPrivacyRequestByID(ctx, id) +} + +// SearchDataPrivacyRequestComments returns all matching DataPrivacyRequestComments from store +// +// This function is auto-generated +func SearchDataPrivacyRequestComments(ctx context.Context, s DataPrivacyRequestComments, f systemType.DataPrivacyRequestCommentFilter) (systemType.DataPrivacyRequestCommentSet, systemType.DataPrivacyRequestCommentFilter, error) { + return s.SearchDataPrivacyRequestComments(ctx, f) +} + +// CreateDataPrivacyRequestComment creates one or more DataPrivacyRequestComments in store +// +// This function is auto-generated +func CreateDataPrivacyRequestComment(ctx context.Context, s DataPrivacyRequestComments, rr ...*systemType.DataPrivacyRequestComment) error { + return s.CreateDataPrivacyRequestComment(ctx, rr...) +} + +// UpdateDataPrivacyRequestComment updates one or more (existing) DataPrivacyRequestComments in store +// +// This function is auto-generated +func UpdateDataPrivacyRequestComment(ctx context.Context, s DataPrivacyRequestComments, rr ...*systemType.DataPrivacyRequestComment) error { + return s.UpdateDataPrivacyRequestComment(ctx, rr...) +} + +// UpsertDataPrivacyRequestComment creates new or updates existing one or more DataPrivacyRequestComments in store +// +// This function is auto-generated +func UpsertDataPrivacyRequestComment(ctx context.Context, s DataPrivacyRequestComments, rr ...*systemType.DataPrivacyRequestComment) error { + return s.UpsertDataPrivacyRequestComment(ctx, rr...) +} + +// DeleteDataPrivacyRequestComment deletes one or more DataPrivacyRequestComments from store +// +// This function is auto-generated +func DeleteDataPrivacyRequestComment(ctx context.Context, s DataPrivacyRequestComments, rr ...*systemType.DataPrivacyRequestComment) error { + return s.DeleteDataPrivacyRequestComment(ctx, rr...) +} + +// DeleteDataPrivacyRequestCommentByID deletes one or more DataPrivacyRequestComments from store +// +// This function is auto-generated +func DeleteDataPrivacyRequestCommentByID(ctx context.Context, s DataPrivacyRequestComments, id uint64) error { + return s.DeleteDataPrivacyRequestCommentByID(ctx, id) +} + +// TruncateDataPrivacyRequestComments Deletes all DataPrivacyRequestComments from store +// +// This function is auto-generated +func TruncateDataPrivacyRequestComments(ctx context.Context, s DataPrivacyRequestComments) error { + return s.TruncateDataPrivacyRequestComments(ctx) +} + // SearchFederationExposedModules returns all matching FederationExposedModules from store // // This function is auto-generated diff --git a/store/tests/all_test.go b/store/tests/all_test.go index 504c31ef7..dc8fd493b 100644 --- a/store/tests/all_test.go +++ b/store/tests/all_test.go @@ -83,6 +83,12 @@ func testAllGenerated(t *testing.T, s store.Storer) { t.Run("dalSensitivityLevel", func(t *testing.T) { testDalSensitivityLevels(t, s) }) + t.Run("dataPrivacyRequest", func(t *testing.T) { + testDataPrivacyRequests(t, s) + }) + t.Run("dataPrivacyRequestComment", func(t *testing.T) { + testDataPrivacyRequestComments(t, s) + }) t.Run("federationExposedModule", func(t *testing.T) { testFederationExposedModules(t, s) }) diff --git a/system/component.cue b/system/component.cue index ec031bd1c..7e028c032 100644 --- a/system/component.cue +++ b/system/component.cue @@ -8,27 +8,29 @@ component: schema.#component & { handle: "system" resources: { - "attachment": attachment - "application": application - "apigw-route": apigw_route - "apigw-filter": apigw_filter - "auth-client": auth_client - "auth-confirmed-client": auth_confirmed_client - "auth-session": auth_session - "auth-oa2token": auth_oa2token - "credential": credential - "queue": queue - "queue_message": queue_message - "reminder": reminder - "report": report - "resource-translation": resource_translation - "role": role - "role_member": role_member - "settings": settings - "template": template - "user": user - "dal_connection": dal_connection - "dal_sensitivity_level": dal_sensitivity_level + "attachment": attachment + "application": application + "apigw-route": apigw_route + "apigw-filter": apigw_filter + "auth-client": auth_client + "auth-confirmed-client": auth_confirmed_client + "auth-session": auth_session + "auth-oa2token": auth_oa2token + "credential": credential + "data-privacy-request": data_privacy_request + "data-privacy-request_comment": data_privacy_request_comment + "queue": queue + "queue_message": queue_message + "reminder": reminder + "report": report + "resource-translation": resource_translation + "role": role + "role_member": role_member + "settings": settings + "template": template + "user": user + "dal_connection": dal_connection + "dal_sensitivity_level": dal_sensitivity_level } rbac: operations: { @@ -70,5 +72,8 @@ component: schema.#component & { "apigw-routes.search": description: "List search or filter API gateway routes" "resource-translations.manage": description: "List, search, create, or update resource translations" + + "data-privacy-request.create": description: "Create data privacy requests" + "data-privacy-requests.search": description: "List, search or filter data privacy requests" } } diff --git a/system/data_privacy_request.cue b/system/data_privacy_request.cue new file mode 100644 index 000000000..edbd5d892 --- /dev/null +++ b/system/data_privacy_request.cue @@ -0,0 +1,66 @@ +package system + +import ( + "github.com/cortezaproject/corteza-server/codegen/schema" +) + +data_privacy_request: schema.#Resource & { + features: { + labels: false + } + + struct: { + id: schema.IdField + kind: { goType: "types.RequestKind", sortable: true } + status: { goType: "types.RequestStatus", sortable: true } + + requested_at: schema.SortableTimestampField + requested_by: { goType: "uint64" } + completed_at: schema.SortableTimestampNilField + completed_by: { goType: "uint64" } + + created_at: schema.SortableTimestampField + updated_at: schema.SortableTimestampNilField + deleted_at: schema.SortableTimestampNilField + created_by: { goType: "uint64" } + updated_by: { goType: "uint64" } + deleted_by: { goType: "uint64" } + } + + filter: { + struct: { + request_id: {goType: "[]uint64", ident: "requestID", storeIdent: "id" } + requested_by: {goType: "[]uint64", ident: "requestedBy" } + kind: {goType: "[]types.RequestKind"} + status: {goType: "[]types.RequestStatus"} + } + + query: ["kind", "status"] + byValue: ["kind", "status", "requested_by"] + } + + rbac: { + operations: { + read: + description: "Read data privacy request" + approve: + description: "Approve/Reject data privacy request" + } + } + + store: { + api: { + lookups: [ + { + fields: ["id"] + description: """ + searches for data privacy request by ID + + It returns data privacy request even if deleted + """ + } + ] + functions: [] + } + } +} diff --git a/system/data_privacy_request_comment.cue b/system/data_privacy_request_comment.cue new file mode 100644 index 000000000..3c5411beb --- /dev/null +++ b/system/data_privacy_request_comment.cue @@ -0,0 +1,43 @@ +package system + +import ( + "github.com/cortezaproject/corteza-server/codegen/schema" +) + +data_privacy_request_comment: schema.#Resource & { + features: { + labels: false + } + + struct: { + id: schema.IdField + request_id: { ident: "requestID", goType: "uint64", storeIdent: "rel_request" } + comment: { goType: "string" } + + created_at: schema.SortableTimestampField + updated_at: schema.SortableTimestampNilField + deleted_at: schema.SortableTimestampNilField + created_by: { goType: "uint64" } + updated_by: { goType: "uint64" } + deleted_by: { goType: "uint64" } + } + + filter: { + struct: { + request_id: {goType: "[]uint64", ident: "requestID", storeIdent: "rel_request" } + } + + query: [] + byValue: ["request_id"] + } + + rbac: { + operations: {} + } + + store: { + api: { + functions: [] + } + } +} diff --git a/system/rest.yaml b/system/rest.yaml index 48b702d6a..d2a9e01e3 100644 --- a/system/rest.yaml +++ b/system/rest.yaml @@ -2198,3 +2198,68 @@ endpoints: path: - { type: string, name: lang, required: true, title: Language } - { type: string, name: application, required: true, title: Application name } + +- title: Data Privacy Request + entrypoint: dataPrivacyRequest + path: "/data-privacy/requests" + apis: + - name: list + method: GET + title: List data privacy requests + path: "/" + parameters: + get: + - { name: requestedBy, type: "[]string", title: "Filter by user ID" } + - { name: query, type: "string", title: "Filter requests" } + - { name: kind, type: "[]string", title: "Filter by kind: correct, delete, export" } + - { name: status, type: "[]string", title: "Filter by status: pending, cancel, approve, reject" } + - { name: limit, type: "uint", title: "Limit" } + - { name: pageCursor, type: "string", title: "Page cursor" } + - { name: sort, type: "string", title: "Sort items" } + - name: create + method: POST + title: Create data privacy request + path: "/" + parameters: + post: + - { name: kind, type: "string", title: "Request Kind", required: true } + - name: update status + method: PATCH + title: Update data privacy request status + path: "/{requestID}/status/{status}" + parameters: + path: + - { name: requestID, type: "uint64", title: "ID", required: true } + - { name: status, type: "string", title: "Request Status", required: true } + - name: read + method: GET + title: Get details about specific request + path: "/{requestID}" + parameters: + path: + - { name: requestID, type: "uint64", title: "Request ID", required: true } + +- title: Data Privacy Request Comment + entrypoint: dataPrivacyRequestComment + path: "/data-privacy/requests/{requestID}/comments" + parameters: + path: + - { name: requestID, type: "uint64", title: "Request ID", required: true } + apis: + - name: list + method: GET + title: List data privacy request comments + path: "/" + parameters: + get: + - { name: limit, type: "uint", title: "Limit" } + - { name: pageCursor, type: "string", title: "Page cursor" } + - { name: sort, type: "string", title: "Sort items" } + - name: create + method: POST + title: Create data privacy request comment + path: "/" + parameters: + post: + - { name: comment, type: "string", title: "Comment description", required: true } + diff --git a/system/rest/data_privacy_request.go b/system/rest/data_privacy_request.go new file mode 100644 index 000000000..9707b74da --- /dev/null +++ b/system/rest/data_privacy_request.go @@ -0,0 +1,147 @@ +package rest + +import ( + "context" + "github.com/cortezaproject/corteza-server/pkg/filter" + "github.com/cortezaproject/corteza-server/pkg/payload" + "github.com/cortezaproject/corteza-server/system/rest/request" + "github.com/cortezaproject/corteza-server/system/service" + "github.com/cortezaproject/corteza-server/system/types" +) + +type ( + DataPrivacyRequest struct { + dataPrivacy service.DataPrivacyService + ac dataPrivacyAccessController + } + + dataPrivacyRequestSetPayload struct { + Filter types.DataPrivacyRequestFilter `json:"filter"` + Set types.DataPrivacyRequestSet `json:"set"` + } + + dataPrivacyAccessController interface { + CanGrant(context.Context) bool + } + + DataPrivacyRequestComment struct { + dataPrivacy service.DataPrivacyService + ac dataPrivacyAccessController + } + + dataPrivacyRequestCommentSetPayload struct { + Filter types.DataPrivacyRequestCommentFilter `json:"filter"` + Set types.DataPrivacyRequestCommentSet `json:"set"` + } +) + +func (DataPrivacyRequest) New() *DataPrivacyRequest { + return &DataPrivacyRequest{ + dataPrivacy: service.DefaultDataPrivacy, + ac: service.DefaultAccessControl, + } +} + +func (ctrl DataPrivacyRequest) List(ctx context.Context, r *request.DataPrivacyRequestList) (interface{}, error) { + var ( + err error + f = types.DataPrivacyRequestFilter{ + RequestedBy: payload.ParseUint64s(r.RequestedBy), + Query: r.Query, + Kind: r.Kind, + Status: r.Status, + } + ) + + if f.Paging, err = filter.NewPaging(r.Limit, r.PageCursor); err != nil { + return nil, err + } + + if f.Sorting, err = filter.NewSorting(r.Sort); err != nil { + return nil, err + } + + set, f, err := ctrl.dataPrivacy.FindRequests(ctx, f) + return ctrl.makeFilterPayload(ctx, set, f, err) +} + +func (ctrl DataPrivacyRequest) makeFilterPayload(_ context.Context, rr types.DataPrivacyRequestSet, f types.DataPrivacyRequestFilter, err error) (*dataPrivacyRequestSetPayload, error) { + if err != nil { + return nil, err + } + + if len(rr) == 0 { + rr = make([]*types.DataPrivacyRequest, 0) + } + + return &dataPrivacyRequestSetPayload{Filter: f, Set: rr}, nil +} + +func (ctrl DataPrivacyRequest) Create(ctx context.Context, r *request.DataPrivacyRequestCreate) (interface{}, error) { + req := &types.DataPrivacyRequest{ + Kind: types.CastToRequestKind(r.Kind), + } + + return ctrl.dataPrivacy.CreateRequest(ctx, req) +} + +func (ctrl DataPrivacyRequest) UpdateStatus(ctx context.Context, r *request.DataPrivacyRequestUpdateStatus) (interface{}, error) { + req := &types.DataPrivacyRequest{ + ID: r.RequestID, + Status: types.CastToRequestStatus(r.Status), + } + + return ctrl.dataPrivacy.UpdateRequestStatus(ctx, req) +} + +func (ctrl DataPrivacyRequest) Read(ctx context.Context, r *request.DataPrivacyRequestRead) (interface{}, error) { + return ctrl.dataPrivacy.FindRequestByID(ctx, r.RequestID) +} + +func (DataPrivacyRequestComment) New() *DataPrivacyRequestComment { + return &DataPrivacyRequestComment{ + dataPrivacy: service.DefaultDataPrivacy, + ac: service.DefaultAccessControl, + } +} + +func (ctrl DataPrivacyRequestComment) List(ctx context.Context, r *request.DataPrivacyRequestCommentList) (interface{}, error) { + var ( + err error + f = types.DataPrivacyRequestCommentFilter{ + RequestID: []uint64{r.RequestID}, + } + ) + + if f.Paging, err = filter.NewPaging(r.Limit, r.PageCursor); err != nil { + return nil, err + } + + if f.Sorting, err = filter.NewSorting(r.Sort); err != nil { + return nil, err + } + + set, f, err := ctrl.dataPrivacy.FindRequestComments(ctx, f) + return ctrl.makeFilterPayload(ctx, set, f, err) +} + +func (ctrl DataPrivacyRequestComment) makeFilterPayload(_ context.Context, rr types.DataPrivacyRequestCommentSet, f types.DataPrivacyRequestCommentFilter, err error) (*dataPrivacyRequestCommentSetPayload, error) { + if err != nil { + return nil, err + } + + if len(rr) == 0 { + rr = make([]*types.DataPrivacyRequestComment, 0) + } + + return &dataPrivacyRequestCommentSetPayload{Filter: f, Set: rr}, nil +} + +func (ctrl DataPrivacyRequestComment) Create(ctx context.Context, r *request.DataPrivacyRequestCommentCreate) (interface{}, error) { + req := &types.DataPrivacyRequestComment{ + RequestID: r.RequestID, + Comment: r.Comment, + } + + return ctrl.dataPrivacy.CreateRequestComment(ctx, req) +} diff --git a/system/rest/handlers/dataPrivacyRequest.go b/system/rest/handlers/dataPrivacyRequest.go new file mode 100644 index 000000000..7fd9bae52 --- /dev/null +++ b/system/rest/handlers/dataPrivacyRequest.go @@ -0,0 +1,114 @@ +package handlers + +// This file is auto-generated. +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +// Definitions file that controls how this file is generated: +// + +import ( + "context" + "github.com/cortezaproject/corteza-server/pkg/api" + "github.com/cortezaproject/corteza-server/system/rest/request" + "github.com/go-chi/chi/v5" + "net/http" +) + +type ( + // Internal API interface + DataPrivacyRequestAPI interface { + List(context.Context, *request.DataPrivacyRequestList) (interface{}, error) + Create(context.Context, *request.DataPrivacyRequestCreate) (interface{}, error) + UpdateStatus(context.Context, *request.DataPrivacyRequestUpdateStatus) (interface{}, error) + Read(context.Context, *request.DataPrivacyRequestRead) (interface{}, error) + } + + // HTTP API interface + DataPrivacyRequest struct { + List func(http.ResponseWriter, *http.Request) + Create func(http.ResponseWriter, *http.Request) + UpdateStatus func(http.ResponseWriter, *http.Request) + Read func(http.ResponseWriter, *http.Request) + } +) + +func NewDataPrivacyRequest(h DataPrivacyRequestAPI) *DataPrivacyRequest { + return &DataPrivacyRequest{ + List: func(w http.ResponseWriter, r *http.Request) { + defer r.Body.Close() + params := request.NewDataPrivacyRequestList() + if err := params.Fill(r); err != nil { + api.Send(w, r, err) + return + } + + value, err := h.List(r.Context(), params) + if err != nil { + api.Send(w, r, err) + return + } + + api.Send(w, r, value) + }, + Create: func(w http.ResponseWriter, r *http.Request) { + defer r.Body.Close() + params := request.NewDataPrivacyRequestCreate() + if err := params.Fill(r); err != nil { + api.Send(w, r, err) + return + } + + value, err := h.Create(r.Context(), params) + if err != nil { + api.Send(w, r, err) + return + } + + api.Send(w, r, value) + }, + UpdateStatus: func(w http.ResponseWriter, r *http.Request) { + defer r.Body.Close() + params := request.NewDataPrivacyRequestUpdateStatus() + if err := params.Fill(r); err != nil { + api.Send(w, r, err) + return + } + + value, err := h.UpdateStatus(r.Context(), params) + if err != nil { + api.Send(w, r, err) + return + } + + api.Send(w, r, value) + }, + Read: func(w http.ResponseWriter, r *http.Request) { + defer r.Body.Close() + params := request.NewDataPrivacyRequestRead() + if err := params.Fill(r); err != nil { + api.Send(w, r, err) + return + } + + value, err := h.Read(r.Context(), params) + if err != nil { + api.Send(w, r, err) + return + } + + api.Send(w, r, value) + }, + } +} + +func (h DataPrivacyRequest) MountRoutes(r chi.Router, middlewares ...func(http.Handler) http.Handler) { + r.Group(func(r chi.Router) { + r.Use(middlewares...) + r.Get("/data-privacy/requests/", h.List) + r.Post("/data-privacy/requests/", h.Create) + r.Patch("/data-privacy/requests/{requestID}/status/{status}", h.UpdateStatus) + r.Get("/data-privacy/requests/{requestID}", h.Read) + }) +} diff --git a/system/rest/handlers/dataPrivacyRequestComment.go b/system/rest/handlers/dataPrivacyRequestComment.go new file mode 100644 index 000000000..4f1946560 --- /dev/null +++ b/system/rest/handlers/dataPrivacyRequestComment.go @@ -0,0 +1,76 @@ +package handlers + +// This file is auto-generated. +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +// Definitions file that controls how this file is generated: +// + +import ( + "context" + "github.com/cortezaproject/corteza-server/pkg/api" + "github.com/cortezaproject/corteza-server/system/rest/request" + "github.com/go-chi/chi/v5" + "net/http" +) + +type ( + // Internal API interface + DataPrivacyRequestCommentAPI interface { + List(context.Context, *request.DataPrivacyRequestCommentList) (interface{}, error) + Create(context.Context, *request.DataPrivacyRequestCommentCreate) (interface{}, error) + } + + // HTTP API interface + DataPrivacyRequestComment struct { + List func(http.ResponseWriter, *http.Request) + Create func(http.ResponseWriter, *http.Request) + } +) + +func NewDataPrivacyRequestComment(h DataPrivacyRequestCommentAPI) *DataPrivacyRequestComment { + return &DataPrivacyRequestComment{ + List: func(w http.ResponseWriter, r *http.Request) { + defer r.Body.Close() + params := request.NewDataPrivacyRequestCommentList() + if err := params.Fill(r); err != nil { + api.Send(w, r, err) + return + } + + value, err := h.List(r.Context(), params) + if err != nil { + api.Send(w, r, err) + return + } + + api.Send(w, r, value) + }, + Create: func(w http.ResponseWriter, r *http.Request) { + defer r.Body.Close() + params := request.NewDataPrivacyRequestCommentCreate() + if err := params.Fill(r); err != nil { + api.Send(w, r, err) + return + } + + value, err := h.Create(r.Context(), params) + if err != nil { + api.Send(w, r, err) + return + } + + api.Send(w, r, value) + }, + } +} + +func (h DataPrivacyRequestComment) MountRoutes(r chi.Router, middlewares ...func(http.Handler) http.Handler) { + r.Group(func(r chi.Router) { + r.Use(middlewares...) + r.Get("/data-privacy/requests/{requestID}/comments/", h.List) + r.Post("/data-privacy/requests/{requestID}/comments/", h.Create) + }) +} diff --git a/system/rest/request/dataPrivacyRequest.go b/system/rest/request/dataPrivacyRequest.go new file mode 100644 index 000000000..d2a63a9b3 --- /dev/null +++ b/system/rest/request/dataPrivacyRequest.go @@ -0,0 +1,367 @@ +package request + +// This file is auto-generated. +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +// Definitions file that controls how this file is generated: +// + +import ( + "encoding/json" + "fmt" + "github.com/cortezaproject/corteza-server/pkg/payload" + "github.com/go-chi/chi/v5" + "io" + "mime/multipart" + "net/http" + "strings" +) + +// dummy vars to prevent +// unused imports complain +var ( + _ = chi.URLParam + _ = multipart.ErrMessageTooLarge + _ = payload.ParseUint64s + _ = strings.ToLower + _ = io.EOF + _ = fmt.Errorf + _ = json.NewEncoder +) + +type ( + // Internal API interface + DataPrivacyRequestList struct { + // RequestedBy GET parameter + // + // Filter by user ID + RequestedBy []string + + // Query GET parameter + // + // Filter requests + Query string + + // Kind GET parameter + // + // Filter by kind: correct, delete, export + Kind []string + + // Status GET parameter + // + // Filter by status: pending, cancel, approve, reject + Status []string + + // Limit GET parameter + // + // Limit + Limit uint + + // PageCursor GET parameter + // + // Page cursor + PageCursor string + + // Sort GET parameter + // + // Sort items + Sort string + } + + DataPrivacyRequestCreate struct { + // Kind POST parameter + // + // Request Kind + Kind string + } + + DataPrivacyRequestUpdateStatus struct { + // RequestID PATH parameter + // + // ID + RequestID uint64 `json:",string"` + + // Status PATH parameter + // + // Request Status + Status string + } + + DataPrivacyRequestRead struct { + // RequestID PATH parameter + // + // Request ID + RequestID uint64 `json:",string"` + } +) + +// NewDataPrivacyRequestList request +func NewDataPrivacyRequestList() *DataPrivacyRequestList { + return &DataPrivacyRequestList{} +} + +// Auditable returns all auditable/loggable parameters +func (r DataPrivacyRequestList) Auditable() map[string]interface{} { + return map[string]interface{}{ + "requestedBy": r.RequestedBy, + "query": r.Query, + "kind": r.Kind, + "status": r.Status, + "limit": r.Limit, + "pageCursor": r.PageCursor, + "sort": r.Sort, + } +} + +// Auditable returns all auditable/loggable parameters +func (r DataPrivacyRequestList) GetRequestedBy() []string { + return r.RequestedBy +} + +// Auditable returns all auditable/loggable parameters +func (r DataPrivacyRequestList) GetQuery() string { + return r.Query +} + +// Auditable returns all auditable/loggable parameters +func (r DataPrivacyRequestList) GetKind() []string { + return r.Kind +} + +// Auditable returns all auditable/loggable parameters +func (r DataPrivacyRequestList) GetStatus() []string { + return r.Status +} + +// Auditable returns all auditable/loggable parameters +func (r DataPrivacyRequestList) GetLimit() uint { + return r.Limit +} + +// Auditable returns all auditable/loggable parameters +func (r DataPrivacyRequestList) GetPageCursor() string { + return r.PageCursor +} + +// Auditable returns all auditable/loggable parameters +func (r DataPrivacyRequestList) GetSort() string { + return r.Sort +} + +// Fill processes request and fills internal variables +func (r *DataPrivacyRequestList) Fill(req *http.Request) (err error) { + + { + // GET params + tmp := req.URL.Query() + + if val, ok := tmp["requestedBy[]"]; ok { + r.RequestedBy, err = val, nil + if err != nil { + return err + } + } else if val, ok := tmp["requestedBy"]; ok { + r.RequestedBy, err = val, nil + if err != nil { + return err + } + } + if val, ok := tmp["query"]; ok && len(val) > 0 { + r.Query, err = val[0], nil + if err != nil { + return err + } + } + if val, ok := tmp["kind[]"]; ok { + r.Kind, err = val, nil + if err != nil { + return err + } + } else if val, ok := tmp["kind"]; ok { + r.Kind, err = val, nil + if err != nil { + return err + } + } + if val, ok := tmp["status[]"]; ok { + r.Status, err = val, nil + if err != nil { + return err + } + } else if val, ok := tmp["status"]; ok { + r.Status, err = val, nil + if err != nil { + return err + } + } + if val, ok := tmp["limit"]; ok && len(val) > 0 { + r.Limit, err = payload.ParseUint(val[0]), nil + if err != nil { + return err + } + } + if val, ok := tmp["pageCursor"]; ok && len(val) > 0 { + r.PageCursor, err = val[0], nil + if err != nil { + return err + } + } + if val, ok := tmp["sort"]; ok && len(val) > 0 { + r.Sort, err = val[0], nil + if err != nil { + return err + } + } + } + + return err +} + +// NewDataPrivacyRequestCreate request +func NewDataPrivacyRequestCreate() *DataPrivacyRequestCreate { + return &DataPrivacyRequestCreate{} +} + +// Auditable returns all auditable/loggable parameters +func (r DataPrivacyRequestCreate) Auditable() map[string]interface{} { + return map[string]interface{}{ + "kind": r.Kind, + } +} + +// Auditable returns all auditable/loggable parameters +func (r DataPrivacyRequestCreate) GetKind() string { + return r.Kind +} + +// Fill processes request and fills internal variables +func (r *DataPrivacyRequestCreate) Fill(req *http.Request) (err error) { + + if strings.HasPrefix(strings.ToLower(req.Header.Get("content-type")), "application/json") { + err = json.NewDecoder(req.Body).Decode(r) + + switch { + case err == io.EOF: + err = nil + case err != nil: + return fmt.Errorf("error parsing http request body: %w", err) + } + } + + { + // Caching 32MB to memory, the rest to disk + if err = req.ParseMultipartForm(32 << 20); err != nil && err != http.ErrNotMultipart { + return err + } else if err == nil { + // Multipart params + + if val, ok := req.MultipartForm.Value["kind"]; ok && len(val) > 0 { + r.Kind, err = val[0], nil + if err != nil { + return err + } + } + } + } + + { + if err = req.ParseForm(); err != nil { + return err + } + + // POST params + + if val, ok := req.Form["kind"]; ok && len(val) > 0 { + r.Kind, err = val[0], nil + if err != nil { + return err + } + } + } + + return err +} + +// NewDataPrivacyRequestUpdateStatus request +func NewDataPrivacyRequestUpdateStatus() *DataPrivacyRequestUpdateStatus { + return &DataPrivacyRequestUpdateStatus{} +} + +// Auditable returns all auditable/loggable parameters +func (r DataPrivacyRequestUpdateStatus) Auditable() map[string]interface{} { + return map[string]interface{}{ + "requestID": r.RequestID, + "status": r.Status, + } +} + +// Auditable returns all auditable/loggable parameters +func (r DataPrivacyRequestUpdateStatus) GetRequestID() uint64 { + return r.RequestID +} + +// Auditable returns all auditable/loggable parameters +func (r DataPrivacyRequestUpdateStatus) GetStatus() string { + return r.Status +} + +// Fill processes request and fills internal variables +func (r *DataPrivacyRequestUpdateStatus) Fill(req *http.Request) (err error) { + + { + var val string + // path params + + val = chi.URLParam(req, "requestID") + r.RequestID, err = payload.ParseUint64(val), nil + if err != nil { + return err + } + + val = chi.URLParam(req, "status") + r.Status, err = val, nil + if err != nil { + return err + } + + } + + return err +} + +// NewDataPrivacyRequestRead request +func NewDataPrivacyRequestRead() *DataPrivacyRequestRead { + return &DataPrivacyRequestRead{} +} + +// Auditable returns all auditable/loggable parameters +func (r DataPrivacyRequestRead) Auditable() map[string]interface{} { + return map[string]interface{}{ + "requestID": r.RequestID, + } +} + +// Auditable returns all auditable/loggable parameters +func (r DataPrivacyRequestRead) GetRequestID() uint64 { + return r.RequestID +} + +// Fill processes request and fills internal variables +func (r *DataPrivacyRequestRead) Fill(req *http.Request) (err error) { + + { + var val string + // path params + + val = chi.URLParam(req, "requestID") + r.RequestID, err = payload.ParseUint64(val), nil + if err != nil { + return err + } + + } + + return err +} diff --git a/system/rest/request/dataPrivacyRequestComment.go b/system/rest/request/dataPrivacyRequestComment.go new file mode 100644 index 000000000..6b1ef1bb1 --- /dev/null +++ b/system/rest/request/dataPrivacyRequestComment.go @@ -0,0 +1,229 @@ +package request + +// This file is auto-generated. +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +// Definitions file that controls how this file is generated: +// + +import ( + "encoding/json" + "fmt" + "github.com/cortezaproject/corteza-server/pkg/payload" + "github.com/go-chi/chi/v5" + "io" + "mime/multipart" + "net/http" + "strings" +) + +// dummy vars to prevent +// unused imports complain +var ( + _ = chi.URLParam + _ = multipart.ErrMessageTooLarge + _ = payload.ParseUint64s + _ = strings.ToLower + _ = io.EOF + _ = fmt.Errorf + _ = json.NewEncoder +) + +type ( + // Internal API interface + DataPrivacyRequestCommentList struct { + // RequestID PATH parameter + // + // Request ID + RequestID uint64 `json:",string"` + + // Limit GET parameter + // + // Limit + Limit uint + + // PageCursor GET parameter + // + // Page cursor + PageCursor string + + // Sort GET parameter + // + // Sort items + Sort string + } + + DataPrivacyRequestCommentCreate struct { + // RequestID PATH parameter + // + // Request ID + RequestID uint64 `json:",string"` + + // Comment POST parameter + // + // Comment description + Comment string + } +) + +// NewDataPrivacyRequestCommentList request +func NewDataPrivacyRequestCommentList() *DataPrivacyRequestCommentList { + return &DataPrivacyRequestCommentList{} +} + +// Auditable returns all auditable/loggable parameters +func (r DataPrivacyRequestCommentList) Auditable() map[string]interface{} { + return map[string]interface{}{ + "requestID": r.RequestID, + "limit": r.Limit, + "pageCursor": r.PageCursor, + "sort": r.Sort, + } +} + +// Auditable returns all auditable/loggable parameters +func (r DataPrivacyRequestCommentList) GetRequestID() uint64 { + return r.RequestID +} + +// Auditable returns all auditable/loggable parameters +func (r DataPrivacyRequestCommentList) GetLimit() uint { + return r.Limit +} + +// Auditable returns all auditable/loggable parameters +func (r DataPrivacyRequestCommentList) GetPageCursor() string { + return r.PageCursor +} + +// Auditable returns all auditable/loggable parameters +func (r DataPrivacyRequestCommentList) GetSort() string { + return r.Sort +} + +// Fill processes request and fills internal variables +func (r *DataPrivacyRequestCommentList) Fill(req *http.Request) (err error) { + + { + // GET params + tmp := req.URL.Query() + + if val, ok := tmp["limit"]; ok && len(val) > 0 { + r.Limit, err = payload.ParseUint(val[0]), nil + if err != nil { + return err + } + } + if val, ok := tmp["pageCursor"]; ok && len(val) > 0 { + r.PageCursor, err = val[0], nil + if err != nil { + return err + } + } + if val, ok := tmp["sort"]; ok && len(val) > 0 { + r.Sort, err = val[0], nil + if err != nil { + return err + } + } + } + + { + var val string + // path params + + val = chi.URLParam(req, "requestID") + r.RequestID, err = payload.ParseUint64(val), nil + if err != nil { + return err + } + + } + + return err +} + +// NewDataPrivacyRequestCommentCreate request +func NewDataPrivacyRequestCommentCreate() *DataPrivacyRequestCommentCreate { + return &DataPrivacyRequestCommentCreate{} +} + +// Auditable returns all auditable/loggable parameters +func (r DataPrivacyRequestCommentCreate) Auditable() map[string]interface{} { + return map[string]interface{}{ + "requestID": r.RequestID, + "comment": r.Comment, + } +} + +// Auditable returns all auditable/loggable parameters +func (r DataPrivacyRequestCommentCreate) GetRequestID() uint64 { + return r.RequestID +} + +// Auditable returns all auditable/loggable parameters +func (r DataPrivacyRequestCommentCreate) GetComment() string { + return r.Comment +} + +// Fill processes request and fills internal variables +func (r *DataPrivacyRequestCommentCreate) Fill(req *http.Request) (err error) { + + if strings.HasPrefix(strings.ToLower(req.Header.Get("content-type")), "application/json") { + err = json.NewDecoder(req.Body).Decode(r) + + switch { + case err == io.EOF: + err = nil + case err != nil: + return fmt.Errorf("error parsing http request body: %w", err) + } + } + + { + // Caching 32MB to memory, the rest to disk + if err = req.ParseMultipartForm(32 << 20); err != nil && err != http.ErrNotMultipart { + return err + } else if err == nil { + // Multipart params + + if val, ok := req.MultipartForm.Value["comment"]; ok && len(val) > 0 { + r.Comment, err = val[0], nil + if err != nil { + return err + } + } + } + } + + { + if err = req.ParseForm(); err != nil { + return err + } + + // POST params + + if val, ok := req.Form["comment"]; ok && len(val) > 0 { + r.Comment, err = val[0], nil + if err != nil { + return err + } + } + } + + { + var val string + // path params + + val = chi.URLParam(req, "requestID") + r.RequestID, err = payload.ParseUint64(val), nil + if err != nil { + return err + } + + } + + return err +} diff --git a/system/rest/router.go b/system/rest/router.go index 85f0b6883..8239f7f81 100644 --- a/system/rest/router.go +++ b/system/rest/router.go @@ -47,6 +47,8 @@ func MountRoutes() func(r chi.Router) { handlers.NewApigwRoute(ApigwRoute{}.New()).MountRoutes(r) handlers.NewApigwFilter(ApigwFilter{}.New()).MountRoutes(r) handlers.NewApigwProfiler(ApigwProfiler{}.New()).MountRoutes(r) + handlers.NewDataPrivacyRequest(DataPrivacyRequest{}.New()).MountRoutes(r) + handlers.NewDataPrivacyRequestComment(DataPrivacyRequestComment{}.New()).MountRoutes(r) }) } } diff --git a/system/service/access_control.gen.go b/system/service/access_control.gen.go index 74c7607de..1894b4f50 100644 --- a/system/service/access_control.gen.go +++ b/system/service/access_control.gen.go @@ -104,6 +104,16 @@ func (svc accessControl) List() (out []map[string]string) { "any": types.AuthClientRbacResource(0), "op": "authorize", }, + { + "type": types.DataPrivacyRequestResourceType, + "any": types.DataPrivacyRequestRbacResource(0), + "op": "read", + }, + { + "type": types.DataPrivacyRequestResourceType, + "any": types.DataPrivacyRequestRbacResource(0), + "op": "approve", + }, { "type": types.QueueResourceType, "any": types.QueueRbacResource(0), @@ -404,6 +414,16 @@ func (svc accessControl) List() (out []map[string]string) { "any": types.ComponentRbacResource(), "op": "resource-translations.manage", }, + { + "type": types.ComponentResourceType, + "any": types.ComponentRbacResource(), + "op": "data-privacy-request.create", + }, + { + "type": types.ComponentResourceType, + "any": types.ComponentRbacResource(), + "op": "data-privacy-requests.search", + }, } func(svc interface{}) { @@ -547,6 +567,20 @@ func (svc accessControl) CanAuthorizeAuthClient(ctx context.Context, r *types.Au return svc.can(ctx, "authorize", r) } +// CanReadDataPrivacyRequest checks if current user can read data privacy request +// +// This function is auto-generated +func (svc accessControl) CanReadDataPrivacyRequest(ctx context.Context, r *types.DataPrivacyRequest) bool { + return svc.can(ctx, "read", r) +} + +// CanApproveDataPrivacyRequest checks if current user can approve/reject data privacy request +// +// This function is auto-generated +func (svc accessControl) CanApproveDataPrivacyRequest(ctx context.Context, r *types.DataPrivacyRequest) bool { + return svc.can(ctx, "approve", r) +} + // CanReadQueue checks if current user can read queue // // This function is auto-generated @@ -994,6 +1028,22 @@ func (svc accessControl) CanManageResourceTranslations(ctx context.Context) bool return svc.can(ctx, "resource-translations.manage", r) } +// CanCreateDataPrivacyRequest checks if current user can create data privacy requests +// +// This function is auto-generated +func (svc accessControl) CanCreateDataPrivacyRequest(ctx context.Context) bool { + r := &types.Component{} + return svc.can(ctx, "data-privacy-request.create", r) +} + +// CanSearchDataPrivacyRequests checks if current user can list, search or filter data privacy requests +// +// This function is auto-generated +func (svc accessControl) CanSearchDataPrivacyRequests(ctx context.Context) bool { + r := &types.Component{} + return svc.can(ctx, "data-privacy-requests.search", r) +} + // rbacResourceValidator validates known component's resource by routing it to the appropriate validator // // This function is auto-generated @@ -1005,6 +1055,10 @@ func rbacResourceValidator(r string, oo ...string) error { return rbacApigwRouteResourceValidator(r, oo...) case types.AuthClientResourceType: return rbacAuthClientResourceValidator(r, oo...) + case types.DataPrivacyRequestResourceType: + return rbacDataPrivacyRequestResourceValidator(r, oo...) + case types.DataPrivacyRequestCommentResourceType: + return rbacDataPrivacyRequestCommentResourceValidator(r, oo...) case types.QueueResourceType: return rbacQueueResourceValidator(r, oo...) case types.QueueMessageResourceType: @@ -1052,6 +1106,13 @@ func rbacResourceOperations(r string) map[string]bool { "delete": true, "authorize": true, } + case types.DataPrivacyRequestResourceType: + return map[string]bool{ + "read": true, + "approve": true, + } + case types.DataPrivacyRequestCommentResourceType: + return map[string]bool{} case types.QueueResourceType: return map[string]bool{ "read": true, @@ -1137,6 +1198,8 @@ func rbacResourceOperations(r string) map[string]bool { "apigw-route.create": true, "apigw-routes.search": true, "resource-translations.manage": true, + "data-privacy-request.create": true, + "data-privacy-requests.search": true, } } @@ -1275,6 +1338,94 @@ func rbacAuthClientResourceValidator(r string, oo ...string) error { return nil } +// rbacDataPrivacyRequestResourceValidator checks validity of RBAC resource and operations +// +// Can be called without operations to check for validity of resource string only +// +// This function is auto-generated +func rbacDataPrivacyRequestResourceValidator(r string, oo ...string) error { + if !strings.HasPrefix(r, types.DataPrivacyRequestResourceType) { + // expecting resource to always include path + return fmt.Errorf("invalid resource type") + } + + defOps := rbacResourceOperations(r) + for _, o := range oo { + if !defOps[o] { + return fmt.Errorf("invalid operation '%s' for dataPrivacyRequest resource", o) + } + } + + const sep = "/" + var ( + pp = strings.Split(strings.Trim(r[len(types.DataPrivacyRequestResourceType):], sep), sep) + prc = []string{ + "ID", + } + ) + + if len(pp) != len(prc) { + return fmt.Errorf("invalid resource path structure") + } + + for i := 0; i < len(pp); i++ { + if pp[i] != "*" { + if i > 0 && pp[i-1] == "*" { + return fmt.Errorf("invalid path wildcard level (%d) for dataPrivacyRequest resource", i) + } + + if _, err := cast.ToUint64E(pp[i]); err != nil { + return fmt.Errorf("invalid reference for %s: '%s'", prc[i], pp[i]) + } + } + } + return nil +} + +// rbacDataPrivacyRequestCommentResourceValidator checks validity of RBAC resource and operations +// +// Can be called without operations to check for validity of resource string only +// +// This function is auto-generated +func rbacDataPrivacyRequestCommentResourceValidator(r string, oo ...string) error { + if !strings.HasPrefix(r, types.DataPrivacyRequestCommentResourceType) { + // expecting resource to always include path + return fmt.Errorf("invalid resource type") + } + + defOps := rbacResourceOperations(r) + for _, o := range oo { + if !defOps[o] { + return fmt.Errorf("invalid operation '%s' for dataPrivacyRequestComment resource", o) + } + } + + const sep = "/" + var ( + pp = strings.Split(strings.Trim(r[len(types.DataPrivacyRequestCommentResourceType):], sep), sep) + prc = []string{ + "ID", + } + ) + + if len(pp) != len(prc) { + return fmt.Errorf("invalid resource path structure") + } + + for i := 0; i < len(pp); i++ { + if pp[i] != "*" { + if i > 0 && pp[i-1] == "*" { + return fmt.Errorf("invalid path wildcard level (%d) for dataPrivacyRequestComment resource", i) + } + + if _, err := cast.ToUint64E(pp[i]); err != nil { + return fmt.Errorf("invalid reference for %s: '%s'", prc[i], pp[i]) + } + } + } + return nil +} + // rbacQueueResourceValidator checks validity of RBAC resource and operations // // Can be called without operations to check for validity of resource string only diff --git a/system/service/data_privacy.go b/system/service/data_privacy.go new file mode 100644 index 000000000..3c24d9109 --- /dev/null +++ b/system/service/data_privacy.go @@ -0,0 +1,241 @@ +package service + +import ( + "context" + "github.com/cortezaproject/corteza-server/pkg/actionlog" + a "github.com/cortezaproject/corteza-server/pkg/auth" + "github.com/cortezaproject/corteza-server/pkg/errors" + "github.com/cortezaproject/corteza-server/store" + "github.com/cortezaproject/corteza-server/system/service/event" + "github.com/cortezaproject/corteza-server/system/types" +) + +type ( + dataPrivacy struct { + actionlog actionlog.Recorder + + ac dataPrivacyAccessController + eventbus eventDispatcher + + store store.Storer + } + + dataPrivacyAccessController interface { + CanSearchDataPrivacyRequests(context.Context) bool + CanCreateDataPrivacyRequest(context.Context) bool + CanReadDataPrivacyRequest(context.Context, *types.DataPrivacyRequest) bool + CanApproveDataPrivacyRequest(context.Context, *types.DataPrivacyRequest) bool + } + + DataPrivacyService interface { + FindRequestByID(ctx context.Context, requestID uint64) (*types.DataPrivacyRequest, error) + FindRequests(context.Context, types.DataPrivacyRequestFilter) (types.DataPrivacyRequestSet, types.DataPrivacyRequestFilter, error) + CreateRequest(ctx context.Context, request *types.DataPrivacyRequest) (*types.DataPrivacyRequest, error) + UpdateRequestStatus(ctx context.Context, request *types.DataPrivacyRequest) (*types.DataPrivacyRequest, error) + + FindRequestComments(ctx context.Context, filter types.DataPrivacyRequestCommentFilter) (rr types.DataPrivacyRequestCommentSet, f types.DataPrivacyRequestCommentFilter, err error) + CreateRequestComment(ctx context.Context, new *types.DataPrivacyRequestComment) (r *types.DataPrivacyRequestComment, err error) + } +) + +func DataPrivacy(s store.Storer, ac dataPrivacyAccessController, al actionlog.Recorder, eb eventDispatcher) *dataPrivacy { + return &dataPrivacy{ + actionlog: al, + ac: ac, + eventbus: eb, + store: s, + } +} + +func (svc dataPrivacy) FindRequestByID(ctx context.Context, requestID uint64) (r *types.DataPrivacyRequest, err error) { + var ( + raProps = &dataPrivacyActionProps{dataPrivacyRequest: &types.DataPrivacyRequest{ID: requestID}} + ) + + err = func() error { + if requestID == 0 { + return DataPrivacyErrInvalidID() + } + + r, err = store.LookupDataPrivacyRequestByID(ctx, svc.store, requestID) + if r, err = svc.procRequest(ctx, r, err); err != nil { + return err + } + + raProps.setDataPrivacyRequest(r) + + if !svc.ac.CanReadDataPrivacyRequest(ctx, r) { + return DataPrivacyErrNotAllowedToRead() + } + + return nil + }() + + return r, svc.recordAction(ctx, raProps, DataPrivacyActionLookup, err) +} + +func (svc dataPrivacy) procRequest(_ context.Context, r *types.DataPrivacyRequest, err error) (*types.DataPrivacyRequest, error) { + if err != nil { + if errors.IsNotFound(err) { + return nil, DataPrivacyErrNotFound() + } + + return nil, err + } + + return r, nil +} + +func (svc dataPrivacy) FindRequests(ctx context.Context, filter types.DataPrivacyRequestFilter) (rr types.DataPrivacyRequestSet, f types.DataPrivacyRequestFilter, err error) { + var ( + raProps = &dataPrivacyActionProps{filter: &filter} + ) + + // For each fetched item, store backend will check if it is valid or not + filter.Check = func(req *types.DataPrivacyRequest) (bool, error) { + if !svc.ac.CanReadDataPrivacyRequest(ctx, req) { + return false, nil + } + + return true, nil + } + + err = func() error { + if !svc.ac.CanSearchDataPrivacyRequests(ctx) { + return DataPrivacyErrNotAllowedToSearch() + } + + if rr, f, err = store.SearchDataPrivacyRequests(ctx, svc.store, filter); err != nil { + return err + } + + return nil + }() + + return rr, f, svc.recordAction(ctx, raProps, DataPrivacyActionSearch, err) +} + +func (svc dataPrivacy) CreateRequest(ctx context.Context, new *types.DataPrivacyRequest) (r *types.DataPrivacyRequest, err error) { + var ( + raProps = &dataPrivacyActionProps{new: new} + ) + + err = func() (err error) { + if len(new.Kind.String()) == 0 { + return DataPrivacyErrInvalidKind() + } + + if !svc.ac.CanCreateDataPrivacyRequest(ctx) { + return DataPrivacyErrNotAllowedToCreate() + } + + if err = svc.eventbus.WaitFor(ctx, event.DataPrivacyRequestBeforeCreate(new, r)); err != nil { + return + } + + new.ID = nextID() + new.Status = types.RequestStatusPending + new.RequestedAt = *now() + new.RequestedBy = a.GetIdentityFromContext(ctx).Identity() + new.CreatedAt = *now() + + if err = store.CreateDataPrivacyRequest(ctx, svc.store, new); err != nil { + return + } + + r = new + + svc.eventbus.Dispatch(ctx, event.DataPrivacyRequestAfterCreate(new, r)) + return + }() + + return r, svc.recordAction(ctx, raProps, DataPrivacyActionCreate, err) +} + +func (svc dataPrivacy) UpdateRequestStatus(ctx context.Context, upd *types.DataPrivacyRequest) (r *types.DataPrivacyRequest, err error) { + var ( + raProps = &dataPrivacyActionProps{update: upd} + ) + + err = func() (err error) { + if upd.ID == 0 { + return DataPrivacyErrInvalidID() + } + + if len(upd.Status.String()) == 0 { + return DataPrivacyErrInvalidStatus() + } + + if upd.Status == types.RequestStatusPending { + return DataPrivacyErrInvalidStatus() + } + + if upd.Status == types.RequestStatusApproved || upd.Status == types.RequestStatusRejected { + if !svc.ac.CanApproveDataPrivacyRequest(ctx, upd) { + return DataPrivacyErrNotAllowedToApprove() + } + } + + if r, err = store.LookupDataPrivacyRequestByID(ctx, svc.store, upd.ID); err != nil { + return + } + + raProps.setDataPrivacyRequest(r) + + if err = svc.eventbus.WaitFor(ctx, event.DataPrivacyRequestBeforeUpdate(upd, r)); err != nil { + return + } + + r.Status = upd.Status + r.CompletedAt = now() + r.CompletedBy = a.GetIdentityFromContext(ctx).Identity() + r.UpdatedAt = now() + + // Assign changed values + if err = store.UpdateDataPrivacyRequest(ctx, svc.store, r); err != nil { + return err + } + + svc.eventbus.Dispatch(ctx, event.DataPrivacyRequestAfterUpdate(upd, r)) + + return nil + }() + + return r, svc.recordAction(ctx, raProps, DataPrivacyActionApprove, err) +} + +func (svc dataPrivacy) FindRequestComments(ctx context.Context, filter types.DataPrivacyRequestCommentFilter) (rr types.DataPrivacyRequestCommentSet, f types.DataPrivacyRequestCommentFilter, err error) { + err = func() error { + if rr, f, err = store.SearchDataPrivacyRequestComments(ctx, svc.store, filter); err != nil { + return err + } + + return nil + }() + + return rr, f, err +} + +func (svc dataPrivacy) CreateRequestComment(ctx context.Context, new *types.DataPrivacyRequestComment) (r *types.DataPrivacyRequestComment, err error) { + err = func() (err error) { + + _, err = svc.FindRequestByID(ctx, new.RequestID) + if err != nil { + return + } + + new.ID = nextID() + new.CreatedBy = a.GetIdentityFromContext(ctx).Identity() + new.CreatedAt = *now() + + if err = store.CreateDataPrivacyRequestComment(ctx, svc.store, new); err != nil { + return + } + + r = new + + return + }() + + return r, err +} diff --git a/system/service/data_privacy_request_actions.gen.go b/system/service/data_privacy_request_actions.gen.go new file mode 100644 index 000000000..1964cf286 --- /dev/null +++ b/system/service/data_privacy_request_actions.gen.go @@ -0,0 +1,706 @@ +package service + +// This file is auto-generated. +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +// Definitions file that controls how this file is generated: +// system/service/data_privacy_request_actions.yaml + +import ( + "context" + "fmt" + "github.com/cortezaproject/corteza-server/pkg/actionlog" + "github.com/cortezaproject/corteza-server/pkg/errors" + "github.com/cortezaproject/corteza-server/pkg/locale" + "github.com/cortezaproject/corteza-server/system/types" + "strings" + "time" +) + +type ( + dataPrivacyActionProps struct { + dataPrivacyRequest *types.DataPrivacyRequest + new *types.DataPrivacyRequest + update *types.DataPrivacyRequest + filter *types.DataPrivacyRequestFilter + } + + dataPrivacyAction struct { + timestamp time.Time + resource string + action string + log string + severity actionlog.Severity + + // prefix for error when action fails + errorMessage string + + props *dataPrivacyActionProps + } + + dataPrivacyLogMetaKey struct{} + dataPrivacyPropsMetaKey struct{} +) + +var ( + // just a placeholder to cover template cases w/o fmt package use + _ = fmt.Println +) + +// ********************************************************************************************************************* +// ********************************************************************************************************************* +// Props methods +// setDataPrivacyRequest updates dataPrivacyActionProps's dataPrivacyRequest +// +// Allows method chaining +// +// This function is auto-generated. +// +func (p *dataPrivacyActionProps) setDataPrivacyRequest(dataPrivacyRequest *types.DataPrivacyRequest) *dataPrivacyActionProps { + p.dataPrivacyRequest = dataPrivacyRequest + return p +} + +// setNew updates dataPrivacyActionProps's new +// +// Allows method chaining +// +// This function is auto-generated. +// +func (p *dataPrivacyActionProps) setNew(new *types.DataPrivacyRequest) *dataPrivacyActionProps { + p.new = new + return p +} + +// setUpdate updates dataPrivacyActionProps's update +// +// Allows method chaining +// +// This function is auto-generated. +// +func (p *dataPrivacyActionProps) setUpdate(update *types.DataPrivacyRequest) *dataPrivacyActionProps { + p.update = update + return p +} + +// setFilter updates dataPrivacyActionProps's filter +// +// Allows method chaining +// +// This function is auto-generated. +// +func (p *dataPrivacyActionProps) setFilter(filter *types.DataPrivacyRequestFilter) *dataPrivacyActionProps { + p.filter = filter + return p +} + +// Serialize converts dataPrivacyActionProps to actionlog.Meta +// +// This function is auto-generated. +// +func (p dataPrivacyActionProps) Serialize() actionlog.Meta { + var ( + m = make(actionlog.Meta) + ) + + if p.dataPrivacyRequest != nil { + m.Set("dataPrivacyRequest.kind", p.dataPrivacyRequest.Kind, true) + m.Set("dataPrivacyRequest.ID", p.dataPrivacyRequest.ID, true) + } + if p.new != nil { + m.Set("new.kind", p.new.Kind, true) + m.Set("new.ID", p.new.ID, true) + } + if p.update != nil { + m.Set("update.kind", p.update.Kind, true) + m.Set("update.ID", p.update.ID, true) + } + if p.filter != nil { + m.Set("filter.kind", p.filter.Kind, true) + m.Set("filter.sort", p.filter.Sort, true) + } + + return m +} + +// tr translates string and replaces meta value placeholder with values +// +// This function is auto-generated. +// +func (p dataPrivacyActionProps) Format(in string, err error) string { + var ( + pairs = []string{"{{err}}"} + // first non-empty string + fns = func(ii ...interface{}) string { + for _, i := range ii { + if s := fmt.Sprintf("%v", i); len(s) > 0 { + return s + } + } + + return "" + } + ) + + if err != nil { + pairs = append(pairs, err.Error()) + } else { + pairs = append(pairs, "nil") + } + + if p.dataPrivacyRequest != nil { + // replacement for "{{dataPrivacyRequest}}" (in order how fields are defined) + pairs = append( + pairs, + "{{dataPrivacyRequest}}", + fns( + p.dataPrivacyRequest.Kind, + p.dataPrivacyRequest.ID, + ), + ) + pairs = append(pairs, "{{dataPrivacyRequest.kind}}", fns(p.dataPrivacyRequest.Kind)) + pairs = append(pairs, "{{dataPrivacyRequest.ID}}", fns(p.dataPrivacyRequest.ID)) + } + + if p.new != nil { + // replacement for "{{new}}" (in order how fields are defined) + pairs = append( + pairs, + "{{new}}", + fns( + p.new.Kind, + p.new.ID, + ), + ) + pairs = append(pairs, "{{new.kind}}", fns(p.new.Kind)) + pairs = append(pairs, "{{new.ID}}", fns(p.new.ID)) + } + + if p.update != nil { + // replacement for "{{update}}" (in order how fields are defined) + pairs = append( + pairs, + "{{update}}", + fns( + p.update.Kind, + p.update.ID, + ), + ) + pairs = append(pairs, "{{update.kind}}", fns(p.update.Kind)) + pairs = append(pairs, "{{update.ID}}", fns(p.update.ID)) + } + + if p.filter != nil { + // replacement for "{{filter}}" (in order how fields are defined) + pairs = append( + pairs, + "{{filter}}", + fns( + p.filter.Kind, + p.filter.Sort, + ), + ) + pairs = append(pairs, "{{filter.kind}}", fns(p.filter.Kind)) + pairs = append(pairs, "{{filter.sort}}", fns(p.filter.Sort)) + } + return strings.NewReplacer(pairs...).Replace(in) +} + +// ********************************************************************************************************************* +// ********************************************************************************************************************* +// Action methods + +// String returns loggable description as string +// +// This function is auto-generated. +// +func (a *dataPrivacyAction) String() string { + var props = &dataPrivacyActionProps{} + + if a.props != nil { + props = a.props + } + + return props.Format(a.log, nil) +} + +func (e *dataPrivacyAction) ToAction() *actionlog.Action { + return &actionlog.Action{ + Resource: e.resource, + Action: e.action, + Severity: e.severity, + Description: e.String(), + Meta: e.props.Serialize(), + } +} + +// ********************************************************************************************************************* +// ********************************************************************************************************************* +// Action constructors + +// DataPrivacyActionSearch returns "system:data-privacy-request.search" action +// +// This function is auto-generated. +// +func DataPrivacyActionSearch(props ...*dataPrivacyActionProps) *dataPrivacyAction { + a := &dataPrivacyAction{ + timestamp: time.Now(), + resource: "system:data-privacy-request", + action: "search", + log: "searched for data privacy requests", + severity: actionlog.Info, + } + + if len(props) > 0 { + a.props = props[0] + } + + return a +} + +// DataPrivacyActionLookup returns "system:data-privacy-request.lookup" action +// +// This function is auto-generated. +// +func DataPrivacyActionLookup(props ...*dataPrivacyActionProps) *dataPrivacyAction { + a := &dataPrivacyAction{ + timestamp: time.Now(), + resource: "system:data-privacy-request", + action: "lookup", + log: "looked-up for a {{dataPrivacyRequest}}", + severity: actionlog.Info, + } + + if len(props) > 0 { + a.props = props[0] + } + + return a +} + +// DataPrivacyActionCreate returns "system:data-privacy-request.create" action +// +// This function is auto-generated. +// +func DataPrivacyActionCreate(props ...*dataPrivacyActionProps) *dataPrivacyAction { + a := &dataPrivacyAction{ + timestamp: time.Now(), + resource: "system:data-privacy-request", + action: "create", + log: "created {{dataPrivacyRequest}}", + severity: actionlog.Notice, + } + + if len(props) > 0 { + a.props = props[0] + } + + return a +} + +// DataPrivacyActionUpdate returns "system:data-privacy-request.update" action +// +// This function is auto-generated. +// +func DataPrivacyActionUpdate(props ...*dataPrivacyActionProps) *dataPrivacyAction { + a := &dataPrivacyAction{ + timestamp: time.Now(), + resource: "system:data-privacy-request", + action: "update", + log: "updated {{dataPrivacyRequest}}", + severity: actionlog.Notice, + } + + if len(props) > 0 { + a.props = props[0] + } + + return a +} + +// DataPrivacyActionApprove returns "system:data-privacy-request.approve" action +// +// This function is auto-generated. +// +func DataPrivacyActionApprove(props ...*dataPrivacyActionProps) *dataPrivacyAction { + a := &dataPrivacyAction{ + timestamp: time.Now(), + resource: "system:data-privacy-request", + action: "approve", + log: "approved {{dataPrivacyRequest}}", + severity: actionlog.Notice, + } + + if len(props) > 0 { + a.props = props[0] + } + + return a +} + +// ********************************************************************************************************************* +// ********************************************************************************************************************* +// Error constructors + +// DataPrivacyErrGeneric returns "system:data-privacy-request.generic" as *errors.Error +// +// +// This function is auto-generated. +// +func DataPrivacyErrGeneric(mm ...*dataPrivacyActionProps) *errors.Error { + var p = &dataPrivacyActionProps{} + if len(mm) > 0 { + p = mm[0] + } + + var e = errors.New( + errors.KindInternal, + + p.Format("failed to complete request due to internal error", nil), + + errors.Meta("type", "generic"), + errors.Meta("resource", "system:data-privacy-request"), + + // action log entry; no formatting, it will be applied inside recordAction fn. + errors.Meta(dataPrivacyLogMetaKey{}, "{err}"), + errors.Meta(dataPrivacyPropsMetaKey{}, p), + + // translation namespace & key + errors.Meta(locale.ErrorMetaNamespace{}, "system"), + errors.Meta(locale.ErrorMetaKey{}, "dataPrivacy.errors.generic"), + + errors.StackSkip(1), + ) + + if len(mm) > 0 { + } + + return e +} + +// DataPrivacyErrNotFound returns "system:data-privacy-request.notFound" as *errors.Error +// +// +// This function is auto-generated. +// +func DataPrivacyErrNotFound(mm ...*dataPrivacyActionProps) *errors.Error { + var p = &dataPrivacyActionProps{} + if len(mm) > 0 { + p = mm[0] + } + + var e = errors.New( + errors.KindInternal, + + p.Format("data privacy request not found", nil), + + errors.Meta("type", "notFound"), + errors.Meta("resource", "system:data-privacy-request"), + + errors.Meta(dataPrivacyPropsMetaKey{}, p), + + // translation namespace & key + errors.Meta(locale.ErrorMetaNamespace{}, "system"), + errors.Meta(locale.ErrorMetaKey{}, "dataPrivacy.errors.notFound"), + + errors.StackSkip(1), + ) + + if len(mm) > 0 { + } + + return e +} + +// DataPrivacyErrInvalidID returns "system:data-privacy-request.invalidID" as *errors.Error +// +// +// This function is auto-generated. +// +func DataPrivacyErrInvalidID(mm ...*dataPrivacyActionProps) *errors.Error { + var p = &dataPrivacyActionProps{} + if len(mm) > 0 { + p = mm[0] + } + + var e = errors.New( + errors.KindInternal, + + p.Format("invalid ID", nil), + + errors.Meta("type", "invalidID"), + errors.Meta("resource", "system:data-privacy-request"), + + errors.Meta(dataPrivacyPropsMetaKey{}, p), + + // translation namespace & key + errors.Meta(locale.ErrorMetaNamespace{}, "system"), + errors.Meta(locale.ErrorMetaKey{}, "dataPrivacy.errors.invalidID"), + + errors.StackSkip(1), + ) + + if len(mm) > 0 { + } + + return e +} + +// DataPrivacyErrInvalidKind returns "system:data-privacy-request.invalidKind" as *errors.Error +// +// +// This function is auto-generated. +// +func DataPrivacyErrInvalidKind(mm ...*dataPrivacyActionProps) *errors.Error { + var p = &dataPrivacyActionProps{} + if len(mm) > 0 { + p = mm[0] + } + + var e = errors.New( + errors.KindInternal, + + p.Format("invalid Kind", nil), + + errors.Meta("type", "invalidKind"), + errors.Meta("resource", "system:data-privacy-request"), + + errors.Meta(dataPrivacyPropsMetaKey{}, p), + + // translation namespace & key + errors.Meta(locale.ErrorMetaNamespace{}, "system"), + errors.Meta(locale.ErrorMetaKey{}, "dataPrivacy.errors.invalidKind"), + + errors.StackSkip(1), + ) + + if len(mm) > 0 { + } + + return e +} + +// DataPrivacyErrInvalidStatus returns "system:data-privacy-request.invalidStatus" as *errors.Error +// +// +// This function is auto-generated. +// +func DataPrivacyErrInvalidStatus(mm ...*dataPrivacyActionProps) *errors.Error { + var p = &dataPrivacyActionProps{} + if len(mm) > 0 { + p = mm[0] + } + + var e = errors.New( + errors.KindInternal, + + p.Format("invalid Status", nil), + + errors.Meta("type", "invalidStatus"), + errors.Meta("resource", "system:data-privacy-request"), + + errors.Meta(dataPrivacyPropsMetaKey{}, p), + + // translation namespace & key + errors.Meta(locale.ErrorMetaNamespace{}, "system"), + errors.Meta(locale.ErrorMetaKey{}, "dataPrivacy.errors.invalidStatus"), + + errors.StackSkip(1), + ) + + if len(mm) > 0 { + } + + return e +} + +// DataPrivacyErrNotAllowedToRead returns "system:data-privacy-request.notAllowedToRead" as *errors.Error +// +// +// This function is auto-generated. +// +func DataPrivacyErrNotAllowedToRead(mm ...*dataPrivacyActionProps) *errors.Error { + var p = &dataPrivacyActionProps{} + if len(mm) > 0 { + p = mm[0] + } + + var e = errors.New( + errors.KindInternal, + + p.Format("not allowed to read this data privacy request", nil), + + errors.Meta("type", "notAllowedToRead"), + errors.Meta("resource", "system:data-privacy-request"), + + // action log entry; no formatting, it will be applied inside recordAction fn. + errors.Meta(dataPrivacyLogMetaKey{}, "failed to read data privacy request; insufficient permissions"), + errors.Meta(dataPrivacyPropsMetaKey{}, p), + + // translation namespace & key + errors.Meta(locale.ErrorMetaNamespace{}, "system"), + errors.Meta(locale.ErrorMetaKey{}, "dataPrivacy.errors.notAllowedToRead"), + + errors.StackSkip(1), + ) + + if len(mm) > 0 { + } + + return e +} + +// DataPrivacyErrNotAllowedToSearch returns "system:data-privacy-request.notAllowedToSearch" as *errors.Error +// +// +// This function is auto-generated. +// +func DataPrivacyErrNotAllowedToSearch(mm ...*dataPrivacyActionProps) *errors.Error { + var p = &dataPrivacyActionProps{} + if len(mm) > 0 { + p = mm[0] + } + + var e = errors.New( + errors.KindInternal, + + p.Format("not allowed to search or list data privacy request", nil), + + errors.Meta("type", "notAllowedToSearch"), + errors.Meta("resource", "system:data-privacy-request"), + + // action log entry; no formatting, it will be applied inside recordAction fn. + errors.Meta(dataPrivacyLogMetaKey{}, "failed to search or list data privacy requests; insufficient permissions"), + errors.Meta(dataPrivacyPropsMetaKey{}, p), + + // translation namespace & key + errors.Meta(locale.ErrorMetaNamespace{}, "system"), + errors.Meta(locale.ErrorMetaKey{}, "dataPrivacy.errors.notAllowedToSearch"), + + errors.StackSkip(1), + ) + + if len(mm) > 0 { + } + + return e +} + +// DataPrivacyErrNotAllowedToCreate returns "system:data-privacy-request.notAllowedToCreate" as *errors.Error +// +// +// This function is auto-generated. +// +func DataPrivacyErrNotAllowedToCreate(mm ...*dataPrivacyActionProps) *errors.Error { + var p = &dataPrivacyActionProps{} + if len(mm) > 0 { + p = mm[0] + } + + var e = errors.New( + errors.KindInternal, + + p.Format("not allowed to create data privacy request", nil), + + errors.Meta("type", "notAllowedToCreate"), + errors.Meta("resource", "system:data-privacy-request"), + + // action log entry; no formatting, it will be applied inside recordAction fn. + errors.Meta(dataPrivacyLogMetaKey{}, "failed to create data privacy request; insufficient permissions"), + errors.Meta(dataPrivacyPropsMetaKey{}, p), + + // translation namespace & key + errors.Meta(locale.ErrorMetaNamespace{}, "system"), + errors.Meta(locale.ErrorMetaKey{}, "dataPrivacy.errors.notAllowedToCreate"), + + errors.StackSkip(1), + ) + + if len(mm) > 0 { + } + + return e +} + +// DataPrivacyErrNotAllowedToApprove returns "system:data-privacy-request.notAllowedToApprove" as *errors.Error +// +// +// This function is auto-generated. +// +func DataPrivacyErrNotAllowedToApprove(mm ...*dataPrivacyActionProps) *errors.Error { + var p = &dataPrivacyActionProps{} + if len(mm) > 0 { + p = mm[0] + } + + var e = errors.New( + errors.KindInternal, + + p.Format("not allowed to approve/reject data privacy request", nil), + + errors.Meta("type", "notAllowedToApprove"), + errors.Meta("resource", "system:data-privacy-request"), + + // action log entry; no formatting, it will be applied inside recordAction fn. + errors.Meta(dataPrivacyLogMetaKey{}, "failed to approve/reject data privacy request; insufficient permissions"), + errors.Meta(dataPrivacyPropsMetaKey{}, p), + + // translation namespace & key + errors.Meta(locale.ErrorMetaNamespace{}, "system"), + errors.Meta(locale.ErrorMetaKey{}, "dataPrivacy.errors.notAllowedToApprove"), + + errors.StackSkip(1), + ) + + if len(mm) > 0 { + } + + return e +} + +// ********************************************************************************************************************* +// ********************************************************************************************************************* + +// recordAction is a service helper function wraps function that can return error +// +// It will wrap unrecognized/internal errors with generic errors. +// +// This function is auto-generated. +// +func (svc dataPrivacy) recordAction(ctx context.Context, props *dataPrivacyActionProps, actionFn func(...*dataPrivacyActionProps) *dataPrivacyAction, err error) error { + if svc.actionlog == nil || actionFn == nil { + // action log disabled or no action fn passed, return error as-is + return err + } else if err == nil { + // action completed w/o error, record it + svc.actionlog.Record(ctx, actionFn(props).ToAction()) + return nil + } + + a := actionFn(props).ToAction() + + // Extracting error information and recording it as action + a.Error = err.Error() + + switch c := err.(type) { + case *errors.Error: + m := c.Meta() + + a.Error = err.Error() + a.Severity = actionlog.Severity(m.AsInt("severity")) + a.Description = props.Format(m.AsString(dataPrivacyLogMetaKey{}), err) + + if p, has := m[dataPrivacyPropsMetaKey{}]; has { + a.Meta = p.(*dataPrivacyActionProps).Serialize() + } + + svc.actionlog.Record(ctx, a) + default: + svc.actionlog.Record(ctx, a) + } + + // Original error is passed on + return err +} diff --git a/system/service/data_privacy_request_actions.yaml b/system/service/data_privacy_request_actions.yaml new file mode 100644 index 000000000..e7693ce9b --- /dev/null +++ b/system/service/data_privacy_request_actions.yaml @@ -0,0 +1,79 @@ +# List of loggable service actions + +resource: system:data-privacy-request +service: dataPrivacy + +# Default sensitivity for actions +defaultActionSeverity: notice + +# default severity for errors +defaultErrorSeverity: alert + +import: + - github.com/cortezaproject/corteza-server/system/types + +props: + - name: dataPrivacyRequest + type: "*types.DataPrivacyRequest" + fields: [ kind, ID ] + - name: new + type: "*types.DataPrivacyRequest" + fields: [ kind, ID ] + - name: update + type: "*types.DataPrivacyRequest" + fields: [ kind, ID ] + - name: filter + type: "*types.DataPrivacyRequestFilter" + fields: [ kind, sort ] + +actions: + - action: search + log: "searched for data privacy requests" + severity: info + + - action: lookup + log: "looked-up for a {{dataPrivacyRequest}}" + severity: info + + - action: create + log: "created {{dataPrivacyRequest}}" + + - action: update + log: "updated {{dataPrivacyRequest}}" + + - action: approve + log: "approved {{dataPrivacyRequest}}" + + +errors: + - error: notFound + message: "data privacy request not found" + severity: warning + + - error: invalidID + message: "invalid ID" + severity: warning + + - error: invalidKind + message: "invalid Kind" + severity: warning + + - error: invalidStatus + message: "invalid Status" + severity: warning + + - error: notAllowedToRead + message: "not allowed to read this data privacy request" + log: "failed to read data privacy request; insufficient permissions" + + - error: notAllowedToSearch + message: "not allowed to search or list data privacy request" + log: "failed to search or list data privacy requests; insufficient permissions" + + - error: notAllowedToCreate + message: "not allowed to create data privacy request" + log: "failed to create data privacy request; insufficient permissions" + + - error: notAllowedToApprove + message: "not allowed to approve/reject data privacy request" + log: "failed to approve/reject data privacy request; insufficient permissions" diff --git a/system/service/event/data_privacy_request.go b/system/service/event/data_privacy_request.go new file mode 100644 index 000000000..6890f7e82 --- /dev/null +++ b/system/service/event/data_privacy_request.go @@ -0,0 +1,19 @@ +package event + +import ( + "github.com/cortezaproject/corteza-server/pkg/eventbus" +) + +var _ = eventbus.ConstraintMaker + +// Match returns false if given conditions do not match event & resource internals +func (res dataPrivacyRequestBase) Match(c eventbus.ConstraintMatcher) bool { + // By default we match no mather what kind of constraints we receive + // + // Function will be called multiple times - once for every trigger constraint + // All should match (return true): + // constraint#1 AND constraint#2 AND constraint#3 ... + // + // When there are multiple values, Match() can decide how to treat them (OR, AND...) + return true +} diff --git a/system/service/event/events.gen.go b/system/service/event/events.gen.go index 9540bc446..0e8eef307 100644 --- a/system/service/event/events.gen.go +++ b/system/service/event/events.gen.go @@ -206,6 +206,65 @@ type ( *authClientBase } + // dataPrivacyRequestBase + // + // This type is auto-generated. + dataPrivacyRequestBase struct { + immutable bool + dataPrivacyRequest *types.DataPrivacyRequest + oldDataPrivacyRequest *types.DataPrivacyRequest + invoker auth.Identifiable + } + + // dataPrivacyRequestOnManual + // + // This type is auto-generated. + dataPrivacyRequestOnManual struct { + *dataPrivacyRequestBase + } + + // dataPrivacyRequestBeforeCreate + // + // This type is auto-generated. + dataPrivacyRequestBeforeCreate struct { + *dataPrivacyRequestBase + } + + // dataPrivacyRequestBeforeUpdate + // + // This type is auto-generated. + dataPrivacyRequestBeforeUpdate struct { + *dataPrivacyRequestBase + } + + // dataPrivacyRequestBeforeDelete + // + // This type is auto-generated. + dataPrivacyRequestBeforeDelete struct { + *dataPrivacyRequestBase + } + + // dataPrivacyRequestAfterCreate + // + // This type is auto-generated. + dataPrivacyRequestAfterCreate struct { + *dataPrivacyRequestBase + } + + // dataPrivacyRequestAfterUpdate + // + // This type is auto-generated. + dataPrivacyRequestAfterUpdate struct { + *dataPrivacyRequestBase + } + + // dataPrivacyRequestAfterDelete + // + // This type is auto-generated. + dataPrivacyRequestAfterDelete struct { + *dataPrivacyRequestBase + } + // mailBase // // This type is auto-generated. @@ -1733,6 +1792,415 @@ func (res *authClientBase) DecodeVars(vars *expr.Vars) (err error) { return } +// ResourceType returns "system:data-privacy-request" +// +// This function is auto-generated. +func (dataPrivacyRequestBase) ResourceType() string { + return "system:data-privacy-request" +} + +// EventType on dataPrivacyRequestOnManual returns "onManual" +// +// This function is auto-generated. +func (dataPrivacyRequestOnManual) EventType() string { + return "onManual" +} + +// EventType on dataPrivacyRequestBeforeCreate returns "beforeCreate" +// +// This function is auto-generated. +func (dataPrivacyRequestBeforeCreate) EventType() string { + return "beforeCreate" +} + +// EventType on dataPrivacyRequestBeforeUpdate returns "beforeUpdate" +// +// This function is auto-generated. +func (dataPrivacyRequestBeforeUpdate) EventType() string { + return "beforeUpdate" +} + +// EventType on dataPrivacyRequestBeforeDelete returns "beforeDelete" +// +// This function is auto-generated. +func (dataPrivacyRequestBeforeDelete) EventType() string { + return "beforeDelete" +} + +// EventType on dataPrivacyRequestAfterCreate returns "afterCreate" +// +// This function is auto-generated. +func (dataPrivacyRequestAfterCreate) EventType() string { + return "afterCreate" +} + +// EventType on dataPrivacyRequestAfterUpdate returns "afterUpdate" +// +// This function is auto-generated. +func (dataPrivacyRequestAfterUpdate) EventType() string { + return "afterUpdate" +} + +// EventType on dataPrivacyRequestAfterDelete returns "afterDelete" +// +// This function is auto-generated. +func (dataPrivacyRequestAfterDelete) EventType() string { + return "afterDelete" +} + +// DataPrivacyRequestOnManual creates onManual for system:data-privacy-request resource +// +// This function is auto-generated. +func DataPrivacyRequestOnManual( + argDataPrivacyRequest *types.DataPrivacyRequest, + argOldDataPrivacyRequest *types.DataPrivacyRequest, +) *dataPrivacyRequestOnManual { + return &dataPrivacyRequestOnManual{ + dataPrivacyRequestBase: &dataPrivacyRequestBase{ + immutable: false, + dataPrivacyRequest: argDataPrivacyRequest, + oldDataPrivacyRequest: argOldDataPrivacyRequest, + }, + } +} + +// DataPrivacyRequestOnManualImmutable creates onManual for system:data-privacy-request resource +// +// None of the arguments will be mutable! +// +// This function is auto-generated. +func DataPrivacyRequestOnManualImmutable( + argDataPrivacyRequest *types.DataPrivacyRequest, + argOldDataPrivacyRequest *types.DataPrivacyRequest, +) *dataPrivacyRequestOnManual { + return &dataPrivacyRequestOnManual{ + dataPrivacyRequestBase: &dataPrivacyRequestBase{ + immutable: true, + dataPrivacyRequest: argDataPrivacyRequest, + oldDataPrivacyRequest: argOldDataPrivacyRequest, + }, + } +} + +// DataPrivacyRequestBeforeCreate creates beforeCreate for system:data-privacy-request resource +// +// This function is auto-generated. +func DataPrivacyRequestBeforeCreate( + argDataPrivacyRequest *types.DataPrivacyRequest, + argOldDataPrivacyRequest *types.DataPrivacyRequest, +) *dataPrivacyRequestBeforeCreate { + return &dataPrivacyRequestBeforeCreate{ + dataPrivacyRequestBase: &dataPrivacyRequestBase{ + immutable: false, + dataPrivacyRequest: argDataPrivacyRequest, + oldDataPrivacyRequest: argOldDataPrivacyRequest, + }, + } +} + +// DataPrivacyRequestBeforeCreateImmutable creates beforeCreate for system:data-privacy-request resource +// +// None of the arguments will be mutable! +// +// This function is auto-generated. +func DataPrivacyRequestBeforeCreateImmutable( + argDataPrivacyRequest *types.DataPrivacyRequest, + argOldDataPrivacyRequest *types.DataPrivacyRequest, +) *dataPrivacyRequestBeforeCreate { + return &dataPrivacyRequestBeforeCreate{ + dataPrivacyRequestBase: &dataPrivacyRequestBase{ + immutable: true, + dataPrivacyRequest: argDataPrivacyRequest, + oldDataPrivacyRequest: argOldDataPrivacyRequest, + }, + } +} + +// DataPrivacyRequestBeforeUpdate creates beforeUpdate for system:data-privacy-request resource +// +// This function is auto-generated. +func DataPrivacyRequestBeforeUpdate( + argDataPrivacyRequest *types.DataPrivacyRequest, + argOldDataPrivacyRequest *types.DataPrivacyRequest, +) *dataPrivacyRequestBeforeUpdate { + return &dataPrivacyRequestBeforeUpdate{ + dataPrivacyRequestBase: &dataPrivacyRequestBase{ + immutable: false, + dataPrivacyRequest: argDataPrivacyRequest, + oldDataPrivacyRequest: argOldDataPrivacyRequest, + }, + } +} + +// DataPrivacyRequestBeforeUpdateImmutable creates beforeUpdate for system:data-privacy-request resource +// +// None of the arguments will be mutable! +// +// This function is auto-generated. +func DataPrivacyRequestBeforeUpdateImmutable( + argDataPrivacyRequest *types.DataPrivacyRequest, + argOldDataPrivacyRequest *types.DataPrivacyRequest, +) *dataPrivacyRequestBeforeUpdate { + return &dataPrivacyRequestBeforeUpdate{ + dataPrivacyRequestBase: &dataPrivacyRequestBase{ + immutable: true, + dataPrivacyRequest: argDataPrivacyRequest, + oldDataPrivacyRequest: argOldDataPrivacyRequest, + }, + } +} + +// DataPrivacyRequestBeforeDelete creates beforeDelete for system:data-privacy-request resource +// +// This function is auto-generated. +func DataPrivacyRequestBeforeDelete( + argDataPrivacyRequest *types.DataPrivacyRequest, + argOldDataPrivacyRequest *types.DataPrivacyRequest, +) *dataPrivacyRequestBeforeDelete { + return &dataPrivacyRequestBeforeDelete{ + dataPrivacyRequestBase: &dataPrivacyRequestBase{ + immutable: false, + dataPrivacyRequest: argDataPrivacyRequest, + oldDataPrivacyRequest: argOldDataPrivacyRequest, + }, + } +} + +// DataPrivacyRequestBeforeDeleteImmutable creates beforeDelete for system:data-privacy-request resource +// +// None of the arguments will be mutable! +// +// This function is auto-generated. +func DataPrivacyRequestBeforeDeleteImmutable( + argDataPrivacyRequest *types.DataPrivacyRequest, + argOldDataPrivacyRequest *types.DataPrivacyRequest, +) *dataPrivacyRequestBeforeDelete { + return &dataPrivacyRequestBeforeDelete{ + dataPrivacyRequestBase: &dataPrivacyRequestBase{ + immutable: true, + dataPrivacyRequest: argDataPrivacyRequest, + oldDataPrivacyRequest: argOldDataPrivacyRequest, + }, + } +} + +// DataPrivacyRequestAfterCreate creates afterCreate for system:data-privacy-request resource +// +// This function is auto-generated. +func DataPrivacyRequestAfterCreate( + argDataPrivacyRequest *types.DataPrivacyRequest, + argOldDataPrivacyRequest *types.DataPrivacyRequest, +) *dataPrivacyRequestAfterCreate { + return &dataPrivacyRequestAfterCreate{ + dataPrivacyRequestBase: &dataPrivacyRequestBase{ + immutable: false, + dataPrivacyRequest: argDataPrivacyRequest, + oldDataPrivacyRequest: argOldDataPrivacyRequest, + }, + } +} + +// DataPrivacyRequestAfterCreateImmutable creates afterCreate for system:data-privacy-request resource +// +// None of the arguments will be mutable! +// +// This function is auto-generated. +func DataPrivacyRequestAfterCreateImmutable( + argDataPrivacyRequest *types.DataPrivacyRequest, + argOldDataPrivacyRequest *types.DataPrivacyRequest, +) *dataPrivacyRequestAfterCreate { + return &dataPrivacyRequestAfterCreate{ + dataPrivacyRequestBase: &dataPrivacyRequestBase{ + immutable: true, + dataPrivacyRequest: argDataPrivacyRequest, + oldDataPrivacyRequest: argOldDataPrivacyRequest, + }, + } +} + +// DataPrivacyRequestAfterUpdate creates afterUpdate for system:data-privacy-request resource +// +// This function is auto-generated. +func DataPrivacyRequestAfterUpdate( + argDataPrivacyRequest *types.DataPrivacyRequest, + argOldDataPrivacyRequest *types.DataPrivacyRequest, +) *dataPrivacyRequestAfterUpdate { + return &dataPrivacyRequestAfterUpdate{ + dataPrivacyRequestBase: &dataPrivacyRequestBase{ + immutable: false, + dataPrivacyRequest: argDataPrivacyRequest, + oldDataPrivacyRequest: argOldDataPrivacyRequest, + }, + } +} + +// DataPrivacyRequestAfterUpdateImmutable creates afterUpdate for system:data-privacy-request resource +// +// None of the arguments will be mutable! +// +// This function is auto-generated. +func DataPrivacyRequestAfterUpdateImmutable( + argDataPrivacyRequest *types.DataPrivacyRequest, + argOldDataPrivacyRequest *types.DataPrivacyRequest, +) *dataPrivacyRequestAfterUpdate { + return &dataPrivacyRequestAfterUpdate{ + dataPrivacyRequestBase: &dataPrivacyRequestBase{ + immutable: true, + dataPrivacyRequest: argDataPrivacyRequest, + oldDataPrivacyRequest: argOldDataPrivacyRequest, + }, + } +} + +// DataPrivacyRequestAfterDelete creates afterDelete for system:data-privacy-request resource +// +// This function is auto-generated. +func DataPrivacyRequestAfterDelete( + argDataPrivacyRequest *types.DataPrivacyRequest, + argOldDataPrivacyRequest *types.DataPrivacyRequest, +) *dataPrivacyRequestAfterDelete { + return &dataPrivacyRequestAfterDelete{ + dataPrivacyRequestBase: &dataPrivacyRequestBase{ + immutable: false, + dataPrivacyRequest: argDataPrivacyRequest, + oldDataPrivacyRequest: argOldDataPrivacyRequest, + }, + } +} + +// DataPrivacyRequestAfterDeleteImmutable creates afterDelete for system:data-privacy-request resource +// +// None of the arguments will be mutable! +// +// This function is auto-generated. +func DataPrivacyRequestAfterDeleteImmutable( + argDataPrivacyRequest *types.DataPrivacyRequest, + argOldDataPrivacyRequest *types.DataPrivacyRequest, +) *dataPrivacyRequestAfterDelete { + return &dataPrivacyRequestAfterDelete{ + dataPrivacyRequestBase: &dataPrivacyRequestBase{ + immutable: true, + dataPrivacyRequest: argDataPrivacyRequest, + oldDataPrivacyRequest: argOldDataPrivacyRequest, + }, + } +} + +// SetDataPrivacyRequest sets new dataPrivacyRequest value +// +// This function is auto-generated. +func (res *dataPrivacyRequestBase) SetDataPrivacyRequest(argDataPrivacyRequest *types.DataPrivacyRequest) { + res.dataPrivacyRequest = argDataPrivacyRequest +} + +// DataPrivacyRequest returns dataPrivacyRequest +// +// This function is auto-generated. +func (res dataPrivacyRequestBase) DataPrivacyRequest() *types.DataPrivacyRequest { + return res.dataPrivacyRequest +} + +// OldDataPrivacyRequest returns oldDataPrivacyRequest +// +// This function is auto-generated. +func (res dataPrivacyRequestBase) OldDataPrivacyRequest() *types.DataPrivacyRequest { + return res.oldDataPrivacyRequest +} + +// SetInvoker sets new invoker value +// +// This function is auto-generated. +func (res *dataPrivacyRequestBase) SetInvoker(argInvoker auth.Identifiable) { + res.invoker = argInvoker +} + +// Invoker returns invoker +// +// This function is auto-generated. +func (res dataPrivacyRequestBase) Invoker() auth.Identifiable { + return res.invoker +} + +// Encode internal data to be passed as event params & arguments to triggered Corredor script +func (res dataPrivacyRequestBase) Encode() (args map[string][]byte, err error) { + args = make(map[string][]byte) + + if args["dataPrivacyRequest"], err = json.Marshal(res.dataPrivacyRequest); err != nil { + return nil, err + } + + if args["oldDataPrivacyRequest"], err = json.Marshal(res.oldDataPrivacyRequest); err != nil { + return nil, err + } + + if args["invoker"], err = json.Marshal(res.invoker); err != nil { + return nil, err + } + + return +} + +// Encode internal data to be passed as event params & arguments to workflow +func (res dataPrivacyRequestBase) EncodeVars() (out *expr.Vars, err error) { + out = &expr.Vars{} + var v expr.TypedValue + + // Could not found expression-type counterpart for *types.DataPrivacyRequest + + // Could not found expression-type counterpart for *types.DataPrivacyRequest + + // Could not found expression-type counterpart for auth.Identifiable + + _ = v + return +} + +// Decode return values from Corredor script into struct props +func (res *dataPrivacyRequestBase) Decode(results map[string][]byte) (err error) { + if res.immutable { + // Respect immutability + return + } + if res.dataPrivacyRequest != nil { + if r, ok := results["result"]; ok && len(results) == 1 { + if err = json.Unmarshal(r, res.dataPrivacyRequest); err != nil { + return + } + } + } + + if res.dataPrivacyRequest != nil { + if r, ok := results["dataPrivacyRequest"]; ok { + if err = json.Unmarshal(r, res.dataPrivacyRequest); err != nil { + return + } + } + } + + // Do not decode oldDataPrivacyRequest; marked as immutable + + if res.invoker != nil { + if r, ok := results["invoker"]; ok { + if err = json.Unmarshal(r, res.invoker); err != nil { + return + } + } + } + return +} + +func (res *dataPrivacyRequestBase) DecodeVars(vars *expr.Vars) (err error) { + if res.immutable { + // Respect immutability + return + } + // Could not find expression-type counterpart for *types.DataPrivacyRequest + // oldDataPrivacyRequest marked as immutable + // Could not find expression-type counterpart for auth.Identifiable + + return +} + // ResourceType returns "system:mail" // // This function is auto-generated. diff --git a/system/service/event/events.yaml b/system/service/event/events.yaml index 5df61e15b..e2bab27d7 100644 --- a/system/service/event/events.yaml +++ b/system/service/event/events.yaml @@ -118,3 +118,15 @@ system:queue: type: '*types.QueueMessage' constraints: - name: payload.queue + +system:data-privacy-request: + on: ['manual'] + ba: ['create', 'update', 'delete'] + props: + - name: 'dataPrivacyRequest' + type: '*types.DataPrivacyRequest' + - name: 'oldDataPrivacyRequest' + type: '*types.DataPrivacyRequest' + immutable: true + constraints: + - name: role.name diff --git a/system/service/service.go b/system/service/service.go index aadbfe6b4..174d75123 100644 --- a/system/service/service.go +++ b/system/service/service.go @@ -89,6 +89,7 @@ var ( DefaultApigwProfiler *apigwProfiler DefaultReport *report primaryConnectionConfig *types.DalConnection + DefaultDataPrivacy *dataPrivacy DefaultStatistics *statistics @@ -213,6 +214,7 @@ func Initialize(ctx context.Context, log *zap.Logger, s store.Storer, primaryCon DefaultApigwRoute = Route() DefaultApigwProfiler = Profiler() DefaultApigwFilter = Filter() + DefaultDataPrivacy = DataPrivacy(DefaultStore, DefaultAccessControl, DefaultActionlog, eventbus.Service()) if err = initRoles(ctx, log.Named("rbac.roles"), c.RBAC, eventbus.Service(), rbac.Global()); err != nil { return err diff --git a/system/types/data_privacy.go b/system/types/data_privacy.go new file mode 100644 index 000000000..4dd5a7317 --- /dev/null +++ b/system/types/data_privacy.go @@ -0,0 +1,132 @@ +package types + +import ( + "time" + + "github.com/cortezaproject/corteza-server/pkg/filter" +) + +type ( + DataPrivacyRequest struct { + ID uint64 `json:"requestID,string"` + + Kind RequestKind `json:"kind"` + Status RequestStatus `json:"status"` + + RequestedAt time.Time `json:"requestedAt,omitempty"` + RequestedBy uint64 `json:"requestedBy,string"` + CompletedAt *time.Time `json:"completedAt,omitempty"` + CompletedBy uint64 `json:"completedBy,string,omitempty" ` + + CreatedAt time.Time `json:"createdAt,omitempty"` + CreatedBy uint64 `json:"createdBy,string" ` + UpdatedAt *time.Time `json:"updatedAt,omitempty"` + UpdatedBy uint64 `json:"updatedBy,string,omitempty"` + DeletedAt *time.Time `json:"deletedAt,omitempty"` + DeletedBy uint64 `json:"deletedBy,string,omitempty"` + } + + DataPrivacyRequestFilter struct { + RequestID []uint64 `json:"requestID"` + RequestedBy []uint64 `json:"requestedBy"` + + Query string `json:"query"` + + Kind []string `json:"kind"` + Status []string `json:"status"` + + // Check fn is called by store backend for each resource found function can + // modify the resource and return false if store should not return it + // + // Store then loads additional resources to satisfy the paging parameters + Check func(request *DataPrivacyRequest) (bool, error) `json:"-"` + + // Standard helpers for paging and sorting + filter.Sorting + filter.Paging + } + + DataPrivacyRequestComment struct { + ID uint64 `json:"commentID,string"` + RequestID uint64 `json:"requestID,string"` + Comment string `json:"comment"` + + CreatedAt time.Time `json:"createdAt,omitempty"` + CreatedBy uint64 `json:"createdBy,string" ` + UpdatedAt *time.Time `json:"updatedAt,omitempty"` + UpdatedBy uint64 `json:"updatedBy,string,omitempty"` + DeletedAt *time.Time `json:"deletedAt,omitempty"` + DeletedBy uint64 `json:"deletedBy,string,omitempty"` + } + + DataPrivacyRequestCommentFilter struct { + RequestID []uint64 `json:"requestID"` + + // Check fn is called by store backend for each resource found function can + // modify the resource and return false if store should not return it + // + // Store then loads additional resources to satisfy the paging parameters + Check func(request *DataPrivacyRequestComment) (bool, error) `json:"-"` + + // Standard helpers for paging and sorting + filter.Sorting + filter.Paging + } + + RequestStatus string + RequestKind string +) + +const ( + // RequestKindCorrect to correct module fields + RequestKindCorrect RequestKind = "correct" + // RequestKindDelete to delete module fields + RequestKindDelete RequestKind = "delete" + // RequestKindExport to export module fields + RequestKindExport RequestKind = "export" + + // RequestStatusPending initially request will be in pending status + RequestStatusPending RequestStatus = "pending" + // RequestStatusCanceled owner of request has cancelled the request + RequestStatusCanceled RequestStatus = "canceled" + // RequestStatusApproved data officer has of request has cancelled the request + RequestStatusApproved RequestStatus = "approved" + // RequestStatusRejected data officer has denied the request + RequestStatusRejected RequestStatus = "rejected" +) + +func CastToRequestKind(s string) RequestKind { + switch s { + case "correct": + return RequestKindCorrect + case "delete": + return RequestKindCorrect + case "export": + return RequestKindCorrect + default: + return "" + } +} + +func (k RequestKind) String() string { + return string(k) +} + +func CastToRequestStatus(s string) RequestStatus { + switch s { + case "pending": + return RequestStatusPending + case "canceled": + return RequestStatusCanceled + case "approved": + return RequestStatusApproved + case "rejected": + return RequestStatusRejected + default: + return "" + } +} + +func (s RequestStatus) String() string { + return string(s) +} diff --git a/system/types/rbac.gen.go b/system/types/rbac.gen.go index ba41ec4af..14d1b0ce4 100644 --- a/system/types/rbac.gen.go +++ b/system/types/rbac.gen.go @@ -24,18 +24,20 @@ var ( ) const ( - ApplicationResourceType = "corteza::system:application" - ApigwRouteResourceType = "corteza::system:apigw-route" - AuthClientResourceType = "corteza::system:auth-client" - QueueResourceType = "corteza::system:queue" - QueueMessageResourceType = "corteza::system:queue_message" - ReportResourceType = "corteza::system:report" - RoleResourceType = "corteza::system:role" - TemplateResourceType = "corteza::system:template" - UserResourceType = "corteza::system:user" - DalConnectionResourceType = "corteza::system:dal_connection" - DalSensitivityLevelResourceType = "corteza::system:dal_sensitivity_level" - ComponentResourceType = "corteza::system" + ApplicationResourceType = "corteza::system:application" + ApigwRouteResourceType = "corteza::system:apigw-route" + AuthClientResourceType = "corteza::system:auth-client" + DataPrivacyRequestResourceType = "corteza::system:data-privacy-request" + DataPrivacyRequestCommentResourceType = "corteza::system:data-privacy-request_comment" + QueueResourceType = "corteza::system:queue" + QueueMessageResourceType = "corteza::system:queue_message" + ReportResourceType = "corteza::system:report" + RoleResourceType = "corteza::system:role" + TemplateResourceType = "corteza::system:template" + UserResourceType = "corteza::system:user" + DalConnectionResourceType = "corteza::system:dal_connection" + DalSensitivityLevelResourceType = "corteza::system:dal_sensitivity_level" + ComponentResourceType = "corteza::system" ) // RbacResource returns string representation of RBAC resource for Application by calling ApplicationRbacResource fn @@ -128,6 +130,66 @@ func AuthClientRbacResourceTpl() string { return "%s/%s" } +// RbacResource returns string representation of RBAC resource for DataPrivacyRequest by calling DataPrivacyRequestRbacResource fn +// +// RBAC resource is in the corteza::system:data-privacy-request/... format +// +// This function is auto-generated +func (r DataPrivacyRequest) RbacResource() string { + return DataPrivacyRequestRbacResource(r.ID) +} + +// DataPrivacyRequestRbacResource returns string representation of RBAC resource for DataPrivacyRequest +// +// RBAC resource is in the corteza::system:data-privacy-request/... format +// +// This function is auto-generated +func DataPrivacyRequestRbacResource(id uint64) string { + cpts := []interface{}{DataPrivacyRequestResourceType} + if id != 0 { + cpts = append(cpts, strconv.FormatUint(id, 10)) + } else { + cpts = append(cpts, "*") + } + + return fmt.Sprintf(DataPrivacyRequestRbacResourceTpl(), cpts...) + +} + +func DataPrivacyRequestRbacResourceTpl() string { + return "%s/%s" +} + +// RbacResource returns string representation of RBAC resource for DataPrivacyRequestComment by calling DataPrivacyRequestCommentRbacResource fn +// +// RBAC resource is in the corteza::system:data-privacy-request_comment/... format +// +// This function is auto-generated +func (r DataPrivacyRequestComment) RbacResource() string { + return DataPrivacyRequestCommentRbacResource(r.ID) +} + +// DataPrivacyRequestCommentRbacResource returns string representation of RBAC resource for DataPrivacyRequestComment +// +// RBAC resource is in the corteza::system:data-privacy-request_comment/... format +// +// This function is auto-generated +func DataPrivacyRequestCommentRbacResource(id uint64) string { + cpts := []interface{}{DataPrivacyRequestCommentResourceType} + if id != 0 { + cpts = append(cpts, strconv.FormatUint(id, 10)) + } else { + cpts = append(cpts, "*") + } + + return fmt.Sprintf(DataPrivacyRequestCommentRbacResourceTpl(), cpts...) + +} + +func DataPrivacyRequestCommentRbacResourceTpl() string { + return "%s/%s" +} + // RbacResource returns string representation of RBAC resource for Queue by calling QueueRbacResource fn // // RBAC resource is in the corteza::system:queue/... format diff --git a/system/types/type_set.gen.go b/system/types/type_set.gen.go index 9d50c4ce3..9c443d877 100644 --- a/system/types/type_set.gen.go +++ b/system/types/type_set.gen.go @@ -75,6 +75,16 @@ type ( // This type is auto-generated. DalSensitivityLevelSet []*DalSensitivityLevel + // DataPrivacyRequestSet slice of DataPrivacyRequest + // + // This type is auto-generated. + DataPrivacyRequestSet []*DataPrivacyRequest + + // DataPrivacyRequestCommentSet slice of DataPrivacyRequestComment + // + // This type is auto-generated. + DataPrivacyRequestCommentSet []*DataPrivacyRequestComment + // QueueSet slice of Queue // // This type is auto-generated. @@ -750,6 +760,118 @@ func (set DalSensitivityLevelSet) IDs() (IDs []uint64) { return } +// Walk iterates through every slice item and calls w(DataPrivacyRequest) err +// +// This function is auto-generated. +func (set DataPrivacyRequestSet) Walk(w func(*DataPrivacyRequest) error) (err error) { + for i := range set { + if err = w(set[i]); err != nil { + return + } + } + + return +} + +// Filter iterates through every slice item, calls f(DataPrivacyRequest) (bool, err) and return filtered slice +// +// This function is auto-generated. +func (set DataPrivacyRequestSet) Filter(f func(*DataPrivacyRequest) (bool, error)) (out DataPrivacyRequestSet, err error) { + var ok bool + out = DataPrivacyRequestSet{} + for i := range set { + if ok, err = f(set[i]); err != nil { + return + } else if ok { + out = append(out, set[i]) + } + } + + return +} + +// FindByID finds items from slice by its ID property +// +// This function is auto-generated. +func (set DataPrivacyRequestSet) FindByID(ID uint64) *DataPrivacyRequest { + for i := range set { + if set[i].ID == ID { + return set[i] + } + } + + return nil +} + +// IDs returns a slice of uint64s from all items in the set +// +// This function is auto-generated. +func (set DataPrivacyRequestSet) IDs() (IDs []uint64) { + IDs = make([]uint64, len(set)) + + for i := range set { + IDs[i] = set[i].ID + } + + return +} + +// Walk iterates through every slice item and calls w(DataPrivacyRequestComment) err +// +// This function is auto-generated. +func (set DataPrivacyRequestCommentSet) Walk(w func(*DataPrivacyRequestComment) error) (err error) { + for i := range set { + if err = w(set[i]); err != nil { + return + } + } + + return +} + +// Filter iterates through every slice item, calls f(DataPrivacyRequestComment) (bool, err) and return filtered slice +// +// This function is auto-generated. +func (set DataPrivacyRequestCommentSet) Filter(f func(*DataPrivacyRequestComment) (bool, error)) (out DataPrivacyRequestCommentSet, err error) { + var ok bool + out = DataPrivacyRequestCommentSet{} + for i := range set { + if ok, err = f(set[i]); err != nil { + return + } else if ok { + out = append(out, set[i]) + } + } + + return +} + +// FindByID finds items from slice by its ID property +// +// This function is auto-generated. +func (set DataPrivacyRequestCommentSet) FindByID(ID uint64) *DataPrivacyRequestComment { + for i := range set { + if set[i].ID == ID { + return set[i] + } + } + + return nil +} + +// IDs returns a slice of uint64s from all items in the set +// +// This function is auto-generated. +func (set DataPrivacyRequestCommentSet) IDs() (IDs []uint64) { + IDs = make([]uint64, len(set)) + + for i := range set { + IDs[i] = set[i].ID + } + + return +} + // Walk iterates through every slice item and calls w(Queue) err // // This function is auto-generated. diff --git a/system/types/type_set.gen_test.go b/system/types/type_set.gen_test.go index f87568185..6bcfc17d5 100644 --- a/system/types/type_set.gen_test.go +++ b/system/types/type_set.gen_test.go @@ -1048,6 +1048,186 @@ func TestDalSensitivityLevelSetIDs(t *testing.T) { } } +func TestDataPrivacyRequestSetWalk(t *testing.T) { + var ( + value = make(DataPrivacyRequestSet, 3) + req = require.New(t) + ) + + // check walk with no errors + { + err := value.Walk(func(*DataPrivacyRequest) error { + return nil + }) + req.NoError(err) + } + + // check walk with error + req.Error(value.Walk(func(*DataPrivacyRequest) error { return fmt.Errorf("walk error") })) +} + +func TestDataPrivacyRequestSetFilter(t *testing.T) { + var ( + value = make(DataPrivacyRequestSet, 3) + req = require.New(t) + ) + + // filter nothing + { + set, err := value.Filter(func(*DataPrivacyRequest) (bool, error) { + return true, nil + }) + req.NoError(err) + req.Equal(len(set), len(value)) + } + + // filter one item + { + found := false + set, err := value.Filter(func(*DataPrivacyRequest) (bool, error) { + if !found { + found = true + return found, nil + } + return false, nil + }) + req.NoError(err) + req.Len(set, 1) + } + + // filter error + { + _, err := value.Filter(func(*DataPrivacyRequest) (bool, error) { + return false, fmt.Errorf("filter error") + }) + req.Error(err) + } +} + +func TestDataPrivacyRequestSetIDs(t *testing.T) { + var ( + value = make(DataPrivacyRequestSet, 3) + req = require.New(t) + ) + + // construct objects + value[0] = new(DataPrivacyRequest) + value[1] = new(DataPrivacyRequest) + value[2] = new(DataPrivacyRequest) + // set ids + value[0].ID = 1 + value[1].ID = 2 + value[2].ID = 3 + + // Find existing + { + val := value.FindByID(2) + req.Equal(uint64(2), val.ID) + } + + // Find non-existing + { + val := value.FindByID(4) + req.Nil(val) + } + + // List IDs from set + { + val := value.IDs() + req.Equal(len(val), len(value)) + } +} + +func TestDataPrivacyRequestCommentSetWalk(t *testing.T) { + var ( + value = make(DataPrivacyRequestCommentSet, 3) + req = require.New(t) + ) + + // check walk with no errors + { + err := value.Walk(func(*DataPrivacyRequestComment) error { + return nil + }) + req.NoError(err) + } + + // check walk with error + req.Error(value.Walk(func(*DataPrivacyRequestComment) error { return fmt.Errorf("walk error") })) +} + +func TestDataPrivacyRequestCommentSetFilter(t *testing.T) { + var ( + value = make(DataPrivacyRequestCommentSet, 3) + req = require.New(t) + ) + + // filter nothing + { + set, err := value.Filter(func(*DataPrivacyRequestComment) (bool, error) { + return true, nil + }) + req.NoError(err) + req.Equal(len(set), len(value)) + } + + // filter one item + { + found := false + set, err := value.Filter(func(*DataPrivacyRequestComment) (bool, error) { + if !found { + found = true + return found, nil + } + return false, nil + }) + req.NoError(err) + req.Len(set, 1) + } + + // filter error + { + _, err := value.Filter(func(*DataPrivacyRequestComment) (bool, error) { + return false, fmt.Errorf("filter error") + }) + req.Error(err) + } +} + +func TestDataPrivacyRequestCommentSetIDs(t *testing.T) { + var ( + value = make(DataPrivacyRequestCommentSet, 3) + req = require.New(t) + ) + + // construct objects + value[0] = new(DataPrivacyRequestComment) + value[1] = new(DataPrivacyRequestComment) + value[2] = new(DataPrivacyRequestComment) + // set ids + value[0].ID = 1 + value[1].ID = 2 + value[2].ID = 3 + + // Find existing + { + val := value.FindByID(2) + req.Equal(uint64(2), val.ID) + } + + // Find non-existing + { + val := value.FindByID(4) + req.Nil(val) + } + + // List IDs from set + { + val := value.IDs() + req.Equal(len(val), len(value)) + } +} + func TestQueueSetWalk(t *testing.T) { var ( value = make(QueueSet, 3) diff --git a/system/types/types.yaml b/system/types/types.yaml index 0fc8703ff..fc62b1b3d 100644 --- a/system/types/types.yaml +++ b/system/types/types.yaml @@ -35,3 +35,5 @@ types: Queue: {} QueueMessage: noIdField: true + DataPrivacyRequest: {} + DataPrivacyRequestComment: {} diff --git a/tests/system/data_privacy_request_comment_test.go b/tests/system/data_privacy_request_comment_test.go new file mode 100644 index 000000000..5a06b0fd4 --- /dev/null +++ b/tests/system/data_privacy_request_comment_test.go @@ -0,0 +1,102 @@ +package system + +import ( + "context" + "fmt" + "github.com/cortezaproject/corteza-server/pkg/id" + "github.com/cortezaproject/corteza-server/store" + "github.com/cortezaproject/corteza-server/system/service" + "github.com/cortezaproject/corteza-server/system/types" + "github.com/cortezaproject/corteza-server/tests/helpers" + "github.com/spf13/cast" + jsonpath "github.com/steinfletcher/apitest-jsonpath" + "net/http" + "testing" + "time" +) + +func (h helper) clearDataPrivacyRequestComments() { + h.noError(store.TruncateDataPrivacyRequestComments(context.Background(), service.DefaultStore)) +} + +func (h helper) createDataPrivacyRequestComment(comment string, requestID uint64) *types.DataPrivacyRequestComment { + res := &types.DataPrivacyRequestComment{ + ID: id.Next(), + RequestID: requestID, + Comment: comment, + CreatedAt: time.Now(), + } + + h.a.NoError(store.CreateDataPrivacyRequestComment(context.Background(), service.DefaultStore, res)) + + return res +} + +func (h helper) createSampleDataPrivacyRequestComment(requestID ...uint64) *types.DataPrivacyRequestComment { + rID := id.Next() + if len(requestID) == 1 { + rID = requestID[0] + } + return h.createDataPrivacyRequestComment(rs(20), rID) +} + +func TestDataPrivacyRequestCommentList(t *testing.T) { + h := newHelper(t) + h.clearDataPrivacyRequests() + h.clearDataPrivacyRequestComments() + + request := h.createSampleDataPrivacyRequest() + reqID := request.ID + h.createSampleDataPrivacyRequestComment(reqID) + h.createSampleDataPrivacyRequestComment(reqID) + h.createSampleDataPrivacyRequestComment() + h.createSampleDataPrivacyRequestComment() + + h.apiInit(). + Get(fmt.Sprintf("/data-privacy/requests/%d/comments/", reqID)). + Expect(t). + Status(http.StatusOK). + Assert(helpers.AssertNoErrors). + Assert(jsonpath.Len(`$.response.set`, 2)). + End() +} + +func TestDataPrivacyRequestCreateComment(t *testing.T) { + h := newHelper(t) + h.clearDataPrivacyRequestComments() + + request := h.createSampleDataPrivacyRequest() + reqID := request.ID + comment := rs(20) + + helpers.AllowMe(h, types.DataPrivacyRequestRbacResource(0), "read") + + h.apiInit(). + Post(fmt.Sprintf("/data-privacy/requests/%d/comments/", reqID)). + Header("Accept", "application/json"). + FormData("comment", comment). + Expect(t). + Status(http.StatusOK). + Assert(helpers.AssertNoErrors). + Assert(jsonpath.Present(`$.response.requestID`)). + Assert(jsonpath.Present(`$.response.createdAt`)). + Assert(jsonpath.Present(`$.response.createdBy`)). + Assert(jsonpath.Equal(`$.response.requestID`, cast.ToString(reqID))). + Assert(jsonpath.Equal(`$.response.comment`, comment)). + End() +} + +func TestDataPrivacyRequestCreateCommentForbidden(t *testing.T) { + h := newHelper(t) + h.clearDataPrivacyRequestComments() + comment := rs(20) + + h.apiInit(). + Post(fmt.Sprintf("/data-privacy/requests/%d/comments/", id.Next())). + Header("Accept", "application/json"). + FormData("comment", comment). + Expect(t). + Status(http.StatusOK). + Assert(helpers.AssertError("dataPrivacy.errors.notFound")). + End() +} diff --git a/tests/system/data_privacy_request_test.go b/tests/system/data_privacy_request_test.go new file mode 100644 index 000000000..5e2cbcd6a --- /dev/null +++ b/tests/system/data_privacy_request_test.go @@ -0,0 +1,238 @@ +package system + +import ( + "context" + "github.com/cortezaproject/corteza-server/pkg/id" + "github.com/cortezaproject/corteza-server/store" + "github.com/cortezaproject/corteza-server/system/service" + "github.com/cortezaproject/corteza-server/system/types" + "github.com/cortezaproject/corteza-server/tests/helpers" + "github.com/spf13/cast" + jsonpath "github.com/steinfletcher/apitest-jsonpath" + "net/http" + "testing" + "time" +) + +func (h helper) clearDataPrivacyRequests() { + h.noError(store.TruncateDataPrivacyRequests(context.Background(), service.DefaultStore)) +} + +func (h helper) createDataPrivacyRequest(kind types.RequestKind, status types.RequestStatus, requestedBy uint64) *types.DataPrivacyRequest { + res := &types.DataPrivacyRequest{ + ID: id.Next(), + Kind: types.RequestKindCorrect, + Status: types.RequestStatusPending, + RequestedAt: time.Now(), + RequestedBy: requestedBy, + CreatedAt: time.Now(), + } + + if len(kind) > 0 { + res.Kind = kind + } + + if len(status) > 0 { + res.Status = status + } + + h.a.NoError(store.CreateDataPrivacyRequest(context.Background(), service.DefaultStore, res)) + + return res +} + +func (h helper) createSampleDataPrivacyRequest() *types.DataPrivacyRequest { + return h.createDataPrivacyRequest("", "", 0) +} + +func TestDataPrivacyRequestList(t *testing.T) { + h := newHelper(t) + h.clearDataPrivacyRequests() + + h.createSampleDataPrivacyRequest() + h.createSampleDataPrivacyRequest() + + helpers.AllowMe(h, types.ComponentRbacResource(), "data-privacy-requests.search") + helpers.AllowMe(h, types.DataPrivacyRequestRbacResource(0), "read") + + h.apiInit(). + Get("/data-privacy/requests/"). + Expect(t). + Status(http.StatusOK). + Assert(helpers.AssertNoErrors). + Assert(jsonpath.Len(`$.response.set`, 2)). + End() +} + +func TestDataPrivacyRequestListWithPaging(t *testing.T) { + h := newHelper(t) + h.clearDataPrivacyRequests() + + seedCount := 40 + for i := 0; i < seedCount; i++ { + h.createSampleDataPrivacyRequest() + } + + helpers.AllowMe(h, types.ComponentRbacResource(), "data-privacy-requests.search") + helpers.AllowMe(h, types.DataPrivacyRequestRbacResource(0), "read") + + var aux = struct { + Response struct { + Filter struct { + NextPage *string + PrevPage *string + } + } + }{} + + h.apiInit(). + Get("/data-privacy/requests/"). + Query("limit", "10"). + Query("sort", "kind,createdAt+DESC"). + Header("Accept", "application/json"). + Expect(t). + Status(http.StatusOK). + Assert(helpers.AssertNoErrors). + Assert(jsonpath.Present(`$.response.filter`)). + Assert(jsonpath.Present(`$.response.set`)). + Assert(jsonpath.Len(`$.response.set`, 10)). + Assert(jsonpath.Present(`$.response.filter.nextPage`)). + End(). + JSON(&aux) + + h.a.NotNil(aux.Response.Filter.NextPage) + + h.apiInit(). + Get("/data-privacy/requests/"). + Header("Accept", "application/json"). + Query("limit", "10"). + Query("sort", "kind,createdAt+DESC"). + Query("pageCursor", *aux.Response.Filter.NextPage). + Expect(t). + Status(http.StatusOK). + Assert(jsonpath.Len(`$.response.set`, 10)). + Assert(jsonpath.Present(`$.response.filter.prevPage`)). + Assert(jsonpath.Present(`$.response.filter.nextPage`)). + End(). + JSON(&aux) + + h.a.NotNil(aux.Response.Filter.PrevPage) + h.a.NotNil(aux.Response.Filter.NextPage) +} + +func TestDataPrivacyRequestListFilters(t *testing.T) { + h := newHelper(t) + h.clearDataPrivacyRequests() + + h.createSampleDataPrivacyRequest() + h.createSampleDataPrivacyRequest() + h.createDataPrivacyRequest("", types.RequestStatusApproved, 0) + h.createDataPrivacyRequest(types.RequestKindExport, types.RequestStatusApproved, 0) + + helpers.AllowMe(h, types.ComponentRbacResource(), "data-privacy-requests.search") + helpers.AllowMe(h, types.DataPrivacyRequestRbacResource(0), "read") + + h.apiInit(). + Get("/data-privacy/requests/"). + Query("query", types.RequestStatusApproved.String()). + Query("kind", types.RequestKindExport.String()). + Expect(t). + Status(http.StatusOK). + Assert(helpers.AssertNoErrors). + Assert(jsonpath.Len(`$.response.set`, 1)). + End() +} + +func TestDataPrivacyRequestListWithRequestedByFilter(t *testing.T) { + h := newHelper(t) + h.clearDataPrivacyRequests() + + user := h.createUserWithEmail(h.randEmail()) + h.createSampleDataPrivacyRequest() + h.createSampleDataPrivacyRequest() + h.createDataPrivacyRequest("", types.RequestStatusApproved, user.ID) + h.createDataPrivacyRequest(types.RequestKindExport, types.RequestStatusApproved, user.ID) + h.createSampleDataPrivacyRequest() + + helpers.AllowMe(h, types.ComponentRbacResource(), "data-privacy-requests.search") + helpers.AllowMe(h, types.DataPrivacyRequestRbacResource(0), "read") + + h.apiInit(). + Get("/data-privacy/requests/"). + Query("requestedBy", cast.ToString(user.ID)). + Expect(t). + Status(http.StatusOK). + Assert(helpers.AssertNoErrors). + Assert(jsonpath.Len(`$.response.set`, 2)). + End() +} + +func TestDataPrivacyRequestRead(t *testing.T) { + h := newHelper(t) + h.clearDataPrivacyRequests() + + client := h.createSampleDataPrivacyRequest() + + helpers.AllowMe(h, types.DataPrivacyRequestRbacResource(0), "read") + + h.apiInit(). + Getf("/data-privacy/requests/%d", client.ID). + Expect(t). + Status(http.StatusOK). + Assert(helpers.AssertNoErrors). + End() +} + +func TestDataPrivacyRequestCreate(t *testing.T) { + h := newHelper(t) + h.clearDataPrivacyRequests() + + helpers.AllowMe(h, types.ComponentRbacResource(), "data-privacy-request.create") + + h.apiInit(). + Post("/data-privacy/requests/"). + Header("Accept", "application/json"). + FormData("kind", "correct"). + Expect(t). + Status(http.StatusOK). + Assert(helpers.AssertNoErrors). + Assert(jsonpath.Present(`$.response.requestID`)). + Assert(jsonpath.Equal(`$.response.kind`, types.RequestKindCorrect.String())). + Assert(jsonpath.Equal(`$.response.status`, types.RequestStatusPending.String())). + End() +} + +func TestDataPrivacyRequestUpdateStatus(t *testing.T) { + h := newHelper(t) + h.clearDataPrivacyRequests() + + req := h.createSampleDataPrivacyRequest() + + helpers.AllowMe(h, types.DataPrivacyRequestRbacResource(0), "approve") + + h.apiInit(). + Patchf("/data-privacy/requests/%d/status/approved", req.ID). + Header("Accept", "application/json"). + JSON(helpers.JSON(req)). + Expect(t). + Status(http.StatusOK). + Assert(helpers.AssertNoErrors). + Assert(jsonpath.Equal(`$.response.status`, types.RequestStatusApproved.String())). + End() +} + +func TestDataPrivacyRequestUpdateStatusForbidden(t *testing.T) { + h := newHelper(t) + h.clearDataPrivacyRequests() + + req := h.createSampleDataPrivacyRequest() + + h.apiInit(). + Patchf("/data-privacy/requests/%d/status/approved", req.ID). + Header("Accept", "application/json"). + JSON(helpers.JSON(req)). + Expect(t). + Status(http.StatusOK). + Assert(helpers.AssertError("dataPrivacy.errors.notAllowedToApprove")). + End() +} diff --git a/tests/system/resource_activity_log_test.go b/tests/system/resource_activity_log_test.go index e8fcbd6b5..3e265d1e2 100644 --- a/tests/system/resource_activity_log_test.go +++ b/tests/system/resource_activity_log_test.go @@ -11,7 +11,7 @@ import ( ) func (h helper) clearActivityLog() { - h.noError(store.TruncateResourceActivityLogs(context.Background(), service.DefaultStore)) + h.noError(store.TruncateResourceActivitys(context.Background(), service.DefaultStore)) } func (h helper) repoMakeActivityLog() *discoveryType.ResourceActivity { @@ -22,7 +22,7 @@ func (h helper) repoMakeActivityLog() *discoveryType.ResourceActivity { ResourceAction: "create", } - h.a.NoError(store.CreateResourceActivityLog(context.Background(), service.DefaultStore, res)) + h.a.NoError(store.CreateResourceActivity(context.Background(), service.DefaultStore, res)) return res }