Add configurable compliance portal commitment cards
The compliance portal home page rendered security-commitment cards from a hardcoded placeholder POJO. Back them with real, per-organization data that admins configure in the console and the portal loads over the trust center GraphQL API. Model two entities under the trust center: a commitment group (title, description, rank) and a commitment card (icon, eyebrow, title, description, rank). The card icon is a curated enum mapped to a Phosphor icon in the portal. New entities adopt the compliance_portal_ prefix as the start of the broader rename away from trust_center_ naming. Expose the groups and cards read-only on the public trust API and with full CRUD on the console API, add a Commitments tab to the compliance page, and replace the placeholder section with a Relay-driven one. Signed-off-by: Émile Ré <emile@probo.com>
This commit is contained in:
@@ -20,6 +20,7 @@
|
|||||||
"heroDescription": "Welcome to our Compliance Portal. Find our security documentation and certifications here.",
|
"heroDescription": "Welcome to our Compliance Portal. Find our security documentation and certifications here.",
|
||||||
"sections": {
|
"sections": {
|
||||||
"compliance": "Compliance",
|
"compliance": "Compliance",
|
||||||
|
"securityCommitments": "Security Commitments",
|
||||||
"trustedBy": "Trusted by",
|
"trustedBy": "Trusted by",
|
||||||
"recentUpdates": "Recent updates"
|
"recentUpdates": "Recent updates"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -20,6 +20,7 @@
|
|||||||
"heroDescription": "Bienvenue dans notre Compliance Portal. Retrouvez ici notre documentation et nos certifications de sécurité.",
|
"heroDescription": "Bienvenue dans notre Compliance Portal. Retrouvez ici notre documentation et nos certifications de sécurité.",
|
||||||
"sections": {
|
"sections": {
|
||||||
"compliance": "Conformité",
|
"compliance": "Conformité",
|
||||||
|
"securityCommitments": "Engagements de sécurité",
|
||||||
"trustedBy": "Ils nous font confiance",
|
"trustedBy": "Ils nous font confiance",
|
||||||
"recentUpdates": "Mises à jour récentes"
|
"recentUpdates": "Mises à jour récentes"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -0,0 +1,83 @@
|
|||||||
|
// 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 { Text } from "@probo/ui/src/v2/typography/Text";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
import { graphql, useFragment } from "react-relay";
|
||||||
|
|
||||||
|
import type { SecurityCommitmentGroupListItem_group$key } from "./__generated__/SecurityCommitmentGroupListItem_group.graphql";
|
||||||
|
import { SecurityCommitmentListItem } from "./SecurityCommitmentListItem";
|
||||||
|
import { securityCommitments } from "./variants";
|
||||||
|
|
||||||
|
const fragment = graphql`
|
||||||
|
fragment SecurityCommitmentGroupListItem_group on CompliancePortalCommitmentGroup {
|
||||||
|
title
|
||||||
|
description
|
||||||
|
commitments(first: 100) {
|
||||||
|
edges {
|
||||||
|
node {
|
||||||
|
id
|
||||||
|
...SecurityCommitmentListItem_commitment
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
interface SecurityCommitmentGroupListItemProps {
|
||||||
|
groupKey: SecurityCommitmentGroupListItem_group$key;
|
||||||
|
// Only the first group carries the section eyebrow, matching the design.
|
||||||
|
showEyebrow: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function SecurityCommitmentGroupListItem({ groupKey, showEyebrow }: SecurityCommitmentGroupListItemProps) {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const group = useFragment(fragment, groupKey);
|
||||||
|
const slots = securityCommitments();
|
||||||
|
|
||||||
|
const commitments = group.commitments.edges.map(edge => edge.node);
|
||||||
|
|
||||||
|
if (commitments.length === 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={slots.group()}>
|
||||||
|
<div className={slots.groupHeader()}>
|
||||||
|
{showEyebrow && (
|
||||||
|
<Text size={1} color="gold">
|
||||||
|
{t("home.sections.securityCommitments")}
|
||||||
|
</Text>
|
||||||
|
)}
|
||||||
|
<Text size={2} weight="medium" color="neutral" highContrast>
|
||||||
|
{group.title}
|
||||||
|
</Text>
|
||||||
|
<Text size={2} color="neutral">
|
||||||
|
{group.description}
|
||||||
|
</Text>
|
||||||
|
</div>
|
||||||
|
<div className={slots.grid()}>
|
||||||
|
{commitments.map(commitment => (
|
||||||
|
<SecurityCommitmentListItem key={commitment.id} commitmentKey={commitment} />
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,57 @@
|
|||||||
|
// 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 { Text } from "@probo/ui/src/v2/typography/Text";
|
||||||
|
import { graphql, useFragment } from "react-relay";
|
||||||
|
|
||||||
|
import { CommitmentCard } from "#/components/CommitmentCard/CommitmentCard";
|
||||||
|
|
||||||
|
import type { SecurityCommitmentListItem_commitment$key } from "./__generated__/SecurityCommitmentListItem_commitment.graphql";
|
||||||
|
import { CommitmentIcon } from "./commitmentIcons";
|
||||||
|
|
||||||
|
const fragment = graphql`
|
||||||
|
fragment SecurityCommitmentListItem_commitment on CompliancePortalCommitment {
|
||||||
|
icon
|
||||||
|
eyebrow
|
||||||
|
title
|
||||||
|
description
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
interface SecurityCommitmentListItemProps {
|
||||||
|
commitmentKey: SecurityCommitmentListItem_commitment$key;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function SecurityCommitmentListItem({ commitmentKey }: SecurityCommitmentListItemProps) {
|
||||||
|
const commitment = useFragment(fragment, commitmentKey);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<CommitmentCard
|
||||||
|
icon={<CommitmentIcon icon={commitment.icon} size={32} weight="light" />}
|
||||||
|
eyebrow={<Text size={1} color="gold">{commitment.eyebrow}</Text>}
|
||||||
|
title={(
|
||||||
|
<Text size={4} weight="medium" color="neutral" highContrast>
|
||||||
|
{commitment.title}
|
||||||
|
</Text>
|
||||||
|
)}
|
||||||
|
description={<Text size={2} color="neutral">{commitment.description}</Text>}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -18,55 +18,71 @@
|
|||||||
// 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.
|
||||||
|
|
||||||
import { Text } from "@probo/ui/src/v2/typography/Text";
|
import { ErrorBoundary } from "@probo/ui/src/v2/ErrorBoundary/ErrorBoundary";
|
||||||
|
import { graphql, useFragment } from "react-relay";
|
||||||
|
|
||||||
import { CommitmentCard } from "#/components/CommitmentCard/CommitmentCard";
|
import { InlineErrorCard } from "#/components/errors/InlineErrorCard";
|
||||||
|
|
||||||
import { SECURITY_COMMITMENT_GROUPS } from "./securityCommitments";
|
import type { SecurityCommitmentsSection_trustCenter$key } from "./__generated__/SecurityCommitmentsSection_trustCenter.graphql";
|
||||||
|
import { SecurityCommitmentGroupListItem } from "./SecurityCommitmentGroupListItem";
|
||||||
import { securityCommitments } from "./variants";
|
import { securityCommitments } from "./variants";
|
||||||
|
|
||||||
// "Security Commitments" section.
|
// @throwOnFieldError makes a field error in this fragment throw at the read
|
||||||
//
|
// below, where the section's ErrorBoundary contains it. See
|
||||||
// TODO: This section renders placeholder data from a local POJO
|
// contrib/claude/error-handling.md.
|
||||||
// (./securityCommitments.ts) because there is no backend / DB structure for it
|
const securityCommitmentsSectionFragment = graphql`
|
||||||
// yet. Replace it with a relay-driven fragment (and i18n copy) once available.
|
fragment SecurityCommitmentsSection_trustCenter on TrustCenter @throwOnFieldError {
|
||||||
export function SecurityCommitmentsSection() {
|
commitmentGroups(first: 100) {
|
||||||
|
edges {
|
||||||
|
node {
|
||||||
|
id
|
||||||
|
...SecurityCommitmentGroupListItem_group
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
interface SecurityCommitmentsSectionProps {
|
||||||
|
trustCenterKey: SecurityCommitmentsSection_trustCenter$key;
|
||||||
|
}
|
||||||
|
|
||||||
|
// "Security Commitments" section: stacked groups, each a header above a grid of
|
||||||
|
// commitment cards. Wraps its data-reading content in a boundary so a load
|
||||||
|
// failure degrades to an inline error instead of taking down the page.
|
||||||
|
export function SecurityCommitmentsSection({ trustCenterKey }: SecurityCommitmentsSectionProps) {
|
||||||
|
return (
|
||||||
|
<ErrorBoundary
|
||||||
|
fallback={(
|
||||||
|
<div className="w-full py-8">
|
||||||
|
<InlineErrorCard onRetry={() => window.location.reload()} />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<SecurityCommitmentsSectionContent trustCenterKey={trustCenterKey} />
|
||||||
|
</ErrorBoundary>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function SecurityCommitmentsSectionContent({ trustCenterKey }: SecurityCommitmentsSectionProps) {
|
||||||
|
const data = useFragment(securityCommitmentsSectionFragment, trustCenterKey);
|
||||||
const slots = securityCommitments();
|
const slots = securityCommitments();
|
||||||
|
|
||||||
|
const groups = data.commitmentGroups.edges.map(edge => edge.node);
|
||||||
|
|
||||||
|
if (groups.length === 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<section className={slots.root()}>
|
<section className={slots.root()}>
|
||||||
{SECURITY_COMMITMENT_GROUPS.map(group => (
|
{groups.map((group, index) => (
|
||||||
<div key={group.title} className={slots.group()}>
|
<SecurityCommitmentGroupListItem
|
||||||
<div className={slots.groupHeader()}>
|
key={group.id}
|
||||||
{group.eyebrow != null && (
|
groupKey={group}
|
||||||
<Text size={1} color="gold">
|
showEyebrow={index === 0}
|
||||||
{group.eyebrow}
|
|
||||||
</Text>
|
|
||||||
)}
|
|
||||||
<Text size={2} weight="medium" color="neutral" highContrast>
|
|
||||||
{group.title}
|
|
||||||
</Text>
|
|
||||||
<Text size={2} color="neutral">
|
|
||||||
{group.description}
|
|
||||||
</Text>
|
|
||||||
</div>
|
|
||||||
<div className={slots.grid()}>
|
|
||||||
{group.items.map(item => (
|
|
||||||
<CommitmentCard
|
|
||||||
key={item.title}
|
|
||||||
icon={<item.Icon size={32} weight="light" />}
|
|
||||||
eyebrow={<Text size={1} color="gold">{item.eyebrow}</Text>}
|
|
||||||
title={(
|
|
||||||
<Text size={4} weight="medium" color="neutral" highContrast>
|
|
||||||
{item.title}
|
|
||||||
</Text>
|
|
||||||
)}
|
|
||||||
description={<Text size={2} color="neutral">{item.description}</Text>}
|
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</section>
|
</section>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,78 @@
|
|||||||
|
// 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 { Icon, IconProps } from "@phosphor-icons/react";
|
||||||
|
import {
|
||||||
|
BellIcon,
|
||||||
|
BugIcon,
|
||||||
|
CertificateIcon,
|
||||||
|
CloudIcon,
|
||||||
|
CodeIcon,
|
||||||
|
DatabaseIcon,
|
||||||
|
EyeIcon,
|
||||||
|
EyeSlashIcon,
|
||||||
|
FingerprintIcon,
|
||||||
|
GavelIcon,
|
||||||
|
GlobeIcon,
|
||||||
|
HardDrivesIcon,
|
||||||
|
HeartbeatIcon,
|
||||||
|
KeyIcon,
|
||||||
|
LockIcon,
|
||||||
|
LockKeyIcon,
|
||||||
|
ShieldCheckIcon,
|
||||||
|
ShieldWarningIcon,
|
||||||
|
SirenIcon,
|
||||||
|
UsersIcon,
|
||||||
|
} from "@phosphor-icons/react";
|
||||||
|
import { createElement } from "react";
|
||||||
|
|
||||||
|
// Maps the CompliancePortalCommitmentIcon enum (coredata) to the Phosphor icon
|
||||||
|
// rendered on each commitment card. Keep in sync with the console icon picker
|
||||||
|
// (see apps/console .../commitments/_lib/commitmentIcons.ts).
|
||||||
|
const COMMITMENT_ICONS: Record<string, Icon> = {
|
||||||
|
LOCK_KEY: LockKeyIcon,
|
||||||
|
EYE_SLASH: EyeSlashIcon,
|
||||||
|
FINGERPRINT: FingerprintIcon,
|
||||||
|
SHIELD_WARNING: ShieldWarningIcon,
|
||||||
|
SHIELD_CHECK: ShieldCheckIcon,
|
||||||
|
SIREN: SirenIcon,
|
||||||
|
KEY: KeyIcon,
|
||||||
|
LOCK: LockIcon,
|
||||||
|
CLOUD: CloudIcon,
|
||||||
|
DATABASE: DatabaseIcon,
|
||||||
|
GLOBE: GlobeIcon,
|
||||||
|
EYE: EyeIcon,
|
||||||
|
USERS: UsersIcon,
|
||||||
|
CERTIFICATE: CertificateIcon,
|
||||||
|
GAVEL: GavelIcon,
|
||||||
|
HEARTBEAT: HeartbeatIcon,
|
||||||
|
BELL: BellIcon,
|
||||||
|
BUG: BugIcon,
|
||||||
|
CODE: CodeIcon,
|
||||||
|
SERVER: HardDrivesIcon,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Renders the Phosphor icon mapped from a CompliancePortalCommitmentIcon enum
|
||||||
|
// value. Declared at module scope (and built via createElement) so the icon
|
||||||
|
// component is never created during a parent's render.
|
||||||
|
export function CommitmentIcon({ icon, ...props }: { icon: string } & IconProps) {
|
||||||
|
const resolved: Icon = COMMITMENT_ICONS[icon] ?? LockKeyIcon;
|
||||||
|
return createElement(resolved, props);
|
||||||
|
}
|
||||||
@@ -1,93 +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 { Icon } from "@phosphor-icons/react";
|
|
||||||
import { EyeSlashIcon, FingerprintIcon, LockKeyIcon, ShieldWarningIcon, SirenIcon } from "@phosphor-icons/react";
|
|
||||||
|
|
||||||
export interface SecurityCommitmentItem {
|
|
||||||
eyebrow: string;
|
|
||||||
title: string;
|
|
||||||
description: string;
|
|
||||||
Icon: Icon;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface SecurityCommitmentGroup {
|
|
||||||
// Only the first group carries the section eyebrow in the design.
|
|
||||||
eyebrow?: string;
|
|
||||||
title: string;
|
|
||||||
description: string;
|
|
||||||
items: SecurityCommitmentItem[];
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: This content is placeholder data. There is no backend / DB structure for
|
|
||||||
// security commitments yet — replace this POJO with relay-sourced data (and move
|
|
||||||
// the copy into i18n) once the schema exists.
|
|
||||||
export const SECURITY_COMMITMENT_GROUPS: SecurityCommitmentGroup[] = [
|
|
||||||
{
|
|
||||||
eyebrow: "Security Commitments",
|
|
||||||
title: "Data Protection",
|
|
||||||
description:
|
|
||||||
"Your data is protected with industry-leading encryption and strict privacy controls, from storage to transit to processing.",
|
|
||||||
items: [
|
|
||||||
{
|
|
||||||
eyebrow: "Data Protection",
|
|
||||||
title: "Encrypted at rest. Encrypted in transit. Always.",
|
|
||||||
description:
|
|
||||||
"Customer data is protected with AES-256 at rest and TLS 1.3 in transit. Keys rotated on a fixed schedule.",
|
|
||||||
Icon: LockKeyIcon,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
eyebrow: "Privacy",
|
|
||||||
title: "Customer data stays customer data.",
|
|
||||||
description:
|
|
||||||
"PII is masked in non-production environments. Access requires SSO with hardware-backed keys.",
|
|
||||||
Icon: EyeSlashIcon,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
eyebrow: "Identity",
|
|
||||||
title: "Phishing-resistant authentication, by default.",
|
|
||||||
description:
|
|
||||||
"Every employee authenticates via WebAuthn. Production access is just-in-time and recorded.",
|
|
||||||
Icon: FingerprintIcon,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: "Operational Security",
|
|
||||||
description:
|
|
||||||
"We maintain continuous uptime and rapid incident response so you can rely on our platform around the clock.",
|
|
||||||
items: [
|
|
||||||
{
|
|
||||||
eyebrow: "Threat Detection",
|
|
||||||
title: "Anomalies do not wait. Neither do we.",
|
|
||||||
description:
|
|
||||||
"24/7 SIEM monitoring with automated triage. Critical incidents page on-call within 90 seconds.",
|
|
||||||
Icon: ShieldWarningIcon,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
eyebrow: "Incident Response",
|
|
||||||
title: "Issues are caught fast. And handled.",
|
|
||||||
description:
|
|
||||||
"Security incidents are investigated within hours. Customers notified within 24 hours of a confirmed breach.",
|
|
||||||
Icon: SirenIcon,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
];
|
|
||||||
@@ -39,6 +39,7 @@ export const homePageQuery = graphql`
|
|||||||
...OrganizationContactInfo_organization
|
...OrganizationContactInfo_organization
|
||||||
}
|
}
|
||||||
...ComplianceFrameworksSection_trustCenter
|
...ComplianceFrameworksSection_trustCenter
|
||||||
|
...SecurityCommitmentsSection_trustCenter
|
||||||
...TrustedBySection_trustCenter
|
...TrustedBySection_trustCenter
|
||||||
...RecentUpdatesSection_trustCenter
|
...RecentUpdatesSection_trustCenter
|
||||||
}
|
}
|
||||||
@@ -66,7 +67,7 @@ export function HomePage({ queryRef }: HomePageProps) {
|
|||||||
<div className="flex w-full flex-col items-center px-8">
|
<div className="flex w-full flex-col items-center px-8">
|
||||||
<div className="flex w-full max-w-5xl flex-col">
|
<div className="flex w-full max-w-5xl flex-col">
|
||||||
<ComplianceFrameworksSection trustCenterKey={currentTrustCenter} />
|
<ComplianceFrameworksSection trustCenterKey={currentTrustCenter} />
|
||||||
<SecurityCommitmentsSection />
|
<SecurityCommitmentsSection trustCenterKey={currentTrustCenter} />
|
||||||
<TrustedBySection trustCenterKey={currentTrustCenter} />
|
<TrustedBySection trustCenterKey={currentTrustCenter} />
|
||||||
<RecentUpdatesSection trustCenterKey={currentTrustCenter} />
|
<RecentUpdatesSection trustCenterKey={currentTrustCenter} />
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -20,7 +20,7 @@
|
|||||||
|
|
||||||
import { usePageTitle } from "@probo/hooks";
|
import { usePageTitle } from "@probo/hooks";
|
||||||
import { useTranslate } from "@probo/i18n";
|
import { useTranslate } from "@probo/i18n";
|
||||||
import { Badge, Button, IconBell2, IconCheckmark1, IconFolder2, IconMedal, IconPageTextLine, IconPencil, IconPeopleAdd, IconSettingsGear2, IconStore, PageHeader, TabLink, Tabs } from "@probo/ui";
|
import { Badge, Button, IconBell2, IconCheckmark1, IconFolder2, IconMedal, IconPageTextLine, IconPencil, IconPeopleAdd, IconSettingsGear2, IconShield, IconStore, PageHeader, TabLink, Tabs } from "@probo/ui";
|
||||||
import { type PreloadedQuery, usePreloadedQuery } from "react-relay";
|
import { type PreloadedQuery, usePreloadedQuery } from "react-relay";
|
||||||
import { Outlet } from "react-router";
|
import { Outlet } from "react-router";
|
||||||
import { graphql } from "relay-runtime";
|
import { graphql } from "relay-runtime";
|
||||||
@@ -107,6 +107,10 @@ export function CompliancePageLayout(props: { queryRef: PreloadedQuery<Complianc
|
|||||||
<IconCheckmark1 className="size-4" />
|
<IconCheckmark1 className="size-4" />
|
||||||
{__("References")}
|
{__("References")}
|
||||||
</TabLink>
|
</TabLink>
|
||||||
|
<TabLink to={`/organizations/${organizationId}/compliance-page/commitments`}>
|
||||||
|
<IconShield className="size-4" />
|
||||||
|
{__("Commitments")}
|
||||||
|
</TabLink>
|
||||||
<TabLink to={`/organizations/${organizationId}/compliance-page/audits`}>
|
<TabLink to={`/organizations/${organizationId}/compliance-page/audits`}>
|
||||||
<IconMedal className="size-4" />
|
<IconMedal className="size-4" />
|
||||||
{__("Audits")}
|
{__("Audits")}
|
||||||
|
|||||||
@@ -0,0 +1,60 @@
|
|||||||
|
// 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 { graphql, type PreloadedQuery, usePreloadedQuery } from "react-relay";
|
||||||
|
|
||||||
|
import type { CompliancePageCommitmentsPageQuery } from "#/__generated__/core/CompliancePageCommitmentsPageQuery.graphql";
|
||||||
|
|
||||||
|
import { CompliancePageCommitmentGroupList } from "./_components/CompliancePageCommitmentGroupList";
|
||||||
|
|
||||||
|
export const compliancePageCommitmentsPageQuery = graphql`
|
||||||
|
query CompliancePageCommitmentsPageQuery($organizationId: ID!) {
|
||||||
|
organization: node(id: $organizationId) {
|
||||||
|
__typename
|
||||||
|
... on Organization {
|
||||||
|
compliancePage: trustCenter @required(action: THROW) {
|
||||||
|
id
|
||||||
|
canCreateGroup: permission(action: "core:compliance-portal-commitment-group:create")
|
||||||
|
...CompliancePageCommitmentGroupListFragment
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
export function CompliancePageCommitmentsPage(props: { queryRef: PreloadedQuery<CompliancePageCommitmentsPageQuery> }) {
|
||||||
|
const { queryRef } = props;
|
||||||
|
|
||||||
|
const { organization } = usePreloadedQuery<CompliancePageCommitmentsPageQuery>(
|
||||||
|
compliancePageCommitmentsPageQuery,
|
||||||
|
queryRef,
|
||||||
|
);
|
||||||
|
if (organization.__typename !== "Organization") {
|
||||||
|
throw new Error("invalid type for node");
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<CompliancePageCommitmentGroupList
|
||||||
|
fragmentRef={organization.compliancePage}
|
||||||
|
trustCenterId={organization.compliancePage.id}
|
||||||
|
canCreate={organization.compliancePage.canCreateGroup}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,52 @@
|
|||||||
|
// 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 { useEffect } from "react";
|
||||||
|
import { useQueryLoader } from "react-relay";
|
||||||
|
|
||||||
|
import type { CompliancePageCommitmentsPageQuery } from "#/__generated__/core/CompliancePageCommitmentsPageQuery.graphql";
|
||||||
|
import { LinkCardSkeleton } from "#/components/skeletons/LinkCardSkeleton";
|
||||||
|
import { useOrganizationId } from "#/hooks/useOrganizationId";
|
||||||
|
import { CoreRelayProvider } from "#/providers/CoreRelayProvider";
|
||||||
|
|
||||||
|
import { CompliancePageCommitmentsPage, compliancePageCommitmentsPageQuery } from "./CompliancePageCommitmentsPage";
|
||||||
|
|
||||||
|
function CompliancePageCommitmentsPageQueryLoader() {
|
||||||
|
const organizationId = useOrganizationId();
|
||||||
|
const [queryRef, loadQuery] = useQueryLoader<CompliancePageCommitmentsPageQuery>(compliancePageCommitmentsPageQuery);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!queryRef) {
|
||||||
|
loadQuery({ organizationId });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!queryRef) return <LinkCardSkeleton />;
|
||||||
|
|
||||||
|
return <CompliancePageCommitmentsPage queryRef={queryRef} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function CompliancePageCommitmentsPageLoader() {
|
||||||
|
return (
|
||||||
|
<CoreRelayProvider>
|
||||||
|
<CompliancePageCommitmentsPageQueryLoader />
|
||||||
|
</CoreRelayProvider>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,208 @@
|
|||||||
|
// 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 { useTranslate } from "@probo/i18n";
|
||||||
|
import { Button, Dialog, DialogContent, DialogFooter, Field, Label, Option, Spinner, Textarea, useDialogRef } from "@probo/ui";
|
||||||
|
import { forwardRef, useImperativeHandle, useState } from "react";
|
||||||
|
import { graphql } from "relay-runtime";
|
||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
|
import type { CompliancePageCommitmentDialogCreateMutation, CompliancePortalCommitmentIcon } from "#/__generated__/core/CompliancePageCommitmentDialogCreateMutation.graphql";
|
||||||
|
import type { CompliancePageCommitmentDialogUpdateMutation } from "#/__generated__/core/CompliancePageCommitmentDialogUpdateMutation.graphql";
|
||||||
|
import type { CompliancePageCommitmentListItemFragment$data } from "#/__generated__/core/CompliancePageCommitmentListItemFragment.graphql";
|
||||||
|
import { ControlledSelect } from "#/components/form/ControlledField";
|
||||||
|
import { useFormWithSchema } from "#/hooks/useFormWithSchema";
|
||||||
|
import { useMutationWithToasts } from "#/hooks/useMutationWithToasts";
|
||||||
|
|
||||||
|
import { COMMITMENT_ICON_LABELS, COMMITMENT_ICON_VALUES } from "../_lib/commitmentIcons";
|
||||||
|
|
||||||
|
const createCommitmentMutation = graphql`
|
||||||
|
mutation CompliancePageCommitmentDialogCreateMutation(
|
||||||
|
$input: CreateCompliancePortalCommitmentInput!
|
||||||
|
) {
|
||||||
|
createCompliancePortalCommitment(input: $input) {
|
||||||
|
compliancePortalCommitmentEdge {
|
||||||
|
node {
|
||||||
|
id
|
||||||
|
icon
|
||||||
|
eyebrow
|
||||||
|
title
|
||||||
|
description
|
||||||
|
rank
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
const updateCommitmentMutation = graphql`
|
||||||
|
mutation CompliancePageCommitmentDialogUpdateMutation(
|
||||||
|
$input: UpdateCompliancePortalCommitmentInput!
|
||||||
|
) {
|
||||||
|
updateCompliancePortalCommitment(input: $input) {
|
||||||
|
compliancePortalCommitment {
|
||||||
|
id
|
||||||
|
icon
|
||||||
|
eyebrow
|
||||||
|
title
|
||||||
|
description
|
||||||
|
rank
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
const commitmentSchema = z.object({
|
||||||
|
icon: z.string().min(1, "Icon is required"),
|
||||||
|
eyebrow: z.string(),
|
||||||
|
title: z.string().min(1, "Title is required"),
|
||||||
|
description: z.string().min(1, "Description is required"),
|
||||||
|
});
|
||||||
|
|
||||||
|
type CommitmentFormData = z.infer<typeof commitmentSchema>;
|
||||||
|
|
||||||
|
export type CompliancePageCommitmentDialogRef = {
|
||||||
|
openCreate: (groupId: string) => void;
|
||||||
|
openEdit: (commitment: CompliancePageCommitmentListItemFragment$data) => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const CompliancePageCommitmentDialog = forwardRef<
|
||||||
|
CompliancePageCommitmentDialogRef,
|
||||||
|
{ onChanged: () => void }
|
||||||
|
>(function CompliancePageCommitmentDialog({ onChanged }, ref) {
|
||||||
|
const { __ } = useTranslate();
|
||||||
|
const dialogRef = useDialogRef();
|
||||||
|
const [mode, setMode] = useState<"create" | "edit">("create");
|
||||||
|
const [groupId, setGroupId] = useState<string>("");
|
||||||
|
const [commitmentId, setCommitmentId] = useState<string>("");
|
||||||
|
|
||||||
|
const [createCommitment, isCreating] = useMutationWithToasts<CompliancePageCommitmentDialogCreateMutation>(
|
||||||
|
createCommitmentMutation,
|
||||||
|
{ successMessage: __("Commitment created successfully"), errorMessage: __("Failed to create commitment") },
|
||||||
|
);
|
||||||
|
const [updateCommitment, isUpdating] = useMutationWithToasts<CompliancePageCommitmentDialogUpdateMutation>(
|
||||||
|
updateCommitmentMutation,
|
||||||
|
{ successMessage: __("Commitment updated successfully"), errorMessage: __("Failed to update commitment") },
|
||||||
|
);
|
||||||
|
|
||||||
|
const { register, handleSubmit, control, formState: { errors }, reset } = useFormWithSchema(commitmentSchema, {
|
||||||
|
defaultValues: { icon: COMMITMENT_ICON_VALUES[0], eyebrow: "", title: "", description: "" },
|
||||||
|
});
|
||||||
|
|
||||||
|
useImperativeHandle(ref, () => ({
|
||||||
|
openCreate: (gId: string) => {
|
||||||
|
setMode("create");
|
||||||
|
setGroupId(gId);
|
||||||
|
reset({ icon: COMMITMENT_ICON_VALUES[0], eyebrow: "", title: "", description: "" });
|
||||||
|
dialogRef.current?.open();
|
||||||
|
},
|
||||||
|
openEdit: (commitment) => {
|
||||||
|
setMode("edit");
|
||||||
|
setCommitmentId(commitment.id);
|
||||||
|
reset({
|
||||||
|
icon: commitment.icon,
|
||||||
|
eyebrow: commitment.eyebrow,
|
||||||
|
title: commitment.title,
|
||||||
|
description: commitment.description,
|
||||||
|
});
|
||||||
|
dialogRef.current?.open();
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
|
const onSubmit = async (data: CommitmentFormData) => {
|
||||||
|
const icon = data.icon as CompliancePortalCommitmentIcon;
|
||||||
|
|
||||||
|
if (mode === "create") {
|
||||||
|
await createCommitment({
|
||||||
|
variables: {
|
||||||
|
input: { groupId, icon, eyebrow: data.eyebrow, title: data.title, description: data.description },
|
||||||
|
},
|
||||||
|
onSuccess: () => {
|
||||||
|
reset();
|
||||||
|
dialogRef.current?.close();
|
||||||
|
onChanged();
|
||||||
|
},
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
await updateCommitment({
|
||||||
|
variables: {
|
||||||
|
input: { id: commitmentId, icon, eyebrow: data.eyebrow, title: data.title, description: data.description },
|
||||||
|
},
|
||||||
|
onSuccess: () => {
|
||||||
|
reset();
|
||||||
|
dialogRef.current?.close();
|
||||||
|
onChanged();
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const isSubmitting = isCreating || isUpdating;
|
||||||
|
const title = mode === "create" ? __("Add Commitment") : __("Edit Commitment");
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Dialog ref={dialogRef} title={title} className="max-w-2xl" onClose={() => reset()}>
|
||||||
|
<form onSubmit={e => void handleSubmit(onSubmit)(e)}>
|
||||||
|
<DialogContent padded className="space-y-6">
|
||||||
|
<div className="space-y-1.5">
|
||||||
|
<Label>{__("Icon")}</Label>
|
||||||
|
<ControlledSelect control={control} name="icon" placeholder={__("Select an icon")}>
|
||||||
|
{COMMITMENT_ICON_VALUES.map(value => (
|
||||||
|
<Option key={value} value={value}>
|
||||||
|
{COMMITMENT_ICON_LABELS[value]}
|
||||||
|
</Option>
|
||||||
|
))}
|
||||||
|
</ControlledSelect>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Field
|
||||||
|
{...register("eyebrow")}
|
||||||
|
label={__("Eyebrow")}
|
||||||
|
type="text"
|
||||||
|
error={errors.eyebrow?.message}
|
||||||
|
placeholder={__("Small accent label above the title")}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Field
|
||||||
|
{...register("title")}
|
||||||
|
label={__("Title")}
|
||||||
|
type="text"
|
||||||
|
required
|
||||||
|
error={errors.title?.message}
|
||||||
|
placeholder={__("Commitment headline")}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Field label={__("Description")} error={errors.description?.message} required>
|
||||||
|
<Textarea
|
||||||
|
{...register("description")}
|
||||||
|
placeholder={__("Supporting body copy")}
|
||||||
|
rows={3}
|
||||||
|
/>
|
||||||
|
</Field>
|
||||||
|
</DialogContent>
|
||||||
|
<DialogFooter>
|
||||||
|
<Button type="submit" disabled={isSubmitting} icon={isSubmitting ? Spinner : undefined}>
|
||||||
|
{mode === "create" ? __("Add Commitment") : __("Update Commitment")}
|
||||||
|
</Button>
|
||||||
|
</DialogFooter>
|
||||||
|
</form>
|
||||||
|
</Dialog>
|
||||||
|
);
|
||||||
|
});
|
||||||
@@ -0,0 +1,168 @@
|
|||||||
|
// 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 { useTranslate } from "@probo/i18n";
|
||||||
|
import { Button, Dialog, DialogContent, DialogFooter, Field, Spinner, Textarea, useDialogRef } from "@probo/ui";
|
||||||
|
import { forwardRef, useImperativeHandle, useState } from "react";
|
||||||
|
import { graphql } from "relay-runtime";
|
||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
|
import type { CompliancePageCommitmentGroupDialogCreateMutation } from "#/__generated__/core/CompliancePageCommitmentGroupDialogCreateMutation.graphql";
|
||||||
|
import type { CompliancePageCommitmentGroupDialogUpdateMutation } from "#/__generated__/core/CompliancePageCommitmentGroupDialogUpdateMutation.graphql";
|
||||||
|
import type { CompliancePageCommitmentGroupListItemFragment$data } from "#/__generated__/core/CompliancePageCommitmentGroupListItemFragment.graphql";
|
||||||
|
import { useFormWithSchema } from "#/hooks/useFormWithSchema";
|
||||||
|
import { useMutationWithToasts } from "#/hooks/useMutationWithToasts";
|
||||||
|
|
||||||
|
const createGroupMutation = graphql`
|
||||||
|
mutation CompliancePageCommitmentGroupDialogCreateMutation(
|
||||||
|
$input: CreateCompliancePortalCommitmentGroupInput!
|
||||||
|
) {
|
||||||
|
createCompliancePortalCommitmentGroup(input: $input) {
|
||||||
|
compliancePortalCommitmentGroupEdge {
|
||||||
|
node {
|
||||||
|
id
|
||||||
|
title
|
||||||
|
description
|
||||||
|
rank
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
const updateGroupMutation = graphql`
|
||||||
|
mutation CompliancePageCommitmentGroupDialogUpdateMutation(
|
||||||
|
$input: UpdateCompliancePortalCommitmentGroupInput!
|
||||||
|
) {
|
||||||
|
updateCompliancePortalCommitmentGroup(input: $input) {
|
||||||
|
compliancePortalCommitmentGroup {
|
||||||
|
id
|
||||||
|
title
|
||||||
|
description
|
||||||
|
rank
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
const groupSchema = z.object({
|
||||||
|
title: z.string().min(1, "Title is required"),
|
||||||
|
description: z.string().min(1, "Description is required"),
|
||||||
|
});
|
||||||
|
|
||||||
|
type GroupFormData = z.infer<typeof groupSchema>;
|
||||||
|
|
||||||
|
export type CompliancePageCommitmentGroupDialogRef = {
|
||||||
|
openCreate: (trustCenterId: string) => void;
|
||||||
|
openEdit: (group: CompliancePageCommitmentGroupListItemFragment$data) => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const CompliancePageCommitmentGroupDialog = forwardRef<
|
||||||
|
CompliancePageCommitmentGroupDialogRef,
|
||||||
|
{ onChanged: () => void }
|
||||||
|
>(function CompliancePageCommitmentGroupDialog({ onChanged }, ref) {
|
||||||
|
const { __ } = useTranslate();
|
||||||
|
const dialogRef = useDialogRef();
|
||||||
|
const [mode, setMode] = useState<"create" | "edit">("create");
|
||||||
|
const [trustCenterId, setTrustCenterId] = useState<string>("");
|
||||||
|
const [groupId, setGroupId] = useState<string>("");
|
||||||
|
|
||||||
|
const [createGroup, isCreating] = useMutationWithToasts<CompliancePageCommitmentGroupDialogCreateMutation>(
|
||||||
|
createGroupMutation,
|
||||||
|
{ successMessage: __("Group created successfully"), errorMessage: __("Failed to create group") },
|
||||||
|
);
|
||||||
|
const [updateGroup, isUpdating] = useMutationWithToasts<CompliancePageCommitmentGroupDialogUpdateMutation>(
|
||||||
|
updateGroupMutation,
|
||||||
|
{ successMessage: __("Group updated successfully"), errorMessage: __("Failed to update group") },
|
||||||
|
);
|
||||||
|
|
||||||
|
const { register, handleSubmit, formState: { errors }, reset } = useFormWithSchema(groupSchema, {
|
||||||
|
defaultValues: { title: "", description: "" },
|
||||||
|
});
|
||||||
|
|
||||||
|
useImperativeHandle(ref, () => ({
|
||||||
|
openCreate: (tId: string) => {
|
||||||
|
setMode("create");
|
||||||
|
setTrustCenterId(tId);
|
||||||
|
reset({ title: "", description: "" });
|
||||||
|
dialogRef.current?.open();
|
||||||
|
},
|
||||||
|
openEdit: (group) => {
|
||||||
|
setMode("edit");
|
||||||
|
setGroupId(group.id);
|
||||||
|
reset({ title: group.title, description: group.description });
|
||||||
|
dialogRef.current?.open();
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
|
const onSubmit = async (data: GroupFormData) => {
|
||||||
|
if (mode === "create") {
|
||||||
|
await createGroup({
|
||||||
|
variables: { input: { trustCenterId, title: data.title, description: data.description } },
|
||||||
|
onSuccess: () => {
|
||||||
|
reset();
|
||||||
|
dialogRef.current?.close();
|
||||||
|
onChanged();
|
||||||
|
},
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
await updateGroup({
|
||||||
|
variables: { input: { id: groupId, title: data.title, description: data.description } },
|
||||||
|
onSuccess: () => {
|
||||||
|
reset();
|
||||||
|
dialogRef.current?.close();
|
||||||
|
onChanged();
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const isSubmitting = isCreating || isUpdating;
|
||||||
|
const title = mode === "create" ? __("Add Group") : __("Edit Group");
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Dialog ref={dialogRef} title={title} className="max-w-2xl" onClose={() => reset()}>
|
||||||
|
<form onSubmit={e => void handleSubmit(onSubmit)(e)}>
|
||||||
|
<DialogContent padded className="space-y-6">
|
||||||
|
<Field
|
||||||
|
{...register("title")}
|
||||||
|
label={__("Title")}
|
||||||
|
type="text"
|
||||||
|
required
|
||||||
|
error={errors.title?.message}
|
||||||
|
placeholder={__("e.g. Data Protection")}
|
||||||
|
/>
|
||||||
|
<Field label={__("Description")} error={errors.description?.message} required>
|
||||||
|
<Textarea
|
||||||
|
{...register("description")}
|
||||||
|
placeholder={__("Describe what this group of commitments covers")}
|
||||||
|
rows={3}
|
||||||
|
/>
|
||||||
|
</Field>
|
||||||
|
</DialogContent>
|
||||||
|
<DialogFooter>
|
||||||
|
<Button type="submit" disabled={isSubmitting} icon={isSubmitting ? Spinner : undefined}>
|
||||||
|
{mode === "create" ? __("Add Group") : __("Update Group")}
|
||||||
|
</Button>
|
||||||
|
</DialogFooter>
|
||||||
|
</form>
|
||||||
|
</Dialog>
|
||||||
|
);
|
||||||
|
});
|
||||||
@@ -0,0 +1,104 @@
|
|||||||
|
// 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 { useTranslate } from "@probo/i18n";
|
||||||
|
import { Button, Card, IconPlusLarge } from "@probo/ui";
|
||||||
|
import { useRef } from "react";
|
||||||
|
import { useRefetchableFragment } from "react-relay";
|
||||||
|
import { graphql } from "relay-runtime";
|
||||||
|
|
||||||
|
import type { CompliancePageCommitmentGroupListFragment$key } from "#/__generated__/core/CompliancePageCommitmentGroupListFragment.graphql";
|
||||||
|
import type { CompliancePageCommitmentGroupListRefetchQuery } from "#/__generated__/core/CompliancePageCommitmentGroupListRefetchQuery.graphql";
|
||||||
|
|
||||||
|
import { CompliancePageCommitmentGroupDialog, type CompliancePageCommitmentGroupDialogRef } from "./CompliancePageCommitmentGroupDialog";
|
||||||
|
import { CompliancePageCommitmentGroupListItem } from "./CompliancePageCommitmentGroupListItem";
|
||||||
|
|
||||||
|
const fragment = graphql`
|
||||||
|
fragment CompliancePageCommitmentGroupListFragment on TrustCenter
|
||||||
|
@refetchable(queryName: "CompliancePageCommitmentGroupListRefetchQuery") {
|
||||||
|
commitmentGroups(first: 100, orderBy: { field: RANK, direction: ASC }) {
|
||||||
|
edges {
|
||||||
|
node {
|
||||||
|
id
|
||||||
|
...CompliancePageCommitmentGroupListItemFragment
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
export function CompliancePageCommitmentGroupList(props: {
|
||||||
|
fragmentRef: CompliancePageCommitmentGroupListFragment$key;
|
||||||
|
trustCenterId: string;
|
||||||
|
canCreate: boolean;
|
||||||
|
}) {
|
||||||
|
const { fragmentRef, trustCenterId, canCreate } = props;
|
||||||
|
|
||||||
|
const { __ } = useTranslate();
|
||||||
|
const dialogRef = useRef<CompliancePageCommitmentGroupDialogRef>(null);
|
||||||
|
|
||||||
|
const [data, refetch] = useRefetchableFragment<
|
||||||
|
CompliancePageCommitmentGroupListRefetchQuery,
|
||||||
|
CompliancePageCommitmentGroupListFragment$key
|
||||||
|
>(fragment, fragmentRef);
|
||||||
|
|
||||||
|
const onChanged = () => refetch({}, { fetchPolicy: "network-only" });
|
||||||
|
|
||||||
|
const groups = data.commitmentGroups.edges.map(edge => edge.node);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="space-y-6">
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<div>
|
||||||
|
<h2 className="text-base font-medium">{__("Security Commitments")}</h2>
|
||||||
|
<p className="text-sm text-txt-tertiary">
|
||||||
|
{__("Group commitment cards into sections shown on your compliance page")}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
{canCreate && (
|
||||||
|
<Button icon={IconPlusLarge} onClick={() => dialogRef.current?.openCreate(trustCenterId)}>
|
||||||
|
{__("Add Group")}
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{groups.length === 0
|
||||||
|
? (
|
||||||
|
<Card className="p-6 text-center text-sm text-txt-secondary">
|
||||||
|
{__("No commitment groups yet")}
|
||||||
|
</Card>
|
||||||
|
)
|
||||||
|
: (
|
||||||
|
<div className="space-y-6">
|
||||||
|
{groups.map(group => (
|
||||||
|
<CompliancePageCommitmentGroupListItem
|
||||||
|
key={group.id}
|
||||||
|
fragmentRef={group}
|
||||||
|
onEdit={g => dialogRef.current?.openEdit(g)}
|
||||||
|
onChanged={onChanged}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<CompliancePageCommitmentGroupDialog ref={dialogRef} onChanged={onChanged} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,173 @@
|
|||||||
|
// 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 { sprintf } from "@probo/helpers";
|
||||||
|
import { useTranslate } from "@probo/i18n";
|
||||||
|
import { Button, Card, Dialog, DialogContent, DialogFooter, IconPencil, IconPlusLarge, IconTrashCan, Spinner, Table, Tbody, Td, Th, Thead, Tr, useDialogRef } from "@probo/ui";
|
||||||
|
import { useRef } from "react";
|
||||||
|
import { useFragment } from "react-relay";
|
||||||
|
import { graphql } from "relay-runtime";
|
||||||
|
|
||||||
|
import type { CompliancePageCommitmentGroupListItemDeleteMutation } from "#/__generated__/core/CompliancePageCommitmentGroupListItemDeleteMutation.graphql";
|
||||||
|
import type { CompliancePageCommitmentGroupListItemFragment$data, CompliancePageCommitmentGroupListItemFragment$key } from "#/__generated__/core/CompliancePageCommitmentGroupListItemFragment.graphql";
|
||||||
|
import { useMutationWithToasts } from "#/hooks/useMutationWithToasts";
|
||||||
|
|
||||||
|
import { CompliancePageCommitmentDialog, type CompliancePageCommitmentDialogRef } from "./CompliancePageCommitmentDialog";
|
||||||
|
import { CompliancePageCommitmentListItem } from "./CompliancePageCommitmentListItem";
|
||||||
|
|
||||||
|
const deleteGroupMutation = graphql`
|
||||||
|
mutation CompliancePageCommitmentGroupListItemDeleteMutation(
|
||||||
|
$input: DeleteCompliancePortalCommitmentGroupInput!
|
||||||
|
) {
|
||||||
|
deleteCompliancePortalCommitmentGroup(input: $input) {
|
||||||
|
deletedCompliancePortalCommitmentGroupId
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
const fragment = graphql`
|
||||||
|
fragment CompliancePageCommitmentGroupListItemFragment on CompliancePortalCommitmentGroup {
|
||||||
|
id
|
||||||
|
title
|
||||||
|
description
|
||||||
|
canUpdate: permission(action: "core:compliance-portal-commitment-group:update")
|
||||||
|
canDelete: permission(action: "core:compliance-portal-commitment-group:delete")
|
||||||
|
canCreateCommitment: permission(action: "core:compliance-portal-commitment:create")
|
||||||
|
commitments(first: 100, orderBy: { field: RANK, direction: ASC }) {
|
||||||
|
edges {
|
||||||
|
node {
|
||||||
|
id
|
||||||
|
...CompliancePageCommitmentListItemFragment
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
export function CompliancePageCommitmentGroupListItem(props: {
|
||||||
|
fragmentRef: CompliancePageCommitmentGroupListItemFragment$key;
|
||||||
|
onEdit: (group: CompliancePageCommitmentGroupListItemFragment$data) => void;
|
||||||
|
onChanged: () => void;
|
||||||
|
}) {
|
||||||
|
const { fragmentRef, onEdit, onChanged } = props;
|
||||||
|
|
||||||
|
const { __ } = useTranslate();
|
||||||
|
const group = useFragment<CompliancePageCommitmentGroupListItemFragment$key>(fragment, fragmentRef);
|
||||||
|
const commitmentDialogRef = useRef<CompliancePageCommitmentDialogRef>(null);
|
||||||
|
const deleteDialogRef = useDialogRef();
|
||||||
|
|
||||||
|
const [deleteGroup, isDeleting] = useMutationWithToasts<CompliancePageCommitmentGroupListItemDeleteMutation>(
|
||||||
|
deleteGroupMutation,
|
||||||
|
{ successMessage: __("Group deleted successfully"), errorMessage: __("Failed to delete group") },
|
||||||
|
);
|
||||||
|
|
||||||
|
const commitments = group.commitments.edges.map(edge => edge.node);
|
||||||
|
|
||||||
|
const handleDelete = async () => {
|
||||||
|
await deleteGroup({
|
||||||
|
variables: { input: { id: group.id } },
|
||||||
|
onSuccess: () => {
|
||||||
|
deleteDialogRef.current?.close();
|
||||||
|
onChanged();
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Card className="space-y-4 p-4">
|
||||||
|
<div className="flex items-start justify-between gap-4">
|
||||||
|
<div>
|
||||||
|
<h3 className="text-base font-medium">{group.title}</h3>
|
||||||
|
<p className="text-sm text-txt-tertiary">{group.description}</p>
|
||||||
|
</div>
|
||||||
|
<div className="flex gap-2">
|
||||||
|
{group.canUpdate && (
|
||||||
|
<Button variant="secondary" icon={IconPencil} onClick={() => onEdit(group)} />
|
||||||
|
)}
|
||||||
|
{group.canDelete && (
|
||||||
|
<>
|
||||||
|
<Button variant="danger" icon={IconTrashCan} onClick={() => deleteDialogRef.current?.open()} />
|
||||||
|
<Dialog ref={deleteDialogRef} title={__("Delete Group")} className="max-w-md">
|
||||||
|
<DialogContent padded>
|
||||||
|
<p className="text-txt-secondary">
|
||||||
|
{sprintf(__("Are you sure you want to delete the group \"%s\"?"), group.title)}
|
||||||
|
</p>
|
||||||
|
<p className="text-txt-secondary mt-2">
|
||||||
|
{__("All commitment cards in this group will also be deleted. This action cannot be undone.")}
|
||||||
|
</p>
|
||||||
|
</DialogContent>
|
||||||
|
<DialogFooter>
|
||||||
|
<Button
|
||||||
|
variant="danger"
|
||||||
|
onClick={() => void handleDelete()}
|
||||||
|
disabled={isDeleting}
|
||||||
|
icon={isDeleting ? Spinner : IconTrashCan}
|
||||||
|
>
|
||||||
|
{isDeleting ? __("Deleting...") : __("Delete")}
|
||||||
|
</Button>
|
||||||
|
</DialogFooter>
|
||||||
|
</Dialog>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Table>
|
||||||
|
<Thead>
|
||||||
|
<Tr>
|
||||||
|
<Th>{__("Icon")}</Th>
|
||||||
|
<Th>{__("Title")}</Th>
|
||||||
|
<Th>{__("Description")}</Th>
|
||||||
|
<Th></Th>
|
||||||
|
</Tr>
|
||||||
|
</Thead>
|
||||||
|
<Tbody>
|
||||||
|
{commitments.length === 0 && (
|
||||||
|
<Tr>
|
||||||
|
<Td colSpan={4} className="text-center text-txt-secondary">
|
||||||
|
{__("No commitment cards yet")}
|
||||||
|
</Td>
|
||||||
|
</Tr>
|
||||||
|
)}
|
||||||
|
{commitments.map(commitment => (
|
||||||
|
<CompliancePageCommitmentListItem
|
||||||
|
key={commitment.id}
|
||||||
|
fragmentRef={commitment}
|
||||||
|
onEdit={c => commitmentDialogRef.current?.openEdit(c)}
|
||||||
|
onChanged={onChanged}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</Tbody>
|
||||||
|
</Table>
|
||||||
|
|
||||||
|
{group.canCreateCommitment && (
|
||||||
|
<Button
|
||||||
|
variant="secondary"
|
||||||
|
icon={IconPlusLarge}
|
||||||
|
onClick={() => commitmentDialogRef.current?.openCreate(group.id)}
|
||||||
|
>
|
||||||
|
{__("Add Commitment")}
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<CompliancePageCommitmentDialog ref={commitmentDialogRef} onChanged={onChanged} />
|
||||||
|
</Card>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,111 @@
|
|||||||
|
// 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 { useTranslate } from "@probo/i18n";
|
||||||
|
import { Badge, Button, IconPencil, IconTrashCan, Spinner, Td, Tr } from "@probo/ui";
|
||||||
|
import { useFragment } from "react-relay";
|
||||||
|
import { graphql } from "relay-runtime";
|
||||||
|
|
||||||
|
import type { CompliancePageCommitmentListItemDeleteMutation } from "#/__generated__/core/CompliancePageCommitmentListItemDeleteMutation.graphql";
|
||||||
|
import type { CompliancePageCommitmentListItemFragment$data, CompliancePageCommitmentListItemFragment$key } from "#/__generated__/core/CompliancePageCommitmentListItemFragment.graphql";
|
||||||
|
import { useMutationWithToasts } from "#/hooks/useMutationWithToasts";
|
||||||
|
|
||||||
|
import { COMMITMENT_ICON_LABELS } from "../_lib/commitmentIcons";
|
||||||
|
|
||||||
|
const deleteCommitmentMutation = graphql`
|
||||||
|
mutation CompliancePageCommitmentListItemDeleteMutation(
|
||||||
|
$input: DeleteCompliancePortalCommitmentInput!
|
||||||
|
) {
|
||||||
|
deleteCompliancePortalCommitment(input: $input) {
|
||||||
|
deletedCompliancePortalCommitmentId
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
const fragment = graphql`
|
||||||
|
fragment CompliancePageCommitmentListItemFragment on CompliancePortalCommitment {
|
||||||
|
id
|
||||||
|
icon
|
||||||
|
eyebrow
|
||||||
|
title
|
||||||
|
description
|
||||||
|
canUpdate: permission(action: "core:compliance-portal-commitment:update")
|
||||||
|
canDelete: permission(action: "core:compliance-portal-commitment:delete")
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
export function CompliancePageCommitmentListItem(props: {
|
||||||
|
fragmentRef: CompliancePageCommitmentListItemFragment$key;
|
||||||
|
onEdit: (commitment: CompliancePageCommitmentListItemFragment$data) => void;
|
||||||
|
onChanged: () => void;
|
||||||
|
}) {
|
||||||
|
const { fragmentRef, onEdit, onChanged } = props;
|
||||||
|
|
||||||
|
const { __ } = useTranslate();
|
||||||
|
const commitment = useFragment<CompliancePageCommitmentListItemFragment$key>(fragment, fragmentRef);
|
||||||
|
|
||||||
|
const [deleteCommitment, isDeleting] = useMutationWithToasts<CompliancePageCommitmentListItemDeleteMutation>(
|
||||||
|
deleteCommitmentMutation,
|
||||||
|
{ successMessage: __("Commitment deleted successfully"), errorMessage: __("Failed to delete commitment") },
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleDelete = async () => {
|
||||||
|
await deleteCommitment({
|
||||||
|
variables: { input: { id: commitment.id } },
|
||||||
|
onSuccess: onChanged,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const iconLabel = COMMITMENT_ICON_LABELS[commitment.icon];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Tr>
|
||||||
|
<Td>
|
||||||
|
<Badge variant="neutral">{iconLabel}</Badge>
|
||||||
|
</Td>
|
||||||
|
<Td>
|
||||||
|
<div className="flex flex-col">
|
||||||
|
{commitment.eyebrow && (
|
||||||
|
<span className="text-xs text-txt-tertiary">{commitment.eyebrow}</span>
|
||||||
|
)}
|
||||||
|
<span className="font-medium">{commitment.title}</span>
|
||||||
|
</div>
|
||||||
|
</Td>
|
||||||
|
<Td>
|
||||||
|
<span className="text-txt-secondary line-clamp-2">{commitment.description}</span>
|
||||||
|
</Td>
|
||||||
|
<Td noLink width={120} className="text-end">
|
||||||
|
<div className="flex gap-2 justify-end">
|
||||||
|
{commitment.canUpdate && (
|
||||||
|
<Button variant="secondary" icon={IconPencil} onClick={() => onEdit(commitment)} />
|
||||||
|
)}
|
||||||
|
{commitment.canDelete && (
|
||||||
|
<Button
|
||||||
|
variant="danger"
|
||||||
|
icon={isDeleting ? Spinner : IconTrashCan}
|
||||||
|
disabled={isDeleting}
|
||||||
|
onClick={() => void handleDelete()}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</Td>
|
||||||
|
</Tr>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,70 @@
|
|||||||
|
// 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 { CompliancePortalCommitmentIcon } from "#/__generated__/core/CompliancePageCommitmentDialogCreateMutation.graphql";
|
||||||
|
|
||||||
|
// The curated icon set mirrors coredata.CompliancePortalCommitmentIcon and the
|
||||||
|
// Phosphor icons the compliance portal renders for each value.
|
||||||
|
export const COMMITMENT_ICON_VALUES: CompliancePortalCommitmentIcon[] = [
|
||||||
|
"LOCK_KEY",
|
||||||
|
"EYE_SLASH",
|
||||||
|
"FINGERPRINT",
|
||||||
|
"SHIELD_WARNING",
|
||||||
|
"SHIELD_CHECK",
|
||||||
|
"SIREN",
|
||||||
|
"KEY",
|
||||||
|
"LOCK",
|
||||||
|
"CLOUD",
|
||||||
|
"DATABASE",
|
||||||
|
"GLOBE",
|
||||||
|
"EYE",
|
||||||
|
"USERS",
|
||||||
|
"CERTIFICATE",
|
||||||
|
"GAVEL",
|
||||||
|
"HEARTBEAT",
|
||||||
|
"BELL",
|
||||||
|
"BUG",
|
||||||
|
"CODE",
|
||||||
|
"SERVER",
|
||||||
|
];
|
||||||
|
|
||||||
|
// Human-readable label for each icon value, shown in the console picker.
|
||||||
|
export const COMMITMENT_ICON_LABELS: Record<CompliancePortalCommitmentIcon, string> = {
|
||||||
|
LOCK_KEY: "Lock & key",
|
||||||
|
EYE_SLASH: "Eye slash",
|
||||||
|
FINGERPRINT: "Fingerprint",
|
||||||
|
SHIELD_WARNING: "Shield warning",
|
||||||
|
SHIELD_CHECK: "Shield check",
|
||||||
|
SIREN: "Siren",
|
||||||
|
KEY: "Key",
|
||||||
|
LOCK: "Lock",
|
||||||
|
CLOUD: "Cloud",
|
||||||
|
DATABASE: "Database",
|
||||||
|
GLOBE: "Globe",
|
||||||
|
EYE: "Eye",
|
||||||
|
USERS: "Users",
|
||||||
|
CERTIFICATE: "Certificate",
|
||||||
|
GAVEL: "Gavel",
|
||||||
|
HEARTBEAT: "Heartbeat",
|
||||||
|
BELL: "Bell",
|
||||||
|
BUG: "Bug",
|
||||||
|
CODE: "Code",
|
||||||
|
SERVER: "Server",
|
||||||
|
};
|
||||||
@@ -52,6 +52,11 @@ export const compliancePageRoutes = [
|
|||||||
Fallback: LinkCardSkeleton,
|
Fallback: LinkCardSkeleton,
|
||||||
Component: lazy(() => import("#/pages/organizations/compliance-page/references/CompliancePageReferencesPageLoader")),
|
Component: lazy(() => import("#/pages/organizations/compliance-page/references/CompliancePageReferencesPageLoader")),
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: "commitments",
|
||||||
|
Fallback: LinkCardSkeleton,
|
||||||
|
Component: lazy(() => import("#/pages/organizations/compliance-page/commitments/CompliancePageCommitmentsPageLoader")),
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: "audits",
|
path: "audits",
|
||||||
Fallback: LinkCardSkeleton,
|
Fallback: LinkCardSkeleton,
|
||||||
|
|||||||
416
pkg/coredata/compliance_portal_commitment.go
Normal file
416
pkg/coredata/compliance_portal_commitment.go
Normal file
@@ -0,0 +1,416 @@
|
|||||||
|
// 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.
|
||||||
|
|
||||||
|
package coredata
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"maps"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/jackc/pgx/v5"
|
||||||
|
"github.com/jackc/pgx/v5/pgconn"
|
||||||
|
"go.gearno.de/kit/pg"
|
||||||
|
"go.probo.inc/probo/pkg/gid"
|
||||||
|
"go.probo.inc/probo/pkg/iam/policy"
|
||||||
|
"go.probo.inc/probo/pkg/page"
|
||||||
|
)
|
||||||
|
|
||||||
|
type (
|
||||||
|
CompliancePortalCommitment struct {
|
||||||
|
ID gid.GID `db:"id"`
|
||||||
|
OrganizationID gid.GID `db:"organization_id"`
|
||||||
|
TrustCenterID gid.GID `db:"trust_center_id"`
|
||||||
|
GroupID gid.GID `db:"group_id"`
|
||||||
|
Icon CompliancePortalCommitmentIcon `db:"icon"`
|
||||||
|
Eyebrow string `db:"eyebrow"`
|
||||||
|
Title string `db:"title"`
|
||||||
|
Description string `db:"description"`
|
||||||
|
Rank int `db:"rank"`
|
||||||
|
CreatedAt time.Time `db:"created_at"`
|
||||||
|
UpdatedAt time.Time `db:"updated_at"`
|
||||||
|
}
|
||||||
|
|
||||||
|
CompliancePortalCommitments []*CompliancePortalCommitment
|
||||||
|
)
|
||||||
|
|
||||||
|
func (t CompliancePortalCommitment) CursorKey(orderBy CompliancePortalCommitmentOrderField) page.CursorKey {
|
||||||
|
switch orderBy {
|
||||||
|
case CompliancePortalCommitmentOrderFieldRank:
|
||||||
|
return page.NewCursorKey(t.ID, t.Rank)
|
||||||
|
case CompliancePortalCommitmentOrderFieldCreatedAt:
|
||||||
|
return page.NewCursorKey(t.ID, t.CreatedAt)
|
||||||
|
case CompliancePortalCommitmentOrderFieldUpdatedAt:
|
||||||
|
return page.NewCursorKey(t.ID, t.UpdatedAt)
|
||||||
|
}
|
||||||
|
|
||||||
|
panic(fmt.Sprintf("unsupported order by: %s", orderBy))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *CompliancePortalCommitment) AuthorizationAttributes(
|
||||||
|
ctx context.Context,
|
||||||
|
conn pg.Querier,
|
||||||
|
resourceIDs []gid.GID,
|
||||||
|
) (policy.AttributesByID, error) {
|
||||||
|
q := `SELECT id, organization_id FROM compliance_portal_commitments WHERE id = ANY(@resource_ids::text[])`
|
||||||
|
|
||||||
|
args := pgx.StrictNamedArgs{
|
||||||
|
"resource_ids": resourceIDs,
|
||||||
|
}
|
||||||
|
|
||||||
|
rows, err := conn.Query(ctx, q, args)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("cannot query authorization attributes: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
defer rows.Close()
|
||||||
|
|
||||||
|
attrsByID := make(policy.AttributesByID)
|
||||||
|
|
||||||
|
for rows.Next() {
|
||||||
|
var id, organizationID gid.GID
|
||||||
|
|
||||||
|
if err := rows.Scan(&id, &organizationID); err != nil {
|
||||||
|
return nil, fmt.Errorf("cannot scan authorization attributes: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
attrsByID[id] = policy.Attributes{
|
||||||
|
"organization_id": organizationID.String(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := rows.Err(); err != nil {
|
||||||
|
return nil, fmt.Errorf("cannot iterate authorization attributes: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return attrsByID, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *CompliancePortalCommitment) LoadByID(
|
||||||
|
ctx context.Context,
|
||||||
|
conn pg.Querier,
|
||||||
|
scope Scoper,
|
||||||
|
commitmentID gid.GID,
|
||||||
|
) error {
|
||||||
|
q := `
|
||||||
|
SELECT
|
||||||
|
id,
|
||||||
|
organization_id,
|
||||||
|
trust_center_id,
|
||||||
|
group_id,
|
||||||
|
icon,
|
||||||
|
eyebrow,
|
||||||
|
title,
|
||||||
|
description,
|
||||||
|
rank,
|
||||||
|
created_at,
|
||||||
|
updated_at
|
||||||
|
FROM
|
||||||
|
compliance_portal_commitments
|
||||||
|
WHERE
|
||||||
|
%s
|
||||||
|
AND id = @commitment_id
|
||||||
|
LIMIT 1;
|
||||||
|
`
|
||||||
|
q = fmt.Sprintf(q, scope.SQLFragment())
|
||||||
|
|
||||||
|
args := pgx.StrictNamedArgs{"commitment_id": commitmentID}
|
||||||
|
maps.Copy(args, scope.SQLArguments())
|
||||||
|
|
||||||
|
rows, err := conn.Query(ctx, q, args)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("cannot query compliance_portal_commitments: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
commitment, err := pgx.CollectExactlyOneRow(rows, pgx.RowToStructByName[CompliancePortalCommitment])
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("cannot collect compliance portal commitment: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
*t = commitment
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *CompliancePortalCommitment) Insert(
|
||||||
|
ctx context.Context,
|
||||||
|
conn pg.Tx,
|
||||||
|
scope Scoper,
|
||||||
|
) error {
|
||||||
|
q := `
|
||||||
|
INSERT INTO
|
||||||
|
compliance_portal_commitments (
|
||||||
|
tenant_id,
|
||||||
|
id,
|
||||||
|
organization_id,
|
||||||
|
trust_center_id,
|
||||||
|
group_id,
|
||||||
|
icon,
|
||||||
|
eyebrow,
|
||||||
|
title,
|
||||||
|
description,
|
||||||
|
rank,
|
||||||
|
created_at,
|
||||||
|
updated_at
|
||||||
|
)
|
||||||
|
VALUES (
|
||||||
|
@tenant_id,
|
||||||
|
@id,
|
||||||
|
@organization_id,
|
||||||
|
@trust_center_id,
|
||||||
|
@group_id,
|
||||||
|
@icon,
|
||||||
|
@eyebrow,
|
||||||
|
@title,
|
||||||
|
@description,
|
||||||
|
(SELECT COALESCE(MAX(rank), 0) + 1 FROM compliance_portal_commitments WHERE group_id = @group_id),
|
||||||
|
@created_at,
|
||||||
|
@updated_at
|
||||||
|
)
|
||||||
|
RETURNING rank;
|
||||||
|
`
|
||||||
|
|
||||||
|
args := pgx.StrictNamedArgs{
|
||||||
|
"tenant_id": scope.GetTenantID(),
|
||||||
|
"id": t.ID,
|
||||||
|
"organization_id": t.OrganizationID,
|
||||||
|
"trust_center_id": t.TrustCenterID,
|
||||||
|
"group_id": t.GroupID,
|
||||||
|
"icon": t.Icon,
|
||||||
|
"eyebrow": t.Eyebrow,
|
||||||
|
"title": t.Title,
|
||||||
|
"description": t.Description,
|
||||||
|
"created_at": t.CreatedAt,
|
||||||
|
"updated_at": t.UpdatedAt,
|
||||||
|
}
|
||||||
|
|
||||||
|
err := conn.QueryRow(ctx, q, args).Scan(&t.Rank)
|
||||||
|
if err != nil {
|
||||||
|
if pgErr, ok := errors.AsType[*pgconn.PgError](err); ok {
|
||||||
|
if pgErr.Code == "23505" && pgErr.ConstraintName == "compliance_portal_commitments_group_id_rank_key" {
|
||||||
|
return ErrResourceAlreadyExists
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return fmt.Errorf("cannot insert compliance portal commitment: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *CompliancePortalCommitment) Update(
|
||||||
|
ctx context.Context,
|
||||||
|
conn pg.Tx,
|
||||||
|
scope Scoper,
|
||||||
|
) error {
|
||||||
|
q := `
|
||||||
|
UPDATE compliance_portal_commitments
|
||||||
|
SET
|
||||||
|
icon = @icon,
|
||||||
|
eyebrow = @eyebrow,
|
||||||
|
title = @title,
|
||||||
|
description = @description,
|
||||||
|
updated_at = @updated_at
|
||||||
|
WHERE
|
||||||
|
%s
|
||||||
|
AND id = @id;
|
||||||
|
`
|
||||||
|
|
||||||
|
q = fmt.Sprintf(q, scope.SQLFragment())
|
||||||
|
|
||||||
|
args := pgx.StrictNamedArgs{
|
||||||
|
"id": t.ID,
|
||||||
|
"icon": t.Icon,
|
||||||
|
"eyebrow": t.Eyebrow,
|
||||||
|
"title": t.Title,
|
||||||
|
"description": t.Description,
|
||||||
|
"updated_at": t.UpdatedAt,
|
||||||
|
}
|
||||||
|
maps.Copy(args, scope.SQLArguments())
|
||||||
|
|
||||||
|
result, err := conn.Exec(ctx, q, args)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("cannot update compliance portal commitment: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if result.RowsAffected() == 0 {
|
||||||
|
return ErrResourceNotFound
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *CompliancePortalCommitment) UpdateRank(
|
||||||
|
ctx context.Context,
|
||||||
|
conn pg.Tx,
|
||||||
|
scope Scoper,
|
||||||
|
) error {
|
||||||
|
q := `
|
||||||
|
WITH old AS (
|
||||||
|
SELECT
|
||||||
|
rank AS old_rank
|
||||||
|
FROM compliance_portal_commitments
|
||||||
|
WHERE %s AND id = @id AND group_id = @group_id
|
||||||
|
)
|
||||||
|
UPDATE compliance_portal_commitments
|
||||||
|
SET
|
||||||
|
rank = CASE
|
||||||
|
WHEN id = @id THEN @new_rank
|
||||||
|
ELSE rank + CASE
|
||||||
|
WHEN @new_rank < old.old_rank THEN 1
|
||||||
|
WHEN @new_rank > old.old_rank THEN -1
|
||||||
|
END
|
||||||
|
END,
|
||||||
|
updated_at = @updated_at
|
||||||
|
FROM old
|
||||||
|
WHERE %s
|
||||||
|
AND group_id = @group_id
|
||||||
|
AND (
|
||||||
|
id = @id
|
||||||
|
OR (rank BETWEEN LEAST(old.old_rank, @new_rank) AND GREATEST(old.old_rank, @new_rank))
|
||||||
|
);
|
||||||
|
`
|
||||||
|
|
||||||
|
scopeFragment := scope.SQLFragment()
|
||||||
|
q = fmt.Sprintf(q, scopeFragment, scopeFragment)
|
||||||
|
|
||||||
|
args := pgx.StrictNamedArgs{
|
||||||
|
"id": t.ID,
|
||||||
|
"new_rank": t.Rank,
|
||||||
|
"group_id": t.GroupID,
|
||||||
|
"updated_at": t.UpdatedAt,
|
||||||
|
}
|
||||||
|
maps.Copy(args, scope.SQLArguments())
|
||||||
|
|
||||||
|
_, err := conn.Exec(ctx, q, args)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("cannot update compliance portal commitment rank: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *CompliancePortalCommitment) Delete(
|
||||||
|
ctx context.Context,
|
||||||
|
conn pg.Tx,
|
||||||
|
scope Scoper,
|
||||||
|
) error {
|
||||||
|
q := `
|
||||||
|
DELETE FROM
|
||||||
|
compliance_portal_commitments
|
||||||
|
WHERE
|
||||||
|
%s
|
||||||
|
AND id = @id
|
||||||
|
`
|
||||||
|
|
||||||
|
q = fmt.Sprintf(q, scope.SQLFragment())
|
||||||
|
|
||||||
|
args := pgx.StrictNamedArgs{"id": t.ID}
|
||||||
|
maps.Copy(args, scope.SQLArguments())
|
||||||
|
|
||||||
|
_, err := conn.Exec(ctx, q, args)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("cannot delete compliance portal commitment: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *CompliancePortalCommitments) LoadByGroupID(
|
||||||
|
ctx context.Context,
|
||||||
|
conn pg.Querier,
|
||||||
|
scope Scoper,
|
||||||
|
groupID gid.GID,
|
||||||
|
cursor *page.Cursor[CompliancePortalCommitmentOrderField],
|
||||||
|
) error {
|
||||||
|
q := `
|
||||||
|
SELECT
|
||||||
|
id,
|
||||||
|
organization_id,
|
||||||
|
trust_center_id,
|
||||||
|
group_id,
|
||||||
|
icon,
|
||||||
|
eyebrow,
|
||||||
|
title,
|
||||||
|
description,
|
||||||
|
rank,
|
||||||
|
created_at,
|
||||||
|
updated_at
|
||||||
|
FROM
|
||||||
|
compliance_portal_commitments
|
||||||
|
WHERE
|
||||||
|
%s
|
||||||
|
AND group_id = @group_id
|
||||||
|
AND %s
|
||||||
|
`
|
||||||
|
|
||||||
|
q = fmt.Sprintf(q, scope.SQLFragment(), cursor.SQLFragment())
|
||||||
|
|
||||||
|
args := pgx.StrictNamedArgs{"group_id": groupID}
|
||||||
|
maps.Copy(args, scope.SQLArguments())
|
||||||
|
maps.Copy(args, cursor.SQLArguments())
|
||||||
|
|
||||||
|
rows, err := conn.Query(ctx, q, args)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("cannot query compliance_portal_commitments: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
commitments, err := pgx.CollectRows(rows, pgx.RowToAddrOfStructByName[CompliancePortalCommitment])
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("cannot collect compliance portal commitments: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
*t = commitments
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *CompliancePortalCommitments) CountByGroupID(
|
||||||
|
ctx context.Context,
|
||||||
|
conn pg.Querier,
|
||||||
|
scope Scoper,
|
||||||
|
groupID gid.GID,
|
||||||
|
) (int, error) {
|
||||||
|
q := `
|
||||||
|
SELECT
|
||||||
|
COUNT(*)
|
||||||
|
FROM
|
||||||
|
compliance_portal_commitments
|
||||||
|
WHERE
|
||||||
|
%s
|
||||||
|
AND group_id = @group_id
|
||||||
|
`
|
||||||
|
|
||||||
|
q = fmt.Sprintf(q, scope.SQLFragment())
|
||||||
|
|
||||||
|
args := pgx.StrictNamedArgs{"group_id": groupID}
|
||||||
|
maps.Copy(args, scope.SQLArguments())
|
||||||
|
|
||||||
|
var count int
|
||||||
|
|
||||||
|
err := conn.QueryRow(ctx, q, args).Scan(&count)
|
||||||
|
if err != nil {
|
||||||
|
return 0, fmt.Errorf("cannot count compliance portal commitments: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return count, nil
|
||||||
|
}
|
||||||
393
pkg/coredata/compliance_portal_commitment_group.go
Normal file
393
pkg/coredata/compliance_portal_commitment_group.go
Normal file
@@ -0,0 +1,393 @@
|
|||||||
|
// 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.
|
||||||
|
|
||||||
|
package coredata
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"maps"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/jackc/pgx/v5"
|
||||||
|
"github.com/jackc/pgx/v5/pgconn"
|
||||||
|
"go.gearno.de/kit/pg"
|
||||||
|
"go.probo.inc/probo/pkg/gid"
|
||||||
|
"go.probo.inc/probo/pkg/iam/policy"
|
||||||
|
"go.probo.inc/probo/pkg/page"
|
||||||
|
)
|
||||||
|
|
||||||
|
type (
|
||||||
|
CompliancePortalCommitmentGroup struct {
|
||||||
|
ID gid.GID `db:"id"`
|
||||||
|
OrganizationID gid.GID `db:"organization_id"`
|
||||||
|
TrustCenterID gid.GID `db:"trust_center_id"`
|
||||||
|
Title string `db:"title"`
|
||||||
|
Description string `db:"description"`
|
||||||
|
Rank int `db:"rank"`
|
||||||
|
CreatedAt time.Time `db:"created_at"`
|
||||||
|
UpdatedAt time.Time `db:"updated_at"`
|
||||||
|
}
|
||||||
|
|
||||||
|
CompliancePortalCommitmentGroups []*CompliancePortalCommitmentGroup
|
||||||
|
)
|
||||||
|
|
||||||
|
func (t CompliancePortalCommitmentGroup) CursorKey(orderBy CompliancePortalCommitmentGroupOrderField) page.CursorKey {
|
||||||
|
switch orderBy {
|
||||||
|
case CompliancePortalCommitmentGroupOrderFieldRank:
|
||||||
|
return page.NewCursorKey(t.ID, t.Rank)
|
||||||
|
case CompliancePortalCommitmentGroupOrderFieldCreatedAt:
|
||||||
|
return page.NewCursorKey(t.ID, t.CreatedAt)
|
||||||
|
case CompliancePortalCommitmentGroupOrderFieldUpdatedAt:
|
||||||
|
return page.NewCursorKey(t.ID, t.UpdatedAt)
|
||||||
|
}
|
||||||
|
|
||||||
|
panic(fmt.Sprintf("unsupported order by: %s", orderBy))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *CompliancePortalCommitmentGroup) AuthorizationAttributes(
|
||||||
|
ctx context.Context,
|
||||||
|
conn pg.Querier,
|
||||||
|
resourceIDs []gid.GID,
|
||||||
|
) (policy.AttributesByID, error) {
|
||||||
|
q := `SELECT id, organization_id FROM compliance_portal_commitment_groups WHERE id = ANY(@resource_ids::text[])`
|
||||||
|
|
||||||
|
args := pgx.StrictNamedArgs{
|
||||||
|
"resource_ids": resourceIDs,
|
||||||
|
}
|
||||||
|
|
||||||
|
rows, err := conn.Query(ctx, q, args)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("cannot query authorization attributes: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
defer rows.Close()
|
||||||
|
|
||||||
|
attrsByID := make(policy.AttributesByID)
|
||||||
|
|
||||||
|
for rows.Next() {
|
||||||
|
var id, organizationID gid.GID
|
||||||
|
|
||||||
|
if err := rows.Scan(&id, &organizationID); err != nil {
|
||||||
|
return nil, fmt.Errorf("cannot scan authorization attributes: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
attrsByID[id] = policy.Attributes{
|
||||||
|
"organization_id": organizationID.String(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := rows.Err(); err != nil {
|
||||||
|
return nil, fmt.Errorf("cannot iterate authorization attributes: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return attrsByID, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *CompliancePortalCommitmentGroup) LoadByID(
|
||||||
|
ctx context.Context,
|
||||||
|
conn pg.Querier,
|
||||||
|
scope Scoper,
|
||||||
|
groupID gid.GID,
|
||||||
|
) error {
|
||||||
|
q := `
|
||||||
|
SELECT
|
||||||
|
id,
|
||||||
|
organization_id,
|
||||||
|
trust_center_id,
|
||||||
|
title,
|
||||||
|
description,
|
||||||
|
rank,
|
||||||
|
created_at,
|
||||||
|
updated_at
|
||||||
|
FROM
|
||||||
|
compliance_portal_commitment_groups
|
||||||
|
WHERE
|
||||||
|
%s
|
||||||
|
AND id = @group_id
|
||||||
|
LIMIT 1;
|
||||||
|
`
|
||||||
|
q = fmt.Sprintf(q, scope.SQLFragment())
|
||||||
|
|
||||||
|
args := pgx.StrictNamedArgs{"group_id": groupID}
|
||||||
|
maps.Copy(args, scope.SQLArguments())
|
||||||
|
|
||||||
|
rows, err := conn.Query(ctx, q, args)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("cannot query compliance_portal_commitment_groups: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
group, err := pgx.CollectExactlyOneRow(rows, pgx.RowToStructByName[CompliancePortalCommitmentGroup])
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("cannot collect compliance portal commitment group: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
*t = group
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *CompliancePortalCommitmentGroup) Insert(
|
||||||
|
ctx context.Context,
|
||||||
|
conn pg.Tx,
|
||||||
|
scope Scoper,
|
||||||
|
) error {
|
||||||
|
q := `
|
||||||
|
INSERT INTO
|
||||||
|
compliance_portal_commitment_groups (
|
||||||
|
tenant_id,
|
||||||
|
id,
|
||||||
|
organization_id,
|
||||||
|
trust_center_id,
|
||||||
|
title,
|
||||||
|
description,
|
||||||
|
rank,
|
||||||
|
created_at,
|
||||||
|
updated_at
|
||||||
|
)
|
||||||
|
VALUES (
|
||||||
|
@tenant_id,
|
||||||
|
@id,
|
||||||
|
@organization_id,
|
||||||
|
@trust_center_id,
|
||||||
|
@title,
|
||||||
|
@description,
|
||||||
|
(SELECT COALESCE(MAX(rank), 0) + 1 FROM compliance_portal_commitment_groups WHERE trust_center_id = @trust_center_id),
|
||||||
|
@created_at,
|
||||||
|
@updated_at
|
||||||
|
)
|
||||||
|
RETURNING rank;
|
||||||
|
`
|
||||||
|
|
||||||
|
args := pgx.StrictNamedArgs{
|
||||||
|
"tenant_id": scope.GetTenantID(),
|
||||||
|
"id": t.ID,
|
||||||
|
"organization_id": t.OrganizationID,
|
||||||
|
"trust_center_id": t.TrustCenterID,
|
||||||
|
"title": t.Title,
|
||||||
|
"description": t.Description,
|
||||||
|
"created_at": t.CreatedAt,
|
||||||
|
"updated_at": t.UpdatedAt,
|
||||||
|
}
|
||||||
|
|
||||||
|
err := conn.QueryRow(ctx, q, args).Scan(&t.Rank)
|
||||||
|
if err != nil {
|
||||||
|
if pgErr, ok := errors.AsType[*pgconn.PgError](err); ok {
|
||||||
|
if pgErr.Code == "23505" && pgErr.ConstraintName == "compliance_portal_commitment_groups_trust_center_id_rank_key" {
|
||||||
|
return ErrResourceAlreadyExists
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return fmt.Errorf("cannot insert compliance portal commitment group: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *CompliancePortalCommitmentGroup) Update(
|
||||||
|
ctx context.Context,
|
||||||
|
conn pg.Tx,
|
||||||
|
scope Scoper,
|
||||||
|
) error {
|
||||||
|
q := `
|
||||||
|
UPDATE compliance_portal_commitment_groups
|
||||||
|
SET
|
||||||
|
title = @title,
|
||||||
|
description = @description,
|
||||||
|
updated_at = @updated_at
|
||||||
|
WHERE
|
||||||
|
%s
|
||||||
|
AND id = @id;
|
||||||
|
`
|
||||||
|
|
||||||
|
q = fmt.Sprintf(q, scope.SQLFragment())
|
||||||
|
|
||||||
|
args := pgx.StrictNamedArgs{
|
||||||
|
"id": t.ID,
|
||||||
|
"title": t.Title,
|
||||||
|
"description": t.Description,
|
||||||
|
"updated_at": t.UpdatedAt,
|
||||||
|
}
|
||||||
|
maps.Copy(args, scope.SQLArguments())
|
||||||
|
|
||||||
|
result, err := conn.Exec(ctx, q, args)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("cannot update compliance portal commitment group: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if result.RowsAffected() == 0 {
|
||||||
|
return ErrResourceNotFound
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *CompliancePortalCommitmentGroup) UpdateRank(
|
||||||
|
ctx context.Context,
|
||||||
|
conn pg.Tx,
|
||||||
|
scope Scoper,
|
||||||
|
) error {
|
||||||
|
q := `
|
||||||
|
WITH old AS (
|
||||||
|
SELECT
|
||||||
|
rank AS old_rank
|
||||||
|
FROM compliance_portal_commitment_groups
|
||||||
|
WHERE %s AND id = @id AND trust_center_id = @trust_center_id
|
||||||
|
)
|
||||||
|
UPDATE compliance_portal_commitment_groups
|
||||||
|
SET
|
||||||
|
rank = CASE
|
||||||
|
WHEN id = @id THEN @new_rank
|
||||||
|
ELSE rank + CASE
|
||||||
|
WHEN @new_rank < old.old_rank THEN 1
|
||||||
|
WHEN @new_rank > old.old_rank THEN -1
|
||||||
|
END
|
||||||
|
END,
|
||||||
|
updated_at = @updated_at
|
||||||
|
FROM old
|
||||||
|
WHERE %s
|
||||||
|
AND (
|
||||||
|
id = @id
|
||||||
|
OR (rank BETWEEN LEAST(old.old_rank, @new_rank) AND GREATEST(old.old_rank, @new_rank))
|
||||||
|
);
|
||||||
|
`
|
||||||
|
|
||||||
|
scopeFragment := scope.SQLFragment()
|
||||||
|
q = fmt.Sprintf(q, scopeFragment, scopeFragment)
|
||||||
|
|
||||||
|
args := pgx.StrictNamedArgs{
|
||||||
|
"id": t.ID,
|
||||||
|
"new_rank": t.Rank,
|
||||||
|
"trust_center_id": t.TrustCenterID,
|
||||||
|
"updated_at": t.UpdatedAt,
|
||||||
|
}
|
||||||
|
maps.Copy(args, scope.SQLArguments())
|
||||||
|
|
||||||
|
_, err := conn.Exec(ctx, q, args)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("cannot update compliance portal commitment group rank: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *CompliancePortalCommitmentGroup) Delete(
|
||||||
|
ctx context.Context,
|
||||||
|
conn pg.Tx,
|
||||||
|
scope Scoper,
|
||||||
|
) error {
|
||||||
|
q := `
|
||||||
|
DELETE FROM
|
||||||
|
compliance_portal_commitment_groups
|
||||||
|
WHERE
|
||||||
|
%s
|
||||||
|
AND id = @id
|
||||||
|
`
|
||||||
|
|
||||||
|
q = fmt.Sprintf(q, scope.SQLFragment())
|
||||||
|
|
||||||
|
args := pgx.StrictNamedArgs{"id": t.ID}
|
||||||
|
maps.Copy(args, scope.SQLArguments())
|
||||||
|
|
||||||
|
_, err := conn.Exec(ctx, q, args)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("cannot delete compliance portal commitment group: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *CompliancePortalCommitmentGroups) LoadByTrustCenterID(
|
||||||
|
ctx context.Context,
|
||||||
|
conn pg.Querier,
|
||||||
|
scope Scoper,
|
||||||
|
trustCenterID gid.GID,
|
||||||
|
cursor *page.Cursor[CompliancePortalCommitmentGroupOrderField],
|
||||||
|
) error {
|
||||||
|
q := `
|
||||||
|
SELECT
|
||||||
|
id,
|
||||||
|
organization_id,
|
||||||
|
trust_center_id,
|
||||||
|
title,
|
||||||
|
description,
|
||||||
|
rank,
|
||||||
|
created_at,
|
||||||
|
updated_at
|
||||||
|
FROM
|
||||||
|
compliance_portal_commitment_groups
|
||||||
|
WHERE
|
||||||
|
%s
|
||||||
|
AND trust_center_id = @trust_center_id
|
||||||
|
AND %s
|
||||||
|
`
|
||||||
|
|
||||||
|
q = fmt.Sprintf(q, scope.SQLFragment(), cursor.SQLFragment())
|
||||||
|
|
||||||
|
args := pgx.StrictNamedArgs{"trust_center_id": trustCenterID}
|
||||||
|
maps.Copy(args, scope.SQLArguments())
|
||||||
|
maps.Copy(args, cursor.SQLArguments())
|
||||||
|
|
||||||
|
rows, err := conn.Query(ctx, q, args)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("cannot query compliance_portal_commitment_groups: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
groups, err := pgx.CollectRows(rows, pgx.RowToAddrOfStructByName[CompliancePortalCommitmentGroup])
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("cannot collect compliance portal commitment groups: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
*t = groups
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *CompliancePortalCommitmentGroups) CountByTrustCenterID(
|
||||||
|
ctx context.Context,
|
||||||
|
conn pg.Querier,
|
||||||
|
scope Scoper,
|
||||||
|
trustCenterID gid.GID,
|
||||||
|
) (int, error) {
|
||||||
|
q := `
|
||||||
|
SELECT
|
||||||
|
COUNT(*)
|
||||||
|
FROM
|
||||||
|
compliance_portal_commitment_groups
|
||||||
|
WHERE
|
||||||
|
%s
|
||||||
|
AND trust_center_id = @trust_center_id
|
||||||
|
`
|
||||||
|
|
||||||
|
q = fmt.Sprintf(q, scope.SQLFragment())
|
||||||
|
|
||||||
|
args := pgx.StrictNamedArgs{"trust_center_id": trustCenterID}
|
||||||
|
maps.Copy(args, scope.SQLArguments())
|
||||||
|
|
||||||
|
var count int
|
||||||
|
|
||||||
|
err := conn.QueryRow(ctx, q, args).Scan(&count)
|
||||||
|
if err != nil {
|
||||||
|
return 0, fmt.Errorf("cannot count compliance portal commitment groups: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return count, nil
|
||||||
|
}
|
||||||
@@ -0,0 +1,97 @@
|
|||||||
|
// 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.
|
||||||
|
|
||||||
|
package coredata
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"go.probo.inc/probo/pkg/page"
|
||||||
|
)
|
||||||
|
|
||||||
|
type (
|
||||||
|
CompliancePortalCommitmentGroupOrderField string
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
CompliancePortalCommitmentGroupOrderFieldRank CompliancePortalCommitmentGroupOrderField = "RANK"
|
||||||
|
CompliancePortalCommitmentGroupOrderFieldCreatedAt CompliancePortalCommitmentGroupOrderField = "CREATED_AT"
|
||||||
|
CompliancePortalCommitmentGroupOrderFieldUpdatedAt CompliancePortalCommitmentGroupOrderField = "UPDATED_AT"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
_ page.OrderField = CompliancePortalCommitmentGroupOrderField("")
|
||||||
|
_ fmt.Stringer = CompliancePortalCommitmentGroupOrderField("")
|
||||||
|
_ encoding.TextMarshaler = CompliancePortalCommitmentGroupOrderField("")
|
||||||
|
_ encoding.TextUnmarshaler = (*CompliancePortalCommitmentGroupOrderField)(nil)
|
||||||
|
)
|
||||||
|
|
||||||
|
func CompliancePortalCommitmentGroupOrderFields() []CompliancePortalCommitmentGroupOrderField {
|
||||||
|
return []CompliancePortalCommitmentGroupOrderField{
|
||||||
|
CompliancePortalCommitmentGroupOrderFieldRank,
|
||||||
|
CompliancePortalCommitmentGroupOrderFieldCreatedAt,
|
||||||
|
CompliancePortalCommitmentGroupOrderFieldUpdatedAt,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v CompliancePortalCommitmentGroupOrderField) IsValid() bool {
|
||||||
|
switch v {
|
||||||
|
case
|
||||||
|
CompliancePortalCommitmentGroupOrderFieldRank,
|
||||||
|
CompliancePortalCommitmentGroupOrderFieldCreatedAt,
|
||||||
|
CompliancePortalCommitmentGroupOrderFieldUpdatedAt:
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v CompliancePortalCommitmentGroupOrderField) String() string {
|
||||||
|
return string(v)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v CompliancePortalCommitmentGroupOrderField) MarshalText() ([]byte, error) {
|
||||||
|
return []byte(v.String()), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *CompliancePortalCommitmentGroupOrderField) UnmarshalText(text []byte) error {
|
||||||
|
val := CompliancePortalCommitmentGroupOrderField(text)
|
||||||
|
if !val.IsValid() {
|
||||||
|
return fmt.Errorf("invalid CompliancePortalCommitmentGroupOrderField value: %q", string(text))
|
||||||
|
}
|
||||||
|
|
||||||
|
*v = val
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p CompliancePortalCommitmentGroupOrderField) Column() string {
|
||||||
|
switch p {
|
||||||
|
case CompliancePortalCommitmentGroupOrderFieldRank:
|
||||||
|
return "rank"
|
||||||
|
case CompliancePortalCommitmentGroupOrderFieldCreatedAt:
|
||||||
|
return "created_at"
|
||||||
|
case CompliancePortalCommitmentGroupOrderFieldUpdatedAt:
|
||||||
|
return "updated_at"
|
||||||
|
default:
|
||||||
|
return string(p)
|
||||||
|
}
|
||||||
|
}
|
||||||
130
pkg/coredata/compliance_portal_commitment_icon.go
Normal file
130
pkg/coredata/compliance_portal_commitment_icon.go
Normal file
@@ -0,0 +1,130 @@
|
|||||||
|
// 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.
|
||||||
|
|
||||||
|
package coredata
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding"
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
|
type CompliancePortalCommitmentIcon string
|
||||||
|
|
||||||
|
const (
|
||||||
|
CompliancePortalCommitmentIconLockKey CompliancePortalCommitmentIcon = "LOCK_KEY"
|
||||||
|
CompliancePortalCommitmentIconEyeSlash CompliancePortalCommitmentIcon = "EYE_SLASH"
|
||||||
|
CompliancePortalCommitmentIconFingerprint CompliancePortalCommitmentIcon = "FINGERPRINT"
|
||||||
|
CompliancePortalCommitmentIconShieldWarning CompliancePortalCommitmentIcon = "SHIELD_WARNING"
|
||||||
|
CompliancePortalCommitmentIconShieldCheck CompliancePortalCommitmentIcon = "SHIELD_CHECK"
|
||||||
|
CompliancePortalCommitmentIconSiren CompliancePortalCommitmentIcon = "SIREN"
|
||||||
|
CompliancePortalCommitmentIconKey CompliancePortalCommitmentIcon = "KEY"
|
||||||
|
CompliancePortalCommitmentIconLock CompliancePortalCommitmentIcon = "LOCK"
|
||||||
|
CompliancePortalCommitmentIconCloud CompliancePortalCommitmentIcon = "CLOUD"
|
||||||
|
CompliancePortalCommitmentIconDatabase CompliancePortalCommitmentIcon = "DATABASE"
|
||||||
|
CompliancePortalCommitmentIconGlobe CompliancePortalCommitmentIcon = "GLOBE"
|
||||||
|
CompliancePortalCommitmentIconEye CompliancePortalCommitmentIcon = "EYE"
|
||||||
|
CompliancePortalCommitmentIconUsers CompliancePortalCommitmentIcon = "USERS"
|
||||||
|
CompliancePortalCommitmentIconCertificate CompliancePortalCommitmentIcon = "CERTIFICATE"
|
||||||
|
CompliancePortalCommitmentIconGavel CompliancePortalCommitmentIcon = "GAVEL"
|
||||||
|
CompliancePortalCommitmentIconHeartbeat CompliancePortalCommitmentIcon = "HEARTBEAT"
|
||||||
|
CompliancePortalCommitmentIconBell CompliancePortalCommitmentIcon = "BELL"
|
||||||
|
CompliancePortalCommitmentIconBug CompliancePortalCommitmentIcon = "BUG"
|
||||||
|
CompliancePortalCommitmentIconCode CompliancePortalCommitmentIcon = "CODE"
|
||||||
|
CompliancePortalCommitmentIconServer CompliancePortalCommitmentIcon = "SERVER"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
_ fmt.Stringer = CompliancePortalCommitmentIcon("")
|
||||||
|
_ encoding.TextMarshaler = CompliancePortalCommitmentIcon("")
|
||||||
|
_ encoding.TextUnmarshaler = (*CompliancePortalCommitmentIcon)(nil)
|
||||||
|
)
|
||||||
|
|
||||||
|
func CompliancePortalCommitmentIcons() []CompliancePortalCommitmentIcon {
|
||||||
|
return []CompliancePortalCommitmentIcon{
|
||||||
|
CompliancePortalCommitmentIconLockKey,
|
||||||
|
CompliancePortalCommitmentIconEyeSlash,
|
||||||
|
CompliancePortalCommitmentIconFingerprint,
|
||||||
|
CompliancePortalCommitmentIconShieldWarning,
|
||||||
|
CompliancePortalCommitmentIconShieldCheck,
|
||||||
|
CompliancePortalCommitmentIconSiren,
|
||||||
|
CompliancePortalCommitmentIconKey,
|
||||||
|
CompliancePortalCommitmentIconLock,
|
||||||
|
CompliancePortalCommitmentIconCloud,
|
||||||
|
CompliancePortalCommitmentIconDatabase,
|
||||||
|
CompliancePortalCommitmentIconGlobe,
|
||||||
|
CompliancePortalCommitmentIconEye,
|
||||||
|
CompliancePortalCommitmentIconUsers,
|
||||||
|
CompliancePortalCommitmentIconCertificate,
|
||||||
|
CompliancePortalCommitmentIconGavel,
|
||||||
|
CompliancePortalCommitmentIconHeartbeat,
|
||||||
|
CompliancePortalCommitmentIconBell,
|
||||||
|
CompliancePortalCommitmentIconBug,
|
||||||
|
CompliancePortalCommitmentIconCode,
|
||||||
|
CompliancePortalCommitmentIconServer,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v CompliancePortalCommitmentIcon) IsValid() bool {
|
||||||
|
switch v {
|
||||||
|
case
|
||||||
|
CompliancePortalCommitmentIconLockKey,
|
||||||
|
CompliancePortalCommitmentIconEyeSlash,
|
||||||
|
CompliancePortalCommitmentIconFingerprint,
|
||||||
|
CompliancePortalCommitmentIconShieldWarning,
|
||||||
|
CompliancePortalCommitmentIconShieldCheck,
|
||||||
|
CompliancePortalCommitmentIconSiren,
|
||||||
|
CompliancePortalCommitmentIconKey,
|
||||||
|
CompliancePortalCommitmentIconLock,
|
||||||
|
CompliancePortalCommitmentIconCloud,
|
||||||
|
CompliancePortalCommitmentIconDatabase,
|
||||||
|
CompliancePortalCommitmentIconGlobe,
|
||||||
|
CompliancePortalCommitmentIconEye,
|
||||||
|
CompliancePortalCommitmentIconUsers,
|
||||||
|
CompliancePortalCommitmentIconCertificate,
|
||||||
|
CompliancePortalCommitmentIconGavel,
|
||||||
|
CompliancePortalCommitmentIconHeartbeat,
|
||||||
|
CompliancePortalCommitmentIconBell,
|
||||||
|
CompliancePortalCommitmentIconBug,
|
||||||
|
CompliancePortalCommitmentIconCode,
|
||||||
|
CompliancePortalCommitmentIconServer:
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v CompliancePortalCommitmentIcon) String() string {
|
||||||
|
return string(v)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v CompliancePortalCommitmentIcon) MarshalText() ([]byte, error) {
|
||||||
|
return []byte(v.String()), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *CompliancePortalCommitmentIcon) UnmarshalText(text []byte) error {
|
||||||
|
val := CompliancePortalCommitmentIcon(text)
|
||||||
|
if !val.IsValid() {
|
||||||
|
return fmt.Errorf("invalid CompliancePortalCommitmentIcon value: %q", string(text))
|
||||||
|
}
|
||||||
|
|
||||||
|
*v = val
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
97
pkg/coredata/compliance_portal_commitment_order_field.go
Normal file
97
pkg/coredata/compliance_portal_commitment_order_field.go
Normal file
@@ -0,0 +1,97 @@
|
|||||||
|
// 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.
|
||||||
|
|
||||||
|
package coredata
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"go.probo.inc/probo/pkg/page"
|
||||||
|
)
|
||||||
|
|
||||||
|
type (
|
||||||
|
CompliancePortalCommitmentOrderField string
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
CompliancePortalCommitmentOrderFieldRank CompliancePortalCommitmentOrderField = "RANK"
|
||||||
|
CompliancePortalCommitmentOrderFieldCreatedAt CompliancePortalCommitmentOrderField = "CREATED_AT"
|
||||||
|
CompliancePortalCommitmentOrderFieldUpdatedAt CompliancePortalCommitmentOrderField = "UPDATED_AT"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
_ page.OrderField = CompliancePortalCommitmentOrderField("")
|
||||||
|
_ fmt.Stringer = CompliancePortalCommitmentOrderField("")
|
||||||
|
_ encoding.TextMarshaler = CompliancePortalCommitmentOrderField("")
|
||||||
|
_ encoding.TextUnmarshaler = (*CompliancePortalCommitmentOrderField)(nil)
|
||||||
|
)
|
||||||
|
|
||||||
|
func CompliancePortalCommitmentOrderFields() []CompliancePortalCommitmentOrderField {
|
||||||
|
return []CompliancePortalCommitmentOrderField{
|
||||||
|
CompliancePortalCommitmentOrderFieldRank,
|
||||||
|
CompliancePortalCommitmentOrderFieldCreatedAt,
|
||||||
|
CompliancePortalCommitmentOrderFieldUpdatedAt,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v CompliancePortalCommitmentOrderField) IsValid() bool {
|
||||||
|
switch v {
|
||||||
|
case
|
||||||
|
CompliancePortalCommitmentOrderFieldRank,
|
||||||
|
CompliancePortalCommitmentOrderFieldCreatedAt,
|
||||||
|
CompliancePortalCommitmentOrderFieldUpdatedAt:
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v CompliancePortalCommitmentOrderField) String() string {
|
||||||
|
return string(v)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v CompliancePortalCommitmentOrderField) MarshalText() ([]byte, error) {
|
||||||
|
return []byte(v.String()), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *CompliancePortalCommitmentOrderField) UnmarshalText(text []byte) error {
|
||||||
|
val := CompliancePortalCommitmentOrderField(text)
|
||||||
|
if !val.IsValid() {
|
||||||
|
return fmt.Errorf("invalid CompliancePortalCommitmentOrderField value: %q", string(text))
|
||||||
|
}
|
||||||
|
|
||||||
|
*v = val
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p CompliancePortalCommitmentOrderField) Column() string {
|
||||||
|
switch p {
|
||||||
|
case CompliancePortalCommitmentOrderFieldRank:
|
||||||
|
return "rank"
|
||||||
|
case CompliancePortalCommitmentOrderFieldCreatedAt:
|
||||||
|
return "created_at"
|
||||||
|
case CompliancePortalCommitmentOrderFieldUpdatedAt:
|
||||||
|
return "updated_at"
|
||||||
|
default:
|
||||||
|
return string(p)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -133,6 +133,8 @@ const (
|
|||||||
RiskAssessmentBoundaryEntityType uint16 = 101
|
RiskAssessmentBoundaryEntityType uint16 = 101
|
||||||
AccessReviewCampaignSourceEntityType uint16 = 102
|
AccessReviewCampaignSourceEntityType uint16 = 102
|
||||||
AccessReviewCampaignSourceFetchAttemptEntityType uint16 = 103
|
AccessReviewCampaignSourceFetchAttemptEntityType uint16 = 103
|
||||||
|
CompliancePortalCommitmentGroupEntityType uint16 = 104
|
||||||
|
CompliancePortalCommitmentEntityType uint16 = 105
|
||||||
)
|
)
|
||||||
|
|
||||||
func NewEntityFromID(id gid.GID) (any, bool) {
|
func NewEntityFromID(id gid.GID) (any, bool) {
|
||||||
@@ -327,6 +329,10 @@ func NewEntityFromID(id gid.GID) (any, bool) {
|
|||||||
return &AccessReviewCampaignSource{ID: id}, true
|
return &AccessReviewCampaignSource{ID: id}, true
|
||||||
case AccessReviewCampaignSourceFetchAttemptEntityType:
|
case AccessReviewCampaignSourceFetchAttemptEntityType:
|
||||||
return &AccessReviewCampaignSourceFetchAttempt{ID: id}, true
|
return &AccessReviewCampaignSourceFetchAttempt{ID: id}, true
|
||||||
|
case CompliancePortalCommitmentGroupEntityType:
|
||||||
|
return &CompliancePortalCommitmentGroup{ID: id}, true
|
||||||
|
case CompliancePortalCommitmentEntityType:
|
||||||
|
return &CompliancePortalCommitment{ID: id}, true
|
||||||
default:
|
default:
|
||||||
return nil, false
|
return nil, false
|
||||||
}
|
}
|
||||||
|
|||||||
53
pkg/coredata/migrations/20260716T084447Z.sql
Normal file
53
pkg/coredata/migrations/20260716T084447Z.sql
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
-- 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.
|
||||||
|
|
||||||
|
CREATE TABLE compliance_portal_commitment_groups (
|
||||||
|
id TEXT PRIMARY KEY,
|
||||||
|
tenant_id TEXT NOT NULL,
|
||||||
|
organization_id TEXT NOT NULL REFERENCES organizations(id)
|
||||||
|
ON UPDATE CASCADE ON DELETE CASCADE,
|
||||||
|
trust_center_id TEXT NOT NULL REFERENCES trust_centers(id)
|
||||||
|
ON UPDATE CASCADE ON DELETE CASCADE,
|
||||||
|
title TEXT NOT NULL,
|
||||||
|
description TEXT NOT NULL,
|
||||||
|
rank INTEGER NOT NULL,
|
||||||
|
created_at TIMESTAMP WITH TIME ZONE NOT NULL,
|
||||||
|
updated_at TIMESTAMP WITH TIME ZONE NOT NULL,
|
||||||
|
UNIQUE (trust_center_id, rank)
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE compliance_portal_commitments (
|
||||||
|
id TEXT PRIMARY KEY,
|
||||||
|
tenant_id TEXT NOT NULL,
|
||||||
|
organization_id TEXT NOT NULL REFERENCES organizations(id)
|
||||||
|
ON UPDATE CASCADE ON DELETE CASCADE,
|
||||||
|
trust_center_id TEXT NOT NULL REFERENCES trust_centers(id)
|
||||||
|
ON UPDATE CASCADE ON DELETE CASCADE,
|
||||||
|
group_id TEXT NOT NULL REFERENCES compliance_portal_commitment_groups(id)
|
||||||
|
ON UPDATE CASCADE ON DELETE CASCADE,
|
||||||
|
icon TEXT NOT NULL,
|
||||||
|
eyebrow TEXT NOT NULL,
|
||||||
|
title TEXT NOT NULL,
|
||||||
|
description TEXT NOT NULL,
|
||||||
|
rank INTEGER NOT NULL,
|
||||||
|
created_at TIMESTAMP WITH TIME ZONE NOT NULL,
|
||||||
|
updated_at TIMESTAMP WITH TIME ZONE NOT NULL,
|
||||||
|
UNIQUE (group_id, rank)
|
||||||
|
);
|
||||||
@@ -69,6 +69,20 @@ const (
|
|||||||
ActionTrustCenterReferenceUpdate = "core:trust-center-reference:update"
|
ActionTrustCenterReferenceUpdate = "core:trust-center-reference:update"
|
||||||
ActionTrustCenterReferenceDelete = "core:trust-center-reference:delete"
|
ActionTrustCenterReferenceDelete = "core:trust-center-reference:delete"
|
||||||
|
|
||||||
|
// CompliancePortalCommitmentGroup actions
|
||||||
|
ActionCompliancePortalCommitmentGroupList = "core:compliance-portal-commitment-group:list"
|
||||||
|
ActionCompliancePortalCommitmentGroupCreate = "core:compliance-portal-commitment-group:create"
|
||||||
|
ActionCompliancePortalCommitmentGroupUpdate = "core:compliance-portal-commitment-group:update"
|
||||||
|
ActionCompliancePortalCommitmentGroupUpdateRank = "core:compliance-portal-commitment-group:update-rank"
|
||||||
|
ActionCompliancePortalCommitmentGroupDelete = "core:compliance-portal-commitment-group:delete"
|
||||||
|
|
||||||
|
// CompliancePortalCommitment actions
|
||||||
|
ActionCompliancePortalCommitmentList = "core:compliance-portal-commitment:list"
|
||||||
|
ActionCompliancePortalCommitmentCreate = "core:compliance-portal-commitment:create"
|
||||||
|
ActionCompliancePortalCommitmentUpdate = "core:compliance-portal-commitment:update"
|
||||||
|
ActionCompliancePortalCommitmentUpdateRank = "core:compliance-portal-commitment:update-rank"
|
||||||
|
ActionCompliancePortalCommitmentDelete = "core:compliance-portal-commitment:delete"
|
||||||
|
|
||||||
// ComplianceFramework actions
|
// ComplianceFramework actions
|
||||||
ActionComplianceFrameworkList = "core:compliance-framework:list"
|
ActionComplianceFrameworkList = "core:compliance-framework:list"
|
||||||
ActionComplianceFrameworkCreate = "core:compliance-framework:create"
|
ActionComplianceFrameworkCreate = "core:compliance-framework:create"
|
||||||
|
|||||||
257
pkg/probo/compliance_portal_commitment_group_service.go
Normal file
257
pkg/probo/compliance_portal_commitment_group_service.go
Normal file
@@ -0,0 +1,257 @@
|
|||||||
|
// 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.
|
||||||
|
|
||||||
|
package probo
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"go.gearno.de/kit/pg"
|
||||||
|
"go.probo.inc/probo/pkg/coredata"
|
||||||
|
"go.probo.inc/probo/pkg/gid"
|
||||||
|
"go.probo.inc/probo/pkg/page"
|
||||||
|
"go.probo.inc/probo/pkg/validator"
|
||||||
|
)
|
||||||
|
|
||||||
|
type (
|
||||||
|
CompliancePortalCommitmentGroupService struct {
|
||||||
|
svc *Service
|
||||||
|
}
|
||||||
|
|
||||||
|
CreateCompliancePortalCommitmentGroupRequest struct {
|
||||||
|
TrustCenterID gid.GID
|
||||||
|
Title string
|
||||||
|
Description string
|
||||||
|
}
|
||||||
|
|
||||||
|
UpdateCompliancePortalCommitmentGroupRequest struct {
|
||||||
|
ID gid.GID
|
||||||
|
Title *string
|
||||||
|
Description *string
|
||||||
|
Rank *int
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
func (r *CreateCompliancePortalCommitmentGroupRequest) Validate() error {
|
||||||
|
v := validator.New()
|
||||||
|
|
||||||
|
v.Check(r.TrustCenterID, "trust_center_id", validator.Required(), validator.GID(coredata.TrustCenterEntityType))
|
||||||
|
v.Check(r.Title, "title", validator.Required(), validator.SafeTextNoNewLine(TitleMaxLength))
|
||||||
|
v.Check(r.Description, "description", validator.SafeText(ContentMaxLength))
|
||||||
|
|
||||||
|
return v.Error()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *UpdateCompliancePortalCommitmentGroupRequest) Validate() error {
|
||||||
|
v := validator.New()
|
||||||
|
|
||||||
|
v.Check(r.ID, "id", validator.Required(), validator.GID(coredata.CompliancePortalCommitmentGroupEntityType))
|
||||||
|
v.Check(r.Title, "title", validator.SafeTextNoNewLine(TitleMaxLength))
|
||||||
|
v.Check(r.Description, "description", validator.SafeText(ContentMaxLength))
|
||||||
|
|
||||||
|
return v.Error()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s CompliancePortalCommitmentGroupService) ListForTrustCenterID(
|
||||||
|
ctx context.Context,
|
||||||
|
scope coredata.Scoper,
|
||||||
|
trustCenterID gid.GID,
|
||||||
|
cursor *page.Cursor[coredata.CompliancePortalCommitmentGroupOrderField],
|
||||||
|
) (*page.Page[*coredata.CompliancePortalCommitmentGroup, coredata.CompliancePortalCommitmentGroupOrderField], error) {
|
||||||
|
var groups coredata.CompliancePortalCommitmentGroups
|
||||||
|
|
||||||
|
err := s.svc.pg.WithConn(ctx, func(ctx context.Context, conn pg.Querier) error {
|
||||||
|
err := groups.LoadByTrustCenterID(ctx, conn, scope, trustCenterID, cursor)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("cannot load compliance portal commitment groups: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return page.NewPage(groups, cursor), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s CompliancePortalCommitmentGroupService) CountForTrustCenterID(
|
||||||
|
ctx context.Context,
|
||||||
|
scope coredata.Scoper,
|
||||||
|
trustCenterID gid.GID,
|
||||||
|
) (int, error) {
|
||||||
|
var count int
|
||||||
|
|
||||||
|
err := s.svc.pg.WithConn(ctx, func(ctx context.Context, conn pg.Querier) (err error) {
|
||||||
|
groups := coredata.CompliancePortalCommitmentGroups{}
|
||||||
|
|
||||||
|
count, err = groups.CountByTrustCenterID(ctx, conn, scope, trustCenterID)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("cannot count compliance portal commitment groups: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return count, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s CompliancePortalCommitmentGroupService) Get(
|
||||||
|
ctx context.Context,
|
||||||
|
scope coredata.Scoper,
|
||||||
|
groupID gid.GID,
|
||||||
|
) (*coredata.CompliancePortalCommitmentGroup, error) {
|
||||||
|
var group coredata.CompliancePortalCommitmentGroup
|
||||||
|
|
||||||
|
err := s.svc.pg.WithConn(ctx, func(ctx context.Context, conn pg.Querier) error {
|
||||||
|
err := group.LoadByID(ctx, conn, scope, groupID)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("cannot load compliance portal commitment group: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &group, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s CompliancePortalCommitmentGroupService) Create(
|
||||||
|
ctx context.Context,
|
||||||
|
scope coredata.Scoper,
|
||||||
|
req *CreateCompliancePortalCommitmentGroupRequest,
|
||||||
|
) (*coredata.CompliancePortalCommitmentGroup, error) {
|
||||||
|
if err := req.Validate(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
now := time.Now()
|
||||||
|
|
||||||
|
groupID := gid.New(scope.GetTenantID(), coredata.CompliancePortalCommitmentGroupEntityType)
|
||||||
|
|
||||||
|
var group *coredata.CompliancePortalCommitmentGroup
|
||||||
|
|
||||||
|
err := s.svc.pg.WithTx(ctx, func(ctx context.Context, tx pg.Tx) error {
|
||||||
|
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{
|
||||||
|
ID: groupID,
|
||||||
|
OrganizationID: trustCenter.OrganizationID,
|
||||||
|
TrustCenterID: req.TrustCenterID,
|
||||||
|
Title: req.Title,
|
||||||
|
Description: req.Description,
|
||||||
|
CreatedAt: now,
|
||||||
|
UpdatedAt: now,
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := group.Insert(ctx, tx, scope); err != nil {
|
||||||
|
return fmt.Errorf("cannot insert compliance portal commitment group: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return group, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s CompliancePortalCommitmentGroupService) Update(
|
||||||
|
ctx context.Context,
|
||||||
|
scope coredata.Scoper,
|
||||||
|
req *UpdateCompliancePortalCommitmentGroupRequest,
|
||||||
|
) (*coredata.CompliancePortalCommitmentGroup, error) {
|
||||||
|
if err := req.Validate(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
now := time.Now()
|
||||||
|
|
||||||
|
var group *coredata.CompliancePortalCommitmentGroup
|
||||||
|
|
||||||
|
err := s.svc.pg.WithTx(ctx, func(ctx context.Context, tx pg.Tx) error {
|
||||||
|
group = &coredata.CompliancePortalCommitmentGroup{}
|
||||||
|
|
||||||
|
if err := group.LoadByID(ctx, tx, scope, req.ID); err != nil {
|
||||||
|
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 {
|
||||||
|
return fmt.Errorf("cannot update compliance portal commitment group: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return group, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s CompliancePortalCommitmentGroupService) Delete(
|
||||||
|
ctx context.Context,
|
||||||
|
scope coredata.Scoper,
|
||||||
|
groupID gid.GID,
|
||||||
|
) error {
|
||||||
|
err := s.svc.pg.WithTx(ctx, func(ctx context.Context, tx pg.Tx) error {
|
||||||
|
group := &coredata.CompliancePortalCommitmentGroup{}
|
||||||
|
|
||||||
|
if err := group.LoadByID(ctx, tx, scope, groupID); err != nil {
|
||||||
|
return fmt.Errorf("cannot load compliance portal commitment group: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := group.Delete(ctx, tx, scope); err != nil {
|
||||||
|
return fmt.Errorf("cannot delete compliance portal commitment group: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
278
pkg/probo/compliance_portal_commitment_service.go
Normal file
278
pkg/probo/compliance_portal_commitment_service.go
Normal file
@@ -0,0 +1,278 @@
|
|||||||
|
// 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.
|
||||||
|
|
||||||
|
package probo
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"go.gearno.de/kit/pg"
|
||||||
|
"go.probo.inc/probo/pkg/coredata"
|
||||||
|
"go.probo.inc/probo/pkg/gid"
|
||||||
|
"go.probo.inc/probo/pkg/page"
|
||||||
|
"go.probo.inc/probo/pkg/validator"
|
||||||
|
)
|
||||||
|
|
||||||
|
type (
|
||||||
|
CompliancePortalCommitmentService struct {
|
||||||
|
svc *Service
|
||||||
|
}
|
||||||
|
|
||||||
|
CreateCompliancePortalCommitmentRequest struct {
|
||||||
|
GroupID gid.GID
|
||||||
|
Icon coredata.CompliancePortalCommitmentIcon
|
||||||
|
Eyebrow string
|
||||||
|
Title string
|
||||||
|
Description string
|
||||||
|
}
|
||||||
|
|
||||||
|
UpdateCompliancePortalCommitmentRequest struct {
|
||||||
|
ID gid.GID
|
||||||
|
Icon *coredata.CompliancePortalCommitmentIcon
|
||||||
|
Eyebrow *string
|
||||||
|
Title *string
|
||||||
|
Description *string
|
||||||
|
Rank *int
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
func (r *CreateCompliancePortalCommitmentRequest) Validate() error {
|
||||||
|
v := validator.New()
|
||||||
|
|
||||||
|
v.Check(r.GroupID, "group_id", validator.Required(), validator.GID(coredata.CompliancePortalCommitmentGroupEntityType))
|
||||||
|
v.Check(r.Icon, "icon", validator.Required(), validator.OneOfSlice(coredata.CompliancePortalCommitmentIcons()))
|
||||||
|
v.Check(r.Eyebrow, "eyebrow", validator.SafeTextNoNewLine(TitleMaxLength))
|
||||||
|
v.Check(r.Title, "title", validator.Required(), validator.SafeTextNoNewLine(TitleMaxLength))
|
||||||
|
v.Check(r.Description, "description", validator.SafeText(ContentMaxLength))
|
||||||
|
|
||||||
|
return v.Error()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *UpdateCompliancePortalCommitmentRequest) Validate() error {
|
||||||
|
v := validator.New()
|
||||||
|
|
||||||
|
v.Check(r.ID, "id", validator.Required(), validator.GID(coredata.CompliancePortalCommitmentEntityType))
|
||||||
|
if r.Icon != nil {
|
||||||
|
v.Check(*r.Icon, "icon", validator.OneOfSlice(coredata.CompliancePortalCommitmentIcons()))
|
||||||
|
}
|
||||||
|
v.Check(r.Eyebrow, "eyebrow", validator.SafeTextNoNewLine(TitleMaxLength))
|
||||||
|
v.Check(r.Title, "title", validator.SafeTextNoNewLine(TitleMaxLength))
|
||||||
|
v.Check(r.Description, "description", validator.SafeText(ContentMaxLength))
|
||||||
|
|
||||||
|
return v.Error()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s CompliancePortalCommitmentService) ListForGroupID(
|
||||||
|
ctx context.Context,
|
||||||
|
scope coredata.Scoper,
|
||||||
|
groupID gid.GID,
|
||||||
|
cursor *page.Cursor[coredata.CompliancePortalCommitmentOrderField],
|
||||||
|
) (*page.Page[*coredata.CompliancePortalCommitment, coredata.CompliancePortalCommitmentOrderField], error) {
|
||||||
|
var commitments coredata.CompliancePortalCommitments
|
||||||
|
|
||||||
|
err := s.svc.pg.WithConn(ctx, func(ctx context.Context, conn pg.Querier) error {
|
||||||
|
err := commitments.LoadByGroupID(ctx, conn, scope, groupID, cursor)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("cannot load compliance portal commitments: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return page.NewPage(commitments, cursor), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s CompliancePortalCommitmentService) CountForGroupID(
|
||||||
|
ctx context.Context,
|
||||||
|
scope coredata.Scoper,
|
||||||
|
groupID gid.GID,
|
||||||
|
) (int, error) {
|
||||||
|
var count int
|
||||||
|
|
||||||
|
err := s.svc.pg.WithConn(ctx, func(ctx context.Context, conn pg.Querier) (err error) {
|
||||||
|
commitments := coredata.CompliancePortalCommitments{}
|
||||||
|
|
||||||
|
count, err = commitments.CountByGroupID(ctx, conn, scope, groupID)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("cannot count compliance portal commitments: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return count, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s CompliancePortalCommitmentService) Get(
|
||||||
|
ctx context.Context,
|
||||||
|
scope coredata.Scoper,
|
||||||
|
commitmentID gid.GID,
|
||||||
|
) (*coredata.CompliancePortalCommitment, error) {
|
||||||
|
var commitment coredata.CompliancePortalCommitment
|
||||||
|
|
||||||
|
err := s.svc.pg.WithConn(ctx, func(ctx context.Context, conn pg.Querier) error {
|
||||||
|
err := commitment.LoadByID(ctx, conn, scope, commitmentID)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("cannot load compliance portal commitment: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &commitment, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s CompliancePortalCommitmentService) Create(
|
||||||
|
ctx context.Context,
|
||||||
|
scope coredata.Scoper,
|
||||||
|
req *CreateCompliancePortalCommitmentRequest,
|
||||||
|
) (*coredata.CompliancePortalCommitment, error) {
|
||||||
|
if err := req.Validate(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
now := time.Now()
|
||||||
|
|
||||||
|
commitmentID := gid.New(scope.GetTenantID(), coredata.CompliancePortalCommitmentEntityType)
|
||||||
|
|
||||||
|
var commitment *coredata.CompliancePortalCommitment
|
||||||
|
|
||||||
|
err := s.svc.pg.WithTx(ctx, func(ctx context.Context, tx pg.Tx) error {
|
||||||
|
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{
|
||||||
|
ID: commitmentID,
|
||||||
|
OrganizationID: group.OrganizationID,
|
||||||
|
TrustCenterID: group.TrustCenterID,
|
||||||
|
GroupID: req.GroupID,
|
||||||
|
Icon: req.Icon,
|
||||||
|
Eyebrow: req.Eyebrow,
|
||||||
|
Title: req.Title,
|
||||||
|
Description: req.Description,
|
||||||
|
CreatedAt: now,
|
||||||
|
UpdatedAt: now,
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := commitment.Insert(ctx, tx, scope); err != nil {
|
||||||
|
return fmt.Errorf("cannot insert compliance portal commitment: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return commitment, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s CompliancePortalCommitmentService) Update(
|
||||||
|
ctx context.Context,
|
||||||
|
scope coredata.Scoper,
|
||||||
|
req *UpdateCompliancePortalCommitmentRequest,
|
||||||
|
) (*coredata.CompliancePortalCommitment, error) {
|
||||||
|
if err := req.Validate(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
now := time.Now()
|
||||||
|
|
||||||
|
var commitment *coredata.CompliancePortalCommitment
|
||||||
|
|
||||||
|
err := s.svc.pg.WithTx(ctx, func(ctx context.Context, tx pg.Tx) error {
|
||||||
|
commitment = &coredata.CompliancePortalCommitment{}
|
||||||
|
|
||||||
|
if err := commitment.LoadByID(ctx, tx, scope, req.ID); err != nil {
|
||||||
|
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 {
|
||||||
|
return fmt.Errorf("cannot update compliance portal commitment: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return commitment, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s CompliancePortalCommitmentService) Delete(
|
||||||
|
ctx context.Context,
|
||||||
|
scope coredata.Scoper,
|
||||||
|
commitmentID gid.GID,
|
||||||
|
) error {
|
||||||
|
err := s.svc.pg.WithTx(ctx, func(ctx context.Context, tx pg.Tx) error {
|
||||||
|
commitment := &coredata.CompliancePortalCommitment{}
|
||||||
|
|
||||||
|
if err := commitment.LoadByID(ctx, tx, scope, commitmentID); err != nil {
|
||||||
|
return fmt.Errorf("cannot load compliance portal commitment: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := commitment.Delete(ctx, tx, scope); err != nil {
|
||||||
|
return fmt.Errorf("cannot delete compliance portal commitment: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
@@ -108,6 +108,7 @@ var ViewerPolicy = policy.NewPolicy(
|
|||||||
ActionTrustCenterDocumentAccessList,
|
ActionTrustCenterDocumentAccessList,
|
||||||
ActionTrustCenterFileGet, ActionTrustCenterFileList, ActionTrustCenterFileGetFileUrl,
|
ActionTrustCenterFileGet, ActionTrustCenterFileList, ActionTrustCenterFileGetFileUrl,
|
||||||
ActionTrustCenterReferenceList, ActionTrustCenterReferenceGetLogoUrl,
|
ActionTrustCenterReferenceList, ActionTrustCenterReferenceGetLogoUrl,
|
||||||
|
ActionCompliancePortalCommitmentGroupList, ActionCompliancePortalCommitmentList,
|
||||||
ActionComplianceFrameworkList,
|
ActionComplianceFrameworkList,
|
||||||
).WithSID("trust-center-read-access").When(organizationCondition),
|
).WithSID("trust-center-read-access").When(organizationCondition),
|
||||||
|
|
||||||
|
|||||||
@@ -113,6 +113,8 @@ type (
|
|||||||
TrustCenters *TrustCenterService
|
TrustCenters *TrustCenterService
|
||||||
TrustCenterAccesses *TrustCenterAccessService
|
TrustCenterAccesses *TrustCenterAccessService
|
||||||
TrustCenterReferences *TrustCenterReferenceService
|
TrustCenterReferences *TrustCenterReferenceService
|
||||||
|
CompliancePortalCommitmentGroups *CompliancePortalCommitmentGroupService
|
||||||
|
CompliancePortalCommitments *CompliancePortalCommitmentService
|
||||||
TrustCenterFiles *TrustCenterFileService
|
TrustCenterFiles *TrustCenterFileService
|
||||||
ComplianceFrameworks *ComplianceFrameworkService
|
ComplianceFrameworks *ComplianceFrameworkService
|
||||||
ComplianceExternalURLs *ComplianceExternalURLService
|
ComplianceExternalURLs *ComplianceExternalURLService
|
||||||
@@ -235,6 +237,8 @@ func NewService(
|
|||||||
svc.TrustCenters = &TrustCenterService{svc: svc}
|
svc.TrustCenters = &TrustCenterService{svc: svc}
|
||||||
svc.TrustCenterAccesses = &TrustCenterAccessService{svc: svc}
|
svc.TrustCenterAccesses = &TrustCenterAccessService{svc: svc}
|
||||||
svc.TrustCenterReferences = &TrustCenterReferenceService{svc: svc}
|
svc.TrustCenterReferences = &TrustCenterReferenceService{svc: svc}
|
||||||
|
svc.CompliancePortalCommitmentGroups = &CompliancePortalCommitmentGroupService{svc: svc}
|
||||||
|
svc.CompliancePortalCommitments = &CompliancePortalCommitmentService{svc: svc}
|
||||||
svc.ComplianceFrameworks = &ComplianceFrameworkService{svc: svc}
|
svc.ComplianceFrameworks = &ComplianceFrameworkService{svc: svc}
|
||||||
svc.ComplianceExternalURLs = &ComplianceExternalURLService{svc: svc}
|
svc.ComplianceExternalURLs = &ComplianceExternalURLService{svc: svc}
|
||||||
svc.TrustCenterFiles = &TrustCenterFileService{
|
svc.TrustCenterFiles = &TrustCenterFileService{
|
||||||
|
|||||||
@@ -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 ComplianceExternalURLOrderField
|
enum ComplianceExternalURLOrderField
|
||||||
@goModel(
|
@goModel(
|
||||||
model: "go.probo.inc/probo/pkg/coredata.ComplianceExternalURLOrderField"
|
model: "go.probo.inc/probo/pkg/coredata.ComplianceExternalURLOrderField"
|
||||||
@@ -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"
|
||||||
@@ -272,6 +370,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
|
||||||
@@ -389,6 +495,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"
|
||||||
@@ -514,6 +680,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!
|
||||||
@@ -613,6 +797,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!
|
||||||
@@ -712,6 +934,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!
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -51,6 +51,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, probo.ActionCompliancePortalCommitmentList)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
count, err := r.probo.CompliancePortalCommitments.CountForGroupID(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, probo.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.probo.CompliancePortalCommitments.ListForGroupID(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, probo.ActionCompliancePortalCommitmentGroupList)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
count, err := r.probo.CompliancePortalCommitmentGroups.CountForTrustCenterID(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)
|
||||||
@@ -364,6 +436,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, probo.ActionCompliancePortalCommitmentGroupCreate)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
group, err := r.probo.CompliancePortalCommitmentGroups.Create(
|
||||||
|
ctx, scope,
|
||||||
|
&probo.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, probo.ActionCompliancePortalCommitmentGroupUpdate)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
group, err := r.probo.CompliancePortalCommitmentGroups.Update(
|
||||||
|
ctx, scope,
|
||||||
|
&probo.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, probo.ActionCompliancePortalCommitmentGroupDelete)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := r.probo.CompliancePortalCommitmentGroups.Delete(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, probo.ActionCompliancePortalCommitmentCreate)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
commitment, err := r.probo.CompliancePortalCommitments.Create(
|
||||||
|
ctx, scope,
|
||||||
|
&probo.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, probo.ActionCompliancePortalCommitmentUpdate)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
commitment, err := r.probo.CompliancePortalCommitments.Update(
|
||||||
|
ctx, scope,
|
||||||
|
&probo.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, probo.ActionCompliancePortalCommitmentDelete)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := r.probo.CompliancePortalCommitments.Delete(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, probo.ActionComplianceFrameworkCreate)
|
scope, err := r.authorize(ctx, input.TrustCenterID, probo.ActionComplianceFrameworkCreate)
|
||||||
@@ -817,6 +1049,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, probo.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.probo.CompliancePortalCommitmentGroups.ListForTrustCenterID(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, probo.ActionComplianceFrameworkList)
|
scope, err := r.authorize(ctx, obj.ID, probo.ActionComplianceFrameworkList)
|
||||||
@@ -1236,6 +1498,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} }
|
||||||
|
|
||||||
@@ -1280,6 +1562,10 @@ func (r *Resolver) TrustCenterReferenceConnection() schema.TrustCenterReferenceC
|
|||||||
type (
|
type (
|
||||||
complianceExternalURLResolver struct{ *Resolver }
|
complianceExternalURLResolver struct{ *Resolver }
|
||||||
complianceFrameworkResolver struct{ *Resolver }
|
complianceFrameworkResolver struct{ *Resolver }
|
||||||
|
compliancePortalCommitmentResolver struct{ *Resolver }
|
||||||
|
compliancePortalCommitmentConnectionResolver struct{ *Resolver }
|
||||||
|
compliancePortalCommitmentGroupResolver struct{ *Resolver }
|
||||||
|
compliancePortalCommitmentGroupConnectionResolver struct{ *Resolver }
|
||||||
customDomainResolver struct{ *Resolver }
|
customDomainResolver struct{ *Resolver }
|
||||||
trustCenterResolver struct{ *Resolver }
|
trustCenterResolver struct{ *Resolver }
|
||||||
trustCenterAccessResolver struct{ *Resolver }
|
trustCenterAccessResolver struct{ *Resolver }
|
||||||
|
|||||||
125
pkg/server/api/console/v1/types/compliance_portal_commitment.go
Normal file
125
pkg/server/api/console/v1/types/compliance_portal_commitment.go
Normal file
@@ -0,0 +1,125 @@
|
|||||||
|
// 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.
|
||||||
|
|
||||||
|
package types
|
||||||
|
|
||||||
|
import (
|
||||||
|
"go.probo.inc/probo/pkg/coredata"
|
||||||
|
"go.probo.inc/probo/pkg/gid"
|
||||||
|
"go.probo.inc/probo/pkg/page"
|
||||||
|
)
|
||||||
|
|
||||||
|
type (
|
||||||
|
CompliancePortalCommitmentGroupOrderBy = OrderBy[coredata.CompliancePortalCommitmentGroupOrderField]
|
||||||
|
|
||||||
|
CompliancePortalCommitmentGroupConnection struct {
|
||||||
|
TotalCount int `json:"totalCount"`
|
||||||
|
Edges []*CompliancePortalCommitmentGroupEdge `json:"edges"`
|
||||||
|
PageInfo *PageInfo `json:"pageInfo"`
|
||||||
|
ParentID gid.GID `json:"-"`
|
||||||
|
}
|
||||||
|
|
||||||
|
CompliancePortalCommitmentOrderBy = OrderBy[coredata.CompliancePortalCommitmentOrderField]
|
||||||
|
|
||||||
|
CompliancePortalCommitmentConnection struct {
|
||||||
|
TotalCount int `json:"totalCount"`
|
||||||
|
Edges []*CompliancePortalCommitmentEdge `json:"edges"`
|
||||||
|
PageInfo *PageInfo `json:"pageInfo"`
|
||||||
|
ParentID gid.GID `json:"-"`
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
func NewCompliancePortalCommitmentGroup(g *coredata.CompliancePortalCommitmentGroup) *CompliancePortalCommitmentGroup {
|
||||||
|
return &CompliancePortalCommitmentGroup{
|
||||||
|
ID: g.ID,
|
||||||
|
Title: g.Title,
|
||||||
|
Description: g.Description,
|
||||||
|
Rank: g.Rank,
|
||||||
|
CreatedAt: g.CreatedAt,
|
||||||
|
UpdatedAt: g.UpdatedAt,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewCompliancePortalCommitmentGroupEdge(
|
||||||
|
g *coredata.CompliancePortalCommitmentGroup,
|
||||||
|
orderBy coredata.CompliancePortalCommitmentGroupOrderField,
|
||||||
|
) *CompliancePortalCommitmentGroupEdge {
|
||||||
|
return &CompliancePortalCommitmentGroupEdge{
|
||||||
|
Cursor: g.CursorKey(orderBy),
|
||||||
|
Node: NewCompliancePortalCommitmentGroup(g),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewCompliancePortalCommitmentGroupConnection(
|
||||||
|
p *page.Page[*coredata.CompliancePortalCommitmentGroup, coredata.CompliancePortalCommitmentGroupOrderField],
|
||||||
|
parentID gid.GID,
|
||||||
|
) *CompliancePortalCommitmentGroupConnection {
|
||||||
|
edges := make([]*CompliancePortalCommitmentGroupEdge, len(p.Data))
|
||||||
|
|
||||||
|
for i := range edges {
|
||||||
|
edges[i] = NewCompliancePortalCommitmentGroupEdge(p.Data[i], p.Cursor.OrderBy.Field)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &CompliancePortalCommitmentGroupConnection{
|
||||||
|
Edges: edges,
|
||||||
|
PageInfo: NewPageInfo(p),
|
||||||
|
ParentID: parentID,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewCompliancePortalCommitment(c *coredata.CompliancePortalCommitment) *CompliancePortalCommitment {
|
||||||
|
return &CompliancePortalCommitment{
|
||||||
|
ID: c.ID,
|
||||||
|
Icon: c.Icon,
|
||||||
|
Eyebrow: c.Eyebrow,
|
||||||
|
Title: c.Title,
|
||||||
|
Description: c.Description,
|
||||||
|
Rank: c.Rank,
|
||||||
|
CreatedAt: c.CreatedAt,
|
||||||
|
UpdatedAt: c.UpdatedAt,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewCompliancePortalCommitmentEdge(
|
||||||
|
c *coredata.CompliancePortalCommitment,
|
||||||
|
orderBy coredata.CompliancePortalCommitmentOrderField,
|
||||||
|
) *CompliancePortalCommitmentEdge {
|
||||||
|
return &CompliancePortalCommitmentEdge{
|
||||||
|
Cursor: c.CursorKey(orderBy),
|
||||||
|
Node: NewCompliancePortalCommitment(c),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewCompliancePortalCommitmentConnection(
|
||||||
|
p *page.Page[*coredata.CompliancePortalCommitment, coredata.CompliancePortalCommitmentOrderField],
|
||||||
|
parentID gid.GID,
|
||||||
|
) *CompliancePortalCommitmentConnection {
|
||||||
|
edges := make([]*CompliancePortalCommitmentEdge, len(p.Data))
|
||||||
|
|
||||||
|
for i := range edges {
|
||||||
|
edges[i] = NewCompliancePortalCommitmentEdge(p.Data[i], p.Cursor.OrderBy.Field)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &CompliancePortalCommitmentConnection{
|
||||||
|
Edges: edges,
|
||||||
|
PageInfo: NewPageInfo(p),
|
||||||
|
ParentID: parentID,
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -47,6 +47,13 @@ type TrustCenter implements Node {
|
|||||||
before: CursorKey
|
before: CursorKey
|
||||||
): TrustCenterReferenceConnection! @goField(forceResolver: true)
|
): TrustCenterReferenceConnection! @goField(forceResolver: true)
|
||||||
|
|
||||||
|
commitmentGroups(
|
||||||
|
first: Int
|
||||||
|
after: CursorKey
|
||||||
|
last: Int
|
||||||
|
before: CursorKey
|
||||||
|
): CompliancePortalCommitmentGroupConnection! @goField(forceResolver: true)
|
||||||
|
|
||||||
trustCenterFiles(
|
trustCenterFiles(
|
||||||
first: Int
|
first: Int
|
||||||
after: CursorKey
|
after: CursorKey
|
||||||
@@ -304,6 +311,91 @@ type TrustCenterReferenceEdge @nda {
|
|||||||
node: TrustCenterReference!
|
node: TrustCenterReference!
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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")
|
||||||
|
}
|
||||||
|
|
||||||
|
type CompliancePortalCommitmentGroup implements Node {
|
||||||
|
id: ID!
|
||||||
|
title: String!
|
||||||
|
description: String!
|
||||||
|
|
||||||
|
commitments(
|
||||||
|
first: Int
|
||||||
|
after: CursorKey
|
||||||
|
last: Int
|
||||||
|
before: CursorKey
|
||||||
|
): CompliancePortalCommitmentConnection! @goField(forceResolver: true)
|
||||||
|
}
|
||||||
|
|
||||||
|
type CompliancePortalCommitmentGroupConnection {
|
||||||
|
edges: [CompliancePortalCommitmentGroupEdge!]!
|
||||||
|
pageInfo: PageInfo!
|
||||||
|
}
|
||||||
|
|
||||||
|
type CompliancePortalCommitmentGroupEdge {
|
||||||
|
cursor: CursorKey!
|
||||||
|
node: CompliancePortalCommitmentGroup!
|
||||||
|
}
|
||||||
|
|
||||||
|
type CompliancePortalCommitment implements Node {
|
||||||
|
id: ID!
|
||||||
|
icon: CompliancePortalCommitmentIcon!
|
||||||
|
eyebrow: String!
|
||||||
|
title: String!
|
||||||
|
description: String!
|
||||||
|
}
|
||||||
|
|
||||||
|
type CompliancePortalCommitmentConnection {
|
||||||
|
edges: [CompliancePortalCommitmentEdge!]!
|
||||||
|
pageInfo: PageInfo!
|
||||||
|
}
|
||||||
|
|
||||||
|
type CompliancePortalCommitmentEdge {
|
||||||
|
cursor: CursorKey!
|
||||||
|
node: CompliancePortalCommitment!
|
||||||
|
}
|
||||||
|
|
||||||
type TrustCenterFile implements Node @nda {
|
type TrustCenterFile implements Node @nda {
|
||||||
id: ID!
|
id: ID!
|
||||||
name: String!
|
name: String!
|
||||||
|
|||||||
@@ -175,6 +175,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) {
|
||||||
|
compliancePage := compliancepage.CompliancePageFromContext(ctx)
|
||||||
|
scope := coredata.NewScopeFromObjectID(compliancePage.OrganizationID)
|
||||||
|
pageOrderBy := page.OrderBy[coredata.CompliancePortalCommitmentOrderField]{
|
||||||
|
Field: coredata.CompliancePortalCommitmentOrderFieldRank,
|
||||||
|
Direction: page.OrderDirectionAsc,
|
||||||
|
}
|
||||||
|
cursor := types.NewCursor(first, after, last, before, pageOrderBy)
|
||||||
|
|
||||||
|
commitmentPage, err := r.trust.CompliancePortalCommitments.ListForGroupID(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)
|
||||||
@@ -897,6 +916,25 @@ 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) {
|
||||||
|
compliancePage := compliancepage.CompliancePageFromContext(ctx)
|
||||||
|
scope := coredata.NewScopeFromObjectID(compliancePage.OrganizationID)
|
||||||
|
pageOrderBy := page.OrderBy[coredata.CompliancePortalCommitmentGroupOrderField]{
|
||||||
|
Field: coredata.CompliancePortalCommitmentGroupOrderFieldRank,
|
||||||
|
Direction: page.OrderDirectionAsc,
|
||||||
|
}
|
||||||
|
cursor := types.NewCursor(first, after, last, before, pageOrderBy)
|
||||||
|
|
||||||
|
groupPage, err := r.trust.CompliancePortalCommitmentGroups.ListForTrustCenterID(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, filter *types.TrustCenterVisibilityFilter) (*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) {
|
||||||
compliancePage := compliancepage.CompliancePageFromContext(ctx)
|
compliancePage := compliancepage.CompliancePageFromContext(ctx)
|
||||||
@@ -1115,6 +1153,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} }
|
||||||
|
|
||||||
@@ -1143,6 +1186,7 @@ type (
|
|||||||
auditResolver struct{ *Resolver }
|
auditResolver struct{ *Resolver }
|
||||||
auditReportResolver struct{ *Resolver }
|
auditReportResolver struct{ *Resolver }
|
||||||
complianceFrameworkResolver struct{ *Resolver }
|
complianceFrameworkResolver struct{ *Resolver }
|
||||||
|
compliancePortalCommitmentGroupResolver struct{ *Resolver }
|
||||||
documentResolver struct{ *Resolver }
|
documentResolver struct{ *Resolver }
|
||||||
frameworkResolver struct{ *Resolver }
|
frameworkResolver struct{ *Resolver }
|
||||||
subprocessorConnectionResolver struct{ *Resolver }
|
subprocessorConnectionResolver struct{ *Resolver }
|
||||||
|
|||||||
@@ -0,0 +1,94 @@
|
|||||||
|
// 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.
|
||||||
|
|
||||||
|
package types
|
||||||
|
|
||||||
|
import (
|
||||||
|
"go.probo.inc/probo/pkg/coredata"
|
||||||
|
"go.probo.inc/probo/pkg/page"
|
||||||
|
)
|
||||||
|
|
||||||
|
func NewCompliancePortalCommitmentGroup(g *coredata.CompliancePortalCommitmentGroup) *CompliancePortalCommitmentGroup {
|
||||||
|
return &CompliancePortalCommitmentGroup{
|
||||||
|
ID: g.ID,
|
||||||
|
Title: g.Title,
|
||||||
|
Description: g.Description,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewCompliancePortalCommitmentGroupConnection(
|
||||||
|
p *page.Page[*coredata.CompliancePortalCommitmentGroup, coredata.CompliancePortalCommitmentGroupOrderField],
|
||||||
|
) *CompliancePortalCommitmentGroupConnection {
|
||||||
|
edges := make([]*CompliancePortalCommitmentGroupEdge, len(p.Data))
|
||||||
|
|
||||||
|
for i, item := range p.Data {
|
||||||
|
edges[i] = NewCompliancePortalCommitmentGroupEdge(item, p.Cursor.OrderBy.Field)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &CompliancePortalCommitmentGroupConnection{
|
||||||
|
Edges: edges,
|
||||||
|
PageInfo: NewPageInfo(p),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewCompliancePortalCommitmentGroupEdge(
|
||||||
|
g *coredata.CompliancePortalCommitmentGroup,
|
||||||
|
orderBy coredata.CompliancePortalCommitmentGroupOrderField,
|
||||||
|
) *CompliancePortalCommitmentGroupEdge {
|
||||||
|
return &CompliancePortalCommitmentGroupEdge{
|
||||||
|
Cursor: g.CursorKey(orderBy),
|
||||||
|
Node: NewCompliancePortalCommitmentGroup(g),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewCompliancePortalCommitment(c *coredata.CompliancePortalCommitment) *CompliancePortalCommitment {
|
||||||
|
return &CompliancePortalCommitment{
|
||||||
|
ID: c.ID,
|
||||||
|
Icon: c.Icon,
|
||||||
|
Eyebrow: c.Eyebrow,
|
||||||
|
Title: c.Title,
|
||||||
|
Description: c.Description,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewCompliancePortalCommitmentConnection(
|
||||||
|
p *page.Page[*coredata.CompliancePortalCommitment, coredata.CompliancePortalCommitmentOrderField],
|
||||||
|
) *CompliancePortalCommitmentConnection {
|
||||||
|
edges := make([]*CompliancePortalCommitmentEdge, len(p.Data))
|
||||||
|
|
||||||
|
for i, item := range p.Data {
|
||||||
|
edges[i] = NewCompliancePortalCommitmentEdge(item, p.Cursor.OrderBy.Field)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &CompliancePortalCommitmentConnection{
|
||||||
|
Edges: edges,
|
||||||
|
PageInfo: NewPageInfo(p),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewCompliancePortalCommitmentEdge(
|
||||||
|
c *coredata.CompliancePortalCommitment,
|
||||||
|
orderBy coredata.CompliancePortalCommitmentOrderField,
|
||||||
|
) *CompliancePortalCommitmentEdge {
|
||||||
|
return &CompliancePortalCommitmentEdge{
|
||||||
|
Cursor: c.CursorKey(orderBy),
|
||||||
|
Node: NewCompliancePortalCommitment(c),
|
||||||
|
}
|
||||||
|
}
|
||||||
83
pkg/trust/compliance_portal_commitment_group_service.go
Normal file
83
pkg/trust/compliance_portal_commitment_group_service.go
Normal file
@@ -0,0 +1,83 @@
|
|||||||
|
// 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.
|
||||||
|
|
||||||
|
package trust
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"go.gearno.de/kit/pg"
|
||||||
|
"go.probo.inc/probo/pkg/coredata"
|
||||||
|
"go.probo.inc/probo/pkg/gid"
|
||||||
|
"go.probo.inc/probo/pkg/page"
|
||||||
|
)
|
||||||
|
|
||||||
|
type CompliancePortalCommitmentGroupService struct {
|
||||||
|
svc *Service
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s CompliancePortalCommitmentGroupService) ListForTrustCenterID(
|
||||||
|
ctx context.Context,
|
||||||
|
scope coredata.Scoper,
|
||||||
|
trustCenterID gid.GID,
|
||||||
|
cursor *page.Cursor[coredata.CompliancePortalCommitmentGroupOrderField],
|
||||||
|
) (*page.Page[*coredata.CompliancePortalCommitmentGroup, coredata.CompliancePortalCommitmentGroupOrderField], error) {
|
||||||
|
var groups coredata.CompliancePortalCommitmentGroups
|
||||||
|
|
||||||
|
err := s.svc.pg.WithConn(ctx, func(ctx context.Context, conn pg.Querier) error {
|
||||||
|
err := groups.LoadByTrustCenterID(ctx, conn, scope, trustCenterID, cursor)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("cannot load compliance portal commitment groups: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return page.NewPage(groups, cursor), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s CompliancePortalCommitmentGroupService) Get(
|
||||||
|
ctx context.Context,
|
||||||
|
scope coredata.Scoper,
|
||||||
|
groupID gid.GID,
|
||||||
|
) (*coredata.CompliancePortalCommitmentGroup, error) {
|
||||||
|
group := &coredata.CompliancePortalCommitmentGroup{}
|
||||||
|
|
||||||
|
err := s.svc.pg.WithConn(
|
||||||
|
ctx,
|
||||||
|
func(ctx context.Context, conn pg.Querier) error {
|
||||||
|
err := group.LoadByID(ctx, conn, scope, groupID)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("cannot load compliance portal commitment group: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return group, nil
|
||||||
|
}
|
||||||
83
pkg/trust/compliance_portal_commitment_service.go
Normal file
83
pkg/trust/compliance_portal_commitment_service.go
Normal file
@@ -0,0 +1,83 @@
|
|||||||
|
// 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.
|
||||||
|
|
||||||
|
package trust
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"go.gearno.de/kit/pg"
|
||||||
|
"go.probo.inc/probo/pkg/coredata"
|
||||||
|
"go.probo.inc/probo/pkg/gid"
|
||||||
|
"go.probo.inc/probo/pkg/page"
|
||||||
|
)
|
||||||
|
|
||||||
|
type CompliancePortalCommitmentService struct {
|
||||||
|
svc *Service
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s CompliancePortalCommitmentService) ListForGroupID(
|
||||||
|
ctx context.Context,
|
||||||
|
scope coredata.Scoper,
|
||||||
|
groupID gid.GID,
|
||||||
|
cursor *page.Cursor[coredata.CompliancePortalCommitmentOrderField],
|
||||||
|
) (*page.Page[*coredata.CompliancePortalCommitment, coredata.CompliancePortalCommitmentOrderField], error) {
|
||||||
|
var commitments coredata.CompliancePortalCommitments
|
||||||
|
|
||||||
|
err := s.svc.pg.WithConn(ctx, func(ctx context.Context, conn pg.Querier) error {
|
||||||
|
err := commitments.LoadByGroupID(ctx, conn, scope, groupID, cursor)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("cannot load compliance portal commitments: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return page.NewPage(commitments, cursor), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s CompliancePortalCommitmentService) Get(
|
||||||
|
ctx context.Context,
|
||||||
|
scope coredata.Scoper,
|
||||||
|
commitmentID gid.GID,
|
||||||
|
) (*coredata.CompliancePortalCommitment, error) {
|
||||||
|
commitment := &coredata.CompliancePortalCommitment{}
|
||||||
|
|
||||||
|
err := s.svc.pg.WithConn(
|
||||||
|
ctx,
|
||||||
|
func(ctx context.Context, conn pg.Querier) error {
|
||||||
|
err := commitment.LoadByID(ctx, conn, scope, commitmentID)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("cannot load compliance portal commitment: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return commitment, nil
|
||||||
|
}
|
||||||
@@ -65,6 +65,10 @@ type (
|
|||||||
ComplianceFrameworks *ComplianceFrameworkService
|
ComplianceFrameworks *ComplianceFrameworkService
|
||||||
TrustCenterAccesses *TrustCenterAccessService
|
TrustCenterAccesses *TrustCenterAccessService
|
||||||
TrustCenterReferences *TrustCenterReferenceService
|
TrustCenterReferences *TrustCenterReferenceService
|
||||||
|
|
||||||
|
CompliancePortalCommitmentGroups *CompliancePortalCommitmentGroupService
|
||||||
|
CompliancePortalCommitments *CompliancePortalCommitmentService
|
||||||
|
|
||||||
TrustCenterFiles *TrustCenterFileService
|
TrustCenterFiles *TrustCenterFileService
|
||||||
Reports *ReportService
|
Reports *ReportService
|
||||||
Organizations *OrganizationService
|
Organizations *OrganizationService
|
||||||
@@ -109,6 +113,8 @@ func NewService(
|
|||||||
svc.ComplianceFrameworks = &ComplianceFrameworkService{svc: svc}
|
svc.ComplianceFrameworks = &ComplianceFrameworkService{svc: svc}
|
||||||
svc.TrustCenterAccesses = &TrustCenterAccessService{svc: svc, iamSvc: iam, logger: logger}
|
svc.TrustCenterAccesses = &TrustCenterAccessService{svc: svc, iamSvc: iam, logger: logger}
|
||||||
svc.TrustCenterReferences = &TrustCenterReferenceService{svc: svc}
|
svc.TrustCenterReferences = &TrustCenterReferenceService{svc: svc}
|
||||||
|
svc.CompliancePortalCommitmentGroups = &CompliancePortalCommitmentGroupService{svc: svc}
|
||||||
|
svc.CompliancePortalCommitments = &CompliancePortalCommitmentService{svc: svc}
|
||||||
svc.TrustCenterFiles = &TrustCenterFileService{svc: svc}
|
svc.TrustCenterFiles = &TrustCenterFileService{svc: svc}
|
||||||
svc.Reports = &ReportService{svc: svc}
|
svc.Reports = &ReportService{svc: svc}
|
||||||
svc.Organizations = &OrganizationService{svc: svc}
|
svc.Organizations = &OrganizationService{svc: svc}
|
||||||
|
|||||||
Reference in New Issue
Block a user