Skip to content

Instantly share code, notes, and snippets.

@developius

developius/README.md

Last active Mar 5, 2021
Embed
What would you like to do?
React Native + Ruby on Rails + AWS S3 Presigned PUT
import { Image } from "react-native-image-crop-picker"
interface IReport {
field_one: string
field_two: string
field_three: string
attachments: string[]
}
interface IDraftAttachment {
signature: {
url: 'https://bucket.s3.region.amazonaws.com/etc',
headers: {
"Content-Type": "image/jpeg",
"Content-MD5": "3Tbhfs6EB0ukAPTziowN0A=="
},
signed_id: 'signedidoftheblob'
},
file: Image
}
export class Api {
async uploadAttachment(
attachment: IDraftAttachment,
onProgress: (progress: number, total: number) => void
): Promise<void> {
console.log('Uploading attachment to:', attachment.signature)
const attachmentUrl = await new Promise<string>((resolve, reject) => {
const xhr = new XMLHttpRequest();
xhr.open('PUT', attachment.signature.url, true);
Object.keys(attachment.signature.headers).forEach(key => {
xhr.setRequestHeader(key, attachment.signature.headers[key])
})
xhr.onreadystatechange = () => {
if (xhr.readyState !== 4) return
if (xhr.status === 200) return resolve()
reject(xhr.status)
}
xhr.onprogress = e => {
onProgress(e.loaded, e.total)
}
xhr.send({
uri: attachment.file.path,
type: attachment.file.mime,
name: attachment.file.filename || Date.now().toString()
});
})
}
async createReport(report: IReport): Promise<number> {
const result = await axios.post('/api/reports', report)
return result.data.id // ID of the newly-created report
}
}
import { Buffer } from 'buffer'
import RNFS from 'react-native-fs'
import ImagePicker, {
Image as ImagePickerImage,
Options as ImagePickerOptions,
} from "react-native-image-crop-picker"
// https://github.com/ivpusic/react-native-image-crop-picker#request-object
const options: ImagePickerOptions = {
cropping: false,
multiple: true,
sortOrder: "asc",
maxFiles: 10,
// we don't need the base64 here as we upload the file directly
// from the FS rather than a base64-encoded string of it
includeBase64: false
}
// call openImageLibrary from a <Button> or similar
const openImageLibrary = async () => {
const results = (await ImagePicker.openPicker(options)) as ImagePickerImage[]
results.forEach(async r => {
const hex = await RNFS.hash(r.path, 'md5')
const base64 = Buffer.from(hex, 'hex').toString('base64')
// save base64 somewhere with the attachment for use when retrieving the presigned url
})
}
class Api::PresignedUploadController < Api::BaseController
# POST /api/presigned-upload
def create
create_blob
render_success(
data: {
url: @blob.service_url_for_direct_upload(expires_in: 30.minutes),
headers: @blob.service_headers_for_direct_upload,
signed_id: @blob.signed_id
}
)
end
private
def create_blob
@blob = ActiveStorage::Blob.create_before_direct_upload!(
filename: blob_params[:filename],
byte_size: blob_params[:byte_size],
checksum: blob_params[:checksum],
content_type: blob_params[:content_type]
)
end
def blob_params
params.require(:file).permit(:filename, :byte_size, :checksum, :content_type)
end
end
class Report < ApplicationRecord
has_many_attached :attachments
# etc
end
class Api::ReportsController < Api::BaseController
# POST /api/reports
def create
@report = Report.new(report_params)
if @report.save
render_success(data: @report)
else
render_error(400, object: @report)
end
end
private
def report_params
params.permit(
:field_one, :field_two, :field_three,
attachments: [] # this is where the signed_ids will end up
)
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment