Port trust center features after main rebase
Move Emile's commitment CRUD into complianceportal management, wire console and visitor GraphQL, and drop portal magic-link sign-in in favor of OAuth /initiate while keeping documents, NDA/full-name gates, and access-request resume markers. Signed-off-by: Bryan Frimin <bryan@probo.com>
This commit is contained in:
@@ -28,29 +28,9 @@
|
|||||||
"zoomIn": "Zoom in"
|
"zoomIn": "Zoom in"
|
||||||
},
|
},
|
||||||
"auth": {
|
"auth": {
|
||||||
"backToPortal": "Back to portal",
|
|
||||||
"signIn": {
|
|
||||||
"title": "Sign in to continue",
|
|
||||||
"description": "Access protected resources or submit a request",
|
|
||||||
"or": "OR",
|
|
||||||
"withProvider": "Sign in with {{provider}}",
|
|
||||||
"emailLabel": "Email Address",
|
|
||||||
"emailPlaceholder": "you@company.com",
|
|
||||||
"emailRequired": "Email is required",
|
|
||||||
"emailInvalid": "Enter a valid email address",
|
|
||||||
"sendMagicLink": "Send Magic Link",
|
|
||||||
"resend": "Resend link",
|
|
||||||
"resendIn": "Resend link in {{seconds}}s",
|
|
||||||
"magicLinkSent": "Magic link sent!",
|
|
||||||
"magicLinkSentNote": "Check your email and use the link to sign in."
|
|
||||||
},
|
|
||||||
"requestAccess": {
|
"requestAccess": {
|
||||||
"success": "Access requested"
|
"success": "Access requested"
|
||||||
},
|
},
|
||||||
"verify": {
|
|
||||||
"title": "Confirming your email",
|
|
||||||
"description": "Please wait while we sign you in\u2026"
|
|
||||||
},
|
|
||||||
"fullName": {
|
"fullName": {
|
||||||
"title": "Add your name",
|
"title": "Add your name",
|
||||||
"label": "Full Name",
|
"label": "Full Name",
|
||||||
@@ -59,17 +39,7 @@
|
|||||||
"tooShort": "Full name is too short",
|
"tooShort": "Full name is too short",
|
||||||
"submit": "Continue"
|
"submit": "Continue"
|
||||||
},
|
},
|
||||||
"magicLinkExpired": {
|
|
||||||
"title": "Link expired",
|
|
||||||
"description": "This magic link has expired. Please request a new one."
|
|
||||||
},
|
|
||||||
"magicLinkAlreadyUsed": {
|
|
||||||
"title": "Link already used",
|
|
||||||
"description": "This magic link has already been used. Please request a new one."
|
|
||||||
},
|
|
||||||
"errors": {
|
"errors": {
|
||||||
"magicLinkFailed": "Couldn't send the magic link. Please try again.",
|
|
||||||
"verifyFailed": "Couldn't verify the link. Please try again.",
|
|
||||||
"fullNameFailed": "Couldn't save your name. Please try again.",
|
"fullNameFailed": "Couldn't save your name. Please try again.",
|
||||||
"requestFailed": "Couldn't complete your access request. Please try again."
|
"requestFailed": "Couldn't complete your access request. Please try again."
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,29 +28,9 @@
|
|||||||
"zoomIn": "Zoom avant"
|
"zoomIn": "Zoom avant"
|
||||||
},
|
},
|
||||||
"auth": {
|
"auth": {
|
||||||
"backToPortal": "Retour au portail",
|
|
||||||
"signIn": {
|
|
||||||
"title": "Connectez-vous pour continuer",
|
|
||||||
"description": "Accédez aux ressources protégées ou soumettez une demande",
|
|
||||||
"or": "OU",
|
|
||||||
"withProvider": "Se connecter avec {{provider}}",
|
|
||||||
"emailLabel": "Adresse e-mail",
|
|
||||||
"emailPlaceholder": "vous@entreprise.com",
|
|
||||||
"emailRequired": "L'e-mail est requis",
|
|
||||||
"emailInvalid": "Saisissez une adresse e-mail valide",
|
|
||||||
"sendMagicLink": "Envoyer le lien magique",
|
|
||||||
"resend": "Renvoyer le lien",
|
|
||||||
"resendIn": "Renvoyer le lien dans {{seconds}}s",
|
|
||||||
"magicLinkSent": "Lien magique envoyé !",
|
|
||||||
"magicLinkSentNote": "Consultez votre e-mail et utilisez le lien pour vous connecter."
|
|
||||||
},
|
|
||||||
"requestAccess": {
|
"requestAccess": {
|
||||||
"success": "Accès demandé"
|
"success": "Accès demandé"
|
||||||
},
|
},
|
||||||
"verify": {
|
|
||||||
"title": "Confirmation de votre e-mail",
|
|
||||||
"description": "Veuillez patienter pendant que nous vous connectons\u2026"
|
|
||||||
},
|
|
||||||
"fullName": {
|
"fullName": {
|
||||||
"title": "Ajoutez votre nom",
|
"title": "Ajoutez votre nom",
|
||||||
"label": "Nom complet",
|
"label": "Nom complet",
|
||||||
@@ -59,17 +39,7 @@
|
|||||||
"tooShort": "Le nom complet est trop court",
|
"tooShort": "Le nom complet est trop court",
|
||||||
"submit": "Continuer"
|
"submit": "Continuer"
|
||||||
},
|
},
|
||||||
"magicLinkExpired": {
|
|
||||||
"title": "Lien expiré",
|
|
||||||
"description": "Ce lien magique a expiré. Veuillez en demander un nouveau."
|
|
||||||
},
|
|
||||||
"magicLinkAlreadyUsed": {
|
|
||||||
"title": "Lien déjà utilisé",
|
|
||||||
"description": "Ce lien magique a déjà été utilisé. Veuillez en demander un nouveau."
|
|
||||||
},
|
|
||||||
"errors": {
|
"errors": {
|
||||||
"magicLinkFailed": "Impossible d'envoyer le lien magique. Veuillez réessayer.",
|
|
||||||
"verifyFailed": "Impossible de vérifier le lien. Veuillez réessayer.",
|
|
||||||
"fullNameFailed": "Impossible d'enregistrer votre nom. Veuillez réessayer.",
|
"fullNameFailed": "Impossible d'enregistrer votre nom. Veuillez réessayer.",
|
||||||
"requestFailed": "Impossible de finaliser votre demande d'accès. Veuillez réessayer."
|
"requestFailed": "Impossible de finaliser votre demande d'accès. Veuillez réessayer."
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,6 +27,8 @@ import { useTranslation } from "react-i18next";
|
|||||||
import { graphql, useFragment } from "react-relay";
|
import { graphql, useFragment } from "react-relay";
|
||||||
import { Link as RouterLink, useLocation } from "react-router";
|
import { Link as RouterLink, useLocation } from "react-router";
|
||||||
|
|
||||||
|
import { buildRequestAllContinueUrl, redirectToInitiate } from "#/lib/auth/continueUrl";
|
||||||
|
|
||||||
import type { TopBar_query$key } from "./__generated__/TopBar_query.graphql";
|
import type { TopBar_query$key } from "./__generated__/TopBar_query.graphql";
|
||||||
import { TOP_BAR_NAV_ITEMS } from "./navItems";
|
import { TOP_BAR_NAV_ITEMS } from "./navItems";
|
||||||
import { TopBarMobileNav } from "./TopBarMobileNav";
|
import { TopBarMobileNav } from "./TopBarMobileNav";
|
||||||
@@ -110,15 +112,7 @@ export function TopBar({ queryKey }: TopBarProps) {
|
|||||||
highContrast
|
highContrast
|
||||||
iconStart={<LockSimpleIcon />}
|
iconStart={<LockSimpleIcon />}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
const initiateURL = new URL(
|
redirectToInitiate(buildRequestAllContinueUrl());
|
||||||
"/initiate",
|
|
||||||
window.location.origin,
|
|
||||||
);
|
|
||||||
initiateURL.searchParams.set(
|
|
||||||
"continue",
|
|
||||||
location.pathname + location.search + location.hash,
|
|
||||||
);
|
|
||||||
window.location.href = initiateURL.toString();
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{t("topBar.getAccess")}
|
{t("topBar.getAccess")}
|
||||||
|
|||||||
@@ -43,8 +43,7 @@ import { useTranslation } from "react-i18next";
|
|||||||
import { graphql, useFragment } from "react-relay";
|
import { graphql, useFragment } from "react-relay";
|
||||||
import { useLocation } from "react-router";
|
import { useLocation } from "react-router";
|
||||||
|
|
||||||
import { buildRequestAllContinueUrl } from "#/lib/auth/continueUrl";
|
import { buildRequestAllContinueUrl, redirectToInitiate } from "#/lib/auth/continueUrl";
|
||||||
import { useSignInDialog } from "#/lib/auth/signInDialogContext";
|
|
||||||
import { useSignOut } from "#/lib/auth/useSignOut";
|
import { useSignOut } from "#/lib/auth/useSignOut";
|
||||||
import { useSubscribeDialog } from "#/lib/mailingList/subscribeDialogContext";
|
import { useSubscribeDialog } from "#/lib/mailingList/subscribeDialogContext";
|
||||||
|
|
||||||
@@ -72,7 +71,6 @@ function isActive(pathname: string, to: string): boolean {
|
|||||||
export function TopBarMobileNav({ identityKey }: TopBarMobileNavProps) {
|
export function TopBarMobileNav({ identityKey }: TopBarMobileNavProps) {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const { pathname } = useLocation();
|
const { pathname } = useLocation();
|
||||||
const { openSignIn } = useSignInDialog();
|
|
||||||
const { openSubscribe, isSubscribed, unsubscribe, isUnsubscribing } = useSubscribeDialog();
|
const { openSubscribe, isSubscribed, unsubscribe, isUnsubscribing } = useSubscribeDialog();
|
||||||
const [signOut, isSigningOut] = useSignOut();
|
const [signOut, isSigningOut] = useSignOut();
|
||||||
const [open, setOpen] = useState(false);
|
const [open, setOpen] = useState(false);
|
||||||
@@ -150,7 +148,7 @@ export function TopBarMobileNav({ identityKey }: TopBarMobileNavProps) {
|
|||||||
iconStart={<LockSimpleIcon />}
|
iconStart={<LockSimpleIcon />}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
close();
|
close();
|
||||||
openSignIn({ continueTo: buildRequestAllContinueUrl() });
|
redirectToInitiate(buildRequestAllContinueUrl());
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{t("topBar.getAccess")}
|
{t("topBar.getAccess")}
|
||||||
|
|||||||
@@ -1,75 +0,0 @@
|
|||||||
// Copyright (c) 2026 Probo Inc <hello@probo.com>.
|
|
||||||
//
|
|
||||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
||||||
// of this software and associated documentation files (the "Software"), to deal
|
|
||||||
// in the Software without restriction, including without limitation the rights
|
|
||||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
||||||
// copies of the Software, and to permit persons to whom the Software is
|
|
||||||
// furnished to do so, subject to the following conditions:
|
|
||||||
//
|
|
||||||
// The above copyright notice and this permission notice shall be included in
|
|
||||||
// all copies or substantial portions of the Software.
|
|
||||||
//
|
|
||||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
||||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
||||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
||||||
// SOFTWARE.
|
|
||||||
|
|
||||||
import { Google } from "@probo/ui/src/Atoms/ThirdParties/Google";
|
|
||||||
import { Microsoft } from "@probo/ui/src/Atoms/ThirdParties/Microsoft";
|
|
||||||
import { Button } from "@probo/ui/src/v2/Button/Button";
|
|
||||||
import type { ComponentProps } from "react";
|
|
||||||
import { useTranslation } from "react-i18next";
|
|
||||||
import { graphql, useFragment } from "react-relay";
|
|
||||||
|
|
||||||
import type { OIDCButton_provider$key } from "./__generated__/OIDCButton_provider.graphql";
|
|
||||||
|
|
||||||
const providerFragment = graphql`
|
|
||||||
fragment OIDCButton_provider on OIDCProviderInfo {
|
|
||||||
name
|
|
||||||
loginURL
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|
||||||
const providerIcons: Record<string, (props: ComponentProps<"svg">) => React.ReactNode> = {
|
|
||||||
google: Google,
|
|
||||||
microsoft: Microsoft,
|
|
||||||
};
|
|
||||||
|
|
||||||
interface OIDCButtonProps {
|
|
||||||
providerKey: OIDCButton_provider$key;
|
|
||||||
// Absolute URL to return to after the provider completes authentication.
|
|
||||||
continueTo: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Redirects the whole window to the provider's hosted login, carrying the
|
|
||||||
// `continue` target so the portal resumes the pending flow on return.
|
|
||||||
export function OIDCButton({ providerKey, continueTo }: OIDCButtonProps) {
|
|
||||||
const { t } = useTranslation();
|
|
||||||
const provider = useFragment(providerFragment, providerKey);
|
|
||||||
const Icon = providerIcons[provider.name];
|
|
||||||
const label = t("auth.signIn.withProvider", {
|
|
||||||
provider: provider.name.charAt(0).toUpperCase() + provider.name.slice(1),
|
|
||||||
});
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Button
|
|
||||||
type="button"
|
|
||||||
variant="soft"
|
|
||||||
color="neutral"
|
|
||||||
highContrast
|
|
||||||
className="w-full"
|
|
||||||
iconStart={Icon ? <Icon className="size-4" /> : undefined}
|
|
||||||
onClick={() => {
|
|
||||||
const loginURL = new URL(provider.loginURL, window.location.origin);
|
|
||||||
loginURL.searchParams.set("continue", continueTo);
|
|
||||||
window.location.href = loginURL.toString();
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{label}
|
|
||||||
</Button>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,94 +0,0 @@
|
|||||||
// Copyright (c) 2026 Probo Inc <hello@probo.com>.
|
|
||||||
//
|
|
||||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
||||||
// of this software and associated documentation files (the "Software"), to deal
|
|
||||||
// in the Software without restriction, including without limitation the rights
|
|
||||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
||||||
// copies of the Software, and to permit persons to whom the Software is
|
|
||||||
// furnished to do so, subject to the following conditions:
|
|
||||||
//
|
|
||||||
// The above copyright notice and this permission notice shall be included in
|
|
||||||
// all copies or substantial portions of the Software.
|
|
||||||
//
|
|
||||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
||||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
||||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
||||||
// SOFTWARE.
|
|
||||||
|
|
||||||
import { ButtonSkeleton } from "@probo/ui/src/v2/Button/ButtonSkeleton";
|
|
||||||
import { ErrorBoundary } from "@probo/ui/src/v2/ErrorBoundary/ErrorBoundary";
|
|
||||||
import { Text } from "@probo/ui/src/v2/typography/Text";
|
|
||||||
import { Suspense } from "react";
|
|
||||||
import { useTranslation } from "react-i18next";
|
|
||||||
import { graphql, useLazyLoadQuery } from "react-relay";
|
|
||||||
|
|
||||||
import type { OIDCProvidersQuery } from "./__generated__/OIDCProvidersQuery.graphql";
|
|
||||||
import { OIDCButton } from "./OIDCButton";
|
|
||||||
|
|
||||||
const oidcProvidersQuery = graphql`
|
|
||||||
query OIDCProvidersQuery {
|
|
||||||
oidcProviders {
|
|
||||||
...OIDCButton_provider
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|
||||||
interface OIDCProvidersProps {
|
|
||||||
continueTo: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
function Divider() {
|
|
||||||
const { t } = useTranslation();
|
|
||||||
return (
|
|
||||||
<div className="flex items-center gap-4">
|
|
||||||
<span className="h-px flex-1 bg-sand-6" />
|
|
||||||
<Text size={1} color="faint">{t("auth.signIn.or")}</Text>
|
|
||||||
<span className="h-px flex-1 bg-sand-6" />
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function OIDCProvidersContent({ continueTo }: OIDCProvidersProps) {
|
|
||||||
const data = useLazyLoadQuery<OIDCProvidersQuery>(oidcProvidersQuery, {});
|
|
||||||
|
|
||||||
if (data.oidcProviders.length === 0) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<div className="flex flex-col gap-2">
|
|
||||||
{data.oidcProviders.map((provider, index) => (
|
|
||||||
<OIDCButton key={index} providerKey={provider} continueTo={continueTo} />
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
<Divider />
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// SSO buttons for the sign-in dialog. Providers load lazily on open; if the
|
|
||||||
// query fails or the trust center has none, the section renders nothing so the
|
|
||||||
// email flow stays usable.
|
|
||||||
export function OIDCProviders({ continueTo }: OIDCProvidersProps) {
|
|
||||||
return (
|
|
||||||
<ErrorBoundary
|
|
||||||
fallback={null}
|
|
||||||
onError={error => console.error("Failed to load SSO providers", error)}
|
|
||||||
>
|
|
||||||
<Suspense
|
|
||||||
fallback={(
|
|
||||||
<div className="flex flex-col gap-2">
|
|
||||||
<ButtonSkeleton className="w-full" />
|
|
||||||
<ButtonSkeleton className="w-full" />
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
<OIDCProvidersContent continueTo={continueTo} />
|
|
||||||
</Suspense>
|
|
||||||
</ErrorBoundary>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,53 +0,0 @@
|
|||||||
// Copyright (c) 2026 Probo Inc <hello@probo.com>.
|
|
||||||
//
|
|
||||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
||||||
// of this software and associated documentation files (the "Software"), to deal
|
|
||||||
// in the Software without restriction, including without limitation the rights
|
|
||||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
||||||
// copies of the Software, and to permit persons to whom the Software is
|
|
||||||
// furnished to do so, subject to the following conditions:
|
|
||||||
//
|
|
||||||
// The above copyright notice and this permission notice shall be included in
|
|
||||||
// all copies or substantial portions of the Software.
|
|
||||||
//
|
|
||||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
||||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
||||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
||||||
// SOFTWARE.
|
|
||||||
|
|
||||||
import { Dialog } from "@probo/ui/src/v2/Dialog/Dialog";
|
|
||||||
import { DialogDescription } from "@probo/ui/src/v2/Dialog/DialogDescription";
|
|
||||||
import { DialogHeader } from "@probo/ui/src/v2/Dialog/DialogHeader";
|
|
||||||
import { DialogPopup } from "@probo/ui/src/v2/Dialog/DialogPopup";
|
|
||||||
import { DialogTitle } from "@probo/ui/src/v2/Dialog/DialogTitle";
|
|
||||||
import { useTranslation } from "react-i18next";
|
|
||||||
|
|
||||||
import { SignInForm } from "./SignInForm";
|
|
||||||
|
|
||||||
interface SignInDialogProps {
|
|
||||||
open: boolean;
|
|
||||||
onOpenChange: (open: boolean) => void;
|
|
||||||
// Absolute URL to return to after authentication.
|
|
||||||
continueTo: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
// The reusable "Login Dialog" from the design: a modal sign-in gate composed of
|
|
||||||
// the kit Dialog plus the magic-link / SSO sign-in form.
|
|
||||||
export function SignInDialog({ open, onOpenChange, continueTo }: SignInDialogProps) {
|
|
||||||
const { t } = useTranslation();
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Dialog open={open} onOpenChange={onOpenChange}>
|
|
||||||
<DialogPopup className="max-w-lg">
|
|
||||||
<DialogHeader>
|
|
||||||
<DialogTitle>{t("auth.signIn.title")}</DialogTitle>
|
|
||||||
<DialogDescription>{t("auth.signIn.description")}</DialogDescription>
|
|
||||||
</DialogHeader>
|
|
||||||
<SignInForm continueTo={continueTo} onCancel={() => onOpenChange(false)} />
|
|
||||||
</DialogPopup>
|
|
||||||
</Dialog>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,168 +0,0 @@
|
|||||||
// Copyright (c) 2026 Probo Inc <hello@probo.com>.
|
|
||||||
//
|
|
||||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
||||||
// of this software and associated documentation files (the "Software"), to deal
|
|
||||||
// in the Software without restriction, including without limitation the rights
|
|
||||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
||||||
// copies of the Software, and to permit persons to whom the Software is
|
|
||||||
// furnished to do so, subject to the following conditions:
|
|
||||||
//
|
|
||||||
// The above copyright notice and this permission notice shall be included in
|
|
||||||
// all copies or substantial portions of the Software.
|
|
||||||
//
|
|
||||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
||||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
||||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
||||||
// SOFTWARE.
|
|
||||||
|
|
||||||
import { Field } from "@base-ui/react/field";
|
|
||||||
import { Form } from "@base-ui/react/form";
|
|
||||||
import { Toast } from "@base-ui/react/toast";
|
|
||||||
import type { GraphQLError } from "@probo/helpers";
|
|
||||||
import { Button } from "@probo/ui/src/v2/Button/Button";
|
|
||||||
import { DialogBody } from "@probo/ui/src/v2/Dialog/DialogBody";
|
|
||||||
import { DialogFooter } from "@probo/ui/src/v2/Dialog/DialogFooter";
|
|
||||||
import { TextField } from "@probo/ui/src/v2/form/TextField";
|
|
||||||
import { Text } from "@probo/ui/src/v2/typography/Text";
|
|
||||||
import { useEffect, useRef, useState } from "react";
|
|
||||||
import { useTranslation } from "react-i18next";
|
|
||||||
import { graphql } from "relay-runtime";
|
|
||||||
|
|
||||||
import { getSafeContinueUrl } from "#/lib/auth/continueUrl";
|
|
||||||
import { useMutation } from "#/lib/relay/useMutation";
|
|
||||||
|
|
||||||
import type { SignInFormMutation } from "./__generated__/SignInFormMutation.graphql";
|
|
||||||
import { OIDCProviders } from "./OIDCProviders";
|
|
||||||
|
|
||||||
const RESEND_COOLDOWN_SECONDS = 60;
|
|
||||||
|
|
||||||
const sendMagicLinkMutation = graphql`
|
|
||||||
mutation SignInFormMutation($input: SendMagicLinkInput!) {
|
|
||||||
sendMagicLink(input: $input) {
|
|
||||||
success
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|
||||||
interface SignInFormProps {
|
|
||||||
// Absolute URL to return to after authentication (carries the request-all
|
|
||||||
// marker so an access request resumes once signed in).
|
|
||||||
continueTo: string;
|
|
||||||
onCancel: () => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Sign-in form used inside the dialog: SSO providers, then a magic-link email
|
|
||||||
// flow. On success it flips to a "check your email" state with a resend timer.
|
|
||||||
export function SignInForm({ continueTo, onCancel }: SignInFormProps) {
|
|
||||||
const { t } = useTranslation();
|
|
||||||
const toast = Toast.useToastManager();
|
|
||||||
const [magicLinkSent, setMagicLinkSent] = useState(false);
|
|
||||||
const [secondsLeft, setSecondsLeft] = useState(RESEND_COOLDOWN_SECONDS);
|
|
||||||
const intervalRef = useRef<ReturnType<typeof setInterval>>(undefined);
|
|
||||||
|
|
||||||
const [sendMagicLink, isSending] = useMutation<SignInFormMutation>(
|
|
||||||
sendMagicLinkMutation,
|
|
||||||
{ errorToast: false },
|
|
||||||
);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (!magicLinkSent) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
intervalRef.current = setInterval(() => {
|
|
||||||
setSecondsLeft(seconds => Math.max(seconds - 1, 0));
|
|
||||||
}, 1000);
|
|
||||||
return () => clearInterval(intervalRef.current);
|
|
||||||
}, [magicLinkSent]);
|
|
||||||
|
|
||||||
const handleSend = (email: string) => {
|
|
||||||
void sendMagicLink({
|
|
||||||
variables: { input: { email, continue: continueTo } },
|
|
||||||
onCompleted: (_response, errors) => {
|
|
||||||
const code = (errors?.[0] as GraphQLError | undefined)?.extensions?.code;
|
|
||||||
|
|
||||||
// Already signed in elsewhere: jump straight to the return URL so any
|
|
||||||
// pending access request resumes.
|
|
||||||
if (code === "ALREADY_AUTHENTICATED") {
|
|
||||||
window.location.href = getSafeContinueUrl(continueTo);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (errors && errors.length > 0) {
|
|
||||||
toast.add({ title: t("auth.errors.magicLinkFailed"), type: "error" });
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
setSecondsLeft(RESEND_COOLDOWN_SECONDS);
|
|
||||||
setMagicLinkSent(true);
|
|
||||||
toast.add({ title: t("auth.signIn.magicLinkSent"), type: "success" });
|
|
||||||
},
|
|
||||||
onError: () => {
|
|
||||||
toast.add({ title: t("auth.errors.magicLinkFailed"), type: "error" });
|
|
||||||
},
|
|
||||||
}).catch(() => {});
|
|
||||||
};
|
|
||||||
|
|
||||||
const resendDisabled = magicLinkSent && secondsLeft > 0;
|
|
||||||
const submitLabel = magicLinkSent
|
|
||||||
? secondsLeft > 0
|
|
||||||
? t("auth.signIn.resendIn", { seconds: secondsLeft })
|
|
||||||
: t("auth.signIn.resend")
|
|
||||||
: t("auth.signIn.sendMagicLink");
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Form
|
|
||||||
className="flex flex-col gap-4"
|
|
||||||
onFormSubmit={(values) => {
|
|
||||||
handleSend(String(values.email ?? ""));
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<DialogBody className="flex flex-col gap-6">
|
|
||||||
<OIDCProviders continueTo={continueTo} />
|
|
||||||
|
|
||||||
<Field.Root name="email" className="flex flex-col gap-1.5">
|
|
||||||
<Field.Label className="text-1 font-medium text-sand-12">
|
|
||||||
{t("auth.signIn.emailLabel")}
|
|
||||||
</Field.Label>
|
|
||||||
<TextField
|
|
||||||
type="email"
|
|
||||||
name="email"
|
|
||||||
required
|
|
||||||
placeholder={t("auth.signIn.emailPlaceholder")}
|
|
||||||
/>
|
|
||||||
<Field.Error className="text-1 text-red-11" match="valueMissing">
|
|
||||||
{t("auth.signIn.emailRequired")}
|
|
||||||
</Field.Error>
|
|
||||||
<Field.Error className="text-1 text-red-11" match="typeMismatch">
|
|
||||||
{t("auth.signIn.emailInvalid")}
|
|
||||||
</Field.Error>
|
|
||||||
</Field.Root>
|
|
||||||
|
|
||||||
{magicLinkSent && (
|
|
||||||
<Text size={1} color="neutral">
|
|
||||||
{t("auth.signIn.magicLinkSentNote")}
|
|
||||||
</Text>
|
|
||||||
)}
|
|
||||||
</DialogBody>
|
|
||||||
|
|
||||||
<DialogFooter>
|
|
||||||
<Button type="button" variant="soft" color="neutral" highContrast onClick={onCancel}>
|
|
||||||
{t("common.cancel")}
|
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
type="submit"
|
|
||||||
variant="solid"
|
|
||||||
color="neutral"
|
|
||||||
highContrast
|
|
||||||
loading={isSending}
|
|
||||||
disabled={resendDisabled}
|
|
||||||
>
|
|
||||||
{submitLabel}
|
|
||||||
</Button>
|
|
||||||
</DialogFooter>
|
|
||||||
</Form>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,53 +0,0 @@
|
|||||||
// Copyright (c) 2026 Probo Inc <hello@probo.com>.
|
|
||||||
//
|
|
||||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
||||||
// of this software and associated documentation files (the "Software"), to deal
|
|
||||||
// in the Software without restriction, including without limitation the rights
|
|
||||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
||||||
// copies of the Software, and to permit persons to whom the Software is
|
|
||||||
// furnished to do so, subject to the following conditions:
|
|
||||||
//
|
|
||||||
// The above copyright notice and this permission notice shall be included in
|
|
||||||
// all copies or substantial portions of the Software.
|
|
||||||
//
|
|
||||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
||||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
||||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
||||||
// SOFTWARE.
|
|
||||||
|
|
||||||
import { type ReactNode, useCallback, useMemo, useState } from "react";
|
|
||||||
|
|
||||||
import { SignInDialog } from "#/components/auth/SignInDialog";
|
|
||||||
import {
|
|
||||||
type OpenSignInOptions,
|
|
||||||
SignInDialogContextProvider,
|
|
||||||
} from "#/lib/auth/signInDialogContext";
|
|
||||||
|
|
||||||
interface SignInDialogProviderProps {
|
|
||||||
children: ReactNode;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Owns the single sign-in dialog instance and exposes `openSignIn` so any
|
|
||||||
// descendant (top bar, resource rows, …) can prompt authentication and pass the
|
|
||||||
// URL to return to afterwards.
|
|
||||||
export function SignInDialogProvider({ children }: SignInDialogProviderProps) {
|
|
||||||
const [open, setOpen] = useState(false);
|
|
||||||
const [continueTo, setContinueTo] = useState(() => window.location.href);
|
|
||||||
|
|
||||||
const openSignIn = useCallback((options?: OpenSignInOptions) => {
|
|
||||||
setContinueTo(options?.continueTo ?? window.location.href);
|
|
||||||
setOpen(true);
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const value = useMemo(() => ({ openSignIn }), [openSignIn]);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<SignInDialogContextProvider value={value}>
|
|
||||||
{children}
|
|
||||||
<SignInDialog open={open} onOpenChange={setOpen} continueTo={continueTo} />
|
|
||||||
</SignInDialogContextProvider>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -108,3 +108,12 @@ export function gateRedirectPath(error: unknown, continueUrl: string): string |
|
|||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Sends the browser to the OAuth entry point, carrying a validated continue URL
|
||||||
|
// so the user returns to the portal (and any deferred access request resumes)
|
||||||
|
// after sign-in.
|
||||||
|
export function redirectToInitiate(continueTo: string): void {
|
||||||
|
const initiateURL = new URL("/initiate", window.location.origin);
|
||||||
|
initiateURL.searchParams.set("continue", getSafeContinueUrl(continueTo));
|
||||||
|
window.location.href = initiateURL.toString();
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,43 +0,0 @@
|
|||||||
// Copyright (c) 2026 Probo Inc <hello@probo.com>.
|
|
||||||
//
|
|
||||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
||||||
// of this software and associated documentation files (the "Software"), to deal
|
|
||||||
// in the Software without restriction, including without limitation the rights
|
|
||||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
||||||
// copies of the Software, and to permit persons to whom the Software is
|
|
||||||
// furnished to do so, subject to the following conditions:
|
|
||||||
//
|
|
||||||
// The above copyright notice and this permission notice shall be included in
|
|
||||||
// all copies or substantial portions of the Software.
|
|
||||||
//
|
|
||||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
||||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
||||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
||||||
// SOFTWARE.
|
|
||||||
|
|
||||||
import { createContext, useContext } from "react";
|
|
||||||
|
|
||||||
export type OpenSignInOptions = {
|
|
||||||
// Absolute URL the user should return to once authenticated. Defaults to the
|
|
||||||
// current page. Include the request-all marker to resume an access request.
|
|
||||||
continueTo?: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type SignInDialogContextValue = {
|
|
||||||
openSignIn: (options?: OpenSignInOptions) => void;
|
|
||||||
};
|
|
||||||
|
|
||||||
const SignInDialogContext = createContext<SignInDialogContextValue | null>(null);
|
|
||||||
|
|
||||||
export const SignInDialogContextProvider = SignInDialogContext.Provider;
|
|
||||||
|
|
||||||
export function useSignInDialog(): SignInDialogContextValue {
|
|
||||||
const context = useContext(SignInDialogContext);
|
|
||||||
if (context === null) {
|
|
||||||
throw new Error("useSignInDialog must be used within a SignInDialogProvider");
|
|
||||||
}
|
|
||||||
return context;
|
|
||||||
}
|
|
||||||
@@ -96,7 +96,7 @@ const requestFileMutation = graphql`
|
|||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
// After a user signs in through the dialog, they land back on the page that
|
// After a user signs in through OAuth /initiate, they land back on the page that
|
||||||
// carried a deferred access marker. This hook fires the matching mutation once
|
// carried a deferred access marker. This hook fires the matching mutation once
|
||||||
// (when authenticated) — request-all from the top bar, or a single
|
// (when authenticated) — request-all from the top bar, or a single
|
||||||
// document / report / file requested from a locked row — routes to the
|
// document / report / file requested from a locked row — routes to the
|
||||||
|
|||||||
@@ -30,8 +30,7 @@ import { graphql, useFragment } from "react-relay";
|
|||||||
import { useSearchParams } from "react-router";
|
import { useSearchParams } from "react-router";
|
||||||
|
|
||||||
import { SubscribeDialog } from "#/components/SubscribeDialog/SubscribeDialog";
|
import { SubscribeDialog } from "#/components/SubscribeDialog/SubscribeDialog";
|
||||||
import { buildSubscribeContinueUrl, SUBSCRIBE_PARAM } from "#/lib/auth/continueUrl";
|
import { buildSubscribeContinueUrl, redirectToInitiate, SUBSCRIBE_PARAM } from "#/lib/auth/continueUrl";
|
||||||
import { useSignInDialog } from "#/lib/auth/signInDialogContext";
|
|
||||||
import {
|
import {
|
||||||
SubscribeDialogContextProvider,
|
SubscribeDialogContextProvider,
|
||||||
} from "#/lib/mailingList/subscribeDialogContext";
|
} from "#/lib/mailingList/subscribeDialogContext";
|
||||||
@@ -69,7 +68,6 @@ export function SubscribeDialogProvider({
|
|||||||
children,
|
children,
|
||||||
}: SubscribeDialogProviderProps) {
|
}: SubscribeDialogProviderProps) {
|
||||||
const data = useFragment(subscribeDialogProviderFragment, queryKey);
|
const data = useFragment(subscribeDialogProviderFragment, queryKey);
|
||||||
const { openSignIn } = useSignInDialog();
|
|
||||||
const [searchParams, setSearchParams] = useSearchParams();
|
const [searchParams, setSearchParams] = useSearchParams();
|
||||||
const [dialogOpen, setDialogOpen] = useState(false);
|
const [dialogOpen, setDialogOpen] = useState(false);
|
||||||
const [unsubscribeFromMailingList, isUnsubscribing] = useUnsubscribeFromMailingList();
|
const [unsubscribeFromMailingList, isUnsubscribing] = useUnsubscribeFromMailingList();
|
||||||
@@ -80,11 +78,11 @@ export function SubscribeDialogProvider({
|
|||||||
|
|
||||||
const openSubscribe = useCallback(() => {
|
const openSubscribe = useCallback(() => {
|
||||||
if (viewer == null) {
|
if (viewer == null) {
|
||||||
openSignIn({ continueTo: buildSubscribeContinueUrl() });
|
redirectToInitiate(buildSubscribeContinueUrl());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
setDialogOpen(true);
|
setDialogOpen(true);
|
||||||
}, [openSignIn, viewer]);
|
}, [viewer]);
|
||||||
|
|
||||||
const unsubscribe = useCallback(async () => {
|
const unsubscribe = useCallback(async () => {
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -25,7 +25,6 @@ import { Outlet, useMatch } from "react-router";
|
|||||||
|
|
||||||
import { PoweredBy } from "#/components/PoweredBy/PoweredBy";
|
import { PoweredBy } from "#/components/PoweredBy/PoweredBy";
|
||||||
import { TopBar } from "#/components/TopBar/TopBar";
|
import { TopBar } from "#/components/TopBar/TopBar";
|
||||||
import { SignInDialogProvider } from "#/lib/auth/SignInDialogProvider";
|
|
||||||
import { useResumeAccessRequest } from "#/lib/auth/useResumeAccessRequest";
|
import { useResumeAccessRequest } from "#/lib/auth/useResumeAccessRequest";
|
||||||
import { SubscribeDialogProvider } from "#/lib/mailingList/SubscribeDialogProvider";
|
import { SubscribeDialogProvider } from "#/lib/mailingList/SubscribeDialogProvider";
|
||||||
|
|
||||||
@@ -57,28 +56,26 @@ export function MainLayout({ queryRef }: MainLayoutProps) {
|
|||||||
// stage; every other page uses normal document flow so the footer sits after
|
// stage; every other page uses normal document flow so the footer sits after
|
||||||
// content (and at the bottom of short pages via flex-1 main).
|
// content (and at the bottom of short pages via flex-1 main).
|
||||||
return (
|
return (
|
||||||
<SignInDialogProvider>
|
<SubscribeDialogProvider queryKey={data}>
|
||||||
<SubscribeDialogProvider queryKey={data}>
|
<div
|
||||||
|
className={
|
||||||
|
isDocumentViewer
|
||||||
|
? "flex h-dvh flex-col bg-sand-2"
|
||||||
|
: "flex min-h-dvh flex-col bg-sand-2"
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<TopBar queryKey={data} />
|
||||||
<div
|
<div
|
||||||
className={
|
className={
|
||||||
isDocumentViewer
|
isDocumentViewer
|
||||||
? "flex h-dvh flex-col bg-sand-2"
|
? "flex min-h-0 flex-1 flex-col overflow-hidden"
|
||||||
: "flex min-h-dvh flex-col bg-sand-2"
|
: "flex flex-1 flex-col"
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<TopBar queryKey={data} />
|
<Outlet />
|
||||||
<div
|
|
||||||
className={
|
|
||||||
isDocumentViewer
|
|
||||||
? "flex min-h-0 flex-1 flex-col overflow-hidden"
|
|
||||||
: "flex flex-1 flex-col"
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<Outlet />
|
|
||||||
</div>
|
|
||||||
{isDocumentViewer ? null : <PoweredBy label={t("footer.poweredBy")} />}
|
|
||||||
</div>
|
</div>
|
||||||
</SubscribeDialogProvider>
|
{isDocumentViewer ? null : <PoweredBy label={t("footer.poweredBy")} />}
|
||||||
</SignInDialogProvider>
|
</div>
|
||||||
|
</SubscribeDialogProvider>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,8 +21,7 @@
|
|||||||
import { Card } from "@probo/ui/src/v2/Card/Card";
|
import { Card } from "@probo/ui/src/v2/Card/Card";
|
||||||
import { Outlet } from "react-router";
|
import { Outlet } from "react-router";
|
||||||
|
|
||||||
// Minimal centered chrome for the standalone auth steps that a magic-link email
|
// Minimal centered chrome for standalone auth steps (e.g. the full-name gate).
|
||||||
// or a full-name gate lands on (these cannot live inside the portal shell).
|
|
||||||
export default function AuthLayout() {
|
export default function AuthLayout() {
|
||||||
return (
|
return (
|
||||||
<div className="flex min-h-screen flex-col items-center justify-center bg-sand-2 p-4">
|
<div className="flex min-h-screen flex-col items-center justify-center bg-sand-2 p-4">
|
||||||
|
|||||||
@@ -1,39 +0,0 @@
|
|||||||
// Copyright (c) 2026 Probo Inc <hello@probo.com>.
|
|
||||||
//
|
|
||||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
||||||
// of this software and associated documentation files (the "Software"), to deal
|
|
||||||
// in the Software without restriction, including without limitation the rights
|
|
||||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
||||||
// copies of the Software, and to permit persons to whom the Software is
|
|
||||||
// furnished to do so, subject to the following conditions:
|
|
||||||
//
|
|
||||||
// The above copyright notice and this permission notice shall be included in
|
|
||||||
// all copies or substantial portions of the Software.
|
|
||||||
//
|
|
||||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
||||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
||||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
||||||
// SOFTWARE.
|
|
||||||
|
|
||||||
import { Link } from "@probo/ui/src/v2/Button/Link";
|
|
||||||
import { Heading } from "@probo/ui/src/v2/typography/Heading";
|
|
||||||
import { Text } from "@probo/ui/src/v2/typography/Text";
|
|
||||||
import { useTranslation } from "react-i18next";
|
|
||||||
|
|
||||||
// Shown when a magic link was already consumed; the user restarts sign-in.
|
|
||||||
export default function MagicLinkAlreadyUsedPage() {
|
|
||||||
const { t } = useTranslation();
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="flex flex-col items-center gap-4 text-center">
|
|
||||||
<Heading level={1} size={5}>{t("auth.magicLinkAlreadyUsed.title")}</Heading>
|
|
||||||
<Text color="neutral">{t("auth.magicLinkAlreadyUsed.description")}</Text>
|
|
||||||
<Link to="/" variant="solid" color="neutral" highContrast>
|
|
||||||
{t("auth.backToPortal")}
|
|
||||||
</Link>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,39 +0,0 @@
|
|||||||
// Copyright (c) 2026 Probo Inc <hello@probo.com>.
|
|
||||||
//
|
|
||||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
||||||
// of this software and associated documentation files (the "Software"), to deal
|
|
||||||
// in the Software without restriction, including without limitation the rights
|
|
||||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
||||||
// copies of the Software, and to permit persons to whom the Software is
|
|
||||||
// furnished to do so, subject to the following conditions:
|
|
||||||
//
|
|
||||||
// The above copyright notice and this permission notice shall be included in
|
|
||||||
// all copies or substantial portions of the Software.
|
|
||||||
//
|
|
||||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
||||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
||||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
||||||
// SOFTWARE.
|
|
||||||
|
|
||||||
import { Link } from "@probo/ui/src/v2/Button/Link";
|
|
||||||
import { Heading } from "@probo/ui/src/v2/typography/Heading";
|
|
||||||
import { Text } from "@probo/ui/src/v2/typography/Text";
|
|
||||||
import { useTranslation } from "react-i18next";
|
|
||||||
|
|
||||||
// Shown when a magic link has expired; the user restarts sign-in from home.
|
|
||||||
export default function MagicLinkExpiredPage() {
|
|
||||||
const { t } = useTranslation();
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="flex flex-col items-center gap-4 text-center">
|
|
||||||
<Heading level={1} size={5}>{t("auth.magicLinkExpired.title")}</Heading>
|
|
||||||
<Text color="neutral">{t("auth.magicLinkExpired.description")}</Text>
|
|
||||||
<Link to="/" variant="solid" color="neutral" highContrast>
|
|
||||||
{t("auth.backToPortal")}
|
|
||||||
</Link>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,103 +0,0 @@
|
|||||||
// Copyright (c) 2026 Probo Inc <hello@probo.com>.
|
|
||||||
//
|
|
||||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
||||||
// of this software and associated documentation files (the "Software"), to deal
|
|
||||||
// in the Software without restriction, including without limitation the rights
|
|
||||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
||||||
// copies of the Software, and to permit persons to whom the Software is
|
|
||||||
// furnished to do so, subject to the following conditions:
|
|
||||||
//
|
|
||||||
// The above copyright notice and this permission notice shall be included in
|
|
||||||
// all copies or substantial portions of the Software.
|
|
||||||
//
|
|
||||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
||||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
||||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
||||||
// SOFTWARE.
|
|
||||||
|
|
||||||
import { Toast } from "@base-ui/react/toast";
|
|
||||||
import type { GraphQLError } from "@probo/helpers";
|
|
||||||
import { Heading } from "@probo/ui/src/v2/typography/Heading";
|
|
||||||
import { Text } from "@probo/ui/src/v2/typography/Text";
|
|
||||||
import { useEffect, useRef } from "react";
|
|
||||||
import { useTranslation } from "react-i18next";
|
|
||||||
import { useNavigate, useSearchParams } from "react-router";
|
|
||||||
import { graphql } from "relay-runtime";
|
|
||||||
|
|
||||||
import { getSafeContinueUrl } from "#/lib/auth/continueUrl";
|
|
||||||
import { useMutation } from "#/lib/relay/useMutation";
|
|
||||||
|
|
||||||
import type { VerifyMagicLinkPageMutation } from "./__generated__/VerifyMagicLinkPageMutation.graphql";
|
|
||||||
|
|
||||||
const verifyMagicLinkMutation = graphql`
|
|
||||||
mutation VerifyMagicLinkPageMutation($input: VerifyMagicLinkInput!) {
|
|
||||||
verifyMagicLink(input: $input) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|
||||||
// Landing page for the magic-link email. It verifies the token on mount and
|
|
||||||
// forwards to the (validated) continue URL, where any pending access request
|
|
||||||
// resumes.
|
|
||||||
export default function VerifyMagicLinkPage() {
|
|
||||||
const { t } = useTranslation();
|
|
||||||
const toast = Toast.useToastManager();
|
|
||||||
const navigate = useNavigate();
|
|
||||||
const [searchParams] = useSearchParams();
|
|
||||||
const submittedRef = useRef(false);
|
|
||||||
|
|
||||||
const [verifyMagicLink] = useMutation<VerifyMagicLinkPageMutation>(
|
|
||||||
verifyMagicLinkMutation,
|
|
||||||
{ errorToast: false },
|
|
||||||
);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const token = searchParams.get("token");
|
|
||||||
if (!token || submittedRef.current) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
submittedRef.current = true;
|
|
||||||
|
|
||||||
void verifyMagicLink({
|
|
||||||
variables: { input: { token: token.trim() } },
|
|
||||||
onCompleted: (response, errors) => {
|
|
||||||
const code = (errors?.[0] as GraphQLError | undefined)?.extensions?.code;
|
|
||||||
|
|
||||||
if (code === "ALREADY_AUTHENTICATED") {
|
|
||||||
// Already signed in: honor a `continue` on the URL if present so a
|
|
||||||
// deferred access request still resumes, instead of always going home.
|
|
||||||
window.location.href = getSafeContinueUrl(searchParams.get("continue"));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (code === "TOKEN_EXPIRED") {
|
|
||||||
void navigate("/magic-link-expired");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (code === "TOKEN_ALREADY_USED") {
|
|
||||||
void navigate("/magic-link-already-used");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (errors && errors.length > 0) {
|
|
||||||
toast.add({ title: t("auth.errors.verifyFailed"), type: "error" });
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
window.location.href = getSafeContinueUrl(response.verifyMagicLink?.continue);
|
|
||||||
},
|
|
||||||
onError: () => {
|
|
||||||
toast.add({ title: t("auth.errors.verifyFailed"), type: "error" });
|
|
||||||
},
|
|
||||||
}).catch(() => {});
|
|
||||||
}, [navigate, searchParams, t, toast, verifyMagicLink]);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="flex flex-col items-center gap-2 text-center">
|
|
||||||
<Heading level={1} size={5}>{t("auth.verify.title")}</Heading>
|
|
||||||
<Text color="neutral">{t("auth.verify.description")}</Text>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -23,29 +23,16 @@ import type { AppRoute } from "@probo/routes";
|
|||||||
|
|
||||||
import { AuthLayoutSkeleton } from "./AuthLayoutSkeleton";
|
import { AuthLayoutSkeleton } from "./AuthLayoutSkeleton";
|
||||||
|
|
||||||
// Standalone auth steps that a magic-link email or a full-name gate lands on.
|
// Standalone auth steps (e.g. the full-name gate) outside the portal shell.
|
||||||
// These sit outside the portal shell, under a minimal centered layout.
|
|
||||||
export const authRoutes = [
|
export const authRoutes = [
|
||||||
{
|
{
|
||||||
Fallback: AuthLayoutSkeleton,
|
Fallback: AuthLayoutSkeleton,
|
||||||
Component: lazy(() => import("#/pages/auth/AuthLayout")),
|
Component: lazy(() => import("#/pages/auth/AuthLayout")),
|
||||||
children: [
|
children: [
|
||||||
{
|
|
||||||
path: "verify-magic-link",
|
|
||||||
Component: lazy(() => import("#/pages/auth/VerifyMagicLinkPage")),
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
path: "full-name",
|
path: "full-name",
|
||||||
Component: lazy(() => import("#/pages/auth/FullNamePage")),
|
Component: lazy(() => import("#/pages/auth/FullNamePage")),
|
||||||
},
|
},
|
||||||
{
|
|
||||||
path: "magic-link-expired",
|
|
||||||
Component: lazy(() => import("#/pages/auth/MagicLinkExpiredPage")),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: "magic-link-already-used",
|
|
||||||
Component: lazy(() => import("#/pages/auth/MagicLinkAlreadyUsedPage")),
|
|
||||||
},
|
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
] satisfies AppRoute[];
|
] satisfies AppRoute[];
|
||||||
|
|||||||
@@ -62,7 +62,7 @@ function StatusIcon({
|
|||||||
|
|
||||||
// Trailing access control for a document entry: a "View" link to the viewer when
|
// Trailing access control for a document entry: a "View" link to the viewer when
|
||||||
// authorized, a pending label when access was requested, otherwise a "Get
|
// authorized, a pending label when access was requested, otherwise a "Get
|
||||||
// Access" action that requests access (prompting sign-in first when needed).
|
// Access" action that requests access (redirecting to OAuth /initiate when needed).
|
||||||
export function DocumentAccessAction({
|
export function DocumentAccessAction({
|
||||||
isAuthorized,
|
isAuthorized,
|
||||||
requested,
|
requested,
|
||||||
|
|||||||
@@ -29,11 +29,11 @@ import { graphql } from "relay-runtime";
|
|||||||
import {
|
import {
|
||||||
buildRequestAccessContinueUrl,
|
buildRequestAccessContinueUrl,
|
||||||
gateRedirectPath,
|
gateRedirectPath,
|
||||||
|
redirectToInitiate,
|
||||||
REQUEST_DOCUMENT_PARAM,
|
REQUEST_DOCUMENT_PARAM,
|
||||||
REQUEST_FILE_PARAM,
|
REQUEST_FILE_PARAM,
|
||||||
REQUEST_REPORT_PARAM,
|
REQUEST_REPORT_PARAM,
|
||||||
} from "#/lib/auth/continueUrl";
|
} from "#/lib/auth/continueUrl";
|
||||||
import { useSignInDialog } from "#/lib/auth/signInDialogContext";
|
|
||||||
import { useMutation } from "#/lib/relay/useMutation";
|
import { useMutation } from "#/lib/relay/useMutation";
|
||||||
|
|
||||||
import type { useAccessRequestDocumentMutation } from "./__generated__/useAccessRequestDocumentMutation.graphql";
|
import type { useAccessRequestDocumentMutation } from "./__generated__/useAccessRequestDocumentMutation.graphql";
|
||||||
@@ -95,11 +95,10 @@ const fileMutation = graphql`
|
|||||||
|
|
||||||
// Shared success / error handling for a single access request. The auth,
|
// Shared success / error handling for a single access request. The auth,
|
||||||
// full-name, and NDA gates are thrown by the fetch layer, so they surface in
|
// full-name, and NDA gates are thrown by the fetch layer, so they surface in
|
||||||
// `onError`: unauthenticated opens the sign-in dialog, while full-name and NDA
|
// `onError`: unauthenticated redirects to OAuth /initiate, while full-name and
|
||||||
// deep-link to their gate page — all deferring the request via the continue URL
|
// NDA deep-link to their gate page — all deferring the request via the continue
|
||||||
// so it resumes once the gate is cleared. Everything else is a generic toast.
|
// URL so it resumes once the gate is cleared. Everything else is a generic toast.
|
||||||
function useAccessRequestHandlers(param: string, id: string) {
|
function useAccessRequestHandlers(param: string, id: string) {
|
||||||
const { openSignIn } = useSignInDialog();
|
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const toast = Toast.useToastManager();
|
const toast = Toast.useToastManager();
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
@@ -116,10 +115,10 @@ function useAccessRequestHandlers(param: string, id: string) {
|
|||||||
onError: (error: Error) => {
|
onError: (error: Error) => {
|
||||||
const continueUrl = buildRequestAccessContinueUrl(param, id);
|
const continueUrl = buildRequestAccessContinueUrl(param, id);
|
||||||
|
|
||||||
// Not signed in: open the dialog, deferring this request until the user
|
// Not signed in: start OAuth, deferring this request until the user lands
|
||||||
// lands back authenticated (see useResumeAccessRequest).
|
// back authenticated (see useResumeAccessRequest).
|
||||||
if (error instanceof UnAuthenticatedError) {
|
if (error instanceof UnAuthenticatedError) {
|
||||||
openSignIn({ continueTo: continueUrl });
|
redirectToInitiate(continueUrl);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// Full-name / NDA gate: deep-link to the gate page, preserving the
|
// Full-name / NDA gate: deep-link to the gate page, preserving the
|
||||||
@@ -132,7 +131,7 @@ function useAccessRequestHandlers(param: string, id: string) {
|
|||||||
toast.add({ title: t("auth.errors.requestFailed"), type: "error" });
|
toast.add({ title: t("auth.errors.requestFailed"), type: "error" });
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
[openSignIn, navigate, toast, t, param, id],
|
[navigate, toast, t, param, id],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -62,9 +62,7 @@ export const ndaPageQuery = graphql`
|
|||||||
id
|
id
|
||||||
}
|
}
|
||||||
currentTrustCenter @required(action: THROW) {
|
currentTrustCenter @required(action: THROW) {
|
||||||
organization {
|
title
|
||||||
name
|
|
||||||
}
|
|
||||||
nonDisclosureAgreement {
|
nonDisclosureAgreement {
|
||||||
fileUrl
|
fileUrl
|
||||||
}
|
}
|
||||||
@@ -238,7 +236,7 @@ export function NDAPage({ queryRef }: NDAPageProps) {
|
|||||||
{t("title")}
|
{t("title")}
|
||||||
</Heading>
|
</Heading>
|
||||||
<Text size={2} color="neutral">
|
<Text size={2} color="neutral">
|
||||||
{t("subtitle", { name: trustCenter.organization.name })}
|
{t("subtitle", { name: trustCenter.title })}
|
||||||
</Text>
|
</Text>
|
||||||
{signature.consentText != null && (
|
{signature.consentText != null && (
|
||||||
<Text size={1} color="faint" className={slots.consent()}>
|
<Text size={1} color="faint" className={slots.consent()}>
|
||||||
|
|||||||
@@ -41,6 +41,20 @@ const (
|
|||||||
ActionCompliancePortalReferenceUpdate = "compliance-portal:portal-reference:update"
|
ActionCompliancePortalReferenceUpdate = "compliance-portal:portal-reference:update"
|
||||||
ActionCompliancePortalReferenceDelete = "compliance-portal:portal-reference:delete"
|
ActionCompliancePortalReferenceDelete = "compliance-portal:portal-reference:delete"
|
||||||
|
|
||||||
|
// Compliance portal commitment group actions.
|
||||||
|
ActionCompliancePortalCommitmentGroupList = "compliance-portal:commitment-group:list"
|
||||||
|
ActionCompliancePortalCommitmentGroupCreate = "compliance-portal:commitment-group:create"
|
||||||
|
ActionCompliancePortalCommitmentGroupUpdate = "compliance-portal:commitment-group:update"
|
||||||
|
ActionCompliancePortalCommitmentGroupUpdateRank = "compliance-portal:commitment-group:update-rank"
|
||||||
|
ActionCompliancePortalCommitmentGroupDelete = "compliance-portal:commitment-group:delete"
|
||||||
|
|
||||||
|
// Compliance portal commitment actions.
|
||||||
|
ActionCompliancePortalCommitmentList = "compliance-portal:commitment:list"
|
||||||
|
ActionCompliancePortalCommitmentCreate = "compliance-portal:commitment:create"
|
||||||
|
ActionCompliancePortalCommitmentUpdate = "compliance-portal:commitment:update"
|
||||||
|
ActionCompliancePortalCommitmentUpdateRank = "compliance-portal:commitment:update-rank"
|
||||||
|
ActionCompliancePortalCommitmentDelete = "compliance-portal:commitment:delete"
|
||||||
|
|
||||||
// Compliance portal file actions.
|
// Compliance portal file actions.
|
||||||
ActionCompliancePortalFileGet = "compliance-portal:portal-file:get"
|
ActionCompliancePortalFileGet = "compliance-portal:portal-file:get"
|
||||||
ActionCompliancePortalFileList = "compliance-portal:portal-file:list"
|
ActionCompliancePortalFileList = "compliance-portal:portal-file:list"
|
||||||
|
|||||||
@@ -18,7 +18,7 @@
|
|||||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
// SOFTWARE.
|
// SOFTWARE.
|
||||||
|
|
||||||
package probo
|
package management
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
@@ -33,10 +33,6 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type (
|
type (
|
||||||
CompliancePortalCommitmentGroupService struct {
|
|
||||||
svc *Service
|
|
||||||
}
|
|
||||||
|
|
||||||
CreateCompliancePortalCommitmentGroupRequest struct {
|
CreateCompliancePortalCommitmentGroupRequest struct {
|
||||||
TrustCenterID gid.GID
|
TrustCenterID gid.GID
|
||||||
Title string
|
Title string
|
||||||
@@ -71,7 +67,7 @@ func (r *UpdateCompliancePortalCommitmentGroupRequest) Validate() error {
|
|||||||
return v.Error()
|
return v.Error()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s CompliancePortalCommitmentGroupService) ListForTrustCenterID(
|
func (s *Service) ListCommitmentGroups(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
scope coredata.Scoper,
|
scope coredata.Scoper,
|
||||||
trustCenterID gid.GID,
|
trustCenterID gid.GID,
|
||||||
@@ -79,14 +75,17 @@ func (s CompliancePortalCommitmentGroupService) ListForTrustCenterID(
|
|||||||
) (*page.Page[*coredata.CompliancePortalCommitmentGroup, coredata.CompliancePortalCommitmentGroupOrderField], error) {
|
) (*page.Page[*coredata.CompliancePortalCommitmentGroup, coredata.CompliancePortalCommitmentGroupOrderField], error) {
|
||||||
var groups coredata.CompliancePortalCommitmentGroups
|
var groups coredata.CompliancePortalCommitmentGroups
|
||||||
|
|
||||||
err := s.svc.pg.WithConn(ctx, func(ctx context.Context, conn pg.Querier) error {
|
err := s.pg.WithConn(
|
||||||
err := groups.LoadByTrustCenterID(ctx, conn, scope, trustCenterID, cursor)
|
ctx,
|
||||||
if err != nil {
|
func(ctx context.Context, conn pg.Querier) error {
|
||||||
return fmt.Errorf("cannot load compliance portal commitment groups: %w", err)
|
err := groups.LoadByTrustCenterID(ctx, conn, scope, trustCenterID, cursor)
|
||||||
}
|
if err != nil {
|
||||||
|
return fmt.Errorf("cannot load compliance portal commitment groups: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
})
|
},
|
||||||
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -94,23 +93,26 @@ func (s CompliancePortalCommitmentGroupService) ListForTrustCenterID(
|
|||||||
return page.NewPage(groups, cursor), nil
|
return page.NewPage(groups, cursor), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s CompliancePortalCommitmentGroupService) CountForTrustCenterID(
|
func (s *Service) CountCommitmentGroups(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
scope coredata.Scoper,
|
scope coredata.Scoper,
|
||||||
trustCenterID gid.GID,
|
trustCenterID gid.GID,
|
||||||
) (int, error) {
|
) (int, error) {
|
||||||
var count int
|
var count int
|
||||||
|
|
||||||
err := s.svc.pg.WithConn(ctx, func(ctx context.Context, conn pg.Querier) (err error) {
|
err := s.pg.WithConn(
|
||||||
groups := coredata.CompliancePortalCommitmentGroups{}
|
ctx,
|
||||||
|
func(ctx context.Context, conn pg.Querier) (err error) {
|
||||||
|
groups := coredata.CompliancePortalCommitmentGroups{}
|
||||||
|
|
||||||
count, err = groups.CountByTrustCenterID(ctx, conn, scope, trustCenterID)
|
count, err = groups.CountByTrustCenterID(ctx, conn, scope, trustCenterID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("cannot count compliance portal commitment groups: %w", err)
|
return fmt.Errorf("cannot count compliance portal commitment groups: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
})
|
},
|
||||||
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, err
|
return 0, err
|
||||||
}
|
}
|
||||||
@@ -118,21 +120,24 @@ func (s CompliancePortalCommitmentGroupService) CountForTrustCenterID(
|
|||||||
return count, nil
|
return count, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s CompliancePortalCommitmentGroupService) Get(
|
func (s *Service) GetCommitmentGroup(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
scope coredata.Scoper,
|
scope coredata.Scoper,
|
||||||
groupID gid.GID,
|
groupID gid.GID,
|
||||||
) (*coredata.CompliancePortalCommitmentGroup, error) {
|
) (*coredata.CompliancePortalCommitmentGroup, error) {
|
||||||
var group coredata.CompliancePortalCommitmentGroup
|
var group coredata.CompliancePortalCommitmentGroup
|
||||||
|
|
||||||
err := s.svc.pg.WithConn(ctx, func(ctx context.Context, conn pg.Querier) error {
|
err := s.pg.WithConn(
|
||||||
err := group.LoadByID(ctx, conn, scope, groupID)
|
ctx,
|
||||||
if err != nil {
|
func(ctx context.Context, conn pg.Querier) error {
|
||||||
return fmt.Errorf("cannot load compliance portal commitment group: %w", err)
|
err := group.LoadByID(ctx, conn, scope, groupID)
|
||||||
}
|
if err != nil {
|
||||||
|
return fmt.Errorf("cannot load compliance portal commitment group: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
})
|
},
|
||||||
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -140,7 +145,7 @@ func (s CompliancePortalCommitmentGroupService) Get(
|
|||||||
return &group, nil
|
return &group, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s CompliancePortalCommitmentGroupService) Create(
|
func (s *Service) CreateCommitmentGroup(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
scope coredata.Scoper,
|
scope coredata.Scoper,
|
||||||
req *CreateCompliancePortalCommitmentGroupRequest,
|
req *CreateCompliancePortalCommitmentGroupRequest,
|
||||||
@@ -155,28 +160,31 @@ func (s CompliancePortalCommitmentGroupService) Create(
|
|||||||
|
|
||||||
var group *coredata.CompliancePortalCommitmentGroup
|
var group *coredata.CompliancePortalCommitmentGroup
|
||||||
|
|
||||||
err := s.svc.pg.WithTx(ctx, func(ctx context.Context, tx pg.Tx) error {
|
err := s.pg.WithTx(
|
||||||
trustCenter := &coredata.TrustCenter{}
|
ctx,
|
||||||
if err := trustCenter.LoadByID(ctx, tx, scope, req.TrustCenterID); err != nil {
|
func(ctx context.Context, tx pg.Tx) error {
|
||||||
return fmt.Errorf("cannot load trust center: %w", err)
|
trustCenter := &coredata.TrustCenter{}
|
||||||
}
|
if err := trustCenter.LoadByID(ctx, tx, scope, req.TrustCenterID); err != nil {
|
||||||
|
return fmt.Errorf("cannot load trust center: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
group = &coredata.CompliancePortalCommitmentGroup{
|
group = &coredata.CompliancePortalCommitmentGroup{
|
||||||
ID: groupID,
|
ID: groupID,
|
||||||
OrganizationID: trustCenter.OrganizationID,
|
OrganizationID: trustCenter.OrganizationID,
|
||||||
TrustCenterID: req.TrustCenterID,
|
TrustCenterID: req.TrustCenterID,
|
||||||
Title: req.Title,
|
Title: req.Title,
|
||||||
Description: req.Description,
|
Description: req.Description,
|
||||||
CreatedAt: now,
|
CreatedAt: now,
|
||||||
UpdatedAt: now,
|
UpdatedAt: now,
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := group.Insert(ctx, tx, scope); err != nil {
|
if err := group.Insert(ctx, tx, scope); err != nil {
|
||||||
return fmt.Errorf("cannot insert compliance portal commitment group: %w", err)
|
return fmt.Errorf("cannot insert compliance portal commitment group: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
})
|
},
|
||||||
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -184,7 +192,7 @@ func (s CompliancePortalCommitmentGroupService) Create(
|
|||||||
return group, nil
|
return group, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s CompliancePortalCommitmentGroupService) Update(
|
func (s *Service) UpdateCommitmentGroup(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
scope coredata.Scoper,
|
scope coredata.Scoper,
|
||||||
req *UpdateCompliancePortalCommitmentGroupRequest,
|
req *UpdateCompliancePortalCommitmentGroupRequest,
|
||||||
@@ -197,36 +205,39 @@ func (s CompliancePortalCommitmentGroupService) Update(
|
|||||||
|
|
||||||
var group *coredata.CompliancePortalCommitmentGroup
|
var group *coredata.CompliancePortalCommitmentGroup
|
||||||
|
|
||||||
err := s.svc.pg.WithTx(ctx, func(ctx context.Context, tx pg.Tx) error {
|
err := s.pg.WithTx(
|
||||||
group = &coredata.CompliancePortalCommitmentGroup{}
|
ctx,
|
||||||
|
func(ctx context.Context, tx pg.Tx) error {
|
||||||
|
group = &coredata.CompliancePortalCommitmentGroup{}
|
||||||
|
|
||||||
if err := group.LoadByID(ctx, tx, scope, req.ID); err != nil {
|
if err := group.LoadByID(ctx, tx, scope, req.ID); err != nil {
|
||||||
return fmt.Errorf("cannot load compliance portal commitment group: %w", err)
|
return fmt.Errorf("cannot load compliance portal commitment group: %w", err)
|
||||||
}
|
|
||||||
|
|
||||||
if req.Title != nil {
|
|
||||||
group.Title = *req.Title
|
|
||||||
}
|
|
||||||
|
|
||||||
if req.Description != nil {
|
|
||||||
group.Description = *req.Description
|
|
||||||
}
|
|
||||||
|
|
||||||
group.UpdatedAt = now
|
|
||||||
|
|
||||||
if req.Rank != nil {
|
|
||||||
group.Rank = *req.Rank
|
|
||||||
if err := group.UpdateRank(ctx, tx, scope); err != nil {
|
|
||||||
return fmt.Errorf("cannot update rank: %w", err)
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if err := group.Update(ctx, tx, scope); err != nil {
|
if req.Title != nil {
|
||||||
return fmt.Errorf("cannot update compliance portal commitment group: %w", err)
|
group.Title = *req.Title
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
if req.Description != nil {
|
||||||
})
|
group.Description = *req.Description
|
||||||
|
}
|
||||||
|
|
||||||
|
group.UpdatedAt = now
|
||||||
|
|
||||||
|
if req.Rank != nil {
|
||||||
|
group.Rank = *req.Rank
|
||||||
|
if err := group.UpdateRank(ctx, tx, scope); err != nil {
|
||||||
|
return fmt.Errorf("cannot update rank: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := group.Update(ctx, tx, scope); err != nil {
|
||||||
|
return fmt.Errorf("cannot update compliance portal commitment group: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -234,24 +245,27 @@ func (s CompliancePortalCommitmentGroupService) Update(
|
|||||||
return group, nil
|
return group, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s CompliancePortalCommitmentGroupService) Delete(
|
func (s *Service) DeleteCommitmentGroup(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
scope coredata.Scoper,
|
scope coredata.Scoper,
|
||||||
groupID gid.GID,
|
groupID gid.GID,
|
||||||
) error {
|
) error {
|
||||||
err := s.svc.pg.WithTx(ctx, func(ctx context.Context, tx pg.Tx) error {
|
err := s.pg.WithTx(
|
||||||
group := &coredata.CompliancePortalCommitmentGroup{}
|
ctx,
|
||||||
|
func(ctx context.Context, tx pg.Tx) error {
|
||||||
|
group := &coredata.CompliancePortalCommitmentGroup{}
|
||||||
|
|
||||||
if err := group.LoadByID(ctx, tx, scope, groupID); err != nil {
|
if err := group.LoadByID(ctx, tx, scope, groupID); err != nil {
|
||||||
return fmt.Errorf("cannot load compliance portal commitment group: %w", err)
|
return fmt.Errorf("cannot load compliance portal commitment group: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := group.Delete(ctx, tx, scope); err != nil {
|
if err := group.Delete(ctx, tx, scope); err != nil {
|
||||||
return fmt.Errorf("cannot delete compliance portal commitment group: %w", err)
|
return fmt.Errorf("cannot delete compliance portal commitment group: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
})
|
},
|
||||||
|
)
|
||||||
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -18,7 +18,7 @@
|
|||||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
// SOFTWARE.
|
// SOFTWARE.
|
||||||
|
|
||||||
package probo
|
package management
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
@@ -33,10 +33,6 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type (
|
type (
|
||||||
CompliancePortalCommitmentService struct {
|
|
||||||
svc *Service
|
|
||||||
}
|
|
||||||
|
|
||||||
CreateCompliancePortalCommitmentRequest struct {
|
CreateCompliancePortalCommitmentRequest struct {
|
||||||
GroupID gid.GID
|
GroupID gid.GID
|
||||||
Icon coredata.CompliancePortalCommitmentIcon
|
Icon coredata.CompliancePortalCommitmentIcon
|
||||||
@@ -83,7 +79,7 @@ func (r *UpdateCompliancePortalCommitmentRequest) Validate() error {
|
|||||||
return v.Error()
|
return v.Error()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s CompliancePortalCommitmentService) ListForGroupID(
|
func (s *Service) ListCommitments(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
scope coredata.Scoper,
|
scope coredata.Scoper,
|
||||||
groupID gid.GID,
|
groupID gid.GID,
|
||||||
@@ -91,14 +87,17 @@ func (s CompliancePortalCommitmentService) ListForGroupID(
|
|||||||
) (*page.Page[*coredata.CompliancePortalCommitment, coredata.CompliancePortalCommitmentOrderField], error) {
|
) (*page.Page[*coredata.CompliancePortalCommitment, coredata.CompliancePortalCommitmentOrderField], error) {
|
||||||
var commitments coredata.CompliancePortalCommitments
|
var commitments coredata.CompliancePortalCommitments
|
||||||
|
|
||||||
err := s.svc.pg.WithConn(ctx, func(ctx context.Context, conn pg.Querier) error {
|
err := s.pg.WithConn(
|
||||||
err := commitments.LoadByGroupID(ctx, conn, scope, groupID, cursor)
|
ctx,
|
||||||
if err != nil {
|
func(ctx context.Context, conn pg.Querier) error {
|
||||||
return fmt.Errorf("cannot load compliance portal commitments: %w", err)
|
err := commitments.LoadByGroupID(ctx, conn, scope, groupID, cursor)
|
||||||
}
|
if err != nil {
|
||||||
|
return fmt.Errorf("cannot load compliance portal commitments: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
})
|
},
|
||||||
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -106,23 +105,26 @@ func (s CompliancePortalCommitmentService) ListForGroupID(
|
|||||||
return page.NewPage(commitments, cursor), nil
|
return page.NewPage(commitments, cursor), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s CompliancePortalCommitmentService) CountForGroupID(
|
func (s *Service) CountCommitments(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
scope coredata.Scoper,
|
scope coredata.Scoper,
|
||||||
groupID gid.GID,
|
groupID gid.GID,
|
||||||
) (int, error) {
|
) (int, error) {
|
||||||
var count int
|
var count int
|
||||||
|
|
||||||
err := s.svc.pg.WithConn(ctx, func(ctx context.Context, conn pg.Querier) (err error) {
|
err := s.pg.WithConn(
|
||||||
commitments := coredata.CompliancePortalCommitments{}
|
ctx,
|
||||||
|
func(ctx context.Context, conn pg.Querier) (err error) {
|
||||||
|
commitments := coredata.CompliancePortalCommitments{}
|
||||||
|
|
||||||
count, err = commitments.CountByGroupID(ctx, conn, scope, groupID)
|
count, err = commitments.CountByGroupID(ctx, conn, scope, groupID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("cannot count compliance portal commitments: %w", err)
|
return fmt.Errorf("cannot count compliance portal commitments: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
})
|
},
|
||||||
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, err
|
return 0, err
|
||||||
}
|
}
|
||||||
@@ -130,21 +132,24 @@ func (s CompliancePortalCommitmentService) CountForGroupID(
|
|||||||
return count, nil
|
return count, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s CompliancePortalCommitmentService) Get(
|
func (s *Service) GetCommitment(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
scope coredata.Scoper,
|
scope coredata.Scoper,
|
||||||
commitmentID gid.GID,
|
commitmentID gid.GID,
|
||||||
) (*coredata.CompliancePortalCommitment, error) {
|
) (*coredata.CompliancePortalCommitment, error) {
|
||||||
var commitment coredata.CompliancePortalCommitment
|
var commitment coredata.CompliancePortalCommitment
|
||||||
|
|
||||||
err := s.svc.pg.WithConn(ctx, func(ctx context.Context, conn pg.Querier) error {
|
err := s.pg.WithConn(
|
||||||
err := commitment.LoadByID(ctx, conn, scope, commitmentID)
|
ctx,
|
||||||
if err != nil {
|
func(ctx context.Context, conn pg.Querier) error {
|
||||||
return fmt.Errorf("cannot load compliance portal commitment: %w", err)
|
err := commitment.LoadByID(ctx, conn, scope, commitmentID)
|
||||||
}
|
if err != nil {
|
||||||
|
return fmt.Errorf("cannot load compliance portal commitment: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
})
|
},
|
||||||
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -152,7 +157,7 @@ func (s CompliancePortalCommitmentService) Get(
|
|||||||
return &commitment, nil
|
return &commitment, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s CompliancePortalCommitmentService) Create(
|
func (s *Service) CreateCommitment(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
scope coredata.Scoper,
|
scope coredata.Scoper,
|
||||||
req *CreateCompliancePortalCommitmentRequest,
|
req *CreateCompliancePortalCommitmentRequest,
|
||||||
@@ -167,31 +172,34 @@ func (s CompliancePortalCommitmentService) Create(
|
|||||||
|
|
||||||
var commitment *coredata.CompliancePortalCommitment
|
var commitment *coredata.CompliancePortalCommitment
|
||||||
|
|
||||||
err := s.svc.pg.WithTx(ctx, func(ctx context.Context, tx pg.Tx) error {
|
err := s.pg.WithTx(
|
||||||
group := &coredata.CompliancePortalCommitmentGroup{}
|
ctx,
|
||||||
if err := group.LoadByID(ctx, tx, scope, req.GroupID); err != nil {
|
func(ctx context.Context, tx pg.Tx) error {
|
||||||
return fmt.Errorf("cannot load compliance portal commitment group: %w", err)
|
group := &coredata.CompliancePortalCommitmentGroup{}
|
||||||
}
|
if err := group.LoadByID(ctx, tx, scope, req.GroupID); err != nil {
|
||||||
|
return fmt.Errorf("cannot load compliance portal commitment group: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
commitment = &coredata.CompliancePortalCommitment{
|
commitment = &coredata.CompliancePortalCommitment{
|
||||||
ID: commitmentID,
|
ID: commitmentID,
|
||||||
OrganizationID: group.OrganizationID,
|
OrganizationID: group.OrganizationID,
|
||||||
TrustCenterID: group.TrustCenterID,
|
TrustCenterID: group.TrustCenterID,
|
||||||
GroupID: req.GroupID,
|
GroupID: req.GroupID,
|
||||||
Icon: req.Icon,
|
Icon: req.Icon,
|
||||||
Eyebrow: req.Eyebrow,
|
Eyebrow: req.Eyebrow,
|
||||||
Title: req.Title,
|
Title: req.Title,
|
||||||
Description: req.Description,
|
Description: req.Description,
|
||||||
CreatedAt: now,
|
CreatedAt: now,
|
||||||
UpdatedAt: now,
|
UpdatedAt: now,
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := commitment.Insert(ctx, tx, scope); err != nil {
|
if err := commitment.Insert(ctx, tx, scope); err != nil {
|
||||||
return fmt.Errorf("cannot insert compliance portal commitment: %w", err)
|
return fmt.Errorf("cannot insert compliance portal commitment: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
})
|
},
|
||||||
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -199,7 +207,7 @@ func (s CompliancePortalCommitmentService) Create(
|
|||||||
return commitment, nil
|
return commitment, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s CompliancePortalCommitmentService) Update(
|
func (s *Service) UpdateCommitment(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
scope coredata.Scoper,
|
scope coredata.Scoper,
|
||||||
req *UpdateCompliancePortalCommitmentRequest,
|
req *UpdateCompliancePortalCommitmentRequest,
|
||||||
@@ -212,44 +220,47 @@ func (s CompliancePortalCommitmentService) Update(
|
|||||||
|
|
||||||
var commitment *coredata.CompliancePortalCommitment
|
var commitment *coredata.CompliancePortalCommitment
|
||||||
|
|
||||||
err := s.svc.pg.WithTx(ctx, func(ctx context.Context, tx pg.Tx) error {
|
err := s.pg.WithTx(
|
||||||
commitment = &coredata.CompliancePortalCommitment{}
|
ctx,
|
||||||
|
func(ctx context.Context, tx pg.Tx) error {
|
||||||
|
commitment = &coredata.CompliancePortalCommitment{}
|
||||||
|
|
||||||
if err := commitment.LoadByID(ctx, tx, scope, req.ID); err != nil {
|
if err := commitment.LoadByID(ctx, tx, scope, req.ID); err != nil {
|
||||||
return fmt.Errorf("cannot load compliance portal commitment: %w", err)
|
return fmt.Errorf("cannot load compliance portal commitment: %w", err)
|
||||||
}
|
|
||||||
|
|
||||||
if req.Icon != nil {
|
|
||||||
commitment.Icon = *req.Icon
|
|
||||||
}
|
|
||||||
|
|
||||||
if req.Eyebrow != nil {
|
|
||||||
commitment.Eyebrow = *req.Eyebrow
|
|
||||||
}
|
|
||||||
|
|
||||||
if req.Title != nil {
|
|
||||||
commitment.Title = *req.Title
|
|
||||||
}
|
|
||||||
|
|
||||||
if req.Description != nil {
|
|
||||||
commitment.Description = *req.Description
|
|
||||||
}
|
|
||||||
|
|
||||||
commitment.UpdatedAt = now
|
|
||||||
|
|
||||||
if req.Rank != nil {
|
|
||||||
commitment.Rank = *req.Rank
|
|
||||||
if err := commitment.UpdateRank(ctx, tx, scope); err != nil {
|
|
||||||
return fmt.Errorf("cannot update rank: %w", err)
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if err := commitment.Update(ctx, tx, scope); err != nil {
|
if req.Icon != nil {
|
||||||
return fmt.Errorf("cannot update compliance portal commitment: %w", err)
|
commitment.Icon = *req.Icon
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
if req.Eyebrow != nil {
|
||||||
})
|
commitment.Eyebrow = *req.Eyebrow
|
||||||
|
}
|
||||||
|
|
||||||
|
if req.Title != nil {
|
||||||
|
commitment.Title = *req.Title
|
||||||
|
}
|
||||||
|
|
||||||
|
if req.Description != nil {
|
||||||
|
commitment.Description = *req.Description
|
||||||
|
}
|
||||||
|
|
||||||
|
commitment.UpdatedAt = now
|
||||||
|
|
||||||
|
if req.Rank != nil {
|
||||||
|
commitment.Rank = *req.Rank
|
||||||
|
if err := commitment.UpdateRank(ctx, tx, scope); err != nil {
|
||||||
|
return fmt.Errorf("cannot update rank: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := commitment.Update(ctx, tx, scope); err != nil {
|
||||||
|
return fmt.Errorf("cannot update compliance portal commitment: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -257,24 +268,27 @@ func (s CompliancePortalCommitmentService) Update(
|
|||||||
return commitment, nil
|
return commitment, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s CompliancePortalCommitmentService) Delete(
|
func (s *Service) DeleteCommitment(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
scope coredata.Scoper,
|
scope coredata.Scoper,
|
||||||
commitmentID gid.GID,
|
commitmentID gid.GID,
|
||||||
) error {
|
) error {
|
||||||
err := s.svc.pg.WithTx(ctx, func(ctx context.Context, tx pg.Tx) error {
|
err := s.pg.WithTx(
|
||||||
commitment := &coredata.CompliancePortalCommitment{}
|
ctx,
|
||||||
|
func(ctx context.Context, tx pg.Tx) error {
|
||||||
|
commitment := &coredata.CompliancePortalCommitment{}
|
||||||
|
|
||||||
if err := commitment.LoadByID(ctx, tx, scope, commitmentID); err != nil {
|
if err := commitment.LoadByID(ctx, tx, scope, commitmentID); err != nil {
|
||||||
return fmt.Errorf("cannot load compliance portal commitment: %w", err)
|
return fmt.Errorf("cannot load compliance portal commitment: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := commitment.Delete(ctx, tx, scope); err != nil {
|
if err := commitment.Delete(ctx, tx, scope); err != nil {
|
||||||
return fmt.Errorf("cannot delete compliance portal commitment: %w", err)
|
return fmt.Errorf("cannot delete compliance portal commitment: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
})
|
},
|
||||||
|
)
|
||||||
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -21,14 +21,6 @@ import (
|
|||||||
|
|
||||||
var organizationCondition = policy.Equals("principal.organization_id", "resource.organization_id")
|
var organizationCondition = policy.Equals("principal.organization_id", "resource.organization_id")
|
||||||
|
|
||||||
// FullAccessPolicy grants organization owners and admins complete access to
|
|
||||||
// every compliance portal capability, including custom domains, portal
|
|
||||||
// configuration, access grants, files, references, frameworks, external URLs
|
|
||||||
// and mailing lists.
|
|
||||||
//
|
|
||||||
// The managed probopage subdomain is a system-owned resource and can never be
|
|
||||||
// deleted, so an explicit deny (which takes precedence over any allow) blocks
|
|
||||||
// deletion of managed domains for every role.
|
|
||||||
var FullAccessPolicy = policy.NewPolicy(
|
var FullAccessPolicy = policy.NewPolicy(
|
||||||
"compliance-portal:full-access",
|
"compliance-portal:full-access",
|
||||||
"Compliance Portal Full Access",
|
"Compliance Portal Full Access",
|
||||||
@@ -40,8 +32,6 @@ var FullAccessPolicy = policy.NewPolicy(
|
|||||||
When(policy.Equals("resource.managed", "true")),
|
When(policy.Equals("resource.managed", "true")),
|
||||||
).WithDescription("Full compliance portal access for organization owners and admins")
|
).WithDescription("Full compliance portal access for organization owners and admins")
|
||||||
|
|
||||||
// ViewerPolicy grants organization viewers read-only access to the compliance
|
|
||||||
// portal.
|
|
||||||
var ViewerPolicy = policy.NewPolicy(
|
var ViewerPolicy = policy.NewPolicy(
|
||||||
"compliance-portal:viewer",
|
"compliance-portal:viewer",
|
||||||
"Compliance Portal Viewer",
|
"Compliance Portal Viewer",
|
||||||
@@ -52,11 +42,11 @@ var ViewerPolicy = policy.NewPolicy(
|
|||||||
ActionCompliancePortalDocumentAccessList,
|
ActionCompliancePortalDocumentAccessList,
|
||||||
ActionCompliancePortalFileGet, ActionCompliancePortalFileList, ActionCompliancePortalFileGetFileUrl,
|
ActionCompliancePortalFileGet, ActionCompliancePortalFileList, ActionCompliancePortalFileGetFileUrl,
|
||||||
ActionCompliancePortalReferenceList, ActionCompliancePortalReferenceGetLogoUrl,
|
ActionCompliancePortalReferenceList, ActionCompliancePortalReferenceGetLogoUrl,
|
||||||
|
ActionCompliancePortalCommitmentGroupList, ActionCompliancePortalCommitmentList,
|
||||||
ActionComplianceFrameworkList,
|
ActionComplianceFrameworkList,
|
||||||
).WithSID("compliance-portal-read-access").When(organizationCondition),
|
).WithSID("compliance-portal-read-access").When(organizationCondition),
|
||||||
).WithDescription("Read-only compliance portal access for organization viewers")
|
).WithDescription("Read-only compliance portal access for organization viewers")
|
||||||
|
|
||||||
// PolicySet returns the PolicySet for the compliance portal service.
|
|
||||||
func PolicySet() *iam.PolicySet {
|
func PolicySet() *iam.PolicySet {
|
||||||
return iam.NewPolicySet().
|
return iam.NewPolicySet().
|
||||||
AddRolePolicy("OWNER", FullAccessPolicy).
|
AddRolePolicy("OWNER", FullAccessPolicy).
|
||||||
|
|||||||
@@ -180,6 +180,25 @@ func (r *complianceFrameworkResolver) Framework(ctx context.Context, obj *types.
|
|||||||
return types.NewFramework(framework), nil
|
return types.NewFramework(framework), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Commitments is the resolver for the commitments field.
|
||||||
|
func (r *compliancePortalCommitmentGroupResolver) Commitments(ctx context.Context, obj *types.CompliancePortalCommitmentGroup, first *int, after *page.CursorKey, last *int, before *page.CursorKey) (*types.CompliancePortalCommitmentConnection, error) {
|
||||||
|
scope := coredata.NewScopeFromObjectID(obj.ID)
|
||||||
|
trustService := r.trust
|
||||||
|
pageOrderBy := page.OrderBy[coredata.CompliancePortalCommitmentOrderField]{
|
||||||
|
Field: coredata.CompliancePortalCommitmentOrderFieldRank,
|
||||||
|
Direction: page.OrderDirectionAsc,
|
||||||
|
}
|
||||||
|
cursor := types.NewCursor(first, after, last, before, pageOrderBy)
|
||||||
|
|
||||||
|
commitmentPage, err := trustService.ListCommitmentsForGroupID(ctx, scope, obj.ID, cursor)
|
||||||
|
if err != nil {
|
||||||
|
r.logger.ErrorCtx(ctx, "cannot list public compliance portal commitments", log.Error(err))
|
||||||
|
return nil, gqlutils.Internal(ctx)
|
||||||
|
}
|
||||||
|
|
||||||
|
return types.NewCompliancePortalCommitmentConnection(commitmentPage), nil
|
||||||
|
}
|
||||||
|
|
||||||
// Alias is the resolver for the alias field.
|
// Alias is the resolver for the alias field.
|
||||||
func (r *documentResolver) Alias(ctx context.Context, obj *types.Document) (*string, error) {
|
func (r *documentResolver) Alias(ctx context.Context, obj *types.Document) (*string, error) {
|
||||||
return r.ResourceAliasResolver(ctx, obj.ID)
|
return r.ResourceAliasResolver(ctx, obj.ID)
|
||||||
@@ -750,7 +769,7 @@ func (r *trustCenterResolver) ViewerSubscription(ctx context.Context, obj *types
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Documents is the resolver for the documents field.
|
// Documents is the resolver for the documents field.
|
||||||
func (r *trustCenterResolver) Documents(ctx context.Context, obj *types.TrustCenter, first *int, after *page.CursorKey, last *int, before *page.CursorKey) (*types.DocumentConnection, error) {
|
func (r *trustCenterResolver) Documents(ctx context.Context, obj *types.TrustCenter, first *int, after *page.CursorKey, last *int, before *page.CursorKey, filter *types.TrustCenterVisibilityFilter) (*types.DocumentConnection, error) {
|
||||||
trustCenter := complianceportal.CompliancePageFromContext(ctx)
|
trustCenter := complianceportal.CompliancePageFromContext(ctx)
|
||||||
scope := coredata.NewScopeFromObjectID(obj.ID)
|
scope := coredata.NewScopeFromObjectID(obj.ID)
|
||||||
trustService := r.trust
|
trustService := r.trust
|
||||||
@@ -760,7 +779,12 @@ func (r *trustCenterResolver) Documents(ctx context.Context, obj *types.TrustCen
|
|||||||
}
|
}
|
||||||
cursor := types.NewCursor(first, after, last, before, pageOrderBy)
|
cursor := types.NewCursor(first, after, last, before, pageOrderBy)
|
||||||
|
|
||||||
documentPage, err := trustService.ListDocumentsForOrganizationID(ctx, scope, trustCenter.OrganizationID, cursor)
|
documentFilter := coredata.NewDocumentTrustCenterFilter()
|
||||||
|
if filter != nil && filter.Visibility != nil {
|
||||||
|
documentFilter = documentFilter.WithTrustCenterVisibilities(*filter.Visibility)
|
||||||
|
}
|
||||||
|
|
||||||
|
documentPage, err := trustService.ListDocumentsForOrganizationID(ctx, scope, trustCenter.OrganizationID, cursor, documentFilter)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
r.logger.ErrorCtx(ctx, "cannot list public documents", log.Error(err))
|
r.logger.ErrorCtx(ctx, "cannot list public documents", log.Error(err))
|
||||||
return nil, gqlutils.Internal(ctx)
|
return nil, gqlutils.Internal(ctx)
|
||||||
@@ -770,7 +794,7 @@ func (r *trustCenterResolver) Documents(ctx context.Context, obj *types.TrustCen
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Audits is the resolver for the audits field.
|
// Audits is the resolver for the audits field.
|
||||||
func (r *trustCenterResolver) Audits(ctx context.Context, obj *types.TrustCenter, first *int, after *page.CursorKey, last *int, before *page.CursorKey) (*types.AuditConnection, error) {
|
func (r *trustCenterResolver) Audits(ctx context.Context, obj *types.TrustCenter, first *int, after *page.CursorKey, last *int, before *page.CursorKey, filter *types.TrustCenterVisibilityFilter) (*types.AuditConnection, error) {
|
||||||
trustCenter := complianceportal.CompliancePageFromContext(ctx)
|
trustCenter := complianceportal.CompliancePageFromContext(ctx)
|
||||||
scope := coredata.NewScopeFromObjectID(obj.ID)
|
scope := coredata.NewScopeFromObjectID(obj.ID)
|
||||||
trustService := r.trust
|
trustService := r.trust
|
||||||
@@ -780,7 +804,12 @@ func (r *trustCenterResolver) Audits(ctx context.Context, obj *types.TrustCenter
|
|||||||
}
|
}
|
||||||
cursor := types.NewCursor(first, after, last, before, pageOrderBy)
|
cursor := types.NewCursor(first, after, last, before, pageOrderBy)
|
||||||
|
|
||||||
auditPage, err := trustService.ListAuditsForOrganizationID(ctx, scope, trustCenter.OrganizationID, cursor)
|
auditFilter := coredata.NewAuditTrustCenterFilter()
|
||||||
|
if filter != nil && filter.Visibility != nil {
|
||||||
|
auditFilter = auditFilter.WithTrustCenterVisibilities(*filter.Visibility)
|
||||||
|
}
|
||||||
|
|
||||||
|
auditPage, err := trustService.ListAuditsForOrganizationID(ctx, scope, trustCenter.OrganizationID, cursor, auditFilter)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
r.logger.ErrorCtx(ctx, "cannot list public audits", log.Error(err))
|
r.logger.ErrorCtx(ctx, "cannot list public audits", log.Error(err))
|
||||||
return nil, gqlutils.Internal(ctx)
|
return nil, gqlutils.Internal(ctx)
|
||||||
@@ -878,8 +907,27 @@ func (r *trustCenterResolver) References(ctx context.Context, obj *types.TrustCe
|
|||||||
return types.NewTrustCenterReferenceConnection(referencePage), nil
|
return types.NewTrustCenterReferenceConnection(referencePage), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CommitmentGroups is the resolver for the commitmentGroups field.
|
||||||
|
func (r *trustCenterResolver) CommitmentGroups(ctx context.Context, obj *types.TrustCenter, first *int, after *page.CursorKey, last *int, before *page.CursorKey) (*types.CompliancePortalCommitmentGroupConnection, error) {
|
||||||
|
scope := coredata.NewScopeFromObjectID(obj.ID)
|
||||||
|
trustService := r.trust
|
||||||
|
pageOrderBy := page.OrderBy[coredata.CompliancePortalCommitmentGroupOrderField]{
|
||||||
|
Field: coredata.CompliancePortalCommitmentGroupOrderFieldRank,
|
||||||
|
Direction: page.OrderDirectionAsc,
|
||||||
|
}
|
||||||
|
cursor := types.NewCursor(first, after, last, before, pageOrderBy)
|
||||||
|
|
||||||
|
groupPage, err := trustService.ListCommitmentGroupsForPortalID(ctx, scope, obj.ID, cursor)
|
||||||
|
if err != nil {
|
||||||
|
r.logger.ErrorCtx(ctx, "cannot list public compliance portal commitment groups", log.Error(err))
|
||||||
|
return nil, gqlutils.Internal(ctx)
|
||||||
|
}
|
||||||
|
|
||||||
|
return types.NewCompliancePortalCommitmentGroupConnection(groupPage), nil
|
||||||
|
}
|
||||||
|
|
||||||
// TrustCenterFiles is the resolver for the trustCenterFiles field.
|
// TrustCenterFiles is the resolver for the trustCenterFiles field.
|
||||||
func (r *trustCenterResolver) TrustCenterFiles(ctx context.Context, obj *types.TrustCenter, first *int, after *page.CursorKey, last *int, before *page.CursorKey) (*types.TrustCenterFileConnection, error) {
|
func (r *trustCenterResolver) TrustCenterFiles(ctx context.Context, obj *types.TrustCenter, first *int, after *page.CursorKey, last *int, before *page.CursorKey, filter *types.TrustCenterVisibilityFilter) (*types.TrustCenterFileConnection, error) {
|
||||||
trustCenter := complianceportal.CompliancePageFromContext(ctx)
|
trustCenter := complianceportal.CompliancePageFromContext(ctx)
|
||||||
scope := coredata.NewScopeFromObjectID(obj.ID)
|
scope := coredata.NewScopeFromObjectID(obj.ID)
|
||||||
trustService := r.trust
|
trustService := r.trust
|
||||||
@@ -889,14 +937,19 @@ func (r *trustCenterResolver) TrustCenterFiles(ctx context.Context, obj *types.T
|
|||||||
}
|
}
|
||||||
cursor := types.NewCursor(first, after, last, before, pageOrderBy)
|
cursor := types.NewCursor(first, after, last, before, pageOrderBy)
|
||||||
|
|
||||||
filter := coredata.NewTrustCenterFileFilter(
|
visibilities := []coredata.TrustCenterVisibility{
|
||||||
coredata.WithTrustCenterFileVisibilities(
|
coredata.TrustCenterVisibilityPublic,
|
||||||
coredata.TrustCenterVisibilityPublic,
|
coredata.TrustCenterVisibilityPrivate,
|
||||||
coredata.TrustCenterVisibilityPrivate,
|
}
|
||||||
),
|
if filter != nil && filter.Visibility != nil {
|
||||||
|
visibilities = []coredata.TrustCenterVisibility{*filter.Visibility}
|
||||||
|
}
|
||||||
|
|
||||||
|
fileFilter := coredata.NewTrustCenterFileFilter(
|
||||||
|
coredata.WithTrustCenterFileVisibilities(visibilities...),
|
||||||
)
|
)
|
||||||
|
|
||||||
trustCenterFilePage, err := trustService.ListPortalFilesForOrganizationID(ctx, scope, trustCenter.OrganizationID, cursor, filter)
|
trustCenterFilePage, err := trustService.ListPortalFilesForOrganizationID(ctx, scope, trustCenter.OrganizationID, cursor, fileFilter)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
r.logger.ErrorCtx(ctx, "cannot list public trust center files", log.Error(err))
|
r.logger.ErrorCtx(ctx, "cannot list public trust center files", log.Error(err))
|
||||||
return nil, gqlutils.Internal(ctx)
|
return nil, gqlutils.Internal(ctx)
|
||||||
@@ -1087,6 +1140,11 @@ func (r *Resolver) ComplianceFramework() schema.ComplianceFrameworkResolver {
|
|||||||
return &complianceFrameworkResolver{r}
|
return &complianceFrameworkResolver{r}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CompliancePortalCommitmentGroup returns schema.CompliancePortalCommitmentGroupResolver implementation.
|
||||||
|
func (r *Resolver) CompliancePortalCommitmentGroup() schema.CompliancePortalCommitmentGroupResolver {
|
||||||
|
return &compliancePortalCommitmentGroupResolver{r}
|
||||||
|
}
|
||||||
|
|
||||||
// Document returns schema.DocumentResolver implementation.
|
// Document returns schema.DocumentResolver implementation.
|
||||||
func (r *Resolver) Document() schema.DocumentResolver { return &documentResolver{r} }
|
func (r *Resolver) Document() schema.DocumentResolver { return &documentResolver{r} }
|
||||||
|
|
||||||
@@ -1112,13 +1170,14 @@ func (r *Resolver) TrustCenterReference() schema.TrustCenterReferenceResolver {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type (
|
type (
|
||||||
auditResolver struct{ *Resolver }
|
auditResolver struct{ *Resolver }
|
||||||
auditReportResolver struct{ *Resolver }
|
auditReportResolver struct{ *Resolver }
|
||||||
complianceFrameworkResolver struct{ *Resolver }
|
complianceFrameworkResolver struct{ *Resolver }
|
||||||
documentResolver struct{ *Resolver }
|
compliancePortalCommitmentGroupResolver struct{ *Resolver }
|
||||||
frameworkResolver struct{ *Resolver }
|
documentResolver struct{ *Resolver }
|
||||||
subprocessorConnectionResolver struct{ *Resolver }
|
frameworkResolver struct{ *Resolver }
|
||||||
trustCenterResolver struct{ *Resolver }
|
subprocessorConnectionResolver struct{ *Resolver }
|
||||||
trustCenterFileResolver struct{ *Resolver }
|
trustCenterResolver struct{ *Resolver }
|
||||||
trustCenterReferenceResolver struct{ *Resolver }
|
trustCenterFileResolver struct{ *Resolver }
|
||||||
|
trustCenterReferenceResolver struct{ *Resolver }
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -106,6 +106,88 @@ enum TrustCenterReferenceOrderField
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
enum CompliancePortalCommitmentGroupOrderField
|
||||||
|
@goModel(
|
||||||
|
model: "go.probo.inc/probo/pkg/coredata.CompliancePortalCommitmentGroupOrderField"
|
||||||
|
) {
|
||||||
|
RANK
|
||||||
|
@goEnum(
|
||||||
|
value: "go.probo.inc/probo/pkg/coredata.CompliancePortalCommitmentGroupOrderFieldRank"
|
||||||
|
)
|
||||||
|
CREATED_AT
|
||||||
|
@goEnum(
|
||||||
|
value: "go.probo.inc/probo/pkg/coredata.CompliancePortalCommitmentGroupOrderFieldCreatedAt"
|
||||||
|
)
|
||||||
|
UPDATED_AT
|
||||||
|
@goEnum(
|
||||||
|
value: "go.probo.inc/probo/pkg/coredata.CompliancePortalCommitmentGroupOrderFieldUpdatedAt"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
enum CompliancePortalCommitmentOrderField
|
||||||
|
@goModel(
|
||||||
|
model: "go.probo.inc/probo/pkg/coredata.CompliancePortalCommitmentOrderField"
|
||||||
|
) {
|
||||||
|
RANK
|
||||||
|
@goEnum(
|
||||||
|
value: "go.probo.inc/probo/pkg/coredata.CompliancePortalCommitmentOrderFieldRank"
|
||||||
|
)
|
||||||
|
CREATED_AT
|
||||||
|
@goEnum(
|
||||||
|
value: "go.probo.inc/probo/pkg/coredata.CompliancePortalCommitmentOrderFieldCreatedAt"
|
||||||
|
)
|
||||||
|
UPDATED_AT
|
||||||
|
@goEnum(
|
||||||
|
value: "go.probo.inc/probo/pkg/coredata.CompliancePortalCommitmentOrderFieldUpdatedAt"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
enum CompliancePortalCommitmentIcon
|
||||||
|
@goModel(
|
||||||
|
model: "go.probo.inc/probo/pkg/coredata.CompliancePortalCommitmentIcon"
|
||||||
|
) {
|
||||||
|
LOCK_KEY
|
||||||
|
@goEnum(value: "go.probo.inc/probo/pkg/coredata.CompliancePortalCommitmentIconLockKey")
|
||||||
|
EYE_SLASH
|
||||||
|
@goEnum(value: "go.probo.inc/probo/pkg/coredata.CompliancePortalCommitmentIconEyeSlash")
|
||||||
|
FINGERPRINT
|
||||||
|
@goEnum(value: "go.probo.inc/probo/pkg/coredata.CompliancePortalCommitmentIconFingerprint")
|
||||||
|
SHIELD_WARNING
|
||||||
|
@goEnum(value: "go.probo.inc/probo/pkg/coredata.CompliancePortalCommitmentIconShieldWarning")
|
||||||
|
SHIELD_CHECK
|
||||||
|
@goEnum(value: "go.probo.inc/probo/pkg/coredata.CompliancePortalCommitmentIconShieldCheck")
|
||||||
|
SIREN
|
||||||
|
@goEnum(value: "go.probo.inc/probo/pkg/coredata.CompliancePortalCommitmentIconSiren")
|
||||||
|
KEY
|
||||||
|
@goEnum(value: "go.probo.inc/probo/pkg/coredata.CompliancePortalCommitmentIconKey")
|
||||||
|
LOCK
|
||||||
|
@goEnum(value: "go.probo.inc/probo/pkg/coredata.CompliancePortalCommitmentIconLock")
|
||||||
|
CLOUD
|
||||||
|
@goEnum(value: "go.probo.inc/probo/pkg/coredata.CompliancePortalCommitmentIconCloud")
|
||||||
|
DATABASE
|
||||||
|
@goEnum(value: "go.probo.inc/probo/pkg/coredata.CompliancePortalCommitmentIconDatabase")
|
||||||
|
GLOBE
|
||||||
|
@goEnum(value: "go.probo.inc/probo/pkg/coredata.CompliancePortalCommitmentIconGlobe")
|
||||||
|
EYE
|
||||||
|
@goEnum(value: "go.probo.inc/probo/pkg/coredata.CompliancePortalCommitmentIconEye")
|
||||||
|
USERS
|
||||||
|
@goEnum(value: "go.probo.inc/probo/pkg/coredata.CompliancePortalCommitmentIconUsers")
|
||||||
|
CERTIFICATE
|
||||||
|
@goEnum(value: "go.probo.inc/probo/pkg/coredata.CompliancePortalCommitmentIconCertificate")
|
||||||
|
GAVEL
|
||||||
|
@goEnum(value: "go.probo.inc/probo/pkg/coredata.CompliancePortalCommitmentIconGavel")
|
||||||
|
HEARTBEAT
|
||||||
|
@goEnum(value: "go.probo.inc/probo/pkg/coredata.CompliancePortalCommitmentIconHeartbeat")
|
||||||
|
BELL
|
||||||
|
@goEnum(value: "go.probo.inc/probo/pkg/coredata.CompliancePortalCommitmentIconBell")
|
||||||
|
BUG
|
||||||
|
@goEnum(value: "go.probo.inc/probo/pkg/coredata.CompliancePortalCommitmentIconBug")
|
||||||
|
CODE
|
||||||
|
@goEnum(value: "go.probo.inc/probo/pkg/coredata.CompliancePortalCommitmentIconCode")
|
||||||
|
SERVER
|
||||||
|
@goEnum(value: "go.probo.inc/probo/pkg/coredata.CompliancePortalCommitmentIconServer")
|
||||||
|
}
|
||||||
|
|
||||||
enum ComplianceCustomLinkOrderField
|
enum ComplianceCustomLinkOrderField
|
||||||
@goModel(
|
@goModel(
|
||||||
model: "go.probo.inc/probo/pkg/coredata.ComplianceCustomLinkOrderField"
|
model: "go.probo.inc/probo/pkg/coredata.ComplianceCustomLinkOrderField"
|
||||||
@@ -218,6 +300,22 @@ input TrustCenterReferenceOrder
|
|||||||
field: TrustCenterReferenceOrderField!
|
field: TrustCenterReferenceOrderField!
|
||||||
}
|
}
|
||||||
|
|
||||||
|
input CompliancePortalCommitmentGroupOrder
|
||||||
|
@goModel(
|
||||||
|
model: "go.probo.inc/probo/pkg/server/api/console/v1/types.CompliancePortalCommitmentGroupOrderBy"
|
||||||
|
) {
|
||||||
|
direction: OrderDirection!
|
||||||
|
field: CompliancePortalCommitmentGroupOrderField!
|
||||||
|
}
|
||||||
|
|
||||||
|
input CompliancePortalCommitmentOrder
|
||||||
|
@goModel(
|
||||||
|
model: "go.probo.inc/probo/pkg/server/api/console/v1/types.CompliancePortalCommitmentOrderBy"
|
||||||
|
) {
|
||||||
|
direction: OrderDirection!
|
||||||
|
field: CompliancePortalCommitmentOrderField!
|
||||||
|
}
|
||||||
|
|
||||||
input TrustCenterFileOrder
|
input TrustCenterFileOrder
|
||||||
@goModel(
|
@goModel(
|
||||||
model: "go.probo.inc/probo/pkg/server/api/console/v1/types.TrustCenterFileOrderBy"
|
model: "go.probo.inc/probo/pkg/server/api/console/v1/types.TrustCenterFileOrderBy"
|
||||||
@@ -277,6 +375,14 @@ type TrustCenter implements Node
|
|||||||
orderBy: TrustCenterReferenceOrder
|
orderBy: TrustCenterReferenceOrder
|
||||||
): TrustCenterReferenceConnection! @goField(forceResolver: true)
|
): TrustCenterReferenceConnection! @goField(forceResolver: true)
|
||||||
|
|
||||||
|
commitmentGroups(
|
||||||
|
first: Int
|
||||||
|
after: CursorKey
|
||||||
|
last: Int
|
||||||
|
before: CursorKey
|
||||||
|
orderBy: CompliancePortalCommitmentGroupOrder
|
||||||
|
): CompliancePortalCommitmentGroupConnection! @goField(forceResolver: true)
|
||||||
|
|
||||||
complianceFrameworks(
|
complianceFrameworks(
|
||||||
first: Int
|
first: Int
|
||||||
after: CursorKey
|
after: CursorKey
|
||||||
@@ -400,6 +506,66 @@ type TrustCenterReferenceEdge {
|
|||||||
node: TrustCenterReference!
|
node: TrustCenterReference!
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type CompliancePortalCommitmentGroup implements Node {
|
||||||
|
id: ID!
|
||||||
|
title: String!
|
||||||
|
description: String!
|
||||||
|
rank: Int!
|
||||||
|
createdAt: Datetime!
|
||||||
|
updatedAt: Datetime!
|
||||||
|
|
||||||
|
commitments(
|
||||||
|
first: Int
|
||||||
|
after: CursorKey
|
||||||
|
last: Int
|
||||||
|
before: CursorKey
|
||||||
|
orderBy: CompliancePortalCommitmentOrder
|
||||||
|
): CompliancePortalCommitmentConnection! @goField(forceResolver: true)
|
||||||
|
|
||||||
|
permission(action: String!): Boolean! @goField(forceResolver: true)
|
||||||
|
}
|
||||||
|
|
||||||
|
type CompliancePortalCommitmentGroupConnection
|
||||||
|
@goModel(
|
||||||
|
model: "go.probo.inc/probo/pkg/server/api/console/v1/types.CompliancePortalCommitmentGroupConnection"
|
||||||
|
) {
|
||||||
|
totalCount: Int! @goField(forceResolver: true)
|
||||||
|
edges: [CompliancePortalCommitmentGroupEdge!]!
|
||||||
|
pageInfo: PageInfo!
|
||||||
|
}
|
||||||
|
|
||||||
|
type CompliancePortalCommitmentGroupEdge {
|
||||||
|
cursor: CursorKey!
|
||||||
|
node: CompliancePortalCommitmentGroup!
|
||||||
|
}
|
||||||
|
|
||||||
|
type CompliancePortalCommitment implements Node {
|
||||||
|
id: ID!
|
||||||
|
icon: CompliancePortalCommitmentIcon!
|
||||||
|
eyebrow: String!
|
||||||
|
title: String!
|
||||||
|
description: String!
|
||||||
|
rank: Int!
|
||||||
|
createdAt: Datetime!
|
||||||
|
updatedAt: Datetime!
|
||||||
|
|
||||||
|
permission(action: String!): Boolean! @goField(forceResolver: true)
|
||||||
|
}
|
||||||
|
|
||||||
|
type CompliancePortalCommitmentConnection
|
||||||
|
@goModel(
|
||||||
|
model: "go.probo.inc/probo/pkg/server/api/console/v1/types.CompliancePortalCommitmentConnection"
|
||||||
|
) {
|
||||||
|
totalCount: Int! @goField(forceResolver: true)
|
||||||
|
edges: [CompliancePortalCommitmentEdge!]!
|
||||||
|
pageInfo: PageInfo!
|
||||||
|
}
|
||||||
|
|
||||||
|
type CompliancePortalCommitmentEdge {
|
||||||
|
cursor: CursorKey!
|
||||||
|
node: CompliancePortalCommitment!
|
||||||
|
}
|
||||||
|
|
||||||
type ComplianceFramework implements Node
|
type ComplianceFramework implements Node
|
||||||
@goModel(
|
@goModel(
|
||||||
model: "go.probo.inc/probo/pkg/server/api/console/v1/types.ComplianceFramework"
|
model: "go.probo.inc/probo/pkg/server/api/console/v1/types.ComplianceFramework"
|
||||||
@@ -526,6 +692,24 @@ extend type Mutation {
|
|||||||
deleteTrustCenterReference(
|
deleteTrustCenterReference(
|
||||||
input: DeleteTrustCenterReferenceInput!
|
input: DeleteTrustCenterReferenceInput!
|
||||||
): DeleteTrustCenterReferencePayload!
|
): DeleteTrustCenterReferencePayload!
|
||||||
|
createCompliancePortalCommitmentGroup(
|
||||||
|
input: CreateCompliancePortalCommitmentGroupInput!
|
||||||
|
): CreateCompliancePortalCommitmentGroupPayload!
|
||||||
|
updateCompliancePortalCommitmentGroup(
|
||||||
|
input: UpdateCompliancePortalCommitmentGroupInput!
|
||||||
|
): UpdateCompliancePortalCommitmentGroupPayload!
|
||||||
|
deleteCompliancePortalCommitmentGroup(
|
||||||
|
input: DeleteCompliancePortalCommitmentGroupInput!
|
||||||
|
): DeleteCompliancePortalCommitmentGroupPayload!
|
||||||
|
createCompliancePortalCommitment(
|
||||||
|
input: CreateCompliancePortalCommitmentInput!
|
||||||
|
): CreateCompliancePortalCommitmentPayload!
|
||||||
|
updateCompliancePortalCommitment(
|
||||||
|
input: UpdateCompliancePortalCommitmentInput!
|
||||||
|
): UpdateCompliancePortalCommitmentPayload!
|
||||||
|
deleteCompliancePortalCommitment(
|
||||||
|
input: DeleteCompliancePortalCommitmentInput!
|
||||||
|
): DeleteCompliancePortalCommitmentPayload!
|
||||||
createComplianceFramework(
|
createComplianceFramework(
|
||||||
input: CreateComplianceFrameworkInput!
|
input: CreateComplianceFrameworkInput!
|
||||||
): CreateComplianceFrameworkPayload!
|
): CreateComplianceFrameworkPayload!
|
||||||
@@ -630,6 +814,44 @@ input DeleteTrustCenterReferenceInput {
|
|||||||
id: ID!
|
id: ID!
|
||||||
}
|
}
|
||||||
|
|
||||||
|
input CreateCompliancePortalCommitmentGroupInput {
|
||||||
|
trustCenterId: ID!
|
||||||
|
title: String!
|
||||||
|
description: String!
|
||||||
|
}
|
||||||
|
|
||||||
|
input UpdateCompliancePortalCommitmentGroupInput {
|
||||||
|
id: ID!
|
||||||
|
title: String
|
||||||
|
description: String
|
||||||
|
rank: Int
|
||||||
|
}
|
||||||
|
|
||||||
|
input DeleteCompliancePortalCommitmentGroupInput {
|
||||||
|
id: ID!
|
||||||
|
}
|
||||||
|
|
||||||
|
input CreateCompliancePortalCommitmentInput {
|
||||||
|
groupId: ID!
|
||||||
|
icon: CompliancePortalCommitmentIcon!
|
||||||
|
eyebrow: String!
|
||||||
|
title: String!
|
||||||
|
description: String!
|
||||||
|
}
|
||||||
|
|
||||||
|
input UpdateCompliancePortalCommitmentInput {
|
||||||
|
id: ID!
|
||||||
|
icon: CompliancePortalCommitmentIcon
|
||||||
|
eyebrow: String
|
||||||
|
title: String
|
||||||
|
description: String
|
||||||
|
rank: Int
|
||||||
|
}
|
||||||
|
|
||||||
|
input DeleteCompliancePortalCommitmentInput {
|
||||||
|
id: ID!
|
||||||
|
}
|
||||||
|
|
||||||
input CreateComplianceFrameworkInput {
|
input CreateComplianceFrameworkInput {
|
||||||
trustCenterId: ID!
|
trustCenterId: ID!
|
||||||
frameworkId: ID!
|
frameworkId: ID!
|
||||||
@@ -729,6 +951,30 @@ type DeleteTrustCenterReferencePayload {
|
|||||||
deletedTrustCenterReferenceId: ID!
|
deletedTrustCenterReferenceId: ID!
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type CreateCompliancePortalCommitmentGroupPayload {
|
||||||
|
compliancePortalCommitmentGroupEdge: CompliancePortalCommitmentGroupEdge!
|
||||||
|
}
|
||||||
|
|
||||||
|
type UpdateCompliancePortalCommitmentGroupPayload {
|
||||||
|
compliancePortalCommitmentGroup: CompliancePortalCommitmentGroup!
|
||||||
|
}
|
||||||
|
|
||||||
|
type DeleteCompliancePortalCommitmentGroupPayload {
|
||||||
|
deletedCompliancePortalCommitmentGroupId: ID!
|
||||||
|
}
|
||||||
|
|
||||||
|
type CreateCompliancePortalCommitmentPayload {
|
||||||
|
compliancePortalCommitmentEdge: CompliancePortalCommitmentEdge!
|
||||||
|
}
|
||||||
|
|
||||||
|
type UpdateCompliancePortalCommitmentPayload {
|
||||||
|
compliancePortalCommitment: CompliancePortalCommitment!
|
||||||
|
}
|
||||||
|
|
||||||
|
type DeleteCompliancePortalCommitmentPayload {
|
||||||
|
deletedCompliancePortalCommitmentId: ID!
|
||||||
|
}
|
||||||
|
|
||||||
type CreateComplianceFrameworkPayload {
|
type CreateComplianceFrameworkPayload {
|
||||||
complianceFrameworkEdge: ComplianceFrameworkEdge!
|
complianceFrameworkEdge: ComplianceFrameworkEdge!
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -52,6 +52,78 @@ func (r *complianceFrameworkResolver) Framework(ctx context.Context, obj *types.
|
|||||||
return types.NewFramework(framework), nil
|
return types.NewFramework(framework), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Permission is the resolver for the permission field.
|
||||||
|
func (r *compliancePortalCommitmentResolver) Permission(ctx context.Context, obj *types.CompliancePortalCommitment, action string) (bool, error) {
|
||||||
|
return r.Resolver.Permission(ctx, obj, action)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TotalCount is the resolver for the totalCount field.
|
||||||
|
func (r *compliancePortalCommitmentConnectionResolver) TotalCount(ctx context.Context, obj *types.CompliancePortalCommitmentConnection) (int, error) {
|
||||||
|
scope, err := r.authorize(ctx, obj.ParentID, management.ActionCompliancePortalCommitmentList)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
count, err := r.management.CountCommitments(ctx, scope, obj.ParentID)
|
||||||
|
if err != nil {
|
||||||
|
r.logger.ErrorCtx(ctx, "cannot count compliance portal commitments", log.Error(err))
|
||||||
|
return 0, gqlutils.Internal(ctx)
|
||||||
|
}
|
||||||
|
|
||||||
|
return count, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Commitments is the resolver for the commitments field.
|
||||||
|
func (r *compliancePortalCommitmentGroupResolver) Commitments(ctx context.Context, obj *types.CompliancePortalCommitmentGroup, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.OrderBy[coredata.CompliancePortalCommitmentOrderField]) (*types.CompliancePortalCommitmentConnection, error) {
|
||||||
|
scope, err := r.authorize(ctx, obj.ID, management.ActionCompliancePortalCommitmentList)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
pageOrderBy := page.OrderBy[coredata.CompliancePortalCommitmentOrderField]{
|
||||||
|
Field: coredata.CompliancePortalCommitmentOrderFieldRank,
|
||||||
|
Direction: page.OrderDirectionAsc,
|
||||||
|
}
|
||||||
|
|
||||||
|
if orderBy != nil {
|
||||||
|
pageOrderBy = page.OrderBy[coredata.CompliancePortalCommitmentOrderField]{
|
||||||
|
Field: orderBy.Field,
|
||||||
|
Direction: orderBy.Direction,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
cursor := types.NewCursor(first, after, last, before, pageOrderBy)
|
||||||
|
|
||||||
|
result, err := r.management.ListCommitments(ctx, scope, obj.ID, cursor)
|
||||||
|
if err != nil {
|
||||||
|
r.logger.ErrorCtx(ctx, "cannot list compliance portal commitments", log.Error(err))
|
||||||
|
return nil, gqlutils.Internal(ctx)
|
||||||
|
}
|
||||||
|
|
||||||
|
return types.NewCompliancePortalCommitmentConnection(result, obj.ID), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Permission is the resolver for the permission field.
|
||||||
|
func (r *compliancePortalCommitmentGroupResolver) Permission(ctx context.Context, obj *types.CompliancePortalCommitmentGroup, action string) (bool, error) {
|
||||||
|
return r.Resolver.Permission(ctx, obj, action)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TotalCount is the resolver for the totalCount field.
|
||||||
|
func (r *compliancePortalCommitmentGroupConnectionResolver) TotalCount(ctx context.Context, obj *types.CompliancePortalCommitmentGroupConnection) (int, error) {
|
||||||
|
scope, err := r.authorize(ctx, obj.ParentID, management.ActionCompliancePortalCommitmentGroupList)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
count, err := r.management.CountCommitmentGroups(ctx, scope, obj.ParentID)
|
||||||
|
if err != nil {
|
||||||
|
r.logger.ErrorCtx(ctx, "cannot count compliance portal commitment groups", log.Error(err))
|
||||||
|
return 0, gqlutils.Internal(ctx)
|
||||||
|
}
|
||||||
|
|
||||||
|
return count, nil
|
||||||
|
}
|
||||||
|
|
||||||
// Permission is the resolver for the permission field.
|
// Permission is the resolver for the permission field.
|
||||||
func (r *customDomainResolver) Permission(ctx context.Context, obj *types.CustomDomain, action string) (bool, error) {
|
func (r *customDomainResolver) Permission(ctx context.Context, obj *types.CustomDomain, action string) (bool, error) {
|
||||||
return r.Resolver.Permission(ctx, obj, action)
|
return r.Resolver.Permission(ctx, obj, action)
|
||||||
@@ -370,6 +442,166 @@ func (r *mutationResolver) DeleteTrustCenterReference(ctx context.Context, input
|
|||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CreateCompliancePortalCommitmentGroup is the resolver for the createCompliancePortalCommitmentGroup field.
|
||||||
|
func (r *mutationResolver) CreateCompliancePortalCommitmentGroup(ctx context.Context, input types.CreateCompliancePortalCommitmentGroupInput) (*types.CreateCompliancePortalCommitmentGroupPayload, error) {
|
||||||
|
scope, err := r.authorize(ctx, input.TrustCenterID, management.ActionCompliancePortalCommitmentGroupCreate)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
group, err := r.management.CreateCommitmentGroup(
|
||||||
|
ctx, scope,
|
||||||
|
&management.CreateCompliancePortalCommitmentGroupRequest{
|
||||||
|
TrustCenterID: input.TrustCenterID,
|
||||||
|
Title: input.Title,
|
||||||
|
Description: input.Description,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
if validationErrors, ok := errors.AsType[validator.ValidationErrors](err); ok {
|
||||||
|
return nil, gqlutils.InvalidValidationErrors(ctx, validationErrors)
|
||||||
|
}
|
||||||
|
|
||||||
|
r.logger.ErrorCtx(ctx, "cannot create compliance portal commitment group", log.Error(err))
|
||||||
|
|
||||||
|
return nil, gqlutils.Internal(ctx)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &types.CreateCompliancePortalCommitmentGroupPayload{
|
||||||
|
CompliancePortalCommitmentGroupEdge: types.NewCompliancePortalCommitmentGroupEdge(group, coredata.CompliancePortalCommitmentGroupOrderFieldRank),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateCompliancePortalCommitmentGroup is the resolver for the updateCompliancePortalCommitmentGroup field.
|
||||||
|
func (r *mutationResolver) UpdateCompliancePortalCommitmentGroup(ctx context.Context, input types.UpdateCompliancePortalCommitmentGroupInput) (*types.UpdateCompliancePortalCommitmentGroupPayload, error) {
|
||||||
|
scope, err := r.authorize(ctx, input.ID, management.ActionCompliancePortalCommitmentGroupUpdate)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
group, err := r.management.UpdateCommitmentGroup(
|
||||||
|
ctx, scope,
|
||||||
|
&management.UpdateCompliancePortalCommitmentGroupRequest{
|
||||||
|
ID: input.ID,
|
||||||
|
Title: input.Title,
|
||||||
|
Description: input.Description,
|
||||||
|
Rank: input.Rank,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
if validationErrors, ok := errors.AsType[validator.ValidationErrors](err); ok {
|
||||||
|
return nil, gqlutils.InvalidValidationErrors(ctx, validationErrors)
|
||||||
|
}
|
||||||
|
|
||||||
|
r.logger.ErrorCtx(ctx, "cannot update compliance portal commitment group", log.Error(err))
|
||||||
|
|
||||||
|
return nil, gqlutils.Internal(ctx)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &types.UpdateCompliancePortalCommitmentGroupPayload{
|
||||||
|
CompliancePortalCommitmentGroup: types.NewCompliancePortalCommitmentGroup(group),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteCompliancePortalCommitmentGroup is the resolver for the deleteCompliancePortalCommitmentGroup field.
|
||||||
|
func (r *mutationResolver) DeleteCompliancePortalCommitmentGroup(ctx context.Context, input types.DeleteCompliancePortalCommitmentGroupInput) (*types.DeleteCompliancePortalCommitmentGroupPayload, error) {
|
||||||
|
scope, err := r.authorize(ctx, input.ID, management.ActionCompliancePortalCommitmentGroupDelete)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := r.management.DeleteCommitmentGroup(ctx, scope, input.ID); err != nil {
|
||||||
|
r.logger.ErrorCtx(ctx, "cannot delete compliance portal commitment group", log.Error(err))
|
||||||
|
return nil, gqlutils.Internal(ctx)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &types.DeleteCompliancePortalCommitmentGroupPayload{
|
||||||
|
DeletedCompliancePortalCommitmentGroupID: input.ID,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateCompliancePortalCommitment is the resolver for the createCompliancePortalCommitment field.
|
||||||
|
func (r *mutationResolver) CreateCompliancePortalCommitment(ctx context.Context, input types.CreateCompliancePortalCommitmentInput) (*types.CreateCompliancePortalCommitmentPayload, error) {
|
||||||
|
scope, err := r.authorize(ctx, input.GroupID, management.ActionCompliancePortalCommitmentCreate)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
commitment, err := r.management.CreateCommitment(
|
||||||
|
ctx, scope,
|
||||||
|
&management.CreateCompliancePortalCommitmentRequest{
|
||||||
|
GroupID: input.GroupID,
|
||||||
|
Icon: input.Icon,
|
||||||
|
Eyebrow: input.Eyebrow,
|
||||||
|
Title: input.Title,
|
||||||
|
Description: input.Description,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
if validationErrors, ok := errors.AsType[validator.ValidationErrors](err); ok {
|
||||||
|
return nil, gqlutils.InvalidValidationErrors(ctx, validationErrors)
|
||||||
|
}
|
||||||
|
|
||||||
|
r.logger.ErrorCtx(ctx, "cannot create compliance portal commitment", log.Error(err))
|
||||||
|
|
||||||
|
return nil, gqlutils.Internal(ctx)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &types.CreateCompliancePortalCommitmentPayload{
|
||||||
|
CompliancePortalCommitmentEdge: types.NewCompliancePortalCommitmentEdge(commitment, coredata.CompliancePortalCommitmentOrderFieldRank),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateCompliancePortalCommitment is the resolver for the updateCompliancePortalCommitment field.
|
||||||
|
func (r *mutationResolver) UpdateCompliancePortalCommitment(ctx context.Context, input types.UpdateCompliancePortalCommitmentInput) (*types.UpdateCompliancePortalCommitmentPayload, error) {
|
||||||
|
scope, err := r.authorize(ctx, input.ID, management.ActionCompliancePortalCommitmentUpdate)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
commitment, err := r.management.UpdateCommitment(
|
||||||
|
ctx, scope,
|
||||||
|
&management.UpdateCompliancePortalCommitmentRequest{
|
||||||
|
ID: input.ID,
|
||||||
|
Icon: input.Icon,
|
||||||
|
Eyebrow: input.Eyebrow,
|
||||||
|
Title: input.Title,
|
||||||
|
Description: input.Description,
|
||||||
|
Rank: input.Rank,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
if validationErrors, ok := errors.AsType[validator.ValidationErrors](err); ok {
|
||||||
|
return nil, gqlutils.InvalidValidationErrors(ctx, validationErrors)
|
||||||
|
}
|
||||||
|
|
||||||
|
r.logger.ErrorCtx(ctx, "cannot update compliance portal commitment", log.Error(err))
|
||||||
|
|
||||||
|
return nil, gqlutils.Internal(ctx)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &types.UpdateCompliancePortalCommitmentPayload{
|
||||||
|
CompliancePortalCommitment: types.NewCompliancePortalCommitment(commitment),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteCompliancePortalCommitment is the resolver for the deleteCompliancePortalCommitment field.
|
||||||
|
func (r *mutationResolver) DeleteCompliancePortalCommitment(ctx context.Context, input types.DeleteCompliancePortalCommitmentInput) (*types.DeleteCompliancePortalCommitmentPayload, error) {
|
||||||
|
scope, err := r.authorize(ctx, input.ID, management.ActionCompliancePortalCommitmentDelete)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := r.management.DeleteCommitment(ctx, scope, input.ID); err != nil {
|
||||||
|
r.logger.ErrorCtx(ctx, "cannot delete compliance portal commitment", log.Error(err))
|
||||||
|
return nil, gqlutils.Internal(ctx)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &types.DeleteCompliancePortalCommitmentPayload{
|
||||||
|
DeletedCompliancePortalCommitmentID: input.ID,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
// CreateComplianceFramework is the resolver for the createComplianceFramework field.
|
// CreateComplianceFramework is the resolver for the createComplianceFramework field.
|
||||||
func (r *mutationResolver) CreateComplianceFramework(ctx context.Context, input types.CreateComplianceFrameworkInput) (*types.CreateComplianceFrameworkPayload, error) {
|
func (r *mutationResolver) CreateComplianceFramework(ctx context.Context, input types.CreateComplianceFrameworkInput) (*types.CreateComplianceFrameworkPayload, error) {
|
||||||
scope, err := r.authorize(ctx, input.TrustCenterID, management.ActionComplianceFrameworkCreate)
|
scope, err := r.authorize(ctx, input.TrustCenterID, management.ActionComplianceFrameworkCreate)
|
||||||
@@ -821,6 +1053,36 @@ func (r *trustCenterResolver) References(ctx context.Context, obj *types.TrustCe
|
|||||||
return types.NewTrustCenterReferenceConnection(result, obj.ID), nil
|
return types.NewTrustCenterReferenceConnection(result, obj.ID), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CommitmentGroups is the resolver for the commitmentGroups field.
|
||||||
|
func (r *trustCenterResolver) CommitmentGroups(ctx context.Context, obj *types.TrustCenter, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.OrderBy[coredata.CompliancePortalCommitmentGroupOrderField]) (*types.CompliancePortalCommitmentGroupConnection, error) {
|
||||||
|
scope, err := r.authorize(ctx, obj.ID, management.ActionCompliancePortalCommitmentGroupList)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
pageOrderBy := page.OrderBy[coredata.CompliancePortalCommitmentGroupOrderField]{
|
||||||
|
Field: coredata.CompliancePortalCommitmentGroupOrderFieldRank,
|
||||||
|
Direction: page.OrderDirectionAsc,
|
||||||
|
}
|
||||||
|
|
||||||
|
if orderBy != nil {
|
||||||
|
pageOrderBy = page.OrderBy[coredata.CompliancePortalCommitmentGroupOrderField]{
|
||||||
|
Field: orderBy.Field,
|
||||||
|
Direction: orderBy.Direction,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
cursor := types.NewCursor(first, after, last, before, pageOrderBy)
|
||||||
|
|
||||||
|
result, err := r.management.ListCommitmentGroups(ctx, scope, obj.ID, cursor)
|
||||||
|
if err != nil {
|
||||||
|
r.logger.ErrorCtx(ctx, "cannot list compliance portal commitment groups", log.Error(err))
|
||||||
|
return nil, gqlutils.Internal(ctx)
|
||||||
|
}
|
||||||
|
|
||||||
|
return types.NewCompliancePortalCommitmentGroupConnection(result, obj.ID), nil
|
||||||
|
}
|
||||||
|
|
||||||
// ComplianceFrameworks is the resolver for the complianceFrameworks field.
|
// ComplianceFrameworks is the resolver for the complianceFrameworks field.
|
||||||
func (r *trustCenterResolver) ComplianceFrameworks(ctx context.Context, obj *types.TrustCenter, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.OrderBy[coredata.ComplianceFrameworkOrderField]) (*types.ComplianceFrameworkConnection, error) {
|
func (r *trustCenterResolver) ComplianceFrameworks(ctx context.Context, obj *types.TrustCenter, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.OrderBy[coredata.ComplianceFrameworkOrderField]) (*types.ComplianceFrameworkConnection, error) {
|
||||||
scope, err := r.authorize(ctx, obj.ID, management.ActionComplianceFrameworkList)
|
scope, err := r.authorize(ctx, obj.ID, management.ActionComplianceFrameworkList)
|
||||||
@@ -1296,6 +1558,26 @@ func (r *Resolver) ComplianceFramework() schema.ComplianceFrameworkResolver {
|
|||||||
return &complianceFrameworkResolver{r}
|
return &complianceFrameworkResolver{r}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CompliancePortalCommitment returns schema.CompliancePortalCommitmentResolver implementation.
|
||||||
|
func (r *Resolver) CompliancePortalCommitment() schema.CompliancePortalCommitmentResolver {
|
||||||
|
return &compliancePortalCommitmentResolver{r}
|
||||||
|
}
|
||||||
|
|
||||||
|
// CompliancePortalCommitmentConnection returns schema.CompliancePortalCommitmentConnectionResolver implementation.
|
||||||
|
func (r *Resolver) CompliancePortalCommitmentConnection() schema.CompliancePortalCommitmentConnectionResolver {
|
||||||
|
return &compliancePortalCommitmentConnectionResolver{r}
|
||||||
|
}
|
||||||
|
|
||||||
|
// CompliancePortalCommitmentGroup returns schema.CompliancePortalCommitmentGroupResolver implementation.
|
||||||
|
func (r *Resolver) CompliancePortalCommitmentGroup() schema.CompliancePortalCommitmentGroupResolver {
|
||||||
|
return &compliancePortalCommitmentGroupResolver{r}
|
||||||
|
}
|
||||||
|
|
||||||
|
// CompliancePortalCommitmentGroupConnection returns schema.CompliancePortalCommitmentGroupConnectionResolver implementation.
|
||||||
|
func (r *Resolver) CompliancePortalCommitmentGroupConnection() schema.CompliancePortalCommitmentGroupConnectionResolver {
|
||||||
|
return &compliancePortalCommitmentGroupConnectionResolver{r}
|
||||||
|
}
|
||||||
|
|
||||||
// CustomDomain returns schema.CustomDomainResolver implementation.
|
// CustomDomain returns schema.CustomDomainResolver implementation.
|
||||||
func (r *Resolver) CustomDomain() schema.CustomDomainResolver { return &customDomainResolver{r} }
|
func (r *Resolver) CustomDomain() schema.CustomDomainResolver { return &customDomainResolver{r} }
|
||||||
|
|
||||||
@@ -1338,15 +1620,19 @@ func (r *Resolver) TrustCenterReferenceConnection() schema.TrustCenterReferenceC
|
|||||||
}
|
}
|
||||||
|
|
||||||
type (
|
type (
|
||||||
complianceCustomLinkResolver struct{ *Resolver }
|
complianceCustomLinkResolver struct{ *Resolver }
|
||||||
complianceFrameworkResolver struct{ *Resolver }
|
complianceFrameworkResolver struct{ *Resolver }
|
||||||
customDomainResolver struct{ *Resolver }
|
compliancePortalCommitmentResolver struct{ *Resolver }
|
||||||
trustCenterResolver struct{ *Resolver }
|
compliancePortalCommitmentConnectionResolver struct{ *Resolver }
|
||||||
trustCenterAccessResolver struct{ *Resolver }
|
compliancePortalCommitmentGroupResolver struct{ *Resolver }
|
||||||
trustCenterDocumentAccessResolver struct{ *Resolver }
|
compliancePortalCommitmentGroupConnectionResolver struct{ *Resolver }
|
||||||
trustCenterDocumentAccessConnectionResolver struct{ *Resolver }
|
customDomainResolver struct{ *Resolver }
|
||||||
trustCenterFileResolver struct{ *Resolver }
|
trustCenterResolver struct{ *Resolver }
|
||||||
trustCenterFileConnectionResolver struct{ *Resolver }
|
trustCenterAccessResolver struct{ *Resolver }
|
||||||
trustCenterReferenceResolver struct{ *Resolver }
|
trustCenterDocumentAccessResolver struct{ *Resolver }
|
||||||
trustCenterReferenceConnectionResolver struct{ *Resolver }
|
trustCenterDocumentAccessConnectionResolver struct{ *Resolver }
|
||||||
|
trustCenterFileResolver struct{ *Resolver }
|
||||||
|
trustCenterFileConnectionResolver struct{ *Resolver }
|
||||||
|
trustCenterReferenceResolver struct{ *Resolver }
|
||||||
|
trustCenterReferenceConnectionResolver struct{ *Resolver }
|
||||||
)
|
)
|
||||||
|
|||||||
Reference in New Issue
Block a user