This Article has been rewritten to fit the new nuxeo-js-client.
Last week we were asked if there is an easy way to rename a bunch of documents, located in some specific folders, in our intranet. After manually updating a lot of main files, the documents titles were no longer synced with the main file name. The question was how to update the document title to match the main file name.
As “eating your own dog food” is quite a good thing, let’s use our own REST API and clients to do this.
I have chosen to use the node.js Nuxeo client. You can find documentation in the README on the nuxeo-js-client GitHub repository.
The full project for this blog post is available here.
Let’s see, step by step, how we can do this.
Project Initialization
First, you need to initialize your node.js project through npm init
:
$ npm init
Answer all questions and you’re ready to go!
Then, add the nuxeo
node module to your project:
$ npm install nuxeo
This will install the latest version of the node.js nuxeo
module (at the time of writing, it’s the 0.2.0 version).
Create the Client
Create an empty file in your project, named sample.js
for instance.
First, we need to require the nuxeo
module so that we can use it:
let Nuxeo = require('nuxeo');
From here, we can create a new Client
object targeting a Nuxeo Platform server:
let nuxeo = new Nuxeo({
baseURL: 'http://localhost:8080/nuxeo/',
auth: {
method: 'basic',
username: 'Administrator',
password: 'Administrator'
}
});
Those are the default values, replace them with the values for your own Nuxeo Platform server.
If you don’t want to change them you can also write:
var client = new nuxeo.Client();
The Query
Let’s say we want to rename all the File
documents which have a main file, inside the /default-domain/workspaces/
document (recursively). We can create the following query (ignoring proxies, versions and deleted documents):
let query = "SELECT * FROM Document"
+ " WHERE ecm:primaryType = 'File'"
+ " AND ecm:path STARTSWITH '/default-domain/workspaces/'"
+ " AND content/data IS NOT NULL"
+ " AND dc:title <> content/name"
+ " AND ecm:isProxy = 0 AND ecm:isCheckedInVersion = 0"
+ " AND ecm:currentLifeCycleState != 'deleted'";
Build the Request
Now that we have our query, here is how we can build a Request
object that will be executed later:
nuxeo.repository().schemas(['dublincore', 'file']).query({query: query}).then((docs) => {
docs.entries.forEach((doc) => {
console.log(`${doc.uid} - ${doc.title}`)
})
});
Some explanations on what we use:
nuxeo.repository()
: actually builds theRepository
object that is the entry point of any Document actions,schemas(['dublincore', 'file'])
: we will retrieve only the ‘dublincore’ and ‘file’ schemas in the returned JSON object,query(query)
: use thequery
function to execute it,.then((docs) => {...})
:query
returns a Promise, this callback is called when the query responds,
Here is the JSON response of executing it on my own server, where one file needs to be renamed:
{ 'entity-type': 'documents',
isPaginable: true,
resultsCount: 1,
pageSize: 50,
maxPageSize: 100,
currentPageSize: 1,
currentPageIndex: 0,
numberOfPages: 1,
isPreviousPageAvailable: false,
isNextPageAvailable: false,
isLastPageAvailable: false,
isSortable: true,
hasError: false,
errorMessage: null,
totalSize: 1,
pageIndex: 0,
pageCount: 1,
entries:
[ { 'entity-type': 'document',
repository: 'default',
uid: '91e4bf57-bc80-4178-9604-b02ccd6cca8c',
path: '/default-domain/workspaces/test/afile',
type: 'File',
state: 'project',
versionLabel: '0.0',
isCheckedOut: true,
title: 'afile',
lastModified: '2014-06-10T13:48:51.00Z',
properties: [Object],
facets: [Object],
changeToken: '1402408131000',
contextParameters: [Object] } ] }
As you can see, the results are paginated, by default with 50 documents. We will process the first page of entries (forced to a pageSize of 5), and then each subsequent page:
let repository = nuxeo.repository().schemas(['dublincore', 'file']);
let queryOpts = {
query: query,
pageSize: 5
};
let processData = (data) => {
// process the first page
data.entries.forEach(processEntry)
// iterate over all the next pages (using the currentPageIndex parameter
// to retrieve the right page)
if (data.isNextPageAvailable) {
console.log(`New page is called: ${data.pageIndex}`)
queryOpts.currentPageIndex = data.pageIndex + 1;
repository.query(queryOpts).then(processData);
// Catch block is global; no need to add another one here.
}
}
repository.query(queryOpts).then(processData).catch((error) => {
console.log(error);
});
Update the Documents
Let’s see the implementation of the processEntry
function:
function processEntry(doc) {
let content = doc.properties['file:content'];
if (content && content.name) {
doc.set({'dc:title' : content.name })
doc.save().then((data) => {
console.log(`Successfully renamed '${data.title}'`);
});
}
}
Here, as the default behavior, we manipulate the Document
object abstraction over a simple JSON document to help us update the properties and save the doc. Otherwise, we would have directly updated the properties
field of the JSON document, and saved it through a PUT
with a Request
object.
Updating the properties of a document is as easy as calling the set
method on the Document
object:
doc.set(propertiesObject)
Only the properties existing on the propertiesObject
will be updated on the document, the properties
object of the document is not completely replaced. Here we update only the title, using the dc:title
property.
After updating the properties, we can now save the document on the server through the save
method:
doc.save().then((data) => {
console.log(`Successfully renamed '${data.title}'`);
});
Full Project
The GitHub project is still using the old version of Nuxeo JS CLient.
I’ve set up this full project, with more logs, comments and options, on the nuxeo-node-sample GitHub repository.
To run it, simply do:
$ git clone https://github.com/troger/nuxeo-node-sample.git
$ cd nuxeo-node-sample
$ node sample.js [--dry-run]
You can setup the client
to use your own Nuxeo Platform server in the sample.js
file, as well as the query you want to use to find documents.
The --dry-run
option allow you to just see which files will be updated, without updating them.