Add policy sign email notif
Signed-off-by: Bryan Frimin <bryan@getprobo.com>
This commit is contained in:
@@ -13,6 +13,7 @@ import AuthLayout from "./layouts/AuthLayout";
|
||||
import { RelayEnvironment } from "./RelayEnvironment";
|
||||
import { AuthenticationRoutes } from "./pages/authentication/Routes";
|
||||
import { OrganizationsRoutes } from "./pages/organizations/Routes";
|
||||
import SigningRequestsPage from "./pages/SigningRequestsPage";
|
||||
|
||||
posthog.init(process.env.POSTHOG_KEY!, {
|
||||
api_host: process.env.POSTHOG_HOST,
|
||||
@@ -53,6 +54,11 @@ function App() {
|
||||
element={<OrganizationsRoutes />}
|
||||
/>
|
||||
|
||||
<Route
|
||||
path="policies/signing-requests"
|
||||
element={<SigningRequestsPage />}
|
||||
/>
|
||||
|
||||
<Route
|
||||
path="*"
|
||||
element={
|
||||
|
||||
235
apps/console/src/pages/SigningRequestsPage.tsx
Normal file
235
apps/console/src/pages/SigningRequestsPage.tsx
Normal file
@@ -0,0 +1,235 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import { useSearchParams } from "react-router";
|
||||
import { Helmet } from "react-helmet-async";
|
||||
import { buildEndpoint } from "../utils";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from "@/components/ui/card";
|
||||
import { Progress } from "@/components/ui/progress";
|
||||
|
||||
type Document = {
|
||||
id: string;
|
||||
title: string;
|
||||
content: string;
|
||||
signed: boolean;
|
||||
};
|
||||
|
||||
type SigningResponse = {
|
||||
documents: Document[];
|
||||
requesterName: string;
|
||||
requesterOrganization: string;
|
||||
};
|
||||
|
||||
export default function SigningRequestsPage() {
|
||||
const [searchParams] = useSearchParams();
|
||||
const token = searchParams.get("token");
|
||||
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const [signingData, setSigningData] = useState<SigningResponse | null>(null);
|
||||
const [currentDocIndex, setCurrentDocIndex] = useState(0);
|
||||
|
||||
// Fetch documents to sign using the token
|
||||
useEffect(() => {
|
||||
if (!token) {
|
||||
setError("Missing signing token. Please check your URL and try again.");
|
||||
setLoading(false);
|
||||
return;
|
||||
}
|
||||
|
||||
async function fetchDocuments() {
|
||||
try {
|
||||
const response = await fetch(buildEndpoint("/api/signing-requests"), {
|
||||
method: "GET",
|
||||
headers: {
|
||||
"Authorization": `Bearer ${token}`,
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error("Failed to fetch signing documents");
|
||||
}
|
||||
|
||||
const data: SigningResponse = await response.json();
|
||||
setSigningData(data);
|
||||
} catch (err) {
|
||||
setError(err instanceof Error ? err.message : "An unknown error occurred");
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
}
|
||||
|
||||
fetchDocuments();
|
||||
}, [token]);
|
||||
|
||||
// Handle document signing
|
||||
const handleSignDocument = async () => {
|
||||
if (!signingData || !token) return;
|
||||
|
||||
const docToSign = signingData.documents[currentDocIndex];
|
||||
|
||||
try {
|
||||
const response = await fetch(buildEndpoint(`/api/signing-requests/${docToSign.id}/sign`), {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Authorization": `Bearer ${token}`,
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error("Failed to sign document");
|
||||
}
|
||||
|
||||
// Update local state
|
||||
const updatedDocs = [...signingData.documents];
|
||||
updatedDocs[currentDocIndex] = {
|
||||
...updatedDocs[currentDocIndex],
|
||||
signed: true,
|
||||
};
|
||||
|
||||
setSigningData({
|
||||
...signingData,
|
||||
documents: updatedDocs,
|
||||
});
|
||||
|
||||
// Move to next document if available
|
||||
if (currentDocIndex < updatedDocs.length - 1) {
|
||||
setCurrentDocIndex(currentDocIndex + 1);
|
||||
}
|
||||
} catch (err) {
|
||||
setError(err instanceof Error ? err.message : "Failed to sign document");
|
||||
}
|
||||
};
|
||||
|
||||
// Handle going to next document
|
||||
const handleNextDocument = () => {
|
||||
if (signingData && currentDocIndex < signingData.documents.length - 1) {
|
||||
setCurrentDocIndex(currentDocIndex + 1);
|
||||
}
|
||||
};
|
||||
|
||||
// Calculate progress
|
||||
const getSignedCount = () => {
|
||||
if (!signingData) return 0;
|
||||
return signingData.documents.filter(doc => doc.signed).length;
|
||||
};
|
||||
|
||||
const getProgressPercentage = () => {
|
||||
if (!signingData || signingData.documents.length === 0) return 0;
|
||||
return (getSignedCount() / signingData.documents.length) * 100;
|
||||
};
|
||||
|
||||
// Loading state
|
||||
if (loading) {
|
||||
return (
|
||||
<div className="container mx-auto py-10 flex justify-center">
|
||||
<Card className="w-full max-w-3xl">
|
||||
<CardHeader>
|
||||
<CardTitle>Loading Signing Requests</CardTitle>
|
||||
<CardDescription>Please wait while we fetch your documents...</CardDescription>
|
||||
</CardHeader>
|
||||
</Card>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// Error state
|
||||
if (error) {
|
||||
return (
|
||||
<div className="container mx-auto py-10 flex justify-center">
|
||||
<Card className="w-full max-w-3xl">
|
||||
<CardHeader>
|
||||
<CardTitle>Error</CardTitle>
|
||||
<CardDescription>{error}</CardDescription>
|
||||
</CardHeader>
|
||||
<CardFooter>
|
||||
<Button onClick={() => window.location.reload()}>Try Again</Button>
|
||||
</CardFooter>
|
||||
</Card>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// No documents or missing data
|
||||
if (!signingData || signingData.documents.length === 0) {
|
||||
return (
|
||||
<div className="container mx-auto py-10 flex justify-center">
|
||||
<Card className="w-full max-w-3xl">
|
||||
<CardHeader>
|
||||
<CardTitle>No Documents to Sign</CardTitle>
|
||||
<CardDescription>There are no documents requiring your signature at this time.</CardDescription>
|
||||
</CardHeader>
|
||||
</Card>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// Current document
|
||||
const currentDoc = signingData.documents[currentDocIndex];
|
||||
const isLastDocument = currentDocIndex === signingData.documents.length - 1;
|
||||
const allSigned = getSignedCount() === signingData.documents.length;
|
||||
|
||||
return (
|
||||
<div className="container mx-auto py-10">
|
||||
<Helmet>
|
||||
<title>Document Signing - Probo</title>
|
||||
</Helmet>
|
||||
|
||||
<div className="mb-8">
|
||||
<h1 className="text-2xl font-bold mb-2">Document Signing Request</h1>
|
||||
<p className="text-muted-foreground">
|
||||
From {signingData.requesterName} at {signingData.requesterOrganization}
|
||||
</p>
|
||||
|
||||
<div className="mt-4">
|
||||
<div className="flex items-center justify-between mb-2">
|
||||
<span className="text-sm text-muted-foreground">
|
||||
{getSignedCount()} of {signingData.documents.length} documents signed
|
||||
</span>
|
||||
<span className="text-sm font-medium">{Math.round(getProgressPercentage())}%</span>
|
||||
</div>
|
||||
<Progress value={getProgressPercentage()} className="h-2" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Card className="w-full">
|
||||
<CardHeader>
|
||||
<CardTitle>{currentDoc.title}</CardTitle>
|
||||
<CardDescription>
|
||||
Document {currentDocIndex + 1} of {signingData.documents.length}
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
|
||||
<CardContent>
|
||||
<div className="border rounded-md p-4 min-h-[400px] bg-muted/20">
|
||||
<div dangerouslySetInnerHTML={{ __html: currentDoc.content }} />
|
||||
</div>
|
||||
</CardContent>
|
||||
|
||||
<CardFooter className="flex justify-between">
|
||||
{currentDoc.signed ? (
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="text-sm text-green-600 font-medium">✓ Signed</div>
|
||||
{!isLastDocument && (
|
||||
<Button onClick={handleNextDocument}>
|
||||
Next Document
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
) : (
|
||||
<Button onClick={handleSignDocument}>
|
||||
Sign Document
|
||||
</Button>
|
||||
)}
|
||||
|
||||
{allSigned && (
|
||||
<div className="text-green-600 font-medium">
|
||||
All documents have been signed
|
||||
</div>
|
||||
)}
|
||||
</CardFooter>
|
||||
</Card>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -29,6 +29,7 @@ import { format } from "date-fns";
|
||||
import type { PolicyListViewQuery, PolicyListViewQuery$data } from "./__generated__/PolicyListViewQuery.graphql";
|
||||
import type { PolicyListViewDeleteMutation } from "./__generated__/PolicyListViewDeleteMutation.graphql";
|
||||
import type { PolicyListViewCreateMutation } from "./__generated__/PolicyListViewCreateMutation.graphql";
|
||||
import type { PolicyListViewSendSigningNotificationsMutation } from "./__generated__/PolicyListViewSendSigningNotificationsMutation.graphql";
|
||||
import { PageTemplate } from "@/components/PageTemplate";
|
||||
import { PolicyListViewSkeleton } from "./PolicyListPage";
|
||||
import {
|
||||
@@ -117,6 +118,13 @@ const createPolicyMutation = graphql`
|
||||
}
|
||||
`;
|
||||
|
||||
const sendSigningNotificationsMutation = graphql`
|
||||
mutation PolicyListViewSendSigningNotificationsMutation($input: SendSigningNotificationsInput!) {
|
||||
sendSigningNotifications(input: $input) {
|
||||
success
|
||||
}
|
||||
}
|
||||
`;
|
||||
function PolicyTableRow({
|
||||
policy,
|
||||
organizationId,
|
||||
@@ -360,7 +368,10 @@ function CreatePolicyModal({
|
||||
className={`text-4xl leading-tight font-bold outline-none focus:outline-none ${!title ? 'text-gray-400' : 'text-black'}`}
|
||||
contentEditable
|
||||
suppressContentEditableWarning
|
||||
onInput={(e) => setTitle(e.currentTarget.textContent || "")}
|
||||
onInput={(e) => {
|
||||
const newText = e.currentTarget.textContent || "";
|
||||
setTitle(newText);
|
||||
}}
|
||||
style={{ WebkitTapHighlightColor: 'transparent' }}
|
||||
onClick={(e) => {
|
||||
if (!title) {
|
||||
@@ -368,12 +379,22 @@ function CreatePolicyModal({
|
||||
}
|
||||
}}
|
||||
onFocus={(e) => {
|
||||
if (!title) {
|
||||
if (e.currentTarget.textContent === "Enter policy title...") {
|
||||
e.currentTarget.textContent = '';
|
||||
}
|
||||
}}
|
||||
onBlur={(e) => {
|
||||
if (!e.currentTarget.textContent?.trim()) {
|
||||
e.currentTarget.textContent = "Enter policy title...";
|
||||
setTitle("");
|
||||
}
|
||||
}}
|
||||
ref={(el) => {
|
||||
if (el && !el.textContent) {
|
||||
el.textContent = title || "Enter policy title...";
|
||||
}
|
||||
}}
|
||||
>
|
||||
{title || "Enter policy title..."}
|
||||
</h1>
|
||||
</div>
|
||||
<Textarea
|
||||
@@ -452,6 +473,8 @@ function PolicyListViewContent({
|
||||
|
||||
const [createModalOpen, setCreateModalOpen] = useState(false);
|
||||
|
||||
const [sendSigningNotifications, _] = useMutation<PolicyListViewSendSigningNotificationsMutation>(sendSigningNotificationsMutation);
|
||||
|
||||
const handleOpenModal = () => {
|
||||
setCreateModalOpen(true);
|
||||
};
|
||||
@@ -460,14 +483,29 @@ function PolicyListViewContent({
|
||||
setCreateModalOpen(open);
|
||||
};
|
||||
|
||||
const handleSendSigningNotifications = () => {
|
||||
sendSigningNotifications({
|
||||
variables: {
|
||||
input: {
|
||||
organizationId: organizationId!,
|
||||
},
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<PageTemplate
|
||||
title="Policies"
|
||||
actions={
|
||||
<Button onClick={handleOpenModal}>
|
||||
<Plus className="mr-2 h-4 w-4" />
|
||||
New policy
|
||||
<div>
|
||||
<Button onClick={handleOpenModal}>
|
||||
<Plus className="mr-2 h-4 w-4" />
|
||||
New policy
|
||||
</Button>
|
||||
<Button onClick={handleSendSigningNotifications}>
|
||||
Send signing notifications
|
||||
</Button>
|
||||
</div>
|
||||
}
|
||||
>
|
||||
{/* Policy table */}
|
||||
|
||||
@@ -0,0 +1,92 @@
|
||||
/**
|
||||
* @generated SignedSource<<ec7d0ef29b8366117ddf75e75669f5fa>>
|
||||
* @lightSyntaxTransform
|
||||
* @nogrep
|
||||
*/
|
||||
|
||||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
// @ts-nocheck
|
||||
|
||||
import { ConcreteRequest } from 'relay-runtime';
|
||||
export type SendSigningNotificationsInput = {
|
||||
organizationId: string;
|
||||
};
|
||||
export type PolicyListViewSendSigningNotificationsMutation$variables = {
|
||||
input: SendSigningNotificationsInput;
|
||||
};
|
||||
export type PolicyListViewSendSigningNotificationsMutation$data = {
|
||||
readonly sendSigningNotifications: {
|
||||
readonly success: boolean;
|
||||
};
|
||||
};
|
||||
export type PolicyListViewSendSigningNotificationsMutation = {
|
||||
response: PolicyListViewSendSigningNotificationsMutation$data;
|
||||
variables: PolicyListViewSendSigningNotificationsMutation$variables;
|
||||
};
|
||||
|
||||
const node: ConcreteRequest = (function(){
|
||||
var v0 = [
|
||||
{
|
||||
"defaultValue": null,
|
||||
"kind": "LocalArgument",
|
||||
"name": "input"
|
||||
}
|
||||
],
|
||||
v1 = [
|
||||
{
|
||||
"alias": null,
|
||||
"args": [
|
||||
{
|
||||
"kind": "Variable",
|
||||
"name": "input",
|
||||
"variableName": "input"
|
||||
}
|
||||
],
|
||||
"concreteType": "SendSigningNotificationsPayload",
|
||||
"kind": "LinkedField",
|
||||
"name": "sendSigningNotifications",
|
||||
"plural": false,
|
||||
"selections": [
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "success",
|
||||
"storageKey": null
|
||||
}
|
||||
],
|
||||
"storageKey": null
|
||||
}
|
||||
];
|
||||
return {
|
||||
"fragment": {
|
||||
"argumentDefinitions": (v0/*: any*/),
|
||||
"kind": "Fragment",
|
||||
"metadata": null,
|
||||
"name": "PolicyListViewSendSigningNotificationsMutation",
|
||||
"selections": (v1/*: any*/),
|
||||
"type": "Mutation",
|
||||
"abstractKey": null
|
||||
},
|
||||
"kind": "Request",
|
||||
"operation": {
|
||||
"argumentDefinitions": (v0/*: any*/),
|
||||
"kind": "Operation",
|
||||
"name": "PolicyListViewSendSigningNotificationsMutation",
|
||||
"selections": (v1/*: any*/)
|
||||
},
|
||||
"params": {
|
||||
"cacheID": "a6b25b9e4abf4243131b90b47f4e2ef4",
|
||||
"id": null,
|
||||
"metadata": {},
|
||||
"name": "PolicyListViewSendSigningNotificationsMutation",
|
||||
"operationKind": "mutation",
|
||||
"text": "mutation PolicyListViewSendSigningNotificationsMutation(\n $input: SendSigningNotificationsInput!\n) {\n sendSigningNotifications(input: $input) {\n success\n }\n}\n"
|
||||
}
|
||||
};
|
||||
})();
|
||||
|
||||
(node as any).hash = "71b0a1e7d209ed71a9bb6552eaf95cee";
|
||||
|
||||
export default node;
|
||||
@@ -293,3 +293,52 @@ WHERE %s
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *Peoples) LoadAwaitingSigning(
|
||||
ctx context.Context,
|
||||
conn pg.Conn,
|
||||
scope Scoper,
|
||||
) error {
|
||||
q := `
|
||||
WITH signatories AS (
|
||||
SELECT
|
||||
signed_by
|
||||
FROM
|
||||
policy_version_signatures
|
||||
WHERE
|
||||
%s
|
||||
AND state = 'REQUESTED'
|
||||
GROUP BY
|
||||
signed_by
|
||||
)
|
||||
SELECT
|
||||
id,
|
||||
organization_id,
|
||||
kind,
|
||||
user_id,
|
||||
full_name,
|
||||
primary_email_address,
|
||||
additional_email_addresses,
|
||||
created_at,
|
||||
updated_at
|
||||
FROM
|
||||
peoples
|
||||
INNER JOIN signatories ON peoples.id = signatories.signed_by
|
||||
`
|
||||
|
||||
q = fmt.Sprintf(q, scope.SQLFragment())
|
||||
|
||||
rows, err := conn.Query(ctx, q, scope.SQLArguments())
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot query people: %w", err)
|
||||
}
|
||||
|
||||
peoples, err := pgx.CollectRows(rows, pgx.RowToAddrOfStructByName[People])
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot collect people: %w", err)
|
||||
}
|
||||
|
||||
*p = peoples
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -53,6 +53,53 @@ func (pvs PolicyVersionSignature) CursorKey(orderBy PolicyVersionSignatureOrderF
|
||||
panic(fmt.Sprintf("unsupported order by: %s", orderBy))
|
||||
}
|
||||
|
||||
func (pvs *PolicyVersionSignature) LoadByPolicyVersionIDAndSignatory(
|
||||
ctx context.Context,
|
||||
conn pg.Conn,
|
||||
scope Scoper,
|
||||
policyVersionID gid.GID,
|
||||
signatory gid.GID,
|
||||
) error {
|
||||
q := `
|
||||
SELECT
|
||||
id,
|
||||
policy_version_id,
|
||||
state,
|
||||
signed_by,
|
||||
signed_at,
|
||||
requested_at,
|
||||
requested_by,
|
||||
created_at,
|
||||
updated_at
|
||||
FROM
|
||||
policy_version_signatures
|
||||
WHERE
|
||||
%s
|
||||
AND policy_version_id = @policy_version_id
|
||||
AND signed_by = @signatory
|
||||
LIMIT 1
|
||||
`
|
||||
|
||||
q = fmt.Sprintf(q, scope.SQLFragment())
|
||||
|
||||
args := pgx.StrictNamedArgs{"policy_version_id": policyVersionID, "signatory": signatory}
|
||||
maps.Copy(args, scope.SQLArguments())
|
||||
|
||||
rows, err := conn.Query(ctx, q, args)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot query policy version signature: %w", err)
|
||||
}
|
||||
|
||||
policyVersionSignature, err := pgx.CollectOneRow(rows, pgx.RowToStructByName[PolicyVersionSignature])
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot collect policy version signature: %w", err)
|
||||
}
|
||||
|
||||
*pvs = policyVersionSignature
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (pvs *PolicyVersionSignature) LoadByID(
|
||||
ctx context.Context,
|
||||
conn pg.Conn,
|
||||
@@ -195,3 +242,40 @@ WHERE
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (pvs *PolicyVersionSignature) Update(
|
||||
ctx context.Context,
|
||||
conn pg.Conn,
|
||||
scope Scoper,
|
||||
) error {
|
||||
q := `
|
||||
UPDATE policy_version_signatures
|
||||
SET
|
||||
state = @state,
|
||||
signed_by = @signed_by,
|
||||
signed_at = @signed_at,
|
||||
updated_at = @updated_at
|
||||
WHERE
|
||||
%s
|
||||
AND id = @id
|
||||
`
|
||||
|
||||
q = fmt.Sprintf(q, scope.SQLFragment())
|
||||
|
||||
args := pgx.StrictNamedArgs{
|
||||
"id": pvs.ID,
|
||||
"state": pvs.State,
|
||||
"signed_by": pvs.SignedBy,
|
||||
"signed_at": pvs.SignedAt,
|
||||
"updated_at": pvs.UpdatedAt,
|
||||
}
|
||||
|
||||
maps.Copy(args, scope.SQLArguments())
|
||||
|
||||
_, err := conn.Exec(ctx, q, args)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot update policy version signature: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -3,19 +3,21 @@ package probo
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"time"
|
||||
|
||||
"github.com/getprobo/probo/pkg/coredata"
|
||||
"github.com/getprobo/probo/pkg/gid"
|
||||
"github.com/getprobo/probo/pkg/page"
|
||||
"github.com/getprobo/probo/pkg/statelesstoken"
|
||||
"go.gearno.de/kit/pg"
|
||||
)
|
||||
|
||||
type PolicyService struct {
|
||||
svc *TenantService
|
||||
}
|
||||
|
||||
type (
|
||||
PolicyService struct {
|
||||
svc *TenantService
|
||||
}
|
||||
|
||||
CreatePolicyRequest struct {
|
||||
OrganizationID gid.GID
|
||||
Title string
|
||||
@@ -34,6 +36,15 @@ type (
|
||||
RequestedBy gid.GID
|
||||
Signatory gid.GID
|
||||
}
|
||||
|
||||
SigningRequestData struct {
|
||||
OrganizationID gid.GID `json:"organization_id"`
|
||||
PeopleID gid.GID `json:"people_id"`
|
||||
}
|
||||
)
|
||||
|
||||
const (
|
||||
TokenTypeSigningRequest = "signing_request"
|
||||
)
|
||||
|
||||
func (s *PolicyService) Get(
|
||||
@@ -163,6 +174,125 @@ func (s *PolicyService) Create(
|
||||
return policy, policyVersion, nil
|
||||
}
|
||||
|
||||
func (s *PolicyService) SendSigningNotifications(
|
||||
ctx context.Context,
|
||||
organizationID gid.GID,
|
||||
) error {
|
||||
err := s.svc.pg.WithTx(
|
||||
ctx,
|
||||
func(tx pg.Conn) error {
|
||||
var peoples coredata.Peoples
|
||||
if err := peoples.LoadAwaitingSigning(ctx, tx, s.svc.scope); err != nil {
|
||||
return fmt.Errorf("cannot load people: %w", err)
|
||||
}
|
||||
|
||||
for _, people := range peoples {
|
||||
now := time.Now()
|
||||
|
||||
emailID, err := gid.NewGID(s.svc.scope.GetTenantID(), coredata.EmailEntityType)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot create email global id: %w", err)
|
||||
}
|
||||
|
||||
token, err := statelesstoken.NewToken(
|
||||
s.svc.tokenSecret,
|
||||
TokenTypeSigningRequest,
|
||||
time.Hour*24*7,
|
||||
SigningRequestData{
|
||||
OrganizationID: organizationID,
|
||||
PeopleID: people.ID,
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot create signing request token: %w", err)
|
||||
}
|
||||
|
||||
signRequestURL := url.URL{
|
||||
Scheme: "https",
|
||||
Host: s.svc.hostname,
|
||||
Path: "/policies/signing-requests",
|
||||
RawQuery: url.Values{
|
||||
"token": []string{token},
|
||||
}.Encode(),
|
||||
}
|
||||
|
||||
email := &coredata.Email{
|
||||
ID: emailID,
|
||||
RecipientEmail: people.PrimaryEmailAddress,
|
||||
RecipientName: people.FullName,
|
||||
Subject: "Probo - Policies Signing Request",
|
||||
TextBody: fmt.Sprintf("Hi,\nYou have documents awaiting your signature. Please follow this link to sign them: %s", signRequestURL.String()),
|
||||
CreatedAt: now,
|
||||
UpdatedAt: now,
|
||||
}
|
||||
|
||||
if err := email.Insert(ctx, tx); err != nil {
|
||||
return fmt.Errorf("cannot insert email: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot send signing notifications: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *PolicyService) SignPolicyVersion(
|
||||
ctx context.Context,
|
||||
policyVersionID gid.GID,
|
||||
signatory gid.GID,
|
||||
) error {
|
||||
policyVersion := &coredata.PolicyVersion{}
|
||||
policyVersionSignature := &coredata.PolicyVersionSignature{}
|
||||
now := time.Now()
|
||||
|
||||
err := s.svc.pg.WithTx(
|
||||
ctx,
|
||||
func(conn pg.Conn) error {
|
||||
if err := policyVersion.LoadByID(ctx, conn, s.svc.scope, policyVersionID); err != nil {
|
||||
return fmt.Errorf("cannot load policy version %q: %w", policyVersionID, err)
|
||||
}
|
||||
|
||||
if policyVersion.Status != coredata.PolicyStatusPublished {
|
||||
return fmt.Errorf("cannot sign unpublished version")
|
||||
}
|
||||
|
||||
if err := policyVersionSignature.LoadByPolicyVersionIDAndSignatory(ctx, conn, s.svc.scope, policyVersionID, signatory); err != nil {
|
||||
return fmt.Errorf("cannot load policy version signature: %w", err)
|
||||
}
|
||||
|
||||
if policyVersionSignature.State == coredata.PolicyVersionSignatureStateSigned {
|
||||
return fmt.Errorf("policy version already signed")
|
||||
}
|
||||
|
||||
policyVersionSignature.State = coredata.PolicyVersionSignatureStateSigned
|
||||
policyVersionSignature.SignedAt = &now
|
||||
policyVersionSignature.UpdatedAt = now
|
||||
|
||||
if err := policyVersion.Update(ctx, conn, s.svc.scope); err != nil {
|
||||
return fmt.Errorf("cannot update policy version: %w", err)
|
||||
}
|
||||
|
||||
if err := policyVersionSignature.Update(ctx, conn, s.svc.scope); err != nil {
|
||||
return fmt.Errorf("cannot update policy version signature: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot sign policy version: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *PolicyService) UpdateVersion(
|
||||
ctx context.Context,
|
||||
req UpdatePolicyVersionRequest,
|
||||
|
||||
@@ -31,15 +31,18 @@ type (
|
||||
s3 *s3.Client
|
||||
bucket string
|
||||
encryptionKey cipher.EncryptionKey
|
||||
hostname string
|
||||
tokenSecret string
|
||||
}
|
||||
|
||||
TenantService struct {
|
||||
pg *pg.Client
|
||||
s3 *s3.Client
|
||||
bucket string
|
||||
encryptionKey cipher.EncryptionKey
|
||||
scope coredata.Scoper
|
||||
|
||||
pg *pg.Client
|
||||
s3 *s3.Client
|
||||
bucket string
|
||||
encryptionKey cipher.EncryptionKey
|
||||
scope coredata.Scoper
|
||||
hostname string
|
||||
tokenSecret string
|
||||
Frameworks *FrameworkService
|
||||
Mesures *MesureService
|
||||
Tasks *TaskService
|
||||
@@ -61,6 +64,8 @@ func NewService(
|
||||
pgClient *pg.Client,
|
||||
s3Client *s3.Client,
|
||||
bucket string,
|
||||
hostname string,
|
||||
tokenSecret string,
|
||||
) (*Service, error) {
|
||||
if bucket == "" {
|
||||
return nil, fmt.Errorf("bucket is required")
|
||||
@@ -71,6 +76,8 @@ func NewService(
|
||||
s3: s3Client,
|
||||
bucket: bucket,
|
||||
encryptionKey: encryptionKey,
|
||||
hostname: hostname,
|
||||
tokenSecret: tokenSecret,
|
||||
}
|
||||
|
||||
return svc, nil
|
||||
@@ -82,7 +89,9 @@ func (s *Service) WithTenant(tenantID gid.TenantID) *TenantService {
|
||||
s3: s.s3,
|
||||
bucket: s.bucket,
|
||||
encryptionKey: s.encryptionKey,
|
||||
hostname: s.hostname,
|
||||
scope: coredata.NewScope(tenantID),
|
||||
tokenSecret: s.tokenSecret,
|
||||
}
|
||||
|
||||
tenantService.Frameworks = &FrameworkService{svc: tenantService}
|
||||
|
||||
@@ -197,7 +197,15 @@ func (impl *Implm) Run(
|
||||
return fmt.Errorf("cannot create usrmgr service: %w", err)
|
||||
}
|
||||
|
||||
proboService, err := probo.NewService(ctx, impl.cfg.EncryptionKey, pgClient, s3Client, impl.cfg.AWS.Bucket)
|
||||
proboService, err := probo.NewService(
|
||||
ctx,
|
||||
impl.cfg.EncryptionKey,
|
||||
pgClient,
|
||||
s3Client,
|
||||
impl.cfg.AWS.Bucket,
|
||||
impl.cfg.Hostname,
|
||||
impl.cfg.Auth.Cookie.Secret,
|
||||
)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot create probo service: %w", err)
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@ directive @goModel(
|
||||
|
||||
directive @goEnum(value: String) on ENUM_VALUE
|
||||
|
||||
|
||||
# Scalars
|
||||
scalar CursorKey
|
||||
scalar Void
|
||||
@@ -1006,6 +1007,7 @@ type Mutation {
|
||||
createDraftPolicyVersion(input: CreateDraftPolicyVersionInput!): CreateDraftPolicyVersionPayload!
|
||||
updatePolicyVersion(input: UpdatePolicyVersionInput!): UpdatePolicyVersionPayload!
|
||||
requestSignature(input: RequestSignatureInput!): RequestSignaturePayload!
|
||||
sendSigningNotifications(input: SendSigningNotificationsInput!): SendSigningNotificationsPayload!
|
||||
|
||||
createVendorRiskAssessment(input: CreateVendorRiskAssessmentInput!): CreateVendorRiskAssessmentPayload!
|
||||
}
|
||||
@@ -1648,3 +1650,11 @@ input UpdatePolicyVersionInput {
|
||||
type UpdatePolicyVersionPayload {
|
||||
policyVersion: PolicyVersion!
|
||||
}
|
||||
|
||||
input SendSigningNotificationsInput {
|
||||
organizationId: ID!
|
||||
}
|
||||
|
||||
type SendSigningNotificationsPayload {
|
||||
success: Boolean!
|
||||
}
|
||||
@@ -351,6 +351,7 @@ type ComplexityRoot struct {
|
||||
RemoveUser func(childComplexity int, input types.RemoveUserInput) int
|
||||
RequestEvidence func(childComplexity int, input types.RequestEvidenceInput) int
|
||||
RequestSignature func(childComplexity int, input types.RequestSignatureInput) int
|
||||
SendSigningNotifications func(childComplexity int, input types.SendSigningNotificationsInput) int
|
||||
UnassignTask func(childComplexity int, input types.UnassignTaskInput) int
|
||||
UpdateFramework func(childComplexity int, input types.UpdateFrameworkInput) int
|
||||
UpdateMesure func(childComplexity int, input types.UpdateMesureInput) int
|
||||
@@ -537,6 +538,10 @@ type ComplexityRoot struct {
|
||||
Node func(childComplexity int) int
|
||||
}
|
||||
|
||||
SendSigningNotificationsPayload struct {
|
||||
Success func(childComplexity int) int
|
||||
}
|
||||
|
||||
Session struct {
|
||||
ExpiresAt func(childComplexity int) int
|
||||
ID func(childComplexity int) int
|
||||
@@ -778,6 +783,7 @@ type MutationResolver interface {
|
||||
CreateDraftPolicyVersion(ctx context.Context, input types.CreateDraftPolicyVersionInput) (*types.CreateDraftPolicyVersionPayload, error)
|
||||
UpdatePolicyVersion(ctx context.Context, input types.UpdatePolicyVersionInput) (*types.UpdatePolicyVersionPayload, error)
|
||||
RequestSignature(ctx context.Context, input types.RequestSignatureInput) (*types.RequestSignaturePayload, error)
|
||||
SendSigningNotifications(ctx context.Context, input types.SendSigningNotificationsInput) (*types.SendSigningNotificationsPayload, error)
|
||||
CreateVendorRiskAssessment(ctx context.Context, input types.CreateVendorRiskAssessmentInput) (*types.CreateVendorRiskAssessmentPayload, error)
|
||||
}
|
||||
type OrganizationResolver interface {
|
||||
@@ -2035,6 +2041,18 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in
|
||||
|
||||
return e.complexity.Mutation.RequestSignature(childComplexity, args["input"].(types.RequestSignatureInput)), true
|
||||
|
||||
case "Mutation.sendSigningNotifications":
|
||||
if e.complexity.Mutation.SendSigningNotifications == nil {
|
||||
break
|
||||
}
|
||||
|
||||
args, err := ec.field_Mutation_sendSigningNotifications_args(context.TODO(), rawArgs)
|
||||
if err != nil {
|
||||
return 0, false
|
||||
}
|
||||
|
||||
return e.complexity.Mutation.SendSigningNotifications(childComplexity, args["input"].(types.SendSigningNotificationsInput)), true
|
||||
|
||||
case "Mutation.unassignTask":
|
||||
if e.complexity.Mutation.UnassignTask == nil {
|
||||
break
|
||||
@@ -2944,6 +2962,13 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in
|
||||
|
||||
return e.complexity.RiskEdge.Node(childComplexity), true
|
||||
|
||||
case "SendSigningNotificationsPayload.success":
|
||||
if e.complexity.SendSigningNotificationsPayload.Success == nil {
|
||||
break
|
||||
}
|
||||
|
||||
return e.complexity.SendSigningNotificationsPayload.Success(childComplexity), true
|
||||
|
||||
case "Session.expiresAt":
|
||||
if e.complexity.Session.ExpiresAt == nil {
|
||||
break
|
||||
@@ -3672,6 +3697,7 @@ func (e *executableSchema) Exec(ctx context.Context) graphql.ResponseHandler {
|
||||
ec.unmarshalInputRequestEvidenceInput,
|
||||
ec.unmarshalInputRequestSignatureInput,
|
||||
ec.unmarshalInputRiskOrder,
|
||||
ec.unmarshalInputSendSigningNotificationsInput,
|
||||
ec.unmarshalInputTaskOrder,
|
||||
ec.unmarshalInputUnassignTaskInput,
|
||||
ec.unmarshalInputUpdateFrameworkInput,
|
||||
@@ -3799,6 +3825,7 @@ directive @goModel(
|
||||
|
||||
directive @goEnum(value: String) on ENUM_VALUE
|
||||
|
||||
|
||||
# Scalars
|
||||
scalar CursorKey
|
||||
scalar Void
|
||||
@@ -4793,6 +4820,7 @@ type Mutation {
|
||||
createDraftPolicyVersion(input: CreateDraftPolicyVersionInput!): CreateDraftPolicyVersionPayload!
|
||||
updatePolicyVersion(input: UpdatePolicyVersionInput!): UpdatePolicyVersionPayload!
|
||||
requestSignature(input: RequestSignatureInput!): RequestSignaturePayload!
|
||||
sendSigningNotifications(input: SendSigningNotificationsInput!): SendSigningNotificationsPayload!
|
||||
|
||||
createVendorRiskAssessment(input: CreateVendorRiskAssessmentInput!): CreateVendorRiskAssessmentPayload!
|
||||
}
|
||||
@@ -5435,7 +5463,14 @@ input UpdatePolicyVersionInput {
|
||||
type UpdatePolicyVersionPayload {
|
||||
policyVersion: PolicyVersion!
|
||||
}
|
||||
`, BuiltIn: false},
|
||||
|
||||
input SendSigningNotificationsInput {
|
||||
organizationId: ID!
|
||||
}
|
||||
|
||||
type SendSigningNotificationsPayload {
|
||||
success: Boolean!
|
||||
}`, BuiltIn: false},
|
||||
}
|
||||
var parsedSchema = gqlparser.MustLoadSchema(sources...)
|
||||
|
||||
@@ -6910,6 +6945,29 @@ func (ec *executionContext) field_Mutation_requestSignature_argsInput(
|
||||
return zeroVal, nil
|
||||
}
|
||||
|
||||
func (ec *executionContext) field_Mutation_sendSigningNotifications_args(ctx context.Context, rawArgs map[string]any) (map[string]any, error) {
|
||||
var err error
|
||||
args := map[string]any{}
|
||||
arg0, err := ec.field_Mutation_sendSigningNotifications_argsInput(ctx, rawArgs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
args["input"] = arg0
|
||||
return args, nil
|
||||
}
|
||||
func (ec *executionContext) field_Mutation_sendSigningNotifications_argsInput(
|
||||
ctx context.Context,
|
||||
rawArgs map[string]any,
|
||||
) (types.SendSigningNotificationsInput, error) {
|
||||
ctx = graphql.WithPathContext(ctx, graphql.NewPathWithField("input"))
|
||||
if tmp, ok := rawArgs["input"]; ok {
|
||||
return ec.unmarshalNSendSigningNotificationsInput2githubᚗcomᚋgetproboᚋproboᚋpkgᚋserverᚋapiᚋconsoleᚋv1ᚋtypesᚐSendSigningNotificationsInput(ctx, tmp)
|
||||
}
|
||||
|
||||
var zeroVal types.SendSigningNotificationsInput
|
||||
return zeroVal, nil
|
||||
}
|
||||
|
||||
func (ec *executionContext) field_Mutation_unassignTask_args(ctx context.Context, rawArgs map[string]any) (map[string]any, error) {
|
||||
var err error
|
||||
args := map[string]any{}
|
||||
@@ -16473,6 +16531,65 @@ func (ec *executionContext) fieldContext_Mutation_requestSignature(ctx context.C
|
||||
return fc, nil
|
||||
}
|
||||
|
||||
func (ec *executionContext) _Mutation_sendSigningNotifications(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) {
|
||||
fc, err := ec.fieldContext_Mutation_sendSigningNotifications(ctx, field)
|
||||
if err != nil {
|
||||
return graphql.Null
|
||||
}
|
||||
ctx = graphql.WithFieldContext(ctx, fc)
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
ec.Error(ctx, ec.Recover(ctx, r))
|
||||
ret = graphql.Null
|
||||
}
|
||||
}()
|
||||
resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (any, error) {
|
||||
ctx = rctx // use context from middleware stack in children
|
||||
return ec.resolvers.Mutation().SendSigningNotifications(rctx, fc.Args["input"].(types.SendSigningNotificationsInput))
|
||||
})
|
||||
if err != nil {
|
||||
ec.Error(ctx, err)
|
||||
return graphql.Null
|
||||
}
|
||||
if resTmp == nil {
|
||||
if !graphql.HasFieldError(ctx, fc) {
|
||||
ec.Errorf(ctx, "must not be null")
|
||||
}
|
||||
return graphql.Null
|
||||
}
|
||||
res := resTmp.(*types.SendSigningNotificationsPayload)
|
||||
fc.Result = res
|
||||
return ec.marshalNSendSigningNotificationsPayload2ᚖgithubᚗcomᚋgetproboᚋproboᚋpkgᚋserverᚋapiᚋconsoleᚋv1ᚋtypesᚐSendSigningNotificationsPayload(ctx, field.Selections, res)
|
||||
}
|
||||
|
||||
func (ec *executionContext) fieldContext_Mutation_sendSigningNotifications(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) {
|
||||
fc = &graphql.FieldContext{
|
||||
Object: "Mutation",
|
||||
Field: field,
|
||||
IsMethod: true,
|
||||
IsResolver: true,
|
||||
Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) {
|
||||
switch field.Name {
|
||||
case "success":
|
||||
return ec.fieldContext_SendSigningNotificationsPayload_success(ctx, field)
|
||||
}
|
||||
return nil, fmt.Errorf("no field named %q was found under type SendSigningNotificationsPayload", field.Name)
|
||||
},
|
||||
}
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
err = ec.Recover(ctx, r)
|
||||
ec.Error(ctx, err)
|
||||
}
|
||||
}()
|
||||
ctx = graphql.WithFieldContext(ctx, fc)
|
||||
if fc.Args, err = ec.field_Mutation_sendSigningNotifications_args(ctx, field.ArgumentMap(ec.Variables)); err != nil {
|
||||
ec.Error(ctx, err)
|
||||
return fc, err
|
||||
}
|
||||
return fc, nil
|
||||
}
|
||||
|
||||
func (ec *executionContext) _Mutation_createVendorRiskAssessment(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) {
|
||||
fc, err := ec.fieldContext_Mutation_createVendorRiskAssessment(ctx, field)
|
||||
if err != nil {
|
||||
@@ -21806,6 +21923,50 @@ func (ec *executionContext) fieldContext_RiskEdge_node(_ context.Context, field
|
||||
return fc, nil
|
||||
}
|
||||
|
||||
func (ec *executionContext) _SendSigningNotificationsPayload_success(ctx context.Context, field graphql.CollectedField, obj *types.SendSigningNotificationsPayload) (ret graphql.Marshaler) {
|
||||
fc, err := ec.fieldContext_SendSigningNotificationsPayload_success(ctx, field)
|
||||
if err != nil {
|
||||
return graphql.Null
|
||||
}
|
||||
ctx = graphql.WithFieldContext(ctx, fc)
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
ec.Error(ctx, ec.Recover(ctx, r))
|
||||
ret = graphql.Null
|
||||
}
|
||||
}()
|
||||
resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (any, error) {
|
||||
ctx = rctx // use context from middleware stack in children
|
||||
return obj.Success, nil
|
||||
})
|
||||
if err != nil {
|
||||
ec.Error(ctx, err)
|
||||
return graphql.Null
|
||||
}
|
||||
if resTmp == nil {
|
||||
if !graphql.HasFieldError(ctx, fc) {
|
||||
ec.Errorf(ctx, "must not be null")
|
||||
}
|
||||
return graphql.Null
|
||||
}
|
||||
res := resTmp.(bool)
|
||||
fc.Result = res
|
||||
return ec.marshalNBoolean2bool(ctx, field.Selections, res)
|
||||
}
|
||||
|
||||
func (ec *executionContext) fieldContext_SendSigningNotificationsPayload_success(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) {
|
||||
fc = &graphql.FieldContext{
|
||||
Object: "SendSigningNotificationsPayload",
|
||||
Field: field,
|
||||
IsMethod: false,
|
||||
IsResolver: false,
|
||||
Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) {
|
||||
return nil, errors.New("field of type Boolean does not have child fields")
|
||||
},
|
||||
}
|
||||
return fc, nil
|
||||
}
|
||||
|
||||
func (ec *executionContext) _Session_id(ctx context.Context, field graphql.CollectedField, obj *types.Session) (ret graphql.Marshaler) {
|
||||
fc, err := ec.fieldContext_Session_id(ctx, field)
|
||||
if err != nil {
|
||||
@@ -30448,6 +30609,33 @@ func (ec *executionContext) unmarshalInputRiskOrder(ctx context.Context, obj any
|
||||
return it, nil
|
||||
}
|
||||
|
||||
func (ec *executionContext) unmarshalInputSendSigningNotificationsInput(ctx context.Context, obj any) (types.SendSigningNotificationsInput, error) {
|
||||
var it types.SendSigningNotificationsInput
|
||||
asMap := map[string]any{}
|
||||
for k, v := range obj.(map[string]any) {
|
||||
asMap[k] = v
|
||||
}
|
||||
|
||||
fieldsInOrder := [...]string{"organizationId"}
|
||||
for _, k := range fieldsInOrder {
|
||||
v, ok := asMap[k]
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
switch k {
|
||||
case "organizationId":
|
||||
ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("organizationId"))
|
||||
data, err := ec.unmarshalNID2githubᚗcomᚋgetproboᚋproboᚋpkgᚋgidᚐGID(ctx, v)
|
||||
if err != nil {
|
||||
return it, err
|
||||
}
|
||||
it.OrganizationID = data
|
||||
}
|
||||
}
|
||||
|
||||
return it, nil
|
||||
}
|
||||
|
||||
func (ec *executionContext) unmarshalInputTaskOrder(ctx context.Context, obj any) (types.TaskOrderBy, error) {
|
||||
var it types.TaskOrderBy
|
||||
asMap := map[string]any{}
|
||||
@@ -34161,6 +34349,13 @@ func (ec *executionContext) _Mutation(ctx context.Context, sel ast.SelectionSet)
|
||||
if out.Values[i] == graphql.Null {
|
||||
out.Invalids++
|
||||
}
|
||||
case "sendSigningNotifications":
|
||||
out.Values[i] = ec.OperationContext.RootResolverMiddleware(innerCtx, func(ctx context.Context) (res graphql.Marshaler) {
|
||||
return ec._Mutation_sendSigningNotifications(ctx, field)
|
||||
})
|
||||
if out.Values[i] == graphql.Null {
|
||||
out.Invalids++
|
||||
}
|
||||
case "createVendorRiskAssessment":
|
||||
out.Values[i] = ec.OperationContext.RootResolverMiddleware(innerCtx, func(ctx context.Context) (res graphql.Marshaler) {
|
||||
return ec._Mutation_createVendorRiskAssessment(ctx, field)
|
||||
@@ -36225,6 +36420,45 @@ func (ec *executionContext) _RiskEdge(ctx context.Context, sel ast.SelectionSet,
|
||||
return out
|
||||
}
|
||||
|
||||
var sendSigningNotificationsPayloadImplementors = []string{"SendSigningNotificationsPayload"}
|
||||
|
||||
func (ec *executionContext) _SendSigningNotificationsPayload(ctx context.Context, sel ast.SelectionSet, obj *types.SendSigningNotificationsPayload) graphql.Marshaler {
|
||||
fields := graphql.CollectFields(ec.OperationContext, sel, sendSigningNotificationsPayloadImplementors)
|
||||
|
||||
out := graphql.NewFieldSet(fields)
|
||||
deferred := make(map[string]*graphql.FieldSet)
|
||||
for i, field := range fields {
|
||||
switch field.Name {
|
||||
case "__typename":
|
||||
out.Values[i] = graphql.MarshalString("SendSigningNotificationsPayload")
|
||||
case "success":
|
||||
out.Values[i] = ec._SendSigningNotificationsPayload_success(ctx, field, obj)
|
||||
if out.Values[i] == graphql.Null {
|
||||
out.Invalids++
|
||||
}
|
||||
default:
|
||||
panic("unknown field " + strconv.Quote(field.Name))
|
||||
}
|
||||
}
|
||||
out.Dispatch(ctx)
|
||||
if out.Invalids > 0 {
|
||||
return graphql.Null
|
||||
}
|
||||
|
||||
atomic.AddInt32(&ec.deferred, int32(len(deferred)))
|
||||
|
||||
for label, dfs := range deferred {
|
||||
ec.processDeferredGroup(graphql.DeferredGroup{
|
||||
Label: label,
|
||||
Path: graphql.GetPath(ctx),
|
||||
FieldSet: dfs,
|
||||
Context: ctx,
|
||||
})
|
||||
}
|
||||
|
||||
return out
|
||||
}
|
||||
|
||||
var sessionImplementors = []string{"Session"}
|
||||
|
||||
func (ec *executionContext) _Session(ctx context.Context, sel ast.SelectionSet, obj *types.Session) graphql.Marshaler {
|
||||
@@ -40544,6 +40778,25 @@ var (
|
||||
}
|
||||
)
|
||||
|
||||
func (ec *executionContext) unmarshalNSendSigningNotificationsInput2githubᚗcomᚋgetproboᚋproboᚋpkgᚋserverᚋapiᚋconsoleᚋv1ᚋtypesᚐSendSigningNotificationsInput(ctx context.Context, v any) (types.SendSigningNotificationsInput, error) {
|
||||
res, err := ec.unmarshalInputSendSigningNotificationsInput(ctx, v)
|
||||
return res, graphql.ErrorOnPath(ctx, err)
|
||||
}
|
||||
|
||||
func (ec *executionContext) marshalNSendSigningNotificationsPayload2githubᚗcomᚋgetproboᚋproboᚋpkgᚋserverᚋapiᚋconsoleᚋv1ᚋtypesᚐSendSigningNotificationsPayload(ctx context.Context, sel ast.SelectionSet, v types.SendSigningNotificationsPayload) graphql.Marshaler {
|
||||
return ec._SendSigningNotificationsPayload(ctx, sel, &v)
|
||||
}
|
||||
|
||||
func (ec *executionContext) marshalNSendSigningNotificationsPayload2ᚖgithubᚗcomᚋgetproboᚋproboᚋpkgᚋserverᚋapiᚋconsoleᚋv1ᚋtypesᚐSendSigningNotificationsPayload(ctx context.Context, sel ast.SelectionSet, v *types.SendSigningNotificationsPayload) graphql.Marshaler {
|
||||
if v == nil {
|
||||
if !graphql.HasFieldError(ctx, graphql.GetFieldContext(ctx)) {
|
||||
ec.Errorf(ctx, "the requested element is null which the schema does not allow")
|
||||
}
|
||||
return graphql.Null
|
||||
}
|
||||
return ec._SendSigningNotificationsPayload(ctx, sel, v)
|
||||
}
|
||||
|
||||
func (ec *executionContext) unmarshalNString2string(ctx context.Context, v any) (string, error) {
|
||||
res, err := graphql.UnmarshalString(v)
|
||||
return res, graphql.ErrorOnPath(ctx, err)
|
||||
|
||||
@@ -723,6 +723,14 @@ type RiskEdge struct {
|
||||
Node *Risk `json:"node"`
|
||||
}
|
||||
|
||||
type SendSigningNotificationsInput struct {
|
||||
OrganizationID gid.GID `json:"organizationId"`
|
||||
}
|
||||
|
||||
type SendSigningNotificationsPayload struct {
|
||||
Success bool `json:"success"`
|
||||
}
|
||||
|
||||
type Session struct {
|
||||
ID gid.GID `json:"id"`
|
||||
ExpiresAt time.Time `json:"expiresAt"`
|
||||
|
||||
@@ -1134,6 +1134,20 @@ func (r *mutationResolver) RequestSignature(ctx context.Context, input types.Req
|
||||
}, nil
|
||||
}
|
||||
|
||||
// SendSigningNotifications is the resolver for the sendSigningNotifications field.
|
||||
func (r *mutationResolver) SendSigningNotifications(ctx context.Context, input types.SendSigningNotificationsInput) (*types.SendSigningNotificationsPayload, error) {
|
||||
svc := GetTenantService(ctx, r.proboSvc, input.OrganizationID.TenantID())
|
||||
|
||||
err := svc.Policies.SendSigningNotifications(ctx, input.OrganizationID)
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("cannot send signing notifications: %w", err))
|
||||
}
|
||||
|
||||
return &types.SendSigningNotificationsPayload{
|
||||
Success: true,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// CreateVendorRiskAssessment is the resolver for the createVendorRiskAssessment field.
|
||||
func (r *mutationResolver) CreateVendorRiskAssessment(ctx context.Context, input types.CreateVendorRiskAssessmentInput) (*types.CreateVendorRiskAssessmentPayload, error) {
|
||||
svc := GetTenantService(ctx, r.proboSvc, input.VendorID.TenantID())
|
||||
|
||||
Reference in New Issue
Block a user