UNPKG

@warp-works/core

Version:

Core library for WarpWorks

373 lines (328 loc) 13.3 kB
const _ = require('lodash'); // const debug = require('debug')('W2:models:domain'); const fs = require('fs'); const Promise = require('bluebird'); const Base = require('./base'); const Entity = require('./entity'); const WarpWorksError = require('./../error'); const utils = require('./../utils'); class DomainError extends WarpWorksError { } // TODO: Fix this hard-coded value. function isRolesRelationship(relationship) { return relationship.name === 'Roles'; } class Domain extends Base { constructor(warpworks, name, desc, recreate) { // Special case - the parent of domain is warpworks, which is not of type "Base" super("Domain", warpworks, 1, name, desc); this.id_counter = 1; this.entities = []; this.definitionOfMany = 100; // Create rootEntity entity: if (!recreate) { this.addNewEntity(this.name, "Root for domain " + this.name, null, false, true); } } save() { var fn = this.getWarpWorks().getDir("domains", `${this.name}.jsn`); fs.writeFileSync(fn, JSON.stringify(this, null, 2)); this.getWarpWorks().expireDomainCache(this.name); } createNewID() { if (this.id_counter < 2) { var max = 1; var all = this.getAllElements(); for (var i in all) { if (all[i].id > max) { max = all[i].id; } } this.id_counter = max + 1; } return this.id_counter++; } compareIDs(id1, id2) { return id1.toString() === id2.toString(); } validateModel() { let i; let vRes = ""; let wCount = 0; if (this.name === "New_Domain") { wCount++; vRes += "<br>[" + wCount + "]: <strong>" + this.name + "</strong> is not a unique name - please rename your Domain!"; } // All Relationships need targets for (i in this.entities) { for (let j in this.entities[i].relationships) { if (!this.entities[i].relationships[j].hasTargetEntity()) { wCount++; vRes += "<br>[" + wCount + "]: <strong>" + this.entities[i].name + "::" + this.entities[i].relationships[j].name + "</strong> does not have a target!"; } } } // All entities should either be abstract, root entity or aggregated by another entity (directly or through inheritance): for (i in this.entities) { if (!this.entities[i].isAbstract && !this.entities[i].canBeInstantiated()) { wCount++; vRes += "<br>[" + wCount + "]: <strong>" + this.entities[i].name + "</strong> can not be instantiated (solution: make it a RootEntity or child of another entity)"; } } // No Entity of type "Embedded" should aggregate an Entity of type "Document" for (i in this.entities) { for (let j in this.entities[i].relationships) { if (!this.entities[i].isDocument() && this.entities[i].relationships[j].isAggregation && this.entities[i].relationships[j].hasTargetEntity() && this.entities[i].relationships[j].getTargetEntity().isDocument()) { wCount++; vRes += "<br>[" + wCount + "]: <strong>" + this.entities[i].name + "::" + this.entities[i].relationships[j].name + "</strong>: Embedded entity '" + this.entities[i].name + "' can not aggregate document-type '" + this.entities[i].relationships[j].getTargetEntity().name + "'!"; } } } if (wCount === 0) { return null; } return wCount === 1 ? "<strong>1 Warning:</strong>" + vRes : "<strong>" + wCount + " Warnings:</strong>" + vRes; } getEntityByName(name) { const entities = this.getEntities().filter((entity) => entity.name === name); if (entities.length === 1) { return entities[0]; } throw new DomainError(`Cannot find entity with name='${name}'.`); } getEntityByPluralName(pluralName) { const entities = this.getEntities().filter((entity) => entity.namePlural === pluralName); if (entities.length === 1) { return entities[0]; } throw new DomainError(`Cannot find entity with name='${name}'.`); } getParentEntityByRelationship(parentRelnID) { return this.getEntities(/*true*/).find((entity) => { return entity.getRelationships(/*true*/).find((relationship) => { return relationship.id === parentRelnID; }); }); } /** * Retrieve the parent's entity based on the information of * `parentBaseClassName` from the instance data. */ getParentEntityByParentBaseClassName(instance) { if (instance.parentBaseClassName) { return this.getEntityByName(instance.parentBaseClassName); } return null; } getEntities(sortByInheritance) { if (!sortByInheritance) { return this.entities; } for (var i in this.entities) { var entity = this.entities[i]; entity.longName = entity.name; var tmpEntity = entity; while (tmpEntity.hasParentClass()) { tmpEntity = tmpEntity.getParentClass(); entity.longName = tmpEntity.name + ":" + entity.longName; } } return this.entities.sort(function(a, b) { if (a.longName < b.longName) { return -1; } if (a.longName > b.longName) { return 1; } return 0; }); } getRootEntities() { var allEntities = this.entities; var rootEntities = []; for (var i in allEntities) { if (allEntities[i].isRootEntity) { rootEntities.push(allEntities[i]); } } return allEntities; } getRootInstance() { var allEntities = this.entities; for (var i in allEntities) { if (allEntities[i].isRootInstance) { return allEntities[i]; } } throw new Error("Domain without root instance!"); } addEntity(newEntity) { return this.entities.push(newEntity); } addNewEntity(name, desc, parentClass, isRootEntity, isRootInstance) { var id = this.getDomain().createNewID(); var newEntity = new Entity(this, id, name, desc, parentClass, isRootEntity, isRootInstance); this.addEntity(newEntity); return newEntity; } createNewDefaultViews() { this.getEntities().forEach(function(elem) { elem.createNewDefaultViews(); }); } getAllElements(includeSelf) { // Returns an array containing all child elements; optional: also include self var r = []; if (includeSelf) { r = r.concat(this); } for (var i in this.getEntities()) { r = r.concat(this.getEntities()[i].getAllElements(true)); } return r; } /** * Gets the authenticated user or reject. * * @param {object} persistence - Persistence layer. * @param {string} username - Username to authenticate. * @param {string} password - Password to authenticate with. * @returns {Promise} - Resolve with user info if authentication succeed. * Reject if fails. */ authenticateUser(persistence, UserName, Password) { // TODO: Not hard-code entity names. return persistence.aggregate('Account', 'parentID', 'User', '_id', 'userInfo', { UserName, Password }) .then((users) => { if (users && users.length === 1) { return users[0].userInfo[0]; } throw new DomainError("Authentication failed."); }) .then((user) => { const userEntity = this.getEntityByName('User'); const relationships = userEntity.getRelationships().filter(isRolesRelationship); return Promise.reduce( relationships, (memo, relationship) => relationship.getDocuments(persistence, user) .then((roles) => memo.concat(roles)), [] ) .then((roles) => { return _.extend({}, _.pick(user, ['_id', 'type', 'Name']), { UserName, Roles: roles.map((role) => ({ type: role.type, Name: role.Name, Description: role.Description, id: role.id, label: role.Name })) }); }); }); } createTestDataForEntity(entityDef, relationship, parentInstanceID, parentBaseClass, path) { // TBD: Change algorithm to create as many entities as possible with one DB insert if (entityDef.isAbstract) { return; } // Don't create test instances for abstract entity types! // Create test document, including embedded entities path = parentInstanceID ? path : "/"; var testData = entityDef.createTestDocument(true, path); if (parentInstanceID) { testData.parentID = parentInstanceID; testData.parentRelnID = relationship.id; testData.parentRelnName = relationship.name; testData.parentBaseClassID = parentBaseClass.id; testData.parentBaseClassName = parentBaseClass.name; } else { testData.isRootInstance = true; testData.parentID = null; testData.parentRelID = null; testData.parentBaseClassID = null; testData.parentBaseClassName = null; } // TBD: TEST - var ObjectID = require("mongodb").ObjectID; // testData._id = new ObjectID().toString(); var domain = this; this.getWarpWorks().useDB(domain.name, function(db) { var collection = db.collection(entityDef.getBaseClass().name); collection.insertOne(testData, function(mongoErr, mongoRes) { if (mongoErr) { console.log("Error creating test data: " + mongoErr); } else { var aggs = entityDef.getAggregations(); if (aggs) { aggs.forEach(function(rel) { if (!rel.getTargetEntity().isDocument()) { return; } var avg = rel.targetAverage; if (isNaN(avg)) { console.log("WARNING: Incomplete Quantity Model - Average for relationship '" + rel.name + "' not defined! Assuming AVG=1"); avg = 1; } for (var i = 0; i < avg; i++) { var nextPath = path + rel.name + ':' + (i + 1) + '/'; domain.createTestDataForEntity(rel.getTargetEntity(), rel, mongoRes.ops[0]._id, entityDef.getBaseClass(), nextPath); } }); } } }); }); } processLocalTemplateFunctions(template) { var children = [["Entity", this.getEntities(true)]]; template = this.processTemplateWithChildElements(template, children); return super.processLocalTemplateFunctions(template); } toJSON() { return { name: this.name, desc: this.desc, type: this.type, id: this.idToJSON(), definitionOfMany: this.definitionOfMany, entities: utils.mapJSON(this.getEntities()) }; } toString() { var e; var es; var i; var s = "//\n// Domain '" + this.name + "'\n//\n"; s += "\n// Basic Entity Definitions:\n"; for (i in this.getEntities()) { e = this.getEntities()[i]; s += e.toString("properties") + "\n"; } s += "\n// Aggregation Hierarchy:\n"; for (i in this.getEntities()) { e = this.getEntities()[i]; es = e.toString("aggregations"); if (es.length > 0) { s += es + "\n"; } } s += "\n// Associations:\n"; for (i in this.getEntities()) { e = this.getEntities()[i]; es = e.toString("associations"); if (es.length > 0) { s += es + "\n"; } } return s; } getFileName(folder) { var f = folder ? folder + "\\" : ""; var fn = '.\\generated\\' + f + this.name + '.html'; return fn; } } module.exports = Domain;