Add file to Notes via REST API using NodeJS

I can successfully create the note.

I now need to upload the file and then link to the note.

Which I think I can do with this endpoint:

"/rest/v11_20/Notes/5f95bbe8-cd49-11ee-b015-fa163ec72422/file/filename"

I'm POSTing a file using FormData from my app and handling this on my NodeJS server.

Here I use axios to call the Endpoint above.

However, whatever format I pass the file in, I get either a 400 or a 422 returned from the Sugar API.

I've tried several things:

  • setting. removing / changing content-type 
  • setting / removing content disposition
  •  pass file as raw File, FormData, Buffer

Fullscreen
1
2
3
4
5
6
7
8
9
// this adds in basepath and sets api version
const fileUrl = getFileUrl(config, noteId)
const fileResponse = await axios.post(fileUrl, file, {
headers: {
'OAuth-Token': accessToken,
},
});
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

 
Has anybody had success doing this? Can you share your code?
Thanks
  • When I do this in php, I do the following:

    Fullscreen
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    //////////////////////////////////////////////////////////
    //Create note record - POST /<module>/:record
    //////////////////////////////////////////////////////////
    $url = $base_url . "/Notes";
    $note_arguments = array(
    "set_created_by" => true,
    "portal_flag" => false,
    "embed_flag" => false,
    "following" => false,
    "my_favorite" => false,
    );
    $note_response = call($url, $oauth2_token_response->access_token, 'POST', $note_arguments);
    $note_id = $note_response->id;
    if ($note_id != "") {
    //////////////////////////////////////////////////////////
    XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

    The call function (grown over years):

    Fullscreen
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    /**
    * Generic function to make cURL request.
    * @param $url - The URL route to use.
    * @param string $oauthtoken - The oauth token.
    * @param string $type - GET, POST, PUT, DELETE. Defaults to GET.
    * @param array $arguments - Endpoint arguments.
    * @param array $encodeData - Whether or not to JSON encode the data.
    * @param array $returnHeaders - Whether or not to return the headers.
    * @param array $filenHeader - Whether or not to upload a file
    * @return mixed
    */
    function call(
    $url,
    $oauthtoken='',
    $type='GET',
    $arguments=array(),
    $encodeData=true,
    $returnHeaders=false,
    $fileHeader=false
    )
    {
    XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

  • Thanks for the reply  , I got there in the end.

    In order to send both the file and filename (and the note content) I used FormData.

    However, this would appear to require the use of a busboy (or similar) to handle the multi-part form data.

    Here is the relevant code, in case it helps someone else

    Angular Client Code to send note and file

    Fullscreen
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    // Angular Service method to POST the file to the NodeJs Server
    addNote(jobId: string, payload: any) {
    const httpOptions = new CustomHttpParams(true);
    // it's necessary to use a form data object to get the boundary set correctly
    const fd = new FormData();
    fd.append('note', payload.file, payload.file.name);
    fd.append('filename', payload.filename);
    fd.append('note', JSON.stringify(payload.note));
    return this.http
    .post(`/functions/app/sugar/jobs/${jobId}/notes`, fd, {
    responseType: 'text',
    params: httpOptions,
    })
    .pipe(
    catchError((err) => this.handleError(err, [])),
    tap((response) => {
    console.log(response);
    })
    );
    XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

    NodeJS server code to handle POST from Angular

    Fullscreen
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    //NodeJS Handler
    const Busboy = require('busboy');
    const path = require('path');
    const os = require('os');
    const fs = require('fs');
    const addNoteEndpoint = async (request: Request, response: Response) => {
    const jobId = request.params.jobId;
    const uploads: any[] = [];
    const busboy = new Busboy({ headers: request.headers });
    // This callback will be invoked for each file uploaded
    busboy.on('file', (fieldName: string, file: any, filename: string, encoding: any, mimetype: any) => {
    // save the file locally (tmp memory)
    const filepath = path.join(os.tmpdir(), filename);
    uploads.push({ file: filepath, filename: filename });
    file.pipe(fs.createWriteStream(filepath));
    });
    XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

    Method to add file to Note using Sugar REST API

    Fullscreen
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    //NodeJS method to call Sugar API and add file to Note (created prior)
    function getAddFileToNoteUrl(config: SugarConfig, noteId: string) {
    return `https://${config.basePath}/rest/v${config.apiVersion}/Notes/${noteId}/file/filename`;
    }
    async function addFileToNote({ noteId, file, filename }: Params): Promise<any> {
    const accessToken = await getAccessToken();
    const url = getAddFileToNoteUrl(config, noteId);
    const form = new FormData();
    form.append('filename', fs.createReadStream(file), filename);
    const fileResponse = await axios.post(url, form, {
    headers: {
    ...form.getHeaders(),
    'OAuth-Token': accessToken,
    'Cache-Control': 'no-cache',
    },
    });
    XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

    Method to link note to a job using Sugar REST API

    Fullscreen
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    // NodeJS method to link the note to a job (both already exist)
    function getLinkNoteToJobUrl(config: SugarConfig, jobId: string, noteId: string) {
    return `https://${config.basePath}/rest/v${config.apiVersion}/JOB_Jobs/${jobId}/link/job_jobs_activities_1_notes/${noteId}`;
    }
    async function linkNoteToJob({ jobId, noteId }: Params): Promise<any> {
    const accessToken = await getAccessToken();
    const linkNoteUrl = getLinkNoteToJobUrl(config, jobId, noteId);
    const linkResponse = await axios.post(linkNoteUrl, null, {
    headers: {
    'OAuth-Token': accessToken,
    'Cache-Control': 'no-cache',
    },
    });
    return linkResponse.data;
    }
    XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX