From 87f7654b106d7bdb4fc15791bd757ffbf884e6d3 Mon Sep 17 00:00:00 2001 From: gearnode Date: Fri, 14 Mar 2025 09:38:01 +0100 Subject: [PATCH] Add organization member list Signed-off-by: gearnode --- apps/console/src/components/NavUser.tsx | 12 +- .../src/components/OrganizationSwitcher.tsx | 2 +- .../__generated__/AppSidebarQuery.graphql.ts | 40 +- .../__generated__/NavUser_viewer.graphql.ts | 57 +- ...anizationSwitcher_organizations.graphql.ts | 6 +- .../src/pages/OrganizationSelectionPage.tsx | 5 +- apps/console/src/pages/SettingsPage.tsx | 184 ++- ...eateOrganizationPageViewerQuery.graphql.ts | 4 +- .../OrganizationSelectionPageQuery.graphql.ts | 162 +-- .../SettingsPageQuery.graphql.ts | 79 +- pkg/coredata/user.go | 47 + pkg/server/api/console/v1/schema.graphql | 40 +- pkg/server/api/console/v1/schema/schema.go | 1004 ++++++++++++++--- pkg/server/api/console/v1/types/types.go | 28 +- pkg/server/api/console/v1/types/user.go | 20 + pkg/server/api/console/v1/v1_resolver.go | 33 +- pkg/usrmgr/usrmgr.go | 22 + 17 files changed, 1401 insertions(+), 344 deletions(-) diff --git a/apps/console/src/components/NavUser.tsx b/apps/console/src/components/NavUser.tsx index c76f565bb..653da9958 100644 --- a/apps/console/src/components/NavUser.tsx +++ b/apps/console/src/components/NavUser.tsx @@ -22,16 +22,18 @@ import { NavUser_viewer$key } from "./__generated__/NavUser_viewer.graphql"; import { buildEndpoint } from "@/utils"; export const navUserFragment = graphql` - fragment NavUser_viewer on User { - id - fullName - email + fragment NavUser_viewer on Viewer { + user { + id + fullName + email + } } `; export function NavUser({ viewer }: { viewer: NavUser_viewer$key }) { const { isMobile } = useSidebar(); - const currentUser = useFragment(navUserFragment, viewer); + const currentUser = useFragment(navUserFragment, viewer).user; const handleLogout = async () => { fetch(buildEndpoint("/api/console/v1/auth/logout"), { diff --git a/apps/console/src/components/OrganizationSwitcher.tsx b/apps/console/src/components/OrganizationSwitcher.tsx index bf552def9..dc36139a8 100644 --- a/apps/console/src/components/OrganizationSwitcher.tsx +++ b/apps/console/src/components/OrganizationSwitcher.tsx @@ -27,7 +27,7 @@ import { import { cn } from "@/lib/utils"; export const organizationSwitcherFragment = graphql` - fragment OrganizationSwitcher_organizations on User { + fragment OrganizationSwitcher_organizations on Viewer { organizations(first: 25) @connection(key: "OrganizationSwitcher_organizations") { __id diff --git a/apps/console/src/components/__generated__/AppSidebarQuery.graphql.ts b/apps/console/src/components/__generated__/AppSidebarQuery.graphql.ts index d0ed8e932..59553f74b 100644 --- a/apps/console/src/components/__generated__/AppSidebarQuery.graphql.ts +++ b/apps/console/src/components/__generated__/AppSidebarQuery.graphql.ts @@ -1,5 +1,5 @@ /** - * @generated SignedSource<> + * @generated SignedSource<<72d01b98680bc82c2d5bdc8c2fa2d49d>> * @lightSyntaxTransform * @nogrep */ @@ -47,7 +47,7 @@ return { { "alias": null, "args": null, - "concreteType": "User", + "concreteType": "Viewer", "kind": "LinkedField", "name": "viewer", "plural": false, @@ -79,7 +79,7 @@ return { { "alias": null, "args": null, - "concreteType": "User", + "concreteType": "Viewer", "kind": "LinkedField", "name": "viewer", "plural": false, @@ -196,15 +196,27 @@ return { { "alias": null, "args": null, - "kind": "ScalarField", - "name": "fullName", - "storageKey": null - }, - { - "alias": null, - "args": null, - "kind": "ScalarField", - "name": "email", + "concreteType": "User", + "kind": "LinkedField", + "name": "user", + "plural": false, + "selections": [ + (v0/*: any*/), + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "fullName", + "storageKey": null + }, + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "email", + "storageKey": null + } + ], "storageKey": null } ], @@ -213,12 +225,12 @@ return { ] }, "params": { - "cacheID": "d92e5e742128d82b461ab1b6d52cd2a6", + "cacheID": "51c72d5d6b9e1d4e5e7f45e7280a0534", "id": null, "metadata": {}, "name": "AppSidebarQuery", "operationKind": "query", - "text": "query AppSidebarQuery {\n viewer {\n id\n ...OrganizationSwitcher_organizations\n ...NavUser_viewer\n }\n}\n\nfragment NavUser_viewer on User {\n id\n fullName\n email\n}\n\nfragment OrganizationSwitcher_organizations on User {\n organizations(first: 25) {\n edges {\n node {\n id\n name\n logoUrl\n __typename\n }\n cursor\n }\n pageInfo {\n endCursor\n hasNextPage\n }\n }\n}\n" + "text": "query AppSidebarQuery {\n viewer {\n id\n ...OrganizationSwitcher_organizations\n ...NavUser_viewer\n }\n}\n\nfragment NavUser_viewer on Viewer {\n user {\n id\n fullName\n email\n }\n}\n\nfragment OrganizationSwitcher_organizations on Viewer {\n organizations(first: 25) {\n edges {\n node {\n id\n name\n logoUrl\n __typename\n }\n cursor\n }\n pageInfo {\n endCursor\n hasNextPage\n }\n }\n}\n" } }; })(); diff --git a/apps/console/src/components/__generated__/NavUser_viewer.graphql.ts b/apps/console/src/components/__generated__/NavUser_viewer.graphql.ts index 330b49549..a8d189d25 100644 --- a/apps/console/src/components/__generated__/NavUser_viewer.graphql.ts +++ b/apps/console/src/components/__generated__/NavUser_viewer.graphql.ts @@ -1,5 +1,5 @@ /** - * @generated SignedSource<<0965f25c8dedb59a9316e4df4d773af6>> + * @generated SignedSource<<0d5ada0f912fcf3a8f2f3328b0943b28>> * @lightSyntaxTransform * @nogrep */ @@ -11,9 +11,11 @@ import { ReaderFragment } from 'relay-runtime'; import { FragmentRefs } from "relay-runtime"; export type NavUser_viewer$data = { - readonly email: string; - readonly fullName: string; - readonly id: string; + readonly user: { + readonly email: string; + readonly fullName: string; + readonly id: string; + }; readonly " $fragmentType": "NavUser_viewer"; }; export type NavUser_viewer$key = { @@ -30,29 +32,40 @@ const node: ReaderFragment = { { "alias": null, "args": null, - "kind": "ScalarField", - "name": "id", - "storageKey": null - }, - { - "alias": null, - "args": null, - "kind": "ScalarField", - "name": "fullName", - "storageKey": null - }, - { - "alias": null, - "args": null, - "kind": "ScalarField", - "name": "email", + "concreteType": "User", + "kind": "LinkedField", + "name": "user", + "plural": false, + "selections": [ + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "id", + "storageKey": null + }, + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "fullName", + "storageKey": null + }, + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "email", + "storageKey": null + } + ], "storageKey": null } ], - "type": "User", + "type": "Viewer", "abstractKey": null }; -(node as any).hash = "7ee85a272f543b514c5a6a3148fd1613"; +(node as any).hash = "05e26e238976738b4cff26f37c5f29e7"; export default node; diff --git a/apps/console/src/components/__generated__/OrganizationSwitcher_organizations.graphql.ts b/apps/console/src/components/__generated__/OrganizationSwitcher_organizations.graphql.ts index 1b8777f38..13a143c2e 100644 --- a/apps/console/src/components/__generated__/OrganizationSwitcher_organizations.graphql.ts +++ b/apps/console/src/components/__generated__/OrganizationSwitcher_organizations.graphql.ts @@ -1,5 +1,5 @@ /** - * @generated SignedSource<<075aa1caf8416e0cce7536d0da9b5ca9>> + * @generated SignedSource<<152991faff70c7ef6c4751fac48090be>> * @lightSyntaxTransform * @nogrep */ @@ -151,10 +151,10 @@ const node: ReaderFragment = { "storageKey": null } ], - "type": "User", + "type": "Viewer", "abstractKey": null }; -(node as any).hash = "8150704a009e88c096c6f6aa13cada52"; +(node as any).hash = "4bc97175bc64a6c7554a6ec865d12850"; export default node; diff --git a/apps/console/src/pages/OrganizationSelectionPage.tsx b/apps/console/src/pages/OrganizationSelectionPage.tsx index da4413c93..64c08942a 100644 --- a/apps/console/src/pages/OrganizationSelectionPage.tsx +++ b/apps/console/src/pages/OrganizationSelectionPage.tsx @@ -16,7 +16,6 @@ import { OrganizationSelectionPageQuery } from "./__generated__/OrganizationSele const organizationSelectionQuery = graphql` query OrganizationSelectionPageQuery { viewer { - id organizations(first: 25) { edges { node { @@ -34,11 +33,11 @@ export default function OrganizationSelectionPage() { const navigate = useNavigate(); const data = useLazyLoadQuery( organizationSelectionQuery, - {}, + {} ); const organizations = data.viewer.organizations.edges.map( - (edge) => edge.node, + (edge) => edge.node ); useEffect(() => { diff --git a/apps/console/src/pages/SettingsPage.tsx b/apps/console/src/pages/SettingsPage.tsx index 30d2ba6f9..138e52d98 100644 --- a/apps/console/src/pages/SettingsPage.tsx +++ b/apps/console/src/pages/SettingsPage.tsx @@ -45,6 +45,16 @@ const settingsPageQuery = graphql` ... on Organization { name logoUrl + users(first: 100) { + edges { + node { + id + fullName + email + createdAt + } + } + } } } } @@ -64,13 +74,6 @@ const updateOrganizationMutation = graphql` } `; -interface Member { - id: string; - fullName: string; - primaryEmailAddress: string; - role: string; -} - function SettingsPageContent({ queryRef, }: { @@ -78,11 +81,14 @@ function SettingsPageContent({ }) { const data = usePreloadedQuery(settingsPageQuery, queryRef); const organization = data.organization; - const members: Member[] = []; + const users = organization.users?.edges.map((edge) => edge.node) || []; const { toast } = useToast(); const fileInputRef = useRef(null); const [isEditNameOpen, setIsEditNameOpen] = useState(false); + const [isInviteOpen, setIsInviteOpen] = useState(false); + const [inviteEmail, setInviteEmail] = useState(""); + const [inviteRole, setInviteRole] = useState("Member"); const [organizationName, setOrganizationName] = useState( organization.name || "" ); @@ -162,6 +168,19 @@ function SettingsPageContent({ reader.readAsDataURL(file); }; + const handleInviteMember = () => { + // This is a placeholder for the actual invite functionality + // In a real implementation, we would use a GraphQL mutation to invite the user + // For now, we just show a toast message and close the dialog + toast({ + title: "Invitation sent", + description: `An invitation has been sent to ${inviteEmail}`, + variant: "default", + }); + setIsInviteOpen(false); + setInviteEmail(""); + }; + return ( <>
@@ -245,56 +264,77 @@ function SettingsPageContent({ - - Workspace members - - Manage who has privileged access to your workspace and their - permissions. - + +
+ Workspace members + + Manage who has privileged access to your workspace and their + permissions. + +
+
- {members.map((member) => ( -
-
- - - {member.fullName.charAt(0)} - - -
- - {member.fullName} - + {users.length === 0 ? ( +
+
+ +
+

No members found

+

+ You haven't added any members to your workspace yet. +

+
+ ) : ( + users.map((user) => ( +
+
+ + + {user.fullName.charAt(0).toUpperCase()} + + +
+ + {user.fullName} + + + {user.email} + +
+
+
- {member.primaryEmailAddress} + Owner + + + + + + + Remove member + + +
-
- - {member.role} - - - - - - - Change role - - Remove member - - - -
-
- ))} + )) + )}
@@ -327,6 +367,48 @@ function SettingsPageContent({ + + + + + Invite Team Member + + Send an invitation to join your workspace. + + +
+
+ + setInviteEmail(e.target.value)} + placeholder="Enter email address" + /> +
+
+ + +
+
+ + + + +
+
); } diff --git a/apps/console/src/pages/__generated__/CreateOrganizationPageViewerQuery.graphql.ts b/apps/console/src/pages/__generated__/CreateOrganizationPageViewerQuery.graphql.ts index 967c30494..208f22160 100644 --- a/apps/console/src/pages/__generated__/CreateOrganizationPageViewerQuery.graphql.ts +++ b/apps/console/src/pages/__generated__/CreateOrganizationPageViewerQuery.graphql.ts @@ -1,5 +1,5 @@ /** - * @generated SignedSource<<91e1415ad0a7d8ee0ae2a8e212f42f19>> + * @generated SignedSource<<371bd1a7433b281f20dd8925915a7ff2>> * @lightSyntaxTransform * @nogrep */ @@ -25,7 +25,7 @@ var v0 = [ { "alias": null, "args": null, - "concreteType": "User", + "concreteType": "Viewer", "kind": "LinkedField", "name": "viewer", "plural": false, diff --git a/apps/console/src/pages/__generated__/OrganizationSelectionPageQuery.graphql.ts b/apps/console/src/pages/__generated__/OrganizationSelectionPageQuery.graphql.ts index 66ef66a16..d67cd011f 100644 --- a/apps/console/src/pages/__generated__/OrganizationSelectionPageQuery.graphql.ts +++ b/apps/console/src/pages/__generated__/OrganizationSelectionPageQuery.graphql.ts @@ -1,5 +1,5 @@ /** - * @generated SignedSource<> + * @generated SignedSource<<282a94b76fe5e17bd40b45ced6445ef3>> * @lightSyntaxTransform * @nogrep */ @@ -12,7 +12,6 @@ import { ConcreteRequest } from 'relay-runtime'; export type OrganizationSelectionPageQuery$variables = Record; export type OrganizationSelectionPageQuery$data = { readonly viewer: { - readonly id: string; readonly organizations: { readonly edges: ReadonlyArray<{ readonly node: { @@ -37,81 +36,80 @@ var v0 = { "name": "id", "storageKey": null }, -v1 = [ - { - "alias": null, - "args": null, - "concreteType": "User", - "kind": "LinkedField", - "name": "viewer", - "plural": false, - "selections": [ - (v0/*: any*/), - { - "alias": null, - "args": [ - { - "kind": "Literal", - "name": "first", - "value": 25 - } - ], - "concreteType": "OrganizationConnection", - "kind": "LinkedField", - "name": "organizations", - "plural": false, - "selections": [ - { - "alias": null, - "args": null, - "concreteType": "OrganizationEdge", - "kind": "LinkedField", - "name": "edges", - "plural": true, - "selections": [ - { - "alias": null, - "args": null, - "concreteType": "Organization", - "kind": "LinkedField", - "name": "node", - "plural": false, - "selections": [ - (v0/*: any*/), - { - "alias": null, - "args": null, - "kind": "ScalarField", - "name": "name", - "storageKey": null - }, - { - "alias": null, - "args": null, - "kind": "ScalarField", - "name": "logoUrl", - "storageKey": null - } - ], - "storageKey": null - } - ], - "storageKey": null - } - ], - "storageKey": "organizations(first:25)" - } - ], - "storageKey": null - } -]; +v1 = { + "alias": null, + "args": [ + { + "kind": "Literal", + "name": "first", + "value": 25 + } + ], + "concreteType": "OrganizationConnection", + "kind": "LinkedField", + "name": "organizations", + "plural": false, + "selections": [ + { + "alias": null, + "args": null, + "concreteType": "OrganizationEdge", + "kind": "LinkedField", + "name": "edges", + "plural": true, + "selections": [ + { + "alias": null, + "args": null, + "concreteType": "Organization", + "kind": "LinkedField", + "name": "node", + "plural": false, + "selections": [ + (v0/*: any*/), + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "name", + "storageKey": null + }, + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "logoUrl", + "storageKey": null + } + ], + "storageKey": null + } + ], + "storageKey": null + } + ], + "storageKey": "organizations(first:25)" +}; return { "fragment": { "argumentDefinitions": [], "kind": "Fragment", "metadata": null, "name": "OrganizationSelectionPageQuery", - "selections": (v1/*: any*/), + "selections": [ + { + "alias": null, + "args": null, + "concreteType": "Viewer", + "kind": "LinkedField", + "name": "viewer", + "plural": false, + "selections": [ + (v1/*: any*/) + ], + "storageKey": null + } + ], "type": "Query", "abstractKey": null }, @@ -120,19 +118,33 @@ return { "argumentDefinitions": [], "kind": "Operation", "name": "OrganizationSelectionPageQuery", - "selections": (v1/*: any*/) + "selections": [ + { + "alias": null, + "args": null, + "concreteType": "Viewer", + "kind": "LinkedField", + "name": "viewer", + "plural": false, + "selections": [ + (v1/*: any*/), + (v0/*: any*/) + ], + "storageKey": null + } + ] }, "params": { - "cacheID": "634e8f413a8b1f2685cfc1f4fd3c13cc", + "cacheID": "2938cb693a84c22366862de424a84d4b", "id": null, "metadata": {}, "name": "OrganizationSelectionPageQuery", "operationKind": "query", - "text": "query OrganizationSelectionPageQuery {\n viewer {\n id\n organizations(first: 25) {\n edges {\n node {\n id\n name\n logoUrl\n }\n }\n }\n }\n}\n" + "text": "query OrganizationSelectionPageQuery {\n viewer {\n organizations(first: 25) {\n edges {\n node {\n id\n name\n logoUrl\n }\n }\n }\n id\n }\n}\n" } }; })(); -(node as any).hash = "786037ac4c8d61f3b7cf6b4536aadbbb"; +(node as any).hash = "c41774fd198ab5f0f4b866d805099baf"; export default node; diff --git a/apps/console/src/pages/__generated__/SettingsPageQuery.graphql.ts b/apps/console/src/pages/__generated__/SettingsPageQuery.graphql.ts index 767ea647a..451ea27bf 100644 --- a/apps/console/src/pages/__generated__/SettingsPageQuery.graphql.ts +++ b/apps/console/src/pages/__generated__/SettingsPageQuery.graphql.ts @@ -1,5 +1,5 @@ /** - * @generated SignedSource<> + * @generated SignedSource<<7fcee268b2974653bc3734b49b4082a4>> * @lightSyntaxTransform * @nogrep */ @@ -17,6 +17,16 @@ export type SettingsPageQuery$data = { readonly id: string; readonly logoUrl?: string | null | undefined; readonly name?: string; + readonly users?: { + readonly edges: ReadonlyArray<{ + readonly node: { + readonly createdAt: string; + readonly email: string; + readonly fullName: string; + readonly id: string; + }; + }>; + }; }; }; export type SettingsPageQuery = { @@ -62,6 +72,67 @@ v3 = { "kind": "ScalarField", "name": "logoUrl", "storageKey": null + }, + { + "alias": null, + "args": [ + { + "kind": "Literal", + "name": "first", + "value": 100 + } + ], + "concreteType": "UserConnection", + "kind": "LinkedField", + "name": "users", + "plural": false, + "selections": [ + { + "alias": null, + "args": null, + "concreteType": "UserEdge", + "kind": "LinkedField", + "name": "edges", + "plural": true, + "selections": [ + { + "alias": null, + "args": null, + "concreteType": "User", + "kind": "LinkedField", + "name": "node", + "plural": false, + "selections": [ + (v2/*: any*/), + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "fullName", + "storageKey": null + }, + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "email", + "storageKey": null + }, + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "createdAt", + "storageKey": null + } + ], + "storageKey": null + } + ], + "storageKey": null + } + ], + "storageKey": "users(first:100)" } ], "type": "Organization", @@ -120,16 +191,16 @@ return { ] }, "params": { - "cacheID": "1ff42392cdd403c92101dc9c5fb7ceda", + "cacheID": "de417aa8583ad2d1cc7ef18223a4dc2d", "id": null, "metadata": {}, "name": "SettingsPageQuery", "operationKind": "query", - "text": "query SettingsPageQuery(\n $organizationID: ID!\n) {\n organization: node(id: $organizationID) {\n __typename\n id\n ... on Organization {\n name\n logoUrl\n }\n }\n}\n" + "text": "query SettingsPageQuery(\n $organizationID: ID!\n) {\n organization: node(id: $organizationID) {\n __typename\n id\n ... on Organization {\n name\n logoUrl\n users(first: 100) {\n edges {\n node {\n id\n fullName\n email\n createdAt\n }\n }\n }\n }\n }\n}\n" } }; })(); -(node as any).hash = "60cdb8a35f7e5f58536101243b57e57c"; +(node as any).hash = "1d9482689dd9f305ca7a45fdf4ab088c"; export default node; diff --git a/pkg/coredata/user.go b/pkg/coredata/user.go index 528da9a56..0e5cf6ed3 100644 --- a/pkg/coredata/user.go +++ b/pkg/coredata/user.go @@ -18,6 +18,7 @@ import ( "context" "errors" "fmt" + "maps" "strings" "time" @@ -39,6 +40,8 @@ type ( UpdatedAt time.Time `db:"updated_at"` } + Users []*User + ErrUserNotFound struct { Identifier string } @@ -60,6 +63,50 @@ func (u User) CursorKey() page.CursorKey { return page.NewCursorKey(u.ID, u.CreatedAt) } +func (u *Users) LoadByOrganizationID( + ctx context.Context, + conn pg.Conn, + organizationID gid.GID, + cursor *page.Cursor, +) error { + q := ` +SELECT + id, + email_address, + hashed_password, + email_address_verified, + fullname, + created_at, + updated_at +FROM + users +WHERE + id IN ( + SELECT user_id FROM users_organizations WHERE organization_id = @organization_id + ) + AND %s +` + + q = fmt.Sprintf(q, cursor.SQLFragment()) + + args := pgx.StrictNamedArgs{"organization_id": organizationID} + maps.Copy(args, cursor.SQLArguments()) + + rows, err := conn.Query(ctx, q, args) + if err != nil { + return fmt.Errorf("cannot query users: %w", err) + } + + users, err := pgx.CollectRows(rows, pgx.RowToAddrOfStructByName[User]) + if err != nil { + return fmt.Errorf("cannot collect users: %w", err) + } + + *u = users + + return nil +} + func (u *User) LoadByEmail( ctx context.Context, conn pg.Conn, diff --git a/pkg/server/api/console/v1/schema.graphql b/pkg/server/api/console/v1/schema.graphql index a15620f48..613a2a160 100644 --- a/pkg/server/api/console/v1/schema.graphql +++ b/pkg/server/api/console/v1/schema.graphql @@ -109,6 +109,13 @@ type Organization implements Node { name: String! logoUrl: String @goField(forceResolver: true) + users( + first: Int + after: CursorKey + last: Int + before: CursorKey + ): UserConnection! @goField(forceResolver: true) + frameworks( first: Int after: CursorKey @@ -298,18 +305,20 @@ type Evidence implements Node { updatedAt: Datetime! } +type UserConnection { + edges: [UserEdge!]! + pageInfo: PageInfo! +} + +type UserEdge { + cursor: CursorKey! + node: User! +} + type User implements Node { id: ID! fullName: String! email: String! - - organizations( - first: Int - after: CursorKey - last: Int - before: CursorKey - ): OrganizationConnection! @goField(forceResolver: true) - createdAt: Datetime! updatedAt: Datetime! } @@ -321,7 +330,20 @@ type Session { type Query { node(id: ID!): Node! - viewer: User! + viewer: Viewer! +} + +type Viewer { + id: ID! + + user: User! + + organizations( + first: Int + after: CursorKey + last: Int + before: CursorKey + ): OrganizationConnection! @goField(forceResolver: true) } type Mutation { diff --git a/pkg/server/api/console/v1/schema/schema.go b/pkg/server/api/console/v1/schema/schema.go index 65b2ea48f..ac20d58b8 100644 --- a/pkg/server/api/console/v1/schema/schema.go +++ b/pkg/server/api/console/v1/schema/schema.go @@ -50,7 +50,7 @@ type ResolverRoot interface { Policy() PolicyResolver Query() QueryResolver Task() TaskResolver - User() UserResolver + Viewer() ViewerResolver } type DirectiveRoot struct { @@ -222,6 +222,7 @@ type ComplexityRoot struct { Peoples func(childComplexity int, first *int, after *page.CursorKey, last *int, before *page.CursorKey) int Policies func(childComplexity int, first *int, after *page.CursorKey, last *int, before *page.CursorKey) int UpdatedAt func(childComplexity int) int + Users func(childComplexity int, first *int, after *page.CursorKey, last *int, before *page.CursorKey) int Vendors func(childComplexity int, first *int, after *page.CursorKey, last *int, before *page.CursorKey) int } @@ -355,12 +356,21 @@ type ComplexityRoot struct { } User struct { - CreatedAt func(childComplexity int) int - Email func(childComplexity int) int - FullName func(childComplexity int) int - ID func(childComplexity int) int - Organizations func(childComplexity int, first *int, after *page.CursorKey, last *int, before *page.CursorKey) int - UpdatedAt func(childComplexity int) int + CreatedAt func(childComplexity int) int + Email func(childComplexity int) int + FullName func(childComplexity int) int + ID func(childComplexity int) int + UpdatedAt func(childComplexity int) int + } + + UserConnection struct { + Edges func(childComplexity int) int + PageInfo func(childComplexity int) int + } + + UserEdge struct { + Cursor func(childComplexity int) int + Node func(childComplexity int) int } Vendor struct { @@ -388,6 +398,12 @@ type ComplexityRoot struct { Cursor func(childComplexity int) int Node func(childComplexity int) int } + + Viewer struct { + ID func(childComplexity int) int + Organizations func(childComplexity int, first *int, after *page.CursorKey, last *int, before *page.CursorKey) int + User func(childComplexity int) int + } } type ControlResolver interface { @@ -428,6 +444,7 @@ type MutationResolver interface { } type OrganizationResolver interface { LogoURL(ctx context.Context, obj *types.Organization) (*string, error) + Users(ctx context.Context, obj *types.Organization, first *int, after *page.CursorKey, last *int, before *page.CursorKey) (*types.UserConnection, error) Frameworks(ctx context.Context, obj *types.Organization, first *int, after *page.CursorKey, last *int, before *page.CursorKey) (*types.FrameworkConnection, error) Vendors(ctx context.Context, obj *types.Organization, first *int, after *page.CursorKey, last *int, before *page.CursorKey) (*types.VendorConnection, error) Peoples(ctx context.Context, obj *types.Organization, first *int, after *page.CursorKey, last *int, before *page.CursorKey) (*types.PeopleConnection, error) @@ -438,14 +455,14 @@ type PolicyResolver interface { } type QueryResolver interface { Node(ctx context.Context, id gid.GID) (types.Node, error) - Viewer(ctx context.Context) (*types.User, error) + Viewer(ctx context.Context) (*types.Viewer, error) } type TaskResolver interface { AssignedTo(ctx context.Context, obj *types.Task) (*types.People, error) Evidences(ctx context.Context, obj *types.Task, first *int, after *page.CursorKey, last *int, before *page.CursorKey) (*types.EvidenceConnection, error) } -type UserResolver interface { - Organizations(ctx context.Context, obj *types.User, first *int, after *page.CursorKey, last *int, before *page.CursorKey) (*types.OrganizationConnection, error) +type ViewerResolver interface { + Organizations(ctx context.Context, obj *types.Viewer, first *int, after *page.CursorKey, last *int, before *page.CursorKey) (*types.OrganizationConnection, error) } type executableSchema struct { @@ -1219,6 +1236,18 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in return e.complexity.Organization.UpdatedAt(childComplexity), true + case "Organization.users": + if e.complexity.Organization.Users == nil { + break + } + + args, err := ec.field_Organization_users_args(context.TODO(), rawArgs) + if err != nil { + return 0, false + } + + return e.complexity.Organization.Users(childComplexity, args["first"].(*int), args["after"].(*page.CursorKey), args["last"].(*int), args["before"].(*page.CursorKey)), true + case "Organization.vendors": if e.complexity.Organization.Vendors == nil { break @@ -1689,18 +1718,6 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in return e.complexity.User.ID(childComplexity), true - case "User.organizations": - if e.complexity.User.Organizations == nil { - break - } - - args, err := ec.field_User_organizations_args(context.TODO(), rawArgs) - if err != nil { - return 0, false - } - - return e.complexity.User.Organizations(childComplexity, args["first"].(*int), args["after"].(*page.CursorKey), args["last"].(*int), args["before"].(*page.CursorKey)), true - case "User.updatedAt": if e.complexity.User.UpdatedAt == nil { break @@ -1708,6 +1725,34 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in return e.complexity.User.UpdatedAt(childComplexity), true + case "UserConnection.edges": + if e.complexity.UserConnection.Edges == nil { + break + } + + return e.complexity.UserConnection.Edges(childComplexity), true + + case "UserConnection.pageInfo": + if e.complexity.UserConnection.PageInfo == nil { + break + } + + return e.complexity.UserConnection.PageInfo(childComplexity), true + + case "UserEdge.cursor": + if e.complexity.UserEdge.Cursor == nil { + break + } + + return e.complexity.UserEdge.Cursor(childComplexity), true + + case "UserEdge.node": + if e.complexity.UserEdge.Node == nil { + break + } + + return e.complexity.UserEdge.Node(childComplexity), true + case "Vendor.createdAt": if e.complexity.Vendor.CreatedAt == nil { break @@ -1827,6 +1872,32 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in return e.complexity.VendorEdge.Node(childComplexity), true + case "Viewer.id": + if e.complexity.Viewer.ID == nil { + break + } + + return e.complexity.Viewer.ID(childComplexity), true + + case "Viewer.organizations": + if e.complexity.Viewer.Organizations == nil { + break + } + + args, err := ec.field_Viewer_organizations_args(context.TODO(), rawArgs) + if err != nil { + return 0, false + } + + return e.complexity.Viewer.Organizations(childComplexity, args["first"].(*int), args["after"].(*page.CursorKey), args["last"].(*int), args["before"].(*page.CursorKey)), true + + case "Viewer.user": + if e.complexity.Viewer.User == nil { + break + } + + return e.complexity.Viewer.User(childComplexity), true + } return 0, false } @@ -2068,6 +2139,13 @@ type Organization implements Node { name: String! logoUrl: String @goField(forceResolver: true) + users( + first: Int + after: CursorKey + last: Int + before: CursorKey + ): UserConnection! @goField(forceResolver: true) + frameworks( first: Int after: CursorKey @@ -2257,18 +2335,20 @@ type Evidence implements Node { updatedAt: Datetime! } +type UserConnection { + edges: [UserEdge!]! + pageInfo: PageInfo! +} + +type UserEdge { + cursor: CursorKey! + node: User! +} + type User implements Node { id: ID! fullName: String! email: String! - - organizations( - first: Int - after: CursorKey - last: Int - before: CursorKey - ): OrganizationConnection! @goField(forceResolver: true) - createdAt: Datetime! updatedAt: Datetime! } @@ -2280,7 +2360,20 @@ type Session { type Query { node(id: ID!): Node! - viewer: User! + viewer: Viewer! +} + +type Viewer { + id: ID! + + user: User! + + organizations( + first: Int + after: CursorKey + last: Int + before: CursorKey + ): OrganizationConnection! @goField(forceResolver: true) } type Mutation { @@ -3617,6 +3710,83 @@ func (ec *executionContext) field_Organization_policies_argsBefore( return zeroVal, nil } +func (ec *executionContext) field_Organization_users_args(ctx context.Context, rawArgs map[string]any) (map[string]any, error) { + var err error + args := map[string]any{} + arg0, err := ec.field_Organization_users_argsFirst(ctx, rawArgs) + if err != nil { + return nil, err + } + args["first"] = arg0 + arg1, err := ec.field_Organization_users_argsAfter(ctx, rawArgs) + if err != nil { + return nil, err + } + args["after"] = arg1 + arg2, err := ec.field_Organization_users_argsLast(ctx, rawArgs) + if err != nil { + return nil, err + } + args["last"] = arg2 + arg3, err := ec.field_Organization_users_argsBefore(ctx, rawArgs) + if err != nil { + return nil, err + } + args["before"] = arg3 + return args, nil +} +func (ec *executionContext) field_Organization_users_argsFirst( + ctx context.Context, + rawArgs map[string]any, +) (*int, error) { + ctx = graphql.WithPathContext(ctx, graphql.NewPathWithField("first")) + if tmp, ok := rawArgs["first"]; ok { + return ec.unmarshalOInt2ᚖint(ctx, tmp) + } + + var zeroVal *int + return zeroVal, nil +} + +func (ec *executionContext) field_Organization_users_argsAfter( + ctx context.Context, + rawArgs map[string]any, +) (*page.CursorKey, error) { + ctx = graphql.WithPathContext(ctx, graphql.NewPathWithField("after")) + if tmp, ok := rawArgs["after"]; ok { + return ec.unmarshalOCursorKey2ᚖgithubᚗcomᚋgetproboᚋproboᚋpkgᚋpageᚐCursorKey(ctx, tmp) + } + + var zeroVal *page.CursorKey + return zeroVal, nil +} + +func (ec *executionContext) field_Organization_users_argsLast( + ctx context.Context, + rawArgs map[string]any, +) (*int, error) { + ctx = graphql.WithPathContext(ctx, graphql.NewPathWithField("last")) + if tmp, ok := rawArgs["last"]; ok { + return ec.unmarshalOInt2ᚖint(ctx, tmp) + } + + var zeroVal *int + return zeroVal, nil +} + +func (ec *executionContext) field_Organization_users_argsBefore( + ctx context.Context, + rawArgs map[string]any, +) (*page.CursorKey, error) { + ctx = graphql.WithPathContext(ctx, graphql.NewPathWithField("before")) + if tmp, ok := rawArgs["before"]; ok { + return ec.unmarshalOCursorKey2ᚖgithubᚗcomᚋgetproboᚋproboᚋpkgᚋpageᚐCursorKey(ctx, tmp) + } + + var zeroVal *page.CursorKey + return zeroVal, nil +} + func (ec *executionContext) field_Organization_vendors_args(ctx context.Context, rawArgs map[string]any) (map[string]any, error) { var err error args := map[string]any{} @@ -3817,32 +3987,32 @@ func (ec *executionContext) field_Task_evidences_argsBefore( return zeroVal, nil } -func (ec *executionContext) field_User_organizations_args(ctx context.Context, rawArgs map[string]any) (map[string]any, error) { +func (ec *executionContext) field_Viewer_organizations_args(ctx context.Context, rawArgs map[string]any) (map[string]any, error) { var err error args := map[string]any{} - arg0, err := ec.field_User_organizations_argsFirst(ctx, rawArgs) + arg0, err := ec.field_Viewer_organizations_argsFirst(ctx, rawArgs) if err != nil { return nil, err } args["first"] = arg0 - arg1, err := ec.field_User_organizations_argsAfter(ctx, rawArgs) + arg1, err := ec.field_Viewer_organizations_argsAfter(ctx, rawArgs) if err != nil { return nil, err } args["after"] = arg1 - arg2, err := ec.field_User_organizations_argsLast(ctx, rawArgs) + arg2, err := ec.field_Viewer_organizations_argsLast(ctx, rawArgs) if err != nil { return nil, err } args["last"] = arg2 - arg3, err := ec.field_User_organizations_argsBefore(ctx, rawArgs) + arg3, err := ec.field_Viewer_organizations_argsBefore(ctx, rawArgs) if err != nil { return nil, err } args["before"] = arg3 return args, nil } -func (ec *executionContext) field_User_organizations_argsFirst( +func (ec *executionContext) field_Viewer_organizations_argsFirst( ctx context.Context, rawArgs map[string]any, ) (*int, error) { @@ -3855,7 +4025,7 @@ func (ec *executionContext) field_User_organizations_argsFirst( return zeroVal, nil } -func (ec *executionContext) field_User_organizations_argsAfter( +func (ec *executionContext) field_Viewer_organizations_argsAfter( ctx context.Context, rawArgs map[string]any, ) (*page.CursorKey, error) { @@ -3868,7 +4038,7 @@ func (ec *executionContext) field_User_organizations_argsAfter( return zeroVal, nil } -func (ec *executionContext) field_User_organizations_argsLast( +func (ec *executionContext) field_Viewer_organizations_argsLast( ctx context.Context, rawArgs map[string]any, ) (*int, error) { @@ -3881,7 +4051,7 @@ func (ec *executionContext) field_User_organizations_argsLast( return zeroVal, nil } -func (ec *executionContext) field_User_organizations_argsBefore( +func (ec *executionContext) field_Viewer_organizations_argsBefore( ctx context.Context, rawArgs map[string]any, ) (*page.CursorKey, error) { @@ -7490,6 +7660,55 @@ func (ec *executionContext) fieldContext_Organization_logoUrl(_ context.Context, return fc, nil } +func (ec *executionContext) _Organization_users(ctx context.Context, field graphql.CollectedField, obj *types.Organization) (ret graphql.Marshaler) { + fc, err := ec.fieldContext_Organization_users(ctx, field) + if err != nil { + return graphql.Null + } + ctx = graphql.WithFieldContext(ctx, fc) + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (any, error) { + ctx = rctx // use context from middleware stack in children + return ec.resolvers.Organization().Users(rctx, obj, fc.Args["first"].(*int), fc.Args["after"].(*page.CursorKey), fc.Args["last"].(*int), fc.Args["before"].(*page.CursorKey)) + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + if !graphql.HasFieldError(ctx, fc) { + ec.Errorf(ctx, "must not be null") + } + return graphql.Null + } + res := resTmp.(*types.UserConnection) + fc.Result = res + return ec.marshalNUserConnection2ᚖgithubᚗcomᚋgetproboᚋproboᚋpkgᚋserverᚋapiᚋconsoleᚋv1ᚋtypesᚐUserConnection(ctx, field.Selections, res) +} + +func (ec *executionContext) fieldContext_Organization_users(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "Organization", + Field: field, + IsMethod: true, + IsResolver: true, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + switch field.Name { + case "edges": + return ec.fieldContext_UserConnection_edges(ctx, field) + case "pageInfo": + return ec.fieldContext_UserConnection_pageInfo(ctx, field) + } + return nil, fmt.Errorf("no field named %q was found under type UserConnection", field.Name) + }, + } + ctx = graphql.WithFieldContext(ctx, fc) + if fc.Args, err = ec.field_Organization_users_args(ctx, field.ArgumentMap(ec.Variables)); err != nil { + ec.Error(ctx, err) + return fc, err + } + return fc, nil +} + func (ec *executionContext) _Organization_frameworks(ctx context.Context, field graphql.CollectedField, obj *types.Organization) (ret graphql.Marshaler) { fc, err := ec.fieldContext_Organization_frameworks(ctx, field) if err != nil { @@ -7931,6 +8150,8 @@ func (ec *executionContext) fieldContext_OrganizationEdge_node(_ context.Context return ec.fieldContext_Organization_name(ctx, field) case "logoUrl": return ec.fieldContext_Organization_logoUrl(ctx, field) + case "users": + return ec.fieldContext_Organization_users(ctx, field) case "frameworks": return ec.fieldContext_Organization_frameworks(ctx, field) case "vendors": @@ -9194,9 +9415,9 @@ func (ec *executionContext) _Query_viewer(ctx context.Context, field graphql.Col } return graphql.Null } - res := resTmp.(*types.User) + res := resTmp.(*types.Viewer) fc.Result = res - return ec.marshalNUser2ᚖgithubᚗcomᚋgetproboᚋproboᚋpkgᚋserverᚋapiᚋconsoleᚋv1ᚋtypesᚐUser(ctx, field.Selections, res) + return ec.marshalNViewer2ᚖgithubᚗcomᚋgetproboᚋproboᚋpkgᚋserverᚋapiᚋconsoleᚋv1ᚋtypesᚐViewer(ctx, field.Selections, res) } func (ec *executionContext) fieldContext_Query_viewer(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { @@ -9208,19 +9429,13 @@ func (ec *executionContext) fieldContext_Query_viewer(_ context.Context, field g Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { switch field.Name { case "id": - return ec.fieldContext_User_id(ctx, field) - case "fullName": - return ec.fieldContext_User_fullName(ctx, field) - case "email": - return ec.fieldContext_User_email(ctx, field) + return ec.fieldContext_Viewer_id(ctx, field) + case "user": + return ec.fieldContext_Viewer_user(ctx, field) case "organizations": - return ec.fieldContext_User_organizations(ctx, field) - case "createdAt": - return ec.fieldContext_User_createdAt(ctx, field) - case "updatedAt": - return ec.fieldContext_User_updatedAt(ctx, field) + return ec.fieldContext_Viewer_organizations(ctx, field) } - return nil, fmt.Errorf("no field named %q was found under type User", field.Name) + return nil, fmt.Errorf("no field named %q was found under type Viewer", field.Name) }, } return fc, nil @@ -10224,6 +10439,8 @@ func (ec *executionContext) fieldContext_UpdateOrganizationPayload_organization( return ec.fieldContext_Organization_name(ctx, field) case "logoUrl": return ec.fieldContext_Organization_logoUrl(ctx, field) + case "users": + return ec.fieldContext_Organization_users(ctx, field) case "frameworks": return ec.fieldContext_Organization_frameworks(ctx, field) case "vendors": @@ -10641,55 +10858,6 @@ func (ec *executionContext) fieldContext_User_email(_ context.Context, field gra return fc, nil } -func (ec *executionContext) _User_organizations(ctx context.Context, field graphql.CollectedField, obj *types.User) (ret graphql.Marshaler) { - fc, err := ec.fieldContext_User_organizations(ctx, field) - if err != nil { - return graphql.Null - } - ctx = graphql.WithFieldContext(ctx, fc) - resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (any, error) { - ctx = rctx // use context from middleware stack in children - return ec.resolvers.User().Organizations(rctx, obj, fc.Args["first"].(*int), fc.Args["after"].(*page.CursorKey), fc.Args["last"].(*int), fc.Args["before"].(*page.CursorKey)) - }) - if err != nil { - ec.Error(ctx, err) - return graphql.Null - } - if resTmp == nil { - if !graphql.HasFieldError(ctx, fc) { - ec.Errorf(ctx, "must not be null") - } - return graphql.Null - } - res := resTmp.(*types.OrganizationConnection) - fc.Result = res - return ec.marshalNOrganizationConnection2ᚖgithubᚗcomᚋgetproboᚋproboᚋpkgᚋserverᚋapiᚋconsoleᚋv1ᚋtypesᚐOrganizationConnection(ctx, field.Selections, res) -} - -func (ec *executionContext) fieldContext_User_organizations(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { - fc = &graphql.FieldContext{ - Object: "User", - Field: field, - IsMethod: true, - IsResolver: true, - Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { - switch field.Name { - case "edges": - return ec.fieldContext_OrganizationConnection_edges(ctx, field) - case "pageInfo": - return ec.fieldContext_OrganizationConnection_pageInfo(ctx, field) - } - return nil, fmt.Errorf("no field named %q was found under type OrganizationConnection", field.Name) - }, - } - ctx = graphql.WithFieldContext(ctx, fc) - if fc.Args, err = ec.field_User_organizations_args(ctx, field.ArgumentMap(ec.Variables)); err != nil { - ec.Error(ctx, err) - return fc, err - } - return fc, nil -} - func (ec *executionContext) _User_createdAt(ctx context.Context, field graphql.CollectedField, obj *types.User) (ret graphql.Marshaler) { fc, err := ec.fieldContext_User_createdAt(ctx, field) if err != nil { @@ -10766,6 +10934,186 @@ func (ec *executionContext) fieldContext_User_updatedAt(_ context.Context, field return fc, nil } +func (ec *executionContext) _UserConnection_edges(ctx context.Context, field graphql.CollectedField, obj *types.UserConnection) (ret graphql.Marshaler) { + fc, err := ec.fieldContext_UserConnection_edges(ctx, field) + if err != nil { + return graphql.Null + } + ctx = graphql.WithFieldContext(ctx, fc) + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (any, error) { + ctx = rctx // use context from middleware stack in children + return obj.Edges, nil + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + if !graphql.HasFieldError(ctx, fc) { + ec.Errorf(ctx, "must not be null") + } + return graphql.Null + } + res := resTmp.([]*types.UserEdge) + fc.Result = res + return ec.marshalNUserEdge2ᚕᚖgithubᚗcomᚋgetproboᚋproboᚋpkgᚋserverᚋapiᚋconsoleᚋv1ᚋtypesᚐUserEdgeᚄ(ctx, field.Selections, res) +} + +func (ec *executionContext) fieldContext_UserConnection_edges(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "UserConnection", + Field: field, + IsMethod: false, + IsResolver: false, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + switch field.Name { + case "cursor": + return ec.fieldContext_UserEdge_cursor(ctx, field) + case "node": + return ec.fieldContext_UserEdge_node(ctx, field) + } + return nil, fmt.Errorf("no field named %q was found under type UserEdge", field.Name) + }, + } + return fc, nil +} + +func (ec *executionContext) _UserConnection_pageInfo(ctx context.Context, field graphql.CollectedField, obj *types.UserConnection) (ret graphql.Marshaler) { + fc, err := ec.fieldContext_UserConnection_pageInfo(ctx, field) + if err != nil { + return graphql.Null + } + ctx = graphql.WithFieldContext(ctx, fc) + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (any, error) { + ctx = rctx // use context from middleware stack in children + return obj.PageInfo, nil + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + if !graphql.HasFieldError(ctx, fc) { + ec.Errorf(ctx, "must not be null") + } + return graphql.Null + } + res := resTmp.(*types.PageInfo) + fc.Result = res + return ec.marshalNPageInfo2ᚖgithubᚗcomᚋgetproboᚋproboᚋpkgᚋserverᚋapiᚋconsoleᚋv1ᚋtypesᚐPageInfo(ctx, field.Selections, res) +} + +func (ec *executionContext) fieldContext_UserConnection_pageInfo(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "UserConnection", + Field: field, + IsMethod: false, + IsResolver: false, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + switch field.Name { + case "hasNextPage": + return ec.fieldContext_PageInfo_hasNextPage(ctx, field) + case "hasPreviousPage": + return ec.fieldContext_PageInfo_hasPreviousPage(ctx, field) + case "startCursor": + return ec.fieldContext_PageInfo_startCursor(ctx, field) + case "endCursor": + return ec.fieldContext_PageInfo_endCursor(ctx, field) + } + return nil, fmt.Errorf("no field named %q was found under type PageInfo", field.Name) + }, + } + return fc, nil +} + +func (ec *executionContext) _UserEdge_cursor(ctx context.Context, field graphql.CollectedField, obj *types.UserEdge) (ret graphql.Marshaler) { + fc, err := ec.fieldContext_UserEdge_cursor(ctx, field) + if err != nil { + return graphql.Null + } + ctx = graphql.WithFieldContext(ctx, fc) + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (any, error) { + ctx = rctx // use context from middleware stack in children + return obj.Cursor, nil + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + if !graphql.HasFieldError(ctx, fc) { + ec.Errorf(ctx, "must not be null") + } + return graphql.Null + } + res := resTmp.(page.CursorKey) + fc.Result = res + return ec.marshalNCursorKey2githubᚗcomᚋgetproboᚋproboᚋpkgᚋpageᚐCursorKey(ctx, field.Selections, res) +} + +func (ec *executionContext) fieldContext_UserEdge_cursor(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "UserEdge", + Field: field, + IsMethod: false, + IsResolver: false, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + return nil, errors.New("field of type CursorKey does not have child fields") + }, + } + return fc, nil +} + +func (ec *executionContext) _UserEdge_node(ctx context.Context, field graphql.CollectedField, obj *types.UserEdge) (ret graphql.Marshaler) { + fc, err := ec.fieldContext_UserEdge_node(ctx, field) + if err != nil { + return graphql.Null + } + ctx = graphql.WithFieldContext(ctx, fc) + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (any, error) { + ctx = rctx // use context from middleware stack in children + return obj.Node, nil + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + if !graphql.HasFieldError(ctx, fc) { + ec.Errorf(ctx, "must not be null") + } + return graphql.Null + } + res := resTmp.(*types.User) + fc.Result = res + return ec.marshalNUser2ᚖgithubᚗcomᚋgetproboᚋproboᚋpkgᚋserverᚋapiᚋconsoleᚋv1ᚋtypesᚐUser(ctx, field.Selections, res) +} + +func (ec *executionContext) fieldContext_UserEdge_node(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "UserEdge", + Field: field, + IsMethod: false, + IsResolver: false, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + switch field.Name { + case "id": + return ec.fieldContext_User_id(ctx, field) + case "fullName": + return ec.fieldContext_User_fullName(ctx, field) + case "email": + return ec.fieldContext_User_email(ctx, field) + case "createdAt": + return ec.fieldContext_User_createdAt(ctx, field) + case "updatedAt": + return ec.fieldContext_User_updatedAt(ctx, field) + } + return nil, fmt.Errorf("no field named %q was found under type User", field.Name) + }, + } + return fc, nil +} + func (ec *executionContext) _Vendor_id(ctx context.Context, field graphql.CollectedField, obj *types.Vendor) (ret graphql.Marshaler) { fc, err := ec.fieldContext_Vendor_id(ctx, field) if err != nil { @@ -11444,6 +11792,143 @@ func (ec *executionContext) fieldContext_VendorEdge_node(_ context.Context, fiel return fc, nil } +func (ec *executionContext) _Viewer_id(ctx context.Context, field graphql.CollectedField, obj *types.Viewer) (ret graphql.Marshaler) { + fc, err := ec.fieldContext_Viewer_id(ctx, field) + if err != nil { + return graphql.Null + } + ctx = graphql.WithFieldContext(ctx, fc) + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (any, error) { + ctx = rctx // use context from middleware stack in children + return obj.ID, nil + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + if !graphql.HasFieldError(ctx, fc) { + ec.Errorf(ctx, "must not be null") + } + return graphql.Null + } + res := resTmp.(gid.GID) + fc.Result = res + return ec.marshalNID2githubᚗcomᚋgetproboᚋproboᚋpkgᚋgidᚐGID(ctx, field.Selections, res) +} + +func (ec *executionContext) fieldContext_Viewer_id(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "Viewer", + Field: field, + IsMethod: false, + IsResolver: false, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + return nil, errors.New("field of type ID does not have child fields") + }, + } + return fc, nil +} + +func (ec *executionContext) _Viewer_user(ctx context.Context, field graphql.CollectedField, obj *types.Viewer) (ret graphql.Marshaler) { + fc, err := ec.fieldContext_Viewer_user(ctx, field) + if err != nil { + return graphql.Null + } + ctx = graphql.WithFieldContext(ctx, fc) + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (any, error) { + ctx = rctx // use context from middleware stack in children + return obj.User, nil + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + if !graphql.HasFieldError(ctx, fc) { + ec.Errorf(ctx, "must not be null") + } + return graphql.Null + } + res := resTmp.(*types.User) + fc.Result = res + return ec.marshalNUser2ᚖgithubᚗcomᚋgetproboᚋproboᚋpkgᚋserverᚋapiᚋconsoleᚋv1ᚋtypesᚐUser(ctx, field.Selections, res) +} + +func (ec *executionContext) fieldContext_Viewer_user(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "Viewer", + Field: field, + IsMethod: false, + IsResolver: false, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + switch field.Name { + case "id": + return ec.fieldContext_User_id(ctx, field) + case "fullName": + return ec.fieldContext_User_fullName(ctx, field) + case "email": + return ec.fieldContext_User_email(ctx, field) + case "createdAt": + return ec.fieldContext_User_createdAt(ctx, field) + case "updatedAt": + return ec.fieldContext_User_updatedAt(ctx, field) + } + return nil, fmt.Errorf("no field named %q was found under type User", field.Name) + }, + } + return fc, nil +} + +func (ec *executionContext) _Viewer_organizations(ctx context.Context, field graphql.CollectedField, obj *types.Viewer) (ret graphql.Marshaler) { + fc, err := ec.fieldContext_Viewer_organizations(ctx, field) + if err != nil { + return graphql.Null + } + ctx = graphql.WithFieldContext(ctx, fc) + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (any, error) { + ctx = rctx // use context from middleware stack in children + return ec.resolvers.Viewer().Organizations(rctx, obj, fc.Args["first"].(*int), fc.Args["after"].(*page.CursorKey), fc.Args["last"].(*int), fc.Args["before"].(*page.CursorKey)) + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + if !graphql.HasFieldError(ctx, fc) { + ec.Errorf(ctx, "must not be null") + } + return graphql.Null + } + res := resTmp.(*types.OrganizationConnection) + fc.Result = res + return ec.marshalNOrganizationConnection2ᚖgithubᚗcomᚋgetproboᚋproboᚋpkgᚋserverᚋapiᚋconsoleᚋv1ᚋtypesᚐOrganizationConnection(ctx, field.Selections, res) +} + +func (ec *executionContext) fieldContext_Viewer_organizations(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "Viewer", + Field: field, + IsMethod: true, + IsResolver: true, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + switch field.Name { + case "edges": + return ec.fieldContext_OrganizationConnection_edges(ctx, field) + case "pageInfo": + return ec.fieldContext_OrganizationConnection_pageInfo(ctx, field) + } + return nil, fmt.Errorf("no field named %q was found under type OrganizationConnection", field.Name) + }, + } + ctx = graphql.WithFieldContext(ctx, fc) + if fc.Args, err = ec.field_Viewer_organizations_args(ctx, field.ArgumentMap(ec.Variables)); err != nil { + ec.Error(ctx, err) + return fc, err + } + return fc, nil +} + func (ec *executionContext) ___Directive_name(ctx context.Context, field graphql.CollectedField, obj *introspection.Directive) (ret graphql.Marshaler) { fc, err := ec.fieldContext___Directive_name(ctx, field) if err != nil { @@ -15839,6 +16324,37 @@ func (ec *executionContext) _Organization(ctx context.Context, sel ast.Selection continue } + out.Concurrently(i, func(ctx context.Context) graphql.Marshaler { return innerFunc(ctx, out) }) + case "users": + field := field + + innerFunc := func(ctx context.Context, fs *graphql.FieldSet) (res graphql.Marshaler) { + res = ec._Organization_users(ctx, field, obj) + if res == graphql.Null { + atomic.AddUint32(&fs.Invalids, 1) + } + return res + } + + if field.Deferrable != nil { + dfs, ok := deferred[field.Deferrable.Label] + di := 0 + if ok { + dfs.AddField(field) + di = len(dfs.Values) - 1 + } else { + dfs = graphql.NewFieldSet([]graphql.CollectedField{field}) + deferred[field.Deferrable.Label] = dfs + } + dfs.Concurrently(di, func(ctx context.Context) graphql.Marshaler { + return innerFunc(ctx, dfs) + }) + + // don't run the out.Concurrently() call below + out.Values[i] = graphql.Null + continue + } + out.Concurrently(i, func(ctx context.Context) graphql.Marshaler { return innerFunc(ctx, out) }) case "frameworks": field := field @@ -17199,58 +17715,115 @@ func (ec *executionContext) _User(ctx context.Context, sel ast.SelectionSet, obj case "id": out.Values[i] = ec._User_id(ctx, field, obj) if out.Values[i] == graphql.Null { - atomic.AddUint32(&out.Invalids, 1) + out.Invalids++ } case "fullName": out.Values[i] = ec._User_fullName(ctx, field, obj) if out.Values[i] == graphql.Null { - atomic.AddUint32(&out.Invalids, 1) + out.Invalids++ } case "email": out.Values[i] = ec._User_email(ctx, field, obj) if out.Values[i] == graphql.Null { - atomic.AddUint32(&out.Invalids, 1) + out.Invalids++ } - case "organizations": - field := field - - innerFunc := func(ctx context.Context, fs *graphql.FieldSet) (res graphql.Marshaler) { - res = ec._User_organizations(ctx, field, obj) - if res == graphql.Null { - atomic.AddUint32(&fs.Invalids, 1) - } - return res - } - - if field.Deferrable != nil { - dfs, ok := deferred[field.Deferrable.Label] - di := 0 - if ok { - dfs.AddField(field) - di = len(dfs.Values) - 1 - } else { - dfs = graphql.NewFieldSet([]graphql.CollectedField{field}) - deferred[field.Deferrable.Label] = dfs - } - dfs.Concurrently(di, func(ctx context.Context) graphql.Marshaler { - return innerFunc(ctx, dfs) - }) - - // don't run the out.Concurrently() call below - out.Values[i] = graphql.Null - continue - } - - out.Concurrently(i, func(ctx context.Context) graphql.Marshaler { return innerFunc(ctx, out) }) case "createdAt": out.Values[i] = ec._User_createdAt(ctx, field, obj) if out.Values[i] == graphql.Null { - atomic.AddUint32(&out.Invalids, 1) + out.Invalids++ } case "updatedAt": out.Values[i] = ec._User_updatedAt(ctx, field, obj) if out.Values[i] == graphql.Null { - atomic.AddUint32(&out.Invalids, 1) + out.Invalids++ + } + default: + panic("unknown field " + strconv.Quote(field.Name)) + } + } + out.Dispatch(ctx) + if out.Invalids > 0 { + return graphql.Null + } + + atomic.AddInt32(&ec.deferred, int32(len(deferred))) + + for label, dfs := range deferred { + ec.processDeferredGroup(graphql.DeferredGroup{ + Label: label, + Path: graphql.GetPath(ctx), + FieldSet: dfs, + Context: ctx, + }) + } + + return out +} + +var userConnectionImplementors = []string{"UserConnection"} + +func (ec *executionContext) _UserConnection(ctx context.Context, sel ast.SelectionSet, obj *types.UserConnection) graphql.Marshaler { + fields := graphql.CollectFields(ec.OperationContext, sel, userConnectionImplementors) + + out := graphql.NewFieldSet(fields) + deferred := make(map[string]*graphql.FieldSet) + for i, field := range fields { + switch field.Name { + case "__typename": + out.Values[i] = graphql.MarshalString("UserConnection") + case "edges": + out.Values[i] = ec._UserConnection_edges(ctx, field, obj) + if out.Values[i] == graphql.Null { + out.Invalids++ + } + case "pageInfo": + out.Values[i] = ec._UserConnection_pageInfo(ctx, field, obj) + if out.Values[i] == graphql.Null { + out.Invalids++ + } + default: + panic("unknown field " + strconv.Quote(field.Name)) + } + } + out.Dispatch(ctx) + if out.Invalids > 0 { + return graphql.Null + } + + atomic.AddInt32(&ec.deferred, int32(len(deferred))) + + for label, dfs := range deferred { + ec.processDeferredGroup(graphql.DeferredGroup{ + Label: label, + Path: graphql.GetPath(ctx), + FieldSet: dfs, + Context: ctx, + }) + } + + return out +} + +var userEdgeImplementors = []string{"UserEdge"} + +func (ec *executionContext) _UserEdge(ctx context.Context, sel ast.SelectionSet, obj *types.UserEdge) graphql.Marshaler { + fields := graphql.CollectFields(ec.OperationContext, sel, userEdgeImplementors) + + out := graphql.NewFieldSet(fields) + deferred := make(map[string]*graphql.FieldSet) + for i, field := range fields { + switch field.Name { + case "__typename": + out.Values[i] = graphql.MarshalString("UserEdge") + case "cursor": + out.Values[i] = ec._UserEdge_cursor(ctx, field, obj) + if out.Values[i] == graphql.Null { + out.Invalids++ + } + case "node": + out.Values[i] = ec._UserEdge_node(ctx, field, obj) + if out.Values[i] == graphql.Null { + out.Invalids++ } default: panic("unknown field " + strconv.Quote(field.Name)) @@ -17450,6 +18023,81 @@ func (ec *executionContext) _VendorEdge(ctx context.Context, sel ast.SelectionSe return out } +var viewerImplementors = []string{"Viewer"} + +func (ec *executionContext) _Viewer(ctx context.Context, sel ast.SelectionSet, obj *types.Viewer) graphql.Marshaler { + fields := graphql.CollectFields(ec.OperationContext, sel, viewerImplementors) + + out := graphql.NewFieldSet(fields) + deferred := make(map[string]*graphql.FieldSet) + for i, field := range fields { + switch field.Name { + case "__typename": + out.Values[i] = graphql.MarshalString("Viewer") + case "id": + out.Values[i] = ec._Viewer_id(ctx, field, obj) + if out.Values[i] == graphql.Null { + atomic.AddUint32(&out.Invalids, 1) + } + case "user": + out.Values[i] = ec._Viewer_user(ctx, field, obj) + if out.Values[i] == graphql.Null { + atomic.AddUint32(&out.Invalids, 1) + } + case "organizations": + field := field + + innerFunc := func(ctx context.Context, fs *graphql.FieldSet) (res graphql.Marshaler) { + res = ec._Viewer_organizations(ctx, field, obj) + if res == graphql.Null { + atomic.AddUint32(&fs.Invalids, 1) + } + return res + } + + if field.Deferrable != nil { + dfs, ok := deferred[field.Deferrable.Label] + di := 0 + if ok { + dfs.AddField(field) + di = len(dfs.Values) - 1 + } else { + dfs = graphql.NewFieldSet([]graphql.CollectedField{field}) + deferred[field.Deferrable.Label] = dfs + } + dfs.Concurrently(di, func(ctx context.Context) graphql.Marshaler { + return innerFunc(ctx, dfs) + }) + + // don't run the out.Concurrently() call below + out.Values[i] = graphql.Null + continue + } + + out.Concurrently(i, func(ctx context.Context) graphql.Marshaler { return innerFunc(ctx, out) }) + default: + panic("unknown field " + strconv.Quote(field.Name)) + } + } + out.Dispatch(ctx) + if out.Invalids > 0 { + return graphql.Null + } + + atomic.AddInt32(&ec.deferred, int32(len(deferred))) + + for label, dfs := range deferred { + ec.processDeferredGroup(graphql.DeferredGroup{ + Label: label, + Path: graphql.GetPath(ctx), + FieldSet: dfs, + Context: ctx, + }) + } + + return out +} + var __DirectiveImplementors = []string{"__Directive"} func (ec *executionContext) ___Directive(ctx context.Context, sel ast.SelectionSet, obj *introspection.Directive) graphql.Marshaler { @@ -19168,10 +19816,6 @@ func (ec *executionContext) marshalNUploadEvidencePayload2ᚖgithubᚗcomᚋgetp return ec._UploadEvidencePayload(ctx, sel, v) } -func (ec *executionContext) marshalNUser2githubᚗcomᚋgetproboᚋproboᚋpkgᚋserverᚋapiᚋconsoleᚋv1ᚋtypesᚐUser(ctx context.Context, sel ast.SelectionSet, v types.User) graphql.Marshaler { - return ec._User(ctx, sel, &v) -} - func (ec *executionContext) marshalNUser2ᚖgithubᚗcomᚋgetproboᚋproboᚋpkgᚋserverᚋapiᚋconsoleᚋv1ᚋtypesᚐUser(ctx context.Context, sel ast.SelectionSet, v *types.User) graphql.Marshaler { if v == nil { if !graphql.HasFieldError(ctx, graphql.GetFieldContext(ctx)) { @@ -19182,6 +19826,68 @@ func (ec *executionContext) marshalNUser2ᚖgithubᚗcomᚋgetproboᚋproboᚋpk return ec._User(ctx, sel, v) } +func (ec *executionContext) marshalNUserConnection2githubᚗcomᚋgetproboᚋproboᚋpkgᚋserverᚋapiᚋconsoleᚋv1ᚋtypesᚐUserConnection(ctx context.Context, sel ast.SelectionSet, v types.UserConnection) graphql.Marshaler { + return ec._UserConnection(ctx, sel, &v) +} + +func (ec *executionContext) marshalNUserConnection2ᚖgithubᚗcomᚋgetproboᚋproboᚋpkgᚋserverᚋapiᚋconsoleᚋv1ᚋtypesᚐUserConnection(ctx context.Context, sel ast.SelectionSet, v *types.UserConnection) graphql.Marshaler { + if v == nil { + if !graphql.HasFieldError(ctx, graphql.GetFieldContext(ctx)) { + ec.Errorf(ctx, "the requested element is null which the schema does not allow") + } + return graphql.Null + } + return ec._UserConnection(ctx, sel, v) +} + +func (ec *executionContext) marshalNUserEdge2ᚕᚖgithubᚗcomᚋgetproboᚋproboᚋpkgᚋserverᚋapiᚋconsoleᚋv1ᚋtypesᚐUserEdgeᚄ(ctx context.Context, sel ast.SelectionSet, v []*types.UserEdge) graphql.Marshaler { + ret := make(graphql.Array, len(v)) + var wg sync.WaitGroup + isLen1 := len(v) == 1 + if !isLen1 { + wg.Add(len(v)) + } + for i := range v { + i := i + fc := &graphql.FieldContext{ + Index: &i, + Result: &v[i], + } + ctx := graphql.WithFieldContext(ctx, fc) + f := func(i int) { + if !isLen1 { + defer wg.Done() + } + ret[i] = ec.marshalNUserEdge2ᚖgithubᚗcomᚋgetproboᚋproboᚋpkgᚋserverᚋapiᚋconsoleᚋv1ᚋtypesᚐUserEdge(ctx, sel, v[i]) + } + if isLen1 { + f(i) + } else { + go f(i) + } + + } + wg.Wait() + + for _, e := range ret { + if e == graphql.Null { + return graphql.Null + } + } + + return ret +} + +func (ec *executionContext) marshalNUserEdge2ᚖgithubᚗcomᚋgetproboᚋproboᚋpkgᚋserverᚋapiᚋconsoleᚋv1ᚋtypesᚐUserEdge(ctx context.Context, sel ast.SelectionSet, v *types.UserEdge) graphql.Marshaler { + if v == nil { + if !graphql.HasFieldError(ctx, graphql.GetFieldContext(ctx)) { + ec.Errorf(ctx, "the requested element is null which the schema does not allow") + } + return graphql.Null + } + return ec._UserEdge(ctx, sel, v) +} + func (ec *executionContext) marshalNVendor2ᚖgithubᚗcomᚋgetproboᚋproboᚋpkgᚋserverᚋapiᚋconsoleᚋv1ᚋtypesᚐVendor(ctx context.Context, sel ast.SelectionSet, v *types.Vendor) graphql.Marshaler { if v == nil { if !graphql.HasFieldError(ctx, graphql.GetFieldContext(ctx)) { @@ -19254,6 +19960,20 @@ func (ec *executionContext) marshalNVendorEdge2ᚖgithubᚗcomᚋgetproboᚋprob return ec._VendorEdge(ctx, sel, v) } +func (ec *executionContext) marshalNViewer2githubᚗcomᚋgetproboᚋproboᚋpkgᚋserverᚋapiᚋconsoleᚋv1ᚋtypesᚐViewer(ctx context.Context, sel ast.SelectionSet, v types.Viewer) graphql.Marshaler { + return ec._Viewer(ctx, sel, &v) +} + +func (ec *executionContext) marshalNViewer2ᚖgithubᚗcomᚋgetproboᚋproboᚋpkgᚋserverᚋapiᚋconsoleᚋv1ᚋtypesᚐViewer(ctx context.Context, sel ast.SelectionSet, v *types.Viewer) graphql.Marshaler { + if v == nil { + if !graphql.HasFieldError(ctx, graphql.GetFieldContext(ctx)) { + ec.Errorf(ctx, "the requested element is null which the schema does not allow") + } + return graphql.Null + } + return ec._Viewer(ctx, sel, v) +} + func (ec *executionContext) marshalN__Directive2githubᚗcomᚋ99designsᚋgqlgenᚋgraphqlᚋintrospectionᚐDirective(ctx context.Context, sel ast.SelectionSet, v introspection.Directive) graphql.Marshaler { return ec.___Directive(ctx, sel, &v) } diff --git a/pkg/server/api/console/v1/types/types.go b/pkg/server/api/console/v1/types/types.go index 920dc0b46..cdd2f6fa2 100644 --- a/pkg/server/api/console/v1/types/types.go +++ b/pkg/server/api/console/v1/types/types.go @@ -254,6 +254,7 @@ type Organization struct { ID gid.GID `json:"id"` Name string `json:"name"` LogoURL *string `json:"logoUrl,omitempty"` + Users *UserConnection `json:"users"` Frameworks *FrameworkConnection `json:"frameworks"` Vendors *VendorConnection `json:"vendors"` Peoples *PeopleConnection `json:"peoples"` @@ -477,17 +478,26 @@ type UploadEvidencePayload struct { } type User struct { - ID gid.GID `json:"id"` - FullName string `json:"fullName"` - Email string `json:"email"` - Organizations *OrganizationConnection `json:"organizations"` - CreatedAt time.Time `json:"createdAt"` - UpdatedAt time.Time `json:"updatedAt"` + ID gid.GID `json:"id"` + FullName string `json:"fullName"` + Email string `json:"email"` + CreatedAt time.Time `json:"createdAt"` + UpdatedAt time.Time `json:"updatedAt"` } func (User) IsNode() {} func (this User) GetID() gid.GID { return this.ID } +type UserConnection struct { + Edges []*UserEdge `json:"edges"` + PageInfo *PageInfo `json:"pageInfo"` +} + +type UserEdge struct { + Cursor page.CursorKey `json:"cursor"` + Node *User `json:"node"` +} + type Vendor struct { ID gid.GID `json:"id"` Name string `json:"name"` @@ -516,3 +526,9 @@ type VendorEdge struct { Cursor page.CursorKey `json:"cursor"` Node *Vendor `json:"node"` } + +type Viewer struct { + ID gid.GID `json:"id"` + User *User `json:"user"` + Organizations *OrganizationConnection `json:"organizations"` +} diff --git a/pkg/server/api/console/v1/types/user.go b/pkg/server/api/console/v1/types/user.go index a12ca78ff..51994b45c 100644 --- a/pkg/server/api/console/v1/types/user.go +++ b/pkg/server/api/console/v1/types/user.go @@ -16,8 +16,28 @@ package types import ( "github.com/getprobo/probo/pkg/coredata" + "github.com/getprobo/probo/pkg/page" ) +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]) + } + + return &UserConnection{ + Edges: edges, + PageInfo: NewPageInfo(p), + } +} + +func NewUserEdge(user *coredata.User) *UserEdge { + return &UserEdge{ + Node: NewUser(user), + } +} + func NewUser(u *coredata.User) *User { return &User{ ID: u.ID, diff --git a/pkg/server/api/console/v1/v1_resolver.go b/pkg/server/api/console/v1/v1_resolver.go index 16a8bde15..86ee55a36 100644 --- a/pkg/server/api/console/v1/v1_resolver.go +++ b/pkg/server/api/console/v1/v1_resolver.go @@ -517,6 +517,18 @@ func (r *organizationResolver) LogoURL(ctx context.Context, obj *types.Organizat return svc.Organizations.GenerateLogoURL(ctx, obj.ID, 1*time.Hour) } +// 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) { + cursor := types.NewCursor(first, after, last, before) + + page, err := r.usrmgrSvc.ListUsersForTenant(ctx, obj.ID, cursor) + if err != nil { + return nil, fmt.Errorf("cannot list users: %w", err) + } + + return types.NewUserConnection(page), nil +} + // Frameworks is the resolver for the frameworks field. func (r *organizationResolver) Frameworks(ctx context.Context, obj *types.Organization, first *int, after *page.CursorKey, last *int, before *page.CursorKey) (*types.FrameworkConnection, error) { svc := r.GetTenantServiceIfAuthorized(ctx, obj.ID.TenantID()) @@ -657,9 +669,14 @@ func (r *queryResolver) Node(ctx context.Context, id gid.GID) (types.Node, error } // Viewer is the resolver for the viewer field. -func (r *queryResolver) Viewer(ctx context.Context) (*types.User, error) { +func (r *queryResolver) Viewer(ctx context.Context) (*types.Viewer, error) { user := UserFromContext(ctx) - return types.NewUser(user), nil + session := SessionFromContext(ctx) + + return &types.Viewer{ + ID: session.ID, + User: types.NewUser(user), + }, nil } // AssignedTo is the resolver for the assignedTo field. @@ -697,8 +714,10 @@ func (r *taskResolver) Evidences(ctx context.Context, obj *types.Task, first *in } // Organizations is the resolver for the organizations field. -func (r *userResolver) Organizations(ctx context.Context, obj *types.User, first *int, after *page.CursorKey, last *int, before *page.CursorKey) (*types.OrganizationConnection, error) { - organizations, err := r.usrmgrSvc.ListOrganizationsForUserID(ctx, obj.ID) +func (r *viewerResolver) Organizations(ctx context.Context, obj *types.Viewer, first *int, after *page.CursorKey, last *int, before *page.CursorKey) (*types.OrganizationConnection, error) { + user := UserFromContext(ctx) + + organizations, err := r.usrmgrSvc.ListOrganizationsForUserID(ctx, user.ID) if err != nil { panic(fmt.Errorf("failed to list organizations for user: %w", err)) } @@ -738,8 +757,8 @@ func (r *Resolver) Query() schema.QueryResolver { return &queryResolver{r} } // Task returns schema.TaskResolver implementation. func (r *Resolver) Task() schema.TaskResolver { return &taskResolver{r} } -// User returns schema.UserResolver implementation. -func (r *Resolver) User() schema.UserResolver { return &userResolver{r} } +// Viewer returns schema.ViewerResolver implementation. +func (r *Resolver) Viewer() schema.ViewerResolver { return &viewerResolver{r} } type controlResolver struct{ *Resolver } type evidenceResolver struct{ *Resolver } @@ -749,4 +768,4 @@ type organizationResolver struct{ *Resolver } type policyResolver struct{ *Resolver } type queryResolver struct{ *Resolver } type taskResolver struct{ *Resolver } -type userResolver struct{ *Resolver } +type viewerResolver struct{ *Resolver } diff --git a/pkg/usrmgr/usrmgr.go b/pkg/usrmgr/usrmgr.go index 292097f7f..10fd3a058 100644 --- a/pkg/usrmgr/usrmgr.go +++ b/pkg/usrmgr/usrmgr.go @@ -25,6 +25,7 @@ import ( "github.com/getprobo/probo/pkg/coredata" "github.com/getprobo/probo/pkg/crypto/passwdhash" "github.com/getprobo/probo/pkg/gid" + "github.com/getprobo/probo/pkg/page" "github.com/getprobo/probo/pkg/statelesstoken" "go.gearno.de/kit/pg" ) @@ -491,3 +492,24 @@ func (s Service) ConfirmEmail(ctx context.Context, tokenString string) error { }, ) } + +func (s Service) ListUsersForTenant( + ctx context.Context, + organizationID gid.GID, + cursor *page.Cursor, +) (*page.Page[*coredata.User], error) { + users := coredata.Users{} + + err := s.pg.WithConn( + ctx, + func(tx pg.Conn) error { + return users.LoadByOrganizationID(ctx, tx, organizationID, cursor) + }, + ) + + if err != nil { + return nil, err + } + + return page.NewPage(users, cursor), nil +}