I am using Cloudinary to host my media on the cloud for my NodeJS project.
To delete an image from the Clodinary Cloud, I need to pass a Public Id for that image, to the Cloudinary API.
I realised, Public ID is embedded into the url, how to I extract it out from the URL?
Because, I don't want to store my data in this format :
image : {
url : `http://res.cloudinary.com/cloud_name/image/upload/v1647610701/rsorl4rtziefw46fllvh.png`,
publicId : `rsorl4rtziefw46fllvh`
}
Rather, I find it better to store it like this :
image : `http://res.cloudinary.com/cloud_name/image/upload/v1647610701/rsorl4rtziefw46fllvh.png`
The solution to this problem is to implement a funciton which extracts the publicId for every URL passed in as argument.
Here's the function :
const getPublicId = (imageURL) => imageURL.split("/").pop().split(".")[0];
Edited after #loic-vdb 's suggestion
Explanation :
It splits the string in an array using "/" as seperator.
imageURL="http://res.cloudinary.com/cloud_name/image/upload/v1647610701/rsorl4rtziefw46fllvh.png";
becomes,
imageURL = [ 'http:',
'',
'res.cloudinary.com',
'cloud_name',
'image',
'upload',
'v1647610701',
'rsorl4rtziefw46fllvh.png' ]
Next, pop the array (returns the last element of the array)
imageURL = 'rsorl4rtziefw46fllvh.png';
Now, split this string into array using "." as seperator, we get :
imageURL = [ 'rsorl4rtziefw46fllvh', 'png' ]
Finally select the 0th element that is our PublicId return that
imageURL = 'rsorl4rtziefw46fllvh';
Based on the answer by a Cloudinary support team member
... the public_id contains all folders and the last part of the public_id is the filename.
Here is what I tried and worked
const path = require("path");
const getPublicId = (imageURL) => {
const [, publicIdWithExtensionName] = imageURL.split("upload/");
const extensionName = path.extname(publicIdWithExtensionName)
const publicId = publicIdWithExtensionName.replace(extensionName, "")
return publicId
};
especially for cases where you store your assets in folders
Related
I have an image in File Cabinet that I want to add to my PDF. I have a script that creates a PDF and adds that image to it.
I tested the link https://system.na2.netsuite.com${imgURL} on my browser and the image loads. However I get a strange error when I try to add it to my PDF below:
var myImageFromFileCabinet = file.load({id:10202});
imgURL = myImageFromFileCabinet.url;
xmlStr = `<body><img src="https://system.na2.netsuite.com${imgURL}"></body>`;
let pdfFile = render.xmlToPdf({ xmlString: xmlStr });
context.response.writeFile({
file: pdfFile,
isInline: true
});
"type":"error.SuiteScriptError","name":"USER_ERROR","message":"Error Parsing XML: The reference to entity "c" must end with the ';' delimiter.
How can I add an image to a PDF?
TLDR: Escape the URL string for use in XML
The root cause of your error is that you are not escaping the URL for use in XML. The & characters in the URL must be escaped as XML/HTML entities. You can do this with the N/xml.escape() function:
const imgURL = xml.escape({xmlText: myImageFromFileCabinet.url});
That said, there were several other issues I had to resolve with this code along the way:
Outer tag must be pdf
The initial error I got when running this code was:
Error Parsing XML: Outer tag is body, should be pdf or pdfset
I fixed this by wrapping the <body> in a <pdf>.
img tag must be closed
Next I needed to close the <img> with </img> (or /> whichever you prefer).
Summary
My full working onRequest looks like:
const onRequest = (context) => {
const myImageFromFileCabinet = file.load({id:1820});
const imgURL = xml.escape({xmlText: myImageFromFileCabinet.url});
const xmlString = `<pdf><body><img src="https://system.na2.netsuite.com${imgURL}"/></body></pdf>`;
const pdfFile = render.xmlToPdf({ xmlString });
context.response.writeFile({
file: pdfFile,
isInline: true
});
};
Note that I've also made some minor changes like renaming variables and adding some const keywords, as well as of course changing the image's internal ID for my own account.
I'm new to GCS. I'm trying to list the files in a "folder" & download them, however my list is return the folder name as index 0.
How do I only list the files name and not the name of the directory? Or should I just shift() the array after I get the list back?
const prefix = 'testFiles/'; //contains jpgs
const delimiter = '/';
const options = {
prefix: prefix,
};
if (delimiter) {
options.delimiter = delimiter;
}
const [files] = await storage.bucket(bucketName).getFiles();
files.forEach((file, i) => {
options.destination = `./downloads/${i}.jpg`
console.log(options);
file.download(options)
});
}
Google Cloud Storage does not have real "folders". Instead, they are emulated using the prefix and delimiter parameters to the List Objects method.
The way you're doing it is correct. But when you list with a prefix, all objects that start with that prefix are returned, up to the delmiter, if specified. That means, like you found, that if have a "directory placeholder" object (an object that ends in /), it will be returned. Note that it also means any "subdirectories" of that prefix will also be returned. For example, if you have the following list of objects in a bucket:
testFiles/
testFiles/a
testFiles/b
testFiles/c/d
testFiles/v
If you call list objects with prefix=testFiles/ and delimiter=/, you'll have the following returned:
items =
testFiles/
testFiles/a
testFiles/b
testFiles/v
prefixes =
testFiles/c/
This is because testFiles/ is actually an object in your bucket in GCS.
I need to get an image URL from Contentful entry id.
I am getting such an JSON from Contentful query
{
"sys":{
"space":{
"sys":{
"type":"Link",
"linkType":"Space",
"id":"8v1e7eaw70p2"
}
},
"id":"1JfEwVlD9WmYikE8kS8iCA",
"type":"Entry",
"createdAt":"2018-02-28T18:50:08.758Z",
"updatedAt":"2018-02-28T18:50:08.758Z",
"revision":1,
"contentType":{
"sys":{
"type":"Link",
"linkType":"ContentType",
"id":"image"
}
},
"locale":"en-US"
},
"fields":{
"name":"heat",
"image":{
"sys":{
"type":"Link",
"linkType":"Asset",
"id":"6Inruq2U0M2kOYsSAu8Ywk"
}
}
}
}
I am using JS driver they provide:
client.getEntry()
so how to go thru that link: 6Inruq2U0M2kOYsSAu8Ywk ?
Unfortunately, the js SDK will not be able to resolve links when using the single entry endpoint i.e client.getEntry() because there won't be enough data.
When thing I always recommend to work around this is to use the collection endpoint with a query the desired id as a query param. This way you will always get the desired entry with all it's linked data.
Your code should look something like this
client.getEntries({'sys.id': '6Inruq2U0M2kOYsSAu8Ywk'})
.then(response => console.log(response.items[0].fields.image.fields.file.url))
I hope that helps.
Best,
Khaled
Use client.getEntries({'sys.id': '1JfEwVlD9WmYikE8kS8iCA'})
To get the entry fields and the asset fields.
You can also patch the assets to the fields by running this after fetching the data:
/* Patch all the assets to the fields */
const patchAssets = (fields, assets) => {
Object.keys(fields).forEach(function (key) {
let obj = fields[key];
if (obj.sys && obj.sys.linkType === 'Asset') {
const assetId = obj.sys.id;
const matchAsset = assets.find(asset => {
return asset.id === assetId;
});
obj.file = matchAsset;
}
});
return fields;
};
Another way to get image url is to use getAsset('<asset_id>'). So first, using the getEntry() method, you need to get the entry data, then extract the id from the field: fields.image.sys.id, and pass it to the getAsset method.
In my user interface (angularjs) I create new row. Each row have file upload button. I want to upload all files together with metadata and save each row in one call. The complex object which I post to Nodejs API is somewhat like below
var activity = {
"Id" : 1,
"Name" : "Test",
"Steps" : [
{
"StepId":1,
"FileUrl": {fileObject} // this property if bound with the file upload directive 'ng-file-upload' by Daniel Farid
"Description" : "Save this file"
},
{
"StepId":2,
"FileUrl": {fileObject} // this property if bound with the file upload directive 'ng-file-upload' by Daniel Farid
"Description" : "Save this file2"
}
]
}
This JSON will be posted to Node js API. On Nodejs side I am using multer to save the uploaded files to server. I get all the files in API using multer's .any() method, but I get the posted object without Steps[x].FileUrl property.
The file object that has the information about the field name in which this file was added. Below is the info I see in debugger.
Array[2]
length:2
[0]:Object
destination:"C:\DeleteThis\"
encoding:"7bit"
fieldname:"Steps[0][FileUrl]"
filename:"ed13d2a61cb38c43f1f46a221855a896"
mimetype:"image/png"
originalname:"deploy.png"
path:"C:\DeleteThis\ed13d2a61cb38c43f1f46a221855a896"
size:2347
[1]:Object
Now what I want to do it, since My complex object that is posted does not have Steps[0].FileUrl property, I want to iterate for each file (i.e. req.files) and use fieldname to create this property and assign the originalName as value to it.
How I am trying to do it
var deployment = req.body;
if(req.files){
var app = _config.getApplicationConfig(req.body.ApplicationId);
req.files.forEach(function(f){
//Move file to the deployment folder.
_utils.createDirIfNotExist(app.packageDir);
var newPath = _utils.DetermineFileName(f.originalname, app.packageDir);
_fs.renameSync(f.path, path.join(app.packageDir,newPath));
var newFileName = path.basename(newPath);
//set the file url to corresponding field
var evalExp = "deployment." + f.fieldname; //I get evalExpression as "deployment.Steps[0][FileUrl]"
eval(evalExp); //Here it fails saying FileUrl is not defined
evalExp = "deployment." + f.fieldname + "= \"" + newFileName.toString() + "\"";
eval(evalExp);
});
}
Does anyone know how can as assign the property to an object at run time?
I have found solution to this as below
I have wrote a function that converts the [] notation to . notation ie. myobj[myprop] to myobj.myprop
var convertToDotNotation = function (keyPath) {
var bracketSyntaxRegex = new RegExp(/\[([^0-9])\w+\]/g); //matches the javascript property defined in [] syntax but not an array
var matches = keyPath.match(bracketSyntaxRegex)
if(matches && matches.length > 0){
matches.forEach(function(p){
//replace '[' with '.' and ']' with emptyspace
var dotSyntax = p.replace("[",".").replace("]","");
keyPath = keyPath.replace(p,dotSyntax);
});
}
return keyPath;
}
This will give me the '.' notation which can dynamically create the property and set the value
var newFileName = "MyFile.pdf";
var evalExp = "deployment[0].[FileUrl]" ;
var temp = convertToDotNotation(evalExp);
eval(temp + "= \"" + newFileName + "\"");
Hope it helps someone.
I'm using linqjs in my website and I'm trying to get all values of a dictionary populated with toDictionary() library extension.
Here is my code:
var imagesDictionary = Enumerable.from(data)
.select(function (x) {
var images = Enumerable.from(x.ImagesSections)
.selectMany(function (y) {
return Enumerable.from(y.Images)
.select(function (z) {
return z.Thumb;
});
})
.toArray();
return { Title: x.Title, Images: images };
})
.toDictionary("$.Title", "$.Images");
var imagesToPreload = imagesDictionary.toEnumerable()
.selectMany("$.Value");
I would that imagesToPreload become an array of all images contained in the dictionary but I can't understand how to do that and this:
var imagesToPreload = imagesDictionary.toEnumerable()
.selectMany("$.Value");
seems the way than everyone used to obtain that.
Could someone help me?
Since it appears you're using the linqjs 3 beta version, the format of the entries have changed. The properties are in lowercase now.
var imagesDictionary = Enumerable.from(data)
.toDictionary("$.Title",
"Enumerable.from($.ImagesSections).selectMany('$.Images', '$$.Thumb').toArray()"
);
var imagesToPreload = imagesDictionary.toEnumerable()
.selectMany("$.value") // lowercase 'value'
.toArray();