Replug organizations page without assumable sessions

Signed-off-by: Émile Ré <nemile.re@gmail.com>
This commit is contained in:
Émile Ré
2025-12-18 20:28:03 +01:00
committed by Bryan Frimin
parent 970a734964
commit ddf585fc18
14 changed files with 1342 additions and 69 deletions

View File

@@ -0,0 +1,116 @@
import { usePreloadedQuery, type PreloadedQuery } from "react-relay";
import { useTranslate } from "@probo/i18n";
import {
Button,
Card,
IconMagnifyingGlass,
IconPlusLarge,
Input,
} from "@probo/ui";
import { useMemo, useState } from "react";
import { OrganizationCard } from "./_components/OrganizationCard";
import { InvitationCard } from "./_components/InvitationCard";
import type {
OrganizationsQuery,
OrganizationsQuery$data,
} from "./__generated__/OrganizationsQuery.graphql";
import { organizationsQuery } from "./OrganizationsQuery";
interface PageProps {
queryRef: PreloadedQuery<OrganizationsQuery>;
}
export default function Page(props: PageProps) {
const { __ } = useTranslate();
const [search, setSearch] = useState("");
const { queryRef } = props;
const {
viewer: {
memberships: { edges: initialMemberships },
pendingInvitations: { edges: invitations },
},
} = usePreloadedQuery<OrganizationsQuery>(organizationsQuery, queryRef);
const memberships = useMemo<
OrganizationsQuery$data["viewer"]["memberships"]["edges"]
>(() => {
if (!search.trim()) {
return initialMemberships;
}
return initialMemberships.filter(({ node }) =>
node.organization.name.toLowerCase().includes(search.toLowerCase()),
);
}, [initialMemberships, search]);
return (
<>
<div className="space-y-6 w-full py-6">
<h1 className="text-3xl font-bold text-center">
{__("Select an organization")}
</h1>
<div className="space-y-4 w-full">
{invitations.length > 0 && (
<div className="space-y-3">
<h2 className="text-xl font-semibold">
{__("Pending invitations")}
</h2>
{invitations.map(({ node }) => (
<InvitationCard
key={node.id}
fKey={node}
// onAccept={handleAcceptInvitation}
// isAccepting={isAccepting}
/>
))}
</div>
)}
{memberships.length > 0 && (
<div className="space-y-3">
{invitations.length > 0 && (
<h2 className="text-xl font-semibold">
{__("Your organizations")}
</h2>
)}
{memberships.length > 3 && (
<div className="w-full">
<Input
icon={IconMagnifyingGlass}
placeholder={__("Search organizations...")}
value={search}
onValueChange={setSearch}
/>
</div>
)}
{memberships.length === 0 ? (
<div className="text-center text-txt-secondary py-4">
{__("No organizations found")}
</div>
) : (
memberships.map(({ node: { id, organization } }) => (
<OrganizationCard key={id} fKey={organization} />
))
)}
</div>
)}
<Card padded>
<h2 className="text-xl font-semibold mb-1">
{__("Create an organization")}
</h2>
<p className="text-txt-tertiary mb-4">
{__("Add a new organization to your account")}
</p>
<Button
to="/organizations/new"
variant="quaternary"
icon={IconPlusLarge}
className="w-full"
>
{__("Create organization")}
</Button>
</Card>
</div>
</div>
</>
);
}

View File

@@ -0,0 +1,60 @@
import { lazy } from "@probo/react-lazy";
import { useQueryLoader } from "react-relay";
import { graphql } from "relay-runtime";
import type { OrganizationsQuery } from "./__generated__/OrganizationsQuery.graphql";
import { Suspense, useEffect } from "react";
import { CenteredLayoutSkeleton } from "@probo/ui";
const Page = lazy(() => import("./OrganizationsPage"));
export const organizationsQuery = graphql`
query OrganizationsQuery {
viewer @required(action: THROW) {
memberships(
first: 1000
orderBy: { direction: DESC, field: CREATED_AT }
) {
edges {
node {
id
createdAt
organization {
name
...OrganizationCardFragment
}
}
}
}
pendingInvitations(
first: 1000
orderBy: { direction: DESC, field: CREATED_AT }
) {
edges {
node {
id
...InvitationCardFragment
}
}
}
}
}
`;
export function OrganizationsQuery() {
const [queryRef, loadQuery] =
useQueryLoader<OrganizationsQuery>(organizationsQuery);
useEffect(() => {
loadQuery({});
}, [loadQuery]);
if (!queryRef) {
return <CenteredLayoutSkeleton />;
}
return (
<Suspense fallback={<CenteredLayoutSkeleton />}>
<Page queryRef={queryRef} />
</Suspense>
);
}

View File

@@ -0,0 +1,340 @@
/**
* @generated SignedSource<<c7e497311fbff76ab3b574012b9b9660>>
* @lightSyntaxTransform
* @nogrep
*/
/* tslint:disable */
/* eslint-disable */
// @ts-nocheck
import { ConcreteRequest } from 'relay-runtime';
import { FragmentRefs } from "relay-runtime";
export type OrganizationsQuery$variables = Record<PropertyKey, never>;
export type OrganizationsQuery$data = {
readonly viewer: {
readonly memberships: {
readonly edges: ReadonlyArray<{
readonly node: {
readonly createdAt: any;
readonly id: string;
readonly organization: {
readonly name: string;
readonly " $fragmentSpreads": FragmentRefs<"OrganizationCardFragment">;
};
};
}>;
};
readonly pendingInvitations: {
readonly edges: ReadonlyArray<{
readonly node: {
readonly id: string;
readonly " $fragmentSpreads": FragmentRefs<"InvitationCardFragment">;
};
}>;
};
};
};
export type OrganizationsQuery = {
response: OrganizationsQuery$data;
variables: OrganizationsQuery$variables;
};
const node: ConcreteRequest = (function(){
var v0 = [
{
"kind": "Literal",
"name": "first",
"value": 1000
},
{
"kind": "Literal",
"name": "orderBy",
"value": {
"direction": "DESC",
"field": "CREATED_AT"
}
}
],
v1 = {
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "id",
"storageKey": null
},
v2 = {
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "createdAt",
"storageKey": null
},
v3 = {
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "name",
"storageKey": null
};
return {
"fragment": {
"argumentDefinitions": [],
"kind": "Fragment",
"metadata": null,
"name": "OrganizationsQuery",
"selections": [
{
"kind": "RequiredField",
"field": {
"alias": null,
"args": null,
"concreteType": "Identity",
"kind": "LinkedField",
"name": "viewer",
"plural": false,
"selections": [
{
"alias": null,
"args": (v0/*: any*/),
"concreteType": "MembershipConnection",
"kind": "LinkedField",
"name": "memberships",
"plural": false,
"selections": [
{
"alias": null,
"args": null,
"concreteType": "MembershipEdge",
"kind": "LinkedField",
"name": "edges",
"plural": true,
"selections": [
{
"alias": null,
"args": null,
"concreteType": "Membership",
"kind": "LinkedField",
"name": "node",
"plural": false,
"selections": [
(v1/*: any*/),
(v2/*: any*/),
{
"alias": null,
"args": null,
"concreteType": "Organization",
"kind": "LinkedField",
"name": "organization",
"plural": false,
"selections": [
(v3/*: any*/),
{
"args": null,
"kind": "FragmentSpread",
"name": "OrganizationCardFragment"
}
],
"storageKey": null
}
],
"storageKey": null
}
],
"storageKey": null
}
],
"storageKey": "memberships(first:1000,orderBy:{\"direction\":\"DESC\",\"field\":\"CREATED_AT\"})"
},
{
"alias": null,
"args": (v0/*: any*/),
"concreteType": "InvitationConnection",
"kind": "LinkedField",
"name": "pendingInvitations",
"plural": false,
"selections": [
{
"alias": null,
"args": null,
"concreteType": "InvitationEdge",
"kind": "LinkedField",
"name": "edges",
"plural": true,
"selections": [
{
"alias": null,
"args": null,
"concreteType": "Invitation",
"kind": "LinkedField",
"name": "node",
"plural": false,
"selections": [
(v1/*: any*/),
{
"args": null,
"kind": "FragmentSpread",
"name": "InvitationCardFragment"
}
],
"storageKey": null
}
],
"storageKey": null
}
],
"storageKey": "pendingInvitations(first:1000,orderBy:{\"direction\":\"DESC\",\"field\":\"CREATED_AT\"})"
}
],
"storageKey": null
},
"action": "THROW"
}
],
"type": "Query",
"abstractKey": null
},
"kind": "Request",
"operation": {
"argumentDefinitions": [],
"kind": "Operation",
"name": "OrganizationsQuery",
"selections": [
{
"alias": null,
"args": null,
"concreteType": "Identity",
"kind": "LinkedField",
"name": "viewer",
"plural": false,
"selections": [
{
"alias": null,
"args": (v0/*: any*/),
"concreteType": "MembershipConnection",
"kind": "LinkedField",
"name": "memberships",
"plural": false,
"selections": [
{
"alias": null,
"args": null,
"concreteType": "MembershipEdge",
"kind": "LinkedField",
"name": "edges",
"plural": true,
"selections": [
{
"alias": null,
"args": null,
"concreteType": "Membership",
"kind": "LinkedField",
"name": "node",
"plural": false,
"selections": [
(v1/*: any*/),
(v2/*: any*/),
{
"alias": null,
"args": null,
"concreteType": "Organization",
"kind": "LinkedField",
"name": "organization",
"plural": false,
"selections": [
(v3/*: any*/),
(v1/*: any*/),
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "logoUrl",
"storageKey": null
}
],
"storageKey": null
}
],
"storageKey": null
}
],
"storageKey": null
}
],
"storageKey": "memberships(first:1000,orderBy:{\"direction\":\"DESC\",\"field\":\"CREATED_AT\"})"
},
{
"alias": null,
"args": (v0/*: any*/),
"concreteType": "InvitationConnection",
"kind": "LinkedField",
"name": "pendingInvitations",
"plural": false,
"selections": [
{
"alias": null,
"args": null,
"concreteType": "InvitationEdge",
"kind": "LinkedField",
"name": "edges",
"plural": true,
"selections": [
{
"alias": null,
"args": null,
"concreteType": "Invitation",
"kind": "LinkedField",
"name": "node",
"plural": false,
"selections": [
(v1/*: any*/),
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "role",
"storageKey": null
},
(v2/*: any*/),
{
"alias": null,
"args": null,
"concreteType": "Organization",
"kind": "LinkedField",
"name": "organization",
"plural": false,
"selections": [
(v1/*: any*/),
(v3/*: any*/)
],
"storageKey": null
}
],
"storageKey": null
}
],
"storageKey": null
}
],
"storageKey": "pendingInvitations(first:1000,orderBy:{\"direction\":\"DESC\",\"field\":\"CREATED_AT\"})"
},
(v1/*: any*/)
],
"storageKey": null
}
]
},
"params": {
"cacheID": "145dddcd59c5c55a1c61913d9f43b38b",
"id": null,
"metadata": {},
"name": "OrganizationsQuery",
"operationKind": "query",
"text": "query OrganizationsQuery {\n viewer {\n memberships(first: 1000, orderBy: {direction: DESC, field: CREATED_AT}) {\n edges {\n node {\n id\n createdAt\n organization {\n name\n ...OrganizationCardFragment\n id\n }\n }\n }\n }\n pendingInvitations(first: 1000, orderBy: {direction: DESC, field: CREATED_AT}) {\n edges {\n node {\n id\n ...InvitationCardFragment\n }\n }\n }\n id\n }\n}\n\nfragment InvitationCardFragment on Invitation {\n id\n role\n createdAt\n organization {\n id\n name\n }\n}\n\nfragment OrganizationCardFragment on Organization {\n id\n name\n logoUrl\n}\n"
}
};
})();
(node as any).hash = "f85219c0fcd69be1b5cfb0a5b9c9b8db";
export default node;

View File

@@ -0,0 +1,56 @@
import { formatDate } from "@probo/helpers";
import { useTranslate } from "@probo/i18n";
import { Card } from "@probo/ui";
import { graphql } from "relay-runtime";
import { useFragment } from "react-relay";
import type { InvitationCardFragment$key } from "./__generated__/InvitationCardFragment.graphql";
const fragment = graphql`
fragment InvitationCardFragment on Invitation {
id
role
createdAt
organization {
id
name
}
}
`;
interface InvitationCardProps {
fKey: InvitationCardFragment$key;
// onAccept: (invitationId: string, organizationId: string) => void;
// isAccepting: boolean;
}
export function InvitationCard(props: InvitationCardProps) {
const { fKey } = props;
const { __ } = useTranslate();
const invitation = useFragment<InvitationCardFragment$key>(fragment, fKey);
return (
<Card padded className="w-full">
<div className="flex items-start justify-between gap-4">
<div className="flex-1 space-y-1">
<h3 className="text-lg font-semibold">
{invitation.organization.name}
</h3>
<p className="text-sm text-txt-secondary">
{__("Role")}: <span className="font-medium">{invitation.role}</span>
</p>
<p className="text-xs text-txt-tertiary">
{__("Invited on")} {formatDate(invitation.createdAt)}
</p>
</div>
{/* <Button
onClick={() => onAccept(invitation.id, invitation.organization.id)}
disabled={isAccepting}
>
{isAccepting ? __("Accepting...") : __("Accept invitation")}
</Button> */}
</div>
</Card>
);
}

View File

@@ -0,0 +1,126 @@
import { useTranslate } from "@probo/i18n";
import { Avatar, Badge, Button, Card, IconLock } from "@probo/ui";
import { Link } from "react-router";
import { graphql } from "relay-runtime";
import { useFragment } from "react-relay";
import type { OrganizationCardFragment$key } from "./__generated__/OrganizationCardFragment.graphql";
const fragment = graphql`
fragment OrganizationCardFragment on Organization {
id
name
logoUrl
}
`;
interface OrganizationCardProps {
fKey: OrganizationCardFragment$key;
}
export function OrganizationCard(props: OrganizationCardProps) {
const { fKey } = props;
const { __ } = useTranslate();
const organization = useFragment<OrganizationCardFragment$key>(
fragment,
fKey,
);
// const isAuthenticated = organization.authStatus === "authenticated";
// const isExpired = organization.authStatus === "expired";
// const needsAuth = organization.authStatus === "unauthenticated";
// Determine target URL and button text based on auth status
// const targetUrl = isAuthenticated
// ? `/organizations/${organization.id}`
// : organization.loginUrl;
const targetUrl = `/organizations/${organization.id}`;
const getAuthBadge = () => {
// if (isAuthenticated) {
// return (
// <Badge variant="success" className="flex items-center gap-1">
// <IconCheckmark1 size={14} />
// {__("Authenticated")}
// </Badge>
// );
// }
// if (isExpired) {
// return (
// <Badge variant="warning" className="flex items-center gap-1">
// <IconClock size={14} />
// {__("Session expired")}
// </Badge>
// );
// }
// if (needsAuth) {
return (
<Badge variant="neutral" className="flex items-center gap-1">
<IconLock size={14} />
{__("Authentication required")}
</Badge>
);
// }
return null;
};
// const getButtonText = () => {
// if (isAuthenticated) return __("Select");
// if (organization.authenticationMethod === "saml")
// return __("Login with SAML");
// return __("Login");
// };
// Check if the URL is a backend SAML endpoint
const isSAMLUrl = targetUrl.includes("/connect/saml/");
return (
<Card padded className="w-full">
<div className="flex items-center justify-between">
{isSAMLUrl ? (
<a
href={targetUrl}
className="flex items-center gap-4 hover:text-primary flex-1"
>
<Avatar
src={organization.logoUrl}
name={organization.name}
size="l"
/>
<div className="flex flex-col gap-1">
<h2 className="font-semibold text-xl">{organization.name}</h2>
{getAuthBadge()}
</div>
</a>
) : (
<Link
to={targetUrl}
className="flex items-center gap-4 hover:text-primary flex-1"
>
<Avatar
src={organization.logoUrl}
name={organization.name}
size="l"
/>
<div className="flex flex-col gap-1">
<h2 className="font-semibold text-xl">{organization.name}</h2>
{getAuthBadge()}
</div>
</Link>
)}
<div className="flex items-center gap-3">
<Button asChild>
{/* {isSAMLUrl ? (
<a href={targetUrl}>{getButtonText()}</a>
) : (
<Link to={targetUrl}>{getButtonText()}</Link>
)} */}
<Link to={targetUrl}>LOGIN</Link>
</Button>
</div>
</div>
</Card>
);
}

View File

@@ -0,0 +1,85 @@
/**
* @generated SignedSource<<5193da6ee4b5da0bed510c255c3407b1>>
* @lightSyntaxTransform
* @nogrep
*/
/* tslint:disable */
/* eslint-disable */
// @ts-nocheck
import { ReaderFragment } from 'relay-runtime';
export type MembershipRole = "ADMIN" | "AUDITOR" | "EMPLOYEE" | "OWNER" | "VIEWER";
import { FragmentRefs } from "relay-runtime";
export type InvitationCardFragment$data = {
readonly createdAt: any;
readonly id: string;
readonly organization: {
readonly id: string;
readonly name: string;
};
readonly role: MembershipRole;
readonly " $fragmentType": "InvitationCardFragment";
};
export type InvitationCardFragment$key = {
readonly " $data"?: InvitationCardFragment$data;
readonly " $fragmentSpreads": FragmentRefs<"InvitationCardFragment">;
};
const node: ReaderFragment = (function(){
var v0 = {
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "id",
"storageKey": null
};
return {
"argumentDefinitions": [],
"kind": "Fragment",
"metadata": null,
"name": "InvitationCardFragment",
"selections": [
(v0/*: any*/),
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "role",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "createdAt",
"storageKey": null
},
{
"alias": null,
"args": null,
"concreteType": "Organization",
"kind": "LinkedField",
"name": "organization",
"plural": false,
"selections": [
(v0/*: any*/),
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "name",
"storageKey": null
}
],
"storageKey": null
}
],
"type": "Invitation",
"abstractKey": null
};
})();
(node as any).hash = "1f61b5f07abc69ad33c880bc39cd0e96";
export default node;

View File

@@ -0,0 +1,58 @@
/**
* @generated SignedSource<<06aa55abfa21c09c85bb308f813565d8>>
* @lightSyntaxTransform
* @nogrep
*/
/* tslint:disable */
/* eslint-disable */
// @ts-nocheck
import { ReaderFragment } from 'relay-runtime';
import { FragmentRefs } from "relay-runtime";
export type OrganizationCardFragment$data = {
readonly id: string;
readonly logoUrl: string | null | undefined;
readonly name: string;
readonly " $fragmentType": "OrganizationCardFragment";
};
export type OrganizationCardFragment$key = {
readonly " $data"?: OrganizationCardFragment$data;
readonly " $fragmentSpreads": FragmentRefs<"OrganizationCardFragment">;
};
const node: ReaderFragment = {
"argumentDefinitions": [],
"kind": "Fragment",
"metadata": null,
"name": "OrganizationCardFragment",
"selections": [
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "id",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "name",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "logoUrl",
"storageKey": null
}
],
"type": "Organization",
"abstractKey": null
};
(node as any).hash = "a8b9f4a515650db79f41507fb52f7b96";
export default node;

View File

@@ -49,7 +49,7 @@ import {
UnAuthenticatedError, UnAuthenticatedError,
UnauthorizedError, UnauthorizedError,
} from "@probo/relay"; } from "@probo/relay";
import { OrganizationListLoading } from "./pages/iam/organization/list/loading.tsx"; import { OrganizationsQuery } from "./pages/iam/organizations/OrganizationsQuery.tsx";
/** /**
* Top level error boundary * Top level error boundary
@@ -121,7 +121,7 @@ const routes = [
index: true, index: true,
Component: () => ( Component: () => (
<RelayEnvironmentProvider environment={connectEnvironment}> <RelayEnvironmentProvider environment={connectEnvironment}>
<OrganizationListLoading /> <OrganizationsQuery />
</RelayEnvironmentProvider> </RelayEnvironmentProvider>
), ),
}, },
@@ -134,7 +134,7 @@ const routes = [
{ {
path: "documents/signing-requests", path: "documents/signing-requests",
Component: lazy( Component: lazy(
() => import("./pages/DocumentSigningRequestsPage.tsx") () => import("./pages/DocumentSigningRequestsPage.tsx"),
), ),
}, },
{ {
@@ -154,12 +154,13 @@ const routes = [
loader: loaderFromQueryLoader(({ organizationId }) => loader: loaderFromQueryLoader(({ organizationId }) =>
loadQuery(consoleEnvironment, employeeDocumentsQuery, { loadQuery(consoleEnvironment, employeeDocumentsQuery, {
organizationId: organizationId!, organizationId: organizationId!,
}) }),
), ),
Component: withQueryRef( Component: withQueryRef(
lazy( lazy(
() => import("./pages/organizations/employee/EmployeeDocumentsPage") () =>
) import("./pages/organizations/employee/EmployeeDocumentsPage"),
),
), ),
}, },
{ {
@@ -169,13 +170,13 @@ const routes = [
loader: loaderFromQueryLoader(({ documentId }) => loader: loaderFromQueryLoader(({ documentId }) =>
loadQuery(consoleEnvironment, employeeDocumentSignatureQuery, { loadQuery(consoleEnvironment, employeeDocumentSignatureQuery, {
documentId: documentId!, documentId: documentId!,
}) }),
), ),
Component: withQueryRef( Component: withQueryRef(
lazy( lazy(
() => () =>
import("./pages/organizations/employee/EmployeeDocumentSignaturePage") import("./pages/organizations/employee/EmployeeDocumentSignaturePage"),
) ),
), ),
}, },
], ],
@@ -209,10 +210,10 @@ const routes = [
loader: loaderFromQueryLoader(({ organizationId }) => loader: loaderFromQueryLoader(({ organizationId }) =>
loadQuery(consoleEnvironment, organizationViewQuery, { loadQuery(consoleEnvironment, organizationViewQuery, {
organizationId: organizationId!, organizationId: organizationId!,
}) }),
), ),
Component: withQueryRef( Component: withQueryRef(
lazy(() => import("./pages/organizations/SettingsPage")) lazy(() => import("./pages/organizations/SettingsPage")),
), ),
children: [ children: [
{ {
@@ -236,7 +237,7 @@ const routes = [
{ {
path: "domain", path: "domain",
Component: lazy( Component: lazy(
() => import("./pages/organizations/settings/DomainSettingsTab") () => import("./pages/organizations/settings/DomainSettingsTab"),
), ),
}, },
// { // {

View File

@@ -753,6 +753,45 @@ func (s *OrganizationService) GetOrganizationForMembership(ctx context.Context,
return organization, nil return organization, nil
} }
func (s *OrganizationService) GetOrganizationForInvitation(ctx context.Context, invitationID gid.GID) (*coredata.Organization, error) {
var (
scope = coredata.NewScopeFromObjectID(invitationID)
organization = &coredata.Organization{}
)
err := s.pg.WithConn(
ctx,
func(conn pg.Conn) error {
invitation := &coredata.Invitation{}
err := invitation.LoadByID(ctx, conn, scope, invitationID)
if err != nil {
if err == coredata.ErrResourceNotFound {
return NewInvitationNotFoundError(invitationID)
}
return fmt.Errorf("cannot load invitation: %w", err)
}
err = organization.LoadByID(ctx, conn, scope, invitation.OrganizationID)
if err != nil {
if err == coredata.ErrResourceNotFound {
return NewOrganizationNotFoundError(invitation.OrganizationID)
}
return fmt.Errorf("cannot load organization: %w", err)
}
return nil
},
)
if err != nil {
return nil, err
}
return organization, nil
}
func (s OrganizationService) GenerateLogoURL( func (s OrganizationService) GenerateLogoURL(
ctx context.Context, ctx context.Context,
organizationID gid.GID, organizationID gid.GID,

View File

@@ -145,6 +145,7 @@ type Identity implements Node {
after: CursorKey after: CursorKey
last: Int last: Int
before: CursorKey before: CursorKey
orderBy: InvitationOrder
): InvitationConnection! @goField(forceResolver: true) @isViewer ): InvitationConnection! @goField(forceResolver: true) @isViewer
sessions( sessions(
@@ -224,6 +225,17 @@ type Organization implements Node {
availableApplications: [Application!]! availableApplications: [Application!]!
} }
enum MembershipRole
@goModel(model: "go.probo.inc/probo/pkg/coredata.MembershipRole") {
OWNER @goEnum(value: "go.probo.inc/probo/pkg/coredata.MembershipRoleOwner")
ADMIN @goEnum(value: "go.probo.inc/probo/pkg/coredata.MembershipRoleAdmin")
EMPLOYEE
@goEnum(value: "go.probo.inc/probo/pkg/coredata.MembershipRoleEmployee")
VIEWER @goEnum(value: "go.probo.inc/probo/pkg/coredata.MembershipRoleViewer")
AUDITOR
@goEnum(value: "go.probo.inc/probo/pkg/coredata.MembershipRoleAuditor")
}
type Membership implements Node { type Membership implements Node {
id: ID! id: ID!
identityId: ID! identityId: ID!
@@ -231,6 +243,7 @@ type Membership implements Node {
profile: IdentityProfile! profile: IdentityProfile!
identity: Identity! @goField(forceResolver: true) identity: Identity! @goField(forceResolver: true)
organization: Organization! @goField(forceResolver: true) organization: Organization! @goField(forceResolver: true)
role: MembershipRole!
permissions: [Permission!]! permissions: [Permission!]!
provisionedBy: ProvisioningSource! provisionedBy: ProvisioningSource!
active: Boolean! active: Boolean!
@@ -242,10 +255,12 @@ type Membership implements Node {
type Invitation implements Node { type Invitation implements Node {
id: ID! id: ID!
email: EmailAddr! email: EmailAddr!
role: MembershipRole!
expiresAt: Datetime! expiresAt: Datetime!
acceptedAt: Datetime acceptedAt: Datetime
createdAt: Datetime! createdAt: Datetime!
status: InvitationStatus! status: InvitationStatus!
organization: Organization! @goField(forceResolver: true)
} }
type Session implements Node { type Session implements Node {
@@ -404,14 +419,6 @@ enum ProvisioningSource {
enum MembershipOrderField enum MembershipOrderField
@goModel(model: "go.probo.inc/probo/pkg/coredata.MembershipOrderField") { @goModel(model: "go.probo.inc/probo/pkg/coredata.MembershipOrderField") {
FULL_NAME
@goEnum(
value: "go.probo.inc/probo/pkg/coredata.MembershipOrderFieldFullName"
)
EMAIL_ADDRESS
@goEnum(
value: "go.probo.inc/probo/pkg/coredata.MembershipOrderFieldEmailAddress"
)
ROLE ROLE
@goEnum(value: "go.probo.inc/probo/pkg/coredata.MembershipOrderFieldRole") @goEnum(value: "go.probo.inc/probo/pkg/coredata.MembershipOrderFieldRole")
CREATED_AT CREATED_AT
@@ -442,6 +449,34 @@ type MembershipEdge {
cursor: CursorKey! cursor: CursorKey!
} }
enum InvitationOrderField
@goModel(model: "go.probo.inc/probo/pkg/coredata.InvitationOrderField") {
EMAIL
@goEnum(value: "go.probo.inc/probo/pkg/coredata.InvitationOrderFieldEmail")
ROLE
@goEnum(value: "go.probo.inc/probo/pkg/coredata.InvitationOrderFieldRole")
CREATED_AT
@goEnum(
value: "go.probo.inc/probo/pkg/coredata.InvitationOrderFieldCreatedAt"
)
EXPIRES_AT
@goEnum(
value: "go.probo.inc/probo/pkg/coredata.InvitationOrderFieldExpiresAt"
)
ACCEPTED_AT
@goEnum(
value: "go.probo.inc/probo/pkg/coredata.InvitationOrderFieldAcceptedAt"
)
}
input InvitationOrder
@goModel(
model: "go.probo.inc/probo/pkg/server/api/connect/v1/types.InvitationOrderBy"
) {
direction: OrderDirection!
field: InvitationOrderField!
}
type InvitationConnection type InvitationConnection
@goModel( @goModel(
model: "go.probo.inc/probo/pkg/server/api/connect/v1/types.InvitationConnection" model: "go.probo.inc/probo/pkg/server/api/connect/v1/types.InvitationConnection"

View File

@@ -47,6 +47,7 @@ type Config struct {
type ResolverRoot interface { type ResolverRoot interface {
Identity() IdentityResolver Identity() IdentityResolver
Invitation() InvitationResolver
InvitationConnection() InvitationConnectionResolver InvitationConnection() InvitationConnectionResolver
Membership() MembershipResolver Membership() MembershipResolver
MembershipConnection() MembershipConnectionResolver MembershipConnection() MembershipConnectionResolver
@@ -132,7 +133,7 @@ type ComplexityRoot struct {
EmailVerified func(childComplexity int) int EmailVerified func(childComplexity int) int
ID func(childComplexity int) int ID func(childComplexity int) int
Memberships func(childComplexity int, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.MembershipOrderBy) int Memberships func(childComplexity int, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.MembershipOrderBy) int
PendingInvitations func(childComplexity int, first *int, after *page.CursorKey, last *int, before *page.CursorKey) int PendingInvitations func(childComplexity int, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.InvitationOrderBy) int
PersonalAPIKeys func(childComplexity int, first *int, after *page.CursorKey, last *int, before *page.CursorKey) int PersonalAPIKeys func(childComplexity int, first *int, after *page.CursorKey, last *int, before *page.CursorKey) int
ProfileFor func(childComplexity int, organizationID gid.GID) int ProfileFor func(childComplexity int, organizationID gid.GID) int
Sessions func(childComplexity int, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.SessionOrder) int Sessions func(childComplexity int, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.SessionOrder) int
@@ -166,6 +167,8 @@ type ComplexityRoot struct {
Email func(childComplexity int) int Email func(childComplexity int) int
ExpiresAt func(childComplexity int) int ExpiresAt func(childComplexity int) int
ID func(childComplexity int) int ID func(childComplexity int) int
Organization func(childComplexity int) int
Role func(childComplexity int) int
Status func(childComplexity int) int Status func(childComplexity int) int
} }
@@ -196,6 +199,7 @@ type ComplexityRoot struct {
Permissions func(childComplexity int) int Permissions func(childComplexity int) int
Profile func(childComplexity int) int Profile func(childComplexity int) int
ProvisionedBy func(childComplexity int) int ProvisionedBy func(childComplexity int) int
Role func(childComplexity int) int
} }
MembershipConnection struct { MembershipConnection struct {
@@ -429,10 +433,13 @@ type ComplexityRoot struct {
type IdentityResolver interface { type IdentityResolver interface {
Memberships(ctx context.Context, obj *types.Identity, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.MembershipOrderBy) (*types.MembershipConnection, error) Memberships(ctx context.Context, obj *types.Identity, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.MembershipOrderBy) (*types.MembershipConnection, error)
PendingInvitations(ctx context.Context, obj *types.Identity, first *int, after *page.CursorKey, last *int, before *page.CursorKey) (*types.InvitationConnection, error) PendingInvitations(ctx context.Context, obj *types.Identity, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.InvitationOrderBy) (*types.InvitationConnection, error)
Sessions(ctx context.Context, obj *types.Identity, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.SessionOrder) (*types.SessionConnection, error) Sessions(ctx context.Context, obj *types.Identity, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.SessionOrder) (*types.SessionConnection, error)
PersonalAPIKeys(ctx context.Context, obj *types.Identity, first *int, after *page.CursorKey, last *int, before *page.CursorKey) (*types.PersonalAPIKeyConnection, error) PersonalAPIKeys(ctx context.Context, obj *types.Identity, first *int, after *page.CursorKey, last *int, before *page.CursorKey) (*types.PersonalAPIKeyConnection, error)
} }
type InvitationResolver interface {
Organization(ctx context.Context, obj *types.Invitation) (*types.Organization, error)
}
type InvitationConnectionResolver interface { type InvitationConnectionResolver interface {
TotalCount(ctx context.Context, obj *types.InvitationConnection) (int, error) TotalCount(ctx context.Context, obj *types.InvitationConnection) (int, error)
} }
@@ -693,7 +700,7 @@ func (e *executableSchema) Complexity(ctx context.Context, typeName, field strin
return 0, false return 0, false
} }
return e.complexity.Identity.PendingInvitations(childComplexity, args["first"].(*int), args["after"].(*page.CursorKey), args["last"].(*int), args["before"].(*page.CursorKey)), true return e.complexity.Identity.PendingInvitations(childComplexity, args["first"].(*int), args["after"].(*page.CursorKey), args["last"].(*int), args["before"].(*page.CursorKey), args["orderBy"].(*types.InvitationOrderBy)), true
case "Identity.personalAPIKeys": case "Identity.personalAPIKeys":
if e.complexity.Identity.PersonalAPIKeys == nil { if e.complexity.Identity.PersonalAPIKeys == nil {
break break
@@ -873,6 +880,18 @@ func (e *executableSchema) Complexity(ctx context.Context, typeName, field strin
} }
return e.complexity.Invitation.ID(childComplexity), true return e.complexity.Invitation.ID(childComplexity), true
case "Invitation.organization":
if e.complexity.Invitation.Organization == nil {
break
}
return e.complexity.Invitation.Organization(childComplexity), true
case "Invitation.role":
if e.complexity.Invitation.Role == nil {
break
}
return e.complexity.Invitation.Role(childComplexity), true
case "Invitation.status": case "Invitation.status":
if e.complexity.Invitation.Status == nil { if e.complexity.Invitation.Status == nil {
break break
@@ -985,6 +1004,12 @@ func (e *executableSchema) Complexity(ctx context.Context, typeName, field strin
} }
return e.complexity.Membership.ProvisionedBy(childComplexity), true return e.complexity.Membership.ProvisionedBy(childComplexity), true
case "Membership.role":
if e.complexity.Membership.Role == nil {
break
}
return e.complexity.Membership.Role(childComplexity), true
case "MembershipConnection.edges": case "MembershipConnection.edges":
if e.complexity.MembershipConnection.Edges == nil { if e.complexity.MembershipConnection.Edges == nil {
@@ -1937,6 +1962,7 @@ func (e *executableSchema) Exec(ctx context.Context) graphql.ResponseHandler {
ec.unmarshalInputDeleteOrganizationInput, ec.unmarshalInputDeleteOrganizationInput,
ec.unmarshalInputDeleteSAMLConfigurationInput, ec.unmarshalInputDeleteSAMLConfigurationInput,
ec.unmarshalInputForgotPasswordInput, ec.unmarshalInputForgotPasswordInput,
ec.unmarshalInputInvitationOrder,
ec.unmarshalInputInviteMemberInput, ec.unmarshalInputInviteMemberInput,
ec.unmarshalInputMembershipOrder, ec.unmarshalInputMembershipOrder,
ec.unmarshalInputRemoveIPAllowlistEntryInput, ec.unmarshalInputRemoveIPAllowlistEntryInput,
@@ -2199,6 +2225,7 @@ type Identity implements Node {
after: CursorKey after: CursorKey
last: Int last: Int
before: CursorKey before: CursorKey
orderBy: InvitationOrder
): InvitationConnection! @goField(forceResolver: true) @isViewer ): InvitationConnection! @goField(forceResolver: true) @isViewer
sessions( sessions(
@@ -2278,6 +2305,17 @@ type Organization implements Node {
availableApplications: [Application!]! availableApplications: [Application!]!
} }
enum MembershipRole
@goModel(model: "go.probo.inc/probo/pkg/coredata.MembershipRole") {
OWNER @goEnum(value: "go.probo.inc/probo/pkg/coredata.MembershipRoleOwner")
ADMIN @goEnum(value: "go.probo.inc/probo/pkg/coredata.MembershipRoleAdmin")
EMPLOYEE
@goEnum(value: "go.probo.inc/probo/pkg/coredata.MembershipRoleEmployee")
VIEWER @goEnum(value: "go.probo.inc/probo/pkg/coredata.MembershipRoleViewer")
AUDITOR
@goEnum(value: "go.probo.inc/probo/pkg/coredata.MembershipRoleAuditor")
}
type Membership implements Node { type Membership implements Node {
id: ID! id: ID!
identityId: ID! identityId: ID!
@@ -2285,6 +2323,7 @@ type Membership implements Node {
profile: IdentityProfile! profile: IdentityProfile!
identity: Identity! @goField(forceResolver: true) identity: Identity! @goField(forceResolver: true)
organization: Organization! @goField(forceResolver: true) organization: Organization! @goField(forceResolver: true)
role: MembershipRole!
permissions: [Permission!]! permissions: [Permission!]!
provisionedBy: ProvisioningSource! provisionedBy: ProvisioningSource!
active: Boolean! active: Boolean!
@@ -2296,10 +2335,12 @@ type Membership implements Node {
type Invitation implements Node { type Invitation implements Node {
id: ID! id: ID!
email: EmailAddr! email: EmailAddr!
role: MembershipRole!
expiresAt: Datetime! expiresAt: Datetime!
acceptedAt: Datetime acceptedAt: Datetime
createdAt: Datetime! createdAt: Datetime!
status: InvitationStatus! status: InvitationStatus!
organization: Organization! @goField(forceResolver: true)
} }
type Session implements Node { type Session implements Node {
@@ -2458,14 +2499,6 @@ enum ProvisioningSource {
enum MembershipOrderField enum MembershipOrderField
@goModel(model: "go.probo.inc/probo/pkg/coredata.MembershipOrderField") { @goModel(model: "go.probo.inc/probo/pkg/coredata.MembershipOrderField") {
FULL_NAME
@goEnum(
value: "go.probo.inc/probo/pkg/coredata.MembershipOrderFieldFullName"
)
EMAIL_ADDRESS
@goEnum(
value: "go.probo.inc/probo/pkg/coredata.MembershipOrderFieldEmailAddress"
)
ROLE ROLE
@goEnum(value: "go.probo.inc/probo/pkg/coredata.MembershipOrderFieldRole") @goEnum(value: "go.probo.inc/probo/pkg/coredata.MembershipOrderFieldRole")
CREATED_AT CREATED_AT
@@ -2496,6 +2529,34 @@ type MembershipEdge {
cursor: CursorKey! cursor: CursorKey!
} }
enum InvitationOrderField
@goModel(model: "go.probo.inc/probo/pkg/coredata.InvitationOrderField") {
EMAIL
@goEnum(value: "go.probo.inc/probo/pkg/coredata.InvitationOrderFieldEmail")
ROLE
@goEnum(value: "go.probo.inc/probo/pkg/coredata.InvitationOrderFieldRole")
CREATED_AT
@goEnum(
value: "go.probo.inc/probo/pkg/coredata.InvitationOrderFieldCreatedAt"
)
EXPIRES_AT
@goEnum(
value: "go.probo.inc/probo/pkg/coredata.InvitationOrderFieldExpiresAt"
)
ACCEPTED_AT
@goEnum(
value: "go.probo.inc/probo/pkg/coredata.InvitationOrderFieldAcceptedAt"
)
}
input InvitationOrder
@goModel(
model: "go.probo.inc/probo/pkg/server/api/connect/v1/types.InvitationOrderBy"
) {
direction: OrderDirection!
field: InvitationOrderField!
}
type InvitationConnection type InvitationConnection
@goModel( @goModel(
model: "go.probo.inc/probo/pkg/server/api/connect/v1/types.InvitationConnection" model: "go.probo.inc/probo/pkg/server/api/connect/v1/types.InvitationConnection"
@@ -2908,6 +2969,11 @@ func (ec *executionContext) field_Identity_pendingInvitations_args(ctx context.C
return nil, err return nil, err
} }
args["before"] = arg3 args["before"] = arg3
arg4, err := graphql.ProcessArgField(ctx, rawArgs, "orderBy", ec.unmarshalOInvitationOrder2ᚖgoᚗproboᚗincᚋproboᚋpkgᚋserverᚋapiᚋconnectᚋv1ᚋtypesᚐInvitationOrderBy)
if err != nil {
return nil, err
}
args["orderBy"] = arg4
return args, nil return args, nil
} }
@@ -4241,7 +4307,7 @@ func (ec *executionContext) _Identity_pendingInvitations(ctx context.Context, fi
ec.fieldContext_Identity_pendingInvitations, ec.fieldContext_Identity_pendingInvitations,
func(ctx context.Context) (any, error) { func(ctx context.Context) (any, error) {
fc := graphql.GetFieldContext(ctx) fc := graphql.GetFieldContext(ctx)
return ec.resolvers.Identity().PendingInvitations(ctx, obj, fc.Args["first"].(*int), fc.Args["after"].(*page.CursorKey), fc.Args["last"].(*int), fc.Args["before"].(*page.CursorKey)) return ec.resolvers.Identity().PendingInvitations(ctx, obj, fc.Args["first"].(*int), fc.Args["after"].(*page.CursorKey), fc.Args["last"].(*int), fc.Args["before"].(*page.CursorKey), fc.Args["orderBy"].(*types.InvitationOrderBy))
}, },
func(ctx context.Context, next graphql.Resolver) graphql.Resolver { func(ctx context.Context, next graphql.Resolver) graphql.Resolver {
directive0 := next directive0 := next
@@ -5178,6 +5244,35 @@ func (ec *executionContext) fieldContext_Invitation_email(_ context.Context, fie
return fc, nil return fc, nil
} }
func (ec *executionContext) _Invitation_role(ctx context.Context, field graphql.CollectedField, obj *types.Invitation) (ret graphql.Marshaler) {
return graphql.ResolveField(
ctx,
ec.OperationContext,
field,
ec.fieldContext_Invitation_role,
func(ctx context.Context) (any, error) {
return obj.Role, nil
},
nil,
ec.marshalNMembershipRole2goᚗproboᚗincᚋproboᚋpkgᚋcoredataᚐMembershipRole,
true,
true,
)
}
func (ec *executionContext) fieldContext_Invitation_role(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) {
fc = &graphql.FieldContext{
Object: "Invitation",
Field: field,
IsMethod: false,
IsResolver: false,
Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) {
return nil, errors.New("field of type MembershipRole does not have child fields")
},
}
return fc, nil
}
func (ec *executionContext) _Invitation_expiresAt(ctx context.Context, field graphql.CollectedField, obj *types.Invitation) (ret graphql.Marshaler) { func (ec *executionContext) _Invitation_expiresAt(ctx context.Context, field graphql.CollectedField, obj *types.Invitation) (ret graphql.Marshaler) {
return graphql.ResolveField( return graphql.ResolveField(
ctx, ctx,
@@ -5294,6 +5389,57 @@ func (ec *executionContext) fieldContext_Invitation_status(_ context.Context, fi
return fc, nil return fc, nil
} }
func (ec *executionContext) _Invitation_organization(ctx context.Context, field graphql.CollectedField, obj *types.Invitation) (ret graphql.Marshaler) {
return graphql.ResolveField(
ctx,
ec.OperationContext,
field,
ec.fieldContext_Invitation_organization,
func(ctx context.Context) (any, error) {
return ec.resolvers.Invitation().Organization(ctx, obj)
},
nil,
ec.marshalNOrganization2ᚖgoᚗproboᚗincᚋproboᚋpkgᚋserverᚋapiᚋconnectᚋv1ᚋtypesᚐOrganization,
true,
true,
)
}
func (ec *executionContext) fieldContext_Invitation_organization(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) {
fc = &graphql.FieldContext{
Object: "Invitation",
Field: field,
IsMethod: true,
IsResolver: true,
Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) {
switch field.Name {
case "id":
return ec.fieldContext_Organization_id(ctx, field)
case "name":
return ec.fieldContext_Organization_name(ctx, field)
case "logoUrl":
return ec.fieldContext_Organization_logoUrl(ctx, field)
case "horizontalLogoUrl":
return ec.fieldContext_Organization_horizontalLogoUrl(ctx, field)
case "createdAt":
return ec.fieldContext_Organization_createdAt(ctx, field)
case "updatedAt":
return ec.fieldContext_Organization_updatedAt(ctx, field)
case "members":
return ec.fieldContext_Organization_members(ctx, field)
case "invitations":
return ec.fieldContext_Organization_invitations(ctx, field)
case "samlConfigurations":
return ec.fieldContext_Organization_samlConfigurations(ctx, field)
case "availableApplications":
return ec.fieldContext_Organization_availableApplications(ctx, field)
}
return nil, fmt.Errorf("no field named %q was found under type Organization", field.Name)
},
}
return fc, nil
}
func (ec *executionContext) _InvitationConnection_edges(ctx context.Context, field graphql.CollectedField, obj *types.InvitationConnection) (ret graphql.Marshaler) { func (ec *executionContext) _InvitationConnection_edges(ctx context.Context, field graphql.CollectedField, obj *types.InvitationConnection) (ret graphql.Marshaler) {
return graphql.ResolveField( return graphql.ResolveField(
ctx, ctx,
@@ -5425,6 +5571,8 @@ func (ec *executionContext) fieldContext_InvitationEdge_node(_ context.Context,
return ec.fieldContext_Invitation_id(ctx, field) return ec.fieldContext_Invitation_id(ctx, field)
case "email": case "email":
return ec.fieldContext_Invitation_email(ctx, field) return ec.fieldContext_Invitation_email(ctx, field)
case "role":
return ec.fieldContext_Invitation_role(ctx, field)
case "expiresAt": case "expiresAt":
return ec.fieldContext_Invitation_expiresAt(ctx, field) return ec.fieldContext_Invitation_expiresAt(ctx, field)
case "acceptedAt": case "acceptedAt":
@@ -5433,6 +5581,8 @@ func (ec *executionContext) fieldContext_InvitationEdge_node(_ context.Context,
return ec.fieldContext_Invitation_createdAt(ctx, field) return ec.fieldContext_Invitation_createdAt(ctx, field)
case "status": case "status":
return ec.fieldContext_Invitation_status(ctx, field) return ec.fieldContext_Invitation_status(ctx, field)
case "organization":
return ec.fieldContext_Invitation_organization(ctx, field)
} }
return nil, fmt.Errorf("no field named %q was found under type Invitation", field.Name) return nil, fmt.Errorf("no field named %q was found under type Invitation", field.Name)
}, },
@@ -5760,6 +5910,35 @@ func (ec *executionContext) fieldContext_Membership_organization(_ context.Conte
return fc, nil return fc, nil
} }
func (ec *executionContext) _Membership_role(ctx context.Context, field graphql.CollectedField, obj *types.Membership) (ret graphql.Marshaler) {
return graphql.ResolveField(
ctx,
ec.OperationContext,
field,
ec.fieldContext_Membership_role,
func(ctx context.Context) (any, error) {
return obj.Role, nil
},
nil,
ec.marshalNMembershipRole2goᚗproboᚗincᚋproboᚋpkgᚋcoredataᚐMembershipRole,
true,
true,
)
}
func (ec *executionContext) fieldContext_Membership_role(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) {
fc = &graphql.FieldContext{
Object: "Membership",
Field: field,
IsMethod: false,
IsResolver: false,
Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) {
return nil, errors.New("field of type MembershipRole does not have child fields")
},
}
return fc, nil
}
func (ec *executionContext) _Membership_permissions(ctx context.Context, field graphql.CollectedField, obj *types.Membership) (ret graphql.Marshaler) { func (ec *executionContext) _Membership_permissions(ctx context.Context, field graphql.CollectedField, obj *types.Membership) (ret graphql.Marshaler) {
return graphql.ResolveField( return graphql.ResolveField(
ctx, ctx,
@@ -6087,6 +6266,8 @@ func (ec *executionContext) fieldContext_MembershipEdge_node(_ context.Context,
return ec.fieldContext_Membership_identity(ctx, field) return ec.fieldContext_Membership_identity(ctx, field)
case "organization": case "organization":
return ec.fieldContext_Membership_organization(ctx, field) return ec.fieldContext_Membership_organization(ctx, field)
case "role":
return ec.fieldContext_Membership_role(ctx, field)
case "permissions": case "permissions":
return ec.fieldContext_Membership_permissions(ctx, field) return ec.fieldContext_Membership_permissions(ctx, field)
case "provisionedBy": case "provisionedBy":
@@ -13046,6 +13227,40 @@ func (ec *executionContext) unmarshalInputForgotPasswordInput(ctx context.Contex
return it, nil return it, nil
} }
func (ec *executionContext) unmarshalInputInvitationOrder(ctx context.Context, obj any) (types.InvitationOrderBy, error) {
var it types.InvitationOrderBy
asMap := map[string]any{}
for k, v := range obj.(map[string]any) {
asMap[k] = v
}
fieldsInOrder := [...]string{"direction", "field"}
for _, k := range fieldsInOrder {
v, ok := asMap[k]
if !ok {
continue
}
switch k {
case "direction":
ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("direction"))
data, err := ec.unmarshalNOrderDirection2goᚗproboᚗincᚋproboᚋpkgᚋpageᚐOrderDirection(ctx, v)
if err != nil {
return it, err
}
it.Direction = data
case "field":
ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("field"))
data, err := ec.unmarshalNInvitationOrderField2goᚗproboᚗincᚋproboᚋpkgᚋcoredataᚐInvitationOrderField(ctx, v)
if err != nil {
return it, err
}
it.Field = data
}
}
return it, nil
}
func (ec *executionContext) unmarshalInputInviteMemberInput(ctx context.Context, obj any) (types.InviteMemberInput, error) { func (ec *executionContext) unmarshalInputInviteMemberInput(ctx context.Context, obj any) (types.InviteMemberInput, error) {
var it types.InviteMemberInput var it types.InviteMemberInput
asMap := map[string]any{} asMap := map[string]any{}
@@ -14750,30 +14965,71 @@ func (ec *executionContext) _Invitation(ctx context.Context, sel ast.SelectionSe
case "id": case "id":
out.Values[i] = ec._Invitation_id(ctx, field, obj) out.Values[i] = ec._Invitation_id(ctx, field, obj)
if out.Values[i] == graphql.Null { if out.Values[i] == graphql.Null {
out.Invalids++ atomic.AddUint32(&out.Invalids, 1)
} }
case "email": case "email":
out.Values[i] = ec._Invitation_email(ctx, field, obj) out.Values[i] = ec._Invitation_email(ctx, field, obj)
if out.Values[i] == graphql.Null { if out.Values[i] == graphql.Null {
out.Invalids++ atomic.AddUint32(&out.Invalids, 1)
}
case "role":
out.Values[i] = ec._Invitation_role(ctx, field, obj)
if out.Values[i] == graphql.Null {
atomic.AddUint32(&out.Invalids, 1)
} }
case "expiresAt": case "expiresAt":
out.Values[i] = ec._Invitation_expiresAt(ctx, field, obj) out.Values[i] = ec._Invitation_expiresAt(ctx, field, obj)
if out.Values[i] == graphql.Null { if out.Values[i] == graphql.Null {
out.Invalids++ atomic.AddUint32(&out.Invalids, 1)
} }
case "acceptedAt": case "acceptedAt":
out.Values[i] = ec._Invitation_acceptedAt(ctx, field, obj) out.Values[i] = ec._Invitation_acceptedAt(ctx, field, obj)
case "createdAt": case "createdAt":
out.Values[i] = ec._Invitation_createdAt(ctx, field, obj) out.Values[i] = ec._Invitation_createdAt(ctx, field, obj)
if out.Values[i] == graphql.Null { if out.Values[i] == graphql.Null {
out.Invalids++ atomic.AddUint32(&out.Invalids, 1)
} }
case "status": case "status":
out.Values[i] = ec._Invitation_status(ctx, field, obj) out.Values[i] = ec._Invitation_status(ctx, field, obj)
if out.Values[i] == graphql.Null { if out.Values[i] == graphql.Null {
out.Invalids++ atomic.AddUint32(&out.Invalids, 1)
} }
case "organization":
field := field
innerFunc := func(ctx context.Context, fs *graphql.FieldSet) (res graphql.Marshaler) {
defer func() {
if r := recover(); r != nil {
ec.Error(ctx, ec.Recover(ctx, r))
}
}()
res = ec._Invitation_organization(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: default:
panic("unknown field " + strconv.Quote(field.Name)) panic("unknown field " + strconv.Quote(field.Name))
} }
@@ -15063,6 +15319,11 @@ func (ec *executionContext) _Membership(ctx context.Context, sel ast.SelectionSe
} }
out.Concurrently(i, func(ctx context.Context) graphql.Marshaler { return innerFunc(ctx, out) }) out.Concurrently(i, func(ctx context.Context) graphql.Marshaler { return innerFunc(ctx, out) })
case "role":
out.Values[i] = ec._Membership_role(ctx, field, obj)
if out.Values[i] == graphql.Null {
atomic.AddUint32(&out.Invalids, 1)
}
case "permissions": case "permissions":
out.Values[i] = ec._Membership_permissions(ctx, field, obj) out.Values[i] = ec._Membership_permissions(ctx, field, obj)
if out.Values[i] == graphql.Null { if out.Values[i] == graphql.Null {
@@ -18209,6 +18470,40 @@ func (ec *executionContext) marshalNInvitationEdge2ᚖgoᚗproboᚗincᚋprobo
return ec._InvitationEdge(ctx, sel, v) return ec._InvitationEdge(ctx, sel, v)
} }
func (ec *executionContext) unmarshalNInvitationOrderField2goᚗproboᚗincᚋproboᚋpkgᚋcoredataᚐInvitationOrderField(ctx context.Context, v any) (coredata.InvitationOrderField, error) {
tmp, err := graphql.UnmarshalString(v)
res := unmarshalNInvitationOrderField2goᚗproboᚗincᚋproboᚋpkgᚋcoredataᚐInvitationOrderField[tmp]
return res, graphql.ErrorOnPath(ctx, err)
}
func (ec *executionContext) marshalNInvitationOrderField2goᚗproboᚗincᚋproboᚋpkgᚋcoredataᚐInvitationOrderField(ctx context.Context, sel ast.SelectionSet, v coredata.InvitationOrderField) graphql.Marshaler {
_ = sel
res := graphql.MarshalString(marshalNInvitationOrderField2goᚗproboᚗincᚋproboᚋpkgᚋcoredataᚐInvitationOrderField[v])
if res == graphql.Null {
if !graphql.HasFieldError(ctx, graphql.GetFieldContext(ctx)) {
graphql.AddErrorf(ctx, "the requested element is null which the schema does not allow")
}
}
return res
}
var (
unmarshalNInvitationOrderField2goᚗproboᚗincᚋproboᚋpkgᚋcoredataᚐInvitationOrderField = map[string]coredata.InvitationOrderField{
"EMAIL": coredata.InvitationOrderFieldEmail,
"ROLE": coredata.InvitationOrderFieldRole,
"CREATED_AT": coredata.InvitationOrderFieldCreatedAt,
"EXPIRES_AT": coredata.InvitationOrderFieldExpiresAt,
"ACCEPTED_AT": coredata.InvitationOrderFieldAcceptedAt,
}
marshalNInvitationOrderField2goᚗproboᚗincᚋproboᚋpkgᚋcoredataᚐInvitationOrderField = map[coredata.InvitationOrderField]string{
coredata.InvitationOrderFieldEmail: "EMAIL",
coredata.InvitationOrderFieldRole: "ROLE",
coredata.InvitationOrderFieldCreatedAt: "CREATED_AT",
coredata.InvitationOrderFieldExpiresAt: "EXPIRES_AT",
coredata.InvitationOrderFieldAcceptedAt: "ACCEPTED_AT",
}
)
func (ec *executionContext) unmarshalNInvitationStatus2goᚗproboᚗincᚋproboᚋpkgᚋcoredataᚐInvitationStatus(ctx context.Context, v any) (coredata.InvitationStatus, error) { func (ec *executionContext) unmarshalNInvitationStatus2goᚗproboᚗincᚋproboᚋpkgᚋcoredataᚐInvitationStatus(ctx context.Context, v any) (coredata.InvitationStatus, error) {
tmp, err := graphql.UnmarshalString(v) tmp, err := graphql.UnmarshalString(v)
res := unmarshalNInvitationStatus2goᚗproboᚗincᚋproboᚋpkgᚋcoredataᚐInvitationStatus[tmp] res := unmarshalNInvitationStatus2goᚗproboᚗincᚋproboᚋpkgᚋcoredataᚐInvitationStatus[tmp]
@@ -18355,19 +18650,49 @@ func (ec *executionContext) marshalNMembershipOrderField2goᚗproboᚗincᚋprob
var ( var (
unmarshalNMembershipOrderField2goᚗproboᚗincᚋproboᚋpkgᚋcoredataᚐMembershipOrderField = map[string]coredata.MembershipOrderField{ unmarshalNMembershipOrderField2goᚗproboᚗincᚋproboᚋpkgᚋcoredataᚐMembershipOrderField = map[string]coredata.MembershipOrderField{
"FULL_NAME": coredata.MembershipOrderFieldFullName,
"EMAIL_ADDRESS": coredata.MembershipOrderFieldEmailAddress,
"ROLE": coredata.MembershipOrderFieldRole, "ROLE": coredata.MembershipOrderFieldRole,
"CREATED_AT": coredata.MembershipOrderFieldCreatedAt, "CREATED_AT": coredata.MembershipOrderFieldCreatedAt,
} }
marshalNMembershipOrderField2goᚗproboᚗincᚋproboᚋpkgᚋcoredataᚐMembershipOrderField = map[coredata.MembershipOrderField]string{ marshalNMembershipOrderField2goᚗproboᚗincᚋproboᚋpkgᚋcoredataᚐMembershipOrderField = map[coredata.MembershipOrderField]string{
coredata.MembershipOrderFieldFullName: "FULL_NAME",
coredata.MembershipOrderFieldEmailAddress: "EMAIL_ADDRESS",
coredata.MembershipOrderFieldRole: "ROLE", coredata.MembershipOrderFieldRole: "ROLE",
coredata.MembershipOrderFieldCreatedAt: "CREATED_AT", coredata.MembershipOrderFieldCreatedAt: "CREATED_AT",
} }
) )
func (ec *executionContext) unmarshalNMembershipRole2goᚗproboᚗincᚋproboᚋpkgᚋcoredataᚐMembershipRole(ctx context.Context, v any) (coredata.MembershipRole, error) {
tmp, err := graphql.UnmarshalString(v)
res := unmarshalNMembershipRole2goᚗproboᚗincᚋproboᚋpkgᚋcoredataᚐMembershipRole[tmp]
return res, graphql.ErrorOnPath(ctx, err)
}
func (ec *executionContext) marshalNMembershipRole2goᚗproboᚗincᚋproboᚋpkgᚋcoredataᚐMembershipRole(ctx context.Context, sel ast.SelectionSet, v coredata.MembershipRole) graphql.Marshaler {
_ = sel
res := graphql.MarshalString(marshalNMembershipRole2goᚗproboᚗincᚋproboᚋpkgᚋcoredataᚐMembershipRole[v])
if res == graphql.Null {
if !graphql.HasFieldError(ctx, graphql.GetFieldContext(ctx)) {
graphql.AddErrorf(ctx, "the requested element is null which the schema does not allow")
}
}
return res
}
var (
unmarshalNMembershipRole2goᚗproboᚗincᚋproboᚋpkgᚋcoredataᚐMembershipRole = map[string]coredata.MembershipRole{
"OWNER": coredata.MembershipRoleOwner,
"ADMIN": coredata.MembershipRoleAdmin,
"EMPLOYEE": coredata.MembershipRoleEmployee,
"VIEWER": coredata.MembershipRoleViewer,
"AUDITOR": coredata.MembershipRoleAuditor,
}
marshalNMembershipRole2goᚗproboᚗincᚋproboᚋpkgᚋcoredataᚐMembershipRole = map[coredata.MembershipRole]string{
coredata.MembershipRoleOwner: "OWNER",
coredata.MembershipRoleAdmin: "ADMIN",
coredata.MembershipRoleEmployee: "EMPLOYEE",
coredata.MembershipRoleViewer: "VIEWER",
coredata.MembershipRoleAuditor: "AUDITOR",
}
)
func (ec *executionContext) unmarshalNOrderDirection2goᚗproboᚗincᚋproboᚋpkgᚋpageᚐOrderDirection(ctx context.Context, v any) (page.OrderDirection, error) { func (ec *executionContext) unmarshalNOrderDirection2goᚗproboᚗincᚋproboᚋpkgᚋpageᚐOrderDirection(ctx context.Context, v any) (page.OrderDirection, error) {
tmp, err := graphql.UnmarshalString(v) tmp, err := graphql.UnmarshalString(v)
res := unmarshalNOrderDirection2goᚗproboᚗincᚋproboᚋpkgᚋpageᚐOrderDirection[tmp] res := unmarshalNOrderDirection2goᚗproboᚗincᚋproboᚋpkgᚋpageᚐOrderDirection[tmp]
@@ -19624,6 +19949,14 @@ func (ec *executionContext) marshalOInt2ᚖint(ctx context.Context, sel ast.Sele
return res return res
} }
func (ec *executionContext) unmarshalOInvitationOrder2ᚖgoᚗproboᚗincᚋproboᚋpkgᚋserverᚋapiᚋconnectᚋv1ᚋtypesᚐInvitationOrderBy(ctx context.Context, v any) (*types.InvitationOrderBy, error) {
if v == nil {
return nil, nil
}
res, err := ec.unmarshalInputInvitationOrder(ctx, v)
return &res, graphql.ErrorOnPath(ctx, err)
}
func (ec *executionContext) unmarshalOInvitationStatus2ᚖgoᚗproboᚗincᚋproboᚋpkgᚋcoredataᚐInvitationStatus(ctx context.Context, v any) (*coredata.InvitationStatus, error) { func (ec *executionContext) unmarshalOInvitationStatus2ᚖgoᚗproboᚗincᚋproboᚋpkgᚋcoredataᚐInvitationStatus(ctx context.Context, v any) (*coredata.InvitationStatus, error) {
if v == nil { if v == nil {
return nil, nil return nil, nil

View File

@@ -66,6 +66,7 @@ func NewInvitation(invitation *coredata.Invitation) *Invitation {
return &Invitation{ return &Invitation{
ID: invitation.ID, ID: invitation.ID,
Email: invitation.Email, Email: invitation.Email,
Role: invitation.Role,
ExpiresAt: invitation.ExpiresAt, ExpiresAt: invitation.ExpiresAt,
AcceptedAt: invitation.AcceptedAt, AcceptedAt: invitation.AcceptedAt,
CreatedAt: invitation.CreatedAt, CreatedAt: invitation.CreatedAt,

View File

@@ -195,10 +195,12 @@ func (this IdentityProfile) GetID() gid.GID { return this.ID }
type Invitation struct { type Invitation struct {
ID gid.GID `json:"id"` ID gid.GID `json:"id"`
Email mail.Addr `json:"email"` Email mail.Addr `json:"email"`
Role coredata.MembershipRole `json:"role"`
ExpiresAt time.Time `json:"expiresAt"` ExpiresAt time.Time `json:"expiresAt"`
AcceptedAt *time.Time `json:"acceptedAt,omitempty"` AcceptedAt *time.Time `json:"acceptedAt,omitempty"`
CreatedAt time.Time `json:"createdAt"` CreatedAt time.Time `json:"createdAt"`
Status coredata.InvitationStatus `json:"status"` Status coredata.InvitationStatus `json:"status"`
Organization *Organization `json:"organization"`
} }
func (Invitation) IsNode() {} func (Invitation) IsNode() {}
@@ -226,6 +228,7 @@ type Membership struct {
Profile *IdentityProfile `json:"profile"` Profile *IdentityProfile `json:"profile"`
Identity *Identity `json:"identity"` Identity *Identity `json:"identity"`
Organization *Organization `json:"organization"` Organization *Organization `json:"organization"`
Role coredata.MembershipRole `json:"role"`
Permissions []*Permission `json:"permissions"` Permissions []*Permission `json:"permissions"`
ProvisionedBy ProvisioningSource `json:"provisionedBy"` ProvisionedBy ProvisioningSource `json:"provisionedBy"`
Active bool `json:"active"` Active bool `json:"active"`

View File

@@ -29,6 +29,12 @@ func (r *identityResolver) Memberships(ctx context.Context, obj *types.Identity,
Field: coredata.MembershipOrderFieldCreatedAt, Field: coredata.MembershipOrderFieldCreatedAt,
Direction: page.OrderDirectionDesc, Direction: page.OrderDirectionDesc,
} }
if orderBy != nil {
pageOrderBy = page.OrderBy[coredata.MembershipOrderField]{
Field: orderBy.Field,
Direction: orderBy.Direction,
}
}
cursor := cursor.NewCursor(first, after, last, before, pageOrderBy) cursor := cursor.NewCursor(first, after, last, before, pageOrderBy)
@@ -41,7 +47,7 @@ func (r *identityResolver) Memberships(ctx context.Context, obj *types.Identity,
} }
// PendingInvitations is the resolver for the pendingInvitations field. // PendingInvitations is the resolver for the pendingInvitations field.
func (r *identityResolver) PendingInvitations(ctx context.Context, obj *types.Identity, first *int, after *page.CursorKey, last *int, before *page.CursorKey) (*types.InvitationConnection, error) { func (r *identityResolver) PendingInvitations(ctx context.Context, obj *types.Identity, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.InvitationOrderBy) (*types.InvitationConnection, error) {
pageOrderBy := page.OrderBy[coredata.InvitationOrderField]{ pageOrderBy := page.OrderBy[coredata.InvitationOrderField]{
Field: coredata.InvitationOrderFieldCreatedAt, Field: coredata.InvitationOrderFieldCreatedAt,
Direction: page.OrderDirectionDesc, Direction: page.OrderDirectionDesc,
@@ -97,6 +103,16 @@ func (r *identityResolver) PersonalAPIKeys(ctx context.Context, obj *types.Ident
return types.NewPersonalAPIKeyConnection(page, r, obj.ID), nil return types.NewPersonalAPIKeyConnection(page, r, obj.ID), nil
} }
// Organization is the resolver for the organization field.
func (r *invitationResolver) Organization(ctx context.Context, obj *types.Invitation) (*types.Organization, error) {
organization, err := r.iam.OrganizationService.GetOrganizationForInvitation(ctx, obj.ID)
if err != nil {
panic(fmt.Errorf("cannot get organization for invitation: %w", err))
}
return types.NewOrganization(organization), nil
}
// TotalCount is the resolver for the totalCount field. // TotalCount is the resolver for the totalCount field.
func (r *invitationConnectionResolver) TotalCount(ctx context.Context, obj *types.InvitationConnection) (int, error) { func (r *invitationConnectionResolver) TotalCount(ctx context.Context, obj *types.InvitationConnection) (int, error) {
switch obj.Resolver.(type) { switch obj.Resolver.(type) {
@@ -994,6 +1010,9 @@ func (r *sessionConnectionResolver) TotalCount(ctx context.Context, obj *types.S
// Identity returns schema.IdentityResolver implementation. // Identity returns schema.IdentityResolver implementation.
func (r *Resolver) Identity() schema.IdentityResolver { return &identityResolver{r} } func (r *Resolver) Identity() schema.IdentityResolver { return &identityResolver{r} }
// Invitation returns schema.InvitationResolver implementation.
func (r *Resolver) Invitation() schema.InvitationResolver { return &invitationResolver{r} }
// InvitationConnection returns schema.InvitationConnectionResolver implementation. // InvitationConnection returns schema.InvitationConnectionResolver implementation.
func (r *Resolver) InvitationConnection() schema.InvitationConnectionResolver { func (r *Resolver) InvitationConnection() schema.InvitationConnectionResolver {
return &invitationConnectionResolver{r} return &invitationConnectionResolver{r}
@@ -1032,6 +1051,7 @@ func (r *Resolver) SessionConnection() schema.SessionConnectionResolver {
} }
type identityResolver struct{ *Resolver } type identityResolver struct{ *Resolver }
type invitationResolver struct{ *Resolver }
type invitationConnectionResolver struct{ *Resolver } type invitationConnectionResolver struct{ *Resolver }
type membershipResolver struct{ *Resolver } type membershipResolver struct{ *Resolver }
type membershipConnectionResolver struct{ *Resolver } type membershipConnectionResolver struct{ *Resolver }