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>
This commit is contained in:
Carlos Jimenez Ruiz 2020-12-21 09:55:45 +00:00
commit ffc9b8dba9
161 changed files with 972 additions and 351 deletions

6
.gitignore vendored
View File

@ -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

View File

@ -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;
}
};

View File

@ -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;
}
};

View File

@ -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}"`];
};
};

View File

@ -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();
});

View File

@ -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`);
});
});
});

View File

@ -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;
}
};
};

View File

@ -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"
},

View File

@ -1,13 +0,0 @@
{
"name": "Container",
"base": "VnModel",
"idInjection": true,
"options": {
"validateUpsert": true
},
"properties": {},
"validations": [],
"relations": {},
"acls": [],
"methods": []
}

View File

@ -0,0 +1,10 @@
{
"name": "DmsContainer",
"base": "Container",
"acls": [{
"accessType": "READ",
"principalType": "ROLE",
"principalId": "$everyone",
"permission": "ALLOW"
}]
}

View File

@ -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}"`];
};

View 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;
}
};

View File

@ -48,6 +48,11 @@
"type": "belongsTo",
"model": "Role",
"foreignKey": "readRoleFk"
},
"writeRole": {
"type": "belongsTo",
"model": "Role",
"foreignKey": "writeRoleFk"
}
},
"acls": [

View File

@ -0,0 +1,10 @@
{
"name": "ImageContainer",
"base": "Container",
"acls": [{
"accessType": "READ",
"principalType": "ROLE",
"principalId": "$everyone",
"permission": "ALLOW"
}]
}

View File

@ -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,

View File

@ -0,0 +1,10 @@
{
"name": "TempContainer",
"base": "Container",
"acls": [{
"accessType": "READ",
"principalType": "ROLE",
"principalId": "$everyone",
"permission": "ALLOW"
}]
}

View File

@ -0,0 +1 @@
INSERT INTO salix.ACL (model, property, accessType, permission, principalType, principalId) VALUES ('Image', '*', 'WRITE', 'ALLOW', 'ROLE', 'employee')

View File

@ -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();

View File

@ -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');

View File

@ -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`
--

View File

@ -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

View File

@ -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(),

View File

@ -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

View File

@ -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;

View File

@ -13,3 +13,4 @@ import './section';
import './summary';
import './topbar/topbar';
import './user-popover';
import './upload-photo';

View File

@ -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 {

View File

@ -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>

View File

@ -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: '<'
}
});

View File

@ -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');
});
});
});
});

View File

@ -0,0 +1,3 @@
Upload new photo: Subir una nueva foto
Select an image: Selecciona una imagen
File name: Nombre del fichero

View File

@ -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
}
}
}

View File

@ -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>

View File

@ -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'];

View File

@ -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`);
});
});
});

View File

@ -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;

View File

@ -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;
};
};

View File

@ -0,0 +1,12 @@
{
"name": "Container",
"base": "VnModel",
"acls": [
{
"property": "status",
"principalType": "ROLE",
"principalId": "$everyone",
"permission": "ALLOW"
}
]
}

View File

@ -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"
}

View File

@ -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);
};
};

View File

@ -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"
]
}
}

View File

@ -49,5 +49,8 @@
},
"Application": {
"dataSource": "vn"
},
"Container": {
"dataSource": "vn"
}
}

View File

@ -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;

View File

@ -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;
};
};

View File

@ -1,4 +1,3 @@
module.exports = Self => {
require('../methods/client-dms/removeFile')(Self);
require('../methods/client-dms/allowedContentTypes')(Self);
};

View File

@ -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;
});

View File

@ -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();

View File

@ -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;
});

View File

@ -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();

View File

@ -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>

View File

@ -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);

View File

@ -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>

View File

@ -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>

View File

@ -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: '&'
}
});

View File

@ -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>

View File

@ -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>

View File

@ -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">

View File

@ -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>

View File

@ -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;
};
};

View File

@ -1,4 +1,3 @@
module.exports = Self => {
require('../methods/ticket-dms/removeFile')(Self);
require('../methods/ticket-dms/allowedContentTypes')(Self);
};

View File

@ -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;
});

View File

@ -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();

View File

@ -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;
});

View File

@ -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();

View File

@ -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">

View File

@ -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>

View File

@ -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;
};
};

View File

@ -1,5 +1,4 @@
module.exports = Self => {
require('../methods/travel-thermograph/allowedContentTypes')(Self);
require('../methods/travel-thermograph/getThermographTemperatures')(Self);
};

View File

@ -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()
};

View File

@ -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();

View File

@ -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;
});

View File

@ -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();

View File

@ -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

View File

@ -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;
};
};

View File

@ -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) {

View File

@ -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>

View File

@ -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'),

View File

@ -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;
});

View File

@ -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();

View File

@ -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;
});

View File

@ -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();

View File

@ -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>

View File

@ -43,6 +43,6 @@
"pool": true
},
"storage": {
"root": "./e2e/dms"
"root": "./storage/dms"
}
}

View File

Before

Width:  |  Height:  |  Size: 13 KiB

After

Width:  |  Height:  |  Size: 13 KiB

BIN
storage/dms/ecc/3.jpeg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 86 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 81 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 74 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 98 KiB

Some files were not shown because too many files have changed in this diff Show More