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

// this adds in basepath and sets api version

const fileUrl = getFileUrl(config, noteId)

const fileResponse = await axios.post(fileUrl, file, {
   headers: {
      'OAuth-Token': accessToken,
   },
});

 
Has anybody had success doing this? Can you share your code?
Thanks
Parents
  • 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

    // 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);
            })
          );
      }

    NodeJS server code to handle POST from Angular

    //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));
      });
    
      let note: any, filename: string;
    
      busboy.on('field', (fieldName: string, val: any) => {
        if (fieldName === 'note') {
          note = val;
        } else if (fieldName === 'filename') {
          filename = val;
        } else {
          logger.info(`unknown field ${fieldName} => ${val}`);
        }
      });
    
      busboy.on('finish', async () => {
        if (uploads.length === 0) {
          response.end('no files found');
        }
    
        let upload = uploads[0];
        let file = upload.file;
    
        const uid = await addNote(jobId, note, filename, file);
    
        response.status(200).send(uid);
      });
    
      // The raw bytes of the upload will be in request.rawBody.
      // This line required to send it to busboy, and trigger file events
      //@ts-ignore
      busboy.end(request.rawBody);
    };

    Method to add file to Note using Sugar REST API

    //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',
        },
      });
    
      return fileResponse?.data;
    }

    Method to link note to a job using Sugar REST API

    // 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;
    }

Reply
  • 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

    // 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);
            })
          );
      }

    NodeJS server code to handle POST from Angular

    //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));
      });
    
      let note: any, filename: string;
    
      busboy.on('field', (fieldName: string, val: any) => {
        if (fieldName === 'note') {
          note = val;
        } else if (fieldName === 'filename') {
          filename = val;
        } else {
          logger.info(`unknown field ${fieldName} => ${val}`);
        }
      });
    
      busboy.on('finish', async () => {
        if (uploads.length === 0) {
          response.end('no files found');
        }
    
        let upload = uploads[0];
        let file = upload.file;
    
        const uid = await addNote(jobId, note, filename, file);
    
        response.status(200).send(uid);
      });
    
      // The raw bytes of the upload will be in request.rawBody.
      // This line required to send it to busboy, and trigger file events
      //@ts-ignore
      busboy.end(request.rawBody);
    };

    Method to add file to Note using Sugar REST API

    //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',
        },
      });
    
      return fileResponse?.data;
    }

    Method to link note to a job using Sugar REST API

    // 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;
    }

Children
No Data