Welcome to mirror list, hosted at ThFree Co, Russian Federation.

git.mdns.eu/nextcloud/passwords-client.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorMarius David Wieschollek <passwords.public@mdns.eu>2020-12-02 02:09:25 +0300
committerMarius David Wieschollek <passwords.public@mdns.eu>2020-12-02 02:09:25 +0300
commit277f5a1571a1a839d4f000a8ef65e35cba5b05ce (patch)
treefca78bd10526c8b1dc62d51252dd4ca0e26ac6e0 /src
parent16ac1f3d221b49b09001bda1d5f1194c5a7cafdd (diff)
Add model service and support for detail levels
Signed-off-by: Marius David Wieschollek <passwords.public@mdns.eu>
Diffstat (limited to 'src')
-rw-r--r--src/Collection/AbstractCollection.js18
-rw-r--r--src/Configuration/Folder.json22
-rw-r--r--src/Configuration/Password.json22
-rw-r--r--src/Configuration/Tag.json10
-rw-r--r--src/Converter/AbstractConverter.js31
-rw-r--r--src/Model/AbstractModel.js13
-rw-r--r--src/Model/AbstractRevisionModel.js28
-rw-r--r--src/Model/Folder/EnhancedFolder.js36
-rw-r--r--src/Model/Folder/Folder.js76
-rw-r--r--src/Repositories/AbstractRepository.js136
-rw-r--r--src/Repositories/FolderRepository.js16
-rw-r--r--src/Repositories/PasswordRepository.js16
-rw-r--r--src/Repositories/SettingRepository.js12
-rw-r--r--src/Repositories/TagRepository.js16
-rw-r--r--src/Services/ModelService.js255
15 files changed, 610 insertions, 97 deletions
diff --git a/src/Collection/AbstractCollection.js b/src/Collection/AbstractCollection.js
index 662abbc..41acbd5 100644
--- a/src/Collection/AbstractCollection.js
+++ b/src/Collection/AbstractCollection.js
@@ -15,8 +15,6 @@ export default class AbstractCollection {
* @param {AbstractModel} elements
*/
constructor(converter, ...elements) {
- this._index = 0;
-
/** @type AbstractConverter **/
this._converter = converter;
@@ -84,6 +82,15 @@ export default class AbstractCollection {
}
/**
+ *
+ * @param {(AbstractModel|AbstractModel[])} elements
+ */
+ replaceAll(...elements) {
+ /** @type AbstractModel[] **/
+ this._elements = this._getParamArray(elements);
+ }
+
+ /**
* @param {(String|Object|AbstractModel)} element
* @private
*/
@@ -144,18 +151,19 @@ export default class AbstractCollection {
}
[Symbol.iterator]() {
+ let index = 0;
return {
/**
* @return {{value: AbstractModel, done: boolean}|{done: boolean}}
*/
next: () => {
- if(this._index < this._elements.length) {
+ if(index < this._elements.length) {
return {
- value: this._elements[this._index++],
+ value: this._elements[index++],
done : false
};
} else {
- this._index = 0;
+ index = 0;
return {done: true};
}
}
diff --git a/src/Configuration/Folder.json b/src/Configuration/Folder.json
index ee37966..4c8b3e3 100644
--- a/src/Configuration/Folder.json
+++ b/src/Configuration/Folder.json
@@ -15,7 +15,12 @@
"writeable": true
},
"parent": {
- "type": "string",
+ "type": "Folder",
+ "match": "",
+ "writeable": true
+ },
+ "parentId": {
+ "type": "Folder",
"match": "",
"writeable": true
},
@@ -61,5 +66,20 @@
"updated": {
"type": "date",
"writeable": false
+ },
+ "revisions": {
+ "type": "FolderCollection",
+ "match": "",
+ "writeable": true
+ },
+ "passwords": {
+ "type": "PasswordCollection",
+ "match": "",
+ "writeable": true
+ },
+ "folders": {
+ "type": "FolderCollection",
+ "match": "",
+ "writeable": true
}
} \ No newline at end of file
diff --git a/src/Configuration/Password.json b/src/Configuration/Password.json
index fbec5a0..2c8a3d9 100644
--- a/src/Configuration/Password.json
+++ b/src/Configuration/Password.json
@@ -54,7 +54,7 @@
"match": ".+{0,32}",
"writeable": true
},
- "folder": {
+ "folderId": {
"type": "string",
"match": "",
"writeable": true
@@ -114,5 +114,25 @@
"updated": {
"type": "date",
"writeable": false
+ },
+ "folder": {
+ "type": "Folder",
+ "match": "",
+ "writeable": true
+ },
+ "revisions": {
+ "type": "PasswordCollection",
+ "match": "",
+ "writeable": true
+ },
+ "passwords": {
+ "type": "PasswordCollection",
+ "match": "",
+ "writeable": true
+ },
+ "tags": {
+ "type": "TagCollection",
+ "match": "",
+ "writeable": true
}
} \ No newline at end of file
diff --git a/src/Configuration/Tag.json b/src/Configuration/Tag.json
index 5d212ba..335870e 100644
--- a/src/Configuration/Tag.json
+++ b/src/Configuration/Tag.json
@@ -61,5 +61,15 @@
"updated": {
"type": "date",
"writeable": false
+ },
+ "revisions": {
+ "type": "TagCollection",
+ "match": "",
+ "writeable": true
+ },
+ "passwords": {
+ "type": "PasswordCollection",
+ "match": "",
+ "writeable": true
}
} \ No newline at end of file
diff --git a/src/Converter/AbstractConverter.js b/src/Converter/AbstractConverter.js
index e8cd2ab..b8270b1 100644
--- a/src/Converter/AbstractConverter.js
+++ b/src/Converter/AbstractConverter.js
@@ -44,7 +44,26 @@ export default class AbstractConverter {
clone.edited = new Date(clone.edited * 1e3);
}
- return this._api.getClass(`model.${this._type}`, clone, this._api);
+ return this.makeModel(clone);
+ }
+
+ /**
+ *
+ * @param properties
+ * @return {*}
+ */
+ fromModel(model) {
+ // @TODO actually clone models
+ return model;
+ }
+
+ /**
+ *
+ * @param properties
+ * @return {*}
+ */
+ makeModel(properties) {
+ return this._api.getClass(`model.${this._type}`, properties, this._api);
}
/**
@@ -53,10 +72,12 @@ export default class AbstractConverter {
* @api
*/
async fromEncryptedData(data) {
- if(data.cseType === 'CSEv1r1') {
- data = await this._api.getCseV1Encryption().decrypt(data, this._type);
- } else if(data.cseType !== 'none') {
- throw this._api.getClass('exception.encryption', data.id, data.cseType);
+ if(data.hasOwnProperty('cseType')) {
+ if(data.cseType === 'CSEv1r1') {
+ data = await this._api.getCseV1Encryption().decrypt(data, this._type);
+ } else if(data.cseType !== 'none') {
+ throw this._api.getClass('exception.encryption', data.id, data.cseType);
+ }
}
return this.fromObject(data);
diff --git a/src/Model/AbstractModel.js b/src/Model/AbstractModel.js
index bf69014..1178cd5 100644
--- a/src/Model/AbstractModel.js
+++ b/src/Model/AbstractModel.js
@@ -15,13 +15,22 @@ export default class AbstractModel {
}
/**
+ *
+ * @param {String} property
+ * @return {boolean}
+ */
+ hasProperty(property) {
+ return this._properties.hasOwnProperty(property);
+ }
+
+ /**
* @param {String} property
*
* @return {*}
* @api
*/
getProperty(property) {
- if(!this._properties.hasOwnProperty(property)) {
+ if(!this.hasProperty(property)) {
throw new UnknownPropertyError(`Read access to unknown property ${property}`);
}
@@ -40,7 +49,7 @@ export default class AbstractModel {
* @api
*/
setProperty(property, value) {
- if(!this._properties.hasOwnProperty(property)) {
+ if(!this.hasProperty(property)) {
throw new UnknownPropertyError(`Write access to unknown property ${property}`);
}
diff --git a/src/Model/AbstractRevisionModel.js b/src/Model/AbstractRevisionModel.js
index 93c455c..b565f18 100644
--- a/src/Model/AbstractRevisionModel.js
+++ b/src/Model/AbstractRevisionModel.js
@@ -2,6 +2,26 @@ import AbstractModel from './AbstractModel';
export default class AbstractRevisionModel extends AbstractModel {
+ constructor(properties, data) {
+ super(properties, data);
+ this._detailLevel = [];
+ }
+
+ /**
+ * @return {String[]}
+ */
+ getDetailLevel() {
+ return this._detailLevel;
+ }
+
+ /**
+ * @param {String[]} value
+ * @return {AbstractRevisionModel}
+ */
+ setDetailLevel(value) {
+ return this._detailLevel;
+ }
+
/**
* @return {String}
* @api
@@ -114,6 +134,14 @@ export default class AbstractRevisionModel extends AbstractModel {
* @return {Boolean}
* @api
*/
+ isHidden() {
+ return this.getProperty('hidden');
+ }
+
+ /**
+ * @return {Boolean}
+ * @api
+ */
getHidden() {
return this.getProperty('hidden');
}
diff --git a/src/Model/Folder/EnhancedFolder.js b/src/Model/Folder/EnhancedFolder.js
index 4198d61..fbec6ce 100644
--- a/src/Model/Folder/EnhancedFolder.js
+++ b/src/Model/Folder/EnhancedFolder.js
@@ -22,17 +22,49 @@ export default class EnhancedFolder extends Folder {
/**
*
- * @returns {Promise<Folder[]>}
+ * @returns {Promise<FolderCollection[]>}
*/
async fetchRevisions() {
+ if(this.getProperty('revisions') === undefined) {
+ await this._api.getFolderRepository().findById(this.getId(), 'revisions');
+ }
+ return this.getProperty('revisions');
}
/**
*
- * @returns {Promise<Password[]>}
+ * @returns {Promise<PasswordCollection[]>}
*/
async fetchPasswords() {
+ if(this.getProperty('passwords') === undefined) {
+ await this._api.getFolderRepository().findById(this.getId(), 'passwords');
+ }
+
+ return this.getProperty('passwords');
+ }
+
+ /**
+ *
+ * @returns {Promise<FolderCollection[]>}
+ */
+ async fetchFolders() {
+ if(this.getProperty('folders') === undefined) {
+ await this._api.getFolderRepository().findById(this.getId(), 'folders');
+ }
+
+ return this.getProperty('folders');
+ }
+
+ /**
+ *
+ * @returns {Promise<Folder[]>}
+ */
+ async fetchParent() {
+ if(this.getProperty('parent') === undefined) {
+ await this._api.getFolderRepository().findById(this.getId(), 'parent');
+ }
+ return this.getProperty('parent');
}
} \ No newline at end of file
diff --git a/src/Model/Folder/Folder.js b/src/Model/Folder/Folder.js
index cac6f17..46c4b76 100644
--- a/src/Model/Folder/Folder.js
+++ b/src/Model/Folder/Folder.js
@@ -30,16 +30,90 @@ export default class Folder extends AbstractRevisionModel {
/**
* @return {String}
*/
+ getParentId() {
+ if(this._properties.hasOwnProperty('parent')) {
+ return this.getParent().getId();
+ }
+
+ return this.getProperty('parentId');
+ }
+
+ /**
+ * @param {String} value
+ *
+ * @return {Folder}
+ */
+ setParentId(value) {
+ if(this._properties.hasOwnProperty('parent')) {
+ return this.setParent(null);
+ }
+
+ this.setProperty('parentId', value);
+
+ return this;
+ }
+
+ /**
+ * @return {Folder}
+ */
getParent() {
return this.getProperty('parent');
}
/**
- * @param {String} value
+ * @param {Folder} value
*
* @return {Folder}
*/
setParent(value) {
return this.setProperty('parent', value);
}
+
+ /**
+ * @return {(FolderCollection|null)}
+ */
+ getFolders() {
+ return this.getProperty('folders');
+ }
+
+ /**
+ * @param {(FolderCollection|null)} value
+ *
+ * @return {Folder}
+ */
+ setFolders(value) {
+ return this.setProperty('folders', value);
+ }
+
+ /**
+ * @return {(PasswordCollection|null)}
+ */
+ getPasswords() {
+ return this.getProperty('passwords');
+ }
+
+ /**
+ * @param {(PasswordCollection|null)} value
+ *
+ * @return {Folder}
+ */
+ setPasswords(value) {
+ return this.setProperty('passwords', value);
+ }
+
+ /**
+ * @return {(FolderCollection|null)}
+ */
+ getRevisions() {
+ return this.getProperty('revisions');
+ }
+
+ /**
+ * @param {(FolderCollection|null)} value
+ *
+ * @return {Folder}
+ */
+ setRevisions(value) {
+ return this.setProperty('revisions', value);
+ }
} \ No newline at end of file
diff --git a/src/Repositories/AbstractRepository.js b/src/Repositories/AbstractRepository.js
index cf5593e..ee2cf29 100644
--- a/src/Repositories/AbstractRepository.js
+++ b/src/Repositories/AbstractRepository.js
@@ -1,25 +1,44 @@
export default class AbstractRepository {
/**
+ * @return {String[]}
+ * @constructor
+ */
+ get AVAILABLE_DETAIL_LEVELS() {
+ return ['id', 'model'];
+ }
+
+ /**
+ * @return {String[]}
+ * @constructor
+ */
+ get DEFAULT_DETAIL_LEVEL() {
+ return ['model'];
+ }
+
+ get TYPE() {
+ return 'abstract';
+ }
+
+ /**
*
* @param {Api} api
- * @param {String} type
*/
- constructor(api, type) {
+ constructor(api) {
this._api = api;
- /** @type Cache **/
- this._cache = api.getInstance('cache.cache');
- /** @type AbstractConverter **/
- this._converter = api.getInstance(`converter.${type}`);
- this._type = type;
+ /** @type {ModelService} **/
+ this._modelService = api.getInstance('service.model');
+ /** @type {AbstractConverter} **/
+ this._converter = api.getInstance(`converter.${this.TYPE}`);
}
/**
*
+ * @deprecated
* @return {AbstractRepository}
*/
clearCache() {
- this._cache.clear();
+ console.trace('AbstractRepository.clearCache() is deprecated');
return this;
}
@@ -34,11 +53,12 @@ export default class AbstractRepository {
// @TODO: Custom error here
throw new Error('Can not create object with id');
}
+ this._modelService.addModel(this.TYPE, model);
let data = await this._converter.toApiObject(model),
request = this._api.getRequest()
- .setPath(`1.0/${this._type}/create`)
- .setData(data);
+ .setPath(`1.0/${this.TYPE}/create`)
+ .setData(data);
try {
let response = await request.send();
@@ -46,9 +66,6 @@ export default class AbstractRepository {
model.setRevision(response.getData().revision);
model.setCreated(new Date());
model.setUpdated(new Date());
-
- this._cache.set(model.getId(), true);
- this._cache.set(`${model.getId()}.model`, model);
} catch(e) {
console.error(e);
throw e;
@@ -70,16 +87,13 @@ export default class AbstractRepository {
let data = this._converter.toApiObject(model),
request = this._api.getRequest()
- .setPath(`1.0/${this._type}/update`)
- .setData(data);
+ .setPath(`1.0/${this.TYPE}/update`)
+ .setData(data);
try {
let response = await request.send();
model.setRevision(response.getData().revision);
model.setUpdated(new Date());
-
- this._cache.set(model.getId(), true);
- this._cache.set(`${model.getId()}.model`, model);
} catch(e) {
console.error(e);
throw e;
@@ -95,20 +109,16 @@ export default class AbstractRepository {
*/
async delete(model) {
let request = this._api.getRequest()
- .setPath(`1.0/${this._type}/delete`)
- .setData({id: model.getId(), revision: model.getRevision()});
+ .setPath(`1.0/${this.TYPE}/delete`)
+ .setData({id: model.getId(), revision: model.getRevision()});
try {
let response = await request.send();
model.setRevision(response.getData().revision);
model.setUpdated(new Date());
- this._cache.set(`${model.getId()}.model`, model);
if(!model.isTrashed()) {
model.setTrashed(true);
- this._cache.set(model.getId(), true);
- } else {
- this._cache.remove(model.getId());
}
} catch(e) {
console.error(e);
@@ -127,16 +137,14 @@ export default class AbstractRepository {
if(!model.getTrashed()) return model;
let request = this._api.getRequest()
- .setPath(`1.0/${this._type}/restore`)
- .setData({id: model.getId(), revision: model.getRevision()});
+ .setPath(`1.0/${this.TYPE}/restore`)
+ .setData({id: model.getId(), revision: model.getRevision()});
try {
let response = await request.send();
model.setRevision(response.getData().revision);
model.setUpdated(new Date());
model.setTrashed(false);
- this._cache.set(model.getId(), true);
- this._cache.set(`${model.getId()}.model`, model);
} catch(e) {
console.error(e);
throw e;
@@ -147,56 +155,54 @@ export default class AbstractRepository {
/**
*
- * @param id
+ * @param {String} id
+ * @param {(String|String[]|null)} detailLevel
* @returns {Promise<AbstractRevisionModel>}
*/
- async findById(id) {
- if(this._cache.has(id)) {
- return this._cache.get(`${id}.model`);
- }
+ async findById(id, detailLevel = null) {
+ detailLevel = this._getDetailLevel(detailLevel);
- let request = this._api.getRequest()
- .setPath(`1.0/${this._type}/show`)
- .setData({id}),
+ let details = detailLevel.join('+'),
+ request = this._api.getRequest()
+ .setPath(`1.0/${this.TYPE}/show`)
+ .setData({id, details}),
response = await request.send();
- return await this._dataToModel(response.getData());
+ return await this._dataToModel(response.getData(), detailLevel);
}
/**
* @api
+ * @param {(String|String[]|null)} detailLevel
* @returns {Promise<AbstractRevisionModel[]>}
*/
- async findAll() {
- if(this._cache.has(`${this._type}.collection`)) {
- return this._cache.getByType(`${this._type}.collection`);
- }
-
- let request = this._api.getRequest()
- .setPath(`1.0/${this._type}/list`),
+ async findAll(detailLevel = null) {
+ detailLevel = this._getDetailLevel(detailLevel);
+ let details = detailLevel.join('+'),
+ request = this._api.getRequest()
+ .setData({details})
+ .setPath(`1.0/${this.TYPE}/list`),
response = await request.send(),
data = response.getData(),
- models = await this._dataToModels(data);
+ models = await this._dataToModels(data, detailLevel);
- let collection = this._api.getClass(`collection.${this._type}`, models);
- this._cache.set(`${this._type}.collection`, true);
-
- return collection;
+ return this._api.getClass(`collection.${this.TYPE}`, models);
}
/**
*
* @param {Object[]} data
+ * @param {(String[])} detailLevel
* @return {Promise<AbstractRevisionModel[]>}
* @private
*/
- async _dataToModels(data) {
+ async _dataToModels(data, detailLevel) {
let promises = [],
models = [];
for(let element of data) {
promises.push(new Promise((resolve) => {
- this._dataToModel(element)
+ this._dataToModel(element, detailLevel)
.then((model) => {
models.push(model);
resolve();
@@ -215,14 +221,32 @@ export default class AbstractRepository {
/**
*
* @param {Object} data
+ * @param {String[]} detailLevel
* @returns {Promise<AbstractRevisionModel>}
* @private
*/
- async _dataToModel(data) {
- let model = await this._converter.fromEncryptedData(data);
- this._cache.set(model.getId(), true);
- this._cache.set(`${model.getId()}.model`, model);
+ async _dataToModel(data, detailLevel) {
+ return await this._modelService.makeFromApiData(this.TYPE, data, detailLevel);
+ }
- return model;
+ /**
+ * @param {(String[]|String|null)} detailLevel
+ * @return {String[]}
+ * @private
+ */
+ _getDetailLevel(detailLevel) {
+ if(typeof detailLevel === 'string') {
+ detailLevel = detailLevel.trim().split('+');
+ }
+ if(detailLevel===null || detailLevel.length === 0) return this.DEFAULT_DETAIL_LEVEL;
+
+ for(let level of detailLevel) {
+ if(this.AVAILABLE_DETAIL_LEVELS.indexOf(level) === -1) {
+ // @TODO custom error
+ throw new Error('Unknown detail level '+level);
+ }
+ }
+
+ return detailLevel;
}
} \ No newline at end of file
diff --git a/src/Repositories/FolderRepository.js b/src/Repositories/FolderRepository.js
index eda78c8..1830293 100644
--- a/src/Repositories/FolderRepository.js
+++ b/src/Repositories/FolderRepository.js
@@ -3,10 +3,18 @@ import AbstractRepository from './AbstractRepository';
export default class FolderRepository extends AbstractRepository {
/**
- *
- * @param {Api} api
+ * @return {String[]}
+ * @constructor
*/
- constructor(api) {
- super(api, 'folder');
+ get AVAILABLE_DETAIL_LEVELS() {
+ return ['id', 'model', 'revisions', 'parent', 'passwords', 'folders'];
+ }
+
+ /**
+ * @returns {String}
+ * @constructor
+ */
+ get TYPE() {
+ return 'folder';
}
} \ No newline at end of file
diff --git a/src/Repositories/PasswordRepository.js b/src/Repositories/PasswordRepository.js
index df72fc5..e160cba 100644
--- a/src/Repositories/PasswordRepository.js
+++ b/src/Repositories/PasswordRepository.js
@@ -3,10 +3,18 @@ import AbstractRepository from './AbstractRepository';
export default class PasswordRepository extends AbstractRepository {
/**
- *
- * @param {Api} api
+ * @return {String[]}
+ * @constructor
*/
- constructor(api) {
- super(api, 'password');
+ get AVAILABLE_DETAIL_LEVELS() {
+ return ['id', 'model', 'revisions', 'folder', 'tags'];
+ }
+
+ /**
+ * @returns {String}
+ * @constructor
+ */
+ get TYPE() {
+ return 'password';
}
} \ No newline at end of file
diff --git a/src/Repositories/SettingRepository.js b/src/Repositories/SettingRepository.js
index f1bd357..c8860c0 100644
--- a/src/Repositories/SettingRepository.js
+++ b/src/Repositories/SettingRepository.js
@@ -6,22 +6,10 @@ export default class SettingRepository {
*/
constructor(api) {
this._api = api;
- /** @type Cache **/
- this._cache = api.getInstance('cache.cache');
/** @type SettingConverter **/
this._converter = api.getInstance(`converter.setting`);
}
- /**
- *
- * @return {AbstractRepository}
- */
- clearCache() {
- this._cache.clear();
-
- return this;
- }
-
async findAll() {
let response = await this._api.getRequest()
.setPath(`1.0/settings/list`)
diff --git a/src/Repositories/TagRepository.js b/src/Repositories/TagRepository.js
index 51df06f..8f7603e 100644
--- a/src/Repositories/TagRepository.js
+++ b/src/Repositories/TagRepository.js
@@ -3,10 +3,18 @@ import AbstractRepository from './AbstractRepository';
export default class TagRepository extends AbstractRepository {
/**
- *
- * @param {Api} api
+ * @return {String[]}
+ * @constructor
*/
- constructor(api) {
- super(api, 'tag');
+ get AVAILABLE_DETAIL_LEVELS() {
+ return ['id', 'model', 'revisions', 'passwords'];
+ }
+
+ /**
+ * @returns {String}
+ * @constructor
+ */
+ get TYPE() {
+ return 'tag';
}
} \ No newline at end of file
diff --git a/src/Services/ModelService.js b/src/Services/ModelService.js
new file mode 100644
index 0000000..e06bcb9
--- /dev/null
+++ b/src/Services/ModelService.js
@@ -0,0 +1,255 @@
+export default class ModelService {
+
+ /**
+ *
+ * @param {BaseClassLoader} cl
+ */
+ constructor(cl) {
+ this._cl = cl;
+ this._cache = /** @type {Cache} **/ cl.getInstance('cache.cache');
+ }
+
+ hasModel(type, id) {
+ return this._cache.has(`${type}.${id}`);
+ }
+
+ getModel(type, id) {
+ if(this.hasModel(type, id)) {
+ return this._cache.get(`${type}.${id}`);
+ }
+ return null;
+ }
+
+ addModel(type, model) {
+ this._cache.set(`${type}.${model.getId()}`, model);
+ // @TODO update related models
+ }
+
+ /**
+ *
+ * @param type
+ * @param data
+ * @param detailLevel
+ * @returns {Promise<AbstractRevisionModel|Tag>|Promise<AbstractRevisionModel|Password>|Folder}
+ */
+ makeFromApiData(type, data, detailLevel = []) {
+ if(type === 'password') return this.makePasswordFromApiData(data, detailLevel)
+ if(type === 'folder') return this.makeFolderFromApiData(data, detailLevel)
+ if(type === 'tag') return this.makeTagFromApiData(data, detailLevel)
+ }
+
+ async makePasswordCollectionFromApiData(data, detailLevel = []) {
+ let passwords = [];
+ for(let passwordData of data) {
+ passwords.push(await this.makePasswordFromApiData(passwordData, detailLevel));
+ }
+
+ return this._cl.getClass('collection.password', passwords);
+ }
+
+ async makePasswordFromApiData(data, detailLevel = []) {
+ let converter = /** @type {PasswordConverter} **/ this._cl.getInstance('converter.password'),
+ newModel = await converter.fromEncryptedData(data);
+ newModel.setDetailLevel(detailLevel);
+
+ let revisionCacheKey = `password.${newModel.getId()}.${newModel.getRevision()}`;
+ if(!this._cache.has(revisionCacheKey)) {
+ let revisionModel = converter.fromModel(newModel);
+ this._cache.set(revisionCacheKey, revisionModel);
+ }
+ if(detailLevel.indexOf('folder') !== -1) {
+ newModel.setParent(await this.makeFolderFromApiData(data.folder, 'model'));
+ }
+ await this._makeCollectionFromData(newModel, 'password', 'tag', detailLevel, data);
+ await this._makeCollectionFromData(newModel, 'password', 'revision', detailLevel, data);
+
+ let cacheKey = `password.${newModel.getId()}`;
+ if(!this._cache.has(cacheKey)) {
+ this._cache.set(cacheKey, newModel);
+ return newModel;
+ } else {
+ return this._mergePasswordModel(this._cache.get(cacheKey), newModel);
+ }
+ }
+
+ async makeFolderCollectionFromApiData(data, detailLevel = []) {
+ let folders = [];
+ for(let folderData of data) {
+ folders.push(await this.makeFolderFromApiData(folderData, detailLevel));
+ }
+
+ return this._cl.getClass('collection.folder', folders);
+ }
+
+ /**
+ *
+ * @param data
+ * @param detailLevel
+ * @return {Folder}
+ */
+ async makeFolderFromApiData(data, detailLevel = []) {
+ let converter = /** @type {FolderConverter} **/ this._cl.getInstance('converter.folder'),
+ newModel = await converter.fromEncryptedData(data);
+ newModel.setDetailLevel(detailLevel);
+
+ let revisionCacheKey = `folder.${newModel.getId()}.${newModel.getRevision()}`;
+ if(!this._cache.has(revisionCacheKey)) {
+ let revisionModel = converter.fromModel(newModel);
+ this._cache.set(revisionCacheKey, revisionModel);
+ }
+ await this._makeCollectionFromData(newModel, 'folder', 'revision', detailLevel, data);
+ await this._makeCollectionFromData(newModel, 'folder', 'folder', detailLevel, data);
+ await this._makeCollectionFromData(newModel, 'folder', 'password', detailLevel, data);
+
+ if(detailLevel.indexOf('parent') !== -1) {
+ newModel.setParent(await this.makeFolderFromApiData(data.parent, 'model'));
+ }
+
+ let cacheKey = `folder.${newModel.getId()}`;
+ if(!this._cache.has(cacheKey)) {
+ this._cache.set(cacheKey, newModel);
+ return newModel;
+ } else {
+ return this._mergeFolderModel(this._cache.get(cacheKey), newModel);
+ }
+ }
+
+ async makeTagCollectionFromApiData(data, detailLevel = []) {
+ let tags = [];
+ for(let tagData of data) {
+ tags.push(await this.makeTagFromApiData(tagData, detailLevel));
+ }
+
+ return this._cl.getClass('collection.tag', tags);
+ }
+
+ async makeTagFromApiData(data, detailLevel = []) {
+ let converter = /** @type {TagConverter} **/ this._cl.getInstance('converter.tag'),
+ newModel = await converter.fromEncryptedData(data);
+ newModel.setDetailLevel(detailLevel);
+
+ let revisionCacheKey = `tag.${newModel.getId()}.${newModel.getRevision()}`;
+ if(!this._cache.has(revisionCacheKey)) {
+ let revisionModel = converter.fromModel(newModel);
+ this._cache.set(revisionCacheKey, revisionModel);
+ }
+ await this._makeCollectionFromData(newModel, 'tag', 'password', detailLevel, data);
+ await this._makeCollectionFromData(newModel, 'tag', 'revision', detailLevel, data);
+
+ let cacheKey = `tag.${newModel.getId()}`;
+ if(!this._cache.has(cacheKey)) {
+ this._cache.set(cacheKey, newModel);
+ return newModel;
+ } else {
+ return this._mergeTagModel(this._cache.get(cacheKey), newModel);
+ }
+ }
+
+ async _makeCollectionFromData(model, baseType, type, detailLevel, data) {
+ let property = `${type}s`;
+ if(detailLevel.indexOf(property) !== -1) {
+ if(!data.hasOwnProperty(property)) {
+ // @TODO data missing = potential bug?
+ }
+ let method = type === 'revision' ?
+ `_make${baseType[0].toUpperCase()}${baseType.substr(1)}Revision`:
+ `make${type[0].toUpperCase()}${type.substr(1)}FromApiData`,
+ models = [];
+ for(let modelData of data[property]) {
+ models.push(await this[method](modelData, 'model'));
+ }
+
+ let cacheKey = `${baseType}.${model.getId()}.${property}`;
+ if(this._cache.has(cacheKey)) {
+ let collection = /** @type {AbstractCollection} **/ this._cache.has(cacheKey);
+ collection.replaceAll(models);
+ model.setProperty(property, collection);
+ } else {
+ let collection = this._cl.getClass(type === 'revision' ? `collection.${baseType}`:`collection.${type}`, models);
+ this._cache.set(cacheKey, collection);
+ model.setProperty(property, collection);
+ }
+ }
+ }
+
+ /**
+ *
+ * @param {Password} model
+ * @param {Password} newModel
+ * @returns {Password}
+ * @private
+ */
+ _mergePasswordModel(model, newModel) {
+ if(newModel.getProperty('revisions') !== undefined) {
+ model.setProperty('revisions', newModel.getProperty('revisions'));
+ }
+ if(newModel.getProperty('tags') !== undefined) {
+ model.setProperty('tags', newModel.getProperty('tags'));
+ }
+ if(newModel.getProperty('folder') !== undefined) {
+ model.setProperty('folder', newModel.getProperty('folder'));
+ }
+ this._mergeModelDetailLevel(model, newModel.getDetailLevel());
+
+ return newModel;
+ }
+
+ /**
+ *
+ * @param {Folder} model
+ * @param {Folder} newModel
+ * @returns {Folder}
+ * @private
+ */
+ _mergeFolderModel(model, newModel) {
+ if(newModel.getProperty('revisions') !== undefined) {
+ model.setProperty('revisions', newModel.getProperty('revisions'));
+ }
+ if(newModel.getProperty('passwords') !== undefined) {
+ model.setProperty('passwords', newModel.getProperty('passwords'));
+ }
+ if(newModel.getProperty('folders') !== undefined) {
+ model.setProperty('folders', newModel.getProperty('folders'));
+ }
+ if(newModel.getProperty('parent') !== undefined) {
+ model.setProperty('parent', newModel.getProperty('parent'));
+ }
+ this._mergeModelDetailLevel(model, newModel.getDetailLevel());
+
+ return newModel;
+ }
+
+ /**
+ *
+ * @param {Tag} model
+ * @param {Tag} newModel
+ * @returns {Tag}
+ * @private
+ */
+ _mergeTagModel(model, newModel) {
+ if(newModel.getProperty('revisions') !== undefined) {
+ model.setProperty('revisions', newModel.getProperty('revisions'));
+ }
+ if(newModel.getProperty('passwords') !== undefined) {
+ model.setProperty('passwords', newModel.getProperty('passwords'));
+ }
+ this._mergeModelDetailLevel(model, newModel.getDetailLevel());
+
+ return newModel;
+ }
+
+ /**
+ * @param {AbstractRevisionModel} model
+ * @param {String[]} detailLevel
+ * @private
+ */
+ _mergeModelDetailLevel(model, detailLevel = []) {
+ let modelDetailLevel = model.getDetailLevel();
+ for(let level of detailLevel) {
+ if(modelDetailLevel.indexOf(level) === -1) {
+ modelDetailLevel.push(level);
+ }
+ }
+ model.setDetailLevel(modelDetailLevel);
+ }
+} \ No newline at end of file