diff options
-rw-r--r-- | src/Collection/AbstractCollection.js | 18 | ||||
-rw-r--r-- | src/Configuration/Folder.json | 22 | ||||
-rw-r--r-- | src/Configuration/Password.json | 22 | ||||
-rw-r--r-- | src/Configuration/Tag.json | 10 | ||||
-rw-r--r-- | src/Converter/AbstractConverter.js | 31 | ||||
-rw-r--r-- | src/Model/AbstractModel.js | 13 | ||||
-rw-r--r-- | src/Model/AbstractRevisionModel.js | 28 | ||||
-rw-r--r-- | src/Model/Folder/EnhancedFolder.js | 36 | ||||
-rw-r--r-- | src/Model/Folder/Folder.js | 76 | ||||
-rw-r--r-- | src/Repositories/AbstractRepository.js | 136 | ||||
-rw-r--r-- | src/Repositories/FolderRepository.js | 16 | ||||
-rw-r--r-- | src/Repositories/PasswordRepository.js | 16 | ||||
-rw-r--r-- | src/Repositories/SettingRepository.js | 12 | ||||
-rw-r--r-- | src/Repositories/TagRepository.js | 16 | ||||
-rw-r--r-- | src/Services/ModelService.js | 255 |
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 |