diff --git a/blueprints/api.py b/blueprints/api.py
index 0b0b930..f2a359a 100644
--- a/blueprints/api.py
+++ b/blueprints/api.py
@@ -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
\ No newline at end of file
diff --git a/package.json b/package.json
index 9dbd8c6..abf2860 100644
--- a/package.json
+++ b/package.json
@@ -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",
diff --git a/src/actions/apiActions.js b/src/actions/apiActions.js
index 6cba6c3..81894ea 100644
--- a/src/actions/apiActions.js
+++ b/src/actions/apiActions.js
@@ -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
\ No newline at end of file
diff --git a/src/actions/userActions.js b/src/actions/userActions.js
index d8129f7..5797939 100644
--- a/src/actions/userActions.js
+++ b/src/actions/userActions.js
@@ -122,6 +122,12 @@ const userActions = {
hideAdmin: () =>
({
type: 'HIDE_ADMIN_HEADER'
+ }),
+ // Media
+ uploadMediaInput: input =>
+ ({
+ type: 'UPLOAD_MEDIA_SUBMIT',
+ data: input
})
}
diff --git a/src/components/mediaUploadInput.js b/src/components/mediaUploadInput.js
index 914446d..a1d1f12 100644
--- a/src/components/mediaUploadInput.js
+++ b/src/components/mediaUploadInput.js
@@ -1,11 +1,14 @@
import React from 'react'
-const MediaUploadInput = ({input, onChange}) =>
+const MediaUploadInput = ({input, s3Path, onChange, onUpload}) =>
+
+
+
export default MediaUploadInput
\ No newline at end of file
diff --git a/src/containers/editorEditModeContainer.js b/src/containers/editorEditModeContainer.js
index fd7ef47..0dec22c 100644
--- a/src/containers/editorEditModeContainer.js
+++ b/src/containers/editorEditModeContainer.js
@@ -23,6 +23,7 @@ const EditorEditMode = ({
onColorPickerInputChange,
onColorSwatchClick,
onMediaInputChange,
+ onMediaUpload,
cancelEdit,
onSubmitClick,
onToolButtonClick,
@@ -56,9 +57,12 @@ const EditorEditMode = ({
onColorSwatchClick={onColorSwatchClick}
/>
}
- {docType === 'media' &&
-
- }
+ {docType === 'media' && inputs.s3Path &&
+ File uploaded!
+ }
+ {docType === 'media' && !inputs.s3Path &&
+
+ }
{
onMediaInputChange: input => {
dispatch(actions.updateMediaInput(input))
},
+ onMediaUpload: input => {
+ dispatch(actions.uploadMediaInput(input))
+ },
cancelEdit: () => {
dispatch(actions.cancelEdit())
},
diff --git a/src/middleware/joyceAPI.js b/src/middleware/joyceAPI.js
index 4f9755e..e528b89 100644
--- a/src/middleware/joyceAPI.js
+++ b/src/middleware/joyceAPI.js
@@ -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
}
diff --git a/src/middleware/joyceInterface.js b/src/middleware/joyceInterface.js
index cf53e84..15bad3d 100644
--- a/src/middleware/joyceInterface.js
+++ b/src/middleware/joyceInterface.js
@@ -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
}
diff --git a/src/modules/api.js b/src/modules/api.js
index 2099d93..0828c68 100644
--- a/src/modules/api.js
+++ b/src/modules/api.js
@@ -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
\ No newline at end of file
diff --git a/src/reducers/inputs.js b/src/reducers/inputs.js
index dacfeb0..345f295 100644
--- a/src/reducers/inputs.js
+++ b/src/reducers/inputs.js
@@ -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 }
}
}