Merge pull request '2576 - Photo upload component' (#470) from 2576-photo_uploader into dev
gitea/salix/pipeline/head This commit looks good
Details
Reviewed-on: #470 Reviewed-by: Carlos Jimenez Ruiz <carlosjr@verdnatura.es>
|
@ -1,11 +1,7 @@
|
|||
coverage
|
||||
node_modules
|
||||
dist
|
||||
e2e/dms/*/
|
||||
!e2e/dms/c4c
|
||||
!e2e/dms/c81
|
||||
!e2e/dms/ecc
|
||||
!e2e/dms/a87
|
||||
storage
|
||||
npm-debug.log
|
||||
.eslintcache
|
||||
datasources.*.json
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
const UserError = require('vn-loopback/util/user-error');
|
||||
const fs = require('fs-extra');
|
||||
const path = require('path');
|
||||
|
||||
module.exports = Self => {
|
||||
Self.remoteMethodCtx('updateFile', {
|
||||
|
@ -84,66 +85,46 @@ module.exports = Self => {
|
|||
};
|
||||
|
||||
async function uploadNewFile(ctx, dms, myOptions) {
|
||||
const storageConnector = Self.app.dataSources.storage.connector;
|
||||
const models = Self.app.models;
|
||||
const TempContainer = models.TempContainer;
|
||||
const DmsContainer = models.DmsContainer;
|
||||
const fileOptions = {};
|
||||
|
||||
const tempContainer = await getContainer('temp');
|
||||
const makeUpload = await models.Container.upload(tempContainer.name, ctx.req, ctx.result, fileOptions);
|
||||
const tempContainer = await TempContainer.container('dms');
|
||||
const makeUpload = await TempContainer.upload(tempContainer.name, ctx.req, ctx.result, fileOptions);
|
||||
const keys = Object.values(makeUpload.files);
|
||||
const files = keys.map(file => file[0]);
|
||||
const file = files[0];
|
||||
const uploadedFile = files[0];
|
||||
|
||||
if (file) {
|
||||
const oldExtension = storageConnector.getFileExtension(dms.file);
|
||||
const newExtension = storageConnector.getFileExtension(file.name);
|
||||
if (uploadedFile) {
|
||||
const oldExtension = DmsContainer.getFileExtension(dms.file);
|
||||
const newExtension = DmsContainer.getFileExtension(uploadedFile.name);
|
||||
const fileName = `${dms.id}.${newExtension}`;
|
||||
|
||||
try {
|
||||
if (oldExtension != newExtension) {
|
||||
const pathHash = storageConnector.getPathHash(dms.id);
|
||||
const pathHash = DmsContainer.getHash(dms.id);
|
||||
|
||||
await models.Container.removeFile(pathHash, dms.file);
|
||||
await DmsContainer.removeFile(pathHash, dms.file);
|
||||
}
|
||||
} catch (err) {}
|
||||
|
||||
const updatedDms = await dms.updateAttributes({
|
||||
contentType: file.type,
|
||||
contentType: uploadedFile.type,
|
||||
file: fileName
|
||||
}, myOptions);
|
||||
|
||||
const pathHash = storageConnector.getPathHash(updatedDms.id);
|
||||
const container = await getContainer(pathHash);
|
||||
const file = await TempContainer.getFile(tempContainer.name, uploadedFile.name);
|
||||
const srcFile = path.join(file.client.root, file.container, file.name);
|
||||
|
||||
const originPath = `${tempContainer.client.root}/${tempContainer.name}/${file.name}`;
|
||||
const destinationPath = `${container.client.root}/${pathHash}/${updatedDms.file}`;
|
||||
const pathHash = DmsContainer.getHash(updatedDms.id);
|
||||
const dmsContainer = await DmsContainer.container(pathHash);
|
||||
const dstFile = path.join(dmsContainer.client.root, pathHash, updatedDms.file);
|
||||
|
||||
fs.rename(originPath, destinationPath);
|
||||
await fs.move(srcFile, dstFile, {
|
||||
overwrite: true
|
||||
});
|
||||
|
||||
return updatedDms;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a container instance
|
||||
* If doesn't exists creates a new one
|
||||
*
|
||||
* @param {String} name Container name
|
||||
* @return {Object} Container instance
|
||||
*/
|
||||
async function getContainer(name) {
|
||||
const models = Self.app.models;
|
||||
let container;
|
||||
try {
|
||||
container = await models.Container.getContainer(name);
|
||||
} catch (err) {
|
||||
if (err.code === 'ENOENT') {
|
||||
container = await models.Container.createContainer({
|
||||
name: name
|
||||
});
|
||||
} else throw err;
|
||||
}
|
||||
|
||||
return container;
|
||||
}
|
||||
};
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
const UserError = require('vn-loopback/util/user-error');
|
||||
const fs = require('fs-extra');
|
||||
const path = require('path');
|
||||
|
||||
module.exports = Self => {
|
||||
Self.remoteMethodCtx('uploadFile', {
|
||||
|
@ -46,8 +47,9 @@ module.exports = Self => {
|
|||
});
|
||||
|
||||
Self.uploadFile = async(ctx, options) => {
|
||||
const storageConnector = Self.app.dataSources.storage.connector;
|
||||
const models = Self.app.models;
|
||||
const TempContainer = models.TempContainer;
|
||||
const DmsContainer = models.DmsContainer;
|
||||
const fileOptions = {};
|
||||
const args = ctx.args;
|
||||
|
||||
|
@ -62,28 +64,33 @@ module.exports = Self => {
|
|||
myOptions.transaction = tx;
|
||||
}
|
||||
|
||||
let srcFile;
|
||||
try {
|
||||
const hasWriteRole = await models.DmsType.hasWriteRole(ctx, args.dmsTypeId, myOptions);
|
||||
if (!hasWriteRole)
|
||||
throw new UserError(`You don't have enough privileges`);
|
||||
|
||||
// Upload file to temporary path
|
||||
const tempContainer = await getContainer('temp');
|
||||
const uploaded = await models.Container.upload(tempContainer.name, ctx.req, ctx.result, fileOptions);
|
||||
const tempContainer = await TempContainer.container('dms');
|
||||
const uploaded = await TempContainer.upload(tempContainer.name, ctx.req, ctx.result, fileOptions);
|
||||
const files = Object.values(uploaded.files).map(file => {
|
||||
return file[0];
|
||||
});
|
||||
|
||||
const addedDms = [];
|
||||
for (const file of files) {
|
||||
const newDms = await createDms(ctx, file, myOptions);
|
||||
const pathHash = storageConnector.getPathHash(newDms.id);
|
||||
const container = await getContainer(pathHash);
|
||||
for (const uploadedFile of files) {
|
||||
const newDms = await createDms(ctx, uploadedFile, myOptions);
|
||||
const pathHash = DmsContainer.getHash(newDms.id);
|
||||
|
||||
const originPath = `${tempContainer.client.root}/${tempContainer.name}/${file.name}`;
|
||||
const destinationPath = `${container.client.root}/${pathHash}/${newDms.file}`;
|
||||
const file = await TempContainer.getFile(tempContainer.name, uploadedFile.name);
|
||||
srcFile = path.join(file.client.root, file.container, file.name);
|
||||
|
||||
await fs.rename(originPath, destinationPath);
|
||||
const dmsContainer = await DmsContainer.container(pathHash);
|
||||
const dstFile = path.join(dmsContainer.client.root, pathHash, newDms.file);
|
||||
|
||||
await fs.move(srcFile, dstFile, {
|
||||
overwrite: true
|
||||
});
|
||||
|
||||
addedDms.push(newDms);
|
||||
}
|
||||
|
@ -92,13 +99,16 @@ module.exports = Self => {
|
|||
return addedDms;
|
||||
} catch (e) {
|
||||
if (tx) await tx.rollback();
|
||||
|
||||
if (fs.existsSync(srcFile))
|
||||
await fs.unlink(srcFile);
|
||||
|
||||
throw e;
|
||||
}
|
||||
};
|
||||
|
||||
async function createDms(ctx, file, myOptions) {
|
||||
const models = Self.app.models;
|
||||
const storageConnector = Self.app.dataSources.storage.connector;
|
||||
const myUserId = ctx.req.accessToken.userId;
|
||||
const myWorker = await models.Worker.findOne({where: {userFk: myUserId}}, myOptions);
|
||||
const args = ctx.args;
|
||||
|
@ -115,33 +125,9 @@ module.exports = Self => {
|
|||
}, myOptions);
|
||||
|
||||
let fileName = file.name;
|
||||
const extension = storageConnector.getFileExtension(fileName);
|
||||
const extension = models.DmsContainer.getFileExtension(fileName);
|
||||
fileName = `${newDms.id}.${extension}`;
|
||||
|
||||
return newDms.updateAttribute('file', fileName, myOptions);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns a container instance
|
||||
* If doesn't exists creates a new one
|
||||
*
|
||||
* @param {String} name Container name
|
||||
* @return {Object} Container instance
|
||||
*/
|
||||
async function getContainer(name) {
|
||||
const models = Self.app.models;
|
||||
let container;
|
||||
try {
|
||||
container = await models.Container.getContainer(name);
|
||||
} catch (err) {
|
||||
if (err.code === 'ENOENT') {
|
||||
container = await models.Container.createContainer({
|
||||
name: name
|
||||
});
|
||||
} else throw err;
|
||||
}
|
||||
|
||||
return container;
|
||||
}
|
||||
};
|
||||
|
|
|
@ -1,8 +1,9 @@
|
|||
const UserError = require('vn-loopback/util/user-error');
|
||||
const fs = require('fs-extra');
|
||||
const path = require('path');
|
||||
|
||||
module.exports = Self => {
|
||||
Self.remoteMethod('download', {
|
||||
Self.remoteMethodCtx('download', {
|
||||
description: 'Get the user image',
|
||||
accessType: 'READ',
|
||||
accepts: [
|
||||
|
@ -49,15 +50,9 @@ module.exports = Self => {
|
|||
}
|
||||
});
|
||||
|
||||
Self.download = async function(collection, size, id) {
|
||||
Self.download = async function(ctx, collection, size, id) {
|
||||
const models = Self.app.models;
|
||||
const filter = {
|
||||
where: {
|
||||
name: collection},
|
||||
include: {
|
||||
relation: 'readRole'
|
||||
}
|
||||
};
|
||||
const filter = {where: {name: collection}};
|
||||
const imageCollection = await models.ImageCollection.findOne(filter);
|
||||
const entity = await models[imageCollection.model].findById(id, {
|
||||
fields: ['id', imageCollection.property]
|
||||
|
@ -69,28 +64,23 @@ module.exports = Self => {
|
|||
|
||||
if (!image) return false;
|
||||
|
||||
const imageRole = imageCollection.readRole().name;
|
||||
const hasRole = await models.Account.hasRole(id, imageRole);
|
||||
if (!hasRole)
|
||||
const hasReadRole = models.ImageCollection.hasReadRole(ctx, collection);
|
||||
if (!hasReadRole)
|
||||
throw new UserError(`You don't have enough privileges`);
|
||||
|
||||
let file;
|
||||
let env = process.env.NODE_ENV;
|
||||
if (env && env != 'development') {
|
||||
file = {
|
||||
path: `/var/lib/salix/image/${collection}/${size}/${image.name}.png`,
|
||||
const container = await models.ImageContainer.getContainer(collection);
|
||||
const rootPath = container.client.root;
|
||||
const fileSrc = path.join(rootPath, collection, size);
|
||||
const file = {
|
||||
path: `${fileSrc}/${image.name}.png`,
|
||||
contentType: 'image/png',
|
||||
name: `${image.name}.png`
|
||||
};
|
||||
} else {
|
||||
file = {
|
||||
path: `${process.cwd()}/storage/image/${collection}/${size}/${image.name}.png`,
|
||||
contentType: 'image/png',
|
||||
name: `${image.name}.png`
|
||||
};
|
||||
}
|
||||
|
||||
if (!fs.existsSync(file.path)) return [];
|
||||
|
||||
await fs.access(file.path);
|
||||
let stream = fs.createReadStream(file.path);
|
||||
const stream = fs.createReadStream(file.path);
|
||||
return [stream, file.contentType, `filename="${file.name}"`];
|
||||
};
|
||||
};
|
||||
|
|
|
@ -3,10 +3,12 @@ const app = require('vn-loopback/server/server');
|
|||
describe('image download()', () => {
|
||||
const collection = 'user';
|
||||
const size = '160x160';
|
||||
const employeeId = 1;
|
||||
const ctx = {req: {accessToken: {userId: employeeId}}};
|
||||
|
||||
it('should return the image content-type of the user', async() => {
|
||||
const userId = 9;
|
||||
const image = await app.models.Image.download(collection, size, userId);
|
||||
const image = await app.models.Image.download(ctx, collection, size, userId);
|
||||
const contentType = image[1];
|
||||
|
||||
expect(contentType).toEqual('image/png');
|
||||
|
@ -14,7 +16,7 @@ describe('image download()', () => {
|
|||
|
||||
it(`should return false if the user doesn't have image`, async() => {
|
||||
const userId = 110;
|
||||
const image = await app.models.Image.download(collection, size, userId);
|
||||
const image = await app.models.Image.download(ctx, collection, size, userId);
|
||||
|
||||
expect(image).toBeFalse();
|
||||
});
|
||||
|
|
|
@ -0,0 +1,129 @@
|
|||
const app = require('vn-loopback/server/server');
|
||||
|
||||
describe('image upload()', () => {
|
||||
describe('as buyer', () => {
|
||||
const buyerId = 35;
|
||||
const workerId = 106;
|
||||
const itemId = 4;
|
||||
|
||||
it('should try to upload a file for the collection "catalog" and throw a privileges error', async() => {
|
||||
const ctx = {req: {accessToken: {userId: buyerId}},
|
||||
args: {
|
||||
id: workerId,
|
||||
collection: 'user'
|
||||
}
|
||||
};
|
||||
|
||||
let error;
|
||||
try {
|
||||
await app.models.Image.upload(ctx);
|
||||
} catch (err) {
|
||||
error = err;
|
||||
}
|
||||
|
||||
expect(error.message).toEqual(`You don't have enough privileges`);
|
||||
});
|
||||
|
||||
it('should call to the TempContainer upload method for the collection "catalog"', async() => {
|
||||
const containerModel = app.models.TempContainer;
|
||||
spyOn(containerModel, 'upload');
|
||||
|
||||
const ctx = {req: {accessToken: {userId: buyerId}},
|
||||
args: {
|
||||
id: itemId,
|
||||
collection: 'catalog'
|
||||
}
|
||||
};
|
||||
|
||||
try {
|
||||
await app.models.Image.upload(ctx);
|
||||
} catch (err) { }
|
||||
|
||||
expect(containerModel.upload).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('as marketing', () => {
|
||||
const marketingId = 51;
|
||||
const workerId = 106;
|
||||
const itemId = 4;
|
||||
|
||||
it('should be able to call to the TempContainer upload method for the collection "user"', async() => {
|
||||
const containerModel = app.models.TempContainer;
|
||||
spyOn(containerModel, 'upload');
|
||||
|
||||
const ctx = {req: {accessToken: {userId: marketingId}},
|
||||
args: {
|
||||
id: workerId,
|
||||
collection: 'user'
|
||||
}
|
||||
};
|
||||
|
||||
try {
|
||||
await app.models.Image.upload(ctx);
|
||||
} catch (err) { }
|
||||
|
||||
expect(containerModel.upload).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should be able to call to the TempContainer upload method for the collection "catalog"', async() => {
|
||||
const containerModel = app.models.TempContainer;
|
||||
spyOn(containerModel, 'upload');
|
||||
|
||||
const ctx = {req: {accessToken: {userId: marketingId}},
|
||||
args: {
|
||||
id: itemId,
|
||||
collection: 'catalog'
|
||||
}
|
||||
};
|
||||
|
||||
try {
|
||||
await app.models.Image.upload(ctx);
|
||||
} catch (err) { }
|
||||
|
||||
expect(containerModel.upload).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('as hhrr', () => {
|
||||
const hhrrId = 37;
|
||||
const workerId = 106;
|
||||
const itemId = 4;
|
||||
|
||||
it('should upload a file for the collection "user" and call to the TempContainer upload method', async() => {
|
||||
const containerModel = app.models.TempContainer;
|
||||
spyOn(containerModel, 'upload');
|
||||
|
||||
const ctx = {req: {accessToken: {userId: hhrrId}},
|
||||
args: {
|
||||
id: itemId,
|
||||
collection: 'user'
|
||||
}
|
||||
};
|
||||
|
||||
try {
|
||||
await app.models.Image.upload(ctx);
|
||||
} catch (err) { }
|
||||
|
||||
expect(containerModel.upload).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should try to upload a file for the collection "catalog" and throw a privilege error', async() => {
|
||||
const ctx = {req: {accessToken: {userId: hhrrId}},
|
||||
args: {
|
||||
id: workerId,
|
||||
collection: 'catalog'
|
||||
}
|
||||
};
|
||||
|
||||
let error;
|
||||
try {
|
||||
await app.models.Image.upload(ctx);
|
||||
} catch (err) {
|
||||
error = err;
|
||||
}
|
||||
|
||||
expect(error.message).toEqual(`You don't have enough privileges`);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,70 @@
|
|||
const UserError = require('vn-loopback/util/user-error');
|
||||
const fs = require('fs-extra');
|
||||
const path = require('path');
|
||||
|
||||
module.exports = Self => {
|
||||
Self.remoteMethodCtx('upload', {
|
||||
description: 'Uploads a file and inserts into dms model',
|
||||
accessType: 'WRITE',
|
||||
accepts: [
|
||||
{
|
||||
arg: 'id',
|
||||
type: 'Number',
|
||||
description: 'The entity id',
|
||||
required: true
|
||||
},
|
||||
{
|
||||
arg: 'collection',
|
||||
type: 'string',
|
||||
description: 'The collection name',
|
||||
required: true
|
||||
},
|
||||
{
|
||||
arg: 'fileName',
|
||||
type: 'string',
|
||||
description: 'The file name',
|
||||
required: true
|
||||
}],
|
||||
returns: {
|
||||
type: 'Object',
|
||||
root: true
|
||||
},
|
||||
http: {
|
||||
path: `/upload`,
|
||||
verb: 'POST'
|
||||
}
|
||||
});
|
||||
|
||||
Self.upload = async ctx => {
|
||||
const models = Self.app.models;
|
||||
const TempContainer = models.TempContainer;
|
||||
const fileOptions = {};
|
||||
const args = ctx.args;
|
||||
|
||||
let srcFile;
|
||||
try {
|
||||
const hasWriteRole = await models.ImageCollection.hasWriteRole(ctx, args.collection);
|
||||
if (!hasWriteRole)
|
||||
throw new UserError(`You don't have enough privileges`);
|
||||
|
||||
if (process.env.NODE_ENV == 'test')
|
||||
throw new UserError(`You can't upload images on the test instance`);
|
||||
|
||||
// Upload file to temporary path
|
||||
const tempContainer = await TempContainer.container(args.collection);
|
||||
const uploaded = await TempContainer.upload(tempContainer.name, ctx.req, ctx.result, fileOptions);
|
||||
const [uploadedFile] = Object.values(uploaded.files).map(file => {
|
||||
return file[0];
|
||||
});
|
||||
const file = await TempContainer.getFile(tempContainer.name, uploadedFile.name);
|
||||
srcFile = path.join(file.client.root, file.container, file.name);
|
||||
|
||||
await models.Image.registerImage(args.collection, srcFile, args.fileName, args.id);
|
||||
} catch (e) {
|
||||
if (fs.existsSync(srcFile))
|
||||
await fs.unlink(srcFile);
|
||||
|
||||
throw e;
|
||||
}
|
||||
};
|
||||
};
|
|
@ -17,9 +17,6 @@
|
|||
"Company": {
|
||||
"dataSource": "vn"
|
||||
},
|
||||
"Container": {
|
||||
"dataSource": "storage"
|
||||
},
|
||||
"Continent": {
|
||||
"dataSource": "vn"
|
||||
},
|
||||
|
@ -35,6 +32,9 @@
|
|||
"Delivery": {
|
||||
"dataSource": "vn"
|
||||
},
|
||||
"DmsContainer": {
|
||||
"dataSource": "dmsStorage"
|
||||
},
|
||||
"Image": {
|
||||
"dataSource": "vn"
|
||||
},
|
||||
|
@ -44,12 +44,18 @@
|
|||
"ImageCollectionSize": {
|
||||
"dataSource": "vn"
|
||||
},
|
||||
"ImageContainer": {
|
||||
"dataSource": "imageStorage"
|
||||
},
|
||||
"Language": {
|
||||
"dataSource": "vn"
|
||||
},
|
||||
"Province": {
|
||||
"dataSource": "vn"
|
||||
},
|
||||
"TempContainer": {
|
||||
"dataSource": "tempStorage"
|
||||
},
|
||||
"UserConfig": {
|
||||
"dataSource": "vn"
|
||||
},
|
||||
|
|
|
@ -1,13 +0,0 @@
|
|||
{
|
||||
"name": "Container",
|
||||
"base": "VnModel",
|
||||
"idInjection": true,
|
||||
"options": {
|
||||
"validateUpsert": true
|
||||
},
|
||||
"properties": {},
|
||||
"validations": [],
|
||||
"relations": {},
|
||||
"acls": [],
|
||||
"methods": []
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
{
|
||||
"name": "DmsContainer",
|
||||
"base": "Container",
|
||||
"acls": [{
|
||||
"accessType": "READ",
|
||||
"principalType": "ROLE",
|
||||
"principalId": "$everyone",
|
||||
"permission": "ALLOW"
|
||||
}]
|
||||
}
|
|
@ -14,12 +14,12 @@ module.exports = Self => {
|
|||
};
|
||||
|
||||
Self.getFile = async function(id) {
|
||||
const storageConnector = Self.app.dataSources.storage.connector;
|
||||
const models = Self.app.models;
|
||||
const DmsContainer = models.DmsContainer;
|
||||
const dms = await Self.findById(id);
|
||||
const pathHash = storageConnector.getPathHash(dms.id);
|
||||
const pathHash = DmsContainer.getHash(dms.id);
|
||||
try {
|
||||
await models.Container.getFile(pathHash, dms.file);
|
||||
await DmsContainer.getFile(pathHash, dms.file);
|
||||
} catch (e) {
|
||||
if (e.code != 'ENOENT')
|
||||
throw e;
|
||||
|
@ -30,7 +30,7 @@ module.exports = Self => {
|
|||
throw error;
|
||||
}
|
||||
|
||||
const stream = models.Container.downloadStream(pathHash, dms.file);
|
||||
const stream = DmsContainer.downloadStream(pathHash, dms.file);
|
||||
|
||||
return [stream, dms.contentType, `filename="${dms.file}"`];
|
||||
};
|
||||
|
|
|
@ -0,0 +1,64 @@
|
|||
module.exports = Self => {
|
||||
/**
|
||||
* Checks if current user has
|
||||
* read privileges over a collection
|
||||
*
|
||||
* @param {object} ctx - Request context
|
||||
* @param {interger} name - Collection name
|
||||
* @param {object} options - Query options
|
||||
* @return {boolean} True for user with read privileges
|
||||
*/
|
||||
Self.hasReadRole = async(ctx, name, options) => {
|
||||
const collection = await Self.findOne({where: {name}}, {
|
||||
include: {
|
||||
relation: 'readRole'
|
||||
}
|
||||
}, options);
|
||||
|
||||
return await hasRole(ctx, collection, options);
|
||||
};
|
||||
|
||||
/**
|
||||
* Checks if current user has
|
||||
* write privileges over a collection
|
||||
*
|
||||
* @param {object} ctx - Request context
|
||||
* @param {string} name - Collection name
|
||||
* @param {object} options - Query options
|
||||
* @return {boolean} True for user with write privileges
|
||||
*/
|
||||
Self.hasWriteRole = async(ctx, name, options) => {
|
||||
const collection = await Self.findOne({
|
||||
include: {
|
||||
relation: 'writeRole'
|
||||
},
|
||||
where: {name}
|
||||
}, options);
|
||||
|
||||
return await hasRole(ctx, collection, options);
|
||||
};
|
||||
|
||||
/**
|
||||
* Checks if current user has
|
||||
* read or write privileges
|
||||
* @param {Object} ctx - Context
|
||||
* @param {Object} collection - Collection [read/write]
|
||||
* @param {Object} options - Query options
|
||||
*/
|
||||
async function hasRole(ctx, collection, options) {
|
||||
const models = Self.app.models;
|
||||
const myUserId = ctx.req.accessToken.userId;
|
||||
|
||||
const readRole = collection.readRole() && collection.readRole().name;
|
||||
const writeRole = collection.writeRole() && collection.writeRole().name;
|
||||
const requiredRole = readRole || writeRole;
|
||||
|
||||
const hasRequiredRole = await models.Account.hasRole(myUserId, requiredRole, options);
|
||||
const isRoot = await models.Account.hasRole(myUserId, 'root', options);
|
||||
|
||||
if (isRoot || hasRequiredRole)
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
};
|
|
@ -48,6 +48,11 @@
|
|||
"type": "belongsTo",
|
||||
"model": "Role",
|
||||
"foreignKey": "readRoleFk"
|
||||
},
|
||||
"writeRole": {
|
||||
"type": "belongsTo",
|
||||
"model": "Role",
|
||||
"foreignKey": "writeRoleFk"
|
||||
}
|
||||
},
|
||||
"acls": [
|
||||
|
|
|
@ -0,0 +1,10 @@
|
|||
{
|
||||
"name": "ImageContainer",
|
||||
"base": "Container",
|
||||
"acls": [{
|
||||
"accessType": "READ",
|
||||
"principalType": "ROLE",
|
||||
"principalId": "$everyone",
|
||||
"permission": "ALLOW"
|
||||
}]
|
||||
}
|
|
@ -4,12 +4,9 @@ const path = require('path');
|
|||
|
||||
module.exports = Self => {
|
||||
require('../methods/image/download')(Self);
|
||||
require('../methods/image/upload')(Self);
|
||||
|
||||
Self.getPath = function() {
|
||||
return '/var/lib/salix/image';
|
||||
};
|
||||
|
||||
Self.registerImage = async(collectionName, file, srcFilePath) => {
|
||||
Self.registerImage = async(collectionName, srcFilePath, fileName, entityId) => {
|
||||
const models = Self.app.models;
|
||||
const tx = await Self.beginTransaction({});
|
||||
const myOptions = {transaction: tx};
|
||||
|
@ -33,13 +30,10 @@ module.exports = Self => {
|
|||
}
|
||||
}, myOptions);
|
||||
|
||||
const fileName = file.split('.')[0];
|
||||
const rootPath = Self.getPath();
|
||||
const data = {
|
||||
name: fileName,
|
||||
collectionFk: collectionName
|
||||
};
|
||||
|
||||
const newImage = await Self.upsertWithWhere(data, {
|
||||
name: fileName,
|
||||
collectionFk: collectionName,
|
||||
|
@ -47,7 +41,10 @@ module.exports = Self => {
|
|||
}, myOptions);
|
||||
|
||||
// Resizes and saves the image
|
||||
const container = await models.ImageContainer.container(collectionName);
|
||||
const rootPath = container.client.root;
|
||||
const collectionDir = path.join(rootPath, collectionName);
|
||||
const file = `${fileName}.png`;
|
||||
const dstDir = path.join(collectionDir, 'full');
|
||||
const dstFile = path.join(dstDir, file);
|
||||
|
||||
|
@ -83,7 +80,7 @@ module.exports = Self => {
|
|||
if (!model)
|
||||
throw new Error('Matching model not found');
|
||||
|
||||
const item = await model.findById(fileName, null, myOptions);
|
||||
const item = await model.findById(entityId, null, myOptions);
|
||||
if (item) {
|
||||
await item.updateAttribute(
|
||||
collection.property,
|
||||
|
|
|
@ -0,0 +1,10 @@
|
|||
{
|
||||
"name": "TempContainer",
|
||||
"base": "Container",
|
||||
"acls": [{
|
||||
"accessType": "READ",
|
||||
"principalType": "ROLE",
|
||||
"principalId": "$everyone",
|
||||
"permission": "ALLOW"
|
||||
}]
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
INSERT INTO salix.ACL (model, property, accessType, permission, principalType, principalId) VALUES ('Image', '*', 'WRITE', 'ALLOW', 'ROLE', 'employee')
|
|
@ -0,0 +1,13 @@
|
|||
INSERT INTO account.role (id, name, description)
|
||||
VALUES
|
||||
(74, 'userPhotos', 'Privilegios para subir fotos de usuario'),
|
||||
(75, 'catalogPhotos', 'Privilegios para subir fotos del catálogo');
|
||||
|
||||
INSERT INTO account.roleInherit (role, inheritsFrom)
|
||||
VALUES
|
||||
(37, (SELECT id FROM account.role WHERE name = 'userPhotos')),
|
||||
(51, (SELECT id FROM account.role WHERE name = 'userPhotos')),
|
||||
(51, (SELECT id FROM account.role WHERE name = 'catalogPhotos')),
|
||||
(35, (SELECT id FROM account.role WHERE name = 'catalogPhotos'));
|
||||
|
||||
CALL account.role_sync();
|
|
@ -0,0 +1,27 @@
|
|||
ALTER TABLE `hedera`.`imageCollection`
|
||||
ADD writeRoleFk INT UNSIGNED NULL DEFAULT 1;
|
||||
|
||||
ALTER TABLE `hedera`.`imageCollection`
|
||||
ADD CONSTRAINT role_id_writeRoleFk
|
||||
FOREIGN KEY (writeRoleFk) REFERENCES account.role (id)
|
||||
ON UPDATE CASCADE;
|
||||
|
||||
ALTER TABLE `hedera`.`imageCollection` modify readRoleFk INT UNSIGNED default 1 null;
|
||||
|
||||
ALTER TABLE `hedera`.`imageCollection`
|
||||
ADD CONSTRAINT role_id_readRoleFk
|
||||
FOREIGN KEY (readRoleFk) REFERENCES account.role (id)
|
||||
ON UPDATE CASCADE;
|
||||
|
||||
UPDATE hedera.imageCollection t SET t.writeRoleFk = (
|
||||
SELECT id FROM `account`.`role` WHERE name = 'catalogPhotos'
|
||||
)
|
||||
WHERE t.name = 'catalog';
|
||||
|
||||
UPDATE hedera.imageCollection t SET t.writeRoleFk = (
|
||||
SELECT id FROM `account`.`role` WHERE name = 'userPhotos'
|
||||
)
|
||||
WHERE t.name = 'user';
|
||||
|
||||
UPDATE hedera.imageCollection t SET t.writeRoleFk = 9
|
||||
WHERE t.name IN ('link', 'news');
|
|
@ -447,6 +447,25 @@ INSERT INTO `imageCollection` VALUES (1,'catalog','Artículo',3840,2160,'Item','
|
|||
/*!40000 ALTER TABLE `imageCollection` ENABLE KEYS */;
|
||||
UNLOCK TABLES;
|
||||
|
||||
--
|
||||
-- Dumping data for table `imageCollectionSize`
|
||||
--
|
||||
LOCK TABLES `imageCollectionSize` WRITE;
|
||||
/*!40000 ALTER TABLE `imageCollectionSize` DISABLE KEYS */;
|
||||
INSERT INTO `imageCollectionSize` (`id`, `collectionFk`, `width`, `height`, `crop`)
|
||||
VALUES
|
||||
(2, 1, 50, 50, 1),
|
||||
(3, 1, 200, 200, 1),
|
||||
(5, 5, 200, 200, 1),
|
||||
(6, 1, 70, 70, 1),
|
||||
(8, 5, 50, 50, 1),
|
||||
(9, 1, 1600, 900, 0),
|
||||
(13, 6, 160, 160, 1),
|
||||
(14, 6, 520, 520, 1),
|
||||
(15, 6, 1600, 1600, 1);
|
||||
/*!40000 ALTER TABLE `imageCollectionSize` ENABLE KEYS */;
|
||||
UNLOCK TABLES;
|
||||
|
||||
--
|
||||
-- Dumping data for table `tpvError`
|
||||
--
|
||||
|
|
|
@ -30,7 +30,7 @@ UPDATE `account`.`role` SET id = 100 WHERE id = 0;
|
|||
CALL `account`.`role_sync`;
|
||||
|
||||
INSERT INTO `account`.`user`(`id`,`name`, `nickname`, `password`,`role`,`active`,`email`, `lang`, `image`)
|
||||
SELECT id, name, CONCAT(name, 'Nick'),MD5('nightmare'), id, 1, CONCAT(name, '@mydomain.com'), 'en', 'e7723f0b24ff05b32ed09d95196f2f29'
|
||||
SELECT id, name, CONCAT(name, 'Nick'),MD5('nightmare'), id, 1, CONCAT(name, '@mydomain.com'), 'en', '4fa3ada0-3ac4-11eb-9ab8-27f6fc3b85fd'
|
||||
FROM `account`.`role` WHERE id <> 20
|
||||
ORDER BY id;
|
||||
|
||||
|
@ -772,23 +772,23 @@ INSERT INTO `vn`.`intrastat`(`id`, `description`, `taxClassFk`, `taxCodeFk`)
|
|||
|
||||
INSERT INTO `vn`.`item`(`id`, `typeFk`, `size`, `inkFk`, `stems`, `originFk`, `description`, `producerFk`, `intrastatFk`, `isOnOffer`, `expenceFk`, `isBargain`, `comment`, `relevancy`, `image`, `taxClassFk`, `subName`, `minPrice`)
|
||||
VALUES
|
||||
(1, 2, 70, 'YEL', 1, 1, NULL, 1, 06021010, 0, 2000000000, 0, NULL, 0, 67, 1, NULL, 0),
|
||||
(2, 2, 70, 'BLU', 1, 2, NULL, 1, 06021010, 0, 2000000000, 0, NULL, 0, 66, 1, NULL, 0),
|
||||
(3, 1, 60, 'YEL', 1, 3, NULL, 1, 05080000, 0, 4751000000, 0, NULL, 0, 65, 1, NULL, 0),
|
||||
(4, 1, 60, 'YEL', 1, 1, 'Increases block', 1, 05080000, 1, 4751000000, 0, NULL, 0, 69, 2, NULL, 0),
|
||||
(5, 3, 30, 'RED', 1, 2, NULL, 2, 06021010, 1, 4751000000, 0, NULL, 0, 74, 2, NULL, 0),
|
||||
(6, 5, 30, 'RED', 1, 2, NULL, NULL, 06021010, 0, 4751000000, 0, NULL, 0, 62, 2, NULL, 0),
|
||||
(7, 5, 90, 'BLU', 1, 2, NULL, NULL, 06021010, 0, 4751000000, 0, NULL, 0, 64, 2, NULL, 0),
|
||||
(8, 2, 70, 'YEL', 1, 1, NULL, 1, 06021010, 0, 2000000000, 0, NULL, 0, 75, 1, NULL, 0),
|
||||
(9, 2, 70, 'BLU', 1, 2, NULL, 1, 06021010, 0, 2000000000, 0, NULL, 0, 76, 1, NULL, 0),
|
||||
(10, 1, 60, 'YEL', 1, 3, NULL, 1, 05080000, 0, 4751000000, 0, NULL, 0, 77, 1, NULL, 0),
|
||||
(11, 1, 60, 'YEL', 1, 1, NULL, 1, 05080000, 1, 4751000000, 0, NULL, 0, 78, 2, NULL, 0),
|
||||
(12, 3, 30, 'RED', 1, 2, NULL, 2, 06021010, 1, 4751000000, 0, NULL, 0, 82, 2, NULL, 0),
|
||||
(13, 5, 30, 'RED', 1, 2, NULL, NULL, 06021010, 0, 4751000000, 0, NULL, 0, 83, 2, NULL, 0),
|
||||
(14, 5, 90, 'BLU', 1, 2, NULL, NULL, 06021010, 0, 4751000000, 0, NULL, 0, 84, 2, NULL, 0),
|
||||
(15, 4, NULL, NULL, NULL, 1, NULL, NULL, 06021010, 0, 4751000000, 0, NULL, 0, 88, 2, NULL, 0),
|
||||
(16, 4, NULL, NULL, NULL, 1, NULL, NULL, 06021010, 0, 4751000000, 0, NULL, 0, 88, 2, NULL, 0),
|
||||
(71, 4, NULL, NULL, NULL, 1, NULL, NULL, 06021010, 1, 4751000000, 0, NULL, 0, 88, 2, NULL, 0);
|
||||
(1, 2, 70, 'YEL', 1, 1, NULL, 1, 06021010, 0, 2000000000, 0, NULL, 0, '1', 1, NULL, 0),
|
||||
(2, 2, 70, 'BLU', 1, 2, NULL, 1, 06021010, 0, 2000000000, 0, NULL, 0, '2', 1, NULL, 0),
|
||||
(3, 1, 60, 'YEL', 1, 3, NULL, 1, 05080000, 0, 4751000000, 0, NULL, 0, '3', 1, NULL, 0),
|
||||
(4, 1, 60, 'YEL', 1, 1, 'Increases block', 1, 05080000, 1, 4751000000, 0, NULL, 0, '4', 2, NULL, 0),
|
||||
(5, 3, 30, 'RED', 1, 2, NULL, 2, 06021010, 1, 4751000000, 0, NULL, 0, '5', 2, NULL, 0),
|
||||
(6, 5, 30, 'RED', 1, 2, NULL, NULL, 06021010, 0, 4751000000, 0, NULL, 0, '6', 2, NULL, 0),
|
||||
(7, 5, 90, 'BLU', 1, 2, NULL, NULL, 06021010, 0, 4751000000, 0, NULL, 0, '7', 2, NULL, 0),
|
||||
(8, 2, 70, 'YEL', 1, 1, NULL, 1, 06021010, 0, 2000000000, 0, NULL, 0, '8', 1, NULL, 0),
|
||||
(9, 2, 70, 'BLU', 1, 2, NULL, 1, 06021010, 0, 2000000000, 0, NULL, 0, '9', 1, NULL, 0),
|
||||
(10, 1, 60, 'YEL', 1, 3, NULL, 1, 05080000, 0, 4751000000, 0, NULL, 0, '10', 1, NULL, 0),
|
||||
(11, 1, 60, 'YEL', 1, 1, NULL, 1, 05080000, 1, 4751000000, 0, NULL, 0, '11', 2, NULL, 0),
|
||||
(12, 3, 30, 'RED', 1, 2, NULL, 2, 06021010, 1, 4751000000, 0, NULL, 0, '12', 2, NULL, 0),
|
||||
(13, 5, 30, 'RED', 1, 2, NULL, NULL, 06021010, 0, 4751000000, 0, NULL, 0, '13', 2, NULL, 0),
|
||||
(14, 5, 90, 'BLU', 1, 2, NULL, NULL, 06021010, 0, 4751000000, 0, NULL, 0, '', 2, NULL, 0),
|
||||
(15, 4, NULL, NULL, NULL, 1, NULL, NULL, 06021010, 0, 4751000000, 0, NULL, 0, '', 2, NULL, 0),
|
||||
(16, 4, NULL, NULL, NULL, 1, NULL, NULL, 06021010, 0, 4751000000, 0, NULL, 0, '', 2, NULL, 0),
|
||||
(71, 4, NULL, NULL, NULL, 1, NULL, NULL, 06021010, 1, 4751000000, 0, NULL, 0, '', 2, NULL, 0);
|
||||
|
||||
INSERT INTO `vn`.`priceFixed`(`id`, `itemFk`, `rate0`, `rate1`, `rate2`, `rate3`, `started`, `ended`, `bonus`, `warehouseFk`, `created`)
|
||||
VALUES
|
||||
|
@ -2155,7 +2155,7 @@ INSERT INTO `vn`.`campaign`(`code`, `dated`)
|
|||
|
||||
INSERT INTO `hedera`.`image`(`collectionFk`, `name`)
|
||||
VALUES
|
||||
('user', 'e7723f0b24ff05b32ed09d95196f2f29');
|
||||
('user', '4fa3ada0-3ac4-11eb-9ab8-27f6fc3b85fd');
|
||||
|
||||
INSERT INTO `hedera`.`imageCollectionSize`(`id`, `collectionFk`,`width`, `height`)
|
||||
VALUES
|
||||
|
|
|
@ -38,7 +38,7 @@ describe('Travel thermograph path', () => {
|
|||
|
||||
it('should select the file to upload', async() => {
|
||||
let currentDir = process.cwd();
|
||||
let filePath = `${currentDir}/e2e/dms/ecc/3.jpeg`;
|
||||
let filePath = `${currentDir}/storage/dms/ecc/3.jpeg`;
|
||||
|
||||
const [fileChooser] = await Promise.all([
|
||||
page.waitForFileChooser(),
|
||||
|
|
|
@ -46,7 +46,7 @@ export function directive($timeout) {
|
|||
$element.on('click', function(event) {
|
||||
if (event.defaultPrevented) return;
|
||||
|
||||
let src = $attrs.zoomImage || $attrs.src;
|
||||
let src = $element[0].getAttribute('zoom-image') || $element[0].src;
|
||||
if (src)
|
||||
createContainers(src);
|
||||
else
|
||||
|
|
|
@ -5,6 +5,24 @@
|
|||
vn-descriptor-content {
|
||||
display: block;
|
||||
|
||||
.photo {
|
||||
position: relative;
|
||||
|
||||
& > img[ng-src] {
|
||||
min-height: 16em;
|
||||
display: block;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
vn-float-button {
|
||||
position: absolute;
|
||||
margin: 1em;
|
||||
bottom: 0;
|
||||
right: 0
|
||||
}
|
||||
}
|
||||
|
||||
& > vn-spinner {
|
||||
display: block;
|
||||
height: 40px;
|
||||
|
|
|
@ -13,3 +13,4 @@ import './section';
|
|||
import './summary';
|
||||
import './topbar/topbar';
|
||||
import './user-popover';
|
||||
import './upload-photo';
|
||||
|
|
|
@ -109,13 +109,14 @@ vn-layout {
|
|||
}
|
||||
}
|
||||
}
|
||||
.buttonAccount {
|
||||
background: none;
|
||||
border: none;
|
||||
|
||||
img {
|
||||
width: 40px;
|
||||
border-radius: 50%;
|
||||
}
|
||||
.buttonAccount {
|
||||
background: none;
|
||||
border: none;
|
||||
}
|
||||
@media screen and (max-width: $mobile-width) {
|
||||
& > vn-topbar {
|
||||
|
|
|
@ -0,0 +1,39 @@
|
|||
<vn-dialog class="edit"
|
||||
vn-id="dialog"
|
||||
on-accept="$ctrl.onUploadAccept()"
|
||||
message="Upload new photo">
|
||||
<tpl-body class="upload-photo">
|
||||
<vn-horizontal ng-show="file.value" class="photo vn-mb-md">
|
||||
<div><img vn-id="photo" ng-src=""/></div>
|
||||
</vn-horizontal>
|
||||
<vn-horizontal>
|
||||
<vn-textfield
|
||||
vn-one
|
||||
label="File name"
|
||||
ng-model="$ctrl.newPhoto.fileName"
|
||||
required="true">
|
||||
</vn-input-file>
|
||||
</vn-horizontal>
|
||||
<vn-horizontal>
|
||||
<vn-input-file vn-id="file"
|
||||
vn-one
|
||||
label="File"
|
||||
ng-model="$ctrl.newPhoto.files"
|
||||
on-change="$ctrl.updatePhotoPreview(value)"
|
||||
accept="{{$ctrl.allowedContentTypes}}"
|
||||
required="true">
|
||||
<append>
|
||||
<vn-icon vn-none
|
||||
color-marginal
|
||||
title="{{$ctrl.contentTypesInfo}}"
|
||||
icon="info">
|
||||
</vn-icon>
|
||||
</append>
|
||||
</vn-input-file>
|
||||
</vn-horizontal>
|
||||
</tpl-body>
|
||||
<tpl-buttons>
|
||||
<input type="button" response="cancel" translate-attr="{value: 'Cancel'}"/>
|
||||
<button response="accept" translate>Upload</button>
|
||||
</tpl-buttons>
|
||||
</vn-dialog>
|
|
@ -0,0 +1,105 @@
|
|||
import ngModule from '../../module';
|
||||
import Component from 'core/lib/component';
|
||||
import './style.scss';
|
||||
|
||||
/**
|
||||
* Small card with basing entity information and actions.
|
||||
*/
|
||||
export default class UploadPhoto extends Component {
|
||||
/**
|
||||
* Opens the dialog and sets the default data
|
||||
* @param {*} collection - Collection name
|
||||
* @param {*} id - Entity id
|
||||
*/
|
||||
show(collection, id) {
|
||||
this.newPhoto = {
|
||||
id: id,
|
||||
collection: collection,
|
||||
fileName: id
|
||||
};
|
||||
this.$.dialog.show();
|
||||
this.getAllowedContentTypes();
|
||||
}
|
||||
|
||||
getAllowedContentTypes() {
|
||||
this.$http.get('ImageContainers/allowedContentTypes').then(res => {
|
||||
const contentTypes = res.data.join(', ');
|
||||
this.allowedContentTypes = contentTypes;
|
||||
});
|
||||
}
|
||||
|
||||
get contentTypesInfo() {
|
||||
return this.$t('ContentTypesInfo', {
|
||||
allowedContentTypes: this.allowedContentTypes
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the image preview
|
||||
*
|
||||
* @param {string} value
|
||||
*/
|
||||
updatePhotoPreview(value) {
|
||||
if (value && value[0]) {
|
||||
const reader = new FileReader();
|
||||
reader.onload = e => this.$.photo.src = e.target.result;
|
||||
reader.readAsDataURL(value[0]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Dialog response handler
|
||||
*
|
||||
* @return {boolean} Response
|
||||
*/
|
||||
onUploadAccept() {
|
||||
try {
|
||||
if (!this.newPhoto.files)
|
||||
throw new Error(`Select an image`);
|
||||
|
||||
this.makeRequest();
|
||||
} catch (e) {
|
||||
this.vnApp.showError(this.$t(e.message));
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs a cancellable request.
|
||||
*
|
||||
*/
|
||||
makeRequest() {
|
||||
if (this.canceler) this.canceler.resolve();
|
||||
this.canceler = this.$q.defer();
|
||||
|
||||
const options = {
|
||||
method: 'POST',
|
||||
url: `Images/upload`,
|
||||
params: this.newPhoto,
|
||||
headers: {'Content-Type': undefined},
|
||||
timeout: this.canceler.promise,
|
||||
transformRequest: files => {
|
||||
const formData = new FormData();
|
||||
for (let i = 0; i < files.length; i++)
|
||||
formData.append(files[i].name, files[i]);
|
||||
|
||||
return formData;
|
||||
},
|
||||
data: this.newPhoto.files
|
||||
};
|
||||
|
||||
this.$http(options)
|
||||
.then(() => this.vnApp.showSuccess(this.$t('Data saved!')))
|
||||
.then(() => this.emit('response'))
|
||||
.finally(() => this.canceler = null);
|
||||
}
|
||||
}
|
||||
|
||||
ngModule.vnComponent('vnUploadPhoto', {
|
||||
controller: UploadPhoto,
|
||||
template: require('./index.html'),
|
||||
bindings: {
|
||||
data: '<'
|
||||
}
|
||||
});
|
|
@ -0,0 +1,57 @@
|
|||
import './index.js';
|
||||
|
||||
describe('Salix', () => {
|
||||
describe('Component vnUploadPhoto', () => {
|
||||
let controller;
|
||||
let $scope;
|
||||
let $httpBackend;
|
||||
|
||||
beforeEach(ngModule('salix'));
|
||||
|
||||
beforeEach(inject(($componentController, $rootScope, _$httpBackend_) => {
|
||||
$scope = $rootScope.$new();
|
||||
$httpBackend = _$httpBackend_;
|
||||
const $element = angular.element('<vn-upload-photo></vn-upload-photo>');
|
||||
controller = $componentController('vnUploadPhoto', {$element, $scope});
|
||||
controller.newPhoto = {};
|
||||
}));
|
||||
|
||||
afterEach(() => {
|
||||
$scope.$destroy();
|
||||
});
|
||||
|
||||
describe('onUploadAccept()', () => {
|
||||
it('should throw an error message containing "Select an image"', () => {
|
||||
jest.spyOn(controller.vnApp, 'showError');
|
||||
|
||||
controller.onUploadAccept();
|
||||
|
||||
expect(controller.vnApp.showError).toHaveBeenCalledWith('Select an image');
|
||||
});
|
||||
|
||||
it('should call to the makeRequest() method', () => {
|
||||
jest.spyOn(controller, 'makeRequest');
|
||||
|
||||
controller.newPhoto.files = [0];
|
||||
controller.onUploadAccept();
|
||||
|
||||
expect(controller.makeRequest).toHaveBeenCalledWith();
|
||||
});
|
||||
});
|
||||
|
||||
describe('makeRequest()', () => {
|
||||
it('should make an http query and then emit a response event', () => {
|
||||
jest.spyOn(controller.vnApp, 'showSuccess');
|
||||
jest.spyOn(controller, 'emit');
|
||||
|
||||
controller.newPhoto.files = [{name: 'hola'}];
|
||||
$httpBackend.expectRoute('POST', 'Images/upload').respond(200);
|
||||
controller.makeRequest();
|
||||
$httpBackend.flush();
|
||||
|
||||
expect(controller.vnApp.showSuccess).toHaveBeenCalledWith('Data saved!');
|
||||
expect(controller.emit).toHaveBeenCalledWith('response');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,3 @@
|
|||
Upload new photo: Subir una nueva foto
|
||||
Select an image: Selecciona una imagen
|
||||
File name: Nombre del fichero
|
|
@ -0,0 +1,39 @@
|
|||
@import "./variables";
|
||||
|
||||
.upload-photo {
|
||||
.photo {
|
||||
position: relative;
|
||||
margin: 0 auto;
|
||||
text-align: center;
|
||||
|
||||
& > div {
|
||||
border: 3px solid $color-primary;
|
||||
max-width: 256px;
|
||||
max-height: 256px;
|
||||
border-radius: 50%;
|
||||
overflow: hidden
|
||||
}
|
||||
|
||||
& > div > img[ng-src] {
|
||||
width: 256px;
|
||||
height: 256px;
|
||||
display: block;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
& > vn-spinner {
|
||||
display: block;
|
||||
height: 40px;
|
||||
padding: $spacing-md;
|
||||
}
|
||||
|
||||
vn-input-file {
|
||||
max-width: 256px;
|
||||
|
||||
div.control {
|
||||
overflow: hidden
|
||||
}
|
||||
}
|
||||
}
|
|
@ -14,7 +14,7 @@
|
|||
<vn-vertical class="user-popover vn-pa-md">
|
||||
<div class="profile-card vn-pb-md">
|
||||
<img
|
||||
ng-src="{{$ctrl.getImageUrl($root.user.id)}}"
|
||||
ng-src="{{::$root.imagePath('user', '160x160', $root.user.id)}}"
|
||||
on-error-src/>
|
||||
<div class="vn-pl-sm">
|
||||
<div>
|
||||
|
|
|
@ -78,10 +78,6 @@ class Controller {
|
|||
this.$.companies.refresh();
|
||||
this.$.popover.show(event.target);
|
||||
}
|
||||
|
||||
getImageUrl(userId) {
|
||||
return '/api/Images/user/160x160/' + userId + '/download?access_token=' + this.vnToken.token;
|
||||
}
|
||||
}
|
||||
Controller.$inject = ['$scope', '$translate', 'vnConfig', 'vnAuth', 'vnToken'];
|
||||
|
||||
|
|
|
@ -2,14 +2,16 @@ import './index.js';
|
|||
|
||||
describe('Salix', () => {
|
||||
describe('Component vnUserPopover', () => {
|
||||
const userId = 9;
|
||||
let controller;
|
||||
let $scope;
|
||||
let $root;
|
||||
|
||||
beforeEach(ngModule('salix'));
|
||||
|
||||
beforeEach(inject(($componentController, $rootScope, $httpBackend) => {
|
||||
$httpBackend.expectGET('UserConfigs/getUserConfig');
|
||||
|
||||
$root = $rootScope;
|
||||
$scope = $rootScope.$new();
|
||||
controller = $componentController('vnUserPopover', {$scope});
|
||||
}));
|
||||
|
@ -60,9 +62,10 @@ describe('Salix', () => {
|
|||
|
||||
describe('getImageUrl()', () => {
|
||||
it('should return de url image', () => {
|
||||
const url = controller.getImageUrl();
|
||||
const url = $root.imagePath('user', '160x160', userId);
|
||||
|
||||
expect(url).toBeDefined();
|
||||
expect(url).toEqual(`/api/Images/user/160x160/${userId}/download?access_token=null`);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -7,9 +7,14 @@ export const appName = 'salix';
|
|||
const ngModule = ng.module('salix', ['vnCore']);
|
||||
export default ngModule;
|
||||
|
||||
run.$inject = ['$window', '$rootScope', 'vnAuth', 'vnApp', '$state'];
|
||||
export function run($window, $rootScope, vnAuth, vnApp, $state) {
|
||||
$rootScope.imagePath = appConfig.imagePath;
|
||||
run.$inject = ['$window', '$rootScope', 'vnAuth', 'vnApp', 'vnToken', '$state'];
|
||||
export function run($window, $rootScope, vnAuth, vnApp, vnToken, $state) {
|
||||
$rootScope.imagePath = (collection, size, id) => {
|
||||
if (!collection || !size || !id) return;
|
||||
|
||||
const basePath = `/api/Images/${collection}/${size}/${id}`;
|
||||
return `${basePath}/download?access_token=${vnToken.token}`;
|
||||
};
|
||||
|
||||
$window.validations = {};
|
||||
vnApp.name = appName;
|
||||
|
|
|
@ -0,0 +1,57 @@
|
|||
const md5 = require('md5');
|
||||
|
||||
module.exports = function(Self) {
|
||||
Self.setup = function() {
|
||||
Self.super_.setup.call(this);
|
||||
|
||||
this.remoteMethod('allowedContentTypes', {
|
||||
description: 'Returns a list of allowed contentTypes',
|
||||
accessType: 'READ',
|
||||
returns: {
|
||||
type: ['Object'],
|
||||
root: true
|
||||
},
|
||||
http: {
|
||||
path: `/allowedContentTypes`,
|
||||
verb: 'GET'
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns a container instance
|
||||
* If doesn't exists creates a new one
|
||||
*
|
||||
* @param {String} name Container name
|
||||
* @return {Object} Container instance
|
||||
*/
|
||||
Self.container = async function(name) {
|
||||
const models = Self.app.models;
|
||||
let container;
|
||||
try {
|
||||
container = await models[this.modelName].getContainer(name);
|
||||
} catch (err) {
|
||||
if (err.code === 'ENOENT') {
|
||||
container = await models[this.modelName].createContainer({
|
||||
name: name
|
||||
});
|
||||
} else throw err;
|
||||
}
|
||||
|
||||
return container;
|
||||
};
|
||||
|
||||
Self.getHash = function(id) {
|
||||
return md5(id.toString()).substring(0, 3);
|
||||
};
|
||||
|
||||
Self.getFileExtension = function(fileName) {
|
||||
return fileName.split('.').pop().toLowerCase();
|
||||
};
|
||||
|
||||
Self.allowedContentTypes = async function() {
|
||||
const connector = this.dataSource.connector;
|
||||
const allowedContentTypes = connector.allowedContentTypes;
|
||||
return allowedContentTypes;
|
||||
};
|
||||
};
|
|
@ -0,0 +1,12 @@
|
|||
{
|
||||
"name": "Container",
|
||||
"base": "VnModel",
|
||||
"acls": [
|
||||
{
|
||||
"property": "status",
|
||||
"principalType": "ROLE",
|
||||
"principalId": "$everyone",
|
||||
"permission": "ALLOW"
|
||||
}
|
||||
]
|
||||
}
|
|
@ -161,5 +161,6 @@
|
|||
"The nif cannot be empty": "El NIF no puede quedar en blanco",
|
||||
"You need to fill sage information before you check verified data": "Debes rellenar la información de sage antes de marcar datos comprobados",
|
||||
"ASSIGN_ZONE_FIRST": "Asigna una zona primero",
|
||||
"You can't upload images on the test environment": "No puedes subir imágenes en el entorno de pruebas",
|
||||
"You can not select this payment method without a registered bankery account": "No se puede utilizar este método de pago si no has registrado una cuenta bancaria"
|
||||
}
|
|
@ -1,18 +0,0 @@
|
|||
const uuid = require('uuid/v1');
|
||||
const md5 = require('md5');
|
||||
|
||||
module.exports = app => {
|
||||
const storageConnector = app.dataSources.storage.connector;
|
||||
|
||||
storageConnector.getFilename = function(file) {
|
||||
return `${uuid()}.${storageConnector.getFileExtension(file.name)}`;
|
||||
};
|
||||
|
||||
storageConnector.getFileExtension = function(fileName) {
|
||||
return fileName.split('.').pop().toLowerCase();
|
||||
};
|
||||
|
||||
storageConnector.getPathHash = function(id) {
|
||||
return md5(id.toString()).substring(0, 3);
|
||||
};
|
||||
};
|
|
@ -17,11 +17,11 @@
|
|||
"connectTimeout": 40000,
|
||||
"acquireTimeout": 20000
|
||||
},
|
||||
"storage": {
|
||||
"name": "storage",
|
||||
"tempStorage": {
|
||||
"name": "tempStorage",
|
||||
"connector": "loopback-component-storage",
|
||||
"provider": "filesystem",
|
||||
"root": "./e2e/dms",
|
||||
"root": "./storage/tmp",
|
||||
"maxFileSize": "262144000",
|
||||
"allowedContentTypes": [
|
||||
"application/x-7z-compressed",
|
||||
|
@ -36,5 +36,37 @@
|
|||
"image/jpeg",
|
||||
"image/jpg"
|
||||
]
|
||||
},
|
||||
"dmsStorage": {
|
||||
"name": "dmsStorage",
|
||||
"connector": "loopback-component-storage",
|
||||
"provider": "filesystem",
|
||||
"root": "./storage/dms",
|
||||
"maxFileSize": "262144000",
|
||||
"allowedContentTypes": [
|
||||
"application/x-7z-compressed",
|
||||
"application/x-zip-compressed",
|
||||
"application/x-rar-compressed",
|
||||
"application/octet-stream",
|
||||
"application/pdf",
|
||||
"application/zip",
|
||||
"application/rar",
|
||||
"multipart/x-zip",
|
||||
"image/png",
|
||||
"image/jpeg",
|
||||
"image/jpg"
|
||||
]
|
||||
},
|
||||
"imageStorage": {
|
||||
"name": "imageStorage",
|
||||
"connector": "loopback-component-storage",
|
||||
"provider": "filesystem",
|
||||
"root": "./storage/image",
|
||||
"maxFileSize": "52428800",
|
||||
"allowedContentTypes": [
|
||||
"image/png",
|
||||
"image/jpeg",
|
||||
"image/jpg"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
|
@ -49,5 +49,8 @@
|
|||
},
|
||||
"Application": {
|
||||
"dataSource": "vn"
|
||||
},
|
||||
"Container": {
|
||||
"dataSource": "vn"
|
||||
}
|
||||
}
|
|
@ -13,7 +13,7 @@ module.exports = Self => {
|
|||
});
|
||||
|
||||
Self.allowedContentTypes = async() => {
|
||||
const storageConnector = Self.app.dataSources.storage.connector;
|
||||
const storageConnector = Self.app.dataSources.dmsStorage.connector;
|
||||
const allowedContentTypes = storageConnector.allowedContentTypes;
|
||||
const modelAllowedContentTypes = Self.definition.settings.allowedContentTypes;
|
||||
|
||||
|
|
|
@ -1,23 +0,0 @@
|
|||
module.exports = Self => {
|
||||
Self.remoteMethodCtx('allowedContentTypes', {
|
||||
description: 'Returns a list of allowed contentTypes',
|
||||
accessType: 'READ',
|
||||
returns: {
|
||||
type: ['Object'],
|
||||
root: true
|
||||
},
|
||||
http: {
|
||||
path: `/allowedContentTypes`,
|
||||
verb: 'GET'
|
||||
}
|
||||
});
|
||||
|
||||
Self.allowedContentTypes = async() => {
|
||||
const storageConnector = Self.app.dataSources.storage.connector;
|
||||
const allowedContentTypes = storageConnector.allowedContentTypes;
|
||||
const modelAllowedContentTypes = Self.definition.settings.allowedContentTypes;
|
||||
|
||||
return modelAllowedContentTypes || allowedContentTypes;
|
||||
};
|
||||
};
|
||||
|
|
@ -1,4 +1,3 @@
|
|||
module.exports = Self => {
|
||||
require('../methods/client-dms/removeFile')(Self);
|
||||
require('../methods/client-dms/allowedContentTypes')(Self);
|
||||
};
|
||||
|
|
|
@ -26,7 +26,7 @@ class Controller extends Section {
|
|||
}
|
||||
|
||||
getAllowedContentTypes() {
|
||||
this.$http.get('clientDms/allowedContentTypes').then(res => {
|
||||
this.$http.get('DmsContainers/allowedContentTypes').then(res => {
|
||||
const contentTypes = res.data.join(', ');
|
||||
this.allowedContentTypes = contentTypes;
|
||||
});
|
||||
|
|
|
@ -62,7 +62,7 @@ describe('Client', () => {
|
|||
describe('getAllowedContentTypes()', () => {
|
||||
it('should make an HTTP GET request to get the allowed content types', () => {
|
||||
const expectedResponse = ['image/png', 'image/jpg'];
|
||||
$httpBackend.expect('GET', `clientDms/allowedContentTypes`).respond(expectedResponse);
|
||||
$httpBackend.expect('GET', `DmsContainers/allowedContentTypes`).respond(expectedResponse);
|
||||
controller.getAllowedContentTypes();
|
||||
$httpBackend.flush();
|
||||
|
||||
|
|
|
@ -17,7 +17,7 @@ class Controller extends Section {
|
|||
}
|
||||
|
||||
getAllowedContentTypes() {
|
||||
this.$http.get('clientDms/allowedContentTypes').then(res => {
|
||||
this.$http.get('DmsContainers/allowedContentTypes').then(res => {
|
||||
const contentTypes = res.data.join(', ');
|
||||
this.allowedContentTypes = contentTypes;
|
||||
});
|
||||
|
|
|
@ -69,7 +69,7 @@ describe('Client', () => {
|
|||
describe('getAllowedContentTypes()', () => {
|
||||
it('should make an HTTP GET request to get the allowed content types', () => {
|
||||
const expectedResponse = ['image/png', 'image/jpg'];
|
||||
$httpBackend.expect('GET', `clientDms/allowedContentTypes`).respond(expectedResponse);
|
||||
$httpBackend.expect('GET', `DmsContainers/allowedContentTypes`).respond(expectedResponse);
|
||||
controller.getAllowedContentTypes();
|
||||
$httpBackend.flush();
|
||||
|
||||
|
|
|
@ -70,8 +70,8 @@
|
|||
</vn-td>
|
||||
<vn-td shrink >
|
||||
<img
|
||||
ng-src="{{::$root.imagePath}}/catalog/50x50/{{::buy.image}}"
|
||||
zoom-image="{{::$root.imagePath}}/catalog/1600x900/{{::buy.image}}"
|
||||
ng-src="{{::$root.imagePath('catalog', '50x50', buy.itemFk)}}"
|
||||
zoom-image="{{::$root.imagePath('catalog', '1600x900', buy.itemFk)}}"
|
||||
vn-click-stop
|
||||
on-error-src/>
|
||||
</vn-td>
|
||||
|
|
|
@ -33,8 +33,10 @@ module.exports = Self => {
|
|||
// Exit loop
|
||||
if (!image) return clearInterval(timer);
|
||||
|
||||
const fileName = `${image.itemFk}.png`;
|
||||
const filePath = path.join(tempPath, fileName);
|
||||
const srcFile = image.url.split('/').pop();
|
||||
const fileName = srcFile.split('.')[0];
|
||||
const file = `${fileName}.png`;
|
||||
const filePath = path.join(tempPath, file);
|
||||
|
||||
const writeStream = fs.createWriteStream(filePath);
|
||||
writeStream.on('open', () => {
|
||||
|
@ -57,7 +59,7 @@ module.exports = Self => {
|
|||
|
||||
writeStream.on('finish', async function() {
|
||||
try {
|
||||
await models.Image.registerImage('catalog', fileName, filePath);
|
||||
await models.Image.registerImage('catalog', filePath, fileName, image.itemFk);
|
||||
await image.destroy();
|
||||
} catch (error) {
|
||||
await errorHandler(image.itemFk, error, filePath);
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<vn-portal slot="menu">
|
||||
<vn-item-descriptor item="$ctrl.item"></vn-item-descriptor>
|
||||
<vn-item-descriptor item="$ctrl.item" card-reload="$ctrl.reload()"></vn-item-descriptor>
|
||||
<vn-left-menu source="card"></vn-left-menu>
|
||||
</vn-portal>
|
||||
<ui-view></ui-view>
|
||||
|
|
|
@ -16,19 +16,15 @@
|
|||
</vn-item>
|
||||
</slot-menu>
|
||||
<slot-before>
|
||||
<div style="position: relative" text-center>
|
||||
<img
|
||||
ng-src="{{::$root.imagePath}}/catalog/200x200/{{$ctrl.item.image}}"
|
||||
zoom-image="{{::$root.imagePath}}/catalog/1600x900/{{$ctrl.item.image}}"
|
||||
on-error-src
|
||||
/>
|
||||
<a href="//verdnatura.es/#!form=admin/items&filter={{$ctrl.item.id}}" target="_blank">
|
||||
<vn-float-button
|
||||
<div class="photo" text-center>
|
||||
<img vn-id="photo"
|
||||
ng-src="{{$root.imagePath('catalog', '200x200', $ctrl.item.id)}}"
|
||||
zoom-image="{{$root.imagePath('catalog', '1600x900', $ctrl.item.id)}}"
|
||||
on-error-src/>
|
||||
<vn-float-button ng-click="uploadPhoto.show('catalog', $ctrl.item.id)"
|
||||
icon="edit"
|
||||
style="position: absolute; margin: 1em; bottom: 0; right: 0;"
|
||||
vn-visible-by="marketing, buyer">
|
||||
vn-visible-by="catalogPhotos">
|
||||
</vn-float-button>
|
||||
</a>
|
||||
</div>
|
||||
<vn-horizontal class="item-state">
|
||||
<vn-one>
|
||||
|
@ -103,3 +99,9 @@
|
|||
<vn-worker-descriptor-popover
|
||||
vn-id="workerDescriptor">
|
||||
</vn-worker-descriptor-popover>
|
||||
|
||||
<!-- Upload photo dialog -->
|
||||
<vn-upload-photo
|
||||
vn-id="uploadPhoto"
|
||||
on-response="$ctrl.onUploadResponse()">
|
||||
</vn-upload-photo>
|
|
@ -3,6 +3,11 @@ import Descriptor from 'salix/components/descriptor';
|
|||
import './style.scss';
|
||||
|
||||
class Controller extends Descriptor {
|
||||
constructor($element, $, $rootScope) {
|
||||
super($element, $);
|
||||
this.$rootScope = $rootScope;
|
||||
}
|
||||
|
||||
get item() {
|
||||
return this.entity;
|
||||
}
|
||||
|
@ -65,13 +70,27 @@ class Controller extends Descriptor {
|
|||
this.$http.post(`Items/${this.item.id}/clone`)
|
||||
.then(res => this.$state.go('item.card.tags', {id: res.data.id}));
|
||||
}
|
||||
|
||||
onUploadResponse() {
|
||||
const timestamp = new Date().getTime();
|
||||
const src = this.$rootScope.imagePath('catalog', '200x200', this.item.id);
|
||||
const zoomSrc = this.$rootScope.imagePath('catalog', '1600x900', this.item.id);
|
||||
const newSrc = `${src}&t=${timestamp}`;
|
||||
const newZoomSrc = `${zoomSrc}&t=${timestamp}`;
|
||||
|
||||
this.$.photo.setAttribute('src', newSrc);
|
||||
this.$.photo.setAttribute('zoom-image', newZoomSrc);
|
||||
}
|
||||
}
|
||||
|
||||
Controller.$inject = ['$element', '$scope', '$rootScope'];
|
||||
|
||||
ngModule.vnComponent('vnItemDescriptor', {
|
||||
template: require('./index.html'),
|
||||
controller: Controller,
|
||||
bindings: {
|
||||
item: '<',
|
||||
dated: '<'
|
||||
dated: '<',
|
||||
cardReload: '&'
|
||||
}
|
||||
});
|
||||
|
|
|
@ -35,8 +35,8 @@
|
|||
ui-sref="item.card.summary({id: item.id})">
|
||||
<vn-td shrink>
|
||||
<img
|
||||
ng-src="{{::$root.imagePath}}/catalog/50x50/{{::item.image}}"
|
||||
zoom-image="{{::$root.imagePath}}/catalog/1600x900/{{::item.image}}"
|
||||
ng-src="{{::$root.imagePath('catalog', '50x50', item.id)}}"
|
||||
zoom-image="{{::$root.imagePath('catalog', '1600x900', item.id)}}"
|
||||
vn-click-stop
|
||||
on-error-src/>
|
||||
</vn-td>
|
||||
|
@ -44,7 +44,7 @@
|
|||
<span
|
||||
vn-click-stop="itemDescriptor.show($event, item.id)"
|
||||
class="link">
|
||||
{{::item.id | zeroFill:6}}
|
||||
{{::item.id}}
|
||||
</span>
|
||||
</vn-td>
|
||||
<vn-td shrink>{{::item.grouping | dashIfEmpty}}</vn-td>
|
||||
|
|
|
@ -11,8 +11,8 @@
|
|||
<vn-horizontal>
|
||||
<vn-one>
|
||||
<img style="width: 100%; display: block;"
|
||||
ng-src="{{::$root.imagePath}}/catalog/200x200/{{$ctrl.item.image}}"
|
||||
zoom-image="{{::$root.imagePath}}/catalog/1600x900/{{$ctrl.item.image}}" on-error-src/>
|
||||
ng-src="{{$root.imagePath('catalog', '200x200', $ctrl.item.id)}}"
|
||||
zoom-image="{{$root.imagePath('catalog', '1600x900', $ctrl.item.id)}}" on-error-src/>
|
||||
<vn-horizontal class="item-state">
|
||||
<vn-one>
|
||||
<p translate>Visible</p>
|
||||
|
|
|
@ -5,11 +5,11 @@
|
|||
<vn-card>
|
||||
<div class="image">
|
||||
<div ng-if="::item.hex != null" class="item-color-background">
|
||||
<div class="item-color" style="background-color: #{{::item.hex}}"></div>
|
||||
<div class="item-color" ng-style="{'background-color': '#' + item.hex}"></div>
|
||||
</div>
|
||||
<img
|
||||
ng-src="{{::$root.imagePath}}/catalog/200x200/{{::item.image}}"
|
||||
zoom-image="{{::$root.imagePath}}/catalog/1600x900/{{::item.image}}"
|
||||
ng-src="{{::$root.imagePath('catalog', '200x200', item.id)}}"
|
||||
zoom-image="{{::$root.imagePath('catalog', '1600x900', item.id)}}"
|
||||
on-error-src/>
|
||||
</div>
|
||||
<div class="description">
|
||||
|
|
|
@ -32,8 +32,8 @@
|
|||
<vn-tr ng-repeat="row in $ctrl.rows">
|
||||
<vn-td shrink>
|
||||
<img
|
||||
ng-src="{{::$root.imagePath}}/catalog/50x50/{{::row.item.image}}"
|
||||
zoom-image="{{::$root.imagePath}}/catalog/1600x900/{{::row.item.image}}"
|
||||
ng-src="{{::$root.imagePath('catalog', '50x50', row.item.id)}}"
|
||||
zoom-image="{{::$root.imagePath('catalog', '1600x900', row.item.id)}}"
|
||||
on-error-src/>
|
||||
</vn-td>
|
||||
<vn-td number>
|
||||
|
|
|
@ -1,23 +0,0 @@
|
|||
module.exports = Self => {
|
||||
Self.remoteMethodCtx('allowedContentTypes', {
|
||||
description: 'Returns a list of allowed contentTypes',
|
||||
accessType: 'READ',
|
||||
returns: {
|
||||
type: ['Object'],
|
||||
root: true
|
||||
},
|
||||
http: {
|
||||
path: `/allowedContentTypes`,
|
||||
verb: 'GET'
|
||||
}
|
||||
});
|
||||
|
||||
Self.allowedContentTypes = async() => {
|
||||
const storageConnector = Self.app.dataSources.storage.connector;
|
||||
const allowedContentTypes = storageConnector.allowedContentTypes;
|
||||
const modelAllowedContentTypes = Self.definition.settings.allowedContentTypes;
|
||||
|
||||
return modelAllowedContentTypes || allowedContentTypes;
|
||||
};
|
||||
};
|
||||
|
|
@ -1,4 +1,3 @@
|
|||
module.exports = Self => {
|
||||
require('../methods/ticket-dms/removeFile')(Self);
|
||||
require('../methods/ticket-dms/allowedContentTypes')(Self);
|
||||
};
|
||||
|
|
|
@ -25,7 +25,7 @@ class Controller extends Section {
|
|||
}
|
||||
|
||||
getAllowedContentTypes() {
|
||||
this.$http.get('ticketDms/allowedContentTypes').then(res => {
|
||||
this.$http.get('DmsContainers/allowedContentTypes').then(res => {
|
||||
const contentTypes = res.data.join(', ');
|
||||
this.allowedContentTypes = contentTypes;
|
||||
});
|
||||
|
|
|
@ -67,7 +67,7 @@ describe('Ticket', () => {
|
|||
describe('getAllowedContentTypes()', () => {
|
||||
it('should make an HTTP GET request to get the allowed content types', () => {
|
||||
const expectedResponse = ['image/png', 'image/jpg'];
|
||||
$httpBackend.expect('GET', `ticketDms/allowedContentTypes`).respond(expectedResponse);
|
||||
$httpBackend.expect('GET', `DmsContainers/allowedContentTypes`).respond(expectedResponse);
|
||||
controller.getAllowedContentTypes();
|
||||
$httpBackend.flush();
|
||||
|
||||
|
|
|
@ -16,7 +16,7 @@ class Controller extends Section {
|
|||
}
|
||||
|
||||
getAllowedContentTypes() {
|
||||
this.$http.get('ticketDms/allowedContentTypes').then(res => {
|
||||
this.$http.get('DmsContainers/allowedContentTypes').then(res => {
|
||||
const contentTypes = res.data.join(', ');
|
||||
this.allowedContentTypes = contentTypes;
|
||||
});
|
||||
|
|
|
@ -69,7 +69,7 @@ describe('Ticket', () => {
|
|||
describe('getAllowedContentTypes()', () => {
|
||||
it('should make an HTTP GET request to get the allowed content types', () => {
|
||||
const expectedResponse = ['image/png', 'image/jpg'];
|
||||
$httpBackend.expect('GET', `ticketDms/allowedContentTypes`).respond(expectedResponse);
|
||||
$httpBackend.expect('GET', `DmsContainers/allowedContentTypes`).respond(expectedResponse);
|
||||
controller.getAllowedContentTypes();
|
||||
$httpBackend.flush();
|
||||
|
||||
|
|
|
@ -19,8 +19,8 @@
|
|||
<vn-card>
|
||||
<div class="image">
|
||||
<img
|
||||
ng-src="{{::$root.imagePath}}/catalog/200x200/{{::sale.item.image}}"
|
||||
zoom-image="{{::$root.imagePath}}/catalog/1600x900/{{::sale.item.image}}"
|
||||
ng-src="{{::$root.imagePath('catalog', '200x200', sale.itemFk)}}"
|
||||
zoom-image="{{::$root.imagePath('catalog', '1600x900', sale.itemFk)}}"
|
||||
on-error-src/>
|
||||
</div>
|
||||
<div class="description">
|
||||
|
|
|
@ -93,8 +93,8 @@
|
|||
</vn-td>
|
||||
<vn-td shrink>
|
||||
<img
|
||||
ng-src="{{::$root.imagePath}}/catalog/50x50/{{sale.image}}"
|
||||
zoom-image="{{::$root.imagePath}}/catalog/1600x900/{{sale.image}}"
|
||||
ng-src="{{::$root.imagePath('catalog', '50x50', sale.itemFk)}}"
|
||||
zoom-image="{{::$root.imagePath('catalog', '1600x900', sale.itemFk)}}"
|
||||
on-error-src/>
|
||||
</vn-td>
|
||||
<vn-td number>
|
||||
|
|
|
@ -1,23 +0,0 @@
|
|||
module.exports = Self => {
|
||||
Self.remoteMethodCtx('allowedContentTypes', {
|
||||
description: 'Returns a list of allowed contentTypes',
|
||||
accessType: 'READ',
|
||||
returns: {
|
||||
type: ['Object'],
|
||||
root: true
|
||||
},
|
||||
http: {
|
||||
path: `/allowedContentTypes`,
|
||||
verb: 'GET'
|
||||
}
|
||||
});
|
||||
|
||||
Self.allowedContentTypes = async() => {
|
||||
const storageConnector = Self.app.dataSources.storage.connector;
|
||||
const allowedContentTypes = storageConnector.allowedContentTypes;
|
||||
const modelAllowedContentTypes = Self.definition.settings.allowedContentTypes;
|
||||
|
||||
return modelAllowedContentTypes || allowedContentTypes;
|
||||
};
|
||||
};
|
||||
|
|
@ -1,5 +1,4 @@
|
|||
module.exports = Self => {
|
||||
require('../methods/travel-thermograph/allowedContentTypes')(Self);
|
||||
require('../methods/travel-thermograph/getThermographTemperatures')(Self);
|
||||
};
|
||||
|
||||
|
|
|
@ -21,7 +21,7 @@ class Controller extends Section {
|
|||
}
|
||||
|
||||
getAllowedContentTypes() {
|
||||
this.$http.get('TravelThermographs/allowedContentTypes').then(res => {
|
||||
this.$http.get('DmsContainers/allowedContentTypes').then(res => {
|
||||
const contentTypes = res.data.join(', ');
|
||||
this.allowedContentTypes = contentTypes;
|
||||
});
|
||||
|
@ -46,7 +46,7 @@ class Controller extends Section {
|
|||
warehouseId: warehouseId,
|
||||
companyId: companyId,
|
||||
dmsTypeId: dmsTypeId,
|
||||
description: this.$t('FileDescription', {
|
||||
description: this.$t('TravelFileDescription', {
|
||||
travelId: this.travel.id
|
||||
}).toUpperCase()
|
||||
};
|
||||
|
|
|
@ -53,7 +53,7 @@ describe('Ticket', () => {
|
|||
describe('getAllowedContentTypes()', () => {
|
||||
it('should make an HTTP GET request to get the allowed content types', () => {
|
||||
const expectedResponse = ['application/pdf', 'image/png', 'image/jpg'];
|
||||
$httpBackend.expect('GET', `TravelThermographs/allowedContentTypes`).respond(expectedResponse);
|
||||
$httpBackend.expect('GET', `DmsContainers/allowedContentTypes`).respond(expectedResponse);
|
||||
controller.getAllowedContentTypes();
|
||||
$httpBackend.flush();
|
||||
|
||||
|
|
|
@ -17,7 +17,7 @@ class Controller extends Section {
|
|||
}
|
||||
|
||||
getAllowedContentTypes() {
|
||||
this.$http.get('TravelThermographs/allowedContentTypes').then(res => {
|
||||
this.$http.get('DmsContainers/allowedContentTypes').then(res => {
|
||||
const contentTypes = res.data.join(', ');
|
||||
this.allowedContentTypes = contentTypes;
|
||||
});
|
||||
|
|
|
@ -79,7 +79,7 @@ describe('Worker', () => {
|
|||
describe('getAllowedContentTypes()', () => {
|
||||
it('should make an HTTP GET request to get the allowed content types', () => {
|
||||
const expectedResponse = ['image/png', 'image/jpg'];
|
||||
$httpBackend.expect('GET', `TravelThermographs/allowedContentTypes`).respond(expectedResponse);
|
||||
$httpBackend.expect('GET', `DmsContainers/allowedContentTypes`).respond(expectedResponse);
|
||||
controller.getAllowedContentTypes();
|
||||
$httpBackend.flush();
|
||||
|
||||
|
|
|
@ -8,7 +8,7 @@ Upload file: Subir fichero
|
|||
Edit file: Editar fichero
|
||||
Upload: Subir
|
||||
File: Fichero
|
||||
FileDescription: Travel id {{travelId}}
|
||||
TravelFileDescription: Travel id {{travelId}}
|
||||
ContentTypesInfo: 'Tipos de archivo permitidos: {{allowedContentTypes}}'
|
||||
Are you sure you want to continue?: ¿Seguro que quieres continuar?
|
||||
Add thermograph: Añadir termógrafo
|
||||
|
|
|
@ -1,23 +0,0 @@
|
|||
module.exports = Self => {
|
||||
Self.remoteMethodCtx('allowedContentTypes', {
|
||||
description: 'Returns a list of allowed contentTypes',
|
||||
accessType: 'READ',
|
||||
returns: {
|
||||
type: ['Object'],
|
||||
root: true
|
||||
},
|
||||
http: {
|
||||
path: `/allowedContentTypes`,
|
||||
verb: 'GET'
|
||||
}
|
||||
});
|
||||
|
||||
Self.allowedContentTypes = async() => {
|
||||
const storageConnector = Self.app.dataSources.storage.connector;
|
||||
const allowedContentTypes = storageConnector.allowedContentTypes;
|
||||
const modelAllowedContentTypes = Self.definition.settings.allowedContentTypes;
|
||||
|
||||
return modelAllowedContentTypes || allowedContentTypes;
|
||||
};
|
||||
};
|
||||
|
|
@ -1,7 +1,6 @@
|
|||
module.exports = Self => {
|
||||
require('../methods/worker-dms/downloadFile')(Self);
|
||||
require('../methods/worker-dms/removeFile')(Self);
|
||||
require('../methods/worker-dms/allowedContentTypes')(Self);
|
||||
require('../methods/worker-dms/filter')(Self);
|
||||
|
||||
Self.isMine = async function(ctx, dmsId) {
|
||||
|
|
|
@ -1,6 +1,18 @@
|
|||
<vn-descriptor-content
|
||||
module="worker"
|
||||
description="$ctrl.worker.firstName +' '+ $ctrl.worker.lastName">
|
||||
<slot-before>
|
||||
<div class="photo" text-center>
|
||||
<img vn-id="photo"
|
||||
ng-src="{{$root.imagePath('user', '520x520', $ctrl.worker.id)}}"
|
||||
zoom-image="{{$root.imagePath('user', '1600x1600', $ctrl.worker.id)}}"
|
||||
on-error-src/>
|
||||
<vn-float-button ng-click="uploadPhoto.show('user', $ctrl.worker.id)"
|
||||
icon="edit"
|
||||
vn-visible-by="userPhotos">
|
||||
</vn-float-button>
|
||||
</div>
|
||||
</slot-before>
|
||||
<slot-body>
|
||||
<div class="attributes">
|
||||
<vn-label-value
|
||||
|
@ -43,3 +55,9 @@
|
|||
</div>
|
||||
</slot-body>
|
||||
</vn-descriptor-content>
|
||||
|
||||
<!-- Upload photo dialog -->
|
||||
<vn-upload-photo
|
||||
vn-id="uploadPhoto"
|
||||
on-response="$ctrl.onUploadResponse()">
|
||||
</vn-upload-photo>
|
|
@ -2,6 +2,11 @@ import ngModule from '../module';
|
|||
import Descriptor from 'salix/components/descriptor';
|
||||
|
||||
class Controller extends Descriptor {
|
||||
constructor($element, $, $rootScope) {
|
||||
super($element, $);
|
||||
this.$rootScope = $rootScope;
|
||||
}
|
||||
|
||||
get worker() {
|
||||
return this.entity;
|
||||
}
|
||||
|
@ -48,7 +53,20 @@ class Controller extends Descriptor {
|
|||
return this.getData(`Workers/${this.id}`, {filter})
|
||||
.then(res => this.entity = res.data);
|
||||
}
|
||||
|
||||
onUploadResponse() {
|
||||
const timestamp = new Date().getTime();
|
||||
const src = this.$rootScope.imagePath('user', '520x520', this.worker.id);
|
||||
const zoomSrc = this.$rootScope.imagePath('user', '1600x900', this.worker.id);
|
||||
const newSrc = `${src}&t=${timestamp}`;
|
||||
const newZoomSrc = `${zoomSrc}&t=${timestamp}`;
|
||||
|
||||
this.$.photo.setAttribute('src', newSrc);
|
||||
this.$.photo.setAttribute('zoom-image', newZoomSrc);
|
||||
}
|
||||
}
|
||||
|
||||
Controller.$inject = ['$element', '$scope', '$rootScope'];
|
||||
|
||||
ngModule.vnComponent('vnWorkerDescriptor', {
|
||||
template: require('./index.html'),
|
||||
|
|
|
@ -26,7 +26,7 @@ class Controller extends Section {
|
|||
}
|
||||
|
||||
getAllowedContentTypes() {
|
||||
this.$http.get('workerDms/allowedContentTypes').then(res => {
|
||||
this.$http.get('DmsContainers/allowedContentTypes').then(res => {
|
||||
const contentTypes = res.data.join(', ');
|
||||
this.allowedContentTypes = contentTypes;
|
||||
});
|
||||
|
|
|
@ -64,7 +64,7 @@ describe('Client', () => {
|
|||
describe('getAllowedContentTypes()', () => {
|
||||
it('should make an HTTP GET request to get the allowed content types', () => {
|
||||
const expectedResponse = ['image/png', 'image/jpg'];
|
||||
$httpBackend.expect('GET', `workerDms/allowedContentTypes`).respond(expectedResponse);
|
||||
$httpBackend.expect('GET', `DmsContainers/allowedContentTypes`).respond(expectedResponse);
|
||||
controller.getAllowedContentTypes();
|
||||
$httpBackend.flush();
|
||||
|
||||
|
|
|
@ -17,7 +17,7 @@ class Controller extends Section {
|
|||
}
|
||||
|
||||
getAllowedContentTypes() {
|
||||
this.$http.get('WorkerDms/allowedContentTypes').then(res => {
|
||||
this.$http.get('DmsContainers/allowedContentTypes').then(res => {
|
||||
const contentTypes = res.data.join(', ');
|
||||
this.allowedContentTypes = contentTypes;
|
||||
});
|
||||
|
|
|
@ -70,7 +70,7 @@ describe('Worker', () => {
|
|||
describe('getAllowedContentTypes()', () => {
|
||||
it('should make an HTTP GET request to get the allowed content types', () => {
|
||||
const expectedResponse = ['image/png', 'image/jpg'];
|
||||
$httpBackend.expect('GET', `WorkerDms/allowedContentTypes`).respond(expectedResponse);
|
||||
$httpBackend.expect('GET', `DmsContainers/allowedContentTypes`).respond(expectedResponse);
|
||||
controller.getAllowedContentTypes();
|
||||
$httpBackend.flush();
|
||||
|
||||
|
|
|
@ -60,7 +60,7 @@
|
|||
</vn-icon-button>
|
||||
</vn-td>
|
||||
<vn-td shrink>
|
||||
<vn-icon-button ui-sref="worker.card.edit({dmsId: {{::document.dmsFk}}})"
|
||||
<vn-icon-button ui-sref="worker.card.dms.edit({dmsId: {{::document.dmsFk}}})"
|
||||
icon="edit"
|
||||
title="{{'Edit file' | translate}}">
|
||||
</vn-icon-button>
|
||||
|
|
|
@ -43,6 +43,6 @@
|
|||
"pool": true
|
||||
},
|
||||
"storage": {
|
||||
"root": "./e2e/dms"
|
||||
"root": "./storage/dms"
|
||||
}
|
||||
}
|
Before Width: | Height: | Size: 13 KiB After Width: | Height: | Size: 13 KiB |
After Width: | Height: | Size: 13 KiB |
After Width: | Height: | Size: 23 KiB |
After Width: | Height: | Size: 30 KiB |
After Width: | Height: | Size: 1.4 MiB |
After Width: | Height: | Size: 1.4 MiB |
After Width: | Height: | Size: 86 KiB |
After Width: | Height: | Size: 25 KiB |
After Width: | Height: | Size: 81 KiB |
After Width: | Height: | Size: 74 KiB |
After Width: | Height: | Size: 33 KiB |
After Width: | Height: | Size: 98 KiB |