mirror of https://github.com/laurent22/joplin.git
Plugins: Allow updating a resource via the data API
parent
d5dfecc19f
commit
37d51c3b58
|
@ -313,6 +313,10 @@ async function fetchAllNotes() {
|
||||||
lines.push('');
|
lines.push('');
|
||||||
lines.push('\tcurl -F \'data=@/path/to/file.jpg\' -F \'props={"title":"my resource title"}\' http://localhost:41184/resources');
|
lines.push('\tcurl -F \'data=@/path/to/file.jpg\' -F \'props={"title":"my resource title"}\' http://localhost:41184/resources');
|
||||||
lines.push('');
|
lines.push('');
|
||||||
|
lines.push('Or to **update** a resource:');
|
||||||
|
lines.push('');
|
||||||
|
lines.push('\tcurl -X PUT -F \'data=@/path/to/file.jpg\' -F \'props={"title":"my modified title"}\' http://localhost:41184/resources/8fe1417d7b184324bf6b0122b76c4696');
|
||||||
|
lines.push('');
|
||||||
lines.push('The "data" field is required, while the "props" one is not. If not specified, default values will be used.');
|
lines.push('The "data" field is required, while the "props" one is not. If not specified, default values will be used.');
|
||||||
lines.push('');
|
lines.push('');
|
||||||
lines.push('**From a plugin** the syntax to create a resource is also a bit special:');
|
lines.push('**From a plugin** the syntax to create a resource is also a bit special:');
|
||||||
|
@ -368,6 +372,11 @@ async function fetchAllNotes() {
|
||||||
lines.push(`Sets the properties of the ${singular} with ID :id`);
|
lines.push(`Sets the properties of the ${singular} with ID :id`);
|
||||||
lines.push('');
|
lines.push('');
|
||||||
|
|
||||||
|
if (model.type === BaseModel.TYPE_RESOURCE) {
|
||||||
|
lines.push('You may also update the file data by specifying a file (See `POST /resources` example).');
|
||||||
|
lines.push('');
|
||||||
|
}
|
||||||
|
|
||||||
lines.push(`## DELETE /${tableName}/:id`);
|
lines.push(`## DELETE /${tableName}/:id`);
|
||||||
lines.push('');
|
lines.push('');
|
||||||
lines.push(`Deletes the ${singular} with ID :id`);
|
lines.push(`Deletes the ${singular} with ID :id`);
|
||||||
|
|
|
@ -325,7 +325,7 @@ describe('services_rest_Api', function() {
|
||||||
expect(response.body.indexOf(resource.id) >= 0).toBe(true);
|
expect(response.body.indexOf(resource.id) >= 0).toBe(true);
|
||||||
}));
|
}));
|
||||||
|
|
||||||
it('should not compress images uploaded through resource api', (async () => {
|
it('should not compress images uploaded through resource API', (async () => {
|
||||||
const originalImagePath = `${supportDir}/photo-large.png`;
|
const originalImagePath = `${supportDir}/photo-large.png`;
|
||||||
await api.route(RequestMethod.POST, 'resources', null, JSON.stringify({
|
await api.route(RequestMethod.POST, 'resources', null, JSON.stringify({
|
||||||
title: 'testing resource',
|
title: 'testing resource',
|
||||||
|
@ -345,6 +345,39 @@ describe('services_rest_Api', function() {
|
||||||
expect(originalImageSize).toEqual(uploadedImageSize);
|
expect(originalImageSize).toEqual(uploadedImageSize);
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
it('should update a resource', (async () => {
|
||||||
|
await api.route(RequestMethod.POST, 'resources', null, JSON.stringify({
|
||||||
|
title: 'resource',
|
||||||
|
}), [
|
||||||
|
{
|
||||||
|
path: `${supportDir}/photo.jpg`,
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
|
const resourceV1 = (await Resource.all())[0];
|
||||||
|
|
||||||
|
await msleep(1);
|
||||||
|
|
||||||
|
await api.route(RequestMethod.PUT, `resources/${resourceV1.id}`, null, JSON.stringify({
|
||||||
|
title: 'resource mod',
|
||||||
|
}), [
|
||||||
|
{
|
||||||
|
path: `${supportDir}/photo-large.png`,
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
|
const resourceV2 = (await Resource.all())[0];
|
||||||
|
|
||||||
|
expect(resourceV2.title).toBe('resource mod');
|
||||||
|
expect(resourceV2.mime).toBe('image/png');
|
||||||
|
expect(resourceV2.file_extension).toBe('png');
|
||||||
|
expect(resourceV2.updated_time).toBeGreaterThan(resourceV1.updated_time);
|
||||||
|
expect(resourceV2.created_time).toBe(resourceV1.created_time);
|
||||||
|
expect(resourceV2.size).toBeGreaterThan(resourceV1.size);
|
||||||
|
|
||||||
|
expect(resourceV2.size).toBe((await shim.fsDriver().stat(Resource.fullPath(resourceV2))).size);
|
||||||
|
}));
|
||||||
|
|
||||||
it('should delete resources', (async () => {
|
it('should delete resources', (async () => {
|
||||||
const f = await Folder.save({ title: 'mon carnet' });
|
const f = await Folder.save({ title: 'mon carnet' });
|
||||||
|
|
||||||
|
|
|
@ -47,13 +47,17 @@ export default async function(request: Request, id: string = null, link: string
|
||||||
if (link) throw new ErrorNotFound();
|
if (link) throw new ErrorNotFound();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (request.method === RequestMethod.POST) {
|
if (request.method === RequestMethod.POST || request.method === RequestMethod.PUT) {
|
||||||
|
const isUpdate = request.method === RequestMethod.PUT;
|
||||||
|
|
||||||
if (!request.files.length) throw new ErrorBadRequest('Resource cannot be created without a file');
|
if (!request.files.length) throw new ErrorBadRequest('Resource cannot be created without a file');
|
||||||
|
if (isUpdate && !id) throw new ErrorBadRequest('Missing resource ID');
|
||||||
const filePath = request.files[0].path;
|
const filePath = request.files[0].path;
|
||||||
const defaultProps = request.bodyJson(readonlyProperties('POST'));
|
const defaultProps = request.bodyJson(readonlyProperties(request.method));
|
||||||
return shim.createResourceFromPath(filePath, defaultProps, {
|
return shim.createResourceFromPath(filePath, defaultProps, {
|
||||||
userSideValidation: true,
|
userSideValidation: true,
|
||||||
resizeLargeImages: 'never',
|
resizeLargeImages: 'never',
|
||||||
|
destinationResourceId: isUpdate ? id : '',
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -221,10 +221,15 @@ function shimInit(options = null) {
|
||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// This is a bit of an ugly method that's used to both create a new resource
|
||||||
|
// from a file, and update one. To update a resource, pass the
|
||||||
|
// destinationResourceId option. This method is indirectly tested in
|
||||||
|
// Api.test.ts.
|
||||||
shim.createResourceFromPath = async function(filePath, defaultProps = null, options = null) {
|
shim.createResourceFromPath = async function(filePath, defaultProps = null, options = null) {
|
||||||
options = Object.assign({
|
options = Object.assign({
|
||||||
resizeLargeImages: 'always', // 'always', 'ask' or 'never'
|
resizeLargeImages: 'always', // 'always', 'ask' or 'never'
|
||||||
userSideValidation: false,
|
userSideValidation: false,
|
||||||
|
destinationResourceId: '',
|
||||||
}, options);
|
}, options);
|
||||||
|
|
||||||
const readChunk = require('read-chunk');
|
const readChunk = require('read-chunk');
|
||||||
|
@ -236,9 +241,10 @@ function shimInit(options = null) {
|
||||||
|
|
||||||
defaultProps = defaultProps ? defaultProps : {};
|
defaultProps = defaultProps ? defaultProps : {};
|
||||||
|
|
||||||
const resourceId = defaultProps.id ? defaultProps.id : uuid.create();
|
let resourceId = defaultProps.id ? defaultProps.id : uuid.create();
|
||||||
|
if (options.destinationResourceId) resourceId = options.destinationResourceId;
|
||||||
|
|
||||||
const resource = Resource.new();
|
let resource = options.destinationResourceId ? {} : Resource.new();
|
||||||
resource.id = resourceId;
|
resource.id = resourceId;
|
||||||
resource.mime = mimeUtils.fromFilename(filePath);
|
resource.mime = mimeUtils.fromFilename(filePath);
|
||||||
resource.title = basename(filePath);
|
resource.title = basename(filePath);
|
||||||
|
@ -281,7 +287,18 @@ function shimInit(options = null) {
|
||||||
|
|
||||||
const saveOptions = { isNew: true };
|
const saveOptions = { isNew: true };
|
||||||
if (options.userSideValidation) saveOptions.userSideValidation = true;
|
if (options.userSideValidation) saveOptions.userSideValidation = true;
|
||||||
|
|
||||||
|
if (options.destinationResourceId) {
|
||||||
|
saveOptions.isNew = false;
|
||||||
|
const tempPath = `${targetPath}.tmp`;
|
||||||
|
await shim.fsDriver().move(targetPath, tempPath);
|
||||||
|
resource = await Resource.save(resource, saveOptions);
|
||||||
|
await Resource.updateResourceBlobContent(resource.id, tempPath);
|
||||||
|
await shim.fsDriver().remove(tempPath);
|
||||||
|
return resource;
|
||||||
|
} else {
|
||||||
return Resource.save(resource, saveOptions);
|
return Resource.save(resource, saveOptions);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
shim.attachFileToNoteBody = async function(noteBody, filePath, position = null, options = null) {
|
shim.attachFileToNoteBody = async function(noteBody, filePath, position = null, options = null) {
|
||||||
|
|
Loading…
Reference in New Issue