Add company name and logo to documents
Signed-off-by: Sacha Al Himdani <sacha@getprobo.com>
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* @generated SignedSource<<267b2fc05603188ad26c38ce1485887b>>
|
||||
* @generated SignedSource<<5a41cd3709282172e4d5a3ca65d07b96>>
|
||||
* @lightSyntaxTransform
|
||||
* @nogrep
|
||||
*/
|
||||
@@ -151,6 +151,13 @@ return {
|
||||
"name": "logoUrl",
|
||||
"storageKey": null
|
||||
},
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "horizontalLogoUrl",
|
||||
"storageKey": null
|
||||
},
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
@@ -338,12 +345,12 @@ return {
|
||||
]
|
||||
},
|
||||
"params": {
|
||||
"cacheID": "5118b2778835b232009a9d5ef5647aba",
|
||||
"cacheID": "8b9e1f3b1e93e1823354e77f10764e54",
|
||||
"id": null,
|
||||
"metadata": {},
|
||||
"name": "OrganizationGraph_ViewQuery",
|
||||
"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"
|
||||
}
|
||||
};
|
||||
})();
|
||||
|
||||
@@ -4,6 +4,9 @@ import {
|
||||
Badge,
|
||||
Button,
|
||||
Card,
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogFooter,
|
||||
DropdownItem,
|
||||
Field,
|
||||
FileButton,
|
||||
@@ -13,6 +16,7 @@ import {
|
||||
Spinner,
|
||||
Textarea,
|
||||
useConfirm,
|
||||
useDialogRef,
|
||||
useToast,
|
||||
} from "@probo/ui";
|
||||
import { useTranslate } from "@probo/i18n";
|
||||
@@ -58,6 +62,7 @@ const organizationFragment = graphql`
|
||||
id
|
||||
name
|
||||
logoUrl
|
||||
horizontalLogoUrl
|
||||
description
|
||||
websiteUrl
|
||||
email
|
||||
@@ -107,6 +112,7 @@ const updateOrganizationMutation = graphql`
|
||||
id
|
||||
name
|
||||
logoUrl
|
||||
horizontalLogoUrl
|
||||
description
|
||||
websiteUrl
|
||||
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) {
|
||||
const { __ } = useTranslate();
|
||||
const navigate = useNavigate();
|
||||
@@ -129,6 +146,13 @@ export default function SettingsPage({ queryRef }: Props) {
|
||||
organizationKey
|
||||
);
|
||||
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 users = organization.users.edges.map((edge) => edge.node);
|
||||
|
||||
@@ -167,10 +191,10 @@ export default function SettingsPage({ queryRef }: Props) {
|
||||
headquarterAddress: data.headquarterAddress || null,
|
||||
},
|
||||
},
|
||||
onError(error) {
|
||||
onError() {
|
||||
toast({
|
||||
title: __("Failed to update organization"),
|
||||
description: error.message || __("Please try again."),
|
||||
title: __("Error"),
|
||||
description: __("Failed to update organization."),
|
||||
variant: "error",
|
||||
});
|
||||
},
|
||||
@@ -201,13 +225,67 @@ export default function SettingsPage({ queryRef }: Props) {
|
||||
uploadables: {
|
||||
"input.logo": file,
|
||||
},
|
||||
onError(error) {
|
||||
onError() {
|
||||
toast({
|
||||
title: __("Failed to update organization logo"),
|
||||
description: error.message || __("Please try again."),
|
||||
title: __("Error"),
|
||||
description: __("Failed to update logo"),
|
||||
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}
|
||||
variant="secondary"
|
||||
className="ml-auto"
|
||||
accept="image/png,image/jpeg,image/jpg"
|
||||
>
|
||||
{__("Change logo")}
|
||||
</FileButton>
|
||||
</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
|
||||
{...register("name")}
|
||||
readOnly={formState.isSubmitting}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* @generated SignedSource<<daff4e27d1bdd110777f99826abd4e17>>
|
||||
* @generated SignedSource<<122f3da12674107565d07159872492db>>
|
||||
* @lightSyntaxTransform
|
||||
* @nogrep
|
||||
*/
|
||||
@@ -40,6 +40,7 @@ export type SettingsPageFragment$data = {
|
||||
readonly description: string | null | undefined;
|
||||
readonly email: string | null | undefined;
|
||||
readonly headquarterAddress: string | null | undefined;
|
||||
readonly horizontalLogoUrl: string | null | undefined;
|
||||
readonly id: string;
|
||||
readonly logoUrl: string | null | undefined;
|
||||
readonly name: string;
|
||||
@@ -119,6 +120,13 @@ return {
|
||||
"name": "logoUrl",
|
||||
"storageKey": null
|
||||
},
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "horizontalLogoUrl",
|
||||
"storageKey": null
|
||||
},
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
@@ -302,6 +310,6 @@ return {
|
||||
};
|
||||
})();
|
||||
|
||||
(node as any).hash = "1f76d60a35b4b5116821419432ec4aed";
|
||||
(node as any).hash = "6cea6f88fb0d7b2ae7ef9053b9979898";
|
||||
|
||||
export default node;
|
||||
|
||||
113
apps/console/src/pages/organizations/__generated__/SettingsPage_DeleteHorizontalLogoMutation.graphql.ts
generated
Normal file
113
apps/console/src/pages/organizations/__generated__/SettingsPage_DeleteHorizontalLogoMutation.graphql.ts
generated
Normal 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;
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* @generated SignedSource<<892a78db951d547b53d595d219b94e75>>
|
||||
* @generated SignedSource<<ce571f7246b6f7500a132f28c8081f84>>
|
||||
* @lightSyntaxTransform
|
||||
* @nogrep
|
||||
*/
|
||||
@@ -13,6 +13,7 @@ export type UpdateOrganizationInput = {
|
||||
description?: string | null | undefined;
|
||||
email?: string | null | undefined;
|
||||
headquarterAddress?: string | null | undefined;
|
||||
horizontalLogoFile?: any | null | undefined;
|
||||
logo?: any | null | undefined;
|
||||
name?: string | null | undefined;
|
||||
organizationId: string;
|
||||
@@ -27,6 +28,7 @@ export type SettingsPage_UpdateMutation$data = {
|
||||
readonly description: string | null | undefined;
|
||||
readonly email: string | null | undefined;
|
||||
readonly headquarterAddress: string | null | undefined;
|
||||
readonly horizontalLogoUrl: string | null | undefined;
|
||||
readonly id: string;
|
||||
readonly logoUrl: string | null | undefined;
|
||||
readonly name: string;
|
||||
@@ -91,6 +93,13 @@ v1 = [
|
||||
"name": "logoUrl",
|
||||
"storageKey": null
|
||||
},
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "horizontalLogoUrl",
|
||||
"storageKey": null
|
||||
},
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
@@ -144,16 +153,16 @@ return {
|
||||
"selections": (v1/*: any*/)
|
||||
},
|
||||
"params": {
|
||||
"cacheID": "3526d270f311db33e0563abc99c96978",
|
||||
"cacheID": "097a6e519249d6f2b3c2a95963f85a6c",
|
||||
"id": null,
|
||||
"metadata": {},
|
||||
"name": "SettingsPage_UpdateMutation",
|
||||
"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;
|
||||
|
||||
@@ -20,13 +20,20 @@ export function FileButton({
|
||||
children,
|
||||
icon: IconComponent,
|
||||
ref,
|
||||
accept,
|
||||
...props
|
||||
}: Props) {
|
||||
return (
|
||||
<label className={button({ ...props })}>
|
||||
{IconComponent && <IconComponent size={16} className="flex-none" />}
|
||||
{children}
|
||||
<input type="file" onChange={onChange} hidden ref={ref} />
|
||||
<input
|
||||
type="file"
|
||||
onChange={onChange}
|
||||
hidden
|
||||
ref={ref}
|
||||
{...(accept && { accept })}
|
||||
/>
|
||||
</label>
|
||||
);
|
||||
}
|
||||
|
||||
51
pkg/coredata/migrations/20251009T140000Z.sql
Normal file
51
pkg/coredata/migrations/20251009T140000Z.sql
Normal 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;
|
||||
@@ -28,17 +28,18 @@ import (
|
||||
|
||||
type (
|
||||
Organization struct {
|
||||
ID gid.GID `db:"id"`
|
||||
TenantID gid.TenantID `db:"tenant_id"`
|
||||
Name string `db:"name"`
|
||||
LogoObjectKey string `db:"logo_object_key"`
|
||||
Description *string `db:"description"`
|
||||
WebsiteURL *string `db:"website_url"`
|
||||
Email *string `db:"email"`
|
||||
HeadquarterAddress *string `db:"headquarter_address"`
|
||||
CustomDomainID *gid.GID `db:"custom_domain_id"`
|
||||
CreatedAt time.Time `db:"created_at"`
|
||||
UpdatedAt time.Time `db:"updated_at"`
|
||||
ID gid.GID `db:"id"`
|
||||
TenantID gid.TenantID `db:"tenant_id"`
|
||||
Name string `db:"name"`
|
||||
LogoFileID *gid.GID `db:"logo_file_id"`
|
||||
HorizontalLogoFileID *gid.GID `db:"horizontal_logo_file_id"`
|
||||
Description *string `db:"description"`
|
||||
WebsiteURL *string `db:"website_url"`
|
||||
Email *string `db:"email"`
|
||||
HeadquarterAddress *string `db:"headquarter_address"`
|
||||
CustomDomainID *gid.GID `db:"custom_domain_id"`
|
||||
CreatedAt time.Time `db:"created_at"`
|
||||
UpdatedAt time.Time `db:"updated_at"`
|
||||
}
|
||||
|
||||
Organizations []*Organization
|
||||
@@ -68,7 +69,8 @@ SELECT
|
||||
tenant_id,
|
||||
id,
|
||||
name,
|
||||
logo_object_key,
|
||||
logo_file_id,
|
||||
horizontal_logo_file_id,
|
||||
description,
|
||||
website_url,
|
||||
email,
|
||||
@@ -124,7 +126,8 @@ SELECT
|
||||
tenant_id,
|
||||
id,
|
||||
name,
|
||||
logo_object_key,
|
||||
logo_file_id,
|
||||
horizontal_logo_file_id,
|
||||
description,
|
||||
website_url,
|
||||
email,
|
||||
@@ -169,7 +172,8 @@ INSERT INTO organizations (
|
||||
tenant_id,
|
||||
id,
|
||||
name,
|
||||
logo_object_key,
|
||||
logo_file_id,
|
||||
horizontal_logo_file_id,
|
||||
description,
|
||||
website_url,
|
||||
email,
|
||||
@@ -177,21 +181,22 @@ INSERT INTO organizations (
|
||||
custom_domain_id,
|
||||
created_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{
|
||||
"tenant_id": o.TenantID,
|
||||
"id": o.ID,
|
||||
"name": o.Name,
|
||||
"logo_object_key": o.LogoObjectKey,
|
||||
"description": o.Description,
|
||||
"website_url": o.WebsiteURL,
|
||||
"email": o.Email,
|
||||
"headquarter_address": o.HeadquarterAddress,
|
||||
"custom_domain_id": o.CustomDomainID,
|
||||
"created_at": o.CreatedAt,
|
||||
"updated_at": o.UpdatedAt,
|
||||
"tenant_id": o.TenantID,
|
||||
"id": o.ID,
|
||||
"name": o.Name,
|
||||
"logo_file_id": o.LogoFileID,
|
||||
"horizontal_logo_file_id": o.HorizontalLogoFileID,
|
||||
"description": o.Description,
|
||||
"website_url": o.WebsiteURL,
|
||||
"email": o.Email,
|
||||
"headquarter_address": o.HeadquarterAddress,
|
||||
"custom_domain_id": o.CustomDomainID,
|
||||
"created_at": o.CreatedAt,
|
||||
"updated_at": o.UpdatedAt,
|
||||
}
|
||||
|
||||
_, err := conn.Exec(ctx, q, args)
|
||||
@@ -211,7 +216,8 @@ func (o *Organization) Update(
|
||||
UPDATE organizations
|
||||
SET
|
||||
name = @name,
|
||||
logo_object_key = @logo_object_key,
|
||||
logo_file_id = @logo_file_id,
|
||||
horizontal_logo_file_id = @horizontal_logo_file_id,
|
||||
description = @description,
|
||||
website_url = @website_url,
|
||||
email = @email,
|
||||
@@ -226,15 +232,16 @@ WHERE
|
||||
q = fmt.Sprintf(q, scope.SQLFragment())
|
||||
|
||||
args := pgx.StrictNamedArgs{
|
||||
"id": o.ID,
|
||||
"name": o.Name,
|
||||
"logo_object_key": o.LogoObjectKey,
|
||||
"description": o.Description,
|
||||
"website_url": o.WebsiteURL,
|
||||
"email": o.Email,
|
||||
"headquarter_address": o.HeadquarterAddress,
|
||||
"custom_domain_id": o.CustomDomainID,
|
||||
"updated_at": o.UpdatedAt,
|
||||
"id": o.ID,
|
||||
"name": o.Name,
|
||||
"logo_file_id": o.LogoFileID,
|
||||
"horizontal_logo_file_id": o.HorizontalLogoFileID,
|
||||
"description": o.Description,
|
||||
"website_url": o.WebsiteURL,
|
||||
"email": o.Email,
|
||||
"headquarter_address": o.HeadquarterAddress,
|
||||
"custom_domain_id": o.CustomDomainID,
|
||||
"updated_at": o.UpdatedAt,
|
||||
}
|
||||
|
||||
maps.Copy(args, scope.SQLArguments())
|
||||
@@ -283,7 +290,8 @@ SELECT
|
||||
tenant_id,
|
||||
id,
|
||||
name,
|
||||
logo_object_key,
|
||||
logo_file_id,
|
||||
horizontal_logo_file_id,
|
||||
description,
|
||||
website_url,
|
||||
email,
|
||||
|
||||
@@ -53,6 +53,9 @@ var (
|
||||
}
|
||||
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))
|
||||
@@ -62,14 +65,15 @@ type (
|
||||
Classification string
|
||||
|
||||
DocumentData struct {
|
||||
Title string
|
||||
Content string
|
||||
Version int
|
||||
Classification Classification
|
||||
Approver string
|
||||
Description string
|
||||
PublishedAt *time.Time
|
||||
Signatures []SignatureData
|
||||
Title string
|
||||
Content string
|
||||
Version int
|
||||
Classification Classification
|
||||
Approver string
|
||||
Description string
|
||||
PublishedAt *time.Time
|
||||
Signatures []SignatureData
|
||||
CompanyHorizontalLogoBase64 string
|
||||
}
|
||||
|
||||
SignatureData struct {
|
||||
|
||||
@@ -11,17 +11,17 @@
|
||||
margin: 2.5cm;
|
||||
@bottom-right {
|
||||
content: "Page " counter(page) " of " counter(pages);
|
||||
font-family: 'Times New Roman', Times, serif;
|
||||
font-family: Arial, sans-serif;
|
||||
font-size: 9pt;
|
||||
color: #666;
|
||||
}
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: 'Times New Roman', Times, serif;
|
||||
font-family: Arial, sans-serif;
|
||||
font-size: 11pt;
|
||||
line-height: 1.4;
|
||||
color: #000;
|
||||
line-height: 1.6;
|
||||
color: #333;
|
||||
margin: 0;
|
||||
padding: 20px 0;
|
||||
background: #f5f5f5;
|
||||
@@ -49,46 +49,58 @@
|
||||
page-break-after: auto;
|
||||
}
|
||||
|
||||
|
||||
|
||||
.document-header {
|
||||
border-bottom: 1px solid #333;
|
||||
padding-bottom: 15px;
|
||||
padding-bottom: 0;
|
||||
margin-bottom: 25px;
|
||||
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 {
|
||||
font-size: 18pt;
|
||||
font-weight: bold;
|
||||
color: #000;
|
||||
margin: 0 0 10px 0;
|
||||
text-align: center;
|
||||
font-size: 22pt;
|
||||
font-weight: normal;
|
||||
color: #1a1a1a;
|
||||
margin: 0 0 25px 0;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.company-header + .document-title {
|
||||
margin-top: 30px;
|
||||
}
|
||||
|
||||
.document-meta {
|
||||
background: #f9f9f9;
|
||||
padding: 12px;
|
||||
border: 1px solid #ddd;
|
||||
margin: 15px 0;
|
||||
margin: 25px 0 0 0;
|
||||
font-size: 9pt;
|
||||
}
|
||||
|
||||
.meta-table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
border: 1px solid #333;
|
||||
}
|
||||
|
||||
.meta-table td {
|
||||
padding: 4px 8px;
|
||||
border-bottom: 1px solid #eee;
|
||||
vertical-align: top;
|
||||
padding: 6px 8px;
|
||||
border: 1px solid #333;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.meta-table td:first-child {
|
||||
font-weight: bold;
|
||||
font-weight: 600;
|
||||
width: 25%;
|
||||
background: #f8f8f8;
|
||||
color: #333;
|
||||
width: 120px;
|
||||
}
|
||||
|
||||
.classification {
|
||||
@@ -141,7 +153,7 @@
|
||||
}
|
||||
|
||||
.document-content p {
|
||||
margin-bottom: 12px;
|
||||
margin: 0 0 12px 0;
|
||||
text-align: justify;
|
||||
orphans: 3;
|
||||
widows: 3;
|
||||
@@ -312,18 +324,23 @@
|
||||
<div class="document-container">
|
||||
<div class="page-section">
|
||||
<div class="document-header">
|
||||
{{- if .CompanyHorizontalLogoBase64}}
|
||||
<div class="company-header">
|
||||
{{imgTag .CompanyHorizontalLogoBase64 "Company Logo" "company-logo"}}
|
||||
</div>
|
||||
{{- end}}
|
||||
<h1 class="document-title">{{.Title}}</h1>
|
||||
|
||||
<div class="document-meta">
|
||||
<table class="meta-table">
|
||||
<tr>
|
||||
<td>Classification:</td>
|
||||
<td>Classification</td>
|
||||
<td>
|
||||
<span class="classification">{{.Classification | classificationString}}</span>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Approver:</td>
|
||||
<td>Approver</td>
|
||||
<td>{{.Approver}}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
@@ -332,7 +349,7 @@
|
||||
</tr>
|
||||
{{- if .PublishedAt}}
|
||||
<tr>
|
||||
<td>Published:</td>
|
||||
<td>Published</td>
|
||||
<td>{{.PublishedAt.Format "January 2, 2006"}}</td>
|
||||
</tr>
|
||||
{{- end}}
|
||||
|
||||
148
pkg/filemanager/service.go
Normal file
148
pkg/filemanager/service.go
Normal 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
|
||||
}
|
||||
@@ -1201,7 +1201,7 @@ func (s *DocumentService) ExportPDF(
|
||||
err := s.svc.pg.WithTx(
|
||||
ctx,
|
||||
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 {
|
||||
return fmt.Errorf("cannot export document PDF: %w", err)
|
||||
}
|
||||
@@ -1333,6 +1333,7 @@ func (s *DocumentService) BuildAndUploadExport(ctx context.Context, exportJobID
|
||||
|
||||
func exportDocumentPDF(
|
||||
ctx context.Context,
|
||||
svc *TenantService,
|
||||
html2pdfConverter *html2pdf.Converter,
|
||||
conn pg.Conn,
|
||||
scope coredata.Scoper,
|
||||
@@ -1342,6 +1343,7 @@ func exportDocumentPDF(
|
||||
document := &coredata.Document{}
|
||||
version := &coredata.DocumentVersion{}
|
||||
owner := &coredata.People{}
|
||||
organization := &coredata.Organization{}
|
||||
signatures := coredata.DocumentVersionSignatures{}
|
||||
peopleMap := make(map[gid.GID]*coredata.People)
|
||||
|
||||
@@ -1357,6 +1359,10 @@ func exportDocumentPDF(
|
||||
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
|
||||
if options.WithSignatures {
|
||||
cursor := page.NewCursor(
|
||||
@@ -1403,14 +1409,29 @@ func exportDocumentPDF(
|
||||
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{
|
||||
Title: version.Title,
|
||||
Content: version.Content,
|
||||
Version: version.VersionNumber,
|
||||
Classification: classification,
|
||||
Approver: owner.FullName,
|
||||
PublishedAt: version.PublishedAt,
|
||||
Signatures: signatureData,
|
||||
Title: version.Title,
|
||||
Content: version.Content,
|
||||
Version: version.VersionNumber,
|
||||
Classification: classification,
|
||||
Approver: owner.FullName,
|
||||
PublishedAt: version.PublishedAt,
|
||||
Signatures: signatureData,
|
||||
CompanyHorizontalLogoBase64: horizontalLogoBase64,
|
||||
}
|
||||
|
||||
htmlContent, err := docgen.RenderHTML(docData)
|
||||
@@ -1492,6 +1513,7 @@ func (s *DocumentService) Export(
|
||||
|
||||
exportedPDF, err := exportDocumentPDF(
|
||||
ctx,
|
||||
s.svc,
|
||||
s.html2pdfConverter,
|
||||
conn,
|
||||
s.svc.scope,
|
||||
|
||||
@@ -285,6 +285,7 @@ func (s FrameworkService) Export(
|
||||
|
||||
exportedPDF, err := exportDocumentPDF(
|
||||
ctx,
|
||||
s.svc,
|
||||
s.html2pdfConverter,
|
||||
conn,
|
||||
s.svc.scope,
|
||||
|
||||
@@ -15,18 +15,13 @@
|
||||
package probo
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"mime"
|
||||
"net/mail"
|
||||
"net/url"
|
||||
"path/filepath"
|
||||
"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/filevalidation"
|
||||
"github.com/getprobo/probo/pkg/gid"
|
||||
@@ -71,6 +66,7 @@ type (
|
||||
ID gid.GID
|
||||
Name *string
|
||||
File *File
|
||||
HorizontalLogoFile *File
|
||||
Description **string
|
||||
WebsiteURL **string
|
||||
Email **string
|
||||
@@ -160,14 +156,15 @@ func (s OrganizationService) Update(
|
||||
) (*coredata.Organization, error) {
|
||||
organization := &coredata.Organization{}
|
||||
|
||||
err := s.svc.pg.WithConn(
|
||||
err := s.svc.pg.WithTx(
|
||||
ctx,
|
||||
func(conn pg.Conn) error {
|
||||
if err := organization.LoadByID(ctx, conn, s.svc.scope, req.ID); err != nil {
|
||||
func(tx pg.Conn) error {
|
||||
if err := organization.LoadByID(ctx, tx, s.svc.scope, req.ID); err != nil {
|
||||
return fmt.Errorf("cannot load organization: %w", err)
|
||||
}
|
||||
|
||||
organization.UpdatedAt = time.Now()
|
||||
now := time.Now()
|
||||
organization.UpdatedAt = now
|
||||
|
||||
if req.Name != nil {
|
||||
organization.Name = *req.Name
|
||||
@@ -195,41 +192,15 @@ func (s OrganizationService) Update(
|
||||
}
|
||||
|
||||
if req.File != 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)
|
||||
}
|
||||
|
||||
var fileSize int64
|
||||
var fileContent io.ReadSeeker
|
||||
filename := req.File.Filename
|
||||
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 == "" {
|
||||
contentType = "application/octet-stream"
|
||||
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 {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = s.svc.s3.PutObject(ctx, &s3.PutObjectInput{
|
||||
Bucket: aws.String(s.svc.bucket),
|
||||
Key: aws.String(objectKey.String()),
|
||||
Body: fileContent,
|
||||
ContentType: aws.String(contentType),
|
||||
Metadata: map[string]string{
|
||||
"type": "organization-logo",
|
||||
"organization-id": organization.ID.String(),
|
||||
},
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot upload file to S3: %w", err)
|
||||
fileRecord := &coredata.File{
|
||||
ID: fileID,
|
||||
BucketName: s.svc.bucket,
|
||||
MimeType: contentType,
|
||||
FileName: filename,
|
||||
FileKey: objectKey.String(),
|
||||
CreatedAt: now,
|
||||
UpdatedAt: now,
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
@@ -281,34 +321,114 @@ func (s OrganizationService) GenerateLogoURL(
|
||||
organizationID gid.GID,
|
||||
expiresIn time.Duration,
|
||||
) (*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 {
|
||||
return nil, fmt.Errorf("cannot get organization: %w", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if organization.LogoObjectKey == "" {
|
||||
if file.FileKey == "" {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
presignClient := s3.NewPresignClient(s.svc.s3)
|
||||
|
||||
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
|
||||
})
|
||||
presignedURL, err := s.svc.fileManager.GenerateFileUrl(ctx, file, expiresIn)
|
||||
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(
|
||||
@@ -343,16 +463,6 @@ func (s OrganizationService) Delete(
|
||||
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
|
||||
}
|
||||
|
||||
|
||||
@@ -24,6 +24,7 @@ import (
|
||||
"github.com/getprobo/probo/pkg/certmanager"
|
||||
"github.com/getprobo/probo/pkg/coredata"
|
||||
"github.com/getprobo/probo/pkg/crypto/cipher"
|
||||
"github.com/getprobo/probo/pkg/filemanager"
|
||||
"github.com/getprobo/probo/pkg/filevalidation"
|
||||
"github.com/getprobo/probo/pkg/gid"
|
||||
"github.com/getprobo/probo/pkg/html2pdf"
|
||||
@@ -57,6 +58,7 @@ type (
|
||||
html2pdfConverter *html2pdf.Converter
|
||||
usrmgr *usrmgr.Service
|
||||
acmeService *certmanager.ACMEService
|
||||
fileManager *filemanager.Service
|
||||
logger *log.Logger
|
||||
}
|
||||
|
||||
@@ -70,6 +72,7 @@ type (
|
||||
tokenSecret string
|
||||
trustConfig TrustConfig
|
||||
agent *agents.Agent
|
||||
fileManager *filemanager.Service
|
||||
Frameworks *FrameworkService
|
||||
Measures *MeasureService
|
||||
Tasks *TaskService
|
||||
@@ -116,6 +119,7 @@ func NewService(
|
||||
html2pdfConverter *html2pdf.Converter,
|
||||
usrmgrService *usrmgr.Service,
|
||||
acmeService *certmanager.ACMEService,
|
||||
fileManagerService *filemanager.Service,
|
||||
logger *log.Logger,
|
||||
) (*Service, error) {
|
||||
if bucket == "" {
|
||||
@@ -134,6 +138,7 @@ func NewService(
|
||||
html2pdfConverter: html2pdfConverter,
|
||||
usrmgr: usrmgrService,
|
||||
acmeService: acmeService,
|
||||
fileManager: fileManagerService,
|
||||
logger: logger,
|
||||
}
|
||||
|
||||
@@ -151,6 +156,7 @@ func (s *Service) WithTenant(tenantID gid.TenantID) *TenantService {
|
||||
tokenSecret: s.tokenSecret,
|
||||
trustConfig: s.trustConfig,
|
||||
agent: agents.NewAgent(nil, s.agentConfig),
|
||||
fileManager: s.fileManager,
|
||||
}
|
||||
|
||||
tenantService.Frameworks = &FrameworkService{
|
||||
|
||||
@@ -36,6 +36,7 @@ import (
|
||||
"github.com/getprobo/probo/pkg/crypto/keys"
|
||||
"github.com/getprobo/probo/pkg/crypto/passwdhash"
|
||||
"github.com/getprobo/probo/pkg/crypto/pem"
|
||||
"github.com/getprobo/probo/pkg/filemanager"
|
||||
"github.com/getprobo/probo/pkg/html2pdf"
|
||||
"github.com/getprobo/probo/pkg/mailer"
|
||||
"github.com/getprobo/probo/pkg/probo"
|
||||
@@ -269,6 +270,8 @@ func (impl *Implm) Run(
|
||||
return fmt.Errorf("cannot create usrmgr service: %w", err)
|
||||
}
|
||||
|
||||
fileManagerService := filemanager.NewService(s3Client)
|
||||
|
||||
var accountKey crypto.Signer
|
||||
if impl.cfg.CustomDomains.ACME.AccountKey != "" {
|
||||
accountKey, err = pem.DecodePrivateKey([]byte(impl.cfg.CustomDomains.ACME.AccountKey))
|
||||
@@ -311,6 +314,7 @@ func (impl *Implm) Run(
|
||||
html2pdfConverter,
|
||||
usrmgrService,
|
||||
acmeService,
|
||||
fileManagerService,
|
||||
l.Named("probo"),
|
||||
)
|
||||
if err != nil {
|
||||
@@ -325,6 +329,7 @@ func (impl *Implm) Run(
|
||||
impl.cfg.TrustAuth.TokenSecret,
|
||||
usrmgrService,
|
||||
html2pdfConverter,
|
||||
fileManagerService,
|
||||
)
|
||||
|
||||
serverHandler, err := server.NewServer(
|
||||
|
||||
@@ -1488,6 +1488,7 @@ type Organization implements Node {
|
||||
id: ID!
|
||||
name: String!
|
||||
logoUrl: String @goField(forceResolver: true)
|
||||
horizontalLogoUrl: String @goField(forceResolver: true)
|
||||
|
||||
description: String
|
||||
websiteUrl: String
|
||||
@@ -2619,6 +2620,9 @@ type Mutation {
|
||||
updateOrganization(
|
||||
input: UpdateOrganizationInput!
|
||||
): UpdateOrganizationPayload!
|
||||
deleteOrganizationHorizontalLogo(
|
||||
input: DeleteOrganizationHorizontalLogoInput!
|
||||
): DeleteOrganizationHorizontalLogoPayload!
|
||||
deleteOrganization(
|
||||
input: DeleteOrganizationInput!
|
||||
): DeleteOrganizationPayload!
|
||||
@@ -2941,12 +2945,17 @@ input UpdateOrganizationInput {
|
||||
organizationId: ID!
|
||||
name: String
|
||||
logo: Upload
|
||||
horizontalLogoFile: Upload
|
||||
description: String
|
||||
websiteUrl: String
|
||||
email: String
|
||||
headquarterAddress: String
|
||||
}
|
||||
|
||||
input DeleteOrganizationHorizontalLogoInput {
|
||||
organizationId: ID!
|
||||
}
|
||||
|
||||
input DeleteOrganizationInput {
|
||||
organizationId: ID!
|
||||
}
|
||||
@@ -3628,6 +3637,10 @@ type UpdateOrganizationPayload {
|
||||
organization: Organization!
|
||||
}
|
||||
|
||||
type DeleteOrganizationHorizontalLogoPayload {
|
||||
organization: Organization!
|
||||
}
|
||||
|
||||
type DeleteOrganizationPayload {
|
||||
deletedOrganizationId: ID!
|
||||
}
|
||||
|
||||
@@ -516,6 +516,10 @@ type ComplexityRoot struct {
|
||||
DeletedObligationID func(childComplexity int) int
|
||||
}
|
||||
|
||||
DeleteOrganizationHorizontalLogoPayload struct {
|
||||
Organization func(childComplexity int) int
|
||||
}
|
||||
|
||||
DeleteOrganizationPayload struct {
|
||||
DeletedOrganizationID func(childComplexity int) int
|
||||
}
|
||||
@@ -835,6 +839,7 @@ type ComplexityRoot struct {
|
||||
DeleteNonconformity func(childComplexity int, input types.DeleteNonconformityInput) int
|
||||
DeleteObligation func(childComplexity int, input types.DeleteObligationInput) 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
|
||||
DeleteProcessingActivity func(childComplexity int, input types.DeleteProcessingActivityInput) int
|
||||
DeleteRisk func(childComplexity int, input types.DeleteRiskInput) int
|
||||
@@ -968,6 +973,7 @@ type ComplexityRoot struct {
|
||||
Email func(childComplexity int) int
|
||||
Frameworks func(childComplexity int, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.FrameworkOrderBy) int
|
||||
HeadquarterAddress func(childComplexity int) int
|
||||
HorizontalLogoURL func(childComplexity int) int
|
||||
ID 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
|
||||
@@ -1673,6 +1679,7 @@ type MeasureConnectionResolver interface {
|
||||
type MutationResolver interface {
|
||||
CreateOrganization(ctx context.Context, input types.CreateOrganizationInput) (*types.CreateOrganizationPayload, 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)
|
||||
UpdateTrustCenter(ctx context.Context, input types.UpdateTrustCenterInput) (*types.UpdateTrustCenterPayload, error)
|
||||
UploadTrustCenterNda(ctx context.Context, input types.UploadTrustCenterNDAInput) (*types.UploadTrustCenterNDAPayload, error)
|
||||
@@ -1809,6 +1816,7 @@ type ObligationConnectionResolver interface {
|
||||
}
|
||||
type OrganizationResolver interface {
|
||||
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)
|
||||
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
|
||||
|
||||
case "DeleteOrganizationHorizontalLogoPayload.organization":
|
||||
if e.complexity.DeleteOrganizationHorizontalLogoPayload.Organization == nil {
|
||||
break
|
||||
}
|
||||
|
||||
return e.complexity.DeleteOrganizationHorizontalLogoPayload.Organization(childComplexity), true
|
||||
|
||||
case "DeleteOrganizationPayload.deletedOrganizationId":
|
||||
if e.complexity.DeleteOrganizationPayload.DeletedOrganizationID == nil {
|
||||
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
|
||||
|
||||
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":
|
||||
if e.complexity.Mutation.DeletePeople == nil {
|
||||
break
|
||||
@@ -5969,6 +5996,13 @@ func (e *executableSchema) Complexity(ctx context.Context, typeName, field strin
|
||||
|
||||
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":
|
||||
if e.complexity.Organization.ID == nil {
|
||||
break
|
||||
@@ -8506,6 +8540,7 @@ func (e *executableSchema) Exec(ctx context.Context) graphql.ResponseHandler {
|
||||
ec.unmarshalInputDeleteMeasureInput,
|
||||
ec.unmarshalInputDeleteNonconformityInput,
|
||||
ec.unmarshalInputDeleteObligationInput,
|
||||
ec.unmarshalInputDeleteOrganizationHorizontalLogoInput,
|
||||
ec.unmarshalInputDeleteOrganizationInput,
|
||||
ec.unmarshalInputDeletePeopleInput,
|
||||
ec.unmarshalInputDeleteProcessingActivityInput,
|
||||
@@ -10188,6 +10223,7 @@ type Organization implements Node {
|
||||
id: ID!
|
||||
name: String!
|
||||
logoUrl: String @goField(forceResolver: true)
|
||||
horizontalLogoUrl: String @goField(forceResolver: true)
|
||||
|
||||
description: String
|
||||
websiteUrl: String
|
||||
@@ -11319,6 +11355,9 @@ type Mutation {
|
||||
updateOrganization(
|
||||
input: UpdateOrganizationInput!
|
||||
): UpdateOrganizationPayload!
|
||||
deleteOrganizationHorizontalLogo(
|
||||
input: DeleteOrganizationHorizontalLogoInput!
|
||||
): DeleteOrganizationHorizontalLogoPayload!
|
||||
deleteOrganization(
|
||||
input: DeleteOrganizationInput!
|
||||
): DeleteOrganizationPayload!
|
||||
@@ -11641,12 +11680,17 @@ input UpdateOrganizationInput {
|
||||
organizationId: ID!
|
||||
name: String
|
||||
logo: Upload
|
||||
horizontalLogoFile: Upload
|
||||
description: String
|
||||
websiteUrl: String
|
||||
email: String
|
||||
headquarterAddress: String
|
||||
}
|
||||
|
||||
input DeleteOrganizationHorizontalLogoInput {
|
||||
organizationId: ID!
|
||||
}
|
||||
|
||||
input DeleteOrganizationInput {
|
||||
organizationId: ID!
|
||||
}
|
||||
@@ -12328,6 +12372,10 @@ type UpdateOrganizationPayload {
|
||||
organization: Organization!
|
||||
}
|
||||
|
||||
type DeleteOrganizationHorizontalLogoPayload {
|
||||
organization: Organization!
|
||||
}
|
||||
|
||||
type DeleteOrganizationPayload {
|
||||
deletedOrganizationId: ID!
|
||||
}
|
||||
@@ -16056,6 +16104,29 @@ func (ec *executionContext) field_Mutation_deleteObligation_argsInput(
|
||||
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) {
|
||||
var err error
|
||||
args := map[string]any{}
|
||||
@@ -21512,6 +21583,8 @@ func (ec *executionContext) fieldContext_Asset_organization(_ context.Context, f
|
||||
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":
|
||||
@@ -22119,6 +22192,8 @@ func (ec *executionContext) fieldContext_Audit_organization(_ context.Context, f
|
||||
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":
|
||||
@@ -23846,6 +23921,8 @@ func (ec *executionContext) fieldContext_ContinualImprovement_organization(_ con
|
||||
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":
|
||||
@@ -27604,6 +27681,8 @@ func (ec *executionContext) fieldContext_CustomDomain_organization(_ context.Con
|
||||
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":
|
||||
@@ -28503,6 +28582,8 @@ func (ec *executionContext) fieldContext_Datum_organization(_ context.Context, f
|
||||
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":
|
||||
@@ -29902,6 +29983,112 @@ func (ec *executionContext) fieldContext_DeleteObligationPayload_deletedObligati
|
||||
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) {
|
||||
fc, err := ec.fieldContext_DeleteOrganizationPayload_deletedOrganizationId(ctx, field)
|
||||
if err != nil {
|
||||
@@ -31220,6 +31407,8 @@ func (ec *executionContext) fieldContext_Document_organization(_ context.Context
|
||||
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":
|
||||
@@ -34542,6 +34731,8 @@ func (ec *executionContext) fieldContext_Framework_organization(_ context.Contex
|
||||
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":
|
||||
@@ -36224,6 +36415,65 @@ func (ec *executionContext) fieldContext_Mutation_updateOrganization(ctx context
|
||||
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) {
|
||||
fc, err := ec.fieldContext_Mutation_deleteOrganization(ctx, field)
|
||||
if err != nil {
|
||||
@@ -43173,6 +43423,8 @@ func (ec *executionContext) fieldContext_Nonconformity_organization(_ context.Co
|
||||
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":
|
||||
@@ -44234,6 +44486,8 @@ func (ec *executionContext) fieldContext_Obligation_organization(_ context.Conte
|
||||
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":
|
||||
@@ -45175,6 +45429,47 @@ func (ec *executionContext) fieldContext_Organization_logoUrl(_ context.Context,
|
||||
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) {
|
||||
fc, err := ec.fieldContext_Organization_description(ctx, field)
|
||||
if err != nil {
|
||||
@@ -46872,6 +47167,8 @@ func (ec *executionContext) fieldContext_OrganizationEdge_node(_ context.Context
|
||||
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":
|
||||
@@ -47961,6 +48258,8 @@ func (ec *executionContext) fieldContext_ProcessingActivity_organization(_ conte
|
||||
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":
|
||||
@@ -50638,6 +50937,8 @@ func (ec *executionContext) fieldContext_Risk_organization(_ context.Context, fi
|
||||
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":
|
||||
@@ -51538,6 +51839,8 @@ func (ec *executionContext) fieldContext_Snapshot_organization(_ context.Context
|
||||
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":
|
||||
@@ -52451,6 +52754,8 @@ func (ec *executionContext) fieldContext_Task_organization(_ context.Context, fi
|
||||
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":
|
||||
@@ -53335,6 +53640,8 @@ func (ec *executionContext) fieldContext_TrustCenter_organization(_ context.Cont
|
||||
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":
|
||||
@@ -56374,6 +56681,8 @@ func (ec *executionContext) fieldContext_UpdateOrganizationPayload_organization(
|
||||
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":
|
||||
@@ -58414,6 +58723,8 @@ func (ec *executionContext) fieldContext_Vendor_organization(_ context.Context,
|
||||
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":
|
||||
@@ -68401,6 +68712,33 @@ func (ec *executionContext) unmarshalInputDeleteObligationInput(ctx context.Cont
|
||||
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) {
|
||||
var it types.DeleteOrganizationInput
|
||||
asMap := map[string]any{}
|
||||
@@ -70950,7 +71288,7 @@ func (ec *executionContext) unmarshalInputUpdateOrganizationInput(ctx context.Co
|
||||
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 {
|
||||
v, ok := asMap[k]
|
||||
if !ok {
|
||||
@@ -70978,6 +71316,13 @@ func (ec *executionContext) unmarshalInputUpdateOrganizationInput(ctx context.Co
|
||||
return it, err
|
||||
}
|
||||
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":
|
||||
ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("description"))
|
||||
data, err := ec.unmarshalOString2ᚖstring(ctx, v)
|
||||
@@ -76888,6 +77233,45 @@ func (ec *executionContext) _DeleteObligationPayload(ctx context.Context, sel as
|
||||
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"}
|
||||
|
||||
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 {
|
||||
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":
|
||||
out.Values[i] = ec.OperationContext.RootResolverMiddleware(innerCtx, func(ctx context.Context) (res graphql.Marshaler) {
|
||||
return ec._Mutation_deleteOrganization(ctx, field)
|
||||
@@ -81228,6 +81619,39 @@ func (ec *executionContext) _Organization(ctx context.Context, sel ast.Selection
|
||||
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) })
|
||||
case "description":
|
||||
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)
|
||||
}
|
||||
|
||||
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) {
|
||||
res, err := ec.unmarshalInputDeleteOrganizationInput(ctx, v)
|
||||
return res, graphql.ErrorOnPath(ctx, err)
|
||||
|
||||
@@ -831,6 +831,14 @@ type DeleteObligationPayload struct {
|
||||
DeletedObligationID gid.GID `json:"deletedObligationId"`
|
||||
}
|
||||
|
||||
type DeleteOrganizationHorizontalLogoInput struct {
|
||||
OrganizationID gid.GID `json:"organizationId"`
|
||||
}
|
||||
|
||||
type DeleteOrganizationHorizontalLogoPayload struct {
|
||||
Organization *Organization `json:"organization"`
|
||||
}
|
||||
|
||||
type DeleteOrganizationInput struct {
|
||||
OrganizationID gid.GID `json:"organizationId"`
|
||||
}
|
||||
@@ -1288,6 +1296,7 @@ type Organization struct {
|
||||
ID gid.GID `json:"id"`
|
||||
Name string `json:"name"`
|
||||
LogoURL *string `json:"logoUrl,omitempty"`
|
||||
HorizontalLogoURL *string `json:"horizontalLogoUrl,omitempty"`
|
||||
Description *string `json:"description,omitempty"`
|
||||
WebsiteURL *string `json:"websiteUrl,omitempty"`
|
||||
Email *string `json:"email,omitempty"`
|
||||
@@ -1798,6 +1807,7 @@ type UpdateOrganizationInput struct {
|
||||
OrganizationID gid.GID `json:"organizationId"`
|
||||
Name *string `json:"name,omitempty"`
|
||||
Logo *graphql.Upload `json:"logo,omitempty"`
|
||||
HorizontalLogoFile *graphql.Upload `json:"horizontalLogoFile,omitempty"`
|
||||
Description *string `json:"description,omitempty"`
|
||||
WebsiteURL *string `json:"websiteUrl,omitempty"`
|
||||
Email *string `json:"email,omitempty"`
|
||||
|
||||
@@ -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)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot update organization: %w", err)
|
||||
panic(fmt.Errorf("cannot update organization: %w", err))
|
||||
}
|
||||
|
||||
return &types.UpdateOrganizationPayload{
|
||||
@@ -1109,6 +1118,20 @@ func (r *mutationResolver) UpdateOrganization(ctx context.Context, input types.U
|
||||
}, 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.
|
||||
func (r *mutationResolver) DeleteOrganization(ctx context.Context, input types.DeleteOrganizationInput) (*types.DeleteOrganizationPayload, error) {
|
||||
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)
|
||||
}
|
||||
|
||||
// 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.
|
||||
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]{
|
||||
|
||||
@@ -141,6 +141,7 @@ func (s *DocumentService) exportPDFData(
|
||||
document := &coredata.Document{}
|
||||
version := &coredata.DocumentVersion{}
|
||||
owner := &coredata.People{}
|
||||
organization := &coredata.Organization{}
|
||||
|
||||
err := s.svc.pg.WithConn(
|
||||
ctx,
|
||||
@@ -161,6 +162,10 @@ func (s *DocumentService) exportPDFData(
|
||||
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
|
||||
},
|
||||
)
|
||||
@@ -177,13 +182,28 @@ func (s *DocumentService) exportPDFData(
|
||||
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{
|
||||
Title: version.Title,
|
||||
Content: version.Content,
|
||||
Version: version.VersionNumber,
|
||||
Classification: classification,
|
||||
Approver: owner.FullName,
|
||||
PublishedAt: version.PublishedAt,
|
||||
Title: version.Title,
|
||||
Content: version.Content,
|
||||
Version: version.VersionNumber,
|
||||
Classification: classification,
|
||||
Approver: owner.FullName,
|
||||
PublishedAt: version.PublishedAt,
|
||||
CompanyHorizontalLogoBase64: horizontalLogoBase64,
|
||||
}
|
||||
|
||||
htmlContent, err := docgen.RenderHTML(docData)
|
||||
|
||||
@@ -71,19 +71,30 @@ func (s OrganizationService) GenerateLogoURL(
|
||||
return nil, fmt.Errorf("cannot get organization: %w", err)
|
||||
}
|
||||
|
||||
if organization.LogoObjectKey == "" {
|
||||
if organization.LogoFileID == 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)
|
||||
|
||||
encodedFilename := url.QueryEscape(organization.Name)
|
||||
encodedFilename := url.QueryEscape(file.FileName)
|
||||
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),
|
||||
Key: aws.String(file.FileKey),
|
||||
ResponseCacheControl: aws.String("max-age=3600, public"),
|
||||
ResponseContentDisposition: aws.String(contentDisposition),
|
||||
}, func(opts *s3.PresignOptions) {
|
||||
|
||||
@@ -18,6 +18,7 @@ import (
|
||||
"github.com/aws/aws-sdk-go-v2/service/s3"
|
||||
"github.com/getprobo/probo/pkg/coredata"
|
||||
"github.com/getprobo/probo/pkg/crypto/cipher"
|
||||
"github.com/getprobo/probo/pkg/filemanager"
|
||||
"github.com/getprobo/probo/pkg/gid"
|
||||
"github.com/getprobo/probo/pkg/html2pdf"
|
||||
"github.com/getprobo/probo/pkg/probo"
|
||||
@@ -35,27 +36,29 @@ type (
|
||||
tokenSecret string
|
||||
usrmgr *usrmgr.Service
|
||||
html2pdfConverter *html2pdf.Converter
|
||||
fileManager *filemanager.Service
|
||||
}
|
||||
|
||||
TenantService struct {
|
||||
pg *pg.Client
|
||||
s3 *s3.Client
|
||||
bucket string
|
||||
scope coredata.Scoper
|
||||
proboSvc *probo.Service
|
||||
encryptionKey cipher.EncryptionKey
|
||||
tokenSecret string
|
||||
usrmgr *usrmgr.Service
|
||||
html2pdfConverter *html2pdf.Converter
|
||||
TrustCenters *TrustCenterService
|
||||
Documents *DocumentService
|
||||
Audits *AuditService
|
||||
Vendors *VendorService
|
||||
Frameworks *FrameworkService
|
||||
TrustCenterAccesses *TrustCenterAccessService
|
||||
pg *pg.Client
|
||||
s3 *s3.Client
|
||||
bucket string
|
||||
scope coredata.Scoper
|
||||
proboSvc *probo.Service
|
||||
encryptionKey cipher.EncryptionKey
|
||||
tokenSecret string
|
||||
usrmgr *usrmgr.Service
|
||||
html2pdfConverter *html2pdf.Converter
|
||||
fileManager *filemanager.Service
|
||||
TrustCenters *TrustCenterService
|
||||
Documents *DocumentService
|
||||
Audits *AuditService
|
||||
Vendors *VendorService
|
||||
Frameworks *FrameworkService
|
||||
TrustCenterAccesses *TrustCenterAccessService
|
||||
TrustCenterReferences *TrustCenterReferenceService
|
||||
Reports *ReportService
|
||||
Organizations *OrganizationService
|
||||
Reports *ReportService
|
||||
Organizations *OrganizationService
|
||||
}
|
||||
)
|
||||
|
||||
@@ -67,6 +70,7 @@ func NewService(
|
||||
tokenSecret string,
|
||||
usrmgr *usrmgr.Service,
|
||||
html2pdfConverter *html2pdf.Converter,
|
||||
fileManagerService *filemanager.Service,
|
||||
) *Service {
|
||||
return &Service{
|
||||
pg: pgClient,
|
||||
@@ -76,6 +80,7 @@ func NewService(
|
||||
tokenSecret: tokenSecret,
|
||||
usrmgr: usrmgr,
|
||||
html2pdfConverter: html2pdfConverter,
|
||||
fileManager: fileManagerService,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -90,6 +95,7 @@ func (s *Service) WithTenant(tenantID gid.TenantID) *TenantService {
|
||||
tokenSecret: s.tokenSecret,
|
||||
usrmgr: s.usrmgr,
|
||||
html2pdfConverter: s.html2pdfConverter,
|
||||
fileManager: s.fileManager,
|
||||
}
|
||||
|
||||
tenantService.TrustCenters = &TrustCenterService{svc: tenantService}
|
||||
|
||||
Reference in New Issue
Block a user