diff --git a/pkg/coredata/control.go b/pkg/coredata/control.go index eebb27151..8b0c5a6cd 100644 --- a/pkg/coredata/control.go +++ b/pkg/coredata/control.go @@ -55,7 +55,7 @@ type ( } ) -func (c Control) CursorKey() page.CursorKey { +func (c Control) CursorKey(orderBy page.OrderField) page.CursorKey { return page.NewCursorKey(c.ID, c.CreatedAt) } diff --git a/pkg/coredata/evidence.go b/pkg/coredata/evidence.go index b977a0c83..cd3226526 100644 --- a/pkg/coredata/evidence.go +++ b/pkg/coredata/evidence.go @@ -42,7 +42,7 @@ type ( Evidences []*Evidence ) -func (e Evidence) CursorKey() page.CursorKey { +func (e Evidence) CursorKey(orderBy page.OrderField) page.CursorKey { return page.NewCursorKey(e.ID, e.CreatedAt) } diff --git a/pkg/coredata/framework.go b/pkg/coredata/framework.go index cd13bc946..5503c232a 100644 --- a/pkg/coredata/framework.go +++ b/pkg/coredata/framework.go @@ -47,7 +47,7 @@ type ( } ) -func (f Framework) CursorKey() page.CursorKey { +func (f Framework) CursorKey(orderBy page.OrderField) page.CursorKey { return page.NewCursorKey(f.ID, f.CreatedAt) } @@ -78,7 +78,7 @@ WHERE q = fmt.Sprintf(q, scope.SQLFragment(), cursor.SQLFragment()) - args := pgx.StrictNamedArgs{"organization_id": organizationID} + args := pgx.NamedArgs{"organization_id": organizationID} maps.Copy(args, scope.SQLArguments()) maps.Copy(args, cursor.SQLArguments()) diff --git a/pkg/coredata/organization.go b/pkg/coredata/organization.go index 613725afe..76434c05d 100644 --- a/pkg/coredata/organization.go +++ b/pkg/coredata/organization.go @@ -21,6 +21,7 @@ import ( "time" "github.com/getprobo/probo/pkg/gid" + "github.com/getprobo/probo/pkg/page" "github.com/jackc/pgx/v5" "go.gearno.de/kit/pg" ) @@ -38,6 +39,10 @@ type ( Organizations []*Organization ) +func (o Organization) CursorKey(orderBy page.OrderField) page.CursorKey { + return page.NewCursorKey(o.ID, o.CreatedAt) +} + func (o *Organization) LoadByID( ctx context.Context, conn pg.Conn, diff --git a/pkg/coredata/people.go b/pkg/coredata/people.go index 0a35e9e79..90579be4e 100644 --- a/pkg/coredata/people.go +++ b/pkg/coredata/people.go @@ -50,7 +50,7 @@ type ( } ) -func (p People) CursorKey() page.CursorKey { +func (p People) CursorKey(orderBy page.OrderField) page.CursorKey { return page.NewCursorKey(p.ID, p.CreatedAt) } diff --git a/pkg/coredata/policy.go b/pkg/coredata/policy.go index 7ebe8aea7..9b191c2b4 100644 --- a/pkg/coredata/policy.go +++ b/pkg/coredata/policy.go @@ -38,7 +38,7 @@ type ( } ) -func (p Policy) CursorKey() page.CursorKey { +func (p Policy) CursorKey(orderBy page.OrderField) page.CursorKey { return page.NewCursorKey(p.ID, p.CreatedAt) } diff --git a/pkg/coredata/session.go b/pkg/coredata/session.go index 6badf06ad..19a1a40a3 100644 --- a/pkg/coredata/session.go +++ b/pkg/coredata/session.go @@ -38,7 +38,7 @@ type ( SessionData struct{} ) -func (s Session) CursorKey() page.CursorKey { +func (s Session) CursorKey(orderBy page.OrderField) page.CursorKey { return page.NewCursorKey(s.ID, s.CreatedAt) } diff --git a/pkg/coredata/task.go b/pkg/coredata/task.go index 351361616..39e86c521 100644 --- a/pkg/coredata/task.go +++ b/pkg/coredata/task.go @@ -53,7 +53,7 @@ type ( } ) -func (t Task) CursorKey() page.CursorKey { +func (t Task) CursorKey(orderBy page.OrderField) page.CursorKey { return page.NewCursorKey(t.ID, t.CreatedAt) } diff --git a/pkg/coredata/user.go b/pkg/coredata/user.go index 0e5cf6ed3..bfa1a550f 100644 --- a/pkg/coredata/user.go +++ b/pkg/coredata/user.go @@ -59,7 +59,7 @@ func (e ErrUserAlreadyExists) Error() string { return e.message } -func (u User) CursorKey() page.CursorKey { +func (u User) CursorKey(orderBy page.OrderField) page.CursorKey { return page.NewCursorKey(u.ID, u.CreatedAt) } diff --git a/pkg/coredata/vendor.go b/pkg/coredata/vendor.go index 7b54dbff1..53b2b61f3 100644 --- a/pkg/coredata/vendor.go +++ b/pkg/coredata/vendor.go @@ -63,8 +63,17 @@ type ( } ) -func (v Vendor) CursorKey() page.CursorKey { - return page.NewCursorKey(v.ID, v.CreatedAt) +func (v Vendor) CursorKey(orderBy page.OrderField) page.CursorKey { + switch orderBy { + case page.OrderFieldCreatedAt: + return page.NewCursorKey(v.ID, v.CreatedAt) + case page.OrderFieldUpdatedAt: + return page.NewCursorKey(v.ID, v.UpdatedAt) + case page.OrderFieldName: + return page.NewCursorKey(v.ID, v.Name) + } + + panic(fmt.Sprintf("unknown order by: %s", orderBy)) } func (v *Vendor) LoadByID( @@ -229,7 +238,6 @@ WHERE AND organization_id = @organization_id AND %s ` - q = fmt.Sprintf(q, scope.SQLFragment(), cursor.SQLFragment()) args := pgx.StrictNamedArgs{"organization_id": organizationID} diff --git a/pkg/page/order_field.go b/pkg/page/order_field.go index 3a6869c32..864ecf2e2 100644 --- a/pkg/page/order_field.go +++ b/pkg/page/order_field.go @@ -23,20 +23,20 @@ type ( Column() string } - BaseOrderField string + GenericOrderField string ) const ( - OrderFieldCreatedAt BaseOrderField = "CREATED_AT" - OrderFieldUpdatedAt BaseOrderField = "UPDATED_AT" - OrderFieldName BaseOrderField = "NAME" + OrderFieldCreatedAt GenericOrderField = "CREATED_AT" + OrderFieldUpdatedAt GenericOrderField = "UPDATED_AT" + OrderFieldName GenericOrderField = "NAME" ) -func (of BaseOrderField) String() string { +func (of GenericOrderField) String() string { return string(of) } -func (of BaseOrderField) Column() string { +func (of GenericOrderField) Column() string { switch of { case OrderFieldCreatedAt: return "created_at" @@ -49,11 +49,11 @@ func (of BaseOrderField) Column() string { } } -func (of BaseOrderField) MarshalText() ([]byte, error) { +func (of GenericOrderField) MarshalText() ([]byte, error) { return []byte(of.String()), nil } -func (of *BaseOrderField) UnmarshalText(data []byte) error { +func (of *GenericOrderField) UnmarshalText(data []byte) error { val := string(data) switch val { @@ -64,7 +64,7 @@ func (of *BaseOrderField) UnmarshalText(data []byte) error { case OrderFieldName.String(): *of = OrderFieldName default: - return fmt.Errorf("invalid BaseOrderField value: %q", val) + return fmt.Errorf("invalid GenericOrderField value: %q", val) } return nil diff --git a/pkg/server/api/console/v1/types/control.go b/pkg/server/api/console/v1/types/control.go index 453034f9b..32f15d622 100644 --- a/pkg/server/api/console/v1/types/control.go +++ b/pkg/server/api/console/v1/types/control.go @@ -23,7 +23,7 @@ func NewControlConnection(p *page.Page[*coredata.Control]) *ControlConnection { var edges = make([]*ControlEdge, len(p.Data)) for i := range edges { - edges[i] = NewControlEdge(p.Data[i]) + edges[i] = NewControlEdge(p.Data[i], p.Cursor.OrderBy.Field) } return &ControlConnection{ @@ -32,9 +32,9 @@ func NewControlConnection(p *page.Page[*coredata.Control]) *ControlConnection { } } -func NewControlEdge(c *coredata.Control) *ControlEdge { +func NewControlEdge(c *coredata.Control, orderBy page.OrderField) *ControlEdge { return &ControlEdge{ - Cursor: c.CursorKey(), + Cursor: c.CursorKey(orderBy), Node: NewControl(c), } } diff --git a/pkg/server/api/console/v1/types/cursorkey.go b/pkg/server/api/console/v1/types/cursorkey.go index 7baf7bf4c..cfd2c1255 100644 --- a/pkg/server/api/console/v1/types/cursorkey.go +++ b/pkg/server/api/console/v1/types/cursorkey.go @@ -45,7 +45,33 @@ func NewCursor( from = before } - return page.NewCursor(size, from, direction) + return page.NewCursor(size, from, direction, nil) +} + +func NewCursorWithOrder( + first *int, + after *page.CursorKey, + last *int, + before *page.CursorKey, + orderBy *page.OrderBy, +) *page.Cursor { + var ( + size int + from *page.CursorKey + direction = page.Head + ) + + if first != nil { + size = *first + direction = page.Head + from = after + } else if last != nil { + size = *last + direction = page.Tail + from = before + } + + return page.NewCursor(size, from, direction, orderBy) } func MarshalCursorKeyScalar(ck page.CursorKey) graphql.Marshaler { diff --git a/pkg/server/api/console/v1/types/evidence.go b/pkg/server/api/console/v1/types/evidence.go index 805e65ce9..7cace7510 100644 --- a/pkg/server/api/console/v1/types/evidence.go +++ b/pkg/server/api/console/v1/types/evidence.go @@ -23,7 +23,7 @@ func NewEvidenceConnection(p *page.Page[*coredata.Evidence]) *EvidenceConnection var edges = make([]*EvidenceEdge, len(p.Data)) for i := range edges { - edges[i] = NewEvidenceEdge(p.Data[i]) + edges[i] = NewEvidenceEdge(p.Data[i], p.Cursor.OrderBy.Field) } return &EvidenceConnection{ @@ -32,9 +32,9 @@ func NewEvidenceConnection(p *page.Page[*coredata.Evidence]) *EvidenceConnection } } -func NewEvidenceEdge(e *coredata.Evidence) *EvidenceEdge { +func NewEvidenceEdge(e *coredata.Evidence, orderBy page.OrderField) *EvidenceEdge { return &EvidenceEdge{ - Cursor: e.CursorKey(), + Cursor: e.CursorKey(orderBy), Node: NewEvidence(e), } } diff --git a/pkg/server/api/console/v1/types/framework.go b/pkg/server/api/console/v1/types/framework.go index 83e228ec2..42163a087 100644 --- a/pkg/server/api/console/v1/types/framework.go +++ b/pkg/server/api/console/v1/types/framework.go @@ -23,7 +23,7 @@ func NewFrameworkConnection(p *page.Page[*coredata.Framework]) *FrameworkConnect var edges = make([]*FrameworkEdge, len(p.Data)) for i := range edges { - edges[i] = NewFrameworkEdge(p.Data[i]) + edges[i] = NewFrameworkEdge(p.Data[i], p.Cursor.OrderBy.Field) } return &FrameworkConnection{ @@ -32,9 +32,9 @@ func NewFrameworkConnection(p *page.Page[*coredata.Framework]) *FrameworkConnect } } -func NewFrameworkEdge(f *coredata.Framework) *FrameworkEdge { +func NewFrameworkEdge(f *coredata.Framework, orderBy page.OrderField) *FrameworkEdge { return &FrameworkEdge{ - Cursor: f.CursorKey(), + Cursor: f.CursorKey(orderBy), Node: NewFramework(f), } } diff --git a/pkg/server/api/console/v1/types/organization.go b/pkg/server/api/console/v1/types/organization.go index 20e99cdc4..073f92504 100644 --- a/pkg/server/api/console/v1/types/organization.go +++ b/pkg/server/api/console/v1/types/organization.go @@ -16,8 +16,29 @@ package types import ( "github.com/getprobo/probo/pkg/coredata" + "github.com/getprobo/probo/pkg/page" ) +func NewOrganizationConnection(page *page.Page[*coredata.Organization]) *OrganizationConnection { + var edges = make([]*OrganizationEdge, len(page.Data)) + + for i := range edges { + edges[i] = NewOrganizationEdge(page.Data[i], page.Cursor.OrderBy.Field) + } + + return &OrganizationConnection{ + Edges: edges, + PageInfo: NewPageInfo(page), + } +} + +func NewOrganizationEdge(o *coredata.Organization, orderBy page.OrderField) *OrganizationEdge { + return &OrganizationEdge{ + Cursor: o.CursorKey(orderBy), + Node: NewOrganization(o), + } +} + func NewOrganization(o *coredata.Organization) *Organization { return &Organization{ ID: o.ID, @@ -26,9 +47,3 @@ func NewOrganization(o *coredata.Organization) *Organization { UpdatedAt: o.UpdatedAt, } } - -func NewOrganizationEdge(o *coredata.Organization) *OrganizationEdge { - return &OrganizationEdge{ - Node: NewOrganization(o), - } -} diff --git a/pkg/server/api/console/v1/types/pageinfo.go b/pkg/server/api/console/v1/types/pageinfo.go index 5dae92089..902cb50ff 100644 --- a/pkg/server/api/console/v1/types/pageinfo.go +++ b/pkg/server/api/console/v1/types/pageinfo.go @@ -26,8 +26,8 @@ func NewPageInfo[T page.Paginable](p *page.Page[T]) *PageInfo { ) if len(p.Data) > 0 { - startCursor = ref.Ref(p.First().CursorKey()) - endCursor = ref.Ref(p.Last().CursorKey()) + startCursor = ref.Ref(p.First().CursorKey(p.Cursor.OrderBy.Field)) + endCursor = ref.Ref(p.Last().CursorKey(p.Cursor.OrderBy.Field)) } return &PageInfo{ diff --git a/pkg/server/api/console/v1/types/people.go b/pkg/server/api/console/v1/types/people.go index 7bb97e2a1..d90966e8a 100644 --- a/pkg/server/api/console/v1/types/people.go +++ b/pkg/server/api/console/v1/types/people.go @@ -23,7 +23,7 @@ func NewPeopleConnection(p *page.Page[*coredata.People]) *PeopleConnection { var edges = make([]*PeopleEdge, len(p.Data)) for i := range edges { - edges[i] = NewPeopleEdge(p.Data[i]) + edges[i] = NewPeopleEdge(p.Data[i], p.Cursor.OrderBy.Field) } return &PeopleConnection{ @@ -32,9 +32,9 @@ func NewPeopleConnection(p *page.Page[*coredata.People]) *PeopleConnection { } } -func NewPeopleEdge(p *coredata.People) *PeopleEdge { +func NewPeopleEdge(p *coredata.People, orderBy page.OrderField) *PeopleEdge { return &PeopleEdge{ - Cursor: p.CursorKey(), + Cursor: p.CursorKey(orderBy), Node: NewPeople(p), } } diff --git a/pkg/server/api/console/v1/types/policy.go b/pkg/server/api/console/v1/types/policy.go index 2a377755b..643f53204 100644 --- a/pkg/server/api/console/v1/types/policy.go +++ b/pkg/server/api/console/v1/types/policy.go @@ -19,6 +19,25 @@ import ( "github.com/getprobo/probo/pkg/page" ) +func NewPolicyConnection(page *page.Page[*coredata.Policy]) *PolicyConnection { + edges := make([]*PolicyEdge, len(page.Data)) + for i, policy := range page.Data { + edges[i] = NewPolicyEdge(policy, page.Cursor.OrderBy.Field) + } + + return &PolicyConnection{ + Edges: edges, + PageInfo: NewPageInfo(page), + } +} + +func NewPolicyEdge(policy *coredata.Policy, orderBy page.OrderField) *PolicyEdge { + return &PolicyEdge{ + Cursor: policy.CursorKey(orderBy), + Node: NewPolicy(policy), + } +} + func NewPolicy(policy *coredata.Policy) *Policy { return &Policy{ ID: policy.ID, @@ -31,22 +50,3 @@ func NewPolicy(policy *coredata.Policy) *Policy { ReviewDate: policy.ReviewDate, } } - -func NewPolicyEdge(policy *coredata.Policy) *PolicyEdge { - return &PolicyEdge{ - Cursor: policy.CursorKey(), - Node: NewPolicy(policy), - } -} - -func NewPolicyConnection(page *page.Page[*coredata.Policy]) *PolicyConnection { - edges := make([]*PolicyEdge, len(page.Data)) - for i, policy := range page.Data { - edges[i] = NewPolicyEdge(policy) - } - - return &PolicyConnection{ - Edges: edges, - PageInfo: NewPageInfo(page), - } -} diff --git a/pkg/server/api/console/v1/types/task.go b/pkg/server/api/console/v1/types/task.go index 4dadd6f41..f5311a54f 100644 --- a/pkg/server/api/console/v1/types/task.go +++ b/pkg/server/api/console/v1/types/task.go @@ -23,7 +23,7 @@ func NewTaskConnection(p *page.Page[*coredata.Task]) *TaskConnection { var edges = make([]*TaskEdge, len(p.Data)) for i := range edges { - edges[i] = NewTaskEdge(p.Data[i]) + edges[i] = NewTaskEdge(p.Data[i], p.Cursor.OrderBy.Field) } return &TaskConnection{ @@ -32,9 +32,9 @@ func NewTaskConnection(p *page.Page[*coredata.Task]) *TaskConnection { } } -func NewTaskEdge(t *coredata.Task) *TaskEdge { +func NewTaskEdge(t *coredata.Task, orderBy page.OrderField) *TaskEdge { return &TaskEdge{ - Cursor: t.CursorKey(), + Cursor: t.CursorKey(orderBy), Node: NewTask(t), } } diff --git a/pkg/server/api/console/v1/types/types.go b/pkg/server/api/console/v1/types/types.go index 368202808..d72450c60 100644 --- a/pkg/server/api/console/v1/types/types.go +++ b/pkg/server/api/console/v1/types/types.go @@ -3,6 +3,9 @@ package types import ( + "fmt" + "io" + "strconv" "time" "github.com/99designs/gqlgen/graphql" @@ -59,6 +62,11 @@ type ControlEdge struct { Node *Control `json:"node"` } +type ControlOrder struct { + Direction page.OrderDirection `json:"direction"` + Field page.GenericOrderField `json:"field"` +} + type CreateControlInput struct { FrameworkID gid.GID `json:"frameworkId"` Name string `json:"name"` @@ -215,6 +223,11 @@ type EvidenceEdge struct { Node *Evidence `json:"node"` } +type EvidenceOrder struct { + Direction page.OrderDirection `json:"direction"` + Field page.GenericOrderField `json:"field"` +} + type Framework struct { ID gid.GID `json:"id"` Version int `json:"version"` @@ -238,6 +251,11 @@ type FrameworkEdge struct { Node *Framework `json:"node"` } +type FrameworkOrder struct { + Direction page.OrderDirection `json:"direction"` + Field page.GenericOrderField `json:"field"` +} + type ImportFrameworkInput struct { OrganizationID gid.GID `json:"organizationId"` File graphql.Upload `json:"file"` @@ -286,6 +304,11 @@ type OrganizationEdge struct { Node *Organization `json:"node"` } +type OrganizationOrder struct { + Direction page.OrderDirection `json:"direction"` + Field OrganizationOrderField `json:"field"` +} + type PageInfo struct { HasNextPage bool `json:"hasNextPage"` HasPreviousPage bool `json:"hasPreviousPage"` @@ -342,6 +365,11 @@ type PolicyEdge struct { Node *Policy `json:"node"` } +type PolicyOrder struct { + Direction page.OrderDirection `json:"direction"` + Field page.GenericOrderField `json:"field"` +} + type Query struct { } @@ -385,6 +413,11 @@ type TaskEdge struct { Node *Task `json:"node"` } +type TaskOrder struct { + Direction page.OrderDirection `json:"direction"` + Field page.GenericOrderField `json:"field"` +} + type UnassignTaskInput struct { TaskID gid.GID `json:"taskId"` } @@ -517,6 +550,11 @@ type UserEdge struct { Node *User `json:"node"` } +type UserOrder struct { + Direction page.OrderDirection `json:"direction"` + Field UserOrderField `json:"field"` +} + type Vendor struct { ID gid.GID `json:"id"` Name string `json:"name"` @@ -551,3 +589,91 @@ type Viewer struct { User *User `json:"user"` Organizations *OrganizationConnection `json:"organizations"` } + +type OrganizationOrderField string + +const ( + OrganizationOrderFieldName OrganizationOrderField = "NAME" + OrganizationOrderFieldCreatedAt OrganizationOrderField = "CREATED_AT" + OrganizationOrderFieldUpdatedAt OrganizationOrderField = "UPDATED_AT" +) + +var AllOrganizationOrderField = []OrganizationOrderField{ + OrganizationOrderFieldName, + OrganizationOrderFieldCreatedAt, + OrganizationOrderFieldUpdatedAt, +} + +func (e OrganizationOrderField) IsValid() bool { + switch e { + case OrganizationOrderFieldName, OrganizationOrderFieldCreatedAt, OrganizationOrderFieldUpdatedAt: + return true + } + return false +} + +func (e OrganizationOrderField) String() string { + return string(e) +} + +func (e *OrganizationOrderField) UnmarshalGQL(v any) error { + str, ok := v.(string) + if !ok { + return fmt.Errorf("enums must be strings") + } + + *e = OrganizationOrderField(str) + if !e.IsValid() { + return fmt.Errorf("%s is not a valid OrganizationOrderField", str) + } + return nil +} + +func (e OrganizationOrderField) MarshalGQL(w io.Writer) { + fmt.Fprint(w, strconv.Quote(e.String())) +} + +type UserOrderField string + +const ( + UserOrderFieldFullName UserOrderField = "FULL_NAME" + UserOrderFieldEmail UserOrderField = "EMAIL" + UserOrderFieldCreatedAt UserOrderField = "CREATED_AT" + UserOrderFieldUpdatedAt UserOrderField = "UPDATED_AT" +) + +var AllUserOrderField = []UserOrderField{ + UserOrderFieldFullName, + UserOrderFieldEmail, + UserOrderFieldCreatedAt, + UserOrderFieldUpdatedAt, +} + +func (e UserOrderField) IsValid() bool { + switch e { + case UserOrderFieldFullName, UserOrderFieldEmail, UserOrderFieldCreatedAt, UserOrderFieldUpdatedAt: + return true + } + return false +} + +func (e UserOrderField) String() string { + return string(e) +} + +func (e *UserOrderField) UnmarshalGQL(v any) error { + str, ok := v.(string) + if !ok { + return fmt.Errorf("enums must be strings") + } + + *e = UserOrderField(str) + if !e.IsValid() { + return fmt.Errorf("%s is not a valid UserOrderField", str) + } + return nil +} + +func (e UserOrderField) MarshalGQL(w io.Writer) { + fmt.Fprint(w, strconv.Quote(e.String())) +} diff --git a/pkg/server/api/console/v1/types/user.go b/pkg/server/api/console/v1/types/user.go index 51994b45c..7a288fcd0 100644 --- a/pkg/server/api/console/v1/types/user.go +++ b/pkg/server/api/console/v1/types/user.go @@ -23,7 +23,7 @@ func NewUserConnection(p *page.Page[*coredata.User]) *UserConnection { var edges = make([]*UserEdge, len(p.Data)) for i := range edges { - edges[i] = NewUserEdge(p.Data[i]) + edges[i] = NewUserEdge(p.Data[i], p.Cursor.OrderBy.Field) } return &UserConnection{ @@ -32,9 +32,10 @@ func NewUserConnection(p *page.Page[*coredata.User]) *UserConnection { } } -func NewUserEdge(user *coredata.User) *UserEdge { +func NewUserEdge(user *coredata.User, orderBy page.OrderField) *UserEdge { return &UserEdge{ - Node: NewUser(user), + Cursor: user.CursorKey(orderBy), + Node: NewUser(user), } } diff --git a/pkg/server/api/console/v1/types/vendor.go b/pkg/server/api/console/v1/types/vendor.go index c6e5340ee..130bec999 100644 --- a/pkg/server/api/console/v1/types/vendor.go +++ b/pkg/server/api/console/v1/types/vendor.go @@ -23,7 +23,7 @@ func NewVendorConnection(p *page.Page[*coredata.Vendor]) *VendorConnection { var edges = make([]*VendorEdge, len(p.Data)) for i := range edges { - edges[i] = NewVendorEdge(p.Data[i]) + edges[i] = NewVendorEdge(p.Data[i], p.Cursor.OrderBy.Field) } return &VendorConnection{ @@ -32,9 +32,9 @@ func NewVendorConnection(p *page.Page[*coredata.Vendor]) *VendorConnection { } } -func NewVendorEdge(v *coredata.Vendor) *VendorEdge { +func NewVendorEdge(v *coredata.Vendor, orderBy page.OrderField) *VendorEdge { return &VendorEdge{ - Cursor: v.CursorKey(), + Cursor: v.CursorKey(orderBy), Node: NewVendor(v), } } diff --git a/pkg/server/api/console/v1/v1_resolver.go b/pkg/server/api/console/v1/v1_resolver.go index bafad8244..bcab1f9cf 100644 --- a/pkg/server/api/console/v1/v1_resolver.go +++ b/pkg/server/api/console/v1/v1_resolver.go @@ -20,7 +20,7 @@ import ( ) // Tasks is the resolver for the tasks field. -func (r *controlResolver) Tasks(ctx context.Context, obj *types.Control, first *int, after *page.CursorKey, last *int, before *page.CursorKey) (*types.TaskConnection, error) { +func (r *controlResolver) Tasks(ctx context.Context, obj *types.Control, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.TaskOrder) (*types.TaskConnection, error) { svc := r.GetTenantServiceIfAuthorized(ctx, obj.ID.TenantID()) cursor := types.NewCursor(first, after, last, before) @@ -45,7 +45,7 @@ func (r *evidenceResolver) FileURL(ctx context.Context, obj *types.Evidence) (st } // Controls is the resolver for the controls field. -func (r *frameworkResolver) Controls(ctx context.Context, obj *types.Framework, first *int, after *page.CursorKey, last *int, before *page.CursorKey) (*types.ControlConnection, error) { +func (r *frameworkResolver) Controls(ctx context.Context, obj *types.Framework, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.ControlOrder) (*types.ControlConnection, error) { svc := r.GetTenantServiceIfAuthorized(ctx, obj.ID.TenantID()) cursor := types.NewCursor(first, after, last, before) @@ -77,7 +77,7 @@ func (r *mutationResolver) CreateVendor(ctx context.Context, input types.CreateV return nil, fmt.Errorf("cannot create vendor: %w", err) } return &types.CreateVendorPayload{ - VendorEdge: types.NewVendorEdge(vendor), + VendorEdge: types.NewVendorEdge(vendor, nil), }, nil } @@ -138,7 +138,7 @@ func (r *mutationResolver) CreatePeople(ctx context.Context, input types.CreateP } return &types.CreatePeoplePayload{ - PeopleEdge: types.NewPeopleEdge(people), + PeopleEdge: types.NewPeopleEdge(people, nil), }, nil } @@ -197,7 +197,7 @@ func (r *mutationResolver) CreateOrganization(ctx context.Context, input types.C *tenantIDs = append(*tenantIDs, organization.ID.TenantID()) return &types.CreateOrganizationPayload{ - OrganizationEdge: types.NewOrganizationEdge(organization), + OrganizationEdge: types.NewOrganizationEdge(organization, nil), }, nil } @@ -244,7 +244,7 @@ func (r *mutationResolver) CreateTask(ctx context.Context, input types.CreateTas } return &types.CreateTaskPayload{ - TaskEdge: types.NewTaskEdge(task), + TaskEdge: types.NewTaskEdge(task, nil), }, nil } @@ -324,7 +324,7 @@ func (r *mutationResolver) CreateFramework(ctx context.Context, input types.Crea } return &types.CreateFrameworkPayload{ - FrameworkEdge: types.NewFrameworkEdge(framework), + FrameworkEdge: types.NewFrameworkEdge(framework, nil), }, nil } @@ -362,7 +362,7 @@ func (r *mutationResolver) ImportFramework(ctx context.Context, input types.Impo } return &types.ImportFrameworkPayload{ - FrameworkEdge: types.NewFrameworkEdge(framework), + FrameworkEdge: types.NewFrameworkEdge(framework, nil), }, nil } @@ -382,7 +382,7 @@ func (r *mutationResolver) CreateControl(ctx context.Context, input types.Create } return &types.CreateControlPayload{ - ControlEdge: types.NewControlEdge(control), + ControlEdge: types.NewControlEdge(control, nil), }, nil } @@ -424,7 +424,7 @@ func (r *mutationResolver) UploadEvidence(ctx context.Context, input types.Uploa } return &types.UploadEvidencePayload{ - EvidenceEdge: types.NewEvidenceEdge(evidence), + EvidenceEdge: types.NewEvidenceEdge(evidence, nil), }, nil } @@ -459,7 +459,7 @@ func (r *mutationResolver) CreatePolicy(ctx context.Context, input types.CreateP } return &types.CreatePolicyPayload{ - PolicyEdge: types.NewPolicyEdge(policy), + PolicyEdge: types.NewPolicyEdge(policy, nil), }, nil } @@ -564,7 +564,7 @@ func (r *organizationResolver) LogoURL(ctx context.Context, obj *types.Organizat } // Users is the resolver for the users field. -func (r *organizationResolver) Users(ctx context.Context, obj *types.Organization, first *int, after *page.CursorKey, last *int, before *page.CursorKey) (*types.UserConnection, error) { +func (r *organizationResolver) Users(ctx context.Context, obj *types.Organization, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.UserOrder) (*types.UserConnection, error) { cursor := types.NewCursor(first, after, last, before) page, err := r.usrmgrSvc.ListUsersForTenant(ctx, obj.ID, cursor) @@ -576,7 +576,7 @@ func (r *organizationResolver) Users(ctx context.Context, obj *types.Organizatio } // Frameworks is the resolver for the frameworks field. -func (r *organizationResolver) Frameworks(ctx context.Context, obj *types.Organization, first *int, after *page.CursorKey, last *int, before *page.CursorKey) (*types.FrameworkConnection, error) { +func (r *organizationResolver) Frameworks(ctx context.Context, obj *types.Organization, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.FrameworkOrder) (*types.FrameworkConnection, error) { svc := r.GetTenantServiceIfAuthorized(ctx, obj.ID.TenantID()) cursor := types.NewCursor(first, after, last, before) @@ -590,10 +590,10 @@ func (r *organizationResolver) Frameworks(ctx context.Context, obj *types.Organi } // Vendors is the resolver for the vendors field. -func (r *organizationResolver) Vendors(ctx context.Context, obj *types.Organization, first *int, after *page.CursorKey, last *int, before *page.CursorKey) (*types.VendorConnection, error) { +func (r *organizationResolver) Vendors(ctx context.Context, obj *types.Organization, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *page.OrderBy) (*types.VendorConnection, error) { svc := r.GetTenantServiceIfAuthorized(ctx, obj.ID.TenantID()) - cursor := types.NewCursor(first, after, last, before) + cursor := types.NewCursorWithOrder(first, after, last, before, orderBy) page, err := svc.Vendors.ListForOrganizationID(ctx, obj.ID, cursor) if err != nil { @@ -604,11 +604,10 @@ func (r *organizationResolver) Vendors(ctx context.Context, obj *types.Organizat } // Peoples is the resolver for the peoples field. -func (r *organizationResolver) Peoples(ctx context.Context, obj *types.Organization, first *int, after *page.CursorKey, last *int, before *page.CursorKey) (*types.PeopleConnection, error) { +func (r *organizationResolver) Peoples(ctx context.Context, obj *types.Organization, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *page.OrderBy) (*types.PeopleConnection, error) { svc := r.GetTenantServiceIfAuthorized(ctx, obj.ID.TenantID()) - cursor := types.NewCursor(first, after, last, before) - + cursor := types.NewCursorWithOrder(first, after, last, before, orderBy) page, err := svc.Peoples.ListForOrganizationID(ctx, obj.ID, cursor) if err != nil { return nil, fmt.Errorf("cannot list organization peoples: %w", err) @@ -618,8 +617,9 @@ func (r *organizationResolver) Peoples(ctx context.Context, obj *types.Organizat } // Policies is the resolver for the policies field. -func (r *organizationResolver) Policies(ctx context.Context, obj *types.Organization, first *int, after *page.CursorKey, last *int, before *page.CursorKey) (*types.PolicyConnection, error) { +func (r *organizationResolver) Policies(ctx context.Context, obj *types.Organization, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.PolicyOrder) (*types.PolicyConnection, error) { svc := r.GetTenantServiceIfAuthorized(ctx, obj.ID.TenantID()) + cursor := types.NewCursor(first, after, last, before) page, err := svc.Policies.ListByOrganizationID(ctx, obj.ID, cursor) @@ -747,7 +747,7 @@ func (r *taskResolver) AssignedTo(ctx context.Context, obj *types.Task) (*types. } // Evidences is the resolver for the evidences field. -func (r *taskResolver) Evidences(ctx context.Context, obj *types.Task, first *int, after *page.CursorKey, last *int, before *page.CursorKey) (*types.EvidenceConnection, error) { +func (r *taskResolver) Evidences(ctx context.Context, obj *types.Task, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.EvidenceOrder) (*types.EvidenceConnection, error) { svc := r.GetTenantServiceIfAuthorized(ctx, obj.ID.TenantID()) cursor := types.NewCursor(first, after, last, before) @@ -760,9 +760,10 @@ func (r *taskResolver) Evidences(ctx context.Context, obj *types.Task, first *in } // Organizations is the resolver for the organizations field. -func (r *viewerResolver) Organizations(ctx context.Context, obj *types.Viewer, first *int, after *page.CursorKey, last *int, before *page.CursorKey) (*types.OrganizationConnection, error) { +func (r *viewerResolver) Organizations(ctx context.Context, obj *types.Viewer, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.OrganizationOrder) (*types.OrganizationConnection, error) { user := UserFromContext(ctx) + // For now, we're not using cursor pagination since we're loading all organizations organizations, err := r.usrmgrSvc.ListOrganizationsForUserID(ctx, user.ID) if err != nil { panic(fmt.Errorf("failed to list organizations for user: %w", err)) @@ -770,12 +771,16 @@ func (r *viewerResolver) Organizations(ctx context.Context, obj *types.Viewer, f var edges []*types.OrganizationEdge for _, organization := range organizations { - edges = append(edges, types.NewOrganizationEdge(organization)) + edges = append(edges, types.NewOrganizationEdge(organization, nil)) } + // The simple implementation doesn't handle pagination yet return &types.OrganizationConnection{ - Edges: edges, - PageInfo: &types.PageInfo{}, + Edges: edges, + PageInfo: &types.PageInfo{ + HasNextPage: false, + HasPreviousPage: false, + }, }, nil } @@ -815,3 +820,23 @@ type policyResolver struct{ *Resolver } type queryResolver struct{ *Resolver } type taskResolver struct{ *Resolver } type viewerResolver struct{ *Resolver } + +// !!! WARNING !!! +// The code below was going to be deleted when updating resolvers. It has been copied here so you have +// one last chance to move it out of harms way if you want. There are two reasons this happens: +// - When renaming or deleting a resolver the old code will be put in here. You can safely delete +// it when you're done. +// - You have helper methods in this file. Move them out to keep these resolver files clean. +/* + func (r *peopleOrderResolver) Field(ctx context.Context, obj *page.OrderBy, data page.GenericOrderField) error { + panic(fmt.Errorf("not implemented: Field - field")) +} +func (r *vendorOrderResolver) Field(ctx context.Context, obj *page.OrderBy, data page.GenericOrderField) error { + obj.Field = data + return nil +} +func (r *Resolver) PeopleOrder() schema.PeopleOrderResolver { return &peopleOrderResolver{r} } +func (r *Resolver) VendorOrder() schema.VendorOrderResolver { return &vendorOrderResolver{r} } +type peopleOrderResolver struct{ *Resolver } +type vendorOrderResolver struct{ *Resolver } +*/