Added ability to upload images to s3 and store them as part of ES object
This commit is contained in:
parent
9c74599950
commit
d14518ac91
|
@ -1,10 +1,12 @@
|
|||
from flask import Blueprint, render_template, abort, jsonify, request
|
||||
from elasticsearch import Elasticsearch, RequestsHttpConnection
|
||||
from requests_aws4auth import AWS4Auth
|
||||
import boto3
|
||||
import json
|
||||
import sys
|
||||
import config
|
||||
import setup
|
||||
import uuid
|
||||
|
||||
sys.path.insert(0,'..')
|
||||
|
||||
|
@ -13,6 +15,18 @@ sys.path.insert(0,'..')
|
|||
reload(sys)
|
||||
sys.setdefaultencoding("utf-8")
|
||||
|
||||
s3 = boto3.client('s3')
|
||||
|
||||
def create_presigned_post():
|
||||
bucket_name = config.JOYCE_S3_BUCKET
|
||||
key_name = str(uuid.uuid4())
|
||||
response = s3.generate_presigned_post(
|
||||
bucket_name,
|
||||
key_name,
|
||||
ExpiresIn = 3600
|
||||
)
|
||||
return response
|
||||
|
||||
if config.ENVIRONMENT == 'local':
|
||||
es = Elasticsearch(config.ELASTICSEARCH_LOCAL_HOST)
|
||||
|
||||
|
@ -62,7 +76,6 @@ def es_get_document(index, id):
|
|||
return data
|
||||
|
||||
def es_index_document(index, id, body):
|
||||
print(index)
|
||||
res = es.index(
|
||||
index=index,
|
||||
doc_type='doc',
|
||||
|
@ -325,7 +338,6 @@ def search_text():
|
|||
data = json.loads(request.data)
|
||||
results = es_search_text(data.get('data'))
|
||||
return jsonify(results)
|
||||
|
||||
#
|
||||
# Refresh ES
|
||||
# TODO: Restrict to dev only
|
||||
|
@ -333,3 +345,13 @@ def search_text():
|
|||
def refresh_es():
|
||||
setup.es_setup()
|
||||
return 'Success!'
|
||||
|
||||
#
|
||||
# Get Signed URL for Upload
|
||||
#
|
||||
@api.route('/signed_post/')
|
||||
def media_post_url():
|
||||
data = jsonify(create_presigned_post())
|
||||
print 'hey'
|
||||
# + '?signature=' + url.fields.signature + '&AWSAccessKeyId=' + url.fields.AWSAccessKeyId
|
||||
return data
|
|
@ -25,7 +25,8 @@
|
|||
"react-router": "^4.2.0",
|
||||
"react-router-dom": "^4.2.2",
|
||||
"react-router-redux": "^5.0.0-alpha.9",
|
||||
"redux": "^3.7.2"
|
||||
"redux": "^3.7.2",
|
||||
"uuid": "^3.3.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@fortawesome/fontawesome-free": "^5.5.0",
|
||||
|
|
|
@ -47,7 +47,19 @@ const apiActions = {
|
|||
type: 'GET_SEARCH_RESULTS',
|
||||
data: response.data,
|
||||
status: response.status ? response.status : 'request'
|
||||
})
|
||||
}),
|
||||
uploadMediaToS3Request: (response, data) =>
|
||||
({
|
||||
type: 'UPLOAD_TO_S3_REQUEST',
|
||||
file: data,
|
||||
signed_post: response.data
|
||||
}),
|
||||
uploadMediaToS3Response: (response) =>
|
||||
({
|
||||
type: 'UPLOAD_TO_S3_RESPONSE',
|
||||
status: response.status,
|
||||
url: response.url ? response.url : undefined
|
||||
}),
|
||||
}
|
||||
|
||||
export default apiActions
|
|
@ -122,6 +122,12 @@ const userActions = {
|
|||
hideAdmin: () =>
|
||||
({
|
||||
type: 'HIDE_ADMIN_HEADER'
|
||||
}),
|
||||
// Media
|
||||
uploadMediaInput: input =>
|
||||
({
|
||||
type: 'UPLOAD_MEDIA_SUBMIT',
|
||||
data: input
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
@ -1,11 +1,14 @@
|
|||
import React from 'react'
|
||||
|
||||
const MediaUploadInput = ({input, onChange}) =>
|
||||
const MediaUploadInput = ({input, s3Path, onChange, onUpload}) =>
|
||||
<div className="input-group mb-3">
|
||||
<div className="custom-file">
|
||||
<input type="file" className="custom-file-input" id="media_input" onChange={onChange}/>
|
||||
<label className="custom-file-label" htmlFor="media_input">{input ? input[0].name : 'Choose file'}</label>
|
||||
</div>
|
||||
<div className="input-group-append">
|
||||
<button className="btn btn-outline-warning" type="button" disabled={input ? false : true} onClick={()=>onUpload(input)}>Upload</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
export default MediaUploadInput
|
|
@ -23,6 +23,7 @@ const EditorEditMode = ({
|
|||
onColorPickerInputChange,
|
||||
onColorSwatchClick,
|
||||
onMediaInputChange,
|
||||
onMediaUpload,
|
||||
cancelEdit,
|
||||
onSubmitClick,
|
||||
onToolButtonClick,
|
||||
|
@ -56,9 +57,12 @@ const EditorEditMode = ({
|
|||
onColorSwatchClick={onColorSwatchClick}
|
||||
/>
|
||||
}
|
||||
{docType === 'media' &&
|
||||
<MediaUploadInput input={inputs.fileUpload} onChange={onMediaInputChange}/>
|
||||
}
|
||||
{docType === 'media' && inputs.s3Path &&
|
||||
<p>File uploaded!</p>
|
||||
}
|
||||
{docType === 'media' && !inputs.s3Path &&
|
||||
<MediaUploadInput input={inputs.uploadFile} onChange={onMediaInputChange} onUpload={onMediaUpload}/>
|
||||
}
|
||||
</EditorAttributeContentBlock>
|
||||
<EditorBottomBarContentBlock>
|
||||
<EditorSubmitOptions
|
||||
|
@ -100,6 +104,9 @@ const mapDispatchToProps = dispatch => {
|
|||
onMediaInputChange: input => {
|
||||
dispatch(actions.updateMediaInput(input))
|
||||
},
|
||||
onMediaUpload: input => {
|
||||
dispatch(actions.uploadMediaInput(input))
|
||||
},
|
||||
cancelEdit: () => {
|
||||
dispatch(actions.cancelEdit())
|
||||
},
|
||||
|
|
|
@ -24,8 +24,7 @@ const joyceAPI = store => next => action => {
|
|||
if (action.status === 'request') {
|
||||
if (action.id) {
|
||||
api.HTTPPostWriteDocument(action.id, action.docType, action.data).then(response =>
|
||||
store.dispatch(actions.saveDocument(response)
|
||||
)
|
||||
store.dispatch(actions.saveDocument(response))
|
||||
)} else {
|
||||
api.HTTPPutCreateDocument(action.docType, action.data).then(response =>
|
||||
store.dispatch(actions.saveDocument(response))
|
||||
|
@ -46,7 +45,23 @@ const joyceAPI = store => next => action => {
|
|||
store.dispatch(actions.getSearchResults(response))
|
||||
)
|
||||
}
|
||||
break
|
||||
break
|
||||
case 'UPLOAD_MEDIA_SUBMIT':
|
||||
api.HTTPGetSignedPost().then(response =>
|
||||
store.dispatch(actions.uploadMediaToS3Request(response, action.data[0])
|
||||
))
|
||||
break
|
||||
case 'UPLOAD_TO_S3_REQUEST':
|
||||
const formData = new FormData()
|
||||
const url = action.signed_post.url
|
||||
formData.append('AWSAccessKeyId', action.signed_post.fields.AWSAccessKeyId)
|
||||
formData.append('key', action.signed_post.fields.key)
|
||||
formData.append('policy', action.signed_post.fields.policy)
|
||||
formData.append('signature', action.signed_post.fields.signature)
|
||||
formData.append('file', action.file)
|
||||
api.HTTPPostMedia(url, formData).then(response =>
|
||||
store.dispatch(actions.uploadMediaToS3Response(response))
|
||||
)
|
||||
default:
|
||||
break
|
||||
}
|
||||
|
|
|
@ -3,6 +3,7 @@ import { EditorState, Modifier } from 'draft-js'
|
|||
import { stateToHTML } from 'draft-js-export-html'
|
||||
|
||||
import actions from '../actions'
|
||||
import api from '../modules/api'
|
||||
import helpers from '../modules/helpers'
|
||||
import { validateSubmittedDocument, validateSubmittedAnnotation } from '../modules/validation'
|
||||
import { html_export_options, convertToSearchText, linkDecorator } from '../modules/editorSettings.js'
|
||||
|
@ -25,11 +26,14 @@ const joyceInterface = store => next => action => {
|
|||
const data = {
|
||||
title: action.inputs.documentTitle,
|
||||
html_source: stateToHTML(textContent, html_export_options),
|
||||
search_text: convertToSearchText(textContent)
|
||||
search_text: convertToSearchText(textContent),
|
||||
}
|
||||
if (action.docType === 'tags') {
|
||||
data.color = action.inputs.colorPicker
|
||||
}
|
||||
if (action.docType === 'media') {
|
||||
data.s3Path = action.inputs.s3Path
|
||||
}
|
||||
if (action.currentDocument.id) {
|
||||
data.id = action.currentDocument.id
|
||||
}
|
||||
|
@ -90,7 +94,7 @@ const joyceInterface = store => next => action => {
|
|||
// Search Action Middleware
|
||||
case 'CLICK_SEARCH':
|
||||
store.dispatch(actions.getSearchResults({data: action.data}))
|
||||
break
|
||||
break
|
||||
default:
|
||||
break
|
||||
}
|
||||
|
|
|
@ -43,9 +43,20 @@ const api = {
|
|||
axios.get(apiRoute + 'refresh/').then(res => {
|
||||
return {status: 'success', data: res.data}
|
||||
}).catch(error => {
|
||||
console.log(error)
|
||||
return {status: 'error', data: error}
|
||||
}),
|
||||
}),
|
||||
HTTPGetSignedPost: () =>
|
||||
axios.get(apiRoute + 'signed_post/').then(res=> {
|
||||
return {status: 'success', data: res.data}
|
||||
}).catch(error => {
|
||||
return {status: 'error', data: error}
|
||||
}),
|
||||
HTTPPostMedia: (url, formData) =>
|
||||
axios.post(url, formData, {headers: {'Content-Type': 'image/*', 'ACL': 'public-read'}}).then(res=> {
|
||||
return {status: 'success', url: url + formData.get('key')}
|
||||
}).catch(error => {
|
||||
return {status: 'error', data: error}
|
||||
})
|
||||
}
|
||||
|
||||
export default api
|
|
@ -2,18 +2,34 @@ const initialState = {
|
|||
documentTitle: '',
|
||||
search: '',
|
||||
colorPicker: '',
|
||||
fileUpload: undefined
|
||||
uploadFile: undefined,
|
||||
s3Path: undefined
|
||||
}
|
||||
|
||||
const inputs = (state=initialState, action) => {
|
||||
switch(action.type) {
|
||||
// Document Title
|
||||
case 'GET_DOCUMENT_TEXT':
|
||||
if (action.status === 'success' && action.state === 'currentDocument') {
|
||||
console.log('state', action.state)
|
||||
console.log('status', action.status)
|
||||
console.log('docType', action.docType)
|
||||
if (action.status === 'success' && action.state === 'currentDocument' && ['tags', 'media'].indexOf(action.docType) <= 0 ) {
|
||||
return {
|
||||
...state,
|
||||
documentTitle: action.data.title
|
||||
}
|
||||
} else if (action.status === 'success' && action.docType === 'tags') {
|
||||
return {
|
||||
...state,
|
||||
documentTitle: action.data.title,
|
||||
colorPicker: action.data.color
|
||||
}
|
||||
} else if (action.status === 'success' && action.state === 'currentDocument' && action.docType === 'media') {
|
||||
return {
|
||||
...state,
|
||||
documentTitle: action.data.title,
|
||||
s3Path: action.data.s3Path
|
||||
}
|
||||
} else { return state }
|
||||
case 'CREATE_DOCUMENT':
|
||||
return {
|
||||
|
@ -32,13 +48,6 @@ const inputs = (state=initialState, action) => {
|
|||
search: action.data
|
||||
}
|
||||
// Color Picker
|
||||
case 'GET_DOCUMENT_TEXT':
|
||||
if (action.status === 'success' && action.docType === 'tags') {
|
||||
return {
|
||||
...state,
|
||||
colorPicker: action.data.color
|
||||
}
|
||||
} else { return state }
|
||||
case 'SAVE_DOCUMENT':
|
||||
if (action.status === 'success' && action.docType === 'tags') {
|
||||
return {
|
||||
|
@ -65,10 +74,18 @@ const inputs = (state=initialState, action) => {
|
|||
case 'UPDATE_MEDIA_INPUT':
|
||||
return {
|
||||
...state,
|
||||
fileUpload: action.data
|
||||
uploadFile: action.data
|
||||
}
|
||||
default:
|
||||
return state
|
||||
// S3 File
|
||||
case 'UPLOAD_TO_S3_RESPONSE':
|
||||
if (action.status === 'success') {
|
||||
return {
|
||||
...state,
|
||||
s3Path: action.url
|
||||
}
|
||||
} else { return state }
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue