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

View File

@@ -145,6 +145,7 @@ type Identity implements Node {
after: CursorKey
last: Int
before: CursorKey
orderBy: InvitationOrder
): InvitationConnection! @goField(forceResolver: true) @isViewer
sessions(
@@ -224,6 +225,17 @@ type Organization implements Node {
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 {
id: ID!
identityId: ID!
@@ -231,6 +243,7 @@ type Membership implements Node {
profile: IdentityProfile!
identity: Identity! @goField(forceResolver: true)
organization: Organization! @goField(forceResolver: true)
role: MembershipRole!
permissions: [Permission!]!
provisionedBy: ProvisioningSource!
active: Boolean!
@@ -242,10 +255,12 @@ type Membership implements Node {
type Invitation implements Node {
id: ID!
email: EmailAddr!
role: MembershipRole!
expiresAt: Datetime!
acceptedAt: Datetime
createdAt: Datetime!
status: InvitationStatus!
organization: Organization! @goField(forceResolver: true)
}
type Session implements Node {
@@ -404,14 +419,6 @@ enum ProvisioningSource {
enum 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
@goEnum(value: "go.probo.inc/probo/pkg/coredata.MembershipOrderFieldRole")
CREATED_AT
@@ -442,6 +449,34 @@ type MembershipEdge {
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
@goModel(
model: "go.probo.inc/probo/pkg/server/api/connect/v1/types.InvitationConnection"

View File

@@ -47,6 +47,7 @@ type Config struct {
type ResolverRoot interface {
Identity() IdentityResolver
Invitation() InvitationResolver
InvitationConnection() InvitationConnectionResolver
Membership() MembershipResolver
MembershipConnection() MembershipConnectionResolver
@@ -132,7 +133,7 @@ type ComplexityRoot struct {
EmailVerified 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
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
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
@@ -161,12 +162,14 @@ type ComplexityRoot struct {
}
Invitation struct {
AcceptedAt func(childComplexity int) int
CreatedAt func(childComplexity int) int
Email func(childComplexity int) int
ExpiresAt func(childComplexity int) int
ID func(childComplexity int) int
Status func(childComplexity int) int
AcceptedAt func(childComplexity int) int
CreatedAt func(childComplexity int) int
Email func(childComplexity int) int
ExpiresAt func(childComplexity int) int
ID func(childComplexity int) int
Organization func(childComplexity int) int
Role func(childComplexity int) int
Status func(childComplexity int) int
}
InvitationConnection struct {
@@ -196,6 +199,7 @@ type ComplexityRoot struct {
Permissions func(childComplexity int) int
Profile func(childComplexity int) int
ProvisionedBy func(childComplexity int) int
Role func(childComplexity int) int
}
MembershipConnection struct {
@@ -429,10 +433,13 @@ type ComplexityRoot struct {
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)
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)
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 {
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 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":
if e.complexity.Identity.PersonalAPIKeys == nil {
break
@@ -873,6 +880,18 @@ func (e *executableSchema) Complexity(ctx context.Context, typeName, field strin
}
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":
if e.complexity.Invitation.Status == nil {
break
@@ -985,6 +1004,12 @@ func (e *executableSchema) Complexity(ctx context.Context, typeName, field strin
}
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":
if e.complexity.MembershipConnection.Edges == nil {
@@ -1937,6 +1962,7 @@ func (e *executableSchema) Exec(ctx context.Context) graphql.ResponseHandler {
ec.unmarshalInputDeleteOrganizationInput,
ec.unmarshalInputDeleteSAMLConfigurationInput,
ec.unmarshalInputForgotPasswordInput,
ec.unmarshalInputInvitationOrder,
ec.unmarshalInputInviteMemberInput,
ec.unmarshalInputMembershipOrder,
ec.unmarshalInputRemoveIPAllowlistEntryInput,
@@ -2199,6 +2225,7 @@ type Identity implements Node {
after: CursorKey
last: Int
before: CursorKey
orderBy: InvitationOrder
): InvitationConnection! @goField(forceResolver: true) @isViewer
sessions(
@@ -2278,6 +2305,17 @@ type Organization implements Node {
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 {
id: ID!
identityId: ID!
@@ -2285,6 +2323,7 @@ type Membership implements Node {
profile: IdentityProfile!
identity: Identity! @goField(forceResolver: true)
organization: Organization! @goField(forceResolver: true)
role: MembershipRole!
permissions: [Permission!]!
provisionedBy: ProvisioningSource!
active: Boolean!
@@ -2296,10 +2335,12 @@ type Membership implements Node {
type Invitation implements Node {
id: ID!
email: EmailAddr!
role: MembershipRole!
expiresAt: Datetime!
acceptedAt: Datetime
createdAt: Datetime!
status: InvitationStatus!
organization: Organization! @goField(forceResolver: true)
}
type Session implements Node {
@@ -2458,14 +2499,6 @@ enum ProvisioningSource {
enum 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
@goEnum(value: "go.probo.inc/probo/pkg/coredata.MembershipOrderFieldRole")
CREATED_AT
@@ -2496,6 +2529,34 @@ type MembershipEdge {
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
@goModel(
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
}
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
}
@@ -4241,7 +4307,7 @@ func (ec *executionContext) _Identity_pendingInvitations(ctx context.Context, fi
ec.fieldContext_Identity_pendingInvitations,
func(ctx context.Context) (any, error) {
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 {
directive0 := next
@@ -5178,6 +5244,35 @@ func (ec *executionContext) fieldContext_Invitation_email(_ context.Context, fie
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) {
return graphql.ResolveField(
ctx,
@@ -5294,6 +5389,57 @@ func (ec *executionContext) fieldContext_Invitation_status(_ context.Context, fi
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) {
return graphql.ResolveField(
ctx,
@@ -5425,6 +5571,8 @@ func (ec *executionContext) fieldContext_InvitationEdge_node(_ context.Context,
return ec.fieldContext_Invitation_id(ctx, field)
case "email":
return ec.fieldContext_Invitation_email(ctx, field)
case "role":
return ec.fieldContext_Invitation_role(ctx, field)
case "expiresAt":
return ec.fieldContext_Invitation_expiresAt(ctx, field)
case "acceptedAt":
@@ -5433,6 +5581,8 @@ func (ec *executionContext) fieldContext_InvitationEdge_node(_ context.Context,
return ec.fieldContext_Invitation_createdAt(ctx, field)
case "status":
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)
},
@@ -5760,6 +5910,35 @@ func (ec *executionContext) fieldContext_Membership_organization(_ context.Conte
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) {
return graphql.ResolveField(
ctx,
@@ -6087,6 +6266,8 @@ func (ec *executionContext) fieldContext_MembershipEdge_node(_ context.Context,
return ec.fieldContext_Membership_identity(ctx, field)
case "organization":
return ec.fieldContext_Membership_organization(ctx, field)
case "role":
return ec.fieldContext_Membership_role(ctx, field)
case "permissions":
return ec.fieldContext_Membership_permissions(ctx, field)
case "provisionedBy":
@@ -13046,6 +13227,40 @@ func (ec *executionContext) unmarshalInputForgotPasswordInput(ctx context.Contex
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) {
var it types.InviteMemberInput
asMap := map[string]any{}
@@ -14750,30 +14965,71 @@ func (ec *executionContext) _Invitation(ctx context.Context, sel ast.SelectionSe
case "id":
out.Values[i] = ec._Invitation_id(ctx, field, obj)
if out.Values[i] == graphql.Null {
out.Invalids++
atomic.AddUint32(&out.Invalids, 1)
}
case "email":
out.Values[i] = ec._Invitation_email(ctx, field, obj)
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":
out.Values[i] = ec._Invitation_expiresAt(ctx, field, obj)
if out.Values[i] == graphql.Null {
out.Invalids++
atomic.AddUint32(&out.Invalids, 1)
}
case "acceptedAt":
out.Values[i] = ec._Invitation_acceptedAt(ctx, field, obj)
case "createdAt":
out.Values[i] = ec._Invitation_createdAt(ctx, field, obj)
if out.Values[i] == graphql.Null {
out.Invalids++
atomic.AddUint32(&out.Invalids, 1)
}
case "status":
out.Values[i] = ec._Invitation_status(ctx, field, obj)
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:
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) })
case "role":
out.Values[i] = ec._Membership_role(ctx, field, obj)
if out.Values[i] == graphql.Null {
atomic.AddUint32(&out.Invalids, 1)
}
case "permissions":
out.Values[i] = ec._Membership_permissions(ctx, field, obj)
if out.Values[i] == graphql.Null {
@@ -18209,6 +18470,40 @@ func (ec *executionContext) marshalNInvitationEdge2ᚖgoᚗproboᚗincᚋprobo
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) {
tmp, err := graphql.UnmarshalString(v)
res := unmarshalNInvitationStatus2goᚗproboᚗincᚋproboᚋpkgᚋcoredataᚐInvitationStatus[tmp]
@@ -18355,16 +18650,46 @@ func (ec *executionContext) marshalNMembershipOrderField2goᚗproboᚗincᚋprob
var (
unmarshalNMembershipOrderField2goᚗproboᚗincᚋproboᚋpkgᚋcoredataᚐMembershipOrderField = map[string]coredata.MembershipOrderField{
"FULL_NAME": coredata.MembershipOrderFieldFullName,
"EMAIL_ADDRESS": coredata.MembershipOrderFieldEmailAddress,
"ROLE": coredata.MembershipOrderFieldRole,
"CREATED_AT": coredata.MembershipOrderFieldCreatedAt,
"ROLE": coredata.MembershipOrderFieldRole,
"CREATED_AT": coredata.MembershipOrderFieldCreatedAt,
}
marshalNMembershipOrderField2goᚗproboᚗincᚋproboᚋpkgᚋcoredataᚐMembershipOrderField = map[coredata.MembershipOrderField]string{
coredata.MembershipOrderFieldFullName: "FULL_NAME",
coredata.MembershipOrderFieldEmailAddress: "EMAIL_ADDRESS",
coredata.MembershipOrderFieldRole: "ROLE",
coredata.MembershipOrderFieldCreatedAt: "CREATED_AT",
coredata.MembershipOrderFieldRole: "ROLE",
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",
}
)
@@ -19624,6 +19949,14 @@ func (ec *executionContext) marshalOInt2ᚖint(ctx context.Context, sel ast.Sele
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) {
if v == nil {
return nil, nil

View File

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

View File

@@ -193,12 +193,14 @@ func (IdentityProfile) IsNode() {}
func (this IdentityProfile) GetID() gid.GID { return this.ID }
type Invitation struct {
ID gid.GID `json:"id"`
Email mail.Addr `json:"email"`
ExpiresAt time.Time `json:"expiresAt"`
AcceptedAt *time.Time `json:"acceptedAt,omitempty"`
CreatedAt time.Time `json:"createdAt"`
Status coredata.InvitationStatus `json:"status"`
ID gid.GID `json:"id"`
Email mail.Addr `json:"email"`
Role coredata.MembershipRole `json:"role"`
ExpiresAt time.Time `json:"expiresAt"`
AcceptedAt *time.Time `json:"acceptedAt,omitempty"`
CreatedAt time.Time `json:"createdAt"`
Status coredata.InvitationStatus `json:"status"`
Organization *Organization `json:"organization"`
}
func (Invitation) IsNode() {}
@@ -220,17 +222,18 @@ type InviteMemberPayload struct {
}
type Membership struct {
ID gid.GID `json:"id"`
IdentityID gid.GID `json:"identityId"`
CreatedAt time.Time `json:"createdAt"`
Profile *IdentityProfile `json:"profile"`
Identity *Identity `json:"identity"`
Organization *Organization `json:"organization"`
Permissions []*Permission `json:"permissions"`
ProvisionedBy ProvisioningSource `json:"provisionedBy"`
Active bool `json:"active"`
LastSyncedAt *time.Time `json:"lastSyncedAt,omitempty"`
ActiveSession *Session `json:"activeSession,omitempty"`
ID gid.GID `json:"id"`
IdentityID gid.GID `json:"identityId"`
CreatedAt time.Time `json:"createdAt"`
Profile *IdentityProfile `json:"profile"`
Identity *Identity `json:"identity"`
Organization *Organization `json:"organization"`
Role coredata.MembershipRole `json:"role"`
Permissions []*Permission `json:"permissions"`
ProvisionedBy ProvisioningSource `json:"provisionedBy"`
Active bool `json:"active"`
LastSyncedAt *time.Time `json:"lastSyncedAt,omitempty"`
ActiveSession *Session `json:"activeSession,omitempty"`
}
func (Membership) IsNode() {}

View File

@@ -29,6 +29,12 @@ func (r *identityResolver) Memberships(ctx context.Context, obj *types.Identity,
Field: coredata.MembershipOrderFieldCreatedAt,
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)
@@ -41,7 +47,7 @@ func (r *identityResolver) Memberships(ctx context.Context, obj *types.Identity,
}
// 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]{
Field: coredata.InvitationOrderFieldCreatedAt,
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
}
// 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.
func (r *invitationConnectionResolver) TotalCount(ctx context.Context, obj *types.InvitationConnection) (int, error) {
switch obj.Resolver.(type) {
@@ -994,6 +1010,9 @@ func (r *sessionConnectionResolver) TotalCount(ctx context.Context, obj *types.S
// Identity returns schema.IdentityResolver implementation.
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.
func (r *Resolver) InvitationConnection() schema.InvitationConnectionResolver {
return &invitationConnectionResolver{r}
@@ -1032,6 +1051,7 @@ func (r *Resolver) SessionConnection() schema.SessionConnectionResolver {
}
type identityResolver struct{ *Resolver }
type invitationResolver struct{ *Resolver }
type invitationConnectionResolver struct{ *Resolver }
type membershipResolver struct{ *Resolver }
type membershipConnectionResolver struct{ *Resolver }