Add company name and logo to documents

Signed-off-by: Sacha Al Himdani <sacha@getprobo.com>
This commit is contained in:
Sacha Al Himdani
2025-10-08 16:04:27 +02:00
parent 9bdecdf232
commit d757c9604b
23 changed files with 1391 additions and 203 deletions

View File

@@ -1,5 +1,5 @@
/** /**
* @generated SignedSource<<267b2fc05603188ad26c38ce1485887b>> * @generated SignedSource<<5a41cd3709282172e4d5a3ca65d07b96>>
* @lightSyntaxTransform * @lightSyntaxTransform
* @nogrep * @nogrep
*/ */
@@ -151,6 +151,13 @@ return {
"name": "logoUrl", "name": "logoUrl",
"storageKey": null "storageKey": null
}, },
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "horizontalLogoUrl",
"storageKey": null
},
{ {
"alias": null, "alias": null,
"args": null, "args": null,
@@ -338,12 +345,12 @@ return {
] ]
}, },
"params": { "params": {
"cacheID": "5118b2778835b232009a9d5ef5647aba", "cacheID": "8b9e1f3b1e93e1823354e77f10764e54",
"id": null, "id": null,
"metadata": {}, "metadata": {},
"name": "OrganizationGraph_ViewQuery", "name": "OrganizationGraph_ViewQuery",
"operationKind": "query", "operationKind": "query",
"text": "query OrganizationGraph_ViewQuery(\n $organizationId: ID!\n) {\n node(id: $organizationId) {\n __typename\n ... on Organization {\n id\n name\n ...SettingsPageFragment\n }\n id\n }\n}\n\nfragment SettingsPageFragment on Organization {\n id\n name\n logoUrl\n description\n websiteUrl\n email\n headquarterAddress\n customDomain {\n id\n domain\n sslStatus\n dnsRecords {\n type\n name\n value\n ttl\n purpose\n }\n createdAt\n updatedAt\n sslExpiresAt\n }\n users(first: 100) {\n edges {\n node {\n id\n fullName\n email\n createdAt\n }\n }\n }\n connectors(first: 100) {\n edges {\n node {\n id\n name\n type\n createdAt\n }\n }\n }\n}\n" "text": "query OrganizationGraph_ViewQuery(\n $organizationId: ID!\n) {\n node(id: $organizationId) {\n __typename\n ... on Organization {\n id\n name\n ...SettingsPageFragment\n }\n id\n }\n}\n\nfragment SettingsPageFragment on Organization {\n id\n name\n logoUrl\n horizontalLogoUrl\n description\n websiteUrl\n email\n headquarterAddress\n customDomain {\n id\n domain\n sslStatus\n dnsRecords {\n type\n name\n value\n ttl\n purpose\n }\n createdAt\n updatedAt\n sslExpiresAt\n }\n users(first: 100) {\n edges {\n node {\n id\n fullName\n email\n createdAt\n }\n }\n }\n connectors(first: 100) {\n edges {\n node {\n id\n name\n type\n createdAt\n }\n }\n }\n}\n"
} }
}; };
})(); })();

View File

@@ -4,6 +4,9 @@ import {
Badge, Badge,
Button, Button,
Card, Card,
Dialog,
DialogContent,
DialogFooter,
DropdownItem, DropdownItem,
Field, Field,
FileButton, FileButton,
@@ -13,6 +16,7 @@ import {
Spinner, Spinner,
Textarea, Textarea,
useConfirm, useConfirm,
useDialogRef,
useToast, useToast,
} from "@probo/ui"; } from "@probo/ui";
import { useTranslate } from "@probo/i18n"; import { useTranslate } from "@probo/i18n";
@@ -58,6 +62,7 @@ const organizationFragment = graphql`
id id
name name
logoUrl logoUrl
horizontalLogoUrl
description description
websiteUrl websiteUrl
email email
@@ -107,6 +112,7 @@ const updateOrganizationMutation = graphql`
id id
name name
logoUrl logoUrl
horizontalLogoUrl
description description
websiteUrl websiteUrl
email email
@@ -116,6 +122,17 @@ const updateOrganizationMutation = graphql`
} }
`; `;
const deleteHorizontalLogoMutation = graphql`
mutation SettingsPage_DeleteHorizontalLogoMutation($input: DeleteOrganizationHorizontalLogoInput!) {
deleteOrganizationHorizontalLogo(input: $input) {
organization {
id
horizontalLogoUrl
}
}
}
`;
export default function SettingsPage({ queryRef }: Props) { export default function SettingsPage({ queryRef }: Props) {
const { __ } = useTranslate(); const { __ } = useTranslate();
const navigate = useNavigate(); const navigate = useNavigate();
@@ -129,6 +146,13 @@ export default function SettingsPage({ queryRef }: Props) {
organizationKey organizationKey
); );
const [updateOrganization] = useMutation(updateOrganizationMutation); const [updateOrganization] = useMutation(updateOrganizationMutation);
const [deleteHorizontalLogo, isDeletingHorizontalLogo] = useMutationWithToasts(
deleteHorizontalLogoMutation,
{
successMessage: __("Horizontal logo deleted successfully"),
errorMessage: __("Failed to delete horizontal logo"),
}
);
const [deleteOrganization, isDeleting] = useDeleteOrganizationMutation(); const [deleteOrganization, isDeleting] = useDeleteOrganizationMutation();
const users = organization.users.edges.map((edge) => edge.node); const users = organization.users.edges.map((edge) => edge.node);
@@ -167,10 +191,10 @@ export default function SettingsPage({ queryRef }: Props) {
headquarterAddress: data.headquarterAddress || null, headquarterAddress: data.headquarterAddress || null,
}, },
}, },
onError(error) { onError() {
toast({ toast({
title: __("Failed to update organization"), title: __("Error"),
description: error.message || __("Please try again."), description: __("Failed to update organization."),
variant: "error", variant: "error",
}); });
}, },
@@ -201,13 +225,67 @@ export default function SettingsPage({ queryRef }: Props) {
uploadables: { uploadables: {
"input.logo": file, "input.logo": file,
}, },
onError(error) { onError() {
toast({ toast({
title: __("Failed to update organization logo"), title: __("Error"),
description: error.message || __("Please try again."), description: __("Failed to update logo"),
variant: "error", variant: "error",
}); });
}, },
onCompleted() {
toast({
title: __("Success"),
description: __("Your organization logo has been updated successfully."),
variant: "success",
});
},
});
};
const updateHorizontalLogo: ChangeEventHandler<HTMLInputElement> = (e) => {
const file = e.target.files?.[0];
if (!file) {
return;
}
updateOrganization({
variables: {
input: {
organizationId: organization.id,
horizontalLogoFile: null,
},
},
uploadables: {
"input.horizontalLogoFile": file,
},
onError() {
toast({
title: __("Error"),
description: __("Failed to update horizontal logo."),
variant: "error",
});
},
onCompleted() {
toast({
title: __("Success"),
description: __("Your organization horizontal logo has been updated successfully."),
variant: "success",
});
},
});
};
const deleteDialogRef = useDialogRef();
const handleDeleteHorizontalLogo = () => {
deleteHorizontalLogo({
variables: {
input: {
organizationId: organization.id,
},
},
onSuccess: () => {
deleteDialogRef.current?.close();
},
}); });
}; };
@@ -252,11 +330,72 @@ export default function SettingsPage({ queryRef }: Props) {
onChange={updateOrganizationLogo} onChange={updateOrganizationLogo}
variant="secondary" variant="secondary"
className="ml-auto" className="ml-auto"
accept="image/png,image/jpeg,image/jpg"
> >
{__("Change logo")} {__("Change logo")}
</FileButton> </FileButton>
</div> </div>
</div> </div>
<div>
<Label>{__("Horizontal logo")}</Label>
<p className="text-sm text-txt-tertiary mb-2">
{__("Upload a horizontal version of your logo for use in documents")}
</p>
<div className="flex items-center gap-4">
{organization.horizontalLogoUrl && (
<div className="border border-border-solid rounded-md p-4 bg-surface-secondary">
<img
src={organization.horizontalLogoUrl}
alt={__("Horizontal logo")}
className="h-12 max-w-xs object-contain"
/>
</div>
)}
<FileButton
disabled={formState.isSubmitting}
onChange={updateHorizontalLogo}
variant="secondary"
accept="image/png,image/jpeg,image/jpg"
>
{organization.horizontalLogoUrl ? __("Change horizontal logo") : __("Upload horizontal logo")}
</FileButton>
{organization.horizontalLogoUrl && (
<Dialog
ref={deleteDialogRef}
trigger={
<Button
variant="quaternary"
icon={IconTrashCan}
aria-label={__("Delete horizontal logo")}
className="text-red-600 hover:text-red-700"
/>
}
title={__("Delete Horizontal Logo")}
className="max-w-md"
>
<DialogContent padded>
<p className="text-txt-secondary">
{__("Are you sure you want to delete the horizontal logo?")}
</p>
<p className="text-txt-secondary mt-2">
{__("This action cannot be undone.")}
</p>
</DialogContent>
<DialogFooter>
<Button
variant="danger"
onClick={handleDeleteHorizontalLogo}
disabled={isDeletingHorizontalLogo}
icon={isDeletingHorizontalLogo ? Spinner : IconTrashCan}
>
{isDeletingHorizontalLogo ? __("Deleting...") : __("Delete")}
</Button>
</DialogFooter>
</Dialog>
)}
</div>
</div>
<Field <Field
{...register("name")} {...register("name")}
readOnly={formState.isSubmitting} readOnly={formState.isSubmitting}

View File

@@ -1,5 +1,5 @@
/** /**
* @generated SignedSource<<daff4e27d1bdd110777f99826abd4e17>> * @generated SignedSource<<122f3da12674107565d07159872492db>>
* @lightSyntaxTransform * @lightSyntaxTransform
* @nogrep * @nogrep
*/ */
@@ -40,6 +40,7 @@ export type SettingsPageFragment$data = {
readonly description: string | null | undefined; readonly description: string | null | undefined;
readonly email: string | null | undefined; readonly email: string | null | undefined;
readonly headquarterAddress: string | null | undefined; readonly headquarterAddress: string | null | undefined;
readonly horizontalLogoUrl: string | null | undefined;
readonly id: string; readonly id: string;
readonly logoUrl: string | null | undefined; readonly logoUrl: string | null | undefined;
readonly name: string; readonly name: string;
@@ -119,6 +120,13 @@ return {
"name": "logoUrl", "name": "logoUrl",
"storageKey": null "storageKey": null
}, },
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "horizontalLogoUrl",
"storageKey": null
},
{ {
"alias": null, "alias": null,
"args": null, "args": null,
@@ -302,6 +310,6 @@ return {
}; };
})(); })();
(node as any).hash = "1f76d60a35b4b5116821419432ec4aed"; (node as any).hash = "6cea6f88fb0d7b2ae7ef9053b9979898";
export default node; export default node;

View File

@@ -0,0 +1,113 @@
/**
* @generated SignedSource<<4ed3d530d746d8b84b2dba8752e57abc>>
* @lightSyntaxTransform
* @nogrep
*/
/* tslint:disable */
/* eslint-disable */
// @ts-nocheck
import { ConcreteRequest } from 'relay-runtime';
export type DeleteOrganizationHorizontalLogoInput = {
organizationId: string;
};
export type SettingsPage_DeleteHorizontalLogoMutation$variables = {
input: DeleteOrganizationHorizontalLogoInput;
};
export type SettingsPage_DeleteHorizontalLogoMutation$data = {
readonly deleteOrganizationHorizontalLogo: {
readonly organization: {
readonly horizontalLogoUrl: string | null | undefined;
readonly id: string;
};
};
};
export type SettingsPage_DeleteHorizontalLogoMutation = {
response: SettingsPage_DeleteHorizontalLogoMutation$data;
variables: SettingsPage_DeleteHorizontalLogoMutation$variables;
};
const node: ConcreteRequest = (function(){
var v0 = [
{
"defaultValue": null,
"kind": "LocalArgument",
"name": "input"
}
],
v1 = [
{
"alias": null,
"args": [
{
"kind": "Variable",
"name": "input",
"variableName": "input"
}
],
"concreteType": "DeleteOrganizationHorizontalLogoPayload",
"kind": "LinkedField",
"name": "deleteOrganizationHorizontalLogo",
"plural": false,
"selections": [
{
"alias": null,
"args": null,
"concreteType": "Organization",
"kind": "LinkedField",
"name": "organization",
"plural": false,
"selections": [
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "id",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "horizontalLogoUrl",
"storageKey": null
}
],
"storageKey": null
}
],
"storageKey": null
}
];
return {
"fragment": {
"argumentDefinitions": (v0/*: any*/),
"kind": "Fragment",
"metadata": null,
"name": "SettingsPage_DeleteHorizontalLogoMutation",
"selections": (v1/*: any*/),
"type": "Mutation",
"abstractKey": null
},
"kind": "Request",
"operation": {
"argumentDefinitions": (v0/*: any*/),
"kind": "Operation",
"name": "SettingsPage_DeleteHorizontalLogoMutation",
"selections": (v1/*: any*/)
},
"params": {
"cacheID": "e631480d50c9347050fdc62075a2e3a3",
"id": null,
"metadata": {},
"name": "SettingsPage_DeleteHorizontalLogoMutation",
"operationKind": "mutation",
"text": "mutation SettingsPage_DeleteHorizontalLogoMutation(\n $input: DeleteOrganizationHorizontalLogoInput!\n) {\n deleteOrganizationHorizontalLogo(input: $input) {\n organization {\n id\n horizontalLogoUrl\n }\n }\n}\n"
}
};
})();
(node as any).hash = "751c3ff44c59511451095ffc66446c2f";
export default node;

View File

@@ -1,5 +1,5 @@
/** /**
* @generated SignedSource<<892a78db951d547b53d595d219b94e75>> * @generated SignedSource<<ce571f7246b6f7500a132f28c8081f84>>
* @lightSyntaxTransform * @lightSyntaxTransform
* @nogrep * @nogrep
*/ */
@@ -13,6 +13,7 @@ export type UpdateOrganizationInput = {
description?: string | null | undefined; description?: string | null | undefined;
email?: string | null | undefined; email?: string | null | undefined;
headquarterAddress?: string | null | undefined; headquarterAddress?: string | null | undefined;
horizontalLogoFile?: any | null | undefined;
logo?: any | null | undefined; logo?: any | null | undefined;
name?: string | null | undefined; name?: string | null | undefined;
organizationId: string; organizationId: string;
@@ -27,6 +28,7 @@ export type SettingsPage_UpdateMutation$data = {
readonly description: string | null | undefined; readonly description: string | null | undefined;
readonly email: string | null | undefined; readonly email: string | null | undefined;
readonly headquarterAddress: string | null | undefined; readonly headquarterAddress: string | null | undefined;
readonly horizontalLogoUrl: string | null | undefined;
readonly id: string; readonly id: string;
readonly logoUrl: string | null | undefined; readonly logoUrl: string | null | undefined;
readonly name: string; readonly name: string;
@@ -91,6 +93,13 @@ v1 = [
"name": "logoUrl", "name": "logoUrl",
"storageKey": null "storageKey": null
}, },
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "horizontalLogoUrl",
"storageKey": null
},
{ {
"alias": null, "alias": null,
"args": null, "args": null,
@@ -144,16 +153,16 @@ return {
"selections": (v1/*: any*/) "selections": (v1/*: any*/)
}, },
"params": { "params": {
"cacheID": "3526d270f311db33e0563abc99c96978", "cacheID": "097a6e519249d6f2b3c2a95963f85a6c",
"id": null, "id": null,
"metadata": {}, "metadata": {},
"name": "SettingsPage_UpdateMutation", "name": "SettingsPage_UpdateMutation",
"operationKind": "mutation", "operationKind": "mutation",
"text": "mutation SettingsPage_UpdateMutation(\n $input: UpdateOrganizationInput!\n) {\n updateOrganization(input: $input) {\n organization {\n id\n name\n logoUrl\n description\n websiteUrl\n email\n headquarterAddress\n }\n }\n}\n" "text": "mutation SettingsPage_UpdateMutation(\n $input: UpdateOrganizationInput!\n) {\n updateOrganization(input: $input) {\n organization {\n id\n name\n logoUrl\n horizontalLogoUrl\n description\n websiteUrl\n email\n headquarterAddress\n }\n }\n}\n"
} }
}; };
})(); })();
(node as any).hash = "54949158defa7f1bb8e1e578db7cd7be"; (node as any).hash = "c676129018636d84dbca9d8c98962af7";
export default node; export default node;

View File

@@ -20,13 +20,20 @@ export function FileButton({
children, children,
icon: IconComponent, icon: IconComponent,
ref, ref,
accept,
...props ...props
}: Props) { }: Props) {
return ( return (
<label className={button({ ...props })}> <label className={button({ ...props })}>
{IconComponent && <IconComponent size={16} className="flex-none" />} {IconComponent && <IconComponent size={16} className="flex-none" />}
{children} {children}
<input type="file" onChange={onChange} hidden ref={ref} /> <input
type="file"
onChange={onChange}
hidden
ref={ref}
{...(accept && { accept })}
/>
</label> </label>
); );
} }

View File

@@ -0,0 +1,51 @@
ALTER TABLE organizations ALTER COLUMN logo_object_key SET DEFAULT '';
ALTER TABLE organizations
ADD COLUMN horizontal_logo_file_id TEXT,
ADD CONSTRAINT organizations_horizontal_logo_file_id_fkey
FOREIGN KEY (horizontal_logo_file_id)
REFERENCES files(id)
ON UPDATE CASCADE
ON DELETE RESTRICT;
ALTER TABLE organizations
ADD COLUMN logo_file_id TEXT;
/* 25 is for FileEntityType */
WITH
logo_files AS (
SELECT
o.id as organization_id,
generate_gid(decode_base64_unpadded(o.tenant_id), 25) as file_id,
o.tenant_id,
'probod' as bucket_name,
'image/png' as mime_type,
'logo.png' as file_name,
o.logo_object_key,
o.created_at,
o.updated_at
FROM organizations o
WHERE o.logo_object_key IS NOT NULL AND o.logo_object_key != ''
),
inserted_files AS (
INSERT INTO files (id, tenant_id, bucket_name, mime_type, file_name, file_key, file_size, created_at, updated_at)
SELECT file_id, tenant_id, bucket_name, mime_type, file_name, logo_object_key::uuid, 0, created_at, updated_at
FROM logo_files
RETURNING id, tenant_id
)
SELECT lf.organization_id, lf.file_id
INTO TEMP TABLE file_logo_mapping
FROM logo_files lf;
UPDATE organizations
SET logo_file_id = fm.file_id
FROM file_logo_mapping fm
WHERE organizations.id = fm.organization_id;
ALTER TABLE organizations
ADD CONSTRAINT organizations_logo_file_id_fkey
FOREIGN KEY (logo_file_id)
REFERENCES files(id)
ON UPDATE CASCADE
ON DELETE RESTRICT;

View File

@@ -28,17 +28,18 @@ import (
type ( type (
Organization struct { Organization struct {
ID gid.GID `db:"id"` ID gid.GID `db:"id"`
TenantID gid.TenantID `db:"tenant_id"` TenantID gid.TenantID `db:"tenant_id"`
Name string `db:"name"` Name string `db:"name"`
LogoObjectKey string `db:"logo_object_key"` LogoFileID *gid.GID `db:"logo_file_id"`
Description *string `db:"description"` HorizontalLogoFileID *gid.GID `db:"horizontal_logo_file_id"`
WebsiteURL *string `db:"website_url"` Description *string `db:"description"`
Email *string `db:"email"` WebsiteURL *string `db:"website_url"`
HeadquarterAddress *string `db:"headquarter_address"` Email *string `db:"email"`
CustomDomainID *gid.GID `db:"custom_domain_id"` HeadquarterAddress *string `db:"headquarter_address"`
CreatedAt time.Time `db:"created_at"` CustomDomainID *gid.GID `db:"custom_domain_id"`
UpdatedAt time.Time `db:"updated_at"` CreatedAt time.Time `db:"created_at"`
UpdatedAt time.Time `db:"updated_at"`
} }
Organizations []*Organization Organizations []*Organization
@@ -68,7 +69,8 @@ SELECT
tenant_id, tenant_id,
id, id,
name, name,
logo_object_key, logo_file_id,
horizontal_logo_file_id,
description, description,
website_url, website_url,
email, email,
@@ -124,7 +126,8 @@ SELECT
tenant_id, tenant_id,
id, id,
name, name,
logo_object_key, logo_file_id,
horizontal_logo_file_id,
description, description,
website_url, website_url,
email, email,
@@ -169,7 +172,8 @@ INSERT INTO organizations (
tenant_id, tenant_id,
id, id,
name, name,
logo_object_key, logo_file_id,
horizontal_logo_file_id,
description, description,
website_url, website_url,
email, email,
@@ -177,21 +181,22 @@ INSERT INTO organizations (
custom_domain_id, custom_domain_id,
created_at, created_at,
updated_at updated_at
) VALUES (@tenant_id, @id, @name, @logo_object_key, @description, @website_url, @email, @headquarter_address, @custom_domain_id, @created_at, @updated_at) ) VALUES (@tenant_id, @id, @name, @logo_file_id, @horizontal_logo_file_id, @description, @website_url, @email, @headquarter_address, @custom_domain_id, @created_at, @updated_at)
` `
args := pgx.StrictNamedArgs{ args := pgx.StrictNamedArgs{
"tenant_id": o.TenantID, "tenant_id": o.TenantID,
"id": o.ID, "id": o.ID,
"name": o.Name, "name": o.Name,
"logo_object_key": o.LogoObjectKey, "logo_file_id": o.LogoFileID,
"description": o.Description, "horizontal_logo_file_id": o.HorizontalLogoFileID,
"website_url": o.WebsiteURL, "description": o.Description,
"email": o.Email, "website_url": o.WebsiteURL,
"headquarter_address": o.HeadquarterAddress, "email": o.Email,
"custom_domain_id": o.CustomDomainID, "headquarter_address": o.HeadquarterAddress,
"created_at": o.CreatedAt, "custom_domain_id": o.CustomDomainID,
"updated_at": o.UpdatedAt, "created_at": o.CreatedAt,
"updated_at": o.UpdatedAt,
} }
_, err := conn.Exec(ctx, q, args) _, err := conn.Exec(ctx, q, args)
@@ -211,7 +216,8 @@ func (o *Organization) Update(
UPDATE organizations UPDATE organizations
SET SET
name = @name, name = @name,
logo_object_key = @logo_object_key, logo_file_id = @logo_file_id,
horizontal_logo_file_id = @horizontal_logo_file_id,
description = @description, description = @description,
website_url = @website_url, website_url = @website_url,
email = @email, email = @email,
@@ -226,15 +232,16 @@ WHERE
q = fmt.Sprintf(q, scope.SQLFragment()) q = fmt.Sprintf(q, scope.SQLFragment())
args := pgx.StrictNamedArgs{ args := pgx.StrictNamedArgs{
"id": o.ID, "id": o.ID,
"name": o.Name, "name": o.Name,
"logo_object_key": o.LogoObjectKey, "logo_file_id": o.LogoFileID,
"description": o.Description, "horizontal_logo_file_id": o.HorizontalLogoFileID,
"website_url": o.WebsiteURL, "description": o.Description,
"email": o.Email, "website_url": o.WebsiteURL,
"headquarter_address": o.HeadquarterAddress, "email": o.Email,
"custom_domain_id": o.CustomDomainID, "headquarter_address": o.HeadquarterAddress,
"updated_at": o.UpdatedAt, "custom_domain_id": o.CustomDomainID,
"updated_at": o.UpdatedAt,
} }
maps.Copy(args, scope.SQLArguments()) maps.Copy(args, scope.SQLArguments())
@@ -283,7 +290,8 @@ SELECT
tenant_id, tenant_id,
id, id,
name, name,
logo_object_key, logo_file_id,
horizontal_logo_file_id,
description, description,
website_url, website_url,
email, email,

View File

@@ -53,6 +53,9 @@ var (
} }
return template.HTML(buf.String()) return template.HTML(buf.String())
}, },
"imgTag": func(src, alt, class string) template.HTML {
return template.HTML(fmt.Sprintf(`<img src="%s" alt="%s" class="%s">`, html.EscapeString(src), html.EscapeString(alt), html.EscapeString(class)))
},
} }
documentTemplate = template.Must(template.New("document").Funcs(templateFuncs).Parse(htmlTemplateContent)) documentTemplate = template.Must(template.New("document").Funcs(templateFuncs).Parse(htmlTemplateContent))
@@ -62,14 +65,15 @@ type (
Classification string Classification string
DocumentData struct { DocumentData struct {
Title string Title string
Content string Content string
Version int Version int
Classification Classification Classification Classification
Approver string Approver string
Description string Description string
PublishedAt *time.Time PublishedAt *time.Time
Signatures []SignatureData Signatures []SignatureData
CompanyHorizontalLogoBase64 string
} }
SignatureData struct { SignatureData struct {

View File

@@ -11,17 +11,17 @@
margin: 2.5cm; margin: 2.5cm;
@bottom-right { @bottom-right {
content: "Page " counter(page) " of " counter(pages); content: "Page " counter(page) " of " counter(pages);
font-family: 'Times New Roman', Times, serif; font-family: Arial, sans-serif;
font-size: 9pt; font-size: 9pt;
color: #666; color: #666;
} }
} }
body { body {
font-family: 'Times New Roman', Times, serif; font-family: Arial, sans-serif;
font-size: 11pt; font-size: 11pt;
line-height: 1.4; line-height: 1.6;
color: #000; color: #333;
margin: 0; margin: 0;
padding: 20px 0; padding: 20px 0;
background: #f5f5f5; background: #f5f5f5;
@@ -49,46 +49,58 @@
page-break-after: auto; page-break-after: auto;
} }
.document-header { .document-header {
border-bottom: 1px solid #333; padding-bottom: 0;
padding-bottom: 15px;
margin-bottom: 25px; margin-bottom: 25px;
page-break-after: avoid; page-break-after: avoid;
} }
.company-header {
margin-bottom: 25px;
page-break-after: avoid;
}
.company-logo {
max-height: 50px;
max-width: 250px;
object-fit: contain;
display: block;
}
.document-title { .document-title {
font-size: 18pt; font-size: 22pt;
font-weight: bold; font-weight: normal;
color: #000; color: #1a1a1a;
margin: 0 0 10px 0; margin: 0 0 25px 0;
text-align: center; text-align: left;
}
.company-header + .document-title {
margin-top: 30px;
} }
.document-meta { .document-meta {
background: #f9f9f9; margin: 25px 0 0 0;
padding: 12px;
border: 1px solid #ddd;
margin: 15px 0;
font-size: 9pt; font-size: 9pt;
} }
.meta-table { .meta-table {
width: 100%; width: 100%;
border-collapse: collapse; border-collapse: collapse;
border: 1px solid #333;
} }
.meta-table td { .meta-table td {
padding: 4px 8px; padding: 6px 8px;
border-bottom: 1px solid #eee; border: 1px solid #333;
vertical-align: top; vertical-align: middle;
} }
.meta-table td:first-child { .meta-table td:first-child {
font-weight: bold; font-weight: 600;
width: 25%;
background: #f8f8f8;
color: #333; color: #333;
width: 120px;
} }
.classification { .classification {
@@ -141,7 +153,7 @@
} }
.document-content p { .document-content p {
margin-bottom: 12px; margin: 0 0 12px 0;
text-align: justify; text-align: justify;
orphans: 3; orphans: 3;
widows: 3; widows: 3;
@@ -312,18 +324,23 @@
<div class="document-container"> <div class="document-container">
<div class="page-section"> <div class="page-section">
<div class="document-header"> <div class="document-header">
{{- if .CompanyHorizontalLogoBase64}}
<div class="company-header">
{{imgTag .CompanyHorizontalLogoBase64 "Company Logo" "company-logo"}}
</div>
{{- end}}
<h1 class="document-title">{{.Title}}</h1> <h1 class="document-title">{{.Title}}</h1>
<div class="document-meta"> <div class="document-meta">
<table class="meta-table"> <table class="meta-table">
<tr> <tr>
<td>Classification:</td> <td>Classification</td>
<td> <td>
<span class="classification">{{.Classification | classificationString}}</span> <span class="classification">{{.Classification | classificationString}}</span>
</td> </td>
</tr> </tr>
<tr> <tr>
<td>Approver:</td> <td>Approver</td>
<td>{{.Approver}}</td> <td>{{.Approver}}</td>
</tr> </tr>
<tr> <tr>
@@ -332,7 +349,7 @@
</tr> </tr>
{{- if .PublishedAt}} {{- if .PublishedAt}}
<tr> <tr>
<td>Published:</td> <td>Published</td>
<td>{{.PublishedAt.Format "January 2, 2006"}}</td> <td>{{.PublishedAt.Format "January 2, 2006"}}</td>
</tr> </tr>
{{- end}} {{- end}}

148
pkg/filemanager/service.go Normal file
View File

@@ -0,0 +1,148 @@
// Copyright (c) 2025 Probo Inc <hello@getprobo.com>.
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies.
//
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
package filemanager
import (
"context"
"encoding/base64"
"fmt"
"io"
"net/url"
"time"
"github.com/aws/aws-sdk-go-v2/aws"
"github.com/aws/aws-sdk-go-v2/service/s3"
"github.com/getprobo/probo/pkg/coredata"
)
type Service struct {
s3Client *s3.Client
}
func NewService(s3Client *s3.Client) *Service {
return &Service{
s3Client: s3Client,
}
}
func (s *Service) GetFileBase64(
ctx context.Context,
file *coredata.File,
) (base64Data string, mimeType string, err error) {
result, err := s.s3Client.GetObject(
ctx,
&s3.GetObjectInput{
Bucket: &file.BucketName,
Key: &file.FileKey,
},
)
if err != nil {
return "", "", fmt.Errorf("cannot get file from S3: %w", err)
}
defer result.Body.Close()
fileData, err := io.ReadAll(result.Body)
if err != nil {
return "", "", fmt.Errorf("cannot read file data: %w", err)
}
if result.ContentType == nil || *result.ContentType == "" {
return "", "", fmt.Errorf("no MIME type available for file %s", file.FileKey)
}
base64Data = base64.StdEncoding.EncodeToString(fileData)
mimeType = *result.ContentType
return base64Data, mimeType, nil
}
func (s *Service) GetFileSize(content io.Reader) (int64, error) {
seeker, ok := content.(io.Seeker)
if !ok {
return 0, fmt.Errorf("cannot determine file size: content is not seekable")
}
size, err := seeker.Seek(0, io.SeekEnd)
if err != nil {
return 0, fmt.Errorf("cannot determine file size: %w", err)
}
_, err = seeker.Seek(0, io.SeekStart)
if err != nil {
return 0, fmt.Errorf("cannot reset file position: %w", err)
}
return size, nil
}
func (s *Service) PutFile(
ctx context.Context,
file *coredata.File,
content io.Reader,
metadata map[string]string,
) (int64, error) {
_, err := s.s3Client.PutObject(
ctx,
&s3.PutObjectInput{
Bucket: &file.BucketName,
Key: &file.FileKey,
Body: content,
ContentType: &file.MimeType,
Metadata: metadata,
})
if err != nil {
return 0, fmt.Errorf("cannot upload file to S3: %w", err)
}
headOutput, err := s.s3Client.HeadObject(ctx, &s3.HeadObjectInput{
Bucket: &file.BucketName,
Key: &file.FileKey,
})
if err != nil {
return 0, fmt.Errorf("cannot get object metadata: %w", err)
}
return *headOutput.ContentLength, nil
}
func (s *Service) GenerateFileUrl(
ctx context.Context,
file *coredata.File,
expiresIn time.Duration,
) (string, error) {
presignClient := s3.NewPresignClient(s.s3Client)
encodedFilename := url.QueryEscape(file.FileName)
contentDisposition := fmt.Sprintf("attachment; filename=%q; filename*=UTF-8''%s",
encodedFilename, encodedFilename)
presignedReq, err := presignClient.PresignGetObject(
ctx,
&s3.GetObjectInput{
Bucket: &file.BucketName,
Key: &file.FileKey,
ResponseCacheControl: aws.String("max-age=3600, public"),
ResponseContentDisposition: &contentDisposition,
},
func(opts *s3.PresignOptions) {
opts.Expires = expiresIn
},
)
if err != nil {
return "", fmt.Errorf("cannot presign GetObject request: %w", err)
}
return presignedReq.URL, nil
}

View File

@@ -1201,7 +1201,7 @@ func (s *DocumentService) ExportPDF(
err := s.svc.pg.WithTx( err := s.svc.pg.WithTx(
ctx, ctx,
func(conn pg.Conn) (err error) { func(conn pg.Conn) (err error) {
data, err = exportDocumentPDF(ctx, s.html2pdfConverter, conn, s.svc.scope, documentVersionID, options) data, err = exportDocumentPDF(ctx, s.svc, s.html2pdfConverter, conn, s.svc.scope, documentVersionID, options)
if err != nil { if err != nil {
return fmt.Errorf("cannot export document PDF: %w", err) return fmt.Errorf("cannot export document PDF: %w", err)
} }
@@ -1333,6 +1333,7 @@ func (s *DocumentService) BuildAndUploadExport(ctx context.Context, exportJobID
func exportDocumentPDF( func exportDocumentPDF(
ctx context.Context, ctx context.Context,
svc *TenantService,
html2pdfConverter *html2pdf.Converter, html2pdfConverter *html2pdf.Converter,
conn pg.Conn, conn pg.Conn,
scope coredata.Scoper, scope coredata.Scoper,
@@ -1342,6 +1343,7 @@ func exportDocumentPDF(
document := &coredata.Document{} document := &coredata.Document{}
version := &coredata.DocumentVersion{} version := &coredata.DocumentVersion{}
owner := &coredata.People{} owner := &coredata.People{}
organization := &coredata.Organization{}
signatures := coredata.DocumentVersionSignatures{} signatures := coredata.DocumentVersionSignatures{}
peopleMap := make(map[gid.GID]*coredata.People) peopleMap := make(map[gid.GID]*coredata.People)
@@ -1357,6 +1359,10 @@ func exportDocumentPDF(
return nil, fmt.Errorf("cannot load document owner: %w", err) return nil, fmt.Errorf("cannot load document owner: %w", err)
} }
if err := organization.LoadByID(ctx, conn, scope, document.OrganizationID); err != nil {
return nil, fmt.Errorf("cannot load organization: %w", err)
}
var signatureData []docgen.SignatureData var signatureData []docgen.SignatureData
if options.WithSignatures { if options.WithSignatures {
cursor := page.NewCursor( cursor := page.NewCursor(
@@ -1403,14 +1409,29 @@ func exportDocumentPDF(
classification = docgen.ClassificationSecret classification = docgen.ClassificationSecret
} }
horizontalLogoBase64 := ""
if organization.HorizontalLogoFileID != nil {
fileRecord := &coredata.File{}
fileErr := svc.pg.WithConn(ctx, func(conn pg.Conn) error {
return fileRecord.LoadByID(ctx, conn, scope, *organization.HorizontalLogoFileID)
})
if fileErr == nil {
base64Data, mimeType, logoErr := svc.fileManager.GetFileBase64(ctx, fileRecord)
if logoErr == nil {
horizontalLogoBase64 = fmt.Sprintf("data:%s;base64,%s", mimeType, base64Data)
}
}
}
docData := docgen.DocumentData{ docData := docgen.DocumentData{
Title: version.Title, Title: version.Title,
Content: version.Content, Content: version.Content,
Version: version.VersionNumber, Version: version.VersionNumber,
Classification: classification, Classification: classification,
Approver: owner.FullName, Approver: owner.FullName,
PublishedAt: version.PublishedAt, PublishedAt: version.PublishedAt,
Signatures: signatureData, Signatures: signatureData,
CompanyHorizontalLogoBase64: horizontalLogoBase64,
} }
htmlContent, err := docgen.RenderHTML(docData) htmlContent, err := docgen.RenderHTML(docData)
@@ -1492,6 +1513,7 @@ func (s *DocumentService) Export(
exportedPDF, err := exportDocumentPDF( exportedPDF, err := exportDocumentPDF(
ctx, ctx,
s.svc,
s.html2pdfConverter, s.html2pdfConverter,
conn, conn,
s.svc.scope, s.svc.scope,

View File

@@ -285,6 +285,7 @@ func (s FrameworkService) Export(
exportedPDF, err := exportDocumentPDF( exportedPDF, err := exportDocumentPDF(
ctx, ctx,
s.svc,
s.html2pdfConverter, s.html2pdfConverter,
conn, conn,
s.svc.scope, s.svc.scope,

View File

@@ -15,18 +15,13 @@
package probo package probo
import ( import (
"bytes"
"context" "context"
"fmt" "fmt"
"io"
"mime" "mime"
"net/mail" "net/mail"
"net/url"
"path/filepath" "path/filepath"
"time" "time"
"github.com/aws/aws-sdk-go-v2/aws"
"github.com/aws/aws-sdk-go-v2/service/s3"
"github.com/getprobo/probo/pkg/coredata" "github.com/getprobo/probo/pkg/coredata"
"github.com/getprobo/probo/pkg/filevalidation" "github.com/getprobo/probo/pkg/filevalidation"
"github.com/getprobo/probo/pkg/gid" "github.com/getprobo/probo/pkg/gid"
@@ -71,6 +66,7 @@ type (
ID gid.GID ID gid.GID
Name *string Name *string
File *File File *File
HorizontalLogoFile *File
Description **string Description **string
WebsiteURL **string WebsiteURL **string
Email **string Email **string
@@ -160,14 +156,15 @@ func (s OrganizationService) Update(
) (*coredata.Organization, error) { ) (*coredata.Organization, error) {
organization := &coredata.Organization{} organization := &coredata.Organization{}
err := s.svc.pg.WithConn( err := s.svc.pg.WithTx(
ctx, ctx,
func(conn pg.Conn) error { func(tx pg.Conn) error {
if err := organization.LoadByID(ctx, conn, s.svc.scope, req.ID); err != nil { if err := organization.LoadByID(ctx, tx, s.svc.scope, req.ID); err != nil {
return fmt.Errorf("cannot load organization: %w", err) return fmt.Errorf("cannot load organization: %w", err)
} }
organization.UpdatedAt = time.Now() now := time.Now()
organization.UpdatedAt = now
if req.Name != nil { if req.Name != nil {
organization.Name = *req.Name organization.Name = *req.Name
@@ -195,41 +192,15 @@ func (s OrganizationService) Update(
} }
if req.File != nil { if req.File != nil {
fileID := gid.New(s.svc.scope.GetTenantID(), coredata.FileEntityType)
objectKey, err := uuid.NewV7() objectKey, err := uuid.NewV7()
if err != nil { if err != nil {
return fmt.Errorf("cannot generate object key: %w", err) return fmt.Errorf("cannot generate object key: %w", err)
} }
var fileSize int64
var fileContent io.ReadSeeker
filename := req.File.Filename filename := req.File.Filename
contentType := req.File.ContentType contentType := req.File.ContentType
if seeker, ok := req.File.Content.(io.Seeker); ok {
if req.File.Size <= 0 {
size, err := seeker.Seek(0, io.SeekEnd)
if err != nil {
return fmt.Errorf("cannot determine file size: %w", err)
}
fileSize = size
_, err = seeker.Seek(0, io.SeekStart)
if err != nil {
return fmt.Errorf("cannot reset file position: %w", err)
}
} else {
fileSize = req.File.Size
}
fileContent = req.File.Content.(io.ReadSeeker)
} else {
buf, err := io.ReadAll(req.File.Content)
if err != nil {
return fmt.Errorf("cannot read file: %w", err)
}
fileSize = int64(len(buf))
fileContent = bytes.NewReader(buf)
}
if contentType == "" { if contentType == "" {
contentType = "application/octet-stream" contentType = "application/octet-stream"
if filename != "" { if filename != "" {
@@ -239,29 +210,98 @@ func (s OrganizationService) Update(
} }
} }
fileSize, err := s.svc.fileManager.GetFileSize(req.File.Content)
if err != nil {
return fmt.Errorf("cannot get file size: %w", err)
}
if err := s.fileValidator.Validate(filename, contentType, fileSize); err != nil { if err := s.fileValidator.Validate(filename, contentType, fileSize); err != nil {
return err return err
} }
_, err = s.svc.s3.PutObject(ctx, &s3.PutObjectInput{ fileRecord := &coredata.File{
Bucket: aws.String(s.svc.bucket), ID: fileID,
Key: aws.String(objectKey.String()), BucketName: s.svc.bucket,
Body: fileContent, MimeType: contentType,
ContentType: aws.String(contentType), FileName: filename,
Metadata: map[string]string{ FileKey: objectKey.String(),
"type": "organization-logo", CreatedAt: now,
"organization-id": organization.ID.String(), UpdatedAt: now,
},
})
if err != nil {
return fmt.Errorf("cannot upload file to S3: %w", err)
} }
organization.LogoObjectKey = objectKey.String() fileSize, err = s.svc.fileManager.PutFile(ctx, fileRecord, req.File.Content, map[string]string{
"type": "organization-logo",
"organization-id": organization.ID.String(),
})
if err != nil {
return fmt.Errorf("cannot upload logo file: %w", err)
}
fileRecord.FileSize = fileSize
if err := fileRecord.Insert(ctx, tx, s.svc.scope); err != nil {
return fmt.Errorf("cannot insert file: %w", err)
}
organization.LogoFileID = &fileID
} }
if err := organization.Update(ctx, s.svc.scope, conn); err != nil { if req.HorizontalLogoFile != nil {
fileID := gid.New(s.svc.scope.GetTenantID(), coredata.FileEntityType)
objectKey, err := uuid.NewV7()
if err != nil {
return fmt.Errorf("cannot generate object key: %w", err)
}
filename := req.HorizontalLogoFile.Filename
contentType := req.HorizontalLogoFile.ContentType
if contentType == "" {
contentType = "application/octet-stream"
if filename != "" {
if detectedType := mime.TypeByExtension(filepath.Ext(filename)); detectedType != "" {
contentType = detectedType
}
}
}
fileSize, err := s.svc.fileManager.GetFileSize(req.HorizontalLogoFile.Content)
if err != nil {
return fmt.Errorf("cannot get file size: %w", err)
}
if err := s.fileValidator.Validate(filename, contentType, fileSize); err != nil {
return err
}
fileRecord := &coredata.File{
ID: fileID,
BucketName: s.svc.bucket,
MimeType: contentType,
FileName: filename,
FileKey: objectKey.String(),
CreatedAt: now,
UpdatedAt: now,
}
fileSize, err = s.svc.fileManager.PutFile(ctx, fileRecord, req.HorizontalLogoFile.Content, map[string]string{
"type": "organization-horizontal-logo",
"organization-id": organization.ID.String(),
})
if err != nil {
return fmt.Errorf("cannot upload horizontal logo file: %w", err)
}
fileRecord.FileSize = fileSize
if err := fileRecord.Insert(ctx, tx, s.svc.scope); err != nil {
return fmt.Errorf("cannot insert file: %w", err)
}
organization.HorizontalLogoFileID = &fileID
}
if err := organization.Update(ctx, s.svc.scope, tx); err != nil {
return fmt.Errorf("cannot update organization: %w", err) return fmt.Errorf("cannot update organization: %w", err)
} }
@@ -281,34 +321,114 @@ func (s OrganizationService) GenerateLogoURL(
organizationID gid.GID, organizationID gid.GID,
expiresIn time.Duration, expiresIn time.Duration,
) (*string, error) { ) (*string, error) {
organization, err := s.Get(ctx, organizationID) file := &coredata.File{}
err := s.svc.pg.WithConn(
ctx,
func(conn pg.Conn) error {
organization := &coredata.Organization{}
if err := organization.LoadByID(ctx, conn, s.svc.scope, organizationID); err != nil {
return fmt.Errorf("cannot load organization: %w", err)
}
if organization.LogoFileID == nil {
return nil
}
if err := file.LoadByID(ctx, conn, s.svc.scope, *organization.LogoFileID); err != nil {
return fmt.Errorf("cannot load file: %w", err)
}
return nil
},
)
if err != nil { if err != nil {
return nil, fmt.Errorf("cannot get organization: %w", err) return nil, err
} }
if organization.LogoObjectKey == "" { if file.FileKey == "" {
return nil, nil return nil, nil
} }
presignClient := s3.NewPresignClient(s.svc.s3) presignedURL, err := s.svc.fileManager.GenerateFileUrl(ctx, file, expiresIn)
encodedFilename := url.QueryEscape(organization.Name)
contentDisposition := fmt.Sprintf("attachment; filename=\"%s\"; filename*=UTF-8''%s",
encodedFilename, encodedFilename)
presignedReq, err := presignClient.PresignGetObject(ctx, &s3.GetObjectInput{
Bucket: aws.String(s.svc.bucket),
Key: aws.String(organization.LogoObjectKey),
ResponseCacheControl: aws.String("max-age=3600, public"),
ResponseContentDisposition: aws.String(contentDisposition),
}, func(opts *s3.PresignOptions) {
opts.Expires = expiresIn
})
if err != nil { if err != nil {
return nil, fmt.Errorf("cannot presign GetObject request: %w", err) return nil, fmt.Errorf("cannot generate file URL: %w", err)
} }
return &presignedReq.URL, nil return &presignedURL, nil
}
func (s OrganizationService) GenerateHorizontalLogoURL(
ctx context.Context,
organizationID gid.GID,
expiresIn time.Duration,
) (*string, error) {
file := &coredata.File{}
err := s.svc.pg.WithConn(
ctx,
func(conn pg.Conn) error {
organization := &coredata.Organization{}
if err := organization.LoadByID(ctx, conn, s.svc.scope, organizationID); err != nil {
return fmt.Errorf("cannot load organization: %w", err)
}
if organization.HorizontalLogoFileID == nil {
return nil
}
if err := file.LoadByID(ctx, conn, s.svc.scope, *organization.HorizontalLogoFileID); err != nil {
return fmt.Errorf("cannot load file: %w", err)
}
return nil
},
)
if err != nil {
return nil, err
}
if file.FileKey == "" {
return nil, nil
}
presignedURL, err := s.svc.fileManager.GenerateFileUrl(ctx, file, expiresIn)
if err != nil {
return nil, fmt.Errorf("cannot generate file URL: %w", err)
}
return &presignedURL, nil
}
func (s OrganizationService) DeleteHorizontalLogo(
ctx context.Context,
organizationID gid.GID,
) (*coredata.Organization, error) {
organization := &coredata.Organization{}
err := s.svc.pg.WithTx(
ctx,
func(tx pg.Conn) error {
if err := organization.LoadByID(ctx, tx, s.svc.scope, organizationID); err != nil {
return fmt.Errorf("cannot load organization: %w", err)
}
organization.HorizontalLogoFileID = nil
organization.UpdatedAt = time.Now()
if err := organization.Update(ctx, s.svc.scope, tx); err != nil {
return fmt.Errorf("cannot update organization: %w", err)
}
return nil
},
)
if err != nil {
return nil, err
}
return organization, nil
} }
func (s OrganizationService) Delete( func (s OrganizationService) Delete(
@@ -343,16 +463,6 @@ func (s OrganizationService) Delete(
return err return err
} }
if organization.LogoObjectKey != "" {
_, err := s.svc.s3.DeleteObject(ctx, &s3.DeleteObjectInput{
Bucket: aws.String(s.svc.bucket),
Key: aws.String(organization.LogoObjectKey),
})
if err != nil {
return fmt.Errorf("organization deleted but failed to delete logo from S3: %w", err)
}
}
return nil return nil
} }

View File

@@ -24,6 +24,7 @@ import (
"github.com/getprobo/probo/pkg/certmanager" "github.com/getprobo/probo/pkg/certmanager"
"github.com/getprobo/probo/pkg/coredata" "github.com/getprobo/probo/pkg/coredata"
"github.com/getprobo/probo/pkg/crypto/cipher" "github.com/getprobo/probo/pkg/crypto/cipher"
"github.com/getprobo/probo/pkg/filemanager"
"github.com/getprobo/probo/pkg/filevalidation" "github.com/getprobo/probo/pkg/filevalidation"
"github.com/getprobo/probo/pkg/gid" "github.com/getprobo/probo/pkg/gid"
"github.com/getprobo/probo/pkg/html2pdf" "github.com/getprobo/probo/pkg/html2pdf"
@@ -57,6 +58,7 @@ type (
html2pdfConverter *html2pdf.Converter html2pdfConverter *html2pdf.Converter
usrmgr *usrmgr.Service usrmgr *usrmgr.Service
acmeService *certmanager.ACMEService acmeService *certmanager.ACMEService
fileManager *filemanager.Service
logger *log.Logger logger *log.Logger
} }
@@ -70,6 +72,7 @@ type (
tokenSecret string tokenSecret string
trustConfig TrustConfig trustConfig TrustConfig
agent *agents.Agent agent *agents.Agent
fileManager *filemanager.Service
Frameworks *FrameworkService Frameworks *FrameworkService
Measures *MeasureService Measures *MeasureService
Tasks *TaskService Tasks *TaskService
@@ -116,6 +119,7 @@ func NewService(
html2pdfConverter *html2pdf.Converter, html2pdfConverter *html2pdf.Converter,
usrmgrService *usrmgr.Service, usrmgrService *usrmgr.Service,
acmeService *certmanager.ACMEService, acmeService *certmanager.ACMEService,
fileManagerService *filemanager.Service,
logger *log.Logger, logger *log.Logger,
) (*Service, error) { ) (*Service, error) {
if bucket == "" { if bucket == "" {
@@ -134,6 +138,7 @@ func NewService(
html2pdfConverter: html2pdfConverter, html2pdfConverter: html2pdfConverter,
usrmgr: usrmgrService, usrmgr: usrmgrService,
acmeService: acmeService, acmeService: acmeService,
fileManager: fileManagerService,
logger: logger, logger: logger,
} }
@@ -151,6 +156,7 @@ func (s *Service) WithTenant(tenantID gid.TenantID) *TenantService {
tokenSecret: s.tokenSecret, tokenSecret: s.tokenSecret,
trustConfig: s.trustConfig, trustConfig: s.trustConfig,
agent: agents.NewAgent(nil, s.agentConfig), agent: agents.NewAgent(nil, s.agentConfig),
fileManager: s.fileManager,
} }
tenantService.Frameworks = &FrameworkService{ tenantService.Frameworks = &FrameworkService{

View File

@@ -36,6 +36,7 @@ import (
"github.com/getprobo/probo/pkg/crypto/keys" "github.com/getprobo/probo/pkg/crypto/keys"
"github.com/getprobo/probo/pkg/crypto/passwdhash" "github.com/getprobo/probo/pkg/crypto/passwdhash"
"github.com/getprobo/probo/pkg/crypto/pem" "github.com/getprobo/probo/pkg/crypto/pem"
"github.com/getprobo/probo/pkg/filemanager"
"github.com/getprobo/probo/pkg/html2pdf" "github.com/getprobo/probo/pkg/html2pdf"
"github.com/getprobo/probo/pkg/mailer" "github.com/getprobo/probo/pkg/mailer"
"github.com/getprobo/probo/pkg/probo" "github.com/getprobo/probo/pkg/probo"
@@ -269,6 +270,8 @@ func (impl *Implm) Run(
return fmt.Errorf("cannot create usrmgr service: %w", err) return fmt.Errorf("cannot create usrmgr service: %w", err)
} }
fileManagerService := filemanager.NewService(s3Client)
var accountKey crypto.Signer var accountKey crypto.Signer
if impl.cfg.CustomDomains.ACME.AccountKey != "" { if impl.cfg.CustomDomains.ACME.AccountKey != "" {
accountKey, err = pem.DecodePrivateKey([]byte(impl.cfg.CustomDomains.ACME.AccountKey)) accountKey, err = pem.DecodePrivateKey([]byte(impl.cfg.CustomDomains.ACME.AccountKey))
@@ -311,6 +314,7 @@ func (impl *Implm) Run(
html2pdfConverter, html2pdfConverter,
usrmgrService, usrmgrService,
acmeService, acmeService,
fileManagerService,
l.Named("probo"), l.Named("probo"),
) )
if err != nil { if err != nil {
@@ -325,6 +329,7 @@ func (impl *Implm) Run(
impl.cfg.TrustAuth.TokenSecret, impl.cfg.TrustAuth.TokenSecret,
usrmgrService, usrmgrService,
html2pdfConverter, html2pdfConverter,
fileManagerService,
) )
serverHandler, err := server.NewServer( serverHandler, err := server.NewServer(

View File

@@ -1488,6 +1488,7 @@ type Organization implements Node {
id: ID! id: ID!
name: String! name: String!
logoUrl: String @goField(forceResolver: true) logoUrl: String @goField(forceResolver: true)
horizontalLogoUrl: String @goField(forceResolver: true)
description: String description: String
websiteUrl: String websiteUrl: String
@@ -2619,6 +2620,9 @@ type Mutation {
updateOrganization( updateOrganization(
input: UpdateOrganizationInput! input: UpdateOrganizationInput!
): UpdateOrganizationPayload! ): UpdateOrganizationPayload!
deleteOrganizationHorizontalLogo(
input: DeleteOrganizationHorizontalLogoInput!
): DeleteOrganizationHorizontalLogoPayload!
deleteOrganization( deleteOrganization(
input: DeleteOrganizationInput! input: DeleteOrganizationInput!
): DeleteOrganizationPayload! ): DeleteOrganizationPayload!
@@ -2941,12 +2945,17 @@ input UpdateOrganizationInput {
organizationId: ID! organizationId: ID!
name: String name: String
logo: Upload logo: Upload
horizontalLogoFile: Upload
description: String description: String
websiteUrl: String websiteUrl: String
email: String email: String
headquarterAddress: String headquarterAddress: String
} }
input DeleteOrganizationHorizontalLogoInput {
organizationId: ID!
}
input DeleteOrganizationInput { input DeleteOrganizationInput {
organizationId: ID! organizationId: ID!
} }
@@ -3628,6 +3637,10 @@ type UpdateOrganizationPayload {
organization: Organization! organization: Organization!
} }
type DeleteOrganizationHorizontalLogoPayload {
organization: Organization!
}
type DeleteOrganizationPayload { type DeleteOrganizationPayload {
deletedOrganizationId: ID! deletedOrganizationId: ID!
} }

View File

@@ -516,6 +516,10 @@ type ComplexityRoot struct {
DeletedObligationID func(childComplexity int) int DeletedObligationID func(childComplexity int) int
} }
DeleteOrganizationHorizontalLogoPayload struct {
Organization func(childComplexity int) int
}
DeleteOrganizationPayload struct { DeleteOrganizationPayload struct {
DeletedOrganizationID func(childComplexity int) int DeletedOrganizationID func(childComplexity int) int
} }
@@ -835,6 +839,7 @@ type ComplexityRoot struct {
DeleteNonconformity func(childComplexity int, input types.DeleteNonconformityInput) int DeleteNonconformity func(childComplexity int, input types.DeleteNonconformityInput) int
DeleteObligation func(childComplexity int, input types.DeleteObligationInput) int DeleteObligation func(childComplexity int, input types.DeleteObligationInput) int
DeleteOrganization func(childComplexity int, input types.DeleteOrganizationInput) int DeleteOrganization func(childComplexity int, input types.DeleteOrganizationInput) int
DeleteOrganizationHorizontalLogo func(childComplexity int, input types.DeleteOrganizationHorizontalLogoInput) int
DeletePeople func(childComplexity int, input types.DeletePeopleInput) int DeletePeople func(childComplexity int, input types.DeletePeopleInput) int
DeleteProcessingActivity func(childComplexity int, input types.DeleteProcessingActivityInput) int DeleteProcessingActivity func(childComplexity int, input types.DeleteProcessingActivityInput) int
DeleteRisk func(childComplexity int, input types.DeleteRiskInput) int DeleteRisk func(childComplexity int, input types.DeleteRiskInput) int
@@ -968,6 +973,7 @@ type ComplexityRoot struct {
Email func(childComplexity int) int Email func(childComplexity int) int
Frameworks func(childComplexity int, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.FrameworkOrderBy) int Frameworks func(childComplexity int, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.FrameworkOrderBy) int
HeadquarterAddress func(childComplexity int) int HeadquarterAddress func(childComplexity int) int
HorizontalLogoURL func(childComplexity int) int
ID func(childComplexity int) int ID func(childComplexity int) int
LogoURL func(childComplexity int) int LogoURL func(childComplexity int) int
Measures func(childComplexity int, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.MeasureOrderBy, filter *types.MeasureFilter) int Measures func(childComplexity int, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.MeasureOrderBy, filter *types.MeasureFilter) int
@@ -1673,6 +1679,7 @@ type MeasureConnectionResolver interface {
type MutationResolver interface { type MutationResolver interface {
CreateOrganization(ctx context.Context, input types.CreateOrganizationInput) (*types.CreateOrganizationPayload, error) CreateOrganization(ctx context.Context, input types.CreateOrganizationInput) (*types.CreateOrganizationPayload, error)
UpdateOrganization(ctx context.Context, input types.UpdateOrganizationInput) (*types.UpdateOrganizationPayload, error) UpdateOrganization(ctx context.Context, input types.UpdateOrganizationInput) (*types.UpdateOrganizationPayload, error)
DeleteOrganizationHorizontalLogo(ctx context.Context, input types.DeleteOrganizationHorizontalLogoInput) (*types.DeleteOrganizationHorizontalLogoPayload, error)
DeleteOrganization(ctx context.Context, input types.DeleteOrganizationInput) (*types.DeleteOrganizationPayload, error) DeleteOrganization(ctx context.Context, input types.DeleteOrganizationInput) (*types.DeleteOrganizationPayload, error)
UpdateTrustCenter(ctx context.Context, input types.UpdateTrustCenterInput) (*types.UpdateTrustCenterPayload, error) UpdateTrustCenter(ctx context.Context, input types.UpdateTrustCenterInput) (*types.UpdateTrustCenterPayload, error)
UploadTrustCenterNda(ctx context.Context, input types.UploadTrustCenterNDAInput) (*types.UploadTrustCenterNDAPayload, error) UploadTrustCenterNda(ctx context.Context, input types.UploadTrustCenterNDAInput) (*types.UploadTrustCenterNDAPayload, error)
@@ -1809,6 +1816,7 @@ type ObligationConnectionResolver interface {
} }
type OrganizationResolver interface { type OrganizationResolver interface {
LogoURL(ctx context.Context, obj *types.Organization) (*string, error) LogoURL(ctx context.Context, obj *types.Organization) (*string, error)
HorizontalLogoURL(ctx context.Context, obj *types.Organization) (*string, error)
Users(ctx context.Context, obj *types.Organization, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.UserOrderBy) (*types.UserConnection, error) Users(ctx context.Context, obj *types.Organization, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.UserOrderBy) (*types.UserConnection, error)
Connectors(ctx context.Context, obj *types.Organization, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.ConnectorOrder) (*types.ConnectorConnection, error) Connectors(ctx context.Context, obj *types.Organization, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.ConnectorOrder) (*types.ConnectorConnection, error)
@@ -3239,6 +3247,13 @@ func (e *executableSchema) Complexity(ctx context.Context, typeName, field strin
return e.complexity.DeleteObligationPayload.DeletedObligationID(childComplexity), true return e.complexity.DeleteObligationPayload.DeletedObligationID(childComplexity), true
case "DeleteOrganizationHorizontalLogoPayload.organization":
if e.complexity.DeleteOrganizationHorizontalLogoPayload.Organization == nil {
break
}
return e.complexity.DeleteOrganizationHorizontalLogoPayload.Organization(childComplexity), true
case "DeleteOrganizationPayload.deletedOrganizationId": case "DeleteOrganizationPayload.deletedOrganizationId":
if e.complexity.DeleteOrganizationPayload.DeletedOrganizationID == nil { if e.complexity.DeleteOrganizationPayload.DeletedOrganizationID == nil {
break break
@@ -4850,6 +4865,18 @@ func (e *executableSchema) Complexity(ctx context.Context, typeName, field strin
return e.complexity.Mutation.DeleteOrganization(childComplexity, args["input"].(types.DeleteOrganizationInput)), true return e.complexity.Mutation.DeleteOrganization(childComplexity, args["input"].(types.DeleteOrganizationInput)), true
case "Mutation.deleteOrganizationHorizontalLogo":
if e.complexity.Mutation.DeleteOrganizationHorizontalLogo == nil {
break
}
args, err := ec.field_Mutation_deleteOrganizationHorizontalLogo_args(ctx, rawArgs)
if err != nil {
return 0, false
}
return e.complexity.Mutation.DeleteOrganizationHorizontalLogo(childComplexity, args["input"].(types.DeleteOrganizationHorizontalLogoInput)), true
case "Mutation.deletePeople": case "Mutation.deletePeople":
if e.complexity.Mutation.DeletePeople == nil { if e.complexity.Mutation.DeletePeople == nil {
break break
@@ -5969,6 +5996,13 @@ func (e *executableSchema) Complexity(ctx context.Context, typeName, field strin
return e.complexity.Organization.HeadquarterAddress(childComplexity), true return e.complexity.Organization.HeadquarterAddress(childComplexity), true
case "Organization.horizontalLogoUrl":
if e.complexity.Organization.HorizontalLogoURL == nil {
break
}
return e.complexity.Organization.HorizontalLogoURL(childComplexity), true
case "Organization.id": case "Organization.id":
if e.complexity.Organization.ID == nil { if e.complexity.Organization.ID == nil {
break break
@@ -8506,6 +8540,7 @@ func (e *executableSchema) Exec(ctx context.Context) graphql.ResponseHandler {
ec.unmarshalInputDeleteMeasureInput, ec.unmarshalInputDeleteMeasureInput,
ec.unmarshalInputDeleteNonconformityInput, ec.unmarshalInputDeleteNonconformityInput,
ec.unmarshalInputDeleteObligationInput, ec.unmarshalInputDeleteObligationInput,
ec.unmarshalInputDeleteOrganizationHorizontalLogoInput,
ec.unmarshalInputDeleteOrganizationInput, ec.unmarshalInputDeleteOrganizationInput,
ec.unmarshalInputDeletePeopleInput, ec.unmarshalInputDeletePeopleInput,
ec.unmarshalInputDeleteProcessingActivityInput, ec.unmarshalInputDeleteProcessingActivityInput,
@@ -10188,6 +10223,7 @@ type Organization implements Node {
id: ID! id: ID!
name: String! name: String!
logoUrl: String @goField(forceResolver: true) logoUrl: String @goField(forceResolver: true)
horizontalLogoUrl: String @goField(forceResolver: true)
description: String description: String
websiteUrl: String websiteUrl: String
@@ -11319,6 +11355,9 @@ type Mutation {
updateOrganization( updateOrganization(
input: UpdateOrganizationInput! input: UpdateOrganizationInput!
): UpdateOrganizationPayload! ): UpdateOrganizationPayload!
deleteOrganizationHorizontalLogo(
input: DeleteOrganizationHorizontalLogoInput!
): DeleteOrganizationHorizontalLogoPayload!
deleteOrganization( deleteOrganization(
input: DeleteOrganizationInput! input: DeleteOrganizationInput!
): DeleteOrganizationPayload! ): DeleteOrganizationPayload!
@@ -11641,12 +11680,17 @@ input UpdateOrganizationInput {
organizationId: ID! organizationId: ID!
name: String name: String
logo: Upload logo: Upload
horizontalLogoFile: Upload
description: String description: String
websiteUrl: String websiteUrl: String
email: String email: String
headquarterAddress: String headquarterAddress: String
} }
input DeleteOrganizationHorizontalLogoInput {
organizationId: ID!
}
input DeleteOrganizationInput { input DeleteOrganizationInput {
organizationId: ID! organizationId: ID!
} }
@@ -12328,6 +12372,10 @@ type UpdateOrganizationPayload {
organization: Organization! organization: Organization!
} }
type DeleteOrganizationHorizontalLogoPayload {
organization: Organization!
}
type DeleteOrganizationPayload { type DeleteOrganizationPayload {
deletedOrganizationId: ID! deletedOrganizationId: ID!
} }
@@ -16056,6 +16104,29 @@ func (ec *executionContext) field_Mutation_deleteObligation_argsInput(
return zeroVal, nil return zeroVal, nil
} }
func (ec *executionContext) field_Mutation_deleteOrganizationHorizontalLogo_args(ctx context.Context, rawArgs map[string]any) (map[string]any, error) {
var err error
args := map[string]any{}
arg0, err := ec.field_Mutation_deleteOrganizationHorizontalLogo_argsInput(ctx, rawArgs)
if err != nil {
return nil, err
}
args["input"] = arg0
return args, nil
}
func (ec *executionContext) field_Mutation_deleteOrganizationHorizontalLogo_argsInput(
ctx context.Context,
rawArgs map[string]any,
) (types.DeleteOrganizationHorizontalLogoInput, error) {
ctx = graphql.WithPathContext(ctx, graphql.NewPathWithField("input"))
if tmp, ok := rawArgs["input"]; ok {
return ec.unmarshalNDeleteOrganizationHorizontalLogoInput2githubᚗcomᚋgetproboᚋproboᚋpkgᚋserverᚋapiᚋconsoleᚋv1ᚋtypesᚐDeleteOrganizationHorizontalLogoInput(ctx, tmp)
}
var zeroVal types.DeleteOrganizationHorizontalLogoInput
return zeroVal, nil
}
func (ec *executionContext) field_Mutation_deleteOrganization_args(ctx context.Context, rawArgs map[string]any) (map[string]any, error) { func (ec *executionContext) field_Mutation_deleteOrganization_args(ctx context.Context, rawArgs map[string]any) (map[string]any, error) {
var err error var err error
args := map[string]any{} args := map[string]any{}
@@ -21512,6 +21583,8 @@ func (ec *executionContext) fieldContext_Asset_organization(_ context.Context, f
return ec.fieldContext_Organization_name(ctx, field) return ec.fieldContext_Organization_name(ctx, field)
case "logoUrl": case "logoUrl":
return ec.fieldContext_Organization_logoUrl(ctx, field) return ec.fieldContext_Organization_logoUrl(ctx, field)
case "horizontalLogoUrl":
return ec.fieldContext_Organization_horizontalLogoUrl(ctx, field)
case "description": case "description":
return ec.fieldContext_Organization_description(ctx, field) return ec.fieldContext_Organization_description(ctx, field)
case "websiteUrl": case "websiteUrl":
@@ -22119,6 +22192,8 @@ func (ec *executionContext) fieldContext_Audit_organization(_ context.Context, f
return ec.fieldContext_Organization_name(ctx, field) return ec.fieldContext_Organization_name(ctx, field)
case "logoUrl": case "logoUrl":
return ec.fieldContext_Organization_logoUrl(ctx, field) return ec.fieldContext_Organization_logoUrl(ctx, field)
case "horizontalLogoUrl":
return ec.fieldContext_Organization_horizontalLogoUrl(ctx, field)
case "description": case "description":
return ec.fieldContext_Organization_description(ctx, field) return ec.fieldContext_Organization_description(ctx, field)
case "websiteUrl": case "websiteUrl":
@@ -23846,6 +23921,8 @@ func (ec *executionContext) fieldContext_ContinualImprovement_organization(_ con
return ec.fieldContext_Organization_name(ctx, field) return ec.fieldContext_Organization_name(ctx, field)
case "logoUrl": case "logoUrl":
return ec.fieldContext_Organization_logoUrl(ctx, field) return ec.fieldContext_Organization_logoUrl(ctx, field)
case "horizontalLogoUrl":
return ec.fieldContext_Organization_horizontalLogoUrl(ctx, field)
case "description": case "description":
return ec.fieldContext_Organization_description(ctx, field) return ec.fieldContext_Organization_description(ctx, field)
case "websiteUrl": case "websiteUrl":
@@ -27604,6 +27681,8 @@ func (ec *executionContext) fieldContext_CustomDomain_organization(_ context.Con
return ec.fieldContext_Organization_name(ctx, field) return ec.fieldContext_Organization_name(ctx, field)
case "logoUrl": case "logoUrl":
return ec.fieldContext_Organization_logoUrl(ctx, field) return ec.fieldContext_Organization_logoUrl(ctx, field)
case "horizontalLogoUrl":
return ec.fieldContext_Organization_horizontalLogoUrl(ctx, field)
case "description": case "description":
return ec.fieldContext_Organization_description(ctx, field) return ec.fieldContext_Organization_description(ctx, field)
case "websiteUrl": case "websiteUrl":
@@ -28503,6 +28582,8 @@ func (ec *executionContext) fieldContext_Datum_organization(_ context.Context, f
return ec.fieldContext_Organization_name(ctx, field) return ec.fieldContext_Organization_name(ctx, field)
case "logoUrl": case "logoUrl":
return ec.fieldContext_Organization_logoUrl(ctx, field) return ec.fieldContext_Organization_logoUrl(ctx, field)
case "horizontalLogoUrl":
return ec.fieldContext_Organization_horizontalLogoUrl(ctx, field)
case "description": case "description":
return ec.fieldContext_Organization_description(ctx, field) return ec.fieldContext_Organization_description(ctx, field)
case "websiteUrl": case "websiteUrl":
@@ -29902,6 +29983,112 @@ func (ec *executionContext) fieldContext_DeleteObligationPayload_deletedObligati
return fc, nil return fc, nil
} }
func (ec *executionContext) _DeleteOrganizationHorizontalLogoPayload_organization(ctx context.Context, field graphql.CollectedField, obj *types.DeleteOrganizationHorizontalLogoPayload) (ret graphql.Marshaler) {
fc, err := ec.fieldContext_DeleteOrganizationHorizontalLogoPayload_organization(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.Organization, 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.(*types.Organization)
fc.Result = res
return ec.marshalNOrganization2ᚖgithubᚗcomᚋgetproboᚋproboᚋpkgᚋserverᚋapiᚋconsoleᚋv1ᚋtypesᚐOrganization(ctx, field.Selections, res)
}
func (ec *executionContext) fieldContext_DeleteOrganizationHorizontalLogoPayload_organization(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) {
fc = &graphql.FieldContext{
Object: "DeleteOrganizationHorizontalLogoPayload",
Field: field,
IsMethod: false,
IsResolver: false,
Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) {
switch field.Name {
case "id":
return ec.fieldContext_Organization_id(ctx, field)
case "name":
return ec.fieldContext_Organization_name(ctx, field)
case "logoUrl":
return ec.fieldContext_Organization_logoUrl(ctx, field)
case "horizontalLogoUrl":
return ec.fieldContext_Organization_horizontalLogoUrl(ctx, field)
case "description":
return ec.fieldContext_Organization_description(ctx, field)
case "websiteUrl":
return ec.fieldContext_Organization_websiteUrl(ctx, field)
case "email":
return ec.fieldContext_Organization_email(ctx, field)
case "headquarterAddress":
return ec.fieldContext_Organization_headquarterAddress(ctx, field)
case "users":
return ec.fieldContext_Organization_users(ctx, field)
case "connectors":
return ec.fieldContext_Organization_connectors(ctx, field)
case "frameworks":
return ec.fieldContext_Organization_frameworks(ctx, field)
case "controls":
return ec.fieldContext_Organization_controls(ctx, field)
case "vendors":
return ec.fieldContext_Organization_vendors(ctx, field)
case "peoples":
return ec.fieldContext_Organization_peoples(ctx, field)
case "documents":
return ec.fieldContext_Organization_documents(ctx, field)
case "measures":
return ec.fieldContext_Organization_measures(ctx, field)
case "risks":
return ec.fieldContext_Organization_risks(ctx, field)
case "tasks":
return ec.fieldContext_Organization_tasks(ctx, field)
case "assets":
return ec.fieldContext_Organization_assets(ctx, field)
case "data":
return ec.fieldContext_Organization_data(ctx, field)
case "audits":
return ec.fieldContext_Organization_audits(ctx, field)
case "nonconformities":
return ec.fieldContext_Organization_nonconformities(ctx, field)
case "obligations":
return ec.fieldContext_Organization_obligations(ctx, field)
case "continualImprovements":
return ec.fieldContext_Organization_continualImprovements(ctx, field)
case "processingActivities":
return ec.fieldContext_Organization_processingActivities(ctx, field)
case "snapshots":
return ec.fieldContext_Organization_snapshots(ctx, field)
case "trustCenter":
return ec.fieldContext_Organization_trustCenter(ctx, field)
case "customDomain":
return ec.fieldContext_Organization_customDomain(ctx, field)
case "createdAt":
return ec.fieldContext_Organization_createdAt(ctx, field)
case "updatedAt":
return ec.fieldContext_Organization_updatedAt(ctx, field)
}
return nil, fmt.Errorf("no field named %q was found under type Organization", field.Name)
},
}
return fc, nil
}
func (ec *executionContext) _DeleteOrganizationPayload_deletedOrganizationId(ctx context.Context, field graphql.CollectedField, obj *types.DeleteOrganizationPayload) (ret graphql.Marshaler) { func (ec *executionContext) _DeleteOrganizationPayload_deletedOrganizationId(ctx context.Context, field graphql.CollectedField, obj *types.DeleteOrganizationPayload) (ret graphql.Marshaler) {
fc, err := ec.fieldContext_DeleteOrganizationPayload_deletedOrganizationId(ctx, field) fc, err := ec.fieldContext_DeleteOrganizationPayload_deletedOrganizationId(ctx, field)
if err != nil { if err != nil {
@@ -31220,6 +31407,8 @@ func (ec *executionContext) fieldContext_Document_organization(_ context.Context
return ec.fieldContext_Organization_name(ctx, field) return ec.fieldContext_Organization_name(ctx, field)
case "logoUrl": case "logoUrl":
return ec.fieldContext_Organization_logoUrl(ctx, field) return ec.fieldContext_Organization_logoUrl(ctx, field)
case "horizontalLogoUrl":
return ec.fieldContext_Organization_horizontalLogoUrl(ctx, field)
case "description": case "description":
return ec.fieldContext_Organization_description(ctx, field) return ec.fieldContext_Organization_description(ctx, field)
case "websiteUrl": case "websiteUrl":
@@ -34542,6 +34731,8 @@ func (ec *executionContext) fieldContext_Framework_organization(_ context.Contex
return ec.fieldContext_Organization_name(ctx, field) return ec.fieldContext_Organization_name(ctx, field)
case "logoUrl": case "logoUrl":
return ec.fieldContext_Organization_logoUrl(ctx, field) return ec.fieldContext_Organization_logoUrl(ctx, field)
case "horizontalLogoUrl":
return ec.fieldContext_Organization_horizontalLogoUrl(ctx, field)
case "description": case "description":
return ec.fieldContext_Organization_description(ctx, field) return ec.fieldContext_Organization_description(ctx, field)
case "websiteUrl": case "websiteUrl":
@@ -36224,6 +36415,65 @@ func (ec *executionContext) fieldContext_Mutation_updateOrganization(ctx context
return fc, nil return fc, nil
} }
func (ec *executionContext) _Mutation_deleteOrganizationHorizontalLogo(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) {
fc, err := ec.fieldContext_Mutation_deleteOrganizationHorizontalLogo(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().DeleteOrganizationHorizontalLogo(rctx, fc.Args["input"].(types.DeleteOrganizationHorizontalLogoInput))
})
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.DeleteOrganizationHorizontalLogoPayload)
fc.Result = res
return ec.marshalNDeleteOrganizationHorizontalLogoPayload2ᚖgithubᚗcomᚋgetproboᚋproboᚋpkgᚋserverᚋapiᚋconsoleᚋv1ᚋtypesᚐDeleteOrganizationHorizontalLogoPayload(ctx, field.Selections, res)
}
func (ec *executionContext) fieldContext_Mutation_deleteOrganizationHorizontalLogo(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 "organization":
return ec.fieldContext_DeleteOrganizationHorizontalLogoPayload_organization(ctx, field)
}
return nil, fmt.Errorf("no field named %q was found under type DeleteOrganizationHorizontalLogoPayload", 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_deleteOrganizationHorizontalLogo_args(ctx, field.ArgumentMap(ec.Variables)); err != nil {
ec.Error(ctx, err)
return fc, err
}
return fc, nil
}
func (ec *executionContext) _Mutation_deleteOrganization(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) { func (ec *executionContext) _Mutation_deleteOrganization(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) {
fc, err := ec.fieldContext_Mutation_deleteOrganization(ctx, field) fc, err := ec.fieldContext_Mutation_deleteOrganization(ctx, field)
if err != nil { if err != nil {
@@ -43173,6 +43423,8 @@ func (ec *executionContext) fieldContext_Nonconformity_organization(_ context.Co
return ec.fieldContext_Organization_name(ctx, field) return ec.fieldContext_Organization_name(ctx, field)
case "logoUrl": case "logoUrl":
return ec.fieldContext_Organization_logoUrl(ctx, field) return ec.fieldContext_Organization_logoUrl(ctx, field)
case "horizontalLogoUrl":
return ec.fieldContext_Organization_horizontalLogoUrl(ctx, field)
case "description": case "description":
return ec.fieldContext_Organization_description(ctx, field) return ec.fieldContext_Organization_description(ctx, field)
case "websiteUrl": case "websiteUrl":
@@ -44234,6 +44486,8 @@ func (ec *executionContext) fieldContext_Obligation_organization(_ context.Conte
return ec.fieldContext_Organization_name(ctx, field) return ec.fieldContext_Organization_name(ctx, field)
case "logoUrl": case "logoUrl":
return ec.fieldContext_Organization_logoUrl(ctx, field) return ec.fieldContext_Organization_logoUrl(ctx, field)
case "horizontalLogoUrl":
return ec.fieldContext_Organization_horizontalLogoUrl(ctx, field)
case "description": case "description":
return ec.fieldContext_Organization_description(ctx, field) return ec.fieldContext_Organization_description(ctx, field)
case "websiteUrl": case "websiteUrl":
@@ -45175,6 +45429,47 @@ func (ec *executionContext) fieldContext_Organization_logoUrl(_ context.Context,
return fc, nil return fc, nil
} }
func (ec *executionContext) _Organization_horizontalLogoUrl(ctx context.Context, field graphql.CollectedField, obj *types.Organization) (ret graphql.Marshaler) {
fc, err := ec.fieldContext_Organization_horizontalLogoUrl(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.Organization().HorizontalLogoURL(rctx, obj)
})
if err != nil {
ec.Error(ctx, err)
return graphql.Null
}
if resTmp == nil {
return graphql.Null
}
res := resTmp.(*string)
fc.Result = res
return ec.marshalOString2ᚖstring(ctx, field.Selections, res)
}
func (ec *executionContext) fieldContext_Organization_horizontalLogoUrl(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) {
fc = &graphql.FieldContext{
Object: "Organization",
Field: field,
IsMethod: true,
IsResolver: true,
Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) {
return nil, errors.New("field of type String does not have child fields")
},
}
return fc, nil
}
func (ec *executionContext) _Organization_description(ctx context.Context, field graphql.CollectedField, obj *types.Organization) (ret graphql.Marshaler) { func (ec *executionContext) _Organization_description(ctx context.Context, field graphql.CollectedField, obj *types.Organization) (ret graphql.Marshaler) {
fc, err := ec.fieldContext_Organization_description(ctx, field) fc, err := ec.fieldContext_Organization_description(ctx, field)
if err != nil { if err != nil {
@@ -46872,6 +47167,8 @@ func (ec *executionContext) fieldContext_OrganizationEdge_node(_ context.Context
return ec.fieldContext_Organization_name(ctx, field) return ec.fieldContext_Organization_name(ctx, field)
case "logoUrl": case "logoUrl":
return ec.fieldContext_Organization_logoUrl(ctx, field) return ec.fieldContext_Organization_logoUrl(ctx, field)
case "horizontalLogoUrl":
return ec.fieldContext_Organization_horizontalLogoUrl(ctx, field)
case "description": case "description":
return ec.fieldContext_Organization_description(ctx, field) return ec.fieldContext_Organization_description(ctx, field)
case "websiteUrl": case "websiteUrl":
@@ -47961,6 +48258,8 @@ func (ec *executionContext) fieldContext_ProcessingActivity_organization(_ conte
return ec.fieldContext_Organization_name(ctx, field) return ec.fieldContext_Organization_name(ctx, field)
case "logoUrl": case "logoUrl":
return ec.fieldContext_Organization_logoUrl(ctx, field) return ec.fieldContext_Organization_logoUrl(ctx, field)
case "horizontalLogoUrl":
return ec.fieldContext_Organization_horizontalLogoUrl(ctx, field)
case "description": case "description":
return ec.fieldContext_Organization_description(ctx, field) return ec.fieldContext_Organization_description(ctx, field)
case "websiteUrl": case "websiteUrl":
@@ -50638,6 +50937,8 @@ func (ec *executionContext) fieldContext_Risk_organization(_ context.Context, fi
return ec.fieldContext_Organization_name(ctx, field) return ec.fieldContext_Organization_name(ctx, field)
case "logoUrl": case "logoUrl":
return ec.fieldContext_Organization_logoUrl(ctx, field) return ec.fieldContext_Organization_logoUrl(ctx, field)
case "horizontalLogoUrl":
return ec.fieldContext_Organization_horizontalLogoUrl(ctx, field)
case "description": case "description":
return ec.fieldContext_Organization_description(ctx, field) return ec.fieldContext_Organization_description(ctx, field)
case "websiteUrl": case "websiteUrl":
@@ -51538,6 +51839,8 @@ func (ec *executionContext) fieldContext_Snapshot_organization(_ context.Context
return ec.fieldContext_Organization_name(ctx, field) return ec.fieldContext_Organization_name(ctx, field)
case "logoUrl": case "logoUrl":
return ec.fieldContext_Organization_logoUrl(ctx, field) return ec.fieldContext_Organization_logoUrl(ctx, field)
case "horizontalLogoUrl":
return ec.fieldContext_Organization_horizontalLogoUrl(ctx, field)
case "description": case "description":
return ec.fieldContext_Organization_description(ctx, field) return ec.fieldContext_Organization_description(ctx, field)
case "websiteUrl": case "websiteUrl":
@@ -52451,6 +52754,8 @@ func (ec *executionContext) fieldContext_Task_organization(_ context.Context, fi
return ec.fieldContext_Organization_name(ctx, field) return ec.fieldContext_Organization_name(ctx, field)
case "logoUrl": case "logoUrl":
return ec.fieldContext_Organization_logoUrl(ctx, field) return ec.fieldContext_Organization_logoUrl(ctx, field)
case "horizontalLogoUrl":
return ec.fieldContext_Organization_horizontalLogoUrl(ctx, field)
case "description": case "description":
return ec.fieldContext_Organization_description(ctx, field) return ec.fieldContext_Organization_description(ctx, field)
case "websiteUrl": case "websiteUrl":
@@ -53335,6 +53640,8 @@ func (ec *executionContext) fieldContext_TrustCenter_organization(_ context.Cont
return ec.fieldContext_Organization_name(ctx, field) return ec.fieldContext_Organization_name(ctx, field)
case "logoUrl": case "logoUrl":
return ec.fieldContext_Organization_logoUrl(ctx, field) return ec.fieldContext_Organization_logoUrl(ctx, field)
case "horizontalLogoUrl":
return ec.fieldContext_Organization_horizontalLogoUrl(ctx, field)
case "description": case "description":
return ec.fieldContext_Organization_description(ctx, field) return ec.fieldContext_Organization_description(ctx, field)
case "websiteUrl": case "websiteUrl":
@@ -56374,6 +56681,8 @@ func (ec *executionContext) fieldContext_UpdateOrganizationPayload_organization(
return ec.fieldContext_Organization_name(ctx, field) return ec.fieldContext_Organization_name(ctx, field)
case "logoUrl": case "logoUrl":
return ec.fieldContext_Organization_logoUrl(ctx, field) return ec.fieldContext_Organization_logoUrl(ctx, field)
case "horizontalLogoUrl":
return ec.fieldContext_Organization_horizontalLogoUrl(ctx, field)
case "description": case "description":
return ec.fieldContext_Organization_description(ctx, field) return ec.fieldContext_Organization_description(ctx, field)
case "websiteUrl": case "websiteUrl":
@@ -58414,6 +58723,8 @@ func (ec *executionContext) fieldContext_Vendor_organization(_ context.Context,
return ec.fieldContext_Organization_name(ctx, field) return ec.fieldContext_Organization_name(ctx, field)
case "logoUrl": case "logoUrl":
return ec.fieldContext_Organization_logoUrl(ctx, field) return ec.fieldContext_Organization_logoUrl(ctx, field)
case "horizontalLogoUrl":
return ec.fieldContext_Organization_horizontalLogoUrl(ctx, field)
case "description": case "description":
return ec.fieldContext_Organization_description(ctx, field) return ec.fieldContext_Organization_description(ctx, field)
case "websiteUrl": case "websiteUrl":
@@ -68401,6 +68712,33 @@ func (ec *executionContext) unmarshalInputDeleteObligationInput(ctx context.Cont
return it, nil return it, nil
} }
func (ec *executionContext) unmarshalInputDeleteOrganizationHorizontalLogoInput(ctx context.Context, obj any) (types.DeleteOrganizationHorizontalLogoInput, error) {
var it types.DeleteOrganizationHorizontalLogoInput
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) unmarshalInputDeleteOrganizationInput(ctx context.Context, obj any) (types.DeleteOrganizationInput, error) { func (ec *executionContext) unmarshalInputDeleteOrganizationInput(ctx context.Context, obj any) (types.DeleteOrganizationInput, error) {
var it types.DeleteOrganizationInput var it types.DeleteOrganizationInput
asMap := map[string]any{} asMap := map[string]any{}
@@ -70950,7 +71288,7 @@ func (ec *executionContext) unmarshalInputUpdateOrganizationInput(ctx context.Co
asMap[k] = v asMap[k] = v
} }
fieldsInOrder := [...]string{"organizationId", "name", "logo", "description", "websiteUrl", "email", "headquarterAddress"} fieldsInOrder := [...]string{"organizationId", "name", "logo", "horizontalLogoFile", "description", "websiteUrl", "email", "headquarterAddress"}
for _, k := range fieldsInOrder { for _, k := range fieldsInOrder {
v, ok := asMap[k] v, ok := asMap[k]
if !ok { if !ok {
@@ -70978,6 +71316,13 @@ func (ec *executionContext) unmarshalInputUpdateOrganizationInput(ctx context.Co
return it, err return it, err
} }
it.Logo = data it.Logo = data
case "horizontalLogoFile":
ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("horizontalLogoFile"))
data, err := ec.unmarshalOUpload2ᚖgithubᚗcomᚋ99designsᚋgqlgenᚋgraphqlᚐUpload(ctx, v)
if err != nil {
return it, err
}
it.HorizontalLogoFile = data
case "description": case "description":
ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("description")) ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("description"))
data, err := ec.unmarshalOString2ᚖstring(ctx, v) data, err := ec.unmarshalOString2ᚖstring(ctx, v)
@@ -76888,6 +77233,45 @@ func (ec *executionContext) _DeleteObligationPayload(ctx context.Context, sel as
return out return out
} }
var deleteOrganizationHorizontalLogoPayloadImplementors = []string{"DeleteOrganizationHorizontalLogoPayload"}
func (ec *executionContext) _DeleteOrganizationHorizontalLogoPayload(ctx context.Context, sel ast.SelectionSet, obj *types.DeleteOrganizationHorizontalLogoPayload) graphql.Marshaler {
fields := graphql.CollectFields(ec.OperationContext, sel, deleteOrganizationHorizontalLogoPayloadImplementors)
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("DeleteOrganizationHorizontalLogoPayload")
case "organization":
out.Values[i] = ec._DeleteOrganizationHorizontalLogoPayload_organization(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 deleteOrganizationPayloadImplementors = []string{"DeleteOrganizationPayload"} var deleteOrganizationPayloadImplementors = []string{"DeleteOrganizationPayload"}
func (ec *executionContext) _DeleteOrganizationPayload(ctx context.Context, sel ast.SelectionSet, obj *types.DeleteOrganizationPayload) graphql.Marshaler { func (ec *executionContext) _DeleteOrganizationPayload(ctx context.Context, sel ast.SelectionSet, obj *types.DeleteOrganizationPayload) graphql.Marshaler {
@@ -79771,6 +80155,13 @@ func (ec *executionContext) _Mutation(ctx context.Context, sel ast.SelectionSet)
if out.Values[i] == graphql.Null { if out.Values[i] == graphql.Null {
out.Invalids++ out.Invalids++
} }
case "deleteOrganizationHorizontalLogo":
out.Values[i] = ec.OperationContext.RootResolverMiddleware(innerCtx, func(ctx context.Context) (res graphql.Marshaler) {
return ec._Mutation_deleteOrganizationHorizontalLogo(ctx, field)
})
if out.Values[i] == graphql.Null {
out.Invalids++
}
case "deleteOrganization": case "deleteOrganization":
out.Values[i] = ec.OperationContext.RootResolverMiddleware(innerCtx, func(ctx context.Context) (res graphql.Marshaler) { out.Values[i] = ec.OperationContext.RootResolverMiddleware(innerCtx, func(ctx context.Context) (res graphql.Marshaler) {
return ec._Mutation_deleteOrganization(ctx, field) return ec._Mutation_deleteOrganization(ctx, field)
@@ -81228,6 +81619,39 @@ func (ec *executionContext) _Organization(ctx context.Context, sel ast.Selection
continue continue
} }
out.Concurrently(i, func(ctx context.Context) graphql.Marshaler { return innerFunc(ctx, out) })
case "horizontalLogoUrl":
field := field
innerFunc := func(ctx context.Context, _ *graphql.FieldSet) (res graphql.Marshaler) {
defer func() {
if r := recover(); r != nil {
ec.Error(ctx, ec.Recover(ctx, r))
}
}()
res = ec._Organization_horizontalLogoUrl(ctx, field, obj)
return res
}
if field.Deferrable != nil {
dfs, ok := deferred[field.Deferrable.Label]
di := 0
if ok {
dfs.AddField(field)
di = len(dfs.Values) - 1
} else {
dfs = graphql.NewFieldSet([]graphql.CollectedField{field})
deferred[field.Deferrable.Label] = dfs
}
dfs.Concurrently(di, func(ctx context.Context) graphql.Marshaler {
return innerFunc(ctx, dfs)
})
// don't run the out.Concurrently() call below
out.Values[i] = graphql.Null
continue
}
out.Concurrently(i, func(ctx context.Context) graphql.Marshaler { return innerFunc(ctx, out) }) out.Concurrently(i, func(ctx context.Context) graphql.Marshaler { return innerFunc(ctx, out) })
case "description": case "description":
out.Values[i] = ec._Organization_description(ctx, field, obj) out.Values[i] = ec._Organization_description(ctx, field, obj)
@@ -91639,6 +92063,25 @@ func (ec *executionContext) marshalNDeleteObligationPayload2ᚖgithubᚗcomᚋge
return ec._DeleteObligationPayload(ctx, sel, v) return ec._DeleteObligationPayload(ctx, sel, v)
} }
func (ec *executionContext) unmarshalNDeleteOrganizationHorizontalLogoInput2githubᚗcomᚋgetproboᚋproboᚋpkgᚋserverᚋapiᚋconsoleᚋv1ᚋtypesᚐDeleteOrganizationHorizontalLogoInput(ctx context.Context, v any) (types.DeleteOrganizationHorizontalLogoInput, error) {
res, err := ec.unmarshalInputDeleteOrganizationHorizontalLogoInput(ctx, v)
return res, graphql.ErrorOnPath(ctx, err)
}
func (ec *executionContext) marshalNDeleteOrganizationHorizontalLogoPayload2githubᚗcomᚋgetproboᚋproboᚋpkgᚋserverᚋapiᚋconsoleᚋv1ᚋtypesᚐDeleteOrganizationHorizontalLogoPayload(ctx context.Context, sel ast.SelectionSet, v types.DeleteOrganizationHorizontalLogoPayload) graphql.Marshaler {
return ec._DeleteOrganizationHorizontalLogoPayload(ctx, sel, &v)
}
func (ec *executionContext) marshalNDeleteOrganizationHorizontalLogoPayload2ᚖgithubᚗcomᚋgetproboᚋproboᚋpkgᚋserverᚋapiᚋconsoleᚋv1ᚋtypesᚐDeleteOrganizationHorizontalLogoPayload(ctx context.Context, sel ast.SelectionSet, v *types.DeleteOrganizationHorizontalLogoPayload) 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._DeleteOrganizationHorizontalLogoPayload(ctx, sel, v)
}
func (ec *executionContext) unmarshalNDeleteOrganizationInput2githubᚗcomᚋgetproboᚋproboᚋpkgᚋserverᚋapiᚋconsoleᚋv1ᚋtypesᚐDeleteOrganizationInput(ctx context.Context, v any) (types.DeleteOrganizationInput, error) { func (ec *executionContext) unmarshalNDeleteOrganizationInput2githubᚗcomᚋgetproboᚋproboᚋpkgᚋserverᚋapiᚋconsoleᚋv1ᚋtypesᚐDeleteOrganizationInput(ctx context.Context, v any) (types.DeleteOrganizationInput, error) {
res, err := ec.unmarshalInputDeleteOrganizationInput(ctx, v) res, err := ec.unmarshalInputDeleteOrganizationInput(ctx, v)
return res, graphql.ErrorOnPath(ctx, err) return res, graphql.ErrorOnPath(ctx, err)

View File

@@ -831,6 +831,14 @@ type DeleteObligationPayload struct {
DeletedObligationID gid.GID `json:"deletedObligationId"` DeletedObligationID gid.GID `json:"deletedObligationId"`
} }
type DeleteOrganizationHorizontalLogoInput struct {
OrganizationID gid.GID `json:"organizationId"`
}
type DeleteOrganizationHorizontalLogoPayload struct {
Organization *Organization `json:"organization"`
}
type DeleteOrganizationInput struct { type DeleteOrganizationInput struct {
OrganizationID gid.GID `json:"organizationId"` OrganizationID gid.GID `json:"organizationId"`
} }
@@ -1288,6 +1296,7 @@ type Organization struct {
ID gid.GID `json:"id"` ID gid.GID `json:"id"`
Name string `json:"name"` Name string `json:"name"`
LogoURL *string `json:"logoUrl,omitempty"` LogoURL *string `json:"logoUrl,omitempty"`
HorizontalLogoURL *string `json:"horizontalLogoUrl,omitempty"`
Description *string `json:"description,omitempty"` Description *string `json:"description,omitempty"`
WebsiteURL *string `json:"websiteUrl,omitempty"` WebsiteURL *string `json:"websiteUrl,omitempty"`
Email *string `json:"email,omitempty"` Email *string `json:"email,omitempty"`
@@ -1798,6 +1807,7 @@ type UpdateOrganizationInput struct {
OrganizationID gid.GID `json:"organizationId"` OrganizationID gid.GID `json:"organizationId"`
Name *string `json:"name,omitempty"` Name *string `json:"name,omitempty"`
Logo *graphql.Upload `json:"logo,omitempty"` Logo *graphql.Upload `json:"logo,omitempty"`
HorizontalLogoFile *graphql.Upload `json:"horizontalLogoFile,omitempty"`
Description *string `json:"description,omitempty"` Description *string `json:"description,omitempty"`
WebsiteURL *string `json:"websiteUrl,omitempty"` WebsiteURL *string `json:"websiteUrl,omitempty"`
Email *string `json:"email,omitempty"` Email *string `json:"email,omitempty"`

View File

@@ -1099,9 +1099,18 @@ func (r *mutationResolver) UpdateOrganization(ctx context.Context, input types.U
} }
} }
if input.HorizontalLogoFile != nil {
req.HorizontalLogoFile = &probo.File{
Filename: input.HorizontalLogoFile.Filename,
ContentType: input.HorizontalLogoFile.ContentType,
Size: input.HorizontalLogoFile.Size,
Content: input.HorizontalLogoFile.File,
}
}
organization, err := prb.Organizations.Update(ctx, req) organization, err := prb.Organizations.Update(ctx, req)
if err != nil { if err != nil {
return nil, fmt.Errorf("cannot update organization: %w", err) panic(fmt.Errorf("cannot update organization: %w", err))
} }
return &types.UpdateOrganizationPayload{ return &types.UpdateOrganizationPayload{
@@ -1109,6 +1118,20 @@ func (r *mutationResolver) UpdateOrganization(ctx context.Context, input types.U
}, nil }, nil
} }
// DeleteOrganizationHorizontalLogo is the resolver for the deleteOrganizationHorizontalLogo field.
func (r *mutationResolver) DeleteOrganizationHorizontalLogo(ctx context.Context, input types.DeleteOrganizationHorizontalLogoInput) (*types.DeleteOrganizationHorizontalLogoPayload, error) {
prb := r.ProboService(ctx, input.OrganizationID.TenantID())
organization, err := prb.Organizations.DeleteHorizontalLogo(ctx, input.OrganizationID)
if err != nil {
return nil, fmt.Errorf("cannot delete horizontal logo: %w", err)
}
return &types.DeleteOrganizationHorizontalLogoPayload{
Organization: types.NewOrganization(organization),
}, nil
}
// DeleteOrganization is the resolver for the deleteOrganization field. // DeleteOrganization is the resolver for the deleteOrganization field.
func (r *mutationResolver) DeleteOrganization(ctx context.Context, input types.DeleteOrganizationInput) (*types.DeleteOrganizationPayload, error) { func (r *mutationResolver) DeleteOrganization(ctx context.Context, input types.DeleteOrganizationInput) (*types.DeleteOrganizationPayload, error) {
prb := r.ProboService(ctx, input.OrganizationID.TenantID()) prb := r.ProboService(ctx, input.OrganizationID.TenantID())
@@ -3497,6 +3520,13 @@ func (r *organizationResolver) LogoURL(ctx context.Context, obj *types.Organizat
return prb.Organizations.GenerateLogoURL(ctx, obj.ID, 1*time.Hour) return prb.Organizations.GenerateLogoURL(ctx, obj.ID, 1*time.Hour)
} }
// HorizontalLogoURL is the resolver for the horizontalLogoUrl field.
func (r *organizationResolver) HorizontalLogoURL(ctx context.Context, obj *types.Organization) (*string, error) {
prb := r.ProboService(ctx, obj.ID.TenantID())
return prb.Organizations.GenerateHorizontalLogoURL(ctx, obj.ID, 1*time.Hour)
}
// Users is the resolver for the users field. // Users is the resolver for the users field.
func (r *organizationResolver) Users(ctx context.Context, obj *types.Organization, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.UserOrderBy) (*types.UserConnection, error) { func (r *organizationResolver) Users(ctx context.Context, obj *types.Organization, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.UserOrderBy) (*types.UserConnection, error) {
pageOrderBy := page.OrderBy[coredata.UserOrderField]{ pageOrderBy := page.OrderBy[coredata.UserOrderField]{

View File

@@ -141,6 +141,7 @@ func (s *DocumentService) exportPDFData(
document := &coredata.Document{} document := &coredata.Document{}
version := &coredata.DocumentVersion{} version := &coredata.DocumentVersion{}
owner := &coredata.People{} owner := &coredata.People{}
organization := &coredata.Organization{}
err := s.svc.pg.WithConn( err := s.svc.pg.WithConn(
ctx, ctx,
@@ -161,6 +162,10 @@ func (s *DocumentService) exportPDFData(
return fmt.Errorf("cannot load document owner: %w", err) return fmt.Errorf("cannot load document owner: %w", err)
} }
if err := organization.LoadByID(ctx, conn, s.svc.scope, document.OrganizationID); err != nil {
return fmt.Errorf("cannot load organization: %w", err)
}
return nil return nil
}, },
) )
@@ -177,13 +182,28 @@ func (s *DocumentService) exportPDFData(
classification = docgen.ClassificationSecret classification = docgen.ClassificationSecret
} }
horizontalLogoBase64 := ""
if organization.HorizontalLogoFileID != nil {
fileRecord := &coredata.File{}
fileErr := s.svc.pg.WithConn(ctx, func(conn pg.Conn) error {
return fileRecord.LoadByID(ctx, conn, s.svc.scope, *organization.HorizontalLogoFileID)
})
if fileErr == nil {
base64Data, mimeType, logoErr := s.svc.fileManager.GetFileBase64(ctx, fileRecord)
if logoErr == nil {
horizontalLogoBase64 = fmt.Sprintf("data:%s;base64,%s", mimeType, base64Data)
}
}
}
docData := docgen.DocumentData{ docData := docgen.DocumentData{
Title: version.Title, Title: version.Title,
Content: version.Content, Content: version.Content,
Version: version.VersionNumber, Version: version.VersionNumber,
Classification: classification, Classification: classification,
Approver: owner.FullName, Approver: owner.FullName,
PublishedAt: version.PublishedAt, PublishedAt: version.PublishedAt,
CompanyHorizontalLogoBase64: horizontalLogoBase64,
} }
htmlContent, err := docgen.RenderHTML(docData) htmlContent, err := docgen.RenderHTML(docData)

View File

@@ -71,19 +71,30 @@ func (s OrganizationService) GenerateLogoURL(
return nil, fmt.Errorf("cannot get organization: %w", err) return nil, fmt.Errorf("cannot get organization: %w", err)
} }
if organization.LogoObjectKey == "" { if organization.LogoFileID == nil {
return nil, nil return nil, nil
} }
file := &coredata.File{}
err = s.svc.pg.WithConn(
ctx,
func(conn pg.Conn) error {
return file.LoadByID(ctx, conn, s.svc.scope, *organization.LogoFileID)
},
)
if err != nil {
return nil, fmt.Errorf("cannot load file: %w", err)
}
presignClient := s3.NewPresignClient(s.svc.s3) presignClient := s3.NewPresignClient(s.svc.s3)
encodedFilename := url.QueryEscape(organization.Name) encodedFilename := url.QueryEscape(file.FileName)
contentDisposition := fmt.Sprintf("attachment; filename=\"%s\"; filename*=UTF-8''%s", contentDisposition := fmt.Sprintf("attachment; filename=\"%s\"; filename*=UTF-8''%s",
encodedFilename, encodedFilename) encodedFilename, encodedFilename)
presignedReq, err := presignClient.PresignGetObject(ctx, &s3.GetObjectInput{ presignedReq, err := presignClient.PresignGetObject(ctx, &s3.GetObjectInput{
Bucket: aws.String(s.svc.bucket), Bucket: aws.String(s.svc.bucket),
Key: aws.String(organization.LogoObjectKey), Key: aws.String(file.FileKey),
ResponseCacheControl: aws.String("max-age=3600, public"), ResponseCacheControl: aws.String("max-age=3600, public"),
ResponseContentDisposition: aws.String(contentDisposition), ResponseContentDisposition: aws.String(contentDisposition),
}, func(opts *s3.PresignOptions) { }, func(opts *s3.PresignOptions) {

View File

@@ -18,6 +18,7 @@ import (
"github.com/aws/aws-sdk-go-v2/service/s3" "github.com/aws/aws-sdk-go-v2/service/s3"
"github.com/getprobo/probo/pkg/coredata" "github.com/getprobo/probo/pkg/coredata"
"github.com/getprobo/probo/pkg/crypto/cipher" "github.com/getprobo/probo/pkg/crypto/cipher"
"github.com/getprobo/probo/pkg/filemanager"
"github.com/getprobo/probo/pkg/gid" "github.com/getprobo/probo/pkg/gid"
"github.com/getprobo/probo/pkg/html2pdf" "github.com/getprobo/probo/pkg/html2pdf"
"github.com/getprobo/probo/pkg/probo" "github.com/getprobo/probo/pkg/probo"
@@ -35,27 +36,29 @@ type (
tokenSecret string tokenSecret string
usrmgr *usrmgr.Service usrmgr *usrmgr.Service
html2pdfConverter *html2pdf.Converter html2pdfConverter *html2pdf.Converter
fileManager *filemanager.Service
} }
TenantService struct { TenantService struct {
pg *pg.Client pg *pg.Client
s3 *s3.Client s3 *s3.Client
bucket string bucket string
scope coredata.Scoper scope coredata.Scoper
proboSvc *probo.Service proboSvc *probo.Service
encryptionKey cipher.EncryptionKey encryptionKey cipher.EncryptionKey
tokenSecret string tokenSecret string
usrmgr *usrmgr.Service usrmgr *usrmgr.Service
html2pdfConverter *html2pdf.Converter html2pdfConverter *html2pdf.Converter
TrustCenters *TrustCenterService fileManager *filemanager.Service
Documents *DocumentService TrustCenters *TrustCenterService
Audits *AuditService Documents *DocumentService
Vendors *VendorService Audits *AuditService
Frameworks *FrameworkService Vendors *VendorService
TrustCenterAccesses *TrustCenterAccessService Frameworks *FrameworkService
TrustCenterAccesses *TrustCenterAccessService
TrustCenterReferences *TrustCenterReferenceService TrustCenterReferences *TrustCenterReferenceService
Reports *ReportService Reports *ReportService
Organizations *OrganizationService Organizations *OrganizationService
} }
) )
@@ -67,6 +70,7 @@ func NewService(
tokenSecret string, tokenSecret string,
usrmgr *usrmgr.Service, usrmgr *usrmgr.Service,
html2pdfConverter *html2pdf.Converter, html2pdfConverter *html2pdf.Converter,
fileManagerService *filemanager.Service,
) *Service { ) *Service {
return &Service{ return &Service{
pg: pgClient, pg: pgClient,
@@ -76,6 +80,7 @@ func NewService(
tokenSecret: tokenSecret, tokenSecret: tokenSecret,
usrmgr: usrmgr, usrmgr: usrmgr,
html2pdfConverter: html2pdfConverter, html2pdfConverter: html2pdfConverter,
fileManager: fileManagerService,
} }
} }
@@ -90,6 +95,7 @@ func (s *Service) WithTenant(tenantID gid.TenantID) *TenantService {
tokenSecret: s.tokenSecret, tokenSecret: s.tokenSecret,
usrmgr: s.usrmgr, usrmgr: s.usrmgr,
html2pdfConverter: s.html2pdfConverter, html2pdfConverter: s.html2pdfConverter,
fileManager: s.fileManager,
} }
tenantService.TrustCenters = &TrustCenterService{svc: tenantService} tenantService.TrustCenters = &TrustCenterService{svc: tenantService}