KeystoneJS Cloudinary image upload - express

I am using the latest version of KeystoneJS and have a form working to add a record to the database.
I'm having trouble getting image uploads to work.
My model conatains:
heroImage: { type: Types.CloudinaryImage, autoCleanup : true },
My form includes:
<input type="file" accept="image/*" id="heroImage" name="heroImage_upload" className='field-upload'>
and my middleware for saving the form simply includes:
view.on('post', {action: 'save'}, function(next)
{
var newProperty = new Property.model(req.body);
console.log(newProperty);
newProperty.save(function(err, body)
{});
});
which works great for all field's except the file upload.
I've tried adding:
newProperty.heroImage = req.files['heroImage'];
which leaves heroImage as null.
I also tried creating a cloudinaryImage but this causes an error:
var img = new CloudinaryImage(req.files['heroImage']);
It all works fine when I use the KeystoneJS admin dashboard to upload images. Can someone please explain how I should use the cloudinaryImage field type in my own form?
Thanks

Not sure if this will help, but there is a note at the bottom of the docs:
http://keystonejs.com/docs/database/#fieldtypes-cloudinaryimage
"Remember that if you are uploading images to a CloudinaryImage field using an HTML form, you need to specify enctype="multipart/form-data" in your form tag."
Have you included that in your form?
Update:
So, the following should work, assuming your model is called MyModel, and your form data uses the same object keys as your model. (i.e. the image for your heroImage field should be provided as heroImage in the POST data).
var MyModel = keystone.list('MyModel');
view.on('post', {action: 'save'}, function(next)
{
var item = new MyModel.model();
data = req.body;
item.getUpdateHandler(req).process(data, function(err) {
// Handle error
}
}
Keystone should then handle all the specific cloudinary stuff internally.

Here is a way to do it with cloudinary.v2
untested keystone
cloudinary.v2.uploader.upload(base64_file_data, (err, result) => {
var newMyModel = new MyModel.model(model_data);
newMyModel.image = result;
let updater = newMyModel.getUpdateHandler(req);
updater.process(newMyModel, {
fields: image
}, err => {...})
})
tested mongoose
cloudinary.v2.uploader.upload(base64_file_data, (err, result) => {
var newMyModel = new MyModel.model(model_data);
newMyModel.image = result;
newMyModel.save(err => {...})
})

Related

Display an image when Blob is returned from an API

I’m writing a Vue app which uses the Microsoft Graph API and SDK for initial authentication on the front end and then uses different aspects of the API throughout the app. Like displaying emails, OneDrive files, etc.
I’m using the profile photo from a users Microsoft account to display an avatar to other users. My issue is that when I call {graphApi}/me/photo/$value the result returned is a Blob. This is the endpoint provided in MS Graph.
I’ve read the MS Graph docs thoroughly, combed MDN & other sources and have not found a way to transform this result into a simple image in my markup.
Template markup:
<template>
<img :src="userPhoto" :alt="user.displayName" />
</template>
Setup function logic:
<script setup>
import { client } from "./foobar"
const userPhoto = ref();
async function getPhoto(){
const photo = await client.api("/me/photo/$value").get()
console.log(photo.value)
userPhoto.value = photo
};
</script>
Returned result:
{Blob, image:{id: default, size:48x48}}
So how do I decode or download the Blob properly to display an image in my Vue markup?? I’ve tried createObjectURL and FileReader() without any luck. I’m sure there is a simple solution but I am not finding it. Thanks for the help.
Explanation:
In below snippet as you can see I am passing the objectId of the Employee fetched from Graph previously.
Then making call for employee to get their Avatar/DP
The Graph Profile Photo endpoint returns binary Data of the photo.
Convert that binary data into data:image/png;base64,<readAsDataURL> URL e.g. data:image/png;base64,iVBORw0KGgoAAAANSU...
Use in <img src="dataUrl"/>
let imageUrl = (await request.get(GRAPH_CONFIG.GRAPH_DP_ENDPT + objectId + "/photos/48x48/\$value", { responseType: 'arraybuffer', validateStatus: (status) => status === 200 || status === 404 }))
if (imageUrl.status === 200) {
let reader = new FileReader()
let blob = new Blob([imageUrl.data], {type: 'image/jpeg'})
reader.onload = (event) => {
return event.target?.result.toString();
}
reader.readAsDataURL(blob)
}

A better way to handle async saving to backend server and cloud storage from React Native app

In my React Native 0.63.2 app, after user uploads images of artwork, the app will do 2 things:
1. save artwork record and image records on backend server
2. save the images into cloud storage
Those 2 things are related and have to be done successfully all together. Here is the code:
const clickSave = async () => {
console.log("save art work");
try {
//save artwork to backend server
let art_obj = {
_device_id,
name,
description,
tag: (tagSelected.map((it) => it.name)),
note:'',
};
let img_array=[], oneImg;
imgs.forEach(ele => {
oneImg = {
fileName:"f"+helper.genRandomstring(8)+"_"+ele.fileName,
path: ele.path,
width: ele.width,
height: ele.height,
size_kb:Math.ceil(ele.size/1024),
image_data: ele.image_data,
};
img_array.push(oneImg);
});
art_obj.img_array = [...img_array];
art_obj = JSON.stringify(art_obj);
//assemble images
let url = `${GLOBAL.BASE_URL}/api/artworks/new`;
await helper.getAPI(url, _result, "POST", art_obj); //<<==#1. send artwork and image record to backend server
//save image to cloud storage
var storageAccessInfo = await helper.getStorageAccessInfo(stateVal.storageAccessInfo);
if (storageAccessInfo && storageAccessInfo !== "upToDate")
//update the context value
stateVal.updateStorageAccessInfo(storageAccessInfo);
//
let bucket_name = "oss-hz-1"; //<<<
const configuration = {
maxRetryCount: 3,
timeoutIntervalForRequest: 30,
timeoutIntervalForResource: 24 * 60 * 60
};
const STSConfig = {
AccessKeyId:accessInfo.accessKeyId,
SecretKeyId:accessInfo.accessKeySecret,
SecurityToken:accessInfo.securityToken
}
const endPoint = 'oss-cn-hangzhou.aliyuncs.com'; //<<<
const last_5_cell_number = _myself.cell.substring(myself.cell.length - 5);
let filePath, objkey;
img_array.forEach(item => {
console.log("init sts");
AliyunOSS.initWithSecurityToken(STSConfig.SecurityToken,STSConfig.AccessKeyId,STSConfig.SecretKeyId,endPoint,configuration)
//console.log("before upload", AliyunOSS);
objkey = `${last_5_cell_number}/${item.fileName}`; //virtual subdir and file name
filePath = item.path;
AliyunOSS.asyncUpload(bucket_name, objkey, filePath).then( (res) => { //<<==#2 send images to cloud storage with callback. But no action required after success.
console.log("Success : ", res) //<<==not really necessary to have console output
}).catch((error)=>{
console.log(error)
})
})
} catch(err) {
console.log(err);
return false;
};
};
The concern with the code above is that those 2 async calls may take long time to finish while user may be waiting for too long. After clicking saving button, user may just want to move to next page on user interface and leaves those everything behind. Is there a way to do so? is removing await (#1) and callback (#2) able to do that?
if you want to do both tasks in the background, then you can't use await. I see that you are using await on sending the images to the backend, so remove that and use .then().catch(); you don't need to remove the callback on #2.
If you need to make sure #1 finishes before doing #2, then you will need to move the code for #2 intp #1's promise resolving code (inside the .then()).
Now, for catching error. You will need some sort of error handling that alerts the user that an error had occurred and the user should trigger another upload. One thing you can do is a red banner. I'm sure there are packages out there that can do that for you.

Upload image need to refresh using $emit / $on in Vue

I have a method that calls the user data via axios
// method name getUser()
const user = await axios.get(`/user/${this.id}`)
this.id = user.data.data.id
this.name = user.data.data.name
this.email = user.data.data.email
I then use that in the mounted so if user visits /profile/id
it'll load the user data
mounted() {
this.getUser()
}
I tried to upload an image and I emit the event using global event bus once the image is successfully uploaded.
this.$event.$emit('IMAGE_UPLOAD')
Then catch that on the mounted too
mounted () {
// if I remove this it works, but I need to preload the data of the user
this.getUser()
this.$event.$on('IMAGE_UPLOAD', () => {
this.getUser()
})
}
my problem is it doesn't change the image meaning I still need to refresh the page if I call the this.getUser() too inside the mounted.
So I'm wondering how to work around this.
Thanks!
Since the url and name of the image does not change when the new image is uploaded the image in the browser is not updated. So what I have done in the past is a little trick to essentially change the url to the image by adding a unique query parameter. So use a data property for the location of your user image and in your method where you update the users data also update the img url and add something unique to the query parameter. I usually use new Date().getTime(). So you will end up with something like /img/user-xxxx.png?1559289852686
data(){
userImg: '/img/user-xxxx.png'
},
methods:{
getUser(){
//... get your user data
.then((data)=>{
this.userImg = data.user.img +'?'+ new Date().getTime();
})
}

Nativescript Webview callback uri

Can we have post back from external url to web view in nativescript and get the values from postback? It is the oauth2 flow with redirect uri where user display external link of website in native webview and get tokens value from postback url . Any suggestion or pointer to tut or blog? All the major players provide support for this and it is very much used for oauth.
This is my main-page.js where all the tokens and value i get within the function under args.url
var vmModule = require("./main-view-model");
var webViewModule = require('ui/web-view');
function pageLoaded(args) {
var page = args.object;
page.bindingContext = vmModule.mainViewModel;
var webView = page.getViewById('myWebView');
debugger;
//webView.url =
webView.on(webViewModule.WebView.loadFinishedEvent, function (args) {
alert(JSON.stringify(args.url));
});
webView.src = vmModule.mainViewModel.url;
}
exports.pageLoaded = pageLoaded;
And my view is
<Page xmlns="http://www.nativescript.org/tns.xsd" loaded="pageLoaded">
<GridLayout>
<WebView id="myWebView" />
</GridLayout>
</Page>
All the time it was written there in documentation and i just didn't look at it carefully. Hopefully it will help others.
You should be able to watch the urlProperty for changes. E.g.
Given you have a view which looks like this:
<Page loaded="loaded">
<WebView id="myWebView" src="{{ url }}" />
</Page>
Then you can attach an observer to that WebView and react to changes to the URL property like this:
var webViewModule = require('ui/web-view');
function loaded(args) {
var page = args.object;
var webView = page.getViewById('myWebView');
webView.on(webViewModule.WebView.urlProperty, function (changeArgs) {
console.dir(changeArgs);
// Do something with the URL here.
// E.g. extract the token and hide the WebView.
});
}
I Know this is old. But the code below can help a lot of people.
YOUR_WEB_VIEW_OBJECT.on(webViewModule.WebView.loadFinishedEvent, function (args) {
args.object.android.getSettings().setJavaScriptEnabled(true);
args.object.android.evaluateJavascript('(function() { console.log("LOGS"); return "MESSAGE"; })();', new android.webkit.ValueCallback({
onReceiveValue: function (result) {
console.log(result);
}
}));
});
This code currently works for Android. You can create iOS version as well by digging into their APIs Reference then converting it into {N} Suitable.
On IOS you can do it like this:
args.object.ios.stringByEvaluatingJavaScriptFromString('YOUR_JAVASCRIPT_CODE');
Just for the record, now this is how you do it
var webViewNat = this.webView.nativeElement;
this.oLangWebViewInterface = new webViewInterfaceModule.WebViewInterface(webViewNat)
webViewNat.ios.evaluateJavaScriptCompletionHandler(`var myvar = document.getElementById('userNameInput').value = '${getString('Email')}';`, (id, err) => {
if (err) {
return err;
}
return id;
});

jquery .live on form submit not picking up dynamically added inputs

when my ajaxupload script finishes it adds a read-only input w/ the value of the image's URL.
it is a long script, but i think this is the relevant part that fires on successful completion:
var location = '<div id="'+ID+'_location" class="img_location">' + '<input name="'+ID+'" class="location regular-text" type="text" size="50" readonly="readonly" value="'+response+'" />';
$(container).append(location).show(); //create readonly input
$(container) is defined just as the parent div of the upload button. that part seems to work... the image is uploaded, it is saved properly, and the input w/ the image's location is added to to the DOM. but i've discovered a bug that if I click my SAVE button (which triggers my ajax save function) then this new input is NOT captured.
here is my save function:
$('form#childoptions').live('submit', function(e) {
e.preventDefault();
var values = $(this).serialize();
alert(values);
var data = {
action: 'save_function',
type : 'save',
_nonce: '<?php echo $nonce; ?>',
formdata: values
};
$.post(ajaxurl, data, function(response) {
//alert(response);
if(response == 1) {
show_message(1);
t = setTimeout('fade_message()', 2000);
} else {
show_message(99);
t = setTimeout('fade_message()', 2000);
}
});
//return false;
});
only the new input is not captured. the rest works properly. there is also no problem if i refresh in between as I presume the input is part of the DOM. which is why i thought to use .live. i thought i had solved the issue twice- 1. i wasn't using a "name" on the dynamic input and 2. i wasn't using .live on the form. but now i am doing both and not getting anywhere.
all help is much appreciated. let me know if there is more information I can provide.
It appears that your using live on the whole form, not on inputs. So the live event binding would try to pickup new forms with id childoptions. This won't work. You'd be better off using bind() instead. Have you tried:
$('form#childoptions').bind('submit', function(e) {…}
I'm curious if this will fix your issue.