EnCiv / undebate-ssp

Undebate Self Service Portal- Web portal where election administrators of democratically run organizations create undebates for their candidates and voters
Other
6 stars 9 forks source link

Upload a logo #212

Closed ddfridley closed 1 year ago

ddfridley commented 1 year ago

In app/components/election-component.js - there is an option to provide a organizationUrl. We need to provide an an option for the user to upload a url image and save that to cloudinary and then use that url. The user should still be able to enter the url directly.

It might be good to find a package on npm - make sure it's MIT licensed and has a good number of weekly downloads.

ddfridley commented 1 year ago

@mattwinne This is a link to the stream-upload-video api in the undebate repo: https://github.com/EnCiv/undebate/blob/master/app/socket-apis/stream-upload-video.js

And here is a link to the browser side code that does this: https://github.com/EnCiv/undebate/blob/master/app/components/lib/create-participant.js

    var stream = ss.createStream()
    stream.on('error', err => {
      logger.error('createParticipant.upload socket stream error:', err)
    })

    var ssSocket = ss(window.socket)
    //use this for debugging
    //ssSocket._oldEmit = ssSocket.emit
    //ssSocket.emit = ((...args) => (console.info("emit", ...args), ssSocket._oldEmit(...args)))
    ssSocket.emit('stream-upload-video', stream, { name: file_name, size: blob.size }, responseUrl)

    var bstream = ss
      .createBlobReadStream(blob, { highWaterMark: 1024 * 200 }) // high hiwWaterMark to increase upload speed
      .pipe(
        through2((chunk, enc, cb) => {
          updateProgress(chunk)
          cb(null, chunk) // 'this' becomes this of the react component rather than this of through2 - so pass the data back in the callback
        })
      )
      .pipe(stream)

    bstream.on('error', err => {
      logger.error('createParticipant.upload blob stream error:', err)
    })
    stream.on('end', () => {
      var uploadArgs
      if ((uploadArgs = uploadQueue.shift())) {
        return upload(...uploadArgs)
      } else {
        progressFunc && progressFunc({ progress: 'complete.', uploadComplete: true })
        logger.trace('createParticipant upload after login complete')
      }
    })
  }

Streams are for big files, you don't have to use it, but it might be easier to clone this code.

Note if you are going to use a stream, the api name has to begin with 'stream' - there is special handline in the server that has to go around the socket for streams.

mattwinne commented 1 year ago

@ddfridley Implemented by uploading the image in the browser, sending to server via websocket (non-stream), and uploading to cloudinary from server. I also looked into ways it could be uploaded entirely from the browser.

One method is with the cloudinary widget. This only requires sharing the cloud name with the browser (not the api key or api secret). That doesn't seem like too much of a security risk since technically the cloud name is visible / can be inspected in any cloudinary urls we use. This may be the simplest solution if we want to shift to uploading entirely from the frond end.

I also looked into how a one time key can be sent from the server to the client to upload. After reading the cloudinary docs, there is some functionality for this, but as far as I understand this only adds extra security through the addition of SHA authentication signatures, and still requires at a minimum the cloud name to be available at the client. I may look into this more. Perhaps it is a matter of configuring your cloudinary repo settings to only allow uploads that are signed, thus having the cloud name visible is not any security risk.

I added some stylistic decisions in my solution, including displaying the URL once it is uploaded or the URL is added. Please let me know if you have any comments or recommended changes.