Skip to content

Commit

Permalink
feat: initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
pozil committed Dec 23, 2024
0 parents commit 97a6f84
Show file tree
Hide file tree
Showing 25 changed files with 1,710 additions and 0 deletions.
12 changes: 12 additions & 0 deletions .forceignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# List files or directories below to ignore them when running force:source:push, force:source:pull, and force:source:status
# More information: https://developer.salesforce.com/docs/atlas.en-us.sfdx_dev.meta/sfdx_dev/sfdx_dev_exclude_source.htm
#

package.xml

# LWC configuration files
**/jsconfig.json
**/.eslintrc.json

# LWC Jest
**/__tests__/**
47 changes: 47 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
# This file is used for Git repositories to specify intentionally untracked files that Git should ignore.
# If you are not using git, you can delete this file. For more information see: https://git-scm.com/docs/gitignore
# For useful gitignore templates see: https://github.com/github/gitignore

# Salesforce cache
.sf/
.sfdx/
.localdevserver/
deploy-options.json

# LWC VSCode autocomplete
**/lwc/jsconfig.json

# LWC Jest coverage reports
coverage/

# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*

# Dependency directories
node_modules/

# Eslint cache
.eslintcache

# MacOS system files
.DS_Store

# Windows system files
Thumbs.db
ehthumbs.db
[Dd]esktop.ini
$RECYCLE.BIN/

# Local environment variables
.env

# VS Code settings
.vscode

# Named/External credentials
**/namedCredentials
**/externalCredentials
4 changes: 4 additions & 0 deletions .husky/pre-commit
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
#!/bin/sh
. "$(dirname "$0")/_/husky.sh"

npm run precommit
11 changes: 11 additions & 0 deletions .prettierignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# List files or directories below to ignore them when running prettier
# More information: https://prettier.io/docs/en/ignore.html
#

**/staticresources/**
.localdevserver
.sfdx
.sf
.vscode

coverage/
17 changes: 17 additions & 0 deletions .prettierrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
{
"trailingComma": "none",
"plugins": [
"prettier-plugin-apex",
"@prettier/plugin-xml"
],
"overrides": [
{
"files": "**/lwc/**/*.html",
"options": { "parser": "lwc" }
},
{
"files": "*.{cmp,page,component}",
"options": { "parser": "html" }
}
]
}
121 changes: 121 additions & 0 deletions LICENSE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
Creative Commons Legal Code

CC0 1.0 Universal

CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE
LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE AN
ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS
INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES
REGARDING THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS
PROVIDED HEREUNDER, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM
THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED
HEREUNDER.

Statement of Purpose

The laws of most jurisdictions throughout the world automatically confer
exclusive Copyright and Related Rights (defined below) upon the creator
and subsequent owner(s) (each and all, an "owner") of an original work of
authorship and/or a database (each, a "Work").

Certain owners wish to permanently relinquish those rights to a Work for
the purpose of contributing to a commons of creative, cultural and
scientific works ("Commons") that the public can reliably and without fear
of later claims of infringement build upon, modify, incorporate in other
works, reuse and redistribute as freely as possible in any form whatsoever
and for any purposes, including without limitation commercial purposes.
These owners may contribute to the Commons to promote the ideal of a free
culture and the further production of creative, cultural and scientific
works, or to gain reputation or greater distribution for their Work in
part through the use and efforts of others.

For these and/or other purposes and motivations, and without any
expectation of additional consideration or compensation, the person
associating CC0 with a Work (the "Affirmer"), to the extent that he or she
is an owner of Copyright and Related Rights in the Work, voluntarily
elects to apply CC0 to the Work and publicly distribute the Work under its
terms, with knowledge of his or her Copyright and Related Rights in the
Work and the meaning and intended legal effect of CC0 on those rights.

1. Copyright and Related Rights. A Work made available under CC0 may be
protected by copyright and related or neighboring rights ("Copyright and
Related Rights"). Copyright and Related Rights include, but are not
limited to, the following:

i. the right to reproduce, adapt, distribute, perform, display,
communicate, and translate a Work;
ii. moral rights retained by the original author(s) and/or performer(s);
iii. publicity and privacy rights pertaining to a person's image or
likeness depicted in a Work;
iv. rights protecting against unfair competition in regards to a Work,
subject to the limitations in paragraph 4(a), below;
v. rights protecting the extraction, dissemination, use and reuse of data
in a Work;
vi. database rights (such as those arising under Directive 96/9/EC of the
European Parliament and of the Council of 11 March 1996 on the legal
protection of databases, and under any national implementation
thereof, including any amended or successor version of such
directive); and
vii. other similar, equivalent or corresponding rights throughout the
world based on applicable law or treaty, and any national
implementations thereof.

2. Waiver. To the greatest extent permitted by, but not in contravention
of, applicable law, Affirmer hereby overtly, fully, permanently,
irrevocably and unconditionally waives, abandons, and surrenders all of
Affirmer's Copyright and Related Rights and associated claims and causes
of action, whether now known or unknown (including existing as well as
future claims and causes of action), in the Work (i) in all territories
worldwide, (ii) for the maximum duration provided by applicable law or
treaty (including future time extensions), (iii) in any current or future
medium and for any number of copies, and (iv) for any purpose whatsoever,
including without limitation commercial, advertising or promotional
purposes (the "Waiver"). Affirmer makes the Waiver for the benefit of each
member of the public at large and to the detriment of Affirmer's heirs and
successors, fully intending that such Waiver shall not be subject to
revocation, rescission, cancellation, termination, or any other legal or
equitable action to disrupt the quiet enjoyment of the Work by the public
as contemplated by Affirmer's express Statement of Purpose.

3. Public License Fallback. Should any part of the Waiver for any reason
be judged legally invalid or ineffective under applicable law, then the
Waiver shall be preserved to the maximum extent permitted taking into
account Affirmer's express Statement of Purpose. In addition, to the
extent the Waiver is so judged Affirmer hereby grants to each affected
person a royalty-free, non transferable, non sublicensable, non exclusive,
irrevocable and unconditional license to exercise Affirmer's Copyright and
Related Rights in the Work (i) in all territories worldwide, (ii) for the
maximum duration provided by applicable law or treaty (including future
time extensions), (iii) in any current or future medium and for any number
of copies, and (iv) for any purpose whatsoever, including without
limitation commercial, advertising or promotional purposes (the
"License"). The License shall be deemed effective as of the date CC0 was
applied by Affirmer to the Work. Should any part of the License for any
reason be judged legally invalid or ineffective under applicable law, such
partial invalidity or ineffectiveness shall not invalidate the remainder
of the License, and in such case Affirmer hereby affirms that he or she
will not (i) exercise any of his or her remaining Copyright and Related
Rights in the Work or (ii) assert any associated claims and causes of
action with respect to the Work, in either case contrary to Affirmer's
express Statement of Purpose.

4. Limitations and Disclaimers.

a. No trademark or patent rights held by Affirmer are waived, abandoned,
surrendered, licensed or otherwise affected by this document.
b. Affirmer offers the Work as-is and makes no representations or
warranties of any kind concerning the Work, express, implied,
statutory or otherwise, including without limitation warranties of
title, merchantability, fitness for a particular purpose, non
infringement, or the absence of latent or other defects, accuracy, or
the present or absence of errors, whether or not discoverable, all to
the greatest extent permissible under applicable law.
c. Affirmer disclaims responsibility for clearing rights of other persons
that may apply to the Work or any use thereof, including without
limitation any person's Copyright and Related Rights in the Work.
Further, Affirmer disclaims responsibility for obtaining any necessary
consents, permissions or other rights required for any use of the
Work.
d. Affirmer understands and acknowledges that Creative Commons is not a
party to this document and has no duty or obligation with respect to
this CC0 or use of the Work.
40 changes: 40 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
# Sample Code for Retrieving and Sharing Files with Agentforce

This repo holds an example of how you can implement two custom Apex actions that let you retrieve an invoice PDF from a third party accounting system (or more generally, a file from any external system) and share it publicly.

Read this blog post [link TBD] to learn more.

## Requirements

This code requires the following:

1. That you have a remote host that serves the invoice PDF. In my case, I wrote a basic HTTP Node server that I deployed on Heroku.
1. That you deploy this code to a Salesforce Org with Agentforce.

## Limitations

There are two important limitations to this solution:

1. This implementation cannot handle files that are larger than the [Apex heap size limit](https://developer.salesforce.com/docs/atlas.en-us.apexcode.meta/apexcode/apex_gov_limits.htm#in_topic_per_transaction_section).
2. Storing documents on the Platform consumes [data storage](https://help.salesforce.com/s/articleView?id=sf.admin_monitorresources.htm&type=5) and you’ll need to schedule some regular cleanup so that you org does not run out of space over time.

## Installation

1. Configure an `Accounting_Service` [External Credential](https://help.salesforce.com/s/articleView?id=sf.nc_named_creds_and_ext_creds.htm&type=5) that points to the server that holds the documents.
1. Optionnaly edit `force-app/main/default/classes/GenerateInvoice.cls` to match your service endpoint that returns the invoice. The default is `/orders/ORDER_Id/invoice` where `ORDER_ID` is the order ID.
1. Deploy the content of this repo to your org:
```sh
sf project deploy start -d force-app
```

## Sample Prompts

You can experiment the actions with these sample prompts.

A basic example:

> Get me a shareable password-protected link for the invoice of order O-12345
A more advanced example:

> Draft an email to John Doe with a shareable and password-protected link to his invoice for order O-12345. Thank him for trusting ACME Corp.
7 changes: 7 additions & 0 deletions apex-scripts/clear-sharing-links.apex
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
// Clear sharing links
Id docId = '069Wx000000sGxRIAU';
ContentVersion version = [SELECT Id, Title FROM ContentVersion WHERE ContentDocumentId = :docId AND IsLatest=true LIMIT 1];
List<ContentDistribution> distributions = [SELECT Id FROM ContentDistribution WHERE ContentVersionId = :version.Id];
System.debug('Removed '+ distributions.size() + ' links for document '+ docId);
System.debug(distributions);
delete distributions;
5 changes: 5 additions & 0 deletions apex-scripts/generate-invoice.apex
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
// Generate an invoice
GenerateInvoice.InvoiceRequest request = new GenerateInvoice.InvoiceRequest();
request.orderId = 'O-12345';
List<GenerateInvoice.Invoice> invoices = GenerateInvoice.generateInvoices(new List<GenerateInvoice.InvoiceRequest>{ request });
System.debug(invoices);
5 changes: 5 additions & 0 deletions apex-scripts/remove-invoice-docs.apex
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
// Remove invoice documents
List<ContentDocument> invoices = [SELECT Id FROM ContentDocument WHERE Title LIKE '%Invoice for order%'];
System.debug('Removed '+ invoices.size() + ' invoices');
System.debug(invoices);
delete invoices;
8 changes: 8 additions & 0 deletions apex-scripts/share-document.apex
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
// Share a document
GeneratePublicDocumentLink.LinkRequest request = new GeneratePublicDocumentLink.LinkRequest();
request.contentDocumentId = '069Wx000000sGxRIAU';
request.isPasswordProtected = true;

List<GeneratePublicDocumentLink.LinkRequest> requests = new List<GeneratePublicDocumentLink.LinkRequest>{ request };
List<GeneratePublicDocumentLink.Link> links = generatePublicDocumentLink.generatePublicDocumentLinks(requests);
System.debug(links);
71 changes: 71 additions & 0 deletions force-app/main/default/classes/GenerateInvoice.cls
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
public with sharing class GenerateInvoice {
@InvocableMethod(
label='Generate Invoice'
description='Generates an invoice for an order and stores it in a file.'
)
public static List<Invoice> generateInvoices(List<InvoiceRequest> requests) {
// Fetch and create invoices files
List<ContentVersion> invoiceFiles = new List<ContentVersion>();
for (InvoiceRequest request : requests) {
// Download invoice
Blob invoiceBlob = downloadInvoice(request.orderId);
// Prepare invoice file
ContentVersion cv = new ContentVersion();
cv.Title = 'Invoice for order ' + request.orderId;
cv.PathOnClient = request.orderId + '.pdf';
cv.VersionData = invoiceBlob;
invoiceFiles.add(cv);
}
insert invoiceFiles;

// Re-fetch content versions to obtain generated document content IDs
Set<Id> contentVersionIds = new Set<Id>();
for (ContentVersion invoice : invoiceFiles) {
contentVersionIds.add(invoice.Id);
}
invoiceFiles = [
SELECT ContentDocumentId
FROM ContentVersion
WHERE Id IN :contentVersionIds
];

// Prepare output
List<Invoice> invoices = new List<Invoice>();
for (ContentVersion invoiceFile : invoiceFiles) {
Invoice invoice = new Invoice();
invoice.contentDocumentId = invoiceFile.ContentDocumentId;
invoices.add(invoice);
}
return invoices;
}

private static Blob downloadInvoice(String orderId) {
// Prepare API request
HttpRequest req = new HttpRequest();
req.setEndpoint(
'callout:Accounting_Service/orders/' + orderId + '/invoice'
);
req.setMethod('GET');
// Make callout
Http http = new Http();
HttpResponse res = http.send(req);
if (res.getStatusCode() != 200) {
throw new CalloutException('Bad response: ' + res);
}
Blob invoiceBlob = res.getBodyAsBlob();
return invoiceBlob;
}

public class InvoiceRequest {
@InvocableVariable(
required=true
description='Order for which the invoice should be generated.'
)
public String orderId;
}

public class Invoice {
@InvocableVariable(description='Document ID that holds the invoice.')
public Id contentDocumentId;
}
}
5 changes: 5 additions & 0 deletions force-app/main/default/classes/GenerateInvoice.cls-meta.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="UTF-8" ?>
<ApexClass xmlns="http://soap.sforce.com/2006/04/metadata">
<apiVersion>62.0</apiVersion>
<status>Active</status>
</ApexClass>
Loading

0 comments on commit 97a6f84

Please sign in to comment.