[SalesForce] Salesforce to SFTP

I have requirement wherein a csv file should generate with some users data and need to place the same file in a SFTP path without using any external tool.
Can I have an alternative approach to proceed with the same. A sample code would be much appreciated.

Best Answer

We have implemented a REST API which allow to directly integrate Salesforce with SFTP Server. You can directly upload file from salesforce to SFTP Server using this REST API.

Here we are addng API documentation link. (Note: this link will never change)

FTP API: https://www.ftp-api.com/

Here we are adding upload example with code snippts.

#1. FTP_UploadFile.cmp

<aura:component implements="force:lightningQuickAction,force:hasRecordId"
            access="global"
            controller="FTPWebServiceCompCtrl">
<!--attribute-->
<aura:attribute name="showSpinner" type="boolean" default="false"/>
<aura:attribute name="oFTPResponseWrapper" type="FTPResponseWrapper"/>

<!--handler-->
<aura:handler name="init" value="{!this}" action="{!c.doInit}"/>    

<!--Markup-->
<aura:if isTrue="{!v.showSpinner}">
    <div>
        <lightning:spinner alternativeText="Loading" size="large" />
    </div>
</aura:if>

<aura:if isTrue="{!v.oFTPResponseWrapper != null}">
    <div class="{!(v.oFTPResponseWrapper.Status == 'ERROR'?'slds-notify slds-notify_alert slds-theme_alert-texture slds-theme_error':'slds-notify slds-notify_alert slds-theme_alert-texture slds-theme_success')}" role="alert">
        <span class="slds-assistive-text">error</span>
        <span class="{!(v.oFTPResponseWrapper.Status == 'ERROR'?'slds-icon_container slds-icon-utility-error slds-m-right_x-small':'slds-icon_container slds-icon-utility-user slds-m-right_x-small')}">
            <lightning:icon iconName="{!(v.oFTPResponseWrapper.Status == 'ERROR'?'utility:error':'utility:upload')}" size="small" variant="inverse"/>
        </span>
        <h2>{!v.oFTPResponseWrapper.Message} - {!v.oFTPResponseWrapper.Code}</h2>
        <div class="slds-notify__close"></div>
    </div>
    
    <aura:if isTrue="{!v.oFTPResponseWrapper.Files != undefined &amp;&amp; v.oFTPResponseWrapper.Files.length>0}">
        <table class="slds-table slds-table_cell-buffer slds-table_bordered slds-table_col-bordered slds-m-top_medium">
            <thead>
                <tr class="slds-line-height_reset">
                    <th class="" scope="col">
                        <div class="slds-truncate" title="File Name">File Name</div>
                    </th>
                    <th class="" scope="col">
                        <div class="slds-truncate" title="Path">Path</div>
                    </th>
                    <th class="" scope="col">
                        <div class="slds-truncate" title="Message">Message</div>
                    </th>
                    <th class="" scope="col">
                        <div class="slds-truncate" title="Status">Status</div>
                    </th>
                    <th class="" scope="col">
                        <div class="slds-truncate" title="Code">Code</div>
                    </th>
                </tr>
            </thead>
            <tbody>
                <aura:iteration items="{!v.oFTPResponseWrapper.Files}" var="oFileWrapper">
                    <tr class="{!(oFileWrapper.Status=='ERROR'?'slds-hint-parent has-error':'slds-hint-parent')}">
                        <th data-label="File Name" scope="row">
                            <div class="slds-truncate" title="{!oFileWrapper.FileName}">
                                {!oFileWrapper.FileName}
                            </div>
                        </th>
                        <td data-label="Path">
                            <div class="slds-truncate" title="{!oFileWrapper.Path}">{!oFileWrapper.Path}</div>
                        </td>
                        <td data-label="Message">
                            <div class="slds-truncate" title="{!oFileWrapper.Message}">{!oFileWrapper.Message}</div>
                        </td>
                        <td data-label="Status">
                            <div class="slds-truncate" title="{!oFileWrapper.Status}">{!oFileWrapper.Status}</div>
                        </td>
                        <td data-label="Code">
                            <div class="slds-truncate" title="{!oFileWrapper.Code}">{!oFileWrapper.Code}</div>
                        </td>
                    </tr>
                </aura:iteration>
            </tbody>
        </table>
    </aura:if>
</aura:if>
</aura:component>

FTP_UploadFileController.js

({
doInit : function(component, event, helper) {
    helper.uploadFile(component, event);
}
})

FTP_UploadFileHelper.js

({
uploadFile : function(component, event) {
    this.startWaiting(component);
    var action = component.get("c.uploadFileToFTPServer");
    action.setParams({ recordId : component.get("v.recordId") });
    action.setCallback(this, function(response) {
        var state = response.getState();
        if (state === "SUCCESS") {
            let result = response.getReturnValue();
            component.set("v.oFTPResponseWrapper", result);
        }else if (state === "ERROR") {
            var errors = response.getError();
            if (errors) {
                if (errors[0] && errors[0].message) {
                    this.showToastMessage(component, event, 'Error', errors[0].message, 'error');
                    console.log("Error message: " + errors[0].message);
                }
            } else {
                this.showToastMessage(component, event, 'Error', "Unknown error", 'error');
                console.log("Unknown error");
            }
        }
        this.stopWaiting(component);
    });
    $A.enqueueAction(action);
},

showToastMessage : function(component, event, title, message, type) {
    var toastEvent = $A.get("e.force:showToast");
    toastEvent.setParams({
        "title": title,
        "message": message,
        "type":type,
        "duration":10000
    });
    toastEvent.fire();
},

startWaiting : function(component) {
    component.set("v.showSpinner", true);
},

stopWaiting : function(component){
    component.set("v.showSpinner", false);
}
})

#2. FTPWebServiceCompCtrl.cls

public class FTPWebServiceCompCtrl {

@AuraEnabled
public static FTPResponseWrapper uploadFileToFTPServer(Id recordId){
    try{
        return FTPWebServiceExample.uploadFiles(recordId);
    }catch(Exception ex){
        throw new AuraException(ex.getMessage());
    }
}
}

#3. FTPWebServiceExample.cls

public class FTPWebServiceExample {

public static FTPResponseWrapper uploadFiles(Id parentId){
    List<FileWrapper> lstFileWrapper = new List<FileWrapper>();
    for(Attachment oAttachment : [SELECT Id, body, Name FROM Attachment Where ParentId=:parentId]){
        lstFileWrapper.add(new FileWrapper(oAttachment.Name, '/tmp', EncodingUtil.base64Encode(oAttachment.body)));
    }
    String endpoint = FTPWebServiceUtility.FTP_WebServiceEndpoint+'upload'; 
    HttpResponse response = FTPWebService.uploadFileOnFTPServer(endpoint, 'POST', JSON.serialize(lstFileWrapper));
    if(response != null){
        if(response.getStatusCode() == 200){
            return (FTPResponseWrapper)JSON.deserialize(response.getBody(), FTPResponseWrapper.class);
        }else if(response.getStatusCode() == 400){
            return (FTPResponseWrapper)JSON.deserialize(response.getBody(), FTPResponseWrapper.class);
        }else{
            return FTPWebServiceUtility.getFTPResponseWrapper('ERROR', 'Callout Failed!', response.getStatusCode());
        }
    }else{
        return FTPWebServiceUtility.getFTPResponseWrapper('ERROR', 'Callout Failed!', 0);
    }
}
}

#4. FTPWebService.cls

public class FTPWebService{

//Upload Files on FTP Server
public static HttpResponse uploadFileOnFTPServer(String endpoint, String http_method, String http_body){
    HttpRequest req = createHttpRequest(endpoint, http_method, http_body, FTPWebServiceUtility.oSourceFtpWebServiceConfig);
    Http httpreq = new Http();
    return httpreq.send(req);
}

private static HttpRequest createHttpRequest(String endpoint, String method, String body, FTP_Web_Service_Configuration__c oConfiguration){
    HttpRequest req = new HttpRequest();
    req.setHeader('ftp-host', oConfiguration.Host__c);
    req.setHeader('ftp-type', oConfiguration.Source__c);
    req.setHeader('username', oConfiguration.Username__c);
    req.setHeader('password', oConfiguration.Password__c);
    req.setHeader('port', oConfiguration.Port__c);
    req.setHeader('Content-Type', 'application/json');
    req.setEndpoint(endpoint);
    req.setMethod(method);
    if(String.isNotBlank(body) && String.isNotEmpty(body)){
        req.setBody(body);
    }
    req.setTimeout(120000);
    return req;
}
}

#5. FTPWebServiceUtility.cls

public class FTPWebServiceUtility {

public static String FTP_WebServiceEndpoint = 'https://www.ftp-api.com/ftp/';

public static FTP_Web_Service_Configuration__c oSourceFtpWebServiceConfig{
    get{
        if(oSourceFtpWebServiceConfig == null){
            return getFTPWebServiceConfiguration(false);
        }
        return oSourceFtpWebServiceConfig;  
    }
    set;
}

public static FTP_Web_Service_Configuration__c oTargetFtpWebServiceConfig{
    get{
        if(oTargetFtpWebServiceConfig == null){
            return getFTPWebServiceConfiguration(true);
        }
        return oTargetFtpWebServiceConfig;
    }
    set;
}

public static FTP_Web_Service_Configuration__c getFTPWebServiceConfiguration(Boolean isTargetEnvironment){
    FTP_Web_Service_Configuration__c oWebServiceConfiguration = new FTP_Web_Service_Configuration__c();
    String sSOQL = 'SELECT Id, Host__c, Username__c, Password__c, Port__c, Source__c';
    sSOQL += ' FROM FTP_Web_Service_Configuration__c';
    sSOQL += ' WHERE IsActive__c = true';
    if(isTargetEnvironment){
        sSOQL += ' AND IsTargetEnvironment__c = true';
    }else{
        sSOQL += ' AND IsSourceEnvironment__c = true';
    }
    for(FTP_Web_Service_Configuration__c wsc : Database.query(sSOQL)){
        oWebServiceConfiguration = wsc;
    }
    return oWebServiceConfiguration;
}


public static FTPResponseWrapper getFTPResponseWrapper(String status, String message, Integer code){
    FTPResponseWrapper oWrapper = new FTPResponseWrapper();
    oWrapper.Status = status;
    oWrapper.Message = message;
    oWrapper.Code = code;
    return oWrapper;
}
}

#6. FTPResponseWrapper.cls

public class FTPResponseWrapper {
@AuraEnabled public String Status;
@AuraEnabled public String Message;
@AuraEnabled public Integer Code;
@AuraEnabled public List<FTPFileResponseWrapper> Files;
}

Here we are adding complete github repository link. (Note: repository link will never change) Link:https://github.com/ftprestapi/ftp-api-example

Related Topic